aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/input/serio
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@ppc970.osdl.org>2005-04-16 15:20:36 -0700
committerLinus Torvalds <torvalds@ppc970.osdl.org>2005-04-16 15:20:36 -0700
commit1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch)
tree0bba044c4ce775e45a88a51686b5d9f90697ea9d /drivers/input/serio
downloadlinux-dev-1da177e4c3f41524e886b7f1b8a0c1fc7321cac2.tar.xz
linux-dev-1da177e4c3f41524e886b7f1b8a0c1fc7321cac2.zip
Linux-2.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')
-rw-r--r--drivers/input/serio/Kconfig183
-rw-r--r--drivers/input/serio/Makefile23
-rw-r--r--drivers/input/serio/ambakmi.c231
-rw-r--r--drivers/input/serio/ct82c710.c225
-rw-r--r--drivers/input/serio/gscps2.c467
-rw-r--r--drivers/input/serio/hil_mlc.c949
-rw-r--r--drivers/input/serio/hp_sdc.c1054
-rw-r--r--drivers/input/serio/hp_sdc_mlc.c358
-rw-r--r--drivers/input/serio/i8042-io.h93
-rw-r--r--drivers/input/serio/i8042-ip22io.h76
-rw-r--r--drivers/input/serio/i8042-jazzio.h69
-rw-r--r--drivers/input/serio/i8042-ppcio.h136
-rw-r--r--drivers/input/serio/i8042-sparcio.h116
-rw-r--r--drivers/input/serio/i8042-x86ia64io.h333
-rw-r--r--drivers/input/serio/i8042.c1116
-rw-r--r--drivers/input/serio/i8042.h133
-rw-r--r--drivers/input/serio/libps2.c305
-rw-r--r--drivers/input/serio/maceps2.c176
-rw-r--r--drivers/input/serio/parkbd.c218
-rw-r--r--drivers/input/serio/pcips2.c234
-rw-r--r--drivers/input/serio/q40kbd.c163
-rw-r--r--drivers/input/serio/rpckbd.c156
-rw-r--r--drivers/input/serio/sa1111ps2.c359
-rw-r--r--drivers/input/serio/serio.c859
-rw-r--r--drivers/input/serio/serio_raw.c403
-rw-r--r--drivers/input/serio/serport.c226
26 files changed, 8661 insertions, 0 deletions
diff --git a/drivers/input/serio/Kconfig b/drivers/input/serio/Kconfig
new file mode 100644
index 000000000000..b3710733b36b
--- /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 000000000000..678a8599f9ff
--- /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 000000000000..9b1ab5e7a98d
--- /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 000000000000..dd0f5bd90241
--- /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 000000000000..897e4c12b642
--- /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 000000000000..c243cb6fdfc4
--- /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 000000000000..7629452dd64b
--- /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 000000000000..e3c44ffae674
--- /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 000000000000..c9e633d21d90
--- /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 000000000000..863b9c95fbb8
--- /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 000000000000..5c20ab131488
--- /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 000000000000..2906e1b60c04
--- /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 000000000000..da2a19812485
--- /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 000000000000..f64867808fea
--- /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 000000000000..8e63e464d361
--- /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(&param, I8042_CMD_AUX_LOOP) || param != 0x0f)
+ return -1;
+ param = mode ? 0x56 : 0xf6;
+ if (i8042_command(&param, I8042_CMD_AUX_LOOP) || param != (mode ? 0xa9 : 0x09))
+ return -1;
+ param = mode ? 0xa4 : 0xa5;
+ if (i8042_command(&param, 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(&param, I8042_CMD_MUX_PFX + i);
+ i8042_command(&param, 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(&param, 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(&param, 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(&param, I8042_CMD_AUX_DISABLE))
+ return -1;
+ if (i8042_command(&param, 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(&param, I8042_CMD_AUX_ENABLE))
+ return -1;
+ if (i8042_command(&param, 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(&param, 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(&param, 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 000000000000..13835039a2a7
--- /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 000000000000..c978657068c5
--- /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 000000000000..9880fc145d90
--- /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 000000000000..1d15c2819818
--- /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 000000000000..1e139c5e59de
--- /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 000000000000..46093c507988
--- /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 000000000000..106f5eefd89a
--- /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 000000000000..3f0df3330fb2
--- /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 000000000000..3313e2daeab0
--- /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 000000000000..d914e7e93db4
--- /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 000000000000..22f73683952b
--- /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);