/* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later * Copyright 2021 IBM Corp. */ #include #include #include #include #include #include #include /* Number of PEs supported */ #define PAU_MAX_PE_NUM 16 #define PAU_RESERVED_PE_NUM 15 #define PAU_SLOT_NORMAL PCI_SLOT_STATE_NORMAL #define PAU_SLOT_LINK PCI_SLOT_STATE_LINK #define PAU_SLOT_LINK_START (PAU_SLOT_LINK + 1) #define PAU_SLOT_LINK_WAIT (PAU_SLOT_LINK + 2) #define PAU_SLOT_LINK_TRAINED (PAU_SLOT_LINK + 3) #define PAU_SLOT_FRESET PCI_SLOT_STATE_FRESET #define PAU_SLOT_FRESET_START (PAU_SLOT_FRESET + 1) #define PAU_SLOT_FRESET_INIT (PAU_SLOT_FRESET + 2) #define PAU_SLOT_FRESET_ASSERT_DELAY (PAU_SLOT_FRESET + 3) #define PAU_SLOT_FRESET_DEASSERT_DELAY (PAU_SLOT_FRESET + 4) #define PAU_SLOT_FRESET_INIT_DELAY (PAU_SLOT_FRESET + 5) #define PAU_LINK_TRAINING_RETRIES 2 #define PAU_LINK_TRAINING_TIMEOUT 15000 /* ms */ #define PAU_LINK_STATE_TRAINED 0x7 struct pau_dev *pau_next_dev(struct pau *pau, struct pau_dev *dev, enum pau_dev_type type) { uint32_t i = 0; if (dev) i = dev->index + 1; for (; i < pau->links; i++) { dev = &pau->devices[i]; if (dev->type == type || type == PAU_DEV_TYPE_ANY) return dev; } return NULL; } static void pau_opencapi_dump_scom_reg(struct pau *pau, uint64_t reg) { PAUDBG(pau, "0x%llx = 0x%016llx\n", reg, pau_read(pau, reg)); } void pau_opencapi_dump_scoms(struct pau *pau) { struct pau_dev *dev; uint64_t cq_sm; for (uint32_t i = 1; i < 4; i++) { cq_sm = PAU_BLOCK_CQ_SM(i); pau_opencapi_dump_scom_reg(pau, cq_sm + PAU_REG_OFFSET(PAU_MCP_MISC_CERR_MESSAGE0)); pau_opencapi_dump_scom_reg(pau, cq_sm + PAU_REG_OFFSET(PAU_MCP_MISC_CERR_MESSAGE1)); pau_opencapi_dump_scom_reg(pau, cq_sm + PAU_REG_OFFSET(PAU_MCP_MISC_CERR_MESSAGE2)); pau_opencapi_dump_scom_reg(pau, cq_sm + PAU_REG_OFFSET(PAU_MCP_MISC_CERR_MESSAGE3)); pau_opencapi_dump_scom_reg(pau, cq_sm + PAU_REG_OFFSET(PAU_MCP_MISC_CERR_MESSAGE4)); pau_opencapi_dump_scom_reg(pau, cq_sm + PAU_REG_OFFSET(PAU_MCP_MISC_CERR_MESSAGE5)); pau_opencapi_dump_scom_reg(pau, cq_sm + PAU_REG_OFFSET(PAU_MCP_MISC_CERR_MESSAGE6)); pau_opencapi_dump_scom_reg(pau, cq_sm + PAU_REG_OFFSET(PAU_MCP_MISC_CERR_MESSAGE7)); pau_opencapi_dump_scom_reg(pau, cq_sm + PAU_REG_OFFSET(PAU_MCP_MISC_CERR_FIRST0)); pau_opencapi_dump_scom_reg(pau, cq_sm + PAU_REG_OFFSET(PAU_MCP_MISC_CERR_FIRST1)); pau_opencapi_dump_scom_reg(pau, cq_sm + PAU_REG_OFFSET(PAU_MCP_MISC_CERR_FIRST2)); } pau_opencapi_dump_scom_reg(pau, PAU_CTL_MISC_CERR_MESSAGE0); pau_opencapi_dump_scom_reg(pau, PAU_CTL_MISC_CERR_MESSAGE1); pau_opencapi_dump_scom_reg(pau, PAU_CTL_MISC_CERR_MESSAGE2); pau_opencapi_dump_scom_reg(pau, PAU_CTL_MISC_CERR_FIRST0); pau_opencapi_dump_scom_reg(pau, PAU_CTL_MISC_CERR_FIRST1); pau_opencapi_dump_scom_reg(pau, PAU_DAT_MISC_CERR_ECC_HOLD); pau_opencapi_dump_scom_reg(pau, PAU_DAT_MISC_CERR_ECC_MASK); pau_opencapi_dump_scom_reg(pau, PAU_DAT_MISC_CERR_ECC_FIRST); pau_for_each_opencapi_dev(dev, pau) { pau_opencapi_dump_scom_reg(pau, PAU_OTL_MISC_ERR_RPT_HOLD0(dev->index)); pau_opencapi_dump_scom_reg(pau, PAU_OTL_MISC_OTL_REM0(dev->index)); pau_opencapi_dump_scom_reg(pau, PAU_OTL_MISC_ERROR_SIG_RXI(dev->index)); pau_opencapi_dump_scom_reg(pau, PAU_OTL_MISC_ERROR_SIG_RXO(dev->index)); pau_opencapi_dump_scom_reg(pau, PAU_OTL_MISC_ERR_RPT_HOLD1(dev->index)); } } static void pau_dt_create_link(struct dt_node *pau, uint32_t pau_index, uint32_t dev_index) { struct dt_node *link; uint32_t phy_lane_mask = 0, pau_unit = 0; uint32_t op_unit = 0, odl_index = 0; link = dt_new_addr(pau, "link", dev_index); dt_add_property_string(link, "compatible", "ibm,pau-link"); dt_add_property_cells(link, "reg", dev_index); dt_add_property_cells(link, "ibm,pau-link-index", dev_index); /* pau_index Interface Link - OPxA/B * 0 OPT0 -- PAU0 * OPT1 -- no PAU, SMP only * OPT2 -- no PAU, SMP only * 1 OPT3 -- PAU3 * 2 OPT4 -- PAU4 by default, but can be muxed to use PAU5 * 3 OPT5 -- PAU5 by default, but can be muxed to use PAU4 * 4 OPT6 -- PAU6 by default, but can be muxed to use PAU7 * 5 OPT7 -- PAU7 by default, but can be muxed to use PAU6 */ switch (pau_index) { case 0: /* OP0A - OP0B */ pau_unit = 0; op_unit = 0; break; case 1: /* OP3A - OP3B */ pau_unit = 3; op_unit = 3; break; case 2: /* OP4A - OP4B or OP5A - OP5B (TO DO) */ pau_unit = 4; op_unit = 4; break; case 3: /* OP5A - OP5B or OP4A - OP4B (TO DO) */ pau_unit = 5; op_unit = 5; break; case 4: /* OP6A - OP6B or OP7A - OP7B (TO DO) */ pau_unit = 6; op_unit = 6; break; case 5: /* OP7A - OP7B or OP6A - OP6B (TO DO) */ pau_unit = 7; op_unit = 7; break; default: return; } /* ODL0 is hooked up to OTL0 */ if (dev_index == 0) { odl_index = 0; phy_lane_mask = PPC_BITMASK32(0, 3); phy_lane_mask |= PPC_BITMASK32(5, 8); } else if (dev_index == 1) { odl_index = 1; phy_lane_mask = PPC_BITMASK32(9, 12); phy_lane_mask |= PPC_BITMASK32(14, 17); } dt_add_property_cells(link, "ibm,odl-index", odl_index); dt_add_property_cells(link, "ibm,pau-unit", pau_unit); dt_add_property_cells(link, "ibm,op-unit", op_unit); dt_add_property_cells(link, "ibm,pau-lane-mask", phy_lane_mask); dt_add_property_cells(link, "ibm,phb-index", pau_get_phb_index(pau_index, dev_index)); } static void pau_dt_create_pau(struct dt_node *xscom, uint32_t pau_index) { const uint32_t pau_base[] = { 0x10010800, 0x11010800, 0x12010800, 0x12011000, 0x13010800, 0x13011000}; struct dt_node *pau; uint32_t links; assert(pau_index < PAU_NBR); pau = dt_new_addr(xscom, "pau", pau_base[pau_index]); dt_add_property_cells(pau, "#size-cells", 0); dt_add_property_cells(pau, "#address-cells", 1); dt_add_property_cells(pau, "reg", pau_base[pau_index], 0x2c); dt_add_property_string(pau, "compatible", "ibm,power10-pau"); dt_add_property_cells(pau, "ibm,pau-chiplet", pau_base[pau_index] >> 24); dt_add_property_cells(pau, "ibm,pau-index", pau_index); links = PAU_LINKS_OPENCAPI_PER_PAU; for (uint32_t i = 0; i < links; i++) pau_dt_create_link(pau, pau_index, i); } static bool pau_dt_create(void) { struct dt_node *xscom; /* P10 chips only */ if (proc_gen < proc_gen_p10) return false; dt_for_each_compatible(dt_root, xscom, "ibm,xscom") for (uint32_t i = 0; i < PAU_NBR; i++) pau_dt_create_pau(xscom, i); return true; } static struct pau *pau_create(struct dt_node *dn) { struct pau *pau; struct dt_node *link; struct pau_dev *dev; char *path; uint32_t i; pau = zalloc(sizeof(*pau)); assert(pau); init_lock(&pau->lock); init_lock(&pau->procedure_state.lock); pau->dt_node = dn; pau->index = dt_prop_get_u32(dn, "ibm,pau-index"); pau->xscom_base = dt_get_address(dn, 0, NULL); pau->chip_id = dt_get_chip_id(dn); pau->op_chiplet = dt_prop_get_u32(dn, "ibm,pau-chiplet"); assert(get_chip(pau->chip_id)); pau->links = PAU_LINKS_OPENCAPI_PER_PAU; dt_for_each_compatible(dn, link, "ibm,pau-link") { i = dt_prop_get_u32(link, "ibm,pau-link-index"); assert(i < PAU_LINKS_OPENCAPI_PER_PAU); dev = &pau->devices[i]; dev->index = i; dev->pau = pau; dev->dn = link; dev->odl_index = dt_prop_get_u32(link, "ibm,odl-index"); dev->pau_unit = dt_prop_get_u32(link, "ibm,pau-unit"); dev->op_unit = dt_prop_get_u32(link, "ibm,op-unit"); dev->phy_lane_mask = dt_prop_get_u32(link, "ibm,pau-lane-mask"); }; path = dt_get_path(dn); PAUINF(pau, "Found %s\n", path); PAUINF(pau, "SCOM base: 0x%llx\n", pau->xscom_base); free(path); return pau; } static void pau_device_detect_fixup(struct pau_dev *dev) { struct dt_node *dn = dev->dn; if (dev->type == PAU_DEV_TYPE_OPENCAPI) { PAUDEVDBG(dev, "Link type opencapi\n"); dt_add_property_strings(dn, "ibm,pau-link-type", "opencapi"); return; } PAUDEVDBG(dev, "Link type unknown\n"); dt_add_property_strings(dn, "ibm,pau-link-type", "unknown"); } #define CQ_CTL_STATUS_TIMEOUT 10 /* milliseconds */ static int pau_opencapi_set_fence_control(struct pau_dev *dev, uint8_t state_requested) { uint64_t timeout = mftb() + msecs_to_tb(CQ_CTL_STATUS_TIMEOUT); uint8_t status; struct pau *pau = dev->pau; uint64_t reg, val; reg = PAU_CTL_MISC_FENCE_CTRL(dev->index); val = pau_read(pau, reg); val = SETFIELD(PAU_CTL_MISC_FENCE_REQUEST, val, state_requested); pau_write(pau, reg, val); /* Wait for fence status to update */ do { reg = PAU_CTL_MISC_STATUS(dev->index); val = pau_read(pau, reg); status = GETFIELD(PAU_CTL_MISC_STATUS_AM_FENCED(dev->index), val); if (status == state_requested) return OPAL_SUCCESS; time_wait_ms(1); } while (tb_compare(mftb(), timeout) == TB_ABEFOREB); /* * @fwts-label OCAPIFenceStatusTimeout * @fwts-advice The PAU fence status did not update as expected. This * could be the result of a firmware or hardware bug. OpenCAPI * functionality could be broken. */ PAUDEVERR(dev, "Bad fence status: expected 0x%x, got 0x%x\n", state_requested, status); return OPAL_HARDWARE; } #define PAU_DEV_STATUS_BROKEN 0x1 static void pau_opencapi_set_broken(struct pau_dev *dev) { PAUDEVDBG(dev, "Update status to broken\n"); dev->status = PAU_DEV_STATUS_BROKEN; } static void pau_opencapi_mask_firs(struct pau *pau) { uint64_t reg, val; reg = pau->xscom_base + PAU_FIR_MASK(1); xscom_read(pau->chip_id, reg, &val); val |= PAU_FIR1_NDL_BRICKS_0_5; val |= PAU_FIR1_NDL_BRICKS_6_11; xscom_write(pau->chip_id, reg, val); reg = pau->xscom_base + PAU_FIR_MASK(2); xscom_read(pau->chip_id, reg, &val); val |= PAU_FIR2_OTL_PERR; xscom_write(pau->chip_id, reg, val); } static void pau_opencapi_assign_bars(struct pau *pau) { struct pau_dev *dev; uint64_t addr, size, val; /* Global MMIO bar (per pau) * 16M aligned address -> 0x1000000 (bit 24) */ phys_map_get(pau->chip_id, PAU_REGS, pau->index, &addr, &size); val = SETFIELD(PAU_MMIO_BAR_ADDR, 0ull, addr >> 24); val |= PAU_MMIO_BAR_ENABLE; pau_write(pau, PAU_MMIO_BAR, val); PAUINF(pau, "MMIO base: 0x%016llx (%lldMB)\n", addr, size >> 20); pau->regs[0] = addr; pau->regs[1] = size; /* NTL bar (per device) * 64K aligned address -> 0x10000 (bit 16) */ pau_for_each_dev(dev, pau) { if (dev->type == PAU_DEV_TYPE_UNKNOWN) continue; phys_map_get(pau->chip_id, PAU_OCAPI_MMIO, pau_dev_index(dev, PAU_LINKS_OPENCAPI_PER_PAU), &addr, &size); val = SETFIELD(PAU_NTL_BAR_ADDR, 0ull, addr >> 16); val = SETFIELD(PAU_NTL_BAR_SIZE, val, ilog2(size >> 16)); pau_write(pau, PAU_NTL_BAR(dev->index), val); val = SETFIELD(PAU_CTL_MISC_MMIOPA_CONFIG_BAR_ADDR, 0ull, addr >> 16); val = SETFIELD(PAU_CTL_MISC_MMIOPA_CONFIG_BAR_SIZE, val, ilog2(size >> 16)); pau_write(pau, PAU_CTL_MISC_MMIOPA_CONFIG(dev->index), val); dev->ntl_bar.addr = addr; dev->ntl_bar.size = size; } /* GENID bar (logically divided per device) * 512K aligned address -> 0x80000 (bit 19) */ phys_map_get(pau->chip_id, PAU_GENID, pau->index, &addr, &size); val = SETFIELD(PAU_GENID_BAR_ADDR, 0ull, addr >> 19); pau_write(pau, PAU_GENID_BAR, val); pau_for_each_dev(dev, pau) { if (dev->type == PAU_DEV_TYPE_UNKNOWN) continue; dev->genid_bar.size = size; /* +320K = Bricks 0-4 Config Addr/Data registers */ dev->genid_bar.cfg = addr + 0x50000; } } static uint64_t pau_opencapi_ipi_attributes(struct irq_source *is, uint32_t isn) { struct pau *pau = is->data; uint32_t level = isn - pau->irq_base; if (level >= 37 && level <= 40) { /* level 37-40: OTL/XSL interrupt */ return IRQ_ATTR_TARGET_OPAL | IRQ_ATTR_TARGET_RARE | IRQ_ATTR_TYPE_MSI; } return IRQ_ATTR_TARGET_LINUX; } static void pau_opencapi_ipi_interrupt(struct irq_source *is, uint32_t isn) { struct pau *pau = is->data; uint32_t level = isn - pau->irq_base; struct pau_dev *dev; switch (level) { case 37 ... 40: pau_for_each_opencapi_dev(dev, pau) pau_opencapi_set_broken(dev); opal_update_pending_evt(OPAL_EVENT_PCI_ERROR, OPAL_EVENT_PCI_ERROR); break; default: PAUERR(pau, "Received unknown interrupt %d\n", level); return; } } #define PAU_IRQ_LEVELS 60 static char *pau_opencapi_ipi_name(struct irq_source *is, uint32_t isn) { struct pau *pau = is->data; uint32_t level = isn - pau->irq_base; switch (level) { case 0 ... 19: return strdup("Reserved"); case 20: return strdup("An error event related to PAU CQ functions"); case 21: return strdup("An error event related to PAU MISC functions"); case 22 ... 34: return strdup("Reserved"); case 35: return strdup("Translation failure for OCAPI link 0"); case 36: return strdup("Translation failure for OCAPI link 1"); case 37: return strdup("An error event related to OTL for link 0"); case 38: return strdup("An error event related to OTL for link 1"); case 39: return strdup("An error event related to XSL for link 0"); case 40: return strdup("An error event related to XSL for link 1"); case 41 ... 59: return strdup("Reserved"); } return strdup("Unknown"); } static const struct irq_source_ops pau_opencapi_ipi_ops = { .attributes = pau_opencapi_ipi_attributes, .interrupt = pau_opencapi_ipi_interrupt, .name = pau_opencapi_ipi_name, }; static void pau_opencapi_setup_irqs(struct pau *pau) { uint64_t reg, val; uint32_t base; base = xive2_alloc_ipi_irqs(pau->chip_id, PAU_IRQ_LEVELS, 64); if (base == XIVE_IRQ_ERROR) { PAUERR(pau, "Failed to allocate interrupt sources\n"); return; } xive2_register_ipi_source(base, PAU_IRQ_LEVELS, pau, &pau_opencapi_ipi_ops); /* Set IPI configuration */ reg = PAU_MISC_CONFIG; val = pau_read(pau, reg); val = SETFIELD(PAU_MISC_CONFIG_IPI_PS, val, PAU_MISC_CONFIG_IPI_PS_64K); val = SETFIELD(PAU_MISC_CONFIG_IPI_OS, val, PAU_MISC_CONFIG_IPI_OS_AIX); pau_write(pau, reg, val); /* Set IRQ base */ reg = PAU_MISC_INT_BAR; val = SETFIELD(PAU_MISC_INT_BAR_ADDR, 0ull, (uint64_t)xive2_get_trigger_port(base) >> 12); pau_write(pau, reg, val); pau->irq_base = base; } static void pau_opencapi_enable_bars(struct pau_dev *dev, bool enable) { struct pau *pau = dev->pau; uint64_t reg, val; if (dev->ntl_bar.enable == enable) /* No state change */ return; dev->ntl_bar.enable = enable; dev->genid_bar.enable = enable; reg = PAU_NTL_BAR(dev->index); val = pau_read(pau, reg); val = SETFIELD(PAU_NTL_BAR_ENABLE, val, enable); pau_write(pau, reg, val); /* * Generation IDs are a single space in the hardware but we split them * per device. Only disable in hardware if every device has disabled. */ if (!enable) pau_for_each_dev(dev, pau) if (dev->genid_bar.enable) return; reg = PAU_GENID_BAR; val = pau_read(pau, reg); val = SETFIELD(PAU_GENID_BAR_ENABLE, val, enable); pau_write(pau, reg, val); } static int64_t pau_opencapi_creset(struct pci_slot *slot) { struct pau_dev *dev = pau_phb_to_opencapi_dev(slot->phb); PAUDEVERR(dev, "creset not supported\n"); return OPAL_UNSUPPORTED; } static int64_t pau_opencapi_hreset(struct pci_slot *slot) { struct pau_dev *dev = pau_phb_to_opencapi_dev(slot->phb); PAUDEVERR(dev, "hreset not supported\n"); return OPAL_UNSUPPORTED; } static void pau_opencapi_assert_odl_reset(struct pau_dev *dev) { struct pau *pau = dev->pau; uint64_t reg, val; reg = P10_OB_ODL_CONFIG(dev->op_unit, dev->odl_index); val = P10_OB_ODL_CONFIG_RESET; val = SETFIELD(P10_OB_ODL_CONFIG_VERSION, val, 0b000100); // OCAPI 4 val = SETFIELD(P10_OB_ODL_CONFIG_TRAIN_MODE, val, 0b0101); // ts2 val = SETFIELD(P10_OB_ODL_CONFIG_SUPPORTED_MODES, val, 0b0010); val |= P10_OB_ODL_CONFIG_X4_BACKOFF_ENABLE; val = SETFIELD(P10_OB_ODL_CONFIG_PHY_CNTR_LIMIT, val, 0b1111); val |= P10_OB_ODL_CONFIG_DEBUG_ENABLE; val = SETFIELD(P10_OB_ODL_CONFIG_FWD_PROGRESS_TIMER, val, 0b0110); xscom_write(pau->chip_id, reg, val); } static void pau_opencapi_deassert_odl_reset(struct pau_dev *dev) { struct pau *pau = dev->pau; uint64_t reg, val; reg = P10_OB_ODL_CONFIG(dev->op_unit, dev->odl_index); xscom_read(pau->chip_id, reg, &val); val &= ~P10_OB_ODL_CONFIG_RESET; xscom_write(pau->chip_id, reg, val); } static void pau_opencapi_training_mode(struct pau_dev *dev, uint8_t pattern) { struct pau *pau = dev->pau; uint64_t reg, val; reg = P10_OB_ODL_CONFIG(dev->op_unit, dev->odl_index); xscom_read(pau->chip_id, reg, &val); val = SETFIELD(P10_OB_ODL_CONFIG_TRAIN_MODE, val, pattern); xscom_write(pau->chip_id, reg, val); } static int64_t pau_opencapi_assert_adapter_reset(struct pau_dev *dev) { int64_t rc = OPAL_PARAMETER; if (platform.ocapi->i2c_assert_reset) rc = platform.ocapi->i2c_assert_reset(dev->i2c_bus_id); if (rc) PAUDEVERR(dev, "Error writing I2C reset signal: %lld\n", rc); return rc; } static int64_t pau_opencapi_deassert_adapter_reset(struct pau_dev *dev) { int64_t rc = OPAL_PARAMETER; if (platform.ocapi->i2c_deassert_reset) rc = platform.ocapi->i2c_deassert_reset(dev->i2c_bus_id); if (rc) PAUDEVERR(dev, "Error writing I2C reset signal: %lld\n", rc); return rc; } static void pau_opencapi_fence_brick(struct pau_dev *dev) { struct pau *pau = dev->pau; PAUDEVDBG(dev, "Fencing brick\n"); pau_opencapi_set_fence_control(dev, 0b11); /* Place all bricks into Fence state */ pau_write(pau, PAU_MISC_FENCE_STATE, PAU_MISC_FENCE_STATE_SET(pau_dev_index(dev, PAU_LINKS_OPENCAPI_PER_PAU))); } static void pau_opencapi_unfence_brick(struct pau_dev *dev) { struct pau *pau = dev->pau; PAUDEVDBG(dev, "Unfencing brick\n"); pau_write(pau, PAU_MISC_FENCE_STATE, PAU_MISC_FENCE_STATE_CLEAR(pau_dev_index(dev, PAU_LINKS_OPENCAPI_PER_PAU))); pau_opencapi_set_fence_control(dev, 0b10); pau_opencapi_set_fence_control(dev, 0b00); } static int64_t pau_opencapi_freset(struct pci_slot *slot) { struct pau_dev *dev = pau_phb_to_opencapi_dev(slot->phb); uint8_t presence = 1; int64_t rc = OPAL_SUCCESS; switch (slot->state) { case PAU_SLOT_NORMAL: case PAU_SLOT_FRESET_START: PAUDEVDBG(dev, "FRESET: Starts\n"); if (slot->ops.get_presence_state) slot->ops.get_presence_state(slot, &presence); if (!presence) { /* * FIXME: if there's no card on the link, we * should consider powering off the unused * lanes to save energy */ PAUDEVINF(dev, "no card detected\n"); return OPAL_SUCCESS; } slot->link_retries = PAU_LINK_TRAINING_RETRIES; /* fall-through */ case PAU_SLOT_FRESET_INIT: pau_opencapi_fence_brick(dev); pau_opencapi_enable_bars(dev, false); pau_opencapi_assert_odl_reset(dev); pau_opencapi_assert_adapter_reset(dev); pci_slot_set_state(slot, PAU_SLOT_FRESET_ASSERT_DELAY); /* assert for 5ms */ return pci_slot_set_sm_timeout(slot, msecs_to_tb(5)); case PAU_SLOT_FRESET_ASSERT_DELAY: rc = pau_dev_phy_reset(dev); if (rc) { PAUDEVERR(dev, "FRESET: PHY reset error\n"); return OPAL_HARDWARE; } pau_opencapi_deassert_odl_reset(dev); pau_opencapi_deassert_adapter_reset(dev); pci_slot_set_state(slot, PAU_SLOT_FRESET_DEASSERT_DELAY); /* give 250ms to device to be ready */ return pci_slot_set_sm_timeout(slot, msecs_to_tb(250)); case PAU_SLOT_FRESET_DEASSERT_DELAY: pau_opencapi_unfence_brick(dev); pau_opencapi_enable_bars(dev, true); pau_opencapi_training_mode(dev, 0b0001); /* send pattern A */ pci_slot_set_state(slot, PAU_SLOT_FRESET_INIT_DELAY); return pci_slot_set_sm_timeout(slot, msecs_to_tb(5)); case PAU_SLOT_FRESET_INIT_DELAY: pau_opencapi_training_mode(dev, 0b1000); /* enable training */ dev->train_start = mftb(); dev->train_timeout = dev->train_start + msecs_to_tb(PAU_LINK_TRAINING_TIMEOUT); pci_slot_set_state(slot, PAU_SLOT_LINK_START); return slot->ops.poll_link(slot); default: PAUDEVERR(dev, "FRESET: unexpected slot state %08x\n", slot->state); } pci_slot_set_state(slot, PAU_SLOT_NORMAL); return OPAL_HARDWARE; } static uint64_t pau_opencapi_get_odl_endpoint_info(struct pau_dev *dev) { struct pau *pau = dev->pau; uint64_t val; xscom_read(pau->chip_id, P10_OB_ODL_DLX_INFO(dev->op_unit, dev->odl_index), &val); return val; } static uint64_t pau_opencapi_get_odl_training_status(struct pau_dev *dev) { struct pau *pau = dev->pau; uint64_t val; xscom_read(pau->chip_id, P10_OB_ODL_TRAIN_STAT(dev->op_unit, dev->odl_index), &val); return val; } static uint64_t pau_opencapi_get_odl_status(struct pau_dev *dev) { struct pau *pau = dev->pau; uint64_t val; xscom_read(pau->chip_id, P10_OB_ODL_STATUS(dev->op_unit, dev->odl_index), &val); return val; } static uint64_t pau_opencapi_get_odl_link_speed_status(struct pau_dev *dev) { struct pau *pau = dev->pau; uint64_t val; xscom_read(pau->chip_id, P10_OB_ODL_LINK_SPEED_STATUS(dev->op_unit, dev->odl_index), &val); return val; } static enum OpalShpcLinkState pau_opencapi_get_link_width(uint64_t status) { uint64_t tx_lanes, rx_lanes, state; state = GETFIELD(P10_OB_ODL_STATUS_TRAINING_STATE, status); if (state != PAU_LINK_STATE_TRAINED) return OPAL_SHPC_LINK_DOWN; rx_lanes = GETFIELD(P10_OB_ODL_STATUS_RX_TRAINED_LANES, status); tx_lanes = GETFIELD(P10_OB_ODL_STATUS_TX_TRAINED_LANES, status); if ((rx_lanes != 0xFF) || (tx_lanes != 0xFF)) return OPAL_SHPC_LINK_UP_x4; else return OPAL_SHPC_LINK_UP_x8; } static int64_t pau_opencapi_get_link_state(struct pci_slot *slot, uint8_t *val) { struct pau_dev *dev = pau_phb_to_opencapi_dev(slot->phb); uint64_t status; status = pau_opencapi_get_odl_status(dev); *val = pau_opencapi_get_link_width(status); return OPAL_SUCCESS; } static int64_t pau_opencapi_get_power_state(struct pci_slot *slot, uint8_t *val) { *val = slot->power_state; return OPAL_SUCCESS; } static int64_t pau_opencapi_get_presence_state(struct pci_slot __unused * slot, uint8_t *val) { /* * Presence detection for OpenCAPI is currently done at the start of * PAU initialisation, and we only create slots if a device is present. * As such we will never be asked to get the presence of a slot that's * empty. * * This may change if we ever support hotplug down the track. */ *val = OPAL_PCI_SLOT_PRESENT; return OPAL_SUCCESS; } static void pau_opencapi_check_trained_link(struct pau_dev *dev, uint64_t status) { if (pau_opencapi_get_link_width(status) != OPAL_SHPC_LINK_UP_x8) { PAUDEVERR(dev, "Link trained in degraded mode (%016llx)\n", status); PAUDEVDBG(dev, "Link endpoint info: %016llx\n", pau_opencapi_get_odl_endpoint_info(dev)); } } static int64_t pau_opencapi_retry_state(struct pci_slot *slot, uint64_t status) { struct pau_dev *dev = pau_phb_to_opencapi_dev(slot->phb); if (!slot->link_retries--) { /** * @fwts-label OCAPILinkTrainingFailed * @fwts-advice The OpenCAPI link training procedure failed. * This indicates a hardware or firmware bug. OpenCAPI * functionality will not be available on this link. */ PAUDEVERR(dev, "Link failed to train, final link status: %016llx\n", status); PAUDEVDBG(dev, "Final link training status: %016llx (Link Speed Status: %016llx)\n", pau_opencapi_get_odl_training_status(dev), pau_opencapi_get_odl_link_speed_status(dev)); return OPAL_HARDWARE; } PAUDEVERR(dev, "Link failed to train, retrying\n"); PAUDEVERR(dev, "Link status: %016llx, training status: %016llx " "(Link Speed Status: %016llx)\n", status, pau_opencapi_get_odl_training_status(dev), pau_opencapi_get_odl_link_speed_status(dev)); pci_slot_set_state(slot, PAU_SLOT_FRESET_INIT); return pci_slot_set_sm_timeout(slot, msecs_to_tb(1)); } static void pau_opencapi_otl_tx_send_enable(struct pau_dev *dev) { struct pau *pau = dev->pau; uint64_t reg, val; /* Allows OTL TX to send out packets to AFU */ PAUDEVDBG(dev, "OTL TX Send Enable\n"); reg = PAU_OTL_MISC_CFG_TX2(dev->index); val = pau_read(pau, reg); val |= PAU_OTL_MISC_CFG_TX2_SEND_EN; pau_write(pau, reg, val); } static void pau_opencapi_setup_perf_counters(struct pau_dev *dev) { struct pau *pau = dev->pau; uint64_t reg, val; PAUDEVDBG(dev, "Setup perf counter\n"); reg = P10_OB_ODL_PERF_MON_CONFIG(dev->op_unit); xscom_read(pau->chip_id, reg, &val); val = SETFIELD(P10_OB_ODL_PERF_MON_CONFIG_ENABLE, val, P10_OB_ODL_PERF_MON_CONFIG_LINK0 >> dev->index); val = SETFIELD(P10_OB_ODL_PERF_MON_CONFIG_SIZE, val, P10_OB_ODL_PERF_MON_CONFIG_SIZE16); xscom_write(pau->chip_id, reg, val); PAUDEVDBG(dev, "perf counter config %llx = %llx\n", reg, val); reg = P10_OB_ODL_PERF_MON_SELECT(dev->op_unit); xscom_read(pau->chip_id, reg, &val); val = SETFIELD(P10_OB_ODL_PERF_MON_SELECT_COUNTER >> (dev->index * 16), val, P10_OB_ODL_PERF_MON_SELECT_CRC_ODL); val = SETFIELD(P10_OB_ODL_PERF_MON_SELECT_COUNTER >> ((dev->index * 16) + 8), val, P10_OB_ODL_PERF_MON_SELECT_CRC_DLX); xscom_write(pau->chip_id, reg, val); PAUDEVDBG(dev, "perf counter select %llx = %llx\n", reg, val); } static void pau_opencapi_check_perf_counters(struct pau_dev *dev) { struct pau *pau = dev->pau; uint64_t reg, val; reg = P10_OB_PERF_COUNTER0(dev->op_unit); xscom_read(pau->chip_id, reg, &val); if (val) PAUDEVERR(dev, "CRC error count perf_counter0..3=0%#llx\n", val); } static int64_t pau_opencapi_poll_link(struct pci_slot *slot) { struct pau_dev *dev = pau_phb_to_opencapi_dev(slot->phb); uint64_t status; switch (slot->state) { case PAU_SLOT_NORMAL: case PAU_SLOT_LINK_START: PAUDEVDBG(dev, "Start polling\n"); pci_slot_set_state(slot, PAU_SLOT_LINK_WAIT); /* fall-through */ case PAU_SLOT_LINK_WAIT: status = pau_opencapi_get_odl_status(dev); if (GETFIELD(P10_OB_ODL_STATUS_TRAINING_STATE, status) == PAU_LINK_STATE_TRAINED) { PAUDEVINF(dev, "link trained in %ld ms (Link Speed Status: %016llx)\n", tb_to_msecs(mftb() - dev->train_start), pau_opencapi_get_odl_link_speed_status(dev)); pau_opencapi_check_trained_link(dev, status); pci_slot_set_state(slot, PAU_SLOT_LINK_TRAINED); return pci_slot_set_sm_timeout(slot, msecs_to_tb(1)); } if (tb_compare(mftb(), dev->train_timeout) == TB_AAFTERB) return pau_opencapi_retry_state(slot, status); return pci_slot_set_sm_timeout(slot, msecs_to_tb(1)); case PAU_SLOT_LINK_TRAINED: pau_opencapi_otl_tx_send_enable(dev); pci_slot_set_state(slot, PAU_SLOT_NORMAL); if (dev->status & PAU_DEV_STATUS_BROKEN) { PAUDEVERR(dev, "Resetting a device which hit a " "previous error. Device recovery " "is not supported, so future behavior is undefined\n"); dev->status &= ~PAU_DEV_STATUS_BROKEN; } pau_opencapi_check_perf_counters(dev); dev->phb.scan_map = 1; return OPAL_SUCCESS; default: PAUDEVERR(dev, "unexpected slot state %08x\n", slot->state); } pci_slot_set_state(slot, PAU_SLOT_NORMAL); return OPAL_HARDWARE; } static void pau_opencapi_prepare_link_change(struct pci_slot *slot __unused, bool up __unused) { /* * PCI hotplug wants it defined, but we don't need to do anything */ } static int64_t pau_opencapi_set_power_state(struct pci_slot *slot, uint8_t val) { struct pau_dev *dev = pau_phb_to_opencapi_dev(slot->phb); switch (val) { case PCI_SLOT_POWER_OFF: PAUDEVDBG(dev, "Fake power off\n"); pau_opencapi_fence_brick(dev); pau_opencapi_assert_adapter_reset(dev); slot->power_state = PCI_SLOT_POWER_OFF; return OPAL_SUCCESS; case PCI_SLOT_POWER_ON: if (slot->power_state != PCI_SLOT_POWER_OFF) return OPAL_SUCCESS; PAUDEVDBG(dev, "Fake power on\n"); slot->power_state = PCI_SLOT_POWER_ON; slot->state = PAU_SLOT_NORMAL; return OPAL_SUCCESS; default: return OPAL_UNSUPPORTED; } } static void pau_opencapi_create_phb_slot(struct pau_dev *dev) { struct pci_slot *slot; slot = pci_slot_alloc(&dev->phb, NULL); if (!slot) { /** * @fwts-label OCAPICannotCreatePHBSlot * @fwts-advice Firmware probably ran out of memory creating * PAU slot. OpenCAPI functionality could be broken. */ PAUDEVERR(dev, "Cannot create PHB slot\n"); } /* Elementary functions */ slot->ops.creset = pau_opencapi_creset; slot->ops.hreset = pau_opencapi_hreset; slot->ops.freset = pau_opencapi_freset; slot->ops.get_link_state = pau_opencapi_get_link_state; slot->ops.get_power_state = pau_opencapi_get_power_state; slot->ops.get_presence_state = pau_opencapi_get_presence_state; slot->ops.poll_link = pau_opencapi_poll_link; slot->ops.prepare_link_change = pau_opencapi_prepare_link_change; slot->ops.set_power_state = pau_opencapi_set_power_state; /* hotplug capability */ slot->pluggable = 1; } static int64_t pau_opencapi_pcicfg_check(struct pau_dev *dev, uint32_t offset, uint32_t size) { if (!dev || offset > 0xfff || (offset & (size - 1))) return OPAL_PARAMETER; return OPAL_SUCCESS; } static int64_t pau_opencapi_pcicfg_read(struct phb *phb, uint32_t bdfn, uint32_t offset, uint32_t size, void *data) { struct pau_dev *dev = pau_phb_to_opencapi_dev(phb); uint64_t cfg_addr, genid_base; int64_t rc; rc = pau_opencapi_pcicfg_check(dev, offset, size); if (rc) return rc; /* Config Address for Brick 0 – Offset 0 * Config Address for Brick 1 – Offset 256 */ genid_base = dev->genid_bar.cfg + (dev->index << 8); cfg_addr = PAU_CTL_MISC_CFG_ADDR_ENABLE; cfg_addr = SETFIELD(PAU_CTL_MISC_CFG_ADDR_BUS_NBR | PAU_CTL_MISC_CFG_ADDR_DEVICE_NBR | PAU_CTL_MISC_CFG_ADDR_FUNCTION_NBR, cfg_addr, bdfn); cfg_addr = SETFIELD(PAU_CTL_MISC_CFG_ADDR_REGISTER_NBR, cfg_addr, offset & ~3u); out_be64((uint64_t *)genid_base, cfg_addr); sync(); switch (size) { case 1: *((uint8_t *)data) = in_8((uint8_t *)(genid_base + 128 + (offset & 3))); break; case 2: *((uint16_t *)data) = in_le16((uint16_t *)(genid_base + 128 + (offset & 2))); break; case 4: *((uint32_t *)data) = in_le32((uint32_t *)(genid_base + 128)); break; default: return OPAL_PARAMETER; } return OPAL_SUCCESS; } #define PAU_OPENCAPI_PCI_CFG_READ(size, type) \ static int64_t pau_opencapi_pcicfg_read##size(struct phb *phb, uint32_t bdfn, \ uint32_t offset, type * data) \ { \ /* Initialize data in case of error */ \ *data = (type)0xffffffff; \ return pau_opencapi_pcicfg_read(phb, bdfn, offset, sizeof(type), data); \ } static int64_t pau_opencapi_pcicfg_write(struct phb *phb, uint32_t bdfn, uint32_t offset, uint32_t size, uint32_t data) { struct pau_dev *dev = pau_phb_to_opencapi_dev(phb); uint64_t genid_base, cfg_addr; int64_t rc; rc = pau_opencapi_pcicfg_check(dev, offset, size); if (rc) return rc; /* Config Address for Brick 0 – Offset 0 * Config Address for Brick 1 – Offset 256 */ genid_base = dev->genid_bar.cfg + (dev->index << 8); cfg_addr = PAU_CTL_MISC_CFG_ADDR_ENABLE; cfg_addr = SETFIELD(PAU_CTL_MISC_CFG_ADDR_BUS_NBR | PAU_CTL_MISC_CFG_ADDR_DEVICE_NBR | PAU_CTL_MISC_CFG_ADDR_FUNCTION_NBR, cfg_addr, bdfn); cfg_addr = SETFIELD(PAU_CTL_MISC_CFG_ADDR_REGISTER_NBR, cfg_addr, offset & ~3u); out_be64((uint64_t *)genid_base, cfg_addr); sync(); switch (size) { case 1: out_8((uint8_t *)(genid_base + 128 + (offset & 3)), data); break; case 2: out_le16((uint16_t *)(genid_base + 128 + (offset & 2)), data); break; case 4: out_le32((uint32_t *)(genid_base + 128), data); break; default: return OPAL_PARAMETER; } return OPAL_SUCCESS; } #define PAU_OPENCAPI_PCI_CFG_WRITE(size, type) \ static int64_t pau_opencapi_pcicfg_write##size(struct phb *phb, uint32_t bdfn, \ uint32_t offset, type data) \ { \ return pau_opencapi_pcicfg_write(phb, bdfn, offset, sizeof(type), data);\ } PAU_OPENCAPI_PCI_CFG_READ(8, u8) PAU_OPENCAPI_PCI_CFG_READ(16, u16) PAU_OPENCAPI_PCI_CFG_READ(32, u32) PAU_OPENCAPI_PCI_CFG_WRITE(8, u8) PAU_OPENCAPI_PCI_CFG_WRITE(16, u16) PAU_OPENCAPI_PCI_CFG_WRITE(32, u32) static int64_t pau_opencapi_eeh_freeze_status(struct phb *phb __unused, uint64_t pe_num __unused, uint8_t *freeze_state, uint16_t *pci_error_type, uint16_t *severity) { *freeze_state = OPAL_EEH_STOPPED_NOT_FROZEN; *pci_error_type = OPAL_EEH_NO_ERROR; if (severity) *severity = OPAL_EEH_SEV_NO_ERROR; return OPAL_SUCCESS; } static int64_t pau_opencapi_ioda_reset(struct phb __unused * phb, bool __unused purge) { /* Not relevant to OpenCAPI - we do this just to silence the error */ return OPAL_SUCCESS; } static int64_t pau_opencapi_next_error(struct phb *phb, uint64_t *first_frozen_pe, uint16_t *pci_error_type, uint16_t *severity) { struct pau_dev *dev = pau_phb_to_opencapi_dev(phb); struct pau *pau = dev->pau; uint32_t pe_num; uint64_t val; if (!first_frozen_pe || !pci_error_type || !severity) return OPAL_PARAMETER; if (dev->status & PAU_DEV_STATUS_BROKEN) { val = pau_read(pau, PAU_MISC_BDF2PE_CFG(dev->index)); pe_num = GETFIELD(PAU_MISC_BDF2PE_CFG_PE, val); PAUDEVDBG(dev, "Reporting device as broken\n"); PAUDEVDBG(dev, "Brick %d fenced! (pe_num: %08x\n", pau_dev_index(dev, PAU_LINKS_OPENCAPI_PER_PAU), pe_num); *first_frozen_pe = pe_num; *pci_error_type = OPAL_EEH_PHB_ERROR; *severity = OPAL_EEH_SEV_PHB_DEAD; } else { *first_frozen_pe = -1; *pci_error_type = OPAL_EEH_NO_ERROR; *severity = OPAL_EEH_SEV_NO_ERROR; } return OPAL_SUCCESS; } static uint32_t pau_opencapi_dev_interrupt_level(struct pau_dev *dev) { /* Interrupt Levels * 35: Translation failure for OCAPI link 0 * 36: Translation failure for OCAPI link 1 */ const uint32_t level[2] = {35, 36}; return level[dev->index]; } static int pau_opencapi_dt_add_interrupts(struct phb *phb, struct pci_device *pd, void *data __unused) { struct pau_dev *dev = pau_phb_to_opencapi_dev(phb); struct pau *pau = dev->pau; uint64_t dsisr, dar, tfc, handle; uint32_t irq; irq = pau->irq_base + pau_opencapi_dev_interrupt_level(dev); /* When an address translation fail causes the PAU to send an * interrupt, information is stored in three registers for use * by the interrupt handler. The OS accesses them by mmio. */ dsisr = pau->regs[0] + PAU_OTL_MISC_PSL_DSISR_AN(dev->index); dar = pau->regs[0] + PAU_OTL_MISC_PSL_DAR_AN(dev->index); tfc = pau->regs[0] + PAU_OTL_MISC_PSL_TFC_AN(dev->index); handle = pau->regs[0] + PAU_OTL_MISC_PSL_PEHANDLE_AN(dev->index); dt_add_property_cells(pd->dn, "ibm,opal-xsl-irq", irq); dt_add_property_cells(pd->dn, "ibm,opal-xsl-mmio", hi32(dsisr), lo32(dsisr), hi32(dar), lo32(dar), hi32(tfc), lo32(tfc), hi32(handle), lo32(handle)); return 0; } static void pau_opencapi_phb_final_fixup(struct phb *phb) { pci_walk_dev(phb, NULL, pau_opencapi_dt_add_interrupts, NULL); } static int64_t pau_opencapi_set_pe(struct phb *phb, uint64_t pe_num, uint64_t bdfn, uint8_t bcompare, uint8_t dcompare, uint8_t fcompare, uint8_t action) { struct pau_dev *dev = pau_phb_to_opencapi_dev(phb); struct pau *pau = dev->pau; uint64_t val; PAUDEVDBG(dev, "Set partitionable endpoint = %08llx, bdfn = %08llx\n", pe_num, bdfn); if (action != OPAL_MAP_PE && action != OPAL_UNMAP_PE) return OPAL_PARAMETER; if (pe_num >= PAU_MAX_PE_NUM) return OPAL_PARAMETER; if (bcompare != OpalPciBusAll || dcompare != OPAL_COMPARE_RID_DEVICE_NUMBER || fcompare != OPAL_COMPARE_RID_FUNCTION_NUMBER) return OPAL_UNSUPPORTED; val = PAU_MISC_BDF2PE_CFG_ENABLE; val = SETFIELD(PAU_MISC_BDF2PE_CFG_PE, val, pe_num); val = SETFIELD(PAU_MISC_BDF2PE_CFG_BDF, val, 0); pau_write(pau, PAU_MISC_BDF2PE_CFG(dev->index), val); return OPAL_SUCCESS; } static const struct phb_ops pau_opencapi_ops = { .cfg_read8 = pau_opencapi_pcicfg_read8, .cfg_read16 = pau_opencapi_pcicfg_read16, .cfg_read32 = pau_opencapi_pcicfg_read32, .cfg_write8 = pau_opencapi_pcicfg_write8, .cfg_write16 = pau_opencapi_pcicfg_write16, .cfg_write32 = pau_opencapi_pcicfg_write32, .eeh_freeze_status = pau_opencapi_eeh_freeze_status, .next_error = pau_opencapi_next_error, .ioda_reset = pau_opencapi_ioda_reset, .phb_final_fixup = pau_opencapi_phb_final_fixup, .set_pe = pau_opencapi_set_pe, }; static void pau_opencapi_create_phb(struct pau_dev *dev) { struct phb *phb = &dev->phb; uint64_t mm_win[2]; mm_win[0] = dev->ntl_bar.addr; mm_win[1] = dev->ntl_bar.size; phb->phb_type = phb_type_pau_opencapi; phb->scan_map = 0; phb->ops = &pau_opencapi_ops; phb->dt_node = dt_new_addr(dt_root, "pciex", mm_win[0]); assert(phb->dt_node); pci_register_phb(phb, pau_get_opal_id(dev->pau->chip_id, pau_get_phb_index(dev->pau->index, dev->index))); pau_opencapi_create_phb_slot(dev); } static void pau_opencapi_dt_add_mmio_window(struct pau_dev *dev) { struct dt_node *dn = dev->phb.dt_node; uint64_t mm_win[2]; mm_win[0] = dev->ntl_bar.addr; mm_win[1] = dev->ntl_bar.size; PAUDEVDBG(dev, "Setting AFU MMIO window to %016llx %016llx\n", mm_win[0], mm_win[1]); dt_add_property(dn, "reg", mm_win, sizeof(mm_win)); dt_add_property(dn, "ibm,mmio-window", mm_win, sizeof(mm_win)); dt_add_property_cells(dn, "ranges", 0x02000000, hi32(mm_win[0]), lo32(mm_win[0]), hi32(mm_win[0]), lo32(mm_win[0]), hi32(mm_win[1]), lo32(mm_win[1])); } static void pau_opencapi_dt_add_hotpluggable(struct pau_dev *dev) { struct pci_slot *slot = dev->phb.slot; struct dt_node *dn = dev->phb.dt_node; char label[40]; /* * Add a few definitions to the DT so that the linux PCI * hotplug framework can find the slot and identify it as * hot-pluggable. * * The "ibm,slot-label" property is used by linux as the slot name */ pci_slot_add_dt_properties(slot, dn); snprintf(label, sizeof(label), "OPENCAPI-%04x", (int)PCI_SLOT_PHB_INDEX(slot->id)); dt_add_property_string(dn, "ibm,slot-label", label); } static void pau_opencapi_dt_add_props(struct pau_dev *dev) { struct dt_node *dn = dev->phb.dt_node; struct pau *pau = dev->pau; dt_add_property_strings(dn, "compatible", "ibm,power10-pau-opencapi-pciex", "ibm,ioda3-pau-opencapi-phb", "ibm,ioda2-npu2-opencapi-phb"); dt_add_property_cells(dn, "#address-cells", 3); dt_add_property_cells(dn, "#size-cells", 2); dt_add_property_cells(dn, "#interrupt-cells", 1); dt_add_property_cells(dn, "bus-range", 0, 0xff); dt_add_property_cells(dn, "clock-frequency", 0x200, 0); dt_add_property_cells(dn, "interrupt-parent", get_ics_phandle()); dt_add_property_strings(dn, "device_type", "pciex"); dt_add_property_cells(dn, "ibm,pau-index", pau->index); dt_add_property_cells(dn, "ibm,chip-id", pau->chip_id); dt_add_property_cells(dn, "ibm,xscom-base", pau->xscom_base); dt_add_property_cells(dn, "ibm,npcq", pau->dt_node->phandle); dt_add_property_cells(dn, "ibm,links", 1); dt_add_property_cells(dn, "ibm,phb-diag-data-size", 0); dt_add_property_cells(dn, "ibm,opal-num-pes", PAU_MAX_PE_NUM); dt_add_property_cells(dn, "ibm,opal-reserved-pe", PAU_RESERVED_PE_NUM); pau_opencapi_dt_add_mmio_window(dev); pau_opencapi_dt_add_hotpluggable(dev); } static void pau_opencapi_set_transport_mux_controls(struct pau_dev *dev) { struct pau *pau = dev->pau; uint32_t typemap = 0; uint64_t reg, val = 0; PAUDEVDBG(dev, "Setting transport mux controls\n"); typemap = 0x2 >> dev->index; reg = PAU_MISC_OPTICAL_IO_CONFIG; val = pau_read(pau, reg); typemap |= GETFIELD(PAU_MISC_OPTICAL_IO_CONFIG_OTL, val); val = SETFIELD(PAU_MISC_OPTICAL_IO_CONFIG_OTL, val, typemap); pau_write(pau, reg, val); } static void pau_opencapi_odl_config_phy(struct pau_dev *dev) { struct pau *pau = dev->pau; uint8_t typemap = 0; uint64_t reg, val; PAUDEVDBG(dev, "Configure ODL\n"); /* ODL must be in reset when enabling. * It stays in reset until the link is trained */ pau_opencapi_assert_odl_reset(dev); /* DLO (Open CAPI links) */ typemap = 0x2 >> dev->odl_index; reg = P10_OB_ODL_PHY_CONFIG(dev->op_unit); xscom_read(pau->chip_id, reg, &val); typemap |= GETFIELD(P10_OB_ODL_PHY_CONFIG_LINK_SELECT, val); val = SETFIELD(P10_OB_ODL_PHY_CONFIG_LINK_SELECT, val, typemap); val = SETFIELD(P10_OB_ODL_PHY_CONFIG_DL_SELECT, val, 0b10); xscom_write(pau->chip_id, reg, val); } static void pau_opencapi_enable_xsl_clocks(struct pau *pau) { uint64_t reg, val; PAUDBG(pau, "Enable clocks in XSL\n"); reg = PAU_XSL_WRAP_CFG; val = pau_read(pau, reg); val |= PAU_XSL_WRAP_CFG_CLOCK_ENABLE; pau_write(pau, reg, val); } static void pau_opencapi_enable_misc_clocks(struct pau *pau) { uint64_t reg, val; PAUDBG(pau, "Enable clocks in MISC\n"); /* clear any spurious NDL stall or no_stall_c_err_rpts */ reg = PAU_MISC_HOLD; val = pau_read(pau, reg); val = SETFIELD(PAU_MISC_HOLD_NDL_STALL, val, 0b0000); pau_write(pau, reg, val); reg = PAU_MISC_CONFIG; val = pau_read(pau, reg); val |= PAU_MISC_CONFIG_OC_MODE; pau_write(pau, reg, val); } static void pau_opencapi_set_npcq_config(struct pau *pau) { struct pau_dev *dev; uint8_t oc_typemap = 0; uint64_t reg, val; /* MCP_MISC_CFG0 * SNP_MISC_CFG0 done in pau_opencapi_enable_pb */ pau_for_each_opencapi_dev(dev, pau) oc_typemap |= 0x10 >> dev->index; PAUDBG(pau, "Set NPCQ Config\n"); reg = PAU_CTL_MISC_CFG2; val = pau_read(pau, reg); val = SETFIELD(PAU_CTL_MISC_CFG2_OCAPI_MODE, val, oc_typemap); val = SETFIELD(PAU_CTL_MISC_CFG2_OCAPI_4, val, oc_typemap); val = SETFIELD(PAU_CTL_MISC_CFG2_OCAPI_C2, val, oc_typemap); val = SETFIELD(PAU_CTL_MISC_CFG2_OCAPI_AMO, val, oc_typemap); val = SETFIELD(PAU_CTL_MISC_CFG2_OCAPI_MEM_OS_BIT, val, oc_typemap); pau_write(pau, reg, val); reg = PAU_DAT_MISC_CFG1; val = pau_read(pau, reg); val = SETFIELD(PAU_DAT_MISC_CFG1_OCAPI_MODE, val, oc_typemap); pau_write(pau, reg, val); } static void pau_opencapi_enable_xsl_xts_interfaces(struct pau *pau) { uint64_t reg, val; PAUDBG(pau, "Enable XSL-XTS Interfaces\n"); reg = PAU_XTS_CFG; val = pau_read(pau, reg); val |= PAU_XTS_CFG_OPENCAPI; pau_write(pau, reg, val); reg = PAU_XTS_CFG2; val = pau_read(pau, reg); val |= PAU_XTS_CFG2_XSL2_ENA; pau_write(pau, reg, val); } static void pau_opencapi_enable_sm_allocation(struct pau *pau) { uint64_t reg, val; PAUDBG(pau, "Enable State Machine Allocation\n"); reg = PAU_MISC_MACHINE_ALLOC; val = pau_read(pau, reg); val |= PAU_MISC_MACHINE_ALLOC_ENABLE; pau_write(pau, reg, val); } static void pau_opencapi_enable_powerbus(struct pau *pau) { struct pau_dev *dev; uint8_t oc_typemap = 0; uint64_t reg, val; PAUDBG(pau, "Enable PowerBus\n"); pau_for_each_opencapi_dev(dev, pau) oc_typemap |= 0x10 >> dev->index; /* PowerBus interfaces must be enabled prior to MMIO */ reg = PAU_MCP_MISC_CFG0; val = pau_read(pau, reg); val |= PAU_MCP_MISC_CFG0_ENABLE_PBUS; val |= PAU_MCP_MISC_CFG0_MA_MCRESP_OPT_WRP; val = SETFIELD(PAU_MCP_MISC_CFG0_OCAPI_MODE, val, oc_typemap); pau_write(pau, reg, val); reg = PAU_SNP_MISC_CFG0; val = pau_read(pau, reg); val |= PAU_SNP_MISC_CFG0_ENABLE_PBUS; val = SETFIELD(PAU_SNP_MISC_CFG0_OCAPI_MODE, val, oc_typemap); val = SETFIELD(PAU_SNP_MISC_CFG0_OCAPI_C2, val, oc_typemap); pau_write(pau, reg, val); } static void pau_opencapi_tl_config(struct pau_dev *dev) { struct pau *pau = dev->pau; uint64_t val; PAUDEVDBG(dev, "TL Configuration\n"); /* OTL Config 0 */ val = 0; val |= PAU_OTL_MISC_CFG0_EN; val |= PAU_OTL_MISC_CFG0_BLOCK_PE_HANDLE; val = SETFIELD(PAU_OTL_MISC_CFG0_BRICKID, val, dev->index); val |= PAU_OTL_MISC_CFG0_ENABLE_4_0; val |= PAU_OTL_MISC_CFG0_XLATE_RELEASE; val |= PAU_OTL_MISC_CFG0_ENABLE_5_0; pau_write(pau, PAU_OTL_MISC_CFG0(dev->index), val); /* OTL Config 1 */ val = 0; val = SETFIELD(PAU_OTL_MISC_CFG_TX_DRDY_WAIT, val, 0b010); val = SETFIELD(PAU_OTL_MISC_CFG_TX_TEMP0_RATE, val, 0b0000); val = SETFIELD(PAU_OTL_MISC_CFG_TX_TEMP1_RATE, val, 0b0011); val = SETFIELD(PAU_OTL_MISC_CFG_TX_TEMP2_RATE, val, 0b0111); val = SETFIELD(PAU_OTL_MISC_CFG_TX_TEMP3_RATE, val, 0b0010); val = SETFIELD(PAU_OTL_MISC_CFG_TX_CRET_FREQ, val, 0b001); pau_write(pau, PAU_OTL_MISC_CFG_TX(dev->index), val); /* OTL Config 2 - Done after link training, in otl_tx_send_enable() */ /* TLX Credit Configuration */ val = 0; val = SETFIELD(PAU_OTL_MISC_CFG_TLX_CREDITS_VC0, val, 0x40); val = SETFIELD(PAU_OTL_MISC_CFG_TLX_CREDITS_VC1, val, 0x40); val = SETFIELD(PAU_OTL_MISC_CFG_TLX_CREDITS_VC2, val, 0x40); val = SETFIELD(PAU_OTL_MISC_CFG_TLX_CREDITS_VC3, val, 0x40); val = SETFIELD(PAU_OTL_MISC_CFG_TLX_CREDITS_DCP0, val, 0x80); val = SETFIELD(PAU_OTL_MISC_CFG_TLX_CREDITS_SPARE, val, 0x80); val = SETFIELD(PAU_OTL_MISC_CFG_TLX_CREDITS_DCP2, val, 0x80); val = SETFIELD(PAU_OTL_MISC_CFG_TLX_CREDITS_DCP3, val, 0x80); pau_write(pau, PAU_OTL_MISC_CFG_TLX_CREDITS(dev->index), val); } static void pau_opencapi_enable_otlcq_interface(struct pau_dev *dev) { struct pau *pau = dev->pau; uint8_t typemap = 0; uint64_t reg, val; PAUDEVDBG(dev, "Enabling OTL-CQ Interface\n"); typemap |= 0x10 >> dev->index; reg = PAU_CTL_MISC_CFG0; val = pau_read(pau, reg); typemap |= GETFIELD(PAU_CTL_MISC_CFG0_OTL_ENABLE, val); val = SETFIELD(PAU_CTL_MISC_CFG0_OTL_ENABLE, val, typemap); pau_write(pau, reg, val); } static void pau_opencapi_address_translation_config(struct pau_dev *dev) { struct pau *pau = dev->pau; uint64_t reg, val; PAUDEVDBG(dev, "Address Translation Configuration\n"); /* OpenCAPI 4.0 Mode */ reg = PAU_XSL_OSL_XLATE_CFG(dev->index); val = pau_read(pau, reg); val |= PAU_XSL_OSL_XLATE_CFG_AFU_DIAL; val &= ~PAU_XSL_OSL_XLATE_CFG_OPENCAPI3; pau_write(pau, reg, val); /* MMIO shootdowns (OpenCAPI 5.0) */ reg = PAU_XTS_CFG3; val = pau_read(pau, reg); val |= PAU_XTS_CFG3_MMIOSD_OCAPI; pau_write(pau, reg, val); /* XSL_GP - use defaults */ } static void pau_opencapi_enable_interrupt_on_error(struct pau_dev *dev) { struct pau *pau = dev->pau; uint64_t reg, val; PAUDEVDBG(dev, "Enable Interrupt-on-error\n"); /* translation fault */ reg = PAU_MISC_INT_2_CONFIG; val = pau_read(pau, reg); val |= PAU_MISC_INT_2_CONFIG_XFAULT_2_5(dev->index); pau_write(pau, reg, val); /* freeze disable */ reg = PAU_MISC_FREEZE_1_CONFIG; val = pau_read(pau, reg); val &= ~PAU_FIR1_NDL_BRICKS_0_5; val &= ~PAU_FIR1_NDL_BRICKS_6_11; pau_write(pau, reg, val); /* fence disable */ reg = PAU_MISC_FENCE_1_CONFIG; val = pau_read(pau, reg); val &= ~PAU_FIR1_NDL_BRICKS_0_5; val &= ~PAU_FIR1_NDL_BRICKS_6_11; pau_write(pau, reg, val); /* irq disable */ reg = PAU_MISC_INT_1_CONFIG; val = pau_read(pau, reg); val &= ~PAU_FIR1_NDL_BRICKS_0_5; val &= ~PAU_FIR1_NDL_BRICKS_6_11; pau_write(pau, reg, val); } static void pau_opencapi_enable_ref_clock(struct pau_dev *dev) { uint64_t reg, val; int bit; switch (dev->pau_unit) { case 0: if (dev->index == 0) bit = 16; else bit = 17; break; case 3: if (dev->index == 0) bit = 18; else bit = 19; break; case 4: bit = 20; break; case 5: bit = 21; break; case 6: bit = 22; break; case 7: bit = 23; break; default: assert(false); } reg = P10_ROOT_CONTROL_7; xscom_read(dev->pau->chip_id, reg, &val); val |= PPC_BIT(bit); PAUDEVDBG(dev, "Enabling ref clock for PAU%d => %llx\n", dev->pau_unit, val); xscom_write(dev->pau->chip_id, reg, val); } static void pau_opencapi_init_hw(struct pau *pau) { struct pau_dev *dev = NULL; pau_opencapi_mask_firs(pau); pau_opencapi_assign_bars(pau); pau_opencapi_setup_irqs(pau); /* Create phb */ pau_for_each_opencapi_dev(dev, pau) { PAUDEVINF(dev, "Create phb\n"); pau_opencapi_create_phb(dev); pau_opencapi_enable_bars(dev, true); pau_opencapi_dt_add_props(dev); } /* Procedure 17.1.3.1 - Enabling OpenCAPI */ pau_for_each_opencapi_dev(dev, pau) { PAUDEVINF(dev, "Configuring link ...\n"); pau_opencapi_set_transport_mux_controls(dev); /* step 1 */ pau_opencapi_odl_config_phy(dev); } pau_opencapi_enable_xsl_clocks(pau); /* step 2 */ pau_opencapi_enable_misc_clocks(pau); /* step 3 */ /* OTL disabled */ pau_for_each_opencapi_dev(dev, pau) pau_opencapi_set_fence_control(dev, 0b01); pau_opencapi_set_npcq_config(pau); /* step 4 */ pau_opencapi_enable_xsl_xts_interfaces(pau); /* step 5 */ pau_opencapi_enable_sm_allocation(pau); /* step 6 */ pau_opencapi_enable_powerbus(pau); /* step 7 */ /* * access to the PAU registers through mmio requires setting * up the PAU mmio BAR (in pau_opencapi_assign_bars() above) * and machine state allocation */ pau->mmio_access = true; pau_for_each_opencapi_dev(dev, pau) { /* Procedure 17.1.3.4 - Transaction Layer Configuration * OCAPI Link Transaction Layer functions */ pau_opencapi_tl_config(dev); /* Procedure 17.1.3.4.1 - Enabling OTL-CQ Interface */ pau_opencapi_enable_otlcq_interface(dev); /* Procedure 17.1.3.4.2 - Place OTL into Reset State * Reset (Fence) both OTL and the PowerBus for this * Brick */ pau_opencapi_set_fence_control(dev, 0b11); /* Take PAU out of OTL Reset State * Reset (Fence) only the PowerBus for this Brick, OTL * will be operational */ pau_opencapi_set_fence_control(dev, 0b10); /* Procedure 17.1.3.5 - Address Translation Configuration */ pau_opencapi_address_translation_config(dev); /* Procedure 17.1.3.6 - AFU Memory Range BARs */ /* Will be done out of this process */ /* Procedure 17.1.3.8 - AFU MMIO Range BARs */ /* done in pau_opencapi_assign_bars() */ /* Procedure 17.1.3.9 - AFU Config BARs */ /* done in pau_opencapi_assign_bars() */ /* Precedure 17.1.3.10 - Relaxed Ordering Configuration */ /* Procedure 17.1.3.10.1 - Generation-Id Registers MMIO Bars */ /* done in pau_opencapi_assign_bars() */ /* Procedure 17.1.3.10.2 - Relaxed Ordering Source Configuration */ /* For an OpenCAPI AFU that uses M2 Memory Mode, * Relaxed Ordering can be used for accesses to the * AFU's memory */ /* Procedure 17.1.3.11 - Interrupt Configuration */ /* done in pau_opencapi_setup_irqs() */ pau_opencapi_enable_interrupt_on_error(dev); /* enable performance monitor */ pau_opencapi_setup_perf_counters(dev); /* Reset disabled. Place OTLs into Run State */ pau_opencapi_set_fence_control(dev, 0b00); /* Enable reference clock */ pau_opencapi_enable_ref_clock(dev); } } static void pau_opencapi_init(struct pau *pau) { if (!pau_next_dev(pau, NULL, PAU_DEV_TYPE_OPENCAPI)) return; assert(platform.ocapi); pau_opencapi_init_hw(pau); disable_fast_reboot("OpenCAPI device enabled"); } static void pau_init(struct pau *pau) { struct pau_dev *dev; platform.pau_device_detect(pau); pau_for_each_dev(dev, pau) pau_device_detect_fixup(dev); pau_opencapi_init(pau); } void probe_pau(void) { struct dt_node *dn; struct pau *pau; /* This can be removed when/if we decide to use HDAT instead */ if (!pau_dt_create()) return; if (!platform.pau_device_detect) { prlog(PR_INFO, "PAU: Platform does not support PAU\n"); return; } dt_for_each_compatible(dt_root, dn, "ibm,power10-pau") { pau = pau_create(dn); pau_init(pau); } }