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
