diff options
Diffstat (limited to 'drivers/auxdisplay')
-rw-r--r-- | drivers/auxdisplay/Kconfig | 304 | ||||
-rw-r--r-- | drivers/auxdisplay/Makefile | 4 | ||||
-rw-r--r-- | drivers/auxdisplay/arm-charlcd.c | 368 | ||||
-rw-r--r-- | drivers/auxdisplay/charlcd.c | 818 | ||||
-rw-r--r-- | drivers/auxdisplay/hd44780.c | 326 | ||||
-rw-r--r-- | drivers/auxdisplay/ht16k33.c | 20 | ||||
-rw-r--r-- | drivers/auxdisplay/img-ascii-lcd.c | 1 | ||||
-rw-r--r-- | drivers/auxdisplay/panel.c | 1796 |
8 files changed, 3632 insertions, 5 deletions
diff --git a/drivers/auxdisplay/Kconfig b/drivers/auxdisplay/Kconfig index 8a8e403644d6..9ae6681c90ad 100644 --- a/drivers/auxdisplay/Kconfig +++ b/drivers/auxdisplay/Kconfig @@ -13,8 +13,22 @@ menuconfig AUXDISPLAY If you say N, all options in this submenu will be skipped and disabled. +config CHARLCD + tristate "Character LCD core support" if COMPILE_TEST + if AUXDISPLAY +config HD44780 + tristate "HD44780 Character LCD support" + depends on GPIOLIB || COMPILE_TEST + 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 @@ -142,3 +156,293 @@ config HT16K33 LED controller driver with keyscan. endif # AUXDISPLAY + +config ARM_CHARLCD + bool "ARM Ltd. Character LCD Driver" + depends on PLAT_VERSATILE + help + This is a driver for the character LCD found on the ARM Ltd. + Versatile and RealView Platform Baseboards. It doesn't do + very much more than display the text "ARM Linux" on the first + line and the Linux version on the second line, but that's + still useful. + +config PANEL + tristate "Parallel port LCD/Keypad Panel support" + depends on PARPORT + select CHARLCD + ---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 + is accessible through the /dev/lcd char device (10, 156), and the + keypad through /dev/keypad (10, 185). 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. + +if PANEL + +config PANEL_PARPORT + int "Default parallel port number (0=LPT1)" + range 0 255 + default "0" + ---help--- + This is the index of the parallel port the panel is connected to. One + driver instance only supports one parallel port, so if your keypad + and LCD are connected to two separate ports, you have to start two + modules with different arguments. Numbering starts with '0' for LPT1, + and so on. + +config PANEL_PROFILE + int "Default panel profile (0-5, 0=custom)" + range 0 5 + default "5" + ---help--- + To ease configuration, the driver supports different configuration + profiles for past and recent wirings. These profiles can also be + used to define an approximative configuration, completed by a few + other options. Here are the profiles : + + 0 = custom (see further) + 1 = 2x16 parallel LCD, old keypad + 2 = 2x16 serial LCD (KS-0074), new keypad + 3 = 2x16 parallel LCD (Hantronix), no keypad + 4 = 2x16 parallel LCD (Nexcom NSA1045) with Nexcom's keypad + 5 = 2x40 parallel LCD (old one), with old keypad + + Custom configurations allow you to define how your display is + wired to the parallel port, and how it works. This is only intended + for experts. + +config PANEL_KEYPAD + depends on PANEL_PROFILE="0" + int "Keypad type (0=none, 1=old 6 keys, 2=new 6 keys, 3=Nexcom 4 keys)" + range 0 3 + default 0 + ---help--- + This enables and configures a keypad connected to the parallel port. + The keys will be read from character device 10,185. Valid values are : + + 0 : do not enable this driver + 1 : old 6 keys keypad + 2 : new 6 keys keypad, as used on the server at www.ant-computing.com + 3 : Nexcom NSA1045's 4 keys keypad + + New profiles can be described in the driver source. The driver also + supports simultaneous keys pressed when the keypad supports them. + +config PANEL_LCD + depends on PANEL_PROFILE="0" + int "LCD type (0=none, 1=custom, 2=old //, 3=ks0074, 4=hantronix, 5=Nexcom)" + range 0 5 + default 0 + ---help--- + This enables and configures an LCD connected to the parallel port. + The driver includes an interpreter for escape codes starting with + '\e[L' which are specific to the LCD, and a few ANSI codes. The + driver will be registered as character device 10,156, usually + under the name '/dev/lcd'. There are a total of 6 supported types : + + 0 : do not enable the driver + 1 : custom configuration and wiring (see further) + 2 : 2x16 & 2x40 parallel LCD (old wiring) + 3 : 2x16 serial LCD (KS-0074 based) + 4 : 2x16 parallel LCD (Hantronix wiring) + 5 : 2x16 parallel LCD (Nexcom wiring) + + When type '1' is specified, other options will appear to configure + more precise aspects (wiring, dimensions, protocol, ...). Please note + that those values changed from the 2.4 driver for better consistency. + +config PANEL_LCD_HEIGHT + depends on PANEL_PROFILE="0" && PANEL_LCD="1" + int "Number of lines on the LCD (1-2)" + range 1 2 + default 2 + ---help--- + This is the number of visible character lines on the LCD in custom profile. + It can either be 1 or 2. + +config PANEL_LCD_WIDTH + depends on PANEL_PROFILE="0" && PANEL_LCD="1" + int "Number of characters per line on the LCD (1-40)" + range 1 40 + default 40 + ---help--- + This is the number of characters per line on the LCD in custom profile. + Common values are 16,20,24,40. + +config PANEL_LCD_BWIDTH + depends on PANEL_PROFILE="0" && PANEL_LCD="1" + int "Internal LCD line width (1-40, 40 by default)" + range 1 40 + default 40 + ---help--- + Most LCDs use a standard controller which supports hardware lines of 40 + characters, although sometimes only 16, 20 or 24 of them are really wired + to the terminal. This results in some non-visible but addressable characters, + and is the case for most parallel LCDs. Other LCDs, and some serial ones, + however, use the same line width internally as what is visible. The KS0074 + for example, uses 16 characters per line for 16 visible characters per line. + + This option lets you configure the value used by your LCD in 'custom' profile. + If you don't know, put '40' here. + +config PANEL_LCD_HWIDTH + depends on PANEL_PROFILE="0" && PANEL_LCD="1" + int "Hardware LCD line width (1-64, 64 by default)" + range 1 64 + default 64 + ---help--- + Most LCDs use a single address bit to differentiate line 0 and line 1. Since + some of them need to be able to address 40 chars with the lower bits, they + often use the immediately superior power of 2, which is 64, to address the + next line. + + If you don't know what your LCD uses, in doubt let 16 here for a 2x16, and + 64 here for a 2x40. + +config PANEL_LCD_CHARSET + depends on PANEL_PROFILE="0" && PANEL_LCD="1" + int "LCD character set (0=normal, 1=KS0074)" + range 0 1 + default 0 + ---help--- + Some controllers such as the KS0074 use a somewhat strange character set + where many symbols are at unusual places. The driver knows how to map + 'standard' ASCII characters to the character sets used by these controllers. + Valid values are : + + 0 : normal (untranslated) character set + 1 : KS0074 character set + + If you don't know, use the normal one (0). + +config PANEL_LCD_PROTO + depends on PANEL_PROFILE="0" && PANEL_LCD="1" + int "LCD communication mode (0=parallel 8 bits, 1=serial)" + range 0 1 + default 0 + ---help--- + This driver now supports any serial or parallel LCD wired to a parallel + port. But before assigning signals, the driver needs to know if it will + be driving a serial LCD or a parallel one. Serial LCDs only use 2 wires + (SDA/SCL), while parallel ones use 2 or 3 wires for the control signals + (E, RS, sometimes RW), and 4 or 8 for the data. Use 0 here for a 8 bits + parallel LCD, and 1 for a serial LCD. + +config PANEL_LCD_PIN_E + depends on PANEL_PROFILE="0" && PANEL_LCD="1" && PANEL_LCD_PROTO="0" + int "Parallel port pin number & polarity connected to the LCD E signal (-17...17) " + range -17 17 + default 14 + ---help--- + This describes the number of the parallel port pin to which the LCD 'E' + signal has been connected. It can be : + + 0 : no connection (eg: connected to ground) + 1..17 : directly connected to any of these pins on the DB25 plug + -1..-17 : connected to the same pin through an inverter (eg: transistor). + + Default for the 'E' pin in custom profile is '14' (AUTOFEED). + +config PANEL_LCD_PIN_RS + depends on PANEL_PROFILE="0" && PANEL_LCD="1" && PANEL_LCD_PROTO="0" + int "Parallel port pin number & polarity connected to the LCD RS signal (-17...17) " + range -17 17 + default 17 + ---help--- + This describes the number of the parallel port pin to which the LCD 'RS' + signal has been connected. It can be : + + 0 : no connection (eg: connected to ground) + 1..17 : directly connected to any of these pins on the DB25 plug + -1..-17 : connected to the same pin through an inverter (eg: transistor). + + Default for the 'RS' pin in custom profile is '17' (SELECT IN). + +config PANEL_LCD_PIN_RW + depends on PANEL_PROFILE="0" && PANEL_LCD="1" && PANEL_LCD_PROTO="0" + int "Parallel port pin number & polarity connected to the LCD RW signal (-17...17) " + range -17 17 + default 16 + ---help--- + This describes the number of the parallel port pin to which the LCD 'RW' + signal has been connected. It can be : + + 0 : no connection (eg: connected to ground) + 1..17 : directly connected to any of these pins on the DB25 plug + -1..-17 : connected to the same pin through an inverter (eg: transistor). + + Default for the 'RW' pin in custom profile is '16' (INIT). + +config PANEL_LCD_PIN_SCL + depends on PANEL_PROFILE="0" && PANEL_LCD="1" && PANEL_LCD_PROTO!="0" + int "Parallel port pin number & polarity connected to the LCD SCL signal (-17...17) " + range -17 17 + default 1 + ---help--- + This describes the number of the parallel port pin to which the serial + LCD 'SCL' signal has been connected. It can be : + + 0 : no connection (eg: connected to ground) + 1..17 : directly connected to any of these pins on the DB25 plug + -1..-17 : connected to the same pin through an inverter (eg: transistor). + + Default for the 'SCL' pin in custom profile is '1' (STROBE). + +config PANEL_LCD_PIN_SDA + depends on PANEL_PROFILE="0" && PANEL_LCD="1" && PANEL_LCD_PROTO!="0" + int "Parallel port pin number & polarity connected to the LCD SDA signal (-17...17) " + range -17 17 + default 2 + ---help--- + This describes the number of the parallel port pin to which the serial + LCD 'SDA' signal has been connected. It can be : + + 0 : no connection (eg: connected to ground) + 1..17 : directly connected to any of these pins on the DB25 plug + -1..-17 : connected to the same pin through an inverter (eg: transistor). + + Default for the 'SDA' pin in custom profile is '2' (D0). + +config PANEL_LCD_PIN_BL + depends on PANEL_PROFILE="0" && PANEL_LCD="1" + int "Parallel port pin number & polarity connected to the LCD backlight signal (-17...17) " + range -17 17 + default 0 + ---help--- + This describes the number of the parallel port pin to which the LCD 'BL' signal + has been connected. It can be : + + 0 : no connection (eg: connected to ground) + 1..17 : directly connected to any of these pins on the DB25 plug + -1..-17 : connected to the same pin through an inverter (eg: transistor). + + Default for the 'BL' pin in custom profile is '0' (uncontrolled). + +config PANEL_CHANGE_MESSAGE + bool "Change LCD initialization message ?" + default "n" + ---help--- + This allows you to replace the boot message indicating the kernel version + and the driver version with a custom message. This is useful on appliances + where a simple 'Starting system' message can be enough to stop a customer + from worrying. + + If you say 'Y' here, you'll be able to choose a message yourself. Otherwise, + say 'N' and keep the default message with the version. + +config PANEL_BOOT_MESSAGE + depends on PANEL_CHANGE_MESSAGE="y" + string "New initialization message" + default "" + ---help--- + This allows you to replace the boot message indicating the kernel version + and the driver version with a custom message. This is useful on appliances + where a simple 'Starting system' message can be enough to stop a customer + from worrying. + + An empty message will only clear the display at driver init time. Any other + printf()-formatted message is valid with newline and escape codes. + +endif # PANEL diff --git a/drivers/auxdisplay/Makefile b/drivers/auxdisplay/Makefile index cb3dd847713b..2b8af3dc5e42 100644 --- a/drivers/auxdisplay/Makefile +++ b/drivers/auxdisplay/Makefile @@ -2,7 +2,11 @@ # Makefile for the kernel auxiliary displays device drivers. # +obj-$(CONFIG_CHARLCD) += charlcd.o +obj-$(CONFIG_ARM_CHARLCD) += arm-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 +obj-$(CONFIG_PANEL) += panel.o diff --git a/drivers/auxdisplay/arm-charlcd.c b/drivers/auxdisplay/arm-charlcd.c new file mode 100644 index 000000000000..b3176ee92b90 --- /dev/null +++ b/drivers/auxdisplay/arm-charlcd.c @@ -0,0 +1,368 @@ +/* + * Driver for the on-board character LCD found on some ARM reference boards + * This is basically an Hitachi HD44780 LCD with a custom IP block to drive it + * http://en.wikipedia.org/wiki/HD44780_Character_LCD + * Currently it will just display the text "ARM Linux" and the linux version + * + * License terms: GNU General Public License (GPL) version 2 + * Author: Linus Walleij <triad@df.lth.se> + */ +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/of.h> +#include <linux/completion.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/slab.h> +#include <linux/workqueue.h> +#include <generated/utsrelease.h> + +#define DRIVERNAME "arm-charlcd" +#define CHARLCD_TIMEOUT (msecs_to_jiffies(1000)) + +/* Offsets to registers */ +#define CHAR_COM 0x00U +#define CHAR_DAT 0x04U +#define CHAR_RD 0x08U +#define CHAR_RAW 0x0CU +#define CHAR_MASK 0x10U +#define CHAR_STAT 0x14U + +#define CHAR_RAW_CLEAR 0x00000000U +#define CHAR_RAW_VALID 0x00000100U + +/* Hitachi HD44780 display commands */ +#define HD_CLEAR 0x01U +#define HD_HOME 0x02U +#define HD_ENTRYMODE 0x04U +#define HD_ENTRYMODE_INCREMENT 0x02U +#define HD_ENTRYMODE_SHIFT 0x01U +#define HD_DISPCTRL 0x08U +#define HD_DISPCTRL_ON 0x04U +#define HD_DISPCTRL_CURSOR_ON 0x02U +#define HD_DISPCTRL_CURSOR_BLINK 0x01U +#define HD_CRSR_SHIFT 0x10U +#define HD_CRSR_SHIFT_DISPLAY 0x08U +#define HD_CRSR_SHIFT_DISPLAY_RIGHT 0x04U +#define HD_FUNCSET 0x20U +#define HD_FUNCSET_8BIT 0x10U +#define HD_FUNCSET_2_LINES 0x08U +#define HD_FUNCSET_FONT_5X10 0x04U +#define HD_SET_CGRAM 0x40U +#define HD_SET_DDRAM 0x80U +#define HD_BUSY_FLAG 0x80U + +/** + * @dev: a pointer back to containing device + * @phybase: the offset to the controller in physical memory + * @physize: the size of the physical page + * @virtbase: the offset to the controller in virtual memory + * @irq: reserved interrupt number + * @complete: completion structure for the last LCD command + */ +struct charlcd { + struct device *dev; + u32 phybase; + u32 physize; + void __iomem *virtbase; + int irq; + struct completion complete; + struct delayed_work init_work; +}; + +static irqreturn_t charlcd_interrupt(int irq, void *data) +{ + struct charlcd *lcd = data; + u8 status; + + status = readl(lcd->virtbase + CHAR_STAT) & 0x01; + /* Clear IRQ */ + writel(CHAR_RAW_CLEAR, lcd->virtbase + CHAR_RAW); + if (status) + complete(&lcd->complete); + else + dev_info(lcd->dev, "Spurious IRQ (%02x)\n", status); + return IRQ_HANDLED; +} + + +static void charlcd_wait_complete_irq(struct charlcd *lcd) +{ + int ret; + + ret = wait_for_completion_interruptible_timeout(&lcd->complete, + CHARLCD_TIMEOUT); + /* Disable IRQ after completion */ + writel(0x00, lcd->virtbase + CHAR_MASK); + + if (ret < 0) { + dev_err(lcd->dev, + "wait_for_completion_interruptible_timeout() " + "returned %d waiting for ready\n", ret); + return; + } + + if (ret == 0) { + dev_err(lcd->dev, "charlcd controller timed out " + "waiting for ready\n"); + return; + } +} + +static u8 charlcd_4bit_read_char(struct charlcd *lcd) +{ + u8 data; + u32 val; + int i; + + /* If we can, use an IRQ to wait for the data, else poll */ + if (lcd->irq >= 0) + charlcd_wait_complete_irq(lcd); + else { + i = 0; + val = 0; + while (!(val & CHAR_RAW_VALID) && i < 10) { + udelay(100); + val = readl(lcd->virtbase + CHAR_RAW); + i++; + } + + writel(CHAR_RAW_CLEAR, lcd->virtbase + CHAR_RAW); + } + msleep(1); + + /* Read the 4 high bits of the data */ + data = readl(lcd->virtbase + CHAR_RD) & 0xf0; + + /* + * The second read for the low bits does not trigger an IRQ + * so in this case we have to poll for the 4 lower bits + */ + i = 0; + val = 0; + while (!(val & CHAR_RAW_VALID) && i < 10) { + udelay(100); + val = readl(lcd->virtbase + CHAR_RAW); + i++; + } + writel(CHAR_RAW_CLEAR, lcd->virtbase + CHAR_RAW); + msleep(1); + + /* Read the 4 low bits of the data */ + data |= (readl(lcd->virtbase + CHAR_RD) >> 4) & 0x0f; + + return data; +} + +static bool charlcd_4bit_read_bf(struct charlcd *lcd) +{ + if (lcd->irq >= 0) { + /* + * If we'll use IRQs to wait for the busyflag, clear any + * pending flag and enable IRQ + */ + writel(CHAR_RAW_CLEAR, lcd->virtbase + CHAR_RAW); + init_completion(&lcd->complete); + writel(0x01, lcd->virtbase + CHAR_MASK); + } + readl(lcd->virtbase + CHAR_COM); + return charlcd_4bit_read_char(lcd) & HD_BUSY_FLAG ? true : false; +} + +static void charlcd_4bit_wait_busy(struct charlcd *lcd) +{ + int retries = 50; + + udelay(100); + while (charlcd_4bit_read_bf(lcd) && retries) + retries--; + if (!retries) + dev_err(lcd->dev, "timeout waiting for busyflag\n"); +} + +static void charlcd_4bit_command(struct charlcd *lcd, u8 cmd) +{ + u32 cmdlo = (cmd << 4) & 0xf0; + u32 cmdhi = (cmd & 0xf0); + + writel(cmdhi, lcd->virtbase + CHAR_COM); + udelay(10); + writel(cmdlo, lcd->virtbase + CHAR_COM); + charlcd_4bit_wait_busy(lcd); +} + +static void charlcd_4bit_char(struct charlcd *lcd, u8 ch) +{ + u32 chlo = (ch << 4) & 0xf0; + u32 chhi = (ch & 0xf0); + + writel(chhi, lcd->virtbase + CHAR_DAT); + udelay(10); + writel(chlo, lcd->virtbase + CHAR_DAT); + charlcd_4bit_wait_busy(lcd); +} + +static void charlcd_4bit_print(struct charlcd *lcd, int line, const char *str) +{ + u8 offset; + int i; + + /* + * We support line 0, 1 + * Line 1 runs from 0x00..0x27 + * Line 2 runs from 0x28..0x4f + */ + if (line == 0) + offset = 0; + else if (line == 1) + offset = 0x28; + else + return; + + /* Set offset */ + charlcd_4bit_command(lcd, HD_SET_DDRAM | offset); + + /* Send string */ + for (i = 0; i < strlen(str) && i < 0x28; i++) + charlcd_4bit_char(lcd, str[i]); +} + +static void charlcd_4bit_init(struct charlcd *lcd) +{ + /* These commands cannot be checked with the busy flag */ + writel(HD_FUNCSET | HD_FUNCSET_8BIT, lcd->virtbase + CHAR_COM); + msleep(5); + writel(HD_FUNCSET | HD_FUNCSET_8BIT, lcd->virtbase + CHAR_COM); + udelay(100); + writel(HD_FUNCSET | HD_FUNCSET_8BIT, lcd->virtbase + CHAR_COM); + udelay(100); + /* Go to 4bit mode */ + writel(HD_FUNCSET, lcd->virtbase + CHAR_COM); + udelay(100); + /* + * 4bit mode, 2 lines, 5x8 font, after this the number of lines + * and the font cannot be changed until the next initialization sequence + */ + charlcd_4bit_command(lcd, HD_FUNCSET | HD_FUNCSET_2_LINES); + charlcd_4bit_command(lcd, HD_DISPCTRL | HD_DISPCTRL_ON); + charlcd_4bit_command(lcd, HD_ENTRYMODE | HD_ENTRYMODE_INCREMENT); + charlcd_4bit_command(lcd, HD_CLEAR); + charlcd_4bit_command(lcd, HD_HOME); + /* Put something useful in the display */ + charlcd_4bit_print(lcd, 0, "ARM Linux"); + charlcd_4bit_print(lcd, 1, UTS_RELEASE); +} + +static void charlcd_init_work(struct work_struct *work) +{ + struct charlcd *lcd = + container_of(work, struct charlcd, init_work.work); + + charlcd_4bit_init(lcd); +} + +static int __init charlcd_probe(struct platform_device *pdev) +{ + int ret; + struct charlcd *lcd; + struct resource *res; + + lcd = kzalloc(sizeof(struct charlcd), GFP_KERNEL); + if (!lcd) + return -ENOMEM; + + lcd->dev = &pdev->dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + ret = -ENOENT; + goto out_no_resource; + } + lcd->phybase = res->start; + lcd->physize = resource_size(res); + + if (request_mem_region(lcd->phybase, lcd->physize, + DRIVERNAME) == NULL) { + ret = -EBUSY; + goto out_no_memregion; + } + + lcd->virtbase = ioremap(lcd->phybase, lcd->physize); + if (!lcd->virtbase) { + ret = -ENOMEM; + goto out_no_memregion; + } + + lcd->irq = platform_get_irq(pdev, 0); + /* If no IRQ is supplied, we'll survive without it */ + if (lcd->irq >= 0) { + if (request_irq(lcd->irq, charlcd_interrupt, 0, + DRIVERNAME, lcd)) { + ret = -EIO; + goto out_no_irq; + } + } + + platform_set_drvdata(pdev, lcd); + + /* + * Initialize the display in a delayed work, because + * it is VERY slow and would slow down the boot of the system. + */ + INIT_DELAYED_WORK(&lcd->init_work, charlcd_init_work); + schedule_delayed_work(&lcd->init_work, 0); + + dev_info(&pdev->dev, "initialized ARM character LCD at %08x\n", + lcd->phybase); + + return 0; + +out_no_irq: + iounmap(lcd->virtbase); +out_no_memregion: + release_mem_region(lcd->phybase, SZ_4K); +out_no_resource: + kfree(lcd); + return ret; +} + +static int charlcd_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct charlcd *lcd = platform_get_drvdata(pdev); + + /* Power the display off */ + charlcd_4bit_command(lcd, HD_DISPCTRL); + return 0; +} + +static int charlcd_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct charlcd *lcd = platform_get_drvdata(pdev); + + /* Turn the display back on */ + charlcd_4bit_command(lcd, HD_DISPCTRL | HD_DISPCTRL_ON); + return 0; +} + +static const struct dev_pm_ops charlcd_pm_ops = { + .suspend = charlcd_suspend, + .resume = charlcd_resume, +}; + +static const struct of_device_id charlcd_match[] = { + { .compatible = "arm,versatile-lcd", }, + {} +}; + +static struct platform_driver charlcd_driver = { + .driver = { + .name = DRIVERNAME, + .pm = &charlcd_pm_ops, + .suppress_bind_attrs = true, + .of_match_table = of_match_ptr(charlcd_match), + }, +}; +builtin_platform_driver_probe(charlcd_driver, charlcd_probe); diff --git a/drivers/auxdisplay/charlcd.c b/drivers/auxdisplay/charlcd.c new file mode 100644 index 000000000000..cfeb049a01ef --- /dev/null +++ b/drivers/auxdisplay/charlcd.c @@ -0,0 +1,818 @@ +/* + * 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/atomic.h> +#include <linux/delay.h> +#include <linux/fs.h> +#include <linux/miscdevice.h> +#include <linux/module.h> +#include <linux/notifier.h> +#include <linux/reboot.h> +#include <linux/slab.h> +#include <linux/uaccess.h> +#include <linux/workqueue.h> + +#include <generated/utsrelease.h> + +#include <misc/charlcd.h> + +#define LCD_MINOR 156 + +#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 */ + +struct charlcd_priv { + struct charlcd lcd; + + struct delayed_work bl_work; + struct mutex bl_tempo_lock; /* Protects access to bl_tempo */ + bool bl_tempo; + + bool must_clear; + + /* 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]; + int len; + } esc_seq; + + unsigned long long drvdata[0]; +}; + +#define to_priv(p) container_of(p, struct charlcd_priv, lcd) + +/* 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) +{ + if (in_interrupt()) + mdelay(ms); + else + schedule_timeout_interruptible(msecs_to_jiffies(ms)); +} + +/* turn the backlight on or off */ +static void charlcd_backlight(struct charlcd *lcd, int on) +{ + struct charlcd_priv *priv = to_priv(lcd); + + if (!lcd->ops->backlight) + return; + + mutex_lock(&priv->bl_tempo_lock); + if (!priv->bl_tempo) + lcd->ops->backlight(lcd, on); + mutex_unlock(&priv->bl_tempo_lock); +} + +static void charlcd_bl_off(struct work_struct *work) +{ + struct delayed_work *dwork = to_delayed_work(work); + struct charlcd_priv *priv = + container_of(dwork, struct charlcd_priv, bl_work); + + mutex_lock(&priv->bl_tempo_lock); + if (priv->bl_tempo) { + priv->bl_tempo = false; + if (!(priv->flags & LCD_FLAG_L)) + priv->lcd.ops->backlight(&priv->lcd, 0); + } + mutex_unlock(&priv->bl_tempo_lock); +} + +/* turn the backlight on for a little while */ +void charlcd_poke(struct charlcd *lcd) +{ + struct charlcd_priv *priv = to_priv(lcd); + + if (!lcd->ops->backlight) + return; + + cancel_delayed_work_sync(&priv->bl_work); + + mutex_lock(&priv->bl_tempo_lock); + if (!priv->bl_tempo && !(priv->flags & LCD_FLAG_L)) + lcd->ops->backlight(lcd, 1); + 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 = 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 = to_priv(lcd); + + priv->addr.x = 0; + priv->addr.y = 0; + charlcd_gotoxy(lcd); +} + +static void charlcd_print(struct charlcd *lcd, char c) +{ + struct charlcd_priv *priv = 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; + + 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 = 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 = 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; +} + +/* + * These are the file operation function for user access to /dev/lcd + * This function can also be called from inside the kernel, by + * setting file and ppos to NULL. + * + */ + +static inline int handle_lcd_special_code(struct charlcd *lcd) +{ + struct charlcd_priv *priv = to_priv(lcd); + + /* LCD special codes */ + + int processed = 0; + + char *esc = priv->esc_seq.buf + 2; + int oldflags = priv->flags; + + /* check for display mode flags */ + switch (*esc) { + case 'D': /* Display ON */ + priv->flags |= LCD_FLAG_D; + processed = 1; + break; + case 'd': /* Display OFF */ + priv->flags &= ~LCD_FLAG_D; + processed = 1; + break; + case 'C': /* Cursor ON */ + priv->flags |= LCD_FLAG_C; + processed = 1; + break; + case 'c': /* Cursor OFF */ + priv->flags &= ~LCD_FLAG_C; + processed = 1; + break; + case 'B': /* Blink ON */ + priv->flags |= LCD_FLAG_B; + processed = 1; + break; + case 'b': /* Blink OFF */ + priv->flags &= ~LCD_FLAG_B; + processed = 1; + break; + case '+': /* Back light ON */ + priv->flags |= LCD_FLAG_L; + processed = 1; + break; + case '-': /* Back light OFF */ + priv->flags &= ~LCD_FLAG_L; + processed = 1; + break; + case '*': /* Flash back light */ + charlcd_poke(lcd); + processed = 1; + break; + case 'f': /* Small Font */ + priv->flags &= ~LCD_FLAG_F; + processed = 1; + break; + case 'F': /* Large Font */ + priv->flags |= LCD_FLAG_F; + processed = 1; + break; + case 'n': /* One Line */ + priv->flags &= ~LCD_FLAG_N; + processed = 1; + break; + case 'N': /* Two Lines */ + priv->flags |= LCD_FLAG_N; + 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--; + } + 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++; + } + processed = 1; + break; + case 'L': /* shift display left */ + lcd->ops->write_cmd(lcd, LCD_CMD_SHIFT | LCD_CMD_DISPLAY_SHIFT); + processed = 1; + break; + case 'R': /* shift display right */ + lcd->ops->write_cmd(lcd, + LCD_CMD_SHIFT | LCD_CMD_DISPLAY_SHIFT | + LCD_CMD_SHIFT_RIGHT); + processed = 1; + break; + case 'k': { /* kill end of line */ + int x; + + for (x = priv->addr.x; x < lcd->bwidth; x++) + lcd->ops->write_data(lcd, ' '); + + /* restore cursor position */ + charlcd_gotoxy(lcd); + processed = 1; + break; + } + case 'I': /* reinitialize display */ + charlcd_init_display(lcd); + 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) { + processed = 1; + break; + } + + cgoffset = 0; + shift = 0; + value = 0; + while (*esc && cgoffset < 8) { + shift ^= 4; + if (*esc >= '0' && *esc <= '9') { + value |= (*esc - '0') << shift; + } else if (*esc >= 'A' && *esc <= 'Z') { + value |= (*esc - 'A' + 10) << shift; + } else if (*esc >= 'a' && *esc <= 'z') { + value |= (*esc - 'a' + 10) << shift; + } else { + esc++; + continue; + } + + if (shift == 0) { + cgbytes[cgoffset++] = value; + value = 0; + } + + esc++; + } + + 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 (!strchr(esc, ';')) + break; + + while (*esc) { + if (*esc == 'x') { + esc++; + if (kstrtoul(esc, 10, &priv->addr.x) < 0) + break; + } else if (*esc == 'y') { + esc++; + if (kstrtoul(esc, 10, &priv->addr.y) < 0) + break; + } else { + break; + } + } + + charlcd_gotoxy(lcd); + 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; +} + +static void charlcd_write_char(struct charlcd *lcd, char c) +{ + struct charlcd_priv *priv = to_priv(lcd); + + /* first, we'll test if we're in escape mode */ + if ((c != '\n') && priv->esc_seq.len >= 0) { + /* yes, let's add this char to the buffer */ + priv->esc_seq.buf[priv->esc_seq.len++] = c; + priv->esc_seq.buf[priv->esc_seq.len] = 0; + } else { + /* aborts any previous escape sequence */ + priv->esc_seq.len = -1; + + switch (c) { + case LCD_ESCAPE_CHAR: + /* start of an escape sequence */ + priv->esc_seq.len = 0; + priv->esc_seq.buf[priv->esc_seq.len] = 0; + 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--; + } + /* replace with a space */ + lcd->ops->write_data(lcd, ' '); + /* back one char again */ + lcd->ops->write_cmd(lcd, LCD_CMD_SHIFT); + break; + case '\014': + /* quickly clear the display */ + charlcd_clear_fast(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); + break; + case '\r': + /* go to the beginning of the same line */ + priv->addr.x = 0; + charlcd_gotoxy(lcd); + break; + case '\t': + /* print a space instead of the tab */ + charlcd_print(lcd, ' '); + break; + default: + /* simply print this char */ + charlcd_print(lcd, c); + break; + } + } + + /* + * now we'll see if we're in an escape mode and if the current + * escape sequence can be understood. + */ + if (priv->esc_seq.len >= 2) { + int processed = 0; + + if (!strcmp(priv->esc_seq.buf, "[2J")) { + /* clear the display */ + charlcd_clear_fast(lcd); + processed = 1; + } else if (!strcmp(priv->esc_seq.buf, "[H")) { + /* cursor to home */ + charlcd_home(lcd); + processed = 1; + } + /* codes starting with ^[[L */ + else if ((priv->esc_seq.len >= 3) && + (priv->esc_seq.buf[0] == '[') && + (priv->esc_seq.buf[1] == 'L')) { + processed = handle_lcd_special_code(lcd); + } + + /* LCD special escape codes */ + /* + * flush the escape sequence if it's been processed + * or if it is getting too long. + */ + if (processed || (priv->esc_seq.len >= LCD_ESCAPE_LEN)) + priv->esc_seq.len = -1; + } /* escape codes */ +} + +static struct charlcd *the_charlcd; + +static ssize_t charlcd_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + const char __user *tmp = buf; + char c; + + for (; count-- > 0; (*ppos)++, tmp++) { + if (!in_interrupt() && (((count + 1) & 0x1f) == 0)) + /* + * let's be a little nice with other processes + * that need some CPU + */ + schedule(); + + if (get_user(c, tmp)) + return -EFAULT; + + charlcd_write_char(the_charlcd, c); + } + + return tmp - buf; +} + +static int charlcd_open(struct inode *inode, struct file *file) +{ + struct charlcd_priv *priv = to_priv(the_charlcd); + + if (!atomic_dec_and_test(&charlcd_available)) + return -EBUSY; /* open only once at a time */ + + if (file->f_mode & FMODE_READ) /* device is write-only */ + return -EPERM; + + if (priv->must_clear) { + charlcd_clear_display(&priv->lcd); + priv->must_clear = false; + } + return nonseekable_open(inode, file); +} + +static int charlcd_release(struct inode *inode, struct file *file) +{ + atomic_inc(&charlcd_available); + return 0; +} + +static const struct file_operations charlcd_fops = { + .write = charlcd_write, + .open = charlcd_open, + .release = charlcd_release, + .llseek = no_llseek, +}; + +static struct miscdevice charlcd_dev = { + .minor = LCD_MINOR, + .name = "lcd", + .fops = &charlcd_fops, +}; + +static void charlcd_puts(struct charlcd *lcd, const char *s) +{ + const char *tmp = s; + int count = strlen(s); + + for (; count-- > 0; tmp++) { + if (!in_interrupt() && (((count + 1) & 0x1f) == 0)) + /* + * let's be a little nice with other processes + * that need some CPU + */ + schedule(); + + charlcd_write_char(lcd, *tmp); + } +} + +/* initialize the LCD driver */ +static int charlcd_init(struct charlcd *lcd) +{ + struct charlcd_priv *priv = to_priv(lcd); + int ret; + + if (lcd->ops->backlight) { + mutex_init(&priv->bl_tempo_lock); + INIT_DELAYED_WORK(&priv->bl_work, charlcd_bl_off); + } + + /* + * before this line, we must NOT send anything to the display. + * Since charlcd_init_display() needs to write data, we have to + * enable mark the LCD initialized just before. + */ + ret = charlcd_init_display(lcd); + if (ret) + return ret; + + /* display a short message */ +#ifdef CONFIG_PANEL_CHANGE_MESSAGE +#ifdef CONFIG_PANEL_BOOT_MESSAGE + charlcd_puts(lcd, "\x1b[Lc\x1b[Lb\x1b[L*" CONFIG_PANEL_BOOT_MESSAGE); +#endif +#else + charlcd_puts(lcd, "\x1b[Lc\x1b[Lb\x1b[L*Linux-" UTS_RELEASE "\n"); +#endif + /* clear the display on the next device opening */ + priv->must_clear = true; + charlcd_home(lcd); + return 0; +} + +struct charlcd *charlcd_alloc(unsigned int drvdata_size) +{ + struct charlcd_priv *priv; + struct charlcd *lcd; + + priv = kzalloc(sizeof(*priv) + drvdata_size, 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; +} +EXPORT_SYMBOL_GPL(charlcd_alloc); + +static int panel_notify_sys(struct notifier_block *this, unsigned long code, + void *unused) +{ + struct charlcd *lcd = the_charlcd; + + switch (code) { + case SYS_DOWN: + charlcd_puts(lcd, + "\x0cReloading\nSystem...\x1b[Lc\x1b[Lb\x1b[L+"); + break; + case SYS_HALT: + charlcd_puts(lcd, "\x0cSystem Halted.\x1b[Lc\x1b[Lb\x1b[L+"); + break; + case SYS_POWER_OFF: + charlcd_puts(lcd, "\x0cPower off.\x1b[Lc\x1b[Lb\x1b[L+"); + break; + default: + break; + } + return NOTIFY_DONE; +} + +static struct notifier_block panel_notifier = { + panel_notify_sys, + NULL, + 0 +}; + +int charlcd_register(struct charlcd *lcd) +{ + int ret; + + ret = charlcd_init(lcd); + if (ret) + return ret; + + ret = misc_register(&charlcd_dev); + if (ret) + return ret; + + the_charlcd = lcd; + register_reboot_notifier(&panel_notifier); + return 0; +} +EXPORT_SYMBOL_GPL(charlcd_register); + +int charlcd_unregister(struct charlcd *lcd) +{ + struct charlcd_priv *priv = to_priv(lcd); + + unregister_reboot_notifier(&panel_notifier); + charlcd_puts(lcd, "\x0cLCD driver unloaded.\x1b[Lc\x1b[Lb\x1b[L-"); + misc_deregister(&charlcd_dev); + the_charlcd = NULL; + if (lcd->ops->backlight) { + cancel_delayed_work_sync(&priv->bl_work); + priv->lcd.ops->backlight(&priv->lcd, 0); + } + + return 0; +} +EXPORT_SYMBOL_GPL(charlcd_unregister); + +MODULE_LICENSE("GPL"); diff --git a/drivers/auxdisplay/hd44780.c b/drivers/auxdisplay/hd44780.c new file mode 100644 index 000000000000..036eec404289 --- /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/gpio/consumer.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/property.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; + 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 <geert@linux-m68k.org>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/auxdisplay/ht16k33.c b/drivers/auxdisplay/ht16k33.c index f66b45b235b0..fbfa5b4cc567 100644 --- a/drivers/auxdisplay/ht16k33.c +++ b/drivers/auxdisplay/ht16k33.c @@ -254,18 +254,22 @@ static bool ht16k33_keypad_scan(struct ht16k33_keypad *keypad) { const unsigned short *keycodes = keypad->dev->keycode; u16 new_state[HT16K33_MATRIX_KEYPAD_MAX_COLS]; - u8 data[HT16K33_MATRIX_KEYPAD_MAX_COLS * 2]; + __le16 data[HT16K33_MATRIX_KEYPAD_MAX_COLS]; unsigned long bits_changed; int row, col, code; + int rc; bool pressed = false; - if (i2c_smbus_read_i2c_block_data(keypad->client, 0x40, 6, data) != 6) { - dev_err(&keypad->client->dev, "Failed to read key data\n"); + rc = i2c_smbus_read_i2c_block_data(keypad->client, 0x40, + sizeof(data), (u8 *)data); + if (rc != sizeof(data)) { + dev_err(&keypad->client->dev, + "Failed to read key data, rc=%d\n", rc); return false; } for (col = 0; col < keypad->cols; col++) { - new_state[col] = (data[col * 2 + 1] << 8) | data[col * 2]; + new_state[col] = le16_to_cpu(data[col]); if (new_state[col]) pressed = true; bits_changed = keypad->last_key_state[col] ^ new_state[col]; @@ -278,7 +282,7 @@ static bool ht16k33_keypad_scan(struct ht16k33_keypad *keypad) } } input_sync(keypad->dev); - memcpy(keypad->last_key_state, new_state, sizeof(new_state)); + memcpy(keypad->last_key_state, new_state, sizeof(u16) * keypad->cols); return pressed; } @@ -353,6 +357,12 @@ static int ht16k33_keypad_probe(struct i2c_client *client, err = matrix_keypad_parse_of_params(&client->dev, &rows, &cols); if (err) return err; + if (rows > HT16K33_MATRIX_KEYPAD_MAX_ROWS || + cols > HT16K33_MATRIX_KEYPAD_MAX_COLS) { + dev_err(&client->dev, "%u rows or %u cols out of range in DT\n", + rows, cols); + return -ERANGE; + } keypad->rows = rows; keypad->cols = cols; diff --git a/drivers/auxdisplay/img-ascii-lcd.c b/drivers/auxdisplay/img-ascii-lcd.c index 83f1439e57fd..25306fa27251 100644 --- a/drivers/auxdisplay/img-ascii-lcd.c +++ b/drivers/auxdisplay/img-ascii-lcd.c @@ -220,6 +220,7 @@ static const struct of_device_id img_ascii_lcd_matches[] = { { .compatible = "mti,sead3-lcd", .data = &sead3_config }, { /* sentinel */ } }; +MODULE_DEVICE_TABLE(of, img_ascii_lcd_matches); /** * img_ascii_lcd_scroll() - scroll the display by a character diff --git a/drivers/auxdisplay/panel.c b/drivers/auxdisplay/panel.c new file mode 100644 index 000000000000..e0c014c2356f --- /dev/null +++ b/drivers/auxdisplay/panel.c @@ -0,0 +1,1796 @@ +/* + * Front panel 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. + * + * This code drives an LCD module (/dev/lcd), and a keypad (/dev/keypad) + * connected to a parallel printer port. + * + * The LCD module may either be an HD44780-like 8-bit parallel LCD, or a 1-bit + * serial module compatible with Samsung's KS0074. The pins may be connected in + * any combination, everything is programmable. + * + * The keypad consists in a matrix of push buttons connecting input pins to + * data output pins or to the ground. The combinations have to be hard-coded + * in the driver, though several profiles exist and adding new ones is easy. + * + * Several profiles are provided for commonly found LCD+keypad modules on the + * market, such as those found in Nexcom's appliances. + * + * FIXME: + * - the initialization/deinitialization process is very dirty and should + * be rewritten. It may even be buggy. + * + * TODO: + * - document 24 keys keyboard (3 rows of 8 cols, 32 diodes + 2 inputs) + * - make the LCD a part of a virtual screen of Vx*Vy + * - make the inputs list smp-safe + * - change the keyboard to a double mapping : signals -> key_id -> values + * so that applications can change values without knowing signals + * + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/module.h> + +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/signal.h> +#include <linux/sched.h> +#include <linux/spinlock.h> +#include <linux/interrupt.h> +#include <linux/miscdevice.h> +#include <linux/slab.h> +#include <linux/ioport.h> +#include <linux/fcntl.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/kernel.h> +#include <linux/ctype.h> +#include <linux/parport.h> +#include <linux/list.h> + +#include <linux/io.h> +#include <linux/uaccess.h> + +#include <misc/charlcd.h> + +#define KEYPAD_MINOR 185 + +#define LCD_MAXBYTES 256 /* max burst write */ + +#define KEYPAD_BUFFER 64 + +/* poll the keyboard this every second */ +#define INPUT_POLL_TIME (HZ / 50) +/* a key starts to repeat after this times INPUT_POLL_TIME */ +#define KEYPAD_REP_START (10) +/* a key repeats this times INPUT_POLL_TIME */ +#define KEYPAD_REP_DELAY (2) + +/* converts an r_str() input to an active high, bits string : 000BAOSE */ +#define PNL_PINPUT(a) ((((unsigned char)(a)) ^ 0x7F) >> 3) + +#define PNL_PBUSY 0x80 /* inverted input, active low */ +#define PNL_PACK 0x40 /* direct input, active low */ +#define PNL_POUTPA 0x20 /* direct input, active high */ +#define PNL_PSELECD 0x10 /* direct input, active high */ +#define PNL_PERRORP 0x08 /* direct input, active low */ + +#define PNL_PBIDIR 0x20 /* bi-directional ports */ +/* high to read data in or-ed with data out */ +#define PNL_PINTEN 0x10 +#define PNL_PSELECP 0x08 /* inverted output, active low */ +#define PNL_PINITP 0x04 /* direct output, active low */ +#define PNL_PAUTOLF 0x02 /* inverted output, active low */ +#define PNL_PSTROBE 0x01 /* inverted output */ + +#define PNL_PD0 0x01 +#define PNL_PD1 0x02 +#define PNL_PD2 0x04 +#define PNL_PD3 0x08 +#define PNL_PD4 0x10 +#define PNL_PD5 0x20 +#define PNL_PD6 0x40 +#define PNL_PD7 0x80 + +#define PIN_NONE 0 +#define PIN_STROBE 1 +#define PIN_D0 2 +#define PIN_D1 3 +#define PIN_D2 4 +#define PIN_D3 5 +#define PIN_D4 6 +#define PIN_D5 7 +#define PIN_D6 8 +#define PIN_D7 9 +#define PIN_AUTOLF 14 +#define PIN_INITP 16 +#define PIN_SELECP 17 +#define PIN_NOT_SET 127 + +#define NOT_SET -1 + +/* macros to simplify use of the parallel port */ +#define r_ctr(x) (parport_read_control((x)->port)) +#define r_dtr(x) (parport_read_data((x)->port)) +#define r_str(x) (parport_read_status((x)->port)) +#define w_ctr(x, y) (parport_write_control((x)->port, (y))) +#define w_dtr(x, y) (parport_write_data((x)->port, (y))) + +/* this defines which bits are to be used and which ones to be ignored */ +/* logical or of the output bits involved in the scan matrix */ +static __u8 scan_mask_o; +/* logical or of the input bits involved in the scan matrix */ +static __u8 scan_mask_i; + +enum input_type { + INPUT_TYPE_STD, + INPUT_TYPE_KBD, +}; + +enum input_state { + INPUT_ST_LOW, + INPUT_ST_RISING, + INPUT_ST_HIGH, + INPUT_ST_FALLING, +}; + +struct logical_input { + struct list_head list; + __u64 mask; + __u64 value; + enum input_type type; + enum input_state state; + __u8 rise_time, fall_time; + __u8 rise_timer, fall_timer, high_timer; + + union { + struct { /* valid when type == INPUT_TYPE_STD */ + void (*press_fct)(int); + void (*release_fct)(int); + int press_data; + int release_data; + } std; + struct { /* valid when type == INPUT_TYPE_KBD */ + /* strings can be non null-terminated */ + char press_str[sizeof(void *) + sizeof(int)]; + char repeat_str[sizeof(void *) + sizeof(int)]; + char release_str[sizeof(void *) + sizeof(int)]; + } kbd; + } u; +}; + +static LIST_HEAD(logical_inputs); /* list of all defined logical inputs */ + +/* physical contacts history + * Physical contacts are a 45 bits string of 9 groups of 5 bits each. + * The 8 lower groups correspond to output bits 0 to 7, and the 9th group + * corresponds to the ground. + * Within each group, bits are stored in the same order as read on the port : + * BAPSE (busy=4, ack=3, paper empty=2, select=1, error=0). + * So, each __u64 is represented like this : + * 0000000000000000000BAPSEBAPSEBAPSEBAPSEBAPSEBAPSEBAPSEBAPSEBAPSE + * <-----unused------><gnd><d07><d06><d05><d04><d03><d02><d01><d00> + */ + +/* what has just been read from the I/O ports */ +static __u64 phys_read; +/* previous phys_read */ +static __u64 phys_read_prev; +/* stabilized phys_read (phys_read|phys_read_prev) */ +static __u64 phys_curr; +/* previous phys_curr */ +static __u64 phys_prev; +/* 0 means that at least one logical signal needs be computed */ +static char inputs_stable; + +/* these variables are specific to the keypad */ +static struct { + bool enabled; +} keypad; + +static char keypad_buffer[KEYPAD_BUFFER]; +static int keypad_buflen; +static int keypad_start; +static char keypressed; +static wait_queue_head_t keypad_read_wait; + +/* lcd-specific variables */ +static struct { + bool enabled; + bool initialized; + + int charset; + int proto; + + /* TODO: use union here? */ + struct { + int e; + int rs; + int rw; + int cl; + int da; + int bl; + } pins; + + struct charlcd *charlcd; +} lcd; + +/* Needed only for init */ +static int selected_lcd_type = NOT_SET; + +/* + * Bit masks to convert LCD signals to parallel port outputs. + * _d_ are values for data port, _c_ are for control port. + * [0] = signal OFF, [1] = signal ON, [2] = mask + */ +#define BIT_CLR 0 +#define BIT_SET 1 +#define BIT_MSK 2 +#define BIT_STATES 3 +/* + * one entry for each bit on the LCD + */ +#define LCD_BIT_E 0 +#define LCD_BIT_RS 1 +#define LCD_BIT_RW 2 +#define LCD_BIT_BL 3 +#define LCD_BIT_CL 4 +#define LCD_BIT_DA 5 +#define LCD_BITS 6 + +/* + * each bit can be either connected to a DATA or CTRL port + */ +#define LCD_PORT_C 0 +#define LCD_PORT_D 1 +#define LCD_PORTS 2 + +static unsigned char lcd_bits[LCD_PORTS][LCD_BITS][BIT_STATES]; + +/* + * LCD protocols + */ +#define LCD_PROTO_PARALLEL 0 +#define LCD_PROTO_SERIAL 1 +#define LCD_PROTO_TI_DA8XX_LCD 2 + +/* + * LCD character sets + */ +#define LCD_CHARSET_NORMAL 0 +#define LCD_CHARSET_KS0074 1 + +/* + * LCD types + */ +#define LCD_TYPE_NONE 0 +#define LCD_TYPE_CUSTOM 1 +#define LCD_TYPE_OLD 2 +#define LCD_TYPE_KS0074 3 +#define LCD_TYPE_HANTRONIX 4 +#define LCD_TYPE_NEXCOM 5 + +/* + * keypad types + */ +#define KEYPAD_TYPE_NONE 0 +#define KEYPAD_TYPE_OLD 1 +#define KEYPAD_TYPE_NEW 2 +#define KEYPAD_TYPE_NEXCOM 3 + +/* + * panel profiles + */ +#define PANEL_PROFILE_CUSTOM 0 +#define PANEL_PROFILE_OLD 1 +#define PANEL_PROFILE_NEW 2 +#define PANEL_PROFILE_HANTRONIX 3 +#define PANEL_PROFILE_NEXCOM 4 +#define PANEL_PROFILE_LARGE 5 + +/* + * Construct custom config from the kernel's configuration + */ +#define DEFAULT_PARPORT 0 +#define DEFAULT_PROFILE PANEL_PROFILE_LARGE +#define DEFAULT_KEYPAD_TYPE KEYPAD_TYPE_OLD +#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 + +#define DEFAULT_LCD_PIN_E PIN_AUTOLF +#define DEFAULT_LCD_PIN_RS PIN_SELECP +#define DEFAULT_LCD_PIN_RW PIN_INITP +#define DEFAULT_LCD_PIN_SCL PIN_STROBE +#define DEFAULT_LCD_PIN_SDA PIN_D0 +#define DEFAULT_LCD_PIN_BL PIN_NOT_SET + +#ifdef CONFIG_PANEL_PARPORT +#undef DEFAULT_PARPORT +#define DEFAULT_PARPORT CONFIG_PANEL_PARPORT +#endif + +#ifdef CONFIG_PANEL_PROFILE +#undef DEFAULT_PROFILE +#define DEFAULT_PROFILE CONFIG_PANEL_PROFILE +#endif + +#if DEFAULT_PROFILE == 0 /* custom */ +#ifdef CONFIG_PANEL_KEYPAD +#undef DEFAULT_KEYPAD_TYPE +#define DEFAULT_KEYPAD_TYPE CONFIG_PANEL_KEYPAD +#endif + +#ifdef CONFIG_PANEL_LCD +#undef DEFAULT_LCD_TYPE +#define DEFAULT_LCD_TYPE CONFIG_PANEL_LCD +#endif + +#ifdef CONFIG_PANEL_LCD_HEIGHT +#undef DEFAULT_LCD_HEIGHT +#define DEFAULT_LCD_HEIGHT CONFIG_PANEL_LCD_HEIGHT +#endif + +#ifdef CONFIG_PANEL_LCD_WIDTH +#undef DEFAULT_LCD_WIDTH +#define DEFAULT_LCD_WIDTH CONFIG_PANEL_LCD_WIDTH +#endif + +#ifdef CONFIG_PANEL_LCD_BWIDTH +#undef DEFAULT_LCD_BWIDTH +#define DEFAULT_LCD_BWIDTH CONFIG_PANEL_LCD_BWIDTH +#endif + +#ifdef CONFIG_PANEL_LCD_HWIDTH +#undef DEFAULT_LCD_HWIDTH +#define DEFAULT_LCD_HWIDTH CONFIG_PANEL_LCD_HWIDTH +#endif + +#ifdef CONFIG_PANEL_LCD_CHARSET +#undef DEFAULT_LCD_CHARSET +#define DEFAULT_LCD_CHARSET CONFIG_PANEL_LCD_CHARSET +#endif + +#ifdef CONFIG_PANEL_LCD_PROTO +#undef DEFAULT_LCD_PROTO +#define DEFAULT_LCD_PROTO CONFIG_PANEL_LCD_PROTO +#endif + +#ifdef CONFIG_PANEL_LCD_PIN_E +#undef DEFAULT_LCD_PIN_E +#define DEFAULT_LCD_PIN_E CONFIG_PANEL_LCD_PIN_E +#endif + +#ifdef CONFIG_PANEL_LCD_PIN_RS +#undef DEFAULT_LCD_PIN_RS +#define DEFAULT_LCD_PIN_RS CONFIG_PANEL_LCD_PIN_RS +#endif + +#ifdef CONFIG_PANEL_LCD_PIN_RW +#undef DEFAULT_LCD_PIN_RW +#define DEFAULT_LCD_PIN_RW CONFIG_PANEL_LCD_PIN_RW +#endif + +#ifdef CONFIG_PANEL_LCD_PIN_SCL +#undef DEFAULT_LCD_PIN_SCL +#define DEFAULT_LCD_PIN_SCL CONFIG_PANEL_LCD_PIN_SCL +#endif + +#ifdef CONFIG_PANEL_LCD_PIN_SDA +#undef DEFAULT_LCD_PIN_SDA +#define DEFAULT_LCD_PIN_SDA CONFIG_PANEL_LCD_PIN_SDA +#endif + +#ifdef CONFIG_PANEL_LCD_PIN_BL +#undef DEFAULT_LCD_PIN_BL +#define DEFAULT_LCD_PIN_BL CONFIG_PANEL_LCD_PIN_BL +#endif + +#endif /* DEFAULT_PROFILE == 0 */ + +/* global variables */ + +/* Device single-open policy control */ +static atomic_t keypad_available = ATOMIC_INIT(1); + +static struct pardevice *pprt; + +static int keypad_initialized; + +static DEFINE_SPINLOCK(pprt_lock); +static struct timer_list scan_timer; + +MODULE_DESCRIPTION("Generic parallel port LCD/Keypad driver"); + +static int parport = DEFAULT_PARPORT; +module_param(parport, int, 0000); +MODULE_PARM_DESC(parport, "Parallel port index (0=lpt1, 1=lpt2, ...)"); + +static int profile = DEFAULT_PROFILE; +module_param(profile, int, 0000); +MODULE_PARM_DESC(profile, + "1=16x2 old kp; 2=serial 16x2, new kp; 3=16x2 hantronix; " + "4=16x2 nexcom; default=40x2, old kp"); + +static int keypad_type = NOT_SET; +module_param(keypad_type, int, 0000); +MODULE_PARM_DESC(keypad_type, + "Keypad type: 0=none, 1=old 6 keys, 2=new 6+1 keys, 3=nexcom 4 keys"); + +static int lcd_type = NOT_SET; +module_param(lcd_type, int, 0000); +MODULE_PARM_DESC(lcd_type, + "LCD type: 0=none, 1=compiled-in, 2=old, 3=serial ks0074, 4=hantronix, 5=nexcom"); + +static int lcd_height = NOT_SET; +module_param(lcd_height, int, 0000); +MODULE_PARM_DESC(lcd_height, "Number of lines on the LCD"); + +static int lcd_width = NOT_SET; +module_param(lcd_width, int, 0000); +MODULE_PARM_DESC(lcd_width, "Number of columns on the LCD"); + +static int lcd_bwidth = NOT_SET; /* internal buffer width (usually 40) */ +module_param(lcd_bwidth, int, 0000); +MODULE_PARM_DESC(lcd_bwidth, "Internal LCD line width (40)"); + +static int lcd_hwidth = NOT_SET; /* hardware buffer width (usually 64) */ +module_param(lcd_hwidth, int, 0000); +MODULE_PARM_DESC(lcd_hwidth, "LCD line hardware address (64)"); + +static int lcd_charset = NOT_SET; +module_param(lcd_charset, int, 0000); +MODULE_PARM_DESC(lcd_charset, "LCD character set: 0=standard, 1=KS0074"); + +static int lcd_proto = NOT_SET; +module_param(lcd_proto, int, 0000); +MODULE_PARM_DESC(lcd_proto, + "LCD communication: 0=parallel (//), 1=serial, 2=TI LCD Interface"); + +/* + * These are the parallel port pins the LCD control signals are connected to. + * Set this to 0 if the signal is not used. Set it to its opposite value + * (negative) if the signal is negated. -MAXINT is used to indicate that the + * pin has not been explicitly specified. + * + * WARNING! no check will be performed about collisions with keypad ! + */ + +static int lcd_e_pin = PIN_NOT_SET; +module_param(lcd_e_pin, int, 0000); +MODULE_PARM_DESC(lcd_e_pin, + "# of the // port pin connected to LCD 'E' signal, with polarity (-17..17)"); + +static int lcd_rs_pin = PIN_NOT_SET; +module_param(lcd_rs_pin, int, 0000); +MODULE_PARM_DESC(lcd_rs_pin, + "# of the // port pin connected to LCD 'RS' signal, with polarity (-17..17)"); + +static int lcd_rw_pin = PIN_NOT_SET; +module_param(lcd_rw_pin, int, 0000); +MODULE_PARM_DESC(lcd_rw_pin, + "# of the // port pin connected to LCD 'RW' signal, with polarity (-17..17)"); + +static int lcd_cl_pin = PIN_NOT_SET; +module_param(lcd_cl_pin, int, 0000); +MODULE_PARM_DESC(lcd_cl_pin, + "# of the // port pin connected to serial LCD 'SCL' signal, with polarity (-17..17)"); + +static int lcd_da_pin = PIN_NOT_SET; +module_param(lcd_da_pin, int, 0000); +MODULE_PARM_DESC(lcd_da_pin, + "# of the // port pin connected to serial LCD 'SDA' signal, with polarity (-17..17)"); + +static int lcd_bl_pin = PIN_NOT_SET; +module_param(lcd_bl_pin, int, 0000); +MODULE_PARM_DESC(lcd_bl_pin, + "# of the // port pin connected to LCD backlight, with polarity (-17..17)"); + +/* Deprecated module parameters - consider not using them anymore */ + +static int lcd_enabled = NOT_SET; +module_param(lcd_enabled, int, 0000); +MODULE_PARM_DESC(lcd_enabled, "Deprecated option, use lcd_type instead"); + +static int keypad_enabled = NOT_SET; +module_param(keypad_enabled, int, 0000); +MODULE_PARM_DESC(keypad_enabled, "Deprecated option, use keypad_type instead"); + +/* for some LCD drivers (ks0074) we need a charset conversion table. */ +static const unsigned char lcd_char_conv_ks0074[256] = { + /* 0|8 1|9 2|A 3|B 4|C 5|D 6|E 7|F */ + /* 0x00 */ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + /* 0x08 */ 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + /* 0x10 */ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + /* 0x18 */ 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + /* 0x20 */ 0x20, 0x21, 0x22, 0x23, 0xa2, 0x25, 0x26, 0x27, + /* 0x28 */ 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + /* 0x30 */ 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + /* 0x38 */ 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + /* 0x40 */ 0xa0, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + /* 0x48 */ 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + /* 0x50 */ 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + /* 0x58 */ 0x58, 0x59, 0x5a, 0xfa, 0xfb, 0xfc, 0x1d, 0xc4, + /* 0x60 */ 0x96, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + /* 0x68 */ 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + /* 0x70 */ 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + /* 0x78 */ 0x78, 0x79, 0x7a, 0xfd, 0xfe, 0xff, 0xce, 0x20, + /* 0x80 */ 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + /* 0x88 */ 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + /* 0x90 */ 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + /* 0x98 */ 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + /* 0xA0 */ 0x20, 0x40, 0xb1, 0xa1, 0x24, 0xa3, 0xfe, 0x5f, + /* 0xA8 */ 0x22, 0xc8, 0x61, 0x14, 0x97, 0x2d, 0xad, 0x96, + /* 0xB0 */ 0x80, 0x8c, 0x82, 0x83, 0x27, 0x8f, 0x86, 0xdd, + /* 0xB8 */ 0x2c, 0x81, 0x6f, 0x15, 0x8b, 0x8a, 0x84, 0x60, + /* 0xC0 */ 0xe2, 0xe2, 0xe2, 0x5b, 0x5b, 0xae, 0xbc, 0xa9, + /* 0xC8 */ 0xc5, 0xbf, 0xc6, 0xf1, 0xe3, 0xe3, 0xe3, 0xe3, + /* 0xD0 */ 0x44, 0x5d, 0xa8, 0xe4, 0xec, 0xec, 0x5c, 0x78, + /* 0xD8 */ 0xab, 0xa6, 0xe5, 0x5e, 0x5e, 0xe6, 0xaa, 0xbe, + /* 0xE0 */ 0x7f, 0xe7, 0xaf, 0x7b, 0x7b, 0xaf, 0xbd, 0xc8, + /* 0xE8 */ 0xa4, 0xa5, 0xc7, 0xf6, 0xa7, 0xe8, 0x69, 0x69, + /* 0xF0 */ 0xed, 0x7d, 0xa8, 0xe4, 0xec, 0x5c, 0x5c, 0x25, + /* 0xF8 */ 0xac, 0xa6, 0xea, 0xef, 0x7e, 0xeb, 0xb2, 0x79, +}; + +static const char old_keypad_profile[][4][9] = { + {"S0", "Left\n", "Left\n", ""}, + {"S1", "Down\n", "Down\n", ""}, + {"S2", "Up\n", "Up\n", ""}, + {"S3", "Right\n", "Right\n", ""}, + {"S4", "Esc\n", "Esc\n", ""}, + {"S5", "Ret\n", "Ret\n", ""}, + {"", "", "", ""} +}; + +/* signals, press, repeat, release */ +static const char new_keypad_profile[][4][9] = { + {"S0", "Left\n", "Left\n", ""}, + {"S1", "Down\n", "Down\n", ""}, + {"S2", "Up\n", "Up\n", ""}, + {"S3", "Right\n", "Right\n", ""}, + {"S4s5", "", "Esc\n", "Esc\n"}, + {"s4S5", "", "Ret\n", "Ret\n"}, + {"S4S5", "Help\n", "", ""}, + /* add new signals above this line */ + {"", "", "", ""} +}; + +/* signals, press, repeat, release */ +static const char nexcom_keypad_profile[][4][9] = { + {"a-p-e-", "Down\n", "Down\n", ""}, + {"a-p-E-", "Ret\n", "Ret\n", ""}, + {"a-P-E-", "Esc\n", "Esc\n", ""}, + {"a-P-e-", "Up\n", "Up\n", ""}, + /* add new signals above this line */ + {"", "", "", ""} +}; + +static const char (*keypad_profile)[4][9] = old_keypad_profile; + +static DECLARE_BITMAP(bits, LCD_BITS); + +static void lcd_get_bits(unsigned int port, int *val) +{ + unsigned int bit, state; + + for (bit = 0; bit < LCD_BITS; bit++) { + state = test_bit(bit, bits) ? BIT_SET : BIT_CLR; + *val &= lcd_bits[port][bit][BIT_MSK]; + *val |= lcd_bits[port][bit][state]; + } +} + +/* sets data port bits according to current signals values */ +static int set_data_bits(void) +{ + int val; + + val = r_dtr(pprt); + lcd_get_bits(LCD_PORT_D, &val); + w_dtr(pprt, val); + return val; +} + +/* sets ctrl port bits according to current signals values */ +static int set_ctrl_bits(void) +{ + int val; + + val = r_ctr(pprt); + lcd_get_bits(LCD_PORT_C, &val); + w_ctr(pprt, val); + return val; +} + +/* sets ctrl & data port bits according to current signals values */ +static void panel_set_bits(void) +{ + set_data_bits(); + set_ctrl_bits(); +} + +/* + * Converts a parallel port pin (from -25 to 25) to data and control ports + * masks, and data and control port bits. The signal will be considered + * unconnected if it's on pin 0 or an invalid pin (<-25 or >25). + * + * Result will be used this way : + * out(dport, in(dport) & d_val[2] | d_val[signal_state]) + * out(cport, in(cport) & c_val[2] | c_val[signal_state]) + */ +static void pin_to_bits(int pin, unsigned char *d_val, unsigned char *c_val) +{ + int d_bit, c_bit, inv; + + d_val[0] = 0; + c_val[0] = 0; + d_val[1] = 0; + c_val[1] = 0; + d_val[2] = 0xFF; + c_val[2] = 0xFF; + + if (pin == 0) + return; + + inv = (pin < 0); + if (inv) + pin = -pin; + + d_bit = 0; + c_bit = 0; + + switch (pin) { + case PIN_STROBE: /* strobe, inverted */ + c_bit = PNL_PSTROBE; + inv = !inv; + break; + case PIN_D0...PIN_D7: /* D0 - D7 = 2 - 9 */ + d_bit = 1 << (pin - 2); + break; + case PIN_AUTOLF: /* autofeed, inverted */ + c_bit = PNL_PAUTOLF; + inv = !inv; + break; + case PIN_INITP: /* init, direct */ + c_bit = PNL_PINITP; + break; + case PIN_SELECP: /* select_in, inverted */ + c_bit = PNL_PSELECP; + inv = !inv; + break; + default: /* unknown pin, ignore */ + break; + } + + if (c_bit) { + c_val[2] &= ~c_bit; + c_val[!inv] = c_bit; + } else if (d_bit) { + d_val[2] &= ~d_bit; + d_val[!inv] = d_bit; + } +} + +/* + * send a serial byte to the LCD panel. The caller is responsible for locking + * if needed. + */ +static void lcd_send_serial(int byte) +{ + int bit; + + /* + * the data bit is set on D0, and the clock on STROBE. + * LCD reads D0 on STROBE's rising edge. + */ + for (bit = 0; bit < 8; bit++) { + clear_bit(LCD_BIT_CL, bits); /* CLK low */ + panel_set_bits(); + if (byte & 1) { + set_bit(LCD_BIT_DA, bits); + } else { + clear_bit(LCD_BIT_DA, bits); + } + + panel_set_bits(); + udelay(2); /* maintain the data during 2 us before CLK up */ + set_bit(LCD_BIT_CL, bits); /* CLK high */ + panel_set_bits(); + udelay(1); /* maintain the strobe during 1 us */ + byte >>= 1; + } +} + +/* turn the backlight on or off */ +static void lcd_backlight(struct charlcd *charlcd, int on) +{ + if (lcd.pins.bl == PIN_NONE) + return; + + /* The backlight is activated by setting the AUTOFEED line to +5V */ + spin_lock_irq(&pprt_lock); + if (on) + set_bit(LCD_BIT_BL, bits); + else + clear_bit(LCD_BIT_BL, bits); + panel_set_bits(); + spin_unlock_irq(&pprt_lock); +} + +/* send a command to the LCD panel in serial mode */ +static void lcd_write_cmd_s(struct charlcd *charlcd, int cmd) +{ + spin_lock_irq(&pprt_lock); + lcd_send_serial(0x1F); /* R/W=W, RS=0 */ + lcd_send_serial(cmd & 0x0F); + lcd_send_serial((cmd >> 4) & 0x0F); + udelay(40); /* the shortest command takes at least 40 us */ + spin_unlock_irq(&pprt_lock); +} + +/* send data to the LCD panel in serial mode */ +static void lcd_write_data_s(struct charlcd *charlcd, int data) +{ + spin_lock_irq(&pprt_lock); + lcd_send_serial(0x5F); /* R/W=W, RS=1 */ + lcd_send_serial(data & 0x0F); + lcd_send_serial((data >> 4) & 0x0F); + udelay(40); /* the shortest data takes at least 40 us */ + spin_unlock_irq(&pprt_lock); +} + +/* send a command to the LCD panel in 8 bits parallel mode */ +static void lcd_write_cmd_p8(struct charlcd *charlcd, int cmd) +{ + spin_lock_irq(&pprt_lock); + /* present the data to the data port */ + w_dtr(pprt, cmd); + udelay(20); /* maintain the data during 20 us before the strobe */ + + set_bit(LCD_BIT_E, bits); + clear_bit(LCD_BIT_RS, bits); + clear_bit(LCD_BIT_RW, bits); + set_ctrl_bits(); + + udelay(40); /* maintain the strobe during 40 us */ + + clear_bit(LCD_BIT_E, bits); + set_ctrl_bits(); + + udelay(120); /* the shortest command takes at least 120 us */ + spin_unlock_irq(&pprt_lock); +} + +/* send data to the LCD panel in 8 bits parallel mode */ +static void lcd_write_data_p8(struct charlcd *charlcd, int data) +{ + spin_lock_irq(&pprt_lock); + /* present the data to the data port */ + w_dtr(pprt, data); + udelay(20); /* maintain the data during 20 us before the strobe */ + + set_bit(LCD_BIT_E, bits); + set_bit(LCD_BIT_RS, bits); + clear_bit(LCD_BIT_RW, bits); + set_ctrl_bits(); + + udelay(40); /* maintain the strobe during 40 us */ + + clear_bit(LCD_BIT_E, bits); + set_ctrl_bits(); + + udelay(45); /* the shortest data takes at least 45 us */ + spin_unlock_irq(&pprt_lock); +} + +/* send a command to the TI LCD panel */ +static void lcd_write_cmd_tilcd(struct charlcd *charlcd, int cmd) +{ + spin_lock_irq(&pprt_lock); + /* present the data to the control port */ + w_ctr(pprt, cmd); + udelay(60); + spin_unlock_irq(&pprt_lock); +} + +/* send data to the TI LCD panel */ +static void lcd_write_data_tilcd(struct charlcd *charlcd, int data) +{ + spin_lock_irq(&pprt_lock); + /* present the data to the data port */ + w_dtr(pprt, data); + udelay(60); + 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 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 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 struct charlcd_ops charlcd_tilcd_ops = { + .write_cmd = lcd_write_cmd_tilcd, + .write_data = lcd_write_data_tilcd, + .clear_fast = lcd_clear_fast_tilcd, + .backlight = lcd_backlight, +}; + +/* initialize the LCD driver */ +static void lcd_init(void) +{ + struct charlcd *charlcd; + + charlcd = charlcd_alloc(0); + if (!charlcd) + return; + + /* + * 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; + + switch (selected_lcd_type) { + case LCD_TYPE_OLD: + /* parallel mode, 8 bits */ + lcd.proto = LCD_PROTO_PARALLEL; + lcd.charset = LCD_CHARSET_NORMAL; + lcd.pins.e = PIN_STROBE; + lcd.pins.rs = PIN_AUTOLF; + + charlcd->width = 40; + charlcd->bwidth = 40; + charlcd->hwidth = 64; + charlcd->height = 2; + break; + case LCD_TYPE_KS0074: + /* serial mode, ks0074 */ + lcd.proto = LCD_PROTO_SERIAL; + lcd.charset = LCD_CHARSET_KS0074; + lcd.pins.bl = PIN_AUTOLF; + lcd.pins.cl = PIN_STROBE; + lcd.pins.da = PIN_D0; + + charlcd->width = 16; + charlcd->bwidth = 40; + charlcd->hwidth = 16; + charlcd->height = 2; + break; + case LCD_TYPE_NEXCOM: + /* parallel mode, 8 bits, generic */ + lcd.proto = LCD_PROTO_PARALLEL; + lcd.charset = LCD_CHARSET_NORMAL; + lcd.pins.e = PIN_AUTOLF; + lcd.pins.rs = PIN_SELECP; + lcd.pins.rw = PIN_INITP; + + charlcd->width = 16; + charlcd->bwidth = 40; + charlcd->hwidth = 64; + charlcd->height = 2; + break; + case LCD_TYPE_CUSTOM: + /* customer-defined */ + lcd.proto = DEFAULT_LCD_PROTO; + lcd.charset = DEFAULT_LCD_CHARSET; + /* default geometry will be set later */ + break; + case LCD_TYPE_HANTRONIX: + /* parallel mode, 8 bits, hantronix-like */ + default: + lcd.proto = LCD_PROTO_PARALLEL; + lcd.charset = LCD_CHARSET_NORMAL; + lcd.pins.e = PIN_STROBE; + lcd.pins.rs = PIN_SELECP; + + charlcd->width = 16; + charlcd->bwidth = 40; + charlcd->hwidth = 64; + charlcd->height = 2; + break; + } + + /* Overwrite with module params set on loading */ + if (lcd_height != NOT_SET) + charlcd->height = lcd_height; + if (lcd_width != NOT_SET) + charlcd->width = lcd_width; + if (lcd_bwidth != NOT_SET) + charlcd->bwidth = lcd_bwidth; + if (lcd_hwidth != NOT_SET) + charlcd->hwidth = lcd_hwidth; + if (lcd_charset != NOT_SET) + lcd.charset = lcd_charset; + if (lcd_proto != NOT_SET) + lcd.proto = lcd_proto; + if (lcd_e_pin != PIN_NOT_SET) + lcd.pins.e = lcd_e_pin; + if (lcd_rs_pin != PIN_NOT_SET) + lcd.pins.rs = lcd_rs_pin; + if (lcd_rw_pin != PIN_NOT_SET) + lcd.pins.rw = lcd_rw_pin; + if (lcd_cl_pin != PIN_NOT_SET) + lcd.pins.cl = lcd_cl_pin; + if (lcd_da_pin != PIN_NOT_SET) + lcd.pins.da = lcd_da_pin; + if (lcd_bl_pin != PIN_NOT_SET) + lcd.pins.bl = lcd_bl_pin; + + /* 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 (charlcd->height <= 0) + charlcd->height = DEFAULT_LCD_HEIGHT; + + if (lcd.proto == LCD_PROTO_SERIAL) { /* SERIAL */ + charlcd->ops = &charlcd_serial_ops; + + if (lcd.pins.cl == PIN_NOT_SET) + lcd.pins.cl = DEFAULT_LCD_PIN_SCL; + if (lcd.pins.da == PIN_NOT_SET) + lcd.pins.da = DEFAULT_LCD_PIN_SDA; + + } else if (lcd.proto == LCD_PROTO_PARALLEL) { /* PARALLEL */ + charlcd->ops = &charlcd_parallel_ops; + + if (lcd.pins.e == PIN_NOT_SET) + lcd.pins.e = DEFAULT_LCD_PIN_E; + if (lcd.pins.rs == PIN_NOT_SET) + lcd.pins.rs = DEFAULT_LCD_PIN_RS; + if (lcd.pins.rw == PIN_NOT_SET) + lcd.pins.rw = DEFAULT_LCD_PIN_RW; + } else { + charlcd->ops = &charlcd_tilcd_ops; + } + + if (lcd.pins.bl == PIN_NOT_SET) + lcd.pins.bl = DEFAULT_LCD_PIN_BL; + + if (lcd.pins.e == PIN_NOT_SET) + lcd.pins.e = PIN_NONE; + if (lcd.pins.rs == PIN_NOT_SET) + lcd.pins.rs = PIN_NONE; + if (lcd.pins.rw == PIN_NOT_SET) + lcd.pins.rw = PIN_NONE; + if (lcd.pins.bl == PIN_NOT_SET) + lcd.pins.bl = PIN_NONE; + if (lcd.pins.cl == PIN_NOT_SET) + lcd.pins.cl = PIN_NONE; + if (lcd.pins.da == PIN_NOT_SET) + lcd.pins.da = PIN_NONE; + + if (lcd.charset == NOT_SET) + lcd.charset = DEFAULT_LCD_CHARSET; + + if (lcd.charset == LCD_CHARSET_KS0074) + charlcd->char_conv = lcd_char_conv_ks0074; + else + charlcd->char_conv = NULL; + + pin_to_bits(lcd.pins.e, lcd_bits[LCD_PORT_D][LCD_BIT_E], + lcd_bits[LCD_PORT_C][LCD_BIT_E]); + pin_to_bits(lcd.pins.rs, lcd_bits[LCD_PORT_D][LCD_BIT_RS], + lcd_bits[LCD_PORT_C][LCD_BIT_RS]); + pin_to_bits(lcd.pins.rw, lcd_bits[LCD_PORT_D][LCD_BIT_RW], + lcd_bits[LCD_PORT_C][LCD_BIT_RW]); + pin_to_bits(lcd.pins.bl, lcd_bits[LCD_PORT_D][LCD_BIT_BL], + lcd_bits[LCD_PORT_C][LCD_BIT_BL]); + pin_to_bits(lcd.pins.cl, lcd_bits[LCD_PORT_D][LCD_BIT_CL], + lcd_bits[LCD_PORT_C][LCD_BIT_CL]); + pin_to_bits(lcd.pins.da, lcd_bits[LCD_PORT_D][LCD_BIT_DA], + lcd_bits[LCD_PORT_C][LCD_BIT_DA]); + + lcd.charlcd = charlcd; + lcd.initialized = true; +} + +/* + * These are the file operation function for user access to /dev/keypad + */ + +static ssize_t keypad_read(struct file *file, + char __user *buf, size_t count, loff_t *ppos) +{ + unsigned i = *ppos; + char __user *tmp = buf; + + if (keypad_buflen == 0) { + if (file->f_flags & O_NONBLOCK) + return -EAGAIN; + + if (wait_event_interruptible(keypad_read_wait, + keypad_buflen != 0)) + return -EINTR; + } + + for (; count-- > 0 && (keypad_buflen > 0); + ++i, ++tmp, --keypad_buflen) { + put_user(keypad_buffer[keypad_start], tmp); + keypad_start = (keypad_start + 1) % KEYPAD_BUFFER; + } + *ppos = i; + + return tmp - buf; +} + +static int keypad_open(struct inode *inode, struct file *file) +{ + if (!atomic_dec_and_test(&keypad_available)) + return -EBUSY; /* open only once at a time */ + + if (file->f_mode & FMODE_WRITE) /* device is read-only */ + return -EPERM; + + keypad_buflen = 0; /* flush the buffer on opening */ + return 0; +} + +static int keypad_release(struct inode *inode, struct file *file) +{ + atomic_inc(&keypad_available); + return 0; +} + +static const struct file_operations keypad_fops = { + .read = keypad_read, /* read */ + .open = keypad_open, /* open */ + .release = keypad_release, /* close */ + .llseek = default_llseek, +}; + +static struct miscdevice keypad_dev = { + .minor = KEYPAD_MINOR, + .name = "keypad", + .fops = &keypad_fops, +}; + +static void keypad_send_key(const char *string, int max_len) +{ + /* send the key to the device only if a process is attached to it. */ + if (!atomic_read(&keypad_available)) { + while (max_len-- && keypad_buflen < KEYPAD_BUFFER && *string) { + keypad_buffer[(keypad_start + keypad_buflen++) % + KEYPAD_BUFFER] = *string++; + } + wake_up_interruptible(&keypad_read_wait); + } +} + +/* this function scans all the bits involving at least one logical signal, + * and puts the results in the bitfield "phys_read" (one bit per established + * contact), and sets "phys_read_prev" to "phys_read". + * + * Note: to debounce input signals, we will only consider as switched a signal + * which is stable across 2 measures. Signals which are different between two + * reads will be kept as they previously were in their logical form (phys_prev). + * A signal which has just switched will have a 1 in + * (phys_read ^ phys_read_prev). + */ +static void phys_scan_contacts(void) +{ + int bit, bitval; + char oldval; + char bitmask; + char gndmask; + + phys_prev = phys_curr; + phys_read_prev = phys_read; + phys_read = 0; /* flush all signals */ + + /* keep track of old value, with all outputs disabled */ + oldval = r_dtr(pprt) | scan_mask_o; + /* activate all keyboard outputs (active low) */ + w_dtr(pprt, oldval & ~scan_mask_o); + + /* will have a 1 for each bit set to gnd */ + bitmask = PNL_PINPUT(r_str(pprt)) & scan_mask_i; + /* disable all matrix signals */ + w_dtr(pprt, oldval); + + /* now that all outputs are cleared, the only active input bits are + * directly connected to the ground + */ + + /* 1 for each grounded input */ + gndmask = PNL_PINPUT(r_str(pprt)) & scan_mask_i; + + /* grounded inputs are signals 40-44 */ + phys_read |= (__u64)gndmask << 40; + + if (bitmask != gndmask) { + /* + * since clearing the outputs changed some inputs, we know + * that some input signals are currently tied to some outputs. + * So we'll scan them. + */ + for (bit = 0; bit < 8; bit++) { + bitval = BIT(bit); + + if (!(scan_mask_o & bitval)) /* skip unused bits */ + continue; + + w_dtr(pprt, oldval & ~bitval); /* enable this output */ + bitmask = PNL_PINPUT(r_str(pprt)) & ~gndmask; + phys_read |= (__u64)bitmask << (5 * bit); + } + w_dtr(pprt, oldval); /* disable all outputs */ + } + /* + * this is easy: use old bits when they are flapping, + * use new ones when stable + */ + phys_curr = (phys_prev & (phys_read ^ phys_read_prev)) | + (phys_read & ~(phys_read ^ phys_read_prev)); +} + +static inline int input_state_high(struct logical_input *input) +{ +#if 0 + /* FIXME: + * this is an invalid test. It tries to catch + * transitions from single-key to multiple-key, but + * doesn't take into account the contacts polarity. + * The only solution to the problem is to parse keys + * from the most complex to the simplest combinations, + * and mark them as 'caught' once a combination + * matches, then unmatch it for all other ones. + */ + + /* try to catch dangerous transitions cases : + * someone adds a bit, so this signal was a false + * positive resulting from a transition. We should + * invalidate the signal immediately and not call the + * release function. + * eg: 0 -(press A)-> A -(press B)-> AB : don't match A's release. + */ + if (((phys_prev & input->mask) == input->value) && + ((phys_curr & input->mask) > input->value)) { + input->state = INPUT_ST_LOW; /* invalidate */ + return 1; + } +#endif + + if ((phys_curr & input->mask) == input->value) { + if ((input->type == INPUT_TYPE_STD) && + (input->high_timer == 0)) { + input->high_timer++; + if (input->u.std.press_fct) + input->u.std.press_fct(input->u.std.press_data); + } else if (input->type == INPUT_TYPE_KBD) { + /* will turn on the light */ + keypressed = 1; + + if (input->high_timer == 0) { + char *press_str = input->u.kbd.press_str; + + if (press_str[0]) { + int s = sizeof(input->u.kbd.press_str); + + keypad_send_key(press_str, s); + } + } + + if (input->u.kbd.repeat_str[0]) { + char *repeat_str = input->u.kbd.repeat_str; + + if (input->high_timer >= KEYPAD_REP_START) { + int s = sizeof(input->u.kbd.repeat_str); + + input->high_timer -= KEYPAD_REP_DELAY; + keypad_send_key(repeat_str, s); + } + /* we will need to come back here soon */ + inputs_stable = 0; + } + + if (input->high_timer < 255) + input->high_timer++; + } + return 1; + } + + /* else signal falling down. Let's fall through. */ + input->state = INPUT_ST_FALLING; + input->fall_timer = 0; + + return 0; +} + +static inline void input_state_falling(struct logical_input *input) +{ +#if 0 + /* FIXME !!! same comment as in input_state_high */ + if (((phys_prev & input->mask) == input->value) && + ((phys_curr & input->mask) > input->value)) { + input->state = INPUT_ST_LOW; /* invalidate */ + return; + } +#endif + + if ((phys_curr & input->mask) == input->value) { + if (input->type == INPUT_TYPE_KBD) { + /* will turn on the light */ + keypressed = 1; + + if (input->u.kbd.repeat_str[0]) { + char *repeat_str = input->u.kbd.repeat_str; + + if (input->high_timer >= KEYPAD_REP_START) { + int s = sizeof(input->u.kbd.repeat_str); + + input->high_timer -= KEYPAD_REP_DELAY; + keypad_send_key(repeat_str, s); + } + /* we will need to come back here soon */ + inputs_stable = 0; + } + + if (input->high_timer < 255) + input->high_timer++; + } + input->state = INPUT_ST_HIGH; + } else if (input->fall_timer >= input->fall_time) { + /* call release event */ + if (input->type == INPUT_TYPE_STD) { + void (*release_fct)(int) = input->u.std.release_fct; + + if (release_fct) + release_fct(input->u.std.release_data); + } else if (input->type == INPUT_TYPE_KBD) { + char *release_str = input->u.kbd.release_str; + + if (release_str[0]) { + int s = sizeof(input->u.kbd.release_str); + + keypad_send_key(release_str, s); + } + } + + input->state = INPUT_ST_LOW; + } else { + input->fall_timer++; + inputs_stable = 0; + } +} + +static void panel_process_inputs(void) +{ + struct list_head *item; + struct logical_input *input; + + keypressed = 0; + inputs_stable = 1; + list_for_each(item, &logical_inputs) { + input = list_entry(item, struct logical_input, list); + + switch (input->state) { + case INPUT_ST_LOW: + if ((phys_curr & input->mask) != input->value) + break; + /* if all needed ones were already set previously, + * this means that this logical signal has been + * activated by the releasing of another combined + * signal, so we don't want to match. + * eg: AB -(release B)-> A -(release A)-> 0 : + * don't match A. + */ + if ((phys_prev & input->mask) == input->value) + break; + input->rise_timer = 0; + input->state = INPUT_ST_RISING; + /* no break here, fall through */ + case INPUT_ST_RISING: + if ((phys_curr & input->mask) != input->value) { + input->state = INPUT_ST_LOW; + break; + } + if (input->rise_timer < input->rise_time) { + inputs_stable = 0; + input->rise_timer++; + break; + } + input->high_timer = 0; + input->state = INPUT_ST_HIGH; + /* no break here, fall through */ + case INPUT_ST_HIGH: + if (input_state_high(input)) + break; + /* no break here, fall through */ + case INPUT_ST_FALLING: + input_state_falling(input); + } + } +} + +static void panel_scan_timer(void) +{ + if (keypad.enabled && keypad_initialized) { + if (spin_trylock_irq(&pprt_lock)) { + phys_scan_contacts(); + + /* no need for the parport anymore */ + spin_unlock_irq(&pprt_lock); + } + + if (!inputs_stable || phys_curr != phys_prev) + panel_process_inputs(); + } + + if (keypressed && lcd.enabled && lcd.initialized) + charlcd_poke(lcd.charlcd); + + mod_timer(&scan_timer, jiffies + INPUT_POLL_TIME); +} + +static void init_scan_timer(void) +{ + if (scan_timer.function) + return; /* already started */ + + setup_timer(&scan_timer, (void *)&panel_scan_timer, 0); + scan_timer.expires = jiffies + INPUT_POLL_TIME; + add_timer(&scan_timer); +} + +/* converts a name of the form "({BbAaPpSsEe}{01234567-})*" to a series of bits. + * if <omask> or <imask> are non-null, they will be or'ed with the bits + * corresponding to out and in bits respectively. + * returns 1 if ok, 0 if error (in which case, nothing is written). + */ +static u8 input_name2mask(const char *name, __u64 *mask, __u64 *value, + u8 *imask, u8 *omask) +{ + const char sigtab[] = "EeSsPpAaBb"; + u8 im, om; + __u64 m, v; + + om = 0; + im = 0; + m = 0ULL; + v = 0ULL; + while (*name) { + int in, out, bit, neg; + const char *idx; + + idx = strchr(sigtab, *name); + if (!idx) + return 0; /* input name not found */ + + in = idx - sigtab; + neg = (in & 1); /* odd (lower) names are negated */ + in >>= 1; + im |= BIT(in); + + name++; + if (*name >= '0' && *name <= '7') { + out = *name - '0'; + om |= BIT(out); + } else if (*name == '-') { + out = 8; + } else { + return 0; /* unknown bit name */ + } + + bit = (out * 5) + in; + + m |= 1ULL << bit; + if (!neg) + v |= 1ULL << bit; + name++; + } + *mask = m; + *value = v; + if (imask) + *imask |= im; + if (omask) + *omask |= om; + return 1; +} + +/* tries to bind a key to the signal name <name>. The key will send the + * strings <press>, <repeat>, <release> for these respective events. + * Returns the pointer to the new key if ok, NULL if the key could not be bound. + */ +static struct logical_input *panel_bind_key(const char *name, const char *press, + const char *repeat, + const char *release) +{ + struct logical_input *key; + + key = kzalloc(sizeof(*key), GFP_KERNEL); + if (!key) + return NULL; + + if (!input_name2mask(name, &key->mask, &key->value, &scan_mask_i, + &scan_mask_o)) { + kfree(key); + return NULL; + } + + key->type = INPUT_TYPE_KBD; + key->state = INPUT_ST_LOW; + key->rise_time = 1; + key->fall_time = 1; + + strncpy(key->u.kbd.press_str, press, sizeof(key->u.kbd.press_str)); + strncpy(key->u.kbd.repeat_str, repeat, sizeof(key->u.kbd.repeat_str)); + strncpy(key->u.kbd.release_str, release, + sizeof(key->u.kbd.release_str)); + list_add(&key->list, &logical_inputs); + return key; +} + +#if 0 +/* tries to bind a callback function to the signal name <name>. The function + * <press_fct> will be called with the <press_data> arg when the signal is + * activated, and so on for <release_fct>/<release_data> + * Returns the pointer to the new signal if ok, NULL if the signal could not + * be bound. + */ +static struct logical_input *panel_bind_callback(char *name, + void (*press_fct)(int), + int press_data, + void (*release_fct)(int), + int release_data) +{ + struct logical_input *callback; + + callback = kmalloc(sizeof(*callback), GFP_KERNEL); + if (!callback) + return NULL; + + memset(callback, 0, sizeof(struct logical_input)); + if (!input_name2mask(name, &callback->mask, &callback->value, + &scan_mask_i, &scan_mask_o)) + return NULL; + + callback->type = INPUT_TYPE_STD; + callback->state = INPUT_ST_LOW; + callback->rise_time = 1; + callback->fall_time = 1; + callback->u.std.press_fct = press_fct; + callback->u.std.press_data = press_data; + callback->u.std.release_fct = release_fct; + callback->u.std.release_data = release_data; + list_add(&callback->list, &logical_inputs); + return callback; +} +#endif + +static void keypad_init(void) +{ + int keynum; + + init_waitqueue_head(&keypad_read_wait); + keypad_buflen = 0; /* flushes any eventual noisy keystroke */ + + /* Let's create all known keys */ + + for (keynum = 0; keypad_profile[keynum][0][0]; keynum++) { + panel_bind_key(keypad_profile[keynum][0], + keypad_profile[keynum][1], + keypad_profile[keynum][2], + keypad_profile[keynum][3]); + } + + init_scan_timer(); + keypad_initialized = 1; +} + +/**************************************************/ +/* device initialization */ +/**************************************************/ + +static void panel_attach(struct parport *port) +{ + struct pardev_cb panel_cb; + + if (port->number != parport) + return; + + if (pprt) { + pr_err("%s: port->number=%d parport=%d, already registered!\n", + __func__, port->number, parport); + return; + } + + memset(&panel_cb, 0, sizeof(panel_cb)); + panel_cb.private = &pprt; + /* panel_cb.flags = 0 should be PARPORT_DEV_EXCL? */ + + pprt = parport_register_dev_model(port, "panel", &panel_cb, 0); + if (!pprt) { + pr_err("%s: port->number=%d parport=%d, parport_register_device() failed\n", + __func__, port->number, parport); + return; + } + + if (parport_claim(pprt)) { + pr_err("could not claim access to parport%d. Aborting.\n", + parport); + goto err_unreg_device; + } + + /* must init LCD first, just in case an IRQ from the keypad is + * generated at keypad init + */ + if (lcd.enabled) { + lcd_init(); + if (!lcd.charlcd || charlcd_register(lcd.charlcd)) + goto err_unreg_device; + } + + if (keypad.enabled) { + keypad_init(); + if (misc_register(&keypad_dev)) + goto err_lcd_unreg; + } + return; + +err_lcd_unreg: + if (lcd.enabled) + charlcd_unregister(lcd.charlcd); +err_unreg_device: + kfree(lcd.charlcd); + lcd.charlcd = NULL; + parport_unregister_device(pprt); + pprt = NULL; +} + +static void panel_detach(struct parport *port) +{ + if (port->number != parport) + return; + + if (!pprt) { + pr_err("%s: port->number=%d parport=%d, nothing to unregister.\n", + __func__, port->number, parport); + return; + } + if (scan_timer.function) + del_timer_sync(&scan_timer); + + if (keypad.enabled) { + misc_deregister(&keypad_dev); + keypad_initialized = 0; + } + + if (lcd.enabled) { + charlcd_unregister(lcd.charlcd); + lcd.initialized = false; + kfree(lcd.charlcd); + lcd.charlcd = NULL; + } + + /* TODO: free all input signals */ + parport_release(pprt); + parport_unregister_device(pprt); + pprt = NULL; +} + +static struct parport_driver panel_driver = { + .name = "panel", + .match_port = panel_attach, + .detach = panel_detach, + .devmodel = true, +}; + +/* init function */ +static int __init panel_init_module(void) +{ + int selected_keypad_type = NOT_SET, err; + + /* take care of an eventual profile */ + switch (profile) { + case PANEL_PROFILE_CUSTOM: + /* custom profile */ + selected_keypad_type = DEFAULT_KEYPAD_TYPE; + selected_lcd_type = DEFAULT_LCD_TYPE; + break; + case PANEL_PROFILE_OLD: + /* 8 bits, 2*16, old keypad */ + selected_keypad_type = KEYPAD_TYPE_OLD; + selected_lcd_type = LCD_TYPE_OLD; + + /* TODO: This two are a little hacky, sort it out later */ + if (lcd_width == NOT_SET) + lcd_width = 16; + if (lcd_hwidth == NOT_SET) + lcd_hwidth = 16; + break; + case PANEL_PROFILE_NEW: + /* serial, 2*16, new keypad */ + selected_keypad_type = KEYPAD_TYPE_NEW; + selected_lcd_type = LCD_TYPE_KS0074; + break; + case PANEL_PROFILE_HANTRONIX: + /* 8 bits, 2*16 hantronix-like, no keypad */ + selected_keypad_type = KEYPAD_TYPE_NONE; + selected_lcd_type = LCD_TYPE_HANTRONIX; + break; + case PANEL_PROFILE_NEXCOM: + /* generic 8 bits, 2*16, nexcom keypad, eg. Nexcom. */ + selected_keypad_type = KEYPAD_TYPE_NEXCOM; + selected_lcd_type = LCD_TYPE_NEXCOM; + break; + case PANEL_PROFILE_LARGE: + /* 8 bits, 2*40, old keypad */ + selected_keypad_type = KEYPAD_TYPE_OLD; + selected_lcd_type = LCD_TYPE_OLD; + break; + } + + /* + * Overwrite selection with module param values (both keypad and lcd), + * where the deprecated params have lower prio. + */ + if (keypad_enabled != NOT_SET) + selected_keypad_type = keypad_enabled; + if (keypad_type != NOT_SET) + selected_keypad_type = keypad_type; + + keypad.enabled = (selected_keypad_type > 0); + + if (lcd_enabled != NOT_SET) + selected_lcd_type = lcd_enabled; + if (lcd_type != NOT_SET) + selected_lcd_type = lcd_type; + + lcd.enabled = (selected_lcd_type > 0); + + if (lcd.enabled) { + /* + * Init lcd struct with load-time values to preserve exact + * current functionality (at least for now). + */ + lcd.charset = lcd_charset; + lcd.proto = lcd_proto; + lcd.pins.e = lcd_e_pin; + lcd.pins.rs = lcd_rs_pin; + lcd.pins.rw = lcd_rw_pin; + lcd.pins.cl = lcd_cl_pin; + lcd.pins.da = lcd_da_pin; + lcd.pins.bl = lcd_bl_pin; + } + + switch (selected_keypad_type) { + case KEYPAD_TYPE_OLD: + keypad_profile = old_keypad_profile; + break; + case KEYPAD_TYPE_NEW: + keypad_profile = new_keypad_profile; + break; + case KEYPAD_TYPE_NEXCOM: + keypad_profile = nexcom_keypad_profile; + break; + default: + keypad_profile = NULL; + break; + } + + if (!lcd.enabled && !keypad.enabled) { + /* no device enabled, let's exit */ + pr_err("panel driver disabled.\n"); + return -ENODEV; + } + + err = parport_register_driver(&panel_driver); + if (err) { + pr_err("could not register with parport. Aborting.\n"); + return err; + } + + if (pprt) + pr_info("panel driver registered on parport%d (io=0x%lx).\n", + parport, pprt->port->base); + else + pr_info("panel driver not yet registered\n"); + return 0; +} + +static void __exit panel_cleanup_module(void) +{ + parport_unregister_driver(&panel_driver); +} + +module_init(panel_init_module); +module_exit(panel_cleanup_module); +MODULE_AUTHOR("Willy Tarreau"); +MODULE_LICENSE("GPL"); + +/* + * Local variables: + * c-indent-level: 4 + * tab-width: 8 + * End: + */ |