]> git.hungrycats.org Git - linux/commitdiff
Add new serio modules for PS/2 AUX/KBD.
authorVojtech Pavlik <vojtech@twilight.ucw.cz>
Wed, 3 Jul 2002 16:56:19 +0000 (18:56 +0200)
committerVojtech Pavlik <vojtech@twilight.ucw.cz>
Wed, 3 Jul 2002 16:56:19 +0000 (18:56 +0200)
drivers/input/serio/Config.help
drivers/input/serio/Config.in
drivers/input/serio/Makefile
drivers/input/serio/ct82c710.c [new file with mode: 0644]
drivers/input/serio/i8042.c [new file with mode: 0644]
drivers/input/serio/i8042.h [new file with mode: 0644]
drivers/input/serio/parkbd.c [new file with mode: 0644]
drivers/input/serio/rpckbd.c [new file with mode: 0644]

index a82785414452b0bc45e6c951499c366ea6511d58..ae97e4223fc09f33a0b05aaa5f6f41226c53ab3d 100644 (file)
@@ -12,6 +12,18 @@ CONFIG_SERIO
   The module will be called serio.o. If you want to compile it
   as a module, say M here and read <file:Documentation/modules.txt>.
 
+CONFIG_SERIO_I8042
+  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.
+
+  This driver is also available as a module ( = code which can be
+  inserted in and removed from the running kernel whenever you want).
+  The module will be called i8042.o. If you want to compile it
+  as a module, say M here and read <file:Documentation/modules.txt>.
+
 CONFIG_SERIO_SERPORT
   Say Y here if you plan to use an input device (mouse, joystick,
   tablet, 6dof) that communicates over the RS232 serial (COM) port.
@@ -24,3 +36,38 @@ CONFIG_SERIO_SERPORT
   inserted in and removed from the running kernel whenever you want).
   The module will be called serport.o. If you want to compile it as a
   module, say M here and read <file:Documentation/modules.txt>.
+
+CONFIG_SERIO_CT82C710
+  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.
+
+  This driver is also available as a module ( = code which can be
+  inserted in and removed from the running kernel whenever you want).
+  The module will be called ct82c710.o. If you want to compile it as a
+  module, say M here and read <file:Documentation/modules.txt>.
+
+CONFIG_SERIO_PARKBD
+  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.
+
+  This driver is also available as a module ( = code which can be
+  inserted in and removed from the running kernel whenever you want).
+  The module will be called parkbd.o. If you want to compile it as a
+  module, say M here and read <file:Documentation/modules.txt>.
+
+CONFIG_SERIO_ACORN
+  Say Y here if you have the Acorn RiscPC and want to use an AT
+  keyboard connected to its keyboard controller.
+
+  This driver is also available as a module ( = code which can be
+  inserted in and removed from the running kernel whenever you want).
+  The module will be called rpckbd.o. If you want to compile it as a
+  module, say M here and read <file:Documentation/modules.txt>.
+
index cc566ad205862b4dfd3fcff4c4ada2a8f8ca3f01..f8ecb038c7be1a083731ed8573063d27260de3b3 100644 (file)
@@ -4,4 +4,16 @@
 
 tristate 'Serial i/o support' CONFIG_SERIO
 
+dep_tristate '  i8042 PC Keyboard controller' CONFIG_SERIO_I8042 $CONFIG_SERIO $CONFIG_ISA
+if [ "$CONFIG_INPUT_I8042" != "n" ]; then
+   hex '    Register Base Address' CONFIG_I8042_REG_BASE 60
+   int '    PS/2 Keyboard IRQ' CONFIG_I8042_KBD_IRQ 1
+   int '    PS/2 AUX IRQ' CONFIG_I8042_AUX_IRQ 12
+fi
 dep_tristate '  Serial port line discipline' CONFIG_SERIO_SERPORT $CONFIG_SERIO 
+dep_tristate '  ct82c710 Aux port controller' CONFIG_SERIO_CT82C710 $CONFIG_SERIO $CONFIG_ISA
+dep_tristate '  Parallel port keyboard adapter' CONFIG_SERIO_PARKBD $CONFIG_SERIO $CONFIG_PARPORT
+
+if [ "$CONFIG_ARCH_ACORN" = "y" ]; then
+   dep_tristate '  Acorn RiscPC keyboard controller' CONFIG_SERIO_ACORN $CONFIG_SERIO
+fi
index 5865d9be9059cdb62632a0693fc5260353d834a1..8e8e036a0e17cbf6fb8c5a997b3f5d15808f713c 100644 (file)
@@ -9,7 +9,11 @@ export-objs    := serio.o
 # 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
 
 # The global Rules.make.
 
diff --git a/drivers/input/serio/ct82c710.c b/drivers/input/serio/ct82c710.c
new file mode 100644 (file)
index 0000000..8202a64
--- /dev/null
@@ -0,0 +1,212 @@
+/*
+ * $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 <asm/io.h>
+
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/ioport.h>
+#include <linux/config.h>
+#include <linux/init.h>
+#include <linux/serio.h>
+#include <linux/errno.h>
+#include <linux/sched.h>
+
+MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>");
+MODULE_DESCRIPTION("82C710 C&T mouse port chip driver");
+MODULE_LICENSE("GPL");
+
+static char ct82c710_name[] = "C&T 82c710 mouse port";
+static char ct82c710_phys[16];
+
+/*
+ * 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
+
+static int ct82c710_data = 0;
+static int ct82c710_status = 0;
+
+static void ct82c710_interrupt(int cpl, void *dev_id, struct pt_regs * 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;
+}
+
+static struct serio ct82c710_port =
+{
+       type:   SERIO_8042,
+       name:   ct82c710_name,
+       phys:   ct82c710_phys,
+       write:  ct82c710_write,
+       open:   ct82c710_open,
+       close:  ct82c710_close,
+};
+
+/*
+ * Interrupt handler for the 82C710 mouse port. A character
+ * is waiting in the 82C710.
+ */
+
+static void ct82c710_interrupt(int cpl, void *dev_id, struct pt_regs * regs)
+{
+       if (ct82c710_port.dev)
+               ct82c710_port.dev->interrupt(&ct82c710_port, inb(ct82c710_data), 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_data = inb_p(0x391) << 2;              /* Get mouse I/O address */
+       ct82c710_status = ct82c710_data + 1;
+       outb_p(0x0f, 0x390);
+       outb_p(0x0f, 0x391);                            /* Close config mode */
+
+       return 0;
+}
+
+int __init ct82c710_init(void)
+{
+       if (ct82c710_probe())
+               return -ENODEV;
+
+       if (request_region(ct82c710_data, 2, "ct82c710"))
+               return -EBUSY;
+
+       sprintf(ct82c710_phys, "isa%04x/serio0", ct82c710_data);
+
+       serio_register_port(&ct82c710_port);
+
+       printk(KERN_INFO "serio: C&T 82c710 mouse port at %#x irq %d\n",
+               ct82c710_data, CT82C710_IRQ);
+
+       return 0;
+}
+
+void __exit ct82c710_exit(void)
+{
+       serio_unregister_port(&ct82c710_port);
+       release_region(ct82c710_data, 2);
+}
+
+module_init(ct82c710_init);
+module_exit(ct82c710_exit);
diff --git a/drivers/input/serio/i8042.c b/drivers/input/serio/i8042.c
new file mode 100644 (file)
index 0000000..5e222b2
--- /dev/null
@@ -0,0 +1,717 @@
+/*
+ * $Id: i8042.c,v 1.21 2002/03/01 22:09:27 jsimmons Exp $
+ *
+ *  Copyright (c) 1999-2001 Vojtech Pavlik
+ */
+
+/*
+ *  i8042 keyboard and mouse controller 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 <asm/io.h>
+
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/ioport.h>
+#include <linux/config.h>
+#include <linux/reboot.h>
+#include <linux/init.h>
+#include <linux/serio.h>
+#include <linux/sched.h>       /* request/free_irq */
+
+#include "i8042.h"
+
+MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>");
+MODULE_DESCRIPTION("i8042 keyboard and mouse controller driver");
+MODULE_LICENSE("GPL");
+
+MODULE_PARM(i8042_noaux, "1i");
+MODULE_PARM(i8042_unlock, "1i");
+MODULE_PARM(i8042_reset, "1i");
+MODULE_PARM(i8042_direct, "1i");
+
+static int i8042_noaux;
+static int i8042_unlock;
+static int i8042_reset;
+static int i8042_direct;
+
+spinlock_t i8042_lock = SPIN_LOCK_UNLOCKED;
+
+struct i8042_values {
+       int irq;
+       unsigned char disable;
+       unsigned char irqen;
+       unsigned char exists;
+       unsigned char *name;
+       unsigned char *phys;
+};
+
+static struct serio i8042_kbd_port;
+static struct serio i8042_aux_port;
+static unsigned char i8042_initial_ctr;
+static unsigned char i8042_ctr;
+
+#ifdef I8042_DEBUG_IO
+static unsigned long i8042_start;
+#endif
+
+static unsigned long i8042_unxlate_seen[128 / BITS_PER_LONG];
+static unsigned char i8042_unxlate_table[128] = {
+         0,118, 22, 30, 38, 37, 46, 54, 61, 62, 70, 69, 78, 85,102, 13,
+        21, 29, 36, 45, 44, 53, 60, 67, 68, 77, 84, 91, 90, 20, 28, 27,
+        35, 43, 52, 51, 59, 66, 75, 76, 82, 14, 18, 93, 26, 34, 33, 42,
+        50, 49, 58, 65, 73, 74, 89,124, 17, 41, 88,  5,  6,  4, 12,  3,
+        11,  2, 10,  1,  9,119,126,108,117,125,123,107,115,116,121,105,
+       114,122,112,113,127, 96, 97,120,  7, 15, 23, 31, 39, 47, 55, 63,
+        71, 79, 86, 94,  8, 16, 24, 32, 40, 48, 56, 64, 72, 80, 87,111,
+        19, 25, 57, 81, 83, 92, 95, 98, 99,100,101,103,104,106,109,110
+};
+
+static void 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.
+ */
+
+static int i8042_wait_read(void)
+{
+       int i = 0;
+       while ((~inb(I8042_STATUS_REG) & 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 ((inb(I8042_STATUS_REG) & 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;
+       int i = 0;
+
+       spin_lock_irqsave(&i8042_lock, flags);
+
+       while ((inb(I8042_STATUS_REG) & I8042_STR_OBF) && (i++ < I8042_BUFFER_SIZE))
+#ifdef I8042_DEBUG_IO
+               printk(KERN_DEBUG "i8042.c: %02x <- i8042 (flush) [%d]\n",
+                       inb(I8042_DATA_REG), (int) (jiffies - i8042_start));
+#else
+               inb(I8042_DATA_REG);
+#endif
+
+       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;
+
+       spin_lock_irqsave(&i8042_lock, flags);
+
+       retval = i8042_wait_write();
+       if (!retval) {
+#ifdef I8042_DEBUG_IO
+               printk(KERN_DEBUG "i8042.c: %02x -> i8042 (command) [%d]\n",
+                       command & 0xff, (int) (jiffies - i8042_start));
+#endif
+               outb(command & 0xff, I8042_COMMAND_REG);
+       }
+       
+       if (!retval)
+               for (i = 0; i < ((command >> 12) & 0xf); i++) {
+                       if ((retval = i8042_wait_write())) break;
+#ifdef I8042_DEBUG_IO
+                       printk(KERN_DEBUG "i8042.c: %02x -> i8042 (parameter) [%d]\n",
+                               param[i], (int) (jiffies - i8042_start));
+#endif
+                       outb(param[i], I8042_DATA_REG);
+               }
+
+       if (!retval)
+               for (i = 0; i < ((command >> 8) & 0xf); i++) {
+                       if ((retval = i8042_wait_read())) break;
+                       if (inb(I8042_STATUS_REG) & I8042_STR_AUXDATA) 
+                               param[i] = ~inb(I8042_DATA_REG);
+                       else
+                               param[i] = inb(I8042_DATA_REG);
+#ifdef I8042_DEBUG_IO
+                       printk(KERN_DEBUG "i8042.c: %02x <- i8042 (return) [%d]\n",
+                               param[i], (int) (jiffies - i8042_start));
+#endif
+               }
+
+       spin_unlock_irqrestore(&i8042_lock, flags);
+
+#ifdef I8042_DEBUG_IO
+       if (retval)
+               printk(KERN_DEBUG "i8042.c:      -- i8042 (timeout) [%d]\n",
+                       (int) (jiffies - i8042_start));
+#endif
+
+       return retval;
+}
+
+/*
+ * i8042_kbd_write() sends a byte out through the keyboard interface.
+ * It also automatically refreshes the CTR value, since some i8042's
+ * trash their CTR after attempting to send data to an nonexistent
+ * device.
+ */
+
+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())) {
+#ifdef I8042_DEBUG_IO
+               printk(KERN_DEBUG "i8042.c: %02x -> i8042 (kbd-data) [%d]\n",
+                       c, (int) (jiffies - i8042_start));
+#endif
+               outb(c, I8042_DATA_REG);
+       }
+
+       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 *port, unsigned char c)
+{
+       int retval;
+
+/*
+ * Send the byte out.
+ */
+
+       retval  = i8042_command(&c, I8042_CMD_AUX_SEND);
+
+/*
+ * Here we restore the CTR value. I don't know why, but i8042's in half-AT
+ * mode tend to trash their CTR when doing the AUX_SEND command.
+ */
+
+       retval += i8042_command(&i8042_ctr, I8042_CMD_CTL_WCTR);
+
+/*
+ * 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, port, NULL);
+       return retval;
+}
+
+/*
+ * i8042_open() is called when a port is open by the higher layer.
+ * It allocates an interrupt and enables the port.
+ */
+
+static int i8042_open(struct serio *port)
+{
+       struct i8042_values *values = port->driver;
+
+/*
+ * Allocate the interrupt
+ */
+
+       if (request_irq(values->irq, i8042_interrupt, 0, "i8042", NULL)) {
+               printk(KERN_ERR "i8042.c: Can't get irq %d for %s\n", values->irq, values->name);
+               return -1;
+       }
+
+/*
+ * Enable the device and its interrupt.
+ */
+
+       i8042_ctr |= values->irqen;
+       i8042_ctr &= ~values->disable;
+
+       if (i8042_command(&i8042_ctr, I8042_CMD_CTL_WCTR)) {
+               printk(KERN_ERR "i8042.c: Can't write CTR while opening %s.\n", values->name);
+               return -1;
+       }
+
+/*
+ * Flush buffers
+ */
+
+       i8042_flush();
+
+       return 0;
+}
+
+/*
+ * i8042_close() frees the interrupt, and disables the interface when the
+ * upper layer doesn't need it anymore.
+ */
+
+static void i8042_close(struct serio *port)
+{
+       struct i8042_values *values = port->driver;
+
+/*
+ * Disable the device and its interrupt.
+ */
+
+       i8042_ctr &= ~values->irqen;
+       i8042_ctr |= values->disable;
+
+       if (i8042_command(&i8042_ctr, I8042_CMD_CTL_WCTR)) {
+               printk(KERN_ERR "i8042.c: Can't write CTR while closing %s.\n", values->name);
+               return;
+       }
+
+/*
+ * Free the interrupt
+ */
+
+       free_irq(values->irq, NULL);
+}
+
+/*
+ * Structures for registering the devices in the serio.c module.
+ */
+
+static struct i8042_values i8042_kbd_values = {
+       irq:            I8042_KBD_IRQ,
+       irqen:          I8042_CTR_KBDINT,
+       disable:        I8042_CTR_KBDDIS,
+       name:           "KBD",
+       exists:         0,
+};
+
+static struct serio i8042_kbd_port =
+{
+       type:           SERIO_8042,
+       write:          i8042_kbd_write,
+       open:           i8042_open,
+       close:          i8042_close,
+       driver:         &i8042_kbd_values,
+       name:           "i8042 Kbd Port",
+       phys:           "isa0060/serio0",
+};
+
+static struct i8042_values i8042_aux_values = {
+       irq:            I8042_AUX_IRQ,
+       irqen:          I8042_CTR_AUXINT,
+       disable:        I8042_CTR_AUXDIS,
+       name:           "AUX",
+       exists:         0,
+};
+
+static struct serio i8042_aux_port =
+{
+       type:           SERIO_8042,
+       write:          i8042_aux_write,
+       open:           i8042_open,
+       close:          i8042_close,
+       driver:         &i8042_aux_values,
+       name:           "i8042 Aux Port",
+       phys:           "isa0060/serio1",
+};
+
+/*
+ * 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 void i8042_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+       unsigned long flags;
+       unsigned char str, data;
+
+       spin_lock_irqsave(&i8042_lock, flags);
+
+       while ((str = inb(I8042_STATUS_REG)) & I8042_STR_OBF) {
+
+               data = inb(I8042_DATA_REG);
+
+#ifdef I8042_DEBUG_IO
+               printk(KERN_DEBUG "i8042.c: %02x <- i8042 (interrupt-%s) [%d]\n",
+                       data, (str & I8042_STR_AUXDATA) ? "aux" : "kbd", (int) (jiffies - i8042_start));
+#endif
+
+               if (i8042_aux_values.exists && (str & I8042_STR_AUXDATA)) {
+                       if (i8042_aux_port.dev)
+                               i8042_aux_port.dev->interrupt(&i8042_aux_port, data, 0);
+               } else {
+                       if (i8042_kbd_values.exists && i8042_kbd_port.dev) {
+                               if (!i8042_direct) {
+                                       if (data > 0x7f) {
+                                               if (test_and_clear_bit(data & 0x7f, i8042_unxlate_seen)) {
+                                                       i8042_kbd_port.dev->interrupt(&i8042_kbd_port, 0xf0, 0);        
+                                                       data = i8042_unxlate_table[data & 0x7f];
+                                               }
+                                       } else {
+                                               set_bit(data, i8042_unxlate_seen);
+                                               data = i8042_unxlate_table[data];
+                                       }
+                               }
+                               i8042_kbd_port.dev->interrupt(&i8042_kbd_port, data, 0);
+                       }
+               }
+       }
+
+       spin_unlock_irqrestore(&i8042_lock, flags);
+}
+
+/*
+ * i8042_controller init initializes the i8042 controller, and,
+ * most importantly, sets it into non-xlated mode.
+ */
+       
+static int __init i8042_controller_init(void)
+{
+
+/*
+ * Check the i/o region before we touch it.
+ */
+#if !defined(__i386__) && !defined(__sh__) && !defined(__alpha__)      
+       if (check_region(I8042_DATA_REG, 16)) {
+               printk(KERN_ERR "i8042.c: %#x port already in use!\n", I8042_DATA_REG);
+               return -1;
+       }
+#endif
+
+/*
+ * Test the i8042. We need to know if it thinks it's working correctly
+ * before doing anything else.
+ */
+
+       i8042_flush();
+
+       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;
+               }
+       }
+
+/*
+ * Read the CTR.
+ */
+
+       if (i8042_command(&i8042_ctr, I8042_CMD_CTL_RCTR)) {
+               printk(KERN_ERR "i8042.c: Can't read CTR while initializing i8042.\n");
+               return -1;
+       }
+
+/*
+ * Save the CTR for restoral on unload / reboot.
+ */
+
+       i8042_initial_ctr = i8042_ctr;
+
+/*
+ * Disable both interfaces and their interrupts.
+ */
+
+       i8042_ctr |= I8042_CTR_KBDDIS;
+       i8042_ctr &= ~I8042_CTR_KBDINT;
+
+/*
+ * Handle keylock.
+ */
+
+       if (~inb(I8042_STATUS_REG) & I8042_STR_KEYLOCK) {
+
+               if (i8042_unlock) {
+                       i8042_ctr |= I8042_CTR_IGNKEYLOCK;
+               } else {
+                       printk(KERN_WARNING "i8042.c: Warning: Keylock active.\n");
+               }
+       }
+
+/*
+ * If the chip is configured into nontranslated mode by the BIOS, don't
+ * bother enabling translating and just use that happily.
+ */
+
+       if (~i8042_ctr & I8042_CTR_XLATE)
+               i8042_direct = 1;
+
+/*
+ * Set nontranslated mode for the kbd interface if requested by an option.
+ * This is vital for a working scancode set 3 support. After this the kbd
+ * interface becomes a simple serial in/out, like the aux interface is. If
+ * the user doesn't wish this, the driver tries to untranslate the values
+ * after the i8042 translates them.
+ */
+
+       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;
+}
+
+/*
+ * Here we try to reset everything back to a state in which the BIOS will be
+ * able to talk to the hardware when rebooting.
+ */
+
+void i8042_controller_cleanup(void)
+{
+
+/*
+ * Reset the controller.
+ */
+
+       if (i8042_reset) {
+               unsigned char param;
+
+               if (i8042_command(&param, I8042_CMD_CTL_TEST))
+                       printk(KERN_ERR "i8042.c: i8042 controller reset timeout.\n");
+       }
+
+/*
+ * 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");
+
+/*
+ * Reset anything that is connected to the ports if the ports
+ * are enabled in the original config.
+ */
+
+       if (i8042_kbd_values.exists)
+               i8042_kbd_write(&i8042_kbd_port, 0xff);
+
+       if (i8042_aux_values.exists)
+               i8042_aux_write(&i8042_aux_port, 0xff);
+}
+
+/*
+ * i8042_check_aux() applies as much paranoia as it can at detecting
+ * the presence of an AUX interface.
+ */
+
+static int __init i8042_check_aux(struct i8042_values *values, struct serio *port)
+{
+       unsigned char param;
+
+       i8042_flush();
+
+/*
+ * Internal loopback test - filters out AT-type i8042's
+ */
+
+       param = 0x5a;
+
+       if (i8042_command(&param, I8042_CMD_AUX_LOOP) || param != 0xa5)
+               return -1;
+
+/*
+ * External connection test - filters out AT-soldered PS/2 i8042's
+ */
+
+       if (i8042_command(&param, I8042_CMD_AUX_TEST) || param)
+               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))
+               return -1;      
+
+       if (i8042_command(&param, I8042_CMD_AUX_TEST) || param) {
+
+/*
+ * We've got an old AMI i8042 with 'Bad Cache' commands.
+ */
+
+               i8042_command(&param, I8042_CMD_AUX_ENABLE);
+               return -1;
+       }
+
+       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_values *values, struct serio *port)
+{
+       values->exists = 1;
+       serio_register_port(port);
+       printk(KERN_INFO "serio: i8042 %s port at %#x,%#x irq %d\n",
+               values->name, I8042_DATA_REG, I8042_COMMAND_REG, values->irq);
+
+       return 0;
+}
+
+/*
+ * Module init and cleanup functions.
+ */
+
+void __init i8042_setup(char *str, int *ints)
+{
+       if (!strcmp(str, "i8042_reset=1"))
+               i8042_reset = 1;
+       if (!strcmp(str, "i8042_noaux=1"))
+               i8042_noaux = 1;
+       if (!strcmp(str, "i8042_unlock=1"))
+               i8042_unlock = 1;
+       if (!strcmp(str, "i8042_direct=1"))
+               i8042_direct = 1;
+}
+
+/*
+ * Reset the 8042 back to original mode.
+ */
+static int i8042_notify_sys(struct notifier_block *this, unsigned long code,
+                           void *unused)
+{
+        if (code==SYS_DOWN || code==SYS_HALT) 
+               i8042_controller_cleanup();
+        return NOTIFY_DONE;
+}
+
+static struct notifier_block i8042_notifier=
+{
+        i8042_notify_sys,
+        NULL,
+        0
+};
+
+int __init i8042_init(void)
+{
+#ifdef I8042_DEBUG_IO
+       i8042_start = jiffies;
+#endif
+
+       if (i8042_controller_init())
+               return -ENODEV;
+
+       i8042_port_register(&i8042_kbd_values, &i8042_kbd_port);
+               
+       if (!i8042_noaux && !i8042_check_aux(&i8042_aux_values, &i8042_aux_port))
+               i8042_port_register(&i8042_aux_values, &i8042_aux_port);
+
+/* 
+ * 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 !defined(__i386__) && !defined(__sh__) && !defined(__alpha__)
+       request_region(I8042_DATA_REG, 16, "i8042");
+#endif
+       register_reboot_notifier(&i8042_notifier);
+       return 0;
+}
+
+void __exit i8042_exit(void)
+{
+       unregister_reboot_notifier(&i8042_notifier);
+       
+       if (i8042_kbd_values.exists)
+               serio_unregister_port(&i8042_kbd_port);
+
+       if (i8042_aux_values.exists)
+               serio_unregister_port(&i8042_aux_port);
+
+       i8042_controller_cleanup();
+#if !defined(__i386__) && !defined(__sh__) && !defined(__alpha__)
+       release_region(I8042_DATA_REG, 16);
+#endif
+}
+
+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 (file)
index 0000000..569f819
--- /dev/null
@@ -0,0 +1,128 @@
+#ifndef _I8042_H
+#define _I8042_H
+
+/*
+ * $Id: i8042.h,v 1.6 2001/10/05 22:48:09 vojtech Exp $
+ *
+ *  Copyright (c) 1999-2001 Vojtech Pavlik
+ */
+
+/*
+ * 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
+ */
+
+/*
+ * If you want to reset your i8042 upon boot, define this.
+ */
+
+#undef I8042_RESET
+
+/*
+ * If you want to trace all the i/o the i8042 module does for
+ * debugging purposes, define this.
+ */
+
+#undef I8042_DEBUG_IO
+
+/*
+ * On most PC based systems the keyboard IRQ is 1.
+ */
+
+#define I8042_KBD_IRQ CONFIG_I8042_KBD_IRQ 
+
+/*
+ * On most PC based systems the aux port IRQ is 12. There are exceptions,
+ * though. Unfortunately IRQ probing is not possible without touching
+ * the device attached to the port.
+ */
+
+#define I8042_AUX_IRQ CONFIG_I8042_AUX_IRQ
+
+/*
+ * 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
+
+/*
+ * Register numbers.
+ */
+
+#define I8042_COMMAND_REG      CONFIG_I8042_REG_BASE + 4       
+#define I8042_STATUS_REG       CONFIG_I8042_REG_BASE + 4       
+#define I8042_DATA_REG         CONFIG_I8042_REG_BASE   
+
+/*
+ * 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_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
+
+/*
+ * Return codes.
+ */
+
+#define I8042_RET_CTL_TEST     0x55
+
+/*
+ * Expected maximum internal i8042 buffer size. This is used for flushing
+ * the i8042 buffers. 32 should be more than enough.
+ */
+
+#define I8042_BUFFER_SIZE      32
+
+#endif /* _I8042_H */
diff --git a/drivers/input/serio/parkbd.c b/drivers/input/serio/parkbd.c
new file mode 100644 (file)
index 0000000..18e8c51
--- /dev/null
@@ -0,0 +1,203 @@
+/*
+ * $Id: parkbd.c,v 1.10 2002/03/13 10:09:20 vojtech Exp $
+ *
+ *  Copyright (c) 1999-2001 Vojtech Pavlik
+ */
+
+/*
+ *  Parallel port to Keyboard port adapter 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/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");
+
+MODULE_PARM(parkbd, "1i");
+MODULE_PARM(parkbd_mode, "1i");
+
+#define PARKBD_CLOCK   0x01    /* Strobe & Ack */
+#define PARKBD_DATA    0x02    /* AutoFd & Busy */
+
+static int parkbd = 0;
+static int parkbd_mode = SERIO_8042;
+
+static int parkbd_buffer = 0;
+static int parkbd_counter = 0;
+static int parkbd_last = 0;
+static int parkbd_writing = 0;
+static unsigned long parkbd_start = 0;
+
+static struct pardevice *parkbd_dev;
+
+static char parkbd_name[] = "PARKBD AT/XT keyboard adapter";
+static char parkbd_phys[32];
+
+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 int parkbd_open(struct serio *port)
+{
+       return 0;
+}
+
+static void parkbd_close(struct serio *port)
+{
+}
+
+static struct serio parkbd_port =
+{
+       write:  parkbd_write,
+       open:   parkbd_open,
+       close:  parkbd_close,
+       name:   parkbd_name,
+       phys:   parkbd_phys,
+};
+
+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) {
+                       if (parkbd_port.dev)
+                               parkbd_port.dev->interrupt(&parkbd_port, (parkbd_buffer >> (2 - parkbd_mode)) & 0xff, 0);
+               }
+       }
+
+       parkbd_last = jiffies;
+}
+
+static int parkbd_getport(void)
+{
+       struct parport *pp;
+
+       if (parkbd < 0) {
+               printk(KERN_ERR "parkbd: no port specified\n");
+               return -ENODEV;
+       }
+
+       for (pp = parport_enumerate(); pp != NULL && (parkbd > 0); pp = pp->next) parkbd--;
+
+       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);
+
+       if (!parkbd_dev)
+               return -ENODEV;
+
+       if (parport_claim(parkbd_dev)) {
+               parport_unregister_device(parkbd_dev);
+               return -EBUSY;
+       }
+
+       parkbd_start = jiffies;
+
+       return 0;
+}
+
+
+int __init parkbd_init(void)
+{
+       if (parkbd_getport()) return -1;
+       parkbd_writelines(3);
+       parkbd_port.type = parkbd_mode;
+
+       sprintf(parkbd_phys, "%s/serio0", parkbd_dev->port->name);
+
+       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;
+}
+
+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/rpckbd.c b/drivers/input/serio/rpckbd.c
new file mode 100644 (file)
index 0000000..a90879b
--- /dev/null
@@ -0,0 +1,117 @@
+/*
+ * $Id: rpckbd.c,v 1.7 2001/09/25 10:12:07 vojtech Exp $
+ *
+ *  Copyright (c) 2000-2001 Vojtech Pavlik
+ *
+ *  Based on the work of:
+ *     unknown author
+ */
+
+/*
+ * 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/init.h>
+#include <linux/serio.h>
+
+#include <asm/irq.h>
+#include <asm/hardware.h>
+#include <asm/io.h>
+#include <asm/iomd.h>
+#include <asm/system.h>
+
+MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>");
+MODULE_DESCRIPTION("Acorn RiscPC PS/2 keyboard controller driver");
+MODULE_LICENSE("GPL");
+
+static inline void rpckbd_write(unsigned char val)
+{
+       while(!(inb(IOMD_KCTRL) & (1 << 7)));
+       outb(val, IOMD_KARTTX);
+}
+
+static struct serio rpckbd_port =
+{
+       type:   SERIO_8042,
+       write:  rpckbd_write,
+       name:   "RiscPC PS/2 kbd port",
+       phys:   "rpckbd/serio0",
+};
+
+static void rpckbd_rx(int irq, void *dev_id, struct pt_regs *regs)
+{
+       kbd_pt_regs = regs;
+
+       while (inb(IOMD_KCTRL) & (1 << 5))
+               if (rpckbd_port.dev)
+                                rpckbd_port.dev->interrupt(&rpckbd_port, inb(IOMD_KARTRX), 0);
+
+}
+
+static void rpckbd_tx(int irq, void *dev_id, struct pt_regs *regs)
+{
+}
+
+static int __init rpckbd_init(void)
+{
+       unsigned long flags;
+
+       /* Reset the keyboard state machine. */
+       outb(0, IOMD_KCTRL);
+       outb(8, IOMD_KCTRL);
+
+       save_flags_cli(flags);
+
+       if (request_irq(IRQ_KEYBOARDRX, rpckbd_rx, 0, "rpckbd", NULL) != 0) {
+               printk(KERN_ERR "rpckbd.c: Could not allocate keyboard receive IRQ!\n")
+               return -EBUSY;
+       }
+
+       if (request_irq(IRQ_KEYBOARDTX, rpckbd_tx, 0, "rpckbd", NULL) != 0) {
+               printk(KERN_ERR "rpckbd.c: Could not allocate keyboard transmit IRQ!\n")
+               free_irq(IRQ_KEYBOARDRX, NULL);
+               return -EBUSY;
+       }
+
+       disable_irq(IRQ_KEYBOARDTX);
+       (void)IOMD_KARTRX;
+
+       restore_flags(flags);
+
+       register_serio_port(&rpckbd_port);
+       printk(KERN_INFO "serio: RiscPC PS/2 kbd port irq %d %d\n", IRQ_KEYBOARDRX, IRQ_KEYBOARDTX);
+
+       return 0;
+}
+
+static void __exit rpckbd_exit(void)
+{
+       free_irq(IRQ_KEYBOARDRX, NULL);
+       free_irq(IRQ_KEYBOARDTX, NULL); 
+
+       unregister_serio_port(&rpckbd_port);
+}
+
+module_init(rpckbd_init);
+module_exit(rpckbd_exit);