Merge tag 'powerpc-4.11-2' of git://git.kernel.org/pub/scm/linux/kernel/git/powerpc...
[platform/kernel/linux-starfive.git] / drivers / pci / hotplug / pnv_php.c
index d2961ef..7c20319 100644 (file)
@@ -35,9 +35,11 @@ static void pnv_php_register(struct device_node *dn);
 static void pnv_php_unregister_one(struct device_node *dn);
 static void pnv_php_unregister(struct device_node *dn);
 
-static void pnv_php_disable_irq(struct pnv_php_slot *php_slot)
+static void pnv_php_disable_irq(struct pnv_php_slot *php_slot,
+                               bool disable_device)
 {
        struct pci_dev *pdev = php_slot->pdev;
+       int irq = php_slot->irq;
        u16 ctrl;
 
        if (php_slot->irq > 0) {
@@ -56,10 +58,14 @@ static void pnv_php_disable_irq(struct pnv_php_slot *php_slot)
                php_slot->wq = NULL;
        }
 
-       if (pdev->msix_enabled)
-               pci_disable_msix(pdev);
-       else if (pdev->msi_enabled)
-               pci_disable_msi(pdev);
+       if (disable_device || irq > 0) {
+               if (pdev->msix_enabled)
+                       pci_disable_msix(pdev);
+               else if (pdev->msi_enabled)
+                       pci_disable_msi(pdev);
+
+               pci_disable_device(pdev);
+       }
 }
 
 static void pnv_php_free_slot(struct kref *kref)
@@ -68,7 +74,7 @@ static void pnv_php_free_slot(struct kref *kref)
                                        struct pnv_php_slot, kref);
 
        WARN_ON(!list_empty(&php_slot->children));
-       pnv_php_disable_irq(php_slot);
+       pnv_php_disable_irq(php_slot, false);
        kfree(php_slot->name);
        kfree(php_slot);
 }
@@ -76,7 +82,7 @@ static void pnv_php_free_slot(struct kref *kref)
 static inline void pnv_php_put_slot(struct pnv_php_slot *php_slot)
 {
 
-       if (WARN_ON(!php_slot))
+       if (!php_slot)
                return;
 
        kref_put(&php_slot->kref, pnv_php_free_slot);
@@ -430,9 +436,21 @@ static int pnv_php_enable(struct pnv_php_slot *php_slot, bool rescan)
        if (ret)
                return ret;
 
-       /* Proceed if there have nothing behind the slot */
-       if (presence == OPAL_PCI_SLOT_EMPTY)
+       /*
+        * Proceed if there have nothing behind the slot. However,
+        * we should leave the slot in registered state at the
+        * beginning. Otherwise, the PCI devices inserted afterwards
+        * won't be probed and populated.
+        */
+       if (presence == OPAL_PCI_SLOT_EMPTY) {
+               if (!php_slot->power_state_check) {
+                       php_slot->power_state_check = true;
+
+                       return 0;
+               }
+
                goto scan;
+       }
 
        /*
         * If the power supply to the slot is off, we can't detect
@@ -705,10 +723,15 @@ static irqreturn_t pnv_php_interrupt(int irq, void *data)
        if (sts & PCI_EXP_SLTSTA_DLLSC) {
                pcie_capability_read_word(pdev, PCI_EXP_LNKSTA, &lsts);
                added = !!(lsts & PCI_EXP_LNKSTA_DLLLA);
-       } else if (sts & PCI_EXP_SLTSTA_PDC) {
+       } else if (!(php_slot->flags & PNV_PHP_FLAG_BROKEN_PDC) &&
+                  (sts & PCI_EXP_SLTSTA_PDC)) {
                ret = pnv_pci_get_presence_state(php_slot->id, &presence);
-               if (!ret)
+               if (ret) {
+                       dev_warn(&pdev->dev, "PCI slot [%s] error %d getting presence (0x%04x), to retry the operation.\n",
+                                php_slot->name, ret, sts);
                        return IRQ_HANDLED;
+               }
+
                added = !!(presence == OPAL_PCI_SLOT_PRESENT);
        } else {
                return IRQ_NONE;
@@ -752,6 +775,7 @@ static irqreturn_t pnv_php_interrupt(int irq, void *data)
 static void pnv_php_init_irq(struct pnv_php_slot *php_slot, int irq)
 {
        struct pci_dev *pdev = php_slot->pdev;
+       u32 broken_pdc = 0;
        u16 sts, ctrl;
        int ret;
 
@@ -759,29 +783,44 @@ static void pnv_php_init_irq(struct pnv_php_slot *php_slot, int irq)
        php_slot->wq = alloc_workqueue("pciehp-%s", 0, 0, php_slot->name);
        if (!php_slot->wq) {
                dev_warn(&pdev->dev, "Cannot alloc workqueue\n");
-               pnv_php_disable_irq(php_slot);
+               pnv_php_disable_irq(php_slot, true);
                return;
        }
 
+       /* Check PDC (Presence Detection Change) is broken or not */
+       ret = of_property_read_u32(php_slot->dn, "ibm,slot-broken-pdc",
+                                  &broken_pdc);
+       if (!ret && broken_pdc)
+               php_slot->flags |= PNV_PHP_FLAG_BROKEN_PDC;
+
        /* Clear pending interrupts */
        pcie_capability_read_word(pdev, PCI_EXP_SLTSTA, &sts);
-       sts |= (PCI_EXP_SLTSTA_PDC | PCI_EXP_SLTSTA_DLLSC);
+       if (php_slot->flags & PNV_PHP_FLAG_BROKEN_PDC)
+               sts |= PCI_EXP_SLTSTA_DLLSC;
+       else
+               sts |= (PCI_EXP_SLTSTA_PDC | PCI_EXP_SLTSTA_DLLSC);
        pcie_capability_write_word(pdev, PCI_EXP_SLTSTA, sts);
 
        /* Request the interrupt */
        ret = request_irq(irq, pnv_php_interrupt, IRQF_SHARED,
                          php_slot->name, php_slot);
        if (ret) {
-               pnv_php_disable_irq(php_slot);
+               pnv_php_disable_irq(php_slot, true);
                dev_warn(&pdev->dev, "Error %d enabling IRQ %d\n", ret, irq);
                return;
        }
 
        /* Enable the interrupts */
        pcie_capability_read_word(pdev, PCI_EXP_SLTCTL, &ctrl);
-       ctrl |= (PCI_EXP_SLTCTL_HPIE |
-                PCI_EXP_SLTCTL_PDCE |
-                PCI_EXP_SLTCTL_DLLSCE);
+       if (php_slot->flags & PNV_PHP_FLAG_BROKEN_PDC) {
+               ctrl &= ~PCI_EXP_SLTCTL_PDCE;
+               ctrl |= (PCI_EXP_SLTCTL_HPIE |
+                        PCI_EXP_SLTCTL_DLLSCE);
+       } else {
+               ctrl |= (PCI_EXP_SLTCTL_HPIE |
+                        PCI_EXP_SLTCTL_PDCE |
+                        PCI_EXP_SLTCTL_DLLSCE);
+       }
        pcie_capability_write_word(pdev, PCI_EXP_SLTCTL, ctrl);
 
        /* The interrupt is initialized successfully when @irq is valid */
@@ -793,6 +832,14 @@ static void pnv_php_enable_irq(struct pnv_php_slot *php_slot)
        struct pci_dev *pdev = php_slot->pdev;
        int irq, ret;
 
+       /*
+        * The MSI/MSIx interrupt might have been occupied by other
+        * drivers. Don't populate the surprise hotplug capability
+        * in that case.
+        */
+       if (pci_dev_msi_enabled(pdev))
+               return;
+
        ret = pci_enable_device(pdev);
        if (ret) {
                dev_warn(&pdev->dev, "Error %d enabling device\n", ret);