A bigger set of changes than usual for auxdisplay:

- 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)
 -----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCgAdFiEEPjU5OPd5QIZ9jqqOGXyLc2htIW0FAl/VkIYACgkQGXyLc2ht
 IW264w/7BcxSja4zpH4ij70Gn40K62xIb0ZXoSrLeh71cmMcWkMdzHgMbeGKYcLC
 xX3pUazq7g2gEbSEnlBZcMpO8SnT0ZtLTgR8pMP3tTP9HzL7lQMK6z8khm1uYCam
 mSvQy1UzX/tzIVggagKwiN+l4pZyfuLu9z+klI5rqlhD9LTC1qZ6O8kpMx5s6ykJ
 WV2zcsbocSZ4kI5tomO3/jj8NsramjWkaQ24ikCpkOKYhs+nO7YdP1KMD2oFqYJQ
 1f9Z58lQSsTDYVoIr5PUdUqNUwjo+gjof4pdcKcSReaAfJnMSIoX8ZSCSFD0mXR4
 0rKn9hrQGQmECHMGlDWKPCeyXBmkyG8EqnicZje+hLDjI+H/+yLz2CFWcnMXST+X
 DPiF4C7q84AzGLOmtcDKfjn0/VjoSN3tXQx7MJA2qtvAmvr7H40M9kq/L/4yZ6lJ
 go8NQ6tLX4ITK3sslBgyjPOhSoRWYe8UvRdYTkFK7ajeqUvWkdp7Rp3yFI8KceBy
 4bKXuquWXQNxDDQb8U/zAf7w44oUK8mk/dKZNZyZanfTiuhCPEHQ6oEAZdifLiUq
 Bn+svl8tMvZh9V+I/d5Slx+7DANmc8f3JNbiYFpXeAQ01708oPKgj1/24pS2rxSG
 EH9TpoWra2rd3XJ7PyXvr14Dz/Nn+BeY53lFdZBDpIGmsH3lkeY=
 =Hc52
 -----END PGP SIGNATURE-----

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
  ...
This commit is contained in:
Linus Torvalds 2020-12-14 11:25:18 -08:00
commit bcc68bd816
11 changed files with 1218 additions and 464 deletions

View File

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

View 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,.*":

View 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

View 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

View 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>
@ -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 (lcd->addr.x >= lcd->width)
return;
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++;
if (lcd->char_conv)
c = lcd->char_conv[(unsigned char)c];
/* prevents the cursor from wrapping onto the next line */
if (priv->addr.x == lcd->bwidth)
charlcd_gotoxy(lcd);
}
if (!lcd->ops->print(lcd, c))
lcd->addr.x++;
/* 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);
}
static void charlcd_clear_fast(struct charlcd *lcd)
{
int pos;
charlcd_home(lcd);
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, ' ');
charlcd_home(lcd);
}
/* 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;

View 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);

View 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);
device_property_read_u32(dev, "internal-buffer-width", &hdc->bwidth);
lcd->ifwidth = ifwidth;
lcd->ops = ifwidth == 8 ? &hd44780_ops_gpio8 : &hd44780_ops_gpio4;
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;
}

View File

@ -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");

View File

@ -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);

402
drivers/auxdisplay/lcd2s.c Normal file
View File

@ -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");

View 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;
}