From: Alice Guo <[email protected]> Add a driver for the MaxLinear GPY215C Ethernet PHY.
Signed-off-by: Alice Guo <[email protected]> --- CI: https://github.com/u-boot/u-boot/pull/1009/checks --- drivers/net/phy/Kconfig | 6 + drivers/net/phy/Makefile | 1 + drivers/net/phy/mxl_gpy.c | 342 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 349 insertions(+) diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig index 3f7953d693c..f76636a897a 100644 --- a/drivers/net/phy/Kconfig +++ b/drivers/net/phy/Kconfig @@ -242,6 +242,12 @@ config PHY_MSCC depends on !COMPILE_TEST bool "Microsemi Corp Ethernet PHYs support" +config PHY_MXL_GPY + bool "MaxLinear GPY215C PHY support" + help + This adds support for the MaxLinear GPY215C Ethernet PHY, + operating in SGMII with auto-negotiation. + config PHY_NATSEMI bool "National Semiconductor Ethernet PHYs support" diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile index 83520de7f1f..0e79c03df7e 100644 --- a/drivers/net/phy/Makefile +++ b/drivers/net/phy/Makefile @@ -27,6 +27,7 @@ obj-$(CONFIG_PHY_MICREL_KSZ8XXX) += micrel_ksz8xxx.o obj-$(CONFIG_PHY_MICREL_KSZ90X1) += micrel_ksz90x1.o obj-$(CONFIG_PHY_MESON_GXL) += meson-gxl.o obj-$(CONFIG_PHY_MOTORCOMM) += motorcomm.o +obj-$(CONFIG_PHY_MXL_GPY) += mxl_gpy.o obj-$(CONFIG_PHY_NATSEMI) += natsemi.o obj-$(CONFIG_PHY_NXP_C45_TJA11XX) += nxp-c45-tja11xx.o obj-$(CONFIG_PHY_NXP_TJA11XX) += nxp-tja11xx.o diff --git a/drivers/net/phy/mxl_gpy.c b/drivers/net/phy/mxl_gpy.c new file mode 100644 index 00000000000..7445ec5f051 --- /dev/null +++ b/drivers/net/phy/mxl_gpy.c @@ -0,0 +1,342 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * MaxLinear GPY215 2.5G Ethernet PHY driver for U-Boot + * + * Based on Ethernet Network Connection GPY215 (GPY215B1VI, GPY215C0VI) Data + * Sheet Revision 1.4 + * Reference ID 617800 + * + * Copyright 2026 NXP + */ + +#include <phy.h> +#include <linux/bitops.h> +#include <linux/delay.h> + +/* ----------------------------------------------------------------------- + * Standard MDIO registers (Clause 22, Device 0) + * ----------------------------------------------------------------------- + */ +#define GPY215_STD_CTRL 0x00 +#define STD_CTRL_RST BIT(15) /* Self-clearing reset */ +#define STD_CTRL_LB BIT(14) /* Loopback */ +#define STD_CTRL_SSL BIT(13) /* Speed select LSB */ +#define STD_CTRL_ANEN BIT(12) /* Auto-negotiation enable */ +#define STD_CTRL_PD BIT(11) /* Power down (SLEEP) */ +#define STD_CTRL_ANRS BIT(9) /* Restart auto-negotiation */ +#define STD_CTRL_DPLX BIT(8) /* Duplex (1=full) */ +#define STD_CTRL_SSM BIT(6) /* Speed select MSB */ + +#define GPY215_STD_STAT 0x01 +#define STD_STAT_LS BIT(2) /* Link status (latching low) */ +#define STD_STAT_ANEG_DONE BIT(5) /* Auto-neg complete */ + +#define GPY215_STD_PHYID1 0x02 /* OUI bits [3:18] = 0x67C9 */ +#define GPY215_STD_PHYID2 0x03 /* OUI bits [19:24] + model + rev */ + +/* PHY ID: OUI = 0x67C9 (bits 3:18) | 0xDC (bits 19:24) + * PHYID1 = 0x67C9, PHYID2 = 0xDCxx (model/rev in lower 10 bits) + */ +#define GPY215_PHY_ID1 0x67C9 +#define GPY215_PHY_ID2_MASK 0xFC00 /* OUI[19:24] only */ +#define GPY215_PHY_ID2_VAL 0xDC00 + +/* ----------------------------------------------------------------------- + * GPY-specific registers (Clause 22, Device 0) + * ----------------------------------------------------------------------- + */ +#define GPY215_PHY_MIISTAT 0x18 /* Register 0.24 */ +#define MIISTAT_LS BIT(10) /* TPI link status */ +#define MIISTAT_DPX BIT(3) /* Duplex (1=full) */ +#define MIISTAT_SPEED_MASK GENMASK(2, 0) +#define MIISTAT_SPEED_10 0x0 +#define MIISTAT_SPEED_100 0x1 +#define MIISTAT_SPEED_1000 0x2 +#define MIISTAT_SPEED_ANEG 0x3 +#define MIISTAT_SPEED_2500 0x4 + +#define GPY215_PHY_IMASK 0x19 /* Register 0.25 */ +#define IMASK_LSTC BIT(0) /* Link state change */ +#define IMASK_LSPC BIT(1) /* Link speed change */ + +#define GPY215_PHY_ISTAT 0x1A /* Register 0.26 (clr-on-rd) */ +#define ISTAT_LSTC BIT(0) +#define ISTAT_LSPC BIT(1) + +/* ----------------------------------------------------------------------- + * Vendor Specific 1 registers (MMD device 30) + * Accessed via Clause 45 or Clause 22 Extended indirect + * ----------------------------------------------------------------------- + */ +#define GPY215_MMD_VSPEC1 30 /* MMD device address */ + +#define GPY215_VSPEC1_SGMII_CTRL 0x08 /* Register 30.8 */ +#define SGMII_CTRL_RST BIT(15) /* SGMII reset (self-clear) */ +#define SGMII_CTRL_LB BIT(14) /* SGMII loopback */ +#define SGMII_CTRL_ANEN BIT(12) /* SGMII ANEG enable */ +#define SGMII_CTRL_PD BIT(11) /* SGMII power down */ +#define SGMII_CTRL_RXINV BIT(10) /* Invert RX0_M/RX0_P */ +#define SGMII_CTRL_EEE_CAP BIT(7) /* Advertise EEE in ANEG */ +#define SGMII_CTRL_FIXED2G5 BIT(5) /* Force SGMII to 2.5G */ +#define SGMII_CTRL_ANMODE_MASK GENMASK(1, 0) +#define SGMII_CTRL_ANMODE_1000BX 0x1 /* IEEE 1000Bx Clause 37 */ +#define SGMII_CTRL_ANMODE_CIS_PHY 0x2 /* Cisco SGMII, PHY side */ +#define SGMII_CTRL_ANMODE_CIS_MAC 0x3 /* Cisco SGMII, MAC side */ +/* Default reset value = 0x34DA: + * ANEN=1, ANMODE=AN_CIS_PHY(2), FIXED2G5=0 + */ + +#define GPY215_VSPEC1_SGMII_STAT 0x09 /* Register 30.9 (read-only) */ +#define SGMII_STAT_ANOK BIT(5) /* ANEG completed */ +#define SGMII_STAT_RF BIT(4) /* Remote fault (latch high) */ +#define SGMII_STAT_ANAB BIT(3) /* ANEG ability (static) */ +#define SGMII_STAT_LS BIT(2) /* SGMII link up (latch low) */ +#define SGMII_STAT_DR_MASK GENMASK(1, 0) +#define SGMII_STAT_DR_10 0x0 +#define SGMII_STAT_DR_100 0x1 +#define SGMII_STAT_DR_1G 0x2 +#define SGMII_STAT_DR_2G5 0x3 + +/* ----------------------------------------------------------------------- + * Timing / retry constants + * ----------------------------------------------------------------------- + */ +#define GPY215_RESET_TIMEOUT_MS 500 +#define GPY215_ANEG_TIMEOUT_MS 5000 +#define GPY215_POLL_INTERVAL_MS 10 + +/** + * gpy215_probe - Verify PHY identity + */ +static int gpy215_probe(struct phy_device *phydev) +{ + int id1, id2; + + id1 = phy_read(phydev, MDIO_DEVAD_NONE, GPY215_STD_PHYID1); + id2 = phy_read(phydev, MDIO_DEVAD_NONE, GPY215_STD_PHYID2); + + if (id1 < 0 || id2 < 0) { + printf("GPY215: MDIO read failed (id1=%d id2=%d)\n", id1, id2); + return -EIO; + } + + if (id1 != GPY215_PHY_ID1 || + (id2 & GPY215_PHY_ID2_MASK) != GPY215_PHY_ID2_VAL) { + printf("GPY215: unexpected PHY ID 0x%04X 0x%04X\n", id1, id2); + return -ENODEV; + } + + debug("GPY215: detected (ID1=0x%04X ID2=0x%04X)\n", id1, id2); + return 0; +} + +/** + * gpy215_config - Configure PHY for SGMII operation + * + * The GPY215 SGMII interface comes up automatically after reset with: + * - SGMII ANEG enabled (Cisco SGMII, PHY side) + * - PHY side SGMII speed tracks TPI link speed + */ +static int gpy215_config(struct phy_device *phydev) +{ + int val, timeout; + + val = phy_read(phydev, MDIO_DEVAD_NONE, GPY215_STD_CTRL); + if (val < 0) + return val; + + phy_write(phydev, MDIO_DEVAD_NONE, GPY215_STD_CTRL, + val | STD_CTRL_RST); + + /* Wait for self-clearing RST bit */ + timeout = GPY215_RESET_TIMEOUT_MS / GPY215_POLL_INTERVAL_MS; + do { + mdelay(GPY215_POLL_INTERVAL_MS); + val = phy_read(phydev, MDIO_DEVAD_NONE, GPY215_STD_CTRL); + if (val < 0) + return val; + } while ((val & STD_CTRL_RST) && --timeout); + + if (val & STD_CTRL_RST) { + printf("GPY215: reset timeout\n"); + return -ETIMEDOUT; + } + + /* Clear stale interrupt status (read-to-clear) */ + phy_read(phydev, MDIO_DEVAD_NONE, GPY215_PHY_ISTAT); + + val = phy_read(phydev, MDIO_DEVAD_NONE, GPY215_STD_CTRL); + if (val < 0) + return val; + + /* Enable ANEG, clear forced speed/duplex bits */ + val |= STD_CTRL_ANEN; + val &= ~(STD_CTRL_SSL | STD_CTRL_SSM | STD_CTRL_PD); + phy_write(phydev, MDIO_DEVAD_NONE, GPY215_STD_CTRL, val); + + phy_write(phydev, MDIO_DEVAD_NONE, GPY215_STD_CTRL, + val | STD_CTRL_ANRS); + + /* Configure SGMII (Cisco ANEG, PHY side, ANEG enabled) */ + val = phy_read_mmd(phydev, GPY215_MMD_VSPEC1, + GPY215_VSPEC1_SGMII_CTRL); + if (val < 0) + return val; + + val &= ~SGMII_CTRL_PD; + val |= SGMII_CTRL_ANEN; + val &= ~SGMII_CTRL_ANMODE_MASK; + val |= SGMII_CTRL_ANMODE_CIS_PHY; + val &= ~SGMII_CTRL_FIXED2G5; + phy_write_mmd(phydev, GPY215_MMD_VSPEC1, + GPY215_VSPEC1_SGMII_CTRL, val); + + debug("GPY215: configured for SGMII (Cisco ANEG, PHY side)\n"); + return 0; +} + +/** + * gpy215_startup - Wait for link and report status + * + * Polls PHY_MIISTAT (TPI link) and VSPEC1_SGMII_STAT (SGMII link). + * Updates phydev->speed, phydev->duplex, phydev->link. + */ +static int gpy215_startup(struct phy_device *phydev) +{ + int miistat, sgmii_stat, val; + int timeout = GPY215_ANEG_TIMEOUT_MS / GPY215_POLL_INTERVAL_MS; + int speed_code; + + debug("GPY215: waiting for link"); + + /* Poll TPI link status */ + do { + mdelay(GPY215_POLL_INTERVAL_MS); + miistat = phy_read(phydev, MDIO_DEVAD_NONE, + GPY215_PHY_MIISTAT); + if (miistat < 0) + return miistat; + } while (!(miistat & MIISTAT_LS) && --timeout); + + if (!(miistat & MIISTAT_LS)) { + printf("GPY215: link down (TPI timeout)\n"); + phydev->link = 0; + return 0; + } + + do { + mdelay(GPY215_POLL_INTERVAL_MS); + val = phy_read(phydev, MDIO_DEVAD_NONE, GPY215_STD_STAT); + if (val < 0) + return val; + } while (!(val & STD_STAT_ANEG_DONE) && --timeout); + + if (!(val & STD_STAT_ANEG_DONE)) { + printf("GPY215: auto-negotiation not complete\n"); + phydev->link = 0; + return 0; + } + + do { + mdelay(GPY215_POLL_INTERVAL_MS); + phy_read_mmd(phydev, GPY215_MMD_VSPEC1, + GPY215_VSPEC1_SGMII_STAT); + sgmii_stat = phy_read_mmd(phydev, GPY215_MMD_VSPEC1, + GPY215_VSPEC1_SGMII_STAT); + if (sgmii_stat < 0) + return sgmii_stat; + } while (((sgmii_stat & (SGMII_STAT_LS | SGMII_STAT_ANOK)) != + (SGMII_STAT_LS | SGMII_STAT_ANOK)) && --timeout); + + if ((sgmii_stat & (SGMII_STAT_LS | SGMII_STAT_ANOK)) != + (SGMII_STAT_LS | SGMII_STAT_ANOK)) { + printf("GPY215: SGMII not ready (stat=0x%04X link=%d ANOK=%d RF=%d)\n", + sgmii_stat, + !!(sgmii_stat & SGMII_STAT_LS), + !!(sgmii_stat & SGMII_STAT_ANOK), + !!(sgmii_stat & SGMII_STAT_RF)); + phydev->link = 0; + return 0; + } + + /* Clear interrupt status */ + phy_read(phydev, MDIO_DEVAD_NONE, GPY215_PHY_ISTAT); + + miistat = phy_read(phydev, MDIO_DEVAD_NONE, GPY215_PHY_MIISTAT); + if (miistat < 0) + return miistat; + + speed_code = miistat & MIISTAT_SPEED_MASK; + switch (speed_code) { + case MIISTAT_SPEED_10: + phydev->speed = SPEED_10; + break; + case MIISTAT_SPEED_100: + phydev->speed = SPEED_100; + break; + case MIISTAT_SPEED_1000: + phydev->speed = SPEED_1000; + break; + case MIISTAT_SPEED_2500: + phydev->speed = SPEED_2500; + break; + default: + printf("GPY215: speed not resolved (miistat=0x%04X)\n", + miistat); + phydev->link = 0; + return 0; + } + + phydev->duplex = (miistat & MIISTAT_DPX) ? DUPLEX_FULL : DUPLEX_HALF; + phydev->link = 1; + + debug("GPY215: TPI link up %d Mbit/s %s-duplex\n", + phydev->speed, + phydev->duplex == DUPLEX_FULL ? "full" : "half"); + + debug("GPY215: SGMII status 0x%04X (link=%d ANOK=%d DR=%ld)\n", + sgmii_stat, + !!(sgmii_stat & SGMII_STAT_LS), + !!(sgmii_stat & SGMII_STAT_ANOK), + sgmii_stat & SGMII_STAT_DR_MASK); + + return 0; +} + +/** + * gpy215_shutdown - Power down SGMII and TPI + */ +static int gpy215_shutdown(struct phy_device *phydev) +{ + int val; + + val = phy_read_mmd(phydev, GPY215_MMD_VSPEC1, + GPY215_VSPEC1_SGMII_CTRL); + if (val >= 0) + phy_write_mmd(phydev, GPY215_MMD_VSPEC1, + GPY215_VSPEC1_SGMII_CTRL, + val | SGMII_CTRL_PD); + + val = phy_read(phydev, MDIO_DEVAD_NONE, GPY215_STD_CTRL); + if (val >= 0) + phy_write(phydev, MDIO_DEVAD_NONE, GPY215_STD_CTRL, + val | STD_CTRL_PD); + + return 0; +} + +U_BOOT_PHY_DRIVER(mxl_gpy215c) = { + .name = "MaxLinear GPY215C", + /* + * Match on OUI only. The 22-bit OUI spans PHYID1[15:0] and PHYID2[15:10]. + * Combined 22-bit PHY ID = (0x67C9 << 6) | (0xDC00 >> 10) = 0x19F277 + * U-Boot phy_id = (PHYID1 << 16) | PHYID2 with mask 0xFFFFFC00 + */ + .uid = (GPY215_PHY_ID1 << 16) | GPY215_PHY_ID2_VAL, + .mask = 0xFFFFFC00, + .features = PHY_GBIT_FEATURES, + .probe = &gpy215_probe, + .config = &gpy215_config, + .startup = &gpy215_startup, + .shutdown = &gpy215_shutdown, +}; --- base-commit: a18265f1ccb7a272721ed4286ed3b5a6182ff424 change-id: 20260710-phy-a8d7427ab27d Best regards, -- Alice Guo <[email protected]>

