OMAPDSS: fix shared irq handlers
authorTomi Valkeinen <tomi.valkeinen@ti.com>
Fri, 11 Apr 2014 10:49:55 +0000 (13:49 +0300)
committerTomi Valkeinen <tomi.valkeinen@ti.com>
Mon, 14 Apr 2014 11:52:08 +0000 (14:52 +0300)
DSS uses shared irq handlers for DISPC and DSI, because on OMAP3, the
DISPC and DSI share the same irq line.

However, the irq handlers presume that the hardware is enabled, which,
in theory, may not be the case with shared irq handlers. So if an
interrupt happens while the DISPC/DSI is off, the kernel will halt as
the irq handler tries to access the DISPC/DSI registers.

In practice that should never happen, as both DSI and DISPC are in the
same power domain. So if there's an IRQ for one of them, the other is
also enabled. However, if CONFIG_DEBUG_SHIRQ is enabled, the kernel will
generate a spurious IRQ, which then causes the problem.

This patch adds an is_enabled field for both DISPC and DSI, which is
used to track if the HW is enabled. For DISPC the code is slightly more
complex, as the users of DISPC can register the interrupt handler, and
we want to hide the is_enabled handling from the users of DISPC.

Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ti.com>
drivers/video/omap2/dss/dispc.c
drivers/video/omap2/dss/dsi.c

index 2bbdb7f..b37e3fb 100644 (file)
@@ -101,6 +101,8 @@ static struct {
        void __iomem    *base;
 
        int irq;
+       irq_handler_t user_handler;
+       void *user_data;
 
        unsigned long core_clk_rate;
        unsigned long tv_pclk_rate;
@@ -113,6 +115,8 @@ static struct {
        u32             ctx[DISPC_SZ_REGS / sizeof(u32)];
 
        const struct dispc_features *feat;
+
+       bool is_enabled;
 } dispc;
 
 enum omap_color_component {
@@ -3669,16 +3673,44 @@ static int __init dispc_init_features(struct platform_device *pdev)
        return 0;
 }
 
+static irqreturn_t dispc_irq_handler(int irq, void *arg)
+{
+       if (!dispc.is_enabled)
+               return IRQ_NONE;
+
+       return dispc.user_handler(irq, dispc.user_data);
+}
+
 int dispc_request_irq(irq_handler_t handler, void *dev_id)
 {
-       return devm_request_irq(&dispc.pdev->dev, dispc.irq, handler,
-                            IRQF_SHARED, "OMAP DISPC", dev_id);
+       int r;
+
+       if (dispc.user_handler != NULL)
+               return -EBUSY;
+
+       dispc.user_handler = handler;
+       dispc.user_data = dev_id;
+
+       /* ensure the dispc_irq_handler sees the values above */
+       smp_wmb();
+
+       r = devm_request_irq(&dispc.pdev->dev, dispc.irq, dispc_irq_handler,
+                            IRQF_SHARED, "OMAP DISPC", &dispc);
+       if (r) {
+               dispc.user_handler = NULL;
+               dispc.user_data = NULL;
+       }
+
+       return r;
 }
 EXPORT_SYMBOL(dispc_request_irq);
 
 void dispc_free_irq(void *dev_id)
 {
-       devm_free_irq(&dispc.pdev->dev, dispc.irq, dev_id);
+       devm_free_irq(&dispc.pdev->dev, dispc.irq, &dispc);
+
+       dispc.user_handler = NULL;
+       dispc.user_data = NULL;
 }
 EXPORT_SYMBOL(dispc_free_irq);
 
@@ -3750,6 +3782,12 @@ static int __exit omap_dispchw_remove(struct platform_device *pdev)
 
 static int dispc_runtime_suspend(struct device *dev)
 {
+       dispc.is_enabled = false;
+       /* ensure the dispc_irq_handler sees the is_enabled value */
+       smp_wmb();
+       /* wait for current handler to finish before turning the DISPC off */
+       synchronize_irq(dispc.irq);
+
        dispc_save_context();
 
        return 0;
@@ -3763,12 +3801,15 @@ static int dispc_runtime_resume(struct device *dev)
         * _omap_dispc_initial_config(). We can thus use it to detect if
         * we have lost register context.
         */
-       if (REG_GET(DISPC_CONFIG, 2, 1) == OMAP_DSS_LOAD_FRAME_ONLY)
-               return 0;
+       if (REG_GET(DISPC_CONFIG, 2, 1) != OMAP_DSS_LOAD_FRAME_ONLY) {
+               _omap_dispc_initial_config();
 
-       _omap_dispc_initial_config();
+               dispc_restore_context();
+       }
 
-       dispc_restore_context();
+       dispc.is_enabled = true;
+       /* ensure the dispc_irq_handler sees the is_enabled value */
+       smp_wmb();
 
        return 0;
 }
index 121d104..8be9b04 100644 (file)
@@ -297,6 +297,8 @@ struct dsi_data {
 
        int irq;
 
+       bool is_enabled;
+
        struct clk *dss_clk;
        struct clk *sys_clk;
 
@@ -795,6 +797,9 @@ static irqreturn_t omap_dsi_irq_handler(int irq, void *arg)
        dsidev = (struct platform_device *) arg;
        dsi = dsi_get_dsidrv_data(dsidev);
 
+       if (!dsi->is_enabled)
+               return IRQ_NONE;
+
        spin_lock(&dsi->irq_lock);
 
        irqstatus = dsi_read_reg(dsidev, DSI_IRQSTATUS);
@@ -5671,6 +5676,15 @@ static int __exit omap_dsihw_remove(struct platform_device *dsidev)
 
 static int dsi_runtime_suspend(struct device *dev)
 {
+       struct platform_device *pdev = to_platform_device(dev);
+       struct dsi_data *dsi = dsi_get_dsidrv_data(pdev);
+
+       dsi->is_enabled = false;
+       /* ensure the irq handler sees the is_enabled value */
+       smp_wmb();
+       /* wait for current handler to finish before turning the DSI off */
+       synchronize_irq(dsi->irq);
+
        dispc_runtime_put();
 
        return 0;
@@ -5678,12 +5692,18 @@ static int dsi_runtime_suspend(struct device *dev)
 
 static int dsi_runtime_resume(struct device *dev)
 {
+       struct platform_device *pdev = to_platform_device(dev);
+       struct dsi_data *dsi = dsi_get_dsidrv_data(pdev);
        int r;
 
        r = dispc_runtime_get();
        if (r)
                return r;
 
+       dsi->is_enabled = true;
+       /* ensure the irq handler sees the is_enabled value */
+       smp_wmb();
+
        return 0;
 }