diff options
author | Benjamin Herrenschmidt <benh@kernel.crashing.org> | 2016-07-06 12:09:52 +1000 |
---|---|---|
committer | Stewart Smith <stewart@linux.vnet.ibm.com> | 2016-12-08 11:34:20 +1100 |
commit | aed1fbe848ec7dcfef5c2831ab779ae9c21b41f6 (patch) | |
tree | 55bd5326c2fc4bbe66627812c96a3f7091bbeb9a /hw/phb3.c | |
parent | 95edf12c0872ef59475d6d2afc64865d2e923b71 (diff) | |
download | skiboot-aed1fbe848ec7dcfef5c2831ab779ae9c21b41f6.zip skiboot-aed1fbe848ec7dcfef5c2831ab779ae9c21b41f6.tar.gz skiboot-aed1fbe848ec7dcfef5c2831ab779ae9c21b41f6.tar.bz2 |
phb3: Trick to allow control of the PCIe link width and speed
This implements a hook inside OPAL that catches 16 and 32 bit writes
to the link status register of the PHB.
It allows you to write a new speed or a new width, and OPAL will then
cause the PHB to renegociate.
Example:
First read the link status on PHB4:
setpci -s 0004:00:00.0 0x5a.w
a103
It's at x16 Gen3 speed (8GT/s)
bits 0x0ff0 are the width and 0x000f the speed. The width can be
1 to 16 and the speed 1 to 3 (2.5, 5 and 8GT/s)
Then try to bring it down to 1x Gen1 :
setpci -s 0004:00:00.0 0x5a.w=0xa011
Observe the result in the PHB:
/ # lspci -s 0004:00:00.0 -vv
0004:00:00.0 PCI bridge: IBM Device 03dc (prog-if 00 [Normal decode])
.../...
LnkSta: Speed 2.5GT/s, Width x1, TrErr- Train- SlotClk- DLActive+ BWMgmt- ABWMgmt+
And in the device:
/ # lspci -s 0004:01:00.0 -vv
.../...
LnkSta: Speed 2.5GT/s, Width x1, TrErr- Train- SlotClk+ DLActive- BWMgmt- ABWMgmt-
Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
Signed-off-by: Stewart Smith <stewart@linux.vnet.ibm.com>
Diffstat (limited to 'hw/phb3.c')
-rw-r--r-- | hw/phb3.c | 86 |
1 files changed, 86 insertions, 0 deletions
@@ -137,6 +137,80 @@ static int64_t phb3_pcicfg_check(struct phb3 *p, uint32_t bdfn, return OPAL_SUCCESS; } +static void phb3_link_update(struct phb *phb, uint16_t data) +{ + struct phb3 *p = phb_to_phb3(phb); + uint32_t new_spd, new_wid; + uint32_t old_spd, old_wid; + uint16_t old_data; + uint64_t lreg; + int i; + + /* Read the old speed and width */ + pci_cfg_read16(phb, 0, 0x5a, &old_data); + + /* Decode the register values */ + new_spd = data & PCICAP_EXP_LSTAT_SPEED; + new_wid = (data & PCICAP_EXP_LSTAT_WIDTH) >> 4; + old_spd = old_data & PCICAP_EXP_LSTAT_SPEED; + old_wid = (old_data & PCICAP_EXP_LSTAT_WIDTH) >> 4; + + /* Apply maximums */ + if (new_wid > 16) + new_wid = 16; + if (new_wid < 1) + new_wid = 1; + if (new_spd > 3) + new_spd = 3; + if (new_spd < 1) + new_spd = 1; + + PHBINF(p, "Link change request: speed %d->%d, width %d->%d\n", + old_spd, new_spd, old_wid, new_wid); + + /* Check if width needs to be changed */ + if (old_wid != new_wid) { + PHBINF(p, "Changing width...\n"); + lreg = in_be64(p->regs + PHB_PCIE_LINK_MANAGEMENT); + lreg = SETFIELD(PHB_PCIE_LM_TGT_LINK_WIDTH, lreg, new_wid); + lreg |= PHB_PCIE_LM_CHG_LINK_WIDTH; + out_be64(p->regs + PHB_PCIE_LINK_MANAGEMENT, lreg); + for (i=0; i<10;i++) { + lreg = in_be64(p->regs + PHB_PCIE_LINK_MANAGEMENT); + if (lreg & PHB_PCIE_LM_DL_WCHG_PENDING) + break; + time_wait_ms_nopoll(1); + } + if (!(lreg & PHB_PCIE_LM_DL_WCHG_PENDING)) + PHBINF(p, "Timeout waiting for speed change start\n"); + for (i=0; i<100;i++) { + lreg = in_be64(p->regs + PHB_PCIE_LINK_MANAGEMENT); + if (!(lreg & PHB_PCIE_LM_DL_WCHG_PENDING)) + break; + time_wait_ms_nopoll(1); + } + if (lreg & PHB_PCIE_LM_DL_WCHG_PENDING) + PHBINF(p, "Timeout waiting for speed change end\n"); + } + /* Check if speed needs to be changed */ + if (old_spd != new_spd) { + PHBINF(p, "Changing speed...\n"); + lreg = in_be64(p->regs + PHB_PCIE_LINK_MANAGEMENT); + if (lreg & PPC_BIT(19)) { + uint16_t lctl2; + PHBINF(p, " Bit19 set ! working around...\n"); + pci_cfg_read16(phb, 0, 0x78, &lctl2); + PHBINF(p, " LCTL2=%04x\n", lctl2); + lctl2 &= ~PCICAP_EXP_LCTL2_HWAUTSPDIS; + pci_cfg_write16(phb, 0, 0x78, lctl2); + } + lreg = in_be64(p->regs + PHB_PCIE_LINK_MANAGEMENT); + lreg = SETFIELD(PHB_PCIE_LM_TGT_SPEED, lreg, new_spd); + lreg |= PHB_PCIE_LM_CHG_SPEED; + out_be64(p->regs + PHB_PCIE_LINK_MANAGEMENT, lreg); + } +} + static void phb3_pcicfg_filter(struct phb *phb, uint32_t bdfn, uint32_t offset, uint32_t len, uint32_t *data, bool write) @@ -145,6 +219,18 @@ static void phb3_pcicfg_filter(struct phb *phb, uint32_t bdfn, struct pci_cfg_reg_filter *pcrf; uint32_t flags; + /* Hack for link speed changes. We intercept attempts at writing + * the link control/status register + */ + if (bdfn == 0 && write && len == 4 && offset == 0x58) { + phb3_link_update(phb, (*data) >> 16); + return; + } + if (bdfn == 0 && write && len == 2 && offset == 0x5a) { + phb3_link_update(phb, *(uint16_t *)data); + return; + } + /* FIXME: It harms the performance to search the PCI * device which doesn't have any filters at all. So * it's worthy to maintain a table in PHB to indicate |