Skip to content

Commit 69c4ee3

Browse files
committed
drivers: display: hub12: add horizontal chaining support
- Add support for chaining multiple 32x16 panels horizontally by setting width to multiples of 32 (64, 96, 128, etc). - Also fixed a race condition issue. Signed-off-by: Siratul Islam <email@sirat.me>
1 parent 0ffef90 commit 69c4ee3

File tree

2 files changed

+68
-35
lines changed

2 files changed

+68
-35
lines changed

drivers/display/display_hub12.c

Lines changed: 63 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
/*
2-
* Copyright (c) 2025 Siratul Islam <sirat4757@gmail.com>
2+
* Copyright (c) 2025 Siratul Islam <email@sirat.me>
33
* SPDX-License-Identifier: Apache-2.0
44
*
5-
* Driver for 32x16 monochrome LED panels with HUB12 interface.
5+
* Driver for 32x16 monochrome P10 LED panels with HUB12 interface.
66
*/
77

88
#include <zephyr/kernel.h>
@@ -18,15 +18,16 @@ LOG_MODULE_REGISTER(hub12, CONFIG_DISPLAY_LOG_LEVEL);
1818

1919
#define DT_DRV_COMPAT zephyr_hub12
2020

21-
/* Display layout constants */
21+
/* Single panel constants */
22+
#define HUB12_PANEL_WIDTH 32
23+
#define HUB12_PANEL_HEIGHT 16
2224
#define HUB12_ROWS 4
23-
#define HUB12_BYTES_PER_ROW 16
25+
#define HUB12_BYTES_PER_PANEL 16
2426
#define HUB12_GROUP_SIZE 4
2527
#define HUB12_NUM_GROUPS 4
2628
#define HUB12_PIXELS_PER_BYTE 8
2729

2830
/* Brightness control parameters */
29-
#define HUB12_PWM_FREQ 1000
3031
#define HUB12_DEFAULT_BRIGHTNESS 5
3132
#define HUB12_MIN_BRIGHTNESS 1
3233
#define HUB12_MAX_BRIGHTNESS 50
@@ -39,40 +40,50 @@ struct hub12_config {
3940
struct spi_dt_spec spi;
4041
uint16_t width;
4142
uint16_t height;
43+
uint8_t num_panels;
44+
uint16_t bytes_per_row;
4245
};
4346

4447
struct hub12_data {
4548
uint8_t *framebuffer;
46-
uint8_t cache[HUB12_ROWS][HUB12_BYTES_PER_ROW];
49+
uint8_t *cache;
4750
uint8_t current_row;
4851
struct k_timer scan_timer;
4952
struct k_work scan_work;
50-
struct k_sem lock;
53+
struct k_mutex lock;
5154
const struct device *dev;
5255
uint8_t brightness_us;
5356
};
5457

55-
static void hub12_update_cache(struct hub12_data *data, uint8_t row)
58+
static void hub12_update_cache(struct hub12_data *data, const struct hub12_config *config,
59+
uint8_t row)
5660
{
5761
const uint8_t *fb = data->framebuffer;
62+
uint8_t *cache_row = data->cache + (row * config->bytes_per_row);
5863

59-
for (int i = 0; i < HUB12_BYTES_PER_ROW; i++) {
60-
int group = i / HUB12_GROUP_SIZE;
61-
int offset = i % HUB12_GROUP_SIZE;
62-
int reverse_offset = (HUB12_GROUP_SIZE - 1) - offset;
63-
int fb_idx = reverse_offset * HUB12_NUM_GROUPS * HUB12_ROWS +
64-
row * HUB12_NUM_GROUPS + group;
64+
for (uint8_t panel = 0; panel < config->num_panels; panel++) {
65+
for (int i = 0; i < HUB12_BYTES_PER_PANEL; i++) {
66+
int group = i / HUB12_GROUP_SIZE;
67+
int offset = i % HUB12_GROUP_SIZE;
68+
int reverse_offset = (HUB12_GROUP_SIZE - 1) - offset;
6569

66-
data->cache[row][i] = fb[fb_idx];
70+
int fb_idx = reverse_offset * config->num_panels * HUB12_NUM_GROUPS *
71+
HUB12_ROWS +
72+
row * config->num_panels * HUB12_NUM_GROUPS +
73+
panel * HUB12_NUM_GROUPS + group;
74+
75+
cache_row[panel * HUB12_BYTES_PER_PANEL + i] = ~fb[fb_idx];
76+
}
6777
}
6878
}
6979

7080
static void hub12_scan_row(struct hub12_data *data, const struct hub12_config *config)
7181
{
7282
uint8_t row = data->current_row;
83+
uint8_t *cache_row = data->cache + (row * config->bytes_per_row);
7384
int ret;
7485

75-
struct spi_buf tx_buf = {.buf = data->cache[row], .len = HUB12_BYTES_PER_ROW};
86+
struct spi_buf tx_buf = {.buf = cache_row, .len = config->bytes_per_row};
7687
struct spi_buf_set tx = {.buffers = &tx_buf, .count = 1};
7788

7889
ret = spi_write_dt(&config->spi, &tx);
@@ -97,16 +108,23 @@ static void hub12_scan_row(struct hub12_data *data, const struct hub12_config *c
97108
}
98109

99110
data->current_row = (data->current_row + 1) % HUB12_ROWS;
100-
101-
hub12_update_cache(data, data->current_row);
102111
}
103112

104113
static void hub12_scan_work_handler(struct k_work *work)
105114
{
106115
struct hub12_data *data = CONTAINER_OF(work, struct hub12_data, scan_work);
107116
const struct hub12_config *config = data->dev->config;
108117

109-
hub12_scan_row(data, config);
118+
/* K_NO_WAIT to avoid blocking work queue */
119+
if (k_mutex_lock(&data->lock, K_NO_WAIT) == 0) {
120+
hub12_scan_row(data, config);
121+
122+
uint8_t next_row = data->current_row;
123+
124+
hub12_update_cache(data, config, next_row);
125+
126+
k_mutex_unlock(&data->lock);
127+
}
110128
}
111129

112130
static void hub12_scan_timer_handler(struct k_timer *timer)
@@ -142,7 +160,7 @@ static int hub12_write(const struct device *dev, const uint16_t x, const uint16_
142160
return -EINVAL;
143161
}
144162

145-
k_sem_take(&data->lock, K_FOREVER);
163+
k_mutex_lock(&data->lock, K_FOREVER);
146164

147165
if (x == 0 && y == 0 && desc->width == config->width && desc->height == config->height) {
148166
memcpy(data->framebuffer, src, fb_size);
@@ -175,10 +193,10 @@ static int hub12_write(const struct device *dev, const uint16_t x, const uint16_
175193
}
176194

177195
for (int i = 0; i < HUB12_ROWS; i++) {
178-
hub12_update_cache(data, i);
196+
hub12_update_cache(data, config, i);
179197
}
180198

181-
k_sem_give(&data->lock);
199+
k_mutex_unlock(&data->lock);
182200

183201
return 0;
184202
}
@@ -280,10 +298,16 @@ static int hub12_init(const struct device *dev)
280298

281299
data->dev = dev;
282300

283-
/* Only supporting single, unchained panels for now */
284-
if (config->width != 32 || config->height != 16) {
285-
LOG_ERR("Unsupported dimensions %dx%d. Only 32x16 panels supported", config->width,
286-
config->height);
301+
/* Validate dimensions support horizontal chaining */
302+
if (config->height != HUB12_PANEL_HEIGHT) {
303+
LOG_ERR("Unsupported height %d. Only %d supported", config->height,
304+
HUB12_PANEL_HEIGHT);
305+
return -ENOTSUP;
306+
}
307+
308+
if (config->width % HUB12_PANEL_WIDTH != 0) {
309+
LOG_ERR("Width %d must be multiple of %d for horizontal chaining", config->width,
310+
HUB12_PANEL_WIDTH);
287311
return -ENOTSUP;
288312
}
289313

@@ -319,25 +343,26 @@ static int hub12_init(const struct device *dev)
319343
}
320344

321345
memset(data->framebuffer, 0, (config->width * config->height) / HUB12_PIXELS_PER_BYTE);
322-
memset(data->cache, 0, sizeof(data->cache));
346+
memset(data->cache, 0, HUB12_ROWS * config->bytes_per_row);
323347
data->current_row = 0;
324348
data->brightness_us = HUB12_DEFAULT_BRIGHTNESS;
325349

326-
ret = k_sem_init(&data->lock, 1, 1);
350+
ret = k_mutex_init(&data->lock);
327351
if (ret < 0) {
328-
LOG_ERR("Failed to initialize semaphore");
352+
LOG_ERR("Failed to initialize mutex");
329353
return ret;
330354
}
331355

332356
for (int i = 0; i < HUB12_ROWS; i++) {
333-
hub12_update_cache(data, i);
357+
hub12_update_cache(data, config, i);
334358
}
335359

336360
k_work_init(&data->scan_work, hub12_scan_work_handler);
337361
k_timer_init(&data->scan_timer, hub12_scan_timer_handler, NULL);
338362
k_timer_start(&data->scan_timer, K_MSEC(1), K_MSEC(1));
339363

340-
LOG_INF("HUB12 display initialized: %dx%d", config->width, config->height);
364+
LOG_INF("HUB12 display initialized: %dx%d (%d panels)", config->width, config->height,
365+
config->num_panels);
341366

342367
return 0;
343368
}
@@ -346,8 +371,12 @@ static int hub12_init(const struct device *dev)
346371
static uint8_t hub12_framebuffer_##inst[(DT_INST_PROP(inst, width) * \
347372
DT_INST_PROP(inst, height)) / \
348373
HUB12_PIXELS_PER_BYTE]; \
374+
static uint8_t \
375+
hub12_cache_##inst[HUB12_ROWS * ((DT_INST_PROP(inst, width) / HUB12_PANEL_WIDTH) * \
376+
HUB12_BYTES_PER_PANEL)]; \
349377
static struct hub12_data hub12_data_##inst = { \
350378
.framebuffer = hub12_framebuffer_##inst, \
379+
.cache = hub12_cache_##inst, \
351380
}; \
352381
\
353382
static const struct hub12_config hub12_config_##inst = { \
@@ -358,6 +387,9 @@ static int hub12_init(const struct device *dev)
358387
.spi = SPI_DT_SPEC_INST_GET(inst, SPI_OP_MODE_MASTER | SPI_WORD_SET(8)), \
359388
.width = DT_INST_PROP(inst, width), \
360389
.height = DT_INST_PROP(inst, height), \
390+
.num_panels = DT_INST_PROP(inst, width) / HUB12_PANEL_WIDTH, \
391+
.bytes_per_row = \
392+
(DT_INST_PROP(inst, width) / HUB12_PANEL_WIDTH) * HUB12_BYTES_PER_PANEL, \
361393
}; \
362394
\
363395
DEVICE_DT_INST_DEFINE(inst, hub12_init, NULL, &hub12_data_##inst, &hub12_config_##inst, \

dts/bindings/display/zephyr,hub12.yaml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ description: |
1111
plus additional GPIO pins for row address selection (PA, PB),
1212
output enable (PE), and data latching (PLAT).
1313
14+
Supports horizontal chaining of multiple 32x16 panels by increasing the width
15+
property. For example, width=64 chains two panels horizontally, width=96 chains
16+
three panels, etc.
17+
1418
See:
1519
- https://www.auselectronicsdirect.com.au/assets/files/TA0094%20and%20TA0095%20user%20manual.pdf
1620
- https://www.researchgate.net/publication/377952697_Light_Emitting_Diode_LED_Matrix_Display_Board#pf7
@@ -45,11 +49,8 @@ properties:
4549
width:
4650
type: int
4751
required: true
48-
enum:
49-
- 32
5052
description: |
51-
Display width in pixels. Must match the physical hardware panel.
52-
Currently only single 32-pixel wide panels are supported.
53+
Display width in pixels. Must be a multiple of 32 for horizontal chaining.
5354
5455
height:
5556
type: int

0 commit comments

Comments
 (0)