summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWendly Li <wendlyli@google.com>2022-02-15 15:37:02 +0000
committerWendly Li <wendlyli@google.com>2022-02-15 17:31:58 +0000
commitb6096bc9122860432ebf55fc7538518d99a429a6 (patch)
tree2f78ed8b980b91b5262f3adf0e2df39ef52d8939
parente286e1f6629672757861e310ec65f10181be865e (diff)
downloadgoodix_touch-b6096bc9122860432ebf55fc7538518d99a429a6.tar.gz
Add touch power manager
Bug: 214118711 Test: Build succeeded Signed-off-by: Wendly Li <wendlyli@google.com> Change-Id: Id9a404e0535da3f8a0ea6aab282c6889c74d967f
-rw-r--r--Kbuild3
-rw-r--r--Kconfig5
-rw-r--r--touch_pm.c263
-rw-r--r--touch_pm.h82
4 files changed, 352 insertions, 1 deletions
diff --git a/Kbuild b/Kbuild
index c8e9396..8df601b 100644
--- a/Kbuild
+++ b/Kbuild
@@ -11,4 +11,5 @@ goodix_brl_touch-objs += \
goodix_ts_tools.o \
goodix_ts_utils.o \
goodix_ts_proc.o \
- touch_apis.o
+ touch_apis.o \
+ touch_pm.o
diff --git a/Kconfig b/Kconfig
index 7ab73b3..692694e 100644
--- a/Kconfig
+++ b/Kconfig
@@ -19,3 +19,8 @@ config TOUCHSCREEN_GOODIX_BRL_SPI
Say Y here if the touchscreen is connected via SPI bus.
endif
+
+config TOUCHSCREEN_PM
+ bool "touch power manager"
+ help
+ Say Y here if the touchscreen is connected via SPI bus.
diff --git a/touch_pm.c b/touch_pm.c
new file mode 100644
index 0000000..87c7997
--- /dev/null
+++ b/touch_pm.c
@@ -0,0 +1,263 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Sysfs APIs for Google Pixel devices.
+ *
+ * Copyright 2022 Google LLC.
+ */
+
+#include "touch_pm.h"
+
+struct drm_connector *tpm_get_bridge_connector(struct drm_bridge *bridge)
+{
+ struct drm_connector *connector;
+ struct drm_connector_list_iter conn_iter;
+
+ drm_connector_list_iter_begin(bridge->dev, &conn_iter);
+ drm_for_each_connector_iter(connector, &conn_iter)
+ {
+ if (connector->encoder == bridge->encoder)
+ break;
+ }
+ drm_connector_list_iter_end(&conn_iter);
+ return connector;
+}
+
+static bool tpm_bridge_is_lp_mode(struct drm_connector *connector)
+{
+ if (connector && connector->state) {
+ struct exynos_drm_connector_state *s =
+ to_exynos_connector_state(connector->state);
+ return s->exynos_mode.is_lp_mode;
+ }
+ return false;
+}
+
+static void tpm_panel_bridge_enable(struct drm_bridge *bridge)
+{
+ struct touch_pm *tpm =
+ container_of(bridge, struct touch_pm, panel_bridge);
+
+ pr_debug("%s\n", __func__);
+ if (!tpm->is_panel_lp_mode)
+ tpm_lock_wakelock(tpm, TPM_WAKELOCK_TYPE_SCREEN_ON);
+}
+
+static void tpm_panel_bridge_disable(struct drm_bridge *bridge)
+{
+ struct touch_pm *tpm =
+ container_of(bridge, struct touch_pm, panel_bridge);
+
+ if (bridge->encoder && bridge->encoder->crtc) {
+ const struct drm_crtc_state *crtc_state =
+ bridge->encoder->crtc->state;
+
+ if (drm_atomic_crtc_effectively_active(crtc_state))
+ return;
+ }
+
+ pr_debug("%s\n", __func__);
+ tpm_unlock_wakelock(tpm, TPM_WAKELOCK_TYPE_SCREEN_ON);
+}
+
+static void tpm_panel_bridge_mode_set(struct drm_bridge *bridge,
+ const struct drm_display_mode *mode,
+ const struct drm_display_mode *adjusted_mode)
+{
+ struct touch_pm *tpm =
+ container_of(bridge, struct touch_pm, panel_bridge);
+
+ pr_debug("%s\n", __func__);
+
+ if (!tpm->connector || !tpm->connector->state) {
+ pr_info("%s: Get bridge connector.\n", __func__);
+ tpm->connector = tpm_get_bridge_connector(bridge);
+ }
+
+ tpm->is_panel_lp_mode = tpm_bridge_is_lp_mode(tpm->connector);
+ if (tpm->is_panel_lp_mode)
+ tpm_unlock_wakelock(tpm, TPM_WAKELOCK_TYPE_SCREEN_ON);
+ else
+ tpm_lock_wakelock(tpm, TPM_WAKELOCK_TYPE_SCREEN_ON);
+}
+
+static const struct drm_bridge_funcs panel_bridge_funcs = {
+ .enable = tpm_panel_bridge_enable,
+ .disable = tpm_panel_bridge_disable,
+ .mode_set = tpm_panel_bridge_mode_set,
+};
+
+static int tpm_register_panel_bridge(struct touch_pm *tpm)
+{
+#ifdef CONFIG_OF
+ tpm->panel_bridge.of_node = tpm->of_node;
+#endif
+ tpm->panel_bridge.funcs = &panel_bridge_funcs;
+ drm_bridge_add(&tpm->panel_bridge);
+ return 0;
+}
+
+static void tpm_unregister_panel_bridge(struct drm_bridge *bridge)
+{
+ struct drm_bridge *node;
+
+ drm_bridge_remove(bridge);
+
+ if (!bridge->dev) /* not attached */
+ return;
+
+ drm_modeset_lock(&bridge->dev->mode_config.connection_mutex, NULL);
+ list_for_each_entry(node, &bridge->encoder->bridge_chain,
+ chain_node) if (node == bridge)
+ {
+ if (bridge->funcs->detach)
+ bridge->funcs->detach(bridge);
+ list_del(&bridge->chain_node);
+ break;
+ }
+ drm_modeset_unlock(&bridge->dev->mode_config.connection_mutex);
+ bridge->dev = NULL;
+}
+
+int tpm_lock_wakelock(struct touch_pm *tpm, enum tpm_wakelock_type lock)
+{
+ int ret = 0;
+
+ mutex_lock(&tpm->lock_mutex);
+
+ if (tpm->locks & lock) {
+ dev_dbg(&tpm->pdev->dev,
+ "unexpectedly lock: locks=0x%04X, lock=0x%04X\n",
+ tpm->locks, lock);
+ mutex_unlock(&tpm->lock_mutex);
+ return -EINVAL;
+ }
+
+ /*
+ * IRQs can only keep the bus active. IRQs received while the
+ * bus is transferred to AOC should be ignored.
+ */
+ if (lock == TPM_WAKELOCK_TYPE_IRQ && tpm->locks == 0) {
+ mutex_unlock(&tpm->lock_mutex);
+ return -EAGAIN;
+ }
+
+ tpm->locks |= lock;
+
+ /* Complete or cancel any outstanding transitions */
+ cancel_work_sync(&tpm->suspend_work);
+ cancel_work_sync(&tpm->resume_work);
+ if (tpm->pwr_state != TPM_PWR_ON)
+ queue_work(tpm->event_wq, &tpm->resume_work);
+
+ mutex_unlock(&tpm->lock_mutex);
+
+ /*
+ * When triggering a wake, wait up to one second to resume. SCREEN_ON
+ * and IRQ references do not need to wait.
+ */
+ if (lock != TPM_WAKELOCK_TYPE_SCREEN_ON &&
+ lock != TPM_WAKELOCK_TYPE_IRQ) {
+ wait_for_completion_timeout(&tpm->bus_resumed, HZ);
+ if (tpm->pwr_state != TPM_PWR_ON) {
+ dev_err(&tpm->pdev->dev,
+ "Failed to wake the touch bus.\n");
+ ret = -ETIMEDOUT;
+ }
+ }
+
+ return ret;
+}
+
+int tpm_unlock_wakelock(struct touch_pm *tpm, enum tpm_wakelock_type lock)
+{
+ int ret = 0;
+
+ mutex_lock(&tpm->lock_mutex);
+
+ if (!(tpm->locks & lock)) {
+ dev_dbg(&tpm->pdev->dev,
+ "unexpectedly unlock: locks=0x%04X, lock=0x%04X\n",
+ tpm->locks, lock);
+ mutex_unlock(&tpm->lock_mutex);
+ return -EINVAL;
+ }
+
+ tpm->locks &= ~lock;
+
+ /* Complete or cancel any outstanding transitions */
+ cancel_work_sync(&tpm->suspend_work);
+ cancel_work_sync(&tpm->resume_work);
+ if (tpm->locks == 0 && tpm->pwr_state == TPM_PWR_ON)
+ queue_work(tpm->event_wq, &tpm->suspend_work);
+
+ mutex_unlock(&tpm->lock_mutex);
+
+ return ret;
+}
+
+static void tpm_suspend_work(struct work_struct *work)
+{
+ struct touch_pm *tpm =
+ container_of(work, struct touch_pm, suspend_work);
+
+ /* exit directly if device is already in suspend state */
+ if (tpm->pwr_state == TPM_PWR_OFF)
+ return;
+ tpm->pwr_state = TPM_PWR_OFF;
+
+ reinit_completion(&tpm->bus_resumed);
+ if (tpm->suspend) {
+ tpm->suspend(&tpm->pdev->dev);
+ }
+}
+
+static void tpm_resume_work(struct work_struct *work)
+{
+ struct touch_pm *tpm = container_of(work, struct touch_pm, resume_work);
+
+ /* exit directly if device isn't in suspend state */
+ if (tpm->pwr_state == TPM_PWR_ON)
+ return;
+ tpm->pwr_state = TPM_PWR_ON;
+
+ if (tpm->resume) {
+ tpm->resume(&tpm->pdev->dev);
+ }
+ complete_all(&tpm->bus_resumed);
+}
+
+int tpm_register_notification(struct touch_pm *tpm)
+{
+ int ret = 0;
+
+ tpm->pwr_state = TPM_PWR_ON;
+ tpm->locks = 0;
+ tpm->event_wq = alloc_workqueue(
+ "tpm_wq", WQ_UNBOUND | WQ_HIGHPRI | WQ_CPU_INTENSIVE, 1);
+ if (!tpm->event_wq) {
+ dev_err(&tpm->pdev->dev, "Cannot create work thread\n");
+ ret = -ENOMEM;
+ goto err_alloc_workqueue;
+ }
+
+ mutex_init(&tpm->lock_mutex);
+ INIT_WORK(&tpm->suspend_work, tpm_suspend_work);
+ INIT_WORK(&tpm->resume_work, tpm_resume_work);
+
+ init_completion(&tpm->bus_resumed);
+ complete_all(&tpm->bus_resumed);
+
+ tpm_register_panel_bridge(tpm);
+ return ret;
+
+err_alloc_workqueue:
+ return ret;
+}
+
+int tpm_unregister_notification(struct touch_pm *tpm)
+{
+ tpm_unregister_panel_bridge(&tpm->panel_bridge);
+ tpm->resume = NULL;
+ tpm->suspend = NULL;
+ return 0;
+}
diff --git a/touch_pm.h b/touch_pm.h
new file mode 100644
index 0000000..556ff02
--- /dev/null
+++ b/touch_pm.h
@@ -0,0 +1,82 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Sysfs APIs for Google Pixel devices.
+ *
+ * Copyright 2022 Google LLC.
+ */
+
+#ifndef _TOUCH_PM_H_
+#define _TOUCH_PM_H_
+
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <samsung/exynos_drm_connector.h>
+#include <samsung/panel/panel-samsung-drv.h>
+
+enum tpm_power_state {
+ TPM_PWR_OFF = 0,
+ TPM_PWR_ON,
+};
+
+/**
+ * @brief: wakelock type.
+ */
+enum tpm_wakelock_type {
+ TPM_WAKELOCK_TYPE_SCREEN_ON = 0x0001,
+ TPM_WAKELOCK_TYPE_IRQ = 0x0002,
+ TPM_WAKELOCK_TYPE_FW_UPDATE = 0x0004,
+ TPM_WAKELOCK_TYPE_SYSFS = 0x0008,
+ TPM_WAKELOCK_TYPE_FORCE_ACTIVE = 0x0010,
+ TPM_WAKELOCK_TYPE_BUGREPORT = 0x0020,
+};
+
+struct touch_pm {
+ /* PLatform device driver */
+ struct platform_device *pdev;
+#ifdef CONFIG_OF
+ struct device_node *of_node;
+#endif
+
+ struct work_struct suspend_work;
+ struct work_struct resume_work;
+ struct workqueue_struct *event_wq;
+ struct completion bus_resumed;
+
+ u32 locks;
+ struct mutex lock_mutex;
+
+ /* flags */
+ int pwr_state;
+
+ struct drm_bridge panel_bridge;
+ struct drm_connector *connector;
+ bool is_panel_lp_mode;
+
+ /* Specific function pointer to resume the device from suspend state.
+ *
+ * @param
+ * [ in] dev: an instance of device
+ *
+ * @return
+ * on success, 0; otherwise, negative value on error.
+ */
+ int (*resume)(struct device *dev);
+
+ /* Specific function pointer to put device into suspend state.
+ *
+ * @param
+ * [ in] dev: an instance of device
+ *
+ * @return
+ * on success, 0; otherwise, negative value on error.
+ */
+ int (*suspend)(struct device *dev);
+};
+
+extern int tpm_lock_wakelock(struct touch_pm *tpm, enum tpm_wakelock_type lock);
+extern int tpm_unlock_wakelock(
+ struct touch_pm *tpm, enum tpm_wakelock_type lock);
+extern int tpm_register_notification(struct touch_pm *tpm);
+extern int tpm_unregister_notification(struct touch_pm *tpm);
+
+#endif