drm/tegra: dsi: Implement runtime PM
authorThierry Reding <treding@nvidia.com>
Fri, 7 Aug 2015 07:29:54 +0000 (09:29 +0200)
committerThierry Reding <treding@nvidia.com>
Mon, 4 Jul 2016 09:37:00 +0000 (11:37 +0200)
Use runtime PM to clock-(un)gate, (de)assert reset and control power to
the DSI 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.

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

index d1239eb..7e75215 100644 (file)
@@ -13,6 +13,7 @@
 #include <linux/of.h>
 #include <linux/of_platform.h>
 #include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
 #include <linux/reset.h>
 
 #include <linux/regulator/consumer.h>
@@ -677,6 +678,45 @@ static void tegra_dsi_ganged_disable(struct tegra_dsi *dsi)
        tegra_dsi_writel(dsi, 0, DSI_GANGED_MODE_CONTROL);
 }
 
+static int tegra_dsi_pad_enable(struct tegra_dsi *dsi)
+{
+       u32 value;
+
+       value = DSI_PAD_CONTROL_VS1_PULLDN(0) | DSI_PAD_CONTROL_VS1_PDIO(0);
+       tegra_dsi_writel(dsi, value, DSI_PAD_CONTROL_0);
+
+       return 0;
+}
+
+static int tegra_dsi_pad_calibrate(struct tegra_dsi *dsi)
+{
+       u32 value;
+
+       /*
+        * XXX Is this still needed? The module reset is deasserted right
+        * before this function is called.
+        */
+       tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_0);
+       tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_1);
+       tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_2);
+       tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_3);
+       tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_4);
+
+       /* start calibration */
+       tegra_dsi_pad_enable(dsi);
+
+       value = DSI_PAD_SLEW_UP(0x7) | DSI_PAD_SLEW_DN(0x7) |
+               DSI_PAD_LP_UP(0x1) | DSI_PAD_LP_DN(0x1) |
+               DSI_PAD_OUT_CLK(0x0);
+       tegra_dsi_writel(dsi, value, DSI_PAD_CONTROL_2);
+
+       value = DSI_PAD_PREEMP_PD_CLK(0x3) | DSI_PAD_PREEMP_PU_CLK(0x3) |
+               DSI_PAD_PREEMP_PD(0x03) | DSI_PAD_PREEMP_PU(0x3);
+       tegra_dsi_writel(dsi, value, DSI_PAD_CONTROL_3);
+
+       return tegra_mipi_calibrate(dsi->mipi);
+}
+
 static void tegra_dsi_set_timeout(struct tegra_dsi *dsi, unsigned long bclk,
                                  unsigned int vrefresh)
 {
@@ -837,7 +877,7 @@ static void tegra_dsi_encoder_disable(struct drm_encoder *encoder)
 
        tegra_dsi_disable(dsi);
 
-       return;
+       pm_runtime_put(dsi->dev);
 }
 
 static void tegra_dsi_encoder_enable(struct drm_encoder *encoder)
@@ -848,6 +888,13 @@ static void tegra_dsi_encoder_enable(struct drm_encoder *encoder)
        struct tegra_dsi *dsi = to_dsi(output);
        struct tegra_dsi_state *state;
        u32 value;
+       int err;
+
+       pm_runtime_get_sync(dsi->dev);
+
+       err = tegra_dsi_pad_calibrate(dsi);
+       if (err < 0)
+               dev_err(dsi->dev, "MIPI calibration failed: %d\n", err);
 
        state = tegra_dsi_get_state(dsi);
 
@@ -876,8 +923,6 @@ static void tegra_dsi_encoder_enable(struct drm_encoder *encoder)
 
        if (output->panel)
                drm_panel_enable(output->panel);
-
-       return;
 }
 
 static int
@@ -967,55 +1012,12 @@ static const struct drm_encoder_helper_funcs tegra_dsi_encoder_helper_funcs = {
        .atomic_check = tegra_dsi_encoder_atomic_check,
 };
 
-static int tegra_dsi_pad_enable(struct tegra_dsi *dsi)
-{
-       u32 value;
-
-       value = DSI_PAD_CONTROL_VS1_PULLDN(0) | DSI_PAD_CONTROL_VS1_PDIO(0);
-       tegra_dsi_writel(dsi, value, DSI_PAD_CONTROL_0);
-
-       return 0;
-}
-
-static int tegra_dsi_pad_calibrate(struct tegra_dsi *dsi)
-{
-       u32 value;
-
-       tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_0);
-       tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_1);
-       tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_2);
-       tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_3);
-       tegra_dsi_writel(dsi, 0, DSI_PAD_CONTROL_4);
-
-       /* start calibration */
-       tegra_dsi_pad_enable(dsi);
-
-       value = DSI_PAD_SLEW_UP(0x7) | DSI_PAD_SLEW_DN(0x7) |
-               DSI_PAD_LP_UP(0x1) | DSI_PAD_LP_DN(0x1) |
-               DSI_PAD_OUT_CLK(0x0);
-       tegra_dsi_writel(dsi, value, DSI_PAD_CONTROL_2);
-
-       value = DSI_PAD_PREEMP_PD_CLK(0x3) | DSI_PAD_PREEMP_PU_CLK(0x3) |
-               DSI_PAD_PREEMP_PD(0x03) | DSI_PAD_PREEMP_PU(0x3);
-       tegra_dsi_writel(dsi, value, DSI_PAD_CONTROL_3);
-
-       return tegra_mipi_calibrate(dsi->mipi);
-}
-
 static int tegra_dsi_init(struct host1x_client *client)
 {
        struct drm_device *drm = dev_get_drvdata(client->parent);
        struct tegra_dsi *dsi = host1x_client_to_dsi(client);
        int err;
 
-       reset_control_deassert(dsi->rst);
-
-       err = tegra_dsi_pad_calibrate(dsi);
-       if (err < 0) {
-               dev_err(dsi->dev, "MIPI calibration failed: %d\n", err);
-               goto reset;
-       }
-
        /* Gangsters must not register their own outputs. */
        if (!dsi->master) {
                dsi->output.dev = client->dev;
@@ -1038,12 +1040,9 @@ static int tegra_dsi_init(struct host1x_client *client)
                drm_connector_register(&dsi->output.connector);
 
                err = tegra_output_init(drm, &dsi->output);
-               if (err < 0) {
-                       dev_err(client->dev,
-                               "failed to initialize output: %d\n",
+               if (err < 0)
+                       dev_err(dsi->dev, "failed to initialize output: %d\n",
                                err);
-                       goto reset;
-               }
 
                dsi->output.encoder.possible_crtcs = 0x3;
        }
@@ -1055,10 +1054,6 @@ static int tegra_dsi_init(struct host1x_client *client)
        }
 
        return 0;
-
-reset:
-       reset_control_assert(dsi->rst);
-       return err;
 }
 
 static int tegra_dsi_exit(struct host1x_client *client)
@@ -1070,7 +1065,7 @@ static int tegra_dsi_exit(struct host1x_client *client)
        if (IS_ENABLED(CONFIG_DEBUG_FS))
                tegra_dsi_debugfs_exit(dsi);
 
-       reset_control_assert(dsi->rst);
+       regulator_disable(dsi->vdd);
 
        return 0;
 }
@@ -1501,67 +1496,41 @@ static int tegra_dsi_probe(struct platform_device *pdev)
        dsi->clk = devm_clk_get(&pdev->dev, NULL);
        if (IS_ERR(dsi->clk)) {
                dev_err(&pdev->dev, "cannot get DSI clock\n");
-               err = PTR_ERR(dsi->clk);
-               goto reset;
-       }
-
-       err = clk_prepare_enable(dsi->clk);
-       if (err < 0) {
-               dev_err(&pdev->dev, "cannot enable DSI clock\n");
-               goto reset;
+               return PTR_ERR(dsi->clk);
        }
 
        dsi->clk_lp = devm_clk_get(&pdev->dev, "lp");
        if (IS_ERR(dsi->clk_lp)) {
                dev_err(&pdev->dev, "cannot get low-power clock\n");
-               err = PTR_ERR(dsi->clk_lp);
-               goto disable_clk;
-       }
-
-       err = clk_prepare_enable(dsi->clk_lp);
-       if (err < 0) {
-               dev_err(&pdev->dev, "cannot enable low-power clock\n");
-               goto disable_clk;
+               return PTR_ERR(dsi->clk_lp);
        }
 
        dsi->clk_parent = devm_clk_get(&pdev->dev, "parent");
        if (IS_ERR(dsi->clk_parent)) {
                dev_err(&pdev->dev, "cannot get parent clock\n");
-               err = PTR_ERR(dsi->clk_parent);
-               goto disable_clk_lp;
+               return PTR_ERR(dsi->clk_parent);
        }
 
        dsi->vdd = devm_regulator_get(&pdev->dev, "avdd-dsi-csi");
        if (IS_ERR(dsi->vdd)) {
                dev_err(&pdev->dev, "cannot get VDD supply\n");
-               err = PTR_ERR(dsi->vdd);
-               goto disable_clk_lp;
-       }
-
-       err = regulator_enable(dsi->vdd);
-       if (err < 0) {
-               dev_err(&pdev->dev, "cannot enable VDD supply\n");
-               goto disable_clk_lp;
+               return PTR_ERR(dsi->vdd);
        }
 
        err = tegra_dsi_setup_clocks(dsi);
        if (err < 0) {
                dev_err(&pdev->dev, "cannot setup clocks\n");
-               goto disable_vdd;
+               return err;
        }
 
        regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
        dsi->regs = devm_ioremap_resource(&pdev->dev, regs);
-       if (IS_ERR(dsi->regs)) {
-               err = PTR_ERR(dsi->regs);
-               goto disable_vdd;
-       }
+       if (IS_ERR(dsi->regs))
+               return PTR_ERR(dsi->regs);
 
        dsi->mipi = tegra_mipi_request(&pdev->dev);
-       if (IS_ERR(dsi->mipi)) {
-               err = PTR_ERR(dsi->mipi);
-               goto disable_vdd;
-       }
+       if (IS_ERR(dsi->mipi))
+               return PTR_ERR(dsi->mipi);
 
        dsi->host.ops = &tegra_dsi_host_ops;
        dsi->host.dev = &pdev->dev;
@@ -1572,6 +1541,9 @@ static int tegra_dsi_probe(struct platform_device *pdev)
                goto mipi_free;
        }
 
+       platform_set_drvdata(pdev, dsi);
+       pm_runtime_enable(&pdev->dev);
+
        INIT_LIST_HEAD(&dsi->client.list);
        dsi->client.ops = &dsi_client_ops;
        dsi->client.dev = &pdev->dev;
@@ -1583,22 +1555,12 @@ static int tegra_dsi_probe(struct platform_device *pdev)
                goto unregister;
        }
 
-       platform_set_drvdata(pdev, dsi);
-
        return 0;
 
 unregister:
        mipi_dsi_host_unregister(&dsi->host);
 mipi_free:
        tegra_mipi_free(dsi->mipi);
-disable_vdd:
-       regulator_disable(dsi->vdd);
-disable_clk_lp:
-       clk_disable_unprepare(dsi->clk_lp);
-disable_clk:
-       clk_disable_unprepare(dsi->clk);
-reset:
-       reset_control_assert(dsi->rst);
        return err;
 }
 
@@ -1607,6 +1569,8 @@ static int tegra_dsi_remove(struct platform_device *pdev)
        struct tegra_dsi *dsi = platform_get_drvdata(pdev);
        int err;
 
+       pm_runtime_disable(&pdev->dev);
+
        err = host1x_client_unregister(&dsi->client);
        if (err < 0) {
                dev_err(&pdev->dev, "failed to unregister host1x client: %d\n",
@@ -1619,14 +1583,78 @@ static int tegra_dsi_remove(struct platform_device *pdev)
        mipi_dsi_host_unregister(&dsi->host);
        tegra_mipi_free(dsi->mipi);
 
-       regulator_disable(dsi->vdd);
+       return 0;
+}
+
+#ifdef CONFIG_PM
+static int tegra_dsi_suspend(struct device *dev)
+{
+       struct tegra_dsi *dsi = dev_get_drvdata(dev);
+       int err;
+
+       err = reset_control_assert(dsi->rst);
+       if (err < 0) {
+               dev_err(dev, "failed to assert reset: %d\n", err);
+               return err;
+       }
+
+       usleep_range(1000, 2000);
+
        clk_disable_unprepare(dsi->clk_lp);
        clk_disable_unprepare(dsi->clk);
-       reset_control_assert(dsi->rst);
+
+       regulator_disable(dsi->vdd);
 
        return 0;
 }
 
+static int tegra_dsi_resume(struct device *dev)
+{
+       struct tegra_dsi *dsi = dev_get_drvdata(dev);
+       int err;
+
+       err = regulator_enable(dsi->vdd);
+       if (err < 0) {
+               dev_err(dsi->dev, "failed to enable VDD supply: %d\n", err);
+               return err;
+       }
+
+       err = clk_prepare_enable(dsi->clk);
+       if (err < 0) {
+               dev_err(dev, "cannot enable DSI clock: %d\n", err);
+               goto disable_vdd;
+       }
+
+       err = clk_prepare_enable(dsi->clk_lp);
+       if (err < 0) {
+               dev_err(dev, "cannot enable low-power clock: %d\n", err);
+               goto disable_clk;
+       }
+
+       usleep_range(1000, 2000);
+
+       err = reset_control_deassert(dsi->rst);
+       if (err < 0) {
+               dev_err(dev, "cannot assert reset: %d\n", err);
+               goto disable_clk_lp;
+       }
+
+       return 0;
+
+disable_clk_lp:
+       clk_disable_unprepare(dsi->clk_lp);
+disable_clk:
+       clk_disable_unprepare(dsi->clk);
+disable_vdd:
+       regulator_disable(dsi->vdd);
+       return err;
+}
+#endif
+
+static const struct dev_pm_ops tegra_dsi_pm_ops = {
+       SET_RUNTIME_PM_OPS(tegra_dsi_suspend, tegra_dsi_resume, NULL)
+};
+
 static const struct of_device_id tegra_dsi_of_match[] = {
        { .compatible = "nvidia,tegra210-dsi", },
        { .compatible = "nvidia,tegra132-dsi", },
@@ -1640,6 +1668,7 @@ struct platform_driver tegra_dsi_driver = {
        .driver = {
                .name = "tegra-dsi",
                .of_match_table = tegra_dsi_of_match,
+               .pm = &tegra_dsi_pm_ops,
        },
        .probe = tegra_dsi_probe,
        .remove = tegra_dsi_remove,