/*
* i8042 keyboard and mouse controller driver for Linux
*
- * Copyright (c) 1999-2002 Vojtech Pavlik
+ * Copyright (c) 1999-2004 Vojtech Pavlik
*/
/*
#include <linux/sysdev.h>
#include <linux/pm.h>
#include <linux/serio.h>
+#include <linux/err.h>
#include <asm/io.h>
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(dumbkbd, "Disable the AUX Loopback command while probing for the AUX port");
+
__obsolete_setup("i8042_noaux");
__obsolete_setup("i8042_nomux");
__obsolete_setup("i8042_unlock");
unsigned char irqen;
unsigned char exists;
signed char mux;
- unsigned char *name;
- unsigned char *phys;
+ char name[8];
+};
+
+static struct i8042_values i8042_kbd_values = {
+ .disable = I8042_CTR_KBDDIS,
+ .irqen = I8042_CTR_KBDINT,
+ .mux = -1,
+ .name = "KBD",
+};
+
+static struct i8042_values i8042_aux_values = {
+ .disable = I8042_CTR_AUXDIS,
+ .irqen = I8042_CTR_AUXINT,
+ .mux = -1,
+ .name = "AUX",
};
-static struct serio i8042_kbd_port;
-static struct serio i8042_aux_port;
+static struct i8042_values i8042_mux_values[I8042_NUM_MUX_PORTS];
+
+static struct serio *i8042_kbd_port;
+static struct serio *i8042_aux_port;
+static struct serio *i8042_mux_port[I8042_NUM_MUX_PORTS];
static unsigned char i8042_initial_ctr;
static unsigned char i8042_ctr;
static unsigned char i8042_mux_open;
static unsigned char i8042_mux_present;
-static unsigned char i8042_sysdev_initialized;
static struct pm_dev *i8042_pm_dev;
-struct timer_list i8042_timer;
+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
/*
* 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)
spin_lock_irqsave(&i8042_lock, flags);
while ((i8042_read_status() & I8042_STR_OBF) && (i++ < I8042_BUFFER_SIZE)) {
+ udelay(50);
data = i8042_read_data();
dbg("%02x <- i8042 (flush, %s)", data,
i8042_read_status() & I8042_STR_AUXDATA ? "aux" : "kbd");
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();
static int i8042_aux_write(struct serio *port, unsigned char c)
{
- struct i8042_values *values = port->driver;
+ struct i8042_values *values = port->port_data;
int retval;
/*
static int i8042_activate_port(struct serio *port)
{
- struct i8042_values *values = port->driver;
+ struct i8042_values *values = port->port_data;
i8042_flush();
static int i8042_open(struct serio *port)
{
- struct i8042_values *values = port->driver;
+ struct i8042_values *values = port->port_data;
if (values->mux != -1)
if (i8042_mux_open++)
static void i8042_close(struct serio *port)
{
- struct i8042_values *values = port->driver;
+ struct i8042_values *values = port->port_data;
if (values->mux != -1)
if (--i8042_mux_open)
i8042_flush();
}
-/*
- * Structures for registering the devices in the serio.c module.
- */
-
-static struct i8042_values i8042_kbd_values = {
- .irqen = I8042_CTR_KBDINT,
- .disable = I8042_CTR_KBDDIS,
- .name = "KBD",
- .mux = -1,
-};
-
-static struct serio i8042_kbd_port =
-{
- .type = SERIO_8042_XL,
- .write = i8042_kbd_write,
- .open = i8042_open,
- .close = i8042_close,
- .driver = &i8042_kbd_values,
- .name = "i8042 Kbd Port",
- .phys = I8042_KBD_PHYS_DESC,
-};
-
-static struct i8042_values i8042_aux_values = {
- .irqen = I8042_CTR_AUXINT,
- .disable = I8042_CTR_AUXDIS,
- .name = "AUX",
- .mux = -1,
-};
-
-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 = I8042_AUX_PHYS_DESC,
-};
-
-static struct i8042_values i8042_mux_values[4];
-static struct serio i8042_mux_port[4];
-static char i8042_mux_names[4][32];
-static char i8042_mux_short[4][16];
-static char i8042_mux_phys[4][32];
-
/*
* i8042_interrupt() is the most important function in this driver -
* it handles the interrupts from the i8042, and sends incoming bytes
unsigned long flags;
unsigned char str, data = 0;
unsigned int dfl;
+ unsigned int aux_idx;
int ret;
mod_timer(&i8042_timer, jiffies + I8042_POLL_PERIOD);
goto out;
}
- dfl = ((str & I8042_STR_PARITY) ? SERIO_PARITY : 0) |
- ((str & I8042_STR_TIMEOUT) ? SERIO_TIMEOUT : 0);
-
- if (i8042_mux_values[0].exists && (str & I8042_STR_AUXDATA)) {
+ 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; break;
- case 0xff: dfl = SERIO_PARITY; break;
+ case 0xfe: dfl = SERIO_TIMEOUT; data = 0xfe; break;
+ case 0xff: dfl = SERIO_PARITY; data = 0xfe; break;
}
- data = 0xfe;
- } else dfl = 0;
+ }
+
+ aux_idx = (str >> 6) & 3;
dbg("%02x <- i8042 (interrupt, aux%d, %d%s%s)",
- data, (str >> 6), irq,
+ data, aux_idx, irq,
dfl & SERIO_PARITY ? ", bad parity" : "",
dfl & SERIO_TIMEOUT ? ", timeout" : "");
- serio_interrupt(i8042_mux_port + ((str >> 6) & 3), data, dfl, regs);
+ if (likely(i8042_mux_values[aux_idx].exists))
+ serio_interrupt(i8042_mux_port[aux_idx], data, dfl, regs);
+ last_str = str;
+ last_transmit = jiffies;
goto irq_ret;
}
+ dfl = ((str & I8042_STR_PARITY) ? SERIO_PARITY : 0) |
+ ((str & I8042_STR_TIMEOUT) ? SERIO_TIMEOUT : 0);
+
dbg("%02x <- i8042 (interrupt, %s, %d%s%s)",
data, (str & I8042_STR_AUXDATA) ? "aux" : "kbd", irq,
dfl & SERIO_PARITY ? ", bad parity" : "",
dfl & SERIO_TIMEOUT ? ", timeout" : "");
- if (i8042_aux_values.exists && (str & I8042_STR_AUXDATA)) {
- serio_interrupt(&i8042_aux_port, data, dfl, regs);
- goto irq_ret;
- }
-
- if (!i8042_kbd_values.exists)
- goto irq_ret;
- serio_interrupt(&i8042_kbd_port, data, dfl, regs);
+ if (str & I8042_STR_AUXDATA) {
+ if (likely(i8042_aux_values.exists))
+ serio_interrupt(i8042_aux_port, data, dfl, regs);
+ } else {
+ if (likely(i8042_kbd_values.exists))
+ serio_interrupt(i8042_kbd_port, data, dfl, regs);
+ }
irq_ret:
ret = 1;
if (i8042_command(¶m, I8042_CMD_AUX_LOOP) || param != 0xa9)
return -1;
param = 0xa4;
- if (i8042_command(¶m, I8042_CMD_AUX_LOOP) || param == 0x5b) {
-
-/*
- * Do another loop test with the 0x5a value. Doing anything else upsets
- * Profusion/ServerWorks OSB4 chipsets.
- */
-
- param = 0x5a;
- i8042_command(¶m, I8042_CMD_AUX_LOOP);
+ if (i8042_command(¶m, I8042_CMD_AUX_LOOP) || param == 0x5b)
return -1;
- }
if (mux_version)
*mux_version = ~param;
* registers it, and reports to the user.
*/
-static int __init i8042_port_register(struct i8042_values *values, struct serio *port)
+static int __init i8042_port_register(struct serio *port)
{
+ struct i8042_values *values = port->port_data;
+
values->exists = 1;
i8042_ctr &= ~values->disable;
static int i8042_controller_init(void)
{
+ unsigned long flags;
/*
* Test the i8042. We need to know if it thinks it's working correctly
* 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
* BIOSes.
*/
- if (i8042_direct) {
+ if (i8042_direct)
i8042_ctr &= ~I8042_CTR_XLATE;
- i8042_kbd_port.type = SERIO_8042;
- }
/*
* Write CTR back.
*/
if (i8042_kbd_values.exists)
- serio_cleanup(&i8042_kbd_port);
+ serio_cleanup(i8042_kbd_port);
if (i8042_aux_values.exists)
- serio_cleanup(&i8042_aux_port);
+ serio_cleanup(i8042_aux_port);
- for (i = 0; i < 4; i++)
+ for (i = 0; i < I8042_NUM_MUX_PORTS; i++)
if (i8042_mux_values[i].exists)
- serio_cleanup(i8042_mux_port + i);
+ serio_cleanup(i8042_mux_port[i]);
i8042_controller_reset();
}
* Reconnect anything that was connected to the ports.
*/
- if (i8042_kbd_values.exists && i8042_activate_port(&i8042_kbd_port) == 0)
- serio_reconnect(&i8042_kbd_port);
+ if (i8042_kbd_values.exists && i8042_activate_port(i8042_kbd_port) == 0)
+ serio_reconnect(i8042_kbd_port);
- if (i8042_aux_values.exists && i8042_activate_port(&i8042_aux_port) == 0)
- serio_reconnect(&i8042_aux_port);
+ if (i8042_aux_values.exists && i8042_activate_port(i8042_aux_port) == 0)
+ serio_reconnect(i8042_aux_port);
- for (i = 0; i < 4; i++)
- if (i8042_mux_values[i].exists && i8042_activate_port(i8042_mux_port + i) == 0)
- serio_reconnect(i8042_mux_port + i);
+ for (i = 0; i < I8042_NUM_MUX_PORTS; i++)
+ if (i8042_mux_values[i].exists && i8042_activate_port(i8042_mux_port[i]) == 0)
+ serio_reconnect(i8042_mux_port[i]);
/*
* Restart timer (for polling "stuck" data)
*/
return NOTIFY_DONE;
}
-static struct notifier_block i8042_notifier=
+static struct notifier_block i8042_notifier =
{
i8042_notify_sys,
NULL,
/*
* Suspend/resume handlers for the new PM scheme (driver model)
*/
-static int i8042_suspend(struct sys_device *dev, u32 state)
+static int i8042_suspend(struct device *dev, u32 state, u32 level)
{
- return i8042_controller_suspend();
+ return level == SUSPEND_DISABLE ? i8042_controller_suspend() : 0;
}
-static int i8042_resume(struct sys_device *dev)
+static int i8042_resume(struct device *dev, u32 level)
{
- return i8042_controller_resume();
+ return level == RESUME_ENABLE ? i8042_controller_resume() : 0;
}
-static struct sysdev_class kbc_sysclass = {
- set_kset_name("i8042"),
- .suspend = i8042_suspend,
- .resume = i8042_resume,
-};
+static void i8042_shutdown(struct device *dev)
+{
+ i8042_controller_cleanup();
+}
-static struct sys_device device_i8042 = {
- .id = 0,
- .cls = &kbc_sysclass,
+static struct device_driver i8042_driver = {
+ .name = "i8042",
+ .bus = &platform_bus_type,
+ .suspend = i8042_suspend,
+ .resume = i8042_resume,
+ .shutdown = i8042_shutdown,
};
/*
return 0;
}
-static void __init i8042_init_mux_values(struct i8042_values *values, struct serio *port, int index)
+static struct serio * __init i8042_allocate_kbd_port(void)
+{
+ struct serio *serio;
+
+ serio = kmalloc(sizeof(struct serio), GFP_KERNEL);
+ if (serio) {
+ memset(serio, 0, sizeof(struct serio));
+ serio->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->port_data = &i8042_kbd_values,
+ 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));
+ }
+
+ return serio;
+}
+
+static struct serio * __init i8042_allocate_aux_port(void)
{
- memcpy(port, &i8042_aux_port, sizeof(struct serio));
- memcpy(values, &i8042_aux_values, sizeof(struct i8042_values));
- sprintf(i8042_mux_names[index], "i8042 Aux-%d Port", index);
- sprintf(i8042_mux_phys[index], I8042_MUX_PHYS_DESC, index + 1);
- sprintf(i8042_mux_short[index], "AUX%d", index);
- port->name = i8042_mux_names[index];
- port->phys = i8042_mux_phys[index];
- port->driver = values;
- values->name = i8042_mux_short[index];
- values->mux = index;
+ struct serio *serio;
+
+ serio = kmalloc(sizeof(struct serio), GFP_KERNEL);
+ if (serio) {
+ memset(serio, 0, sizeof(struct serio));
+ serio->type = SERIO_8042;
+ serio->write = i8042_aux_write;
+ serio->open = i8042_open;
+ serio->close = i8042_close;
+ serio->port_data = &i8042_aux_values,
+ 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));
+ }
+
+ return serio;
+}
+
+static struct serio * __init i8042_allocate_mux_port(int index)
+{
+ struct serio *serio;
+ struct i8042_values *values = &i8042_mux_values[index];
+
+ serio = kmalloc(sizeof(struct serio), GFP_KERNEL);
+ if (serio) {
+ *values = i8042_aux_values;
+ snprintf(values->name, sizeof(values->name), "AUX%d", index);
+ values->mux = index;
+
+ memset(serio, 0, sizeof(struct serio));
+ serio->type = SERIO_8042;
+ serio->write = i8042_aux_write;
+ serio->open = i8042_open;
+ serio->close = i8042_close;
+ serio->port_data = values;
+ 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);
+ }
+
+ return serio;
}
int __init i8042_init(void)
{
int i;
+ int err;
dbg_init();
if (i8042_controller_init())
return -ENODEV;
- if (i8042_dumbkbd)
- i8042_kbd_port.write = NULL;
+ err = driver_register(&i8042_driver);
+ if (err)
+ return err;
+
+ i8042_platform_device = platform_device_register_simple("i8042", -1, NULL, 0);
+ if (IS_ERR(i8042_platform_device)) {
+ driver_unregister(&i8042_driver);
+ return PTR_ERR(i8042_platform_device);
+ }
if (!i8042_noaux && !i8042_check_aux(&i8042_aux_values)) {
if (!i8042_nomux && !i8042_check_mux(&i8042_aux_values))
- for (i = 0; i < 4; i++) {
- i8042_init_mux_values(i8042_mux_values + i, i8042_mux_port + i, i);
- i8042_port_register(i8042_mux_values + i, i8042_mux_port + i);
+ for (i = 0; i < I8042_NUM_MUX_PORTS; i++) {
+ i8042_mux_port[i] = i8042_allocate_mux_port(i);
+ if (i8042_mux_port[i])
+ i8042_port_register(i8042_mux_port[i]);
}
- else
- i8042_port_register(&i8042_aux_values, &i8042_aux_port);
+ else {
+ i8042_aux_port = i8042_allocate_aux_port();
+ if (i8042_aux_port)
+ i8042_port_register(i8042_aux_port);
+ }
}
- i8042_port_register(&i8042_kbd_values, &i8042_kbd_port);
+ i8042_kbd_port = i8042_allocate_kbd_port();
+ if (i8042_kbd_port)
+ i8042_port_register(i8042_kbd_port);
mod_timer(&i8042_timer, jiffies + I8042_POLL_PERIOD);
- if (sysdev_class_register(&kbc_sysclass) == 0) {
- if (sysdev_register(&device_i8042) == 0)
- i8042_sysdev_initialized = 1;
- else
- sysdev_class_unregister(&kbc_sysclass);
- }
-
i8042_pm_dev = pm_register(PM_SYS_DEV, PM_SYS_UNKNOWN, i8042_pm_callback);
register_reboot_notifier(&i8042_notifier);
if (i8042_pm_dev)
pm_unregister(i8042_pm_dev);
- if (i8042_sysdev_initialized) {
- sysdev_unregister(&device_i8042);
- sysdev_class_unregister(&kbc_sysclass);
- }
-
i8042_controller_cleanup();
if (i8042_kbd_values.exists)
- serio_unregister_port(&i8042_kbd_port);
+ serio_unregister_port(i8042_kbd_port);
if (i8042_aux_values.exists)
- serio_unregister_port(&i8042_aux_port);
+ serio_unregister_port(i8042_aux_port);
- for (i = 0; i < 4; i++)
+ for (i = 0; i < I8042_NUM_MUX_PORTS; i++)
if (i8042_mux_values[i].exists)
- serio_unregister_port(i8042_mux_port + i);
+ serio_unregister_port(i8042_mux_port[i]);
+
del_timer_sync(&i8042_timer);
+ platform_device_unregister(i8042_platform_device);
+ driver_unregister(&i8042_driver);
+
i8042_platform_exit();
}