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