diff options
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() - |