diff options
Diffstat (limited to 'arch/powerpc/platforms/powernv')
-rw-r--r-- | arch/powerpc/platforms/powernv/eeh-ioda.c | 110 |
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 753f08e36dfa..79d0cdf786d0 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, ®, &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; |