Browse Source

Merge branch 'pm-opp' into pm-cpufreq

Rafael J. Wysocki 10 years ago
parent
commit
2af3411f0e
2 changed files with 166 additions and 42 deletions
  1. 155 41
      drivers/base/power/opp.c
  2. 11 1
      include/linux/pm_opp.h

+ 155 - 41
drivers/base/power/opp.c

@@ -49,11 +49,12 @@
  *		are protected by the dev_opp_list_lock for integrity.
  *		are protected by the dev_opp_list_lock for integrity.
  *		IMPORTANT: the opp nodes should be maintained in increasing
  *		IMPORTANT: the opp nodes should be maintained in increasing
  *		order.
  *		order.
+ * @dynamic:	not-created from static DT entries.
  * @available:	true/false - marks if this OPP as available or not
  * @available:	true/false - marks if this OPP as available or not
  * @rate:	Frequency in hertz
  * @rate:	Frequency in hertz
  * @u_volt:	Nominal voltage in microvolts corresponding to this OPP
  * @u_volt:	Nominal voltage in microvolts corresponding to this OPP
  * @dev_opp:	points back to the device_opp struct this opp belongs to
  * @dev_opp:	points back to the device_opp struct this opp belongs to
- * @head:	RCU callback head used for deferred freeing
+ * @rcu_head:	RCU callback head used for deferred freeing
  *
  *
  * This structure stores the OPP information for a given device.
  * This structure stores the OPP information for a given device.
  */
  */
@@ -61,11 +62,12 @@ struct dev_pm_opp {
 	struct list_head node;
 	struct list_head node;
 
 
 	bool available;
 	bool available;
+	bool dynamic;
 	unsigned long rate;
 	unsigned long rate;
 	unsigned long u_volt;
 	unsigned long u_volt;
 
 
 	struct device_opp *dev_opp;
 	struct device_opp *dev_opp;
-	struct rcu_head head;
+	struct rcu_head rcu_head;
 };
 };
 
 
 /**
 /**
@@ -76,7 +78,8 @@ struct dev_pm_opp {
  *		RCU usage: nodes are not modified in the list of device_opp,
  *		RCU usage: nodes are not modified in the list of device_opp,
  *		however addition is possible and is secured by dev_opp_list_lock
  *		however addition is possible and is secured by dev_opp_list_lock
  * @dev:	device pointer
  * @dev:	device pointer
- * @head:	notifier head to notify the OPP availability changes.
+ * @srcu_head:	notifier head to notify the OPP availability changes.
+ * @rcu_head:	RCU callback head used for deferred freeing
  * @opp_list:	list of opps
  * @opp_list:	list of opps
  *
  *
  * This is an internal data structure maintaining the link to opps attached to
  * This is an internal data structure maintaining the link to opps attached to
@@ -87,7 +90,8 @@ struct device_opp {
 	struct list_head node;
 	struct list_head node;
 
 
 	struct device *dev;
 	struct device *dev;
-	struct srcu_notifier_head head;
+	struct srcu_notifier_head srcu_head;
+	struct rcu_head rcu_head;
 	struct list_head opp_list;
 	struct list_head opp_list;
 };
 };
 
 
@@ -378,30 +382,8 @@ struct dev_pm_opp *dev_pm_opp_find_freq_floor(struct device *dev,
 }
 }
 EXPORT_SYMBOL_GPL(dev_pm_opp_find_freq_floor);
 EXPORT_SYMBOL_GPL(dev_pm_opp_find_freq_floor);
 
 
-/**
- * dev_pm_opp_add()  - Add an OPP table from a table definitions
- * @dev:	device for which we do this operation
- * @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.
- * 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.
- * 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.
- *
- * Return:
- * 0:		On success OR
- *		Duplicate OPPs (both freq and volt are same) and opp->available
- * -EEXIST:	Freq are same and volt are different OR
- *		Duplicate OPPs (both freq and volt are same) and !opp->available
- * -ENOMEM:	Memory allocation failure
- */
-int dev_pm_opp_add(struct device *dev, unsigned long freq, unsigned long u_volt)
+static int dev_pm_opp_add_dynamic(struct device *dev, unsigned long freq,
+				  unsigned long u_volt, bool dynamic)
 {
 {
 	struct device_opp *dev_opp = NULL;
 	struct device_opp *dev_opp = NULL;
 	struct dev_pm_opp *opp, *new_opp;
 	struct dev_pm_opp *opp, *new_opp;
@@ -417,6 +399,13 @@ int dev_pm_opp_add(struct device *dev, unsigned long freq, unsigned long u_volt)
 	/* Hold our list modification lock here */
 	/* Hold our list modification lock here */
 	mutex_lock(&dev_opp_list_lock);
 	mutex_lock(&dev_opp_list_lock);
 
 
+	/* populate the opp table */
+	new_opp->dev_opp = dev_opp;
+	new_opp->rate = freq;
+	new_opp->u_volt = u_volt;
+	new_opp->available = true;
+	new_opp->dynamic = dynamic;
+
 	/* Check for existing list for 'dev' */
 	/* Check for existing list for 'dev' */
 	dev_opp = find_device_opp(dev);
 	dev_opp = find_device_opp(dev);
 	if (IS_ERR(dev_opp)) {
 	if (IS_ERR(dev_opp)) {
@@ -436,19 +425,15 @@ int dev_pm_opp_add(struct device *dev, unsigned long freq, unsigned long u_volt)
 		}
 		}
 
 
 		dev_opp->dev = dev;
 		dev_opp->dev = dev;
-		srcu_init_notifier_head(&dev_opp->head);
+		srcu_init_notifier_head(&dev_opp->srcu_head);
 		INIT_LIST_HEAD(&dev_opp->opp_list);
 		INIT_LIST_HEAD(&dev_opp->opp_list);
 
 
 		/* Secure the device list modification */
 		/* Secure the device list modification */
 		list_add_rcu(&dev_opp->node, &dev_opp_list);
 		list_add_rcu(&dev_opp->node, &dev_opp_list);
+		head = &dev_opp->opp_list;
+		goto list_add;
 	}
 	}
 
 
-	/* populate the opp table */
-	new_opp->dev_opp = dev_opp;
-	new_opp->rate = freq;
-	new_opp->u_volt = u_volt;
-	new_opp->available = true;
-
 	/*
 	/*
 	 * Insert new OPP in order of increasing frequency
 	 * Insert new OPP in order of increasing frequency
 	 * and discard if already present
 	 * and discard if already present
@@ -474,6 +459,7 @@ int dev_pm_opp_add(struct device *dev, unsigned long freq, unsigned long u_volt)
 		return ret;
 		return ret;
 	}
 	}
 
 
+list_add:
 	list_add_rcu(&new_opp->node, head);
 	list_add_rcu(&new_opp->node, head);
 	mutex_unlock(&dev_opp_list_lock);
 	mutex_unlock(&dev_opp_list_lock);
 
 
@@ -481,11 +467,109 @@ int dev_pm_opp_add(struct device *dev, unsigned long freq, unsigned long u_volt)
 	 * Notify the changes in the availability of the operable
 	 * Notify the changes in the availability of the operable
 	 * frequency/voltage list.
 	 * frequency/voltage list.
 	 */
 	 */
-	srcu_notifier_call_chain(&dev_opp->head, OPP_EVENT_ADD, new_opp);
+	srcu_notifier_call_chain(&dev_opp->srcu_head, OPP_EVENT_ADD, new_opp);
 	return 0;
 	return 0;
 }
 }
+
+/**
+ * dev_pm_opp_add()  - Add an OPP table from a table definitions
+ * @dev:	device for which we do this operation
+ * @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.
+ * 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.
+ * 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.
+ *
+ * Return:
+ * 0:		On success OR
+ *		Duplicate OPPs (both freq and volt are same) and opp->available
+ * -EEXIST:	Freq are same and volt are different OR
+ *		Duplicate OPPs (both freq and volt are same) and !opp->available
+ * -ENOMEM:	Memory allocation failure
+ */
+int dev_pm_opp_add(struct device *dev, unsigned long freq, unsigned long u_volt)
+{
+	return dev_pm_opp_add_dynamic(dev, freq, u_volt, true);
+}
 EXPORT_SYMBOL_GPL(dev_pm_opp_add);
 EXPORT_SYMBOL_GPL(dev_pm_opp_add);
 
 
+static void kfree_opp_rcu(struct rcu_head *head)
+{
+	struct dev_pm_opp *opp = container_of(head, struct dev_pm_opp, rcu_head);
+
+	kfree_rcu(opp, rcu_head);
+}
+
+static void kfree_device_rcu(struct rcu_head *head)
+{
+	struct device_opp *device_opp = container_of(head, struct device_opp, rcu_head);
+
+	kfree(device_opp);
+}
+
+void __dev_pm_opp_remove(struct device_opp *dev_opp, struct dev_pm_opp *opp)
+{
+	/*
+	 * Notify the changes in the availability of the operable
+	 * frequency/voltage list.
+	 */
+	srcu_notifier_call_chain(&dev_opp->srcu_head, OPP_EVENT_REMOVE, opp);
+	list_del_rcu(&opp->node);
+	call_srcu(&dev_opp->srcu_head.srcu, &opp->rcu_head, kfree_opp_rcu);
+
+	if (list_empty(&dev_opp->opp_list)) {
+		list_del_rcu(&dev_opp->node);
+		call_srcu(&dev_opp->srcu_head.srcu, &dev_opp->rcu_head,
+			  kfree_device_rcu);
+	}
+}
+
+/**
+ * dev_pm_opp_remove()  - Remove an OPP from OPP list
+ * @dev:	device for which we do this operation
+ * @freq:	OPP to remove with matching 'freq'
+ *
+ * This function removes an opp from the opp list.
+ */
+void dev_pm_opp_remove(struct device *dev, unsigned long freq)
+{
+	struct dev_pm_opp *opp;
+	struct device_opp *dev_opp;
+	bool found = false;
+
+	/* Hold our list modification lock here */
+	mutex_lock(&dev_opp_list_lock);
+
+	dev_opp = find_device_opp(dev);
+	if (IS_ERR(dev_opp))
+		goto unlock;
+
+	list_for_each_entry(opp, &dev_opp->opp_list, node) {
+		if (opp->rate == freq) {
+			found = true;
+			break;
+		}
+	}
+
+	if (!found) {
+		dev_warn(dev, "%s: Couldn't find OPP with freq: %lu\n",
+			 __func__, freq);
+		goto unlock;
+	}
+
+	__dev_pm_opp_remove(dev_opp, opp);
+unlock:
+	mutex_unlock(&dev_opp_list_lock);
+}
+EXPORT_SYMBOL_GPL(dev_pm_opp_remove);
+
 /**
 /**
  * opp_set_availability() - helper to set the availability of an opp
  * opp_set_availability() - helper to set the availability of an opp
  * @dev:		device for which we do this operation
  * @dev:		device for which we do this operation
@@ -557,14 +641,14 @@ static int opp_set_availability(struct device *dev, unsigned long freq,
 
 
 	list_replace_rcu(&opp->node, &new_opp->node);
 	list_replace_rcu(&opp->node, &new_opp->node);
 	mutex_unlock(&dev_opp_list_lock);
 	mutex_unlock(&dev_opp_list_lock);
-	kfree_rcu(opp, head);
+	call_srcu(&dev_opp->srcu_head.srcu, &opp->rcu_head, kfree_opp_rcu);
 
 
 	/* Notify the change of the OPP availability */
 	/* Notify the change of the OPP availability */
 	if (availability_req)
 	if (availability_req)
-		srcu_notifier_call_chain(&dev_opp->head, OPP_EVENT_ENABLE,
+		srcu_notifier_call_chain(&dev_opp->srcu_head, OPP_EVENT_ENABLE,
 					 new_opp);
 					 new_opp);
 	else
 	else
-		srcu_notifier_call_chain(&dev_opp->head, OPP_EVENT_DISABLE,
+		srcu_notifier_call_chain(&dev_opp->srcu_head, OPP_EVENT_DISABLE,
 					 new_opp);
 					 new_opp);
 
 
 	return 0;
 	return 0;
@@ -629,7 +713,7 @@ struct srcu_notifier_head *dev_pm_opp_get_notifier(struct device *dev)
 	if (IS_ERR(dev_opp))
 	if (IS_ERR(dev_opp))
 		return ERR_CAST(dev_opp); /* matching type */
 		return ERR_CAST(dev_opp); /* matching type */
 
 
-	return &dev_opp->head;
+	return &dev_opp->srcu_head;
 }
 }
 
 
 #ifdef CONFIG_OF
 #ifdef CONFIG_OF
@@ -666,7 +750,7 @@ int of_init_opp_table(struct device *dev)
 		unsigned long freq = be32_to_cpup(val++) * 1000;
 		unsigned long freq = be32_to_cpup(val++) * 1000;
 		unsigned long volt = be32_to_cpup(val++);
 		unsigned long volt = be32_to_cpup(val++);
 
 
-		if (dev_pm_opp_add(dev, freq, volt))
+		if (dev_pm_opp_add_dynamic(dev, freq, volt, false))
 			dev_warn(dev, "%s: Failed to add OPP %ld\n",
 			dev_warn(dev, "%s: Failed to add OPP %ld\n",
 				 __func__, freq);
 				 __func__, freq);
 		nr -= 2;
 		nr -= 2;
@@ -675,4 +759,34 @@ int of_init_opp_table(struct device *dev)
 	return 0;
 	return 0;
 }
 }
 EXPORT_SYMBOL_GPL(of_init_opp_table);
 EXPORT_SYMBOL_GPL(of_init_opp_table);
+
+/**
+ * of_free_opp_table() - Free OPP table entries created from static DT entries
+ * @dev:	device pointer used to lookup device OPPs.
+ *
+ * Free OPPs created using static entries present in DT.
+ */
+void of_free_opp_table(struct device *dev)
+{
+	struct device_opp *dev_opp = find_device_opp(dev);
+	struct dev_pm_opp *opp, *tmp;
+
+	/* Check for existing list for 'dev' */
+	dev_opp = find_device_opp(dev);
+	if (WARN(IS_ERR(dev_opp), "%s: dev_opp: %ld\n", dev_name(dev),
+		 PTR_ERR(dev_opp)))
+		return;
+
+	/* 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)
+			__dev_pm_opp_remove(dev_opp, opp);
+	}
+
+	mutex_unlock(&dev_opp_list_lock);
+}
+EXPORT_SYMBOL_GPL(of_free_opp_table);
 #endif
 #endif

+ 11 - 1
include/linux/pm_opp.h

@@ -21,7 +21,7 @@ struct dev_pm_opp;
 struct device;
 struct device;
 
 
 enum dev_pm_opp_event {
 enum dev_pm_opp_event {
-	OPP_EVENT_ADD, OPP_EVENT_ENABLE, OPP_EVENT_DISABLE,
+	OPP_EVENT_ADD, OPP_EVENT_REMOVE, OPP_EVENT_ENABLE, OPP_EVENT_DISABLE,
 };
 };
 
 
 #if defined(CONFIG_PM_OPP)
 #if defined(CONFIG_PM_OPP)
@@ -44,6 +44,7 @@ struct dev_pm_opp *dev_pm_opp_find_freq_ceil(struct device *dev,
 
 
 int dev_pm_opp_add(struct device *dev, unsigned long freq,
 int dev_pm_opp_add(struct device *dev, unsigned long freq,
 		   unsigned long u_volt);
 		   unsigned long u_volt);
+void dev_pm_opp_remove(struct device *dev, unsigned long freq);
 
 
 int dev_pm_opp_enable(struct device *dev, unsigned long freq);
 int dev_pm_opp_enable(struct device *dev, unsigned long freq);
 
 
@@ -90,6 +91,10 @@ static inline int dev_pm_opp_add(struct device *dev, unsigned long freq,
 	return -EINVAL;
 	return -EINVAL;
 }
 }
 
 
+static inline void dev_pm_opp_remove(struct device *dev, unsigned long freq)
+{
+}
+
 static inline int dev_pm_opp_enable(struct device *dev, unsigned long freq)
 static inline int dev_pm_opp_enable(struct device *dev, unsigned long freq)
 {
 {
 	return 0;
 	return 0;
@@ -109,11 +114,16 @@ static inline struct srcu_notifier_head *dev_pm_opp_get_notifier(
 
 
 #if defined(CONFIG_PM_OPP) && defined(CONFIG_OF)
 #if defined(CONFIG_PM_OPP) && defined(CONFIG_OF)
 int of_init_opp_table(struct device *dev);
 int of_init_opp_table(struct device *dev);
+void of_free_opp_table(struct device *dev);
 #else
 #else
 static inline int of_init_opp_table(struct device *dev)
 static inline int of_init_opp_table(struct device *dev)
 {
 {
 	return -EINVAL;
 	return -EINVAL;
 }
 }
+
+static inline void of_free_opp_table(struct device *dev)
+{
+}
 #endif
 #endif
 
 
 #endif		/* __LINUX_OPP_H__ */
 #endif		/* __LINUX_OPP_H__ */