// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause)
 /* Copyright 2017-2019 NXP */
 
+#include <linux/mdio.h>
 #include <linux/module.h>
 #include <linux/fsl/enetc_mdio.h>
 #include <linux/of_mdio.h>
                of_node_put(priv->phy_node);
 }
 
+static int enetc_imdio_init(struct enetc_pf *pf, bool is_c45)
+{
+       struct device *dev = &pf->si->pdev->dev;
+       struct enetc_mdio_priv *mdio_priv;
+       struct phy_device *pcs;
+       struct mii_bus *bus;
+       int err;
+
+       bus = mdiobus_alloc_size(sizeof(*mdio_priv));
+       if (!bus)
+               return -ENOMEM;
+
+       bus->name = "Freescale ENETC internal MDIO Bus";
+       bus->read = enetc_mdio_read;
+       bus->write = enetc_mdio_write;
+       bus->parent = dev;
+       bus->phy_mask = ~0;
+       mdio_priv = bus->priv;
+       mdio_priv->hw = &pf->si->hw;
+       mdio_priv->mdio_base = ENETC_PM_IMDIO_BASE;
+       snprintf(bus->id, MII_BUS_ID_SIZE, "%s-imdio", dev_name(dev));
+
+       err = mdiobus_register(bus);
+       if (err) {
+               dev_err(dev, "cannot register internal MDIO bus (%d)\n", err);
+               goto free_mdio_bus;
+       }
+
+       pcs = get_phy_device(bus, 0, is_c45);
+       if (IS_ERR(pcs)) {
+               err = PTR_ERR(pcs);
+               dev_err(dev, "cannot get internal PCS PHY (%d)\n", err);
+               goto unregister_mdiobus;
+       }
+
+       pf->imdio = bus;
+       pf->pcs = pcs;
+
+       return 0;
+
+unregister_mdiobus:
+       mdiobus_unregister(bus);
+free_mdio_bus:
+       mdiobus_free(bus);
+       return err;
+}
+
+static void enetc_imdio_remove(struct enetc_pf *pf)
+{
+       if (pf->pcs)
+               put_device(&pf->pcs->mdio.dev);
+       if (pf->imdio) {
+               mdiobus_unregister(pf->imdio);
+               mdiobus_free(pf->imdio);
+       }
+}
+
+static void enetc_configure_sgmii(struct phy_device *pcs)
+{
+       /* SGMII spec requires tx_config_Reg[15:0] to be exactly 0x4001
+        * for the MAC PCS in order to acknowledge the AN.
+        */
+       phy_write(pcs, MII_ADVERTISE, ADVERTISE_SGMII | ADVERTISE_LPACK);
+
+       phy_write(pcs, ENETC_PCS_IF_MODE,
+                 ENETC_PCS_IF_MODE_SGMII_EN |
+                 ENETC_PCS_IF_MODE_USE_SGMII_AN);
+
+       /* Adjust link timer for SGMII */
+       phy_write(pcs, ENETC_PCS_LINK_TIMER1, ENETC_PCS_LINK_TIMER1_VAL);
+       phy_write(pcs, ENETC_PCS_LINK_TIMER2, ENETC_PCS_LINK_TIMER2_VAL);
+
+       phy_write(pcs, MII_BMCR, BMCR_ANRESTART | BMCR_ANENABLE);
+}
+
+static void enetc_configure_2500basex(struct phy_device *pcs)
+{
+       phy_write(pcs, ENETC_PCS_IF_MODE,
+                 ENETC_PCS_IF_MODE_SGMII_EN |
+                 ENETC_PCS_IF_MODE_SGMII_SPEED(ENETC_PCS_SPEED_2500));
+
+       phy_write(pcs, MII_BMCR, BMCR_SPEED1000 | BMCR_FULLDPLX | BMCR_RESET);
+}
+
+static void enetc_configure_usxgmii(struct phy_device *pcs)
+{
+       /* Configure device ability for the USXGMII Replicator */
+       phy_write_mmd(pcs, MDIO_MMD_VEND2, MII_ADVERTISE,
+                     ADVERTISE_SGMII | ADVERTISE_LPACK |
+                     MDIO_USXGMII_FULL_DUPLEX);
+
+       /* Restart PCS AN */
+       phy_write_mmd(pcs, MDIO_MMD_VEND2, MII_BMCR,
+                     BMCR_RESET | BMCR_ANENABLE | BMCR_ANRESTART);
+}
+
+static int enetc_configure_serdes(struct enetc_ndev_priv *priv)
+{
+       bool is_c45 = priv->if_mode == PHY_INTERFACE_MODE_USXGMII;
+       struct enetc_pf *pf = enetc_si_priv(priv->si);
+       int err;
+
+       if (priv->if_mode != PHY_INTERFACE_MODE_SGMII &&
+           priv->if_mode != PHY_INTERFACE_MODE_2500BASEX &&
+           priv->if_mode != PHY_INTERFACE_MODE_USXGMII)
+               return 0;
+
+       err = enetc_imdio_init(pf, is_c45);
+       if (err)
+               return err;
+
+       switch (priv->if_mode) {
+       case PHY_INTERFACE_MODE_SGMII:
+               enetc_configure_sgmii(pf->pcs);
+               break;
+       case PHY_INTERFACE_MODE_2500BASEX:
+               enetc_configure_2500basex(pf->pcs);
+               break;
+       case PHY_INTERFACE_MODE_USXGMII:
+               enetc_configure_usxgmii(pf->pcs);
+               break;
+       default:
+               dev_err(&pf->si->pdev->dev, "Unsupported link mode %s\n",
+                       phy_modes(priv->if_mode));
+       }
+
+       return 0;
+}
+
 static int enetc_pf_probe(struct pci_dev *pdev,
                          const struct pci_device_id *ent)
 {
        if (err)
                dev_warn(&pdev->dev, "Fallback to PHY-less operation\n");
 
+       err = enetc_configure_serdes(priv);
+       if (err)
+               dev_warn(&pdev->dev, "Attempted SerDes config but failed\n");
+
        err = register_netdev(ndev);
        if (err)
                goto err_reg_netdev;
        priv = netdev_priv(si->ndev);
        unregister_netdev(si->ndev);
 
+       enetc_imdio_remove(pf);
        enetc_mdio_remove(pf);
        enetc_of_put_phy(priv);