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>
---

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

diff --git a/drivers/hid/i2c-hid/i2c-hid-core.c 
b/drivers/hid/i2c-hid/i2c-hid-core.c
index f1bb89377e8d..800f0dc6f6cf 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 @@ int i2c_hid_core_initial_power_up(struct i2c_hid *ihid)
        return ret;
 }
 
-int i2c_hid_core_panel_prepared(struct drm_panel_follower *follower)
+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,44 @@ 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);
+
+       /* Match with i2c_hid_core_panel_unpreparing() */
+       smp_wmb();
+}
+
+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;
 }
 
 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 +1157,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.40.1.698.g37aff9b760-goog

Reply via email to