1 /*
2 * Copyright (c) 2022 Esco Medical ApS
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #define DT_DRV_COMPAT ti_tlc5971
8
9 #include <zephyr/kernel.h>
10 #include <zephyr/drivers/spi.h>
11 #include <zephyr/drivers/led_strip.h>
12 #include <zephyr/drivers/led_strip/tlc5971.h>
13 #include <zephyr/dt-bindings/led/led.h>
14 #include <zephyr/sys/util.h>
15
16 #include <zephyr/logging/log.h>
17 LOG_MODULE_REGISTER(tlc5971, CONFIG_LED_STRIP_LOG_LEVEL);
18
19 struct tlc5971_config {
20 struct spi_dt_spec bus;
21 const uint8_t *color_mapping;
22 uint8_t num_pixels;
23 uint8_t num_colors;
24 };
25
26 struct tlc5971_data {
27 uint8_t *data_buffer;
28 uint8_t gbc_color_1;
29 uint8_t gbc_color_2;
30 uint8_t gbc_color_3;
31 uint8_t control_data;
32 };
33
34 /** SPI operation word constant, SPI mode 0, CPOL = 0, CPHA = 0 */
35 #define TLC5971_SPI_OPERATION (SPI_OP_MODE_MASTER | SPI_TRANSFER_MSB | SPI_WORD_SET(8))
36
37 /** Number of supported colors */
38 #define TLC5971_NUMBER_OF_COLORS 3
39
40 /**
41 * @brief Number of RGB pixels per TLC5791 device
42 *
43 * The TLC5971 has 4x RGB outputs per device, where each RGB group constitues a pixel from this
44 * drivers point of view.
45 */
46 #define TLC5971_PIXELS_PER_DEVICE 4
47
48 /** Length in bytes of data packet per TLC5791 device */
49 #define TLC5971_PACKET_LEN 28
50
51 /** write command for writing control data and GS data to internal registers */
52 #define TLC5971_WRITE_COMMAND 0x25
53
54 /** GS reference clock select bit in FC data (0 = internal oscillator clock, 1 = SCKI clock). */
55 #define TLC5971_BYTE27_CTRL_BIT_EXTGCK BIT(0)
56
57 /** GS reference clock edge select bit for OUTXn on-off timing control in FC data */
58 #define TLC5971_BYTE27_CTRL_BIT_OUTTMG BIT(1)
59
60 /** Constant-current output enable bit in FC data (0 = output control enabled, 1 = blank). */
61 #define TLC5971_BYTE26_CTRL_BIT_BLANK BIT(5)
62
63 /** Auto display repeat mode enable bit in FC data (0 = disabled, 1 = enabled). */
64 #define TLC5971_BYTE26_CTRL_BIT_DSPRPT BIT(6)
65
66 /** Display timing reset mode enable bit in FC data (0 = disabled, 1 = enabled). */
67 #define TLC5971_BYTE26_CTRL_BIT_TMGRST BIT(7)
68
69 /** Bit mask for write cmd in data byte 27 */
70 #define TLC5971_BYTE27_WRITE_CMD_MASK GENMASK(7, 2)
71
72 /** Bit mask for control bits in data byte 27 */
73 #define TLC5971_BYTE27_CTRL_MASK GENMASK(1, 0)
74
75 /** Bit mask for control bits in data byte 26 */
76 #define TLC5971_BYTE26_CTRL_MASK GENMASK(7, 5)
77
78 /** Bit mask for global brightness control for color 1 in data byte 26, upper 5 bits of GBC */
79 #define TLC5971_BYTE26_GBC1_MASK GENMASK(4, 0)
80
81 /** Bit mask for global brightness control for color 1 in data byte 25, lower 2 bits of GBC */
82 #define TLC5971_BYTE25_GBC1_MASK GENMASK(7, 6)
83
84 /** Bit mask for global brightness control for color 2 in data byte 25, upper 6 bits of GBC */
85 #define TLC5971_BYTE25_GBC2_MASK GENMASK(5, 0)
86
87 /** Bit mask for global brightness control for color 2 in data byte 24, lower 1 bits of GBC */
88 #define TLC5971_BYTE24_GBC2_MASK BIT(7)
89
90 /** Bit mask for global brightness control for color 3 in data byte 24, all 7 bits of GBC */
91 #define TLC5971_BYTE24_GBC3_MASK GENMASK(6, 0)
92
93 /**
94 * @brief create data byte 27 from control data
95 *
96 * @param control_data control bits
97 * @return uint8_t the serialized data byte 27
98 */
tlc5971_data_byte27(uint8_t control_data)99 static inline uint8_t tlc5971_data_byte27(uint8_t control_data)
100 {
101 return FIELD_PREP(TLC5971_BYTE27_WRITE_CMD_MASK, TLC5971_WRITE_COMMAND) |
102 FIELD_PREP(TLC5971_BYTE27_CTRL_MASK, control_data);
103 }
104
105 /**
106 * @brief create data byte 26 from control data and color 1 GBC
107 *
108 * @param control_data control bits
109 * @param gbc_color_1 global brightness control for color 1 LEDs
110 * @return uint8_t the serialized data byte 26
111 */
tlc5971_data_byte26(uint8_t control_data,uint8_t gbc_color_1)112 static inline uint8_t tlc5971_data_byte26(uint8_t control_data, uint8_t gbc_color_1)
113 {
114 return FIELD_PREP(TLC5971_BYTE26_CTRL_MASK, control_data) |
115 FIELD_PREP(TLC5971_BYTE26_GBC1_MASK, (gbc_color_1 >> 2));
116 }
117
118 /**
119 * @brief create data byte 25 from color 1 and 2 GBC
120 *
121 * @param gbc_color_1 global brightness control for color 1 LEDs
122 * @param gbc_color_2 global brightness control for color 2 LEDs
123 * @return uint8_t the serialized data byte 25
124 */
tlc5971_data_byte25(uint8_t gbc_color_1,uint8_t gbc_color_2)125 static inline uint8_t tlc5971_data_byte25(uint8_t gbc_color_1, uint8_t gbc_color_2)
126 {
127 return FIELD_PREP(TLC5971_BYTE25_GBC1_MASK, gbc_color_1) |
128 FIELD_PREP(TLC5971_BYTE25_GBC2_MASK, (gbc_color_2 >> 1));
129 }
130
131 /**
132 * @brief create data byte 24 from color 2 and 3 GBC
133 *
134 * @param gbc_color_2 global brightness control for color 2 LEDs
135 * @param gbc_color_3 global brightness control for color 3 LEDs
136 * @return uint8_t the serialized data byte 24
137 */
tlc5971_data_byte24(uint8_t gbc_color_2,uint8_t gbc_color_3)138 static inline uint8_t tlc5971_data_byte24(uint8_t gbc_color_2, uint8_t gbc_color_3)
139 {
140 return FIELD_PREP(TLC5971_BYTE24_GBC2_MASK, gbc_color_2) |
141 FIELD_PREP(TLC5971_BYTE24_GBC3_MASK, gbc_color_3);
142 }
143
144 /**
145 * @brief map user colors to tlc5971 color order
146 *
147 * @param color_id color id from color mapping
148 * @param pixel_data rgb data to be mapped
149 * @return the mapped color value
150 */
tlc5971_map_color(int color_id,const struct led_rgb * pixel_data)151 static uint8_t tlc5971_map_color(int color_id, const struct led_rgb *pixel_data)
152 {
153 uint8_t temp = 0;
154
155 switch (color_id) {
156 case LED_COLOR_ID_RED:
157 temp = pixel_data->r;
158 break;
159 case LED_COLOR_ID_GREEN:
160 temp = pixel_data->g;
161 break;
162 case LED_COLOR_ID_BLUE:
163 temp = pixel_data->b;
164 break;
165 default:
166 temp = 0;
167 break;
168 }
169
170 return temp;
171 }
172
173 /**
174 * @brief serialize control data and pixel data for device daisy chain
175 *
176 * the serializer only supports "full" devices, meaning each device is expected
177 * to be mounted with all 4 LEDs.
178 *
179 * @param dev device pointer
180 * @param pixels pixel RGB data for daisy chain
181 * @param num_pixels number of pixels in daisy chain
182 */
tlc5971_fill_data_buffer(const struct device * dev,struct led_rgb * pixels,size_t num_pixels)183 static void tlc5971_fill_data_buffer(const struct device *dev, struct led_rgb *pixels,
184 size_t num_pixels)
185 {
186 const struct tlc5971_config *cfg = dev->config;
187 struct tlc5971_data *data = dev->data;
188 uint8_t *data_buffer = data->data_buffer;
189 int count = 0;
190
191 /*
192 * tlc5971 device order is reversed as the rgb data for the last device in the daisy chain
193 * should be transmitted first.
194 */
195 for (int device = (num_pixels / TLC5971_PIXELS_PER_DEVICE) - 1; device >= 0; device--) {
196 /*
197 * The SPI frame format expects a BGR color order for the global brightness control
198 * values, but since the led_strip API allows custom color mappings, we simply use
199 * color_x terms to keep things generic.
200 */
201 data_buffer[count++] = tlc5971_data_byte27(data->control_data);
202 data_buffer[count++] = tlc5971_data_byte26(data->control_data, data->gbc_color_1);
203 data_buffer[count++] = tlc5971_data_byte25(data->gbc_color_1, data->gbc_color_2);
204 data_buffer[count++] = tlc5971_data_byte24(data->gbc_color_2, data->gbc_color_3);
205
206 for (int pixel = (TLC5971_PIXELS_PER_DEVICE - 1); pixel >= 0; pixel--) {
207 /* data is "reversed" so RGB0 comes last, i.e at byte 0 */
208 const struct led_rgb *pixel_data =
209 &pixels[(device * TLC5971_PIXELS_PER_DEVICE) + pixel];
210
211 /*
212 * Convert pixel data into SPI frames, mapping user colors to tlc5971
213 * data frame color order (BGR).
214 */
215 for (int color = 0; color < cfg->num_colors; color++) {
216 uint8_t temp =
217 tlc5971_map_color(cfg->color_mapping[color], pixel_data);
218
219 /*
220 * The tlc5971 rgb values are 16 bit but zephyr's rgb values are
221 * 8 bit. Simply upscale to 16 bit by using the 8 bit value for both
222 * LSB and MSB of the 16 bit word.
223 */
224 data_buffer[count++] = temp;
225 data_buffer[count++] = temp;
226 }
227 }
228 }
229 }
230
tlc5971_transmit_data(const struct device * dev,size_t num_pixels)231 static int tlc5971_transmit_data(const struct device *dev, size_t num_pixels)
232 {
233 const struct tlc5971_config *cfg = dev->config;
234 struct tlc5971_data *data = dev->data;
235
236 struct spi_buf buf = {
237 .buf = data->data_buffer,
238 .len = (num_pixels / TLC5971_PIXELS_PER_DEVICE) * TLC5971_PACKET_LEN,
239 };
240
241 const struct spi_buf_set tx = {
242 .buffers = &buf,
243 .count = 1,
244 };
245
246 return spi_write_dt(&cfg->bus, &tx);
247 }
248
tlc5971_update_rgb(const struct device * dev,struct led_rgb * pixels,size_t num_pixels)249 static int tlc5971_update_rgb(const struct device *dev, struct led_rgb *pixels, size_t num_pixels)
250 {
251 tlc5971_fill_data_buffer(dev, pixels, num_pixels);
252
253 return tlc5971_transmit_data(dev, num_pixels);
254 }
255
tlc5971_length(const struct device * dev)256 static size_t tlc5971_length(const struct device *dev)
257 {
258 const struct tlc5971_config *cfg = dev->config;
259
260 return (size_t)cfg->num_pixels;
261 }
262
tlc5971_set_global_brightness(const struct device * dev,struct led_rgb pixel)263 int tlc5971_set_global_brightness(const struct device *dev, struct led_rgb pixel)
264 {
265 const struct tlc5971_config *cfg = dev->config;
266 struct tlc5971_data *data = dev->data;
267 int res = -EINVAL;
268
269 if ((pixel.r <= TLC5971_GLOBAL_BRIGHTNESS_CONTROL_MAX) &&
270 (pixel.g <= TLC5971_GLOBAL_BRIGHTNESS_CONTROL_MAX) &&
271 (pixel.b <= TLC5971_GLOBAL_BRIGHTNESS_CONTROL_MAX)) {
272 data->gbc_color_1 = tlc5971_map_color(cfg->color_mapping[0], &pixel);
273 data->gbc_color_2 = tlc5971_map_color(cfg->color_mapping[1], &pixel);
274 data->gbc_color_3 = tlc5971_map_color(cfg->color_mapping[2], &pixel);
275 res = 0;
276 }
277
278 return res;
279 }
280
tlc5971_init(const struct device * dev)281 static int tlc5971_init(const struct device *dev)
282 {
283 const struct tlc5971_config *cfg = dev->config;
284 struct tlc5971_data *data = dev->data;
285
286 if (!spi_is_ready_dt(&cfg->bus)) {
287 LOG_ERR("%s: SPI device %s not ready", dev->name, cfg->bus.bus->name);
288 return -ENODEV;
289 }
290
291 if ((cfg->num_pixels % TLC5971_PIXELS_PER_DEVICE) != 0) {
292 LOG_ERR("%s: chain length must be multiple of 4", dev->name);
293 return -EINVAL;
294 }
295
296 if (cfg->num_colors != TLC5971_NUMBER_OF_COLORS) {
297 LOG_ERR("%s: the tlc5971 only supports %i colors", dev->name,
298 TLC5971_NUMBER_OF_COLORS);
299 return -EINVAL;
300 }
301
302 for (int i = 0; i < cfg->num_colors; i++) {
303 switch (cfg->color_mapping[i]) {
304 case LED_COLOR_ID_RED:
305 case LED_COLOR_ID_GREEN:
306 case LED_COLOR_ID_BLUE:
307 break;
308 default:
309 LOG_ERR("%s: invalid color mapping", dev->name);
310 return -EINVAL;
311 }
312 }
313
314 /*
315 * set up sane defaults for control data.
316 * unblanks leds, enables auto display repeat, enables timing resetm uses internal clock for
317 * PWM generation and sets the GS reference clock edge select to rising edge
318 */
319 data->control_data = TLC5971_BYTE27_CTRL_BIT_OUTTMG | TLC5971_BYTE26_CTRL_BIT_DSPRPT |
320 TLC5971_BYTE26_CTRL_BIT_TMGRST;
321
322 return 0;
323 }
324
325 static DEVICE_API(led_strip, tlc5971_api) = {
326 .update_rgb = tlc5971_update_rgb,
327 .length = tlc5971_length,
328 };
329
330 #define TLC5971_DATA_BUFFER_LENGTH(inst) \
331 (DT_INST_PROP(inst, chain_length) / TLC5971_PIXELS_PER_DEVICE) * TLC5971_PACKET_LEN
332
333 #define TLC5971_DEVICE(inst) \
334 static const uint8_t tlc5971_##inst##_color_mapping[] = DT_INST_PROP(inst, color_mapping); \
335 static const struct tlc5971_config tlc5971_##inst##_config = { \
336 .bus = SPI_DT_SPEC_INST_GET(inst, TLC5971_SPI_OPERATION, 0), \
337 .num_pixels = DT_INST_PROP(inst, chain_length), \
338 .num_colors = DT_INST_PROP_LEN(inst, color_mapping), \
339 .color_mapping = tlc5971_##inst##_color_mapping, \
340 }; \
341 static uint8_t tlc5971_##inst##_data_buffer[TLC5971_DATA_BUFFER_LENGTH(inst)]; \
342 static struct tlc5971_data tlc5971_##inst##_data = { \
343 .data_buffer = tlc5971_##inst##_data_buffer, \
344 .gbc_color_1 = TLC5971_GLOBAL_BRIGHTNESS_CONTROL_MAX, \
345 .gbc_color_2 = TLC5971_GLOBAL_BRIGHTNESS_CONTROL_MAX, \
346 .gbc_color_3 = TLC5971_GLOBAL_BRIGHTNESS_CONTROL_MAX, \
347 }; \
348 DEVICE_DT_INST_DEFINE(inst, tlc5971_init, NULL, &tlc5971_##inst##_data, \
349 &tlc5971_##inst##_config, POST_KERNEL, \
350 CONFIG_LED_STRIP_INIT_PRIORITY, &tlc5971_api);
351
352 DT_INST_FOREACH_STATUS_OKAY(TLC5971_DEVICE)
353