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 		  COND_CODE_1(DT_INST_PROP(idx, spi_cpol), (SPI_MODE_CPOL), (0)) | \
38 		  COND_CODE_1(DT_INST_PROP(idx, spi_cpha), (SPI_MODE_CPHA), (0)) | \
39 		  SPI_WORD_SET(SPI_FRAME_BITS))
40 
41 struct ws2812_spi_cfg {
42 	struct spi_dt_spec bus;
43 	uint8_t *px_buf;
44 	size_t px_buf_size;
45 	uint8_t one_frame;
46 	uint8_t zero_frame;
47 	uint8_t num_colors;
48 	const uint8_t *color_mapping;
49 	uint16_t reset_delay;
50 };
51 
dev_cfg(const struct device * dev)52 static const struct ws2812_spi_cfg *dev_cfg(const struct device *dev)
53 {
54 	return dev->config;
55 }
56 
57 /*
58  * Serialize an 8-bit color channel value into an equivalent sequence
59  * of SPI frames, MSbit first, where a one bit becomes SPI frame
60  * one_frame, and zero bit becomes zero_frame.
61  */
ws2812_spi_ser(uint8_t buf[8],uint8_t color,const uint8_t one_frame,const uint8_t zero_frame)62 static inline void ws2812_spi_ser(uint8_t buf[8], uint8_t color,
63 				  const uint8_t one_frame, const uint8_t zero_frame)
64 {
65 	int i;
66 
67 	for (i = 0; i < 8; i++) {
68 		buf[i] = color & BIT(7 - i) ? one_frame : zero_frame;
69 	}
70 }
71 
72 /*
73  * Returns true if and only if cfg->px_buf is big enough to convert
74  * num_pixels RGB color values into SPI frames.
75  */
num_pixels_ok(const struct ws2812_spi_cfg * cfg,size_t num_pixels)76 static inline bool num_pixels_ok(const struct ws2812_spi_cfg *cfg,
77 				 size_t num_pixels)
78 {
79 	size_t nbytes;
80 	bool overflow;
81 
82 	overflow = size_mul_overflow(num_pixels, cfg->num_colors * 8, &nbytes);
83 	return !overflow && (nbytes <= cfg->px_buf_size);
84 }
85 
86 /*
87  * Latch current color values on strip and reset its state machines.
88  */
ws2812_reset_delay(uint16_t delay)89 static inline void ws2812_reset_delay(uint16_t delay)
90 {
91 	k_usleep(delay);
92 }
93 
ws2812_strip_update_rgb(const struct device * dev,struct led_rgb * pixels,size_t num_pixels)94 static int ws2812_strip_update_rgb(const struct device *dev,
95 				   struct led_rgb *pixels,
96 				   size_t num_pixels)
97 {
98 	const struct ws2812_spi_cfg *cfg = dev_cfg(dev);
99 	const uint8_t one = cfg->one_frame, zero = cfg->zero_frame;
100 	struct spi_buf buf = {
101 		.buf = cfg->px_buf,
102 		.len = cfg->px_buf_size,
103 	};
104 	const struct spi_buf_set tx = {
105 		.buffers = &buf,
106 		.count = 1
107 	};
108 	uint8_t *px_buf = cfg->px_buf;
109 	size_t i;
110 	int rc;
111 
112 	if (!num_pixels_ok(cfg, num_pixels)) {
113 		return -ENOMEM;
114 	}
115 
116 	/*
117 	 * Convert pixel data into SPI frames. Each frame has pixel data
118 	 * in color mapping on-wire format (e.g. GRB, GRBW, RGB, etc).
119 	 */
120 	for (i = 0; i < num_pixels; i++) {
121 		uint8_t j;
122 
123 		for (j = 0; j < cfg->num_colors; j++) {
124 			uint8_t pixel;
125 
126 			switch (cfg->color_mapping[j]) {
127 			/* White channel is not supported by LED strip API. */
128 			case LED_COLOR_ID_WHITE:
129 				pixel = 0;
130 				break;
131 			case LED_COLOR_ID_RED:
132 				pixel = pixels[i].r;
133 				break;
134 			case LED_COLOR_ID_GREEN:
135 				pixel = pixels[i].g;
136 				break;
137 			case LED_COLOR_ID_BLUE:
138 				pixel = pixels[i].b;
139 				break;
140 			default:
141 				return -EINVAL;
142 			}
143 			ws2812_spi_ser(px_buf, pixel, one, zero);
144 			px_buf += 8;
145 		}
146 	}
147 
148 	/*
149 	 * Display the pixel data.
150 	 */
151 	rc = spi_write_dt(&cfg->bus, &tx);
152 	ws2812_reset_delay(cfg->reset_delay);
153 
154 	return rc;
155 }
156 
ws2812_strip_update_channels(const struct device * dev,uint8_t * channels,size_t num_channels)157 static int ws2812_strip_update_channels(const struct device *dev,
158 					uint8_t *channels,
159 					size_t num_channels)
160 {
161 	LOG_ERR("update_channels not implemented");
162 	return -ENOTSUP;
163 }
164 
ws2812_spi_init(const struct device * dev)165 static int ws2812_spi_init(const struct device *dev)
166 {
167 	const struct ws2812_spi_cfg *cfg = dev_cfg(dev);
168 	uint8_t i;
169 
170 	if (!spi_is_ready_dt(&cfg->bus)) {
171 		LOG_ERR("SPI device %s not ready", cfg->bus.bus->name);
172 		return -ENODEV;
173 	}
174 
175 	for (i = 0; i < cfg->num_colors; i++) {
176 		switch (cfg->color_mapping[i]) {
177 		case LED_COLOR_ID_WHITE:
178 		case LED_COLOR_ID_RED:
179 		case LED_COLOR_ID_GREEN:
180 		case LED_COLOR_ID_BLUE:
181 			break;
182 		default:
183 			LOG_ERR("%s: invalid channel to color mapping."
184 				"Check the color-mapping DT property",
185 				dev->name);
186 			return -EINVAL;
187 		}
188 	}
189 
190 	return 0;
191 }
192 
193 static const struct led_strip_driver_api ws2812_spi_api = {
194 	.update_rgb = ws2812_strip_update_rgb,
195 	.update_channels = ws2812_strip_update_channels,
196 };
197 
198 #define WS2812_SPI_NUM_PIXELS(idx) \
199 	(DT_INST_PROP(idx, chain_length))
200 #define WS2812_SPI_HAS_WHITE(idx) \
201 	(DT_INST_PROP(idx, has_white_channel) == 1)
202 #define WS2812_SPI_ONE_FRAME(idx) \
203 	(DT_INST_PROP(idx, spi_one_frame))
204 #define WS2812_SPI_ZERO_FRAME(idx) \
205 	(DT_INST_PROP(idx, spi_zero_frame))
206 #define WS2812_SPI_BUFSZ(idx) \
207 	(WS2812_NUM_COLORS(idx) * 8 * WS2812_SPI_NUM_PIXELS(idx))
208 
209 /*
210  * Retrieve the channel to color mapping (e.g. RGB, BGR, GRB, ...) from the
211  * "color-mapping" DT property.
212  */
213 #define WS2812_COLOR_MAPPING(idx)				  \
214 	static const uint8_t ws2812_spi_##idx##_color_mapping[] = \
215 		DT_INST_PROP(idx, color_mapping)
216 
217 #define WS2812_NUM_COLORS(idx) (DT_INST_PROP_LEN(idx, color_mapping))
218 
219 /* Get the latch/reset delay from the "reset-delay" DT property. */
220 #define WS2812_RESET_DELAY(idx) DT_INST_PROP(idx, reset_delay)
221 
222 #define WS2812_SPI_DEVICE(idx)						 \
223 									 \
224 	static uint8_t ws2812_spi_##idx##_px_buf[WS2812_SPI_BUFSZ(idx)]; \
225 									 \
226 	WS2812_COLOR_MAPPING(idx);					 \
227 									 \
228 	static const struct ws2812_spi_cfg ws2812_spi_##idx##_cfg = {	 \
229 		.bus = SPI_DT_SPEC_INST_GET(idx, SPI_OPER(idx), 0),	 \
230 		.px_buf = ws2812_spi_##idx##_px_buf,			 \
231 		.px_buf_size = WS2812_SPI_BUFSZ(idx),			 \
232 		.one_frame = WS2812_SPI_ONE_FRAME(idx),			 \
233 		.zero_frame = WS2812_SPI_ZERO_FRAME(idx),		 \
234 		.num_colors = WS2812_NUM_COLORS(idx),			 \
235 		.color_mapping = ws2812_spi_##idx##_color_mapping,	 \
236 		.reset_delay = WS2812_RESET_DELAY(idx),			 \
237 	};								 \
238 									 \
239 	DEVICE_DT_INST_DEFINE(idx,					 \
240 			      ws2812_spi_init,				 \
241 			      NULL,					 \
242 			      NULL,					 \
243 			      &ws2812_spi_##idx##_cfg,			 \
244 			      POST_KERNEL,				 \
245 			      CONFIG_LED_STRIP_INIT_PRIORITY,		 \
246 			      &ws2812_spi_api);
247 
248 DT_INST_FOREACH_STATUS_OKAY(WS2812_SPI_DEVICE)
249