| /* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later |
| * Copyright 2021 IBM Corp. |
| */ |
| |
| #include <interrupts.h> |
| #include <pci-slot.h> |
| #include <phys-map.h> |
| #include <xive.h> |
| #include <pau.h> |
| #include <pau-regs.h> |
| #include <xscom-p10-regs.h> |
| |
| /* Number of PEs supported */ |
| #define PAU_MAX_PE_NUM 16 |
| #define PAU_RESERVED_PE_NUM 15 |
| |
| #define PAU_TL_MAX_TEMPLATE 63 |
| #define PAU_TL_RATE_BUF_SIZE 32 |
| |
| #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"); |
| } |
| |
| int64_t pau_opencapi_map_atsd_lpar(struct phb *phb, uint64_t __unused bdf, |
| uint64_t lparid, uint64_t __unused lpcr) |
| { |
| struct pau_dev *dev = pau_phb_to_opencapi_dev(phb); |
| struct pau *pau = dev->pau; |
| uint64_t val; |
| |
| if (lparid >= PAU_XTS_ATSD_MAX) |
| return OPAL_PARAMETER; |
| |
| lock(&pau->lock); |
| |
| /* We need to allocate an ATSD per link */ |
| val = SETFIELD(PAU_XTS_ATSD_HYP_LPARID, 0ull, lparid); |
| if (!lparid) |
| val |= PAU_XTS_ATSD_HYP_MSR_HV; |
| |
| pau_write(pau, PAU_XTS_ATSD_HYP(lparid), val); |
| |
| unlock(&pau->lock); |
| return OPAL_SUCCESS; |
| } |
| |
| int64_t pau_opencapi_spa_setup(struct phb *phb, uint32_t __unused bdfn, |
| uint64_t addr, uint64_t PE_mask) |
| { |
| struct pau_dev *dev = pau_phb_to_opencapi_dev(phb); |
| struct pau *pau = dev->pau; |
| uint64_t reg, val; |
| int64_t rc; |
| |
| lock(&pau->lock); |
| |
| reg = PAU_XSL_OSL_SPAP_AN(dev->index); |
| val = pau_read(pau, reg); |
| if ((addr && (val & PAU_XSL_OSL_SPAP_AN_EN)) || |
| (!addr && !(val & PAU_XSL_OSL_SPAP_AN_EN))) { |
| rc = OPAL_BUSY; |
| goto out; |
| } |
| |
| /* SPA is disabled by passing a NULL address */ |
| val = addr; |
| if (addr) |
| val = addr | PAU_XSL_OSL_SPAP_AN_EN; |
| pau_write(pau, reg, val); |
| |
| /* |
| * set the PE mask that the OS uses for PASID -> PE handle |
| * conversion |
| */ |
| reg = PAU_OTL_MISC_CFG0(dev->index); |
| val = pau_read(pau, reg); |
| val = SETFIELD(PAU_OTL_MISC_CFG0_PE_MASK, val, PE_mask); |
| pau_write(pau, reg, val); |
| rc = OPAL_SUCCESS; |
| out: |
| unlock(&pau->lock); |
| return rc; |
| } |
| |
| int64_t pau_opencapi_spa_clear_cache(struct phb *phb, |
| uint32_t __unused bdfn, |
| uint64_t PE_handle) |
| { |
| struct pau_dev *dev = pau_phb_to_opencapi_dev(phb); |
| struct pau *pau = dev->pau; |
| uint64_t reg, val; |
| int64_t rc, retries = 5; |
| |
| lock(&pau->lock); |
| |
| reg = PAU_XSL_OSL_CCINV; |
| val = pau_read(pau, reg); |
| if (val & PAU_XSL_OSL_CCINV_PENDING) { |
| rc = OPAL_BUSY; |
| goto out; |
| } |
| |
| val = PAU_XSL_OSL_CCINV_REMOVE; |
| val |= SETFIELD(PAU_XSL_OSL_CCINV_PE_HANDLE, val, PE_handle); |
| if (dev->index) |
| val |= PAU_XSL_OSL_CCINV_BRICK; |
| pau_write(pau, reg, val); |
| |
| rc = OPAL_HARDWARE; |
| while (retries--) { |
| val = pau_read(pau, reg); |
| if (!(val & PAU_XSL_OSL_CCINV_PENDING)) { |
| rc = OPAL_SUCCESS; |
| break; |
| } |
| /* the bit expected to flip in less than 200us */ |
| time_wait_us(200); |
| } |
| out: |
| unlock(&pau->lock); |
| return rc; |
| } |
| |
| static int pau_opencapi_get_templ_rate(unsigned int templ, |
| char *rate_buf) |
| { |
| int shift, idx, val; |
| |
| /* |
| * Each rate is encoded over 4 bits (0->15), with 15 being the |
| * slowest. The buffer is a succession of rates for all the |
| * templates. The first 4 bits are for template 63, followed |
| * by 4 bits for template 62, ... etc. So the rate for |
| * template 0 is at the very end of the buffer. |
| */ |
| idx = (PAU_TL_MAX_TEMPLATE - templ) / 2; |
| shift = 4 * (1 - ((PAU_TL_MAX_TEMPLATE - templ) % 2)); |
| val = rate_buf[idx] >> shift; |
| return val; |
| } |
| |
| static bool pau_opencapi_is_templ_supported(unsigned int templ, |
| long capabilities) |
| { |
| return !!(capabilities & (1ull << templ)); |
| } |
| |
| int64_t pau_opencapi_tl_set(struct phb *phb, uint32_t __unused bdfn, |
| long capabilities, char *rate_buf) |
| { |
| struct pau_dev *dev = pau_phb_to_opencapi_dev(phb); |
| struct pau *pau; |
| uint64_t reg, val, templ_rate; |
| int i, rate_pos; |
| |
| if (!dev) |
| return OPAL_PARAMETER; |
| pau = dev->pau; |
| |
| /* The 'capabilities' argument defines what TL template the |
| * device can receive. OpenCAPI 5.0 defines 64 templates, so |
| * that's one bit per template. |
| * |
| * For each template, the device processing time may vary, so |
| * the device advertises at what rate a message of a given |
| * template can be sent. That's encoded in the 'rate' buffer. |
| * |
| * On P10, PAU only knows about TL templates 0 -> 3. |
| * Per the spec, template 0 must be supported. |
| */ |
| if (!pau_opencapi_is_templ_supported(0, capabilities)) |
| return OPAL_PARAMETER; |
| |
| reg = PAU_OTL_MISC_CFG_TX(dev->index); |
| val = pau_read(pau, reg); |
| val &= ~PAU_OTL_MISC_CFG_TX_TEMP1_EN; |
| val &= ~PAU_OTL_MISC_CFG_TX_TEMP2_EN; |
| val &= ~PAU_OTL_MISC_CFG_TX_TEMP3_EN; |
| |
| for (i = 0; i < 4; i++) { |
| /* Skip template 0 as it is implicitly enabled. |
| * Enable other template If supported by AFU |
| */ |
| if (i && pau_opencapi_is_templ_supported(i, capabilities)) |
| val |= PAU_OTL_MISC_CFG_TX_TEMP_EN(i); |
| /* The tx rate should still be set for template 0 */ |
| templ_rate = pau_opencapi_get_templ_rate(i, rate_buf); |
| rate_pos = 8 + i * 4; |
| val = SETFIELD(PAU_OTL_MISC_CFG_TX_TEMP_RATE(rate_pos, rate_pos + 3), |
| val, templ_rate); |
| } |
| pau_write(pau, reg, val); |
| PAUDEVDBG(dev, "OTL configuration register set to %llx\n", val); |
| |
| return OPAL_SUCCESS; |
| } |
| |
| static int64_t pau_opencapi_afu_memory_bars(struct pau_dev *dev, |
| uint64_t size, |
| uint64_t *bar) |
| { |
| struct pau *pau = dev->pau; |
| uint64_t addr, psize; |
| uint64_t reg, val; |
| |
| PAUDEVDBG(dev, "Setup AFU Memory BARs\n"); |
| |
| if (dev->memory_bar.enable) { |
| PAUDEVERR(dev, "AFU memory allocation failed - BAR already in use\n"); |
| return OPAL_RESOURCE; |
| } |
| |
| phys_map_get(pau->chip_id, OCAPI_MEM, |
| dev->index, |
| &addr, &psize); |
| |
| if (size > psize) { |
| PAUDEVERR(dev, "Invalid AFU memory BAR allocation size " |
| "requested: 0x%llx bytes (limit 0x%llx)\n", |
| size, psize); |
| return OPAL_PARAMETER; |
| } |
| |
| if (size < (1 << 30)) |
| size = 1 << 30; |
| |
| dev->memory_bar.enable = true; |
| dev->memory_bar.addr = addr; |
| dev->memory_bar.size = size; |
| |
| reg = PAU_GPU_MEM_BAR(dev->index); |
| val = PAU_GPU_MEM_BAR_ENABLE | |
| PAU_GPU_MEM_BAR_POISON; |
| val = SETFIELD(PAU_GPU_MEM_BAR_ADDR, val, addr >> 30); |
| if (!is_pow2(size)) |
| size = 1ull << (ilog2(size) + 1); |
| |
| size = (size >> 30) - 1; |
| val = SETFIELD(PAU_GPU_MEM_BAR_SIZE, val, size); |
| pau_write(pau, reg, val); |
| |
| reg = PAU_CTL_MISC_GPU_MEM_BAR(dev->index); |
| pau_write(pau, reg, val); |
| |
| reg = PAU_XSL_GPU_MEM_BAR(dev->index); |
| pau_write(pau, reg, val); |
| |
| *bar = addr; |
| return OPAL_SUCCESS; |
| } |
| |
| int64_t pau_opencapi_mem_alloc(struct phb *phb, uint32_t __unused bdfn, |
| uint64_t size, uint64_t *bar) |
| { |
| struct pau_dev *dev = pau_phb_to_opencapi_dev(phb); |
| int64_t rc; |
| |
| if (!dev) |
| return OPAL_PARAMETER; |
| |
| if (!opal_addr_valid(bar)) |
| return OPAL_PARAMETER; |
| |
| lock(&dev->pau->lock); |
| rc = pau_opencapi_afu_memory_bars(dev, size, bar); |
| |
| unlock(&dev->pau->lock); |
| return rc; |
| } |
| |
| int64_t pau_opencapi_mem_release(struct phb *phb, uint32_t __unused bdfn) |
| { |
| struct pau_dev *dev = pau_phb_to_opencapi_dev(phb); |
| |
| if (!dev) |
| return OPAL_PARAMETER; |
| |
| lock(&dev->pau->lock); |
| pau_write(dev->pau, PAU_GPU_MEM_BAR(dev->index), 0ull); |
| pau_write(dev->pau, PAU_CTL_MISC_GPU_MEM_BAR(dev->index), 0ull); |
| pau_write(dev->pau, PAU_XSL_GPU_MEM_BAR(dev->index), 0ull); |
| |
| dev->memory_bar.enable = false; |
| dev->memory_bar.addr = 0ull; |
| dev->memory_bar.size = 0ull; |
| unlock(&dev->pau->lock); |
| |
| return OPAL_SUCCESS; |
| } |
| |
| #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_dt_add_mmio_atsd(struct pau_dev *dev) |
| { |
| struct dt_node *dn = dev->phb.dt_node; |
| struct pau *pau = dev->pau; |
| uint64_t mmio_atsd[PAU_XTS_ATSD_MAX]; |
| |
| for (uint32_t i = 0; i < PAU_XTS_ATSD_MAX; i++) |
| mmio_atsd[i] = pau->regs[0] + PAU_XTS_ATSD_LAUNCH(i); |
| |
| dt_add_property(dn, "ibm,mmio-atsd", mmio_atsd, sizeof(mmio_atsd)); |
| } |
| |
| 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_dt_add_mmio_atsd(dev); |
| 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); |
| } |
| |
| static 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); |
| } |
| } |
| DEFINE_HWPROBE_DEPS(pau, probe_pau, "phb4"); |