diff options
author | masonwang <masonwang@google.com> | 2021-08-11 21:05:54 +0800 |
---|---|---|
committer | masonwang <masonwang@google.com> | 2021-08-13 20:39:21 +0800 |
commit | 8679f5c537224dfeee6917d4fda681fa1bc705cc (patch) | |
tree | b6765028a39b62bc1a177c3ec1ef44762010e214 | |
parent | 533a40307e2d8d1bcb6504b4c87d669459e49fb4 (diff) | |
download | himax_touch-8679f5c537224dfeee6917d4fda681fa1bc705cc.tar.gz |
touch/himax: fix malfunction of suspend and resume.
To register to panel bridge to notify touch to suspend or
resume when display is on or off.
Bug: 196147257
Test: Verify pass by checking the log while display is on or
off, please refer to test result in the b/196147257#comment2.
Change-Id: Ic5db2be975a6df387422f0398d6d69aff0f52f9f
-rw-r--r-- | Makefile | 1 | ||||
-rw-r--r-- | himax_common.c | 334 | ||||
-rw-r--r-- | himax_common.h | 44 | ||||
-rw-r--r-- | himax_debug.c | 2 | ||||
-rw-r--r-- | himax_platform.c | 37 | ||||
-rw-r--r-- | himax_platform.h | 1 |
6 files changed, 386 insertions, 33 deletions
@@ -8,6 +8,7 @@ KBUILD_OPTIONS += CONFIG_TOUCHSCREEN_HIMAX_INCELL=y KBUILD_OPTIONS += CONFIG_TOUCHSCREEN_HIMAX_IC_HX83102=y EXTRA_CFLAGS += -DDYNAMIC_DEBUG_MODULE +EXTRA_CFLAGS += -DCONFIG_TOUCHSCREEN_PANEL_BRIDGE EXTRA_CFLAGS += -DCONFIG_TOUCHSCREEN_TBN EXTRA_CFLAGS += -DCONFIG_TOUCHSCREEN_HEATMAP EXTRA_CFLAGS += -DCONFIG_TOUCHSCREEN_OFFLOAD diff --git a/himax_common.c b/himax_common.c index e64826b..cb5c816 100644 --- a/himax_common.c +++ b/himax_common.c @@ -17,7 +17,9 @@ /*#include "himax_ic_core.h"*/ #include "himax_inspection.h" #include "himax_modular.h" - +#if IS_ENABLED(CONFIG_TOUCHSCREEN_PANEL_BRIDGE) +#include <samsung/exynos_drm_connector.h> +#endif #if defined(__HIMAX_MOD__) int (*hx_msm_drm_register_client)(struct notifier_block *nb); int (*hx_msm_drm_unregister_client)(struct notifier_block *nb); @@ -215,6 +217,11 @@ static int gest_width, gest_height, gest_mid_x, gest_mid_y; static int hx_gesture_coor[16]; #endif +#if IS_ENABLED(CONFIG_TOUCHSCREEN_PANEL_BRIDGE) +static int register_panel_bridge(struct himax_ts_data *ts); +static void unregister_panel_bridge(struct drm_bridge *bridge); +#endif + int g_ts_dbg; EXPORT_SYMBOL(g_ts_dbg); @@ -2800,8 +2807,194 @@ static int hx_chk_flash_sts(uint32_t size) } #endif +#if IS_ENABLED(CONFIG_TOUCHSCREEN_PANEL_BRIDGE) +static void himax_ts_aggregate_bus_state(struct himax_ts_data *ts) +{ + I("%s: bus_refmask = 0x%02X.\n", __func__, ts->bus_refmask); + + /* Complete or cancel any outstanding transitions */ + cancel_work_sync(&ts->suspend_work); + cancel_work_sync(&ts->resume_work); + + if ((ts->bus_refmask == 0 && + ts->power_status == HIMAX_TS_STATE_SUSPEND) || + (ts->bus_refmask != 0 && + ts->power_status != HIMAX_TS_STATE_SUSPEND)) + return; + + if (ts->bus_refmask == 0) + queue_work(ts->event_wq, &ts->suspend_work); + else + queue_work(ts->event_wq, &ts->resume_work); +} + +int himax_ts_set_bus_ref(struct himax_ts_data *ts, u16 ref, bool enable) +{ + int result = 0; + + mutex_lock(&ts->bus_mutex); + + I("%s: bus_refmask = 0x%02X, enable = %d.\n", __func__, ref, enable); + + if ((enable && (ts->bus_refmask & ref)) || + (!enable && !(ts->bus_refmask & ref))) { + D("%s: reference is unexpectedly set: mask=0x%04X, ref=0x%04X, enable=%d\n", + __func__, ts->bus_refmask, ref, enable); + mutex_unlock(&ts->bus_mutex); + return -EINVAL; + } + + if (enable) { + /* IRQs can only keep the bus active. IRQs received while the + * bus is transferred to AOC should be ignored. + */ + if (ref == HIMAX_TS_BUS_REF_IRQ && ts->bus_refmask == 0) + result = -EAGAIN; + else + ts->bus_refmask |= ref; + } else + ts->bus_refmask &= ~ref; + himax_ts_aggregate_bus_state(ts); + + mutex_unlock(&ts->bus_mutex); + + /* When triggering a wake, wait up to one second to resume. SCREEN_ON + * and IRQ references do not need to wait. + */ + if (enable && + ref != HIMAX_TS_BUS_REF_SCREEN_ON && ref != HIMAX_TS_BUS_REF_IRQ) { + wait_for_completion_timeout(&ts->bus_resumed, HZ); + if (ts->power_status != HIMAX_TS_STATE_POWER_ON) { + I("%s: Failed to wake the touch bus.\n", __func__); + result = -ETIMEDOUT; + } + } + + return result; +} + +struct drm_connector *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 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 panel_bridge_enable(struct drm_bridge *bridge) +{ + struct himax_ts_data *ts = + container_of(bridge, struct himax_ts_data, panel_bridge); + + I("%s\n", __func__); + if (!ts->is_panel_lp_mode) + himax_ts_set_bus_ref(ts, HIMAX_TS_BUS_REF_SCREEN_ON, true); +} + +static void panel_bridge_disable(struct drm_bridge *bridge) +{ + struct himax_ts_data *ts = + container_of(bridge, struct himax_ts_data, 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; + } + + I("%s\n", __func__); + himax_ts_set_bus_ref(ts, HIMAX_TS_BUS_REF_SCREEN_ON, false); +} + +static void panel_bridge_mode_set(struct drm_bridge *bridge, + const struct drm_display_mode *mode, + const struct drm_display_mode *adjusted_mode) +{ + struct himax_ts_data *ts = + container_of(bridge, struct himax_ts_data, panel_bridge); + + if (!ts->connector || !ts->connector->state) + ts->connector = get_bridge_connector(bridge); + + ts->is_panel_lp_mode = bridge_is_lp_mode(ts->connector); + himax_ts_set_bus_ref(ts, HIMAX_TS_BUS_REF_SCREEN_ON, !ts->is_panel_lp_mode); + + if (adjusted_mode) { + int vrefresh = drm_mode_vrefresh(adjusted_mode); + + if (ts->display_refresh_rate != vrefresh) { + I("%s: refresh rate(Hz) changed to %d from %d\n", + __func__, vrefresh, ts->display_refresh_rate); + ts->display_refresh_rate = vrefresh; + } + } +} + +static const struct drm_bridge_funcs panel_bridge_funcs = { + .enable = panel_bridge_enable, + .disable = panel_bridge_disable, + .mode_set = panel_bridge_mode_set, +}; + +static int register_panel_bridge(struct himax_ts_data *ts) +{ + I("%s\n", __func__); +#ifdef CONFIG_OF + ts->panel_bridge.of_node = ts->spi->dev.of_node; +#endif + ts->panel_bridge.funcs = &panel_bridge_funcs; + drm_bridge_add(&ts->panel_bridge); + + return 0; +} + +static void unregister_panel_bridge(struct drm_bridge *bridge) +{ + struct drm_bridge *node; + + I("%s\n", __func__); + 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; +} +#endif + static void himax_boot_upgrade(struct work_struct *work) { +#if IS_ENABLED(CONFIG_TOUCHSCREEN_PANEL_BRIDGE) + struct himax_ts_data *ts = container_of(work, struct himax_ts_data, + work_boot_upgrade.work); + int ret = 0; +#endif #if defined(HX_BOOT_UPGRADE) || defined(HX_ZERO_FLASH) int fw_sts = -1; #endif @@ -2863,6 +3056,12 @@ static void himax_boot_upgrade(struct work_struct *work) #endif END: +#if IS_ENABLED(CONFIG_TOUCHSCREEN_PANEL_BRIDGE) + ret = register_panel_bridge(ts); + if (ret < 0) { + E("%s: register_panel_bridge failed. ret = 0x%08X\n", __func__, ret); + } +#endif ic_boot_done = 1; himax_int_enable(1); } @@ -3007,6 +3206,89 @@ END: return ret; } +int himax_ts_pinctrl_configure(struct himax_ts_data *ts, bool enable) +{ + struct pinctrl_state *state; + + I("%s: %s\n", __func__, enable ? "ACTIVE" : "SUSPEND"); + + if (enable) { + state = pinctrl_lookup_state(ts->pdata->pinctrl, "on_state"); + if (IS_ERR(ts->pdata->pinctrl)) + E("%s: could not get active pinstate\n", __func__); + } else { + state = pinctrl_lookup_state(ts->pdata->pinctrl, "off_state"); + if (IS_ERR(ts->pdata->pinctrl)) + E("%s: could not get suspend pinstate\n", __func__); + } + + if (!IS_ERR_OR_NULL(state)) + return pinctrl_select_state(ts->pdata->pinctrl, state); + + return 0; +} + +static void himax_ts_suspend_work(struct work_struct *work) +{ + struct himax_ts_data *ts = container_of(work, struct himax_ts_data, + suspend_work); + + I("%s\n", __func__); + + mutex_lock(&ts->device_mutex); + + reinit_completion(&ts->bus_resumed); + + if (ts->power_status == HIMAX_TS_STATE_SUSPEND) { + E("%s: already suspended.\n", __func__); + mutex_unlock(&ts->device_mutex); + return; + } + + himax_chip_common_suspend(ts); + + ts->power_status = HIMAX_TS_STATE_SUSPEND; + + himax_ts_pinctrl_configure(ts, false); + +#if IS_ENABLED(CONFIG_TOUCHSCREEN_TBN) + if (ts->tbn_register_mask) + tbn_release_bus(ts->tbn_register_mask); +#endif + mutex_unlock(&ts->device_mutex); +} + +static void himax_ts_resume_work(struct work_struct *work) +{ + struct himax_ts_data *ts = container_of(work, struct himax_ts_data, + resume_work); + + I("%s\n", __func__); + + mutex_lock(&ts->device_mutex); + +#if IS_ENABLED(CONFIG_TOUCHSCREEN_TBN) + if (ts->tbn_register_mask) + tbn_request_bus(ts->tbn_register_mask); +#endif + + if (ts->power_status == HIMAX_TS_STATE_POWER_ON) { + E("%s: already resumed.\n", __func__); + mutex_unlock(&ts->device_mutex); + return; + } + + himax_ts_pinctrl_configure(ts, true); + + himax_chip_common_resume(ts); + + ts->power_status = HIMAX_TS_STATE_POWER_ON; + + complete_all(&ts->bus_resumed); + + mutex_unlock(&ts->device_mutex); +} + int himax_chip_common_init(void) { int ret = 0; @@ -3100,6 +3382,35 @@ int himax_chip_common_init(void) #endif g_core_fp.fp_touch_information(); +#if IS_ENABLED(CONFIG_TOUCHSCREEN_PANEL_BRIDGE) + ts->power_status = HIMAX_TS_STATE_POWER_ON; + ts->bus_refmask = HIMAX_TS_BUS_REF_SCREEN_ON; + mutex_init(&ts->bus_mutex); +#endif + mutex_init(&ts->device_mutex); + INIT_WORK(&ts->suspend_work, himax_ts_suspend_work); + INIT_WORK(&ts->resume_work, himax_ts_resume_work); + ts->event_wq = alloc_workqueue("himax_ts-event-queue", WQ_UNBOUND | + WQ_HIGHPRI | WQ_CPU_INTENSIVE, 1); + if (!ts->event_wq) { + E("%s: Cannot create work thread\n", __func__); + ret = -ENOMEM; + goto err_alloc_workqueue; + } + + init_completion(&ts->bus_resumed); + complete_all(&ts->bus_resumed); + init_completion(&ts->resume_done); + complete_all(&ts->resume_done); + +#if IS_ENABLED(CONFIG_TOUCHSCREEN_TBN) + if (register_tbn(&ts->tbn_register_mask)) { + ret = -ENODEV; + E("%s: Failed to register tbn context.\n", __func__); + goto err_init_tbn; + } + I("%s: tbn_register_mask = %#x.\n", __func__, ts->tbn_register_mask); +#endif spin_lock_init(&ts->irq_lock); if (himax_ts_register_interrupt()) { @@ -3235,6 +3546,14 @@ err_debug_init_failed: #endif himax_common_proc_deinit(); err_creat_proc_file_failed: + if (ts->event_wq) + destroy_workqueue(ts->event_wq); +err_alloc_workqueue: +#if IS_ENABLED(CONFIG_TOUCHSCREEN_TBN) + if (ts->tbn_register_mask) + unregister_tbn(&ts->tbn_register_mask); +err_init_tbn: +#endif #if defined(HX_SMART_WAKEUP) #if defined(KERNEL_VER_ABOVE_4_19) wakeup_source_unregister(ts->ts_SMWP_wake_lock); @@ -3274,6 +3593,8 @@ void himax_chip_common_deinit(void) { struct himax_ts_data *ts = private_ts; + himax_ts_pinctrl_configure(ts, false); + himax_ts_unregister_interrupt(); #if defined(CONFIG_TOUCHSCREEN_HIMAX_INSPECT) @@ -3326,6 +3647,17 @@ else cancel_delayed_work_sync(&ts->work_att); destroy_workqueue(ts->himax_att_wq); #endif + cancel_work_sync(&ts->suspend_work); + cancel_work_sync(&ts->resume_work); + destroy_workqueue(ts->event_wq); + +#if IS_ENABLED(CONFIG_TOUCHSCREEN_TBN) + if (ts->tbn_register_mask) + unregister_tbn(&ts->tbn_register_mask); +#endif +#if IS_ENABLED(CONFIG_TOUCHSCREEN_PANEL_BRIDGE) + unregister_panel_bridge(&ts->panel_bridge); +#endif input_free_device(ts->input_dev); #if defined(HX_CONTAINER_SPEED_UP) cancel_delayed_work_sync(&ts->ts_int_work); diff --git a/himax_common.h b/himax_common.h index 83c13d5..8790cef 100644 --- a/himax_common.h +++ b/himax_common.h @@ -39,6 +39,13 @@ #include <linux/kallsyms.h> #include <linux/version.h> #include <linux/time64.h> +#include <drm/drm_bridge.h> +#include <drm/drm_device.h> +#include <drm/drm_encoder.h> +#include <drm/drm_modes.h> +#if IS_ENABLED(CONFIG_TOUCHSCREEN_TBN) +#include <touch_bus_negotiator.h> +#endif #if defined(CONFIG_OF) #include <linux/of_gpio.h> @@ -108,6 +115,9 @@ extern struct drm_panel gNotifier_dummy_panel; #elif defined(HX_CONFIG_DRM) #include <linux/msm_drm_notify.h> #endif +#if IS_ENABLED(CONFIG_TOUCHSCREEN_PANEL_BRIDGE) +#include <drm/drm_panel.h> +#endif #if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 19, 0)) @@ -331,6 +341,16 @@ enum cell_type { #define HX_KEY_DF_RIGHT 275 #endif +enum { + HIMAX_TS_BUS_REF_SCREEN_ON = 0x01, + HIMAX_TS_BUS_REF_IRQ = 0x02 +}; + +enum TOUCH_POWER_MODE { + HIMAX_TS_STATE_POWER_ON = 0, + HIMAX_TS_STATE_SUSPEND, +}; + enum fix_touch_info { FIX_HX_RX_NUM = 40, FIX_HX_TX_NUM = 28, @@ -521,6 +541,27 @@ struct himax_ts_data { struct delayed_work work_att; #endif +#if IS_ENABLED(CONFIG_TOUCHSCREEN_PANEL_BRIDGE) + volatile int power_status; + u16 bus_refmask; + struct mutex bus_mutex; + struct drm_bridge panel_bridge; + struct drm_connector *connector; + bool is_panel_lp_mode; + int display_refresh_rate; /* Display rate in Hz */ +#endif + + struct mutex device_mutex; + struct completion bus_resumed; + struct work_struct suspend_work; + struct work_struct resume_work; + struct workqueue_struct *event_wq; + struct completion resume_done; + +#if IS_ENABLED(CONFIG_TOUCHSCREEN_TBN) + u32 tbn_register_mask; +#endif + struct workqueue_struct *dump_wq; struct work_struct dump_work; struct workqueue_struct *himax_boot_upgrade_wq; @@ -557,7 +598,6 @@ struct himax_ts_data { struct work_struct guest_info_work; #endif - }; struct himax_debug { @@ -625,8 +665,6 @@ extern bool ic_boot_done; int himax_parse_dt(struct himax_ts_data *ts, struct himax_platform_data *pdata); -int himax_ts_pinctrl_configure(struct himax_ts_data *ts, bool enable); - extern void himax_parse_dt_ic_info(struct himax_ts_data *ts, struct himax_platform_data *pdata); diff --git a/himax_debug.c b/himax_debug.c index d0abb47..72df84c 100644 --- a/himax_debug.c +++ b/himax_debug.c @@ -2987,12 +2987,10 @@ CONTI: /* Compare Touch Information */ g_core_fp.fp_tp_info_check(); goto ENDFUCTION; -#if !defined(HX_ZERO_FLASH) } else if (buf[0] == 'e' && buf[1] == 'c') { /* Erase firmware */ g_core_fp.fp_sense_off(true); g_core_fp.fp_chip_erase(); -#endif } else { /* others, do nothing */ debug_level_cmd = 0; diff --git a/himax_platform.c b/himax_platform.c index 7481657..14ed92e 100644 --- a/himax_platform.c +++ b/himax_platform.c @@ -692,9 +692,7 @@ err_regulator_not_on: int himax_gpio_power_config(struct himax_platform_data *pdata) { int error = 0; - /* struct i2c_client *client = private_ts->client; */ #if defined(HX_RST_PIN_FUNC) - if (pdata->gpio_reset >= 0) { error = gpio_request_one(pdata->gpio_reset, GPIOF_OUT_INIT_HIGH, "himax-reset"); @@ -711,7 +709,6 @@ int himax_gpio_power_config(struct himax_platform_data *pdata) goto err_gpio_reset_dir; } } - #endif #if defined(HX_PON_PIN_SUPPORT) @@ -889,8 +886,18 @@ static void himax_ts_isr_func(struct himax_ts_data *ts) irqreturn_t himax_ts_thread(int irq, void *ptr) { + struct himax_ts_data *ts = (struct himax_ts_data *)ptr; + if (himax_ts_set_bus_ref(ts, HIMAX_TS_BUS_REF_IRQ, true) < 0) { + /* Interrupt during bus suspend */ + I("%s: Skipping stray interrupt since bus is suspended(power_status: %d)\n", + __func__, ts->power_status); + return IRQ_HANDLED; + } + himax_ts_isr_func((struct himax_ts_data *)ptr); + himax_ts_set_bus_ref(ts, HIMAX_TS_BUS_REF_IRQ, false); + return IRQ_HANDLED; } @@ -1178,28 +1185,6 @@ int drm_notifier_callback(struct notifier_block *self, } #endif -int himax_ts_pinctrl_configure(struct himax_ts_data *ts, bool enable) -{ - struct pinctrl_state *state; - - I("%s: %s\n", __func__, enable ? "ACTIVE" : "SUSPEND"); - - if (enable) { - state = pinctrl_lookup_state(ts->pdata->pinctrl, "on_state"); - if (IS_ERR(ts->pdata->pinctrl)) - E("%s: could not get active pinstate\n", __func__); - } else { - state = pinctrl_lookup_state(ts->pdata->pinctrl, "off_state"); - if (IS_ERR(ts->pdata->pinctrl)) - E("%s: could not get suspend pinstate\n", __func__); - } - - if (!IS_ERR_OR_NULL(state)) - return pinctrl_select_state(ts->pdata->pinctrl, state); - - return 0; -} - int himax_chip_common_probe(struct spi_device *spi) { struct himax_ts_data *ts; @@ -1272,8 +1257,6 @@ int himax_chip_common_remove(struct spi_device *spi) { struct himax_ts_data *ts = spi_get_drvdata(spi); - himax_ts_pinctrl_configure(ts, false); - if (g_hx_chip_inited) himax_chip_common_deinit(); diff --git a/himax_platform.h b/himax_platform.h index 8eb4c83..fa3b452 100644 --- a/himax_platform.h +++ b/himax_platform.h @@ -131,6 +131,7 @@ extern uint8_t himax_int_gpio_read(int pinnum); extern int himax_gpio_power_config(struct himax_platform_data *pdata); void himax_gpio_power_deconfig(struct himax_platform_data *pdata); +extern int himax_ts_set_bus_ref(struct himax_ts_data *ts, u16 ref, bool enable); #if defined(HX_CONFIG_FB) extern int fb_notifier_callback(struct notifier_block *self, |