Turning on an i2c-hid device can be a slow process. This is why
i2c-hid devices use PROBE_PREFER_ASYNCHRONOUS. Unfortunately, when
we're a panel follower the i2c-hid power up sequence now blocks the
power on of the panel. Let's fix that by scheduling the work on the
system_wq.

Signed-off-by: Douglas Anderson <diand...@chromium.org>
---

Changes in v2:
- ihid_core_panel_prepare_work() is now static.
- Improve documentation for smp_wmb().

 drivers/hid/i2c-hid/i2c-hid-core.c | 50 +++++++++++++++++++++++++++---
 1 file changed, 46 insertions(+), 4 deletions(-)

diff --git a/drivers/hid/i2c-hid/i2c-hid-core.c 
b/drivers/hid/i2c-hid/i2c-hid-core.c
index 368db3ae612f..de1a0624be08 100644
--- a/drivers/hid/i2c-hid/i2c-hid-core.c
+++ b/drivers/hid/i2c-hid/i2c-hid-core.c
@@ -110,7 +110,9 @@ struct i2c_hid {
 
        struct i2chid_ops       *ops;
        struct drm_panel_follower panel_follower;
+       struct work_struct      panel_follower_prepare_work;
        bool                    is_panel_follower;
+       bool                    prepare_work_finished;
 };
 
 static const struct i2c_hid_quirks {
@@ -1062,10 +1064,12 @@ static int i2c_hid_core_initial_power_up(struct i2c_hid 
*ihid)
        return ret;
 }
 
-static int i2c_hid_core_panel_prepared(struct drm_panel_follower *follower)
+static void ihid_core_panel_prepare_work(struct work_struct *work)
 {
-       struct i2c_hid *ihid = container_of(follower, struct i2c_hid, 
panel_follower);
+       struct i2c_hid *ihid = container_of(work, struct i2c_hid,
+                                           panel_follower_prepare_work);
        struct hid_device *hid = ihid->hid;
+       int ret;
 
        /*
         * hid->version is set on the first power up. If it's still zero then
@@ -1073,15 +1077,52 @@ static int i2c_hid_core_panel_prepared(struct 
drm_panel_follower *follower)
         * steps.
         */
        if (!hid->version)
-               return i2c_hid_core_initial_power_up(ihid);
+               ret = i2c_hid_core_initial_power_up(ihid);
+       else
+               ret = i2c_hid_core_resume(ihid);
 
-       return i2c_hid_core_resume(ihid);
+       if (ret)
+               dev_warn(&ihid->client->dev, "Power on failed: %d\n", ret);
+       else
+               WRITE_ONCE(ihid->prepare_work_finished, true);
+
+       /*
+        * The work APIs provide a number of memory ordering guarantees
+        * including one that says that memory writes before schedule_work()
+        * are always visible to the work function, but they don't appear to
+        * guarantee that a write that happened in the work is visible after
+        * cancel_work_sync(). We'll add a write memory barrier here to match
+        * with i2c_hid_core_panel_unpreparing() to ensure that our write to
+        * prepare_work_finished is visible there.
+        */
+       smp_wmb();
+}
+
+static int i2c_hid_core_panel_prepared(struct drm_panel_follower *follower)
+{
+       struct i2c_hid *ihid = container_of(follower, struct i2c_hid, 
panel_follower);
+
+       /*
+        * Powering on a touchscreen can be a slow process. Queue the work to
+        * the system workqueue so we don't block the panel's power up.
+        */
+       WRITE_ONCE(ihid->prepare_work_finished, false);
+       schedule_work(&ihid->panel_follower_prepare_work);
+
+       return 0;
 }
 
 static int i2c_hid_core_panel_unpreparing(struct drm_panel_follower *follower)
 {
        struct i2c_hid *ihid = container_of(follower, struct i2c_hid, 
panel_follower);
 
+       cancel_work_sync(&ihid->panel_follower_prepare_work);
+
+       /* Match with ihid_core_panel_prepare_work() */
+       smp_rmb();
+       if (!READ_ONCE(ihid->prepare_work_finished))
+               return 0;
+
        return i2c_hid_core_suspend(ihid);
 }
 
@@ -1124,6 +1165,7 @@ int i2c_hid_core_probe(struct i2c_client *client, struct 
i2chid_ops *ops,
 
        init_waitqueue_head(&ihid->wait);
        mutex_init(&ihid->reset_lock);
+       INIT_WORK(&ihid->panel_follower_prepare_work, 
ihid_core_panel_prepare_work);
 
        /* we need to allocate the command buffer without knowing the maximum
         * size of the reports. Let's use HID_MIN_BUFFER_SIZE, then we do the
-- 
2.41.0.162.gfafddb0af9-goog

Reply via email to