aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLinus Torvalds2020-12-14 11:25:18 -0800
committerLinus Torvalds2020-12-14 11:25:18 -0800
commitbcc68bd8161261ceeb1a4ab02b5265758944f90d (patch)
treecff54a01825d855ee8680e13eab4ca451570d7ae
parent1d36dffa5d887715dacca0f717f4519b7be5e498 (diff)
parent351dcacc6d774258be9fec6f51c14f8ff38243f6 (diff)
Merge tag 'auxdisplay-for-linus-v5.11' of git://github.com/ojeda/linux
Pull auxdisplay updates from Miguel Ojeda: "A bigger set of changes than usual for auxdisplay. There have been quite a few changes in auxdisplay thanks to a refactor by Lars Poeschel to share code in order to introduce a new driver. Summary: - Significant refactor work to make charlcd independent of device, i.e. hd44780 (Lars Poeschel) - New driver: lcd2s (Lars Poeschel) - Fixes on top of the rework while being tested in -next (Lars Poeschel, Dan Carpenter and kernel test robot)" * tag 'auxdisplay-for-linus-v5.11' of git://github.com/ojeda/linux: (30 commits) auxdisplay: panel: Remove redundant charlcd_ops structures auxdisplay: panel: Fix missing print function pointer auxdisplay: fix platform_no_drv_owner.cocci warnings auxdisplay: fix use after free in lcd2s_i2c_remove() auxdisplay: hd44780_common: Fix build error auxdisplay: add a driver for lcd2s character display auxdisplay: lcd2s DT binding doc auxdisplay: charlcd: Do not print chars at end of line auxdisplay: Change gotoxy calling interface auxdisplay: charlcd: replace last device specific stuff auxdisplay: hd44780: Remove clear_fast auxdisplay: hd44780_common: Reduce clear_display timeout auxdisplay: Call charlcd_backlight in place auxdisplay: Move char redefine code to hd44780_common auxdisplay: cleanup unnecessary hd44780 code in charlcd auxdisplay: implement various hd44780_common_ functions auxdisplay: Move init_display to hd44780_common auxdisplay: Make use of enum for backlight on / off auxdisplay: make charlcd_backlight visible to hd44780_common auxdisplay: Move clear_display to hd44780_common ...
-rw-r--r--Documentation/devicetree/bindings/auxdisplay/modtronix,lcd2s.yaml58
-rw-r--r--Documentation/devicetree/bindings/vendor-prefixes.yaml2
-rw-r--r--drivers/auxdisplay/Kconfig33
-rw-r--r--drivers/auxdisplay/Makefile2
-rw-r--r--drivers/auxdisplay/charlcd.c412
-rw-r--r--drivers/auxdisplay/charlcd.h86
-rw-r--r--drivers/auxdisplay/hd44780.c120
-rw-r--r--drivers/auxdisplay/hd44780_common.c361
-rw-r--r--drivers/auxdisplay/hd44780_common.h33
-rw-r--r--drivers/auxdisplay/lcd2s.c402
-rw-r--r--drivers/auxdisplay/panel.c173
11 files changed, 1218 insertions, 464 deletions
diff --git a/Documentation/devicetree/bindings/auxdisplay/modtronix,lcd2s.yaml b/Documentation/devicetree/bindings/auxdisplay/modtronix,lcd2s.yaml
new file mode 100644
index 000000000000..a1d55a2634a5
--- /dev/null
+++ b/Documentation/devicetree/bindings/auxdisplay/modtronix,lcd2s.yaml
@@ -0,0 +1,58 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/auxdisplay/modtronix,lcd2s.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Modtronix engineering LCD2S Character LCD Display
+
+maintainers:
+ - Lars Poeschel <poeschel@lemonage.de>
+
+description:
+ The LCD2S is a Character LCD Display manufactured by Modtronix Engineering.
+ The display supports a serial I2C and SPI interface. The driver currently
+ only supports the I2C interface.
+
+properties:
+ compatible:
+ const: modtronix,lcd2s
+
+ reg:
+ maxItems: 1
+ description:
+ I2C bus address of the display.
+
+ display-height-chars:
+ description: Height of the display, in character cells.
+ $ref: /schemas/types.yaml#/definitions/uint32
+ minimum: 1
+ maximum: 4
+
+ display-width-chars:
+ description: Width of the display, in character cells.
+ $ref: /schemas/types.yaml#/definitions/uint32
+ minimum: 16
+ maximum: 20
+
+required:
+ - compatible
+ - reg
+ - display-height-chars
+ - display-width-chars
+
+additionalProperties: false
+
+examples:
+ - |
+ i2c {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ lcd2s: auxdisplay@28 {
+ compatible = "modtronix,lcd2s";
+ reg = <0x28>;
+ display-height-chars = <4>;
+ display-width-chars = <20>;
+ };
+ };
diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml
index e40ee369f808..772b148795f4 100644
--- a/Documentation/devicetree/bindings/vendor-prefixes.yaml
+++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml
@@ -683,6 +683,8 @@ patternProperties:
description: MiraMEMS Sensing Technology Co., Ltd.
"^mitsubishi,.*":
description: Mitsubishi Electric Corporation
+ "^modtronix,.*":
+ description: Modtronix Engineering
"^mosaixtech,.*":
description: Mosaix Technologies, Inc.
"^motorola,.*":
diff --git a/drivers/auxdisplay/Kconfig b/drivers/auxdisplay/Kconfig
index 81757eeded68..a2b59b84bb88 100644
--- a/drivers/auxdisplay/Kconfig
+++ b/drivers/auxdisplay/Kconfig
@@ -16,10 +16,29 @@ menuconfig AUXDISPLAY
if AUXDISPLAY
+config CHARLCD
+ tristate "Character LCD core support" if COMPILE_TEST
+ help
+ This is the base system for character-based LCD displays.
+ It makes no sense to have this alone, you select your display driver
+ and if it needs the charlcd core, it will select it automatically.
+ This is some character LCD core interface that multiple drivers can
+ use.
+
+config HD44780_COMMON
+ tristate "Common functions for HD44780 (and compatibles) LCD displays" if COMPILE_TEST
+ select CHARLCD
+ help
+ This is a module with the common symbols for HD44780 (and compatibles)
+ displays. This is the code that multiple other modules use. It is not
+ useful alone. If you have some sort of HD44780 compatible display,
+ you very likely use this. It is selected automatically by selecting
+ your concrete display.
+
config HD44780
tristate "HD44780 Character LCD support"
depends on GPIOLIB || COMPILE_TEST
- select CHARLCD
+ select HD44780_COMMON
help
Enable support for Character LCDs using a HD44780 controller.
The LCD is accessible through the /dev/lcd char device (10, 156).
@@ -154,6 +173,16 @@ config HT16K33
Say yes here to add support for Holtek HT16K33, RAM mapping 16*8
LED controller driver with keyscan.
+config LCD2S
+ tristate "lcd2s 20x4 character display over I2C console"
+ depends on I2C
+ select CHARLCD
+ help
+ This is a driver that lets you use the lcd2s 20x4 character display
+ from Modtronix engineering as a console output device. The display
+ is a simple single color character display. You have to connect it
+ to an I2C bus.
+
config ARM_CHARLCD
bool "ARM Ltd. Character LCD Driver"
depends on PLAT_VERSATILE
@@ -167,7 +196,7 @@ config ARM_CHARLCD
menuconfig PARPORT_PANEL
tristate "Parallel port LCD/Keypad Panel support"
depends on PARPORT
- select CHARLCD
+ select HD44780_COMMON
help
Say Y here if you have an HD44780 or KS-0074 LCD connected to your
parallel port. This driver also features 4 and 6-key keypads. The LCD
diff --git a/drivers/auxdisplay/Makefile b/drivers/auxdisplay/Makefile
index cf54b5efb07e..307771027c89 100644
--- a/drivers/auxdisplay/Makefile
+++ b/drivers/auxdisplay/Makefile
@@ -4,6 +4,7 @@
#
obj-$(CONFIG_CHARLCD) += charlcd.o
+obj-$(CONFIG_HD44780_COMMON) += hd44780_common.o
obj-$(CONFIG_ARM_CHARLCD) += arm-charlcd.o
obj-$(CONFIG_KS0108) += ks0108.o
obj-$(CONFIG_CFAG12864B) += cfag12864b.o cfag12864bfb.o
@@ -11,3 +12,4 @@ obj-$(CONFIG_IMG_ASCII_LCD) += img-ascii-lcd.o
obj-$(CONFIG_HD44780) += hd44780.o
obj-$(CONFIG_HT16K33) += ht16k33.o
obj-$(CONFIG_PARPORT_PANEL) += panel.o
+obj-$(CONFIG_LCD2S) += lcd2s.o
diff --git a/drivers/auxdisplay/charlcd.c b/drivers/auxdisplay/charlcd.c
index 5aee0f546351..f43430e9dcee 100644
--- a/drivers/auxdisplay/charlcd.c
+++ b/drivers/auxdisplay/charlcd.c
@@ -8,7 +8,6 @@
#include <linux/atomic.h>
#include <linux/ctype.h>
-#include <linux/delay.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <linux/module.h>
@@ -22,43 +21,9 @@
#include "charlcd.h"
-#define DEFAULT_LCD_BWIDTH 40
-#define DEFAULT_LCD_HWIDTH 64
-
/* Keep the backlight on this many seconds for each flash */
#define LCD_BL_TEMPO_PERIOD 4
-#define LCD_FLAG_B 0x0004 /* Blink on */
-#define LCD_FLAG_C 0x0008 /* Cursor on */
-#define LCD_FLAG_D 0x0010 /* Display on */
-#define LCD_FLAG_F 0x0020 /* Large font mode */
-#define LCD_FLAG_N 0x0040 /* 2-rows mode */
-#define LCD_FLAG_L 0x0080 /* Backlight enabled */
-
-/* LCD commands */
-#define LCD_CMD_DISPLAY_CLEAR 0x01 /* Clear entire display */
-
-#define LCD_CMD_ENTRY_MODE 0x04 /* Set entry mode */
-#define LCD_CMD_CURSOR_INC 0x02 /* Increment cursor */
-
-#define LCD_CMD_DISPLAY_CTRL 0x08 /* Display control */
-#define LCD_CMD_DISPLAY_ON 0x04 /* Set display on */
-#define LCD_CMD_CURSOR_ON 0x02 /* Set cursor on */
-#define LCD_CMD_BLINK_ON 0x01 /* Set blink on */
-
-#define LCD_CMD_SHIFT 0x10 /* Shift cursor/display */
-#define LCD_CMD_DISPLAY_SHIFT 0x08 /* Shift display instead of cursor */
-#define LCD_CMD_SHIFT_RIGHT 0x04 /* Shift display/cursor to the right */
-
-#define LCD_CMD_FUNCTION_SET 0x20 /* Set function */
-#define LCD_CMD_DATA_LEN_8BITS 0x10 /* Set data length to 8 bits */
-#define LCD_CMD_TWO_LINES 0x08 /* Set to two display lines */
-#define LCD_CMD_FONT_5X10_DOTS 0x04 /* Set char font to 5x10 dots */
-
-#define LCD_CMD_SET_CGRAM_ADDR 0x40 /* Set char generator RAM address */
-
-#define LCD_CMD_SET_DDRAM_ADDR 0x80 /* Set display data RAM address */
-
#define LCD_ESCAPE_LEN 24 /* Max chars for LCD escape command */
#define LCD_ESCAPE_CHAR 27 /* Use char 27 for escape command */
@@ -74,12 +39,6 @@ struct charlcd_priv {
/* contains the LCD config state */
unsigned long int flags;
- /* Contains the LCD X and Y offset */
- struct {
- unsigned long int x;
- unsigned long int y;
- } addr;
-
/* Current escape sequence and it's length or -1 if outside */
struct {
char buf[LCD_ESCAPE_LEN + 1];
@@ -94,14 +53,8 @@ struct charlcd_priv {
/* Device single-open policy control */
static atomic_t charlcd_available = ATOMIC_INIT(1);
-/* sleeps that many milliseconds with a reschedule */
-static void long_sleep(int ms)
-{
- schedule_timeout_interruptible(msecs_to_jiffies(ms));
-}
-
/* turn the backlight on or off */
-static void charlcd_backlight(struct charlcd *lcd, int on)
+void charlcd_backlight(struct charlcd *lcd, enum charlcd_onoff on)
{
struct charlcd_priv *priv = charlcd_to_priv(lcd);
@@ -113,6 +66,7 @@ static void charlcd_backlight(struct charlcd *lcd, int on)
lcd->ops->backlight(lcd, on);
mutex_unlock(&priv->bl_tempo_lock);
}
+EXPORT_SYMBOL_GPL(charlcd_backlight);
static void charlcd_bl_off(struct work_struct *work)
{
@@ -124,7 +78,7 @@ static void charlcd_bl_off(struct work_struct *work)
if (priv->bl_tempo) {
priv->bl_tempo = false;
if (!(priv->flags & LCD_FLAG_L))
- priv->lcd.ops->backlight(&priv->lcd, 0);
+ priv->lcd.ops->backlight(&priv->lcd, CHARLCD_OFF);
}
mutex_unlock(&priv->bl_tempo_lock);
}
@@ -141,148 +95,41 @@ void charlcd_poke(struct charlcd *lcd)
mutex_lock(&priv->bl_tempo_lock);
if (!priv->bl_tempo && !(priv->flags & LCD_FLAG_L))
- lcd->ops->backlight(lcd, 1);
+ lcd->ops->backlight(lcd, CHARLCD_ON);
priv->bl_tempo = true;
schedule_delayed_work(&priv->bl_work, LCD_BL_TEMPO_PERIOD * HZ);
mutex_unlock(&priv->bl_tempo_lock);
}
EXPORT_SYMBOL_GPL(charlcd_poke);
-static void charlcd_gotoxy(struct charlcd *lcd)
-{
- struct charlcd_priv *priv = charlcd_to_priv(lcd);
- unsigned int addr;
-
- /*
- * we force the cursor to stay at the end of the
- * line if it wants to go farther
- */
- addr = priv->addr.x < lcd->bwidth ? priv->addr.x & (lcd->hwidth - 1)
- : lcd->bwidth - 1;
- if (priv->addr.y & 1)
- addr += lcd->hwidth;
- if (priv->addr.y & 2)
- addr += lcd->bwidth;
- lcd->ops->write_cmd(lcd, LCD_CMD_SET_DDRAM_ADDR | addr);
-}
-
static void charlcd_home(struct charlcd *lcd)
{
- struct charlcd_priv *priv = charlcd_to_priv(lcd);
-
- priv->addr.x = 0;
- priv->addr.y = 0;
- charlcd_gotoxy(lcd);
+ lcd->addr.x = 0;
+ lcd->addr.y = 0;
+ lcd->ops->home(lcd);
}
static void charlcd_print(struct charlcd *lcd, char c)
{
- struct charlcd_priv *priv = charlcd_to_priv(lcd);
-
- if (priv->addr.x < lcd->bwidth) {
- if (lcd->char_conv)
- c = lcd->char_conv[(unsigned char)c];
- lcd->ops->write_data(lcd, c);
- priv->addr.x++;
-
- /* prevents the cursor from wrapping onto the next line */
- if (priv->addr.x == lcd->bwidth)
- charlcd_gotoxy(lcd);
- }
-}
-
-static void charlcd_clear_fast(struct charlcd *lcd)
-{
- int pos;
+ if (lcd->addr.x >= lcd->width)
+ return;
- charlcd_home(lcd);
+ if (lcd->char_conv)
+ c = lcd->char_conv[(unsigned char)c];
- if (lcd->ops->clear_fast)
- lcd->ops->clear_fast(lcd);
- else
- for (pos = 0; pos < min(2, lcd->height) * lcd->hwidth; pos++)
- lcd->ops->write_data(lcd, ' ');
+ if (!lcd->ops->print(lcd, c))
+ lcd->addr.x++;
- charlcd_home(lcd);
+ /* prevents the cursor from wrapping onto the next line */
+ if (lcd->addr.x == lcd->width)
+ lcd->ops->gotoxy(lcd, lcd->addr.x - 1, lcd->addr.y);
}
-/* clears the display and resets X/Y */
static void charlcd_clear_display(struct charlcd *lcd)
{
- struct charlcd_priv *priv = charlcd_to_priv(lcd);
-
- lcd->ops->write_cmd(lcd, LCD_CMD_DISPLAY_CLEAR);
- priv->addr.x = 0;
- priv->addr.y = 0;
- /* we must wait a few milliseconds (15) */
- long_sleep(15);
-}
-
-static int charlcd_init_display(struct charlcd *lcd)
-{
- void (*write_cmd_raw)(struct charlcd *lcd, int cmd);
- struct charlcd_priv *priv = charlcd_to_priv(lcd);
- u8 init;
-
- if (lcd->ifwidth != 4 && lcd->ifwidth != 8)
- return -EINVAL;
-
- priv->flags = ((lcd->height > 1) ? LCD_FLAG_N : 0) | LCD_FLAG_D |
- LCD_FLAG_C | LCD_FLAG_B;
-
- long_sleep(20); /* wait 20 ms after power-up for the paranoid */
-
- /*
- * 8-bit mode, 1 line, small fonts; let's do it 3 times, to make sure
- * the LCD is in 8-bit mode afterwards
- */
- init = LCD_CMD_FUNCTION_SET | LCD_CMD_DATA_LEN_8BITS;
- if (lcd->ifwidth == 4) {
- init >>= 4;
- write_cmd_raw = lcd->ops->write_cmd_raw4;
- } else {
- write_cmd_raw = lcd->ops->write_cmd;
- }
- write_cmd_raw(lcd, init);
- long_sleep(10);
- write_cmd_raw(lcd, init);
- long_sleep(10);
- write_cmd_raw(lcd, init);
- long_sleep(10);
-
- if (lcd->ifwidth == 4) {
- /* Switch to 4-bit mode, 1 line, small fonts */
- lcd->ops->write_cmd_raw4(lcd, LCD_CMD_FUNCTION_SET >> 4);
- long_sleep(10);
- }
-
- /* set font height and lines number */
- lcd->ops->write_cmd(lcd,
- LCD_CMD_FUNCTION_SET |
- ((lcd->ifwidth == 8) ? LCD_CMD_DATA_LEN_8BITS : 0) |
- ((priv->flags & LCD_FLAG_F) ? LCD_CMD_FONT_5X10_DOTS : 0) |
- ((priv->flags & LCD_FLAG_N) ? LCD_CMD_TWO_LINES : 0));
- long_sleep(10);
-
- /* display off, cursor off, blink off */
- lcd->ops->write_cmd(lcd, LCD_CMD_DISPLAY_CTRL);
- long_sleep(10);
-
- lcd->ops->write_cmd(lcd,
- LCD_CMD_DISPLAY_CTRL | /* set display mode */
- ((priv->flags & LCD_FLAG_D) ? LCD_CMD_DISPLAY_ON : 0) |
- ((priv->flags & LCD_FLAG_C) ? LCD_CMD_CURSOR_ON : 0) |
- ((priv->flags & LCD_FLAG_B) ? LCD_CMD_BLINK_ON : 0));
-
- charlcd_backlight(lcd, (priv->flags & LCD_FLAG_L) ? 1 : 0);
-
- long_sleep(10);
-
- /* entry mode set : increment, cursor shifting */
- lcd->ops->write_cmd(lcd, LCD_CMD_ENTRY_MODE | LCD_CMD_CURSOR_INC);
-
- charlcd_clear_display(lcd);
- return 0;
+ lcd->ops->clear_display(lcd);
+ lcd->addr.x = 0;
+ lcd->addr.y = 0;
}
/*
@@ -360,34 +207,58 @@ static inline int handle_lcd_special_code(struct charlcd *lcd)
switch (*esc) {
case 'D': /* Display ON */
priv->flags |= LCD_FLAG_D;
+ if (priv->flags != oldflags)
+ lcd->ops->display(lcd, CHARLCD_ON);
+
processed = 1;
break;
case 'd': /* Display OFF */
priv->flags &= ~LCD_FLAG_D;
+ if (priv->flags != oldflags)
+ lcd->ops->display(lcd, CHARLCD_OFF);
+
processed = 1;
break;
case 'C': /* Cursor ON */
priv->flags |= LCD_FLAG_C;
+ if (priv->flags != oldflags)
+ lcd->ops->cursor(lcd, CHARLCD_ON);
+
processed = 1;
break;
case 'c': /* Cursor OFF */
priv->flags &= ~LCD_FLAG_C;
+ if (priv->flags != oldflags)
+ lcd->ops->cursor(lcd, CHARLCD_OFF);
+
processed = 1;
break;
case 'B': /* Blink ON */
priv->flags |= LCD_FLAG_B;
+ if (priv->flags != oldflags)
+ lcd->ops->blink(lcd, CHARLCD_ON);
+
processed = 1;
break;
case 'b': /* Blink OFF */
priv->flags &= ~LCD_FLAG_B;
+ if (priv->flags != oldflags)
+ lcd->ops->blink(lcd, CHARLCD_OFF);
+
processed = 1;
break;
case '+': /* Back light ON */
priv->flags |= LCD_FLAG_L;
+ if (priv->flags != oldflags)
+ charlcd_backlight(lcd, CHARLCD_ON);
+
processed = 1;
break;
case '-': /* Back light OFF */
priv->flags &= ~LCD_FLAG_L;
+ if (priv->flags != oldflags)
+ charlcd_backlight(lcd, CHARLCD_OFF);
+
processed = 1;
break;
case '*': /* Flash back light */
@@ -396,158 +267,98 @@ static inline int handle_lcd_special_code(struct charlcd *lcd)
break;
case 'f': /* Small Font */
priv->flags &= ~LCD_FLAG_F;
+ if (priv->flags != oldflags)
+ lcd->ops->fontsize(lcd, CHARLCD_FONTSIZE_SMALL);
+
processed = 1;
break;
case 'F': /* Large Font */
priv->flags |= LCD_FLAG_F;
+ if (priv->flags != oldflags)
+ lcd->ops->fontsize(lcd, CHARLCD_FONTSIZE_LARGE);
+
processed = 1;
break;
case 'n': /* One Line */
priv->flags &= ~LCD_FLAG_N;
+ if (priv->flags != oldflags)
+ lcd->ops->lines(lcd, CHARLCD_LINES_1);
+
processed = 1;
break;
case 'N': /* Two Lines */
priv->flags |= LCD_FLAG_N;
+ if (priv->flags != oldflags)
+ lcd->ops->lines(lcd, CHARLCD_LINES_2);
+
processed = 1;
break;
case 'l': /* Shift Cursor Left */
- if (priv->addr.x > 0) {
- /* back one char if not at end of line */
- if (priv->addr.x < lcd->bwidth)
- lcd->ops->write_cmd(lcd, LCD_CMD_SHIFT);
- priv->addr.x--;
+ if (lcd->addr.x > 0) {
+ if (!lcd->ops->shift_cursor(lcd, CHARLCD_SHIFT_LEFT))
+ lcd->addr.x--;
}
+
processed = 1;
break;
case 'r': /* shift cursor right */
- if (priv->addr.x < lcd->width) {
- /* allow the cursor to pass the end of the line */
- if (priv->addr.x < (lcd->bwidth - 1))
- lcd->ops->write_cmd(lcd,
- LCD_CMD_SHIFT | LCD_CMD_SHIFT_RIGHT);
- priv->addr.x++;
+ if (lcd->addr.x < lcd->width) {
+ if (!lcd->ops->shift_cursor(lcd, CHARLCD_SHIFT_RIGHT))
+ lcd->addr.x++;
}
+
processed = 1;
break;
case 'L': /* shift display left */
- lcd->ops->write_cmd(lcd, LCD_CMD_SHIFT | LCD_CMD_DISPLAY_SHIFT);
+ lcd->ops->shift_display(lcd, CHARLCD_SHIFT_LEFT);
processed = 1;
break;
case 'R': /* shift display right */
- lcd->ops->write_cmd(lcd,
- LCD_CMD_SHIFT | LCD_CMD_DISPLAY_SHIFT |
- LCD_CMD_SHIFT_RIGHT);
+ lcd->ops->shift_display(lcd, CHARLCD_SHIFT_RIGHT);
processed = 1;
break;
case 'k': { /* kill end of line */
- int x;
+ int x, xs, ys;
- for (x = priv->addr.x; x < lcd->bwidth; x++)
- lcd->ops->write_data(lcd, ' ');
+ xs = lcd->addr.x;
+ ys = lcd->addr.y;
+ for (x = lcd->addr.x; x < lcd->width; x++)
+ lcd->ops->print(lcd, ' ');
/* restore cursor position */
- charlcd_gotoxy(lcd);
+ lcd->addr.x = xs;
+ lcd->addr.y = ys;
+ lcd->ops->gotoxy(lcd, lcd->addr.x, lcd->addr.y);
processed = 1;
break;
}
case 'I': /* reinitialize display */
- charlcd_init_display(lcd);
+ lcd->ops->init_display(lcd);
+ priv->flags = ((lcd->height > 1) ? LCD_FLAG_N : 0) | LCD_FLAG_D |
+ LCD_FLAG_C | LCD_FLAG_B;
processed = 1;
break;
- case 'G': {
- /* Generator : LGcxxxxx...xx; must have <c> between '0'
- * and '7', representing the numerical ASCII code of the
- * redefined character, and <xx...xx> a sequence of 16
- * hex digits representing 8 bytes for each character.
- * Most LCDs will only use 5 lower bits of the 7 first
- * bytes.
- */
-
- unsigned char cgbytes[8];
- unsigned char cgaddr;
- int cgoffset;
- int shift;
- char value;
- int addr;
-
- if (!strchr(esc, ';'))
- break;
-
- esc++;
-
- cgaddr = *(esc++) - '0';
- if (cgaddr > 7) {
+ case 'G':
+ if (lcd->ops->redefine_char)
+ processed = lcd->ops->redefine_char(lcd, esc);
+ else
processed = 1;
- break;
- }
-
- cgoffset = 0;
- shift = 0;
- value = 0;
- while (*esc && cgoffset < 8) {
- int half;
-
- shift ^= 4;
-
- half = hex_to_bin(*esc++);
- if (half < 0)
- continue;
-
- value |= half << shift;
- if (shift == 0) {
- cgbytes[cgoffset++] = value;
- value = 0;
- }
- }
-
- lcd->ops->write_cmd(lcd, LCD_CMD_SET_CGRAM_ADDR | (cgaddr * 8));
- for (addr = 0; addr < cgoffset; addr++)
- lcd->ops->write_data(lcd, cgbytes[addr]);
-
- /* ensures that we stop writing to CGRAM */
- charlcd_gotoxy(lcd);
- processed = 1;
break;
- }
+
case 'x': /* gotoxy : LxXXX[yYYY]; */
case 'y': /* gotoxy : LyYYY[xXXX]; */
if (priv->esc_seq.buf[priv->esc_seq.len - 1] != ';')
break;
/* If the command is valid, move to the new address */
- if (parse_xy(esc, &priv->addr.x, &priv->addr.y))
- charlcd_gotoxy(lcd);
+ if (parse_xy(esc, &lcd->addr.x, &lcd->addr.y))
+ lcd->ops->gotoxy(lcd, lcd->addr.x, lcd->addr.y);
/* Regardless of its validity, mark as processed */
processed = 1;
break;
}
- /* TODO: This indent party here got ugly, clean it! */
- /* Check whether one flag was changed */
- if (oldflags == priv->flags)
- return processed;
-
- /* check whether one of B,C,D flags were changed */
- if ((oldflags ^ priv->flags) &
- (LCD_FLAG_B | LCD_FLAG_C | LCD_FLAG_D))
- /* set display mode */
- lcd->ops->write_cmd(lcd,
- LCD_CMD_DISPLAY_CTRL |
- ((priv->flags & LCD_FLAG_D) ? LCD_CMD_DISPLAY_ON : 0) |
- ((priv->flags & LCD_FLAG_C) ? LCD_CMD_CURSOR_ON : 0) |
- ((priv->flags & LCD_FLAG_B) ? LCD_CMD_BLINK_ON : 0));
- /* check whether one of F,N flags was changed */
- else if ((oldflags ^ priv->flags) & (LCD_FLAG_F | LCD_FLAG_N))
- lcd->ops->write_cmd(lcd,
- LCD_CMD_FUNCTION_SET |
- ((lcd->ifwidth == 8) ? LCD_CMD_DATA_LEN_8BITS : 0) |
- ((priv->flags & LCD_FLAG_F) ? LCD_CMD_FONT_5X10_DOTS : 0) |
- ((priv->flags & LCD_FLAG_N) ? LCD_CMD_TWO_LINES : 0));
- /* check whether L flag was changed */
- else if ((oldflags ^ priv->flags) & LCD_FLAG_L)
- charlcd_backlight(lcd, !!(priv->flags & LCD_FLAG_L));
-
return processed;
}
@@ -572,40 +383,39 @@ static void charlcd_write_char(struct charlcd *lcd, char c)
break;
case '\b':
/* go back one char and clear it */
- if (priv->addr.x > 0) {
- /*
- * check if we're not at the
- * end of the line
- */
- if (priv->addr.x < lcd->bwidth)
- /* back one char */
- lcd->ops->write_cmd(lcd, LCD_CMD_SHIFT);
- priv->addr.x--;
+ if (lcd->addr.x > 0) {
+ /* back one char */
+ if (!lcd->ops->shift_cursor(lcd,
+ CHARLCD_SHIFT_LEFT))
+ lcd->addr.x--;
}
/* replace with a space */
- lcd->ops->write_data(lcd, ' ');
+ charlcd_print(lcd, ' ');
/* back one char again */
- lcd->ops->write_cmd(lcd, LCD_CMD_SHIFT);
+ if (!lcd->ops->shift_cursor(lcd, CHARLCD_SHIFT_LEFT))
+ lcd->addr.x--;
+
break;
case '\f':
/* quickly clear the display */
- charlcd_clear_fast(lcd);
+ charlcd_clear_display(lcd);
break;
case '\n':
/*
* flush the remainder of the current line and
* go to the beginning of the next line
*/
- for (; priv->addr.x < lcd->bwidth; priv->addr.x++)
- lcd->ops->write_data(lcd, ' ');
- priv->addr.x = 0;
- priv->addr.y = (priv->addr.y + 1) % lcd->height;
- charlcd_gotoxy(lcd);
+ for (; lcd->addr.x < lcd->width; lcd->addr.x++)
+ lcd->ops->print(lcd, ' ');
+
+ lcd->addr.x = 0;
+ lcd->addr.y = (lcd->addr.y + 1) % lcd->height;
+ lcd->ops->gotoxy(lcd, lcd->addr.x, lcd->addr.y);
break;
case '\r':
/* go to the beginning of the same line */
- priv->addr.x = 0;
- charlcd_gotoxy(lcd);
+ lcd->addr.x = 0;
+ lcd->ops->gotoxy(lcd, lcd->addr.x, lcd->addr.y);
break;
case '\t':
/* print a space instead of the tab */
@@ -627,7 +437,7 @@ static void charlcd_write_char(struct charlcd *lcd, char c)
if (!strcmp(priv->esc_seq.buf, "[2J")) {
/* clear the display */
- charlcd_clear_fast(lcd);
+ charlcd_clear_display(lcd);
processed = 1;
} else if (!strcmp(priv->esc_seq.buf, "[H")) {
/* cursor to home */
@@ -690,8 +500,10 @@ static int charlcd_open(struct inode *inode, struct file *file)
goto fail;
if (priv->must_clear) {
- charlcd_clear_display(&priv->lcd);
+ priv->lcd.ops->clear_display(&priv->lcd);
priv->must_clear = false;
+ priv->lcd.addr.x = 0;
+ priv->lcd.addr.y = 0;
}
return nonseekable_open(inode, file);
@@ -756,6 +568,8 @@ static int charlcd_init(struct charlcd *lcd)
struct charlcd_priv *priv = charlcd_to_priv(lcd);
int ret;
+ priv->flags = ((lcd->height > 1) ? LCD_FLAG_N : 0) | LCD_FLAG_D |
+ LCD_FLAG_C | LCD_FLAG_B;
if (lcd->ops->backlight) {
mutex_init(&priv->bl_tempo_lock);
INIT_DELAYED_WORK(&priv->bl_work, charlcd_bl_off);
@@ -766,7 +580,7 @@ static int charlcd_init(struct charlcd *lcd)
* Since charlcd_init_display() needs to write data, we have to
* enable mark the LCD initialized just before.
*/
- ret = charlcd_init_display(lcd);
+ ret = lcd->ops->init_display(lcd);
if (ret)
return ret;
@@ -779,22 +593,18 @@ static int charlcd_init(struct charlcd *lcd)
return 0;
}
-struct charlcd *charlcd_alloc(unsigned int drvdata_size)
+struct charlcd *charlcd_alloc(void)
{
struct charlcd_priv *priv;
struct charlcd *lcd;
- priv = kzalloc(sizeof(*priv) + drvdata_size, GFP_KERNEL);
+ priv = kzalloc(sizeof(*priv), GFP_KERNEL);
if (!priv)
return NULL;
priv->esc_seq.len = -1;
lcd = &priv->lcd;
- lcd->ifwidth = 8;
- lcd->bwidth = DEFAULT_LCD_BWIDTH;
- lcd->hwidth = DEFAULT_LCD_HWIDTH;
- lcd->drvdata = priv->drvdata;
return lcd;
}
@@ -862,7 +672,7 @@ int charlcd_unregister(struct charlcd *lcd)
the_charlcd = NULL;
if (lcd->ops->backlight) {
cancel_delayed_work_sync(&priv->bl_work);
- priv->lcd.ops->backlight(&priv->lcd, 0);
+ priv->lcd.ops->backlight(&priv->lcd, CHARLCD_OFF);
}
return 0;
diff --git a/drivers/auxdisplay/charlcd.h b/drivers/auxdisplay/charlcd.h
index 00911ad0f3de..eed80063a6d2 100644
--- a/drivers/auxdisplay/charlcd.h
+++ b/drivers/auxdisplay/charlcd.h
@@ -9,31 +9,91 @@
#ifndef _CHARLCD_H
#define _CHARLCD_H
+#define LCD_FLAG_B 0x0004 /* Blink on */
+#define LCD_FLAG_C 0x0008 /* Cursor on */
+#define LCD_FLAG_D 0x0010 /* Display on */
+#define LCD_FLAG_F 0x0020 /* Large font mode */
+#define LCD_FLAG_N 0x0040 /* 2-rows mode */
+#define LCD_FLAG_L 0x0080 /* Backlight enabled */
+
+enum charlcd_onoff {
+ CHARLCD_OFF = 0,
+ CHARLCD_ON,
+};
+
+enum charlcd_shift_dir {
+ CHARLCD_SHIFT_LEFT,
+ CHARLCD_SHIFT_RIGHT,
+};
+
+enum charlcd_fontsize {
+ CHARLCD_FONTSIZE_SMALL,
+ CHARLCD_FONTSIZE_LARGE,
+};
+
+enum charlcd_lines {
+ CHARLCD_LINES_1,
+ CHARLCD_LINES_2,
+};
+
struct charlcd {
const struct charlcd_ops *ops;
const unsigned char *char_conv; /* Optional */
- int ifwidth; /* 4-bit or 8-bit (default) */
int height;
int width;
- int bwidth; /* Default set by charlcd_alloc() */
- int hwidth; /* Default set by charlcd_alloc() */
- void *drvdata; /* Set by charlcd_alloc() */
+ /* Contains the LCD X and Y offset */
+ struct {
+ unsigned long x;
+ unsigned long y;
+ } addr;
+
+ void *drvdata;
};
+/**
+ * struct charlcd_ops - Functions used by charlcd. Drivers have to implement
+ * these.
+ * @backlight: Turn backlight on or off. Optional.
+ * @print: Print one character to the display at current cursor position.
+ * The buffered cursor position is advanced by charlcd. The cursor should not
+ * wrap to the next line at the end of a line.
+ * @gotoxy: Set cursor to x, y. The x and y values to set the cursor to are
+ * previously set in addr.x and addr.y by charlcd.
+ * @home: Set cursor to 0, 0. The values in addr.x and addr.y are set to 0, 0 by
+ * charlcd prior to calling this function.
+ * @clear_display: Clear the whole display and set the cursor to 0, 0. The
+ * values in addr.x and addr.y are set to 0, 0 by charlcd after to calling this
+ * function.
+ * @init_display: Initialize the display.
+ * @shift_cursor: Shift cursor left or right one position.
+ * @shift_display: Shift whole display content left or right.
+ * @display: Turn display on or off.
+ * @cursor: Turn cursor on or off.
+ * @blink: Turn cursor blink on or off.
+ * @lines: One or two lines.
+ * @redefine_char: Redefine the actual pixel matrix of character.
+ */
struct charlcd_ops {
- /* Required */
- void (*write_cmd)(struct charlcd *lcd, int cmd);
- void (*write_data)(struct charlcd *lcd, int data);
-
- /* Optional */
- void (*write_cmd_raw4)(struct charlcd *lcd, int cmd); /* 4-bit only */
- void (*clear_fast)(struct charlcd *lcd);
- void (*backlight)(struct charlcd *lcd, int on);
+ void (*backlight)(struct charlcd *lcd, enum charlcd_onoff on);
+ int (*print)(struct charlcd *lcd, int c);
+ int (*gotoxy)(struct charlcd *lcd, unsigned int x, unsigned int y);
+ int (*home)(struct charlcd *lcd);
+ int (*clear_display)(struct charlcd *lcd);
+ int (*init_display)(struct charlcd *lcd);
+ int (*shift_cursor)(struct charlcd *lcd, enum charlcd_shift_dir dir);
+ int (*shift_display)(struct charlcd *lcd, enum charlcd_shift_dir dir);
+ int (*display)(struct charlcd *lcd, enum charlcd_onoff on);
+ int (*cursor)(struct charlcd *lcd, enum charlcd_onoff on);
+ int (*blink)(struct charlcd *lcd, enum charlcd_onoff on);
+ int (*fontsize)(struct charlcd *lcd, enum charlcd_fontsize size);
+ int (*lines)(struct charlcd *lcd, enum charlcd_lines lines);
+ int (*redefine_char)(struct charlcd *lcd, char *esc);
};
-struct charlcd *charlcd_alloc(unsigned int drvdata_size);
+void charlcd_backlight(struct charlcd *lcd, enum charlcd_onoff on);
+struct charlcd *charlcd_alloc(void);
void charlcd_free(struct charlcd *lcd);
int charlcd_register(struct charlcd *lcd);
diff --git a/drivers/auxdisplay/hd44780.c b/drivers/auxdisplay/hd44780.c
index bcbe13092327..2e5e7c993933 100644
--- a/drivers/auxdisplay/hd44780.c
+++ b/drivers/auxdisplay/hd44780.c
@@ -15,6 +15,7 @@
#include <linux/slab.h>
#include "charlcd.h"
+#include "hd44780_common.h"
enum hd44780_pin {
/* Order does matter due to writing to GPIO array subsets! */
@@ -37,9 +38,10 @@ struct hd44780 {
struct gpio_desc *pins[PIN_NUM];
};
-static void hd44780_backlight(struct charlcd *lcd, int on)
+static void hd44780_backlight(struct charlcd *lcd, enum charlcd_onoff on)
{
- struct hd44780 *hd = lcd->drvdata;
+ struct hd44780_common *hdc = lcd->drvdata;
+ struct hd44780 *hd = hdc->hd44780;
if (hd->pins[PIN_CTRL_BL])
gpiod_set_value_cansleep(hd->pins[PIN_CTRL_BL], on);
@@ -101,9 +103,9 @@ static void hd44780_write_gpio4(struct hd44780 *hd, u8 val, unsigned int rs)
}
/* Send a command to the LCD panel in 8 bit GPIO mode */
-static void hd44780_write_cmd_gpio8(struct charlcd *lcd, int cmd)
+static void hd44780_write_cmd_gpio8(struct hd44780_common *hdc, int cmd)
{
- struct hd44780 *hd = lcd->drvdata;
+ struct hd44780 *hd = hdc->hd44780;
hd44780_write_gpio8(hd, cmd, 0);
@@ -112,9 +114,9 @@ static void hd44780_write_cmd_gpio8(struct charlcd *lcd, int cmd)
}
/* Send data to the LCD panel in 8 bit GPIO mode */
-static void hd44780_write_data_gpio8(struct charlcd *lcd, int data)
+static void hd44780_write_data_gpio8(struct hd44780_common *hdc, int data)
{
- struct hd44780 *hd = lcd->drvdata;
+ struct hd44780 *hd = hdc->hd44780;
hd44780_write_gpio8(hd, data, 1);
@@ -123,15 +125,26 @@ static void hd44780_write_data_gpio8(struct charlcd *lcd, int data)
}
static const struct charlcd_ops hd44780_ops_gpio8 = {
- .write_cmd = hd44780_write_cmd_gpio8,
- .write_data = hd44780_write_data_gpio8,
.backlight = hd44780_backlight,
+ .print = hd44780_common_print,
+ .gotoxy = hd44780_common_gotoxy,
+ .home = hd44780_common_home,
+ .clear_display = hd44780_common_clear_display,
+ .init_display = hd44780_common_init_display,
+ .shift_cursor = hd44780_common_shift_cursor,
+ .shift_display = hd44780_common_shift_display,
+ .display = hd44780_common_display,
+ .cursor = hd44780_common_cursor,
+ .blink = hd44780_common_blink,
+ .fontsize = hd44780_common_fontsize,
+ .lines = hd44780_common_lines,
+ .redefine_char = hd44780_common_redefine_char,
};
/* Send a command to the LCD panel in 4 bit GPIO mode */
-static void hd44780_write_cmd_gpio4(struct charlcd *lcd, int cmd)
+static void hd44780_write_cmd_gpio4(struct hd44780_common *hdc, int cmd)
{
- struct hd44780 *hd = lcd->drvdata;
+ struct hd44780 *hd = hdc->hd44780;
hd44780_write_gpio4(hd, cmd, 0);
@@ -140,10 +153,10 @@ static void hd44780_write_cmd_gpio4(struct charlcd *lcd, int cmd)
}
/* Send 4-bits of a command to the LCD panel in raw 4 bit GPIO mode */
-static void hd44780_write_cmd_raw_gpio4(struct charlcd *lcd, int cmd)
+static void hd44780_write_cmd_raw_gpio4(struct hd44780_common *hdc, int cmd)
{
DECLARE_BITMAP(values, 6); /* for DATA[4-7], RS, RW */
- struct hd44780 *hd = lcd->drvdata;
+ struct hd44780 *hd = hdc->hd44780;
unsigned int n;
/* Command nibble + RS, RW */
@@ -157,9 +170,9 @@ static void hd44780_write_cmd_raw_gpio4(struct charlcd *lcd, int cmd)
}
/* Send data to the LCD panel in 4 bit GPIO mode */
-static void hd44780_write_data_gpio4(struct charlcd *lcd, int data)
+static void hd44780_write_data_gpio4(struct hd44780_common *hdc, int data)
{
- struct hd44780 *hd = lcd->drvdata;
+ struct hd44780 *hd = hdc->hd44780;
hd44780_write_gpio4(hd, data, 1);
@@ -168,10 +181,20 @@ static void hd44780_write_data_gpio4(struct charlcd *lcd, int data)
}
static const struct charlcd_ops hd44780_ops_gpio4 = {
- .write_cmd = hd44780_write_cmd_gpio4,
- .write_cmd_raw4 = hd44780_write_cmd_raw_gpio4,
- .write_data = hd44780_write_data_gpio4,
.backlight = hd44780_backlight,
+ .print = hd44780_common_print,
+ .gotoxy = hd44780_common_gotoxy,
+ .home = hd44780_common_home,
+ .clear_display = hd44780_common_clear_display,
+ .init_display = hd44780_common_init_display,
+ .shift_cursor = hd44780_common_shift_cursor,
+ .shift_display = hd44780_common_shift_display,
+ .display = hd44780_common_display,
+ .cursor = hd44780_common_cursor,
+ .blink = hd44780_common_blink,
+ .fontsize = hd44780_common_fontsize,
+ .lines = hd44780_common_lines,
+ .redefine_char = hd44780_common_redefine_char,
};
static int hd44780_probe(struct platform_device *pdev)
@@ -179,8 +202,9 @@ static int hd44780_probe(struct platform_device *pdev)
struct device *dev = &pdev->dev;
unsigned int i, base;
struct charlcd *lcd;
+ struct hd44780_common *hdc;
struct hd44780 *hd;
- int ifwidth, ret;
+ int ifwidth, ret = -ENOMEM;
/* Required pins */
ifwidth = gpiod_count(dev, "data");
@@ -198,31 +222,39 @@ static int hd44780_probe(struct platform_device *pdev)
return -EINVAL;
}
- lcd = charlcd_alloc(sizeof(struct hd44780));
- if (!lcd)
+ hdc = hd44780_common_alloc();
+ if (!hdc)
return -ENOMEM;
- hd = lcd->drvdata;
+ lcd = charlcd_alloc();
+ if (!lcd)
+ goto fail1;
+
+ hd = kzalloc(sizeof(struct hd44780), GFP_KERNEL);
+ if (!hd)
+ goto fail2;
+ hdc->hd44780 = hd;
+ lcd->drvdata = hdc;
for (i = 0; i < ifwidth; i++) {
hd->pins[base + i] = devm_gpiod_get_index(dev, "data", i,
GPIOD_OUT_LOW);
if (IS_ERR(hd->pins[base + i])) {
ret = PTR_ERR(hd->pins[base + i]);
- goto fail;
+ goto fail3;
}
}
hd->pins[PIN_CTRL_E] = devm_gpiod_get(dev, "enable", GPIOD_OUT_LOW);
if (IS_ERR(hd->pins[PIN_CTRL_E])) {
ret = PTR_ERR(hd->pins[PIN_CTRL_E]);
- goto fail;
+ goto fail3;
}
hd->pins[PIN_CTRL_RS] = devm_gpiod_get(dev, "rs", GPIOD_OUT_HIGH);
if (IS_ERR(hd->pins[PIN_CTRL_RS])) {
ret = PTR_ERR(hd->pins[PIN_CTRL_RS]);
- goto fail;
+ goto fail3;
}
/* Optional pins */
@@ -230,47 +262,60 @@ static int hd44780_probe(struct platform_device *pdev)
GPIOD_OUT_LOW);
if (IS_ERR(hd->pins[PIN_CTRL_RW])) {
ret = PTR_ERR(hd->pins[PIN_CTRL_RW]);
- goto fail;
+ goto fail3;
}
hd->pins[PIN_CTRL_BL] = devm_gpiod_get_optional(dev, "backlight",
GPIOD_OUT_LOW);
if (IS_ERR(hd->pins[PIN_CTRL_BL])) {
ret = PTR_ERR(hd->pins[PIN_CTRL_BL]);
- goto fail;
+ goto fail3;
}
/* Required properties */
ret = device_property_read_u32(dev, "display-height-chars",
&lcd->height);
if (ret)
- goto fail;
+ goto fail3;
ret = device_property_read_u32(dev, "display-width-chars", &lcd->width);
if (ret)
- goto fail;
+ goto fail3;
/*
* On displays with more than two rows, the internal buffer width is
* usually equal to the display width
*/
if (lcd->height > 2)
- lcd->bwidth = lcd->width;
+ hdc->bwidth = lcd->width;
/* Optional properties */
- device_property_read_u32(dev, "internal-buffer-width", &lcd->bwidth);
-
- lcd->ifwidth = ifwidth;
- lcd->ops = ifwidth == 8 ? &hd44780_ops_gpio8 : &hd44780_ops_gpio4;
+ device_property_read_u32(dev, "internal-buffer-width", &hdc->bwidth);
+
+ hdc->ifwidth = ifwidth;
+ if (ifwidth == 8) {
+ lcd->ops = &hd44780_ops_gpio8;
+ hdc->write_data = hd44780_write_data_gpio8;
+ hdc->write_cmd = hd44780_write_cmd_gpio8;
+ } else {
+ lcd->ops = &hd44780_ops_gpio4;
+ hdc->write_data = hd44780_write_data_gpio4;
+ hdc->write_cmd = hd44780_write_cmd_gpio4;
+ hdc->write_cmd_raw4 = hd44780_write_cmd_raw_gpio4;
+ }
ret = charlcd_register(lcd);
if (ret)
- goto fail;
+ goto fail3;
platform_set_drvdata(pdev, lcd);
return 0;
-fail:
- charlcd_free(lcd);
+fail3:
+ kfree(hd);
+fail2:
+ kfree(lcd);
+fail1:
+ kfree(hdc);
return ret;
}
@@ -278,9 +323,10 @@ static int hd44780_remove(struct platform_device *pdev)
{
struct charlcd *lcd = platform_get_drvdata(pdev);
+ kfree(lcd->drvdata);
charlcd_unregister(lcd);
- charlcd_free(lcd);
+ kfree(lcd);
return 0;
}
diff --git a/drivers/auxdisplay/hd44780_common.c b/drivers/auxdisplay/hd44780_common.c
new file mode 100644
index 000000000000..3934c2eebf33
--- /dev/null
+++ b/drivers/auxdisplay/hd44780_common.c
@@ -0,0 +1,361 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+
+#include "charlcd.h"
+#include "hd44780_common.h"
+
+/* LCD commands */
+#define LCD_CMD_DISPLAY_CLEAR 0x01 /* Clear entire display */
+
+#define LCD_CMD_ENTRY_MODE 0x04 /* Set entry mode */
+#define LCD_CMD_CURSOR_INC 0x02 /* Increment cursor */
+
+#define LCD_CMD_DISPLAY_CTRL 0x08 /* Display control */
+#define LCD_CMD_DISPLAY_ON 0x04 /* Set display on */
+#define LCD_CMD_CURSOR_ON 0x02 /* Set cursor on */
+#define LCD_CMD_BLINK_ON 0x01 /* Set blink on */
+
+#define LCD_CMD_SHIFT 0x10 /* Shift cursor/display */
+#define LCD_CMD_DISPLAY_SHIFT 0x08 /* Shift display instead of cursor */
+#define LCD_CMD_SHIFT_RIGHT 0x04 /* Shift display/cursor to the right */
+
+#define LCD_CMD_FUNCTION_SET 0x20 /* Set function */
+#define LCD_CMD_DATA_LEN_8BITS 0x10 /* Set data length to 8 bits */
+#define LCD_CMD_TWO_LINES 0x08 /* Set to two display lines */
+#define LCD_CMD_FONT_5X10_DOTS 0x04 /* Set char font to 5x10 dots */
+
+#define LCD_CMD_SET_CGRAM_ADDR 0x40 /* Set char generator RAM address */
+
+#define LCD_CMD_SET_DDRAM_ADDR 0x80 /* Set display data RAM address */
+
+/* sleeps that many milliseconds with a reschedule */
+static void long_sleep(int ms)
+{
+ schedule_timeout_interruptible(msecs_to_jiffies(ms));
+}
+
+int hd44780_common_print(struct charlcd *lcd, int c)
+{
+ struct hd44780_common *hdc = lcd->drvdata;
+
+ if (lcd->addr.x < hdc->bwidth) {
+ hdc->write_data(hdc, c);
+ return 0;
+ }
+
+ return 1;
+}
+EXPORT_SYMBOL_GPL(hd44780_common_print);
+
+int hd44780_common_gotoxy(struct charlcd *lcd, unsigned int x, unsigned int y)
+{
+ struct hd44780_common *hdc = lcd->drvdata;
+ unsigned int addr;
+
+ /*
+ * we force the cursor to stay at the end of the
+ * line if it wants to go farther
+ */
+ addr = x < hdc->bwidth ? x & (hdc->hwidth - 1) : hdc->bwidth - 1;
+ if (y & 1)
+ addr += hdc->hwidth;
+ if (y & 2)
+ addr += hdc->bwidth;
+ hdc->write_cmd(hdc, LCD_CMD_SET_DDRAM_ADDR | addr);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(hd44780_common_gotoxy);
+
+int hd44780_common_home(struct charlcd *lcd)
+{
+ return hd44780_common_gotoxy(lcd, 0, 0);
+}
+EXPORT_SYMBOL_GPL(hd44780_common_home);
+
+/* clears the display and resets X/Y */
+int hd44780_common_clear_display(struct charlcd *lcd)
+{
+ struct hd44780_common *hdc = lcd->drvdata;
+
+ hdc->write_cmd(hdc, LCD_CMD_DISPLAY_CLEAR);
+ /* datasheet says to wait 1,64 milliseconds */
+ long_sleep(2);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(hd44780_common_clear_display);
+
+int hd44780_common_init_display(struct charlcd *lcd)
+{
+ struct hd44780_common *hdc = lcd->drvdata;
+
+ void (*write_cmd_raw)(struct hd44780_common *hdc, int cmd);
+ u8 init;
+
+ if (hdc->ifwidth != 4 && hdc->ifwidth != 8)
+ return -EINVAL;
+
+ hdc->hd44780_common_flags = ((lcd->height > 1) ? LCD_FLAG_N : 0) |
+ LCD_FLAG_D | LCD_FLAG_C | LCD_FLAG_B;
+
+ long_sleep(20); /* wait 20 ms after power-up for the paranoid */
+
+ /*
+ * 8-bit mode, 1 line, small fonts; let's do it 3 times, to make sure
+ * the LCD is in 8-bit mode afterwards
+ */
+ init = LCD_CMD_FUNCTION_SET | LCD_CMD_DATA_LEN_8BITS;
+ if (hdc->ifwidth == 4) {
+ init >>= 4;
+ write_cmd_raw = hdc->write_cmd_raw4;
+ } else {
+ write_cmd_raw = hdc->write_cmd;
+ }
+ write_cmd_raw(hdc, init);
+ long_sleep(10);
+ write_cmd_raw(hdc, init);
+ long_sleep(10);
+ write_cmd_raw(hdc, init);
+ long_sleep(10);
+
+ if (hdc->ifwidth == 4) {
+ /* Switch to 4-bit mode, 1 line, small fonts */
+ hdc->write_cmd_raw4(hdc, LCD_CMD_FUNCTION_SET >> 4);
+ long_sleep(10);
+ }
+
+ /* set font height and lines number */
+ hdc->write_cmd(hdc,
+ LCD_CMD_FUNCTION_SET |
+ ((hdc->ifwidth == 8) ? LCD_CMD_DATA_LEN_8BITS : 0) |
+ ((hdc->hd44780_common_flags & LCD_FLAG_F) ?
+ LCD_CMD_FONT_5X10_DOTS : 0) |
+ ((hdc->hd44780_common_flags & LCD_FLAG_N) ?
+ LCD_CMD_TWO_LINES : 0));
+ long_sleep(10);
+
+ /* display off, cursor off, blink off */
+ hdc->write_cmd(hdc, LCD_CMD_DISPLAY_CTRL);
+ long_sleep(10);
+
+ hdc->write_cmd(hdc,
+ LCD_CMD_DISPLAY_CTRL | /* set display mode */
+ ((hdc->hd44780_common_flags & LCD_FLAG_D) ?
+ LCD_CMD_DISPLAY_ON : 0) |
+ ((hdc->hd44780_common_flags & LCD_FLAG_C) ?
+ LCD_CMD_CURSOR_ON : 0) |
+ ((hdc->hd44780_common_flags & LCD_FLAG_B) ?
+ LCD_CMD_BLINK_ON : 0));
+
+ charlcd_backlight(lcd,
+ (hdc->hd44780_common_flags & LCD_FLAG_L) ? 1 : 0);
+
+ long_sleep(10);
+
+ /* entry mode set : increment, cursor shifting */
+ hdc->write_cmd(hdc, LCD_CMD_ENTRY_MODE | LCD_CMD_CURSOR_INC);
+
+ hd44780_common_clear_display(lcd);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(hd44780_common_init_display);
+
+int hd44780_common_shift_cursor(struct charlcd *lcd, enum charlcd_shift_dir dir)
+{
+ struct hd44780_common *hdc = lcd->drvdata;
+
+ if (dir == CHARLCD_SHIFT_LEFT) {
+ /* back one char if not at end of line */
+ if (lcd->addr.x < hdc->bwidth)
+ hdc->write_cmd(hdc, LCD_CMD_SHIFT);
+ } else if (dir == CHARLCD_SHIFT_RIGHT) {
+ /* allow the cursor to pass the end of the line */
+ if (lcd->addr.x < (hdc->bwidth - 1))
+ hdc->write_cmd(hdc,
+ LCD_CMD_SHIFT | LCD_CMD_SHIFT_RIGHT);
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(hd44780_common_shift_cursor);
+
+int hd44780_common_shift_display(struct charlcd *lcd,
+ enum charlcd_shift_dir dir)
+{
+ struct hd44780_common *hdc = lcd->drvdata;
+
+ if (dir == CHARLCD_SHIFT_LEFT)
+ hdc->write_cmd(hdc, LCD_CMD_SHIFT | LCD_CMD_DISPLAY_SHIFT);
+ else if (dir == CHARLCD_SHIFT_RIGHT)
+ hdc->write_cmd(hdc, LCD_CMD_SHIFT | LCD_CMD_DISPLAY_SHIFT |
+ LCD_CMD_SHIFT_RIGHT);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(hd44780_common_shift_display);
+
+static void hd44780_common_set_mode(struct hd44780_common *hdc)
+{
+ hdc->write_cmd(hdc,
+ LCD_CMD_DISPLAY_CTRL |
+ ((hdc->hd44780_common_flags & LCD_FLAG_D) ?
+ LCD_CMD_DISPLAY_ON : 0) |
+ ((hdc->hd44780_common_flags & LCD_FLAG_C) ?
+ LCD_CMD_CURSOR_ON : 0) |
+ ((hdc->hd44780_common_flags & LCD_FLAG_B) ?
+ LCD_CMD_BLINK_ON : 0));
+}
+
+int hd44780_common_display(struct charlcd *lcd, enum charlcd_onoff on)
+{
+ struct hd44780_common *hdc = lcd->drvdata;
+
+ if (on == CHARLCD_ON)
+ hdc->hd44780_common_flags |= LCD_FLAG_D;
+ else
+ hdc->hd44780_common_flags &= ~LCD_FLAG_D;
+
+ hd44780_common_set_mode(hdc);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(hd44780_common_display);
+
+int hd44780_common_cursor(struct charlcd *lcd, enum charlcd_onoff on)
+{
+ struct hd44780_common *hdc = lcd->drvdata;
+
+ if (on == CHARLCD_ON)
+ hdc->hd44780_common_flags |= LCD_FLAG_C;
+ else
+ hdc->hd44780_common_flags &= ~LCD_FLAG_C;
+
+ hd44780_common_set_mode(hdc);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(hd44780_common_cursor);
+
+int hd44780_common_blink(struct charlcd *lcd, enum charlcd_onoff on)
+{
+ struct hd44780_common *hdc = lcd->drvdata;
+
+ if (on == CHARLCD_ON)
+ hdc->hd44780_common_flags |= LCD_FLAG_B;
+ else
+ hdc->hd44780_common_flags &= ~LCD_FLAG_B;
+
+ hd44780_common_set_mode(hdc);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(hd44780_common_blink);
+
+static void hd44780_common_set_function(struct hd44780_common *hdc)
+{
+ hdc->write_cmd(hdc,
+ LCD_CMD_FUNCTION_SET |
+ ((hdc->ifwidth == 8) ? LCD_CMD_DATA_LEN_8BITS : 0) |
+ ((hdc->hd44780_common_flags & LCD_FLAG_F) ?
+ LCD_CMD_FONT_5X10_DOTS : 0) |
+ ((hdc->hd44780_common_flags & LCD_FLAG_N) ?
+ LCD_CMD_TWO_LINES : 0));
+}
+
+int hd44780_common_fontsize(struct charlcd *lcd, enum charlcd_fontsize size)
+{
+ struct hd44780_common *hdc = lcd->drvdata;
+
+ if (size == CHARLCD_FONTSIZE_LARGE)
+ hdc->hd44780_common_flags |= LCD_FLAG_F;
+ else
+ hdc->hd44780_common_flags &= ~LCD_FLAG_F;
+
+ hd44780_common_set_function(hdc);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(hd44780_common_fontsize);
+
+int hd44780_common_lines(struct charlcd *lcd, enum charlcd_lines lines)
+{
+ struct hd44780_common *hdc = lcd->drvdata;
+
+ if (lines == CHARLCD_LINES_2)
+ hdc->hd44780_common_flags |= LCD_FLAG_N;
+ else
+ hdc->hd44780_common_flags &= ~LCD_FLAG_N;
+
+ hd44780_common_set_function(hdc);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(hd44780_common_lines);
+
+int hd44780_common_redefine_char(struct charlcd *lcd, char *esc)
+{
+ /* Generator : LGcxxxxx...xx; must have <c> between '0'
+ * and '7', representing the numerical ASCII code of the
+ * redefined character, and <xx...xx> a sequence of 16
+ * hex digits representing 8 bytes for each character.
+ * Most LCDs will only use 5 lower bits of the 7 first
+ * bytes.
+ */
+
+ struct hd44780_common *hdc = lcd->drvdata;
+ unsigned char cgbytes[8];
+ unsigned char cgaddr;
+ int cgoffset;
+ int shift;
+ char value;
+ int addr;
+
+ if (!strchr(esc, ';'))
+ return 0;
+
+ esc++;
+
+ cgaddr = *(esc++) - '0';
+ if (cgaddr > 7)
+ return 1;
+
+ cgoffset = 0;
+ shift = 0;
+ value = 0;
+ while (*esc && cgoffset < 8) {
+ int half;
+
+ shift ^= 4;
+ half = hex_to_bin(*esc++);
+ if (half < 0)
+ continue;
+
+ value |= half << shift;
+ if (shift == 0) {
+ cgbytes[cgoffset++] = value;
+ value = 0;
+ }
+ }
+
+ hdc->write_cmd(hdc, LCD_CMD_SET_CGRAM_ADDR | (cgaddr * 8));
+ for (addr = 0; addr < cgoffset; addr++)
+ hdc->write_data(hdc, cgbytes[addr]);
+
+ /* ensures that we stop writing to CGRAM */
+ lcd->ops->gotoxy(lcd, lcd->addr.x, lcd->addr.y);
+ return 1;
+}
+EXPORT_SYMBOL_GPL(hd44780_common_redefine_char);
+
+struct hd44780_common *hd44780_common_alloc(void)
+{
+ struct hd44780_common *hd;
+
+ hd = kzalloc(sizeof(*hd), GFP_KERNEL);
+ if (!hd)
+ return NULL;
+
+ hd->ifwidth = 8;
+ hd->bwidth = DEFAULT_LCD_BWIDTH;
+ hd->hwidth = DEFAULT_LCD_HWIDTH;
+ return hd;
+}
+EXPORT_SYMBOL_GPL(hd44780_common_alloc);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/auxdisplay/hd44780_common.h b/drivers/auxdisplay/hd44780_common.h
new file mode 100644
index 000000000000..a16aa8c29c99
--- /dev/null
+++ b/drivers/auxdisplay/hd44780_common.h
@@ -0,0 +1,33 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#define DEFAULT_LCD_BWIDTH 40
+#define DEFAULT_LCD_HWIDTH 64
+
+struct hd44780_common {
+ int ifwidth; /* 4-bit or 8-bit (default) */
+ int bwidth; /* Default set by hd44780_alloc() */
+ int hwidth; /* Default set by hd44780_alloc() */
+ unsigned long hd44780_common_flags;
+ void (*write_data)(struct hd44780_common *hdc, int data);
+ void (*write_cmd)(struct hd44780_common *hdc, int cmd);
+ /* write_cmd_raw4 is for 4-bit connected displays only */
+ void (*write_cmd_raw4)(struct hd44780_common *hdc, int cmd);
+ void *hd44780;
+};
+
+int hd44780_common_print(struct charlcd *lcd, int c);
+int hd44780_common_gotoxy(struct charlcd *lcd, unsigned int x, unsigned int y);
+int hd44780_common_home(struct charlcd *lcd);
+int hd44780_common_clear_display(struct charlcd *lcd);
+int hd44780_common_init_display(struct charlcd *lcd);
+int hd44780_common_shift_cursor(struct charlcd *lcd,
+ enum charlcd_shift_dir dir);
+int hd44780_common_shift_display(struct charlcd *lcd,
+ enum charlcd_shift_dir dir);
+int hd44780_common_display(struct charlcd *lcd, enum charlcd_onoff on);
+int hd44780_common_cursor(struct charlcd *lcd, enum charlcd_onoff on);
+int hd44780_common_blink(struct charlcd *lcd, enum charlcd_onoff on);
+int hd44780_common_fontsize(struct charlcd *lcd, enum charlcd_fontsize size);
+int hd44780_common_lines(struct charlcd *lcd, enum charlcd_lines lines);
+int hd44780_common_redefine_char(struct charlcd *lcd, char *esc);
+struct hd44780_common *hd44780_common_alloc(void);
diff --git a/drivers/auxdisplay/lcd2s.c b/drivers/auxdisplay/lcd2s.c
new file mode 100644
index 000000000000..38ba08628ccb
--- /dev/null
+++ b/drivers/auxdisplay/lcd2s.c
@@ -0,0 +1,402 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * console driver for LCD2S 4x20 character displays connected through i2c.
+ * The display also has a spi interface, but the driver does not support
+ * this yet.
+ *
+ * This is a driver allowing you to use a LCD2S 4x20 from modtronix
+ * engineering as auxdisplay character device.
+ *
+ * (C) 2019 by Lemonage Software GmbH
+ * Author: Lars Pöschel <poeschel@lemonage.de>
+ * All rights reserved.
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+#include <linux/delay.h>
+
+#include "charlcd.h"
+
+#define LCD2S_CMD_CUR_MOVES_FWD 0x09
+#define LCD2S_CMD_CUR_BLINK_OFF 0x10
+#define LCD2S_CMD_CUR_UL_OFF 0x11
+#define LCD2S_CMD_DISPLAY_OFF 0x12
+#define LCD2S_CMD_CUR_BLINK_ON 0x18
+#define LCD2S_CMD_CUR_UL_ON 0x19
+#define LCD2S_CMD_DISPLAY_ON 0x1a
+#define LCD2S_CMD_BACKLIGHT_OFF 0x20
+#define LCD2S_CMD_BACKLIGHT_ON 0x28
+#define LCD2S_CMD_WRITE 0x80
+#define LCD2S_CMD_MOV_CUR_RIGHT 0x83
+#define LCD2S_CMD_MOV_CUR_LEFT 0x84
+#define LCD2S_CMD_SHIFT_RIGHT 0x85
+#define LCD2S_CMD_SHIFT_LEFT 0x86
+#define LCD2S_CMD_SHIFT_UP 0x87
+#define LCD2S_CMD_SHIFT_DOWN 0x88
+#define LCD2S_CMD_CUR_ADDR 0x89
+#define LCD2S_CMD_CUR_POS 0x8a
+#define LCD2S_CMD_CUR_RESET 0x8b
+#define LCD2S_CMD_CLEAR 0x8c
+#define LCD2S_CMD_DEF_CUSTOM_CHAR 0x92
+#define LCD2S_CMD_READ_STATUS 0xd0
+
+#define LCD2S_CHARACTER_SIZE 8
+
+#define LCD2S_STATUS_BUF_MASK 0x7f
+
+struct lcd2s_data {
+ struct i2c_client *i2c;
+ struct charlcd *charlcd;
+};
+
+static s32 lcd2s_wait_buf_free(const struct i2c_client *client, int count)
+{
+ s32 status;
+
+ status = i2c_smbus_read_byte_data(client, LCD2S_CMD_READ_STATUS);
+ if (status < 0)
+ return status;
+
+ while ((status & LCD2S_STATUS_BUF_MASK) < count) {
+ mdelay(1);
+ status = i2c_smbus_read_byte_data(client,
+ LCD2S_CMD_READ_STATUS);
+ if (status < 0)
+ return status;
+ }
+ return 0;
+}
+
+static int lcd2s_i2c_master_send(const struct i2c_client *client,
+ const char *buf, int count)
+{
+ s32 status;
+
+ status = lcd2s_wait_buf_free(client, count);
+ if (status < 0)
+ return status;
+
+ return i2c_master_send(client, buf, count);
+}
+
+static int lcd2s_i2c_smbus_write_byte(const struct i2c_client *client, u8 value)
+{
+ s32 status;
+
+ status = lcd2s_wait_buf_free(client, 1);
+ if (status < 0)
+ return status;
+
+ return i2c_smbus_write_byte(client, value);
+}
+
+static int lcd2s_print(struct charlcd *lcd, int c)
+{
+ struct lcd2s_data *lcd2s = lcd->drvdata;
+ u8 buf[2] = { LCD2S_CMD_WRITE, c };
+
+ lcd2s_i2c_master_send(lcd2s->i2c, buf, sizeof(buf));
+ return 0;
+}
+
+static int lcd2s_gotoxy(struct charlcd *lcd, unsigned int x, unsigned int y)
+{
+ struct lcd2s_data *lcd2s = lcd->drvdata;
+ u8 buf[] = { LCD2S_CMD_CUR_POS, y + 1, x + 1};
+
+ lcd2s_i2c_master_send(lcd2s->i2c, buf, sizeof(buf));
+
+ return 0;
+}
+
+static int lcd2s_home(struct charlcd *lcd)
+{
+ struct lcd2s_data *lcd2s = lcd->drvdata;
+
+ lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_CUR_RESET);
+ return 0;
+}
+
+static int lcd2s_init_display(struct charlcd *lcd)
+{
+ struct lcd2s_data *lcd2s = lcd->drvdata;
+
+ /* turn everything off, but display on */
+ lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_DISPLAY_ON);
+ lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_BACKLIGHT_OFF);
+ lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_CUR_MOVES_FWD);
+ lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_CUR_BLINK_OFF);
+ lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_CUR_UL_OFF);
+ lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_CLEAR);
+
+ return 0;
+}
+
+static int lcd2s_shift_cursor(struct charlcd *lcd, enum charlcd_shift_dir dir)
+{
+ struct lcd2s_data *lcd2s = lcd->drvdata;
+
+ if (dir == CHARLCD_SHIFT_LEFT)
+ lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_MOV_CUR_LEFT);
+ else
+ lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_MOV_CUR_RIGHT);
+
+ return 0;
+}
+
+static int lcd2s_shift_display(struct charlcd *lcd, enum charlcd_shift_dir dir)
+{
+ struct lcd2s_data *lcd2s = lcd->drvdata;
+
+ if (dir == CHARLCD_SHIFT_LEFT)
+ lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_SHIFT_LEFT);
+ else
+ lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_SHIFT_RIGHT);
+
+ return 0;
+}
+
+static void lcd2s_backlight(struct charlcd *lcd, enum charlcd_onoff on)
+{
+ struct lcd2s_data *lcd2s = lcd->drvdata;
+
+ if (on)
+ lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_BACKLIGHT_ON);
+ else
+ lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_BACKLIGHT_OFF);
+}
+
+static int lcd2s_display(struct charlcd *lcd, enum charlcd_onoff on)
+{
+ struct lcd2s_data *lcd2s = lcd->drvdata;
+
+ if (on)
+ lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_DISPLAY_ON);
+ else
+ lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_DISPLAY_OFF);
+
+ return 0;
+}
+
+static int lcd2s_cursor(struct charlcd *lcd, enum charlcd_onoff on)
+{
+ struct lcd2s_data *lcd2s = lcd->drvdata;
+
+ if (on)
+ lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_CUR_UL_ON);
+ else
+ lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_CUR_UL_OFF);
+
+ return 0;
+}
+
+static int lcd2s_blink(struct charlcd *lcd, enum charlcd_onoff on)
+{
+ struct lcd2s_data *lcd2s = lcd->drvdata;
+
+ if (on)
+ lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_CUR_BLINK_ON);
+ else
+ lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_CUR_BLINK_OFF);
+
+ return 0;
+}
+
+static int lcd2s_fontsize(struct charlcd *lcd, enum charlcd_fontsize size)
+{
+ return 0;
+}
+
+static int lcd2s_lines(struct charlcd *lcd, enum charlcd_lines lines)
+{
+ return 0;
+}
+
+static int lcd2s_redefine_char(struct charlcd *lcd, char *esc)
+{
+ /* Generator : LGcxxxxx...xx; must have <c> between '0'
+ * and '7', representing the numerical ASCII code of the
+ * redefined character, and <xx...xx> a sequence of 16
+ * hex digits representing 8 bytes for each character.
+ * Most LCDs will only use 5 lower bits of the 7 first
+ * bytes.
+ */
+
+ struct lcd2s_data *lcd2s = lcd->drvdata;
+ u8 buf[LCD2S_CHARACTER_SIZE + 2] = { LCD2S_CMD_DEF_CUSTOM_CHAR };
+ u8 value;
+ int shift, i;
+
+ if (!strchr(esc, ';'))
+ return 0;
+
+ esc++;
+
+ buf[1] = *(esc++) - '0';
+ if (buf[1] > 7)
+ return 1;
+
+ i = 0;
+ shift = 0;
+ value = 0;
+ while (*esc && i < LCD2S_CHARACTER_SIZE + 2) {
+ int half;
+
+ shift ^= 4;
+ half = hex_to_bin(*esc++);
+ if (half < 0)
+ continue;
+
+ value |= half << shift;
+ if (shift == 0) {
+ buf[i++] = value;
+ value = 0;
+ }
+ }
+
+ lcd2s_i2c_master_send(lcd2s->i2c, buf, sizeof(buf));
+ return 1;
+}
+
+static int lcd2s_clear_display(struct charlcd *lcd)
+{
+ struct lcd2s_data *lcd2s = lcd->drvdata;
+
+ /* This implicitly sets cursor to first row and column */
+ lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_CLEAR);
+ return 0;
+}
+
+static const struct charlcd_ops lcd2s_ops = {
+ .print = lcd2s_print,
+ .backlight = lcd2s_backlight,
+ .gotoxy = lcd2s_gotoxy,
+ .home = lcd2s_home,
+ .clear_display = lcd2s_clear_display,
+ .init_display = lcd2s_init_display,
+ .shift_cursor = lcd2s_shift_cursor,
+ .shift_display = lcd2s_shift_display,
+ .display = lcd2s_display,
+ .cursor = lcd2s_cursor,
+ .blink = lcd2s_blink,
+ .fontsize = lcd2s_fontsize,
+ .lines = lcd2s_lines,
+ .redefine_char = lcd2s_redefine_char,
+};
+
+static int lcd2s_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct charlcd *lcd;
+ struct lcd2s_data *lcd2s;
+ int err;
+
+ if (!i2c_check_functionality(i2c->adapter,
+ I2C_FUNC_SMBUS_WRITE_BYTE_DATA |
+ I2C_FUNC_SMBUS_WRITE_BLOCK_DATA))
+ return -EIO;
+
+ /* Test, if the display is responding */
+ err = lcd2s_i2c_smbus_write_byte(i2c, LCD2S_CMD_DISPLAY_OFF);
+ if (err < 0)
+ return err;
+
+ lcd = charlcd_alloc();
+ if (!lcd)
+ return -ENOMEM;
+
+ lcd2s = kzalloc(sizeof(struct lcd2s_data), GFP_KERNEL);
+ if (!lcd2s) {
+ err = -ENOMEM;
+ goto fail1;
+ }
+
+ lcd->drvdata = lcd2s;
+ lcd2s->i2c = i2c;
+ lcd2s->charlcd = lcd;
+
+ /* Required properties */
+ err = device_property_read_u32(&i2c->dev, "display-height-chars",
+ &lcd->height);
+ if (err)
+ goto fail2;
+
+ err = device_property_read_u32(&i2c->dev, "display-width-chars",
+ &lcd->width);
+ if (err)
+ goto fail2;
+
+ lcd->ops = &lcd2s_ops;
+
+ err = charlcd_register(lcd2s->charlcd);
+ if (err)
+ goto fail2;
+
+ i2c_set_clientdata(i2c, lcd2s);
+ return 0;
+
+fail2:
+ kfree(lcd2s);
+fail1:
+ kfree(lcd);
+ return err;
+}
+
+static int lcd2s_i2c_remove(struct i2c_client *i2c)
+{
+ struct lcd2s_data *lcd2s = i2c_get_clientdata(i2c);
+
+ charlcd_unregister(lcd2s->charlcd);
+ kfree(lcd2s->charlcd);
+ return 0;
+}
+
+static const struct i2c_device_id lcd2s_i2c_id[] = {
+ { "lcd2s", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, lcd2s_i2c_id);
+
+#ifdef CONFIG_OF
+static const struct of_device_id lcd2s_of_table[] = {
+ { .compatible = "modtronix,lcd2s" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, lcd2s_of_table);
+#endif
+
+static struct i2c_driver lcd2s_i2c_driver = {
+ .driver = {
+ .name = "lcd2s",
+#ifdef CONFIG_OF
+ .of_match_table = of_match_ptr(lcd2s_of_table),
+#endif
+ },
+ .probe = lcd2s_i2c_probe,
+ .remove = lcd2s_i2c_remove,
+ .id_table = lcd2s_i2c_id,
+};
+
+static int __init lcd2s_modinit(void)
+{
+ int ret = 0;
+
+ ret = i2c_add_driver(&lcd2s_i2c_driver);
+ if (ret != 0)
+ pr_err("Failed to register lcd2s driver\n");
+
+ return ret;
+}
+module_init(lcd2s_modinit)
+
+static void __exit lcd2s_exit(void)
+{
+ i2c_del_driver(&lcd2s_i2c_driver);
+}
+module_exit(lcd2s_exit)
+
+MODULE_DESCRIPTION("LCD2S character display driver");
+MODULE_AUTHOR("Lars Poeschel");
+MODULE_LICENSE("GPL");
diff --git a/drivers/auxdisplay/panel.c b/drivers/auxdisplay/panel.c
index 1c82d824ae00..ff5755ee5694 100644
--- a/drivers/auxdisplay/panel.c
+++ b/drivers/auxdisplay/panel.c
@@ -56,6 +56,7 @@
#include <linux/uaccess.h>
#include "charlcd.h"
+#include "hd44780_common.h"
#define LCD_MAXBYTES 256 /* max burst write */
@@ -298,8 +299,6 @@ static unsigned char lcd_bits[LCD_PORTS][LCD_BITS][BIT_STATES];
#define DEFAULT_LCD_TYPE LCD_TYPE_OLD
#define DEFAULT_LCD_HEIGHT 2
#define DEFAULT_LCD_WIDTH 40
-#define DEFAULT_LCD_BWIDTH 40
-#define DEFAULT_LCD_HWIDTH 64
#define DEFAULT_LCD_CHARSET LCD_CHARSET_NORMAL
#define DEFAULT_LCD_PROTO LCD_PROTO_PARALLEL
@@ -708,7 +707,7 @@ static void lcd_send_serial(int byte)
}
/* turn the backlight on or off */
-static void lcd_backlight(struct charlcd *charlcd, int on)
+static void lcd_backlight(struct charlcd *charlcd, enum charlcd_onoff on)
{
if (lcd.pins.bl == PIN_NONE)
return;
@@ -724,7 +723,7 @@ static void lcd_backlight(struct charlcd *charlcd, int on)
}
/* send a command to the LCD panel in serial mode */
-static void lcd_write_cmd_s(struct charlcd *charlcd, int cmd)
+static void lcd_write_cmd_s(struct hd44780_common *hdc, int cmd)
{
spin_lock_irq(&pprt_lock);
lcd_send_serial(0x1F); /* R/W=W, RS=0 */
@@ -735,7 +734,7 @@ static void lcd_write_cmd_s(struct charlcd *charlcd, int cmd)
}
/* send data to the LCD panel in serial mode */
-static void lcd_write_data_s(struct charlcd *charlcd, int data)
+static void lcd_write_data_s(struct hd44780_common *hdc, int data)
{
spin_lock_irq(&pprt_lock);
lcd_send_serial(0x5F); /* R/W=W, RS=1 */
@@ -746,7 +745,7 @@ static void lcd_write_data_s(struct charlcd *charlcd, int data)
}
/* send a command to the LCD panel in 8 bits parallel mode */
-static void lcd_write_cmd_p8(struct charlcd *charlcd, int cmd)
+static void lcd_write_cmd_p8(struct hd44780_common *hdc, int cmd)
{
spin_lock_irq(&pprt_lock);
/* present the data to the data port */
@@ -768,7 +767,7 @@ static void lcd_write_cmd_p8(struct charlcd *charlcd, int cmd)
}
/* send data to the LCD panel in 8 bits parallel mode */
-static void lcd_write_data_p8(struct charlcd *charlcd, int data)
+static void lcd_write_data_p8(struct hd44780_common *hdc, int data)
{
spin_lock_irq(&pprt_lock);
/* present the data to the data port */
@@ -790,7 +789,7 @@ static void lcd_write_data_p8(struct charlcd *charlcd, int data)
}
/* send a command to the TI LCD panel */
-static void lcd_write_cmd_tilcd(struct charlcd *charlcd, int cmd)
+static void lcd_write_cmd_tilcd(struct hd44780_common *hdc, int cmd)
{
spin_lock_irq(&pprt_lock);
/* present the data to the control port */
@@ -800,7 +799,7 @@ static void lcd_write_cmd_tilcd(struct charlcd *charlcd, int cmd)
}
/* send data to the TI LCD panel */
-static void lcd_write_data_tilcd(struct charlcd *charlcd, int data)
+static void lcd_write_data_tilcd(struct hd44780_common *hdc, int data)
{
spin_lock_irq(&pprt_lock);
/* present the data to the data port */
@@ -809,105 +808,50 @@ static void lcd_write_data_tilcd(struct charlcd *charlcd, int data)
spin_unlock_irq(&pprt_lock);
}
-/* fills the display with spaces and resets X/Y */
-static void lcd_clear_fast_s(struct charlcd *charlcd)
-{
- int pos;
-
- spin_lock_irq(&pprt_lock);
- for (pos = 0; pos < charlcd->height * charlcd->hwidth; pos++) {
- lcd_send_serial(0x5F); /* R/W=W, RS=1 */
- lcd_send_serial(' ' & 0x0F);
- lcd_send_serial((' ' >> 4) & 0x0F);
- /* the shortest data takes at least 40 us */
- udelay(40);
- }
- spin_unlock_irq(&pprt_lock);
-}
-
-/* fills the display with spaces and resets X/Y */
-static void lcd_clear_fast_p8(struct charlcd *charlcd)
-{
- int pos;
-
- spin_lock_irq(&pprt_lock);
- for (pos = 0; pos < charlcd->height * charlcd->hwidth; pos++) {
- /* present the data to the data port */
- w_dtr(pprt, ' ');
-
- /* maintain the data during 20 us before the strobe */
- udelay(20);
-
- set_bit(LCD_BIT_E, bits);
- set_bit(LCD_BIT_RS, bits);
- clear_bit(LCD_BIT_RW, bits);
- set_ctrl_bits();
-
- /* maintain the strobe during 40 us */
- udelay(40);
-
- clear_bit(LCD_BIT_E, bits);
- set_ctrl_bits();
-
- /* the shortest data takes at least 45 us */
- udelay(45);
- }
- spin_unlock_irq(&pprt_lock);
-}
-
-/* fills the display with spaces and resets X/Y */
-static void lcd_clear_fast_tilcd(struct charlcd *charlcd)
-{
- int pos;
-
- spin_lock_irq(&pprt_lock);
- for (pos = 0; pos < charlcd->height * charlcd->hwidth; pos++) {
- /* present the data to the data port */
- w_dtr(pprt, ' ');
- udelay(60);
- }
-
- spin_unlock_irq(&pprt_lock);
-}
-
-static const struct charlcd_ops charlcd_serial_ops = {
- .write_cmd = lcd_write_cmd_s,
- .write_data = lcd_write_data_s,
- .clear_fast = lcd_clear_fast_s,
- .backlight = lcd_backlight,
-};
-
-static const struct charlcd_ops charlcd_parallel_ops = {
- .write_cmd = lcd_write_cmd_p8,
- .write_data = lcd_write_data_p8,
- .clear_fast = lcd_clear_fast_p8,
- .backlight = lcd_backlight,
-};
-
-static const struct charlcd_ops charlcd_tilcd_ops = {
- .write_cmd = lcd_write_cmd_tilcd,
- .write_data = lcd_write_data_tilcd,
- .clear_fast = lcd_clear_fast_tilcd,
+static const struct charlcd_ops charlcd_ops = {
.backlight = lcd_backlight,
+ .print = hd44780_common_print,
+ .gotoxy = hd44780_common_gotoxy,
+ .home = hd44780_common_home,
+ .clear_display = hd44780_common_clear_display,
+ .init_display = hd44780_common_init_display,
+ .shift_cursor = hd44780_common_shift_cursor,
+ .shift_display = hd44780_common_shift_display,
+ .display = hd44780_common_display,
+ .cursor = hd44780_common_cursor,
+ .blink = hd44780_common_blink,
+ .fontsize = hd44780_common_fontsize,
+ .lines = hd44780_common_lines,
+ .redefine_char = hd44780_common_redefine_char,
};
/* initialize the LCD driver */
static void lcd_init(void)
{
struct charlcd *charlcd;
+ struct hd44780_common *hdc;
- charlcd = charlcd_alloc(0);
- if (!charlcd)
+ hdc = hd44780_common_alloc();
+ if (!hdc)
return;
+ charlcd = charlcd_alloc();
+ if (!charlcd) {
+ kfree(hdc);
+ return;
+ }
+
+ hdc->hd44780 = &lcd;
+ charlcd->drvdata = hdc;
+
/*
* Init lcd struct with load-time values to preserve exact
* current functionality (at least for now).
*/
charlcd->height = lcd_height;
charlcd->width = lcd_width;
- charlcd->bwidth = lcd_bwidth;
- charlcd->hwidth = lcd_hwidth;
+ hdc->bwidth = lcd_bwidth;
+ hdc->hwidth = lcd_hwidth;
switch (selected_lcd_type) {
case LCD_TYPE_OLD:
@@ -918,8 +862,8 @@ static void lcd_init(void)
lcd.pins.rs = PIN_AUTOLF;
charlcd->width = 40;
- charlcd->bwidth = 40;
- charlcd->hwidth = 64;
+ hdc->bwidth = 40;
+ hdc->hwidth = 64;
charlcd->height = 2;
break;
case LCD_TYPE_KS0074:
@@ -931,8 +875,8 @@ static void lcd_init(void)
lcd.pins.da = PIN_D0;
charlcd->width = 16;
- charlcd->bwidth = 40;
- charlcd->hwidth = 16;
+ hdc->bwidth = 40;
+ hdc->hwidth = 16;
charlcd->height = 2;
break;
case LCD_TYPE_NEXCOM:
@@ -944,8 +888,8 @@ static void lcd_init(void)
lcd.pins.rw = PIN_INITP;
charlcd->width = 16;
- charlcd->bwidth = 40;
- charlcd->hwidth = 64;
+ hdc->bwidth = 40;
+ hdc->hwidth = 64;
charlcd->height = 2;
break;
case LCD_TYPE_CUSTOM:
@@ -963,8 +907,8 @@ static void lcd_init(void)
lcd.pins.rs = PIN_SELECP;
charlcd->width = 16;
- charlcd->bwidth = 40;
- charlcd->hwidth = 64;
+ hdc->bwidth = 40;
+ hdc->hwidth = 64;
charlcd->height = 2;
break;
}
@@ -975,9 +919,9 @@ static void lcd_init(void)
if (lcd_width != NOT_SET)
charlcd->width = lcd_width;
if (lcd_bwidth != NOT_SET)
- charlcd->bwidth = lcd_bwidth;
+ hdc->bwidth = lcd_bwidth;
if (lcd_hwidth != NOT_SET)
- charlcd->hwidth = lcd_hwidth;
+ hdc->hwidth = lcd_hwidth;
if (lcd_charset != NOT_SET)
lcd.charset = lcd_charset;
if (lcd_proto != NOT_SET)
@@ -998,15 +942,17 @@ static void lcd_init(void)
/* this is used to catch wrong and default values */
if (charlcd->width <= 0)
charlcd->width = DEFAULT_LCD_WIDTH;
- if (charlcd->bwidth <= 0)
- charlcd->bwidth = DEFAULT_LCD_BWIDTH;
- if (charlcd->hwidth <= 0)
- charlcd->hwidth = DEFAULT_LCD_HWIDTH;
+ if (hdc->bwidth <= 0)
+ hdc->bwidth = DEFAULT_LCD_BWIDTH;
+ if (hdc->hwidth <= 0)
+ hdc->hwidth = DEFAULT_LCD_HWIDTH;
if (charlcd->height <= 0)
charlcd->height = DEFAULT_LCD_HEIGHT;
if (lcd.proto == LCD_PROTO_SERIAL) { /* SERIAL */
- charlcd->ops = &charlcd_serial_ops;
+ charlcd->ops = &charlcd_ops;
+ hdc->write_data = lcd_write_data_s;
+ hdc->write_cmd = lcd_write_cmd_s;
if (lcd.pins.cl == PIN_NOT_SET)
lcd.pins.cl = DEFAULT_LCD_PIN_SCL;
@@ -1014,7 +960,9 @@ static void lcd_init(void)
lcd.pins.da = DEFAULT_LCD_PIN_SDA;
} else if (lcd.proto == LCD_PROTO_PARALLEL) { /* PARALLEL */
- charlcd->ops = &charlcd_parallel_ops;
+ charlcd->ops = &charlcd_ops;
+ hdc->write_data = lcd_write_data_p8;
+ hdc->write_cmd = lcd_write_cmd_p8;
if (lcd.pins.e == PIN_NOT_SET)
lcd.pins.e = DEFAULT_LCD_PIN_E;
@@ -1023,7 +971,9 @@ static void lcd_init(void)
if (lcd.pins.rw == PIN_NOT_SET)
lcd.pins.rw = DEFAULT_LCD_PIN_RW;
} else {
- charlcd->ops = &charlcd_tilcd_ops;
+ charlcd->ops = &charlcd_ops;
+ hdc->write_data = lcd_write_data_tilcd;
+ hdc->write_cmd = lcd_write_cmd_tilcd;
}
if (lcd.pins.bl == PIN_NOT_SET)
@@ -1620,7 +1570,7 @@ err_lcd_unreg:
if (lcd.enabled)
charlcd_unregister(lcd.charlcd);
err_unreg_device:
- charlcd_free(lcd.charlcd);
+ kfree(lcd.charlcd);
lcd.charlcd = NULL;
parport_unregister_device(pprt);
pprt = NULL;
@@ -1647,7 +1597,8 @@ static void panel_detach(struct parport *port)
if (lcd.enabled) {
charlcd_unregister(lcd.charlcd);
lcd.initialized = false;
- charlcd_free(lcd.charlcd);
+ kfree(lcd.charlcd->drvdata);
+ kfree(lcd.charlcd);
lcd.charlcd = NULL;
}