284 lines
10 KiB
C
284 lines
10 KiB
C
/*
|
|
* 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)
|