1 /*
2  * Copyright (c) 2023 TOKITA Hiroshi
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include <zephyr/drivers/gpio.h>
8 #include <zephyr/drivers/pinctrl.h>
9 #include <zephyr/drivers/led_strip.h>
10 #include <zephyr/drivers/misc/pio_rpi_pico/pio_rpi_pico.h>
11 #include <zephyr/dt-bindings/led/led.h>
12 #include <zephyr/kernel.h>
13 
14 #include <zephyr/logging/log.h>
15 LOG_MODULE_REGISTER(ws2812_rpi_pico_pio, CONFIG_LED_STRIP_LOG_LEVEL);
16 
17 #define DT_DRV_COMPAT worldsemi_ws2812_rpi_pico_pio
18 
19 struct ws2812_led_strip_data {
20 	uint32_t sm;
21 };
22 
23 struct ws2812_led_strip_config {
24 	const struct device *piodev;
25 	const uint8_t gpio_pin;
26 	uint8_t num_colors;
27 	size_t length;
28 	uint32_t frequency;
29 	const uint8_t *const color_mapping;
30 	uint16_t reset_delay;
31 	uint32_t cycles_per_bit;
32 };
33 
34 struct ws2812_rpi_pico_pio_config {
35 	const struct device *piodev;
36 	const struct pinctrl_dev_config *const pcfg;
37 	struct pio_program program;
38 };
39 
ws2812_led_strip_sm_init(const struct device * dev)40 static int ws2812_led_strip_sm_init(const struct device *dev)
41 {
42 	const struct ws2812_led_strip_config *config = dev->config;
43 	const float clkdiv =
44 		sys_clock_hw_cycles_per_sec() / (config->cycles_per_bit * config->frequency);
45 	pio_sm_config sm_config = pio_get_default_sm_config();
46 	PIO pio;
47 	int sm;
48 
49 	pio = pio_rpi_pico_get_pio(config->piodev);
50 
51 	sm = pio_claim_unused_sm(pio, false);
52 	if (sm < 0) {
53 		return -EINVAL;
54 	}
55 
56 	sm_config_set_sideset(&sm_config, 1, false, false);
57 	sm_config_set_sideset_pins(&sm_config, config->gpio_pin);
58 	sm_config_set_out_shift(&sm_config, false, true, (config->num_colors == 4 ? 32 : 24));
59 	sm_config_set_fifo_join(&sm_config, PIO_FIFO_JOIN_TX);
60 	sm_config_set_clkdiv(&sm_config, clkdiv);
61 	pio_sm_set_consecutive_pindirs(pio, sm, config->gpio_pin, 1, true);
62 	pio_sm_init(pio, sm, -1, &sm_config);
63 	pio_sm_set_enabled(pio, sm, true);
64 
65 	return sm;
66 }
67 
68 /*
69  * Latch current color values on strip and reset its state machines.
70  */
ws2812_led_strip_reset_delay(uint16_t delay)71 static inline void ws2812_led_strip_reset_delay(uint16_t delay)
72 {
73 	k_usleep(delay);
74 }
75 
ws2812_led_strip_update_rgb(const struct device * dev,struct led_rgb * pixels,size_t num_pixels)76 static int ws2812_led_strip_update_rgb(const struct device *dev, struct led_rgb *pixels,
77 				       size_t num_pixels)
78 {
79 	const struct ws2812_led_strip_config *config = dev->config;
80 	struct ws2812_led_strip_data *data = dev->data;
81 	PIO pio = pio_rpi_pico_get_pio(config->piodev);
82 
83 	for (size_t i = 0; i < num_pixels; i++) {
84 		uint32_t color = 0;
85 
86 		for (size_t j = 0; j < config->num_colors; j++) {
87 			switch (config->color_mapping[j]) {
88 			/* White channel is not supported by LED strip API. */
89 			case LED_COLOR_ID_WHITE:
90 				color |= 0;
91 				break;
92 			case LED_COLOR_ID_RED:
93 				color |= pixels[i].r << (8 * (2 - j));
94 				break;
95 			case LED_COLOR_ID_GREEN:
96 				color |= pixels[i].g << (8 * (2 - j));
97 				break;
98 			case LED_COLOR_ID_BLUE:
99 				color |= pixels[i].b << (8 * (2 - j));
100 				break;
101 			}
102 		}
103 
104 		pio_sm_put_blocking(pio, data->sm, color << (config->num_colors == 4 ? 0 : 8));
105 	}
106 
107 	ws2812_led_strip_reset_delay(config->reset_delay);
108 
109 	return 0;
110 }
111 
ws2812_led_strip_length(const struct device * dev)112 static size_t ws2812_led_strip_length(const struct device *dev)
113 {
114 	const struct ws2812_led_strip_config *config = dev->config;
115 
116 	return config->length;
117 }
118 
119 static DEVICE_API(led_strip, ws2812_led_strip_api) = {
120 	.update_rgb = ws2812_led_strip_update_rgb,
121 	.length = ws2812_led_strip_length,
122 };
123 
124 /*
125  * Retrieve the channel to color mapping (e.g. RGB, BGR, GRB, ...) from the
126  * "color-mapping" DT property.
127  */
ws2812_led_strip_init(const struct device * dev)128 static int ws2812_led_strip_init(const struct device *dev)
129 {
130 	const struct ws2812_led_strip_config *config = dev->config;
131 	struct ws2812_led_strip_data *data = dev->data;
132 	int sm;
133 
134 	if (!device_is_ready(config->piodev)) {
135 		LOG_ERR("%s: PIO device not ready", dev->name);
136 		return -ENODEV;
137 	}
138 
139 	for (uint32_t i = 0; i < config->num_colors; i++) {
140 		switch (config->color_mapping[i]) {
141 		case LED_COLOR_ID_WHITE:
142 		case LED_COLOR_ID_RED:
143 		case LED_COLOR_ID_GREEN:
144 		case LED_COLOR_ID_BLUE:
145 			break;
146 		default:
147 			LOG_ERR("%s: invalid channel to color mapping."
148 				" Check the color-mapping DT property",
149 				dev->name);
150 			return -EINVAL;
151 		}
152 	}
153 
154 	sm = ws2812_led_strip_sm_init(dev);
155 	if (sm < 0) {
156 		return sm;
157 	}
158 
159 	data->sm = sm;
160 
161 	return 0;
162 }
163 
ws2812_rpi_pico_pio_init(const struct device * dev)164 static int ws2812_rpi_pico_pio_init(const struct device *dev)
165 {
166 	const struct ws2812_rpi_pico_pio_config *config = dev->config;
167 	PIO pio;
168 
169 	if (!device_is_ready(config->piodev)) {
170 		LOG_ERR("%s: PIO device not ready", dev->name);
171 		return -ENODEV;
172 	}
173 
174 	pio = pio_rpi_pico_get_pio(config->piodev);
175 
176 	pio_add_program(pio, &config->program);
177 
178 	return pinctrl_apply_state(config->pcfg, PINCTRL_STATE_DEFAULT);
179 }
180 
181 #define CYCLES_PER_BIT(node)                                                                       \
182 	(DT_PROP_BY_IDX(node, bit_waveform, 0) + DT_PROP_BY_IDX(node, bit_waveform, 1) +           \
183 	 DT_PROP_BY_IDX(node, bit_waveform, 2))
184 
185 #define WS2812_CHILD_INIT(node)                                                                    \
186 	static const uint8_t ws2812_led_strip_##node##_color_mapping[] =                           \
187 		DT_PROP(node, color_mapping);                                                      \
188 	struct ws2812_led_strip_data ws2812_led_strip_##node##_data;                               \
189                                                                                                    \
190 	static const struct ws2812_led_strip_config ws2812_led_strip_##node##_config = {           \
191 		.piodev = DEVICE_DT_GET(DT_PARENT(DT_PARENT(node))),                               \
192 		.gpio_pin = DT_GPIO_PIN_BY_IDX(node, gpios, 0),                                    \
193 		.num_colors = DT_PROP_LEN(node, color_mapping),                                    \
194 		.length = DT_PROP(node, chain_length),                                             \
195 		.color_mapping = ws2812_led_strip_##node##_color_mapping,                          \
196 		.reset_delay = DT_PROP(node, reset_delay),                                         \
197 		.frequency = DT_PROP(node, frequency),                                             \
198 		.cycles_per_bit = CYCLES_PER_BIT(DT_PARENT(node)),                                 \
199 	};                                                                                         \
200                                                                                                    \
201 	DEVICE_DT_DEFINE(node, &ws2812_led_strip_init, NULL, &ws2812_led_strip_##node##_data,      \
202 			 &ws2812_led_strip_##node##_config, POST_KERNEL,                           \
203 			 CONFIG_LED_STRIP_INIT_PRIORITY, &ws2812_led_strip_api);
204 
205 #define SET_DELAY(op, inst, i)                                                                     \
206 	(op | (((DT_INST_PROP_BY_IDX(inst, bit_waveform, i) - 1) & 0xF) << 8))
207 
208 /*
209  * This pio program runs [T0+T1+T2] cycles per 1 loop.
210  * The first `out` instruction outputs 0 by [T2] times to the sideset pin.
211  * These zeros are padding. Here is the start of actual data transmission.
212  * The second `jmp` instruction output 1 by [T0] times to the sideset pin.
213  * This `jmp` instruction jumps to line 3 if the value of register x is true.
214  * Otherwise, jump to line 4.
215  * The third `jmp` instruction outputs 1 by [T1] times to the sideset pin.
216  * After output, return to the first line.
217  * The fourth `jmp` instruction outputs 0 by [T1] times.
218  * After output, return to the first line and output 0 by [T2] times.
219  *
220  * In the case of configuration, T0=3, T1=3, T2 =4,
221  * the final output is 1110000000 in case register x is false.
222  * It represents code 0, defined in the datasheet.
223  * And outputs 1111110000 in case of x is true. It represents code 1.
224  */
225 #define WS2812_RPI_PICO_PIO_INIT(inst)                                                             \
226 	PINCTRL_DT_INST_DEFINE(inst);                                                              \
227                                                                                                    \
228 	DT_INST_FOREACH_CHILD_STATUS_OKAY(inst, WS2812_CHILD_INIT);                                \
229                                                                                                    \
230 	static const uint16_t rpi_pico_pio_ws2812_instructions_##inst[] = {                        \
231 		SET_DELAY(0x6021, inst, 2), /*  0: out    x, 1  side 0 [T2 - 1]  */                \
232 		SET_DELAY(0x1023, inst, 0), /*  1: jmp    !x, 3 side 1 [T0 - 1]  */                \
233 		SET_DELAY(0x1000, inst, 1), /*  2: jmp    0     side 1 [T1 - 1]  */                \
234 		SET_DELAY(0x0000, inst, 1), /*  3: jmp    0     side 0 [T1 - 1]  */                \
235 	};                                                                                         \
236                                                                                                    \
237 	static const struct ws2812_rpi_pico_pio_config rpi_pico_pio_ws2812_##inst##_config = {     \
238 		.piodev = DEVICE_DT_GET(DT_INST_PARENT(inst)),                                     \
239 		.pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(inst),                                      \
240 		.program =                                                                         \
241 			{                                                                          \
242 				.instructions = rpi_pico_pio_ws2812_instructions_##inst,           \
243 				.length = ARRAY_SIZE(rpi_pico_pio_ws2812_instructions_##inst),     \
244 				.origin = -1,                                                      \
245 			},                                                                         \
246 	};                                                                                         \
247                                                                                                    \
248 	DEVICE_DT_INST_DEFINE(inst, &ws2812_rpi_pico_pio_init, NULL, NULL,                         \
249 			      &rpi_pico_pio_ws2812_##inst##_config, POST_KERNEL,                   \
250 			      CONFIG_LED_STRIP_INIT_PRIORITY, NULL);
251 
252 DT_INST_FOREACH_STATUS_OKAY(WS2812_RPI_PICO_PIO_INIT)
253