Skip to content

Commit a042872

Browse files
geertuojeda
authored andcommitted
auxdisplay: ht16k33: Add support for segment displays
The Holtek HT16K33 LED controller is not only used for driving dot-matrix displays, but also for driving segment displays. Add support for 4-digit 7-segment and quad 14-segment alphanumeric displays, like the Adafruit 7-segment and 14-segment display backpack and FeatherWing expansion boards. Use the character line display core support to display a message, which will be scrolled if it doesn't fit. Signed-off-by: Geert Uytterhoeven <[email protected]> Acked-by: Robin van der Gracht <[email protected]> Signed-off-by: Miguel Ojeda <[email protected]>
1 parent fcbb3c3 commit a042872

File tree

2 files changed

+190
-7
lines changed

2 files changed

+190
-7
lines changed

drivers/auxdisplay/Kconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ config HT16K33
176176
select FB_SYS_IMAGEBLIT
177177
select INPUT_MATRIXKMAP
178178
select FB_BACKLIGHT
179+
select LINEDISP
179180
help
180181
Say yes here to add support for Holtek HT16K33, RAM mapping 16*8
181182
LED controller driver with keyscan.

drivers/auxdisplay/ht16k33.c

Lines changed: 189 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
* Author: Robin van der Gracht <[email protected]>
66
*
77
* Copyright: (C) 2016 Protonic Holland.
8+
* Copyright (C) 2021 Glider bv
89
*/
910

1011
#include <linux/kernel.h>
@@ -20,6 +21,13 @@
2021
#include <linux/workqueue.h>
2122
#include <linux/mm.h>
2223

24+
#include <linux/map_to_7segment.h>
25+
#include <linux/map_to_14segment.h>
26+
27+
#include <asm/unaligned.h>
28+
29+
#include "line-display.h"
30+
2331
/* Registers */
2432
#define REG_SYSTEM_SETUP 0x20
2533
#define REG_SYSTEM_SETUP_OSC_ON BIT(0)
@@ -47,6 +55,12 @@
4755
#define BYTES_PER_ROW (HT16K33_MATRIX_LED_MAX_ROWS / 8)
4856
#define HT16K33_FB_SIZE (HT16K33_MATRIX_LED_MAX_COLS * BYTES_PER_ROW)
4957

58+
enum display_type {
59+
DISP_MATRIX = 0,
60+
DISP_QUAD_7SEG,
61+
DISP_QUAD_14SEG,
62+
};
63+
5064
struct ht16k33_keypad {
5165
struct i2c_client *client;
5266
struct input_dev *dev;
@@ -67,11 +81,25 @@ struct ht16k33_fbdev {
6781
uint8_t *cache;
6882
};
6983

84+
struct ht16k33_seg {
85+
struct linedisp linedisp;
86+
union {
87+
struct seg7_conversion_map seg7;
88+
struct seg14_conversion_map seg14;
89+
} map;
90+
unsigned int map_size;
91+
char curr[4];
92+
};
93+
7094
struct ht16k33_priv {
7195
struct i2c_client *client;
7296
struct delayed_work work;
7397
struct ht16k33_keypad keypad;
74-
struct ht16k33_fbdev fbdev;
98+
union {
99+
struct ht16k33_fbdev fbdev;
100+
struct ht16k33_seg seg;
101+
};
102+
enum display_type type;
75103
};
76104

77105
static const struct fb_fix_screeninfo ht16k33_fb_fix = {
@@ -101,6 +129,33 @@ static const struct fb_var_screeninfo ht16k33_fb_var = {
101129
.vmode = FB_VMODE_NONINTERLACED,
102130
};
103131

132+
static const SEG7_DEFAULT_MAP(initial_map_seg7);
133+
static const SEG14_DEFAULT_MAP(initial_map_seg14);
134+
135+
static ssize_t map_seg_show(struct device *dev, struct device_attribute *attr,
136+
char *buf)
137+
{
138+
struct ht16k33_priv *priv = dev_get_drvdata(dev);
139+
140+
memcpy(buf, &priv->seg.map, priv->seg.map_size);
141+
return priv->seg.map_size;
142+
}
143+
144+
static ssize_t map_seg_store(struct device *dev, struct device_attribute *attr,
145+
const char *buf, size_t cnt)
146+
{
147+
struct ht16k33_priv *priv = dev_get_drvdata(dev);
148+
149+
if (cnt != priv->seg.map_size)
150+
return -EINVAL;
151+
152+
memcpy(&priv->seg.map, buf, cnt);
153+
return cnt;
154+
}
155+
156+
static DEVICE_ATTR(map_seg7, 0644, map_seg_show, map_seg_store);
157+
static DEVICE_ATTR(map_seg14, 0644, map_seg_show, map_seg_store);
158+
104159
static int ht16k33_display_on(struct ht16k33_priv *priv)
105160
{
106161
uint8_t data = REG_DISPLAY_SETUP | REG_DISPLAY_SETUP_ON;
@@ -335,6 +390,51 @@ static void ht16k33_keypad_stop(struct input_dev *dev)
335390
disable_irq(keypad->client->irq);
336391
}
337392

393+
static void ht16k33_linedisp_update(struct linedisp *linedisp)
394+
{
395+
struct ht16k33_priv *priv = container_of(linedisp, struct ht16k33_priv,
396+
seg.linedisp);
397+
398+
schedule_delayed_work(&priv->work, 0);
399+
}
400+
401+
static void ht16k33_seg7_update(struct work_struct *work)
402+
{
403+
struct ht16k33_priv *priv = container_of(work, struct ht16k33_priv,
404+
work.work);
405+
struct ht16k33_seg *seg = &priv->seg;
406+
char *s = seg->curr;
407+
uint8_t buf[9];
408+
409+
buf[0] = map_to_seg7(&seg->map.seg7, *s++);
410+
buf[1] = 0;
411+
buf[2] = map_to_seg7(&seg->map.seg7, *s++);
412+
buf[3] = 0;
413+
buf[4] = 0;
414+
buf[5] = 0;
415+
buf[6] = map_to_seg7(&seg->map.seg7, *s++);
416+
buf[7] = 0;
417+
buf[8] = map_to_seg7(&seg->map.seg7, *s++);
418+
419+
i2c_smbus_write_i2c_block_data(priv->client, 0, ARRAY_SIZE(buf), buf);
420+
}
421+
422+
static void ht16k33_seg14_update(struct work_struct *work)
423+
{
424+
struct ht16k33_priv *priv = container_of(work, struct ht16k33_priv,
425+
work.work);
426+
struct ht16k33_seg *seg = &priv->seg;
427+
char *s = seg->curr;
428+
uint8_t buf[8];
429+
430+
put_unaligned_le16(map_to_seg14(&seg->map.seg14, *s++), buf);
431+
put_unaligned_le16(map_to_seg14(&seg->map.seg14, *s++), buf + 2);
432+
put_unaligned_le16(map_to_seg14(&seg->map.seg14, *s++), buf + 4);
433+
put_unaligned_le16(map_to_seg14(&seg->map.seg14, *s++), buf + 6);
434+
435+
i2c_smbus_write_i2c_block_data(priv->client, 0, ARRAY_SIZE(buf), buf);
436+
}
437+
338438
static int ht16k33_keypad_probe(struct i2c_client *client,
339439
struct ht16k33_keypad *keypad)
340440
{
@@ -479,9 +579,56 @@ static int ht16k33_fbdev_probe(struct device *dev, struct ht16k33_priv *priv,
479579
return err;
480580
}
481581

582+
static int ht16k33_seg_probe(struct device *dev, struct ht16k33_priv *priv,
583+
uint32_t brightness)
584+
{
585+
struct ht16k33_seg *seg = &priv->seg;
586+
int err;
587+
588+
err = ht16k33_brightness_set(priv, brightness);
589+
if (err)
590+
return err;
591+
592+
switch (priv->type) {
593+
case DISP_MATRIX:
594+
/* not handled here */
595+
err = -EINVAL;
596+
break;
597+
598+
case DISP_QUAD_7SEG:
599+
INIT_DELAYED_WORK(&priv->work, ht16k33_seg7_update);
600+
seg->map.seg7 = initial_map_seg7;
601+
seg->map_size = sizeof(seg->map.seg7);
602+
err = device_create_file(dev, &dev_attr_map_seg7);
603+
break;
604+
605+
case DISP_QUAD_14SEG:
606+
INIT_DELAYED_WORK(&priv->work, ht16k33_seg14_update);
607+
seg->map.seg14 = initial_map_seg14;
608+
seg->map_size = sizeof(seg->map.seg14);
609+
err = device_create_file(dev, &dev_attr_map_seg14);
610+
break;
611+
}
612+
if (err)
613+
return err;
614+
615+
err = linedisp_register(&seg->linedisp, dev, 4, seg->curr,
616+
ht16k33_linedisp_update);
617+
if (err)
618+
goto err_remove_map_file;
619+
620+
return 0;
621+
622+
err_remove_map_file:
623+
device_remove_file(dev, &dev_attr_map_seg7);
624+
device_remove_file(dev, &dev_attr_map_seg14);
625+
return err;
626+
}
627+
482628
static int ht16k33_probe(struct i2c_client *client)
483629
{
484630
struct device *dev = &client->dev;
631+
const struct of_device_id *id;
485632
struct ht16k33_priv *priv;
486633
uint32_t dft_brightness;
487634
int err;
@@ -496,6 +643,9 @@ static int ht16k33_probe(struct i2c_client *client)
496643
return -ENOMEM;
497644

498645
priv->client = client;
646+
id = i2c_of_match_device(dev->driver->of_match_table, client);
647+
if (id)
648+
priv->type = (uintptr_t)id->data;
499649
i2c_set_clientdata(client, priv);
500650

501651
err = ht16k33_initialize(priv);
@@ -520,8 +670,19 @@ static int ht16k33_probe(struct i2c_client *client)
520670
return err;
521671
}
522672

523-
/* Frame Buffer Display */
524-
return ht16k33_fbdev_probe(dev, priv, dft_brightness);
673+
switch (priv->type) {
674+
case DISP_MATRIX:
675+
/* Frame Buffer Display */
676+
err = ht16k33_fbdev_probe(dev, priv, dft_brightness);
677+
break;
678+
679+
case DISP_QUAD_7SEG:
680+
case DISP_QUAD_14SEG:
681+
/* Segment Display */
682+
err = ht16k33_seg_probe(dev, priv, dft_brightness);
683+
break;
684+
}
685+
return err;
525686
}
526687

527688
static int ht16k33_remove(struct i2c_client *client)
@@ -530,9 +691,21 @@ static int ht16k33_remove(struct i2c_client *client)
530691
struct ht16k33_fbdev *fbdev = &priv->fbdev;
531692

532693
cancel_delayed_work_sync(&priv->work);
533-
unregister_framebuffer(fbdev->info);
534-
framebuffer_release(fbdev->info);
535-
free_page((unsigned long) fbdev->buffer);
694+
695+
switch (priv->type) {
696+
case DISP_MATRIX:
697+
unregister_framebuffer(fbdev->info);
698+
framebuffer_release(fbdev->info);
699+
free_page((unsigned long)fbdev->buffer);
700+
break;
701+
702+
case DISP_QUAD_7SEG:
703+
case DISP_QUAD_14SEG:
704+
linedisp_unregister(&priv->seg.linedisp);
705+
device_remove_file(&client->dev, &dev_attr_map_seg7);
706+
device_remove_file(&client->dev, &dev_attr_map_seg14);
707+
break;
708+
}
536709

537710
return 0;
538711
}
@@ -544,7 +717,16 @@ static const struct i2c_device_id ht16k33_i2c_match[] = {
544717
MODULE_DEVICE_TABLE(i2c, ht16k33_i2c_match);
545718

546719
static const struct of_device_id ht16k33_of_match[] = {
547-
{ .compatible = "holtek,ht16k33", },
720+
{
721+
/* 0.56" 4-Digit 7-Segment FeatherWing Display (Red) */
722+
.compatible = "adafruit,3108", .data = (void *)DISP_QUAD_7SEG,
723+
}, {
724+
/* 0.54" Quad Alphanumeric FeatherWing Display (Red) */
725+
.compatible = "adafruit,3130", .data = (void *)DISP_QUAD_14SEG,
726+
}, {
727+
/* Generic, assumed Dot-Matrix Display */
728+
.compatible = "holtek,ht16k33", .data = (void *)DISP_MATRIX,
729+
},
548730
{ }
549731
};
550732
MODULE_DEVICE_TABLE(of, ht16k33_of_match);

0 commit comments

Comments
 (0)