1 /*
2 * Copyright (c) 2018 Google LLC.
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #define DT_DRV_COMPAT apa_apa102
8
9 #include <errno.h>
10 #include <zephyr/drivers/led_strip.h>
11 #include <zephyr/drivers/spi.h>
12 #include <zephyr/drivers/gpio.h>
13 #include <zephyr/sys/util.h>
14
15 struct apa102_config {
16 struct spi_dt_spec bus;
17 size_t length;
18 uint8_t *const end_frame;
19 const size_t end_frame_size;
20 };
21
apa102_update(const struct device * dev,void * buf,size_t size)22 static int apa102_update(const struct device *dev, void *buf, size_t size)
23 {
24 const struct apa102_config *config = dev->config;
25 static const uint8_t zeros[] = { 0, 0, 0, 0 };
26
27 const struct spi_buf tx_bufs[] = {
28 {
29 /* Start frame: at least 32 zeros */
30 .buf = (uint8_t *)zeros,
31 .len = sizeof(zeros),
32 },
33 {
34 /* LED data itself */
35 .buf = buf,
36 .len = size,
37 },
38 {
39 /* End frame: at least 32 ones to clock the
40 * remaining bits to the LEDs at the end of
41 * the strip.
42 */
43 .buf = (uint8_t *)config->end_frame,
44 .len = config->end_frame_size,
45 },
46 };
47 const struct spi_buf_set tx = {
48 .buffers = tx_bufs,
49 .count = ARRAY_SIZE(tx_bufs)
50 };
51
52 return spi_write_dt(&config->bus, &tx);
53 }
54
apa102_update_rgb(const struct device * dev,struct led_rgb * pixels,size_t count)55 static int apa102_update_rgb(const struct device *dev, struct led_rgb *pixels,
56 size_t count)
57 {
58 uint8_t *p = (uint8_t *)pixels;
59 size_t i;
60 /* SOF (3 bits) followed by the 0 to 31 global dimming level */
61 uint8_t prefix = 0xE0 | 31;
62
63 /* Rewrite to the on-wire format */
64 for (i = 0; i < count; i++) {
65 uint8_t r = pixels[i].r;
66 uint8_t g = pixels[i].g;
67 uint8_t b = pixels[i].b;
68
69 *p++ = prefix;
70 *p++ = b;
71 *p++ = g;
72 *p++ = r;
73 }
74
75 BUILD_ASSERT(sizeof(struct led_rgb) == 4);
76 return apa102_update(dev, pixels, sizeof(struct led_rgb) * count);
77 }
78
apa102_length(const struct device * dev)79 static size_t apa102_length(const struct device *dev)
80 {
81 const struct apa102_config *config = dev->config;
82
83 return config->length;
84 }
85
apa102_init(const struct device * dev)86 static int apa102_init(const struct device *dev)
87 {
88 const struct apa102_config *config = dev->config;
89
90 if (!spi_is_ready_dt(&config->bus)) {
91 return -ENODEV;
92 }
93
94 memset(config->end_frame, 0xFF, config->end_frame_size);
95
96 return 0;
97 }
98
99 static DEVICE_API(led_strip, apa102_api) = {
100 .update_rgb = apa102_update_rgb,
101 .length = apa102_length,
102 };
103
104 /*
105 * The "End frame" is statically allocated, as a sequence of 0xFF bytes
106 * The only function of the “End frame” is to supply more clock pulses
107 * to the string until the data has permeated to the last LED. The
108 * number of clock pulses required is exactly half the total number
109 * of LEDs in the string. See below `end_frame`.
110 */
111 #define APA102_DEVICE(idx) \
112 static uint8_t apa102_end_frame_##idx \
113 [(DT_INST_PROP(idx, chain_length) / \
114 sizeof(struct led_rgb) / 2) + 1]; \
115 static const struct apa102_config apa102_##idx##_config = { \
116 .bus = SPI_DT_SPEC_INST_GET( \
117 idx, \
118 SPI_OP_MODE_MASTER | SPI_TRANSFER_MSB | SPI_WORD_SET(8), \
119 0), \
120 .length = DT_INST_PROP(idx, chain_length), \
121 .end_frame = apa102_end_frame_##idx, \
122 .end_frame_size = (DT_INST_PROP(idx, chain_length) / \
123 sizeof(struct led_rgb) / 2) + 1, \
124 }; \
125 \
126 DEVICE_DT_INST_DEFINE(idx, \
127 apa102_init, \
128 NULL, \
129 NULL, \
130 &apa102_##idx##_config, \
131 POST_KERNEL, \
132 CONFIG_LED_STRIP_INIT_PRIORITY, \
133 &apa102_api);
134
135 DT_INST_FOREACH_STATUS_OKAY(APA102_DEVICE)
136