310 lines
8.7 KiB
C
310 lines
8.7 KiB
C
// SPDX-License-Identifier: Apache-2.0
|
|
/*
|
|
* Copyright 2020 IBM Corp.
|
|
*/
|
|
#include <timebase.h>
|
|
#include <pau.h>
|
|
|
|
#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;
|
|
}
|