[PATCH net-next v4 12/12] ax88796b: Add support for AX88772D, AX88179A and AX88279
From: Birger Koblitz <hidden>
Date: 2026-07-31 16:19:30
Also in:
linux-usb, lkml
Subsystem:
ethernet phy library, networking drivers, the rest · Maintainers:
Andrew Lunn, Heiner Kallweit, Andrew Lunn, "David S. Miller", Eric Dumazet, Jakub Kicinski, Paolo Abeni, Linus Torvalds
The AX88772D, AX88179A and AX88279 are 100MBit, 1GBit and respectively 2.5 GBit PHYs that are found in ASIX USB-Ethernet controllers of the same name. Signed-off-by: Birger Koblitz <redacted> --- drivers/net/phy/ax88796b.c | 192 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 192 insertions(+)
diff --git a/drivers/net/phy/ax88796b.c b/drivers/net/phy/ax88796b.c
index f20ddf64914993f7097070d1f56c1103ec1e40e8..686f4e8d08f74da98e453ea50a5347cf3673e869 100644
--- a/drivers/net/phy/ax88796b.c
+++ b/drivers/net/phy/ax88796b.c@@ -13,6 +13,19 @@ #define PHY_ID_ASIX_AX88772A 0x003b1861 #define PHY_ID_ASIX_AX88772C 0x003b1881 #define PHY_ID_ASIX_AX88796B 0x003b1841 +#define PHY_ID_ASIX_AX88772D 0x003b772d +#define PHY_ID_ASIX_AX88179A 0x003b179a +#define PHY_ID_ASIX_AX88279 0x003b2790 + +#define AX_ADVERTISE_2500 0x1000 + +/* MII Vendor registers */ +#define AX_CTRL_STATUS 0x1d +#define AX_CTRL_STATUS_SPEED_MASK 0x0c +#define AX_CTRL_STATUS_SPEED_10 0x0 +#define AX_CTRL_STATUS_SPEED_100 0x4 +#define AX_CTRL_STATUS_SPEED_1000 0x8 +#define AX_CTRL_STATUS_SPEED_2500 0xc MODULE_DESCRIPTION("Asix PHY driver"); MODULE_AUTHOR("Michael Schmitz <schmitzmic@gmail.com>");
@@ -83,6 +96,135 @@ static int asix_ax88772a_read_status(struct phy_device *phydev) return 0; } +static int asix_ax88279_read_status(struct phy_device *phydev) +{ + int ret, val; + + ret = genphy_update_link(phydev); + if (ret) + return ret; + + phydev->master_slave_get = MASTER_SLAVE_CFG_UNSUPPORTED; + phydev->master_slave_state = MASTER_SLAVE_STATE_UNSUPPORTED; + phydev->speed = SPEED_UNKNOWN; + phydev->duplex = DUPLEX_UNKNOWN; + phydev->pause = 0; + phydev->asym_pause = 0; + phydev->rate_matching = RATE_MATCH_PAUSE; + + if (!phydev->link) + return 0; + + ret = genphy_read_master_slave(phydev); + if (ret < 0) + return ret; + + ret = genphy_read_lpa(phydev); + if (ret < 0) + return ret; + + val = phy_read(phydev, MII_ADVERTISE); + if (val < 0) + return val; + + linkmode_mod_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT, + phydev->advertising, val & AX_ADVERTISE_2500); + + /* Read actual speed from vendor register */ + val = phy_read(phydev, AX_CTRL_STATUS); + switch (val & AX_CTRL_STATUS_SPEED_MASK) { + case AX_CTRL_STATUS_SPEED_2500: + phydev->speed = SPEED_2500; + break; + case AX_CTRL_STATUS_SPEED_1000: + phydev->speed = SPEED_1000; + break; + case AX_CTRL_STATUS_SPEED_100: + phydev->speed = SPEED_100; + break; + case AX_CTRL_STATUS_SPEED_10: + phydev->speed = SPEED_10; + } + + /* MDIO_AN_10GBT_STAT_LP2_5G is broken, but we can deduce that + * the link-partner advertised 2500M if remotely AN succceded + * for link speed > 1000M and we locally have a link speed of + * 2500M + */ + val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_10GBT_STAT); + if (val >= 0 && val & MDIO_AN_10GBT_STAT_REMOK) { + linkmode_mod_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT, + phydev->lp_advertising, + phydev->speed == SPEED_2500); + } + /* Only supports full duplex */ + phydev->duplex = DUPLEX_FULL; + + return 0; +} + +static int asix_ax88279_config_aneg(struct phy_device *phydev) +{ + bool changed = false; + int ret; + u32 adv; + + if (phydev->autoneg == AUTONEG_DISABLE) { + phydev_warn(phydev, "Disabling autoneg is not supported\n"); + return -EINVAL; + } + + adv = linkmode_adv_to_mii_10gbt_adv_t(phydev->advertising); + + ret = phy_modify_mmd_changed(phydev, MDIO_MMD_AN, MDIO_AN_10GBT_CTRL, + MDIO_AN_10GBT_CTRL_ADV2_5G, adv); + if (ret < 0) + return ret; + if (ret > 0) + changed = true; + + return __genphy_config_aneg(phydev, changed); +} + +static int asix_ax88279_get_features(struct phy_device *phydev) +{ + int ret; + + /* MDIO_DEVS1/2 empty, so set mmds_present bits to allow reading abilities */ + phydev->c45_ids.mmds_present |= MDIO_DEVS_PMAPMD | MDIO_DEVS_AN; + + linkmode_set_bit_array(phy_basic_ports_array, ARRAY_SIZE(phy_basic_ports_array), + phydev->supported); + + ret = genphy_c45_pma_read_abilities(phydev); + if (ret < 0) + return ret; + + /* AX88279 does not support reported 100baseT-half duplex mode */ + linkmode_clear_bit(ETHTOOL_LINK_MODE_100baseT_Half_BIT, phydev->supported); + + return 0; +} + +/* AX88179A-based PHYs cannot access MMD registers via MII_MMD_CTRL/MII_MMD_DATA + * Make sure to use c45 access instead. + */ +static int asix_read_mmd(struct phy_device *phydev, int devnum, u16 reg) +{ + struct mii_bus *bus = phydev->mdio.bus; + int addr = phydev->mdio.addr; + + return __mdiobus_c45_read(bus, addr, devnum, reg); +} + +static int asix_write_mmd(struct phy_device *phydev, int devnum, u16 reg, u16 val) +{ + struct mii_bus *bus = phydev->mdio.bus; + int addr = phydev->mdio.addr; + + return __mdiobus_c45_write(bus, addr, devnum, reg, val); +} + static void asix_ax88772a_link_change_notify(struct phy_device *phydev) { /* Reset PHY, otherwise MII_LPA will provide outdated information.
@@ -94,6 +236,25 @@ static void asix_ax88772a_link_change_notify(struct phy_device *phydev) } } +static int asix_ax88772D_get_features(struct phy_device *phydev) +{ + int ret; + __ETHTOOL_DECLARE_LINK_MODE_MASK(mask) = {}; + + /* MDIO_DEVS1/2 empty, so set mmds_present bits to allow reading abilities */ + phydev->c45_ids.mmds_present |= MDIO_DEVS_PMAPMD | MDIO_DEVS_AN; + + ret = genphy_read_abilities(phydev); + if (ret < 0) + return ret; + + /* AX88772D does not support reported 1000baseT mode */ + linkmode_set_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT, mask); + linkmode_andnot(phydev->supported, phydev->supported, mask); + + return 0; +} + static struct phy_driver asix_driver[] = { { PHY_ID_MATCH_EXACT(PHY_ID_ASIX_AX88772A),
@@ -111,6 +272,34 @@ static struct phy_driver asix_driver[] = { .suspend = genphy_suspend, .resume = genphy_resume, .soft_reset = asix_soft_reset, +}, { + PHY_ID_MATCH_EXACT(PHY_ID_ASIX_AX88772D), + .name = "Asix Electronics AX88772D", + .flags = PHY_IS_INTERNAL, + .get_features = asix_ax88772D_get_features, + .read_mmd = asix_read_mmd, + .write_mmd = asix_write_mmd, + .suspend = genphy_suspend, + .resume = genphy_resume, +}, { + PHY_ID_MATCH_EXACT(PHY_ID_ASIX_AX88179A), + .name = "Asix Electronics AX88179A", + .flags = PHY_IS_INTERNAL, + .suspend = genphy_suspend, + .resume = genphy_resume, + .read_mmd = asix_read_mmd, + .write_mmd = asix_write_mmd, +}, { + PHY_ID_MATCH_EXACT(PHY_ID_ASIX_AX88279), + .name = "Asix Electronics AX88279", + .flags = PHY_IS_INTERNAL, + .get_features = asix_ax88279_get_features, + .read_status = asix_ax88279_read_status, + .config_aneg = asix_ax88279_config_aneg, + .read_mmd = asix_read_mmd, + .write_mmd = asix_write_mmd, + .suspend = genphy_suspend, + .resume = genphy_resume, }, { PHY_ID_MATCH_MODEL(PHY_ID_ASIX_AX88796B), .name = "Asix Electronics AX88796B",
@@ -124,6 +313,9 @@ static const struct mdio_device_id __maybe_unused asix_tbl[] = { { PHY_ID_MATCH_EXACT(PHY_ID_ASIX_AX88772A) }, { PHY_ID_MATCH_EXACT(PHY_ID_ASIX_AX88772C) }, { PHY_ID_MATCH_MODEL(PHY_ID_ASIX_AX88796B) }, + { PHY_ID_MATCH_EXACT(PHY_ID_ASIX_AX88772D) }, + { PHY_ID_MATCH_EXACT(PHY_ID_ASIX_AX88179A) }, + { PHY_ID_MATCH_EXACT(PHY_ID_ASIX_AX88279) }, { } };
--
2.47.3