diff options
| -rw-r--r-- | drivers/base/power/opp.c | 102 | ||||
| -rw-r--r-- | include/linux/pm_opp.h | 12 |
2 files changed, 113 insertions, 1 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 | } |
| 499 | EXPORT_SYMBOL_GPL(dev_pm_opp_add); | 501 | EXPORT_SYMBOL_GPL(dev_pm_opp_add); |
| 500 | 502 | ||
| 503 | static 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 | |||
| 510 | static 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 | |||
| 517 | void __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 | */ | ||
| 541 | void 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); | ||
| 568 | unlock: | ||
| 569 | mutex_unlock(&dev_opp_list_lock); | ||
| 570 | } | ||
| 571 | EXPORT_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 | } |
| 689 | EXPORT_SYMBOL_GPL(of_init_opp_table); | 761 | EXPORT_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 | */ | ||
| 769 | void 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 | } | ||
| 791 | EXPORT_SYMBOL_GPL(of_free_opp_table); | ||
| 690 | #endif | 792 | #endif |
diff --git a/include/linux/pm_opp.h b/include/linux/pm_opp.h index 0330217abfad..cec2d4540914 100644 --- a/include/linux/pm_opp.h +++ b/include/linux/pm_opp.h | |||
| @@ -21,7 +21,7 @@ struct dev_pm_opp; | |||
| 21 | struct device; | 21 | struct device; |
| 22 | 22 | ||
| 23 | enum dev_pm_opp_event { | 23 | enum dev_pm_opp_event { |
| 24 | OPP_EVENT_ADD, OPP_EVENT_ENABLE, OPP_EVENT_DISABLE, | 24 | OPP_EVENT_ADD, OPP_EVENT_REMOVE, OPP_EVENT_ENABLE, OPP_EVENT_DISABLE, |
| 25 | }; | 25 | }; |
| 26 | 26 | ||
| 27 | #if defined(CONFIG_PM_OPP) | 27 | #if defined(CONFIG_PM_OPP) |
| @@ -44,6 +44,7 @@ struct dev_pm_opp *dev_pm_opp_find_freq_ceil(struct device *dev, | |||
| 44 | 44 | ||
| 45 | int dev_pm_opp_add(struct device *dev, unsigned long freq, | 45 | int dev_pm_opp_add(struct device *dev, unsigned long freq, |
| 46 | unsigned long u_volt); | 46 | unsigned long u_volt); |
| 47 | void dev_pm_opp_remove(struct device *dev, unsigned long freq); | ||
| 47 | 48 | ||
| 48 | int dev_pm_opp_enable(struct device *dev, unsigned long freq); | 49 | int dev_pm_opp_enable(struct device *dev, unsigned long freq); |
| 49 | 50 | ||
| @@ -90,6 +91,10 @@ static inline int dev_pm_opp_add(struct device *dev, unsigned long freq, | |||
| 90 | return -EINVAL; | 91 | return -EINVAL; |
| 91 | } | 92 | } |
| 92 | 93 | ||
| 94 | static inline void dev_pm_opp_remove(struct device *dev, unsigned long freq) | ||
| 95 | { | ||
| 96 | } | ||
| 97 | |||
| 93 | static inline int dev_pm_opp_enable(struct device *dev, unsigned long freq) | 98 | static inline int dev_pm_opp_enable(struct device *dev, unsigned long freq) |
| 94 | { | 99 | { |
| 95 | return 0; | 100 | return 0; |
| @@ -109,11 +114,16 @@ static inline struct srcu_notifier_head *dev_pm_opp_get_notifier( | |||
| 109 | 114 | ||
| 110 | #if defined(CONFIG_PM_OPP) && defined(CONFIG_OF) | 115 | #if defined(CONFIG_PM_OPP) && defined(CONFIG_OF) |
| 111 | int of_init_opp_table(struct device *dev); | 116 | int of_init_opp_table(struct device *dev); |
| 117 | void of_free_opp_table(struct device *dev); | ||
| 112 | #else | 118 | #else |
| 113 | static inline int of_init_opp_table(struct device *dev) | 119 | static inline int of_init_opp_table(struct device *dev) |
| 114 | { | 120 | { |
| 115 | return -EINVAL; | 121 | return -EINVAL; |
| 116 | } | 122 | } |
| 123 | |||
| 124 | static inline void of_free_opp_table(struct device *dev) | ||
| 125 | { | ||
| 126 | } | ||
| 117 | #endif | 127 | #endif |
| 118 | 128 | ||
| 119 | #endif /* __LINUX_OPP_H__ */ | 129 | #endif /* __LINUX_OPP_H__ */ |
