All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 1/1] net: phy: Add the Airoha EN8811H PHY driver
@ 2025-04-06 13:02 Lucien.Jheng
  2025-04-06 13:30 ` Marek Vasut
  2025-04-17 17:06 ` Tom Rini
  0 siblings, 2 replies; 6+ messages in thread
From: Lucien.Jheng @ 2025-04-06 13:02 UTC (permalink / raw)
  To: ilias.apalodimas, jerome.forissier, marek.vasut+renesas,
	paul.barker.ct, pbrobinson, lucienzx159, u-boot
  Cc: joseph.lin, wenshin.chung, lucien.jheng, frank-w, daniel,
	ericwouds

Add the driver for the Airoha EN8811H 2.5 Gigabit PHY. The PHY supports
100/1000/2500 Mbps with auto negotiation only.

The driver uses two firmware files, for which updated versions are added to
linux-firmware already.

Based on the Linux upstream 8811 driver code(air_en8811h.c),
I have modified the relevant process to align with the U-Boot boot sequence.
and have validated this on Banana Pi BPI-R3 Mini.

The MD32 FW is currently stored in eMMC partition 1 on Banana Pi BPI-R3 Mini,
and it is loaded from there.

Signed-off-by: Lucien.Jheng <lucienzx159@gmail.com>
---
 drivers/net/phy/Kconfig       |  34 ++
 drivers/net/phy/Makefile      |   1 +
 drivers/net/phy/air_en8811h.c | 783 ++++++++++++++++++++++++++++++++++
 3 files changed, 818 insertions(+)
 create mode 100644 drivers/net/phy/air_en8811h.c

diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
index 3132718e4f8..5b4cf30b0a3 100644
--- a/drivers/net/phy/Kconfig
+++ b/drivers/net/phy/Kconfig
@@ -79,6 +79,40 @@ config PHY_ADIN
 	help
 		Add support for configuring RGMII on Analog Devices ADIN PHYs.
 
+menuconfig PHY_AIROHA
+	bool "Airoha Ethernet PHYs support"
+
+config PHY_AIROHA_EN8811H
+	bool "Airoha Ethernet EN8811H support"
+	depends on PHY_AIROHA
+	help
+		AIROHA EN8811H supported.
+
+choice
+	prompt "Location of the Airoha PHY firmware"
+	default PHY_AIROHA_FW_IN_MMC
+	depends on PHY_AIROHA_EN8811H
+
+config PHY_AIROHA_FW_IN_MMC
+	bool "Airoha firmware in MMC boot1 partition"
+
+endchoice
+
+config AIROHA_FW_ADDR
+	hex "Airoha Firmware Address"
+	depends on PHY_AIROHA_EN8811H
+	default 0x0
+
+config AIROHA_MD32_DM_SIZE
+	hex "Airoha Firmware MD32 DM Size"
+	depends on PHY_AIROHA_EN8811H
+	default 0x4000
+
+config AIROHA_MD32_DSP_SIZE
+	hex "Airoha Firmware MD32 DSP Size"
+	depends on PHY_AIROHA_EN8811H
+	default 0x20000
+
 menuconfig PHY_AQUANTIA
 	bool "Aquantia Ethernet PHYs support"
 	select PHY_GIGE
diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile
index 2487f366e1c..87dee3c15b9 100644
--- a/drivers/net/phy/Makefile
+++ b/drivers/net/phy/Makefile
@@ -11,6 +11,7 @@ obj-$(CONFIG_MV88E6352_SWITCH) += mv88e6352.o
 obj-$(CONFIG_PHYLIB) += phy.o
 obj-$(CONFIG_PHYLIB_10G) += generic_10g.o
 obj-$(CONFIG_PHY_ADIN) += adin.o
+obj-$(CONFIG_PHY_AIROHA_EN8811H) += air_en8811h.o
 obj-$(CONFIG_PHY_AQUANTIA) += aquantia.o
 obj-$(CONFIG_PHY_ATHEROS) += atheros.o
 obj-$(CONFIG_PHY_BROADCOM) += broadcom.o
diff --git a/drivers/net/phy/air_en8811h.c b/drivers/net/phy/air_en8811h.c
new file mode 100644
index 00000000000..96bb24418a0
--- /dev/null
+++ b/drivers/net/phy/air_en8811h.c
@@ -0,0 +1,783 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Driver for the Airoha EN8811H 2.5 Gigabit PHY.
+ *
+ * Limitations of the EN8811H:
+ * - Only full duplex supported
+ * - Forced speed (AN off) is not supported by hardware (100Mbps)
+ *
+ * Source originated from linux air_en8811h.c
+ *
+ * Copyright (C) 2025 Airoha Technology Corp.
+ */
+#include <phy.h>
+#include <errno.h>
+#include <malloc.h>
+#include <asm/unaligned.h>
+#include <linux/iopoll.h>
+#include <dm/device_compat.h>
+#include <linux/bitops.h>
+#include <mmc.h>
+
+#define EN8811H_PHY_ID		0x03a2a411
+
+#define AIR_FW_ADDR_DM	0x00000000
+#define AIR_FW_ADDR_DSP	0x00100000
+
+#define EN8811H_MD32_DM_SIZE	0x4000
+#define EN8811H_MD32_DSP_SIZE	0x20000
+
+ #define	EN8811H_FW_CTRL_1		0x0f0018
+ #define	EN8811H_FW_CTRL_1_START		0x0
+ #define	EN8811H_FW_CTRL_1_FINISH	0x1
+ #define	EN8811H_FW_CTRL_2		0x800000
+ #define	EN8811H_FW_CTRL_2_LOADING	BIT(11)
+
+ /* MII Registers */
+ #define AIR_AUX_CTRL_STATUS		0x1d
+ #define   AIR_AUX_CTRL_STATUS_SPEED_MASK	GENMASK(4, 2)
+ #define   AIR_AUX_CTRL_STATUS_SPEED_100		0x4
+ #define   AIR_AUX_CTRL_STATUS_SPEED_1000	0x8
+ #define   AIR_AUX_CTRL_STATUS_SPEED_2500	0xc
+
+#define AIR_EXT_PAGE_ACCESS		0x1f
+#define   AIR_PHY_PAGE_STANDARD			0x0000
+#define   AIR_PHY_PAGE_EXTENDED_4		0x0004
+
+/* MII Registers Page 4*/
+#define AIR_BPBUS_MODE			0x10
+#define   AIR_BPBUS_MODE_ADDR_FIXED		0x0000
+#define   AIR_BPBUS_MODE_ADDR_INCR		BIT(15)
+#define AIR_BPBUS_WR_ADDR_HIGH		0x11
+#define AIR_BPBUS_WR_ADDR_LOW		0x12
+#define AIR_BPBUS_WR_DATA_HIGH		0x13
+#define AIR_BPBUS_WR_DATA_LOW		0x14
+#define AIR_BPBUS_RD_ADDR_HIGH		0x15
+#define AIR_BPBUS_RD_ADDR_LOW		0x16
+#define AIR_BPBUS_RD_DATA_HIGH		0x17
+#define AIR_BPBUS_RD_DATA_LOW		0x18
+
+/* Registers on MDIO_MMD_VEND1 */
+#define EN8811H_PHY_FW_STATUS		0x8009
+#define   EN8811H_PHY_READY			0x02
+
+/* Registers on MDIO_MMD_VEND2 */
+#define AIR_PHY_LED_BCR			0x021
+#define   AIR_PHY_LED_BCR_MODE_MASK		GENMASK(1, 0)
+#define   AIR_PHY_LED_BCR_TIME_TEST		BIT(2)
+#define   AIR_PHY_LED_BCR_CLK_EN		BIT(3)
+#define   AIR_PHY_LED_BCR_EXT_CTRL		BIT(15)
+
+#define AIR_PHY_LED_DUR_ON		0x022
+
+#define AIR_PHY_LED_DUR_BLINK		0x023
+
+#define AIR_PHY_LED_ON(i)	       (0x024 + ((i) * 2))
+#define   AIR_PHY_LED_ON_MASK			(GENMASK(6, 0) | BIT(8))
+#define   AIR_PHY_LED_ON_LINK1000		BIT(0)
+#define   AIR_PHY_LED_ON_LINK100		BIT(1)
+#define   AIR_PHY_LED_ON_LINK10			BIT(2)
+#define   AIR_PHY_LED_ON_LINKDOWN		BIT(3)
+#define   AIR_PHY_LED_ON_FDX			BIT(4) /* Full duplex */
+#define   AIR_PHY_LED_ON_HDX			BIT(5) /* Half duplex */
+#define   AIR_PHY_LED_ON_FORCE_ON		BIT(6)
+#define   AIR_PHY_LED_ON_LINK2500		BIT(8)
+#define   AIR_PHY_LED_ON_POLARITY		BIT(14)
+#define   AIR_PHY_LED_ON_ENABLE			BIT(15)
+
+#define AIR_PHY_LED_BLINK(i)	       (0x025 + ((i) * 2))
+#define   AIR_PHY_LED_BLINK_1000TX		BIT(0)
+#define   AIR_PHY_LED_BLINK_1000RX		BIT(1)
+#define   AIR_PHY_LED_BLINK_100TX		BIT(2)
+#define   AIR_PHY_LED_BLINK_100RX		BIT(3)
+#define   AIR_PHY_LED_BLINK_10TX		BIT(4)
+#define   AIR_PHY_LED_BLINK_10RX		BIT(5)
+#define   AIR_PHY_LED_BLINK_COLLISION		BIT(6)
+#define   AIR_PHY_LED_BLINK_RX_CRC_ERR		BIT(7)
+#define   AIR_PHY_LED_BLINK_RX_IDLE_ERR		BIT(8)
+#define   AIR_PHY_LED_BLINK_FORCE_BLINK		BIT(9)
+#define   AIR_PHY_LED_BLINK_2500TX		BIT(10)
+#define   AIR_PHY_LED_BLINK_2500RX		BIT(11)
+
+#define EN8811H_FW_VERSION		0x3b3c
+
+#define EN8811H_POLARITY		0xca0f8
+#define   EN8811H_POLARITY_TX_NORMAL		BIT(0)
+#define   EN8811H_POLARITY_RX_REVERSE		BIT(1)
+
+#define EN8811H_CLK_CGM		0xcf958
+#define   EN8811H_CLK_CGM_CKO		BIT(26)
+#define EN8811H_HWTRAP1		0xcf914
+#define   EN8811H_HWTRAP1_CKO		BIT(12)
+
+#define air_upper_16_bits(n) ((u16)((n) >> 16))
+#define air_lower_16_bits(n) ((u16)((n) & 0xffff))
+
+/* Led definitions */
+#define EN8811H_LED_COUNT	3
+
+/* Default LED setup:
+ * GPIO5 <-> LED0  On: Link detected
+ * GPIO4 <-> LED1  On: Link detected at 2500 and 1000 Mbps
+ * GPIO3 <-> LED2  On: Link detected at 2500 and  100 Mbps
+ */
+#define AIR_DEFAULT_TRIGGER_LED0 (AIR_PHY_LED_ON_LINK2500 | \
+				  AIR_PHY_LED_ON_LINK1000 | \
+				  AIR_PHY_LED_ON_LINK100)
+#define AIR_DEFAULT_TRIGGER_LED1 (AIR_PHY_LED_ON_LINK2500 | \
+				  AIR_PHY_LED_ON_LINK1000 | \
+				  AIR_PHY_LED_BLINK_2500TX | \
+				  AIR_PHY_LED_BLINK_2500RX | \
+				  AIR_PHY_LED_BLINK_1000TX | \
+				  AIR_PHY_LED_BLINK_1000RX)
+#define AIR_DEFAULT_TRIGGER_LED2 (AIR_PHY_LED_ON_LINK2500 | \
+				  AIR_PHY_LED_ON_LINK100 | \
+				  AIR_PHY_LED_BLINK_2500TX | \
+				  AIR_PHY_LED_BLINK_2500RX | \
+				  AIR_PHY_LED_BLINK_100TX | \
+				  AIR_PHY_LED_BLINK_100RX)
+
+struct led {
+	unsigned long rules;
+};
+
+enum {
+	AIR_PHY_LED_DUR_BLINK_32MS,
+	AIR_PHY_LED_DUR_BLINK_64MS,
+	AIR_PHY_LED_DUR_BLINK_128MS,
+	AIR_PHY_LED_DUR_BLINK_256MS,
+	AIR_PHY_LED_DUR_BLINK_512MS,
+	AIR_PHY_LED_DUR_BLINK_1024MS,
+};
+
+enum {
+	AIR_LED_DISABLE,
+	AIR_LED_ENABLE,
+};
+
+enum {
+	AIR_ACTIVE_LOW,
+	AIR_ACTIVE_HIGH,
+};
+
+enum {
+	AIR_LED_MODE_DISABLE,
+	AIR_LED_MODE_USER_DEFINE,
+};
+
+#define AIR_PHY_LED_DUR_UNIT	781
+#define AIR_PHY_LED_DUR (AIR_PHY_LED_DUR_UNIT << AIR_PHY_LED_DUR_BLINK_64MS)
+
+struct en8811h_priv {
+	int firmware_version;
+	bool		mcu_needs_restart;
+	struct led	led[EN8811H_LED_COUNT];
+};
+
+static int air_phy_read_page(struct phy_device *phydev)
+{
+	return phy_read(phydev, MDIO_DEVAD_NONE, AIR_EXT_PAGE_ACCESS);
+}
+
+static int air_phy_write_page(struct phy_device *phydev, int page)
+{
+	return phy_write(phydev, MDIO_DEVAD_NONE, AIR_EXT_PAGE_ACCESS, page);
+}
+
+int air_phy_select_page(struct phy_device *phydev, int page)
+{
+	int ret, oldpage;
+
+	oldpage = air_phy_read_page(phydev);
+	if (oldpage < 0)
+		return oldpage;
+
+	if (oldpage != page) {
+		ret = air_phy_write_page(phydev, page);
+		if (ret < 0)
+			return ret;
+	}
+
+	return oldpage;
+}
+
+int air_phy_restore_page(struct phy_device *phydev, int oldpage, int ret)
+{
+	int r;
+
+	if (oldpage >= 0) {
+		r = air_phy_write_page(phydev, oldpage);
+
+		if (ret >= 0 && r < 0)
+			ret = r;
+	} else {
+		ret = oldpage;
+	}
+
+	return ret;
+}
+
+static int air_buckpbus_reg_write(struct phy_device *phydev,
+				  u32 pbus_address, u32 pbus_data)
+{
+	int ret, saved_page;
+
+	saved_page = air_phy_select_page(phydev, AIR_PHY_PAGE_EXTENDED_4);
+
+	if (saved_page >= 0) {
+		ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_MODE, AIR_BPBUS_MODE_ADDR_FIXED);
+		if (ret < 0)
+			goto restore_page;
+
+		ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_ADDR_HIGH,
+				air_upper_16_bits(pbus_address));
+		if (ret < 0)
+			goto restore_page;
+
+		ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_ADDR_LOW,
+				air_lower_16_bits(pbus_address));
+		if (ret < 0)
+			goto restore_page;
+
+		ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_DATA_HIGH,
+				air_upper_16_bits(pbus_data));
+		if (ret < 0)
+			goto restore_page;
+
+		ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_DATA_LOW,
+				air_lower_16_bits(pbus_data));
+		if (ret < 0)
+			goto restore_page;
+	}
+
+restore_page:
+	if (ret < 0)
+		printf("%s 0x%08x failed: %d\n", __func__,
+		       pbus_address, ret);
+
+	return air_phy_restore_page(phydev, saved_page, ret);
+}
+
+static int air_buckpbus_reg_read(struct phy_device *phydev,
+				 u32 pbus_address, u32 *pbus_data)
+{
+	int pbus_data_low, pbus_data_high;
+	int ret = 0, saved_page;
+
+	saved_page = air_phy_select_page(phydev, AIR_PHY_PAGE_EXTENDED_4);
+
+	if (saved_page >= 0) {
+		ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_MODE, AIR_BPBUS_MODE_ADDR_FIXED);
+		if (ret < 0)
+			goto restore_page;
+
+		ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_RD_ADDR_HIGH,
+				air_upper_16_bits(pbus_address));
+		if (ret < 0)
+			goto restore_page;
+
+		ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_RD_ADDR_LOW,
+				air_lower_16_bits(pbus_address));
+		if (ret < 0)
+			goto restore_page;
+
+		pbus_data_high = phy_read(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_RD_DATA_HIGH);
+		if (pbus_data_high < 0) {
+			ret = pbus_data_high;
+			goto restore_page;
+		}
+
+		pbus_data_low = phy_read(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_RD_DATA_LOW);
+		if (pbus_data_low < 0) {
+			ret = pbus_data_low;
+			goto restore_page;
+		}
+
+		*pbus_data = pbus_data_low | (pbus_data_high << 16);
+	}
+
+restore_page:
+	if (ret < 0)
+		printf("%s 0x%08x failed: %d\n", __func__,
+		       pbus_address, ret);
+
+	return air_phy_restore_page(phydev, saved_page, ret);
+}
+
+static int air_buckpbus_reg_modify(struct phy_device *phydev,
+				   u32 pbus_address, u32 mask, u32 set)
+{
+	int pbus_data_low, pbus_data_high;
+	u32 pbus_data_old, pbus_data_new;
+	int ret = 0, saved_page;
+
+	saved_page = air_phy_select_page(phydev, AIR_PHY_PAGE_EXTENDED_4);
+
+	if (saved_page >= 0) {
+		ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_MODE, AIR_BPBUS_MODE_ADDR_FIXED);
+		if (ret < 0)
+			goto restore_page;
+
+		ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_RD_ADDR_HIGH,
+				air_upper_16_bits(pbus_address));
+		if (ret < 0)
+			goto restore_page;
+
+		ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_RD_ADDR_LOW,
+				air_lower_16_bits(pbus_address));
+		if (ret < 0)
+			goto restore_page;
+
+		pbus_data_high = phy_read(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_RD_DATA_HIGH);
+		if (pbus_data_high < 0)
+			return pbus_data_high;
+
+		pbus_data_low = phy_read(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_RD_DATA_LOW);
+		if (pbus_data_low < 0)
+			return pbus_data_low;
+
+		pbus_data_old = pbus_data_low | (pbus_data_high << 16);
+		pbus_data_new = (pbus_data_old & ~mask) | set;
+		if (pbus_data_new == pbus_data_old)
+			return 0;
+
+		ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_ADDR_HIGH,
+				air_upper_16_bits(pbus_address));
+		if (ret < 0)
+			goto restore_page;
+
+		ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_ADDR_LOW,
+				air_lower_16_bits(pbus_address));
+		if (ret < 0)
+			goto restore_page;
+
+		ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_DATA_HIGH,
+				air_upper_16_bits(pbus_data_new));
+		if (ret < 0)
+			goto restore_page;
+
+		ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_DATA_LOW,
+				air_lower_16_bits(pbus_data_new));
+		if (ret < 0)
+			goto restore_page;
+	}
+
+restore_page:
+	if (ret < 0)
+		printf("%s 0x%08x failed: %d\n", __func__,
+		       pbus_address, ret);
+
+	return air_phy_restore_page(phydev, saved_page, ret);
+}
+
+static int air_write_buf(struct phy_device *phydev, unsigned long address,
+			 unsigned long array_size, const unsigned char *buffer)
+{
+	unsigned int offset;
+	int ret, saved_page;
+	u16 val;
+
+	saved_page = air_phy_select_page(phydev, AIR_PHY_PAGE_EXTENDED_4);
+
+	if (saved_page >= 0) {
+		ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_MODE, AIR_BPBUS_MODE_ADDR_INCR);
+		if (ret < 0)
+			goto restore_page;
+
+		ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_ADDR_HIGH,
+				air_upper_16_bits(address));
+		if (ret < 0)
+			goto restore_page;
+
+		ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_ADDR_LOW,
+				air_lower_16_bits(address));
+		if (ret < 0)
+			goto restore_page;
+
+		for (offset = 0; offset < array_size; offset += 4) {
+			val = get_unaligned_le16(&buffer[offset + 2]);
+			ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_DATA_HIGH, val);
+			if (ret < 0)
+				goto restore_page;
+
+			val = get_unaligned_le16(&buffer[offset]);
+			ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_DATA_LOW, val);
+			if (ret < 0)
+				goto restore_page;
+		}
+	}
+
+restore_page:
+	if (ret < 0)
+		printf("%s 0x%08lx failed: %d\n", __func__,
+		       address, ret);
+
+	return air_phy_restore_page(phydev, saved_page, ret);
+}
+
+__weak ulong *en8811h_get_fw_addr(void)
+{
+	return (ulong *)CONFIG_AIROHA_FW_ADDR;
+}
+
+static int en8811h_wait_mcu_ready(struct phy_device *phydev)
+{
+	int ret, reg_value;
+
+	/* Because of mdio-lock, may have to wait for multiple loads */
+	ret = phy_read_mmd_poll_timeout(phydev, MDIO_MMD_VEND1,
+					EN8811H_PHY_FW_STATUS, reg_value,
+					reg_value == EN8811H_PHY_READY,
+					20000, 7500000, true);
+	if (ret) {
+		printf("MCU not ready: 0x%x\n", reg_value);
+		return -ENODEV;
+	}
+
+	return 0;
+}
+
+static int en8811h_load_firmware(struct phy_device *phydev)
+{
+	int ret;
+	char *addr = NULL;
+	struct en8811h_priv *priv = phydev->priv;
+	int dev = CONFIG_SYS_MMC_ENV_DEV;
+	u32 cnt = (CONFIG_AIROHA_MD32_DM_SIZE +
+		   CONFIG_AIROHA_MD32_DSP_SIZE) / 512;
+	ulong airoha_fw_addr = (ulong)en8811h_get_fw_addr();
+	u32 blk = airoha_fw_addr / 512;
+
+	addr = malloc(CONFIG_AIROHA_MD32_DM_SIZE + CONFIG_AIROHA_MD32_DSP_SIZE);
+	if (!addr) {
+		puts("cannot allocated buffer for firmware.\n");
+		return -ENOMEM;
+	}
+
+	if (IS_ENABLED(CONFIG_PHY_AIROHA_FW_IN_MMC)) {
+		struct mmc *mmc = find_mmc_device(dev);
+
+		if (!mmc) {
+			puts("Failed to find MMC device for Airoha ucode\n");
+			goto en8811h_load_firmware_out;
+		}
+
+		printf("MMC read: dev # %u, block # %u, count %u ...\n",
+		       dev, blk, cnt);
+
+		if (mmc_init(mmc)) {
+			puts("initializing MMC device failed.\n");
+			goto en8811h_load_firmware_out;
+		}
+
+		ret = mmc_set_part_conf(mmc, 1, 2, 2);
+		if (ret) {
+			puts("cannot access eMMC boot1 hw partition.\n");
+			goto en8811h_load_firmware_out;
+		}
+
+		(void)blk_dread(mmc_get_blk_desc(mmc), blk, cnt, addr);
+
+		mmc_set_part_conf(mmc, 1, 1, 0);
+
+	} else {
+		puts("EN8811H firmware loading not implemented");
+		free(addr);
+		addr = NULL;
+		return -EOPNOTSUPP;
+	}
+
+	ret = air_buckpbus_reg_write(phydev, EN8811H_FW_CTRL_1,
+				     EN8811H_FW_CTRL_1_START);
+	if (ret < 0)
+		return ret;
+
+	ret = air_buckpbus_reg_modify(phydev, EN8811H_FW_CTRL_2,
+				      EN8811H_FW_CTRL_2_LOADING,
+				      EN8811H_FW_CTRL_2_LOADING);
+	if (ret < 0)
+		return ret;
+
+	ret = air_write_buf(phydev, AIR_FW_ADDR_DM, CONFIG_AIROHA_MD32_DM_SIZE, addr);
+	if (ret < 0)
+		goto en8811h_load_firmware_out;
+
+	ret = air_write_buf(phydev, AIR_FW_ADDR_DSP, CONFIG_AIROHA_MD32_DSP_SIZE,
+			    addr + CONFIG_AIROHA_MD32_DM_SIZE);
+	if (ret < 0)
+		goto en8811h_load_firmware_out;
+
+	ret = air_buckpbus_reg_modify(phydev, EN8811H_FW_CTRL_2,
+				      EN8811H_FW_CTRL_2_LOADING, 0);
+	if (ret < 0)
+		return ret;
+
+	ret = air_buckpbus_reg_write(phydev, EN8811H_FW_CTRL_1,
+				     EN8811H_FW_CTRL_1_FINISH);
+	if (ret < 0)
+		return ret;
+
+	ret = en8811h_wait_mcu_ready(phydev);
+
+	air_buckpbus_reg_read(phydev, EN8811H_FW_VERSION,
+			      &priv->firmware_version);
+	printf("MD32 firmware version: %08x\n",
+	       priv->firmware_version);
+
+en8811h_load_firmware_out:
+	free(addr);
+	if (ret < 0)
+		printf("Firmware loading failed: %d\n", ret);
+
+	return ret;
+}
+
+static int en8811h_restart_mcu(struct phy_device *phydev)
+{
+	int ret;
+
+	ret = phy_write_mmd(phydev, 0x1e, 0x8009, 0x0);
+	if (ret < 0)
+		return ret;
+
+	ret = air_buckpbus_reg_write(phydev, EN8811H_FW_CTRL_1,
+				     EN8811H_FW_CTRL_1_START);
+	if (ret < 0)
+		return ret;
+
+	return air_buckpbus_reg_write(phydev, EN8811H_FW_CTRL_1,
+				      EN8811H_FW_CTRL_1_FINISH);
+}
+
+static int air_led_hw_control_set(struct phy_device *phydev,
+				  u8 index, unsigned long rules)
+{
+	struct en8811h_priv *priv = phydev->priv;
+	u16 on = 0, blink = 0;
+	int ret;
+
+	if (index >= EN8811H_LED_COUNT)
+		return -EINVAL;
+
+	on |= rules & (AIR_PHY_LED_ON_LINK100 |
+		AIR_PHY_LED_ON_LINK1000 |
+		AIR_PHY_LED_ON_LINK2500);
+
+	blink |= rules & (AIR_PHY_LED_BLINK_100TX |
+		AIR_PHY_LED_BLINK_100RX |
+		AIR_PHY_LED_BLINK_1000TX |
+		AIR_PHY_LED_BLINK_1000RX |
+		AIR_PHY_LED_BLINK_2500TX |
+		AIR_PHY_LED_BLINK_2500RX);
+
+	if (blink || on) {
+		on &= ~AIR_PHY_LED_ON_FORCE_ON;
+		blink &= ~AIR_PHY_LED_BLINK_FORCE_BLINK;
+	} else {
+		priv->led[index].rules = 0;
+	}
+
+	ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, AIR_PHY_LED_ON(index),
+			     AIR_PHY_LED_ON_MASK, on);
+	if (ret < 0)
+		return ret;
+
+	return phy_write_mmd(phydev, MDIO_MMD_VEND2, AIR_PHY_LED_BLINK(index),
+			blink);
+}
+
+static int air_led_init(struct phy_device *phydev, u8 index, u8 state, u8 pol)
+{
+	int val = 0;
+	int err;
+
+	if (index >= EN8811H_LED_COUNT)
+		return -EINVAL;
+
+	if (state == AIR_LED_ENABLE)
+		val |= AIR_PHY_LED_ON_ENABLE;
+	else
+		val &= ~AIR_PHY_LED_ON_ENABLE;
+
+	if (pol == AIR_ACTIVE_HIGH)
+		val |= AIR_PHY_LED_ON_POLARITY;
+	else
+		val &= ~AIR_PHY_LED_ON_POLARITY;
+
+	err = phy_write_mmd(phydev, 0x1f, AIR_PHY_LED_ON(index), val);
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+
+static int air_leds_init(struct phy_device *phydev, int num, int dur, int mode)
+{
+	int ret, i;
+	struct en8811h_priv *priv = phydev->priv;
+
+	ret = phy_write_mmd(phydev, 0x1f, AIR_PHY_LED_DUR_BLINK, dur);
+	if (ret < 0)
+		return ret;
+
+	ret = phy_write_mmd(phydev, 0x1f, AIR_PHY_LED_DUR_ON, dur >> 1);
+	if (ret < 0)
+		return ret;
+
+	switch (mode) {
+	case AIR_LED_MODE_DISABLE:
+		ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, AIR_PHY_LED_BCR,
+				     AIR_PHY_LED_BCR_EXT_CTRL |
+				     AIR_PHY_LED_BCR_MODE_MASK, 0);
+		break;
+	case AIR_LED_MODE_USER_DEFINE:
+		ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, AIR_PHY_LED_BCR,
+				     AIR_PHY_LED_BCR_EXT_CTRL |
+				     AIR_PHY_LED_BCR_CLK_EN,
+				     AIR_PHY_LED_BCR_EXT_CTRL |
+				     AIR_PHY_LED_BCR_CLK_EN);
+		if (ret < 0)
+			return ret;
+		break;
+	default:
+		printf("LED mode %d is not supported\n", mode);
+		return -EINVAL;
+	}
+
+	for (i = 0; i < num; ++i) {
+		ret = air_led_init(phydev, i, AIR_LED_ENABLE, AIR_ACTIVE_HIGH);
+		if (ret < 0) {
+			printf("LED%d init failed: %d\n", i, ret);
+			return ret;
+		}
+		air_led_hw_control_set(phydev, i, priv->led[i].rules);
+	}
+
+	return 0;
+}
+
+static int en8811h_config(struct phy_device *phydev)
+{
+	ofnode node = phy_get_ofnode(phydev);
+	struct en8811h_priv *priv = phydev->priv;
+	int ret = 0;
+	u32 pbus_value = 0;
+
+	/* If restart happened in .probe(), no need to restart now */
+	if (priv->mcu_needs_restart) {
+		ret = en8811h_restart_mcu(phydev);
+		if (ret < 0)
+			return ret;
+	} else {
+		ret = en8811h_load_firmware(phydev);
+		if (ret) {
+			printf("Load firmware fail.\n");
+			return ret;
+		}
+		/* Next calls to .config() mcu needs to restart */
+		priv->mcu_needs_restart = true;
+	}
+
+	ret = phy_write_mmd(phydev, 0x1e, 0x800c, 0x0);
+	ret |= phy_write_mmd(phydev, 0x1e, 0x800d, 0x0);
+	ret |= phy_write_mmd(phydev, 0x1e, 0x800e, 0x1101);
+	ret |= phy_write_mmd(phydev, 0x1e, 0x800f, 0x0002);
+	if (ret < 0)
+		return ret;
+
+	/* Serdes polarity */
+	pbus_value = 0;
+	if (ofnode_read_bool(node, "airoha,pnswap-rx"))
+		pbus_value |=  EN8811H_POLARITY_RX_REVERSE;
+	else
+		pbus_value &= ~EN8811H_POLARITY_RX_REVERSE;
+	if (ofnode_read_bool(node, "airoha,pnswap-tx"))
+		pbus_value &= ~EN8811H_POLARITY_TX_NORMAL;
+	else
+		pbus_value |=  EN8811H_POLARITY_TX_NORMAL;
+	ret = air_buckpbus_reg_modify(phydev, EN8811H_POLARITY,
+				      EN8811H_POLARITY_RX_REVERSE |
+				      EN8811H_POLARITY_TX_NORMAL, pbus_value);
+	if (ret < 0)
+		return ret;
+
+	ret = air_leds_init(phydev, EN8811H_LED_COUNT, AIR_PHY_LED_DUR,
+			    AIR_LED_MODE_USER_DEFINE);
+	if (ret < 0) {
+		printf("Failed to disable leds: %d\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int en8811h_parse_status(struct phy_device *phydev)
+{
+	int ret = 0, reg_value;
+
+	phydev->duplex = DUPLEX_FULL;
+
+	reg_value = phy_read(phydev, MDIO_DEVAD_NONE, AIR_AUX_CTRL_STATUS);
+	if (reg_value < 0)
+		return reg_value;
+
+	switch (reg_value & AIR_AUX_CTRL_STATUS_SPEED_MASK) {
+	case AIR_AUX_CTRL_STATUS_SPEED_2500:
+		phydev->speed = SPEED_2500;
+		break;
+	case AIR_AUX_CTRL_STATUS_SPEED_1000:
+		phydev->speed = SPEED_1000;
+		break;
+	case AIR_AUX_CTRL_STATUS_SPEED_100:
+		phydev->speed = SPEED_100;
+		break;
+	default:
+		printf("Auto-neg error, defaulting to 100M/FD\n");
+		phydev->speed = SPEED_100;
+		break;
+	}
+
+	return ret;
+}
+
+static int en8811h_startup(struct phy_device *phydev)
+{
+	int ret = 0;
+
+	ret = genphy_update_link(phydev);
+	if (ret)
+		return ret;
+
+	return en8811h_parse_status(phydev);
+}
+
+static int en8811h_probe(struct phy_device *phydev)
+{
+	struct en8811h_priv *priv;
+
+	priv = malloc(sizeof(*priv));
+	if (!priv)
+		return -ENOMEM;
+	memset(priv, 0, sizeof(*priv));
+
+	priv->led[0].rules = AIR_DEFAULT_TRIGGER_LED0;
+	priv->led[1].rules = AIR_DEFAULT_TRIGGER_LED1;
+	priv->led[2].rules = AIR_DEFAULT_TRIGGER_LED2;
+
+	/* mcu has just restarted after firmware load */
+	priv->mcu_needs_restart = false;
+
+	phydev->priv = priv;
+
+	return 0;
+}
+
+U_BOOT_PHY_DRIVER(en8811h) = {
+	.name = "Airoha EN8811H",
+	.uid = EN8811H_PHY_ID,
+	.mask = 0x0ffffff0,
+	.config = &en8811h_config,
+	.probe = &en8811h_probe,
+	.startup = &en8811h_startup,
+	.shutdown = &genphy_shutdown,
+};
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* Re: [PATCH 1/1] net: phy: Add the Airoha EN8811H PHY driver
  2025-04-06 13:02 [PATCH 1/1] net: phy: Add the Airoha EN8811H PHY driver Lucien.Jheng
@ 2025-04-06 13:30 ` Marek Vasut
       [not found]   ` <2aa57092-f844-4ef8-9b97-0998a697c0be@gmail.com>
  2025-04-17 17:06 ` Tom Rini
  1 sibling, 1 reply; 6+ messages in thread
From: Marek Vasut @ 2025-04-06 13:30 UTC (permalink / raw)
  To: Lucien.Jheng, ilias.apalodimas, jerome.forissier,
	marek.vasut+renesas, paul.barker.ct, pbrobinson, u-boot
  Cc: joseph.lin, wenshin.chung, lucien.jheng, frank-w, daniel,
	ericwouds

On 4/6/25 3:02 PM, Lucien.Jheng wrote:
> Add the driver for the Airoha EN8811H 2.5 Gigabit PHY. The PHY supports
> 100/1000/2500 Mbps with auto negotiation only.
> 
> The driver uses two firmware files, for which updated versions are added to
> linux-firmware already.
> 
> Based on the Linux upstream 8811 driver code(air_en8811h.c),
> I have modified the relevant process to align with the U-Boot boot sequence.
> and have validated this on Banana Pi BPI-R3 Mini.
> 
> The MD32 FW is currently stored in eMMC partition 1 on Banana Pi BPI-R3 Mini,
> and it is loaded from there.

If this is ported from Linux, please include Linux commit SHA from which 
this is ported.

> diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
> index 3132718e4f8..5b4cf30b0a3 100644
> --- a/drivers/net/phy/Kconfig
> +++ b/drivers/net/phy/Kconfig
> @@ -79,6 +79,40 @@ config PHY_ADIN
>   	help
>   		Add support for configuring RGMII on Analog Devices ADIN PHYs.
>   
> +menuconfig PHY_AIROHA
> +	bool "Airoha Ethernet PHYs support"
> +
> +config PHY_AIROHA_EN8811H
> +	bool "Airoha Ethernet EN8811H support"
> +	depends on PHY_AIROHA
> +	help
> +		AIROHA EN8811H supported.
> +
> +choice
> +	prompt "Location of the Airoha PHY firmware"

Can this do the same thing as Aquatia PHY and load the PHY firmware from 
filesystem on block device instead , using the blk* or fs* commands ? 
Then the hard-coded filesize and co. won't be necessary.

If you need board specific loading override, implement __weak default 
loading from file on filesystem by default (see what Aquatia does) and 
then override the loading function on board or architecture level.

U-Boot really needs a proper generic firmware loader, because this 
firmware stuff keeps showing up more and more, but I cannot ask you to 
write one only because of this PHY ... but if you are up for a challenge ...

[...]

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH 1/1] net: phy: Add the Airoha EN8811H PHY driver
  2025-04-06 13:02 [PATCH 1/1] net: phy: Add the Airoha EN8811H PHY driver Lucien.Jheng
  2025-04-06 13:30 ` Marek Vasut
@ 2025-04-17 17:06 ` Tom Rini
  1 sibling, 0 replies; 6+ messages in thread
From: Tom Rini @ 2025-04-17 17:06 UTC (permalink / raw)
  To: ilias.apalodimas, jerome.forissier, marek.vasut+renesas,
	paul.barker.ct, pbrobinson, u-boot, Lucien.Jheng
  Cc: joseph.lin, wenshin.chung, lucien.jheng, frank-w, daniel,
	ericwouds

On Sun, 06 Apr 2025 21:02:44 +0800, Lucien.Jheng wrote:

> Add the driver for the Airoha EN8811H 2.5 Gigabit PHY. The PHY supports
> 100/1000/2500 Mbps with auto negotiation only.
> 
> The driver uses two firmware files, for which updated versions are added to
> linux-firmware already.
> 
> Based on the Linux upstream 8811 driver code(air_en8811h.c),
> I have modified the relevant process to align with the U-Boot boot sequence.
> and have validated this on Banana Pi BPI-R3 Mini.
> 
> [...]

Applied to u-boot/master, thanks!

[1/1] net: phy: Add the Airoha EN8811H PHY driver
      commit: c9c8df2c377e512553f2e9ad5d19c4b85efbf07d
-- 
Tom



^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH 1/1] net: phy: Add the Airoha EN8811H PHY driver
       [not found]   ` <2aa57092-f844-4ef8-9b97-0998a697c0be@gmail.com>
@ 2025-04-20  1:43     ` Daniel Golle
  2025-04-20 17:55       ` Marek Vasut
  0 siblings, 1 reply; 6+ messages in thread
From: Daniel Golle @ 2025-04-20  1:43 UTC (permalink / raw)
  To: Lucien.Jheng
  Cc: Marek Vasut, ilias.apalodimas, jerome.forissier,
	marek.vasut+renesas, paul.barker.ct, pbrobinson, u-boot,
	joseph.lin, wenshin.chung, lucien.jheng, frank-w, ericwouds

Hi Lucien,
Hi Marek,

On Fri, Apr 18, 2025 at 11:57:19PM +0800, Lucien.Jheng wrote:
> Hi Marek Vasut
> 
> Thanks for the review.
> 
> The next commit will change the loading of the PHY firmware to the
> filesystem on a block device.

Please keep in mind that most systems using this PHY are (like the
OpenWrt One, for example) simple embedded devices with only a small
amount of NAND or NOR flash. Loading the firmware from a filesystem
means overhead and complexity, while loading it from a simple
MTD partition, raw UBI volume or one of the EMMC boot hw partitions
is a better match to the reality of the devices it is being used on,
making the process less complex and more deterministic, and hence
more robust.

> [...]
> Marek Vasut 於 2025/4/6 下午 09:30 寫道:
> > [...]
> > 
> > Can this do the same thing as Aquatia PHY and load the PHY firmware from
> > filesystem on block device instead , using the blk* or fs* commands ?
> > Then the hard-coded filesize and co. won't be necessary.

Note that the size is afaik given by the hardware in this case, it won't
every change. Hence any handling of a more dynamic approach of loading
the firmware will also have to come with a size-check against the (again)
hard-coded size anyway.

> > 
> > If you need board specific loading override, implement __weak default
> > loading from file on filesystem by default (see what Aquatia does) and
> > then override the loading function on board or architecture level.

That can work, but in practice it will lead to code duplication, as all
platforms using those PHYs are kinda similar (ie. MT7981, MT7986 and
probably Airoha/EcoNet router SoCs as well)

> > 
> > U-Boot really needs a proper generic firmware loader, because this
> > firmware stuff keeps showing up more and more, but I cannot ask you to
> > write one only because of this PHY ... but if you are up for a challenge
> > ...
> > 
> > [...]

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH 1/1] net: phy: Add the Airoha EN8811H PHY driver
  2025-04-20  1:43     ` Daniel Golle
@ 2025-04-20 17:55       ` Marek Vasut
  2025-04-21 15:00         ` Lucien.Jheng
  0 siblings, 1 reply; 6+ messages in thread
From: Marek Vasut @ 2025-04-20 17:55 UTC (permalink / raw)
  To: Daniel Golle, Lucien.Jheng
  Cc: ilias.apalodimas, jerome.forissier, paul.barker.ct, pbrobinson,
	u-boot, joseph.lin, wenshin.chung, lucien.jheng, frank-w,
	ericwouds

On 4/20/25 3:43 AM, Daniel Golle wrote:
> Hi Lucien,
> Hi Marek,

Hi,

> On Fri, Apr 18, 2025 at 11:57:19PM +0800, Lucien.Jheng wrote:
>> Hi Marek Vasut
>>
>> Thanks for the review.
>>
>> The next commit will change the loading of the PHY firmware to the
>> filesystem on a block device.
> 
> Please keep in mind that most systems using this PHY are (like the
> OpenWrt One, for example) simple embedded devices with only a small
> amount of NAND or NOR flash. Loading the firmware from a filesystem
> means overhead and complexity, while loading it from a simple
> MTD partition, raw UBI volume or one of the EMMC boot hw partitions
> is a better match to the reality of the devices it is being used on,
> making the process less complex and more deterministic, and hence
> more robust.

That works until someone else needs to load the firmware from somewhere 
else. U-Boot is not a one-board-special-hack but a generic bootloader, 
which is why special board-specific Kconfig options and other such 
ad-hoc board-specific hacks in drivers are frowned upon. Having a PHY 
driver depend on loading firmware in a non-generic manner is not helpful.

Whatever board specific firmware loading should not be in the driver, it 
should be either in board code, or in some generic firmware loader (if 
that is something someone wants to write, but that is more involved).

I am not buying the "reality of devices" argument, sorry, this is a 
generic ethernet PHY, users can use it on whatever device they populate 
it on, and they can very well chose to place the firmware into 
/lib/firmware to share it with Linux expectations or in some EEPROM .

>> [...]
>> Marek Vasut 於 2025/4/6 下午 09:30 寫道:
>>> [...]
>>>
>>> Can this do the same thing as Aquatia PHY and load the PHY firmware from
>>> filesystem on block device instead , using the blk* or fs* commands ?
>>> Then the hard-coded filesize and co. won't be necessary.
> 
> Note that the size is afaik given by the hardware in this case, it won't
> every change. Hence any handling of a more dynamic approach of loading
> the firmware will also have to come with a size-check against the (again)
> hard-coded size anyway.

If the firmware size is fixed, it shouldn't be exposed via Kconfig in 
the first place. Linux doesn't do that either, why is it done here ?

>>> If you need board specific loading override, implement __weak default
>>> loading from file on filesystem by default (see what Aquatia does) and
>>> then override the loading function on board or architecture level.
> 
> That can work, but in practice it will lead to code duplication, as all
> platforms using those PHYs are kinda similar (ie. MT7981, MT7986 and
> probably Airoha/EcoNet router SoCs as well)
Surely there is a way to factor common loader code parts into dedicated 
loader-implementation.c to avoid duplication.

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH 1/1] net: phy: Add the Airoha EN8811H PHY driver
  2025-04-20 17:55       ` Marek Vasut
@ 2025-04-21 15:00         ` Lucien.Jheng
  0 siblings, 0 replies; 6+ messages in thread
From: Lucien.Jheng @ 2025-04-21 15:00 UTC (permalink / raw)
  To: Marek Vasut, Daniel Golle
  Cc: ilias.apalodimas, jerome.forissier, paul.barker.ct, pbrobinson,
	u-boot, joseph.lin, wenshin.chung, lucien.jheng, frank-w,
	ericwouds

Hi Marek

Marek Vasut 於 2025/4/21 上午 01:55 寫道:
> If the firmware size is fixed, it shouldn't be exposed via Kconfig in 
> the first place. Linux doesn't do that either, why is it done here ? 

I misunderstood this part when I was porting the driver. I'll modify it 
in the next commit,

thank you.


^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2025-04-21 16:52 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-04-06 13:02 [PATCH 1/1] net: phy: Add the Airoha EN8811H PHY driver Lucien.Jheng
2025-04-06 13:30 ` Marek Vasut
     [not found]   ` <2aa57092-f844-4ef8-9b97-0998a697c0be@gmail.com>
2025-04-20  1:43     ` Daniel Golle
2025-04-20 17:55       ` Marek Vasut
2025-04-21 15:00         ` Lucien.Jheng
2025-04-17 17:06 ` Tom Rini

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.