diff options
Diffstat (limited to 'drivers/pci/pcie/aspm.c')
-rw-r--r-- | drivers/pci/pcie/aspm.c | 125 |
1 files changed, 106 insertions, 19 deletions
diff --git a/drivers/pci/pcie/aspm.c b/drivers/pci/pcie/aspm.c index e361c7dc726f..4d8e2c7b2ad1 100644 --- a/drivers/pci/pcie/aspm.c +++ b/drivers/pci/pcie/aspm.c | |||
@@ -33,6 +33,11 @@ struct endpoint_state { | |||
33 | struct pcie_link_state { | 33 | struct pcie_link_state { |
34 | struct list_head sibiling; | 34 | struct list_head sibiling; |
35 | struct pci_dev *pdev; | 35 | struct pci_dev *pdev; |
36 | bool downstream_has_switch; | ||
37 | |||
38 | struct pcie_link_state *parent; | ||
39 | struct list_head children; | ||
40 | struct list_head link; | ||
36 | 41 | ||
37 | /* ASPM state */ | 42 | /* ASPM state */ |
38 | unsigned int support_state; | 43 | unsigned int support_state; |
@@ -125,7 +130,7 @@ static void pcie_set_clock_pm(struct pci_dev *pdev, int enable) | |||
125 | link_state->clk_pm_enabled = !!enable; | 130 | link_state->clk_pm_enabled = !!enable; |
126 | } | 131 | } |
127 | 132 | ||
128 | static void pcie_check_clock_pm(struct pci_dev *pdev) | 133 | static void pcie_check_clock_pm(struct pci_dev *pdev, int blacklist) |
129 | { | 134 | { |
130 | int pos; | 135 | int pos; |
131 | u32 reg32; | 136 | u32 reg32; |
@@ -149,10 +154,26 @@ static void pcie_check_clock_pm(struct pci_dev *pdev) | |||
149 | if (!(reg16 & PCI_EXP_LNKCTL_CLKREQ_EN)) | 154 | if (!(reg16 & PCI_EXP_LNKCTL_CLKREQ_EN)) |
150 | enabled = 0; | 155 | enabled = 0; |
151 | } | 156 | } |
152 | link_state->clk_pm_capable = capable; | ||
153 | link_state->clk_pm_enabled = enabled; | 157 | link_state->clk_pm_enabled = enabled; |
154 | link_state->bios_clk_state = enabled; | 158 | link_state->bios_clk_state = enabled; |
155 | pcie_set_clock_pm(pdev, policy_to_clkpm_state(pdev)); | 159 | if (!blacklist) { |
160 | link_state->clk_pm_capable = capable; | ||
161 | pcie_set_clock_pm(pdev, policy_to_clkpm_state(pdev)); | ||
162 | } else { | ||
163 | link_state->clk_pm_capable = 0; | ||
164 | pcie_set_clock_pm(pdev, 0); | ||
165 | } | ||
166 | } | ||
167 | |||
168 | static bool pcie_aspm_downstream_has_switch(struct pci_dev *pdev) | ||
169 | { | ||
170 | struct pci_dev *child_dev; | ||
171 | |||
172 | list_for_each_entry(child_dev, &pdev->subordinate->devices, bus_list) { | ||
173 | if (child_dev->pcie_type == PCI_EXP_TYPE_UPSTREAM) | ||
174 | return true; | ||
175 | } | ||
176 | return false; | ||
156 | } | 177 | } |
157 | 178 | ||
158 | /* | 179 | /* |
@@ -419,9 +440,9 @@ static unsigned int pcie_aspm_check_state(struct pci_dev *pdev, | |||
419 | { | 440 | { |
420 | struct pci_dev *child_dev; | 441 | struct pci_dev *child_dev; |
421 | 442 | ||
422 | /* If no child, disable the link */ | 443 | /* If no child, ignore the link */ |
423 | if (list_empty(&pdev->subordinate->devices)) | 444 | if (list_empty(&pdev->subordinate->devices)) |
424 | return 0; | 445 | return state; |
425 | list_for_each_entry(child_dev, &pdev->subordinate->devices, bus_list) { | 446 | list_for_each_entry(child_dev, &pdev->subordinate->devices, bus_list) { |
426 | if (child_dev->pcie_type == PCI_EXP_TYPE_PCI_BRIDGE) { | 447 | if (child_dev->pcie_type == PCI_EXP_TYPE_PCI_BRIDGE) { |
427 | /* | 448 | /* |
@@ -462,6 +483,9 @@ static void __pcie_aspm_config_link(struct pci_dev *pdev, unsigned int state) | |||
462 | int valid = 1; | 483 | int valid = 1; |
463 | struct pcie_link_state *link_state = pdev->link_state; | 484 | struct pcie_link_state *link_state = pdev->link_state; |
464 | 485 | ||
486 | /* If no child, disable the link */ | ||
487 | if (list_empty(&pdev->subordinate->devices)) | ||
488 | state = 0; | ||
465 | /* | 489 | /* |
466 | * if the downstream component has pci bridge function, don't do ASPM | 490 | * if the downstream component has pci bridge function, don't do ASPM |
467 | * now | 491 | * now |
@@ -493,20 +517,52 @@ static void __pcie_aspm_config_link(struct pci_dev *pdev, unsigned int state) | |||
493 | link_state->enabled_state = state; | 517 | link_state->enabled_state = state; |
494 | } | 518 | } |
495 | 519 | ||
520 | static struct pcie_link_state *get_root_port_link(struct pcie_link_state *link) | ||
521 | { | ||
522 | struct pcie_link_state *root_port_link = link; | ||
523 | while (root_port_link->parent) | ||
524 | root_port_link = root_port_link->parent; | ||
525 | return root_port_link; | ||
526 | } | ||
527 | |||
528 | /* check the whole hierarchy, and configure each link in the hierarchy */ | ||
496 | static void __pcie_aspm_configure_link_state(struct pci_dev *pdev, | 529 | static void __pcie_aspm_configure_link_state(struct pci_dev *pdev, |
497 | unsigned int state) | 530 | unsigned int state) |
498 | { | 531 | { |
499 | struct pcie_link_state *link_state = pdev->link_state; | 532 | struct pcie_link_state *link_state = pdev->link_state; |
533 | struct pcie_link_state *root_port_link = get_root_port_link(link_state); | ||
534 | struct pcie_link_state *leaf; | ||
500 | 535 | ||
501 | if (link_state->support_state == 0) | ||
502 | return; | ||
503 | state &= PCIE_LINK_STATE_L0S|PCIE_LINK_STATE_L1; | 536 | state &= PCIE_LINK_STATE_L0S|PCIE_LINK_STATE_L1; |
504 | 537 | ||
505 | /* state 0 means disabling aspm */ | 538 | /* check all links who have specific root port link */ |
506 | state = pcie_aspm_check_state(pdev, state); | 539 | list_for_each_entry(leaf, &link_list, sibiling) { |
540 | if (!list_empty(&leaf->children) || | ||
541 | get_root_port_link(leaf) != root_port_link) | ||
542 | continue; | ||
543 | state = pcie_aspm_check_state(leaf->pdev, state); | ||
544 | } | ||
545 | /* check root port link too in case it hasn't children */ | ||
546 | state = pcie_aspm_check_state(root_port_link->pdev, state); | ||
547 | |||
507 | if (link_state->enabled_state == state) | 548 | if (link_state->enabled_state == state) |
508 | return; | 549 | return; |
509 | __pcie_aspm_config_link(pdev, state); | 550 | |
551 | /* | ||
552 | * we must change the hierarchy. See comments in | ||
553 | * __pcie_aspm_config_link for the order | ||
554 | **/ | ||
555 | if (state & PCIE_LINK_STATE_L1) { | ||
556 | list_for_each_entry(leaf, &link_list, sibiling) { | ||
557 | if (get_root_port_link(leaf) == root_port_link) | ||
558 | __pcie_aspm_config_link(leaf->pdev, state); | ||
559 | } | ||
560 | } else { | ||
561 | list_for_each_entry_reverse(leaf, &link_list, sibiling) { | ||
562 | if (get_root_port_link(leaf) == root_port_link) | ||
563 | __pcie_aspm_config_link(leaf->pdev, state); | ||
564 | } | ||
565 | } | ||
510 | } | 566 | } |
511 | 567 | ||
512 | /* | 568 | /* |
@@ -570,6 +626,7 @@ void pcie_aspm_init_link_state(struct pci_dev *pdev) | |||
570 | unsigned int state; | 626 | unsigned int state; |
571 | struct pcie_link_state *link_state; | 627 | struct pcie_link_state *link_state; |
572 | int error = 0; | 628 | int error = 0; |
629 | int blacklist; | ||
573 | 630 | ||
574 | if (aspm_disabled || !pdev->is_pcie || pdev->link_state) | 631 | if (aspm_disabled || !pdev->is_pcie || pdev->link_state) |
575 | return; | 632 | return; |
@@ -580,29 +637,58 @@ void pcie_aspm_init_link_state(struct pci_dev *pdev) | |||
580 | if (list_empty(&pdev->subordinate->devices)) | 637 | if (list_empty(&pdev->subordinate->devices)) |
581 | goto out; | 638 | goto out; |
582 | 639 | ||
583 | if (pcie_aspm_sanity_check(pdev)) | 640 | blacklist = !!pcie_aspm_sanity_check(pdev); |
584 | goto out; | ||
585 | 641 | ||
586 | mutex_lock(&aspm_lock); | 642 | mutex_lock(&aspm_lock); |
587 | 643 | ||
588 | link_state = kzalloc(sizeof(*link_state), GFP_KERNEL); | 644 | link_state = kzalloc(sizeof(*link_state), GFP_KERNEL); |
589 | if (!link_state) | 645 | if (!link_state) |
590 | goto unlock_out; | 646 | goto unlock_out; |
591 | pdev->link_state = link_state; | ||
592 | 647 | ||
593 | pcie_aspm_configure_common_clock(pdev); | 648 | link_state->downstream_has_switch = pcie_aspm_downstream_has_switch(pdev); |
649 | INIT_LIST_HEAD(&link_state->children); | ||
650 | INIT_LIST_HEAD(&link_state->link); | ||
651 | if (pdev->bus->self) {/* this is a switch */ | ||
652 | struct pcie_link_state *parent_link_state; | ||
594 | 653 | ||
595 | pcie_aspm_cap_init(pdev); | 654 | parent_link_state = pdev->bus->parent->self->link_state; |
655 | if (!parent_link_state) { | ||
656 | kfree(link_state); | ||
657 | goto unlock_out; | ||
658 | } | ||
659 | list_add(&link_state->link, &parent_link_state->children); | ||
660 | link_state->parent = parent_link_state; | ||
661 | } | ||
596 | 662 | ||
597 | /* config link state to avoid BIOS error */ | 663 | 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 | 664 | ||
601 | pcie_check_clock_pm(pdev); | 665 | if (!blacklist) { |
666 | pcie_aspm_configure_common_clock(pdev); | ||
667 | pcie_aspm_cap_init(pdev); | ||
668 | } else { | ||
669 | link_state->enabled_state = PCIE_LINK_STATE_L0S|PCIE_LINK_STATE_L1; | ||
670 | link_state->bios_aspm_state = 0; | ||
671 | /* Set support state to 0, so we will disable ASPM later */ | ||
672 | link_state->support_state = 0; | ||
673 | } | ||
602 | 674 | ||
603 | link_state->pdev = pdev; | 675 | link_state->pdev = pdev; |
604 | list_add(&link_state->sibiling, &link_list); | 676 | list_add(&link_state->sibiling, &link_list); |
605 | 677 | ||
678 | if (link_state->downstream_has_switch) { | ||
679 | /* | ||
680 | * If link has switch, delay the link config. The leaf link | ||
681 | * initialization will config the whole hierarchy. but we must | ||
682 | * make sure BIOS doesn't set unsupported link state | ||
683 | **/ | ||
684 | state = pcie_aspm_check_state(pdev, link_state->bios_aspm_state); | ||
685 | __pcie_aspm_config_link(pdev, state); | ||
686 | } else | ||
687 | __pcie_aspm_configure_link_state(pdev, | ||
688 | policy_to_aspm_state(pdev)); | ||
689 | |||
690 | pcie_check_clock_pm(pdev, blacklist); | ||
691 | |||
606 | unlock_out: | 692 | unlock_out: |
607 | if (error) | 693 | if (error) |
608 | free_link_state(pdev); | 694 | free_link_state(pdev); |
@@ -635,6 +721,7 @@ void pcie_aspm_exit_link_state(struct pci_dev *pdev) | |||
635 | /* All functions are removed, so just disable ASPM for the link */ | 721 | /* All functions are removed, so just disable ASPM for the link */ |
636 | __pcie_aspm_config_one_dev(parent, 0); | 722 | __pcie_aspm_config_one_dev(parent, 0); |
637 | list_del(&link_state->sibiling); | 723 | list_del(&link_state->sibiling); |
724 | list_del(&link_state->link); | ||
638 | /* Clock PM is for endpoint device */ | 725 | /* Clock PM is for endpoint device */ |
639 | 726 | ||
640 | free_link_state(parent); | 727 | free_link_state(parent); |