From ca520f8493408dd9a7b2602174ce9cca2f3d9f00 Mon Sep 17 00:00:00 2001 From: TOKITA Hiroshi Date: Sun, 19 Nov 2023 21:02:12 +0900 Subject: [PATCH] drivers: display: Add LED-Strip matrix display driver Adds a driver for a display of LED strips arranged in a grid. Signed-off-by: TOKITA Hiroshi --- drivers/display/CMakeLists.txt | 1 + drivers/display/Kconfig | 1 + drivers/display/Kconfig.led_strip_matrix | 11 + drivers/display/display_led_strip_matrix.c | 283 ++++++++++++++++++++ dts/bindings/display/led-strip-matrix.yaml | 182 +++++++++++++ tests/drivers/build_all/display/app.overlay | 31 +++ 6 files changed, 509 insertions(+) create mode 100644 drivers/display/Kconfig.led_strip_matrix create mode 100644 drivers/display/display_led_strip_matrix.c create mode 100644 dts/bindings/display/led-strip-matrix.yaml diff --git a/drivers/display/CMakeLists.txt b/drivers/display/CMakeLists.txt index 3b166e73892..0350efdd44b 100644 --- a/drivers/display/CMakeLists.txt +++ b/drivers/display/CMakeLists.txt @@ -24,6 +24,7 @@ zephyr_library_sources_ifdef(CONFIG_RM68200 display_rm68200.c) zephyr_library_sources_ifdef(CONFIG_RM67162 display_rm67162.c) zephyr_library_sources_ifdef(CONFIG_HX8394 display_hx8394.c) zephyr_library_sources_ifdef(CONFIG_GC9X01X display_gc9x01x.c) +zephyr_library_sources_ifdef(CONFIG_LED_STRIP_MATRIX display_led_strip_matrix.c) zephyr_library_sources_ifdef(CONFIG_MICROBIT_DISPLAY mb_display.c diff --git a/drivers/display/Kconfig b/drivers/display/Kconfig index 53b1a6f3917..cb811c7965f 100644 --- a/drivers/display/Kconfig +++ b/drivers/display/Kconfig @@ -41,5 +41,6 @@ source "drivers/display/Kconfig.mcux_dcnano_lcdif" source "drivers/display/Kconfig.otm8009a" source "drivers/display/Kconfig.hx8394" source "drivers/display/Kconfig.gc9x01x" +source "drivers/display/Kconfig.led_strip_matrix" endif # DISPLAY diff --git a/drivers/display/Kconfig.led_strip_matrix b/drivers/display/Kconfig.led_strip_matrix new file mode 100644 index 00000000000..1230a83ce67 --- /dev/null +++ b/drivers/display/Kconfig.led_strip_matrix @@ -0,0 +1,11 @@ +# Copyright (c) 2024 TOKITA Hiroshi +# SPDX-License-Identifier: Apache-2.0 + +config LED_STRIP_MATRIX + bool "LED strip matrix display driver" + default y + depends on DT_HAS_LED_STRIP_MATRIX_ENABLED + depends on LED_STRIP + help + Enable LED strip matrix display (LED strip arranged in + a grid pattern) driver. diff --git a/drivers/display/display_led_strip_matrix.c b/drivers/display/display_led_strip_matrix.c new file mode 100644 index 00000000000..0f562eeb66c --- /dev/null +++ b/drivers/display/display_led_strip_matrix.c @@ -0,0 +1,283 @@ +/* + * 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 then 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 const struct display_driver_api 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) diff --git a/dts/bindings/display/led-strip-matrix.yaml b/dts/bindings/display/led-strip-matrix.yaml new file mode 100644 index 00000000000..f46cee2f0c4 --- /dev/null +++ b/dts/bindings/display/led-strip-matrix.yaml @@ -0,0 +1,182 @@ +# Copyright (c) 2024 TOKITA Hiroshi +# SPDX-License-Identifier: Apache-2.0 + +description: | + Generic LED strip matrix (LED strip arranged in a grid pattern) + +compatible: "led-strip-matrix" + +include: display-controller.yaml + +properties: + circulative: + type: boolean + description: | + Use a circulative layout that returns to the left edge of the next row + after reaching the right edge. + If not set, turn around and go left in a serpentine layout when it reaches + the right edge. + + * circulative layout + [ 0][ 1][ 2][ 3] + [ 4][ 5][ 6][ 7] + [ 8][ 9][10][11] + [12][13][14][15] + + * serpentine layout + [ 0][ 1][ 2][ 3] + [ 7][ 6][ 5][ 4] + [ 8][ 9][10][11] + [15][14][13][12] + + start-from-right: + type: boolean + description: | + Specify if the first LED is at the right. + + * Start from the right with a serpentine layout + [ 3][ 2][ 1][ 0] + [ 4][ 5][ 6][ 7] + [11][10][ 9][ 8] + [12][13][14][15] + + * Start from the right with a circulative layout + [ 3][ 2][ 1][ 0] + [ 7][ 6][ 5][ 4] + [11][10][ 9][ 8] + [15][14][13][12] + + start-from-bottom: + type: boolean + description: | + Specify if the first LED is at the bottom. + + * Start from the bottom with a circulative layout + [12][13][14][15] + [ 8][ 9][10][11] + [ 4][ 5][ 6][ 7] + [ 0][ 1][ 2][ 3] + + * Start from the bottom with a serpentine layout + [15][14][13][12] + [ 8][ 9][10][11] + [ 7][ 6][ 5][ 4] + [ 0][ 1][ 2][ 3] + + width: + description: | + Specifies the overall width of the matrix. + If the matrix consists of multiple modules, it is the sum of their widths. + + height: + description: | + Specifies the overall height of the matrix. + If the matrix consists of multiple modules, it is the sum of their heights. + + horizontal-modules: + type: int + default: 1 + description: | + If the display forms with multiple modules, + specify the horizontal number of modules. + The number must be able to divide the width value. + If not set, it controls a single matrix. + + * 8x4 display with 2 serpentine layout modules + [ 0][ 1][ 2][ 3] [16][17][18][19] + [ 7][ 6][ 5][ 4] [23][22][21][20] + [ 8][ 9][10][11] [24][25][26][27] + [15][14][13][12] [31][30][29][28] + + vertical-modules: + type: int + default: 1 + description: | + If the display forms with multiple modules, + specify the vertical number of modules. + The number must be able to divide the height value. + If not set, it controls a single matrix. + + * 4x8 display with 2 serpentine layout modules + [ 0][ 1][ 2][ 3] + [ 7][ 6][ 5][ 4] + [ 8][ 9][10][11] + [15][14][13][12] + + [16][17][18][19] + [23][22][21][20] + [24][25][26][27] + [31][30][29][28] + + modules-circulative: + type: boolean + description: | + Specifies that the order of the modules that make up the matrix is circulative. + + * circulative module layout + [M0][M1][M2] + [M3][M4][M5] + [M6][M7][M8] + + * serpentine module layout + [M0][M1][M2] + [M5][M4][M3] + [M6][M7][M8] + + modules-start-from-right: + type: boolean + description: | + Specifies that modules are ordered from right to left. + + * Start from the right with a module serpentine layout + [M2][M1][M0] + [M3][M4][M5] + [M8][M7][M6] + + * Start from the right with a module circulative layout + [M2][M1][M0] + [M5][M4][M3] + [M8][M7][M6] + + modules-start-from-bottom: + type: boolean + description: | + Specifies that modules are ordered from bottom to top. + + * Start from the right with a module serpentine layout + [M6][M7][M8] + [M5][M4][M3] + [M0][M1][M2] + + * Start from the right with a module circulative layout + [M6][M7][M8] + [M3][M4][M5] + [M0][M1][M2] + + led-strips: + type: phandles + required: true + description: | + Specify the LED strip that is the substance of the matrix. + If multiple strips are specified, they are "flattened" and sequentialized. + For example, if `strip0` and `strip1` with 128 LEDs are specified, + the first LED of `strip1` will be treated as the 129th LED. + These LEDs are mapped to coordinates according to the layout rule in order. + The amount of LEDs must equal the [width * height] value. + + chain-lengths: + type: array + description: | + Specify the number of LEDs for each strip. + It can omit the value if all strip nodes have a `chain-length` property. + Each value must be a multiple of the number of LEDs per module + [(width / horizontal-modules) * (height / vertical-modules)]. + + pixel-format: + type: int + default: 1 + description: | + Initial Pixel format. + See dt-bindings/display/panel.h for a list. + This property only accepts PANEL_PIXEL_FORMAT_RGB_888 and PANEL_PIXEL_FORMAT_RRGB_8888. + If this property is not set, use PANEL_PIXEL_FORMAT_RGB_888 as a default. diff --git a/tests/drivers/build_all/display/app.overlay b/tests/drivers/build_all/display/app.overlay index 61a821dd1ad..f287255274e 100644 --- a/tests/drivers/build_all/display/app.overlay +++ b/tests/drivers/build_all/display/app.overlay @@ -65,6 +65,37 @@ width = <240>; height = <240>; }; + + test_led_strip_0: lpd8806@2 { + compatible = "greeled,lpd8806"; + reg = <2>; + spi-max-frequency = <2000000>; + }; + + test_led_strip_1: ws2812_spi@3 { + compatible = "worldsemi,ws2812-spi"; + reg = <3>; + spi-max-frequency = <2000000>; + spi-one-frame = <1>; + spi-zero-frame = <1>; + chain-length = <256>; + color-mapping = <0 1 2>; + reset-delay = <280>; + }; + }; + + test_led_strip_matrix { + compatible = "led-strip-matrix"; + status = "okay"; + + led-strips = <&test_led_strip_0>, <&test_led_strip_1>; + chain-lengths = <256>, <256>; + width = <32>; + height = <16>; + horizontal-modules = <2>; + vertical-modules = <1>; + circulative; + start-from-right; }; }; };