Commit 76edfcf4 authored by Douglas Anderson's avatar Douglas Anderson

HID: i2c-hid: Do panel follower work on the system_wq

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.
Reviewed-by: default avatarMaxime Ripard <mripard@kernel.org>
Reviewed-by: default avatarBenjamin Tissoires <bentiss@kernel.org>
Acked-by: default avatarBenjamin Tissoires <bentiss@kernel.org>
Signed-off-by: default avatarDouglas Anderson <dianders@chromium.org>
Link: https://patchwork.freedesktop.org/patch/msgid/20230727101636.v4.10.I962bb462ede779005341c49320740ed95810021d@changeid
parent 96a37bfd
...@@ -110,7 +110,9 @@ struct i2c_hid { ...@@ -110,7 +110,9 @@ struct i2c_hid {
struct i2chid_ops *ops; struct i2chid_ops *ops;
struct drm_panel_follower panel_follower; struct drm_panel_follower panel_follower;
struct work_struct panel_follower_prepare_work;
bool is_panel_follower; bool is_panel_follower;
bool prepare_work_finished;
}; };
static const struct i2c_hid_quirks { static const struct i2c_hid_quirks {
...@@ -1062,10 +1064,12 @@ static int __do_i2c_hid_core_initial_power_up(struct i2c_hid *ihid) ...@@ -1062,10 +1064,12 @@ static int __do_i2c_hid_core_initial_power_up(struct i2c_hid *ihid)
return ret; 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; struct hid_device *hid = ihid->hid;
int ret;
/* /*
* hid->version is set on the first power up. If it's still zero then * 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) ...@@ -1073,15 +1077,52 @@ static int i2c_hid_core_panel_prepared(struct drm_panel_follower *follower)
* steps. * steps.
*/ */
if (!hid->version) if (!hid->version)
return __do_i2c_hid_core_initial_power_up(ihid); ret = __do_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) static int i2c_hid_core_panel_unpreparing(struct drm_panel_follower *follower)
{ {
struct i2c_hid *ihid = container_of(follower, struct i2c_hid, panel_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, true); return i2c_hid_core_suspend(ihid, true);
} }
...@@ -1173,6 +1214,7 @@ int i2c_hid_core_probe(struct i2c_client *client, struct i2chid_ops *ops, ...@@ -1173,6 +1214,7 @@ int i2c_hid_core_probe(struct i2c_client *client, struct i2chid_ops *ops,
init_waitqueue_head(&ihid->wait); init_waitqueue_head(&ihid->wait);
mutex_init(&ihid->reset_lock); 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 /* 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 * size of the reports. Let's use HID_MIN_BUFFER_SIZE, then we do the
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment