/*
  * Microchip KSZ9477 switch driver main logic
  *
- * Copyright (C) 2017-2024 Microchip Technology Inc.
+ * Copyright (C) 2017-2025 Microchip Technology Inc.
  */
 
 #include <linux/kernel.h>
                                        10, 1000);
 }
 
+static void port_sgmii_s(struct ksz_device *dev, uint port, u16 devid, u16 reg)
+{
+       u32 data;
+
+       data = (devid & MII_MMD_CTRL_DEVAD_MASK) << 16;
+       data |= reg;
+       ksz_pwrite32(dev, port, REG_PORT_SGMII_ADDR__4, data);
+}
+
+static void port_sgmii_r(struct ksz_device *dev, uint port, u16 devid, u16 reg,
+                        u16 *buf)
+{
+       port_sgmii_s(dev, port, devid, reg);
+       ksz_pread16(dev, port, REG_PORT_SGMII_DATA__4 + 2, buf);
+}
+
+static void port_sgmii_w(struct ksz_device *dev, uint port, u16 devid, u16 reg,
+                        u16 buf)
+{
+       port_sgmii_s(dev, port, devid, reg);
+       ksz_pwrite32(dev, port, REG_PORT_SGMII_DATA__4, buf);
+}
+
+static int ksz9477_pcs_read(struct mii_bus *bus, int phy, int mmd, int reg)
+{
+       struct ksz_device *dev = bus->priv;
+       int port = ksz_get_sgmii_port(dev);
+       u16 val;
+
+       port_sgmii_r(dev, port, mmd, reg, &val);
+
+       /* Simulate a value to activate special code in the XPCS driver if
+        * supported.
+        */
+       if (mmd == MDIO_MMD_PMAPMD) {
+               if (reg == MDIO_DEVID1)
+                       val = 0x9477;
+               else if (reg == MDIO_DEVID2)
+                       val = 0x22 << 10;
+       } else if (mmd == MDIO_MMD_VEND2) {
+               struct ksz_port *p = &dev->ports[port];
+
+               /* Need to update MII_BMCR register with the exact speed and
+                * duplex mode when running in SGMII mode and this register is
+                * used to detect connected speed in that mode.
+                */
+               if (reg == MMD_SR_MII_AUTO_NEG_STATUS) {
+                       int duplex, speed;
+
+                       if (val & SR_MII_STAT_LINK_UP) {
+                               speed = (val >> SR_MII_STAT_S) & SR_MII_STAT_M;
+                               if (speed == SR_MII_STAT_1000_MBPS)
+                                       speed = SPEED_1000;
+                               else if (speed == SR_MII_STAT_100_MBPS)
+                                       speed = SPEED_100;
+                               else
+                                       speed = SPEED_10;
+
+                               if (val & SR_MII_STAT_FULL_DUPLEX)
+                                       duplex = DUPLEX_FULL;
+                               else
+                                       duplex = DUPLEX_HALF;
+
+                               if (!p->phydev.link ||
+                                   p->phydev.speed != speed ||
+                                   p->phydev.duplex != duplex) {
+                                       u16 ctrl;
+
+                                       p->phydev.link = 1;
+                                       p->phydev.speed = speed;
+                                       p->phydev.duplex = duplex;
+                                       port_sgmii_r(dev, port, mmd, MII_BMCR,
+                                                    &ctrl);
+                                       ctrl &= BMCR_ANENABLE;
+                                       ctrl |= mii_bmcr_encode_fixed(speed,
+                                                                     duplex);
+                                       port_sgmii_w(dev, port, mmd, MII_BMCR,
+                                                    ctrl);
+                               }
+                       } else {
+                               p->phydev.link = 0;
+                       }
+               } else if (reg == MII_BMSR) {
+                       p->phydev.link = (val & BMSR_LSTATUS);
+               }
+       }
+
+       return val;
+}
+
+static int ksz9477_pcs_write(struct mii_bus *bus, int phy, int mmd, int reg,
+                            u16 val)
+{
+       struct ksz_device *dev = bus->priv;
+       int port = ksz_get_sgmii_port(dev);
+
+       if (mmd == MDIO_MMD_VEND2) {
+               struct ksz_port *p = &dev->ports[port];
+
+               if (reg == MMD_SR_MII_AUTO_NEG_CTRL) {
+                       u16 sgmii_mode = SR_MII_PCS_SGMII << SR_MII_PCS_MODE_S;
+
+                       /* Need these bits for 1000BASE-X mode to work with
+                        * AN on.
+                        */
+                       if (!(val & sgmii_mode))
+                               val |= SR_MII_SGMII_LINK_UP |
+                                      SR_MII_TX_CFG_PHY_MASTER;
+
+                       /* SGMII interrupt in the port cannot be masked, so
+                        * make sure interrupt is not enabled as it is not
+                        * handled.
+                        */
+                       val &= ~SR_MII_AUTO_NEG_COMPLETE_INTR;
+               } else if (reg == MII_BMCR) {
+                       /* The MII_ADVERTISE register needs to write once
+                        * before doing auto-negotiation for the correct
+                        * config_word to be sent out after reset.
+                        */
+                       if ((val & BMCR_ANENABLE) && !p->sgmii_adv_write) {
+                               u16 adv;
+
+                               /* The SGMII port cannot disable flow control
+                                * so it is better to just advertise symmetric
+                                * pause.
+                                */
+                               port_sgmii_r(dev, port, mmd, MII_ADVERTISE,
+                                            &adv);
+                               adv |= ADVERTISE_1000XPAUSE;
+                               adv &= ~ADVERTISE_1000XPSE_ASYM;
+                               port_sgmii_w(dev, port, mmd, MII_ADVERTISE,
+                                            adv);
+                               p->sgmii_adv_write = 1;
+                       } else if (val & BMCR_RESET) {
+                               p->sgmii_adv_write = 0;
+                       }
+               } else if (reg == MII_ADVERTISE) {
+                       /* XPCS driver writes to this register so there is no
+                        * need to update it for the errata.
+                        */
+                       p->sgmii_adv_write = 1;
+               }
+       }
+       port_sgmii_w(dev, port, mmd, reg, val);
+
+       return 0;
+}
+
+int ksz9477_pcs_create(struct ksz_device *dev)
+{
+       /* This chip has a SGMII port. */
+       if (ksz_has_sgmii_port(dev)) {
+               int port = ksz_get_sgmii_port(dev);
+               struct ksz_port *p = &dev->ports[port];
+               struct phylink_pcs *pcs;
+               struct mii_bus *bus;
+               int ret;
+
+               bus = devm_mdiobus_alloc(dev->dev);
+               if (!bus)
+                       return -ENOMEM;
+
+               bus->name = "ksz_pcs_mdio_bus";
+               snprintf(bus->id, MII_BUS_ID_SIZE, "%s-pcs",
+                        dev_name(dev->dev));
+               bus->read_c45 = &ksz9477_pcs_read;
+               bus->write_c45 = &ksz9477_pcs_write;
+               bus->parent = dev->dev;
+               bus->phy_mask = ~0;
+               bus->priv = dev;
+
+               ret = devm_mdiobus_register(dev->dev, bus);
+               if (ret)
+                       return ret;
+
+               pcs = xpcs_create_pcs_mdiodev(bus, 0);
+               if (IS_ERR(pcs))
+                       return PTR_ERR(pcs);
+               p->pcs = pcs;
+       }
+
+       return 0;
+}
+
 int ksz9477_reset_switch(struct ksz_device *dev)
 {
        u8 data8;
 
        if (dev->info->gbit_capable[port])
                config->mac_capabilities |= MAC_1000FD;
+
+       if (ksz_is_sgmii_port(dev, port)) {
+               struct ksz_port *p = &dev->ports[port];
+
+               phy_interface_or(config->supported_interfaces,
+                                config->supported_interfaces,
+                                p->pcs->supported_interfaces);
+       }
 }
 
 int ksz9477_set_ageing_time(struct ksz_device *dev, unsigned int msecs)
 
 /*
  * Microchip switch driver main logic
  *
- * Copyright (C) 2017-2024 Microchip Technology Inc.
+ * Copyright (C) 2017-2025 Microchip Technology Inc.
  */
 
 #include <linux/delay.h>
                                        int speed, int duplex, bool tx_pause,
                                        bool rx_pause);
 
+static struct phylink_pcs *
+ksz_phylink_mac_select_pcs(struct phylink_config *config,
+                          phy_interface_t interface)
+{
+       struct dsa_port *dp = dsa_phylink_to_port(config);
+       struct ksz_device *dev = dp->ds->priv;
+       struct ksz_port *p = &dev->ports[dp->index];
+
+       if (ksz_is_sgmii_port(dev, dp->index) &&
+           (interface == PHY_INTERFACE_MODE_SGMII ||
+           interface == PHY_INTERFACE_MODE_1000BASEX))
+               return p->pcs;
+
+       return NULL;
+}
+
 static const struct phylink_mac_ops ksz9477_phylink_mac_ops = {
        .mac_config     = ksz_phylink_mac_config,
        .mac_link_down  = ksz_phylink_mac_link_down,
        .mac_link_up    = ksz9477_phylink_mac_link_up,
        .mac_disable_tx_lpi = ksz_phylink_mac_disable_tx_lpi,
        .mac_enable_tx_lpi = ksz_phylink_mac_enable_tx_lpi,
+       .mac_select_pcs = ksz_phylink_mac_select_pcs,
 };
 
 static const struct ksz_dev_ops ksz9477_dev_ops = {
        .reset = ksz9477_reset_switch,
        .init = ksz9477_switch_init,
        .exit = ksz9477_switch_exit,
+       .pcs_create = ksz9477_pcs_create,
 };
 
 static const struct phylink_mac_ops lan937x_phylink_mac_ops = {
        regmap_reg_range(0x701b, 0x701b),
        regmap_reg_range(0x701f, 0x7020),
        regmap_reg_range(0x7030, 0x7030),
-       regmap_reg_range(0x7200, 0x7203),
-       regmap_reg_range(0x7206, 0x7207),
+       regmap_reg_range(0x7200, 0x7207),
        regmap_reg_range(0x7300, 0x7301),
        regmap_reg_range(0x7400, 0x7401),
        regmap_reg_range(0x7403, 0x7403),
                                   true, false, false},
                .gbit_capable   = {true, true, true, true, true, true, true},
                .ptp_capable = true,
+               .sgmii_port = 7,
                .wr_table = &ksz9477_register_set,
                .rd_table = &ksz9477_register_set,
        },
                .internal_phy   = {true, true, true, true,
                                   true, false, false},
                .gbit_capable   = {true, true, true, true, true, true, true},
+               .sgmii_port = 7,
                .wr_table = &ksz9477_register_set,
                .rd_table = &ksz9477_register_set,
        },
 
        spin_unlock(&mib->stats64_lock);
 
-       if (dev->info->phy_errata_9477) {
+       if (dev->info->phy_errata_9477 && !ksz_is_sgmii_port(dev, port)) {
                ret = ksz9477_errata_monitor(dev, port, raw->tx_late_col);
                if (ret)
                        dev_err(dev->dev, "Failed to monitor transmission halt\n");
        if (ret)
                return ret;
 
+       if (ksz_has_sgmii_port(dev) && dev->dev_ops->pcs_create) {
+               ret = dev->dev_ops->pcs_create(dev);
+               if (ret)
+                       return ret;
+       }
+
        /* set broadcast storm protection 10% rate */
        regmap_update_bits(ksz_regmap_16(dev), regs[S_BROADCAST_CTRL],
                           BROADCAST_STORM_RATE,
        if (dev->info->internal_phy[port])
                return;
 
+       /* No need to configure XMII control register when using SGMII. */
+       if (ksz_is_sgmii_port(dev, port))
+               return;
+
        if (phylink_autoneg_inband(mode)) {
                dev_err(dev->dev, "In-band AN not supported!\n");
                return;
 
 /* SPDX-License-Identifier: GPL-2.0 */
 /* Microchip switch driver common header
  *
- * Copyright (C) 2017-2024 Microchip Technology Inc.
+ * Copyright (C) 2017-2025 Microchip Technology Inc.
  */
 
 #ifndef __KSZ_COMMON_H
 #include <linux/etherdevice.h>
 #include <linux/kernel.h>
 #include <linux/mutex.h>
+#include <linux/pcs/pcs-xpcs.h>
 #include <linux/phy.h>
 #include <linux/regmap.h>
 #include <net/dsa.h>
        bool internal_phy[KSZ_MAX_NUM_PORTS];
        bool gbit_capable[KSZ_MAX_NUM_PORTS];
        bool ptp_capable;
+       u8 sgmii_port;
        const struct regmap_access_table *wr_table;
        const struct regmap_access_table *rd_table;
 };
        u32 force:1;
        u32 read:1;                     /* read MIB counters in background */
        u32 freeze:1;                   /* MIB counter freeze is enabled */
+       u32 sgmii_adv_write:1;
 
        struct ksz_port_mib mib;
        phy_interface_t interface;
        void *acl_priv;
        struct ksz_irq pirq;
        u8 num;
+       struct phylink_pcs *pcs;
 #if IS_ENABLED(CONFIG_NET_DSA_MICROCHIP_KSZ_PTP)
        struct kernel_hwtstamp_config tstamp_config;
        bool hwts_tx_en;
        int (*reset)(struct ksz_device *dev);
        int (*init)(struct ksz_device *dev);
        void (*exit)(struct ksz_device *dev);
+
+       int (*pcs_create)(struct ksz_device *dev);
 };
 
 struct ksz_device *ksz_switch_alloc(struct device *base, void *priv);
                dev->chip_id == LAN9372_CHIP_ID) && port == KSZ_PORT_4;
 }
 
+static inline int ksz_get_sgmii_port(struct ksz_device *dev)
+{
+       return dev->info->sgmii_port - 1;
+}
+
+static inline bool ksz_has_sgmii_port(struct ksz_device *dev)
+{
+       return dev->info->sgmii_port > 0;
+}
+
+static inline bool ksz_is_sgmii_port(struct ksz_device *dev, int port)
+{
+       return dev->info->sgmii_port == port + 1;
+}
+
 /* STP State Defines */
 #define PORT_TX_ENABLE                 BIT(2)
 #define PORT_RX_ENABLE                 BIT(1)