summaryrefslogtreecommitdiffstats
path: root/arch/powerpc/platforms
diff options
context:
space:
mode:
authorGavin Shan <gwshan@linux.vnet.ibm.com>2014-04-24 18:00:27 +1000
committerBenjamin Herrenschmidt <benh@kernel.crashing.org>2014-04-28 17:35:05 +1000
commitb2b5efcf208ddc9444aca77336627428782a39f4 (patch)
tree8a532861c06c0109f373aa69ad3b58fb692433e3 /arch/powerpc/platforms
parent35845a7826a27eb1c16ee5b0c5a0307159c1d1c4 (diff)
powerpc/powernv: Fundamental reset on PLX ports
The patch intends to support fundamental reset on PLX downstream ports. If the PCI device matches any one of the internal table, which includes PLX vendor ID, bridge device ID, register offset for fundamental reset and bit, fundamental reset will be done accordingly. Otherwise, it will fail back to hot reset. Additional flag (EEH_DEV_FRESET) is introduced to record the last reset type on the PCI bridge. Signed-off-by: Gavin Shan <gwshan@linux.vnet.ibm.com> Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
Diffstat (limited to 'arch/powerpc/platforms')
-rw-r--r--arch/powerpc/platforms/powernv/eeh-ioda.c110
1 files changed, 94 insertions, 16 deletions
diff --git a/arch/powerpc/platforms/powernv/eeh-ioda.c b/arch/powerpc/platforms/powernv/eeh-ioda.c
index 753f08e36df..79d0cdf786d 100644
--- a/arch/powerpc/platforms/powernv/eeh-ioda.c
+++ b/arch/powerpc/platforms/powernv/eeh-ioda.c
@@ -477,49 +477,127 @@ out:
return 0;
}
+static bool ioda_eeh_is_plx_dnport(struct pci_dev *dev, int *reg,
+ int *mask, int *len)
+{
+ unsigned short *pid;
+ unsigned short ids[] = {
+ 0x10b5, 0x8748, 0x0080, 0x0400, /* PLX#8748 */
+ 0x0000, 0x0000, 0x0000, 0x0000, /* End flag */
+ };
+
+ if (!pci_is_pcie(dev))
+ return false;
+ if (pci_pcie_type(dev) != PCI_EXP_TYPE_DOWNSTREAM)
+ return false;
+
+ pid = &ids[0];
+ while (!reg) {
+ if (pid[0] == 0x0)
+ break;
+
+ if (dev->vendor == pid[0] &&
+ dev->device == pid[1]) {
+ *reg = pid[2];
+ *mask = pid[3];
+ *len = 2;
+ return true;
+ }
+ }
+
+ *reg = PCI_BRIDGE_CONTROL;
+ *mask = PCI_BRIDGE_CTL_BUS_RESET;
+ *len = 2;
+ return false;
+}
+
static int ioda_eeh_bridge_reset(struct pci_dev *dev, int option)
{
struct device_node *dn = pci_device_to_OF_node(dev);
struct eeh_dev *edev = of_node_to_eeh_dev(dn);
int aer = edev ? edev->aer_cap : 0;
- u32 ctrl;
+ int reg, mask, val, len;
+ bool is_plx_dnport;
pr_debug("%s: Reset PCI bus %04x:%02x with option %d\n",
__func__, pci_domain_nr(dev->bus),
dev->bus->number, option);
+
+ is_plx_dnport = ioda_eeh_is_plx_dnport(dev, &reg, &mask, &len);
+ if (option == EEH_RESET_FUNDAMENTAL)
+ if (!is_plx_dnport || !edev)
+ option = EEH_RESET_HOT;
+
+ if (option == EEH_RESET_HOT) {
+ reg = PCI_BRIDGE_CONTROL;
+ mask = PCI_BRIDGE_CTL_BUS_RESET;
+ len = 2;
+ }
+
+ if (option == EEH_RESET_DEACTIVATE) {
+ if (!is_plx_dnport || !edev ||
+ !(edev->mode & EEH_DEV_FRESET)) {
+ reg = PCI_BRIDGE_CONTROL;
+ mask = PCI_BRIDGE_CTL_BUS_RESET;
+ len = 2;
+ }
+ }
+
switch (option) {
case EEH_RESET_FUNDAMENTAL:
+ edev->mode |= EEH_DEV_FRESET;
+ /* Fall through */
case EEH_RESET_HOT:
- /* Don't report linkDown event */
if (aer) {
+ /* Mask receiver error */
+ eeh_ops->read_config(dn, aer + PCI_ERR_COR_MASK,
+ 4, &val);
+ val |= PCI_ERR_COR_RCVR;
+ eeh_ops->write_config(dn, aer + PCI_ERR_COR_MASK,
+ 4, val);
+
+ /* Mask linkDown */
eeh_ops->read_config(dn, aer + PCI_ERR_UNCOR_MASK,
- 4, &ctrl);
- ctrl |= PCI_ERR_UNC_SURPDN;
+ 4, &val);
+ val |= PCI_ERR_UNC_SURPDN;
eeh_ops->write_config(dn, aer + PCI_ERR_UNCOR_MASK,
- 4, ctrl);
- }
+ 4, val);
+ }
- eeh_ops->read_config(dn, PCI_BRIDGE_CONTROL, 2, &ctrl);
- ctrl |= PCI_BRIDGE_CTL_BUS_RESET;
- eeh_ops->write_config(dn, PCI_BRIDGE_CONTROL, 2, ctrl);
+ eeh_ops->read_config(dn, reg, len, &val);
+ val |= mask;
+ eeh_ops->write_config(dn, reg, len, val);
msleep(EEH_PE_RST_HOLD_TIME);
break;
case EEH_RESET_DEACTIVATE:
- eeh_ops->read_config(dn, PCI_BRIDGE_CONTROL, 2, &ctrl);
- ctrl &= ~PCI_BRIDGE_CTL_BUS_RESET;
- eeh_ops->write_config(dn, PCI_BRIDGE_CONTROL, 2, ctrl);
+ eeh_ops->read_config(dn, reg, len, &val);
+ val &= ~mask;
+ eeh_ops->write_config(dn, reg, len, val);
msleep(EEH_PE_RST_SETTLE_TIME);
- /* Continue reporting linkDown event */
+ if (edev)
+ edev->mode &= ~EEH_DEV_FRESET;
if (aer) {
+ /* Clear receive error and enable it */
+ eeh_ops->write_config(dn, aer + PCI_ERR_COR_STATUS,
+ 4, PCI_ERR_COR_RCVR);
+ eeh_ops->read_config(dn, aer + PCI_ERR_COR_MASK,
+ 4, &val);
+ val &= ~PCI_ERR_COR_RCVR;
+ eeh_ops->write_config(dn, aer + PCI_ERR_COR_MASK,
+ 4, val);
+
+ /* Clear linkDown and enable it */
+ eeh_ops->write_config(dn, aer + PCI_ERR_UNCOR_STATUS,
+ 4, PCI_ERR_UNC_SURPDN);
eeh_ops->read_config(dn, aer + PCI_ERR_UNCOR_MASK,
- 4, &ctrl);
- ctrl &= ~PCI_ERR_UNC_SURPDN;
+ 4, &val);
+ val &= ~PCI_ERR_UNC_SURPDN;
eeh_ops->write_config(dn, aer + PCI_ERR_UNCOR_MASK,
- 4, ctrl);
+ 4, val);
}
break;