From: Dmitry Torokhov - Implement resume methods using serio_reconnect facility - Register i8042 with sysfs - Register i8042 with older PM scheme to restore keyboard and mouse for APM users - Convert parameter handling to the new style - Unregister port not only when there is no free IRQ but also if the port fails to activate. drivers/input/serio/i8042.c | 523 +++++++++++++++++++++++++++----------------- 1 files changed, 321 insertions(+), 202 deletions(-) diff -puN drivers/input/serio/i8042.c~input-03-resume-methods drivers/input/serio/i8042.c --- 25/drivers/input/serio/i8042.c~input-03-resume-methods 2003-12-16 22:47:32.000000000 -0800 +++ 25-akpm/drivers/input/serio/i8042.c 2003-12-16 22:47:32.000000000 -0800 @@ -12,11 +12,14 @@ #include #include +#include #include #include #include #include #include +#include +#include #include #include @@ -25,19 +28,23 @@ MODULE_AUTHOR("Vojtech Pavlik driver; i8042_flush(); + /* + * Enable port again here because it is disabled if we are + * resuming (normally it is enabled already). + */ + i8042_ctr &= ~values->disable; + + i8042_ctr |= values->irqen; + + if (i8042_command(&i8042_ctr, I8042_CMD_CTL_WCTR)) { + i8042_ctr &= ~values->irqen; + return -1; + } + + return 0; +} + + +/* + * i8042_open() is called when a port is open by the higher layer. + * It allocates the interrupt and calls i8042_enable_port. + */ + +static int i8042_open(struct serio *port) +{ + struct i8042_values *values = port->driver; + if (values->mux != -1) if (i8042_mux_open++) return 0; @@ -234,19 +269,16 @@ static int i8042_open(struct serio *port goto irq_fail; } - i8042_ctr |= values->irqen; - - if (i8042_command(&i8042_ctr, I8042_CMD_CTL_WCTR)) { - printk(KERN_ERR "i8042.c: Can't write CTR while opening %s, unregistering the port\n", values->name); - goto ctr_fail; + if (i8042_activate_port(port)) { + printk(KERN_ERR "i8042.c: Can't activate %s, unregistering the port\n", values->name); + goto activate_fail; } i8042_interrupt(0, NULL, NULL); return 0; -ctr_fail: - i8042_ctr &= ~values->irqen; +activate_fail: free_irq(values->irq, i8042_request_irq_cookie); irq_fail: @@ -401,145 +433,78 @@ static irqreturn_t i8042_interrupt(int i } /* - * i8042_controller init initializes the i8042 controller, and, - * most importantly, sets it into non-xlated mode if that's - * desired. + * i8042_enable_mux_mode checks whether the controller has an active + * multiplexor and puts the chip into Multiplexed (as opposed to + * Legacy) mode. */ - -static int __init i8042_controller_init(void) + +static int i8042_enable_mux_mode(struct i8042_values *values, unsigned char *mux_version) { + unsigned char param; /* - * Test the i8042. We need to know if it thinks it's working correctly - * before doing anything else. + * Get rid of bytes in the queue. */ i8042_flush(); - if (i8042_reset) { - - unsigned char param; - - if (i8042_command(¶m, I8042_CMD_CTL_TEST)) { - printk(KERN_ERR "i8042.c: i8042 controller self test timeout.\n"); - return -1; - } - - if (param != I8042_RET_CTL_TEST) { - printk(KERN_ERR "i8042.c: i8042 controller selftest failed. (%#x != %#x)\n", - param, I8042_RET_CTL_TEST); - return -1; - } - } - /* - * Save the CTR for restoral on unload / reboot. + * Internal loopback test - send three bytes, they should come back from the + * mouse interface, the last should be version. Note that we negate mouseport + * command responses for the i8042_check_aux() routine. */ - if (i8042_command(&i8042_ctr, I8042_CMD_CTL_RCTR)) { - printk(KERN_ERR "i8042.c: Can't read CTR while initializing i8042.\n"); + param = 0xf0; + if (i8042_command(¶m, I8042_CMD_AUX_LOOP) || param != 0x0f) + return -1; + param = 0x56; + if (i8042_command(¶m, I8042_CMD_AUX_LOOP) || param != 0xa9) + return -1; + param = 0xa4; + if (i8042_command(¶m, I8042_CMD_AUX_LOOP) || param == 0x5b) return -1; - } - - i8042_initial_ctr = i8042_ctr; - -/* - * Disable the keyboard interface and interrupt. - */ - i8042_ctr |= I8042_CTR_KBDDIS; - i8042_ctr &= ~I8042_CTR_KBDINT; + if (mux_version) + *mux_version = ~param; -/* - * Handle keylock. - */ + return 0; +} - 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"); - } /* - * If the chip is configured into nontranslated mode by the BIOS, don't - * bother enabling translating and be happy. + * i8042_enable_mux_ports enables 4 individual AUX ports after + * the controller has been switched into Multiplexed mode */ - if (~i8042_ctr & I8042_CTR_XLATE) - i8042_direct = 1; - +static int i8042_enable_mux_ports(struct i8042_values *values) +{ + unsigned char param; + int i; /* - * Set nontranslated mode for the kbd interface if requested by an option. - * After this the kbd interface becomes a simple serial in/out, like the aux - * interface is. We don't do this by default, since it can confuse notebook - * BIOSes. + * Disable all muxed ports by disabling AUX. */ - if (i8042_direct) { - i8042_ctr &= ~I8042_CTR_XLATE; - i8042_kbd_port.type = SERIO_8042; - } - -/* - * Write CTR back. - */ + i8042_ctr |= I8042_CTR_AUXDIS; + i8042_ctr &= ~I8042_CTR_AUXINT; if (i8042_command(&i8042_ctr, I8042_CMD_CTL_WCTR)) { - printk(KERN_ERR "i8042.c: Can't write CTR while initializing i8042.\n"); + printk(KERN_ERR "i8042.c: Failed to disable AUX port, can't use MUX.\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) -{ - int i; - - i8042_flush(); - -/* - * Reset anything that is connected to the ports. - */ - - if (i8042_kbd_values.exists) - serio_cleanup(&i8042_kbd_port); - - if (i8042_aux_values.exists) - serio_cleanup(&i8042_aux_port); - - for (i = 0; i < 4; i++) - if (i8042_mux_values[i].exists) - serio_cleanup(i8042_mux_port + i); - -/* - * Reset the controller. + * Enable all muxed ports. */ - if (i8042_reset) { - unsigned char param; - - if (i8042_command(¶m, I8042_CMD_CTL_TEST)) - printk(KERN_ERR "i8042.c: i8042 controller reset timeout.\n"); + for (i = 0; i < 4; i++) { + i8042_command(¶m, I8042_CMD_MUX_PFX + i); + i8042_command(¶m, I8042_CMD_AUX_ENABLE); } -/* - * 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"); - + return 0; } + /* * i8042_check_mux() checks whether the controller supports the PS/2 Active * Multiplexing specification by Synaptics, Phoenix, Insyde and @@ -548,66 +513,31 @@ void i8042_controller_cleanup(void) static int __init i8042_check_mux(struct i8042_values *values) { - unsigned char param; static int i8042_check_mux_cookie; - int i; + unsigned char mux_version; /* * Check if AUX irq is available. */ - if (request_irq(values->irq, i8042_interrupt, SA_SHIRQ, "i8042", &i8042_check_mux_cookie)) return -1; free_irq(values->irq, &i8042_check_mux_cookie); -/* - * Get rid of bytes in the queue. - */ - - i8042_flush(); - -/* - * Internal loopback test - send three bytes, they should come back from the - * mouse interface, the last should be version. Note that we negate mouseport - * command responses for the i8042_check_aux() routine. - */ - - param = 0xf0; - if (i8042_command(¶m, I8042_CMD_AUX_LOOP) || param != 0x0f) - return -1; - param = 0x56; - if (i8042_command(¶m, I8042_CMD_AUX_LOOP) || param != 0xa9) - return -1; - param = 0xa4; - if (i8042_command(¶m, I8042_CMD_AUX_LOOP) || param == 0x5b) + if (i8042_enable_mux_mode(values, &mux_version)) return -1; printk(KERN_INFO "i8042.c: Detected active multiplexing controller, rev %d.%d.\n", - (~param >> 4) & 0xf, ~param & 0xf); - -/* - * Disable all muxed ports by disabling AUX. - */ - - i8042_ctr |= I8042_CTR_AUXDIS; - i8042_ctr &= ~I8042_CTR_AUXINT; + (mux_version >> 4) & 0xf, mux_version & 0xf); - if (i8042_command(&i8042_ctr, I8042_CMD_CTL_WCTR)) + if (i8042_enable_mux_ports(values)) return -1; -/* - * Enable all muxed ports. - */ - - for (i = 0; i < 4; i++) { - i8042_command(¶m, I8042_CMD_MUX_PFX + i); - i8042_command(¶m, I8042_CMD_AUX_ENABLE); - } - + i8042_mux_present = 1; return 0; } + /* * i8042_check_aux() applies as much paranoia as it can at detecting * the presence of an AUX interface. @@ -683,6 +613,7 @@ static int __init i8042_check_aux(struct return 0; } + /* * i8042_port_register() marks the device as existing, * registers it, and reports to the user. @@ -710,52 +641,194 @@ static int __init i8042_port_register(st return 0; } + static void i8042_timer_func(unsigned long data) { i8042_interrupt(0, NULL, NULL); mod_timer(&i8042_timer, jiffies + I8042_POLL_PERIOD); } -#ifndef MODULE -static int __init i8042_setup_reset(char *str) -{ - i8042_reset = 1; - return 1; -} -static int __init i8042_setup_noaux(char *str) -{ - i8042_noaux = 1; - i8042_nomux = 1; - return 1; -} -static int __init i8042_setup_nomux(char *str) -{ - i8042_nomux = 1; - return 1; -} -static int __init i8042_setup_unlock(char *str) + +/* + * i8042_controller init initializes the i8042 controller, and, + * most importantly, sets it into non-xlated mode if that's + * desired. + */ + +static int i8042_controller_init(void) { - i8042_unlock = 1; - return 1; + + if (i8042_noaux) + i8042_nomux = 1; +/* + * 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(¶m, I8042_CMD_CTL_TEST)) { + printk(KERN_ERR "i8042.c: i8042 controller self test timeout.\n"); + return -1; + } + + if (param != I8042_RET_CTL_TEST) { + printk(KERN_ERR "i8042.c: i8042 controller selftest failed. (%#x != %#x)\n", + param, I8042_RET_CTL_TEST); + return -1; + } + } + +/* + * Save the CTR for restoral on unload / reboot. + */ + + if (i8042_command(&i8042_ctr, I8042_CMD_CTL_RCTR)) { + printk(KERN_ERR "i8042.c: Can't read CTR while initializing i8042.\n"); + return -1; + } + + i8042_initial_ctr = i8042_ctr; + +/* + * Disable the keyboard interface and interrupt. + */ + + i8042_ctr |= I8042_CTR_KBDDIS; + i8042_ctr &= ~I8042_CTR_KBDINT; + +/* + * Handle keylock. + */ + + 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"); + } + +/* + * If the chip is configured into nontranslated mode by the BIOS, don't + * bother enabling translating and be happy. + */ + + if (~i8042_ctr & I8042_CTR_XLATE) + i8042_direct = 1; + +/* + * Set nontranslated mode for the kbd interface if requested by an option. + * After this the kbd interface becomes a simple serial in/out, like the aux + * interface is. We don't do this by default, since it can confuse notebook + * BIOSes. + */ + + if (i8042_direct) { + i8042_ctr &= ~I8042_CTR_XLATE; + i8042_kbd_port.type = SERIO_8042; + } + +/* + * 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; } -static int __init i8042_setup_direct(char *str) + + +/* + * 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) { - i8042_direct = 1; - return 1; + int i; + + i8042_flush(); + +/* + * Reset anything that is connected to the ports. + */ + + if (i8042_kbd_values.exists) + serio_cleanup(&i8042_kbd_port); + + if (i8042_aux_values.exists) + serio_cleanup(&i8042_aux_port); + + for (i = 0; i < 4; i++) + if (i8042_mux_values[i].exists) + serio_cleanup(i8042_mux_port + i); + +/* + * Reset the controller. + */ + + if (i8042_reset) { + unsigned char param; + + if (i8042_command(¶m, 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"); + } -static int __init i8042_setup_dumbkbd(char *str) + + +/* + * Here we try to reset everything back to a state in which suspended + */ + +static int i8042_controller_resume(void) { - i8042_dumbkbd = 1; - return 1; + int i; + + if (i8042_controller_init()) { + printk(KERN_ERR "i8042: resume failed\n"); + return -1; + } + + if (i8042_mux_present) + if (i8042_enable_mux_mode(&i8042_aux_values, NULL) || + i8042_enable_mux_ports(&i8042_aux_values)) { + printk(KERN_WARNING "i8042: failed to resume active multiplexor, mouse won't wotk.\n"); + } + +/* + * 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_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); + + return 0; } -__setup("i8042_reset", i8042_setup_reset); -__setup("i8042_noaux", i8042_setup_noaux); -__setup("i8042_nomux", i8042_setup_nomux); -__setup("i8042_unlock", i8042_setup_unlock); -__setup("i8042_direct", i8042_setup_direct); -__setup("i8042_dumbkbd", i8042_setup_dumbkbd); -#endif /* * We need to reset the 8042 back to original mode on system shutdown, @@ -777,6 +850,35 @@ static struct notifier_block i8042_notif 0 }; +/* + * Resume handler for the new PM scheme (driver model) + */ +static int i8042_resume(struct sys_device *dev) +{ + return i8042_controller_resume(); +} + +static struct sysdev_class kbc_sysclass = { + set_kset_name("i8042"), + .resume = i8042_resume, +}; + +static struct sys_device device_i8042 = { + .id = 0, + .cls = &kbc_sysclass, +}; + +/* + * Resume handler for the old PM scheme (APM) + */ +static int i8042_pm_callback(struct pm_dev *dev, pm_request_t request, void *dummy) +{ + if (request == PM_RESUME) + return i8042_controller_resume(); + + return 0; +} + static void __init i8042_init_mux_values(struct i8042_values *values, struct serio *port, int index) { memcpy(port, &i8042_aux_port, sizeof(struct serio)); @@ -825,6 +927,15 @@ int __init i8042_init(void) i8042_timer.function = i8042_timer_func; mod_timer(&i8042_timer, jiffies + I8042_POLL_PERIOD); + if (sysdev_class_register(&kbc_sysclass) == 0) { + if (sys_device_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); return 0; @@ -836,6 +947,14 @@ void __exit i8042_exit(void) unregister_reboot_notifier(&i8042_notifier); + if (i8042_pm_dev) + pm_unregister(i8042_pm_dev); + + if (i8042_sysdev_initialized) { + sys_device_unregister(&device_i8042); + sysdev_class_unregister(&kbc_sysclass); + } + del_timer(&i8042_timer); i8042_controller_cleanup(); _