]> git.hungrycats.org Git - linux/commitdiff
[PATCH] Input: implement resume methods
authorDmitry Torokhov <dtor_core@ameritech.net>
Thu, 18 Dec 2003 13:34:47 +0000 (05:34 -0800)
committerDmitry Torokhov <dtor_core@ameritech.net>
Thu, 18 Dec 2003 13:34:47 +0000 (05:34 -0800)
- 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

index 8e03d14fcd618f6af84618e62394b580e922fd33..b603a4db0efe8ad4d9ae80d6aabc7ae52bbe6735 100644 (file)
 
 #include <linux/delay.h>
 #include <linux/module.h>
+#include <linux/moduleparam.h>
 #include <linux/interrupt.h>
 #include <linux/ioport.h>
 #include <linux/config.h>
 #include <linux/reboot.h>
 #include <linux/init.h>
+#include <linux/sysdev.h>
+#include <linux/pm.h>
 #include <linux/serio.h>
 
 #include <asm/io.h>
@@ -25,19 +28,23 @@ MODULE_AUTHOR("Vojtech Pavlik <vojtech@suse.cz>");
 MODULE_DESCRIPTION("i8042 keyboard and mouse controller driver");
 MODULE_LICENSE("GPL");
 
-MODULE_PARM(i8042_noaux, "1i");
-MODULE_PARM(i8042_nomux, "1i");
-MODULE_PARM(i8042_unlock, "1i");
-MODULE_PARM(i8042_reset, "1i");
-MODULE_PARM(i8042_direct, "1i");
-MODULE_PARM(i8042_dumbkbd, "1i");
+static unsigned int i8042_noaux;
+module_param(i8042_noaux, bool, 0);
 
-static int i8042_reset;
-static int i8042_noaux;
-static int i8042_nomux;
-static int i8042_unlock;
-static int i8042_direct;
-static int i8042_dumbkbd;
+static unsigned int i8042_nomux;
+module_param(i8042_nomux, bool, 0);
+
+static unsigned int i8042_unlock;
+module_param(i8042_unlock, bool, 0);
+
+static unsigned int i8042_reset;
+module_param(i8042_reset, bool, 0);
+
+static unsigned int i8042_direct;
+module_param(i8042_direct, bool, 0);
+
+static unsigned int i8042_dumbkbd;
+module_param(i8042_dumbkbd, bool, 0);
 
 #undef DEBUG
 #include "i8042.h"
@@ -59,6 +66,9 @@ static struct serio i8042_aux_port;
 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;
 
 /*
@@ -214,16 +224,41 @@ static int i8042_aux_write(struct serio *port, unsigned char c)
 }
 
 /*
- * i8042_open() is called when a port is open by the higher layer.
- * It allocates the interrupt and enables it in the chip.
+ * i8042_activate_port() enables port on a chip.
  */
 
-static int i8042_open(struct serio *port)
+static int i8042_activate_port(struct serio *port)
 {
        struct i8042_values *values = port->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 irq, void *dev_id, struct pt_regs *regs)
 }
 
 /*
- * 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(&param, I8042_CMD_CTL_TEST)) {
-                       printk(KERN_ERR "i8042.c: i8042 controller self test timeout.\n");
-                       return -1;
-               }
-
-               if (param != I8042_RET_CTL_TEST) {
-                       printk(KERN_ERR "i8042.c: i8042 controller selftest failed. (%#x != %#x)\n",
-                                param, I8042_RET_CTL_TEST);
-                       return -1;
-               }
-       }
-
 /*
- * Save the CTR for restoral on unload / reboot.
+ * 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(&param, I8042_CMD_AUX_LOOP) || param != 0x0f)
                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");
+       param = 0x56;
+       if (i8042_command(&param, I8042_CMD_AUX_LOOP) || param != 0xa9)
                return -1;
-       }
+       param = 0xa4;
+       if (i8042_command(&param, I8042_CMD_AUX_LOOP) || param == 0x5b)
+               return -1;
+
+       if (mux_version)
+               *mux_version = ~param;
 
        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.
+ * i8042_enable_mux_ports enables 4 individual AUX ports after
+ * the controller has been switched into Multiplexed mode
  */
 
-void i8042_controller_cleanup(void)
+static int i8042_enable_mux_ports(struct i8042_values *values)
 {
+       unsigned char param;
        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.
+ * Disable all muxed ports by disabling AUX.
  */
 
-       if (i8042_reset) {
-               unsigned char param;
+       i8042_ctr |= I8042_CTR_AUXDIS;
+       i8042_ctr &= ~I8042_CTR_AUXINT;
 
-               if (i8042_command(&param, I8042_CMD_CTL_TEST))
-                       printk(KERN_ERR "i8042.c: i8042 controller reset timeout.\n");
+       if (i8042_command(&i8042_ctr, I8042_CMD_CTL_WCTR)) {
+               printk(KERN_ERR "i8042.c: Failed to disable AUX port, can't use MUX.\n");
+               return -1;
        }
 
 /*
- * Restore the original control register setting.
+ * Enable all muxed ports.
  */
 
-       i8042_ctr = i8042_initial_ctr;
-
-       if (i8042_command(&i8042_ctr, I8042_CMD_CTL_WCTR))
-               printk(KERN_WARNING "i8042.c: Can't restore CTR.\n");
+       for (i = 0; i < 4; i++) {
+               i8042_command(&param, I8042_CMD_MUX_PFX + i);
+               i8042_command(&param, I8042_CMD_AUX_ENABLE);
+       }
 
+       return 0;
 }
 
+
 /*
  * i8042_check_mux() checks whether the controller supports the PS/2 Active
  * Multiplexing specification by Synaptics, Phoenix, Insyde and
@@ -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(&param, I8042_CMD_AUX_LOOP) || param != 0x0f)
-               return -1;
-       param = 0x56;
-       if (i8042_command(&param, I8042_CMD_AUX_LOOP) || param != 0xa9)
-               return -1;
-       param = 0xa4;
-       if (i8042_command(&param, 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(&param, I8042_CMD_MUX_PFX + i);
-               i8042_command(&param, 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 i8042_values *values)
        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(struct i8042_values *values, struct serio
        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(&param, I8042_CMD_CTL_TEST)) {
+                       printk(KERN_ERR "i8042.c: i8042 controller self test timeout.\n");
+                       return -1;
+               }
+
+               if (param != I8042_RET_CTL_TEST) {
+                       printk(KERN_ERR "i8042.c: i8042 controller selftest failed. (%#x != %#x)\n",
+                                param, I8042_RET_CTL_TEST);
+                       return -1;
+               }
+       }
+
+/*
+ * Save the CTR for restoral on unload / reboot.
+ */
+
+       if (i8042_command(&i8042_ctr, I8042_CMD_CTL_RCTR)) {
+               printk(KERN_ERR "i8042.c: Can't read CTR while initializing i8042.\n");
+               return -1;
+       }
+
+       i8042_initial_ctr = i8042_ctr;
+
+/*
+ * Disable the keyboard interface and interrupt.
+ */
+
+       i8042_ctr |= I8042_CTR_KBDDIS;
+       i8042_ctr &= ~I8042_CTR_KBDINT;
+
+/*
+ * Handle keylock.
+ */
+
+       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(&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");
+
 }
-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_notifier=
         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();