Merge tag 'auxdisplay-for-linus-v5.11' of git://github.com/ojeda/linux
authorLinus Torvalds <torvalds@linux-foundation.org>
Mon, 14 Dec 2020 19:25:18 +0000 (11:25 -0800)
committerLinus Torvalds <torvalds@linux-foundation.org>
Mon, 14 Dec 2020 19:25:18 +0000 (11:25 -0800)
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
  ...

Documentation/devicetree/bindings/auxdisplay/modtronix,lcd2s.yaml [new file with mode: 0644]
Documentation/devicetree/bindings/vendor-prefixes.yaml
drivers/auxdisplay/Kconfig
drivers/auxdisplay/Makefile
drivers/auxdisplay/charlcd.c
drivers/auxdisplay/charlcd.h
drivers/auxdisplay/hd44780.c
drivers/auxdisplay/hd44780_common.c [new file with mode: 0644]
drivers/auxdisplay/hd44780_common.h [new file with mode: 0644]
drivers/auxdisplay/lcd2s.c [new file with mode: 0644]
drivers/auxdisplay/panel.c

diff --git a/Documentation/devicetree/bindings/auxdisplay/modtronix,lcd2s.yaml b/Documentation/devicetree/bindings/auxdisplay/modtronix,lcd2s.yaml
new file mode 100644 (file)
index 0000000..a1d55a2
--- /dev/null
@@ -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>;
+      };
+    };
index e40ee36..772b148 100644 (file)
@@ -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,.*":
index 81757ee..a2b59b8 100644 (file)
@@ -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
index cf54b5e..3077710 100644 (file)
@@ -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
index 5aee0f5..f43430e 100644 (file)
@@ -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>
 
 #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;
index 00911ad..eed8006 100644 (file)
@@ -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);
index bcbe130..2e5e7c9 100644 (file)
@@ -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 (file)
index 0000000..3934c2e
--- /dev/null
@@ -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 (file)
index 0000000..a16aa8c
--- /dev/null
@@ -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 (file)
index 0000000..38ba086
--- /dev/null
@@ -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");
index 1c82d82..ff5755e 100644 (file)
@@ -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;
        }