diff options
author | Rafael J. Wysocki <rafael.j.wysocki@intel.com> | 2014-11-30 20:46:54 -0500 |
---|---|---|
committer | Rafael J. Wysocki <rafael.j.wysocki@intel.com> | 2014-11-30 20:46:54 -0500 |
commit | 2af3411f0e559914b8c6e289ed287e166fd502d7 (patch) | |
tree | 8507df49d028045023e11ef62614fc77ae293b31 | |
parent | 8a497cfdc03412c36ba1689f0e68529d179f4e03 (diff) | |
parent | b4037aaa584bd914bbf578f5ceb2d9884fa7ddb6 (diff) |
Merge branch 'pm-opp' into pm-cpufreq
-rw-r--r-- | drivers/base/power/opp.c | 196 | ||||
-rw-r--r-- | include/linux/pm_opp.h | 12 |
2 files changed, 166 insertions, 42 deletions
diff --git a/drivers/base/power/opp.c b/drivers/base/power/opp.c index 89ced955fafa..2d195f3a1998 100644 --- a/drivers/base/power/opp.c +++ b/drivers/base/power/opp.c | |||
@@ -49,11 +49,12 @@ | |||
49 | * are protected by the dev_opp_list_lock for integrity. | 49 | * are protected by the dev_opp_list_lock for integrity. |
50 | * IMPORTANT: the opp nodes should be maintained in increasing | 50 | * IMPORTANT: the opp nodes should be maintained in increasing |
51 | * order. | 51 | * order. |
52 | * @dynamic: not-created from static DT entries. | ||
52 | * @available: true/false - marks if this OPP as available or not | 53 | * @available: true/false - marks if this OPP as available or not |
53 | * @rate: Frequency in hertz | 54 | * @rate: Frequency in hertz |
54 | * @u_volt: Nominal voltage in microvolts corresponding to this OPP | 55 | * @u_volt: Nominal voltage in microvolts corresponding to this OPP |
55 | * @dev_opp: points back to the device_opp struct this opp belongs to | 56 | * @dev_opp: points back to the device_opp struct this opp belongs to |
56 | * @head: RCU callback head used for deferred freeing | 57 | * @rcu_head: RCU callback head used for deferred freeing |
57 | * | 58 | * |
58 | * This structure stores the OPP information for a given device. | 59 | * This structure stores the OPP information for a given device. |
59 | */ | 60 | */ |
@@ -61,11 +62,12 @@ struct dev_pm_opp { | |||
61 | struct list_head node; | 62 | struct list_head node; |
62 | 63 | ||
63 | bool available; | 64 | bool available; |
65 | bool dynamic; | ||
64 | unsigned long rate; | 66 | unsigned long rate; |
65 | unsigned long u_volt; | 67 | unsigned long u_volt; |
66 | 68 | ||
67 | struct device_opp *dev_opp; | 69 | struct device_opp *dev_opp; |
68 | struct rcu_head head; | 70 | struct rcu_head rcu_head; |
69 | }; | 71 | }; |
70 | 72 | ||
71 | /** | 73 | /** |
@@ -76,7 +78,8 @@ struct dev_pm_opp { | |||
76 | * RCU usage: nodes are not modified in the list of device_opp, | 78 | * RCU usage: nodes are not modified in the list of device_opp, |
77 | * 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 |
78 | * @dev: device pointer | 80 | * @dev: device pointer |
79 | * @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 | ||
80 | * @opp_list: list of opps | 83 | * @opp_list: list of opps |
81 | * | 84 | * |
82 | * 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 |
@@ -87,7 +90,8 @@ struct device_opp { | |||
87 | struct list_head node; | 90 | struct list_head node; |
88 | 91 | ||
89 | struct device *dev; | 92 | struct device *dev; |
90 | struct srcu_notifier_head head; | 93 | struct srcu_notifier_head srcu_head; |
94 | struct rcu_head rcu_head; | ||
91 | struct list_head opp_list; | 95 | struct list_head opp_list; |
92 | }; | 96 | }; |
93 | 97 | ||
@@ -378,30 +382,8 @@ struct dev_pm_opp *dev_pm_opp_find_freq_floor(struct device *dev, | |||
378 | } | 382 | } |
379 | EXPORT_SYMBOL_GPL(dev_pm_opp_find_freq_floor); | 383 | EXPORT_SYMBOL_GPL(dev_pm_opp_find_freq_floor); |
380 | 384 | ||
381 | /** | 385 | static int dev_pm_opp_add_dynamic(struct device *dev, unsigned long freq, |
382 | * dev_pm_opp_add() - Add an OPP table from a table definitions | 386 | unsigned long u_volt, bool dynamic) |
383 | * @dev: device for which we do this operation | ||
384 | * @freq: Frequency in Hz for this OPP | ||
385 | * @u_volt: Voltage in uVolts for this OPP | ||
386 | * | ||
387 | * This function adds an opp definition to the opp list and returns status. | ||
388 | * The opp is made available by default and it can be controlled using | ||
389 | * dev_pm_opp_enable/disable functions. | ||
390 | * | ||
391 | * Locking: The internal device_opp and opp structures are RCU protected. | ||
392 | * Hence this function internally uses RCU updater strategy with mutex locks | ||
393 | * to keep the integrity of the internal data structures. Callers should ensure | ||
394 | * that this function is *NOT* called under RCU protection or in contexts where | ||
395 | * mutex cannot be locked. | ||
396 | * | ||
397 | * Return: | ||
398 | * 0: On success OR | ||
399 | * Duplicate OPPs (both freq and volt are same) and opp->available | ||
400 | * -EEXIST: Freq are same and volt are different OR | ||
401 | * Duplicate OPPs (both freq and volt are same) and !opp->available | ||
402 | * -ENOMEM: Memory allocation failure | ||
403 | */ | ||
404 | int dev_pm_opp_add(struct device *dev, unsigned long freq, unsigned long u_volt) | ||
405 | { | 387 | { |
406 | struct device_opp *dev_opp = NULL; | 388 | struct device_opp *dev_opp = NULL; |
407 | struct dev_pm_opp *opp, *new_opp; | 389 | 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) | |||
417 | /* Hold our list modification lock here */ | 399 | /* Hold our list modification lock here */ |
418 | mutex_lock(&dev_opp_list_lock); | 400 | mutex_lock(&dev_opp_list_lock); |
419 | 401 | ||
402 | /* populate the opp table */ | ||
403 | new_opp->dev_opp = dev_opp; | ||
404 | new_opp->rate = freq; | ||
405 | new_opp->u_volt = u_volt; | ||
406 | new_opp->available = true; | ||
407 | new_opp->dynamic = dynamic; | ||
408 | |||
420 | /* Check for existing list for 'dev' */ | 409 | /* Check for existing list for 'dev' */ |
421 | dev_opp = find_device_opp(dev); | 410 | dev_opp = find_device_opp(dev); |
422 | if (IS_ERR(dev_opp)) { | 411 | if (IS_ERR(dev_opp)) { |
@@ -436,19 +425,15 @@ int dev_pm_opp_add(struct device *dev, unsigned long freq, unsigned long u_volt) | |||
436 | } | 425 | } |
437 | 426 | ||
438 | dev_opp->dev = dev; | 427 | dev_opp->dev = dev; |
439 | srcu_init_notifier_head(&dev_opp->head); | 428 | srcu_init_notifier_head(&dev_opp->srcu_head); |
440 | INIT_LIST_HEAD(&dev_opp->opp_list); | 429 | INIT_LIST_HEAD(&dev_opp->opp_list); |
441 | 430 | ||
442 | /* Secure the device list modification */ | 431 | /* Secure the device list modification */ |
443 | list_add_rcu(&dev_opp->node, &dev_opp_list); | 432 | list_add_rcu(&dev_opp->node, &dev_opp_list); |
433 | head = &dev_opp->opp_list; | ||
434 | goto list_add; | ||
444 | } | 435 | } |
445 | 436 | ||
446 | /* populate the opp table */ | ||
447 | new_opp->dev_opp = dev_opp; | ||
448 | new_opp->rate = freq; | ||
449 | new_opp->u_volt = u_volt; | ||
450 | new_opp->available = true; | ||
451 | |||
452 | /* | 437 | /* |
453 | * Insert new OPP in order of increasing frequency | 438 | * Insert new OPP in order of increasing frequency |
454 | * and discard if already present | 439 | * and discard if already present |
@@ -474,6 +459,7 @@ int dev_pm_opp_add(struct device *dev, unsigned long freq, unsigned long u_volt) | |||
474 | return ret; | 459 | return ret; |
475 | } | 460 | } |
476 | 461 | ||
462 | list_add: | ||
477 | list_add_rcu(&new_opp->node, head); | 463 | list_add_rcu(&new_opp->node, head); |
478 | mutex_unlock(&dev_opp_list_lock); | 464 | mutex_unlock(&dev_opp_list_lock); |
479 | 465 | ||
@@ -481,11 +467,109 @@ int dev_pm_opp_add(struct device *dev, unsigned long freq, unsigned long u_volt) | |||
481 | * Notify the changes in the availability of the operable | 467 | * Notify the changes in the availability of the operable |
482 | * frequency/voltage list. | 468 | * frequency/voltage list. |
483 | */ | 469 | */ |
484 | srcu_notifier_call_chain(&dev_opp->head, OPP_EVENT_ADD, new_opp); | 470 | srcu_notifier_call_chain(&dev_opp->srcu_head, OPP_EVENT_ADD, new_opp); |
485 | return 0; | 471 | return 0; |
486 | } | 472 | } |
473 | |||
474 | /** | ||
475 | * dev_pm_opp_add() - Add an OPP table from a table definitions | ||
476 | * @dev: device for which we do this operation | ||
477 | * @freq: Frequency in Hz for this OPP | ||
478 | * @u_volt: Voltage in uVolts for this OPP | ||
479 | * | ||
480 | * This function adds an opp definition to the opp list and returns status. | ||
481 | * The opp is made available by default and it can be controlled using | ||
482 | * dev_pm_opp_enable/disable functions. | ||
483 | * | ||
484 | * Locking: The internal device_opp and opp structures are RCU protected. | ||
485 | * Hence this function internally uses RCU updater strategy with mutex locks | ||
486 | * to keep the integrity of the internal data structures. Callers should ensure | ||
487 | * that this function is *NOT* called under RCU protection or in contexts where | ||
488 | * mutex cannot be locked. | ||
489 | * | ||
490 | * Return: | ||
491 | * 0: On success OR | ||
492 | * Duplicate OPPs (both freq and volt are same) and opp->available | ||
493 | * -EEXIST: Freq are same and volt are different OR | ||
494 | * Duplicate OPPs (both freq and volt are same) and !opp->available | ||
495 | * -ENOMEM: Memory allocation failure | ||
496 | */ | ||
497 | int dev_pm_opp_add(struct device *dev, unsigned long freq, unsigned long u_volt) | ||
498 | { | ||
499 | return dev_pm_opp_add_dynamic(dev, freq, u_volt, true); | ||
500 | } | ||
487 | EXPORT_SYMBOL_GPL(dev_pm_opp_add); | 501 | EXPORT_SYMBOL_GPL(dev_pm_opp_add); |
488 | 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 | |||
489 | /** | 573 | /** |
490 | * opp_set_availability() - helper to set the availability of an opp | 574 | * opp_set_availability() - helper to set the availability of an opp |
491 | * @dev: device for which we do this operation | 575 | * @dev: device for which we do this operation |
@@ -557,14 +641,14 @@ static int opp_set_availability(struct device *dev, unsigned long freq, | |||
557 | 641 | ||
558 | list_replace_rcu(&opp->node, &new_opp->node); | 642 | list_replace_rcu(&opp->node, &new_opp->node); |
559 | mutex_unlock(&dev_opp_list_lock); | 643 | mutex_unlock(&dev_opp_list_lock); |
560 | kfree_rcu(opp, head); | 644 | call_srcu(&dev_opp->srcu_head.srcu, &opp->rcu_head, kfree_opp_rcu); |
561 | 645 | ||
562 | /* Notify the change of the OPP availability */ | 646 | /* Notify the change of the OPP availability */ |
563 | if (availability_req) | 647 | if (availability_req) |
564 | srcu_notifier_call_chain(&dev_opp->head, OPP_EVENT_ENABLE, | 648 | srcu_notifier_call_chain(&dev_opp->srcu_head, OPP_EVENT_ENABLE, |
565 | new_opp); | 649 | new_opp); |
566 | else | 650 | else |
567 | srcu_notifier_call_chain(&dev_opp->head, OPP_EVENT_DISABLE, | 651 | srcu_notifier_call_chain(&dev_opp->srcu_head, OPP_EVENT_DISABLE, |
568 | new_opp); | 652 | new_opp); |
569 | 653 | ||
570 | return 0; | 654 | return 0; |
@@ -629,7 +713,7 @@ struct srcu_notifier_head *dev_pm_opp_get_notifier(struct device *dev) | |||
629 | if (IS_ERR(dev_opp)) | 713 | if (IS_ERR(dev_opp)) |
630 | return ERR_CAST(dev_opp); /* matching type */ | 714 | return ERR_CAST(dev_opp); /* matching type */ |
631 | 715 | ||
632 | return &dev_opp->head; | 716 | return &dev_opp->srcu_head; |
633 | } | 717 | } |
634 | 718 | ||
635 | #ifdef CONFIG_OF | 719 | #ifdef CONFIG_OF |
@@ -666,7 +750,7 @@ int of_init_opp_table(struct device *dev) | |||
666 | unsigned long freq = be32_to_cpup(val++) * 1000; | 750 | unsigned long freq = be32_to_cpup(val++) * 1000; |
667 | unsigned long volt = be32_to_cpup(val++); | 751 | unsigned long volt = be32_to_cpup(val++); |
668 | 752 | ||
669 | if (dev_pm_opp_add(dev, freq, volt)) | 753 | if (dev_pm_opp_add_dynamic(dev, freq, volt, false)) |
670 | dev_warn(dev, "%s: Failed to add OPP %ld\n", | 754 | dev_warn(dev, "%s: Failed to add OPP %ld\n", |
671 | __func__, freq); | 755 | __func__, freq); |
672 | nr -= 2; | 756 | nr -= 2; |
@@ -675,4 +759,34 @@ int of_init_opp_table(struct device *dev) | |||
675 | return 0; | 759 | return 0; |
676 | } | 760 | } |
677 | 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); | ||
678 | #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__ */ |