aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/base
diff options
context:
space:
mode:
authorViresh Kumar <viresh.kumar@linaro.org>2014-11-26 22:24:06 -0500
committerRafael J. Wysocki <rafael.j.wysocki@intel.com>2014-11-29 17:53:20 -0500
commit129eec55df6ab1b5ecdd89fd7db7a2cd103200b5 (patch)
tree2e9bae88e582e9a8f883dd6029802641c1fab93d /drivers/base
parent38393409da345cd48d94a0e74c7bbc3402742882 (diff)
PM / OPP Introduce APIs to remove OPPs
OPPs are created statically (from DT) or dynamically. Currently we don't free OPPs that are created statically, when the module unloads. And so if the module is inserted back again, we get warning for duplicate OPPs as the same were already present. Also, there might be a need to remove dynamic OPPs in future and so API for that is also added. This patch adds helper APIs to remove/free existing static and dynamic OPPs. Because the OPPs are used both under RCU and SRCU, we have to wait for grace period of both. And so are using kfree_rcu() from within call_srcu(). Signed-off-by: Viresh Kumar <viresh.kumar@linaro.org> Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Diffstat (limited to 'drivers/base')
-rw-r--r--drivers/base/power/opp.c102
1 files changed, 102 insertions, 0 deletions
diff --git a/drivers/base/power/opp.c b/drivers/base/power/opp.c
index b249b0127eff..977474a3c64f 100644
--- a/drivers/base/power/opp.c
+++ b/drivers/base/power/opp.c
@@ -79,6 +79,7 @@ struct dev_pm_opp {
79 * however addition is possible and is secured by dev_opp_list_lock 79 * however addition is possible and is secured by dev_opp_list_lock
80 * @dev: device pointer 80 * @dev: device pointer
81 * @srcu_head: notifier head to notify the OPP availability changes. 81 * @srcu_head: notifier head to notify the OPP availability changes.
82 * @rcu_head: RCU callback head used for deferred freeing
82 * @opp_list: list of opps 83 * @opp_list: list of opps
83 * 84 *
84 * This is an internal data structure maintaining the link to opps attached to 85 * This is an internal data structure maintaining the link to opps attached to
@@ -90,6 +91,7 @@ struct device_opp {
90 91
91 struct device *dev; 92 struct device *dev;
92 struct srcu_notifier_head srcu_head; 93 struct srcu_notifier_head srcu_head;
94 struct rcu_head rcu_head;
93 struct list_head opp_list; 95 struct list_head opp_list;
94}; 96};
95 97
@@ -498,6 +500,76 @@ int dev_pm_opp_add(struct device *dev, unsigned long freq, unsigned long u_volt)
498} 500}
499EXPORT_SYMBOL_GPL(dev_pm_opp_add); 501EXPORT_SYMBOL_GPL(dev_pm_opp_add);
500 502
503static void kfree_opp_rcu(struct rcu_head *head)
504{
505 struct dev_pm_opp *opp = container_of(head, struct dev_pm_opp, rcu_head);
506
507 kfree_rcu(opp, rcu_head);
508}
509
510static void kfree_device_rcu(struct rcu_head *head)
511{
512 struct device_opp *device_opp = container_of(head, struct device_opp, rcu_head);
513
514 kfree(device_opp);
515}
516
517void __dev_pm_opp_remove(struct device_opp *dev_opp, struct dev_pm_opp *opp)
518{
519 /*
520 * Notify the changes in the availability of the operable
521 * frequency/voltage list.
522 */
523 srcu_notifier_call_chain(&dev_opp->srcu_head, OPP_EVENT_REMOVE, opp);
524 list_del_rcu(&opp->node);
525 call_srcu(&dev_opp->srcu_head.srcu, &opp->rcu_head, kfree_opp_rcu);
526
527 if (list_empty(&dev_opp->opp_list)) {
528 list_del_rcu(&dev_opp->node);
529 call_srcu(&dev_opp->srcu_head.srcu, &dev_opp->rcu_head,
530 kfree_device_rcu);
531 }
532}
533
534/**
535 * dev_pm_opp_remove() - Remove an OPP from OPP list
536 * @dev: device for which we do this operation
537 * @freq: OPP to remove with matching 'freq'
538 *
539 * This function removes an opp from the opp list.
540 */
541void dev_pm_opp_remove(struct device *dev, unsigned long freq)
542{
543 struct dev_pm_opp *opp;
544 struct device_opp *dev_opp;
545 bool found = false;
546
547 /* Hold our list modification lock here */
548 mutex_lock(&dev_opp_list_lock);
549
550 dev_opp = find_device_opp(dev);
551 if (IS_ERR(dev_opp))
552 goto unlock;
553
554 list_for_each_entry(opp, &dev_opp->opp_list, node) {
555 if (opp->rate == freq) {
556 found = true;
557 break;
558 }
559 }
560
561 if (!found) {
562 dev_warn(dev, "%s: Couldn't find OPP with freq: %lu\n",
563 __func__, freq);
564 goto unlock;
565 }
566
567 __dev_pm_opp_remove(dev_opp, opp);
568unlock:
569 mutex_unlock(&dev_opp_list_lock);
570}
571EXPORT_SYMBOL_GPL(dev_pm_opp_remove);
572
501/** 573/**
502 * opp_set_availability() - helper to set the availability of an opp 574 * opp_set_availability() - helper to set the availability of an opp
503 * @dev: device for which we do this operation 575 * @dev: device for which we do this operation
@@ -687,4 +759,34 @@ int of_init_opp_table(struct device *dev)
687 return 0; 759 return 0;
688} 760}
689EXPORT_SYMBOL_GPL(of_init_opp_table); 761EXPORT_SYMBOL_GPL(of_init_opp_table);
762
763/**
764 * of_free_opp_table() - Free OPP table entries created from static DT entries
765 * @dev: device pointer used to lookup device OPPs.
766 *
767 * Free OPPs created using static entries present in DT.
768 */
769void of_free_opp_table(struct device *dev)
770{
771 struct device_opp *dev_opp = find_device_opp(dev);
772 struct dev_pm_opp *opp, *tmp;
773
774 /* Check for existing list for 'dev' */
775 dev_opp = find_device_opp(dev);
776 if (WARN(IS_ERR(dev_opp), "%s: dev_opp: %ld\n", dev_name(dev),
777 PTR_ERR(dev_opp)))
778 return;
779
780 /* Hold our list modification lock here */
781 mutex_lock(&dev_opp_list_lock);
782
783 /* Free static OPPs */
784 list_for_each_entry_safe(opp, tmp, &dev_opp->opp_list, node) {
785 if (!opp->dynamic)
786 __dev_pm_opp_remove(dev_opp, opp);
787 }
788
789 mutex_unlock(&dev_opp_list_lock);
790}
791EXPORT_SYMBOL_GPL(of_free_opp_table);
690#endif 792#endif