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