|
|
@@ -85,6 +85,21 @@ struct dev_pm_opp {
|
|
|
struct device_node *np;
|
|
|
};
|
|
|
|
|
|
+/**
|
|
|
+ * struct device_list_opp - devices managed by 'struct device_opp'
|
|
|
+ * @node: list node
|
|
|
+ * @dev: device to which the struct object belongs
|
|
|
+ * @rcu_head: RCU callback head used for deferred freeing
|
|
|
+ *
|
|
|
+ * This is an internal data structure maintaining the list of devices that are
|
|
|
+ * managed by 'struct device_opp'.
|
|
|
+ */
|
|
|
+struct device_list_opp {
|
|
|
+ struct list_head node;
|
|
|
+ const struct device *dev;
|
|
|
+ struct rcu_head rcu_head;
|
|
|
+};
|
|
|
+
|
|
|
/**
|
|
|
* struct device_opp - Device opp structure
|
|
|
* @node: list node - contains the devices with OPPs that
|
|
|
@@ -92,10 +107,12 @@ struct dev_pm_opp {
|
|
|
* list.
|
|
|
* RCU usage: nodes are not modified in the list of device_opp,
|
|
|
* however addition is possible and is secured by dev_opp_list_lock
|
|
|
- * @dev: device pointer
|
|
|
* @srcu_head: notifier head to notify the OPP availability changes.
|
|
|
* @rcu_head: RCU callback head used for deferred freeing
|
|
|
+ * @dev_list: list of devices that share these OPPs
|
|
|
* @opp_list: list of opps
|
|
|
+ * @np: struct device_node pointer for opp's DT node.
|
|
|
+ * @shared_opp: OPP is shared between multiple devices.
|
|
|
*
|
|
|
* This is an internal data structure maintaining the link to opps attached to
|
|
|
* a device. This structure is not meant to be shared to users as it is
|
|
|
@@ -108,12 +125,14 @@ struct dev_pm_opp {
|
|
|
struct device_opp {
|
|
|
struct list_head node;
|
|
|
|
|
|
- struct device *dev;
|
|
|
struct srcu_notifier_head srcu_head;
|
|
|
struct rcu_head rcu_head;
|
|
|
+ struct list_head dev_list;
|
|
|
struct list_head opp_list;
|
|
|
|
|
|
+ struct device_node *np;
|
|
|
unsigned long clock_latency_ns_max;
|
|
|
+ bool shared_opp;
|
|
|
};
|
|
|
|
|
|
/*
|
|
|
@@ -133,6 +152,38 @@ do { \
|
|
|
"dev_opp_list_lock protection"); \
|
|
|
} while (0)
|
|
|
|
|
|
+static struct device_list_opp *_find_list_dev(const struct device *dev,
|
|
|
+ struct device_opp *dev_opp)
|
|
|
+{
|
|
|
+ struct device_list_opp *list_dev;
|
|
|
+
|
|
|
+ list_for_each_entry(list_dev, &dev_opp->dev_list, node)
|
|
|
+ if (list_dev->dev == dev)
|
|
|
+ return list_dev;
|
|
|
+
|
|
|
+ return NULL;
|
|
|
+}
|
|
|
+
|
|
|
+static struct device_opp *_managed_opp(const struct device_node *np)
|
|
|
+{
|
|
|
+ struct device_opp *dev_opp;
|
|
|
+
|
|
|
+ list_for_each_entry_rcu(dev_opp, &dev_opp_list, node) {
|
|
|
+ if (dev_opp->np == np) {
|
|
|
+ /*
|
|
|
+ * Multiple devices can point to the same OPP table and
|
|
|
+ * so will have same node-pointer, 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 NULL;
|
|
|
+}
|
|
|
+
|
|
|
/**
|
|
|
* _find_device_opp() - find device_opp struct using device pointer
|
|
|
* @dev: device pointer used to lookup device OPPs
|
|
|
@@ -149,21 +200,18 @@ do { \
|
|
|
*/
|
|
|
static struct device_opp *_find_device_opp(struct device *dev)
|
|
|
{
|
|
|
- struct device_opp *tmp_dev_opp, *dev_opp = ERR_PTR(-ENODEV);
|
|
|
+ struct device_opp *dev_opp;
|
|
|
|
|
|
if (unlikely(IS_ERR_OR_NULL(dev))) {
|
|
|
pr_err("%s: Invalid parameters\n", __func__);
|
|
|
return ERR_PTR(-EINVAL);
|
|
|
}
|
|
|
|
|
|
- list_for_each_entry_rcu(tmp_dev_opp, &dev_opp_list, node) {
|
|
|
- if (tmp_dev_opp->dev == dev) {
|
|
|
- dev_opp = tmp_dev_opp;
|
|
|
- break;
|
|
|
- }
|
|
|
- }
|
|
|
+ list_for_each_entry_rcu(dev_opp, &dev_opp_list, node)
|
|
|
+ if (_find_list_dev(dev, dev_opp))
|
|
|
+ return dev_opp;
|
|
|
|
|
|
- return dev_opp;
|
|
|
+ return ERR_PTR(-ENODEV);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -450,6 +498,39 @@ 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)
|
|
|
+{
|
|
|
+ struct device_list_opp *list_dev;
|
|
|
+
|
|
|
+ list_dev = container_of(head, struct device_list_opp, rcu_head);
|
|
|
+ kfree_rcu(list_dev, rcu_head);
|
|
|
+}
|
|
|
+
|
|
|
+static void _remove_list_dev(struct device_list_opp *list_dev,
|
|
|
+ struct device_opp *dev_opp)
|
|
|
+{
|
|
|
+ list_del(&list_dev->node);
|
|
|
+ call_srcu(&dev_opp->srcu_head.srcu, &list_dev->rcu_head,
|
|
|
+ _kfree_list_dev_rcu);
|
|
|
+}
|
|
|
+
|
|
|
+static struct device_list_opp *_add_list_dev(const struct device *dev,
|
|
|
+ struct device_opp *dev_opp)
|
|
|
+{
|
|
|
+ struct device_list_opp *list_dev;
|
|
|
+
|
|
|
+ list_dev = kzalloc(sizeof(*list_dev), GFP_KERNEL);
|
|
|
+ if (!list_dev)
|
|
|
+ return NULL;
|
|
|
+
|
|
|
+ /* Initialize list-dev */
|
|
|
+ list_dev->dev = dev;
|
|
|
+ list_add_rcu(&list_dev->node, &dev_opp->dev_list);
|
|
|
+
|
|
|
+ return list_dev;
|
|
|
+}
|
|
|
+
|
|
|
/**
|
|
|
* _add_device_opp() - Find device OPP table or allocate a new one
|
|
|
* @dev: device for which we do this operation
|
|
|
@@ -462,6 +543,7 @@ EXPORT_SYMBOL_GPL(dev_pm_opp_find_freq_floor);
|
|
|
static struct device_opp *_add_device_opp(struct device *dev)
|
|
|
{
|
|
|
struct device_opp *dev_opp;
|
|
|
+ struct device_list_opp *list_dev;
|
|
|
|
|
|
/* Check for existing list for 'dev' first */
|
|
|
dev_opp = _find_device_opp(dev);
|
|
|
@@ -476,7 +558,14 @@ static struct device_opp *_add_device_opp(struct device *dev)
|
|
|
if (!dev_opp)
|
|
|
return NULL;
|
|
|
|
|
|
- dev_opp->dev = dev;
|
|
|
+ INIT_LIST_HEAD(&dev_opp->dev_list);
|
|
|
+
|
|
|
+ list_dev = _add_list_dev(dev, dev_opp);
|
|
|
+ if (!list_dev) {
|
|
|
+ kfree(dev_opp);
|
|
|
+ return NULL;
|
|
|
+ }
|
|
|
+
|
|
|
srcu_init_notifier_head(&dev_opp->srcu_head);
|
|
|
INIT_LIST_HEAD(&dev_opp->opp_list);
|
|
|
|
|
|
@@ -504,9 +593,19 @@ static void _kfree_device_rcu(struct rcu_head *head)
|
|
|
*/
|
|
|
static void _remove_device_opp(struct device_opp *dev_opp)
|
|
|
{
|
|
|
+ struct device_list_opp *list_dev;
|
|
|
+
|
|
|
if (!list_empty(&dev_opp->opp_list))
|
|
|
return;
|
|
|
|
|
|
+ list_dev = list_first_entry(&dev_opp->dev_list, struct device_list_opp,
|
|
|
+ node);
|
|
|
+
|
|
|
+ _remove_list_dev(list_dev, dev_opp);
|
|
|
+
|
|
|
+ /* dev_list must be empty now */
|
|
|
+ WARN_ON(!list_empty(&dev_opp->dev_list));
|
|
|
+
|
|
|
list_del_rcu(&dev_opp->node);
|
|
|
call_srcu(&dev_opp->srcu_head.srcu, &dev_opp->rcu_head,
|
|
|
_kfree_device_rcu);
|
|
|
@@ -616,7 +715,8 @@ static struct dev_pm_opp *_allocate_opp(struct device *dev,
|
|
|
return opp;
|
|
|
}
|
|
|
|
|
|
-static int _opp_add(struct dev_pm_opp *new_opp, struct device_opp *dev_opp)
|
|
|
+static int _opp_add(struct device *dev, struct dev_pm_opp *new_opp,
|
|
|
+ struct device_opp *dev_opp)
|
|
|
{
|
|
|
struct dev_pm_opp *opp;
|
|
|
struct list_head *head = &dev_opp->opp_list;
|
|
|
@@ -639,7 +739,7 @@ static int _opp_add(struct dev_pm_opp *new_opp, struct device_opp *dev_opp)
|
|
|
break;
|
|
|
|
|
|
/* Duplicate OPPs */
|
|
|
- dev_warn(dev_opp->dev, "%s: duplicate OPPs detected. Existing: freq: %lu, volt: %lu, enabled: %d. New: freq: %lu, volt: %lu, enabled: %d\n",
|
|
|
+ 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->u_volt, opp->available,
|
|
|
new_opp->rate, new_opp->u_volt, new_opp->available);
|
|
|
|
|
|
@@ -702,7 +802,7 @@ static int _opp_add_dynamic(struct device *dev, unsigned long freq,
|
|
|
new_opp->available = true;
|
|
|
new_opp->dynamic = dynamic;
|
|
|
|
|
|
- ret = _opp_add(new_opp, dev_opp);
|
|
|
+ ret = _opp_add(dev, new_opp, dev_opp);
|
|
|
if (ret)
|
|
|
goto free_opp;
|
|
|
|
|
|
@@ -819,7 +919,7 @@ static int _opp_add_static_v2(struct device *dev, struct device_node *np)
|
|
|
|
|
|
of_property_read_u32(np, "opp-microamp", (u32 *)&new_opp->u_amp);
|
|
|
|
|
|
- ret = _opp_add(new_opp, dev_opp);
|
|
|
+ ret = _opp_add(dev, new_opp, dev_opp);
|
|
|
if (ret)
|
|
|
goto free_opp;
|
|
|
|
|
|
@@ -1052,6 +1152,9 @@ void of_free_opp_table(struct device *dev)
|
|
|
struct device_opp *dev_opp;
|
|
|
struct dev_pm_opp *opp, *tmp;
|
|
|
|
|
|
+ /* Hold our list modification lock here */
|
|
|
+ mutex_lock(&dev_opp_list_lock);
|
|
|
+
|
|
|
/* Check for existing list for 'dev' */
|
|
|
dev_opp = _find_device_opp(dev);
|
|
|
if (IS_ERR(dev_opp)) {
|
|
|
@@ -1062,18 +1165,21 @@ void of_free_opp_table(struct device *dev)
|
|
|
IS_ERR_OR_NULL(dev) ?
|
|
|
"Invalid device" : dev_name(dev),
|
|
|
error);
|
|
|
- return;
|
|
|
+ goto unlock;
|
|
|
}
|
|
|
|
|
|
- /* Hold our list modification lock here */
|
|
|
- mutex_lock(&dev_opp_list_lock);
|
|
|
-
|
|
|
- /* Free static OPPs */
|
|
|
- list_for_each_entry_safe(opp, tmp, &dev_opp->opp_list, node) {
|
|
|
- if (!opp->dynamic)
|
|
|
- _opp_remove(dev_opp, opp, true);
|
|
|
+ /* Find if dev_opp manages a single device */
|
|
|
+ if (list_is_singular(&dev_opp->dev_list)) {
|
|
|
+ /* Free static OPPs */
|
|
|
+ list_for_each_entry_safe(opp, tmp, &dev_opp->opp_list, node) {
|
|
|
+ if (!opp->dynamic)
|
|
|
+ _opp_remove(dev_opp, opp, true);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ _remove_list_dev(_find_list_dev(dev, dev_opp), dev_opp);
|
|
|
}
|
|
|
|
|
|
+unlock:
|
|
|
mutex_unlock(&dev_opp_list_lock);
|
|
|
}
|
|
|
EXPORT_SYMBOL_GPL(of_free_opp_table);
|
|
|
@@ -1099,6 +1205,7 @@ static int _of_init_opp_table_v2(struct device *dev,
|
|
|
const struct property *prop)
|
|
|
{
|
|
|
struct device_node *opp_np, *np;
|
|
|
+ struct device_opp *dev_opp;
|
|
|
int ret = 0, count = 0;
|
|
|
|
|
|
if (!prop->value)
|
|
|
@@ -1109,6 +1216,14 @@ static int _of_init_opp_table_v2(struct device *dev,
|
|
|
if (IS_ERR(opp_np))
|
|
|
return PTR_ERR(opp_np);
|
|
|
|
|
|
+ dev_opp = _managed_opp(opp_np);
|
|
|
+ if (dev_opp) {
|
|
|
+ /* OPPs are already managed */
|
|
|
+ if (!_add_list_dev(dev, dev_opp))
|
|
|
+ ret = -ENOMEM;
|
|
|
+ goto put_opp_np;
|
|
|
+ }
|
|
|
+
|
|
|
/* We have opp-list node now, iterate over it and add OPPs */
|
|
|
for_each_available_child_of_node(opp_np, np) {
|
|
|
count++;
|
|
|
@@ -1125,8 +1240,19 @@ static int _of_init_opp_table_v2(struct device *dev,
|
|
|
if (WARN_ON(!count))
|
|
|
goto put_opp_np;
|
|
|
|
|
|
- if (ret)
|
|
|
+ if (!ret) {
|
|
|
+ if (!dev_opp) {
|
|
|
+ dev_opp = _find_device_opp(dev);
|
|
|
+ if (WARN_ON(!dev_opp))
|
|
|
+ goto put_opp_np;
|
|
|
+ }
|
|
|
+
|
|
|
+ dev_opp->np = opp_np;
|
|
|
+ dev_opp->shared_opp = of_property_read_bool(opp_np,
|
|
|
+ "opp-shared");
|
|
|
+ } else {
|
|
|
of_free_opp_table(dev);
|
|
|
+ }
|
|
|
|
|
|
put_opp_np:
|
|
|
of_node_put(opp_np);
|