|
@@ -687,6 +687,7 @@ int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq)
|
|
|
{
|
|
|
struct opp_table *opp_table;
|
|
|
unsigned long freq, old_freq;
|
|
|
+ int (*set_opp)(struct dev_pm_set_opp_data *data);
|
|
|
struct dev_pm_opp *old_opp, *opp;
|
|
|
struct regulator **regulators;
|
|
|
struct dev_pm_set_opp_data *data;
|
|
@@ -751,6 +752,11 @@ int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq)
|
|
|
return _generic_set_opp_clk_only(dev, clk, old_freq, freq);
|
|
|
}
|
|
|
|
|
|
+ if (opp_table->set_opp)
|
|
|
+ set_opp = opp_table->set_opp;
|
|
|
+ else
|
|
|
+ set_opp = _generic_set_opp;
|
|
|
+
|
|
|
data = opp_table->set_opp_data;
|
|
|
data->regulators = regulators;
|
|
|
data->regulator_count = opp_table->regulator_count;
|
|
@@ -769,7 +775,7 @@ int dev_pm_opp_set_rate(struct device *dev, unsigned long target_freq)
|
|
|
|
|
|
rcu_read_unlock();
|
|
|
|
|
|
- return _generic_set_opp(data);
|
|
|
+ return set_opp(data);
|
|
|
}
|
|
|
EXPORT_SYMBOL_GPL(dev_pm_opp_set_rate);
|
|
|
|
|
@@ -903,6 +909,9 @@ static void _remove_opp_table(struct opp_table *opp_table)
|
|
|
if (opp_table->regulators)
|
|
|
return;
|
|
|
|
|
|
+ if (opp_table->set_opp)
|
|
|
+ return;
|
|
|
+
|
|
|
/* Release clk */
|
|
|
if (!IS_ERR(opp_table->clk))
|
|
|
clk_put(opp_table->clk);
|
|
@@ -1569,6 +1578,109 @@ unlock:
|
|
|
}
|
|
|
EXPORT_SYMBOL_GPL(dev_pm_opp_put_regulators);
|
|
|
|
|
|
+/**
|
|
|
+ * dev_pm_opp_register_set_opp_helper() - Register custom set OPP helper
|
|
|
+ * @dev: Device for which the helper is getting registered.
|
|
|
+ * @set_opp: Custom set OPP helper.
|
|
|
+ *
|
|
|
+ * This is useful to support complex platforms (like platforms with multiple
|
|
|
+ * regulators per device), instead of the generic OPP set rate helper.
|
|
|
+ *
|
|
|
+ * 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_register_set_opp_helper(struct device *dev,
|
|
|
+ int (*set_opp)(struct dev_pm_set_opp_data *data))
|
|
|
+{
|
|
|
+ struct opp_table *opp_table;
|
|
|
+ int ret;
|
|
|
+
|
|
|
+ if (!set_opp)
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ 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 custom set_opp helper */
|
|
|
+ if (WARN_ON(opp_table->set_opp)) {
|
|
|
+ ret = -EBUSY;
|
|
|
+ goto err;
|
|
|
+ }
|
|
|
+
|
|
|
+ opp_table->set_opp = set_opp;
|
|
|
+
|
|
|
+ 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_register_set_opp_helper);
|
|
|
+
|
|
|
+/**
|
|
|
+ * dev_pm_opp_register_put_opp_helper() - Releases resources blocked for
|
|
|
+ * set_opp helper
|
|
|
+ * @dev: Device for which custom set_opp helper has to be cleared.
|
|
|
+ *
|
|
|
+ * 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_register_put_opp_helper(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 (!opp_table->set_opp) {
|
|
|
+ dev_err(dev, "%s: Doesn't have custom set_opp helper set\n",
|
|
|
+ __func__);
|
|
|
+ goto unlock;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Make sure there are no concurrent readers while updating opp_table */
|
|
|
+ WARN_ON(!list_empty(&opp_table->opp_list));
|
|
|
+
|
|
|
+ opp_table->set_opp = NULL;
|
|
|
+
|
|
|
+ /* 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_register_put_opp_helper);
|
|
|
+
|
|
|
/**
|
|
|
* dev_pm_opp_add() - Add an OPP table from a table definitions
|
|
|
* @dev: device for which we do this operation
|