]> git.dujemihanovic.xyz Git - u-boot.git/commitdiff
net: dsa: sja1105: add support for SGMII
authorVladimir Oltean <vladimir.oltean@nxp.com>
Wed, 29 Sep 2021 15:04:42 +0000 (18:04 +0300)
committerRamon Fried <rfried.dev@gmail.com>
Tue, 23 Nov 2021 07:57:56 +0000 (09:57 +0200)
The list of ports which support SGMII depending on switch generation is
available here:
https://www.kernel.org/doc/html/latest/networking/dsa/sja1105.html#port-compatibility-matrix

SGMII can either be used to connect to an external PHY or to the host
port. In the first case, the use of in-band autoneg is expected, in the
last, in-band autoneg is expected to be turned off (fixed-link). So the
driver supports both cases.

SGMII support means configuring the PCS and PMA. The PCS is a Synopsys
Designware XPCS, in Linux this has a separate driver but here it is
embedded within the sja1105 driver. If needed it can be taken out later,
although we would need a UCLASS_PCS for it, which we don't have atm.

Nonetheless, I did go all the way to export an internal MDIO bus for PCS
access, because it is nice to be able to debug the PCS through commands
such as:

=> mdio read ethernet-switch@1-pcs 4 1f.0
Reading from bus ethernet-switch@1-pcs
PHY at address 4:
31.0 - 0x1140

The internal MDIO bus is not registered with DM because there is no
udevice on it, as mentioned. But the XPCS code can still be ripped out,
as needed.

I did not add support for 2500base-x because I do not expect this
interface type to be used as a boot source for anybody, it would just
add unnecessary bloat.

Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
Reviewed-by: Ramon Fried <rfried.dev@gmail.com>
drivers/net/sja1105.c

index 0772403116190d05dbff90428378f131be5397a6..17bab33eddb7b95d9c4c177b24c66e506b37cb17 100644 (file)
@@ -37,6 +37,7 @@ enum packing_op {
 #define SJA1105ET_FDB_BIN_SIZE                         4
 #define SJA1105_SIZE_CGU_CMD                           4
 #define SJA1105_SIZE_RESET_CMD                         4
+#define SJA1105_SIZE_MDIO_CMD                          4
 #define SJA1105_SIZE_SPI_MSG_HEADER                    4
 #define SJA1105_SIZE_SPI_MSG_MAXLEN                    (64 * 4)
 #define SJA1105_SIZE_DEVICE_ID                         4
@@ -95,6 +96,8 @@ enum packing_op {
 
 #define SJA1105_RSV_ADDR               0xffffffffffffffffull
 
+#define SJA1110_PCS_BANK_REG           SJA1110_SPI_ADDR(0x3fc)
+
 #define DSA_8021Q_DIR_TX               BIT(11)
 #define DSA_8021Q_PORT_SHIFT           0
 #define DSA_8021Q_PORT_MASK            GENMASK(3, 0)
@@ -103,6 +106,89 @@ enum packing_op {
 
 #define SJA1105_RATE_MBPS(speed) (((speed) * 64000) / 1000)
 
+/* XPCS registers */
+
+/* VR MII MMD registers offsets */
+#define DW_VR_MII_DIG_CTRL1            0x8000
+#define DW_VR_MII_AN_CTRL              0x8001
+#define DW_VR_MII_DIG_CTRL2            0x80e1
+
+/* VR_MII_DIG_CTRL1 */
+#define DW_VR_MII_DIG_CTRL1_MAC_AUTO_SW                BIT(9)
+
+/* VR_MII_DIG_CTRL2 */
+#define DW_VR_MII_DIG_CTRL2_TX_POL_INV         BIT(4)
+
+/* VR_MII_AN_CTRL */
+#define DW_VR_MII_AN_CTRL_TX_CONFIG_SHIFT      3
+#define DW_VR_MII_TX_CONFIG_MASK               BIT(3)
+#define DW_VR_MII_TX_CONFIG_MAC_SIDE_SGMII     0x0
+#define DW_VR_MII_AN_CTRL_PCS_MODE_SHIFT       1
+#define DW_VR_MII_PCS_MODE_MASK                        GENMASK(2, 1)
+#define DW_VR_MII_PCS_MODE_C37_SGMII           0x2
+
+/* PMA registers */
+
+/* LANE_DRIVER1_0 register */
+#define SJA1110_LANE_DRIVER1_0         0x8038
+#define SJA1110_TXDRV(x)               (((x) << 12) & GENMASK(14, 12))
+
+/* LANE_DRIVER2_0 register */
+#define SJA1110_LANE_DRIVER2_0         0x803a
+#define SJA1110_TXDRVTRIM_LSB(x)       ((x) & GENMASK_ULL(15, 0))
+
+/* LANE_DRIVER2_1 register */
+#define SJA1110_LANE_DRIVER2_1         0x803b
+#define SJA1110_LANE_DRIVER2_1_RSV     BIT(9)
+#define SJA1110_TXDRVTRIM_MSB(x)       (((x) & GENMASK_ULL(23, 16)) >> 16)
+
+/* LANE_TRIM register */
+#define SJA1110_LANE_TRIM              0x8040
+#define SJA1110_TXTEN                  BIT(11)
+#define SJA1110_TXRTRIM(x)             (((x) << 8) & GENMASK(10, 8))
+#define SJA1110_TXPLL_BWSEL            BIT(7)
+#define SJA1110_RXTEN                  BIT(6)
+#define SJA1110_RXRTRIM(x)             (((x) << 3) & GENMASK(5, 3))
+#define SJA1110_CDR_GAIN               BIT(2)
+#define SJA1110_ACCOUPLE_RXVCM_EN      BIT(0)
+
+/* LANE_DATAPATH_1 register */
+#define SJA1110_LANE_DATAPATH_1                0x8037
+
+/* POWERDOWN_ENABLE register */
+#define SJA1110_POWERDOWN_ENABLE       0x8041
+#define SJA1110_TXPLL_PD               BIT(12)
+#define SJA1110_TXPD                   BIT(11)
+#define SJA1110_RXPKDETEN              BIT(10)
+#define SJA1110_RXCH_PD                        BIT(9)
+#define SJA1110_RXBIAS_PD              BIT(8)
+#define SJA1110_RESET_SER_EN           BIT(7)
+#define SJA1110_RESET_SER              BIT(6)
+#define SJA1110_RESET_DES              BIT(5)
+#define SJA1110_RCVEN                  BIT(4)
+
+/* RXPLL_CTRL0 register */
+#define SJA1110_RXPLL_CTRL0            0x8065
+#define SJA1110_RXPLL_FBDIV(x)         (((x) << 2) & GENMASK(9, 2))
+
+/* RXPLL_CTRL1 register */
+#define SJA1110_RXPLL_CTRL1            0x8066
+#define SJA1110_RXPLL_REFDIV(x)                ((x) & GENMASK(4, 0))
+
+/* TXPLL_CTRL0 register */
+#define SJA1110_TXPLL_CTRL0            0x806d
+#define SJA1110_TXPLL_FBDIV(x)         ((x) & GENMASK(11, 0))
+
+/* TXPLL_CTRL1 register */
+#define SJA1110_TXPLL_CTRL1            0x806e
+#define SJA1110_TXPLL_REFDIV(x)                ((x) & GENMASK(5, 0))
+
+/* RX_DATA_DETECT register */
+#define SJA1110_RX_DATA_DETECT         0x8045
+
+/* RX_CDR_CTLE register */
+#define SJA1110_RX_CDR_CTLE            0x8042
+
 /* UM10944.pdf Page 11, Table 2. Configuration Blocks */
 enum {
        BLKID_L2_POLICING                               = 0x06,
@@ -203,11 +289,18 @@ struct sja1105_static_config {
        struct sja1105_table tables[BLK_IDX_MAX];
 };
 
+struct sja1105_xpcs_cfg {
+       bool inband_an;
+       int speed;
+};
+
 struct sja1105_private {
        struct sja1105_static_config static_config;
        bool rgmii_rx_delay[SJA1105_MAX_NUM_PORTS];
        bool rgmii_tx_delay[SJA1105_MAX_NUM_PORTS];
        u16 pvid[SJA1105_MAX_NUM_PORTS];
+       struct sja1105_xpcs_cfg xpcs_cfg[SJA1105_MAX_NUM_PORTS];
+       struct mii_dev *mdio_pcs;
        const struct sja1105_info *info;
        struct udevice *dev;
 };
@@ -226,6 +319,7 @@ typedef enum {
        XMII_MODE_MII           = 0,
        XMII_MODE_RMII          = 1,
        XMII_MODE_RGMII         = 2,
+       XMII_MODE_SGMII         = 3,
 } sja1105_phy_interface_t;
 
 enum {
@@ -263,6 +357,7 @@ struct sja1105_regs {
        u64 rgmii_tx_clk[SJA1105_MAX_NUM_PORTS];
        u64 rmii_ref_clk[SJA1105_MAX_NUM_PORTS];
        u64 rmii_ext_tx_clk[SJA1105_MAX_NUM_PORTS];
+       u64 pcs_base[SJA1105_MAX_NUM_PORTS];
 };
 
 struct sja1105_info {
@@ -272,10 +367,15 @@ struct sja1105_info {
        const struct sja1105_regs *regs;
        int (*reset_cmd)(struct sja1105_private *priv);
        int (*setup_rgmii_delay)(struct sja1105_private *priv, int port);
+       int (*pcs_mdio_read)(struct mii_dev *bus, int phy, int mmd, int reg);
+       int (*pcs_mdio_write)(struct mii_dev *bus, int phy, int mmd, int reg,
+                             u16 val);
+       int (*pma_config)(struct sja1105_private *priv, int port);
        const char *name;
        bool supports_mii[SJA1105_MAX_NUM_PORTS];
        bool supports_rmii[SJA1105_MAX_NUM_PORTS];
        bool supports_rgmii[SJA1105_MAX_NUM_PORTS];
+       bool supports_sgmii[SJA1105_MAX_NUM_PORTS];
        const u64 port_speed[SJA1105_SPEED_MAX];
 };
 
@@ -2030,6 +2130,233 @@ static int sja1105_rmii_clocking_setup(struct sja1105_private *priv, int port,
        return 0;
 }
 
+static int sja1105_pcs_read(struct sja1105_private *priv, int addr,
+                           int devad, int regnum)
+{
+       return priv->mdio_pcs->read(priv->mdio_pcs, addr, devad, regnum);
+}
+
+static int sja1105_pcs_write(struct sja1105_private *priv, int addr,
+                            int devad, int regnum, u16 val)
+{
+       return priv->mdio_pcs->write(priv->mdio_pcs, addr, devad, regnum, val);
+}
+
+/* In NXP SJA1105, the PCS is integrated with a PMA that has the TX lane
+ * polarity inverted by default (PLUS is MINUS, MINUS is PLUS). To obtain
+ * normal non-inverted behavior, the TX lane polarity must be inverted in the
+ * PCS, via the DIGITAL_CONTROL_2 register.
+ */
+static int sja1105_pma_config(struct sja1105_private *priv, int port)
+{
+       return sja1105_pcs_write(priv, port, MDIO_MMD_VEND2,
+                                DW_VR_MII_DIG_CTRL2,
+                                DW_VR_MII_DIG_CTRL2_TX_POL_INV);
+}
+
+static int sja1110_pma_config(struct sja1105_private *priv, int port)
+{
+       u16 txpll_fbdiv = 0x19, txpll_refdiv = 0x1;
+       u16 rxpll_fbdiv = 0x19, rxpll_refdiv = 0x1;
+       u16 rx_cdr_ctle = 0x212a;
+       u16 val;
+       int rc;
+
+       /* Program TX PLL feedback divider and reference divider settings for
+        * correct oscillation frequency.
+        */
+       rc = sja1105_pcs_write(priv, port, MDIO_MMD_VEND2, SJA1110_TXPLL_CTRL0,
+                              SJA1110_TXPLL_FBDIV(txpll_fbdiv));
+       if (rc < 0)
+               return rc;
+
+       rc = sja1105_pcs_write(priv, port, MDIO_MMD_VEND2, SJA1110_TXPLL_CTRL1,
+                              SJA1110_TXPLL_REFDIV(txpll_refdiv));
+       if (rc < 0)
+               return rc;
+
+       /* Program transmitter amplitude and disable amplitude trimming */
+       rc = sja1105_pcs_write(priv, port, MDIO_MMD_VEND2,
+                              SJA1110_LANE_DRIVER1_0, SJA1110_TXDRV(0x5));
+       if (rc < 0)
+               return rc;
+
+       val = SJA1110_TXDRVTRIM_LSB(0xffffffull);
+
+       rc = sja1105_pcs_write(priv, port, MDIO_MMD_VEND2,
+                              SJA1110_LANE_DRIVER2_0, val);
+       if (rc < 0)
+               return rc;
+
+       val = SJA1110_TXDRVTRIM_MSB(0xffffffull) | SJA1110_LANE_DRIVER2_1_RSV;
+
+       rc = sja1105_pcs_write(priv, port, MDIO_MMD_VEND2,
+                              SJA1110_LANE_DRIVER2_1, val);
+       if (rc < 0)
+               return rc;
+
+       /* Enable input and output resistor terminations for low BER. */
+       val = SJA1110_ACCOUPLE_RXVCM_EN | SJA1110_CDR_GAIN |
+             SJA1110_RXRTRIM(4) | SJA1110_RXTEN | SJA1110_TXPLL_BWSEL |
+             SJA1110_TXRTRIM(3) | SJA1110_TXTEN;
+
+       rc = sja1105_pcs_write(priv, port, MDIO_MMD_VEND2, SJA1110_LANE_TRIM,
+                              val);
+       if (rc < 0)
+               return rc;
+
+       /* Select PCS as transmitter data source. */
+       rc = sja1105_pcs_write(priv, port, MDIO_MMD_VEND2,
+                              SJA1110_LANE_DATAPATH_1, 0);
+       if (rc < 0)
+               return rc;
+
+       /* Program RX PLL feedback divider and reference divider for correct
+        * oscillation frequency.
+        */
+       rc = sja1105_pcs_write(priv, port, MDIO_MMD_VEND2, SJA1110_RXPLL_CTRL0,
+                              SJA1110_RXPLL_FBDIV(rxpll_fbdiv));
+       if (rc < 0)
+               return rc;
+
+       rc = sja1105_pcs_write(priv, port, MDIO_MMD_VEND2, SJA1110_RXPLL_CTRL1,
+                              SJA1110_RXPLL_REFDIV(rxpll_refdiv));
+       if (rc < 0)
+               return rc;
+
+       /* Program threshold for receiver signal detector.
+        * Enable control of RXPLL by receiver signal detector to disable RXPLL
+        * when an input signal is not present.
+        */
+       rc = sja1105_pcs_write(priv, port, MDIO_MMD_VEND2,
+                              SJA1110_RX_DATA_DETECT, 0x0005);
+       if (rc < 0)
+               return rc;
+
+       /* Enable TX and RX PLLs and circuits.
+        * Release reset of PMA to enable data flow to/from PCS.
+        */
+       rc = sja1105_pcs_read(priv, port, MDIO_MMD_VEND2,
+                             SJA1110_POWERDOWN_ENABLE);
+       if (rc < 0)
+               return rc;
+
+       val = rc & ~(SJA1110_TXPLL_PD | SJA1110_TXPD | SJA1110_RXCH_PD |
+                    SJA1110_RXBIAS_PD | SJA1110_RESET_SER_EN |
+                    SJA1110_RESET_SER | SJA1110_RESET_DES);
+       val |= SJA1110_RXPKDETEN | SJA1110_RCVEN;
+
+       rc = sja1105_pcs_write(priv, port, MDIO_MMD_VEND2,
+                              SJA1110_POWERDOWN_ENABLE, val);
+       if (rc < 0)
+               return rc;
+
+       /* Program continuous-time linear equalizer (CTLE) settings. */
+       rc = sja1105_pcs_write(priv, port, MDIO_MMD_VEND2, SJA1110_RX_CDR_CTLE,
+                              rx_cdr_ctle);
+       if (rc < 0)
+               return rc;
+
+       return 0;
+}
+
+static int sja1105_xpcs_config_aneg_c37_sgmii(struct sja1105_private *priv,
+                                             int port)
+{
+       int rc;
+
+       rc = sja1105_pcs_read(priv, port, MDIO_MMD_VEND2, MDIO_CTRL1);
+       if (rc < 0)
+               return rc;
+       rc &= ~MDIO_AN_CTRL1_ENABLE;
+       rc = sja1105_pcs_write(priv, port, MDIO_MMD_VEND2, MDIO_CTRL1,
+                              rc);
+       if (rc < 0)
+               return rc;
+
+       rc = sja1105_pcs_read(priv, port, MDIO_MMD_VEND2, DW_VR_MII_AN_CTRL);
+       if (rc < 0)
+               return rc;
+
+       rc &= ~(DW_VR_MII_PCS_MODE_MASK | DW_VR_MII_TX_CONFIG_MASK);
+       rc |= (DW_VR_MII_PCS_MODE_C37_SGMII <<
+              DW_VR_MII_AN_CTRL_PCS_MODE_SHIFT &
+              DW_VR_MII_PCS_MODE_MASK);
+       rc |= (DW_VR_MII_TX_CONFIG_MAC_SIDE_SGMII <<
+              DW_VR_MII_AN_CTRL_TX_CONFIG_SHIFT &
+              DW_VR_MII_TX_CONFIG_MASK);
+       rc = sja1105_pcs_write(priv, port, MDIO_MMD_VEND2, DW_VR_MII_AN_CTRL,
+                              rc);
+       if (rc < 0)
+               return rc;
+
+       rc = sja1105_pcs_read(priv, port, MDIO_MMD_VEND2, DW_VR_MII_DIG_CTRL1);
+       if (rc < 0)
+               return rc;
+
+       if (priv->xpcs_cfg[port].inband_an)
+               rc |= DW_VR_MII_DIG_CTRL1_MAC_AUTO_SW;
+       else
+               rc &= ~DW_VR_MII_DIG_CTRL1_MAC_AUTO_SW;
+
+       rc = sja1105_pcs_write(priv, port, MDIO_MMD_VEND2, DW_VR_MII_DIG_CTRL1,
+                              rc);
+       if (rc < 0)
+               return rc;
+
+       rc = sja1105_pcs_read(priv, port, MDIO_MMD_VEND2, MDIO_CTRL1);
+       if (rc < 0)
+               return rc;
+
+       if (priv->xpcs_cfg[port].inband_an)
+               rc |= MDIO_AN_CTRL1_ENABLE;
+       else
+               rc &= ~MDIO_AN_CTRL1_ENABLE;
+
+       return sja1105_pcs_write(priv, port, MDIO_MMD_VEND2, MDIO_CTRL1, rc);
+}
+
+static int sja1105_xpcs_link_up_sgmii(struct sja1105_private *priv, int port)
+{
+       int val = BMCR_FULLDPLX;
+
+       if (priv->xpcs_cfg[port].inband_an)
+               return 0;
+
+       switch (priv->xpcs_cfg[port].speed) {
+       case SPEED_1000:
+               val = BMCR_SPEED1000;
+               break;
+       case SPEED_100:
+               val = BMCR_SPEED100;
+               break;
+       case SPEED_10:
+               val = BMCR_SPEED10;
+               break;
+       default:
+               dev_err(priv->dev, "Invalid PCS speed %d\n",
+                       priv->xpcs_cfg[port].speed);
+               return -EINVAL;
+       }
+
+       return sja1105_pcs_write(priv, port, MDIO_MMD_VEND2, MDIO_CTRL1, val);
+}
+
+static int sja1105_sgmii_setup(struct sja1105_private *priv, int port)
+{
+       int rc;
+
+       rc = sja1105_xpcs_config_aneg_c37_sgmii(priv, port);
+       if (rc)
+               return rc;
+
+       rc = sja1105_xpcs_link_up_sgmii(priv, port);
+       if (rc)
+               return rc;
+
+       return priv->info->pma_config(priv, port);
+}
+
 static int sja1105_clocking_setup_port(struct sja1105_private *priv, int port)
 {
        struct sja1105_xmii_params_entry *mii;
@@ -2054,6 +2381,9 @@ static int sja1105_clocking_setup_port(struct sja1105_private *priv, int port)
        case XMII_MODE_RGMII:
                rc = sja1105_rgmii_clocking_setup(priv, port, role);
                break;
+       case XMII_MODE_SGMII:
+               rc = sja1105_sgmii_setup(priv, port);
+               break;
        default:
                return -EINVAL;
        }
@@ -2077,6 +2407,188 @@ static int sja1105_clocking_setup(struct sja1105_private *priv)
        return 0;
 }
 
+static int sja1105_pcs_mdio_read(struct mii_dev *bus, int phy, int mmd, int reg)
+{
+       u8 packed_buf[SJA1105_SIZE_MDIO_CMD] = {0};
+       struct sja1105_private *priv = bus->priv;
+       const int size = SJA1105_SIZE_MDIO_CMD;
+       u64 addr, tmp;
+       int rc;
+
+       if (mmd == MDIO_DEVAD_NONE)
+               return -ENODEV;
+
+       if (!priv->info->supports_sgmii[phy])
+               return -ENODEV;
+
+       addr = (mmd << 16) | (reg & GENMASK(15, 0));
+
+       if (mmd != MDIO_MMD_VEND1 && mmd != MDIO_MMD_VEND2)
+               return 0xffff;
+
+       rc = sja1105_xfer_buf(priv, SPI_READ, addr, packed_buf, size);
+       if (rc < 0)
+               return rc;
+
+       sja1105_packing(packed_buf, &tmp, 31, 0, size, UNPACK);
+
+       return tmp & 0xffff;
+}
+
+static int sja1105_pcs_mdio_write(struct mii_dev *bus, int phy, int mmd,
+                                 int reg, u16 val)
+{
+       u8 packed_buf[SJA1105_SIZE_MDIO_CMD] = {0};
+       struct sja1105_private *priv = bus->priv;
+       const int size = SJA1105_SIZE_MDIO_CMD;
+       u64 addr, tmp;
+
+       if (mmd == MDIO_DEVAD_NONE)
+               return -ENODEV;
+
+       if (!priv->info->supports_sgmii[phy])
+               return -ENODEV;
+
+       addr = (mmd << 16) | (reg & GENMASK(15, 0));
+       tmp = val;
+
+       if (mmd != MDIO_MMD_VEND1 && mmd != MDIO_MMD_VEND2)
+               return -ENODEV;
+
+       sja1105_packing(packed_buf, &tmp, 31, 0, size, PACK);
+
+       return sja1105_xfer_buf(priv, SPI_WRITE, addr, packed_buf, size);
+}
+
+static int sja1110_pcs_mdio_read(struct mii_dev *bus, int phy, int mmd, int reg)
+{
+       struct sja1105_private *priv = bus->priv;
+       const struct sja1105_regs *regs = priv->info->regs;
+       u8 packed_buf[SJA1105_SIZE_MDIO_CMD] = {0};
+       const int size = SJA1105_SIZE_MDIO_CMD;
+       int offset, bank;
+       u64 addr, tmp;
+       int rc;
+
+       if (mmd == MDIO_DEVAD_NONE)
+               return -ENODEV;
+
+       if (regs->pcs_base[phy] == SJA1105_RSV_ADDR)
+               return -ENODEV;
+
+       addr = (mmd << 16) | (reg & GENMASK(15, 0));
+
+       bank = addr >> 8;
+       offset = addr & GENMASK(7, 0);
+
+       /* This addressing scheme reserves register 0xff for the bank address
+        * register, so that can never be addressed.
+        */
+       if (offset == 0xff)
+               return -ENODEV;
+
+       tmp = bank;
+
+       sja1105_packing(packed_buf, &tmp, 31, 0, size, PACK);
+
+       rc = sja1105_xfer_buf(priv, SPI_WRITE,
+                             regs->pcs_base[phy] + SJA1110_PCS_BANK_REG,
+                             packed_buf, size);
+       if (rc < 0)
+               return rc;
+
+       rc = sja1105_xfer_buf(priv, SPI_READ, regs->pcs_base[phy] + offset,
+                             packed_buf, size);
+       if (rc < 0)
+               return rc;
+
+       sja1105_packing(packed_buf, &tmp, 31, 0, size, UNPACK);
+
+       return tmp & 0xffff;
+}
+
+static int sja1110_pcs_mdio_write(struct mii_dev *bus, int phy, int mmd,
+                                 int reg, u16 val)
+{
+       struct sja1105_private *priv = bus->priv;
+       const struct sja1105_regs *regs = priv->info->regs;
+       u8 packed_buf[SJA1105_SIZE_MDIO_CMD] = {0};
+       const int size = SJA1105_SIZE_MDIO_CMD;
+       int offset, bank;
+       u64 addr, tmp;
+       int rc;
+
+       if (mmd == MDIO_DEVAD_NONE)
+               return -ENODEV;
+
+       if (regs->pcs_base[phy] == SJA1105_RSV_ADDR)
+               return -ENODEV;
+
+       addr = (mmd << 16) | (reg & GENMASK(15, 0));
+
+       bank = addr >> 8;
+       offset = addr & GENMASK(7, 0);
+
+       /* This addressing scheme reserves register 0xff for the bank address
+        * register, so that can never be addressed.
+        */
+       if (offset == 0xff)
+               return -ENODEV;
+
+       tmp = bank;
+       sja1105_packing(packed_buf, &tmp, 31, 0, size, PACK);
+
+       rc = sja1105_xfer_buf(priv, SPI_WRITE,
+                             regs->pcs_base[phy] + SJA1110_PCS_BANK_REG,
+                             packed_buf, size);
+       if (rc < 0)
+               return rc;
+
+       tmp = val;
+       sja1105_packing(packed_buf, &tmp, 31, 0, size, PACK);
+
+       return sja1105_xfer_buf(priv, SPI_WRITE, regs->pcs_base[phy] + offset,
+                               packed_buf, size);
+}
+
+static int sja1105_mdiobus_register(struct sja1105_private *priv)
+{
+       struct udevice *dev = priv->dev;
+       struct mii_dev *bus;
+       int rc;
+
+       if (!priv->info->pcs_mdio_read || !priv->info->pcs_mdio_write)
+               return 0;
+
+       bus = mdio_alloc();
+       if (!bus)
+               return -ENOMEM;
+
+       snprintf(bus->name, MDIO_NAME_LEN, "%s-pcs", dev->name);
+       bus->read = priv->info->pcs_mdio_read;
+       bus->write = priv->info->pcs_mdio_write;
+       bus->priv = priv;
+
+       rc = mdio_register(bus);
+       if (rc) {
+               mdio_free(bus);
+               return rc;
+       }
+
+       priv->mdio_pcs = bus;
+
+       return 0;
+}
+
+static void sja1105_mdiobus_unregister(struct sja1105_private *priv)
+{
+       if (!priv->mdio_pcs)
+               return;
+
+       mdio_unregister(priv->mdio_pcs);
+       mdio_free(priv->mdio_pcs);
+}
+
 static const struct sja1105_regs sja1105et_regs = {
        .device_id = 0x0,
        .prod_id = 0x100BC3,
@@ -2185,6 +2697,9 @@ static const struct sja1105_regs sja1110_regs = {
                            SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
                            SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
                            SJA1105_RSV_ADDR},
+       .pcs_base = {SJA1105_RSV_ADDR, 0x1c1400, 0x1c1800, 0x1c1c00, 0x1c2000,
+                    SJA1105_RSV_ADDR, SJA1105_RSV_ADDR, SJA1105_RSV_ADDR,
+                    SJA1105_RSV_ADDR, SJA1105_RSV_ADDR, SJA1105_RSV_ADDR},
 };
 
 enum sja1105_switch_id {
@@ -2279,6 +2794,9 @@ static const struct sja1105_info sja1105_info[] = {
                .setup_rgmii_delay      = sja1105pqrs_setup_rgmii_delay,
                .reset_cmd              = sja1105pqrs_reset_cmd,
                .regs                   = &sja1105pqrs_regs,
+               .pcs_mdio_read          = sja1105_pcs_mdio_read,
+               .pcs_mdio_write         = sja1105_pcs_mdio_write,
+               .pma_config             = sja1105_pma_config,
                .port_speed             = {
                        [SJA1105_SPEED_AUTO] = 0,
                        [SJA1105_SPEED_10MBPS] = 3,
@@ -2288,6 +2806,7 @@ static const struct sja1105_info sja1105_info[] = {
                .supports_mii           = {true, true, true, true, true},
                .supports_rmii          = {true, true, true, true, true},
                .supports_rgmii         = {true, true, true, true, true},
+               .supports_sgmii         = {false, false, false, false, true},
                .name                   = "SJA1105R",
        },
        [SJA1105S] = {
@@ -2297,6 +2816,9 @@ static const struct sja1105_info sja1105_info[] = {
                .setup_rgmii_delay      = sja1105pqrs_setup_rgmii_delay,
                .reset_cmd              = sja1105pqrs_reset_cmd,
                .regs                   = &sja1105pqrs_regs,
+               .pcs_mdio_read          = sja1105_pcs_mdio_read,
+               .pcs_mdio_write         = sja1105_pcs_mdio_write,
+               .pma_config             = sja1105_pma_config,
                .port_speed             = {
                        [SJA1105_SPEED_AUTO] = 0,
                        [SJA1105_SPEED_10MBPS] = 3,
@@ -2306,6 +2828,7 @@ static const struct sja1105_info sja1105_info[] = {
                .supports_mii           = {true, true, true, true, true},
                .supports_rmii          = {true, true, true, true, true},
                .supports_rgmii         = {true, true, true, true, true},
+               .supports_sgmii         = {false, false, false, false, true},
                .name                   = "SJA1105S",
        },
        [SJA1110A] = {
@@ -2315,6 +2838,9 @@ static const struct sja1105_info sja1105_info[] = {
                .setup_rgmii_delay      = sja1110_setup_rgmii_delay,
                .reset_cmd              = sja1110_reset_cmd,
                .regs                   = &sja1110_regs,
+               .pcs_mdio_read          = sja1110_pcs_mdio_read,
+               .pcs_mdio_write         = sja1110_pcs_mdio_write,
+               .pma_config             = sja1110_pma_config,
                .port_speed             = {
                        [SJA1105_SPEED_AUTO] = 0,
                        [SJA1105_SPEED_10MBPS] = 4,
@@ -2327,6 +2853,8 @@ static const struct sja1105_info sja1105_info[] = {
                                           false, false, false, false, false, false},
                .supports_rgmii         = {false, false, true, true, false,
                                           false, false, false, false, false, false},
+               .supports_sgmii         = {false, true, true, true, true,
+                                          false, false, false, false, false, false},
                .name                   = "SJA1110A",
        },
        [SJA1110B] = {
@@ -2336,6 +2864,9 @@ static const struct sja1105_info sja1105_info[] = {
                .setup_rgmii_delay      = sja1110_setup_rgmii_delay,
                .reset_cmd              = sja1110_reset_cmd,
                .regs                   = &sja1110_regs,
+               .pcs_mdio_read          = sja1110_pcs_mdio_read,
+               .pcs_mdio_write         = sja1110_pcs_mdio_write,
+               .pma_config             = sja1110_pma_config,
                .port_speed             = {
                        [SJA1105_SPEED_AUTO] = 0,
                        [SJA1105_SPEED_10MBPS] = 4,
@@ -2348,6 +2879,8 @@ static const struct sja1105_info sja1105_info[] = {
                                           false, false, false, false, false, false},
                .supports_rgmii         = {false, false, true, true, false,
                                           false, false, false, false, false, false},
+               .supports_sgmii         = {false, false, false, true, true,
+                                          false, false, false, false, false, false},
                .name                   = "SJA1110B",
        },
        [SJA1110C] = {
@@ -2357,6 +2890,9 @@ static const struct sja1105_info sja1105_info[] = {
                .setup_rgmii_delay      = sja1110_setup_rgmii_delay,
                .reset_cmd              = sja1110_reset_cmd,
                .regs                   = &sja1110_regs,
+               .pcs_mdio_read          = sja1110_pcs_mdio_read,
+               .pcs_mdio_write         = sja1110_pcs_mdio_write,
+               .pma_config             = sja1110_pma_config,
                .port_speed             = {
                        [SJA1105_SPEED_AUTO] = 0,
                        [SJA1105_SPEED_10MBPS] = 4,
@@ -2369,6 +2905,8 @@ static const struct sja1105_info sja1105_info[] = {
                                           false, false, false, false, false, false},
                .supports_rgmii         = {false, false, true, true, false,
                                           false, false, false, false, false, false},
+               .supports_sgmii         = {false, false, false, false, true,
+                                          false, false, false, false, false, false},
                .name                   = "SJA1110C",
        },
        [SJA1110D] = {
@@ -2378,6 +2916,9 @@ static const struct sja1105_info sja1105_info[] = {
                .setup_rgmii_delay      = sja1110_setup_rgmii_delay,
                .reset_cmd              = sja1110_reset_cmd,
                .regs                   = &sja1110_regs,
+               .pcs_mdio_read          = sja1110_pcs_mdio_read,
+               .pcs_mdio_write         = sja1110_pcs_mdio_write,
+               .pma_config             = sja1110_pma_config,
                .port_speed             = {
                        [SJA1105_SPEED_AUTO] = 0,
                        [SJA1105_SPEED_10MBPS] = 4,
@@ -2390,6 +2931,8 @@ static const struct sja1105_info sja1105_info[] = {
                                           false, false, false, false, false, false},
                .supports_rgmii         = {false, false, true, false, false,
                                           false, false, false, false, false, false},
+               .supports_sgmii         = {false, true, true, true, true,
+                                          false, false, false, false, false, false},
                .name                   = "SJA1110D",
        },
 };
@@ -2541,8 +3084,12 @@ static int sja1105_static_config_reload(struct sja1105_private *priv)
 static int sja1105_port_probe(struct udevice *dev, int port,
                              struct phy_device *phy)
 {
+       struct sja1105_private *priv = dev_get_priv(dev);
+       ofnode node = dsa_port_get_ofnode(dev, port);
        phy_interface_t phy_mode = phy->interface;
 
+       priv->xpcs_cfg[port].inband_an = ofnode_eth_uses_inband_aneg(node);
+
        if (phy_mode == PHY_INTERFACE_MODE_MII ||
            phy_mode == PHY_INTERFACE_MODE_RMII) {
                phy->supported &= PHY_BASIC_FEATURES;
@@ -2593,6 +3140,13 @@ static int sja1105_port_enable(struct udevice *dev, int port,
 
                mii->xmii_mode[port] = XMII_MODE_RGMII;
                break;
+       case PHY_INTERFACE_MODE_SGMII:
+               if (!priv->info->supports_sgmii[port])
+                       goto unsupported;
+
+               mii->xmii_mode[port] = XMII_MODE_SGMII;
+               mii->special[port] = true;
+               break;
 unsupported:
        default:
                dev_err(dev, "Unsupported PHY mode %d on port %d!\n",
@@ -2621,7 +3175,10 @@ unsupported:
                }
        }
 
-       if (phy->speed == SPEED_1000) {
+       if (mii->xmii_mode[port] == XMII_MODE_SGMII) {
+               mac[port].speed = priv->info->port_speed[SJA1105_SPEED_1000MBPS];
+               priv->xpcs_cfg[port].speed = phy->speed;
+       } else if (phy->speed == SPEED_1000) {
                mac[port].speed = priv->info->port_speed[SJA1105_SPEED_1000MBPS];
        } else if (phy->speed == SPEED_100) {
                mac[port].speed = priv->info->port_speed[SJA1105_SPEED_100MBPS];
@@ -2688,7 +3245,18 @@ static int sja1105_init(struct sja1105_private *priv)
                return rc;
        }
 
+       rc = sja1105_mdiobus_register(priv);
+       if (rc) {
+               printf("Failed to register MDIO bus: %d\n", rc);
+               goto err_mdiobus_register;
+       }
+
        return 0;
+
+err_mdiobus_register:
+       sja1105_static_config_free(&priv->static_config);
+
+       return rc;
 }
 
 static int sja1105_check_device_id(struct sja1105_private *priv)
@@ -2777,6 +3345,7 @@ static int sja1105_remove(struct udevice *dev)
 {
        struct sja1105_private *priv = dev_get_priv(dev);
 
+       sja1105_mdiobus_unregister(priv);
        sja1105_static_config_free(&priv->static_config);
 
        return 0;