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