/* * HD44780 Character LCD driver for Linux * * Copyright (C) 2000-2008, Willy Tarreau * 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 #include #include #include #include #include #include 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; unsigned int i, base; struct charlcd *lcd; struct hd44780 *hd; int ifwidth, ret; /* Required pins */ ifwidth = gpiod_count(dev, "data"); 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 = device_property_read_u32(dev, "display-height-chars", &lcd->height); if (ret) goto fail; ret = device_property_read_u32(dev, "display-width-chars", &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 */ device_property_read_u32(dev, "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 "); MODULE_LICENSE("GPL");