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 <tokita.hiroshi@fujitsu.com>
This commit is contained in:
TOKITA Hiroshi 2023-11-19 21:02:12 +09:00 committed by Anas Nashif
commit ca520f8493
6 changed files with 509 additions and 0 deletions

View file

@ -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

View file

@ -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

View file

@ -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.

View file

@ -0,0 +1,283 @@
/*
* Copyright (c) 2024 TOKITA Hiroshi
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT led_strip_matrix
#include <zephyr/drivers/display.h>
#include <zephyr/drivers/led_strip.h>
#include <zephyr/sys/util.h>
#include <zephyr/logging/log.h>
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)

View file

@ -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.

View file

@ -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;
};
};
};