diff options
Diffstat (limited to 'drivers/net/mtk_eth/mt7530.c')
-rw-r--r-- | drivers/net/mtk_eth/mt7530.c | 281 |
1 files changed, 281 insertions, 0 deletions
diff --git a/drivers/net/mtk_eth/mt7530.c b/drivers/net/mtk_eth/mt7530.c new file mode 100644 index 00000000000..3065d35e126 --- /dev/null +++ b/drivers/net/mtk_eth/mt7530.c @@ -0,0 +1,281 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2025 MediaTek Inc. + * + * Author: Weijie Gao <weijie.gao@mediatek.com> + * Author: Mark Lee <mark-mc.lee@mediatek.com> + */ + +#include <miiphy.h> +#include <linux/delay.h> +#include <linux/mdio.h> +#include <linux/mii.h> +#include "mtk_eth.h" +#include "mt753x.h" + +#define CHIP_REV 0x7ffc +#define CHIP_NAME_S 16 +#define CHIP_NAME_M 0xffff0000 +#define CHIP_REV_S 0 +#define CHIP_REV_M 0x0f + +static void mt7530_core_reg_write(struct mt753x_switch_priv *priv, u32 reg, + u32 val) +{ + u8 phy_addr = MT753X_PHY_ADDR(priv->phy_base, 0); + + mtk_mmd_ind_write(priv->epriv.eth, phy_addr, 0x1f, reg, val); +} + +static int mt7530_pad_clk_setup(struct mt753x_switch_priv *priv, int mode) +{ + u32 ncpo1, ssc_delta; + + switch (mode) { + case PHY_INTERFACE_MODE_RGMII: + ncpo1 = 0x0c80; + ssc_delta = 0x87; + break; + + default: + printf("error: xMII mode %d is not supported\n", mode); + return -EINVAL; + } + + /* Disable MT7530 core clock */ + mt7530_core_reg_write(priv, CORE_TRGMII_GSW_CLK_CG, 0); + + /* Disable MT7530 PLL */ + mt7530_core_reg_write(priv, CORE_GSWPLL_GRP1, + (2 << RG_GSWPLL_POSDIV_200M_S) | + (32 << RG_GSWPLL_FBKDIV_200M_S)); + + /* For MT7530 core clock = 500Mhz */ + mt7530_core_reg_write(priv, CORE_GSWPLL_GRP2, + (1 << RG_GSWPLL_POSDIV_500M_S) | + (25 << RG_GSWPLL_FBKDIV_500M_S)); + + /* Enable MT7530 PLL */ + mt7530_core_reg_write(priv, CORE_GSWPLL_GRP1, + (2 << RG_GSWPLL_POSDIV_200M_S) | + (32 << RG_GSWPLL_FBKDIV_200M_S) | + RG_GSWPLL_EN_PRE); + + udelay(20); + + mt7530_core_reg_write(priv, CORE_TRGMII_GSW_CLK_CG, REG_GSWCK_EN); + + /* Setup the MT7530 TRGMII Tx Clock */ + mt7530_core_reg_write(priv, CORE_PLL_GROUP5, ncpo1); + mt7530_core_reg_write(priv, CORE_PLL_GROUP6, 0); + mt7530_core_reg_write(priv, CORE_PLL_GROUP10, ssc_delta); + mt7530_core_reg_write(priv, CORE_PLL_GROUP11, ssc_delta); + mt7530_core_reg_write(priv, CORE_PLL_GROUP4, RG_SYSPLL_DDSFBK_EN | + RG_SYSPLL_BIAS_EN | RG_SYSPLL_BIAS_LPF_EN); + + mt7530_core_reg_write(priv, CORE_PLL_GROUP2, + RG_SYSPLL_EN_NORMAL | RG_SYSPLL_VODEN | + (1 << RG_SYSPLL_POSDIV_S)); + + mt7530_core_reg_write(priv, CORE_PLL_GROUP7, + RG_LCDDS_PCW_NCPO_CHG | (3 << RG_LCCDS_C_S) | + RG_LCDDS_PWDB | RG_LCDDS_ISO_EN); + + /* Enable MT7530 core clock */ + mt7530_core_reg_write(priv, CORE_TRGMII_GSW_CLK_CG, + REG_GSWCK_EN | REG_TRGMIICK_EN); + + return 0; +} + +static void mt7530_mac_control(struct mtk_eth_switch_priv *swpriv, bool enable) +{ + struct mt753x_switch_priv *priv = (struct mt753x_switch_priv *)swpriv; + u32 pmcr = FORCE_MODE; + + if (enable) + pmcr = priv->pmcr; + + mt753x_reg_write(priv, PMCR_REG(6), pmcr); +} + +static int mt7530_mdio_read(struct mii_dev *bus, int addr, int devad, int reg) +{ + struct mt753x_switch_priv *priv = bus->priv; + + if (devad < 0) + return mtk_mii_read(priv->epriv.eth, addr, reg); + + return mtk_mmd_ind_read(priv->epriv.eth, addr, devad, reg); +} + +static int mt7530_mdio_write(struct mii_dev *bus, int addr, int devad, int reg, + u16 val) +{ + struct mt753x_switch_priv *priv = bus->priv; + + if (devad < 0) + return mtk_mii_write(priv->epriv.eth, addr, reg, val); + + return mtk_mmd_ind_write(priv->epriv.eth, addr, devad, reg, val); +} + +static int mt7530_mdio_register(struct mt753x_switch_priv *priv) +{ + struct mii_dev *mdio_bus = mdio_alloc(); + int ret; + + if (!mdio_bus) + return -ENOMEM; + + mdio_bus->read = mt7530_mdio_read; + mdio_bus->write = mt7530_mdio_write; + snprintf(mdio_bus->name, sizeof(mdio_bus->name), priv->epriv.sw->name); + + mdio_bus->priv = priv; + + ret = mdio_register(mdio_bus); + if (ret) { + mdio_free(mdio_bus); + return ret; + } + + priv->mdio_bus = mdio_bus; + + return 0; +} + +static int mt7530_setup(struct mtk_eth_switch_priv *swpriv) +{ + struct mt753x_switch_priv *priv = (struct mt753x_switch_priv *)swpriv; + u16 phy_addr, phy_val; + u32 i, val, txdrv; + + priv->smi_addr = MT753X_DFL_SMI_ADDR; + priv->reg_read = mt753x_mdio_reg_read; + priv->reg_write = mt753x_mdio_reg_write; + + if (!MTK_HAS_CAPS(priv->epriv.soc->caps, MTK_TRGMII_MT7621_CLK)) { + /* Select 250MHz clk for RGMII mode */ + mtk_ethsys_rmw(priv->epriv.eth, ETHSYS_CLKCFG0_REG, + ETHSYS_TRGMII_CLK_SEL362_5, 0); + + txdrv = 8; + } else { + txdrv = 4; + } + + /* Modify HWTRAP first to allow direct access to internal PHYs */ + mt753x_reg_read(priv, HWTRAP_REG, &val); + val |= CHG_TRAP; + val &= ~C_MDIO_BPS; + mt753x_reg_write(priv, MHWTRAP_REG, val); + + /* Calculate the phy base address */ + val = ((val & SMI_ADDR_M) >> SMI_ADDR_S) << 3; + priv->phy_base = (val | 0x7) + 1; + + /* Turn off PHYs */ + for (i = 0; i < MT753X_NUM_PHYS; i++) { + phy_addr = MT753X_PHY_ADDR(priv->phy_base, i); + phy_val = mtk_mii_read(priv->epriv.eth, phy_addr, MII_BMCR); + phy_val |= BMCR_PDOWN; + mtk_mii_write(priv->epriv.eth, phy_addr, MII_BMCR, phy_val); + } + + /* Force MAC link down before reset */ + mt753x_reg_write(priv, PMCR_REG(5), FORCE_MODE); + mt753x_reg_write(priv, PMCR_REG(6), FORCE_MODE); + + /* MT7530 reset */ + mt753x_reg_write(priv, SYS_CTRL_REG, SW_SYS_RST | SW_REG_RST); + udelay(100); + + val = (IPG_96BIT_WITH_SHORT_IPG << IPG_CFG_S) | + MAC_MODE | FORCE_MODE | + MAC_TX_EN | MAC_RX_EN | + BKOFF_EN | BACKPR_EN | + (SPEED_1000M << FORCE_SPD_S) | + FORCE_DPX | FORCE_LINK; + + /* MT7530 Port6: Forced 1000M/FD, FC disabled */ + priv->pmcr = val; + + /* MT7530 Port5: Forced link down */ + mt753x_reg_write(priv, PMCR_REG(5), FORCE_MODE); + + /* Keep MAC link down before starting eth */ + mt753x_reg_write(priv, PMCR_REG(6), FORCE_MODE); + + /* MT7530 Port6: Set to RGMII */ + mt753x_reg_rmw(priv, MT7530_P6ECR, P6_INTF_MODE_M, P6_INTF_MODE_RGMII); + + /* Hardware Trap: Enable Port6, Disable Port5 */ + mt753x_reg_read(priv, HWTRAP_REG, &val); + val |= CHG_TRAP | LOOPDET_DIS | P5_INTF_DIS | + (P5_INTF_SEL_GMAC5 << P5_INTF_SEL_S) | + (P5_INTF_MODE_RGMII << P5_INTF_MODE_S); + val &= ~(C_MDIO_BPS | P6_INTF_DIS); + mt753x_reg_write(priv, MHWTRAP_REG, val); + + /* Setup switch core pll */ + mt7530_pad_clk_setup(priv, priv->epriv.phy_interface); + + /* Lower Tx Driving for TRGMII path */ + for (i = 0 ; i < NUM_TRGMII_CTRL ; i++) + mt753x_reg_write(priv, MT7530_TRGMII_TD_ODT(i), + (txdrv << TD_DM_DRVP_S) | + (txdrv << TD_DM_DRVN_S)); + + for (i = 0 ; i < NUM_TRGMII_CTRL; i++) + mt753x_reg_rmw(priv, MT7530_TRGMII_RD(i), RD_TAP_M, 16); + + /* Enable port isolation to block inter-port communication */ + mt753x_port_isolation(priv); + + /* Turn on PHYs */ + for (i = 0; i < MT753X_NUM_PHYS; i++) { + phy_addr = MT753X_PHY_ADDR(priv->phy_base, i); + phy_val = mtk_mii_read(priv->epriv.eth, phy_addr, MII_BMCR); + phy_val &= ~BMCR_PDOWN; + mtk_mii_write(priv->epriv.eth, phy_addr, MII_BMCR, phy_val); + } + + return mt7530_mdio_register(priv); +} + +static int mt7530_cleanup(struct mtk_eth_switch_priv *swpriv) +{ + struct mt753x_switch_priv *priv = (struct mt753x_switch_priv *)swpriv; + + mdio_unregister(priv->mdio_bus); + + return 0; +} + +static int mt7530_detect(struct mtk_eth_priv *priv) +{ + int ret; + u32 rev; + + ret = __mt753x_mdio_reg_read(priv, MT753X_DFL_SMI_ADDR, CHIP_REV, &rev); + if (ret) + return ret; + + if (((rev & CHIP_NAME_M) >> CHIP_NAME_S) == 0x7530) + return 0; + + return -ENODEV; +} + +MTK_ETH_SWITCH(mt7530) = { + .name = "mt7530", + .desc = "MediaTek MT7530", + .priv_size = sizeof(struct mt753x_switch_priv), + .reset_wait_time = 1000, + + .detect = mt7530_detect, + .setup = mt7530_setup, + .cleanup = mt7530_cleanup, + .mac_control = mt7530_mac_control, +}; |