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