On Mon, 27 Sept 2021 at 00:42, Dmitry Osipenko <digetx@xxxxxxxxx> wrote: > > Add runtime PM and OPP support to the Host1x driver. For the starter we > will keep host1x always-on because dynamic power management require a major > refactoring of the driver code since lot's of code paths are missing the > RPM handling and we're going to remove some of these paths in the future. > > Tested-by: Peter Geis <pgwipeout@xxxxxxxxx> # Ouya T30 > Tested-by: Paul Fertser <fercerpav@xxxxxxxxx> # PAZ00 T20 > Tested-by: Nicolas Chauvet <kwizart@xxxxxxxxx> # PAZ00 T20 and TK1 T124 > Tested-by: Matt Merhar <mattmerhar@xxxxxxxxxxxxxx> # Ouya T30 > Signed-off-by: Dmitry Osipenko <digetx@xxxxxxxxx> > --- [...] > --- a/drivers/gpu/host1x/dev.c > +++ b/drivers/gpu/host1x/dev.c > @@ -6,14 +6,18 @@ > */ > > #include <linux/clk.h> > +#include <linux/delay.h> > #include <linux/dma-mapping.h> > #include <linux/io.h> > #include <linux/list.h> > #include <linux/module.h> > #include <linux/of_device.h> > #include <linux/of.h> > +#include <linux/pm_runtime.h> > #include <linux/slab.h> > > +#include <soc/tegra/common.h> > + > #define CREATE_TRACE_POINTS > #include <trace/events/host1x.h> > #undef CREATE_TRACE_POINTS > @@ -190,6 +194,9 @@ static void host1x_setup_sid_table(struct host1x *host) > const struct host1x_info *info = host->info; > unsigned int i; > > + if (!info->has_hypervisor) > + return; > + > for (i = 0; i < info->num_sid_entries; i++) { > const struct host1x_sid_entry *entry = &info->sid_table[i]; > > @@ -347,6 +354,27 @@ static void host1x_iommu_exit(struct host1x *host) > } > } > > +static int host1x_get_resets(struct host1x *host) > +{ > + int err; > + > + host->resets[0].id = "mc"; > + host->resets[1].id = "host1x"; > + host->nresets = ARRAY_SIZE(host->resets); > + > + err = devm_reset_control_bulk_get_optional_exclusive_released( > + host->dev, host->nresets, host->resets); > + if (err) { > + dev_err(host->dev, "failed to get reset: %d\n", err); > + return err; > + } > + > + if (WARN_ON(!host->resets[1].rstc)) > + return -ENOENT; > + > + return 0; > +} > + > static int host1x_probe(struct platform_device *pdev) > { > struct host1x *host; > @@ -423,12 +451,9 @@ static int host1x_probe(struct platform_device *pdev) > return err; > } > > - host->rst = devm_reset_control_get(&pdev->dev, "host1x"); > - if (IS_ERR(host->rst)) { > - err = PTR_ERR(host->rst); > - dev_err(&pdev->dev, "failed to get reset: %d\n", err); > + err = host1x_get_resets(host); > + if (err) > return err; > - } > > err = host1x_iommu_init(host); > if (err < 0) { > @@ -443,22 +468,10 @@ static int host1x_probe(struct platform_device *pdev) > goto iommu_exit; > } > > - err = clk_prepare_enable(host->clk); > - if (err < 0) { > - dev_err(&pdev->dev, "failed to enable clock\n"); > - goto free_channels; > - } > - > - err = reset_control_deassert(host->rst); > - if (err < 0) { > - dev_err(&pdev->dev, "failed to deassert reset: %d\n", err); > - goto unprepare_disable; > - } > - > err = host1x_syncpt_init(host); > if (err) { > dev_err(&pdev->dev, "failed to initialize syncpts\n"); > - goto reset_assert; > + goto free_channels; > } > > err = host1x_intr_init(host, syncpt_irq); > @@ -467,10 +480,18 @@ static int host1x_probe(struct platform_device *pdev) > goto deinit_syncpt; > } > > - host1x_debug_init(host); > + pm_runtime_enable(&pdev->dev); > + > + err = devm_tegra_core_dev_init_opp_table_common(&pdev->dev); > + if (err) > + goto pm_disable; > > - if (host->info->has_hypervisor) > - host1x_setup_sid_table(host); > + /* the driver's code isn't ready yet for the dynamic RPM */ > + err = pm_runtime_resume_and_get(&pdev->dev); > + if (err) > + goto pm_disable; > + > + host1x_debug_init(host); > > err = host1x_register(host); > if (err < 0) > @@ -486,13 +507,14 @@ static int host1x_probe(struct platform_device *pdev) > host1x_unregister(host); > deinit_debugfs: > host1x_debug_deinit(host); > + > + pm_runtime_put(&pdev->dev); pm_runtime_put() is asynchronous, so it may not actually succeed to trigger the ->runtime_suspend() callback to be invoked. Thus, this could end up that we leave clocks prepared/enabled when ->probe() fails, for example. I guess pm_runtime_put_sync_suspend() is slightly better. Another option is to call pm_runtime_force_suspend(), but then you must skip the call pm_runtime_disable() afterwards, as that has already been done inside that function. > +pm_disable: > + pm_runtime_disable(&pdev->dev); > + > host1x_intr_deinit(host); > deinit_syncpt: > host1x_syncpt_deinit(host); > -reset_assert: > - reset_control_assert(host->rst); > -unprepare_disable: > - clk_disable_unprepare(host->clk); > free_channels: > host1x_channel_list_free(&host->channel_list); > iommu_exit: > @@ -507,19 +529,94 @@ static int host1x_remove(struct platform_device *pdev) > > host1x_unregister(host); > host1x_debug_deinit(host); > + > + pm_runtime_put(&pdev->dev); Similar comment as in ->probe(). > + pm_runtime_disable(&pdev->dev); > + > host1x_intr_deinit(host); > host1x_syncpt_deinit(host); > - reset_control_assert(host->rst); > - clk_disable_unprepare(host->clk); > host1x_iommu_exit(host); > > return 0; > } > > + > + host1x_setup_sid_table(host); > + host1x_syncpt_restore(host); > + host1x_intr_start(host); > + > + return 0; > + > +disable_clk: > + clk_disable_unprepare(host->clk); > +release_reset: > + reset_control_bulk_release(host->nresets, host->resets); > + > + return err; > +} > + > +static const struct dev_pm_ops host1x_pm = { > + SET_RUNTIME_PM_OPS(host1x_runtime_suspend, host1x_runtime_resume, > + NULL) > + /* TODO: add system suspend-resume once driver will be ready for that */ > +}; [...] Kind regards Uffe