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 <pgwipeout@xxxxxxxxx> Tested-by: Nicolas Chauvet <kwizart@xxxxxxxxx> Signed-off-by: Dmitry Osipenko <digetx@xxxxxxxxx> --- 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