1 /*
2 * Copyright (c) 2024 TOKITA Hiroshi
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #define DT_DRV_COMPAT led_strip_matrix
8
9 #include <zephyr/drivers/display.h>
10 #include <zephyr/drivers/led_strip.h>
11 #include <zephyr/sys/util.h>
12
13 #include <zephyr/logging/log.h>
14 LOG_MODULE_REGISTER(led_strip_matrix, CONFIG_DISPLAY_LOG_LEVEL);
15
16 struct led_strip_buffer {
17 const struct device *const dev;
18 const size_t chain_length;
19 struct led_rgb *pixels;
20 };
21
22 struct led_strip_matrix_config {
23 size_t num_of_strips;
24 const struct led_strip_buffer *strips;
25 uint16_t height;
26 uint16_t width;
27 uint16_t module_width;
28 uint16_t module_height;
29 bool circulative;
30 bool start_from_right;
31 bool start_from_bottom;
32 bool modules_circulative;
33 bool modules_start_from_right;
34 bool modules_start_from_bottom;
35 enum display_pixel_format pixel_format;
36 };
37
pixel_index(const struct led_strip_matrix_config * config,uint16_t x,uint16_t y)38 static size_t pixel_index(const struct led_strip_matrix_config *config, uint16_t x, uint16_t y)
39 {
40 const size_t mods_per_row = config->width / config->module_width;
41 const size_t mod_w = config->module_width;
42 const size_t mod_h = config->module_height;
43 const size_t mod_pixels = mod_w * mod_h;
44 const size_t mod_row =
45 config->modules_start_from_bottom ? (mod_h - 1) - (y / mod_h) : y / mod_h;
46 const size_t y_in_mod = config->start_from_bottom ? (mod_h - 1) - (y % mod_h) : y % mod_h;
47 size_t mod_col = x / mod_w;
48 size_t x_in_mod = x % mod_w;
49
50 if (config->modules_circulative) {
51 if (config->modules_start_from_right) {
52 mod_col = mods_per_row - 1 - mod_col;
53 }
54 } else {
55 if ((mod_row % 2) == !config->modules_start_from_right) {
56 mod_col = mods_per_row - 1 - mod_col;
57 }
58 }
59
60 if (config->circulative) {
61 if (config->start_from_right) {
62 x_in_mod = (mod_w - 1) - (x % mod_w);
63 }
64 } else {
65 if ((y_in_mod % 2) == !config->start_from_right) {
66 x_in_mod = (mod_w - 1) - (x % mod_w);
67 }
68 }
69
70 return (mods_per_row * mod_row + mod_col) * mod_pixels + y_in_mod * mod_w + x_in_mod;
71 }
72
pixel_address(const struct led_strip_matrix_config * config,uint16_t x,uint16_t y)73 static struct led_rgb *pixel_address(const struct led_strip_matrix_config *config, uint16_t x,
74 uint16_t y)
75 {
76 size_t idx = pixel_index(config, x, y);
77
78 for (size_t i = 0; i < config->num_of_strips; i++) {
79 if (idx < config->strips[i].chain_length) {
80 return &config->strips[i].pixels[idx];
81 }
82 idx -= config->strips[i].chain_length;
83 }
84
85 return NULL;
86 }
87
check_descriptor(const struct led_strip_matrix_config * config,const uint16_t x,const uint16_t y,const struct display_buffer_descriptor * desc)88 static inline int check_descriptor(const struct led_strip_matrix_config *config, const uint16_t x,
89 const uint16_t y, const struct display_buffer_descriptor *desc)
90 {
91 __ASSERT(desc->width <= desc->pitch, "Pitch is smaller than width");
92 __ASSERT(desc->pitch <= config->width, "Pitch in descriptor is larger than screen size");
93 __ASSERT(desc->height <= config->height, "Height in descriptor is larger than screen size");
94 __ASSERT(x + desc->pitch <= config->width,
95 "Writing outside screen boundaries in horizontal direction");
96 __ASSERT(y + desc->height <= config->height,
97 "Writing outside screen boundaries in vertical direction");
98
99 if (desc->width > desc->pitch || x + desc->pitch > config->width ||
100 y + desc->height > config->height) {
101 return -EINVAL;
102 }
103
104 return 0;
105 }
106
led_strip_matrix_write(const struct device * dev,const uint16_t x,const uint16_t y,const struct display_buffer_descriptor * desc,const void * buf)107 static int led_strip_matrix_write(const struct device *dev, const uint16_t x, const uint16_t y,
108 const struct display_buffer_descriptor *desc, const void *buf)
109 {
110 const struct led_strip_matrix_config *config = dev->config;
111 const uint8_t *buf_ptr = buf;
112 int rc;
113
114 rc = check_descriptor(config, x, y, desc);
115 if (rc) {
116 LOG_ERR("Invalid descriptor: %d", rc);
117 return rc;
118 }
119
120 for (size_t ypos = y; ypos < (y + desc->height); ypos++) {
121 for (size_t xpos = x; xpos < (x + desc->width); xpos++) {
122 struct led_rgb *pix = pixel_address(config, xpos, ypos);
123
124 if (config->pixel_format == PIXEL_FORMAT_ARGB_8888) {
125 uint32_t color = *((uint32_t *)buf_ptr);
126
127 pix->r = (color >> 16) & 0xFF;
128 pix->g = (color >> 8) & 0xFF;
129 pix->b = (color) & 0xFF;
130
131 buf_ptr += 4;
132 } else {
133 pix->r = *buf_ptr;
134 buf_ptr++;
135 pix->g = *buf_ptr;
136 buf_ptr++;
137 pix->b = *buf_ptr;
138 buf_ptr++;
139 }
140 }
141 buf_ptr += (desc->pitch - desc->width) *
142 (config->pixel_format == PIXEL_FORMAT_ARGB_8888 ? 4 : 3);
143 }
144
145 for (size_t i = 0; i < config->num_of_strips; i++) {
146 rc = led_strip_update_rgb(config->strips[i].dev, config->strips[i].pixels,
147 config->width * config->height);
148 if (rc) {
149 LOG_ERR("couldn't update strip: %d", rc);
150 }
151 }
152
153 return rc;
154 }
155
led_strip_matrix_read(const struct device * dev,const uint16_t x,const uint16_t y,const struct display_buffer_descriptor * desc,void * buf)156 static int led_strip_matrix_read(const struct device *dev, const uint16_t x, const uint16_t y,
157 const struct display_buffer_descriptor *desc, void *buf)
158 {
159 const struct led_strip_matrix_config *config = dev->config;
160 uint8_t *buf_ptr = buf;
161 int rc;
162
163 rc = check_descriptor(config, x, y, desc);
164 if (rc) {
165 LOG_ERR("Invalid descriptor: %d", rc);
166 return rc;
167 }
168
169 for (size_t ypos = y; ypos < (y + desc->height); ypos++) {
170 for (size_t xpos = x; xpos < (x + desc->width); xpos++) {
171 struct led_rgb *pix = pixel_address(config, xpos, ypos);
172
173 if (config->pixel_format == PIXEL_FORMAT_ARGB_8888) {
174 uint32_t *pix_ptr = (uint32_t *)buf_ptr;
175
176 *pix_ptr = 0xFF000000 | pix->r << 16 | pix->g << 8 | pix->b;
177 } else {
178 *buf_ptr = pix->r;
179 buf_ptr++;
180 *buf_ptr = pix->g;
181 buf_ptr++;
182 *buf_ptr = pix->b;
183 buf_ptr++;
184 }
185 }
186 buf_ptr += (desc->pitch - desc->width) *
187 (config->pixel_format == PIXEL_FORMAT_ARGB_8888 ? 4 : 3);
188 }
189
190 return 0;
191 }
192
led_strip_matrix_get_capabilities(const struct device * dev,struct display_capabilities * caps)193 static void led_strip_matrix_get_capabilities(const struct device *dev,
194 struct display_capabilities *caps)
195 {
196 const struct led_strip_matrix_config *config = dev->config;
197
198 memset(caps, 0, sizeof(struct display_capabilities));
199 caps->x_resolution = config->width;
200 caps->y_resolution = config->height;
201 caps->supported_pixel_formats = PIXEL_FORMAT_ARGB_8888 | PIXEL_FORMAT_RGB_888;
202 caps->current_pixel_format = config->pixel_format;
203 caps->screen_info = 0;
204 }
205
206 static DEVICE_API(display, led_strip_matrix_api) = {
207 .write = led_strip_matrix_write,
208 .read = led_strip_matrix_read,
209 .get_capabilities = led_strip_matrix_get_capabilities,
210 };
211
led_strip_matrix_init(const struct device * dev)212 static int led_strip_matrix_init(const struct device *dev)
213 {
214 const struct led_strip_matrix_config *config = dev->config;
215
216 for (size_t i = 0; i < config->num_of_strips; i++) {
217 if (!device_is_ready(config->strips[i].dev)) {
218 LOG_ERR("LED strip device %s is not ready", config->strips[i].dev->name);
219 return -EINVAL;
220 }
221 }
222
223 return 0;
224 }
225
226 #define CHAIN_LENGTH(idx, inst) \
227 COND_CODE_1(DT_INST_NODE_HAS_PROP(inst, chain_lengths), \
228 (DT_INST_PROP_BY_IDX(inst, chain_lengths, idx)), \
229 (DT_INST_PROP_BY_PHANDLE_IDX(inst, led_strips, idx, chain_length)))
230
231 #define STRIP_BUFFER_INITIALIZER(idx, inst) \
232 { \
233 .dev = DEVICE_DT_GET(DT_INST_PROP_BY_IDX(inst, led_strips, idx)), \
234 .chain_length = CHAIN_LENGTH(idx, inst), \
235 .pixels = pixels##inst##_##idx, \
236 }
237
238 #define DECLARE_PIXELS(idx, inst) \
239 static struct led_rgb pixels##inst##_##idx[CHAIN_LENGTH(idx, inst)];
240
241 #define AMOUNT_OF_LEDS(inst) LISTIFY(DT_INST_PROP_LEN(inst, led_strips), CHAIN_LENGTH, (+), inst)
242
243 #define VALIDATE_CHAIN_LENGTH(idx, inst) \
244 BUILD_ASSERT( \
245 CHAIN_LENGTH(idx, inst) % \
246 (DT_INST_PROP(inst, width) / DT_INST_PROP(inst, horizontal_modules) * \
247 (DT_INST_PROP(inst, height) / DT_INST_PROP(inst, vertical_modules))) == \
248 0);
249
250 #define LED_STRIP_MATRIX_DEFINE(inst) \
251 LISTIFY(DT_INST_PROP_LEN(inst, led_strips), DECLARE_PIXELS, (;), inst); \
252 static const struct led_strip_buffer strip_buffer##inst[] = { \
253 LISTIFY(DT_INST_PROP_LEN(inst, led_strips), STRIP_BUFFER_INITIALIZER, (,), inst), \
254 }; \
255 static const struct led_strip_matrix_config dd_config_##inst = { \
256 .num_of_strips = DT_INST_PROP_LEN(inst, led_strips), \
257 .strips = strip_buffer##inst, \
258 .width = DT_INST_PROP(inst, width), \
259 .height = DT_INST_PROP(inst, height), \
260 .module_width = \
261 DT_INST_PROP(inst, width) / DT_INST_PROP(inst, horizontal_modules), \
262 .module_height = \
263 DT_INST_PROP(inst, height) / DT_INST_PROP(inst, vertical_modules), \
264 .circulative = DT_INST_PROP(inst, circulative), \
265 .start_from_right = DT_INST_PROP(inst, start_from_right), \
266 .modules_circulative = DT_INST_PROP(inst, modules_circulative), \
267 .modules_start_from_right = DT_INST_PROP(inst, modules_start_from_right), \
268 .pixel_format = DT_INST_PROP(inst, pixel_format), \
269 }; \
270 \
271 BUILD_ASSERT((DT_INST_PROP(inst, pixel_format) == PIXEL_FORMAT_RGB_888) || \
272 (DT_INST_PROP(inst, pixel_format) == PIXEL_FORMAT_ARGB_8888)); \
273 BUILD_ASSERT((DT_INST_PROP(inst, width) * DT_INST_PROP(inst, height)) == \
274 AMOUNT_OF_LEDS(inst)); \
275 BUILD_ASSERT((DT_INST_PROP(inst, width) % DT_INST_PROP(inst, horizontal_modules)) == 0); \
276 BUILD_ASSERT((DT_INST_PROP(inst, height) % DT_INST_PROP(inst, vertical_modules)) == 0); \
277 LISTIFY(DT_INST_PROP_LEN(inst, led_strips), VALIDATE_CHAIN_LENGTH, (;), inst); \
278 \
279 DEVICE_DT_INST_DEFINE(inst, led_strip_matrix_init, NULL, NULL, &dd_config_##inst, \
280 POST_KERNEL, CONFIG_APPLICATION_INIT_PRIORITY, \
281 &led_strip_matrix_api);
282
283 DT_INST_FOREACH_STATUS_OKAY(LED_STRIP_MATRIX_DEFINE)
284