|
@@ -13,50 +13,52 @@
|
|
|
|
|
|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
|
|
|
|
|
+#include <linux/clk.h>
|
|
|
#include <linux/errno.h>
|
|
|
#include <linux/err.h>
|
|
|
#include <linux/slab.h>
|
|
|
#include <linux/device.h>
|
|
|
#include <linux/of.h>
|
|
|
#include <linux/export.h>
|
|
|
+#include <linux/regulator/consumer.h>
|
|
|
|
|
|
#include "opp.h"
|
|
|
|
|
|
/*
|
|
|
- * The root of the list of all devices. All device_opp structures branch off
|
|
|
- * from here, with each device_opp containing the list of opp it supports in
|
|
|
+ * The root of the list of all opp-tables. All opp_table structures branch off
|
|
|
+ * from here, with each opp_table containing the list of opps it supports in
|
|
|
* various states of availability.
|
|
|
*/
|
|
|
-static LIST_HEAD(dev_opp_list);
|
|
|
+static LIST_HEAD(opp_tables);
|
|
|
/* Lock to allow exclusive modification to the device and opp lists */
|
|
|
-DEFINE_MUTEX(dev_opp_list_lock);
|
|
|
+DEFINE_MUTEX(opp_table_lock);
|
|
|
|
|
|
#define opp_rcu_lockdep_assert() \
|
|
|
do { \
|
|
|
RCU_LOCKDEP_WARN(!rcu_read_lock_held() && \
|
|
|
- !lockdep_is_held(&dev_opp_list_lock), \
|
|
|
- "Missing rcu_read_lock() or " \
|
|
|
- "dev_opp_list_lock protection"); \
|
|
|
+ !lockdep_is_held(&opp_table_lock), \
|
|
|
+ "Missing rcu_read_lock() or " \
|
|
|
+ "opp_table_lock protection"); \
|
|
|
} while (0)
|
|
|
|
|
|
-static struct device_list_opp *_find_list_dev(const struct device *dev,
|
|
|
- struct device_opp *dev_opp)
|
|
|
+static struct opp_device *_find_opp_dev(const struct device *dev,
|
|
|
+ struct opp_table *opp_table)
|
|
|
{
|
|
|
- struct device_list_opp *list_dev;
|
|
|
+ struct opp_device *opp_dev;
|
|
|
|
|
|
- list_for_each_entry(list_dev, &dev_opp->dev_list, node)
|
|
|
- if (list_dev->dev == dev)
|
|
|
- return list_dev;
|
|
|
+ list_for_each_entry(opp_dev, &opp_table->dev_list, node)
|
|
|
+ if (opp_dev->dev == dev)
|
|
|
+ return opp_dev;
|
|
|
|
|
|
return NULL;
|
|
|
}
|
|
|
|
|
|
-static struct device_opp *_managed_opp(const struct device_node *np)
|
|
|
+static struct opp_table *_managed_opp(const struct device_node *np)
|
|
|
{
|
|
|
- struct device_opp *dev_opp;
|
|
|
+ struct opp_table *opp_table;
|
|
|
|
|
|
- list_for_each_entry_rcu(dev_opp, &dev_opp_list, node) {
|
|
|
- if (dev_opp->np == np) {
|
|
|
+ list_for_each_entry_rcu(opp_table, &opp_tables, node) {
|
|
|
+ if (opp_table->np == np) {
|
|
|
/*
|
|
|
* Multiple devices can point to the same OPP table and
|
|
|
* so will have same node-pointer, np.
|
|
@@ -64,7 +66,7 @@ static struct device_opp *_managed_opp(const struct device_node *np)
|
|
|
* But the OPPs will be considered as shared only if the
|
|
|
* OPP table contains a "opp-shared" property.
|
|
|
*/
|
|
|
- return dev_opp->shared_opp ? dev_opp : NULL;
|
|
|
+ return opp_table->shared_opp ? opp_table : NULL;
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -72,24 +74,24 @@ static struct device_opp *_managed_opp(const struct device_node *np)
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * _find_device_opp() - find device_opp struct using device pointer
|
|
|
- * @dev: device pointer used to lookup device OPPs
|
|
|
+ * _find_opp_table() - find opp_table struct using device pointer
|
|
|
+ * @dev: device pointer used to lookup OPP table
|
|
|
*
|
|
|
- * Search list of device OPPs for one containing matching device. Does a RCU
|
|
|
- * reader operation to grab the pointer needed.
|
|
|
+ * Search OPP table for one containing matching device. Does a RCU reader
|
|
|
+ * operation to grab the pointer needed.
|
|
|
*
|
|
|
- * Return: pointer to 'struct device_opp' if found, otherwise -ENODEV or
|
|
|
+ * Return: pointer to 'struct opp_table' if found, otherwise -ENODEV or
|
|
|
* -EINVAL based on type of error.
|
|
|
*
|
|
|
* Locking: For readers, this function must be called under rcu_read_lock().
|
|
|
- * device_opp is a RCU protected pointer, which means that device_opp is valid
|
|
|
+ * opp_table is a RCU protected pointer, which means that opp_table is valid
|
|
|
* as long as we are under RCU lock.
|
|
|
*
|
|
|
- * For Writers, this function must be called with dev_opp_list_lock held.
|
|
|
+ * For Writers, this function must be called with opp_table_lock held.
|
|
|
*/
|
|
|
-struct device_opp *_find_device_opp(struct device *dev)
|
|
|
+struct opp_table *_find_opp_table(struct device *dev)
|
|
|
{
|
|
|
- struct device_opp *dev_opp;
|
|
|
+ struct opp_table *opp_table;
|
|
|
|
|
|
opp_rcu_lockdep_assert();
|
|
|
|
|
@@ -98,9 +100,9 @@ struct device_opp *_find_device_opp(struct device *dev)
|
|
|
return ERR_PTR(-EINVAL);
|
|
|
}
|
|
|
|
|
|
- list_for_each_entry_rcu(dev_opp, &dev_opp_list, node)
|
|
|
- if (_find_list_dev(dev, dev_opp))
|
|
|
- return dev_opp;
|
|
|
+ list_for_each_entry_rcu(opp_table, &opp_tables, node)
|
|
|
+ if (_find_opp_dev(dev, opp_table))
|
|
|
+ return opp_table;
|
|
|
|
|
|
return ERR_PTR(-ENODEV);
|
|
|
}
|
|
@@ -213,22 +215,98 @@ EXPORT_SYMBOL_GPL(dev_pm_opp_is_turbo);
|
|
|
*/
|
|
|
unsigned long dev_pm_opp_get_max_clock_latency(struct device *dev)
|
|
|
{
|
|
|
- struct device_opp *dev_opp;
|
|
|
+ struct opp_table *opp_table;
|
|
|
unsigned long clock_latency_ns;
|
|
|
|
|
|
rcu_read_lock();
|
|
|
|
|
|
- dev_opp = _find_device_opp(dev);
|
|
|
- if (IS_ERR(dev_opp))
|
|
|
+ opp_table = _find_opp_table(dev);
|
|
|
+ if (IS_ERR(opp_table))
|
|
|
clock_latency_ns = 0;
|
|
|
else
|
|
|
- clock_latency_ns = dev_opp->clock_latency_ns_max;
|
|
|
+ clock_latency_ns = opp_table->clock_latency_ns_max;
|
|
|
|
|
|
rcu_read_unlock();
|
|
|
return clock_latency_ns;
|
|
|
}
|
|
|
EXPORT_SYMBOL_GPL(dev_pm_opp_get_max_clock_latency);
|
|
|
|
|
|
+/**
|
|
|
+ * dev_pm_opp_get_max_volt_latency() - Get max voltage latency in nanoseconds
|
|
|
+ * @dev: device for which we do this operation
|
|
|
+ *
|
|
|
+ * Return: This function returns the max voltage latency in nanoseconds.
|
|
|
+ *
|
|
|
+ * Locking: This function takes rcu_read_lock().
|
|
|
+ */
|
|
|
+unsigned long dev_pm_opp_get_max_volt_latency(struct device *dev)
|
|
|
+{
|
|
|
+ struct opp_table *opp_table;
|
|
|
+ struct dev_pm_opp *opp;
|
|
|
+ struct regulator *reg;
|
|
|
+ unsigned long latency_ns = 0;
|
|
|
+ unsigned long min_uV = ~0, max_uV = 0;
|
|
|
+ int ret;
|
|
|
+
|
|
|
+ rcu_read_lock();
|
|
|
+
|
|
|
+ opp_table = _find_opp_table(dev);
|
|
|
+ if (IS_ERR(opp_table)) {
|
|
|
+ rcu_read_unlock();
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ reg = opp_table->regulator;
|
|
|
+ if (IS_ERR(reg)) {
|
|
|
+ /* Regulator may not be required for device */
|
|
|
+ if (reg)
|
|
|
+ dev_err(dev, "%s: Invalid regulator (%ld)\n", __func__,
|
|
|
+ PTR_ERR(reg));
|
|
|
+ rcu_read_unlock();
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ list_for_each_entry_rcu(opp, &opp_table->opp_list, node) {
|
|
|
+ if (!opp->available)
|
|
|
+ continue;
|
|
|
+
|
|
|
+ if (opp->u_volt_min < min_uV)
|
|
|
+ min_uV = opp->u_volt_min;
|
|
|
+ if (opp->u_volt_max > max_uV)
|
|
|
+ max_uV = opp->u_volt_max;
|
|
|
+ }
|
|
|
+
|
|
|
+ rcu_read_unlock();
|
|
|
+
|
|
|
+ /*
|
|
|
+ * The caller needs to ensure that opp_table (and hence the regulator)
|
|
|
+ * isn't freed, while we are executing this routine.
|
|
|
+ */
|
|
|
+ ret = regulator_set_voltage_time(reg, min_uV, max_uV);
|
|
|
+ if (ret > 0)
|
|
|
+ latency_ns = ret * 1000;
|
|
|
+
|
|
|
+ return latency_ns;
|
|
|
+}
|
|
|
+EXPORT_SYMBOL_GPL(dev_pm_opp_get_max_volt_latency);
|
|
|
+
|
|
|
+/**
|
|
|
+ * dev_pm_opp_get_max_transition_latency() - Get max transition latency in
|
|
|
+ * nanoseconds
|
|
|
+ * @dev: device for which we do this operation
|
|
|
+ *
|
|
|
+ * Return: This function returns the max transition latency, in nanoseconds, to
|
|
|
+ * switch from one OPP to other.
|
|
|
+ *
|
|
|
+ * Locking: This function takes rcu_read_lock().
|
|
|
+ */
|
|
|
+unsigned long dev_pm_opp_get_max_transition_latency(struct device *dev)
|
|
|
+{
|
|
|
+ return dev_pm_opp_get_max_volt_latency(dev) +
|
|
|
+ dev_pm_opp_get_max_clock_latency(dev);
|
|
|
+}
|
|
|
+EXPORT_SYMBOL_GPL(dev_pm_opp_get_max_transition_latency);
|
|
|
+
|
|
|
/**
|
|
|
* dev_pm_opp_get_suspend_opp() - Get suspend opp
|
|
|
* @dev: device for which we do this operation
|
|
@@ -244,21 +322,21 @@ EXPORT_SYMBOL_GPL(dev_pm_opp_get_max_clock_latency);
|
|
|
*/
|
|
|
struct dev_pm_opp *dev_pm_opp_get_suspend_opp(struct device *dev)
|
|
|
{
|
|
|
- struct device_opp *dev_opp;
|
|
|
+ struct opp_table *opp_table;
|
|
|
|
|
|
opp_rcu_lockdep_assert();
|
|
|
|
|
|
- dev_opp = _find_device_opp(dev);
|
|
|
- if (IS_ERR(dev_opp) || !dev_opp->suspend_opp ||
|
|
|
- !dev_opp->suspend_opp->available)
|
|
|
+ opp_table = _find_opp_table(dev);
|
|
|
+ if (IS_ERR(opp_table) || !opp_table->suspend_opp ||
|
|
|
+ !opp_table->suspend_opp->available)
|
|
|
return NULL;
|
|
|
|
|
|
- return dev_opp->suspend_opp;
|
|
|
+ return opp_table->suspend_opp;
|
|
|
}
|
|
|
EXPORT_SYMBOL_GPL(dev_pm_opp_get_suspend_opp);
|
|
|
|
|
|
/**
|
|
|
- * dev_pm_opp_get_opp_count() - Get number of opps available in the opp list
|
|
|
+ * dev_pm_opp_get_opp_count() - Get number of opps available in the opp table
|
|
|
* @dev: device for which we do this operation
|
|
|
*
|
|
|
* Return: This function returns the number of available opps if there are any,
|
|
@@ -268,21 +346,21 @@ EXPORT_SYMBOL_GPL(dev_pm_opp_get_suspend_opp);
|
|
|
*/
|
|
|
int dev_pm_opp_get_opp_count(struct device *dev)
|
|
|
{
|
|
|
- struct device_opp *dev_opp;
|
|
|
+ struct opp_table *opp_table;
|
|
|
struct dev_pm_opp *temp_opp;
|
|
|
int count = 0;
|
|
|
|
|
|
rcu_read_lock();
|
|
|
|
|
|
- dev_opp = _find_device_opp(dev);
|
|
|
- if (IS_ERR(dev_opp)) {
|
|
|
- count = PTR_ERR(dev_opp);
|
|
|
- dev_err(dev, "%s: device OPP not found (%d)\n",
|
|
|
+ opp_table = _find_opp_table(dev);
|
|
|
+ if (IS_ERR(opp_table)) {
|
|
|
+ count = PTR_ERR(opp_table);
|
|
|
+ dev_err(dev, "%s: OPP table not found (%d)\n",
|
|
|
__func__, count);
|
|
|
goto out_unlock;
|
|
|
}
|
|
|
|
|
|
- list_for_each_entry_rcu(temp_opp, &dev_opp->opp_list, node) {
|
|
|
+ list_for_each_entry_rcu(temp_opp, &opp_table->opp_list, node) {
|
|
|
if (temp_opp->available)
|
|
|
count++;
|
|
|
}
|
|
@@ -299,7 +377,7 @@ EXPORT_SYMBOL_GPL(dev_pm_opp_get_opp_count);
|
|
|
* @freq: frequency to search for
|
|
|
* @available: true/false - match for available opp
|
|
|
*
|
|
|
- * Return: Searches for exact match in the opp list and returns pointer to the
|
|
|
+ * Return: Searches for exact match in the opp table and returns pointer to the
|
|
|
* matching opp if found, else returns ERR_PTR in case of error and should
|
|
|
* be handled using IS_ERR. Error return values can be:
|
|
|
* EINVAL: for bad pointer
|
|
@@ -323,19 +401,20 @@ struct dev_pm_opp *dev_pm_opp_find_freq_exact(struct device *dev,
|
|
|
unsigned long freq,
|
|
|
bool available)
|
|
|
{
|
|
|
- struct device_opp *dev_opp;
|
|
|
+ struct opp_table *opp_table;
|
|
|
struct dev_pm_opp *temp_opp, *opp = ERR_PTR(-ERANGE);
|
|
|
|
|
|
opp_rcu_lockdep_assert();
|
|
|
|
|
|
- dev_opp = _find_device_opp(dev);
|
|
|
- if (IS_ERR(dev_opp)) {
|
|
|
- int r = PTR_ERR(dev_opp);
|
|
|
- dev_err(dev, "%s: device OPP not found (%d)\n", __func__, r);
|
|
|
+ opp_table = _find_opp_table(dev);
|
|
|
+ if (IS_ERR(opp_table)) {
|
|
|
+ int r = PTR_ERR(opp_table);
|
|
|
+
|
|
|
+ dev_err(dev, "%s: OPP table not found (%d)\n", __func__, r);
|
|
|
return ERR_PTR(r);
|
|
|
}
|
|
|
|
|
|
- list_for_each_entry_rcu(temp_opp, &dev_opp->opp_list, node) {
|
|
|
+ list_for_each_entry_rcu(temp_opp, &opp_table->opp_list, node) {
|
|
|
if (temp_opp->available == available &&
|
|
|
temp_opp->rate == freq) {
|
|
|
opp = temp_opp;
|
|
@@ -371,7 +450,7 @@ EXPORT_SYMBOL_GPL(dev_pm_opp_find_freq_exact);
|
|
|
struct dev_pm_opp *dev_pm_opp_find_freq_ceil(struct device *dev,
|
|
|
unsigned long *freq)
|
|
|
{
|
|
|
- struct device_opp *dev_opp;
|
|
|
+ struct opp_table *opp_table;
|
|
|
struct dev_pm_opp *temp_opp, *opp = ERR_PTR(-ERANGE);
|
|
|
|
|
|
opp_rcu_lockdep_assert();
|
|
@@ -381,11 +460,11 @@ struct dev_pm_opp *dev_pm_opp_find_freq_ceil(struct device *dev,
|
|
|
return ERR_PTR(-EINVAL);
|
|
|
}
|
|
|
|
|
|
- dev_opp = _find_device_opp(dev);
|
|
|
- if (IS_ERR(dev_opp))
|
|
|
- return ERR_CAST(dev_opp);
|
|
|
+ opp_table = _find_opp_table(dev);
|
|
|
+ if (IS_ERR(opp_table))
|
|
|
+ return ERR_CAST(opp_table);
|
|
|
|
|
|
- list_for_each_entry_rcu(temp_opp, &dev_opp->opp_list, node) {
|
|
|
+ list_for_each_entry_rcu(temp_opp, &opp_table->opp_list, node) {
|
|
|
if (temp_opp->available && temp_opp->rate >= *freq) {
|
|
|
opp = temp_opp;
|
|
|
*freq = opp->rate;
|
|
@@ -421,7 +500,7 @@ EXPORT_SYMBOL_GPL(dev_pm_opp_find_freq_ceil);
|
|
|
struct dev_pm_opp *dev_pm_opp_find_freq_floor(struct device *dev,
|
|
|
unsigned long *freq)
|
|
|
{
|
|
|
- struct device_opp *dev_opp;
|
|
|
+ struct opp_table *opp_table;
|
|
|
struct dev_pm_opp *temp_opp, *opp = ERR_PTR(-ERANGE);
|
|
|
|
|
|
opp_rcu_lockdep_assert();
|
|
@@ -431,11 +510,11 @@ struct dev_pm_opp *dev_pm_opp_find_freq_floor(struct device *dev,
|
|
|
return ERR_PTR(-EINVAL);
|
|
|
}
|
|
|
|
|
|
- dev_opp = _find_device_opp(dev);
|
|
|
- if (IS_ERR(dev_opp))
|
|
|
- return ERR_CAST(dev_opp);
|
|
|
+ opp_table = _find_opp_table(dev);
|
|
|
+ if (IS_ERR(opp_table))
|
|
|
+ return ERR_CAST(opp_table);
|
|
|
|
|
|
- list_for_each_entry_rcu(temp_opp, &dev_opp->opp_list, node) {
|
|
|
+ list_for_each_entry_rcu(temp_opp, &opp_table->opp_list, node) {
|
|
|
if (temp_opp->available) {
|
|
|
/* go to the next node, before choosing prev */
|
|
|
if (temp_opp->rate > *freq)
|
|
@@ -451,130 +530,343 @@ struct dev_pm_opp *dev_pm_opp_find_freq_floor(struct device *dev,
|
|
|
}
|
|
|
EXPORT_SYMBOL_GPL(dev_pm_opp_find_freq_floor);
|
|
|
|
|
|
-/* List-dev Helpers */
|
|
|
-static void _kfree_list_dev_rcu(struct rcu_head *head)
|
|
|
+/*
|
|
|
+ * The caller needs to ensure that opp_table (and hence the clk) isn't freed,
|
|
|
+ * while clk returned here is used.
|
|
|
+ */
|
|
|
+static struct clk *_get_opp_clk(struct device *dev)
|
|
|
+{
|
|
|
+ struct opp_table *opp_table;
|
|
|
+ struct clk *clk;
|
|
|
+
|
|
|
+ rcu_read_lock();
|
|
|
+
|
|
|
+ opp_table = _find_opp_table(dev);
|
|
|
+ if (IS_ERR(opp_table)) {
|
|
|
+ dev_err(dev, "%s: device opp doesn't exist\n", __func__);
|
|
|
+ clk = ERR_CAST(opp_table);
|
|
|
+ goto unlock;
|
|
|
+ }
|
|
|
+
|
|
|
+ clk = opp_table->clk;
|
|
|
+ if (IS_ERR(clk))
|
|
|
+ dev_err(dev, "%s: No clock available for the device\n",
|
|
|
+ __func__);
|
|
|
+
|
|
|
+unlock:
|
|
|
+ rcu_read_unlock();
|
|
|
+ return clk;
|
|
|
+}
|
|
|
+
|
|
|
+static int _set_opp_voltage(struct device *dev, struct regulator *reg,
|
|
|
+ unsigned long u_volt, unsigned long u_volt_min,
|
|
|
+ unsigned long u_volt_max)
|
|
|
+{
|
|
|
+ int ret;
|
|
|
+
|
|
|
+ /* Regulator not available for device */
|
|
|
+ if (IS_ERR(reg)) {
|
|
|
+ dev_dbg(dev, "%s: regulator not available: %ld\n", __func__,
|
|
|
+ PTR_ERR(reg));
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ dev_dbg(dev, "%s: voltages (mV): %lu %lu %lu\n", __func__, u_volt_min,
|
|
|
+ u_volt, u_volt_max);
|
|
|
+
|
|
|
+ ret = regulator_set_voltage_triplet(reg, u_volt_min, u_volt,
|
|
|
+ u_volt_max);
|
|
|
+ if (ret)
|
|
|
+ dev_err(dev, "%s: failed to set voltage (%lu %lu %lu mV): %d\n",
|
|
|
+ __func__, u_volt_min, u_volt, u_volt_max, ret);
|
|
|
+
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * dev_pm_opp_set_rate() - Configure new OPP based on frequency
|
|
|
+ * @dev: device for which we do this operation
|
|
|
+ * @target_freq: frequency to achieve
|
|
|
+ *
|
|
|
+ * This configures the power-supplies and clock source to the levels specified
|
|
|
+ * by the OPP corresponding to the target_freq.
|
|
|
+ *
|
|
|
+ * Locking: This function takes rcu_read_lock().
|
|
|
+ */
|
|
|
+int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq)
|
|
|
+{
|
|
|
+ struct opp_table *opp_table;
|
|
|
+ struct dev_pm_opp *old_opp, *opp;
|
|
|
+ struct regulator *reg;
|
|
|
+ struct clk *clk;
|
|
|
+ unsigned long freq, old_freq;
|
|
|
+ unsigned long u_volt, u_volt_min, u_volt_max;
|
|
|
+ unsigned long ou_volt, ou_volt_min, ou_volt_max;
|
|
|
+ int ret;
|
|
|
+
|
|
|
+ if (unlikely(!target_freq)) {
|
|
|
+ dev_err(dev, "%s: Invalid target frequency %lu\n", __func__,
|
|
|
+ target_freq);
|
|
|
+ return -EINVAL;
|
|
|
+ }
|
|
|
+
|
|
|
+ clk = _get_opp_clk(dev);
|
|
|
+ if (IS_ERR(clk))
|
|
|
+ return PTR_ERR(clk);
|
|
|
+
|
|
|
+ freq = clk_round_rate(clk, target_freq);
|
|
|
+ if ((long)freq <= 0)
|
|
|
+ freq = target_freq;
|
|
|
+
|
|
|
+ old_freq = clk_get_rate(clk);
|
|
|
+
|
|
|
+ /* Return early if nothing to do */
|
|
|
+ if (old_freq == freq) {
|
|
|
+ dev_dbg(dev, "%s: old/new frequencies (%lu Hz) are same, nothing to do\n",
|
|
|
+ __func__, freq);
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ rcu_read_lock();
|
|
|
+
|
|
|
+ opp_table = _find_opp_table(dev);
|
|
|
+ if (IS_ERR(opp_table)) {
|
|
|
+ dev_err(dev, "%s: device opp doesn't exist\n", __func__);
|
|
|
+ rcu_read_unlock();
|
|
|
+ return PTR_ERR(opp_table);
|
|
|
+ }
|
|
|
+
|
|
|
+ old_opp = dev_pm_opp_find_freq_ceil(dev, &old_freq);
|
|
|
+ if (!IS_ERR(old_opp)) {
|
|
|
+ ou_volt = old_opp->u_volt;
|
|
|
+ ou_volt_min = old_opp->u_volt_min;
|
|
|
+ ou_volt_max = old_opp->u_volt_max;
|
|
|
+ } else {
|
|
|
+ dev_err(dev, "%s: failed to find current OPP for freq %lu (%ld)\n",
|
|
|
+ __func__, old_freq, PTR_ERR(old_opp));
|
|
|
+ }
|
|
|
+
|
|
|
+ opp = dev_pm_opp_find_freq_ceil(dev, &freq);
|
|
|
+ if (IS_ERR(opp)) {
|
|
|
+ ret = PTR_ERR(opp);
|
|
|
+ dev_err(dev, "%s: failed to find OPP for freq %lu (%d)\n",
|
|
|
+ __func__, freq, ret);
|
|
|
+ rcu_read_unlock();
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+
|
|
|
+ u_volt = opp->u_volt;
|
|
|
+ u_volt_min = opp->u_volt_min;
|
|
|
+ u_volt_max = opp->u_volt_max;
|
|
|
+
|
|
|
+ reg = opp_table->regulator;
|
|
|
+
|
|
|
+ rcu_read_unlock();
|
|
|
+
|
|
|
+ /* Scaling up? Scale voltage before frequency */
|
|
|
+ if (freq > old_freq) {
|
|
|
+ ret = _set_opp_voltage(dev, reg, u_volt, u_volt_min,
|
|
|
+ u_volt_max);
|
|
|
+ if (ret)
|
|
|
+ goto restore_voltage;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Change frequency */
|
|
|
+
|
|
|
+ dev_dbg(dev, "%s: switching OPP: %lu Hz --> %lu Hz\n",
|
|
|
+ __func__, old_freq, freq);
|
|
|
+
|
|
|
+ ret = clk_set_rate(clk, freq);
|
|
|
+ if (ret) {
|
|
|
+ dev_err(dev, "%s: failed to set clock rate: %d\n", __func__,
|
|
|
+ ret);
|
|
|
+ goto restore_voltage;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Scaling down? Scale voltage after frequency */
|
|
|
+ if (freq < old_freq) {
|
|
|
+ ret = _set_opp_voltage(dev, reg, u_volt, u_volt_min,
|
|
|
+ u_volt_max);
|
|
|
+ if (ret)
|
|
|
+ goto restore_freq;
|
|
|
+ }
|
|
|
+
|
|
|
+ return 0;
|
|
|
+
|
|
|
+restore_freq:
|
|
|
+ if (clk_set_rate(clk, old_freq))
|
|
|
+ dev_err(dev, "%s: failed to restore old-freq (%lu Hz)\n",
|
|
|
+ __func__, old_freq);
|
|
|
+restore_voltage:
|
|
|
+ /* This shouldn't harm even if the voltages weren't updated earlier */
|
|
|
+ if (!IS_ERR(old_opp))
|
|
|
+ _set_opp_voltage(dev, reg, ou_volt, ou_volt_min, ou_volt_max);
|
|
|
+
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+EXPORT_SYMBOL_GPL(dev_pm_opp_set_rate);
|
|
|
+
|
|
|
+/* OPP-dev Helpers */
|
|
|
+static void _kfree_opp_dev_rcu(struct rcu_head *head)
|
|
|
{
|
|
|
- struct device_list_opp *list_dev;
|
|
|
+ struct opp_device *opp_dev;
|
|
|
|
|
|
- list_dev = container_of(head, struct device_list_opp, rcu_head);
|
|
|
- kfree_rcu(list_dev, rcu_head);
|
|
|
+ opp_dev = container_of(head, struct opp_device, rcu_head);
|
|
|
+ kfree_rcu(opp_dev, rcu_head);
|
|
|
}
|
|
|
|
|
|
-static void _remove_list_dev(struct device_list_opp *list_dev,
|
|
|
- struct device_opp *dev_opp)
|
|
|
+static void _remove_opp_dev(struct opp_device *opp_dev,
|
|
|
+ struct opp_table *opp_table)
|
|
|
{
|
|
|
- opp_debug_unregister(list_dev, dev_opp);
|
|
|
- list_del(&list_dev->node);
|
|
|
- call_srcu(&dev_opp->srcu_head.srcu, &list_dev->rcu_head,
|
|
|
- _kfree_list_dev_rcu);
|
|
|
+ opp_debug_unregister(opp_dev, opp_table);
|
|
|
+ list_del(&opp_dev->node);
|
|
|
+ call_srcu(&opp_table->srcu_head.srcu, &opp_dev->rcu_head,
|
|
|
+ _kfree_opp_dev_rcu);
|
|
|
}
|
|
|
|
|
|
-struct device_list_opp *_add_list_dev(const struct device *dev,
|
|
|
- struct device_opp *dev_opp)
|
|
|
+struct opp_device *_add_opp_dev(const struct device *dev,
|
|
|
+ struct opp_table *opp_table)
|
|
|
{
|
|
|
- struct device_list_opp *list_dev;
|
|
|
+ struct opp_device *opp_dev;
|
|
|
int ret;
|
|
|
|
|
|
- list_dev = kzalloc(sizeof(*list_dev), GFP_KERNEL);
|
|
|
- if (!list_dev)
|
|
|
+ opp_dev = kzalloc(sizeof(*opp_dev), GFP_KERNEL);
|
|
|
+ if (!opp_dev)
|
|
|
return NULL;
|
|
|
|
|
|
- /* Initialize list-dev */
|
|
|
- list_dev->dev = dev;
|
|
|
- list_add_rcu(&list_dev->node, &dev_opp->dev_list);
|
|
|
+ /* Initialize opp-dev */
|
|
|
+ opp_dev->dev = dev;
|
|
|
+ list_add_rcu(&opp_dev->node, &opp_table->dev_list);
|
|
|
|
|
|
- /* Create debugfs entries for the dev_opp */
|
|
|
- ret = opp_debug_register(list_dev, dev_opp);
|
|
|
+ /* Create debugfs entries for the opp_table */
|
|
|
+ ret = opp_debug_register(opp_dev, opp_table);
|
|
|
if (ret)
|
|
|
dev_err(dev, "%s: Failed to register opp debugfs (%d)\n",
|
|
|
__func__, ret);
|
|
|
|
|
|
- return list_dev;
|
|
|
+ return opp_dev;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * _add_device_opp() - Find device OPP table or allocate a new one
|
|
|
+ * _add_opp_table() - Find OPP table or allocate a new one
|
|
|
* @dev: device for which we do this operation
|
|
|
*
|
|
|
* It tries to find an existing table first, if it couldn't find one, it
|
|
|
* allocates a new OPP table and returns that.
|
|
|
*
|
|
|
- * Return: valid device_opp pointer if success, else NULL.
|
|
|
+ * Return: valid opp_table pointer if success, else NULL.
|
|
|
*/
|
|
|
-static struct device_opp *_add_device_opp(struct device *dev)
|
|
|
+static struct opp_table *_add_opp_table(struct device *dev)
|
|
|
{
|
|
|
- struct device_opp *dev_opp;
|
|
|
- struct device_list_opp *list_dev;
|
|
|
+ struct opp_table *opp_table;
|
|
|
+ struct opp_device *opp_dev;
|
|
|
+ struct device_node *np;
|
|
|
+ int ret;
|
|
|
|
|
|
- /* Check for existing list for 'dev' first */
|
|
|
- dev_opp = _find_device_opp(dev);
|
|
|
- if (!IS_ERR(dev_opp))
|
|
|
- return dev_opp;
|
|
|
+ /* Check for existing table for 'dev' first */
|
|
|
+ opp_table = _find_opp_table(dev);
|
|
|
+ if (!IS_ERR(opp_table))
|
|
|
+ return opp_table;
|
|
|
|
|
|
/*
|
|
|
- * Allocate a new device OPP table. In the infrequent case where a new
|
|
|
+ * Allocate a new OPP table. In the infrequent case where a new
|
|
|
* device is needed to be added, we pay this penalty.
|
|
|
*/
|
|
|
- dev_opp = kzalloc(sizeof(*dev_opp), GFP_KERNEL);
|
|
|
- if (!dev_opp)
|
|
|
+ opp_table = kzalloc(sizeof(*opp_table), GFP_KERNEL);
|
|
|
+ if (!opp_table)
|
|
|
return NULL;
|
|
|
|
|
|
- INIT_LIST_HEAD(&dev_opp->dev_list);
|
|
|
+ INIT_LIST_HEAD(&opp_table->dev_list);
|
|
|
|
|
|
- list_dev = _add_list_dev(dev, dev_opp);
|
|
|
- if (!list_dev) {
|
|
|
- kfree(dev_opp);
|
|
|
+ opp_dev = _add_opp_dev(dev, opp_table);
|
|
|
+ if (!opp_dev) {
|
|
|
+ kfree(opp_table);
|
|
|
return NULL;
|
|
|
}
|
|
|
|
|
|
- srcu_init_notifier_head(&dev_opp->srcu_head);
|
|
|
- INIT_LIST_HEAD(&dev_opp->opp_list);
|
|
|
+ /*
|
|
|
+ * Only required for backward compatibility with v1 bindings, but isn't
|
|
|
+ * harmful for other cases. And so we do it unconditionally.
|
|
|
+ */
|
|
|
+ np = of_node_get(dev->of_node);
|
|
|
+ if (np) {
|
|
|
+ u32 val;
|
|
|
+
|
|
|
+ if (!of_property_read_u32(np, "clock-latency", &val))
|
|
|
+ opp_table->clock_latency_ns_max = val;
|
|
|
+ of_property_read_u32(np, "voltage-tolerance",
|
|
|
+ &opp_table->voltage_tolerance_v1);
|
|
|
+ of_node_put(np);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Set regulator to a non-NULL error value */
|
|
|
+ opp_table->regulator = ERR_PTR(-ENXIO);
|
|
|
+
|
|
|
+ /* Find clk for the device */
|
|
|
+ opp_table->clk = clk_get(dev, NULL);
|
|
|
+ if (IS_ERR(opp_table->clk)) {
|
|
|
+ ret = PTR_ERR(opp_table->clk);
|
|
|
+ if (ret != -EPROBE_DEFER)
|
|
|
+ dev_dbg(dev, "%s: Couldn't find clock: %d\n", __func__,
|
|
|
+ ret);
|
|
|
+ }
|
|
|
|
|
|
- /* Secure the device list modification */
|
|
|
- list_add_rcu(&dev_opp->node, &dev_opp_list);
|
|
|
- return dev_opp;
|
|
|
+ srcu_init_notifier_head(&opp_table->srcu_head);
|
|
|
+ INIT_LIST_HEAD(&opp_table->opp_list);
|
|
|
+
|
|
|
+ /* Secure the device table modification */
|
|
|
+ list_add_rcu(&opp_table->node, &opp_tables);
|
|
|
+ return opp_table;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * _kfree_device_rcu() - Free device_opp RCU handler
|
|
|
+ * _kfree_device_rcu() - Free opp_table RCU handler
|
|
|
* @head: RCU head
|
|
|
*/
|
|
|
static void _kfree_device_rcu(struct rcu_head *head)
|
|
|
{
|
|
|
- struct device_opp *device_opp = container_of(head, struct device_opp, rcu_head);
|
|
|
+ struct opp_table *opp_table = container_of(head, struct opp_table,
|
|
|
+ rcu_head);
|
|
|
|
|
|
- kfree_rcu(device_opp, rcu_head);
|
|
|
+ kfree_rcu(opp_table, rcu_head);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * _remove_device_opp() - Removes a device OPP table
|
|
|
- * @dev_opp: device OPP table to be removed.
|
|
|
+ * _remove_opp_table() - Removes a OPP table
|
|
|
+ * @opp_table: OPP table to be removed.
|
|
|
*
|
|
|
- * Removes/frees device OPP table it it doesn't contain any OPPs.
|
|
|
+ * Removes/frees OPP table if it doesn't contain any OPPs.
|
|
|
*/
|
|
|
-static void _remove_device_opp(struct device_opp *dev_opp)
|
|
|
+static void _remove_opp_table(struct opp_table *opp_table)
|
|
|
{
|
|
|
- struct device_list_opp *list_dev;
|
|
|
+ struct opp_device *opp_dev;
|
|
|
+
|
|
|
+ if (!list_empty(&opp_table->opp_list))
|
|
|
+ return;
|
|
|
|
|
|
- if (!list_empty(&dev_opp->opp_list))
|
|
|
+ if (opp_table->supported_hw)
|
|
|
return;
|
|
|
|
|
|
- if (dev_opp->supported_hw)
|
|
|
+ if (opp_table->prop_name)
|
|
|
return;
|
|
|
|
|
|
- if (dev_opp->prop_name)
|
|
|
+ if (!IS_ERR(opp_table->regulator))
|
|
|
return;
|
|
|
|
|
|
- list_dev = list_first_entry(&dev_opp->dev_list, struct device_list_opp,
|
|
|
- node);
|
|
|
+ /* Release clk */
|
|
|
+ if (!IS_ERR(opp_table->clk))
|
|
|
+ clk_put(opp_table->clk);
|
|
|
|
|
|
- _remove_list_dev(list_dev, dev_opp);
|
|
|
+ opp_dev = list_first_entry(&opp_table->dev_list, struct opp_device,
|
|
|
+ node);
|
|
|
+
|
|
|
+ _remove_opp_dev(opp_dev, opp_table);
|
|
|
|
|
|
/* dev_list must be empty now */
|
|
|
- WARN_ON(!list_empty(&dev_opp->dev_list));
|
|
|
+ WARN_ON(!list_empty(&opp_table->dev_list));
|
|
|
|
|
|
- list_del_rcu(&dev_opp->node);
|
|
|
- call_srcu(&dev_opp->srcu_head.srcu, &dev_opp->rcu_head,
|
|
|
+ list_del_rcu(&opp_table->node);
|
|
|
+ call_srcu(&opp_table->srcu_head.srcu, &opp_table->rcu_head,
|
|
|
_kfree_device_rcu);
|
|
|
}
|
|
|
|
|
@@ -591,17 +883,17 @@ static void _kfree_opp_rcu(struct rcu_head *head)
|
|
|
|
|
|
/**
|
|
|
* _opp_remove() - Remove an OPP from a table definition
|
|
|
- * @dev_opp: points back to the device_opp struct this opp belongs to
|
|
|
+ * @opp_table: points back to the opp_table struct this opp belongs to
|
|
|
* @opp: pointer to the OPP to remove
|
|
|
* @notify: OPP_EVENT_REMOVE notification should be sent or not
|
|
|
*
|
|
|
- * This function removes an opp definition from the opp list.
|
|
|
+ * This function removes an opp definition from the opp table.
|
|
|
*
|
|
|
- * Locking: The internal device_opp and opp structures are RCU protected.
|
|
|
+ * Locking: The internal opp_table and opp structures are RCU protected.
|
|
|
* It is assumed that the caller holds required mutex for an RCU updater
|
|
|
* strategy.
|
|
|
*/
|
|
|
-static void _opp_remove(struct device_opp *dev_opp,
|
|
|
+static void _opp_remove(struct opp_table *opp_table,
|
|
|
struct dev_pm_opp *opp, bool notify)
|
|
|
{
|
|
|
/*
|
|
@@ -609,22 +901,23 @@ static void _opp_remove(struct device_opp *dev_opp,
|
|
|
* frequency/voltage list.
|
|
|
*/
|
|
|
if (notify)
|
|
|
- srcu_notifier_call_chain(&dev_opp->srcu_head, OPP_EVENT_REMOVE, opp);
|
|
|
+ srcu_notifier_call_chain(&opp_table->srcu_head,
|
|
|
+ OPP_EVENT_REMOVE, opp);
|
|
|
opp_debug_remove_one(opp);
|
|
|
list_del_rcu(&opp->node);
|
|
|
- call_srcu(&dev_opp->srcu_head.srcu, &opp->rcu_head, _kfree_opp_rcu);
|
|
|
+ call_srcu(&opp_table->srcu_head.srcu, &opp->rcu_head, _kfree_opp_rcu);
|
|
|
|
|
|
- _remove_device_opp(dev_opp);
|
|
|
+ _remove_opp_table(opp_table);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * dev_pm_opp_remove() - Remove an OPP from OPP list
|
|
|
+ * dev_pm_opp_remove() - Remove an OPP from OPP table
|
|
|
* @dev: device for which we do this operation
|
|
|
* @freq: OPP to remove with matching 'freq'
|
|
|
*
|
|
|
- * This function removes an opp from the opp list.
|
|
|
+ * This function removes an opp from the opp table.
|
|
|
*
|
|
|
- * Locking: The internal device_opp and opp structures are RCU protected.
|
|
|
+ * Locking: The internal opp_table and opp structures are RCU protected.
|
|
|
* Hence this function internally uses RCU updater strategy with mutex locks
|
|
|
* to keep the integrity of the internal data structures. Callers should ensure
|
|
|
* that this function is *NOT* called under RCU protection or in contexts where
|
|
@@ -633,17 +926,17 @@ static void _opp_remove(struct device_opp *dev_opp,
|
|
|
void dev_pm_opp_remove(struct device *dev, unsigned long freq)
|
|
|
{
|
|
|
struct dev_pm_opp *opp;
|
|
|
- struct device_opp *dev_opp;
|
|
|
+ struct opp_table *opp_table;
|
|
|
bool found = false;
|
|
|
|
|
|
- /* Hold our list modification lock here */
|
|
|
- mutex_lock(&dev_opp_list_lock);
|
|
|
+ /* Hold our table modification lock here */
|
|
|
+ mutex_lock(&opp_table_lock);
|
|
|
|
|
|
- dev_opp = _find_device_opp(dev);
|
|
|
- if (IS_ERR(dev_opp))
|
|
|
+ opp_table = _find_opp_table(dev);
|
|
|
+ if (IS_ERR(opp_table))
|
|
|
goto unlock;
|
|
|
|
|
|
- list_for_each_entry(opp, &dev_opp->opp_list, node) {
|
|
|
+ list_for_each_entry(opp, &opp_table->opp_list, node) {
|
|
|
if (opp->rate == freq) {
|
|
|
found = true;
|
|
|
break;
|
|
@@ -656,14 +949,14 @@ void dev_pm_opp_remove(struct device *dev, unsigned long freq)
|
|
|
goto unlock;
|
|
|
}
|
|
|
|
|
|
- _opp_remove(dev_opp, opp, true);
|
|
|
+ _opp_remove(opp_table, opp, true);
|
|
|
unlock:
|
|
|
- mutex_unlock(&dev_opp_list_lock);
|
|
|
+ mutex_unlock(&opp_table_lock);
|
|
|
}
|
|
|
EXPORT_SYMBOL_GPL(dev_pm_opp_remove);
|
|
|
|
|
|
static struct dev_pm_opp *_allocate_opp(struct device *dev,
|
|
|
- struct device_opp **dev_opp)
|
|
|
+ struct opp_table **opp_table)
|
|
|
{
|
|
|
struct dev_pm_opp *opp;
|
|
|
|
|
@@ -674,8 +967,8 @@ static struct dev_pm_opp *_allocate_opp(struct device *dev,
|
|
|
|
|
|
INIT_LIST_HEAD(&opp->node);
|
|
|
|
|
|
- *dev_opp = _add_device_opp(dev);
|
|
|
- if (!*dev_opp) {
|
|
|
+ *opp_table = _add_opp_table(dev);
|
|
|
+ if (!*opp_table) {
|
|
|
kfree(opp);
|
|
|
return NULL;
|
|
|
}
|
|
@@ -683,22 +976,38 @@ static struct dev_pm_opp *_allocate_opp(struct device *dev,
|
|
|
return opp;
|
|
|
}
|
|
|
|
|
|
+static bool _opp_supported_by_regulators(struct dev_pm_opp *opp,
|
|
|
+ struct opp_table *opp_table)
|
|
|
+{
|
|
|
+ struct regulator *reg = opp_table->regulator;
|
|
|
+
|
|
|
+ if (!IS_ERR(reg) &&
|
|
|
+ !regulator_is_supported_voltage(reg, opp->u_volt_min,
|
|
|
+ opp->u_volt_max)) {
|
|
|
+ pr_warn("%s: OPP minuV: %lu maxuV: %lu, not supported by regulator\n",
|
|
|
+ __func__, opp->u_volt_min, opp->u_volt_max);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
static int _opp_add(struct device *dev, struct dev_pm_opp *new_opp,
|
|
|
- struct device_opp *dev_opp)
|
|
|
+ struct opp_table *opp_table)
|
|
|
{
|
|
|
struct dev_pm_opp *opp;
|
|
|
- struct list_head *head = &dev_opp->opp_list;
|
|
|
+ struct list_head *head = &opp_table->opp_list;
|
|
|
int ret;
|
|
|
|
|
|
/*
|
|
|
* Insert new OPP in order of increasing frequency and discard if
|
|
|
* already present.
|
|
|
*
|
|
|
- * Need to use &dev_opp->opp_list in the condition part of the 'for'
|
|
|
+ * 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_rcu(opp, &dev_opp->opp_list, node) {
|
|
|
+ list_for_each_entry_rcu(opp, &opp_table->opp_list, node) {
|
|
|
if (new_opp->rate > opp->rate) {
|
|
|
head = &opp->node;
|
|
|
continue;
|
|
@@ -716,14 +1025,20 @@ static int _opp_add(struct device *dev, struct dev_pm_opp *new_opp,
|
|
|
0 : -EEXIST;
|
|
|
}
|
|
|
|
|
|
- new_opp->dev_opp = dev_opp;
|
|
|
+ new_opp->opp_table = opp_table;
|
|
|
list_add_rcu(&new_opp->node, head);
|
|
|
|
|
|
- ret = opp_debug_create_one(new_opp, dev_opp);
|
|
|
+ ret = opp_debug_create_one(new_opp, opp_table);
|
|
|
if (ret)
|
|
|
dev_err(dev, "%s: Failed to register opp to debugfs (%d)\n",
|
|
|
__func__, ret);
|
|
|
|
|
|
+ if (!_opp_supported_by_regulators(new_opp, opp_table)) {
|
|
|
+ new_opp->available = false;
|
|
|
+ dev_warn(dev, "%s: OPP not supported by regulators (%lu)\n",
|
|
|
+ __func__, new_opp->rate);
|
|
|
+ }
|
|
|
+
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
@@ -734,14 +1049,14 @@ static int _opp_add(struct device *dev, struct dev_pm_opp *new_opp,
|
|
|
* @u_volt: Voltage in uVolts for this OPP
|
|
|
* @dynamic: Dynamically added OPPs.
|
|
|
*
|
|
|
- * This function adds an opp definition to the opp list and returns status.
|
|
|
+ * This function adds an opp definition to the opp table and returns status.
|
|
|
* The opp is made available by default and it can be controlled using
|
|
|
* dev_pm_opp_enable/disable functions and may be removed by dev_pm_opp_remove.
|
|
|
*
|
|
|
* NOTE: "dynamic" parameter impacts OPPs added by the dev_pm_opp_of_add_table
|
|
|
* and freed by dev_pm_opp_of_remove_table.
|
|
|
*
|
|
|
- * Locking: The internal device_opp and opp structures are RCU protected.
|
|
|
+ * Locking: The internal opp_table and opp structures are RCU protected.
|
|
|
* Hence this function internally uses RCU updater strategy with mutex locks
|
|
|
* to keep the integrity of the internal data structures. Callers should ensure
|
|
|
* that this function is *NOT* called under RCU protection or in contexts where
|
|
@@ -757,14 +1072,15 @@ static int _opp_add(struct device *dev, struct dev_pm_opp *new_opp,
|
|
|
static int _opp_add_v1(struct device *dev, unsigned long freq, long u_volt,
|
|
|
bool dynamic)
|
|
|
{
|
|
|
- struct device_opp *dev_opp;
|
|
|
+ struct opp_table *opp_table;
|
|
|
struct dev_pm_opp *new_opp;
|
|
|
+ unsigned long tol;
|
|
|
int ret;
|
|
|
|
|
|
- /* Hold our list modification lock here */
|
|
|
- mutex_lock(&dev_opp_list_lock);
|
|
|
+ /* Hold our table modification lock here */
|
|
|
+ mutex_lock(&opp_table_lock);
|
|
|
|
|
|
- new_opp = _allocate_opp(dev, &dev_opp);
|
|
|
+ new_opp = _allocate_opp(dev, &opp_table);
|
|
|
if (!new_opp) {
|
|
|
ret = -ENOMEM;
|
|
|
goto unlock;
|
|
@@ -772,33 +1088,36 @@ static int _opp_add_v1(struct device *dev, unsigned long freq, long u_volt,
|
|
|
|
|
|
/* populate the opp table */
|
|
|
new_opp->rate = freq;
|
|
|
+ tol = u_volt * opp_table->voltage_tolerance_v1 / 100;
|
|
|
new_opp->u_volt = u_volt;
|
|
|
+ new_opp->u_volt_min = u_volt - tol;
|
|
|
+ new_opp->u_volt_max = u_volt + tol;
|
|
|
new_opp->available = true;
|
|
|
new_opp->dynamic = dynamic;
|
|
|
|
|
|
- ret = _opp_add(dev, new_opp, dev_opp);
|
|
|
+ ret = _opp_add(dev, new_opp, opp_table);
|
|
|
if (ret)
|
|
|
goto free_opp;
|
|
|
|
|
|
- mutex_unlock(&dev_opp_list_lock);
|
|
|
+ mutex_unlock(&opp_table_lock);
|
|
|
|
|
|
/*
|
|
|
* Notify the changes in the availability of the operable
|
|
|
* frequency/voltage list.
|
|
|
*/
|
|
|
- srcu_notifier_call_chain(&dev_opp->srcu_head, OPP_EVENT_ADD, new_opp);
|
|
|
+ srcu_notifier_call_chain(&opp_table->srcu_head, OPP_EVENT_ADD, new_opp);
|
|
|
return 0;
|
|
|
|
|
|
free_opp:
|
|
|
- _opp_remove(dev_opp, new_opp, false);
|
|
|
+ _opp_remove(opp_table, new_opp, false);
|
|
|
unlock:
|
|
|
- mutex_unlock(&dev_opp_list_lock);
|
|
|
+ mutex_unlock(&opp_table_lock);
|
|
|
return ret;
|
|
|
}
|
|
|
|
|
|
/* TODO: Support multiple regulators */
|
|
|
static int opp_parse_supplies(struct dev_pm_opp *opp, struct device *dev,
|
|
|
- struct device_opp *dev_opp)
|
|
|
+ struct opp_table *opp_table)
|
|
|
{
|
|
|
u32 microvolt[3] = {0};
|
|
|
u32 val;
|
|
@@ -807,9 +1126,9 @@ static int opp_parse_supplies(struct dev_pm_opp *opp, struct device *dev,
|
|
|
char name[NAME_MAX];
|
|
|
|
|
|
/* Search for "opp-microvolt-<name>" */
|
|
|
- if (dev_opp->prop_name) {
|
|
|
+ if (opp_table->prop_name) {
|
|
|
snprintf(name, sizeof(name), "opp-microvolt-%s",
|
|
|
- dev_opp->prop_name);
|
|
|
+ opp_table->prop_name);
|
|
|
prop = of_find_property(opp->np, name, NULL);
|
|
|
}
|
|
|
|
|
@@ -844,14 +1163,20 @@ static int opp_parse_supplies(struct dev_pm_opp *opp, struct device *dev,
|
|
|
}
|
|
|
|
|
|
opp->u_volt = microvolt[0];
|
|
|
- opp->u_volt_min = microvolt[1];
|
|
|
- opp->u_volt_max = microvolt[2];
|
|
|
+
|
|
|
+ if (count == 1) {
|
|
|
+ opp->u_volt_min = opp->u_volt;
|
|
|
+ opp->u_volt_max = opp->u_volt;
|
|
|
+ } else {
|
|
|
+ opp->u_volt_min = microvolt[1];
|
|
|
+ opp->u_volt_max = microvolt[2];
|
|
|
+ }
|
|
|
|
|
|
/* Search for "opp-microamp-<name>" */
|
|
|
prop = NULL;
|
|
|
- if (dev_opp->prop_name) {
|
|
|
+ if (opp_table->prop_name) {
|
|
|
snprintf(name, sizeof(name), "opp-microamp-%s",
|
|
|
- dev_opp->prop_name);
|
|
|
+ opp_table->prop_name);
|
|
|
prop = of_find_property(opp->np, name, NULL);
|
|
|
}
|
|
|
|
|
@@ -878,7 +1203,7 @@ static int opp_parse_supplies(struct dev_pm_opp *opp, struct device *dev,
|
|
|
* OPPs, which are available for those versions, based on its 'opp-supported-hw'
|
|
|
* property.
|
|
|
*
|
|
|
- * Locking: The internal device_opp and opp structures are RCU protected.
|
|
|
+ * Locking: The internal opp_table and opp structures are RCU protected.
|
|
|
* Hence this function internally uses RCU updater strategy with mutex locks
|
|
|
* to keep the integrity of the internal data structures. Callers should ensure
|
|
|
* that this function is *NOT* called under RCU protection or in contexts where
|
|
@@ -887,44 +1212,44 @@ static int opp_parse_supplies(struct dev_pm_opp *opp, struct device *dev,
|
|
|
int dev_pm_opp_set_supported_hw(struct device *dev, const u32 *versions,
|
|
|
unsigned int count)
|
|
|
{
|
|
|
- struct device_opp *dev_opp;
|
|
|
+ struct opp_table *opp_table;
|
|
|
int ret = 0;
|
|
|
|
|
|
- /* Hold our list modification lock here */
|
|
|
- mutex_lock(&dev_opp_list_lock);
|
|
|
+ /* Hold our table modification lock here */
|
|
|
+ mutex_lock(&opp_table_lock);
|
|
|
|
|
|
- dev_opp = _add_device_opp(dev);
|
|
|
- if (!dev_opp) {
|
|
|
+ opp_table = _add_opp_table(dev);
|
|
|
+ if (!opp_table) {
|
|
|
ret = -ENOMEM;
|
|
|
goto unlock;
|
|
|
}
|
|
|
|
|
|
- /* Make sure there are no concurrent readers while updating dev_opp */
|
|
|
- WARN_ON(!list_empty(&dev_opp->opp_list));
|
|
|
+ /* Make sure there are no concurrent readers while updating opp_table */
|
|
|
+ WARN_ON(!list_empty(&opp_table->opp_list));
|
|
|
|
|
|
- /* Do we already have a version hierarchy associated with dev_opp? */
|
|
|
- if (dev_opp->supported_hw) {
|
|
|
+ /* Do we already have a version hierarchy associated with opp_table? */
|
|
|
+ if (opp_table->supported_hw) {
|
|
|
dev_err(dev, "%s: Already have supported hardware list\n",
|
|
|
__func__);
|
|
|
ret = -EBUSY;
|
|
|
goto err;
|
|
|
}
|
|
|
|
|
|
- dev_opp->supported_hw = kmemdup(versions, count * sizeof(*versions),
|
|
|
+ opp_table->supported_hw = kmemdup(versions, count * sizeof(*versions),
|
|
|
GFP_KERNEL);
|
|
|
- if (!dev_opp->supported_hw) {
|
|
|
+ if (!opp_table->supported_hw) {
|
|
|
ret = -ENOMEM;
|
|
|
goto err;
|
|
|
}
|
|
|
|
|
|
- dev_opp->supported_hw_count = count;
|
|
|
- mutex_unlock(&dev_opp_list_lock);
|
|
|
+ opp_table->supported_hw_count = count;
|
|
|
+ mutex_unlock(&opp_table_lock);
|
|
|
return 0;
|
|
|
|
|
|
err:
|
|
|
- _remove_device_opp(dev_opp);
|
|
|
+ _remove_opp_table(opp_table);
|
|
|
unlock:
|
|
|
- mutex_unlock(&dev_opp_list_lock);
|
|
|
+ mutex_unlock(&opp_table_lock);
|
|
|
|
|
|
return ret;
|
|
|
}
|
|
@@ -932,13 +1257,13 @@ EXPORT_SYMBOL_GPL(dev_pm_opp_set_supported_hw);
|
|
|
|
|
|
/**
|
|
|
* dev_pm_opp_put_supported_hw() - Releases resources blocked for supported hw
|
|
|
- * @dev: Device for which supported-hw has to be set.
|
|
|
+ * @dev: Device for which supported-hw has to be put.
|
|
|
*
|
|
|
* This is required only for the V2 bindings, and is called for a matching
|
|
|
- * dev_pm_opp_set_supported_hw(). Until this is called, the device_opp structure
|
|
|
+ * dev_pm_opp_set_supported_hw(). Until this is called, the opp_table structure
|
|
|
* will not be freed.
|
|
|
*
|
|
|
- * Locking: The internal device_opp and opp structures are RCU protected.
|
|
|
+ * Locking: The internal opp_table and opp structures are RCU protected.
|
|
|
* Hence this function internally uses RCU updater strategy with mutex locks
|
|
|
* to keep the integrity of the internal data structures. Callers should ensure
|
|
|
* that this function is *NOT* called under RCU protection or in contexts where
|
|
@@ -946,42 +1271,43 @@ EXPORT_SYMBOL_GPL(dev_pm_opp_set_supported_hw);
|
|
|
*/
|
|
|
void dev_pm_opp_put_supported_hw(struct device *dev)
|
|
|
{
|
|
|
- struct device_opp *dev_opp;
|
|
|
+ struct opp_table *opp_table;
|
|
|
|
|
|
- /* Hold our list modification lock here */
|
|
|
- mutex_lock(&dev_opp_list_lock);
|
|
|
+ /* Hold our table modification lock here */
|
|
|
+ mutex_lock(&opp_table_lock);
|
|
|
|
|
|
- /* Check for existing list for 'dev' first */
|
|
|
- dev_opp = _find_device_opp(dev);
|
|
|
- if (IS_ERR(dev_opp)) {
|
|
|
- dev_err(dev, "Failed to find dev_opp: %ld\n", PTR_ERR(dev_opp));
|
|
|
+ /* Check for existing table for 'dev' first */
|
|
|
+ opp_table = _find_opp_table(dev);
|
|
|
+ if (IS_ERR(opp_table)) {
|
|
|
+ dev_err(dev, "Failed to find opp_table: %ld\n",
|
|
|
+ PTR_ERR(opp_table));
|
|
|
goto unlock;
|
|
|
}
|
|
|
|
|
|
- /* Make sure there are no concurrent readers while updating dev_opp */
|
|
|
- WARN_ON(!list_empty(&dev_opp->opp_list));
|
|
|
+ /* Make sure there are no concurrent readers while updating opp_table */
|
|
|
+ WARN_ON(!list_empty(&opp_table->opp_list));
|
|
|
|
|
|
- if (!dev_opp->supported_hw) {
|
|
|
+ if (!opp_table->supported_hw) {
|
|
|
dev_err(dev, "%s: Doesn't have supported hardware list\n",
|
|
|
__func__);
|
|
|
goto unlock;
|
|
|
}
|
|
|
|
|
|
- kfree(dev_opp->supported_hw);
|
|
|
- dev_opp->supported_hw = NULL;
|
|
|
- dev_opp->supported_hw_count = 0;
|
|
|
+ kfree(opp_table->supported_hw);
|
|
|
+ opp_table->supported_hw = NULL;
|
|
|
+ opp_table->supported_hw_count = 0;
|
|
|
|
|
|
- /* Try freeing device_opp if this was the last blocking resource */
|
|
|
- _remove_device_opp(dev_opp);
|
|
|
+ /* Try freeing opp_table if this was the last blocking resource */
|
|
|
+ _remove_opp_table(opp_table);
|
|
|
|
|
|
unlock:
|
|
|
- mutex_unlock(&dev_opp_list_lock);
|
|
|
+ mutex_unlock(&opp_table_lock);
|
|
|
}
|
|
|
EXPORT_SYMBOL_GPL(dev_pm_opp_put_supported_hw);
|
|
|
|
|
|
/**
|
|
|
* dev_pm_opp_set_prop_name() - Set prop-extn name
|
|
|
- * @dev: Device for which the regulator has to be set.
|
|
|
+ * @dev: Device for which the prop-name has to be set.
|
|
|
* @name: name to postfix to properties.
|
|
|
*
|
|
|
* This is required only for the V2 bindings, and it enables a platform to
|
|
@@ -989,7 +1315,7 @@ EXPORT_SYMBOL_GPL(dev_pm_opp_put_supported_hw);
|
|
|
* which the extension will apply are opp-microvolt and opp-microamp. OPP core
|
|
|
* should postfix the property name with -<name> while looking for them.
|
|
|
*
|
|
|
- * Locking: The internal device_opp and opp structures are RCU protected.
|
|
|
+ * Locking: The internal opp_table and opp structures are RCU protected.
|
|
|
* Hence this function internally uses RCU updater strategy with mutex locks
|
|
|
* to keep the integrity of the internal data structures. Callers should ensure
|
|
|
* that this function is *NOT* called under RCU protection or in contexts where
|
|
@@ -997,42 +1323,42 @@ EXPORT_SYMBOL_GPL(dev_pm_opp_put_supported_hw);
|
|
|
*/
|
|
|
int dev_pm_opp_set_prop_name(struct device *dev, const char *name)
|
|
|
{
|
|
|
- struct device_opp *dev_opp;
|
|
|
+ struct opp_table *opp_table;
|
|
|
int ret = 0;
|
|
|
|
|
|
- /* Hold our list modification lock here */
|
|
|
- mutex_lock(&dev_opp_list_lock);
|
|
|
+ /* Hold our table modification lock here */
|
|
|
+ mutex_lock(&opp_table_lock);
|
|
|
|
|
|
- dev_opp = _add_device_opp(dev);
|
|
|
- if (!dev_opp) {
|
|
|
+ opp_table = _add_opp_table(dev);
|
|
|
+ if (!opp_table) {
|
|
|
ret = -ENOMEM;
|
|
|
goto unlock;
|
|
|
}
|
|
|
|
|
|
- /* Make sure there are no concurrent readers while updating dev_opp */
|
|
|
- WARN_ON(!list_empty(&dev_opp->opp_list));
|
|
|
+ /* Make sure there are no concurrent readers while updating opp_table */
|
|
|
+ WARN_ON(!list_empty(&opp_table->opp_list));
|
|
|
|
|
|
- /* Do we already have a prop-name associated with dev_opp? */
|
|
|
- if (dev_opp->prop_name) {
|
|
|
+ /* Do we already have a prop-name associated with opp_table? */
|
|
|
+ if (opp_table->prop_name) {
|
|
|
dev_err(dev, "%s: Already have prop-name %s\n", __func__,
|
|
|
- dev_opp->prop_name);
|
|
|
+ opp_table->prop_name);
|
|
|
ret = -EBUSY;
|
|
|
goto err;
|
|
|
}
|
|
|
|
|
|
- dev_opp->prop_name = kstrdup(name, GFP_KERNEL);
|
|
|
- if (!dev_opp->prop_name) {
|
|
|
+ opp_table->prop_name = kstrdup(name, GFP_KERNEL);
|
|
|
+ if (!opp_table->prop_name) {
|
|
|
ret = -ENOMEM;
|
|
|
goto err;
|
|
|
}
|
|
|
|
|
|
- mutex_unlock(&dev_opp_list_lock);
|
|
|
+ mutex_unlock(&opp_table_lock);
|
|
|
return 0;
|
|
|
|
|
|
err:
|
|
|
- _remove_device_opp(dev_opp);
|
|
|
+ _remove_opp_table(opp_table);
|
|
|
unlock:
|
|
|
- mutex_unlock(&dev_opp_list_lock);
|
|
|
+ mutex_unlock(&opp_table_lock);
|
|
|
|
|
|
return ret;
|
|
|
}
|
|
@@ -1040,13 +1366,13 @@ EXPORT_SYMBOL_GPL(dev_pm_opp_set_prop_name);
|
|
|
|
|
|
/**
|
|
|
* dev_pm_opp_put_prop_name() - Releases resources blocked for prop-name
|
|
|
- * @dev: Device for which the regulator has to be set.
|
|
|
+ * @dev: Device for which the prop-name has to be put.
|
|
|
*
|
|
|
* This is required only for the V2 bindings, and is called for a matching
|
|
|
- * dev_pm_opp_set_prop_name(). Until this is called, the device_opp structure
|
|
|
+ * dev_pm_opp_set_prop_name(). Until this is called, the opp_table structure
|
|
|
* will not be freed.
|
|
|
*
|
|
|
- * Locking: The internal device_opp and opp structures are RCU protected.
|
|
|
+ * Locking: The internal opp_table and opp structures are RCU protected.
|
|
|
* Hence this function internally uses RCU updater strategy with mutex locks
|
|
|
* to keep the integrity of the internal data structures. Callers should ensure
|
|
|
* that this function is *NOT* called under RCU protection or in contexts where
|
|
@@ -1054,45 +1380,154 @@ EXPORT_SYMBOL_GPL(dev_pm_opp_set_prop_name);
|
|
|
*/
|
|
|
void dev_pm_opp_put_prop_name(struct device *dev)
|
|
|
{
|
|
|
- struct device_opp *dev_opp;
|
|
|
+ struct opp_table *opp_table;
|
|
|
|
|
|
- /* Hold our list modification lock here */
|
|
|
- mutex_lock(&dev_opp_list_lock);
|
|
|
+ /* Hold our table modification lock here */
|
|
|
+ mutex_lock(&opp_table_lock);
|
|
|
|
|
|
- /* Check for existing list for 'dev' first */
|
|
|
- dev_opp = _find_device_opp(dev);
|
|
|
- if (IS_ERR(dev_opp)) {
|
|
|
- dev_err(dev, "Failed to find dev_opp: %ld\n", PTR_ERR(dev_opp));
|
|
|
+ /* Check for existing table for 'dev' first */
|
|
|
+ opp_table = _find_opp_table(dev);
|
|
|
+ if (IS_ERR(opp_table)) {
|
|
|
+ dev_err(dev, "Failed to find opp_table: %ld\n",
|
|
|
+ PTR_ERR(opp_table));
|
|
|
goto unlock;
|
|
|
}
|
|
|
|
|
|
- /* Make sure there are no concurrent readers while updating dev_opp */
|
|
|
- WARN_ON(!list_empty(&dev_opp->opp_list));
|
|
|
+ /* Make sure there are no concurrent readers while updating opp_table */
|
|
|
+ WARN_ON(!list_empty(&opp_table->opp_list));
|
|
|
|
|
|
- if (!dev_opp->prop_name) {
|
|
|
+ if (!opp_table->prop_name) {
|
|
|
dev_err(dev, "%s: Doesn't have a prop-name\n", __func__);
|
|
|
goto unlock;
|
|
|
}
|
|
|
|
|
|
- kfree(dev_opp->prop_name);
|
|
|
- dev_opp->prop_name = NULL;
|
|
|
+ kfree(opp_table->prop_name);
|
|
|
+ opp_table->prop_name = NULL;
|
|
|
|
|
|
- /* Try freeing device_opp if this was the last blocking resource */
|
|
|
- _remove_device_opp(dev_opp);
|
|
|
+ /* Try freeing opp_table if this was the last blocking resource */
|
|
|
+ _remove_opp_table(opp_table);
|
|
|
|
|
|
unlock:
|
|
|
- mutex_unlock(&dev_opp_list_lock);
|
|
|
+ mutex_unlock(&opp_table_lock);
|
|
|
}
|
|
|
EXPORT_SYMBOL_GPL(dev_pm_opp_put_prop_name);
|
|
|
|
|
|
-static bool _opp_is_supported(struct device *dev, struct device_opp *dev_opp,
|
|
|
+/**
|
|
|
+ * dev_pm_opp_set_regulator() - Set regulator name for the device
|
|
|
+ * @dev: Device for which regulator name is being set.
|
|
|
+ * @name: Name of the regulator.
|
|
|
+ *
|
|
|
+ * In order to support OPP switching, OPP layer needs to know the name of the
|
|
|
+ * device's regulator, as the core would be required to switch voltages as well.
|
|
|
+ *
|
|
|
+ * This must be called before any OPPs are initialized for the device.
|
|
|
+ *
|
|
|
+ * Locking: The internal opp_table and opp structures are RCU protected.
|
|
|
+ * Hence this function internally uses RCU updater strategy with mutex locks
|
|
|
+ * to keep the integrity of the internal data structures. Callers should ensure
|
|
|
+ * that this function is *NOT* called under RCU protection or in contexts where
|
|
|
+ * mutex cannot be locked.
|
|
|
+ */
|
|
|
+int dev_pm_opp_set_regulator(struct device *dev, const char *name)
|
|
|
+{
|
|
|
+ struct opp_table *opp_table;
|
|
|
+ struct regulator *reg;
|
|
|
+ int ret;
|
|
|
+
|
|
|
+ mutex_lock(&opp_table_lock);
|
|
|
+
|
|
|
+ opp_table = _add_opp_table(dev);
|
|
|
+ if (!opp_table) {
|
|
|
+ ret = -ENOMEM;
|
|
|
+ goto unlock;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* This should be called before OPPs are initialized */
|
|
|
+ if (WARN_ON(!list_empty(&opp_table->opp_list))) {
|
|
|
+ ret = -EBUSY;
|
|
|
+ goto err;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Already have a regulator set */
|
|
|
+ if (WARN_ON(!IS_ERR(opp_table->regulator))) {
|
|
|
+ ret = -EBUSY;
|
|
|
+ goto err;
|
|
|
+ }
|
|
|
+ /* Allocate the regulator */
|
|
|
+ reg = regulator_get_optional(dev, name);
|
|
|
+ if (IS_ERR(reg)) {
|
|
|
+ ret = PTR_ERR(reg);
|
|
|
+ if (ret != -EPROBE_DEFER)
|
|
|
+ dev_err(dev, "%s: no regulator (%s) found: %d\n",
|
|
|
+ __func__, name, ret);
|
|
|
+ goto err;
|
|
|
+ }
|
|
|
+
|
|
|
+ opp_table->regulator = reg;
|
|
|
+
|
|
|
+ mutex_unlock(&opp_table_lock);
|
|
|
+ return 0;
|
|
|
+
|
|
|
+err:
|
|
|
+ _remove_opp_table(opp_table);
|
|
|
+unlock:
|
|
|
+ mutex_unlock(&opp_table_lock);
|
|
|
+
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+EXPORT_SYMBOL_GPL(dev_pm_opp_set_regulator);
|
|
|
+
|
|
|
+/**
|
|
|
+ * dev_pm_opp_put_regulator() - Releases resources blocked for regulator
|
|
|
+ * @dev: Device for which regulator was set.
|
|
|
+ *
|
|
|
+ * Locking: The internal opp_table and opp structures are RCU protected.
|
|
|
+ * Hence this function internally uses RCU updater strategy with mutex locks
|
|
|
+ * to keep the integrity of the internal data structures. Callers should ensure
|
|
|
+ * that this function is *NOT* called under RCU protection or in contexts where
|
|
|
+ * mutex cannot be locked.
|
|
|
+ */
|
|
|
+void dev_pm_opp_put_regulator(struct device *dev)
|
|
|
+{
|
|
|
+ struct opp_table *opp_table;
|
|
|
+
|
|
|
+ mutex_lock(&opp_table_lock);
|
|
|
+
|
|
|
+ /* Check for existing table for 'dev' first */
|
|
|
+ opp_table = _find_opp_table(dev);
|
|
|
+ if (IS_ERR(opp_table)) {
|
|
|
+ dev_err(dev, "Failed to find opp_table: %ld\n",
|
|
|
+ PTR_ERR(opp_table));
|
|
|
+ goto unlock;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (IS_ERR(opp_table->regulator)) {
|
|
|
+ dev_err(dev, "%s: Doesn't have regulator set\n", __func__);
|
|
|
+ goto unlock;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Make sure there are no concurrent readers while updating opp_table */
|
|
|
+ WARN_ON(!list_empty(&opp_table->opp_list));
|
|
|
+
|
|
|
+ regulator_put(opp_table->regulator);
|
|
|
+ opp_table->regulator = ERR_PTR(-ENXIO);
|
|
|
+
|
|
|
+ /* Try freeing opp_table if this was the last blocking resource */
|
|
|
+ _remove_opp_table(opp_table);
|
|
|
+
|
|
|
+unlock:
|
|
|
+ mutex_unlock(&opp_table_lock);
|
|
|
+}
|
|
|
+EXPORT_SYMBOL_GPL(dev_pm_opp_put_regulator);
|
|
|
+
|
|
|
+static bool _opp_is_supported(struct device *dev, struct opp_table *opp_table,
|
|
|
struct device_node *np)
|
|
|
{
|
|
|
- unsigned int count = dev_opp->supported_hw_count;
|
|
|
+ unsigned int count = opp_table->supported_hw_count;
|
|
|
u32 version;
|
|
|
int ret;
|
|
|
|
|
|
- if (!dev_opp->supported_hw)
|
|
|
+ if (!opp_table->supported_hw)
|
|
|
return true;
|
|
|
|
|
|
while (count--) {
|
|
@@ -1105,7 +1540,7 @@ static bool _opp_is_supported(struct device *dev, struct device_opp *dev_opp,
|
|
|
}
|
|
|
|
|
|
/* Both of these are bitwise masks of the versions */
|
|
|
- if (!(version & dev_opp->supported_hw[count]))
|
|
|
+ if (!(version & opp_table->supported_hw[count]))
|
|
|
return false;
|
|
|
}
|
|
|
|
|
@@ -1117,11 +1552,11 @@ static bool _opp_is_supported(struct device *dev, struct device_opp *dev_opp,
|
|
|
* @dev: device for which we do this operation
|
|
|
* @np: device node
|
|
|
*
|
|
|
- * This function adds an opp definition to the opp list and returns status. The
|
|
|
+ * This function adds an opp definition to the opp table and returns status. The
|
|
|
* opp can be controlled using dev_pm_opp_enable/disable functions and may be
|
|
|
* removed by dev_pm_opp_remove.
|
|
|
*
|
|
|
- * Locking: The internal device_opp and opp structures are RCU protected.
|
|
|
+ * Locking: The internal opp_table and opp structures are RCU protected.
|
|
|
* Hence this function internally uses RCU updater strategy with mutex locks
|
|
|
* to keep the integrity of the internal data structures. Callers should ensure
|
|
|
* that this function is *NOT* called under RCU protection or in contexts where
|
|
@@ -1137,16 +1572,16 @@ static bool _opp_is_supported(struct device *dev, struct device_opp *dev_opp,
|
|
|
*/
|
|
|
static int _opp_add_static_v2(struct device *dev, struct device_node *np)
|
|
|
{
|
|
|
- struct device_opp *dev_opp;
|
|
|
+ struct opp_table *opp_table;
|
|
|
struct dev_pm_opp *new_opp;
|
|
|
u64 rate;
|
|
|
u32 val;
|
|
|
int ret;
|
|
|
|
|
|
- /* Hold our list modification lock here */
|
|
|
- mutex_lock(&dev_opp_list_lock);
|
|
|
+ /* Hold our table modification lock here */
|
|
|
+ mutex_lock(&opp_table_lock);
|
|
|
|
|
|
- new_opp = _allocate_opp(dev, &dev_opp);
|
|
|
+ new_opp = _allocate_opp(dev, &opp_table);
|
|
|
if (!new_opp) {
|
|
|
ret = -ENOMEM;
|
|
|
goto unlock;
|
|
@@ -1159,7 +1594,7 @@ static int _opp_add_static_v2(struct device *dev, struct device_node *np)
|
|
|
}
|
|
|
|
|
|
/* Check if the OPP supports hardware's hierarchy of versions or not */
|
|
|
- if (!_opp_is_supported(dev, dev_opp, np)) {
|
|
|
+ if (!_opp_is_supported(dev, opp_table, np)) {
|
|
|
dev_dbg(dev, "OPP not supported by hardware: %llu\n", rate);
|
|
|
goto free_opp;
|
|
|
}
|
|
@@ -1179,30 +1614,30 @@ static int _opp_add_static_v2(struct device *dev, struct device_node *np)
|
|
|
if (!of_property_read_u32(np, "clock-latency-ns", &val))
|
|
|
new_opp->clock_latency_ns = val;
|
|
|
|
|
|
- ret = opp_parse_supplies(new_opp, dev, dev_opp);
|
|
|
+ ret = opp_parse_supplies(new_opp, dev, opp_table);
|
|
|
if (ret)
|
|
|
goto free_opp;
|
|
|
|
|
|
- ret = _opp_add(dev, new_opp, dev_opp);
|
|
|
+ ret = _opp_add(dev, new_opp, opp_table);
|
|
|
if (ret)
|
|
|
goto free_opp;
|
|
|
|
|
|
/* OPP to select on device suspend */
|
|
|
if (of_property_read_bool(np, "opp-suspend")) {
|
|
|
- if (dev_opp->suspend_opp) {
|
|
|
+ if (opp_table->suspend_opp) {
|
|
|
dev_warn(dev, "%s: Multiple suspend OPPs found (%lu %lu)\n",
|
|
|
- __func__, dev_opp->suspend_opp->rate,
|
|
|
+ __func__, opp_table->suspend_opp->rate,
|
|
|
new_opp->rate);
|
|
|
} else {
|
|
|
new_opp->suspend = true;
|
|
|
- dev_opp->suspend_opp = new_opp;
|
|
|
+ opp_table->suspend_opp = new_opp;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- if (new_opp->clock_latency_ns > dev_opp->clock_latency_ns_max)
|
|
|
- dev_opp->clock_latency_ns_max = new_opp->clock_latency_ns;
|
|
|
+ if (new_opp->clock_latency_ns > opp_table->clock_latency_ns_max)
|
|
|
+ opp_table->clock_latency_ns_max = new_opp->clock_latency_ns;
|
|
|
|
|
|
- mutex_unlock(&dev_opp_list_lock);
|
|
|
+ mutex_unlock(&opp_table_lock);
|
|
|
|
|
|
pr_debug("%s: turbo:%d rate:%lu uv:%lu uvmin:%lu uvmax:%lu latency:%lu\n",
|
|
|
__func__, new_opp->turbo, new_opp->rate, new_opp->u_volt,
|
|
@@ -1213,13 +1648,13 @@ static int _opp_add_static_v2(struct device *dev, struct device_node *np)
|
|
|
* Notify the changes in the availability of the operable
|
|
|
* frequency/voltage list.
|
|
|
*/
|
|
|
- srcu_notifier_call_chain(&dev_opp->srcu_head, OPP_EVENT_ADD, new_opp);
|
|
|
+ srcu_notifier_call_chain(&opp_table->srcu_head, OPP_EVENT_ADD, new_opp);
|
|
|
return 0;
|
|
|
|
|
|
free_opp:
|
|
|
- _opp_remove(dev_opp, new_opp, false);
|
|
|
+ _opp_remove(opp_table, new_opp, false);
|
|
|
unlock:
|
|
|
- mutex_unlock(&dev_opp_list_lock);
|
|
|
+ mutex_unlock(&opp_table_lock);
|
|
|
return ret;
|
|
|
}
|
|
|
|
|
@@ -1229,11 +1664,11 @@ unlock:
|
|
|
* @freq: Frequency in Hz for this OPP
|
|
|
* @u_volt: Voltage in uVolts for this OPP
|
|
|
*
|
|
|
- * This function adds an opp definition to the opp list and returns status.
|
|
|
+ * This function adds an opp definition to the opp table and returns status.
|
|
|
* The opp is made available by default and it can be controlled using
|
|
|
* dev_pm_opp_enable/disable functions.
|
|
|
*
|
|
|
- * Locking: The internal device_opp and opp structures are RCU protected.
|
|
|
+ * Locking: The internal opp_table and opp structures are RCU protected.
|
|
|
* Hence this function internally uses RCU updater strategy with mutex locks
|
|
|
* to keep the integrity of the internal data structures. Callers should ensure
|
|
|
* that this function is *NOT* called under RCU protection or in contexts where
|
|
@@ -1265,7 +1700,7 @@ EXPORT_SYMBOL_GPL(dev_pm_opp_add);
|
|
|
* copy operation, returns 0 if no modification was done OR modification was
|
|
|
* successful.
|
|
|
*
|
|
|
- * Locking: The internal device_opp and opp structures are RCU protected.
|
|
|
+ * Locking: The internal opp_table and opp structures are RCU protected.
|
|
|
* Hence this function internally uses RCU updater strategy with mutex locks to
|
|
|
* keep the integrity of the internal data structures. Callers should ensure
|
|
|
* that this function is *NOT* called under RCU protection or in contexts where
|
|
@@ -1274,7 +1709,7 @@ EXPORT_SYMBOL_GPL(dev_pm_opp_add);
|
|
|
static int _opp_set_availability(struct device *dev, unsigned long freq,
|
|
|
bool availability_req)
|
|
|
{
|
|
|
- struct device_opp *dev_opp;
|
|
|
+ struct opp_table *opp_table;
|
|
|
struct dev_pm_opp *new_opp, *tmp_opp, *opp = ERR_PTR(-ENODEV);
|
|
|
int r = 0;
|
|
|
|
|
@@ -1283,18 +1718,18 @@ static int _opp_set_availability(struct device *dev, unsigned long freq,
|
|
|
if (!new_opp)
|
|
|
return -ENOMEM;
|
|
|
|
|
|
- mutex_lock(&dev_opp_list_lock);
|
|
|
+ mutex_lock(&opp_table_lock);
|
|
|
|
|
|
- /* Find the device_opp */
|
|
|
- dev_opp = _find_device_opp(dev);
|
|
|
- if (IS_ERR(dev_opp)) {
|
|
|
- r = PTR_ERR(dev_opp);
|
|
|
+ /* Find the opp_table */
|
|
|
+ opp_table = _find_opp_table(dev);
|
|
|
+ if (IS_ERR(opp_table)) {
|
|
|
+ r = PTR_ERR(opp_table);
|
|
|
dev_warn(dev, "%s: Device OPP not found (%d)\n", __func__, r);
|
|
|
goto unlock;
|
|
|
}
|
|
|
|
|
|
/* Do we have the frequency? */
|
|
|
- list_for_each_entry(tmp_opp, &dev_opp->opp_list, node) {
|
|
|
+ list_for_each_entry(tmp_opp, &opp_table->opp_list, node) {
|
|
|
if (tmp_opp->rate == freq) {
|
|
|
opp = tmp_opp;
|
|
|
break;
|
|
@@ -1315,21 +1750,21 @@ static int _opp_set_availability(struct device *dev, unsigned long freq,
|
|
|
new_opp->available = availability_req;
|
|
|
|
|
|
list_replace_rcu(&opp->node, &new_opp->node);
|
|
|
- mutex_unlock(&dev_opp_list_lock);
|
|
|
- call_srcu(&dev_opp->srcu_head.srcu, &opp->rcu_head, _kfree_opp_rcu);
|
|
|
+ mutex_unlock(&opp_table_lock);
|
|
|
+ call_srcu(&opp_table->srcu_head.srcu, &opp->rcu_head, _kfree_opp_rcu);
|
|
|
|
|
|
/* Notify the change of the OPP availability */
|
|
|
if (availability_req)
|
|
|
- srcu_notifier_call_chain(&dev_opp->srcu_head, OPP_EVENT_ENABLE,
|
|
|
- new_opp);
|
|
|
+ srcu_notifier_call_chain(&opp_table->srcu_head,
|
|
|
+ OPP_EVENT_ENABLE, new_opp);
|
|
|
else
|
|
|
- srcu_notifier_call_chain(&dev_opp->srcu_head, OPP_EVENT_DISABLE,
|
|
|
- new_opp);
|
|
|
+ srcu_notifier_call_chain(&opp_table->srcu_head,
|
|
|
+ OPP_EVENT_DISABLE, new_opp);
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
unlock:
|
|
|
- mutex_unlock(&dev_opp_list_lock);
|
|
|
+ mutex_unlock(&opp_table_lock);
|
|
|
kfree(new_opp);
|
|
|
return r;
|
|
|
}
|
|
@@ -1343,7 +1778,7 @@ unlock:
|
|
|
* corresponding error value. It is meant to be used for users an OPP available
|
|
|
* after being temporarily made unavailable with dev_pm_opp_disable.
|
|
|
*
|
|
|
- * Locking: The internal device_opp and opp structures are RCU protected.
|
|
|
+ * Locking: The internal opp_table and opp structures are RCU protected.
|
|
|
* Hence this function indirectly uses RCU and mutex locks to keep the
|
|
|
* integrity of the internal data structures. Callers should ensure that
|
|
|
* this function is *NOT* called under RCU protection or in contexts where
|
|
@@ -1369,7 +1804,7 @@ EXPORT_SYMBOL_GPL(dev_pm_opp_enable);
|
|
|
* control by users to make this OPP not available until the circumstances are
|
|
|
* right to make it available again (with a call to dev_pm_opp_enable).
|
|
|
*
|
|
|
- * Locking: The internal device_opp and opp structures are RCU protected.
|
|
|
+ * Locking: The internal opp_table and opp structures are RCU protected.
|
|
|
* Hence this function indirectly uses RCU and mutex locks to keep the
|
|
|
* integrity of the internal data structures. Callers should ensure that
|
|
|
* this function is *NOT* called under RCU protection or in contexts where
|
|
@@ -1387,26 +1822,26 @@ EXPORT_SYMBOL_GPL(dev_pm_opp_disable);
|
|
|
|
|
|
/**
|
|
|
* dev_pm_opp_get_notifier() - find notifier_head of the device with opp
|
|
|
- * @dev: device pointer used to lookup device OPPs.
|
|
|
+ * @dev: device pointer used to lookup OPP table.
|
|
|
*
|
|
|
* Return: pointer to notifier head if found, otherwise -ENODEV or
|
|
|
* -EINVAL based on type of error casted as pointer. value must be checked
|
|
|
* with IS_ERR to determine valid pointer or error result.
|
|
|
*
|
|
|
- * Locking: This function must be called under rcu_read_lock(). dev_opp is a RCU
|
|
|
- * protected pointer. The reason for the same is that the opp pointer which is
|
|
|
- * returned will remain valid for use with opp_get_{voltage, freq} only while
|
|
|
+ * Locking: This function must be called under rcu_read_lock(). opp_table is a
|
|
|
+ * RCU protected pointer. The reason for the same is that the opp pointer which
|
|
|
+ * is returned will remain valid for use with opp_get_{voltage, freq} only while
|
|
|
* under the locked area. The pointer returned must be used prior to unlocking
|
|
|
* with rcu_read_unlock() to maintain the integrity of the pointer.
|
|
|
*/
|
|
|
struct srcu_notifier_head *dev_pm_opp_get_notifier(struct device *dev)
|
|
|
{
|
|
|
- struct device_opp *dev_opp = _find_device_opp(dev);
|
|
|
+ struct opp_table *opp_table = _find_opp_table(dev);
|
|
|
|
|
|
- if (IS_ERR(dev_opp))
|
|
|
- return ERR_CAST(dev_opp); /* matching type */
|
|
|
+ if (IS_ERR(opp_table))
|
|
|
+ return ERR_CAST(opp_table); /* matching type */
|
|
|
|
|
|
- return &dev_opp->srcu_head;
|
|
|
+ return &opp_table->srcu_head;
|
|
|
}
|
|
|
EXPORT_SYMBOL_GPL(dev_pm_opp_get_notifier);
|
|
|
|
|
@@ -1414,11 +1849,11 @@ EXPORT_SYMBOL_GPL(dev_pm_opp_get_notifier);
|
|
|
/**
|
|
|
* dev_pm_opp_of_remove_table() - Free OPP table entries created from static DT
|
|
|
* entries
|
|
|
- * @dev: device pointer used to lookup device OPPs.
|
|
|
+ * @dev: device pointer used to lookup OPP table.
|
|
|
*
|
|
|
* Free OPPs created using static entries present in DT.
|
|
|
*
|
|
|
- * Locking: The internal device_opp and opp structures are RCU protected.
|
|
|
+ * Locking: The internal opp_table and opp structures are RCU protected.
|
|
|
* Hence this function indirectly uses RCU updater strategy with mutex locks
|
|
|
* to keep the integrity of the internal data structures. Callers should ensure
|
|
|
* that this function is *NOT* called under RCU protection or in contexts where
|
|
@@ -1426,38 +1861,38 @@ EXPORT_SYMBOL_GPL(dev_pm_opp_get_notifier);
|
|
|
*/
|
|
|
void dev_pm_opp_of_remove_table(struct device *dev)
|
|
|
{
|
|
|
- struct device_opp *dev_opp;
|
|
|
+ struct opp_table *opp_table;
|
|
|
struct dev_pm_opp *opp, *tmp;
|
|
|
|
|
|
- /* Hold our list modification lock here */
|
|
|
- mutex_lock(&dev_opp_list_lock);
|
|
|
+ /* Hold our table modification lock here */
|
|
|
+ mutex_lock(&opp_table_lock);
|
|
|
|
|
|
- /* Check for existing list for 'dev' */
|
|
|
- dev_opp = _find_device_opp(dev);
|
|
|
- if (IS_ERR(dev_opp)) {
|
|
|
- int error = PTR_ERR(dev_opp);
|
|
|
+ /* Check for existing table for 'dev' */
|
|
|
+ opp_table = _find_opp_table(dev);
|
|
|
+ if (IS_ERR(opp_table)) {
|
|
|
+ int error = PTR_ERR(opp_table);
|
|
|
|
|
|
if (error != -ENODEV)
|
|
|
- WARN(1, "%s: dev_opp: %d\n",
|
|
|
+ WARN(1, "%s: opp_table: %d\n",
|
|
|
IS_ERR_OR_NULL(dev) ?
|
|
|
"Invalid device" : dev_name(dev),
|
|
|
error);
|
|
|
goto unlock;
|
|
|
}
|
|
|
|
|
|
- /* Find if dev_opp manages a single device */
|
|
|
- if (list_is_singular(&dev_opp->dev_list)) {
|
|
|
+ /* Find if opp_table manages a single device */
|
|
|
+ if (list_is_singular(&opp_table->dev_list)) {
|
|
|
/* Free static OPPs */
|
|
|
- list_for_each_entry_safe(opp, tmp, &dev_opp->opp_list, node) {
|
|
|
+ list_for_each_entry_safe(opp, tmp, &opp_table->opp_list, node) {
|
|
|
if (!opp->dynamic)
|
|
|
- _opp_remove(dev_opp, opp, true);
|
|
|
+ _opp_remove(opp_table, opp, true);
|
|
|
}
|
|
|
} else {
|
|
|
- _remove_list_dev(_find_list_dev(dev, dev_opp), dev_opp);
|
|
|
+ _remove_opp_dev(_find_opp_dev(dev, opp_table), opp_table);
|
|
|
}
|
|
|
|
|
|
unlock:
|
|
|
- mutex_unlock(&dev_opp_list_lock);
|
|
|
+ mutex_unlock(&opp_table_lock);
|
|
|
}
|
|
|
EXPORT_SYMBOL_GPL(dev_pm_opp_of_remove_table);
|
|
|
|
|
@@ -1478,22 +1913,22 @@ struct device_node *_of_get_opp_desc_node(struct device *dev)
|
|
|
static int _of_add_opp_table_v2(struct device *dev, struct device_node *opp_np)
|
|
|
{
|
|
|
struct device_node *np;
|
|
|
- struct device_opp *dev_opp;
|
|
|
+ struct opp_table *opp_table;
|
|
|
int ret = 0, count = 0;
|
|
|
|
|
|
- mutex_lock(&dev_opp_list_lock);
|
|
|
+ mutex_lock(&opp_table_lock);
|
|
|
|
|
|
- dev_opp = _managed_opp(opp_np);
|
|
|
- if (dev_opp) {
|
|
|
+ opp_table = _managed_opp(opp_np);
|
|
|
+ if (opp_table) {
|
|
|
/* OPPs are already managed */
|
|
|
- if (!_add_list_dev(dev, dev_opp))
|
|
|
+ if (!_add_opp_dev(dev, opp_table))
|
|
|
ret = -ENOMEM;
|
|
|
- mutex_unlock(&dev_opp_list_lock);
|
|
|
+ mutex_unlock(&opp_table_lock);
|
|
|
return ret;
|
|
|
}
|
|
|
- mutex_unlock(&dev_opp_list_lock);
|
|
|
+ mutex_unlock(&opp_table_lock);
|
|
|
|
|
|
- /* We have opp-list node now, iterate over it and add OPPs */
|
|
|
+ /* We have opp-table node now, iterate over it and add OPPs */
|
|
|
for_each_available_child_of_node(opp_np, np) {
|
|
|
count++;
|
|
|
|
|
@@ -1509,19 +1944,19 @@ static int _of_add_opp_table_v2(struct device *dev, struct device_node *opp_np)
|
|
|
if (WARN_ON(!count))
|
|
|
return -ENOENT;
|
|
|
|
|
|
- mutex_lock(&dev_opp_list_lock);
|
|
|
+ mutex_lock(&opp_table_lock);
|
|
|
|
|
|
- dev_opp = _find_device_opp(dev);
|
|
|
- if (WARN_ON(IS_ERR(dev_opp))) {
|
|
|
- ret = PTR_ERR(dev_opp);
|
|
|
- mutex_unlock(&dev_opp_list_lock);
|
|
|
+ opp_table = _find_opp_table(dev);
|
|
|
+ if (WARN_ON(IS_ERR(opp_table))) {
|
|
|
+ ret = PTR_ERR(opp_table);
|
|
|
+ mutex_unlock(&opp_table_lock);
|
|
|
goto free_table;
|
|
|
}
|
|
|
|
|
|
- dev_opp->np = opp_np;
|
|
|
- dev_opp->shared_opp = of_property_read_bool(opp_np, "opp-shared");
|
|
|
+ opp_table->np = opp_np;
|
|
|
+ opp_table->shared_opp = of_property_read_bool(opp_np, "opp-shared");
|
|
|
|
|
|
- mutex_unlock(&dev_opp_list_lock);
|
|
|
+ mutex_unlock(&opp_table_lock);
|
|
|
|
|
|
return 0;
|
|
|
|
|
@@ -1550,7 +1985,7 @@ static int _of_add_opp_table_v1(struct device *dev)
|
|
|
*/
|
|
|
nr = prop->length / sizeof(u32);
|
|
|
if (nr % 2) {
|
|
|
- dev_err(dev, "%s: Invalid OPP list\n", __func__);
|
|
|
+ dev_err(dev, "%s: Invalid OPP table\n", __func__);
|
|
|
return -EINVAL;
|
|
|
}
|
|
|
|
|
@@ -1570,11 +2005,11 @@ static int _of_add_opp_table_v1(struct device *dev)
|
|
|
|
|
|
/**
|
|
|
* dev_pm_opp_of_add_table() - Initialize opp table from device tree
|
|
|
- * @dev: device pointer used to lookup device OPPs.
|
|
|
+ * @dev: device pointer used to lookup OPP table.
|
|
|
*
|
|
|
* Register the initial OPP table with the OPP library for given device.
|
|
|
*
|
|
|
- * Locking: The internal device_opp and opp structures are RCU protected.
|
|
|
+ * Locking: The internal opp_table and opp structures are RCU protected.
|
|
|
* Hence this function indirectly uses RCU updater strategy with mutex locks
|
|
|
* to keep the integrity of the internal data structures. Callers should ensure
|
|
|
* that this function is *NOT* called under RCU protection or in contexts where
|