Commit b345ff69 authored by Linus Torvalds's avatar Linus Torvalds

Merge tag 'auxdisplay-v6.9-1' of...

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
  ...
parents 480e035f 5d9e1297
......@@ -39,6 +39,6 @@ additionalProperties: false
examples:
- |
lcd@10008000 {
compatible = "arm,versatile-lcd";
reg = <0x10008000 0x1000>;
compatible = "arm,versatile-lcd";
reg = <0x10008000 0x1000>;
};
# 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>;
};
......@@ -84,42 +84,44 @@ additionalProperties: false
examples:
- |
#include <dt-bindings/gpio/gpio.h>
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 <dt-bindings/gpio/gpio.h>
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 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>;
};
......@@ -74,31 +74,31 @@ examples:
#include <dt-bindings/input/input.h>
#include <dt-bindings/leds/common.h>
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 = <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";
};
#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 = <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";
};
};
};
};
......@@ -50,6 +50,6 @@ additionalProperties: false
examples:
- |
lcd: lcd@17fff000 {
compatible = "img,boston-lcd";
reg = <0x17fff000 0x8>;
compatible = "img,boston-lcd";
reg = <0x17fff000 0x8>;
};
# 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>;
};
};
......@@ -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>
......
......@@ -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
......
......@@ -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
......@@ -96,7 +96,7 @@ static int cfag12864bfb_probe(struct platform_device *device)
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,
},
......
......@@ -319,7 +319,7 @@ static int hd44780_probe(struct platform_device *pdev)
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,
......
......@@ -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 @@ static int ht16k33_fbdev_probe(struct device *dev, struct ht16k33_priv *priv,
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>");
......@@ -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 @@ static int img_ascii_lcd_probe(struct platform_device *pdev)
*
* 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);
......@@ -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
};
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;
};
ATTRIBUTE_GROUPS(linedisp);
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 @@ int linedisp_register(struct linedisp *linedisp, struct device *parent,
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");
......@@ -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 */
// 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);
......@@ -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");
// 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);
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment