drm/tegra: dc: Implement runtime PM
authorThierry Reding <treding@nvidia.com>
Mon, 3 Aug 2015 11:20:49 +0000 (13:20 +0200)
committerThierry Reding <treding@nvidia.com>
Mon, 4 Jul 2016 09:35:46 +0000 (11:35 +0200)
Use runtime PM to clock-gate, assert reset and powergate the display
controller. This ties in nicely with atomic DPMS in that a runtime PM
reference is taken before a pipe is enabled and dropped after it has
been shut down.

To make sure this works, make sure to only ever update planes on active
CRTCs, otherwise register accesses to a clock-gated and reset CRTC will
hang the CPU.

Signed-off-by: Thierry Reding <treding@nvidia.com>
drivers/gpu/drm/tegra/dc.c
drivers/gpu/drm/tegra/drm.c

index 39940f5..8495bd0 100644 (file)
@@ -10,6 +10,7 @@
 #include <linux/clk.h>
 #include <linux/debugfs.h>
 #include <linux/iommu.h>
+#include <linux/pm_runtime.h>
 #include <linux/reset.h>
 
 #include <soc/tegra/pmc.h>
@@ -1216,6 +1217,8 @@ static void tegra_crtc_disable(struct drm_crtc *crtc)
 
        tegra_dc_stats_reset(&dc->stats);
        drm_crtc_vblank_off(crtc);
+
+       pm_runtime_put_sync(dc->dev);
 }
 
 static void tegra_crtc_enable(struct drm_crtc *crtc)
@@ -1225,6 +1228,48 @@ static void tegra_crtc_enable(struct drm_crtc *crtc)
        struct tegra_dc *dc = to_tegra_dc(crtc);
        u32 value;
 
+       pm_runtime_get_sync(dc->dev);
+
+       /* initialize display controller */
+       if (dc->syncpt) {
+               u32 syncpt = host1x_syncpt_id(dc->syncpt);
+
+               value = SYNCPT_CNTRL_NO_STALL;
+               tegra_dc_writel(dc, value, DC_CMD_GENERAL_INCR_SYNCPT_CNTRL);
+
+               value = SYNCPT_VSYNC_ENABLE | syncpt;
+               tegra_dc_writel(dc, value, DC_CMD_CONT_SYNCPT_VSYNC);
+       }
+
+       value = WIN_A_UF_INT | WIN_B_UF_INT | WIN_C_UF_INT |
+               WIN_A_OF_INT | WIN_B_OF_INT | WIN_C_OF_INT;
+       tegra_dc_writel(dc, value, DC_CMD_INT_TYPE);
+
+       value = WIN_A_UF_INT | WIN_B_UF_INT | WIN_C_UF_INT |
+               WIN_A_OF_INT | WIN_B_OF_INT | WIN_C_OF_INT;
+       tegra_dc_writel(dc, value, DC_CMD_INT_POLARITY);
+
+       /* initialize timer */
+       value = CURSOR_THRESHOLD(0) | WINDOW_A_THRESHOLD(0x20) |
+               WINDOW_B_THRESHOLD(0x20) | WINDOW_C_THRESHOLD(0x20);
+       tegra_dc_writel(dc, value, DC_DISP_DISP_MEM_HIGH_PRIORITY);
+
+       value = CURSOR_THRESHOLD(0) | WINDOW_A_THRESHOLD(1) |
+               WINDOW_B_THRESHOLD(1) | WINDOW_C_THRESHOLD(1);
+       tegra_dc_writel(dc, value, DC_DISP_DISP_MEM_HIGH_PRIORITY_TIMER);
+
+       value = VBLANK_INT | WIN_A_UF_INT | WIN_B_UF_INT | WIN_C_UF_INT |
+               WIN_A_OF_INT | WIN_B_OF_INT | WIN_C_OF_INT;
+       tegra_dc_writel(dc, value, DC_CMD_INT_ENABLE);
+
+       value = WIN_A_UF_INT | WIN_B_UF_INT | WIN_C_UF_INT |
+               WIN_A_OF_INT | WIN_B_OF_INT | WIN_C_OF_INT;
+       tegra_dc_writel(dc, value, DC_CMD_INT_MASK);
+
+       if (dc->soc->supports_border_color)
+               tegra_dc_writel(dc, 0, DC_DISP_BORDER_COLOR);
+
+       /* apply PLL and pixel clock changes */
        tegra_dc_commit_state(dc, state);
 
        /* program display mode */
@@ -1685,7 +1730,6 @@ static int tegra_dc_init(struct host1x_client *client)
        struct tegra_drm *tegra = drm->dev_private;
        struct drm_plane *primary = NULL;
        struct drm_plane *cursor = NULL;
-       u32 value;
        int err;
 
        dc->syncpt = host1x_syncpt_request(dc->dev, flags);
@@ -1755,47 +1799,6 @@ static int tegra_dc_init(struct host1x_client *client)
                goto cleanup;
        }
 
-       /* initialize display controller */
-       if (dc->syncpt) {
-               u32 syncpt = host1x_syncpt_id(dc->syncpt);
-
-               value = SYNCPT_CNTRL_NO_STALL;
-               tegra_dc_writel(dc, value, DC_CMD_GENERAL_INCR_SYNCPT_CNTRL);
-
-               value = SYNCPT_VSYNC_ENABLE | syncpt;
-               tegra_dc_writel(dc, value, DC_CMD_CONT_SYNCPT_VSYNC);
-       }
-
-       value = WIN_A_UF_INT | WIN_B_UF_INT | WIN_C_UF_INT |
-               WIN_A_OF_INT | WIN_B_OF_INT | WIN_C_OF_INT;
-       tegra_dc_writel(dc, value, DC_CMD_INT_TYPE);
-
-       value = WIN_A_UF_INT | WIN_B_UF_INT | WIN_C_UF_INT |
-               WIN_A_OF_INT | WIN_B_OF_INT | WIN_C_OF_INT;
-       tegra_dc_writel(dc, value, DC_CMD_INT_POLARITY);
-
-       /* initialize timer */
-       value = CURSOR_THRESHOLD(0) | WINDOW_A_THRESHOLD(0x20) |
-               WINDOW_B_THRESHOLD(0x20) | WINDOW_C_THRESHOLD(0x20);
-       tegra_dc_writel(dc, value, DC_DISP_DISP_MEM_HIGH_PRIORITY);
-
-       value = CURSOR_THRESHOLD(0) | WINDOW_A_THRESHOLD(1) |
-               WINDOW_B_THRESHOLD(1) | WINDOW_C_THRESHOLD(1);
-       tegra_dc_writel(dc, value, DC_DISP_DISP_MEM_HIGH_PRIORITY_TIMER);
-
-       value = VBLANK_INT | WIN_A_UF_INT | WIN_B_UF_INT | WIN_C_UF_INT |
-               WIN_A_OF_INT | WIN_B_OF_INT | WIN_C_OF_INT;
-       tegra_dc_writel(dc, value, DC_CMD_INT_ENABLE);
-
-       value = WIN_A_UF_INT | WIN_B_UF_INT | WIN_C_UF_INT |
-               WIN_A_OF_INT | WIN_B_OF_INT | WIN_C_OF_INT;
-       tegra_dc_writel(dc, value, DC_CMD_INT_MASK);
-
-       if (dc->soc->supports_border_color)
-               tegra_dc_writel(dc, 0, DC_DISP_BORDER_COLOR);
-
-       tegra_dc_stats_reset(&dc->stats);
-
        return 0;
 
 cleanup:
@@ -1987,33 +1990,15 @@ static int tegra_dc_probe(struct platform_device *pdev)
                return PTR_ERR(dc->rst);
        }
 
+       reset_control_assert(dc->rst);
+
        if (dc->soc->has_powergate) {
                if (dc->pipe == 0)
                        dc->powergate = TEGRA_POWERGATE_DIS;
                else
                        dc->powergate = TEGRA_POWERGATE_DISB;
 
-               err = tegra_powergate_sequence_power_up(dc->powergate, dc->clk,
-                                                       dc->rst);
-               if (err < 0) {
-                       dev_err(&pdev->dev, "failed to power partition: %d\n",
-                               err);
-                       return err;
-               }
-       } else {
-               err = clk_prepare_enable(dc->clk);
-               if (err < 0) {
-                       dev_err(&pdev->dev, "failed to enable clock: %d\n",
-                               err);
-                       return err;
-               }
-
-               err = reset_control_deassert(dc->rst);
-               if (err < 0) {
-                       dev_err(&pdev->dev, "failed to deassert reset: %d\n",
-                               err);
-                       return err;
-               }
+               tegra_powergate_power_off(dc->powergate);
        }
 
        regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
@@ -2027,16 +2012,19 @@ static int tegra_dc_probe(struct platform_device *pdev)
                return -ENXIO;
        }
 
-       INIT_LIST_HEAD(&dc->client.list);
-       dc->client.ops = &dc_client_ops;
-       dc->client.dev = &pdev->dev;
-
        err = tegra_dc_rgb_probe(dc);
        if (err < 0 && err != -ENODEV) {
                dev_err(&pdev->dev, "failed to probe RGB output: %d\n", err);
                return err;
        }
 
+       platform_set_drvdata(pdev, dc);
+       pm_runtime_enable(&pdev->dev);
+
+       INIT_LIST_HEAD(&dc->client.list);
+       dc->client.ops = &dc_client_ops;
+       dc->client.dev = &pdev->dev;
+
        err = host1x_client_register(&dc->client);
        if (err < 0) {
                dev_err(&pdev->dev, "failed to register host1x client: %d\n",
@@ -2044,8 +2032,6 @@ static int tegra_dc_probe(struct platform_device *pdev)
                return err;
        }
 
-       platform_set_drvdata(pdev, dc);
-
        return 0;
 }
 
@@ -2067,7 +2053,22 @@ static int tegra_dc_remove(struct platform_device *pdev)
                return err;
        }
 
-       reset_control_assert(dc->rst);
+       pm_runtime_disable(&pdev->dev);
+
+       return 0;
+}
+
+#ifdef CONFIG_PM
+static int tegra_dc_suspend(struct device *dev)
+{
+       struct tegra_dc *dc = dev_get_drvdata(dev);
+       int err;
+
+       err = reset_control_assert(dc->rst);
+       if (err < 0) {
+               dev_err(dev, "failed to assert reset: %d\n", err);
+               return err;
+       }
 
        if (dc->soc->has_powergate)
                tegra_powergate_power_off(dc->powergate);
@@ -2077,10 +2078,45 @@ static int tegra_dc_remove(struct platform_device *pdev)
        return 0;
 }
 
+static int tegra_dc_resume(struct device *dev)
+{
+       struct tegra_dc *dc = dev_get_drvdata(dev);
+       int err;
+
+       if (dc->soc->has_powergate) {
+               err = tegra_powergate_sequence_power_up(dc->powergate, dc->clk,
+                                                       dc->rst);
+               if (err < 0) {
+                       dev_err(dev, "failed to power partition: %d\n", err);
+                       return err;
+               }
+       } else {
+               err = clk_prepare_enable(dc->clk);
+               if (err < 0) {
+                       dev_err(dev, "failed to enable clock: %d\n", err);
+                       return err;
+               }
+
+               err = reset_control_deassert(dc->rst);
+               if (err < 0) {
+                       dev_err(dev, "failed to deassert reset: %d\n", err);
+                       return err;
+               }
+       }
+
+       return 0;
+}
+#endif
+
+static const struct dev_pm_ops tegra_dc_pm_ops = {
+       SET_RUNTIME_PM_OPS(tegra_dc_suspend, tegra_dc_resume, NULL)
+};
+
 struct platform_driver tegra_dc_driver = {
        .driver = {
                .name = "tegra-dc",
                .of_match_table = tegra_dc_of_match,
+               .pm = &tegra_dc_pm_ops,
        },
        .probe = tegra_dc_probe,
        .remove = tegra_dc_remove,
index b59c3bf..f753e23 100644 (file)
@@ -56,8 +56,8 @@ static void tegra_atomic_complete(struct tegra_drm *tegra,
         */
 
        drm_atomic_helper_commit_modeset_disables(drm, state);
-       drm_atomic_helper_commit_planes(drm, state, false);
        drm_atomic_helper_commit_modeset_enables(drm, state);
+       drm_atomic_helper_commit_planes(drm, state, true);
 
        drm_atomic_helper_wait_for_vblanks(drm, state);