[PATCH v4 3/4] HID: Add DRM panel follower function

From: Allen_Lin
Date: Wed Apr 17 2024 - 03:42:33 EST


Add DRM panel follower[1] to trigger suspend/resume due to TDDI nature,
TP need to follow panel power sequence.

[1]:
https://lore.kernel.org/all/20230727171750.633410-1-dianders@xxxxxxxxxxxx

Signed-off-by: Allen_Lin <allencl_lin@xxxxxxxxxxx>
---
drivers/hid/hid-himax.c | 292 +++++++++++++++++++++++++++++++++++++---
drivers/hid/hid-himax.h | 10 ++
2 files changed, 286 insertions(+), 16 deletions(-)

diff --git a/drivers/hid/hid-himax.c b/drivers/hid/hid-himax.c
index f8a417e07f0c..7b0ee48acfae 100644
--- a/drivers/hid/hid-himax.c
+++ b/drivers/hid/hid-himax.c
@@ -8,6 +8,7 @@
#include "hid-himax.h"

static int himax_chip_init(struct himax_ts_data *ts);
+static int himax_platform_init(struct himax_ts_data *ts);
static void himax_ts_work(struct himax_ts_data *ts);

/**
@@ -1145,7 +1146,6 @@ static int himax_hid_probe(struct himax_ts_data *ts)

hid = ts->hid;
if (hid) {
- dev_warn(ts->dev, "%s: hid device already exist!\n", __func__);
hid_destroy_device(hid);
hid = NULL;
}
@@ -1501,6 +1501,154 @@ static int himax_load_config(struct himax_ts_data *ts)
return -EINVAL;
}

+/**
+ * himax_ap_notify_fw_suspend() - Notify the FW of AP suspend status
+ * @ts: Himax touch screen data
+ * @suspend: Suspend status, true for suspend, false for resume
+ *
+ * This function is used to notify the FW of AP suspend status. It will write
+ * the suspend status to the DSRAM and read the status back to check if the
+ * status is written successfully. If IC is powered off when suspend, this
+ * function will only be used when resume.
+ *
+ * Return: None
+ */
+static void himax_ap_notify_fw_suspend(struct himax_ts_data *ts, bool suspend)
+{
+ int ret;
+ u32 retry_cnt;
+ const u32 retry_limit = 10;
+ union himax_dword_data rdata, data;
+
+ if (suspend)
+ data.dword = cpu_to_le32(HIMAX_DSRAM_DATA_AP_NOTIFY_FW_SUSPEND);
+ else
+ data.dword = cpu_to_le32(HIMAX_DSRAM_DATA_AP_NOTIFY_FW_RESUME);
+
+ for (retry_cnt = 0; retry_cnt < retry_limit; retry_cnt++) {
+ ret = himax_mcu_register_write(ts, HIMAX_DSRAM_ADDR_AP_NOTIFY_FW_SUSPEND,
+ data.byte, 4);
+ if (ret) {
+ dev_err(ts->dev, "%s: write suspend status failed!\n", __func__);
+ return;
+ }
+ usleep_range(1000, 1100);
+ ret = himax_mcu_register_read(ts, HIMAX_DSRAM_ADDR_AP_NOTIFY_FW_SUSPEND,
+ rdata.byte, 4);
+ if (ret) {
+ dev_err(ts->dev, "%s: read suspend status failed!\n", __func__);
+ return;
+ }
+
+ if (rdata.dword == data.dword)
+ break;
+ }
+}
+
+/**
+ * himax_resume_proc() - Chip resume procedure of touch screen
+ * @ts: Himax touch screen data
+ *
+ * This function is used to resume the touch screen. It will call the
+ * himax_ap_notify_fw_suspend() to notify the FW of AP resume status.
+ *
+ * Return: None
+ */
+static void himax_resume_proc(struct himax_ts_data *ts)
+{
+ himax_ap_notify_fw_suspend(ts, false);
+}
+
+/**
+ * himax_chip_suspend() - Suspend the touch screen
+ * @ts: Himax touch screen data
+ *
+ * This function is used to suspend the touch screen. It will disable the
+ * interrupt and set the reset pin to activate state. Remove the HID at
+ * the end, to prevent stuck finger when resume.
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+static int himax_chip_suspend(struct himax_ts_data *ts)
+{
+ himax_int_enable(ts, false);
+ gpiod_set_value(ts->pdata.gpiod_rst, 1);
+ himax_power_set(ts, false);
+ himax_hid_remove(ts);
+
+ return 0;
+}
+
+/**
+ * himax_chip_resume() - Setup flags, I/O and resume
+ * @ts: Himax touch screen data
+ *
+ * This function is used to resume the touch screen. It will set the resume
+ * success flag to false, and disable reset pin. Then call the himax_resume_proc()
+ * to process detailed resume procedure.
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+static int himax_chip_resume(struct himax_ts_data *ts)
+{
+ if (himax_power_set(ts, true))
+ return -EIO;
+ gpiod_set_value(ts->pdata.gpiod_rst, 0);
+ himax_resume_proc(ts);
+ himax_hid_probe(ts);
+ himax_int_enable(ts, true);
+
+ return 0;
+}
+
+/**
+ * himax_suspend() - Suspend the touch screen
+ * @dev: Device structure
+ *
+ * Wrapper function for himax_chip_suspend() to be called by the PM or
+ * the DRM panel notifier.
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+static int himax_suspend(struct device *dev)
+{
+ struct himax_ts_data *ts = dev_get_drvdata(dev);
+
+ if (!ts->initialized) {
+ dev_err(ts->dev, "%s: init not ready, skip!\n", __func__);
+ return -ECANCELED;
+ }
+ himax_chip_suspend(ts);
+
+ return 0;
+}
+
+/**
+ * himax_resume() - Resume the touch screen
+ * @dev: Device structure
+ *
+ * Wrapper function for himax_chip_resume() to be called by the PM or
+ * the DRM panel notifier.
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+static int himax_resume(struct device *dev)
+{
+ int ret;
+ struct himax_ts_data *ts = dev_get_drvdata(dev);
+
+ if (!ts->initialized) {
+ if (himax_chip_init(ts))
+ return -ECANCELED;
+ }
+
+ ret = himax_chip_resume(ts);
+ if (ret < 0)
+ dev_err(ts->dev, "%s: resume failed!\n", __func__);
+
+ return ret;
+}
+
/**
* himax_chip_init() - Initialize the Himax touch screen
* @ts: Himax touch screen data
@@ -1531,6 +1679,130 @@ static int himax_chip_init(struct himax_ts_data *ts)
return 0;
}

+/**
+ * __himax_initial_power_up() - Initial power up of the Himax touch screen
+ * @ts: Himax touch screen data
+ *
+ * This function is used to perform the initial power up sequence of the Himax
+ * touch screen for DRM panel notifier.
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+static int __himax_initial_power_up(struct himax_ts_data *ts)
+{
+ int ret;
+
+ ret = himax_platform_init(ts);
+ if (ret) {
+ dev_err(ts->dev, "%s: platform init failed\n", __func__);
+ return ret;
+ }
+
+ ret = hx83102j_chip_detect(ts);
+ if (ret) {
+ dev_err(ts->dev, "%s: IC detect failed\n", __func__);
+ return ret;
+ }
+
+ ret = himax_chip_init(ts);
+ if (ret) {
+ dev_err(ts->dev, "%s: chip init failed\n", __func__);
+ return ret;
+ }
+ ts->probe_finish = true;
+
+ return 0;
+}
+
+/**
+ * himax_panel_prepared() - Panel prepared callback
+ * @follower: DRM panel follower
+ *
+ * This function is called when the panel is prepared. It will call the
+ * __himax_initial_power_up() when the probe is not finished which means
+ * the first time driver start. Otherwise, it will call the himax_resume()
+ * to performed resume process.
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+static int himax_panel_prepared(struct drm_panel_follower *follower)
+{
+ struct himax_platform_data *pdata =
+ container_of(follower, struct himax_platform_data, panel_follower);
+ struct himax_ts_data *ts = container_of(pdata, struct himax_ts_data, pdata);
+
+ if (!ts->probe_finish)
+ return __himax_initial_power_up(ts);
+ else
+ return himax_resume(ts->dev);
+}
+
+/**
+ * himax_panel_unpreparing() - Panel unpreparing callback
+ * @follower: DRM panel follower
+ *
+ * This function is called when the panel is unpreparing. It will call the
+ * himax_suspend() to perform the suspend process.
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+static int himax_panel_unpreparing(struct drm_panel_follower *follower)
+{
+ struct himax_platform_data *pdata =
+ container_of(follower, struct himax_platform_data, panel_follower);
+ struct himax_ts_data *ts = container_of(pdata, struct himax_ts_data, pdata);
+
+ return himax_suspend(ts->dev);
+}
+
+/* Panel follower function table */
+static const struct drm_panel_follower_funcs himax_panel_follower_funcs = {
+ .panel_prepared = himax_panel_prepared,
+ .panel_unpreparing = himax_panel_unpreparing,
+};
+
+/**
+ * himax_register_panel_follower() - Register the panel follower
+ * @ts: Himax touch screen data
+ *
+ * This function is used to register the panel follower. It will set the
+ * pdata.is_panel_follower to true and register the panel follower.
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+static int himax_register_panel_follower(struct himax_ts_data *ts)
+{
+ struct device *dev = ts->dev;
+
+ ts->pdata.is_panel_follower = true;
+ ts->pdata.panel_follower.funcs = &himax_panel_follower_funcs;
+
+ if (device_can_wakeup(dev)) {
+ dev_warn(ts->dev, "Can't wakeup if following panel");
+ device_set_wakeup_capable(dev, false);
+ }
+
+ return drm_panel_add_follower(dev, &ts->pdata.panel_follower);
+}
+
+/**
+ * himax_initial_power_up() - Initial power up of the Himax touch screen
+ * @ts: Himax touch screen data
+ *
+ * This function checks if the device is a panel follower and calls
+ * himax_register_panel_follower() if it is. Otherwise, it calls
+ * __himax_initial_power_up().
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+static int himax_initial_power_up(struct himax_ts_data *ts)
+{
+ if (drm_is_panel_follower(ts->dev))
+ return himax_register_panel_follower(ts);
+ else
+ return __himax_initial_power_up(ts);
+}
+
/**
* himax_platform_deinit() - Deinitialize the platform related settings
* @ts: Pointer to the himax_ts_data structure
@@ -1660,25 +1932,13 @@ static int himax_spi_drv_probe(struct spi_device *spi)
ts->initialized = false;
ts->ic_boot_done = false;

- ret = himax_platform_init(ts);
- if (ret) {
- dev_err(ts->dev, "%s: platform init failed\n", __func__);
- return ret;
- }
-
- ret = hx83102j_chip_detect(ts);
+ ret = himax_initial_power_up(ts);
if (ret) {
- dev_err(ts->dev, "%s: IC detect failed\n", __func__);
- return ret;
+ dev_err(ts->dev, "%s: initial power up failed\n", __func__);
+ return -ENODEV;
}

- ret = himax_chip_init(ts);
- if (ret < 0)
- return ret;
- ts->probe_finish = true;
-
return ret;
- himax_platform_deinit(ts);
}

/**
diff --git a/drivers/hid/hid-himax.h b/drivers/hid/hid-himax.h
index 26b47be49714..45dc3e6283ae 100644
--- a/drivers/hid/hid-himax.h
+++ b/drivers/hid/hid-himax.h
@@ -8,6 +8,7 @@
#ifndef __HID_HIMAX_83102J_H__
#define __HID_HIMAX_83102J_H__

+#include <drm/drm_panel.h>
#include <linux/delay.h>
#include <linux/hid.h>
#include <linux/interrupt.h>
@@ -69,6 +70,11 @@
#define HIMAX_AHB_CMD_INCR4 0x10
#define HIMAX_AHB_CMD_INCR4_ADD_4_BYTE 0x01
#define HIMAX_AHB_CMD_LEAVE_SAFE_MODE 0x0000
+/* DSRAM flag addresses */
+#define HIMAX_DSRAM_ADDR_AP_NOTIFY_FW_SUSPEND 0x10007fd0
+/* dsram flag data */
+#define HIMAX_DSRAM_DATA_AP_NOTIFY_FW_SUSPEND 0xa55aa55a
+#define HIMAX_DSRAM_DATA_AP_NOTIFY_FW_RESUME 0x00000000
/* hx83102j-specific register/dsram flags/data */
#define HIMAX_HX83102J_REG_ADDR_TCON_RST 0x80020004
/* hardware register addresses */
@@ -219,13 +225,17 @@ struct himax_hid_desc {

/**
* struct himax_platform_data - Platform data holder
+ * @is_panel_follower: Is panel follower enabled
* @vccd_supply: VCCD supply
+ * @panel_follower: DRM panel follower
* @gpiod_rst: GPIO reset
*
* This structure is used to hold the platform related data.
*/
struct himax_platform_data {
+ bool is_panel_follower;
struct regulator *vccd_supply;
+ struct drm_panel_follower panel_follower;
struct gpio_desc *gpiod_rst;
};

--
2.34.1