22.08.2021 21:35, Dmitry Osipenko пишет: > 20.08.2021 08:18, Viresh Kumar пишет: >> On 19-08-21, 16:55, Ulf Hansson wrote: >>> Right, that sounds reasonable. >>> >>> We already have pm_genpd_opp_to_performance_state() which translates >>> an OPP to a performance state. This function invokes the >>> ->opp_to_performance_state() for a genpd. Maybe we need to allow a >>> genpd to not have ->opp_to_performance_state() callback assigned >>> though, but continue up in the hierarchy to see if the parent has the >>> callback assigned, to make this work for Tegra? >>> >>> Perhaps we should add an API dev_pm_genpd_opp_to_performance_state(), >>> allowing us to pass the device instead of the genpd. But that's a >>> minor thing. >> >> I am not concerned a lot about how it gets implemented, and am not >> sure as well, as I haven't looked into these details since sometime. >> Any reasonable thing will be accepted, as simple as that. >> >>> Finally, the precondition to use the above, is to first get a handle >>> to an OPP table. This is where I am struggling to find a generic >>> solution, because I guess that would be platform or even consumer >>> driver specific for how to do this. And at what point should we do >>> this? > > GENPD core can't get OPP table handle, setting up OPP table is a > platform/driver specific operation. > >> Hmm, I am not very clear with the whole picture at this point of time. >> >> Dmitry, can you try to frame a sequence of events/calls/etc that will >> define what kind of devices we are looking at here, and how this can >> be made to work ? > > Could you please clarify what do you mean by a "kind of devices"? > > I made hack based on the recent discussions and it partially works. Getting > clock rate involves resuming device which backs the clock and it also may use > GENPD, so lockings are becoming complicated. It doesn't work at all if device > uses multiple domains because virtual domain device doesn't have OPP table. > > Setting up the performance state from a consumer driver is a cleaner variant > so far.
Thinking a bit more about this, I got a nicer variant which actually works in all cases for Tegra. Viresh / Ulf, what do you think about this: diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c index 3a13a942d012..814b0f7a1909 100644 --- a/drivers/base/power/domain.c +++ b/drivers/base/power/domain.c @@ -2700,15 +2700,28 @@ static int __genpd_dev_pm_attach(struct device *dev, struct device *base_dev, goto err; } else if (pstate > 0) { ret = dev_pm_genpd_set_performance_state(dev, pstate); - if (ret) + if (ret) { + dev_err(dev, "failed to set required performance state for power-domain %s: %d\n", + pd->name, ret); goto err; + } dev_gpd_data(dev)->default_pstate = pstate; } + + if (pd->get_performance_state) { + ret = pd->get_performance_state(pd, base_dev); + if (ret < 0) { + dev_err(dev, "failed to get performance state for power-domain %s: %d\n", + pd->name, ret); + goto err; + } + + dev_gpd_data(dev)->rpm_pstate = ret; + } + return 1; err: - dev_err(dev, "failed to set required performance state for power-domain %s: %d\n", - pd->name, ret); genpd_remove_device(pd, dev); return ret; } diff --git a/drivers/opp/core.c b/drivers/opp/core.c index 2f1da33c2cd5..5f045030879b 100644 --- a/drivers/opp/core.c +++ b/drivers/opp/core.c @@ -2136,7 +2136,7 @@ struct opp_table *dev_pm_opp_set_clkname(struct device *dev, const char *name) } /* clk shouldn't be initialized at this point */ - if (WARN_ON(opp_table->clk)) { + if (WARN_ON(!IS_ERR_OR_NULL(opp_table->clk))) { ret = -EBUSY; goto err; } @@ -2967,3 +2967,33 @@ int dev_pm_opp_sync(struct device *dev) return ret; } EXPORT_SYMBOL_GPL(dev_pm_opp_sync); + +/** + * dev_pm_opp_from_clk_rate() - Get OPP from current clock rate + * @dev: device for which we do this operation + * + * Get OPP which corresponds to the current clock rate of a device. + * + * Return: pointer to 'struct dev_pm_opp' on success and errorno otherwise. + */ +struct dev_pm_opp *dev_pm_opp_from_clk_rate(struct device *dev) +{ + struct dev_pm_opp *opp = ERR_PTR(-ENODEV); + struct opp_table *opp_table; + unsigned long freq; + + opp_table = _find_opp_table(dev); + if (IS_ERR(opp_table)) + return ERR_CAST(opp_table); + + if (!IS_ERR(opp_table->clk)) { + freq = clk_get_rate(opp_table->clk); + opp = _find_freq_ceil(opp_table, &freq); + } + + /* Drop reference taken by _find_opp_table() */ + dev_pm_opp_put_opp_table(opp_table); + + return opp; +} +EXPORT_SYMBOL_GPL(dev_pm_opp_from_clk_rate); diff --git a/drivers/soc/tegra/pmc.c b/drivers/soc/tegra/pmc.c index 7c9bc93147f1..fc863d84f8d5 100644 --- a/drivers/soc/tegra/pmc.c +++ b/drivers/soc/tegra/pmc.c @@ -506,6 +506,96 @@ static void tegra_pmc_scratch_writel(struct tegra_pmc *pmc, u32 value, writel(value, pmc->scratch + offset); } +static const char * const tegra_pd_no_perf_compats[] = { + "nvidia,tegra20-sclk", + "nvidia,tegra30-sclk", + "nvidia,tegra30-pllc", + "nvidia,tegra30-plle", + "nvidia,tegra30-pllm", + "nvidia,tegra20-dc", + "nvidia,tegra30-dc", + "nvidia,tegra20-emc", + "nvidia,tegra30-emc", + NULL, +}; + +static int tegra_pmc_pd_get_performance_state(struct generic_pm_domain *genpd, + struct device *dev) +{ + struct opp_table *hw_opp_table, *clk_opp_table; + struct dev_pm_opp *opp; + u32 hw_version; + int ret; + + /* + * Tegra114+ SocS don't support OPP yet. But if they will get OPP + * support, then we want to skip OPP for older kernels to preserve + * compatibility of newer DTBs with older kernels. + */ + if (!pmc->soc->supports_core_domain) + return 0; + + /* + * The EMC devices are a special case because we have a protection + * from non-EMC drivers getting clock handle before EMC driver is + * fully initialized. The goal of the protection is to prevent + * devfreq driver from getting failures if it will try to change + * EMC clock rate until clock is fully initialized. The EMC drivers + * will initialize the performance state by themselves. + * + * Display controller also is a special case because only controller + * driver could get the clock rate based on configuration of internal + * divider. + * + * Clock driver uses its own state syncing. + */ + if (of_device_compatible_match(dev->of_node, tegra_pd_no_perf_compats)) + return 0; + + if (of_machine_is_compatible("nvidia,tegra20")) + hw_version = BIT(tegra_sku_info.soc_process_id); + else + hw_version = BIT(tegra_sku_info.soc_speedo_id); + + hw_opp_table = dev_pm_opp_set_supported_hw(dev, &hw_version, 1); + if (IS_ERR(hw_opp_table)){ + dev_err(dev, "failed to set OPP supported HW: %pe\n", + hw_opp_table); + return PTR_ERR(hw_opp_table); + } + + clk_opp_table = dev_pm_opp_set_clkname(dev, NULL); + if (IS_ERR(clk_opp_table)){ + dev_err(dev, "failed to set OPP clk: %pe\n", clk_opp_table); + ret = PTR_ERR(clk_opp_table); + goto put_hw; + } + + ret = devm_pm_opp_of_add_table(dev); + if (ret) { + dev_err(dev, "failed to add OPP table: %d\n", ret); + goto put_clk; + } + + opp = dev_pm_opp_from_clk_rate(dev); + if (IS_ERR(opp)) { + dev_err(&genpd->dev, "failed to get current OPP for %s: %pe\n", + dev_name(dev), opp); + ret = PTR_ERR(opp); + } else { + ret = dev_pm_opp_get_required_pstate(opp, 0); + dev_pm_opp_put(opp); + } + + dev_pm_opp_of_remove_table(dev); +put_clk: + dev_pm_opp_put_clkname(clk_opp_table); +put_hw: + dev_pm_opp_put_supported_hw(hw_opp_table); + + return ret; +} + /* * TODO Figure out a way to call this with the struct tegra_pmc * passed in. * This currently doesn't work because readx_poll_timeout() can only operate @@ -1238,6 +1328,7 @@ static int tegra_powergate_add(struct tegra_pmc *pmc, struct device_node *np) pg->id = id; pg->genpd.name = np->name; + pg->genpd.get_performance_state = tegra_pmc_pd_get_performance_state; pg->genpd.power_off = tegra_genpd_power_off; pg->genpd.power_on = tegra_genpd_power_on; pg->pmc = pmc; @@ -1354,6 +1445,7 @@ static int tegra_pmc_core_pd_add(struct tegra_pmc *pmc, struct device_node *np) return -ENOMEM; genpd->name = "core"; + genpd->get_performance_state = tegra_pmc_pd_get_performance_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 67017c9390c8..abe33be9828f 100644 --- a/include/linux/pm_domain.h +++ b/include/linux/pm_domain.h @@ -133,6 +133,8 @@ struct generic_pm_domain { struct dev_pm_opp *opp); int (*set_performance_state)(struct generic_pm_domain *genpd, unsigned int state); + int (*get_performance_state)(struct generic_pm_domain *genpd, + struct device *dev); struct gpd_dev_ops dev_ops; s64 max_off_time_ns; /* Maximum allowed "suspended" time. */ ktime_t next_wakeup; /* Maintained by the domain governor */ diff --git a/include/linux/pm_opp.h b/include/linux/pm_opp.h index 686122b59935..e7fd0dd493ca 100644 --- a/include/linux/pm_opp.h +++ b/include/linux/pm_opp.h @@ -169,6 +169,7 @@ void dev_pm_opp_remove_table(struct device *dev); void dev_pm_opp_cpumask_remove_table(const struct cpumask *cpumask); int dev_pm_opp_sync_regulators(struct device *dev); int dev_pm_opp_sync(struct device *dev); +struct dev_pm_opp *dev_pm_opp_from_clk_rate(struct device *dev); #else static inline struct opp_table *dev_pm_opp_get_opp_table(struct device *dev) { @@ -440,6 +441,11 @@ static inline int dev_pm_opp_sync(struct device *dev) return -EOPNOTSUPP; } +static struct inline dev_pm_opp *dev_pm_opp_from_clk_rate(struct device *dev) +{ + return ERR_PTR(-EOPNOTSUPP); +} + #endif /* CONFIG_PM_OPP */ #if defined(CONFIG_PM_OPP) && defined(CONFIG_OF)