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

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
Loading
Loading
Loading
Loading
+46 −4
Original line number Diff line number Diff line
@@ -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 __do_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 __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)
{
	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);
}

@@ -1173,6 +1214,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