Thread (56 messages) flat view 56 messages, 3 authors, 2021-08-12

Re: [PATCH v7 02/37] soc/tegra: pmc: Implement attach_dev() of power domain drivers

From: Ulf Hansson <hidden>
Date: 2021-08-10 10:52:44
Also in: linux-tegra, lkml

On Tue, 10 Aug 2021 at 01:56, Dmitry Osipenko [off-list ref] wrote:
quoted hunk ↗ jump to hunk
09.08.2021 17:15, Ulf Hansson пишет:
quoted
quoted
We did that in a previous versions of this series where drivers were
calling devm_tegra_core_dev_init_opp_table() helper during the probe to
initialize performance state of the domain. Moving OPP state
initialization into central place made drivers cleaner by removing the
boilerplate code.
I am not against doing this in a central place, like $subject patch
suggests. As a matter of fact, it makes perfect sense to me.

However, what I am concerned about, is that you require to use genpd
internal data structures to do it. I think we should try to avoid
that.
Alright, what do you think about this:
diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c
index a934c679e6ce..5faed62075e9 100644
--- a/drivers/base/power/domain.c
+++ b/drivers/base/power/domain.c
@@ -2669,12 +2669,37 @@ static int __genpd_dev_pm_attach(struct device *dev, struct device *base_dev,
        dev->pm_domain->detach = genpd_dev_pm_detach;
        dev->pm_domain->sync = genpd_dev_pm_sync;

+       if (pd->default_performance_state) {
+               unsigned int default_pstate;
+
+               ret = pd->default_performance_state(pd, dev);
+               if (ret < 0) {
+                       dev_err(dev, "failed to get default performance state for PM domain %s: %d\n",
+                               pd->name, ret);
+                       goto out;
+               }
Adding a new callback seems reasonable to support this.
+
+               default_pstate = ret;
+
+               if (power_on) {
+                       ret = dev_pm_genpd_set_performance_state(dev, default_pstate);
However, this is more questionable to me.

First, I don't think we should care about whether this is "power_on"
or not. At this point, performance states are treated orthogonal to
idle states in genpd. We may decide to change that in some way, but
that deserves a different change.

Second, I don't think we should call
dev_pm_genpd_set_performance_state() from here. It's probably better
handled from the genpd callback itself, if/when needed.

That said, perhaps the new callback should just return a regular error
code and zero on success, rather than the current performance state.
See more below.
+                       if (ret) {
+                               dev_err(dev, "failed to set default performance state %u for PM domain %s: %d\n",
+                                       default_pstate, pd->name, ret);
+                               goto out;
+                       }
+               } else {
+                       dev_gpd_data(dev)->rpm_pstate = default_pstate;
No, this isn't the right thing to do.

It looks like you are trying to use the ->rpm_pstate for
synchronization with runtime PM for consumer drivers. This is fragile
as it depends on the runtime PM deployment in the consumer driver. I
think you should look at ->rpm_pstate as a variable solely for
managing save/restore of the performance state for the device, during
runtime suspend/resume in genpd.

Synchronization of a vote for a performance state for a device, needs
to be managed by calling dev_pm_genpd_set_performance_state() - or by
calling an OPP function that calls it, like dev_pm_opp_set_rate(), for
example.
quoted hunk ↗ jump to hunk
+               }
+       }
+
        if (power_on) {
                genpd_lock(pd);
                ret = genpd_power_on(pd, 0);
                genpd_unlock(pd);
        }

+out:
        if (ret)
                genpd_remove_device(pd, dev);
diff --git a/drivers/soc/tegra/pmc.c b/drivers/soc/tegra/pmc.c
index 81d1f019fa0c..9efb55f52462 100644
--- a/drivers/soc/tegra/pmc.c
+++ b/drivers/soc/tegra/pmc.c
@@ -518,15 +518,14 @@ static const char * const tegra_emc_compats[] = {
  * We retrieve clock rate of the attached device and initialize domain's
  * performance state in accordance to the clock rate.
  */
-static int tegra_pmc_pd_attach_dev(struct generic_pm_domain *genpd,
-                                  struct device *dev)
+static int tegra_pmc_genpd_default_perf_state(struct generic_pm_domain *genpd,
+                                             struct device *dev)
 {
-       struct generic_pm_domain_data *gpd_data = dev_gpd_data(dev);
        struct opp_table *opp_table, *pd_opp_table;
        struct generic_pm_domain *core_genpd;
        struct dev_pm_opp *opp, *pd_opp;
-       unsigned long rate, state;
        struct gpd_link *link;
+       unsigned long rate;
        struct clk *clk;
        u32 hw_version;
        int ret;
@@ -633,8 +632,7 @@ static int tegra_pmc_pd_attach_dev(struct generic_pm_domain *genpd,
         * RPM-resume of the device.  This means that drivers don't need to
         * explicitly initialize performance state.
         */
-       state = pm_genpd_opp_to_performance_state(&core_genpd->dev, pd_opp);
-       gpd_data->rpm_pstate = state;
+       ret = pm_genpd_opp_to_performance_state(&core_genpd->dev, pd_opp);
I don't see how this avoids tegra_pmc_genpd_default_perf_state() from
having to walk &genpd->child_links.

That's still an issue, right?
quoted hunk ↗ jump to hunk
        dev_pm_opp_put(pd_opp);

 put_pd_opp_table:
@@ -1383,7 +1381,7 @@ static int tegra_powergate_add(struct tegra_pmc *pmc, struct device_node *np)

        pg->id = id;
        pg->genpd.name = np->name;
-       pg->genpd.attach_dev = tegra_pmc_pd_attach_dev;
+       pg->genpd.default_performance_state = tegra_pmc_genpd_default_perf_state;
        pg->genpd.power_off = tegra_genpd_power_off;
        pg->genpd.power_on = tegra_genpd_power_on;
        pg->pmc = pmc;
@@ -1500,7 +1498,7 @@ static int tegra_pmc_core_pd_add(struct tegra_pmc *pmc, struct device_node *np)
                return -ENOMEM;

        genpd->name = np->name;
-       genpd->attach_dev = tegra_pmc_pd_attach_dev;
+       genpd->default_performance_state = tegra_pmc_genpd_default_perf_state;
        genpd->set_performance_state = tegra_pmc_core_pd_set_performance_state;
        genpd->opp_to_performance_state = tegra_pmc_core_pd_opp_to_performance_state;
diff --git a/include/linux/pm_domain.h b/include/linux/pm_domain.h
index 21a0577305ef..cd4867817ca5 100644
--- a/include/linux/pm_domain.h
+++ b/include/linux/pm_domain.h
@@ -143,6 +143,8 @@ struct generic_pm_domain {
                          struct device *dev);
        void (*detach_dev)(struct generic_pm_domain *domain,
                           struct device *dev);
+       int (*default_performance_state)(struct generic_pm_domain *domain,
+                                        struct device *dev);
        unsigned int flags;             /* Bit field of configs for genpd */
        struct genpd_power_state *states;
        void (*free_states)(struct genpd_power_state *states,
quoted
quoted
I can revert back to the previous variant, although this variant works
well too.
I looked at that code and in that path we end up calling
dev_pm_opp_set_rate(), after it has initialized the opp table for the
device.

Rather than doing the OF parsing above to find out the current state
for the device, why can't you just call dev_pm_opp_set_rate() to
initialize a proper vote instead?
For some devices clock rate is either preset by bootloader, or by clk driver, or by assigned-clocks in a device-tree. And then I don't see what's the difference in comparison to initialization for the current rate.

For some devices, like memory controller, we can't just change the clock rate because it's a complex procedure and some boards will use fixed rate, but the power vote still must be initialized.
I am not saying you should change the clock rate. The current code
path that runs via devm_tegra_core_dev_init_opp_table() just calls
clk_get_rate and then dev_pm_opp_set_rate() with the current rate to
vote for the corresponding OPP level. Right?

Isn't this exactly what you want? No?

Kind regards
Uffe
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help