drivers: net: apply serdes configuration for ENETC Ethernet interfaces

Ethernet interfaces using serial protocols go through the serdes block
integrated in the SoC.  This is accessed over dedicated internal MDIOs
which are part of the Ethernet PCI functions.  Set up serdes at _start,
along with other protocol specific port/MAC configuration.
MDIO code is shared with enetc_mdio, read/write functions are exported
from fsl_enetc_mdio for this reason.

Signed-off-by: Alex Marginean <alexm.osslist@gmail.com>
Reviewed-by: Bin Meng <bmeng.cn@gmail.com>
Acked-by: Joe Hershberger <joe.hershberger@ni.com>
diff --git a/drivers/net/fsl_enetc.c b/drivers/net/fsl_enetc.c
index f14b484..da533a1 100644
--- a/drivers/net/fsl_enetc.c
+++ b/drivers/net/fsl_enetc.c
@@ -39,6 +39,152 @@
 	return 0;
 }
 
+/* MDIO wrappers, we're using these to drive internal MDIO to get to serdes */
+static int enetc_mdio_read(struct mii_dev *bus, int addr, int devad, int reg)
+{
+	struct enetc_mdio_priv priv;
+
+	priv.regs_base = bus->priv;
+	return enetc_mdio_read_priv(&priv, addr, devad, reg);
+}
+
+static int enetc_mdio_write(struct mii_dev *bus, int addr, int devad, int reg,
+			    u16 val)
+{
+	struct enetc_mdio_priv priv;
+
+	priv.regs_base = bus->priv;
+	return enetc_mdio_write_priv(&priv, addr, devad, reg, val);
+}
+
+/* only interfaces that can pin out through serdes have internal MDIO */
+static bool enetc_has_imdio(struct udevice *dev)
+{
+	struct enetc_priv *priv = dev_get_priv(dev);
+
+	return !!(priv->imdio.priv);
+}
+
+/* set up serdes for SGMII */
+static int enetc_init_sgmii(struct udevice *dev)
+{
+	struct enetc_priv *priv = dev_get_priv(dev);
+
+	if (!enetc_has_imdio(dev))
+		return 0;
+
+	/* Set to SGMII mode, use AN */
+	enetc_mdio_write(&priv->imdio, ENETC_PCS_PHY_ADDR, MDIO_DEVAD_NONE,
+			 ENETC_PCS_IF_MODE, ENETC_PCS_IF_MODE_SGMII_AN);
+
+	/* Dev ability - SGMII */
+	enetc_mdio_write(&priv->imdio, ENETC_PCS_PHY_ADDR, MDIO_DEVAD_NONE,
+			 ENETC_PCS_DEV_ABILITY, ENETC_PCS_DEV_ABILITY_SGMII);
+
+	/* Adjust link timer for SGMII */
+	enetc_mdio_write(&priv->imdio, ENETC_PCS_PHY_ADDR, MDIO_DEVAD_NONE,
+			 ENETC_PCS_LINK_TIMER1, ENETC_PCS_LINK_TIMER1_VAL);
+	enetc_mdio_write(&priv->imdio, ENETC_PCS_PHY_ADDR, MDIO_DEVAD_NONE,
+			 ENETC_PCS_LINK_TIMER2, ENETC_PCS_LINK_TIMER2_VAL);
+
+	/* restart PCS AN */
+	enetc_mdio_write(&priv->imdio, ENETC_PCS_PHY_ADDR, MDIO_DEVAD_NONE,
+			 ENETC_PCS_CR,
+			 ENETC_PCS_CR_RESET_AN | ENETC_PCS_CR_DEF_VAL);
+
+	return 0;
+}
+
+/* set up MAC for RGMII */
+static int enetc_init_rgmii(struct udevice *dev)
+{
+	struct enetc_priv *priv = dev_get_priv(dev);
+	u32 if_mode;
+
+	/* enable RGMII AN */
+	if_mode = enetc_read_port(priv, ENETC_PM_IF_MODE);
+	if_mode |= ENETC_PM_IF_MODE_AN_ENA;
+	enetc_write_port(priv, ENETC_PM_IF_MODE, if_mode);
+
+	return 0;
+}
+
+/* set up MAC and serdes for SXGMII */
+static int enetc_init_sxgmii(struct udevice *dev)
+{
+	struct enetc_priv *priv = dev_get_priv(dev);
+	u32 if_mode;
+
+	/* set ifmode to (US)XGMII */
+	if_mode = enetc_read_port(priv, ENETC_PM_IF_MODE);
+	if_mode &= ~ENETC_PM_IF_IFMODE_MASK;
+	enetc_write_port(priv, ENETC_PM_IF_MODE, if_mode);
+
+	if (!enetc_has_imdio(dev))
+		return 0;
+
+	/* Dev ability - SXGMII */
+	enetc_mdio_write(&priv->imdio, ENETC_PCS_PHY_ADDR, ENETC_PCS_DEVAD_REPL,
+			 ENETC_PCS_DEV_ABILITY, ENETC_PCS_DEV_ABILITY_SXGMII);
+
+	/* Restart PCS AN */
+	enetc_mdio_write(&priv->imdio, ENETC_PCS_PHY_ADDR, ENETC_PCS_DEVAD_REPL,
+			 ENETC_PCS_CR,
+			 ENETC_PCS_CR_LANE_RESET | ENETC_PCS_CR_RESET_AN);
+
+	return 0;
+}
+
+/* Apply protocol specific configuration to MAC, serdes as needed */
+static void enetc_start_pcs(struct udevice *dev)
+{
+	struct enetc_priv *priv = dev_get_priv(dev);
+	const char *if_str;
+
+	priv->if_type = PHY_INTERFACE_MODE_NONE;
+
+	/* check internal mdio capability, not all ports need it */
+	if (enetc_read_port(priv, ENETC_PCAPR0) & ENETC_PCAPRO_MDIO) {
+		/*
+		 * set up internal MDIO, this is part of ETH PCI function and is
+		 * used to access serdes / internal SoC PHYs.
+		 * We don't currently register it as a MDIO bus as it goes away
+		 * when the interface is removed, so it can't practically be
+		 * used in the console.
+		 */
+		priv->imdio.read = enetc_mdio_read;
+		priv->imdio.write = enetc_mdio_write;
+		priv->imdio.priv = priv->port_regs + ENETC_PM_IMDIO_BASE;
+		strncpy(priv->imdio.name, dev->name, MDIO_NAME_LEN);
+	}
+
+	if (!ofnode_valid(dev->node)) {
+		enetc_dbg(dev, "no enetc ofnode found, skipping PCS set-up\n");
+		return;
+	}
+
+	if_str = ofnode_read_string(dev->node, "phy-mode");
+	if (if_str)
+		priv->if_type = phy_get_interface_by_name(if_str);
+	else
+		enetc_dbg(dev,
+			  "phy-mode property not found, defaulting to SGMII\n");
+	if (priv->if_type < 0)
+		priv->if_type = PHY_INTERFACE_MODE_NONE;
+
+	switch (priv->if_type) {
+	case PHY_INTERFACE_MODE_SGMII:
+		enetc_init_sgmii(dev);
+		break;
+	case PHY_INTERFACE_MODE_RGMII:
+		enetc_init_rgmii(dev);
+		break;
+	case PHY_INTERFACE_MODE_XGMII:
+		enetc_init_sxgmii(dev);
+		break;
+	};
+}
+
 /* Configure the actual/external ethernet PHY, if one is found */
 static void enetc_start_phy(struct udevice *dev)
 {
@@ -303,7 +449,7 @@
 	enetc_setup_tx_bdr(dev);
 	enetc_setup_rx_bdr(dev);
 
-	priv->if_type = PHY_INTERFACE_MODE_NONE;
+	enetc_start_pcs(dev);
 	enetc_start_phy(dev);
 
 	return 0;
diff --git a/drivers/net/fsl_enetc.h b/drivers/net/fsl_enetc.h
index fbb9dfa..7ac7c1f 100644
--- a/drivers/net/fsl_enetc.h
+++ b/drivers/net/fsl_enetc.h
@@ -61,6 +61,8 @@
 #define ENETC_PSIPMMR			0x0018
 #define ENETC_PSIPMAR0			0x0100
 #define ENETC_PSIPMAR1			0x0104
+#define ENETC_PCAPR0			0x0900
+#define  ENETC_PCAPRO_MDIO		BIT(11)
 #define ENETC_PSICFGR(n)		(0x0940 + (n) * 0x10)
 #define  ENETC_PSICFGR_SET_TXBDR(val)	((val) & 0xff)
 #define  ENETC_PSICFGR_SET_RXBDR(val)	(((val) & 0xff) << 16)
@@ -70,6 +72,11 @@
 #define  ENETC_PM_CC_RX_TX_EN		0x8813
 #define ENETC_PM_MAXFRM			0x8014
 #define  ENETC_RX_MAXFRM_SIZE		PKTSIZE_ALIGN
+#define ENETC_PM_IMDIO_BASE		0x8030
+#define ENETC_PM_IF_MODE		0x8300
+#define  ENETC_PM_IF_MODE_RG		BIT(2)
+#define  ENETC_PM_IF_MODE_AN_ENA	BIT(15)
+#define  ENETC_PM_IF_IFMODE_MASK	GENMASK(1, 0)
 
 /* buffer descriptors count must be multiple of 8 and aligned to 128 bytes */
 #define ENETC_BD_CNT		CONFIG_SYS_RX_ETH_BUFFER
@@ -146,6 +153,7 @@
 	struct bd_ring rx_bdr;
 
 	int if_type;
+	struct mii_dev imdio;
 };
 
 /* register accessors */
@@ -168,6 +176,27 @@
 #define enetc_bdr_write(priv, t, n, off, val) \
 			enetc_write(priv, ENETC_BDR(t, n, off), val)
 
+/* PCS / internal SoC PHY ID, it defaults to 0 on all interfaces */
+#define ENETC_PCS_PHY_ADDR	0
+
+/* PCS registers */
+#define ENETC_PCS_CR			0x00
+#define  ENETC_PCS_CR_RESET_AN		0x1200
+#define  ENETC_PCS_CR_DEF_VAL		0x0140
+#define  ENETC_PCS_CR_LANE_RESET	0x8000
+#define ENETC_PCS_DEV_ABILITY		0x04
+#define  ENETC_PCS_DEV_ABILITY_SGMII	0x4001
+#define  ENETC_PCS_DEV_ABILITY_SXGMII	0x5001
+#define ENETC_PCS_LINK_TIMER1		0x12
+#define  ENETC_PCS_LINK_TIMER1_VAL	0x06a0
+#define ENETC_PCS_LINK_TIMER2		0x13
+#define  ENETC_PCS_LINK_TIMER2_VAL	0x0003
+#define ENETC_PCS_IF_MODE		0x14
+#define  ENETC_PCS_IF_MODE_SGMII_AN	0x0003
+
+/* PCS replicator block for USXGMII */
+#define ENETC_PCS_DEVAD_REPL		0x1f
+
 /* ENETC external MDIO registers */
 #define ENETC_MDIO_BASE		0x1c00
 #define ENETC_MDIO_CFG		0x00
@@ -186,4 +215,13 @@
 	void *regs_base;
 };
 
+/*
+ * these functions are implemented by ENETC_MDIO and are re-used by ENETC driver
+ * to drive serdes / internal SoC PHYs
+ */
+int enetc_mdio_read_priv(struct enetc_mdio_priv *priv, int addr, int devad,
+			 int reg);
+int enetc_mdio_write_priv(struct enetc_mdio_priv *priv, int addr, int devad,
+			  int reg, u16 val);
+
 #endif /* _ENETC_H */
diff --git a/drivers/net/fsl_enetc_mdio.c b/drivers/net/fsl_enetc_mdio.c
index 46afac0..60d2153 100644
--- a/drivers/net/fsl_enetc_mdio.c
+++ b/drivers/net/fsl_enetc_mdio.c
@@ -21,8 +21,8 @@
 		cpu_relax();
 }
 
-static int enetc_mdio_read_priv(struct enetc_mdio_priv *priv, int addr,
-				int devad, int reg)
+int enetc_mdio_read_priv(struct enetc_mdio_priv *priv, int addr, int devad,
+			 int reg)
 {
 	if (devad == MDIO_DEVAD_NONE)
 		enetc_write(priv, ENETC_MDIO_CFG, ENETC_EMDIO_CFG_C22);
@@ -51,8 +51,8 @@
 	return enetc_read(priv, ENETC_MDIO_DATA);
 }
 
-static int enetc_mdio_write_priv(struct enetc_mdio_priv *priv, int addr,
-				 int devad, int reg, u16 val)
+int enetc_mdio_write_priv(struct enetc_mdio_priv *priv, int addr, int devad,
+			  int reg, u16 val)
 {
 	if (devad == MDIO_DEVAD_NONE)
 		enetc_write(priv, ENETC_MDIO_CFG, ENETC_EMDIO_CFG_C22);