drm/exynos/decon5433: move TE handling to DECON
authorAndrzej Hajda <a.hajda@samsung.com>
Wed, 5 Apr 2017 07:28:32 +0000 (09:28 +0200)
committerInki Dae <inki.dae@samsung.com>
Thu, 1 Jun 2017 07:21:37 +0000 (16:21 +0900)
DECON is the only user of TE signal, moving all TE related
code to DECON driver allows to precise control of IRQ handlers.
This control allows to fix race between IRQ handler and DECON disable
code - now it is possible to disable DECON during IRQ handling
which can result in kernel crash. Beside race fixing this change
allows further code simplification.

Signed-off-by: Andrzej Hajda <a.hajda@samsung.com>
Signed-off-by: Inki Dae <inki.dae@samsung.com>
drivers/gpu/drm/exynos/exynos5433_drm_decon.c

index 028a6575f7d55de7e4a4749df142e558a66cfbf0..5bdf1a0ec862275673c259d01a127dfdb85da558 100644 (file)
@@ -63,6 +63,8 @@ struct decon_context {
        void __iomem                    *addr;
        struct regmap                   *sysreg;
        struct clk                      *clks[ARRAY_SIZE(decon_clks_name)];
+       unsigned int                    irq;
+       unsigned int                    te_irq;
        unsigned long                   flags;
        unsigned long                   out_type;
        int                             first_win;
@@ -105,6 +107,11 @@ static int decon_enable_vblank(struct exynos_drm_crtc *crtc)
                val |= VIDINTCON0_INTFRMEN | VIDINTCON0_FRAMESEL_FP;
 
        writel(val, ctx->addr + DECON_VIDINTCON0);
+
+       enable_irq(ctx->irq);
+       if (!(ctx->out_type & I80_HW_TRG))
+               enable_irq(ctx->te_irq);
+
        set_bit(BIT_IRQS_ENABLED, &ctx->flags);
 
        return 0;
@@ -118,6 +125,10 @@ static void decon_disable_vblank(struct exynos_drm_crtc *crtc)
        if (test_bit(BIT_SUSPENDED, &ctx->flags))
                return;
 
+       if (!(ctx->out_type & I80_HW_TRG))
+               disable_irq_nosync(ctx->te_irq);
+       disable_irq_nosync(ctx->irq);
+
        writel(0, ctx->addr + DECON_VIDINTCON0);
 }
 
@@ -491,6 +502,10 @@ static void decon_disable(struct exynos_drm_crtc *crtc)
        struct decon_context *ctx = crtc->ctx;
        int i;
 
+       if (!(ctx->out_type & I80_HW_TRG))
+               synchronize_irq(ctx->te_irq);
+       synchronize_irq(ctx->irq);
+
        if (test_bit(BIT_SUSPENDED, &ctx->flags))
                return;
 
@@ -513,17 +528,19 @@ static void decon_disable(struct exynos_drm_crtc *crtc)
        set_bit(BIT_SUSPENDED, &ctx->flags);
 }
 
-static void decon_te_irq_handler(struct exynos_drm_crtc *crtc)
+static irqreturn_t decon_te_irq_handler(int irq, void *dev_id)
 {
-       struct decon_context *ctx = crtc->ctx;
+       struct decon_context *ctx = dev_id;
 
        if (!test_bit(BIT_CLKS_ENABLED, &ctx->flags) ||
            (ctx->out_type & I80_HW_TRG))
-               return;
+               return IRQ_HANDLED;
 
        if (test_and_clear_bit(BIT_WIN_UPDATED, &ctx->flags) ||
            test_bit(BIT_IRQS_ENABLED, &ctx->flags))
                decon_set_bits(ctx, DECON_TRIGCON, TRIGCON_SWTRIGCMD, ~0);
+
+       return IRQ_HANDLED;
 }
 
 static void decon_clear_channels(struct exynos_drm_crtc *crtc)
@@ -564,7 +581,6 @@ static const struct exynos_drm_crtc_ops decon_crtc_ops = {
        .update_plane           = decon_update_plane,
        .disable_plane          = decon_disable_plane,
        .atomic_flush           = decon_atomic_flush,
-       .te_handler             = decon_te_irq_handler,
 };
 
 static int decon_bind(struct device *dev, struct device *master, void *data)
@@ -717,6 +733,31 @@ static const struct of_device_id exynos5433_decon_driver_dt_match[] = {
 };
 MODULE_DEVICE_TABLE(of, exynos5433_decon_driver_dt_match);
 
+static int decon_conf_irq(struct decon_context *ctx, const char *name,
+               irq_handler_t handler, unsigned long int flags, bool required)
+{
+       struct platform_device *pdev = to_platform_device(ctx->dev);
+       int ret, irq = platform_get_irq_byname(pdev, name);
+
+       if (irq < 0) {
+               if (irq == -EPROBE_DEFER)
+                       return irq;
+               if (required)
+                       dev_err(ctx->dev, "cannot get %s IRQ\n", name);
+               else
+                       irq = 0;
+               return irq;
+       }
+       irq_set_status_flags(irq, IRQ_NOAUTOEN);
+       ret = devm_request_irq(ctx->dev, irq, handler, flags, "drm_decon", ctx);
+       if (ret < 0) {
+               dev_err(ctx->dev, "IRQ %s request failed\n", name);
+               return ret;
+       }
+
+       return irq;
+}
+
 static int exynos5433_decon_probe(struct platform_device *pdev)
 {
        struct device *dev = &pdev->dev;
@@ -740,15 +781,6 @@ static int exynos5433_decon_probe(struct platform_device *pdev)
                ctx->out_type |= IFTYPE_I80;
        }
 
-       if (ctx->out_type & I80_HW_TRG) {
-               ctx->sysreg = syscon_regmap_lookup_by_phandle(dev->of_node,
-                                                       "samsung,disp-sysreg");
-               if (IS_ERR(ctx->sysreg)) {
-                       dev_err(dev, "failed to get system register\n");
-                       return PTR_ERR(ctx->sysreg);
-               }
-       }
-
        for (i = 0; i < ARRAY_SIZE(decon_clks_name); i++) {
                struct clk *clk;
 
@@ -771,18 +803,34 @@ static int exynos5433_decon_probe(struct platform_device *pdev)
                return PTR_ERR(ctx->addr);
        }
 
-       res = platform_get_resource_byname(pdev, IORESOURCE_IRQ,
-                       (ctx->out_type & IFTYPE_I80) ? "lcd_sys" : "vsync");
-       if (!res) {
-               dev_err(dev, "cannot find IRQ resource\n");
-               return -ENXIO;
+       if (ctx->out_type & IFTYPE_I80) {
+               ret = decon_conf_irq(ctx, "lcd_sys", decon_irq_handler, 0, true);
+               if (ret < 0)
+                       return ret;
+               ctx->irq = ret;
+
+               ret = decon_conf_irq(ctx, "te", decon_te_irq_handler,
+                                    IRQF_TRIGGER_RISING, false);
+               if (ret < 0)
+                       return ret;
+               if (ret) {
+                       ctx->te_irq = ret;
+                       ctx->out_type &= ~I80_HW_TRG;
+               }
+       } else {
+               ret = decon_conf_irq(ctx, "vsync", decon_irq_handler, 0, true);
+               if (ret < 0)
+                       return ret;
+               ctx->irq = ret;
        }
 
-       ret = devm_request_irq(dev, res->start, decon_irq_handler, 0,
-                              "drm_decon", ctx);
-       if (ret < 0) {
-               dev_err(dev, "lcd_sys irq request failed\n");
-               return ret;
+       if (ctx->out_type & I80_HW_TRG) {
+               ctx->sysreg = syscon_regmap_lookup_by_phandle(dev->of_node,
+                                                       "samsung,disp-sysreg");
+               if (IS_ERR(ctx->sysreg)) {
+                       dev_err(dev, "failed to get system register\n");
+                       return PTR_ERR(ctx->sysreg);
+               }
        }
 
        platform_set_drvdata(pdev, ctx);