[Skiboot] [PATCH V2 11/15] pau: phy init

Christophe Lombard clombard at linux.vnet.ibm.com
Thu Sep 23 19:03:27 AEST 2021


Follow the Procedure IO_INIT_RESET_PON as described in the
P10 OPHY workbook document to reset and initialize the PHY lanes.

The memory mapped SRAM (64 bit aligned) has to be used to configure the
PHY, which is reachable the linked registers: address and data.
The different links can be configured at the same time, that implies using
a global lock to avoid conflicts.

Authored-by: Frederic Barrat <fbarrat at linux.ibm.com>
Signed-off-by: Christophe Lombard <clombard at linux.vnet.ibm.com>
---
 hw/Makefile.inc        |   2 +-
 hw/pau-hw-procedures.c | 310 +++++++++++++++++++++++++++++++++++++++++
 include/pau.h          |  11 ++
 3 files changed, 322 insertions(+), 1 deletion(-)
 create mode 100644 hw/pau-hw-procedures.c

diff --git a/hw/Makefile.inc b/hw/Makefile.inc
index 0b12f0be..9fcbb63b 100644
--- a/hw/Makefile.inc
+++ b/hw/Makefile.inc
@@ -8,7 +8,7 @@ HW_OBJS += dts.o lpc-rtc.o npu.o npu-hw-procedures.o xive.o phb4.o
 HW_OBJS += fake-nvram.o lpc-mbox.o npu2.o npu2-hw-procedures.o
 HW_OBJS += npu2-common.o npu2-opencapi.o phys-map.o sbe-p9.o capp.o
 HW_OBJS += occ-sensor.o vas.o sbe-p8.o dio-p9.o lpc-port80h.o cache-p9.o
-HW_OBJS += npu-opal.o ocmb.o xive2.o pau.o
+HW_OBJS += npu-opal.o ocmb.o xive2.o pau.o pau-hw-procedures.o
 HW=hw/built-in.a
 
 include $(SRC)/hw/fsp/Makefile.inc
diff --git a/hw/pau-hw-procedures.c b/hw/pau-hw-procedures.c
new file mode 100644
index 00000000..bd299b3f
--- /dev/null
+++ b/hw/pau-hw-procedures.c
@@ -0,0 +1,310 @@
+// 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;
+}
diff --git a/include/pau.h b/include/pau.h
index 547c1934..9fbccb87 100644
--- a/include/pau.h
+++ b/include/pau.h
@@ -28,6 +28,12 @@ struct pau_bar {
 	uint64_t		cfg;
 };
 
+struct phy_proc_state {
+	struct lock		lock; /* protect any change to this structure */
+	unsigned long		timeout;
+	uint16_t		step;
+};
+
 struct pau_dev {
 	enum pau_dev_type	type;
 	uint32_t		index;
@@ -54,6 +60,7 @@ struct pau {
 	uint32_t		index;
 	struct dt_node		*dt_node;
 	uint32_t		chip_id;
+	uint32_t		op_chiplet; /* from pervasive: 0x10 -> 0x13 */
 	uint64_t		xscom_base;
 
 	/* Global MMIO window (all PAU regs) */
@@ -65,6 +72,7 @@ struct pau {
 
 	uint32_t		links;
 	struct pau_dev		devices[PAU_LINKS_OPENCAPI_PER_PAU];
+	struct phy_proc_state	procedure_state;
 };
 
 #define PAUDBG(pau, fmt, a...) PAULOG(PR_DEBUG, pau, fmt, ##a)
@@ -191,4 +199,7 @@ static inline uint64_t pau_read(struct pau *pau, uint64_t reg)
 
 void pau_opencapi_dump_scoms(struct pau *pau);
 
+/* PHY */
+int pau_dev_phy_reset(struct pau_dev *dev);
+
 #endif /* __PAU_H */
-- 
2.31.1



More information about the Skiboot mailing list