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