[PATCH 2/3] net/axgbe: Add support for Marvell M88E1512 PHY

Ashok Kumar Natarajan
2026-02-16T12:52:51Z

Add support for the Marvell M88E1512 PHY in the axgbe driver. This patch
implements device identification and basic initialization required to
operate with this PHY. Only the M88E1512 model is supported in this
update.

Signed-off-by: Ashok Kumar Natarajan <ashokkumar.natarajan@amd.com>
---
drivers/net/axgbe/axgbe_ethdev.h | 1 +
drivers/net/axgbe/axgbe_phy.h | 32 ++++
drivers/net/axgbe/axgbe_phy_impl.c | 261 ++++++++++++++++++++++++++++-
3 files changed, 290 insertions(+), 4 deletions(-)

diff --git a/drivers/net/axgbe/axgbe_ethdev.h b/drivers/net/axgbe/axgbe_ethdev.h
index 12d77cd520..3a9dc81691 100644
--- a/drivers/net/axgbe/axgbe_ethdev.h
+++ b/drivers/net/axgbe/axgbe_ethdev.h
@@ -276,6 +276,7 @@ struct axgbe_phy {
int pause_autoneg;
int tx_pause;
int rx_pause;
+ int id;
};

enum axgbe_i2c_cmd {
diff --git a/drivers/net/axgbe/axgbe_phy.h b/drivers/net/axgbe/axgbe_phy.h
index eee3afc370..ef02488adf 100644
--- a/drivers/net/axgbe/axgbe_phy.h
+++ b/drivers/net/axgbe/axgbe_phy.h
@@ -63,6 +63,29 @@
#define BMCR_RESET 0x8000 /* Reset to default state */
#define BMCR_SPEED10 0x0000 /* Select 10Mbps */

+/* Advertisement control register. */
+#define ADVERTISE_SLCT 0x001f /* Selector bits */
+#define ADVERTISE_CSMA 0x0001 /* Only selector supported */
+#define ADVERTISE_10HALF 0x0020 /* Try for 10mbps half-duplex */
+#define ADVERTISE_1000XFULL 0x0020 /* Try for 1000BASE-X full-duplex */
+#define ADVERTISE_10FULL 0x0040 /* Try for 10mbps full-duplex */
+#define ADVERTISE_1000XHALF 0x0040 /* Try for 1000BASE-X half-duplex */
+#define ADVERTISE_100HALF 0x0080 /* Try for 100mbps half-duplex */
+#define ADVERTISE_1000XPAUSE 0x0080 /* Try for 1000BASE-X pause */
+#define ADVERTISE_100FULL 0x0100 /* Try for 100mbps full-duplex */
+#define ADVERTISE_1000XPSE_ASYM 0x0100 /* Try for 1000BASE-X asym pause */
+#define ADVERTISE_100BASE4 0x0200 /* Try for 100mbps 4k packets */
+#define ADVERTISE_PAUSE_CAP 0x0400 /* Try for pause */
+#define ADVERTISE_PAUSE_ASYM 0x0800 /* Try for asymmetric pause */
+#define ADVERTISE_RESV 0x1000 /* Unused... */
+#define ADVERTISE_RFAULT 0x2000 /* Say we can detect faults */
+#define ADVERTISE_LPACK 0x4000 /* Ack link partners response */
+#define ADVERTISE_NPAGE 0x8000 /* Next page bit */
+
+#define ADVERTISE_FULL (ADVERTISE_100FULL | ADVERTISE_10FULL | \
+ ADVERTISE_CSMA)
+#define ADVERTISE_ALL (ADVERTISE_10HALF | ADVERTISE_10FULL | \
+ ADVERTISE_100HALF | ADVERTISE_100FULL)

/* MDIO Manageable Devices (MMDs). */
#define MDIO_MMD_PMAPMD 1 /* Physical Medium Attachment
@@ -114,6 +137,15 @@
#define MDIO_AN_10GBT_CTRL 32 /* 10GBASE-T auto-negotiation control */
#define MDIO_AN_10GBT_STAT 33 /* 10GBASE-T auto-negotiation status */

+#define AXGBE_M88E1512_PAGE_ADDR 0x0016
+#define AXGBE_M88E1512_CFG_REG_1 0x0010
+#define AXGBE_M88E1512_CFG_REG_2 0x0011
+#define AXGBE_M88E1512_CFG_REG_3 0x0007
+#define AXGBE_M88E1512_MODE 0x0014
+
+#define AXGBE_M88E1512_SCR 0x10
+
+
/* Control register 1. */
/* Enable extended speed selection */
#define MDIO_CTRL1_SPEEDSELEXT (BMCR_SPEED1000 | BMCR_SPEED100)
diff --git a/drivers/net/axgbe/axgbe_phy_impl.c b/drivers/net/axgbe/axgbe_phy_impl.c
index 4f3cc63836..20cfdcc796 100644
--- a/drivers/net/axgbe/axgbe_phy_impl.c
+++ b/drivers/net/axgbe/axgbe_phy_impl.c
@@ -37,6 +37,8 @@
#define AXGBE_CDR_DELAY_INC 10000
#define AXGBE_CDR_DELAY_MAX 100000

+#define M88E1512_E_PHY_ID 0x01410DD0
+
enum axgbe_port_mode {
AXGBE_PORT_MODE_RSVD = 0,
AXGBE_PORT_MODE_BACKPLANE,
@@ -250,9 +252,11 @@ static enum axgbe_an_mode axgbe_phy_an_mode(struct axgbe_port *pdata);
static void axgbe_phy_perform_ratechange(struct axgbe_port *pdata,
enum axgbe_mb_cmd cmd, enum axgbe_mb_subcmd sub_cmd);
static void axgbe_phy_rrc(struct axgbe_port *pdata);
+static int axgbe_get_ext_phy_link_status(struct axgbe_port *pdata);

static int axgbe_phy_get_comm_ownership(struct axgbe_port *pdata);
static void axgbe_phy_put_comm_ownership(struct axgbe_port *pdata);
+static int axgbe_m88e1512_config_aneg(struct axgbe_port *pdata);

static int axgbe_phy_i2c_xfer(struct axgbe_port *pdata,
struct axgbe_i2c_op *i2c_op)
@@ -1116,12 +1120,19 @@ static unsigned int axgbe_phy_an_advertising(struct axgbe_port *pdata)
return advertising;
}

-static int axgbe_phy_an_config(struct axgbe_port *pdata __rte_unused)
+static int axgbe_phy_an_config(struct axgbe_port *pdata)
{
+ struct axgbe_phy_data *phy_data = pdata->phy_data;
+
+ /* Auto-negotiation config is implemented only for 1000BASE-T */
+ if (phy_data->port_mode != AXGBE_PORT_MODE_1000BASE_T)
+ return 0;
+
+ /* Supports only Marvell M88E1512 */
+ if (pdata->phy.id == M88E1512_E_PHY_ID)
+ axgbe_m88e1512_config_aneg(pdata);
+
return 0;
- /* Dummy API since there is no case to support
- * external phy devices registered through kernel APIs
- */
}

static enum axgbe_an_mode axgbe_phy_an_sfp_mode(struct axgbe_phy_data *phy_data)
@@ -1889,6 +1900,17 @@ static int axgbe_phy_link_status(struct axgbe_port *pdata, int *an_restart)
}
}

+ if (phy_data->port_mode == AXGBE_PORT_MODE_1000BASE_T) {
+ if (pdata->phy.id == M88E1512_E_PHY_ID) {
+ if (axgbe_get_ext_phy_link_status(pdata) == 1) {
+ PMD_DRV_LOG_LINE(DEBUG, "M88E1512 PHY link is up");
+ } else {
+ PMD_DRV_LOG_LINE(DEBUG, "M88E1512 PHY link is not up");
+ goto out;
+ }
+ }
+ }
+
/* Link status is latched low, so read once to clear
* and then read again to get current state
*/
@@ -1932,6 +1954,8 @@ static int axgbe_phy_link_status(struct axgbe_port *pdata, int *an_restart)
axgbe_phy_rrc(pdata);
}

+out:
+ pdata->rx_adapt_done = false;
return 0;
}

@@ -2289,6 +2313,225 @@ static int axgbe_phy_reset(struct axgbe_port *pdata)
return 0;
}

+static int axgbe_get_phy_id(struct axgbe_port *pdata)
+{
+ struct axgbe_phy_data *phy_data = pdata->phy_data;
+ unsigned int mdio_addr = phy_data->mdio_addr;
+ int phy_id, ret_val;
+ ret_val = pdata->phy_if.phy_impl.read(pdata, mdio_addr, MII_PHYSID1);
+
+ phy_id = ret_val << 16;
+
+ ret_val = pdata->phy_if.phy_impl.read(pdata, mdio_addr, MII_PHYSID2);
+ phy_id |= ret_val & 0xfff0;
+
+ return phy_id;
+}
+
+static int axgbe_phy_soft_reset(struct axgbe_port *pdata)
+{
+ struct axgbe_phy_data *phy_data = pdata->phy_data;
+ unsigned int mdio_addr = phy_data->mdio_addr;
+ int ret, bmcr;
+
+ bmcr = pdata->phy_if.phy_impl.read(pdata, mdio_addr, MII_BMCR);
+ if (bmcr < 0)
+ return bmcr;
+
+ bmcr |= BMCR_RESET;
+ ret = pdata->phy_if.phy_impl.write(pdata, mdio_addr, MII_BMCR, bmcr);
+ if (ret)
+ return ret;
+
+ rte_delay_us_sleep(1);
+ return 0;
+}
+
+static int axgbe_m88e1512_config_aneg(struct axgbe_port *pdata)
+{
+ struct axgbe_phy_data *phy_data = pdata->phy_data;
+ unsigned int mdio_addr = phy_data->mdio_addr;
+ int an_advert, bmcr;
+ int ret;
+
+ an_advert = ADVERTISE_10FULL |
+ ADVERTISE_100FULL |
+ ADVERTISE_PAUSE_CAP |
+ ADVERTISE_PAUSE_ASYM;
+
+ ret = pdata->phy_if.phy_impl.write(pdata, mdio_addr,
+ MII_ADVERTISE, an_advert);
+ if (ret)
+ return ret;
+
+ bmcr = pdata->phy_if.phy_impl.read(pdata, mdio_addr, MII_BMCR);
+ if (bmcr < 0)
+ return bmcr;
+
+ bmcr &= ~(MDIO_CTRL1_SPEEDSELEXT);
+ switch (phy_data->cur_mode) {
+ case AXGBE_MODE_SGMII_1000:
+ bmcr |= BMCR_SPEED1000;
+ break;
+ case AXGBE_MODE_SGMII_100:
+ bmcr |= BMCR_SPEED100;
+ break;
+ default:
+ break;
+ }
+ if (pdata->phy.autoneg == AUTONEG_ENABLE)
+ bmcr |= BMCR_ANENABLE | BMCR_ANRESTART;
+
+ ret = pdata->phy_if.phy_impl.write(pdata, mdio_addr,
+ MII_BMCR, bmcr);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int axgbe_m88e5112_set_page(struct axgbe_port *pdata, int page)
+{
+ struct axgbe_phy_data *phy_data = pdata->phy_data;
+ unsigned int mdio_addr = phy_data->mdio_addr;
+ return pdata->phy_if.phy_impl.write(pdata, mdio_addr,
+ AXGBE_M88E1512_PAGE_ADDR, page);
+}
+
+static const struct {
+ u16 reg17, reg16;
+} errata_vals[] = {
+ { 0x214b, 0x2144 },
+ { 0x0c28, 0x2146 },
+ { 0xb233, 0x214d },
+ { 0xcc0c, 0x2159 },
+};
+
+static int axgbe_m88e1512_init(struct axgbe_port *pdata)
+{
+ struct axgbe_phy_data *phy_data = pdata->phy_data;
+ unsigned int mdio_addr = phy_data->mdio_addr;
+ int ret;
+ unsigned int i;
+
+ PMD_DRV_LOG_LINE(DEBUG, "Initialize M88E1512 phy");
+
+ /* Switch to PHY page 0xFF */
+ ret = axgbe_m88e5112_set_page(pdata, 0xff);
+ if (ret)
+ return ret;
+ /* Configure M88E1512 errata registers */
+ for (i = 0; i < ARRAY_SIZE(errata_vals); i++) {
+ ret = pdata->phy_if.phy_impl.write(pdata, mdio_addr,
+ AXGBE_M88E1512_CFG_REG_2,
+ errata_vals[i].reg17);
+ if (ret)
+ return ret;
+
+ ret = pdata->phy_if.phy_impl.write(pdata, mdio_addr,
+ AXGBE_M88E1512_CFG_REG_1,
+ errata_vals[i].reg16);
+ if (ret)
+ return ret;
+ }
+
+ /* Switch to PHY page 0xFB */
+ ret = axgbe_m88e5112_set_page(pdata, 0xfb);
+ if (ret)
+ return ret;
+
+ ret = pdata->phy_if.phy_impl.write(pdata, mdio_addr,
+ AXGBE_M88E1512_CFG_REG_3, 0xC00D);
+ if (ret)
+ return ret;
+
+ /* Switch to PHY page 0 */
+ ret = axgbe_m88e5112_set_page(pdata, 0);
+ if (ret)
+ return ret;
+
+ /* SGMII-to-Copper mode initialization */
+
+ /* Switch to PHY page 0x12 */
+ ret = axgbe_m88e5112_set_page(pdata, 0x12);
+ if (ret)
+ return ret;
+
+ ret = pdata->phy_if.phy_impl.write(pdata, mdio_addr,
+ AXGBE_M88E1512_MODE, 0x8001);
+ if (ret)
+ return ret;
+
+ /* Switch to PHY page 0 */
+ ret = axgbe_m88e5112_set_page(pdata, 0);
+ if (ret)
+ return ret;
+
+ ret = axgbe_phy_soft_reset(pdata);
+ if (ret)
+ return ret;
+
+ rte_delay_ms(1000);
+
+
+ /* Switch to PHY page 3 */
+ ret = axgbe_m88e5112_set_page(pdata, 3);
+ if (ret)
+ return ret;
+
+ /* enable downshift */
+ ret = pdata->phy_if.phy_impl.write(pdata, mdio_addr,
+ AXGBE_M88E1512_SCR, 0x1177);
+ if (ret)
+ return ret;
+
+
+ /* Switch to PHY page 0 */
+ ret = axgbe_m88e5112_set_page(pdata, 0);
+ if (ret)
+ return ret;
+
+ ret = axgbe_phy_soft_reset(pdata);
+ if (ret)
+ return ret;
+
+ rte_delay_ms(1000);
+
+ PMD_DRV_LOG_LINE(DEBUG, "M88E1512 phy init done");
+ return 0;
+}
+
+
+static int axgbe_get_ext_phy_link_status(struct axgbe_port *pdata)
+{
+ struct axgbe_phy_data *phy_data = pdata->phy_data;
+ unsigned int mdio_addr = phy_data->mdio_addr;
+ int status = 0, bmcr;
+
+ bmcr = pdata->phy_if.phy_impl.read(pdata, mdio_addr, MII_BMCR);
+ if (bmcr < 0)
+ return bmcr;
+
+ /* Autoneg is being started, therefore disregard BMSR value and
+ * report link as down.
+ */
+ if (bmcr & BMCR_ANRESTART)
+ return 0;
+
+ status = pdata->phy_if.phy_impl.read(pdata, mdio_addr, MII_BMSR);
+ if (status < 0)
+ return status;
+ else if (status & BMSR_LSTATUS)
+ goto done;
+
+ /* Read link and autonegotiation status */
+ status = pdata->phy_if.phy_impl.read(pdata, mdio_addr, MII_BMSR);
+ if (status < 0)
+ return status;
+done:
+ return (status & BMSR_LSTATUS) ? 1 : 0;
+}
+
static int axgbe_phy_init(struct axgbe_port *pdata)
{
struct axgbe_phy_data *phy_data;
@@ -2542,6 +2785,16 @@ static int axgbe_phy_init(struct axgbe_port *pdata)
}

phy_data->phy_cdr_delay = AXGBE_CDR_DELAY_INIT;
+
+ pdata->phy.id = axgbe_get_phy_id(pdata);
+ PMD_DRV_LOG_LINE(DEBUG, "PHY ID = 0x%x", pdata->phy.id);
+
+ if (pdata->phy.id == M88E1512_E_PHY_ID) {
+ ret = axgbe_m88e1512_init(pdata);
+ if(ret)
+ return ret;
+ }
+
return 0;
}
void axgbe_init_function_ptrs_phy_v2(struct axgbe_phy_if *phy_if)
--
2.34.1