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 <pkomandu@codeaurora.org>
[imaund@codeaurora.org: Resolved trivial context conflicts.]
Signed-off-by: Ian Maund <imaund@codeaurora.org>
This commit is contained in:
Padmanabhan Komanduru 2014-10-01 14:03:20 +05:30 committed by David Keitel
parent aa1488988c
commit f6da3384e3
6 changed files with 148 additions and 15 deletions

View file

@ -344,6 +344,7 @@ Optional properties:
- qcom,mdss-dsi-panel-status-check-mode:Specifies the panel status check method for ESD recovery. - 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 "bta_check" = Uses BTA to check the panel status
"reg_read" = Reads panel status register 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 - qcom,mdss-dsi-panel-status-value: Specifies the value of the panel status register when the panel is
in good state. in good state.
- qcom,dynamic-mode-switch-enabled: Boolean used to mention whether panel supports - qcom,dynamic-mode-switch-enabled: Boolean used to mention whether panel supports

View file

@ -18,6 +18,64 @@
#include "mdss_dsi.h" #include "mdss_dsi.h"
#include "mdss_mdp.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. * mdss_check_dsi_ctrl_status() - Check MDP5 DSI controller status periodically.
* @work : dsi controller status data * @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, ctrl_pdata = container_of(pdata, struct mdss_dsi_ctrl_pdata,
panel_data); 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", pr_err("%s: DSI ctrl or status_check callback not available\n",
__func__); __func__);
return; 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) { if (ctl->power_state == MDSS_PANEL_POWER_OFF) {
schedule_delayed_work(&pstatus_data->check_status, schedule_delayed_work(&pstatus_data->check_status,
msecs_to_jiffies(interval)); 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; return;
} }
@ -119,17 +183,10 @@ void mdss_check_dsi_ctrl_status(struct work_struct *work, uint32_t interval)
mutex_unlock(&ctrl_pdata->mutex); mutex_unlock(&ctrl_pdata->mutex);
if ((pstatus_data->mfd->panel_power_state == MDSS_PANEL_POWER_ON)) { if ((pstatus_data->mfd->panel_power_state == MDSS_PANEL_POWER_ON)) {
if (ret > 0) { if (ret > 0)
schedule_delayed_work(&pstatus_data->check_status, schedule_delayed_work(&pstatus_data->check_status,
msecs_to_jiffies(interval)); msecs_to_jiffies(interval));
} else { else
char *envp[2] = {"PANEL_ALIVE=0", NULL}; mdss_report_panel_dead(pstatus_data);
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]);
}
} }
} }

View file

@ -705,8 +705,11 @@ static int mdss_dsi_unblank(struct mdss_panel_data *pdata)
} }
if ((pdata->panel_info.type == MIPI_CMD_PANEL) && 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); 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: error:
mdss_dsi_clk_ctrl(ctrl_pdata, DSI_ALL_CLKS, 0); 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) && 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); mdss_dsi_set_tear_off(ctrl_pdata);
}
if (ctrl_pdata->ctrl_state & CTRL_STATE_PANEL_INIT) { if (ctrl_pdata->ctrl_state & CTRL_STATE_PANEL_INIT) {
if (!pdata->panel_info.dynamic_switch_pending) { if (!pdata->panel_info.dynamic_switch_pending) {
@ -1405,6 +1414,7 @@ static int mdss_dsi_ctrl_probe(struct platform_device *pdev)
rc = -ENODEV; rc = -ENODEV;
goto error_no_mem; goto error_no_mem;
} }
atomic_set(&ctrl_pdata->te_irq_ready, 0);
ctrl_name = of_get_property(pdev->dev.of_node, "label", NULL); ctrl_name = of_get_property(pdev->dev.of_node, "label", NULL);
if (!ctrl_name) if (!ctrl_name)
@ -1488,6 +1498,17 @@ static int mdss_dsi_ctrl_probe(struct platform_device *pdev)
goto error_pan_node; 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); pr_debug("%s: Dsi Ctrl->%d initialized\n", __func__, index);
return 0; return 0;
@ -1750,6 +1771,13 @@ int dsi_panel_device_register(struct device_node *pan_node,
__func__, __LINE__); __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, ctrl_pdata->bklt_en_gpio = of_get_named_gpio(ctrl_pdev->dev.of_node,
"qcom,platform-bklight-en-gpio", 0); "qcom,platform-bklight-en-gpio", 0);
if (!gpio_is_valid(ctrl_pdata->bklt_en_gpio)) if (!gpio_is_valid(ctrl_pdata->bklt_en_gpio))

View file

@ -18,6 +18,7 @@
#include <linux/mdss_io_util.h> #include <linux/mdss_io_util.h>
#include <linux/irqreturn.h> #include <linux/irqreturn.h>
#include <linux/pinctrl/consumer.h> #include <linux/pinctrl/consumer.h>
#include <linux/gpio.h>
#include "mdss_panel.h" #include "mdss_panel.h"
#include "mdss_dsi_cmd.h" #include "mdss_dsi_cmd.h"
@ -96,6 +97,7 @@ enum dsi_panel_status_mode {
ESD_BTA, ESD_BTA,
ESD_REG, ESD_REG,
ESD_REG_NT35596, ESD_REG_NT35596,
ESD_TE,
ESD_MAX, ESD_MAX,
}; };
@ -315,6 +317,7 @@ struct mdss_dsi_ctrl_pdata {
u8 ctrl_state; u8 ctrl_state;
int panel_mode; int panel_mode;
int irq_cnt; int irq_cnt;
int disp_te_gpio;
int rst_gpio; int rst_gpio;
int disp_en_gpio; int disp_en_gpio;
int bklt_en_gpio; int bklt_en_gpio;
@ -329,6 +332,7 @@ struct mdss_dsi_ctrl_pdata {
int pwm_enabled; int pwm_enabled;
bool panel_bias_vreg; bool panel_bias_vreg;
bool dsi_irq_line; bool dsi_irq_line;
atomic_t te_irq_ready;
bool cmd_sync_wait_broadcast; bool cmd_sync_wait_broadcast;
bool cmd_sync_wait_trigger; 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); 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 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_irq_handler_config(struct mdss_dsi_ctrl_pdata *ctrl_pdata);
void mdss_dsi_set_tx_power_mode(int mode, struct mdss_panel_data *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); (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) static inline struct mdss_dsi_ctrl_pdata *mdss_dsi_get_ctrl_clk_master(void)
{ {
return ctrl_list[DSI_CTRL_CLK_MASTER]; return ctrl_list[DSI_CTRL_CLK_MASTER];

View file

@ -1595,6 +1595,11 @@ static int mdss_panel_parse_dt(struct device_node *np,
ctrl_pdata->status_cmds_rlen = 8; ctrl_pdata->status_cmds_rlen = 8;
ctrl_pdata->check_read_status = ctrl_pdata->check_read_status =
mdss_dsi_nt35596_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");
} }
} }

View file

@ -31,7 +31,7 @@
#include "mdss_mdp.h" #include "mdss_mdp.h"
#define STATUS_CHECK_INTERVAL_MS 5000 #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 #define DSI_STATUS_CHECK_DISABLE 0
static uint32_t interval = STATUS_CHECK_INTERVAL_MS; 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); 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() * fb_event_callback() - Call back function for the fb_register_client()
* notifying events * notifying events