Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

OPP: Fix required_opp_tables for multiple genpds using same table

The required_opp_tables parsing is not perfect, as the OPP core does the
parsing solely based on the DT node pointers.

The core sets the required_opp_tables entry to the first OPP table in
the "opp_tables" list, that matches with the node pointer.

If the target DT OPP table is used by multiple devices and they all
create separate instances of 'struct opp_table' from it, then it is
possible that the required_opp_tables entry may be set to the incorrect
sibling device.

Unfortunately, there is no clear way to initialize the right values
during the initial parsing and we need to do this at a later point of
time.

Cross check the OPP table again while the genpds are attached and fix
them if required.

Also add a new API for the genpd core to fetch the device pointer for
the genpd.

Cc: Thorsten Leemhuis <regressions@leemhuis.info>
Reported-by: Vladimir Lypak <vladimir.lypak@gmail.com>
Closes: https://bugzilla.kernel.org/show_bug.cgi?id=218682
Co-developed-by: Vladimir Lypak <vladimir.lypak@gmail.com>
Signed-off-by: Viresh Kumar <viresh.kumar@linaro.org>
Reviewed-by: Ulf Hansson <ulf.hansson@linaro.org>

+46 -1
+30 -1
drivers/opp/core.c
··· 2394 2394 static int _opp_attach_genpd(struct opp_table *opp_table, struct device *dev, 2395 2395 const char * const *names, struct device ***virt_devs) 2396 2396 { 2397 - struct device *virt_dev; 2397 + struct device *virt_dev, *gdev; 2398 + struct opp_table *genpd_table; 2398 2399 int index = 0, ret = -EINVAL; 2399 2400 const char * const *name = names; 2400 2401 ··· 2426 2425 ret = virt_dev ? PTR_ERR(virt_dev) : -ENODEV; 2427 2426 dev_err(dev, "Couldn't attach to pm_domain: %d\n", ret); 2428 2427 goto err; 2428 + } 2429 + 2430 + /* 2431 + * The required_opp_tables parsing is not perfect, as the OPP 2432 + * core does the parsing solely based on the DT node pointers. 2433 + * The core sets the required_opp_tables entry to the first OPP 2434 + * table in the "opp_tables" list, that matches with the node 2435 + * pointer. 2436 + * 2437 + * If the target DT OPP table is used by multiple devices and 2438 + * they all create separate instances of 'struct opp_table' from 2439 + * it, then it is possible that the required_opp_tables entry 2440 + * may be set to the incorrect sibling device. 2441 + * 2442 + * Cross check it again and fix if required. 2443 + */ 2444 + gdev = dev_to_genpd_dev(virt_dev); 2445 + if (IS_ERR(gdev)) 2446 + return PTR_ERR(gdev); 2447 + 2448 + genpd_table = _find_opp_table(gdev); 2449 + if (!IS_ERR(genpd_table)) { 2450 + if (genpd_table != opp_table->required_opp_tables[index]) { 2451 + dev_pm_opp_put_opp_table(opp_table->required_opp_tables[index]); 2452 + opp_table->required_opp_tables[index] = genpd_table; 2453 + } else { 2454 + dev_pm_opp_put_opp_table(genpd_table); 2455 + } 2429 2456 } 2430 2457 2431 2458 /*
+10
drivers/pmdomain/core.c
··· 184 184 return pd_to_genpd(dev->pm_domain); 185 185 } 186 186 187 + struct device *dev_to_genpd_dev(struct device *dev) 188 + { 189 + struct generic_pm_domain *genpd = dev_to_genpd(dev); 190 + 191 + if (IS_ERR(genpd)) 192 + return ERR_CAST(genpd); 193 + 194 + return &genpd->dev; 195 + } 196 + 187 197 static int genpd_stop_dev(const struct generic_pm_domain *genpd, 188 198 struct device *dev) 189 199 {
+6
include/linux/pm_domain.h
··· 260 260 int pm_genpd_init(struct generic_pm_domain *genpd, 261 261 struct dev_power_governor *gov, bool is_off); 262 262 int pm_genpd_remove(struct generic_pm_domain *genpd); 263 + struct device *dev_to_genpd_dev(struct device *dev); 263 264 int dev_pm_genpd_set_performance_state(struct device *dev, unsigned int state); 264 265 int dev_pm_genpd_add_notifier(struct device *dev, struct notifier_block *nb); 265 266 int dev_pm_genpd_remove_notifier(struct device *dev); ··· 306 305 static inline int pm_genpd_remove(struct generic_pm_domain *genpd) 307 306 { 308 307 return -EOPNOTSUPP; 308 + } 309 + 310 + static inline struct device *dev_to_genpd_dev(struct device *dev) 311 + { 312 + return ERR_PTR(-EOPNOTSUPP); 309 313 } 310 314 311 315 static inline int dev_pm_genpd_set_performance_state(struct device *dev,