Add OPP and add PM support to the GR3D driver. This is required for
enabling system-wide DVFS and supporting dynamic power management using
a generic power domain.

Tested-by: Peter Geis <pgwipe...@gmail.com>
Tested-by: Nicolas Chauvet <kwiz...@gmail.com>
Signed-off-by: Dmitry Osipenko <dig...@gmail.com>
---
 drivers/gpu/drm/tegra/gr3d.c | 264 +++++++++++++++++++++++++++++++----
 1 file changed, 238 insertions(+), 26 deletions(-)

diff --git a/drivers/gpu/drm/tegra/gr3d.c b/drivers/gpu/drm/tegra/gr3d.c
index b0b8154e8104..11c38af584ee 100644
--- a/drivers/gpu/drm/tegra/gr3d.c
+++ b/drivers/gpu/drm/tegra/gr3d.c
@@ -10,8 +10,12 @@
 #include <linux/module.h>
 #include <linux/of_device.h>
 #include <linux/platform_device.h>
+#include <linux/pm_domain.h>
+#include <linux/pm_opp.h>
+#include <linux/pm_runtime.h>
 #include <linux/reset.h>
 
+#include <soc/tegra/common.h>
 #include <soc/tegra/pmc.h>
 
 #include "drm.h"
@@ -31,6 +35,9 @@ struct gr3d {
        struct reset_control *rst;
 
        const struct gr3d_soc *soc;
+       struct clk_bulk_data clocks[2];
+       unsigned int nclocks;
+       bool legacy_pd;
 
        DECLARE_BITMAP(addr_regs, GR3D_NUM_REGS);
 };
@@ -278,10 +285,120 @@ static const u32 gr3d_addr_regs[] = {
        GR3D_GLOBAL_SAMP23SURFADDR(15),
 };
 
+static void gr3d_pm_runtime_release(void *dev)
+{
+       pm_runtime_put(dev);
+       pm_runtime_disable(dev);
+}
+
+static int gr3d_link_power_domain(struct device *dev, struct device *pd_dev)
+{
+       const u32 link_flags = DL_FLAG_STATELESS | DL_FLAG_PM_RUNTIME;
+       struct device_link *link;
+       int err;
+
+       link = device_link_add(dev, pd_dev, link_flags);
+       if (!link) {
+               dev_err(dev, "failed to link to %s\n", dev_name(pd_dev));
+               return -EINVAL;
+       }
+
+       err = devm_add_action_or_reset(dev, (void *)device_link_del, link);
+       if (err)
+               return err;
+
+       return 0;
+}
+
+static int devm_gr3d_init_power(struct device *dev, struct gr3d *gr3d)
+{
+       const char *opp_genpd_names[] = { "3d0", "3d1", NULL };
+       struct device **opp_virt_dev;
+       struct opp_table *opp_table;
+       unsigned int i, num_domains;
+       struct device *pd_dev;
+       int err;
+
+       err = of_count_phandle_with_args(dev->of_node, "power-domains",
+                                        "#power-domain-cells");
+       if (err < 0) {
+               if (err != -ENOENT)
+                       return err;
+
+               /*
+                * Older device-trees don't use GENPD. In this case we should
+                * toggle power domain manually.
+                */
+               gr3d->legacy_pd = true;
+               goto power_up;
+       }
+
+       num_domains = err;
+
+       /*
+        * The PM domain core automatically attaches a single power domain,
+        * otherwise it skips attaching completely. We have a single domain
+        * on Tegra20 and two domains on Tegra30+.
+        */
+       if (dev->pm_domain)
+               goto power_up;
+
+       opp_table = devm_pm_opp_attach_genpd(dev, opp_genpd_names, 
&opp_virt_dev);
+       if (IS_ERR(opp_table))
+               return PTR_ERR(opp_table);
+
+       for (i = 0; opp_genpd_names[i]; i++) {
+               pd_dev = opp_virt_dev[i];
+               if (!pd_dev) {
+                       dev_err(dev, "failed to get %s power domain\n",
+                               opp_genpd_names[i]);
+                       return -EINVAL;
+               }
+
+               err = gr3d_link_power_domain(dev, pd_dev);
+               if (err)
+                       return err;
+       }
+
+power_up:
+       pm_runtime_enable(dev);
+       err = pm_runtime_get_sync(dev);
+       if (err < 0) {
+               gr3d_pm_runtime_release(dev);
+               return err;
+       }
+
+       err = devm_add_action_or_reset(dev, gr3d_pm_runtime_release, dev);
+       if (err)
+               return err;
+
+       return 0;
+}
+
+static int gr3d_set_opp(struct dev_pm_set_opp_data *data)
+{
+       struct gr3d *gr3d = dev_get_drvdata(data->dev);
+       unsigned int i;
+       int err;
+
+       for (i = 0; i < gr3d->nclocks; i++) {
+               err = clk_set_rate(gr3d->clocks[i].clk, data->new_opp.rate);
+               if (err) {
+                       dev_err(data->dev, "failed to set %s rate to %lu: %d\n",
+                               gr3d->clocks[i].id, data->new_opp.rate, err);
+                       return err;
+               }
+       }
+
+       return 0;
+}
+
 static int gr3d_probe(struct platform_device *pdev)
 {
+       struct tegra_core_opp_params opp_params = {};
        struct device_node *np = pdev->dev.of_node;
        struct host1x_syncpt **syncpts;
+       struct opp_table *opp_table;
        struct gr3d *gr3d;
        unsigned int i;
        int err;
@@ -290,6 +407,8 @@ static int gr3d_probe(struct platform_device *pdev)
        if (!gr3d)
                return -ENOMEM;
 
+       platform_set_drvdata(pdev, gr3d);
+
        gr3d->soc = of_device_get_match_data(&pdev->dev);
 
        syncpts = devm_kzalloc(&pdev->dev, sizeof(*syncpts), GFP_KERNEL);
@@ -302,7 +421,11 @@ static int gr3d_probe(struct platform_device *pdev)
                return PTR_ERR(gr3d->clk);
        }
 
-       gr3d->rst = devm_reset_control_get(&pdev->dev, "3d");
+       gr3d->clocks[gr3d->nclocks].id = "3d";
+       gr3d->clocks[gr3d->nclocks].clk = gr3d->clk;
+       gr3d->nclocks++;
+
+       gr3d->rst = devm_reset_control_get_exclusive_released(&pdev->dev, "3d");
        if (IS_ERR(gr3d->rst)) {
                dev_err(&pdev->dev, "cannot get reset\n");
                return PTR_ERR(gr3d->rst);
@@ -315,31 +438,31 @@ static int gr3d_probe(struct platform_device *pdev)
                        return PTR_ERR(gr3d->clk_secondary);
                }
 
-               gr3d->rst_secondary = devm_reset_control_get(&pdev->dev,
-                                                               "3d2");
+               gr3d->clocks[gr3d->nclocks].id = "3d2";
+               gr3d->clocks[gr3d->nclocks].clk = gr3d->clk_secondary;
+               gr3d->nclocks++;
+
+               gr3d->rst_secondary =
+                       devm_reset_control_get_exclusive_released(&pdev->dev, 
"3d2");
                if (IS_ERR(gr3d->rst_secondary)) {
                        dev_err(&pdev->dev, "cannot get secondary reset\n");
                        return PTR_ERR(gr3d->rst_secondary);
                }
        }
 
-       err = tegra_powergate_sequence_power_up(TEGRA_POWERGATE_3D, gr3d->clk,
-                                               gr3d->rst);
-       if (err < 0) {
-               dev_err(&pdev->dev, "failed to power up 3D unit\n");
+       err = devm_gr3d_init_power(&pdev->dev, gr3d);
+       if (err)
                return err;
-       }
 
-       if (gr3d->clk_secondary) {
-               err = tegra_powergate_sequence_power_up(TEGRA_POWERGATE_3D1,
-                                                       gr3d->clk_secondary,
-                                                       gr3d->rst_secondary);
-               if (err < 0) {
-                       dev_err(&pdev->dev,
-                               "failed to power up secondary 3D unit\n");
-                       return err;
-               }
-       }
+       opp_table = devm_pm_opp_register_set_opp_helper(&pdev->dev, 
gr3d_set_opp);
+       if (IS_ERR(opp_table))
+               return PTR_ERR(opp_table);
+
+       opp_params.init_state = true;
+
+       err = devm_tegra_core_dev_init_opp_table(&pdev->dev, &opp_params);
+       if (err && err != -ENODEV)
+               return err;
 
        INIT_LIST_HEAD(&gr3d->client.base.list);
        gr3d->client.base.ops = &gr3d_client_ops;
@@ -363,8 +486,6 @@ static int gr3d_probe(struct platform_device *pdev)
        for (i = 0; i < ARRAY_SIZE(gr3d_addr_regs); i++)
                set_bit(gr3d_addr_regs[i], gr3d->addr_regs);
 
-       platform_set_drvdata(pdev, gr3d);
-
        return 0;
 }
 
@@ -380,23 +501,114 @@ static int gr3d_remove(struct platform_device *pdev)
                return err;
        }
 
-       if (gr3d->clk_secondary) {
-               reset_control_assert(gr3d->rst_secondary);
+       return 0;
+}
+
+static int __maybe_unused gr3d_runtime_suspend(struct device *dev)
+{
+       struct gr3d *gr3d = dev_get_drvdata(dev);
+       int err;
+
+       if (gr3d->legacy_pd && gr3d->clk_secondary) {
+               err = reset_control_assert(gr3d->rst_secondary);
+               if (err) {
+                       dev_err(dev, "failed to assert secondary reset: %d\n", 
err);
+                       return err;
+               }
+
                tegra_powergate_power_off(TEGRA_POWERGATE_3D1);
-               clk_disable_unprepare(gr3d->clk_secondary);
        }
 
-       reset_control_assert(gr3d->rst);
-       tegra_powergate_power_off(TEGRA_POWERGATE_3D);
-       clk_disable_unprepare(gr3d->clk);
+       if (gr3d->legacy_pd) {
+               err = reset_control_assert(gr3d->rst);
+               if (err) {
+                       dev_err(dev, "failed to assert reset: %d\n", err);
+                       return err;
+               }
+
+               tegra_powergate_power_off(TEGRA_POWERGATE_3D);
+       }
+
+       clk_bulk_disable_unprepare(gr3d->nclocks, gr3d->clocks);
+       reset_control_release(gr3d->rst_secondary);
+       reset_control_release(gr3d->rst);
+
+       return 0;
+}
+
+static int __maybe_unused gr3d_runtime_resume(struct device *dev)
+{
+       struct gr3d *gr3d = dev_get_drvdata(dev);
+       int err;
+
+       err = reset_control_acquire(gr3d->rst);
+       if (err) {
+               dev_err(dev, "failed to acquire reset: %d\n", err);
+               return err;
+       }
+
+       err = reset_control_acquire(gr3d->rst_secondary);
+       if (err) {
+               dev_err(dev, "failed to acquire secondary reset: %d\n", err);
+               goto release_reset_primary;
+       }
+
+       if (gr3d->legacy_pd) {
+               err = tegra_powergate_sequence_power_up(TEGRA_POWERGATE_3D,
+                                                       gr3d->clk, gr3d->rst);
+               if (err)
+                       goto release_reset_secondary;
+       }
+
+       if (gr3d->legacy_pd && gr3d->clk_secondary) {
+               err = tegra_powergate_sequence_power_up(TEGRA_POWERGATE_3D1,
+                                                       gr3d->clk_secondary,
+                                                       gr3d->rst_secondary);
+               if (err)
+                       goto release_reset_secondary;
+       }
+
+       err = clk_bulk_prepare_enable(gr3d->nclocks, gr3d->clocks);
+       if (err) {
+               dev_err(dev, "failed to enable clock: %d\n", err);
+               goto release_reset_secondary;
+       }
+
+       return 0;
+
+release_reset_secondary:
+       reset_control_release(gr3d->rst_secondary);
+
+release_reset_primary:
+       reset_control_release(gr3d->rst);
+
+       return err;
+}
+
+static __maybe_unused int gr3d_suspend(struct device *dev)
+{
+       struct gr3d *gr3d = dev_get_drvdata(dev);
+       int err;
+
+       host1x_channel_stop(gr3d->channel);
+
+       err = pm_runtime_force_suspend(dev);
+       if (err < 0)
+               return err;
 
        return 0;
 }
 
+static const struct dev_pm_ops tegra_gr3d_pm = {
+       SET_RUNTIME_PM_OPS(gr3d_runtime_suspend, gr3d_runtime_resume, NULL)
+       SET_SYSTEM_SLEEP_PM_OPS(gr3d_suspend, pm_runtime_force_resume)
+};
+
 struct platform_driver tegra_gr3d_driver = {
        .driver = {
                .name = "tegra-gr3d",
                .of_match_table = tegra_gr3d_match,
+               .pm = &tegra_gr3d_pm,
        },
        .probe = gr3d_probe,
        .remove = gr3d_remove,
-- 
2.29.2

_______________________________________________
devel mailing list
de...@linuxdriverproject.org
http://driverdev.linuxdriverproject.org/mailman/listinfo/driverdev-devel

Reply via email to