1 /*
2  * Copyright (c) 2018 Intel Corporation
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_gpio
10 
11 #include <drivers/led_strip.h>
12 
13 #include <string.h>
14 
15 #define LOG_LEVEL CONFIG_LED_STRIP_LOG_LEVEL
16 #include <logging/log.h>
17 LOG_MODULE_REGISTER(ws2812_gpio);
18 
19 #include <zephyr.h>
20 #include <soc.h>
21 #include <drivers/gpio.h>
22 #include <device.h>
23 #include <drivers/clock_control.h>
24 #include <drivers/clock_control/nrf_clock_control.h>
25 #include <dt-bindings/led/led.h>
26 
27 struct ws2812_gpio_data {
28 	const struct device *gpio;
29 };
30 
31 struct ws2812_gpio_cfg {
32 	uint8_t pin;
33 	uint8_t num_colors;
34 	const uint8_t *color_mapping;
35 };
36 
dev_data(const struct device * dev)37 static struct ws2812_gpio_data *dev_data(const struct device *dev)
38 {
39 	return dev->data;
40 }
41 
dev_cfg(const struct device * dev)42 static const struct ws2812_gpio_cfg *dev_cfg(const struct device *dev)
43 {
44 	return dev->config;
45 }
46 
47 /*
48  * This is hard-coded to nRF51 in two ways:
49  *
50  * 1. The assembly delays T1H, T0H, TxL
51  * 2. GPIO set/clear
52  */
53 
54 /*
55  * T1H: 1 bit high pulse delay: 12 cycles == .75 usec
56  * T0H: 0 bit high pulse delay: 4 cycles == .25 usec
57  * TxL: inter-bit low pulse delay: 8 cycles == .5 usec
58  *
59  * We can't use k_busy_wait() here: its argument is in microseconds,
60  * and we need roughly .05 microsecond resolution.
61  */
62 #define DELAY_T1H "nop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\n"
63 #define DELAY_T0H "nop\nnop\nnop\nnop\n"
64 #define DELAY_TxL "nop\nnop\nnop\nnop\nnop\nnop\nnop\nnop\n"
65 
66 /*
67  * GPIO set/clear (these make assumptions about assembly details
68  * below).
69  *
70  * This uses OUTCLR == OUTSET+4.
71  *
72  * We should be able to make this portable using the results of
73  * https://github.com/zephyrproject-rtos/zephyr/issues/11917.
74  *
75  * We already have the GPIO device stashed in ws2812_gpio_data, so
76  * this driver can be used as a test case for the optimized API.
77  *
78  * Per Arm docs, both Rd and Rn must be r0-r7, so we use the "l"
79  * constraint in the below assembly.
80  */
81 #define SET_HIGH "str %[p], [%[r], #0]\n" /* OUTSET = BIT(LED_PIN) */
82 #define SET_LOW "str %[p], [%[r], #4]\n"  /* OUTCLR = BIT(LED_PIN) */
83 
84 /* Send out a 1 bit's pulse */
85 #define ONE_BIT(base, pin) do {			\
86 	__asm volatile (SET_HIGH			\
87 			DELAY_T1H			\
88 			SET_LOW			\
89 			DELAY_TxL			\
90 			::				\
91 			[r] "l" (base),		\
92 			[p] "l" (pin)); } while (0)
93 
94 /* Send out a 0 bit's pulse */
95 #define ZERO_BIT(base, pin) do {			\
96 	__asm volatile (SET_HIGH			\
97 			DELAY_T0H			\
98 			SET_LOW			\
99 			DELAY_TxL			\
100 			::				\
101 			[r] "l" (base),		\
102 			[p] "l" (pin)); } while (0)
103 
send_buf(const struct device * dev,uint8_t * buf,size_t len)104 static int send_buf(const struct device *dev, uint8_t *buf, size_t len)
105 {
106 	volatile uint32_t *base = (uint32_t *)&NRF_GPIO->OUTSET;
107 	const uint32_t val = BIT(dev_cfg(dev)->pin);
108 	struct onoff_manager *mgr =
109 		z_nrf_clock_control_get_onoff(CLOCK_CONTROL_NRF_SUBSYS_HF);
110 	struct onoff_client cli;
111 	unsigned int key;
112 	int rc;
113 
114 	sys_notify_init_spinwait(&cli.notify);
115 	rc = onoff_request(mgr, &cli);
116 	if (rc < 0) {
117 		return rc;
118 	}
119 
120 	while (sys_notify_fetch_result(&cli.notify, &rc)) {
121 		/* pend until clock is up and running */
122 	}
123 
124 	key = irq_lock();
125 
126 	while (len--) {
127 		uint32_t b = *buf++;
128 		int32_t i;
129 
130 		/*
131 		 * Generate signal out of the bits, MSbit first.
132 		 *
133 		 * Accumulator maintenance and branching mean the
134 		 * inter-bit time will be longer than TxL, but the
135 		 * wp.josh.com blog post says we have at least 5 usec
136 		 * of slack time between bits before we risk the
137 		 * signal getting latched, so this will be fine as
138 		 * long as the compiler does something minimally
139 		 * reasonable.
140 		 */
141 		for (i = 7; i >= 0; i--) {
142 			if (b & BIT(i)) {
143 				ONE_BIT(base, val);
144 			} else {
145 				ZERO_BIT(base, val);
146 			}
147 		}
148 	}
149 
150 	irq_unlock(key);
151 
152 	rc = onoff_release(mgr);
153 	/* Returns non-negative value on success. Cap to 0 as API states. */
154 	rc = MIN(rc, 0);
155 
156 	return rc;
157 }
158 
ws2812_gpio_update_rgb(const struct device * dev,struct led_rgb * pixels,size_t num_pixels)159 static int ws2812_gpio_update_rgb(const struct device *dev,
160 				  struct led_rgb *pixels,
161 				  size_t num_pixels)
162 {
163 	const struct ws2812_gpio_cfg *config = dev->config;
164 	uint8_t *ptr = (uint8_t *)pixels;
165 	size_t i;
166 
167 	/* Convert from RGB to on-wire format (e.g. GRB, GRBW, RGB, etc) */
168 	for (i = 0; i < num_pixels; i++) {
169 		uint8_t j;
170 
171 		for (j = 0; j < config->num_colors; j++) {
172 			switch (config->color_mapping[j]) {
173 			/* White channel is not supported by LED strip API. */
174 			case LED_COLOR_ID_WHITE:
175 				*ptr++ = 0;
176 				break;
177 			case LED_COLOR_ID_RED:
178 				*ptr++ = pixels[i].r;
179 				break;
180 			case LED_COLOR_ID_GREEN:
181 				*ptr++ = pixels[i].g;
182 				break;
183 			case LED_COLOR_ID_BLUE:
184 				*ptr++ = pixels[i].b;
185 				break;
186 			default:
187 				return -EINVAL;
188 			}
189 		}
190 	}
191 
192 	return send_buf(dev, (uint8_t *)pixels, num_pixels * config->num_colors);
193 }
194 
ws2812_gpio_update_channels(const struct device * dev,uint8_t * channels,size_t num_channels)195 static int ws2812_gpio_update_channels(const struct device *dev,
196 				       uint8_t *channels,
197 				       size_t num_channels)
198 {
199 	LOG_ERR("update_channels not implemented");
200 	return -ENOTSUP;
201 }
202 
203 static const struct led_strip_driver_api ws2812_gpio_api = {
204 	.update_rgb = ws2812_gpio_update_rgb,
205 	.update_channels = ws2812_gpio_update_channels,
206 };
207 
208 #define WS2812_GPIO_DEV(idx) \
209 	(DT_INST_GPIO_LABEL(idx, in_gpios))
210 #define WS2812_GPIO_PIN(idx) \
211 	(DT_INST_GPIO_PIN(idx, in_gpios))
212 #define WS2812_GPIO_FLAGS(idx) \
213 	(DT_INST_GPIO_FLAGS(idx, in_gpios))
214 
215 /*
216  * Retrieve the channel to color mapping (e.g. RGB, BGR, GRB, ...) from the
217  * "color-mapping" DT property.
218  */
219 #define WS2812_COLOR_MAPPING(idx)					\
220 static const uint8_t ws2812_gpio_##idx##_color_mapping[] =		\
221 	DT_INST_PROP(idx, color_mapping)
222 
223 #define WS2812_NUM_COLORS(idx) (DT_INST_PROP_LEN(idx, color_mapping))
224 
225 /*
226  * The inline assembly above is designed to work on nRF51 devices with
227  * the 16 MHz clock enabled.
228  *
229  * TODO: try to make this portable, or at least port to more devices.
230  */
231 #define WS2812_GPIO_CLK(idx) DT_LABEL(DT_INST(0, nordic_nrf_clock))
232 
233 #define WS2812_GPIO_DEVICE(idx)					\
234 									\
235 	static int ws2812_gpio_##idx##_init(const struct device *dev)	\
236 	{								\
237 		struct ws2812_gpio_data *data = dev_data(dev);		\
238 		const struct ws2812_gpio_cfg *cfg = dev_cfg(dev);	\
239 		uint8_t i;						\
240 									\
241 		data->gpio = device_get_binding(WS2812_GPIO_DEV(idx));	\
242 		if (!data->gpio) {					\
243 			LOG_ERR("Unable to find GPIO controller %s",	\
244 				WS2812_GPIO_DEV(idx));			\
245 			return -ENODEV;				\
246 		}							\
247 									\
248 		for (i = 0; i < cfg->num_colors; i++) {			\
249 			switch (cfg->color_mapping[i]) {		\
250 			case LED_COLOR_ID_WHITE:			\
251 			case LED_COLOR_ID_RED:				\
252 			case LED_COLOR_ID_GREEN:			\
253 			case LED_COLOR_ID_BLUE:				\
254 				break;					\
255 			default:					\
256 				LOG_ERR("%s: invalid channel to color mapping." \
257 					" Check the color-mapping DT property",	\
258 					dev->name);			\
259 				return -EINVAL;				\
260 			}						\
261 		}							\
262 									\
263 		return gpio_pin_configure(data->gpio,			\
264 					  WS2812_GPIO_PIN(idx),	\
265 					  WS2812_GPIO_FLAGS(idx) |	\
266 					  GPIO_OUTPUT);		\
267 	}								\
268 									\
269 	static struct ws2812_gpio_data ws2812_gpio_##idx##_data;	\
270 									\
271 	WS2812_COLOR_MAPPING(idx);					\
272 									\
273 	static const struct ws2812_gpio_cfg ws2812_gpio_##idx##_cfg = { \
274 		.pin = WS2812_GPIO_PIN(idx),				\
275 		.num_colors = WS2812_NUM_COLORS(idx),			\
276 		.color_mapping = ws2812_gpio_##idx##_color_mapping,	\
277 	};								\
278 									\
279 	DEVICE_DT_INST_DEFINE(idx,					\
280 			    ws2812_gpio_##idx##_init,			\
281 			    NULL,					\
282 			    &ws2812_gpio_##idx##_data,			\
283 			    &ws2812_gpio_##idx##_cfg, POST_KERNEL,	\
284 			    CONFIG_LED_STRIP_INIT_PRIORITY,		\
285 			    &ws2812_gpio_api);
286 
287 DT_INST_FOREACH_STATUS_OKAY(WS2812_GPIO_DEVICE)
288