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!
diff --git a/drivers/input/serio/Kconfig b/drivers/input/serio/Kconfig
new file mode 100644
index 0000000..b371073
--- /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 0000000..678a859
--- /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 0000000..9b1ab5e
--- /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 0000000..dd0f5bd
--- /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 0000000..897e4c1
--- /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 0000000..c243cb6f
--- /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 0000000..7629452
--- /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 0000000..e3c44ff
--- /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 0000000..c9e633d
--- /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 0000000..863b9c9
--- /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 0000000..5c20ab1
--- /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 0000000..2906e1b
--- /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 0000000..da2a198
--- /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 0000000..f648678
--- /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 0000000..8e63e46
--- /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 0000000..1383503
--- /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 0000000..c978657
--- /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 0000000..9880fc1
--- /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 0000000..1d15c28
--- /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 0000000..1e139c5
--- /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 0000000..46093c5
--- /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 0000000..106f5ee
--- /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 0000000..3f0df33
--- /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 0000000..3313e2d
--- /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 0000000..d914e7e
--- /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 0000000..22f7368
--- /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);