diff options
author | Linus Torvalds | 2020-12-14 11:25:18 -0800 |
---|---|---|
committer | Linus Torvalds | 2020-12-14 11:25:18 -0800 |
commit | bcc68bd8161261ceeb1a4ab02b5265758944f90d (patch) | |
tree | cff54a01825d855ee8680e13eab4ca451570d7ae | |
parent | 1d36dffa5d887715dacca0f717f4519b7be5e498 (diff) | |
parent | 351dcacc6d774258be9fec6f51c14f8ff38243f6 (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.yaml | 58 | ||||
-rw-r--r-- | Documentation/devicetree/bindings/vendor-prefixes.yaml | 2 | ||||
-rw-r--r-- | drivers/auxdisplay/Kconfig | 33 | ||||
-rw-r--r-- | drivers/auxdisplay/Makefile | 2 | ||||
-rw-r--r-- | drivers/auxdisplay/charlcd.c | 412 | ||||
-rw-r--r-- | drivers/auxdisplay/charlcd.h | 86 | ||||
-rw-r--r-- | drivers/auxdisplay/hd44780.c | 120 | ||||
-rw-r--r-- | drivers/auxdisplay/hd44780_common.c | 361 | ||||
-rw-r--r-- | drivers/auxdisplay/hd44780_common.h | 33 | ||||
-rw-r--r-- | drivers/auxdisplay/lcd2s.c | 402 | ||||
-rw-r--r-- | drivers/auxdisplay/panel.c | 173 |
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; } |