blob: 0f562eeb66cd34cf0f82f481491f1749ff736c2e [file] [log] [blame]
/*
* 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)