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