[PATCH v1 06/12] net: airoha-eth: add EN7528 Ethernet support

AK Sharma <[email protected]>
Newsgroups org.u-boot-project.lists.u-boot
Message-ID <[email protected]>
Extend the Airoha GDM Ethernet driver to support the EN7528: the
gigabit switch (mt7530) ports, the GDMA/QDMA rings and the MAC init.

Signed-off-by: AK Sharma <[email protected]>
---
 drivers/net/Kconfig      |   8 +-
 drivers/net/airoha_eth.c | 850 ++++++++++++++++++++++++++++++++++++---
 drivers/phy/Kconfig      |   2 +-
 3 files changed, 808 insertions(+), 52 deletions(-)

diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig
index 66661868..7bcc58ad 100644
--- a/drivers/net/Kconfig
+++ b/drivers/net/Kconfig
@@ -122,13 +122,13 @@ source "drivers/net/airoha/Kconfig"
 
 config AIROHA_ETH
 	bool "Airoha Ethernet QDMA Driver"
-	depends on ARCH_AIROHA
+	depends on ARCH_AIROHA || ARCH_EN75XX
 	select MISC
 	select PHYLIB
 	select DEVRES
-	select DM_ETH_PHY
-	select DM_RESET
-	select MDIO_MT7531_MMIO
+	select DM_ETH_PHY if ARCH_AIROHA
+	select DM_RESET if ARCH_AIROHA
+	select MDIO_MT7531_MMIO if ARCH_AIROHA
 	help
 	  This Driver support Airoha Ethernet QDMA Driver
 	  Say Y to enable support for the Airoha Ethernet QDMA.
diff --git a/drivers/net/airoha_eth.c b/drivers/net/airoha_eth.c
index e5d39b95..640e8bb2 100644
--- a/drivers/net/airoha_eth.c
+++ b/drivers/net/airoha_eth.c
@@ -4,6 +4,7 @@
  * and simplified for U-Boot usage for single TX/RX ring.
  *
  * Copyright (c) 2024 AIROHA Inc
+ * EN751221 support based on econet_eth by Caleb James DeLisle.
  * Author: Lorenzo Bianconi <[email protected]>
  *         Christian Marangi <[email protected]>
  */
@@ -13,6 +14,7 @@
 #include <dm/devres.h>
 #include <dm/lists.h>
 #include <mapmem.h>
+#include <malloc.h>
 #include <miiphy.h>
 #include <net.h>
 #include <regmap.h>
@@ -24,16 +26,27 @@
 #include <linux/ethtool.h>
 #include <linux/io.h>
 #include <linux/iopoll.h>
+#include <linux/mii.h>
 #include <linux/time.h>
+#if IS_ENABLED(CONFIG_ARCH_EN75XX)
+#include <asm/addrspace.h>	/* CKSEG1ADDR: uncached DMA-ring alias */
+#endif
+#if IS_ENABLED(CONFIG_ARCH_AIROHA)
 #include <asm/arch/scu-regmap.h>
+#endif
 
 #include "airoha/pcs-airoha.h"
 
 #define AIROHA_MAX_NUM_GDM_PORTS	4
 #define AIROHA_MAX_NUM_QDMA		1
-#define AIROHA_MAX_NUM_RSTS		3
+#define AIROHA_MAX_NUM_RSTS		4
 #define AIROHA_MAX_NUM_XSI_RSTS		4
 
+#define AIROHA_MAX_NUM_SWITCH_PORT	4
+#define AIROHA_MAX_PBUS_TRY		10
+#define AIROHA_PBUS_SLEEP		100
+#define AIROHA_PBUS_C22_MASK		0x800000
+
 #define AIROHA_MAX_PACKET_SIZE		2048
 #define AIROHA_NUM_TX_RING		1
 #define AIROHA_NUM_RX_RING		1
@@ -53,6 +66,8 @@
 #define   SWITCH_BC_FFP			GENMASK(31, 24)
 #define   SWITCH_UNM_FFP		GENMASK(23, 16)
 #define   SWITCH_UNU_FFP		GENMASK(15, 8)
+#define   SWITCH_CPU_EN			BIT(7)
+#define   SWITCH_CPU_PORT		GENMASK(6, 4)
 #define SWITCH_PMCR(_n)			0x3000 + ((_n) * 0x100)
 #define   SWITCH_IPG_CFG		GENMASK(19, 18)
 #define     SWITCH_IPG_CFG_NORMAL	FIELD_PREP(SWITCH_IPG_CFG, 0x0)
@@ -86,6 +101,19 @@
 #define   SWITCH_PHY_PRE_EN		BIT(15)
 #define   SWITCH_PHY_END_ADDR		GENMASK(12, 8)
 #define   SWITCH_PHY_ST_ADDR		GENMASK(4, 0)
+#define SWITCH_GEPHY_CONN_CFG		0x7c14
+#define   SWITCH_DPHY_CKIN_SEL		BIT(31)
+#define   SWITCH_PHY_CORE_REG_CLK_SEL	BIT(30)
+#define   SWITCH_ETHER_AFE_PWD		GENMASK(28, 24)
+#define SWITCH_PBUS_PHY_IAC		0x7c20
+#define   SWITCH_PBUS_PHY_START		BIT(31)
+#define   SWITCH_PBUS_PHY_CMD		BIT(30)
+#define   SWITCH_PBUS_PHY_CMD_READ	FIELD_PREP(SWITCH_PBUS_PHY_CMD, 0x0)
+#define   SWITCH_PBUS_PHY_CMD_WRITE	FIELD_PREP(SWITCH_PBUS_PHY_CMD, 0x1)
+#define   SWITCH_PBUS_PHY_PORTADDR	GENMASK(28, 24)
+#define   SWITCH_PBUS_PHY_REGADDR	GENMASK(23, 0)
+#define SWITCH_PBUS_PHY_IAWD		0x7c24
+#define SWITCH_PBUS_PHY_IARD		0x7c28
 
 /* FE */
 #define PSE_BASE			0x0100
@@ -106,6 +134,9 @@
 	 (_n) == 2 ? GDM2_BASE : GDM1_BASE)
 
 #define REG_GDM_FWD_CFG(_n)		GDM_BASE(_n)
+#define REG_GDM_MAC_LSB(_n)		(GDM_BASE(_n) + 0x08)
+#define REG_GDM_MAC_MSB(_n)		(GDM_BASE(_n) + 0x0c)
+#define REG_GDM_RX_LEN_THR(_n)		(GDM_BASE(_n) + 0x14)
 #define GDM_PAD_EN			BIT(28)
 #define GDM_DROP_CRC_ERR		BIT(23)
 #define GDM_IP4_CKSUM			BIT(22)
@@ -116,6 +147,74 @@
 #define GDM_MCFQ_MASK			GENMASK(7, 4)
 #define GDM_OCFQ_MASK			GENMASK(3, 0)
 
+/* EN7512/EN7521 GDM forwarding configuration */
+#define EN751221_GDM_DROP_OVERSIZE	BIT(25)	/* matches en7528 gdm_regs.h */
+#define EN751221_GDM_MYMAC_FPORT	GENMASK(15, 12)
+#define EN751221_GDM_BCAST_FPORT	GENMASK(11, 8)
+#define EN751221_GDM_MCAST_FPORT	GENMASK(7, 4)
+#define EN751221_GDM_DEFAULT_FPORT	GENMASK(3, 0)
+#define EN751221_GDM_OVERSIZE_LEN	GENMASK(31, 16)
+#define EN751221_GDM_RUNT_LEN		GENMASK(15, 0)
+
+/* EN7512/EN7521 QDMA register layout */
+#define EN751221_REG_TX_RING_BASE	0x0008
+#define EN751221_REG_RX_RING_BASE	0x000c
+#define EN751221_REG_TX_CPU_IDX		0x0010
+#define EN751221_REG_TX_DMA_IDX		0x0014
+#define EN751221_REG_RX_CPU_IDX		0x0018
+#define EN751221_REG_RX_DMA_IDX		0x001c
+#define EN751221_REG_FWD_DSCP_BASE	0x0020
+#define EN751221_REG_FWD_BUF_BASE	0x0024
+#define EN751221_REG_FWD_DSCP_CFG	0x0028
+#define EN751221_REG_LMGR_INIT_CFG	0x0030
+#define EN751221_REG_INT_STATUS		0x0050
+#define EN751221_REG_INT_ENABLE		0x0054
+#define EN751221_REG_RX_DELAY_INT	0x005c
+#define EN751221_REG_IRQ_BASE		0x0060
+#define EN751221_REG_IRQ_CFG		0x0064
+#define EN751221_REG_IRQ_CLEAR_LEN	0x0068
+#define EN751221_REG_IRQ_STATUS		0x006c
+#define EN751221_REG_RX_RING_SIZE	0x0100
+#define EN751221_REG_RX_RING_THR		0x0104
+
+#define EN751221_RING_IDX_MASK		GENMASK(11, 0)
+#define EN751221_IRQ_DEPTH_MASK		GENMASK(11, 0)
+#define EN751221_IRQ_THRESHOLD_MASK	GENMASK(27, 16)
+#define EN751221_IRQ_HEAD_IDX_MASK	GENMASK(11, 0)
+#define EN751221_IRQ_ENTRY_LEN_MASK	GENMASK(27, 16)
+#define EN751221_IRQ_CLEAR_LEN_MASK	GENMASK(7, 0)
+#define EN751221_HWFWD_LOW_THR_MASK	GENMASK(12, 0)
+#define EN751221_HWFWD_DESC_NUM_MASK	GENMASK(15, 0)
+#define EN751221_HWFWD_OVERHEAD_MASK	GENMASK(23, 16)
+#define EN751221_HWFWD_OVERHEAD_EN	BIT(24)		/* en7528: stock sets this */
+#define EN751221_GLOBAL_CFG_MSG_WORD_SWAP BIT(28)
+
+#define EN751221_TXMSG_CHANNEL_MASK	GENMASK(10, 3)
+#define EN751221_TXMSG_QUEUE_MASK	GENMASK(2, 0)
+#define EN751221_TXMSG_FPORT_MASK	GENMASK(21, 19)
+#define EN751221_FPORT_GDM1		1
+#define EN751221_HW_DSCP_NUM		256	/* en7528 stock LMGR pool (was 4) */
+#define EN751221_IRQ_QUEUE_LEN		32
+#define EN751221_IRQ_THRESHOLD		4
+/*
+ * SCU reset banks (base 0x1fb00000): kernel clk-en7523.c en751221_rst_ofs[] =
+ * { bank0 = REG_RST_CTRL2 = 0x830, bank1 = REG_RST_CTRL1 = 0x834 }, and
+ * en751221_rst_map[] places the frame-engine domains as (RST_NR_PER_BANK=32):
+ *   FE_QDMA1 "qdma0" = 32+1 -> bank1 bit1     FE_QDMA2 "qdma1" = 32+2 -> bank1 bit2
+ *   FE_RST   "fe"    = 32+21-> bank1 bit21     XPON_MAC = 32+31 -> bank1 bit31
+ *   XPON_PHY "xpon-phy" = 0 -> bank0 bit0
+ * The stock econet-eth eth node resets ALL FIVE as one array pulse.
+ */
+#define EN751221_RESET_CONTROL_B1	0xbfb00834	/* SCU reset bank1 (REG_RST_CTRL1) */
+#define EN751221_RESET_CONTROL_B0	0xbfb00830	/* SCU reset bank0 (REG_RST_CTRL2) */
+#define EN751221_RESET_CONTROL2		EN751221_RESET_CONTROL_B1  /* back-compat alias */
+#define EN751221_FE_SRAM_SEL		0xbfb00958
+#define EN751221_RST_QDMA0		BIT(1)		/* bank1: FE_QDMA1 "qdma0" */
+#define EN751221_RST_QDMA1		BIT(2)		/* bank1: FE_QDMA2 "qdma1" (was missing) */
+#define EN751221_RST_FE			BIT(21)		/* bank1: FE_RST */
+#define EN751221_RST_XPON_MAC		BIT(31)		/* bank1: XPON_MAC (was missing) */
+#define EN751221_RST_XPON_PHY		BIT(0)		/* bank0: XPON_PHY (was missing) */
+
 /* QDMA */
 #define REG_QDMA_GLOBAL_CFG			0x0004
 #define GLOBAL_CFG_RX_2B_OFFSET_MASK		BIT(31)
@@ -290,11 +389,24 @@ struct airoha_qdma_fwd_desc {
 	__le32 rsv1;
 };
 
+struct en751221_qdma_fwd_desc {
+	u32 addr;
+	u32 info;
+	u32 msg0;
+	u32 msg1;
+};
+
 struct airoha_queue {
 	struct airoha_qdma_desc *desc;
 	u16 head;
 
 	int ndesc;
+
+	/* Uncached TX bounce buffer: the same broken-flush issue means a CPU-
+	 * built packet in cached memory is not guaranteed to be in DRAM when the
+	 * QDMA reads it, so copy each TX frame through this uncached buffer. */
+	void *bounce;
+	unsigned long bounce_phys;
 };
 
 struct airoha_tx_irq_queue {
@@ -347,6 +459,7 @@ struct airoha_eth {
 
 struct airoha_eth_soc_data {
 	u32 version;
+	bool econet;
 	int num_xsi_rsts;
 	const char * const *xsi_rsts_names;
 	const char *switch_compatible;
@@ -365,6 +478,12 @@ static const char * const en7581_xsi_rsts_names[] = {
 	"xfp-mac",
 };
 
+static const char * const an7583_xsi_rsts_names[] = {
+	"hsi0-mac",
+	"hsi1-mac",
+	"xfp-mac",
+};
+
 static u32 airoha_rr(void __iomem *base, u32 offset)
 {
 	return readl(base + offset);
@@ -405,8 +524,12 @@ static u32 airoha_rmw(void __iomem *base, u32 offset, u32 mask, u32 val)
 #define airoha_qdma_clear(qdma, offset, val)			\
 	airoha_rmw((qdma)->regs, (offset), (val), 0)
 
+#define airoha_switch_rr(eth, offset)				\
+	airoha_rr((eth)->switch_regs, (offset))
 #define airoha_switch_wr(eth, offset, val)			\
 	airoha_wr((eth)->switch_regs, (offset), (val))
+#define airoha_switch_rmw(eth, offset, mask, val)		\
+	airoha_rmw((eth)->switch_regs, (offset), (mask), (val))
 
 static inline dma_addr_t dma_map_unaligned(void *vaddr, size_t len,
 					   enum dma_data_direction dir)
@@ -429,12 +552,41 @@ static inline void dma_unmap_unaligned(dma_addr_t addr, size_t len,
 	dma_unmap_single(start, end - start, dir);
 }
 
+static void *airoha_dma_alloc_coherent(size_t size, unsigned long *dma_addr)
+{
+#if IS_ENABLED(CONFIG_ARCH_EN75XX)
+	void *buf;
+
+	size = ALIGN(size, ARCH_DMA_MINALIGN);
+	buf = memalign(ARCH_DMA_MINALIGN, size);
+	if (!buf)
+		return NULL;
+
+	*dma_addr = virt_to_phys(buf);
+	/*
+	 * EN7528 CM L2 has no Hit_Writeback_Inv_SD (op 0x17 hangs). DMA-shared
+	 * rings must therefore live in KSEG1 so CPU stores reach DRAM without
+	 * an L2 writeback. QDMA then sees descriptors by construction.
+	 */
+	return (void *)CKSEG1ADDR((unsigned long)buf);
+#else
+	return dma_alloc_coherent(size, dma_addr);
+#endif
+}
+
+static bool airoha_is_en751221(struct airoha_eth *eth)
+{
+	return eth->soc->econet;
+}
+
 static int airoha_get_fe_port(struct airoha_gdm_port *port)
 {
 	struct airoha_qdma *qdma = port->qdma;
 	struct airoha_eth *eth = qdma->eth;
 
 	switch (eth->soc->version) {
+	case 0x7512:
+		return EN751221_FPORT_GDM1;
 	case 0x7523:
 		/* FIXME: GDM1 is the only supported port */
 		return FE_PSE_PORT_GDM1;
@@ -446,6 +598,32 @@ static int airoha_get_fe_port(struct airoha_gdm_port *port)
 
 static void airoha_fe_maccr_init(struct airoha_gdm_port *port)
 {
+	if (airoha_is_en751221(port->qdma->eth)) {
+		u32 fwd_cfg, len_thr;
+
+		/*
+		 * QDMA0 CPU is fport zero: received frames go to the CPU. The
+		 * stock en7528 econet-eth driver sets exactly this (all fport
+		 * classes = CPU) plus drop_oversize. (BIT25 DROP_OVERSIZE is the
+		 * correct en7528 bit per gdm_regs.h — the earlier bits[31:30]
+		 * "forward-enable" guess was wrong: those are VIP/L2LU bits.)
+		 */
+		fwd_cfg = EN751221_GDM_DROP_OVERSIZE |
+			  FIELD_PREP(EN751221_GDM_MYMAC_FPORT, 0) |
+			  FIELD_PREP(EN751221_GDM_BCAST_FPORT, 0) |
+			  FIELD_PREP(EN751221_GDM_MCAST_FPORT, 0) |
+			  FIELD_PREP(EN751221_GDM_DEFAULT_FPORT, 0);
+		airoha_fe_wr(port->qdma->eth, REG_GDM_FWD_CFG(port->id),
+			     fwd_cfg);
+
+		len_thr = FIELD_PREP(EN751221_GDM_OVERSIZE_LEN,
+				     PKTSIZE_ALIGN) |
+			  FIELD_PREP(EN751221_GDM_RUNT_LEN, 60);
+		airoha_fe_wr(port->qdma->eth, REG_GDM_RX_LEN_THR(port->id),
+			     len_thr);
+		return;
+	}
+
 	/*
 	 * Disable any kind of CRC drop or offload.
 	 * Enable padding of short TX packets to 60 bytes.
@@ -460,6 +638,214 @@ static int airoha_fe_init(struct airoha_gdm_port *port)
 	return 0;
 }
 
+static void en751221_qdma_reset_rx_desc(struct airoha_queue *q, int index)
+{
+	struct airoha_qdma_desc *desc = &q->desc[index];
+	uchar *rx_packet = net_rx_packets[index];
+	u32 next;
+
+	dma_map_single(rx_packet, PKTSIZE_ALIGN, DMA_FROM_DEVICE);
+	next = (index + 1) % q->ndesc;
+
+	WRITE_ONCE(desc->rsv, 0);
+	WRITE_ONCE(desc->ctrl,
+		   FIELD_PREP(QDMA_DESC_LEN_MASK, PKTSIZE_ALIGN));
+	WRITE_ONCE(desc->addr, virt_to_phys(rx_packet));
+	WRITE_ONCE(desc->data, next);
+	WRITE_ONCE(desc->msg0, 0);
+	WRITE_ONCE(desc->msg1, 0);
+	WRITE_ONCE(desc->msg2, 0);
+	WRITE_ONCE(desc->msg3, 0);
+
+	dma_map_unaligned(desc, sizeof(*desc), DMA_TO_DEVICE);
+}
+
+static void en751221_qdma_init_rx_desc(struct airoha_qdma *qdma)
+{
+	struct airoha_queue *q = &qdma->q_rx[0];
+	int i;
+
+	for (i = 0; i < q->ndesc; i++)
+		en751221_qdma_reset_rx_desc(q, i);
+
+	/* On EN751221, equal CPU/HW indices mean that the RX ring is full. */
+	q->head = 0;
+	airoha_qdma_wr(qdma, EN751221_REG_RX_CPU_IDX, 0);
+	airoha_qdma_wr(qdma, EN751221_REG_RX_DMA_IDX, 1);
+}
+
+static int en751221_qdma_init_rx(struct airoha_qdma *qdma)
+{
+	struct airoha_queue *q = &qdma->q_rx[0];
+	unsigned long dma_addr;
+
+	q->ndesc = RX_DSCP_NUM;
+	q->desc = airoha_dma_alloc_coherent(q->ndesc * sizeof(*q->desc), &dma_addr);
+	if (!q->desc)
+		return -ENOMEM;
+
+	memset(q->desc, 0, q->ndesc * sizeof(*q->desc));
+	airoha_qdma_wr(qdma, EN751221_REG_RX_RING_BASE, dma_addr);
+	airoha_qdma_wr(qdma, EN751221_REG_RX_RING_SIZE, q->ndesc);
+	airoha_qdma_wr(qdma, EN751221_REG_RX_RING_THR, 0);
+
+	en751221_qdma_init_rx_desc(qdma);
+	return 0;
+}
+
+static int en751221_qdma_init_tx(struct airoha_qdma *qdma)
+{
+	struct airoha_queue *q = &qdma->q_tx[0];
+	unsigned long dma_addr;
+
+	q->ndesc = TX_DSCP_NUM;
+	q->head = 0;
+	q->desc = airoha_dma_alloc_coherent(q->ndesc * sizeof(*q->desc), &dma_addr);
+	if (!q->desc)
+		return -ENOMEM;
+
+	memset(q->desc, 0, q->ndesc * sizeof(*q->desc));
+	airoha_qdma_wr(qdma, EN751221_REG_TX_RING_BASE, dma_addr);
+	airoha_qdma_wr(qdma, EN751221_REG_TX_CPU_IDX, 0);
+	airoha_qdma_wr(qdma, EN751221_REG_TX_DMA_IDX, 0);
+
+	q->bounce = airoha_dma_alloc_coherent(AIROHA_MAX_PACKET_SIZE,
+					      &q->bounce_phys);
+	if (!q->bounce)
+		return -ENOMEM;
+
+	return 0;
+}
+
+static int en751221_qdma_init_tx_irq(struct airoha_qdma *qdma)
+{
+	struct airoha_tx_irq_queue *irq_q = &qdma->q_tx_irq[0];
+	unsigned long dma_addr;
+	u32 cfg;
+
+	irq_q->size = EN751221_IRQ_QUEUE_LEN;
+	irq_q->q = airoha_dma_alloc_coherent(irq_q->size * sizeof(u32),
+					     &dma_addr);
+	if (!irq_q->q)
+		return -ENOMEM;
+
+	irq_q->qdma = qdma;
+	memset(irq_q->q, 0xff, irq_q->size * sizeof(u32));
+	dma_map_single(irq_q->q, irq_q->size * sizeof(u32), DMA_TO_DEVICE);
+
+	airoha_qdma_wr(qdma, EN751221_REG_IRQ_BASE, dma_addr);
+	cfg = FIELD_PREP(EN751221_IRQ_THRESHOLD_MASK,
+			 EN751221_IRQ_THRESHOLD) |
+	      FIELD_PREP(EN751221_IRQ_DEPTH_MASK, irq_q->size);
+	airoha_qdma_wr(qdma, EN751221_REG_IRQ_CFG, cfg);
+
+	return 0;
+}
+
+static void en751221_qdma_reset_tx(struct airoha_qdma *qdma)
+{
+	struct airoha_queue *q = &qdma->q_tx[0];
+
+	memset(q->desc, 0, q->ndesc * sizeof(*q->desc));
+	dma_map_single(q->desc, q->ndesc * sizeof(*q->desc), DMA_TO_DEVICE);
+	q->head = 0;
+	airoha_qdma_wr(qdma, EN751221_REG_TX_CPU_IDX, 0);
+	airoha_qdma_wr(qdma, EN751221_REG_TX_DMA_IDX, 0);
+}
+
+static int en751221_qdma_init_hfwd(struct airoha_qdma *qdma)
+{
+	unsigned long dma_addr;
+	u32 status, val;
+	int size;
+
+	size = EN751221_HW_DSCP_NUM * sizeof(struct en751221_qdma_fwd_desc);
+	qdma->hfwd.desc = airoha_dma_alloc_coherent(size, &dma_addr);
+	if (!qdma->hfwd.desc)
+		return -ENOMEM;
+	memset(qdma->hfwd.desc, 0, size);
+	dma_map_single(qdma->hfwd.desc, size, DMA_TO_DEVICE);
+	airoha_qdma_wr(qdma, EN751221_REG_FWD_DSCP_BASE, dma_addr);
+
+	size = AIROHA_MAX_PACKET_SIZE * EN751221_HW_DSCP_NUM;
+	qdma->hfwd.q = airoha_dma_alloc_coherent(size, &dma_addr);
+	if (!qdma->hfwd.q)
+		return -ENOMEM;
+	memset(qdma->hfwd.q, 0, size);
+	dma_map_single(qdma->hfwd.q, size, DMA_TO_DEVICE);
+	airoha_qdma_wr(qdma, EN751221_REG_FWD_BUF_BASE, dma_addr);
+
+	/* en7528 stock: low_thr = 32 (hwf_cfg = 0x20), not 1. */
+	val = FIELD_PREP(HW_FWD_DSCP_PAYLOAD_SIZE_MASK, 0) |
+	      FIELD_PREP(EN751221_HWFWD_LOW_THR_MASK, 32);
+	airoha_qdma_wr(qdma, EN751221_REG_FWD_DSCP_CFG, val);
+
+	/*
+	 * en7528 stock LMGR init = 0x01140100 (OVERHEAD_EN + overhead 0x14 +
+	 * 256 descriptors). The borrowed value omitted OVERHEAD_EN and used
+	 * only 4 descriptors, starving the PSE store-and-forward pool so
+	 * CPU-injected frames were dropped after QDMA writeback DONE.
+	 */
+	val = LMGR_INIT_START |
+	      EN751221_HWFWD_OVERHEAD_EN |
+	      FIELD_PREP(EN751221_HWFWD_OVERHEAD_MASK, 0x14) |
+	      FIELD_PREP(EN751221_HWFWD_DESC_NUM_MASK,
+			 EN751221_HW_DSCP_NUM);
+	airoha_qdma_wr(qdma, EN751221_REG_LMGR_INIT_CFG, val);
+
+	return read_poll_timeout(airoha_qdma_rr, status,
+				 !(status & LMGR_INIT_START), USEC_PER_MSEC,
+				 30 * USEC_PER_MSEC, qdma,
+				 EN751221_REG_LMGR_INIT_CFG);
+}
+
+static int en751221_qdma_init(struct airoha_qdma *qdma)
+{
+	u32 cfg;
+	int ret;
+
+	/* Stop the engine before replacing all descriptor rings. */
+	airoha_qdma_wr(qdma, REG_QDMA_GLOBAL_CFG, 0);
+	airoha_qdma_wr(qdma, EN751221_REG_INT_ENABLE, 0);
+	airoha_qdma_wr(qdma, EN751221_REG_INT_STATUS, 0xffffffff);
+
+	ret = en751221_qdma_init_rx(qdma);
+	if (ret)
+		return ret;
+
+	ret = en751221_qdma_init_tx(qdma);
+	if (ret)
+		return ret;
+
+	ret = en751221_qdma_init_tx_irq(qdma);
+	if (ret)
+		return ret;
+
+	ret = en751221_qdma_init_hfwd(qdma);
+	if (ret)
+		return ret;
+
+	/*
+	 * EN7528: do NOT set GLOBAL_CFG_DSCP_BYTE_SWAP. The stock en7528
+	 * econet-eth driver programs qdma_cfg = 0x140800f5 (msg_word_swap +
+	 * payload_byte_swap, dscp_byte_swap = soc->dscp_byte_swap = false).
+	 * The mainline-airoha value this port borrowed set dscp_byte_swap,
+	 * byte-swapping the TX descriptor the QDMA reads (addr/msg/FPORT) so
+	 * the frame was routed nowhere while still reporting DONE. Matching
+	 * the stock value restores CPU->GDM1->switch egress.
+	 */
+	cfg = EN751221_GLOBAL_CFG_MSG_WORD_SWAP |
+	      GLOBAL_CFG_PAYLOAD_BYTE_SWAP_MASK |
+	      GLOBAL_CFG_IRQ0_EN_MASK |
+	      GLOBAL_CFG_CHECK_DONE_MASK |
+	      GLOBAL_CFG_TX_WB_DONE_MASK |
+	      FIELD_PREP(GLOBAL_CFG_MAX_ISSUE_NUM_MASK, 3);
+	airoha_qdma_wr(qdma, REG_QDMA_GLOBAL_CFG, cfg);
+	airoha_qdma_wr(qdma, EN751221_REG_RX_DELAY_INT, 0);
+
+	return 0;
+}
+
 static void airoha_qdma_reset_rx_desc(struct airoha_queue *q, int index)
 {
 	struct airoha_qdma_desc *desc;
@@ -501,7 +887,7 @@ static int airoha_qdma_init_rx_queue(struct airoha_queue *q,
 	q->ndesc = ndesc;
 	q->head = 0;
 
-	q->desc = dma_alloc_coherent(q->ndesc * sizeof(*q->desc), &dma_addr);
+	q->desc = airoha_dma_alloc_coherent(q->ndesc * sizeof(*q->desc), &dma_addr);
 	if (!q->desc)
 		return -ENOMEM;
 
@@ -548,7 +934,7 @@ static int airoha_qdma_init_tx_queue(struct airoha_queue *q,
 	q->ndesc = size;
 	q->head = 0;
 
-	q->desc = dma_alloc_coherent(q->ndesc * sizeof(*q->desc), &dma_addr);
+	q->desc = airoha_dma_alloc_coherent(q->ndesc * sizeof(*q->desc), &dma_addr);
 	if (!q->desc)
 		return -ENOMEM;
 
@@ -570,7 +956,7 @@ static int airoha_qdma_tx_irq_init(struct airoha_tx_irq_queue *irq_q,
 	int id = irq_q - &qdma->q_tx_irq[0];
 	unsigned long dma_addr;
 
-	irq_q->q = dma_alloc_coherent(size * sizeof(u32), &dma_addr);
+	irq_q->q = airoha_dma_alloc_coherent(size * sizeof(u32), &dma_addr);
 	if (!irq_q->q)
 		return -ENOMEM;
 
@@ -615,7 +1001,7 @@ static int airoha_qdma_init_hfwd_queues(struct airoha_qdma *qdma)
 	int size;
 
 	size = HW_DSCP_NUM * sizeof(struct airoha_qdma_fwd_desc);
-	qdma->hfwd.desc = dma_alloc_coherent(size, &dma_addr);
+	qdma->hfwd.desc = airoha_dma_alloc_coherent(size, &dma_addr);
 	if (!qdma->hfwd.desc)
 		return -ENOMEM;
 
@@ -625,7 +1011,7 @@ static int airoha_qdma_init_hfwd_queues(struct airoha_qdma *qdma)
 	airoha_qdma_wr(qdma, REG_FWD_DSCP_BASE, dma_addr);
 
 	size = AIROHA_MAX_PACKET_SIZE * HW_DSCP_NUM;
-	qdma->hfwd.q = dma_alloc_coherent(size, &dma_addr);
+	qdma->hfwd.q = airoha_dma_alloc_coherent(size, &dma_addr);
 	if (!qdma->hfwd.q)
 		return -ENOMEM;
 
@@ -689,6 +1075,8 @@ static int airoha_qdma_init(struct udevice *dev,
 	qdma->regs = dev_remap_addr_name(dev, "qdma0");
 	if (IS_ERR(qdma->regs))
 		return PTR_ERR(qdma->regs);
+	if (airoha_is_en751221(eth))
+		return en751221_qdma_init(qdma);
 
 	err = airoha_qdma_init_rx(qdma);
 	if (err)
@@ -735,11 +1123,51 @@ static int airoha_pcs_init(struct udevice *dev)
 }
 #endif
 
+static int en751221_hw_init(struct udevice *dev, struct airoha_eth *eth)
+{
+	u32 val, val2;
+	int ret;
+
+	/* Expose the frame-engine register window instead of the FE SRAM. */
+	__raw_writel(0, (void __iomem *)EN751221_FE_SRAM_SEL);
+
+	/*
+	 * Reset the SAME FIVE frame-engine domains the stock econet-eth DT eth
+	 * node resets as one array pulse: {fe, qdma0=FE_QDMA1, qdma1=FE_QDMA2,
+	 * xpon-mac, xpon-phy}. The earlier port reset only qdma0(bit1)+fe(bit21)
+	 * and left FE_QDMA2 (bank1 bit2) — the second FE-DMA domain that gates
+	 * the QDMA<->PSE store-and-forward datapath shared by both QDMAs — in its
+	 * bootbase state. That back-pressures qdma0's drain: the first CPU-TX
+	 * latches TX_DMA_BUSY (qdma_cfg .f2) but the frame never reaches
+	 * GDM1/egress and tx_hwi never advances. Assert all five, hold, deassert.
+	 */
+	val = __raw_readl((void __iomem *)EN751221_RESET_CONTROL_B1);
+	val2 = __raw_readl((void __iomem *)EN751221_RESET_CONTROL_B0);
+	__raw_writel(val | EN751221_RST_QDMA0 | EN751221_RST_QDMA1 |
+		     EN751221_RST_FE | EN751221_RST_XPON_MAC,
+		     (void __iomem *)EN751221_RESET_CONTROL_B1);
+	__raw_writel(val2 | EN751221_RST_XPON_PHY,
+		     (void __iomem *)EN751221_RESET_CONTROL_B0);
+	mdelay(20);
+	__raw_writel(val, (void __iomem *)EN751221_RESET_CONTROL_B1);
+	__raw_writel(val2, (void __iomem *)EN751221_RESET_CONTROL_B0);
+	mdelay(20);
+
+	ret = airoha_qdma_init(dev, eth, &eth->qdma[0]);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
 static int airoha_hw_init(struct udevice *dev,
 			  struct airoha_eth *eth)
 {
 	int ret, i;
 
+	if (airoha_is_en751221(eth))
+		return en751221_hw_init(dev, eth);
+
 	/* disable xsi */
 	ret = reset_assert_bulk(&eth->xsi_rsts);
 	if (ret)
@@ -787,6 +1215,86 @@ static int airoha_switch_init(struct udevice *dev, struct airoha_eth *eth)
 
 	/* Switch doesn't have a DEV, gets address and setup Flood and CPU port */
 	eth->switch_regs = map_sysmem(addr, 0);
+	if (data->econet) {
+		u32 pmcr;
+
+		/*
+		 * Soft-reset the integrated MT7530 switch core to a known state
+		 * before (re)configuring it, mirroring mt7531_setup(): the stock
+		 * bootbase leaves it in its own config. SYS_CTRL @0x7000, bits
+		 * SW_SYS_RST|SW_REG_RST self-clear when the reset completes.
+		 */
+		airoha_switch_wr(eth, 0x7000, 0x3);
+		mdelay(100);
+
+		/* Integrated MT7530: flood unknown traffic to all ports and use P6. */
+		airoha_switch_wr(eth, SWITCH_MFC,
+				 SWITCH_BC_FFP | SWITCH_UNM_FFP | SWITCH_UNU_FFP |
+				 SWITCH_CPU_EN | FIELD_PREP(SWITCH_CPU_PORT, 6));
+
+		pmcr = SWITCH_MAC_MODE | SWITCH_FORCE_MODE | SWITCH_MAC_TX_EN |
+		       SWITCH_MAC_RX_EN | SWITCH_BKOFF_EN | SWITCH_BKPR_EN |
+		       SWITCH_FORCE_SPD_1000 | SWITCH_FORCE_DPX |
+		       SWITCH_FORCE_LNK;
+
+		/*
+		 * P5 is the TRGMII cascade to the external MCM switch while P6
+		 * is the CPU port.  The EN751221 OEM setup uses shrink IPG only
+		 * on the cascade and short IPG on the CPU link.
+		 */
+		airoha_switch_wr(eth, SWITCH_PMCR(5),
+				 pmcr | SWITCH_IPG_CFG_SHRINK);
+		airoha_switch_wr(eth, SWITCH_PMCR(6),
+				 pmcr | SWITCH_IPG_CFG_SHORT);
+		airoha_switch_wr(eth, SWITCH_PHY_POLL, 0x7f7f8c08);
+
+		/*
+		 * EN7528 LAN ports 1-4 (internal GPHYs at MDIO 9-12): enable the
+		 * MAC but DO NOT force the link. Leaving FORCE_MODE clear lets the
+		 * switch PHY-poll (SWITCH_PHY_POLL, set just above) drive each port
+		 * MAC to the speed/duplex the GPHY actually negotiates with its
+		 * peer. Force-linking these at 1G silently breaks egress to a
+		 * 100M/10M peer: the MAC<->GPHY line rate mismatches, so a CPU-TX
+		 * frame leaves GDM1 (GDM1 TX MIB increments) but never reaches the
+		 * RJ45. Proven on HW with a 100M host — forced 1G => 0 egress, auto
+		 * => ping/traffic works and PMSR reports the real 100M FDX link.
+		 * (Ports 5/6 keep the forced-1G `pmcr` above: those are the fixed
+		 * internal CPU/cascade links, not GPHY line ports.)
+		 */
+		{
+			u32 lan_pmcr = SWITCH_MAC_MODE | SWITCH_MAC_TX_EN |
+				       SWITCH_MAC_RX_EN | SWITCH_BKOFF_EN |
+				       SWITCH_BKPR_EN | SWITCH_IPG_CFG_NORMAL;
+			int p;
+
+			for (p = 1; p <= 4; p++)
+				airoha_switch_wr(eth, SWITCH_PMCR(p), lan_pmcr);
+		}
+
+		/*
+		 * EN7528: the integrated MT7530 needs its per-port forwarding
+		 * matrix programmed, or no frames pass between ports (the LAN
+		 * GPHYs at MDIO 9-12 are already powered and link fine; the
+		 * en7512 econet stub only set MFC/PMCR and never the matrix).
+		 * Give each user port (0-5) a matrix pointing at the CPU port
+		 * (6) and the CPU port a matrix of all user ports, and mark
+		 * every port a VLAN user port. Mirrors mt753x_port_isolation()
+		 * / mt7988_setup(): PCR = 0x2004 + p*0x100 (matrix at bit 16),
+		 * PVC = 0x2010 + p*0x100 (STAG 0x8100, VLAN_ATTR_USER).
+		 */
+		{
+			int p;
+
+			for (p = 0; p < 7; p++) {
+				airoha_switch_wr(eth, 0x2004 + p * 0x100,
+						 (u32)(p == 6 ? 0x3f : 0x40) << 16);
+				airoha_switch_wr(eth, 0x2010 + p * 0x100,
+						 0x81000000);
+			}
+		}
+
+		return 0;
+	}
 
 	/* Set FLOOD, no CPU switch register */
 	airoha_switch_wr(eth, SWITCH_MFC, SWITCH_BC_FFP | SWITCH_UNM_FFP |
@@ -809,6 +1317,59 @@ static int airoha_switch_init(struct udevice *dev, struct airoha_eth *eth)
 			 FIELD_PREP(SWITCH_PHY_END_ADDR, 0xc) |
 			 FIELD_PREP(SWITCH_PHY_ST_ADDR, 0x8));
 
+	/* AN7583 require tweak to GEPHY_CONN_CFG and clear PHY BMCR_PDOWN */
+	if (!strcmp(data->switch_compatible, "airoha,an7583-switch")) {
+		int i;
+
+		airoha_switch_rmw(eth, SWITCH_GEPHY_CONN_CFG,
+				  SWITCH_DPHY_CKIN_SEL |
+				  SWITCH_PHY_CORE_REG_CLK_SEL |
+				  SWITCH_ETHER_AFE_PWD,
+				  SWITCH_DPHY_CKIN_SEL |
+				  SWITCH_PHY_CORE_REG_CLK_SEL |
+				  FIELD_PREP(SWITCH_ETHER_AFE_PWD, 0));
+
+		/* Disable BMCR_PDOWN for every PHY */
+		for (i = 0; i < AIROHA_MAX_NUM_SWITCH_PORT; i++) {
+			int try;
+			u32 val;
+
+			airoha_switch_wr(eth, SWITCH_PBUS_PHY_IAC,
+					 SWITCH_PBUS_PHY_START |
+					 SWITCH_PBUS_PHY_CMD_READ |
+					 FIELD_PREP(SWITCH_PBUS_PHY_PORTADDR, i) |
+					 FIELD_PREP(SWITCH_PBUS_PHY_REGADDR,
+						    AIROHA_PBUS_C22_MASK | MII_BMCR));
+
+			for (try = 0; try < AIROHA_MAX_PBUS_TRY; try++) {
+				val = airoha_switch_rr(eth, SWITCH_PBUS_PHY_IAC);
+				if (!(val & SWITCH_PBUS_PHY_START))
+					break;
+
+				udelay(AIROHA_PBUS_SLEEP);
+			}
+
+			val = airoha_switch_rr(eth, SWITCH_PBUS_PHY_IARD);
+			val &= ~BMCR_PDOWN;
+
+			airoha_switch_wr(eth, SWITCH_PBUS_PHY_IAWD, val);
+			airoha_switch_wr(eth, SWITCH_PBUS_PHY_IAC,
+					 SWITCH_PBUS_PHY_START |
+					 SWITCH_PBUS_PHY_CMD_WRITE |
+					 FIELD_PREP(SWITCH_PBUS_PHY_PORTADDR, i) |
+					 FIELD_PREP(SWITCH_PBUS_PHY_REGADDR,
+						    AIROHA_PBUS_C22_MASK | MII_BMCR));
+
+			for (try = 0; try < AIROHA_MAX_PBUS_TRY; try++) {
+				val = airoha_switch_rr(eth, SWITCH_PBUS_PHY_IAC);
+				if (!(val & SWITCH_PBUS_PHY_START))
+					break;
+
+				udelay(AIROHA_PBUS_SLEEP);
+			}
+		}
+	}
+
 	return 0;
 }
 
@@ -828,6 +1389,8 @@ static int airoha_alloc_gdm_port(struct udevice *dev, ofnode node)
 	ret = ofnode_read_u32(node, "reg", &id);
 	if (ret)
 		return ret;
+	if (eth->soc->econet)
+		id++;
 
 	if (id > AIROHA_MAX_NUM_GDM_PORTS)
 		return -EINVAL;
@@ -882,56 +1445,65 @@ static int airoha_eth_probe(struct udevice *dev)
 {
 	struct airoha_eth_soc_data *data = (void *)dev_get_driver_data(dev);
 	struct airoha_eth *eth = dev_get_priv(dev);
-	struct regmap *scu_regmap;
 	struct udevice *mdio_dev;
 	ofnode node;
 	int i, ret;
 
-	scu_regmap = airoha_get_scu_regmap();
-	if (IS_ERR(scu_regmap))
-		return PTR_ERR(scu_regmap);
+	eth->soc = data;
+	if (!data->econet) {
+#if IS_ENABLED(CONFIG_ARCH_AIROHA)
+		struct regmap *scu_regmap;
 
-	/* It seems by default the FEMEM_SEL is set to Memory (0x1)
-	 * preventing any access to any QDMA and FrameEngine register
-	 * reporting all 0xdeadbeef (poor cow :( )
-	 */
-	regmap_write(scu_regmap, SCU_SHARE_FEMEM_SEL, 0x0);
+		scu_regmap = airoha_get_scu_regmap();
+		if (IS_ERR(scu_regmap))
+			return PTR_ERR(scu_regmap);
 
-	eth->soc = data;
+		/* FEMEM_SEL=1 hides QDMA/FE behind the shared SRAM window. */
+		regmap_write(scu_regmap, SCU_SHARE_FEMEM_SEL, 0x0);
+#else
+		return -EOPNOTSUPP;
+#endif
+	}
 
 	eth->fe_regs = dev_remap_addr_name(dev, "fe");
 	if (!eth->fe_regs)
 		return -ENOMEM;
 
-	eth->rsts.resets = devm_kcalloc(dev, AIROHA_MAX_NUM_RSTS,
-					sizeof(struct reset_ctl), GFP_KERNEL);
-	if (!eth->rsts.resets)
-		return -ENOMEM;
-	eth->rsts.count = AIROHA_MAX_NUM_RSTS;
+	if (!data->econet) {
+		eth->rsts.resets = devm_kcalloc(dev, AIROHA_MAX_NUM_RSTS,
+						sizeof(struct reset_ctl), GFP_KERNEL);
+		if (!eth->rsts.resets)
+			return -ENOMEM;
+		eth->rsts.count = AIROHA_MAX_NUM_RSTS;
 
-	eth->xsi_rsts.resets = devm_kcalloc(dev, data->num_xsi_rsts,
-					    sizeof(struct reset_ctl), GFP_KERNEL);
-	if (!eth->xsi_rsts.resets)
-		return -ENOMEM;
-	eth->xsi_rsts.count = data->num_xsi_rsts;
+		eth->xsi_rsts.resets = devm_kcalloc(dev, data->num_xsi_rsts,
+						    sizeof(struct reset_ctl), GFP_KERNEL);
+		if (!eth->xsi_rsts.resets)
+			return -ENOMEM;
+		eth->xsi_rsts.count = data->num_xsi_rsts;
 
-	ret = reset_get_by_name(dev, "fe", &eth->rsts.resets[0]);
-	if (ret)
-		return ret;
+		ret = reset_get_by_name(dev, "fe", &eth->rsts.resets[0]);
+		if (ret)
+			return ret;
 
-	ret = reset_get_by_name(dev, "pdma", &eth->rsts.resets[1]);
-	if (ret)
-		return ret;
+		ret = reset_get_by_name(dev, "pdma", &eth->rsts.resets[1]);
+		if (ret)
+			return ret;
 
-	ret = reset_get_by_name(dev, "qdma", &eth->rsts.resets[2]);
-	if (ret)
-		return ret;
+		ret = reset_get_by_name(dev, "qdma", &eth->rsts.resets[2]);
+		if (ret)
+			return ret;
 
-	for (i = 0; i < data->num_xsi_rsts; i++) {
-		ret = reset_get_by_name(dev, data->xsi_rsts_names[i],
-					&eth->xsi_rsts.resets[i]);
+		ret = reset_get_by_name(dev, "switch", &eth->rsts.resets[3]);
 		if (ret)
 			return ret;
+
+		for (i = 0; i < data->num_xsi_rsts; i++) {
+			ret = reset_get_by_name(dev, data->xsi_rsts_names[i],
+						&eth->xsi_rsts.resets[i]);
+			if (ret)
+				return ret;
+		}
 	}
 
 	ret = airoha_hw_init(dev, eth);
@@ -943,12 +1515,15 @@ static int airoha_eth_probe(struct udevice *dev)
 		return ret;
 
 	/* Airoha switch mdio PHYs maybe used by several GDM devices */
-	mdio_dev = airoha_switch_mdio_init(dev);
-	if (!IS_ERR_OR_NULL(mdio_dev))
-		eth->switch_mdio_dev = mdio_dev;
+	if (!data->econet) {
+		mdio_dev = airoha_switch_mdio_init(dev);
+		if (!IS_ERR_OR_NULL(mdio_dev))
+			eth->switch_mdio_dev = mdio_dev;
+	}
 
 	ofnode_for_each_subnode(node, dev_ofnode(dev)) {
-		if (!ofnode_device_is_compatible(node, "airoha,eth-mac"))
+		if (!ofnode_device_is_compatible(node, "airoha,eth-mac") &&
+		    !ofnode_device_is_compatible(node, "econet,eth-mac"))
 			continue;
 
 		if (!ofnode_is_enabled(node))
@@ -964,9 +1539,18 @@ static int airoha_eth_probe(struct udevice *dev)
 
 static int airoha_eth_port_of_to_plat(struct udevice *dev)
 {
+	struct airoha_eth *eth = (void *)dev_get_driver_data(dev);
 	struct airoha_gdm_port *port = dev_get_priv(dev);
+	u32 id;
+	int ret;
+
+	ret = dev_read_u32(dev, "reg", &id);
+	if (ret)
+		return ret;
+
+	port->id = id + (eth->soc->econet ? 1 : 0);
 
-	return dev_read_u32(dev, "reg", &port->id);
+	return 0;
 }
 
 static int airoha_eth_port_probe(struct udevice *dev)
@@ -992,6 +1576,9 @@ static int airoha_eth_port_probe(struct udevice *dev)
 		return -EINVAL;
 #endif
 	} else {
+		if (eth->soc->econet)
+			return 0;
+
 		/*
 		 * GDM1 device connected to airoha switch. Probe airoha switch
 		 * mdio to be able set/query states of corresponding LAN ports.
@@ -1016,7 +1603,12 @@ static int airoha_eth_init(struct udevice *dev)
 	qid = 0;
 	q = &qdma->q_rx[qid];
 
-	airoha_qdma_init_rx_desc(q);
+	if (airoha_is_en751221(qdma->eth))
+		en751221_qdma_init_rx_desc(qdma);
+	else
+		airoha_qdma_init_rx_desc(q);
+	if (airoha_is_en751221(qdma->eth))
+		en751221_qdma_reset_tx(qdma);
 
 	airoha_qdma_set(qdma, REG_QDMA_GLOBAL_CFG,
 			GLOBAL_CFG_TX_DMA_EN_MASK |
@@ -1087,6 +1679,129 @@ static void airoha_eth_stop(struct udevice *dev)
 			  GLOBAL_CFG_RX_DMA_EN_MASK);
 }
 
+static int en751221_eth_send(struct udevice *dev, void *packet, int length)
+{
+	struct airoha_gdm_port *port = dev_get_priv(dev);
+	struct airoha_qdma *qdma = port->qdma;
+	struct airoha_queue *q = &qdma->q_tx[0];
+	struct airoha_qdma_desc *desc;
+	u32 index, next, ctrl = 0, irq_status = 0;
+	int i;
+
+	index = q->head;
+	next = (index + 1) % q->ndesc;
+	desc = &q->desc[index];
+
+	/*
+	 * Stage the frame in the KSEG1 bounce buffer (see
+	 * airoha_dma_alloc_coherent). desc and bounce are uncached, so the
+	 * writes land in DRAM; no map/flush.
+	 */
+	memcpy(q->bounce, packet, length);
+
+	WRITE_ONCE(desc->rsv, 0);
+	WRITE_ONCE(desc->ctrl, FIELD_PREP(QDMA_DESC_LEN_MASK, length));
+	WRITE_ONCE(desc->addr, q->bounce_phys);
+	WRITE_ONCE(desc->data, next);
+	WRITE_ONCE(desc->msg0,
+		   FIELD_PREP(EN751221_TXMSG_CHANNEL_MASK, 0) |
+		   FIELD_PREP(EN751221_TXMSG_QUEUE_MASK, 0));
+	WRITE_ONCE(desc->msg1,
+		   FIELD_PREP(EN751221_TXMSG_FPORT_MASK,
+			      EN751221_FPORT_GDM1));
+	WRITE_ONCE(desc->msg2, 0);
+	WRITE_ONCE(desc->msg3, 0);
+
+	airoha_qdma_wr(qdma, EN751221_REG_TX_CPU_IDX, next);
+
+	for (i = 0; i < 10000; i++) {
+		ctrl = READ_ONCE(desc->ctrl);
+		if (ctrl & QDMA_DESC_DONE_MASK)
+			break;
+		udelay(1);
+	}
+
+	if (!(ctrl & QDMA_DESC_DONE_MASK))
+		return -ETIMEDOUT;
+
+	for (i = 0; i < 10000; i++) {
+		irq_status = airoha_qdma_rr(qdma, EN751221_REG_IRQ_STATUS);
+		if (FIELD_GET(EN751221_IRQ_ENTRY_LEN_MASK, irq_status))
+			break;
+		udelay(1);
+	}
+	if (!FIELD_GET(EN751221_IRQ_ENTRY_LEN_MASK, irq_status))
+		return -ETIMEDOUT;
+
+	/*
+	 * QDMA consumes one hardware-forward descriptor for each CPU TX
+	 * context.  Advancing the completion queue returns that descriptor
+	 * to LMGR; without this, the four-entry first-light pool is exhausted
+	 * after four packets even though TX writeback reports DONE.
+	 */
+	airoha_qdma_rmw(qdma, EN751221_REG_IRQ_CLEAR_LEN,
+			EN751221_IRQ_CLEAR_LEN_MASK, 1);
+
+	q->head = next;
+	return 0;
+}
+
+static int en751221_eth_recv(struct udevice *dev, uchar **packetp)
+{
+	struct airoha_gdm_port *port = dev_get_priv(dev);
+	struct airoha_qdma *qdma = port->qdma;
+	struct airoha_queue *q = &qdma->q_rx[0];
+	struct airoha_qdma_desc *desc;
+	uchar *rx_packet;
+	u32 index, hw_index, length, addr;
+
+	index = (q->head + 1) % q->ndesc;
+	hw_index = airoha_qdma_rr(qdma, EN751221_REG_RX_DMA_IDX) &
+		   EN751221_RING_IDX_MASK;
+	if (index == hw_index)
+		return -EAGAIN;
+
+	desc = &q->desc[index];
+	dma_unmap_unaligned((dma_addr_t)desc, sizeof(*desc),
+			    DMA_FROM_DEVICE);
+	length = FIELD_GET(QDMA_DESC_LEN_MASK, READ_ONCE(desc->ctrl));
+	addr = READ_ONCE(desc->addr);
+	if (!length || length > PKTSIZE_ALIGN) {
+		en751221_qdma_reset_rx_desc(q, index);
+		airoha_qdma_wr(qdma, EN751221_REG_RX_CPU_IDX, index);
+		q->head = index;
+		return -EIO;
+	}
+
+	/*
+	 * Hand U-Boot the KSEG1 alias of the received frame. QDMA wrote DRAM
+	 * directly; a cached read can still hit a stale L2 line because this
+	 * CM cannot Hit_Writeback_Inv_SD (see airoha_dma_alloc_coherent).
+	 */
+	rx_packet = (uchar *)CKSEG1ADDR((unsigned long)addr);
+	*packetp = rx_packet;
+
+	return length;
+}
+
+static int en751221_eth_free_pkt(struct udevice *dev, uchar *packet)
+{
+	struct airoha_gdm_port *port = dev_get_priv(dev);
+	struct airoha_qdma *qdma = port->qdma;
+	struct airoha_queue *q = &qdma->q_rx[0];
+	u32 index;
+
+	if (!packet)
+		return 0;
+
+	index = (q->head + 1) % q->ndesc;
+	en751221_qdma_reset_rx_desc(q, index);
+	airoha_qdma_wr(qdma, EN751221_REG_RX_CPU_IDX, index);
+	q->head = index;
+
+	return 0;
+}
+
 static int airoha_eth_send(struct udevice *dev, void *packet, int length)
 {
 	struct airoha_gdm_port *port = dev_get_priv(dev);
@@ -1100,6 +1815,9 @@ static int airoha_eth_send(struct udevice *dev, void *packet, int length)
 	u32 val;
 	int i;
 
+	if (airoha_is_en751221(qdma->eth))
+		return en751221_eth_send(dev, packet, length);
+
 	/*
 	 * There is no need to pad short TX packets to 60 bytes since the
 	 * GDM_PAD_EN bit set in the corresponding REG_GDM_FWD_CFG(n) register.
@@ -1161,6 +1879,9 @@ static int airoha_eth_recv(struct udevice *dev, int flags, uchar **packetp)
 	u16 length;
 	int qid;
 
+	if (airoha_is_en751221(qdma->eth))
+		return en751221_eth_recv(dev, packetp);
+
 	qid = 0;
 	q = &qdma->q_rx[qid];
 	desc = &q->desc[q->head];
@@ -1187,6 +1908,9 @@ static int arht_eth_free_pkt(struct udevice *dev, uchar *packet, int length)
 	struct airoha_queue *q;
 	int qid;
 
+	if (airoha_is_en751221(qdma->eth))
+		return en751221_eth_free_pkt(dev, packet);
+
 	if (!packet)
 		return 0;
 
@@ -1237,6 +1961,11 @@ static int arht_eth_write_hwaddr(struct udevice *dev)
 	macaddr_msb = FIELD_PREP(SMACCR1_MAC1, mac[1]) |
 		      FIELD_PREP(SMACCR1_MAC0, mac[0]);
 
+	if (airoha_is_en751221(qdma->eth)) {
+		airoha_fe_wr(qdma->eth, REG_GDM_MAC_LSB(port->id), macaddr_lsb);
+		airoha_fe_wr(qdma->eth, REG_GDM_MAC_MSB(port->id), macaddr_msb);
+	}
+
 	/* Set MAC for Switch */
 	airoha_switch_wr(qdma->eth, SWITCH_SMACCR0, macaddr_lsb);
 	airoha_switch_wr(qdma->eth, SWITCH_SMACCR1, macaddr_msb);
@@ -1246,11 +1975,20 @@ static int arht_eth_write_hwaddr(struct udevice *dev)
 
 static int airoha_eth_bind(struct udevice *dev)
 {
+	const struct airoha_eth_soc_data *data;
+
+	data = (const void *)dev_get_driver_data(dev);
+
 	/*
-	 * Force Probe as we set the Main ETH driver as misc
-	 * to register multiple eth port for each GDM
+	 * Force probe on Airoha ARM SoCs since the parent is a misc device
+	 * responsible for registering the child GDM Ethernet devices.  Do not
+	 * probe EN751221 here: DM_FLAG_PROBE_AFTER_BIND is handled by
+	 * initr_dm_devices() before serial_initialize(), which makes an early
+	 * QDMA/switch failure look like a silent hang.  The EN751221 board
+	 * probes this parent from board_late_init(), immediately before net init.
 	 */
-	dev_or_flags(dev, DM_FLAG_PROBE_AFTER_BIND);
+	if (!data || !data->econet)
+		dev_or_flags(dev, DM_FLAG_PROBE_AFTER_BIND);
 
 	return 0;
 }
@@ -1262,6 +2000,12 @@ static const struct airoha_eth_soc_data en7523_data = {
 	.switch_compatible = "airoha,en7523-switch",
 };
 
+static const struct airoha_eth_soc_data en751221_data = {
+	.version = 0x7512,
+	.econet = true,
+	.switch_compatible = "mediatek,mt7530",
+};
+
 static const struct airoha_eth_soc_data en7581_data = {
 	.version = 0x7581,
 	.xsi_rsts_names = en7581_xsi_rsts_names,
@@ -1269,13 +2013,25 @@ static const struct airoha_eth_soc_data en7581_data = {
 	.switch_compatible = "airoha,en7581-switch",
 };
 
+static const struct airoha_eth_soc_data an7583_data = {
+	.xsi_rsts_names = an7583_xsi_rsts_names,
+	.num_xsi_rsts = ARRAY_SIZE(an7583_xsi_rsts_names),
+	.switch_compatible = "airoha,an7583-switch",
+};
+
 static const struct udevice_id airoha_eth_ids[] = {
+	{ .compatible = "econet,en751221-eth",
+	  .data = (ulong)&en751221_data,
+	},
 	{ .compatible = "airoha,en7523-eth",
 	  .data = (ulong)&en7523_data,
 	},
 	{ .compatible = "airoha,en7581-eth",
 	  .data = (ulong)&en7581_data,
 	},
+	{ .compatible = "airoha,an7583-eth",
+	  .data = (ulong)&an7583_data,
+	},
 	{ }
 };
 
diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig
index 5c8ec2b1..28909d49 100644
--- a/drivers/phy/Kconfig
+++ b/drivers/phy/Kconfig
@@ -272,7 +272,7 @@ config PHY_EXYNOS_USBDRD
 config PHY_MTK_TPHY
 	bool "MediaTek T-PHY Driver"
 	depends on PHY
-	depends on ARCH_MEDIATEK || SOC_MT7621
+	depends on ARCH_MEDIATEK || SOC_MT7621 || ARCH_EN75XX
 	select REGMAP
 	select SYSCON
 	help
-- 
2.53.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.