Commit b65e5bd5 authored by Vojtech Pavlik's avatar Vojtech Pavlik

Add new serio modules for PS/2 AUX/KBD.

parent 07b8fb25
...@@ -12,6 +12,18 @@ CONFIG_SERIO ...@@ -12,6 +12,18 @@ CONFIG_SERIO
The module will be called serio.o. If you want to compile it 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>. 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 CONFIG_SERIO_SERPORT
Say Y here if you plan to use an input device (mouse, joystick, Say Y here if you plan to use an input device (mouse, joystick,
tablet, 6dof) that communicates over the RS232 serial (COM) port. tablet, 6dof) that communicates over the RS232 serial (COM) port.
...@@ -24,3 +36,38 @@ CONFIG_SERIO_SERPORT ...@@ -24,3 +36,38 @@ CONFIG_SERIO_SERPORT
inserted in and removed from the running kernel whenever you want). 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 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>. 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>.
...@@ -4,4 +4,16 @@ ...@@ -4,4 +4,16 @@
tristate 'Serial i/o support' CONFIG_SERIO 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 ' 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
...@@ -9,7 +9,11 @@ export-objs := serio.o ...@@ -9,7 +9,11 @@ export-objs := serio.o
# Each configuration option enables a list of files. # Each configuration option enables a list of files.
obj-$(CONFIG_SERIO) += serio.o 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_SERPORT) += serport.o
obj-$(CONFIG_SERIO_CT82C710) += ct82c710.o
obj-$(CONFIG_SERIO_RPCKBD) += rpckbd.o
# The global Rules.make. # The global Rules.make.
......
/*
* $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);
/*
* $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);
#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 */
/*
* $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);
/*
* $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);
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment