ASPM: Fix pcie devices with non-pcie children
[pandora-kernel.git] / drivers / pci / pcie / aspm.c
index cbfbab1..2275162 100644 (file)
@@ -68,7 +68,7 @@ struct pcie_link_state {
        struct aspm_latency acceptable[8];
 };
 
-static int aspm_disabled, aspm_force, aspm_clear_state;
+static int aspm_disabled, aspm_force;
 static bool aspm_support_enabled = true;
 static DEFINE_MUTEX(aspm_lock);
 static LIST_HEAD(link_list);
@@ -500,9 +500,6 @@ static int pcie_aspm_sanity_check(struct pci_dev *pdev)
        int pos;
        u32 reg32;
 
-       if (aspm_clear_state)
-               return -EINVAL;
-
        /*
         * Some functions in a slot might not all be PCIe functions,
         * very strange. Disable ASPM for the whole slot
@@ -511,6 +508,16 @@ static int pcie_aspm_sanity_check(struct pci_dev *pdev)
                pos = pci_pcie_cap(child);
                if (!pos)
                        return -EINVAL;
+
+               /*
+                * If ASPM is disabled then we're not going to change
+                * the BIOS state. It's safe to continue even if it's a
+                * pre-1.1 device
+                */
+
+               if (aspm_disabled)
+                       continue;
+
                /*
                 * Disable ASPM for pre-1.1 PCIe device, we follow MS to use
                 * RBER bit to determine if a function is 1.1 version device
@@ -574,9 +581,6 @@ void pcie_aspm_init_link_state(struct pci_dev *pdev)
            pdev->pcie_type != PCI_EXP_TYPE_DOWNSTREAM)
                return;
 
-       if (aspm_disabled && !aspm_clear_state)
-               return;
-
        /* VIA has a strange chipset, root port is under a bridge */
        if (pdev->pcie_type == PCI_EXP_TYPE_ROOT_PORT &&
            pdev->bus->self)
@@ -608,7 +612,7 @@ void pcie_aspm_init_link_state(struct pci_dev *pdev)
         * the BIOS's expectation, we'll do so once pci_enable_device() is
         * called.
         */
-       if (aspm_policy != POLICY_POWERSAVE || aspm_clear_state) {
+       if (aspm_policy != POLICY_POWERSAVE) {
                pcie_config_aspm_path(link);
                pcie_set_clkpm(link, policy_to_clkpm_state(link));
        }
@@ -649,8 +653,7 @@ void pcie_aspm_exit_link_state(struct pci_dev *pdev)
        struct pci_dev *parent = pdev->bus->self;
        struct pcie_link_state *link, *root, *parent_link;
 
-       if ((aspm_disabled && !aspm_clear_state) || !pci_is_pcie(pdev) ||
-           !parent || !parent->link_state)
+       if (!pci_is_pcie(pdev) || !parent || !parent->link_state)
                return;
        if ((parent->pcie_type != PCI_EXP_TYPE_ROOT_PORT) &&
            (parent->pcie_type != PCI_EXP_TYPE_DOWNSTREAM))
@@ -734,13 +737,18 @@ void pcie_aspm_powersave_config_link(struct pci_dev *pdev)
  * pci_disable_link_state - disable pci device's link state, so the link will
  * never enter specific states
  */
-static void __pci_disable_link_state(struct pci_dev *pdev, int state, bool sem)
+static void __pci_disable_link_state(struct pci_dev *pdev, int state, bool sem,
+                                    bool force)
 {
        struct pci_dev *parent = pdev->bus->self;
        struct pcie_link_state *link;
 
-       if (aspm_disabled || !pci_is_pcie(pdev))
+       if (aspm_disabled && !force)
                return;
+
+       if (!pci_is_pcie(pdev))
+               return;
+
        if (pdev->pcie_type == PCI_EXP_TYPE_ROOT_PORT ||
            pdev->pcie_type == PCI_EXP_TYPE_DOWNSTREAM)
                parent = pdev;
@@ -768,16 +776,31 @@ static void __pci_disable_link_state(struct pci_dev *pdev, int state, bool sem)
 
 void pci_disable_link_state_locked(struct pci_dev *pdev, int state)
 {
-       __pci_disable_link_state(pdev, state, false);
+       __pci_disable_link_state(pdev, state, false, false);
 }
 EXPORT_SYMBOL(pci_disable_link_state_locked);
 
 void pci_disable_link_state(struct pci_dev *pdev, int state)
 {
-       __pci_disable_link_state(pdev, state, true);
+       __pci_disable_link_state(pdev, state, true, false);
 }
 EXPORT_SYMBOL(pci_disable_link_state);
 
+void pcie_clear_aspm(struct pci_bus *bus)
+{
+       struct pci_dev *child;
+
+       /*
+        * Clear any ASPM setup that the firmware has carried out on this bus
+        */
+       list_for_each_entry(child, &bus->devices, bus_list) {
+               __pci_disable_link_state(child, PCIE_LINK_STATE_L0S |
+                                        PCIE_LINK_STATE_L1 |
+                                        PCIE_LINK_STATE_CLKPM,
+                                        false, true);
+       }
+}
+
 static int pcie_aspm_set_policy(const char *val, struct kernel_param *kp)
 {
        int i;
@@ -935,6 +958,7 @@ void pcie_aspm_remove_sysfs_dev_files(struct pci_dev *pdev)
 static int __init pcie_aspm_disable(char *str)
 {
        if (!strcmp(str, "off")) {
+               aspm_policy = POLICY_DEFAULT;
                aspm_disabled = 1;
                aspm_support_enabled = false;
                printk(KERN_INFO "PCIe ASPM is disabled\n");
@@ -947,16 +971,18 @@ static int __init pcie_aspm_disable(char *str)
 
 __setup("pcie_aspm=", pcie_aspm_disable);
 
-void pcie_clear_aspm(void)
-{
-       if (!aspm_force)
-               aspm_clear_state = 1;
-}
-
 void pcie_no_aspm(void)
 {
-       if (!aspm_force)
+       /*
+        * Disabling ASPM is intended to prevent the kernel from modifying
+        * existing hardware state, not to clear existing state. To that end:
+        * (a) set policy to POLICY_DEFAULT in order to avoid changing state
+        * (b) prevent userspace from changing policy
+        */
+       if (!aspm_force) {
+               aspm_policy = POLICY_DEFAULT;
                aspm_disabled = 1;
+       }
 }
 
 /**