blob: bd299b3fcddc7dc32a9ec8210beaf0954713bb9e [file] [log] [blame]
// 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;
}