From dbcc56c1a71911850208663846c93f80307eb455 Mon Sep 17 00:00:00 2001 From: Christian Hewitt Date: Sun, 12 Feb 2023 10:45:54 +0000 Subject: [PATCH 109/120] WIP: net: phy: add support for Maxio MAE0621A Maxio MAE0621A is the external PHY found in some Amlogic set-top box devices based on the S905D chipset. Signed-off-by: Zhao Yang Signed-off-by: Christian Hewitt --- .../net/ethernet/stmicro/stmmac/stmmac_main.c | 7 + drivers/net/phy/Kconfig | 5 + drivers/net/phy/Makefile | 1 + drivers/net/phy/maxio.c | 262 ++++++++++++++++++ drivers/net/phy/phy_device.c | 9 + 5 files changed, 284 insertions(+) create mode 100644 drivers/net/phy/maxio.c diff --git a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c index 84e1740b12f1..ed48b963653e 100644 --- a/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c +++ b/drivers/net/ethernet/stmicro/stmmac/stmmac_main.c @@ -7477,6 +7477,8 @@ static void stmmac_reset_tx_queue(struct stmmac_priv *priv, u32 queue) netdev_tx_reset_queue(netdev_get_tx_queue(priv->dev, queue)); } +#define MAXIO_PHY_MAE0621A_ID 0x7b744411 + /** * stmmac_reset_queues_param - reset queue parameters * @priv: device pointer @@ -7553,6 +7555,11 @@ int stmmac_resume(struct device *dev) stmmac_free_tx_skbufs(priv); stmmac_clear_descriptors(priv, &priv->dma_conf); + if (ndev->phydev->drv->config_init) { + if (ndev->phydev->phy_id == MAXIO_PHY_MAE0621A_ID) + ndev->phydev->drv->config_init(ndev->phydev); + } + stmmac_hw_setup(ndev, false); stmmac_init_coalesce(priv); stmmac_set_rx_mode(ndev); diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig index c57a0262fb64..9262cf995743 100644 --- a/drivers/net/phy/Kconfig +++ b/drivers/net/phy/Kconfig @@ -287,6 +287,11 @@ config AT803X_PHY Currently supports the AR8030, AR8031, AR8033, AR8035 and internal QCA8337(Internal qca8k PHY) model +config MAXIO_PHY + tristate "MAXIO PHYs" + help + Supports the Maxio MAExxxx PHYs. + config QSEMI_PHY tristate "Quality Semiconductor PHYs" help diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile index f7138d3c896b..11fbac4e4b6d 100644 --- a/drivers/net/phy/Makefile +++ b/drivers/net/phy/Makefile @@ -67,6 +67,7 @@ obj-$(CONFIG_LXT_PHY) += lxt.o obj-$(CONFIG_MARVELL_10G_PHY) += marvell10g.o obj-$(CONFIG_MARVELL_PHY) += marvell.o obj-$(CONFIG_MARVELL_88X2222_PHY) += marvell-88x2222.o +obj-$(CONFIG_MAXIO_PHY) += maxio.o obj-$(CONFIG_MAXLINEAR_GPHY) += mxl-gpy.o obj-$(CONFIG_MEDIATEK_GE_PHY) += mediatek-ge.o obj-$(CONFIG_MESON_GXL_PHY) += meson-gxl.o diff --git a/drivers/net/phy/maxio.c b/drivers/net/phy/maxio.c new file mode 100644 index 000000000000..8d4db24b7ed1 --- /dev/null +++ b/drivers/net/phy/maxio.c @@ -0,0 +1,262 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ^^ LICENSE TO BE CONFIRMED ^^ + * + * Driver for Maxio PHYs + * + * Copyright (c) 2004 maxio technology, Inc. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#define MAXIO_PAGE_SELECT 0x1f +#define MAXIO_MAE0621A_INER 0x12 +#define MAXIO_MAE0621A_INER_LINK_STATUS BIT(4) +#define MAXIO_MAE0621A_INSR 0x1d +#define MAXIO_MAE0621A_TX_DELAY (BIT(6) | BIT(7)) +#define MAXIO_MAE0621A_RX_DELAY (BIT(4) | BIT(5)) +#define MAXIO_MAE0621A_CLK_MODE_REG 0x02 +#define MAXIO_MAE0621A_WORK_STATUS_REG 0x1d + +int maxio_read_paged(struct phy_device *phydev, int page, u32 regnum) +{ + int ret = 0, oldpage; + + oldpage = phy_read(phydev, MAXIO_PAGE_SELECT); + if (oldpage >= 0) { + phy_write(phydev, MAXIO_PAGE_SELECT, page); + ret = phy_read(phydev, regnum); + } + phy_write(phydev, MAXIO_PAGE_SELECT, oldpage); + + return ret; +} + +int maxio_write_paged(struct phy_device *phydev, int page, u32 regnum, u16 val) +{ + int ret = 0, oldpage; + + oldpage = phy_read(phydev, MAXIO_PAGE_SELECT); + if (oldpage >= 0) { + phy_write(phydev, MAXIO_PAGE_SELECT, page); + ret = phy_write(phydev, regnum, val); + } + phy_write(phydev, MAXIO_PAGE_SELECT, oldpage); + + return ret; +} + +static int maxio_mae0621a_clk_init(struct phy_device *phydev) +{ + u32 workmode, clkmode, oldpage; + + oldpage = phy_read(phydev, MAXIO_PAGE_SELECT); + if (oldpage == 0xFFFF) + oldpage = phy_read(phydev, MAXIO_PAGE_SELECT); + + /* soft reset */ + phy_write(phydev, MAXIO_PAGE_SELECT, 0x0); + phy_write(phydev, MII_BMCR, BMCR_RESET | phy_read(phydev, MII_BMCR)); + + /* get workmode */ + phy_write(phydev, MAXIO_PAGE_SELECT, 0xa43); + workmode = phy_read(phydev, MAXIO_MAE0621A_WORK_STATUS_REG); + + /* get clkmode */ + phy_write(phydev, MAXIO_PAGE_SELECT, 0xd92); + clkmode = phy_read(phydev, MAXIO_MAE0621A_CLK_MODE_REG); + + /* abnormal */ + if (0 == (workmode & BIT(5))) { + if (0 == (clkmode & BIT(8))) { + /* oscillator */ + phy_write(phydev, 0x02, clkmode | BIT(8)); + pr_debug("maxio: mae0621a_clk_init clkmode 0x210a: 0x%x\n", + phydev->phy_id); + } else { + /* crystal */ + pr_debug("maxio: mae0621a_clk_init clkmode 0x200a: 0x%x\n", + phydev->phy_id); + phy_write(phydev, 0x02, clkmode & (~BIT(8))); + } + } + + phy_write(phydev, MAXIO_PAGE_SELECT, 0x0); + phy_write(phydev, MAXIO_PAGE_SELECT, oldpage); + + return 0; +} + +static int maxio_read_mmd(struct phy_device *phydev, int devnum, u16 regnum) +{ + int ret = 0, oldpage; + + oldpage = phy_read(phydev, MAXIO_PAGE_SELECT); + + if (devnum == MDIO_MMD_AN && regnum == MDIO_AN_EEE_ADV) { + /* eee info */ + phy_write(phydev, MAXIO_PAGE_SELECT, 0); + phy_write(phydev, 0xd, MDIO_MMD_AN); + phy_write(phydev, 0xe, MDIO_AN_EEE_ADV); + phy_write(phydev, 0xd, 0x4000 | MDIO_MMD_AN); + ret = phy_read(phydev, 0x0e); + } else { + ret = -EOPNOTSUPP; + } + phy_write(phydev, MAXIO_PAGE_SELECT, oldpage); + + return ret; +} + +static int maxio_write_mmd(struct phy_device *phydev, int devnum, u16 regnum, u16 val) +{ + int ret = 0, oldpage; + + oldpage = phy_read(phydev, MAXIO_PAGE_SELECT); + + if (devnum == MDIO_MMD_AN && regnum == MDIO_AN_EEE_ADV) { + /* eee info */ + phy_write(phydev, MAXIO_PAGE_SELECT, 0); + ret |= phy_write(phydev, 0xd, MDIO_MMD_AN); + ret |= phy_write(phydev, 0xe, MDIO_AN_EEE_ADV); + ret |= phy_write(phydev, 0xd, 0x4000 | MDIO_MMD_AN); + ret |= phy_write(phydev, 0xe, val); + msleep(100); + ret |= genphy_restart_aneg(phydev); + } else { + ret = -EOPNOTSUPP; + } + phy_write(phydev, MAXIO_PAGE_SELECT, oldpage); + + return ret; +} + +static int maxio_mae0621a_config_aneg(struct phy_device *phydev) +{ + return genphy_config_aneg(phydev); +} + +static int maxio_mae0621a_config_init(struct phy_device *phydev) +{ + struct device *dev = &phydev->mdio.dev; + u16 val; + int ret; + u32 broken = 0; + + maxio_mae0621a_clk_init(phydev); + + /* disable eee */ + pr_debug("eee value: 0x%x\n", maxio_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_EEE_ADV)); + maxio_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_EEE_ADV, 0); + pr_debug("eee value: 0x%x\n", maxio_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_EEE_ADV)); + broken |= MDIO_EEE_100TX; + broken |= MDIO_EEE_1000T; + phydev->eee_broken_modes = broken; + + /* enable auto_speed_down */ + ret = maxio_write_paged(phydev, 0xd8f, 0x0, 0x300); + + /* adjust tx/rx delay */ + switch (phydev->interface) { + case PHY_INTERFACE_MODE_RGMII: + val = 0x0; + break; + case PHY_INTERFACE_MODE_RGMII_ID: + val = MAXIO_MAE0621A_TX_DELAY | MAXIO_MAE0621A_RX_DELAY; + break; + case PHY_INTERFACE_MODE_RGMII_RXID: + val = MAXIO_MAE0621A_RX_DELAY; + break; + case PHY_INTERFACE_MODE_RGMII_TXID: + val = MAXIO_MAE0621A_TX_DELAY; + break; + default: + /* leave delays as-is. */ + goto delay_skip; + } + + ret = maxio_read_paged(phydev, 0xd96, 0x0); + + if (ret < 0) { + dev_err(dev, "Failed to update the TX delay register\n"); + return ret; + } + ret = maxio_write_paged(phydev, 0xd96, 0x0, val | ret); + + if (ret < 0) { + dev_err(dev, "Failed to update the TX delay register\n"); + return ret; + } else if (ret == 0) { + dev_dbg(dev, + "2ns delay was already %s (by pin-strapping RXD1 or bootloader configuration)\n", + val ? "enabled" : "disabled"); + } +delay_skip: + + phy_write(phydev, MII_BMCR, BMCR_RESET | phy_read(phydev, MII_BMCR)); + msleep(20); + phy_write(phydev, MAXIO_PAGE_SELECT, 0x0); + + return 0; +} + +static int maxio_mae0621a_resume(struct phy_device *phydev) +{ + int ret = genphy_resume(phydev); + + ret |= phy_write(phydev, MII_BMCR, BMCR_RESET | phy_read(phydev, MII_BMCR)); + + msleep(20); + + return ret; +} + +int maxio_mae0621a_suspend(struct phy_device *phydev) +{ + genphy_suspend(phydev); + phy_write(phydev, MAXIO_PAGE_SELECT, 0); + + return 0; +} + +static int maxio_mae0621a_status(struct phy_device *phydev) +{ + return genphy_read_status(phydev); +} + +static int maxio_mae0621a_probe(struct phy_device *phydev) +{ + int ret = maxio_mae0621a_clk_init(phydev); + + mdelay(100); + + return ret; +} + +static struct phy_driver maxio_nc_drvs[] = { + { + .phy_id = 0x7b744411, + .phy_id_mask = 0x7fffffff, + .name = "MAE0621A Gigabit Ethernet", + .features = PHY_GBIT_FEATURES, + .probe = maxio_mae0621a_probe, + .config_init = maxio_mae0621a_config_init, + .config_aneg = maxio_mae0621a_config_aneg, + .read_status = maxio_mae0621a_status, + .suspend = maxio_mae0621a_suspend, + .resume = maxio_mae0621a_resume, + }, +}; + +module_phy_driver(maxio_nc_drvs); + +MODULE_DESCRIPTION("Maxio PHY Driver"); +MODULE_AUTHOR("Zhao Yang "); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c index 8cff61dbc4b5..d27a396b0905 100644 --- a/drivers/net/phy/phy_device.c +++ b/drivers/net/phy/phy_device.c @@ -844,6 +844,15 @@ static int get_phy_c22_id(struct mii_bus *bus, int addr, u32 *phy_id) { int phy_reg; +#ifdef CONFIG_MAXIO_PHY + /** + * An MDIO connects to multiple PHYs requiring write before read. + * This operation does not affect one MDIO connected to a single PHY + * MII_PHYSID2 is a read-only register and writing to it has no effect + */ + mdiobus_write(bus, addr, MII_PHYSID2, 0); +#endif + /* Grab the bits from PHYIR1, and put them in the upper half */ phy_reg = mdiobus_read(bus, addr, MII_PHYSID1); if (phy_reg < 0) { -- 2.34.1