diff --git a/arch/arm/mach-tegra/include/mach/dc.h b/arch/arm/mach-tegra/include/mach/dc.h index 254b732097ef..43a9ec1e141d 100644 --- a/arch/arm/mach-tegra/include/mach/dc.h +++ b/arch/arm/mach-tegra/include/mach/dc.h @@ -101,6 +101,7 @@ struct tegra_dc_win { unsigned z; int dirty; + int underflows; struct tegra_dc *dc; struct nvmap_handle_ref *cur_handle; diff --git a/drivers/video/tegra/dc/dc.c b/drivers/video/tegra/dc/dc.c index 16789c21c649..29be689fcaff 100644 --- a/drivers/video/tegra/dc/dc.c +++ b/drivers/video/tegra/dc/dc.c @@ -816,6 +816,7 @@ static irqreturn_t tegra_dc_irq(int irq, void *ptr) struct tegra_dc *dc = ptr; unsigned long status; unsigned long val; + unsigned long underflow_mask; int i; status = tegra_dc_readl(dc, DC_CMD_INT_STATUS); @@ -845,6 +846,45 @@ static irqreturn_t tegra_dc_irq(int irq, void *ptr) wake_up(&dc->wq); } + + /* + * Overlays can get thier internal state corrupted during and underflow + * condition. The only way to fix this state is to reset the DC. + * if we get 4 consecutive frames with underflows, assume we're + * hosed and reset. + */ + underflow_mask = status & (WIN_A_UF_INT | WIN_B_UF_INT | WIN_C_UF_INT); + if (underflow_mask) { + val = tegra_dc_readl(dc, DC_CMD_INT_ENABLE); + val |= V_BLANK_INT; + tegra_dc_writel(dc, val, DC_CMD_INT_ENABLE); + dc->underflow_mask |= underflow_mask; + } + + if (status & V_BLANK_INT) { + int i; + + for (i = 0; i< DC_N_WINDOWS; i++) { + if (dc->underflow_mask & (WIN_A_UF_INT <windows[i].underflows++; + + if (dc->windows[i].underflows > 4) + schedule_work(&dc->reset_work); + } else { + dc->windows[i].underflows = 0; + } + } + + if (!dc->underflow_mask) { + val = tegra_dc_readl(dc, DC_CMD_INT_ENABLE); + val &= ~V_BLANK_INT; + tegra_dc_writel(dc, val, DC_CMD_INT_ENABLE); + } + + dc->underflow_mask = 0; + } + + return IRQ_HANDLED; } @@ -935,8 +975,14 @@ static void tegra_dc_init(struct tegra_dc *dc) tegra_dc_writel(dc, 0x00202020, DC_DISP_MEM_HIGH_PRIORITY); tegra_dc_writel(dc, 0x00010101, DC_DISP_MEM_HIGH_PRIORITY_TIMER); - tegra_dc_writel(dc, 0x00000002, DC_CMD_INT_MASK); - tegra_dc_writel(dc, 0x00000000, DC_CMD_INT_ENABLE); + tegra_dc_writel(dc, (FRAME_END_INT | + V_BLANK_INT | + WIN_A_UF_INT | + WIN_B_UF_INT | + WIN_C_UF_INT), DC_CMD_INT_MASK); + tegra_dc_writel(dc, (WIN_A_UF_INT | + WIN_B_UF_INT | + WIN_C_UF_INT), DC_CMD_INT_ENABLE); tegra_dc_writel(dc, 0x00000000, DC_DISP_BORDER_COLOR); @@ -1031,6 +1077,25 @@ void tegra_dc_disable(struct tegra_dc *dc) mutex_unlock(&dc->lock); } +static void tegra_dc_reset_worker(struct work_struct *work) +{ + struct tegra_dc *dc = + container_of(work, struct tegra_dc, reset_work); + + dev_warn(&dc->ndev->dev, "overlay stuck in underflow state. resetting.\n"); + + mutex_lock(&dc->lock); + _tegra_dc_disable(dc); + + tegra_periph_reset_assert(dc->clk); + msleep(10); + tegra_periph_reset_deassert(dc->clk); + + _tegra_dc_enable(dc); + mutex_unlock(&dc->lock); +} + + static int tegra_dc_probe(struct nvhost_device *ndev) { struct tegra_dc *dc; @@ -1120,6 +1185,7 @@ static int tegra_dc_probe(struct nvhost_device *ndev) mutex_init(&dc->lock); init_waitqueue_head(&dc->wq); + INIT_WORK(&dc->reset_work, tegra_dc_reset_worker); dc->n_windows = DC_N_WINDOWS; for (i = 0; i < dc->n_windows; i++) { diff --git a/drivers/video/tegra/dc/dc_priv.h b/drivers/video/tegra/dc/dc_priv.h index 253d03f057d7..3f7fdbff023b 100644 --- a/drivers/video/tegra/dc/dc_priv.h +++ b/drivers/video/tegra/dc/dc_priv.h @@ -84,6 +84,9 @@ struct tegra_dc { u32 syncpt_id; u32 syncpt_min; u32 syncpt_max; + + unsigned long underflow_mask; + struct work_struct reset_work; }; static inline void tegra_dc_io_start(struct tegra_dc *dc)