auxdisplay for v6.9-1

* New driver for GPIO based 7-segment LED display (Chris Packham)
 * New driver for Maxim MAX6958/6959 I²C 7-segment LED display controller
 * Refactor linedisp library to make the above happen
 * Update Holtek HT16k33 driver to follow the linedisp refactoring
 * Convert .remove to return void in platform drivers (Uwe Kleine-König)
 * Fix DT schemas (Krzysztof Kozlowski)
 * Refresh MAINTAINERS database
 
 The following is an automated git shortlog grouped by driver:
 
 Add 7-segment LED display drivers:
  - Add 7-segment LED display driver
  - Add driver for MAX695x 7-segment LED controllers
  - Add 7 and 14 segment mappings to MAINTAINERS
 
 cfag12864bfb:
  -  Convert to platform remove callback returning void
 
 dt-bindings:
  -  auxdisplay: Add bindings for generic 7-segment LED
  -  auxdisplay: Add Maxim MAX6958/6959
  -  auxdisplay: hit,hd44780: use defines for GPIO flags
  -  auxdisplay: adjust example indentation and use generic node names
 
 hd44780:
  -  Convert to platform remove callback returning void
 
 ht16k33:
  -  Drop struct ht16k33_seg
  -  Switch to use line display character mapping
  -  Define a few helper macros
  -  Move ht16k33_linedisp_ops down
  -  Add default to switch-cases
 
 img-ascii-lcd:
  -  Convert to platform remove callback returning void
  -  Make container_of() no-op for struct linedisp
 
 linedisp:
  -  Allocate buffer for the string
  -  Add support for overriding character mapping
  -  Provide struct linedisp_ops for future extension
  -  Move exported symbols to a namespace
  -  Add missing header(s)
  -  Unshadow error codes in ->store()
  -  Use unique number for id
  -  Free allocated resources in ->release()
 
 panel:
  -  Switch to use module_parport_driver()
 
 seg-led-gpio:
  -  Import linedisp namespace
 -----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCgAdFiEEqaflIX74DDDzMJJtb7wzTHR8rCgFAmXxtUoACgkQb7wzTHR8
 rCiyIg//TuyU7/1xqPW2Jri+jWDw3hdFtizE2TB6NjQhn5eS5uPjh3oFVgMPQv0x
 IwhALXzdG9V5s946er0APC1B7b7TppGuQf1bP/mDPuYzy+YqehEO6rSzrcHP2w6T
 Uj7l5ShjkUVJD60ZvognJxJKGcTEffzFTIAP8lqlLzVZ81I79rzfjSmvFnY25mbW
 Wv+m8fBHQTKR7cH8GZFNlhMOmJ2WmmeS0ba9GU09lXKMtvB/89yQinOi/JH5qntk
 kUWmD0M7rLopkKyYJhnlnuBzbCs8ZAwq8ogdrTST3g3V2PO36sxRHerC9Ve1six/
 YarW+z+g/qNm/lbI/iBeNkpKczFENTXJEi5+cXseBcEPl5pV/xRjmI/RqTy6bMe+
 03rCDgrEzxFHZz7e1D88UMLN5cJoaQVkGeUpHHVS1fkAZ6euEXjFhOzS1GZ5B67H
 6oaE/PJLVi0Hs0XDHKYsnhF+l6tkWrMyx2w9XykiXzCY2hkRnofPmNE8D3aOvoYQ
 kvl7hOBqcNbWYS73d/eUGCmISSajHcwiPPxysmw4wtKrrbe6VGbehLWG8neS/Dri
 Y9kpu0t86Ue7qLBcMHvW/U7lqcdKreqc5LFBCdmxa3SaEVmDb1m/RE+jwANGcYFQ
 fJHPmzn1zn9TPstFU2TAQU0JLu3f82kh4evKFPKt7ugwRBKCrnU=
 =Q7mz
 -----END PGP SIGNATURE-----

Merge tag 'auxdisplay-v6.9-1' of git://git.kernel.org/pub/scm/linux/kernel/git/andy/linux-auxdisplay

Pull auxdisplay updates from Andy Shevchenko:

 - New driver for GPIO based 7-segment LED display (Chris Packham)

 - New driver for Maxim MAX6958/6959 I²C 7-segment LED display
   controller

 - Refactor linedisp library to make the above happen

 - Update Holtek HT16k33 driver to follow the linedisp refactoring

 - Convert .remove to return void in platform drivers (Uwe Kleine-König)

 - Fix DT schemas (Krzysztof Kozlowski)

 - Refresh MAINTAINERS database

* tag 'auxdisplay-v6.9-1' of git://git.kernel.org/pub/scm/linux/kernel/git/andy/linux-auxdisplay: (27 commits)
  auxdisplay: img-ascii-lcd: Convert to platform remove callback returning void
  auxdisplay: hd44780: Convert to platform remove callback returning void
  auxdisplay: cfag12864bfb: Convert to platform remove callback returning void
  auxdisplay: seg-led-gpio: Import linedisp namespace
  dt-bindings: auxdisplay: Add bindings for generic 7-segment LED
  auxdisplay: Add 7-segment LED display driver
  auxdisplay: Add driver for MAX695x 7-segment LED controllers
  dt-bindings: auxdisplay: Add Maxim MAX6958/6959
  auxdisplay: ht16k33: Drop struct ht16k33_seg
  auxdisplay: ht16k33: Switch to use line display character mapping
  auxdisplay: ht16k33: Define a few helper macros
  auxdisplay: ht16k33: Move ht16k33_linedisp_ops down
  auxdisplay: ht16k33: Add default to switch-cases
  auxdisplay: linedisp: Allocate buffer for the string
  auxdisplay: linedisp: Add support for overriding character mapping
  auxdisplay: linedisp: Provide struct linedisp_ops for future extension
  auxdisplay: linedisp: Move exported symbols to a namespace
  auxdisplay: linedisp: Add missing header(s)
  auxdisplay: linedisp: Unshadow error codes in ->store()
  auxdisplay: linedisp: Use unique number for id
  ...
This commit is contained in:
Linus Torvalds 2024-03-14 09:43:51 -07:00
commit b345ff698e
18 changed files with 888 additions and 338 deletions

View File

@ -39,6 +39,6 @@ additionalProperties: false
examples:
- |
lcd@10008000 {
compatible = "arm,versatile-lcd";
reg = <0x10008000 0x1000>;
compatible = "arm,versatile-lcd";
reg = <0x10008000 0x1000>;
};

View File

@ -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 <chris.packham@alliedtelesis.co.nz>
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 <dt-bindings/gpio/gpio.h>
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>;
};

View File

@ -84,42 +84,44 @@ additionalProperties: false
examples:
- |
#include <dt-bindings/gpio/gpio.h>
auxdisplay {
compatible = "hit,hd44780";
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>;
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-height-chars = <2>;
display-width-chars = <16>;
};
- |
#include <dt-bindings/gpio/gpio.h>
i2c {
#address-cells = <1>;
#size-cells = <0>;
#address-cells = <1>;
#size-cells = <0>;
pcf8574: pcf8574@27 {
compatible = "nxp,pcf8574";
reg = <0x27>;
gpio-controller;
#gpio-cells = <2>;
};
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 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>;
};

View File

@ -74,31 +74,31 @@ examples:
#include <dt-bindings/input/input.h>
#include <dt-bindings/leds/common.h>
i2c {
#address-cells = <1>;
#size-cells = <0>;
#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 = <MATRIX_KEY(2, 0, KEY_F6)>,
<MATRIX_KEY(3, 0, KEY_F8)>,
<MATRIX_KEY(4, 0, KEY_F10)>,
<MATRIX_KEY(5, 0, KEY_F4)>,
<MATRIX_KEY(6, 0, KEY_F2)>,
<MATRIX_KEY(2, 1, KEY_F5)>,
<MATRIX_KEY(3, 1, KEY_F7)>,
<MATRIX_KEY(4, 1, KEY_F9)>,
<MATRIX_KEY(5, 1, KEY_F3)>,
<MATRIX_KEY(6, 1, KEY_F1)>;
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 = <MATRIX_KEY(2, 0, KEY_F6)>,
<MATRIX_KEY(3, 0, KEY_F8)>,
<MATRIX_KEY(4, 0, KEY_F10)>,
<MATRIX_KEY(5, 0, KEY_F4)>,
<MATRIX_KEY(6, 0, KEY_F2)>,
<MATRIX_KEY(2, 1, KEY_F5)>,
<MATRIX_KEY(3, 1, KEY_F7)>,
<MATRIX_KEY(4, 1, KEY_F9)>,
<MATRIX_KEY(5, 1, KEY_F3)>,
<MATRIX_KEY(6, 1, KEY_F1)>;
led {
color = <LED_COLOR_ID_RED>;
function = LED_FUNCTION_BACKLIGHT;
linux,default-trigger = "backlight";
};
led {
color = <LED_COLOR_ID_RED>;
function = LED_FUNCTION_BACKLIGHT;
linux,default-trigger = "backlight";
};
};
};
};

View File

@ -50,6 +50,6 @@ additionalProperties: false
examples:
- |
lcd: lcd@17fff000 {
compatible = "img,boston-lcd";
reg = <0x17fff000 0x8>;
compatible = "img,boston-lcd";
reg = <0x17fff000 0x8>;
};

View File

@ -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 <andriy.shevchenko@linux.intel.com>
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>;
};
};

View File

@ -3389,11 +3389,15 @@ F: drivers/base/auxiliary.c
F: include/linux/auxiliary_bus.h
AUXILIARY DISPLAY DRIVERS
M: Miguel Ojeda <ojeda@kernel.org>
S: Maintained
M: Andy Shevchenko <andy@kernel.org>
R: Geert Uytterhoeven <geert@linux-m68k.org>
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
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 <ak@it-klinger.de>

View File

@ -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
@ -197,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

View File

@ -14,3 +14,5 @@ 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
obj-$(CONFIG_SEG_LED_GPIO) += seg-led-gpio.o

View File

@ -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,
},

View File

@ -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,

View File

@ -15,6 +15,7 @@
#include <linux/property.h>
#include <linux/fb.h>
#include <linux/backlight.h>
#include <linux/container_of.h>
#include <linux/input.h>
#include <linux/input/matrix_keypad.h>
#include <linux/leds.h>
@ -85,16 +86,6 @@ struct ht16k33_fbdev {
uint8_t *cache;
};
struct ht16k33_seg {
struct linedisp linedisp;
union {
struct seg7_conversion_map seg7;
struct seg14_conversion_map seg14;
} map;
unsigned int map_size;
char curr[4];
};
struct ht16k33_priv {
struct i2c_client *client;
struct delayed_work work;
@ -102,12 +93,21 @@ 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;
};
#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, linedisp)
static const struct fb_fix_screeninfo ht16k33_fb_fix = {
.id = DRIVER_NAME,
.type = FB_TYPE_PACKED_PIXELS,
@ -135,33 +135,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;
@ -195,8 +168,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);
}
@ -204,8 +176,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;
@ -247,8 +218,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;
@ -440,51 +410,71 @@ 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 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;
struct ht16k33_priv *priv = ht16k33_work_to_priv(work);
struct linedisp_map *map = priv->linedisp.map;
char *s = priv->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);
}
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;
struct ht16k33_priv *priv = ht16k33_work_to_priv(work);
struct linedisp_map *map = priv->linedisp.map;
char *s = priv->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);
schedule_delayed_work(&priv->work, 0);
}
static const struct linedisp_ops ht16k33_linedisp_ops = {
.get_map_type = ht16k33_linedisp_get_map_type,
.update = ht16k33_linedisp_update,
};
static int ht16k33_led_probe(struct device *dev, struct led_classdev *led,
unsigned int brightness)
{
@ -666,47 +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;
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;
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;
}
if (err)
return err;
err = linedisp_register(&seg->linedisp, dev, 4, seg->curr,
ht16k33_linedisp_update);
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(linedisp, dev, 4, &ht16k33_linedisp_ops);
}
static int ht16k33_probe(struct i2c_client *client)
@ -770,6 +727,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;
}
@ -790,9 +750,10 @@ 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);
linedisp_unregister(&priv->linedisp);
break;
default:
break;
}
}
@ -831,4 +792,5 @@ module_i2c_driver(ht16k33_driver);
MODULE_DESCRIPTION("Holtek HT16K33 driver");
MODULE_LICENSE("GPL");
MODULE_IMPORT_NS(LINEDISP);
MODULE_AUTHOR("Robin van der Gracht <robin@protonic.nl>");

View File

@ -22,32 +22,30 @@ 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;
};
/**
* 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);
};
/*
@ -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
@ -75,7 +73,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,
},
};
/*
@ -91,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;
}
@ -103,7 +103,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,
},
};
/*
@ -191,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;
}
@ -203,7 +205,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[] = {
@ -230,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;
@ -247,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->update);
err = linedisp_register(&ctx->linedisp, dev, cfg->num_chars, &cfg->ops);
if (err)
return err;
@ -273,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 = {
@ -291,10 +291,11 @@ 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);
MODULE_DESCRIPTION("Imagination Technologies ASCII LCD Display");
MODULE_AUTHOR("Paul Burton <paul.burton@mips.com>");
MODULE_LICENSE("GPL");
MODULE_IMPORT_NS(LINEDISP);

View File

@ -10,13 +10,21 @@
#include <generated/utsrelease.h>
#include <linux/container_of.h>
#include <linux/device.h>
#include <linux/export.h>
#include <linux/idr.h>
#include <linux/jiffies.h>
#include <linux/kstrtox.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/sysfs.h>
#include <linux/timer.h>
#include <linux/map_to_7segment.h>
#include <linux/map_to_14segment.h>
#include "line-display.h"
#define DEFAULT_SCROLL_RATE (HZ / 2)
@ -45,7 +53,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++;
@ -89,7 +97,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;
}
@ -165,9 +173,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) {
@ -181,45 +191,165 @@ 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
};
ATTRIBUTE_GROUPS(linedisp);
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);
static DEFINE_IDA(linedisp_id);
static void linedisp_release(struct device *dev)
{
struct linedisp *linedisp = container_of(dev, struct linedisp, dev);
kfree(linedisp->map);
kfree(linedisp->message);
kfree(linedisp->buf);
ida_free(&linedisp_id, linedisp->id);
}
static const struct device_type linedisp_type = {
.groups = linedisp_groups,
.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
* @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))
unsigned int num_chars, const struct linedisp_ops *ops)
{
static atomic_t linedisp_id = ATOMIC_INIT(-1);
int err;
memset(linedisp, 0, sizeof(*linedisp));
linedisp->dev.parent = parent;
linedisp->dev.type = &linedisp_type;
linedisp->update = update;
linedisp->buf = buf;
linedisp->ops = ops;
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);
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)
goto out_put_device;
/* initialise a timer for scrolling the message */
timer_setup(&linedisp->timer, linedisp_scroll, 0);
@ -239,10 +369,11 @@ out_del_dev:
device_del(&linedisp->dev);
out_del_timer:
del_timer_sync(&linedisp->timer);
out_put_device:
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
@ -253,9 +384,8 @@ 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);
EXPORT_SYMBOL_NS_GPL(linedisp_unregister, LINEDISP);
MODULE_LICENSE("GPL");

View File

@ -11,33 +11,78 @@
#ifndef _LINEDISP_H
#define _LINEDISP_H
#include <linux/device.h>
#include <linux/timer_types.h>
#include <linux/map_to_7segment.h>
#include <linux/map_to_14segment.h>
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);
};
/**
* 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
* @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;
struct timer_list timer;
void (*update)(struct linedisp *linedisp);
const struct linedisp_ops *ops;
struct linedisp_map *map;
char *buf;
char *message;
unsigned int num_chars;
unsigned int message_len;
unsigned int scroll_pos;
unsigned int scroll_rate;
unsigned int id;
};
int linedisp_register(struct linedisp *linedisp, struct device *parent,
unsigned int num_chars, char *buf,
void (*update)(struct linedisp *linedisp));
unsigned int num_chars, const struct linedisp_ops *ops);
void linedisp_unregister(struct linedisp *linedisp);
#endif /* LINEDISP_H */

View File

@ -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 <andriy.shevchenko@linux.intel.com>
*/
#include <linux/array_size.h>
#include <linux/bitrev.h>
#include <linux/bits.h>
#include <linux/container_of.h>
#include <linux/err.h>
#include <linux/i2c.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/pm.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#include <linux/types.h>
#include <linux/workqueue.h>
#include <linux/map_to_7segment.h>
#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 <andriy.shevchenko@linux.intel.com>");
MODULE_LICENSE("GPL");
MODULE_IMPORT_NS(LINEDISP);

View File

@ -1519,8 +1519,103 @@ static void keypad_init(void)
static void panel_attach(struct parport *port)
{
int selected_keypad_type = NOT_SET;
struct pardev_cb panel_cb;
/* take care of an eventual profile */
switch (profile) {
case PANEL_PROFILE_CUSTOM:
/* custom profile */
selected_keypad_type = DEFAULT_KEYPAD_TYPE;
selected_lcd_type = DEFAULT_LCD_TYPE;
break;
case PANEL_PROFILE_OLD:
/* 8 bits, 2*16, old keypad */
selected_keypad_type = KEYPAD_TYPE_OLD;
selected_lcd_type = LCD_TYPE_OLD;
/* TODO: This two are a little hacky, sort it out later */
if (lcd_width == NOT_SET)
lcd_width = 16;
if (lcd_hwidth == NOT_SET)
lcd_hwidth = 16;
break;
case PANEL_PROFILE_NEW:
/* serial, 2*16, new keypad */
selected_keypad_type = KEYPAD_TYPE_NEW;
selected_lcd_type = LCD_TYPE_KS0074;
break;
case PANEL_PROFILE_HANTRONIX:
/* 8 bits, 2*16 hantronix-like, no keypad */
selected_keypad_type = KEYPAD_TYPE_NONE;
selected_lcd_type = LCD_TYPE_HANTRONIX;
break;
case PANEL_PROFILE_NEXCOM:
/* generic 8 bits, 2*16, nexcom keypad, eg. Nexcom. */
selected_keypad_type = KEYPAD_TYPE_NEXCOM;
selected_lcd_type = LCD_TYPE_NEXCOM;
break;
case PANEL_PROFILE_LARGE:
/* 8 bits, 2*40, old keypad */
selected_keypad_type = KEYPAD_TYPE_OLD;
selected_lcd_type = LCD_TYPE_OLD;
break;
}
/*
* Overwrite selection with module param values (both keypad and lcd),
* where the deprecated params have lower prio.
*/
if (keypad_enabled != NOT_SET)
selected_keypad_type = keypad_enabled;
if (keypad_type != NOT_SET)
selected_keypad_type = keypad_type;
keypad.enabled = (selected_keypad_type > 0);
if (lcd_enabled != NOT_SET)
selected_lcd_type = lcd_enabled;
if (lcd_type != NOT_SET)
selected_lcd_type = lcd_type;
lcd.enabled = (selected_lcd_type > 0);
if (lcd.enabled) {
/*
* Init lcd struct with load-time values to preserve exact
* current functionality (at least for now).
*/
lcd.charset = lcd_charset;
lcd.proto = lcd_proto;
lcd.pins.e = lcd_e_pin;
lcd.pins.rs = lcd_rs_pin;
lcd.pins.rw = lcd_rw_pin;
lcd.pins.cl = lcd_cl_pin;
lcd.pins.da = lcd_da_pin;
lcd.pins.bl = lcd_bl_pin;
}
switch (selected_keypad_type) {
case KEYPAD_TYPE_OLD:
keypad_profile = old_keypad_profile;
break;
case KEYPAD_TYPE_NEW:
keypad_profile = new_keypad_profile;
break;
case KEYPAD_TYPE_NEXCOM:
keypad_profile = nexcom_keypad_profile;
break;
default:
keypad_profile = NULL;
break;
}
if (!lcd.enabled && !keypad.enabled) {
/* no device enabled, let's exit */
pr_err("panel driver disabled.\n");
return;
}
if (port->number != parport)
return;
@ -1613,126 +1708,7 @@ static struct parport_driver panel_driver = {
.detach = panel_detach,
.devmodel = true,
};
module_parport_driver(panel_driver);
/* 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:
/* custom profile */
selected_keypad_type = DEFAULT_KEYPAD_TYPE;
selected_lcd_type = DEFAULT_LCD_TYPE;
break;
case PANEL_PROFILE_OLD:
/* 8 bits, 2*16, old keypad */
selected_keypad_type = KEYPAD_TYPE_OLD;
selected_lcd_type = LCD_TYPE_OLD;
/* TODO: This two are a little hacky, sort it out later */
if (lcd_width == NOT_SET)
lcd_width = 16;
if (lcd_hwidth == NOT_SET)
lcd_hwidth = 16;
break;
case PANEL_PROFILE_NEW:
/* serial, 2*16, new keypad */
selected_keypad_type = KEYPAD_TYPE_NEW;
selected_lcd_type = LCD_TYPE_KS0074;
break;
case PANEL_PROFILE_HANTRONIX:
/* 8 bits, 2*16 hantronix-like, no keypad */
selected_keypad_type = KEYPAD_TYPE_NONE;
selected_lcd_type = LCD_TYPE_HANTRONIX;
break;
case PANEL_PROFILE_NEXCOM:
/* generic 8 bits, 2*16, nexcom keypad, eg. Nexcom. */
selected_keypad_type = KEYPAD_TYPE_NEXCOM;
selected_lcd_type = LCD_TYPE_NEXCOM;
break;
case PANEL_PROFILE_LARGE:
/* 8 bits, 2*40, old keypad */
selected_keypad_type = KEYPAD_TYPE_OLD;
selected_lcd_type = LCD_TYPE_OLD;
break;
}
/*
* Overwrite selection with module param values (both keypad and lcd),
* where the deprecated params have lower prio.
*/
if (keypad_enabled != NOT_SET)
selected_keypad_type = keypad_enabled;
if (keypad_type != NOT_SET)
selected_keypad_type = keypad_type;
keypad.enabled = (selected_keypad_type > 0);
if (lcd_enabled != NOT_SET)
selected_lcd_type = lcd_enabled;
if (lcd_type != NOT_SET)
selected_lcd_type = lcd_type;
lcd.enabled = (selected_lcd_type > 0);
if (lcd.enabled) {
/*
* Init lcd struct with load-time values to preserve exact
* current functionality (at least for now).
*/
lcd.charset = lcd_charset;
lcd.proto = lcd_proto;
lcd.pins.e = lcd_e_pin;
lcd.pins.rs = lcd_rs_pin;
lcd.pins.rw = lcd_rw_pin;
lcd.pins.cl = lcd_cl_pin;
lcd.pins.da = lcd_da_pin;
lcd.pins.bl = lcd_bl_pin;
}
switch (selected_keypad_type) {
case KEYPAD_TYPE_OLD:
keypad_profile = old_keypad_profile;
break;
case KEYPAD_TYPE_NEW:
keypad_profile = new_keypad_profile;
break;
case KEYPAD_TYPE_NEXCOM:
keypad_profile = nexcom_keypad_profile;
break;
default:
keypad_profile = NULL;
break;
}
if (!lcd.enabled && !keypad.enabled) {
/* no device enabled, let's exit */
pr_err("panel driver disabled.\n");
return -ENODEV;
}
err = parport_register_driver(&panel_driver);
if (err) {
pr_err("could not register with parport. Aborting.\n");
return err;
}
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;
}
static void __exit panel_cleanup_module(void)
{
parport_unregister_driver(&panel_driver);
}
module_init(panel_init_module);
module_exit(panel_cleanup_module);
MODULE_AUTHOR("Willy Tarreau");
MODULE_LICENSE("GPL");

View File

@ -0,0 +1,113 @@
// 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 <linux/bitmap.h>
#include <linux/container_of.h>
#include <linux/errno.h>
#include <linux/gpio/consumer.h>
#include <linux/map_to_7segment.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/types.h>
#include <linux/workqueue.h>
#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 <chris.packham@alliedtelesis.co.nz>");
MODULE_DESCRIPTION("7 segment LED driver");
MODULE_LICENSE("GPL");
MODULE_IMPORT_NS(LINEDISP);