[13/13] auxdisplay: Add HD44780 Character LCD support
diff mbox series

Message ID 1486391895-9554-14-git-send-email-geert@linux-m68k.org
State New, archived
Headers show
Series
  • Add HD44780 Character LCD support
Related show

Commit Message

Geert Uytterhoeven Feb. 6, 2017, 2:38 p.m. UTC
The Hitachi HD44780 Character LCD Controller is commonly used on
character LCDs that can display one or more lines of text.

This driver supports character LCDs connected to GPIOs, using either a
4-bit or 8-bit data bus, and provides access through the charlcd core
and /dev/lcd.

Signed-off-by: Geert Uytterhoeven <geert@linux-m68k.org>
Cc: Rob Herring <robh+dt@kernel.org>
Cc: Mark Rutland <mark.rutland@arm.com>
Cc: devicetree@vger.kernel.org
---
 drivers/auxdisplay/Kconfig   |  11 ++
 drivers/auxdisplay/Makefile  |   1 +
 drivers/auxdisplay/hd44780.c | 326 +++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 338 insertions(+)
 create mode 100644 drivers/auxdisplay/hd44780.c

Comments

Andy Shevchenko Feb. 7, 2017, 11:23 a.m. UTC | #1
On Mon, Feb 6, 2017 at 4:38 PM, Geert Uytterhoeven <geert@linux-m68k.org> wrote:
> The Hitachi HD44780 Character LCD Controller is commonly used on
> character LCDs that can display one or more lines of text.
>
> This driver supports character LCDs connected to GPIOs, using either a
> 4-bit or 8-bit data bus, and provides access through the charlcd core
> and /dev/lcd.

Can we not limit this to OF? I can easily imagine use case with
x86/ACPI enabled platform.

On the first glance of_property-* -> device_property_* is enough.
Geert Uytterhoeven Feb. 8, 2017, 1:18 p.m. UTC | #2
Hi Andy,

On Tue, Feb 7, 2017 at 12:23 PM, Andy Shevchenko
<andy.shevchenko@gmail.com> wrote:
> On Mon, Feb 6, 2017 at 4:38 PM, Geert Uytterhoeven <geert@linux-m68k.org> wrote:
>> The Hitachi HD44780 Character LCD Controller is commonly used on
>> character LCDs that can display one or more lines of text.
>>
>> This driver supports character LCDs connected to GPIOs, using either a
>> 4-bit or 8-bit data bus, and provides access through the charlcd core
>> and /dev/lcd.
>
> Can we not limit this to OF? I can easily imagine use case with
> x86/ACPI enabled platform.

Sure!

> On the first glance of_property-* -> device_property_* is enough.

And of_gpio_named_count() -> gpiod_count().

Gr{oetje,eeting}s,

                        Geert

--
Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- geert@linux-m68k.org

In personal conversations with technical people, I call myself a hacker. But
when I'm talking to journalists I just say "programmer" or something like that.
                                -- Linus Torvalds
Andy Shevchenko Feb. 8, 2017, 1:38 p.m. UTC | #3
On Wed, Feb 8, 2017 at 3:18 PM, Geert Uytterhoeven <geert@linux-m68k.org> wrote:
> Hi Andy,
>
> On Tue, Feb 7, 2017 at 12:23 PM, Andy Shevchenko
> <andy.shevchenko@gmail.com> wrote:
>> On Mon, Feb 6, 2017 at 4:38 PM, Geert Uytterhoeven <geert@linux-m68k.org> wrote:
>>> The Hitachi HD44780 Character LCD Controller is commonly used on
>>> character LCDs that can display one or more lines of text.
>>>
>>> This driver supports character LCDs connected to GPIOs, using either a
>>> 4-bit or 8-bit data bus, and provides access through the charlcd core
>>> and /dev/lcd.
>>
>> Can we not limit this to OF? I can easily imagine use case with
>> x86/ACPI enabled platform.
>
> Sure!
>
>> On the first glance of_property-* -> device_property_* is enough.
>
> And of_gpio_named_count() -> gpiod_count().

Indeed.

Btw, have you any plans to make an order among spreaded LCD drivers in
kernel? Perhaps we may gather them under auxdisplay hood?
Geert Uytterhoeven Feb. 8, 2017, 1:41 p.m. UTC | #4
Hi Andy,

On Wed, Feb 8, 2017 at 2:38 PM, Andy Shevchenko
<andy.shevchenko@gmail.com> wrote:
> Btw, have you any plans to make an order among spreaded LCD drivers in
> kernel? Perhaps we may gather them under auxdisplay hood?

I have no such plan. I just had a long-standing personal interest in hd44780.

Gr{oetje,eeting}s,

                        Geert

--
Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- geert@linux-m68k.org

In personal conversations with technical people, I call myself a hacker. But
when I'm talking to journalists I just say "programmer" or something like that.
                                -- Linus Torvalds
Andy Shevchenko Feb. 8, 2017, 4:27 p.m. UTC | #5
On Wed, Feb 8, 2017 at 3:41 PM, Geert Uytterhoeven <geert@linux-m68k.org> wrote:
> On Wed, Feb 8, 2017 at 2:38 PM, Andy Shevchenko
> <andy.shevchenko@gmail.com> wrote:
>> Btw, have you any plans to make an order among spreaded LCD drivers in
>> kernel? Perhaps we may gather them under auxdisplay hood?
>
> I have no such plan. I just had a long-standing personal interest in hd44780.

I have one too regarding to I2C/SPI interfaced hardware.
If you remember it was an attempt for lcd-linux to be upstreamed,
which provides (besides i2c/spi bus drivers) those LCDs as tty
devices.
Also interesting feature to have.
Geert Uytterhoeven Feb. 8, 2017, 5:19 p.m. UTC | #6
Hi Andy,

On Wed, Feb 8, 2017 at 5:27 PM, Andy Shevchenko
<andy.shevchenko@gmail.com> wrote:
> On Wed, Feb 8, 2017 at 3:41 PM, Geert Uytterhoeven <geert@linux-m68k.org> wrote:
>> On Wed, Feb 8, 2017 at 2:38 PM, Andy Shevchenko
>> <andy.shevchenko@gmail.com> wrote:
>>> Btw, have you any plans to make an order among spreaded LCD drivers in
>>> kernel? Perhaps we may gather them under auxdisplay hood?
>>
>> I have no such plan. I just had a long-standing personal interest in hd44780.
>
> I have one too regarding to I2C/SPI interfaced hardware.
> If you remember it was an attempt for lcd-linux to be upstreamed,
> which provides (besides i2c/spi bus drivers) those LCDs as tty
> devices.
> Also interesting feature to have.

I'm actually driving mine from SPI, using a 74HCT595 shift register and
the 74x164 GPIO driver.  Should work with i2c and a PCF8574 GPIO
expander too, except that my SoC has 3.3V I/Os, while my display
needs 5V, which is taken care of by the HCT shift register.

Gr{oetje,eeting}s,

                        Geert

--
Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- geert@linux-m68k.org

In personal conversations with technical people, I call myself a hacker. But
when I'm talking to journalists I just say "programmer" or something like that.
                                -- Linus Torvalds

Patch
diff mbox series

diff --git a/drivers/auxdisplay/Kconfig b/drivers/auxdisplay/Kconfig
index b4686380d4e3cca9..2964128b6046882d 100644
--- a/drivers/auxdisplay/Kconfig
+++ b/drivers/auxdisplay/Kconfig
@@ -18,6 +18,17 @@  config CHARLCD
 
 if AUXDISPLAY
 
+config HD44780
+	tristate "HD44780 Character LCD support"
+	depends on OF
+	select CHARLCD
+	---help---
+	  Enable support for Character LCDs using a HD44780 controller.
+	  The LCD is accessible through the /dev/lcd char device (10, 156).
+	  This code can either be compiled as a module, or linked into the
+	  kernel and started at boot.
+	  If you don't understand what all this is about, say N.
+
 config KS0108
 	tristate "KS0108 LCD Controller"
 	depends on PARPORT_PC
diff --git a/drivers/auxdisplay/Makefile b/drivers/auxdisplay/Makefile
index f56f45dcc78e332e..fcefa686be0fea9b 100644
--- a/drivers/auxdisplay/Makefile
+++ b/drivers/auxdisplay/Makefile
@@ -6,4 +6,5 @@  obj-$(CONFIG_CHARLCD)		+= charlcd.o
 obj-$(CONFIG_KS0108)		+= ks0108.o
 obj-$(CONFIG_CFAG12864B)	+= cfag12864b.o cfag12864bfb.o
 obj-$(CONFIG_IMG_ASCII_LCD)	+= img-ascii-lcd.o
+obj-$(CONFIG_HD44780)		+= hd44780.o
 obj-$(CONFIG_HT16K33)		+= ht16k33.o
diff --git a/drivers/auxdisplay/hd44780.c b/drivers/auxdisplay/hd44780.c
new file mode 100644
index 0000000000000000..36aa5942d9374f88
--- /dev/null
+++ b/drivers/auxdisplay/hd44780.c
@@ -0,0 +1,326 @@ 
+/*
+ * HD44780 Character LCD driver for Linux
+ *
+ * Copyright (C) 2000-2008, Willy Tarreau <w@1wt.eu>
+ * Copyright (C) 2016-2017 Glider bvba
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#include <misc/charlcd.h>
+
+
+enum hd44780_pin {
+	/* Order does matter due to writing to GPIO array subsets! */
+	PIN_DATA0,	/* Optional */
+	PIN_DATA1,	/* Optional */
+	PIN_DATA2,	/* Optional */
+	PIN_DATA3,	/* Optional */
+	PIN_DATA4,
+	PIN_DATA5,
+	PIN_DATA6,
+	PIN_DATA7,
+	PIN_CTRL_RS,
+	PIN_CTRL_RW,	/* Optional */
+	PIN_CTRL_E,
+	PIN_CTRL_BL,   /* Optional */
+	PIN_NUM
+};
+
+struct hd44780 {
+	struct gpio_desc *pins[PIN_NUM];
+};
+
+static void hd44780_backlight(struct charlcd *lcd, int on)
+{
+	struct hd44780 *hd = lcd->drvdata;
+
+	if (hd->pins[PIN_CTRL_BL])
+		gpiod_set_value_cansleep(hd->pins[PIN_CTRL_BL], on);
+}
+
+static void hd44780_strobe_gpio(struct hd44780 *hd)
+{
+	/* Maintain the data during 20 us before the strobe */
+	udelay(20);
+
+	gpiod_set_value_cansleep(hd->pins[PIN_CTRL_E], 1);
+
+	/* Maintain the strobe during 40 us */
+	udelay(40);
+
+	gpiod_set_value_cansleep(hd->pins[PIN_CTRL_E], 0);
+}
+
+/* write to an LCD panel register in 8 bit GPIO mode */
+static void hd44780_write_gpio8(struct hd44780 *hd, u8 val, unsigned int rs)
+{
+	int values[10];	/* for DATA[0-7], RS, RW */
+	unsigned int i, n;
+
+	for (i = 0; i < 8; i++)
+		values[PIN_DATA0 + i] = !!(val & BIT(i));
+	values[PIN_CTRL_RS] = rs;
+	n = 9;
+	if (hd->pins[PIN_CTRL_RW]) {
+		values[PIN_CTRL_RW] = 0;
+		n++;
+	}
+
+	/* Present the data to the port */
+	gpiod_set_array_value_cansleep(n, &hd->pins[PIN_DATA0], values);
+
+	hd44780_strobe_gpio(hd);
+}
+
+/* write to an LCD panel register in 4 bit GPIO mode */
+static void hd44780_write_gpio4(struct hd44780 *hd, u8 val, unsigned int rs)
+{
+	int values[10];	/* for DATA[0-7], RS, RW, but DATA[0-3] is unused */
+	unsigned int i, n;
+
+	/* High nibble + RS, RW */
+	for (i = 4; i < 8; i++)
+		values[PIN_DATA0 + i] = !!(val & BIT(i));
+	values[PIN_CTRL_RS] = rs;
+	n = 5;
+	if (hd->pins[PIN_CTRL_RW]) {
+		values[PIN_CTRL_RW] = 0;
+		n++;
+	}
+
+	/* Present the data to the port */
+	gpiod_set_array_value_cansleep(n, &hd->pins[PIN_DATA4],
+				       &values[PIN_DATA4]);
+
+	hd44780_strobe_gpio(hd);
+
+	/* Low nibble */
+	for (i = 0; i < 4; i++)
+		values[PIN_DATA4 + i] = !!(val & BIT(i));
+
+	/* Present the data to the port */
+	gpiod_set_array_value_cansleep(n, &hd->pins[PIN_DATA4],
+				       &values[PIN_DATA4]);
+
+	hd44780_strobe_gpio(hd);
+}
+
+/* Send a command to the LCD panel in 8 bit GPIO mode */
+static void hd44780_write_cmd_gpio8(struct charlcd *lcd, int cmd)
+{
+	struct hd44780 *hd = lcd->drvdata;
+
+	hd44780_write_gpio8(hd, cmd, 0);
+
+	/* The shortest command takes at least 120 us */
+	udelay(120);
+}
+
+/* Send data to the LCD panel in 8 bit GPIO mode */
+static void hd44780_write_data_gpio8(struct charlcd *lcd, int data)
+{
+	struct hd44780 *hd = lcd->drvdata;
+
+	hd44780_write_gpio8(hd, data, 1);
+
+	/* The shortest data takes at least 45 us */
+	udelay(45);
+}
+
+static const struct charlcd_ops hd44780_ops_gpio8 = {
+	.write_cmd	= hd44780_write_cmd_gpio8,
+	.write_data	= hd44780_write_data_gpio8,
+	.backlight	= hd44780_backlight,
+};
+
+/* Send a command to the LCD panel in 4 bit GPIO mode */
+static void hd44780_write_cmd_gpio4(struct charlcd *lcd, int cmd)
+{
+	struct hd44780 *hd = lcd->drvdata;
+
+	hd44780_write_gpio4(hd, cmd, 0);
+
+	/* The shortest command takes at least 120 us */
+	udelay(120);
+}
+
+/* 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)
+{
+	int values[10];	/* for DATA[0-7], RS, RW, but DATA[0-3] is unused */
+	struct hd44780 *hd = lcd->drvdata;
+	unsigned int i, n;
+
+	/* Command nibble + RS, RW */
+	for (i = 0; i < 4; i++)
+		values[PIN_DATA4 + i] = !!(cmd & BIT(i));
+	values[PIN_CTRL_RS] = 0;
+	n = 5;
+	if (hd->pins[PIN_CTRL_RW]) {
+		values[PIN_CTRL_RW] = 0;
+		n++;
+	}
+
+	/* Present the data to the port */
+	gpiod_set_array_value_cansleep(n, &hd->pins[PIN_DATA4],
+				       &values[PIN_DATA4]);
+
+	hd44780_strobe_gpio(hd);
+}
+
+/* Send data to the LCD panel in 4 bit GPIO mode */
+static void hd44780_write_data_gpio4(struct charlcd *lcd, int data)
+{
+	struct hd44780 *hd = lcd->drvdata;
+
+	hd44780_write_gpio4(hd, data, 1);
+
+	/* The shortest data takes at least 45 us */
+	udelay(45);
+}
+
+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,
+};
+
+static int hd44780_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct device_node *np = dev->of_node;
+	unsigned int i, base;
+	struct charlcd *lcd;
+	struct hd44780 *hd;
+	int ifwidth, ret;
+
+	/* Required pins */
+	ifwidth = of_gpio_named_count(np, "data-gpios");
+	if (ifwidth < 0)
+		return ifwidth;
+
+	switch (ifwidth) {
+	case 4:
+		base = PIN_DATA4;
+		break;
+	case 8:
+		base = PIN_DATA0;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	lcd = charlcd_alloc(sizeof(struct hd44780));
+	if (!lcd)
+		return -ENOMEM;
+
+	hd = lcd->drvdata;
+
+	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;
+		}
+	}
+
+	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;
+	}
+
+	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;
+	}
+
+	/* Optional pins */
+	hd->pins[PIN_CTRL_RW] = devm_gpiod_get_optional(dev, "rw",
+							GPIOD_OUT_LOW);
+	if (IS_ERR(hd->pins[PIN_CTRL_RW])) {
+		ret = PTR_ERR(hd->pins[PIN_CTRL_RW]);
+		goto fail;
+	}
+
+	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;
+	}
+
+	/* Required properties */
+	ret = of_property_read_u32(np, "display-height", &lcd->height);
+	if (ret)
+		goto fail;
+	ret = of_property_read_u32(np, "display-width", &lcd->width);
+	if (ret)
+		goto fail;
+
+	/*
+	 * 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;
+
+	/* Optional properties */
+	of_property_read_u32(np, "internal-buffer-width", &lcd->bwidth);
+
+	lcd->ifwidth = ifwidth;
+	lcd->ops = ifwidth == 8 ? &hd44780_ops_gpio8 : &hd44780_ops_gpio4;
+
+	ret = charlcd_register(lcd);
+	if (ret)
+		goto fail;
+
+	platform_set_drvdata(pdev, lcd);
+	return 0;
+
+fail:
+	kfree(lcd);
+	return ret;
+}
+
+static int hd44780_remove(struct platform_device *pdev)
+{
+	struct charlcd *lcd = platform_get_drvdata(pdev);
+
+	charlcd_unregister(lcd);
+	return 0;
+}
+
+static const struct of_device_id hd44780_of_match[] = {
+	{ .compatible = "hit,hd44780" },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, hd44780_of_match);
+
+static struct platform_driver hd44780_driver = {
+	.probe = hd44780_probe,
+	.remove = hd44780_remove,
+	.driver		= {
+		.name	= "hd44780",
+		.of_match_table = hd44780_of_match,
+	},
+};
+
+module_platform_driver(hd44780_driver);
+MODULE_DESCRIPTION("HD44780 Character LCD driver");
+MODULE_AUTHOR("Geert Uytterhoeven <geert@linux-m68k.org>");
+MODULE_LICENSE("GPL");