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
4447struct 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
7080static 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
104113static 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
112130static 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, \
0 commit comments