Add a driver for the (DW XPCS) that implements the interface in <pcs.h>.
It matches the existing "snps,dw-xpcs" binding.
The Clause 45 registers are accessed through the memory mapped
indirect window ("indirect" reg, reg-io-width = <4>). The driver
selects the page of each register and caches it.
Supported modes:
- 1000BASE-KX with Clause 37 auto-negotiation
- 10GBASE-R at fixed speed, without auto-negotiation
- 10GBASE-KR with Clause 73 auto-negotiation
The PCS is only probed. The MAC driver configures it with
pcs_set_config(). The driver has no in-tree user yet, the MAC driver
will follow.
Signed-off-by: Akash Nair <[email protected]>
---
drivers/net/Kconfig | 1 +
drivers/net/Makefile | 1 +
drivers/net/pcs/Kconfig | 12 +
drivers/net/pcs/Makefile | 1 +
drivers/net/pcs/pcs-xpcs.c | 881 +++++++++++++++++++++++++++++++++++++
5 files changed, 896 insertions(+)
create mode 100644 drivers/net/pcs/Kconfig
create mode 100644 drivers/net/pcs/Makefile
create mode 100644 drivers/net/pcs/pcs-xpcs.c
diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig
index ae45bef53f5..552d66ed4de 100644
--- a/drivers/net/Kconfig
+++ b/drivers/net/Kconfig
@@ -1146,6 +1146,7 @@ config MDIO_MUX_MESON_GXL
This driver is used for the MDIO mux found on the Amlogic GXL &
compatible
SoCs.
+source "drivers/net/pcs/Kconfig"
source "drivers/net/phy/Kconfig"
source "drivers/net/pfe_eth/Kconfig"
source "drivers/net/fsl-mc/Kconfig"
diff --git a/drivers/net/Makefile b/drivers/net/Makefile
index e5b3e59e536..4474fe515dd 100644
--- a/drivers/net/Makefile
+++ b/drivers/net/Makefile
@@ -98,6 +98,7 @@ obj-$(CONFIG_OCTEONTX2_CGX_INTF) += octeontx2/cgx_intf.o
obj-$(CONFIG_OCTEONTX_SMI) += octeontx/smi.o
obj-$(CONFIG_PCH_GBE) += pch_gbe.o
obj-$(CONFIG_PCNET) += pcnet.o
+obj-$(CONFIG_PCS_XPCS) += pcs/
obj-$(CONFIG_PIC32_ETH) += pic32_mdio.o pic32_eth.o
obj-$(CONFIG_RENESAS_ETHER_SWITCH) += rswitch.o
obj-$(CONFIG_RENESAS_RAVB) += ravb.o
diff --git a/drivers/net/pcs/Kconfig b/drivers/net/pcs/Kconfig
new file mode 100644
index 00000000000..f5221cfa5d9
--- /dev/null
+++ b/drivers/net/pcs/Kconfig
@@ -0,0 +1,12 @@
+config PCS_XPCS
+ bool "Synopsys DesignWare Ethernet PCS (XPCS)"
+ depends on MISC
+ help
+ Support the Synopsys DesignWare Ethernet PCS (DWC XPCS), accessed
+ through its memory-mapped indirect register window.
+
+ Supported modes are 1000BASE-KX, 10GBASE-R and 10GBASE-KR. The PCS
+ is configured by the MAC driver through the generic interface in
+ <pcs.h>.
+
+ Enable this if the board has a "snps,dw-xpcs" device.
diff --git a/drivers/net/pcs/Makefile b/drivers/net/pcs/Makefile
new file mode 100644
index 00000000000..6963ad9d1f8
--- /dev/null
+++ b/drivers/net/pcs/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_PCS_XPCS) += pcs-xpcs.o
diff --git a/drivers/net/pcs/pcs-xpcs.c b/drivers/net/pcs/pcs-xpcs.c
new file mode 100644
index 00000000000..0b20003b48a
--- /dev/null
+++ b/drivers/net/pcs/pcs-xpcs.c
@@ -0,0 +1,881 @@
+/*
+ * Synopsys DesignWare Ethernet PCS (XPCS) driver
+ */
+
+#include <dm.h>
+#include <errno.h>
+#include <pcs.h>
+#include <asm/io.h>
+#include <dm/device_compat.h>
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/delay.h>
+#include <linux/iopoll.h>
+#include <linux/sizes.h>
+#include <linux/types.h>
+
+/**
+ * struct dw_xpcs - XPCS controller state
+ * @dev: owning device, used for debug messages
+ * @base: mapped XPCS register window
+ * @cur_page: currently selected indirect-access page
+ * @iface: interface mode programmed by the last xpcs_set_config(), or
+ * PHY_INTERFACE_MODE_NA when not configured
+ * @an_mode: AN mode programmed by the last xpcs_set_config()
+ * @speed_mbps: speed of @iface, 0 when not configured
+ * @powered_down: the PCS is in power-down.
+ * @an_done: AN completed (Clause 37 or 73), latched from the AN interrupt
+ * status
+ * @an_err: Clause 73 AN failed, latched from the link partner base page
+ */
+struct dw_xpcs {
+ struct udevice *dev;
+ void __iomem *base;
+ u32 cur_page;
+ phy_interface_t iface;
+ enum pcs_an_mode an_mode;
+ int speed_mbps;
+ bool powered_down;
+ bool an_done;
+ int an_err;
+};
+
+/* Driver constants */
+#define DW_XPCS_PAGE_INVALID 0xFFFFFFFFu
+#define DW_XPCS_SPEED_1G 1000
+#define DW_XPCS_SPEED_10G 10000
+
+/*
+ * Power transitions.
+ */
+#define DW_XPCS_PDOWN_DELAY_MS 1
+#define DW_XPCS_PUP_DELAY_MS 10
+#define DW_XPCS_PUP_TMO_US 100000
+#define DW_XPCS_RST_TMO_US 100000
+#define DW_XPCS_POLL_INTERVAL_US 100
+
+/*
+ * Indirect access: MMD and register number to window offset.
+ */
+#define DW_XPCS_REG_MASK GENMASK(15, 0)
+#define DW_XPCS_MMD_SHIFT 16
+#define DW_XPCS_REG_STRIDE_SHIFT 2
+#define DW_XPCS_REG_IO_WIDTH 4
+#define DW_XPCS_REG_IO_WIDTH_DEFAULT 2
+#define DW_XPCS_MIN_REG_SZ SZ_1K
+
+#define DW_XPCS_CANON(mmd, reg) (((u32)(mmd) << DW_XPCS_MMD_SHIFT) | \
+ ((reg) & DW_XPCS_REG_MASK))
+#define DW_XPCS_IND_SENTINEL 0x3FCu
+#define DW_XPCS_IND_PAGE_SHIFT 10
+#define DW_XPCS_IND_PAGE_MASK GENMASK(12, 0)
+#define DW_XPCS_IND_OFF_MASK GENMASK(9, 0)
+#define DW_XPCS_IND_PAGE(byte) (((byte) >> DW_XPCS_IND_PAGE_SHIFT) & \
+ DW_XPCS_IND_PAGE_MASK)
+#define DW_XPCS_IND_OFF(byte) ((byte) & DW_XPCS_IND_OFF_MASK)
+
+/* MMD (Clause 45 device) numbers */
+#define DW_XPCS_MMD_PMA_PMD 0x01
+#define DW_XPCS_MMD_PCS 0x03
+#define DW_XPCS_MMD_AN 0x07
+#define DW_XPCS_MMD_VEND2 0x1F
+
+/*
+ * Clause 45 device identifier registers, the same in every MMD.
+ */
+#define DW_XPCS_DEV_ID1 0x0002
+#define DW_XPCS_DEV_ID2 0x0003
+#define DW_XPCS_DEV_ID1_SHIFT 16
+#define DW_XPCS_DEV_ID 0x7996ced0u
+
+/*
+ * Speed selection bits.
+ */
+#define DW_XPCS_CTRL1_SS13 BIT(13)
+#define DW_XPCS_CTRL1_SS6 BIT(6)
+#define DW_XPCS_CTRL1_SS_MASK (DW_XPCS_CTRL1_SS13 | \
+ DW_XPCS_CTRL1_SS6)
+#define DW_XPCS_CTRL1_SS_1G DW_XPCS_CTRL1_SS6
+#define DW_XPCS_CTRL1_SS_10G (DW_XPCS_CTRL1_SS13 | \
+ DW_XPCS_CTRL1_SS6)
+
+/* ---- PMA/PMD MMD (device 1) ---- */
+#define DW_XPCS_SR_PMA_CTRL1 0x0000
+
+#define DW_XPCS_VR_PMA_DIG_CTRL1 0x8000
+#define DW_XPCS_VR_PMA_DIG_CTRL1_CL72_AUTO BIT(0)
+
+/* ---- PCS / XS-PCS MMD (device 3) ---- */
+#define DW_XPCS_SR_XS_PCS_CTRL1 0x0000
+#define DW_XPCS_SR_XS_PCS_CTRL1_RST BIT(15)
+#define DW_XPCS_SR_XS_PCS_CTRL1_LPM BIT(11)
+
+#define DW_XPCS_SR_XS_PCS_STS1 0x0001
+#define DW_XPCS_SR_XS_PCS_STS1_LSTATUS BIT(2)
+
+#define DW_XPCS_SR_XS_PCS_CTRL2 0x0007
+#define DW_XPCS_SR_XS_PCS_CTRL2_TYPE_MASK GENMASK(3, 0)
+
+#define DW_XPCS_SR_XS_PCS_CTRL2_TYPE_10GBASEX 0x1
+#define DW_XPCS_SR_XS_PCS_CTRL2_TYPE_10GBASER 0x0
+
+#define DW_XPCS_VR_XS_PCS_DIG_CTRL1 0x8000
+#define DW_XPCS_VR_XS_PCS_DIG_CTRL1_CL37_BP BIT(12)
+
+/* ---- AN MMD (device 7) ---- */
+#define DW_XPCS_SR_AN_CTRL 0x0000
+#define DW_XPCS_SR_AN_CTRL_AN_EN BIT(12)
+#define DW_XPCS_SR_AN_CTRL_RSTRT_AN BIT(9)
+
+#define DW_XPCS_SR_AN_ADV1 0x0010
+#define DW_XPCS_SR_AN_ADV1_SELECTOR 0x0001
+#define DW_XPCS_SR_AN_ADV1_NP BIT(15)
+
+#define DW_XPCS_SR_AN_ADV2 0x0011
+#define DW_XPCS_SR_AN_ADV2_10GKR BIT(7)
+
+#define DW_XPCS_SR_AN_ADV3 0x0012
+
+#define DW_XPCS_SR_AN_LP_ABL1 0x0013
+#define DW_XPCS_SR_AN_LP_ABL1_NP BIT(15)
+
+#define DW_XPCS_SR_AN_LP_ABL2 0x0014
+#define DW_XPCS_SR_AN_LP_ABL2_10GKR BIT(7)
+
+#define DW_XPCS_VR_AN_INTR 0x8002
+#define DW_XPCS_VR_AN_INTR_CMPLT BIT(0)
+#define DW_XPCS_VR_AN_INTR_INC_LINK BIT(1)
+#define DW_XPCS_VR_AN_INTR_PG_RCV BIT(2)
+#define DW_XPCS_VR_AN_INTR_ALL (DW_XPCS_VR_AN_INTR_CMPLT | \
+ DW_XPCS_VR_AN_INTR_INC_LINK | \
+ DW_XPCS_VR_AN_INTR_PG_RCV)
+
+#define DW_XPCS_SR_MII_CTRL 0x0000
+#define DW_XPCS_SR_MII_CTRL_AN_ENABLE BIT(12)
+
+#define DW_XPCS_SR_MII_STS 0x0001
+#define DW_XPCS_SR_MII_STS_LSTATUS BIT(2)
+
+#define DW_XPCS_VR_MII_AN_CTRL 0x8001
+#define DW_XPCS_VR_MII_AN_CTRL_MII_AN_INTR_EN BIT(0)
+#define DW_XPCS_VR_MII_AN_CTRL_PCS_MODE_MASK GENMASK(2, 1)
+#define DW_XPCS_VR_MII_AN_CTRL_PCS_MODE_1000X 0x0
+
+#define DW_XPCS_VR_MII_AN_INTR_STS 0x8002
+#define DW_XPCS_VR_MII_AN_INTR_STS_CL37_ANCMPLT BIT(0)
+
+static void xpcs_set_page(struct dw_xpcs *xpcs, u32 byteaddr)
+{
+ u32 page = DW_XPCS_IND_PAGE(byteaddr);
+
+ if (page != xpcs->cur_page) {
+ writel(page, xpcs->base + DW_XPCS_IND_SENTINEL);
+ xpcs->cur_page = page;
+ }
+}
+
+static int xpcs_read(struct dw_xpcs *xpcs, u8 mmd, u16 reg)
+{
+ u32 byteaddr = DW_XPCS_CANON(mmd, reg) << DW_XPCS_REG_STRIDE_SHIFT;
+
+ xpcs_set_page(xpcs, byteaddr);
+
+ return readl(xpcs->base + DW_XPCS_IND_OFF(byteaddr)) & DW_XPCS_REG_MASK;
+}
+
+static int xpcs_write(struct dw_xpcs *xpcs, u8 mmd, u16 reg, u16 val)
+{
+ u32 byteaddr = DW_XPCS_CANON(mmd, reg) << DW_XPCS_REG_STRIDE_SHIFT;
+
+ xpcs_set_page(xpcs, byteaddr);
+ writel(val, xpcs->base + DW_XPCS_IND_OFF(byteaddr));
+
+ return 0;
+}
+
+static int xpcs_modify(struct dw_xpcs *xpcs, u8 mmd, u16 reg, u16 mask, u16
set)
+{
+ int val;
+
+ val = xpcs_read(xpcs, mmd, reg);
+ if (val < 0)
+ return val;
+
+ return xpcs_write(xpcs, mmd, reg, (val & ~mask) | (set & mask));
+}
+
+static int xpcs_wait_reset_done(struct dw_xpcs *xpcs)
+{
+ int val, ret;
+
+ ret = read_poll_timeout(xpcs_read, val,
+ val < 0 || !(val & DW_XPCS_SR_XS_PCS_CTRL1_RST),
+ DW_XPCS_POLL_INTERVAL_US,
+ DW_XPCS_RST_TMO_US, xpcs,
+ DW_XPCS_MMD_PCS, DW_XPCS_SR_XS_PCS_CTRL1);
+ if (ret)
+ return ret;
+
+ return val < 0 ? val : 0;
+}
+
+/*
+ * Wait for the PCS to leave power-down.
+ */
+static int xpcs_wait_power_up_done(struct dw_xpcs *xpcs)
+{
+ int val, ret;
+
+ mdelay(DW_XPCS_PUP_DELAY_MS);
+
+ ret = read_poll_timeout(xpcs_read, val,
+ val < 0 ||
+ !(val & (DW_XPCS_SR_XS_PCS_CTRL1_LPM |
+ DW_XPCS_SR_XS_PCS_CTRL1_RST)),
+ DW_XPCS_POLL_INTERVAL_US,
+ DW_XPCS_PUP_TMO_US, xpcs,
+ DW_XPCS_MMD_PCS, DW_XPCS_SR_XS_PCS_CTRL1);
+ if (ret) {
+ dev_dbg(xpcs->dev, "PCS not ready after power-up, CTRL1 %#x\n",
+ val);
+ return ret;
+ }
+
+ return val < 0 ? val : 0;
+}
+
+/*
+ * Switch off every auto-negotiation and drop what a previous mode left in
+ * the registers.
+ */
+static int xpcs_an_stop(struct dw_xpcs *xpcs)
+{
+ int ret;
+
+ ret = xpcs_modify(xpcs, DW_XPCS_MMD_AN, DW_XPCS_SR_AN_CTRL,
+ DW_XPCS_SR_AN_CTRL_AN_EN, 0);
+ if (ret)
+ return ret;
+
+ ret = xpcs_modify(xpcs, DW_XPCS_MMD_VEND2, DW_XPCS_SR_MII_CTRL,
+ DW_XPCS_SR_MII_CTRL_AN_ENABLE, 0);
+ if (ret)
+ return ret;
+
+ ret = xpcs_modify(xpcs, DW_XPCS_MMD_PCS, DW_XPCS_VR_XS_PCS_DIG_CTRL1,
+ DW_XPCS_VR_XS_PCS_DIG_CTRL1_CL37_BP, 0);
+ if (ret)
+ return ret;
+
+ return xpcs_modify(xpcs, DW_XPCS_MMD_PMA_PMD, DW_XPCS_VR_PMA_DIG_CTRL1,
+ DW_XPCS_VR_PMA_DIG_CTRL1_CL72_AUTO, 0);
+}
+
+/*
+ * Select the speed bits in the PMA/PMD and the PCS control regs.
+ */
+static int xpcs_set_speed_sel(struct dw_xpcs *xpcs, u16 ss)
+{
+ int ret;
+
+ ret = xpcs_modify(xpcs, DW_XPCS_MMD_PMA_PMD, DW_XPCS_SR_PMA_CTRL1,
+ DW_XPCS_CTRL1_SS_MASK, ss);
+ if (ret)
+ return ret;
+
+ return xpcs_modify(xpcs, DW_XPCS_MMD_PCS, DW_XPCS_SR_XS_PCS_CTRL1,
+ DW_XPCS_CTRL1_SS_MASK, ss);
+}
+
+static int xpcs_set_mode_1g_kx(struct dw_xpcs *xpcs)
+{
+ int ret;
+
+ ret = xpcs_modify(xpcs, DW_XPCS_MMD_PCS, DW_XPCS_SR_XS_PCS_CTRL2,
+ DW_XPCS_SR_XS_PCS_CTRL2_TYPE_MASK,
+ DW_XPCS_SR_XS_PCS_CTRL2_TYPE_10GBASEX);
+ if (ret)
+ return ret;
+
+ return xpcs_set_speed_sel(xpcs, DW_XPCS_CTRL1_SS_1G);
+}
+
+static int xpcs_set_mode_10g(struct dw_xpcs *xpcs)
+{
+ int ret;
+
+ ret = xpcs_modify(xpcs, DW_XPCS_MMD_PCS, DW_XPCS_SR_XS_PCS_CTRL2,
+ DW_XPCS_SR_XS_PCS_CTRL2_TYPE_MASK,
+ DW_XPCS_SR_XS_PCS_CTRL2_TYPE_10GBASER);
+ if (ret)
+ return ret;
+
+ return xpcs_set_speed_sel(xpcs, DW_XPCS_CTRL1_SS_10G);
+}
+
+/*
+ * Put the PCS in low-power, wait, then bring it back up.
+ * powered_down is cleared only once the PCS is ready again. If that fails
+ * the PCS stays marked as powered down, and the next xpcs_set_config() powers
+ * it up first.
+ */
+static int xpcs_power_cycle(struct dw_xpcs *xpcs)
+{
+ int val, ret;
+
+ val = xpcs_read(xpcs, DW_XPCS_MMD_PCS, DW_XPCS_SR_XS_PCS_CTRL1);
+ if (val < 0)
+ return val;
+
+ val &= ~DW_XPCS_SR_XS_PCS_CTRL1_RST;
+
+ ret = xpcs_write(xpcs, DW_XPCS_MMD_PCS, DW_XPCS_SR_XS_PCS_CTRL1,
+ val | DW_XPCS_SR_XS_PCS_CTRL1_LPM);
+ if (ret)
+ return ret;
+ xpcs->powered_down = true;
+
+ mdelay(DW_XPCS_PDOWN_DELAY_MS);
+
+ ret = xpcs_write(xpcs, DW_XPCS_MMD_PCS, DW_XPCS_SR_XS_PCS_CTRL1,
+ val & ~DW_XPCS_SR_XS_PCS_CTRL1_LPM);
+ if (ret)
+ return ret;
+
+ ret = xpcs_wait_power_up_done(xpcs);
+ if (ret)
+ return ret;
+
+ xpcs->powered_down = false;
+
+ return 0;
+}
+
+/* Wake the PCS from power-down and wait until it is ready. */
+static int xpcs_power_up(struct dw_xpcs *xpcs)
+{
+ int ret;
+
+ ret = xpcs_write(xpcs, DW_XPCS_MMD_PCS, DW_XPCS_SR_XS_PCS_CTRL1, 0);
+ if (ret)
+ return ret;
+
+ ret = xpcs_wait_power_up_done(xpcs);
+ if (ret)
+ return ret;
+
+ xpcs->powered_down = false;
+
+ return 0;
+}
+
+/* Start Clause 37 AN. Completion is reported by xpcs_get_state(). */
+static int xpcs_config_an_c37(struct dw_xpcs *xpcs)
+{
+ int ret;
+
+ /* Clear stale status before AN starts; the poll/result keep it. */
+ ret = xpcs_write(xpcs, DW_XPCS_MMD_VEND2, DW_XPCS_VR_MII_AN_INTR_STS,
+ 0);
+ if (ret)
+ return ret;
+
+ ret = xpcs_modify(xpcs, DW_XPCS_MMD_PCS, DW_XPCS_VR_XS_PCS_DIG_CTRL1,
+ DW_XPCS_VR_XS_PCS_DIG_CTRL1_CL37_BP,
+ DW_XPCS_VR_XS_PCS_DIG_CTRL1_CL37_BP);
+ if (ret)
+ return ret;
+
+ ret = xpcs_modify(xpcs, DW_XPCS_MMD_VEND2, DW_XPCS_VR_MII_AN_CTRL,
+ DW_XPCS_VR_MII_AN_CTRL_PCS_MODE_MASK |
+ DW_XPCS_VR_MII_AN_CTRL_MII_AN_INTR_EN,
+ FIELD_PREP(DW_XPCS_VR_MII_AN_CTRL_PCS_MODE_MASK,
+ DW_XPCS_VR_MII_AN_CTRL_PCS_MODE_1000X) |
+ DW_XPCS_VR_MII_AN_CTRL_MII_AN_INTR_EN);
+ if (ret)
+ return ret;
+
+ return xpcs_modify(xpcs, DW_XPCS_MMD_VEND2, DW_XPCS_SR_MII_CTRL,
+ DW_XPCS_SR_MII_CTRL_AN_ENABLE,
+ DW_XPCS_SR_MII_CTRL_AN_ENABLE);
+}
+
+/* Start Clause 73 AN. Completion is reported by xpcs_get_state(). */
+static int xpcs_config_an_c73_10gkr(struct dw_xpcs *xpcs)
+{
+ int ret;
+
+ ret = xpcs_modify(xpcs, DW_XPCS_MMD_PMA_PMD, DW_XPCS_VR_PMA_DIG_CTRL1,
+ DW_XPCS_VR_PMA_DIG_CTRL1_CL72_AUTO,
+ DW_XPCS_VR_PMA_DIG_CTRL1_CL72_AUTO);
+ if (ret)
+ return ret;
+
+ ret = xpcs_write(xpcs, DW_XPCS_MMD_AN, DW_XPCS_VR_AN_INTR, 0);
+ if (ret)
+ return ret;
+
+ ret = xpcs_write(xpcs, DW_XPCS_MMD_AN, DW_XPCS_SR_AN_ADV3, 0);
+ if (ret)
+ return ret;
+
+ ret = xpcs_write(xpcs, DW_XPCS_MMD_AN, DW_XPCS_SR_AN_ADV2,
+ DW_XPCS_SR_AN_ADV2_10GKR);
+ if (ret)
+ return ret;
+
+ ret = xpcs_write(xpcs, DW_XPCS_MMD_AN, DW_XPCS_SR_AN_ADV1,
+ DW_XPCS_SR_AN_ADV1_SELECTOR);
+ if (ret)
+ return ret;
+
+ return xpcs_modify(xpcs, DW_XPCS_MMD_AN, DW_XPCS_SR_AN_CTRL,
+ DW_XPCS_SR_AN_CTRL_AN_EN |
+ DW_XPCS_SR_AN_CTRL_RSTRT_AN,
+ DW_XPCS_SR_AN_CTRL_AN_EN |
+ DW_XPCS_SR_AN_CTRL_RSTRT_AN);
+}
+
+/*
+ * Read a latched link bit. The bit stays low after a link drop until it is
+ * read, so read twice and report the second value.
+ */
+static int xpcs_read_link_bit(struct dw_xpcs *xpcs, u8 mmd, u16 reg, u16 mask,
+ bool *up)
+{
+ int val;
+
+ val = xpcs_read(xpcs, mmd, reg);
+ if (val < 0)
+ return val;
+
+ val = xpcs_read(xpcs, mmd, reg);
+ if (val < 0)
+ return val;
+
+ *up = val & mask;
+
+ return 0;
+}
+
+static int xpcs_kx_link_up(struct dw_xpcs *xpcs, bool *up)
+{
+ return xpcs_read_link_bit(xpcs, DW_XPCS_MMD_VEND2, DW_XPCS_SR_MII_STS,
+ DW_XPCS_SR_MII_STS_LSTATUS, up);
+}
+
+static int xpcs_10g_link_up(struct dw_xpcs *xpcs, bool *up)
+{
+ return xpcs_read_link_bit(xpcs, DW_XPCS_MMD_PCS,
+ DW_XPCS_SR_XS_PCS_STS1,
+ DW_XPCS_SR_XS_PCS_STS1_LSTATUS, up);
+}
+
+static int xpcs_get_link(struct dw_xpcs *xpcs, bool *up)
+{
+ if (xpcs->iface == PHY_INTERFACE_MODE_1000BASEKX)
+ return xpcs_kx_link_up(xpcs, up);
+
+ return xpcs_10g_link_up(xpcs, up);
+}
+
+/*
+ * Report Clause 37 AN completion.
+ */
+static int xpcs_read_an_state_c37(struct dw_xpcs *xpcs,
+ struct pcs_state *state, bool link)
+{
+ int sts, ret;
+
+ sts = xpcs_read(xpcs, DW_XPCS_MMD_VEND2, DW_XPCS_VR_MII_AN_INTR_STS);
+ if (sts < 0)
+ return sts;
+
+ if (sts & DW_XPCS_VR_MII_AN_INTR_STS_CL37_ANCMPLT) {
+ ret = xpcs_write(xpcs, DW_XPCS_MMD_VEND2,
+ DW_XPCS_VR_MII_AN_INTR_STS,
+ sts &
~DW_XPCS_VR_MII_AN_INTR_STS_CL37_ANCMPLT);
+ if (ret)
+ return ret;
+
+ xpcs->an_done = true;
+ } else if (!link) {
+ xpcs->an_done = false;
+ }
+
+ state->an_complete = xpcs->an_done;
+
+ return 0;
+}
+
+/*
+ * Check the base page of the link partner: 0 if it can do 10GBASE-KR
+ * without Next Pages, a negative error otherwise.
+ */
+static int xpcs_check_lp_c73(struct dw_xpcs *xpcs)
+{
+ int adv1, lp1, lp2;
+
+ lp2 = xpcs_read(xpcs, DW_XPCS_MMD_AN, DW_XPCS_SR_AN_LP_ABL2);
+ if (lp2 < 0)
+ return lp2;
+ if (!(lp2 & DW_XPCS_SR_AN_LP_ABL2_10GKR))
+ return -EPROTONOSUPPORT;
+
+ adv1 = xpcs_read(xpcs, DW_XPCS_MMD_AN, DW_XPCS_SR_AN_ADV1);
+ if (adv1 < 0)
+ return adv1;
+
+ lp1 = xpcs_read(xpcs, DW_XPCS_MMD_AN, DW_XPCS_SR_AN_LP_ABL1);
+ if (lp1 < 0)
+ return lp1;
+
+ if ((adv1 & DW_XPCS_SR_AN_ADV1_NP) ||
+ (lp1 & DW_XPCS_SR_AN_LP_ABL1_NP))
+ return -EOPNOTSUPP;
+
+ return 0;
+}
+
+/*
+ * Clause 73 AN (10GBASE-KR). Returns a negative error if the link
+ * partner cannot do 10GBASE-KR, so a failed negotiation is reported
+ * instead of looking like "not complete yet".
+ */
+static int xpcs_read_an_state_c73(struct dw_xpcs *xpcs,
+ struct pcs_state *state)
+{
+ int intr, ret;
+
+ intr = xpcs_read(xpcs, DW_XPCS_MMD_AN, DW_XPCS_VR_AN_INTR);
+ if (intr < 0)
+ return intr;
+
+ intr &= DW_XPCS_VR_AN_INTR_ALL;
+ if (intr) {
+ /*
+ * A received page restarts negotiation, so drop the old result.
+ * Return register errors before clearing the event so it is
+ * seen again.
+ */
+ if (intr & DW_XPCS_VR_AN_INTR_PG_RCV) {
+ ret = xpcs_check_lp_c73(xpcs);
+ if (ret && ret != -EPROTONOSUPPORT &&
+ ret != -EOPNOTSUPP)
+ return ret;
+
+ xpcs->an_done = false;
+ xpcs->an_err = ret;
+ }
+
+ ret = xpcs_write(xpcs, DW_XPCS_MMD_AN, DW_XPCS_VR_AN_INTR,
+ ~intr & DW_XPCS_VR_AN_INTR_ALL);
+ if (ret)
+ return ret;
+
+ if (intr & DW_XPCS_VR_AN_INTR_CMPLT)
+ xpcs->an_done = true;
+ }
+
+ if (xpcs->an_err)
+ return xpcs->an_err;
+
+ state->an_complete = xpcs->an_done;
+
+ return 0;
+}
+
+static int xpcs_get_state(struct udevice *dev, struct pcs_state *state)
+{
+ struct dw_xpcs *xpcs = dev_get_priv(dev);
+ int ret = 0;
+ bool link;
+
+ /*
+ * A powered down or not configured PCS has nothing to report, so
+ * leave the state cleared.
+ */
+ if (xpcs->powered_down || xpcs->iface == PHY_INTERFACE_MODE_NA)
+ return 0;
+
+ state->speed_mbps = xpcs->speed_mbps;
+
+ ret = xpcs_get_link(xpcs, &link);
+ if (ret)
+ return ret;
+
+ switch (xpcs->an_mode) {
+ case PCS_AN_C37:
+ ret = xpcs_read_an_state_c37(xpcs, state, link);
+ break;
+ case PCS_AN_C73:
+ ret = xpcs_read_an_state_c73(xpcs, state);
+ break;
+ case PCS_AN_FORCED:
+ default:
+ break;
+ }
+ if (ret)
+ return ret;
+
+ state->link = link;
+
+ return 0;
+}
+
+/*
+ * Check the whole config before touching the hardware, so a bad request
+ * never leaves the PCS half-configured.
+ */
+static int xpcs_validate_cfg(const struct pcs_config *cfg)
+{
+ if (!cfg)
+ return -EINVAL;
+
+ switch (cfg->iface) {
+ case PHY_INTERFACE_MODE_1000BASEKX:
+ if (cfg->an_mode != PCS_AN_C37)
+ return -EOPNOTSUPP;
+ if (cfg->speed_mbps && cfg->speed_mbps != DW_XPCS_SPEED_1G)
+ return -EINVAL;
+ return 0;
+ case PHY_INTERFACE_MODE_10GBASER:
+ if (cfg->an_mode != PCS_AN_FORCED)
+ return -EOPNOTSUPP;
+ if (cfg->speed_mbps != DW_XPCS_SPEED_10G)
+ return -EINVAL;
+ return 0;
+ case PHY_INTERFACE_MODE_10GKR:
+ if (cfg->an_mode != PCS_AN_C73)
+ return -EOPNOTSUPP;
+ if (cfg->speed_mbps && cfg->speed_mbps != DW_XPCS_SPEED_10G)
+ return -EINVAL;
+ return 0;
+ default:
+ return -EINVAL;
+ }
+}
+
+/* Mark the PCS as not configured, and drop the latched AN events */
+static void xpcs_clear_state(struct dw_xpcs *xpcs)
+{
+ xpcs->iface = PHY_INTERFACE_MODE_NA;
+ xpcs->an_mode = PCS_AN_FORCED;
+ xpcs->speed_mbps = 0;
+ xpcs->an_done = false;
+ xpcs->an_err = 0;
+}
+
+/*
+ * Configure the PCS safely: disable any current AN, program the new mode,
+ * power-cycle to latch it, then re-enable AN if the mode requires it.
+ */
+static int xpcs_set_config(struct udevice *dev, const struct pcs_config *cfg)
+{
+ struct dw_xpcs *xpcs = dev_get_priv(dev);
+ int ret;
+
+ ret = xpcs_validate_cfg(cfg);
+ if (ret)
+ return ret;
+
+ dev_dbg(xpcs->dev, "configure %s, AN mode %d\n",
+ phy_string_for_interface(cfg->iface), cfg->an_mode);
+
+ if (xpcs->powered_down) {
+ ret = xpcs_power_up(xpcs);
+ if (ret)
+ return ret;
+ }
+
+ xpcs_clear_state(xpcs);
+
+ ret = xpcs_an_stop(xpcs);
+ if (ret)
+ return ret;
+
+ if (cfg->iface == PHY_INTERFACE_MODE_1000BASEKX)
+ ret = xpcs_set_mode_1g_kx(xpcs);
+ else
+ ret = xpcs_set_mode_10g(xpcs);
+ if (ret)
+ return ret;
+
+ ret = xpcs_power_cycle(xpcs);
+ if (ret)
+ return ret;
+
+ switch (cfg->iface) {
+ case PHY_INTERFACE_MODE_1000BASEKX:
+ ret = xpcs_config_an_c37(xpcs);
+ break;
+ case PHY_INTERFACE_MODE_10GKR:
+ ret = xpcs_config_an_c73_10gkr(xpcs);
+ break;
+ default:
+ /* 10GBASE-R: fixed speed, AN stays off */
+ ret = 0;
+ break;
+ }
+ if (ret)
+ return ret;
+
+ xpcs->iface = cfg->iface;
+ xpcs->an_mode = cfg->an_mode;
+ xpcs->speed_mbps = cfg->iface == PHY_INTERFACE_MODE_1000BASEKX ?
+ DW_XPCS_SPEED_1G : DW_XPCS_SPEED_10G;
+
+ return 0;
+}
+
+/*
+ * Stop AN and put the PCS in low-power.
+ */
+static int xpcs_disable(struct udevice *dev)
+{
+ struct dw_xpcs *xpcs = dev_get_priv(dev);
+ int ctrl1, ret;
+
+ if (xpcs->powered_down)
+ return 0;
+
+ ret = xpcs_an_stop(xpcs);
+ if (ret)
+ return ret;
+
+ xpcs_clear_state(xpcs);
+
+ ctrl1 = xpcs_read(xpcs, DW_XPCS_MMD_PCS, DW_XPCS_SR_XS_PCS_CTRL1);
+ if (ctrl1 < 0)
+ return ctrl1;
+
+ ctrl1 &= ~DW_XPCS_SR_XS_PCS_CTRL1_RST;
+ ret = xpcs_write(xpcs, DW_XPCS_MMD_PCS, DW_XPCS_SR_XS_PCS_CTRL1,
+ ctrl1 | DW_XPCS_SR_XS_PCS_CTRL1_LPM);
+ if (ret)
+ return ret;
+ xpcs->powered_down = true;
+
+ mdelay(DW_XPCS_PDOWN_DELAY_MS);
+
+ return 0;
+}
+
+static int xpcs_of_to_plat(struct udevice *dev)
+{
+ fdt_size_t size;
+ u32 io_width;
+
+ if (dev_read_addr_size_name(dev, "indirect", &size) ==
+ FDT_ADDR_T_NONE) {
+ dev_err(dev, "missing reg, or reg-names is not \"indirect\"\n");
+ return -EINVAL;
+ }
+
+ if (size < DW_XPCS_MIN_REG_SZ) {
+ dev_err(dev, "reg window too small (%#llx)\n",
+ (unsigned long long)size);
+ return -EINVAL;
+ }
+
+ io_width = dev_read_u32_default(dev, "reg-io-width",
+ DW_XPCS_REG_IO_WIDTH_DEFAULT);
+ if (io_width != DW_XPCS_REG_IO_WIDTH) {
+ dev_err(dev, "unsupported reg-io-width %u (need %u)\n",
+ io_width, DW_XPCS_REG_IO_WIDTH);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int xpcs_get_dev_id(struct dw_xpcs *xpcs, u8 mmd, u32 *id)
+{
+ int id1, id2;
+
+ id1 = xpcs_read(xpcs, mmd, DW_XPCS_DEV_ID1);
+ if (id1 < 0)
+ return id1;
+
+ id2 = xpcs_read(xpcs, mmd, DW_XPCS_DEV_ID2);
+ if (id2 < 0)
+ return id2;
+
+ *id = ((u32)id1 << DW_XPCS_DEV_ID1_SHIFT) | id2;
+
+ return 0;
+}
+
+static int xpcs_check_dev_id(struct dw_xpcs *xpcs)
+{
+ int ret;
+ u32 id;
+
+ ret = xpcs_get_dev_id(xpcs, DW_XPCS_MMD_PMA_PMD, &id);
+ if (ret) {
+ dev_err(xpcs->dev, "failed to read XPCS device ID (%d)\n", ret);
+ return ret;
+ }
+
+ if (id != DW_XPCS_DEV_ID) {
+ dev_err(xpcs->dev,
+ "unexpected XPCS device ID %#010x, expected %#010x\n",
+ id, DW_XPCS_DEV_ID);
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+static int xpcs_probe(struct udevice *dev)
+{
+ struct dw_xpcs *xpcs = dev_get_priv(dev);
+ int ret;
+
+ xpcs->base = dev_remap_addr_name(dev, "indirect");
+ if (!xpcs->base) {
+ dev_err(dev, "failed to map XPCS register window\n");
+ return -ENOMEM;
+ }
+
+ xpcs->dev = dev;
+ xpcs->cur_page = DW_XPCS_PAGE_INVALID;
+ xpcs_clear_state(xpcs);
+
+ /* PCS ready check */
+ ret = xpcs_wait_reset_done(xpcs);
+ if (ret) {
+ dev_err(dev, "XPCS did not come out of reset\n");
+ return ret;
+ }
+
+ ret = xpcs_check_dev_id(xpcs);
+ if (ret)
+ return ret;
+
+ dev_dbg(dev, "XPCS present at %p; awaiting MAC-driven speed config\n",
+ xpcs->base);
+
+ return 0;
+}
+
+static const struct pcs_ops xpcs_ops = {
+ .set_config = xpcs_set_config,
+ .get_state = xpcs_get_state,
+ .disable = xpcs_disable,
+};
+
+static const struct udevice_id xpcs_ids[] = {
+ { .compatible = "snps,dw-xpcs" },
+ { }
+};
+
+U_BOOT_DRIVER(snps_dw_xpcs) = {
+ .name = "snps_dw_xpcs",
+ .id = UCLASS_MISC,
+ .of_match = xpcs_ids,
+ .of_to_plat = xpcs_of_to_plat,
+ .probe = xpcs_probe,
+ .ops = &xpcs_ops,
+ .priv_auto = sizeof(struct dw_xpcs),
+};
--
2.43.0