Amlogic 移植AN8801 PHY 驱动

Amlogic A113x2增加AN8801R PHY驱动支持

修改common/drivers/net/phy/Kconfig

--- a/common/drivers/net/phy/Kconfig
+++ b/common/drivers/net/phy/Kconfig
@@ -338,6 +338,15 @@ config REALTEK_PHY
 	help
 	  Supports the Realtek 821x PHY.
 
+config AIROHA_AN8801_PHY
+	tristate "Drivers for Airoha AN8801 Gigabit PHYs"
+	default y if ARCH_MESON
+	help
+	  Currently supports the Airoha AN8801 PHY.
+	  On Amlogic platforms it defaults to built-in so the driver is
+	  present before stmmac attaches; otherwise eth0 falls back to
+	  Generic PHY when an8801.ko is missing from rootfs.
+
 config RENESAS_PHY
 	tristate "Renesas PHYs"
 	help

修改common/drivers/net/phy/Makefile

--- a/common/drivers/net/phy/Makefile
+++ b/common/drivers/net/phy/Makefile
@@ -88,6 +88,7 @@ obj-$(CONFIG_NXP_CBTX_PHY)	+= nxp-cbtx.o
 obj-$(CONFIG_NXP_TJA11XX_PHY)	+= nxp-tja11xx.o
 obj-$(CONFIG_QSEMI_PHY)		+= qsemi.o
 obj-$(CONFIG_REALTEK_PHY)	+= realtek.o
+obj-$(CONFIG_AIROHA_AN8801_PHY)	+= an8801.o
 obj-$(CONFIG_RENESAS_PHY)	+= uPD60620.o
 obj-$(CONFIG_ROCKCHIP_PHY)	+= rockchip.o
 obj-$(CONFIG_SMSC_PHY)		+= smsc.o

增加common/drivers/net/phy/an8801.c

--- /dev/null
+++ b/common/drivers/net/phy/an8801.c
@@ -0,0 +1,1288 @@
+// SPDX-License-Identifier: GPL-2.0
+/* FILE NAME:  an8801.c
+ * PURPOSE:
+ *      Airoha phy driver for Linux
+ * NOTES:
+ *
+ */
+
+/* INCLUDE FILE DECLARATIONS
+ */
+
+#include <linux/of_device.h>
+#include <linux/of_gpio.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/phy.h>
+#include <linux/gpio.h>
+#include <linux/gpio/consumer.h>
+#include <linux/pinctrl/pinconf-generic.h>
+#include <linux/netdevice.h>
+#include <linux/version.h>
+#include <linux/debugfs.h>
+
+#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(phy) (_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_mdiobus_lock(phy)   (phydev_mdiobus(phy)->mdio_lock)
+#define phydev_cfg(phy)            ((struct an8801r_priv *)(phy)->priv)
+
+#define mdiobus_lock(phy)          (mutex_lock(&phydev_mdiobus_lock(phy)))
+#define mdiobus_unlock(phy)        (mutex_unlock(&phydev_mdiobus_lock(phy)))
+
+#ifdef AN8801R_DEBUGFS
+#define AN8801R_DEBUGFS_PBUS_HELP_STRING \
+	"\nUsage: echo w [pbus_reg] [value] > /sys/" \
+	"kernel/debug/mdio-bus\':[phy_addr]/pbus_reg_op" \
+	"\n       echo r [pbus_reg] > /sys/" \
+	"kernel/debug/mdio-bus\':[phy_addr]/pbus_op" \
+	"\nRead example: 0x10000054" \
+	"\necho r 0x10000054> /sys/" \
+	"kernel/debug/mdio-bus\':[phy_addr]/pbus_reg_op" \
+	"\nWrite example: Register 0x10000054 0x0" \
+	"\necho w 0x10000054 0x0> /sys/" \
+	"kernel/debug/mdio-bus\':[phy_addr]/pbus_op" \
+	"\n"
+#define AN8801R_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 */
+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_GPIO1, AIR_ACTIVE_LOW,  AIR_LED0_ON, AIR_LED0_BLK},
+	/* LED1 */
+	{LED_ENABLE, AIR_LED_GPIO2, AIR_ACTIVE_HIGH, AIR_LED1_ON, AIR_LED1_BLK},
+	/* LED2 */
+	{LED_ENABLE, AIR_LED_GPIO3, AIR_ACTIVE_HIGH, 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;
+
+	err = __phy_write(phydev, 0x1F, 4);
+	if (err)
+		return err;
+
+	err |= __phy_write(phydev, 0x10, 0);
+	err |= __phy_write(phydev, 0x11, (u16)(addr >> 16));
+	err |= __phy_write(phydev, 0x12, (u16)(addr & 0xffff));
+	err |= __phy_write(phydev, 0x13, (u16)(data >> 16));
+	err |= __phy_write(phydev, 0x14, (u16)(data & 0xffff));
+	err |= __phy_write(phydev, 0x1F, 0);
+
+	return err;
+}
+
+static int __air_buckpbus_reg_read(struct phy_device *phydev, u32 addr,
+				   u32 *data)
+{
+	int err = 0;
+	u32 data_h, data_l;
+
+	err = __phy_write(phydev, 0x1F, 4);
+	if (err)
+		return err;
+
+	err |= __phy_write(phydev, 0x10, 0);
+	err |= __phy_write(phydev, 0x15, (u16)(addr >> 16));
+	err |= __phy_write(phydev, 0x16, (u16)(addr & 0xffff));
+	data_h = __phy_read(phydev, 0x17);
+	data_l = __phy_read(phydev, 0x18);
+	err |= __phy_write(phydev, 0x1F, 0);
+	if (err)
+		return err;
+
+	*data = ((data_h & 0xffff) << 16) | (data_l & 0xffff);
+	return 0;
+}
+
+static int __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;
+
+	err = __phy_write(phydev, 0x1F, 4);
+	if (err)
+		return err;
+	err |= __phy_write(phydev, 0x10, 0);
+	err |= __phy_write(phydev, 0x15, (u16)(addr >> 16));
+	err |= __phy_write(phydev, 0x16, (u16)(addr & 0xffff));
+	data_h = __phy_read(phydev, 0x17);
+	data_l = __phy_read(phydev, 0x18);
+	if (err) {
+		__phy_write(phydev, 0x1F, 0);
+		return err;
+	}
+
+	data_old = data_l | (data_h << 16);
+	data_new = (data_old & ~mask) | set;
+	if (data_new == data_old)
+		return __phy_write(phydev, 0x1F, 0);
+
+	err |= __phy_write(phydev, 0x11, (u16)(addr >> 16));
+	err |= __phy_write(phydev, 0x12, (u16)(addr & 0xffff));
+	err |= __phy_write(phydev, 0x13, (u16)(data_new >> 16));
+	err |= __phy_write(phydev, 0x14, (u16)(data_new & 0xffff));
+	err |= __phy_write(phydev, 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 int air_buckpbus_reg_read(struct phy_device *phydev, u32 addr, u32 *data)
+{
+	int err;
+
+	mdiobus_lock(phydev);
+	err = __air_buckpbus_reg_read(phydev, addr, data);
+	mdiobus_unlock(phydev);
+
+	return err;
+}
+
+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_read_mmd(struct phy_device *phydev, int devad, u32 regnum)
+{
+	int val;
+	struct mii_bus *bus = phydev->mdio.bus;
+	int phy_addr = phydev->mdio.addr;
+
+	__mdiobus_write(bus, phy_addr, MII_MMD_CTRL, devad);
+	__mdiobus_write(bus, phy_addr, MII_MMD_DATA, regnum);
+	__mdiobus_write(bus, phy_addr, MII_MMD_CTRL,
+			devad | MII_MMD_CTRL_NOINCR);
+
+	val = __mdiobus_read(bus, phy_addr, MII_MMD_DATA);
+
+	return val;
+}
+
+static int __air_write_mmd(struct phy_device *phydev, int devad, u32 regnum, u16 val)
+{
+	int ret;
+	struct mii_bus *bus = phydev->mdio.bus;
+	int phy_addr = phydev->mdio.addr;
+
+	__mdiobus_write(bus, phy_addr, MII_MMD_CTRL, devad);
+	__mdiobus_write(bus, phy_addr, MII_MMD_DATA, regnum);
+	__mdiobus_write(bus, phy_addr, MII_MMD_CTRL,
+			devad | MII_MMD_CTRL_NOINCR);
+
+	__mdiobus_write(bus, phy_addr, MII_MMD_DATA, val);
+
+	ret = 0;
+
+	return ret;
+}
+
+static int __air_modify_mmd(struct phy_device *phydev, int devad, u32 regnum,
+		     u16 mask, u16 set)
+{
+	int new, ret;
+
+	ret = __air_read_mmd(phydev, devad, regnum);
+	if (ret < 0)
+		return ret;
+
+	new = (ret & ~mask) | set;
+	if (new == ret)
+		return 0;
+
+	ret = __air_write_mmd(phydev, devad, regnum, new);
+
+	return ret < 0 ? ret : 0;
+}
+
+static int air_modify_mmd(struct phy_device *phydev, int devad, u32 regnum,
+		   u16 mask, u16 set)
+{
+	int ret;
+
+	mdiobus_lock(phydev);
+	ret = __air_modify_mmd(phydev, devad, regnum, mask, set);
+	mdiobus_unlock(phydev);
+
+	return ret;
+}
+
+static int an8801r_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 an8801r_led_set_blink(struct phy_device *phydev, u16 blink)
+{
+	int err;
+
+	err = phy_write_mmd(phydev, 0x1f, LED_BLK_DUR,
+			    LED_BLINK_DURATION(blink));
+	if (err)
+		return err;
+
+	return phy_write_mmd(phydev, 0x1f, LED_ON_DUR,
+			     (LED_BLINK_DURATION(blink) >> 1));
+}
+
+static int an8801r_led_set_mode(struct phy_device *phydev, u8 mode)
+{
+	switch (mode) {
+	case AIR_LED_MODE_DISABLE:
+		return air_modify_mmd(phydev, 0x1f, LED_BCR,
+				      (LED_BCR_EXT_CTRL | LED_BCR_CLK_EN),
+				      0x0);
+	case AIR_LED_MODE_USER_DEFINE:
+		return air_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 an8801r_led_set_state(struct phy_device *phydev, u8 entity, u8 state)
+{
+	return air_modify_mmd(phydev, 0x1f, LED_ON_CTRL(entity), LED_ON_EN,
+			      (state)? LED_ON_EN: 0x0);
+}
+
+static int an8801r_led_init(struct phy_device *phydev)
+{
+	struct an8801r_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 = an8801r_led_set_blink(phydev, led_blink_cfg);
+	if (ret != 0)
+		return ret;
+
+	ret = an8801r_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 = an8801r_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 = BIT(led_cfg[led_id].gpio);
+			ret |= air_buckpbus_reg_modify(phydev, 0x10000054, data, data);
+
+			data = LED_GPIO_SEL(led_id, led_cfg[led_id].gpio);
+			ret |= air_buckpbus_reg_modify(phydev, 0x10000058, data, data);
+
+			data = BIT(led_cfg[led_id].gpio);
+			ret |= air_buckpbus_reg_modify(phydev, 0x10000070, data, 0);
+
+			ret |= an8801r_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 an8801r_ack_interrupt(struct phy_device *phydev)
+{
+	u32 reg_val = 0;
+
+	/* Reset WOL status */
+	air_buckpbus_reg_write(phydev, 0x10285404, 0x102);
+	air_buckpbus_reg_read(phydev, 0x10285400, &reg_val);
+	air_buckpbus_reg_write(phydev, 0x10285400, 0x0);
+	air_buckpbus_reg_write(phydev, 0x10285400, reg_val | 0x10);
+	air_buckpbus_reg_write(phydev, 0x10285404, 0x12);
+	/* Clear the interrupts by writing the reg */
+	air_buckpbus_reg_write(phydev, 0x10285704, 0x1f);
+	return 0;
+}
+
+static int an8801r_config_intr(struct phy_device *phydev)
+{
+	if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
+		air_buckpbus_reg_write(phydev, 0x1000007c, 0x10000);
+		air_buckpbus_reg_modify(phydev, 0x10285700, 0x1, 0x1);
+	} else {
+		air_buckpbus_reg_write(phydev, 0x1000007c, 0x0);
+		air_buckpbus_reg_modify(phydev, 0x10285700, 0x1, 0);
+	}
+	an8801r_ack_interrupt(phydev);
+	return 0;
+}
+
+static int an8801r_did_interrupt(struct phy_device *phydev)
+{
+	int err;
+	u32 intr_cfg = 0, reg_val = 0;
+
+	err = air_buckpbus_reg_read(phydev, 0x10285700, &intr_cfg);
+	if (err)
+		return err;
+
+	err = air_buckpbus_reg_read(phydev, 0x10285704, &reg_val);
+	if (err)
+		return err;
+
+	if (reg_val & 0x10)
+		return 1;
+
+	if ((intr_cfg & 0x1) && (reg_val & 0x1))
+		return 1;
+
+	return 0;
+}
+
+#if (KERNEL_VERSION(5, 11, 0) < LINUX_VERSION_CODE)
+static irqreturn_t an8801r_handle_interrupt(struct phy_device *phydev)
+{
+	if (!an8801r_did_interrupt(phydev)) {
+		return IRQ_NONE;
+	}
+
+	an8801r_ack_interrupt(phydev);
+
+	phy_trigger_machine(phydev);
+
+	return IRQ_HANDLED;
+}
+#endif
+
+static void an8801r_get_wol(struct phy_device *phydev,
+			     struct ethtool_wolinfo *wol)
+{
+	u32 reg_val = 0;
+
+	wol->supported = WAKE_MAGIC;
+	wol->wolopts = 0;
+
+	air_buckpbus_reg_read(phydev, 0x10285400, &reg_val);
+
+	wol->wolopts = (reg_val & 0xE)? WAKE_MAGIC: 0;
+}
+
+static int an8801r_set_wol(struct phy_device *phydev,
+			    struct ethtool_wolinfo *wol)
+{
+	struct net_device *attach_dev = phydev->attached_dev;
+	u32 reg_val;
+
+	if (wol->wolopts & WAKE_MAGIC) {
+		reg_val = (attach_dev->dev_addr[2] << 24) |
+			(attach_dev->dev_addr[3] << 16) |
+			(attach_dev->dev_addr[4] << 8) |
+			(attach_dev->dev_addr[5]);
+		air_buckpbus_reg_write(phydev, 0x10285114, reg_val);
+		reg_val = (attach_dev->dev_addr[0] << 8) |
+			(attach_dev->dev_addr[1]);
+		air_buckpbus_reg_write(phydev, 0x10285118, reg_val);
+		air_buckpbus_reg_modify(phydev, 0x10285400, 0xE, 0xE);
+		air_buckpbus_reg_modify(phydev, 0x10285700, 0x10, 0x10);
+	} else {
+		air_buckpbus_reg_modify(phydev, 0x10285400, 0xE, 0x0);
+		air_buckpbus_reg_modify(phydev, 0x10285700, 0x10, 0x0);
+	}
+	an8801r_ack_interrupt(phydev);
+	return 0;
+}
+
+static int an8801r_of_init(struct phy_device *phydev)
+{
+	struct device *dev = &phydev->mdio.dev;
+	struct device_node *of_node = dev->of_node;
+	struct an8801r_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;
+	}
+
+	if (of_find_property(of_node, "airoha,cko-output", NULL)) {
+		if (of_property_read_u32(of_node, "airoha,cko-output",
+					 &val) != 0) {
+			dev_err(phydev_dev(phydev),
+				   "airoha,cko-output value is invalid.");
+			return -1;
+		}
+		if (val < AIR_CKO_OUTPUT_RATE_25M ||
+		    val > AIR_CKO_OUTPUT_RATE_125M) {
+			dev_err(phydev_dev(phydev),
+				   "airoha,cko-output value %u out of range.",
+				   val);
+			return -1;
+		}
+		priv->cko_output_en = TRUE;
+		priv->cko_output_rate = val;
+	}
+
+	return 0;
+}
+
+static int an8801r_rgmii_rxdelay(struct phy_device *phydev, u16 delay, u8 align)
+{
+	u32 reg_val;
+
+	/*
+	 * Equivalent to userspace MDIO sequence (page 4 PBUS write):
+	 *   wr 0x1f,4; 0x10=0; 0x11=0x1021; 0x12=0xc02c;
+	 *   0x13=0x0100; 0x14=0x0003; restore 0x1f
+	 * → 0x1021C02C = 0x01000003 (BIT(24)|step), no RGMII_RXDELAY_ALIGN
+	 */
+	(void)align;
+	reg_val = BIT(24) | (delay & RGMII_DELAY_STEP_MASK);
+	air_buckpbus_reg_write(phydev, 0x1021C02C, reg_val);
+	reg_val = 0;
+	air_buckpbus_reg_read(phydev, 0x1021C02C, &reg_val);
+	dev_info(phydev_dev(phydev),
+		"Force rxdelay step=%d reg=0x%x (expect force|step)\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 = 0;
+	air_buckpbus_reg_read(phydev, 0x1021C024, &reg_val);
+	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 an8801r_priv *priv = phydev_cfg(phydev);
+
+	switch (phydev->interface) {
+	case PHY_INTERFACE_MODE_RGMII_TXID:
+		an8801r_rgmii_txdelay(phydev, AIR_RGMII_DELAY_STEP_4);
+		break;
+	case PHY_INTERFACE_MODE_RGMII_RXID:
+		/* Board validated: 0x1021C02C = 0x01000003 */
+		an8801r_rgmii_rxdelay(phydev, AIR_RGMII_DELAY_STEP_3, FALSE);
+		break;
+	case PHY_INTERFACE_MODE_RGMII_ID:
+		an8801r_rgmii_txdelay(phydev, AIR_RGMII_DELAY_STEP_4);
+		an8801r_rgmii_rxdelay(phydev, AIR_RGMII_DELAY_STEP_3, FALSE);
+		break;
+	case PHY_INTERFACE_MODE_RGMII:
+	default:
+		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);
+		break;
+	}
+	return 0;
+}
+
+static int an8801r_cko_config(struct phy_device *phydev)
+{
+	struct an8801r_priv *priv = phydev_cfg(phydev);
+
+	if (priv->cko_output_en) {
+		if (priv->cko_output_rate == AIR_CKO_OUTPUT_RATE_125M) {
+			air_buckpbus_reg_write(phydev, 0x10000194, 0x80);
+			air_buckpbus_reg_write(phydev, 0x100001A4, (0x3 << 10) |
+									((AIR_CKO_OUT_DRV & 0xF) << 4) |
+									(0x2 << 2));
+		} else if (priv->cko_output_rate == AIR_CKO_OUTPUT_RATE_25M) {
+			air_buckpbus_reg_write(phydev, 0x10000194, 0x0);
+			air_buckpbus_reg_write(phydev, 0x100001A4, (0x0 << 10) |
+									((AIR_CKO_OUT_DRV & 0xF) << 4) |
+									(0x2 << 2));
+		}
+	} else {
+		air_buckpbus_reg_write(phydev, 0x100001A4, 0x3);
+	}
+
+	return 0;
+}
+
+static int an8801r_config_mdix(struct phy_device *phydev)
+{
+	u16 val;
+
+	switch (phydev->mdix_ctrl) {
+	case ETH_TP_MDI:
+		val = AN8801R_MDIX_CTRL_MDI;
+		break;
+	case ETH_TP_MDI_X:
+		val = AN8801R_MDIX_CTRL_MDIX;
+		break;
+	case ETH_TP_MDI_AUTO:
+		val = AN8801R_MDIX_CTRL_AUTO;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return phy_modify_mmd(phydev, MDIO_MMD_VEND1,
+			      AN8801R_MDIX_CTRL_REG,
+			      AN8801R_MDIX_CTRL_MASK, val);
+}
+
+static int an8801r_config_aneg(struct phy_device *phydev)
+{
+	int ret;
+
+	ret = an8801r_config_mdix(phydev);
+	if (ret < 0)
+		return ret;
+
+	return genphy_config_aneg(phydev);
+}
+
+static int an8801r_config_init(struct phy_device *phydev)
+{
+	int ret;
+
+	ret = an8801r_of_init(phydev);
+	if (ret < 0)
+		return ret;
+
+	/* AUTO-MDIX: forced MDI-X often fails aneg with some switches/cables */
+	phydev->mdix_ctrl = ETH_TP_MDI_AUTO;
+	ret = an8801r_config_mdix(phydev);
+	if (ret < 0) {
+		dev_err(phydev_dev(phydev),
+			"failed to set AUTO-MDIX, ret %d\n", ret);
+		return ret;
+	}
+	dev_info(phydev_dev(phydev), "AUTO-MDIX mode\n");
+
+	phy_write_mmd(phydev, 0x1f, 0x600, 0x1e);
+	phy_write_mmd(phydev, 0x1f, 0x601, 0x2);
+	phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_EEE_ADV, 0x0);
+	mdiobus_lock(phydev);
+	__phy_write(phydev, 0x1f, 0x1);
+	__phy_write(phydev, 0x14, 0x3a14);
+	__phy_write(phydev, 0x1f, 0x0);
+	mdiobus_unlock(phydev);
+
+	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);
+
+	phy_write_mmd(phydev, 0x1e, 0x13, 0x4040);
+	phy_write_mmd(phydev, 0x1e, 0xD8, 0x1010);
+	phy_write_mmd(phydev, 0x1e, 0xD9, 0x100);
+	phy_write_mmd(phydev, 0x1e, 0xDA, 0x100);
+
+	an8801r_rgmii_delay_config(phydev);
+	an8801r_cko_config(phydev);
+
+	ret = an8801r_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",
+		AN8801R_DRIVER_VERSION);
+	return 0;
+}
+
+#ifdef AN8801R_DEBUGFS
+static ssize_t an8801r_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", &reg, &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", &reg) == -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(AN8801R_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, &reg, &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, &reg) == -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(AN8801R_DEBUGFS_MDIO_HELP_STRING);
+			return -EINVAL;
+		}
+	} else {
+		pr_notice(AN8801R_DEBUGFS_MDIO_HELP_STRING);
+		return -EINVAL;
+	}
+
+	return count;
+}
+
+static int an8801r_counter_show(struct seq_file *seq, void *v)
+{
+	struct phy_device *phydev = seq->private;
+	int ret = 0;
+	u32 pkt_cnt = 0;
+
+	seq_puts(seq, "|\t<<EFIFO COUNTER>>\n");
+	seq_puts(seq, "| Rx from Line side_S     :");
+	air_buckpbus_reg_read(phydev, 0x10270030, &pkt_cnt);
+	seq_printf(seq, "%010u |\n", pkt_cnt);
+	seq_puts(seq, "| Rx from Line side_E     :");
+	air_buckpbus_reg_read(phydev, 0x10270034, &pkt_cnt);
+	seq_printf(seq, "%010u |\n", pkt_cnt);
+	seq_puts(seq, "| Tx to System side_S     :");
+	air_buckpbus_reg_read(phydev, 0x10270038, &pkt_cnt);
+	seq_printf(seq, "%010u |\n", pkt_cnt);
+	seq_puts(seq, "| Tx to System side_E     :");
+	air_buckpbus_reg_read(phydev, 0x1027003C, &pkt_cnt);
+	seq_printf(seq, "%010u |\n", pkt_cnt);
+	seq_puts(seq, "| Rx from System side_S   :");
+	air_buckpbus_reg_read(phydev, 0x10270020, &pkt_cnt);
+	seq_printf(seq, "%010u |\n", pkt_cnt);
+	seq_puts(seq, "| Rx from System side_E   :");
+	air_buckpbus_reg_read(phydev, 0x10270024, &pkt_cnt);
+	seq_printf(seq, "%010u |\n", pkt_cnt);
+	seq_puts(seq, "| Tx to Line side_S       :");
+	air_buckpbus_reg_read(phydev, 0x10270028, &pkt_cnt);
+	seq_printf(seq, "%010u |\n", pkt_cnt);
+	seq_puts(seq, "| Tx to Line side_E       :");
+	air_buckpbus_reg_read(phydev, 0x1027002C, &pkt_cnt);
+	seq_printf(seq, "%010u |\n", pkt_cnt);
+
+	ret = air_buckpbus_reg_write(phydev, 0x1027001C, 0x3);
+	if (ret < 0)
+		return ret;
+
+	seq_puts(seq, "|\t<<LS Counter>>\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 an8801r_counter_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, an8801r_counter_show, inode->i_private);
+}
+
+static int an8801r_debugfs_pbus_help(void)
+{
+	pr_notice(AN8801R_DEBUGFS_PBUS_HELP_STRING);
+	return 0;
+}
+
+static ssize_t an8801r_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;
+	u32 val = 0;
+
+	memset(buf, 0, 64);
+
+	if (copy_from_user(buf, buffer, count))
+		return -EFAULT;
+
+	if (buf[0] == 'w') {
+		if (sscanf(buf, "w %x %x", &reg, &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;
+
+		val = 0;
+		air_buckpbus_reg_read(phydev, reg, &val);
+		pr_notice("\nphy=%d, reg=0x%x, val=0x%x confirm..\n",
+			phydev_phy_addr(phydev), reg, val);
+	} else if (buf[0] == 'r') {
+		if (sscanf(buf, "r %x", &reg) == -1)
+			return -EFAULT;
+
+		air_buckpbus_reg_read(phydev, reg, &val);
+		pr_notice("\nphy=0x%x, reg=0x%x, val=0x%x\n",
+			phydev_phy_addr(phydev), reg, val);
+	} else if (buf[0] == 'h')
+		an8801r_debugfs_pbus_help();
+
+	return count;
+}
+
+int an8801r_info_show(struct seq_file *seq, void *v)
+{
+	struct phy_device *phydev = seq->private;
+	u32 pbus_data = 0;
+	int reg = 0;
+
+	seq_puts(seq, "\t<<AIR AN8801R Info>>\n");
+	air_buckpbus_reg_read(phydev, 0x10005004, &pbus_data);
+	seq_printf(seq, "| Product Version : E%d\n", pbus_data);
+	seq_printf(seq, "| Driver Version  : %s\n", AN8801R_DRIVER_VERSION);
+	air_buckpbus_reg_read(phydev, 0x10000094, &pbus_data);
+	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 an8801r_info_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, an8801r_info_show, inode->i_private);
+}
+
+static const struct file_operations an8801r_info_fops = {
+	.owner = THIS_MODULE,
+	.open = an8801r_info_open,
+	.read = seq_read,
+	.llseek = noop_llseek,
+	.release = single_release,
+};
+
+static const struct file_operations an8801r_counter_fops = {
+	.owner = THIS_MODULE,
+	.open = an8801r_counter_open,
+	.read = seq_read,
+	.llseek = noop_llseek,
+	.release = single_release,
+};
+
+static const struct file_operations an8801r_debugfs_pbus_fops = {
+	.owner = THIS_MODULE,
+	.open = simple_open,
+	.write = an8801r_debugfs_pbus,
+	.llseek = noop_llseek,
+};
+
+static const struct file_operations an8801r_mdio_fops = {
+	.owner = THIS_MODULE,
+	.open = simple_open,
+	.write = an8801r_mdio_write,
+	.llseek = noop_llseek,
+};
+
+int an8801r_debugfs_init(struct phy_device *phydev)
+{
+	int ret = 0;
+	struct an8801r_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,
+					&an8801r_info_fops);
+	debugfs_create_file(DEBUGFS_COUNTER, 0644,
+					priv->debugfs_root, phydev,
+					&an8801r_counter_fops);
+	debugfs_create_file(DEBUGFS_PBUS_OP, S_IFREG | 0200,
+					priv->debugfs_root, phydev,
+					&an8801r_debugfs_pbus_fops);
+	debugfs_create_file(DEBUGFS_MDIO, S_IFREG | 0200,
+					priv->debugfs_root, phydev,
+					&an8801r_mdio_fops);
+	return ret;
+}
+
+static void air_debugfs_remove(struct phy_device *phydev)
+{
+	struct an8801r_priv *priv = phydev->priv;
+
+	if (priv->debugfs_root != NULL) {
+		debugfs_remove_recursive(priv->debugfs_root);
+		priv->debugfs_root = NULL;
+	}
+}
+#endif /*AN8801R_DEBUGFS*/
+
+/*
+ * Acquire RESET GPIO via of_get_named_gpio + gpio_request.
+ * Avoids gpiod_get() -EINVAL seen on MDIO phy devices.
+ * Search PHY node, then MAC parent. Never use "reset-gpios" (phylib).
+ */
+static int an8801r_request_reset_gpio(struct phy_device *phydev)
+{
+	struct device *phy_dev = &phydev->mdio.dev;
+	struct device *mac_dev = phydev->mdio.bus ?
+				 phydev->mdio.bus->parent : NULL;
+	struct device_node *nodes[2];
+	const char *props[] = { "an8801-reset-gpios", "airoha,reset-gpios" };
+	int i, j, gpio, ret;
+
+	nodes[0] = dev_of_node(phy_dev);
+	nodes[1] = mac_dev ? dev_of_node(mac_dev) : NULL;
+
+	for (i = 0; i < ARRAY_SIZE(nodes); i++) {
+		if (!nodes[i])
+			continue;
+		for (j = 0; j < ARRAY_SIZE(props); j++) {
+			if (!of_property_present(nodes[i], props[j]))
+				continue;
+
+			gpio = of_get_named_gpio(nodes[i], props[j], 0);
+			if (gpio < 0) {
+				dev_err(phy_dev,
+					"AN8801R reset: %s on %pOF failed: %d\n",
+					props[j], nodes[i], gpio);
+				continue;
+			}
+
+			ret = gpio_request(gpio, "an8801-reset");
+			if (ret) {
+				dev_err(phy_dev,
+					"AN8801R reset: gpio_request(%d) failed: %d\n",
+					gpio, ret);
+				return ret;
+			}
+
+			dev_info(phy_dev,
+				 "AN8801R reset: requested %s gpio=%d from %pOF\n",
+				 props[j], gpio, nodes[i]);
+			return gpio;
+		}
+	}
+
+	return -ENOENT;
+}
+
+/*
+ * Hardware RESET_N at PHY identify time.
+ * Sequence: physical low -> input + pull-up (release high). Keep held as input.
+ */
+static int an8801r_hw_reset(struct phy_device *phydev, int *held_gpio)
+{
+	struct gpio_desc *desc;
+	int gpio, raw, ret;
+
+	*held_gpio = -1;
+
+	gpio = an8801r_request_reset_gpio(phydev);
+	if (gpio == -ENOENT) {
+		dev_info(phydev_dev(phydev),
+			 "AN8801R reset: no an8801-reset-gpios, skip hw pulse\n");
+		return 0;
+	}
+	if (gpio < 0) {
+		dev_err(phydev_dev(phydev),
+			"AN8801R reset: get gpio failed (%d), continue without pulse\n",
+			gpio);
+		return 0;
+	}
+
+	desc = gpio_to_desc(gpio);
+	dev_info(phydev_dev(phydev),
+		 "AN8801R reset: pulse RESET_N low->high gpio=%d\n", gpio);
+
+	/* Physical low = RESET_N asserted */
+	ret = gpio_direction_output(gpio, 0);
+	raw = gpio_get_value_cansleep(gpio);
+	dev_info(phydev_dev(phydev),
+		 "AN8801R reset: LOW  ret=%d raw=%d (expect 0)\n", ret, raw);
+	msleep(20);
+
+	/* Deassert: input + pull-up (do not drive high on Meson OD-like pad) */
+	ret = gpio_direction_input(gpio);
+	if (desc)
+		gpiod_set_config(desc,
+				 PIN_CONF_PACKED(PIN_CONFIG_BIAS_PULL_UP, 1));
+	msleep(100);
+	raw = gpio_get_value_cansleep(gpio);
+	dev_info(phydev_dev(phydev),
+		 "AN8801R reset: HIGH ret=%d raw=%d (expect 1)\n", ret, raw);
+	if (raw != 1)
+		dev_err(phydev_dev(phydev),
+			"AN8801R reset: RESET_N still low after release — check GPIOZ_15\n");
+
+	*held_gpio = gpio;
+	return 0;
+}
+
+static void an8801r_hw_reset_release(int gpio)
+{
+	struct gpio_desc *desc;
+
+	if (gpio < 0)
+		return;
+
+	/* Leave released (input+pull-up), never re-assert */
+	gpio_direction_input(gpio);
+	desc = gpio_to_desc(gpio);
+	if (desc)
+		gpiod_set_config(desc,
+				 PIN_CONF_PACKED(PIN_CONFIG_BIAS_PULL_UP, 1));
+	gpio_free(gpio);
+}
+
+static int an8801r_phy_probe(struct phy_device *phydev)
+{
+	u32 reg_val, phy_id, led_id;
+	struct device *dev = &phydev->mdio.dev;
+	struct an8801r_priv *priv = NULL;
+	int reset_held = -1;
+	int retry;
+	int ret = 0;
+
+	dev_info(phydev_dev(phydev),
+		 "AN8801R probe: dt/phy_id=0x%08x addr=%d\n",
+		 phydev->phy_id, phydev->mdio.addr);
+
+	/* Sole software owner of RESET_N: pulse low->high before PHY ID read */
+	ret = an8801r_hw_reset(phydev, &reset_held);
+	if (ret)
+		return ret;
+
+	/* Retry: PHY may need extra time after RESET_N deassert */
+	for (retry = 0; retry < 10; retry++) {
+		reg_val = phy_read(phydev, 2);
+		phy_id = reg_val << 16;
+		reg_val = phy_read(phydev, 3);
+		phy_id |= reg_val;
+		dev_info(phydev_dev(phydev), "PHY-ID = %x (try %d)\n",
+			 phy_id, retry + 1);
+		if (phy_id == AN8801R_PHY_ID)
+			break;
+		if (phy_id != 0xffffffff && phy_id != 0)
+			break;
+		msleep(100);
+	}
+
+	if (phy_id != AN8801R_PHY_ID) {
+		dev_err(phydev_dev(phydev),
+			"AN8801R can't be detected (hw_id=0x%08x expect=0x%08x). "
+			"Driver bind fails → eth0 may show Generic PHY. "
+			"Check RESET_N / AN8801R reset logs.\n",
+			phy_id, AN8801R_PHY_ID);
+		an8801r_hw_reset_release(reset_held);
+		return -ENODEV;
+	}
+
+	dev_info(phydev_dev(phydev), "AN8801R detected, bind driver\n");
+
+	priv = devm_kzalloc(dev, sizeof(struct an8801r_priv), GFP_KERNEL);
+	if (!priv) {
+		an8801r_hw_reset_release(reset_held);
+		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;
+	priv->reset_gpio     = reset_held;
+
+	phydev->priv = priv;
+
+#ifdef AN8801R_DEBUGFS
+	ret = an8801r_debugfs_init(phydev);
+	if (ret < 0) {
+		dev_info(phydev_dev(phydev), "AN8801R debugfs init failed\n");
+		air_debugfs_remove(phydev);
+		an8801r_hw_reset_release(priv->reset_gpio);
+		kfree(priv);
+		phydev->priv = NULL;
+		return ret;
+	}
+#endif
+	return 0;
+}
+
+static void an8801r_phy_remove(struct phy_device *phydev)
+{
+	struct an8801r_priv *priv = (struct an8801r_priv *)phydev->priv;
+
+	if (priv) {
+#ifdef AN8801R_DEBUGFS
+		air_debugfs_remove(phydev);
+#endif
+		an8801r_hw_reset_release(priv->reset_gpio);
+		priv->reset_gpio = -1;
+		kfree(priv);
+		phydev->priv = NULL;
+	}
+}
+
+static int an8801r_read_status(struct phy_device *phydev)
+{
+	int ret, prespeed = phydev->speed;
+
+	ret = genphy_read_status(phydev);
+	if (phydev->mdix_ctrl == ETH_TP_MDI_AUTO)
+		phydev->mdix = ETH_TP_MDI_INVALID;
+	else
+		phydev->mdix = phydev->mdix_ctrl;
+
+	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) {
+			air_buckpbus_reg_modify(phydev, 0x10005054, BIT(0), BIT(0));
+		} else {
+			air_buckpbus_reg_modify(phydev, 0x10005054, BIT(0), 0);
+		}
+	}
+	return ret;
+}
+
+static struct phy_driver airoha_driver[] = {
+	{
+		.phy_id         = AN8801R_PHY_ID,
+		.name           = "Airoha AN8801R",
+		.phy_id_mask    = 0x0ffffff0,
+		.features       = PHY_GBIT_FEATURES,
+		.config_init    = an8801r_config_init,
+		.config_aneg    = an8801r_config_aneg,
+		.probe          = an8801r_phy_probe,
+		.remove         = an8801r_phy_remove,
+		.read_status    = an8801r_read_status,
+		.config_intr    = an8801r_config_intr,
+#if (KERNEL_VERSION(5, 11, 0) < LINUX_VERSION_CODE)
+		.handle_interrupt = an8801r_handle_interrupt,
+#else
+		.did_interrupt  = an8801r_did_interrupt,
+		.ack_interrupt  = an8801r_ack_interrupt,
+#endif
+		.set_wol        = an8801r_set_wol,
+		.get_wol        = an8801r_get_wol,
+		.suspend        = genphy_suspend,
+		.resume         = genphy_resume,
+	}
+};
+
+module_phy_driver(airoha_driver);
+
+static struct mdio_device_id __maybe_unused airoha_tbl[] = {
+	{ AN8801R_PHY_ID, 0x0ffffff0 },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(mdio, airoha_tbl);

增加common/drivers/net/phy/an8801.h

--- /dev/null
+++ b/common/drivers/net/phy/an8801.h
@@ -0,0 +1,202 @@
+// SPDX-License-Identifier: GPL-2.0
+/* FILE NAME:  an8801.h
+ * PURPOSE:
+ *      Define Airoha phy driver function
+ *
+ * NOTES:
+ *
+ */
+
+#ifndef __AN8801_H
+#define __AN8801_H
+
+/* NAMING DECLARATIONS
+ */
+#define AN8801R_DRIVER_VERSION  "1.0.20"
+
+#define DEBUGFS_COUNTER         "counter"
+#define DEBUGFS_INFO            "driver_info"
+#define DEBUGFS_PBUS_OP         "pbus_op"
+#define DEBUGFS_MDIO            "mdio"
+
+#define AN8801R_MDIO_PHY_ID     0x1
+#define AN8801R_PHY_ID1         0xc0ff
+#define AN8801R_PHY_ID2         0x0421
+#define AN8801R_PHY_ID     ((u32)((AN8801R_PHY_ID1 << 16) | AN8801R_PHY_ID2))
+
+#define AN8801R_MDIX_CTRL_REG       0x0145
+#define AN8801R_MDIX_CTRL_MASK      (BIT(4) | BIT(3))
+#define AN8801R_MDIX_CTRL_AUTO      0
+#define AN8801R_MDIX_CTRL_MDI       BIT(4)
+#define AN8801R_MDIX_CTRL_MDIX      (BIT(4) | BIT(3))
+
+#define TRUE                    1
+#define FALSE                   0
+#define LINK_UP                 1
+#define LINK_DOWN               0
+
+#define MAX_LED_SIZE            3
+
+#define MAX_RETRY               5
+
+#define LED_ENABLE                  1
+#define LED_DISABLE                 0
+
+#define AN8801R_DEBUGFS
+
+#ifndef BIT
+#define BIT(nr)                     (1 << (nr))
+#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
+
+#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 AIR_CKO_OUT_DRV				0xF /* Available: 0x0~0xF (about 1.8V~2.5V) */
+
+/*
+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))
+
+/* DATA TYPE DECLARATIONS
+ */
+enum AIR_LED_GPIO_PIN_T {
+	AIR_LED_GPIO1 = 1,
+	AIR_LED_GPIO2,
+	AIR_LED_GPIO3
+};
+
+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,
+};
+
+enum AIR_CKO_OUTPUT_RATE_T {
+	AIR_CKO_OUTPUT_RATE_25M = 0,
+	AIR_CKO_OUTPUT_RATE_125M = 1,
+};
+
+struct AIR_LED_CFG_T {
+	u16 en;
+	u16 gpio;
+	u16 pol;
+	u16 on_cfg;
+	u16 blk_cfg;
+};
+
+struct an8801r_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;
+	u8                    cko_output_en;
+	u8                    cko_output_rate;
+	/* Held as input+pull-up after probe pulse; -1 if unused */
+	int                   reset_gpio;
+#ifdef AN8801R_DEBUGFS
+	struct dentry        *debugfs_root;
+#endif
+};
+
+#endif /* End of __AN8801_H */

修改common_drivers/arch/arm/configs/meson64_a32_smarthome_defconfig

--- a/common_drivers/arch/arm/configs/meson64_a32_smarthome_defconfig
+++ b/common_drivers/arch/arm/configs/meson64_a32_smarthome_defconfig
@@ -220,6 +220,7 @@ CONFIG_TUN=y
 # CONFIG_NET_VENDOR_WIZNET is not set
 # CONFIG_NET_VENDOR_XILINX is not set
 CONFIG_REALTEK_PHY=m
+CONFIG_AIROHA_AN8801_PHY=y
 CONFIG_PPP=y
 CONFIG_PPP_BSDCOMP=y
 CONFIG_PPP_DEFLATE=y

修改common_drivers/arch/arm64/configs/meson64_a64_smarthome_defconfig

--- a/common_drivers/arch/arm64/configs/meson64_a64_smarthome_defconfig
+++ b/common_drivers/arch/arm64/configs/meson64_a64_smarthome_defconfig
@@ -242,6 +242,7 @@ CONFIG_TUN=y
 # CONFIG_NET_VENDOR_WIZNET is not set
 # CONFIG_NET_VENDOR_XILINX is not set
 CONFIG_REALTEK_PHY=m
+CONFIG_AIROHA_AN8801_PHY=y
 CONFIG_PPP=y
 CONFIG_PPP_BSDCOMP=y
 CONFIG_PPP_DEFLATE=y

内容概要:本文提出了一种计及并网波动约束和储能荷电状态(SOC)的混合储能功率协调控制方法,并提供了完整的Matlab代码实现。该方法针对新能源并网系统中存在的功率波动问题,充分发挥超级电容器动态响应快与电池能量密度高的互补优势,通过设计合理的协调控制策略实现两者的功率动态分配。控制算法综合考虑了电网对并网功率波动的安全限值要求以及储能单元荷电状态的实时变化,构建了以平抑功率波动、均衡储能SOC、延长系统寿命为核心的多目标优化机制。文中详细阐述了混合储能系统的数学建模过程、功率分配逻辑设计、控制策略实现流程及仿真验证方案,通过对比实验验证了所提方法在降低并网功率波动幅度、维持储能系统能量平衡、提升电能质量和系统运行稳定性方面的优越性能。; 适合人群:具备一定电力系统分析、新能源并网技术或储能控制系统基础知识的研究生、科研人员及从事相关领域工程开发的技术人员。; 使用场景及目标:①应用于高比例可再生能源接入的微电网或配电网中,实现并网功率的平滑控制;②用于电池-超级电容等混合储能系统的能量管理与优化控制研究;③作为Matlab仿真教学案例或科研复现资料,帮助深入理解储能协调控制策略的设计原理与实现细节。; 阅读建议:读者应结合所提供的Matlab代码进行仿真实践,重点剖析低通滤波与高频补偿相结合的功率分配机制及SOC反馈调节环节的实现逻辑,建议在掌握基本控制理论的基础上,调整参数设置或拓展系统模型以适应不同的应用场景与研究需求。
内容概要:本文研究了构网型GFM-VSG与跟网型GFL-PQ逆变器混合并联并网的仿真系统,重点探讨了在Simulink环境下构建该系统的模型与控制策略。文中深入分析了构网型(Grid-Forming, GFM)采用虚拟同步发电机(VSG)控制和跟网型(Grid-Following, GFL)采用PQ控制的两类逆变器在并网运行中的协同机制与动态交互特性。通过建立高精度的仿真模型,系统研究了其在稳态运行、电网电压不平衡、频率波动及负载突变等动态工况下的响应性能。研究聚焦于提升混合系统在复杂电网环境中的稳定性、电能质量与功率精确分配能力,深入剖析了GFM与GFL逆变器之间的电压、频率支撑与功率振荡抑制等关键问题,旨在为高比例新能源接入背景下多类型变流器并网系统的稳定运行与优化设计提供坚实的理论依据和技术支持。; 适合人群:具备电力电子、自动控制或电气工程相关背景,熟悉Simulink/Matlab仿真工具,从事新能源并网、微电网控制、逆变器控制策略研究的研发人员及研究生。; 使用场景及目标:① 研究GFM与GFL逆变器在混合并联系统中的协同控制逻辑与稳定性机理;② 分析不同控制策略下系统在弱电网、不平衡电网等非理想条件下的动态响应、功率振荡及频率支撑能力;③ 为实际工程中多类型逆变器并网系统的建模、仿真验证与控制策略优化提供参考方案。; 阅读建议:建议结合Simulink仿真模型同步阅读,重点关注控制架构设计、系统交互影响与仿真结果分析部分,深入理解GFM-VSG与GFL-PQ的接口逻辑、动态耦合机制,并可通过修改控制参数复现不同工况以加深对系统稳定边界的理解。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wellnw

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值