From f6da3384e39a9db6018ffc6aa407c37fca8a0527 Mon Sep 17 00:00:00 2001 From: Padmanabhan Komanduru Date: Wed, 1 Oct 2014 14:03:20 +0530 Subject: [PATCH] msm: mdss: add support for ESD status thread based on TE signal For some command mode panels, the HW Vsync signal from the panel stops or becomes irregular if the panel goes bad due to ESD attack. For such panels, the Vsync signal irregularity can be considered as a trigger for recovery due to ESD attack. The ESD thread interval needs to be set based on the irregularity pattern seen that is specific to the panel. Add support for this. Change-Id: I8a2408ac1b2c0e063446f8af60ed6fac4c53cb8c Signed-off-by: Padmanabhan Komanduru [imaund@codeaurora.org: Resolved trivial context conflicts.] Signed-off-by: Ian Maund --- .../devicetree/bindings/fb/mdss-dsi-panel.txt | 1 + drivers/video/fbdev/msm/dsi_status_6g.c | 81 ++++++++++++++++--- drivers/video/fbdev/msm/mdss_dsi.c | 32 +++++++- drivers/video/fbdev/msm/mdss_dsi.h | 12 +++ drivers/video/fbdev/msm/mdss_dsi_panel.c | 5 ++ drivers/video/fbdev/msm/mdss_dsi_status.c | 32 +++++++- 6 files changed, 148 insertions(+), 15 deletions(-) diff --git a/Documentation/devicetree/bindings/fb/mdss-dsi-panel.txt b/Documentation/devicetree/bindings/fb/mdss-dsi-panel.txt index d6e2cb3f7468..e8e29a02b2a5 100644 --- a/Documentation/devicetree/bindings/fb/mdss-dsi-panel.txt +++ b/Documentation/devicetree/bindings/fb/mdss-dsi-panel.txt @@ -344,6 +344,7 @@ Optional properties: - qcom,mdss-dsi-panel-status-check-mode:Specifies the panel status check method for ESD recovery. "bta_check" = Uses BTA to check the panel status "reg_read" = Reads panel status register to check the panel status + "te_signal_check" = Uses TE signal behaviour to check the panel status - qcom,mdss-dsi-panel-status-value: Specifies the value of the panel status register when the panel is in good state. - qcom,dynamic-mode-switch-enabled: Boolean used to mention whether panel supports diff --git a/drivers/video/fbdev/msm/dsi_status_6g.c b/drivers/video/fbdev/msm/dsi_status_6g.c index 133ba1c469a0..b61062911580 100644 --- a/drivers/video/fbdev/msm/dsi_status_6g.c +++ b/drivers/video/fbdev/msm/dsi_status_6g.c @@ -18,6 +18,64 @@ #include "mdss_dsi.h" #include "mdss_mdp.h" +/* + * mdss_report_panel_dead() - Sends the PANEL_ALIVE=0 status to HAL layer. + * @pstatus_data : dsi status data + * + * This function is called if the panel fails to respond as expected to + * the register read/BTA or if the TE signal is not coming as expected + * from the panel. The function sends the PANEL_ALIVE=0 status to HAL + * layer. + */ +static void mdss_report_panel_dead(struct dsi_status_data *pstatus_data) +{ + char *envp[2] = {"PANEL_ALIVE=0", NULL}; + struct mdss_panel_data *pdata = + dev_get_platdata(&pstatus_data->mfd->pdev->dev); + if (!pdata) { + pr_err("%s: Panel data not available\n", __func__); + return; + } + + pdata->panel_info.panel_dead = true; + kobject_uevent_env(&pstatus_data->mfd->fbi->dev->kobj, + KOBJ_CHANGE, envp); + pr_err("%s: Panel has gone bad, sending uevent - %s\n", + __func__, envp[0]); + return; +} + +/* + * mdss_check_te_status() - Check the status of panel for TE based ESD. + * @ctrl_pdata : dsi controller data + * @pstatus_data : dsi status data + * @interval : duration in milliseconds to schedule work queue + * + * This function is called when the TE signal from the panel doesn't arrive + * after 'interval' milliseconds. If the TE IRQ is not ready, the workqueue + * gets re-scheduled. Otherwise, report the panel to be dead due to ESD attack. + */ +static void mdss_check_te_status(struct mdss_dsi_ctrl_pdata *ctrl_pdata, + struct dsi_status_data *pstatus_data, uint32_t interval) +{ + /* + * During resume, the panel status will be ON but due to race condition + * between ESD thread and display UNBLANK (or rather can be put as + * asynchronuous nature between these two threads), the ESD thread might + * reach this point before the TE IRQ line is enabled or before the + * first TE interrupt arrives after the TE IRQ line is enabled. For such + * cases, re-schedule the ESD thread. + */ + if (!atomic_read(&ctrl_pdata->te_irq_ready)) { + schedule_delayed_work(&pstatus_data->check_status, + msecs_to_jiffies(interval)); + pr_debug("%s: TE IRQ line not enabled yet\n", __func__); + return; + } + + mdss_report_panel_dead(pstatus_data); +} + /* * mdss_check_dsi_ctrl_status() - Check MDP5 DSI controller status periodically. * @work : dsi controller status data @@ -53,7 +111,8 @@ void mdss_check_dsi_ctrl_status(struct work_struct *work, uint32_t interval) ctrl_pdata = container_of(pdata, struct mdss_dsi_ctrl_pdata, panel_data); - if (!ctrl_pdata || !ctrl_pdata->check_status) { + if (!ctrl_pdata || (!ctrl_pdata->check_status && + (ctrl_pdata->status_mode != ESD_TE))) { pr_err("%s: DSI ctrl or status_check callback not available\n", __func__); return; @@ -70,7 +129,12 @@ void mdss_check_dsi_ctrl_status(struct work_struct *work, uint32_t interval) if (ctl->power_state == MDSS_PANEL_POWER_OFF) { schedule_delayed_work(&pstatus_data->check_status, msecs_to_jiffies(interval)); - pr_err("%s: ctl not powered on\n", __func__); + pr_debug("%s: ctl not powered on\n", __func__); + return; + } + + if (ctrl_pdata->status_mode == ESD_TE) { + mdss_check_te_status(ctrl_pdata, pstatus_data, interval); return; } @@ -119,17 +183,10 @@ void mdss_check_dsi_ctrl_status(struct work_struct *work, uint32_t interval) mutex_unlock(&ctrl_pdata->mutex); if ((pstatus_data->mfd->panel_power_state == MDSS_PANEL_POWER_ON)) { - if (ret > 0) { + if (ret > 0) schedule_delayed_work(&pstatus_data->check_status, msecs_to_jiffies(interval)); - } else { - char *envp[2] = {"PANEL_ALIVE=0", NULL}; - pdata->panel_info.panel_dead = true; - ret = kobject_uevent_env( - &pstatus_data->mfd->fbi->dev->kobj, - KOBJ_CHANGE, envp); - pr_err("%s: Panel has gone bad, sending uevent - %s\n", - __func__, envp[0]); - } + else + mdss_report_panel_dead(pstatus_data); } } diff --git a/drivers/video/fbdev/msm/mdss_dsi.c b/drivers/video/fbdev/msm/mdss_dsi.c index cc0060fd6955..21acfe8dca25 100644 --- a/drivers/video/fbdev/msm/mdss_dsi.c +++ b/drivers/video/fbdev/msm/mdss_dsi.c @@ -705,8 +705,11 @@ static int mdss_dsi_unblank(struct mdss_panel_data *pdata) } if ((pdata->panel_info.type == MIPI_CMD_PANEL) && - mipi->vsync_enable && mipi->hw_vsync_mode) + mipi->vsync_enable && mipi->hw_vsync_mode) { mdss_dsi_set_tear_on(ctrl_pdata); + if (mdss_dsi_is_te_based_esd(ctrl_pdata)) + enable_irq(gpio_to_irq(ctrl_pdata->disp_te_gpio)); + } error: mdss_dsi_clk_ctrl(ctrl_pdata, DSI_ALL_CLKS, 0); @@ -762,8 +765,14 @@ static int mdss_dsi_blank(struct mdss_panel_data *pdata, int power_state) } if ((pdata->panel_info.type == MIPI_CMD_PANEL) && - mipi->vsync_enable && mipi->hw_vsync_mode) + mipi->vsync_enable && mipi->hw_vsync_mode) { + if (mdss_dsi_is_te_based_esd(ctrl_pdata)) { + disable_irq(gpio_to_irq( + ctrl_pdata->disp_te_gpio)); + atomic_dec(&ctrl_pdata->te_irq_ready); + } mdss_dsi_set_tear_off(ctrl_pdata); + } if (ctrl_pdata->ctrl_state & CTRL_STATE_PANEL_INIT) { if (!pdata->panel_info.dynamic_switch_pending) { @@ -1405,6 +1414,7 @@ static int mdss_dsi_ctrl_probe(struct platform_device *pdev) rc = -ENODEV; goto error_no_mem; } + atomic_set(&ctrl_pdata->te_irq_ready, 0); ctrl_name = of_get_property(pdev->dev.of_node, "label", NULL); if (!ctrl_name) @@ -1488,6 +1498,17 @@ static int mdss_dsi_ctrl_probe(struct platform_device *pdev) goto error_pan_node; } + if (mdss_dsi_is_te_based_esd(ctrl_pdata)) { + rc = devm_request_irq(&pdev->dev, + gpio_to_irq(ctrl_pdata->disp_te_gpio), + hw_vsync_handler, IRQF_TRIGGER_FALLING, + "VSYNC_GPIO", ctrl_pdata); + if (rc) { + pr_err("TE request_irq failed.\n"); + goto error_pan_node; + } + disable_irq(gpio_to_irq(ctrl_pdata->disp_te_gpio)); + } pr_debug("%s: Dsi Ctrl->%d initialized\n", __func__, index); return 0; @@ -1750,6 +1771,13 @@ int dsi_panel_device_register(struct device_node *pan_node, __func__, __LINE__); } + ctrl_pdata->disp_te_gpio = of_get_named_gpio(ctrl_pdev->dev.of_node, + "qcom,platform-te-gpio", 0); + + if (!gpio_is_valid(ctrl_pdata->disp_te_gpio)) + pr_err("%s:%d, TE gpio not specified\n", + __func__, __LINE__); + ctrl_pdata->bklt_en_gpio = of_get_named_gpio(ctrl_pdev->dev.of_node, "qcom,platform-bklight-en-gpio", 0); if (!gpio_is_valid(ctrl_pdata->bklt_en_gpio)) diff --git a/drivers/video/fbdev/msm/mdss_dsi.h b/drivers/video/fbdev/msm/mdss_dsi.h index 86a30e6e10a9..cac9371dff0b 100644 --- a/drivers/video/fbdev/msm/mdss_dsi.h +++ b/drivers/video/fbdev/msm/mdss_dsi.h @@ -18,6 +18,7 @@ #include #include #include +#include #include "mdss_panel.h" #include "mdss_dsi_cmd.h" @@ -96,6 +97,7 @@ enum dsi_panel_status_mode { ESD_BTA, ESD_REG, ESD_REG_NT35596, + ESD_TE, ESD_MAX, }; @@ -315,6 +317,7 @@ struct mdss_dsi_ctrl_pdata { u8 ctrl_state; int panel_mode; int irq_cnt; + int disp_te_gpio; int rst_gpio; int disp_en_gpio; int bklt_en_gpio; @@ -329,6 +332,7 @@ struct mdss_dsi_ctrl_pdata { int pwm_enabled; bool panel_bias_vreg; bool dsi_irq_line; + atomic_t te_irq_ready; bool cmd_sync_wait_broadcast; bool cmd_sync_wait_trigger; @@ -420,6 +424,7 @@ void mdss_dsi_controller_cfg(int enable, void mdss_dsi_sw_reset(struct mdss_dsi_ctrl_pdata *ctrl_pdata, bool restore); irqreturn_t mdss_dsi_isr(int irq, void *ptr); +irqreturn_t hw_vsync_handler(int irq, void *data); void mdss_dsi_irq_handler_config(struct mdss_dsi_ctrl_pdata *ctrl_pdata); void mdss_dsi_set_tx_power_mode(int mode, struct mdss_panel_data *pdata); @@ -539,6 +544,13 @@ static inline bool mdss_dsi_is_ctrl_clk_slave(struct mdss_dsi_ctrl_pdata *ctrl) (ctrl->ndx == DSI_CTRL_CLK_SLAVE); } +static inline bool mdss_dsi_is_te_based_esd(struct mdss_dsi_ctrl_pdata *ctrl) +{ + return (ctrl->status_mode == ESD_TE) && + gpio_is_valid(ctrl->disp_te_gpio) && + mdss_dsi_is_left_ctrl(ctrl); +} + static inline struct mdss_dsi_ctrl_pdata *mdss_dsi_get_ctrl_clk_master(void) { return ctrl_list[DSI_CTRL_CLK_MASTER]; diff --git a/drivers/video/fbdev/msm/mdss_dsi_panel.c b/drivers/video/fbdev/msm/mdss_dsi_panel.c index 8e7707bb13b6..f7c0afb5645c 100644 --- a/drivers/video/fbdev/msm/mdss_dsi_panel.c +++ b/drivers/video/fbdev/msm/mdss_dsi_panel.c @@ -1595,6 +1595,11 @@ static int mdss_panel_parse_dt(struct device_node *np, ctrl_pdata->status_cmds_rlen = 8; ctrl_pdata->check_read_status = mdss_dsi_nt35596_read_status; + } else if (!strcmp(data, "te_signal_check")) { + if (pinfo->mipi.mode == DSI_CMD_MODE) + ctrl_pdata->status_mode = ESD_TE; + else + pr_err("TE-ESD not valid for video mode\n"); } } diff --git a/drivers/video/fbdev/msm/mdss_dsi_status.c b/drivers/video/fbdev/msm/mdss_dsi_status.c index bfabda8cdeea..7de0a49a4b95 100644 --- a/drivers/video/fbdev/msm/mdss_dsi_status.c +++ b/drivers/video/fbdev/msm/mdss_dsi_status.c @@ -31,7 +31,7 @@ #include "mdss_mdp.h" #define STATUS_CHECK_INTERVAL_MS 5000 -#define STATUS_CHECK_INTERVAL_MIN_MS 200 +#define STATUS_CHECK_INTERVAL_MIN_MS 50 #define DSI_STATUS_CHECK_DISABLE 0 static uint32_t interval = STATUS_CHECK_INTERVAL_MS; @@ -68,6 +68,36 @@ static void check_dsi_ctrl_status(struct work_struct *work) pdsi_status->mfd->mdp.check_dsi_status(work, interval); } +/* + * hw_vsync_handler() - Interrupt handler for HW VSYNC signal. + * @irq : irq line number + * @data : Pointer to the device structure. + * + * This function is called whenever a HW vsync signal is received from the + * panel. This resets the timer of ESD delayed workqueue back to initial + * value. + */ +irqreturn_t hw_vsync_handler(int irq, void *data) +{ + struct mdss_dsi_ctrl_pdata *ctrl_pdata = + (struct mdss_dsi_ctrl_pdata *)data; + if (!ctrl_pdata) { + pr_err("%s: DSI ctrl not available\n", __func__); + return IRQ_HANDLED; + } + + if (pstatus_data) + mod_delayed_work(system_wq, &pstatus_data->check_status, + msecs_to_jiffies(interval)); + else + pr_err("Pstatus data is NULL\n"); + + if (!atomic_read(&ctrl_pdata->te_irq_ready)) + atomic_inc(&ctrl_pdata->te_irq_ready); + + return IRQ_HANDLED; +} + /* * fb_event_callback() - Call back function for the fb_register_client() * notifying events