mkbp: Clear buffered keyscans on resume
authorSimon Glass <sjg@chromium.org>
Wed, 10 Oct 2012 21:39:29 +0000 (14:39 -0700)
committerGerrit <chrome-bot@google.com>
Tue, 16 Oct 2012 18:57:07 +0000 (11:57 -0700)
Since the EC buffers key scans, and will do so while in suspend, we should
clear these out on resume if the EC was not a wake device.

BUG=chrome-os-partner:14523
TEST=manual
Login to snow, close lid to suspend, open, see there are no keypresses
Using a magnet, put snow into suspend, type some keys, remove magnet, see
that there are keys.
Using dbus, suspend, resume and see that there are no keys:
$ dbus-send --type=signal --system /org/chromium/PowerManager org.chromium.PowerManager.RequestSuspend

The resume takes a considerable time over i2c if the buffer is full,
since we have no command to clear the keyboard buffer. For example, for
8 keys it takes 55ms:

chromeos-ec-i2c 4-001e: Discarded 16 keyscan(s) in 55000us

Change-Id: Ib315a62e64bb93a51dc8ff9bdcb413940bbb6309
Signed-off-by: Simon Glass <sjg@chromium.org>
Reviewed-on: https://gerrit.chromium.org/gerrit/35198
Reviewed-by: Luigi Semenzato <semenzato@chromium.org>
drivers/input/keyboard/mkbp.c
drivers/mfd/chromeos_ec.c
include/linux/mfd/chromeos_ec.h

index 2dbd193..2cbdb3e 100644 (file)
@@ -42,6 +42,7 @@ struct mkbp_device {
        struct input_dev *idev;
        struct chromeos_ec_device *ec;
        struct notifier_block notifier;
+       struct notifier_block wake_notifier;
 };
 
 
@@ -220,9 +221,21 @@ static void mkbp_process(struct mkbp_device *mkbp_dev,
 static int mkbp_open(struct input_dev *dev)
 {
        struct mkbp_device *mkbp_dev = input_get_drvdata(dev);
+       int ret;
 
-       return blocking_notifier_chain_register(&mkbp_dev->ec->event_notifier,
+       ret = blocking_notifier_chain_register(&mkbp_dev->ec->event_notifier,
                                                &mkbp_dev->notifier);
+       if (ret)
+               return ret;
+       ret = blocking_notifier_chain_register(&mkbp_dev->ec->wake_notifier,
+                                               &mkbp_dev->wake_notifier);
+       if (ret) {
+               blocking_notifier_chain_unregister(
+                       &mkbp_dev->ec->event_notifier, &mkbp_dev->notifier);
+               return ret;
+       }
+
+       return 0;
 }
 
 static void mkbp_close(struct input_dev *dev)
@@ -231,6 +244,14 @@ static void mkbp_close(struct input_dev *dev)
 
        blocking_notifier_chain_unregister(&mkbp_dev->ec->event_notifier,
                                           &mkbp_dev->notifier);
+       blocking_notifier_chain_unregister(&mkbp_dev->ec->wake_notifier,
+                                          &mkbp_dev->wake_notifier);
+}
+
+static int mkbp_get_state(struct mkbp_device *mkbp_dev, uint8_t *kb_state)
+{
+       return mkbp_dev->ec->command_recv(mkbp_dev->ec, EC_CMD_MKBP_STATE,
+                                         kb_state, MKBP_NUM_COLS);
 }
 
 static int mkbp_work(struct notifier_block *nb,
@@ -241,14 +262,47 @@ static int mkbp_work(struct notifier_block *nb,
                                                    notifier);
        uint8_t kb_state[MKBP_NUM_COLS];
 
-       ret = mkbp_dev->ec->command_recv(mkbp_dev->ec, EC_CMD_MKBP_STATE,
-                                        kb_state, MKBP_NUM_COLS);
+       ret = mkbp_get_state(mkbp_dev, kb_state);
        if (ret >= 0)
                mkbp_process(mkbp_dev, kb_state, ret);
 
        return NOTIFY_DONE;
 }
 
+/* On resume, clear any keys in the buffer, crosbug.com/p/14523 */
+static int mkbp_clear_keyboard(struct notifier_block *nb,
+                              unsigned long state, void *_notify)
+{
+       struct mkbp_device *mkbp_dev = container_of(nb, struct mkbp_device,
+                                                   wake_notifier);
+       uint8_t old_state[MKBP_NUM_COLS];
+       uint8_t new_state[MKBP_NUM_COLS];
+       unsigned long duration;
+       int i, ret;
+
+       /*
+        * Keep reading until we see that the scan state does not change.
+        * That indicates that we are done.
+        *
+        * Assume that the EC keyscan buffer is at most 32 deep.
+        *
+        * TODO(sjg@chromium.org): Add EC command to clear keyscan FIFO.
+        */
+       duration = jiffies;
+       ret = mkbp_get_state(mkbp_dev, new_state);
+       for (i = 1; !ret && i < 32; i++) {
+               memcpy(old_state, new_state, sizeof(old_state));
+               ret = mkbp_get_state(mkbp_dev, new_state);
+               if (0 == memcmp(old_state, new_state, sizeof(old_state)))
+                       break;
+       }
+       duration = jiffies - duration;
+       dev_info(mkbp_dev->dev, "Discarded %d keyscan(s) in %dus\n", i,
+               jiffies_to_usecs(duration));
+
+       return 0;
+}
+
 static int __devinit mkbp_probe(struct platform_device *pdev)
 {
        struct chromeos_ec_device *ec = dev_get_drvdata(pdev->dev.parent);
@@ -270,6 +324,7 @@ static int __devinit mkbp_probe(struct platform_device *pdev)
 
        mkbp_dev->ec = ec;
        mkbp_dev->notifier.notifier_call = mkbp_work;
+       mkbp_dev->wake_notifier.notifier_call = mkbp_clear_keyboard;
        mkbp_dev->dev = dev;
 
        idev->name = ec->get_name(ec);
index 2314d59..80a2199 100644 (file)
@@ -143,6 +143,7 @@ int __devinit cros_ec_register(struct chromeos_ec_device *ec_dev)
        int err = 0;
 
        BLOCKING_INIT_NOTIFIER_HEAD(&ec_dev->event_notifier);
+       BLOCKING_INIT_NOTIFIER_HEAD(&ec_dev->wake_notifier);
 
        ec_dev->command_send = cros_ec_command_send;
        ec_dev->command_recv = cros_ec_command_recv;
@@ -229,6 +230,16 @@ int cros_ec_suspend(struct chromeos_ec_device *ec_dev)
 
 int cros_ec_resume(struct chromeos_ec_device *ec_dev)
 {
+       /*
+        * When the EC is not a wake source, then it could not have caused the
+        * resume, so we should do the resume processing. This may clear the
+        * EC's key scan buffer, for example. If the EC is a wake source (e.g.
+        * the lid is open and the user might press a key to wake) then we
+        * don't want to do resume processing (key scan buffer should be
+        * preserved).
+        */
+       if (!ec_dev->wake_enabled)
+               blocking_notifier_call_chain(&ec_dev->wake_notifier, 1, ec_dev);
        enable_irq(ec_dev->irq);
 
        if (ec_dev->wake_enabled) {
index 68571e0..1114746 100644 (file)
@@ -91,6 +91,11 @@ struct chromeos_ec_device {
        bool wake_enabled;
        struct blocking_notifier_head event_notifier;
 
+       /*
+        * Indicate to sub-drivers that we have woken up from resume but we
+        * were not a wakeup source.
+        */
+       struct blocking_notifier_head wake_notifier;
 };
 
 /**