Skip to content

Commit 76edfcf

Browse files
committed
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: Maxime Ripard <[email protected]> Reviewed-by: Benjamin Tissoires <[email protected]> Acked-by: Benjamin Tissoires <[email protected]> Signed-off-by: Douglas Anderson <[email protected]> Link: https://patchwork.freedesktop.org/patch/msgid/20230727101636.v4.10.I962bb462ede779005341c49320740ed95810021d@changeid
1 parent 96a37bf commit 76edfcf

File tree

1 file changed

+46
-4
lines changed

1 file changed

+46
-4
lines changed

drivers/hid/i2c-hid/i2c-hid-core.c

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,9 @@ struct i2c_hid {
110110

111111
struct i2chid_ops *ops;
112112
struct drm_panel_follower panel_follower;
113+
struct work_struct panel_follower_prepare_work;
113114
bool is_panel_follower;
115+
bool prepare_work_finished;
114116
};
115117

116118
static const struct i2c_hid_quirks {
@@ -1062,26 +1064,65 @@ static int __do_i2c_hid_core_initial_power_up(struct i2c_hid *ihid)
10621064
return ret;
10631065
}
10641066

1065-
static int i2c_hid_core_panel_prepared(struct drm_panel_follower *follower)
1067+
static void ihid_core_panel_prepare_work(struct work_struct *work)
10661068
{
1067-
struct i2c_hid *ihid = container_of(follower, struct i2c_hid, panel_follower);
1069+
struct i2c_hid *ihid = container_of(work, struct i2c_hid,
1070+
panel_follower_prepare_work);
10681071
struct hid_device *hid = ihid->hid;
1072+
int ret;
10691073

10701074
/*
10711075
* hid->version is set on the first power up. If it's still zero then
10721076
* this is the first power on so we should perform initial power up
10731077
* steps.
10741078
*/
10751079
if (!hid->version)
1076-
return __do_i2c_hid_core_initial_power_up(ihid);
1080+
ret = __do_i2c_hid_core_initial_power_up(ihid);
1081+
else
1082+
ret = i2c_hid_core_resume(ihid);
10771083

1078-
return i2c_hid_core_resume(ihid);
1084+
if (ret)
1085+
dev_warn(&ihid->client->dev, "Power on failed: %d\n", ret);
1086+
else
1087+
WRITE_ONCE(ihid->prepare_work_finished, true);
1088+
1089+
/*
1090+
* The work APIs provide a number of memory ordering guarantees
1091+
* including one that says that memory writes before schedule_work()
1092+
* are always visible to the work function, but they don't appear to
1093+
* guarantee that a write that happened in the work is visible after
1094+
* cancel_work_sync(). We'll add a write memory barrier here to match
1095+
* with i2c_hid_core_panel_unpreparing() to ensure that our write to
1096+
* prepare_work_finished is visible there.
1097+
*/
1098+
smp_wmb();
1099+
}
1100+
1101+
static int i2c_hid_core_panel_prepared(struct drm_panel_follower *follower)
1102+
{
1103+
struct i2c_hid *ihid = container_of(follower, struct i2c_hid, panel_follower);
1104+
1105+
/*
1106+
* Powering on a touchscreen can be a slow process. Queue the work to
1107+
* the system workqueue so we don't block the panel's power up.
1108+
*/
1109+
WRITE_ONCE(ihid->prepare_work_finished, false);
1110+
schedule_work(&ihid->panel_follower_prepare_work);
1111+
1112+
return 0;
10791113
}
10801114

10811115
static int i2c_hid_core_panel_unpreparing(struct drm_panel_follower *follower)
10821116
{
10831117
struct i2c_hid *ihid = container_of(follower, struct i2c_hid, panel_follower);
10841118

1119+
cancel_work_sync(&ihid->panel_follower_prepare_work);
1120+
1121+
/* Match with ihid_core_panel_prepare_work() */
1122+
smp_rmb();
1123+
if (!READ_ONCE(ihid->prepare_work_finished))
1124+
return 0;
1125+
10851126
return i2c_hid_core_suspend(ihid, true);
10861127
}
10871128

@@ -1173,6 +1214,7 @@ int i2c_hid_core_probe(struct i2c_client *client, struct i2chid_ops *ops,
11731214

11741215
init_waitqueue_head(&ihid->wait);
11751216
mutex_init(&ihid->reset_lock);
1217+
INIT_WORK(&ihid->panel_follower_prepare_work, ihid_core_panel_prepare_work);
11761218

11771219
/* we need to allocate the command buffer without knowing the maximum
11781220
* size of the reports. Let's use HID_MIN_BUFFER_SIZE, then we do the

0 commit comments

Comments
 (0)