[PATCH net-next v8 15/15] ax88796b: Add support for AX88772D, AX88179A and AX88279
From: Birger Koblitz <hidden>
Date: 2026-08-31 09:59:17
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 | 180 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 180 insertions(+)
diff --git a/drivers/net/phy/ax88796b.c b/drivers/net/phy/ax88796b.c
index f20ddf64914993f7097070d1f56c1103ec1e40e8..587e147a36afcb7e9f6a8adb433bb86484ce865a 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,122 @@ 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_read_status(phydev); + if (ret) + return ret; + + /* 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; + + /* PHY switches interface between 2.5GBit and slower modes */ + if (phydev->speed == SPEED_2500) + phydev->interface = PHY_INTERFACE_MODE_2500BASEX; + else + phydev->interface = PHY_INTERFACE_MODE_SGMII; + + return 0; +} + +static int asix_ax88279_config_aneg(struct phy_device *phydev) +{ + bool adv_2500; + int ret; + + if (phydev->autoneg == AUTONEG_DISABLE) { + phydev_warn(phydev, "Disabling autoneg is not supported\n"); + return -EOPNOTSUPP; + } + + ret = genphy_config_aneg(phydev); + + if (ret < 0) + return ret; + + adv_2500 = linkmode_test_bit(ETHTOOL_LINK_MODE_2500baseT_Full_BIT, phydev->advertising); + ret = phy_modify(phydev, MII_ADVERTISE, AX_ADVERTISE_2500, + adv_2500 ? AX_ADVERTISE_2500 : 0); + + return ret; +} + +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; +} + +static int asix_ax88279_config_init(struct phy_device *phydev) +{ + __set_bit(PHY_INTERFACE_MODE_2500BASEX, phydev->possible_interfaces); + __set_bit(PHY_INTERFACE_MODE_SGMII, phydev->possible_interfaces); + + 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 +223,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 +259,35 @@ 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 | PHY_BROKEN_FORCED, + .get_features = asix_ax88279_get_features, + .read_status = asix_ax88279_read_status, + .config_aneg = asix_ax88279_config_aneg, + .config_init = asix_ax88279_config_init, + .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 +301,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