This works in two possible ways: through an external PHY or through
the internal PCS. The existing autonegotiation code is handled through
the MAC driver, even in the external PHY case, and assumes a copper
PHY.

Instead, add two pieces of logic:
- If there is an external PHY, use the interface mode of the PHY,
  as distinct from the interface mode of the MAC, to determine
  how to adjust the 802.3 clause-22 autonegotiation registers.
- If there is no external PHY, and the device tree signals to use
  inband autonegotiation, use the interface mode of the MAC to
  decide this.

In addition, add support for use of the internal PCS without an
external PHY with autonegotiation. The internal PCS provides the
standard set of 802.3 clause-22 PHY registers, which work in
standard ways, so provide access to those registers as a fake MDIO
device. This unifies the external-PHY and internal-PCS cases, and
provides autonegotiation with the internal PCS for both 1000BASE-X
and SGMII devices.

Tested with a variety of SFPs (copper, fiber, GPON) on both the
internal PCS and an external GMII-attached PHY using Microchip
PolarFire SoC hardware.

Signed-off-by: Nathan Whitehorn <[email protected]>
---
 drivers/net/macb.c | 60 +++++++++++++++++++++++++++++++++++++++++-----
 drivers/net/macb.h |  1 +
 2 files changed, 55 insertions(+), 6 deletions(-)

diff --git a/drivers/net/macb.c b/drivers/net/macb.c
index bea1dfed892..06e08d1d87e 100644
--- a/drivers/net/macb.c
+++ b/drivers/net/macb.c
@@ -143,6 +143,7 @@ struct macb_device {
        unsigned long           pclk_rate;
 #endif
        phy_interface_t         phy_interface;
+       phy_interface_t         phy_sub_interface;
 };
 
 struct macb_usrio_cfg {
@@ -186,7 +187,7 @@ static int gem_is_gigabit_capable(struct macb_device *macb)
 /* Is the port a fixed link */
 static int macb_port_is_fixed_link(struct macb_device *macb)
 {
-       return macb->phy_addr > PHY_MAX_ADDR;
+       return macb->phy_addr == PHY_MAX_ADDR + 1;
 }
 
 static void macb_mdio_write(struct macb_device *macb, u8 phy_adr, u8 reg,
@@ -196,6 +197,14 @@ static void macb_mdio_write(struct macb_device *macb, u8 
phy_adr, u8 reg,
        unsigned long netstat;
        unsigned long frame;
 
+       if (phy_adr == PHY_MAX_ADDR + 2) {
+               /* Internal PCS */
+               if (reg > 0x0f)
+                       return;
+               gem_writel(macb, PCS_CONTROL + reg*4, value);
+               return;
+       }
+
        netctl = macb_readl(macb, NCR);
        netctl |= MACB_BIT(MPE);
        macb_writel(macb, NCR, netctl);
@@ -223,6 +232,14 @@ static u16 macb_mdio_read(struct macb_device *macb, u8 
phy_adr, u8 reg)
        unsigned long netstat;
        unsigned long frame;
 
+       if (phy_adr == PHY_MAX_ADDR + 2) {
+               /* Internal PCS: implements the usual MII registers,
+                * so just treat it like a PHY at a weird address. */
+               if (reg > 0x0f)
+                       return (0);
+               return gem_readl(macb, PCS_CONTROL + reg*4);
+       }
+
        netctl = macb_readl(macb, NCR);
        netctl |= MACB_BIT(MPE);
        macb_writel(macb, NCR, netctl);
@@ -532,7 +549,11 @@ static void macb_phy_reset(struct macb_device *macb, const 
char *name)
        int i;
        u16 status, adv;
 
-       adv = ADVERTISE_CSMA | ADVERTISE_ALL;
+       if (macb->phy_interface == PHY_INTERFACE_MODE_1000BASEX ||
+           macb->phy_sub_interface == PHY_INTERFACE_MODE_1000BASEX)
+               adv = ADVERTISE_1000XFULL;
+       else
+               adv = ADVERTISE_CSMA | ADVERTISE_ALL;
        macb_mdio_write(macb, macb->phy_addr, MII_ADVERTISE, adv);
        printf("%s: Starting autonegotiation...\n", name);
        macb_mdio_write(macb, macb->phy_addr, MII_BMCR, (BMCR_ANENABLE
@@ -692,8 +713,18 @@ static int macb_phy_init(struct udevice *dev, const char 
*name)
                }
 
 #ifdef CONFIG_PHYLIB
-               macb->phydev = phy_connect(macb->bus, macb->phy_addr, dev,
-                                    macb->phy_interface);
+               if (macb->phy_addr == PHY_MAX_ADDR+2) {
+                       /* Internal PCS fake PHY */
+                       int id;
+                       get_phy_id(macb->bus, macb->phy_addr, MDIO_DEVAD_NONE, 
&id);
+                       macb->phydev = phy_device_create(macb->bus, 
macb->phy_addr, id, false);
+                       macb->phydev->node = dev_ofnode(dev);
+               } else {
+                       /* Real PHY */
+                       macb->phydev = phy_connect(macb->bus, macb->phy_addr, 
dev,
+                                            macb->phy_interface);
+               }
+
                if (!macb->phydev) {
                        printf("phy_connect failed\n");
                        return -ENODEV;
@@ -727,9 +758,13 @@ static int macb_phy_init(struct udevice *dev, const char 
*name)
                        return -ENETDOWN;
                }
 
+               adv = macb_mdio_read(macb, macb->phy_addr, MII_ADVERTISE);
+               lpa = macb_mdio_read(macb, macb->phy_addr, MII_LPA);
+
                /* First check for GMAC and that it is GiB capable */
                if (gem_is_gigabit_capable(macb)) {
-                       lpa = macb_mdio_read(macb, macb->phy_addr, 
MII_STAT1000);
+                       if (lpa & ADVERTISE_CSMA)
+                               lpa = macb_mdio_read(macb, macb->phy_addr, 
MII_STAT1000);
 
                        if (lpa & (LPA_1000FULL | LPA_1000HALF | LPA_1000XFULL |
                                                LPA_1000XHALF)) {
@@ -988,6 +1023,12 @@ static int _macb_init(struct udevice *dev, const char 
*name)
 
                        ncfgr |= GEM_BIT(SGMIIEN) | GEM_BIT(PCSSEL);
                        macb_writel(macb, NCFGR, ncfgr);
+               } else if (macb->phy_interface == PHY_INTERFACE_MODE_1000BASEX) 
{
+                       unsigned int ncfgr = macb_readl(macb, NCFGR);
+
+                       ncfgr |= GEM_BIT(PCSSEL);
+                       ncfgr &= ~GEM_BIT(SGMIIEN);
+                       macb_writel(macb, NCFGR, ncfgr);
                }
        } else {
        /* choose RMII or MII mode. This depends on the board */
@@ -1269,10 +1310,17 @@ static int macb_eth_probe(struct udevice *dev)
                return -EINVAL;
 
        /* Read phyaddr from DT */
+       macb->phy_sub_interface = macb->phy_interface;
        if (!dev_read_phandle_with_args(dev, "phy-handle", NULL, 0, 0,
-                                       &phandle_args))
+                                       &phandle_args)) {
                macb->phy_addr = ofnode_read_u32_default(phandle_args.node,
                                                         "reg", -1);
+               macb->phy_sub_interface = 
ofnode_read_phy_mode(phandle_args.node);
+       } else if (dev_read_string(dev, "managed") != NULL &&
+                       strcmp(dev_read_string(dev, "managed"), 
"in-band-status") == 0) {
+               /* Use internal PCS */
+               macb->phy_addr = PHY_MAX_ADDR + 2;
+       }
 
        macb->regs = (void *)(uintptr_t)pdata->iobase;
 
diff --git a/drivers/net/macb.h b/drivers/net/macb.h
index 002d5bd31b2..69cede27848 100644
--- a/drivers/net/macb.h
+++ b/drivers/net/macb.h
@@ -144,6 +144,7 @@
 #define GEM_PEFTN              0x01f4 /* PTP Peer Event Frame Tx Ns */
 #define GEM_PEFRSL             0x01f8 /* PTP Peer Event Frame Rx Sec Low */
 #define GEM_PEFRN              0x01fc /* PTP Peer Event Frame Rx Ns */
+#define GEM_PCS_CONTROL                0x0200 /* PCS Control */
 #define GEM_DCFG1              0x0280 /* Design Config 1 */
 #define GEM_DCFG2              0x0284 /* Design Config 2 */
 #define GEM_DCFG3              0x0288 /* Design Config 3 */
-- 
2.34.1

Reply via email to