diff --git a/patches-24.10/0044-mediatek-edgecore-eap111-fixes.patch b/patches-24.10/0044-mediatek-edgecore-eap111-fixes.patch new file mode 100644 index 000000000..73642c18a --- /dev/null +++ b/patches-24.10/0044-mediatek-edgecore-eap111-fixes.patch @@ -0,0 +1,2148 @@ +From 69c79b7f76c992a0d95bd94a4654c4da0ec2d319 Mon Sep 17 00:00:00 2001 +From: John Crispin +Date: Sat, 2 Aug 2025 06:47:35 +0200 +Subject: [PATCH] mediatek: edgecore eap111 fixes + +* add dual boot support +* add bootcount support +* add led support +* add macaddr support +* swap eth ports +* add edgecore_eap111_eeprom_dbdc.bin +* fix dts to allow cmdline + +Signed-off-by: John Crispin +--- + package/kernel/mt76/Makefile | 4 + + .../mt76/edgecore_eap111_eeprom_dbdc.bin | Bin 0 -> 4096 bytes + .../mediatek/dts/mt7981a-edgecore-eap111.dts | 22 +- + .../files-6.6/drivers/net/phy/an8801.c | 1534 +++++++++++++++++ + .../files-6.6/drivers/net/phy/an8801.h | 233 +++ + .../filogic/base-files/etc/board.d/01_leds | 4 + + .../filogic/base-files/etc/board.d/02_network | 9 +- + .../filogic/base-files/etc/init.d/bootcount | 8 + + .../base-files/lib/upgrade/platform.sh | 32 +- + target/linux/mediatek/filogic/config-6.6 | 1 + + .../970-eap111-swap-eth-ports.patch | 14 + + ...y-support-an8801-and-mdio-second-phy.patch | 92 + + 12 files changed, 1947 insertions(+), 6 deletions(-) + create mode 100644 package/kernel/mt76/edgecore_eap111_eeprom_dbdc.bin + create mode 100644 target/linux/mediatek/files-6.6/drivers/net/phy/an8801.c + create mode 100644 target/linux/mediatek/files-6.6/drivers/net/phy/an8801.h + create mode 100644 target/linux/mediatek/patches-6.6/970-eap111-swap-eth-ports.patch + create mode 100644 target/linux/mediatek/patches-6.6/999--net-phy-support-an8801-and-mdio-second-phy.patch + +diff --git a/package/kernel/mt76/Makefile b/package/kernel/mt76/Makefile +index 6943acd89d..481e425c1d 100644 +--- a/package/kernel/mt76/Makefile ++++ b/package/kernel/mt76/Makefile +@@ -618,6 +618,10 @@ define KernelPackage/mt7981-firmware/install + $(PKG_BUILD_DIR)/firmware/mt7981_wm.bin \ + $(PKG_BUILD_DIR)/firmware/mt7981_rom_patch.bin \ + $(1)/lib/firmware/mediatek ++ifeq ($(CONFIG_TARGET_PROFILE), "DEVICE_edgecore_eap111") ++ cp ./edgecore_eap111_eeprom_dbdc.bin \ ++ $(1)/lib/firmware/mediatek/mt7981_eeprom_mt7976_dbdc.bin ++endif + endef + + define KernelPackage/mt7986-firmware/install +diff --git a/package/kernel/mt76/edgecore_eap111_eeprom_dbdc.bin b/package/kernel/mt76/edgecore_eap111_eeprom_dbdc.bin +new file mode 100644 +index 0000000000000000000000000000000000000000..90849cde45042ed0412f1c211d8791509de2f0d4 +GIT binary patch +literal 4096 +zcmeHJJ5Ss|5T1*a0x8uAlqRG=DM*22A4Cu;qX;QRC_3AL6v-b+b?o_ZoTw<%M&c(> +z(Rd&BwV_JI4zbyyV01c*3-30GT*Iu%{6FdT``4Q^PxSE1{X72w +zJJ|WWPoHQ%m&yY?s$?7ThC9%1x5q2Y0gHdYcM<|xICU@*AgJ`hFxLJQ6r>-%AtQGaJ8iR2`t5QIcuTxf~;#5xE- +zXipfHR^c2D`~4sYqrqS}EWK40Mw?^CQHo{Xx3qL{5JXWQdxg>N?%rPMt+KB1P2AB| +zhp3xgFF +z#Cf@cY|nE6LcWf7Tpi~+9&p>oEb5iNywlUOvjyAeyXt@+;QCh*^Ut_SaPJeBxcM1l +u*&l<+5g%!>l32wumX@><3zj8g5reZJ;D1|n6|HnKsoS^Vn-1J$2Yvwy8Fz*N + +literal 0 +HcmV?d00001 + +diff --git a/target/linux/mediatek/dts/mt7981a-edgecore-eap111.dts b/target/linux/mediatek/dts/mt7981a-edgecore-eap111.dts +index 0e7c4fe8b5..1a0b845347 100644 +--- a/target/linux/mediatek/dts/mt7981a-edgecore-eap111.dts ++++ b/target/linux/mediatek/dts/mt7981a-edgecore-eap111.dts +@@ -19,7 +19,6 @@ + }; + + chosen { +- bootargs-override = "console=ttyS0,115200n8"; + stdout-path = "serial0:115200n8"; + }; + +@@ -150,9 +149,13 @@ + }; + + partition@580000 { +- label = "ubi"; ++ label = "rootfs1"; + reg = <0x580000 0x4000000>; +- compatible = "linux,ubi"; ++ }; ++ ++ partition@4580000 { ++ label = "rootfs2"; ++ reg = <0x4580000 0x4000000>; + }; + }; + }; +@@ -168,6 +171,16 @@ + compatible = "ethernet-phy-id03a2.9471"; + phy-mode = "sgmii"; + }; ++ ++ an8801sb: ethernet-phy@30 { ++ reg = <30>; ++ compatible = "ethernet-phy-idc0ff.0421"; ++ phy-mode = "sgmii"; ++ full-duplex; ++ pause; ++ airoha,surge = <0>; ++ airoha,polarity = <2>; ++ }; + }; + + ð { +@@ -179,7 +192,8 @@ + compatible = "mediatek,eth-mac"; + reg = <0>; + phy-mode = "sgmii"; +- phy-handle = <&en8801sc>; ++ phy-handle = <&an8801sb>; ++ phy-handle2 = <&en8801sc>; + managed = "in-band-status"; + nvmem-cells = <&macaddr_lan>; + nvmem-cell-names = "mac-address"; +diff --git a/target/linux/mediatek/files-6.6/drivers/net/phy/an8801.c b/target/linux/mediatek/files-6.6/drivers/net/phy/an8801.c +new file mode 100644 +index 0000000000..293c8c031e +--- /dev/null ++++ b/target/linux/mediatek/files-6.6/drivers/net/phy/an8801.c +@@ -0,0 +1,1534 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/*PURPOSE: ++ *Airoha phy driver for Linux ++ *NOTES: ++ * ++ */ ++ ++/*INCLUDE FILE DECLARATIONS ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "an8801.h" ++ ++MODULE_DESCRIPTION("Airoha AN8801 PHY drivers"); ++MODULE_AUTHOR("Airoha"); ++MODULE_LICENSE("GPL"); ++ ++#if (KERNEL_VERSION(4, 5, 0) > LINUX_VERSION_CODE) ++#define phydev_mdiobus(_dev) (_dev->bus) ++#define phydev_phy_addr(_dev) (_dev->addr) ++#define phydev_dev(_dev) (&_dev->dev) ++#else ++#define phydev_mdiobus(_dev) (_dev->mdio.bus) ++#define phydev_phy_addr(_dev) (_dev->mdio.addr) ++#define phydev_dev(_dev) (&_dev->mdio.dev) ++#endif ++#define phydev_cfg(phy) ((struct an8801_priv *)(phy)->priv) ++#define phydev_mdiobus_lock(phy) (phydev_mdiobus(phy)->mdio_lock) ++#define mdiobus_lock(phy) (mutex_lock(&phydev_mdiobus_lock(phy))) ++#define mdiobus_unlock(phy) (mutex_unlock(&phydev_mdiobus_lock(phy))) ++ ++#define MAX_SGMII_AN_RETRY (100) ++#define MCS_LINK_STATUS_MASK (BIT(2)) ++ ++#ifdef AN8801SB_DEBUGFS ++#define AN8801_DEBUGFS_POLARITY_HELP_STRING \ ++ "\nUsage: echo [tx_polarity] [rx_polarity] > /sys/" \ ++ "kernel/debug/mdio-bus\':[phy_addr]/polarity" \ ++ "\npolarity: tx_normal, tx_reverse, rx_normal, rx_reverse" \ ++ "\ntx_normal is tx polarity is normal." \ ++ "\ntx_reverse is tx polarity need to be swapped." \ ++ "\nrx_normal is rx polarity is normal." \ ++ "\nrx_reverse is rx polarity need to be swapped." \ ++ "\nFor example tx polarity need to be swapped. " \ ++ "But rx polarity is normal." \ ++ "\necho tx_reverse rx_normal > /sys/" \ ++ "kernel/debug/mdio-bus\':[phy_addr]/polarity" \ ++ "\n" ++#define AN8801_DEBUGFS_RX_ERROR_STRING \ ++ "\nRx param is not correct." \ ++ "\nrx_normal: rx polarity is normal." \ ++ "rx_reverse: rx polarity is reverse.\n" ++#define AN8801_DEBUGFS_TX_ERROR_STRING \ ++ "\nTx param is not correct." \ ++ "\ntx_normal: tx polarity is normal." \ ++ "tx_reverse: tx polarity is reverse.\n" ++#define AN8801_DEBUGFS_PBUS_HELP_STRING \ ++ "\nUsage: echo w [pbus_addr] [pbus_reg] [value] > /sys/" \ ++ "kernel/debug/mdio-bus\':[phy_addr]/pbus_reg_op" \ ++ "\n echo r [pbus_addr] [pbus_reg] > /sys/" \ ++ "kernel/debug/mdio-bus\':[phy_addr]/pbus_op" \ ++ "\nRead example: PBUS addr 0x19, Register 0x19a4" \ ++ "\necho r 19 19a4 > /sys/" \ ++ "kernel/debug/mdio-bus\':[phy_addr]/pbus_reg_op" \ ++ "\nWrite example: PBUS addr 0x19, Register 0xcf8 0x1a01503" \ ++ "\necho w 19 cf8 1a01503> /sys/" \ ++ "kernel/debug/mdio-bus\':[phy_addr]/pbus_op" \ ++ "\n" ++#define AN8801_DEBUGFS_MDIO_HELP_STRING \ ++ "\nUsage: echo cl22 w [phy_reg] [value]> /sys/" \ ++ "kernel/debug/mdio-bus\':[phy_addr]/mdio" \ ++ "\n echo cl22 r [phy_reg] > /sys/" \ ++ "kernel/debug/mdio-bus\':[phy_addr]/mdio" \ ++ "\nUsage: echo cl45 w [devad] [phy_reg] [value]> /sys/" \ ++ "kernel/debug/mdio-bus\':[phy_addr]/mdio" \ ++ "\n echo cl45 r [devad] [phy_reg] > /sys/" \ ++ "kernel/debug/mdio-bus\':[phy_addr]/mdio" \ ++ "\n" ++#endif ++ ++/* For reference only ++ * GPIO1 <-> LED0, ++ * GPIO2 <-> LED1, ++ * GPIO3 <-> LED2, ++ */ ++/* User-defined.B */ ++#define R50_SHIFT (-7) ++static const u16 r50ohm_table[] = { ++127, 127, 127, 127, 127, 127, 127, 127, 127, 127, ++127, 127, 127, 127, 127, 127, 127, 127, 127, 124, ++120, 116, 112, 108, 104, 100, 96, 93, 90, 86, ++84, 80, 77, 74, 72, 68, 65, 64, 61, 59, ++56, 54, 52, 48, 48, 45, 43, 40, 39, 36, ++35, 32, 32, 30, 28, 26, 24, 23, 21, 20, ++18, 16, 16, 14 ++}; ++static const u16 r50ohm_table_size = sizeof(r50ohm_table)/sizeof(u16); ++static const struct AIR_LED_CFG_T led_cfg_dlt[MAX_LED_SIZE] = { ++// LED Enable, GPIO, LED Polarity, LED ON, LED Blink ++ /* LED0 */ ++ {LED_ENABLE, AIR_LED_GPIO5, AIR_ACTIVE_LOW, AIR_LED0_ON, AIR_LED0_BLK}, ++ /* LED1 */ ++ {LED_ENABLE, AIR_LED_GPIO8, AIR_ACTIVE_LOW, AIR_LED1_ON, AIR_LED1_BLK}, ++ /* LED2 */ ++ {LED_ENABLE, AIR_LED_GPIO9, AIR_ACTIVE_LOW, AIR_LED2_ON, AIR_LED2_BLK}, ++}; ++static const u16 led_blink_cfg_dlt = AIR_LED_BLK_DUR_64M; ++/* RGMII delay */ ++static const u8 rxdelay_force = FALSE; ++static const u8 txdelay_force = FALSE; ++static const u16 rxdelay_step = AIR_RGMII_DELAY_NOSTEP; ++static const u8 rxdelay_align = FALSE; ++static const u16 txdelay_step = AIR_RGMII_DELAY_NOSTEP; ++/* User-defined.E */ ++ ++/************************************************************************ ++ * F U N C T I O N S ++ ************************************************************************/ ++static int __air_buckpbus_reg_write(struct phy_device *phydev, u32 addr, ++ u32 data) ++{ ++ int err = 0; ++ int phy_addr = phydev_phy_addr(phydev); ++ struct mii_bus *mbus = phydev_mdiobus(phydev); ++ ++ err = mbus->write(mbus, phy_addr, 0x1F, 4); ++ err |= mbus->write(mbus, phy_addr, 0x10, 0); ++ err |= mbus->write(mbus, phy_addr, 0x11, (u16)(addr >> 16)); ++ err |= mbus->write(mbus, phy_addr, 0x12, (u16)(addr & 0xffff)); ++ err |= mbus->write(mbus, phy_addr, 0x13, (u16)(data >> 16)); ++ err |= mbus->write(mbus, phy_addr, 0x14, (u16)(data & 0xffff)); ++ err |= mbus->write(mbus, phy_addr, 0x1F, 0); ++ return err; ++} ++ ++static u32 __air_buckpbus_reg_read(struct phy_device *phydev, u32 addr) ++{ ++ int err = 0; ++ u32 data_h, data_l, data; ++ int phy_addr = phydev_phy_addr(phydev); ++ struct mii_bus *mbus = phydev_mdiobus(phydev); ++ ++ err = mbus->write(mbus, phy_addr, 0x1F, 4); ++ err |= mbus->write(mbus, phy_addr, 0x10, 0); ++ err |= mbus->write(mbus, phy_addr, 0x15, (u16)(addr >> 16)); ++ err |= mbus->write(mbus, phy_addr, 0x16, (u16)(addr & 0xffff)); ++ if (err < 0) ++ return INVALID_DATA; ++ data_h = mbus->read(mbus, phy_addr, 0x17); ++ data_l = mbus->read(mbus, phy_addr, 0x18); ++ err |= mbus->write(mbus, phy_addr, 0x1F, 0); ++ if (err < 0) ++ return INVALID_DATA; ++ ++ data = ((data_h & 0xffff) << 16) | (data_l & 0xffff); ++ return data; ++} ++ ++static u32 __air_buckpbus_reg_modify(struct phy_device *phydev, u32 addr, ++ u32 mask, u32 set) ++{ ++ int err = 0; ++ u32 data_h, data_l, data_old, data_new; ++ int phy_addr = phydev_phy_addr(phydev); ++ struct mii_bus *mbus = phydev_mdiobus(phydev); ++ ++ err = mbus->write(mbus, phy_addr, 0x1F, 4); ++ err |= mbus->write(mbus, phy_addr, 0x10, 0); ++ err |= mbus->write(mbus, phy_addr, 0x15, (u16)(addr >> 16)); ++ err |= mbus->write(mbus, phy_addr, 0x16, (u16)(addr & 0xffff)); ++ data_h = mbus->read(mbus, phy_addr, 0x17); ++ data_l = mbus->read(mbus, phy_addr, 0x18); ++ if (err < 0) { ++ mbus->write(mbus, phy_addr, 0x1F, 0); ++ return INVALID_DATA; ++ } ++ ++ data_old = ((data_h & 0xffff) << 16) | (data_l & 0xffff); ++ data_new = (data_old & ~mask) | set; ++ if (data_new == data_old) { ++ mbus->write(mbus, phy_addr, 0x1F, 0); ++ return 0; ++ } ++ ++ err |= mbus->write(mbus, phy_addr, 0x11, (u16)(addr >> 16)); ++ err |= mbus->write(mbus, phy_addr, 0x12, (u16)(addr & 0xffff)); ++ err |= mbus->write(mbus, phy_addr, 0x13, (u16)(data_new >> 16)); ++ err |= mbus->write(mbus, phy_addr, 0x14, (u16)(data_new & 0xffff)); ++ err |= mbus->write(mbus, phy_addr, 0x1F, 0); ++ ++ return err; ++} ++ ++static int air_buckpbus_reg_write(struct phy_device *phydev, u32 addr, u32 data) ++{ ++ int err = 0; ++ ++ mdiobus_lock(phydev); ++ err = __air_buckpbus_reg_write(phydev, addr, data); ++ mdiobus_unlock(phydev); ++ ++ return err; ++} ++ ++static u32 air_buckpbus_reg_read(struct phy_device *phydev, u32 addr) ++{ ++ u32 data; ++ ++ mdiobus_lock(phydev); ++ data = __air_buckpbus_reg_read(phydev, addr); ++ mdiobus_unlock(phydev); ++ ++ return data; ++} ++ ++static int air_buckpbus_reg_modify(struct phy_device *phydev, u32 addr, ++ u32 mask, u32 set) ++{ ++ int err = 0; ++ ++ mdiobus_lock(phydev); ++ err = __air_buckpbus_reg_modify(phydev, addr, mask, set); ++ mdiobus_unlock(phydev); ++ ++ return err; ++} ++ ++static int air_sw_reset(struct phy_device *phydev) ++{ ++ u32 reg_value; ++ u8 retry = MAX_RETRY; ++ ++ reg_value = phy_read(phydev, MII_BMCR); ++ reg_value |= BMCR_RESET; ++ phy_write(phydev, MII_BMCR, reg_value); ++ do { ++ mdelay(10); ++ reg_value = phy_read(phydev, MII_BMCR); ++ retry--; ++ if (retry == 0) { ++ dev_err(phydev_dev(phydev), "Reset fail !\n"); ++ return -1; ++ } ++ } while (reg_value & BMCR_RESET); ++ ++ return 0; ++} ++ ++static int an8801_led_set_usr_def(struct phy_device *phydev, u8 entity, ++ u16 polar, u16 on_evt, u16 blk_evt) ++{ ++ int err; ++ ++ if (polar == AIR_ACTIVE_HIGH) ++ on_evt |= LED_ON_POL; ++ else ++ on_evt &= ~LED_ON_POL; ++ ++ on_evt |= LED_ON_EN; ++ ++ err = phy_write_mmd(phydev, 0x1f, LED_ON_CTRL(entity), on_evt); ++ if (err) ++ return -1; ++ ++ return phy_write_mmd(phydev, 0x1f, LED_BLK_CTRL(entity), blk_evt); ++} ++ ++static int an8801_led_set_mode(struct phy_device *phydev, u8 mode) ++{ ++ switch (mode) { ++ case AIR_LED_MODE_DISABLE: ++ return phy_modify_mmd(phydev, 0x1f, LED_BCR, ++ (LED_BCR_EXT_CTRL | LED_BCR_CLK_EN), ++ 0x0); ++ case AIR_LED_MODE_USER_DEFINE: ++ return phy_modify_mmd(phydev, 0x1f, LED_BCR, ++ (LED_BCR_EXT_CTRL | LED_BCR_CLK_EN), ++ (LED_BCR_EXT_CTRL | LED_BCR_CLK_EN)); ++ default: ++ break; ++ } ++ dev_err(phydev_dev(phydev), ++ "LED mode %d is not supported\n", mode); ++ return -EINVAL; ++} ++ ++static int an8801_led_set_state(struct phy_device *phydev, u8 entity, u8 state) ++{ ++ return phy_modify_mmd(phydev, 0x1f, LED_ON_CTRL(entity), LED_ON_EN, ++ (state) ? LED_ON_EN : 0x0); ++} ++ ++static int an8801_led_init(struct phy_device *phydev) ++{ ++ struct an8801_priv *priv = phydev_cfg(phydev); ++ struct AIR_LED_CFG_T *led_cfg = priv->led_cfg; ++ int ret, led_id; ++ u32 data; ++ u16 led_blink_cfg = priv->led_blink_cfg; ++ ++ ret = phy_write_mmd(phydev, 0x1f, LED_BLK_DUR, ++ LED_BLINK_DURATION(led_blink_cfg)); ++ if (ret < 0) ++ return ret; ++ ++ ret = phy_write_mmd(phydev, 0x1f, LED_ON_DUR, ++ (LED_BLINK_DURATION(led_blink_cfg) >> 1)); ++ if (ret < 0) ++ return ret; ++ ++ ret = an8801_led_set_mode(phydev, AIR_LED_MODE_USER_DEFINE); ++ if (ret != 0) { ++ dev_err(phydev_dev(phydev), ++ "LED fail to set mode, ret %d !\n", ret); ++ return ret; ++ } ++ ++ for (led_id = AIR_LED0; led_id < MAX_LED_SIZE; led_id++) { ++ ret = an8801_led_set_state(phydev, led_id, led_cfg[led_id].en); ++ if (ret != 0) { ++ dev_err(phydev_dev(phydev), ++ "LED fail to set LED(%d) state, ret %d !\n", ++ led_id, ret); ++ return ret; ++ } ++ if (led_cfg[led_id].en == LED_ENABLE) { ++ data = air_buckpbus_reg_read(phydev, 0x10000054); ++ data |= BIT(led_cfg[led_id].gpio); ++ ret |= air_buckpbus_reg_write(phydev, 0x10000054, data); ++ ++ data = air_buckpbus_reg_read(phydev, 0x10000058); ++ data |= LED_GPIO_SEL(led_id, led_cfg[led_id].gpio); ++ ret |= air_buckpbus_reg_write(phydev, 0x10000058, data); ++ ++ data = air_buckpbus_reg_read(phydev, 0x10000070); ++ data &= ~BIT(led_cfg[led_id].gpio); ++ ret |= air_buckpbus_reg_write(phydev, 0x10000070, data); ++ ++ ret |= an8801_led_set_usr_def(phydev, led_id, ++ led_cfg[led_id].pol, ++ led_cfg[led_id].on_cfg, ++ led_cfg[led_id].blk_cfg); ++ if (ret != 0) { ++ dev_err(phydev_dev(phydev), ++ "Fail to set LED(%d) usr def, ret %d !\n", ++ led_id, ret); ++ return ret; ++ } ++ } ++ } ++ dev_info(phydev_dev(phydev), "LED initialize OK !\n"); ++ return 0; ++} ++ ++static int an8801_ack_interrupt(struct phy_device *phydev) ++{ ++ u32 reg_val = 0; ++ ++ air_buckpbus_reg_write(phydev, 0x10285404, 0x102); ++ reg_val = air_buckpbus_reg_read(phydev, 0x10285400); ++ air_buckpbus_reg_write(phydev, 0x10285400, 0x0); ++ air_buckpbus_reg_write(phydev, 0x10285400, reg_val | 0x10); ++ air_buckpbus_reg_write(phydev, 0x10285404, 0x12); ++ air_buckpbus_reg_write(phydev, 0x10285704, 0x1f); ++ return 0; ++} ++ ++static int an8801_config_intr(struct phy_device *phydev) ++{ ++ if (phydev->interrupts == PHY_INTERRUPT_ENABLED) { ++ air_buckpbus_reg_write(phydev, 0x1000007c, BIT(AIR_INTERRUPT_GPIO) << 16); ++ air_buckpbus_reg_modify(phydev, 0x10285700, 0x1, 0x1); ++ } else { ++ air_buckpbus_reg_write(phydev, 0x1000007c, 0x0); ++ air_buckpbus_reg_modify(phydev, 0x10285700, 0x1, 0x0); ++ } ++ an8801_ack_interrupt(phydev); ++ return 0; ++} ++ ++static int an8801_did_interrupt(struct phy_device *phydev) ++{ ++ u32 reg_val = 0; ++ ++ reg_val = air_buckpbus_reg_read(phydev, 0x10285704); ++ ++ if (reg_val & 0x11) ++ return 1; ++ ++ return 0; ++} ++ ++#if (KERNEL_VERSION(5, 11, 0) < LINUX_VERSION_CODE) ++static irqreturn_t an8801_handle_interrupt(struct phy_device *phydev) ++{ ++ if (!an8801_did_interrupt(phydev)) ++ return IRQ_NONE; ++ ++ an8801_ack_interrupt(phydev); ++ phy_trigger_machine(phydev); ++ return IRQ_HANDLED; ++} ++#endif ++ ++static int findClosestNumber(const u16 *arr, u16 size, u16 target) ++{ ++ int left = 0, right = size - 1; ++ ++ while (left <= right) { ++ int mid = left + ((right - left) >> 2); ++ ++ if (arr[mid] == target) ++ return mid; ++ ++ if (arr[mid] < target) ++ right = mid - 1; ++ else ++ left = mid + 1; ++ } ++ ++ if (left > size - 1) ++ return (size - 1); ++ else ++ return ((left - 1) >= 0 ? (left - 1) : 0); ++} ++ ++static int an8801sb_i2mpb_config(struct phy_device *phydev) ++{ ++ int ret = 0; ++ u16 cl45_value = 0, temp_cl45 = 0; ++ u16 mask = 0; ++ ++ cl45_value = phy_read_mmd(phydev, MMD_DEV_VSPEC1, 0x12); ++ dev_dbg(phydev_dev(phydev), "%s:%d cl45_value 0x%x!\n", __func__, __LINE__, cl45_value); ++ cl45_value = (cl45_value & GENMASK(15, 10)) + (6 << 10); ++ ret = phy_modify_mmd(phydev, MMD_DEV_VSPEC1, 0x12, GENMASK(15, 10), cl45_value); ++ if (ret < 0) ++ return ret; ++ temp_cl45 = phy_read_mmd(phydev, MMD_DEV_VSPEC1, 0x16); ++ dev_dbg(phydev_dev(phydev), "%s:%d cl45_value 0x%x!\n", __func__, __LINE__, temp_cl45); ++ mask = GENMASK(15, 10) | GENMASK(5, 0); ++ cl45_value = (temp_cl45 & GENMASK(15, 10)) + (9 << 10); ++ cl45_value = ((temp_cl45 & GENMASK(5, 0)) + 6) | cl45_value; ++ ret = phy_modify_mmd(phydev, MMD_DEV_VSPEC1, 0x16, mask, cl45_value); ++ if (ret < 0) ++ return ret; ++ cl45_value = phy_read_mmd(phydev, MMD_DEV_VSPEC1, 0x17); ++ dev_dbg(phydev_dev(phydev), "%s:%d cl45_value 0x%x!\n", __func__, __LINE__, cl45_value); ++ cl45_value = (cl45_value & GENMASK(13, 8)) + (6 << 8); ++ ret = phy_modify_mmd(phydev, MMD_DEV_VSPEC1, 0x17, GENMASK(13, 8), cl45_value); ++ if (ret < 0) ++ return ret; ++ temp_cl45 = phy_read_mmd(phydev, MMD_DEV_VSPEC1, 0x18); ++ dev_dbg(phydev_dev(phydev), "%s:%d cl45_value 0x%x!\n", __func__, __LINE__, temp_cl45); ++ mask = GENMASK(13, 8) | GENMASK(5, 0); ++ cl45_value = (temp_cl45 & GENMASK(13, 8)) + (9 << 8); ++ cl45_value = ((temp_cl45 & GENMASK(5, 0)) + 6) | cl45_value; ++ ret = phy_modify_mmd(phydev, MMD_DEV_VSPEC1, 0x18, mask, cl45_value); ++ if (ret < 0) ++ return ret; ++ cl45_value = phy_read_mmd(phydev, MMD_DEV_VSPEC1, 0x19); ++ dev_dbg(phydev_dev(phydev), "%s:%d cl45_value 0x%x!\n", __func__, __LINE__, cl45_value); ++ cl45_value = (cl45_value & GENMASK(13, 8)) + (6 << 8); ++ ret = phy_modify_mmd(phydev, MMD_DEV_VSPEC1, 0x19, GENMASK(13, 8), cl45_value); ++ if (ret < 0) ++ return ret; ++ cl45_value = phy_read_mmd(phydev, MMD_DEV_VSPEC1, 0x20); ++ dev_dbg(phydev_dev(phydev), "%s:%d cl45_value 0x%x!\n", __func__, __LINE__, cl45_value); ++ cl45_value = (cl45_value & GENMASK(5, 0)) + 6; ++ ret = phy_modify_mmd(phydev, MMD_DEV_VSPEC1, 0x20, GENMASK(5, 0), cl45_value); ++ if (ret < 0) ++ return ret; ++ cl45_value = phy_read_mmd(phydev, MMD_DEV_VSPEC1, 0x21); ++ dev_dbg(phydev_dev(phydev), "%s:%d cl45_value 0x%x!\n", __func__, __LINE__, cl45_value); ++ cl45_value = (cl45_value & GENMASK(13, 8)) + (6 << 8); ++ ret = phy_modify_mmd(phydev, MMD_DEV_VSPEC1, 0x21, GENMASK(13, 8), cl45_value); ++ if (ret < 0) ++ return ret; ++ cl45_value = phy_read_mmd(phydev, MMD_DEV_VSPEC1, 0x22); ++ dev_dbg(phydev_dev(phydev), "%s:%d cl45_value 0x%x!\n", __func__, __LINE__, cl45_value); ++ cl45_value = (cl45_value & GENMASK(5, 0)) + 6; ++ ret = phy_modify_mmd(phydev, MMD_DEV_VSPEC1, 0x22, GENMASK(5, 0), cl45_value); ++ if (ret < 0) ++ return ret; ++ ret = phy_write_mmd(phydev, MMD_DEV_VSPEC1, 0x23, 0x883); ++ ret |= phy_write_mmd(phydev, MMD_DEV_VSPEC1, 0x24, 0x883); ++ ret |= phy_write_mmd(phydev, MMD_DEV_VSPEC1, 0x25, 0x883); ++ ret |= phy_write_mmd(phydev, MMD_DEV_VSPEC1, 0x26, 0x883); ++ ret |= phy_write_mmd(phydev, MMD_DEV_VSPEC1, 0x0, 0x100); ++ ret |= phy_write_mmd(phydev, MMD_DEV_VSPEC1, 0x1, 0x1bc); ++ ret |= phy_write_mmd(phydev, MMD_DEV_VSPEC1, 0x2, 0x1d0); ++ ret |= phy_write_mmd(phydev, MMD_DEV_VSPEC1, 0x3, 0x186); ++ ret |= phy_write_mmd(phydev, MMD_DEV_VSPEC1, 0x4, 0x202); ++ ret |= phy_write_mmd(phydev, MMD_DEV_VSPEC1, 0x5, 0x20e); ++ ret |= phy_write_mmd(phydev, MMD_DEV_VSPEC1, 0x6, 0x300); ++ ret |= phy_write_mmd(phydev, MMD_DEV_VSPEC1, 0x7, 0x3c0); ++ ret |= phy_write_mmd(phydev, MMD_DEV_VSPEC1, 0x8, 0x3d0); ++ ret |= phy_write_mmd(phydev, MMD_DEV_VSPEC1, 0x9, 0x317); ++ ret |= phy_write_mmd(phydev, MMD_DEV_VSPEC1, 0xa, 0x206); ++ ret |= phy_write_mmd(phydev, MMD_DEV_VSPEC1, 0xb, 0xe); ++ if (ret < 0) ++ return ret; ++ ++ dev_info(phydev_dev(phydev), "I2MPB Initialize OK\n"); ++ return ret; ++} ++ ++void update_r50_value(struct phy_device *phydev, ++ u16 *cl45_value, int pos1, int pos2) ++{ ++ *cl45_value &= ~(0x007f << 8); ++ *cl45_value |= ((r50ohm_table[pos1]) & 0x007f) << 8; ++ *cl45_value &= ~(0x007f); ++ *cl45_value |= (r50ohm_table[pos2]) & 0x007f; ++ dev_dbg(phydev_dev(phydev), "Read: r50ohm_tx_1=%d r50ohm_tx_2=%d\n", ++ r50ohm_table[pos1], r50ohm_table[pos2]); ++} ++ ++int calculate_position(int pos, int shift, int table_size) ++{ ++ if (shift > 0) ++ return (pos + shift < table_size) ? (pos + shift) : (table_size - 1); ++ else ++ return (pos + shift > 0) ? (pos + shift) : 0; ++} ++ ++int process_r50(struct phy_device *phydev, int reg, ++ u16 *cl45_value, u16 *r50ohm_tx_a, u16 *r50ohm_tx_b) ++{ ++ int pos1 = findClosestNumber(r50ohm_table, r50ohm_table_size, *r50ohm_tx_a); ++ int pos2 = findClosestNumber(r50ohm_table, r50ohm_table_size, *r50ohm_tx_b); ++ ++ if (pos1 != -1 && pos2 != -1) { ++ pos1 = calculate_position(pos1, R50_SHIFT, r50ohm_table_size); ++ pos2 = calculate_position(pos2, R50_SHIFT, r50ohm_table_size); ++ ++ update_r50_value(phydev, cl45_value, pos1, pos2); ++ return phy_write_mmd(phydev, 0x1e, reg, *cl45_value); ++ } ++ return 0; ++} ++ ++#ifdef CONFIG_OF ++static int an8801r_of_init(struct phy_device *phydev) ++{ ++ struct device_node *of_node = phydev_dev(phydev)->of_node; ++ struct an8801_priv *priv = phydev_cfg(phydev); ++ u32 val = 0; ++ ++ if (of_find_property(of_node, "airoha,rxclk-delay", NULL)) { ++ if (of_property_read_u32(of_node, "airoha,rxclk-delay", ++ &val) != 0) { ++ dev_err(phydev_dev(phydev), "airoha,rxclk-delay value is invalid."); ++ return -1; ++ } ++ if (val < AIR_RGMII_DELAY_NOSTEP || ++ val > AIR_RGMII_DELAY_STEP_7) { ++ dev_err(phydev_dev(phydev), ++ "airoha,rxclk-delay value %u out of range.", ++ val); ++ return -1; ++ } ++ priv->rxdelay_force = TRUE; ++ priv->rxdelay_step = val; ++ priv->rxdelay_align = of_property_read_bool(of_node, ++ "airoha,rxclk-delay-align"); ++ } ++ ++ if (of_find_property(of_node, "airoha,txclk-delay", NULL)) { ++ if (of_property_read_u32(of_node, "airoha,txclk-delay", ++ &val) != 0) { ++ dev_err(phydev_dev(phydev), ++ "airoha,txclk-delay value is invalid."); ++ return -1; ++ } ++ if (val < AIR_RGMII_DELAY_NOSTEP || ++ val > AIR_RGMII_DELAY_STEP_7) { ++ dev_err(phydev_dev(phydev), ++ "airoha,txclk-delay value %u out of range.", ++ val); ++ return -1; ++ } ++ priv->txdelay_force = TRUE; ++ priv->txdelay_step = val; ++ } ++ return 0; ++} ++ ++static int an8801sb_of_init(struct phy_device *phydev) ++{ ++ struct device_node *of_node = phydev_dev(phydev)->of_node; ++ struct an8801_priv *priv = phydev_cfg(phydev); ++ u32 val = 0; ++ ++ if (of_find_property(of_node, "airoha,polarity", NULL)) { ++ if (of_property_read_u32(of_node, "airoha,polarity", ++ &val) != 0) { ++ dev_err(phydev_dev(phydev), "airoha,polarity value is invalid."); ++ return -1; ++ } ++ if (val < AIR_POL_TX_NOR_RX_REV || ++ val > AIR_POL_TX_REV_RX_NOR) { ++ dev_err(phydev_dev(phydev), ++ "airoha,polarity value %u out of range.", ++ val); ++ return -1; ++ } ++ priv->pol = val; ++ } else ++ priv->pol = AIR_POL_TX_NOR_RX_NOR; ++ ++ if (of_find_property(of_node, "airoha,surge", NULL)) { ++ if (of_property_read_u32(of_node, "airoha,surge", ++ &val) != 0) { ++ dev_err(phydev_dev(phydev), "airoha,surge value is invalid."); ++ return -1; ++ } ++ if (val < AIR_SURGE_0R || ++ val > AIR_SURGE_5R) { ++ dev_err(phydev_dev(phydev), ++ "airoha,surge value %u out of range.", ++ val); ++ return -1; ++ } ++ priv->surge = val; ++ } else ++ priv->surge = AIR_SURGE_0R; ++ ++ if (of_find_property(of_node, "airoha,sgmii-mode", NULL)) { ++ if (of_property_read_u32(of_node, "airoha,sgmii-mode", ++ &val) != 0) { ++ dev_err(phydev_dev(phydev), "airoha,sgmii-mode value is invalid."); ++ return -1; ++ } ++ if (val < AIR_SGMII_AN || ++ val > AIR_SGMII_FORCE) { ++ dev_err(phydev_dev(phydev), ++ "airoha,sgmii-mode value %u out of range.", ++ val); ++ return -1; ++ } ++ priv->sgmii_mode = val; ++ } else ++ priv->sgmii_mode = AIR_SGMII_AN; ++ ++ return 0; ++} ++#else ++static int an8801r_of_init(struct phy_device *phydev) ++{ ++ return 0; ++} ++static int an8801sb_of_init(struct phy_device *phydev) ++{ ++ return 0; ++} ++#endif ++ ++ ++static int an8801r_rgmii_rxdelay(struct phy_device *phydev, u16 delay, u8 align) ++{ ++ u32 reg_val = delay & RGMII_DELAY_STEP_MASK; ++ ++ if (align) { ++ reg_val |= RGMII_RXDELAY_ALIGN; ++ dev_info(phydev_dev(phydev), "Rxdelay align\n"); ++ } ++ reg_val |= RGMII_RXDELAY_FORCE_MODE; ++ air_buckpbus_reg_write(phydev, 0x1021C02C, reg_val); ++ reg_val = air_buckpbus_reg_read(phydev, 0x1021C02C); ++ dev_info(phydev_dev(phydev), ++ "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; ++ ++ reg_val |= RGMII_TXDELAY_FORCE_MODE; ++ air_buckpbus_reg_write(phydev, 0x1021C024, reg_val); ++ reg_val = air_buckpbus_reg_read(phydev, 0x1021C024); ++ dev_info(phydev_dev(phydev), ++ "Force txdelay = %d(0x%x)\n", delay, reg_val); ++ return 0; ++} ++ ++static int an8801r_rgmii_delay_config(struct phy_device *phydev) ++{ ++ struct an8801_priv *priv = phydev_cfg(phydev); ++ ++ if (priv->rxdelay_force) ++ an8801r_rgmii_rxdelay(phydev, priv->rxdelay_step, ++ priv->rxdelay_align); ++ if (priv->txdelay_force) ++ an8801r_rgmii_txdelay(phydev, priv->txdelay_step); ++ return 0; ++} ++ ++int an8801sb_surge_protect_cfg(struct phy_device *phydev) ++{ ++ int ret = 0; ++ struct device *dev = phydev_dev(phydev); ++ struct an8801_priv *priv = phydev->priv; ++ u16 r50ohm_tx_a = 0, r50ohm_tx_b = 0, r50ohm_tx_c = 0, r50ohm_tx_d = 0; ++ u16 cl45_value = 0; ++ ++ if (priv->surge) { ++ cl45_value = phy_read_mmd(phydev, 0x1e, 0x174); ++ r50ohm_tx_a = (cl45_value >> 8) & 0x007f; ++ r50ohm_tx_b = cl45_value & 0x007f; ++ dev_dbg(phydev_dev(phydev), "Read: (0x174) value=0x%04x r50ohm_tx_a=%d r50ohm_tx_b=%d\n", ++ cl45_value, r50ohm_tx_a, r50ohm_tx_b); ++ ret = process_r50(phydev, 0x174, &cl45_value, &r50ohm_tx_a, &r50ohm_tx_b); ++ if (ret < 0) ++ return ret; ++ cl45_value = phy_read_mmd(phydev, 0x1e, 0x175); ++ r50ohm_tx_c = (cl45_value >> 8) & 0x007f; ++ r50ohm_tx_d = cl45_value & 0x007f; ++ dev_dbg(phydev_dev(phydev), "Read: (0x175) value=0x%04x r50ohm_tx_c=%d r50ohm_tx_d=%d\n", ++ cl45_value, r50ohm_tx_c, r50ohm_tx_d); ++ ret = process_r50(phydev, 0x175, &cl45_value, &r50ohm_tx_c, &r50ohm_tx_d); ++ if (ret < 0) ++ return ret; ++ ret = an8801sb_i2mpb_config(phydev); ++ if (ret < 0) { ++ dev_err(dev, "an8801sb_i2mpb_config fail\n"); ++ return ret; ++ } ++ dev_info(dev, "surge protection mode - 5R\n"); ++ } else ++ dev_info(dev, "surge protection mode - 0R\n"); ++ return ret; ++} ++ ++static int an8801sb_config_init(struct phy_device *phydev) ++{ ++ int ret; ++ struct an8801_priv *priv = phydev_cfg(phydev); ++ u32 pbus_value = 0; ++ u32 reg_value = 0; ++ ++ ret = an8801sb_of_init(phydev); ++ if (ret < 0) ++ return ret; ++ if (priv->sgmii_mode == AIR_SGMII_AN) { ++ dev_info(phydev_dev(phydev), "sgmii mode - AN\n"); ++ reg_value = phy_read(phydev, MII_BMSR); ++ if ((reg_value & MCS_LINK_STATUS_MASK) != 0) { ++ ret = air_buckpbus_reg_write(phydev, 0x10220010, 0x1801); ++ if (ret < 0) ++ return ret; ++ reg_value = air_buckpbus_reg_read(phydev, 0x10220010); ++ dev_dbg(phydev_dev(phydev), ++ "air_buckpbus_reg_read(0x10220010,0x%x).\n", reg_value); ++ ++ ret = air_buckpbus_reg_write(phydev, 0x10220000, 0x9140); ++ if (ret < 0) ++ return ret; ++ reg_value = air_buckpbus_reg_read(phydev, 0x10220000); ++ dev_dbg(phydev_dev(phydev), ++ "air_buckpbus_reg_read(0x10220000,0x%x).\n", reg_value); ++ mdelay(80); ++ } ++ } else { /* SGMII force mode */ ++ dev_info(phydev_dev(phydev), "sgmii mode - Force\n"); ++ ret = air_buckpbus_reg_write(phydev, 0x102260E4, 0xFF11); ++ ret |= air_buckpbus_reg_write(phydev, 0x10224004, 0x0700); ++ ret |= air_buckpbus_reg_write(phydev, 0x10224018, 0x0); ++ ret |= air_buckpbus_reg_write(phydev, 0x1022450C, 0x0700); ++ ret |= air_buckpbus_reg_write(phydev, 0x1022A140, 0x5); ++ ret |= air_buckpbus_reg_write(phydev, 0x10226100, 0xF0000000); ++ ret |= air_buckpbus_reg_write(phydev, 0x10226300, 0x0); ++ ret |= air_buckpbus_reg_write(phydev, 0x1022A078, 0x10050); ++ ret |= air_buckpbus_reg_write(phydev, 0x10220034, 0x31120009); ++ ret |= air_buckpbus_reg_write(phydev, 0x10220000, 0x140); ++ if (ret < 0) ++ return ret; ++ } ++#ifdef CONFIG_OF ++ pbus_value = air_buckpbus_reg_read(phydev, 0x1022a0f8); ++ pbus_value &= ~0x3; ++ pbus_value |= priv->pol; ++ ret = air_buckpbus_reg_write(phydev, 0x1022a0f8, pbus_value); ++ if (ret < 0) ++ return ret; ++#endif ++ pbus_value = air_buckpbus_reg_read(phydev, 0x1022a0f8); ++ dev_info(phydev_dev(phydev), ++ "Tx, Rx Polarity : %08x\n", pbus_value); ++ ++ ret = phy_write_mmd(phydev, MMD_DEV_VSPEC2, 0x600, 0x1e); ++ ret |= phy_write_mmd(phydev, MMD_DEV_VSPEC2, 0x601, 0x02); ++ ++ ret |= phy_write_mmd(phydev, 7, 60, 0x0); ++ if (ret != 0) { ++ dev_err(phydev_dev(phydev), ++ "AN8801SB initialize fail, ret %d !\n", ret); ++ return ret; ++ } ++ ++ ret = an8801_led_init(phydev); ++ if (ret != 0) { ++ dev_err(phydev_dev(phydev), ++ "LED initialize fail, ret %d !\n", ret); ++ return ret; ++ } ++ air_buckpbus_reg_write(phydev, 0x10270100, 0x0f); ++ air_buckpbus_reg_write(phydev, 0x10270108, 0x0a0a0404); ++ ret = an8801sb_surge_protect_cfg(phydev); ++ if (ret < 0) { ++ dev_err(phydev_dev(phydev), ++ "an8801sb_surge_protect_cfg fail. (ret=%d)\n", ret); ++ return ret; ++ } ++ reg_value = phy_read(phydev, MII_CTRL1000); ++ reg_value |= ADVERTISE_1000FULL; ++ ret = phy_write(phydev, MII_CTRL1000, reg_value); ++ if (ret < 0) ++ return ret; ++ reg_value = phy_read(phydev, MII_BMCR); ++ reg_value |= BMCR_ANRESTART; ++ ret = phy_write(phydev, MII_BMCR, reg_value); ++ if (ret < 0) ++ return ret; ++ dev_info(phydev_dev(phydev), ++ "AN8801SB Initialize OK ! (%s)\n", AN8801_DRIVER_VERSION); ++ return 0; ++} ++ ++static int an8801r_config_init(struct phy_device *phydev) ++{ ++ int ret; ++ ++ ret = an8801r_of_init(phydev); ++ if (ret < 0) ++ return ret; ++ ++ ret = air_sw_reset(phydev); ++ if (ret < 0) ++ return ret; ++ ++ air_buckpbus_reg_write(phydev, 0x11F808D0, 0x180); ++ ++ air_buckpbus_reg_write(phydev, 0x1021c004, 0x1); ++ air_buckpbus_reg_write(phydev, 0x10270004, 0x3f); ++ air_buckpbus_reg_write(phydev, 0x10270104, 0xff); ++ air_buckpbus_reg_write(phydev, 0x10270204, 0xff); ++ ++ an8801r_rgmii_delay_config(phydev); ++ ++ ret = an8801_led_init(phydev); ++ if (ret != 0) { ++ dev_err(phydev_dev(phydev), ++ "LED initialize fail, ret %d !\n", ret); ++ return ret; ++ } ++ dev_info(phydev_dev(phydev), "AN8801R Initialize OK ! (%s)\n", ++ AN8801_DRIVER_VERSION); ++ return 0; ++} ++ ++static int an8801_config_init(struct phy_device *phydev) ++{ ++ if (phydev->interface == PHY_INTERFACE_MODE_SGMII) { ++ an8801sb_config_init(phydev); ++ } else if (phydev->interface == PHY_INTERFACE_MODE_RGMII) { ++ an8801r_config_init(phydev); ++ } else { ++ dev_info(phydev_dev(phydev), "AN8801 Phy-mode not support!!!\n"); ++ return -1; ++ } ++ return 0; ++} ++ ++#ifdef AN8801SB_DEBUGFS ++static const char * const tx_rx_string[32] = { ++ "Tx Normal, Rx Reverse", ++ "Tx Reverse, Rx Reverse", ++ "Tx Normal, Rx Normal", ++ "Tx Reverse, Rx Normal", ++}; ++ ++int an8801_set_polarity(struct phy_device *phydev, int tx_rx) ++{ ++ int ret = 0; ++ u32 pbus_data = 0; ++ ++ pr_notice("\n[Write] Polarity %s\n", tx_rx_string[tx_rx]); ++ pbus_data = (air_buckpbus_reg_read(phydev, 0x1022a0f8) & ++ (~(BIT(0) | BIT(1)))); ++ pbus_data |= (BIT(4) | tx_rx); ++ ret = air_buckpbus_reg_write(phydev, 0x1022a0f8, pbus_data); ++ if (ret < 0) ++ return ret; ++ usleep_range(9800, 12000); ++ pbus_data &= ~BIT(4); ++ ret = air_buckpbus_reg_write(phydev, 0x1022a0f8, pbus_data); ++ if (ret < 0) ++ return ret; ++ pbus_data = air_buckpbus_reg_read(phydev, 0x1022a0f8); ++ tx_rx = pbus_data & (BIT(0) | BIT(1)); ++ pr_notice("\n[Read] Polarity %s confirm....(%8x)\n", ++ tx_rx_string[tx_rx], pbus_data); ++ ++ return ret; ++} ++ ++int air_polarity_help(void) ++{ ++ pr_notice(AN8801_DEBUGFS_POLARITY_HELP_STRING); ++ return 0; ++} ++ ++static ssize_t an8801_polarity_write(struct file *file, const char __user *ptr, ++ size_t len, loff_t *off) ++{ ++ struct phy_device *phydev = file->private_data; ++ char buf[32], param1[32], param2[32]; ++ int count = len, ret = 0, tx_rx = 0; ++ ++ memset(buf, 0, 32); ++ memset(param1, 0, 32); ++ memset(param2, 0, 32); ++ ++ if (count > sizeof(buf) - 1) ++ return -EINVAL; ++ if (copy_from_user(buf, ptr, len)) ++ return -EFAULT; ++ ++ ret = sscanf(buf, "%s %s", param1, param2); ++ if (ret < 0) ++ return ret; ++ ++ if (!strncmp("help", param1, strlen("help"))) { ++ air_polarity_help(); ++ return count; ++ } ++ if (!strncmp("tx_normal", param1, strlen("tx_normal"))) { ++ if (!strncmp("rx_normal", param2, strlen("rx_normal"))) ++ tx_rx = AIR_POL_TX_NOR_RX_NOR; ++ else if (!strncmp("rx_reverse", param2, strlen("rx_reverse"))) ++ tx_rx = AIR_POL_TX_NOR_RX_REV; ++ else { ++ pr_notice(AN8801_DEBUGFS_RX_ERROR_STRING); ++ return -EINVAL; ++ } ++ } else if (!strncmp("tx_reverse", param1, strlen("tx_reverse"))) { ++ if (!strncmp("rx_normal", param2, strlen("rx_normal"))) ++ tx_rx = AIR_POL_TX_REV_RX_NOR; ++ else if (!strncmp("rx_reverse", param2, strlen("rx_reverse"))) ++ tx_rx = AIR_POL_TX_REV_RX_REV; ++ else { ++ pr_notice(AN8801_DEBUGFS_RX_ERROR_STRING); ++ return -EINVAL; ++ } ++ } else { ++ pr_notice(AN8801_DEBUGFS_TX_ERROR_STRING); ++ return -EINVAL; ++ } ++ ret = an8801_set_polarity(phydev, tx_rx); ++ if (ret < 0) ++ return ret; ++ return count; ++} ++ ++static ssize_t an8801_mdio_write(struct file *file, const char __user *ptr, ++ size_t len, loff_t *off) ++{ ++ struct phy_device *phydev = file->private_data; ++ char buf[64], param1[32], param2[32]; ++ int count = len, ret = 0; ++ unsigned int reg, devad, val; ++ u16 reg_val; ++ ++ memset(buf, 0, 64); ++ memset(param1, 0, 32); ++ memset(param2, 0, 32); ++ ++ if (count > sizeof(buf) - 1) ++ return -EINVAL; ++ if (copy_from_user(buf, ptr, len)) ++ return -EFAULT; ++ ++ ret = sscanf(buf, "%s %s", param1, param2); ++ if (ret < 0) ++ return ret; ++ ++ if (!strncmp("cl22", param1, strlen("cl22"))) { ++ if (!strncmp("w", param2, strlen("w"))) { ++ if (sscanf(buf, "cl22 w %x %x", ®, &val) == -1) ++ return -EFAULT; ++ pr_notice("\nphy=0x%x, reg=0x%x, val=0x%x\n", ++ phydev_phy_addr(phydev), reg, val); ++ ++ ret = phy_write(phydev, reg, val); ++ if (ret < 0) ++ return ret; ++ pr_notice("\nphy=0x%x, reg=0x%x, val=0x%x confirm..\n", ++ phydev_phy_addr(phydev), reg, ++ phy_read(phydev, reg)); ++ } else if (!strncmp("r", param2, strlen("r"))) { ++ if (sscanf(buf, "cl22 r %x", ®) == -1) ++ return -EFAULT; ++ pr_notice("\nphy=0x%x, reg=0x%x, val=0x%x\n", ++ phydev_phy_addr(phydev), reg, ++ phy_read(phydev, reg)); ++ } else { ++ pr_notice(AN8801_DEBUGFS_MDIO_HELP_STRING); ++ return -EINVAL; ++ } ++ } else if (!strncmp("cl45", param1, strlen("cl45"))) { ++ if (!strncmp("w", param2, strlen("w"))) { ++ if (sscanf(buf, "cl45 w %x %x %x", &devad, ®, &val) == -1) ++ return -EFAULT; ++ pr_notice("\nphy=0x%x, devad=0x%x, reg=0x%x, val=0x%x\n", ++ phydev_phy_addr(phydev), devad, reg, val); ++ ++ ret = phy_write_mmd(phydev, devad, reg, val); ++ if (ret < 0) ++ return ret; ++ reg_val = phy_read_mmd(phydev, devad, reg); ++ pr_notice("\nphy=0x%x, devad=0x%x, reg=0x%x, val=0x%x confirm..\n", ++ phydev_phy_addr(phydev), devad, reg, reg_val); ++ } else if (!strncmp("r", param2, strlen("r"))) { ++ if (sscanf(buf, "cl45 r %x %x", &devad, ®) == -1) ++ return -EFAULT; ++ reg_val = phy_read_mmd(phydev, devad, reg); ++ pr_notice("\nphy=0x%x, devad=0x%x, reg=0x%x, val=0x%x\n", ++ phydev_phy_addr(phydev), devad, reg, reg_val); ++ } else { ++ pr_notice(AN8801_DEBUGFS_MDIO_HELP_STRING); ++ return -EINVAL; ++ } ++ } else { ++ pr_notice(AN8801_DEBUGFS_MDIO_HELP_STRING); ++ return -EINVAL; ++ } ++ ++ return count; ++} ++ ++static int an8801_counter_show(struct seq_file *seq, void *v) ++{ ++ struct phy_device *phydev = seq->private; ++ int ret = 0; ++ u32 pkt_cnt = 0; ++ ++ seq_puts(seq, "==========AIR PHY COUNTER==========\n"); ++ seq_puts(seq, "|\t<>\n"); ++ air_buckpbus_reg_write(phydev, 0x10226124, 0xaa); ++ air_buckpbus_reg_write(phydev, 0x10226124, 0x0); ++ seq_puts(seq, "| PHY Rx DV CNT :"); ++ pkt_cnt = air_buckpbus_reg_read(phydev, 0x1022614c); ++ seq_printf(seq, "%010u |\n", pkt_cnt); ++ seq_puts(seq, "| PHY Tx EN CNT :"); ++ pkt_cnt = air_buckpbus_reg_read(phydev, 0x10226148); ++ seq_printf(seq, "%010u |\n", pkt_cnt); ++ seq_puts(seq, "| Tx ER CNT :"); ++ pkt_cnt = air_buckpbus_reg_read(phydev, 0x10226150); ++ seq_printf(seq, "%010u |\n", pkt_cnt); ++ seq_puts(seq, "| MAC Rx DV CNT :"); ++ pkt_cnt = air_buckpbus_reg_read(phydev, 0x10226138); ++ seq_printf(seq, "%010u |\n", pkt_cnt); ++ seq_puts(seq, "| MAC Tx EN CNT :"); ++ pkt_cnt = air_buckpbus_reg_read(phydev, 0x1022612C); ++ seq_printf(seq, "%010u |\n", pkt_cnt); ++ seq_puts(seq, "| Tx ER CNT :"); ++ pkt_cnt = air_buckpbus_reg_read(phydev, 0x10226130); ++ seq_printf(seq, "%010u |\n", pkt_cnt); ++ ret = air_buckpbus_reg_write(phydev, 0x10226124, 0x55); ++ ret |= air_buckpbus_reg_write(phydev, 0x10226124, 0x0); ++ if (ret < 0) ++ return ret; ++ ++ seq_puts(seq, "|\t<>\n"); ++ seq_puts(seq, "| Rx from Line side_S :"); ++ pkt_cnt = air_buckpbus_reg_read(phydev, 0x10270130); ++ seq_printf(seq, "%010u |\n", pkt_cnt); ++ seq_puts(seq, "| Rx from Line side_E :"); ++ pkt_cnt = air_buckpbus_reg_read(phydev, 0x10270134); ++ seq_printf(seq, "%010u |\n", pkt_cnt); ++ seq_puts(seq, "| Tx to System side_S :"); ++ pkt_cnt = air_buckpbus_reg_read(phydev, 0x10270138); ++ seq_printf(seq, "%010u |\n", pkt_cnt); ++ seq_puts(seq, "| Tx to System side_E :"); ++ pkt_cnt = air_buckpbus_reg_read(phydev, 0x1027013C); ++ seq_printf(seq, "%010u |\n", pkt_cnt); ++ seq_puts(seq, "| Rx from System side_S :"); ++ pkt_cnt = air_buckpbus_reg_read(phydev, 0x10270120); ++ seq_printf(seq, "%010u |\n", pkt_cnt); ++ seq_puts(seq, "| Rx from System side_E :"); ++ pkt_cnt = air_buckpbus_reg_read(phydev, 0x10270124); ++ seq_printf(seq, "%010u |\n", pkt_cnt); ++ seq_puts(seq, "| Tx to Line side_S :"); ++ pkt_cnt = air_buckpbus_reg_read(phydev, 0x10270128); ++ seq_printf(seq, "%010u |\n", pkt_cnt); ++ seq_puts(seq, "| Tx to Line side_E :"); ++ pkt_cnt = air_buckpbus_reg_read(phydev, 0x1027012C); ++ seq_printf(seq, "%010u |\n", pkt_cnt); ++ ++ ret = air_buckpbus_reg_write(phydev, 0x1027011C, 0x3); ++ if (ret < 0) ++ return ret; ++ ++ seq_puts(seq, "|\t<>\n"); ++ ret = phy_write(phydev, 0x1f, 1); ++ if (ret < 0) ++ return ret; ++ seq_puts(seq, "| Rx from Line side :"); ++ pkt_cnt = phy_read(phydev, 0x12) & 0x7fff; ++ seq_printf(seq, "%010u |\n", pkt_cnt); ++ seq_puts(seq, "| Rx Error from Line side :"); ++ pkt_cnt = phy_read(phydev, 0x17) & 0xff; ++ seq_printf(seq, "%010u |\n", pkt_cnt); ++ ++ ret = phy_write(phydev, 0x1f, 0); ++ if (ret < 0) ++ return ret; ++ ret = phy_write(phydev, 0x1f, 0x52B5); ++ if (ret < 0) ++ return ret; ++ ret = phy_write(phydev, 0x10, 0xBF92); ++ if (ret < 0) ++ return ret; ++ ++ seq_puts(seq, "| Tx to Line side :"); ++ pkt_cnt = (phy_read(phydev, 0x11) & 0x7ffe) >> 1; ++ seq_printf(seq, "%010u |\n", pkt_cnt); ++ seq_puts(seq, "| Tx Error to Line side :"); ++ pkt_cnt = phy_read(phydev, 0x12); ++ pkt_cnt &= 0x7f; ++ seq_printf(seq, "%010u |\n\n", pkt_cnt); ++ ret = phy_write(phydev, 0x1f, 0); ++ if (ret < 0) ++ return ret; ++ ++ return ret; ++} ++ ++static int an8801_counter_open(struct inode *inode, struct file *file) ++{ ++ return single_open(file, an8801_counter_show, inode->i_private); ++} ++ ++static int an8801_debugfs_pbus_help(void) ++{ ++ pr_notice(AN8801_DEBUGFS_PBUS_HELP_STRING); ++ return 0; ++} ++ ++static ssize_t an8801_debugfs_pbus(struct file *file, ++ const char __user *buffer, size_t count, ++ loff_t *data) ++{ ++ struct phy_device *phydev = file->private_data; ++ char buf[64]; ++ int ret = 0; ++ unsigned int reg, addr; ++ u32 val; ++ ++ memset(buf, 0, 64); ++ ++ if (copy_from_user(buf, buffer, count)) ++ return -EFAULT; ++ ++ if (buf[0] == 'w') { ++ if (sscanf(buf, "w %x %x", ®, &val) == -1) ++ return -EFAULT; ++ ++ pr_notice("\nphy=0x%x, reg=0x%x, val=0x%x\n", ++ phydev_phy_addr(phydev), reg, val); ++ ++ ret = air_buckpbus_reg_write(phydev, reg, val); ++ if (ret < 0) ++ return ret; ++ ++ pr_notice("\nphy=%d, reg=0x%x, val=0x%x confirm..\n", ++ addr, reg, ++ air_buckpbus_reg_read(phydev, reg)); ++ } else if (buf[0] == 'r') { ++ if (sscanf(buf, "r %x", ®) == -1) ++ return -EFAULT; ++ ++ pr_notice("\nphy=0x%x, reg=0x%x, val=0x%x\n", ++ phydev_phy_addr(phydev), reg, ++ air_buckpbus_reg_read(phydev, reg)); ++ } else if (buf[0] == 'h') ++ an8801_debugfs_pbus_help(); ++ ++ return count; ++} ++ ++int an8801_info_show(struct seq_file *seq, void *v) ++{ ++ struct phy_device *phydev = seq->private; ++ unsigned int tx_rx = ++ (air_buckpbus_reg_read(phydev, 0x1022a0f8) & 0x3); ++ u32 pbus_data = 0; ++ int reg = 0; ++ ++ seq_puts(seq, "\t<>\n"); ++ pbus_data = air_buckpbus_reg_read(phydev, 0x10005004); ++ seq_printf(seq, "| Product Version : E%d\n", pbus_data); ++ seq_printf(seq, "| Driver Version : %s\n", AN8801_DRIVER_VERSION); ++ pbus_data = air_buckpbus_reg_read(phydev, 0x10220b04); ++ seq_printf(seq, "| Serdes Status : Rx_Sync(%01ld), AN_Done(%01ld)\n", ++ GET_BIT(pbus_data, 4), GET_BIT(pbus_data, 0)); ++ seq_printf(seq, "| Tx, Rx Polarity : %s(%02d)\n", ++ tx_rx_string[tx_rx], tx_rx); ++ pbus_data = air_buckpbus_reg_read(phydev, 0x10000094); ++ seq_printf(seq, "| RG_HW_STRAP : 0x%08x\n", pbus_data); ++ for (reg = MII_BMCR; reg <= MII_STAT1000; reg++) { ++ if ((reg <= MII_LPA) || (reg >= MII_CTRL1000)) ++ seq_printf(seq, "| RG_MII 0x%02x : 0x%08x\n", ++ reg, phy_read(phydev, reg)); ++ } ++ seq_puts(seq, "\n"); ++ return 0; ++} ++ ++static int an8801_info_open(struct inode *inode, struct file *file) ++{ ++ return single_open(file, an8801_info_show, inode->i_private); ++} ++ ++static const struct file_operations an8801_info_fops = { ++ .owner = THIS_MODULE, ++ .open = an8801_info_open, ++ .read = seq_read, ++ .llseek = noop_llseek, ++ .release = single_release, ++}; ++ ++static const struct file_operations an8801_counter_fops = { ++ .owner = THIS_MODULE, ++ .open = an8801_counter_open, ++ .read = seq_read, ++ .llseek = noop_llseek, ++ .release = single_release, ++}; ++ ++static const struct file_operations an8801_debugfs_pbus_fops = { ++ .owner = THIS_MODULE, ++ .open = simple_open, ++ .write = an8801_debugfs_pbus, ++ .llseek = noop_llseek, ++}; ++ ++static const struct file_operations an8801_polarity_fops = { ++ .owner = THIS_MODULE, ++ .open = simple_open, ++ .write = an8801_polarity_write, ++ .llseek = noop_llseek, ++}; ++ ++static const struct file_operations an8801_mdio_fops = { ++ .owner = THIS_MODULE, ++ .open = simple_open, ++ .write = an8801_mdio_write, ++ .llseek = noop_llseek, ++}; ++ ++int an8801_debugfs_init(struct phy_device *phydev) ++{ ++ int ret = 0; ++ struct an8801_priv *priv = phydev->priv; ++ ++ dev_info(phydev_dev(phydev), "Debugfs init start\n"); ++ priv->debugfs_root = ++ debugfs_create_dir(dev_name(phydev_dev(phydev)), NULL); ++ if (!priv->debugfs_root) { ++ dev_err(phydev_dev(phydev), "Debugfs init err\n"); ++ ret = -ENOMEM; ++ } ++ debugfs_create_file(DEBUGFS_INFO, 0444, ++ priv->debugfs_root, phydev, ++ &an8801_info_fops); ++ debugfs_create_file(DEBUGFS_COUNTER, 0644, ++ priv->debugfs_root, phydev, ++ &an8801_counter_fops); ++ debugfs_create_file(DEBUGFS_PBUS_OP, S_IFREG | 0200, ++ priv->debugfs_root, phydev, ++ &an8801_debugfs_pbus_fops); ++ debugfs_create_file(DEBUGFS_POLARITY, S_IFREG | 0200, ++ priv->debugfs_root, phydev, ++ &an8801_polarity_fops); ++ debugfs_create_file(DEBUGFS_MDIO, S_IFREG | 0200, ++ priv->debugfs_root, phydev, ++ &an8801_mdio_fops); ++ return ret; ++} ++ ++static void air_debugfs_remove(struct phy_device *phydev) ++{ ++ struct an8801_priv *priv = phydev->priv; ++ ++ if (priv->debugfs_root != NULL) { ++ debugfs_remove_recursive(priv->debugfs_root); ++ priv->debugfs_root = NULL; ++ } ++} ++#endif /*AN8801SB_DEBUGFS*/ ++ ++static int an8801_phy_probe(struct phy_device *phydev) ++{ ++ u32 reg_value, phy_id, led_id; ++ struct an8801_priv *priv = NULL; ++ ++ reg_value = phy_read(phydev, 2); ++ phy_id = reg_value << 16; ++ reg_value = phy_read(phydev, 3); ++ phy_id |= reg_value; ++ dev_info(phydev_dev(phydev), "PHY-ID = %x\n", phy_id); ++ ++ if (phy_id != AN8801_PHY_ID) { ++ dev_err(phydev_dev(phydev), ++ "AN8801 can't be detected.\n"); ++ return -1; ++ } ++ ++ priv = devm_kzalloc(phydev_dev(phydev) ++ , sizeof(struct an8801_priv) ++ , GFP_KERNEL); ++ if (!priv) ++ return -ENOMEM; ++ ++ for (led_id = AIR_LED0; led_id < MAX_LED_SIZE; led_id++) ++ priv->led_cfg[led_id] = led_cfg_dlt[led_id]; ++ ++ priv->led_blink_cfg = led_blink_cfg_dlt; ++ priv->rxdelay_force = rxdelay_force; ++ priv->txdelay_force = txdelay_force; ++ priv->rxdelay_step = rxdelay_step; ++ priv->rxdelay_align = rxdelay_align; ++ priv->txdelay_step = txdelay_step; ++ ++ phydev->priv = priv; ++#ifdef AN8801SB_DEBUGFS ++ reg_value = air_buckpbus_reg_read(phydev, 0x10000094); ++ if (0 == (reg_value & ++ (AN8801_RG_PKG_SEL_LSB | AN8801_RG_PKG_SEL_MSB))) { ++ int ret = 0; ++ ++ ret = an8801_debugfs_init(phydev); ++ if (ret < 0) { ++ air_debugfs_remove(phydev); ++ kfree(priv); ++ return ret; ++ } ++ } else { ++ dev_info(phydev_dev(phydev), "AN8801R not supprt debugfs\n"); ++ } ++#endif ++ return 0; ++} ++ ++static void an8801_phy_remove(struct phy_device *phydev) ++{ ++ struct an8801_priv *priv = (struct an8801_priv *)phydev->priv; ++ ++ if (priv) { ++#ifdef AN8801SB_DEBUGFS ++ air_debugfs_remove(phydev); ++#endif ++ kfree(priv); ++ dev_info(phydev_dev(phydev), "AN8801 remove OK!\n"); ++ } ++} ++ ++static int an8801sb_read_status(struct phy_device *phydev) ++{ ++ int ret, prespeed = phydev->speed; ++ struct an8801_priv *priv = phydev->priv; ++ u32 reg_value = 0; ++ u32 an_retry = MAX_SGMII_AN_RETRY; ++ ++ ret = genphy_read_status(phydev); ++ if (phydev->link == LINK_DOWN) { ++ prespeed = 0; ++ phydev->speed = 0; ++ ret |= phy_write_mmd( ++ phydev, MMD_DEV_VSPEC2, PHY_PRE_SPEED_REG, prespeed); ++ ++ if (priv->sgmii_mode == AIR_SGMII_AN) { ++ mdelay(10); /* delay 10 ms */ ++ reg_value = air_buckpbus_reg_read(phydev, 0x10220010); ++ reg_value &= 0x7fff; ++ air_buckpbus_reg_write(phydev, 0x10220010, reg_value); ++ ++ reg_value = air_buckpbus_reg_read(phydev, 0x10220000); ++ reg_value |= AN8801SB_SGMII_AN0_ANRESTART; ++ air_buckpbus_reg_write(phydev, 0x10220000, reg_value); ++ } ++ } ++ ++ if (prespeed != phydev->speed && phydev->link == LINK_UP) { ++ prespeed = phydev->speed; ++ ret |= phy_write_mmd( ++ phydev, MMD_DEV_VSPEC2, PHY_PRE_SPEED_REG, prespeed); ++ dev_info(phydev_dev(phydev), "AN8801SB SPEED %d\n", prespeed); ++ if (priv->sgmii_mode == AIR_SGMII_AN) { ++ while (an_retry > 0) { ++ mdelay(1); /* delay 1 ms */ ++ reg_value = air_buckpbus_reg_read( ++ phydev, 0x10220b04); ++ if (reg_value & AN8801SB_SGMII_AN0_AN_DONE) ++ break; ++ an_retry--; ++ } ++ mdelay(10); /* delay 10 ms */ ++ ++ ++ if (prespeed == SPEED_1000) { ++ air_buckpbus_reg_write( ++ phydev, 0x10220010, 0xd801); ++ } else if (prespeed == SPEED_100) { ++ air_buckpbus_reg_write( ++ phydev, 0x10220010, 0xd401); ++ } else { ++ air_buckpbus_reg_write( ++ phydev, 0x10220010, 0xd001); ++ } ++ ++ reg_value = air_buckpbus_reg_read(phydev, 0x10220000); ++ reg_value |= (AN8801SB_SGMII_AN0_RESET | AN8801SB_SGMII_AN0_ANRESTART); ++ air_buckpbus_reg_write(phydev, 0x10220000, reg_value); ++ } else { /* SGMII force mode */ ++ if (prespeed == SPEED_1000) { ++ ret = air_buckpbus_reg_write(phydev, 0x102260E4, 0xFF11); ++ ret |= air_buckpbus_reg_write(phydev, 0x10224004, 0x0700); ++ ret |= air_buckpbus_reg_write(phydev, 0x10224018, 0x0); ++ ret |= air_buckpbus_reg_write(phydev, 0x1022450C, 0x0700); ++ ret |= air_buckpbus_reg_write(phydev, 0x1022A140, 0x5); ++ ret |= air_buckpbus_reg_write(phydev, 0x10226100, 0xF0000000); ++ ret |= air_buckpbus_reg_write(phydev, 0x10270100, 0xF); ++ } else if (prespeed == SPEED_100) { ++ ret = air_buckpbus_reg_write(phydev, 0x102260E4, 0xFF11); ++ ret |= air_buckpbus_reg_write(phydev, 0x10224004, 0x0755); ++ ret |= air_buckpbus_reg_write(phydev, 0x10224018, 0x14); ++ ret |= air_buckpbus_reg_write(phydev, 0x1022450C, 0x0755); ++ ret |= air_buckpbus_reg_write(phydev, 0x1022A140, 0x10); ++ ret |= air_buckpbus_reg_write(phydev, 0x10226100, 0xF000000C); ++ ret |= air_buckpbus_reg_write(phydev, 0x10270100, 0xC); ++ } else { ++ ret = air_buckpbus_reg_write(phydev, 0x102260E4, 0xFFAA); ++ ret |= air_buckpbus_reg_write(phydev, 0x10224004, 0x07AA); ++ ret |= air_buckpbus_reg_write(phydev, 0x10224018, 0x4); ++ ret |= air_buckpbus_reg_write(phydev, 0x1022450C, 0x07AA); ++ ret |= air_buckpbus_reg_write(phydev, 0x1022A140, 0x20); ++ ret |= air_buckpbus_reg_write(phydev, 0x10226100, 0xF000000F); ++ ret |= air_buckpbus_reg_write(phydev, 0x10270100, 0xC); ++ } ++ } ++ } ++ return ret; ++} ++ ++static int an8801r_read_status(struct phy_device *phydev) ++{ ++ int ret, prespeed = phydev->speed; ++ u32 data; ++ ++ ret = genphy_read_status(phydev); ++ if (phydev->link == LINK_DOWN) { ++ prespeed = 0; ++ phydev->speed = 0; ++ } ++ if (prespeed != phydev->speed && phydev->link == LINK_UP) { ++ prespeed = phydev->speed; ++ dev_dbg(phydev_dev(phydev), "AN8801R SPEED %d\n", prespeed); ++ if (prespeed == SPEED_1000) { ++ data = air_buckpbus_reg_read(phydev, 0x10005054); ++ data |= BIT(0); ++ air_buckpbus_reg_write(phydev, 0x10005054, data); ++ } else { ++ data = air_buckpbus_reg_read(phydev, 0x10005054); ++ data &= ~BIT(0); ++ air_buckpbus_reg_write(phydev, 0x10005054, data); ++ } ++ } ++ return ret; ++} ++ ++static int an8801_read_status(struct phy_device *phydev) ++{ ++ if (phydev->interface == PHY_INTERFACE_MODE_SGMII) { ++ an8801sb_read_status(phydev); ++ } else if (phydev->interface == PHY_INTERFACE_MODE_RGMII) { ++ an8801r_read_status(phydev); ++ } else { ++ dev_info(phydev_dev(phydev), "AN8801 Phy-mode not support!\n"); ++ return -1; ++ } ++ return 0; ++} ++ ++static struct phy_driver airoha_driver[] = { ++ { ++ .phy_id = AN8801_PHY_ID, ++ .name = "Airoha AN8801", ++ .phy_id_mask = 0x0ffffff0, ++ .features = PHY_GBIT_FEATURES, ++ .config_init = an8801_config_init, ++ .config_aneg = genphy_config_aneg, ++ .probe = an8801_phy_probe, ++ .remove = an8801_phy_remove, ++ .read_status = an8801_read_status, ++ .config_intr = an8801_config_intr, ++#if (KERNEL_VERSION(5, 11, 0) < LINUX_VERSION_CODE) ++ .handle_interrupt = an8801_handle_interrupt, ++#else ++ .did_interrupt = an8801_did_interrupt, ++ .ack_interrupt = an8801_ack_interrupt, ++#endif ++ } ++}; ++ ++module_phy_driver(airoha_driver); ++ ++static struct mdio_device_id __maybe_unused airoha_tbl[] = { ++ { AN8801_PHY_ID, 0x0ffffff0 }, ++ { } ++}; ++ ++MODULE_DEVICE_TABLE(mdio, airoha_tbl); +diff --git a/target/linux/mediatek/files-6.6/drivers/net/phy/an8801.h b/target/linux/mediatek/files-6.6/drivers/net/phy/an8801.h +new file mode 100644 +index 0000000000..a7f481155c +--- /dev/null ++++ b/target/linux/mediatek/files-6.6/drivers/net/phy/an8801.h +@@ -0,0 +1,233 @@ ++/* SPDX-License-Identifier: GPL-2.0 */ ++/*PURPOSE: ++ *Define Airoha phy driver function ++ * ++ *NOTES: ++ * ++ */ ++ ++#ifndef __AN8801_H ++#define __AN8801_H ++ ++/* NAMING DECLARATIONS ++ */ ++#define AN8801_DRIVER_VERSION "1.1.7" ++ ++#define DEBUGFS_COUNTER "counter" ++#define DEBUGFS_INFO "driver_info" ++#define DEBUGFS_PBUS_OP "pbus_op" ++#define DEBUGFS_POLARITY "polarity" ++#define DEBUGFS_MDIO "mdio" ++ ++#define AN8801_MDIO_PHY_ID 0x1 ++#define AN8801_PHY_ID1 0xc0ff ++#define AN8801_PHY_ID2 0x0421 ++#define AN8801_PHY_ID ((u32)((AN8801_PHY_ID1 << 16) | AN8801_PHY_ID2)) ++ ++#define TRUE 1 ++#define FALSE 0 ++#define LINK_UP 1 ++#define LINK_DOWN 0 ++ ++#define MAX_LED_SIZE 3 ++ ++#define MAX_RETRY 5 ++ ++#define AN8801_EPHY_ADDR 0x11000000 ++#define AN8801_CL22 0x00800000 ++ ++#define LED_ENABLE 1 ++#define LED_DISABLE 0 ++ ++#define AN8801SB_DEBUGFS ++ ++#ifndef BIT ++#define BIT(nr) (1 << (nr)) ++#endif ++#ifndef GET_BIT ++#define GET_BIT(val, bit) ((val & BIT(bit)) >> bit) ++#endif ++ ++#define LED_BCR (0x021) ++#define LED_BCR_EXT_CTRL BIT(15) ++#define LED_BCR_EVT_ALL BIT(4) ++#define LED_BCR_CLK_EN BIT(3) ++#define LED_BCR_TIME_TEST BIT(2) ++#define LED_BCR_MODE_MASK (3) ++#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 (0x022) ++#define LED_ON_DUR_MASK (0xffff) ++ ++#define LED_BLK_DUR (0x023) ++#define LED_BLK_DUR_MASK (0xffff) ++ ++#define LED_ON_CTRL(i) (0x024 + ((i) * 2)) ++#define LED_ON_EN BIT(15) ++#define LED_ON_POL BIT(14) ++#define LED_ON_EVT_MASK (0x7f) ++#define LED_ON_EVT_FORCE BIT(6) ++#define LED_ON_EVT_HDX BIT(5) ++#define LED_ON_EVT_FDX BIT(4) ++#define LED_ON_EVT_LINK_DN BIT(3) ++#define LED_ON_EVT_LINK_10M BIT(2) ++#define LED_ON_EVT_LINK_100M BIT(1) ++#define LED_ON_EVT_LINK_1000M BIT(0) ++ ++#define LED_BLK_CTRL(i) (0x025 + ((i) * 2)) ++#define LED_BLK_EVT_MASK (0x3ff) ++#define LED_BLK_EVT_FORCE BIT(9) ++#define LED_BLK_EVT_10M_RX BIT(5) ++#define LED_BLK_EVT_10M_TX BIT(4) ++#define LED_BLK_EVT_100M_RX BIT(3) ++#define LED_BLK_EVT_100M_TX BIT(2) ++#define LED_BLK_EVT_1000M_RX BIT(1) ++#define LED_BLK_EVT_1000M_TX BIT(0) ++ ++#define UNIT_LED_BLINK_DURATION 780 ++ ++/* Serdes auto negotiation restart */ ++#define AN8801SB_SGMII_AN0_ANRESTART (0x0200) ++#define AN8801SB_SGMII_AN0_AN_DONE (0x0001) ++#define AN8801SB_SGMII_AN0_RESET (0x8000) ++ ++ ++#define PHY_PRE_SPEED_REG (0x2b) ++ ++#define MMD_DEV_VSPEC1 (0x1E) ++#define MMD_DEV_VSPEC2 (0x1F) ++ ++#define RGMII_DELAY_STEP_MASK 0x7 ++#define RGMII_RXDELAY_ALIGN BIT(4) ++#define RGMII_RXDELAY_FORCE_MODE BIT(24) ++#define RGMII_TXDELAY_FORCE_MODE BIT(24) ++#define AN8801_RG_PKG_SEL_LSB BIT(4) ++#define AN8801_RG_PKG_SEL_MSB BIT(5) ++ ++/* ++ *For reference only ++ */ ++/* User-defined.B */ ++/* 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_BLK (0x0) ++/* No link on, activity(1G/100M/10M TX/RX) */ ++#define AIR_LED1_ON (0x0) ++#define AIR_LED1_BLK \ ++ (LED_BLK_EVT_1000M_TX | LED_BLK_EVT_1000M_RX | \ ++ LED_BLK_EVT_100M_TX | LED_BLK_EVT_100M_RX | \ ++ LED_BLK_EVT_10M_TX | LED_BLK_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_BLK \ ++ (LED_BLK_EVT_100M_TX | LED_BLK_EVT_100M_RX | \ ++ LED_BLK_EVT_10M_TX | LED_BLK_EVT_10M_RX) ++/* User-defined.E */ ++ ++/* Invalid data */ ++#define INVALID_DATA 0xffffffff ++ ++#define LED_BLINK_DURATION(f) (UNIT_LED_BLINK_DURATION << (f)) ++#define LED_GPIO_SEL(led, gpio) ((led) << ((gpio) * 3)) ++ ++/* Interrupt GPIO number, should not conflict with LED */ ++#define AIR_INTERRUPT_GPIO 3 ++ ++/* DATA TYPE DECLARATIONS ++ */ ++enum AIR_LED_GPIO_PIN_T { ++ AIR_LED_GPIO1 = 1, ++ AIR_LED_GPIO2 = 2, ++ AIR_LED_GPIO3 = 3, ++ AIR_LED_GPIO5 = 5, ++ AIR_LED_GPIO8 = 8, ++ AIR_LED_GPIO9 = 9, ++}; ++ ++enum AIR_LED_T { ++ AIR_LED0 = 0, ++ AIR_LED1, ++ AIR_LED2, ++ AIR_LED3 ++}; ++ ++enum AIR_LED_BLK_DUT_T { ++ AIR_LED_BLK_DUR_32M = 0, ++ AIR_LED_BLK_DUR_64M, ++ AIR_LED_BLK_DUR_128M, ++ AIR_LED_BLK_DUR_256M, ++ AIR_LED_BLK_DUR_512M, ++ AIR_LED_BLK_DUR_1024M, ++ AIR_LED_BLK_DUR_LAST ++}; ++ ++enum AIR_LED_POLARITY { ++ AIR_ACTIVE_LOW = 0, ++ AIR_ACTIVE_HIGH, ++}; ++ ++enum AIR_LED_MODE_T { ++ AIR_LED_MODE_DISABLE = 0, ++ AIR_LED_MODE_USER_DEFINE, ++ AIR_LED_MODE_LAST ++}; ++ ++enum AIR_RGMII_DELAY_STEP_T { ++ AIR_RGMII_DELAY_NOSTEP = 0, ++ AIR_RGMII_DELAY_STEP_1 = 1, ++ AIR_RGMII_DELAY_STEP_2 = 2, ++ AIR_RGMII_DELAY_STEP_3 = 3, ++ AIR_RGMII_DELAY_STEP_4 = 4, ++ AIR_RGMII_DELAY_STEP_5 = 5, ++ AIR_RGMII_DELAY_STEP_6 = 6, ++ AIR_RGMII_DELAY_STEP_7 = 7, ++}; ++ ++struct AIR_LED_CFG_T { ++ u16 en; ++ u16 gpio; ++ u16 pol; ++ u16 on_cfg; ++ u16 blk_cfg; ++}; ++ ++struct an8801_priv { ++ struct AIR_LED_CFG_T led_cfg[MAX_LED_SIZE]; ++ u32 led_blink_cfg; ++ u8 rxdelay_force; ++ u8 txdelay_force; ++ u16 rxdelay_step; ++ u8 rxdelay_align; ++ u16 txdelay_step; ++#ifdef AN8801SB_DEBUGFS ++ struct dentry *debugfs_root; ++#endif ++ int pol; ++ int surge; ++ int sgmii_mode; ++}; ++ ++enum an8801_polarity { ++ AIR_POL_TX_NOR_RX_REV, ++ AIR_POL_TX_REV_RX_REV, ++ AIR_POL_TX_NOR_RX_NOR, ++ AIR_POL_TX_REV_RX_NOR, ++}; ++ ++enum air_surge { ++ AIR_SURGE_0R, ++ AIR_SURGE_5R, ++ AIR_SURGE_LAST = 0xff ++}; ++ ++enum air_sgmii_mode { ++ AIR_SGMII_AN, ++ AIR_SGMII_FORCE, ++ AIR_SGMII_LAST = 0xff ++}; ++ ++#endif /* End of __AN8801_H */ +diff --git a/target/linux/mediatek/filogic/base-files/etc/board.d/01_leds b/target/linux/mediatek/filogic/base-files/etc/board.d/01_leds +index 217f495f95..85f6472771 100644 +--- a/target/linux/mediatek/filogic/base-files/etc/board.d/01_leds ++++ b/target/linux/mediatek/filogic/base-files/etc/board.d/01_leds +@@ -50,6 +50,10 @@ cudy,wr3000h-v1) + ucidef_set_led_netdev "wan" "wan" "white:wan" "wan" "link tx rx" + ucidef_set_led_netdev "internet" "internet" "white:wan-online" "wan" "link" + ;; ++edgecore,eap111|\ ++edgecore,eap112) ++ ucidef_set_led_netdev "wan" "wan" "orange:indicator" "eth1" ++ ucidef_set_led_netdev "wlan5g" "WLAN5G" "blue:indicator" "phy1-ap0" + glinet,gl-x3000|\ + glinet,gl-xe3000) + ucidef_set_led_default "power" "POWER" "green:power" "1" +diff --git a/target/linux/mediatek/filogic/base-files/etc/board.d/02_network b/target/linux/mediatek/filogic/base-files/etc/board.d/02_network +index 59a7a720f3..49d187a09c 100644 +--- a/target/linux/mediatek/filogic/base-files/etc/board.d/02_network ++++ b/target/linux/mediatek/filogic/base-files/etc/board.d/02_network +@@ -69,11 +69,13 @@ mediatek_setup_interfaces() + ucidef_set_interfaces_lan_wan "lan1 lan2 lan3 lan4 sfp2" "eth1 wan" + ;; + bananapi,bpi-r3-mini|\ +- edgecore,eap111|\ + huasifei,wh3000|\ + huasifei,wh3000-pro) + ucidef_set_interfaces_lan_wan eth0 eth1 + ;; ++ edgecore,eap111) ++ ucidef_set_interfaces_lan_wan eth1 eth0 ++ ;; + bananapi,bpi-r4|\ + bananapi,bpi-r4-poe) + ucidef_set_interfaces_lan_wan "lan1 lan2 lan3 eth1" "wan eth2" +@@ -170,6 +172,11 @@ mediatek_setup_macs() + bananapi,bpi-r4) + wan_mac=$(macaddr_add $(cat /sys/class/net/eth0/address) 1) + ;; ++ edgecore,eap111|\ ++ edgecore,eap112) ++ lan_mac=$(mtd_get_mac_binary "Factory" 0x24) ++ wan_mac=$(mtd_get_mac_binary "Factory" 0x2a) ++ ;; + h3c,magic-nx30-pro) + wan_mac=$(mtd_get_mac_ascii pdt_data_1 ethaddr) + lan_mac=$(macaddr_add "$wan_mac" 1) +diff --git a/target/linux/mediatek/filogic/base-files/etc/init.d/bootcount b/target/linux/mediatek/filogic/base-files/etc/init.d/bootcount +index a0e7d40862..57a0bc2ec9 100755 +--- a/target/linux/mediatek/filogic/base-files/etc/init.d/bootcount ++++ b/target/linux/mediatek/filogic/base-files/etc/init.d/bootcount +@@ -5,6 +5,14 @@ START=99 + + boot() { + case $(board_name) in ++ edgecore,eap111|\ ++ edgecore,eap112) ++ bootcount=$(fw_printenv -n bootcount) ++ [ "$bootcount" != 0 ] && fw_setenv bootcount 0 ++ # enable dualboot ++ avail=$(fw_printenv -n upgrade_available) ++ [ ${avail} -eq 0 ] && fw_setenv upgrade_available 1 ++ ;; + xiaomi,mi-router-ax3000t) + . /lib/upgrade/common.sh + [ "$(rootfs_type)" = "tmpfs" ] && \ +diff --git a/target/linux/mediatek/filogic/base-files/lib/upgrade/platform.sh b/target/linux/mediatek/filogic/base-files/lib/upgrade/platform.sh +index 64b6d4b60b..27bdc2b07a 100755 +--- a/target/linux/mediatek/filogic/base-files/lib/upgrade/platform.sh ++++ b/target/linux/mediatek/filogic/base-files/lib/upgrade/platform.sh +@@ -1,5 +1,5 @@ + REQUIRE_IMAGE_METADATA=1 +-RAMFS_COPY_BIN='fitblk' ++RAMFS_COPY_BIN='fitblk fw_setenv' + + asus_initial_setup() + { +@@ -177,6 +177,36 @@ platform_do_upgrade() { + CI_ROOT_UBIPART=ubi + nand_do_upgrade "$1" + ;; ++ edgecore,eap111|\ ++ edgecore,eap112) ++ if [ -e /tmp/downgrade ]; then ++ CI_UBIPART="rootfs1" ++ { echo 'active 1'; echo 'upgrade_available 0'; } > /tmp/fw_setenv.txt || exit 1 ++ CI_FWSETENV="-s /tmp/fw_setenv.txt" ++ else ++ local CI_UBIPART_B="" ++ if grep -q rootfs1 /proc/cmdline; then ++ CI_UBIPART="rootfs2" ++ CI_UBIPART_B="rootfs1" ++ CI_FWSETENV="active 2" ++ elif grep -q rootfs2 /proc/cmdline; then ++ CI_UBIPART="rootfs1" ++ CI_UBIPART_B="rootfs2" ++ CI_FWSETENV="active 1" ++ else ++ CI_UBIPART="rootfs1" ++ CI_UBIPART_B="" ++ CI_FWSETENV="active 1" ++ fi ++ if [ "$(fw_printenv -n upgrade_available 2>/dev/null)" = "0" ]; then ++ if [ -n "$CI_UBIPART_B" ]; then ++ CI_UBIPART="$CI_UBIPART_B" ++ CI_FWSETENV="" ++ fi ++ fi ++ fi ++ nand_do_upgrade "$1" ++ ;; + *) + nand_do_upgrade "$1" + ;; +diff --git a/target/linux/mediatek/filogic/config-6.6 b/target/linux/mediatek/filogic/config-6.6 +index 607d1b3c6f..621cccf5d2 100644 +--- a/target/linux/mediatek/filogic/config-6.6 ++++ b/target/linux/mediatek/filogic/config-6.6 +@@ -1,6 +1,7 @@ + CONFIG_64BIT=y + # CONFIG_AHCI_MTK is not set + CONFIG_AIROHA_EN8801SC_PHY=y ++CONFIG_AIROHA_AN8801_PHY=y + CONFIG_AIR_AN8855_PHY=y + CONFIG_ARCH_BINFMT_ELF_EXTRA_PHDRS=y + CONFIG_ARCH_CORRECT_STACKTRACE_ON_KRETPROBE=y +diff --git a/target/linux/mediatek/patches-6.6/970-eap111-swap-eth-ports.patch b/target/linux/mediatek/patches-6.6/970-eap111-swap-eth-ports.patch +new file mode 100644 +index 0000000000..948869e911 +--- /dev/null ++++ b/target/linux/mediatek/patches-6.6/970-eap111-swap-eth-ports.patch +@@ -0,0 +1,14 @@ ++--- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c +++++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c ++@@ -5246,6 +5246,11 @@ static int mtk_probe(struct platform_dev ++ } ++ } ++ +++ if (of_machine_is_compatible("edgecore,eap111")) { +++ sprintf(eth->netdev[0]->name, "eth1"); +++ sprintf(eth->netdev[1]->name, "eth0"); +++ } +++ ++ for (i = 0; i < MTK_MAX_DEVS; i++) { ++ if (!eth->netdev[i]) ++ continue; +diff --git a/target/linux/mediatek/patches-6.6/999--net-phy-support-an8801-and-mdio-second-phy.patch b/target/linux/mediatek/patches-6.6/999--net-phy-support-an8801-and-mdio-second-phy.patch +new file mode 100644 +index 0000000000..e43e02d837 +--- /dev/null ++++ b/target/linux/mediatek/patches-6.6/999--net-phy-support-an8801-and-mdio-second-phy.patch +@@ -0,0 +1,92 @@ ++diff -upr linux-6.6.89/drivers/net/mdio/of_mdio.c /home/build9/seb_working/linux-6.6.89/drivers/net/mdio/of_mdio.c ++--- a/drivers/net/mdio/of_mdio.c 2025-08-22 14:44:34.467093772 +0800 +++++ b/drivers/net/mdio/of_mdio.c 2025-08-22 14:39:10.212748831 +0800 ++@@ -145,7 +145,10 @@ static int __of_mdiobus_parse_phys(struc ++ int addr, rc = 0; ++ ++ /* Loop over the child nodes and register a phy_device for each phy */ +++ int an8801=0; ++ for_each_available_child_of_node(np, child) { +++ if(an8801==1)break; +++ ++ if (of_node_name_eq(child, "ethernet-phy-package")) { ++ /* Ignore invalid ethernet-phy-package node */ ++ if (!of_find_property(child, "reg", NULL)) ++@@ -166,8 +169,25 @@ static int __of_mdiobus_parse_phys(struc ++ continue; ++ } ++ ++- if (of_mdiobus_child_is_phy(child)) +++ if (of_mdiobus_child_is_phy(child)) { +++ if(addr==30) +++ { +++ int phy_id ; +++ +++ phy_id = mdiobus_read(mdio, addr, MII_PHYSID1) << 16 ; +++ phy_id = phy_id + mdiobus_read(mdio, addr, MII_PHYSID2); +++ dev_info(&mdio->dev, "[of_mdio] %s %d addr:%d phy_id:0x%x \n",__func__, __LINE__, addr, phy_id); +++ +++ if (phy_id==0 || phy_id==0x1a750000) +++ { +++ dev_info(&mdio->dev, "[of_mdio] %s %d continue \n",__func__, __LINE__); +++ continue; +++ } +++ else +++ an8801=1; +++ } ++ rc = of_mdiobus_register_phy(mdio, child, addr); +++ } ++ else ++ rc = of_mdiobus_register_device(mdio, child, addr); ++ ++diff -upr linux-6.6.89/drivers/net/phy/Kconfig /home/build9/seb_working/linux-6.6.89/drivers/net/phy/Kconfig ++--- a/drivers/net/phy/Kconfig 2025-08-08 17:02:38.023751484 +0800 +++++ b/drivers/net/phy/Kconfig 2025-08-22 15:01:27.708103066 +0800 ++@@ -142,6 +142,11 @@ endif # RTL8366_SMI ++ ++ comment "MII PHY device drivers" ++ +++config AIROHA_AN8801_PHY +++ tristate "Drivers for Airoha AN8801 Gigabit PHYs" +++ help +++ Currently supports the Airoha AN8801 PHY. +++ ++ config AIROHA_EN8801SC_PHY ++ tristate "Airoha EN8801SC Gigabit PHY" ++ help ++diff -upr linux-6.6.89/drivers/net/phy/Makefile /home/build9/seb_working/linux-6.6.89/drivers/net/phy/Makefile ++--- a/drivers/net/phy/Makefile 2025-08-08 17:02:38.023751484 +0800 +++++ b/drivers/net/phy/Makefile 2025-08-22 15:02:07.464601732 +0800 ++@@ -49,6 +49,7 @@ obj-y += $(sfp-obj-y) $(sfp-obj-m) ++ ++ obj-$(CONFIG_ADIN_PHY) += adin.o ++ obj-$(CONFIG_ADIN1100_PHY) += adin1100.o +++obj-$(CONFIG_AIROHA_AN8801_PHY) += an8801.o ++ obj-$(CONFIG_AIROHA_EN8801SC_PHY) += en8801sc.o ++ obj-$(CONFIG_AIR_AN8855_PHY) += air_an8855.o ++ obj-$(CONFIG_AIR_EN8811H_PHY) += air_en8811h.o ++diff -upr linux-6.6.89/drivers/net/phy/phylink.c /home/build9/seb_working/linux-6.6.89/drivers/net/phy/phylink.c ++--- a/drivers/net/phy/phylink.c 2025-08-22 14:43:00.994406615 +0800 +++++ b/drivers/net/phy/phylink.c 2025-08-22 14:39:10.280749309 +0800 ++@@ -2290,7 +2290,20 @@ int phylink_fwnode_phy_connect(struct ph ++ /* We're done with the phy_node handle */ ++ fwnode_handle_put(phy_fwnode); ++ if (!phy_dev) ++- return -ENODEV; +++ { +++ phylink_info(pl, "[phylink] reload phy-handle2. %s %d\n",__func__, __LINE__); +++ phy_fwnode = fwnode_find_reference(fwnode, "phy-handle2", 0); +++ if (IS_ERR(phy_fwnode)) { +++ if (pl->cfg_link_an_mode == MLO_AN_PHY) +++ return -ENODEV; +++ return 0; +++ } +++ phy_dev = fwnode_phy_find_device(phy_fwnode); +++ fwnode_handle_put(phy_fwnode); +++ +++ if (!phy_dev) +++ return -ENODEV; +++ } ++ ++ /* Use PHY device/driver interface */ ++ if (pl->link_interface == PHY_INTERFACE_MODE_NA) { +-- +2.34.1 +