/* * Copyright (c) 2024 TOKITA Hiroshi * * SPDX-License-Identifier: Apache-2.0 */ #define DT_DRV_COMPAT led_strip_matrix #include #include #include #include LOG_MODULE_REGISTER(led_strip_matrix, CONFIG_DISPLAY_LOG_LEVEL); struct led_strip_buffer { const struct device *const dev; const size_t chain_length; struct led_rgb *pixels; }; struct led_strip_matrix_config { size_t num_of_strips; const struct led_strip_buffer *strips; uint16_t height; uint16_t width; uint16_t module_width; uint16_t module_height; bool circulative; bool start_from_right; bool start_from_bottom; bool modules_circulative; bool modules_start_from_right; bool modules_start_from_bottom; enum display_pixel_format pixel_format; }; static size_t pixel_index(const struct led_strip_matrix_config *config, uint16_t x, uint16_t y) { const size_t mods_per_row = config->width / config->module_width; const size_t mod_w = config->module_width; const size_t mod_h = config->module_height; const size_t mod_pixels = mod_w * mod_h; const size_t mod_row = config->modules_start_from_bottom ? (mod_h - 1) - (y / mod_h) : y / mod_h; const size_t y_in_mod = config->start_from_bottom ? (mod_h - 1) - (y % mod_h) : y % mod_h; size_t mod_col = x / mod_w; size_t x_in_mod = x % mod_w; if (config->modules_circulative) { if (config->modules_start_from_right) { mod_col = mods_per_row - 1 - mod_col; } } else { if ((mod_row % 2) == !config->modules_start_from_right) { mod_col = mods_per_row - 1 - mod_col; } } if (config->circulative) { if (config->start_from_right) { x_in_mod = (mod_w - 1) - (x % mod_w); } } else { if ((y_in_mod % 2) == !config->start_from_right) { x_in_mod = (mod_w - 1) - (x % mod_w); } } return (mods_per_row * mod_row + mod_col) * mod_pixels + y_in_mod * mod_w + x_in_mod; } static struct led_rgb *pixel_address(const struct led_strip_matrix_config *config, uint16_t x, uint16_t y) { size_t idx = pixel_index(config, x, y); for (size_t i = 0; i < config->num_of_strips; i++) { if (idx < config->strips[i].chain_length) { return &config->strips[i].pixels[idx]; } idx -= config->strips[i].chain_length; } return NULL; } static inline int check_descriptor(const struct led_strip_matrix_config *config, const uint16_t x, const uint16_t y, const struct display_buffer_descriptor *desc) { __ASSERT(desc->width <= desc->pitch, "Pitch is smaller than width"); __ASSERT(desc->pitch <= config->width, "Pitch in descriptor is larger than screen size"); __ASSERT(desc->height <= config->height, "Height in descriptor is larger than screen size"); __ASSERT(x + desc->pitch <= config->width, "Writing outside screen boundaries in horizontal direction"); __ASSERT(y + desc->height <= config->height, "Writing outside screen boundaries in vertical direction"); if (desc->width > desc->pitch || x + desc->pitch > config->width || y + desc->height > config->height) { return -EINVAL; } return 0; } static int 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) { const struct led_strip_matrix_config *config = dev->config; const uint8_t *buf_ptr = buf; int rc; rc = check_descriptor(config, x, y, desc); if (rc) { LOG_ERR("Invalid descriptor: %d", rc); return rc; } for (size_t ypos = y; ypos < (y + desc->height); ypos++) { for (size_t xpos = x; xpos < (x + desc->width); xpos++) { struct led_rgb *pix = pixel_address(config, xpos, ypos); if (config->pixel_format == PIXEL_FORMAT_ARGB_8888) { uint32_t color = *((uint32_t *)buf_ptr); pix->r = (color >> 16) & 0xFF; pix->g = (color >> 8) & 0xFF; pix->b = (color) & 0xFF; buf_ptr += 4; } else { pix->r = *buf_ptr; buf_ptr++; pix->g = *buf_ptr; buf_ptr++; pix->b = *buf_ptr; buf_ptr++; } } buf_ptr += (desc->pitch - desc->width) * (config->pixel_format == PIXEL_FORMAT_ARGB_8888 ? 4 : 3); } for (size_t i = 0; i < config->num_of_strips; i++) { rc = led_strip_update_rgb(config->strips[i].dev, config->strips[i].pixels, config->width * config->height); if (rc) { LOG_ERR("couldn't update strip: %d", rc); } } return rc; } static int led_strip_matrix_read(const struct device *dev, const uint16_t x, const uint16_t y, const struct display_buffer_descriptor *desc, void *buf) { const struct led_strip_matrix_config *config = dev->config; uint8_t *buf_ptr = buf; int rc; rc = check_descriptor(config, x, y, desc); if (rc) { LOG_ERR("Invalid descriptor: %d", rc); return rc; } for (size_t ypos = y; ypos < (y + desc->height); ypos++) { for (size_t xpos = x; xpos < (x + desc->width); xpos++) { struct led_rgb *pix = pixel_address(config, xpos, ypos); if (config->pixel_format == PIXEL_FORMAT_ARGB_8888) { uint32_t *pix_ptr = (uint32_t *)buf_ptr; *pix_ptr = 0xFF000000 | pix->r << 16 | pix->g << 8 | pix->b; } else { *buf_ptr = pix->r; buf_ptr++; *buf_ptr = pix->g; buf_ptr++; *buf_ptr = pix->b; buf_ptr++; } } buf_ptr += (desc->pitch - desc->width) * (config->pixel_format == PIXEL_FORMAT_ARGB_8888 ? 4 : 3); } return 0; } static void led_strip_matrix_get_capabilities(const struct device *dev, struct display_capabilities *caps) { const struct led_strip_matrix_config *config = dev->config; memset(caps, 0, sizeof(struct display_capabilities)); caps->x_resolution = config->width; caps->y_resolution = config->height; caps->supported_pixel_formats = PIXEL_FORMAT_ARGB_8888 | PIXEL_FORMAT_RGB_888; caps->current_pixel_format = config->pixel_format; caps->screen_info = 0; } static DEVICE_API(display, led_strip_matrix_api) = { .write = led_strip_matrix_write, .read = led_strip_matrix_read, .get_capabilities = led_strip_matrix_get_capabilities, }; static int led_strip_matrix_init(const struct device *dev) { const struct led_strip_matrix_config *config = dev->config; for (size_t i = 0; i < config->num_of_strips; i++) { if (!device_is_ready(config->strips[i].dev)) { LOG_ERR("LED strip device %s is not ready", config->strips[i].dev->name); return -EINVAL; } } return 0; } #define CHAIN_LENGTH(idx, inst) \ COND_CODE_1(DT_INST_NODE_HAS_PROP(inst, chain_lengths), \ (DT_INST_PROP_BY_IDX(inst, chain_lengths, idx)), \ (DT_INST_PROP_BY_PHANDLE_IDX(inst, led_strips, idx, chain_length))) #define STRIP_BUFFER_INITIALIZER(idx, inst) \ { \ .dev = DEVICE_DT_GET(DT_INST_PROP_BY_IDX(inst, led_strips, idx)), \ .chain_length = CHAIN_LENGTH(idx, inst), \ .pixels = pixels##inst##_##idx, \ } #define DECLARE_PIXELS(idx, inst) \ static struct led_rgb pixels##inst##_##idx[CHAIN_LENGTH(idx, inst)]; #define AMOUNT_OF_LEDS(inst) LISTIFY(DT_INST_PROP_LEN(inst, led_strips), CHAIN_LENGTH, (+), inst) #define VALIDATE_CHAIN_LENGTH(idx, inst) \ BUILD_ASSERT( \ CHAIN_LENGTH(idx, inst) % \ (DT_INST_PROP(inst, width) / DT_INST_PROP(inst, horizontal_modules) * \ (DT_INST_PROP(inst, height) / DT_INST_PROP(inst, vertical_modules))) == \ 0); #define LED_STRIP_MATRIX_DEFINE(inst) \ LISTIFY(DT_INST_PROP_LEN(inst, led_strips), DECLARE_PIXELS, (;), inst); \ static const struct led_strip_buffer strip_buffer##inst[] = { \ LISTIFY(DT_INST_PROP_LEN(inst, led_strips), STRIP_BUFFER_INITIALIZER, (,), inst), \ }; \ static const struct led_strip_matrix_config dd_config_##inst = { \ .num_of_strips = DT_INST_PROP_LEN(inst, led_strips), \ .strips = strip_buffer##inst, \ .width = DT_INST_PROP(inst, width), \ .height = DT_INST_PROP(inst, height), \ .module_width = \ DT_INST_PROP(inst, width) / DT_INST_PROP(inst, horizontal_modules), \ .module_height = \ DT_INST_PROP(inst, height) / DT_INST_PROP(inst, vertical_modules), \ .circulative = DT_INST_PROP(inst, circulative), \ .start_from_right = DT_INST_PROP(inst, start_from_right), \ .modules_circulative = DT_INST_PROP(inst, modules_circulative), \ .modules_start_from_right = DT_INST_PROP(inst, modules_start_from_right), \ .pixel_format = DT_INST_PROP(inst, pixel_format), \ }; \ \ BUILD_ASSERT((DT_INST_PROP(inst, pixel_format) == PIXEL_FORMAT_RGB_888) || \ (DT_INST_PROP(inst, pixel_format) == PIXEL_FORMAT_ARGB_8888)); \ BUILD_ASSERT((DT_INST_PROP(inst, width) * DT_INST_PROP(inst, height)) == \ AMOUNT_OF_LEDS(inst)); \ BUILD_ASSERT((DT_INST_PROP(inst, width) % DT_INST_PROP(inst, horizontal_modules)) == 0); \ BUILD_ASSERT((DT_INST_PROP(inst, height) % DT_INST_PROP(inst, vertical_modules)) == 0); \ LISTIFY(DT_INST_PROP_LEN(inst, led_strips), VALIDATE_CHAIN_LENGTH, (;), inst); \ \ DEVICE_DT_INST_DEFINE(inst, led_strip_matrix_init, NULL, NULL, &dd_config_##inst, \ POST_KERNEL, CONFIG_APPLICATION_INIT_PRIORITY, \ &led_strip_matrix_api); DT_INST_FOREACH_STATUS_OKAY(LED_STRIP_MATRIX_DEFINE)