diff --git a/drivers/opp/core.c b/drivers/opp/core.c index 92fa94a6dcc1..a0f72c732718 100644 --- a/drivers/opp/core.c +++ b/drivers/opp/core.c @@ -281,6 +281,23 @@ unsigned long dev_pm_opp_get_suspend_opp_freq(struct device *dev) } EXPORT_SYMBOL_GPL(dev_pm_opp_get_suspend_opp_freq); +int _get_opp_count(struct opp_table *opp_table) +{ + struct dev_pm_opp *opp; + int count = 0; + + mutex_lock(&opp_table->lock); + + list_for_each_entry(opp, &opp_table->opp_list, node) { + if (opp->available) + count++; + } + + mutex_unlock(&opp_table->lock); + + return count; +} + /** * dev_pm_opp_get_opp_count() - Get number of opps available in the opp table * @dev: device for which we do this operation @@ -291,25 +308,17 @@ EXPORT_SYMBOL_GPL(dev_pm_opp_get_suspend_opp_freq); int dev_pm_opp_get_opp_count(struct device *dev) { struct opp_table *opp_table; - struct dev_pm_opp *temp_opp; - int count = 0; + int count; opp_table = _find_opp_table(dev); if (IS_ERR(opp_table)) { count = PTR_ERR(opp_table); dev_dbg(dev, "%s: OPP table not found (%d)\n", __func__, count); - return count; + return 0; } - mutex_lock(&opp_table->lock); - - list_for_each_entry(temp_opp, &opp_table->opp_list, node) { - if (temp_opp->available) - count++; - } - - mutex_unlock(&opp_table->lock); + count = _get_opp_count(opp_table); dev_pm_opp_put_opp_table(opp_table); return count; @@ -985,6 +994,43 @@ static bool _opp_supported_by_regulators(struct dev_pm_opp *opp, return true; } +static int _opp_is_duplicate(struct device *dev, struct dev_pm_opp *new_opp, + struct opp_table *opp_table, + struct list_head **head) +{ + struct dev_pm_opp *opp; + + /* + * Insert new OPP in order of increasing frequency and discard if + * already present. + * + * Need to use &opp_table->opp_list in the condition part of the 'for' + * loop, don't replace it with head otherwise it will become an infinite + * loop. + */ + list_for_each_entry(opp, &opp_table->opp_list, node) { + if (new_opp->rate > opp->rate) { + *head = &opp->node; + continue; + } + + if (new_opp->rate < opp->rate) + return 0; + + /* Duplicate OPPs */ + dev_warn(dev, "%s: duplicate OPPs detected. Existing: freq: %lu, volt: %lu, enabled: %d. New: freq: %lu, volt: %lu, enabled: %d\n", + __func__, opp->rate, opp->supplies[0].u_volt, + opp->available, new_opp->rate, + new_opp->supplies[0].u_volt, new_opp->available); + + /* Should we compare voltages for all regulators here ? */ + return opp->available && + new_opp->supplies[0].u_volt == opp->supplies[0].u_volt ? -EBUSY : -EEXIST; + } + + return 0; +} + /* * Returns: * 0: On success. And appropriate error message for duplicate OPPs. @@ -996,44 +1042,20 @@ static bool _opp_supported_by_regulators(struct dev_pm_opp *opp, * should be considered an error by the callers of _opp_add(). */ int _opp_add(struct device *dev, struct dev_pm_opp *new_opp, - struct opp_table *opp_table) + struct opp_table *opp_table, bool rate_not_available) { - struct dev_pm_opp *opp; struct list_head *head; int ret; - /* - * Insert new OPP in order of increasing frequency and discard if - * already present. - * - * Need to use &opp_table->opp_list in the condition part of the 'for' - * loop, don't replace it with head otherwise it will become an infinite - * loop. - */ mutex_lock(&opp_table->lock); head = &opp_table->opp_list; - list_for_each_entry(opp, &opp_table->opp_list, node) { - if (new_opp->rate > opp->rate) { - head = &opp->node; - continue; + if (likely(!rate_not_available)) { + ret = _opp_is_duplicate(dev, new_opp, opp_table, &head); + if (ret) { + mutex_unlock(&opp_table->lock); + return ret; } - - if (new_opp->rate < opp->rate) - break; - - /* Duplicate OPPs */ - dev_warn(dev, "%s: duplicate OPPs detected. Existing: freq: %lu, volt: %lu, enabled: %d. New: freq: %lu, volt: %lu, enabled: %d\n", - __func__, opp->rate, opp->supplies[0].u_volt, - opp->available, new_opp->rate, - new_opp->supplies[0].u_volt, new_opp->available); - - /* Should we compare voltages for all regulators here ? */ - ret = opp->available && - new_opp->supplies[0].u_volt == opp->supplies[0].u_volt ? -EBUSY : -EEXIST; - - mutex_unlock(&opp_table->lock); - return ret; } if (opp_table->get_pstate) @@ -1104,7 +1126,7 @@ int _opp_add_v1(struct opp_table *opp_table, struct device *dev, new_opp->available = true; new_opp->dynamic = dynamic; - ret = _opp_add(dev, new_opp, opp_table); + ret = _opp_add(dev, new_opp, opp_table, false); if (ret) { /* Don't return error for duplicate OPPs */ if (ret == -EBUSY) diff --git a/drivers/opp/debugfs.c b/drivers/opp/debugfs.c index b03c03576a62..e6828e5f81b0 100644 --- a/drivers/opp/debugfs.c +++ b/drivers/opp/debugfs.c @@ -77,10 +77,21 @@ int opp_debug_create_one(struct dev_pm_opp *opp, struct opp_table *opp_table) { struct dentry *pdentry = opp_table->dentry; struct dentry *d; + unsigned long id; char name[25]; /* 20 chars for 64 bit value + 5 (opp:\0) */ - /* Rate is unique to each OPP, use it to give opp-name */ - snprintf(name, sizeof(name), "opp:%lu", opp->rate); + /* + * Get directory name for OPP. + * + * - Normally rate is unique to each OPP, use it to get unique opp-name. + * - For some devices rate isn't available, use index instead. + */ + if (likely(opp->rate)) + id = opp->rate; + else + id = _get_opp_count(opp_table); + + snprintf(name, sizeof(name), "opp:%lu", id); /* Create per-opp directory */ d = debugfs_create_dir(name, pdentry); diff --git a/drivers/opp/of.c b/drivers/opp/of.c index cb716aa2f44b..c5c5afcaa2e3 100644 --- a/drivers/opp/of.c +++ b/drivers/opp/of.c @@ -292,6 +292,7 @@ static int _opp_add_static_v2(struct opp_table *opp_table, struct device *dev, u64 rate; u32 val; int ret; + bool rate_not_available = false; new_opp = _opp_allocate(opp_table); if (!new_opp) @@ -299,8 +300,21 @@ static int _opp_add_static_v2(struct opp_table *opp_table, struct device *dev, ret = of_property_read_u64(np, "opp-hz", &rate); if (ret < 0) { - dev_err(dev, "%s: opp-hz not found\n", __func__); - goto free_opp; + /* "opp-hz" is optional for devices like power domains. */ + if (!of_find_property(dev->of_node, "#power-domain-cells", + NULL)) { + dev_err(dev, "%s: opp-hz not found\n", __func__); + goto free_opp; + } + + rate_not_available = true; + } else { + /* + * Rate is defined as an unsigned long in clk API, and so + * casting explicitly to its type. Must be fixed once rate is 64 + * bit guaranteed in clk API. + */ + new_opp->rate = (unsigned long)rate; } /* Check if the OPP supports hardware's hierarchy of versions or not */ @@ -309,12 +323,6 @@ static int _opp_add_static_v2(struct opp_table *opp_table, struct device *dev, goto free_opp; } - /* - * Rate is defined as an unsigned long in clk API, and so casting - * explicitly to its type. Must be fixed once rate is 64 bit - * guaranteed in clk API. - */ - new_opp->rate = (unsigned long)rate; new_opp->turbo = of_property_read_bool(np, "turbo-mode"); new_opp->np = np; @@ -328,7 +336,7 @@ static int _opp_add_static_v2(struct opp_table *opp_table, struct device *dev, if (ret) goto free_opp; - ret = _opp_add(dev, new_opp, opp_table); + ret = _opp_add(dev, new_opp, opp_table, rate_not_available); if (ret) { /* Don't return error for duplicate OPPs */ if (ret == -EBUSY) diff --git a/drivers/opp/opp.h b/drivers/opp/opp.h index 4d00061648a3..381a4fb15d5c 100644 --- a/drivers/opp/opp.h +++ b/drivers/opp/opp.h @@ -188,13 +188,14 @@ struct opp_table { /* Routines internal to opp core */ void _get_opp_table_kref(struct opp_table *opp_table); +int _get_opp_count(struct opp_table *opp_table); struct opp_table *_find_opp_table(struct device *dev); struct opp_device *_add_opp_dev(const struct device *dev, struct opp_table *opp_table); void _dev_pm_opp_remove_table(struct opp_table *opp_table, struct device *dev, bool remove_all); void _dev_pm_opp_find_and_remove_table(struct device *dev, bool remove_all); struct dev_pm_opp *_opp_allocate(struct opp_table *opp_table); void _opp_free(struct dev_pm_opp *opp); -int _opp_add(struct device *dev, struct dev_pm_opp *new_opp, struct opp_table *opp_table); +int _opp_add(struct device *dev, struct dev_pm_opp *new_opp, struct opp_table *opp_table, bool rate_not_available); int _opp_add_v1(struct opp_table *opp_table, struct device *dev, unsigned long freq, long u_volt, bool dynamic); void _dev_pm_opp_cpumask_remove_table(const struct cpumask *cpumask, bool of); struct opp_table *_add_opp_table(struct device *dev);