@@ -2369,7 +2369,6 @@ static void _opp_detach_genpd(struct opp_table *opp_table)
continue;
dev_pm_domain_detach(opp_table->required_devs[index], false);
- opp_table->required_devs[index] = NULL;
}
}
@@ -2393,8 +2392,7 @@ static void _opp_detach_genpd(struct opp_table *opp_table)
static int _opp_attach_genpd(struct opp_table *opp_table, struct device *dev,
const char * const *names, struct device ***virt_devs)
{
- struct device *virt_dev, *gdev;
- struct opp_table *genpd_table;
+ struct device *virt_dev;
int index = 0, ret = -EINVAL;
const char * const *name = names;
@@ -2427,37 +2425,6 @@ static int _opp_attach_genpd(struct opp_table *opp_table, struct device *dev,
goto err;
}
- /*
- * 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.
- *
- * Cross check it again and fix if required.
- */
- gdev = dev_to_genpd_dev(virt_dev);
- if (IS_ERR(gdev)) {
- ret = PTR_ERR(gdev);
- goto err;
- }
-
- genpd_table = _find_opp_table(gdev);
- if (!IS_ERR(genpd_table)) {
- if (genpd_table != opp_table->required_opp_tables[index]) {
- dev_pm_opp_put_opp_table(opp_table->required_opp_tables[index]);
- opp_table->required_opp_tables[index] = genpd_table;
- } else {
- dev_pm_opp_put_opp_table(genpd_table);
- }
- }
-
- opp_table->required_devs[index] = virt_dev;
index++;
name++;
}
@@ -2774,6 +2774,61 @@ static void genpd_dev_pm_sync(struct device *dev)
genpd_queue_power_off_work(pd);
}
+static struct opp_table *genpd_find_opp_table(struct generic_pm_domain *genpd,
+ unsigned int depth)
+{
+ struct opp_table *opp_table;
+ struct gpd_link *link;
+
+ if (genpd->opp_table)
+ return genpd->opp_table;
+
+ list_for_each_entry(link, &genpd->child_links, child_node) {
+ struct generic_pm_domain *parent = link->parent;
+
+ genpd_lock_nested(parent, depth + 1);
+ opp_table = genpd_find_opp_table(parent, depth + 1);
+ genpd_unlock(parent);
+
+ if (opp_table)
+ return opp_table;
+ }
+
+ return NULL;
+}
+
+static int genpd_set_required_opp_dev(struct device *dev,
+ struct device *base_dev)
+{
+ struct generic_pm_domain *genpd = dev_to_genpd(dev);
+ struct opp_table *opp_table;
+ int ret = 0;
+
+ /* Limit support to non-providers for now. */
+ if (of_property_present(base_dev->of_node, "#power-domain-cells"))
+ return 0;
+
+ if (!dev_pm_opp_of_has_required_opp(base_dev))
+ return 0;
+
+ genpd_lock(genpd);
+ opp_table = genpd_find_opp_table(genpd, 0);
+ genpd_unlock(genpd);
+
+ if (opp_table) {
+ struct dev_pm_opp_config config = {
+ .required_dev = dev,
+ .required_opp_table = opp_table,
+ };
+
+ ret = devm_pm_opp_set_config(base_dev, &config);
+ if (ret < 0)
+ dev_err(dev, "failed to set opp config %d\n", ret);
+ }
+
+ return ret;
+}
+
static int genpd_set_required_opp(struct device *dev, unsigned int index)
{
int ret, pstate;
@@ -2830,6 +2885,10 @@ 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;
+ ret = genpd_set_required_opp_dev(dev, base_dev);
+ if (ret)
+ goto err;
+
ret = genpd_set_required_opp(dev, index);
if (ret)
goto err;
Through dev_pm_opp_set_config() the _opp_attach_genpd() allows consumer drivers to hook up a device to its PM domains. Their corresponding virtual devices that are created by genpd during attach, are later being assigned as the required_devs for the corresponding required OPPs. In _opp_attach_genpd() we are also cross-checking whether the attached device's required OPPs really belongs to its PM domain's OPP table - and tries to fix it up if possible. In principle this works fine, but sometimes it's not convenient for consumer drivers to use _opp_attach_genpd(). Especially in the single PM domain case, when a device is usually attached by the bus-level ->probe() callbacks. Moreover, we now have dev_pm_domain_attach|detach_list() that helps consumer drivers to attach their devices to their PM domains. To improve the situation, let's instead assign the required_devs during device attach in genpd, by using _opp_set_required_dev(). In this way, the cross-check of the OPP table is always being done. Note that, we still need to allow existing users of _opp_attach_genpd(), which makes this change slightly more complicated. On the other hand, once we have migrated those users to dev_pm_domain_attach|detach_list(), the entire _opp_attach_genpd() should be removed. Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org> --- Changes in v2: - Clarified the commitmsg. - Add a check for #power-domain-cells in genpd to avoid assigning the required-devs for non-genpd-providers. --- drivers/opp/core.c | 35 +----------------------- drivers/pmdomain/core.c | 59 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 34 deletions(-)