The Maxio MAE0621A(-Q2C / -Q3C(I)) gigabit PHYs ship in a non-functional power-on state: the clock-mode register must be programmed for crystal operation and a set of analog tuning registers applied before the RGMII interface starts toggling (RXC output included). With the generic C22 driver the RX clock never appears, which surfaces as an EQOS DMA_MODE_SWR timeout on MACs sourcing their RX clock from the PHY.
Port the init sequence from the vendor out-of-tree Linux driver (MAXIO_PHY_VER v1.8.1.1): paged register writes selecting crystal clock mode, the analog tuning sequence, and the ADC self-check loop. Link status is read straight from BMSR (read twice per the IEEE 802.3 latching rules) instead of waiting on the autoneg-complete bit: this PHY reports link before aneg-complete settles, which the vendor Linux driver works around as well. PHY ID1 has been observed reading 0xffff on a board where the vendor kernel reads 0x7b74, so match on the low 16 ID bits only: Q2C ends in 0x4411, Q3C(I) in 0x4412. Signed-off-by: kongchen <[email protected]> --- drivers/net/phy/Kconfig | 8 ++ drivers/net/phy/Makefile | 1 + drivers/net/phy/maxio.c | 228 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 237 insertions(+) create mode 100644 drivers/net/phy/maxio.c diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig index ee438524490..02aeb1db977 100644 --- a/drivers/net/phy/Kconfig +++ b/drivers/net/phy/Kconfig @@ -238,6 +238,14 @@ config PHY_MOTORCOMM Enables support for Motorcomm network PHYs. Currently supports the YT8511 and YT8531 Gigabit Ethernet PHYs. +config PHY_MAXIO + bool "Maxio PHYs" + help + Enables support for Maxio network PHYs. + Currently supports the MAE0621A/MAE0621B Gigabit Ethernet PHYs, + which require a vendor init sequence (crystal clock mode plus + analog tuning) before their RGMII interface comes up. + config PHY_MSCC depends on !COMPILE_TEST bool "Microsemi Corp Ethernet PHYs support" diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile index 83520de7f1f..bd57fa005b1 100644 --- a/drivers/net/phy/Makefile +++ b/drivers/net/phy/Makefile @@ -21,6 +21,7 @@ obj-$(CONFIG_PHY_ET1011C) += et1011c.o obj-$(CONFIG_PHY_LXT) += lxt.o obj-$(CONFIG_PHY_MARVELL) += marvell.o obj-$(CONFIG_PHY_MARVELL_10G) += marvell10g.o +obj-$(CONFIG_PHY_MAXIO) += maxio.o obj-y += mediatek/ obj-y += airoha/ obj-$(CONFIG_PHY_MICREL_KSZ8XXX) += micrel_ksz8xxx.o diff --git a/drivers/net/phy/maxio.c b/drivers/net/phy/maxio.c new file mode 100644 index 00000000000..c673a342093 --- /dev/null +++ b/drivers/net/phy/maxio.c @@ -0,0 +1,228 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Maxio MAE0621A PHY driver, ported from the vendor out-of-tree + * Linux driver (MAXIO_PHY_VER "v1.8.1.1"). + * + * The PHY comes up in a non-functional state: the clock-mode register + * must be programmed for crystal operation and a set of analog tuning + * registers applied before the RGMII interface starts toggling. The + * generic C22 driver leaves the RX clock dead, which makes the MAC's + * EQOS_DMA_MODE_SWR reset never complete. + */ + +#include <phy.h> +#include <linux/bitops.h> +#include <linux/delay.h> + +#define PHY_ID_MAE0621A 0x7b744411 +#define PHY_ID_MAE0621A_Q3C 0x7b744412 +#define PHY_ID_MASK 0x7fffffff + +#define MAXIO_PAGE_SELECT 0x1f + +/* Vendor status register, page 0xa43 */ +#define MAXIO_PHYSR 0x1a +#define MAXIO_PHY_LINK BIT(2) +#define MAXIO_PHY_DUPLEX BIT(3) +#define MAXIO_PHY_SPEED_MASK GENMASK(5, 4) +#define MAXIO_PHY_1000M BIT(5) +#define MAXIO_PHY_100M BIT(4) + +static int maxio_write_paged(struct phy_device *phydev, int page, + int regnum, u16 val) +{ + int ret; + + ret = phy_write(phydev, MDIO_DEVAD_NONE, MAXIO_PAGE_SELECT, page); + if (ret < 0) + return ret; + + ret = phy_write(phydev, MDIO_DEVAD_NONE, regnum, val); + + phy_write(phydev, MDIO_DEVAD_NONE, MAXIO_PAGE_SELECT, 0); + + return ret; +} + +static int maxio_read_paged(struct phy_device *phydev, int page, int regnum) +{ + int ret; + + ret = phy_write(phydev, MDIO_DEVAD_NONE, MAXIO_PAGE_SELECT, page); + if (ret < 0) + return ret; + + ret = phy_read(phydev, MDIO_DEVAD_NONE, regnum); + + phy_write(phydev, MDIO_DEVAD_NONE, MAXIO_PAGE_SELECT, 0); + + return ret; +} + +static int maxio_adcc_check(struct phy_device *phydev) +{ + int adcvalue, regval, i; + + maxio_write_paged(phydev, 0xd96, 0x2, 0x1fff); + maxio_write_paged(phydev, 0xd96, 0x2, 0x1000); + + for (i = 0; i < 4; i++) { + regval = 0xf908 + i * 0x100; + maxio_write_paged(phydev, 0xd8f, 0xb, regval); + adcvalue = maxio_read_paged(phydev, 0xd92, 0xb); + if (!(adcvalue & 0x1ff)) + return -1; + } + + return 0; +} + +static void maxio_self_check(struct phy_device *phydev, int checknum) +{ + int ret, i; + + for (i = 0; i < checknum; i++) { + ret = maxio_adcc_check(phydev); + if (!ret) + break; + + maxio_write_paged(phydev, 0x0, 0x0, 0x1940); + phy_write(phydev, MDIO_DEVAD_NONE, MAXIO_PAGE_SELECT, 0x0); + mdelay(10); + maxio_write_paged(phydev, 0x0, 0x0, 0x1140); + maxio_write_paged(phydev, 0x0, 0x0, 0x9140); + } + + maxio_write_paged(phydev, 0xd96, 0x2, 0xfff); + maxio_write_paged(phydev, 0x0, 0x0, 0x9140); + phy_write(phydev, MDIO_DEVAD_NONE, MAXIO_PAGE_SELECT, 0x0); +} + +static int mae0621a_config(struct phy_device *phydev) +{ + pr_info("Maxio MAE0621A init (crystal clock mode + analog tuning)\n"); + + maxio_write_paged(phydev, 0xd92, 0x2, 0x200a); /* clock mode: crystal */ + + maxio_write_paged(phydev, 0xda0, 0x10, 0xc13); + maxio_write_paged(phydev, 0x0, 0xd, 0x7); + maxio_write_paged(phydev, 0x0, 0xe, 0x3c); + maxio_write_paged(phydev, 0x0, 0xd, 0x4007); + maxio_write_paged(phydev, 0x0, 0xe, 0x0); + maxio_write_paged(phydev, 0xd96, 0x13, 0x7bc); + maxio_write_paged(phydev, 0xd8f, 0x8, 0x2500); + maxio_write_paged(phydev, 0xd90, 0x2, 0x1555); + maxio_write_paged(phydev, 0xd90, 0x5, 0x2b15); + maxio_write_paged(phydev, 0xd92, 0x14, 0xa); + maxio_write_paged(phydev, 0xd91, 0x7, 0x5b00); + maxio_write_paged(phydev, 0xd8f, 0x0, 0x300); + maxio_write_paged(phydev, 0xd92, 0xa, 0x8506); + maxio_write_paged(phydev, 0xd91, 0x6, 0x6870); + maxio_write_paged(phydev, 0xd91, 0x1, 0x940); + maxio_write_paged(phydev, 0xda0, 0x13, 0x1303); + maxio_write_paged(phydev, 0xd97, 0xc, 0x177); + maxio_write_paged(phydev, 0xd97, 0xb, 0x9a9); + maxio_write_paged(phydev, 0xa42, 0x12, 0x28); + maxio_write_paged(phydev, 0x0, 0x4, 0xde1); + maxio_write_paged(phydev, 0x0, 0x0, 0x9140); + + maxio_self_check(phydev, 50); + mdelay(100); + + return 0; +} + +static int mae0621a_q3c_config(struct phy_device *phydev) +{ + maxio_write_paged(phydev, 0xa43, 0x19, 0x823); + + maxio_write_paged(phydev, 0xdab, 0x17, 0xc13); + maxio_write_paged(phydev, 0xd96, 0x15, 0xc08a); + maxio_write_paged(phydev, 0xda4, 0x12, 0x7bc); + maxio_write_paged(phydev, 0xd8f, 0x16, 0x2500); + maxio_write_paged(phydev, 0xd90, 0x16, 0x1555); + maxio_write_paged(phydev, 0xd92, 0x11, 0x2b15); + maxio_write_paged(phydev, 0xd96, 0x16, 0x4010); + maxio_write_paged(phydev, 0xda5, 0x11, 0x4a12); + maxio_write_paged(phydev, 0xda5, 0x12, 0x4a12); + maxio_write_paged(phydev, 0xd99, 0x16, 0xa); + maxio_write_paged(phydev, 0xd95, 0x13, 0x5b00); + maxio_write_paged(phydev, 0xd8f, 0x10, 0x300); + maxio_write_paged(phydev, 0xd98, 0x17, 0x8506); + maxio_write_paged(phydev, 0xd95, 0x12, 0x6870); + maxio_write_paged(phydev, 0xd93, 0x15, 0x940); + maxio_write_paged(phydev, 0xdad, 0x12, 0x1303); + maxio_write_paged(phydev, 0xda8, 0x11, 0x177); + maxio_write_paged(phydev, 0xda8, 0x10, 0x9a9); + maxio_write_paged(phydev, 0xa42, 0x12, 0x28); + maxio_write_paged(phydev, 0x0, 0x4, 0xde1); + maxio_write_paged(phydev, 0x0, 0x0, 0x9140); + + phy_write(phydev, MDIO_DEVAD_NONE, MAXIO_PAGE_SELECT, 0); + + return 0; +} + +static int mae0621a_startup(struct phy_device *phydev) +{ + int bmsr, physr; + + /* + * Read link straight from BMSR (twice: the link bit is latched low + * per IEEE 802.3). Do not wait on the autoneg-complete bit: this + * PHY reports link before aneg-complete settles, which the vendor + * kernel driver also works around. + */ + bmsr = phy_read(phydev, MDIO_DEVAD_NONE, MII_BMSR); + if (bmsr < 0) + return bmsr; + bmsr = phy_read(phydev, MDIO_DEVAD_NONE, MII_BMSR); + if (bmsr < 0) + return bmsr; + + phydev->link = !!(bmsr & BMSR_LSTATUS); + if (!phydev->link) + return 0; + + physr = maxio_read_paged(phydev, 0xa43, MAXIO_PHYSR); + if (physr < 0) + return physr; + + if ((physr & MAXIO_PHY_SPEED_MASK) == MAXIO_PHY_1000M) + phydev->speed = SPEED_1000; + else if ((physr & MAXIO_PHY_SPEED_MASK) == MAXIO_PHY_100M) + phydev->speed = SPEED_100; + else + phydev->speed = SPEED_10; + + phydev->duplex = (physr & MAXIO_PHY_DUPLEX) ? DUPLEX_FULL : DUPLEX_HALF; + + return 0; +} + +/* + * This PHY has been observed reporting 0xffff for PHY ID1 on this board + * (raw, pre-init state) while the vendor kernel reads 0x7b74, so match on + * the ID2 low bits only; Q2C ends in 0x4411 and Q3C in 0x4412. + */ +#define PHY_ID_MATCH_LOW16 0x0000ffff + +U_BOOT_PHY_DRIVER(mae0621a) = { + .name = "Maxio MAE0621A-Q2C Gigabit Ethernet", + .uid = PHY_ID_MAE0621A, + .mask = PHY_ID_MATCH_LOW16, + .features = PHY_GBIT_FEATURES, + .config = &mae0621a_config, + .startup = &mae0621a_startup, + .shutdown = &genphy_shutdown, +}; + +U_BOOT_PHY_DRIVER(mae0621a_q3c) = { + .name = "Maxio MAE0621A/B-Q3C(I) Gigabit Ethernet", + .uid = PHY_ID_MAE0621A_Q3C, + .mask = PHY_ID_MATCH_LOW16, + .features = PHY_GBIT_FEATURES, + .config = &mae0621a_q3c_config, + .startup = &mae0621a_startup, + .shutdown = &genphy_shutdown, +}; -- 2.55.0
