1 /*
2 * Copyright (c) 2022 Jonathan Rico
3 *
4 * Adapted from the SPI driver, using the procedure in this blog post:
5 * https://electronut.in/nrf52-i2s-ws2812/
6 *
7 * Note: the word "word" refers to a 32-bit integer unless otherwise stated.
8 *
9 * WS/LRCK frequency:
10 * This refers to the "I2S word or channel select" clock.
11 * The I2S peripheral sends two 16-bit channel values for each clock period.
12 * A single LED color (8 data bits) will take up one 32-bit word or one LRCK
13 * period. This means a standard RGB led will take 3 LRCK periods to transmit.
14 *
15 * SPDX-License-Identifier: Apache-2.0
16 */
17
18 #define DT_DRV_COMPAT worldsemi_ws2812_i2s
19
20 #include <zephyr/drivers/led_strip.h>
21
22 #define LOG_LEVEL CONFIG_LED_STRIP_LOG_LEVEL
23 #include <zephyr/logging/log.h>
24 LOG_MODULE_REGISTER(ws2812_i2s);
25
26 #include <zephyr/device.h>
27 #include <zephyr/drivers/i2s.h>
28 #include <zephyr/dt-bindings/led/led.h>
29 #include <zephyr/kernel.h>
30 #include <zephyr/sys/util.h>
31
32 #define WS2812_I2S_PRE_DELAY_WORDS 1
33
34 struct ws2812_i2s_cfg {
35 struct device const *dev;
36 size_t tx_buf_bytes;
37 struct k_mem_slab *mem_slab;
38 uint8_t num_colors;
39 size_t length;
40 const uint8_t *color_mapping;
41 uint16_t reset_words;
42 uint32_t lrck_period;
43 uint32_t extra_wait_time_us;
44 bool active_low;
45 uint8_t nibble_one;
46 uint8_t nibble_zero;
47 };
48
49 /* Serialize an 8-bit color channel value into two 16-bit I2S values (or 1 32-bit
50 * word).
51 */
ws2812_i2s_ser(uint8_t color,const uint8_t sym_one,const uint8_t sym_zero)52 static inline uint32_t ws2812_i2s_ser(uint8_t color, const uint8_t sym_one, const uint8_t sym_zero)
53 {
54 uint32_t word = 0;
55
56 for (uint_fast8_t mask = 0x80; mask != 0; mask >>= 1) {
57 word <<= 4;
58 word |= (color & mask) ? sym_one : sym_zero;
59 }
60
61 /* Swap the two I2S values due to the (audio) channel TX order. */
62 return (word >> 16) | (word << 16);
63 }
64
ws2812_strip_update_rgb(const struct device * dev,struct led_rgb * pixels,size_t num_pixels)65 static int ws2812_strip_update_rgb(const struct device *dev, struct led_rgb *pixels,
66 size_t num_pixels)
67 {
68 const struct ws2812_i2s_cfg *cfg = dev->config;
69 const uint8_t sym_one = cfg->nibble_one;
70 const uint8_t sym_zero = cfg->nibble_zero;
71 const uint32_t reset_word = cfg->active_low ? ~0 : 0;
72 uint32_t *tx_buf;
73 uint32_t flush_time_us;
74 void *mem_block;
75 int ret;
76
77 /* Acquire memory for the I2S payload. */
78 ret = k_mem_slab_alloc(cfg->mem_slab, &mem_block, K_SECONDS(10));
79 if (ret < 0) {
80 LOG_ERR("Unable to allocate mem slab for TX (err %d)", ret);
81 return -ENOMEM;
82 }
83 tx_buf = (uint32_t *)mem_block;
84
85 /* Add a pre-data reset, so the first pixel isn't skipped by the strip. */
86 for (uint16_t i = 0; i < WS2812_I2S_PRE_DELAY_WORDS; i++) {
87 *tx_buf = reset_word;
88 tx_buf++;
89 }
90
91 /*
92 * Convert pixel data into I2S frames. Each frame has pixel data
93 * in color mapping on-wire format (e.g. GRB, GRBW, RGB, etc).
94 */
95 for (uint16_t i = 0; i < num_pixels; i++) {
96 for (uint16_t j = 0; j < cfg->num_colors; j++) {
97 uint8_t pixel;
98
99 switch (cfg->color_mapping[j]) {
100 /* White channel is not supported by LED strip API. */
101 case LED_COLOR_ID_WHITE:
102 pixel = 0;
103 break;
104 case LED_COLOR_ID_RED:
105 pixel = pixels[i].r;
106 break;
107 case LED_COLOR_ID_GREEN:
108 pixel = pixels[i].g;
109 break;
110 case LED_COLOR_ID_BLUE:
111 pixel = pixels[i].b;
112 break;
113 default:
114 return -EINVAL;
115 }
116 *tx_buf = ws2812_i2s_ser(pixel, sym_one, sym_zero) ^ reset_word;
117 tx_buf++;
118 }
119 }
120
121 for (uint16_t i = 0; i < cfg->reset_words; i++) {
122 *tx_buf = reset_word;
123 tx_buf++;
124 }
125
126 /* Flush the buffer on the wire. */
127 ret = i2s_write(cfg->dev, mem_block, cfg->tx_buf_bytes);
128 if (ret < 0) {
129 k_mem_slab_free(cfg->mem_slab, mem_block);
130 LOG_ERR("Failed to write data: %d", ret);
131 return ret;
132 }
133
134 ret = i2s_trigger(cfg->dev, I2S_DIR_TX, I2S_TRIGGER_START);
135 if (ret < 0) {
136 LOG_ERR("Failed to trigger command %d on TX: %d", I2S_TRIGGER_START, ret);
137 return ret;
138 }
139
140 ret = i2s_trigger(cfg->dev, I2S_DIR_TX, I2S_TRIGGER_DRAIN);
141 if (ret < 0) {
142 LOG_ERR("Failed to trigger command %d on TX: %d", I2S_TRIGGER_DRAIN, ret);
143 return ret;
144 }
145
146 /* Wait until transaction is over */
147 flush_time_us = cfg->lrck_period * cfg->tx_buf_bytes / sizeof(uint32_t);
148 k_usleep(flush_time_us + cfg->extra_wait_time_us);
149
150 return ret;
151 }
152
ws2812_strip_length(const struct device * dev)153 static size_t ws2812_strip_length(const struct device *dev)
154 {
155 const struct ws2812_i2s_cfg *cfg = dev->config;
156
157 return cfg->length;
158 }
159
ws2812_i2s_init(const struct device * dev)160 static int ws2812_i2s_init(const struct device *dev)
161 {
162 const struct ws2812_i2s_cfg *cfg = dev->config;
163 struct i2s_config config;
164 uint32_t lrck_hz;
165 int ret;
166
167 lrck_hz = USEC_PER_SEC / cfg->lrck_period;
168 LOG_DBG("Word clock: freq %u Hz period %u us",
169 lrck_hz, cfg->lrck_period);
170
171 /* 16-bit stereo, 100kHz LCLK */
172 config.word_size = 16;
173 config.channels = 2;
174 config.format = I2S_FMT_DATA_FORMAT_I2S;
175 config.options = I2S_OPT_BIT_CLK_MASTER | I2S_OPT_FRAME_CLK_MASTER;
176 config.frame_clk_freq = lrck_hz; /* WS (or LRCK) */
177 config.mem_slab = cfg->mem_slab;
178 config.block_size = cfg->tx_buf_bytes;
179 config.timeout = 1000;
180
181 ret = i2s_configure(cfg->dev, I2S_DIR_TX, &config);
182 if (ret < 0) {
183 LOG_ERR("Failed to configure I2S device: %d\n", ret);
184 return ret;
185 }
186
187 for (uint16_t i = 0; i < cfg->num_colors; i++) {
188 switch (cfg->color_mapping[i]) {
189 case LED_COLOR_ID_WHITE:
190 case LED_COLOR_ID_RED:
191 case LED_COLOR_ID_GREEN:
192 case LED_COLOR_ID_BLUE:
193 break;
194 default:
195 LOG_ERR("%s: invalid channel to color mapping."
196 "Check the color-mapping DT property",
197 dev->name);
198 return -EINVAL;
199 }
200 }
201
202 return 0;
203 }
204
205 static DEVICE_API(led_strip, ws2812_i2s_api) = {
206 .update_rgb = ws2812_strip_update_rgb,
207 .length = ws2812_strip_length,
208 };
209
210 #define WS2812_I2S_LRCK_PERIOD_US(idx) DT_INST_PROP(idx, lrck_period)
211
212 #define WS2812_RESET_DELAY_US(idx) DT_INST_PROP(idx, reset_delay)
213 /* Rounds up to the next 20us. */
214 #define WS2812_RESET_DELAY_WORDS(idx) \
215 DIV_ROUND_UP(WS2812_RESET_DELAY_US(idx), WS2812_I2S_LRCK_PERIOD_US(idx))
216
217 #define WS2812_NUM_COLORS(idx) (DT_INST_PROP_LEN(idx, color_mapping))
218
219 #define WS2812_I2S_NUM_PIXELS(idx) (DT_INST_PROP(idx, chain_length))
220
221 #define WS2812_I2S_BUFSIZE(idx) \
222 (((WS2812_NUM_COLORS(idx) * WS2812_I2S_NUM_PIXELS(idx)) + \
223 WS2812_I2S_PRE_DELAY_WORDS + WS2812_RESET_DELAY_WORDS(idx)) * 4)
224
225 #define WS2812_I2S_DEVICE(idx) \
226 \
227 K_MEM_SLAB_DEFINE_STATIC(ws2812_i2s_##idx##_slab, WS2812_I2S_BUFSIZE(idx), 2, 4); \
228 \
229 static const uint8_t ws2812_i2s_##idx##_color_mapping[] = \
230 DT_INST_PROP(idx, color_mapping); \
231 \
232 static const struct ws2812_i2s_cfg ws2812_i2s_##idx##_cfg = { \
233 .dev = DEVICE_DT_GET(DT_INST_BUS(idx)), \
234 .tx_buf_bytes = WS2812_I2S_BUFSIZE(idx), \
235 .mem_slab = &ws2812_i2s_##idx##_slab, \
236 .num_colors = WS2812_NUM_COLORS(idx), \
237 .length = DT_INST_PROP(idx, chain_length), \
238 .color_mapping = ws2812_i2s_##idx##_color_mapping, \
239 .lrck_period = WS2812_I2S_LRCK_PERIOD_US(idx), \
240 .extra_wait_time_us = DT_INST_PROP(idx, extra_wait_time), \
241 .reset_words = WS2812_RESET_DELAY_WORDS(idx), \
242 .active_low = DT_INST_PROP(idx, out_active_low), \
243 .nibble_one = DT_INST_PROP(idx, nibble_one), \
244 .nibble_zero = DT_INST_PROP(idx, nibble_zero), \
245 }; \
246 \
247 DEVICE_DT_INST_DEFINE(idx, ws2812_i2s_init, NULL, NULL, &ws2812_i2s_##idx##_cfg, \
248 POST_KERNEL, CONFIG_LED_STRIP_INIT_PRIORITY, &ws2812_i2s_api);
249
250 DT_INST_FOREACH_STATUS_OKAY(WS2812_I2S_DEVICE)
251