diff options
Diffstat (limited to 'drivers/pci/pcie/aspm.c')
-rw-r--r-- | drivers/pci/pcie/aspm.c | 165 |
1 files changed, 124 insertions, 41 deletions
diff --git a/drivers/pci/pcie/aspm.c b/drivers/pci/pcie/aspm.c index 9aad608bcf3f..586b6f75910d 100644 --- a/drivers/pci/pcie/aspm.c +++ b/drivers/pci/pcie/aspm.c | |||
@@ -17,6 +17,7 @@ | |||
17 | #include <linux/init.h> | 17 | #include <linux/init.h> |
18 | #include <linux/slab.h> | 18 | #include <linux/slab.h> |
19 | #include <linux/jiffies.h> | 19 | #include <linux/jiffies.h> |
20 | #include <linux/delay.h> | ||
20 | #include <linux/pci-aspm.h> | 21 | #include <linux/pci-aspm.h> |
21 | #include "../pci.h" | 22 | #include "../pci.h" |
22 | 23 | ||
@@ -33,6 +34,11 @@ struct endpoint_state { | |||
33 | struct pcie_link_state { | 34 | struct pcie_link_state { |
34 | struct list_head sibiling; | 35 | struct list_head sibiling; |
35 | struct pci_dev *pdev; | 36 | struct pci_dev *pdev; |
37 | bool downstream_has_switch; | ||
38 | |||
39 | struct pcie_link_state *parent; | ||
40 | struct list_head children; | ||
41 | struct list_head link; | ||
36 | 42 | ||
37 | /* ASPM state */ | 43 | /* ASPM state */ |
38 | unsigned int support_state; | 44 | unsigned int support_state; |
@@ -70,6 +76,8 @@ static const char *policy_str[] = { | |||
70 | [POLICY_POWERSAVE] = "powersave" | 76 | [POLICY_POWERSAVE] = "powersave" |
71 | }; | 77 | }; |
72 | 78 | ||
79 | #define LINK_RETRAIN_TIMEOUT HZ | ||
80 | |||
73 | static int policy_to_aspm_state(struct pci_dev *pdev) | 81 | static int policy_to_aspm_state(struct pci_dev *pdev) |
74 | { | 82 | { |
75 | struct pcie_link_state *link_state = pdev->link_state; | 83 | struct pcie_link_state *link_state = pdev->link_state; |
@@ -125,7 +133,7 @@ static void pcie_set_clock_pm(struct pci_dev *pdev, int enable) | |||
125 | link_state->clk_pm_enabled = !!enable; | 133 | link_state->clk_pm_enabled = !!enable; |
126 | } | 134 | } |
127 | 135 | ||
128 | static void pcie_check_clock_pm(struct pci_dev *pdev) | 136 | static void pcie_check_clock_pm(struct pci_dev *pdev, int blacklist) |
129 | { | 137 | { |
130 | int pos; | 138 | int pos; |
131 | u32 reg32; | 139 | u32 reg32; |
@@ -149,10 +157,26 @@ static void pcie_check_clock_pm(struct pci_dev *pdev) | |||
149 | if (!(reg16 & PCI_EXP_LNKCTL_CLKREQ_EN)) | 157 | if (!(reg16 & PCI_EXP_LNKCTL_CLKREQ_EN)) |
150 | enabled = 0; | 158 | enabled = 0; |
151 | } | 159 | } |
152 | link_state->clk_pm_capable = capable; | ||
153 | link_state->clk_pm_enabled = enabled; | 160 | link_state->clk_pm_enabled = enabled; |
154 | link_state->bios_clk_state = enabled; | 161 | link_state->bios_clk_state = enabled; |
155 | pcie_set_clock_pm(pdev, policy_to_clkpm_state(pdev)); | 162 | if (!blacklist) { |
163 | link_state->clk_pm_capable = capable; | ||
164 | pcie_set_clock_pm(pdev, policy_to_clkpm_state(pdev)); | ||
165 | } else { | ||
166 | link_state->clk_pm_capable = 0; | ||
167 | pcie_set_clock_pm(pdev, 0); | ||
168 | } | ||
169 | } | ||
170 | |||
171 | static bool pcie_aspm_downstream_has_switch(struct pci_dev *pdev) | ||
172 | { | ||
173 | struct pci_dev *child_dev; | ||
174 | |||
175 | list_for_each_entry(child_dev, &pdev->subordinate->devices, bus_list) { | ||
176 | if (child_dev->pcie_type == PCI_EXP_TYPE_UPSTREAM) | ||
177 | return true; | ||
178 | } | ||
179 | return false; | ||
156 | } | 180 | } |
157 | 181 | ||
158 | /* | 182 | /* |
@@ -217,16 +241,18 @@ static void pcie_aspm_configure_common_clock(struct pci_dev *pdev) | |||
217 | pci_write_config_word(pdev, pos + PCI_EXP_LNKCTL, reg16); | 241 | pci_write_config_word(pdev, pos + PCI_EXP_LNKCTL, reg16); |
218 | 242 | ||
219 | /* Wait for link training end */ | 243 | /* Wait for link training end */ |
220 | /* break out after waiting for 1 second */ | 244 | /* break out after waiting for timeout */ |
221 | start_jiffies = jiffies; | 245 | start_jiffies = jiffies; |
222 | while ((jiffies - start_jiffies) < HZ) { | 246 | for (;;) { |
223 | pci_read_config_word(pdev, pos + PCI_EXP_LNKSTA, ®16); | 247 | pci_read_config_word(pdev, pos + PCI_EXP_LNKSTA, ®16); |
224 | if (!(reg16 & PCI_EXP_LNKSTA_LT)) | 248 | if (!(reg16 & PCI_EXP_LNKSTA_LT)) |
225 | break; | 249 | break; |
226 | cpu_relax(); | 250 | if (time_after(jiffies, start_jiffies + LINK_RETRAIN_TIMEOUT)) |
251 | break; | ||
252 | msleep(1); | ||
227 | } | 253 | } |
228 | /* training failed -> recover */ | 254 | /* training failed -> recover */ |
229 | if ((jiffies - start_jiffies) >= HZ) { | 255 | if (reg16 & PCI_EXP_LNKSTA_LT) { |
230 | dev_printk (KERN_ERR, &pdev->dev, "ASPM: Could not configure" | 256 | dev_printk (KERN_ERR, &pdev->dev, "ASPM: Could not configure" |
231 | " common clock\n"); | 257 | " common clock\n"); |
232 | i = 0; | 258 | i = 0; |
@@ -419,9 +445,9 @@ static unsigned int pcie_aspm_check_state(struct pci_dev *pdev, | |||
419 | { | 445 | { |
420 | struct pci_dev *child_dev; | 446 | struct pci_dev *child_dev; |
421 | 447 | ||
422 | /* If no child, disable the link */ | 448 | /* If no child, ignore the link */ |
423 | if (list_empty(&pdev->subordinate->devices)) | 449 | if (list_empty(&pdev->subordinate->devices)) |
424 | return 0; | 450 | return state; |
425 | list_for_each_entry(child_dev, &pdev->subordinate->devices, bus_list) { | 451 | list_for_each_entry(child_dev, &pdev->subordinate->devices, bus_list) { |
426 | if (child_dev->pcie_type == PCI_EXP_TYPE_PCI_BRIDGE) { | 452 | if (child_dev->pcie_type == PCI_EXP_TYPE_PCI_BRIDGE) { |
427 | /* | 453 | /* |
@@ -462,6 +488,9 @@ static void __pcie_aspm_config_link(struct pci_dev *pdev, unsigned int state) | |||
462 | int valid = 1; | 488 | int valid = 1; |
463 | struct pcie_link_state *link_state = pdev->link_state; | 489 | struct pcie_link_state *link_state = pdev->link_state; |
464 | 490 | ||
491 | /* If no child, disable the link */ | ||
492 | if (list_empty(&pdev->subordinate->devices)) | ||
493 | state = 0; | ||
465 | /* | 494 | /* |
466 | * if the downstream component has pci bridge function, don't do ASPM | 495 | * if the downstream component has pci bridge function, don't do ASPM |
467 | * now | 496 | * now |
@@ -493,20 +522,52 @@ static void __pcie_aspm_config_link(struct pci_dev *pdev, unsigned int state) | |||
493 | link_state->enabled_state = state; | 522 | link_state->enabled_state = state; |
494 | } | 523 | } |
495 | 524 | ||
525 | static struct pcie_link_state *get_root_port_link(struct pcie_link_state *link) | ||
526 | { | ||
527 | struct pcie_link_state *root_port_link = link; | ||
528 | while (root_port_link->parent) | ||
529 | root_port_link = root_port_link->parent; | ||
530 | return root_port_link; | ||
531 | } | ||
532 | |||
533 | /* check the whole hierarchy, and configure each link in the hierarchy */ | ||
496 | static void __pcie_aspm_configure_link_state(struct pci_dev *pdev, | 534 | static void __pcie_aspm_configure_link_state(struct pci_dev *pdev, |
497 | unsigned int state) | 535 | unsigned int state) |
498 | { | 536 | { |
499 | struct pcie_link_state *link_state = pdev->link_state; | 537 | struct pcie_link_state *link_state = pdev->link_state; |
538 | struct pcie_link_state *root_port_link = get_root_port_link(link_state); | ||
539 | struct pcie_link_state *leaf; | ||
500 | 540 | ||
501 | if (link_state->support_state == 0) | ||
502 | return; | ||
503 | state &= PCIE_LINK_STATE_L0S|PCIE_LINK_STATE_L1; | 541 | state &= PCIE_LINK_STATE_L0S|PCIE_LINK_STATE_L1; |
504 | 542 | ||
505 | /* state 0 means disabling aspm */ | 543 | /* check all links who have specific root port link */ |
506 | state = pcie_aspm_check_state(pdev, state); | 544 | list_for_each_entry(leaf, &link_list, sibiling) { |
545 | if (!list_empty(&leaf->children) || | ||
546 | get_root_port_link(leaf) != root_port_link) | ||
547 | continue; | ||
548 | state = pcie_aspm_check_state(leaf->pdev, state); | ||
549 | } | ||
550 | /* check root port link too in case it hasn't children */ | ||
551 | state = pcie_aspm_check_state(root_port_link->pdev, state); | ||
552 | |||
507 | if (link_state->enabled_state == state) | 553 | if (link_state->enabled_state == state) |
508 | return; | 554 | return; |
509 | __pcie_aspm_config_link(pdev, state); | 555 | |
556 | /* | ||
557 | * we must change the hierarchy. See comments in | ||
558 | * __pcie_aspm_config_link for the order | ||
559 | **/ | ||
560 | if (state & PCIE_LINK_STATE_L1) { | ||
561 | list_for_each_entry(leaf, &link_list, sibiling) { | ||
562 | if (get_root_port_link(leaf) == root_port_link) | ||
563 | __pcie_aspm_config_link(leaf->pdev, state); | ||
564 | } | ||
565 | } else { | ||
566 | list_for_each_entry_reverse(leaf, &link_list, sibiling) { | ||
567 | if (get_root_port_link(leaf) == root_port_link) | ||
568 | __pcie_aspm_config_link(leaf->pdev, state); | ||
569 | } | ||
570 | } | ||
510 | } | 571 | } |
511 | 572 | ||
512 | /* | 573 | /* |
@@ -570,6 +631,7 @@ void pcie_aspm_init_link_state(struct pci_dev *pdev) | |||
570 | unsigned int state; | 631 | unsigned int state; |
571 | struct pcie_link_state *link_state; | 632 | struct pcie_link_state *link_state; |
572 | int error = 0; | 633 | int error = 0; |
634 | int blacklist; | ||
573 | 635 | ||
574 | if (aspm_disabled || !pdev->is_pcie || pdev->link_state) | 636 | if (aspm_disabled || !pdev->is_pcie || pdev->link_state) |
575 | return; | 637 | return; |
@@ -580,29 +642,58 @@ void pcie_aspm_init_link_state(struct pci_dev *pdev) | |||
580 | if (list_empty(&pdev->subordinate->devices)) | 642 | if (list_empty(&pdev->subordinate->devices)) |
581 | goto out; | 643 | goto out; |
582 | 644 | ||
583 | if (pcie_aspm_sanity_check(pdev)) | 645 | blacklist = !!pcie_aspm_sanity_check(pdev); |
584 | goto out; | ||
585 | 646 | ||
586 | mutex_lock(&aspm_lock); | 647 | mutex_lock(&aspm_lock); |
587 | 648 | ||
588 | link_state = kzalloc(sizeof(*link_state), GFP_KERNEL); | 649 | link_state = kzalloc(sizeof(*link_state), GFP_KERNEL); |
589 | if (!link_state) | 650 | if (!link_state) |
590 | goto unlock_out; | 651 | goto unlock_out; |
591 | pdev->link_state = link_state; | ||
592 | 652 | ||
593 | pcie_aspm_configure_common_clock(pdev); | 653 | link_state->downstream_has_switch = pcie_aspm_downstream_has_switch(pdev); |
654 | INIT_LIST_HEAD(&link_state->children); | ||
655 | INIT_LIST_HEAD(&link_state->link); | ||
656 | if (pdev->bus->self) {/* this is a switch */ | ||
657 | struct pcie_link_state *parent_link_state; | ||
594 | 658 | ||
595 | pcie_aspm_cap_init(pdev); | 659 | parent_link_state = pdev->bus->parent->self->link_state; |
660 | if (!parent_link_state) { | ||
661 | kfree(link_state); | ||
662 | goto unlock_out; | ||
663 | } | ||
664 | list_add(&link_state->link, &parent_link_state->children); | ||
665 | link_state->parent = parent_link_state; | ||
666 | } | ||
596 | 667 | ||
597 | /* config link state to avoid BIOS error */ | 668 | pdev->link_state = link_state; |
598 | state = pcie_aspm_check_state(pdev, policy_to_aspm_state(pdev)); | ||
599 | __pcie_aspm_config_link(pdev, state); | ||
600 | 669 | ||
601 | pcie_check_clock_pm(pdev); | 670 | if (!blacklist) { |
671 | pcie_aspm_configure_common_clock(pdev); | ||
672 | pcie_aspm_cap_init(pdev); | ||
673 | } else { | ||
674 | link_state->enabled_state = PCIE_LINK_STATE_L0S|PCIE_LINK_STATE_L1; | ||
675 | link_state->bios_aspm_state = 0; | ||
676 | /* Set support state to 0, so we will disable ASPM later */ | ||
677 | link_state->support_state = 0; | ||
678 | } | ||
602 | 679 | ||
603 | link_state->pdev = pdev; | 680 | link_state->pdev = pdev; |
604 | list_add(&link_state->sibiling, &link_list); | 681 | list_add(&link_state->sibiling, &link_list); |
605 | 682 | ||
683 | if (link_state->downstream_has_switch) { | ||
684 | /* | ||
685 | * If link has switch, delay the link config. The leaf link | ||
686 | * initialization will config the whole hierarchy. but we must | ||
687 | * make sure BIOS doesn't set unsupported link state | ||
688 | **/ | ||
689 | state = pcie_aspm_check_state(pdev, link_state->bios_aspm_state); | ||
690 | __pcie_aspm_config_link(pdev, state); | ||
691 | } else | ||
692 | __pcie_aspm_configure_link_state(pdev, | ||
693 | policy_to_aspm_state(pdev)); | ||
694 | |||
695 | pcie_check_clock_pm(pdev, blacklist); | ||
696 | |||
606 | unlock_out: | 697 | unlock_out: |
607 | if (error) | 698 | if (error) |
608 | free_link_state(pdev); | 699 | free_link_state(pdev); |
@@ -635,6 +726,7 @@ void pcie_aspm_exit_link_state(struct pci_dev *pdev) | |||
635 | /* All functions are removed, so just disable ASPM for the link */ | 726 | /* All functions are removed, so just disable ASPM for the link */ |
636 | __pcie_aspm_config_one_dev(parent, 0); | 727 | __pcie_aspm_config_one_dev(parent, 0); |
637 | list_del(&link_state->sibiling); | 728 | list_del(&link_state->sibiling); |
729 | list_del(&link_state->link); | ||
638 | /* Clock PM is for endpoint device */ | 730 | /* Clock PM is for endpoint device */ |
639 | 731 | ||
640 | free_link_state(parent); | 732 | free_link_state(parent); |
@@ -857,24 +949,15 @@ void pcie_no_aspm(void) | |||
857 | aspm_disabled = 1; | 949 | aspm_disabled = 1; |
858 | } | 950 | } |
859 | 951 | ||
860 | #ifdef CONFIG_ACPI | 952 | /** |
861 | #include <acpi/acpi_bus.h> | 953 | * pcie_aspm_enabled - is PCIe ASPM enabled? |
862 | #include <linux/pci-acpi.h> | 954 | * |
863 | static void pcie_aspm_platform_init(void) | 955 | * Returns true if ASPM has not been disabled by the command-line option |
864 | { | 956 | * pcie_aspm=off. |
865 | pcie_osc_support_set(OSC_ACTIVE_STATE_PWR_SUPPORT| | 957 | **/ |
866 | OSC_CLOCK_PWR_CAPABILITY_SUPPORT); | 958 | int pcie_aspm_enabled(void) |
867 | } | ||
868 | #else | ||
869 | static inline void pcie_aspm_platform_init(void) { } | ||
870 | #endif | ||
871 | |||
872 | static int __init pcie_aspm_init(void) | ||
873 | { | 959 | { |
874 | if (aspm_disabled) | 960 | return !aspm_disabled; |
875 | return 0; | ||
876 | pcie_aspm_platform_init(); | ||
877 | return 0; | ||
878 | } | 961 | } |
962 | EXPORT_SYMBOL(pcie_aspm_enabled); | ||
879 | 963 | ||
880 | fs_initcall(pcie_aspm_init); | ||