summaryrefslogtreecommitdiff
path: root/drivers/net
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/net')
-rw-r--r--drivers/net/dwc_eth_qos_adi.c1
-rw-r--r--drivers/net/e1000_spi.c25
-rw-r--r--drivers/net/fm/eth.c9
-rw-r--r--drivers/net/fm/memac_phy.c4
-rw-r--r--drivers/net/fsl-mc/mc.c28
-rw-r--r--drivers/net/fsl_enetc_netc_blk_ctrl.c2
-rw-r--r--drivers/net/mdio_mux_meson_gxl.c3
-rw-r--r--drivers/net/mtk_eth/mtk_eth.c4
-rw-r--r--drivers/net/phy/Kconfig2
-rw-r--r--drivers/net/phy/Makefile1
-rw-r--r--drivers/net/phy/cortina.c9
-rw-r--r--drivers/net/phy/mediatek/Kconfig16
-rw-r--r--drivers/net/phy/mediatek/Makefile4
-rw-r--r--drivers/net/phy/mediatek/mtk-2p5ge.c627
-rw-r--r--drivers/net/phy/mediatek/mtk-phy-lib.c106
-rw-r--r--drivers/net/phy/mediatek/mtk.h103
-rw-r--r--drivers/net/ti/Kconfig1
-rw-r--r--drivers/net/ti/am65-cpsw-nuss.c77
-rw-r--r--drivers/net/ti/icssg_prueth.c2
19 files changed, 975 insertions, 49 deletions
diff --git a/drivers/net/dwc_eth_qos_adi.c b/drivers/net/dwc_eth_qos_adi.c
index 0e6a901e303..fee50a88156 100644
--- a/drivers/net/dwc_eth_qos_adi.c
+++ b/drivers/net/dwc_eth_qos_adi.c
@@ -10,6 +10,7 @@
#include <clk.h>
#include <dm.h>
+#include <env.h>
#include <net.h>
#include <phy.h>
#include <reset.h>
diff --git a/drivers/net/e1000_spi.c b/drivers/net/e1000_spi.c
index 1e830b99f1d..33d38b57824 100644
--- a/drivers/net/e1000_spi.c
+++ b/drivers/net/e1000_spi.c
@@ -472,6 +472,7 @@ static int do_e1000_spi_checksum(struct cmd_tbl *cmdtp, struct e1000_hw *hw,
uint16_t i, length, checksum = 0, checksum_reg;
uint16_t *buffer;
bool upd;
+ int ret = 0;
if (argc == 0)
upd = 0;
@@ -493,14 +494,15 @@ static int do_e1000_spi_checksum(struct cmd_tbl *cmdtp, struct e1000_hw *hw,
/* Acquire the EEPROM */
if (e1000_acquire_eeprom(hw)) {
E1000_ERR(hw, "EEPROM SPI cannot be acquired!\n");
- return 1;
+ ret = 1;
+ goto free_exit;
}
/* Read the EEPROM */
if (e1000_spi_eeprom_dump(hw, buffer, 0, length, true) < 0) {
E1000_ERR(hw, "Interrupted!\n");
- e1000_release_eeprom(hw);
- return 1;
+ ret = 1;
+ goto release_exit;
}
/* Compute the checksum and read the expected value */
@@ -513,8 +515,8 @@ static int do_e1000_spi_checksum(struct cmd_tbl *cmdtp, struct e1000_hw *hw,
if (checksum_reg == checksum) {
printf("%s: INFO: EEPROM checksum is correct! (0x%04hx)\n",
hw->name, checksum);
- e1000_release_eeprom(hw);
- return 0;
+ ret = 0;
+ goto release_exit;
}
/* Hrm, verification failed, print an error */
@@ -524,8 +526,8 @@ static int do_e1000_spi_checksum(struct cmd_tbl *cmdtp, struct e1000_hw *hw,
/* If they didn't ask us to update it, just return an error */
if (!upd) {
- e1000_release_eeprom(hw);
- return 1;
+ ret = 1;
+ goto release_exit;
}
/* Ok, correct it! */
@@ -534,12 +536,15 @@ static int do_e1000_spi_checksum(struct cmd_tbl *cmdtp, struct e1000_hw *hw,
if (e1000_spi_eeprom_program(hw, &buffer[i], i * sizeof(uint16_t),
sizeof(uint16_t), true)) {
E1000_ERR(hw, "Interrupted!\n");
- e1000_release_eeprom(hw);
- return 1;
+ ret = 1;
+ /* goto release_exit; */
}
+release_exit:
e1000_release_eeprom(hw);
- return 0;
+free_exit:
+ free(buffer);
+ return ret;
}
int do_e1000_spi(struct cmd_tbl *cmdtp, struct e1000_hw *hw,
diff --git a/drivers/net/fm/eth.c b/drivers/net/fm/eth.c
index 63fe4b2d33c..f0e7c0eca42 100644
--- a/drivers/net/fm/eth.c
+++ b/drivers/net/fm/eth.c
@@ -169,7 +169,7 @@ static void bmi_rx_port_disable(struct fm_bmi_rx_port *rx_port)
/* wait until the rx port is not busy */
while ((in_be32(&rx_port->fmbm_rst) & FMBM_RST_BSY) && timeout--)
;
- if (!timeout)
+ if (timeout == -1)
printf("%s - timeout\n", __func__);
}
@@ -199,7 +199,7 @@ static void bmi_tx_port_disable(struct fm_bmi_tx_port *tx_port)
/* wait until the tx port is not busy */
while ((in_be32(&tx_port->fmbm_tst) & FMBM_TST_BSY) && timeout--)
;
- if (!timeout)
+ if (timeout == -1)
printf("%s - timeout\n", __func__);
}
@@ -727,12 +727,15 @@ static int fm_eth_bind(struct udevice *dev)
char mac_name[11];
u32 fm, num;
+ if (!dev)
+ return -EINVAL;
+
if (ofnode_read_u32(ofnode_get_parent(dev_ofnode(dev)), "cell-index", &fm)) {
printf("FMan node property cell-index missing\n");
return -EINVAL;
}
- if (dev && dev_read_u32(dev, "cell-index", &num)) {
+ if (dev_read_u32(dev, "cell-index", &num)) {
printf("FMan MAC node property cell-index missing\n");
return -EINVAL;
}
diff --git a/drivers/net/fm/memac_phy.c b/drivers/net/fm/memac_phy.c
index 26425d94ae5..1ad3c053593 100644
--- a/drivers/net/fm/memac_phy.c
+++ b/drivers/net/fm/memac_phy.c
@@ -46,7 +46,7 @@ static int memac_wait_until_free(struct memac_mdio_controller *regs)
while ((memac_in_32(&regs->mdio_stat) & MDIO_STAT_BSY) && timeout--)
;
- if (!timeout) {
+ if (timeout == -1) {
printf("timeout waiting for MDIO bus to be free\n");
return -ETIMEDOUT;
}
@@ -64,7 +64,7 @@ static int memac_wait_until_done(struct memac_mdio_controller *regs)
while ((memac_in_32(&regs->mdio_stat) & MDIO_STAT_BSY) && timeout--)
;
- if (!timeout) {
+ if (timeout == -1) {
printf("timeout waiting for MDIO operation to complete\n");
return -ETIMEDOUT;
}
diff --git a/drivers/net/fsl-mc/mc.c b/drivers/net/fsl-mc/mc.c
index 86daf0fb2bb..c8ed702f50a 100644
--- a/drivers/net/fsl-mc/mc.c
+++ b/drivers/net/fsl-mc/mc.c
@@ -366,8 +366,8 @@ static int mc_fixup_dpc_mac_addr(void *blob, int dpmac_id,
noff = fdt_add_subnode(blob, nodeoffset, mac_name);
if (noff < 0) {
printf("fdt_add_subnode: err=%s\n",
- fdt_strerror(err));
- return err;
+ fdt_strerror(noff));
+ return noff;
}
/* add default property of fixed link */
@@ -1178,6 +1178,7 @@ err_get_api_ver:
dflt_dpio->dpio_id);
err_create:
free(dflt_dpio);
+ dflt_dpio = NULL;
err_calloc:
return err;
}
@@ -1186,6 +1187,9 @@ static int dpio_exit(void)
{
int err;
+ if (!dflt_dpio)
+ return -ENODEV;
+
err = dpio_disable(dflt_mc_io, MC_CMD_NO_FLAGS, dflt_dpio->dpio_handle);
if (err < 0) {
printf("dpio_disable() failed: %d\n", err);
@@ -1211,8 +1215,8 @@ static int dpio_exit(void)
printf("Exit: DPIO.%d\n", dflt_dpio->dpio_id);
#endif
- if (dflt_dpio)
- free(dflt_dpio);
+ free(dflt_dpio);
+ dflt_dpio = NULL;
return 0;
err:
@@ -1434,6 +1438,7 @@ err_close:
err_open:
err_create:
free(dflt_dpbp);
+ dflt_dpbp = NULL;
err_calloc:
return err;
}
@@ -1442,6 +1447,9 @@ static int dpbp_exit(void)
{
int err;
+ if (!dflt_dpbp)
+ return -ENODEV;
+
err = dpbp_destroy(dflt_mc_io, dflt_dprc_handle, MC_CMD_NO_FLAGS,
dflt_dpbp->dpbp_id);
if (err < 0) {
@@ -1453,8 +1461,8 @@ static int dpbp_exit(void)
printf("Exit: DPBP.%d\n", dflt_dpbp->dpbp_attr.id);
#endif
- if (dflt_dpbp)
- free(dflt_dpbp);
+ free(dflt_dpbp);
+ dflt_dpbp = NULL;
return 0;
err:
@@ -1531,6 +1539,7 @@ err_get_version:
dflt_dpni->dpni_id);
err_create:
free(dflt_dpni);
+ dflt_dpni = NULL;
err_calloc:
return err;
}
@@ -1539,6 +1548,9 @@ static int dpni_exit(void)
{
int err;
+ if (!dflt_dpni)
+ return -ENODEV;
+
err = dpni_destroy(dflt_mc_io, dflt_dprc_handle, MC_CMD_NO_FLAGS,
dflt_dpni->dpni_id);
if (err < 0) {
@@ -1550,8 +1562,8 @@ static int dpni_exit(void)
printf("Exit: DPNI.%d\n", dflt_dpni->dpni_id);
#endif
- if (dflt_dpni)
- free(dflt_dpni);
+ free(dflt_dpni);
+ dflt_dpni = NULL;
return 0;
err:
diff --git a/drivers/net/fsl_enetc_netc_blk_ctrl.c b/drivers/net/fsl_enetc_netc_blk_ctrl.c
index 46b68d3d8a4..fecd66eb15a 100644
--- a/drivers/net/fsl_enetc_netc_blk_ctrl.c
+++ b/drivers/net/fsl_enetc_netc_blk_ctrl.c
@@ -293,7 +293,7 @@ static int netc_blk_ctrl_probe(struct udevice *dev)
err = clk_prepare_enable(ipg_clk);
if (err) {
dev_err(dev, "Enable ipg clock failed\n");
- return PTR_ERR(ipg_clk);
+ return err;
}
regs = dev_read_addr_name(dev, "ierb");
diff --git a/drivers/net/mdio_mux_meson_gxl.c b/drivers/net/mdio_mux_meson_gxl.c
index 8ef3ae598b7..31898ed437e 100644
--- a/drivers/net/mdio_mux_meson_gxl.c
+++ b/drivers/net/mdio_mux_meson_gxl.c
@@ -19,6 +19,7 @@
#define REG2_LEDACT GENMASK(23, 22)
#define REG2_LEDLINK GENMASK(25, 24)
#define REG2_DIV4SEL BIT(27)
+#define REG2_REVERSED BIT(28)
#define REG2_ADCBYPASS BIT(30)
#define REG2_CLKINSEL BIT(31)
#define ETH_REG3 0x4
@@ -66,7 +67,7 @@ static int meson_gxl_enable_internal_mdio(struct mdio_mux_meson_gxl_priv *priv)
* The only constraint is that it must match the one in
* drivers/net/phy/meson-gxl.c to properly match the PHY.
*/
- writel(FIELD_PREP(REG2_PHYID, EPHY_GXL_ID),
+ writel(REG2_REVERSED | FIELD_PREP(REG2_PHYID, EPHY_GXL_ID),
priv->regs + ETH_REG2);
/* Enable the internal phy */
diff --git a/drivers/net/mtk_eth/mtk_eth.c b/drivers/net/mtk_eth/mtk_eth.c
index b172838ba3a..a35754ccd1f 100644
--- a/drivers/net/mtk_eth/mtk_eth.c
+++ b/drivers/net/mtk_eth/mtk_eth.c
@@ -100,6 +100,7 @@ struct mtk_eth_priv {
bool pn_swap;
struct phy_device *phydev;
+ ofnode phy_node;
int phy_interface;
int phy_addr;
@@ -572,6 +573,7 @@ static int mtk_phy_probe(struct udevice *dev)
if (!phydev)
return -ENODEV;
+ phydev->node = priv->phy_node;
phydev->supported &= PHY_GBIT_FEATURES;
phydev->advertising = phydev->supported;
@@ -1458,6 +1460,8 @@ static int mtk_eth_of_to_plat(struct udevice *dev)
return ret;
}
+ priv->phy_node = args.node;
+
priv->phy_addr = ofnode_read_s32_default(args.node, "reg", -1);
if (priv->phy_addr < 0) {
printf("error: phy address is not specified\n");
diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
index 21bf983056a..185c6a3156e 100644
--- a/drivers/net/phy/Kconfig
+++ b/drivers/net/phy/Kconfig
@@ -184,6 +184,8 @@ config PHY_MARVELL_10G
help
Support for the Marvell Alaska MV88X3310 and compatible PHYs.
+source "drivers/net/phy/mediatek/Kconfig"
+
config PHY_MESON_GXL
bool "Amlogic Meson GXL Internal PHY support"
diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile
index a119eb5e177..a339b8ac29d 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-y += mediatek/
obj-$(CONFIG_PHY_MICREL_KSZ8XXX) += micrel_ksz8xxx.o
obj-$(CONFIG_PHY_MICREL_KSZ90X1) += micrel_ksz90x1.o
obj-$(CONFIG_PHY_MESON_GXL) += meson-gxl.o
diff --git a/drivers/net/phy/cortina.c b/drivers/net/phy/cortina.c
index be480ecef6c..dec024844b5 100644
--- a/drivers/net/phy/cortina.c
+++ b/drivers/net/phy/cortina.c
@@ -135,6 +135,7 @@ void cs4340_upload_firmware(struct phy_device *phydev)
int i, line_cnt = 0, column_cnt = 0;
struct cortina_reg_config fw_temp;
char *addr = NULL;
+ char *to_be_freed = NULL;
ulong cortina_fw_addr = (ulong)cs4340_get_fw_addr();
#ifdef CONFIG_TFABOOT
@@ -147,6 +148,7 @@ void cs4340_upload_firmware(struct phy_device *phydev)
size_t fw_length = CONFIG_CORTINA_FW_LENGTH;
addr = malloc(CONFIG_CORTINA_FW_LENGTH);
+ to_be_freed = addr;
ret = nand_read(get_nand_dev_by_index(0),
(loff_t)cortina_fw_addr, &fw_length, (u_char *)addr);
if (ret == -EUCLEAN) {
@@ -158,6 +160,7 @@ void cs4340_upload_firmware(struct phy_device *phydev)
struct spi_flash *ucode_flash;
addr = malloc(CONFIG_CORTINA_FW_LENGTH);
+ to_be_freed = addr;
ucode_flash = spi_flash_probe(CONFIG_SF_DEFAULT_BUS, CONFIG_SF_DEFAULT_CS,
CONFIG_SF_DEFAULT_SPEED, CONFIG_SF_DEFAULT_MODE);
if (!ucode_flash) {
@@ -179,6 +182,7 @@ void cs4340_upload_firmware(struct phy_device *phydev)
puts("Failed to find MMC device for Cortina ucode\n");
} else {
addr = malloc(CONFIG_CORTINA_FW_LENGTH);
+ to_be_freed = addr;
printf("MMC read: dev # %u, block # %u, count %u ...\n",
dev, blk, cnt);
mmc_init(mmc);
@@ -199,6 +203,7 @@ void cs4340_upload_firmware(struct phy_device *phydev)
size_t fw_length = CONFIG_CORTINA_FW_LENGTH;
addr = malloc(CONFIG_CORTINA_FW_LENGTH);
+ to_be_freed = addr;
ret = nand_read(get_nand_dev_by_index(0),
(loff_t)cortina_fw_addr,
&fw_length, (u_char *)addr);
@@ -211,6 +216,7 @@ void cs4340_upload_firmware(struct phy_device *phydev)
struct spi_flash *ucode_flash;
addr = malloc(CONFIG_CORTINA_FW_LENGTH);
+ to_be_freed = addr;
ucode_flash = spi_flash_probe(CONFIG_SF_DEFAULT_BUS, CONFIG_SF_DEFAULT_CS,
CONFIG_SF_DEFAULT_SPEED, CONFIG_SF_DEFAULT_MODE);
if (!ucode_flash) {
@@ -232,6 +238,7 @@ void cs4340_upload_firmware(struct phy_device *phydev)
puts("Failed to find MMC device for Cortina ucode\n");
} else {
addr = malloc(CONFIG_CORTINA_FW_LENGTH);
+ to_be_freed = addr;
printf("MMC read: dev # %u, block # %u, count %u ...\n",
dev, blk, cnt);
mmc_init(mmc);
@@ -280,6 +287,8 @@ void cs4340_upload_firmware(struct phy_device *phydev)
0xffff;
phy_write(phydev, 0x00, fw_temp.reg_addr, fw_temp.reg_value);
}
+ if (to_be_freed)
+ free(to_be_freed);
}
#endif
diff --git a/drivers/net/phy/mediatek/Kconfig b/drivers/net/phy/mediatek/Kconfig
new file mode 100644
index 00000000000..933271f01fa
--- /dev/null
+++ b/drivers/net/phy/mediatek/Kconfig
@@ -0,0 +1,16 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+config MTK_NET_PHYLIB
+ bool
+
+config PHY_MEDIATEK_2P5GE
+ bool "MediaTek built-in 2.5Gb ethernet PHYs"
+ depends on OF_CONTROL && (TARGET_MT7987 || TARGET_MT7988)
+ select FW_LOADER
+ select MTK_NET_PHYLIB
+ help
+ Supports MediaTek SoC built-in 2.5Gb ethernet PHYs.
+
+ This driver requires firmware download for PHY to enable its
+ functionality. The board can override certian firmware downloading
+ function to provide the firmware data.
diff --git a/drivers/net/phy/mediatek/Makefile b/drivers/net/phy/mediatek/Makefile
new file mode 100644
index 00000000000..bc8dd4e878c
--- /dev/null
+++ b/drivers/net/phy/mediatek/Makefile
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0
+
+obj-$(CONFIG_MTK_NET_PHYLIB) += mtk-phy-lib.o
+obj-$(CONFIG_PHY_MEDIATEK_2P5GE) += mtk-2p5ge.o
diff --git a/drivers/net/phy/mediatek/mtk-2p5ge.c b/drivers/net/phy/mediatek/mtk-2p5ge.c
new file mode 100644
index 00000000000..4090db0b474
--- /dev/null
+++ b/drivers/net/phy/mediatek/mtk-2p5ge.c
@@ -0,0 +1,627 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2025 MediaTek Inc. All Rights Reserved.
+ *
+ * Author: Sky Huang <SkyLake.Huang@mediatek.com>
+ * Author: Weijie Gao <weijie.gao@mediatek.com>
+ */
+#include <asm/io.h>
+#include <dm/device_compat.h>
+#include <dm/of_access.h>
+#include <dm/pinctrl.h>
+#include <dm/ofnode.h>
+#include <fw_loader.h>
+#include <linux/bitfield.h>
+#include <linux/delay.h>
+#include <linux/iopoll.h>
+#include <phy.h>
+#include "mtk.h"
+
+#define MTK_2P5GPHY_ID_MT7987 0x00339c91
+#define MTK_2P5GPHY_ID_MT7988 0x00339c11
+
+#define PBUS_BASE 0x0f000000
+#define PBUS_SIZE 0x1f0024
+
+#define MTK_2P5GPHY_PMD_REG 0x010000
+#define DO_NOT_RESET (MTK_2P5GPHY_PMD_REG + 0x28)
+#define DO_NOT_RESET_XBZ BIT(0)
+#define DO_NOT_RESET_PMA BIT(3)
+#define DO_NOT_RESET_RX BIT(5)
+#define FNPLL_PWR_CTRL1 (MTK_2P5GPHY_PMD_REG + 0x208)
+#define RG_SPEED_MASK GENMASK(3, 0)
+#define RG_SPEED_2500 BIT(3)
+#define RG_SPEED_100 BIT(0)
+#define FNPLL_PWR_CTRL_STATUS (MTK_2P5GPHY_PMD_REG + 0x20c)
+#define RG_STABLE_MASK GENMASK(3, 0)
+#define RG_SPEED_2500_STABLE BIT(3)
+#define RG_SPEED_100_STABLE BIT(0)
+
+#define MTK_2P5GPHY_XBZ_PCS 0x030000
+#define PHY_CTRL_CONFIG (MTK_2P5GPHY_XBZ_PCS + 0x200)
+#define PMU_WP (MTK_2P5GPHY_XBZ_PCS + 0x800)
+#define WRITE_PROTECT_KEY 0xCAFEF00D
+#define PMU_PMA_AUTO_CFG (MTK_2P5GPHY_XBZ_PCS + 0x820)
+#define POWER_ON_AUTO_MODE BIT(16)
+#define PMU_AUTO_MODE_EN BIT(0)
+#define PMU_PMA_STATUS (MTK_2P5GPHY_XBZ_PCS + 0x840)
+#define CLK_IS_DISABLED BIT(3)
+
+#define MTK_2P5GPHY_XBZ_PMA_RX 0x080000
+#define SMEM_WDAT0 (MTK_2P5GPHY_XBZ_PMA_RX + 0x5000)
+#define SMEM_WDAT1 (MTK_2P5GPHY_XBZ_PMA_RX + 0x5004)
+#define SMEM_WDAT2 (MTK_2P5GPHY_XBZ_PMA_RX + 0x5008)
+#define SMEM_WDAT3 (MTK_2P5GPHY_XBZ_PMA_RX + 0x500c)
+#define SMEM_CTRL (MTK_2P5GPHY_XBZ_PMA_RX + 0x5024)
+#define SMEM_HW_RDATA_ZERO BIT(24)
+#define SMEM_ADDR_REF_ADDR (MTK_2P5GPHY_XBZ_PMA_RX + 0x502c)
+#define CM_CTRL_P01 (MTK_2P5GPHY_XBZ_PMA_RX + 0x5100)
+#define CM_CTRL_P23 (MTK_2P5GPHY_XBZ_PMA_RX + 0x5124)
+#define DM_CTRL_P01 (MTK_2P5GPHY_XBZ_PMA_RX + 0x5200)
+#define DM_CTRL_P23 (MTK_2P5GPHY_XBZ_PMA_RX + 0x5224)
+
+#define MTK_2P5GPHY_CHIP_SCU 0x0cf800
+#define SYS_SW_RESET (MTK_2P5GPHY_CHIP_SCU + 0x128)
+#define RESET_RST_CNT BIT(0)
+
+#define MTK_2P5GPHY_MCU_CSR 0x0f0000
+#define MD32_EN_CFG (MTK_2P5GPHY_MCU_CSR + 0x18)
+#define MD32_EN BIT(0)
+
+#define MTK_2P5GPHY_PMB_FW 0x100000
+
+#define MTK_2P5GPHY_FCM_BASE 0x0e0000
+#define FC_LWM (MTK_2P5GPHY_FCM_BASE + 0x14)
+#define TX_FC_LWM_MASK GENMASK(31, 16)
+#define MIN_IPG_NUM (MTK_2P5GPHY_FCM_BASE + 0x2c)
+#define LS_MIN_IPG_NUM_MASK GENMASK(7, 0)
+#define FIFO_CTRL (MTK_2P5GPHY_FCM_BASE + 0x40)
+#define TX_SFIFO_IDLE_CNT_MASK GENMASK(31, 28)
+#define TX_SFIFO_DEL_IPG_WM_MASK GENMASK(23, 16)
+
+#define MTK_2P5GPHY_APB_BASE 0x11c30000
+#define MTK_2P5GPHY_APB_SIZE 0x9c
+#define SW_RESET 0x94
+#define MD32_RESTART_EN_CLEAR BIT(9)
+
+/* Registers on CL22 page 0 */
+#define PHY_AUX_CTRL_STATUS 0x1d
+#define PHY_AUX_DPX_MASK GENMASK(5, 5)
+#define PHY_AUX_SPEED_MASK GENMASK(4, 2)
+
+enum {
+ PHY_AUX_SPD_10 = 0,
+ PHY_AUX_SPD_100,
+ PHY_AUX_SPD_1000,
+ PHY_AUX_SPD_2500,
+};
+
+/* Registers on MDIO_MMD_VEND1 */
+#define MTK_PHY_LINK_STATUS_RELATED 0x147
+#define MTK_PHY_BYPASS_LINK_STATUS_OK BIT(4)
+#define MTK_PHY_FORCE_LINK_STATUS_HCD BIT(3)
+
+#define MTK_PHY_AN_FORCE_SPEED_REG 0x313
+#define MTK_PHY_MASTER_FORCE_SPEED_SEL_EN BIT(7)
+#define MTK_PHY_MASTER_FORCE_SPEED_SEL_MASK GENMASK(6, 0)
+
+#define MTK_PHY_LPI_PCS_DSP_CTRL 0x121
+#define MTK_PHY_LPI_SIG_EN_LO_THRESH100_MASK GENMASK(12, 8)
+
+#define MTK_PHY_PMA_PMD_SPEED_ABILITY 0x300
+#define CAP_100X_HDX BIT(14)
+#define CAP_10T_HDX BIT(12)
+
+/* Registers on MDIO_MMD_VEND2 */
+#define MT7987_OPTIONS 0x110
+#define NORMAL_RETRAIN_DISABLE BIT(0)
+
+/* Registers on Token Ring debug nodes */
+/* ch_addr = 0x0, node_addr = 0xf, data_addr = 0x3c */
+#define AUTO_NP_10XEN BIT(6)
+
+/* Firmware file size */
+#define MT7987_2P5GE_PMB_FW_SIZE 0x18000
+#define MT7988_2P5GE_PMB_FW_SIZE 0x20000
+
+#define MT7987_2P5GE_DSPBITTB_SIZE 0x7000
+
+struct mtk_i2p5ge_fw_info {
+ u8 datecode[4];
+ u8 plat[2];
+ u8 ver[2];
+};
+
+struct mtk_i2p5ge_priv {
+ void __iomem *reg_base;
+};
+
+static inline void pbus_write(struct mtk_i2p5ge_priv *priv, u32 offset,
+ u32 val)
+{
+ writel(val, priv->reg_base + offset);
+}
+
+static inline void pbus_rmw(struct mtk_i2p5ge_priv *priv, u32 offset, u32 clr,
+ u32 set)
+{
+ clrsetbits_le32(priv->reg_base + offset, clr, set);
+}
+
+static int mt798x_i2p5ge_download_fw(struct mtk_i2p5ge_priv *priv,
+ size_t fwsize, const void *fwdata)
+{
+ u32 __iomem *fwmem = priv->reg_base + MTK_2P5GPHY_PMB_FW;
+ const u32 *fw;
+ u32 i;
+
+ /* Assume fw data is 4-byte aligned */
+ fw = fwdata;
+
+ for (i = 0; i < (fwsize >> 2); i++)
+ writel(fw[i], &fwmem[i]);
+
+ return 0;
+}
+
+static int mt798x_i2p5ge_phy_probe(struct phy_device *phydev)
+{
+ struct mtk_i2p5ge_priv *priv;
+
+ priv = malloc(sizeof(*priv));
+ if (!priv)
+ return -ENOMEM;
+
+ priv->reg_base = ioremap(PBUS_BASE, PBUS_SIZE);
+ if (!priv->reg_base) {
+ free(priv);
+ return -ENODEV;
+ }
+
+ phydev->priv = priv;
+
+ return 0;
+}
+
+static int mt798x_i2p5ge_phy_config(struct phy_device *phydev)
+{
+
+ /* Setup LED */
+ phy_set_bits_mmd(phydev, MDIO_MMD_VEND2, MTK_PHY_LED0_ON_CTRL,
+ MTK_PHY_LED_ON_LINK10 |
+ MTK_PHY_LED_ON_LINK100 |
+ MTK_PHY_LED_ON_LINK1000 |
+ MTK_PHY_LED_ON_LINK2500);
+ phy_set_bits_mmd(phydev, MDIO_MMD_VEND2, MTK_PHY_LED1_ON_CTRL,
+ MTK_PHY_LED_ON_FDX | MTK_PHY_LED_ON_HDX);
+
+ /* Switch pinctrl after setting polarity to avoid bogus blinking */
+ pinctrl_select_state(phydev->dev, "i2p5gbe-led");
+
+ phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_LPI_PCS_DSP_CTRL,
+ MTK_PHY_LPI_SIG_EN_LO_THRESH100_MASK, 0);
+
+ /* Enable 16-bit next page exchange bit if 1000-BT isn't advertising */
+ mtk_tr_modify(phydev, 0x0, 0xf, 0x3c, AUTO_NP_10XEN,
+ FIELD_PREP(AUTO_NP_10XEN, 0x1));
+
+ /* Set HW auto downshift */
+ mtk_phy_select_page(phydev, MTK_PHY_PAGE_EXTENDED_1);
+ phy_set_bits_mmd(phydev, MDIO_MMD_VEND1,
+ MTK_PHY_AUX_CTRL_AND_STATUS,
+ MTK_PHY_ENABLE_DOWNSHIFT);
+ mtk_phy_restore_page(phydev);
+
+ /* Configure parallel detction functionality */
+ phy_clear_bits_mmd(phydev, MDIO_MMD_VEND1,
+ MTK_PHY_PMA_PMD_SPEED_ABILITY,
+ CAP_10T_HDX | CAP_100X_HDX);
+ phy_clear_bits_mmd(phydev, MDIO_DEVAD_NONE,
+ MII_ADVERTISE,
+ ADVERTISE_10HALF | ADVERTISE_100HALF);
+
+ return 0;
+}
+
+static void mt798x_i2p5ge_print_fw_info(const void *fwdata, size_t fwsize)
+{
+ const struct mtk_i2p5ge_fw_info *info;
+ u8 ver_minor, ver_patch;
+
+ info = (const void *)((uintptr_t)fwdata + fwsize - sizeof(*info));
+
+ ver_minor = info->ver[1] >> 4;
+ ver_patch = info->ver[1] & 0xf;
+
+ printf("Firmware loaded, date %02x%02x/%02x/%02x, ver %x.%u.%u\n",
+ info->datecode[0], info->datecode[1], info->datecode[2],
+ info->datecode[3], info->ver[0], ver_minor, ver_patch);
+}
+
+int __weak mt7987_i2p5ge_get_fw(void **fw, size_t *fwsize,
+ void **dspfw, size_t *dspfwsize)
+{
+ void *pmb, *dsp;
+ int ret;
+
+ pmb = malloc(MT7987_2P5GE_PMB_FW_SIZE);
+ if (!pmb)
+ return -ENOMEM;
+
+ ret = request_firmware_into_buf_via_script(
+ pmb, MT7987_2P5GE_PMB_FW_SIZE,
+ "mt7987_i2p5ge_load_pmb_firmware", fwsize);
+ if (ret) {
+ free(pmb);
+ return ret;
+ }
+
+ dsp = malloc(MT7987_2P5GE_DSPBITTB_SIZE);
+ if (!dsp) {
+ free(pmb);
+ return -ENOMEM;
+ }
+
+ ret = request_firmware_into_buf_via_script(
+ dsp, MT7987_2P5GE_DSPBITTB_SIZE,
+ "mt7987_i2p5ge_load_dspbit_firmware", dspfwsize);
+ if (ret) {
+ free(pmb);
+ free(dsp);
+ return ret;
+ }
+
+ *fw = pmb;
+ *dspfw = dsp;
+
+ return 1;
+}
+
+static int mt7987_i2p5ge_download_dspfw(struct mtk_i2p5ge_priv *priv,
+ const void *dspfw_data)
+{
+ const u32 *dspfw;
+ u32 i;
+
+ pbus_rmw(priv, SMEM_CTRL, 0, SMEM_HW_RDATA_ZERO);
+
+ pbus_rmw(priv, PHY_CTRL_CONFIG, 0, BIT(16));
+
+ /* Initialize data memory */
+ pbus_rmw(priv, DM_CTRL_P01, 0, BIT(28));
+ pbus_rmw(priv, DM_CTRL_P23, 0, BIT(28));
+
+ /* Initialize coefficient memory */
+ pbus_rmw(priv, CM_CTRL_P01, 0, BIT(28));
+ pbus_rmw(priv, CM_CTRL_P23, 0, BIT(28));
+
+ /* Initialize PM offset */
+ pbus_write(priv, SMEM_ADDR_REF_ADDR, 0);
+
+ /* Assume DSP bit data is 4-byte aligned */
+ dspfw = dspfw_data;
+
+ for (i = 0; i < (MT7987_2P5GE_DSPBITTB_SIZE >> 2); i += 4) {
+ pbus_write(priv, SMEM_WDAT0, dspfw[i]);
+ pbus_write(priv, SMEM_WDAT1, dspfw[i + 1]);
+ pbus_write(priv, SMEM_WDAT2, dspfw[i + 2]);
+ pbus_write(priv, SMEM_WDAT3, dspfw[i + 3]);
+ }
+
+ pbus_rmw(priv, DM_CTRL_P01, BIT(28), 0);
+ pbus_rmw(priv, DM_CTRL_P23, BIT(28), 0);
+
+ pbus_rmw(priv, CM_CTRL_P01, BIT(28), 0);
+ pbus_rmw(priv, CM_CTRL_P23, BIT(28), 0);
+
+ return 0;
+}
+
+static int mt7987_i2p5ge_phy_load_fw(struct phy_device *phydev)
+{
+ struct mtk_i2p5ge_priv *priv = phydev->priv;
+ size_t fw_size, dspfw_size;
+ void __iomem *apb_base;
+ void *fw, *dspfw;
+ int ret, fwrc;
+ u32 reg;
+
+ apb_base = ioremap(MTK_2P5GPHY_APB_BASE, MTK_2P5GPHY_APB_SIZE);
+ if (!apb_base)
+ return -ENODEV;
+
+ fwrc = mt7987_i2p5ge_get_fw(&fw, &fw_size, &dspfw, &dspfw_size);
+ if (fwrc < 0) {
+ dev_err(phydev->dev, "Failed to get firmware data\n");
+ return -EINVAL;
+ }
+
+ if (fw_size != MT7987_2P5GE_PMB_FW_SIZE) {
+ dev_err(phydev->dev,
+ "PMB firmware size mismatch (0x%zx != 0x%x)\n",
+ fw_size, MT7987_2P5GE_PMB_FW_SIZE);
+ ret = -EINVAL;
+ goto cleanup;
+ }
+
+ if (dspfw_size != MT7987_2P5GE_DSPBITTB_SIZE) {
+ dev_err(phydev->dev,
+ "DSP code size mismatch (0x%zx != 0x%x)\n",
+ dspfw_size, MT7987_2P5GE_DSPBITTB_SIZE);
+ ret = -EINVAL;
+ goto cleanup;
+ }
+
+ /* Force 2.5Gphy back to AN state */
+ phy_set_bits_mmd(phydev, MDIO_DEVAD_NONE, MII_BMCR, BMCR_RESET);
+ mdelay(5);
+ phy_set_bits_mmd(phydev, MDIO_DEVAD_NONE, MII_BMCR, BMCR_PDOWN);
+
+ clrbits_le16(apb_base + SW_RESET, MD32_RESTART_EN_CLEAR);
+ setbits_le16(apb_base + SW_RESET, MD32_RESTART_EN_CLEAR);
+ clrbits_le16(apb_base + SW_RESET, MD32_RESTART_EN_CLEAR);
+
+ pbus_rmw(priv, MD32_EN_CFG, MD32_EN, 0);
+
+ ret = mt798x_i2p5ge_download_fw(priv, MT7987_2P5GE_PMB_FW_SIZE, fw);
+ if (ret)
+ goto cleanup;
+
+ /* Enable 100Mbps module clock. */
+ pbus_rmw(priv, FNPLL_PWR_CTRL1, RG_SPEED_MASK, RG_SPEED_100);
+
+ /* Check if 100Mbps module clock is ready. */
+ ret = readl_poll_timeout(priv->reg_base + FNPLL_PWR_CTRL_STATUS, reg,
+ reg & RG_SPEED_100_STABLE, 10000);
+ if (ret) {
+ dev_err(phydev->dev,
+ "Timed out enabling 100Mbps module clock\n");
+ }
+
+ /* Enable 2.5Gbps module clock. */
+ pbus_rmw(priv, FNPLL_PWR_CTRL1, RG_SPEED_MASK, RG_SPEED_2500);
+
+ /* Check if 2.5Gbps module clock is ready. */
+ ret = readl_poll_timeout(priv->reg_base + FNPLL_PWR_CTRL_STATUS, reg,
+ reg & RG_SPEED_2500_STABLE, 10000);
+
+ if (ret) {
+ dev_err(phydev->dev,
+ "Timed out enabling 2.5Gbps module clock\n");
+ }
+
+ /* Disable AN */
+ phy_clear_bits_mmd(phydev, MDIO_DEVAD_NONE, MII_BMCR, BMCR_ANENABLE);
+
+ /* Force to run at 2.5G speed */
+ phy_modify_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_AN_FORCE_SPEED_REG,
+ MTK_PHY_MASTER_FORCE_SPEED_SEL_MASK,
+ MTK_PHY_MASTER_FORCE_SPEED_SEL_EN |
+ FIELD_PREP(MTK_PHY_MASTER_FORCE_SPEED_SEL_MASK, 0x1b));
+
+ phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, MTK_PHY_LINK_STATUS_RELATED,
+ MTK_PHY_BYPASS_LINK_STATUS_OK |
+ MTK_PHY_FORCE_LINK_STATUS_HCD);
+
+ /* Set xbz, pma and rx as "do not reset" in order to input DSP code. */
+ pbus_rmw(priv, DO_NOT_RESET, 0,
+ DO_NOT_RESET_XBZ | DO_NOT_RESET_PMA | DO_NOT_RESET_RX);
+
+ pbus_rmw(priv, SYS_SW_RESET, RESET_RST_CNT, 0);
+
+ pbus_write(priv, PMU_WP, WRITE_PROTECT_KEY);
+
+ pbus_rmw(priv, PMU_PMA_AUTO_CFG, 0,
+ PMU_AUTO_MODE_EN | POWER_ON_AUTO_MODE);
+
+ /* Check if clock in auto mode is disabled. */
+ ret = readl_poll_timeout(priv->reg_base + PMU_PMA_STATUS, reg,
+ (reg & CLK_IS_DISABLED) == 0x0, 100000);
+ if (ret)
+ dev_err(phydev->dev, "Timed out enabling clock auto mode\n");
+
+ ret = mt7987_i2p5ge_download_dspfw(priv, dspfw);
+ if (ret)
+ goto cleanup;
+
+ pbus_rmw(priv, MD32_EN_CFG, 0, MD32_EN);
+
+ phy_set_bits_mmd(phydev, MDIO_DEVAD_NONE, MII_BMCR, BMCR_RESET);
+
+ /* We need a delay here to stabilize initialization of MCU */
+ mdelay(8);
+
+ mt798x_i2p5ge_print_fw_info(fw, fw_size);
+
+cleanup:
+ if (fwrc > 0) {
+ free(fw);
+ free(dspfw);
+ }
+
+ iounmap(apb_base);
+
+ return ret;
+}
+
+static int mt7987_i2p5ge_phy_config(struct phy_device *phydev)
+{
+ phy_clear_bits_mmd(phydev, MDIO_MMD_VEND2, MTK_PHY_LED0_ON_CTRL,
+ MTK_PHY_LED_ON_POLARITY);
+
+ phy_clear_bits_mmd(phydev, MDIO_MMD_VEND2, MT7987_OPTIONS,
+ NORMAL_RETRAIN_DISABLE);
+
+ return mt798x_i2p5ge_phy_config(phydev);
+}
+
+static int mt7987_i2p5ge_phy_probe(struct phy_device *phydev)
+{
+ int ret;
+
+ ret = mt798x_i2p5ge_phy_probe(phydev);
+ if (ret)
+ return ret;
+
+ return mt7987_i2p5ge_phy_load_fw(phydev);
+}
+
+int __weak mt7988_i2p5ge_get_fw(void **fw, size_t *size)
+{
+ void *pmb;
+ int ret;
+
+ pmb = malloc(MT7988_2P5GE_PMB_FW_SIZE);
+ if (!pmb)
+ return -ENOMEM;
+
+ ret = request_firmware_into_buf_via_script(
+ pmb, MT7988_2P5GE_PMB_FW_SIZE,
+ "mt7988_i2p5ge_load_pmb_firmware", size);
+ if (ret) {
+ free(pmb);
+ return ret;
+ }
+
+ *fw = pmb;
+
+ return 1;
+}
+
+static int mt7988_i2p5ge_phy_load_fw(struct phy_device *phydev)
+{
+ struct mtk_i2p5ge_priv *priv = phydev->priv;
+ size_t fw_size;
+ int ret, fwrc;
+ void *fw;
+
+ fwrc = mt7988_i2p5ge_get_fw(&fw, &fw_size);
+ if (fwrc < 0) {
+ dev_err(phydev->dev, "Failed to get firmware data\n");
+ return -EINVAL;
+ }
+
+ if (fw_size != MT7988_2P5GE_PMB_FW_SIZE) {
+ dev_err(phydev->dev, "Firmware size mismatch (0x%zx != 0x%x)\n",
+ fw_size, MT7988_2P5GE_PMB_FW_SIZE);
+ ret = -EINVAL;
+ goto cleanup;
+ }
+
+ ret = mt798x_i2p5ge_download_fw(priv, MT7988_2P5GE_PMB_FW_SIZE, fw);
+ if (ret)
+ goto cleanup;
+
+ pbus_rmw(priv, MD32_EN_CFG, MD32_EN, 0);
+ pbus_rmw(priv, MD32_EN_CFG, 0, MD32_EN);
+
+ phy_set_bits_mmd(phydev, MDIO_DEVAD_NONE, MII_BMCR, BMCR_RESET);
+
+ /* We need a delay here to stabilize initialization of MCU */
+ mdelay(8);
+
+ mt798x_i2p5ge_print_fw_info(fw, fw_size);
+
+cleanup:
+ if (fwrc > 0)
+ free(fw);
+
+ return ret;
+}
+
+static int mt7988_i2p5ge_phy_config(struct phy_device *phydev)
+{
+ phy_set_bits_mmd(phydev, MDIO_MMD_VEND2, MTK_PHY_LED0_ON_CTRL,
+ MTK_PHY_LED_ON_POLARITY);
+
+ return mt798x_i2p5ge_phy_config(phydev);
+}
+
+static int mt7988_i2p5ge_phy_probe(struct phy_device *phydev)
+{
+ int ret;
+
+ ret = mt798x_i2p5ge_phy_probe(phydev);
+ if (ret)
+ return ret;
+
+ return mt7988_i2p5ge_phy_load_fw(phydev);
+}
+
+static int mt798x_i2p5ge_phy_startup(struct phy_device *phydev)
+{
+ struct mtk_i2p5ge_priv *priv = phydev->priv;
+ int ret;
+
+ ret = genphy_update_link(phydev);
+ if (ret)
+ return ret;
+
+ /* Initialize speed/duplex */
+ phydev->speed = SPEED_10;
+ phydev->duplex = DUPLEX_HALF;
+
+ if (phydev->link) {
+ ret = phy_read(phydev, MDIO_DEVAD_NONE, PHY_AUX_CTRL_STATUS);
+ if (ret < 0)
+ return ret;
+
+ switch (FIELD_GET(PHY_AUX_SPEED_MASK, ret)) {
+ case PHY_AUX_SPD_10:
+ phydev->speed = SPEED_10;
+ break;
+
+ case PHY_AUX_SPD_100:
+ phydev->speed = SPEED_100;
+ break;
+
+ case PHY_AUX_SPD_1000:
+ pbus_rmw(priv, FIFO_CTRL,
+ TX_SFIFO_IDLE_CNT_MASK | TX_SFIFO_DEL_IPG_WM_MASK,
+ FIELD_PREP(TX_SFIFO_IDLE_CNT_MASK, 0x1) |
+ FIELD_PREP(TX_SFIFO_DEL_IPG_WM_MASK, 0x10));
+ pbus_rmw(priv, MIN_IPG_NUM, LS_MIN_IPG_NUM_MASK,
+ FIELD_PREP(LS_MIN_IPG_NUM_MASK, 0xa));
+ pbus_rmw(priv, FC_LWM, TX_FC_LWM_MASK,
+ FIELD_PREP(TX_FC_LWM_MASK, 0x340));
+ phydev->speed = SPEED_1000;
+ break;
+
+ case PHY_AUX_SPD_2500:
+ phydev->speed = SPEED_2500;
+ break;
+
+ default:
+ break;
+ }
+
+ /* This PHY always operates in full duplex */
+ phydev->duplex = DUPLEX_FULL;
+ }
+
+ return 0;
+}
+
+U_BOOT_PHY_DRIVER(mt7987_i2p5ge) = {
+ .name = "MediaTek MT7987 built-in 2.5GbE PHY",
+ .uid = MTK_2P5GPHY_ID_MT7987,
+ .mask = 0xfffffff0,
+ .features = PHY_10G_FEATURES,
+ .mmds = (MDIO_MMD_PCS | MDIO_MMD_AN | MDIO_MMD_VEND1 | MDIO_MMD_VEND2),
+ .probe = &mt7987_i2p5ge_phy_probe,
+ .config = &mt7987_i2p5ge_phy_config,
+ .startup = &mt798x_i2p5ge_phy_startup,
+ .shutdown = &genphy_shutdown,
+};
+
+U_BOOT_PHY_DRIVER(mt7988_i2p5ge) = {
+ .name = "MediaTek MT7988 built-in 2.5GbE PHY",
+ .uid = MTK_2P5GPHY_ID_MT7988,
+ .mask = 0xfffffff0,
+ .features = PHY_10G_FEATURES,
+ .mmds = (MDIO_MMD_PCS | MDIO_MMD_AN | MDIO_MMD_VEND1 | MDIO_MMD_VEND2),
+ .probe = &mt7988_i2p5ge_phy_probe,
+ .config = &mt7988_i2p5ge_phy_config,
+ .startup = &mt798x_i2p5ge_phy_startup,
+ .shutdown = &genphy_shutdown,
+};
diff --git a/drivers/net/phy/mediatek/mtk-phy-lib.c b/drivers/net/phy/mediatek/mtk-phy-lib.c
new file mode 100644
index 00000000000..55e7a6b6eec
--- /dev/null
+++ b/drivers/net/phy/mediatek/mtk-phy-lib.c
@@ -0,0 +1,106 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2025 MediaTek Inc. All Rights Reserved.
+ *
+ * Author: Sky Huang <SkyLake.Huang@mediatek.com>
+ */
+#include <dm/device_compat.h>
+#include <phy.h>
+
+#include "mtk.h"
+
+void mtk_phy_select_page(struct phy_device *phydev, int page)
+{
+ phy_write(phydev, MDIO_DEVAD_NONE, MTK_EXT_PAGE_ACCESS, page);
+}
+
+void mtk_phy_restore_page(struct phy_device *phydev)
+{
+ phy_write(phydev, MDIO_DEVAD_NONE, MTK_EXT_PAGE_ACCESS,
+ MTK_PHY_PAGE_STANDARD);
+}
+
+/* Difference between functions with mtk_tr* and __mtk_tr* prefixes is
+ * mtk_tr* functions: wrapped by page switching operations
+ * __mtk_tr* functions: no page switching operations
+ */
+static void __mtk_tr_access(struct phy_device *phydev, bool read, u8 ch_addr,
+ u8 node_addr, u8 data_addr)
+{
+ u16 tr_cmd = BIT(15); /* bit 14 & 0 are reserved */
+
+ if (read)
+ tr_cmd |= BIT(13);
+
+ tr_cmd |= (((ch_addr & 0x3) << 11) |
+ ((node_addr & 0xf) << 7) |
+ ((data_addr & 0x3f) << 1));
+ dev_dbg(phydev->dev, "tr_cmd: 0x%x\n", tr_cmd);
+ phy_write(phydev, MDIO_DEVAD_NONE, 0x10, tr_cmd);
+}
+
+static void __mtk_tr_read(struct phy_device *phydev, u8 ch_addr, u8 node_addr,
+ u8 data_addr, u16 *tr_high, u16 *tr_low)
+{
+ __mtk_tr_access(phydev, true, ch_addr, node_addr, data_addr);
+ *tr_low = phy_read(phydev, MDIO_DEVAD_NONE, 0x11);
+ *tr_high = phy_read(phydev, MDIO_DEVAD_NONE, 0x12);
+ dev_dbg(phydev->dev, "tr_high read: 0x%x, tr_low read: 0x%x\n",
+ *tr_high, *tr_low);
+}
+
+u32 mtk_tr_read(struct phy_device *phydev, u8 ch_addr, u8 node_addr,
+ u8 data_addr)
+{
+ u16 tr_high;
+ u16 tr_low;
+
+ mtk_phy_select_page(phydev, MTK_PHY_PAGE_EXTENDED_52B5);
+ __mtk_tr_read(phydev, ch_addr, node_addr, data_addr, &tr_high, &tr_low);
+ mtk_phy_restore_page(phydev);
+
+ return (tr_high << 16) | tr_low;
+}
+
+static void __mtk_tr_write(struct phy_device *phydev, u8 ch_addr, u8 node_addr,
+ u8 data_addr, u32 tr_data)
+{
+ phy_write(phydev, MDIO_DEVAD_NONE, 0x11, tr_data & 0xffff);
+ phy_write(phydev, MDIO_DEVAD_NONE, 0x12, tr_data >> 16);
+ dev_dbg(phydev->dev, "tr_high write: 0x%x, tr_low write: 0x%x\n",
+ tr_data >> 16, tr_data & 0xffff);
+ __mtk_tr_access(phydev, false, ch_addr, node_addr, data_addr);
+}
+
+void __mtk_tr_modify(struct phy_device *phydev, u8 ch_addr, u8 node_addr,
+ u8 data_addr, u32 mask, u32 set)
+{
+ u32 tr_data;
+ u16 tr_high;
+ u16 tr_low;
+
+ __mtk_tr_read(phydev, ch_addr, node_addr, data_addr, &tr_high, &tr_low);
+ tr_data = (tr_high << 16) | tr_low;
+ tr_data = (tr_data & ~mask) | set;
+ __mtk_tr_write(phydev, ch_addr, node_addr, data_addr, tr_data);
+}
+
+void mtk_tr_modify(struct phy_device *phydev, u8 ch_addr, u8 node_addr,
+ u8 data_addr, u32 mask, u32 set)
+{
+ mtk_phy_select_page(phydev, MTK_PHY_PAGE_EXTENDED_52B5);
+ __mtk_tr_modify(phydev, ch_addr, node_addr, data_addr, mask, set);
+ mtk_phy_restore_page(phydev);
+}
+
+void __mtk_tr_set_bits(struct phy_device *phydev, u8 ch_addr, u8 node_addr,
+ u8 data_addr, u32 set)
+{
+ __mtk_tr_modify(phydev, ch_addr, node_addr, data_addr, 0, set);
+}
+
+void __mtk_tr_clr_bits(struct phy_device *phydev, u8 ch_addr, u8 node_addr,
+ u8 data_addr, u32 clr)
+{
+ __mtk_tr_modify(phydev, ch_addr, node_addr, data_addr, clr, 0);
+}
diff --git a/drivers/net/phy/mediatek/mtk.h b/drivers/net/phy/mediatek/mtk.h
new file mode 100644
index 00000000000..68044cbdb32
--- /dev/null
+++ b/drivers/net/phy/mediatek/mtk.h
@@ -0,0 +1,103 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2025 MediaTek Inc. All Rights Reserved.
+ *
+ * Author: Sky Huang <SkyLake.Huang@mediatek.com>
+ *
+ * Common definition for Mediatek Ethernet PHYs
+ */
+
+#ifndef _MTK_EPHY_H_
+#define _MTK_EPHY_H_
+
+#define MTK_EXT_PAGE_ACCESS 0x1f
+#define MTK_PHY_PAGE_STANDARD 0x0000
+#define MTK_PHY_PAGE_EXTENDED_1 0x0001
+#define MTK_PHY_AUX_CTRL_AND_STATUS 0x14
+/* suprv_media_select_RefClk */
+#define MTK_PHY_LP_DETECTED_MASK GENMASK(7, 6)
+#define MTK_PHY_ENABLE_DOWNSHIFT BIT(4)
+
+#define MTK_PHY_PAGE_EXTENDED_52B5 0x52b5
+
+/* Registers on Token Ring debug nodes */
+/* ch_addr = 0x0, node_addr = 0xf, data_addr = 0x2 */
+#define AN_STATE_MASK GENMASK(22, 19)
+#define AN_STATE_SHIFT 19
+#define AN_STATE_TX_DISABLE 1
+
+/* ch_addr = 0x0, node_addr = 0xf, data_addr = 0x3c */
+#define AN_NEW_LP_CNT_LIMIT_MASK GENMASK(23, 20)
+#define AUTO_NP_10XEN BIT(6)
+
+/* Registers on MDIO_MMD_VEND1 */
+#define MTK_PHY_LINK_STATUS_MISC (0xa2)
+#define MTK_PHY_FINAL_SPEED_1000 BIT(3)
+
+/* Registers on MDIO_MMD_VEND2 */
+#define MTK_PHY_LED0_ON_CTRL 0x24
+#define MTK_PHY_LED1_ON_CTRL 0x26
+#define MTK_GPHY_LED_ON_MASK GENMASK(6, 0)
+#define MTK_2P5GPHY_LED_ON_MASK GENMASK(7, 0)
+#define MTK_PHY_LED_ON_LINK1000 BIT(0)
+#define MTK_PHY_LED_ON_LINK100 BIT(1)
+#define MTK_PHY_LED_ON_LINK10 BIT(2)
+#define MTK_PHY_LED_ON_LINKDOWN BIT(3)
+#define MTK_PHY_LED_ON_FDX BIT(4) /* Full duplex */
+#define MTK_PHY_LED_ON_HDX BIT(5) /* Half duplex */
+#define MTK_PHY_LED_ON_FORCE_ON BIT(6)
+#define MTK_PHY_LED_ON_LINK2500 BIT(7)
+#define MTK_PHY_LED_ON_POLARITY BIT(14)
+#define MTK_PHY_LED_ON_ENABLE BIT(15)
+
+#define MTK_PHY_LED0_BLINK_CTRL 0x25
+#define MTK_PHY_LED1_BLINK_CTRL 0x27
+#define MTK_PHY_LED_BLINK_1000TX BIT(0)
+#define MTK_PHY_LED_BLINK_1000RX BIT(1)
+#define MTK_PHY_LED_BLINK_100TX BIT(2)
+#define MTK_PHY_LED_BLINK_100RX BIT(3)
+#define MTK_PHY_LED_BLINK_10TX BIT(4)
+#define MTK_PHY_LED_BLINK_10RX BIT(5)
+#define MTK_PHY_LED_BLINK_COLLISION BIT(6)
+#define MTK_PHY_LED_BLINK_RX_CRC_ERR BIT(7)
+#define MTK_PHY_LED_BLINK_RX_IDLE_ERR BIT(8)
+#define MTK_PHY_LED_BLINK_FORCE_BLINK BIT(9)
+#define MTK_PHY_LED_BLINK_2500TX BIT(10)
+#define MTK_PHY_LED_BLINK_2500RX BIT(11)
+
+#define MTK_GPHY_LED_ON_SET (MTK_PHY_LED_ON_LINK1000 | \
+ MTK_PHY_LED_ON_LINK100 | \
+ MTK_PHY_LED_ON_LINK10)
+#define MTK_GPHY_LED_RX_BLINK_SET (MTK_PHY_LED_BLINK_1000RX | \
+ MTK_PHY_LED_BLINK_100RX | \
+ MTK_PHY_LED_BLINK_10RX)
+#define MTK_GPHY_LED_TX_BLINK_SET (MTK_PHY_LED_BLINK_1000RX | \
+ MTK_PHY_LED_BLINK_100RX | \
+ MTK_PHY_LED_BLINK_10RX)
+
+#define MTK_2P5GPHY_LED_ON_SET (MTK_PHY_LED_ON_LINK2500 | \
+ MTK_GPHY_LED_ON_SET)
+#define MTK_2P5GPHY_LED_RX_BLINK_SET (MTK_PHY_LED_BLINK_2500RX | \
+ MTK_GPHY_LED_RX_BLINK_SET)
+#define MTK_2P5GPHY_LED_TX_BLINK_SET (MTK_PHY_LED_BLINK_2500RX | \
+ MTK_GPHY_LED_TX_BLINK_SET)
+
+#define MTK_PHY_LED_STATE_FORCE_ON 0
+#define MTK_PHY_LED_STATE_FORCE_BLINK 1
+#define MTK_PHY_LED_STATE_NETDEV 2
+
+void mtk_phy_select_page(struct phy_device *phydev, int page);
+void mtk_phy_restore_page(struct phy_device *phydev);
+
+u32 mtk_tr_read(struct phy_device *phydev, u8 ch_addr, u8 node_addr,
+ u8 data_addr);
+void __mtk_tr_modify(struct phy_device *phydev, u8 ch_addr, u8 node_addr,
+ u8 data_addr, u32 mask, u32 set);
+void mtk_tr_modify(struct phy_device *phydev, u8 ch_addr, u8 node_addr,
+ u8 data_addr, u32 mask, u32 set);
+void __mtk_tr_set_bits(struct phy_device *phydev, u8 ch_addr, u8 node_addr,
+ u8 data_addr, u32 set);
+void __mtk_tr_clr_bits(struct phy_device *phydev, u8 ch_addr, u8 node_addr,
+ u8 data_addr, u32 clr);
+
+#endif /* _MTK_EPHY_H_ */
diff --git a/drivers/net/ti/Kconfig b/drivers/net/ti/Kconfig
index 52267339de0..93c3a0c35f2 100644
--- a/drivers/net/ti/Kconfig
+++ b/drivers/net/ti/Kconfig
@@ -49,6 +49,7 @@ config TI_AM65_CPSW_NUSS
imply MISC
imply SYSCON
imply MDIO_TI_CPSW
+ imply SPL_SYSCON
select PHYLIB
help
This driver supports TI K3 MCU CPSW Nuss Ethernet controller
diff --git a/drivers/net/ti/am65-cpsw-nuss.c b/drivers/net/ti/am65-cpsw-nuss.c
index 9b69f36d04d..7a88f76fd09 100644
--- a/drivers/net/ti/am65-cpsw-nuss.c
+++ b/drivers/net/ti/am65-cpsw-nuss.c
@@ -234,14 +234,11 @@ out:
#define AM65_GMII_SEL_MODE_RGMII 2
#define AM65_GMII_SEL_MODE_SGMII 3
-#define AM65_GMII_SEL_RGMII_IDMODE BIT(4)
-
static int am65_cpsw_gmii_sel_k3(struct am65_cpsw_priv *priv,
phy_interface_t phy_mode)
{
struct udevice *dev = priv->dev;
u32 offset, reg, phandle;
- bool rgmii_id = false;
fdt_addr_t gmii_sel;
u32 mode = 0;
ofnode node;
@@ -278,12 +275,6 @@ static int am65_cpsw_gmii_sel_k3(struct am65_cpsw_priv *priv,
mode = AM65_GMII_SEL_MODE_RGMII;
break;
- case PHY_INTERFACE_MODE_RGMII_ID:
- case PHY_INTERFACE_MODE_RGMII_TXID:
- mode = AM65_GMII_SEL_MODE_RGMII;
- rgmii_id = true;
- break;
-
case PHY_INTERFACE_MODE_SGMII:
mode = AM65_GMII_SEL_MODE_SGMII;
break;
@@ -298,9 +289,6 @@ static int am65_cpsw_gmii_sel_k3(struct am65_cpsw_priv *priv,
break;
};
- if (rgmii_id)
- mode |= AM65_GMII_SEL_RGMII_IDMODE;
-
reg = mode;
dev_dbg(dev, "gmii_sel PHY mode: %u, new gmii_sel: %08x\n",
phy_mode, reg);
@@ -628,9 +616,9 @@ static int am65_cpsw_phy_init(struct udevice *dev)
struct eth_pdata *pdata = dev_get_plat(dev);
struct phy_device *phydev;
u32 supported = PHY_GBIT_FEATURES;
- int ret;
+ int ret = 0;
- phydev = dm_eth_phy_connect(dev);
+ phydev = dm_eth_phy_connect_interface(dev, pdata->phy_interface);
if (!phydev) {
dev_err(dev, "phy_connect() failed\n");
return -ENODEV;
@@ -657,9 +645,28 @@ static int am65_cpsw_ofdata_parse_phy(struct udevice *dev)
dev_read_u32(dev, "reg", &priv->port_id);
pdata->phy_interface = dev_read_phy_mode(dev);
- if (pdata->phy_interface == PHY_INTERFACE_MODE_NA) {
+
+ /* CPSW controllers supported by this driver have a fixed internal TX
+ * delay in RGMII mode. Fix up PHY mode to account for this and warn
+ * about Device Trees that claim to have a TX delay on the PCB.
+ */
+ switch (pdata->phy_interface) {
+ case PHY_INTERFACE_MODE_RGMII_ID:
+ pdata->phy_interface = PHY_INTERFACE_MODE_RGMII_RXID;
+ break;
+ case PHY_INTERFACE_MODE_RGMII_TXID:
+ pdata->phy_interface = PHY_INTERFACE_MODE_RGMII;
+ break;
+ case PHY_INTERFACE_MODE_RGMII:
+ case PHY_INTERFACE_MODE_RGMII_RXID:
+ dev_warn(dev,
+ "RGMII mode without internal TX delay unsupported; please fix your Device Tree\n");
+ break;
+ case PHY_INTERFACE_MODE_NA:
dev_err(dev, "Invalid PHY mode, port %u\n", priv->port_id);
return -EINVAL;
+ default:
+ break;
}
dev_read_u32(dev, "max-speed", (u32 *)&pdata->max_speed);
@@ -705,7 +712,6 @@ static int am65_cpsw_probe_nuss(struct udevice *dev)
struct am65_cpsw_common *cpsw_common = dev_get_priv(dev);
ofnode ports_np, node;
int ret, i;
- struct udevice *port_dev;
cpsw_common->dev = dev;
cpsw_common->ss_base = dev_read_addr(dev);
@@ -732,6 +738,7 @@ static int am65_cpsw_probe_nuss(struct udevice *dev)
ports_np = dev_read_subnode(dev, "ethernet-ports");
if (!ofnode_valid(ports_np)) {
ret = -ENOENT;
+ dev_err(dev, "Invalid device tree node %d\n", ret);
goto out;
}
@@ -763,12 +770,6 @@ static int am65_cpsw_probe_nuss(struct udevice *dev)
continue;
cpsw_common->ports[port_id].disabled = disabled;
- if (disabled)
- continue;
-
- ret = device_bind_driver_to_node(dev, "am65_cpsw_nuss_port", ofnode_get_name(node), node, &port_dev);
- if (ret)
- dev_err(dev, "Failed to bind to %s node\n", ofnode_get_name(node));
}
for (i = 0; i < AM65_CPSW_CPSWNU_MAX_PORTS; i++) {
@@ -798,6 +799,37 @@ out:
return ret;
}
+static int am65_cpsw_nuss_bind(struct udevice *dev)
+{
+ struct uclass_driver *drv;
+ struct udevice *port_dev;
+ ofnode ports_np, node;
+ int ret;
+
+ drv = lists_uclass_lookup(UCLASS_ETH);
+ if (!drv) {
+ puts("Cannot find eth driver");
+ return -ENOENT;
+ }
+
+ ports_np = dev_read_subnode(dev, "ethernet-ports");
+ if (!ofnode_valid(ports_np))
+ return -ENOENT;
+
+ ofnode_for_each_subnode(node, ports_np) {
+ const char *node_name;
+
+ node_name = ofnode_get_name(node);
+
+ ret = device_bind_driver_to_node(dev, "am65_cpsw_nuss_port", node_name, node,
+ &port_dev);
+ if (ret)
+ dev_err(dev, "Failed to bind to %s node\n", node_name);
+ }
+
+ return ret;
+}
+
static const struct udevice_id am65_cpsw_nuss_ids[] = {
{ .compatible = "ti,am654-cpsw-nuss" },
{ .compatible = "ti,j721e-cpsw-nuss" },
@@ -809,6 +841,7 @@ U_BOOT_DRIVER(am65_cpsw_nuss) = {
.name = "am65_cpsw_nuss",
.id = UCLASS_MISC,
.of_match = am65_cpsw_nuss_ids,
+ .bind = am65_cpsw_nuss_bind,
.probe = am65_cpsw_probe_nuss,
.priv_auto = sizeof(struct am65_cpsw_common),
};
diff --git a/drivers/net/ti/icssg_prueth.c b/drivers/net/ti/icssg_prueth.c
index d8df3c9afb0..12a162b9d68 100644
--- a/drivers/net/ti/icssg_prueth.c
+++ b/drivers/net/ti/icssg_prueth.c
@@ -647,8 +647,6 @@ static int prueth_probe(struct udevice *dev)
return -EINVAL;
}
- if (port_id < 0)
- continue;
if (disabled)
continue;