Subject: [PATCH] 9997-ethernet-add-wed-rx-support-for-mt7986

Signed-off-by: Sujuan Chen <sujuan.chen@mediatek.com>
---
 include/linux/soc/mediatek/mtk_wed.h          | 93 +++++++++++++++++++++++++---
 drivers/net/ethernet/mediatek/Makefile        |   2 +-
 .../net/ethernet/mediatek/mtk_ppe_offload.c   |   4 +-
 drivers/net/ethernet/mediatek/mtk_wed.c       | 707 +++++++++++++++---
 drivers/net/ethernet/mediatek/mtk_wed.h       |  99 ++-
 drivers/net/ethernet/mediatek/mtk_wed_ccif.c  | 137 ++++
 drivers/net/ethernet/mediatek/mtk_wed_ccif.h  |  47 ++
 .../net/ethernet/mediatek/mtk_wed_debugfs.c   | 174 +++++
 drivers/net/ethernet/mediatek/mtk_wed_mcu.c   | 641 ++++++++++++++++
 drivers/net/ethernet/mediatek/mtk_wed_mcu.h   |  97 +++
 drivers/net/ethernet/mediatek/mtk_wed_regs.h  | 173 ++++-
 drivers/net/ethernet/mediatek/mtk_wed_wo.c    | 562 ++++++++++++++
 drivers/net/ethernet/mediatek/mtk_wed_wo.h    | 330 ++++++++
 12 files changed, 2939 insertions(+), 127 deletions(-)
 create mode 100644 drivers/net/ethernet/mediatek/mtk_wed_ccif.c
 create mode 100644 drivers/net/ethernet/mediatek/mtk_wed_ccif.h
 create mode 100644 drivers/net/ethernet/mediatek/mtk_wed_mcu.c
 create mode 100644 drivers/net/ethernet/mediatek/mtk_wed_mcu.h
 create mode 100644 drivers/net/ethernet/mediatek/mtk_wed_wo.c
 create mode 100644 drivers/net/ethernet/mediatek/mtk_wed_wo.h

diff --git a/include/linux/soc/mediatek/mtk_wed.h b/include/linux/soc/mediatek/mtk_wed.h
index 2859cc9..009fb02 100644
--- a/include/linux/soc/mediatek/mtk_wed.h
+++ b/include/linux/soc/mediatek/mtk_wed.h
@@ -7,10 +7,45 @@
 #include <linux/pci.h>
 
 #define MTK_WED_TX_QUEUES		2
+#define MTK_WED_RX_QUEUES		2
+
+enum wo_cmd_id {
+	WO_CMD_WED_CFG,
+	WO_CMD_WED_RX_STAT,
+	WO_CMD_RRO_SER,
+	WO_CMD_DBG_INFO,
+	WO_CMD_DEV_INFO,
+	WO_CMD_BSS_INFO,
+	WO_CMD_STA_REC,
+	WO_CMD_DEV_INFO_DUMP,
+	WO_CMD_BSS_INFO_DUMP,
+	WO_CMD_STA_REC_DUMP,
+	WO_CMD_BA_INFO_DUMP,
+	WO_CMD_FBCMD_Q_DUMP,
+	WO_CMD_FW_LOG_CTRL,
+	WO_CMD_LOG_FLUSH,
+	WO_CMD_CHANGE_STATE,
+	WO_CMD_CPU_STATS_ENABLE,
+	WO_CMD_CPU_STATS_DUMP,
+	WO_CMD_EXCEPTION_INIT,
+	WO_CMD_PROF_CTRL,
+	WO_CMD_STA_BA_DUMP,
+	WO_CMD_BA_CTRL_DUMP,
+	WO_CMD_RXCNT_CTRL,
+	WO_CMD_RXCNT_INFO,
+	WO_CMD_SET_CAP,
+	WO_CMD_CCIF_RING_DUMP,
+	WO_CMD_WED_END
+};
 
 struct mtk_wed_hw;
 struct mtk_wdma_desc;
 
+struct mtk_rxbm_desc {
+	__le32 buf0;
+	__le32 token;
+} __packed __aligned(4);
+
 struct mtk_wed_ring {
 	struct mtk_wdma_desc *desc;
 	dma_addr_t desc_phys;
@@ -20,37 +55,72 @@ struct mtk_wed_ring {
 	void __iomem *wpdma;
 };
 
+struct dma_buf {
+	int size;
+	void **pages;
+	struct mtk_wdma_desc *desc;
+	dma_addr_t desc_phys;
+};
+
+struct dma_entry {
+	int size;
+	struct mtk_rxbm_desc *desc;
+	dma_addr_t desc_phys;
+};
+
 struct mtk_wed_device {
 #ifdef CONFIG_NET_MEDIATEK_SOC_WED
 	const struct mtk_wed_ops *ops;
 	struct mtk_wed_hw *hw;
 	bool init_done, running, wdma_init;
+	bool wdma_rx_init, wdma_tx_init;
 	int wdma_idx;
 	int irq;
 
 	struct mtk_wed_ring tx_ring[MTK_WED_TX_QUEUES];
 	struct mtk_wed_ring txfree_ring;
 	struct mtk_wed_ring tx_wdma[MTK_WED_TX_QUEUES];
+	struct mtk_wed_ring rx_ring[MTK_WED_RX_QUEUES];
+	struct mtk_wed_ring rx_wdma[MTK_WED_RX_QUEUES];
+
+	struct dma_buf buf_ring;
+	struct dma_entry rx_buf_ring;
+	struct page_frag_cache rx_page;
 
 	struct {
-		int size;
-		void **pages;
-		struct mtk_wdma_desc *desc;
-		dma_addr_t desc_phys;
-	} buf_ring;
+		struct mtk_wed_ring rro_ring;
+		void __iomem *rro_desc;
+		dma_addr_t miod_desc_phys;
+		dma_addr_t fdbk_desc_phys;
+		u32 mcu_view_miod;
+	} rro;
 
 	/* filled by driver: */
 	struct {
 		struct pci_dev *pci_dev;
 
-		u32 wpdma_phys;
+		union {
+			u32 wpdma_phys;
+			u32 wpdma_int;
+		};
+		void __iomem *base;
+		u32 wpdma_mask;
 		u32 wpdma_tx;
 		u32 wpdma_txfree;
+		u32 wpdma_rx_glo;
+		u32 wpdma_rx;
 
 		u16 token_start;
 		unsigned int nbuf;
+		unsigned int rx_nbuf;
+		unsigned int rx_pkt;
+		unsigned int rx_pkt_size;
 
 		u32 (*init_buf)(void *ptr, dma_addr_t phys, int token_id);
+		u32 (*init_rx_buf)(struct mtk_wed_device *wed,
+				   int pkt_num);
+		void (*release_rx_buf)(struct mtk_wed_device *wed);
+		void (*rx_stat)(struct mtk_wed_device *wed, void *wo_rxcnt);
 	} wlan;
 #endif
 };
@@ -61,8 +131,11 @@ struct mtk_wed_ops {
 			     void __iomem *regs);
 	int (*txfree_ring_setup)(struct mtk_wed_device *dev,
 				 void __iomem *regs);
+	int (*rx_ring_setup)(struct mtk_wed_device *dev, int ring,
+				 void __iomem *regs);
+	int (*msg_update)(struct mtk_wed_device *dev, int cmd_id,
+			  void *data, int len);
 	void (*detach)(struct mtk_wed_device *dev);
-
 	void (*stop)(struct mtk_wed_device *dev);
 	void (*start)(struct mtk_wed_device *dev, u32 irq_mask);
 	void (*reset_dma)(struct mtk_wed_device *dev);
@@ -103,6 +176,10 @@ mtk_wed_device_attach(struct mtk_wed_device *dev)
 	(_dev)->ops->tx_ring_setup(_dev, _ring, _regs)
 #define mtk_wed_device_txfree_ring_setup(_dev, _regs) \
 	(_dev)->ops->txfree_ring_setup(_dev, _regs)
+#define mtk_wed_device_rx_ring_setup(_dev, _ring, _regs) \
+	(_dev)->ops->rx_ring_setup(_dev, _ring, _regs)
+#define mtk_wed_device_update_msg(_dev, _id, _msg, _len) \
+	(_dev)->ops->msg_update(_dev, _id, _msg, _len)
 #define mtk_wed_device_reg_read(_dev, _reg) \
 	(_dev)->ops->reg_read(_dev, _reg)
 #define mtk_wed_device_reg_write(_dev, _reg, _val) \
@@ -118,6 +195,8 @@ mtk_wed_device_attach(struct mtk_wed_device *dev)
 #define mtk_wed_device_start(_dev, _mask) do {} while (0)
 #define mtk_wed_device_tx_ring_setup(_dev, _ring, _regs) -ENODEV
 #define mtk_wed_device_txfree_ring_setup(_dev, _ring, _regs) -ENODEV
+#define mtk_wed_device_rx_ring_setup(_dev, _ring, _regs) -ENODEV
+#define mtk_wed_device_update_msg(_dev, _id, _msg, _len) -ENODEV
 #define mtk_wed_device_reg_read(_dev, _reg) 0
 #define mtk_wed_device_reg_write(_dev, _reg, _val) do {} while (0)
 #define mtk_wed_device_irq_get(_dev, _mask) 0
diff --git a/drivers/net/ethernet/mediatek/Makefile b/drivers/net/ethernet/mediatek/Makefile
index 92d6da9..fbbe2d7 100644
--- a/drivers/net/ethernet/mediatek/Makefile
+++ b/drivers/net/ethernet/mediatek/Makefile
@@ -10,4 +10,4 @@ mtk_eth-$(CONFIG_NET_MEDIATEK_SOC_WED) += mtk_wed.o
 ifdef CONFIG_DEBUG_FS
 mtk_eth-$(CONFIG_NET_MEDIATEK_SOC_WED) += mtk_wed_debugfs.o
 endif
-obj-$(CONFIG_NET_MEDIATEK_SOC_WED) += mtk_wed_ops.o
+obj-$(CONFIG_NET_MEDIATEK_SOC_WED) += mtk_wed_ops.o mtk_wed_wo.o mtk_wed_mcu.o mtk_wed_ccif.o
diff --git a/drivers/net/ethernet/mediatek/mtk_ppe_offload.c b/drivers/net/ethernet/mediatek/mtk_ppe_offload.c
index efd565a..614b4b7 100644
--- a/drivers/net/ethernet/mediatek/mtk_ppe_offload.c
+++ b/drivers/net/ethernet/mediatek/mtk_ppe_offload.c
@@ -163,9 +163,9 @@ mtk_flow_set_output_device(struct mtk_eth *eth, struct mtk_foe_entry *foe,
 		break;
 	}
 
-	if (dev == eth->netdev[0])
+	if (dev == eth->netdev[0] || (!strncmp(dev->name, "lan", 3)))
 		pse_port = 1;
-	else if (dev == eth->netdev[1])
+	else if (dev == eth->netdev[1] || (!strncmp(dev->name, "wan", 3)))
 		pse_port = 2;
 	else
 		return -EOPNOTSUPP;
diff --git a/drivers/net/ethernet/mediatek/mtk_wed.c b/drivers/net/ethernet/mediatek/mtk_wed.c
index 683a006..f5c50c6 100644
--- a/drivers/net/ethernet/mediatek/mtk_wed.c
+++ b/drivers/net/ethernet/mediatek/mtk_wed.c
@@ -8,13 +8,18 @@
 #include <linux/dma-mapping.h>
 #include <linux/skbuff.h>
 #include <linux/of_platform.h>
+#include <linux/of_address.h>
 #include <linux/mfd/syscon.h>
 #include <linux/debugfs.h>
 #include <linux/iopoll.h>
+#include <linux/kernel.h>
+#include <linux/io.h>
+#include <linux/of.h>
 #include <linux/soc/mediatek/mtk_wed.h>
 #include "mtk_wed_regs.h"
 #include "mtk_wed.h"
 #include "mtk_ppe.h"
+#include "mtk_wed_mcu.h"
 
 static struct mtk_wed_hw *hw_list[2];
 static DEFINE_MUTEX(hw_lock);
@@ -71,6 +76,48 @@ mtk_wed_reset(struct mtk_wed_device *dev, u32 mask)
 	WARN_ON_ONCE(1);
 }
 
+static void
+mtk_wed_wo_reset(struct mtk_wed_device *dev)
+{
+	struct mtk_wed_wo *wo = dev->hw->wed_wo;
+	u8 state = WO_STATE_DISABLE;
+	u8 state_done = WOIF_DISABLE_DONE;
+	void __iomem *reg;
+	u32 value;
+	unsigned long timeout = jiffies + WOCPU_TIMEOUT;
+
+	mtk_wed_mcu_send_msg(wo, MODULE_ID_WO, WO_CMD_CHANGE_STATE,
+			     &state, sizeof(state), false);
+
+	do {
+		value = wed_r32(dev, MTK_WED_SCR0 + 4 * WED_DUMMY_CR_WO_STATUS);
+	} while (value != state_done && !time_after(jiffies, timeout));
+
+	reg = ioremap(WOCPU_MCUSYS_RESET_ADDR, 4);
+	value = readl((void *)reg);
+	switch(dev->hw->index) {
+	case 0:
+		value |= WOCPU_WO0_MCUSYS_RESET_MASK;
+		writel(value, (void *)reg);
+		value &= ~WOCPU_WO0_MCUSYS_RESET_MASK;
+		writel(value, (void *)reg);
+		break;
+	case 1:
+		value |= WOCPU_WO1_MCUSYS_RESET_MASK;
+		writel(value, (void *)reg);
+		value &= ~WOCPU_WO1_MCUSYS_RESET_MASK;
+		writel(value, (void *)reg);
+		break;
+	default:
+		dev_err(dev->hw->dev, "wrong mtk_wed%d\n",
+			dev->hw->index);
+
+		break;
+	}
+
+	iounmap((void *)reg);
+}
+
 static struct mtk_wed_hw *
 mtk_wed_assign(struct mtk_wed_device *dev)
 {
@@ -200,25 +247,126 @@ free_pagelist:
 	kfree(page_list);
 }
 
+static int
+mtk_wed_rx_bm_alloc(struct mtk_wed_device *dev)
+{
+	struct mtk_rxbm_desc *desc;
+	dma_addr_t desc_phys;
+	int ring_size;
+
+	ring_size = dev->wlan.rx_nbuf;
+	dev->rx_buf_ring.size = ring_size;
+	desc = dma_alloc_coherent(dev->hw->dev, ring_size * sizeof(*desc),
+				  &desc_phys, GFP_KERNEL);
+	if (!desc)
+		return -ENOMEM;
+
+	dev->rx_buf_ring.desc = desc;
+	dev->rx_buf_ring.desc_phys = desc_phys;
+
+	dev->wlan.init_rx_buf(dev, dev->wlan.rx_pkt);
+	return 0;
+}
+
+//todo
 static void
-mtk_wed_free_ring(struct mtk_wed_device *dev, struct mtk_wed_ring *ring)
+mtk_wed_free_rx_bm(struct mtk_wed_device *dev)
+{
+	struct mtk_rxbm_desc *desc = dev->rx_buf_ring.desc;
+	int ring_size =dev->rx_buf_ring.size;
+
+	if (!desc)
+		return;
+
+	dev->wlan.release_rx_buf(dev);
+
+	dma_free_coherent(dev->hw->dev, ring_size * sizeof(*desc),
+			  desc, dev->buf_ring.desc_phys);
+}
+
+static void
+mtk_wed_free_ring(struct mtk_wed_device *dev, struct mtk_wed_ring *ring, int scale)
 {
 	if (!ring->desc)
 		return;
 
-	dma_free_coherent(dev->hw->dev, ring->size * sizeof(*ring->desc),
+	dma_free_coherent(dev->hw->dev, ring->size * sizeof(*ring->desc) * scale,
 			  ring->desc, ring->desc_phys);
 }
 
 static void
 mtk_wed_free_tx_rings(struct mtk_wed_device *dev)
 {
+	int scale =1;
 	int i;
 
+	if (dev->hw->ver == 2)
+		scale = 2;
+
 	for (i = 0; i < ARRAY_SIZE(dev->tx_ring); i++)
-		mtk_wed_free_ring(dev, &dev->tx_ring[i]);
+		mtk_wed_free_ring(dev, &dev->tx_ring[i], 1);
 	for (i = 0; i < ARRAY_SIZE(dev->tx_wdma); i++)
-		mtk_wed_free_ring(dev, &dev->tx_wdma[i]);
+		mtk_wed_free_ring(dev, &dev->tx_wdma[i], scale);
+}
+
+static void
+mtk_wed_free_rx_rings(struct mtk_wed_device *dev)
+{
+	mtk_wed_free_rx_bm(dev);
+	mtk_wed_free_ring(dev, &dev->rro.rro_ring, 1);
+}
+
+static void
+mtk_wed_set_int(struct mtk_wed_device *dev, u32 irq_mask)
+{
+	u32 wdma_mask;
+
+	wdma_mask = FIELD_PREP(MTK_WDMA_INT_MASK_RX_DONE, GENMASK(1, 0)) |
+		    FIELD_PREP(MTK_WDMA_INT_MASK_TX_DONE, GENMASK(1, 0));
+
+	/* wed control cr set */
+	wed_set(dev, MTK_WED_CTRL,
+		MTK_WED_CTRL_WDMA_INT_AGENT_EN |
+		MTK_WED_CTRL_WPDMA_INT_AGENT_EN |
+		MTK_WED_CTRL_WED_TX_BM_EN |
+		MTK_WED_CTRL_WED_TX_FREE_AGENT_EN);
+
+	/* initail tx interrupt trigger */
+	wed_w32(dev, MTK_WED_WPDMA_INT_CTRL_TX,
+		MTK_WED_WPDMA_INT_CTRL_TX0_DONE_EN |
+		MTK_WED_WPDMA_INT_CTRL_TX0_DONE_CLR |
+		MTK_WED_WPDMA_INT_CTRL_TX1_DONE_EN |
+		MTK_WED_WPDMA_INT_CTRL_TX1_DONE_CLR |
+		FIELD_PREP(MTK_WED_WPDMA_INT_CTRL_TX0_DONE_TRIG, 30) |
+		FIELD_PREP(MTK_WED_WPDMA_INT_CTRL_TX1_DONE_TRIG, 31));
+
+	/* initail txfree interrupt trigger */
+	wed_w32(dev, MTK_WED_WPDMA_INT_CTRL_TX_FREE,
+		MTK_WED_WPDMA_INT_CTRL_TX0_FREE_DONE_EN |
+		MTK_WED_WPDMA_INT_CTRL_TX0_FREE_DONE_CLR |
+		FIELD_PREP(MTK_WED_WPDMA_INT_CTRL_TX0_FREE_DONE_TRIG, 2));
+
+	/* initail rx interrupt trigger */
+	wed_w32(dev, MTK_WED_WPDMA_INT_CTRL_RX,
+		MTK_WED_WPDMA_INT_CTRL_RX0_EN |
+		MTK_WED_WPDMA_INT_CTRL_RX0_CLR |
+		MTK_WED_WPDMA_INT_CTRL_RX1_EN |
+		MTK_WED_WPDMA_INT_CTRL_RX1_CLR |
+		FIELD_PREP(MTK_WED_WPDMA_INT_CTRL_RX0_DONE_TRIG, 22) |
+		FIELD_PREP(MTK_WED_WPDMA_INT_CTRL_RX1_DONE_TRIG, 23));
+
+	/* initail wdma interrupt agent */
+	wed_w32(dev, MTK_WED_WDMA_INT_TRIGGER, wdma_mask);
+	wed_w32(dev, MTK_WED_WDMA_INT_CLR, wdma_mask);
+
+	wed_set(dev, MTK_WED_WDMA_INT_CTRL, dev->wdma_idx);
+
+	wdma_w32(dev, MTK_WDMA_INT_MASK, wdma_mask);
+	wdma_w32(dev, MTK_WDMA_INT_GRP2, wdma_mask);
+
+	wed_w32(dev, MTK_WED_WPDMA_INT_MASK, irq_mask);
+	wed_w32(dev, MTK_WED_INT_MASK, irq_mask);
+
 }
 
 static void
@@ -226,59 +374,75 @@ mtk_wed_set_ext_int(struct mtk_wed_device *dev, bool en)
 {
 	wed_w32(dev, MTK_WED_EXT_INT_MASK,
 		en ? MTK_WED_EXT_INT_STATUS_ERROR_MASK : 0);
+	wed_w32(dev, MTK_WED_EXT_INT_MASK1,
+		en ? MTK_WED_EXT_INT_STATUS_WPDMA_MID_RDY : 0);
+	wed_w32(dev, MTK_WED_EXT_INT_MASK2,
+		en ? MTK_WED_EXT_INT_STATUS_WPDMA_MID_RDY : 0);
+
 	wed_r32(dev, MTK_WED_EXT_INT_MASK);
+	wed_r32(dev, MTK_WED_EXT_INT_MASK1);
+	wed_r32(dev, MTK_WED_EXT_INT_MASK2);
 }
 
 static void
 mtk_wed_set_512_support(struct mtk_wed_device *dev, bool en)
 {
-
-	u32 value;
 	if (en) {
+		wed_w32(dev, MTK_WED_TXDP_CTRL, MTK_WED_TXDP_DW9_OVERWR);
+		wed_w32(dev, MTK_WED_TXP_DW1,
+			FIELD_PREP(MTK_WED_WPDMA_WRITE_TXP, 0x0103));
+	} else {
+		wed_w32(dev, MTK_WED_TXP_DW1,
+			FIELD_PREP(MTK_WED_WPDMA_WRITE_TXP, 0x0100));
+		wed_clr(dev, MTK_WED_TXDP_CTRL, MTK_WED_TXDP_DW9_OVERWR);
+	}
+}
 
-		value |= BIT(9);
-		wed_w32(dev, MTK_WED_TXDP_CTRL, value);
+static void
+mtk_wed_dma_disable(struct mtk_wed_device *dev)
+{
+	wed_clr(dev, MTK_WED_WPDMA_RX_D_GLO_CFG,
+		MTK_WED_WPDMA_RX_D_RX_DRV_EN);
 
-		value &= (0x0000ffff);
-		value |= (0x0103 << 16);
-		wed_w32(dev, MTK_WED_TXP_DW1, value);
-	} else {
+	wed_clr(dev, MTK_WED_WPDMA_GLO_CFG,
+		MTK_WED_WPDMA_GLO_CFG_TX_DRV_EN |
+		MTK_WED_WPDMA_GLO_CFG_RX_DRV_EN |
+		MTK_WED_WPDMA_GLO_CFG_RX_DRV_R0_PKT_PROC |
+		MTK_WED_WPDMA_GLO_CFG_RX_DRV_R0_CRX_SYNC);
 
-		value &= (0x0000ffff);
-		value |= (0x0100 << 16);
-		wed_w32(dev, MTK_WED_TXP_DW1, value);
+	wed_clr(dev, MTK_WED_WDMA_GLO_CFG,
+		MTK_WED_WDMA_GLO_CFG_RX_DRV_EN |
+		MTK_WED_WDMA_GLO_CFG_TX_DDONE_CHK);
 
-		value &= ~(1 << 9);
-		wed_w32(dev, MTK_WED_TXDP_CTRL, value);
-	}
+	wed_clr(dev, MTK_WED_GLO_CFG,
+		MTK_WED_GLO_CFG_TX_DMA_EN |
+		MTK_WED_GLO_CFG_RX_DMA_EN);
 
+	wdma_clr(dev, MTK_WDMA_GLO_CFG,
+		 MTK_WDMA_GLO_CFG_TX_DMA_EN |
+		 MTK_WDMA_GLO_CFG_RX_INFO1_PRERES |
+		 MTK_WDMA_GLO_CFG_RX_INFO2_PRERES);
 }
 
-
 static void
 mtk_wed_stop(struct mtk_wed_device *dev)
 {
+	mtk_wed_dma_disable(dev);
+
+	mtk_wed_set_512_support(dev, false);
 	mtk_wed_set_ext_int(dev, false);
 
 	wed_clr(dev, MTK_WED_CTRL,
 		MTK_WED_CTRL_WDMA_INT_AGENT_EN |
 		MTK_WED_CTRL_WPDMA_INT_AGENT_EN |
 		MTK_WED_CTRL_WED_TX_BM_EN |
-		MTK_WED_CTRL_WED_TX_FREE_AGENT_EN);
-	wed_w32(dev, MTK_WED_WPDMA_INT_TRIGGER, 0);
+		MTK_WED_CTRL_WED_TX_FREE_AGENT_EN |
+		MTK_WED_CTRL_WED_RX_BM_EN);
+
 	wed_w32(dev, MTK_WED_WDMA_INT_TRIGGER, 0);
 	wdma_w32(dev, MTK_WDMA_INT_MASK, 0);
 	wdma_w32(dev, MTK_WDMA_INT_GRP2, 0);
 	wed_w32(dev, MTK_WED_WPDMA_INT_MASK, 0);
-
-	wed_clr(dev, MTK_WED_GLO_CFG,
-		MTK_WED_GLO_CFG_TX_DMA_EN |
-		MTK_WED_GLO_CFG_RX_DMA_EN);
-	wed_clr(dev, MTK_WED_WPDMA_GLO_CFG,
-		MTK_WED_WPDMA_GLO_CFG_TX_DRV_EN |
-		MTK_WED_WPDMA_GLO_CFG_RX_DRV_EN);
-	wed_clr(dev, MTK_WED_WDMA_GLO_CFG,
-		MTK_WED_WDMA_GLO_CFG_RX_DRV_EN);
 }
 
 static void
@@ -294,9 +458,15 @@ mtk_wed_detach(struct mtk_wed_device *dev)
 	wdma_w32(dev, MTK_WDMA_RESET_IDX, 0);
 
 	mtk_wed_reset(dev, MTK_WED_RESET_WED);
+	mtk_wed_wo_reset(dev);
+
+	wdma_clr(dev, MTK_WDMA_GLO_CFG, MTK_WDMA_GLO_CFG_TX_DMA_EN);
+	wdma_w32(dev, MTK_WDMA_RESET_IDX, MTK_WDMA_RESET_IDX_TX);
+	wdma_w32(dev, MTK_WDMA_RESET_IDX, 0);
 
 	mtk_wed_free_buffer(dev);
 	mtk_wed_free_tx_rings(dev);
+	mtk_wed_free_rx_rings(dev);
 
 	memset(dev, 0, sizeof(*dev));
 	module_put(THIS_MODULE);
@@ -315,6 +485,20 @@ mtk_wed_bus_init(struct mtk_wed_device *dev)
 	return;
 }
 
+static void
+mtk_wed_set_wpdma(struct mtk_wed_device *dev)
+{
+	/* tx wpdma */
+	wed_w32(dev, MTK_WED_WPDMA_CFG_INT_STATUS,  dev->wlan.wpdma_int);
+	wed_w32(dev, MTK_WED_WPDMA_CFG_INT_MASK,  dev->wlan.wpdma_mask);
+	wed_w32(dev, MTK_WED_WPDMA_CFG_TX,  dev->wlan.wpdma_tx);
+	wed_w32(dev, MTK_WED_WPDMA_CFG_TX_FREE,  dev->wlan.wpdma_txfree);
+
+	/* WPDMA_RX_D_DRV WPDMA mapping */
+	wed_w32(dev, MTK_WED_WPDMA_RX_GLO_CFG,  dev->wlan.wpdma_rx_glo);
+	wed_w32(dev, MTK_WED_WPDMA_RX_RING,  dev->wlan.wpdma_rx);
+}
+
 static void
 mtk_wed_hw_init_early(struct mtk_wed_device *dev)
 {
@@ -324,14 +508,12 @@ mtk_wed_hw_init_early(struct mtk_wed_device *dev)
 	mtk_wed_reset(dev, MTK_WED_RESET_WED);
 
 	mtk_wed_bus_init(dev);
-	wed_w32(dev, MTK_WED_WPDMA_CFG_INT_STATUS,  dev->wlan.wpdma_phys);
-	wed_w32(dev, MTK_WED_WPDMA_CFG_INT_MASK,  dev->wlan.wpdma_phys + 0x4);
-	wed_w32(dev, MTK_WED_WPDMA_CFG_TX,  dev->wlan.wpdma_tx);
-	wed_w32(dev, MTK_WED_WPDMA_CFG_TX_FREE,  dev->wlan.wpdma_txfree);
+	mtk_wed_set_wpdma(dev);
 
 	mask = MTK_WED_WDMA_GLO_CFG_BT_SIZE |
 	       MTK_WED_WDMA_GLO_CFG_DYNAMIC_DMAD_RECYCLE |
 	       MTK_WED_WDMA_GLO_CFG_RX_DIS_FSM_AUTO_IDLE;
+
 	set = FIELD_PREP(MTK_WED_WDMA_GLO_CFG_BT_SIZE, 2) |
 	      MTK_WED_WDMA_GLO_CFG_DYNAMIC_SKIP_DMAD_PREP |
 	      MTK_WED_WDMA_GLO_CFG_IDLE_DMAD_SUPPLY;
@@ -340,7 +522,7 @@ mtk_wed_hw_init_early(struct mtk_wed_device *dev)
 
 	wed_w32(dev, MTK_WED_WDMA_CFG_BASE, dev->hw->wdma_phy);
 
-	wed_w32(dev, MTK_WED_CTRL, MTK_WED_CTRL_ETH_DMAD_FMT);
+	wed_set(dev, MTK_WED_CTRL, MTK_WED_CTRL_ETH_DMAD_FMT);
 
 
 	wed_w32(dev, MTK_WED_WDMA_OFFSET0, 
@@ -348,20 +530,105 @@ mtk_wed_hw_init_early(struct mtk_wed_device *dev)
 		FIELD_PREP(MTK_WED_WDMA_OFST0_GLO_CFG, MTK_WDMA_GLO_CFG));
 
 	wed_w32(dev, MTK_WED_WDMA_OFFSET1,
-		FIELD_PREP(MTK_WED_WDMA_OFST1_TX_CTRL,MTK_WDMA_RING_TX(0)) |
+		FIELD_PREP(MTK_WED_WDMA_OFST1_TX_CTRL, MTK_WDMA_RING_TX(0)) |
 		FIELD_PREP(MTK_WED_WDMA_OFST1_RX_CTRL, MTK_WDMA_RING_RX(0)));
 }
 
 static void
-mtk_wed_hw_init(struct mtk_wed_device *dev)
+mtk_wed_rx_data_ring_init_hw(struct mtk_wed_device *dev)
 {
-	if (dev->init_done)
-		return;
+	wed_w32(dev, MTK_WED_WPDMA_RX_D_RST_IDX,
+		MTK_WED_WPDMA_RX_D_RST_CRX_IDX0 |
+		MTK_WED_WPDMA_RX_D_RST_CRX_IDX1 |
+		MTK_WED_WPDMA_RX_D_RST_DRV_IDX0 |
+		MTK_WED_WPDMA_RX_D_RST_DRV_IDX1);
 
-	dev->init_done = true;
+	wed_w32(dev, MTK_WED_WPDMA_RX_D_RST_IDX, 0);
+}
 
-	mtk_wed_set_ext_int(dev, false);
+static void
+mtk_wed_rx_bm_init_hw(struct mtk_wed_device *dev)
+{
+	wed_w32(dev, MTK_WED_RX_BM_RX_DMAD,
+		FIELD_PREP(MTK_WED_RX_BM_RX_DMAD_SDL0,  dev->wlan.rx_pkt_size));
 
+	wed_w32(dev, MTK_WED_RX_BM_BASE, dev->rx_buf_ring.desc_phys);
+
+	wed_w32(dev, MTK_WED_RX_BM_INIT_PTR, MTK_WED_RX_BM_INIT_SW_TAIL |
+		FIELD_PREP(MTK_WED_RX_BM_SW_TAIL, dev->wlan.rx_pkt));
+
+	wed_w32(dev, MTK_WED_RX_BM_DYN_ALLOC_TH,
+		FIELD_PREP(MTK_WED_RX_BM_DYN_ALLOC_TH_H, 0xffff));
+
+	wed_set(dev, MTK_WED_CTRL, MTK_WED_CTRL_WED_RX_BM_EN);
+}
+
+static void
+mtk_wed_rro_init_hw(struct mtk_wed_device *dev)
+{
+	/* mid/mod basic setting */
+	wed_w32(dev, MTK_WED_RROQM_MIOD_CFG,
+		FIELD_PREP(MTK_WED_RROQM_MIOD_MID_DW, 0x70 >> 2) |
+		FIELD_PREP(MTK_WED_RROQM_MIOD_MOD_DW, 0x10 >> 2) |
+		FIELD_PREP(MTK_WED_RROQM_MIOD_ENTRY_DW,
+			   MTK_WED_MIOD_ENTRY_CNT >> 2));
+
+	/* Specify the MID/MOD Ring */
+	wed_w32(dev, MTK_WED_RROQM_MIOD_CTRL0, dev->rro.miod_desc_phys);
+
+	wed_w32(dev, MTK_WED_RROQM_MIOD_CTRL1,
+		FIELD_PREP(MTK_WED_RROQM_MIOD_CNT, MTK_WED_MIOD_CNT));
+
+	/* Specify the Feedback Command Ring */
+	wed_w32(dev, MTK_WED_RROQM_FDBK_CTRL0, dev->rro.fdbk_desc_phys);
+
+	wed_w32(dev, MTK_WED_RROQM_FDBK_CTRL1,
+		FIELD_PREP(MTK_WED_RROQM_FDBK_CNT, MTK_WED_FB_CMD_CNT));
+
+	wed_w32(dev, MTK_WED_RROQM_FDBK_CTRL2, 0);
+
+	/* Specify the RRO Queue */
+	wed_w32(dev, MTK_WED_RROQ_BASE_L, dev->rro.rro_ring.desc_phys);
+
+	/* Reset the Index of Rings */
+	wed_set(dev, MTK_WED_RROQM_RST_IDX,
+		MTK_WED_RROQM_RST_IDX_MIOD |
+		MTK_WED_RROQM_RST_IDX_FDBK);
+
+	wed_w32(dev, MTK_WED_RROQM_RST_IDX, 0);
+
+	wed_w32(dev, MTK_WED_RROQM_MIOD_CTRL2, MTK_WED_MIOD_CNT -1);
+
+	wed_set(dev, MTK_WED_CTRL, MTK_WED_CTRL_RX_RRO_QM_EN);
+}
+
+static void
+mtk_wed_route_qm_init_hw(struct mtk_wed_device *dev)
+{
+	wed_w32(dev, MTK_WED_RESET, MTK_WED_RESET_RX_ROUTE_QM);
+
+	do {
+		udelay(100);
+
+		if (!(wed_r32(dev, MTK_WED_RESET) & MTK_WED_RESET_RX_ROUTE_QM))
+			break;
+	} while (1);
+
+	/* configure RX_ROUTE_QM */
+	wed_clr(dev, MTK_WED_RTQM_GLO_CFG, MTK_WED_RTQM_Q_RST);
+	wed_clr(dev, MTK_WED_RTQM_GLO_CFG, MTK_WED_RTQM_TXDMAD_FPORT);
+	wed_set(dev, MTK_WED_RTQM_GLO_CFG,
+		FIELD_PREP(MTK_WED_RTQM_TXDMAD_FPORT, 0x3));
+	wed_clr(dev, MTK_WED_RTQM_GLO_CFG, MTK_WED_RTQM_Q_RST);
+
+	/* enable RX_ROUTE_QM */
+	wed_set(dev, MTK_WED_CTRL, MTK_WED_CTRL_RX_ROUTE_QM_EN);
+}
+
+static void
+mtk_wed_tx_hw_init(struct mtk_wed_device *dev)
+{
+	/* initial txbm setting */
 	wed_w32(dev, MTK_WED_TX_BM_CTRL,
 		MTK_WED_TX_BM_CTRL_PAUSE |
 		FIELD_PREP(MTK_WED_TX_BM_CTRL_VLD_GRP_NUM,
@@ -400,38 +667,49 @@ mtk_wed_hw_init(struct mtk_wed_device *dev)
 
 	wed_clr(dev, MTK_WED_TX_TKID_CTRL, MTK_WED_TX_TKID_CTRL_PAUSE);
 
-	/* wed_dma_ctrl */
-	wed_set(dev, MTK_WED_WPDMA_CTRL,
-		MTK_WED_WPDMA_CTRL_SDL1_FIXED);
+}
 
-	wed_set(dev, MTK_WED_GLO_CFG,
-		MTK_WED_GLO_CFG_TX_DMA_EN |
-		MTK_WED_GLO_CFG_RX_DMA_EN);
+static void
+mtk_wed_rx_hw_init(struct mtk_wed_device *dev)
+{
+	mtk_wed_rx_data_ring_init_hw(dev);
+	mtk_wed_rx_bm_init_hw(dev);
+	mtk_wed_rro_init_hw(dev);
+	mtk_wed_route_qm_init_hw(dev);
+}
 
-	wed_set(dev, MTK_WED_WDMA_GLO_CFG,
-		MTK_WED_WDMA_GLO_CFG_RX_DRV_EN);
+static void
+mtk_wed_hw_init(struct mtk_wed_device *dev)
+{
+	if (dev->init_done)
+		return;
 
-	wed_set(dev, MTK_WED_WPDMA_GLO_CFG,
-		MTK_WED_WPDMA_GLO_CFG_TX_DRV_EN |
-		MTK_WED_WPDMA_GLO_CFG_RX_DRV_EN |
-		MTK_WED_WPDMA_GLO_CFG_RX_DRV_R0_PKT_PROC |
-		MTK_WED_WPDMA_GLO_CFG_RX_DRV_R0_CRX_SYNC);
+	dev->init_done = true;
 
-	wed_clr(dev, MTK_WED_WPDMA_GLO_CFG,
-		MTK_WED_WPDMA_GLO_CFG_TX_TKID_KEEP |
-		MTK_WED_WPDMA_GLO_CFG_TX_DMAD_DW3_PREV);
+	mtk_wed_set_ext_int(dev, false);
+
+	mtk_wed_tx_hw_init(dev);
+
+	mtk_wed_rx_hw_init(dev);
 }
 
 static void
-mtk_wed_ring_reset(struct mtk_wdma_desc *desc, int size)
+mtk_wed_ring_reset(struct mtk_wdma_desc *desc, int size, bool tx, int scale)
 {
+	__le32 ctrl;
 	int i;
 
+	if (tx)
+		ctrl = cpu_to_le32(MTK_WDMA_DESC_CTRL_DMA_DONE);
+	else
+		ctrl = cpu_to_le32(MTK_WFDMA_DESC_CTRL_TO_HOST);
+
 	for (i = 0; i < size; i++) {
-		desc[i].buf0 = 0;
-		desc[i].ctrl = cpu_to_le32(MTK_WDMA_DESC_CTRL_DMA_DONE);
-		desc[i].buf1 = 0;
-		desc[i].info = 0;
+		desc->buf0 = 0;
+		desc->ctrl = ctrl;
+		desc->buf1 = 0;
+		desc->info = 0;
+		desc += scale;
 	}
 }
 
@@ -487,7 +765,7 @@ mtk_wed_reset_dma(struct mtk_wed_device *dev)
 		if (!desc)
 			continue;
 
-		mtk_wed_ring_reset(desc, MTK_WED_TX_RING_SIZE);
+		mtk_wed_ring_reset(desc, MTK_WED_TX_RING_SIZE, true, dev->hw->ver);
 	}
 
 	if (mtk_wed_poll_busy(dev))
@@ -543,38 +821,57 @@ mtk_wed_reset_dma(struct mtk_wed_device *dev)
 }
 
 static int
-mtk_wed_ring_alloc(struct mtk_wed_device *dev, struct mtk_wed_ring *ring,
+mtk_wed_rro_ring_alloc(struct mtk_wed_device *dev, struct mtk_wed_ring *ring,
 		   int size)
 {
 	ring->desc = dma_alloc_coherent(dev->hw->dev,
-					size * sizeof(*ring->desc) * 2,
+					size * sizeof(*ring->desc),
 					&ring->desc_phys, GFP_KERNEL);
 	if (!ring->desc)
 		return -ENOMEM;
 
 	ring->size = size;
-	mtk_wed_ring_reset(ring->desc, size);
+	memset(ring->desc, 0, size);
+	return 0;
+}
+
+static int
+mtk_wed_ring_alloc(struct mtk_wed_device *dev, struct mtk_wed_ring *ring,
+		   int size, int scale, bool tx)
+{
+
+
+	ring->desc = dma_alloc_coherent(dev->hw->dev,
+					size * sizeof(*ring->desc) * scale,
+					&ring->desc_phys, GFP_KERNEL);
+	if (!ring->desc)
+		return -ENOMEM;
+
+	ring->size = size;
+	mtk_wed_ring_reset(ring->desc, size, tx, scale);
 
 	return 0;
 }
 
 static int
-mtk_wed_wdma_ring_setup(struct mtk_wed_device *dev, int idx, int size)
+mtk_wed_wdma_rx_ring_setup(struct mtk_wed_device *dev, int idx, int size, int scale)
 {
-	if (dev->wdma_init)
+	if (dev->wdma_tx_init)
 		return 0;
 
 	for (idx = 0; idx < MTK_WED_TX_QUEUES; idx++) {
 		struct mtk_wed_ring *wdma = &dev->tx_wdma[idx];
 
-		if (mtk_wed_ring_alloc(dev, wdma, MTK_WED_WDMA_RING_SIZE))
+		if (mtk_wed_ring_alloc(dev, wdma,MTK_WED_WDMA_RING_SIZE,
+				       scale, true))
 			return -ENOMEM;
 
 		wdma_w32(dev, MTK_WDMA_RING_RX(idx) + MTK_WED_RING_OFS_BASE,
 			 wdma->desc_phys);
 		wdma_w32(dev, MTK_WDMA_RING_RX(idx) + MTK_WED_RING_OFS_COUNT,
 			 size);
-		wdma_w32(dev, MTK_WDMA_RING_RX(idx) + MTK_WED_RING_OFS_CPU_IDX, 0);
+		wdma_w32(dev,
+			 MTK_WDMA_RING_RX(idx) + MTK_WED_RING_OFS_CPU_IDX, 0);
 
 		wed_w32(dev, MTK_WED_WDMA_RING_RX(idx) + MTK_WED_RING_OFS_BASE,
 			wdma->desc_phys);
@@ -582,54 +879,202 @@ mtk_wed_wdma_ring_setup(struct mtk_wed_device *dev, int idx, int size)
 			size);
 	}
 
-	dev->wdma_init = true;
+	dev->wdma_tx_init = true;
+	return 0;
+}
+
+static int
+mtk_wed_wdma_tx_ring_setup(struct mtk_wed_device *dev, int idx, int size, int scale)
+{
+	if (dev->wdma_rx_init)
+		return 0;
+
+	for (idx = 0; idx < MTK_WED_RX_QUEUES; idx++) {
+		struct mtk_wed_ring *wdma = &dev->rx_wdma[idx];
+
+		if (mtk_wed_ring_alloc(dev, wdma, MTK_WED_WDMA_RING_SIZE, scale, true))
+			return -ENOMEM;
+
+		wdma_w32(dev, MTK_WDMA_RING_TX(idx) + MTK_WED_RING_OFS_BASE,
+			 wdma->desc_phys);
+		wdma_w32(dev, MTK_WDMA_RING_TX(idx) + MTK_WED_RING_OFS_COUNT,
+			 size);
+		wdma_w32(dev,
+			 MTK_WDMA_RING_TX(idx) + MTK_WED_RING_OFS_CPU_IDX, 0);
+		wdma_w32(dev,
+			 MTK_WDMA_RING_TX(idx) + MTK_WED_RING_OFS_DMA_IDX, 0);
+
+		if (idx == 0)  {
+			wed_w32(dev, MTK_WED_WDMA_RING_TX
+				+ MTK_WED_RING_OFS_BASE, wdma->desc_phys);
+			wed_w32(dev, MTK_WED_WDMA_RING_TX
+				+ MTK_WED_RING_OFS_COUNT, size);
+			wed_w32(dev, MTK_WED_WDMA_RING_TX
+				+ MTK_WED_RING_OFS_CPU_IDX, 0);
+			wed_w32(dev, MTK_WED_WDMA_RING_TX
+				+ MTK_WED_RING_OFS_DMA_IDX, 0);
+		}
+	}
+
+	dev->wdma_rx_init = true;
+
+	return 0;
+}
+
+static int
+mtk_wed_rro_alloc(struct mtk_wed_device *dev)
+{
+	struct mtk_wed_ring *ring;
+	struct device_node *np;
+	struct resource res;
+	int ret;
+
+	np = of_find_compatible_node(NULL, NULL, "mediatek,wocpu_dlm");
+
+	/* get MID/MOD from device tree */
+	ret = of_address_to_resource(np, dev->hw->index, &res);
+	if (ret)
+		return -1;
+
+	dev->rro.rro_desc = ioremap(res.start, resource_size(&res));
+
+	ring = &dev->rro.rro_ring;
+
+	dev->rro.miod_desc_phys = res.start;
+
+	dev->rro.mcu_view_miod = MTK_WED_WOCPU_VIEW_MIOD_BASE;
+	dev->rro.fdbk_desc_phys = MTK_WED_MIOD_ENTRY_CNT * MTK_WED_MIOD_CNT
+				  + dev->rro.miod_desc_phys;
+
+	if (mtk_wed_rro_ring_alloc(dev, ring, MTK_WED_RRO_QUE_CNT))
+		return -ENOMEM;
+
 	return 0;
 }
 
+static int
+mtk_wed_rro_cfg(struct mtk_wed_device *dev)
+{
+	struct mtk_wed_wo *wo = dev->hw->wed_wo;
+	struct {
+		struct wo_cmd_ring ring[2];
+
+		u32 wed;
+		u8 ver;
+	} req = {
+		.ring = {
+			[0] = {
+				.q_base = dev->rro.mcu_view_miod,
+				.cnt = MTK_WED_MIOD_CNT,
+				.unit = MTK_WED_MIOD_ENTRY_CNT,
+			},
+			[1] = {
+				.q_base = dev->rro.mcu_view_miod + MTK_WED_MIOD_ENTRY_CNT * MTK_WED_MIOD_CNT,
+				.cnt = MTK_WED_FB_CMD_CNT,
+				.unit = 4,
+			},
+		},
+		.wed = 0,
+	};
+
+	return mtk_wed_mcu_send_msg(wo, MODULE_ID_WO,
+				    WO_CMD_WED_CFG, &req, sizeof(req), true);
+}
+
+static int
+mtk_wed_send_msg(struct mtk_wed_device *dev, int cmd_id, void *data, int len)
+{
+	struct mtk_wed_wo *wo = dev->hw->wed_wo;
+
+	return mtk_wed_mcu_send_msg(wo, MODULE_ID_WO,
+				    cmd_id, data, len, true);
+}
+
 static void
-mtk_wed_start(struct mtk_wed_device *dev, u32 irq_mask)
+mtk_wed_dma_enable(struct mtk_wed_device *dev)
 {
-	u32 wdma_mask;
+#define MTK_WFMDA_RX_DMA_EN 	BIT(2)
 
-	wdma_mask = FIELD_PREP(MTK_WDMA_INT_MASK_RX_DONE, GENMASK(1, 0));
+	int idx = 0;
 
-	mtk_wed_hw_init(dev);
+	wed_set(dev, MTK_WED_WPDMA_CTRL,
+		MTK_WED_WPDMA_CTRL_SDL1_FIXED);
 
-	wed_set(dev, MTK_WED_CTRL,
-		MTK_WED_CTRL_WDMA_INT_AGENT_EN |
-		MTK_WED_CTRL_WPDMA_INT_AGENT_EN |
-		MTK_WED_CTRL_WED_TX_BM_EN |
-		MTK_WED_CTRL_WED_TX_FREE_AGENT_EN);
+	wed_set(dev, MTK_WED_GLO_CFG,
+		MTK_WED_GLO_CFG_RX_DMA_EN |
+		MTK_WED_GLO_CFG_TX_DMA_EN);
 
-	/* initail tx interrupt trigger */
-	wed_w32(dev, MTK_WED_WPDMA_INT_CTRL_TX,
-		MTK_WED_WPDMA_INT_CTRL_TX_DONE_EN0 |
-		MTK_WED_WPDMA_INT_CTRL_TX_DONE_CLR0 |
-		MTK_WED_WPDMA_INT_CTRL_TX_DONE_EN1 |
-		MTK_WED_WPDMA_INT_CTRL_TX_DONE_CLR1 |
-		FIELD_PREP(MTK_WED_WPDMA_INT_CTRL_TX_DONE_TRIG0, 30) |
-		FIELD_PREP(MTK_WED_WPDMA_INT_CTRL_TX_DONE_TRIG1, 31));
+	wed_set(dev, MTK_WED_WDMA_GLO_CFG,
+		MTK_WED_WDMA_GLO_CFG_RX_DRV_EN |
+		MTK_WED_WDMA_GLO_CFG_TX_DDONE_CHK);
 
-	wed_w32(dev, MTK_WED_WPDMA_INT_CTRL_TX_FREE,
-		MTK_WED_WPDMA_INT_CTRL_TX_FREE_DONE_EN0 |
-		MTK_WED_WPDMA_INT_CTRL_TX_FREE_DONE_CLR0 |
-		FIELD_PREP(MTK_WED_WPDMA_INT_CTRL_TX_FREE_DONE_TRIG0, 2));
 
-	wed_w32(dev, MTK_WED_WDMA_INT_TRIGGER, wdma_mask);
-	wed_w32(dev, MTK_WED_WDMA_INT_CLR, wdma_mask);
+	wed_set(dev, MTK_WED_WPDMA_GLO_CFG,
+		MTK_WED_WPDMA_GLO_CFG_TX_DRV_EN |
+		MTK_WED_WPDMA_GLO_CFG_RX_DRV_EN |
+		MTK_WED_WPDMA_GLO_CFG_RX_DRV_R0_PKT_PROC |
+		MTK_WED_WPDMA_GLO_CFG_RX_DRV_R0_CRX_SYNC);
 
-	wed_set(dev, MTK_WED_WDMA_INT_CTRL, dev->wdma_idx);
+	wed_clr(dev, MTK_WED_WPDMA_GLO_CFG,
+		MTK_WED_WPDMA_GLO_CFG_TX_TKID_KEEP |
+		MTK_WED_WPDMA_GLO_CFG_TX_DMAD_DW3_PREV);
 
-	wdma_w32(dev, MTK_WDMA_INT_MASK, wdma_mask);
-	wdma_w32(dev, MTK_WDMA_INT_GRP2, wdma_mask);
+	wed_set(dev, MTK_WED_WPDMA_RX_D_GLO_CFG,
+		MTK_WED_WPDMA_RX_D_RX_DRV_EN |
+		FIELD_PREP(MTK_WED_WPDMA_RX_D_RXD_READ_LEN, 0x18) |
+		FIELD_PREP(MTK_WED_WPDMA_RX_D_INIT_PHASE_RXEN_SEL, 0x2));
 
-	wed_w32(dev, MTK_WED_WPDMA_INT_MASK, irq_mask);
-	wed_w32(dev, MTK_WED_INT_MASK, irq_mask);
+	for (idx = 0; idx < MTK_WED_RX_QUEUES; idx++) {
+		int timeout = 3;
+		u32 cur_idx, regs;
+
+		do {
+			regs = MTK_WED_WPDMA_RING_RX_DATA(idx) + MTK_WED_RING_OFS_COUNT;
+			cur_idx = wed_r32(dev, regs);
+			if (cur_idx == MTK_WED_RX_RING_SIZE - 1)
+				break;
+
+			usleep_range(100000, 200000);
+		} while (timeout-- > 0);
+
+		if (timeout) {
+			unsigned int val;
+
+			val = wifi_r32(dev, dev->wlan.wpdma_rx_glo - 0x18000000);
+			val |= MTK_WFMDA_RX_DMA_EN;
+
+			wifi_w32(dev,  dev->wlan.wpdma_rx_glo - 0x18000000, val);
+		} else {
+			dev_err(dev->hw->dev, "mtk_wed%d: rx dma enable failed!\n",
+				       dev->hw->index);
+		}
+	}
+
+	wdma_set(dev, MTK_WDMA_GLO_CFG,
+		 MTK_WDMA_GLO_CFG_TX_DMA_EN |
+		 MTK_WDMA_GLO_CFG_RX_INFO1_PRERES |
+		 MTK_WDMA_GLO_CFG_RX_INFO2_PRERES);
+}
+
+static void
+mtk_wed_start(struct mtk_wed_device *dev, u32 irq_mask)
+{
+	int ret = 0;
+
+	mtk_wed_hw_init(dev);
+
+	mtk_wed_set_int(dev, irq_mask);
 
 	mtk_wed_set_ext_int(dev, true);
 
 	mtk_wed_set_512_support(dev, true);
 
+	ret = mtk_wed_rro_cfg(dev);
+	if (ret)
+		return;
+
+	mtk_wed_dma_enable(dev);
+
 	dev->running = true;
 }
 
@@ -656,13 +1101,26 @@ mtk_wed_attach(struct mtk_wed_device *dev)
 	dev->wdma_idx = hw->index;
 
 	ret = mtk_wed_buffer_alloc(dev);
-	if (ret) {
-		mtk_wed_detach(dev);
-		goto out;
-	}
+	if (ret)
+		goto error;
+
+	ret = mtk_wed_rx_bm_alloc(dev);
+	if (ret)
+		goto error;
+
+	ret = mtk_wed_rro_alloc(dev);
+	if (ret)
+		goto error;
+
+	hw->ver = FIELD_GET(MTK_WED_REV_ID_MAJOR,
+			    wed_r32(dev, MTK_WED_REV_ID));
 
 	mtk_wed_hw_init_early(dev);
+	ret = mtk_wed_wo_init(hw);
 
+error:
+	if (ret)
+		mtk_wed_detach(dev);
 out:
 	mutex_unlock(&hw_lock);
 
@@ -673,6 +1131,7 @@ static int
 mtk_wed_tx_ring_setup(struct mtk_wed_device *dev, int idx, void __iomem *regs)
 {
 	struct mtk_wed_ring *ring = &dev->tx_ring[idx];
+	int scale = 1;
 
 	/*
 	 * Tx ring redirection:
@@ -685,13 +1144,14 @@ mtk_wed_tx_ring_setup(struct mtk_wed_device *dev, int idx, void __iomem *regs)
 	 * It gets filled with packets picked up from WED TX ring and from
 	 * WDMA RX.
 	 */
+	if (dev->hw->ver == 2)
+		scale = 2;
 
 	BUG_ON(idx > ARRAY_SIZE(dev->tx_ring));
-
-	if (mtk_wed_ring_alloc(dev, ring, MTK_WED_TX_RING_SIZE))
+	if (mtk_wed_ring_alloc(dev, ring, MTK_WED_TX_RING_SIZE, 1, true))
 		return -ENOMEM;
 
-	if (mtk_wed_wdma_ring_setup(dev, idx, MTK_WED_WDMA_RING_SIZE))
+	if (mtk_wed_wdma_rx_ring_setup(dev, idx, MTK_WED_WDMA_RING_SIZE, scale))
 		return -ENOMEM;
 
 	ring->reg_base = MTK_WED_RING_TX(idx);
@@ -735,6 +1195,38 @@ mtk_wed_txfree_ring_setup(struct mtk_wed_device *dev, void __iomem *regs)
 	return 0;
 }
 
+static int
+mtk_wed_rx_ring_setup(struct mtk_wed_device *dev, int idx, void __iomem *regs)
+{
+	struct mtk_wed_ring *ring = &dev->rx_ring[idx];
+	int scale = 1;
+
+	BUG_ON(idx > ARRAY_SIZE(dev->rx_ring));
+
+	if (dev->hw->ver == 2)
+		scale = 2;
+
+	if (mtk_wed_ring_alloc(dev, ring, MTK_WED_RX_RING_SIZE, 1, false))
+		return -ENOMEM;
+
+	if (mtk_wed_wdma_tx_ring_setup(dev, idx, MTK_WED_WDMA_RING_SIZE, scale))
+		return -ENOMEM;
+
+	ring->reg_base = MTK_WED_RING_RX_DATA(idx);
+	ring->wpdma = regs;
+
+	/* WPDMA ->  WED */
+	wpdma_rx_w32(dev, idx, MTK_WED_RING_OFS_BASE, ring->desc_phys);
+	wpdma_rx_w32(dev, idx, MTK_WED_RING_OFS_COUNT, MTK_WED_RX_RING_SIZE);
+
+	wed_w32(dev, MTK_WED_WPDMA_RING_RX_DATA(idx) + MTK_WED_RING_OFS_BASE,
+		ring->desc_phys);
+	wed_w32(dev, MTK_WED_WPDMA_RING_RX_DATA(idx) + MTK_WED_RING_OFS_COUNT,
+		MTK_WED_RX_RING_SIZE);
+
+	return 0;
+}
+
 static u32
 mtk_wed_irq_get(struct mtk_wed_device *dev, u32 mask)
 {
@@ -759,7 +1251,6 @@ mtk_wed_irq_set_mask(struct mtk_wed_device *dev, u32 mask)
 	if (!dev->running)
 		return;
 
-	mtk_wed_set_ext_int(dev, !!mask);
 	wed_w32(dev, MTK_WED_INT_MASK, mask);
 }
 
@@ -770,6 +1261,8 @@ void mtk_wed_add_hw(struct device_node *np, void __iomem *wdma,
 		.attach = mtk_wed_attach,
 		.tx_ring_setup = mtk_wed_tx_ring_setup,
 		.txfree_ring_setup = mtk_wed_txfree_ring_setup,
+		.rx_ring_setup = mtk_wed_rx_ring_setup,
+		.msg_update = mtk_wed_send_msg,
 		.start = mtk_wed_start,
 		.stop = mtk_wed_stop,
 		.reset_dma = mtk_wed_reset_dma,
diff --git a/drivers/net/ethernet/mediatek/mtk_wed.h b/drivers/net/ethernet/mediatek/mtk_wed.h
index 7166450..5545e54 100644
--- a/drivers/net/ethernet/mediatek/mtk_wed.h
+++ b/drivers/net/ethernet/mediatek/mtk_wed.h
@@ -5,21 +5,35 @@
 #define __MTK_WED_PRIV_H
 
 #include <linux/soc/mediatek/mtk_wed.h>
+#include <linux/netdevice.h>
 #include <linux/debugfs.h>
 #include <linux/regmap.h>
+
 #define MTK_PCIE_BASE(n)		(0x1a143000 + (n) * 0x2000)
 
 #define MTK_WED_PKT_SIZE		1900
 #define MTK_WED_BUF_SIZE		2048
 #define MTK_WED_BUF_PER_PAGE		(PAGE_SIZE / 2048)
+#define MTK_WED_RX_RING_SIZE		1536
 
 #define MTK_WED_TX_RING_SIZE		2048
-#define MTK_WED_WDMA_RING_SIZE		512//1024
+#define MTK_WED_WDMA_RING_SIZE		512
 #define MTK_WED_MAX_GROUP_SIZE		0x100
 #define MTK_WED_VLD_GROUP_SIZE		0x40
 #define MTK_WED_PER_GROUP_PKT		128
 
 #define MTK_WED_FBUF_SIZE		128
+#define MTK_WED_MIOD_CNT		16
+#define MTK_WED_FB_CMD_CNT		1024
+#define MTK_WED_RRO_QUE_CNT		8192
+#define MTK_WED_MIOD_ENTRY_CNT		128
+struct mtk_wed_wo;
+struct wo_cmd_ring;
+enum {
+	MODULE_ID_WARP = 0,
+	MODULE_ID_WO = 1,
+	MAX_MODULE_ID
+};
 
 struct mtk_wed_hw {
 	struct device_node *node;
@@ -29,13 +43,23 @@ struct mtk_wed_hw {
 	struct regmap *mirror;
 	struct dentry *debugfs_dir;
 	struct mtk_wed_device *wed_dev;
+	struct mtk_wed_wo *wed_wo;
 	u32 debugfs_reg;
 	u32 num_flows;
 	u32 wdma_phy;
 	u32 token_id;
+	u32 rx_token_id;
+	u32 wdma_txd;
 	char dirname[5];
 	int irq;
 	int index;
+	u32 ver;
+};
+
+struct wo_cmd_ring {
+	u32 q_base;
+	u32 cnt;
+	u32 unit;
 };
 
 static inline void
@@ -54,6 +78,19 @@ wed_r32(struct mtk_wed_device *dev, u32 reg)
 	return val;
 }
 
+static inline u32
+wifi_r32(struct mtk_wed_device *dev, u32 reg)
+{
+	return readl(dev->wlan.base + reg);
+}
+
+static inline void
+wifi_w32(struct mtk_wed_device *dev, u32 reg, u32 val)
+{
+	writel(val, dev->wlan.base + reg);
+
+}
+
 static inline void
 wdma_w32(struct mtk_wed_device *dev, u32 reg, u32 val)
 {
@@ -102,20 +139,76 @@ wpdma_txfree_w32(struct mtk_wed_device *dev, u32 reg, u32 val)
 	writel(val, dev->txfree_ring.wpdma + reg);
 }
 
+static inline u32
+wpdma_rx_r32(struct mtk_wed_device *dev, int ring, u32 reg)
+{
+	if (!dev->rx_ring[ring].wpdma)
+		return 0;
+
+	return readl(dev->rx_ring[ring].wpdma + reg);
+}
+
+static inline void
+wpdma_rx_w32(struct mtk_wed_device *dev, int ring, u32 reg, u32 val)
+{
+	if (!dev->rx_ring[ring].wpdma)
+		return;
+
+	writel(val, dev->rx_ring[ring].wpdma + reg);
+}
+
 #ifdef CONFIG_NET_MEDIATEK_SOC_WED
 void mtk_wed_add_hw(struct device_node *np, void __iomem *wdma, u32 wdma_phy,
 		    void __iomem *mirror, int index);
 void mtk_wed_exit(void);
+void mtk_wed_wo_exit(struct mtk_wed_hw *hw);
+
+int mtk_wed_mcu_send_msg(struct mtk_wed_wo *wo,int to_id, int cmd,
+			const void *data, int len, bool wait_resp);
+int mtk_wed_mcu_cmd_sanity_check(struct mtk_wed_wo *wo, struct sk_buff *skb);
+void mtk_wed_mcu_rx_unsolicited_event(struct mtk_wed_wo *wo, struct sk_buff *skb);
+int wed_wo_mcu_init(struct mtk_wed_wo *wo);
+int mtk_wed_exception_init(struct mtk_wed_wo *wo);
+void wed_wo_mcu_debugfs(struct mtk_wed_hw *hw, struct dentry *dir);
+void mtk_wed_mcu_rx_event(struct mtk_wed_wo *wo, struct sk_buff *skb);
+int mtk_wed_wo_rx_poll(struct napi_struct *napi, int budget);
+int wed_wo_hardware_init(struct mtk_wed_wo *wo, void *isr);
+int mtk_wed_wo_init(struct mtk_wed_hw *hw);
 #else
 static inline void
-mtk_wed_add_hw(struct device_node *np, void __iomem *wdma, u32 wdma_phy,
-	       void __iomem *mirror, int index)
+mtk_wed_add_hw(struct device_node *np, void __iomem *wdma,
+	        u32 wdma_phy, void __iomem *mirror, int index)
 {
 }
+
 static inline void
 mtk_wed_exit(void)
 {
 }
+
+int mtk_wed_wo_init(struct mtk_wed_hw *hw)
+{
+	return -EINVAL;
+}
+
+void mtk_wed_wo_exit(struct mtk_wed_hw *hw)
+{
+}
+
+int mtk_wed_mcu_send_msg(struct mtk_wed_wo *wo,int to_id, int cmd,
+			const void *data, int len, bool wait_resp)
+{
+	return -EINVAL;
+}
+
+int mtk_wed_mcu_cmd_sanity_check(struct mtk_wed_wo *wo, struct sk_buff *skb)
+{
+	return -EINVAL;
+}
+
+void mtk_wed_mcu_rx_unsolicited_event(struct mtk_wed_wo *wo, struct sk_buff *skb)
+{
+}
 #endif
 
 #ifdef CONFIG_DEBUG_FS
diff --git a/drivers/net/ethernet/mediatek/mtk_wed_ccif.c b/drivers/net/ethernet/mediatek/mtk_wed_ccif.c
new file mode 100644
index 0000000..3718e98
--- /dev/null
+++ b/drivers/net/ethernet/mediatek/mtk_wed_ccif.c
@@ -0,0 +1,137 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/soc/mediatek/mtk_wed.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include "mtk_wed_ccif.h"
+#include "mtk_wed_regs.h"
+#include "mtk_wed_wo.h"
+
+static inline void woif_set_isr(struct mtk_wed_wo *wo, u32 mask)
+{
+	woccif_w32(wo, WOX_AP2WO_MCU_CCIF4_CPUA_PCCIF_IRQ0_MASK_ADDR, mask);
+}
+
+static inline u32 woif_get_csr(struct mtk_wed_wo *wo)
+{
+	u32 val;
+
+	val = woccif_r32(wo, WOX_AP2WO_MCU_CCIF4_CPUA_PCCIF_RCHNUM_ADDR);
+
+	return  val & WOX_AP2WO_MCU_CCIF4_CPUA_PCCIF_RCHNUM_MASK;
+}
+
+static inline void woif_set_ack(struct mtk_wed_wo *wo, u32 mask)
+{
+	woccif_w32(wo,
+		   WOX_AP2WO_MCU_CCIF4_CPUA_PCCIF_ACK_ADDR, mask);
+}
+
+static inline void woif_kickout_test(struct mtk_wed_wo *wo, bool busy)
+{
+	if(busy) {
+		woccif_w32(wo,
+			   WOX_AP2WO_MCU_CCIF4_CPUA_PCCIF_BUSY_ADDR, 1 << AP2WO_TXCH_NUM);
+		woccif_w32(wo,
+			   WOX_AP2WO_MCU_CCIF4_CPUA_PCCIF_TCHNUM_ADDR, AP2WO_TXCH_NUM);
+	} else {
+		woccif_w32(wo,
+			   WOX_AP2WO_MCU_CCIF4_CPUA_PCCIF_BUSY_ADDR, 0);
+		woccif_w32(wo,
+			   WOX_AP2WO_MCU_CCIF4_CPUA_PCCIF_TCHNUM_ADDR, AP2WO_TXCH_NUM);
+	}
+}
+
+static inline void woif_kickout(struct mtk_wed_wo *wo)
+{
+	woccif_w32(wo,
+		   WOX_AP2WO_MCU_CCIF4_CPUA_PCCIF_BUSY_ADDR, 1 << AP2WO_TXCH_NUM);
+	woccif_w32(wo,
+		   WOX_AP2WO_MCU_CCIF4_CPUA_PCCIF_TCHNUM_ADDR, AP2WO_TXCH_NUM);
+}
+
+static inline void woif_clear_int(struct mtk_wed_wo *wo, u32 mask)
+{
+	woccif_w32(wo,
+		   WOX_AP2WO_MCU_CCIF4_CPUA_PCCIF_ACK_ADDR, mask);
+	woccif_r32(wo,
+		   WOX_AP2WO_MCU_CCIF4_CPUA_PCCIF_RCHNUM_ADDR);
+
+}
+
+int wed_wo_hardware_init(struct mtk_wed_wo *wo, void *isr)
+{
+	static const struct wed_wo_drv_ops wo_drv_ops = {
+		.kickout_t = woif_kickout_test,
+		.kickout = woif_kickout,
+		.set_ack = woif_set_ack,
+		.set_isr = woif_set_isr,
+		.get_csr = woif_get_csr,
+		.clear_int = woif_clear_int,
+	};
+	struct device_node *node = NULL;
+	struct wed_wo_queue_regs regs;
+	int ret;
+
+	wo->drv_ops = &wo_drv_ops;
+
+	node = of_find_compatible_node(NULL, NULL, AP2WOCCIF_DEV_NODE);
+	if (!node) {
+		pr_info("%s: dts node ap2woccif not found\n", __func__);
+		return -EINVAL;
+	}
+
+	wo->ccif.regs  = (void __iomem *)of_iomap(node, wo->hw->index);
+	wo->ccif.irq = irq_of_parse_and_map(node, wo->hw->index);
+	spin_lock_init(&wo->ccif.irq_lock);
+
+	/*assign interrupt*/
+	ret = request_irq(wo->ccif.irq, (irq_handler_t)isr,
+			  IRQF_TRIGGER_HIGH, "wo_ccif_isr", wo);
+	if (ret)
+		goto free_irq;
+
+	/* tx queue init */
+	regs.desc_base = WOX_AP2WO_MCU_CCIF4_CPUA_PCCIF_DUMMY1_ADDR;
+	regs.ring_size = WOX_AP2WO_MCU_CCIF4_CPUA_PCCIF_DUMMY2_ADDR;
+	regs.cpu_idx = WOX_AP2WO_MCU_CCIF4_CPUA_PCCIF_DUMMY3_ADDR;
+	regs.dma_idx = WOX_AP2WO_MCU_CCIF4_CPUB2CPUA_SHADOW4_ADDR;
+
+	ret = mtk_wed_wo_q_alloc(wo, &wo->q_tx, WED_WO_RING_SIZE,
+				 WED_WO_CMD_LEN, AP2WO_TXCH_NUM, &regs);//woif_q_alloc
+
+	if (ret)
+		goto free_irq;
+
+	/* rx queue init */
+	regs.desc_base = WOX_AP2WO_MCU_CCIF4_CPUA_PCCIF_DUMMY5_ADDR;
+	regs.ring_size = WOX_AP2WO_MCU_CCIF4_CPUA_PCCIF_DUMMY6_ADDR;
+	regs.cpu_idx = WOX_AP2WO_MCU_CCIF4_CPUA_PCCIF_DUMMY7_ADDR;
+	regs.dma_idx = WOX_AP2WO_MCU_CCIF4_CPUB2CPUA_SHADOW8_ADDR;
+
+	ret = mtk_wed_wo_q_alloc(wo, &wo->q_rx, WED_WO_RING_SIZE,
+				 WED_WO_CMD_LEN, AP2WO_RXCH_NUM, &regs);
+	if (ret)
+		goto free_irq;
+
+	wo->ccif.q_int_mask = AP2WO_RXCH_INT_MASK;
+
+	ret = mtk_wed_wo_q_init(wo, mtk_wed_wo_rx_poll);//woif_q_init
+
+	wo->ccif.q_exep_mask = AP2WO_RXCH_WO_EXCEPTION_INT_MASK;
+	wo->ccif.irqmask = AP2WO_ALL_INT_MASK;
+
+	/* rx queue irqmask */
+	wo->drv_ops->set_isr(wo, wo->ccif.irqmask);
+
+	return 0;
+
+free_irq:
+	devm_free_irq(wo->hw->dev, wo->ccif.irq, wo);
+
+	return ret;
+}
+
+static void wed_wo_hardware_exit(struct mtk_wed_wo *wo)
+{
+}
diff --git a/drivers/net/ethernet/mediatek/mtk_wed_ccif.h b/drivers/net/ethernet/mediatek/mtk_wed_ccif.h
new file mode 100644
index 0000000..72b1482
--- /dev/null
+++ b/drivers/net/ethernet/mediatek/mtk_wed_ccif.h
@@ -0,0 +1,47 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#ifndef __MTK_WED_CCIF_H
+#define __MTK_WED_CCIF_H
+
+#define AP2WOCCIF_DEV_NODE	"mediatek,ap2woccif"
+#define WED_WO_RING_SIZE	256
+#define WED_WO_CMD_LEN		1504
+
+#define AP2WO_TXCH_NUM 0
+#define AP2WO_RXCH_NUM 1
+#define AP2WO_RXCH_NUM_WO_EXCEPTION 7
+
+#define AP2WO_TXCH_INT_MASK	BIT(0)
+#define AP2WO_RXCH_INT_MASK	BIT(1)
+#define AP2WO_RXCH_WO_EXCEPTION_INT_MASK	BIT(7)
+#define AP2WO_ALL_INT_MASK	AP2WO_RXCH_INT_MASK | AP2WO_RXCH_WO_EXCEPTION_INT_MASK
+
+#define WOX_AP2WO_MCU_CCIF4_BASE	0 /*0x151A5000*/
+
+#define WOX_AP2WO_MCU_CCIF4_CPUA_PCCIF_BUSY_ADDR	0x0004
+#define WOX_AP2WO_MCU_CCIF4_CPUA_PCCIF_START_ADDR	0x0008
+#define WOX_AP2WO_MCU_CCIF4_CPUA_PCCIF_TCHNUM_ADDR	0x000C
+#define WOX_AP2WO_MCU_CCIF4_CPUA_PCCIF_RCHNUM_ADDR	0x0010
+#define WOX_AP2WO_MCU_CCIF4_CPUA_PCCIF_RCHNUM_MASK	GENMASK(7, 0)
+
+#define WOX_AP2WO_MCU_CCIF4_CPUA_PCCIF_ACK_ADDR		0x0014
+#define WOX_AP2WO_MCU_CCIF4_CPUA_PCCIF_IRQ0_MASK_ADDR	(WOX_AP2WO_MCU_CCIF4_BASE + 0x0018) // 5018
+#define WOX_AP2WO_MCU_CCIF4_CPUA_PCCIF_IRQ1_MASK_ADDR	(WOX_AP2WO_MCU_CCIF4_BASE + 0x001C) // 501C
+#define WOX_AP2WO_MCU_CCIF4_CPUA_PCCIF_DUMMY1_ADDR	(WOX_AP2WO_MCU_CCIF4_BASE + 0x0020) // 5020
+#define WOX_AP2WO_MCU_CCIF4_CPUA_PCCIF_DUMMY2_ADDR	(WOX_AP2WO_MCU_CCIF4_BASE + 0x0024) // 5024
+#define WOX_AP2WO_MCU_CCIF4_CPUA_PCCIF_DUMMY3_ADDR	(WOX_AP2WO_MCU_CCIF4_BASE + 0x0028) // 5028
+#define WOX_AP2WO_MCU_CCIF4_CPUA_PCCIF_DUMMY4_ADDR	(WOX_AP2WO_MCU_CCIF4_BASE + 0x002C) // 502C
+#define WOX_AP2WO_MCU_CCIF4_CPUB2CPUA_SHADOW1_ADDR	(WOX_AP2WO_MCU_CCIF4_BASE + 0x0030) // 5030
+#define WOX_AP2WO_MCU_CCIF4_CPUB2CPUA_SHADOW2_ADDR	(WOX_AP2WO_MCU_CCIF4_BASE + 0x0034) // 5034
+#define WOX_AP2WO_MCU_CCIF4_CPUB2CPUA_SHADOW3_ADDR	(WOX_AP2WO_MCU_CCIF4_BASE + 0x0038) // 5038
+#define WOX_AP2WO_MCU_CCIF4_CPUB2CPUA_SHADOW4_ADDR	(WOX_AP2WO_MCU_CCIF4_BASE + 0x003C) // 503C
+#define WOX_AP2WO_MCU_CCIF4_CPUA_PCCIF_DUMMY5_ADDR	(WOX_AP2WO_MCU_CCIF4_BASE + 0x0050) // 5050
+#define WOX_AP2WO_MCU_CCIF4_CPUA_PCCIF_DUMMY6_ADDR	(WOX_AP2WO_MCU_CCIF4_BASE + 0x0054) // 5054
+#define WOX_AP2WO_MCU_CCIF4_CPUA_PCCIF_DUMMY7_ADDR	(WOX_AP2WO_MCU_CCIF4_BASE + 0x0058) // 5058
+#define WOX_AP2WO_MCU_CCIF4_CPUA_PCCIF_DUMMY8_ADDR	(WOX_AP2WO_MCU_CCIF4_BASE + 0x005C) // 505C
+#define WOX_AP2WO_MCU_CCIF4_CPUB2CPUA_SHADOW5_ADDR	(WOX_AP2WO_MCU_CCIF4_BASE + 0x0060) // 5060
+#define WOX_AP2WO_MCU_CCIF4_CPUB2CPUA_SHADOW6_ADDR	(WOX_AP2WO_MCU_CCIF4_BASE + 0x0064) // 5064
+#define WOX_AP2WO_MCU_CCIF4_CPUB2CPUA_SHADOW7_ADDR	(WOX_AP2WO_MCU_CCIF4_BASE + 0x0068) // 5068
+#define WOX_AP2WO_MCU_CCIF4_CPUB2CPUA_SHADOW8_ADDR	(WOX_AP2WO_MCU_CCIF4_BASE + 0x006C) // 506C
+
+#endif
diff --git a/drivers/net/ethernet/mediatek/mtk_wed_debugfs.c b/drivers/net/ethernet/mediatek/mtk_wed_debugfs.c
index 8d995b7..083e556 100644
--- a/drivers/net/ethernet/mediatek/mtk_wed_debugfs.c
+++ b/drivers/net/ethernet/mediatek/mtk_wed_debugfs.c
@@ -2,6 +2,7 @@
 /* Copyright (C) 2021 Felix Fietkau <nbd@nbd.name> */
 
 #include <linux/seq_file.h>
+#include <linux/soc/mediatek/mtk_wed.h>
 #include "mtk_wed.h"
 #include "mtk_wed_regs.h"
 
@@ -18,6 +19,8 @@ enum {
 	DUMP_TYPE_WDMA,
 	DUMP_TYPE_WPDMA_TX,
 	DUMP_TYPE_WPDMA_TXFREE,
+	DUMP_TYPE_WPDMA_RX,
+	DUMP_TYPE_WED_RRO,
 };
 
 #define DUMP_STR(_str) { _str, 0, DUMP_TYPE_STRING }
@@ -36,6 +39,10 @@ enum {
 
 #define DUMP_WPDMA_TX_RING(_n) DUMP_RING("WPDMA_TX" #_n, 0, DUMP_TYPE_WPDMA_TX, _n)
 #define DUMP_WPDMA_TXFREE_RING DUMP_RING("WPDMA_RX1", 0, DUMP_TYPE_WPDMA_TXFREE)
+#define DUMP_WPDMA_RX_RING(_n)	DUMP_RING("WPDMA_RX" #_n, 0, DUMP_TYPE_WPDMA_RX, _n)
+#define DUMP_WED_RRO_RING(_base)DUMP_RING("WED_RRO_MIOD", MTK_##_base, DUMP_TYPE_WED_RRO)
+#define DUMP_WED_RRO_FDBK(_base)DUMP_RING("WED_RRO_FDBK", MTK_##_base, DUMP_TYPE_WED_RRO)
+
 
 static void
 print_reg_val(struct seq_file *s, const char *name, u32 val)
@@ -58,6 +65,7 @@ dump_wed_regs(struct seq_file *s, struct mtk_wed_device *dev,
 				   cur->name);
 			continue;
 		case DUMP_TYPE_WED:
+		case DUMP_TYPE_WED_RRO:
 			val = wed_r32(dev, cur->offset);
 			break;
 		case DUMP_TYPE_WDMA:
@@ -69,6 +77,9 @@ dump_wed_regs(struct seq_file *s, struct mtk_wed_device *dev,
 		case DUMP_TYPE_WPDMA_TXFREE:
 			val = wpdma_txfree_r32(dev, cur->offset);
 			break;
+		case DUMP_TYPE_WPDMA_RX:
+			val = wpdma_rx_r32(dev, cur->base, cur->offset);
+			break;
 		}
 		print_reg_val(s, cur->name, val);
 	}
@@ -116,6 +127,9 @@ wed_txinfo_show(struct seq_file *s, void *data)
 		DUMP_WDMA(WDMA_GLO_CFG),
 		DUMP_WDMA_RING(WDMA_RING_RX(0)),
 		DUMP_WDMA_RING(WDMA_RING_RX(1)),
+
+		DUMP_STR("TX FREE"),
+		DUMP_WED(WED_RX_MIB(0)),
 	};
 	struct mtk_wed_hw *hw = s->private;
 	struct mtk_wed_device *dev = hw->wed_dev;
@@ -129,6 +143,81 @@ wed_txinfo_show(struct seq_file *s, void *data)
 }
 DEFINE_SHOW_ATTRIBUTE(wed_txinfo);
 
+static int
+wed_rxinfo_show(struct seq_file *s, void *data)
+{
+	static const struct reg_dump regs[] = {
+		DUMP_STR("WPDMA RX"),
+		DUMP_WPDMA_RX_RING(0),
+		DUMP_WPDMA_RX_RING(1),
+
+		DUMP_STR("WPDMA RX"),
+		DUMP_WED(WED_WPDMA_RX_D_MIB(0)),
+		DUMP_WED_RING(WED_WPDMA_RING_RX_DATA(0)),
+		DUMP_WED(WED_WPDMA_RX_D_PROCESSED_MIB(0)),
+		DUMP_WED(WED_WPDMA_RX_D_MIB(1)),
+		DUMP_WED_RING(WED_WPDMA_RING_RX_DATA(1)),
+		DUMP_WED(WED_WPDMA_RX_D_PROCESSED_MIB(1)),
+		DUMP_WED(WED_WPDMA_RX_D_COHERENT_MIB),
+
+		DUMP_STR("WED RX"),
+		DUMP_WED_RING(WED_RING_RX_DATA(0)),
+		DUMP_WED_RING(WED_RING_RX_DATA(1)),
+
+		DUMP_STR("WED RRO"),
+		DUMP_WED_RRO_RING(WED_RROQM_MIOD_CTRL0),
+		DUMP_WED(WED_RROQM_MID_MIB),
+		DUMP_WED(WED_RROQM_MOD_MIB),
+		DUMP_WED(WED_RROQM_MOD_COHERENT_MIB),
+		DUMP_WED_RRO_FDBK(WED_RROQM_FDBK_CTRL0),
+		DUMP_WED(WED_RROQM_FDBK_IND_MIB),
+		DUMP_WED(WED_RROQM_FDBK_ENQ_MIB),
+		DUMP_WED(WED_RROQM_FDBK_ANC_MIB),
+		DUMP_WED(WED_RROQM_FDBK_ANC2H_MIB),
+
+		DUMP_STR("WED Route QM"),
+		DUMP_WED(WED_RTQM_R2H_MIB(0)),
+		DUMP_WED(WED_RTQM_R2Q_MIB(0)),
+		DUMP_WED(WED_RTQM_Q2H_MIB(0)),
+		DUMP_WED(WED_RTQM_R2H_MIB(1)),
+		DUMP_WED(WED_RTQM_R2Q_MIB(1)),
+		DUMP_WED(WED_RTQM_Q2H_MIB(1)),
+		DUMP_WED(WED_RTQM_Q2N_MIB),
+		DUMP_WED(WED_RTQM_Q2B_MIB),
+		DUMP_WED(WED_RTQM_PFDBK_MIB),
+
+		DUMP_STR("WED WDMA TX"),
+		DUMP_WED(WED_WDMA_TX_MIB),
+		DUMP_WED_RING(WED_WDMA_RING_TX),
+
+		DUMP_STR("WDMA TX"),
+		DUMP_WDMA(WDMA_GLO_CFG),
+		DUMP_WDMA_RING(WDMA_RING_TX(0)),
+		DUMP_WDMA_RING(WDMA_RING_TX(1)),
+
+		DUMP_STR("WED RX BM"),
+		DUMP_WED(WED_RX_BM_BASE),
+		DUMP_WED(WED_RX_BM_RX_DMAD),
+		DUMP_WED(WED_RX_BM_PTR),
+		DUMP_WED(WED_RX_BM_TKID_MIB),
+		DUMP_WED(WED_RX_BM_BLEN),
+		DUMP_WED(WED_RX_BM_STS),
+		DUMP_WED(WED_RX_BM_INTF2),
+		DUMP_WED(WED_RX_BM_INTF),
+		DUMP_WED(WED_RX_BM_ERR_STS),
+	};
+
+	struct mtk_wed_hw *hw = s->private;
+	struct mtk_wed_device *dev = hw->wed_dev;
+
+	if (!dev)
+		return 0;
+
+	dump_wed_regs(s, dev, regs, ARRAY_SIZE(regs));
+
+	return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(wed_rxinfo);
 
 static int
 mtk_wed_reg_set(void *data, u64 val)
@@ -199,6 +288,83 @@ wed_token_txd_show(struct seq_file *s, void *data)
 
 DEFINE_SHOW_ATTRIBUTE(wed_token_txd);
 
+static int
+wed_token_rxd_show(struct seq_file *s, void *data)
+{
+	struct mtk_wed_hw *hw = s->private;
+	struct mtk_wed_device *dev = hw->wed_dev;
+	struct mtk_rxbm_desc *desc = dev->rx_buf_ring.desc;
+	int i;
+
+	if(hw->rx_token_id < 16 ) {
+		for (i = 0; i < 16; i++) {
+			printk("[RX D]:token id = %d\n", i);
+			print_hex_dump(KERN_ERR , "", DUMP_PREFIX_OFFSET, 16, 1, (u8 *)desc, sizeof(*desc), false);
+			desc++;
+		}
+	} else {
+		desc += (hw->rx_token_id);
+		print_hex_dump(KERN_ERR , "", DUMP_PREFIX_OFFSET, 16, 1, (u8 *)desc, sizeof(*desc), false);
+	}
+
+	return 0;
+}
+
+DEFINE_SHOW_ATTRIBUTE(wed_token_rxd);
+
+
+
+static int
+wed_wdma_txd_show(struct seq_file *s, void *data)
+{
+	struct mtk_wed_hw *hw = s->private;
+	struct mtk_wed_device *dev = hw->wed_dev;
+
+	int i, idx;
+
+	for (idx = 0; idx < 2; idx++) {
+		struct mtk_wed_ring *wdma = &dev->rx_wdma[idx];
+		struct mtk_wdma_desc *desc = wdma->desc;
+
+		if(hw->wdma_txd == 512 ) {
+			for (i = 0; i < MTK_WED_WDMA_RING_SIZE; i++) {
+				printk("RXD[%d]", i);
+				print_hex_dump(KERN_ERR , "", DUMP_PREFIX_OFFSET, 16, 1, (u8 *)desc, sizeof(*desc) * 2, false);
+				desc += 2;
+			}
+		} else {
+			desc += hw->wdma_txd;
+			print_hex_dump(KERN_ERR , "", DUMP_PREFIX_OFFSET, 16, 1, (u8 *)desc, sizeof(*desc) * 2, false);
+		}
+	}
+
+	return 0;
+}
+
+DEFINE_SHOW_ATTRIBUTE(wed_wdma_txd);
+
+
+static int
+wed_rro_miod_show(struct seq_file *s, void *data)
+{
+	struct mtk_wed_hw *hw = s->private;
+	struct mtk_wed_device *dev = hw->wed_dev;
+	void __iomem *rro =  dev->rro.rro_desc;
+	int i;
+
+	for (i = 0; i < MTK_WED_MIOD_CNT; i++) {
+		printk("RRO RXD[%d]", i);
+		print_hex_dump(KERN_ERR , "", DUMP_PREFIX_OFFSET, 16, 1, (u8 *)rro, MTK_WED_MIOD_ENTRY_CNT, false);
+		rro += MTK_WED_MIOD_ENTRY_CNT;
+	}
+
+
+
+	return 0;
+}
+
+DEFINE_SHOW_ATTRIBUTE(wed_rro_miod);
+
 void mtk_wed_hw_add_debugfs(struct mtk_wed_hw *hw)
 {
 	struct dentry *dir;
@@ -212,6 +378,14 @@ void mtk_wed_hw_add_debugfs(struct mtk_wed_hw *hw)
 	debugfs_create_u32("regidx", 0600, dir, &hw->debugfs_reg);
 	debugfs_create_file_unsafe("regval", 0600, dir, hw, &fops_regval);
 	debugfs_create_file_unsafe("txinfo", 0400, dir, hw, &wed_txinfo_fops);
+	debugfs_create_file_unsafe("rxinfo", 0400, dir, hw, &wed_rxinfo_fops);
 	debugfs_create_u32("token_id", 0600, dir, &hw->token_id);
 	debugfs_create_file_unsafe("token_txd", 0600, dir, hw, &wed_token_txd_fops);
+	debugfs_create_u32("rx_token_id", 0600, dir, &hw->rx_token_id);
+	debugfs_create_file_unsafe("token_rxd", 0600, dir, hw, &wed_token_rxd_fops);
+	debugfs_create_u32("wdma_txd_id", 0600, dir, &hw->wdma_txd);
+	debugfs_create_file_unsafe("wdma_txd", 0600, dir, hw, &wed_wdma_txd_fops);
+	debugfs_create_file_unsafe("rro_rxd", 0600, dir, hw, &wed_rro_miod_fops);
+
+	wed_wo_mcu_debugfs(hw, dir);
 }
diff --git a/drivers/net/ethernet/mediatek/mtk_wed_mcu.c b/drivers/net/ethernet/mediatek/mtk_wed_mcu.c
new file mode 100644
index 0000000..c28dfa6
--- /dev/null
+++ b/drivers/net/ethernet/mediatek/mtk_wed_mcu.c
@@ -0,0 +1,641 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/skbuff.h>
+#include <linux/debugfs.h>
+#include <linux/firmware.h>
+#include <linux/of_address.h>
+#include <linux/soc/mediatek/mtk_wed.h>
+#include "mtk_wed_regs.h"
+#include "mtk_wed_mcu.h"
+#include "mtk_wed_wo.h"
+
+struct sk_buff *
+mtk_wed_mcu_msg_alloc(struct mtk_wed_wo *wo,
+		      const void *data, int data_len)
+{
+	const struct wed_wo_mcu_ops *ops = wo->mcu_ops;
+	int length = ops->headroom + data_len;
+	struct sk_buff *skb;
+
+	skb = alloc_skb(length, GFP_KERNEL);
+	if (!skb)
+		return NULL;
+
+	memset(skb->head, 0, length);
+	skb_reserve(skb, ops->headroom);
+
+	if (data && data_len)
+		skb_put_data(skb, data, data_len);
+
+	return skb;
+}
+
+struct sk_buff *
+mtk_wed_mcu_get_response(struct mtk_wed_wo *wo, unsigned long expires)
+{
+	unsigned long timeout;
+
+	if (!time_is_after_jiffies(expires))
+		return NULL;
+
+	timeout = expires - jiffies;
+	wait_event_timeout(wo->mcu.wait,
+			   (!skb_queue_empty(&wo->mcu.res_q)),
+			   timeout);
+
+	return skb_dequeue(&wo->mcu.res_q);
+}
+
+int
+mtk_wed_mcu_skb_send_and_get_msg(struct mtk_wed_wo *wo,
+				 int to_id, int cmd, struct sk_buff *skb,
+				 bool wait_resp, struct sk_buff **ret_skb)
+{
+	unsigned long expires;
+	int ret, seq;
+
+	if (ret_skb)
+		*ret_skb = NULL;
+
+	mutex_lock(&wo->mcu.mutex);
+
+	ret = wo->mcu_ops->mcu_skb_send_msg(wo, to_id, cmd, skb, &seq, wait_resp);
+	if (ret < 0)
+		goto out;
+
+	if (!wait_resp) {
+		ret = 0;
+		goto out;
+	}
+
+	expires = jiffies + wo->mcu.timeout;
+
+	do {
+		skb = mtk_wed_mcu_get_response(wo, expires);
+		ret = wo->mcu_ops->mcu_parse_response(wo, cmd, skb, seq);
+
+		if (!ret && ret_skb)
+			*ret_skb = skb;
+		else
+			dev_kfree_skb(skb);
+	} while (ret == -EAGAIN);
+
+out:
+	mutex_unlock(&wo->mcu.mutex);
+
+	return ret;
+}
+
+void mtk_wed_mcu_rx_event(struct mtk_wed_wo *wo,
+			struct sk_buff *skb)
+{
+	skb_queue_tail(&wo->mcu.res_q, skb);
+	wake_up(&wo->mcu.wait);
+}
+
+static int mtk_wed_mcu_send_and_get_msg(struct mtk_wed_wo *wo,
+			int to_id, int cmd, const void *data, int len,
+			bool wait_resp, struct sk_buff **ret_skb)
+{
+	struct sk_buff *skb;
+
+	skb = mtk_wed_mcu_msg_alloc(wo, data, len);
+	if (!skb)
+		return -ENOMEM;
+
+	return mtk_wed_mcu_skb_send_and_get_msg(wo, to_id, cmd, skb, wait_resp, ret_skb);
+}
+
+void wo_rxcnt_update_handle(struct mtk_wed_wo *wo, struct sk_buff *skb)
+{
+	struct wo_cmd_rxcnt_t *r_cnt = NULL;
+	u32 count = 0;
+	u8 report_idx = 0;
+
+	if(skb == NULL)
+		return;
+
+	count = *(u32 *)skb->data;
+	r_cnt = (struct wo_cmd_rxcnt_t *)((u32 *)skb->data + 1);
+	pr_info("%s: report count:%d\n", __func__, count);
+
+	for (report_idx = 0 ; report_idx < count ; report_idx++)
+		if ((r_cnt->wlan_idx < 544) && (r_cnt->tid < 8)) {
+			wo->wo_rxcnt[r_cnt->tid][r_cnt->wlan_idx].rx_pkt_cnt += r_cnt->rx_pkt_cnt;
+			wo->wo_rxcnt[r_cnt->tid][r_cnt->wlan_idx].rx_byte_cnt += r_cnt->rx_byte_cnt;
+			wo->wo_rxcnt[r_cnt->tid][r_cnt->wlan_idx].rx_err_cnt += r_cnt->rx_err_cnt;
+			wo->wo_rxcnt[r_cnt->tid][r_cnt->wlan_idx].rx_drop_cnt += r_cnt->rx_drop_cnt;
+
+			pr_info("%s: wo_rxcnt[%d][%d].rx_pkt_cnt = %lu\n", __func__, r_cnt->tid, r_cnt->wlan_idx, wo->wo_rxcnt[r_cnt->tid][r_cnt->wlan_idx].rx_pkt_cnt);
+			pr_info("%s: wo_rxcnt[%d][%d].rx_byte_cnt = %lu\n", __func__, r_cnt->tid, r_cnt->wlan_idx, wo->wo_rxcnt[r_cnt->tid][r_cnt->wlan_idx].rx_byte_cnt);
+			pr_info("%s: wo_rxcnt[%d][%d].rx_err_cnt = %lu\n", __func__, r_cnt->tid, r_cnt->wlan_idx, wo->wo_rxcnt[r_cnt->tid][r_cnt->wlan_idx].rx_err_cnt);
+			pr_info("%s: wo_rxcnt[%d][%d].rx_drop_cnt = %lu\n", __func__, r_cnt->tid, r_cnt->wlan_idx, wo->wo_rxcnt[r_cnt->tid][r_cnt->wlan_idx].rx_drop_cnt);
+		} else {
+			if (r_cnt->wlan_idx >= 544) {
+				pr_info("%s(): invalid wcid=%d, ignored!\n", __func__, r_cnt->wlan_idx);
+				return;
+			}
+			if (r_cnt->tid >= 8) {
+				pr_info("%s(): invalid tid=%d, ignored!\n", __func__, r_cnt->tid);
+				return;
+			}
+		}
+}
+
+int
+mtk_wed_mcu_send_msg(struct mtk_wed_wo *wo,
+			int to_id, int cmd,
+			const void *data, int len, bool wait_resp)
+{
+	struct sk_buff *skb = NULL;
+	int ret = 0;
+
+	ret = mtk_wed_mcu_send_and_get_msg(wo, to_id, cmd, data, len, wait_resp, &skb);
+	if (skb) {
+		if (cmd == WO_CMD_RXCNT_INFO)
+			wo_rxcnt_update_handle(wo, skb);
+
+		dev_kfree_skb(skb);
+	}
+
+	return ret;
+}
+
+int mtk_wed_exception_init(struct mtk_wed_wo *wo)
+{
+	struct wed_wo_exception *exp = &wo->exp;
+	struct {
+		u32 arg0;
+		u32 arg1;
+	}req;
+
+	exp->log_size = (32 * 1024 * sizeof(char));
+	exp->log = kmalloc(exp->log_size, GFP_ATOMIC);
+	if (!exp->log) {
+		pr_info("%s: alloc exp->log failed\n", __func__);
+		return -ENOMEM;
+	}
+
+	memset(exp->log, 0, exp->log_size);
+	exp->phys = dma_map_single(wo->hw->dev, exp->log,
+				   exp->log_size, DMA_FROM_DEVICE);
+	if (unlikely(dma_mapping_error(wo->hw->dev, exp->phys))) {
+		dev_info(wo->hw->dev, "dma map error\n");
+		pr_info("%s: dma map errod\n", __func__);
+		goto free;
+	}
+
+	req.arg0 = (u32)exp->phys;
+	req.arg1 = (u32)exp->log_size;
+
+	return mtk_wed_mcu_send_msg(wo, MODULE_ID_WO,
+				    WO_CMD_EXCEPTION_INIT, &req, sizeof(req), false);
+
+free:
+	kfree(exp->log);
+	return -ENOMEM;
+}
+
+int
+mtk_wed_mcu_cmd_sanity_check(struct mtk_wed_wo *wo, struct sk_buff *skb)
+{
+	struct wed_cmd_hdr *hdr = (struct wed_cmd_hdr *)skb->data;
+
+	if (hdr->ver != 0)
+		return WARP_INVALID_PARA_STATUS;
+
+	if (skb->len < sizeof(struct wed_cmd_hdr))
+		return WARP_INVALID_PARA_STATUS;
+
+	if (skb->len != hdr->length)
+		return WARP_INVALID_PARA_STATUS;
+
+	return WARP_OK_STATUS;
+}
+
+void
+mtk_wed_mcu_rx_unsolicited_event(struct mtk_wed_wo *wo, struct sk_buff *skb)
+{
+	struct wed_cmd_hdr *hdr = (struct wed_cmd_hdr *)skb->data;
+	struct wed_wo_log *record;
+	struct wed_wo_rxcnt *rxcnt;
+	char *msg = (char *)(skb->data + sizeof(struct wed_cmd_hdr));
+	u16 msg_len = skb->len - sizeof(struct wed_cmd_hdr);
+	u32 i, cnt = 0;
+
+	switch (hdr->cmd_id) {
+	case WO_EVT_LOG_DUMP:
+		pr_info("[WO LOG]: %s\n", msg);
+		break;
+	case WO_EVT_PROFILING:
+		cnt = msg_len / (sizeof(struct wed_wo_log));
+		record = (struct wed_wo_log *) msg;
+		dev_info(wo->hw->dev, "[WO Profiling]: %d report arrived!\n", cnt);
+
+		for (i = 0 ; i < cnt ; i++) {
+			//PROFILE_STAT(wo->total, record[i].total);
+			//PROFILE_STAT(wo->mod, record[i].mod);
+			//PROFILE_STAT(wo->rro, record[i].rro);
+
+			dev_info(wo->hw->dev, "[WO Profiling]:  SN:%u with latency: total=%u, rro:%u, mod:%u\n",
+				 record[i].sn,
+				 record[i].total,
+				 record[i].rro,
+				 record[i].mod);
+		}
+		break;
+	case WO_EVT_RXCNT_INFO:
+		cnt = *(u32 *)msg;
+		rxcnt = (struct wed_wo_rxcnt *)((u32 *) msg + 1);
+		dev_info(wo->hw->dev, "[WO Report]: report count:%d\n", cnt);
+
+		for (i = 0 ; i < cnt ; i++)
+			wo->hw->wed_dev->wlan.rx_stat(wo->hw->wed_dev, (void *)&rxcnt[i]);
+		break;
+	default:
+		break;
+	}
+
+	dev_kfree_skb(skb);
+
+}
+
+static int
+mtk_wed_load_firmware(struct mtk_wed_wo *wo)
+{
+	struct fw_info {
+		__le32 decomp_crc;
+		__le32 decomp_len;
+		__le32 decomp_blk_sz;
+		u8 reserved[4];
+		__le32 addr;
+		__le32 len;
+		u8 feature_set;
+		u8 reserved1[15];
+	} __packed *region;
+
+	char *mcu;
+	const struct mtk_wed_fw_trailer *hdr;
+	static u8 shared[MAX_REGION_SIZE] = {0};
+	const struct firmware *fw;
+	int ret, i;
+	u32 ofs = 0;
+	u32 boot_cr, val;
+
+	mcu = wo->hw->index ? MT7986_FIRMWARE_WO_2 : MT7986_FIRMWARE_WO_1;
+
+	ret = request_firmware(&fw, mcu, wo->hw->dev);
+	if (ret) {
+		pr_info("%s : request_firmware failed\n", __func__);
+		return ret;
+
+	} else {
+		pr_info("%s : request_firmware %s success\n", __func__, mcu);
+	}
+
+	hdr = (const struct mtk_wed_fw_trailer *)(fw->data + fw->size -
+					sizeof(*hdr));
+
+	dev_info(wo->hw->dev, "WO Firmware Version: %.10s, Build Time: %.15s\n",
+		 hdr->fw_ver, hdr->build_date);
+
+	for (i = 0; i < hdr->n_region; i++) {
+		int j = 0;
+		region = (struct fw_info *)(fw->data + fw->size - sizeof(*hdr) -
+					(hdr->n_region - i) * sizeof(*region));
+
+		pr_info("%s : region = %d, Target address = 0x%08x, Download size = %d\n", __func__, i, region->addr, region->len);
+		while (j < MAX_REGION_SIZE) {
+			struct mtk_wed_fw_region *wo_region;
+
+			wo_region = &wo->region[j];
+			if (!wo_region->addr) {
+				pr_info("%s : region[%d] is null\n", __func__, i);
+				break;
+			}
+
+			if (wo_region->addr_pa == region->addr) {
+				if(!wo_region->shared) {
+					memcpy(wo_region->addr, fw->data + ofs, region->len);
+				}else if (!shared[j]) {
+					memcpy(wo_region->addr, fw->data + ofs, region->len);
+					shared[j] = true;
+				}
+			}
+			j++;
+		}
+
+		if (j == __WO_REGION_MAX) {
+			ret = -1;
+			goto done;
+		}
+		ofs += region->len;
+	}
+
+	/* write the start address */
+	boot_cr = wo->hw->index ?
+		WOX_MCU_CFG_LS_WA_BOOT_ADDR_ADDR : WOX_MCU_CFG_LS_WM_BOOT_ADDR_ADDR;
+	wo_w32(wo, boot_cr, (wo->region[WO_REGION_EMI].addr_pa >> 16));
+
+	/* wo firmware reset */
+	wo_w32(wo, WOX_MCU_CFG_LS_WF_MCCR_CLR_ADDR, 0xc00);
+
+	val = wo_r32(wo, WOX_MCU_CFG_LS_WF_MCU_CFG_WM_WA_ADDR);
+
+	val |= wo->hw->index ? WOX_MCU_CFG_LS_WF_MCU_CFG_WM_WA_WA_CPU_RSTB_MASK :
+		WOX_MCU_CFG_LS_WF_MCU_CFG_WM_WA_WM_CPU_RSTB_MASK;
+
+	wo_w32(wo, WOX_MCU_CFG_LS_WF_MCU_CFG_WM_WA_ADDR, val);
+
+done:
+	release_firmware(fw);
+
+	return ret;
+}
+
+static int
+mtk_wed_get_firmware_region(struct mtk_wed_wo *wo)
+{
+	struct mtk_wed_fw_region *region;
+	struct device_node *node;
+	struct resource res;
+	char *compat = NULL;
+	int i, ret;
+
+	for (i = WO_REGION_EMI; i < __WO_REGION_MAX; i++) {
+		region = &wo->region[i];
+
+		switch (i){
+		case WO_REGION_EMI:
+			compat = wo->hw->index ? WOCPU1_EMI_DEV_NODE : WOCPU0_EMI_DEV_NODE;
+			break;
+		case WO_REGION_ILM:
+			compat = wo->hw->index ? WOCPU1_ILM_DEV_NODE : WOCPU0_ILM_DEV_NODE;
+			break;
+		case WO_REGION_DATA:
+			compat = WOCPU_DATA_DEV_NODE;
+			break;
+		case WO_REGION_BOOT:
+			compat = WOCPU_BOOT_DEV_NODE;
+			break;
+		default:
+			break;
+		}
+
+		if (!compat) {
+			pr_info("%s : region = %d, dts pase fail\n", __func__, i);
+			return -1;
+		}
+
+		node = of_find_compatible_node(NULL, NULL, compat);
+		if (!node) {
+			pr_info("%s : region = %d, dts pase fail, node = null\n", __func__, i);
+			return -1;
+		}
+
+		ret = of_address_to_resource(node, 0, &res);
+		if (ret)
+			{
+			pr_info("%s : region = %d, dts pase fail, of_address_to_resource = null\n", __func__, i);
+			return -1;
+		}
+
+		region->addr = ioremap(res.start, resource_size(&res));
+		region->addr_pa = res.start;
+		region->size = resource_size(&res);
+
+		of_property_read_u32_index(node, "shared", 0, &region->shared);
+	}
+
+	return 0;
+}
+
+static int
+wo_mcu_send_message(struct mtk_wed_wo *wo,
+			int to_id, int cmd, struct sk_buff *skb,
+			int *wait_seq, bool wait_resp)
+{
+	struct wed_cmd_hdr  *hdr;
+	u8 seq = 0;
+
+	/* TDO: make dynamic based on msg type */
+	wo->mcu.timeout = 20 * HZ;
+
+	if (wait_resp && wait_seq) {
+		seq = wo->mcu.msg_seq++ ;
+		*wait_seq = seq;
+	}
+
+	hdr = (struct wed_cmd_hdr *)skb_push(skb, sizeof(*hdr));
+
+	hdr->cmd_id = cmd;
+	hdr->length = cpu_to_le16(skb->len);
+	hdr->uni_id = seq;
+
+	if (to_id == MODULE_ID_WO)
+		hdr->flag |= WARP_CMD_FLAG_FROM_TO_WO;
+
+	if (wait_resp && wait_seq)
+		hdr->flag |= WARP_CMD_FLAG_NEED_RSP;
+
+	return mtk_wed_wo_q_tx_skb(wo, &wo->q_tx, skb);
+}
+
+static int
+wo_mcu_parse_response(struct mtk_wed_wo *wo, int cmd,
+			  struct sk_buff *skb, int seq)
+{
+	struct wed_cmd_hdr  *hdr;
+
+	if (!skb) {
+		dev_err(wo->hw->dev, "Message %08x (seq %d) timeout\n",
+			cmd, seq);
+		return -ETIMEDOUT;
+	}
+
+	hdr = (struct wed_cmd_hdr *)skb->data;
+	if (seq != hdr->uni_id) {
+		dev_err(wo->hw->dev, "Message %08x (seq %d) with not match uid(%d)\n",
+			cmd, seq, hdr->uni_id);
+		return -EAGAIN;
+	}
+
+	//skb_pull(skb, sizeof(struct wed_cmd_hdr));
+
+	return 0;
+}
+
+int wed_wo_mcu_init(struct mtk_wed_wo *wo)
+{
+	static const struct wed_wo_mcu_ops wo_mcu_ops = {
+		.headroom = sizeof(struct wed_cmd_hdr),
+		.mcu_skb_send_msg = wo_mcu_send_message,
+		.mcu_parse_response = wo_mcu_parse_response,
+		/*TDO .mcu_restart = wo_mcu_restart,*/
+	};
+	unsigned long timeout = jiffies + FW_DL_TIMEOUT;
+	int ret;
+	u32 val;
+
+	wo->mcu_ops = &wo_mcu_ops;
+
+	/* get the region config info */
+	ret = mtk_wed_get_firmware_region(wo);
+	if (ret) {
+		pr_info("%s : get_firmware_region failed\n", __func__);
+		return ret;
+	}
+
+	/* set dummy cr */
+	wed_w32(wo->hw->wed_dev, MTK_WED_SCR0 + 4 * WED_DUMMY_CR_FWDL,
+		wo->hw->index + 1);
+
+	ret = mtk_wed_load_firmware(wo);
+	if (ret) {
+		pr_info("%s : mtk_wed_load_firmware failed\n", __func__);
+		return ret;
+	}
+	do {
+		/* get dummy cr */
+		val = wed_r32(wo->hw->wed_dev, MTK_WED_SCR0 + 4 * WED_DUMMY_CR_FWDL);
+	} while (val != 0 && !time_after(jiffies, timeout));
+
+	if (val) {
+		pr_info("%s(): wo fw dl failed\n", __func__);
+		return -1;
+	} else {
+		pr_info("%s(): wocpu is ready\n", __func__);
+	}
+
+	return 0;
+}
+
+static ssize_t
+mtk_wed_wo_ctrl(struct file *file,
+			 const char __user *user_buf,
+			 size_t count,
+			 loff_t *ppos)
+{
+	struct mtk_wed_hw *hw = file->private_data;
+	struct mtk_wed_wo *wo = hw->wed_wo;
+	char buf[100], *cmd = NULL, *input[11] = {0};
+	char msgbuf[128] = {0};
+	struct wo_cmd_query *query = (struct wo_cmd_query *)msgbuf;
+	u32 cmd_id;
+	bool wait = false;
+	char *sub_str = NULL;
+	int  input_idx = 0, input_total = 0, scan_num = 0;
+	char *p;
+
+	if (count > sizeof(buf))
+		return -EINVAL;
+
+	if (copy_from_user(buf, user_buf, count))
+		return -EFAULT;
+
+	if (count && buf[count - 1] == '\n')
+		buf[count - 1] = '\0';
+	else
+		buf[count] = '\0';
+
+	p = buf;
+
+	while ((sub_str = strsep(&p, " ")) != NULL) {
+		input[input_idx] = sub_str;
+		input_idx++;
+		input_total++;
+	}
+	cmd = input[0];
+	if (input_total == 1 && cmd) {
+		if (strncmp(cmd, "bainfo", strlen(cmd)) == 0) {
+			cmd_id = WO_CMD_BA_INFO_DUMP;
+		} else if (strncmp(cmd, "bactrl", strlen(cmd)) == 0) {
+			cmd_id = WO_CMD_BA_CTRL_DUMP;
+		} else if (strncmp(cmd, "fbcmdq", strlen(cmd)) == 0) {
+			cmd_id = WO_CMD_FBCMD_Q_DUMP;
+		} else if (strncmp(cmd, "logflush", strlen(cmd)) == 0) {
+			cmd_id = WO_CMD_LOG_FLUSH;
+		} else if (strncmp(cmd, "cpustat.dump", strlen(cmd)) == 0) {
+			cmd_id = WO_CMD_CPU_STATS_DUMP;
+		} else if (strncmp(cmd, "state", strlen(cmd)) == 0) {
+			cmd_id = WO_CMD_WED_RX_STAT;
+		} else if (strncmp(cmd, "prof_hit_dump", strlen(cmd)) == 0) {
+			//wo_profiling_report();
+			return count;
+		} else if (strncmp(cmd, "rxcnt_info", strlen(cmd)) == 0) {
+			cmd_id = WO_CMD_RXCNT_INFO;
+			wait = true;
+		} else {
+			pr_info("(%s) unknown comand string(%s)!\n", __func__, cmd);
+			 return count;
+		}
+	}  else if (input_total > 1) {
+		for (input_idx = 1 ; input_idx < input_total ; input_idx++) {
+			scan_num = sscanf(input[input_idx], "%u", &query->query0+(input_idx - 1));
+
+			if (scan_num < 1) {
+				pr_info("(%s) require more input!\n", __func__);
+				return count;
+			}
+		}
+		if(strncmp(cmd, "devinfo", strlen(cmd)) == 0) {
+			cmd_id = WO_CMD_DEV_INFO_DUMP;
+		} else if (strncmp(cmd, "bssinfo", strlen(cmd)) == 0) {
+			cmd_id = WO_CMD_BSS_INFO_DUMP;
+		} else if (strncmp(cmd, "starec", strlen(cmd)) == 0) {
+			cmd_id = WO_CMD_STA_REC_DUMP;
+		} else if (strncmp(cmd, "starec_ba", strlen(cmd)) == 0) {
+			cmd_id = WO_CMD_STA_BA_DUMP;
+		} else if (strncmp(cmd, "logctrl", strlen(cmd)) == 0) {
+			cmd_id = WO_CMD_FW_LOG_CTRL;
+		} else if (strncmp(cmd, "cpustat.en", strlen(cmd)) == 0) {
+			cmd_id = WO_CMD_CPU_STATS_ENABLE;
+		} else if (strncmp(cmd, "prof_conf", strlen(cmd)) == 0) {
+			cmd_id = WO_CMD_PROF_CTRL;
+		} else if (strncmp(cmd, "rxcnt_ctrl", strlen(cmd)) == 0) {
+			cmd_id = WO_CMD_RXCNT_CTRL;
+		} else if (strncmp(cmd, "dbg_set", strlen(cmd)) == 0) {
+			cmd_id = WO_CMD_DBG_INFO;
+		}
+	} else {
+		dev_info(hw->dev, "usage: echo cmd='cmd_str' > wo_write\n");
+		dev_info(hw->dev, "cmd_str value range:\n");
+		dev_info(hw->dev, "\tbainfo:\n");
+		dev_info(hw->dev, "\tbactrl:\n");
+		dev_info(hw->dev, "\tfbcmdq:\n");
+		dev_info(hw->dev, "\tlogflush:\n");
+		dev_info(hw->dev, "\tcpustat.dump:\n");
+		dev_info(hw->dev, "\tprof_hit_dump:\n");
+		dev_info(hw->dev, "\trxcnt_info:\n");
+		dev_info(hw->dev, "\tdevinfo:\n");
+		dev_info(hw->dev, "\tbssinfo:\n");
+		dev_info(hw->dev, "\tstarec:\n");
+		dev_info(hw->dev, "\tstarec_ba:\n");
+		dev_info(hw->dev, "\tlogctrl:\n");
+		dev_info(hw->dev, "\tcpustat.en:\n");
+		dev_info(hw->dev, "\tprof_conf:\n");
+		dev_info(hw->dev, "\trxcnt_ctrl:\n");
+		dev_info(hw->dev, "\tdbg_set [level] [category]:\n");
+		return count;
+	}
+
+	mtk_wed_mcu_send_msg(wo, MODULE_ID_WO, cmd_id, (void *)msgbuf, sizeof(struct wo_cmd_query), wait);
+
+	return count;
+
+}
+
+static const struct file_operations fops_wo_ctrl = {
+	.write = mtk_wed_wo_ctrl,
+	.open = simple_open,
+	.llseek = default_llseek,
+};
+
+void wed_wo_mcu_debugfs(struct mtk_wed_hw *hw, struct dentry *dir)
+{
+	if (!dir)
+		return;
+
+	debugfs_create_file("wo_write", 0600, dir, hw, &fops_wo_ctrl);
+}
+
diff --git a/drivers/net/ethernet/mediatek/mtk_wed_mcu.h b/drivers/net/ethernet/mediatek/mtk_wed_mcu.h
new file mode 100644
index 0000000..1ec9565
--- /dev/null
+++ b/drivers/net/ethernet/mediatek/mtk_wed_mcu.h
@@ -0,0 +1,97 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#ifndef __MTK_WED_MCU_H
+#define __MTK_WED_MCU_H
+
+#define WOCPU_MCUSYS_RESET_ADDR		0x15194050
+#define WOCPU_WO0_MCUSYS_RESET_MASK 	0x20
+#define WOCPU_WO1_MCUSYS_RESET_MASK 	0x1
+
+#define WARP_INVALID_LENGTH_STATUS (-2)
+#define WARP_NULL_POINTER_STATUS (-3)
+#define WARP_INVALID_PARA_STATUS (-4)
+#define WARP_NOT_HANDLE_STATUS (-5)
+#define WARP_FAIL_STATUS (-1)
+#define WARP_OK_STATUS (0)
+#define WARP_ALREADY_DONE_STATUS (1)
+
+#define MT7986_FIRMWARE_WO_1		"mediatek/mt7986_wo_0.bin"
+#define MT7986_FIRMWARE_WO_2		"mediatek/mt7986_wo_1.bin"
+
+#define WOCPU0_EMI_DEV_NODE		"mediatek,wocpu0_emi"
+#define WOCPU1_EMI_DEV_NODE		"mediatek,wocpu1_emi"
+#define WOCPU0_ILM_DEV_NODE		"mediatek,wocpu0_ilm"
+#define WOCPU1_ILM_DEV_NODE		"mediatek,wocpu1_ilm"
+#define WOCPU_DLM_DEV_NODE		"mediatek,wocpu_dlm"
+#define WOCPU_DATA_DEV_NODE		"mediatek,wocpu_data"
+#define WOCPU_BOOT_DEV_NODE		"mediatek,wocpu_boot"
+
+#define FW_DL_TIMEOUT		((3000 * HZ) / 1000)
+#define WOCPU_TIMEOUT		((1000 * HZ) / 1000)
+
+#define MAX_REGION_SIZE	3
+
+#define WOX_MCU_CFG_LS_BASE	0 /*0x15194000*/
+
+#define WOX_MCU_CFG_LS_HW_VER_ADDR		(WOX_MCU_CFG_LS_BASE + 0x000) // 4000
+#define WOX_MCU_CFG_LS_FW_VER_ADDR		(WOX_MCU_CFG_LS_BASE + 0x004) // 4004
+#define WOX_MCU_CFG_LS_CFG_DBG1_ADDR		(WOX_MCU_CFG_LS_BASE + 0x00C) // 400C
+#define WOX_MCU_CFG_LS_CFG_DBG2_ADDR 		(WOX_MCU_CFG_LS_BASE + 0x010) // 4010
+#define WOX_MCU_CFG_LS_WF_MCCR_ADDR		(WOX_MCU_CFG_LS_BASE + 0x014) // 4014
+#define WOX_MCU_CFG_LS_WF_MCCR_SET_ADDR		(WOX_MCU_CFG_LS_BASE + 0x018) // 4018
+#define WOX_MCU_CFG_LS_WF_MCCR_CLR_ADDR		(WOX_MCU_CFG_LS_BASE + 0x01C) // 401C
+#define WOX_MCU_CFG_LS_WF_MCU_CFG_WM_WA_ADDR	(WOX_MCU_CFG_LS_BASE + 0x050) // 4050
+#define WOX_MCU_CFG_LS_WM_BOOT_ADDR_ADDR 	(WOX_MCU_CFG_LS_BASE + 0x060) // 4060
+#define WOX_MCU_CFG_LS_WA_BOOT_ADDR_ADDR	(WOX_MCU_CFG_LS_BASE + 0x064) // 4064
+
+#define WOX_MCU_CFG_LS_WF_MCU_CFG_WM_WA_WM_CPU_RSTB_MASK	BIT(5)
+#define WOX_MCU_CFG_LS_WF_MCU_CFG_WM_WA_WA_CPU_RSTB_MASK	BIT(0)
+
+
+enum wo_event_id {
+	WO_EVT_LOG_DUMP = 0x1,
+	WO_EVT_PROFILING = 0x2,
+	WO_EVT_RXCNT_INFO = 0x3
+};
+
+enum wo_state {
+	WO_STATE_UNDEFINED 	= 0x0,
+	WO_STATE_INIT 		= 0x1,
+	WO_STATE_ENABLE		= 0x2,
+	WO_STATE_DISABLE	= 0x3,
+	WO_STATE_HALT		= 0x4,
+	WO_STATE_GATING		= 0x5,
+	WO_STATE_SER_RESET 	= 0x6,
+	WO_STATE_WF_RESET	= 0x7,
+	WO_STATE_END
+};
+
+enum wo_done_state {
+	WOIF_UNDEFINED		= 0,
+	WOIF_DISABLE_DONE 	= 1,
+	WOIF_TRIGGER_ENABLE	= 2,
+	WOIF_ENABLE_DONE	= 3,
+	WOIF_TRIGGER_GATING	= 4,
+	WOIF_GATING_DONE	= 5,
+	WOIF_TRIGGER_HALT	= 6,
+	WOIF_HALT_DONE		= 7,
+};
+
+enum wed_dummy_cr_idx {
+	WED_DUMMY_CR_FWDL = 0,
+	WED_DUMMY_CR_WO_STATUS = 1
+};
+
+struct mtk_wed_fw_trailer {
+	u8 chip_id;
+	u8 eco_code;
+	u8 n_region;
+	u8 format_ver;
+	u8 format_flag;
+	u8 reserved[2];
+	char fw_ver[10];
+	char build_date[15];
+	u32 crc;
+};
+
+#endif
diff --git a/drivers/net/ethernet/mediatek/mtk_wed_regs.h b/drivers/net/ethernet/mediatek/mtk_wed_regs.h
index b7dc424..1885637 100644
--- a/drivers/net/ethernet/mediatek/mtk_wed_regs.h
+++ b/drivers/net/ethernet/mediatek/mtk_wed_regs.h
@@ -4,12 +4,14 @@
 #ifndef __MTK_WED_REGS_H
 #define __MTK_WED_REGS_H
 
+#define MTK_WFDMA_DESC_CTRL_TO_HOST		BIT(8)
 #define MTK_WDMA_DESC_CTRL_LEN1			GENMASK(13, 0)
 #define MTK_WDMA_DESC_CTRL_LAST_SEG1		BIT(14)
 #define MTK_WDMA_DESC_CTRL_BURST		BIT(15)
 #define MTK_WDMA_DESC_CTRL_LEN0			GENMASK(29, 16)
 #define MTK_WDMA_DESC_CTRL_LAST_SEG0		BIT(30)
 #define MTK_WDMA_DESC_CTRL_DMA_DONE		BIT(31)
+#define MTK_WED_RX_BM_TOKEN			GENMASK(31, 16)
 
 struct mtk_wdma_desc {
 	__le32 buf0;
@@ -18,6 +20,10 @@ struct mtk_wdma_desc {
 	__le32 info;
 } __packed __aligned(4);
 
+#define MTK_WED_REV_ID					0x004
+#define MTK_WED_REV_ID_MAJOR				GENMASK(31, 28)
+#define MTK_WED_REV_ID_MINOR				GENMASK(27, 16)
+
 #define MTK_WED_RESET					0x008
 #define MTK_WED_RESET_TX_BM				BIT(0)
 #define MTK_WED_RESET_TX_FREE_AGENT			BIT(4)
@@ -27,6 +33,8 @@ struct mtk_wdma_desc {
 #define MTK_WED_RESET_WED_TX_DMA			BIT(12)
 #define MTK_WED_RESET_WDMA_RX_DRV			BIT(17)
 #define MTK_WED_RESET_WDMA_INT_AGENT			BIT(19)
+#define MTK_WED_RESET_RX_RRO_QM				BIT(20)
+#define MTK_WED_RESET_RX_ROUTE_QM			BIT(21)
 #define MTK_WED_RESET_WED				BIT(31)
 
 #define MTK_WED_CTRL					0x00c
@@ -38,8 +46,12 @@ struct mtk_wdma_desc {
 #define MTK_WED_CTRL_WED_TX_BM_BUSY			BIT(9)
 #define MTK_WED_CTRL_WED_TX_FREE_AGENT_EN		BIT(10)
 #define MTK_WED_CTRL_WED_TX_FREE_AGENT_BUSY		BIT(11)
-#define MTK_WED_CTRL_RESERVE_EN				BIT(12)
-#define MTK_WED_CTRL_RESERVE_BUSY			BIT(13)
+#define MTK_WED_CTRL_WED_RX_BM_EN			BIT(12)
+#define MTK_WED_CTRL_WED_RX_BM_BUSY			BIT(13)
+#define MTK_WED_CTRL_RX_RRO_QM_EN			BIT(14)
+#define MTK_WED_CTRL_RX_RRO_QM_BUSY			BIT(15)
+#define MTK_WED_CTRL_RX_ROUTE_QM_EN			BIT(16)
+#define MTK_WED_CTRL_RX_ROUTE_QM_BUSY			BIT(17)
 #define MTK_WED_CTRL_FINAL_DIDX_READ			BIT(24)
 #define MTK_WED_CTRL_ETH_DMAD_FMT			BIT(25)
 #define MTK_WED_CTRL_MIB_READ_CLEAR			BIT(28)
@@ -84,7 +96,6 @@ struct mtk_wdma_desc {
 #define MTK_WED_EXT_INT_MASK1				0x02c
 #define MTK_WED_EXT_INT_MASK2				0x030
 
-
 #define MTK_WED_TX_BM_CTRL				0x080
 #define MTK_WED_TX_BM_CTRL_VLD_GRP_NUM			GENMASK(6, 0)
 #define MTK_WED_TX_BM_CTRL_RSV_GRP_NUM			GENMASK(22, 16)
@@ -119,7 +130,10 @@ struct mtk_wdma_desc {
 
 #define MTK_WED_TXP_DW0					0x120
 #define MTK_WED_TXP_DW1					0x124
+#define MTK_WED_WPDMA_WRITE_TXP				GENMASK(31, 16)
 #define MTK_WED_TXDP_CTRL				0x130
+#define MTK_WED_TXDP_DW9_OVERWR				BIT(9)
+#define MTK_WED_RX_BM_TKID_MIB				0x1cc
 
 #define MTK_WED_INT_STATUS				0x200
 #define MTK_WED_INT_MASK				0x204
@@ -150,11 +164,23 @@ struct mtk_wdma_desc {
 #define MTK_WED_RESET_IDX_RX				GENMASK(17, 16)
 
 #define MTK_WED_TX_MIB(_n)				(0x2a0 + (_n) * 4)
+#define MTK_WED_RX_MIB(_n)				(0x2e0 + (_n) * 4)
 
 #define MTK_WED_RING_TX(_n)				(0x300 + (_n) * 0x10)
 
 #define MTK_WED_RING_RX(_n)				(0x400 + (_n) * 0x10)
 
+#define MTK_WED_RING_RX_DATA(_n)			(0x420 + (_n) * 0x10)
+
+#define MTK_WED_SCR0					0x3c0
+#define MTK_WED_SCR1					0x3c4
+#define MTK_WED_SCR2					0x3c8
+#define MTK_WED_SCR3					0x3cc
+#define MTK_WED_SCR4					0x3d0
+#define MTK_WED_SCR5					0x3d4
+#define MTK_WED_SCR6					0x3d8
+#define MTK_WED_SCR7					0x3dc
+
 #define MTK_WED_WPDMA_INT_TRIGGER			0x504
 #define MTK_WED_WPDMA_INT_TRIGGER_RX_DONE		BIT(1)
 #define MTK_WED_WPDMA_INT_TRIGGER_TX_DONE		GENMASK(5, 4)
@@ -194,19 +220,25 @@ struct mtk_wdma_desc {
 #define MTK_WED_WPDMA_INT_MASK				0x524
 
 #define MTK_WED_WPDMA_INT_CTRL_TX			0x530
-#define MTK_WED_WPDMA_INT_CTRL_TX_DONE_EN0 		BIT(0)
-#define MTK_WED_WPDMA_INT_CTRL_TX_DONE_CLR0 		BIT(1)
-#define MTK_WED_WPDMA_INT_CTRL_TX_DONE_TRIG0		GENMASK(6, 2)
-#define MTK_WED_WPDMA_INT_CTRL_TX_DONE_EN1		BIT(8)
-#define MTK_WED_WPDMA_INT_CTRL_TX_DONE_CLR1		BIT(9)
-#define MTK_WED_WPDMA_INT_CTRL_TX_DONE_TRIG1		GENMASK(14, 10)
+#define MTK_WED_WPDMA_INT_CTRL_TX0_DONE_EN 		BIT(0)
+#define MTK_WED_WPDMA_INT_CTRL_TX0_DONE_CLR		BIT(1)
+#define MTK_WED_WPDMA_INT_CTRL_TX0_DONE_TRIG		GENMASK(6, 2)
+#define MTK_WED_WPDMA_INT_CTRL_TX1_DONE_EN		BIT(8)
+#define MTK_WED_WPDMA_INT_CTRL_TX1_DONE_CLR		BIT(9)
+#define MTK_WED_WPDMA_INT_CTRL_TX1_DONE_TRIG		GENMASK(14, 10)
 
 #define MTK_WED_WPDMA_INT_CTRL_RX			0x534
+#define MTK_WED_WPDMA_INT_CTRL_RX0_EN			BIT(0)
+#define MTK_WED_WPDMA_INT_CTRL_RX0_CLR			BIT(1)
+#define MTK_WED_WPDMA_INT_CTRL_RX0_DONE_TRIG		GENMASK(6, 2)
+#define MTK_WED_WPDMA_INT_CTRL_RX1_EN			BIT(8)
+#define MTK_WED_WPDMA_INT_CTRL_RX1_CLR			BIT(9)
+#define MTK_WED_WPDMA_INT_CTRL_RX1_DONE_TRIG		GENMASK(14, 10)
 
 #define MTK_WED_WPDMA_INT_CTRL_TX_FREE			0x538
-#define MTK_WED_WPDMA_INT_CTRL_TX_FREE_DONE_EN0		BIT(0)
-#define MTK_WED_WPDMA_INT_CTRL_TX_FREE_DONE_CLR0	BIT(1)
-#define MTK_WED_WPDMA_INT_CTRL_TX_FREE_DONE_TRIG0	GENMASK(6, 2)
+#define MTK_WED_WPDMA_INT_CTRL_TX0_FREE_DONE_EN		BIT(0)
+#define MTK_WED_WPDMA_INT_CTRL_TX0_FREE_DONE_CLR	BIT(1)
+#define MTK_WED_WPDMA_INT_CTRL_TX0_FREE_DONE_TRIG	GENMASK(6, 2)
 
 #define MTK_WED_PCIE_CFG_BASE				0x560
 
@@ -222,13 +254,43 @@ struct mtk_wdma_desc {
 #define MTK_WED_WPDMA_TX_MIB(_n)			(0x5a0 + (_n) * 4)
 #define MTK_WED_WPDMA_TX_COHERENT_MIB(_n)		(0x5d0 + (_n) * 4)
 
+#define MTK_WED_WPDMA_RX_MIB(_n)			(0x5e0 + (_n) * 4)
+#define MTK_WED_WPDMA_RX_COHERENT_MIB(_n)		(0x5f0 + (_n) * 4)
+
 #define MTK_WED_WPDMA_RING_TX(_n)			(0x600 + (_n) * 0x10)
 #define MTK_WED_WPDMA_RING_RX(_n)			(0x700 + (_n) * 0x10)
+#define MTK_WED_WPDMA_RING_RX_DATA(_n)		(0x730 + (_n) * 0x10)
+
+
+#define MTK_WED_WPDMA_RX_D_GLO_CFG			0x75c
+#define MTK_WED_WPDMA_RX_D_RX_DRV_EN			BIT(0)
+#define MTK_WED_WPDMA_RX_D_INIT_PHASE_RXEN_SEL		GENMASK(11, 7)
+#define MTK_WED_WPDMA_RX_D_RXD_READ_LEN			GENMASK(31, 24)
+
+#define MTK_WED_WPDMA_RX_D_RST_IDX			0x760
+#define MTK_WED_WPDMA_RX_D_RST_CRX_IDX0			BIT(16)
+#define MTK_WED_WPDMA_RX_D_RST_CRX_IDX1			BIT(17)
+#define MTK_WED_WPDMA_RX_D_RST_DRV_IDX0			BIT(24)
+#define MTK_WED_WPDMA_RX_D_RST_DRV_IDX1			BIT(25)
+
+#define MTK_WED_WPDMA_RX_GLO_CFG			0x76c
+#define MTK_WED_WPDMA_RX_RING				0x770
+
+#define MTK_WED_WPDMA_RX_D_MIB(_n)			(0x774 + (_n) * 4)
+#define MTK_WED_WPDMA_RX_D_PROCESSED_MIB(_n)		(0x784 + (_n) * 4)
+#define MTK_WED_WPDMA_RX_D_COHERENT_MIB			0x78c
+
+#define MTK_WED_WDMA_RING_TX				0x800
+
+#define MTK_WED_WDMA_TX_MIB				0x810
+
+
 #define MTK_WED_WDMA_RING_RX(_n)			(0x900 + (_n) * 0x10)
 #define MTK_WED_WDMA_RX_THRES(_n)			(0x940 + (_n) * 0x4)
 
 #define MTK_WED_WDMA_GLO_CFG				0xa04
 #define MTK_WED_WDMA_GLO_CFG_TX_DRV_EN			BIT(0)
+#define MTK_WED_WDMA_GLO_CFG_TX_DDONE_CHK		BIT(1)
 #define MTK_WED_WDMA_GLO_CFG_RX_DRV_EN			BIT(2)
 #define MTK_WED_WDMA_GLO_CFG_RX_DRV_BUSY		BIT(3)
 #define MTK_WED_WDMA_GLO_CFG_BT_SIZE			GENMASK(5, 4)
@@ -272,6 +334,20 @@ struct mtk_wdma_desc {
 #define MTK_WED_WDMA_RX_RECYCLE_MIB(_n)			(0xae8 + (_n) * 4)
 #define MTK_WED_WDMA_RX_PROCESSED_MIB(_n)		(0xaf0 + (_n) * 4)
 
+#define MTK_WED_RX_BM_RX_DMAD				0xd80
+#define MTK_WED_RX_BM_RX_DMAD_SDL0			GENMASK(13, 0)
+
+#define MTK_WED_RX_BM_BASE				0xd84
+#define MTK_WED_RX_BM_INIT_PTR				0xd88
+#define MTK_WED_RX_BM_SW_TAIL 				GENMASK(15, 0)
+#define MTK_WED_RX_BM_INIT_SW_TAIL			BIT(16)
+
+#define MTK_WED_RX_PTR					0xd8c
+
+#define MTK_WED_RX_BM_DYN_ALLOC_TH			0xdb4
+#define MTK_WED_RX_BM_DYN_ALLOC_TH_H			GENMASK(31, 16)
+#define MTK_WED_RX_BM_DYN_ALLOC_TH_L			GENMASK(15, 0)
+
 #define MTK_WED_RING_OFS_BASE				0x00
 #define MTK_WED_RING_OFS_COUNT				0x04
 #define MTK_WED_RING_OFS_CPU_IDX			0x08
@@ -281,10 +357,13 @@ struct mtk_wdma_desc {
 #define MTK_WDMA_RING_RX(_n)				(0x100 + (_n) * 0x10)
 
 #define MTK_WDMA_GLO_CFG				0x204
-#define MTK_WDMA_GLO_CFG_RX_INFO_PRERES			GENMASK(28, 26)
+#define MTK_WDMA_GLO_CFG_TX_DMA_EN			BIT(0)
+#define MTK_WDMA_GLO_CFG_RX_DMA_EN			BIT(2)
+#define MTK_WDMA_GLO_CFG_RX_INFO2_PRERES		BIT(27)
+#define MTK_WDMA_GLO_CFG_RX_INFO1_PRERES		BIT(28)
 
 #define MTK_WDMA_RESET_IDX				0x208
-#define MTK_WDMA_RESET_IDX_TX				GENMASK(3, 0)
+#define MTK_WDMA_RESET_IDX_TX				GENMASK(1, 0)
 #define MTK_WDMA_RESET_IDX_RX				GENMASK(17, 16)
 #define MTK_WDMA_INT_STATUS				0x220
 
@@ -303,4 +382,70 @@ struct mtk_wdma_desc {
 #define MTK_PCIE_MIRROR_MAP_EN				BIT(0)
 #define MTK_PCIE_MIRROR_MAP_WED_ID			BIT(1)
 
+#define MTK_WED_RTQM_GLO_CFG				0xb00
+#define MTK_WED_RTQM_BUSY	 			BIT(1)
+#define MTK_WED_RTQM_Q_RST	 			BIT(2)
+#define MTK_WED_RTQM_Q_DBG_BYPASS			BIT(5)
+#define MTK_WED_RTQM_TXDMAD_FPORT			GENMASK(23, 20)
+
+#define MTK_WED_RTQM_R2H_MIB(_n)			(0xb70 + (_n) * 0x4)
+#define MTK_WED_RTQM_R2Q_MIB(_n)			(0xb78 + (_n) * 0x4)
+#define MTK_WED_RTQM_Q2N_MIB				0xb80
+#define MTK_WED_RTQM_Q2H_MIB(_n)			(0xb84 + (_n) * 0x4)
+
+#define MTK_WED_RTQM_Q2B_MIB				0xb8c
+#define MTK_WED_RTQM_PFDBK_MIB				0xb90
+
+#define MTK_WED_RROQM_GLO_CFG				0xc04
+#define MTK_WED_RROQM_RST_IDX				0xc08
+#define MTK_WED_RROQM_RST_IDX_MIOD 			BIT(0)
+#define MTK_WED_RROQM_RST_IDX_FDBK 			BIT(4)
+
+#define MTK_WED_RROQM_MIOD_CTRL0			0xc40
+#define MTK_WED_RROQM_MIOD_CTRL1			0xc44
+#define MTK_WED_RROQM_MIOD_CNT 				GENMASK(11, 0)
+
+#define MTK_WED_RROQM_MIOD_CTRL2			0xc48
+#define MTK_WED_RROQM_MIOD_CTRL3			0xc4c
+
+#define MTK_WED_RROQM_FDBK_CTRL0			0xc50
+#define MTK_WED_RROQM_FDBK_CTRL1			0xc54
+#define MTK_WED_RROQM_FDBK_CNT 				GENMASK(11, 0)
+
+#define MTK_WED_RROQM_FDBK_CTRL2			0xc58
+
+#define MTK_WED_RROQ_BASE_L				0xc80
+#define MTK_WED_RROQ_BASE_H				0xc84
+
+
+#define MTK_WED_RROQM_MIOD_CFG                          0xc8c
+#define MTK_WED_RROQM_MIOD_MID_DW 			GENMASK(5, 0)
+#define MTK_WED_RROQM_MIOD_MOD_DW			GENMASK(13, 8)
+#define MTK_WED_RROQM_MIOD_ENTRY_DW			GENMASK(22, 16)
+
+#define MTK_WED_RROQM_MID_MIB				0xcc0
+#define MTK_WED_RROQM_MOD_MIB				0xcc4
+#define MTK_WED_RROQM_MOD_COHERENT_MIB			0xcc8
+#define MTK_WED_RROQM_FDBK_MIB				0xcd0
+#define MTK_WED_RROQM_FDBK_COHERENT_MIB			0xcd4
+#define MTK_WED_RROQM_FDBK_IND_MIB			0xce0
+#define MTK_WED_RROQM_FDBK_ENQ_MIB			0xce4
+#define MTK_WED_RROQM_FDBK_ANC_MIB			0xce8
+#define MTK_WED_RROQM_FDBK_ANC2H_MIB			0xcec
+
+#define MTK_WED_RX_BM_RX_DMAD  				0xd80
+#define MTK_WED_RX_BM_BASE				0xd84
+#define MTK_WED_RX_BM_INIT_PTR				0xd88
+#define MTK_WED_RX_BM_PTR	      			0xd8c
+#define MTK_WED_RX_BM_PTR_HEAD				GENMASK(32, 16)
+#define MTK_WED_RX_BM_PTR_TAIL				GENMASK(15, 0)
+
+#define MTK_WED_RX_BM_BLEN	      			0xd90
+#define MTK_WED_RX_BM_STS				0xd94
+#define MTK_WED_RX_BM_INTF2				0xd98
+#define MTK_WED_RX_BM_INTF				0xd9c
+#define MTK_WED_RX_BM_ERR_STS				0xda8
+
+#define MTK_WED_WOCPU_VIEW_MIOD_BASE		 	0x8000
+
 #endif
diff --git a/drivers/net/ethernet/mediatek/mtk_wed_wo.c b/drivers/net/ethernet/mediatek/mtk_wed_wo.c
new file mode 100644
index 0000000..6b53dc5
--- /dev/null
+++ b/drivers/net/ethernet/mediatek/mtk_wed_wo.c
@@ -0,0 +1,562 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/kernel.h>
+#include <linux/bitfield.h>
+#include <linux/dma-mapping.h>
+#include <linux/skbuff.h>
+#include <linux/of_platform.h>
+#include <linux/interrupt.h>
+#include <linux/of_address.h>
+#include <linux/iopoll.h>
+#include <linux/soc/mediatek/mtk_wed.h>
+#include "mtk_wed.h"
+#include "mtk_wed_regs.h"
+#include "mtk_wed_ccif.h"
+#include "mtk_wed_wo.h"
+
+struct wed_wo_profile_stat profile_total[6] = {
+	{1001, 0},
+	{1501, 0},
+	{3001, 0},
+	{5001, 0},
+	{10001, 0},
+	{0xffffffff, 0}
+};
+
+struct wed_wo_profile_stat profiling_mod[6] = {
+	{1001, 0},
+	{1501, 0},
+	{3001, 0},
+	{5001, 0},
+	{10001, 0},
+	{0xffffffff, 0}
+};
+
+struct wed_wo_profile_stat profiling_rro[6] = {
+	{1001, 0},
+	{1501, 0},
+	{3001, 0},
+	{5001, 0},
+	{10001, 0},
+	{0xffffffff, 0}
+};
+
+static void
+woif_q_sync_idx(struct mtk_wed_wo *wo, struct wed_wo_queue *q)
+{
+	woccif_w32(wo, q->regs->desc_base, q->desc_dma);
+	woccif_w32(wo, q->regs->ring_size, q->ndesc);
+
+	q->head = woccif_r32(wo, q->regs->dma_idx) + 1;
+	q->tail = q->head;
+}
+
+static void
+woif_q_reset(struct mtk_wed_wo *dev, struct wed_wo_queue *q)
+{
+
+	if (!q || !q->ndesc)
+		return;
+
+	woccif_w32(dev, q->regs->cpu_idx, 0);
+
+	woif_q_sync_idx(dev, q);
+}
+
+static void
+woif_q_kick(struct mtk_wed_wo *wo, struct wed_wo_queue *q)
+{
+	wmb();
+	woccif_w32(wo, q->regs->cpu_idx, q->head);
+}
+
+static int
+woif_q_rx_fill(struct mtk_wed_wo *wo, struct wed_wo_queue *q)
+{
+	dma_addr_t addr;
+	void *buf;
+	int frames = 0;
+	int len = q->buf_size;
+	struct wed_wo_desc *desc;
+	struct wed_wo_queue_entry *entry;
+	u32 ctrl = 0;
+
+	if (!q->ndesc)
+		return 0;
+
+	spin_lock_bh(&q->lock);
+
+	while (q->queued < q->ndesc - 1) {
+
+		buf = page_frag_alloc(&q->rx_page, len, GFP_ATOMIC);
+		if (!buf)
+			break;
+
+		addr = dma_map_single(wo->hw->dev, buf, len, DMA_FROM_DEVICE);
+		if (unlikely(dma_mapping_error(wo->hw->dev, addr))) {
+			skb_free_frag(buf);
+			break;
+		}
+		dma_sync_single_for_cpu(wo->hw->dev, addr, len,
+					DMA_TO_DEVICE);
+		desc = &q->desc[q->head];
+		entry = &q->entry[q->head];
+
+		entry->dma_addr = addr;
+		entry->dma_len = len;
+
+		ctrl = FIELD_PREP(WED_CTL_SD_LEN0, entry->dma_len);
+		ctrl |= WED_CTL_LAST_SEC0;
+
+		WRITE_ONCE(desc->buf0, cpu_to_le32(addr));
+		WRITE_ONCE(desc->ctrl, cpu_to_le32(ctrl));
+		dma_sync_single_for_device(wo->hw->dev, addr, len,
+					DMA_TO_DEVICE);
+		q->queued++;
+		q->entry[q->head].buf = buf;
+
+		q->head = (q->head + 1) % q->ndesc;
+		frames++;
+	}
+
+	spin_unlock_bh(&q->lock);
+
+	return frames;
+}
+
+static void
+woif_q_rx_fill_process(struct mtk_wed_wo *wo, struct wed_wo_queue *q)
+{
+	if(woif_q_rx_fill(wo, q)) {
+		spin_lock_bh(&q->lock);
+		q->head -= 1;
+		woif_q_kick(wo, q);
+		q->head += 1;
+		spin_unlock_bh(&q->lock);
+	}
+}
+
+static int
+woif_q_alloc(struct mtk_wed_wo *dev, struct wed_wo_queue *q,
+		     int n_desc, int bufsize, int idx,
+		     struct wed_wo_queue_regs *regs)
+{
+	struct wed_wo_queue_regs *q_regs;
+	int size;
+
+	spin_lock_init(&q->lock);
+	spin_lock_init(&q->cleanup_lock);
+
+	q_regs = devm_kzalloc(dev->hw->dev, sizeof(*q_regs), GFP_KERNEL);
+
+	q_regs->desc_base = regs->desc_base;
+	q_regs->ring_size = regs->ring_size;
+	q_regs->cpu_idx = regs->cpu_idx;
+	q_regs->dma_idx = regs->dma_idx;
+
+	q->regs = q_regs;
+	q->ndesc = n_desc;
+	q->buf_size = bufsize;
+	q->hw_idx = idx;
+
+	size = q->ndesc * sizeof(struct wed_wo_desc);
+
+	q->desc = dmam_alloc_coherent(dev->hw->dev, size, &q->desc_dma, GFP_KERNEL);
+	if (!q->desc)
+		return -ENOMEM;
+
+	size = q->ndesc * sizeof(*q->entry);
+	q->entry = devm_kzalloc(dev->hw->dev, size, GFP_KERNEL);
+	if (!q->entry)
+		return -ENOMEM;
+	if (idx == 0)
+		woif_q_reset(dev, &dev->q_tx);
+
+	return 0;
+}
+
+static void
+woif_q_tx_clean(struct mtk_wed_wo *wo, struct wed_wo_queue *q, bool flush)
+{
+	int last;
+
+	if (!q || !q->ndesc)
+		return;
+
+	spin_lock_bh(&q->cleanup_lock);
+	if (flush)
+		last = -1;
+	else
+		last = readl(&q->regs->dma_idx);
+
+	while (q->queued > 0 && q->tail != last) {
+		struct wed_wo_queue_entry *e;
+
+		e = &q->entry[q->tail];
+
+		dma_unmap_single(wo->hw->dev, e->dma_addr, e->dma_len,
+				 DMA_TO_DEVICE);
+
+		if (e->skb)
+			dev_kfree_skb(e->skb);
+
+		memset(e, 0, sizeof(*e));
+
+		spin_lock_bh(&q->lock);
+		q->tail = (q->tail + 1) % q->ndesc;
+		q->queued--;
+		spin_unlock_bh(&q->lock);
+
+		if (!flush && q->tail == last)
+			last = readl(&q->regs->dma_idx);
+	}
+	spin_unlock_bh(&q->cleanup_lock);
+
+	if (flush) {
+		spin_lock_bh(&q->lock);
+		woif_q_sync_idx(wo, q);
+		woif_q_kick(wo, q);
+		spin_unlock_bh(&q->lock);
+	}
+}
+
+static void
+woif_q_rx_clean(struct mtk_wed_wo *wo, struct wed_wo_queue *q)
+{
+}
+
+static void *
+woif_q_deq(struct mtk_wed_wo *wo, struct wed_wo_queue *q, bool flush,
+		 int *len, u32 *info, bool *more)
+{
+	int idx = q->tail;
+	struct wed_wo_desc *desc;
+	struct wed_wo_queue_entry *e;
+	void *buf;
+	int buf_len = SKB_WITH_OVERHEAD(q->buf_size);
+
+	*more = false;
+	if (!q->queued)
+		return NULL;
+
+	if (flush)
+		q->desc[idx].ctrl |= cpu_to_le32(WED_CTL_DMA_DONE);
+	else if (!(q->desc[idx].ctrl & cpu_to_le32(WED_CTL_DMA_DONE))){
+		/* TDO: err handle */
+		return NULL;
+	}
+
+	q->tail = (q->tail + 1) % q->ndesc;
+	q->queued--;
+
+	desc = &q->desc[idx];
+	e = &q->entry[idx];
+
+	buf = e->buf;
+	if (len) {
+		u32 ctl = le32_to_cpu(READ_ONCE(desc->ctrl));
+		*len = FIELD_GET(WED_CTL_SD_LEN0, ctl);
+		*more = !(ctl & WED_CTL_LAST_SEC0);
+	}
+
+	if (info)
+		*info = le32_to_cpu(desc->info);
+	if(buf)
+		dma_unmap_single(wo->hw->dev, e->dma_addr, buf_len, DMA_FROM_DEVICE);
+	e->skb = NULL;
+
+	return buf;
+}
+
+static int
+woif_q_init(struct mtk_wed_wo *dev,
+	       int (*poll)(struct napi_struct *napi, int budget))
+{
+	init_dummy_netdev(&dev->napi_dev);
+	snprintf(dev->napi_dev.name, sizeof(dev->napi_dev.name), "%s",
+		 "woif_q");
+	//dev->napi_dev.threaded = 1;
+
+	if (dev->q_rx.ndesc) {
+		netif_napi_add(&dev->napi_dev,
+					   &dev->napi, poll, 64);
+
+		woif_q_rx_fill(dev, &dev->q_rx);
+		woif_q_reset(dev, &dev->q_rx);
+		napi_enable(&dev->napi);
+	}
+
+	return 0;
+}
+
+void woif_q_rx_skb(struct mtk_wed_wo *wo, struct sk_buff *skb)
+{
+	int ret;
+	struct wed_cmd_hdr *hdr = (struct wed_cmd_hdr *)skb->data;
+
+	ret = mtk_wed_mcu_cmd_sanity_check(wo, skb);
+	if (ret)
+		goto free_skb;
+
+	if (WED_WO_CMD_FLAG_IS_RSP(hdr)) {
+		//pr_info("%s: wo cmd rsp\n", __func__);
+		mtk_wed_mcu_rx_event(wo, skb);
+		return;
+	} else {
+		mtk_wed_mcu_rx_unsolicited_event(wo, skb);
+		return;
+	}
+
+free_skb:
+	dev_kfree_skb(skb);
+}
+
+static int
+woif_q_tx_skb(struct mtk_wed_wo *wo, struct wed_wo_queue *q,
+		      struct sk_buff *skb)//woif_bus_tx
+{
+	int len, ret = -ENOMEM;
+	dma_addr_t addr;
+	struct wed_wo_desc *desc;
+	struct wed_wo_queue_entry *entry;
+	int idx = -1;
+	u32 ctrl = 0;
+
+	len = skb->len;
+	addr = dma_map_single(wo->hw->dev, skb->data, len, DMA_TO_DEVICE);
+	if (unlikely(dma_mapping_error(wo->hw->dev, addr)))
+		goto error;
+
+	/* packet tx, force trigger tx clean. */
+	if (q->queued + MTK_WED_WO_TXQ_FREE_THR >= q->ndesc - 1)
+		woif_q_tx_clean(wo, q, false);
+
+	if (q->queued + 1 >= q->ndesc - 1) {
+		ret = -ENOMEM;
+		goto error;
+	}
+
+	spin_lock_bh(&q->lock);
+
+	dma_sync_single_for_device(wo->hw->dev, addr, len,
+					   DMA_TO_DEVICE);
+
+	wo->drv_ops->kickout_t(wo, true);
+	idx = q->head;
+
+	desc = &q->desc[idx];
+	entry = &q->entry[idx];
+
+	entry->dma_addr = addr;
+	entry->dma_len = len;
+
+	ctrl = FIELD_PREP(WED_CTL_SD_LEN0, len);
+	ctrl |= WED_CTL_LAST_SEC0;
+	ctrl |= WED_CTL_DMA_DONE;
+
+	WRITE_ONCE(desc->buf0, cpu_to_le32(addr));
+	WRITE_ONCE(desc->ctrl, cpu_to_le32(ctrl));
+
+	q->queued++;
+	q->entry[idx].skb = skb;
+
+	woif_q_kick(wo, q);
+	wo->drv_ops->kickout(wo);
+
+	q->head = (q->head + 1) % q->ndesc;
+	spin_unlock_bh(&q->lock);
+	return 0;
+
+error:
+	dev_kfree_skb(skb);
+	return -ENOMEM;
+}
+
+static const struct wed_wo_queue_ops wo_queue_ops = {
+	.init = woif_q_init,
+	.alloc = woif_q_alloc,
+	.reset = woif_q_reset,
+	.tx_skb = woif_q_tx_skb,
+	.tx_clean = woif_q_tx_clean,
+	.rx_clean = woif_q_rx_clean,
+	.kick = woif_q_kick,
+};
+
+static int
+mtk_wed_wo_rx_process(struct mtk_wed_wo *wo, struct wed_wo_queue *q, int budget)
+{
+	int len, data_len, done = 0;
+	struct sk_buff *skb;
+	unsigned char *data;
+	bool more;
+
+	while (done < budget) {
+		u32 info;
+
+		data = woif_q_deq(wo, q, false, &len, &info, &more);
+		if (!data)
+			break;
+
+		data_len = SKB_WITH_OVERHEAD(q->buf_size);
+
+		if (data_len < len) {
+			skb_free_frag(data);
+			continue;
+		}
+
+		skb = build_skb(data, q->buf_size);
+		if (!skb) {
+			skb_free_frag(data);
+			continue;
+		}
+
+		__skb_put(skb, len);
+		done++;
+
+		woif_q_rx_skb(wo, skb);
+	}
+
+	woif_q_rx_fill_process(wo, q);
+	return done;
+}
+
+void mtk_wed_wo_set_isr_mask(struct mtk_wed_wo *wo, bool set,
+		       u32 clear, u32 val)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&wo->ccif.irq_lock, flags);
+	wo->ccif.irqmask &= ~clear;
+	wo->ccif.irqmask |= val;
+	if (set)
+		wo->drv_ops->set_isr(wo, wo->ccif.irqmask);
+
+	spin_unlock_irqrestore(&wo->ccif.irq_lock, flags);
+}
+
+static inline void mtk_wed_wo_set_ack_mask(struct mtk_wed_wo *wo, u32 mask)
+{
+	wo->drv_ops->set_ack(wo, mask);
+}
+
+static void mtk_wed_wo_poll_complete(struct mtk_wed_wo *wo)
+{
+	mtk_wed_wo_set_ack_mask(wo, wo->ccif.q_int_mask);
+	mtk_wed_wo_isr_enable(wo, wo->ccif.q_int_mask);
+}
+
+int mtk_wed_wo_rx_poll(struct napi_struct *napi, int budget)
+{
+	struct mtk_wed_wo *wo;
+	int done = 0, cur;
+
+	wo = container_of(napi->dev, struct mtk_wed_wo, napi_dev);
+
+	rcu_read_lock();
+
+	do {
+		cur = mtk_wed_wo_rx_process(wo, &wo->q_rx, budget - done);
+		/* rx packet handle */
+		done += cur;
+	} while (cur && done < budget);
+
+	rcu_read_unlock();
+
+	if (done < budget && napi_complete(napi))
+		mtk_wed_wo_poll_complete(wo);
+
+	return done;
+}
+
+static void mtk_wed_wo_isr_tasklet(unsigned long data)
+{
+	struct mtk_wed_wo *wo = (struct mtk_wed_wo *)data;
+	u32 intr, mask;
+
+	/* disable isr */
+	wo->drv_ops->set_isr(wo, 0);
+
+	intr = wo->drv_ops->get_csr(wo);
+	intr &= wo->ccif.irqmask;
+
+
+
+	mask = intr & (wo->ccif.q_int_mask | wo->ccif.q_exep_mask);
+	mtk_wed_wo_isr_disable(wo, mask);
+
+	if (intr & wo->ccif.q_int_mask)
+		napi_schedule(&wo->napi);
+
+	if (intr & wo->ccif.q_exep_mask) {
+		/* todo */
+	}
+}
+
+static irqreturn_t mtk_wed_wo_isr_handler(int irq, void *wo_instance)
+{
+	struct mtk_wed_wo *wo = wo_instance;
+
+	wo->drv_ops->set_isr(wo, 0);
+
+	tasklet_schedule(&wo->irq_tasklet);
+
+	return IRQ_HANDLED;
+}
+
+/* add for wed wo cpu */
+int mtk_wed_wo_init(struct mtk_wed_hw *hw)
+{
+	struct mtk_wed_wo *wo;
+	int ret = 0;
+
+	wo = kzalloc(sizeof(*wo), GFP_KERNEL);
+	if (!wo)
+		return -ENOMEM;
+
+	wo->hw = hw;
+	wo->queue_ops = &wo_queue_ops;
+	hw->wed_wo = wo;
+
+	tasklet_init(&wo->irq_tasklet, mtk_wed_wo_isr_tasklet, (unsigned long)wo);
+
+	skb_queue_head_init(&wo->mcu.res_q);
+	init_waitqueue_head(&wo->mcu.wait);
+	mutex_init(&wo->mcu.mutex);
+
+	ret = wed_wo_hardware_init(wo, (void *)mtk_wed_wo_isr_handler);
+	if (ret)
+		goto error;
+
+	/* fw download */
+	ret = wed_wo_mcu_init(wo);
+	if (ret)
+		goto error;
+
+	ret = mtk_wed_exception_init(wo);
+	if (ret)
+		pr_info("%s : mtk_wed_exception_init failed\n", __func__);
+
+	return ret;
+
+error:
+	kfree(wo);
+
+	return ret;
+}
+
+void mtk_wed_wo_exit(struct mtk_wed_hw *hw)
+{
+/*
+#ifdef CONFIG_WED_HW_RRO_SUPPORT
+		woif_bus_exit(woif);
+		wo_exception_exit(woif);
+#endif
+*/
+	struct mtk_wed_wo *wo = hw->wed_wo;
+
+	if (wo->exp.log) {
+		dma_unmap_single(wo->hw->dev, wo->exp.phys, wo->exp.log_size, DMA_FROM_DEVICE);
+		kfree(wo->exp.log);
+	}
+
+}
diff --git a/drivers/net/ethernet/mediatek/mtk_wed_wo.h b/drivers/net/ethernet/mediatek/mtk_wed_wo.h
new file mode 100644
index 0000000..d6b95e2
--- /dev/null
+++ b/drivers/net/ethernet/mediatek/mtk_wed_wo.h
@@ -0,0 +1,330 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (C) 2021 Felix Fietkau <nbd@nbd.name> */
+
+#ifndef __MTK_WED_WO_H
+#define __MTK_WED_WO_H
+
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+#include "mtk_wed.h"
+
+#define WED_CTL_SD_LEN1		GENMASK(13, 0)
+#define WED_CTL_LAST_SEC1	BIT(14)
+#define WED_CTL_BURST		BIT(15)
+#define WED_CTL_SD_LEN0_SHIFT	16
+#define WED_CTL_SD_LEN0		GENMASK(29, 16)
+#define WED_CTL_LAST_SEC0	BIT(30)
+#define WED_CTL_DMA_DONE	BIT(31)
+#define WED_INFO_WINFO		GENMASK(15, 0)
+
+#define MTK_WED_WO_TXQ_FREE_THR		10
+
+#define WED_WO_PROFILE_MAX_LVL		6
+
+
+enum mtk_wed_fw_region_id {
+	WO_REGION_EMI = 0,
+	WO_REGION_ILM,
+	WO_REGION_DATA,
+	WO_REGION_BOOT,
+	__WO_REGION_MAX
+};
+
+struct wed_wo_profile_stat {
+	u32 bound;
+	u32 record;
+};
+
+#define PROFILE_STAT(record, val) do {			\
+		u8 lvl = 0;				\
+		while (lvl < WED_WO_PROFILE_MAX_LVL) {	\
+			if (val < record[lvl].bound) {	\
+				record[lvl].record++;	\
+				break;			\
+			}				\
+			lvl++;				\
+		}					\
+	} while (0)
+
+/* align with wo report structure */
+struct wed_wo_log {
+	u32 sn;
+	u32 total;
+	u32 rro;
+	u32 mod;
+};
+
+struct wed_wo_rxcnt {
+	u16 wlan_idx;
+	u16 tid;
+	u32 rx_pkt_cnt;
+	u32 rx_byte_cnt;
+	u32 rx_err_cnt;
+	u32 rx_drop_cnt;
+};
+
+struct wed_wo_queue {
+	struct wed_wo_queue_regs *regs;
+
+	spinlock_t lock;
+	spinlock_t cleanup_lock;
+	struct wed_wo_queue_entry *entry;
+	struct wed_wo_desc *desc;
+
+	u16 first;
+	u16 head;
+	u16 tail;
+	int ndesc;
+	int queued;
+	int buf_size;
+
+	u8 hw_idx;
+	u8 qid;
+	u8 flags;
+
+	dma_addr_t desc_dma;
+	struct page_frag_cache rx_page;
+};
+
+
+struct wed_wo_mmio {
+	void __iomem *regs;
+
+	spinlock_t irq_lock;
+	u8 irq;
+	u32 irqmask;
+
+	u32 q_int_mask;
+	u32 q_exep_mask;
+};
+
+struct wed_wo_mcu {
+	struct mutex mutex;
+	u32 msg_seq;
+	int timeout;
+
+	struct sk_buff_head res_q;
+	wait_queue_head_t wait;
+};
+
+struct wed_wo_exception {
+	void* log;
+	int log_size;
+	dma_addr_t phys;
+};
+
+struct wed_wo_queue_regs {
+	u32 desc_base;
+	u32 ring_size;
+	u32 cpu_idx;
+	u32 dma_idx;
+};
+
+struct wed_wo_desc {
+	__le32 buf0;
+	__le32 ctrl;
+	__le32 buf1;
+	__le32 info;
+	__le32 reserved[4];
+} __packed __aligned(32);
+
+struct wed_wo_queue_entry {
+	union {
+		void *buf;
+		struct sk_buff *skb;
+	};
+
+	u32 dma_addr;
+	u16 dma_len;
+	u16 wcid;
+	bool skip_buf0:1;
+	bool skip_buf1:1;
+	bool done:1;
+};
+
+struct wo_cmd_rxcnt_t {
+	u16 wlan_idx;
+	u16 tid;
+	u32 rx_pkt_cnt;
+	u32 rx_byte_cnt;
+	u32 rx_err_cnt;
+	u32 rx_drop_cnt;
+};
+
+struct wo_cmd_query {
+	u32 query0;
+	u32 query1;
+};
+
+struct wed_cmd_hdr {
+	/*DW0*/
+	u8 ver;
+	u8 cmd_id;
+	u16 length;
+
+	/*DW1*/
+	u16 uni_id;
+	u16 flag;
+
+	/*DW2*/
+	int status;
+
+	/*DW3*/
+	u8 reserved[20];
+};
+
+struct mtk_wed_fw_region {
+	void *addr;
+	u32 addr_pa;
+	u32 size;
+	u32 shared;
+};
+
+struct wed_wo_queue_ops;
+struct wed_wo_drv_ops;
+struct wed_wo_mcu_ops;
+
+struct wo_rx_total_cnt {
+	u64 rx_pkt_cnt;
+	u64 rx_byte_cnt;
+	u64 rx_err_cnt;
+	u64 rx_drop_cnt;
+};
+
+struct mtk_wed_wo {
+	struct mtk_wed_hw *hw;
+
+	struct wed_wo_mmio ccif;
+	struct wed_wo_mcu mcu;
+	struct wed_wo_exception exp;
+
+	const struct wed_wo_drv_ops *drv_ops;
+	const struct wed_wo_mcu_ops *mcu_ops;
+	const struct wed_wo_queue_ops *queue_ops;
+
+	struct net_device napi_dev;
+	spinlock_t rx_lock;
+	struct napi_struct napi;
+	struct sk_buff_head rx_skb;
+	struct wed_wo_queue q_rx;
+	struct tasklet_struct irq_tasklet;
+
+	struct wed_wo_queue q_tx;
+
+	struct mtk_wed_fw_region region[__WO_REGION_MAX];
+
+	struct wed_wo_profile_stat total[WED_WO_PROFILE_MAX_LVL];
+	struct wed_wo_profile_stat mod[WED_WO_PROFILE_MAX_LVL];
+	struct wed_wo_profile_stat rro[WED_WO_PROFILE_MAX_LVL];
+	char dirname[4];
+	struct wo_rx_total_cnt wo_rxcnt[8][544];
+};
+
+struct wed_wo_queue_ops {
+	int (*init)(struct mtk_wed_wo *wo,
+		    int (*poll)(struct napi_struct *napi, int budget));
+
+	int (*alloc)(struct mtk_wed_wo *wo, struct wed_wo_queue *q,
+		     int idx, int n_desc, int bufsize,
+		     struct wed_wo_queue_regs *regs);
+
+	void (*reset)(struct mtk_wed_wo *wo, struct wed_wo_queue *q);
+
+	int (*tx_skb)(struct mtk_wed_wo *wo, struct wed_wo_queue *q,
+		      struct sk_buff *skb);
+	int (*tx_skb1)(struct mtk_wed_wo *wo, struct wed_wo_queue *q,
+		       u8 *msg, u32 msg_len);
+	void (*tx_clean)(struct mtk_wed_wo *wo, struct wed_wo_queue *q,
+			 bool flush);
+
+	void (*rx_clean)(struct mtk_wed_wo *wo, struct wed_wo_queue *q);
+
+	void (*kick)(struct mtk_wed_wo *wo, struct wed_wo_queue *q);
+};
+
+struct wed_wo_drv_ops {
+	void (*kickout_t)(struct mtk_wed_wo *wo, bool busy);
+	void (*kickout)(struct mtk_wed_wo *wo);
+	void (*set_ack)(struct mtk_wed_wo *wo, u32 mask);
+	void (*set_isr)(struct mtk_wed_wo *wo, u32 mask);
+	u32 (*get_csr)(struct mtk_wed_wo *wo);
+	int (*tx_prepare_skb)(struct mtk_wed_wo *wo);
+	bool (*check_excpetion)(struct mtk_wed_wo *wo);
+	void (*clear_int)(struct mtk_wed_wo *wo, u32 mask);
+};
+
+struct wed_wo_mcu_ops {
+	u32 headroom;
+
+	int (*mcu_skb_send_msg)(struct mtk_wed_wo *wo, int to_id,
+				int cmd, struct sk_buff *skb,
+				int *seq, bool wait_resp);
+
+	int (*mcu_parse_response)(struct mtk_wed_wo *wo, int cmd,
+				  struct sk_buff *skb, int seq);
+
+	int (*mcu_restart)(struct mtk_wed_wo *wo);
+};
+
+#define mtk_wed_wo_q_init(wo, ...)	(wo)->queue_ops->init((wo), __VA_ARGS__)
+#define mtk_wed_wo_q_alloc(wo, ...)	(wo)->queue_ops->alloc((wo), __VA_ARGS__)
+#define mtk_wed_wo_q_reset(wo, ...)	(wo)->queue_ops->init((wo), __VA_ARGS__)
+#define mtk_wed_wo_q_tx_skb(wo, ...)	(wo)->queue_ops->tx_skb((wo), __VA_ARGS__)
+#define mtk_wed_wo_q_tx_skb1(wo, ...)	(wo)->queue_ops->tx_skb1((wo), __VA_ARGS__)
+#define mtk_wed_wo_q_tx_clean(wo, ...)	(wo)->queue_ops->tx_clean((wo), __VA_ARGS__)
+#define mtk_wed_wo_q_rx_clean(wo, ...)	(wo)->queue_ops->rx_clean((wo), __VA_ARGS__)
+#define mtk_wed_wo_q_kick(wo, ...)	(wo)->queue_ops->kick((wo), __VA_ARGS__)
+
+enum {
+	WARP_CMD_FLAG_RSP		= 1 << 0, /* is responce*/
+	WARP_CMD_FLAG_NEED_RSP		= 1 << 1, /* need responce */
+	WARP_CMD_FLAG_FROM_TO_WO	= 1 << 2, /* send between host and wo */
+};
+
+#define WED_WO_CMD_FLAG_IS_RSP(_hdr)		((_hdr)->flag & (WARP_CMD_FLAG_RSP))
+#define WED_WO_CMD_FLAG_SET_RSP(_hdr)		((_hdr)->flag |= (WARP_CMD_FLAG_RSP))
+#define WED_WO_CMD_FLAG_IS_NEED_RSP(_hdr)	((_hdr)->flag & (WARP_CMD_FLAG_NEED_RSP))
+#define WED_WO_CMD_FLAG_SET_NEED_RSP(_hdr)	((_hdr)->flag |= (WARP_CMD_FLAG_NEED_RSP))
+#define WED_WO_CMD_FLAG_IS_FROM_TO_WO(_hdr)	((_hdr)->flag & (WARP_CMD_FLAG_FROM_TO_WO))
+#define WED_WO_CMD_FLAG_SET_FROM_TO_WO(_hdr)	((_hdr)->flag |= (WARP_CMD_FLAG_FROM_TO_WO))
+
+void mtk_wed_wo_set_isr_mask(struct mtk_wed_wo *wo, bool set,
+			     u32 clear, u32 val);
+
+static inline void mtk_wed_wo_isr_enable(struct mtk_wed_wo *wo, u32 mask)
+{
+	mtk_wed_wo_set_isr_mask(wo, false, 0, mask);
+
+	tasklet_schedule(&wo->irq_tasklet);
+}
+
+static inline void mtk_wed_wo_isr_disable(struct mtk_wed_wo *wo, u32 mask)
+{
+	mtk_wed_wo_set_isr_mask(wo, true, mask, 0);
+}
+
+static inline void
+wo_w32(struct mtk_wed_wo *dev, u32 reg, u32 val)
+{
+	writel(val, dev->region[WO_REGION_BOOT].addr + reg);
+}
+
+static inline u32
+wo_r32(struct mtk_wed_wo *dev, u32 reg)
+{
+	return readl(dev->region[WO_REGION_BOOT].addr + reg);
+}
+static inline void
+woccif_w32(struct mtk_wed_wo *dev, u32 reg, u32 val)
+{
+	writel(val, dev->ccif.regs + reg);
+}
+
+static inline u32
+woccif_r32(struct mtk_wed_wo *dev, u32 reg)
+{
+	return readl(dev->ccif.regs + reg);
+}
+
+#endif
+
-- 
2.18.0

