From 021515abb9b44630f56a29de8d453efb487603aa Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 12 Feb 2024 13:33:57 +0200 Subject: auxdisplay: Take over maintainership, but in Odd Fixes mode I have no time for this, but since it looks like I'm the main contributor for the last few years to the subsystem, I'll take it for now. Geert agreed to help me as a designated reviewer. Let's see how it will go... Acked-by: Geert Uytterhoeven Acked-by: Miguel Ojeda Signed-off-by: Andy Shevchenko --- MAINTAINERS | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/MAINTAINERS b/MAINTAINERS index 8d1052fa6a69..3ab80c4ab9e3 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3376,8 +3376,10 @@ F: drivers/base/auxiliary.c F: include/linux/auxiliary_bus.h AUXILIARY DISPLAY DRIVERS -M: Miguel Ojeda -S: Maintained +M: Andy Shevchenko +R: Geert Uytterhoeven +S: Odd Fixes +T: git git://git.kernel.org/pub/scm/linux/kernel/git/andy/linux-auxdisplay.git F: Documentation/devicetree/bindings/auxdisplay/ F: drivers/auxdisplay/ F: include/linux/cfag12864b.h -- cgit From 933feb122336bd02f8fc4a084d5e7f010bc24614 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Wed, 14 Feb 2024 20:54:46 +0200 Subject: auxdisplay: Add 7 and 14 segment mappings to MAINTAINERS The mapping files are tightly related to auxdisplay subsystem. Add them to the MAINTAINERS database. Signed-off-by: Andy Shevchenko Reviewed-by: Geert Uytterhoeven --- MAINTAINERS | 2 ++ 1 file changed, 2 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 3ab80c4ab9e3..68cfb0b9f558 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3383,6 +3383,8 @@ T: git git://git.kernel.org/pub/scm/linux/kernel/git/andy/linux-auxdisplay.git F: Documentation/devicetree/bindings/auxdisplay/ F: drivers/auxdisplay/ F: include/linux/cfag12864b.h +F: include/uapi/linux/map_to_14segment.h +F: include/uapi/linux/map_to_7segment.h AVIA HX711 ANALOG DIGITAL CONVERTER IIO DRIVER M: Andreas Klinger -- cgit From 961454590d4d9a50114e729b16d2bbfe05f1cc02 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Wed, 14 Feb 2024 16:54:37 +0100 Subject: dt-bindings: auxdisplay: adjust example indentation and use generic node names The example DTS should be indented with two or four (preferred) spaces, as mentioned in Writing Schema document. While re-indenting, change the node names to somehow generic names, as expected by Devicetree specification. Cc: Geert Uytterhoeven Cc: Ralf Schlatterbeck Cc: Andy Shevchenko Reviewed-by: Andy Shevchenko Signed-off-by: Krzysztof Kozlowski Reviewed-by: Geert Uytterhoeven Signed-off-by: Andy Shevchenko --- .../bindings/auxdisplay/arm,versatile-lcd.yaml | 4 +- .../bindings/auxdisplay/hit,hd44780.yaml | 68 +++++++++++----------- .../bindings/auxdisplay/holtek,ht16k33.yaml | 54 ++++++++--------- .../bindings/auxdisplay/img,ascii-lcd.yaml | 4 +- 4 files changed, 66 insertions(+), 64 deletions(-) diff --git a/Documentation/devicetree/bindings/auxdisplay/arm,versatile-lcd.yaml b/Documentation/devicetree/bindings/auxdisplay/arm,versatile-lcd.yaml index 5d02bd032a85..439f7b811a94 100644 --- a/Documentation/devicetree/bindings/auxdisplay/arm,versatile-lcd.yaml +++ b/Documentation/devicetree/bindings/auxdisplay/arm,versatile-lcd.yaml @@ -39,6 +39,6 @@ additionalProperties: false examples: - | lcd@10008000 { - compatible = "arm,versatile-lcd"; - reg = <0x10008000 0x1000>; + compatible = "arm,versatile-lcd"; + reg = <0x10008000 0x1000>; }; diff --git a/Documentation/devicetree/bindings/auxdisplay/hit,hd44780.yaml b/Documentation/devicetree/bindings/auxdisplay/hit,hd44780.yaml index 406a922a714e..e26d61af9011 100644 --- a/Documentation/devicetree/bindings/auxdisplay/hit,hd44780.yaml +++ b/Documentation/devicetree/bindings/auxdisplay/hit,hd44780.yaml @@ -84,42 +84,44 @@ additionalProperties: false examples: - | #include - auxdisplay { - compatible = "hit,hd44780"; - - data-gpios = <&hc595 0 GPIO_ACTIVE_HIGH>, - <&hc595 1 GPIO_ACTIVE_HIGH>, - <&hc595 2 GPIO_ACTIVE_HIGH>, - <&hc595 3 GPIO_ACTIVE_HIGH>; - enable-gpios = <&hc595 4 GPIO_ACTIVE_HIGH>; - rs-gpios = <&hc595 5 GPIO_ACTIVE_HIGH>; - - display-height-chars = <2>; - display-width-chars = <16>; + display-controller { + compatible = "hit,hd44780"; + + data-gpios = <&hc595 0 GPIO_ACTIVE_HIGH>, + <&hc595 1 GPIO_ACTIVE_HIGH>, + <&hc595 2 GPIO_ACTIVE_HIGH>, + <&hc595 3 GPIO_ACTIVE_HIGH>; + enable-gpios = <&hc595 4 GPIO_ACTIVE_HIGH>; + rs-gpios = <&hc595 5 GPIO_ACTIVE_HIGH>; + + display-height-chars = <2>; + display-width-chars = <16>; }; + - | #include i2c { - #address-cells = <1>; - #size-cells = <0>; - - pcf8574: pcf8574@27 { - compatible = "nxp,pcf8574"; - reg = <0x27>; - gpio-controller; - #gpio-cells = <2>; - }; + #address-cells = <1>; + #size-cells = <0>; + + pcf8574: gpio-expander@27 { + compatible = "nxp,pcf8574"; + reg = <0x27>; + gpio-controller; + #gpio-cells = <2>; + }; }; - hd44780 { - compatible = "hit,hd44780"; - display-height-chars = <2>; - display-width-chars = <16>; - data-gpios = <&pcf8574 4 0>, - <&pcf8574 5 0>, - <&pcf8574 6 0>, - <&pcf8574 7 0>; - enable-gpios = <&pcf8574 2 0>; - rs-gpios = <&pcf8574 0 0>; - rw-gpios = <&pcf8574 1 0>; - backlight-gpios = <&pcf8574 3 0>; + + display-controller { + compatible = "hit,hd44780"; + display-height-chars = <2>; + display-width-chars = <16>; + data-gpios = <&pcf8574 4 0>, + <&pcf8574 5 0>, + <&pcf8574 6 0>, + <&pcf8574 7 0>; + enable-gpios = <&pcf8574 2 0>; + rs-gpios = <&pcf8574 0 0>; + rw-gpios = <&pcf8574 1 0>; + backlight-gpios = <&pcf8574 3 0>; }; diff --git a/Documentation/devicetree/bindings/auxdisplay/holtek,ht16k33.yaml b/Documentation/devicetree/bindings/auxdisplay/holtek,ht16k33.yaml index be95f6b97b41..b90eec2077b4 100644 --- a/Documentation/devicetree/bindings/auxdisplay/holtek,ht16k33.yaml +++ b/Documentation/devicetree/bindings/auxdisplay/holtek,ht16k33.yaml @@ -74,31 +74,31 @@ examples: #include #include i2c { - #address-cells = <1>; - #size-cells = <0>; - - ht16k33: ht16k33@70 { - compatible = "holtek,ht16k33"; - reg = <0x70>; - refresh-rate-hz = <20>; - interrupt-parent = <&gpio4>; - interrupts = <5 (IRQ_TYPE_LEVEL_HIGH | IRQ_TYPE_EDGE_RISING)>; - debounce-delay-ms = <50>; - linux,keymap = , - , - , - , - , - , - , - , - , - ; - - led { - color = ; - function = LED_FUNCTION_BACKLIGHT; - linux,default-trigger = "backlight"; - }; + #address-cells = <1>; + #size-cells = <0>; + + display-controller@70 { + compatible = "holtek,ht16k33"; + reg = <0x70>; + refresh-rate-hz = <20>; + interrupt-parent = <&gpio4>; + interrupts = <5 (IRQ_TYPE_LEVEL_HIGH | IRQ_TYPE_EDGE_RISING)>; + debounce-delay-ms = <50>; + linux,keymap = , + , + , + , + , + , + , + , + , + ; + + led { + color = ; + function = LED_FUNCTION_BACKLIGHT; + linux,default-trigger = "backlight"; }; - }; + }; + }; diff --git a/Documentation/devicetree/bindings/auxdisplay/img,ascii-lcd.yaml b/Documentation/devicetree/bindings/auxdisplay/img,ascii-lcd.yaml index 1899b23de7d1..55e9831b3f67 100644 --- a/Documentation/devicetree/bindings/auxdisplay/img,ascii-lcd.yaml +++ b/Documentation/devicetree/bindings/auxdisplay/img,ascii-lcd.yaml @@ -50,6 +50,6 @@ additionalProperties: false examples: - | lcd: lcd@17fff000 { - compatible = "img,boston-lcd"; - reg = <0x17fff000 0x8>; + compatible = "img,boston-lcd"; + reg = <0x17fff000 0x8>; }; -- cgit From 4fdcbb88b1a8203d95b13a43545c1a330c1cc526 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Wed, 14 Feb 2024 16:54:38 +0100 Subject: dt-bindings: auxdisplay: hit,hd44780: use defines for GPIO flags Improve example DTS readability by using known defines for GPIO flags. Cc: Andy Shevchenko Reviewed-by: Andy Shevchenko Signed-off-by: Krzysztof Kozlowski Reviewed-by: Geert Uytterhoeven Signed-off-by: Andy Shevchenko --- .../devicetree/bindings/auxdisplay/hit,hd44780.yaml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Documentation/devicetree/bindings/auxdisplay/hit,hd44780.yaml b/Documentation/devicetree/bindings/auxdisplay/hit,hd44780.yaml index e26d61af9011..3ca0e9863d83 100644 --- a/Documentation/devicetree/bindings/auxdisplay/hit,hd44780.yaml +++ b/Documentation/devicetree/bindings/auxdisplay/hit,hd44780.yaml @@ -116,12 +116,12 @@ examples: compatible = "hit,hd44780"; display-height-chars = <2>; display-width-chars = <16>; - data-gpios = <&pcf8574 4 0>, - <&pcf8574 5 0>, - <&pcf8574 6 0>, - <&pcf8574 7 0>; - enable-gpios = <&pcf8574 2 0>; - rs-gpios = <&pcf8574 0 0>; - rw-gpios = <&pcf8574 1 0>; - backlight-gpios = <&pcf8574 3 0>; + data-gpios = <&pcf8574 4 GPIO_ACTIVE_HIGH>, + <&pcf8574 5 GPIO_ACTIVE_HIGH>, + <&pcf8574 6 GPIO_ACTIVE_HIGH>, + <&pcf8574 7 GPIO_ACTIVE_HIGH>; + enable-gpios = <&pcf8574 2 GPIO_ACTIVE_HIGH>; + rs-gpios = <&pcf8574 0 GPIO_ACTIVE_HIGH>; + rw-gpios = <&pcf8574 1 GPIO_ACTIVE_HIGH>; + backlight-gpios = <&pcf8574 3 GPIO_ACTIVE_HIGH>; }; -- cgit From b33190d0fd9099e7eed208716269c53a8a536dfa Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 15 Feb 2021 22:19:53 +0200 Subject: auxdisplay: panel: Switch to use module_parport_driver() Switch to use module_parport_driver() to reduce boilerplate code. Signed-off-by: Andy Shevchenko --- drivers/auxdisplay/panel.c | 202 ++++++++++++++++++++------------------------- 1 file changed, 89 insertions(+), 113 deletions(-) diff --git a/drivers/auxdisplay/panel.c b/drivers/auxdisplay/panel.c index e20d35bdf5fe..049ff443e790 100644 --- a/drivers/auxdisplay/panel.c +++ b/drivers/auxdisplay/panel.c @@ -1519,106 +1519,9 @@ static void keypad_init(void) static void panel_attach(struct parport *port) { + int selected_keypad_type = NOT_SET; struct pardev_cb panel_cb; - if (port->number != parport) - return; - - if (pprt) { - pr_err("%s: port->number=%d parport=%d, already registered!\n", - __func__, port->number, parport); - return; - } - - memset(&panel_cb, 0, sizeof(panel_cb)); - panel_cb.private = &pprt; - /* panel_cb.flags = 0 should be PARPORT_DEV_EXCL? */ - - pprt = parport_register_dev_model(port, "panel", &panel_cb, 0); - if (!pprt) { - pr_err("%s: port->number=%d parport=%d, parport_register_device() failed\n", - __func__, port->number, parport); - return; - } - - if (parport_claim(pprt)) { - pr_err("could not claim access to parport%d. Aborting.\n", - parport); - goto err_unreg_device; - } - - /* must init LCD first, just in case an IRQ from the keypad is - * generated at keypad init - */ - if (lcd.enabled) { - lcd_init(); - if (!lcd.charlcd || charlcd_register(lcd.charlcd)) - goto err_unreg_device; - } - - if (keypad.enabled) { - keypad_init(); - if (misc_register(&keypad_dev)) - goto err_lcd_unreg; - } - return; - -err_lcd_unreg: - if (scan_timer.function) - del_timer_sync(&scan_timer); - if (lcd.enabled) - charlcd_unregister(lcd.charlcd); -err_unreg_device: - kfree(lcd.charlcd); - lcd.charlcd = NULL; - parport_unregister_device(pprt); - pprt = NULL; -} - -static void panel_detach(struct parport *port) -{ - if (port->number != parport) - return; - - if (!pprt) { - pr_err("%s: port->number=%d parport=%d, nothing to unregister.\n", - __func__, port->number, parport); - return; - } - if (scan_timer.function) - del_timer_sync(&scan_timer); - - if (keypad.enabled) { - misc_deregister(&keypad_dev); - keypad_initialized = 0; - } - - if (lcd.enabled) { - charlcd_unregister(lcd.charlcd); - lcd.initialized = false; - kfree(lcd.charlcd->drvdata); - kfree(lcd.charlcd); - lcd.charlcd = NULL; - } - - /* TODO: free all input signals */ - parport_release(pprt); - parport_unregister_device(pprt); - pprt = NULL; -} - -static struct parport_driver panel_driver = { - .name = "panel", - .match_port = panel_attach, - .detach = panel_detach, - .devmodel = true, -}; - -/* init function */ -static int __init panel_init_module(void) -{ - int selected_keypad_type = NOT_SET, err; - /* take care of an eventual profile */ switch (profile) { case PANEL_PROFILE_CUSTOM: @@ -1710,29 +1613,102 @@ static int __init panel_init_module(void) if (!lcd.enabled && !keypad.enabled) { /* no device enabled, let's exit */ pr_err("panel driver disabled.\n"); - return -ENODEV; + return; } - err = parport_register_driver(&panel_driver); - if (err) { - pr_err("could not register with parport. Aborting.\n"); - return err; + if (port->number != parport) + return; + + if (pprt) { + pr_err("%s: port->number=%d parport=%d, already registered!\n", + __func__, port->number, parport); + return; } - if (pprt) - pr_info("panel driver registered on parport%d (io=0x%lx).\n", - parport, pprt->port->base); - else - pr_info("panel driver not yet registered\n"); - return 0; + memset(&panel_cb, 0, sizeof(panel_cb)); + panel_cb.private = &pprt; + /* panel_cb.flags = 0 should be PARPORT_DEV_EXCL? */ + + pprt = parport_register_dev_model(port, "panel", &panel_cb, 0); + if (!pprt) { + pr_err("%s: port->number=%d parport=%d, parport_register_device() failed\n", + __func__, port->number, parport); + return; + } + + if (parport_claim(pprt)) { + pr_err("could not claim access to parport%d. Aborting.\n", + parport); + goto err_unreg_device; + } + + /* must init LCD first, just in case an IRQ from the keypad is + * generated at keypad init + */ + if (lcd.enabled) { + lcd_init(); + if (!lcd.charlcd || charlcd_register(lcd.charlcd)) + goto err_unreg_device; + } + + if (keypad.enabled) { + keypad_init(); + if (misc_register(&keypad_dev)) + goto err_lcd_unreg; + } + return; + +err_lcd_unreg: + if (scan_timer.function) + del_timer_sync(&scan_timer); + if (lcd.enabled) + charlcd_unregister(lcd.charlcd); +err_unreg_device: + kfree(lcd.charlcd); + lcd.charlcd = NULL; + parport_unregister_device(pprt); + pprt = NULL; } -static void __exit panel_cleanup_module(void) +static void panel_detach(struct parport *port) { - parport_unregister_driver(&panel_driver); + if (port->number != parport) + return; + + if (!pprt) { + pr_err("%s: port->number=%d parport=%d, nothing to unregister.\n", + __func__, port->number, parport); + return; + } + if (scan_timer.function) + del_timer_sync(&scan_timer); + + if (keypad.enabled) { + misc_deregister(&keypad_dev); + keypad_initialized = 0; + } + + if (lcd.enabled) { + charlcd_unregister(lcd.charlcd); + lcd.initialized = false; + kfree(lcd.charlcd->drvdata); + kfree(lcd.charlcd); + lcd.charlcd = NULL; + } + + /* TODO: free all input signals */ + parport_release(pprt); + parport_unregister_device(pprt); + pprt = NULL; } -module_init(panel_init_module); -module_exit(panel_cleanup_module); +static struct parport_driver panel_driver = { + .name = "panel", + .match_port = panel_attach, + .detach = panel_detach, + .devmodel = true, +}; +module_parport_driver(panel_driver); + MODULE_AUTHOR("Willy Tarreau"); MODULE_LICENSE("GPL"); -- cgit From a8fc3d587fa6de33c2ade327bd9d4fcbb16148f0 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 12 Feb 2024 19:01:34 +0200 Subject: auxdisplay: img-ascii-lcd: Make container_of() no-op for struct linedisp Move embedded struct linedisp member to make container_of() no-op. Reviewed-by: Geert Uytterhoeven Tested-by: Geert Uytterhoeven Signed-off-by: Andy Shevchenko --- drivers/auxdisplay/img-ascii-lcd.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/auxdisplay/img-ascii-lcd.c b/drivers/auxdisplay/img-ascii-lcd.c index 56efda0740fb..09014ada38bd 100644 --- a/drivers/auxdisplay/img-ascii-lcd.c +++ b/drivers/auxdisplay/img-ascii-lcd.c @@ -32,21 +32,21 @@ struct img_ascii_lcd_config { /** * struct img_ascii_lcd_ctx - Private data structure + * @linedisp: line display structure * @base: the base address of the LCD registers * @regmap: the regmap through which LCD registers are accessed * @offset: the offset within regmap to the start of the LCD registers * @cfg: pointer to the LCD model configuration - * @linedisp: line display structure * @curr: the string currently displayed on the LCD */ struct img_ascii_lcd_ctx { + struct linedisp linedisp; union { void __iomem *base; struct regmap *regmap; }; u32 offset; const struct img_ascii_lcd_config *cfg; - struct linedisp linedisp; char curr[] __aligned(8); }; -- cgit From 2327960f0c5ecd7a285e69284b6ec3fdbd2757be Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 12 Feb 2024 19:01:35 +0200 Subject: auxdisplay: linedisp: Free allocated resources in ->release() While there is no issue currently with the resources allocation, the code may still be made more robust by deallocating message in the ->release() callback. Reviewed-by: Geert Uytterhoeven Tested-by: Geert Uytterhoeven Signed-off-by: Andy Shevchenko --- drivers/auxdisplay/line-display.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/auxdisplay/line-display.c b/drivers/auxdisplay/line-display.c index 03e7f104aa1a..310e9bfb41ae 100644 --- a/drivers/auxdisplay/line-display.c +++ b/drivers/auxdisplay/line-display.c @@ -188,8 +188,16 @@ static struct attribute *linedisp_attrs[] = { }; ATTRIBUTE_GROUPS(linedisp); +static void linedisp_release(struct device *dev) +{ + struct linedisp *linedisp = container_of(dev, struct linedisp, dev); + + kfree(linedisp->message); +} + static const struct device_type linedisp_type = { .groups = linedisp_groups, + .release = linedisp_release, }; /** @@ -253,7 +261,6 @@ void linedisp_unregister(struct linedisp *linedisp) { device_del(&linedisp->dev); del_timer_sync(&linedisp->timer); - kfree(linedisp->message); put_device(&linedisp->dev); } EXPORT_SYMBOL_GPL(linedisp_unregister); -- cgit From 50d6b9d56e69563d3b261ee25bc644c59c5bff58 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 12 Feb 2024 19:01:36 +0200 Subject: auxdisplay: linedisp: Use unique number for id The absence of decrementation of linedisp_id is incorrect in two ways, i.e. it may cause: - an ID exhaustion - (and if the above is addressed) a duplicate id number may be allocated next time a device is added Replace above mentioned approach by using IDA framework. Reviewed-by: Geert Uytterhoeven Tested-by: Geert Uytterhoeven Signed-off-by: Andy Shevchenko --- drivers/auxdisplay/line-display.c | 13 ++++++++++--- drivers/auxdisplay/line-display.h | 2 ++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/drivers/auxdisplay/line-display.c b/drivers/auxdisplay/line-display.c index 310e9bfb41ae..c4dbb13293d1 100644 --- a/drivers/auxdisplay/line-display.c +++ b/drivers/auxdisplay/line-display.c @@ -11,6 +11,7 @@ #include #include +#include #include #include #include @@ -188,11 +189,14 @@ static struct attribute *linedisp_attrs[] = { }; ATTRIBUTE_GROUPS(linedisp); +static DEFINE_IDA(linedisp_id); + static void linedisp_release(struct device *dev) { struct linedisp *linedisp = container_of(dev, struct linedisp, dev); kfree(linedisp->message); + ida_free(&linedisp_id, linedisp->id); } static const struct device_type linedisp_type = { @@ -214,7 +218,6 @@ int linedisp_register(struct linedisp *linedisp, struct device *parent, unsigned int num_chars, char *buf, void (*update)(struct linedisp *linedisp)) { - static atomic_t linedisp_id = ATOMIC_INIT(-1); int err; memset(linedisp, 0, sizeof(*linedisp)); @@ -225,9 +228,13 @@ int linedisp_register(struct linedisp *linedisp, struct device *parent, linedisp->num_chars = num_chars; linedisp->scroll_rate = DEFAULT_SCROLL_RATE; + err = ida_alloc(&linedisp_id, GFP_KERNEL); + if (err < 0) + return err; + linedisp->id = err; + device_initialize(&linedisp->dev); - dev_set_name(&linedisp->dev, "linedisp.%lu", - (unsigned long)atomic_inc_return(&linedisp_id)); + dev_set_name(&linedisp->dev, "linedisp.%u", linedisp->id); /* initialise a timer for scrolling the message */ timer_setup(&linedisp->timer, linedisp_scroll, 0); diff --git a/drivers/auxdisplay/line-display.h b/drivers/auxdisplay/line-display.h index 0f5891d34c48..5068e3445e4f 100644 --- a/drivers/auxdisplay/line-display.h +++ b/drivers/auxdisplay/line-display.h @@ -22,6 +22,7 @@ * @message_len: the length of the @message string * @scroll_pos: index of the first character of @message currently displayed * @scroll_rate: scroll interval in jiffies + * @id: instance id of this display */ struct linedisp { struct device dev; @@ -33,6 +34,7 @@ struct linedisp { unsigned int message_len; unsigned int scroll_pos; unsigned int scroll_rate; + unsigned int id; }; int linedisp_register(struct linedisp *linedisp, struct device *parent, -- cgit From 7168791fcab5bde00b6c777924addff014d0e359 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 12 Feb 2024 19:01:37 +0200 Subject: auxdisplay: linedisp: Unshadow error codes in ->store() kstrtox() may return different error codes. Unshadow them in the ->store() callback to give better error report. While at it, add missing kstrtox.h inclusion. Reviewed-by: Geert Uytterhoeven Tested-by: Geert Uytterhoeven Signed-off-by: Andy Shevchenko --- drivers/auxdisplay/line-display.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/auxdisplay/line-display.c b/drivers/auxdisplay/line-display.c index c4dbb13293d1..8d91c2099661 100644 --- a/drivers/auxdisplay/line-display.c +++ b/drivers/auxdisplay/line-display.c @@ -12,6 +12,7 @@ #include #include +#include #include #include #include @@ -166,9 +167,11 @@ static ssize_t scroll_step_ms_store(struct device *dev, { struct linedisp *linedisp = container_of(dev, struct linedisp, dev); unsigned int ms; + int err; - if (kstrtouint(buf, 10, &ms) != 0) - return -EINVAL; + err = kstrtouint(buf, 10, &ms); + if (err) + return err; linedisp->scroll_rate = msecs_to_jiffies(ms); if (linedisp->message && linedisp->message_len > linedisp->num_chars) { -- cgit From 6134b0be91f5d23a5000e5a0c3ee3d061bb4ad82 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 12 Feb 2024 19:01:38 +0200 Subject: auxdisplay: linedisp: Add missing header(s) Do not imply that some of the generic headers may be always included. Instead, include explicitly what we are direct user of. Reviewed-by: Geert Uytterhoeven Tested-by: Geert Uytterhoeven Signed-off-by: Andy Shevchenko --- drivers/auxdisplay/line-display.c | 3 +++ drivers/auxdisplay/line-display.h | 3 +++ 2 files changed, 6 insertions(+) diff --git a/drivers/auxdisplay/line-display.c b/drivers/auxdisplay/line-display.c index 8d91c2099661..a0339e4b5939 100644 --- a/drivers/auxdisplay/line-display.c +++ b/drivers/auxdisplay/line-display.c @@ -10,8 +10,11 @@ #include +#include #include +#include #include +#include #include #include #include diff --git a/drivers/auxdisplay/line-display.h b/drivers/auxdisplay/line-display.h index 5068e3445e4f..a6efda4ac11d 100644 --- a/drivers/auxdisplay/line-display.h +++ b/drivers/auxdisplay/line-display.h @@ -11,6 +11,9 @@ #ifndef _LINEDISP_H #define _LINEDISP_H +#include +#include + /** * struct linedisp - character line display private data structure * @dev: the line display device -- cgit From fe5bd82f5941e44f31aec72f5b29a3253bbebe11 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 12 Feb 2024 19:01:39 +0200 Subject: auxdisplay: linedisp: Move exported symbols to a namespace Avoid unnecessary pollution of the global symbol namespace by moving library functions in to a specific namespace and import that into the drivers that make use of the functions. For more info: https://lwn.net/Articles/760045/ Reviewed-by: Geert Uytterhoeven Tested-by: Geert Uytterhoeven Signed-off-by: Andy Shevchenko --- drivers/auxdisplay/ht16k33.c | 1 + drivers/auxdisplay/img-ascii-lcd.c | 1 + drivers/auxdisplay/line-display.c | 4 ++-- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/auxdisplay/ht16k33.c b/drivers/auxdisplay/ht16k33.c index a90430b7d07b..c6a42c5c128f 100644 --- a/drivers/auxdisplay/ht16k33.c +++ b/drivers/auxdisplay/ht16k33.c @@ -831,4 +831,5 @@ module_i2c_driver(ht16k33_driver); MODULE_DESCRIPTION("Holtek HT16K33 driver"); MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(LINEDISP); MODULE_AUTHOR("Robin van der Gracht "); diff --git a/drivers/auxdisplay/img-ascii-lcd.c b/drivers/auxdisplay/img-ascii-lcd.c index 09014ada38bd..c571e54d9eb5 100644 --- a/drivers/auxdisplay/img-ascii-lcd.c +++ b/drivers/auxdisplay/img-ascii-lcd.c @@ -298,3 +298,4 @@ module_platform_driver(img_ascii_lcd_driver); MODULE_DESCRIPTION("Imagination Technologies ASCII LCD Display"); MODULE_AUTHOR("Paul Burton "); MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(LINEDISP); diff --git a/drivers/auxdisplay/line-display.c b/drivers/auxdisplay/line-display.c index a0339e4b5939..8d0ebdf0f10d 100644 --- a/drivers/auxdisplay/line-display.c +++ b/drivers/auxdisplay/line-display.c @@ -263,7 +263,7 @@ out_del_timer: put_device(&linedisp->dev); return err; } -EXPORT_SYMBOL_GPL(linedisp_register); +EXPORT_SYMBOL_NS_GPL(linedisp_register, LINEDISP); /** * linedisp_unregister - unregister a character line display @@ -276,6 +276,6 @@ void linedisp_unregister(struct linedisp *linedisp) del_timer_sync(&linedisp->timer); put_device(&linedisp->dev); } -EXPORT_SYMBOL_GPL(linedisp_unregister); +EXPORT_SYMBOL_NS_GPL(linedisp_unregister, LINEDISP); MODULE_LICENSE("GPL"); -- cgit From 70fb97c0611ed76be5b44cbd3593d1c0b731321e Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 12 Feb 2024 19:01:41 +0200 Subject: auxdisplay: linedisp: Provide struct linedisp_ops for future extension Currently the line display library doesn't scale in case we want to provide more operations. Prepare the library to take a newly created struct linedisp_ops that scales. Reviewed-by: Geert Uytterhoeven Tested-by: Geert Uytterhoeven Signed-off-by: Andy Shevchenko --- drivers/auxdisplay/ht16k33.c | 6 +++++- drivers/auxdisplay/img-ascii-lcd.c | 18 ++++++++++++------ drivers/auxdisplay/line-display.c | 10 +++++----- drivers/auxdisplay/line-display.h | 16 +++++++++++++--- 4 files changed, 35 insertions(+), 15 deletions(-) diff --git a/drivers/auxdisplay/ht16k33.c b/drivers/auxdisplay/ht16k33.c index c6a42c5c128f..32d3afd29177 100644 --- a/drivers/auxdisplay/ht16k33.c +++ b/drivers/auxdisplay/ht16k33.c @@ -448,6 +448,10 @@ static void ht16k33_linedisp_update(struct linedisp *linedisp) schedule_delayed_work(&priv->work, 0); } +static const struct linedisp_ops ht16k33_linedisp_ops = { + .update = ht16k33_linedisp_update, +}; + static void ht16k33_seg7_update(struct work_struct *work) { struct ht16k33_priv *priv = container_of(work, struct ht16k33_priv, @@ -697,7 +701,7 @@ static int ht16k33_seg_probe(struct device *dev, struct ht16k33_priv *priv, return err; err = linedisp_register(&seg->linedisp, dev, 4, seg->curr, - ht16k33_linedisp_update); + &ht16k33_linedisp_ops); if (err) goto err_remove_map_file; diff --git a/drivers/auxdisplay/img-ascii-lcd.c b/drivers/auxdisplay/img-ascii-lcd.c index c571e54d9eb5..ecfb1c05bf55 100644 --- a/drivers/auxdisplay/img-ascii-lcd.c +++ b/drivers/auxdisplay/img-ascii-lcd.c @@ -22,12 +22,12 @@ struct img_ascii_lcd_ctx; * struct img_ascii_lcd_config - Configuration information about an LCD model * @num_chars: the number of characters the LCD can display * @external_regmap: true if registers are in a system controller, else false - * @update: function called to update the LCD + * @ops: character line display operations */ struct img_ascii_lcd_config { unsigned int num_chars; bool external_regmap; - void (*update)(struct linedisp *linedisp); + const struct linedisp_ops ops; }; /** @@ -75,7 +75,9 @@ static void boston_update(struct linedisp *linedisp) static struct img_ascii_lcd_config boston_config = { .num_chars = 8, - .update = boston_update, + .ops = { + .update = boston_update, + }, }; /* @@ -103,7 +105,9 @@ static void malta_update(struct linedisp *linedisp) static struct img_ascii_lcd_config malta_config = { .num_chars = 8, .external_regmap = true, - .update = malta_update, + .ops = { + .update = malta_update, + }, }; /* @@ -203,7 +207,9 @@ static void sead3_update(struct linedisp *linedisp) static struct img_ascii_lcd_config sead3_config = { .num_chars = 16, .external_regmap = true, - .update = sead3_update, + .ops = { + .update = sead3_update, + }, }; static const struct of_device_id img_ascii_lcd_matches[] = { @@ -248,7 +254,7 @@ static int img_ascii_lcd_probe(struct platform_device *pdev) } err = linedisp_register(&ctx->linedisp, dev, cfg->num_chars, ctx->curr, - cfg->update); + &cfg->ops); if (err) return err; diff --git a/drivers/auxdisplay/line-display.c b/drivers/auxdisplay/line-display.c index 8d0ebdf0f10d..8bb15bd166dd 100644 --- a/drivers/auxdisplay/line-display.c +++ b/drivers/auxdisplay/line-display.c @@ -50,7 +50,7 @@ static void linedisp_scroll(struct timer_list *t) } /* update the display */ - linedisp->update(linedisp); + linedisp->ops->update(linedisp); /* move on to the next character */ linedisp->scroll_pos++; @@ -94,7 +94,7 @@ static int linedisp_display(struct linedisp *linedisp, const char *msg, linedisp->message = NULL; linedisp->message_len = 0; memset(linedisp->buf, ' ', linedisp->num_chars); - linedisp->update(linedisp); + linedisp->ops->update(linedisp); return 0; } @@ -216,20 +216,20 @@ static const struct device_type linedisp_type = { * @parent: parent device * @num_chars: the number of characters that can be displayed * @buf: pointer to a buffer that can hold @num_chars characters - * @update: Function called to update the display. This must not sleep! + * @ops: character line display operations * * Return: zero on success, else a negative error code. */ int linedisp_register(struct linedisp *linedisp, struct device *parent, unsigned int num_chars, char *buf, - void (*update)(struct linedisp *linedisp)) + const struct linedisp_ops *ops) { int err; memset(linedisp, 0, sizeof(*linedisp)); linedisp->dev.parent = parent; linedisp->dev.type = &linedisp_type; - linedisp->update = update; + linedisp->ops = ops; linedisp->buf = buf; linedisp->num_chars = num_chars; linedisp->scroll_rate = DEFAULT_SCROLL_RATE; diff --git a/drivers/auxdisplay/line-display.h b/drivers/auxdisplay/line-display.h index a6efda4ac11d..88d051445599 100644 --- a/drivers/auxdisplay/line-display.h +++ b/drivers/auxdisplay/line-display.h @@ -14,11 +14,21 @@ #include #include +struct linedisp; + +/** + * struct linedisp_ops - character line display operations + * @update: Function called to update the display. This must not sleep! + */ +struct linedisp_ops { + void (*update)(struct linedisp *linedisp); +}; + /** * struct linedisp - character line display private data structure * @dev: the line display device * @timer: timer used to implement scrolling - * @update: function called to update the display + * @ops: character line display operations * @buf: pointer to the buffer for the string currently displayed * @message: the full message to display or scroll on the display * @num_chars: the number of characters that can be displayed @@ -30,7 +40,7 @@ struct linedisp { struct device dev; struct timer_list timer; - void (*update)(struct linedisp *linedisp); + const struct linedisp_ops *ops; char *buf; char *message; unsigned int num_chars; @@ -42,7 +52,7 @@ struct linedisp { int linedisp_register(struct linedisp *linedisp, struct device *parent, unsigned int num_chars, char *buf, - void (*update)(struct linedisp *linedisp)); + const struct linedisp_ops *ops); void linedisp_unregister(struct linedisp *linedisp); #endif /* LINEDISP_H */ -- cgit From 34ddc83dc72030ded90b5ff038cca67354ea8d34 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 12 Feb 2024 19:01:42 +0200 Subject: auxdisplay: linedisp: Add support for overriding character mapping There is already the driver using character mapping table for 7 or 14 segment display. It is possible to override it. Make the similar in the line display library to allow other drivers to utilise the same functionality. Reviewed-by: Geert Uytterhoeven Tested-by: Geert Uytterhoeven Signed-off-by: Andy Shevchenko --- drivers/auxdisplay/line-display.c | 111 +++++++++++++++++++++++++++++++++++++- drivers/auxdisplay/line-display.h | 31 +++++++++++ 2 files changed, 140 insertions(+), 2 deletions(-) diff --git a/drivers/auxdisplay/line-display.c b/drivers/auxdisplay/line-display.c index 8bb15bd166dd..13be7c2f6bc3 100644 --- a/drivers/auxdisplay/line-display.c +++ b/drivers/auxdisplay/line-display.c @@ -22,6 +22,9 @@ #include #include +#include +#include + #include "line-display.h" #define DEFAULT_SCROLL_RATE (HZ / 2) @@ -188,12 +191,71 @@ static ssize_t scroll_step_ms_store(struct device *dev, static DEVICE_ATTR_RW(scroll_step_ms); +static ssize_t map_seg_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct linedisp *linedisp = container_of(dev, struct linedisp, dev); + struct linedisp_map *map = linedisp->map; + + memcpy(buf, &map->map, map->size); + return map->size; +} + +static ssize_t map_seg_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct linedisp *linedisp = container_of(dev, struct linedisp, dev); + struct linedisp_map *map = linedisp->map; + + if (count != map->size) + return -EINVAL; + + memcpy(&map->map, buf, count); + return count; +} + +static const SEG7_DEFAULT_MAP(initial_map_seg7); +static DEVICE_ATTR(map_seg7, 0644, map_seg_show, map_seg_store); + +static const SEG14_DEFAULT_MAP(initial_map_seg14); +static DEVICE_ATTR(map_seg14, 0644, map_seg_show, map_seg_store); + static struct attribute *linedisp_attrs[] = { &dev_attr_message.attr, &dev_attr_scroll_step_ms.attr, - NULL, + &dev_attr_map_seg7.attr, + &dev_attr_map_seg14.attr, + NULL +}; + +static umode_t linedisp_attr_is_visible(struct kobject *kobj, struct attribute *attr, int n) +{ + struct device *dev = kobj_to_dev(kobj); + struct linedisp *linedisp = container_of(dev, struct linedisp, dev); + struct linedisp_map *map = linedisp->map; + umode_t mode = attr->mode; + + if (attr == &dev_attr_map_seg7.attr) { + if (!map) + return 0; + if (map->type != LINEDISP_MAP_SEG7) + return 0; + } + + if (attr == &dev_attr_map_seg14.attr) { + if (!map) + return 0; + if (map->type != LINEDISP_MAP_SEG14) + return 0; + } + + return mode; +}; + +static const struct attribute_group linedisp_group = { + .is_visible = linedisp_attr_is_visible, + .attrs = linedisp_attrs, }; -ATTRIBUTE_GROUPS(linedisp); +__ATTRIBUTE_GROUPS(linedisp); static DEFINE_IDA(linedisp_id); @@ -201,6 +263,7 @@ static void linedisp_release(struct device *dev) { struct linedisp *linedisp = container_of(dev, struct linedisp, dev); + kfree(linedisp->map); kfree(linedisp->message); ida_free(&linedisp_id, linedisp->id); } @@ -210,6 +273,44 @@ static const struct device_type linedisp_type = { .release = linedisp_release, }; +static int linedisp_init_map(struct linedisp *linedisp) +{ + struct linedisp_map *map; + int err; + + if (!linedisp->ops->get_map_type) + return 0; + + err = linedisp->ops->get_map_type(linedisp); + if (err < 0) + return err; + + map = kmalloc(sizeof(*map), GFP_KERNEL); + if (!map) + return -ENOMEM; + + map->type = err; + + /* assign initial mapping */ + switch (map->type) { + case LINEDISP_MAP_SEG7: + map->map.seg7 = initial_map_seg7; + map->size = sizeof(map->map.seg7); + break; + case LINEDISP_MAP_SEG14: + map->map.seg14 = initial_map_seg14; + map->size = sizeof(map->map.seg14); + break; + default: + kfree(map); + return -EINVAL; + } + + linedisp->map = map; + + return 0; +} + /** * linedisp_register - register a character line display * @linedisp: pointer to character line display structure @@ -242,6 +343,11 @@ int linedisp_register(struct linedisp *linedisp, struct device *parent, device_initialize(&linedisp->dev); dev_set_name(&linedisp->dev, "linedisp.%u", linedisp->id); + /* initialise a character mapping, if required */ + err = linedisp_init_map(linedisp); + if (err) + goto out_put_device; + /* initialise a timer for scrolling the message */ timer_setup(&linedisp->timer, linedisp_scroll, 0); @@ -260,6 +366,7 @@ out_del_dev: device_del(&linedisp->dev); out_del_timer: del_timer_sync(&linedisp->timer); +out_put_device: put_device(&linedisp->dev); return err; } diff --git a/drivers/auxdisplay/line-display.h b/drivers/auxdisplay/line-display.h index 88d051445599..4e310b0e611e 100644 --- a/drivers/auxdisplay/line-display.h +++ b/drivers/auxdisplay/line-display.h @@ -14,13 +14,43 @@ #include #include +#include +#include + struct linedisp; +/** + * enum linedisp_map_type - type of the character mapping + * @LINEDISP_MAP_SEG7: Map characters to 7 segment display + * @LINEDISP_MAP_SEG14: Map characters to 14 segment display + */ +enum linedisp_map_type { + LINEDISP_MAP_SEG7, + LINEDISP_MAP_SEG14, +}; + +/** + * struct linedisp_map - character mapping + * @type: type of the character mapping + * @map: conversion character mapping + * @size: size of the @map + */ +struct linedisp_map { + enum linedisp_map_type type; + union { + struct seg7_conversion_map seg7; + struct seg14_conversion_map seg14; + } map; + unsigned int size; +}; + /** * struct linedisp_ops - character line display operations + * @get_map_type: Function called to get the character mapping, if required * @update: Function called to update the display. This must not sleep! */ struct linedisp_ops { + int (*get_map_type)(struct linedisp *linedisp); void (*update)(struct linedisp *linedisp); }; @@ -41,6 +71,7 @@ struct linedisp { struct device dev; struct timer_list timer; const struct linedisp_ops *ops; + struct linedisp_map *map; char *buf; char *message; unsigned int num_chars; -- cgit From 4ce026d5f4ccb89a493b7c57d87d53a3ccb0be59 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 19 Feb 2024 18:58:01 +0200 Subject: auxdisplay: linedisp: Allocate buffer for the string Always allocate a buffer for the currently displayed characters. It makes the line display API simpler. Reviewed-by: Geert Uytterhoeven Tested-by: Geert Uytterhoeven Signed-off-by: Andy Shevchenko --- drivers/auxdisplay/ht16k33.c | 8 +++----- drivers/auxdisplay/img-ascii-lcd.c | 17 +++++++---------- drivers/auxdisplay/line-display.c | 11 +++++++---- drivers/auxdisplay/line-display.h | 3 +-- 4 files changed, 18 insertions(+), 21 deletions(-) diff --git a/drivers/auxdisplay/ht16k33.c b/drivers/auxdisplay/ht16k33.c index 32d3afd29177..19805f39a257 100644 --- a/drivers/auxdisplay/ht16k33.c +++ b/drivers/auxdisplay/ht16k33.c @@ -92,7 +92,6 @@ struct ht16k33_seg { struct seg14_conversion_map seg14; } map; unsigned int map_size; - char curr[4]; }; struct ht16k33_priv { @@ -457,7 +456,7 @@ static void ht16k33_seg7_update(struct work_struct *work) struct ht16k33_priv *priv = container_of(work, struct ht16k33_priv, work.work); struct ht16k33_seg *seg = &priv->seg; - char *s = seg->curr; + char *s = seg->linedisp.buf; uint8_t buf[9]; buf[0] = map_to_seg7(&seg->map.seg7, *s++); @@ -478,7 +477,7 @@ static void ht16k33_seg14_update(struct work_struct *work) struct ht16k33_priv *priv = container_of(work, struct ht16k33_priv, work.work); struct ht16k33_seg *seg = &priv->seg; - char *s = seg->curr; + char *s = seg->linedisp.buf; uint8_t buf[8]; put_unaligned_le16(map_to_seg14(&seg->map.seg14, *s++), buf); @@ -700,8 +699,7 @@ static int ht16k33_seg_probe(struct device *dev, struct ht16k33_priv *priv, if (err) return err; - err = linedisp_register(&seg->linedisp, dev, 4, seg->curr, - &ht16k33_linedisp_ops); + err = linedisp_register(&seg->linedisp, dev, 4, &ht16k33_linedisp_ops); if (err) goto err_remove_map_file; diff --git a/drivers/auxdisplay/img-ascii-lcd.c b/drivers/auxdisplay/img-ascii-lcd.c index ecfb1c05bf55..925c4cd101e9 100644 --- a/drivers/auxdisplay/img-ascii-lcd.c +++ b/drivers/auxdisplay/img-ascii-lcd.c @@ -37,7 +37,6 @@ struct img_ascii_lcd_config { * @regmap: the regmap through which LCD registers are accessed * @offset: the offset within regmap to the start of the LCD registers * @cfg: pointer to the LCD model configuration - * @curr: the string currently displayed on the LCD */ struct img_ascii_lcd_ctx { struct linedisp linedisp; @@ -47,7 +46,6 @@ struct img_ascii_lcd_ctx { }; u32 offset; const struct img_ascii_lcd_config *cfg; - char curr[] __aligned(8); }; /* @@ -61,12 +59,12 @@ static void boston_update(struct linedisp *linedisp) ulong val; #if BITS_PER_LONG == 64 - val = *((u64 *)&ctx->curr[0]); + val = *((u64 *)&linedisp->buf[0]); __raw_writeq(val, ctx->base); #elif BITS_PER_LONG == 32 - val = *((u32 *)&ctx->curr[0]); + val = *((u32 *)&linedisp->buf[0]); __raw_writel(val, ctx->base); - val = *((u32 *)&ctx->curr[4]); + val = *((u32 *)&linedisp->buf[4]); __raw_writel(val, ctx->base + 4); #else # error Not 32 or 64 bit @@ -93,7 +91,7 @@ static void malta_update(struct linedisp *linedisp) for (i = 0; i < linedisp->num_chars; i++) { err = regmap_write(ctx->regmap, - ctx->offset + (i * 8), ctx->curr[i]); + ctx->offset + (i * 8), linedisp->buf[i]); if (err) break; } @@ -195,7 +193,7 @@ static void sead3_update(struct linedisp *linedisp) err = regmap_write(ctx->regmap, ctx->offset + SEAD3_REG_LCD_DATA, - ctx->curr[i]); + linedisp->buf[i]); if (err) break; } @@ -236,7 +234,7 @@ static int img_ascii_lcd_probe(struct platform_device *pdev) struct img_ascii_lcd_ctx *ctx; int err; - ctx = devm_kzalloc(dev, sizeof(*ctx) + cfg->num_chars, GFP_KERNEL); + ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); if (!ctx) return -ENOMEM; @@ -253,8 +251,7 @@ static int img_ascii_lcd_probe(struct platform_device *pdev) return PTR_ERR(ctx->base); } - err = linedisp_register(&ctx->linedisp, dev, cfg->num_chars, ctx->curr, - &cfg->ops); + err = linedisp_register(&ctx->linedisp, dev, cfg->num_chars, &cfg->ops); if (err) return err; diff --git a/drivers/auxdisplay/line-display.c b/drivers/auxdisplay/line-display.c index 13be7c2f6bc3..e2b546210f8d 100644 --- a/drivers/auxdisplay/line-display.c +++ b/drivers/auxdisplay/line-display.c @@ -265,6 +265,7 @@ static void linedisp_release(struct device *dev) kfree(linedisp->map); kfree(linedisp->message); + kfree(linedisp->buf); ida_free(&linedisp_id, linedisp->id); } @@ -316,14 +317,12 @@ static int linedisp_init_map(struct linedisp *linedisp) * @linedisp: pointer to character line display structure * @parent: parent device * @num_chars: the number of characters that can be displayed - * @buf: pointer to a buffer that can hold @num_chars characters * @ops: character line display operations * * Return: zero on success, else a negative error code. */ int linedisp_register(struct linedisp *linedisp, struct device *parent, - unsigned int num_chars, char *buf, - const struct linedisp_ops *ops) + unsigned int num_chars, const struct linedisp_ops *ops) { int err; @@ -331,7 +330,6 @@ int linedisp_register(struct linedisp *linedisp, struct device *parent, linedisp->dev.parent = parent; linedisp->dev.type = &linedisp_type; linedisp->ops = ops; - linedisp->buf = buf; linedisp->num_chars = num_chars; linedisp->scroll_rate = DEFAULT_SCROLL_RATE; @@ -343,6 +341,11 @@ int linedisp_register(struct linedisp *linedisp, struct device *parent, device_initialize(&linedisp->dev); dev_set_name(&linedisp->dev, "linedisp.%u", linedisp->id); + err = -ENOMEM; + linedisp->buf = kzalloc(linedisp->num_chars, GFP_KERNEL); + if (!linedisp->buf) + goto out_put_device; + /* initialise a character mapping, if required */ err = linedisp_init_map(linedisp); if (err) diff --git a/drivers/auxdisplay/line-display.h b/drivers/auxdisplay/line-display.h index 4e310b0e611e..4348d7a2f69a 100644 --- a/drivers/auxdisplay/line-display.h +++ b/drivers/auxdisplay/line-display.h @@ -82,8 +82,7 @@ struct linedisp { }; int linedisp_register(struct linedisp *linedisp, struct device *parent, - unsigned int num_chars, char *buf, - const struct linedisp_ops *ops); + unsigned int num_chars, const struct linedisp_ops *ops); void linedisp_unregister(struct linedisp *linedisp); #endif /* LINEDISP_H */ -- cgit From ef2086a9e1c9e82ae35e5b0d0d522fc344e3357a Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 19 Feb 2024 18:58:02 +0200 Subject: auxdisplay: ht16k33: Add default to switch-cases Currently the compiler (GCC) is able to figure out that there is no other choices possible than those that are already listed in the switch-cases. However, if we want to move some code to the callback, compiler will start complaining that no default is defined. Make sure we have all switch-cases equiped with default. Reported-by: Geert Uytterhoeven Reviewed-by: Geert Uytterhoeven Tested-by: Geert Uytterhoeven Signed-off-by: Andy Shevchenko --- drivers/auxdisplay/ht16k33.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/drivers/auxdisplay/ht16k33.c b/drivers/auxdisplay/ht16k33.c index 19805f39a257..c0067a3b2b61 100644 --- a/drivers/auxdisplay/ht16k33.c +++ b/drivers/auxdisplay/ht16k33.c @@ -677,11 +677,6 @@ static int ht16k33_seg_probe(struct device *dev, struct ht16k33_priv *priv, return err; switch (priv->type) { - case DISP_MATRIX: - /* not handled here */ - err = -EINVAL; - break; - case DISP_QUAD_7SEG: INIT_DELAYED_WORK(&priv->work, ht16k33_seg7_update); seg->map.seg7 = initial_map_seg7; @@ -695,6 +690,9 @@ static int ht16k33_seg_probe(struct device *dev, struct ht16k33_priv *priv, seg->map_size = sizeof(seg->map.seg14); err = device_create_file(dev, &dev_attr_map_seg14); break; + + default: + return -EINVAL; } if (err) return err; @@ -772,6 +770,9 @@ static int ht16k33_probe(struct i2c_client *client) /* Segment Display */ err = ht16k33_seg_probe(dev, priv, dft_brightness); break; + + default: + return -EINVAL; } return err; } @@ -796,6 +797,9 @@ static void ht16k33_remove(struct i2c_client *client) device_remove_file(&client->dev, &dev_attr_map_seg7); device_remove_file(&client->dev, &dev_attr_map_seg14); break; + + default: + break; } } -- cgit From 5a805a7827b64125ddfbd7d34d7a374db30d4caa Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 19 Feb 2024 18:58:03 +0200 Subject: auxdisplay: ht16k33: Move ht16k33_linedisp_ops down We will need the update functions to be defined before ht16k33_linedisp_ops. Move the latter down in the code. No functional change intended. Reviewed-by: Geert Uytterhoeven Acked-by: Robin van der Gracht Tested-by: Geert Uytterhoeven Signed-off-by: Andy Shevchenko --- drivers/auxdisplay/ht16k33.c | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/drivers/auxdisplay/ht16k33.c b/drivers/auxdisplay/ht16k33.c index c0067a3b2b61..f016130835b1 100644 --- a/drivers/auxdisplay/ht16k33.c +++ b/drivers/auxdisplay/ht16k33.c @@ -439,18 +439,6 @@ static void ht16k33_keypad_stop(struct input_dev *dev) disable_irq(keypad->client->irq); } -static void ht16k33_linedisp_update(struct linedisp *linedisp) -{ - struct ht16k33_priv *priv = container_of(linedisp, struct ht16k33_priv, - seg.linedisp); - - schedule_delayed_work(&priv->work, 0); -} - -static const struct linedisp_ops ht16k33_linedisp_ops = { - .update = ht16k33_linedisp_update, -}; - static void ht16k33_seg7_update(struct work_struct *work) { struct ht16k33_priv *priv = container_of(work, struct ht16k33_priv, @@ -488,6 +476,18 @@ static void ht16k33_seg14_update(struct work_struct *work) i2c_smbus_write_i2c_block_data(priv->client, 0, ARRAY_SIZE(buf), buf); } +static void ht16k33_linedisp_update(struct linedisp *linedisp) +{ + struct ht16k33_priv *priv = container_of(linedisp, struct ht16k33_priv, + seg.linedisp); + + schedule_delayed_work(&priv->work, 0); +} + +static const struct linedisp_ops ht16k33_linedisp_ops = { + .update = ht16k33_linedisp_update, +}; + static int ht16k33_led_probe(struct device *dev, struct led_classdev *led, unsigned int brightness) { -- cgit From 0ee6eb851ec549367d28e5eab31387ea289ffd20 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 19 Feb 2024 18:58:04 +0200 Subject: auxdisplay: ht16k33: Define a few helper macros MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Define a few helper macros — wrappers on container_of() — for easier maintenance in the future. While at it, include missing container_of.h. Reviewed-by: Geert Uytterhoeven Tested-by: Geert Uytterhoeven Signed-off-by: Andy Shevchenko --- drivers/auxdisplay/ht16k33.c | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/drivers/auxdisplay/ht16k33.c b/drivers/auxdisplay/ht16k33.c index f016130835b1..b76c4d83528f 100644 --- a/drivers/auxdisplay/ht16k33.c +++ b/drivers/auxdisplay/ht16k33.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -107,6 +108,15 @@ struct ht16k33_priv { uint8_t blink; }; +#define ht16k33_work_to_priv(p) \ + container_of(p, struct ht16k33_priv, work.work) + +#define ht16k33_led_to_priv(p) \ + container_of(p, struct ht16k33_priv, led) + +#define ht16k33_linedisp_to_priv(p) \ + container_of(p, struct ht16k33_priv, seg.linedisp) + static const struct fb_fix_screeninfo ht16k33_fb_fix = { .id = DRIVER_NAME, .type = FB_TYPE_PACKED_PIXELS, @@ -194,8 +204,7 @@ static int ht16k33_brightness_set(struct ht16k33_priv *priv, static int ht16k33_brightness_set_blocking(struct led_classdev *led_cdev, enum led_brightness brightness) { - struct ht16k33_priv *priv = container_of(led_cdev, struct ht16k33_priv, - led); + struct ht16k33_priv *priv = ht16k33_led_to_priv(led_cdev); return ht16k33_brightness_set(priv, brightness); } @@ -203,8 +212,7 @@ static int ht16k33_brightness_set_blocking(struct led_classdev *led_cdev, static int ht16k33_blink_set(struct led_classdev *led_cdev, unsigned long *delay_on, unsigned long *delay_off) { - struct ht16k33_priv *priv = container_of(led_cdev, struct ht16k33_priv, - led); + struct ht16k33_priv *priv = ht16k33_led_to_priv(led_cdev); unsigned int delay; uint8_t blink; int err; @@ -246,8 +254,7 @@ static void ht16k33_fb_queue(struct ht16k33_priv *priv) */ static void ht16k33_fb_update(struct work_struct *work) { - struct ht16k33_priv *priv = container_of(work, struct ht16k33_priv, - work.work); + struct ht16k33_priv *priv = ht16k33_work_to_priv(work); struct ht16k33_fbdev *fbdev = &priv->fbdev; uint8_t *p1, *p2; @@ -441,8 +448,7 @@ static void ht16k33_keypad_stop(struct input_dev *dev) static void ht16k33_seg7_update(struct work_struct *work) { - struct ht16k33_priv *priv = container_of(work, struct ht16k33_priv, - work.work); + struct ht16k33_priv *priv = ht16k33_work_to_priv(work); struct ht16k33_seg *seg = &priv->seg; char *s = seg->linedisp.buf; uint8_t buf[9]; @@ -462,8 +468,7 @@ static void ht16k33_seg7_update(struct work_struct *work) static void ht16k33_seg14_update(struct work_struct *work) { - struct ht16k33_priv *priv = container_of(work, struct ht16k33_priv, - work.work); + struct ht16k33_priv *priv = ht16k33_work_to_priv(work); struct ht16k33_seg *seg = &priv->seg; char *s = seg->linedisp.buf; uint8_t buf[8]; @@ -478,8 +483,7 @@ static void ht16k33_seg14_update(struct work_struct *work) static void ht16k33_linedisp_update(struct linedisp *linedisp) { - struct ht16k33_priv *priv = container_of(linedisp, struct ht16k33_priv, - seg.linedisp); + struct ht16k33_priv *priv = ht16k33_linedisp_to_priv(linedisp); schedule_delayed_work(&priv->work, 0); } -- cgit From 815876dc0b40787f75f26cca25d2cc62291ee8c2 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 19 Feb 2024 18:58:05 +0200 Subject: auxdisplay: ht16k33: Switch to use line display character mapping Since line display library supports necessary bits to map the characters (if required), switch this driver to use that. Reviewed-by: Geert Uytterhoeven Acked-by: Robin van der Gracht Tested-by: Geert Uytterhoeven Signed-off-by: Andy Shevchenko --- drivers/auxdisplay/ht16k33.c | 103 +++++++++++++------------------------------ 1 file changed, 30 insertions(+), 73 deletions(-) diff --git a/drivers/auxdisplay/ht16k33.c b/drivers/auxdisplay/ht16k33.c index b76c4d83528f..41a961342dc3 100644 --- a/drivers/auxdisplay/ht16k33.c +++ b/drivers/auxdisplay/ht16k33.c @@ -88,11 +88,6 @@ struct ht16k33_fbdev { struct ht16k33_seg { struct linedisp linedisp; - union { - struct seg7_conversion_map seg7; - struct seg14_conversion_map seg14; - } map; - unsigned int map_size; }; struct ht16k33_priv { @@ -144,33 +139,6 @@ static const struct fb_var_screeninfo ht16k33_fb_var = { .vmode = FB_VMODE_NONINTERLACED, }; -static const SEG7_DEFAULT_MAP(initial_map_seg7); -static const SEG14_DEFAULT_MAP(initial_map_seg14); - -static ssize_t map_seg_show(struct device *dev, struct device_attribute *attr, - char *buf) -{ - struct ht16k33_priv *priv = dev_get_drvdata(dev); - - memcpy(buf, &priv->seg.map, priv->seg.map_size); - return priv->seg.map_size; -} - -static ssize_t map_seg_store(struct device *dev, struct device_attribute *attr, - const char *buf, size_t cnt) -{ - struct ht16k33_priv *priv = dev_get_drvdata(dev); - - if (cnt != priv->seg.map_size) - return -EINVAL; - - memcpy(&priv->seg.map, buf, cnt); - return cnt; -} - -static DEVICE_ATTR(map_seg7, 0644, map_seg_show, map_seg_store); -static DEVICE_ATTR(map_seg14, 0644, map_seg_show, map_seg_store); - static int ht16k33_display_on(struct ht16k33_priv *priv) { uint8_t data = REG_DISPLAY_SETUP | REG_DISPLAY_SETUP_ON | priv->blink; @@ -450,18 +418,19 @@ static void ht16k33_seg7_update(struct work_struct *work) { struct ht16k33_priv *priv = ht16k33_work_to_priv(work); struct ht16k33_seg *seg = &priv->seg; + struct linedisp_map *map = seg->linedisp.map; char *s = seg->linedisp.buf; uint8_t buf[9]; - buf[0] = map_to_seg7(&seg->map.seg7, *s++); + buf[0] = map_to_seg7(&map->map.seg7, *s++); buf[1] = 0; - buf[2] = map_to_seg7(&seg->map.seg7, *s++); + buf[2] = map_to_seg7(&map->map.seg7, *s++); buf[3] = 0; buf[4] = 0; buf[5] = 0; - buf[6] = map_to_seg7(&seg->map.seg7, *s++); + buf[6] = map_to_seg7(&map->map.seg7, *s++); buf[7] = 0; - buf[8] = map_to_seg7(&seg->map.seg7, *s++); + buf[8] = map_to_seg7(&map->map.seg7, *s++); i2c_smbus_write_i2c_block_data(priv->client, 0, ARRAY_SIZE(buf), buf); } @@ -470,17 +439,36 @@ static void ht16k33_seg14_update(struct work_struct *work) { struct ht16k33_priv *priv = ht16k33_work_to_priv(work); struct ht16k33_seg *seg = &priv->seg; + struct linedisp_map *map = seg->linedisp.map; char *s = seg->linedisp.buf; uint8_t buf[8]; - put_unaligned_le16(map_to_seg14(&seg->map.seg14, *s++), buf); - put_unaligned_le16(map_to_seg14(&seg->map.seg14, *s++), buf + 2); - put_unaligned_le16(map_to_seg14(&seg->map.seg14, *s++), buf + 4); - put_unaligned_le16(map_to_seg14(&seg->map.seg14, *s++), buf + 6); + put_unaligned_le16(map_to_seg14(&map->map.seg14, *s++), buf + 0); + put_unaligned_le16(map_to_seg14(&map->map.seg14, *s++), buf + 2); + put_unaligned_le16(map_to_seg14(&map->map.seg14, *s++), buf + 4); + put_unaligned_le16(map_to_seg14(&map->map.seg14, *s++), buf + 6); i2c_smbus_write_i2c_block_data(priv->client, 0, ARRAY_SIZE(buf), buf); } +static int ht16k33_linedisp_get_map_type(struct linedisp *linedisp) +{ + struct ht16k33_priv *priv = ht16k33_linedisp_to_priv(linedisp); + + switch (priv->type) { + case DISP_QUAD_7SEG: + INIT_DELAYED_WORK(&priv->work, ht16k33_seg7_update); + return LINEDISP_MAP_SEG7; + + case DISP_QUAD_14SEG: + INIT_DELAYED_WORK(&priv->work, ht16k33_seg14_update); + return LINEDISP_MAP_SEG14; + + default: + return -EINVAL; + } +} + static void ht16k33_linedisp_update(struct linedisp *linedisp) { struct ht16k33_priv *priv = ht16k33_linedisp_to_priv(linedisp); @@ -489,6 +477,7 @@ static void ht16k33_linedisp_update(struct linedisp *linedisp) } static const struct linedisp_ops ht16k33_linedisp_ops = { + .get_map_type = ht16k33_linedisp_get_map_type, .update = ht16k33_linedisp_update, }; @@ -680,37 +669,7 @@ static int ht16k33_seg_probe(struct device *dev, struct ht16k33_priv *priv, if (err) return err; - switch (priv->type) { - case DISP_QUAD_7SEG: - INIT_DELAYED_WORK(&priv->work, ht16k33_seg7_update); - seg->map.seg7 = initial_map_seg7; - seg->map_size = sizeof(seg->map.seg7); - err = device_create_file(dev, &dev_attr_map_seg7); - break; - - case DISP_QUAD_14SEG: - INIT_DELAYED_WORK(&priv->work, ht16k33_seg14_update); - seg->map.seg14 = initial_map_seg14; - seg->map_size = sizeof(seg->map.seg14); - err = device_create_file(dev, &dev_attr_map_seg14); - break; - - default: - return -EINVAL; - } - if (err) - return err; - - err = linedisp_register(&seg->linedisp, dev, 4, &ht16k33_linedisp_ops); - if (err) - goto err_remove_map_file; - - return 0; - -err_remove_map_file: - device_remove_file(dev, &dev_attr_map_seg7); - device_remove_file(dev, &dev_attr_map_seg14); - return err; + return linedisp_register(&seg->linedisp, dev, 4, &ht16k33_linedisp_ops); } static int ht16k33_probe(struct i2c_client *client) @@ -798,8 +757,6 @@ static void ht16k33_remove(struct i2c_client *client) case DISP_QUAD_7SEG: case DISP_QUAD_14SEG: linedisp_unregister(&priv->seg.linedisp); - device_remove_file(&client->dev, &dev_attr_map_seg7); - device_remove_file(&client->dev, &dev_attr_map_seg14); break; default: -- cgit From a459b2707abd417a502d9fc5e7a1f43a2d7bef2e Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 19 Feb 2024 18:58:06 +0200 Subject: auxdisplay: ht16k33: Drop struct ht16k33_seg The struct ht16k33_seg is repeating struct linedisp. Use the latter directly. Reviewed-by: Geert Uytterhoeven Tested-by: Geert Uytterhoeven Signed-off-by: Andy Shevchenko --- drivers/auxdisplay/ht16k33.c | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/drivers/auxdisplay/ht16k33.c b/drivers/auxdisplay/ht16k33.c index 41a961342dc3..96acfb2b58cd 100644 --- a/drivers/auxdisplay/ht16k33.c +++ b/drivers/auxdisplay/ht16k33.c @@ -86,10 +86,6 @@ struct ht16k33_fbdev { uint8_t *cache; }; -struct ht16k33_seg { - struct linedisp linedisp; -}; - struct ht16k33_priv { struct i2c_client *client; struct delayed_work work; @@ -97,7 +93,7 @@ struct ht16k33_priv { struct ht16k33_keypad keypad; union { struct ht16k33_fbdev fbdev; - struct ht16k33_seg seg; + struct linedisp linedisp; }; enum display_type type; uint8_t blink; @@ -110,7 +106,7 @@ struct ht16k33_priv { container_of(p, struct ht16k33_priv, led) #define ht16k33_linedisp_to_priv(p) \ - container_of(p, struct ht16k33_priv, seg.linedisp) + container_of(p, struct ht16k33_priv, linedisp) static const struct fb_fix_screeninfo ht16k33_fb_fix = { .id = DRIVER_NAME, @@ -417,9 +413,8 @@ static void ht16k33_keypad_stop(struct input_dev *dev) static void ht16k33_seg7_update(struct work_struct *work) { struct ht16k33_priv *priv = ht16k33_work_to_priv(work); - struct ht16k33_seg *seg = &priv->seg; - struct linedisp_map *map = seg->linedisp.map; - char *s = seg->linedisp.buf; + struct linedisp_map *map = priv->linedisp.map; + char *s = priv->linedisp.buf; uint8_t buf[9]; buf[0] = map_to_seg7(&map->map.seg7, *s++); @@ -438,9 +433,8 @@ static void ht16k33_seg7_update(struct work_struct *work) static void ht16k33_seg14_update(struct work_struct *work) { struct ht16k33_priv *priv = ht16k33_work_to_priv(work); - struct ht16k33_seg *seg = &priv->seg; - struct linedisp_map *map = seg->linedisp.map; - char *s = seg->linedisp.buf; + struct linedisp_map *map = priv->linedisp.map; + char *s = priv->linedisp.buf; uint8_t buf[8]; put_unaligned_le16(map_to_seg14(&map->map.seg14, *s++), buf + 0); @@ -662,14 +656,14 @@ err_fbdev_buffer: static int ht16k33_seg_probe(struct device *dev, struct ht16k33_priv *priv, uint32_t brightness) { - struct ht16k33_seg *seg = &priv->seg; + struct linedisp *linedisp = &priv->linedisp; int err; err = ht16k33_brightness_set(priv, brightness); if (err) return err; - return linedisp_register(&seg->linedisp, dev, 4, &ht16k33_linedisp_ops); + return linedisp_register(linedisp, dev, 4, &ht16k33_linedisp_ops); } static int ht16k33_probe(struct i2c_client *client) @@ -756,7 +750,7 @@ static void ht16k33_remove(struct i2c_client *client) case DISP_QUAD_7SEG: case DISP_QUAD_14SEG: - linedisp_unregister(&priv->seg.linedisp); + linedisp_unregister(&priv->linedisp); break; default: -- cgit From f6815c79d2292cbb1e2305e44b733d01fb33f65f Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Thu, 8 Feb 2024 17:23:38 +0200 Subject: dt-bindings: auxdisplay: Add Maxim MAX6958/6959 Add initial device tree documentation for Maxim MAX6958/6959. As per reviewer's request mention the fact of absence the reset and power enable pins, since the hardware is quite simple. Signed-off-by: Andy Shevchenko Reviewed-by: Krzysztof Kozlowski Reviewed-by: Geert Uytterhoeven --- .../bindings/auxdisplay/maxim,max6959.yaml | 44 ++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 Documentation/devicetree/bindings/auxdisplay/maxim,max6959.yaml diff --git a/Documentation/devicetree/bindings/auxdisplay/maxim,max6959.yaml b/Documentation/devicetree/bindings/auxdisplay/maxim,max6959.yaml new file mode 100644 index 000000000000..20dd9e8c8190 --- /dev/null +++ b/Documentation/devicetree/bindings/auxdisplay/maxim,max6959.yaml @@ -0,0 +1,44 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/auxdisplay/maxim,max6959.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: MAX6958/6959 7-segment LED display controller + +maintainers: + - Andy Shevchenko + +description: + The Maxim MAX6958/6959 7-segment LED display controller provides + an I2C interface to up to four 7-segment LED digits. The MAX6959, + in comparison to MAX6958, adds input support. Type of the chip can + be autodetected via specific register read, and hence the features + may be enabled in the driver at run-time, in case they are requested + via Device Tree. A given hardware is simple and does not provide + any additional pins, such as reset or power enable. + +properties: + compatible: + const: maxim,max6959 + + reg: + maxItems: 1 + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + i2c { + #address-cells = <1>; + #size-cells = <0>; + + display-controller@38 { + compatible = "maxim,max6959"; + reg = <0x38>; + }; + }; -- cgit From a9bcd02fa42217c795bc9538a149936c82648476 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 26 Feb 2024 18:19:21 +0200 Subject: auxdisplay: Add driver for MAX695x 7-segment LED controllers Add initial driver for the MAX6958 and MAX6959 7-segment LED controllers. Reviewed-by: Geert Uytterhoeven Signed-off-by: Andy Shevchenko --- drivers/auxdisplay/Kconfig | 14 ++++ drivers/auxdisplay/Makefile | 1 + drivers/auxdisplay/max6959.c | 194 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 209 insertions(+) create mode 100644 drivers/auxdisplay/max6959.c diff --git a/drivers/auxdisplay/Kconfig b/drivers/auxdisplay/Kconfig index d944d5298eca..d4be0a3695ce 100644 --- a/drivers/auxdisplay/Kconfig +++ b/drivers/auxdisplay/Kconfig @@ -177,6 +177,20 @@ config HT16K33 Say yes here to add support for Holtek HT16K33, RAM mapping 16*8 LED controller driver with keyscan. +config MAX6959 + tristate "Maxim MAX6958/6959 7-segment LED controller" + depends on I2C + select REGMAP_I2C + select LINEDISP + help + If you say yes here you get support for the following Maxim chips + (I2C 7-segment LED display controller): + - MAX6958 + - MAX6959 (input support) + + This driver can also be built as a module. If so, the module + will be called max6959. + config LCD2S tristate "lcd2s 20x4 character display over I2C console" depends on I2C diff --git a/drivers/auxdisplay/Makefile b/drivers/auxdisplay/Makefile index 6968ed4d3f0a..a725010ca651 100644 --- a/drivers/auxdisplay/Makefile +++ b/drivers/auxdisplay/Makefile @@ -14,3 +14,4 @@ obj-$(CONFIG_HT16K33) += ht16k33.o obj-$(CONFIG_PARPORT_PANEL) += panel.o obj-$(CONFIG_LCD2S) += lcd2s.o obj-$(CONFIG_LINEDISP) += line-display.o +obj-$(CONFIG_MAX6959) += max6959.o diff --git a/drivers/auxdisplay/max6959.c b/drivers/auxdisplay/max6959.c new file mode 100644 index 000000000000..5519c014bd29 --- /dev/null +++ b/drivers/auxdisplay/max6959.c @@ -0,0 +1,194 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * MAX6958/6959 7-segment LED display controller + * Datasheet: + * https://www.analog.com/media/en/technical-documentation/data-sheets/MAX6958-MAX6959.pdf + * + * Copyright (c) 2024, Intel Corporation. + * Author: Andy Shevchenko + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "line-display.h" + +/* Registers */ +#define REG_DECODE_MODE 0x01 +#define REG_INTENSITY 0x02 +#define REG_SCAN_LIMIT 0x03 +#define REG_CONFIGURATION 0x04 +#define REG_CONFIGURATION_S_BIT BIT(0) + +#define REG_DIGIT(x) (0x20 + (x)) +#define REG_DIGIT0 0x20 +#define REG_DIGIT1 0x21 +#define REG_DIGIT2 0x22 +#define REG_DIGIT3 0x23 + +#define REG_SEGMENTS 0x24 +#define REG_MAX REG_SEGMENTS + +struct max6959_priv { + struct linedisp linedisp; + struct delayed_work work; + struct regmap *regmap; +}; + +static void max6959_disp_update(struct work_struct *work) +{ + struct max6959_priv *priv = container_of(work, struct max6959_priv, work.work); + struct linedisp *linedisp = &priv->linedisp; + struct linedisp_map *map = linedisp->map; + char *s = linedisp->buf; + u8 buf[4]; + + /* Map segments according to datasheet */ + buf[0] = bitrev8(map_to_seg7(&map->map.seg7, *s++)) >> 1; + buf[1] = bitrev8(map_to_seg7(&map->map.seg7, *s++)) >> 1; + buf[2] = bitrev8(map_to_seg7(&map->map.seg7, *s++)) >> 1; + buf[3] = bitrev8(map_to_seg7(&map->map.seg7, *s++)) >> 1; + + regmap_bulk_write(priv->regmap, REG_DIGIT(0), buf, ARRAY_SIZE(buf)); +} + +static int max6959_linedisp_get_map_type(struct linedisp *linedisp) +{ + struct max6959_priv *priv = container_of(linedisp, struct max6959_priv, linedisp); + + INIT_DELAYED_WORK(&priv->work, max6959_disp_update); + return LINEDISP_MAP_SEG7; +} + +static void max6959_linedisp_update(struct linedisp *linedisp) +{ + struct max6959_priv *priv = container_of(linedisp, struct max6959_priv, linedisp); + + schedule_delayed_work(&priv->work, 0); +} + +static const struct linedisp_ops max6959_linedisp_ops = { + .get_map_type = max6959_linedisp_get_map_type, + .update = max6959_linedisp_update, +}; + +static int max6959_enable(struct max6959_priv *priv, bool enable) +{ + u8 mask = REG_CONFIGURATION_S_BIT; + u8 value = enable ? mask : 0; + + return regmap_update_bits(priv->regmap, REG_CONFIGURATION, mask, value); +} + +static void max6959_power_off(void *priv) +{ + max6959_enable(priv, false); +} + +static int max6959_power_on(struct max6959_priv *priv) +{ + struct device *dev = regmap_get_device(priv->regmap); + int ret; + + ret = max6959_enable(priv, true); + if (ret) + return ret; + + return devm_add_action_or_reset(dev, max6959_power_off, priv); +} + +static const struct regmap_config max6959_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = REG_MAX, + .cache_type = REGCACHE_MAPLE, +}; + +static int max6959_i2c_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct max6959_priv *priv; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->regmap = devm_regmap_init_i2c(client, &max6959_regmap_config); + if (IS_ERR(priv->regmap)) + return PTR_ERR(priv->regmap); + + ret = max6959_power_on(priv); + if (ret) + return ret; + + ret = linedisp_register(&priv->linedisp, dev, 4, &max6959_linedisp_ops); + if (ret) + return ret; + + i2c_set_clientdata(client, priv); + + return 0; +} + +static void max6959_i2c_remove(struct i2c_client *client) +{ + struct max6959_priv *priv = i2c_get_clientdata(client); + + cancel_delayed_work_sync(&priv->work); + linedisp_unregister(&priv->linedisp); +} + +static int max6959_suspend(struct device *dev) +{ + return max6959_enable(dev_get_drvdata(dev), false); +} + +static int max6959_resume(struct device *dev) +{ + return max6959_enable(dev_get_drvdata(dev), true); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(max6959_pm_ops, max6959_suspend, max6959_resume); + +static const struct i2c_device_id max6959_i2c_id[] = { + { "max6959" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max6959_i2c_id); + +static const struct of_device_id max6959_of_table[] = { + { .compatible = "maxim,max6959" }, + { } +}; +MODULE_DEVICE_TABLE(of, max6959_of_table); + +static struct i2c_driver max6959_i2c_driver = { + .driver = { + .name = "max6959", + .pm = pm_sleep_ptr(&max6959_pm_ops), + .of_match_table = max6959_of_table, + }, + .probe = max6959_i2c_probe, + .remove = max6959_i2c_remove, + .id_table = max6959_i2c_id, +}; +module_i2c_driver(max6959_i2c_driver); + +MODULE_DESCRIPTION("MAX6958/6959 7-segment LED controller"); +MODULE_AUTHOR("Andy Shevchenko "); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(LINEDISP); -- cgit From 899383f9ecf59881a0f43fbf7d74b26bd1e1a16f Mon Sep 17 00:00:00 2001 From: Chris Packham Date: Fri, 8 Mar 2024 08:50:51 +1300 Subject: auxdisplay: Add 7-segment LED display driver Add a driver for a 7-segment LED display. At the moment only one character is supported but it should be possible to expand this to support more characters and/or 14-segment displays in the future. Signed-off-by: Chris Packham Reviewed-by: Geert Uytterhoeven Signed-off-by: Andy Shevchenko --- drivers/auxdisplay/Kconfig | 11 ++++ drivers/auxdisplay/Makefile | 1 + drivers/auxdisplay/seg-led-gpio.c | 112 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 124 insertions(+) create mode 100644 drivers/auxdisplay/seg-led-gpio.c diff --git a/drivers/auxdisplay/Kconfig b/drivers/auxdisplay/Kconfig index d4be0a3695ce..151d95f96b11 100644 --- a/drivers/auxdisplay/Kconfig +++ b/drivers/auxdisplay/Kconfig @@ -211,6 +211,17 @@ config ARM_CHARLCD line and the Linux version on the second line, but that's still useful. +config SEG_LED_GPIO + tristate "Generic 7-segment LED display" + depends on GPIOLIB || COMPILE_TEST + select LINEDISP + help + This driver supports a generic 7-segment LED display made up + of GPIO pins connected to the individual segments. + + This driver can also be built as a module. If so, the module + will be called seg-led-gpio. + menuconfig PARPORT_PANEL tristate "Parallel port LCD/Keypad Panel support" depends on PARPORT diff --git a/drivers/auxdisplay/Makefile b/drivers/auxdisplay/Makefile index a725010ca651..4a8ea41b0550 100644 --- a/drivers/auxdisplay/Makefile +++ b/drivers/auxdisplay/Makefile @@ -15,3 +15,4 @@ obj-$(CONFIG_PARPORT_PANEL) += panel.o obj-$(CONFIG_LCD2S) += lcd2s.o obj-$(CONFIG_LINEDISP) += line-display.o obj-$(CONFIG_MAX6959) += max6959.o +obj-$(CONFIG_SEG_LED_GPIO) += seg-led-gpio.o diff --git a/drivers/auxdisplay/seg-led-gpio.c b/drivers/auxdisplay/seg-led-gpio.c new file mode 100644 index 000000000000..5dc2a006cac5 --- /dev/null +++ b/drivers/auxdisplay/seg-led-gpio.c @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for a 7-segment LED display + * + * The decimal point LED present on some devices is currently not + * supported. + * + * Copyright (C) Allied Telesis Labs + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "line-display.h" + +struct seg_led_priv { + struct linedisp linedisp; + struct delayed_work work; + struct gpio_descs *segment_gpios; +}; + +static void seg_led_update(struct work_struct *work) +{ + struct seg_led_priv *priv = container_of(work, struct seg_led_priv, work.work); + struct linedisp *linedisp = &priv->linedisp; + struct linedisp_map *map = linedisp->map; + DECLARE_BITMAP(values, 8) = { }; + + bitmap_set_value8(values, map_to_seg7(&map->map.seg7, linedisp->buf[0]), 0); + + gpiod_set_array_value_cansleep(priv->segment_gpios->ndescs, priv->segment_gpios->desc, + priv->segment_gpios->info, values); +} + +static int seg_led_linedisp_get_map_type(struct linedisp *linedisp) +{ + struct seg_led_priv *priv = container_of(linedisp, struct seg_led_priv, linedisp); + + INIT_DELAYED_WORK(&priv->work, seg_led_update); + return LINEDISP_MAP_SEG7; +} + +static void seg_led_linedisp_update(struct linedisp *linedisp) +{ + struct seg_led_priv *priv = container_of(linedisp, struct seg_led_priv, linedisp); + + schedule_delayed_work(&priv->work, 0); +} + +static const struct linedisp_ops seg_led_linedisp_ops = { + .get_map_type = seg_led_linedisp_get_map_type, + .update = seg_led_linedisp_update, +}; + +static int seg_led_probe(struct platform_device *pdev) +{ + struct seg_led_priv *priv; + struct device *dev = &pdev->dev; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + platform_set_drvdata(pdev, priv); + + priv->segment_gpios = devm_gpiod_get_array(dev, "segment", GPIOD_OUT_LOW); + if (IS_ERR(priv->segment_gpios)) + return PTR_ERR(priv->segment_gpios); + + if (priv->segment_gpios->ndescs < 7 || priv->segment_gpios->ndescs > 8) + return -EINVAL; + + return linedisp_register(&priv->linedisp, dev, 1, &seg_led_linedisp_ops); +} + +static int seg_led_remove(struct platform_device *pdev) +{ + struct seg_led_priv *priv = platform_get_drvdata(pdev); + + cancel_delayed_work_sync(&priv->work); + linedisp_unregister(&priv->linedisp); + + return 0; +} + +static const struct of_device_id seg_led_of_match[] = { + { .compatible = "gpio-7-segment"}, + {} +}; +MODULE_DEVICE_TABLE(of, seg_led_of_match); + +static struct platform_driver seg_led_driver = { + .probe = seg_led_probe, + .remove = seg_led_remove, + .driver = { + .name = "seg-led-gpio", + .of_match_table = seg_led_of_match, + }, +}; +module_platform_driver(seg_led_driver); + +MODULE_AUTHOR("Chris Packham "); +MODULE_DESCRIPTION("7 segment LED driver"); +MODULE_LICENSE("GPL"); -- cgit From 4664b0bbb69ce61a913336a1f093383a4aa43824 Mon Sep 17 00:00:00 2001 From: Chris Packham Date: Fri, 8 Mar 2024 08:50:52 +1300 Subject: dt-bindings: auxdisplay: Add bindings for generic 7-segment LED Add bindings for a generic 7-segment LED display using GPIOs. Signed-off-by: Chris Packham Reviewed-by: Geert Uytterhoeven Reviewed-by: Rob Herring Signed-off-by: Andy Shevchenko --- .../bindings/auxdisplay/gpio-7-segment.yaml | 55 ++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 Documentation/devicetree/bindings/auxdisplay/gpio-7-segment.yaml diff --git a/Documentation/devicetree/bindings/auxdisplay/gpio-7-segment.yaml b/Documentation/devicetree/bindings/auxdisplay/gpio-7-segment.yaml new file mode 100644 index 000000000000..328954893c64 --- /dev/null +++ b/Documentation/devicetree/bindings/auxdisplay/gpio-7-segment.yaml @@ -0,0 +1,55 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/auxdisplay/gpio-7-segment.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: GPIO based LED segment display + +maintainers: + - Chris Packham + +properties: + compatible: + const: gpio-7-segment + + segment-gpios: + description: | + An array of GPIOs one per segment. The first GPIO corresponds to the A + segment, the seventh GPIO corresponds to the G segment. Some LED blocks + also have a decimal point which can be specified as an optional eighth + segment. + + -a- + | | + f b + | | + -g- + | | + e c + | | + -d- dp + + minItems: 7 + maxItems: 8 + +required: + - segment-gpios + +additionalProperties: false + +examples: + - | + + #include + + led-7seg { + compatible = "gpio-7-segment"; + segment-gpios = <&gpio 0 GPIO_ACTIVE_LOW>, + <&gpio 1 GPIO_ACTIVE_LOW>, + <&gpio 2 GPIO_ACTIVE_LOW>, + <&gpio 3 GPIO_ACTIVE_LOW>, + <&gpio 4 GPIO_ACTIVE_LOW>, + <&gpio 5 GPIO_ACTIVE_LOW>, + <&gpio 6 GPIO_ACTIVE_LOW>; + }; -- cgit From d8abf9d4ea3578e594fdde0d25d29cbabab96981 Mon Sep 17 00:00:00 2001 From: Chris Packham Date: Mon, 11 Mar 2024 10:13:04 +1300 Subject: auxdisplay: seg-led-gpio: Import linedisp namespace The seg-led-gpio driver uses symbols from the linedisp namespace. Ensure these are imported. Fixes: 899383f9ecf5 ("auxdisplay: Add 7-segment LED display driver") Signed-off-by: Chris Packham Closes: https://lore.kernel.org/r/202403100401.IDre04gu-lkp@intel.com/ Reviewed-by: Geert Uytterhoeven Signed-off-by: Andy Shevchenko --- drivers/auxdisplay/seg-led-gpio.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/auxdisplay/seg-led-gpio.c b/drivers/auxdisplay/seg-led-gpio.c index 5dc2a006cac5..35a8dbb1e9d2 100644 --- a/drivers/auxdisplay/seg-led-gpio.c +++ b/drivers/auxdisplay/seg-led-gpio.c @@ -110,3 +110,4 @@ module_platform_driver(seg_led_driver); MODULE_AUTHOR("Chris Packham "); MODULE_DESCRIPTION("7 segment LED driver"); MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(LINEDISP); -- cgit From 07d03559ed28e514c022e7ffd1e43705d48dee5e Mon Sep 17 00:00:00 2001 From: Uwe Kleine-König Date: Mon, 11 Mar 2024 22:59:22 +0100 Subject: auxdisplay: cfag12864bfb: Convert to platform remove callback returning void MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The .remove() callback for a platform driver returns an int which makes many driver authors wrongly assume it's possible to do error handling by returning an error code. However the value returned is ignored (apart from emitting a warning) and this typically results in resource leaks. To improve here there is a quest to make the remove callback return void. In the first step of this quest all drivers are converted to .remove_new(), which already returns void. Eventually after all drivers are converted, .remove_new() will be renamed to .remove(). Trivially convert this driver from always returning zero in the remove callback to the void returning variant. Signed-off-by: Uwe Kleine-König Acked-by: Miguel Ojeda Reviewed-by: Geert Uytterhoeven Signed-off-by: Andy Shevchenko --- drivers/auxdisplay/cfag12864bfb.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/auxdisplay/cfag12864bfb.c b/drivers/auxdisplay/cfag12864bfb.c index 5ba19c339f08..2b74dabe7e17 100644 --- a/drivers/auxdisplay/cfag12864bfb.c +++ b/drivers/auxdisplay/cfag12864bfb.c @@ -96,7 +96,7 @@ none: return ret; } -static int cfag12864bfb_remove(struct platform_device *device) +static void cfag12864bfb_remove(struct platform_device *device) { struct fb_info *info = platform_get_drvdata(device); @@ -104,13 +104,11 @@ static int cfag12864bfb_remove(struct platform_device *device) unregister_framebuffer(info); framebuffer_release(info); } - - return 0; } static struct platform_driver cfag12864bfb_driver = { .probe = cfag12864bfb_probe, - .remove = cfag12864bfb_remove, + .remove_new = cfag12864bfb_remove, .driver = { .name = CFAG12864BFB_NAME, }, -- cgit From 9ea02f7cc39d484d16e8a14f3713fefcd33407c0 Mon Sep 17 00:00:00 2001 From: Uwe Kleine-König Date: Mon, 11 Mar 2024 22:59:23 +0100 Subject: auxdisplay: hd44780: Convert to platform remove callback returning void MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The .remove() callback for a platform driver returns an int which makes many driver authors wrongly assume it's possible to do error handling by returning an error code. However the value returned is ignored (apart from emitting a warning) and this typically results in resource leaks. To improve here there is a quest to make the remove callback return void. In the first step of this quest all drivers are converted to .remove_new(), which already returns void. Eventually after all drivers are converted, .remove_new() will be renamed to .remove(). Trivially convert this driver from always returning zero in the remove callback to the void returning variant. Signed-off-by: Uwe Kleine-König Reviewed-by: Geert Uytterhoeven Signed-off-by: Andy Shevchenko --- drivers/auxdisplay/hd44780.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/auxdisplay/hd44780.c b/drivers/auxdisplay/hd44780.c index d56a5d508ccd..7ac0b1b1d548 100644 --- a/drivers/auxdisplay/hd44780.c +++ b/drivers/auxdisplay/hd44780.c @@ -319,7 +319,7 @@ fail1: return ret; } -static int hd44780_remove(struct platform_device *pdev) +static void hd44780_remove(struct platform_device *pdev) { struct charlcd *lcd = platform_get_drvdata(pdev); struct hd44780_common *hdc = lcd->drvdata; @@ -329,7 +329,6 @@ static int hd44780_remove(struct platform_device *pdev) kfree(lcd->drvdata); kfree(lcd); - return 0; } static const struct of_device_id hd44780_of_match[] = { @@ -340,7 +339,7 @@ MODULE_DEVICE_TABLE(of, hd44780_of_match); static struct platform_driver hd44780_driver = { .probe = hd44780_probe, - .remove = hd44780_remove, + .remove_new = hd44780_remove, .driver = { .name = "hd44780", .of_match_table = hd44780_of_match, -- cgit From 5d9e12972259cd86ae9c3fc0d5338b15831b9929 Mon Sep 17 00:00:00 2001 From: Uwe Kleine-König Date: Mon, 11 Mar 2024 22:59:24 +0100 Subject: auxdisplay: img-ascii-lcd: Convert to platform remove callback returning void MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The .remove() callback for a platform driver returns an int which makes many driver authors wrongly assume it's possible to do error handling by returning an error code. However the value returned is ignored (apart from emitting a warning) and this typically results in resource leaks. To improve here there is a quest to make the remove callback return void. In the first step of this quest all drivers are converted to .remove_new(), which already returns void. Eventually after all drivers are converted, .remove_new() will be renamed to .remove(). Trivially convert this driver from always returning zero in the remove callback to the void returning variant. Signed-off-by: Uwe Kleine-König Reviewed-by: Geert Uytterhoeven Signed-off-by: Andy Shevchenko --- drivers/auxdisplay/img-ascii-lcd.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/drivers/auxdisplay/img-ascii-lcd.c b/drivers/auxdisplay/img-ascii-lcd.c index 925c4cd101e9..9ba132dc6143 100644 --- a/drivers/auxdisplay/img-ascii-lcd.c +++ b/drivers/auxdisplay/img-ascii-lcd.c @@ -276,16 +276,13 @@ err_unregister: * * Remove an LCD display device, freeing private resources & ensuring that the * driver stops using the LCD display registers. - * - * Return: 0 */ -static int img_ascii_lcd_remove(struct platform_device *pdev) +static void img_ascii_lcd_remove(struct platform_device *pdev) { struct img_ascii_lcd_ctx *ctx = platform_get_drvdata(pdev); sysfs_remove_link(&pdev->dev.kobj, "message"); linedisp_unregister(&ctx->linedisp); - return 0; } static struct platform_driver img_ascii_lcd_driver = { @@ -294,7 +291,7 @@ static struct platform_driver img_ascii_lcd_driver = { .of_match_table = img_ascii_lcd_matches, }, .probe = img_ascii_lcd_probe, - .remove = img_ascii_lcd_remove, + .remove_new = img_ascii_lcd_remove, }; module_platform_driver(img_ascii_lcd_driver); -- cgit