// SPDX-License-Identifier: Apache-2.0 /* * Copyright 2020 IBM Corp. */ #include #include #define PAU_PHY_INIT_TIMEOUT 8000 /* ms */ #define PAU_PHY_ADDR_REG 0x10012C0D #define PAU_PHY_ADDR_CHIPLET PPC_BITMASK(32, 39) #define PAU_PHY_ADDR_SRAM_ADDR PPC_BITMASK(15, 31) #define PAU_PHY_DATA_REG 0x10012C0E #define PAU_PHY_DATA_CHIPLET PPC_BITMASK(32, 39) #define PAU_MAX_PHY_LANE 18 /* * We configure the PHY using the memory mapped SRAM, which is * accessible through a pair of (addr, data) registers. The caveat is * that accesses to the SRAM must be 64-bit aligned, yet the PHY * registers are 16-bit, so special care is needed. * * A PAU chiplet may control up to 2 OP units = 4 links and each link * has its own virtual PHB in skiboot. They can be initialized or * reset concurrently so we need a lock when accessing the SRAM. * See section "5.2.5 PPE SRAM" of the workbook for the layout of the * SRAM registers. Here is the subset of the table which is meaningful * for us, since we're only touching a few registers: * * Address Bytes Linker Symbol Description * FFFF_11B0 16 _fw_regs0_start fw_regs for thread 0 * FFFF_11C0 16 _fw_regs1_start fw_regs for thread 1 * * FFFF_2800 1024 _mem_regs0_start mem_regs for thread 0 * FFFF_2C00 1024 _mem_regs1_start mem_regs for thread 1 * * In each PAU, per-group registers are replicated for every OP (each * OP units is being called a 'thread' in the workbook). * Per-lane registers have an offset < 0x10 and are replicated for * each lane. Their offset in their section is: * 0byyyyyxxxx (y = 5-bit lane number, x = 4-bit per-lane register offset) */ struct PPE_sram_section { uint32_t offset; uint32_t size; }; static struct PPE_sram_section PPE_FIRMWARE = { 0x111B0, 0x10 }; static struct PPE_sram_section PPE_MEMORY = { 0x12800, 0x400 }; struct PPE_sram_reg { struct PPE_sram_section *section; uint32_t offset; }; /* PPE firmware */ static struct PPE_sram_reg PAU_PHY_EXT_CMD_LANES_00_15 = { &PPE_FIRMWARE, 0x000 }; static struct PPE_sram_reg PAU_PHY_EXT_CMD_LANES_16_31 = { &PPE_FIRMWARE, 0x001 }; static struct PPE_sram_reg PAU_PHY_EXT_CMD_REQ = { &PPE_FIRMWARE, 0x002 }; #define PAU_PHY_EXT_CMD_REQ_IO_RESET PPC_BIT16(1) #define PAU_PHY_EXT_CMD_REQ_DCCAL PPC_BIT16(3) #define PAU_PHY_EXT_CMD_REQ_TX_ZCAL PPC_BIT16(4) #define PAU_PHY_EXT_CMD_REQ_TX_FFE PPC_BIT16(5) #define PAU_PHY_EXT_CMD_REQ_POWER_ON PPC_BIT16(7) static struct PPE_sram_reg PAU_PHY_EXT_CMD_DONE = { &PPE_FIRMWARE, 0x005 }; /* PPE memory */ static struct PPE_sram_reg PAU_PHY_RX_PPE_CNTL1 = { &PPE_MEMORY, 0x000 }; #define PAU_PHY_RX_ENABLE_AUTO_RECAL PPC_BIT16(1) enum pau_phy_status { PAU_PROC_INPROGRESS, PAU_PROC_COMPLETE, PAU_PROC_NEXT, PAU_PROC_FAILED }; struct procedure { const char *name; uint32_t (*steps[])(struct pau_dev *); }; #define DEFINE_PROCEDURE(NAME, STEPS...) \ static struct procedure procedure_##NAME = { \ .name = #NAME, \ .steps = { STEPS } \ } /* * We could/should have one phy_sram_lock per PAU chiplet. Each PAU * chiplet drives 2 OPT units. Since we don't have a PAU chiplet * structure to host the lock and don't anticipate much contention, we * go with a global lock for now */ static struct lock phy_sram_lock = LOCK_UNLOCKED; static int get_thread_id(uint32_t op_unit) { int ppe_thread[8] = { 0, 1, 1, 0, 1, 0, 1, 0 }; /* static mapping between OP unit and PPE thread ID */ if (op_unit >= sizeof(ppe_thread)) return -1; return ppe_thread[op_unit]; } /* * Compute the address in the memory mapped SRAM of a 16-bit PHY register */ static uint32_t pau_phy_sram_addr(struct pau_dev *dev, struct PPE_sram_reg *reg, int lane) { uint32_t base, addr; base = reg->section->offset + reg->section->size * get_thread_id(dev->op_unit); addr = reg->offset; if (lane >= 0) { assert(reg->offset < 0x10); addr += lane << 4; } addr <<= 1; // each register is 16-bit return base + addr; } static void pau_phy_set_access(struct pau_dev *dev, struct PPE_sram_reg *reg, int lane, uint64_t *data_addr, uint64_t *mask) { struct pau *pau = dev->pau; uint64_t scom_addr, sram_addr, addr, bit_start; scom_addr = SETFIELD(PAU_PHY_ADDR_CHIPLET, PAU_PHY_ADDR_REG, pau->op_chiplet); sram_addr = pau_phy_sram_addr(dev, reg, lane); bit_start = 8 * (sram_addr & 7); addr = SETFIELD(PAU_PHY_ADDR_SRAM_ADDR, 0ull, sram_addr & 0xFFFFFFF8); xscom_write(pau->chip_id, scom_addr, addr); *data_addr = SETFIELD(PAU_PHY_DATA_CHIPLET, PAU_PHY_DATA_REG, pau->op_chiplet); *mask = PPC_BITMASK(bit_start, bit_start + 15); } static void pau_phy_write_lane(struct pau_dev *dev, struct PPE_sram_reg *reg, int lane, uint16_t val) { struct pau *pau = dev->pau; uint64_t data_addr, scom_val, mask; lock(&phy_sram_lock); pau_phy_set_access(dev, reg, lane, &data_addr, &mask); xscom_read(pau->chip_id, data_addr, &scom_val); scom_val = SETFIELD(mask, scom_val, val); xscom_write(pau->chip_id, data_addr, scom_val); unlock(&phy_sram_lock); } static uint16_t pau_phy_read_lane(struct pau_dev *dev, struct PPE_sram_reg *reg, int lane) { struct pau *pau = dev->pau; uint64_t data_addr, scom_val, mask; uint16_t res; lock(&phy_sram_lock); pau_phy_set_access(dev, reg, lane, &data_addr, &mask); xscom_read(pau->chip_id, data_addr, &scom_val); res = GETFIELD(mask, scom_val); unlock(&phy_sram_lock); return res; } static void pau_phy_write(struct pau_dev *dev, struct PPE_sram_reg *reg, uint16_t val) { pau_phy_write_lane(dev, reg, -1, val); } static uint16_t pau_phy_read(struct pau_dev *dev, struct PPE_sram_reg *reg) { return pau_phy_read_lane(dev, reg, -1); } static uint16_t get_reset_request_val(void) { return PAU_PHY_EXT_CMD_REQ_IO_RESET | PAU_PHY_EXT_CMD_REQ_DCCAL | PAU_PHY_EXT_CMD_REQ_TX_ZCAL | PAU_PHY_EXT_CMD_REQ_TX_FFE | PAU_PHY_EXT_CMD_REQ_POWER_ON; } static uint32_t reset_start(struct pau_dev *dev) { uint16_t val16; // Procedure IO_INIT_RESET_PON // Clear external command request / done registers val16 = 0; pau_phy_write(dev, &PAU_PHY_EXT_CMD_REQ, val16); pau_phy_write(dev, &PAU_PHY_EXT_CMD_DONE, val16); // Write the external command lanes to target val16 = dev->phy_lane_mask >> 16; pau_phy_write(dev, &PAU_PHY_EXT_CMD_LANES_00_15, val16); val16 = dev->phy_lane_mask & 0xFFFF; pau_phy_write(dev, &PAU_PHY_EXT_CMD_LANES_16_31, val16); // Initialize PHY Lanes val16 = get_reset_request_val(); pau_phy_write(dev, &PAU_PHY_EXT_CMD_REQ, val16); return PAU_PROC_NEXT; } static uint32_t reset_check(struct pau_dev *dev) { uint16_t val16, done; val16 = get_reset_request_val(); done = pau_phy_read(dev, &PAU_PHY_EXT_CMD_DONE); if (val16 == done) return PAU_PROC_NEXT; else return PAU_PROC_INPROGRESS; } static uint32_t enable_recal(struct pau_dev *dev) { uint32_t lane; // Enable auto-recalibration for (lane = 0; lane <= PAU_MAX_PHY_LANE; lane++) if (!(dev->phy_lane_mask & (1 << (31 - lane)))) continue; else pau_phy_write_lane(dev, &PAU_PHY_RX_PPE_CNTL1, lane, PAU_PHY_RX_ENABLE_AUTO_RECAL); return PAU_PROC_COMPLETE; } DEFINE_PROCEDURE(phy_reset, reset_start, reset_check, enable_recal); static enum pau_phy_status run_steps(struct pau_dev *dev) { struct procedure *p = &procedure_phy_reset; struct phy_proc_state *procedure_state = &dev->pau->procedure_state; enum pau_phy_status rc; do { rc = p->steps[procedure_state->step](dev); if (rc == PAU_PROC_NEXT) { procedure_state->step++; PAUDEVDBG(dev, "Running procedure %s step %d\n", p->name, procedure_state->step); } } while (rc == PAU_PROC_NEXT); return rc; } static enum pau_phy_status run_procedure(struct pau_dev *dev) { struct procedure *p = &procedure_phy_reset; struct phy_proc_state *procedure_state = &dev->pau->procedure_state; enum pau_phy_status rc; do { rc = run_steps(dev); if (rc == PAU_PROC_INPROGRESS) { if (tb_compare(mftb(), procedure_state->timeout) == TB_AAFTERB) { PAUDEVERR(dev, "Procedure %s timed out\n", p->name); rc = PAU_PROC_FAILED; } else { time_wait_ms(1); } } } while (rc == PAU_PROC_INPROGRESS); return rc; } int pau_dev_phy_reset(struct pau_dev *dev) { struct procedure *p = &procedure_phy_reset; struct phy_proc_state *procedure_state = &dev->pau->procedure_state; enum pau_phy_status rc; lock(&procedure_state->lock); procedure_state->step = 0; procedure_state->timeout = mftb() + msecs_to_tb(PAU_PHY_INIT_TIMEOUT); PAUDEVDBG(dev, "Running procedure %s step %d\n", p->name, procedure_state->step); rc = run_procedure(dev); unlock(&procedure_state->lock); if (rc == PAU_PROC_COMPLETE) { PAUDEVDBG(dev, "Procedure %s complete\n", p->name); return OPAL_SUCCESS; } PAUDEVDBG(dev, "Procedure %s failed\n", p->name); return OPAL_HARDWARE; }