From: alice.guo@oss.nxp.com
To: u-boot@lists.denx.de, u-boot@lists.u-boot-project.org
Cc: Fabio Estevam <festevam@gmail.com>, Tom Rini <trini@konsulko.com>,
Jerome Forissier <jerome.forissier@arm.com>,
Quentin Schulz <quentin.schulz@cherry.de>,
Peter Robinson <pbrobinson@gmail.com>,
"Lucien.Jheng" <lucienzx159@gmail.com>,
Weijie Gao <weijie.gao@mediatek.com>,
Beiyan Yun <root@infi.wang>, Johan Jonker <jbx6244@gmail.com>,
Siddharth Vadapalli <s-vadapalli@ti.com>,
Sky Huang <SkyLake.Huang@mediatek.com>,
Marek Vasut <marek.vasut+renesas@mailbox.org>,
Alice Guo <alice.guo@nxp.com>
Subject: [PATCH v1] net: phy: Add MaxLinear GPY215C Ethernet PHY driver
Date: Fri, 10 Jul 2026 16:12:34 +0800 [thread overview]
Message-ID: <20260710-phy-v1-1-87f37559b2ed@nxp.com> (raw)
From: Alice Guo <alice.guo@nxp.com>
Add a driver for the MaxLinear GPY215C Ethernet PHY.
Signed-off-by: Alice Guo <alice.guo@nxp.com>
---
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 <alice.guo@nxp.com>
next reply other threads:[~2026-07-10 8:09 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-10 8:12 alice.guo [this message]
2026-07-27 13:25 ` [PATCH v1] net: phy: Add MaxLinear GPY215C Ethernet PHY driver Jerome Forissier
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260710-phy-v1-1-87f37559b2ed@nxp.com \
--to=alice.guo@oss.nxp.com \
--cc=SkyLake.Huang@mediatek.com \
--cc=alice.guo@nxp.com \
--cc=festevam@gmail.com \
--cc=jbx6244@gmail.com \
--cc=jerome.forissier@arm.com \
--cc=lucienzx159@gmail.com \
--cc=marek.vasut+renesas@mailbox.org \
--cc=pbrobinson@gmail.com \
--cc=quentin.schulz@cherry.de \
--cc=root@infi.wang \
--cc=s-vadapalli@ti.com \
--cc=trini@konsulko.com \
--cc=u-boot@lists.denx.de \
--cc=u-boot@lists.u-boot-project.org \
--cc=weijie.gao@mediatek.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox