1 /*
2 * Copyright (c) 2017 Linaro Limited
3 * Copyright (c) 2019, Nordic Semiconductor ASA
4 * Copyright (c) 2021 Seagate Technology LLC
5 *
6 * SPDX-License-Identifier: Apache-2.0
7 */
8
9 #define DT_DRV_COMPAT worldsemi_ws2812_spi
10
11 #include <zephyr/drivers/led_strip.h>
12
13 #include <string.h>
14
15 #define LOG_LEVEL CONFIG_LED_STRIP_LOG_LEVEL
16 #include <zephyr/logging/log.h>
17 LOG_MODULE_REGISTER(ws2812_spi);
18
19 #include <zephyr/kernel.h>
20 #include <zephyr/device.h>
21 #include <zephyr/drivers/spi.h>
22 #include <zephyr/sys/math_extras.h>
23 #include <zephyr/sys/util.h>
24 #include <zephyr/dt-bindings/led/led.h>
25
26 /* spi-one-frame and spi-zero-frame in DT are for 8-bit frames. */
27 #define SPI_FRAME_BITS 8
28
29 /*
30 * SPI master configuration:
31 *
32 * - mode 0 (the default), 8 bit, MSB first (arbitrary), one-line SPI
33 * - no shenanigans (don't hold CS, don't hold the device lock, this
34 * isn't an EEPROM)
35 */
36 #define SPI_OPER(idx) (SPI_OP_MODE_MASTER | SPI_TRANSFER_MSB | \
37 SPI_WORD_SET(SPI_FRAME_BITS))
38
39 struct ws2812_spi_cfg {
40 struct spi_dt_spec bus;
41 uint8_t *px_buf;
42 uint8_t one_frame;
43 uint8_t zero_frame;
44 uint8_t num_colors;
45 const uint8_t *color_mapping;
46 size_t length;
47 uint16_t reset_delay;
48 };
49
dev_cfg(const struct device * dev)50 static const struct ws2812_spi_cfg *dev_cfg(const struct device *dev)
51 {
52 return dev->config;
53 }
54
55 /*
56 * Serialize an 8-bit color channel value into an equivalent sequence
57 * of SPI frames, MSbit first, where a one bit becomes SPI frame
58 * one_frame, and zero bit becomes zero_frame.
59 */
ws2812_spi_ser(uint8_t buf[8],uint8_t color,const uint8_t one_frame,const uint8_t zero_frame)60 static inline void ws2812_spi_ser(uint8_t buf[8], uint8_t color,
61 const uint8_t one_frame, const uint8_t zero_frame)
62 {
63 int i;
64
65 for (i = 0; i < 8; i++) {
66 buf[i] = color & BIT(7 - i) ? one_frame : zero_frame;
67 }
68 }
69
70 /*
71 * Latch current color values on strip and reset its state machines.
72 */
ws2812_reset_delay(uint16_t delay)73 static inline void ws2812_reset_delay(uint16_t delay)
74 {
75 k_usleep(delay);
76 }
77
ws2812_strip_update_rgb(const struct device * dev,struct led_rgb * pixels,size_t num_pixels)78 static int ws2812_strip_update_rgb(const struct device *dev,
79 struct led_rgb *pixels,
80 size_t num_pixels)
81 {
82 const struct ws2812_spi_cfg *cfg = dev_cfg(dev);
83 const uint8_t one = cfg->one_frame, zero = cfg->zero_frame;
84 struct spi_buf buf = {
85 .buf = cfg->px_buf,
86 .len = (cfg->length * 8 * cfg->num_colors),
87 };
88 const struct spi_buf_set tx = {
89 .buffers = &buf,
90 .count = 1
91 };
92 uint8_t *px_buf = cfg->px_buf;
93 size_t i;
94 int rc;
95
96 /*
97 * Convert pixel data into SPI frames. Each frame has pixel data
98 * in color mapping on-wire format (e.g. GRB, GRBW, RGB, etc).
99 */
100 for (i = 0; i < num_pixels; i++) {
101 uint8_t j;
102
103 for (j = 0; j < cfg->num_colors; j++) {
104 uint8_t pixel;
105
106 switch (cfg->color_mapping[j]) {
107 /* White channel is not supported by LED strip API. */
108 case LED_COLOR_ID_WHITE:
109 pixel = 0;
110 break;
111 case LED_COLOR_ID_RED:
112 pixel = pixels[i].r;
113 break;
114 case LED_COLOR_ID_GREEN:
115 pixel = pixels[i].g;
116 break;
117 case LED_COLOR_ID_BLUE:
118 pixel = pixels[i].b;
119 break;
120 default:
121 return -EINVAL;
122 }
123 ws2812_spi_ser(px_buf, pixel, one, zero);
124 px_buf += 8;
125 }
126 }
127
128 /*
129 * Display the pixel data.
130 */
131 rc = spi_write_dt(&cfg->bus, &tx);
132 ws2812_reset_delay(cfg->reset_delay);
133
134 return rc;
135 }
136
ws2812_strip_length(const struct device * dev)137 static size_t ws2812_strip_length(const struct device *dev)
138 {
139 const struct ws2812_spi_cfg *cfg = dev_cfg(dev);
140
141 return cfg->length;
142 }
143
ws2812_spi_init(const struct device * dev)144 static int ws2812_spi_init(const struct device *dev)
145 {
146 const struct ws2812_spi_cfg *cfg = dev_cfg(dev);
147 uint8_t i;
148
149 if (!spi_is_ready_dt(&cfg->bus)) {
150 LOG_ERR("SPI device %s not ready", cfg->bus.bus->name);
151 return -ENODEV;
152 }
153
154 for (i = 0; i < cfg->num_colors; i++) {
155 switch (cfg->color_mapping[i]) {
156 case LED_COLOR_ID_WHITE:
157 case LED_COLOR_ID_RED:
158 case LED_COLOR_ID_GREEN:
159 case LED_COLOR_ID_BLUE:
160 break;
161 default:
162 LOG_ERR("%s: invalid channel to color mapping."
163 "Check the color-mapping DT property",
164 dev->name);
165 return -EINVAL;
166 }
167 }
168
169 return 0;
170 }
171
172 static DEVICE_API(led_strip, ws2812_spi_api) = {
173 .update_rgb = ws2812_strip_update_rgb,
174 .length = ws2812_strip_length,
175 };
176
177 #define WS2812_SPI_NUM_PIXELS(idx) \
178 (DT_INST_PROP(idx, chain_length))
179 #define WS2812_SPI_HAS_WHITE(idx) \
180 (DT_INST_PROP(idx, has_white_channel) == 1)
181 #define WS2812_SPI_ONE_FRAME(idx) \
182 (DT_INST_PROP(idx, spi_one_frame))
183 #define WS2812_SPI_ZERO_FRAME(idx) \
184 (DT_INST_PROP(idx, spi_zero_frame))
185 #define WS2812_SPI_BUFSZ(idx) \
186 (WS2812_NUM_COLORS(idx) * 8 * WS2812_SPI_NUM_PIXELS(idx))
187
188 /*
189 * Retrieve the channel to color mapping (e.g. RGB, BGR, GRB, ...) from the
190 * "color-mapping" DT property.
191 */
192 #define WS2812_COLOR_MAPPING(idx) \
193 static const uint8_t ws2812_spi_##idx##_color_mapping[] = \
194 DT_INST_PROP(idx, color_mapping)
195
196 #define WS2812_NUM_COLORS(idx) (DT_INST_PROP_LEN(idx, color_mapping))
197
198 /* Get the latch/reset delay from the "reset-delay" DT property. */
199 #define WS2812_RESET_DELAY(idx) DT_INST_PROP(idx, reset_delay)
200
201 #define WS2812_SPI_DEVICE(idx) \
202 \
203 static uint8_t ws2812_spi_##idx##_px_buf[WS2812_SPI_BUFSZ(idx)]; \
204 \
205 WS2812_COLOR_MAPPING(idx); \
206 \
207 static const struct ws2812_spi_cfg ws2812_spi_##idx##_cfg = { \
208 .bus = SPI_DT_SPEC_INST_GET(idx, SPI_OPER(idx), 0), \
209 .px_buf = ws2812_spi_##idx##_px_buf, \
210 .one_frame = WS2812_SPI_ONE_FRAME(idx), \
211 .zero_frame = WS2812_SPI_ZERO_FRAME(idx), \
212 .num_colors = WS2812_NUM_COLORS(idx), \
213 .color_mapping = ws2812_spi_##idx##_color_mapping, \
214 .length = DT_INST_PROP(idx, chain_length), \
215 .reset_delay = WS2812_RESET_DELAY(idx), \
216 }; \
217 \
218 DEVICE_DT_INST_DEFINE(idx, \
219 ws2812_spi_init, \
220 NULL, \
221 NULL, \
222 &ws2812_spi_##idx##_cfg, \
223 POST_KERNEL, \
224 CONFIG_LED_STRIP_INIT_PRIORITY, \
225 &ws2812_spi_api);
226
227 DT_INST_FOREACH_STATUS_OKAY(WS2812_SPI_DEVICE)
228