tristate "NXP SJA1105 Ethernet switch family support"
        depends on NET_DSA && SPI
        select NET_DSA_TAG_SJA1105
+       select PCS_XPCS
        select PACKING
        select CRC32
        help
 
        bool (*rxtstamp)(struct dsa_switch *ds, int port, struct sk_buff *skb);
        void (*txtstamp)(struct dsa_switch *ds, int port, struct sk_buff *skb);
        int (*clocking_setup)(struct sja1105_private *priv);
+       int (*pcs_mdio_read)(struct mii_bus *bus, int phy, int reg);
+       int (*pcs_mdio_write)(struct mii_bus *bus, int phy, int reg, u16 val);
        const char *name;
        bool supports_mii[SJA1105_MAX_NUM_PORTS];
        bool supports_rmii[SJA1105_MAX_NUM_PORTS];
        struct sja1105_cbs_entry *cbs;
        struct mii_bus *mdio_base_t1;
        struct mii_bus *mdio_base_tx;
+       struct mii_bus *mdio_pcs;
+       struct dw_xpcs *xpcs[SJA1105_MAX_NUM_PORTS];
        struct sja1105_tagger_data tagger_data;
        struct sja1105_ptp_data ptp_data;
        struct sja1105_tas_data tas_data;
 /* From sja1105_mdio.c */
 int sja1105_mdiobus_register(struct dsa_switch *ds);
 void sja1105_mdiobus_unregister(struct dsa_switch *ds);
+int sja1105_pcs_mdio_read(struct mii_bus *bus, int phy, int reg);
+int sja1105_pcs_mdio_write(struct mii_bus *bus, int phy, int reg, u16 val);
 
 /* From sja1105_devlink.c */
 int sja1105_devlink_setup(struct dsa_switch *ds);
 
 #include <linux/of_net.h>
 #include <linux/of_mdio.h>
 #include <linux/of_device.h>
+#include <linux/pcs/pcs-xpcs.h>
 #include <linux/netdev_features.h>
 #include <linux/netdevice.h>
 #include <linux/if_bridge.h>
 #include <linux/if_ether.h>
 #include <linux/dsa/8021q.h>
 #include "sja1105.h"
-#include "sja1105_sgmii.h"
 #include "sja1105_tas.h"
 
 #define SJA1105_UNKNOWN_MULTICAST      0x010000000000ull
        return rc;
 }
 
-static int sja1105_sgmii_read(struct sja1105_private *priv, int port, int mmd,
-                             int pcs_reg)
-{
-       u64 addr = (mmd << 16) | pcs_reg;
-       u32 val;
-       int rc;
-
-       if (port != SJA1105_SGMII_PORT)
-               return -ENODEV;
-
-       rc = sja1105_xfer_u32(priv, SPI_READ, addr, &val, NULL);
-       if (rc < 0)
-               return rc;
-
-       return val;
-}
-
-static int sja1105_sgmii_write(struct sja1105_private *priv, int port, int mmd,
-                              int pcs_reg, u16 pcs_val)
-{
-       u64 addr = (mmd << 16) | pcs_reg;
-       u32 val = pcs_val;
-       int rc;
-
-       if (port != SJA1105_SGMII_PORT)
-               return -ENODEV;
-
-       rc = sja1105_xfer_u32(priv, SPI_WRITE, addr, &val, NULL);
-       if (rc < 0)
-               return rc;
-
-       return val;
-}
-
-static void sja1105_sgmii_pcs_config(struct sja1105_private *priv, int port,
-                                    bool an_enabled, bool an_master)
-{
-       u16 ac = SJA1105_AC_AUTONEG_MODE_SGMII;
-
-       /* DIGITAL_CONTROL_1: Enable vendor-specific MMD1, allow the PHY to
-        * stop the clock during LPI mode, make the MAC reconfigure
-        * autonomously after PCS autoneg is done, flush the internal FIFOs.
-        */
-       sja1105_sgmii_write(priv, port, MDIO_MMD_VEND2, SJA1105_DC1,
-                           SJA1105_DC1_EN_VSMMD1 |
-                           SJA1105_DC1_CLOCK_STOP_EN |
-                           SJA1105_DC1_MAC_AUTO_SW |
-                           SJA1105_DC1_INIT);
-       /* DIGITAL_CONTROL_2: No polarity inversion for TX and RX lanes */
-       sja1105_sgmii_write(priv, port, MDIO_MMD_VEND2, SJA1105_DC2,
-                           SJA1105_DC2_TX_POL_INV_DISABLE);
-       /* AUTONEG_CONTROL: Use SGMII autoneg */
-       if (an_master)
-               ac |= SJA1105_AC_PHY_MODE | SJA1105_AC_SGMII_LINK;
-       sja1105_sgmii_write(priv, port, MDIO_MMD_VEND2, SJA1105_AC, ac);
-       /* BASIC_CONTROL: enable in-band AN now, if requested. Otherwise,
-        * sja1105_sgmii_pcs_force_speed must be called later for the link
-        * to become operational.
-        */
-       if (an_enabled)
-               sja1105_sgmii_write(priv, port, MDIO_MMD_VEND2, MDIO_CTRL1,
-                                   BMCR_ANENABLE | BMCR_ANRESTART);
-}
-
-static void sja1105_sgmii_pcs_force_speed(struct sja1105_private *priv,
-                                         int port, int speed)
-{
-       int pcs_speed;
-
-       switch (speed) {
-       case SPEED_1000:
-               pcs_speed = BMCR_SPEED1000;
-               break;
-       case SPEED_100:
-               pcs_speed = BMCR_SPEED100;
-               break;
-       case SPEED_10:
-               pcs_speed = BMCR_SPEED10;
-               break;
-       default:
-               dev_err(priv->ds->dev, "Invalid speed %d\n", speed);
-               return;
-       }
-       sja1105_sgmii_write(priv, port, MDIO_MMD_VEND2, MDIO_CTRL1,
-                           pcs_speed | BMCR_FULLDPLX);
-}
-
 /* Convert link speed from SJA1105 to ethtool encoding */
 static int sja1105_port_speed_to_ethtool(struct sja1105_private *priv,
                                         u64 speed)
                               unsigned int mode,
                               const struct phylink_link_state *state)
 {
+       struct dsa_port *dp = dsa_to_port(ds, port);
        struct sja1105_private *priv = ds->priv;
-       bool is_sgmii;
-
-       is_sgmii = (state->interface == PHY_INTERFACE_MODE_SGMII);
+       struct dw_xpcs *xpcs;
 
        if (sja1105_phy_mode_mismatch(priv, port, state->interface)) {
                dev_err(ds->dev, "Changing PHY mode to %s not supported!\n",
                return;
        }
 
-       if (phylink_autoneg_inband(mode) && !is_sgmii) {
-               dev_err(ds->dev, "In-band AN not supported!\n");
-               return;
-       }
+       xpcs = priv->xpcs[port];
 
-       if (is_sgmii)
-               sja1105_sgmii_pcs_config(priv, port,
-                                        phylink_autoneg_inband(mode),
-                                        false);
+       if (xpcs)
+               phylink_set_pcs(dp->pl, &xpcs->pcs);
 }
 
 static void sja1105_mac_link_down(struct dsa_switch *ds, int port,
 
        sja1105_adjust_port_config(priv, port, speed);
 
-       if (priv->phy_mode[port] == PHY_INTERFACE_MODE_SGMII &&
-           !phylink_autoneg_inband(mode))
-               sja1105_sgmii_pcs_force_speed(priv, port, speed);
-
        sja1105_inhibit_tx(priv, BIT(port), false);
 }
 
                   __ETHTOOL_LINK_MODE_MASK_NBITS);
 }
 
-static int sja1105_mac_pcs_get_state(struct dsa_switch *ds, int port,
-                                    struct phylink_link_state *state)
-{
-       struct sja1105_private *priv = ds->priv;
-       int ais;
-
-       /* Read the vendor-specific AUTONEG_INTR_STATUS register */
-       ais = sja1105_sgmii_read(priv, port, MDIO_MMD_VEND2, SJA1105_AIS);
-       if (ais < 0)
-               return ais;
-
-       switch (SJA1105_AIS_SPEED(ais)) {
-       case 0:
-               state->speed = SPEED_10;
-               break;
-       case 1:
-               state->speed = SPEED_100;
-               break;
-       case 2:
-               state->speed = SPEED_1000;
-               break;
-       default:
-               dev_err(ds->dev, "Invalid SGMII PCS speed %lu\n",
-                       SJA1105_AIS_SPEED(ais));
-       }
-       state->duplex = SJA1105_AIS_DUPLEX_MODE(ais);
-       state->an_complete = SJA1105_AIS_COMPLETE(ais);
-       state->link = SJA1105_AIS_LINK_STATUS(ais);
-
-       return 0;
-}
-
 static int
 sja1105_find_static_fdb_entry(struct sja1105_private *priv, int port,
                              const struct sja1105_l2_lookup_entry *requested)
         * change it through the dynamic interface later.
         */
        for (i = 0; i < ds->num_ports; i++) {
+               u32 reg_addr = mdiobus_c45_addr(MDIO_MMD_VEND2, MDIO_CTRL1);
+
                speed_mbps[i] = sja1105_port_speed_to_ethtool(priv,
                                                              mac[i].speed);
                mac[i].speed = priv->info->port_speed[SJA1105_SPEED_AUTO];
 
-               if (priv->phy_mode[i] == PHY_INTERFACE_MODE_SGMII)
-                       bmcr[i] = sja1105_sgmii_read(priv, i,
-                                                    MDIO_MMD_VEND2,
-                                                    MDIO_CTRL1);
+               if (priv->xpcs[i])
+                       bmcr[i] = mdiobus_read(priv->mdio_pcs, i, reg_addr);
        }
 
        /* No PTP operations can run right now */
                goto out;
 
        for (i = 0; i < ds->num_ports; i++) {
-               bool an_enabled;
+               struct dw_xpcs *xpcs = priv->xpcs[i];
+               unsigned int mode;
 
                rc = sja1105_adjust_port_config(priv, i, speed_mbps[i]);
                if (rc < 0)
                        goto out;
 
-               if (priv->phy_mode[i] != PHY_INTERFACE_MODE_SGMII)
+               if (!xpcs)
                        continue;
 
-               an_enabled = !!(bmcr[i] & BMCR_ANENABLE);
+               if (bmcr[i] & BMCR_ANENABLE)
+                       mode = MLO_AN_INBAND;
+               else if (priv->fixed_link[i])
+                       mode = MLO_AN_FIXED;
+               else
+                       mode = MLO_AN_PHY;
 
-               sja1105_sgmii_pcs_config(priv, i, an_enabled, false);
+               rc = xpcs_do_config(xpcs, priv->phy_mode[i], mode);
+               if (rc < 0)
+                       goto out;
 
-               if (!an_enabled) {
+               if (!phylink_autoneg_inband(mode)) {
                        int speed = SPEED_UNKNOWN;
 
                        if (bmcr[i] & BMCR_SPEED1000)
                        else
                                speed = SPEED_10;
 
-                       sja1105_sgmii_pcs_force_speed(priv, i, speed);
+                       xpcs_link_up(&xpcs->pcs, mode, priv->phy_mode[i],
+                                    speed, DUPLEX_FULL);
                }
        }
 
        .port_change_mtu        = sja1105_change_mtu,
        .port_max_mtu           = sja1105_get_max_mtu,
        .phylink_validate       = sja1105_phylink_validate,
-       .phylink_mac_link_state = sja1105_mac_pcs_get_state,
        .phylink_mac_config     = sja1105_mac_config,
        .phylink_mac_link_up    = sja1105_mac_link_up,
        .phylink_mac_link_down  = sja1105_mac_link_down,
 
 // SPDX-License-Identifier: GPL-2.0
 /* Copyright 2021, NXP Semiconductors
  */
+#include <linux/pcs/pcs-xpcs.h>
 #include <linux/of_mdio.h>
 #include "sja1105.h"
 
+int sja1105_pcs_mdio_read(struct mii_bus *bus, int phy, int reg)
+{
+       struct sja1105_mdio_private *mdio_priv = bus->priv;
+       struct sja1105_private *priv = mdio_priv->priv;
+       u64 addr;
+       u32 tmp;
+       u16 mmd;
+       int rc;
+
+       if (!(reg & MII_ADDR_C45))
+               return -EINVAL;
+
+       mmd = (reg >> MII_DEVADDR_C45_SHIFT) & 0x1f;
+       addr = (mmd << 16) | (reg & GENMASK(15, 0));
+
+       if (mmd != MDIO_MMD_VEND1 && mmd != MDIO_MMD_VEND2)
+               return 0xffff;
+
+       if (mmd == MDIO_MMD_VEND2 && (reg & GENMASK(15, 0)) == MII_PHYSID1)
+               return NXP_SJA1105_XPCS_ID >> 16;
+       if (mmd == MDIO_MMD_VEND2 && (reg & GENMASK(15, 0)) == MII_PHYSID2)
+               return NXP_SJA1105_XPCS_ID & GENMASK(15, 0);
+
+       rc = sja1105_xfer_u32(priv, SPI_READ, addr, &tmp, NULL);
+       if (rc < 0)
+               return rc;
+
+       return tmp & 0xffff;
+}
+
+int sja1105_pcs_mdio_write(struct mii_bus *bus, int phy, int reg, u16 val)
+{
+       struct sja1105_mdio_private *mdio_priv = bus->priv;
+       struct sja1105_private *priv = mdio_priv->priv;
+       u64 addr;
+       u32 tmp;
+       u16 mmd;
+
+       if (!(reg & MII_ADDR_C45))
+               return -EINVAL;
+
+       mmd = (reg >> MII_DEVADDR_C45_SHIFT) & 0x1f;
+       addr = (mmd << 16) | (reg & GENMASK(15, 0));
+       tmp = val;
+
+       if (mmd != MDIO_MMD_VEND1 && mmd != MDIO_MMD_VEND2)
+               return -EINVAL;
+
+       return sja1105_xfer_u32(priv, SPI_WRITE, addr, &tmp, NULL);
+}
+
 enum sja1105_mdio_opcode {
        SJA1105_C45_ADDR = 0,
        SJA1105_C22 = 1,
        priv->mdio_base_t1 = NULL;
 }
 
+static int sja1105_mdiobus_pcs_register(struct sja1105_private *priv)
+{
+       struct sja1105_mdio_private *mdio_priv;
+       struct dsa_switch *ds = priv->ds;
+       struct mii_bus *bus;
+       int rc = 0;
+       int port;
+
+       if (!priv->info->pcs_mdio_read || !priv->info->pcs_mdio_write)
+               return 0;
+
+       bus = mdiobus_alloc_size(sizeof(*mdio_priv));
+       if (!bus)
+               return -ENOMEM;
+
+       bus->name = "SJA1105 PCS MDIO bus";
+       snprintf(bus->id, MII_BUS_ID_SIZE, "%s-pcs",
+                dev_name(ds->dev));
+       bus->read = priv->info->pcs_mdio_read;
+       bus->write = priv->info->pcs_mdio_write;
+       bus->parent = ds->dev;
+       /* There is no PHY on this MDIO bus => mask out all PHY addresses
+        * from auto probing.
+        */
+       bus->phy_mask = ~0;
+       mdio_priv = bus->priv;
+       mdio_priv->priv = priv;
+
+       rc = mdiobus_register(bus);
+       if (rc) {
+               mdiobus_free(bus);
+               return rc;
+       }
+
+       for (port = 0; port < ds->num_ports; port++) {
+               struct mdio_device *mdiodev;
+               struct dw_xpcs *xpcs;
+
+               if (dsa_is_unused_port(ds, port))
+                       continue;
+
+               if (priv->phy_mode[port] != PHY_INTERFACE_MODE_SGMII)
+                       continue;
+
+               mdiodev = mdio_device_create(bus, port);
+               if (IS_ERR(mdiodev)) {
+                       rc = PTR_ERR(mdiodev);
+                       goto out_pcs_free;
+               }
+
+               xpcs = xpcs_create(mdiodev, priv->phy_mode[port]);
+               if (IS_ERR(xpcs)) {
+                       rc = PTR_ERR(xpcs);
+                       goto out_pcs_free;
+               }
+
+               priv->xpcs[port] = xpcs;
+       }
+
+       priv->mdio_pcs = bus;
+
+       return 0;
+
+out_pcs_free:
+       for (port = 0; port < ds->num_ports; port++) {
+               if (!priv->xpcs[port])
+                       continue;
+
+               mdio_device_free(priv->xpcs[port]->mdiodev);
+               xpcs_destroy(priv->xpcs[port]);
+               priv->xpcs[port] = NULL;
+       }
+
+       mdiobus_unregister(bus);
+       mdiobus_free(bus);
+
+       return rc;
+}
+
+static void sja1105_mdiobus_pcs_unregister(struct sja1105_private *priv)
+{
+       struct dsa_switch *ds = priv->ds;
+       int port;
+
+       if (!priv->mdio_pcs)
+               return;
+
+       for (port = 0; port < ds->num_ports; port++) {
+               if (!priv->xpcs[port])
+                       continue;
+
+               mdio_device_free(priv->xpcs[port]->mdiodev);
+               xpcs_destroy(priv->xpcs[port]);
+               priv->xpcs[port] = NULL;
+       }
+
+       mdiobus_unregister(priv->mdio_pcs);
+       mdiobus_free(priv->mdio_pcs);
+       priv->mdio_pcs = NULL;
+}
+
 int sja1105_mdiobus_register(struct dsa_switch *ds)
 {
        struct sja1105_private *priv = ds->priv;
        struct device_node *mdio_node;
        int rc;
 
+       rc = sja1105_mdiobus_pcs_register(priv);
+       if (rc)
+               return rc;
+
        mdio_node = of_get_child_by_name(switch_node, "mdios");
        if (!mdio_node)
                return 0;
        sja1105_mdiobus_base_tx_unregister(priv);
 err_put_mdio_node:
        of_node_put(mdio_node);
+       sja1105_mdiobus_pcs_unregister(priv);
 
        return rc;
 }
 
        sja1105_mdiobus_base_t1_unregister(priv);
        sja1105_mdiobus_base_tx_unregister(priv);
+       sja1105_mdiobus_pcs_unregister(priv);
 }
 
+++ /dev/null
-/* SPDX-License-Identifier: BSD-3-Clause */
-/* Copyright 2020, NXP Semiconductors
- */
-#ifndef _SJA1105_SGMII_H
-#define _SJA1105_SGMII_H
-
-#define SJA1105_SGMII_PORT             4
-
-/* DIGITAL_CONTROL_1 (address 1f8000h) */
-#define SJA1105_DC1                    0x8000
-#define SJA1105_DC1_VS_RESET           BIT(15)
-#define SJA1105_DC1_REMOTE_LOOPBACK    BIT(14)
-#define SJA1105_DC1_EN_VSMMD1          BIT(13)
-#define SJA1105_DC1_POWER_SAVE         BIT(11)
-#define SJA1105_DC1_CLOCK_STOP_EN      BIT(10)
-#define SJA1105_DC1_MAC_AUTO_SW                BIT(9)
-#define SJA1105_DC1_INIT               BIT(8)
-#define SJA1105_DC1_TX_DISABLE         BIT(4)
-#define SJA1105_DC1_AUTONEG_TIMER_OVRR BIT(3)
-#define SJA1105_DC1_BYP_POWERUP                BIT(1)
-#define SJA1105_DC1_PHY_MODE_CONTROL   BIT(0)
-
-/* DIGITAL_CONTROL_2 register (address 1f80E1h) */
-#define SJA1105_DC2                    0x80e1
-#define SJA1105_DC2_TX_POL_INV_DISABLE BIT(4)
-#define SJA1105_DC2_RX_POL_INV         BIT(0)
-
-/* DIGITAL_ERROR_CNT register (address 1f80E2h) */
-#define SJA1105_DEC                    0x80e2
-#define SJA1105_DEC_ICG_EC_ENA         BIT(4)
-#define SJA1105_DEC_CLEAR_ON_READ      BIT(0)
-
-/* AUTONEG_CONTROL register (address 1f8001h) */
-#define SJA1105_AC                     0x8001
-#define SJA1105_AC_MII_CONTROL         BIT(8)
-#define SJA1105_AC_SGMII_LINK          BIT(4)
-#define SJA1105_AC_PHY_MODE            BIT(3)
-#define SJA1105_AC_AUTONEG_MODE(x)     (((x) << 1) & GENMASK(2, 1))
-#define SJA1105_AC_AUTONEG_MODE_SGMII  SJA1105_AC_AUTONEG_MODE(2)
-
-/* AUTONEG_INTR_STATUS register (address 1f8002h) */
-#define SJA1105_AIS                    0x8002
-#define SJA1105_AIS_LINK_STATUS(x)     (!!((x) & BIT(4)))
-#define SJA1105_AIS_SPEED(x)           (((x) & GENMASK(3, 2)) >> 2)
-#define SJA1105_AIS_DUPLEX_MODE(x)     (!!((x) & BIT(1)))
-#define SJA1105_AIS_COMPLETE(x)                (!!((x) & BIT(0)))
-
-/* DEBUG_CONTROL register (address 1f8005h) */
-#define SJA1105_DC                     0x8005
-#define SJA1105_DC_SUPPRESS_LOS                BIT(4)
-#define SJA1105_DC_RESTART_SYNC                BIT(0)
-
-#endif
 
        .ptp_cmd_packing        = sja1105pqrs_ptp_cmd_packing,
        .rxtstamp               = sja1105_rxtstamp,
        .clocking_setup         = sja1105_clocking_setup,
+       .pcs_mdio_read          = sja1105_pcs_mdio_read,
+       .pcs_mdio_write         = sja1105_pcs_mdio_write,
        .regs                   = &sja1105pqrs_regs,
        .port_speed             = {
                [SJA1105_SPEED_AUTO] = 0,
        .ptp_cmd_packing        = sja1105pqrs_ptp_cmd_packing,
        .rxtstamp               = sja1105_rxtstamp,
        .clocking_setup         = sja1105_clocking_setup,
+       .pcs_mdio_read          = sja1105_pcs_mdio_read,
+       .pcs_mdio_write         = sja1105_pcs_mdio_write,
        .port_speed             = {
                [SJA1105_SPEED_AUTO] = 0,
                [SJA1105_SPEED_10MBPS] = 3,