@@ -754,6 +754,9 @@ static int _set_required_opps(struct device *dev,
if (!required_opp_tables)
return 0;
+ if (!_of_lazy_link_required_tables(opp_table))
+ return -EPROBE_DEFER;
+
/* Single genpd case */
if (!genpd_virt_devs && required_opp_tables[0]->is_genpd) {
pstate = likely(opp) ? opp->required_opps[0]->pstate : 0;
@@ -774,11 +777,16 @@ static int _set_required_opps(struct device *dev,
mutex_lock(&opp_table->genpd_virt_dev_lock);
for (i = 0; i < opp_table->required_opp_count; i++) {
- pstate = likely(opp) ? opp->required_opps[i]->pstate : 0;
-
if (!genpd_virt_devs[i])
continue;
+ if (!opp->required_opps[i]) {
+ ret = -ENODEV;
+ break;
+ }
+
+ pstate = likely(opp) ? opp->required_opps[i]->pstate : 0;
+
ret = dev_pm_genpd_set_performance_state(genpd_virt_devs[i], pstate);
if (ret) {
dev_err(dev, "Failed to set performance rate of %s: %d (%d)\n",
@@ -1945,8 +1953,11 @@ struct dev_pm_opp *dev_pm_opp_xlate_required_opp(struct opp_table *src_table,
if (!src_table || !dst_table || !src_opp)
return NULL;
+ _of_lazy_link_required_tables(src_table);
+
for (i = 0; i < src_table->required_opp_count; i++) {
- if (src_table->required_opp_tables[i]->np == dst_table->np)
+ if (src_table->required_opp_tables[i]
+ && src_table->required_opp_tables[i]->np == dst_table->np)
break;
}
@@ -2009,6 +2020,8 @@ int dev_pm_opp_xlate_performance_state(struct opp_table *src_table,
if (!src_table->required_opp_count)
return pstate;
+ _of_lazy_link_required_tables(src_table);
+
for (i = 0; i < src_table->required_opp_count; i++) {
if (src_table->required_opp_tables[i]->np == dst_table->np)
break;
@@ -2024,15 +2037,16 @@ int dev_pm_opp_xlate_performance_state(struct opp_table *src_table,
list_for_each_entry(opp, &src_table->opp_list, node) {
if (opp->pstate == pstate) {
- dest_pstate = opp->required_opps[i]->pstate;
- goto unlock;
+ if (opp->required_opps[i])
+ dest_pstate = opp->required_opps[i]->pstate;
+ break;
}
}
- pr_err("%s: Couldn't find matching OPP (%p: %p)\n", __func__, src_table,
- dst_table);
+ if (dest_pstate < 0)
+ pr_err("%s: Couldn't find matching OPP (%p: %p)\n", __func__,
+ src_table, dst_table);
-unlock:
mutex_unlock(&src_table->lock);
return dest_pstate;
@@ -143,7 +143,7 @@ static void _opp_table_free_required_tables(struct opp_table *opp_table)
for (i = 0; i < opp_table->required_opp_count; i++) {
if (IS_ERR_OR_NULL(required_opp_tables[i]))
- break;
+ continue;
dev_pm_opp_put_opp_table(required_opp_tables[i]);
}
@@ -163,8 +163,8 @@ static void _opp_table_alloc_required_tables(struct opp_table *opp_table,
struct device_node *opp_np)
{
struct opp_table **required_opp_tables;
- struct device_node *required_np, *np;
- int count, i;
+ struct device_node *np;
+ int count;
/* Traversing the first OPP node is all we need */
np = of_get_next_available_child(opp_np, NULL);
@@ -174,35 +174,65 @@ static void _opp_table_alloc_required_tables(struct opp_table *opp_table,
}
count = of_count_phandle_with_args(np, "required-opps", NULL);
+ of_node_put(np);
if (!count)
- goto put_np;
+ return;
required_opp_tables = kcalloc(count, sizeof(*required_opp_tables),
GFP_KERNEL);
if (!required_opp_tables)
- goto put_np;
+ return;
opp_table->required_opp_tables = required_opp_tables;
opp_table->required_opp_count = count;
+}
+
+/*
+ * Try to link all required tables and return true if all of them have been
+ * linked. Otherwise, return false.
+ */
+bool _of_lazy_link_required_tables(struct opp_table *src)
+{
+ struct dev_pm_opp *src_opp, *tmp_opp;
+ struct opp_table *req_table;
+ struct device_node *req_np;
+ int i, num_linked = 0;
- for (i = 0; i < count; i++) {
- required_np = of_parse_required_opp(np, i);
- if (!required_np)
- goto free_required_tables;
+ mutex_lock(&src->lock);
- required_opp_tables[i] = _find_table_of_opp_np(required_np);
- of_node_put(required_np);
+ if (list_empty(&src->opp_list))
+ goto out;
- if (IS_ERR(required_opp_tables[i]))
- goto free_required_tables;
- }
+ src_opp = list_first_entry(&src->opp_list, struct dev_pm_opp, node);
- goto put_np;
+ for (i = 0; i < src->required_opp_count; i++) {
+ if (src->required_opp_tables[i]) {
+ num_linked++;
+ continue;
+ }
-free_required_tables:
- _opp_table_free_required_tables(opp_table);
-put_np:
- of_node_put(np);
+ req_np = of_parse_required_opp(src_opp->np, i);
+ if (!req_np)
+ continue;
+
+ req_table = _find_table_of_opp_np(req_np);
+ of_node_put(req_np);
+ if (!req_table)
+ continue;
+
+ src->required_opp_tables[i] = req_table;
+ list_for_each_entry(tmp_opp, &src->opp_list, node) {
+ req_np = of_parse_required_opp(tmp_opp->np, i);
+ tmp_opp->required_opps[i] = _find_opp_of_np(req_table,
+ req_np);
+ of_node_put(req_np);
+ }
+ num_linked++;
+ }
+
+out:
+ mutex_unlock(&src->lock);
+ return num_linked == src->required_opp_count;
}
void _of_init_opp_table(struct opp_table *opp_table, struct device *dev,
@@ -265,7 +295,7 @@ void _of_opp_free_required_opps(struct opp_table *opp_table,
for (i = 0; i < opp_table->required_opp_count; i++) {
if (!required_opps[i])
- break;
+ continue;
/* Put the reference back */
dev_pm_opp_put(required_opps[i]);
@@ -280,9 +310,7 @@ static int _of_opp_alloc_required_opps(struct opp_table *opp_table,
struct dev_pm_opp *opp)
{
struct dev_pm_opp **required_opps;
- struct opp_table *required_table;
- struct device_node *np;
- int i, ret, count = opp_table->required_opp_count;
+ int count = opp_table->required_opp_count;
if (!count)
return 0;
@@ -293,32 +321,7 @@ static int _of_opp_alloc_required_opps(struct opp_table *opp_table,
opp->required_opps = required_opps;
- for (i = 0; i < count; i++) {
- required_table = opp_table->required_opp_tables[i];
-
- np = of_parse_required_opp(opp->np, i);
- if (unlikely(!np)) {
- ret = -ENODEV;
- goto free_required_opps;
- }
-
- required_opps[i] = _find_opp_of_np(required_table, np);
- of_node_put(np);
-
- if (!required_opps[i]) {
- pr_err("%s: Unable to find required OPP node: %pOF (%d)\n",
- __func__, opp->np, i);
- ret = -ENODEV;
- goto free_required_opps;
- }
- }
-
return 0;
-
-free_required_opps:
- _of_opp_free_required_opps(opp_table, opp);
-
- return ret;
}
static bool _opp_is_supported(struct device *dev, struct opp_table *opp_table,
@@ -691,6 +694,8 @@ static int _of_add_opp_table_v2(struct device *dev, struct opp_table *opp_table)
if (pstate_count)
opp_table->genpd_performance_state = true;
+ _of_lazy_link_required_tables(opp_table);
+
return 0;
remove_static_opp:
@@ -221,12 +221,17 @@ void _put_opp_list_kref(struct opp_table *opp_table);
void _of_init_opp_table(struct opp_table *opp_table, struct device *dev, int index);
void _of_clear_opp_table(struct opp_table *opp_table);
struct opp_table *_managed_opp(struct device *dev, int index);
+bool _of_lazy_link_required_tables(struct opp_table *src);
void _of_opp_free_required_opps(struct opp_table *opp_table,
struct dev_pm_opp *opp);
#else
static inline void _of_init_opp_table(struct opp_table *opp_table, struct device *dev, int index) {}
static inline void _of_clear_opp_table(struct opp_table *opp_table) {}
static inline struct opp_table *_managed_opp(struct device *dev, int index) { return NULL; }
+bool _of_lazy_link_required_tables(struct opp_table *src)
+{
+ return true;
+}
static inline void _of_opp_free_required_opps(struct opp_table *opp_table,
struct dev_pm_opp *opp) {}
#endif