diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /drivers/input/serio |
Linux-2.6.12-rc2v2.6.12-rc2
Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.
Let it rip!
Diffstat (limited to 'drivers/input/serio')
26 files changed, 8661 insertions, 0 deletions
diff --git a/drivers/input/serio/Kconfig b/drivers/input/serio/Kconfig new file mode 100644 index 00000000000..b3710733b36 --- /dev/null +++ b/drivers/input/serio/Kconfig @@ -0,0 +1,183 @@ +# +# Input core configuration +# +config SERIO + tristate "Serial I/O support" if EMBEDDED || !X86 + default y + ---help--- + Say Yes here if you have any input device that uses serial I/O to + communicate with the system. This includes the + * standard AT keyboard and PS/2 mouse * + as well as serial mice, Sun keyboards, some joysticks and 6dof + devices and more. + + If unsure, say Y. + + To compile this driver as a module, choose M here: the + module will be called serio. + +if SERIO + +config SERIO_I8042 + tristate "i8042 PC Keyboard controller" if EMBEDDED || !X86 + default y + depends on !PARISC && (!ARM || ARCH_SHARK || FOOTBRIDGE_HOST) && !M68K + ---help--- + i8042 is the chip over which the standard AT keyboard and PS/2 + mouse are connected to the computer. If you use these devices, + you'll need to say Y here. + + If unsure, say Y. + + To compile this driver as a module, choose M here: the + module will be called i8042. + +config SERIO_SERPORT + tristate "Serial port line discipline" + default y + ---help--- + Say Y here if you plan to use an input device (mouse, joystick, + tablet, 6dof) that communicates over the RS232 serial (COM) port. + + More information is available: <file:Documentation/input/input.txt> + + If unsure, say Y. + + To compile this driver as a module, choose M here: the + module will be called serport. + +config SERIO_CT82C710 + tristate "ct82c710 Aux port controller" + depends on X86 + ---help--- + Say Y here if you have a Texas Instruments TravelMate notebook + equipped with the ct82c710 chip and want to use a mouse connected + to the "QuickPort". + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called ct82c710. + +config SERIO_Q40KBD + tristate "Q40 keyboard controller" + depends on Q40 + +config SERIO_PARKBD + tristate "Parallel port keyboard adapter" + depends on PARPORT + ---help--- + Say Y here if you built a simple parallel port adapter to attach + an additional AT keyboard, XT keyboard or PS/2 mouse. + + More information is available: <file:Documentation/input/input.txt> + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called parkbd. + +config SERIO_RPCKBD + tristate "Acorn RiscPC keyboard controller" + depends on ARCH_ACORN || ARCH_CLPS7500 + default y + help + Say Y here if you have the Acorn RiscPC and want to use an AT + keyboard connected to its keyboard controller. + + To compile this driver as a module, choose M here: the + module will be called rpckbd. + +config SERIO_AMBAKMI + tristate "AMBA KMI keyboard controller" + depends on ARM_AMBA + +config SERIO_SA1111 + tristate "Intel SA1111 keyboard controller" + depends on SA1111 + +config SERIO_GSCPS2 + tristate "HP GSC PS/2 keyboard and PS/2 mouse controller" + depends on GSC + default y + help + This driver provides support for the PS/2 ports on PA-RISC machines + over which HP PS/2 keyboards and PS/2 mice may be connected. + If you use these devices, you'll need to say Y here. + + It's safe to enable this driver, so if unsure, say Y. + + To compile this driver as a module, choose M here: the + module will be called gscps2. + +config HP_SDC + tristate "HP System Device Controller i8042 Support" + depends on GSC && SERIO + default y + ---help--- + This option enables supports for the the "System Device + Controller", an i8042 carrying microcode to manage a + few miscellanous devices on some Hewlett Packard systems. + The SDC itself contains a 10ms resolution timer/clock capable + of delivering interrupts on a periodic and one-shot basis. + The SDC may also be connected to a battery-backed real-time + clock, a basic audio waveform generator, and an HP-HIL Master + Link Controller serving up to seven input devices. + + By itself this option is rather useless, but enabling it will + enable selection of drivers for the abovementioned devices. + It is, however, incompatible with the old, reliable HIL keyboard + driver, and the new HIL driver is experimental, so if you plan + to use a HIL keyboard as your primary keyboard, you may wish + to keep using that driver until the new HIL drivers have had + more testing. + +config HIL_MLC + tristate "HIL MLC Support (needed for HIL input devices)" + depends on HP_SDC + +config SERIO_PCIPS2 + tristate "PCI PS/2 keyboard and PS/2 mouse controller" + depends on PCI + help + Say Y here if you have a Mobility Docking station with PS/2 + keyboard and mice ports. + + To compile this driver as a module, choose M here: the + module will be called pcips2. + +config SERIO_MACEPS2 + tristate "SGI O2 MACE PS/2 controller" + depends on SGI_IP32 + help + Say Y here if you have SGI O2 workstation and want to use its + PS/2 ports. + + To compile this driver as a module, choose M here: the + module will be called maceps2. + +config SERIO_LIBPS2 + tristate "PS/2 driver library" if EMBEDDED + help + Say Y here if you are using a driver for device connected + to a PS/2 port, such as PS/2 mouse or standard AT keyboard. + + To compile this driver as a module, choose M here: the + module will be called libps2. + +config SERIO_RAW + tristate "Raw access to serio ports" + help + Say Y here if you want to have raw access to serio ports, such as + AUX ports on i8042 keyboard controller. Each serio port that is + bound to this driver will be accessible via a char device with + major 10 and dynamically allocated minor. The driver will try + allocating minor 1 (that historically corresponds to /dev/psaux) + first. To bind this driver to a serio port use sysfs interface: + + echo -n "serio_raw" > /sys/bus/serio/devices/serioX/driver + + To compile this driver as a module, choose M here: the + module will be called serio_raw. + +endif diff --git a/drivers/input/serio/Makefile b/drivers/input/serio/Makefile new file mode 100644 index 00000000000..678a8599f9f --- /dev/null +++ b/drivers/input/serio/Makefile @@ -0,0 +1,23 @@ +# +# Makefile for the input core drivers. +# + +# Each configuration option enables a list of files. + +obj-$(CONFIG_SERIO) += serio.o +obj-$(CONFIG_SERIO_I8042) += i8042.o +obj-$(CONFIG_SERIO_PARKBD) += parkbd.o +obj-$(CONFIG_SERIO_SERPORT) += serport.o +obj-$(CONFIG_SERIO_CT82C710) += ct82c710.o +obj-$(CONFIG_SERIO_RPCKBD) += rpckbd.o +obj-$(CONFIG_SERIO_SA1111) += sa1111ps2.o +obj-$(CONFIG_SERIO_AMBAKMI) += ambakmi.o +obj-$(CONFIG_SERIO_Q40KBD) += q40kbd.o +obj-$(CONFIG_SERIO_98KBD) += 98kbd-io.o +obj-$(CONFIG_SERIO_GSCPS2) += gscps2.o +obj-$(CONFIG_HP_SDC) += hp_sdc.o +obj-$(CONFIG_HIL_MLC) += hp_sdc_mlc.o hil_mlc.o +obj-$(CONFIG_SERIO_PCIPS2) += pcips2.o +obj-$(CONFIG_SERIO_MACEPS2) += maceps2.o +obj-$(CONFIG_SERIO_LIBPS2) += libps2.o +obj-$(CONFIG_SERIO_RAW) += serio_raw.o diff --git a/drivers/input/serio/ambakmi.c b/drivers/input/serio/ambakmi.c new file mode 100644 index 00000000000..9b1ab5e7a98 --- /dev/null +++ b/drivers/input/serio/ambakmi.c @@ -0,0 +1,231 @@ +/* + * linux/drivers/input/serio/ambakmi.c + * + * Copyright (C) 2000-2003 Deep Blue Solutions Ltd. + * Copyright (C) 2002 Russell King. + * + * 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/module.h> +#include <linux/init.h> +#include <linux/serio.h> +#include <linux/errno.h> +#include <linux/interrupt.h> +#include <linux/ioport.h> +#include <linux/device.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/err.h> + +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/hardware/amba.h> +#include <asm/hardware/amba_kmi.h> +#include <asm/hardware/clock.h> + +#define KMI_BASE (kmi->base) + +struct amba_kmi_port { + struct serio *io; + struct clk *clk; + void __iomem *base; + unsigned int irq; + unsigned int divisor; + unsigned int open; +}; + +static irqreturn_t amba_kmi_int(int irq, void *dev_id, struct pt_regs *regs) +{ + struct amba_kmi_port *kmi = dev_id; + unsigned int status = readb(KMIIR); + int handled = IRQ_NONE; + + while (status & KMIIR_RXINTR) { + serio_interrupt(kmi->io, readb(KMIDATA), 0, regs); + status = readb(KMIIR); + handled = IRQ_HANDLED; + } + + return handled; +} + +static int amba_kmi_write(struct serio *io, unsigned char val) +{ + struct amba_kmi_port *kmi = io->port_data; + unsigned int timeleft = 10000; /* timeout in 100ms */ + + while ((readb(KMISTAT) & KMISTAT_TXEMPTY) == 0 && timeleft--) + udelay(10); + + if (timeleft) + writeb(val, KMIDATA); + + return timeleft ? 0 : SERIO_TIMEOUT; +} + +static int amba_kmi_open(struct serio *io) +{ + struct amba_kmi_port *kmi = io->port_data; + unsigned int divisor; + int ret; + + ret = clk_use(kmi->clk); + if (ret) + goto out; + + ret = clk_enable(kmi->clk); + if (ret) + goto clk_unuse; + + divisor = clk_get_rate(kmi->clk) / 8000000 - 1; + writeb(divisor, KMICLKDIV); + writeb(KMICR_EN, KMICR); + + ret = request_irq(kmi->irq, amba_kmi_int, 0, "kmi-pl050", kmi); + if (ret) { + printk(KERN_ERR "kmi: failed to claim IRQ%d\n", kmi->irq); + writeb(0, KMICR); + goto clk_disable; + } + + writeb(KMICR_EN | KMICR_RXINTREN, KMICR); + + return 0; + + clk_disable: + clk_disable(kmi->clk); + clk_unuse: + clk_unuse(kmi->clk); + out: + return ret; +} + +static void amba_kmi_close(struct serio *io) +{ + struct amba_kmi_port *kmi = io->port_data; + + writeb(0, KMICR); + + free_irq(kmi->irq, kmi); + clk_disable(kmi->clk); + clk_unuse(kmi->clk); +} + +static int amba_kmi_probe(struct amba_device *dev, void *id) +{ + struct amba_kmi_port *kmi; + struct serio *io; + int ret; + + ret = amba_request_regions(dev, NULL); + if (ret) + return ret; + + kmi = kmalloc(sizeof(struct amba_kmi_port), GFP_KERNEL); + io = kmalloc(sizeof(struct serio), GFP_KERNEL); + if (!kmi || !io) { + ret = -ENOMEM; + goto out; + } + + memset(kmi, 0, sizeof(struct amba_kmi_port)); + memset(io, 0, sizeof(struct serio)); + + io->id.type = SERIO_8042; + io->write = amba_kmi_write; + io->open = amba_kmi_open; + io->close = amba_kmi_close; + strlcpy(io->name, dev->dev.bus_id, sizeof(io->name)); + strlcpy(io->phys, dev->dev.bus_id, sizeof(io->phys)); + io->port_data = kmi; + io->dev.parent = &dev->dev; + + kmi->io = io; + kmi->base = ioremap(dev->res.start, KMI_SIZE); + if (!kmi->base) { + ret = -ENOMEM; + goto out; + } + + kmi->clk = clk_get(&dev->dev, "KMIREFCLK"); + if (IS_ERR(kmi->clk)) { + ret = PTR_ERR(kmi->clk); + goto unmap; + } + + kmi->irq = dev->irq[0]; + amba_set_drvdata(dev, kmi); + + serio_register_port(kmi->io); + return 0; + + unmap: + iounmap(kmi->base); + out: + kfree(kmi); + kfree(io); + amba_release_regions(dev); + return ret; +} + +static int amba_kmi_remove(struct amba_device *dev) +{ + struct amba_kmi_port *kmi = amba_get_drvdata(dev); + + amba_set_drvdata(dev, NULL); + + serio_unregister_port(kmi->io); + clk_put(kmi->clk); + iounmap(kmi->base); + kfree(kmi); + amba_release_regions(dev); + return 0; +} + +static int amba_kmi_resume(struct amba_device *dev) +{ + struct amba_kmi_port *kmi = amba_get_drvdata(dev); + + /* kick the serio layer to rescan this port */ + serio_reconnect(kmi->io); + + return 0; +} + +static struct amba_id amba_kmi_idtable[] = { + { + .id = 0x00041050, + .mask = 0x000fffff, + }, + { 0, 0 } +}; + +static struct amba_driver ambakmi_driver = { + .drv = { + .name = "kmi-pl050", + }, + .id_table = amba_kmi_idtable, + .probe = amba_kmi_probe, + .remove = amba_kmi_remove, + .resume = amba_kmi_resume, +}; + +static int __init amba_kmi_init(void) +{ + return amba_driver_register(&ambakmi_driver); +} + +static void __exit amba_kmi_exit(void) +{ + amba_driver_unregister(&ambakmi_driver); +} + +module_init(amba_kmi_init); +module_exit(amba_kmi_exit); + +MODULE_AUTHOR("Russell King <rmk@arm.linux.org.uk>"); +MODULE_DESCRIPTION("AMBA KMI controller driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/serio/ct82c710.c b/drivers/input/serio/ct82c710.c new file mode 100644 index 00000000000..dd0f5bd9024 --- /dev/null +++ b/drivers/input/serio/ct82c710.c @@ -0,0 +1,225 @@ +/* + * $Id: ct82c710.c,v 1.11 2001/09/25 10:12:07 vojtech Exp $ + * + * Copyright (c) 1999-2001 Vojtech Pavlik + */ + +/* + * 82C710 C&T mouse port chip driver for Linux + */ + +/* + * 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 program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Should you need to contact me, the author, you can do so either by + * e-mail - mail your message to <vojtech@ucw.cz>, or by paper mail: + * Vojtech Pavlik, Simunkova 1594, Prague 8, 182 00 Czech Republic + */ + +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/ioport.h> +#include <linux/config.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/serio.h> +#include <linux/errno.h> +#include <linux/err.h> + +#include <asm/io.h> + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION("82C710 C&T mouse port chip driver"); +MODULE_LICENSE("GPL"); + +/* + * ct82c710 interface + */ + +#define CT82C710_DEV_IDLE 0x01 /* Device Idle */ +#define CT82C710_RX_FULL 0x02 /* Device Char received */ +#define CT82C710_TX_IDLE 0x04 /* Device XMIT Idle */ +#define CT82C710_RESET 0x08 /* Device Reset */ +#define CT82C710_INTS_ON 0x10 /* Device Interrupt On */ +#define CT82C710_ERROR_FLAG 0x20 /* Device Error */ +#define CT82C710_CLEAR 0x40 /* Device Clear */ +#define CT82C710_ENABLE 0x80 /* Device Enable */ + +#define CT82C710_IRQ 12 + +#define CT82C710_DATA ct82c710_iores.start +#define CT82C710_STATUS (ct82c710_iores.start + 1) + +static struct serio *ct82c710_port; +static struct platform_device *ct82c710_device; +static struct resource ct82c710_iores; + +/* + * Interrupt handler for the 82C710 mouse port. A character + * is waiting in the 82C710. + */ + +static irqreturn_t ct82c710_interrupt(int cpl, void *dev_id, struct pt_regs * regs) +{ + return serio_interrupt(ct82c710_port, inb(CT82C710_DATA), 0, regs); +} + +/* + * Wait for device to send output char and flush any input char. + */ + +static int ct82c170_wait(void) +{ + int timeout = 60000; + + while ((inb(CT82C710_STATUS) & (CT82C710_RX_FULL | CT82C710_TX_IDLE | CT82C710_DEV_IDLE)) + != (CT82C710_DEV_IDLE | CT82C710_TX_IDLE) && timeout) { + + if (inb_p(CT82C710_STATUS) & CT82C710_RX_FULL) inb_p(CT82C710_DATA); + + udelay(1); + timeout--; + } + + return !timeout; +} + +static void ct82c710_close(struct serio *serio) +{ + if (ct82c170_wait()) + printk(KERN_WARNING "ct82c710.c: Device busy in close()\n"); + + outb_p(inb_p(CT82C710_STATUS) & ~(CT82C710_ENABLE | CT82C710_INTS_ON), CT82C710_STATUS); + + if (ct82c170_wait()) + printk(KERN_WARNING "ct82c710.c: Device busy in close()\n"); + + free_irq(CT82C710_IRQ, NULL); +} + +static int ct82c710_open(struct serio *serio) +{ + unsigned char status; + + if (request_irq(CT82C710_IRQ, ct82c710_interrupt, 0, "ct82c710", NULL)) + return -1; + + status = inb_p(CT82C710_STATUS); + + status |= (CT82C710_ENABLE | CT82C710_RESET); + outb_p(status, CT82C710_STATUS); + + status &= ~(CT82C710_RESET); + outb_p(status, CT82C710_STATUS); + + status |= CT82C710_INTS_ON; + outb_p(status, CT82C710_STATUS); /* Enable interrupts */ + + while (ct82c170_wait()) { + printk(KERN_ERR "ct82c710: Device busy in open()\n"); + status &= ~(CT82C710_ENABLE | CT82C710_INTS_ON); + outb_p(status, CT82C710_STATUS); + free_irq(CT82C710_IRQ, NULL); + return -1; + } + + return 0; +} + +/* + * Write to the 82C710 mouse device. + */ + +static int ct82c710_write(struct serio *port, unsigned char c) +{ + if (ct82c170_wait()) return -1; + outb_p(c, CT82C710_DATA); + return 0; +} + +/* + * See if we can find a 82C710 device. Read mouse address. + */ + +static int __init ct82c710_probe(void) +{ + outb_p(0x55, 0x2fa); /* Any value except 9, ff or 36 */ + outb_p(0xaa, 0x3fa); /* Inverse of 55 */ + outb_p(0x36, 0x3fa); /* Address the chip */ + outb_p(0xe4, 0x3fa); /* 390/4; 390 = config address */ + outb_p(0x1b, 0x2fa); /* Inverse of e4 */ + outb_p(0x0f, 0x390); /* Write index */ + if (inb_p(0x391) != 0xe4) /* Config address found? */ + return -1; /* No: no 82C710 here */ + + outb_p(0x0d, 0x390); /* Write index */ + ct82c710_iores.start = inb_p(0x391) << 2; /* Get mouse I/O address */ + ct82c710_iores.end = ct82c710_iores.start + 1; + ct82c710_iores.flags = IORESOURCE_IO; + outb_p(0x0f, 0x390); + outb_p(0x0f, 0x391); /* Close config mode */ + + return 0; +} + +static struct serio * __init ct82c710_allocate_port(void) +{ + struct serio *serio; + + serio = kmalloc(sizeof(struct serio), GFP_KERNEL); + if (serio) { + memset(serio, 0, sizeof(struct serio)); + serio->id.type = SERIO_8042; + serio->open = ct82c710_open; + serio->close = ct82c710_close; + serio->write = ct82c710_write; + serio->dev.parent = &ct82c710_device->dev; + strlcpy(serio->name, "C&T 82c710 mouse port", sizeof(serio->name)); + snprintf(serio->phys, sizeof(serio->phys), "isa%04lx/serio0", CT82C710_DATA); + } + + return serio; +} + +static int __init ct82c710_init(void) +{ + if (ct82c710_probe()) + return -ENODEV; + + ct82c710_device = platform_device_register_simple("ct82c710", -1, &ct82c710_iores, 1); + if (IS_ERR(ct82c710_device)) + return PTR_ERR(ct82c710_device); + + if (!(ct82c710_port = ct82c710_allocate_port())) { + platform_device_unregister(ct82c710_device); + return -ENOMEM; + } + + serio_register_port(ct82c710_port); + + printk(KERN_INFO "serio: C&T 82c710 mouse port at %#lx irq %d\n", + CT82C710_DATA, CT82C710_IRQ); + + return 0; +} + +static void __exit ct82c710_exit(void) +{ + serio_unregister_port(ct82c710_port); + platform_device_unregister(ct82c710_device); +} + +module_init(ct82c710_init); +module_exit(ct82c710_exit); diff --git a/drivers/input/serio/gscps2.c b/drivers/input/serio/gscps2.c new file mode 100644 index 00000000000..897e4c12b64 --- /dev/null +++ b/drivers/input/serio/gscps2.c @@ -0,0 +1,467 @@ +/* + * drivers/input/serio/gscps2.c + * + * Copyright (c) 2004 Helge Deller <deller@gmx.de> + * Copyright (c) 2002 Laurent Canet <canetl@esiee.fr> + * Copyright (c) 2002 Thibaut Varene <varenet@parisc-linux.org> + * + * Pieces of code based on linux-2.4's hp_mouse.c & hp_keyb.c + * Copyright (c) 1999 Alex deVries <alex@onefishtwo.ca> + * Copyright (c) 1999-2000 Philipp Rumpf <prumpf@tux.org> + * Copyright (c) 2000 Xavier Debacker <debackex@esiee.fr> + * Copyright (c) 2000-2001 Thomas Marteau <marteaut@esiee.fr> + * + * HP GSC PS/2 port driver, found in PA/RISC Workstations + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + * + * TODO: + * - Dino testing (did HP ever shipped a machine on which this port + * was usable/enabled ?) + */ + +#include <linux/config.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/serio.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/spinlock.h> +#include <linux/delay.h> +#include <linux/ioport.h> +#include <linux/pci_ids.h> + +#include <asm/irq.h> +#include <asm/io.h> +#include <asm/parisc-device.h> + +MODULE_AUTHOR("Laurent Canet <canetl@esiee.fr>, Thibaut Varene <varenet@parisc-linux.org>, Helge Deller <deller@gmx.de>"); +MODULE_DESCRIPTION("HP GSC PS2 port driver"); +MODULE_LICENSE("GPL"); +MODULE_DEVICE_TABLE(parisc, gscps2_device_tbl); + +#define PFX "gscps2.c: " + +/* + * Driver constants + */ + +/* various constants */ +#define ENABLE 1 +#define DISABLE 0 + +#define GSC_DINO_OFFSET 0x0800 /* offset for DINO controller versus LASI one */ + +/* PS/2 IO port offsets */ +#define GSC_ID 0x00 /* device ID offset (see: GSC_ID_XXX) */ +#define GSC_RESET 0x00 /* reset port offset */ +#define GSC_RCVDATA 0x04 /* receive port offset */ +#define GSC_XMTDATA 0x04 /* transmit port offset */ +#define GSC_CONTROL 0x08 /* see: Control register bits */ +#define GSC_STATUS 0x0C /* see: Status register bits */ + +/* Control register bits */ +#define GSC_CTRL_ENBL 0x01 /* enable interface */ +#define GSC_CTRL_LPBXR 0x02 /* loopback operation */ +#define GSC_CTRL_DIAG 0x20 /* directly control clock/data line */ +#define GSC_CTRL_DATDIR 0x40 /* data line direct control */ +#define GSC_CTRL_CLKDIR 0x80 /* clock line direct control */ + +/* Status register bits */ +#define GSC_STAT_RBNE 0x01 /* Receive Buffer Not Empty */ +#define GSC_STAT_TBNE 0x02 /* Transmit Buffer Not Empty */ +#define GSC_STAT_TERR 0x04 /* Timeout Error */ +#define GSC_STAT_PERR 0x08 /* Parity Error */ +#define GSC_STAT_CMPINTR 0x10 /* Composite Interrupt = irq on any port */ +#define GSC_STAT_DATSHD 0x40 /* Data Line Shadow */ +#define GSC_STAT_CLKSHD 0x80 /* Clock Line Shadow */ + +/* IDs returned by GSC_ID port register */ +#define GSC_ID_KEYBOARD 0 /* device ID values */ +#define GSC_ID_MOUSE 1 + + +static irqreturn_t gscps2_interrupt(int irq, void *dev, struct pt_regs *regs); + +#define BUFFER_SIZE 0x0f + +/* GSC PS/2 port device struct */ +struct gscps2port { + struct list_head node; + struct parisc_device *padev; + struct serio *port; + spinlock_t lock; + char *addr; + u8 act, append; /* position in buffer[] */ + struct { + u8 data; + u8 str; + } buffer[BUFFER_SIZE+1]; + int id; +}; + +/* + * Various HW level routines + */ + +#define gscps2_readb_input(x) readb((x)+GSC_RCVDATA) +#define gscps2_readb_control(x) readb((x)+GSC_CONTROL) +#define gscps2_readb_status(x) readb((x)+GSC_STATUS) +#define gscps2_writeb_control(x, y) writeb((x), (y)+GSC_CONTROL) + + +/* + * wait_TBE() - wait for Transmit Buffer Empty + */ + +static int wait_TBE(char *addr) +{ + int timeout = 25000; /* device is expected to react within 250 msec */ + while (gscps2_readb_status(addr) & GSC_STAT_TBNE) { + if (!--timeout) + return 0; /* This should not happen */ + udelay(10); + } + return 1; +} + + +/* + * gscps2_flush() - flush the receive buffer + */ + +static void gscps2_flush(struct gscps2port *ps2port) +{ + while (gscps2_readb_status(ps2port->addr) & GSC_STAT_RBNE) + gscps2_readb_input(ps2port->addr); + ps2port->act = ps2port->append = 0; +} + +/* + * gscps2_writeb_output() - write a byte to the port + * + * returns 1 on sucess, 0 on error + */ + +static inline int gscps2_writeb_output(struct gscps2port *ps2port, u8 data) +{ + unsigned long flags; + char *addr = ps2port->addr; + + if (!wait_TBE(addr)) { + printk(KERN_DEBUG PFX "timeout - could not write byte %#x\n", data); + return 0; + } + + while (gscps2_readb_status(ps2port->addr) & GSC_STAT_RBNE) + /* wait */; + + spin_lock_irqsave(&ps2port->lock, flags); + writeb(data, addr+GSC_XMTDATA); + spin_unlock_irqrestore(&ps2port->lock, flags); + + /* this is ugly, but due to timing of the port it seems to be necessary. */ + mdelay(6); + + /* make sure any received data is returned as fast as possible */ + /* this is important e.g. when we set the LEDs on the keyboard */ + gscps2_interrupt(0, NULL, NULL); + + return 1; +} + + +/* + * gscps2_enable() - enables or disables the port + */ + +static void gscps2_enable(struct gscps2port *ps2port, int enable) +{ + unsigned long flags; + u8 data; + + /* now enable/disable the port */ + spin_lock_irqsave(&ps2port->lock, flags); + gscps2_flush(ps2port); + data = gscps2_readb_control(ps2port->addr); + if (enable) + data |= GSC_CTRL_ENBL; + else + data &= ~GSC_CTRL_ENBL; + gscps2_writeb_control(data, ps2port->addr); + spin_unlock_irqrestore(&ps2port->lock, flags); + wait_TBE(ps2port->addr); + gscps2_flush(ps2port); +} + +/* + * gscps2_reset() - resets the PS/2 port + */ + +static void gscps2_reset(struct gscps2port *ps2port) +{ + char *addr = ps2port->addr; + unsigned long flags; + + /* reset the interface */ + spin_lock_irqsave(&ps2port->lock, flags); + gscps2_flush(ps2port); + writeb(0xff, addr+GSC_RESET); + gscps2_flush(ps2port); + spin_unlock_irqrestore(&ps2port->lock, flags); + + /* enable it */ + gscps2_enable(ps2port, ENABLE); +} + +static LIST_HEAD(ps2port_list); + +/** + * gscps2_interrupt() - Interruption service routine + * + * This function reads received PS/2 bytes and processes them on + * all interfaces. + * The problematic part here is, that the keyboard and mouse PS/2 port + * share the same interrupt and it's not possible to send data if any + * one of them holds input data. To solve this problem we try to receive + * the data as fast as possible and handle the reporting to the upper layer + * later. + */ + +static irqreturn_t gscps2_interrupt(int irq, void *dev, struct pt_regs *regs) +{ + struct gscps2port *ps2port; + + list_for_each_entry(ps2port, &ps2port_list, node) { + + unsigned long flags; + spin_lock_irqsave(&ps2port->lock, flags); + + while ( (ps2port->buffer[ps2port->append].str = + gscps2_readb_status(ps2port->addr)) & GSC_STAT_RBNE ) { + ps2port->buffer[ps2port->append].data = + gscps2_readb_input(ps2port->addr); + ps2port->append = ((ps2port->append+1) & BUFFER_SIZE); + } + + spin_unlock_irqrestore(&ps2port->lock, flags); + + } /* list_for_each_entry */ + + /* all data was read from the ports - now report the data to upper layer */ + + list_for_each_entry(ps2port, &ps2port_list, node) { + + while (ps2port->act != ps2port->append) { + + unsigned int rxflags; + u8 data, status; + + /* Did new data arrived while we read existing data ? + If yes, exit now and let the new irq handler start over again */ + if (gscps2_readb_status(ps2port->addr) & GSC_STAT_CMPINTR) + return IRQ_HANDLED; + + status = ps2port->buffer[ps2port->act].str; + data = ps2port->buffer[ps2port->act].data; + + ps2port->act = ((ps2port->act+1) & BUFFER_SIZE); + rxflags = ((status & GSC_STAT_TERR) ? SERIO_TIMEOUT : 0 ) | + ((status & GSC_STAT_PERR) ? SERIO_PARITY : 0 ); + + serio_interrupt(ps2port->port, data, rxflags, regs); + + } /* while() */ + + } /* list_for_each_entry */ + + return IRQ_HANDLED; +} + + +/* + * gscps2_write() - send a byte out through the aux interface. + */ + +static int gscps2_write(struct serio *port, unsigned char data) +{ + struct gscps2port *ps2port = port->port_data; + + if (!gscps2_writeb_output(ps2port, data)) { + printk(KERN_DEBUG PFX "sending byte %#x failed.\n", data); + return -1; + } + return 0; +} + +/* + * gscps2_open() is called when a port is opened by the higher layer. + * It resets and enables the port. + */ + +static int gscps2_open(struct serio *port) +{ + struct gscps2port *ps2port = port->port_data; + + gscps2_reset(ps2port); + + gscps2_interrupt(0, NULL, NULL); + + return 0; +} + +/* + * gscps2_close() disables the port + */ + +static void gscps2_close(struct serio *port) +{ + struct gscps2port *ps2port = port->port_data; + gscps2_enable(ps2port, DISABLE); +} + +/** + * gscps2_probe() - Probes PS2 devices + * @return: success/error report + */ + +static int __init gscps2_probe(struct parisc_device *dev) +{ + struct gscps2port *ps2port; + struct serio *serio; + unsigned long hpa = dev->hpa; + int ret; + + if (!dev->irq) + return -ENODEV; + + /* Offset for DINO PS/2. Works with LASI even */ + if (dev->id.sversion == 0x96) + hpa += GSC_DINO_OFFSET; + + ps2port = kmalloc(sizeof(struct gscps2port), GFP_KERNEL); + serio = kmalloc(sizeof(struct serio), GFP_KERNEL); + if (!ps2port || !serio) { + ret = -ENOMEM; + goto fail_nomem; + } + + dev_set_drvdata(&dev->dev, ps2port); + + memset(ps2port, 0, sizeof(struct gscps2port)); + memset(serio, 0, sizeof(struct serio)); + ps2port->port = serio; + ps2port->padev = dev; + ps2port->addr = ioremap(hpa, GSC_STATUS + 4); + spin_lock_init(&ps2port->lock); + + gscps2_reset(ps2port); + ps2port->id = readb(ps2port->addr + GSC_ID) & 0x0f; + + snprintf(serio->name, sizeof(serio->name), "GSC PS/2 %s", + (ps2port->id == GSC_ID_KEYBOARD) ? "keyboard" : "mouse"); + strlcpy(serio->phys, dev->dev.bus_id, sizeof(serio->phys)); + serio->id.type = SERIO_8042; + serio->write = gscps2_write; + serio->open = gscps2_open; + serio->close = gscps2_close; + serio->port_data = ps2port; + serio->dev.parent = &dev->dev; + + list_add_tail(&ps2port->node, &ps2port_list); + + ret = -EBUSY; + if (request_irq(dev->irq, gscps2_interrupt, SA_SHIRQ, ps2port->port->name, ps2port)) + goto fail_miserably; + + if (ps2port->id != GSC_ID_KEYBOARD && ps2port->id != GSC_ID_MOUSE) { + printk(KERN_WARNING PFX "Unsupported PS/2 port at 0x%08lx (id=%d) ignored\n", + hpa, ps2port->id); + ret = -ENODEV; + goto fail; + } + +#if 0 + if (!request_mem_region(hpa, GSC_STATUS + 4, ps2port->port.name)) + goto fail; +#endif + + printk(KERN_INFO "serio: %s port at 0x%p irq %d @ %s\n", + ps2port->port->name, + ps2port->addr, + ps2port->padev->irq, + ps2port->port->phys); + + serio_register_port(ps2port->port); + + return 0; + +fail: + free_irq(dev->irq, ps2port); + +fail_miserably: + list_del(&ps2port->node); + iounmap(ps2port->addr); + release_mem_region(dev->hpa, GSC_STATUS + 4); + +fail_nomem: + kfree(ps2port); + kfree(serio); + return ret; +} + +/** + * gscps2_remove() - Removes PS2 devices + * @return: success/error report + */ + +static int __devexit gscps2_remove(struct parisc_device *dev) +{ + struct gscps2port *ps2port = dev_get_drvdata(&dev->dev); + + serio_unregister_port(ps2port->port); + free_irq(dev->irq, ps2port); + gscps2_flush(ps2port); + list_del(&ps2port->node); + iounmap(ps2port->addr); +#if 0 + release_mem_region(dev->hpa, GSC_STATUS + 4); +#endif + dev_set_drvdata(&dev->dev, NULL); + kfree(ps2port); + return 0; +} + + +static struct parisc_device_id gscps2_device_tbl[] = { + { HPHW_FIO, HVERSION_REV_ANY_ID, HVERSION_ANY_ID, 0x00084 }, /* LASI PS/2 */ +#ifdef DINO_TESTED + { HPHW_FIO, HVERSION_REV_ANY_ID, HVERSION_ANY_ID, 0x00096 }, /* DINO PS/2 */ +#endif + { 0, } /* 0 terminated list */ +}; + +static struct parisc_driver parisc_ps2_driver = { + .name = "GSC PS2", + .id_table = gscps2_device_tbl, + .probe = gscps2_probe, + .remove = gscps2_remove, +}; + +static int __init gscps2_init(void) +{ + register_parisc_driver(&parisc_ps2_driver); + return 0; +} + +static void __exit gscps2_exit(void) +{ + unregister_parisc_driver(&parisc_ps2_driver); +} + + +module_init(gscps2_init); +module_exit(gscps2_exit); + diff --git a/drivers/input/serio/hil_mlc.c b/drivers/input/serio/hil_mlc.c new file mode 100644 index 00000000000..c243cb6fdfc --- /dev/null +++ b/drivers/input/serio/hil_mlc.c @@ -0,0 +1,949 @@ +/* + * HIL MLC state machine and serio interface driver + * + * Copyright (c) 2001 Brian S. Julin + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * Alternatively, this software may be distributed under the terms of the + * GNU General Public License ("GPL"). + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * + * References: + * HP-HIL Technical Reference Manual. Hewlett Packard Product No. 45918A + * + * + * Driver theory of operation: + * + * Some access methods and an ISR is defined by the sub-driver + * (e.g. hp_sdc_mlc.c). These methods are expected to provide a + * few bits of logic in addition to raw access to the HIL MLC, + * specifically, the ISR, which is entirely registered by the + * sub-driver and invoked directly, must check for record + * termination or packet match, at which point a semaphore must + * be cleared and then the hil_mlcs_tasklet must be scheduled. + * + * The hil_mlcs_tasklet processes the state machine for all MLCs + * each time it runs, checking each MLC's progress at the current + * node in the state machine, and moving the MLC to subsequent nodes + * in the state machine when appropriate. It will reschedule + * itself if output is pending. (This rescheduling should be replaced + * at some point with a sub-driver-specific mechanism.) + * + * A timer task prods the tasklet once per second to prevent + * hangups when attached devices do not return expected data + * and to initiate probes of the loop for new devices. + */ + +#include <linux/hil_mlc.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/timer.h> +#include <linux/sched.h> +#include <linux/list.h> + +MODULE_AUTHOR("Brian S. Julin <bri@calyx.com>"); +MODULE_DESCRIPTION("HIL MLC serio"); +MODULE_LICENSE("Dual BSD/GPL"); + +EXPORT_SYMBOL(hil_mlc_register); +EXPORT_SYMBOL(hil_mlc_unregister); + +#define PREFIX "HIL MLC: " + +static LIST_HEAD(hil_mlcs); +static DEFINE_RWLOCK(hil_mlcs_lock); +static struct timer_list hil_mlcs_kicker; +static int hil_mlcs_probe; + +static void hil_mlcs_process(unsigned long unused); +DECLARE_TASKLET_DISABLED(hil_mlcs_tasklet, hil_mlcs_process, 0); + + +/* #define HIL_MLC_DEBUG */ + +/********************** Device info/instance management **********************/ + +static void hil_mlc_clear_di_map (hil_mlc *mlc, int val) { + int j; + for (j = val; j < 7 ; j++) { + mlc->di_map[j] = -1; + } +} + +static void hil_mlc_clear_di_scratch (hil_mlc *mlc) { + memset(&(mlc->di_scratch), 0, sizeof(mlc->di_scratch)); +} + +static void hil_mlc_copy_di_scratch (hil_mlc *mlc, int idx) { + memcpy(&(mlc->di[idx]), &(mlc->di_scratch), sizeof(mlc->di_scratch)); +} + +static int hil_mlc_match_di_scratch (hil_mlc *mlc) { + int idx; + + for (idx = 0; idx < HIL_MLC_DEVMEM; idx++) { + int j, found; + + /* In-use slots are not eligible. */ + found = 0; + for (j = 0; j < 7 ; j++) { + if (mlc->di_map[j] == idx) found++; + } + if (found) continue; + if (!memcmp(mlc->di + idx, + &(mlc->di_scratch), + sizeof(mlc->di_scratch))) break; + } + return((idx >= HIL_MLC_DEVMEM) ? -1 : idx); +} + +static int hil_mlc_find_free_di(hil_mlc *mlc) { + int idx; + /* TODO: Pick all-zero slots first, failing that, + * randomize the slot picked among those eligible. + */ + for (idx = 0; idx < HIL_MLC_DEVMEM; idx++) { + int j, found; + found = 0; + for (j = 0; j < 7 ; j++) { + if (mlc->di_map[j] == idx) found++; + } + if (!found) break; + } + return(idx); /* Note: It is guaranteed at least one above will match */ +} + +static inline void hil_mlc_clean_serio_map(hil_mlc *mlc) { + int idx; + for (idx = 0; idx < HIL_MLC_DEVMEM; idx++) { + int j, found; + found = 0; + for (j = 0; j < 7 ; j++) { + if (mlc->di_map[j] == idx) found++; + } + if (!found) mlc->serio_map[idx].di_revmap = -1; + } +} + +static void hil_mlc_send_polls(hil_mlc *mlc) { + int did, i, cnt; + struct serio *serio; + struct serio_driver *drv; + + i = cnt = 0; + did = (mlc->ipacket[0] & HIL_PKT_ADDR_MASK) >> 8; + serio = did ? mlc->serio[mlc->di_map[did - 1]] : NULL; + drv = (serio != NULL) ? serio->drv : NULL; + + while (mlc->icount < 15 - i) { + hil_packet p; + p = mlc->ipacket[i]; + if (did != (p & HIL_PKT_ADDR_MASK) >> 8) { + if (drv == NULL || drv->interrupt == NULL) goto skip; + + drv->interrupt(serio, 0, 0, NULL); + drv->interrupt(serio, HIL_ERR_INT >> 16, 0, NULL); + drv->interrupt(serio, HIL_PKT_CMD >> 8, 0, NULL); + drv->interrupt(serio, HIL_CMD_POL + cnt, 0, NULL); + skip: + did = (p & HIL_PKT_ADDR_MASK) >> 8; + serio = did ? mlc->serio[mlc->di_map[did-1]] : NULL; + drv = (serio != NULL) ? serio->drv : NULL; + cnt = 0; + } + cnt++; i++; + if (drv == NULL || drv->interrupt == NULL) continue; + drv->interrupt(serio, (p >> 24), 0, NULL); + drv->interrupt(serio, (p >> 16) & 0xff, 0, NULL); + drv->interrupt(serio, (p >> 8) & ~HIL_PKT_ADDR_MASK, 0, NULL); + drv->interrupt(serio, p & 0xff, 0, NULL); + } +} + +/*************************** State engine *********************************/ + +#define HILSEN_SCHED 0x000100 /* Schedule the tasklet */ +#define HILSEN_BREAK 0x000200 /* Wait until next pass */ +#define HILSEN_UP 0x000400 /* relative node#, decrement */ +#define HILSEN_DOWN 0x000800 /* relative node#, increment */ +#define HILSEN_FOLLOW 0x001000 /* use retval as next node# */ + +#define HILSEN_MASK 0x0000ff +#define HILSEN_START 0 +#define HILSEN_RESTART 1 +#define HILSEN_DHR 9 +#define HILSEN_DHR2 10 +#define HILSEN_IFC 14 +#define HILSEN_HEAL0 16 +#define HILSEN_HEAL 18 +#define HILSEN_ACF 21 +#define HILSEN_ACF2 22 +#define HILSEN_DISC0 25 +#define HILSEN_DISC 27 +#define HILSEN_MATCH 40 +#define HILSEN_OPERATE 41 +#define HILSEN_PROBE 44 +#define HILSEN_DSR 52 +#define HILSEN_REPOLL 55 +#define HILSEN_IFCACF 58 +#define HILSEN_END 60 + +#define HILSEN_NEXT (HILSEN_DOWN | 1) +#define HILSEN_SAME (HILSEN_DOWN | 0) +#define HILSEN_LAST (HILSEN_UP | 1) + +#define HILSEN_DOZE (HILSEN_SAME | HILSEN_SCHED | HILSEN_BREAK) +#define HILSEN_SLEEP (HILSEN_SAME | HILSEN_BREAK) + +static int hilse_match(hil_mlc *mlc, int unused) { + int rc; + rc = hil_mlc_match_di_scratch(mlc); + if (rc == -1) { + rc = hil_mlc_find_free_di(mlc); + if (rc == -1) goto err; +#ifdef HIL_MLC_DEBUG + printk(KERN_DEBUG PREFIX "new in slot %i\n", rc); +#endif + hil_mlc_copy_di_scratch(mlc, rc); + mlc->di_map[mlc->ddi] = rc; + mlc->serio_map[rc].di_revmap = mlc->ddi; + hil_mlc_clean_serio_map(mlc); + serio_rescan(mlc->serio[rc]); + return -1; + } + mlc->di_map[mlc->ddi] = rc; +#ifdef HIL_MLC_DEBUG + printk(KERN_DEBUG PREFIX "same in slot %i\n", rc); +#endif + mlc->serio_map[rc].di_revmap = mlc->ddi; + hil_mlc_clean_serio_map(mlc); + return 0; + err: + printk(KERN_ERR PREFIX "Residual device slots exhausted, close some serios!\n"); + return 1; +} + +/* An LCV used to prevent runaway loops, forces 5 second sleep when reset. */ +static int hilse_init_lcv(hil_mlc *mlc, int unused) { + struct timeval tv; + + do_gettimeofday(&tv); + + if(mlc->lcv == 0) goto restart; /* First init, no need to dally */ + if(tv.tv_sec - mlc->lcv_tv.tv_sec < 5) return -1; + restart: + mlc->lcv_tv = tv; + mlc->lcv = 0; + return 0; +} + +static int hilse_inc_lcv(hil_mlc *mlc, int lim) { + if (mlc->lcv++ >= lim) return -1; + return 0; +} + +#if 0 +static int hilse_set_lcv(hil_mlc *mlc, int val) { + mlc->lcv = val; + return 0; +} +#endif + +/* Management of the discovered device index (zero based, -1 means no devs) */ +static int hilse_set_ddi(hil_mlc *mlc, int val) { + mlc->ddi = val; + hil_mlc_clear_di_map(mlc, val + 1); + return 0; +} + +static int hilse_dec_ddi(hil_mlc *mlc, int unused) { + mlc->ddi--; + if (mlc->ddi <= -1) { + mlc->ddi = -1; + hil_mlc_clear_di_map(mlc, 0); + return -1; + } + hil_mlc_clear_di_map(mlc, mlc->ddi + 1); + return 0; +} + +static int hilse_inc_ddi(hil_mlc *mlc, int unused) { + if (mlc->ddi >= 6) { + BUG(); + return -1; + } + mlc->ddi++; + return 0; +} + +static int hilse_take_idd(hil_mlc *mlc, int unused) { + int i; + + /* Help the state engine: + * Is this a real IDD response or just an echo? + * + * Real IDD response does not start with a command. + */ + if (mlc->ipacket[0] & HIL_PKT_CMD) goto bail; + /* Should have the command echoed further down. */ + for (i = 1; i < 16; i++) { + if (((mlc->ipacket[i] & HIL_PKT_ADDR_MASK) == + (mlc->ipacket[0] & HIL_PKT_ADDR_MASK)) && + (mlc->ipacket[i] & HIL_PKT_CMD) && + ((mlc->ipacket[i] & HIL_PKT_DATA_MASK) == HIL_CMD_IDD)) + break; + } + if (i > 15) goto bail; + /* And the rest of the packets should still be clear. */ + while (++i < 16) { + if (mlc->ipacket[i]) break; + } + if (i < 16) goto bail; + for (i = 0; i < 16; i++) { + mlc->di_scratch.idd[i] = + mlc->ipacket[i] & HIL_PKT_DATA_MASK; + } + /* Next step is to see if RSC supported */ + if (mlc->di_scratch.idd[1] & HIL_IDD_HEADER_RSC) + return HILSEN_NEXT; + if (mlc->di_scratch.idd[1] & HIL_IDD_HEADER_EXD) + return HILSEN_DOWN | 4; + return 0; + bail: + mlc->ddi--; + return -1; /* This should send us off to ACF */ +} + +static int hilse_take_rsc(hil_mlc *mlc, int unused) { + int i; + + for (i = 0; i < 16; i++) { + mlc->di_scratch.rsc[i] = + mlc->ipacket[i] & HIL_PKT_DATA_MASK; + } + /* Next step is to see if EXD supported (IDD has already been read) */ + if (mlc->di_scratch.idd[1] & HIL_IDD_HEADER_EXD) + return HILSEN_NEXT; + return 0; +} + +static int hilse_take_exd(hil_mlc *mlc, int unused) { + int i; + + for (i = 0; i < 16; i++) { + mlc->di_scratch.exd[i] = + mlc->ipacket[i] & HIL_PKT_DATA_MASK; + } + /* Next step is to see if RNM supported. */ + if (mlc->di_scratch.exd[0] & HIL_EXD_HEADER_RNM) + return HILSEN_NEXT; + return 0; +} + +static int hilse_take_rnm(hil_mlc *mlc, int unused) { + int i; + + for (i = 0; i < 16; i++) { + mlc->di_scratch.rnm[i] = + mlc->ipacket[i] & HIL_PKT_DATA_MASK; + } + do { + char nam[17]; + snprintf(nam, 16, "%s", mlc->di_scratch.rnm); + nam[16] = '\0'; + printk(KERN_INFO PREFIX "Device name gotten: %s\n", nam); + } while (0); + return 0; +} + +static int hilse_operate(hil_mlc *mlc, int repoll) { + + if (mlc->opercnt == 0) hil_mlcs_probe = 0; + mlc->opercnt = 1; + + hil_mlc_send_polls(mlc); + + if (!hil_mlcs_probe) return 0; + hil_mlcs_probe = 0; + mlc->opercnt = 0; + return 1; +} + +#define FUNC(funct, funct_arg, zero_rc, neg_rc, pos_rc) \ +{ HILSE_FUNC, { func: &funct }, funct_arg, zero_rc, neg_rc, pos_rc }, +#define OUT(pack) \ +{ HILSE_OUT, { packet: pack }, 0, HILSEN_NEXT, HILSEN_DOZE, 0 }, +#define CTS \ +{ HILSE_CTS, { packet: 0 }, 0, HILSEN_NEXT | HILSEN_SCHED | HILSEN_BREAK, HILSEN_DOZE, 0 }, +#define EXPECT(comp, to, got, got_wrong, timed_out) \ +{ HILSE_EXPECT, { packet: comp }, to, got, got_wrong, timed_out }, +#define EXPECT_LAST(comp, to, got, got_wrong, timed_out) \ +{ HILSE_EXPECT_LAST, { packet: comp }, to, got, got_wrong, timed_out }, +#define EXPECT_DISC(comp, to, got, got_wrong, timed_out) \ +{ HILSE_EXPECT_DISC, { packet: comp }, to, got, got_wrong, timed_out }, +#define IN(to, got, got_error, timed_out) \ +{ HILSE_IN, { packet: 0 }, to, got, got_error, timed_out }, +#define OUT_DISC(pack) \ +{ HILSE_OUT_DISC, { packet: pack }, 0, 0, 0, 0 }, +#define OUT_LAST(pack) \ +{ HILSE_OUT_LAST, { packet: pack }, 0, 0, 0, 0 }, + +struct hilse_node hil_mlc_se[HILSEN_END] = { + + /* 0 HILSEN_START */ + FUNC(hilse_init_lcv, 0, HILSEN_NEXT, HILSEN_SLEEP, 0) + + /* 1 HILSEN_RESTART */ + FUNC(hilse_inc_lcv, 10, HILSEN_NEXT, HILSEN_START, 0) + OUT(HIL_CTRL_ONLY) /* Disable APE */ + CTS + +#define TEST_PACKET(x) \ +(HIL_PKT_CMD | (x << HIL_PKT_ADDR_SHIFT) | x << 4 | x) + + OUT(HIL_DO_ALTER_CTRL | HIL_CTRL_TEST | TEST_PACKET(0x5)) + EXPECT(HIL_ERR_INT | TEST_PACKET(0x5), + 2000, HILSEN_NEXT, HILSEN_RESTART, HILSEN_RESTART) + OUT(HIL_DO_ALTER_CTRL | HIL_CTRL_TEST | TEST_PACKET(0xa)) + EXPECT(HIL_ERR_INT | TEST_PACKET(0xa), + 2000, HILSEN_NEXT, HILSEN_RESTART, HILSEN_RESTART) + OUT(HIL_CTRL_ONLY | 0) /* Disable test mode */ + + /* 9 HILSEN_DHR */ + FUNC(hilse_init_lcv, 0, HILSEN_NEXT, HILSEN_SLEEP, 0) + + /* 10 HILSEN_DHR2 */ + FUNC(hilse_inc_lcv, 10, HILSEN_NEXT, HILSEN_START, 0) + FUNC(hilse_set_ddi, -1, HILSEN_NEXT, 0, 0) + OUT(HIL_PKT_CMD | HIL_CMD_DHR) + IN(300000, HILSEN_DHR2, HILSEN_DHR2, HILSEN_NEXT) + + /* 14 HILSEN_IFC */ + OUT(HIL_PKT_CMD | HIL_CMD_IFC) + EXPECT(HIL_PKT_CMD | HIL_CMD_IFC | HIL_ERR_INT, + 20000, HILSEN_DISC, HILSEN_DHR2, HILSEN_NEXT ) + + /* If devices are there, they weren't in PUP or other loopback mode. + * We're more concerned at this point with restoring operation + * to devices than discovering new ones, so we try to salvage + * the loop configuration by closing off the loop. + */ + + /* 16 HILSEN_HEAL0 */ + FUNC(hilse_dec_ddi, 0, HILSEN_NEXT, HILSEN_ACF, 0) + FUNC(hilse_inc_ddi, 0, HILSEN_NEXT, 0, 0) + + /* 18 HILSEN_HEAL */ + OUT_LAST(HIL_CMD_ELB) + EXPECT_LAST(HIL_CMD_ELB | HIL_ERR_INT, + 20000, HILSEN_REPOLL, HILSEN_DSR, HILSEN_NEXT) + FUNC(hilse_dec_ddi, 0, HILSEN_HEAL, HILSEN_NEXT, 0) + + /* 21 HILSEN_ACF */ + FUNC(hilse_init_lcv, 0, HILSEN_NEXT, HILSEN_DOZE, 0) + + /* 22 HILSEN_ACF2 */ + FUNC(hilse_inc_lcv, 10, HILSEN_NEXT, HILSEN_START, 0) + OUT(HIL_PKT_CMD | HIL_CMD_ACF | 1) + IN(20000, HILSEN_NEXT, HILSEN_DSR, HILSEN_NEXT) + + /* 25 HILSEN_DISC0 */ + OUT_DISC(HIL_PKT_CMD | HIL_CMD_ELB) + EXPECT_DISC(HIL_PKT_CMD | HIL_CMD_ELB | HIL_ERR_INT, + 20000, HILSEN_NEXT, HILSEN_DSR, HILSEN_DSR) + + /* Only enter here if response just received */ + /* 27 HILSEN_DISC */ + OUT_DISC(HIL_PKT_CMD | HIL_CMD_IDD) + EXPECT_DISC(HIL_PKT_CMD | HIL_CMD_IDD | HIL_ERR_INT, + 20000, HILSEN_NEXT, HILSEN_DSR, HILSEN_START) + FUNC(hilse_inc_ddi, 0, HILSEN_NEXT, HILSEN_START, 0) + FUNC(hilse_take_idd, 0, HILSEN_MATCH, HILSEN_IFCACF, HILSEN_FOLLOW) + OUT_LAST(HIL_PKT_CMD | HIL_CMD_RSC) + EXPECT_LAST(HIL_PKT_CMD | HIL_CMD_RSC | HIL_ERR_INT, + 30000, HILSEN_NEXT, HILSEN_DSR, HILSEN_DSR) + FUNC(hilse_take_rsc, 0, HILSEN_MATCH, 0, HILSEN_FOLLOW) + OUT_LAST(HIL_PKT_CMD | HIL_CMD_EXD) + EXPECT_LAST(HIL_PKT_CMD | HIL_CMD_EXD | HIL_ERR_INT, + 30000, HILSEN_NEXT, HILSEN_DSR, HILSEN_DSR) + FUNC(hilse_take_exd, 0, HILSEN_MATCH, 0, HILSEN_FOLLOW) + OUT_LAST(HIL_PKT_CMD | HIL_CMD_RNM) + EXPECT_LAST(HIL_PKT_CMD | HIL_CMD_RNM | HIL_ERR_INT, + 30000, HILSEN_NEXT, HILSEN_DSR, HILSEN_DSR) + FUNC(hilse_take_rnm, 0, HILSEN_MATCH, 0, 0) + + /* 40 HILSEN_MATCH */ + FUNC(hilse_match, 0, HILSEN_NEXT, HILSEN_NEXT, /* TODO */ 0) + + /* 41 HILSEN_OPERATE */ + OUT(HIL_PKT_CMD | HIL_CMD_POL) + EXPECT(HIL_PKT_CMD | HIL_CMD_POL | HIL_ERR_INT, + 20000, HILSEN_NEXT, HILSEN_DSR, HILSEN_NEXT) + FUNC(hilse_operate, 0, HILSEN_OPERATE, HILSEN_IFC, HILSEN_NEXT) + + /* 44 HILSEN_PROBE */ + OUT_LAST(HIL_PKT_CMD | HIL_CMD_EPT) + IN(10000, HILSEN_DISC, HILSEN_DSR, HILSEN_NEXT) + OUT_DISC(HIL_PKT_CMD | HIL_CMD_ELB) + IN(10000, HILSEN_DISC, HILSEN_DSR, HILSEN_NEXT) + OUT(HIL_PKT_CMD | HIL_CMD_ACF | 1) + IN(10000, HILSEN_DISC0, HILSEN_DSR, HILSEN_NEXT) + OUT_LAST(HIL_PKT_CMD | HIL_CMD_ELB) + IN(10000, HILSEN_OPERATE, HILSEN_DSR, HILSEN_DSR) + + /* 52 HILSEN_DSR */ + FUNC(hilse_set_ddi, -1, HILSEN_NEXT, 0, 0) + OUT(HIL_PKT_CMD | HIL_CMD_DSR) + IN(20000, HILSEN_DHR, HILSEN_DHR, HILSEN_IFC) + + /* 55 HILSEN_REPOLL */ + OUT(HIL_PKT_CMD | HIL_CMD_RPL) + EXPECT(HIL_PKT_CMD | HIL_CMD_RPL | HIL_ERR_INT, + 20000, HILSEN_NEXT, HILSEN_DSR, HILSEN_NEXT) + FUNC(hilse_operate, 1, HILSEN_OPERATE, HILSEN_IFC, HILSEN_PROBE) + + /* 58 HILSEN_IFCACF */ + OUT(HIL_PKT_CMD | HIL_CMD_IFC) + EXPECT(HIL_PKT_CMD | HIL_CMD_IFC | HIL_ERR_INT, + 20000, HILSEN_ACF2, HILSEN_DHR2, HILSEN_HEAL) + + /* 60 HILSEN_END */ +}; + +static inline void hilse_setup_input(hil_mlc *mlc, struct hilse_node *node) { + + switch (node->act) { + case HILSE_EXPECT_DISC: + mlc->imatch = node->object.packet; + mlc->imatch |= ((mlc->ddi + 2) << HIL_PKT_ADDR_SHIFT); + break; + case HILSE_EXPECT_LAST: + mlc->imatch = node->object.packet; + mlc->imatch |= ((mlc->ddi + 1) << HIL_PKT_ADDR_SHIFT); + break; + case HILSE_EXPECT: + mlc->imatch = node->object.packet; + break; + case HILSE_IN: + mlc->imatch = 0; + break; + default: + BUG(); + } + mlc->istarted = 1; + mlc->intimeout = node->arg; + do_gettimeofday(&(mlc->instart)); + mlc->icount = 15; + memset(mlc->ipacket, 0, 16 * sizeof(hil_packet)); + if (down_trylock(&(mlc->isem))) BUG(); + + return; +} + +#ifdef HIL_MLC_DEBUG +static int doze = 0; +static int seidx; /* For debug */ +static int kick = 1; +#endif + +static int hilse_donode (hil_mlc *mlc) { + struct hilse_node *node; + int nextidx = 0; + int sched_long = 0; + unsigned long flags; + +#ifdef HIL_MLC_DEBUG + if (mlc->seidx && (mlc->seidx != seidx) && mlc->seidx != 41 && mlc->seidx != 42 && mlc->seidx != 43) { + printk(KERN_DEBUG PREFIX "z%i \n%s {%i}", doze, kick ? "K" : "", mlc->seidx); + doze = 0; + } + kick = 0; + + seidx = mlc->seidx; +#endif + node = hil_mlc_se + mlc->seidx; + + switch (node->act) { + int rc; + hil_packet pack; + + case HILSE_FUNC: + if (node->object.func == NULL) break; + rc = node->object.func(mlc, node->arg); + nextidx = (rc > 0) ? node->ugly : + ((rc < 0) ? node->bad : node->good); + if (nextidx == HILSEN_FOLLOW) nextidx = rc; + break; + case HILSE_EXPECT_LAST: + case HILSE_EXPECT_DISC: + case HILSE_EXPECT: + case HILSE_IN: + /* Already set up from previous HILSE_OUT_* */ + write_lock_irqsave(&(mlc->lock), flags); + rc = mlc->in(mlc, node->arg); + if (rc == 2) { + nextidx = HILSEN_DOZE; + sched_long = 1; + write_unlock_irqrestore(&(mlc->lock), flags); + break; + } + if (rc == 1) nextidx = node->ugly; + else if (rc == 0) nextidx = node->good; + else nextidx = node->bad; + mlc->istarted = 0; + write_unlock_irqrestore(&(mlc->lock), flags); + break; + case HILSE_OUT_LAST: + write_lock_irqsave(&(mlc->lock), flags); + pack = node->object.packet; + pack |= ((mlc->ddi + 1) << HIL_PKT_ADDR_SHIFT); + goto out; + case HILSE_OUT_DISC: + write_lock_irqsave(&(mlc->lock), flags); + pack = node->object.packet; + pack |= ((mlc->ddi + 2) << HIL_PKT_ADDR_SHIFT); + goto out; + case HILSE_OUT: + write_lock_irqsave(&(mlc->lock), flags); + pack = node->object.packet; + out: + if (mlc->istarted) goto out2; + /* Prepare to receive input */ + if ((node + 1)->act & HILSE_IN) + hilse_setup_input(mlc, node + 1); + + out2: + write_unlock_irqrestore(&(mlc->lock), flags); + + if (down_trylock(&mlc->osem)) { + nextidx = HILSEN_DOZE; + break; + } + up(&mlc->osem); + + write_lock_irqsave(&(mlc->lock), flags); + if (!(mlc->ostarted)) { + mlc->ostarted = 1; + mlc->opacket = pack; + mlc->out(mlc); + nextidx = HILSEN_DOZE; + write_unlock_irqrestore(&(mlc->lock), flags); + break; + } + mlc->ostarted = 0; + do_gettimeofday(&(mlc->instart)); + write_unlock_irqrestore(&(mlc->lock), flags); + nextidx = HILSEN_NEXT; + break; + case HILSE_CTS: + nextidx = mlc->cts(mlc) ? node->bad : node->good; + break; + default: + BUG(); + nextidx = 0; + break; + } + +#ifdef HIL_MLC_DEBUG + if (nextidx == HILSEN_DOZE) doze++; +#endif + + while (nextidx & HILSEN_SCHED) { + struct timeval tv; + + if (!sched_long) goto sched; + + do_gettimeofday(&tv); + tv.tv_usec += 1000000 * (tv.tv_sec - mlc->instart.tv_sec); + tv.tv_usec -= mlc->instart.tv_usec; + if (tv.tv_usec >= mlc->intimeout) goto sched; + tv.tv_usec = (mlc->intimeout - tv.tv_usec) * HZ / 1000000; + if (!tv.tv_usec) goto sched; + mod_timer(&hil_mlcs_kicker, jiffies + tv.tv_usec); + break; + sched: + tasklet_schedule(&hil_mlcs_tasklet); + break; + } + if (nextidx & HILSEN_DOWN) mlc->seidx += nextidx & HILSEN_MASK; + else if (nextidx & HILSEN_UP) mlc->seidx -= nextidx & HILSEN_MASK; + else mlc->seidx = nextidx & HILSEN_MASK; + + if (nextidx & HILSEN_BREAK) return 1; + return 0; +} + +/******************** tasklet context functions **************************/ +static void hil_mlcs_process(unsigned long unused) { + struct list_head *tmp; + + read_lock(&hil_mlcs_lock); + list_for_each(tmp, &hil_mlcs) { + struct hil_mlc *mlc = list_entry(tmp, hil_mlc, list); + while (hilse_donode(mlc) == 0) { +#ifdef HIL_MLC_DEBUG + if (mlc->seidx != 41 && + mlc->seidx != 42 && + mlc->seidx != 43) + printk(KERN_DEBUG PREFIX " + "); +#endif + }; + } + read_unlock(&hil_mlcs_lock); +} + +/************************* Keepalive timer task *********************/ + +void hil_mlcs_timer (unsigned long data) { + hil_mlcs_probe = 1; + tasklet_schedule(&hil_mlcs_tasklet); + /* Re-insert the periodic task. */ + if (!timer_pending(&hil_mlcs_kicker)) + mod_timer(&hil_mlcs_kicker, jiffies + HZ); +} + +/******************** user/kernel context functions **********************/ + +static int hil_mlc_serio_write(struct serio *serio, unsigned char c) { + struct hil_mlc_serio_map *map; + struct hil_mlc *mlc; + struct serio_driver *drv; + uint8_t *idx, *last; + + map = serio->port_data; + if (map == NULL) { + BUG(); + return -EIO; + } + mlc = map->mlc; + if (mlc == NULL) { + BUG(); + return -EIO; + } + mlc->serio_opacket[map->didx] |= + ((hil_packet)c) << (8 * (3 - mlc->serio_oidx[map->didx])); + + if (mlc->serio_oidx[map->didx] >= 3) { + /* for now only commands */ + if (!(mlc->serio_opacket[map->didx] & HIL_PKT_CMD)) + return -EIO; + switch (mlc->serio_opacket[map->didx] & HIL_PKT_DATA_MASK) { + case HIL_CMD_IDD: + idx = mlc->di[map->didx].idd; + goto emu; + case HIL_CMD_RSC: + idx = mlc->di[map->didx].rsc; + goto emu; + case HIL_CMD_EXD: + idx = mlc->di[map->didx].exd; + goto emu; + case HIL_CMD_RNM: + idx = mlc->di[map->didx].rnm; + goto emu; + default: + break; + } + mlc->serio_oidx[map->didx] = 0; + mlc->serio_opacket[map->didx] = 0; + } + + mlc->serio_oidx[map->didx]++; + return -EIO; + emu: + drv = serio->drv; + if (drv == NULL) { + BUG(); + return -EIO; + } + last = idx + 15; + while ((last != idx) && (*last == 0)) last--; + + while (idx != last) { + drv->interrupt(serio, 0, 0, NULL); + drv->interrupt(serio, HIL_ERR_INT >> 16, 0, NULL); + drv->interrupt(serio, 0, 0, NULL); + drv->interrupt(serio, *idx, 0, NULL); + idx++; + } + drv->interrupt(serio, 0, 0, NULL); + drv->interrupt(serio, HIL_ERR_INT >> 16, 0, NULL); + drv->interrupt(serio, HIL_PKT_CMD >> 8, 0, NULL); + drv->interrupt(serio, *idx, 0, NULL); + + mlc->serio_oidx[map->didx] = 0; + mlc->serio_opacket[map->didx] = 0; + + return 0; +} + +static int hil_mlc_serio_open(struct serio *serio) { + struct hil_mlc_serio_map *map; + struct hil_mlc *mlc; + + if (serio->private != NULL) return -EBUSY; + + map = serio->port_data; + if (map == NULL) { + BUG(); + return -ENODEV; + } + mlc = map->mlc; + if (mlc == NULL) { + BUG(); + return -ENODEV; + } + + return 0; +} + +static void hil_mlc_serio_close(struct serio *serio) { + struct hil_mlc_serio_map *map; + struct hil_mlc *mlc; + + map = serio->port_data; + if (map == NULL) { + BUG(); + return; + } + mlc = map->mlc; + if (mlc == NULL) { + BUG(); + return; + } + + serio->private = NULL; + serio->drv = NULL; + /* TODO wake up interruptable */ +} + +int hil_mlc_register(hil_mlc *mlc) { + int i; + unsigned long flags; + + if (mlc == NULL) { + return -EINVAL; + } + + mlc->istarted = 0; + mlc->ostarted = 0; + + rwlock_init(&mlc->lock); + init_MUTEX(&(mlc->osem)); + + init_MUTEX(&(mlc->isem)); + mlc->icount = -1; + mlc->imatch = 0; + + mlc->opercnt = 0; + + init_MUTEX_LOCKED(&(mlc->csem)); + + hil_mlc_clear_di_scratch(mlc); + hil_mlc_clear_di_map(mlc, 0); + for (i = 0; i < HIL_MLC_DEVMEM; i++) { + struct serio *mlc_serio; + hil_mlc_copy_di_scratch(mlc, i); + mlc_serio = kmalloc(sizeof(*mlc_serio), GFP_KERNEL); + mlc->serio[i] = mlc_serio; + memset(mlc_serio, 0, sizeof(*mlc_serio)); + mlc_serio->type = SERIO_HIL | SERIO_HIL_MLC; + mlc_serio->write = hil_mlc_serio_write; + mlc_serio->open = hil_mlc_serio_open; + mlc_serio->close = hil_mlc_serio_close; + mlc_serio->port_data = &(mlc->serio_map[i]); + mlc->serio_map[i].mlc = mlc; + mlc->serio_map[i].didx = i; + mlc->serio_map[i].di_revmap = -1; + mlc->serio_opacket[i] = 0; + mlc->serio_oidx[i] = 0; + serio_register_port(mlc_serio); + } + + mlc->tasklet = &hil_mlcs_tasklet; + + write_lock_irqsave(&hil_mlcs_lock, flags); + list_add_tail(&mlc->list, &hil_mlcs); + mlc->seidx = HILSEN_START; + write_unlock_irqrestore(&hil_mlcs_lock, flags); + + tasklet_schedule(&hil_mlcs_tasklet); + return 0; +} + +int hil_mlc_unregister(hil_mlc *mlc) { + struct list_head *tmp; + unsigned long flags; + int i; + + if (mlc == NULL) + return -EINVAL; + + write_lock_irqsave(&hil_mlcs_lock, flags); + list_for_each(tmp, &hil_mlcs) { + if (list_entry(tmp, hil_mlc, list) == mlc) + goto found; + } + + /* not found in list */ + write_unlock_irqrestore(&hil_mlcs_lock, flags); + tasklet_schedule(&hil_mlcs_tasklet); + return -ENODEV; + + found: + list_del(tmp); + write_unlock_irqrestore(&hil_mlcs_lock, flags); + + for (i = 0; i < HIL_MLC_DEVMEM; i++) { + serio_unregister_port(mlc->serio[i]); + mlc->serio[i] = NULL; + } + + tasklet_schedule(&hil_mlcs_tasklet); + return 0; +} + +/**************************** Module interface *************************/ + +static int __init hil_mlc_init(void) +{ + init_timer(&hil_mlcs_kicker); + hil_mlcs_kicker.expires = jiffies + HZ; + hil_mlcs_kicker.function = &hil_mlcs_timer; + add_timer(&hil_mlcs_kicker); + + tasklet_enable(&hil_mlcs_tasklet); + + return 0; +} + +static void __exit hil_mlc_exit(void) +{ + del_timer(&hil_mlcs_kicker); + + tasklet_disable(&hil_mlcs_tasklet); + tasklet_kill(&hil_mlcs_tasklet); +} + +module_init(hil_mlc_init); +module_exit(hil_mlc_exit); diff --git a/drivers/input/serio/hp_sdc.c b/drivers/input/serio/hp_sdc.c new file mode 100644 index 00000000000..7629452dd64 --- /dev/null +++ b/drivers/input/serio/hp_sdc.c @@ -0,0 +1,1054 @@ +/* + * HP i8042-based System Device Controller driver. + * + * Copyright (c) 2001 Brian S. Julin + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * Alternatively, this software may be distributed under the terms of the + * GNU General Public License ("GPL"). + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * + * References: + * System Device Controller Microprocessor Firmware Theory of Operation + * for Part Number 1820-4784 Revision B. Dwg No. A-1820-4784-2 + * Helge Deller's original hilkbd.c port for PA-RISC. + * + * + * Driver theory of operation: + * + * hp_sdc_put does all writing to the SDC. ISR can run on a different + * CPU than hp_sdc_put, but only one CPU runs hp_sdc_put at a time + * (it cannot really benefit from SMP anyway.) A tasket fit this perfectly. + * + * All data coming back from the SDC is sent via interrupt and can be read + * fully in the ISR, so there are no latency/throughput problems there. + * The problem is with output, due to the slow clock speed of the SDC + * compared to the CPU. This should not be too horrible most of the time, + * but if used with HIL devices that support the multibyte transfer command, + * keeping outbound throughput flowing at the 6500KBps that the HIL is + * capable of is more than can be done at HZ=100. + * + * Busy polling for IBF clear wastes CPU cycles and bus cycles. hp_sdc.ibf + * is set to 0 when the IBF flag in the status register has cleared. ISR + * may do this, and may also access the parts of queued transactions related + * to reading data back from the SDC, but otherwise will not touch the + * hp_sdc state. Whenever a register is written hp_sdc.ibf is set to 1. + * + * The i8042 write index and the values in the 4-byte input buffer + * starting at 0x70 are kept track of in hp_sdc.wi, and .r7[], respectively, + * to minimize the amount of IO needed to the SDC. However these values + * do not need to be locked since they are only ever accessed by hp_sdc_put. + * + * A timer task schedules the tasklet once per second just to make + * sure it doesn't freeze up and to allow for bad reads to time out. + */ + +#include <linux/hp_sdc.h> +#include <linux/sched.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/ioport.h> +#include <linux/time.h> +#include <linux/slab.h> +#include <linux/hil.h> +#include <asm/io.h> +#include <asm/system.h> + +/* Machine-specific abstraction */ + +#if defined(__hppa__) +# include <asm/parisc-device.h> +# define sdc_readb(p) gsc_readb(p) +# define sdc_writeb(v,p) gsc_writeb((v),(p)) +#elif defined(__mc68000__) +# include <asm/uaccess.h> +# define sdc_readb(p) in_8(p) +# define sdc_writeb(v,p) out_8((p),(v)) +#else +# error "HIL is not supported on this platform" +#endif + +#define PREFIX "HP SDC: " + +MODULE_AUTHOR("Brian S. Julin <bri@calyx.com>"); +MODULE_DESCRIPTION("HP i8042-based SDC Driver"); +MODULE_LICENSE("Dual BSD/GPL"); + +EXPORT_SYMBOL(hp_sdc_request_timer_irq); +EXPORT_SYMBOL(hp_sdc_request_hil_irq); +EXPORT_SYMBOL(hp_sdc_request_cooked_irq); + +EXPORT_SYMBOL(hp_sdc_release_timer_irq); +EXPORT_SYMBOL(hp_sdc_release_hil_irq); +EXPORT_SYMBOL(hp_sdc_release_cooked_irq); + +EXPORT_SYMBOL(hp_sdc_enqueue_transaction); +EXPORT_SYMBOL(hp_sdc_dequeue_transaction); + +static hp_i8042_sdc hp_sdc; /* All driver state is kept in here. */ + +/*************** primitives for use in any context *********************/ +static inline uint8_t hp_sdc_status_in8 (void) { + uint8_t status; + unsigned long flags; + + write_lock_irqsave(&hp_sdc.ibf_lock, flags); + status = sdc_readb(hp_sdc.status_io); + if (!(status & HP_SDC_STATUS_IBF)) hp_sdc.ibf = 0; + write_unlock_irqrestore(&hp_sdc.ibf_lock, flags); + + return status; +} + +static inline uint8_t hp_sdc_data_in8 (void) { + return sdc_readb(hp_sdc.data_io); +} + +static inline void hp_sdc_status_out8 (uint8_t val) { + unsigned long flags; + + write_lock_irqsave(&hp_sdc.ibf_lock, flags); + hp_sdc.ibf = 1; + if ((val & 0xf0) == 0xe0) hp_sdc.wi = 0xff; + sdc_writeb(val, hp_sdc.status_io); + write_unlock_irqrestore(&hp_sdc.ibf_lock, flags); +} + +static inline void hp_sdc_data_out8 (uint8_t val) { + unsigned long flags; + + write_lock_irqsave(&hp_sdc.ibf_lock, flags); + hp_sdc.ibf = 1; + sdc_writeb(val, hp_sdc.data_io); + write_unlock_irqrestore(&hp_sdc.ibf_lock, flags); +} + +/* Care must be taken to only invoke hp_sdc_spin_ibf when + * absolutely needed, or in rarely invoked subroutines. + * Not only does it waste CPU cycles, it also wastes bus cycles. + */ +static inline void hp_sdc_spin_ibf(void) { + unsigned long flags; + rwlock_t *lock; + + lock = &hp_sdc.ibf_lock; + + read_lock_irqsave(lock, flags); + if (!hp_sdc.ibf) { + read_unlock_irqrestore(lock, flags); + return; + } + read_unlock(lock); + write_lock(lock); + while (sdc_readb(hp_sdc.status_io) & HP_SDC_STATUS_IBF) {}; + hp_sdc.ibf = 0; + write_unlock_irqrestore(lock, flags); +} + + +/************************ Interrupt context functions ************************/ +static void hp_sdc_take (int irq, void *dev_id, uint8_t status, uint8_t data) { + hp_sdc_transaction *curr; + + read_lock(&hp_sdc.rtq_lock); + if (hp_sdc.rcurr < 0) { + read_unlock(&hp_sdc.rtq_lock); + return; + } + curr = hp_sdc.tq[hp_sdc.rcurr]; + read_unlock(&hp_sdc.rtq_lock); + + curr->seq[curr->idx++] = status; + curr->seq[curr->idx++] = data; + hp_sdc.rqty -= 2; + do_gettimeofday(&hp_sdc.rtv); + + if (hp_sdc.rqty <= 0) { + /* All data has been gathered. */ + if(curr->seq[curr->actidx] & HP_SDC_ACT_SEMAPHORE) { + if (curr->act.semaphore) up(curr->act.semaphore); + } + if(curr->seq[curr->actidx] & HP_SDC_ACT_CALLBACK) { + if (curr->act.irqhook) + curr->act.irqhook(irq, dev_id, status, data); + } + curr->actidx = curr->idx; + curr->idx++; + /* Return control of this transaction */ + write_lock(&hp_sdc.rtq_lock); + hp_sdc.rcurr = -1; + hp_sdc.rqty = 0; + write_unlock(&hp_sdc.rtq_lock); + tasklet_schedule(&hp_sdc.task); + } +} + +static irqreturn_t hp_sdc_isr(int irq, void *dev_id, struct pt_regs * regs) { + uint8_t status, data; + + status = hp_sdc_status_in8(); + /* Read data unconditionally to advance i8042. */ + data = hp_sdc_data_in8(); + + /* For now we are ignoring these until we get the SDC to behave. */ + if (((status & 0xf1) == 0x51) && data == 0x82) { + return IRQ_HANDLED; + } + + switch(status & HP_SDC_STATUS_IRQMASK) { + case 0: /* This case is not documented. */ + break; + case HP_SDC_STATUS_USERTIMER: + case HP_SDC_STATUS_PERIODIC: + case HP_SDC_STATUS_TIMER: + read_lock(&hp_sdc.hook_lock); + if (hp_sdc.timer != NULL) + hp_sdc.timer(irq, dev_id, status, data); + read_unlock(&hp_sdc.hook_lock); + break; + case HP_SDC_STATUS_REG: + hp_sdc_take(irq, dev_id, status, data); + break; + case HP_SDC_STATUS_HILCMD: + case HP_SDC_STATUS_HILDATA: + read_lock(&hp_sdc.hook_lock); + if (hp_sdc.hil != NULL) + hp_sdc.hil(irq, dev_id, status, data); + read_unlock(&hp_sdc.hook_lock); + break; + case HP_SDC_STATUS_PUP: + read_lock(&hp_sdc.hook_lock); + if (hp_sdc.pup != NULL) + hp_sdc.pup(irq, dev_id, status, data); + else printk(KERN_INFO PREFIX "HP SDC reports successful PUP.\n"); + read_unlock(&hp_sdc.hook_lock); + break; + default: + read_lock(&hp_sdc.hook_lock); + if (hp_sdc.cooked != NULL) + hp_sdc.cooked(irq, dev_id, status, data); + read_unlock(&hp_sdc.hook_lock); + break; + } + return IRQ_HANDLED; +} + + +static irqreturn_t hp_sdc_nmisr(int irq, void *dev_id, struct pt_regs * regs) { + int status; + + status = hp_sdc_status_in8(); + printk(KERN_WARNING PREFIX "NMI !\n"); + +#if 0 + if (status & HP_SDC_NMISTATUS_FHS) { + read_lock(&hp_sdc.hook_lock); + if (hp_sdc.timer != NULL) + hp_sdc.timer(irq, dev_id, status, 0); + read_unlock(&hp_sdc.hook_lock); + } + else { + /* TODO: pass this on to the HIL handler, or do SAK here? */ + printk(KERN_WARNING PREFIX "HIL NMI\n"); + } +#endif + return IRQ_HANDLED; +} + + +/***************** Kernel (tasklet) context functions ****************/ + +unsigned long hp_sdc_put(void); + +static void hp_sdc_tasklet(unsigned long foo) { + + write_lock_irq(&hp_sdc.rtq_lock); + if (hp_sdc.rcurr >= 0) { + struct timeval tv; + do_gettimeofday(&tv); + if (tv.tv_sec > hp_sdc.rtv.tv_sec) tv.tv_usec += 1000000; + if (tv.tv_usec - hp_sdc.rtv.tv_usec > HP_SDC_MAX_REG_DELAY) { + hp_sdc_transaction *curr; + uint8_t tmp; + + curr = hp_sdc.tq[hp_sdc.rcurr]; + /* If this turns out to be a normal failure mode + * we'll need to figure out a way to communicate + * it back to the application. and be less verbose. + */ + printk(KERN_WARNING PREFIX "read timeout (%ius)!\n", + tv.tv_usec - hp_sdc.rtv.tv_usec); + curr->idx += hp_sdc.rqty; + hp_sdc.rqty = 0; + tmp = curr->seq[curr->actidx]; + curr->seq[curr->actidx] |= HP_SDC_ACT_DEAD; + if(tmp & HP_SDC_ACT_SEMAPHORE) { + if (curr->act.semaphore) + up(curr->act.semaphore); + } + if(tmp & HP_SDC_ACT_CALLBACK) { + /* Note this means that irqhooks may be called + * in tasklet/bh context. + */ + if (curr->act.irqhook) + curr->act.irqhook(0, 0, 0, 0); + } + curr->actidx = curr->idx; + curr->idx++; + hp_sdc.rcurr = -1; + } + } + write_unlock_irq(&hp_sdc.rtq_lock); + hp_sdc_put(); +} + +unsigned long hp_sdc_put(void) { + hp_sdc_transaction *curr; + uint8_t act; + int idx, curridx; + + int limit = 0; + + write_lock(&hp_sdc.lock); + + /* If i8042 buffers are full, we cannot do anything that + requires output, so we skip to the administrativa. */ + if (hp_sdc.ibf) { + hp_sdc_status_in8(); + if (hp_sdc.ibf) goto finish; + } + + anew: + /* See if we are in the middle of a sequence. */ + if (hp_sdc.wcurr < 0) hp_sdc.wcurr = 0; + read_lock_irq(&hp_sdc.rtq_lock); + if (hp_sdc.rcurr == hp_sdc.wcurr) hp_sdc.wcurr++; + read_unlock_irq(&hp_sdc.rtq_lock); + if (hp_sdc.wcurr >= HP_SDC_QUEUE_LEN) hp_sdc.wcurr = 0; + curridx = hp_sdc.wcurr; + + if (hp_sdc.tq[curridx] != NULL) goto start; + + while (++curridx != hp_sdc.wcurr) { + if (curridx >= HP_SDC_QUEUE_LEN) { + curridx = -1; /* Wrap to top */ + continue; + } + read_lock_irq(&hp_sdc.rtq_lock); + if (hp_sdc.rcurr == curridx) { + read_unlock_irq(&hp_sdc.rtq_lock); + continue; + } + read_unlock_irq(&hp_sdc.rtq_lock); + if (hp_sdc.tq[curridx] != NULL) break; /* Found one. */ + } + if (curridx == hp_sdc.wcurr) { /* There's nothing queued to do. */ + curridx = -1; + } + hp_sdc.wcurr = curridx; + + start: + + /* Check to see if the interrupt mask needs to be set. */ + if (hp_sdc.set_im) { + hp_sdc_status_out8(hp_sdc.im | HP_SDC_CMD_SET_IM); + hp_sdc.set_im = 0; + goto finish; + } + + if (hp_sdc.wcurr == -1) goto done; + + curr = hp_sdc.tq[curridx]; + idx = curr->actidx; + + if (curr->actidx >= curr->endidx) { + hp_sdc.tq[curridx] = NULL; + /* Interleave outbound data between the transactions. */ + hp_sdc.wcurr++; + if (hp_sdc.wcurr >= HP_SDC_QUEUE_LEN) hp_sdc.wcurr = 0; + goto finish; + } + + act = curr->seq[idx]; + idx++; + + if (curr->idx >= curr->endidx) { + if (act & HP_SDC_ACT_DEALLOC) kfree(curr); + hp_sdc.tq[curridx] = NULL; + /* Interleave outbound data between the transactions. */ + hp_sdc.wcurr++; + if (hp_sdc.wcurr >= HP_SDC_QUEUE_LEN) hp_sdc.wcurr = 0; + goto finish; + } + + while (act & HP_SDC_ACT_PRECMD) { + if (curr->idx != idx) { + idx++; + act &= ~HP_SDC_ACT_PRECMD; + break; + } + hp_sdc_status_out8(curr->seq[idx]); + curr->idx++; + /* act finished? */ + if ((act & HP_SDC_ACT_DURING) == HP_SDC_ACT_PRECMD) + goto actdone; + /* skip quantity field if data-out sequence follows. */ + if (act & HP_SDC_ACT_DATAOUT) curr->idx++; + goto finish; + } + if (act & HP_SDC_ACT_DATAOUT) { + int qty; + + qty = curr->seq[idx]; + idx++; + if (curr->idx - idx < qty) { + hp_sdc_data_out8(curr->seq[curr->idx]); + curr->idx++; + /* act finished? */ + if ((curr->idx - idx >= qty) && + ((act & HP_SDC_ACT_DURING) == HP_SDC_ACT_DATAOUT)) + goto actdone; + goto finish; + } + idx += qty; + act &= ~HP_SDC_ACT_DATAOUT; + } + else while (act & HP_SDC_ACT_DATAREG) { + int mask; + uint8_t w7[4]; + + mask = curr->seq[idx]; + if (idx != curr->idx) { + idx++; + idx += !!(mask & 1); + idx += !!(mask & 2); + idx += !!(mask & 4); + idx += !!(mask & 8); + act &= ~HP_SDC_ACT_DATAREG; + break; + } + + w7[0] = (mask & 1) ? curr->seq[++idx] : hp_sdc.r7[0]; + w7[1] = (mask & 2) ? curr->seq[++idx] : hp_sdc.r7[1]; + w7[2] = (mask & 4) ? curr->seq[++idx] : hp_sdc.r7[2]; + w7[3] = (mask & 8) ? curr->seq[++idx] : hp_sdc.r7[3]; + + if (hp_sdc.wi > 0x73 || hp_sdc.wi < 0x70 || + w7[hp_sdc.wi-0x70] == hp_sdc.r7[hp_sdc.wi-0x70]) { + int i = 0; + + /* Need to point the write index register */ + while ((i < 4) && w7[i] == hp_sdc.r7[i]) i++; + if (i < 4) { + hp_sdc_status_out8(HP_SDC_CMD_SET_D0 + i); + hp_sdc.wi = 0x70 + i; + goto finish; + } + idx++; + if ((act & HP_SDC_ACT_DURING) == HP_SDC_ACT_DATAREG) + goto actdone; + curr->idx = idx; + act &= ~HP_SDC_ACT_DATAREG; + break; + } + + hp_sdc_data_out8(w7[hp_sdc.wi - 0x70]); + hp_sdc.r7[hp_sdc.wi - 0x70] = w7[hp_sdc.wi - 0x70]; + hp_sdc.wi++; /* write index register autoincrements */ + { + int i = 0; + + while ((i < 4) && w7[i] == hp_sdc.r7[i]) i++; + if (i >= 4) { + curr->idx = idx + 1; + if ((act & HP_SDC_ACT_DURING) == + HP_SDC_ACT_DATAREG) + goto actdone; + } + } + goto finish; + } + /* We don't go any further in the command if there is a pending read, + because we don't want interleaved results. */ + read_lock_irq(&hp_sdc.rtq_lock); + if (hp_sdc.rcurr >= 0) { + read_unlock_irq(&hp_sdc.rtq_lock); + goto finish; + } + read_unlock_irq(&hp_sdc.rtq_lock); + + + if (act & HP_SDC_ACT_POSTCMD) { + uint8_t postcmd; + + /* curr->idx should == idx at this point. */ + postcmd = curr->seq[idx]; + curr->idx++; + if (act & HP_SDC_ACT_DATAIN) { + + /* Start a new read */ + hp_sdc.rqty = curr->seq[curr->idx]; + do_gettimeofday(&hp_sdc.rtv); + curr->idx++; + /* Still need to lock here in case of spurious irq. */ + write_lock_irq(&hp_sdc.rtq_lock); + hp_sdc.rcurr = curridx; + write_unlock_irq(&hp_sdc.rtq_lock); + hp_sdc_status_out8(postcmd); + goto finish; + } + hp_sdc_status_out8(postcmd); + goto actdone; + } + +actdone: + if (act & HP_SDC_ACT_SEMAPHORE) { + up(curr->act.semaphore); + } + else if (act & HP_SDC_ACT_CALLBACK) { + curr->act.irqhook(0,0,0,0); + } + if (curr->idx >= curr->endidx) { /* This transaction is over. */ + if (act & HP_SDC_ACT_DEALLOC) kfree(curr); + hp_sdc.tq[curridx] = NULL; + } + else { + curr->actidx = idx + 1; + curr->idx = idx + 2; + } + /* Interleave outbound data between the transactions. */ + hp_sdc.wcurr++; + if (hp_sdc.wcurr >= HP_SDC_QUEUE_LEN) hp_sdc.wcurr = 0; + + finish: + /* If by some quirk IBF has cleared and our ISR has run to + see that that has happened, do it all again. */ + if (!hp_sdc.ibf && limit++ < 20) goto anew; + + done: + if (hp_sdc.wcurr >= 0) tasklet_schedule(&hp_sdc.task); + write_unlock(&hp_sdc.lock); + return 0; +} + +/******* Functions called in either user or kernel context ****/ +int hp_sdc_enqueue_transaction(hp_sdc_transaction *this) { + unsigned long flags; + int i; + + if (this == NULL) { + tasklet_schedule(&hp_sdc.task); + return -EINVAL; + }; + + write_lock_irqsave(&hp_sdc.lock, flags); + + /* Can't have same transaction on queue twice */ + for (i=0; i < HP_SDC_QUEUE_LEN; i++) + if (hp_sdc.tq[i] == this) goto fail; + + this->actidx = 0; + this->idx = 1; + + /* Search for empty slot */ + for (i=0; i < HP_SDC_QUEUE_LEN; i++) { + if (hp_sdc.tq[i] == NULL) { + hp_sdc.tq[i] = this; + write_unlock_irqrestore(&hp_sdc.lock, flags); + tasklet_schedule(&hp_sdc.task); + return 0; + } + } + write_unlock_irqrestore(&hp_sdc.lock, flags); + printk(KERN_WARNING PREFIX "No free slot to add transaction.\n"); + return -EBUSY; + + fail: + write_unlock_irqrestore(&hp_sdc.lock,flags); + printk(KERN_WARNING PREFIX "Transaction add failed: transaction already queued?\n"); + return -EINVAL; +} + +int hp_sdc_dequeue_transaction(hp_sdc_transaction *this) { + unsigned long flags; + int i; + + write_lock_irqsave(&hp_sdc.lock, flags); + + /* TODO: don't remove it if it's not done. */ + + for (i=0; i < HP_SDC_QUEUE_LEN; i++) + if (hp_sdc.tq[i] == this) hp_sdc.tq[i] = NULL; + + write_unlock_irqrestore(&hp_sdc.lock, flags); + return 0; +} + + + +/********************** User context functions **************************/ +int hp_sdc_request_timer_irq(hp_sdc_irqhook *callback) { + + if (callback == NULL || hp_sdc.dev == NULL) { + return -EINVAL; + } + write_lock_irq(&hp_sdc.hook_lock); + if (hp_sdc.timer != NULL) { + write_unlock_irq(&hp_sdc.hook_lock); + return -EBUSY; + } + + hp_sdc.timer = callback; + /* Enable interrupts from the timers */ + hp_sdc.im &= ~HP_SDC_IM_FH; + hp_sdc.im &= ~HP_SDC_IM_PT; + hp_sdc.im &= ~HP_SDC_IM_TIMERS; + hp_sdc.set_im = 1; + write_unlock_irq(&hp_sdc.hook_lock); + + tasklet_schedule(&hp_sdc.task); + + return 0; +} + +int hp_sdc_request_hil_irq(hp_sdc_irqhook *callback) { + + if (callback == NULL || hp_sdc.dev == NULL) { + return -EINVAL; + } + write_lock_irq(&hp_sdc.hook_lock); + if (hp_sdc.hil != NULL) { + write_unlock_irq(&hp_sdc.hook_lock); + return -EBUSY; + } + + hp_sdc.hil = callback; + hp_sdc.im &= ~(HP_SDC_IM_HIL | HP_SDC_IM_RESET); + hp_sdc.set_im = 1; + write_unlock_irq(&hp_sdc.hook_lock); + + tasklet_schedule(&hp_sdc.task); + + return 0; +} + +int hp_sdc_request_cooked_irq(hp_sdc_irqhook *callback) { + + if (callback == NULL || hp_sdc.dev == NULL) { + return -EINVAL; + } + write_lock_irq(&hp_sdc.hook_lock); + if (hp_sdc.cooked != NULL) { + write_unlock_irq(&hp_sdc.hook_lock); + return -EBUSY; + } + + /* Enable interrupts from the HIL MLC */ + hp_sdc.cooked = callback; + hp_sdc.im &= ~(HP_SDC_IM_HIL | HP_SDC_IM_RESET); + hp_sdc.set_im = 1; + write_unlock_irq(&hp_sdc.hook_lock); + + tasklet_schedule(&hp_sdc.task); + + return 0; +} + +int hp_sdc_release_timer_irq(hp_sdc_irqhook *callback) { + + + write_lock_irq(&hp_sdc.hook_lock); + if ((callback != hp_sdc.timer) || + (hp_sdc.timer == NULL)) { + write_unlock_irq(&hp_sdc.hook_lock); + return -EINVAL; + } + + /* Disable interrupts from the timers */ + hp_sdc.timer = NULL; + hp_sdc.im |= HP_SDC_IM_TIMERS; + hp_sdc.im |= HP_SDC_IM_FH; + hp_sdc.im |= HP_SDC_IM_PT; + hp_sdc.set_im = 1; + write_unlock_irq(&hp_sdc.hook_lock); + tasklet_schedule(&hp_sdc.task); + + return 0; +} + +int hp_sdc_release_hil_irq(hp_sdc_irqhook *callback) { + + write_lock_irq(&hp_sdc.hook_lock); + if ((callback != hp_sdc.hil) || + (hp_sdc.hil == NULL)) { + write_unlock_irq(&hp_sdc.hook_lock); + return -EINVAL; + } + + hp_sdc.hil = NULL; + /* Disable interrupts from HIL only if there is no cooked driver. */ + if(hp_sdc.cooked == NULL) { + hp_sdc.im |= (HP_SDC_IM_HIL | HP_SDC_IM_RESET); + hp_sdc.set_im = 1; + } + write_unlock_irq(&hp_sdc.hook_lock); + tasklet_schedule(&hp_sdc.task); + + return 0; +} + +int hp_sdc_release_cooked_irq(hp_sdc_irqhook *callback) { + + write_lock_irq(&hp_sdc.hook_lock); + if ((callback != hp_sdc.cooked) || + (hp_sdc.cooked == NULL)) { + write_unlock_irq(&hp_sdc.hook_lock); + return -EINVAL; + } + + hp_sdc.cooked = NULL; + /* Disable interrupts from HIL only if there is no raw HIL driver. */ + if(hp_sdc.hil == NULL) { + hp_sdc.im |= (HP_SDC_IM_HIL | HP_SDC_IM_RESET); + hp_sdc.set_im = 1; + } + write_unlock_irq(&hp_sdc.hook_lock); + tasklet_schedule(&hp_sdc.task); + + return 0; +} + +/************************* Keepalive timer task *********************/ + +void hp_sdc_kicker (unsigned long data) { + tasklet_schedule(&hp_sdc.task); + /* Re-insert the periodic task. */ + mod_timer(&hp_sdc.kicker, jiffies + HZ); +} + +/************************** Module Initialization ***************************/ + +#if defined(__hppa__) + +static struct parisc_device_id hp_sdc_tbl[] = { + { + .hw_type = HPHW_FIO, + .hversion_rev = HVERSION_REV_ANY_ID, + .hversion = HVERSION_ANY_ID, + .sversion = 0x73, + }, + { 0, } +}; + +MODULE_DEVICE_TABLE(parisc, hp_sdc_tbl); + +static int __init hp_sdc_init_hppa(struct parisc_device *d); + +static struct parisc_driver hp_sdc_driver = { + .name = "HP SDC", + .id_table = hp_sdc_tbl, + .probe = hp_sdc_init_hppa, +}; + +#endif /* __hppa__ */ + +static int __init hp_sdc_init(void) +{ + int i; + char *errstr; + hp_sdc_transaction t_sync; + uint8_t ts_sync[6]; + struct semaphore s_sync; + + rwlock_init(&hp_sdc.lock); + rwlock_init(&hp_sdc.ibf_lock); + rwlock_init(&hp_sdc.rtq_lock); + rwlock_init(&hp_sdc.hook_lock); + + hp_sdc.timer = NULL; + hp_sdc.hil = NULL; + hp_sdc.pup = NULL; + hp_sdc.cooked = NULL; + hp_sdc.im = HP_SDC_IM_MASK; /* Mask maskable irqs */ + hp_sdc.set_im = 1; + hp_sdc.wi = 0xff; + hp_sdc.r7[0] = 0xff; + hp_sdc.r7[1] = 0xff; + hp_sdc.r7[2] = 0xff; + hp_sdc.r7[3] = 0xff; + hp_sdc.ibf = 1; + + for (i = 0; i < HP_SDC_QUEUE_LEN; i++) hp_sdc.tq[i] = NULL; + hp_sdc.wcurr = -1; + hp_sdc.rcurr = -1; + hp_sdc.rqty = 0; + + hp_sdc.dev_err = -ENODEV; + + errstr = "IO not found for"; + if (!hp_sdc.base_io) goto err0; + + errstr = "IRQ not found for"; + if (!hp_sdc.irq) goto err0; + + hp_sdc.dev_err = -EBUSY; + +#if defined(__hppa__) + errstr = "IO not available for"; + if (request_region(hp_sdc.data_io, 2, hp_sdc_driver.name)) goto err0; +#endif + + errstr = "IRQ not available for"; + if(request_irq(hp_sdc.irq, &hp_sdc_isr, 0, "HP SDC", + (void *) hp_sdc.base_io)) goto err1; + + errstr = "NMI not available for"; + if (request_irq(hp_sdc.nmi, &hp_sdc_nmisr, 0, "HP SDC NMI", + (void *) hp_sdc.base_io)) goto err2; + + printk(KERN_INFO PREFIX "HP SDC at 0x%p, IRQ %d (NMI IRQ %d)\n", + (void *)hp_sdc.base_io, hp_sdc.irq, hp_sdc.nmi); + + hp_sdc_status_in8(); + hp_sdc_data_in8(); + + tasklet_init(&hp_sdc.task, hp_sdc_tasklet, 0); + + /* Sync the output buffer registers, thus scheduling hp_sdc_tasklet. */ + t_sync.actidx = 0; + t_sync.idx = 1; + t_sync.endidx = 6; + t_sync.seq = ts_sync; + ts_sync[0] = HP_SDC_ACT_DATAREG | HP_SDC_ACT_SEMAPHORE; + ts_sync[1] = 0x0f; + ts_sync[2] = ts_sync[3] = ts_sync[4] = ts_sync[5] = 0; + t_sync.act.semaphore = &s_sync; + init_MUTEX_LOCKED(&s_sync); + hp_sdc_enqueue_transaction(&t_sync); + down(&s_sync); /* Wait for t_sync to complete */ + + /* Create the keepalive task */ + init_timer(&hp_sdc.kicker); + hp_sdc.kicker.expires = jiffies + HZ; + hp_sdc.kicker.function = &hp_sdc_kicker; + add_timer(&hp_sdc.kicker); + + hp_sdc.dev_err = 0; + return 0; + err2: + free_irq(hp_sdc.irq, NULL); + err1: + release_region(hp_sdc.data_io, 2); + err0: + printk(KERN_WARNING PREFIX ": %s SDC IO=0x%p IRQ=0x%x NMI=0x%x\n", + errstr, (void *)hp_sdc.base_io, hp_sdc.irq, hp_sdc.nmi); + hp_sdc.dev = NULL; + return hp_sdc.dev_err; +} + +#if defined(__hppa__) + +static int __init hp_sdc_init_hppa(struct parisc_device *d) +{ + if (!d) return 1; + if (hp_sdc.dev != NULL) return 1; /* We only expect one SDC */ + + hp_sdc.dev = d; + hp_sdc.irq = d->irq; + hp_sdc.nmi = d->aux_irq; + hp_sdc.base_io = d->hpa; + hp_sdc.data_io = d->hpa + 0x800; + hp_sdc.status_io = d->hpa + 0x801; + + return hp_sdc_init(); +} + +#endif /* __hppa__ */ + +#if !defined(__mc68000__) /* Link error on m68k! */ +static void __exit hp_sdc_exit(void) +#else +static void hp_sdc_exit(void) +#endif +{ + write_lock_irq(&hp_sdc.lock); + + /* Turn off all maskable "sub-function" irq's. */ + hp_sdc_spin_ibf(); + sdc_writeb(HP_SDC_CMD_SET_IM | HP_SDC_IM_MASK, hp_sdc.status_io); + + /* Wait until we know this has been processed by the i8042 */ + hp_sdc_spin_ibf(); + + free_irq(hp_sdc.nmi, NULL); + free_irq(hp_sdc.irq, NULL); + write_unlock_irq(&hp_sdc.lock); + + del_timer(&hp_sdc.kicker); + + tasklet_kill(&hp_sdc.task); + +/* release_region(hp_sdc.data_io, 2); */ + +#if defined(__hppa__) + if (unregister_parisc_driver(&hp_sdc_driver)) + printk(KERN_WARNING PREFIX "Error unregistering HP SDC"); +#endif +} + +static int __init hp_sdc_register(void) +{ + hp_sdc_transaction tq_init; + uint8_t tq_init_seq[5]; + struct semaphore tq_init_sem; +#if defined(__mc68000__) + mm_segment_t fs; + unsigned char i; +#endif + + hp_sdc.dev = NULL; + hp_sdc.dev_err = 0; +#if defined(__hppa__) + if (register_parisc_driver(&hp_sdc_driver)) { + printk(KERN_WARNING PREFIX "Error registering SDC with system bus tree.\n"); + return -ENODEV; + } +#elif defined(__mc68000__) + if (!MACH_IS_HP300) + return -ENODEV; + + hp_sdc.irq = 1; + hp_sdc.nmi = 7; + hp_sdc.base_io = (unsigned long) 0xf0428000; + hp_sdc.data_io = (unsigned long) hp_sdc.base_io + 1; + hp_sdc.status_io = (unsigned long) hp_sdc.base_io + 3; + fs = get_fs(); + set_fs(KERNEL_DS); + if (!get_user(i, (unsigned char *)hp_sdc.data_io)) + hp_sdc.dev = (void *)1; + set_fs(fs); + hp_sdc.dev_err = hp_sdc_init(); +#endif + if (hp_sdc.dev == NULL) { + printk(KERN_WARNING PREFIX "No SDC found.\n"); + return hp_sdc.dev_err; + } + + init_MUTEX_LOCKED(&tq_init_sem); + + tq_init.actidx = 0; + tq_init.idx = 1; + tq_init.endidx = 5; + tq_init.seq = tq_init_seq; + tq_init.act.semaphore = &tq_init_sem; + + tq_init_seq[0] = + HP_SDC_ACT_POSTCMD | HP_SDC_ACT_DATAIN | HP_SDC_ACT_SEMAPHORE; + tq_init_seq[1] = HP_SDC_CMD_READ_KCC; + tq_init_seq[2] = 1; + tq_init_seq[3] = 0; + tq_init_seq[4] = 0; + + hp_sdc_enqueue_transaction(&tq_init); + + down(&tq_init_sem); + up(&tq_init_sem); + + if ((tq_init_seq[0] & HP_SDC_ACT_DEAD) == HP_SDC_ACT_DEAD) { + printk(KERN_WARNING PREFIX "Error reading config byte.\n"); + hp_sdc_exit(); + return -ENODEV; + } + hp_sdc.r11 = tq_init_seq[4]; + if (hp_sdc.r11 & HP_SDC_CFG_NEW) { + char *str; + printk(KERN_INFO PREFIX "New style SDC\n"); + tq_init_seq[1] = HP_SDC_CMD_READ_XTD; + tq_init.actidx = 0; + tq_init.idx = 1; + down(&tq_init_sem); + hp_sdc_enqueue_transaction(&tq_init); + down(&tq_init_sem); + up(&tq_init_sem); + if ((tq_init_seq[0] & HP_SDC_ACT_DEAD) == HP_SDC_ACT_DEAD) { + printk(KERN_WARNING PREFIX "Error reading extended config byte.\n"); + return -ENODEV; + } + hp_sdc.r7e = tq_init_seq[4]; + HP_SDC_XTD_REV_STRINGS(hp_sdc.r7e & HP_SDC_XTD_REV, str) + printk(KERN_INFO PREFIX "Revision: %s\n", str); + if (hp_sdc.r7e & HP_SDC_XTD_BEEPER) { + printk(KERN_INFO PREFIX "TI SN76494 beeper present\n"); + } + if (hp_sdc.r7e & HP_SDC_XTD_BBRTC) { + printk(KERN_INFO PREFIX "OKI MSM-58321 BBRTC present\n"); + } + printk(KERN_INFO PREFIX "Spunking the self test register to force PUP " + "on next firmware reset.\n"); + tq_init_seq[0] = HP_SDC_ACT_PRECMD | + HP_SDC_ACT_DATAOUT | HP_SDC_ACT_SEMAPHORE; + tq_init_seq[1] = HP_SDC_CMD_SET_STR; + tq_init_seq[2] = 1; + tq_init_seq[3] = 0; + tq_init.actidx = 0; + tq_init.idx = 1; + tq_init.endidx = 4; + down(&tq_init_sem); + hp_sdc_enqueue_transaction(&tq_init); + down(&tq_init_sem); + up(&tq_init_sem); + } + else { + printk(KERN_INFO PREFIX "Old style SDC (1820-%s).\n", + (hp_sdc.r11 & HP_SDC_CFG_REV) ? "3300" : "2564/3087"); + } + + return 0; +} + +module_init(hp_sdc_register); +module_exit(hp_sdc_exit); + +/* Timing notes: These measurements taken on my 64MHz 7100-LC (715/64) + * cycles cycles-adj time + * between two consecutive mfctl(16)'s: 4 n/a 63ns + * hp_sdc_spin_ibf when idle: 119 115 1.7us + * gsc_writeb status register: 83 79 1.2us + * IBF to clear after sending SET_IM: 6204 6006 93us + * IBF to clear after sending LOAD_RT: 4467 4352 68us + * IBF to clear after sending two LOAD_RTs: 18974 18859 295us + * READ_T1, read status/data, IRQ, call handler: 35564 n/a 556us + * cmd to ~IBF READ_T1 2nd time right after: 5158403 n/a 81ms + * between IRQ received and ~IBF for above: 2578877 n/a 40ms + * + * Performance stats after a run of this module configuring HIL and + * receiving a few mouse events: + * + * status in8 282508 cycles 7128 calls + * status out8 8404 cycles 341 calls + * data out8 1734 cycles 78 calls + * isr 174324 cycles 617 calls (includes take) + * take 1241 cycles 2 calls + * put 1411504 cycles 6937 calls + * task 1655209 cycles 6937 calls (includes put) + * + */ diff --git a/drivers/input/serio/hp_sdc_mlc.c b/drivers/input/serio/hp_sdc_mlc.c new file mode 100644 index 00000000000..e3c44ffae67 --- /dev/null +++ b/drivers/input/serio/hp_sdc_mlc.c @@ -0,0 +1,358 @@ +/* + * Access to HP-HIL MLC through HP System Device Controller. + * + * Copyright (c) 2001 Brian S. Julin + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * Alternatively, this software may be distributed under the terms of the + * GNU General Public License ("GPL"). + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * + * References: + * HP-HIL Technical Reference Manual. Hewlett Packard Product No. 45918A + * System Device Controller Microprocessor Firmware Theory of Operation + * for Part Number 1820-4784 Revision B. Dwg No. A-1820-4784-2 + * + */ + +#include <linux/hil_mlc.h> +#include <linux/hp_sdc.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/string.h> + +#define PREFIX "HP SDC MLC: " + +static hil_mlc hp_sdc_mlc; + +MODULE_AUTHOR("Brian S. Julin <bri@calyx.com>"); +MODULE_DESCRIPTION("Glue for onboard HIL MLC in HP-PARISC machines"); +MODULE_LICENSE("Dual BSD/GPL"); + +struct hp_sdc_mlc_priv_s { + int emtestmode; + hp_sdc_transaction trans; + u8 tseq[16]; + int got5x; +} hp_sdc_mlc_priv; + +/************************* Interrupt context ******************************/ +static void hp_sdc_mlc_isr (int irq, void *dev_id, + uint8_t status, uint8_t data) { + int idx; + hil_mlc *mlc = &hp_sdc_mlc; + + write_lock(&(mlc->lock)); + if (mlc->icount < 0) { + printk(KERN_WARNING PREFIX "HIL Overflow!\n"); + up(&mlc->isem); + goto out; + } + idx = 15 - mlc->icount; + if ((status & HP_SDC_STATUS_IRQMASK) == HP_SDC_STATUS_HILDATA) { + mlc->ipacket[idx] |= data | HIL_ERR_INT; + mlc->icount--; + if (hp_sdc_mlc_priv.got5x) goto check; + if (!idx) goto check; + if ((mlc->ipacket[idx-1] & HIL_PKT_ADDR_MASK) != + (mlc->ipacket[idx] & HIL_PKT_ADDR_MASK)) { + mlc->ipacket[idx] &= ~HIL_PKT_ADDR_MASK; + mlc->ipacket[idx] |= (mlc->ipacket[idx-1] + & HIL_PKT_ADDR_MASK); + } + goto check; + } + /* We know status is 5X */ + if (data & HP_SDC_HIL_ISERR) goto err; + mlc->ipacket[idx] = + (data & HP_SDC_HIL_R1MASK) << HIL_PKT_ADDR_SHIFT; + hp_sdc_mlc_priv.got5x = 1; + goto out; + + check: + hp_sdc_mlc_priv.got5x = 0; + if (mlc->imatch == 0) goto done; + if ((mlc->imatch == (HIL_ERR_INT | HIL_PKT_CMD | HIL_CMD_POL)) + && (mlc->ipacket[idx] == (mlc->imatch | idx))) goto done; + if (mlc->ipacket[idx] == mlc->imatch) goto done; + goto out; + + err: + printk(KERN_DEBUG PREFIX "err code %x\n", data); + switch (data) { + case HP_SDC_HIL_RC_DONE: + printk(KERN_WARNING PREFIX "Bastard SDC reconfigured loop!\n"); + break; + case HP_SDC_HIL_ERR: + mlc->ipacket[idx] |= HIL_ERR_INT | HIL_ERR_PERR | + HIL_ERR_FERR | HIL_ERR_FOF; + break; + case HP_SDC_HIL_TO: + mlc->ipacket[idx] |= HIL_ERR_INT | HIL_ERR_LERR; + break; + case HP_SDC_HIL_RC: + printk(KERN_WARNING PREFIX "Bastard SDC decided to reconfigure loop!\n"); + break; + default: + printk(KERN_WARNING PREFIX "Unkown HIL Error status (%x)!\n", data); + break; + } + /* No more data will be coming due to an error. */ + done: + tasklet_schedule(mlc->tasklet); + up(&(mlc->isem)); + out: + write_unlock(&(mlc->lock)); +} + + +/******************** Tasklet or userspace context functions ****************/ + +static int hp_sdc_mlc_in (hil_mlc *mlc, suseconds_t timeout) { + unsigned long flags; + struct hp_sdc_mlc_priv_s *priv; + int rc = 2; + + priv = mlc->priv; + + write_lock_irqsave(&(mlc->lock), flags); + + /* Try to down the semaphore */ + if (down_trylock(&(mlc->isem))) { + struct timeval tv; + if (priv->emtestmode) { + mlc->ipacket[0] = + HIL_ERR_INT | (mlc->opacket & + (HIL_PKT_CMD | + HIL_PKT_ADDR_MASK | + HIL_PKT_DATA_MASK)); + mlc->icount = 14; + /* printk(KERN_DEBUG PREFIX ">[%x]\n", mlc->ipacket[0]); */ + goto wasup; + } + do_gettimeofday(&tv); + tv.tv_usec += 1000000 * (tv.tv_sec - mlc->instart.tv_sec); + if (tv.tv_usec - mlc->instart.tv_usec > mlc->intimeout) { + /* printk("!%i %i", + tv.tv_usec - mlc->instart.tv_usec, + mlc->intimeout); + */ + rc = 1; + up(&(mlc->isem)); + } + goto done; + } + wasup: + up(&(mlc->isem)); + rc = 0; + goto done; + done: + write_unlock_irqrestore(&(mlc->lock), flags); + return rc; +} + +static int hp_sdc_mlc_cts (hil_mlc *mlc) { + struct hp_sdc_mlc_priv_s *priv; + unsigned long flags; + + priv = mlc->priv; + + write_lock_irqsave(&(mlc->lock), flags); + + /* Try to down the semaphores -- they should be up. */ + if (down_trylock(&(mlc->isem))) { + BUG(); + goto busy; + } + if (down_trylock(&(mlc->osem))) { + BUG(); + up(&(mlc->isem)); + goto busy; + } + up(&(mlc->isem)); + up(&(mlc->osem)); + + if (down_trylock(&(mlc->csem))) { + if (priv->trans.act.semaphore != &(mlc->csem)) goto poll; + goto busy; + } + if (!(priv->tseq[4] & HP_SDC_USE_LOOP)) goto done; + + poll: + priv->trans.act.semaphore = &(mlc->csem); + priv->trans.actidx = 0; + priv->trans.idx = 1; + priv->trans.endidx = 5; + priv->tseq[0] = + HP_SDC_ACT_POSTCMD | HP_SDC_ACT_DATAIN | HP_SDC_ACT_SEMAPHORE; + priv->tseq[1] = HP_SDC_CMD_READ_USE; + priv->tseq[2] = 1; + priv->tseq[3] = 0; + priv->tseq[4] = 0; + hp_sdc_enqueue_transaction(&(priv->trans)); + busy: + write_unlock_irqrestore(&(mlc->lock), flags); + return 1; + done: + priv->trans.act.semaphore = &(mlc->osem); + up(&(mlc->csem)); + write_unlock_irqrestore(&(mlc->lock), flags); + return 0; +} + +static void hp_sdc_mlc_out (hil_mlc *mlc) { + struct hp_sdc_mlc_priv_s *priv; + unsigned long flags; + + priv = mlc->priv; + + write_lock_irqsave(&(mlc->lock), flags); + + /* Try to down the semaphore -- it should be up. */ + if (down_trylock(&(mlc->osem))) { + BUG(); + goto done; + } + + if (mlc->opacket & HIL_DO_ALTER_CTRL) goto do_control; + + do_data: + if (priv->emtestmode) { + up(&(mlc->osem)); + goto done; + } + /* Shouldn't be sending commands when loop may be busy */ + if (down_trylock(&(mlc->csem))) { + BUG(); + goto done; + } + up(&(mlc->csem)); + + priv->trans.actidx = 0; + priv->trans.idx = 1; + priv->trans.act.semaphore = &(mlc->osem); + priv->trans.endidx = 6; + priv->tseq[0] = + HP_SDC_ACT_DATAREG | HP_SDC_ACT_POSTCMD | HP_SDC_ACT_SEMAPHORE; + priv->tseq[1] = 0x7; + priv->tseq[2] = + (mlc->opacket & + (HIL_PKT_ADDR_MASK | HIL_PKT_CMD)) + >> HIL_PKT_ADDR_SHIFT; + priv->tseq[3] = + (mlc->opacket & HIL_PKT_DATA_MASK) + >> HIL_PKT_DATA_SHIFT; + priv->tseq[4] = 0; /* No timeout */ + if (priv->tseq[3] == HIL_CMD_DHR) priv->tseq[4] = 1; + priv->tseq[5] = HP_SDC_CMD_DO_HIL; + goto enqueue; + + do_control: + priv->emtestmode = mlc->opacket & HIL_CTRL_TEST; + if ((mlc->opacket & (HIL_CTRL_APE | HIL_CTRL_IPF)) == HIL_CTRL_APE) { + BUG(); /* we cannot emulate this, it should not be used. */ + } + if ((mlc->opacket & HIL_CTRL_ONLY) == HIL_CTRL_ONLY) goto control_only; + if (mlc->opacket & HIL_CTRL_APE) { + BUG(); /* Should not send command/data after engaging APE */ + goto done; + } + /* Disengaging APE this way would not be valid either since + * the loop must be allowed to idle. + * + * So, it works out that we really never actually send control + * and data when using SDC, we just send the data. + */ + goto do_data; + + control_only: + priv->trans.actidx = 0; + priv->trans.idx = 1; + priv->trans.act.semaphore = &(mlc->osem); + priv->trans.endidx = 4; + priv->tseq[0] = + HP_SDC_ACT_PRECMD | HP_SDC_ACT_DATAOUT | HP_SDC_ACT_SEMAPHORE; + priv->tseq[1] = HP_SDC_CMD_SET_LPC; + priv->tseq[2] = 1; + // priv->tseq[3] = (mlc->ddc + 1) | HP_SDC_LPS_ACSUCC; + priv->tseq[3] = 0; + if (mlc->opacket & HIL_CTRL_APE) { + priv->tseq[3] |= HP_SDC_LPC_APE_IPF; + down_trylock(&(mlc->csem)); + } + enqueue: + hp_sdc_enqueue_transaction(&(priv->trans)); + done: + write_unlock_irqrestore(&(mlc->lock), flags); +} + +static int __init hp_sdc_mlc_init(void) +{ + hil_mlc *mlc = &hp_sdc_mlc; + + printk(KERN_INFO PREFIX "Registering the System Domain Controller's HIL MLC.\n"); + + hp_sdc_mlc_priv.emtestmode = 0; + hp_sdc_mlc_priv.trans.seq = hp_sdc_mlc_priv.tseq; + hp_sdc_mlc_priv.trans.act.semaphore = &(mlc->osem); + hp_sdc_mlc_priv.got5x = 0; + + mlc->cts = &hp_sdc_mlc_cts; + mlc->in = &hp_sdc_mlc_in; + mlc->out = &hp_sdc_mlc_out; + + if (hil_mlc_register(mlc)) { + printk(KERN_WARNING PREFIX "Failed to register MLC structure with hil_mlc\n"); + goto err0; + } + mlc->priv = &hp_sdc_mlc_priv; + + if (hp_sdc_request_hil_irq(&hp_sdc_mlc_isr)) { + printk(KERN_WARNING PREFIX "Request for raw HIL ISR hook denied\n"); + goto err1; + } + return 0; + err1: + if (hil_mlc_unregister(mlc)) { + printk(KERN_ERR PREFIX "Failed to unregister MLC structure with hil_mlc.\n" + "This is bad. Could cause an oops.\n"); + } + err0: + return -EBUSY; +} + +static void __exit hp_sdc_mlc_exit(void) +{ + hil_mlc *mlc = &hp_sdc_mlc; + if (hp_sdc_release_hil_irq(&hp_sdc_mlc_isr)) { + printk(KERN_ERR PREFIX "Failed to release the raw HIL ISR hook.\n" + "This is bad. Could cause an oops.\n"); + } + if (hil_mlc_unregister(mlc)) { + printk(KERN_ERR PREFIX "Failed to unregister MLC structure with hil_mlc.\n" + "This is bad. Could cause an oops.\n"); + } +} + +module_init(hp_sdc_mlc_init); +module_exit(hp_sdc_mlc_exit); diff --git a/drivers/input/serio/i8042-io.h b/drivers/input/serio/i8042-io.h new file mode 100644 index 00000000000..c9e633d21d9 --- /dev/null +++ b/drivers/input/serio/i8042-io.h @@ -0,0 +1,93 @@ +#ifndef _I8042_IO_H +#define _I8042_IO_H + +/* + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +/* + * Names. + */ + +#define I8042_KBD_PHYS_DESC "isa0060/serio0" +#define I8042_AUX_PHYS_DESC "isa0060/serio1" +#define I8042_MUX_PHYS_DESC "isa0060/serio%d" + +/* + * IRQs. + */ + +#ifdef __alpha__ +# define I8042_KBD_IRQ 1 +# define I8042_AUX_IRQ (RTC_PORT(0) == 0x170 ? 9 : 12) /* Jensen is special */ +#elif defined(__arm__) +/* defined in include/asm-arm/arch-xxx/irqs.h */ +#include <asm/irq.h> +#elif defined(CONFIG_SUPERH64) +#include <asm/irq.h> +#else +# define I8042_KBD_IRQ 1 +# define I8042_AUX_IRQ 12 +#endif + + +/* + * Register numbers. + */ + +#define I8042_COMMAND_REG 0x64 +#define I8042_STATUS_REG 0x64 +#define I8042_DATA_REG 0x60 + +static inline int i8042_read_data(void) +{ + return inb(I8042_DATA_REG); +} + +static inline int i8042_read_status(void) +{ + return inb(I8042_STATUS_REG); +} + +static inline void i8042_write_data(int val) +{ + outb(val, I8042_DATA_REG); +} + +static inline void i8042_write_command(int val) +{ + outb(val, I8042_COMMAND_REG); +} + +static inline int i8042_platform_init(void) +{ +/* + * On some platforms touching the i8042 data register region can do really + * bad things. Because of this the region is always reserved on such boxes. + */ +#if !defined(__sh__) && !defined(__alpha__) && !defined(__mips__) && !defined(CONFIG_PPC64) + if (!request_region(I8042_DATA_REG, 16, "i8042")) + return -1; +#endif + + i8042_reset = 1; + +#if defined(CONFIG_PPC64) + if (check_legacy_ioport(I8042_DATA_REG)) + return -1; + if (!request_region(I8042_DATA_REG, 16, "i8042")) + return -1; +#endif + return 0; +} + +static inline void i8042_platform_exit(void) +{ +#if !defined(__sh__) && !defined(__alpha__) && !defined(CONFIG_PPC64) + release_region(I8042_DATA_REG, 16); +#endif +} + +#endif /* _I8042_IO_H */ diff --git a/drivers/input/serio/i8042-ip22io.h b/drivers/input/serio/i8042-ip22io.h new file mode 100644 index 00000000000..863b9c95fbb --- /dev/null +++ b/drivers/input/serio/i8042-ip22io.h @@ -0,0 +1,76 @@ +#ifndef _I8042_IP22_H +#define _I8042_IP22_H + +#include <asm/sgi/ioc.h> +#include <asm/sgi/ip22.h> + +/* + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +/* + * Names. + */ + +#define I8042_KBD_PHYS_DESC "hpc3ps2/serio0" +#define I8042_AUX_PHYS_DESC "hpc3ps2/serio1" +#define I8042_MUX_PHYS_DESC "hpc3ps2/serio%d" + +/* + * IRQs. + */ + +#define I8042_KBD_IRQ SGI_KEYBD_IRQ +#define I8042_AUX_IRQ SGI_KEYBD_IRQ + +/* + * Register numbers. + */ + +#define I8042_COMMAND_REG ((unsigned long)&sgioc->kbdmouse.command) +#define I8042_STATUS_REG ((unsigned long)&sgioc->kbdmouse.command) +#define I8042_DATA_REG ((unsigned long)&sgioc->kbdmouse.data) + +static inline int i8042_read_data(void) +{ + return sgioc->kbdmouse.data; +} + +static inline int i8042_read_status(void) +{ + return sgioc->kbdmouse.command; +} + +static inline void i8042_write_data(int val) +{ + sgioc->kbdmouse.data = val; +} + +static inline void i8042_write_command(int val) +{ + sgioc->kbdmouse.command = val; +} + +static inline int i8042_platform_init(void) +{ +#if 0 + /* XXX sgi_kh is a virtual address */ + if (!request_mem_region(sgi_kh, sizeof(struct hpc_keyb), "i8042")) + return 1; +#endif + + i8042_reset = 1; + + return 0; +} + +static inline void i8042_platform_exit(void) +{ +#if 0 + release_mem_region(JAZZ_KEYBOARD_ADDRESS, sizeof(struct hpc_keyb)); +#endif +} + +#endif /* _I8042_IP22_H */ diff --git a/drivers/input/serio/i8042-jazzio.h b/drivers/input/serio/i8042-jazzio.h new file mode 100644 index 00000000000..5c20ab13148 --- /dev/null +++ b/drivers/input/serio/i8042-jazzio.h @@ -0,0 +1,69 @@ +#ifndef _I8042_JAZZ_H +#define _I8042_JAZZ_H + +#include <asm/jazz.h> + +/* + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +/* + * Names. + */ + +#define I8042_KBD_PHYS_DESC "R4030/serio0" +#define I8042_AUX_PHYS_DESC "R4030/serio1" +#define I8042_MUX_PHYS_DESC "R4030/serio%d" + +/* + * IRQs. + */ + +#define I8042_KBD_IRQ JAZZ_KEYBOARD_IRQ +#define I8042_AUX_IRQ JAZZ_MOUSE_IRQ + +#define I8042_COMMAND_REG ((unsigned long)&jazz_kh->command) +#define I8042_STATUS_REG ((unsigned long)&jazz_kh->command) +#define I8042_DATA_REG ((unsigned long)&jazz_kh->data) + +static inline int i8042_read_data(void) +{ + return jazz_kh->data; +} + +static inline int i8042_read_status(void) +{ + return jazz_kh->command; +} + +static inline void i8042_write_data(int val) +{ + jazz_kh->data = val; +} + +static inline void i8042_write_command(int val) +{ + jazz_kh->command = val; +} + +static inline int i8042_platform_init(void) +{ +#if 0 + /* XXX JAZZ_KEYBOARD_ADDRESS is a virtual address */ + if (!request_mem_region(JAZZ_KEYBOARD_ADDRESS, 2, "i8042")) + return 1; +#endif + + return 0; +} + +static inline void i8042_platform_exit(void) +{ +#if 0 + release_mem_region(JAZZ_KEYBOARD_ADDRESS, 2); +#endif +} + +#endif /* _I8042_JAZZ_H */ diff --git a/drivers/input/serio/i8042-ppcio.h b/drivers/input/serio/i8042-ppcio.h new file mode 100644 index 00000000000..2906e1b60c0 --- /dev/null +++ b/drivers/input/serio/i8042-ppcio.h @@ -0,0 +1,136 @@ +#ifndef _I8042_PPCIO_H +#define _I8042_PPCIO_H + +/* + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +#if defined(CONFIG_WALNUT) + +#define I8042_KBD_IRQ 25 +#define I8042_AUX_IRQ 26 + +#define I8042_KBD_PHYS_DESC "walnutps2/serio0" +#define I8042_AUX_PHYS_DESC "walnutps2/serio1" +#define I8042_MUX_PHYS_DESC "walnutps2/serio%d" + +extern void *kb_cs; +extern void *kb_data; + +#define I8042_COMMAND_REG (*(int *)kb_cs) +#define I8042_DATA_REG (*(int *)kb_data) + +static inline int i8042_read_data(void) +{ + return readb(kb_data); +} + +static inline int i8042_read_status(void) +{ + return readb(kb_cs); +} + +static inline void i8042_write_data(int val) +{ + writeb(val, kb_data); +} + +static inline void i8042_write_command(int val) +{ + writeb(val, kb_cs); +} + +static inline int i8042_platform_init(void) +{ + i8042_reset = 1; + return 0; +} + +static inline void i8042_platform_exit(void) +{ +} + +#elif defined(CONFIG_SPRUCE) + +#define I8042_KBD_IRQ 22 +#define I8042_AUX_IRQ 21 + +#define I8042_KBD_PHYS_DESC "spruceps2/serio0" +#define I8042_AUX_PHYS_DESC "spruceps2/serio1" +#define I8042_MUX_PHYS_DESC "spruceps2/serio%d" + +#define I8042_COMMAND_REG 0xff810000 +#define I8042_DATA_REG 0xff810001 + +static inline int i8042_read_data(void) +{ + unsigned long kbd_data; + + __raw_writel(0x00000088, 0xff500008); + eieio(); + + __raw_writel(0x03000000, 0xff50000c); + eieio(); + + asm volatile("lis 7,0xff88 \n\ + lswi 6,7,0x8 \n\ + mr %0,6" + : "=r" (kbd_data) :: "6", "7"); + + __raw_writel(0x00000000, 0xff50000c); + eieio(); + + return (unsigned char)(kbd_data >> 24); +} + +static inline int i8042_read_status(void) +{ + unsigned long kbd_status; + + __raw_writel(0x00000088, 0xff500008); + eieio(); + + __raw_writel(0x03000000, 0xff50000c); + eieio(); + + asm volatile("lis 7,0xff88 \n\ + ori 7,7,0x8 \n\ + lswi 6,7,0x8 \n\ + mr %0,6" + : "=r" (kbd_status) :: "6", "7"); + + __raw_writel(0x00000000, 0xff50000c); + eieio(); + + return (unsigned char)(kbd_status >> 24); +} + +static inline void i8042_write_data(int val) +{ + *((unsigned char *)0xff810000) = (char)val; +} + +static inline void i8042_write_command(int val) +{ + *((unsigned char *)0xff810001) = (char)val; +} + +static inline int i8042_platform_init(void) +{ + i8042_reset = 1; + return 0; +} + +static inline void i8042_platform_exit(void) +{ +} + +#else + +#include "i8042-io.h" + +#endif + +#endif /* _I8042_PPCIO_H */ diff --git a/drivers/input/serio/i8042-sparcio.h b/drivers/input/serio/i8042-sparcio.h new file mode 100644 index 00000000000..da2a1981248 --- /dev/null +++ b/drivers/input/serio/i8042-sparcio.h @@ -0,0 +1,116 @@ +#ifndef _I8042_SPARCIO_H +#define _I8042_SPARCIO_H + +#include <linux/config.h> +#include <asm/io.h> + +#ifdef CONFIG_PCI +#include <asm/oplib.h> +#include <asm/ebus.h> +#endif + +static int i8042_kbd_irq = -1; +static int i8042_aux_irq = -1; +#define I8042_KBD_IRQ i8042_kbd_irq +#define I8042_AUX_IRQ i8042_aux_irq + +#define I8042_KBD_PHYS_DESC "sparcps2/serio0" +#define I8042_AUX_PHYS_DESC "sparcps2/serio1" +#define I8042_MUX_PHYS_DESC "sparcps2/serio%d" + +static void __iomem *kbd_iobase; + +#define I8042_COMMAND_REG (kbd_iobase + 0x64UL) +#define I8042_DATA_REG (kbd_iobase + 0x60UL) + +static inline int i8042_read_data(void) +{ + return readb(kbd_iobase + 0x60UL); +} + +static inline int i8042_read_status(void) +{ + return readb(kbd_iobase + 0x64UL); +} + +static inline void i8042_write_data(int val) +{ + writeb(val, kbd_iobase + 0x60UL); +} + +static inline void i8042_write_command(int val) +{ + writeb(val, kbd_iobase + 0x64UL); +} + +#define OBP_PS2KBD_NAME1 "kb_ps2" +#define OBP_PS2KBD_NAME2 "keyboard" +#define OBP_PS2MS_NAME1 "kdmouse" +#define OBP_PS2MS_NAME2 "mouse" + +static int i8042_platform_init(void) +{ +#ifndef CONFIG_PCI + return -1; +#else + char prop[128]; + int len; + + len = prom_getproperty(prom_root_node, "name", prop, sizeof(prop)); + if (len < 0) { + printk("i8042: Cannot get name property of root OBP node.\n"); + return -1; + } + if (strncmp(prop, "SUNW,JavaStation-1", len) == 0) { + /* Hardcoded values for MrCoffee. */ + i8042_kbd_irq = i8042_aux_irq = 13 | 0x20; + kbd_iobase = ioremap(0x71300060, 8); + if (!kbd_iobase) + return -1; + } else { + struct linux_ebus *ebus; + struct linux_ebus_device *edev; + struct linux_ebus_child *child; + + for_each_ebus(ebus) { + for_each_ebusdev(edev, ebus) { + if (!strcmp(edev->prom_name, "8042")) + goto edev_found; + } + } + return -1; + + edev_found: + for_each_edevchild(edev, child) { + if (!strcmp(child->prom_name, OBP_PS2KBD_NAME1) || + !strcmp(child->prom_name, OBP_PS2KBD_NAME2)) { + i8042_kbd_irq = child->irqs[0]; + kbd_iobase = + ioremap(child->resource[0].start, 8); + } + if (!strcmp(child->prom_name, OBP_PS2MS_NAME1) || + !strcmp(child->prom_name, OBP_PS2MS_NAME2)) + i8042_aux_irq = child->irqs[0]; + } + if (i8042_kbd_irq == -1 || + i8042_aux_irq == -1) { + printk("i8042: Error, 8042 device lacks both kbd and " + "mouse nodes.\n"); + return -1; + } + } + + i8042_reset = 1; + + return 0; +#endif /* CONFIG_PCI */ +} + +static inline void i8042_platform_exit(void) +{ +#ifdef CONFIG_PCI + iounmap(kbd_iobase); +#endif +} + +#endif /* _I8042_SPARCIO_H */ diff --git a/drivers/input/serio/i8042-x86ia64io.h b/drivers/input/serio/i8042-x86ia64io.h new file mode 100644 index 00000000000..f64867808fe --- /dev/null +++ b/drivers/input/serio/i8042-x86ia64io.h @@ -0,0 +1,333 @@ +#ifndef _I8042_X86IA64IO_H +#define _I8042_X86IA64IO_H + +/* + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +/* + * Names. + */ + +#define I8042_KBD_PHYS_DESC "isa0060/serio0" +#define I8042_AUX_PHYS_DESC "isa0060/serio1" +#define I8042_MUX_PHYS_DESC "isa0060/serio%d" + +/* + * IRQs. + */ + +#if defined(__ia64__) +# define I8042_MAP_IRQ(x) isa_irq_to_vector((x)) +#else +# define I8042_MAP_IRQ(x) (x) +#endif + +#define I8042_KBD_IRQ i8042_kbd_irq +#define I8042_AUX_IRQ i8042_aux_irq + +static int i8042_kbd_irq; +static int i8042_aux_irq; + +/* + * Register numbers. + */ + +#define I8042_COMMAND_REG i8042_command_reg +#define I8042_STATUS_REG i8042_command_reg +#define I8042_DATA_REG i8042_data_reg + +static int i8042_command_reg = 0x64; +static int i8042_data_reg = 0x60; + + +static inline int i8042_read_data(void) +{ + return inb(I8042_DATA_REG); +} + +static inline int i8042_read_status(void) +{ + return inb(I8042_STATUS_REG); +} + +static inline void i8042_write_data(int val) +{ + outb(val, I8042_DATA_REG); +} + +static inline void i8042_write_command(int val) +{ + outb(val, I8042_COMMAND_REG); +} + +#if defined(__i386__) + +#include <linux/dmi.h> + +static struct dmi_system_id __initdata i8042_dmi_noloop_table[] = { + { + .ident = "Compaq Proliant 8500", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Compaq"), + DMI_MATCH(DMI_PRODUCT_NAME , "ProLiant"), + DMI_MATCH(DMI_PRODUCT_VERSION, "8500"), + }, + }, + { + .ident = "Compaq Proliant DL760", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Compaq"), + DMI_MATCH(DMI_PRODUCT_NAME , "ProLiant"), + DMI_MATCH(DMI_PRODUCT_VERSION, "DL760"), + }, + }, + { } +}; + +/* + * Some Fujitsu notebooks are ahving trouble with touhcpads if + * active multiplexing mode is activated. Luckily they don't have + * external PS/2 ports so we can safely disable it. + */ +static struct dmi_system_id __initdata i8042_dmi_nomux_table[] = { + { + .ident = "Fujitsu Lifebook P7010/P7010D", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"), + DMI_MATCH(DMI_PRODUCT_NAME, "P7010"), + }, + }, + { + .ident = "Fujitsu Lifebook P5020D", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"), + DMI_MATCH(DMI_PRODUCT_NAME, "LifeBook P Series"), + }, + }, + { + .ident = "Fujitsu Lifebook S2000", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"), + DMI_MATCH(DMI_PRODUCT_NAME, "LifeBook S Series"), + }, + }, + { + .ident = "Fujitsu T70H", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"), + DMI_MATCH(DMI_PRODUCT_NAME, "FMVLT70H"), + }, + }, + { } +}; + + + +#endif + + +#ifdef CONFIG_PNP +#include <linux/pnp.h> + +static int i8042_pnp_kbd_registered; +static int i8042_pnp_aux_registered; + +static int i8042_pnp_command_reg; +static int i8042_pnp_data_reg; +static int i8042_pnp_kbd_irq; +static int i8042_pnp_aux_irq; + +static char i8042_pnp_kbd_name[32]; +static char i8042_pnp_aux_name[32]; + +static int i8042_pnp_kbd_probe(struct pnp_dev *dev, const struct pnp_device_id *did) +{ + if (pnp_port_valid(dev, 0) && pnp_port_len(dev, 0) == 1) + i8042_pnp_data_reg = pnp_port_start(dev,0); + + if (pnp_port_valid(dev, 1) && pnp_port_len(dev, 1) == 1) + i8042_pnp_command_reg = pnp_port_start(dev, 1); + + if (pnp_irq_valid(dev,0)) + i8042_pnp_kbd_irq = pnp_irq(dev, 0); + + strncpy(i8042_pnp_kbd_name, did->id, sizeof(i8042_pnp_kbd_name)); + if (strlen(pnp_dev_name(dev))) { + strncat(i8042_pnp_kbd_name, ":", sizeof(i8042_pnp_kbd_name)); + strncat(i8042_pnp_kbd_name, pnp_dev_name(dev), sizeof(i8042_pnp_kbd_name)); + } + + return 0; +} + +static int i8042_pnp_aux_probe(struct pnp_dev *dev, const struct pnp_device_id *did) +{ + if (pnp_port_valid(dev, 0) && pnp_port_len(dev, 0) == 1) + i8042_pnp_data_reg = pnp_port_start(dev,0); + + if (pnp_port_valid(dev, 1) && pnp_port_len(dev, 1) == 1) + i8042_pnp_command_reg = pnp_port_start(dev, 1); + + if (pnp_irq_valid(dev, 0)) + i8042_pnp_aux_irq = pnp_irq(dev, 0); + + strncpy(i8042_pnp_aux_name, did->id, sizeof(i8042_pnp_aux_name)); + if (strlen(pnp_dev_name(dev))) { + strncat(i8042_pnp_aux_name, ":", sizeof(i8042_pnp_aux_name)); + strncat(i8042_pnp_aux_name, pnp_dev_name(dev), sizeof(i8042_pnp_aux_name)); + } + + return 0; +} + +static struct pnp_device_id pnp_kbd_devids[] = { + { .id = "PNP0303", .driver_data = 0 }, + { .id = "PNP030b", .driver_data = 0 }, + { .id = "", }, +}; + +static struct pnp_driver i8042_pnp_kbd_driver = { + .name = "i8042 kbd", + .id_table = pnp_kbd_devids, + .probe = i8042_pnp_kbd_probe, +}; + +static struct pnp_device_id pnp_aux_devids[] = { + { .id = "PNP0f03", .driver_data = 0 }, + { .id = "PNP0f0b", .driver_data = 0 }, + { .id = "PNP0f0e", .driver_data = 0 }, + { .id = "PNP0f12", .driver_data = 0 }, + { .id = "PNP0f13", .driver_data = 0 }, + { .id = "PNP0f19", .driver_data = 0 }, + { .id = "PNP0f1c", .driver_data = 0 }, + { .id = "SYN0801", .driver_data = 0 }, + { .id = "", }, +}; + +static struct pnp_driver i8042_pnp_aux_driver = { + .name = "i8042 aux", + .id_table = pnp_aux_devids, + .probe = i8042_pnp_aux_probe, +}; + +static void i8042_pnp_exit(void) +{ + if (i8042_pnp_kbd_registered) + pnp_unregister_driver(&i8042_pnp_kbd_driver); + + if (i8042_pnp_aux_registered) + pnp_unregister_driver(&i8042_pnp_aux_driver); +} + +static int i8042_pnp_init(void) +{ + int result_kbd, result_aux; + + if (i8042_nopnp) { + printk("i8042: PNP detection disabled\n"); + return 0; + } + + if ((result_kbd = pnp_register_driver(&i8042_pnp_kbd_driver)) >= 0) + i8042_pnp_kbd_registered = 1; + if ((result_aux = pnp_register_driver(&i8042_pnp_aux_driver)) >= 0) + i8042_pnp_aux_registered = 1; + + if (result_kbd <= 0 && result_aux <= 0) { + i8042_pnp_exit(); +#if defined(__ia64__) + return -ENODEV; +#else + printk(KERN_WARNING "PNP: No PS/2 controller found. Probing ports directly.\n"); + return 0; +#endif + } + + if (((i8042_pnp_data_reg & ~0xf) == (i8042_data_reg & ~0xf) && + i8042_pnp_data_reg != i8042_data_reg) || !i8042_pnp_data_reg) { + printk(KERN_WARNING "PNP: PS/2 controller has invalid data port %#x; using default %#x\n", + i8042_pnp_data_reg, i8042_data_reg); + i8042_pnp_data_reg = i8042_data_reg; + } + + if (((i8042_pnp_command_reg & ~0xf) == (i8042_command_reg & ~0xf) && + i8042_pnp_command_reg != i8042_command_reg) || !i8042_pnp_command_reg) { + printk(KERN_WARNING "PNP: PS/2 controller has invalid command port %#x; using default %#x\n", + i8042_pnp_command_reg, i8042_command_reg); + i8042_pnp_command_reg = i8042_command_reg; + } + + if (!i8042_pnp_kbd_irq) { + printk(KERN_WARNING "PNP: PS/2 controller doesn't have KBD irq; using default %#x\n", i8042_kbd_irq); + i8042_pnp_kbd_irq = i8042_kbd_irq; + } + + if (result_aux > 0 && !i8042_pnp_aux_irq) { + printk(KERN_WARNING "PNP: PS/2 controller doesn't have AUX irq; using default %#x\n", i8042_aux_irq); + i8042_pnp_aux_irq = i8042_aux_irq; + } + +#if defined(__ia64__) + if (result_aux <= 0) + i8042_noaux = 1; +#endif + + i8042_data_reg = i8042_pnp_data_reg; + i8042_command_reg = i8042_pnp_command_reg; + i8042_kbd_irq = i8042_pnp_kbd_irq; + i8042_aux_irq = i8042_pnp_aux_irq; + + printk(KERN_INFO "PNP: PS/2 Controller [%s%s%s] at %#x,%#x irq %d%s%d\n", + i8042_pnp_kbd_name, (result_kbd > 0 && result_aux > 0) ? "," : "", i8042_pnp_aux_name, + i8042_data_reg, i8042_command_reg, i8042_kbd_irq, + (result_aux > 0) ? "," : "", i8042_aux_irq); + + return 0; +} + +#endif + +static inline int i8042_platform_init(void) +{ +/* + * On ix86 platforms touching the i8042 data register region can do really + * bad things. Because of this the region is always reserved on ix86 boxes. + * + * if (!request_region(I8042_DATA_REG, 16, "i8042")) + * return -1; + */ + + i8042_kbd_irq = I8042_MAP_IRQ(1); + i8042_aux_irq = I8042_MAP_IRQ(12); + +#ifdef CONFIG_PNP + if (i8042_pnp_init()) + return -1; +#endif + +#if defined(__ia64__) + i8042_reset = 1; +#endif + +#if defined(__i386__) + if (dmi_check_system(i8042_dmi_noloop_table)) + i8042_noloop = 1; + + if (dmi_check_system(i8042_dmi_nomux_table)) + i8042_nomux = 1; +#endif + + return 0; +} + +static inline void i8042_platform_exit(void) +{ +#ifdef CONFIG_PNP + i8042_pnp_exit(); +#endif +} + +#endif /* _I8042_X86IA64IO_H */ diff --git a/drivers/input/serio/i8042.c b/drivers/input/serio/i8042.c new file mode 100644 index 00000000000..8e63e464d36 --- /dev/null +++ b/drivers/input/serio/i8042.c @@ -0,0 +1,1116 @@ +/* + * i8042 keyboard and mouse controller driver for Linux + * + * Copyright (c) 1999-2004 Vojtech Pavlik + */ + +/* + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/interrupt.h> +#include <linux/ioport.h> +#include <linux/config.h> +#include <linux/init.h> +#include <linux/serio.h> +#include <linux/err.h> +#include <linux/rcupdate.h> + +#include <asm/io.h> + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@suse.cz>"); +MODULE_DESCRIPTION("i8042 keyboard and mouse controller driver"); +MODULE_LICENSE("GPL"); + +static unsigned int i8042_noaux; +module_param_named(noaux, i8042_noaux, bool, 0); +MODULE_PARM_DESC(noaux, "Do not probe or use AUX (mouse) port."); + +static unsigned int i8042_nomux; +module_param_named(nomux, i8042_nomux, bool, 0); +MODULE_PARM_DESC(nomux, "Do not check whether an active multiplexing conrtoller is present."); + +static unsigned int i8042_unlock; +module_param_named(unlock, i8042_unlock, bool, 0); +MODULE_PARM_DESC(unlock, "Ignore keyboard lock."); + +static unsigned int i8042_reset; +module_param_named(reset, i8042_reset, bool, 0); +MODULE_PARM_DESC(reset, "Reset controller during init and cleanup."); + +static unsigned int i8042_direct; +module_param_named(direct, i8042_direct, bool, 0); +MODULE_PARM_DESC(direct, "Put keyboard port into non-translated mode."); + +static unsigned int i8042_dumbkbd; +module_param_named(dumbkbd, i8042_dumbkbd, bool, 0); +MODULE_PARM_DESC(dumbkbd, "Pretend that controller can only read data from keyboard"); + +static unsigned int i8042_noloop; +module_param_named(noloop, i8042_noloop, bool, 0); +MODULE_PARM_DESC(noloop, "Disable the AUX Loopback command while probing for the AUX port"); + +static unsigned int i8042_blink_frequency = 500; +module_param_named(panicblink, i8042_blink_frequency, uint, 0600); +MODULE_PARM_DESC(panicblink, "Frequency with which keyboard LEDs should blink when kernel panics"); + +#ifdef CONFIG_PNP +static int i8042_nopnp; +module_param_named(nopnp, i8042_nopnp, bool, 0); +MODULE_PARM_DESC(nopnp, "Do not use PNP to detect controller settings"); +#endif + +#define DEBUG +#ifdef DEBUG +static int i8042_debug; +module_param_named(debug, i8042_debug, bool, 0600); +MODULE_PARM_DESC(debug, "Turn i8042 debugging mode on and off"); +#endif + +__obsolete_setup("i8042_noaux"); +__obsolete_setup("i8042_nomux"); +__obsolete_setup("i8042_unlock"); +__obsolete_setup("i8042_reset"); +__obsolete_setup("i8042_direct"); +__obsolete_setup("i8042_dumbkbd"); + +#include "i8042.h" + +static DEFINE_SPINLOCK(i8042_lock); + +struct i8042_port { + struct serio *serio; + int irq; + unsigned char disable; + unsigned char irqen; + unsigned char exists; + signed char mux; + char name[8]; +}; + +#define I8042_KBD_PORT_NO 0 +#define I8042_AUX_PORT_NO 1 +#define I8042_MUX_PORT_NO 2 +#define I8042_NUM_PORTS (I8042_NUM_MUX_PORTS + 2) +static struct i8042_port i8042_ports[I8042_NUM_PORTS] = { + { + .disable = I8042_CTR_KBDDIS, + .irqen = I8042_CTR_KBDINT, + .mux = -1, + .name = "KBD", + }, + { + .disable = I8042_CTR_AUXDIS, + .irqen = I8042_CTR_AUXINT, + .mux = -1, + .name = "AUX", + } +}; + +static unsigned char i8042_initial_ctr; +static unsigned char i8042_ctr; +static unsigned char i8042_mux_open; +static unsigned char i8042_mux_present; +static struct timer_list i8042_timer; +static struct platform_device *i8042_platform_device; + + +/* + * Shared IRQ's require a device pointer, but this driver doesn't support + * multiple devices + */ +#define i8042_request_irq_cookie (&i8042_timer) + +static irqreturn_t i8042_interrupt(int irq, void *dev_id, struct pt_regs *regs); + +/* + * The i8042_wait_read() and i8042_wait_write functions wait for the i8042 to + * be ready for reading values from it / writing values to it. + * Called always with i8042_lock held. + */ + +static int i8042_wait_read(void) +{ + int i = 0; + while ((~i8042_read_status() & I8042_STR_OBF) && (i < I8042_CTL_TIMEOUT)) { + udelay(50); + i++; + } + return -(i == I8042_CTL_TIMEOUT); +} + +static int i8042_wait_write(void) +{ + int i = 0; + while ((i8042_read_status() & I8042_STR_IBF) && (i < I8042_CTL_TIMEOUT)) { + udelay(50); + i++; + } + return -(i == I8042_CTL_TIMEOUT); +} + +/* + * i8042_flush() flushes all data that may be in the keyboard and mouse buffers + * of the i8042 down the toilet. + */ + +static int i8042_flush(void) +{ + unsigned long flags; + unsigned char data, str; + int i = 0; + + spin_lock_irqsave(&i8042_lock, flags); + + while (((str = i8042_read_status()) & I8042_STR_OBF) && (i < I8042_BUFFER_SIZE)) { + udelay(50); + data = i8042_read_data(); + i++; + dbg("%02x <- i8042 (flush, %s)", data, + str & I8042_STR_AUXDATA ? "aux" : "kbd"); + } + + spin_unlock_irqrestore(&i8042_lock, flags); + + return i; +} + +/* + * i8042_command() executes a command on the i8042. It also sends the input + * parameter(s) of the commands to it, and receives the output value(s). The + * parameters are to be stored in the param array, and the output is placed + * into the same array. The number of the parameters and output values is + * encoded in bits 8-11 of the command number. + */ + +static int i8042_command(unsigned char *param, int command) +{ + unsigned long flags; + int retval = 0, i = 0; + + if (i8042_noloop && command == I8042_CMD_AUX_LOOP) + return -1; + + spin_lock_irqsave(&i8042_lock, flags); + + retval = i8042_wait_write(); + if (!retval) { + dbg("%02x -> i8042 (command)", command & 0xff); + i8042_write_command(command & 0xff); + } + + if (!retval) + for (i = 0; i < ((command >> 12) & 0xf); i++) { + if ((retval = i8042_wait_write())) break; + dbg("%02x -> i8042 (parameter)", param[i]); + i8042_write_data(param[i]); + } + + if (!retval) + for (i = 0; i < ((command >> 8) & 0xf); i++) { + if ((retval = i8042_wait_read())) break; + if (i8042_read_status() & I8042_STR_AUXDATA) + param[i] = ~i8042_read_data(); + else + param[i] = i8042_read_data(); + dbg("%02x <- i8042 (return)", param[i]); + } + + spin_unlock_irqrestore(&i8042_lock, flags); + + if (retval) + dbg(" -- i8042 (timeout)"); + + return retval; +} + +/* + * i8042_kbd_write() sends a byte out through the keyboard interface. + */ + +static int i8042_kbd_write(struct serio *port, unsigned char c) +{ + unsigned long flags; + int retval = 0; + + spin_lock_irqsave(&i8042_lock, flags); + + if(!(retval = i8042_wait_write())) { + dbg("%02x -> i8042 (kbd-data)", c); + i8042_write_data(c); + } + + spin_unlock_irqrestore(&i8042_lock, flags); + + return retval; +} + +/* + * i8042_aux_write() sends a byte out through the aux interface. + */ + +static int i8042_aux_write(struct serio *serio, unsigned char c) +{ + struct i8042_port *port = serio->port_data; + int retval; + +/* + * Send the byte out. + */ + + if (port->mux == -1) + retval = i8042_command(&c, I8042_CMD_AUX_SEND); + else + retval = i8042_command(&c, I8042_CMD_MUX_SEND + port->mux); + +/* + * Make sure the interrupt happens and the character is received even + * in the case the IRQ isn't wired, so that we can receive further + * characters later. + */ + + i8042_interrupt(0, NULL, NULL); + return retval; +} + +/* + * i8042_activate_port() enables port on a chip. + */ + +static int i8042_activate_port(struct i8042_port *port) +{ + if (!port->serio) + return -1; + + i8042_flush(); + + /* + * Enable port again here because it is disabled if we are + * resuming (normally it is enabled already). + */ + i8042_ctr &= ~port->disable; + + i8042_ctr |= port->irqen; + + if (i8042_command(&i8042_ctr, I8042_CMD_CTL_WCTR)) { + i8042_ctr &= ~port->irqen; + return -1; + } + + return 0; +} + + +/* + * i8042_open() is called when a port is open by the higher layer. + * It allocates the interrupt and calls i8042_enable_port. + */ + +static int i8042_open(struct serio *serio) +{ + struct i8042_port *port = serio->port_data; + + if (port->mux != -1) + if (i8042_mux_open++) + return 0; + + if (request_irq(port->irq, i8042_interrupt, + SA_SHIRQ, "i8042", i8042_request_irq_cookie)) { + printk(KERN_ERR "i8042.c: Can't get irq %d for %s, unregistering the port.\n", port->irq, port->name); + goto irq_fail; + } + + if (i8042_activate_port(port)) { + printk(KERN_ERR "i8042.c: Can't activate %s, unregistering the port\n", port->name); + goto activate_fail; + } + + i8042_interrupt(0, NULL, NULL); + + return 0; + +activate_fail: + free_irq(port->irq, i8042_request_irq_cookie); + +irq_fail: + serio_unregister_port_delayed(serio); + + return -1; +} + +/* + * i8042_close() frees the interrupt, so that it can possibly be used + * by another driver. We never know - if the user doesn't have a mouse, + * the BIOS could have used the AUX interrupt for PCI. + */ + +static void i8042_close(struct serio *serio) +{ + struct i8042_port *port = serio->port_data; + + if (port->mux != -1) + if (--i8042_mux_open) + return; + + i8042_ctr &= ~port->irqen; + + if (i8042_command(&i8042_ctr, I8042_CMD_CTL_WCTR)) { + printk(KERN_WARNING "i8042.c: Can't write CTR while closing %s.\n", port->name); +/* + * We still want to continue and free IRQ so if more data keeps coming in + * kernel will just ignore the irq. + */ + } + + free_irq(port->irq, i8042_request_irq_cookie); + + i8042_flush(); +} + +/* + * i8042_start() is called by serio core when port is about to finish + * registering. It will mark port as existing so i8042_interrupt can + * start sending data through it. + */ +static int i8042_start(struct serio *serio) +{ + struct i8042_port *port = serio->port_data; + + port->exists = 1; + mb(); + return 0; +} + +/* + * i8042_stop() marks serio port as non-existing so i8042_interrupt + * will not try to send data to the port that is about to go away. + * The function is called by serio core as part of unregister procedure. + */ +static void i8042_stop(struct serio *serio) +{ + struct i8042_port *port = serio->port_data; + + port->exists = 0; + synchronize_kernel(); + port->serio = NULL; +} + +/* + * i8042_interrupt() is the most important function in this driver - + * it handles the interrupts from the i8042, and sends incoming bytes + * to the upper layers. + */ + +static irqreturn_t i8042_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + struct i8042_port *port; + unsigned long flags; + unsigned char str, data; + unsigned int dfl; + unsigned int port_no; + int ret; + + mod_timer(&i8042_timer, jiffies + I8042_POLL_PERIOD); + + spin_lock_irqsave(&i8042_lock, flags); + str = i8042_read_status(); + if (unlikely(~str & I8042_STR_OBF)) { + spin_unlock_irqrestore(&i8042_lock, flags); + if (irq) dbg("Interrupt %d, without any data", irq); + ret = 0; + goto out; + } + data = i8042_read_data(); + spin_unlock_irqrestore(&i8042_lock, flags); + + if (i8042_mux_present && (str & I8042_STR_AUXDATA)) { + static unsigned long last_transmit; + static unsigned char last_str; + + dfl = 0; + if (str & I8042_STR_MUXERR) { + dbg("MUX error, status is %02x, data is %02x", str, data); + switch (data) { + default: +/* + * When MUXERR condition is signalled the data register can only contain + * 0xfd, 0xfe or 0xff if implementation follows the spec. Unfortunately + * it is not always the case. Some KBC just get confused which port the + * data came from and signal error leaving the data intact. They _do not_ + * revert to legacy mode (actually I've never seen KBC reverting to legacy + * mode yet, when we see one we'll add proper handling). + * Anyway, we will assume that the data came from the same serio last byte + * was transmitted (if transmission happened not too long ago). + */ + if (time_before(jiffies, last_transmit + HZ/10)) { + str = last_str; + break; + } + /* fall through - report timeout */ + case 0xfd: + case 0xfe: dfl = SERIO_TIMEOUT; data = 0xfe; break; + case 0xff: dfl = SERIO_PARITY; data = 0xfe; break; + } + } + + port_no = I8042_MUX_PORT_NO + ((str >> 6) & 3); + last_str = str; + last_transmit = jiffies; + } else { + + dfl = ((str & I8042_STR_PARITY) ? SERIO_PARITY : 0) | + ((str & I8042_STR_TIMEOUT) ? SERIO_TIMEOUT : 0); + + port_no = (str & I8042_STR_AUXDATA) ? + I8042_AUX_PORT_NO : I8042_KBD_PORT_NO; + } + + port = &i8042_ports[port_no]; + + dbg("%02x <- i8042 (interrupt, %s, %d%s%s)", + data, port->name, irq, + dfl & SERIO_PARITY ? ", bad parity" : "", + dfl & SERIO_TIMEOUT ? ", timeout" : ""); + + if (likely(port->exists)) + serio_interrupt(port->serio, data, dfl, regs); + + ret = 1; +out: + return IRQ_RETVAL(ret); +} + +/* + * i8042_set_mux_mode checks whether the controller has an active + * multiplexor and puts the chip into Multiplexed (1) or Legacy (0) mode. + */ + +static int i8042_set_mux_mode(unsigned int mode, unsigned char *mux_version) +{ + + unsigned char param; +/* + * Get rid of bytes in the queue. + */ + + i8042_flush(); + +/* + * Internal loopback test - send three bytes, they should come back from the + * mouse interface, the last should be version. Note that we negate mouseport + * command responses for the i8042_check_aux() routine. + */ + + param = 0xf0; + if (i8042_command(¶m, I8042_CMD_AUX_LOOP) || param != 0x0f) + return -1; + param = mode ? 0x56 : 0xf6; + if (i8042_command(¶m, I8042_CMD_AUX_LOOP) || param != (mode ? 0xa9 : 0x09)) + return -1; + param = mode ? 0xa4 : 0xa5; + if (i8042_command(¶m, I8042_CMD_AUX_LOOP) || param == (mode ? 0x5b : 0x5a)) + return -1; + + if (mux_version) + *mux_version = ~param; + + return 0; +} + + +/* + * i8042_enable_mux_ports enables 4 individual AUX ports after + * the controller has been switched into Multiplexed mode + */ + +static int i8042_enable_mux_ports(void) +{ + unsigned char param; + int i; +/* + * Disable all muxed ports by disabling AUX. + */ + + i8042_ctr |= I8042_CTR_AUXDIS; + i8042_ctr &= ~I8042_CTR_AUXINT; + + if (i8042_command(&i8042_ctr, I8042_CMD_CTL_WCTR)) { + printk(KERN_ERR "i8042.c: Failed to disable AUX port, can't use MUX.\n"); + return -1; + } + +/* + * Enable all muxed ports. + */ + + for (i = 0; i < 4; i++) { + i8042_command(¶m, I8042_CMD_MUX_PFX + i); + i8042_command(¶m, I8042_CMD_AUX_ENABLE); + } + + return 0; +} + + +/* + * i8042_check_mux() checks whether the controller supports the PS/2 Active + * Multiplexing specification by Synaptics, Phoenix, Insyde and + * LCS/Telegraphics. + */ + +static int __init i8042_check_mux(void) +{ + unsigned char mux_version; + + if (i8042_set_mux_mode(1, &mux_version)) + return -1; + + /* Workaround for interference with USB Legacy emulation */ + /* that causes a v10.12 MUX to be found. */ + if (mux_version == 0xAC) + return -1; + + printk(KERN_INFO "i8042.c: Detected active multiplexing controller, rev %d.%d.\n", + (mux_version >> 4) & 0xf, mux_version & 0xf); + + if (i8042_enable_mux_ports()) + return -1; + + i8042_mux_present = 1; + return 0; +} + + +/* + * i8042_check_aux() applies as much paranoia as it can at detecting + * the presence of an AUX interface. + */ + +static int __init i8042_check_aux(void) +{ + unsigned char param; + static int i8042_check_aux_cookie; + +/* + * Check if AUX irq is available. If it isn't, then there is no point + * in trying to detect AUX presence. + */ + + if (request_irq(i8042_ports[I8042_AUX_PORT_NO].irq, i8042_interrupt, + SA_SHIRQ, "i8042", &i8042_check_aux_cookie)) + return -1; + free_irq(i8042_ports[I8042_AUX_PORT_NO].irq, &i8042_check_aux_cookie); + +/* + * Get rid of bytes in the queue. + */ + + i8042_flush(); + +/* + * Internal loopback test - filters out AT-type i8042's. Unfortunately + * SiS screwed up and their 5597 doesn't support the LOOP command even + * though it has an AUX port. + */ + + param = 0x5a; + if (i8042_command(¶m, I8042_CMD_AUX_LOOP) || param != 0xa5) { + +/* + * External connection test - filters out AT-soldered PS/2 i8042's + * 0x00 - no error, 0x01-0x03 - clock/data stuck, 0xff - general error + * 0xfa - no error on some notebooks which ignore the spec + * Because it's common for chipsets to return error on perfectly functioning + * AUX ports, we test for this only when the LOOP command failed. + */ + + if (i8042_command(¶m, I8042_CMD_AUX_TEST) + || (param && param != 0xfa && param != 0xff)) + return -1; + } + +/* + * Bit assignment test - filters out PS/2 i8042's in AT mode + */ + + if (i8042_command(¶m, I8042_CMD_AUX_DISABLE)) + return -1; + if (i8042_command(¶m, I8042_CMD_CTL_RCTR) || (~param & I8042_CTR_AUXDIS)) { + printk(KERN_WARNING "Failed to disable AUX port, but continuing anyway... Is this a SiS?\n"); + printk(KERN_WARNING "If AUX port is really absent please use the 'i8042.noaux' option.\n"); + } + + if (i8042_command(¶m, I8042_CMD_AUX_ENABLE)) + return -1; + if (i8042_command(¶m, I8042_CMD_CTL_RCTR) || (param & I8042_CTR_AUXDIS)) + return -1; + +/* + * Disable the interface. + */ + + i8042_ctr |= I8042_CTR_AUXDIS; + i8042_ctr &= ~I8042_CTR_AUXINT; + + if (i8042_command(&i8042_ctr, I8042_CMD_CTL_WCTR)) + return -1; + + return 0; +} + + +/* + * i8042_port_register() marks the device as existing, + * registers it, and reports to the user. + */ + +static int __init i8042_port_register(struct i8042_port *port) +{ + i8042_ctr &= ~port->disable; + + if (i8042_command(&i8042_ctr, I8042_CMD_CTL_WCTR)) { + printk(KERN_WARNING "i8042.c: Can't write CTR while registering.\n"); + kfree(port->serio); + port->serio = NULL; + i8042_ctr |= port->disable; + return -1; + } + + printk(KERN_INFO "serio: i8042 %s port at %#lx,%#lx irq %d\n", + port->name, + (unsigned long) I8042_DATA_REG, + (unsigned long) I8042_COMMAND_REG, + port->irq); + + serio_register_port(port->serio); + + return 0; +} + + +static void i8042_timer_func(unsigned long data) +{ + i8042_interrupt(0, NULL, NULL); +} + + +/* + * i8042_controller init initializes the i8042 controller, and, + * most importantly, sets it into non-xlated mode if that's + * desired. + */ + +static int i8042_controller_init(void) +{ + unsigned long flags; + +/* + * Test the i8042. We need to know if it thinks it's working correctly + * before doing anything else. + */ + + if (i8042_flush() == I8042_BUFFER_SIZE) { + printk(KERN_ERR "i8042.c: No controller found.\n"); + return -1; + } + + if (i8042_reset) { + + unsigned char param; + + if (i8042_command(¶m, I8042_CMD_CTL_TEST)) { + printk(KERN_ERR "i8042.c: i8042 controller self test timeout.\n"); + return -1; + } + + if (param != I8042_RET_CTL_TEST) { + printk(KERN_ERR "i8042.c: i8042 controller selftest failed. (%#x != %#x)\n", + param, I8042_RET_CTL_TEST); + return -1; + } + } + +/* + * Save the CTR for restoral on unload / reboot. + */ + + if (i8042_command(&i8042_ctr, I8042_CMD_CTL_RCTR)) { + printk(KERN_ERR "i8042.c: Can't read CTR while initializing i8042.\n"); + return -1; + } + + i8042_initial_ctr = i8042_ctr; + +/* + * Disable the keyboard interface and interrupt. + */ + + i8042_ctr |= I8042_CTR_KBDDIS; + i8042_ctr &= ~I8042_CTR_KBDINT; + +/* + * Handle keylock. + */ + + spin_lock_irqsave(&i8042_lock, flags); + if (~i8042_read_status() & I8042_STR_KEYLOCK) { + if (i8042_unlock) + i8042_ctr |= I8042_CTR_IGNKEYLOCK; + else + printk(KERN_WARNING "i8042.c: Warning: Keylock active.\n"); + } + spin_unlock_irqrestore(&i8042_lock, flags); + +/* + * If the chip is configured into nontranslated mode by the BIOS, don't + * bother enabling translating and be happy. + */ + + if (~i8042_ctr & I8042_CTR_XLATE) + i8042_direct = 1; + +/* + * Set nontranslated mode for the kbd interface if requested by an option. + * After this the kbd interface becomes a simple serial in/out, like the aux + * interface is. We don't do this by default, since it can confuse notebook + * BIOSes. + */ + + if (i8042_direct) + i8042_ctr &= ~I8042_CTR_XLATE; + +/* + * Write CTR back. + */ + + if (i8042_command(&i8042_ctr, I8042_CMD_CTL_WCTR)) { + printk(KERN_ERR "i8042.c: Can't write CTR while initializing i8042.\n"); + return -1; + } + + return 0; +} + + +/* + * Reset the controller. + */ +static void i8042_controller_reset(void) +{ + unsigned char param; + +/* + * Reset the controller if requested. + */ + + if (i8042_reset) + if (i8042_command(¶m, I8042_CMD_CTL_TEST)) + printk(KERN_ERR "i8042.c: i8042 controller reset timeout.\n"); + +/* + * Disable MUX mode if present. + */ + + if (i8042_mux_present) + i8042_set_mux_mode(0, NULL); + +/* + * Restore the original control register setting. + */ + + i8042_ctr = i8042_initial_ctr; + + if (i8042_command(&i8042_ctr, I8042_CMD_CTL_WCTR)) + printk(KERN_WARNING "i8042.c: Can't restore CTR.\n"); +} + + +/* + * Here we try to reset everything back to a state in which the BIOS will be + * able to talk to the hardware when rebooting. + */ + +static void i8042_controller_cleanup(void) +{ + int i; + + i8042_flush(); + +/* + * Reset anything that is connected to the ports. + */ + + for (i = 0; i < I8042_NUM_PORTS; i++) + if (i8042_ports[i].exists) + serio_cleanup(i8042_ports[i].serio); + + i8042_controller_reset(); +} + + +/* + * i8042_panic_blink() will flash the keyboard LEDs and is called when + * kernel panics. Flashing LEDs is useful for users running X who may + * not see the console and will help distingushing panics from "real" + * lockups. + * + * Note that DELAY has a limit of 10ms so we will not get stuck here + * waiting for KBC to free up even if KBD interrupt is off + */ + +#define DELAY do { mdelay(1); if (++delay > 10) return delay; } while(0) + +static long i8042_panic_blink(long count) +{ + long delay = 0; + static long last_blink; + static char led; + + /* + * We expect frequency to be about 1/2s. KDB uses about 1s. + * Make sure they are different. + */ + if (!i8042_blink_frequency) + return 0; + if (count - last_blink < i8042_blink_frequency) + return 0; + + led ^= 0x01 | 0x04; + while (i8042_read_status() & I8042_STR_IBF) + DELAY; + i8042_write_data(0xed); /* set leds */ + DELAY; + while (i8042_read_status() & I8042_STR_IBF) + DELAY; + DELAY; + i8042_write_data(led); + DELAY; + last_blink = count; + return delay; +} + +#undef DELAY + +/* + * Here we try to restore the original BIOS settings + */ + +static int i8042_suspend(struct device *dev, pm_message_t state, u32 level) +{ + if (level == SUSPEND_DISABLE) { + del_timer_sync(&i8042_timer); + i8042_controller_reset(); + } + + return 0; +} + + +/* + * Here we try to reset everything back to a state in which suspended + */ + +static int i8042_resume(struct device *dev, u32 level) +{ + int i; + + if (level != RESUME_ENABLE) + return 0; + + if (i8042_controller_init()) { + printk(KERN_ERR "i8042: resume failed\n"); + return -1; + } + + if (i8042_mux_present) + if (i8042_set_mux_mode(1, NULL) || i8042_enable_mux_ports()) + printk(KERN_WARNING "i8042: failed to resume active multiplexor, mouse won't work.\n"); + +/* + * Activate all ports. + */ + + for (i = 0; i < I8042_NUM_PORTS; i++) + i8042_activate_port(&i8042_ports[i]); + +/* + * Restart timer (for polling "stuck" data) + */ + mod_timer(&i8042_timer, jiffies + I8042_POLL_PERIOD); + + panic_blink = i8042_panic_blink; + + return 0; + +} + +/* + * We need to reset the 8042 back to original mode on system shutdown, + * because otherwise BIOSes will be confused. + */ + +static void i8042_shutdown(struct device *dev) +{ + i8042_controller_cleanup(); +} + +static struct device_driver i8042_driver = { + .name = "i8042", + .bus = &platform_bus_type, + .suspend = i8042_suspend, + .resume = i8042_resume, + .shutdown = i8042_shutdown, +}; + +static void __init i8042_create_kbd_port(void) +{ + struct serio *serio; + struct i8042_port *port = &i8042_ports[I8042_KBD_PORT_NO]; + + serio = kmalloc(sizeof(struct serio), GFP_KERNEL); + if (serio) { + memset(serio, 0, sizeof(struct serio)); + serio->id.type = i8042_direct ? SERIO_8042 : SERIO_8042_XL; + serio->write = i8042_dumbkbd ? NULL : i8042_kbd_write; + serio->open = i8042_open; + serio->close = i8042_close; + serio->start = i8042_start; + serio->stop = i8042_stop; + serio->port_data = port; + serio->dev.parent = &i8042_platform_device->dev; + strlcpy(serio->name, "i8042 Kbd Port", sizeof(serio->name)); + strlcpy(serio->phys, I8042_KBD_PHYS_DESC, sizeof(serio->phys)); + + port->serio = serio; + i8042_port_register(port); + } +} + +static void __init i8042_create_aux_port(void) +{ + struct serio *serio; + struct i8042_port *port = &i8042_ports[I8042_AUX_PORT_NO]; + + serio = kmalloc(sizeof(struct serio), GFP_KERNEL); + if (serio) { + memset(serio, 0, sizeof(struct serio)); + serio->id.type = SERIO_8042; + serio->write = i8042_aux_write; + serio->open = i8042_open; + serio->close = i8042_close; + serio->start = i8042_start; + serio->stop = i8042_stop; + serio->port_data = port; + serio->dev.parent = &i8042_platform_device->dev; + strlcpy(serio->name, "i8042 Aux Port", sizeof(serio->name)); + strlcpy(serio->phys, I8042_AUX_PHYS_DESC, sizeof(serio->phys)); + + port->serio = serio; + i8042_port_register(port); + } +} + +static void __init i8042_create_mux_port(int index) +{ + struct serio *serio; + struct i8042_port *port = &i8042_ports[I8042_MUX_PORT_NO + index]; + + serio = kmalloc(sizeof(struct serio), GFP_KERNEL); + if (serio) { + memset(serio, 0, sizeof(struct serio)); + serio->id.type = SERIO_8042; + serio->write = i8042_aux_write; + serio->open = i8042_open; + serio->close = i8042_close; + serio->start = i8042_start; + serio->stop = i8042_stop; + serio->port_data = port; + serio->dev.parent = &i8042_platform_device->dev; + snprintf(serio->name, sizeof(serio->name), "i8042 Aux-%d Port", index); + snprintf(serio->phys, sizeof(serio->phys), I8042_MUX_PHYS_DESC, index + 1); + + *port = i8042_ports[I8042_AUX_PORT_NO]; + port->exists = 0; + snprintf(port->name, sizeof(port->name), "AUX%d", index); + port->mux = index; + port->serio = serio; + i8042_port_register(port); + } +} + +static int __init i8042_init(void) +{ + int i; + int err; + + dbg_init(); + + init_timer(&i8042_timer); + i8042_timer.function = i8042_timer_func; + + if (i8042_platform_init()) + return -EBUSY; + + i8042_ports[I8042_AUX_PORT_NO].irq = I8042_AUX_IRQ; + i8042_ports[I8042_KBD_PORT_NO].irq = I8042_KBD_IRQ; + + if (i8042_controller_init()) { + i8042_platform_exit(); + return -ENODEV; + } + + err = driver_register(&i8042_driver); + if (err) { + i8042_platform_exit(); + return err; + } + + i8042_platform_device = platform_device_register_simple("i8042", -1, NULL, 0); + if (IS_ERR(i8042_platform_device)) { + driver_unregister(&i8042_driver); + i8042_platform_exit(); + return PTR_ERR(i8042_platform_device); + } + + if (!i8042_noaux && !i8042_check_aux()) { + if (!i8042_nomux && !i8042_check_mux()) + for (i = 0; i < I8042_NUM_MUX_PORTS; i++) + i8042_create_mux_port(i); + else + i8042_create_aux_port(); + } + + i8042_create_kbd_port(); + + mod_timer(&i8042_timer, jiffies + I8042_POLL_PERIOD); + + return 0; +} + +static void __exit i8042_exit(void) +{ + int i; + + i8042_controller_cleanup(); + + for (i = 0; i < I8042_NUM_PORTS; i++) + if (i8042_ports[i].exists) + serio_unregister_port(i8042_ports[i].serio); + + del_timer_sync(&i8042_timer); + + platform_device_unregister(i8042_platform_device); + driver_unregister(&i8042_driver); + + i8042_platform_exit(); + + panic_blink = NULL; +} + +module_init(i8042_init); +module_exit(i8042_exit); diff --git a/drivers/input/serio/i8042.h b/drivers/input/serio/i8042.h new file mode 100644 index 00000000000..13835039a2a --- /dev/null +++ b/drivers/input/serio/i8042.h @@ -0,0 +1,133 @@ +#ifndef _I8042_H +#define _I8042_H + +#include <linux/config.h> + +/* + * Copyright (c) 1999-2002 Vojtech Pavlik + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +/* + * Arch-dependent inline functions and defines. + */ + +#if defined(CONFIG_MACH_JAZZ) +#include "i8042-jazzio.h" +#elif defined(CONFIG_SGI_IP22) +#include "i8042-ip22io.h" +#elif defined(CONFIG_PPC) +#include "i8042-ppcio.h" +#elif defined(CONFIG_SPARC32) || defined(CONFIG_SPARC64) +#include "i8042-sparcio.h" +#elif defined(CONFIG_X86) || defined(CONFIG_IA64) +#include "i8042-x86ia64io.h" +#else +#include "i8042-io.h" +#endif + +/* + * This is in 50us units, the time we wait for the i8042 to react. This + * has to be long enough for the i8042 itself to timeout on sending a byte + * to a non-existent mouse. + */ + +#define I8042_CTL_TIMEOUT 10000 + +/* + * When the device isn't opened and it's interrupts aren't used, we poll it at + * regular intervals to see if any characters arrived. If yes, we can start + * probing for any mouse / keyboard connected. This is the period of the + * polling. + */ + +#define I8042_POLL_PERIOD HZ/20 + +/* + * Status register bits. + */ + +#define I8042_STR_PARITY 0x80 +#define I8042_STR_TIMEOUT 0x40 +#define I8042_STR_AUXDATA 0x20 +#define I8042_STR_KEYLOCK 0x10 +#define I8042_STR_CMDDAT 0x08 +#define I8042_STR_MUXERR 0x04 +#define I8042_STR_IBF 0x02 +#define I8042_STR_OBF 0x01 + +/* + * Control register bits. + */ + +#define I8042_CTR_KBDINT 0x01 +#define I8042_CTR_AUXINT 0x02 +#define I8042_CTR_IGNKEYLOCK 0x08 +#define I8042_CTR_KBDDIS 0x10 +#define I8042_CTR_AUXDIS 0x20 +#define I8042_CTR_XLATE 0x40 + +/* + * Commands. + */ + +#define I8042_CMD_CTL_RCTR 0x0120 +#define I8042_CMD_CTL_WCTR 0x1060 +#define I8042_CMD_CTL_TEST 0x01aa + +#define I8042_CMD_KBD_DISABLE 0x00ad +#define I8042_CMD_KBD_ENABLE 0x00ae +#define I8042_CMD_KBD_TEST 0x01ab +#define I8042_CMD_KBD_LOOP 0x11d2 + +#define I8042_CMD_AUX_DISABLE 0x00a7 +#define I8042_CMD_AUX_ENABLE 0x00a8 +#define I8042_CMD_AUX_TEST 0x01a9 +#define I8042_CMD_AUX_SEND 0x10d4 +#define I8042_CMD_AUX_LOOP 0x11d3 + +#define I8042_CMD_MUX_PFX 0x0090 +#define I8042_CMD_MUX_SEND 0x1090 + +/* + * Return codes. + */ + +#define I8042_RET_CTL_TEST 0x55 + +/* + * Expected maximum internal i8042 buffer size. This is used for flushing + * the i8042 buffers. + */ + +#define I8042_BUFFER_SIZE 16 + +/* + * Number of AUX ports on controllers supporting active multiplexing + * specification + */ + +#define I8042_NUM_MUX_PORTS 4 + +/* + * Debug. + */ + +#ifdef DEBUG +static unsigned long i8042_start_time; +#define dbg_init() do { i8042_start_time = jiffies; } while (0) +#define dbg(format, arg...) \ + do { \ + if (i8042_debug) \ + printk(KERN_DEBUG __FILE__ ": " format " [%d]\n" , \ + ## arg, (int) (jiffies - i8042_start_time)); \ + } while (0) +#else +#define dbg_init() do { } while (0) +#define dbg(format, arg...) do {} while (0) +#endif + +#endif /* _I8042_H */ diff --git a/drivers/input/serio/libps2.c b/drivers/input/serio/libps2.c new file mode 100644 index 00000000000..c978657068c --- /dev/null +++ b/drivers/input/serio/libps2.c @@ -0,0 +1,305 @@ +/* + * PS/2 driver library + * + * Copyright (c) 1999-2002 Vojtech Pavlik + * Copyright (c) 2004 Dmitry Torokhov + */ + +/* + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/input.h> +#include <linux/serio.h> +#include <linux/init.h> +#include <linux/libps2.h> + +#define DRIVER_DESC "PS/2 driver library" + +MODULE_AUTHOR("Dmitry Torokhov <dtor@mail.ru>"); +MODULE_DESCRIPTION("PS/2 driver library"); +MODULE_LICENSE("GPL"); + +EXPORT_SYMBOL(ps2_init); +EXPORT_SYMBOL(ps2_sendbyte); +EXPORT_SYMBOL(ps2_command); +EXPORT_SYMBOL(ps2_schedule_command); +EXPORT_SYMBOL(ps2_handle_ack); +EXPORT_SYMBOL(ps2_handle_response); +EXPORT_SYMBOL(ps2_cmd_aborted); + +/* Work structure to schedule execution of a command */ +struct ps2work { + struct work_struct work; + struct ps2dev *ps2dev; + int command; + unsigned char param[0]; +}; + + +/* + * ps2_sendbyte() sends a byte to the mouse, and waits for acknowledge. + * It doesn't handle retransmission, though it could - because when there would + * be need for retransmissions, the mouse has to be replaced anyway. + * + * ps2_sendbyte() can only be called from a process context + */ + +int ps2_sendbyte(struct ps2dev *ps2dev, unsigned char byte, int timeout) +{ + serio_pause_rx(ps2dev->serio); + ps2dev->nak = 1; + ps2dev->flags |= PS2_FLAG_ACK; + serio_continue_rx(ps2dev->serio); + + if (serio_write(ps2dev->serio, byte) == 0) + wait_event_timeout(ps2dev->wait, + !(ps2dev->flags & PS2_FLAG_ACK), + msecs_to_jiffies(timeout)); + + serio_pause_rx(ps2dev->serio); + ps2dev->flags &= ~PS2_FLAG_ACK; + serio_continue_rx(ps2dev->serio); + + return -ps2dev->nak; +} + +/* + * ps2_command() sends a command and its parameters to the mouse, + * then waits for the response and puts it in the param array. + * + * ps2_command() can only be called from a process context + */ + +int ps2_command(struct ps2dev *ps2dev, unsigned char *param, int command) +{ + int timeout; + int send = (command >> 12) & 0xf; + int receive = (command >> 8) & 0xf; + int rc = -1; + int i; + + down(&ps2dev->cmd_sem); + + serio_pause_rx(ps2dev->serio); + ps2dev->flags = command == PS2_CMD_GETID ? PS2_FLAG_WAITID : 0; + ps2dev->cmdcnt = receive; + if (receive && param) + for (i = 0; i < receive; i++) + ps2dev->cmdbuf[(receive - 1) - i] = param[i]; + serio_continue_rx(ps2dev->serio); + + /* + * Some devices (Synaptics) peform the reset before + * ACKing the reset command, and so it can take a long + * time before the ACK arrrives. + */ + if (command & 0xff) + if (ps2_sendbyte(ps2dev, command & 0xff, + command == PS2_CMD_RESET_BAT ? 1000 : 200)) + goto out; + + for (i = 0; i < send; i++) + if (ps2_sendbyte(ps2dev, param[i], 200)) + goto out; + + /* + * The reset command takes a long time to execute. + */ + timeout = msecs_to_jiffies(command == PS2_CMD_RESET_BAT ? 4000 : 500); + + timeout = wait_event_timeout(ps2dev->wait, + !(ps2dev->flags & PS2_FLAG_CMD1), timeout); + + if (ps2dev->cmdcnt && timeout > 0) { + + if (command == PS2_CMD_RESET_BAT && timeout > msecs_to_jiffies(100)) { + /* + * Device has sent the first response byte + * after a reset command, reset is thus done, + * shorten the timeout. The next byte will come + * soon (keyboard) or not at all (mouse). + */ + timeout = msecs_to_jiffies(100); + } + + if (command == PS2_CMD_GETID && + ps2dev->cmdbuf[receive - 1] != 0xab && /* Regular keyboards */ + ps2dev->cmdbuf[receive - 1] != 0xac && /* NCD Sun keyboard */ + ps2dev->cmdbuf[receive - 1] != 0x2b && /* Trust keyboard, translated */ + ps2dev->cmdbuf[receive - 1] != 0x5d && /* Trust keyboard */ + ps2dev->cmdbuf[receive - 1] != 0x60 && /* NMB SGI keyboard, translated */ + ps2dev->cmdbuf[receive - 1] != 0x47) { /* NMB SGI keyboard */ + /* + * Device behind the port is not a keyboard + * so we don't need to wait for the 2nd byte + * of ID response. + */ + serio_pause_rx(ps2dev->serio); + ps2dev->flags = ps2dev->cmdcnt = 0; + serio_continue_rx(ps2dev->serio); + } + + wait_event_timeout(ps2dev->wait, + !(ps2dev->flags & PS2_FLAG_CMD), timeout); + } + + if (param) + for (i = 0; i < receive; i++) + param[i] = ps2dev->cmdbuf[(receive - 1) - i]; + + if (ps2dev->cmdcnt && (command != PS2_CMD_RESET_BAT || ps2dev->cmdcnt != 1)) + goto out; + + rc = 0; + +out: + serio_pause_rx(ps2dev->serio); + ps2dev->flags = 0; + serio_continue_rx(ps2dev->serio); + + up(&ps2dev->cmd_sem); + return rc; +} + +/* + * ps2_execute_scheduled_command() sends a command, previously scheduled by + * ps2_schedule_command(), to a PS/2 device (keyboard, mouse, etc.) + */ + +static void ps2_execute_scheduled_command(void *data) +{ + struct ps2work *ps2work = data; + + ps2_command(ps2work->ps2dev, ps2work->param, ps2work->command); + kfree(ps2work); +} + +/* + * ps2_schedule_command() allows to schedule delayed execution of a PS/2 + * command and can be used to issue a command from an interrupt or softirq + * context. + */ + +int ps2_schedule_command(struct ps2dev *ps2dev, unsigned char *param, int command) +{ + struct ps2work *ps2work; + int send = (command >> 12) & 0xf; + int receive = (command >> 8) & 0xf; + + if (!(ps2work = kmalloc(sizeof(struct ps2work) + max(send, receive), GFP_ATOMIC))) + return -1; + + memset(ps2work, 0, sizeof(struct ps2work)); + ps2work->ps2dev = ps2dev; + ps2work->command = command; + memcpy(ps2work->param, param, send); + INIT_WORK(&ps2work->work, ps2_execute_scheduled_command, ps2work); + + if (!schedule_work(&ps2work->work)) { + kfree(ps2work); + return -1; + } + + return 0; +} + +/* + * ps2_init() initializes ps2dev structure + */ + +void ps2_init(struct ps2dev *ps2dev, struct serio *serio) +{ + init_MUTEX(&ps2dev->cmd_sem); + init_waitqueue_head(&ps2dev->wait); + ps2dev->serio = serio; +} + +/* + * ps2_handle_ack() is supposed to be used in interrupt handler + * to properly process ACK/NAK of a command from a PS/2 device. + */ + +int ps2_handle_ack(struct ps2dev *ps2dev, unsigned char data) +{ + switch (data) { + case PS2_RET_ACK: + ps2dev->nak = 0; + break; + + case PS2_RET_NAK: + ps2dev->nak = 1; + break; + + /* + * Workaround for mice which don't ACK the Get ID command. + * These are valid mouse IDs that we recognize. + */ + case 0x00: + case 0x03: + case 0x04: + if (ps2dev->flags & PS2_FLAG_WAITID) { + ps2dev->nak = 0; + break; + } + /* Fall through */ + default: + return 0; + } + + + if (!ps2dev->nak && ps2dev->cmdcnt) + ps2dev->flags |= PS2_FLAG_CMD | PS2_FLAG_CMD1; + + ps2dev->flags &= ~PS2_FLAG_ACK; + wake_up(&ps2dev->wait); + + if (data != PS2_RET_ACK) + ps2_handle_response(ps2dev, data); + + return 1; +} + +/* + * ps2_handle_response() is supposed to be used in interrupt handler + * to properly store device's response to a command and notify process + * waiting for completion of the command. + */ + +int ps2_handle_response(struct ps2dev *ps2dev, unsigned char data) +{ + if (ps2dev->cmdcnt) + ps2dev->cmdbuf[--ps2dev->cmdcnt] = data; + + if (ps2dev->flags & PS2_FLAG_CMD1) { + ps2dev->flags &= ~PS2_FLAG_CMD1; + if (ps2dev->cmdcnt) + wake_up(&ps2dev->wait); + } + + if (!ps2dev->cmdcnt) { + ps2dev->flags &= ~PS2_FLAG_CMD; + wake_up(&ps2dev->wait); + } + + return 1; +} + +void ps2_cmd_aborted(struct ps2dev *ps2dev) +{ + if (ps2dev->flags & PS2_FLAG_ACK) + ps2dev->nak = 1; + + if (ps2dev->flags & (PS2_FLAG_ACK | PS2_FLAG_CMD)) + wake_up(&ps2dev->wait); + + ps2dev->flags = 0; +} + diff --git a/drivers/input/serio/maceps2.c b/drivers/input/serio/maceps2.c new file mode 100644 index 00000000000..9880fc145d9 --- /dev/null +++ b/drivers/input/serio/maceps2.c @@ -0,0 +1,176 @@ +/* + * SGI O2 MACE PS2 controller driver for linux + * + * Copyright (C) 2002 Vivien Chappelier + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation + */ +#include <linux/module.h> +#include <linux/init.h> +#include <linux/serio.h> +#include <linux/errno.h> +#include <linux/interrupt.h> +#include <linux/ioport.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/err.h> + +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/system.h> +#include <asm/ip32/mace.h> +#include <asm/ip32/ip32_ints.h> + +MODULE_AUTHOR("Vivien Chappelier <vivien.chappelier@linux-mips.org"); +MODULE_DESCRIPTION("SGI O2 MACE PS2 controller driver"); +MODULE_LICENSE("GPL"); + +#define MACE_PS2_TIMEOUT 10000 /* in 50us unit */ + +#define PS2_STATUS_CLOCK_SIGNAL BIT(0) /* external clock signal */ +#define PS2_STATUS_CLOCK_INHIBIT BIT(1) /* clken output signal */ +#define PS2_STATUS_TX_INPROGRESS BIT(2) /* transmission in progress */ +#define PS2_STATUS_TX_EMPTY BIT(3) /* empty transmit buffer */ +#define PS2_STATUS_RX_FULL BIT(4) /* full receive buffer */ +#define PS2_STATUS_RX_INPROGRESS BIT(5) /* reception in progress */ +#define PS2_STATUS_ERROR_PARITY BIT(6) /* parity error */ +#define PS2_STATUS_ERROR_FRAMING BIT(7) /* framing error */ + +#define PS2_CONTROL_TX_CLOCK_DISABLE BIT(0) /* inhibit clock signal after TX */ +#define PS2_CONTROL_TX_ENABLE BIT(1) /* transmit enable */ +#define PS2_CONTROL_TX_INT_ENABLE BIT(2) /* enable transmit interrupt */ +#define PS2_CONTROL_RX_INT_ENABLE BIT(3) /* enable receive interrupt */ +#define PS2_CONTROL_RX_CLOCK_ENABLE BIT(4) /* pause reception if set to 0 */ +#define PS2_CONTROL_RESET BIT(5) /* reset */ + +struct maceps2_data { + struct mace_ps2port *port; + int irq; +}; + +static struct maceps2_data port_data[2]; +static struct serio *maceps2_port[2]; +static struct platform_device *maceps2_device; + +static int maceps2_write(struct serio *dev, unsigned char val) +{ + struct mace_ps2port *port = ((struct maceps2_data *)dev->port_data)->port; + unsigned int timeout = MACE_PS2_TIMEOUT; + + do { + if (port->status & PS2_STATUS_TX_EMPTY) { + port->tx = val; + return 0; + } + udelay(50); + } while (timeout--); + + return -1; +} + +static irqreturn_t maceps2_interrupt(int irq, void *dev_id, + struct pt_regs *regs) +{ + struct serio *dev = dev_id; + struct mace_ps2port *port = ((struct maceps2_data *)dev->port_data)->port; + unsigned long byte; + + if (port->status & PS2_STATUS_RX_FULL) { + byte = port->rx; + serio_interrupt(dev, byte & 0xff, 0, regs); + } + + return IRQ_HANDLED; +} + +static int maceps2_open(struct serio *dev) +{ + struct maceps2_data *data = (struct maceps2_data *)dev->port_data; + + if (request_irq(data->irq, maceps2_interrupt, 0, "PS2 port", dev)) { + printk(KERN_ERR "Could not allocate PS/2 IRQ\n"); + return -EBUSY; + } + + /* Reset port */ + data->port->control = PS2_CONTROL_TX_CLOCK_DISABLE | PS2_CONTROL_RESET; + udelay(100); + + /* Enable interrupts */ + data->port->control = PS2_CONTROL_RX_CLOCK_ENABLE | + PS2_CONTROL_TX_ENABLE | + PS2_CONTROL_RX_INT_ENABLE; + + return 0; +} + +static void maceps2_close(struct serio *dev) +{ + struct maceps2_data *data = (struct maceps2_data *)dev->port_data; + + data->port->control = PS2_CONTROL_TX_CLOCK_DISABLE | PS2_CONTROL_RESET; + udelay(100); + free_irq(data->irq, dev); +} + + +static struct serio * __init maceps2_allocate_port(int idx) +{ + struct serio *serio; + + serio = kmalloc(sizeof(struct serio), GFP_KERNEL); + if (serio) { + memset(serio, 0, sizeof(struct serio)); + serio->id.type = SERIO_8042; + serio->write = maceps2_write; + serio->open = maceps2_open; + serio->close = maceps2_close; + snprintf(serio->name, sizeof(serio->name), "MACE PS/2 port%d", idx); + snprintf(serio->phys, sizeof(serio->phys), "mace/serio%d", idx); + serio->port_data = &port_data[idx]; + serio->dev.parent = &maceps2_device->dev; + } + + return serio; +} + + +static int __init maceps2_init(void) +{ + maceps2_device = platform_device_register_simple("maceps2", -1, NULL, 0); + if (IS_ERR(maceps2_device)) + return PTR_ERR(maceps2_device); + + port_data[0].port = &mace->perif.ps2.keyb; + port_data[0].irq = MACEISA_KEYB_IRQ; + port_data[1].port = &mace->perif.ps2.mouse; + port_data[1].irq = MACEISA_MOUSE_IRQ; + + maceps2_port[0] = maceps2_allocate_port(0); + maceps2_port[1] = maceps2_allocate_port(1); + if (!maceps2_port[0] || !maceps2_port[1]) { + kfree(maceps2_port[0]); + kfree(maceps2_port[1]); + platform_device_unregister(maceps2_device); + return -ENOMEM; + } + + serio_register_port(maceps2_port[0]); + serio_register_port(maceps2_port[1]); + + return 0; +} + +static void __exit maceps2_exit(void) +{ + serio_unregister_port(maceps2_port[0]); + serio_unregister_port(maceps2_port[1]); + platform_device_unregister(maceps2_device); +} + +module_init(maceps2_init); +module_exit(maceps2_exit); diff --git a/drivers/input/serio/parkbd.c b/drivers/input/serio/parkbd.c new file mode 100644 index 00000000000..1d15c281981 --- /dev/null +++ b/drivers/input/serio/parkbd.c @@ -0,0 +1,218 @@ +/* + * Parallel port to Keyboard port adapter driver for Linux + * + * Copyright (c) 1999-2004 Vojtech Pavlik + */ + +/* + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +/* + * To connect an AT or XT keyboard to the parallel port, a fairly simple adapter + * can be made: + * + * Parallel port Keyboard port + * + * +5V --------------------- +5V (4) + * + * ______ + * +5V -------|______|--. + * | + * ACK (10) ------------| + * |--- KBD CLOCK (5) + * STROBE (1) ---|<|----' + * + * ______ + * +5V -------|______|--. + * | + * BUSY (11) -----------| + * |--- KBD DATA (1) + * AUTOFD (14) --|<|----' + * + * GND (18-25) ------------- GND (3) + * + * The diodes can be fairly any type, and the resistors should be somewhere + * around 5 kOhm, but the adapter will likely work without the resistors, + * too. + * + * The +5V source can be taken either from USB, from mouse or keyboard ports, + * or from a joystick port. Unfortunately, the parallel port of a PC doesn't + * have a +5V pin, and feeding the keyboard from signal pins is out of question + * with 300 mA power reqirement of a typical AT keyboard. + */ + +#include <linux/module.h> +#include <linux/parport.h> +#include <linux/init.h> +#include <linux/serio.h> + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION("Parallel port to Keyboard port adapter driver"); +MODULE_LICENSE("GPL"); + +static unsigned int parkbd_pp_no; +module_param_named(port, parkbd_pp_no, int, 0); +MODULE_PARM_DESC(port, "Parallel port the adapter is connected to (default is 0)"); + +static unsigned int parkbd_mode = SERIO_8042; +module_param_named(mode, parkbd_mode, uint, 0); +MODULE_PARM_DESC(mode, "Mode of operation: XT = 0/AT = 1 (default)"); + +#define PARKBD_CLOCK 0x01 /* Strobe & Ack */ +#define PARKBD_DATA 0x02 /* AutoFd & Busy */ + +static int parkbd_buffer; +static int parkbd_counter; +static unsigned long parkbd_last; +static int parkbd_writing; +static unsigned long parkbd_start; + +static struct pardevice *parkbd_dev; +static struct serio *parkbd_port; + +static int parkbd_readlines(void) +{ + return (parport_read_status(parkbd_dev->port) >> 6) ^ 2; +} + +static void parkbd_writelines(int data) +{ + parport_write_control(parkbd_dev->port, (~data & 3) | 0x10); +} + +static int parkbd_write(struct serio *port, unsigned char c) +{ + unsigned char p; + + if (!parkbd_mode) return -1; + + p = c ^ (c >> 4); + p = p ^ (p >> 2); + p = p ^ (p >> 1); + + parkbd_counter = 0; + parkbd_writing = 1; + parkbd_buffer = c | (((int) (~p & 1)) << 8) | 0x600; + + parkbd_writelines(2); + + return 0; +} + +static void parkbd_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + + if (parkbd_writing) { + + if (parkbd_counter && ((parkbd_counter == 11) || time_after(jiffies, parkbd_last + HZ/100))) { + parkbd_counter = 0; + parkbd_buffer = 0; + parkbd_writing = 0; + parkbd_writelines(3); + return; + } + + parkbd_writelines(((parkbd_buffer >> parkbd_counter++) & 1) | 2); + + if (parkbd_counter == 11) { + parkbd_counter = 0; + parkbd_buffer = 0; + parkbd_writing = 0; + parkbd_writelines(3); + } + + } else { + + if ((parkbd_counter == parkbd_mode + 10) || time_after(jiffies, parkbd_last + HZ/100)) { + parkbd_counter = 0; + parkbd_buffer = 0; + } + + parkbd_buffer |= (parkbd_readlines() >> 1) << parkbd_counter++; + + if (parkbd_counter == parkbd_mode + 10) + serio_interrupt(parkbd_port, (parkbd_buffer >> (2 - parkbd_mode)) & 0xff, 0, regs); + } + + parkbd_last = jiffies; +} + +static int parkbd_getport(void) +{ + struct parport *pp; + + pp = parport_find_number(parkbd_pp_no); + + if (pp == NULL) { + printk(KERN_ERR "parkbd: no such parport\n"); + return -ENODEV; + } + + parkbd_dev = parport_register_device(pp, "parkbd", NULL, NULL, parkbd_interrupt, PARPORT_DEV_EXCL, NULL); + parport_put_port(pp); + + if (!parkbd_dev) + return -ENODEV; + + if (parport_claim(parkbd_dev)) { + parport_unregister_device(parkbd_dev); + return -EBUSY; + } + + parkbd_start = jiffies; + + return 0; +} + +static struct serio * __init parkbd_allocate_serio(void) +{ + struct serio *serio; + + serio = kmalloc(sizeof(struct serio), GFP_KERNEL); + if (serio) { + memset(serio, 0, sizeof(struct serio)); + serio->id.type = parkbd_mode; + serio->write = parkbd_write, + strlcpy(serio->name, "PARKBD AT/XT keyboard adapter", sizeof(serio->name)); + snprintf(serio->phys, sizeof(serio->phys), "%s/serio0", parkbd_dev->port->name); + } + + return serio; +} + +static int __init parkbd_init(void) +{ + int err; + + err = parkbd_getport(); + if (err) + return err; + + parkbd_port = parkbd_allocate_serio(); + if (!parkbd_port) { + parport_release(parkbd_dev); + return -ENOMEM; + } + + parkbd_writelines(3); + + serio_register_port(parkbd_port); + + printk(KERN_INFO "serio: PARKBD %s adapter on %s\n", + parkbd_mode ? "AT" : "XT", parkbd_dev->port->name); + + return 0; +} + +static void __exit parkbd_exit(void) +{ + parport_release(parkbd_dev); + serio_unregister_port(parkbd_port); + parport_unregister_device(parkbd_dev); +} + +module_init(parkbd_init); +module_exit(parkbd_exit); diff --git a/drivers/input/serio/pcips2.c b/drivers/input/serio/pcips2.c new file mode 100644 index 00000000000..1e139c5e59d --- /dev/null +++ b/drivers/input/serio/pcips2.c @@ -0,0 +1,234 @@ +/* + * linux/drivers/input/serio/pcips2.c + * + * Copyright (C) 2003 Russell King, All Rights Reserved. + * + * 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. + * + * I'm not sure if this is a generic PS/2 PCI interface or specific to + * the Mobility Electronics docking station. + */ +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/ioport.h> +#include <linux/input.h> +#include <linux/pci.h> +#include <linux/init.h> +#include <linux/serio.h> +#include <linux/delay.h> +#include <asm/io.h> + +#define PS2_CTRL (0) +#define PS2_STATUS (1) +#define PS2_DATA (2) + +#define PS2_CTRL_CLK (1<<0) +#define PS2_CTRL_DAT (1<<1) +#define PS2_CTRL_TXIRQ (1<<2) +#define PS2_CTRL_ENABLE (1<<3) +#define PS2_CTRL_RXIRQ (1<<4) + +#define PS2_STAT_CLK (1<<0) +#define PS2_STAT_DAT (1<<1) +#define PS2_STAT_PARITY (1<<2) +#define PS2_STAT_RXFULL (1<<5) +#define PS2_STAT_TXBUSY (1<<6) +#define PS2_STAT_TXEMPTY (1<<7) + +struct pcips2_data { + struct serio *io; + unsigned int base; + struct pci_dev *dev; +}; + +static int pcips2_write(struct serio *io, unsigned char val) +{ + struct pcips2_data *ps2if = io->port_data; + unsigned int stat; + + do { + stat = inb(ps2if->base + PS2_STATUS); + cpu_relax(); + } while (!(stat & PS2_STAT_TXEMPTY)); + + outb(val, ps2if->base + PS2_DATA); + + return 0; +} + +static irqreturn_t pcips2_interrupt(int irq, void *devid, struct pt_regs *regs) +{ + struct pcips2_data *ps2if = devid; + unsigned char status, scancode; + int handled = 0; + + do { + unsigned int flag; + + status = inb(ps2if->base + PS2_STATUS); + if (!(status & PS2_STAT_RXFULL)) + break; + handled = 1; + scancode = inb(ps2if->base + PS2_DATA); + if (status == 0xff && scancode == 0xff) + break; + + flag = (status & PS2_STAT_PARITY) ? 0 : SERIO_PARITY; + + if (hweight8(scancode) & 1) + flag ^= SERIO_PARITY; + + serio_interrupt(ps2if->io, scancode, flag, regs); + } while (1); + return IRQ_RETVAL(handled); +} + +static void pcips2_flush_input(struct pcips2_data *ps2if) +{ + unsigned char status, scancode; + + do { + status = inb(ps2if->base + PS2_STATUS); + if (!(status & PS2_STAT_RXFULL)) + break; + scancode = inb(ps2if->base + PS2_DATA); + if (status == 0xff && scancode == 0xff) + break; + } while (1); +} + +static int pcips2_open(struct serio *io) +{ + struct pcips2_data *ps2if = io->port_data; + int ret, val = 0; + + outb(PS2_CTRL_ENABLE, ps2if->base); + pcips2_flush_input(ps2if); + + ret = request_irq(ps2if->dev->irq, pcips2_interrupt, SA_SHIRQ, + "pcips2", ps2if); + if (ret == 0) + val = PS2_CTRL_ENABLE | PS2_CTRL_RXIRQ; + + outb(val, ps2if->base); + + return ret; +} + +static void pcips2_close(struct serio *io) +{ + struct pcips2_data *ps2if = io->port_data; + + outb(0, ps2if->base); + + free_irq(ps2if->dev->irq, ps2if); +} + +static int __devinit pcips2_probe(struct pci_dev *dev, const struct pci_device_id *id) +{ + struct pcips2_data *ps2if; + struct serio *serio; + int ret; + + ret = pci_enable_device(dev); + if (ret) + goto out; + + ret = pci_request_regions(dev, "pcips2"); + if (ret) + goto disable; + + ps2if = kmalloc(sizeof(struct pcips2_data), GFP_KERNEL); + serio = kmalloc(sizeof(struct serio), GFP_KERNEL); + if (!ps2if || !serio) { + ret = -ENOMEM; + goto release; + } + + memset(ps2if, 0, sizeof(struct pcips2_data)); + memset(serio, 0, sizeof(struct serio)); + + serio->id.type = SERIO_8042; + serio->write = pcips2_write; + serio->open = pcips2_open; + serio->close = pcips2_close; + strlcpy(serio->name, pci_name(dev), sizeof(serio->name)); + strlcpy(serio->phys, dev->dev.bus_id, sizeof(serio->phys)); + serio->port_data = ps2if; + serio->dev.parent = &dev->dev; + ps2if->io = serio; + ps2if->dev = dev; + ps2if->base = pci_resource_start(dev, 0); + + pci_set_drvdata(dev, ps2if); + + serio_register_port(ps2if->io); + return 0; + + release: + kfree(ps2if); + kfree(serio); + pci_release_regions(dev); + disable: + pci_disable_device(dev); + out: + return ret; +} + +static void __devexit pcips2_remove(struct pci_dev *dev) +{ + struct pcips2_data *ps2if = pci_get_drvdata(dev); + + serio_unregister_port(ps2if->io); + pci_set_drvdata(dev, NULL); + kfree(ps2if); + pci_release_regions(dev); + pci_disable_device(dev); +} + +static struct pci_device_id pcips2_ids[] = { + { + .vendor = 0x14f2, /* MOBILITY */ + .device = 0x0123, /* Keyboard */ + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + .class = PCI_CLASS_INPUT_KEYBOARD << 8, + .class_mask = 0xffff00, + }, + { + .vendor = 0x14f2, /* MOBILITY */ + .device = 0x0124, /* Mouse */ + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + .class = PCI_CLASS_INPUT_MOUSE << 8, + .class_mask = 0xffff00, + }, + { 0, } +}; + +static struct pci_driver pcips2_driver = { + .name = "pcips2", + .id_table = pcips2_ids, + .probe = pcips2_probe, + .remove = __devexit_p(pcips2_remove), +}; + +static int __init pcips2_init(void) +{ + return pci_register_driver(&pcips2_driver); +} + +static void __exit pcips2_exit(void) +{ + pci_unregister_driver(&pcips2_driver); +} + +module_init(pcips2_init); +module_exit(pcips2_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Russell King <rmk@arm.linux.org.uk>"); +MODULE_DESCRIPTION("PCI PS/2 keyboard/mouse driver"); +MODULE_DEVICE_TABLE(pci, pcips2_ids); diff --git a/drivers/input/serio/q40kbd.c b/drivers/input/serio/q40kbd.c new file mode 100644 index 00000000000..46093c50798 --- /dev/null +++ b/drivers/input/serio/q40kbd.c @@ -0,0 +1,163 @@ +/* + * $Id: q40kbd.c,v 1.12 2002/02/02 22:26:44 vojtech Exp $ + * + * Copyright (c) 2000-2001 Vojtech Pavlik + * + * Based on the work of: + * Richard Zidlicky <Richard.Zidlicky@stud.informatik.uni-erlangen.de> + */ + +/* + * Q40 PS/2 keyboard controller driver for Linux/m68k + */ + +/* + * 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 program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Should you need to contact me, the author, you can do so either by + * e-mail - mail your message to <vojtech@ucw.cz>, or by paper mail: + * Vojtech Pavlik, Simunkova 1594, Prague 8, 182 00 Czech Republic + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/serio.h> +#include <linux/interrupt.h> +#include <linux/err.h> +#include <linux/bitops.h> + +#include <asm/io.h> +#include <asm/uaccess.h> +#include <asm/q40_master.h> +#include <asm/irq.h> +#include <asm/q40ints.h> + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION("Q40 PS/2 keyboard controller driver"); +MODULE_LICENSE("GPL"); + +DEFINE_SPINLOCK(q40kbd_lock); +static struct serio *q40kbd_port; +static struct platform_device *q40kbd_device; + +static irqreturn_t q40kbd_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + unsigned long flags; + + spin_lock_irqsave(&q40kbd_lock, flags); + + if (Q40_IRQ_KEYB_MASK & master_inb(INTERRUPT_REG)) + serio_interrupt(q40kbd_port, master_inb(KEYCODE_REG), 0, regs); + + master_outb(-1, KEYBOARD_UNLOCK_REG); + + spin_unlock_irqrestore(&q40kbd_lock, flags); + + return IRQ_HANDLED; +} + +/* + * q40kbd_flush() flushes all data that may be in the keyboard buffers + */ + +static void q40kbd_flush(void) +{ + int maxread = 100; + unsigned long flags; + + spin_lock_irqsave(&q40kbd_lock, flags); + + while (maxread-- && (Q40_IRQ_KEYB_MASK & master_inb(INTERRUPT_REG))) + master_inb(KEYCODE_REG); + + spin_unlock_irqrestore(&q40kbd_lock, flags); +} + +/* + * q40kbd_open() is called when a port is open by the higher layer. + * It allocates the interrupt and enables in in the chip. + */ + +static int q40kbd_open(struct serio *port) +{ + q40kbd_flush(); + + if (request_irq(Q40_IRQ_KEYBOARD, q40kbd_interrupt, 0, "q40kbd", NULL)) { + printk(KERN_ERR "q40kbd.c: Can't get irq %d.\n", Q40_IRQ_KEYBOARD); + return -1; + } + + /* off we go */ + master_outb(-1, KEYBOARD_UNLOCK_REG); + master_outb(1, KEY_IRQ_ENABLE_REG); + + return 0; +} + +static void q40kbd_close(struct serio *port) +{ + master_outb(0, KEY_IRQ_ENABLE_REG); + master_outb(-1, KEYBOARD_UNLOCK_REG); + free_irq(Q40_IRQ_KEYBOARD, NULL); + + q40kbd_flush(); +} + +static struct serio * __init q40kbd_allocate_port(void) +{ + struct serio *serio; + + serio = kmalloc(sizeof(struct serio), GFP_KERNEL); + if (serio) { + memset(serio, 0, sizeof(struct serio)); + serio->id.type = SERIO_8042; + serio->open = q40kbd_open; + serio->close = q40kbd_close; + serio->dev.parent = &q40kbd_device->dev; + strlcpy(serio->name, "Q40 Kbd Port", sizeof(serio->name)); + strlcpy(serio->phys, "Q40", sizeof(serio->phys)); + } + + return serio; +} + +static int __init q40kbd_init(void) +{ + if (!MACH_IS_Q40) + return -EIO; + + q40kbd_device = platform_device_register_simple("q40kbd", -1, NULL, 0); + if (IS_ERR(q40kbd_device)) + return PTR_ERR(q40kbd_device); + + if (!(q40kbd_port = q40kbd_allocate_port())) { + platform_device_unregister(q40kbd_device); + return -ENOMEM; + } + + serio_register_port(q40kbd_port); + printk(KERN_INFO "serio: Q40 kbd registered\n"); + + return 0; +} + +static void __exit q40kbd_exit(void) +{ + serio_unregister_port(q40kbd_port); + platform_device_unregister(q40kbd_device); +} + +module_init(q40kbd_init); +module_exit(q40kbd_exit); diff --git a/drivers/input/serio/rpckbd.c b/drivers/input/serio/rpckbd.c new file mode 100644 index 00000000000..106f5eefd89 --- /dev/null +++ b/drivers/input/serio/rpckbd.c @@ -0,0 +1,156 @@ +/* + * $Id: rpckbd.c,v 1.7 2001/09/25 10:12:07 vojtech Exp $ + * + * Copyright (c) 2000-2001 Vojtech Pavlik + * Copyright (c) 2002 Russell King + */ + +/* + * Acorn RiscPC PS/2 keyboard controller driver for Linux/ARM + */ + +/* + * 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 program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Should you need to contact me, the author, you can do so either by + * e-mail - mail your message to <vojtech@ucw.cz>, or by paper mail: + * Vojtech Pavlik, Simunkova 1594, Prague 8, 182 00 Czech Republic + */ + +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/init.h> +#include <linux/serio.h> +#include <linux/err.h> + +#include <asm/irq.h> +#include <asm/hardware.h> +#include <asm/io.h> +#include <asm/hardware/iomd.h> +#include <asm/system.h> + +MODULE_AUTHOR("Vojtech Pavlik, Russell King"); +MODULE_DESCRIPTION("Acorn RiscPC PS/2 keyboard controller driver"); +MODULE_LICENSE("GPL"); + +static int rpckbd_write(struct serio *port, unsigned char val) +{ + while (!(iomd_readb(IOMD_KCTRL) & (1 << 7))) + cpu_relax(); + + iomd_writeb(val, IOMD_KARTTX); + + return 0; +} + +static irqreturn_t rpckbd_rx(int irq, void *dev_id, struct pt_regs *regs) +{ + struct serio *port = dev_id; + unsigned int byte; + int handled = IRQ_NONE; + + while (iomd_readb(IOMD_KCTRL) & (1 << 5)) { + byte = iomd_readb(IOMD_KARTRX); + + serio_interrupt(port, byte, 0, regs); + handled = IRQ_HANDLED; + } + return handled; +} + +static irqreturn_t rpckbd_tx(int irq, void *dev_id, struct pt_regs *regs) +{ + return IRQ_HANDLED; +} + +static int rpckbd_open(struct serio *port) +{ + /* Reset the keyboard state machine. */ + iomd_writeb(0, IOMD_KCTRL); + iomd_writeb(8, IOMD_KCTRL); + iomd_readb(IOMD_KARTRX); + + if (request_irq(IRQ_KEYBOARDRX, rpckbd_rx, 0, "rpckbd", port) != 0) { + printk(KERN_ERR "rpckbd.c: Could not allocate keyboard receive IRQ\n"); + return -EBUSY; + } + + if (request_irq(IRQ_KEYBOARDTX, rpckbd_tx, 0, "rpckbd", port) != 0) { + printk(KERN_ERR "rpckbd.c: Could not allocate keyboard transmit IRQ\n"); + free_irq(IRQ_KEYBOARDRX, NULL); + return -EBUSY; + } + + return 0; +} + +static void rpckbd_close(struct serio *port) +{ + free_irq(IRQ_KEYBOARDRX, port); + free_irq(IRQ_KEYBOARDTX, port); +} + +/* + * Allocate and initialize serio structure for subsequent registration + * with serio core. + */ +static int __devinit rpckbd_probe(struct device *dev) +{ + struct serio *serio; + + serio = kmalloc(sizeof(struct serio), GFP_KERNEL); + if (!serio) + return -ENOMEM; + + memset(serio, 0, sizeof(struct serio)); + serio->id.type = SERIO_8042; + serio->write = rpckbd_write; + serio->open = rpckbd_open; + serio->close = rpckbd_close; + serio->dev.parent = dev; + strlcpy(serio->name, "RiscPC PS/2 kbd port", sizeof(serio->name)); + strlcpy(serio->phys, "rpckbd/serio0", sizeof(serio->phys)); + + dev_set_drvdata(dev, serio); + serio_register_port(serio); + return 0; +} + +static int __devexit rpckbd_remove(struct device *dev) +{ + struct serio *serio = dev_get_drvdata(dev); + serio_unregister_port(serio); + return 0; +} + +static struct device_driver rpckbd_driver = { + .name = "kart", + .bus = &platform_bus_type, + .probe = rpckbd_probe, + .remove = __devexit_p(rpckbd_remove), +}; + +static int __init rpckbd_init(void) +{ + return driver_register(&rpckbd_driver); +} + +static void __exit rpckbd_exit(void) +{ + driver_unregister(&rpckbd_driver); +} + +module_init(rpckbd_init); +module_exit(rpckbd_exit); diff --git a/drivers/input/serio/sa1111ps2.c b/drivers/input/serio/sa1111ps2.c new file mode 100644 index 00000000000..3f0df3330fb --- /dev/null +++ b/drivers/input/serio/sa1111ps2.c @@ -0,0 +1,359 @@ +/* + * linux/drivers/input/serio/sa1111ps2.c + * + * Copyright (C) 2002 Russell King + * + * 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. + */ +#include <linux/module.h> +#include <linux/init.h> +#include <linux/input.h> +#include <linux/serio.h> +#include <linux/errno.h> +#include <linux/interrupt.h> +#include <linux/ioport.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/slab.h> +#include <linux/spinlock.h> + +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/system.h> + +#include <asm/hardware/sa1111.h> + +struct ps2if { + struct serio *io; + struct sa1111_dev *dev; + void __iomem *base; + unsigned int open; + spinlock_t lock; + unsigned int head; + unsigned int tail; + unsigned char buf[4]; +}; + +/* + * Read all bytes waiting in the PS2 port. There should be + * at the most one, but we loop for safety. If there was a + * framing error, we have to manually clear the status. + */ +static irqreturn_t ps2_rxint(int irq, void *dev_id, struct pt_regs *regs) +{ + struct ps2if *ps2if = dev_id; + unsigned int scancode, flag, status; + + status = sa1111_readl(ps2if->base + SA1111_PS2STAT); + while (status & PS2STAT_RXF) { + if (status & PS2STAT_STP) + sa1111_writel(PS2STAT_STP, ps2if->base + SA1111_PS2STAT); + + flag = (status & PS2STAT_STP ? SERIO_FRAME : 0) | + (status & PS2STAT_RXP ? 0 : SERIO_PARITY); + + scancode = sa1111_readl(ps2if->base + SA1111_PS2DATA) & 0xff; + + if (hweight8(scancode) & 1) + flag ^= SERIO_PARITY; + + serio_interrupt(ps2if->io, scancode, flag, regs); + + status = sa1111_readl(ps2if->base + SA1111_PS2STAT); + } + + return IRQ_HANDLED; +} + +/* + * Completion of ps2 write + */ +static irqreturn_t ps2_txint(int irq, void *dev_id, struct pt_regs *regs) +{ + struct ps2if *ps2if = dev_id; + unsigned int status; + + spin_lock(&ps2if->lock); + status = sa1111_readl(ps2if->base + SA1111_PS2STAT); + if (ps2if->head == ps2if->tail) { + disable_irq(irq); + /* done */ + } else if (status & PS2STAT_TXE) { + sa1111_writel(ps2if->buf[ps2if->tail], ps2if->base + SA1111_PS2DATA); + ps2if->tail = (ps2if->tail + 1) & (sizeof(ps2if->buf) - 1); + } + spin_unlock(&ps2if->lock); + + return IRQ_HANDLED; +} + +/* + * Write a byte to the PS2 port. We have to wait for the + * port to indicate that the transmitter is empty. + */ +static int ps2_write(struct serio *io, unsigned char val) +{ + struct ps2if *ps2if = io->port_data; + unsigned long flags; + unsigned int head; + + spin_lock_irqsave(&ps2if->lock, flags); + + /* + * If the TX register is empty, we can go straight out. + */ + if (sa1111_readl(ps2if->base + SA1111_PS2STAT) & PS2STAT_TXE) { + sa1111_writel(val, ps2if->base + SA1111_PS2DATA); + } else { + if (ps2if->head == ps2if->tail) + enable_irq(ps2if->dev->irq[1]); + head = (ps2if->head + 1) & (sizeof(ps2if->buf) - 1); + if (head != ps2if->tail) { + ps2if->buf[ps2if->head] = val; + ps2if->head = head; + } + } + + spin_unlock_irqrestore(&ps2if->lock, flags); + return 0; +} + +static int ps2_open(struct serio *io) +{ + struct ps2if *ps2if = io->port_data; + int ret; + + sa1111_enable_device(ps2if->dev); + + ret = request_irq(ps2if->dev->irq[0], ps2_rxint, 0, + SA1111_DRIVER_NAME(ps2if->dev), ps2if); + if (ret) { + printk(KERN_ERR "sa1111ps2: could not allocate IRQ%d: %d\n", + ps2if->dev->irq[0], ret); + return ret; + } + + ret = request_irq(ps2if->dev->irq[1], ps2_txint, 0, + SA1111_DRIVER_NAME(ps2if->dev), ps2if); + if (ret) { + printk(KERN_ERR "sa1111ps2: could not allocate IRQ%d: %d\n", + ps2if->dev->irq[1], ret); + free_irq(ps2if->dev->irq[0], ps2if); + return ret; + } + + ps2if->open = 1; + + enable_irq_wake(ps2if->dev->irq[0]); + + sa1111_writel(PS2CR_ENA, ps2if->base + SA1111_PS2CR); + return 0; +} + +static void ps2_close(struct serio *io) +{ + struct ps2if *ps2if = io->port_data; + + sa1111_writel(0, ps2if->base + SA1111_PS2CR); + + disable_irq_wake(ps2if->dev->irq[0]); + + ps2if->open = 0; + + free_irq(ps2if->dev->irq[1], ps2if); + free_irq(ps2if->dev->irq[0], ps2if); + + sa1111_disable_device(ps2if->dev); +} + +/* + * Clear the input buffer. + */ +static void __init ps2_clear_input(struct ps2if *ps2if) +{ + int maxread = 100; + + while (maxread--) { + if ((sa1111_readl(ps2if->base + SA1111_PS2DATA) & 0xff) == 0xff) + break; + } +} + +static inline unsigned int +ps2_test_one(struct ps2if *ps2if, unsigned int mask) +{ + unsigned int val; + + sa1111_writel(PS2CR_ENA | mask, ps2if->base + SA1111_PS2CR); + + udelay(2); + + val = sa1111_readl(ps2if->base + SA1111_PS2STAT); + return val & (PS2STAT_KBC | PS2STAT_KBD); +} + +/* + * Test the keyboard interface. We basically check to make sure that + * we can drive each line to the keyboard independently of each other. + */ +static int __init ps2_test(struct ps2if *ps2if) +{ + unsigned int stat; + int ret = 0; + + stat = ps2_test_one(ps2if, PS2CR_FKC); + if (stat != PS2STAT_KBD) { + printk("PS/2 interface test failed[1]: %02x\n", stat); + ret = -ENODEV; + } + + stat = ps2_test_one(ps2if, 0); + if (stat != (PS2STAT_KBC | PS2STAT_KBD)) { + printk("PS/2 interface test failed[2]: %02x\n", stat); + ret = -ENODEV; + } + + stat = ps2_test_one(ps2if, PS2CR_FKD); + if (stat != PS2STAT_KBC) { + printk("PS/2 interface test failed[3]: %02x\n", stat); + ret = -ENODEV; + } + + sa1111_writel(0, ps2if->base + SA1111_PS2CR); + + return ret; +} + +/* + * Add one device to this driver. + */ +static int ps2_probe(struct sa1111_dev *dev) +{ + struct ps2if *ps2if; + struct serio *serio; + int ret; + + ps2if = kmalloc(sizeof(struct ps2if), GFP_KERNEL); + serio = kmalloc(sizeof(struct serio), GFP_KERNEL); + if (!ps2if || !serio) { + ret = -ENOMEM; + goto free; + } + + memset(ps2if, 0, sizeof(struct ps2if)); + memset(serio, 0, sizeof(struct serio)); + + serio->id.type = SERIO_8042; + serio->write = ps2_write; + serio->open = ps2_open; + serio->close = ps2_close; + strlcpy(serio->name, dev->dev.bus_id, sizeof(serio->name)); + strlcpy(serio->phys, dev->dev.bus_id, sizeof(serio->phys)); + serio->port_data = ps2if; + serio->dev.parent = &dev->dev; + ps2if->io = serio; + ps2if->dev = dev; + sa1111_set_drvdata(dev, ps2if); + + spin_lock_init(&ps2if->lock); + + /* + * Request the physical region for this PS2 port. + */ + if (!request_mem_region(dev->res.start, + dev->res.end - dev->res.start + 1, + SA1111_DRIVER_NAME(dev))) { + ret = -EBUSY; + goto free; + } + + /* + * Our parent device has already mapped the region. + */ + ps2if->base = dev->mapbase; + + sa1111_enable_device(ps2if->dev); + + /* Incoming clock is 8MHz */ + sa1111_writel(0, ps2if->base + SA1111_PS2CLKDIV); + sa1111_writel(127, ps2if->base + SA1111_PS2PRECNT); + + /* + * Flush any pending input. + */ + ps2_clear_input(ps2if); + + /* + * Test the keyboard interface. + */ + ret = ps2_test(ps2if); + if (ret) + goto out; + + /* + * Flush any pending input. + */ + ps2_clear_input(ps2if); + + sa1111_disable_device(ps2if->dev); + serio_register_port(ps2if->io); + return 0; + + out: + sa1111_disable_device(ps2if->dev); + release_mem_region(dev->res.start, + dev->res.end - dev->res.start + 1); + free: + sa1111_set_drvdata(dev, NULL); + kfree(ps2if); + kfree(serio); + return ret; +} + +/* + * Remove one device from this driver. + */ +static int ps2_remove(struct sa1111_dev *dev) +{ + struct ps2if *ps2if = sa1111_get_drvdata(dev); + + serio_unregister_port(ps2if->io); + release_mem_region(dev->res.start, + dev->res.end - dev->res.start + 1); + sa1111_set_drvdata(dev, NULL); + + kfree(ps2if); + + return 0; +} + +/* + * Our device driver structure + */ +static struct sa1111_driver ps2_driver = { + .drv = { + .name = "sa1111-ps2", + }, + .devid = SA1111_DEVID_PS2, + .probe = ps2_probe, + .remove = ps2_remove, +}; + +static int __init ps2_init(void) +{ + return sa1111_driver_register(&ps2_driver); +} + +static void __exit ps2_exit(void) +{ + sa1111_driver_unregister(&ps2_driver); +} + +module_init(ps2_init); +module_exit(ps2_exit); + +MODULE_AUTHOR("Russell King <rmk@arm.linux.org.uk>"); +MODULE_DESCRIPTION("SA1111 PS2 controller driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/serio/serio.c b/drivers/input/serio/serio.c new file mode 100644 index 00000000000..3313e2daeab --- /dev/null +++ b/drivers/input/serio/serio.c @@ -0,0 +1,859 @@ +/* + * The Serio abstraction module + * + * Copyright (c) 1999-2004 Vojtech Pavlik + * Copyright (c) 2004 Dmitry Torokhov + * Copyright (c) 2003 Daniele Bellucci + */ + +/* + * 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 program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Should you need to contact me, the author, you can do so either by + * e-mail - mail your message to <vojtech@ucw.cz>, or by paper mail: + * Vojtech Pavlik, Simunkova 1594, Prague 8, 182 00 Czech Republic + */ + +#include <linux/stddef.h> +#include <linux/module.h> +#include <linux/serio.h> +#include <linux/errno.h> +#include <linux/wait.h> +#include <linux/completion.h> +#include <linux/sched.h> +#include <linux/smp_lock.h> +#include <linux/slab.h> + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION("Serio abstraction core"); +MODULE_LICENSE("GPL"); + +EXPORT_SYMBOL(serio_interrupt); +EXPORT_SYMBOL(__serio_register_port); +EXPORT_SYMBOL(serio_unregister_port); +EXPORT_SYMBOL(__serio_unregister_port_delayed); +EXPORT_SYMBOL(__serio_register_driver); +EXPORT_SYMBOL(serio_unregister_driver); +EXPORT_SYMBOL(serio_open); +EXPORT_SYMBOL(serio_close); +EXPORT_SYMBOL(serio_rescan); +EXPORT_SYMBOL(serio_reconnect); + +/* + * serio_sem protects entire serio subsystem and is taken every time + * serio port or driver registrered or unregistered. + */ +static DECLARE_MUTEX(serio_sem); + +static LIST_HEAD(serio_list); + +static struct bus_type serio_bus = { + .name = "serio", +}; + +static void serio_add_port(struct serio *serio); +static void serio_destroy_port(struct serio *serio); +static void serio_reconnect_port(struct serio *serio); +static void serio_disconnect_port(struct serio *serio); + +static int serio_match_port(const struct serio_device_id *ids, struct serio *serio) +{ + while (ids->type || ids->proto) { + if ((ids->type == SERIO_ANY || ids->type == serio->id.type) && + (ids->proto == SERIO_ANY || ids->proto == serio->id.proto) && + (ids->extra == SERIO_ANY || ids->extra == serio->id.extra) && + (ids->id == SERIO_ANY || ids->id == serio->id.id)) + return 1; + ids++; + } + return 0; +} + +/* + * Basic serio -> driver core mappings + */ + +static void serio_bind_driver(struct serio *serio, struct serio_driver *drv) +{ + down_write(&serio_bus.subsys.rwsem); + + if (serio_match_port(drv->id_table, serio)) { + serio->dev.driver = &drv->driver; + if (drv->connect(serio, drv)) { + serio->dev.driver = NULL; + goto out; + } + device_bind_driver(&serio->dev); + } +out: + up_write(&serio_bus.subsys.rwsem); +} + +static void serio_release_driver(struct serio *serio) +{ + down_write(&serio_bus.subsys.rwsem); + device_release_driver(&serio->dev); + up_write(&serio_bus.subsys.rwsem); +} + +static void serio_find_driver(struct serio *serio) +{ + down_write(&serio_bus.subsys.rwsem); + device_attach(&serio->dev); + up_write(&serio_bus.subsys.rwsem); +} + + +/* + * Serio event processing. + */ + +enum serio_event_type { + SERIO_RESCAN, + SERIO_RECONNECT, + SERIO_REGISTER_PORT, + SERIO_UNREGISTER_PORT, + SERIO_REGISTER_DRIVER, +}; + +struct serio_event { + enum serio_event_type type; + void *object; + struct module *owner; + struct list_head node; +}; + +static DEFINE_SPINLOCK(serio_event_lock); /* protects serio_event_list */ +static LIST_HEAD(serio_event_list); +static DECLARE_WAIT_QUEUE_HEAD(serio_wait); +static DECLARE_COMPLETION(serio_exited); +static int serio_pid; + +static void serio_queue_event(void *object, struct module *owner, + enum serio_event_type event_type) +{ + unsigned long flags; + struct serio_event *event; + + spin_lock_irqsave(&serio_event_lock, flags); + + /* + * Scan event list for the other events for the same serio port, + * starting with the most recent one. If event is the same we + * do not need add new one. If event is of different type we + * need to add this event and should not look further because + * we need to preseve sequence of distinct events. + */ + list_for_each_entry_reverse(event, &serio_event_list, node) { + if (event->object == object) { + if (event->type == event_type) + goto out; + break; + } + } + + if ((event = kmalloc(sizeof(struct serio_event), GFP_ATOMIC))) { + if (!try_module_get(owner)) { + printk(KERN_WARNING "serio: Can't get module reference, dropping event %d\n", event_type); + goto out; + } + + event->type = event_type; + event->object = object; + event->owner = owner; + + list_add_tail(&event->node, &serio_event_list); + wake_up(&serio_wait); + } else { + printk(KERN_ERR "serio: Not enough memory to queue event %d\n", event_type); + } +out: + spin_unlock_irqrestore(&serio_event_lock, flags); +} + +static void serio_free_event(struct serio_event *event) +{ + module_put(event->owner); + kfree(event); +} + +static void serio_remove_duplicate_events(struct serio_event *event) +{ + struct list_head *node, *next; + struct serio_event *e; + unsigned long flags; + + spin_lock_irqsave(&serio_event_lock, flags); + + list_for_each_safe(node, next, &serio_event_list) { + e = list_entry(node, struct serio_event, node); + if (event->object == e->object) { + /* + * If this event is of different type we should not + * look further - we only suppress duplicate events + * that were sent back-to-back. + */ + if (event->type != e->type) + break; + + list_del_init(node); + serio_free_event(e); + } + } + + spin_unlock_irqrestore(&serio_event_lock, flags); +} + + +static struct serio_event *serio_get_event(void) +{ + struct serio_event *event; + struct list_head *node; + unsigned long flags; + + spin_lock_irqsave(&serio_event_lock, flags); + + if (list_empty(&serio_event_list)) { + spin_unlock_irqrestore(&serio_event_lock, flags); + return NULL; + } + + node = serio_event_list.next; + event = list_entry(node, struct serio_event, node); + list_del_init(node); + + spin_unlock_irqrestore(&serio_event_lock, flags); + + return event; +} + +static void serio_handle_events(void) +{ + struct serio_event *event; + struct serio_driver *serio_drv; + + down(&serio_sem); + + while ((event = serio_get_event())) { + + switch (event->type) { + case SERIO_REGISTER_PORT: + serio_add_port(event->object); + break; + + case SERIO_UNREGISTER_PORT: + serio_disconnect_port(event->object); + serio_destroy_port(event->object); + break; + + case SERIO_RECONNECT: + serio_reconnect_port(event->object); + break; + + case SERIO_RESCAN: + serio_disconnect_port(event->object); + serio_find_driver(event->object); + break; + + case SERIO_REGISTER_DRIVER: + serio_drv = event->object; + driver_register(&serio_drv->driver); + break; + + default: + break; + } + + serio_remove_duplicate_events(event); + serio_free_event(event); + } + + up(&serio_sem); +} + +/* + * Remove all events that have been submitted for a given serio port. + */ +static void serio_remove_pending_events(struct serio *serio) +{ + struct list_head *node, *next; + struct serio_event *event; + unsigned long flags; + + spin_lock_irqsave(&serio_event_lock, flags); + + list_for_each_safe(node, next, &serio_event_list) { + event = list_entry(node, struct serio_event, node); + if (event->object == serio) { + list_del_init(node); + serio_free_event(event); + } + } + + spin_unlock_irqrestore(&serio_event_lock, flags); +} + +/* + * Destroy child serio port (if any) that has not been fully registered yet. + * + * Note that we rely on the fact that port can have only one child and therefore + * only one child registration request can be pending. Additionally, children + * are registered by driver's connect() handler so there can't be a grandchild + * pending registration together with a child. + */ +static struct serio *serio_get_pending_child(struct serio *parent) +{ + struct serio_event *event; + struct serio *serio, *child = NULL; + unsigned long flags; + + spin_lock_irqsave(&serio_event_lock, flags); + + list_for_each_entry(event, &serio_event_list, node) { + if (event->type == SERIO_REGISTER_PORT) { + serio = event->object; + if (serio->parent == parent) { + child = serio; + break; + } + } + } + + spin_unlock_irqrestore(&serio_event_lock, flags); + return child; +} + +static int serio_thread(void *nothing) +{ + lock_kernel(); + daemonize("kseriod"); + allow_signal(SIGTERM); + + do { + serio_handle_events(); + wait_event_interruptible(serio_wait, !list_empty(&serio_event_list)); + try_to_freeze(PF_FREEZE); + } while (!signal_pending(current)); + + printk(KERN_DEBUG "serio: kseriod exiting\n"); + + unlock_kernel(); + complete_and_exit(&serio_exited, 0); +} + + +/* + * Serio port operations + */ + +static ssize_t serio_show_description(struct device *dev, char *buf) +{ + struct serio *serio = to_serio_port(dev); + return sprintf(buf, "%s\n", serio->name); +} + +static ssize_t serio_show_id_type(struct device *dev, char *buf) +{ + struct serio *serio = to_serio_port(dev); + return sprintf(buf, "%02x\n", serio->id.type); +} + +static ssize_t serio_show_id_proto(struct device *dev, char *buf) +{ + struct serio *serio = to_serio_port(dev); + return sprintf(buf, "%02x\n", serio->id.proto); +} + +static ssize_t serio_show_id_id(struct device *dev, char *buf) +{ + struct serio *serio = to_serio_port(dev); + return sprintf(buf, "%02x\n", serio->id.id); +} + +static ssize_t serio_show_id_extra(struct device *dev, char *buf) +{ + struct serio *serio = to_serio_port(dev); + return sprintf(buf, "%02x\n", serio->id.extra); +} + +static ssize_t serio_rebind_driver(struct device *dev, const char *buf, size_t count) +{ + struct serio *serio = to_serio_port(dev); + struct device_driver *drv; + int retval; + + retval = down_interruptible(&serio_sem); + if (retval) + return retval; + + retval = count; + if (!strncmp(buf, "none", count)) { + serio_disconnect_port(serio); + } else if (!strncmp(buf, "reconnect", count)) { + serio_reconnect_port(serio); + } else if (!strncmp(buf, "rescan", count)) { + serio_disconnect_port(serio); + serio_find_driver(serio); + } else if ((drv = driver_find(buf, &serio_bus)) != NULL) { + serio_disconnect_port(serio); + serio_bind_driver(serio, to_serio_driver(drv)); + put_driver(drv); + } else { + retval = -EINVAL; + } + + up(&serio_sem); + + return retval; +} + +static ssize_t serio_show_bind_mode(struct device *dev, char *buf) +{ + struct serio *serio = to_serio_port(dev); + return sprintf(buf, "%s\n", serio->manual_bind ? "manual" : "auto"); +} + +static ssize_t serio_set_bind_mode(struct device *dev, const char *buf, size_t count) +{ + struct serio *serio = to_serio_port(dev); + int retval; + + retval = count; + if (!strncmp(buf, "manual", count)) { + serio->manual_bind = 1; + } else if (!strncmp(buf, "auto", count)) { + serio->manual_bind = 0; + } else { + retval = -EINVAL; + } + + return retval; +} + +static struct device_attribute serio_device_attrs[] = { + __ATTR(description, S_IRUGO, serio_show_description, NULL), + __ATTR(id_type, S_IRUGO, serio_show_id_type, NULL), + __ATTR(id_proto, S_IRUGO, serio_show_id_proto, NULL), + __ATTR(id_id, S_IRUGO, serio_show_id_id, NULL), + __ATTR(id_extra, S_IRUGO, serio_show_id_extra, NULL), + __ATTR(drvctl, S_IWUSR, NULL, serio_rebind_driver), + __ATTR(bind_mode, S_IWUSR | S_IRUGO, serio_show_bind_mode, serio_set_bind_mode), + __ATTR_NULL +}; + + +static void serio_release_port(struct device *dev) +{ + struct serio *serio = to_serio_port(dev); + + kfree(serio); + module_put(THIS_MODULE); +} + +/* + * Prepare serio port for registration. + */ +static void serio_init_port(struct serio *serio) +{ + static atomic_t serio_no = ATOMIC_INIT(0); + + __module_get(THIS_MODULE); + + spin_lock_init(&serio->lock); + init_MUTEX(&serio->drv_sem); + device_initialize(&serio->dev); + snprintf(serio->dev.bus_id, sizeof(serio->dev.bus_id), + "serio%ld", (long)atomic_inc_return(&serio_no) - 1); + serio->dev.bus = &serio_bus; + serio->dev.release = serio_release_port; + if (serio->parent) + serio->dev.parent = &serio->parent->dev; +} + +/* + * Complete serio port registration. + * Driver core will attempt to find appropriate driver for the port. + */ +static void serio_add_port(struct serio *serio) +{ + if (serio->parent) { + serio_pause_rx(serio->parent); + serio->parent->child = serio; + serio_continue_rx(serio->parent); + } + + list_add_tail(&serio->node, &serio_list); + if (serio->start) + serio->start(serio); + device_add(&serio->dev); + serio->registered = 1; +} + +/* + * serio_destroy_port() completes deregistration process and removes + * port from the system + */ +static void serio_destroy_port(struct serio *serio) +{ + struct serio *child; + + child = serio_get_pending_child(serio); + if (child) { + serio_remove_pending_events(child); + put_device(&child->dev); + } + + if (serio->stop) + serio->stop(serio); + + if (serio->parent) { + serio_pause_rx(serio->parent); + serio->parent->child = NULL; + serio_continue_rx(serio->parent); + serio->parent = NULL; + } + + if (serio->registered) { + device_del(&serio->dev); + list_del_init(&serio->node); + serio->registered = 0; + } + + serio_remove_pending_events(serio); + put_device(&serio->dev); +} + +/* + * Reconnect serio port and all its children (re-initialize attached devices) + */ +static void serio_reconnect_port(struct serio *serio) +{ + do { + if (!serio->drv || !serio->drv->reconnect || serio->drv->reconnect(serio)) { + serio_disconnect_port(serio); + serio_find_driver(serio); + /* Ok, old children are now gone, we are done */ + break; + } + serio = serio->child; + } while (serio); +} + +/* + * serio_disconnect_port() unbinds a port from its driver. As a side effect + * all child ports are unbound and destroyed. + */ +static void serio_disconnect_port(struct serio *serio) +{ + struct serio *s, *parent; + + if (serio->child) { + /* + * Children ports should be disconnected and destroyed + * first, staring with the leaf one, since we don't want + * to do recursion + */ + for (s = serio; s->child; s = s->child) + /* empty */; + + do { + parent = s->parent; + + serio_release_driver(s); + serio_destroy_port(s); + } while ((s = parent) != serio); + } + + /* + * Ok, no children left, now disconnect this port + */ + serio_release_driver(serio); +} + +void serio_rescan(struct serio *serio) +{ + serio_queue_event(serio, NULL, SERIO_RESCAN); +} + +void serio_reconnect(struct serio *serio) +{ + serio_queue_event(serio, NULL, SERIO_RECONNECT); +} + +/* + * Submits register request to kseriod for subsequent execution. + * Note that port registration is always asynchronous. + */ +void __serio_register_port(struct serio *serio, struct module *owner) +{ + serio_init_port(serio); + serio_queue_event(serio, owner, SERIO_REGISTER_PORT); +} + +/* + * Synchronously unregisters serio port. + */ +void serio_unregister_port(struct serio *serio) +{ + down(&serio_sem); + serio_disconnect_port(serio); + serio_destroy_port(serio); + up(&serio_sem); +} + +/* + * Submits register request to kseriod for subsequent execution. + * Can be used when it is not obvious whether the serio_sem is + * taken or not and when delayed execution is feasible. + */ +void __serio_unregister_port_delayed(struct serio *serio, struct module *owner) +{ + serio_queue_event(serio, owner, SERIO_UNREGISTER_PORT); +} + + +/* + * Serio driver operations + */ + +static ssize_t serio_driver_show_description(struct device_driver *drv, char *buf) +{ + struct serio_driver *driver = to_serio_driver(drv); + return sprintf(buf, "%s\n", driver->description ? driver->description : "(none)"); +} + +static ssize_t serio_driver_show_bind_mode(struct device_driver *drv, char *buf) +{ + struct serio_driver *serio_drv = to_serio_driver(drv); + return sprintf(buf, "%s\n", serio_drv->manual_bind ? "manual" : "auto"); +} + +static ssize_t serio_driver_set_bind_mode(struct device_driver *drv, const char *buf, size_t count) +{ + struct serio_driver *serio_drv = to_serio_driver(drv); + int retval; + + retval = count; + if (!strncmp(buf, "manual", count)) { + serio_drv->manual_bind = 1; + } else if (!strncmp(buf, "auto", count)) { + serio_drv->manual_bind = 0; + } else { + retval = -EINVAL; + } + + return retval; +} + + +static struct driver_attribute serio_driver_attrs[] = { + __ATTR(description, S_IRUGO, serio_driver_show_description, NULL), + __ATTR(bind_mode, S_IWUSR | S_IRUGO, + serio_driver_show_bind_mode, serio_driver_set_bind_mode), + __ATTR_NULL +}; + +static int serio_driver_probe(struct device *dev) +{ + struct serio *serio = to_serio_port(dev); + struct serio_driver *drv = to_serio_driver(dev->driver); + + return drv->connect(serio, drv); +} + +static int serio_driver_remove(struct device *dev) +{ + struct serio *serio = to_serio_port(dev); + struct serio_driver *drv = to_serio_driver(dev->driver); + + drv->disconnect(serio); + return 0; +} + +void __serio_register_driver(struct serio_driver *drv, struct module *owner) +{ + drv->driver.bus = &serio_bus; + drv->driver.probe = serio_driver_probe; + drv->driver.remove = serio_driver_remove; + + serio_queue_event(drv, owner, SERIO_REGISTER_DRIVER); +} + +void serio_unregister_driver(struct serio_driver *drv) +{ + struct serio *serio; + + down(&serio_sem); + drv->manual_bind = 1; /* so serio_find_driver ignores it */ + +start_over: + list_for_each_entry(serio, &serio_list, node) { + if (serio->drv == drv) { + serio_disconnect_port(serio); + serio_find_driver(serio); + /* we could've deleted some ports, restart */ + goto start_over; + } + } + + driver_unregister(&drv->driver); + up(&serio_sem); +} + +static void serio_set_drv(struct serio *serio, struct serio_driver *drv) +{ + down(&serio->drv_sem); + serio_pause_rx(serio); + serio->drv = drv; + serio_continue_rx(serio); + up(&serio->drv_sem); +} + +static int serio_bus_match(struct device *dev, struct device_driver *drv) +{ + struct serio *serio = to_serio_port(dev); + struct serio_driver *serio_drv = to_serio_driver(drv); + + if (serio->manual_bind || serio_drv->manual_bind) + return 0; + + return serio_match_port(serio_drv->id_table, serio); +} + +#ifdef CONFIG_HOTPLUG + +#define PUT_ENVP(fmt, val) \ +do { \ + envp[i++] = buffer; \ + length += snprintf(buffer, buffer_size - length, fmt, val); \ + if (buffer_size - length <= 0 || i >= num_envp) \ + return -ENOMEM; \ + length++; \ + buffer += length; \ +} while (0) +static int serio_hotplug(struct device *dev, char **envp, int num_envp, char *buffer, int buffer_size) +{ + struct serio *serio; + int i = 0; + int length = 0; + + if (!dev) + return -ENODEV; + + serio = to_serio_port(dev); + + PUT_ENVP("SERIO_TYPE=%02x", serio->id.type); + PUT_ENVP("SERIO_PROTO=%02x", serio->id.proto); + PUT_ENVP("SERIO_ID=%02x", serio->id.id); + PUT_ENVP("SERIO_EXTRA=%02x", serio->id.extra); + + envp[i] = NULL; + + return 0; +} +#undef PUT_ENVP + +#else + +static int serio_hotplug(struct device *dev, char **envp, int num_envp, char *buffer, int buffer_size) +{ + return -ENODEV; +} + +#endif /* CONFIG_HOTPLUG */ + +static int serio_resume(struct device *dev) +{ + struct serio *serio = to_serio_port(dev); + + if (!serio->drv || !serio->drv->reconnect || serio->drv->reconnect(serio)) { + serio_disconnect_port(serio); + /* + * Driver re-probing can take a while, so better let kseriod + * deal with it. + */ + serio_rescan(serio); + } + + return 0; +} + +/* called from serio_driver->connect/disconnect methods under serio_sem */ +int serio_open(struct serio *serio, struct serio_driver *drv) +{ + serio_set_drv(serio, drv); + + if (serio->open && serio->open(serio)) { + serio_set_drv(serio, NULL); + return -1; + } + return 0; +} + +/* called from serio_driver->connect/disconnect methods under serio_sem */ +void serio_close(struct serio *serio) +{ + if (serio->close) + serio->close(serio); + + serio_set_drv(serio, NULL); +} + +irqreturn_t serio_interrupt(struct serio *serio, + unsigned char data, unsigned int dfl, struct pt_regs *regs) +{ + unsigned long flags; + irqreturn_t ret = IRQ_NONE; + + spin_lock_irqsave(&serio->lock, flags); + + if (likely(serio->drv)) { + ret = serio->drv->interrupt(serio, data, dfl, regs); + } else if (!dfl && serio->registered) { + serio_rescan(serio); + ret = IRQ_HANDLED; + } + + spin_unlock_irqrestore(&serio->lock, flags); + + return ret; +} + +static int __init serio_init(void) +{ + if (!(serio_pid = kernel_thread(serio_thread, NULL, CLONE_KERNEL))) { + printk(KERN_ERR "serio: Failed to start kseriod\n"); + return -1; + } + + serio_bus.dev_attrs = serio_device_attrs; + serio_bus.drv_attrs = serio_driver_attrs; + serio_bus.match = serio_bus_match; + serio_bus.hotplug = serio_hotplug; + serio_bus.resume = serio_resume; + bus_register(&serio_bus); + + return 0; +} + +static void __exit serio_exit(void) +{ + bus_unregister(&serio_bus); + kill_proc(serio_pid, SIGTERM, 1); + wait_for_completion(&serio_exited); +} + +module_init(serio_init); +module_exit(serio_exit); diff --git a/drivers/input/serio/serio_raw.c b/drivers/input/serio/serio_raw.c new file mode 100644 index 00000000000..d914e7e93db --- /dev/null +++ b/drivers/input/serio/serio_raw.c @@ -0,0 +1,403 @@ +/* + * Raw serio device providing access to a raw byte stream from underlying + * serio port. Closely emulates behavior of pre-2.6 /dev/psaux device + * + * Copyright (c) 2004 Dmitry Torokhov + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +#include <linux/slab.h> +#include <linux/poll.h> +#include <linux/module.h> +#include <linux/serio.h> +#include <linux/init.h> +#include <linux/major.h> +#include <linux/device.h> +#include <linux/devfs_fs_kernel.h> +#include <linux/miscdevice.h> +#include <linux/wait.h> + +#define DRIVER_DESC "Raw serio driver" + +MODULE_AUTHOR("Dmitry Torokhov <dtor@mail.ru>"); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +#define SERIO_RAW_QUEUE_LEN 64 +struct serio_raw { + unsigned char queue[SERIO_RAW_QUEUE_LEN]; + unsigned int tail, head; + + char name[16]; + unsigned int refcnt; + struct serio *serio; + struct miscdevice dev; + wait_queue_head_t wait; + struct list_head list; + struct list_head node; +}; + +struct serio_raw_list { + struct fasync_struct *fasync; + struct serio_raw *serio_raw; + struct list_head node; +}; + +static DECLARE_MUTEX(serio_raw_sem); +static LIST_HEAD(serio_raw_list); +static unsigned int serio_raw_no; + +/********************************************************************* + * Interface with userspace (file operations) * + *********************************************************************/ + +static int serio_raw_fasync(int fd, struct file *file, int on) +{ + struct serio_raw_list *list = file->private_data; + int retval; + + retval = fasync_helper(fd, file, on, &list->fasync); + return retval < 0 ? retval : 0; +} + +static struct serio_raw *serio_raw_locate(int minor) +{ + struct serio_raw *serio_raw; + + list_for_each_entry(serio_raw, &serio_raw_list, node) { + if (serio_raw->dev.minor == minor) + return serio_raw; + } + + return NULL; +} + +static int serio_raw_open(struct inode *inode, struct file *file) +{ + struct serio_raw *serio_raw; + struct serio_raw_list *list; + int retval = 0; + + retval = down_interruptible(&serio_raw_sem); + if (retval) + return retval; + + if (!(serio_raw = serio_raw_locate(iminor(inode)))) { + retval = -ENODEV; + goto out; + } + + if (!serio_raw->serio) { + retval = -ENODEV; + goto out; + } + + if (!(list = kmalloc(sizeof(struct serio_raw_list), GFP_KERNEL))) { + retval = -ENOMEM; + goto out; + } + + memset(list, 0, sizeof(struct serio_raw_list)); + list->serio_raw = serio_raw; + file->private_data = list; + + serio_raw->refcnt++; + list_add_tail(&list->node, &serio_raw->list); + +out: + up(&serio_raw_sem); + return retval; +} + +static int serio_raw_cleanup(struct serio_raw *serio_raw) +{ + if (--serio_raw->refcnt == 0) { + misc_deregister(&serio_raw->dev); + list_del_init(&serio_raw->node); + kfree(serio_raw); + + return 1; + } + + return 0; +} + +static int serio_raw_release(struct inode *inode, struct file *file) +{ + struct serio_raw_list *list = file->private_data; + struct serio_raw *serio_raw = list->serio_raw; + + down(&serio_raw_sem); + + serio_raw_fasync(-1, file, 0); + serio_raw_cleanup(serio_raw); + + up(&serio_raw_sem); + return 0; +} + +static int serio_raw_fetch_byte(struct serio_raw *serio_raw, char *c) +{ + unsigned long flags; + int empty; + + spin_lock_irqsave(&serio_raw->serio->lock, flags); + + empty = serio_raw->head == serio_raw->tail; + if (!empty) { + *c = serio_raw->queue[serio_raw->tail]; + serio_raw->tail = (serio_raw->tail + 1) % SERIO_RAW_QUEUE_LEN; + } + + spin_unlock_irqrestore(&serio_raw->serio->lock, flags); + + return !empty; +} + +static ssize_t serio_raw_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos) +{ + struct serio_raw_list *list = file->private_data; + struct serio_raw *serio_raw = list->serio_raw; + char c; + ssize_t retval = 0; + + if (!serio_raw->serio) + return -ENODEV; + + if (serio_raw->head == serio_raw->tail && (file->f_flags & O_NONBLOCK)) + return -EAGAIN; + + retval = wait_event_interruptible(list->serio_raw->wait, + serio_raw->head != serio_raw->tail || !serio_raw->serio); + if (retval) + return retval; + + if (!serio_raw->serio) + return -ENODEV; + + while (retval < count && serio_raw_fetch_byte(serio_raw, &c)) { + if (put_user(c, buffer++)) + return -EFAULT; + retval++; + } + + return retval; +} + +static ssize_t serio_raw_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos) +{ + struct serio_raw_list *list = file->private_data; + ssize_t written = 0; + int retval; + unsigned char c; + + retval = down_interruptible(&serio_raw_sem); + if (retval) + return retval; + + if (!list->serio_raw->serio) { + retval = -ENODEV; + goto out; + } + + if (count > 32) + count = 32; + + while (count--) { + if (get_user(c, buffer++)) { + retval = -EFAULT; + goto out; + } + if (serio_write(list->serio_raw->serio, c)) { + retval = -EIO; + goto out; + } + written++; + }; + +out: + up(&serio_raw_sem); + return written; +} + +static unsigned int serio_raw_poll(struct file *file, poll_table *wait) +{ + struct serio_raw_list *list = file->private_data; + + poll_wait(file, &list->serio_raw->wait, wait); + + if (list->serio_raw->head != list->serio_raw->tail) + return POLLIN | POLLRDNORM; + + return 0; +} + +static struct file_operations serio_raw_fops = { + .owner = THIS_MODULE, + .open = serio_raw_open, + .release = serio_raw_release, + .read = serio_raw_read, + .write = serio_raw_write, + .poll = serio_raw_poll, + .fasync = serio_raw_fasync, +}; + + +/********************************************************************* + * Interface with serio port * + *********************************************************************/ + +static irqreturn_t serio_raw_interrupt(struct serio *serio, unsigned char data, + unsigned int dfl, struct pt_regs *regs) +{ + struct serio_raw *serio_raw = serio_get_drvdata(serio); + struct serio_raw_list *list; + unsigned int head = serio_raw->head; + + /* we are holding serio->lock here so we are prootected */ + serio_raw->queue[head] = data; + head = (head + 1) % SERIO_RAW_QUEUE_LEN; + if (likely(head != serio_raw->tail)) { + serio_raw->head = head; + list_for_each_entry(list, &serio_raw->list, node) + kill_fasync(&list->fasync, SIGIO, POLL_IN); + wake_up_interruptible(&serio_raw->wait); + } + + return IRQ_HANDLED; +} + +static int serio_raw_connect(struct serio *serio, struct serio_driver *drv) +{ + struct serio_raw *serio_raw; + int err; + + if (!(serio_raw = kmalloc(sizeof(struct serio_raw), GFP_KERNEL))) { + printk(KERN_ERR "serio_raw.c: can't allocate memory for a device\n"); + return -ENOMEM; + } + + down(&serio_raw_sem); + + memset(serio_raw, 0, sizeof(struct serio_raw)); + snprintf(serio_raw->name, sizeof(serio_raw->name), "serio_raw%d", serio_raw_no++); + serio_raw->refcnt = 1; + serio_raw->serio = serio; + INIT_LIST_HEAD(&serio_raw->list); + init_waitqueue_head(&serio_raw->wait); + + serio_set_drvdata(serio, serio_raw); + + err = serio_open(serio, drv); + if (err) + goto out_free; + + list_add_tail(&serio_raw->node, &serio_raw_list); + + serio_raw->dev.minor = PSMOUSE_MINOR; + serio_raw->dev.name = serio_raw->name; + serio_raw->dev.fops = &serio_raw_fops; + + err = misc_register(&serio_raw->dev); + if (err) { + serio_raw->dev.minor = MISC_DYNAMIC_MINOR; + err = misc_register(&serio_raw->dev); + } + + if (err) { + printk(KERN_INFO "serio_raw: failed to register raw access device for %s\n", + serio->phys); + goto out_close; + } + + printk(KERN_INFO "serio_raw: raw access enabled on %s (%s, minor %d)\n", + serio->phys, serio_raw->name, serio_raw->dev.minor); + goto out; + +out_close: + serio_close(serio); + list_del_init(&serio_raw->node); +out_free: + serio_set_drvdata(serio, NULL); + kfree(serio_raw); +out: + up(&serio_raw_sem); + return err; +} + +static int serio_raw_reconnect(struct serio *serio) +{ + struct serio_raw *serio_raw = serio_get_drvdata(serio); + struct serio_driver *drv = serio->drv; + + if (!drv || !serio_raw) { + printk(KERN_DEBUG "serio_raw: reconnect request, but serio is disconnected, ignoring...\n"); + return -1; + } + + /* + * Nothing needs to be done here, we just need this method to + * keep the same device. + */ + return 0; +} + +static void serio_raw_disconnect(struct serio *serio) +{ + struct serio_raw *serio_raw; + + down(&serio_raw_sem); + + serio_raw = serio_get_drvdata(serio); + + serio_close(serio); + serio_set_drvdata(serio, NULL); + + serio_raw->serio = NULL; + if (!serio_raw_cleanup(serio_raw)) + wake_up_interruptible(&serio_raw->wait); + + up(&serio_raw_sem); +} + +static struct serio_device_id serio_raw_serio_ids[] = { + { + .type = SERIO_8042, + .proto = SERIO_ANY, + .id = SERIO_ANY, + .extra = SERIO_ANY, + }, + { 0 } +}; + +MODULE_DEVICE_TABLE(serio, serio_raw_serio_ids); + +static struct serio_driver serio_raw_drv = { + .driver = { + .name = "serio_raw", + }, + .description = DRIVER_DESC, + .id_table = serio_raw_serio_ids, + .interrupt = serio_raw_interrupt, + .connect = serio_raw_connect, + .reconnect = serio_raw_reconnect, + .disconnect = serio_raw_disconnect, + .manual_bind = 1, +}; + +static int __init serio_raw_init(void) +{ + serio_register_driver(&serio_raw_drv); + return 0; +} + +static void __exit serio_raw_exit(void) +{ + serio_unregister_driver(&serio_raw_drv); +} + +module_init(serio_raw_init); +module_exit(serio_raw_exit); diff --git a/drivers/input/serio/serport.c b/drivers/input/serio/serport.c new file mode 100644 index 00000000000..22f73683952 --- /dev/null +++ b/drivers/input/serio/serport.c @@ -0,0 +1,226 @@ +/* + * Input device TTY line discipline + * + * Copyright (c) 1999-2002 Vojtech Pavlik + * + * This is a module that converts a tty line into a much simpler + * 'serial io port' abstraction that the input device drivers use. + */ + +/* + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +#include <asm/uaccess.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/serio.h> +#include <linux/tty.h> + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION("Input device TTY line discipline"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_LDISC(N_MOUSE); + +#define SERPORT_BUSY 1 + +struct serport { + struct tty_struct *tty; + wait_queue_head_t wait; + struct serio *serio; + unsigned long flags; +}; + +/* + * Callback functions from the serio code. + */ + +static int serport_serio_write(struct serio *serio, unsigned char data) +{ + struct serport *serport = serio->port_data; + return -(serport->tty->driver->write(serport->tty, &data, 1) != 1); +} + +static void serport_serio_close(struct serio *serio) +{ + struct serport *serport = serio->port_data; + + serport->serio->id.type = 0; + wake_up_interruptible(&serport->wait); +} + +/* + * serport_ldisc_open() is the routine that is called upon setting our line + * discipline on a tty. It prepares the serio struct. + */ + +static int serport_ldisc_open(struct tty_struct *tty) +{ + struct serport *serport; + struct serio *serio; + char name[64]; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + + serport = kmalloc(sizeof(struct serport), GFP_KERNEL); + serio = kmalloc(sizeof(struct serio), GFP_KERNEL); + if (unlikely(!serport || !serio)) { + kfree(serport); + kfree(serio); + return -ENOMEM; + } + + memset(serport, 0, sizeof(struct serport)); + serport->serio = serio; + set_bit(TTY_DO_WRITE_WAKEUP, &tty->flags); + serport->tty = tty; + tty->disc_data = serport; + + memset(serio, 0, sizeof(struct serio)); + strlcpy(serio->name, "Serial port", sizeof(serio->name)); + snprintf(serio->phys, sizeof(serio->phys), "%s/serio0", tty_name(tty, name)); + serio->id.type = SERIO_RS232; + serio->write = serport_serio_write; + serio->close = serport_serio_close; + serio->port_data = serport; + + init_waitqueue_head(&serport->wait); + + return 0; +} + +/* + * serport_ldisc_close() is the opposite of serport_ldisc_open() + */ + +static void serport_ldisc_close(struct tty_struct *tty) +{ + struct serport *serport = (struct serport*) tty->disc_data; + kfree(serport); +} + +/* + * serport_ldisc_receive() is called by the low level tty driver when characters + * are ready for us. We forward the characters, one by one to the 'interrupt' + * routine. + * + * FIXME: We should get pt_regs from the tty layer and forward them to + * serio_interrupt here. + */ + +static void serport_ldisc_receive(struct tty_struct *tty, const unsigned char *cp, char *fp, int count) +{ + struct serport *serport = (struct serport*) tty->disc_data; + int i; + for (i = 0; i < count; i++) + serio_interrupt(serport->serio, cp[i], 0, NULL); +} + +/* + * serport_ldisc_room() reports how much room we do have for receiving data. + * Although we in fact have infinite room, we need to specify some value + * here, and 256 seems to be reasonable. + */ + +static int serport_ldisc_room(struct tty_struct *tty) +{ + return 256; +} + +/* + * serport_ldisc_read() just waits indefinitely if everything goes well. + * However, when the serio driver closes the serio port, it finishes, + * returning 0 characters. + */ + +static ssize_t serport_ldisc_read(struct tty_struct * tty, struct file * file, unsigned char __user * buf, size_t nr) +{ + struct serport *serport = (struct serport*) tty->disc_data; + char name[64]; + + if (test_and_set_bit(SERPORT_BUSY, &serport->flags)) + return -EBUSY; + + serio_register_port(serport->serio); + printk(KERN_INFO "serio: Serial port %s\n", tty_name(tty, name)); + wait_event_interruptible(serport->wait, !serport->serio->id.type); + serio_unregister_port(serport->serio); + + clear_bit(SERPORT_BUSY, &serport->flags); + + return 0; +} + +/* + * serport_ldisc_ioctl() allows to set the port protocol, and device ID + */ + +static int serport_ldisc_ioctl(struct tty_struct * tty, struct file * file, unsigned int cmd, unsigned long arg) +{ + struct serport *serport = (struct serport*) tty->disc_data; + struct serio *serio = serport->serio; + unsigned long type; + + if (cmd == SPIOCSTYPE) { + if (get_user(type, (unsigned long __user *) arg)) + return -EFAULT; + + serio->id.proto = type & 0x000000ff; + serio->id.id = (type & 0x0000ff00) >> 8; + serio->id.extra = (type & 0x00ff0000) >> 16; + + return 0; + } + + return -EINVAL; +} + +static void serport_ldisc_write_wakeup(struct tty_struct * tty) +{ + struct serport *sp = (struct serport *) tty->disc_data; + + serio_drv_write_wakeup(sp->serio); +} + +/* + * The line discipline structure. + */ + +static struct tty_ldisc serport_ldisc = { + .owner = THIS_MODULE, + .name = "input", + .open = serport_ldisc_open, + .close = serport_ldisc_close, + .read = serport_ldisc_read, + .ioctl = serport_ldisc_ioctl, + .receive_buf = serport_ldisc_receive, + .receive_room = serport_ldisc_room, + .write_wakeup = serport_ldisc_write_wakeup +}; + +/* + * The functions for insering/removing us as a module. + */ + +static int __init serport_init(void) +{ + int retval; + retval = tty_register_ldisc(N_MOUSE, &serport_ldisc); + if (retval) + printk(KERN_ERR "serport.c: Error registering line discipline.\n"); + + return retval; +} + +static void __exit serport_exit(void) +{ + tty_register_ldisc(N_MOUSE, NULL); +} + +module_init(serport_init); +module_exit(serport_exit); |