summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
authorTom Rini <[email protected]>2026-06-11 07:56:19 -0600
committerTom Rini <[email protected]>2026-06-11 07:56:19 -0600
commitd5dd2ebb5af37f84770f6397869fe80ff49a2030 (patch)
tree317fe08088008ba74fcc1572e4d9ecebf47d99c0 /drivers
parent3bd186835498c544a6cf1efe8d1e2bead1e233c4 (diff)
parentf30d2dbedb6ea14b9b43a2c83ae229778be6979c (diff)
Merge tag 'mediatek-for-next-2026-06-10' of https://source.denx.de/u-boot/custodians/u-boot-mediatek into next
* Network support for Genio 520/720. * Order drivers/net Makefile and Kconfig. * Refactor some common Airoha net/phy functions to a new common file. * Add new AN8801 chip support. * Add board-specific devicetree and config. * Use scnprintf() instead of snprintf() in mtk pinctrl. * Align configs for Genio EVK boards.
Diffstat (limited to 'drivers')
-rw-r--r--drivers/net/Kconfig35
-rw-r--r--drivers/net/Makefile11
-rw-r--r--drivers/net/dwc_eth_qos.c6
-rw-r--r--drivers/net/dwc_eth_qos.h2
-rw-r--r--drivers/net/dwc_eth_qos_mtk.c442
-rw-r--r--drivers/net/phy/airoha/Kconfig13
-rw-r--r--drivers/net/phy/airoha/Makefile2
-rw-r--r--drivers/net/phy/airoha/air_an8801.c594
-rw-r--r--drivers/net/phy/airoha/air_en8811.c303
-rw-r--r--drivers/net/phy/airoha/air_phy_lib.c216
-rw-r--r--drivers/net/phy/airoha/air_phy_lib.h39
-rw-r--r--drivers/pinctrl/mediatek/pinctrl-mtk-common.c10
12 files changed, 1412 insertions, 261 deletions
diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig
index f2e838b84de..5172b2bae8e 100644
--- a/drivers/net/Kconfig
+++ b/drivers/net/Kconfig
@@ -246,6 +246,20 @@ config DWC_ETH_QOS_INTEL
The Synopsys Designware Ethernet QOS IP block with the specific
configuration used in the Intel Elkhart-Lake soc.
+config DWC_ETH_QOS_MTK
+ bool "Synopsys DWC Ethernet QOS device support for MediaTek SoCs"
+ depends on DWC_ETH_QOS && ARCH_MEDIATEK
+ help
+ The Synopsys Designware Ethernet QOS IP block with the specific
+ configuration used in MediaTek SoCs.
+
+config DWC_ETH_QOS_QCOM
+ bool "Synopsys DWC Ethernet QOS device support for Qcom SoCs"
+ depends on DWC_ETH_QOS
+ help
+ The Synopsys Designware Ethernet QOS IP block with specific
+ configuration used in Qcom QCS404 SoC.
+
config DWC_ETH_QOS_ROCKCHIP
bool "Synopsys DWC Ethernet QOS device support for Rockchip SoCs"
depends on DWC_ETH_QOS && ARCH_ROCKCHIP
@@ -254,6 +268,13 @@ config DWC_ETH_QOS_ROCKCHIP
The Synopsys Designware Ethernet QOS IP block with specific
configuration used in Rockchip SoCs.
+config DWC_ETH_QOS_STARFIVE
+ bool "Synopsys DWC Ethernet QOS device support for STARFIVE"
+ depends on DWC_ETH_QOS
+ help
+ The Synopsys Designware Ethernet QOS IP block with specific
+ configuration used in STARFIVE JH7110 soc.
+
config DWC_ETH_QOS_STM32
bool "Synopsys DWC Ethernet QOS device support for STM32"
depends on DWC_ETH_QOS && ARCH_STM32MP
@@ -271,20 +292,6 @@ config DWC_ETH_QOS_TEGRA186
The Synopsys Designware Ethernet QOS IP block with specific
configuration used in NVIDIA's Tegra186 chip.
-config DWC_ETH_QOS_QCOM
- bool "Synopsys DWC Ethernet QOS device support for Qcom SoCs"
- depends on DWC_ETH_QOS
- help
- The Synopsys Designware Ethernet QOS IP block with specific
- configuration used in Qcom QCS404 SoC.
-
-config DWC_ETH_QOS_STARFIVE
- bool "Synopsys DWC Ethernet QOS device support for STARFIVE"
- depends on DWC_ETH_QOS
- help
- The Synopsys Designware Ethernet QOS IP block with specific
- configuration used in STARFIVE JH7110 soc.
-
config E1000
bool "Intel PRO/1000 Gigabit Ethernet support"
depends on PCI
diff --git a/drivers/net/Makefile b/drivers/net/Makefile
index 5e90183d090..761f7f0f451 100644
--- a/drivers/net/Makefile
+++ b/drivers/net/Makefile
@@ -5,7 +5,6 @@
obj-$(CONFIG_AG7XXX) += ag7xxx.o
-obj-y += airoha/
obj-$(CONFIG_AIROHA_ETH) += airoha_eth.o
obj-$(CONFIG_ALTERA_TSE) += altera_tse.o
obj-$(CONFIG_ASPEED_MDIO) += aspeed_mdio.o
@@ -22,12 +21,13 @@ obj-$(CONFIG_DWC_ETH_QOS) += dwc_eth_qos.o
obj-$(CONFIG_DWC_ETH_QOS_ADI) += dwc_eth_qos_adi.o
obj-$(CONFIG_DWC_ETH_QOS_IMX) += dwc_eth_qos_imx.o
obj-$(CONFIG_DWC_ETH_QOS_INTEL) += dwc_eth_qos_intel.o
-obj-$(CONFIG_DWC_ETH_QOS_ROCKCHIP) += dwc_eth_qos_rockchip.o
+obj-$(CONFIG_DWC_ETH_QOS_MTK) += dwc_eth_qos_mtk.o
obj-$(CONFIG_DWC_ETH_QOS_QCOM) += dwc_eth_qos_qcom.o
-obj-$(CONFIG_DWC_ETH_XGMAC) += dwc_eth_xgmac.o
-obj-$(CONFIG_DWC_ETH_XGMAC_SOCFPGA) += dwc_eth_xgmac_socfpga.o
+obj-$(CONFIG_DWC_ETH_QOS_ROCKCHIP) += dwc_eth_qos_rockchip.o
obj-$(CONFIG_DWC_ETH_QOS_STARFIVE) += dwc_eth_qos_starfive.o
obj-$(CONFIG_DWC_ETH_QOS_STM32) += dwc_eth_qos_stm32.o
+obj-$(CONFIG_DWC_ETH_XGMAC) += dwc_eth_xgmac.o
+obj-$(CONFIG_DWC_ETH_XGMAC_SOCFPGA) += dwc_eth_xgmac_socfpga.o
obj-$(CONFIG_E1000) += e1000.o
obj-$(CONFIG_E1000_SPI) += e1000_spi.o
obj-$(CONFIG_EEPRO100) += eepro100.o
@@ -62,9 +62,9 @@ obj-$(CONFIG_KSZ9477) += ksz9477.o
obj-$(CONFIG_LITEETH) += liteeth.o
obj-$(CONFIG_MACB) += macb.o
obj-$(CONFIG_MCFFEC) += mcffec.o mcfmii.o
+obj-$(CONFIG_MDIO_GPIO_BITBANG) += mdio_gpio.o
obj-$(CONFIG_MDIO_IPQ4019) += mdio-ipq4019.o
obj-$(CONFIG_MDIO_MSCC_MIIM) += mdio-mscc-miim.o
-obj-$(CONFIG_MDIO_GPIO_BITBANG) += mdio_gpio.o
obj-$(CONFIG_MDIO_MT7531_MMIO) += mdio-mt7531-mmio.o
obj-$(CONFIG_MDIO_MUX_I2CREG) += mdio_mux_i2creg.o
obj-$(CONFIG_MDIO_MUX_MESON_G12A) += mdio_mux_meson_g12a.o
@@ -109,6 +109,7 @@ obj-$(CONFIG_XILINX_AXIEMAC) += xilinx_axi_emac.o
obj-$(CONFIG_XILINX_AXIMRMAC) += xilinx_axi_mrmac.o
obj-$(CONFIG_XILINX_EMACLITE) += xilinx_emaclite.o
obj-$(CONFIG_ZYNQ_GEM) += zynq_gem.o
+obj-y += airoha/
obj-y += mscc_eswitch/
obj-y += phy/
obj-y += qe/
diff --git a/drivers/net/dwc_eth_qos.c b/drivers/net/dwc_eth_qos.c
index 0f31d646845..b7e6299c307 100644
--- a/drivers/net/dwc_eth_qos.c
+++ b/drivers/net/dwc_eth_qos.c
@@ -1659,6 +1659,12 @@ static const struct udevice_id eqos_ids[] = {
.data = (ulong)&eqos_adi_config
},
#endif
+#if IS_ENABLED(CONFIG_DWC_ETH_QOS_MTK)
+ {
+ .compatible = "mediatek,mt8189-gmac",
+ .data = (ulong)&eqos_mtk_config
+ },
+#endif
{ }
};
diff --git a/drivers/net/dwc_eth_qos.h b/drivers/net/dwc_eth_qos.h
index ba16f1a37cb..978b848b46e 100644
--- a/drivers/net/dwc_eth_qos.h
+++ b/drivers/net/dwc_eth_qos.h
@@ -97,6 +97,7 @@ struct eqos_mac_regs {
#define EQOS_MAC_MDIO_ADDRESS_PA_MASK GENMASK(25, 21)
#define EQOS_MAC_MDIO_ADDRESS_RDA_MASK GENMASK(20, 16)
#define EQOS_MAC_MDIO_ADDRESS_CR_MASK GENMASK(11, 8)
+#define EQOS_MAC_MDIO_ADDRESS_CR_60_100 0
#define EQOS_MAC_MDIO_ADDRESS_CR_100_150 1
#define EQOS_MAC_MDIO_ADDRESS_CR_20_35 2
#define EQOS_MAC_MDIO_ADDRESS_CR_150_250 4
@@ -316,3 +317,4 @@ extern struct eqos_config eqos_stm32mp15_config;
extern struct eqos_config eqos_stm32mp25_config;
extern struct eqos_config eqos_jh7110_config;
extern struct eqos_config eqos_adi_config;
+extern struct eqos_config eqos_mtk_config;
diff --git a/drivers/net/dwc_eth_qos_mtk.c b/drivers/net/dwc_eth_qos_mtk.c
new file mode 100644
index 00000000000..43e1085dfe5
--- /dev/null
+++ b/drivers/net/dwc_eth_qos_mtk.c
@@ -0,0 +1,442 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2026 BayLibre, SAS.
+ * Author: Julien Stephan <[email protected]>
+ */
+
+#include <dm.h>
+#include <dm/device_compat.h>
+#include <linux/bitfield.h>
+#include <net.h>
+#include <phy.h>
+#include <regmap.h>
+#include <syscon.h>
+
+#include "dwc_eth_qos.h"
+
+/*
+ * Peri Configuration register is SoC specific,
+ * so add a SoC specific prefix.
+ */
+#define MT8189_PERI_ETH_CTRL0 0x270
+#define MT8189_PERI_ETH_CTRL1 0x274
+#define MT8189_PERI_ETH_CTRL2 0x278
+
+#define EQOS_MTK_RMII_CLK_SRC_INTERNAL BIT(28)
+#define EQOS_MTK_RMII_CLK_SRC_RXC BIT(27)
+#define EQOS_MTK_ETH_INTF_SEL GENMASK(26, 24)
+#define EQOS_MTK_PHY_INTF_MII 0
+#define EQOS_MTK_PHY_INTF_RGMII 1
+#define EQOS_MTK_PHY_INTF_RMII 4
+#define EQOS_MTK_RGMII_TXC_PHASE_CTRL BIT(22)
+#define EQOS_MTK_EXT_PHY_MODE BIT(21)
+#define EQOS_MTK_TXC_OUT_OP BIT(20)
+#define EQOS_MTK_DLY_GTXC_INV BIT(12)
+#define EQOS_MTK_DLY_GTXC_STAGE_FINE GENMASK(11, 6)
+#define EQOS_MTK_DLY_GTXC_ENABLE BIT(5)
+#define EQOS_MTK_DLY_GTXC_STAGES GENMASK(4, 0)
+
+#define EQOS_MTK_DLY_RXC_INV BIT(25)
+#define EQOS_MTK_DLY_RXC_ENABLE BIT(18)
+#define EQOS_MTK_DLY_RXC_STAGES GENMASK(17, 13)
+#define EQOS_MTK_DLY_TXC_INV BIT(12)
+#define EQOS_MTK_DLY_TXC_ENABLE BIT(5)
+#define EQOS_MTK_DLY_TXC_STAGES GENMASK(4, 0)
+
+#define EQOS_MTK_DLY_RMII_RXC_INV BIT(25)
+#define EQOS_MTK_DLY_RMII_RXC_ENABLE BIT(18)
+#define EQOS_MTK_DLY_RMII_RXC_STAGES GENMASK(17, 13)
+#define EQOS_MTK_DLY_RMII_TXC_INV BIT(12)
+#define EQOS_MTK_DLY_RMII_TXC_ENABLE BIT(5)
+#define EQOS_MTK_DLY_RMII_TXC_STAGES GENMASK(4, 0)
+
+#define DELAY_MAX_PS 9800
+#define DELAY_PS_PER_STAGE 290
+
+struct eqos_mtk_priv {
+ struct regmap *peri_regmap;
+ bool rmii_clk_from_mac;
+ bool rmii_rxc;
+ u32 tx_delay_stage;
+ u32 rx_delay_stage;
+ bool tx_inv;
+ bool rx_inv;
+};
+
+static int mtk_clk_init(struct udevice *dev)
+{
+ struct eqos_priv *eqos = dev_get_priv(dev);
+ int ret;
+
+ ret = clk_get_by_name(dev, "mac_main", &eqos->clk_tx);
+ if (ret) {
+ dev_err(dev, "clk_get_by_name(mac_main) failed: %d", ret);
+ return ret;
+ }
+
+ ret = clk_get_by_name(dev, "ptp_ref", &eqos->clk_ptp_ref);
+ if (ret) {
+ dev_err(dev, "clk_get_by_name(ptp_ref) failed: %d", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int mtk_set_delay(struct udevice *dev)
+{
+ struct eth_pdata *pdata = dev_get_plat(dev);
+ struct eqos_mtk_priv *mtk_pdata = pdata->priv_pdata;
+ u32 gtxc_delay_val = 0, delay_val = 0, rmii_delay_val = 0;
+
+ switch (pdata->phy_interface) {
+ case PHY_INTERFACE_MODE_MII:
+ delay_val |= FIELD_PREP(EQOS_MTK_DLY_TXC_ENABLE,
+ !!mtk_pdata->tx_delay_stage);
+ delay_val |= FIELD_PREP(EQOS_MTK_DLY_TXC_STAGES, mtk_pdata->tx_delay_stage);
+ delay_val |= FIELD_PREP(EQOS_MTK_DLY_TXC_INV, mtk_pdata->tx_inv);
+
+ delay_val |= FIELD_PREP(EQOS_MTK_DLY_RXC_ENABLE,
+ !!mtk_pdata->rx_delay_stage);
+ delay_val |= FIELD_PREP(EQOS_MTK_DLY_RXC_STAGES, mtk_pdata->rx_delay_stage);
+ delay_val |= FIELD_PREP(EQOS_MTK_DLY_RXC_INV, mtk_pdata->rx_inv);
+ break;
+ case PHY_INTERFACE_MODE_RMII:
+ if (mtk_pdata->rmii_clk_from_mac) {
+ /* case 1: mac provides the rmii reference clock,
+ * and the clock output to TXC pin.
+ * The egress timing can be adjusted by RMII_TXC delay macro circuit.
+ * The ingress timing can be adjusted by RMII_RXC delay macro circuit.
+ */
+ rmii_delay_val |= FIELD_PREP(EQOS_MTK_DLY_RMII_TXC_ENABLE,
+ !!mtk_pdata->tx_delay_stage);
+ rmii_delay_val |= FIELD_PREP(EQOS_MTK_DLY_RMII_TXC_STAGES,
+ mtk_pdata->tx_delay_stage);
+ rmii_delay_val |= FIELD_PREP(EQOS_MTK_DLY_RMII_TXC_INV,
+ mtk_pdata->tx_inv);
+
+ rmii_delay_val |= FIELD_PREP(EQOS_MTK_DLY_RMII_RXC_ENABLE,
+ !!mtk_pdata->rx_delay_stage);
+ rmii_delay_val |= FIELD_PREP(EQOS_MTK_DLY_RMII_RXC_STAGES,
+ mtk_pdata->rx_delay_stage);
+ rmii_delay_val |= FIELD_PREP(EQOS_MTK_DLY_RMII_RXC_INV,
+ mtk_pdata->rx_inv);
+ } else {
+ /* case 2: the rmii reference clock is from external phy,
+ * and the property "rmii_rxc" indicates which pin(TXC/RXC)
+ * the reference clk is connected to. The reference clock is a
+ * received signal, so rx_delay_stage/rx_inv are used to indicate
+ * the reference clock timing adjustment
+ */
+ if (mtk_pdata->rmii_rxc) {
+ /* the rmii reference clock from outside is connected
+ * to RXC pin, the reference clock will be adjusted
+ * by RXC delay macro circuit.
+ */
+ delay_val |= FIELD_PREP(EQOS_MTK_DLY_RXC_ENABLE,
+ !!mtk_pdata->rx_delay_stage);
+ delay_val |= FIELD_PREP(EQOS_MTK_DLY_RXC_STAGES,
+ mtk_pdata->rx_delay_stage);
+ delay_val |= FIELD_PREP(EQOS_MTK_DLY_RXC_INV,
+ mtk_pdata->rx_inv);
+ } else {
+ /* the rmii reference clock from outside is connected
+ * to TXC pin, the reference clock will be adjusted
+ * by TXC delay macro circuit.
+ */
+ delay_val |= FIELD_PREP(EQOS_MTK_DLY_TXC_ENABLE,
+ !!mtk_pdata->rx_delay_stage);
+ delay_val |= FIELD_PREP(EQOS_MTK_DLY_TXC_STAGES,
+ mtk_pdata->rx_delay_stage);
+ delay_val |= FIELD_PREP(EQOS_MTK_DLY_TXC_INV,
+ mtk_pdata->rx_inv);
+ }
+ }
+ break;
+ case PHY_INTERFACE_MODE_RGMII:
+ case PHY_INTERFACE_MODE_RGMII_TXID:
+ case PHY_INTERFACE_MODE_RGMII_RXID:
+ case PHY_INTERFACE_MODE_RGMII_ID:
+ gtxc_delay_val |= FIELD_PREP(EQOS_MTK_DLY_GTXC_ENABLE,
+ !!mtk_pdata->tx_delay_stage);
+ gtxc_delay_val |= FIELD_PREP(EQOS_MTK_DLY_GTXC_STAGES,
+ mtk_pdata->tx_delay_stage);
+ gtxc_delay_val |= FIELD_PREP(EQOS_MTK_DLY_GTXC_INV, mtk_pdata->tx_inv);
+ gtxc_delay_val |= EQOS_MTK_DLY_GTXC_STAGE_FINE;
+
+ delay_val |= FIELD_PREP(EQOS_MTK_DLY_RXC_ENABLE,
+ !!mtk_pdata->rx_delay_stage);
+ delay_val |= FIELD_PREP(EQOS_MTK_DLY_RXC_STAGES, mtk_pdata->rx_delay_stage);
+ delay_val |= FIELD_PREP(EQOS_MTK_DLY_RXC_INV, mtk_pdata->rx_inv);
+
+ break;
+ default:
+ dev_err(dev, "phy interface not supported\n");
+ return -EINVAL;
+ }
+
+ regmap_update_bits(mtk_pdata->peri_regmap,
+ MT8189_PERI_ETH_CTRL0,
+ EQOS_MTK_RGMII_TXC_PHASE_CTRL |
+ EQOS_MTK_DLY_GTXC_ENABLE |
+ EQOS_MTK_DLY_GTXC_INV |
+ EQOS_MTK_DLY_GTXC_STAGE_FINE |
+ EQOS_MTK_DLY_GTXC_STAGES,
+ gtxc_delay_val);
+ regmap_write(mtk_pdata->peri_regmap, MT8189_PERI_ETH_CTRL1, delay_val);
+ regmap_write(mtk_pdata->peri_regmap, MT8189_PERI_ETH_CTRL2, rmii_delay_val);
+
+ return 0;
+}
+
+static int mtk_set_interface(struct udevice *dev)
+{
+ struct eth_pdata *pdata = dev_get_plat(dev);
+ struct eqos_mtk_priv *mtk_pdata = pdata->priv_pdata;
+ int rmii_clk_from_mac = mtk_pdata->rmii_clk_from_mac ? EQOS_MTK_RMII_CLK_SRC_INTERNAL : 0;
+ int rmii_rxc = mtk_pdata->rmii_rxc ? EQOS_MTK_RMII_CLK_SRC_RXC : 0;
+ u32 intf_val = 0;
+
+ /* select phy interface in top control domain */
+ switch (pdata->phy_interface) {
+ case PHY_INTERFACE_MODE_MII:
+ intf_val |= FIELD_PREP(EQOS_MTK_ETH_INTF_SEL, EQOS_MTK_PHY_INTF_MII);
+ break;
+ case PHY_INTERFACE_MODE_RMII:
+ intf_val |= (rmii_rxc | rmii_clk_from_mac);
+ intf_val |= FIELD_PREP(EQOS_MTK_ETH_INTF_SEL, EQOS_MTK_PHY_INTF_RMII);
+ break;
+ case PHY_INTERFACE_MODE_RGMII:
+ case PHY_INTERFACE_MODE_RGMII_TXID:
+ case PHY_INTERFACE_MODE_RGMII_RXID:
+ case PHY_INTERFACE_MODE_RGMII_ID:
+ intf_val |= FIELD_PREP(EQOS_MTK_ETH_INTF_SEL, EQOS_MTK_PHY_INTF_RGMII);
+ break;
+ default:
+ dev_err(dev, "phy interface not supported\n");
+ return -EINVAL;
+ }
+
+ /* only support external PHY */
+ intf_val |= EQOS_MTK_EXT_PHY_MODE;
+
+ intf_val |= EQOS_MTK_TXC_OUT_OP;
+
+ regmap_write(mtk_pdata->peri_regmap, MT8189_PERI_ETH_CTRL0, intf_val);
+
+ return 0;
+}
+
+static int mtk_config_dt(struct udevice *dev)
+{ struct eth_pdata *pdata = dev_get_plat(dev);
+ struct eqos_mtk_priv *mtk_pdata = pdata->priv_pdata;
+ struct ofnode_phandle_args args;
+ u32 tx_delay_ps = 0, rx_delay_ps = 0;
+ int ret;
+
+ if (!dev_read_u32(dev, "mediatek,tx-delay-ps", &tx_delay_ps)) {
+ if (tx_delay_ps > DELAY_MAX_PS) {
+ dev_err(dev, "Invalid TX clock delay: %dps\n", tx_delay_ps);
+ return -EINVAL;
+ }
+ }
+
+ if (!dev_read_u32(dev, "mediatek,rx-delay-ps", &rx_delay_ps)) {
+ if (rx_delay_ps > DELAY_MAX_PS) {
+ dev_err(dev, "Invalid RX clock delay: %dps\n", rx_delay_ps);
+ return -EINVAL;
+ }
+ }
+
+ mtk_pdata->tx_delay_stage = tx_delay_ps / DELAY_PS_PER_STAGE;
+ mtk_pdata->rx_delay_stage = rx_delay_ps / DELAY_PS_PER_STAGE;
+
+ mtk_pdata->tx_inv = dev_read_bool(dev, "mediatek,txc-inverse");
+ mtk_pdata->rx_inv = dev_read_bool(dev, "mediatek,rxc-inverse");
+ mtk_pdata->rmii_clk_from_mac = dev_read_bool(dev, "mediatek,rmii-clk-from-mac");
+ mtk_pdata->rmii_rxc = dev_read_bool(dev, "mediatek,rmii-rxc");
+
+ ret = dev_read_phandle_with_args(dev, "mediatek,pericfg", NULL, 0, 0, &args);
+ if (ret) {
+ dev_err(dev, "Failed to get mediatek,pericfg property: %d\n", ret);
+ return ret;
+ }
+
+ mtk_pdata->peri_regmap = syscon_node_to_regmap(args.node);
+ if (IS_ERR(mtk_pdata->peri_regmap)) {
+ dev_err(dev, "fail to get regmap: %d\n", (int)PTR_ERR(mtk_pdata->peri_regmap));
+ return PTR_ERR(mtk_pdata->peri_regmap);
+ }
+
+ return 0;
+}
+
+static int eqos_probe_resources_mtk(struct udevice *dev)
+{
+ struct eqos_priv *eqos = dev_get_priv(dev);
+ struct eth_pdata *pdata = dev_get_plat(dev);
+ struct eqos_mtk_priv *mtk_pdata;
+ int ret;
+
+ debug("%s(dev=%p):\n", __func__, dev);
+
+ ret = eqos_get_base_addr_dt(dev);
+ if (ret) {
+ dev_err(dev, "eqos_get_base_addr_dt failed: %d\n", ret);
+ return ret;
+ }
+
+ mtk_pdata = calloc(1, sizeof(struct eqos_mtk_priv));
+ if (!mtk_pdata)
+ return -ENOMEM;
+
+ pdata->priv_pdata = mtk_pdata;
+
+ ret = mtk_config_dt(dev);
+ if (ret) {
+ dev_err(dev, "mtk config dt failed: %d\n", ret);
+ goto err;
+ }
+
+ ret = mtk_clk_init(dev);
+ if (ret)
+ goto err;
+
+ pdata->phy_interface = eqos->config->interface(dev);
+ if (pdata->phy_interface == PHY_INTERFACE_MODE_NA) {
+ dev_err(dev, "Invalid PHY interface\n");
+ ret = -EINVAL;
+ goto err;
+ }
+
+ ret = mtk_set_interface(dev);
+ if (ret)
+ goto err;
+
+ ret = mtk_set_delay(dev);
+ if (ret)
+ goto err;
+
+ debug("%s: OK\n", __func__);
+ return 0;
+err:
+ free(mtk_pdata);
+ return ret;
+}
+
+static int eqos_remove_resources_mtk(struct udevice *dev)
+{
+ struct eth_pdata *pdata = dev_get_plat(dev);
+ struct eqos_mtk_priv *mtk_pdata = pdata->priv_pdata;
+
+ debug("%s(dev=%p):\n", __func__, dev);
+
+ free(mtk_pdata);
+
+ debug("%s: OK\n", __func__);
+ return 0;
+}
+
+static int eqos_stop_clks_mtk(struct udevice *dev)
+{
+ struct eqos_priv *eqos = dev_get_priv(dev);
+
+ debug("%s(dev=%p):\n", __func__, dev);
+
+ clk_disable(&eqos->clk_ptp_ref);
+ clk_disable(&eqos->clk_tx);
+
+ debug("%s: OK\n", __func__);
+ return 0;
+}
+
+static int eqos_start_clks_mtk(struct udevice *dev)
+{
+ struct eqos_priv *eqos = dev_get_priv(dev);
+ int ret;
+
+ debug("%s(dev=%p):\n", __func__, dev);
+
+ ret = clk_enable(&eqos->clk_tx);
+ if (ret < 0) {
+ dev_err(dev, "clk_enable(mac_main) failed: %d", ret);
+ goto err;
+ }
+
+ ret = clk_enable(&eqos->clk_ptp_ref);
+ if (ret < 0) {
+ dev_err(dev, "clk_enable(ptp_ref) failed: %d", ret);
+ goto err_disable_clk_mac_main;
+ }
+
+ debug("%s: OK\n", __func__);
+ return 0;
+
+err_disable_clk_mac_main:
+ clk_disable(&eqos->clk_tx);
+err:
+ debug("%s: FAILED: %d\n", __func__, ret);
+ return ret;
+}
+
+static int eqos_fix_mac_speed_mtk(struct udevice *dev)
+{
+ struct eqos_priv *eqos = dev_get_priv(dev);
+ struct eth_pdata *pdata = dev_get_plat(dev);
+ struct eqos_mtk_priv *mtk_pdata = pdata->priv_pdata;
+
+ debug("%s(dev=%p):\n", __func__, dev);
+
+ switch (pdata->phy_interface) {
+ case PHY_INTERFACE_MODE_RGMII:
+ case PHY_INTERFACE_MODE_RGMII_TXID:
+ case PHY_INTERFACE_MODE_RGMII_RXID:
+ case PHY_INTERFACE_MODE_RGMII_ID:
+ if (eqos->phy->speed == SPEED_1000)
+ regmap_update_bits(mtk_pdata->peri_regmap,
+ MT8189_PERI_ETH_CTRL0,
+ EQOS_MTK_RGMII_TXC_PHASE_CTRL |
+ EQOS_MTK_DLY_GTXC_ENABLE |
+ EQOS_MTK_DLY_GTXC_INV |
+ EQOS_MTK_DLY_GTXC_STAGE_FINE |
+ EQOS_MTK_DLY_GTXC_STAGES,
+ EQOS_MTK_RGMII_TXC_PHASE_CTRL);
+ else
+ mtk_set_delay(dev);
+ break;
+ default:
+ debug("%s: dev=%p no need to adjust mac delay\n", __func__, dev);
+ break;
+ }
+
+ debug("%s: OK\n", __func__);
+ return 0;
+}
+
+static struct eqos_ops eqos_mtk_ops = {
+ .eqos_inval_desc = eqos_inval_desc_generic,
+ .eqos_flush_desc = eqos_flush_desc_generic,
+ .eqos_inval_buffer = eqos_inval_buffer_generic,
+ .eqos_flush_buffer = eqos_flush_buffer_generic,
+ .eqos_probe_resources = eqos_probe_resources_mtk,
+ .eqos_remove_resources = eqos_remove_resources_mtk,
+ .eqos_stop_resets = eqos_null_ops,
+ .eqos_start_resets = eqos_null_ops,
+ .eqos_stop_clks = eqos_stop_clks_mtk,
+ .eqos_start_clks = eqos_start_clks_mtk,
+ .eqos_calibrate_pads = eqos_null_ops,
+ .eqos_disable_calibration = eqos_null_ops,
+ .eqos_set_tx_clk_speed = eqos_fix_mac_speed_mtk,
+ .eqos_get_enetaddr = eqos_null_ops,
+};
+
+struct eqos_config eqos_mtk_config = {
+ .reg_access_always_ok = false,
+ .mdio_wait = 10000,
+ .swr_wait = 10,
+ .config_mac = EQOS_MAC_RXQ_CTRL0_RXQ0EN_ENABLED_DCB,
+ .config_mac_mdio = EQOS_MAC_MDIO_ADDRESS_CR_60_100,
+ .axi_bus_width = EQOS_AXI_WIDTH_64,
+ .interface = dev_read_phy_mode,
+ .ops = &eqos_mtk_ops
+};
diff --git a/drivers/net/phy/airoha/Kconfig b/drivers/net/phy/airoha/Kconfig
index 4139df343ad..2d58d674200 100644
--- a/drivers/net/phy/airoha/Kconfig
+++ b/drivers/net/phy/airoha/Kconfig
@@ -2,12 +2,25 @@
menuconfig PHY_AIROHA
bool "Airoha Ethernet PHYs support"
+config PHY_AIROHA_AN8801
+ bool "Airoha Ethernet AN8801 support"
+ depends on PHY_AIROHA
+ select PHY_AIROHA_PHYLIB
+ help
+ Currently support AIROHA AN8801 1G PHY.
+
config PHY_AIROHA_EN8811
bool "Airoha Ethernet EN8811H support"
depends on PHY_AIROHA
depends on SUPPORTS_FW_LOADER
select FW_LOADER
+ select PHY_AIROHA_PHYLIB
select PHY_COMMON_PROPS
help
AIROHA EN8811H supported.
AIROHA AN8811HB supported.
+
+config PHY_AIROHA_PHYLIB
+ bool
+ help
+ Airoha Ethernet PHY common library
diff --git a/drivers/net/phy/airoha/Makefile b/drivers/net/phy/airoha/Makefile
index 84d23b19ab0..25e44004cfd 100644
--- a/drivers/net/phy/airoha/Makefile
+++ b/drivers/net/phy/airoha/Makefile
@@ -1,3 +1,5 @@
# SPDX-License-Identifier: GPL-2.0
+obj-$(CONFIG_PHY_AIROHA_AN8801) += air_an8801.o
obj-$(CONFIG_PHY_AIROHA_EN8811) += air_en8811.o
+obj-$(CONFIG_PHY_AIROHA_PHYLIB) += air_phy_lib.o
diff --git a/drivers/net/phy/airoha/air_an8801.c b/drivers/net/phy/airoha/air_an8801.c
new file mode 100644
index 00000000000..9d9958fc665
--- /dev/null
+++ b/drivers/net/phy/airoha/air_an8801.c
@@ -0,0 +1,594 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * air_an8801.c - PHY driver for Airoha AN8801.
+ * Copyright (c) 2026 Airoha Technology Corp.
+ * Copyright (C) 2026 BayLibre, SAS.
+ * Author: Kevin-KW Huang <[email protected]>
+ * Sita Huang <[email protected]>
+ * Julien Stephan <[email protected]>
+ */
+
+#include <malloc.h>
+#include <phy.h>
+#include <dm/device_compat.h>
+
+#include "air_phy_lib.h"
+
+#define AN8801R_PHY_ID1 0xc0ff
+#define AN8801R_PHY_ID2 0x0421
+#define AN8801R_PHY_ID ((u32)((AN8801R_PHY_ID1 << 16) | AN8801R_PHY_ID2))
+
+#define AN8801R_MAX_LED_SIZE 3
+
+/* MII Registers - Airoha Page 4 */
+#define AN8801_PBUS_ACCESS BIT(28)
+
+/* BPBUS Registers */
+#define AN8801_BPBUS_REG_LED_GPIO 0x54
+#define AN8801_BPBUS_REG_LED_ID_SEL 0x58
+#define LED_ID_GPIO_SEL(led, gpio) ((led) << ((gpio) * 3))
+
+#define AN8801_BPBUS_REG_GPIO_MODE 0x70
+
+#define AN8801_BPBUS_REG_LINK_MODE 0x5054
+#define AN8801_BPBUS_LINK_MODE_1000 BIT(0)
+
+#define AN8801_BPBUS_REG_BYPASS_PTP 0x21c004
+#define AN8801_BYP_PTP_RGMII_TO_GPHY BIT(0)
+
+#define AN8801_BPBUS_REG_TXDLY_STEP 0x21c024
+#define RGMII_DELAY_STEP_MASK GENMASK(2, 0)
+#define AIR_RGMII_DELAY_NOSTEP 0
+#define AIR_RGMII_DELAY_STEP_1 1
+#define AIR_RGMII_DELAY_STEP_2 2
+#define AIR_RGMII_DELAY_STEP_3 3
+#define AIR_RGMII_DELAY_STEP_4 4
+#define AIR_RGMII_DELAY_STEP_5 5
+#define AIR_RGMII_DELAY_STEP_6 6
+#define AIR_RGMII_DELAY_STEP_7 7
+#define RGMII_TXDELAY_FORCE_MODE BIT(24)
+
+#define AN8801_BPBUS_REG_RXDLY_STEP 0x21c02c
+#define RGMII_RXDELAY_ALIGN BIT(4)
+#define RGMII_RXDELAY_FORCE_MODE BIT(24)
+
+#define AN8801_BPBUS_REG_EFIFO_CTL(x) (0x270004 + (0x100 * (x))) /* 0..2 */
+#define AN8801_EFIFO_ALL_EN GENMASK(7, 0)
+#define AN8801_EFIFO_RX_EN BIT(0)
+#define AN8801_EFIFO_TX_EN BIT(1)
+#define AN8801_EFIFO_RX_CLK_EN BIT(2)
+#define AN8801_EFIFO_TX_CLK_EN BIT(3)
+#define AN8801_EFIFO_RX_EEE_EN BIT(4)
+#define AN8801_EFIFO_TX_EEE_EN BIT(5)
+#define AN8801_EFIFO_RX_ODD_NIBBLE_EN BIT(6)
+#define AN8801_EFIFO_TX_ODD_NIBBLE_EN BIT(7)
+
+#define AN8801_BPBUS_REG_HWRST_DE_GLITCH 0xc8
+#define AN8801_DE_GLITCH_EN BIT(2)
+#define AN8801_11_CYCLE_XTAL_PERIOD_DE_GLITCH GENMASK(1, 0)
+
+#define LED_BCR 0x21
+#define LED_BCR_MODE_MASK GENMASK(1, 0)
+#define LED_BCR_TIME_TEST BIT(2)
+#define LED_BCR_CLK_EN BIT(3)
+#define LED_BCR_EVT_ALL BIT(4)
+#define LED_BCR_EXT_CTRL BIT(15)
+#define LED_BCR_MODE_DISABLE 0
+#define LED_BCR_MODE_2LED 1
+#define LED_BCR_MODE_3LED_1 2
+#define LED_BCR_MODE_3LED_2 3
+
+#define LED_ON_DUR 0x22
+#define LED_ON_DUR_MASK GENMASK(15, 0)
+
+#define LED_BLINK_DUR 0x23
+#define LED_BLINK_DUR_MASK GENMASK(15, 0)
+
+#define LED_ON_CTRL(i) (0x024 + ((i) * 2))
+#define LED_ON_EVT_MASK GENMASK(6, 0)
+#define LED_ON_EVT_LINK_1000M BIT(0)
+#define LED_ON_EVT_LINK_100M BIT(1)
+#define LED_ON_EVT_LINK_10M BIT(2)
+#define LED_ON_EVT_LINK_DN BIT(3)
+#define LED_ON_EVT_FDX BIT(4)
+#define LED_ON_EVT_HDX BIT(5)
+#define LED_ON_EVT_FORCE BIT(6)
+#define LED_ON_POL BIT(14)
+#define LED_ON_EN BIT(15)
+
+#define LED_BLINK_CTRL(i) (0x025 + ((i) * 2))
+#define LED_BLINK_EVT_MASK GENMASK(9, 0)
+#define LED_BLINK_EVT_1000M_TX BIT(0)
+#define LED_BLINK_EVT_1000M_RX BIT(1)
+#define LED_BLINK_EVT_100M_TX BIT(2)
+#define LED_BLINK_EVT_100M_RX BIT(3)
+#define LED_BLINK_EVT_10M_TX BIT(4)
+#define LED_BLINK_EVT_10M_RX BIT(5)
+#define LED_BLINK_EVT_FORCE BIT(9)
+
+#define UNIT_LED_BLINK_DURATION 780
+#define LED_BLINK_DURATION(f) (UNIT_LED_BLINK_DURATION << (f))
+
+/* Link on(1G/100M/10M), no activity */
+#define AIR_LED0_ON \
+ (LED_ON_EVT_LINK_1000M | LED_ON_EVT_LINK_100M | LED_ON_EVT_LINK_10M)
+#define AIR_LED0_BLINK 0x0
+/* No link on, activity(1G/100M/10M TX/RX) */
+#define AIR_LED1_ON 0x0
+#define AIR_LED1_BLINK \
+ (LED_BLINK_EVT_1000M_TX | LED_BLINK_EVT_1000M_RX | \
+ LED_BLINK_EVT_100M_TX | LED_BLINK_EVT_100M_RX | \
+ LED_BLINK_EVT_10M_TX | LED_BLINK_EVT_10M_RX)
+/* Link on(100M/10M), activity(100M/10M TX/RX) */
+#define AIR_LED2_ON \
+ (LED_ON_EVT_LINK_100M | LED_ON_EVT_LINK_10M)
+#define AIR_LED2_BLINK \
+ (LED_BLINK_EVT_100M_TX | LED_BLINK_EVT_100M_RX | \
+ LED_BLINK_EVT_10M_TX | LED_BLINK_EVT_10M_RX)
+
+#define INVALID_DATA GENMASK(31, 0)
+
+#define AN8801_REG_PHY_INTERNAL0 0x600
+#define AN8801_REG_PHY_INTERNAL1 0x601
+
+#define AN8801_LED_ENABLE 1
+
+enum air_led_gpio_pin {
+ AIR_LED_GPIO1 = 1,
+ AIR_LED_GPIO2,
+ AIR_LED_GPIO3
+};
+
+enum air_led {
+ AIR_LED0 = 0,
+ AIR_LED1,
+ AIR_LED2,
+ AIR_LED3
+};
+
+enum air_led_blink_dut {
+ AIR_LED_BLINK_DUR_32M = 0,
+ AIR_LED_BLINK_DUR_64M,
+ AIR_LED_BLINK_DUR_128M,
+ AIR_LED_BLINK_DUR_256M,
+ AIR_LED_BLINK_DUR_512M,
+ AIR_LED_BLINK_DUR_1024M,
+ AIR_LED_BLINK_DUR_LAST
+};
+
+enum air_led_polarity {
+ AIR_ACTIVE_LOW = 0,
+ AIR_ACTIVE_HIGH,
+};
+
+enum air_led_mode {
+ AIR_LED_MODE_DISABLE = 0,
+ AIR_LED_MODE_USER_DEFINE,
+ AIR_LED_MODE_LAST
+};
+
+struct air_led_cfg {
+ u16 led_en;
+ u16 gpio;
+ u16 led_polarity;
+ u16 led_on_cfg;
+ u16 led_blk_cfg;
+};
+
+struct an8801r_priv {
+ struct air_led_cfg led_cfg[AN8801R_MAX_LED_SIZE];
+ u32 led_blink_cfg;
+ u8 rxdelay_force;
+ u8 txdelay_force;
+ u16 rxdelay_step;
+ u8 rxdelay_align;
+ u16 txdelay_step;
+};
+
+#define phydev_cfg(phy) ((struct an8801r_priv *)(phy)->priv)
+
+/*
+ * GPIO1 <-> LED0,
+ * GPIO2 <-> LED1,
+ * GPIO3 <-> LED2,
+ */
+static const struct an8801r_priv an8801r_priv_defaults = {
+ .led_cfg = {
+ /* LED Enable, GPIO, LED Polarity, LED ON, LED Blink */
+ {AN8801_LED_ENABLE, AIR_LED_GPIO1, AIR_ACTIVE_LOW, AIR_LED0_ON, AIR_LED0_BLINK},
+ {AN8801_LED_ENABLE, AIR_LED_GPIO2, AIR_ACTIVE_HIGH, AIR_LED1_ON, AIR_LED1_BLINK},
+ {AN8801_LED_ENABLE, AIR_LED_GPIO3, AIR_ACTIVE_HIGH, AIR_LED2_ON, AIR_LED2_BLINK},
+ },
+ .led_blink_cfg = AIR_LED_BLINK_DUR_64M,
+ .rxdelay_force = false,
+ .txdelay_force = false,
+ .rxdelay_step = AIR_RGMII_DELAY_NOSTEP,
+ .rxdelay_align = false,
+ .txdelay_step = AIR_RGMII_DELAY_NOSTEP,
+};
+
+static int an8801_buckpbus_reg_rmw(struct phy_device *phydev,
+ u32 addr, u32 mask, u32 set)
+{
+ return air_phy_buckpbus_reg_modify(phydev,
+ addr | AN8801_PBUS_ACCESS,
+ mask, set);
+}
+
+static int an8801_buckpbus_reg_set_bits(struct phy_device *phydev,
+ u32 addr, u32 mask)
+{
+ return air_phy_buckpbus_reg_modify(phydev,
+ addr | AN8801_PBUS_ACCESS,
+ mask, mask);
+}
+
+static int an8801_buckpbus_reg_clear_bits(struct phy_device *phydev,
+ u32 addr, u32 mask)
+{
+ return air_phy_buckpbus_reg_modify(phydev,
+ addr | AN8801_PBUS_ACCESS,
+ mask, 0);
+}
+
+static int an8801_buckpbus_reg_write(struct phy_device *phydev, u32 addr, u32 data)
+{
+ return air_phy_buckpbus_reg_write(phydev, addr | AN8801_PBUS_ACCESS, data);
+}
+
+static int an8801r_led_set_usr_def(struct phy_device *phydev, u8 entity,
+ u16 polar, u16 on_evt, u16 blk_evt)
+{
+ int ret;
+
+ if (polar == AIR_ACTIVE_HIGH)
+ on_evt |= LED_ON_POL;
+ else
+ on_evt &= ~LED_ON_POL;
+
+ on_evt |= LED_ON_EN;
+
+ ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, LED_ON_CTRL(entity), on_evt);
+ if (ret)
+ return ret;
+
+ return phy_write_mmd(phydev, MDIO_MMD_VEND2, LED_BLINK_CTRL(entity), blk_evt);
+}
+
+static int an8801r_led_set_blink(struct phy_device *phydev, u16 blink)
+{
+ int ret;
+
+ ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, LED_BLINK_DUR,
+ LED_BLINK_DURATION(blink));
+ if (ret)
+ return ret;
+
+ return phy_write_mmd(phydev, MDIO_MMD_VEND2, LED_ON_DUR,
+ LED_BLINK_DURATION(blink) / 2);
+}
+
+static int an8801r_led_set_mode(struct phy_device *phydev, u8 mode)
+{
+ int ret;
+
+ ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, LED_BCR);
+ if (ret < 0)
+ return ret;
+
+ switch (mode) {
+ case AIR_LED_MODE_DISABLE:
+ ret &= ~LED_BCR_EXT_CTRL;
+ ret &= ~LED_BCR_MODE_MASK;
+ ret |= LED_BCR_MODE_DISABLE;
+ break;
+ case AIR_LED_MODE_USER_DEFINE:
+ ret |= LED_BCR_EXT_CTRL | LED_BCR_CLK_EN;
+ break;
+ }
+ return phy_write_mmd(phydev, MDIO_MMD_VEND2, LED_BCR, ret);
+}
+
+static int an8801r_led_set_state(struct phy_device *phydev, u8 entity, u8 state)
+{
+ int ret;
+
+ ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, LED_ON_CTRL(entity));
+ if (ret < 0)
+ return ret;
+
+ if (state)
+ ret |= LED_ON_EN;
+ else
+ ret &= ~LED_ON_EN;
+
+ return phy_write_mmd(phydev, MDIO_MMD_VEND2, LED_ON_CTRL(entity), ret);
+}
+
+static int an8801r_led_init(struct phy_device *phydev)
+{
+ struct an8801r_priv *priv = phydev_cfg(phydev);
+ struct air_led_cfg *led_cfg = priv->led_cfg;
+ u16 led_blink_cfg = priv->led_blink_cfg;
+ int ret, led_id;
+
+ ret = an8801r_led_set_blink(phydev, led_blink_cfg);
+ if (ret)
+ return ret;
+
+ ret = an8801r_led_set_mode(phydev, AIR_LED_MODE_USER_DEFINE);
+ if (ret) {
+ dev_err(phydev->dev, "AN8801R: Fail to set LED mode, ret %d!\n", ret);
+ return ret;
+ }
+
+ for (led_id = AIR_LED0; led_id < AN8801R_MAX_LED_SIZE; led_id++) {
+ ret = an8801r_led_set_state(phydev, led_id, led_cfg[led_id].led_en);
+ if (ret) {
+ dev_err(phydev->dev, "AN8801R: Fail to set LED%d state, ret %d!\n",
+ led_id, ret);
+ return ret;
+ }
+
+ if (!led_cfg[led_id].led_en)
+ continue;
+
+ ret = an8801_buckpbus_reg_set_bits(phydev, AN8801_BPBUS_REG_LED_GPIO,
+ BIT(led_cfg[led_id].gpio));
+ if (ret)
+ return ret;
+
+ ret = an8801_buckpbus_reg_set_bits(phydev, AN8801_BPBUS_REG_LED_ID_SEL,
+ LED_ID_GPIO_SEL(led_id,
+ led_cfg[led_id].gpio));
+ if (ret)
+ return ret;
+
+ ret = an8801_buckpbus_reg_clear_bits(phydev, AN8801_BPBUS_REG_GPIO_MODE,
+ BIT(led_cfg[led_id].gpio));
+ if (ret)
+ return ret;
+
+ ret = an8801r_led_set_usr_def(phydev, led_id,
+ led_cfg[led_id].led_polarity,
+ led_cfg[led_id].led_on_cfg,
+ led_cfg[led_id].led_blk_cfg);
+ if (ret) {
+ dev_err(phydev->dev, "AN8801R: Fail to set LED%d, ret %d!\n",
+ led_id, ret);
+ return ret;
+ }
+ }
+ return 0;
+}
+
+static int an8801r_of_init(struct phy_device *phydev)
+{
+ struct an8801r_priv *priv = phydev_cfg(phydev);
+ ofnode node = phy_get_ofnode(phydev);
+ u32 val = 0;
+ int ret;
+
+ if (!ofnode_valid(node))
+ return -EINVAL;
+
+ if (ofnode_has_property(node, "airoha,rxclk-delay")) {
+ ret = ofnode_read_u32(node, "airoha,rxclk-delay", &val);
+ if (ret) {
+ dev_err(phydev->dev, "airoha,rxclk-delay value is invalid.\n");
+ return ret;
+ }
+ if (val > AIR_RGMII_DELAY_STEP_7) {
+ dev_err(phydev->dev, "airoha,rxclk-delay value %u out of range.\n", val);
+ return -EINVAL;
+ }
+ priv->rxdelay_force = true;
+ priv->rxdelay_step = val;
+ priv->rxdelay_align = ofnode_read_bool(node,
+ "airoha,rxclk-delay-align");
+ }
+
+ if (ofnode_has_property(node, "airoha,txclk-delay")) {
+ ret = ofnode_read_u32(node, "airoha,txclk-delay", &val);
+ if (ret) {
+ dev_err(phydev->dev, "airoha,txclk-delay value is invalid.\n");
+ return ret;
+ }
+ if (val > AIR_RGMII_DELAY_STEP_7) {
+ dev_err(phydev->dev, "airoha,txclk-delay value %u out of range.\n", val);
+ return -EINVAL;
+ }
+ priv->txdelay_force = true;
+ priv->txdelay_step = val;
+ }
+ return 0;
+}
+
+static int an8801r_rgmii_rxdelay(struct phy_device *phydev, u16 delay, u8 align)
+{
+ u32 reg_val = delay & RGMII_DELAY_STEP_MASK;
+ int ret;
+
+ if (align) {
+ reg_val |= RGMII_RXDELAY_ALIGN;
+ debug("AN8801R: Rxdelay align\n");
+ }
+ reg_val |= RGMII_RXDELAY_FORCE_MODE;
+ ret = an8801_buckpbus_reg_write(phydev, AN8801_BPBUS_REG_RXDLY_STEP, reg_val);
+ if (ret)
+ return ret;
+
+ debug("AN8801R: Force rxdelay = %d(0x%x)\n", delay, reg_val);
+ return 0;
+}
+
+static int an8801r_rgmii_txdelay(struct phy_device *phydev, u16 delay)
+{
+ u32 reg_val = delay & RGMII_DELAY_STEP_MASK;
+ int ret;
+
+ reg_val |= RGMII_TXDELAY_FORCE_MODE;
+ ret = an8801_buckpbus_reg_write(phydev, AN8801_BPBUS_REG_TXDLY_STEP, reg_val);
+ if (ret)
+ return ret;
+
+ debug("AN8801R: Force txdelay = %d(0x%x)\n", delay, reg_val);
+ return 0;
+}
+
+static int an8801r_rgmii_delay_config(struct phy_device *phydev)
+{
+ struct an8801r_priv *priv = phydev_cfg(phydev);
+ int ret;
+
+ switch (phydev->interface) {
+ case PHY_INTERFACE_MODE_RGMII_TXID:
+ return an8801r_rgmii_txdelay(phydev, AIR_RGMII_DELAY_STEP_4);
+ case PHY_INTERFACE_MODE_RGMII_RXID:
+ return an8801r_rgmii_rxdelay(phydev, AIR_RGMII_DELAY_NOSTEP, true);
+ case PHY_INTERFACE_MODE_RGMII_ID:
+ ret = an8801r_rgmii_txdelay(phydev, AIR_RGMII_DELAY_STEP_4);
+ if (ret)
+ return ret;
+ return an8801r_rgmii_rxdelay(phydev, AIR_RGMII_DELAY_NOSTEP, true);
+ case PHY_INTERFACE_MODE_RGMII:
+ default:
+ if (priv->rxdelay_force) {
+ ret = an8801r_rgmii_rxdelay(phydev, priv->rxdelay_step,
+ priv->rxdelay_align);
+ if (ret)
+ return ret;
+ }
+ if (priv->txdelay_force)
+ return an8801r_rgmii_txdelay(phydev, priv->txdelay_step);
+ return 0;
+ }
+}
+
+static int an8801r_config_init(struct phy_device *phydev)
+{
+ int ret;
+
+ ret = an8801r_of_init(phydev);
+ if (ret < 0)
+ return ret;
+
+ ret = an8801_buckpbus_reg_write(phydev, AN8801_BPBUS_REG_HWRST_DE_GLITCH,
+ AN8801_DE_GLITCH_EN |
+ AN8801_11_CYCLE_XTAL_PERIOD_DE_GLITCH);
+ if (ret)
+ return ret;
+
+ ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, AN8801_REG_PHY_INTERNAL0, 0x1e);
+ if (ret)
+ return ret;
+
+ ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, AN8801_REG_PHY_INTERNAL1, 0x02);
+ if (ret)
+ return ret;
+
+ ret = phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_EEE_ADV, 0x0);
+ if (ret)
+ return ret;
+
+ ret = an8801_buckpbus_reg_write(phydev, AN8801_BPBUS_REG_BYPASS_PTP,
+ AN8801_BYP_PTP_RGMII_TO_GPHY);
+ if (ret)
+ return ret;
+
+ ret = an8801_buckpbus_reg_write(phydev, AN8801_BPBUS_REG_EFIFO_CTL(0),
+ AN8801_EFIFO_RX_EN |
+ AN8801_EFIFO_TX_EN |
+ AN8801_EFIFO_RX_CLK_EN |
+ AN8801_EFIFO_TX_CLK_EN |
+ AN8801_EFIFO_RX_EEE_EN |
+ AN8801_EFIFO_TX_EEE_EN);
+ if (ret)
+ return ret;
+
+ ret = an8801_buckpbus_reg_write(phydev, AN8801_BPBUS_REG_EFIFO_CTL(1),
+ AN8801_EFIFO_ALL_EN);
+ if (ret)
+ return ret;
+
+ ret = an8801_buckpbus_reg_write(phydev, AN8801_BPBUS_REG_EFIFO_CTL(2),
+ AN8801_EFIFO_ALL_EN);
+ if (ret)
+ return ret;
+
+ ret = an8801r_rgmii_delay_config(phydev);
+ if (ret)
+ return ret;
+
+ ret = an8801r_led_init(phydev);
+ if (ret) {
+ dev_err(phydev->dev, "AN8801R: LED initialize fail, ret %d!\n", ret);
+ return ret;
+ }
+ return 0;
+}
+
+static int an8801r_phy_probe(struct phy_device *phydev)
+{
+ struct an8801r_priv *priv;
+ u32 phy_id;
+ int ret;
+
+ ret = get_phy_id(phydev->bus, phydev->addr, MDIO_DEVAD_NONE, &phy_id);
+ if (ret)
+ return ret;
+
+ if (phy_id != AN8801R_PHY_ID) {
+ dev_err(phydev->dev,
+ "AN8801R can't be detected (id=0x%08x).\n", phy_id);
+ return -ENODEV;
+ }
+
+ priv = malloc(sizeof(*priv));
+ if (!priv)
+ return -ENOMEM;
+
+ *priv = an8801r_priv_defaults;
+
+ phydev->priv = priv;
+
+ return 0;
+}
+
+static int an8801r_read_status(struct phy_device *phydev)
+{
+ u32 data;
+
+ if (!phydev->link)
+ return 0;
+
+ debug("AN8801R: SPEED %d\n", phydev->speed);
+ data = phydev->speed == SPEED_1000 ? AN8801_BPBUS_LINK_MODE_1000 : 0;
+
+ return an8801_buckpbus_reg_rmw(phydev, AN8801_BPBUS_REG_LINK_MODE,
+ AN8801_BPBUS_LINK_MODE_1000, data);
+}
+
+static int an8801r_startup(struct phy_device *phydev)
+{
+ int ret;
+
+ ret = genphy_startup(phydev);
+ if (ret)
+ return ret;
+
+ return an8801r_read_status(phydev);
+}
+
+U_BOOT_PHY_DRIVER(an8801r) = {
+ .name = "Airoha AN8801R",
+ .uid = AN8801R_PHY_ID,
+ .mask = 0x0ffffff0,
+ .features = PHY_GBIT_FEATURES,
+ .probe = &an8801r_phy_probe,
+ .config = &an8801r_config_init,
+ .read_page = &air_phy_read_page,
+ .write_page = &air_phy_write_page,
+ .startup = &an8801r_startup,
+ .shutdown = &genphy_shutdown,
+};
diff --git a/drivers/net/phy/airoha/air_en8811.c b/drivers/net/phy/airoha/air_en8811.c
index 32f06dd6dfa..7a07be2e956 100644
--- a/drivers/net/phy/airoha/air_en8811.c
+++ b/drivers/net/phy/airoha/air_en8811.c
@@ -25,6 +25,8 @@
#include <u-boot/crc.h>
#include <linux/phy/phy-common-props.h>
+#include "air_phy_lib.h"
+
/* MII Registers */
#define AIR_AUX_CTRL_STATUS 0x1d
#define AIR_AUX_CTRL_STATUS_SPEED_MASK GENMASK(4, 2)
@@ -33,10 +35,6 @@
#define AIR_AUX_CTRL_STATUS_SPEED_1000 0x8
#define AIR_AUX_CTRL_STATUS_SPEED_2500 0xc
-#define AIR_EXT_PAGE_ACCESS 0x1f
-#define AIR_PHY_PAGE_STANDARD 0x0000
-#define AIR_PHY_PAGE_EXTENDED_4 0x0004
-
#define AIR_PBUS_MODE_ADDR_HIGH 0x1c
/* MII Registers Page 4 */
#define AIR_BPBUS_MODE 0x10
@@ -310,166 +308,6 @@ static int air_pbus_reg_write(struct phy_device *phydev,
return ret;
}
-static int air_buckpbus_reg_write(struct phy_device *phydev,
- u32 pbus_address, u32 pbus_data)
-{
- int ret, saved_page;
-
- saved_page = phy_select_page(phydev, AIR_PHY_PAGE_EXTENDED_4);
- if (saved_page < 0)
- return saved_page;
-
- ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_MODE,
- AIR_BPBUS_MODE_ADDR_FIXED);
- if (ret < 0)
- goto restore_page;
-
- ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_ADDR_HIGH,
- upper_16_bits(pbus_address));
- if (ret < 0)
- goto restore_page;
-
- ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_ADDR_LOW,
- lower_16_bits(pbus_address));
- if (ret < 0)
- goto restore_page;
-
- ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_DATA_HIGH,
- upper_16_bits(pbus_data));
- if (ret < 0)
- goto restore_page;
-
- ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_DATA_LOW,
- lower_16_bits(pbus_data));
- if (ret < 0)
- goto restore_page;
-
-restore_page:
- if (ret < 0)
- dev_err(phydev->dev, "%s 0x%08x failed: %d\n", __func__,
- pbus_address, ret);
-
- return phy_restore_page(phydev, saved_page, ret);
-}
-
-static int air_buckpbus_reg_read(struct phy_device *phydev,
- u32 pbus_address, u32 *pbus_data)
-{
- int pbus_data_low, pbus_data_high;
- int ret = 0, saved_page;
-
- saved_page = phy_select_page(phydev, AIR_PHY_PAGE_EXTENDED_4);
- if (saved_page < 0)
- return saved_page;
-
- ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_MODE,
- AIR_BPBUS_MODE_ADDR_FIXED);
- if (ret < 0)
- goto restore_page;
-
- ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_RD_ADDR_HIGH,
- upper_16_bits(pbus_address));
- if (ret < 0)
- goto restore_page;
-
- ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_RD_ADDR_LOW,
- lower_16_bits(pbus_address));
- if (ret < 0)
- goto restore_page;
-
- pbus_data_high = phy_read(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_RD_DATA_HIGH);
- if (pbus_data_high < 0) {
- ret = pbus_data_high;
- goto restore_page;
- }
-
- pbus_data_low = phy_read(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_RD_DATA_LOW);
- if (pbus_data_low < 0) {
- ret = pbus_data_low;
- goto restore_page;
- }
-
- *pbus_data = pbus_data_low | (pbus_data_high << 16);
-
-restore_page:
- if (ret < 0)
- dev_err(phydev->dev, "%s 0x%08x failed: %d\n", __func__,
- pbus_address, ret);
-
- return phy_restore_page(phydev, saved_page, ret);
-}
-
-static int air_buckpbus_reg_modify(struct phy_device *phydev,
- u32 pbus_address, u32 mask, u32 set)
-{
- int pbus_data_low, pbus_data_high;
- u32 pbus_data_old, pbus_data_new;
- int ret = 0, saved_page;
-
- saved_page = phy_select_page(phydev, AIR_PHY_PAGE_EXTENDED_4);
- if (saved_page < 0)
- return saved_page;
-
- ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_MODE,
- AIR_BPBUS_MODE_ADDR_FIXED);
- if (ret < 0)
- goto restore_page;
-
- ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_RD_ADDR_HIGH,
- upper_16_bits(pbus_address));
- if (ret < 0)
- goto restore_page;
-
- ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_RD_ADDR_LOW,
- lower_16_bits(pbus_address));
- if (ret < 0)
- goto restore_page;
-
- pbus_data_high = phy_read(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_RD_DATA_HIGH);
- if (pbus_data_high < 0) {
- ret = pbus_data_high;
- goto restore_page;
- }
-
- pbus_data_low = phy_read(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_RD_DATA_LOW);
- if (pbus_data_low < 0) {
- ret = pbus_data_low;
- goto restore_page;
- }
-
- pbus_data_old = pbus_data_low | (pbus_data_high << 16);
- pbus_data_new = (pbus_data_old & ~mask) | set;
- if (pbus_data_new == pbus_data_old)
- goto restore_page;
-
- ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_ADDR_HIGH,
- upper_16_bits(pbus_address));
- if (ret < 0)
- goto restore_page;
-
- ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_ADDR_LOW,
- lower_16_bits(pbus_address));
- if (ret < 0)
- goto restore_page;
-
- ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_DATA_HIGH,
- upper_16_bits(pbus_data_new));
- if (ret < 0)
- goto restore_page;
-
- ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_DATA_LOW,
- lower_16_bits(pbus_data_new));
- if (ret < 0)
- goto restore_page;
-
-restore_page:
- if (ret < 0)
- dev_err(phydev->dev, "%s 0x%08x failed: %d\n", __func__,
- pbus_address, ret);
-
- return phy_restore_page(phydev, saved_page, ret);
-}
-
static int air_write_buf(struct phy_device *phydev, unsigned long address,
unsigned long array_size, const unsigned char *buffer)
{
@@ -540,12 +378,12 @@ static int an8811hb_check_crc(struct phy_device *phydev,
u32 pbus_value;
/* Configure CRC */
- ret = air_buckpbus_reg_modify(phydev, set1, AN8811HB_CRC_RD_EN,
- AN8811HB_CRC_RD_EN);
+ ret = air_phy_buckpbus_reg_modify(phydev, set1, AN8811HB_CRC_RD_EN,
+ AN8811HB_CRC_RD_EN);
if (ret < 0)
return ret;
- ret = air_buckpbus_reg_read(phydev, set1, &pbus_value);
+ ret = air_phy_buckpbus_reg_read(phydev, set1, &pbus_value);
if (ret < 0)
return ret;
@@ -554,14 +392,14 @@ static int an8811hb_check_crc(struct phy_device *phydev,
do {
mdelay(300);
- ret = air_buckpbus_reg_read(phydev, mon2, &pbus_value);
+ ret = air_phy_buckpbus_reg_read(phydev, mon2, &pbus_value);
if (ret < 0)
return ret;
debug("%d: reg 0x%x val 0x%x!\n", __LINE__, mon2, pbus_value);
if (pbus_value & AN8811HB_CRC_ST) {
- ret = air_buckpbus_reg_read(phydev, mon3, &pbus_value);
+ ret = air_phy_buckpbus_reg_read(phydev, mon3, &pbus_value);
if (ret < 0)
return ret;
@@ -585,11 +423,11 @@ static int an8811hb_check_crc(struct phy_device *phydev,
}
} while (--retry);
- ret = air_buckpbus_reg_modify(phydev, set1, AN8811HB_CRC_RD_EN, 0);
+ ret = air_phy_buckpbus_reg_modify(phydev, set1, AN8811HB_CRC_RD_EN, 0);
if (ret < 0)
return ret;
- ret = air_buckpbus_reg_read(phydev, set1, &pbus_value);
+ ret = air_phy_buckpbus_reg_read(phydev, set1, &pbus_value);
if (ret < 0)
return ret;
@@ -647,9 +485,9 @@ static int an8811hb_surge_protect_cfg(struct phy_device *phydev)
return ret;
}
- ret = air_buckpbus_reg_modify(phydev, AIR_PHY_CONTROL,
- AIR_PHY_CONTROL_SURGE_5R,
- AIR_PHY_CONTROL_SURGE_5R);
+ ret = air_phy_buckpbus_reg_modify(phydev, AIR_PHY_CONTROL,
+ AIR_PHY_CONTROL_SURGE_5R,
+ AIR_PHY_CONTROL_SURGE_5R);
if (ret < 0)
return ret;
@@ -707,14 +545,14 @@ static int en8811h_load_firmware(struct phy_device *phydev)
goto en8811h_load_firmware_out;
}
- ret = air_buckpbus_reg_write(phydev, EN8811H_FW_CTRL_1,
- EN8811H_FW_CTRL_1_START);
+ ret = air_phy_buckpbus_reg_write(phydev, EN8811H_FW_CTRL_1,
+ EN8811H_FW_CTRL_1_START);
if (ret < 0)
goto en8811h_load_firmware_out;
- ret = air_buckpbus_reg_modify(phydev, EN8811H_FW_CTRL_2,
- EN8811H_FW_CTRL_2_LOADING,
- EN8811H_FW_CTRL_2_LOADING);
+ ret = air_phy_buckpbus_reg_modify(phydev, EN8811H_FW_CTRL_2,
+ EN8811H_FW_CTRL_2_LOADING,
+ EN8811H_FW_CTRL_2_LOADING);
if (ret < 0)
goto en8811h_load_firmware_out;
@@ -728,13 +566,13 @@ static int en8811h_load_firmware(struct phy_device *phydev)
if (ret < 0)
goto en8811h_load_firmware_out;
- ret = air_buckpbus_reg_modify(phydev, EN8811H_FW_CTRL_2,
- EN8811H_FW_CTRL_2_LOADING, 0);
+ ret = air_phy_buckpbus_reg_modify(phydev, EN8811H_FW_CTRL_2,
+ EN8811H_FW_CTRL_2_LOADING, 0);
if (ret < 0)
goto en8811h_load_firmware_out;
- ret = air_buckpbus_reg_write(phydev, EN8811H_FW_CTRL_1,
- EN8811H_FW_CTRL_1_FINISH);
+ ret = air_phy_buckpbus_reg_write(phydev, EN8811H_FW_CTRL_1,
+ EN8811H_FW_CTRL_1_FINISH);
if (ret < 0)
goto en8811h_load_firmware_out;
@@ -742,8 +580,8 @@ static int en8811h_load_firmware(struct phy_device *phydev)
if (ret < 0)
goto en8811h_load_firmware_out;
- air_buckpbus_reg_read(phydev, EN8811H_FW_VERSION,
- &priv->firmware_version);
+ air_phy_buckpbus_reg_read(phydev, EN8811H_FW_VERSION,
+ &priv->firmware_version);
dev_info(phydev->dev, "MD32 firmware version: %08x\n",
priv->firmware_version);
@@ -779,8 +617,8 @@ static int an8811hb_load_firmware(struct phy_device *phydev)
if (ret < 0)
goto an8811hb_load_firmware_out;
- ret = air_buckpbus_reg_write(phydev, AIR_PHY_FW_CTRL_1,
- AIR_PHY_FW_CTRL_1_START);
+ ret = air_phy_buckpbus_reg_write(phydev, AIR_PHY_FW_CTRL_1,
+ AIR_PHY_FW_CTRL_1_START);
if (ret < 0)
goto an8811hb_load_firmware_out;
@@ -804,8 +642,8 @@ static int an8811hb_load_firmware(struct phy_device *phydev)
if (ret < 0)
goto an8811hb_load_firmware_out;
- ret = air_buckpbus_reg_write(phydev, AIR_PHY_FW_CTRL_1,
- AIR_PHY_FW_CTRL_1_FINISH);
+ ret = air_phy_buckpbus_reg_write(phydev, AIR_PHY_FW_CTRL_1,
+ AIR_PHY_FW_CTRL_1_FINISH);
if (ret < 0)
goto an8811hb_load_firmware_out;
@@ -818,7 +656,7 @@ static int an8811hb_load_firmware(struct phy_device *phydev)
do {
mdelay(300);
- ret = air_buckpbus_reg_read(phydev, AIR_PHY_FW_CTRL_1, &reg_val);
+ ret = air_phy_buckpbus_reg_read(phydev, AIR_PHY_FW_CTRL_1, &reg_val);
if (ret < 0)
goto an8811hb_load_firmware_out;
@@ -828,8 +666,8 @@ static int an8811hb_load_firmware(struct phy_device *phydev)
debug("%d: reg 0x%x val 0x%x!\n", __LINE__, AIR_PHY_FW_CTRL_1,
reg_val);
- ret = air_buckpbus_reg_write(phydev, AIR_PHY_FW_CTRL_1,
- AIR_PHY_FW_CTRL_1_FINISH);
+ ret = air_phy_buckpbus_reg_write(phydev, AIR_PHY_FW_CTRL_1,
+ AIR_PHY_FW_CTRL_1_FINISH);
if (ret < 0)
goto an8811hb_load_firmware_out;
@@ -839,8 +677,8 @@ static int an8811hb_load_firmware(struct phy_device *phydev)
if (ret < 0)
goto an8811hb_load_firmware_out;
- air_buckpbus_reg_read(phydev, AIR_PHY_MD32FW_VERSION,
- &priv->firmware_version);
+ air_phy_buckpbus_reg_read(phydev, AIR_PHY_MD32FW_VERSION,
+ &priv->firmware_version);
debug("MD32 firmware version: %08x\n", priv->firmware_version);
@@ -859,17 +697,17 @@ int an8811hb_cko_cfg(struct phy_device *phydev)
int ret = 0;
if (!ofnode_read_bool(node, "airoha,phy-output-clock")) {
- ret = air_buckpbus_reg_modify(phydev, AN8811HB_CLK_DRV,
- AN8811HB_CLK_DRV_CKO_MASK,
- AN8811HB_CLK_DRV_CKOPWD |
- AN8811HB_CLK_DRV_CKO_LDPWD |
- AN8811HB_CLK_DRV_CKO_LPPWD);
+ ret = air_phy_buckpbus_reg_modify(phydev, AN8811HB_CLK_DRV,
+ AN8811HB_CLK_DRV_CKO_MASK,
+ AN8811HB_CLK_DRV_CKOPWD |
+ AN8811HB_CLK_DRV_CKO_LDPWD |
+ AN8811HB_CLK_DRV_CKO_LPPWD);
if (ret < 0)
return ret;
debug("CKO Output mode - Disabled\n");
} else {
- ret = air_buckpbus_reg_read(phydev, AN8811HB_HWTRAP2, &pbus_value);
+ ret = air_phy_buckpbus_reg_read(phydev, AN8811HB_HWTRAP2, &pbus_value);
if (ret < 0)
return ret;
@@ -888,13 +726,13 @@ static int en8811h_restart_mcu(struct phy_device *phydev)
if (ret < 0)
return ret;
- ret = air_buckpbus_reg_write(phydev, EN8811H_FW_CTRL_1,
- EN8811H_FW_CTRL_1_START);
+ ret = air_phy_buckpbus_reg_write(phydev, EN8811H_FW_CTRL_1,
+ EN8811H_FW_CTRL_1_START);
if (ret < 0)
return ret;
- return air_buckpbus_reg_write(phydev, EN8811H_FW_CTRL_1,
- EN8811H_FW_CTRL_1_FINISH);
+ return air_phy_buckpbus_reg_write(phydev, EN8811H_FW_CTRL_1,
+ EN8811H_FW_CTRL_1_FINISH);
}
static int air_led_hw_control_set(struct phy_device *phydev, u8 index,
@@ -1083,9 +921,10 @@ static int en8811h_config_serdes_polarity(struct phy_device *phydev)
if (pol == PHY_POL_NORMAL)
pbus_value |= EN8811H_POLARITY_TX_NORMAL;
- return air_buckpbus_reg_modify(phydev, EN8811H_POLARITY,
- EN8811H_POLARITY_RX_REVERSE |
- EN8811H_POLARITY_TX_NORMAL, pbus_value);
+ return air_phy_buckpbus_reg_modify(phydev, EN8811H_POLARITY,
+ EN8811H_POLARITY_RX_REVERSE |
+ EN8811H_POLARITY_TX_NORMAL,
+ pbus_value);
}
static int en8811h_config(struct phy_device *phydev)
@@ -1170,12 +1009,12 @@ static int an8811hb_config(struct phy_device *phydev)
priv->mcu_needs_restart = true;
}
- ret = air_buckpbus_reg_read(phydev, AN8811HB_PRO_ID, &pbus_value);
+ ret = air_phy_buckpbus_reg_read(phydev, AN8811HB_PRO_ID, &pbus_value);
if (ret < 0)
return ret;
priv->pro_id = (pbus_value & AN8811HB_PRO_ID_VERSION) + 1;
- ret = air_buckpbus_reg_read(phydev, AN8811HB_HWTRAP2, &pbus_value);
+ ret = air_phy_buckpbus_reg_read(phydev, AN8811HB_HWTRAP2, &pbus_value);
if (ret < 0)
return ret;
priv->pkg_sel = (pbus_value & AN8811HB_HWTRAP2_PKG) >> 12;
@@ -1191,8 +1030,8 @@ static int an8811hb_config(struct phy_device *phydev)
pbus_value |= AN8811HB_RX_POLARITY_NORMAL;
debug("1 pbus_value 0x%x\n", pbus_value);
- ret = air_buckpbus_reg_modify(phydev, AN8811HB_RX_POLARITY,
- AN8811HB_RX_POLARITY_NORMAL, pbus_value);
+ ret = air_phy_buckpbus_reg_modify(phydev, AN8811HB_RX_POLARITY,
+ AN8811HB_RX_POLARITY_NORMAL, pbus_value);
if (ret < 0)
return ret;
@@ -1203,35 +1042,35 @@ static int an8811hb_config(struct phy_device *phydev)
pbus_value |= AN8811HB_TX_POLARITY_NORMAL;
debug("2 pbus_value 0x%x\n", pbus_value);
- ret = air_buckpbus_reg_modify(phydev, AN8811HB_TX_POLARITY,
- AN8811HB_TX_POLARITY_NORMAL, pbus_value);
+ ret = air_phy_buckpbus_reg_modify(phydev, AN8811HB_TX_POLARITY,
+ AN8811HB_TX_POLARITY_NORMAL, pbus_value);
if (ret < 0)
return ret;
/* Configure led gpio pins as output */
if (priv->pkg_sel) {
- ret = air_buckpbus_reg_modify(phydev, AN8811HB_GPIO_OUTPUT,
- AN8811HB_GPIO_OUTPUT_MASK,
- AN8811HB_GPIO_OUTPUT_0115);
+ ret = air_phy_buckpbus_reg_modify(phydev, AN8811HB_GPIO_OUTPUT,
+ AN8811HB_GPIO_OUTPUT_MASK,
+ AN8811HB_GPIO_OUTPUT_0115);
if (ret < 0)
return ret;
- ret = air_buckpbus_reg_modify(phydev, AN8811HB_GPIO_SEL_1,
- AN8811HB_GPIO_SEL_1_0_MASK |
- AN8811HB_GPIO_SEL_1_1_MASK,
- AN8811HB_GPIO_SEL_1_0 |
- AN8811HB_GPIO_SEL_1_1);
+ ret = air_phy_buckpbus_reg_modify(phydev, AN8811HB_GPIO_SEL_1,
+ AN8811HB_GPIO_SEL_1_0_MASK |
+ AN8811HB_GPIO_SEL_1_1_MASK,
+ AN8811HB_GPIO_SEL_1_0 |
+ AN8811HB_GPIO_SEL_1_1);
if (ret < 0)
return ret;
- ret = air_buckpbus_reg_modify(phydev, AN8811HB_GPIO_SEL_2,
- AN8811HB_GPIO_SEL_2_15_MASK,
- AN8811HB_GPIO_SEL_2_15);
+ ret = air_phy_buckpbus_reg_modify(phydev, AN8811HB_GPIO_SEL_2,
+ AN8811HB_GPIO_SEL_2_15_MASK,
+ AN8811HB_GPIO_SEL_2_15);
if (ret < 0)
return ret;
} else {
- ret = air_buckpbus_reg_modify(phydev, AN8811HB_GPIO_OUTPUT,
- AN8811HB_GPIO_OUTPUT_345,
- AN8811HB_GPIO_OUTPUT_345);
+ ret = air_phy_buckpbus_reg_modify(phydev, AN8811HB_GPIO_OUTPUT,
+ AN8811HB_GPIO_OUTPUT_345,
+ AN8811HB_GPIO_OUTPUT_345);
if (ret < 0)
return ret;
}
@@ -1401,16 +1240,6 @@ static int en8811h_probe(struct phy_device *phydev)
return 0;
}
-static int air_phy_read_page(struct phy_device *phydev)
-{
- return phy_read(phydev, MDIO_DEVAD_NONE, AIR_EXT_PAGE_ACCESS);
-}
-
-static int air_phy_write_page(struct phy_device *phydev, int page)
-{
- return phy_write(phydev, MDIO_DEVAD_NONE, AIR_EXT_PAGE_ACCESS, page);
-}
-
U_BOOT_PHY_DRIVER(en8811h) = {
.name = "Airoha EN8811H",
.uid = EN8811H_PHY_ID,
diff --git a/drivers/net/phy/airoha/air_phy_lib.c b/drivers/net/phy/airoha/air_phy_lib.c
new file mode 100644
index 00000000000..61c3bf82822
--- /dev/null
+++ b/drivers/net/phy/airoha/air_phy_lib.c
@@ -0,0 +1,216 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Airoha Ethernet PHY common library
+ *
+ * Copyright (C) 2026 Airoha Technology Corp.
+ * Copyright (C) 2026 Collabora Ltd.
+ * Louis-Alexis Eyraud <[email protected]>
+ *
+ * Adapated from https://lore.kernel.org/all/20260326-add-airoha-an8801-support-v2-2-1a42d6b6050f@collabora.com/
+ */
+
+#include <dm/device_compat.h>
+#include <linux/compat.h>
+#include <phy.h>
+
+#include "air_phy_lib.h"
+
+#define AIR_EXT_PAGE_ACCESS 0x1f
+
+static int __air_buckpbus_reg_read(struct phy_device *phydev,
+ u32 pbus_address, u32 *pbus_data)
+{
+ int pbus_data_low, pbus_data_high;
+ int ret;
+
+ ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_MODE,
+ AIR_BPBUS_MODE_ADDR_FIXED);
+ if (ret < 0)
+ return ret;
+
+ ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_RD_ADDR_HIGH,
+ upper_16_bits(pbus_address));
+ if (ret < 0)
+ return ret;
+
+ ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_RD_ADDR_LOW,
+ lower_16_bits(pbus_address));
+ if (ret < 0)
+ return ret;
+
+ pbus_data_high = phy_read(phydev, MDIO_DEVAD_NONE,
+ AIR_BPBUS_RD_DATA_HIGH);
+ if (pbus_data_high < 0)
+ return pbus_data_high;
+
+ pbus_data_low = phy_read(phydev, MDIO_DEVAD_NONE,
+ AIR_BPBUS_RD_DATA_LOW);
+ if (pbus_data_low < 0)
+ return pbus_data_low;
+
+ *pbus_data = pbus_data_low | (pbus_data_high << 16);
+ return 0;
+}
+
+static int __air_buckpbus_reg_write(struct phy_device *phydev,
+ u32 pbus_address, u32 pbus_data)
+{
+ int ret;
+
+ ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_MODE,
+ AIR_BPBUS_MODE_ADDR_FIXED);
+ if (ret < 0)
+ return ret;
+
+ ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_ADDR_HIGH,
+ upper_16_bits(pbus_address));
+ if (ret < 0)
+ return ret;
+
+ ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_ADDR_LOW,
+ lower_16_bits(pbus_address));
+ if (ret < 0)
+ return ret;
+
+ ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_DATA_HIGH,
+ upper_16_bits(pbus_data));
+ if (ret < 0)
+ return ret;
+
+ ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_DATA_LOW,
+ lower_16_bits(pbus_data));
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int __air_buckpbus_reg_modify(struct phy_device *phydev,
+ u32 pbus_address, u32 mask, u32 set)
+{
+ int pbus_data_low, pbus_data_high;
+ u32 pbus_data_old, pbus_data_new;
+ int ret;
+
+ ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_MODE,
+ AIR_BPBUS_MODE_ADDR_FIXED);
+ if (ret < 0)
+ return ret;
+
+ ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_RD_ADDR_HIGH,
+ upper_16_bits(pbus_address));
+ if (ret < 0)
+ return ret;
+
+ ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_RD_ADDR_LOW,
+ lower_16_bits(pbus_address));
+ if (ret < 0)
+ return ret;
+
+ pbus_data_high = phy_read(phydev, MDIO_DEVAD_NONE,
+ AIR_BPBUS_RD_DATA_HIGH);
+ if (pbus_data_high < 0)
+ return pbus_data_high;
+
+ pbus_data_low = phy_read(phydev, MDIO_DEVAD_NONE,
+ AIR_BPBUS_RD_DATA_LOW);
+ if (pbus_data_low < 0)
+ return pbus_data_low;
+
+ pbus_data_old = pbus_data_low | (pbus_data_high << 16);
+ pbus_data_new = (pbus_data_old & ~mask) | set;
+ if (pbus_data_new == pbus_data_old)
+ return 0;
+
+ ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_ADDR_HIGH,
+ upper_16_bits(pbus_address));
+ if (ret < 0)
+ return ret;
+
+ ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_ADDR_LOW,
+ lower_16_bits(pbus_address));
+ if (ret < 0)
+ return ret;
+
+ ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_DATA_HIGH,
+ upper_16_bits(pbus_data_new));
+ if (ret < 0)
+ return ret;
+
+ ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_DATA_LOW,
+ lower_16_bits(pbus_data_new));
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+int air_phy_buckpbus_reg_read(struct phy_device *phydev, u32 pbus_address,
+ u32 *pbus_data)
+{
+ int saved_page;
+ int ret = 0;
+
+ saved_page = phy_select_page(phydev, AIR_PHY_PAGE_EXTENDED_4);
+
+ if (saved_page >= 0) {
+ ret = __air_buckpbus_reg_read(phydev, pbus_address, pbus_data);
+ if (ret < 0)
+ dev_err(phydev->dev, "%s 0x%08x failed: %d\n", __func__,
+ pbus_address, ret);
+ }
+
+ return phy_restore_page(phydev, saved_page, ret);
+}
+
+int air_phy_buckpbus_reg_write(struct phy_device *phydev, u32 pbus_address,
+ u32 pbus_data)
+{
+ int saved_page;
+ int ret = 0;
+
+ saved_page = phy_select_page(phydev, AIR_PHY_PAGE_EXTENDED_4);
+
+ if (saved_page >= 0) {
+ ret = __air_buckpbus_reg_write(phydev, pbus_address,
+ pbus_data);
+ if (ret < 0)
+ dev_err(phydev->dev, "%s 0x%08x failed: %d\n", __func__,
+ pbus_address, ret);
+ }
+
+ return phy_restore_page(phydev, saved_page, ret);
+}
+
+int air_phy_buckpbus_reg_modify(struct phy_device *phydev, u32 pbus_address,
+ u32 mask, u32 set)
+{
+ int saved_page;
+ int ret = 0;
+
+ saved_page = phy_select_page(phydev, AIR_PHY_PAGE_EXTENDED_4);
+
+ if (saved_page >= 0) {
+ ret = __air_buckpbus_reg_modify(phydev, pbus_address, mask,
+ set);
+ if (ret < 0)
+ dev_err(phydev->dev, "%s 0x%08x failed: %d\n", __func__,
+ pbus_address, ret);
+ }
+
+ return phy_restore_page(phydev, saved_page, ret);
+}
+
+int air_phy_read_page(struct phy_device *phydev)
+{
+ return phy_read(phydev, MDIO_DEVAD_NONE, AIR_EXT_PAGE_ACCESS);
+}
+
+int air_phy_write_page(struct phy_device *phydev, int page)
+{
+ return phy_write(phydev, MDIO_DEVAD_NONE, AIR_EXT_PAGE_ACCESS, page);
+}
+
+MODULE_DESCRIPTION("Airoha PHY Library");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Louis-Alexis Eyraud");
diff --git a/drivers/net/phy/airoha/air_phy_lib.h b/drivers/net/phy/airoha/air_phy_lib.h
new file mode 100644
index 00000000000..845d2f7cfb4
--- /dev/null
+++ b/drivers/net/phy/airoha/air_phy_lib.h
@@ -0,0 +1,39 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright (C) 2026 Airoha Technology Corp.
+ * Copyright (C) 2026 Collabora Ltd.
+ * Louis-Alexis Eyraud <[email protected]>
+ */
+
+#ifndef __AIR_PHY_LIB_H
+#define __AIR_PHY_LIB_H
+
+#define AIR_EXT_PAGE_ACCESS 0x1f
+
+#define AIR_PHY_PAGE_STANDARD 0x0000
+#define AIR_PHY_PAGE_EXTENDED_1 0x0001
+#define AIR_PHY_PAGE_EXTENDED_4 0x0004
+
+/* MII Registers Page 4*/
+#define AIR_BPBUS_MODE 0x10
+#define AIR_BPBUS_MODE_ADDR_FIXED 0x0000
+#define AIR_BPBUS_MODE_ADDR_INCR BIT(15)
+#define AIR_BPBUS_WR_ADDR_HIGH 0x11
+#define AIR_BPBUS_WR_ADDR_LOW 0x12
+#define AIR_BPBUS_WR_DATA_HIGH 0x13
+#define AIR_BPBUS_WR_DATA_LOW 0x14
+#define AIR_BPBUS_RD_ADDR_HIGH 0x15
+#define AIR_BPBUS_RD_ADDR_LOW 0x16
+#define AIR_BPBUS_RD_DATA_HIGH 0x17
+#define AIR_BPBUS_RD_DATA_LOW 0x18
+
+int air_phy_buckpbus_reg_modify(struct phy_device *phydev, u32 pbus_address,
+ u32 mask, u32 set);
+int air_phy_buckpbus_reg_read(struct phy_device *phydev, u32 pbus_address,
+ u32 *pbus_data);
+int air_phy_buckpbus_reg_write(struct phy_device *phydev, u32 pbus_address,
+ u32 pbus_data);
+int air_phy_read_page(struct phy_device *phydev);
+int air_phy_write_page(struct phy_device *phydev, int page);
+
+#endif /* __AIR_PHY_LIB_H */
diff --git a/drivers/pinctrl/mediatek/pinctrl-mtk-common.c b/drivers/pinctrl/mediatek/pinctrl-mtk-common.c
index cfffbaeef84..01f67f09407 100644
--- a/drivers/pinctrl/mediatek/pinctrl-mtk-common.c
+++ b/drivers/pinctrl/mediatek/pinctrl-mtk-common.c
@@ -251,7 +251,7 @@ static int mtk_pinconf_get(struct udevice *dev, u32 pin, char *buf, size_t size)
if (mtk_get_pin_io_type(dev, pin, &io_type))
return 0;
- pos = snprintf(buf, size, " (%s)", io_type.name);
+ pos = scnprintf(buf, size, " (%s)", io_type.name);
if (pos >= size)
return pos;
@@ -306,7 +306,7 @@ static int mtk_get_pin_muxing(struct udevice *dev, unsigned int selector,
if (err)
return err;
- pos = snprintf(buf, size, "Aux Func.%d", val);
+ pos = scnprintf(buf, size, "Aux Func.%d", val);
if (pos >= size)
return 0;
@@ -721,7 +721,7 @@ int mtk_pinconf_get_pu_pd(struct udevice *dev, u32 pin, char *buf, size_t size)
if (err)
return err;
- return snprintf(buf, size, " PU:%d PD:%d", pu, pd);
+ return scnprintf(buf, size, " PU:%d PD:%d", pu, pd);
}
int mtk_pinconf_get_pupd_r1_r0(struct udevice *dev, u32 pin, char *buf, size_t size)
@@ -740,7 +740,7 @@ int mtk_pinconf_get_pupd_r1_r0(struct udevice *dev, u32 pin, char *buf, size_t s
if (err)
return err;
- return snprintf(buf, size, " PUPD:%d R1:%d R0:%d", pupd, r1, r0);
+ return scnprintf(buf, size, " PUPD:%d R1:%d R0:%d", pupd, r1, r0);
}
int mtk_pinconf_get_pu_pd_rsel(struct udevice *dev, u32 pin, char *buf, size_t size)
@@ -755,7 +755,7 @@ int mtk_pinconf_get_pu_pd_rsel(struct udevice *dev, u32 pin, char *buf, size_t s
if (err)
return err;
- return pos + snprintf(buf + pos, size - pos, " RSEL:%d", rsel);
+ return pos + scnprintf(buf + pos, size - pos, " RSEL:%d", rsel);
}
#endif