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