You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
283 lines
10 KiB
283 lines
10 KiB
/* |
|
* 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 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)
|
|
|