| // 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 <errno.h> |
| #include <time.h> |
| #include "mtk_eth.h" |
| #include "mt753x.h" |
| |
| /* |
| * MT753x Internal Register Address Bits |
| * ------------------------------------------------------------------- |
| * | 15 14 13 12 11 10 9 8 7 6 | 5 4 3 2 | 1 0 | |
| * |----------------------------------------|---------------|--------| |
| * | Page Address | Reg Address | Unused | |
| * ------------------------------------------------------------------- |
| */ |
| |
| int __mt753x_mdio_reg_read(struct mtk_eth_priv *priv, u32 smi_addr, u32 reg, |
| u32 *data) |
| { |
| int ret, low_word, high_word; |
| |
| /* Write page address */ |
| ret = mtk_mii_write(priv, smi_addr, 0x1f, reg >> 6); |
| if (ret) |
| return ret; |
| |
| /* Read low word */ |
| low_word = mtk_mii_read(priv, smi_addr, (reg >> 2) & 0xf); |
| if (low_word < 0) |
| return low_word; |
| |
| /* Read high word */ |
| high_word = mtk_mii_read(priv, smi_addr, 0x10); |
| if (high_word < 0) |
| return high_word; |
| |
| if (data) |
| *data = ((u32)high_word << 16) | (low_word & 0xffff); |
| |
| return 0; |
| } |
| |
| int mt753x_mdio_reg_read(struct mt753x_switch_priv *priv, u32 reg, u32 *data) |
| { |
| return __mt753x_mdio_reg_read(priv->epriv.eth, priv->smi_addr, reg, |
| data); |
| } |
| |
| int mt753x_mdio_reg_write(struct mt753x_switch_priv *priv, u32 reg, u32 data) |
| { |
| int ret; |
| |
| /* Write page address */ |
| ret = mtk_mii_write(priv->epriv.eth, priv->smi_addr, 0x1f, reg >> 6); |
| if (ret) |
| return ret; |
| |
| /* Write low word */ |
| ret = mtk_mii_write(priv->epriv.eth, priv->smi_addr, (reg >> 2) & 0xf, |
| data & 0xffff); |
| if (ret) |
| return ret; |
| |
| /* Write high word */ |
| return mtk_mii_write(priv->epriv.eth, priv->smi_addr, 0x10, data >> 16); |
| } |
| |
| int mt753x_reg_read(struct mt753x_switch_priv *priv, u32 reg, u32 *data) |
| { |
| return priv->reg_read(priv, reg, data); |
| } |
| |
| int mt753x_reg_write(struct mt753x_switch_priv *priv, u32 reg, u32 data) |
| { |
| return priv->reg_write(priv, reg, data); |
| } |
| |
| void mt753x_reg_rmw(struct mt753x_switch_priv *priv, u32 reg, u32 clr, u32 set) |
| { |
| u32 val; |
| |
| priv->reg_read(priv, reg, &val); |
| val &= ~clr; |
| val |= set; |
| priv->reg_write(priv, reg, val); |
| } |
| |
| /* Indirect MDIO clause 22/45 access */ |
| static int mt7531_mii_rw(struct mt753x_switch_priv *priv, int phy, int reg, |
| u16 data, u32 cmd, u32 st) |
| { |
| u32 val, timeout_ms; |
| ulong timeout; |
| int ret = 0; |
| |
| val = (st << MDIO_ST_S) | |
| ((cmd << MDIO_CMD_S) & MDIO_CMD_M) | |
| ((phy << MDIO_PHY_ADDR_S) & MDIO_PHY_ADDR_M) | |
| ((reg << MDIO_REG_ADDR_S) & MDIO_REG_ADDR_M); |
| |
| if (cmd == MDIO_CMD_WRITE || cmd == MDIO_CMD_ADDR) |
| val |= data & MDIO_RW_DATA_M; |
| |
| mt753x_reg_write(priv, MT7531_PHY_IAC, val | PHY_ACS_ST); |
| |
| timeout_ms = 100; |
| timeout = get_timer(0); |
| while (1) { |
| mt753x_reg_read(priv, MT7531_PHY_IAC, &val); |
| |
| if ((val & PHY_ACS_ST) == 0) |
| break; |
| |
| if (get_timer(timeout) > timeout_ms) |
| return -ETIMEDOUT; |
| } |
| |
| if (cmd == MDIO_CMD_READ || cmd == MDIO_CMD_READ_C45) { |
| mt753x_reg_read(priv, MT7531_PHY_IAC, &val); |
| ret = val & MDIO_RW_DATA_M; |
| } |
| |
| return ret; |
| } |
| |
| int mt7531_mii_read(struct mt753x_switch_priv *priv, u8 phy, u8 reg) |
| { |
| u8 phy_addr; |
| |
| if (phy >= MT753X_NUM_PHYS) |
| return -EINVAL; |
| |
| phy_addr = MT753X_PHY_ADDR(priv->phy_base, phy); |
| |
| return mt7531_mii_rw(priv, phy_addr, reg, 0, MDIO_CMD_READ, |
| MDIO_ST_C22); |
| } |
| |
| int mt7531_mii_write(struct mt753x_switch_priv *priv, u8 phy, u8 reg, u16 val) |
| { |
| u8 phy_addr; |
| |
| if (phy >= MT753X_NUM_PHYS) |
| return -EINVAL; |
| |
| phy_addr = MT753X_PHY_ADDR(priv->phy_base, phy); |
| |
| return mt7531_mii_rw(priv, phy_addr, reg, val, MDIO_CMD_WRITE, |
| MDIO_ST_C22); |
| } |
| |
| int mt7531_mmd_read(struct mt753x_switch_priv *priv, u8 addr, u8 devad, |
| u16 reg) |
| { |
| u8 phy_addr; |
| int ret; |
| |
| if (addr >= MT753X_NUM_PHYS) |
| return -EINVAL; |
| |
| phy_addr = MT753X_PHY_ADDR(priv->phy_base, addr); |
| |
| ret = mt7531_mii_rw(priv, phy_addr, devad, reg, MDIO_CMD_ADDR, |
| MDIO_ST_C45); |
| if (ret) |
| return ret; |
| |
| return mt7531_mii_rw(priv, phy_addr, devad, 0, MDIO_CMD_READ_C45, |
| MDIO_ST_C45); |
| } |
| |
| int mt7531_mmd_write(struct mt753x_switch_priv *priv, u8 addr, u8 devad, |
| u16 reg, u16 val) |
| { |
| u8 phy_addr; |
| int ret; |
| |
| if (addr >= MT753X_NUM_PHYS) |
| return 0; |
| |
| phy_addr = MT753X_PHY_ADDR(priv->phy_base, addr); |
| |
| ret = mt7531_mii_rw(priv, phy_addr, devad, reg, MDIO_CMD_ADDR, |
| MDIO_ST_C45); |
| if (ret) |
| return ret; |
| |
| return mt7531_mii_rw(priv, phy_addr, devad, val, MDIO_CMD_WRITE, |
| MDIO_ST_C45); |
| } |
| |
| static int mt7531_mdio_read(struct mii_dev *bus, int addr, int devad, int reg) |
| { |
| struct mt753x_switch_priv *priv = bus->priv; |
| |
| if (devad < 0) |
| return mt7531_mii_read(priv, addr, reg); |
| |
| return mt7531_mmd_read(priv, addr, devad, reg); |
| } |
| |
| static int mt7531_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 mt7531_mii_write(priv, addr, reg, val); |
| |
| return mt7531_mmd_write(priv, addr, devad, reg, val); |
| } |
| |
| int mt7531_mdio_register(struct mt753x_switch_priv *priv) |
| { |
| struct mii_dev *mdio_bus = mdio_alloc(); |
| int ret; |
| |
| if (!mdio_bus) |
| return -ENOMEM; |
| |
| mdio_bus->read = mt7531_mdio_read; |
| mdio_bus->write = mt7531_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; |
| } |
| |
| void mt753x_port_isolation(struct mt753x_switch_priv *priv) |
| { |
| u32 i; |
| |
| for (i = 0; i < MT753X_NUM_PORTS; i++) { |
| /* Set port matrix mode */ |
| if (i != 6) |
| mt753x_reg_write(priv, PCR_REG(i), |
| (0x40 << PORT_MATRIX_S)); |
| else |
| mt753x_reg_write(priv, PCR_REG(i), |
| (0x3f << PORT_MATRIX_S)); |
| |
| /* Set port mode to user port */ |
| mt753x_reg_write(priv, PVC_REG(i), |
| (0x8100 << STAG_VPID_S) | |
| (VLAN_ATTR_USER << VLAN_ATTR_S)); |
| } |
| } |