diff options
Diffstat (limited to 'hw/p7ioc-phb.c')
-rw-r--r-- | hw/p7ioc-phb.c | 3206 |
1 files changed, 3206 insertions, 0 deletions
diff --git a/hw/p7ioc-phb.c b/hw/p7ioc-phb.c new file mode 100644 index 0000000..8dc7616 --- /dev/null +++ b/hw/p7ioc-phb.c @@ -0,0 +1,3206 @@ +/* Copyright 2013-2014 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + * implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <skiboot.h> +#include <p7ioc.h> +#include <p7ioc-regs.h> +#include <io.h> +#include <timebase.h> +#include <affinity.h> +#include <pci.h> +#include <pci-cfg.h> +#include <interrupts.h> +#include <opal.h> +#include <ccan/str/str.h> + +static void p7ioc_phb_trace(struct p7ioc_phb *p, FILE *s, const char *fmt, ...) +__attribute__ ((format (printf, 3, 4))); + +static void p7ioc_phb_trace(struct p7ioc_phb *p, FILE *s, const char *fmt, ...) +{ + /* Use a temp stack buffer to print all at once to avoid + * mixups of a trace entry on SMP + */ + char tbuf[128 + 10]; + va_list args; + char *b = tbuf; + + b += sprintf(b, "PHB%d: ", p->phb.opal_id); + va_start(args, fmt); + vsnprintf(b, 128, fmt, args); + va_end(args); + fputs(tbuf, s); +} +#define PHBDBG(p, fmt...) p7ioc_phb_trace(p, stdout, fmt) +#define PHBERR(p, fmt...) p7ioc_phb_trace(p, stderr, fmt) + +/* Helper to select an IODA table entry */ +static inline void p7ioc_phb_ioda_sel(struct p7ioc_phb *p, uint32_t table, + uint32_t addr, bool autoinc) +{ + out_be64(p->regs + PHB_IODA_ADDR, + (autoinc ? PHB_IODA_AD_AUTOINC : 0) | + SETFIELD(PHB_IODA_AD_TSEL, 0ul, table) | + SETFIELD(PHB_IODA_AD_TADR, 0ul, addr)); +} + +/* Helper to set the state machine timeout */ +static inline uint64_t p7ioc_set_sm_timeout(struct p7ioc_phb *p, uint64_t dur) +{ + uint64_t target, now = mftb(); + + target = now + dur; + if (target == 0) + target++; + p->delay_tgt_tb = target; + + return dur; +} + +/* + * Lock callbacks. Allows the OPAL API handlers to lock the + * PHB around calls such as config space, EEH, etc... + */ +static void p7ioc_phb_lock(struct phb *phb) +{ + struct p7ioc_phb *p = phb_to_p7ioc_phb(phb); + + lock(&p->lock); +} + +static void p7ioc_phb_unlock(struct phb *phb) +{ + struct p7ioc_phb *p = phb_to_p7ioc_phb(phb); + + unlock(&p->lock); +} + +static bool p7ioc_phb_fenced(struct p7ioc_phb *p) +{ + struct p7ioc *ioc = p->ioc; + uint64_t fence, fbits; + + fbits = 0x0003000000000000 >> (p->index * 4); + fence = in_be64(ioc->regs + P7IOC_CHIP_FENCE_SHADOW); + + return (fence & fbits) != 0; +} + +/* + * Configuration space access + * + * The PHB lock is assumed to be already held + */ +static int64_t p7ioc_pcicfg_check(struct p7ioc_phb *p, uint32_t bdfn, + uint32_t offset, uint32_t size) +{ + uint32_t sm = size - 1; + + if (offset > 0xfff || bdfn > 0xffff) + return OPAL_PARAMETER; + if (offset & sm) + return OPAL_PARAMETER; + + /* The root bus only has a device at 0 and we get into an + * error state if we try to probe beyond that, so let's + * avoid that and just return an error to Linux + */ + if ((bdfn >> 8) == 0 && (bdfn & 0xff)) + return OPAL_HARDWARE; + + /* Check PHB state */ + if (p->state == P7IOC_PHB_STATE_BROKEN) + return OPAL_HARDWARE; + + return OPAL_SUCCESS; +} + +#define P7IOC_PCI_CFG_READ(size, type) \ +static int64_t p7ioc_pcicfg_read##size(struct phb *phb, uint32_t bdfn, \ + uint32_t offset, type *data) \ +{ \ + struct p7ioc_phb *p = phb_to_p7ioc_phb(phb); \ + uint64_t addr; \ + void *base = p->regs; \ + int64_t rc; \ + \ + /* Initialize data in case of error */ \ + *data = (type)0xffffffff; \ + \ + rc = p7ioc_pcicfg_check(p, bdfn, offset, sizeof(type)); \ + if (rc) \ + return rc; \ + \ + if (p7ioc_phb_fenced(p)) { \ + if (!(p->flags & P7IOC_PHB_CFG_USE_ASB)) \ + return OPAL_HARDWARE; \ + \ + base = p->regs_asb; \ + } else if ((p->flags & P7IOC_PHB_CFG_BLOCKED) && bdfn != 0) { \ + return OPAL_HARDWARE; \ + } \ + \ + addr = PHB_CA_ENABLE | ((uint64_t)bdfn << PHB_CA_FUNC_LSH); \ + addr = SETFIELD(PHB_CA_REG, addr, offset); \ + out_be64(base + PHB_CONFIG_ADDRESS, addr); \ + *data = in_le##size(base + PHB_CONFIG_DATA + \ + (offset & (4 - sizeof(type)))); \ + \ + return OPAL_SUCCESS; \ +} + +#define P7IOC_PCI_CFG_WRITE(size, type) \ +static int64_t p7ioc_pcicfg_write##size(struct phb *phb, uint32_t bdfn, \ + uint32_t offset, type data) \ +{ \ + struct p7ioc_phb *p = phb_to_p7ioc_phb(phb); \ + void *base = p->regs; \ + uint64_t addr; \ + int64_t rc; \ + \ + rc = p7ioc_pcicfg_check(p, bdfn, offset, sizeof(type)); \ + if (rc) \ + return rc; \ + \ + if (p7ioc_phb_fenced(p)) { \ + if (!(p->flags & P7IOC_PHB_CFG_USE_ASB)) \ + return OPAL_HARDWARE; \ + \ + base = p->regs_asb; \ + } else if ((p->flags & P7IOC_PHB_CFG_BLOCKED) && bdfn != 0) { \ + return OPAL_HARDWARE; \ + } \ + \ + addr = PHB_CA_ENABLE | ((uint64_t)bdfn << PHB_CA_FUNC_LSH); \ + addr = SETFIELD(PHB_CA_REG, addr, offset); \ + out_be64(base + PHB_CONFIG_ADDRESS, addr); \ + out_le##size(base + PHB_CONFIG_DATA + \ + (offset & (4 - sizeof(type))), data); \ + \ + return OPAL_SUCCESS; \ +} + +P7IOC_PCI_CFG_READ(8, uint8_t) +P7IOC_PCI_CFG_READ(16, uint16_t) +P7IOC_PCI_CFG_READ(32, uint32_t) +P7IOC_PCI_CFG_WRITE(8, uint8_t) +P7IOC_PCI_CFG_WRITE(16, uint16_t) +P7IOC_PCI_CFG_WRITE(32, uint32_t) + +static int64_t p7ioc_presence_detect(struct phb *phb) +{ + struct p7ioc_phb *p = phb_to_p7ioc_phb(phb); + uint64_t reg = in_be64(p->regs + PHB_PCIE_SLOTCTL2); + + /* XXX Test for PHB in error state ? */ + + if (reg & PHB_PCIE_SLOTCTL2_PRSTN_STAT) + return OPAL_SHPC_DEV_PRESENT; + + return OPAL_SHPC_DEV_NOT_PRESENT; +} + +static int64_t p7ioc_link_state(struct phb *phb) +{ + struct p7ioc_phb *p = phb_to_p7ioc_phb(phb); + uint64_t reg = in_be64(p->regs + PHB_PCIE_DLP_TRAIN_CTL); + uint16_t lstat; + int64_t rc; + + /* XXX Test for PHB in error state ? */ + + /* Link is up, let's find the actual speed */ + if (!(reg & PHB_PCIE_DLP_TC_DL_LINKACT)) + return OPAL_SHPC_LINK_DOWN; + + rc = p7ioc_pcicfg_read16(&p->phb, 0, p->ecap + PCICAP_EXP_LSTAT, + &lstat); + if (rc < 0) { + /* Shouldn't happen */ + PHBERR(p, "Failed to read link status\n"); + return OPAL_HARDWARE; + } + if (!(lstat & PCICAP_EXP_LSTAT_DLLL_ACT)) + return OPAL_SHPC_LINK_DOWN; + + return GETFIELD(PCICAP_EXP_LSTAT_WIDTH, lstat); +} + +static int64_t p7ioc_sm_freset(struct p7ioc_phb *p) +{ + uint64_t reg; + uint32_t cfg32; + uint64_t ci_idx = p->index + 2; + + switch(p->state) { + case P7IOC_PHB_STATE_FUNCTIONAL: + /* If the slot isn't present, we needn't do it */ + reg = in_be64(p->regs + PHB_PCIE_SLOTCTL2); + if (!(reg & PHB_PCIE_SLOTCTL2_PRSTN_STAT)) { + PHBDBG(p, "Slot freset: no device\n"); + return OPAL_CLOSED; + } + + /* Mask PCIE port interrupts and AER receiver error */ + out_be64(p->regs + UTL_PCIE_PORT_IRQ_EN, 0x7E00000000000000); + p7ioc_pcicfg_read32(&p->phb, 0, + p->aercap + PCIECAP_AER_CE_MASK, &cfg32); + cfg32 |= PCIECAP_AER_CE_RECVR_ERR; + p7ioc_pcicfg_write32(&p->phb, 0, + p->aercap + PCIECAP_AER_CE_MASK, cfg32); + + /* Mask CI port error and clear it */ + out_be64(p->ioc->regs + P7IOC_CIn_LEM_ERR_MASK(ci_idx), + 0xa4f4000000000000ul); + out_be64(p->regs + PHB_LEM_ERROR_MASK, + 0xadb650c9808dd051ul); + out_be64(p->ioc->regs + P7IOC_CIn_LEM_FIR(ci_idx), + 0x0ul); + + /* Disable link to avoid training issues */ + reg = in_be64(p->regs + PHB_PCIE_DLP_TRAIN_CTL); + reg |= PHB_PCIE_DLP_TCTX_DISABLE; + out_be64(p->regs + PHB_PCIE_DLP_TRAIN_CTL, reg); + PHBDBG(p, "Slot freset: disable link training\n"); + + p->state = P7IOC_PHB_STATE_FRESET_DISABLE_LINK; + p->retries = 12; + return p7ioc_set_sm_timeout(p, msecs_to_tb(10)); + case P7IOC_PHB_STATE_FRESET_DISABLE_LINK: + reg = in_be64(p->regs + PHB_PCIE_DLP_TRAIN_CTL); + if (reg & PHB_PCIE_DLP_TCRX_DISABLED) { + /* Turn on freset */ + reg = in_be64(p->regs + PHB_RESET); + reg &= ~0x2000000000000000ul; + out_be64(p->regs + PHB_RESET, reg); + PHBDBG(p, "Slot freset: assert\n"); + + p->state = P7IOC_PHB_STATE_FRESET_ASSERT_DELAY; + return p7ioc_set_sm_timeout(p, secs_to_tb(1)); + } + + if (p->retries-- == 0) { + PHBDBG(p, "Slot freset: timeout to disable link training\n"); + goto error; + } + + return p7ioc_set_sm_timeout(p, msecs_to_tb(10)); + case P7IOC_PHB_STATE_FRESET_ASSERT_DELAY: + /* Turn off freset */ + reg = in_be64(p->regs + PHB_RESET); + reg |= 0x2000000000000000ul; + out_be64(p->regs + PHB_RESET, reg); + PHBDBG(p, "Slot freset: deassert\n"); + + p->state = P7IOC_PHB_STATE_FRESET_DEASSERT_DELAY; + return p7ioc_set_sm_timeout(p, msecs_to_tb(200)); + case P7IOC_PHB_STATE_FRESET_DEASSERT_DELAY: + /* Restore link control */ + reg = in_be64(p->regs + PHB_PCIE_DLP_TRAIN_CTL); + reg &= ~PHB_PCIE_DLP_TCTX_DISABLE; + out_be64(p->regs + PHB_PCIE_DLP_TRAIN_CTL, reg); + PHBDBG(p, "Slot freset: enable link training\n"); + + p->state = P7IOC_PHB_STATE_FRESET_WAIT_LINK; + p->retries = 100; + return p7ioc_set_sm_timeout(p, msecs_to_tb(10)); + case P7IOC_PHB_STATE_FRESET_WAIT_LINK: + reg = in_be64(p->regs + PHB_PCIE_DLP_TRAIN_CTL); + if (reg & PHB_PCIE_DLP_TC_DL_LINKACT) { + /* + * Clear spurious errors and enable PCIE port + * interrupts + */ + out_be64(p->regs + UTL_PCIE_PORT_STATUS, + 0x00E0000000000000); + out_be64(p->regs + UTL_PCIE_PORT_IRQ_EN, + 0xFE65000000000000); + + /* Clear AER receiver error status */ + p7ioc_pcicfg_write32(&p->phb, 0, + p->aercap + PCIECAP_AER_CE_STATUS, + PCIECAP_AER_CE_RECVR_ERR); + /* Unmask receiver error status in AER */ + p7ioc_pcicfg_read32(&p->phb, 0, + p->aercap + PCIECAP_AER_CE_MASK, &cfg32); + cfg32 &= ~PCIECAP_AER_CE_RECVR_ERR; + p7ioc_pcicfg_write32(&p->phb, 0, + p->aercap + PCIECAP_AER_CE_MASK, cfg32); + /* Clear and Unmask CI port and PHB errors */ + out_be64(p->ioc->regs + P7IOC_CIn_LEM_FIR(ci_idx), + 0x0ul); + out_be64(p->regs + PHB_LEM_FIR_ACCUM, + 0x0ul); + out_be64(p->ioc->regs + P7IOC_CIn_LEM_ERR_MASK_AND(ci_idx), + 0x0ul); + out_be64(p->regs + PHB_LEM_ERROR_MASK, + 0x1249a1147f500f2cul); + PHBDBG(p, "Slot freset: link up!\n"); + + p->state = P7IOC_PHB_STATE_FUNCTIONAL; + p->flags &= ~P7IOC_PHB_CFG_BLOCKED; + return OPAL_SUCCESS; + } + + if (p->retries-- == 0) { + uint16_t val; + + if (p->gen == 1) { + PHBDBG(p, "Slot freset: timeout for link up in Gen1 mode!\n"); + goto error; + } + + PHBDBG(p, "Slot freset: timeout for link up.\n"); + PHBDBG(p, "Slot freset: fallback to Gen1.\n"); + p->gen --; + + /* Limit speed to 2.5G */ + p7ioc_pcicfg_read16(&p->phb, 0, + p->ecap + PCICAP_EXP_LCTL2, &val); + val = SETFIELD(PCICAP_EXP_LCTL2_TLSPD, val, 1); + p7ioc_pcicfg_write16(&p->phb, 0, + p->ecap + PCICAP_EXP_LCTL2, + val); + + /* Retrain */ + p7ioc_pcicfg_read16(&p->phb, 0, + p->ecap + PCICAP_EXP_LCTL, &val); + p7ioc_pcicfg_write16(&p->phb, 0, + p->ecap + PCICAP_EXP_LCTL, + val | PCICAP_EXP_LCTL_LINK_RETRAIN); + + /* Enter FRESET_WAIT_LINK, again */ + p->state = P7IOC_PHB_STATE_FRESET_WAIT_LINK; + p->retries = 100; + return p7ioc_set_sm_timeout(p, msecs_to_tb(10)); + } + + return p7ioc_set_sm_timeout(p, msecs_to_tb(10)); + default: + break; + } + +error: + p->state = P7IOC_PHB_STATE_FUNCTIONAL; + return OPAL_HARDWARE; +} + +static int64_t p7ioc_freset(struct phb *phb) +{ + struct p7ioc_phb *p = phb_to_p7ioc_phb(phb); + + if (p->state != P7IOC_PHB_STATE_FUNCTIONAL) + return OPAL_HARDWARE; + + p->flags |= P7IOC_PHB_CFG_BLOCKED; + return p7ioc_sm_freset(p); +} + +static int64_t p7ioc_power_state(struct phb *phb) +{ + struct p7ioc_phb *p = phb_to_p7ioc_phb(phb); + uint64_t reg = in_be64(p->regs + PHB_PCIE_SLOTCTL2); + + /* XXX Test for PHB in error state ? */ + + if (reg & PHB_PCIE_SLOTCTL2_PWR_EN_STAT) + return OPAL_SHPC_POWER_ON; + + return OPAL_SHPC_POWER_OFF; +} + +static int64_t p7ioc_sm_slot_power_off(struct p7ioc_phb *p) +{ + uint64_t reg; + + switch(p->state) { + case P7IOC_PHB_STATE_FUNCTIONAL: + /* + * Check the presence and power status. If be not + * be present or power down, we stop here. + */ + reg = in_be64(p->regs + PHB_PCIE_SLOTCTL2); + if (!(reg & PHB_PCIE_SLOTCTL2_PRSTN_STAT)) { + PHBDBG(p, "Slot power off: no device\n"); + return OPAL_CLOSED; + } + reg = in_be64(p->regs + PHB_PCIE_SLOTCTL2); + if (!(reg & PHB_PCIE_SLOTCTL2_PWR_EN_STAT)) { + PHBDBG(p, "Slot power off: already off\n"); + p->state = P7IOC_PHB_STATE_FUNCTIONAL; + return OPAL_SUCCESS; + } + + /* + * Mask PCIE port interrupt and turn power off + * + * We have to set bit 0 and clear it explicitly on PHB + * hotplug override register when doing power-off on the + * PHB slot. Otherwise, it won't take effect. That's the + * similar thing as we did for power-on. + */ + out_be64(p->regs + UTL_PCIE_PORT_IRQ_EN, 0x7e00000000000000); + reg = in_be64(p->regs + PHB_HOTPLUG_OVERRIDE); + reg &= ~(0x8c00000000000000ul); + reg |= 0x8400000000000000ul; + out_be64(p->regs + PHB_HOTPLUG_OVERRIDE, reg); + reg &= ~(0x8c00000000000000ul); + reg |= 0x0c00000000000000ul; + out_be64(p->regs + PHB_HOTPLUG_OVERRIDE, reg); + PHBDBG(p, "Slot power off: powering off...\n"); + + p->state = P7IOC_PHB_STATE_SPDOWN_STABILIZE_DELAY; + return p7ioc_set_sm_timeout(p, secs_to_tb(2)); + case P7IOC_PHB_STATE_SPDOWN_STABILIZE_DELAY: + /* + * The link should be stabilized after 2 seconds. + * We still need poll registers to make sure the + * power is really down every 1ms until limited + * 1000 times. + */ + p->retries = 1000; + p->state = P7IOC_PHB_STATE_SPDOWN_SLOT_STATUS; + PHBDBG(p, "Slot power off: waiting for power off\n"); + case P7IOC_PHB_STATE_SPDOWN_SLOT_STATUS: + reg = in_be64(p->regs + PHB_PCIE_SLOTCTL2); + if (!(reg & PHB_PCIE_SLOTCTL2_PWR_EN_STAT)) { + /* + * We completed the task. Clear link errors + * and restore PCIE port interrupts. + */ + out_be64(p->regs + UTL_PCIE_PORT_STATUS, + 0x00E0000000000000ul); + out_be64(p->regs + UTL_PCIE_PORT_IRQ_EN, + 0xFE65000000000000ul); + + PHBDBG(p, "Slot power off: power off completely\n"); + p->state = P7IOC_PHB_STATE_FUNCTIONAL; + return OPAL_SUCCESS; + } + + if (p->retries-- == 0) { + PHBERR(p, "Timeout powering off\n"); + goto error; + } + return p7ioc_set_sm_timeout(p, msecs_to_tb(1)); + default: + break; + } + +error: + p->state = P7IOC_PHB_STATE_FUNCTIONAL; + return OPAL_HARDWARE; +} + +static int64_t p7ioc_slot_power_off(struct phb *phb) +{ + struct p7ioc_phb *p = phb_to_p7ioc_phb(phb); + + if (p->state != P7IOC_PHB_STATE_FUNCTIONAL) + return OPAL_BUSY; + + /* run state machine */ + return p7ioc_sm_slot_power_off(p); +} + +static int64_t p7ioc_sm_slot_power_on(struct p7ioc_phb *p) +{ + uint64_t reg; + uint32_t reg32; + uint64_t ci_idx = p->index + 2; + + switch(p->state) { + case P7IOC_PHB_STATE_FUNCTIONAL: + /* Check presence */ + reg = in_be64(p->regs + PHB_PCIE_SLOTCTL2); + if (!(reg & PHB_PCIE_SLOTCTL2_PRSTN_STAT)) { + PHBDBG(p, "Slot power on: no device\n"); + return OPAL_CLOSED; + } + + /* Adjust UTL interrupt settings to disable various + * errors that would interfere with the process + */ + out_be64(p->regs + UTL_PCIE_PORT_IRQ_EN, 0x7e00000000000000); + + /* If the power is not on, turn it on now */ + if (!(reg & PHB_PCIE_SLOTCTL2_PWR_EN_STAT)) { + /* + * The hotplug override register will not properly + * initiate the poweron sequence unless bit 0 + * transitions from 0 to 1. Since it can already be + * set to 1 as a result of a previous power-on + * operation (even if the slot power is now off) + * we need to first clear it, then set it to 1 or + * nothing will happen + */ + reg = in_be64(p->regs + PHB_HOTPLUG_OVERRIDE); + reg &= ~(0x8c00000000000000ul); + out_be64(p->regs + PHB_HOTPLUG_OVERRIDE, reg); + reg |= 0x8400000000000000ul; + out_be64(p->regs + PHB_HOTPLUG_OVERRIDE, reg); + p->state = P7IOC_PHB_STATE_SPUP_STABILIZE_DELAY; + PHBDBG(p, "Slot power on: powering on...\n"); + return p7ioc_set_sm_timeout(p, secs_to_tb(2)); + } + /* Power is already on */ + power_ok: + /* Mask AER receiver error */ + p7ioc_pcicfg_read32(&p->phb, 0, + p->aercap + PCIECAP_AER_CE_MASK, ®32); + reg32 |= PCIECAP_AER_CE_RECVR_ERR; + p7ioc_pcicfg_write32(&p->phb, 0, + p->aercap + PCIECAP_AER_CE_MASK, reg32); + + /* Mask CI port error and clear it */ + out_be64(p->ioc->regs + P7IOC_CIn_LEM_ERR_MASK(ci_idx), + 0xa4f4000000000000ul); + out_be64(p->regs + PHB_LEM_ERROR_MASK, + 0xadb650c9808dd051ul); + out_be64(p->ioc->regs + P7IOC_CIn_LEM_FIR(ci_idx), + 0x0ul); + + /* Disable link to avoid training issues */ + reg = in_be64(p->regs + PHB_PCIE_DLP_TRAIN_CTL); + reg |= PHB_PCIE_DLP_TCTX_DISABLE; + out_be64(p->regs + PHB_PCIE_DLP_TRAIN_CTL, reg); + PHBDBG(p, "Slot power on: disable link training\n"); + + /* Switch to state machine of fundamental reset */ + p->state = P7IOC_PHB_STATE_FRESET_DISABLE_LINK; + p->retries = 12; + return p7ioc_set_sm_timeout(p, msecs_to_tb(10)); + case P7IOC_PHB_STATE_SPUP_STABILIZE_DELAY: + /* Come here after the 2s delay after power up */ + p->retries = 1000; + p->state = P7IOC_PHB_STATE_SPUP_SLOT_STATUS; + PHBDBG(p, "Slot power on: waiting for power\n"); + /* Fall through */ + case P7IOC_PHB_STATE_SPUP_SLOT_STATUS: + reg = in_be64(p->regs + PHB_PCIE_SLOTCTL2); + + /* Doc says to check LED status, but we ignore that, there + * no point really and it's easier that way + */ + if (reg & PHB_PCIE_SLOTCTL2_PWR_EN_STAT) + goto power_ok; + if (p->retries-- == 0) { + /* XXX Improve error logging */ + PHBERR(p, "Timeout powering up slot\n"); + goto error; + } + return p7ioc_set_sm_timeout(p, msecs_to_tb(10)); + default: + break; + } + + /* Unknown state, hardware error ? */ + error: + p->state = P7IOC_PHB_STATE_FUNCTIONAL; + return OPAL_HARDWARE; +} + +static int64_t p7ioc_slot_power_on(struct phb *phb) +{ + struct p7ioc_phb *p = phb_to_p7ioc_phb(phb); + + if (p->state != P7IOC_PHB_STATE_FUNCTIONAL) + return OPAL_BUSY; + + /* run state machine */ + return p7ioc_sm_slot_power_on(p); +} + +/* + * The OS is expected to do fundamental reset after complete + * reset to make sure the PHB could be recovered from the + * fenced state. However, the OS needn't do that explicitly + * since fundamental reset will be done automatically while + * powering on the PHB. + */ +static int64_t p7ioc_complete_reset(struct phb *phb, uint8_t assert) +{ + struct p7ioc_phb *p = phb_to_p7ioc_phb(phb); + struct p7ioc *ioc = p->ioc; + uint64_t val64; + + if (assert == OPAL_ASSERT_RESET) { + if (p->state != P7IOC_PHB_STATE_FUNCTIONAL && + p->state != P7IOC_PHB_STATE_FENCED) + return OPAL_HARDWARE; + + p->flags |= P7IOC_PHB_CFG_BLOCKED; + p7ioc_phb_reset(phb); + + /* + * According to the experiment, we probably still have + * the fenced state with the corresponding PHB in the Fence + * WOF and we need clear that explicitly. Besides, the RGC + * might already have informational error and we should clear + * that explicitly as well. Otherwise, RGC XIVE#0 won't issue + * interrupt any more. + */ + val64 = in_be64(ioc->regs + P7IOC_CHIP_FENCE_WOF); + val64 &= ~PPC_BIT(15 + p->index * 4); + out_be64(ioc->regs + P7IOC_CHIP_FENCE_WOF, val64); + + /* Clear informational error from RGC */ + val64 = in_be64(ioc->regs + P7IOC_RGC_LEM_BASE + P7IOC_LEM_WOF_OFFSET); + val64 &= ~PPC_BIT(18); + out_be64(ioc->regs + P7IOC_RGC_LEM_BASE + P7IOC_LEM_WOF_OFFSET, val64); + val64 = in_be64(ioc->regs + P7IOC_RGC_LEM_BASE + P7IOC_LEM_FIR_OFFSET); + val64 &= ~PPC_BIT(18); + out_be64(ioc->regs + P7IOC_RGC_LEM_BASE + P7IOC_LEM_FIR_OFFSET, val64); + + return p7ioc_sm_slot_power_off(p); + } else { + if (p->state != P7IOC_PHB_STATE_FUNCTIONAL) + return OPAL_HARDWARE; + + return p7ioc_sm_slot_power_on(p); + } + + /* We shouldn't run to here */ + return OPAL_PARAMETER; +} + +/* + * We have to mask errors prior to disabling link training. + * Otherwise it would cause infinite frozen PEs. Also, we + * should have some delay after enabling link training. It's + * the conclusion from experiment and no document mentioned + * it. + */ +static int64_t p7ioc_sm_hot_reset(struct p7ioc_phb *p) +{ + uint64_t reg; + uint32_t cfg32; + uint16_t brctl; + + switch(p->state) { + case P7IOC_PHB_STATE_FUNCTIONAL: + /* If the slot isn't present, we needn't do it */ + reg = in_be64(p->regs + PHB_PCIE_SLOTCTL2); + if (!(reg & PHB_PCIE_SLOTCTL2_PRSTN_STAT)) { + PHBDBG(p, "Slot hot reset: no device\n"); + return OPAL_CLOSED; + } + + /* Mask PCIE port interrupts and AER receiver error */ + out_be64(p->regs + UTL_PCIE_PORT_IRQ_EN, 0x7E00000000000000); + p7ioc_pcicfg_read32(&p->phb, 0, + p->aercap + PCIECAP_AER_CE_MASK, &cfg32); + cfg32 |= PCIECAP_AER_CE_RECVR_ERR; + p7ioc_pcicfg_write32(&p->phb, 0, + p->aercap + PCIECAP_AER_CE_MASK, cfg32); + + /* Disable link to avoid training issues */ + reg = in_be64(p->regs + PHB_PCIE_DLP_TRAIN_CTL); + reg |= PHB_PCIE_DLP_TCTX_DISABLE; + out_be64(p->regs + PHB_PCIE_DLP_TRAIN_CTL, reg); + PHBDBG(p, "Slot hot reset: disable link training\n"); + + p->state = P7IOC_PHB_STATE_HRESET_DISABLE_LINK; + p->retries = 12; + return p7ioc_set_sm_timeout(p, msecs_to_tb(10)); + case P7IOC_PHB_STATE_HRESET_DISABLE_LINK: + reg = in_be64(p->regs + PHB_PCIE_DLP_TRAIN_CTL); + if (reg & PHB_PCIE_DLP_TCRX_DISABLED) { + /* Turn on host reset */ + p7ioc_pcicfg_read16(&p->phb, 0, PCI_CFG_BRCTL, &brctl); + brctl |= PCI_CFG_BRCTL_SECONDARY_RESET; + p7ioc_pcicfg_write16(&p->phb, 0, PCI_CFG_BRCTL, brctl); + PHBDBG(p, "Slot hot reset: assert reset\n"); + + p->state = P7IOC_PHB_STATE_HRESET_DELAY; + return p7ioc_set_sm_timeout(p, secs_to_tb(1)); + } + + if (p->retries-- == 0) { + PHBDBG(p, "Slot hot reset: timeout to disable link training\n"); + return OPAL_HARDWARE; + } + + return p7ioc_set_sm_timeout(p, msecs_to_tb(10)); + case P7IOC_PHB_STATE_HRESET_DELAY: + /* Turn off host reset */ + p7ioc_pcicfg_read16(&p->phb, 0, PCI_CFG_BRCTL, &brctl); + brctl &= ~PCI_CFG_BRCTL_SECONDARY_RESET; + p7ioc_pcicfg_write16(&p->phb, 0, PCI_CFG_BRCTL, brctl); + PHBDBG(p, "Slot hot reset: deassert reset\n"); + + p->state = P7IOC_PHB_STATE_HRESET_ENABLE_LINK; + return p7ioc_set_sm_timeout(p, msecs_to_tb(200)); + case P7IOC_PHB_STATE_HRESET_ENABLE_LINK: + /* Restore link control */ + reg = in_be64(p->regs + PHB_PCIE_DLP_TRAIN_CTL); + reg &= ~PHB_PCIE_DLP_TCTX_DISABLE; + out_be64(p->regs + PHB_PCIE_DLP_TRAIN_CTL, reg); + PHBDBG(p, "Slot hot reset: enable link training\n"); + + p->state = P7IOC_PHB_STATE_HRESET_WAIT_LINK; + p->retries = 100; + return p7ioc_set_sm_timeout(p, msecs_to_tb(10)); + case P7IOC_PHB_STATE_HRESET_WAIT_LINK: + reg = in_be64(p->regs + PHB_PCIE_DLP_TRAIN_CTL); + if (reg & PHB_PCIE_DLP_TC_DL_LINKACT) { + /* + * Clear spurious errors and enable PCIE port + * interrupts + */ + out_be64(p->regs + UTL_PCIE_PORT_STATUS, 0x00E0000000000000); + out_be64(p->regs + UTL_PCIE_PORT_IRQ_EN, 0xFE65000000000000); + + /* Clear AER receiver error status */ + p7ioc_pcicfg_write32(&p->phb, 0, + p->aercap + PCIECAP_AER_CE_STATUS, + PCIECAP_AER_CE_RECVR_ERR); + /* Unmask receiver error status in AER */ + p7ioc_pcicfg_read32(&p->phb, 0, + p->aercap + PCIECAP_AER_CE_MASK, &cfg32); + cfg32 &= ~PCIECAP_AER_CE_RECVR_ERR; + p7ioc_pcicfg_write32(&p->phb, 0, + p->aercap + PCIECAP_AER_CE_MASK, cfg32); + PHBDBG(p, "Slot hot reset: link up!\n"); + + p->state = P7IOC_PHB_STATE_FUNCTIONAL; + p->flags &= ~P7IOC_PHB_CFG_BLOCKED; + return OPAL_SUCCESS; + } + + if (p->retries-- == 0) { + PHBDBG(p, "Slot hot reset: timeout for link up\n"); + goto error; + } + + return p7ioc_set_sm_timeout(p, msecs_to_tb(10)); + default: + break; + } + + /* Unknown state, hardware error ? */ +error: + p->state = P7IOC_PHB_STATE_FUNCTIONAL; + return OPAL_HARDWARE; +} + +static int64_t p7ioc_hot_reset(struct phb *phb) +{ + struct p7ioc_phb *p = phb_to_p7ioc_phb(phb); + + if (p->state != P7IOC_PHB_STATE_FUNCTIONAL) + return OPAL_HARDWARE; + + p->flags |= P7IOC_PHB_CFG_BLOCKED; + return p7ioc_sm_hot_reset(p); +} + +static int64_t p7ioc_poll(struct phb *phb) +{ + struct p7ioc_phb *p = phb_to_p7ioc_phb(phb); + uint64_t now = mftb(); + + if (p->state == P7IOC_PHB_STATE_FUNCTIONAL) + return OPAL_SUCCESS; + + /* Check timer */ + if (p->delay_tgt_tb && + tb_compare(now, p->delay_tgt_tb) == TB_ABEFOREB) + return p->delay_tgt_tb - now; + + /* Expired (or not armed), clear it */ + p->delay_tgt_tb = 0; + + /* Dispatch to the right state machine */ + switch(p->state) { + case P7IOC_PHB_STATE_SPUP_STABILIZE_DELAY: + case P7IOC_PHB_STATE_SPUP_SLOT_STATUS: + return p7ioc_sm_slot_power_on(p); + case P7IOC_PHB_STATE_SPDOWN_STABILIZE_DELAY: + case P7IOC_PHB_STATE_SPDOWN_SLOT_STATUS: + return p7ioc_sm_slot_power_off(p); + case P7IOC_PHB_STATE_FRESET_DISABLE_LINK: + case P7IOC_PHB_STATE_FRESET_ASSERT_DELAY: + case P7IOC_PHB_STATE_FRESET_DEASSERT_DELAY: + case P7IOC_PHB_STATE_FRESET_WAIT_LINK: + return p7ioc_sm_freset(p); + case P7IOC_PHB_STATE_HRESET_DISABLE_LINK: + case P7IOC_PHB_STATE_HRESET_ASSERT: + case P7IOC_PHB_STATE_HRESET_DELAY: + case P7IOC_PHB_STATE_HRESET_ENABLE_LINK: + case P7IOC_PHB_STATE_HRESET_WAIT_LINK: + return p7ioc_sm_hot_reset(p); + default: + break; + } + + /* Unknown state, could be a HW error */ + return OPAL_HARDWARE; +} + +static void p7ioc_eeh_read_phb_status(struct p7ioc_phb *p, + struct OpalIoP7IOCPhbErrorData *stat) +{ + bool locked; + uint16_t tmp16; + unsigned int i; + + memset(stat, 0, sizeof(struct OpalIoP7IOCPhbErrorData)); + + + /* Error data common part */ + stat->common.version = OPAL_PHB_ERROR_DATA_VERSION_1; + stat->common.ioType = OPAL_PHB_ERROR_DATA_TYPE_P7IOC; + stat->common.len = sizeof(struct OpalIoP7IOCPhbErrorData); + + /* + * We read some registers using config space through AIB. + * + * Get to other registers using ASB when possible to get to them + * through a fence if one is present. + * + * Note that the OpalIoP7IOCPhbErrorData has oddities, such as the + * bridge control being 32-bit and the UTL registers being 32-bit + * (which they really are, but they use the top 32-bit of a 64-bit + * register so we need to be a bit careful). + */ + + /* Use ASB to access PCICFG if the PHB has been fenced */ + locked = lock_recursive(&p->lock); + p->flags |= P7IOC_PHB_CFG_USE_ASB; + + /* Grab RC bridge control, make it 32-bit */ + p7ioc_pcicfg_read16(&p->phb, 0, PCI_CFG_BRCTL, &tmp16); + stat->brdgCtl = tmp16; + + /* Grab UTL status registers */ + stat->portStatusReg = hi32(in_be64(p->regs_asb + + UTL_PCIE_PORT_STATUS)); + stat->rootCmplxStatus = hi32(in_be64(p->regs_asb + + UTL_RC_STATUS)); + stat->busAgentStatus = hi32(in_be64(p->regs_asb + + UTL_SYS_BUS_AGENT_STATUS)); + + /* + * Grab various RC PCIe capability registers. All device, slot + * and link status are 16-bit, so we grab the pair control+status + * for each of them + */ + p7ioc_pcicfg_read32(&p->phb, 0, p->ecap + PCICAP_EXP_DEVCTL, + &stat->deviceStatus); + p7ioc_pcicfg_read32(&p->phb, 0, p->ecap + PCICAP_EXP_SLOTCTL, + &stat->slotStatus); + p7ioc_pcicfg_read32(&p->phb, 0, p->ecap + PCICAP_EXP_LCTL, + &stat->linkStatus); + + /* + * I assume those are the standard config space header, cmd & status + * together makes 32-bit. Secondary status is 16-bit so I'll clear + * the top on that one + */ + p7ioc_pcicfg_read32(&p->phb, 0, PCI_CFG_CMD, &stat->devCmdStatus); + p7ioc_pcicfg_read16(&p->phb, 0, PCI_CFG_SECONDARY_STATUS, &tmp16); + stat->devSecStatus = tmp16; + + /* Grab a bunch of AER regs */ + p7ioc_pcicfg_read32(&p->phb, 0, p->aercap + PCIECAP_AER_RERR_STA, + &stat->rootErrorStatus); + p7ioc_pcicfg_read32(&p->phb, 0, p->aercap + PCIECAP_AER_UE_STATUS, + &stat->uncorrErrorStatus); + p7ioc_pcicfg_read32(&p->phb, 0, p->aercap + PCIECAP_AER_CE_STATUS, + &stat->corrErrorStatus); + p7ioc_pcicfg_read32(&p->phb, 0, p->aercap + PCIECAP_AER_HDR_LOG0, + &stat->tlpHdr1); + p7ioc_pcicfg_read32(&p->phb, 0, p->aercap + PCIECAP_AER_HDR_LOG1, + &stat->tlpHdr2); + p7ioc_pcicfg_read32(&p->phb, 0, p->aercap + PCIECAP_AER_HDR_LOG2, + &stat->tlpHdr3); + p7ioc_pcicfg_read32(&p->phb, 0, p->aercap + PCIECAP_AER_HDR_LOG3, + &stat->tlpHdr4); + p7ioc_pcicfg_read32(&p->phb, 0, p->aercap + PCIECAP_AER_SRCID, + &stat->sourceId); + + /* Restore to AIB */ + p->flags &= ~P7IOC_PHB_CFG_USE_ASB; + if (locked) { + unlock(&p->lock); + pci_put_phb(&p->phb); + } + + /* + * No idea what that that is supposed to be, opal.h says + * "Record data about the call to allocate a buffer." + * + * Let's leave them alone for now... + * + * uint64_t errorClass; + * uint64_t correlator; + */ + + /* P7IOC MMIO Error Regs */ + stat->p7iocPlssr = in_be64(p->regs_asb + PHB_CPU_LOADSTORE_STATUS); + stat->p7iocCsr = in_be64(p->regs_asb + PHB_DMA_CHAN_STATUS); + stat->lemFir = in_be64(p->regs_asb + PHB_LEM_FIR_ACCUM); + stat->lemErrorMask = in_be64(p->regs_asb + PHB_LEM_ERROR_MASK); + stat->lemWOF = in_be64(p->regs_asb + PHB_LEM_WOF); + stat->phbErrorStatus = in_be64(p->regs_asb + PHB_ERR_STATUS); + stat->phbFirstErrorStatus = in_be64(p->regs_asb + PHB_ERR1_STATUS); + stat->phbErrorLog0 = in_be64(p->regs_asb + PHB_ERR_LOG_0); + stat->phbErrorLog1 = in_be64(p->regs_asb + PHB_ERR_LOG_1); + stat->mmioErrorStatus = in_be64(p->regs_asb + PHB_OUT_ERR_STATUS); + stat->mmioFirstErrorStatus = in_be64(p->regs_asb + PHB_OUT_ERR1_STATUS); + stat->mmioErrorLog0 = in_be64(p->regs_asb + PHB_OUT_ERR_LOG_0); + stat->mmioErrorLog1 = in_be64(p->regs_asb + PHB_OUT_ERR_LOG_1); + stat->dma0ErrorStatus = in_be64(p->regs_asb + PHB_INA_ERR_STATUS); + stat->dma0FirstErrorStatus = in_be64(p->regs_asb + PHB_INA_ERR1_STATUS); + stat->dma0ErrorLog0 = in_be64(p->regs_asb + PHB_INA_ERR_LOG_0); + stat->dma0ErrorLog1 = in_be64(p->regs_asb + PHB_INA_ERR_LOG_1); + stat->dma1ErrorStatus = in_be64(p->regs_asb + PHB_INB_ERR_STATUS); + stat->dma1FirstErrorStatus = in_be64(p->regs_asb + PHB_INB_ERR1_STATUS); + stat->dma1ErrorLog0 = in_be64(p->regs_asb + PHB_INB_ERR_LOG_0); + stat->dma1ErrorLog1 = in_be64(p->regs_asb + PHB_INB_ERR_LOG_1); + + /* Grab PESTA & B content */ + p7ioc_phb_ioda_sel(p, IODA_TBL_PESTA, 0, true); + for (i = 0; i < OPAL_P7IOC_NUM_PEST_REGS; i++) + stat->pestA[i] = in_be64(p->regs_asb + PHB_IODA_DATA0); + p7ioc_phb_ioda_sel(p, IODA_TBL_PESTB, 0, true); + for (i = 0; i < OPAL_P7IOC_NUM_PEST_REGS; i++) + stat->pestB[i] = in_be64(p->regs_asb + PHB_IODA_DATA0); +} + +static int64_t p7ioc_eeh_freeze_status(struct phb *phb, uint64_t pe_number, + uint8_t *freeze_state, + uint16_t *pci_error_type, + uint16_t *severity, + uint64_t *phb_status) +{ + struct p7ioc_phb *p = phb_to_p7ioc_phb(phb); + uint64_t peev_bit = PPC_BIT(pe_number & 0x3f); + uint64_t peev, pesta, pestb; + + /* Defaults: not frozen */ + *freeze_state = OPAL_EEH_STOPPED_NOT_FROZEN; + *pci_error_type = OPAL_EEH_NO_ERROR; + + /* Check dead */ + if (p->state == P7IOC_PHB_STATE_BROKEN) { + *freeze_state = OPAL_EEH_STOPPED_MMIO_DMA_FREEZE; + *pci_error_type = OPAL_EEH_PHB_ERROR; + if (severity) + *severity = OPAL_EEH_SEV_PHB_DEAD; + goto bail; + } + + /* Check fence */ + if (p7ioc_phb_fenced(p)) { + /* Should be OPAL_EEH_STOPPED_TEMP_UNAVAIL ? */ + *freeze_state = OPAL_EEH_STOPPED_MMIO_DMA_FREEZE; + *pci_error_type = OPAL_EEH_PHB_ERROR; + if (severity) + *severity = OPAL_EEH_SEV_PHB_FENCED; + p->state = P7IOC_PHB_STATE_FENCED; + goto bail; + } + + /* Check the PEEV */ + p7ioc_phb_ioda_sel(p, IODA_TBL_PEEV, 0, true); + peev = in_be64(p->regs + PHB_IODA_DATA0); + if (pe_number > 63) + peev = in_be64(p->regs + PHB_IODA_DATA0); + if (!(peev & peev_bit)) + return OPAL_SUCCESS; + + /* Indicate that we have an ER pending */ + p7ioc_phb_set_err_pending(p, true); + if (severity) + *severity = OPAL_EEH_SEV_PE_ER; + + /* Read the PESTA & PESTB */ + p7ioc_phb_ioda_sel(p, IODA_TBL_PESTA, pe_number, false); + pesta = in_be64(p->regs + PHB_IODA_DATA0); + p7ioc_phb_ioda_sel(p, IODA_TBL_PESTB, pe_number, false); + pestb = in_be64(p->regs + PHB_IODA_DATA0); + + /* Convert them */ + if (pesta & IODA_PESTA_MMIO_FROZEN) + *freeze_state |= OPAL_EEH_STOPPED_MMIO_FREEZE; + if (pestb & IODA_PESTB_DMA_STOPPED) + *freeze_state |= OPAL_EEH_STOPPED_DMA_FREEZE; + + /* XXX Handle more causes */ + if (pesta & IODA_PESTA_MMIO_CAUSE) + *pci_error_type = OPAL_EEH_PE_MMIO_ERROR; + else + *pci_error_type = OPAL_EEH_PE_DMA_ERROR; + + bail: + if (phb_status) + p7ioc_eeh_read_phb_status(p, (struct OpalIoP7IOCPhbErrorData *) + phb_status); + return OPAL_SUCCESS; +} + +static int64_t p7ioc_eeh_next_error(struct phb *phb, uint64_t *first_frozen_pe, + uint16_t *pci_error_type, uint16_t *severity) +{ + struct p7ioc_phb *p = phb_to_p7ioc_phb(phb); + struct p7ioc *ioc = p->ioc; + uint64_t fir, peev0, peev1; + uint32_t cfg32, i; + + /* Check if there're pending errors on the IOC. */ + if (p7ioc_err_pending(ioc) && + p7ioc_check_LEM(ioc, pci_error_type, severity)) + return OPAL_SUCCESS; + + /* Clear result */ + *pci_error_type = OPAL_EEH_NO_ERROR; + *severity = OPAL_EEH_SEV_NO_ERROR; + *first_frozen_pe = (uint64_t)-1; + + /* Check dead */ + if (p->state == P7IOC_PHB_STATE_BROKEN) { + *pci_error_type = OPAL_EEH_PHB_ERROR; + *severity = OPAL_EEH_SEV_PHB_DEAD; + return OPAL_SUCCESS; + } + + /* Check fence */ + if (p7ioc_phb_fenced(p)) { + /* Should be OPAL_EEH_STOPPED_TEMP_UNAVAIL ? */ + *pci_error_type = OPAL_EEH_PHB_ERROR; + *severity = OPAL_EEH_SEV_PHB_FENCED; + p->state = P7IOC_PHB_STATE_FENCED; + p7ioc_phb_set_err_pending(p, false); + return OPAL_SUCCESS; + } + + /* + * If we don't have pending errors, which might be moved + * from IOC to the PHB, then check if there has any frozen PEs. + */ + if (!p7ioc_phb_err_pending(p)) { + p7ioc_phb_ioda_sel(p, IODA_TBL_PEEV, 0, true); + peev0 = in_be64(p->regs + PHB_IODA_DATA0); + peev1 = in_be64(p->regs + PHB_IODA_DATA0); + if (peev0 || peev1) { + p->err.err_src = P7IOC_ERR_SRC_PHB0 + p->index; + p->err.err_class = P7IOC_ERR_CLASS_ER; + p->err.err_bit = 0; + p7ioc_phb_set_err_pending(p, true); + } + } + + /* Check the pending errors, which might come from IOC */ + if (p7ioc_phb_err_pending(p)) { + /* + * If the frozen PE is caused by a malfunctioning TLP, we + * need reset the PHB. So convert ER to PHB-fatal error + * for the case. + */ + if (p->err.err_class == P7IOC_ERR_CLASS_ER) { + fir = in_be64(p->regs_asb + PHB_LEM_FIR_ACCUM); + if (fir & PPC_BIT(60)) { + p7ioc_pcicfg_read32(&p->phb, 0, + p->aercap + PCIECAP_AER_UE_STATUS, &cfg32); + if (cfg32 & PCIECAP_AER_UE_MALFORMED_TLP) + p->err.err_class = P7IOC_ERR_CLASS_PHB; + } + } + + /* + * Map P7IOC internal error class to that one OS can handle. + * For P7IOC_ERR_CLASS_ER, we also need figure out the frozen + * PE. + */ + switch (p->err.err_class) { + case P7IOC_ERR_CLASS_PHB: + *pci_error_type = OPAL_EEH_PHB_ERROR; + *severity = OPAL_EEH_SEV_PHB_FENCED; + p7ioc_phb_set_err_pending(p, false); + break; + case P7IOC_ERR_CLASS_MAL: + case P7IOC_ERR_CLASS_INF: + *pci_error_type = OPAL_EEH_PHB_ERROR; + *severity = OPAL_EEH_SEV_INF; + p7ioc_phb_set_err_pending(p, false); + break; + case P7IOC_ERR_CLASS_ER: + *pci_error_type = OPAL_EEH_PE_ERROR; + *severity = OPAL_EEH_SEV_PE_ER; + p7ioc_phb_ioda_sel(p, IODA_TBL_PEEV, 0, true); + peev0 = in_be64(p->regs + PHB_IODA_DATA0); + peev1 = in_be64(p->regs + PHB_IODA_DATA0); + + for (i = 0 ; i < 64; i++) { + if (PPC_BIT(i) & peev1) { + *first_frozen_pe = i + 64; + break; + } + } + for (i = 0 ; + *first_frozen_pe == (uint64_t)-1 && i < 64; + i++) { + if (PPC_BIT(i) & peev0) { + *first_frozen_pe = i; + break; + } + } + + /* No frozen PE? */ + if (*first_frozen_pe == (uint64_t)-1) { + *pci_error_type = OPAL_EEH_NO_ERROR; + *severity = OPAL_EEH_SEV_NO_ERROR; + p7ioc_phb_set_err_pending(p, false); + } + + break; + default: + *pci_error_type = OPAL_EEH_NO_ERROR; + *severity = OPAL_EEH_SEV_NO_ERROR; + p7ioc_phb_set_err_pending(p, false); + } + } + + return OPAL_SUCCESS; +} + +static void p7ioc_ER_err_clear(struct p7ioc_phb *p) +{ + u64 err, lem; + u32 val; + + /* Rec 1,2 */ + lem = in_be64(p->regs + PHB_LEM_FIR_ACCUM); + + /* Rec 3,4,5 AER registers (could use cfg space accessors) */ + out_be64(p->regs + PHB_CONFIG_ADDRESS, 0x8000001c00000000ull); + out_be32(p->regs + PHB_CONFIG_DATA, 0x10000000); + + /* Rec 6,7,8 XXX DOC whacks payload & req size ... we don't */ + out_be64(p->regs + PHB_CONFIG_ADDRESS, 0x8000005000000000ull); + val = in_be32(p->regs + PHB_CONFIG_DATA); + out_be32(p->regs + PHB_CONFIG_DATA, (val & 0xe0700000) | 0x0f000f00); + + /* Rec 9,10,11 */ + out_be64(p->regs + PHB_CONFIG_ADDRESS, 0x8000010400000000ull); + out_be32(p->regs + PHB_CONFIG_DATA, 0xffffffff); + + /* Rec 12,13,14 */ + out_be64(p->regs + PHB_CONFIG_ADDRESS, 0x8000011000000000ull); + out_be32(p->regs + PHB_CONFIG_DATA, 0xffffffff); + + /* Rec 23,24,25 */ + out_be64(p->regs + PHB_CONFIG_ADDRESS, 0x8000013000000000ull); + out_be32(p->regs + PHB_CONFIG_DATA, 0xffffffff); + + /* Rec 26,27,28 */ + out_be64(p->regs + PHB_CONFIG_ADDRESS, 0x8000004000000000ull); + out_be32(p->regs + PHB_CONFIG_DATA, 0x470100f8); + + /* Rec 29..34 UTL registers */ + err = in_be64(p->regs + UTL_SYS_BUS_AGENT_STATUS); + out_be64(p->regs + UTL_SYS_BUS_AGENT_STATUS, err); + err = in_be64(p->regs + UTL_PCIE_PORT_STATUS); + out_be64(p->regs + UTL_PCIE_PORT_STATUS, err); + err = in_be64(p->regs + UTL_RC_STATUS); + out_be64(p->regs + UTL_RC_STATUS, err); + + /* PHB error traps registers */ + err = in_be64(p->regs + PHB_ERR_STATUS); + out_be64(p->regs + PHB_ERR_STATUS, err); + out_be64(p->regs + PHB_ERR1_STATUS, 0); + out_be64(p->regs + PHB_ERR_LOG_0, 0); + out_be64(p->regs + PHB_ERR_LOG_1, 0); + + err = in_be64(p->regs + PHB_OUT_ERR_STATUS); + out_be64(p->regs + PHB_OUT_ERR_STATUS, err); + out_be64(p->regs + PHB_OUT_ERR1_STATUS, 0); + out_be64(p->regs + PHB_OUT_ERR_LOG_0, 0); + out_be64(p->regs + PHB_OUT_ERR_LOG_1, 0); + + err = in_be64(p->regs + PHB_INA_ERR_STATUS); + out_be64(p->regs + PHB_INA_ERR_STATUS, err); + out_be64(p->regs + PHB_INA_ERR1_STATUS, 0); + out_be64(p->regs + PHB_INA_ERR_LOG_0, 0); + out_be64(p->regs + PHB_INA_ERR_LOG_1, 0); + + err = in_be64(p->regs + PHB_INB_ERR_STATUS); + out_be64(p->regs + PHB_INB_ERR_STATUS, err); + out_be64(p->regs + PHB_INB_ERR1_STATUS, 0); + out_be64(p->regs + PHB_INB_ERR_LOG_0, 0); + out_be64(p->regs + PHB_INB_ERR_LOG_1, 0); + + /* Rec 67, 68 LEM */ + out_be64(p->regs + PHB_LEM_FIR_AND_MASK, ~lem); + out_be64(p->regs + PHB_LEM_WOF, 0); +} + +static int64_t p7ioc_eeh_freeze_clear(struct phb *phb, uint64_t pe_number, + uint64_t eeh_action_token) +{ + struct p7ioc_phb *p = phb_to_p7ioc_phb(phb); + uint64_t peev0, peev1; + + /* XXX Now this is a heavy hammer, coming roughly from the P7IOC doc + * and my old "pseudopal" code. It will need to be refined. In general + * error handling will have to be reviewed and probably done properly + * "from scratch" based on the description in the p7IOC spec. + * + * XXX Additionally, when handling interrupts, we might want to consider + * masking while processing and/or ack'ing interrupt bits etc... + */ + u64 err; + + /* Summary. If nothing, move to clearing the PESTs which can + * contain a freeze state from a previous error or simply set + * explicitly by the user + */ + err = in_be64(p->regs + PHB_ETU_ERR_SUMMARY); + if (err == 0) + goto clear_pest; + + p7ioc_ER_err_clear(p); + + clear_pest: + /* XXX We just clear the whole PESTA for MMIO clear and PESTB + * for DMA clear. We might want to only clear the frozen bit + * as to not clobber the rest of the state. However, we expect + * the state to have been harvested before the clear operations + * so this might not be an issue + */ + if (eeh_action_token & OPAL_EEH_ACTION_CLEAR_FREEZE_MMIO) { + p7ioc_phb_ioda_sel(p, IODA_TBL_PESTA, pe_number, false); + out_be64(p->regs + PHB_IODA_DATA0, 0); + } + if (eeh_action_token & OPAL_EEH_ACTION_CLEAR_FREEZE_DMA) { + p7ioc_phb_ioda_sel(p, IODA_TBL_PESTB, pe_number, false); + out_be64(p->regs + PHB_IODA_DATA0, 0); + } + + /* Update ER pending indication */ + p7ioc_phb_ioda_sel(p, IODA_TBL_PEEV, 0, true); + peev0 = in_be64(p->regs + PHB_IODA_DATA0); + peev1 = in_be64(p->regs + PHB_IODA_DATA0); + if (peev0 || peev1) { + p->err.err_src = P7IOC_ERR_SRC_PHB0 + p->index; + p->err.err_class = P7IOC_ERR_CLASS_ER; + p->err.err_bit = 0; + p7ioc_phb_set_err_pending(p, true); + } else + p7ioc_phb_set_err_pending(p, false); + + return OPAL_SUCCESS; +} + +static int64_t p7ioc_get_diag_data(struct phb *phb, void *diag_buffer, + uint64_t diag_buffer_len) +{ + struct p7ioc_phb *p = phb_to_p7ioc_phb(phb); + struct OpalIoP7IOCPhbErrorData *diag = diag_buffer; + + if (diag_buffer_len < sizeof(struct OpalIoP7IOCPhbErrorData)) + return OPAL_PARAMETER; + + /* Specific error data */ + p7ioc_eeh_read_phb_status(p, diag); + + /* + * We're running to here probably because of errors (MAL + * or INF class) from IOC. For the case, we need clear + * the pending errors and mask the error bit for MAL class + * error. Fortunately, we shouldn't get MAL class error from + * IOC on P7IOC. + */ + if (p7ioc_phb_err_pending(p) && + p->err.err_class == P7IOC_ERR_CLASS_INF && + p->err.err_src >= P7IOC_ERR_SRC_PHB0 && + p->err.err_src <= P7IOC_ERR_SRC_PHB5) { + p7ioc_ER_err_clear(p); + p7ioc_phb_set_err_pending(p, false); + } + + return OPAL_SUCCESS; +} + +/* + * We don't support address remapping now since all M64 + * BARs are sharing on remapping base address. We might + * introduce flag to the PHB in order to trace that. The + * flag allows to be changed for once. It's something to + * do in future. + */ +static int64_t p7ioc_set_phb_mem_window(struct phb *phb, + uint16_t window_type, + uint16_t window_num, + uint64_t base, + uint64_t __unused pci_base, + uint64_t size) +{ + struct p7ioc_phb *p = phb_to_p7ioc_phb(phb); + uint64_t data64; + + switch (window_type) { + case OPAL_IO_WINDOW_TYPE: + case OPAL_M32_WINDOW_TYPE: + return OPAL_UNSUPPORTED; + case OPAL_M64_WINDOW_TYPE: + if (window_num >= 16) + return OPAL_PARAMETER; + /* The base and size should be 16MB aligned */ + if (base & 0xFFFFFF || size & 0xFFFFFF) + return OPAL_PARAMETER; + data64 = p->m64b_cache[window_num]; + data64 = SETFIELD(IODA_M64BT_BASE, data64, base >> 24); + size = (size >> 24); + data64 = SETFIELD(IODA_M64BT_MASK, data64, 0x1000000 - size); + break; + default: + return OPAL_PARAMETER; + } + + /* + * If the M64 BAR hasn't enabled yet, we needn't flush + * the setting to hardware and just keep it to the cache + */ + p->m64b_cache[window_num] = data64; + if (!(data64 & IODA_M64BT_ENABLE)) + return OPAL_SUCCESS; + p7ioc_phb_ioda_sel(p, IODA_TBL_M64BT, window_num, false); + out_be64(p->regs + PHB_IODA_DATA0, data64); + + return OPAL_SUCCESS; +} + +/* + * We can't enable or disable I/O and M32 dynamically, even + * unnecessary. So the function only support M64 BARs. + */ +static int64_t p7ioc_phb_mmio_enable(struct phb *phb, + uint16_t window_type, + uint16_t window_num, + uint16_t enable) +{ + struct p7ioc_phb *p = phb_to_p7ioc_phb(phb); + uint64_t data64, base, mask; + + switch (window_type) { + case OPAL_IO_WINDOW_TYPE: + case OPAL_M32_WINDOW_TYPE: + return OPAL_UNSUPPORTED; + case OPAL_M64_WINDOW_TYPE: + if (window_num >= 16 || + enable >= OPAL_ENABLE_M64_NON_SPLIT) + return OPAL_PARAMETER; + + break; + default: + return OPAL_PARAMETER; + } + + /* + * While enabling one specific M64 BAR, we should have + * the base/size configured correctly. Otherwise, it + * probably incurs fenced AIB. + */ + data64 = p->m64b_cache[window_num]; + if (enable == OPAL_ENABLE_M64_SPLIT) { + base = GETFIELD(IODA_M64BT_BASE, data64); + base = (base << 24); + mask = GETFIELD(IODA_M64BT_MASK, data64); + if (base < p->m64_base || mask == 0x0ul) + return OPAL_PARTIAL; + + data64 |= IODA_M64BT_ENABLE; + } else if (enable == OPAL_DISABLE_M64) { + data64 &= ~IODA_M64BT_ENABLE; + } else + return OPAL_PARAMETER; + + p7ioc_phb_ioda_sel(p, IODA_TBL_M64BT, window_num, false); + out_be64(p->regs + PHB_IODA_DATA0, data64); + p->m64b_cache[window_num] = data64; + + return OPAL_SUCCESS; +} + +static int64_t p7ioc_map_pe_mmio_window(struct phb *phb, uint16_t pe_number, + uint16_t window_type, + uint16_t window_num, + uint16_t segment_num) +{ + struct p7ioc_phb *p = phb_to_p7ioc_phb(phb); + uint64_t tbl, index; + uint64_t *cache; + + if (pe_number > 127) + return OPAL_PARAMETER; + + switch(window_type) { + case OPAL_IO_WINDOW_TYPE: + if (window_num != 0 || segment_num > 127) + return OPAL_PARAMETER; + tbl = IODA_TBL_IODT; + index = segment_num; + cache = &p->iod_cache[index]; + break; + case OPAL_M32_WINDOW_TYPE: + if (window_num != 0 || segment_num > 127) + return OPAL_PARAMETER; + tbl = IODA_TBL_M32DT; + index = segment_num; + cache = &p->m32d_cache[index]; + break; + case OPAL_M64_WINDOW_TYPE: + if (window_num > 15 || segment_num > 7) + return OPAL_PARAMETER; + + tbl = IODA_TBL_M64DT; + index = window_num << 3 | segment_num; + cache = &p->m64d_cache[index]; + break; + default: + return OPAL_PARAMETER; + } + + p7ioc_phb_ioda_sel(p, tbl, index, false); + out_be64(p->regs + PHB_IODA_DATA0, + SETFIELD(IODA_XXDT_PE, 0ull, pe_number)); + + /* Update cache */ + *cache = SETFIELD(IODA_XXDT_PE, 0ull, pe_number); + + return OPAL_SUCCESS; +} + + +static int64_t p7ioc_set_pe(struct phb *phb, uint64_t pe_number, + uint64_t bdfn, uint8_t bus_compare, + uint8_t dev_compare, uint8_t func_compare, + uint8_t pe_action) +{ + struct p7ioc_phb *p = phb_to_p7ioc_phb(phb); + uint64_t pelt; + uint64_t *cache = &p->peltm_cache[pe_number]; + + if (pe_number > 127 || bdfn > 0xffff) + return OPAL_PARAMETER; + if (pe_action != OPAL_MAP_PE && pe_action != OPAL_UNMAP_PE) + return OPAL_PARAMETER; + if (bus_compare > 7) + return OPAL_PARAMETER; + + if (pe_action == OPAL_MAP_PE) { + pelt = SETFIELD(IODA_PELTM_BUS, 0ul, bdfn >> 8); + pelt |= SETFIELD(IODA_PELTM_DEV, 0ul, (bdfn >> 3) & 0x1f); + pelt |= SETFIELD(IODA_PELTM_FUNC, 0ul, bdfn & 0x7); + pelt |= SETFIELD(IODA_PELTM_BUS_VALID, 0ul, bus_compare); + if (dev_compare) + pelt |= IODA_PELTM_DEV_VALID; + if (func_compare) + pelt |= IODA_PELTM_FUNC_VALID; + } else + pelt = 0; + + p7ioc_phb_ioda_sel(p, IODA_TBL_PELTM, pe_number, false); + out_be64(p->regs + PHB_IODA_DATA0, pelt); + + /* Update cache */ + *cache = pelt; + + return OPAL_SUCCESS; +} + + +static int64_t p7ioc_set_peltv(struct phb *phb, uint32_t parent_pe, + uint32_t child_pe, uint8_t state) +{ + struct p7ioc_phb *p = phb_to_p7ioc_phb(phb); + uint32_t reg; + uint64_t mask, peltv; + uint64_t *cache; + if (parent_pe > 127 || child_pe > 127) + return OPAL_PARAMETER; + + cache = (child_pe >> 6) ? &p->peltv_hi_cache[parent_pe] : + &p->peltv_lo_cache[parent_pe]; + reg = (child_pe >> 6) ? PHB_IODA_DATA1 : PHB_IODA_DATA0; + child_pe &= 0x2f; + mask = 1ull << (63 - child_pe); + + p7ioc_phb_ioda_sel(p, IODA_TBL_PELTV, parent_pe, false); + peltv = in_be64(p->regs + reg); + if (state) + peltv |= mask; + else + peltv &= ~mask; + out_be64(p->regs + reg, peltv); + + /* Update cache */ + *cache = peltv; + + return OPAL_SUCCESS; +} + +static int64_t p7ioc_map_pe_dma_window(struct phb *phb, uint16_t pe_number, + uint16_t window_id, uint16_t tce_levels, + uint64_t tce_table_addr, + uint64_t tce_table_size, + uint64_t tce_page_size) +{ + struct p7ioc_phb *p = phb_to_p7ioc_phb(phb); + uint64_t tvt0, tvt1, t, pelt; + uint64_t dma_window_size; + uint64_t *cache_lo, *cache_hi; + + if (pe_number > 127 || window_id > 255 || tce_levels != 1) + return OPAL_PARAMETER; + cache_lo = &p->tve_lo_cache[window_id]; + cache_hi = &p->tve_hi_cache[window_id]; + + /* Encode table size */ + dma_window_size = tce_page_size * (tce_table_size >> 3); + t = ilog2(dma_window_size); + if (t < 27) + return OPAL_PARAMETER; + tvt0 = SETFIELD(IODA_TVT0_TCE_TABLE_SIZE, 0ul, (t - 26)); + + /* Encode TCE page size */ + switch(tce_page_size) { + case 0x1000: /* 4K */ + tvt1 = SETFIELD(IODA_TVT1_IO_PSIZE, 0ul, 1ul); + break; + case 0x10000: /* 64K */ + tvt1 = SETFIELD(IODA_TVT1_IO_PSIZE, 0ul, 5ul); + break; + case 0x1000000: /* 16M */ + tvt1 = SETFIELD(IODA_TVT1_IO_PSIZE, 0ul, 13ul); + break; + case 0x400000000: /* 16G */ + tvt1 = SETFIELD(IODA_TVT1_IO_PSIZE, 0ul, 23ul); + break; + default: + return OPAL_PARAMETER; + } + + /* XXX Hub number ... leave 0 for now */ + + /* Shift in the address. The table address is "off by 4 bits" + * but since the field is itself shifted by 16, we basically + * need to write the address >> 12, which basically boils down + * to writing a 4k page address + */ + tvt0 = SETFIELD(IODA_TVT0_TABLE_ADDR, tvt0, tce_table_addr >> 12); + + /* Read the PE filter info from the PELT-M */ + p7ioc_phb_ioda_sel(p, IODA_TBL_PELTM, pe_number, false); + pelt = in_be64(p->regs + PHB_IODA_DATA0); + + /* Copy in filter bits from PELT */ + tvt0 = SETFIELD(IODA_TVT0_BUS_VALID, tvt0, + GETFIELD(IODA_PELTM_BUS_VALID, pelt)); + tvt0 = SETFIELD(IODA_TVT0_BUS_NUM, tvt0, + GETFIELD(IODA_PELTM_BUS, pelt)); + tvt1 = SETFIELD(IODA_TVT1_DEV_NUM, tvt1, + GETFIELD(IODA_PELTM_DEV, pelt)); + tvt1 = SETFIELD(IODA_TVT1_FUNC_NUM, tvt1, + GETFIELD(IODA_PELTM_FUNC, pelt)); + if (pelt & IODA_PELTM_DEV_VALID) + tvt1 |= IODA_TVT1_DEV_VALID; + if (pelt & IODA_PELTM_FUNC_VALID) + tvt1 |= IODA_TVT1_FUNC_VALID; + tvt1 = SETFIELD(IODA_TVT1_PE_NUM, tvt1, pe_number); + + /* Write the TVE */ + p7ioc_phb_ioda_sel(p, IODA_TBL_TVT, window_id, false); + out_be64(p->regs + PHB_IODA_DATA1, tvt1); + out_be64(p->regs + PHB_IODA_DATA0, tvt0); + + /* Update cache */ + *cache_lo = tvt0; + *cache_hi = tvt1; + + return OPAL_SUCCESS; +} + +static int64_t p7ioc_map_pe_dma_window_real(struct phb *phb __unused, + uint16_t pe_number __unused, + uint16_t dma_window_num __unused, + uint64_t pci_start_addr __unused, + uint64_t pci_mem_size __unused) +{ + /* XXX Not yet implemented (not yet used by Linux) */ + return OPAL_UNSUPPORTED; +} + +static int64_t p7ioc_set_mve(struct phb *phb, uint32_t mve_number, + uint32_t pe_number) +{ + struct p7ioc_phb *p = phb_to_p7ioc_phb(phb); + uint64_t pelt, mve = 0; + uint64_t *cache = &p->mve_cache[mve_number]; + + if (pe_number > 127 || mve_number > 255) + return OPAL_PARAMETER; + + /* Read the PE filter info from the PELT-M */ + p7ioc_phb_ioda_sel(p, IODA_TBL_PELTM, pe_number, false); + pelt = in_be64(p->regs + PHB_IODA_DATA0); + + mve = SETFIELD(IODA_MVT_BUS_VALID, mve, + GETFIELD(IODA_PELTM_BUS_VALID, pelt)); + mve = SETFIELD(IODA_MVT_BUS_NUM, mve, + GETFIELD(IODA_PELTM_BUS, pelt)); + mve = SETFIELD(IODA_MVT_DEV_NUM, mve, + GETFIELD(IODA_PELTM_DEV, pelt)); + mve = SETFIELD(IODA_MVT_FUNC_NUM, mve, + GETFIELD(IODA_PELTM_FUNC, pelt)); + if (pelt & IODA_PELTM_DEV_VALID) + mve |= IODA_MVT_DEV_VALID; + if (pelt & IODA_PELTM_FUNC_VALID) + mve |= IODA_MVT_FUNC_VALID; + mve = SETFIELD(IODA_MVT_PE_NUM, mve, pe_number); + + p7ioc_phb_ioda_sel(p, IODA_TBL_MVT, mve_number, false); + out_be64(p->regs + PHB_IODA_DATA0, mve); + + /* Update cache */ + *cache = mve; + + return OPAL_SUCCESS; +} + +static int64_t p7ioc_set_mve_enable(struct phb *phb, uint32_t mve_number, + uint32_t state) +{ + struct p7ioc_phb *p = phb_to_p7ioc_phb(phb); + uint64_t mve; + uint64_t *cache = &p->mve_cache[mve_number]; + + if (mve_number > 255) + return OPAL_PARAMETER; + + p7ioc_phb_ioda_sel(p, IODA_TBL_MVT, mve_number, false); + mve = in_be64(p->regs + PHB_IODA_DATA0); + if (state) + mve |= IODA_MVT_VALID; + else + mve &= ~IODA_MVT_VALID; + out_be64(p->regs + PHB_IODA_DATA0, mve); + + /* Update cache */ + *cache = mve; + + return OPAL_SUCCESS; +} + +static int64_t p7ioc_set_xive_pe(struct phb *phb, uint32_t pe_number, + uint32_t xive_num) +{ + struct p7ioc_phb *p = phb_to_p7ioc_phb(phb); + uint64_t xive; + + if (pe_number > 127 || xive_num > 255) + return OPAL_PARAMETER; + + /* Update MXIVE cache */ + xive = p->mxive_cache[xive_num]; + xive = SETFIELD(IODA_XIVT_PENUM, xive, pe_number); + p->mxive_cache[xive_num] = xive; + + /* Update HW */ + p7ioc_phb_ioda_sel(p, IODA_TBL_MXIVT, xive_num, false); + xive = in_be64(p->regs + PHB_IODA_DATA0); + xive = SETFIELD(IODA_XIVT_PENUM, xive, pe_number); + out_be64(p->regs + PHB_IODA_DATA0, xive); + + return OPAL_SUCCESS; +} + +static int64_t p7ioc_get_xive_source(struct phb *phb, uint32_t xive_num, + int32_t *interrupt_source_number) +{ + struct p7ioc_phb *p = phb_to_p7ioc_phb(phb); + + if (xive_num > 255 || !interrupt_source_number) + return OPAL_PARAMETER; + + *interrupt_source_number = (p->buid_msi << 4) | xive_num; + + return OPAL_SUCCESS; +} + +static int64_t p7ioc_get_msi_32(struct phb *phb __unused, uint32_t mve_number, + uint32_t xive_num, uint8_t msi_range, + uint32_t *msi_address, uint32_t *message_data) +{ + if (mve_number > 255 || xive_num > 255 || msi_range != 1) + return OPAL_PARAMETER; + + *msi_address = 0xffff0000 | (mve_number << 4); + *message_data = xive_num; + + return OPAL_SUCCESS; +} + +static int64_t p7ioc_get_msi_64(struct phb *phb __unused, uint32_t mve_number, + uint32_t xive_num, uint8_t msi_range, + uint64_t *msi_address, uint32_t *message_data) +{ + if (mve_number > 255 || xive_num > 255 || msi_range != 1) + return OPAL_PARAMETER; + + *msi_address = (9ul << 60) | (((u64)mve_number) << 48); + *message_data = xive_num; + + return OPAL_SUCCESS; +} + +static void p7ioc_root_port_init(struct phb *phb, struct pci_device *dev, + int ecap, int aercap) +{ + uint16_t bdfn = dev->bdfn; + uint16_t val16; + uint32_t val32; + + /* Enable SERR and parity checking */ + pci_cfg_read16(phb, bdfn, PCI_CFG_CMD, &val16); + val16 |= (PCI_CFG_CMD_SERR_EN | PCI_CFG_CMD_PERR_RESP); + pci_cfg_write16(phb, bdfn, PCI_CFG_CMD, val16); + + /* Enable reporting various errors */ + if (!ecap) return; + pci_cfg_read16(phb, bdfn, ecap + PCICAP_EXP_DEVCTL, &val16); + val16 |= (PCICAP_EXP_DEVCTL_CE_REPORT | + PCICAP_EXP_DEVCTL_NFE_REPORT | + PCICAP_EXP_DEVCTL_FE_REPORT | + PCICAP_EXP_DEVCTL_UR_REPORT); + pci_cfg_write16(phb, bdfn, ecap + PCICAP_EXP_DEVCTL, val16); + + /* Mask various unrecoverable errors */ + if (!aercap) return; + pci_cfg_read32(phb, bdfn, aercap + PCIECAP_AER_UE_MASK, &val32); + val32 |= (PCIECAP_AER_UE_MASK_POISON_TLP | + PCIECAP_AER_UE_MASK_COMPL_TIMEOUT | + PCIECAP_AER_UE_MASK_COMPL_ABORT | + PCIECAP_AER_UE_MASK_ECRC); + pci_cfg_write32(phb, bdfn, aercap + PCIECAP_AER_UE_MASK, val32); + + /* Report various unrecoverable errors as fatal errors */ + pci_cfg_read32(phb, bdfn, aercap + PCIECAP_AER_UE_SEVERITY, &val32); + val32 |= (PCIECAP_AER_UE_SEVERITY_DLLP | + PCIECAP_AER_UE_SEVERITY_SURPRISE_DOWN | + PCIECAP_AER_UE_SEVERITY_FLOW_CTL_PROT | + PCIECAP_AER_UE_SEVERITY_UNEXP_COMPL | + PCIECAP_AER_UE_SEVERITY_RECV_OVFLOW | + PCIECAP_AER_UE_SEVERITY_MALFORMED_TLP); + pci_cfg_write32(phb, bdfn, aercap + PCIECAP_AER_UE_SEVERITY, val32); + + /* Mask various recoverable errors */ + pci_cfg_read32(phb, bdfn, aercap + PCIECAP_AER_CE_MASK, &val32); + val32 |= PCIECAP_AER_CE_MASK_ADV_NONFATAL; + pci_cfg_write32(phb, bdfn, aercap + PCIECAP_AER_CE_MASK, val32); + + /* Enable ECRC check */ + pci_cfg_read32(phb, bdfn, aercap + PCIECAP_AER_CAPCTL, &val32); + val32 |= (PCIECAP_AER_CAPCTL_ECRCG_EN | + PCIECAP_AER_CAPCTL_ECRCC_EN); + pci_cfg_write32(phb, bdfn, aercap + PCIECAP_AER_CAPCTL, val32); + + /* Enable all error reporting */ + pci_cfg_read32(phb, bdfn, aercap + PCIECAP_AER_RERR_CMD, &val32); + val32 |= (PCIECAP_AER_RERR_CMD_FE | + PCIECAP_AER_RERR_CMD_NFE | + PCIECAP_AER_RERR_CMD_CE); + pci_cfg_write32(phb, bdfn, aercap + PCIECAP_AER_RERR_CMD, val32); +} + +static void p7ioc_switch_port_init(struct phb *phb, + struct pci_device *dev, + int ecap, int aercap) +{ + uint16_t bdfn = dev->bdfn; + uint16_t val16; + uint32_t val32; + + /* Enable SERR and parity checking and disable INTx */ + pci_cfg_read16(phb, bdfn, PCI_CFG_CMD, &val16); + val16 |= (PCI_CFG_CMD_PERR_RESP | + PCI_CFG_CMD_SERR_EN | + PCI_CFG_CMD_INTx_DIS); + pci_cfg_write16(phb, bdfn, PCI_CFG_CMD, val16); + + /* Disable partity error and enable system error */ + pci_cfg_read16(phb, bdfn, PCI_CFG_BRCTL, &val16); + val16 &= ~PCI_CFG_BRCTL_PERR_RESP_EN; + val16 |= PCI_CFG_BRCTL_SERR_EN; + pci_cfg_write16(phb, bdfn, PCI_CFG_BRCTL, val16); + + /* Enable reporting various errors */ + if (!ecap) return; + pci_cfg_read16(phb, bdfn, ecap + PCICAP_EXP_DEVCTL, &val16); + val16 |= (PCICAP_EXP_DEVCTL_CE_REPORT | + PCICAP_EXP_DEVCTL_NFE_REPORT | + PCICAP_EXP_DEVCTL_FE_REPORT); + pci_cfg_write16(phb, bdfn, ecap + PCICAP_EXP_DEVCTL, val16); + + /* Unmask all unrecoverable errors */ + if (!aercap) return; + pci_cfg_write32(phb, bdfn, aercap + PCIECAP_AER_UE_MASK, 0x0); + + /* Severity of unrecoverable errors */ + if (dev->dev_type == PCIE_TYPE_SWITCH_UPPORT) + val32 = (PCIECAP_AER_UE_SEVERITY_DLLP | + PCIECAP_AER_UE_SEVERITY_SURPRISE_DOWN | + PCIECAP_AER_UE_SEVERITY_FLOW_CTL_PROT | + PCIECAP_AER_UE_SEVERITY_RECV_OVFLOW | + PCIECAP_AER_UE_SEVERITY_MALFORMED_TLP | + PCIECAP_AER_UE_SEVERITY_INTERNAL); + else + val32 = (PCIECAP_AER_UE_SEVERITY_FLOW_CTL_PROT | + PCIECAP_AER_UE_SEVERITY_INTERNAL); + pci_cfg_write32(phb, bdfn, aercap + PCIECAP_AER_UE_SEVERITY, val32); + + /* Mask various correctable errors */ + val32 = PCIECAP_AER_CE_MASK_ADV_NONFATAL; + pci_cfg_write32(phb, bdfn, aercap + PCIECAP_AER_CE_MASK, val32); + + /* Enable ECRC generation and disable ECRC check */ + pci_cfg_read32(phb, bdfn, aercap + PCIECAP_AER_CAPCTL, &val32); + val32 |= PCIECAP_AER_CAPCTL_ECRCG_EN; + val32 &= ~PCIECAP_AER_CAPCTL_ECRCC_EN; + pci_cfg_write32(phb, bdfn, aercap + PCIECAP_AER_CAPCTL, val32); +} + +static void p7ioc_endpoint_init(struct phb *phb, + struct pci_device *dev, + int ecap, int aercap) +{ + uint16_t bdfn = dev->bdfn; + uint16_t val16; + uint32_t val32; + + /* Enable SERR and parity checking */ + pci_cfg_read16(phb, bdfn, PCI_CFG_CMD, &val16); + val16 |= (PCI_CFG_CMD_PERR_RESP | + PCI_CFG_CMD_SERR_EN); + pci_cfg_write16(phb, bdfn, PCI_CFG_CMD, val16); + + /* Enable reporting various errors */ + if (!ecap) return; + pci_cfg_read16(phb, bdfn, ecap + PCICAP_EXP_DEVCTL, &val16); + val16 &= ~PCICAP_EXP_DEVCTL_CE_REPORT; + val16 |= (PCICAP_EXP_DEVCTL_NFE_REPORT | + PCICAP_EXP_DEVCTL_FE_REPORT | + PCICAP_EXP_DEVCTL_UR_REPORT); + pci_cfg_write16(phb, bdfn, ecap + PCICAP_EXP_DEVCTL, val16); + + /* Enable ECRC generation and check */ + pci_cfg_read32(phb, bdfn, aercap + PCIECAP_AER_CAPCTL, &val32); + val32 |= (PCIECAP_AER_CAPCTL_ECRCG_EN | + PCIECAP_AER_CAPCTL_ECRCC_EN); + pci_cfg_write32(phb, bdfn, aercap + PCIECAP_AER_CAPCTL, val32); +} + +static void p7ioc_device_init(struct phb *phb, struct pci_device *dev) +{ + int ecap = 0; + int aercap = 0; + + /* Figure out AER capability */ + if (pci_has_cap(dev, PCI_CFG_CAP_ID_EXP, false)) { + ecap = pci_cap(dev, PCI_CFG_CAP_ID_EXP, false); + + if (!pci_has_cap(dev, PCIECAP_ID_AER, true)) { + aercap = pci_find_ecap(phb, dev->bdfn, + PCIECAP_ID_AER, NULL); + if (aercap > 0) + pci_set_cap(dev, PCIECAP_ID_AER, aercap, true); + } else { + aercap = pci_cap(dev, PCIECAP_ID_AER, true); + } + } + + /* Reconfigure the MPS */ + pci_configure_mps(phb, dev); + + if (dev->dev_type == PCIE_TYPE_ROOT_PORT) + p7ioc_root_port_init(phb, dev, ecap, aercap); + else if (dev->dev_type == PCIE_TYPE_SWITCH_UPPORT || + dev->dev_type == PCIE_TYPE_SWITCH_DNPORT) + p7ioc_switch_port_init(phb, dev, ecap, aercap); + else + p7ioc_endpoint_init(phb, dev, ecap, aercap); +} + +static int64_t p7ioc_pci_reinit(struct phb *phb, + uint64_t scope, uint64_t data) +{ + struct pci_device *pd; + uint16_t bdfn = data; + + if (scope != OPAL_REINIT_PCI_DEV) + return OPAL_PARAMETER; + + pd = pci_find_dev(phb, bdfn); + if (!pd) + return OPAL_PARAMETER; + + p7ioc_device_init(phb, pd); + return OPAL_SUCCESS; +} + +static uint8_t p7ioc_choose_bus(struct phb *phb __unused, + struct pci_device *bridge, + uint8_t candidate, uint8_t *max_bus, + bool *use_max) +{ + uint8_t m, al; + int i; + + /* Bus number selection is nasty on P7IOC. Our EEH HW can only cope + * with bus ranges that are naturally aligned powers of two. It also + * has "issues" with dealing with more than 32 bus numbers. + * + * On the other hand we can deal with overlaps to some extent as + * the PELT-M entries are ordered. + * + * We also don't need to bother with the busses between the upstream + * and downstream ports of switches. + * + * For now we apply this simple mechanism which matche what OFW does + * under OPAL: + * + * - Top level bus (PHB to RC) is 0 + * - RC to first device is 1..ff + * - Then going down, a switch gets (N = parent bus, M = parent max) + * * Upstream bridge is N+1, M, use_max = false + * * Downstream bridge is closest power of two from 32 down and + * * use max + * + * XXX NOTE: If we have access to HW VPDs, we could know whether + * this is a bridge with a single device on it such as IPR and + * limit ourselves to a single bus number. + */ + + /* Default use_max is false (legacy) */ + *use_max = false; + + /* If we are the root complex or we are not in PCIe land anymore, just + * use legacy algorithm + */ + if (!bridge || !pci_has_cap(bridge, PCI_CFG_CAP_ID_EXP, false)) + return candidate; + + /* Figure out the bridge type */ + switch(bridge->dev_type) { + case PCIE_TYPE_PCIX_TO_PCIE: + /* PCI-X to PCIE ... hrm, let's not bother too much with that */ + return candidate; + case PCIE_TYPE_SWITCH_UPPORT: + case PCIE_TYPE_ROOT_PORT: + /* Upstream port, we use legacy handling as well */ + return candidate; + case PCIE_TYPE_SWITCH_DNPORT: + case PCIE_TYPE_PCIE_TO_PCIX: + /* That leaves us with the interesting cases that we handle */ + break; + default: + /* Should not happen, treat as legacy */ + prerror("PCI: Device %04x has unsupported type %d in choose_bus\n", + bridge->bdfn, bridge->dev_type); + return candidate; + } + + /* Ok, let's find a power of two that fits, fallback to 1 */ + for (i = 5; i >= 0; i--) { + m = (1 << i) - 1; + al = (candidate + m) & ~m; + if (al <= *max_bus && (al + m) <= *max_bus) + break; + } + if (i < 0) + return 0; + *use_max = true; + *max_bus = al + m; + return al; +} + +/* p7ioc_phb_init_ioda_cache - Reset the IODA cache values + */ +static void p7ioc_phb_init_ioda_cache(struct p7ioc_phb *p) +{ + unsigned int i; + + for (i = 0; i < 8; i++) + p->lxive_cache[i] = SETFIELD(IODA_XIVT_PRIORITY, 0ull, 0xff); + for (i = 0; i < 256; i++) { + p->mxive_cache[i] = SETFIELD(IODA_XIVT_PRIORITY, 0ull, 0xff); + p->mve_cache[i] = 0; + } + for (i = 0; i < 16; i++) + p->m64b_cache[i] = 0; + + /* + * Since there is only one root port under the PHB, + * We make all PELTM entries except last one to be + * invalid by configuring their RID to 00:00.1. The + * last entry is to encompass all RIDs. + */ + for (i = 0; i < 127; i++) + p->peltm_cache[i] = 0x0001f80000000000; + p->peltm_cache[127] = 0x0ul; + + for (i = 0; i < 128; i++) { + p->peltv_lo_cache[i] = 0; + p->peltv_hi_cache[i] = 0; + p->tve_lo_cache[i] = 0; + p->tve_hi_cache[i] = 0; + p->iod_cache[i] = 0; + p->m32d_cache[i] = 0; + p->m64d_cache[i] = 0; + } +} + +/* p7ioc_phb_ioda_reset - Reset the IODA tables + * + * @purge: If true, the cache is cleared and the cleared values + * are applied to HW. If false, the cached values are + * applied to HW + * + * This reset the IODA tables in the PHB. It is called at + * initialization time, on PHB reset, and can be called + * explicitly from OPAL + */ +static int64_t p7ioc_ioda_reset(struct phb *phb, bool purge) +{ + struct p7ioc_phb *p = phb_to_p7ioc_phb(phb); + unsigned int i; + uint64_t reg64; + uint64_t data64, data64_hi; + uint8_t prio; + uint16_t server; + uint64_t m_server, m_prio; + + /* If the "purge" argument is set, we clear the table cache */ + if (purge) + p7ioc_phb_init_ioda_cache(p); + + /* Init_18..19: Setup the HRT + * + * XXX NOTE: I still don't completely get that HRT business so + * I'll just mimmic BML and put the PHB number + 1 in there + */ + p7ioc_phb_ioda_sel(p, IODA_TBL_HRT, 0, true); + out_be64(p->regs + PHB_IODA_DATA0, p->index + 1); + out_be64(p->regs + PHB_IODA_DATA0, p->index + 1); + out_be64(p->regs + PHB_IODA_DATA0, p->index + 1); + out_be64(p->regs + PHB_IODA_DATA0, p->index + 1); + + /* Init_20..21: Cleanup the LXIVT + * + * We set the priority to FF (masked) and clear everything + * else. That means we leave the HRT index to 0 which is + * going to remain unmodified... for now. + */ + p7ioc_phb_ioda_sel(p, IODA_TBL_LXIVT, 0, true); + for (i = 0; i < 8; i++) { + data64 = p->lxive_cache[i]; + server = GETFIELD(IODA_XIVT_SERVER, data64); + prio = GETFIELD(IODA_XIVT_PRIORITY, data64); + + /* Now we mangle the server and priority */ + if (prio == 0xff) { + m_server = 0; + m_prio = 0xff; + } else { + m_server = server >> 3; + m_prio = (prio >> 3) | ((server & 7) << 5); + } + + data64 = SETFIELD(IODA_XIVT_SERVER, data64, m_server); + data64 = SETFIELD(IODA_XIVT_PRIORITY, data64, m_prio); + out_be64(p->regs + PHB_IODA_DATA0, data64); + } + + /* Init_22..23: Cleanup the MXIVT + * + * We set the priority to FF (masked) and clear everything + * else. That means we leave the HRT index to 0 which is + * going to remain unmodified... for now. + */ + p7ioc_phb_ioda_sel(p, IODA_TBL_MXIVT, 0, true); + for (i = 0; i < 256; i++) { + data64 = p->mxive_cache[i]; + server = GETFIELD(IODA_XIVT_SERVER, data64); + prio = GETFIELD(IODA_XIVT_PRIORITY, data64); + + /* Now we mangle the server and priority */ + if (prio == 0xff) { + m_server = 0; + m_prio = 0xff; + } else { + m_server = server >> 3; + m_prio = (prio >> 3) | ((server & 7) << 5); + } + + data64 = SETFIELD(IODA_XIVT_SERVER, data64, m_server); + data64 = SETFIELD(IODA_XIVT_PRIORITY, data64, m_prio); + out_be64(p->regs + PHB_IODA_DATA0, data64); + } + + /* Init_24..25: Cleanup the MVT */ + p7ioc_phb_ioda_sel(p, IODA_TBL_MVT, 0, true); + for (i = 0; i < 256; i++) { + data64 = p->mve_cache[i]; + out_be64(p->regs + PHB_IODA_DATA0, data64); + } + + /* Init_26..27: Cleanup the PELTM + * + * A completely clear PELTM should make everything match PE 0 + */ + p7ioc_phb_ioda_sel(p, IODA_TBL_PELTM, 0, true); + for (i = 0; i < 127; i++) { + data64 = p->peltm_cache[i]; + out_be64(p->regs + PHB_IODA_DATA0, data64); + } + + /* Init_28..30: Cleanup the PELTV */ + p7ioc_phb_ioda_sel(p, IODA_TBL_PELTV, 0, true); + for (i = 0; i < 127; i++) { + data64 = p->peltv_lo_cache[i]; + data64_hi = p->peltv_hi_cache[i]; + out_be64(p->regs + PHB_IODA_DATA1, data64_hi); + out_be64(p->regs + PHB_IODA_DATA0, data64); + } + + /* Init_31..33: Cleanup the TVT */ + p7ioc_phb_ioda_sel(p, IODA_TBL_TVT, 0, true); + for (i = 0; i < 127; i++) { + data64 = p->tve_lo_cache[i]; + data64_hi = p->tve_hi_cache[i]; + out_be64(p->regs + PHB_IODA_DATA1, data64_hi); + out_be64(p->regs + PHB_IODA_DATA0, data64); + } + + /* Init_34..35: Cleanup the M64BT + * + * We don't enable M64 BARs by default. However, + * we shouldn't purge the hw and cache for it in + * future. + */ + p7ioc_phb_ioda_sel(p, IODA_TBL_M64BT, 0, true); + for (i = 0; i < 16; i++) + out_be64(p->regs + PHB_IODA_DATA0, 0); + + /* Init_36..37: Cleanup the IODT */ + p7ioc_phb_ioda_sel(p, IODA_TBL_IODT, 0, true); + for (i = 0; i < 127; i++) { + data64 = p->iod_cache[i]; + out_be64(p->regs + PHB_IODA_DATA0, data64); + } + + /* Init_38..39: Cleanup the M32DT */ + p7ioc_phb_ioda_sel(p, IODA_TBL_M32DT, 0, true); + for (i = 0; i < 127; i++) { + data64 = p->m32d_cache[i]; + out_be64(p->regs + PHB_IODA_DATA0, data64); + } + + /* Init_40..41: Cleanup the M64DT */ + p7ioc_phb_ioda_sel(p, IODA_TBL_M64BT, 0, true); + for (i = 0; i < 16; i++) { + data64 = p->m64b_cache[i]; + out_be64(p->regs + PHB_IODA_DATA0, data64); + } + + p7ioc_phb_ioda_sel(p, IODA_TBL_M64DT, 0, true); + for (i = 0; i < 127; i++) { + data64 = p->m64d_cache[i]; + out_be64(p->regs + PHB_IODA_DATA0, data64); + } + + /* Clear up the TCE cache */ + reg64 = in_be64(p->regs + PHB_PHB2_CONFIG); + reg64 &= ~PHB_PHB2C_64B_TCE_EN; + out_be64(p->regs + PHB_PHB2_CONFIG, reg64); + reg64 |= PHB_PHB2C_64B_TCE_EN; + out_be64(p->regs + PHB_PHB2_CONFIG, reg64); + in_be64(p->regs + PHB_PHB2_CONFIG); + + /* Clear PEST & PEEV */ + for (i = 0; i < OPAL_P7IOC_NUM_PEST_REGS; i++) { + uint64_t pesta, pestb; + + p7ioc_phb_ioda_sel(p, IODA_TBL_PESTA, i, false); + pesta = in_be64(p->regs + PHB_IODA_DATA0); + out_be64(p->regs + PHB_IODA_DATA0, 0); + p7ioc_phb_ioda_sel(p, IODA_TBL_PESTB, i, false); + pestb = in_be64(p->regs + PHB_IODA_DATA0); + out_be64(p->regs + PHB_IODA_DATA0, 0); + + if ((pesta & IODA_PESTA_MMIO_FROZEN) || + (pestb & IODA_PESTB_DMA_STOPPED)) + PHBDBG(p, "Frozen PE#%d (%s - %s)\n", + i, (pestb & IODA_PESTB_DMA_STOPPED) ? "DMA" : "", + (pesta & IODA_PESTA_MMIO_FROZEN) ? "MMIO" : ""); + } + + p7ioc_phb_ioda_sel(p, IODA_TBL_PEEV, 0, true); + for (i = 0; i < 2; i++) + out_be64(p->regs + PHB_IODA_DATA0, 0); + + return OPAL_SUCCESS; +} + +static const struct phb_ops p7ioc_phb_ops = { + .lock = p7ioc_phb_lock, + .unlock = p7ioc_phb_unlock, + .cfg_read8 = p7ioc_pcicfg_read8, + .cfg_read16 = p7ioc_pcicfg_read16, + .cfg_read32 = p7ioc_pcicfg_read32, + .cfg_write8 = p7ioc_pcicfg_write8, + .cfg_write16 = p7ioc_pcicfg_write16, + .cfg_write32 = p7ioc_pcicfg_write32, + .choose_bus = p7ioc_choose_bus, + .device_init = p7ioc_device_init, + .pci_reinit = p7ioc_pci_reinit, + .eeh_freeze_status = p7ioc_eeh_freeze_status, + .eeh_freeze_clear = p7ioc_eeh_freeze_clear, + .get_diag_data = NULL, + .get_diag_data2 = p7ioc_get_diag_data, + .next_error = p7ioc_eeh_next_error, + .phb_mmio_enable = p7ioc_phb_mmio_enable, + .set_phb_mem_window = p7ioc_set_phb_mem_window, + .map_pe_mmio_window = p7ioc_map_pe_mmio_window, + .set_pe = p7ioc_set_pe, + .set_peltv = p7ioc_set_peltv, + .map_pe_dma_window = p7ioc_map_pe_dma_window, + .map_pe_dma_window_real = p7ioc_map_pe_dma_window_real, + .set_mve = p7ioc_set_mve, + .set_mve_enable = p7ioc_set_mve_enable, + .set_xive_pe = p7ioc_set_xive_pe, + .get_xive_source = p7ioc_get_xive_source, + .get_msi_32 = p7ioc_get_msi_32, + .get_msi_64 = p7ioc_get_msi_64, + .ioda_reset = p7ioc_ioda_reset, + .presence_detect = p7ioc_presence_detect, + .link_state = p7ioc_link_state, + .power_state = p7ioc_power_state, + .slot_power_off = p7ioc_slot_power_off, + .slot_power_on = p7ioc_slot_power_on, + .complete_reset = p7ioc_complete_reset, + .hot_reset = p7ioc_hot_reset, + .fundamental_reset = p7ioc_freset, + .poll = p7ioc_poll, +}; + +/* p7ioc_phb_get_xive - Interrupt control from OPAL */ +static int64_t p7ioc_msi_get_xive(void *data, uint32_t isn, + uint16_t *server, uint8_t *prio) +{ + struct p7ioc_phb *p = data; + uint32_t irq, fbuid = P7_IRQ_FBUID(isn); + uint64_t xive; + + if (fbuid < p->buid_msi || fbuid >= (p->buid_msi + 0x10)) + return OPAL_PARAMETER; + + irq = isn & 0xff; + xive = p->mxive_cache[irq]; + + *server = GETFIELD(IODA_XIVT_SERVER, xive); + *prio = GETFIELD(IODA_XIVT_PRIORITY, xive); + + return OPAL_SUCCESS; +} + +/* p7ioc_phb_set_xive - Interrupt control from OPAL */ +static int64_t p7ioc_msi_set_xive(void *data, uint32_t isn, + uint16_t server, uint8_t prio) +{ + struct p7ioc_phb *p = data; + uint32_t irq, fbuid = P7_IRQ_FBUID(isn); + uint64_t xive, m_server, m_prio; + + if (fbuid < p->buid_msi || fbuid >= (p->buid_msi + 0x10)) + return OPAL_PARAMETER; + + /* We cache the arguments because we have to mangle + * it in order to hijack 3 bits of priority to extend + * the server number + */ + irq = isn & 0xff; + xive = p->mxive_cache[irq]; + xive = SETFIELD(IODA_XIVT_SERVER, xive, server); + xive = SETFIELD(IODA_XIVT_PRIORITY, xive, prio); + p->mxive_cache[irq] = xive; + + /* Now we mangle the server and priority */ + if (prio == 0xff) { + m_server = 0; + m_prio = 0xff; + } else { + m_server = server >> 3; + m_prio = (prio >> 3) | ((server & 7) << 5); + } + + /* We use HRT entry 0 always for now */ + p7ioc_phb_ioda_sel(p, IODA_TBL_MXIVT, irq, false); + xive = in_be64(p->regs + PHB_IODA_DATA0); + xive = SETFIELD(IODA_XIVT_SERVER, xive, m_server); + xive = SETFIELD(IODA_XIVT_PRIORITY, xive, m_prio); + out_be64(p->regs + PHB_IODA_DATA0, xive); + + return OPAL_SUCCESS; +} + +/* p7ioc_phb_get_xive - Interrupt control from OPAL */ +static int64_t p7ioc_lsi_get_xive(void *data, uint32_t isn, + uint16_t *server, uint8_t *prio) +{ + struct p7ioc_phb *p = data; + uint32_t irq = (isn & 0x7); + uint32_t fbuid = P7_IRQ_FBUID(isn); + uint64_t xive; + + if (fbuid != p->buid_lsi) + return OPAL_PARAMETER; + + xive = p->lxive_cache[irq]; + *server = GETFIELD(IODA_XIVT_SERVER, xive); + *prio = GETFIELD(IODA_XIVT_PRIORITY, xive); + + return OPAL_SUCCESS; +} + +/* p7ioc_phb_set_xive - Interrupt control from OPAL */ +static int64_t p7ioc_lsi_set_xive(void *data, uint32_t isn, + uint16_t server, uint8_t prio) +{ + struct p7ioc_phb *p = data; + uint32_t irq = (isn & 0x7); + uint32_t fbuid = P7_IRQ_FBUID(isn); + uint64_t xive, m_server, m_prio; + + if (fbuid != p->buid_lsi) + return OPAL_PARAMETER; + + xive = SETFIELD(IODA_XIVT_SERVER, 0ull, server); + xive = SETFIELD(IODA_XIVT_PRIORITY, xive, prio); + + /* + * We cache the arguments because we have to mangle + * it in order to hijack 3 bits of priority to extend + * the server number + */ + p->lxive_cache[irq] = xive; + + /* Now we mangle the server and priority */ + if (prio == 0xff) { + m_server = 0; + m_prio = 0xff; + } else { + m_server = server >> 3; + m_prio = (prio >> 3) | ((server & 7) << 5); + } + + /* We use HRT entry 0 always for now */ + p7ioc_phb_ioda_sel(p, IODA_TBL_LXIVT, irq, false); + xive = in_be64(p->regs + PHB_IODA_DATA0); + xive = SETFIELD(IODA_XIVT_SERVER, xive, m_server); + xive = SETFIELD(IODA_XIVT_PRIORITY, xive, m_prio); + out_be64(p->regs + PHB_IODA_DATA0, xive); + + return OPAL_SUCCESS; +} + +static void p7ioc_phb_err_interrupt(void *data, uint32_t isn) +{ + struct p7ioc_phb *p = data; + uint64_t peev0, peev1; + + PHBDBG(p, "Got interrupt 0x%04x\n", isn); + + opal_update_pending_evt(OPAL_EVENT_PCI_ERROR, OPAL_EVENT_PCI_ERROR); + + /* If the PHB is broken, go away */ + if (p->state == P7IOC_PHB_STATE_BROKEN) + return; + + /* + * Check if there's an error pending and update PHB fence + * state and return, the ER error is drowned at this point + */ + lock(&p->lock); + if (p7ioc_phb_fenced(p)) { + p->state = P7IOC_PHB_STATE_FENCED; + PHBERR(p, "ER error ignored, PHB fenced\n"); + unlock(&p->lock); + return; + } + + /* + * If we already had pending errors, which might be + * moved from IOC, then we needn't check PEEV to avoid + * overwriting the errors from IOC. + */ + if (!p7ioc_phb_err_pending(p)) { + unlock(&p->lock); + return; + } + + /* + * We don't have pending errors from IOC, it's safe + * to check PEEV for frozen PEs. + */ + p7ioc_phb_ioda_sel(p, IODA_TBL_PEEV, 0, true); + peev0 = in_be64(p->regs + PHB_IODA_DATA0); + peev1 = in_be64(p->regs + PHB_IODA_DATA0); + if (peev0 || peev1) { + p->err.err_src = P7IOC_ERR_SRC_PHB0 + p->index; + p->err.err_class = P7IOC_ERR_CLASS_ER; + p->err.err_bit = 0; + p7ioc_phb_set_err_pending(p, true); + } + unlock(&p->lock); +} + +/* MSIs (OS owned) */ +static const struct irq_source_ops p7ioc_msi_irq_ops = { + .get_xive = p7ioc_msi_get_xive, + .set_xive = p7ioc_msi_set_xive, +}; + +/* LSIs (OS owned) */ +static const struct irq_source_ops p7ioc_lsi_irq_ops = { + .get_xive = p7ioc_lsi_get_xive, + .set_xive = p7ioc_lsi_set_xive, +}; + +/* PHB Errors (Ski owned) */ +static const struct irq_source_ops p7ioc_phb_err_irq_ops = { + .get_xive = p7ioc_lsi_get_xive, + .set_xive = p7ioc_lsi_set_xive, + .interrupt = p7ioc_phb_err_interrupt, +}; + +static void p7ioc_pcie_add_node(struct p7ioc_phb *p) +{ + + uint64_t reg[2], iob, m32b, m64b, tkill; + uint32_t lsibase, icsp = get_ics_phandle(); + struct dt_node *np; + + reg[0] = cleanup_addr((uint64_t)p->regs); + reg[1] = 0x100000; + + np = dt_new_addr(p->ioc->dt_node, "pciex", reg[0]); + if (!np) + return; + + p->phb.dt_node = np; + dt_add_property_strings(np, "compatible", "ibm,p7ioc-pciex", + "ibm,ioda-phb"); + dt_add_property_strings(np, "device_type", "pciex"); + dt_add_property(np, "reg", reg, sizeof(reg)); + dt_add_property_cells(np, "#address-cells", 3); + dt_add_property_cells(np, "#size-cells", 2); + dt_add_property_cells(np, "#interrupt-cells", 1); + dt_add_property_cells(np, "bus-range", 0, 0xff); + dt_add_property_cells(np, "clock-frequency", 0x200, 0); /* ??? */ + dt_add_property_cells(np, "interrupt-parent", icsp); + /* XXX FIXME: add slot-name */ + //dt_property_cell("bus-width", 8); /* Figure it out from VPD ? */ + + /* "ranges", we only expose IO and M32 + * + * Note: The kernel expects us to have chopped of 64k from the + * M32 size (for the 32-bit MSIs). If we don't do that, it will + * get confused (OPAL does it) + */ + iob = cleanup_addr(p->io_base); + m32b = cleanup_addr(p->m32_base + M32_PCI_START); + dt_add_property_cells(np, "ranges", + /* IO space */ + 0x01000000, 0x00000000, 0x00000000, + hi32(iob), lo32(iob), 0, PHB_IO_SIZE, + /* M32 space */ + 0x02000000, 0x00000000, M32_PCI_START, + hi32(m32b), lo32(m32b), 0,M32_PCI_SIZE - 0x10000); + + /* XXX FIXME: add opal-memwin32, dmawins, etc... */ + m64b = cleanup_addr(p->m64_base); + dt_add_property_cells(np, "ibm,opal-m64-window", + hi32(m64b), lo32(m64b), + hi32(m64b), lo32(m64b), + hi32(PHB_M64_SIZE), lo32(PHB_M64_SIZE)); + dt_add_property_cells(np, "ibm,opal-msi-ports", 256); + dt_add_property_cells(np, "ibm,opal-num-pes", 128); + dt_add_property_cells(np, "ibm,opal-reserved-pe", 127); + dt_add_property_cells(np, "ibm,opal-msi-ranges", + p->buid_msi << 4, 0x100); + tkill = reg[0] + PHB_TCE_KILL; + dt_add_property_cells(np, "ibm,opal-tce-kill", + hi32(tkill), lo32(tkill)); + + /* Add associativity properties */ + add_chip_dev_associativity(np); + + /* The interrupt maps will be generated in the RC node by the + * PCI code based on the content of this structure: + */ + lsibase = p->buid_lsi << 4; + p->phb.lstate.int_size = 1; + p->phb.lstate.int_val[0][0] = lsibase + PHB_LSI_PCIE_INTA; + p->phb.lstate.int_val[1][0] = lsibase + PHB_LSI_PCIE_INTB; + p->phb.lstate.int_val[2][0] = lsibase + PHB_LSI_PCIE_INTC; + p->phb.lstate.int_val[3][0] = lsibase + PHB_LSI_PCIE_INTD; + p->phb.lstate.int_parent[0] = icsp; + p->phb.lstate.int_parent[1] = icsp; + p->phb.lstate.int_parent[2] = icsp; + p->phb.lstate.int_parent[3] = icsp; +} + +/* p7ioc_phb_setup - Setup a p7ioc_phb data structure + * + * WARNING: This is called before the AIB register routing is + * established. If this wants to access PHB registers, it must + * use the ASB hard coded variant (slower) + */ +void p7ioc_phb_setup(struct p7ioc *ioc, uint8_t index) +{ + struct p7ioc_phb *p = &ioc->phbs[index]; + unsigned int buid_base = ioc->buid_base + PHBn_BUID_BASE(index); + + p->index = index; + p->ioc = ioc; + p->gen = 2; /* Operate in Gen2 mode by default */ + p->phb.ops = &p7ioc_phb_ops; + p->phb.phb_type = phb_type_pcie_v2; + p->regs_asb = ioc->regs + PHBn_ASB_BASE(index); + p->regs = ioc->regs + PHBn_AIB_BASE(index); + p->buid_lsi = buid_base + PHB_BUID_LSI_OFFSET; + p->buid_msi = buid_base + PHB_BUID_MSI_OFFSET; + p->io_base = ioc->mmio1_win_start + PHBn_IO_BASE(index); + p->m32_base = ioc->mmio2_win_start + PHBn_M32_BASE(index); + p->m64_base = ioc->mmio2_win_start + PHBn_M64_BASE(index); + p->state = P7IOC_PHB_STATE_UNINITIALIZED; + p->phb.scan_map = 0x1; /* Only device 0 to scan */ + + /* Find P7IOC base location code in IOC */ + p->phb.base_loc_code = dt_prop_get_def(ioc->dt_node, + "ibm,io-base-loc-code", NULL); + if (!p->phb.base_loc_code) + prerror("P7IOC: Base location code not found !\n"); + + /* Create device node for PHB */ + p7ioc_pcie_add_node(p); + + /* Register OS interrupt sources */ + register_irq_source(&p7ioc_msi_irq_ops, p, p->buid_msi << 4, 256); + register_irq_source(&p7ioc_lsi_irq_ops, p, p->buid_lsi << 4, 4); + + /* Register internal interrupt source (LSI 7) */ + register_irq_source(&p7ioc_phb_err_irq_ops, p, + (p->buid_lsi << 4) + PHB_LSI_PCIE_ERROR, 1); + + /* Initialize IODA table caches */ + p7ioc_phb_init_ioda_cache(p); + + /* We register the PHB before we initialize it so we + * get a useful OPAL ID for it + */ + pci_register_phb(&p->phb); + + /* Platform additional setup */ + if (platform.pci_setup_phb) + platform.pci_setup_phb(&p->phb, p->index); +} + +static bool p7ioc_phb_wait_dlp_reset(struct p7ioc_phb *p) +{ + unsigned int i; + uint64_t val; + + /* + * Firmware cannot access the UTL core regs or PCI config space + * until the cores are out of DL_PGRESET. + * DL_PGRESET should be polled until it is inactive with a value + * of '0'. The recommended polling frequency is once every 1ms. + * Firmware should poll at least 200 attempts before giving up. + * MMIO Stores to the link are silently dropped by the UTL core if + * the link is down. + * MMIO Loads to the link will be dropped by the UTL core and will + * eventually time-out and will return an all ones response if the + * link is down. + */ +#define DLP_RESET_ATTEMPTS 400 + + printf("P7IOC: Waiting for DLP PG reset to complete...\n"); + for (i = 0; i < DLP_RESET_ATTEMPTS; i++) { + val = in_be64(p->regs + PHB_PCIE_DLP_TRAIN_CTL); + if (!(val & PHB_PCIE_DLP_TC_DL_PGRESET)) + break; + time_wait_ms(1); + } + if (val & PHB_PCIE_DLP_TC_DL_PGRESET) { + PHBERR(p, "Timeout waiting for DLP PG reset !\n"); + return false; + } + return true; +} + +/* p7ioc_phb_init_rc - Initialize the Root Complex config space + */ +static bool p7ioc_phb_init_rc_cfg(struct p7ioc_phb *p) +{ + int64_t ecap, aercap; + + /* XXX Handle errors ? */ + + /* Init_51..51: + * + * Set primary bus to 0, secondary to 1 and subordinate to 0xff + */ + p7ioc_pcicfg_write32(&p->phb, 0, PCI_CFG_PRIMARY_BUS, 0x00ff0100); + + /* Init_52..57 + * + * IO and Memory base & limits are set to base > limit, which + * allows all inbounds. + * + * XXX This has the potential of confusing the OS which might + * think that nothing is forwarded downstream. We probably need + * to fix this to match the IO and M32 PHB windows + */ + p7ioc_pcicfg_write16(&p->phb, 0, PCI_CFG_IO_BASE, 0x0010); + p7ioc_pcicfg_write32(&p->phb, 0, PCI_CFG_MEM_BASE, 0x00000010); + p7ioc_pcicfg_write32(&p->phb, 0, PCI_CFG_PREF_MEM_BASE, 0x00000010); + + /* Init_58..: Setup bridge control to enable forwarding of CORR, FATAL, + * and NONFATAL errors + */ + p7ioc_pcicfg_write16(&p->phb, 0, PCI_CFG_BRCTL, PCI_CFG_BRCTL_SERR_EN); + + /* Init_60..61 + * + * PCIE Device control/status, enable error reporting, disable relaxed + * ordering, set MPS to 128 (see note), clear errors. + * + * Note: The doc recommends to set MPS to 4K. This has proved to have + * some issues as it requires specific claming of MRSS on devices and + * we've found devices in the field that misbehave when doing that. + * + * We currently leave it all to 128 bytes (minimum setting) at init + * time. The generic PCIe probing later on might apply a different + * value, or the kernel will, but we play it safe at early init + */ + if (p->ecap <= 0) { + ecap = pci_find_cap(&p->phb, 0, PCI_CFG_CAP_ID_EXP); + if (ecap < 0) { + PHBERR(p, "Can't locate PCI-E capability\n"); + return false; + } + p->ecap = ecap; + } else { + ecap = p->ecap; + } + + p7ioc_pcicfg_write16(&p->phb, 0, ecap + PCICAP_EXP_DEVSTAT, + PCICAP_EXP_DEVSTAT_CE | + PCICAP_EXP_DEVSTAT_NFE | + PCICAP_EXP_DEVSTAT_FE | + PCICAP_EXP_DEVSTAT_UE); + + p7ioc_pcicfg_write16(&p->phb, 0, ecap + PCICAP_EXP_DEVCTL, + PCICAP_EXP_DEVCTL_CE_REPORT | + PCICAP_EXP_DEVCTL_NFE_REPORT | + PCICAP_EXP_DEVCTL_FE_REPORT | + PCICAP_EXP_DEVCTL_UR_REPORT | + SETFIELD(PCICAP_EXP_DEVCTL_MPS, 0, PCIE_MPS_128B)); + + /* Init_62..63 + * + * Root Control Register. Enable error reporting + * + * Note: Added CRS visibility. + */ + p7ioc_pcicfg_write16(&p->phb, 0, ecap + PCICAP_EXP_RC, + PCICAP_EXP_RC_SYSERR_ON_CE | + PCICAP_EXP_RC_SYSERR_ON_NFE | + PCICAP_EXP_RC_SYSERR_ON_FE | + PCICAP_EXP_RC_CRS_VISIBLE); + + /* Init_64..65 + * + * Device Control 2. Enable ARI fwd, set timer + */ + p7ioc_pcicfg_write16(&p->phb, 0, ecap + PCICAP_EXP_DCTL2, + SETFIELD(PCICAP_EXP_DCTL2_CMPTOUT, 0, 2) | + PCICAP_EXP_DCTL2_ARI_FWD); + + /* Init_66..81 + * + * AER inits + */ + aercap = pci_find_ecap(&p->phb, 0, PCIECAP_ID_AER, NULL); + if (aercap < 0) { + /* Shouldn't happen */ + PHBERR(p, "Failed to locate AER capability in bridge\n"); + return false; + } + p->aercap = aercap; + + /* Clear all UE status */ + p7ioc_pcicfg_write32(&p->phb, 0, aercap + PCIECAP_AER_UE_STATUS, + 0xffffffff); + /* Disable some error reporting as per the P7IOC spec */ + p7ioc_pcicfg_write32(&p->phb, 0, aercap + PCIECAP_AER_UE_MASK, + PCIECAP_AER_UE_POISON_TLP | + PCIECAP_AER_UE_COMPL_TIMEOUT | + PCIECAP_AER_UE_COMPL_ABORT | + PCIECAP_AER_UE_ECRC); + /* Report some errors as fatal */ + p7ioc_pcicfg_write32(&p->phb, 0, aercap + PCIECAP_AER_UE_SEVERITY, + PCIECAP_AER_UE_DLP | + PCIECAP_AER_UE_SURPRISE_DOWN | + PCIECAP_AER_UE_FLOW_CTL_PROT | + PCIECAP_AER_UE_UNEXP_COMPL | + PCIECAP_AER_UE_RECV_OVFLOW | + PCIECAP_AER_UE_MALFORMED_TLP); + /* Clear all CE status */ + p7ioc_pcicfg_write32(&p->phb, 0, aercap + PCIECAP_AER_CE_STATUS, + 0xffffffff); + /* Disable some error reporting as per the P7IOC spec */ + p7ioc_pcicfg_write32(&p->phb, 0, aercap + PCIECAP_AER_CE_MASK, + PCIECAP_AER_CE_ADV_NONFATAL); + /* Enable ECRC generation & checking */ + p7ioc_pcicfg_write32(&p->phb, 0, aercap + PCIECAP_AER_CAPCTL, + PCIECAP_AER_CAPCTL_ECRCG_EN | + PCIECAP_AER_CAPCTL_ECRCC_EN); + /* Enable reporting in root error control */ + p7ioc_pcicfg_write32(&p->phb, 0, aercap + PCIECAP_AER_RERR_CMD, + PCIECAP_AER_RERR_CMD_FE | + PCIECAP_AER_RERR_CMD_NFE | + PCIECAP_AER_RERR_CMD_CE); + /* Clear root error status */ + p7ioc_pcicfg_write32(&p->phb, 0, aercap + PCIECAP_AER_RERR_STA, + 0xffffffff); + + return true; +} + +static void p7ioc_phb_init_utl(struct p7ioc_phb *p) +{ + /* Init_82..84: Clear spurious errors and assign errors to the + * right "interrupt" signal + */ + out_be64(p->regs + UTL_SYS_BUS_AGENT_STATUS, 0xffffffffffffffff); + out_be64(p->regs + UTL_SYS_BUS_AGENT_ERR_SEVERITY, 0x0000000000000000); + out_be64(p->regs + UTL_SYS_BUS_AGENT_IRQ_EN, 0xac80000000000000); + + /* Init_85..89: Setup buffer allocations */ + out_be64(p->regs + UTL_OUT_POST_DAT_BUF_ALLOC, 0x0400000000000000); + out_be64(p->regs + UTL_IN_POST_HDR_BUF_ALLOC, 0x1000000000000000); + out_be64(p->regs + UTL_IN_POST_DAT_BUF_ALLOC, 0x4000000000000000); + out_be64(p->regs + UTL_PCIE_TAGS_ALLOC, 0x0800000000000000); + out_be64(p->regs + UTL_GBIF_READ_TAGS_ALLOC, 0x0800000000000000); + + /* Init_90: PCI Express port control */ + out_be64(p->regs + UTL_PCIE_PORT_CONTROL, 0x8480000000000000); + + /* Init_91..93: Clean & setup port errors */ + out_be64(p->regs + UTL_PCIE_PORT_STATUS, 0xff7fffffffffffff); + out_be64(p->regs + UTL_PCIE_PORT_ERROR_SEV, 0x00e0000000000000); + out_be64(p->regs + UTL_PCIE_PORT_IRQ_EN, 0x7e65000000000000); + + /* Init_94 : Cleanup RC errors */ + out_be64(p->regs + UTL_RC_STATUS, 0xffffffffffffffff); +} + +static void p7ioc_phb_init_errors(struct p7ioc_phb *p) +{ + /* Init_98: LEM Error Mask : Temporarily disable error interrupts */ + out_be64(p->regs + PHB_LEM_ERROR_MASK, 0xffffffffffffffff); + + /* Init_99..107: Configure main error traps & clear old state */ + out_be64(p->regs + PHB_ERR_STATUS, 0xffffffffffffffff); + out_be64(p->regs + PHB_ERR1_STATUS, 0x0000000000000000); + out_be64(p->regs + PHB_ERR_LEM_ENABLE, 0xffffffffefffffff); + out_be64(p->regs + PHB_ERR_FREEZE_ENABLE, 0x0000000061c00000); + out_be64(p->regs + PHB_ERR_AIB_FENCE_ENABLE, 0xffffffc58c000000); + out_be64(p->regs + PHB_ERR_LOG_0, 0x0000000000000000); + out_be64(p->regs + PHB_ERR_LOG_1, 0x0000000000000000); + out_be64(p->regs + PHB_ERR_STATUS_MASK, 0x0000000000000000); + out_be64(p->regs + PHB_ERR1_STATUS_MASK, 0x0000000000000000); + + /* Init_108_116: Configure MMIO error traps & clear old state */ + out_be64(p->regs + PHB_OUT_ERR_STATUS, 0xffffffffffffffff); + out_be64(p->regs + PHB_OUT_ERR1_STATUS, 0x0000000000000000); + out_be64(p->regs + PHB_OUT_ERR_LEM_ENABLE, 0xffffffffffffffff); + out_be64(p->regs + PHB_OUT_ERR_FREEZE_ENABLE, 0x0000430803000000); + out_be64(p->regs + PHB_OUT_ERR_AIB_FENCE_ENABLE, 0x9df3bc00f0f0700f); + out_be64(p->regs + PHB_OUT_ERR_LOG_0, 0x0000000000000000); + out_be64(p->regs + PHB_OUT_ERR_LOG_1, 0x0000000000000000); + out_be64(p->regs + PHB_OUT_ERR_STATUS_MASK, 0x0000000000000000); + out_be64(p->regs + PHB_OUT_ERR1_STATUS_MASK, 0x0000000000000000); + + /* Init_117_125: Configure DMA_A error traps & clear old state */ + out_be64(p->regs + PHB_INA_ERR_STATUS, 0xffffffffffffffff); + out_be64(p->regs + PHB_INA_ERR1_STATUS, 0x0000000000000000); + out_be64(p->regs + PHB_INA_ERR_LEM_ENABLE, 0xffffffffffffffff); + out_be64(p->regs + PHB_INA_ERR_FREEZE_ENABLE, 0xc00003ff01006000); + out_be64(p->regs + PHB_INA_ERR_AIB_FENCE_ENABLE, 0x3fff50007e559fd8); + out_be64(p->regs + PHB_INA_ERR_LOG_0, 0x0000000000000000); + out_be64(p->regs + PHB_INA_ERR_LOG_1, 0x0000000000000000); + out_be64(p->regs + PHB_INA_ERR_STATUS_MASK, 0x0000000000000000); + out_be64(p->regs + PHB_INA_ERR1_STATUS_MASK, 0x0000000000000000); + + /* Init_126_134: Configure DMA_B error traps & clear old state */ + out_be64(p->regs + PHB_INB_ERR_STATUS, 0xffffffffffffffff); + out_be64(p->regs + PHB_INB_ERR1_STATUS, 0x0000000000000000); + out_be64(p->regs + PHB_INB_ERR_LEM_ENABLE, 0xffffffffffffffff); + out_be64(p->regs + PHB_INB_ERR_FREEZE_ENABLE, 0x0000000000000000); + out_be64(p->regs + PHB_INB_ERR_AIB_FENCE_ENABLE, 0x18ff80ffff7f0000); + out_be64(p->regs + PHB_INB_ERR_LOG_0, 0x0000000000000000); + out_be64(p->regs + PHB_INB_ERR_LOG_1, 0x0000000000000000); + out_be64(p->regs + PHB_INB_ERR_STATUS_MASK, 0x0000000000000000); + out_be64(p->regs + PHB_INB_ERR1_STATUS_MASK, 0x0000000000000000); + + /* Init_135..138: Cleanup & configure LEM */ + out_be64(p->regs + PHB_LEM_FIR_ACCUM, 0x0000000000000000); + out_be64(p->regs + PHB_LEM_ACTION0, 0xffffffffffffffff); + out_be64(p->regs + PHB_LEM_ACTION1, 0x0000000000000000); + out_be64(p->regs + PHB_LEM_WOF, 0x0000000000000000); +} + +/* p7ioc_phb_init - Initialize the PHB hardware + * + * This is currently only called at boot time. It will eventually + * be called at runtime, for example in some cases of error recovery + * after a PHB reset in which case we might need locks etc... + */ +int64_t p7ioc_phb_init(struct p7ioc_phb *p) +{ + uint64_t val; + + PHBDBG(p, "Initializing PHB %d...\n", p->index); + + p->state = P7IOC_PHB_STATE_INITIALIZING; + + /* For some reason, the doc wants us to read the version + * register, so let's do it. We shoud probably check that + * the value makes sense... + */ + val = in_be64(p->regs_asb + PHB_VERSION); + + PHBDBG(p, "Version reg: %llx\n", val); + + /* + * Configure AIB operations + * + * This register maps upbound commands to AIB channels. + * DMA Write=0, DMA Read=2, MMIO Load Response=1, + * Interrupt Request=1, TCE Read=3. + */ + /* Init_1: AIB TX Channel Mapping */ + out_be64(p->regs_asb + PHB_AIB_TX_CHAN_MAPPING, 0x0211300000000000); + + /* + * This group of steps initializes the AIB RX credits for + * the CI block’s port that is attached to this PHB. + * + * Channel 0 (Dkill): 32 command credits, 0 data credits + * (effectively infinite command credits) + * Channel 1 (DMA/TCE Read Responses): 32 command credits, 32 data + * credits (effectively infinite + * command and data credits) + * Channel 2 (Interrupt Reissue/Return): 32 command, 0 data credits + * (effectively infinite + * command credits) + * Channel 3 (MMIO Load/Stores, EOIs): 1 command, 1 data credit + */ + + /* Init_2: AIB RX Command Credit */ + out_be64(p->regs_asb + PHB_AIB_RX_CMD_CRED, 0x0020002000200001); + /* Init_3: AIB RX Data Credit */ + out_be64(p->regs_asb + PHB_AIB_RX_DATA_CRED, 0x0000002000000001); + /* Init_4: AXIB RX Credit Init Timer */ + out_be64(p->regs_asb + PHB_AIB_RX_CRED_INIT_TIMER, 0xFF00000000000000); + + /* + * Enable all 32 AIB and TCE tags. + * + * AIB tags are used for DMA read requests. + * TCE tags are used for every internal transaction as well as TCE + * read requests. + */ + + /* Init_5: PHB - AIB Tag Enable Register */ + out_be64(p->regs_asb + PHB_AIB_TAG_ENABLE, 0xFFFFFFFF00000000); + /* Init_6: PHB – TCE Tag Enable Register */ + out_be64(p->regs_asb + PHB_TCE_TAG_ENABLE, 0xFFFFFFFF00000000); + + /* Init_7: PCIE - System Configuration Register + * + * This is the default value out of reset. This register can be + * modified to change the following fields if needed: + * + * bits 04:09 - SYS_EC0C_MAXLINKWIDTH[5:0] + * The default link width is x8. This can be reduced + * to x1 or x4, if needed. + * + * bits 10:12 - SYS_EC04_MAX_PAYLOAD[2:0] + * + * The default max payload size is 4KB. This can be + * reduced to the allowed ranges from 128B + * to 2KB if needed. + */ + out_be64(p->regs + PHB_PCIE_SYSTEM_CONFIG, 0x422800FC20000000); + + /* Init_8: PHB - PCI-E Reset Register + * + * This will deassert reset for the PCI-E cores, including the + * PHY and HSS macros. The TLDLP core will begin link training + * shortly after this register is written. + * This will also assert reset for the internal scan-only error + * report macros. The error report macro reset will be deasserted + * in a later step. + * Firmware will verify in a later step whether the PCI-E link + * has been established. + * + * NOTE: We perform a PERST at the end of the init sequence so + * we could probably skip that link training. + */ + out_be64(p->regs + PHB_RESET, 0xE800000000000000); + + /* Init_9: BUID + * + * Only the top 5 bit of the MSI field are implemented, the bottom + * are always 0. Our buid_msi value should also be a multiple of + * 16 so it should all fit well + */ + val = SETFIELD(PHB_BUID_LSI, 0ul, P7_BUID_BASE(p->buid_lsi)); + val |= SETFIELD(PHB_BUID_MSI, 0ul, P7_BUID_BASE(p->buid_msi)); + out_be64(p->regs + PHB_BUID, val); + + /* Init_10..12: IO Space */ + out_be64(p->regs + PHB_IO_BASE_ADDR, p->io_base); + out_be64(p->regs + PHB_IO_BASE_MASK, ~(PHB_IO_SIZE - 1)); + out_be64(p->regs + PHB_IO_START_ADDR, 0); + + /* Init_13..15: M32 Space */ + out_be64(p->regs + PHB_M32_BASE_ADDR, p->m32_base + M32_PCI_START); + out_be64(p->regs + PHB_M32_BASE_MASK, ~(M32_PCI_SIZE - 1)); + out_be64(p->regs + PHB_M32_START_ADDR, M32_PCI_START); + + /* Init_16: PCIE-E Outbound Request Upper Address */ + out_be64(p->regs + PHB_M64_UPPER_BITS, 0); + + /* Init_17: PCIE-E PHB2 Configuration + * + * We enable IO, M32, 32-bit MSI and 64-bit MSI + */ + out_be64(p->regs + PHB_PHB2_CONFIG, + PHB_PHB2C_32BIT_MSI_EN | + PHB_PHB2C_IO_EN | + PHB_PHB2C_64BIT_MSI_EN | + PHB_PHB2C_M32_EN | + PHB_PHB2C_64B_TCE_EN); + + /* Init_18..xx: Reset all IODA tables */ + p7ioc_ioda_reset(&p->phb, false); + + /* Init_42..47: Clear UTL & DLP error log regs */ + out_be64(p->regs + PHB_PCIE_UTL_ERRLOG1, 0xffffffffffffffff); + out_be64(p->regs + PHB_PCIE_UTL_ERRLOG2, 0xffffffffffffffff); + out_be64(p->regs + PHB_PCIE_UTL_ERRLOG3, 0xffffffffffffffff); + out_be64(p->regs + PHB_PCIE_UTL_ERRLOG4, 0xffffffffffffffff); + out_be64(p->regs + PHB_PCIE_DLP_ERRLOG1, 0xffffffffffffffff); + out_be64(p->regs + PHB_PCIE_DLP_ERRLOG2, 0xffffffffffffffff); + + /* Init_48: Wait for DLP core to be out of reset */ + if (!p7ioc_phb_wait_dlp_reset(p)) + goto failed; + + /* Init_49 - Clear port status */ + out_be64(p->regs + UTL_PCIE_PORT_STATUS, 0xffffffffffffffff); + + /* Init_50..81: Init root complex config space */ + if (!p7ioc_phb_init_rc_cfg(p)) + goto failed; + + /* Init_82..94 : Init UTL */ + p7ioc_phb_init_utl(p); + + /* Init_95: PCI-E Reset, deassert reset for internal error macros */ + out_be64(p->regs + PHB_RESET, 0xe000000000000000); + + /* Init_96: PHB Control register. Various PHB settings: + * + * - Enable ECC for various internal RAMs + * - Enable all TCAM entries + * - Set failed DMA read requests to return Completer Abort on error + */ + out_be64(p->regs + PHB_CONTROL, 0x7f38000000000000); + + /* Init_97: Legacy Control register + * + * The spec sets bit 0 to enable DKill to flush the TCEs. We do not + * use that mechanism however, we require the OS to directly access + * the TCE Kill register, so we leave that bit set to 0 + */ + out_be64(p->regs + PHB_LEGACY_CTRL, 0x0000000000000000); + + /* Init_98..138 : Setup error registers */ + p7ioc_phb_init_errors(p); + + /* Init_139: Read error summary */ + val = in_be64(p->regs + PHB_ETU_ERR_SUMMARY); + if (val) { + PHBERR(p, "Errors detected during PHB init: 0x%16llx\n", val); + goto failed; + } + + /* Steps Init_140..142 have been removed from the spec. */ + + /* Init_143..144: Enable IO, MMIO, Bus master etc... and clear + * status bits + */ + p7ioc_pcicfg_write16(&p->phb, 0, PCI_CFG_STAT, + PCI_CFG_STAT_SENT_TABORT | + PCI_CFG_STAT_RECV_TABORT | + PCI_CFG_STAT_RECV_MABORT | + PCI_CFG_STAT_SENT_SERR | + PCI_CFG_STAT_RECV_PERR); + p7ioc_pcicfg_write16(&p->phb, 0, PCI_CFG_CMD, + PCI_CFG_CMD_SERR_EN | + PCI_CFG_CMD_PERR_RESP | + PCI_CFG_CMD_BUS_MASTER_EN | + PCI_CFG_CMD_MEM_EN | + PCI_CFG_CMD_IO_EN); + + /* At this point, the spec suggests doing a bus walk. However we + * haven't powered up the slots with the SHCP controller. We'll + * deal with that and link training issues later, for now, let's + * enable the full range of error detection + */ + + /* Init_145..149: Enable error interrupts and LEM */ + out_be64(p->regs + PHB_ERR_IRQ_ENABLE, 0x0000000061c00000); + out_be64(p->regs + PHB_OUT_ERR_IRQ_ENABLE, 0x0000430803000000); + out_be64(p->regs + PHB_INA_ERR_IRQ_ENABLE, 0xc00003ff01006000); + out_be64(p->regs + PHB_INB_ERR_IRQ_ENABLE, 0x0000000000000000); + out_be64(p->regs + PHB_LEM_ERROR_MASK, 0x1249a1147f500f2c); + + /* Init_150: Enable DMA read/write TLP address speculation */ + out_be64(p->regs + PHB_TCE_PREFETCH, 0x0000c00000000000); + + /* Init_151..152: Set various timeouts */ + out_be64(p->regs + PHB_TIMEOUT_CTRL1, 0x1611112010200000); + out_be64(p->regs + PHB_TIMEOUT_CTRL2, 0x0000561300000000); + + /* Mark the PHB as functional which enables all the various sequences */ + p->state = P7IOC_PHB_STATE_FUNCTIONAL; + + return OPAL_SUCCESS; + + failed: + PHBERR(p, "Initialization failed\n"); + p->state = P7IOC_PHB_STATE_BROKEN; + + return OPAL_HARDWARE; +} + +void p7ioc_phb_reset(struct phb *phb) +{ + struct p7ioc_phb *p = phb_to_p7ioc_phb(phb); + struct p7ioc *ioc = p->ioc; + uint64_t ci_idx, rreg; + unsigned int i; + bool fenced; + + /* Check our fence status. The fence bits we care about are + * two bits per PHB at IBM bit location 14 and 15 + 4*phb + */ + fenced = p7ioc_phb_fenced(p); + + PHBDBG(p, "PHB reset... (fenced: %d)\n", (int)fenced); + + /* + * If not fenced and already functional, let's do an IODA reset + * to clear pending DMAs and wait a bit for thing to settle. It's + * notable that the IODA table cache won't be emptied so that we + * can restore them during error recovery. + */ + if (p->state == P7IOC_PHB_STATE_FUNCTIONAL && !fenced) { + PHBDBG(p, " ioda reset ...\n"); + p7ioc_ioda_reset(&p->phb, false); + time_wait_ms(100); + } + + /* CI port index */ + ci_idx = p->index + 2; + + /* Reset register bits for this PHB */ + rreg = 0;/*PPC_BIT(8 + ci_idx * 2);*/ /* CI port config reset */ + rreg |= PPC_BIT(9 + ci_idx * 2); /* CI port func reset */ + rreg |= PPC_BIT(32 + p->index); /* PHBn config reset */ + + /* Mask various errors during reset and clear pending errors */ + out_be64(ioc->regs + P7IOC_CIn_LEM_ERR_MASK(ci_idx), + 0xa4f4000000000000ul); + out_be64(p->regs_asb + PHB_LEM_ERROR_MASK, 0xadb650c9808dd051ul); + out_be64(ioc->regs + P7IOC_CIn_LEM_FIR(ci_idx), 0); + + /* We need to retry in case the fence doesn't lift due to a + * problem with lost credits (HW guys). How many times ? + */ +#define MAX_PHB_RESET_RETRIES 5 + for (i = 0; i < MAX_PHB_RESET_RETRIES; i++) { + PHBDBG(p, " reset try %d...\n", i); + /* Apply reset */ + out_be64(ioc->regs + P7IOC_CCRR, rreg); + time_wait_ms(1); + out_be64(ioc->regs + P7IOC_CCRR, 0); + + /* Check if fence lifed */ + fenced = p7ioc_phb_fenced(p); + PHBDBG(p, " fenced: %d...\n", (int)fenced); + if (!fenced) + break; + } + + /* Reset failed, not much to do, maybe add an error return */ + if (fenced) { + PHBERR(p, "Reset failed, fence still set !\n"); + p->state = P7IOC_PHB_STATE_BROKEN; + return; + } + + /* Wait a bit */ + time_wait_ms(100); + + /* Re-initialize the PHB */ + p7ioc_phb_init(p); + + /* Restore the CI error mask */ + out_be64(ioc->regs + P7IOC_CIn_LEM_ERR_MASK_AND(ci_idx), 0); +} + |