Merge "[rdk-b][common][bsp][Refactor and sync kernel/wifi from Openwrt]"
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7988a-dsa-10g-spim-nand.dts b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7988a-dsa-10g-spim-nand.dts
index 5af27bd..c7aa87b 100644
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7988a-dsa-10g-spim-nand.dts
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/arch/arm64/boot/dts/mediatek/mt7988a-dsa-10g-spim-nand.dts
@@ -110,6 +110,58 @@
 	status = "okay";
 };
 
+&i2c0 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&i2c0_pins>;
+	status = "okay";
+
+	rt5190a_64: rt5190a@64 {
+		compatible = "richtek,rt5190a";
+		reg = <0x64>;
+		/*interrupts-extended = <&gpio26 0 IRQ_TYPE_LEVEL_LOW>;*/
+		vin2-supply = <&rt5190_buck1>;
+		vin3-supply = <&rt5190_buck1>;
+		vin4-supply = <&rt5190_buck1>;
+
+		regulators {
+			rt5190_buck1: buck1 {
+				regulator-name = "rt5190a-buck1";
+				regulator-min-microvolt = <5090000>;
+				regulator-max-microvolt = <5090000>;
+				regulator-allowed-modes =
+				<RT5190A_OPMODE_AUTO RT5190A_OPMODE_FPWM>;
+				regulator-boot-on;
+			};
+			buck2 {
+				regulator-name = "vcore";
+				regulator-min-microvolt = <600000>;
+				regulator-max-microvolt = <1400000>;
+				regulator-boot-on;
+			};
+			buck3 {
+				regulator-name = "proc";
+				regulator-min-microvolt = <600000>;
+				regulator-max-microvolt = <1400000>;
+				regulator-boot-on;
+			};
+			buck4 {
+				regulator-name = "rt5190a-buck4";
+				regulator-min-microvolt = <850000>;
+				regulator-max-microvolt = <850000>;
+				regulator-allowed-modes =
+				<RT5190A_OPMODE_AUTO RT5190A_OPMODE_FPWM>;
+				regulator-boot-on;
+			};
+			ldo {
+				regulator-name = "rt5190a-ldo";
+				regulator-min-microvolt = <1200000>;
+				regulator-max-microvolt = <1200000>;
+				regulator-boot-on;
+			};
+		};
+	};
+};
+
 &i2c1 {
 	pinctrl-names = "default";
 	pinctrl-0 = <&i2c1_pins>;
@@ -199,6 +251,13 @@
 		};
 	};
 
+	i2c0_pins: i2c0-pins-g0 {
+		mux {
+			function = "i2c";
+			groups = "i2c0_1";
+		};
+	};
+
 	pcie0_pins: pcie0-pins {
 		mux {
 			function = "pcie";
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_eth_dbg.c b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_eth_dbg.c
index 69e92db..c914bc0 100755
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_eth_dbg.c
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_eth_dbg.c
@@ -382,20 +382,31 @@
 				atomic_inc(&reset_lock);
 			break;
 		case 1:
-			if (atomic_read(&force) == 0)
+			if (atomic_read(&force) == 0) {
 				atomic_inc(&force);
-			schedule_work(&eth->pending_work);
+				schedule_work(&eth->pending_work);
+			} else
+				pr_info(" device resetting !!!\n");
 			break;
 		case 2:
 			if (atomic_read(&reset_lock) == 1)
 				atomic_dec(&reset_lock);
 			break;
+		case 3:
+			if (atomic_read(&force) == 0) {
+				atomic_inc(&force);
+				mtk_reset_flag = MTK_FE_STOP_TRAFFIC;
+				schedule_work(&eth->pending_work);
+			} else
+				pr_info(" device resetting !!!\n");
+			break;
 		default:
 			pr_info("Usage: echo [level] > /sys/kernel/debug/mtketh/reset\n");
-			pr_info("Commands:	 [level] \n");
-			pr_info("			   0	 disable reset \n");
-			pr_info("			   1	 force reset \n");
+			pr_info("Commands:	 [level]\n");
+			pr_info("			   0	 disable reset\n");
+			pr_info("			   1	 FE and WDMA reset\n");
 			pr_info("			   2	 enable reset\n");
+			pr_info("			   3	 FE reset\n");
 			break;
 	}
 	return count;
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_eth_reset.c b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_eth_reset.c
index e983d6e..02fd90b 100644
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_eth_reset.c
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_eth_reset.c
@@ -24,6 +24,8 @@
 
 static int mtk_wifi_num = 0;
 static int mtk_rest_cnt = 0;
+u32 mtk_reset_flag = MTK_FE_START_RESET;
+typedef u32 (*mtk_monitor_xdma_func) (struct mtk_eth *eth);
 
 void mtk_reset_event_update(struct mtk_eth *eth, u32 id)
 {
@@ -69,12 +71,16 @@
 	}
 
 	if (i < 1000) {
-		reset_bits = 0;
-
+		reset_bits = RSTCTRL_ETH | RSTCTRL_PPE0;
 		if (MTK_HAS_CAPS(eth->soc->caps, MTK_RSTCTRL_PPE1))
-			reset_bits |= RSTCTRL_ETH | RSTCTRL_PPE0 | RSTCTRL_PPE1;
-		else
-			reset_bits |= RSTCTRL_ETH | RSTCTRL_PPE0;
+			reset_bits |= RSTCTRL_PPE1;
+#if defined(CONFIG_MEDIATEK_NETSYS_V3)
+		if (MTK_HAS_CAPS(eth->soc->caps, MTK_RSTCTRL_PPE2))
+			reset_bits |= RSTCTRL_PPE2;
+		if (mtk_reset_flag == MTK_FE_START_RESET)
+			reset_bits |= RSTCTRL_WDMA0 |
+			RSTCTRL_WDMA1 | RSTCTRL_WDMA2;
+#endif
 
 		regmap_update_bits(eth->ethsys, ETHSYS_RSTCTRL,
 				   reset_bits, reset_bits);
@@ -195,84 +201,124 @@
 	mtk_dump_reg(eth, "GMAC", 0x10000, 0x300);
 }
 
-void mtk_dma_monitor(struct timer_list *t)
+u32 mtk_monitor_wdma_tx(struct mtk_eth *eth)
 {
-	struct mtk_eth *eth = from_timer(eth, t, mtk_dma_monitor_timer);
-	static u32 timestamp = 0;
-	static u32 err_cnt1 = 0, err_cnt2 = 0, err_cnt3 = 0;
-	static u32 prev_wdidx = 0;
-	unsigned int mib_base = MTK_GDM1_TX_GBCNT;
+	static u32 pre_dtx[MTK_WDMA_CNT];
+	static u32 err_cnt[MTK_WDMA_CNT];
+	u32 i = 0, cur_dtx = 0, tx_busy = 0, tx_rdy = 0, err_flag = 0;
+	u32 dbg_mon = 0;
 
-	/*wdma tx path*/
-	u32 cur_wdidx = mtk_r32(eth, MTK_WDMA_DTX_PTR(0));
-	u32 is_wtx_busy = mtk_r32(eth, MTK_WDMA_GLO_CFG(0)) & MTK_TX_DMA_BUSY;
-	u32 is_oq_free = ((mtk_r32(eth, MTK_PSE_OQ_STA(0)) & 0x01FF0000) == 0) &&
-			 ((mtk_r32(eth, MTK_PSE_OQ_STA(1)) & 0x000001FF) == 0) &&
-			 ((mtk_r32(eth, MTK_PSE_OQ_STA(4)) & 0x01FF0000) == 0);
-	u32 is_cdm_full =
-		!(mtk_r32(eth, MTK_WDMA_TX_DBG_MON0(0)) & MTK_CDM_TXFIFO_RDY);
-	/*qdma tx path*/
-	u32 is_qfsm_hang = mtk_r32(eth, MTK_QDMA_FSM) != 0;
-	u32 is_qfwd_hang = mtk_r32(eth, MTK_QDMA_FWD_CNT) == 0;
-	u32 is_qfq_hang = mtk_r32(eth, MTK_QDMA_FQ_CNT) !=
-			  ((MTK_DMA_SIZE << 16) | MTK_DMA_SIZE);
-	u32 is_gdm1_tx = (mtk_r32(eth, MTK_FE_GDM1_FSM) & 0xFFFF0000) > 0;
-	u32 is_gdm2_tx = (mtk_r32(eth, MTK_FE_GDM2_FSM) & 0xFFFF0000) > 0;
-	u32 is_gmac1_tx = (mtk_r32(eth, MTK_MAC_FSM(0)) & 0xFF000000) != 0x1000000;
-	u32 is_gmac2_tx = (mtk_r32(eth, MTK_MAC_FSM(1)) & 0xFF000000) != 0x1000000;
-	u32 gdm1_fc =  mtk_r32(eth, mib_base+0x24);
-	u32 gdm2_fc =  mtk_r32(eth, mib_base+0x64);
-	/*adma rx path*/
-	u32 is_oq0_stuck = (mtk_r32(eth, MTK_PSE_OQ_STA(0)) & 0x1FF) != 0;
-	u32 is_cdm1_busy = (mtk_r32(eth, MTK_FE_CDM1_FSM) & 0xFFFF0000) != 0;
-	u32 is_adma_busy = ((mtk_r32(eth, MTK_ADMA_RX_DBG0) & 0x1F) == 0) &&
-			   ((mtk_r32(eth, MTK_ADMA_RX_DBG0) & 0x40) == 0);
+	for (i = 0; i < MTK_WDMA_CNT; i++) {
+		cur_dtx = mtk_r32(eth, MTK_WDMA_DTX_PTR(i));
+		tx_busy = mtk_r32(eth, MTK_WDMA_GLO_CFG(i)) & MTK_TX_DMA_BUSY;
+		dbg_mon = mtk_r32(eth, MTK_WDMA_TX_DBG_MON0(i));
+		tx_rdy = !(dbg_mon & MTK_CDM_TXFIFO_RDY);
+		if (cur_dtx == pre_dtx[i] && tx_busy && tx_rdy) {
+			err_cnt[i]++;
+			if (err_cnt[i] >= 3) {
+				pr_info("WDMA %d Info\n", i);
+				pr_info("err_cnt = %d", err_cnt[i]);
+				pr_info("prev_dtx = 0x%x	| cur_dtx = 0x%x\n",
+					pre_dtx[i], cur_dtx);
+				pr_info("WDMA_CTX_PTR = 0x%x\n",
+					mtk_r32(eth, MTK_WDMA_CTX_PTR(i)));
+				pr_info("WDMA_DTX_PTR = 0x%x\n",
+					mtk_r32(eth, MTK_WDMA_DTX_PTR(i)));
+				pr_info("WDMA_GLO_CFG = 0x%x\n",
+					mtk_r32(eth, MTK_WDMA_GLO_CFG(i)));
+				pr_info("WDMA_TX_DBG_MON0 = 0x%x\n",
+					mtk_r32(eth, MTK_WDMA_TX_DBG_MON0(i)));
+				pr_info("==============================\n");
+				err_flag = 1;
+			}
+		} else
+			err_cnt[i] = 0;
+		pre_dtx[i] = cur_dtx;
+	}
 
-	if (cur_wdidx == prev_wdidx && is_wtx_busy &&
-	    is_oq_free && is_cdm_full) {
-		err_cnt1++;
-		if (err_cnt1 >= 3) {
-			pr_info("WDMA CDM Info\n");
-			pr_info("============== Time: %d ================\n",
-				timestamp);
-			pr_info("err_cnt1 = %d", err_cnt1);
-			pr_info("prev_wdidx = 0x%x	| cur_wdidx = 0x%x\n",
-				prev_wdidx, cur_wdidx);
-			pr_info("is_wtx_busy = %d | is_oq_free = %d	| is_cdm_full = %d\n",
-				is_wtx_busy, is_oq_free, is_cdm_full);
-			pr_info("-- -- -- -- -- -- --\n");
-			pr_info("WDMA_CTX_PTR = 0x%x\n", mtk_r32(eth, 0x4808));
-			pr_info("WDMA_DTX_PTR = 0x%x\n",
-				mtk_r32(eth, MTK_WDMA_DTX_PTR(0)));
-			pr_info("WDMA_GLO_CFG = 0x%x\n",
-				mtk_r32(eth, MTK_WDMA_GLO_CFG(0)));
-			pr_info("WDMA_TX_DBG_MON0 = 0x%x\n",
-				mtk_r32(eth, MTK_WDMA_TX_DBG_MON0(0)));
-			pr_info("PSE_OQ_STA1 = 0x%x\n",
-				mtk_r32(eth, MTK_PSE_OQ_STA(0)));
-			pr_info("PSE_OQ_STA2 = 0x%x\n",
-				mtk_r32(eth, MTK_PSE_OQ_STA(1)));
-			pr_info("PSE_OQ_STA5 = 0x%x\n",
-				mtk_r32(eth, MTK_PSE_OQ_STA(4)));
-			pr_info("==============================\n");
+	if (err_flag)
+		return MTK_FE_START_RESET;
+	else
+		return 0;
+}
 
-			if ((atomic_read(&reset_lock) == 0) &&
-			    (atomic_read(&force) == 0)){
-				atomic_inc(&force);
-				schedule_work(&eth->pending_work);
+u32 mtk_monitor_wdma_rx(struct mtk_eth *eth)
+{
+	static u32 pre_drx[MTK_WDMA_CNT];
+	static u32 pre_opq[MTK_WDMA_CNT];
+	static u32 err_cnt[MTK_WDMA_CNT];
+	u32 i = 0, cur_drx = 0, rx_busy = 0, err_flag = 0;
+	u32 cur_opq = 0;
+
+	for (i = 0; i < MTK_WDMA_CNT; i++) {
+		cur_drx = mtk_r32(eth, MTK_WDMA_DRX_PTR(i));
+		rx_busy = mtk_r32(eth, MTK_WDMA_GLO_CFG(i)) & MTK_RX_DMA_BUSY;
+		if (i == 0)
+			cur_opq = (mtk_r32(eth, MTK_PSE_OQ_STA(5)) & 0x1FF);
+		else if (i == 1)
+			cur_opq = (mtk_r32(eth, MTK_PSE_OQ_STA(5)) & 0x1FF0000);
+		else
+			cur_opq = (mtk_r32(eth, MTK_PSE_OQ_STA(7)) & 0x1FF0000);
+
+		if (cur_drx == pre_drx[i] && rx_busy && cur_opq != 0 &&
+			cur_opq == pre_opq[i]) {
+			err_cnt[i]++;
+			if (err_cnt[i] >= 3) {
+				pr_info("WDMA %d Info\n", i);
+				pr_info("err_cnt = %d", err_cnt[i]);
+				pr_info("prev_drx = 0x%x	| cur_drx = 0x%x\n",
+					pre_drx[i], cur_drx);
+				pr_info("WDMA_CRX_PTR = 0x%x\n",
+					mtk_r32(eth, MTK_WDMA_CRX_PTR(i)));
+				pr_info("WDMA_DRX_PTR = 0x%x\n",
+					mtk_r32(eth, MTK_WDMA_DRX_PTR(i)));
+				pr_info("WDMA_GLO_CFG = 0x%x\n",
+					mtk_r32(eth, MTK_WDMA_GLO_CFG(i)));
+				pr_info("==============================\n");
+				err_flag = 1;
 			}
-		}
-	} else if (is_qfsm_hang && is_qfwd_hang &&
-		((is_gdm1_tx && is_gmac1_tx && (gdm1_fc < 1)) || (is_gdm2_tx && is_gmac2_tx && (gdm2_fc < 1)))) {
-		err_cnt2++;
-		if (err_cnt2 >= 3) {
+		} else
+			err_cnt[i] = 0;
+		pre_drx[i] = cur_drx;
+		pre_opq[i] = cur_opq;
+	}
+
+	if (err_flag)
+		return MTK_FE_START_RESET;
+	else
+		return 0;
+}
+
+u32 mtk_monitor_rx_fc(struct mtk_eth *eth)
+{
+	u32 i = 0, mib_base = 0, gdm_fc = 0;
+
+	for (i = 0; i < MTK_MAC_COUNT; i++) {
+		mib_base = MTK_GDM1_TX_GBCNT + MTK_STAT_OFFSET*i;
+		gdm_fc =  mtk_r32(eth, mib_base);
+		if (gdm_fc < 1)
+			return 1;
+	}
+	return 0;
+}
+
+u32 mtk_monitor_qdma_tx(struct mtk_eth *eth)
+{
+	static u32 err_cnt_qtx;
+	u32 err_flag = 0;
+	u32 i = 0, is_rx_fc = 0;
+
+	u32 is_qfsm_hang = (mtk_r32(eth, MTK_QDMA_FSM) & 0xF00) != 0;
+	u32 is_qfwd_hang = mtk_r32(eth, MTK_QDMA_FWD_CNT) == 0;
+
+	is_rx_fc = mtk_monitor_rx_fc(eth);
+	if (is_qfsm_hang && is_qfwd_hang && is_rx_fc) {
+		err_cnt_qtx++;
+		if (err_cnt_qtx >= 3) {
 			pr_info("QDMA Tx Info\n");
-			pr_info("============== Time: %d ================\n",
-				timestamp);
-			pr_info("err_cnt2 = %d", err_cnt2);
+			pr_info("err_cnt = %d", err_cnt_qtx);
 			pr_info("is_qfsm_hang = %d\n", is_qfsm_hang);
 			pr_info("is_qfwd_hang = %d\n", is_qfwd_hang);
-			pr_info("is_qfq_hang = %d\n", is_qfq_hang);
 			pr_info("-- -- -- -- -- -- --\n");
 			pr_info("MTK_QDMA_FSM = 0x%x\n",
 				mtk_r32(eth, MTK_QDMA_FSM));
@@ -280,27 +326,70 @@
 				mtk_r32(eth, MTK_QDMA_FWD_CNT));
 			pr_info("MTK_QDMA_FQ_CNT = 0x%x\n",
 				mtk_r32(eth, MTK_QDMA_FQ_CNT));
-			pr_info("GDM1 FC = 0x%x\n",gdm1_fc);
-			pr_info("GDM2 FC = 0x%x\n",gdm2_fc);
 			pr_info("==============================\n");
-
-			if ((atomic_read(&reset_lock) == 0) &&
-			    (atomic_read(&force) == 0)){
-				atomic_inc(&force);
-				schedule_work(&eth->pending_work);
-			}
+			err_flag = 1;
 		}
-	} else if (is_oq0_stuck && is_cdm1_busy && is_adma_busy) {
-		err_cnt3++;
-		if (err_cnt3 >= 3) {
+	} else
+		err_cnt_qtx = 0;
+
+	if (err_flag)
+		return MTK_FE_STOP_TRAFFIC;
+	else
+		return 0;
+}
+
+u32 mtk_monitor_qdma_rx(struct mtk_eth *eth)
+{
+	static u32 err_cnt_qrx;
+	static u32 pre_fq_head, pre_fq_tail;
+	u32 err_flag = 0;
+
+	u32 qrx_fsm = (mtk_r32(eth, MTK_QDMA_FSM) & 0x1F) == 9;
+	u32 fq_head = mtk_r32(eth, MTK_QDMA_FQ_HEAD);
+	u32 fq_tail = mtk_r32(eth, MTK_QDMA_FQ_TAIL);
+
+	if (qrx_fsm && fq_head == pre_fq_head &&
+			fq_tail == pre_fq_tail) {
+		err_cnt_qrx++;
+		if (err_cnt_qrx >= 3) {
+			pr_info("QDMA Rx Info\n");
+			pr_info("err_cnt = %d", err_cnt_qrx);
+			pr_info("MTK_QDMA_FSM = %d\n",
+				mtk_r32(eth, MTK_QDMA_FSM));
+			pr_info("FQ_HEAD = 0x%x\n",
+				mtk_r32(eth, MTK_QDMA_FQ_HEAD));
+			pr_info("FQ_TAIL = 0x%x\n",
+				mtk_r32(eth, MTK_QDMA_FQ_TAIL));
+			err_flag = 1;
+		} else
+			err_cnt_qrx = 0;
+	}
+	pre_fq_head = fq_head;
+	pre_fq_tail = fq_tail;
+
+	if (err_flag)
+		return MTK_FE_STOP_TRAFFIC;
+	else
+		return 0;
+}
+
+
+u32 mtk_monitor_adma_rx(struct mtk_eth *eth)
+{
+	static u32 err_cnt_arx;
+	u32 err_flag = 0;
+	u32 opq0 = (mtk_r32(eth, MTK_PSE_OQ_STA(0)) & 0x1FF) != 0;
+	u32 cdm1_fsm = (mtk_r32(eth, MTK_FE_CDM1_FSM) & 0xFFFF0000) != 0;
+	u32 cur_stat = ((mtk_r32(eth, MTK_ADMA_RX_DBG0) & 0x1F) == 0);
+	u32 fifo_rdy = ((mtk_r32(eth, MTK_ADMA_RX_DBG0) & 0x40) == 0);
+
+	if (opq0 && cdm1_fsm && cur_stat && fifo_rdy) {
+		err_cnt_arx++;
+		if (err_cnt_arx >= 3) {
 			pr_info("ADMA Rx Info\n");
-			pr_info("============== Time: %d ================\n",
-				timestamp);
-			pr_info("err_cnt3 = %d", err_cnt3);
-			pr_info("is_oq0_stuck = %d\n", is_oq0_stuck);
-			pr_info("is_cdm1_busy = %d\n", is_cdm1_busy);
-			pr_info("is_adma_busy = %d\n", is_adma_busy);
-			pr_info("-- -- -- -- -- -- --\n");
+			pr_info("err_cnt = %d", err_cnt_arx);
+			pr_info("CDM1_FSM = %d\n",
+				mtk_r32(eth, MTK_FE_CDM1_FSM));
 			pr_info("MTK_PSE_OQ_STA1 = 0x%x\n",
 				mtk_r32(eth, MTK_PSE_OQ_STA(0)));
 			pr_info("MTK_ADMA_RX_DBG0 = 0x%x\n",
@@ -308,25 +397,124 @@
 			pr_info("MTK_ADMA_RX_DBG1 = 0x%x\n",
 				mtk_r32(eth, MTK_ADMA_RX_DBG1));
 			pr_info("==============================\n");
+			err_flag = 1;
+		}
+	} else
+		err_cnt_arx = 0;
+
+	if (err_flag)
+		return MTK_FE_STOP_TRAFFIC;
+	else
+		return 0;
+}
+
+u32 mtk_monitor_tdma_tx(struct mtk_eth *eth)
+{
+	static u32 err_cnt_ttx;
+	static u32 pre_fsm;
+	u32 err_flag = 0;
+	u32 cur_fsm = 0;
+	u32 tx_busy = 0;
+
+	if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V3)) {
+		cur_fsm = (mtk_r32(eth, MTK_FE_CDM6_FSM) & 0x1FFF) != 0;
+		tx_busy = ((mtk_r32(eth, MTK_TDMA_GLO_CFG) & 0x2) != 0);
+
+		if (cur_fsm == pre_fsm && cur_fsm != 0 && tx_busy) {
+			err_cnt_ttx++;
+			if (err_cnt_ttx >= 3) {
+				pr_info("TDMA Tx Info\n");
+				pr_info("err_cnt = %d", err_cnt_ttx);
+				pr_info("CDM6_FSM = %d\n",
+					mtk_r32(eth, MTK_FE_CDM6_FSM));
+				pr_info("DMA CFG = 0x%x\n",
+					mtk_r32(eth, MTK_TDMA_GLO_CFG));
+				pr_info("==============================\n");
+				err_flag = 1;
+			}
+		} else
+			err_cnt_ttx = 0;
+
+		pre_fsm = cur_fsm;
+	}
+
+	if (err_flag)
+		return MTK_FE_STOP_TRAFFIC;
+	else
+		return 0;
+}
+
+u32 mtk_monitor_tdma_rx(struct mtk_eth *eth)
+{
+	static u32 err_cnt_trx;
+	static u32 pre_fsm;
+	u32 err_flag = 0;
+	u32 cur_fsm = 0;
+	u32 rx_busy = 0;
+
+	if (MTK_HAS_CAPS(eth->soc->caps, MTK_NETSYS_V3)) {
+		cur_fsm = (mtk_r32(eth, MTK_FE_CDM6_FSM) & 0xFFF0000) != 0;
+		rx_busy = ((mtk_r32(eth, MTK_TDMA_GLO_CFG) & 0x8) != 0);
+
+		if (cur_fsm == pre_fsm && cur_fsm != 0 && rx_busy) {
+			err_cnt_trx++;
+			if (err_cnt_trx >= 3) {
+				pr_info("TDMA Rx Info\n");
+				pr_info("err_cnt = %d", err_cnt_trx);
+				pr_info("CDM6_FSM = %d\n",
+					mtk_r32(eth, MTK_FE_CDM6_FSM));
+				pr_info("DMA CFG = 0x%x\n",
+					mtk_r32(eth, MTK_TDMA_GLO_CFG));
+				pr_info("==============================\n");
+				err_flag = 1;
+			}
+		} else
+			err_cnt_trx = 0;
+
+		pre_fsm = cur_fsm;
+	}
+
+	if (err_flag)
+		return MTK_FE_STOP_TRAFFIC;
+	else
+		return 0;
+}
+
+static const mtk_monitor_xdma_func mtk_reset_monitor_func[] = {
+	[0] = mtk_monitor_wdma_tx,
+	[1] = mtk_monitor_wdma_rx,
+	[2] = mtk_monitor_qdma_tx,
+	[3] = mtk_monitor_qdma_rx,
+	[4] = mtk_monitor_adma_rx,
+	[5] = mtk_monitor_tdma_tx,
+	[6] = mtk_monitor_tdma_rx,
+};
+
+void mtk_dma_monitor(struct timer_list *t)
+{
+	struct mtk_eth *eth = from_timer(eth, t, mtk_dma_monitor_timer);
+	u32 i = 0, ret = 0;
+
+	for (i = 0; i < 6; i++) {
+		ret = (*mtk_reset_monitor_func[i]) (eth);
+		if ((ret == MTK_FE_START_RESET) ||
+			(ret == MTK_FE_STOP_TRAFFIC)) {
 			if ((atomic_read(&reset_lock) == 0) &&
-			    (atomic_read(&force) == 0)){
+				(atomic_read(&force) == 0)) {
 				atomic_inc(&force);
+				mtk_reset_flag = ret;
 				schedule_work(&eth->pending_work);
 			}
+			break;
 		}
-	}else {
-		err_cnt1 = 0;
-		err_cnt2 = 0;
-		err_cnt3 = 0;
 	}
 
-	prev_wdidx = cur_wdidx;
 	mod_timer(&eth->mtk_dma_monitor_timer, jiffies + 1 * HZ);
 }
 
 void mtk_prepare_reset_fe(struct mtk_eth *eth)
 {
-	u32 i = 0, val = 0;
+	u32 i = 0, val = 0, mcr = 0;
 
 	/* Disable NETSYS Interrupt */
 	mtk_w32(eth, 0, MTK_FE_INT_ENABLE);
@@ -344,28 +532,26 @@
 	val = mtk_r32(eth, MTK_QDMA_GLO_CFG);
 	mtk_w32(eth, val & ~(MTK_TX_DMA_EN), MTK_QDMA_GLO_CFG);
 
-	/* Power down sgmii */
-	for (i = 0; i < MTK_MAX_DEVS; i++) {
-		if (!eth->xgmii->regmap_sgmii[i])
-			continue;
+	for (i = 0; i < MTK_MAC_COUNT; i++) {
+		pr_info("[%s] i:%d type:%d id:%d\n",
+			__func__, i, eth->mac[i]->type, eth->mac[i]->id);
+		if (eth->mac[i]->type == MTK_XGDM_TYPE &&
+		    eth->mac[i]->id != MTK_GMAC1_ID) {
+			mcr = mtk_r32(eth, MTK_XMAC_MCR(eth->mac[i]->id));
+			mcr &= 0xfffffff0;
+			mcr |= XMAC_MCR_TRX_DISABLE;
+			pr_info("disable XMAC TX/RX\n");
+			mtk_w32(eth, mcr, MTK_XMAC_MCR(eth->mac[i]->id));
+		}
 
-		regmap_read(eth->xgmii->regmap_sgmii[i], SGMSYS_QPHY_PWR_STATE_CTRL, &val);
-		val |= SGMII_PHYA_PWD;
-		regmap_write(eth->xgmii->regmap_sgmii[i], SGMSYS_QPHY_PWR_STATE_CTRL, val);
+		if (eth->mac[i]->type == MTK_GDM_TYPE) {
+			mcr = mtk_r32(eth, MTK_MAC_MCR(eth->mac[i]->id));
+			mcr &= ~(MAC_MCR_TX_EN | MAC_MCR_RX_EN);
+			mtk_w32(eth, mcr, MTK_MAC_MCR(eth->mac[i]->id));
+			pr_info("disable GMAC TX/RX\n");
+		}
 	}
 
-	/* Force link down GMAC */
-	val = mtk_r32(eth, MTK_MAC_MCR(0));
-	mtk_w32(eth, val & ~(MAC_MCR_FORCE_LINK), MTK_MAC_MCR(0));
-	val = mtk_r32(eth, MTK_MAC_MCR(1));
-	mtk_w32(eth, val & ~(MAC_MCR_FORCE_LINK), MTK_MAC_MCR(1));
-
-	/* Disable GMAC Rx */
-	val = mtk_r32(eth, MTK_MAC_MCR(0));
-	mtk_w32(eth, val & ~(MAC_MCR_RX_EN), MTK_MAC_MCR(0));
-	val = mtk_r32(eth, MTK_MAC_MCR(1));
-	mtk_w32(eth, val & ~(MAC_MCR_RX_EN), MTK_MAC_MCR(1));
-
 	/* Enable GDM drop */
 	for (i = 0; i < MTK_MAC_COUNT; i++)
 		mtk_gdm_config(eth, i, MTK_GDMA_DROP_ALL);
@@ -418,6 +604,7 @@
 {
 	switch (event) {
 	case MTK_WIFI_RESET_DONE:
+	case MTK_FE_STOP_TRAFFIC_DONE:
 		mtk_rest_cnt--;
 		if(!mtk_rest_cnt) {
 			complete(&wait_ser_done);
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_eth_reset.h b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_eth_reset.h
index 547d48f..096331b 100644
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_eth_reset.h
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_eth_reset.h
@@ -15,6 +15,10 @@
 #define MTK_WIFI_CHIP_OFFLINE 	0x2004
 #define MTK_FE_RESET_NAT_DONE	0x4001
 
+#define MTK_FE_STOP_TRAFFIC	(0x2005)
+#define MTK_FE_STOP_TRAFFIC_DONE	(0x2006)
+#define MTK_FE_START_TRAFFIC	(0x2007)
+
 /* ADMA Rx Debug Monitor */
 #define MTK_ADMA_RX_DBG0	(PDMA_BASE + 0x238)
 #define MTK_ADMA_RX_DBG1	(PDMA_BASE + 0x23C)
@@ -33,6 +37,12 @@
 #define MTK_PPE_SCAN_MODE_MASK	(0x3 << 16)
 #define MTK_PPE_BUSY		BIT(31)
 
+#if defined(CONFIG_MEDIATEK_NETSYS_V3)
+#define MTK_WDMA_CNT	(0x3)
+#else
+#define MTK_WDMA_CNT	(0x2)
+#endif
+
 enum mtk_reset_type {
 	MTK_TYPE_COLD_RESET	= 0,
 	MTK_TYPE_WARM_RESET,
@@ -55,6 +65,8 @@
 extern struct completion wait_ser_done;
 extern char* mtk_reset_event_name[32];
 extern atomic_t reset_lock;
+extern struct completion wait_nat_done;
+extern u32 mtk_reset_flag;
 
 irqreturn_t mtk_handle_fe_irq(int irq, void *_eth);
 u32 mtk_check_reset_event(struct mtk_eth *eth, u32 status);
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_eth_soc.c b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_eth_soc.c
index 889b7fd..ffa8da6 100755
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_eth_soc.c
@@ -762,6 +762,7 @@
 
 		if (MTK_HAS_CAPS(mac->hw->soc->caps, MTK_USXGMII)) {
 			phylink_set(mask, 10000baseKR_Full);
+			phylink_set(mask, 10000baseT_Full);
 			phylink_set(mask, 10000baseSR_Full);
 			phylink_set(mask, 10000baseLR_Full);
 			phylink_set(mask, 10000baseLRM_Full);
@@ -3536,6 +3537,41 @@
 	return -EOPNOTSUPP;
 }
 
+int mtk_phy_config(struct mtk_eth *eth, int enable)
+{
+	struct device_node *mii_np = NULL;
+	struct device_node *child = NULL;
+	int addr = 0;
+	u32 val = 0;
+
+	mii_np = of_get_child_by_name(eth->dev->of_node, "mdio-bus");
+	if (!mii_np) {
+		dev_err(eth->dev, "no %s child node found", "mdio-bus");
+		return -ENODEV;
+	}
+
+	if (!of_device_is_available(mii_np)) {
+		dev_err(eth->dev, "device is not available\n");
+		return -ENODEV;
+	}
+
+	for_each_available_child_of_node(mii_np, child) {
+		addr = of_mdio_parse_addr(&eth->mii_bus->dev, child);
+		if (addr < 0)
+			continue;
+		pr_info("%s %d addr:%d name:%s\n",
+			__func__, __LINE__, addr, child->name);
+		val = _mtk_mdio_read(eth, addr, mdiobus_c45_addr(0x1e, 0));
+		if (enable)
+			val &= ~BMCR_PDOWN;
+		else
+			val |= BMCR_PDOWN;
+		_mtk_mdio_write(eth, addr, mdiobus_c45_addr(0x1e, 0), val);
+	}
+
+	return 0;
+}
+
 static void mtk_pending_work(struct work_struct *work)
 {
 	struct mtk_eth *eth = container_of(work, struct mtk_eth, pending_work);
@@ -3555,12 +3591,10 @@
 
 	rtnl_lock();
 
-	/* Disabe FE P3 and P4 */
-	val = mtk_r32(eth, MTK_FE_GLO_CFG);
-	val |= MTK_FE_LINK_DOWN_P3;
-	if (MTK_HAS_CAPS(eth->soc->caps, MTK_RSTCTRL_PPE1))
-		val |= MTK_FE_LINK_DOWN_P4;
-	mtk_w32(eth, val, MTK_FE_GLO_CFG);
+	while (test_and_set_bit_lock(MTK_RESETTING, &eth->state))
+		cpu_relax();
+
+	mtk_phy_config(eth, 0);
 
 	/* Adjust PPE configurations to prepare for reset */
 	mtk_prepare_reset_ppe(eth, 0);
@@ -3574,18 +3608,22 @@
 	for (i = 0; i < MTK_MAC_COUNT; i++) {
 		if (!eth->netdev[i])
 			continue;
-		call_netdevice_notifiers(MTK_FE_START_RESET, eth->netdev[i]);
+		if (mtk_reset_flag == MTK_FE_STOP_TRAFFIC) {
+			pr_info("send MTK_FE_STOP_TRAFFIC event\n");
+			call_netdevice_notifiers(MTK_FE_STOP_TRAFFIC,
+				eth->netdev[i]);
+		} else {
+			pr_info("send MTK_FE_START_RESET event\n");
+			call_netdevice_notifiers(MTK_FE_START_RESET,
+				eth->netdev[i]);
+		}
 		rtnl_unlock();
-		if (!wait_for_completion_timeout(&wait_ser_done, 5000))
-			pr_warn("[%s] wait for MTK_FE_START_RESET failed\n",
-				__func__);
+		if (!wait_for_completion_timeout(&wait_ser_done, 3000))
+			pr_warn("wait for MTK_FE_START_RESET failed\n");
 		rtnl_lock();
 		break;
 	}
 
-	while (test_and_set_bit_lock(MTK_RESETTING, &eth->state))
-		cpu_relax();
-
 	del_timer_sync(&eth->mtk_dma_monitor_timer);
 	pr_info("[%s] mtk_stop starts !\n", __func__);
 	/* stop all devices to make sure that dma is properly shut down */
@@ -3618,37 +3656,20 @@
 		}
 	}
 
-	/* Set KA tick select */
-	mtk_m32(eth, MTK_PPE_TICK_SEL_MASK, 0, MTK_PPE_TB_CFG(0));
-	if (MTK_HAS_CAPS(eth->soc->caps, MTK_RSTCTRL_PPE1))
-		mtk_m32(eth, MTK_PPE_TICK_SEL_MASK, 0, MTK_PPE_TB_CFG(1));
-
-	/* Enabe FE P3 and P4*/
-	val = mtk_r32(eth, MTK_FE_GLO_CFG);
-	val &= ~MTK_FE_LINK_DOWN_P3;
-	if (MTK_HAS_CAPS(eth->soc->caps, MTK_RSTCTRL_PPE1))
-		val &= ~MTK_FE_LINK_DOWN_P4;
-	mtk_w32(eth, val, MTK_FE_GLO_CFG);
-
-	/* Power up sgmii */
 	for (i = 0; i < MTK_MAC_COUNT; i++) {
 		if (!eth->netdev[i])
 			continue;
-		mac = netdev_priv(eth->netdev[i]);
-		phy_node = of_parse_phandle(mac->of_node, "phy-handle", 0);
-		if (!phy_node && eth->xgmii->regmap_sgmii[i]) {
-			mtk_gmac_sgmii_path_setup(eth, i);
-			regmap_write(eth->xgmii->regmap_sgmii[i], SGMSYS_QPHY_PWR_STATE_CTRL, 0);
+		if (mtk_reset_flag == MTK_FE_STOP_TRAFFIC) {
+			pr_info("send MTK_FE_START_TRAFFIC event\n");
+			call_netdevice_notifiers(MTK_FE_START_TRAFFIC,
+				eth->netdev[i]);
+		} else {
+			pr_info("send MTK_FE_RESET_DONE event\n");
+			call_netdevice_notifiers(MTK_FE_RESET_DONE,
+				eth->netdev[i]);
 		}
-	}
-
-	for (i = 0; i < MTK_MAC_COUNT; i++) {
-		if (!eth->netdev[i])
-			continue;
-		call_netdevice_notifiers(MTK_FE_RESET_NAT_DONE, eth->netdev[i]);
-		pr_info("[%s] HNAT reset done !\n", __func__);
-		call_netdevice_notifiers(MTK_FE_RESET_DONE, eth->netdev[i]);
-		pr_info("[%s] WiFi SER reset done !\n", __func__);
+		call_netdevice_notifiers(MTK_FE_RESET_NAT_DONE,
+			eth->netdev[i]);
 		break;
 	}
 
@@ -3659,6 +3680,9 @@
 	timer_setup(&eth->mtk_dma_monitor_timer, mtk_dma_monitor, 0);
 	eth->mtk_dma_monitor_timer.expires = jiffies;
 	add_timer(&eth->mtk_dma_monitor_timer);
+
+	mtk_phy_config(eth, 1);
+	mtk_reset_flag = 0;
 	clear_bit_unlock(MTK_RESETTING, &eth->state);
 
 	rtnl_unlock();
@@ -4347,7 +4371,7 @@
 	platform_set_drvdata(pdev, eth);
 
 	register_netdevice_notifier(&mtk_eth_netdevice_nb);
-#if defined(CONFIG_MEDIATEK_NETSYS_V2)
+#if defined(CONFIG_MEDIATEK_NETSYS_V2) || defined(CONFIG_MEDIATEK_NETSYS_V3)
 	timer_setup(&eth->mtk_dma_monitor_timer, mtk_dma_monitor, 0);
 	eth->mtk_dma_monitor_timer.expires = jiffies;
 	add_timer(&eth->mtk_dma_monitor_timer);
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_eth_soc.h b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_eth_soc.h
index b617c13..dc02870 100755
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_eth_soc.h
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_eth_soc.h
@@ -494,11 +494,18 @@
 #define MTK_QDMA_FQ_BLEN	(QDMA_BASE +0x32c)
 
 /* WDMA Registers */
+#define MTK_WDMA_CTX_PTR(x)	(WDMA_BASE(x) + 0x8)
 #define MTK_WDMA_DTX_PTR(x)	(WDMA_BASE(x) + 0xC)
 #define MTK_WDMA_GLO_CFG(x)	(WDMA_BASE(x) + 0x204)
 #define MTK_WDMA_TX_DBG_MON0(x)	(WDMA_BASE(x) + 0x230)
+#define MTK_WDMA_RX_DBG_MON1(x)	(WDMA_BASE(x) + 0x3c4)
+#define MTK_WDMA_CRX_PTR(x)	(WDMA_BASE(x) + 0x108)
+#define MTK_WDMA_DRX_PTR(x)	(WDMA_BASE(x) + 0x10C)
 #define MTK_CDM_TXFIFO_RDY	BIT(7)
 
+/*TDMA Register*/
+#define MTK_TDMA_GLO_CFG	(0x6204)
+
 /* GMA1 Received Good Byte Count Register */
 #if defined(CONFIG_MEDIATEK_NETSYS_V2) || defined(CONFIG_MEDIATEK_NETSYS_V3)
 #define MTK_GDM1_TX_GBCNT       0x1C00
@@ -754,9 +761,16 @@
 #if defined(CONFIG_MEDIATEK_NETSYS_V2)
 #define RSTCTRL_PPE0	BIT(30)
 #define RSTCTRL_PPE1 	BIT(31)
+#elif defined(CONFIG_MEDIATEK_NETSYS_V3)
+#define RSTCTRL_PPE0	BIT(29)
+#define RSTCTRL_PPE1	BIT(30)
+#define RSTCTRL_PPE2	BIT(31)
+#define RSTCTRL_WDMA0	BIT(24)
+#define RSTCTRL_WDMA1	BIT(25)
+#define RSTCTRL_WDMA2	BIT(26)
 #else
 #define RSTCTRL_PPE0	BIT(31)
-#define RSTCTRL_PPE1 	0
+#define RSTCTRL_PPE1	0
 #endif
 
 /* ethernet reset check idle register */
@@ -1241,6 +1255,7 @@
 	MTK_NETSYS_V3_BIT,
 	MTK_SOC_MT7628_BIT,
 	MTK_RSTCTRL_PPE1_BIT,
+	MTK_RSTCTRL_PPE2_BIT,
 	MTK_U3_COPHY_V2_BIT,
 	MTK_8GB_ADDRESSING_BIT,
 
@@ -1290,6 +1305,7 @@
 #define MTK_NETSYS_V3		BIT_ULL(MTK_NETSYS_V3_BIT)
 #define MTK_SOC_MT7628		BIT_ULL(MTK_SOC_MT7628_BIT)
 #define MTK_RSTCTRL_PPE1	BIT_ULL(MTK_RSTCTRL_PPE1_BIT)
+#define MTK_RSTCTRL_PPE2	BIT_ULL(MTK_RSTCTRL_PPE2_BIT)
 #define MTK_U3_COPHY_V2		BIT_ULL(MTK_U3_COPHY_V2_BIT)
 #define MTK_8GB_ADDRESSING	BIT_ULL(MTK_8GB_ADDRESSING_BIT)
 
@@ -1401,7 +1417,7 @@
 
 #define MT7988_CAPS   (MTK_GMAC1_SGMII | MTK_GMAC2_SGMII | MTK_GMAC3_SGMII | \
 		       MTK_MUX_GMAC123_TO_GEPHY_SGMII | MTK_QDMA | \
-		       MTK_NETSYS_V3 | MTK_RSTCTRL_PPE1 | \
+		       MTK_NETSYS_V3 | MTK_RSTCTRL_PPE1 | MTK_RSTCTRL_PPE2 | \
 		       MTK_GMAC1_USXGMII | MTK_GMAC2_USXGMII | \
 		       MTK_GMAC3_USXGMII | MTK_MUX_GMAC123_TO_USXGMII | \
 		       MTK_GMAC2_XGMII | MTK_MUX_GMAC2_TO_XGMII | MTK_RSS)
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_hnat/hnat.c b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_hnat/hnat.c
index 37c5587..9c40ac8 100644
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_hnat/hnat.c
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_hnat/hnat.c
@@ -682,8 +682,9 @@
 		hnat_hw_init(ppe_id);
 	}
 
-	set_gmac_ppe_fwd(0, 1);
-	set_gmac_ppe_fwd(1, 1);
+	set_gmac_ppe_fwd(NR_GMAC1_PORT, 1);
+	set_gmac_ppe_fwd(NR_GMAC2_PORT, 1);
+	set_gmac_ppe_fwd(NR_GMAC3_PORT, 1);
 	register_netevent_notifier(&nf_hnat_netevent_nb);
 
 	return 0;
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/net/phy/mediatek-ge.c b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/net/phy/mediatek-ge.c
index c864fce..35ec82f 100644
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/net/phy/mediatek-ge.c
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/files-5.4/drivers/net/phy/mediatek-ge.c
@@ -530,6 +530,17 @@
 					7, 1, 4, 7,
 					7, 1, 4, 7 };
 			memcpy(bias, (const void *)tmp, sizeof(bias));
+			for (i = 0; i <= 12; i += 4) {
+				if (likely(buf[i>>2] + bias[i] >= 32)) {
+					bias[i] -= 13;
+				} else {
+					phy_modify_mmd(phydev, MDIO_MMD_VEND1,
+						0x5c, 0x7 << i, bias[i] << i);
+					bias[i+1] += 13;
+					bias[i+2] += 13;
+					bias[i+3] += 13;
+				}
+			}
 			break;
 		}
 		case 0x03a29481:
@@ -545,18 +556,6 @@
 			break;
 	}
 
-	for (i = 0; i < 12; i += 4) {
-		if (likely(buf[i>>2] + bias[i] >= 32)) {
-			bias[i] -= 13;
-		} else {
-			phy_modify_mmd(phydev, MDIO_MMD_VEND1, 0x5c,
-				0xf << (12-i), 0x6 << (12-i));
-			bias[i+1] += 13;
-			bias[i+2] += 13;
-			bias[i+3] += 13;
-		}
-	}
-
 	/* Prevent overflow */
 	for (i = 0; i < 12; i++) {
 		if (buf[i>>2] + bias[i] > 63) {
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/flow_patch/9999-1-add-wed-ser-support.patch b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/flow_patch/9999-1-add-wed-ser-support.patch
index 3e2b018..b2714d0 100755
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/flow_patch/9999-1-add-wed-ser-support.patch
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/flow_patch/9999-1-add-wed-ser-support.patch
@@ -13,38 +13,45 @@
  5 files changed, 297 insertions(+), 101 deletions(-)
 
 diff --git a/drivers/net/ethernet/mediatek/mtk_eth_soc.c b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
-index c582bb9..5259141 100644
+index 51b40fa..8dbea6b 100644
 --- a/drivers/net/ethernet/mediatek/mtk_eth_soc.c
 +++ b/drivers/net/ethernet/mediatek/mtk_eth_soc.c
-@@ -3619,12 +3619,16 @@ static void mtk_pending_work(struct work_struct *work)
+@@ -3662,6 +3662,9 @@ static void mtk_pending_work(struct work_struct *work)
  	for (i = 0; i < MTK_MAC_COUNT; i++) {
  		if (!eth->netdev[i])
  			continue;
 +#ifdef CONFIG_NET_MEDIATEK_SOC_WED
 +		mtk_wed_fe_reset(MTK_FE_START_RESET);
 +#else
- 		call_netdevice_notifiers(MTK_FE_START_RESET, eth->netdev[i]);
- 		rtnl_unlock();
- 		if (!wait_for_completion_timeout(&wait_ser_done, 5000))
- 			pr_warn("[%s] wait for MTK_FE_START_RESET failed\n",
- 				__func__);
+ 		if (mtk_reset_flag == MTK_FE_STOP_TRAFFIC) {
+ 			pr_info("send MTK_FE_STOP_TRAFFIC event\n");
+ 			call_netdevice_notifiers(MTK_FE_STOP_TRAFFIC,
+@@ -3675,6 +3678,7 @@ static void mtk_pending_work(struct work_struct *work)
+ 		if (!wait_for_completion_timeout(&wait_ser_done, 3000))
+ 			pr_warn("wait for MTK_FE_START_RESET failed\n");
  		rtnl_lock();
 +#endif
  		break;
  	}
  
-@@ -3690,7 +3694,11 @@ static void mtk_pending_work(struct work_struct *work)
+@@ -3713,6 +3717,9 @@ static void mtk_pending_work(struct work_struct *work)
+ 	for (i = 0; i < MTK_MAC_COUNT; i++) {
+ 		if (!eth->netdev[i])
  			continue;
- 		call_netdevice_notifiers(MTK_FE_RESET_NAT_DONE, eth->netdev[i]);
- 		pr_info("[%s] HNAT reset done !\n", __func__);
 +#ifdef CONFIG_NET_MEDIATEK_SOC_WED
 +		mtk_wed_fe_reset(MTK_FE_RESET_DONE);
 +#else
- 		call_netdevice_notifiers(MTK_FE_RESET_DONE, eth->netdev[i]);
+ 		if (mtk_reset_flag == MTK_FE_STOP_TRAFFIC) {
+ 			pr_info("send MTK_FE_START_TRAFFIC event\n");
+ 			call_netdevice_notifiers(MTK_FE_START_TRAFFIC,
+@@ -3722,6 +3729,7 @@ static void mtk_pending_work(struct work_struct *work)
+ 			call_netdevice_notifiers(MTK_FE_RESET_DONE,
+ 				eth->netdev[i]);
+ 		}
 +#endif
- 		pr_info("[%s] WiFi SER reset done !\n", __func__);
+ 		call_netdevice_notifiers(MTK_FE_RESET_NAT_DONE,
+ 			eth->netdev[i]);
  		break;
- 	}
 diff --git a/drivers/net/ethernet/mediatek/mtk_wed.c b/drivers/net/ethernet/mediatek/mtk_wed.c
 index 7552795..c98d749 100644
 --- a/drivers/net/ethernet/mediatek/mtk_wed.c
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/mt7988.cfg b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/mt7988.cfg
index c78b0b7..2ae66c3 100644
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/mt7988.cfg
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/mt7988.cfg
@@ -261,6 +261,7 @@
 CONFIG_LZO_DECOMPRESS=y
 CONFIG_MAGIC_SYSRQ=y
 CONFIG_MAGIC_SYSRQ_SERIAL=y
+CONFIG_MARVELL_10G_PHY=y
 CONFIG_MAXLINEAR_GPHY=y
 CONFIG_MD=y
 CONFIG_MDIO_BUS=y
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/patches-5.4/416-mtd-spinor-support-EN25QX128A.patch b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/patches-5.4/416-mtd-spinor-support-EN25QX128A.patch
new file mode 100644
index 0000000..2985532
--- /dev/null
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/patches-5.4/416-mtd-spinor-support-EN25QX128A.patch
@@ -0,0 +1,12 @@
+--- a/drivers/mtd/spi-nor/spi-nor.c	2022-12-14 15:29:28.587567592 +0800
++++ b/drivers/mtd/spi-nor/spi-nor.c	2022-12-14 15:04:52.625250000 +0800
+@@ -2246,6 +2246,9 @@ static const struct flash_info spi_nor_i
+ 	{ "en25qh64",   INFO(0x1c7017, 0, 64 * 1024,  128,
+ 			SECT_4K | SPI_NOR_DUAL_READ) },
+ 	{ "en25qh128",  INFO(0x1c7018, 0, 64 * 1024,  256, 0) },
++	{ "en25qx128",	INFO(0x1c7118, 0, 64 * 1024,  256,
++			SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ |
++			SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB) },
+ 	{ "en25qh256",  INFO(0x1c7019, 0, 64 * 1024,  512, 0) },
+ 	{ "en25s64",	INFO(0x1c3817, 0, 64 * 1024,  128, SECT_4K) },
+
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/patches-5.4/752-net-dsa-phy-coverity-scan.patch b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/patches-5.4/752-net-dsa-phy-coverity-scan.patch
old mode 100644
new mode 100755
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/patches-5.4/754-net-phy-sfp-add-rollball-support.patch b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/patches-5.4/754-net-phy-sfp-add-rollball-support.patch
new file mode 100644
index 0000000..ef1f3cb
--- /dev/null
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/patches-5.4/754-net-phy-sfp-add-rollball-support.patch
@@ -0,0 +1,1024 @@
+diff --git a/drivers/net/phy/mdio-i2c.c b/drivers/net/phy/mdio-i2c.c
+index 0746e2c..81c8fe7 100644
+--- a/drivers/net/phy/mdio-i2c.c
++++ b/drivers/net/phy/mdio-i2c.c
+@@ -11,6 +11,7 @@
+  */
+ #include <linux/i2c.h>
+ #include <linux/phy.h>
++#include <linux/sfp.h>
+ 
+ #include "mdio-i2c.h"
+ 
+@@ -29,7 +30,7 @@ static unsigned int i2c_mii_phy_addr(int phy_id)
+ 	return phy_id + 0x40;
+ }
+ 
+-static int i2c_mii_read(struct mii_bus *bus, int phy_id, int reg)
++static int i2c_mii_read_default(struct mii_bus *bus, int phy_id, int reg)
+ {
+ 	struct i2c_adapter *i2c = bus->priv;
+ 	struct i2c_msg msgs[2];
+@@ -63,7 +64,8 @@ static int i2c_mii_read(struct mii_bus *bus, int phy_id, int reg)
+ 	return data[0] << 8 | data[1];
+ }
+ 
+-static int i2c_mii_write(struct mii_bus *bus, int phy_id, int reg, u16 val)
++static int i2c_mii_write_default(struct mii_bus *bus, int phy_id, int reg,
++				 u16 val)
+ {
+ 	struct i2c_adapter *i2c = bus->priv;
+ 	struct i2c_msg msg;
+@@ -92,9 +94,288 @@ static int i2c_mii_write(struct mii_bus *bus, int phy_id, int reg, u16 val)
+ 	return ret < 0 ? ret : 0;
+ }
+ 
+-struct mii_bus *mdio_i2c_alloc(struct device *parent, struct i2c_adapter *i2c)
++/* RollBall SFPs do not access internal PHY via I2C address 0x56, but
++ * instead via address 0x51, when SFP page is set to 0x03 and password to
++ * 0xffffffff.
++ *
++ * address  size  contents  description
++ * -------  ----  --------  -----------
++ * 0x80     1     CMD       0x01/0x02/0x04 for write/read/done
++ * 0x81     1     DEV       Clause 45 device
++ * 0x82     2     REG       Clause 45 register
++ * 0x84     2     VAL       Register value
++ */
++#define ROLLBALL_PHY_I2C_ADDR		0x51
++
++#define ROLLBALL_PASSWORD		(SFP_VSL + 3)
++
++#define ROLLBALL_CMD_ADDR		0x80
++#define ROLLBALL_DATA_ADDR		0x81
++
++#define ROLLBALL_CMD_WRITE		0x01
++#define ROLLBALL_CMD_READ		0x02
++#define ROLLBALL_CMD_DONE		0x04
++
++#define SFP_PAGE_ROLLBALL_MDIO		3
++
++static int __i2c_transfer_err(struct i2c_adapter *i2c, struct i2c_msg *msgs,
++			      int num)
++{
++	int ret;
++
++	ret = __i2c_transfer(i2c, msgs, num);
++	if (ret < 0)
++		return ret;
++	else if (ret != num)
++		return -EIO;
++	else
++		return 0;
++}
++
++static int __i2c_rollball_get_page(struct i2c_adapter *i2c, int bus_addr,
++				   u8 *page)
++{
++	struct i2c_msg msgs[2];
++	u8 addr = SFP_PAGE;
++
++	msgs[0].addr = bus_addr;
++	msgs[0].flags = 0;
++	msgs[0].len = 1;
++	msgs[0].buf = &addr;
++
++	msgs[1].addr = bus_addr;
++	msgs[1].flags = I2C_M_RD;
++	msgs[1].len = 1;
++	msgs[1].buf = page;
++
++	return __i2c_transfer_err(i2c, msgs, 2);
++}
++
++static int __i2c_rollball_set_page(struct i2c_adapter *i2c, int bus_addr,
++				   u8 page)
++{
++	struct i2c_msg msg;
++	u8 buf[2];
++
++	buf[0] = SFP_PAGE;
++	buf[1] = page;
++
++	msg.addr = bus_addr;
++	msg.flags = 0;
++	msg.len = 2;
++	msg.buf = buf;
++
++	return __i2c_transfer_err(i2c, &msg, 1);
++}
++
++/* In order to not interfere with other SFP code (which possibly may manipulate
++ * SFP_PAGE), for every transfer we do this:
++ *   1. lock the bus
++ *   2. save content of SFP_PAGE
++ *   3. set SFP_PAGE to 3
++ *   4. do the transfer
++ *   5. restore original SFP_PAGE
++ *   6. unlock the bus
++ * Note that one might think that steps 2 to 5 could be theoretically done all
++ * in one call to i2c_transfer (by constructing msgs array in such a way), but
++ * unfortunately tests show that this does not work :-( Changed SFP_PAGE does
++ * not take into account until i2c_transfer() is done.
++ */
++static int i2c_transfer_rollball(struct i2c_adapter *i2c,
++				 struct i2c_msg *msgs, int num)
++{
++	int ret, main_err = 0;
++	u8 saved_page;
++
++	i2c_lock_bus(i2c, I2C_LOCK_SEGMENT);
++
++	/* save original page */
++	ret = __i2c_rollball_get_page(i2c, msgs->addr, &saved_page);
++	if (ret)
++		goto unlock;
++
++	/* change to RollBall MDIO page */
++	ret = __i2c_rollball_set_page(i2c, msgs->addr, SFP_PAGE_ROLLBALL_MDIO);
++	if (ret)
++		goto unlock;
++
++	/* do the transfer; we try to restore original page if this fails */
++	ret = __i2c_transfer_err(i2c, msgs, num);
++	if (ret)
++		main_err = ret;
++
++	/* restore original page */
++	ret = __i2c_rollball_set_page(i2c, msgs->addr, saved_page);
++
++unlock:
++	i2c_unlock_bus(i2c, I2C_LOCK_SEGMENT);
++
++	return main_err ? : ret;
++}
++
++static int i2c_rollball_mii_poll(struct mii_bus *bus, int bus_addr, u8 *buf,
++				 size_t len)
++{
++	struct i2c_adapter *i2c = bus->priv;
++	struct i2c_msg msgs[2];
++	u8 cmd_addr, tmp, *res;
++	int i, ret;
++
++	cmd_addr = ROLLBALL_CMD_ADDR;
++
++	res = buf ? buf : &tmp;
++	len = buf ? len : 1;
++
++	msgs[0].addr = bus_addr;
++	msgs[0].flags = 0;
++	msgs[0].len = 1;
++	msgs[0].buf = &cmd_addr;
++
++	msgs[1].addr = bus_addr;
++	msgs[1].flags = I2C_M_RD;
++	msgs[1].len = len;
++	msgs[1].buf = res;
++
++	/* By experiment it takes up to 70 ms to access a register for these
++	 * SFPs. Sleep 20ms between iterations and try 10 times.
++	 */
++	i = 10;
++	do {
++		msleep(20);
++
++		ret = i2c_transfer_rollball(i2c, msgs, ARRAY_SIZE(msgs));
++		if (ret)
++			return ret;
++
++		if (*res == ROLLBALL_CMD_DONE)
++			return 0;
++	} while (i-- > 0);
++
++	dev_info(&bus->dev, "poll timed out\n");
++
++	return -ETIMEDOUT;
++}
++
++static int i2c_rollball_mii_cmd(struct mii_bus *bus, int bus_addr, u8 cmd,
++				u8 *data, size_t len)
++{
++	struct i2c_adapter *i2c = bus->priv;
++	struct i2c_msg msgs[2];
++	u8 cmdbuf[2];
++
++	cmdbuf[0] = ROLLBALL_CMD_ADDR;
++	cmdbuf[1] = cmd;
++
++	msgs[0].addr = bus_addr;
++	msgs[0].flags = 0;
++	msgs[0].len = len;
++	msgs[0].buf = data;
++
++	msgs[1].addr = bus_addr;
++	msgs[1].flags = 0;
++	msgs[1].len = sizeof(cmdbuf);
++	msgs[1].buf = cmdbuf;
++
++	return i2c_transfer_rollball(i2c, msgs, ARRAY_SIZE(msgs));
++}
++
++static int i2c_mii_read_rollball(struct mii_bus *bus, int phy_id, int reg)
++{
++	u8 buf[4], res[6];
++	int bus_addr, ret;
++	u16 val;
++
++	if (!(reg & MII_ADDR_C45))
++		return -EOPNOTSUPP;
++
++	bus_addr = i2c_mii_phy_addr(phy_id);
++	if (bus_addr != ROLLBALL_PHY_I2C_ADDR)
++		return 0xffff;
++
++	buf[0] = ROLLBALL_DATA_ADDR;
++	buf[1] = (reg >> 16) & 0x1f;
++	buf[2] = (reg >> 8) & 0xff;
++	buf[3] = reg & 0xff;
++
++	ret = i2c_rollball_mii_cmd(bus, bus_addr, ROLLBALL_CMD_READ, buf,
++				   sizeof(buf));
++	if (ret < 0)
++		return ret;
++
++	ret = i2c_rollball_mii_poll(bus, bus_addr, res, sizeof(res));
++	if (ret == -ETIMEDOUT)
++		return 0xffff;
++	else if (ret < 0)
++		return ret;
++
++	val = res[4] << 8 | res[5];
++
++	return val;
++}
++
++static int i2c_mii_write_rollball(struct mii_bus *bus, int phy_id, int reg,
++				  u16 val)
++{
++	int bus_addr, ret;
++	u8 buf[6];
++
++	if (!(reg & MII_ADDR_C45))
++		return -EOPNOTSUPP;
++
++	bus_addr = i2c_mii_phy_addr(phy_id);
++	if (bus_addr != ROLLBALL_PHY_I2C_ADDR)
++		return 0;
++
++	buf[0] = ROLLBALL_DATA_ADDR;
++	buf[1] = (reg >> 16) & 0x1f;
++	buf[2] = (reg >> 8) & 0xff;
++	buf[3] = reg & 0xff;
++	buf[4] = val >> 8;
++	buf[5] = val & 0xff;
++
++	ret = i2c_rollball_mii_cmd(bus, bus_addr, ROLLBALL_CMD_WRITE, buf,
++				   sizeof(buf));
++	if (ret < 0)
++		return ret;
++
++	ret = i2c_rollball_mii_poll(bus, bus_addr, NULL, 0);
++	if (ret < 0)
++		return ret;
++
++	return 0;
++}
++
++static int i2c_mii_init_rollball(struct i2c_adapter *i2c)
++{
++	struct i2c_msg msg;
++	u8 pw[5];
++	int ret;
++
++	pw[0] = ROLLBALL_PASSWORD;
++	pw[1] = 0xff;
++	pw[2] = 0xff;
++	pw[3] = 0xff;
++	pw[4] = 0xff;
++
++	msg.addr = ROLLBALL_PHY_I2C_ADDR;
++	msg.flags = 0;
++	msg.len = sizeof(pw);
++	msg.buf = pw;
++
++	ret = i2c_transfer(i2c, &msg, 1);
++	if (ret < 0)
++		return ret;
++	else if (ret != 1)
++		return -EIO;
++	else
++		return 0;
++}
++
++struct mii_bus *mdio_i2c_alloc(struct device *parent, struct i2c_adapter *i2c,
++			       enum mdio_i2c_proto protocol)
+ {
+ 	struct mii_bus *mii;
++	int ret;
+ 
+ 	if (!i2c_check_functionality(i2c, I2C_FUNC_I2C))
+ 		return ERR_PTR(-EINVAL);
+@@ -105,10 +386,28 @@ struct mii_bus *mdio_i2c_alloc(struct device *parent, struct i2c_adapter *i2c)
+ 
+ 	snprintf(mii->id, MII_BUS_ID_SIZE, "i2c:%s", dev_name(parent));
+ 	mii->parent = parent;
+-	mii->read = i2c_mii_read;
+-	mii->write = i2c_mii_write;
+ 	mii->priv = i2c;
+ 
++	switch (protocol) {
++	case MDIO_I2C_ROLLBALL:
++		ret = i2c_mii_init_rollball(i2c);
++		if (ret < 0) {
++			dev_err(parent,
++				"Cannot initialize RollBall MDIO I2C protocol: %d\n",
++				ret);
++			mdiobus_free(mii);
++			return ERR_PTR(ret);
++		}
++
++		mii->read = i2c_mii_read_rollball;
++		mii->write = i2c_mii_write_rollball;
++		break;
++	default:
++		mii->read = i2c_mii_read_default;
++		mii->write = i2c_mii_write_default;
++		break;
++	}
++
+ 	return mii;
+ }
+ EXPORT_SYMBOL_GPL(mdio_i2c_alloc);
+diff --git a/drivers/net/phy/mdio-i2c.h b/drivers/net/phy/mdio-i2c.h
+index 751dab2..1c21140 100644
+--- a/drivers/net/phy/mdio-i2c.h
++++ b/drivers/net/phy/mdio-i2c.h
+@@ -11,6 +11,14 @@ struct device;
+ struct i2c_adapter;
+ struct mii_bus;
+ 
+-struct mii_bus *mdio_i2c_alloc(struct device *parent, struct i2c_adapter *i2c);
++enum mdio_i2c_proto {
++	MDIO_I2C_NONE,
++	MDIO_I2C_MARVELL_C22,
++	MDIO_I2C_C45,
++	MDIO_I2C_ROLLBALL,
++};
++
++struct mii_bus *mdio_i2c_alloc(struct device *parent, struct i2c_adapter *i2c,
++			       enum mdio_i2c_proto protocol);
+ 
+ #endif
+diff --git a/drivers/net/phy/sfp-bus.c b/drivers/net/phy/sfp-bus.c
+index 42f0441..0d5ac2a 100644
+--- a/drivers/net/phy/sfp-bus.c
++++ b/drivers/net/phy/sfp-bus.c
+@@ -10,12 +10,6 @@
+ 
+ #include "sfp.h"
+ 
+-struct sfp_quirk {
+-	const char *vendor;
+-	const char *part;
+-	void (*modes)(const struct sfp_eeprom_id *id, unsigned long *modes);
+-};
+-
+ /**
+  * struct sfp_bus - internal representation of a sfp bus
+  */
+@@ -38,87 +32,6 @@ struct sfp_bus {
+ 	bool started;
+ };
+ 
+-static void sfp_quirk_2500basex(const struct sfp_eeprom_id *id,
+-				unsigned long *modes)
+-{
+-	phylink_set(modes, 2500baseX_Full);
+-}
+-
+-static void sfp_quirk_ubnt_uf_instant(const struct sfp_eeprom_id *id,
+-				      unsigned long *modes)
+-{
+-	/* Ubiquiti U-Fiber Instant module claims that support all transceiver
+-	 * types including 10G Ethernet which is not truth. So clear all claimed
+-	 * modes and set only one mode which module supports: 1000baseX_Full.
+-	 */
+-	phylink_zero(modes);
+-	phylink_set(modes, 1000baseX_Full);
+-}
+-
+-static const struct sfp_quirk sfp_quirks[] = {
+-	{
+-		// Alcatel Lucent G-010S-P can operate at 2500base-X, but
+-		// incorrectly report 2500MBd NRZ in their EEPROM
+-		.vendor = "ALCATELLUCENT",
+-		.part = "G010SP",
+-		.modes = sfp_quirk_2500basex,
+-	}, {
+-		// Alcatel Lucent G-010S-A can operate at 2500base-X, but
+-		// report 3.2GBd NRZ in their EEPROM
+-		.vendor = "ALCATELLUCENT",
+-		.part = "3FE46541AA",
+-		.modes = sfp_quirk_2500basex,
+-	}, {
+-		// Huawei MA5671A can operate at 2500base-X, but report 1.2GBd
+-		// NRZ in their EEPROM
+-		.vendor = "HUAWEI",
+-		.part = "MA5671A",
+-		.modes = sfp_quirk_2500basex,
+-	}, {
+-		.vendor = "UBNT",
+-		.part = "UF-INSTANT",
+-		.modes = sfp_quirk_ubnt_uf_instant,
+-	},
+-};
+-
+-static size_t sfp_strlen(const char *str, size_t maxlen)
+-{
+-	size_t size, i;
+-
+-	/* Trailing characters should be filled with space chars */
+-	for (i = 0, size = 0; i < maxlen; i++)
+-		if (str[i] != ' ')
+-			size = i + 1;
+-
+-	return size;
+-}
+-
+-static bool sfp_match(const char *qs, const char *str, size_t len)
+-{
+-	if (!qs)
+-		return true;
+-	if (strlen(qs) != len)
+-		return false;
+-	return !strncmp(qs, str, len);
+-}
+-
+-static const struct sfp_quirk *sfp_lookup_quirk(const struct sfp_eeprom_id *id)
+-{
+-	const struct sfp_quirk *q;
+-	unsigned int i;
+-	size_t vs, ps;
+-
+-	vs = sfp_strlen(id->base.vendor_name, ARRAY_SIZE(id->base.vendor_name));
+-	ps = sfp_strlen(id->base.vendor_pn, ARRAY_SIZE(id->base.vendor_pn));
+-
+-	for (i = 0, q = sfp_quirks; i < ARRAY_SIZE(sfp_quirks); i++, q++)
+-		if (sfp_match(q->vendor, id->base.vendor_name, vs) &&
+-		    sfp_match(q->part, id->base.vendor_pn, ps))
+-			return q;
+-
+-	return NULL;
+-}
+-
+ /**
+  * sfp_parse_port() - Parse the EEPROM base ID, setting the port type
+  * @bus: a pointer to the &struct sfp_bus structure for the sfp module
+@@ -359,7 +272,7 @@ void sfp_parse_support(struct sfp_bus *bus, const struct sfp_eeprom_id *id,
+ 			phylink_set(modes, 1000baseX_Full);
+ 	}
+ 
+-	if (bus->sfp_quirk)
++	if (bus->sfp_quirk && bus->sfp_quirk->modes)
+ 		bus->sfp_quirk->modes(id, modes);
+ 
+ 	bitmap_or(support, support, modes, __ETHTOOL_LINK_MODE_MASK_NBITS);
+@@ -734,12 +647,13 @@ void sfp_link_down(struct sfp_bus *bus)
+ }
+ EXPORT_SYMBOL_GPL(sfp_link_down);
+ 
+-int sfp_module_insert(struct sfp_bus *bus, const struct sfp_eeprom_id *id)
++int sfp_module_insert(struct sfp_bus *bus, const struct sfp_eeprom_id *id,
++		      const struct sfp_quirk *quirk)
+ {
+ 	const struct sfp_upstream_ops *ops = sfp_get_upstream_ops(bus);
+ 	int ret = 0;
+ 
+-	bus->sfp_quirk = sfp_lookup_quirk(id);
++	bus->sfp_quirk = quirk;
+ 
+ 	if (ops && ops->module_insert)
+ 		ret = ops->module_insert(bus->upstream, id);
+diff --git a/drivers/net/phy/sfp.c b/drivers/net/phy/sfp.c
+index 3253366..8d95f49 100644
+--- a/drivers/net/phy/sfp.c
++++ b/drivers/net/phy/sfp.c
+@@ -165,6 +165,7 @@ static const enum gpiod_flags gpio_flags[] = {
+  * on board (for a copper SFP) time to initialise.
+  */
+ #define T_WAIT			msecs_to_jiffies(50)
++#define T_WAIT_ROLLBALL		msecs_to_jiffies(25000)
+ #define T_START_UP		msecs_to_jiffies(300)
+ #define T_START_UP_BAD_GPON	msecs_to_jiffies(60000)
+ 
+@@ -204,8 +205,11 @@ static const enum gpiod_flags gpio_flags[] = {
+ 
+ /* SFP modules appear to always have their PHY configured for bus address
+  * 0x56 (which with mdio-i2c, translates to a PHY address of 22).
++ * RollBall SFPs access phy via SFP Enhanced Digital Diagnostic Interface
++ * via address 0x51 (mdio-i2c will use RollBall protocol on this address).
+  */
+-#define SFP_PHY_ADDR	22
++#define SFP_PHY_ADDR		22
++#define SFP_PHY_ADDR_ROLLBALL	17
+ 
+ struct sff_data {
+ 	unsigned int gpios;
+@@ -217,6 +221,7 @@ struct sfp {
+ 	struct i2c_adapter *i2c;
+ 	struct mii_bus *i2c_mii;
+ 	struct sfp_bus *sfp_bus;
++	enum mdio_i2c_proto mdio_protocol;
+ 	struct phy_device *mod_phy;
+ 	const struct sff_data *type;
+ 	size_t i2c_block_size;
+@@ -233,6 +238,7 @@ struct sfp {
+ 	bool need_poll;
+ 
+ 	struct mutex st_mutex;			/* Protects state */
++	unsigned int state_hw_mask;
+ 	unsigned int state_soft_mask;
+ 	unsigned int state;
+ 	struct delayed_work poll;
+@@ -249,6 +255,10 @@ struct sfp {
+ 	struct sfp_eeprom_id id;
+ 	unsigned int module_power_mW;
+ 	unsigned int module_t_start_up;
++	unsigned int module_t_wait;
++	bool tx_fault_ignore;
++
++	const struct sfp_quirk *quirk;
+ 
+ #if IS_ENABLED(CONFIG_HWMON)
+ 	struct sfp_diag diag;
+@@ -303,6 +313,135 @@ static const struct of_device_id sfp_of_match[] = {
+ };
+ MODULE_DEVICE_TABLE(of, sfp_of_match);
+ 
++static void sfp_fixup_long_startup(struct sfp *sfp)
++{
++	sfp->module_t_start_up = T_START_UP_BAD_GPON;
++}
++
++static void sfp_fixup_ignore_tx_fault(struct sfp *sfp)
++{
++	sfp->tx_fault_ignore = true;
++}
++
++static void sfp_fixup_halny_gsfp(struct sfp *sfp)
++{
++	/* Ignore the TX_FAULT and LOS signals on this module.
++	 * these are possibly used for other purposes on this
++	 * module, e.g. a serial port.
++	 */
++	sfp->state_hw_mask &= ~(SFP_F_TX_FAULT | SFP_F_LOS);
++}
++
++static void sfp_fixup_rollball(struct sfp *sfp)
++{
++	sfp->mdio_protocol = MDIO_I2C_ROLLBALL;
++	sfp->module_t_wait = T_WAIT_ROLLBALL;
++}
++
++static void sfp_fixup_rollball_cc(struct sfp *sfp)
++{
++	sfp_fixup_rollball(sfp);
++
++	/* Some RollBall SFPs may have wrong (zero) extended compliance code
++	 * burned in EEPROM. For PHY probing we need the correct one.
++	 */
++	sfp->id.base.extended_cc = SFF8024_ECC_10GBASE_T_SFI;
++}
++
++static void sfp_quirk_2500basex(const struct sfp_eeprom_id *id,
++				unsigned long *modes)
++{
++	linkmode_set_bit(ETHTOOL_LINK_MODE_2500baseX_Full_BIT, modes);
++}
++
++static void sfp_quirk_ubnt_uf_instant(const struct sfp_eeprom_id *id,
++				      unsigned long *modes)
++{
++	/* Ubiquiti U-Fiber Instant module claims that support all transceiver
++	 * types including 10G Ethernet which is not truth. So clear all claimed
++	 * modes and set only one mode which module supports: 1000baseX_Full.
++	 */
++	linkmode_zero(modes);
++	linkmode_set_bit(ETHTOOL_LINK_MODE_1000baseX_Full_BIT, modes);
++}
++
++#define SFP_QUIRK(_v, _p, _m, _f) \
++	{ .vendor = _v, .part = _p, .modes = _m, .fixup = _f, }
++#define SFP_QUIRK_M(_v, _p, _m) SFP_QUIRK(_v, _p, _m, NULL)
++#define SFP_QUIRK_F(_v, _p, _f) SFP_QUIRK(_v, _p, NULL, _f)
++
++static const struct sfp_quirk sfp_quirks[] = {
++	// Alcatel Lucent G-010S-P can operate at 2500base-X, but incorrectly
++	// report 2500MBd NRZ in their EEPROM
++	SFP_QUIRK_M("ALCATELLUCENT", "G010SP", sfp_quirk_2500basex),
++
++	// Alcatel Lucent G-010S-A can operate at 2500base-X, but report 3.2GBd
++	// NRZ in their EEPROM
++	SFP_QUIRK("ALCATELLUCENT", "3FE46541AA", sfp_quirk_2500basex,
++		  sfp_fixup_long_startup),
++
++	SFP_QUIRK_F("HALNy", "HL-GSFP", sfp_fixup_halny_gsfp),
++
++	// Huawei MA5671A can operate at 2500base-X, but report 1.2GBd NRZ in
++	// their EEPROM
++	SFP_QUIRK("HUAWEI", "MA5671A", sfp_quirk_2500basex,
++		  sfp_fixup_ignore_tx_fault),
++
++	// Lantech 8330-262D-E can operate at 2500base-X, but incorrectly report
++	// 2500MBd NRZ in their EEPROM
++	SFP_QUIRK_M("Lantech", "8330-262D-E", sfp_quirk_2500basex),
++
++	SFP_QUIRK_M("UBNT", "UF-INSTANT", sfp_quirk_ubnt_uf_instant),
++
++	SFP_QUIRK_F("OEM", "SFP-10G-T", sfp_fixup_rollball_cc),
++	SFP_QUIRK_F("OEM", "RTSFP-10", sfp_fixup_rollball_cc),
++	SFP_QUIRK_F("OEM", "RTSFP-10G", sfp_fixup_rollball_cc),
++	SFP_QUIRK_F("OEM", "TNBYV02-C0X-C3", sfp_fixup_rollball_cc),
++	SFP_QUIRK_F("Turris", "RTSFP-10", sfp_fixup_rollball),
++	SFP_QUIRK_F("Turris", "RTSFP-10G", sfp_fixup_rollball),
++	SFP_QUIRK_F("JESS-LINK", "P60000BBC001-1", sfp_fixup_rollball),
++};
++
++static size_t sfp_strlen(const char *str, size_t maxlen)
++{
++	size_t size, i;
++
++	/* Trailing characters should be filled with space chars, but
++	 * some manufacturers can't read SFF-8472 and use NUL.
++	 */
++	for (i = 0, size = 0; i < maxlen; i++)
++		if (str[i] != ' ' && str[i] != '\0')
++			size = i + 1;
++
++	return size;
++}
++
++static bool sfp_match(const char *qs, const char *str, size_t len)
++{
++	if (!qs)
++		return true;
++	if (strlen(qs) != len)
++		return false;
++	return !strncmp(qs, str, len);
++}
++
++static const struct sfp_quirk *sfp_lookup_quirk(const struct sfp_eeprom_id *id)
++{
++	const struct sfp_quirk *q;
++	unsigned int i;
++	size_t vs, ps;
++
++	vs = sfp_strlen(id->base.vendor_name, ARRAY_SIZE(id->base.vendor_name));
++	ps = sfp_strlen(id->base.vendor_pn, ARRAY_SIZE(id->base.vendor_pn));
++
++	for (i = 0, q = sfp_quirks; i < ARRAY_SIZE(sfp_quirks); i++, q++)
++		if (sfp_match(q->vendor, id->base.vendor_name, vs) &&
++		    sfp_match(q->part, id->base.vendor_pn, ps))
++			return q;
++
++	return NULL;
++}
++
+ static unsigned long poll_jiffies;
+ 
+ static unsigned int sfp_gpio_get_state(struct sfp *sfp)
+@@ -414,9 +553,6 @@ static int sfp_i2c_write(struct sfp *sfp, bool a2, u8 dev_addr, void *buf,
+ 
+ static int sfp_i2c_configure(struct sfp *sfp, struct i2c_adapter *i2c)
+ {
+-	struct mii_bus *i2c_mii;
+-	int ret;
+-
+ 	if (!i2c_check_functionality(i2c, I2C_FUNC_I2C))
+ 		return -EINVAL;
+ 
+@@ -424,7 +560,15 @@ static int sfp_i2c_configure(struct sfp *sfp, struct i2c_adapter *i2c)
+ 	sfp->read = sfp_i2c_read;
+ 	sfp->write = sfp_i2c_write;
+ 
+-	i2c_mii = mdio_i2c_alloc(sfp->dev, i2c);
++	return 0;
++}
++
++static int sfp_i2c_mdiobus_create(struct sfp *sfp)
++{
++	struct mii_bus *i2c_mii;
++	int ret;
++
++	i2c_mii = mdio_i2c_alloc(sfp->dev, sfp->i2c, sfp->mdio_protocol);
+ 	if (IS_ERR(i2c_mii))
+ 		return PTR_ERR(i2c_mii);
+ 
+@@ -442,6 +586,12 @@ static int sfp_i2c_configure(struct sfp *sfp, struct i2c_adapter *i2c)
+ 	return 0;
+ }
+ 
++static void sfp_i2c_mdiobus_destroy(struct sfp *sfp)
++{
++	mdiobus_unregister(sfp->i2c_mii);
++	sfp->i2c_mii = NULL;
++}
++
+ /* Interface */
+ static int sfp_read(struct sfp *sfp, bool a2, u8 addr, void *buf, size_t len)
+ {
+@@ -487,17 +637,18 @@ static void sfp_soft_set_state(struct sfp *sfp, unsigned int state)
+ static void sfp_soft_start_poll(struct sfp *sfp)
+ {
+ 	const struct sfp_eeprom_id *id = &sfp->id;
++	unsigned int mask = 0;
+ 
+ 	sfp->state_soft_mask = 0;
+-	if (id->ext.enhopts & SFP_ENHOPTS_SOFT_TX_DISABLE &&
+-	    !sfp->gpio[GPIO_TX_DISABLE])
+-		sfp->state_soft_mask |= SFP_F_TX_DISABLE;
+-	if (id->ext.enhopts & SFP_ENHOPTS_SOFT_TX_FAULT &&
+-	    !sfp->gpio[GPIO_TX_FAULT])
+-		sfp->state_soft_mask |= SFP_F_TX_FAULT;
+-	if (id->ext.enhopts & SFP_ENHOPTS_SOFT_RX_LOS &&
+-	    !sfp->gpio[GPIO_LOS])
+-		sfp->state_soft_mask |= SFP_F_LOS;
++	if (id->ext.enhopts & SFP_ENHOPTS_SOFT_TX_DISABLE)
++		mask |= SFP_F_TX_DISABLE;
++	if (id->ext.enhopts & SFP_ENHOPTS_SOFT_TX_FAULT)
++		mask |= SFP_F_TX_FAULT;
++	if (id->ext.enhopts & SFP_ENHOPTS_SOFT_RX_LOS)
++		mask |= SFP_F_LOS;
++
++	// Poll the soft state for hardware pins we want to ignore
++	sfp->state_soft_mask = ~sfp->state_hw_mask & mask;
+ 
+ 	if (sfp->state_soft_mask & (SFP_F_LOS | SFP_F_TX_FAULT) &&
+ 	    !sfp->need_poll)
+@@ -511,10 +662,11 @@ static void sfp_soft_stop_poll(struct sfp *sfp)
+ 
+ static unsigned int sfp_get_state(struct sfp *sfp)
+ {
+-	unsigned int state = sfp->get_state(sfp);
++	unsigned int soft = sfp->state_soft_mask & (SFP_F_LOS | SFP_F_TX_FAULT);
++	unsigned int state;
+ 
+-	if (state & SFP_F_PRESENT &&
+-	    sfp->state_soft_mask & (SFP_F_LOS | SFP_F_TX_FAULT))
++	state = sfp->get_state(sfp) & sfp->state_hw_mask;
++	if (state & SFP_F_PRESENT && soft)
+ 		state |= sfp_soft_get_state(sfp);
+ 
+ 	return state;
+@@ -1448,12 +1600,12 @@ static void sfp_sm_phy_detach(struct sfp *sfp)
+ 	sfp->mod_phy = NULL;
+ }
+ 
+-static int sfp_sm_probe_phy(struct sfp *sfp, bool is_c45)
++static int sfp_sm_probe_phy(struct sfp *sfp, int addr, bool is_c45)
+ {
+ 	struct phy_device *phy;
+ 	int err;
+ 
+-	phy = get_phy_device(sfp->i2c_mii, SFP_PHY_ADDR, is_c45);
++	phy = get_phy_device(sfp->i2c_mii, addr, is_c45);
+ 	if (phy == ERR_PTR(-ENODEV))
+ 		return PTR_ERR(phy);
+ 	if (IS_ERR(phy)) {
+@@ -1548,6 +1700,14 @@ static void sfp_sm_fault(struct sfp *sfp, unsigned int next_state, bool warn)
+ 	}
+ }
+ 
++static int sfp_sm_add_mdio_bus(struct sfp *sfp)
++{
++	if (sfp->mdio_protocol != MDIO_I2C_NONE)
++		return sfp_i2c_mdiobus_create(sfp);
++
++	return 0;
++}
++
+ /* Probe a SFP for a PHY device if the module supports copper - the PHY
+  * normally sits at I2C bus address 0x56, and may either be a clause 22
+  * or clause 45 PHY.
+@@ -1563,19 +1723,23 @@ static int sfp_sm_probe_for_phy(struct sfp *sfp)
+ {
+ 	int err = 0;
+ 
+-	switch (sfp->id.base.extended_cc) {
+-	case SFF8024_ECC_10GBASE_T_SFI:
+-	case SFF8024_ECC_10GBASE_T_SR:
+-	case SFF8024_ECC_5GBASE_T:
+-	case SFF8024_ECC_2_5GBASE_T:
+-		err = sfp_sm_probe_phy(sfp, true);
++	switch (sfp->mdio_protocol) {
++	case MDIO_I2C_NONE:
+ 		break;
+ 
+-	default:
+-		if (sfp->id.base.e1000_base_t)
+-			err = sfp_sm_probe_phy(sfp, false);
++	case MDIO_I2C_MARVELL_C22:
++		err = sfp_sm_probe_phy(sfp, SFP_PHY_ADDR, false);
++		break;
++
++	case MDIO_I2C_C45:
++		err = sfp_sm_probe_phy(sfp, SFP_PHY_ADDR, true);
++		break;
++
++	case MDIO_I2C_ROLLBALL:
++		err = sfp_sm_probe_phy(sfp, SFP_PHY_ADDR_ROLLBALL, true);
+ 		break;
+ 	}
++
+ 	return err;
+ }
+ 
+@@ -1819,11 +1983,33 @@ static int sfp_sm_mod_probe(struct sfp *sfp, bool report)
+ 	if (ret < 0)
+ 		return ret;
+ 
+-	if (!memcmp(id.base.vendor_name, "ALCATELLUCENT   ", 16) &&
+-	    !memcmp(id.base.vendor_pn, "3FE46541AA      ", 16))
+-		sfp->module_t_start_up = T_START_UP_BAD_GPON;
++	/* Initialise state bits to use from hardware */
++	sfp->state_hw_mask = SFP_F_PRESENT;
++	if (sfp->gpio[GPIO_TX_DISABLE])
++		sfp->state_hw_mask |= SFP_F_TX_DISABLE;
++	if (sfp->gpio[GPIO_TX_FAULT])
++		sfp->state_hw_mask |= SFP_F_TX_FAULT;
++	if (sfp->gpio[GPIO_LOS])
++		sfp->state_hw_mask |= SFP_F_LOS;
++
++	sfp->module_t_start_up = T_START_UP;
++	sfp->module_t_wait = T_WAIT;
++
++	sfp->tx_fault_ignore = false;
++
++	if (sfp->id.base.extended_cc == SFF8024_ECC_10GBASE_T_SFI ||
++	    sfp->id.base.extended_cc == SFF8024_ECC_10GBASE_T_SR ||
++	    sfp->id.base.extended_cc == SFF8024_ECC_5GBASE_T ||
++	    sfp->id.base.extended_cc == SFF8024_ECC_2_5GBASE_T)
++		sfp->mdio_protocol = MDIO_I2C_C45;
++	else if (sfp->id.base.e1000_base_t)
++		sfp->mdio_protocol = MDIO_I2C_MARVELL_C22;
+ 	else
+-		sfp->module_t_start_up = T_START_UP;
++		sfp->mdio_protocol = MDIO_I2C_NONE;
++
++	sfp->quirk = sfp_lookup_quirk(&id);
++	if (sfp->quirk && sfp->quirk->fixup)
++		sfp->quirk->fixup(sfp);
+ 
+ 	return 0;
+ }
+@@ -1936,7 +2122,8 @@ static void sfp_sm_module(struct sfp *sfp, unsigned int event)
+ 			break;
+ 
+ 		/* Report the module insertion to the upstream device */
+-		err = sfp_module_insert(sfp->sfp_bus, &sfp->id);
++		err = sfp_module_insert(sfp->sfp_bus, &sfp->id,
++					sfp->quirk);
+ 		if (err < 0) {
+ 			sfp_sm_mod_next(sfp, SFP_MOD_ERROR, 0);
+ 			break;
+@@ -1995,6 +2182,8 @@ static void sfp_sm_main(struct sfp *sfp, unsigned int event)
+ 			sfp_module_stop(sfp->sfp_bus);
+ 		if (sfp->mod_phy)
+ 			sfp_sm_phy_detach(sfp);
++		if (sfp->i2c_mii)
++			sfp_i2c_mdiobus_destroy(sfp);
+ 		sfp_module_tx_disable(sfp);
+ 		sfp_soft_stop_poll(sfp);
+ 		sfp_sm_next(sfp, SFP_S_DOWN, 0);
+@@ -2018,9 +2207,10 @@ static void sfp_sm_main(struct sfp *sfp, unsigned int event)
+ 
+ 		/* We need to check the TX_FAULT state, which is not defined
+ 		 * while TX_DISABLE is asserted. The earliest we want to do
+-		 * anything (such as probe for a PHY) is 50ms.
++		 * anything (such as probe for a PHY) is 50ms (or more on
++		 * specific modules).
+ 		 */
+-		sfp_sm_next(sfp, SFP_S_WAIT, T_WAIT);
++		sfp_sm_next(sfp, SFP_S_WAIT, sfp->module_t_wait);
+ 		break;
+ 
+ 	case SFP_S_WAIT:
+@@ -2034,8 +2224,8 @@ static void sfp_sm_main(struct sfp *sfp, unsigned int event)
+ 			 * deasserting.
+ 			 */
+ 			timeout = sfp->module_t_start_up;
+-			if (timeout > T_WAIT)
+-				timeout -= T_WAIT;
++			if (timeout > sfp->module_t_wait)
++				timeout -= sfp->module_t_wait;
+ 			else
+ 				timeout = 1;
+ 
+@@ -2057,6 +2247,12 @@ static void sfp_sm_main(struct sfp *sfp, unsigned int event)
+ 				     sfp->sm_fault_retries == N_FAULT_INIT);
+ 		} else if (event == SFP_E_TIMEOUT || event == SFP_E_TX_CLEAR) {
+ 	init_done:
++			/* Create mdiobus and start trying for PHY */
++			ret = sfp_sm_add_mdio_bus(sfp);
++			if (ret < 0) {
++				sfp_sm_next(sfp, SFP_S_FAIL, 0);
++				break;
++			}
+ 			sfp->sm_phy_retries = R_PHY_RETRY;
+ 			goto phy_probe;
+ 		}
+@@ -2409,6 +2605,8 @@ static int sfp_probe(struct platform_device *pdev)
+ 				return PTR_ERR(sfp->gpio[i]);
+ 		}
+ 
++	sfp->state_hw_mask = SFP_F_PRESENT;
++
+ 	sfp->get_state = sfp_gpio_get_state;
+ 	sfp->set_state = sfp_gpio_set_state;
+ 
+diff --git a/drivers/net/phy/sfp.h b/drivers/net/phy/sfp.h
+index b83f705..ef06d35 100644
+--- a/drivers/net/phy/sfp.h
++++ b/drivers/net/phy/sfp.h
+@@ -6,6 +6,13 @@
+ 
+ struct sfp;
+ 
++struct sfp_quirk {
++	const char *vendor;
++	const char *part;
++	void (*modes)(const struct sfp_eeprom_id *id, unsigned long *modes);
++	void (*fixup)(struct sfp *sfp);
++};
++
+ struct sfp_socket_ops {
+ 	void (*attach)(struct sfp *sfp);
+ 	void (*detach)(struct sfp *sfp);
+@@ -20,7 +27,8 @@ int sfp_add_phy(struct sfp_bus *bus, struct phy_device *phydev);
+ void sfp_remove_phy(struct sfp_bus *bus);
+ void sfp_link_up(struct sfp_bus *bus);
+ void sfp_link_down(struct sfp_bus *bus);
+-int sfp_module_insert(struct sfp_bus *bus, const struct sfp_eeprom_id *id);
++int sfp_module_insert(struct sfp_bus *bus, const struct sfp_eeprom_id *id,
++		      const struct sfp_quirk *quirk);
+ void sfp_module_remove(struct sfp_bus *bus);
+ int sfp_module_start(struct sfp_bus *bus);
+ void sfp_module_stop(struct sfp_bus *bus);
+diff --git a/drivers/net/phy/marvell10g.c b/drivers/net/phy/marvell10g.c
+index 512f27b..2c2d638 100644
+--- a/drivers/net/phy/marvell10g.c
++++ b/drivers/net/phy/marvell10g.c
+@@ -155,13 +155,6 @@ static int mv3310_hwmon_config(struct phy_device *phydev, bool enable)
+ 			      MV_V2_TEMP_CTRL_MASK, val);
+ }
+ 
+-static void mv3310_hwmon_disable(void *data)
+-{
+-	struct phy_device *phydev = data;
+-
+-	mv3310_hwmon_config(phydev, false);
+-}
+-
+ static int mv3310_hwmon_probe(struct phy_device *phydev)
+ {
+ 	struct device *dev = &phydev->mdio.dev;
+@@ -185,10 +178,6 @@ static int mv3310_hwmon_probe(struct phy_device *phydev)
+ 	if (ret)
+ 		return ret;
+ 
+-	ret = devm_add_action_or_reset(dev, mv3310_hwmon_disable, phydev);
+-	if (ret)
+-		return ret;
+-
+ 	priv->hwmon_dev = devm_hwmon_device_register_with_info(dev,
+ 				priv->hwmon_name, phydev,
+ 				&mv3310_hwmon_chip_info, NULL);
+@@ -262,6 +251,11 @@ static int mv3310_probe(struct phy_device *phydev)
+ 	return phy_sfp_probe(phydev, &mv3310_sfp_ops);
+ }
+ 
++static void mv3310_remove(struct phy_device *phydev)
++{
++	mv3310_hwmon_config(phydev, false);
++}
++
+ static int mv3310_suspend(struct phy_device *phydev)
+ {
+ 	return phy_set_bits_mmd(phydev, MDIO_MMD_VEND2, MV_V2_PORT_CTRL,
+@@ -513,6 +507,7 @@ static struct phy_driver mv3310_drivers[] = {
+ 		.config_aneg	= mv3310_config_aneg,
+ 		.aneg_done	= mv3310_aneg_done,
+ 		.read_status	= mv3310_read_status,
++		.remove		= mv3310_remove,
+ 	},
+ 	{
+ 		.phy_id		= MARVELL_PHY_ID_88E2110,
+@@ -526,6 +521,7 @@ static struct phy_driver mv3310_drivers[] = {
+ 		.config_aneg	= mv3310_config_aneg,
+ 		.aneg_done	= mv3310_aneg_done,
+ 		.read_status	= mv3310_read_status,
++		.remove		= mv3310_remove,
+ 	},
+ };
+ 
diff --git a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/patches-5.4/patches-5.4.inc b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/patches-5.4/patches-5.4.inc
index 2bc5c34..d3eb31e 100644
--- a/recipes-kernel/linux/linux-mediatek-5.4/mediatek/patches-5.4/patches-5.4.inc
+++ b/recipes-kernel/linux/linux-mediatek-5.4/mediatek/patches-5.4/patches-5.4.inc
@@ -90,6 +90,7 @@
     file://413-mtd-spinand-gigadevice-Add-support-for-GD5FxGQxUExxG-GD5FxGQxUExxH-and-GD5FxGMxUExxG-series.patch \
     file://414-mtd-spinand-fix-gigadevice-read-dummy.patch \
     file://415-mtd-spinand-fix-F50L1G41LB-ecc-check.patch \
+    file://416-mtd-spinor-support-EN25QX128A.patch \
     file://492-mtd-tests-fix-pagetest-load.patch \
     file://500-auxadc-add-auxadc-32k-clk.patch \
     file://6001-mtk-thermal-add-lvts-support.patch \
@@ -115,6 +116,7 @@
     file://751-net-phy-aquantia-add-firmware-download.patch \
     file://752-net-dsa-phy-coverity-scan.patch \
     file://753-net-mt753x-phy-coverity-scan.patch;apply=no \
+    file://754-net-phy-sfp-add-rollball-support.patch \
     file://8000-PATCH-1-4-tphy-support-type-switch-by-pericfg.patch \
     file://8001-PATCH-2-4-dt-bindings-phy-Add-PHY_TYPE_DP-definition.patch \
     file://8002-PATCH-3-4-dt-bindings-phy-Add-PHY_TYPE_XPCS-definition.patch \
diff --git a/recipes-wifi/hostapd/files/patches-2.10.3/761-shared_das_port.patch b/recipes-wifi/hostapd/files/patches-2.10.3/761-shared_das_port.patch
new file mode 100644
index 0000000..7516b73
--- /dev/null
+++ b/recipes-wifi/hostapd/files/patches-2.10.3/761-shared_das_port.patch
@@ -0,0 +1,298 @@
+--- a/src/radius/radius_das.h
++++ b/src/radius/radius_das.h
+@@ -44,6 +44,7 @@ struct radius_das_attrs {
+ struct radius_das_conf {
+ 	int port;
+ 	const u8 *shared_secret;
++	const u8 *nas_identifier;
+ 	size_t shared_secret_len;
+ 	const struct hostapd_ip_addr *client_addr;
+ 	unsigned int time_window;
+--- a/src/ap/hostapd.c
++++ b/src/ap/hostapd.c
+@@ -1367,6 +1367,7 @@ static int hostapd_setup_bss(struct host
+ 		struct radius_das_conf das_conf;
+ 		os_memset(&das_conf, 0, sizeof(das_conf));
+ 		das_conf.port = conf->radius_das_port;
++		das_conf.nas_identifier = conf->nas_identifier;
+ 		das_conf.shared_secret = conf->radius_das_shared_secret;
+ 		das_conf.shared_secret_len =
+ 			conf->radius_das_shared_secret_len;
+--- a/src/radius/radius_das.c
++++ b/src/radius/radius_das.c
+@@ -12,13 +12,26 @@
+ #include "utils/common.h"
+ #include "utils/eloop.h"
+ #include "utils/ip_addr.h"
++#include "utils/list.h"
+ #include "radius.h"
+ #include "radius_das.h"
+ 
+ 
+-struct radius_das_data {
++static struct dl_list das_ports = DL_LIST_HEAD_INIT(das_ports);
++
++struct radius_das_port {
++	struct dl_list list;
++	struct dl_list das_data;
++
++	int port;
+ 	int sock;
++};
++
++struct radius_das_data {
++	struct dl_list list;
++	struct radius_das_port *port;
+ 	u8 *shared_secret;
++	u8 *nas_identifier;
+ 	size_t shared_secret_len;
+ 	struct hostapd_ip_addr client_addr;
+ 	unsigned int time_window;
+@@ -378,56 +391,17 @@ fail:
+ }
+ 
+ 
+-static void radius_das_receive(int sock, void *eloop_ctx, void *sock_ctx)
++static void
++radius_das_receive_msg(struct radius_das_data *das, struct radius_msg *msg,
++		       struct sockaddr *from, socklen_t fromlen,
++		       char *abuf, int from_port)
+ {
+-	struct radius_das_data *das = eloop_ctx;
+-	u8 buf[1500];
+-	union {
+-		struct sockaddr_storage ss;
+-		struct sockaddr_in sin;
+-#ifdef CONFIG_IPV6
+-		struct sockaddr_in6 sin6;
+-#endif /* CONFIG_IPV6 */
+-	} from;
+-	char abuf[50];
+-	int from_port = 0;
+-	socklen_t fromlen;
+-	int len;
+-	struct radius_msg *msg, *reply = NULL;
++	struct radius_msg *reply = NULL;
+ 	struct radius_hdr *hdr;
+ 	struct wpabuf *rbuf;
++	struct os_time now;
+ 	u32 val;
+ 	int res;
+-	struct os_time now;
+-
+-	fromlen = sizeof(from);
+-	len = recvfrom(sock, buf, sizeof(buf), 0,
+-		       (struct sockaddr *) &from.ss, &fromlen);
+-	if (len < 0) {
+-		wpa_printf(MSG_ERROR, "DAS: recvfrom: %s", strerror(errno));
+-		return;
+-	}
+-
+-	os_strlcpy(abuf, inet_ntoa(from.sin.sin_addr), sizeof(abuf));
+-	from_port = ntohs(from.sin.sin_port);
+-
+-	wpa_printf(MSG_DEBUG, "DAS: Received %d bytes from %s:%d",
+-		   len, abuf, from_port);
+-	if (das->client_addr.u.v4.s_addr &&
+-	    das->client_addr.u.v4.s_addr != from.sin.sin_addr.s_addr) {
+-		wpa_printf(MSG_DEBUG, "DAS: Drop message from unknown client");
+-		return;
+-	}
+-
+-	msg = radius_msg_parse(buf, len);
+-	if (msg == NULL) {
+-		wpa_printf(MSG_DEBUG, "DAS: Parsing incoming RADIUS packet "
+-			   "from %s:%d failed", abuf, from_port);
+-		return;
+-	}
+-
+-	if (wpa_debug_level <= MSG_MSGDUMP)
+-		radius_msg_dump(msg);
+ 
+ 	if (radius_msg_verify_das_req(msg, das->shared_secret,
+ 				       das->shared_secret_len,
+@@ -494,9 +468,8 @@ static void radius_das_receive(int sock,
+ 			radius_msg_dump(reply);
+ 
+ 		rbuf = radius_msg_get_buf(reply);
+-		res = sendto(das->sock, wpabuf_head(rbuf),
+-			     wpabuf_len(rbuf), 0,
+-			     (struct sockaddr *) &from.ss, fromlen);
++		res = sendto(das->port->sock, wpabuf_head(rbuf),
++			     wpabuf_len(rbuf), 0, from, fromlen);
+ 		if (res < 0) {
+ 			wpa_printf(MSG_ERROR, "DAS: sendto(to %s:%d): %s",
+ 				   abuf, from_port, strerror(errno));
+@@ -508,6 +481,72 @@ fail:
+ 	radius_msg_free(reply);
+ }
+ 
++static void radius_das_receive(int sock, void *eloop_ctx, void *sock_ctx)
++{
++	struct radius_das_port *p = eloop_ctx;
++	struct radius_das_data *das;
++	u8 buf[1500];
++	union {
++		struct sockaddr_storage ss;
++		struct sockaddr_in sin;
++#ifdef CONFIG_IPV6
++		struct sockaddr_in6 sin6;
++#endif /* CONFIG_IPV6 */
++	} from;
++	struct radius_msg *msg;
++	size_t nasid_len = 0;
++	u8 *nasid_buf = NULL;
++	char abuf[50];
++	int from_port = 0;
++	socklen_t fromlen;
++	int found = 0;
++	int len;
++
++	fromlen = sizeof(from);
++	len = recvfrom(sock, buf, sizeof(buf), 0,
++		       (struct sockaddr *) &from.ss, &fromlen);
++	if (len < 0) {
++		wpa_printf(MSG_ERROR, "DAS: recvfrom: %s", strerror(errno));
++		return;
++	}
++
++	os_strlcpy(abuf, inet_ntoa(from.sin.sin_addr), sizeof(abuf));
++	from_port = ntohs(from.sin.sin_port);
++
++	msg = radius_msg_parse(buf, len);
++	if (msg == NULL) {
++		wpa_printf(MSG_DEBUG, "DAS: Parsing incoming RADIUS packet "
++			   "from %s:%d failed", abuf, from_port);
++		return;
++	}
++
++	wpa_printf(MSG_DEBUG, "DAS: Received %d bytes from %s:%d",
++		   len, abuf, from_port);
++
++	if (wpa_debug_level <= MSG_MSGDUMP)
++		radius_msg_dump(msg);
++
++	radius_msg_get_attr_ptr(msg, RADIUS_ATTR_NAS_IDENTIFIER,
++				&nasid_buf, &nasid_len, NULL);
++	dl_list_for_each(das, &p->das_data, struct radius_das_data, list) {
++		if (das->client_addr.u.v4.s_addr &&
++		    das->client_addr.u.v4.s_addr != from.sin.sin_addr.s_addr)
++			continue;
++
++		if (das->nas_identifier && nasid_buf &&
++		    (nasid_len != os_strlen(das->nas_identifier) ||
++		     os_memcmp(das->nas_identifier, nasid_buf, nasid_len) != 0))
++			continue;
++
++		found = 1;
++		radius_das_receive_msg(das, msg, (struct sockaddr *)&from.ss,
++				       fromlen, abuf, from_port);
++	}
++
++	if (!found)
++		wpa_printf(MSG_DEBUG, "DAS: Drop message from unknown client");
++}
++
+ 
+ static int radius_das_open_socket(int port)
+ {
+@@ -533,6 +572,49 @@ static int radius_das_open_socket(int po
+ }
+ 
+ 
++static struct radius_das_port *
++radius_das_open_port(int port)
++{
++	struct radius_das_port *p;
++
++	dl_list_for_each(p, &das_ports, struct radius_das_port, list) {
++		if (p->port == port)
++			return p;
++	}
++
++	p = os_zalloc(sizeof(*p));
++	if (p == NULL)
++		return NULL;
++
++	dl_list_init(&p->das_data);
++	p->port = port;
++	p->sock = radius_das_open_socket(port);
++	if (p->sock < 0)
++		goto free_port;
++
++	if (eloop_register_read_sock(p->sock, radius_das_receive, p, NULL))
++		goto close_port;
++
++	dl_list_add(&das_ports, &p->list);
++
++	return p;
++
++close_port:
++	close(p->sock);
++free_port:
++	os_free(p);
++
++	return NULL;
++}
++
++static void radius_das_close_port(struct radius_das_port *p)
++{
++	dl_list_del(&p->list);
++	eloop_unregister_read_sock(p->sock);
++	close(p->sock);
++	free(p);
++}
++
+ struct radius_das_data *
+ radius_das_init(struct radius_das_conf *conf)
+ {
+@@ -553,6 +635,8 @@ radius_das_init(struct radius_das_conf *
+ 	das->ctx = conf->ctx;
+ 	das->disconnect = conf->disconnect;
+ 	das->coa = conf->coa;
++	if (conf->nas_identifier)
++		das->nas_identifier = os_strdup(conf->nas_identifier);
+ 
+ 	os_memcpy(&das->client_addr, conf->client_addr,
+ 		  sizeof(das->client_addr));
+@@ -565,19 +649,15 @@ radius_das_init(struct radius_das_conf *
+ 	}
+ 	das->shared_secret_len = conf->shared_secret_len;
+ 
+-	das->sock = radius_das_open_socket(conf->port);
+-	if (das->sock < 0) {
++	das->port = radius_das_open_port(conf->port);
++	if (!das->port) {
+ 		wpa_printf(MSG_ERROR, "Failed to open UDP socket for RADIUS "
+ 			   "DAS");
+ 		radius_das_deinit(das);
+ 		return NULL;
+ 	}
+ 
+-	if (eloop_register_read_sock(das->sock, radius_das_receive, das, NULL))
+-	{
+-		radius_das_deinit(das);
+-		return NULL;
+-	}
++	dl_list_add(&das->port->das_data, &das->list);
+ 
+ 	return das;
+ }
+@@ -588,11 +668,14 @@ void radius_das_deinit(struct radius_das
+ 	if (das == NULL)
+ 		return;
+ 
+-	if (das->sock >= 0) {
+-		eloop_unregister_read_sock(das->sock);
+-		close(das->sock);
++	if (das->port) {
++		dl_list_del(&das->list);
++
++		if (dl_list_empty(&das->port->das_data))
++			radius_das_close_port(das->port);
+ 	}
+ 
++	os_free(das->nas_identifier);
+ 	os_free(das->shared_secret);
+ 	os_free(das);
+ }
diff --git a/recipes-wifi/hostapd/files/patches-2.10.3/patches.inc b/recipes-wifi/hostapd/files/patches-2.10.3/patches.inc
index 90f3cb0..500c124 100644
--- a/recipes-wifi/hostapd/files/patches-2.10.3/patches.inc
+++ b/recipes-wifi/hostapd/files/patches-2.10.3/patches.inc
@@ -58,6 +58,7 @@
     file://750-qos_map_set_without_interworking.patch \
     file://751-qos_map_ignore_when_unsupported.patch \
     file://760-dynamic_own_ip.patch \
+    file://761-shared_das_port.patch \
     file://800-acs-don-t-select-indoor-channel-on-outdoor-operation.patch \
     file://990-ctrl-make-WNM_AP-functions-dependant-on-CONFIG_AP.patch \
     file://992-openssl-include-rsa.patch \
diff --git a/recipes-wifi/hostapd/files/patches/761-shared_das_port.patch b/recipes-wifi/hostapd/files/patches/761-shared_das_port.patch
new file mode 100644
index 0000000..7516b73
--- /dev/null
+++ b/recipes-wifi/hostapd/files/patches/761-shared_das_port.patch
@@ -0,0 +1,298 @@
+--- a/src/radius/radius_das.h
++++ b/src/radius/radius_das.h
+@@ -44,6 +44,7 @@ struct radius_das_attrs {
+ struct radius_das_conf {
+ 	int port;
+ 	const u8 *shared_secret;
++	const u8 *nas_identifier;
+ 	size_t shared_secret_len;
+ 	const struct hostapd_ip_addr *client_addr;
+ 	unsigned int time_window;
+--- a/src/ap/hostapd.c
++++ b/src/ap/hostapd.c
+@@ -1367,6 +1367,7 @@ static int hostapd_setup_bss(struct host
+ 		struct radius_das_conf das_conf;
+ 		os_memset(&das_conf, 0, sizeof(das_conf));
+ 		das_conf.port = conf->radius_das_port;
++		das_conf.nas_identifier = conf->nas_identifier;
+ 		das_conf.shared_secret = conf->radius_das_shared_secret;
+ 		das_conf.shared_secret_len =
+ 			conf->radius_das_shared_secret_len;
+--- a/src/radius/radius_das.c
++++ b/src/radius/radius_das.c
+@@ -12,13 +12,26 @@
+ #include "utils/common.h"
+ #include "utils/eloop.h"
+ #include "utils/ip_addr.h"
++#include "utils/list.h"
+ #include "radius.h"
+ #include "radius_das.h"
+ 
+ 
+-struct radius_das_data {
++static struct dl_list das_ports = DL_LIST_HEAD_INIT(das_ports);
++
++struct radius_das_port {
++	struct dl_list list;
++	struct dl_list das_data;
++
++	int port;
+ 	int sock;
++};
++
++struct radius_das_data {
++	struct dl_list list;
++	struct radius_das_port *port;
+ 	u8 *shared_secret;
++	u8 *nas_identifier;
+ 	size_t shared_secret_len;
+ 	struct hostapd_ip_addr client_addr;
+ 	unsigned int time_window;
+@@ -378,56 +391,17 @@ fail:
+ }
+ 
+ 
+-static void radius_das_receive(int sock, void *eloop_ctx, void *sock_ctx)
++static void
++radius_das_receive_msg(struct radius_das_data *das, struct radius_msg *msg,
++		       struct sockaddr *from, socklen_t fromlen,
++		       char *abuf, int from_port)
+ {
+-	struct radius_das_data *das = eloop_ctx;
+-	u8 buf[1500];
+-	union {
+-		struct sockaddr_storage ss;
+-		struct sockaddr_in sin;
+-#ifdef CONFIG_IPV6
+-		struct sockaddr_in6 sin6;
+-#endif /* CONFIG_IPV6 */
+-	} from;
+-	char abuf[50];
+-	int from_port = 0;
+-	socklen_t fromlen;
+-	int len;
+-	struct radius_msg *msg, *reply = NULL;
++	struct radius_msg *reply = NULL;
+ 	struct radius_hdr *hdr;
+ 	struct wpabuf *rbuf;
++	struct os_time now;
+ 	u32 val;
+ 	int res;
+-	struct os_time now;
+-
+-	fromlen = sizeof(from);
+-	len = recvfrom(sock, buf, sizeof(buf), 0,
+-		       (struct sockaddr *) &from.ss, &fromlen);
+-	if (len < 0) {
+-		wpa_printf(MSG_ERROR, "DAS: recvfrom: %s", strerror(errno));
+-		return;
+-	}
+-
+-	os_strlcpy(abuf, inet_ntoa(from.sin.sin_addr), sizeof(abuf));
+-	from_port = ntohs(from.sin.sin_port);
+-
+-	wpa_printf(MSG_DEBUG, "DAS: Received %d bytes from %s:%d",
+-		   len, abuf, from_port);
+-	if (das->client_addr.u.v4.s_addr &&
+-	    das->client_addr.u.v4.s_addr != from.sin.sin_addr.s_addr) {
+-		wpa_printf(MSG_DEBUG, "DAS: Drop message from unknown client");
+-		return;
+-	}
+-
+-	msg = radius_msg_parse(buf, len);
+-	if (msg == NULL) {
+-		wpa_printf(MSG_DEBUG, "DAS: Parsing incoming RADIUS packet "
+-			   "from %s:%d failed", abuf, from_port);
+-		return;
+-	}
+-
+-	if (wpa_debug_level <= MSG_MSGDUMP)
+-		radius_msg_dump(msg);
+ 
+ 	if (radius_msg_verify_das_req(msg, das->shared_secret,
+ 				       das->shared_secret_len,
+@@ -494,9 +468,8 @@ static void radius_das_receive(int sock,
+ 			radius_msg_dump(reply);
+ 
+ 		rbuf = radius_msg_get_buf(reply);
+-		res = sendto(das->sock, wpabuf_head(rbuf),
+-			     wpabuf_len(rbuf), 0,
+-			     (struct sockaddr *) &from.ss, fromlen);
++		res = sendto(das->port->sock, wpabuf_head(rbuf),
++			     wpabuf_len(rbuf), 0, from, fromlen);
+ 		if (res < 0) {
+ 			wpa_printf(MSG_ERROR, "DAS: sendto(to %s:%d): %s",
+ 				   abuf, from_port, strerror(errno));
+@@ -508,6 +481,72 @@ fail:
+ 	radius_msg_free(reply);
+ }
+ 
++static void radius_das_receive(int sock, void *eloop_ctx, void *sock_ctx)
++{
++	struct radius_das_port *p = eloop_ctx;
++	struct radius_das_data *das;
++	u8 buf[1500];
++	union {
++		struct sockaddr_storage ss;
++		struct sockaddr_in sin;
++#ifdef CONFIG_IPV6
++		struct sockaddr_in6 sin6;
++#endif /* CONFIG_IPV6 */
++	} from;
++	struct radius_msg *msg;
++	size_t nasid_len = 0;
++	u8 *nasid_buf = NULL;
++	char abuf[50];
++	int from_port = 0;
++	socklen_t fromlen;
++	int found = 0;
++	int len;
++
++	fromlen = sizeof(from);
++	len = recvfrom(sock, buf, sizeof(buf), 0,
++		       (struct sockaddr *) &from.ss, &fromlen);
++	if (len < 0) {
++		wpa_printf(MSG_ERROR, "DAS: recvfrom: %s", strerror(errno));
++		return;
++	}
++
++	os_strlcpy(abuf, inet_ntoa(from.sin.sin_addr), sizeof(abuf));
++	from_port = ntohs(from.sin.sin_port);
++
++	msg = radius_msg_parse(buf, len);
++	if (msg == NULL) {
++		wpa_printf(MSG_DEBUG, "DAS: Parsing incoming RADIUS packet "
++			   "from %s:%d failed", abuf, from_port);
++		return;
++	}
++
++	wpa_printf(MSG_DEBUG, "DAS: Received %d bytes from %s:%d",
++		   len, abuf, from_port);
++
++	if (wpa_debug_level <= MSG_MSGDUMP)
++		radius_msg_dump(msg);
++
++	radius_msg_get_attr_ptr(msg, RADIUS_ATTR_NAS_IDENTIFIER,
++				&nasid_buf, &nasid_len, NULL);
++	dl_list_for_each(das, &p->das_data, struct radius_das_data, list) {
++		if (das->client_addr.u.v4.s_addr &&
++		    das->client_addr.u.v4.s_addr != from.sin.sin_addr.s_addr)
++			continue;
++
++		if (das->nas_identifier && nasid_buf &&
++		    (nasid_len != os_strlen(das->nas_identifier) ||
++		     os_memcmp(das->nas_identifier, nasid_buf, nasid_len) != 0))
++			continue;
++
++		found = 1;
++		radius_das_receive_msg(das, msg, (struct sockaddr *)&from.ss,
++				       fromlen, abuf, from_port);
++	}
++
++	if (!found)
++		wpa_printf(MSG_DEBUG, "DAS: Drop message from unknown client");
++}
++
+ 
+ static int radius_das_open_socket(int port)
+ {
+@@ -533,6 +572,49 @@ static int radius_das_open_socket(int po
+ }
+ 
+ 
++static struct radius_das_port *
++radius_das_open_port(int port)
++{
++	struct radius_das_port *p;
++
++	dl_list_for_each(p, &das_ports, struct radius_das_port, list) {
++		if (p->port == port)
++			return p;
++	}
++
++	p = os_zalloc(sizeof(*p));
++	if (p == NULL)
++		return NULL;
++
++	dl_list_init(&p->das_data);
++	p->port = port;
++	p->sock = radius_das_open_socket(port);
++	if (p->sock < 0)
++		goto free_port;
++
++	if (eloop_register_read_sock(p->sock, radius_das_receive, p, NULL))
++		goto close_port;
++
++	dl_list_add(&das_ports, &p->list);
++
++	return p;
++
++close_port:
++	close(p->sock);
++free_port:
++	os_free(p);
++
++	return NULL;
++}
++
++static void radius_das_close_port(struct radius_das_port *p)
++{
++	dl_list_del(&p->list);
++	eloop_unregister_read_sock(p->sock);
++	close(p->sock);
++	free(p);
++}
++
+ struct radius_das_data *
+ radius_das_init(struct radius_das_conf *conf)
+ {
+@@ -553,6 +635,8 @@ radius_das_init(struct radius_das_conf *
+ 	das->ctx = conf->ctx;
+ 	das->disconnect = conf->disconnect;
+ 	das->coa = conf->coa;
++	if (conf->nas_identifier)
++		das->nas_identifier = os_strdup(conf->nas_identifier);
+ 
+ 	os_memcpy(&das->client_addr, conf->client_addr,
+ 		  sizeof(das->client_addr));
+@@ -565,19 +649,15 @@ radius_das_init(struct radius_das_conf *
+ 	}
+ 	das->shared_secret_len = conf->shared_secret_len;
+ 
+-	das->sock = radius_das_open_socket(conf->port);
+-	if (das->sock < 0) {
++	das->port = radius_das_open_port(conf->port);
++	if (!das->port) {
+ 		wpa_printf(MSG_ERROR, "Failed to open UDP socket for RADIUS "
+ 			   "DAS");
+ 		radius_das_deinit(das);
+ 		return NULL;
+ 	}
+ 
+-	if (eloop_register_read_sock(das->sock, radius_das_receive, das, NULL))
+-	{
+-		radius_das_deinit(das);
+-		return NULL;
+-	}
++	dl_list_add(&das->port->das_data, &das->list);
+ 
+ 	return das;
+ }
+@@ -588,11 +668,14 @@ void radius_das_deinit(struct radius_das
+ 	if (das == NULL)
+ 		return;
+ 
+-	if (das->sock >= 0) {
+-		eloop_unregister_read_sock(das->sock);
+-		close(das->sock);
++	if (das->port) {
++		dl_list_del(&das->list);
++
++		if (dl_list_empty(&das->port->das_data))
++			radius_das_close_port(das->port);
+ 	}
+ 
++	os_free(das->nas_identifier);
+ 	os_free(das->shared_secret);
+ 	os_free(das);
+ }
diff --git a/recipes-wifi/hostapd/files/patches/patches.inc b/recipes-wifi/hostapd/files/patches/patches.inc
index 5aa02e0..aeb1f6b 100644
--- a/recipes-wifi/hostapd/files/patches/patches.inc
+++ b/recipes-wifi/hostapd/files/patches/patches.inc
@@ -58,6 +58,7 @@
     file://750-qos_map_set_without_interworking.patch \
     file://751-qos_map_ignore_when_unsupported.patch \
     file://760-dynamic_own_ip.patch \
+    file://761-shared_das_port.patch \
     file://800-acs-don-t-select-indoor-channel-on-outdoor-operation.patch \
     file://990-ctrl-make-WNM_AP-functions-dependant-on-CONFIG_AP.patch \
     file://992-openssl-include-rsa.patch \
diff --git a/recipes-wifi/wpa-supplicant/files/patches-2.10.3/761-shared_das_port.patch b/recipes-wifi/wpa-supplicant/files/patches-2.10.3/761-shared_das_port.patch
new file mode 100644
index 0000000..7516b73
--- /dev/null
+++ b/recipes-wifi/wpa-supplicant/files/patches-2.10.3/761-shared_das_port.patch
@@ -0,0 +1,298 @@
+--- a/src/radius/radius_das.h
++++ b/src/radius/radius_das.h
+@@ -44,6 +44,7 @@ struct radius_das_attrs {
+ struct radius_das_conf {
+ 	int port;
+ 	const u8 *shared_secret;
++	const u8 *nas_identifier;
+ 	size_t shared_secret_len;
+ 	const struct hostapd_ip_addr *client_addr;
+ 	unsigned int time_window;
+--- a/src/ap/hostapd.c
++++ b/src/ap/hostapd.c
+@@ -1367,6 +1367,7 @@ static int hostapd_setup_bss(struct host
+ 		struct radius_das_conf das_conf;
+ 		os_memset(&das_conf, 0, sizeof(das_conf));
+ 		das_conf.port = conf->radius_das_port;
++		das_conf.nas_identifier = conf->nas_identifier;
+ 		das_conf.shared_secret = conf->radius_das_shared_secret;
+ 		das_conf.shared_secret_len =
+ 			conf->radius_das_shared_secret_len;
+--- a/src/radius/radius_das.c
++++ b/src/radius/radius_das.c
+@@ -12,13 +12,26 @@
+ #include "utils/common.h"
+ #include "utils/eloop.h"
+ #include "utils/ip_addr.h"
++#include "utils/list.h"
+ #include "radius.h"
+ #include "radius_das.h"
+ 
+ 
+-struct radius_das_data {
++static struct dl_list das_ports = DL_LIST_HEAD_INIT(das_ports);
++
++struct radius_das_port {
++	struct dl_list list;
++	struct dl_list das_data;
++
++	int port;
+ 	int sock;
++};
++
++struct radius_das_data {
++	struct dl_list list;
++	struct radius_das_port *port;
+ 	u8 *shared_secret;
++	u8 *nas_identifier;
+ 	size_t shared_secret_len;
+ 	struct hostapd_ip_addr client_addr;
+ 	unsigned int time_window;
+@@ -378,56 +391,17 @@ fail:
+ }
+ 
+ 
+-static void radius_das_receive(int sock, void *eloop_ctx, void *sock_ctx)
++static void
++radius_das_receive_msg(struct radius_das_data *das, struct radius_msg *msg,
++		       struct sockaddr *from, socklen_t fromlen,
++		       char *abuf, int from_port)
+ {
+-	struct radius_das_data *das = eloop_ctx;
+-	u8 buf[1500];
+-	union {
+-		struct sockaddr_storage ss;
+-		struct sockaddr_in sin;
+-#ifdef CONFIG_IPV6
+-		struct sockaddr_in6 sin6;
+-#endif /* CONFIG_IPV6 */
+-	} from;
+-	char abuf[50];
+-	int from_port = 0;
+-	socklen_t fromlen;
+-	int len;
+-	struct radius_msg *msg, *reply = NULL;
++	struct radius_msg *reply = NULL;
+ 	struct radius_hdr *hdr;
+ 	struct wpabuf *rbuf;
++	struct os_time now;
+ 	u32 val;
+ 	int res;
+-	struct os_time now;
+-
+-	fromlen = sizeof(from);
+-	len = recvfrom(sock, buf, sizeof(buf), 0,
+-		       (struct sockaddr *) &from.ss, &fromlen);
+-	if (len < 0) {
+-		wpa_printf(MSG_ERROR, "DAS: recvfrom: %s", strerror(errno));
+-		return;
+-	}
+-
+-	os_strlcpy(abuf, inet_ntoa(from.sin.sin_addr), sizeof(abuf));
+-	from_port = ntohs(from.sin.sin_port);
+-
+-	wpa_printf(MSG_DEBUG, "DAS: Received %d bytes from %s:%d",
+-		   len, abuf, from_port);
+-	if (das->client_addr.u.v4.s_addr &&
+-	    das->client_addr.u.v4.s_addr != from.sin.sin_addr.s_addr) {
+-		wpa_printf(MSG_DEBUG, "DAS: Drop message from unknown client");
+-		return;
+-	}
+-
+-	msg = radius_msg_parse(buf, len);
+-	if (msg == NULL) {
+-		wpa_printf(MSG_DEBUG, "DAS: Parsing incoming RADIUS packet "
+-			   "from %s:%d failed", abuf, from_port);
+-		return;
+-	}
+-
+-	if (wpa_debug_level <= MSG_MSGDUMP)
+-		radius_msg_dump(msg);
+ 
+ 	if (radius_msg_verify_das_req(msg, das->shared_secret,
+ 				       das->shared_secret_len,
+@@ -494,9 +468,8 @@ static void radius_das_receive(int sock,
+ 			radius_msg_dump(reply);
+ 
+ 		rbuf = radius_msg_get_buf(reply);
+-		res = sendto(das->sock, wpabuf_head(rbuf),
+-			     wpabuf_len(rbuf), 0,
+-			     (struct sockaddr *) &from.ss, fromlen);
++		res = sendto(das->port->sock, wpabuf_head(rbuf),
++			     wpabuf_len(rbuf), 0, from, fromlen);
+ 		if (res < 0) {
+ 			wpa_printf(MSG_ERROR, "DAS: sendto(to %s:%d): %s",
+ 				   abuf, from_port, strerror(errno));
+@@ -508,6 +481,72 @@ fail:
+ 	radius_msg_free(reply);
+ }
+ 
++static void radius_das_receive(int sock, void *eloop_ctx, void *sock_ctx)
++{
++	struct radius_das_port *p = eloop_ctx;
++	struct radius_das_data *das;
++	u8 buf[1500];
++	union {
++		struct sockaddr_storage ss;
++		struct sockaddr_in sin;
++#ifdef CONFIG_IPV6
++		struct sockaddr_in6 sin6;
++#endif /* CONFIG_IPV6 */
++	} from;
++	struct radius_msg *msg;
++	size_t nasid_len = 0;
++	u8 *nasid_buf = NULL;
++	char abuf[50];
++	int from_port = 0;
++	socklen_t fromlen;
++	int found = 0;
++	int len;
++
++	fromlen = sizeof(from);
++	len = recvfrom(sock, buf, sizeof(buf), 0,
++		       (struct sockaddr *) &from.ss, &fromlen);
++	if (len < 0) {
++		wpa_printf(MSG_ERROR, "DAS: recvfrom: %s", strerror(errno));
++		return;
++	}
++
++	os_strlcpy(abuf, inet_ntoa(from.sin.sin_addr), sizeof(abuf));
++	from_port = ntohs(from.sin.sin_port);
++
++	msg = radius_msg_parse(buf, len);
++	if (msg == NULL) {
++		wpa_printf(MSG_DEBUG, "DAS: Parsing incoming RADIUS packet "
++			   "from %s:%d failed", abuf, from_port);
++		return;
++	}
++
++	wpa_printf(MSG_DEBUG, "DAS: Received %d bytes from %s:%d",
++		   len, abuf, from_port);
++
++	if (wpa_debug_level <= MSG_MSGDUMP)
++		radius_msg_dump(msg);
++
++	radius_msg_get_attr_ptr(msg, RADIUS_ATTR_NAS_IDENTIFIER,
++				&nasid_buf, &nasid_len, NULL);
++	dl_list_for_each(das, &p->das_data, struct radius_das_data, list) {
++		if (das->client_addr.u.v4.s_addr &&
++		    das->client_addr.u.v4.s_addr != from.sin.sin_addr.s_addr)
++			continue;
++
++		if (das->nas_identifier && nasid_buf &&
++		    (nasid_len != os_strlen(das->nas_identifier) ||
++		     os_memcmp(das->nas_identifier, nasid_buf, nasid_len) != 0))
++			continue;
++
++		found = 1;
++		radius_das_receive_msg(das, msg, (struct sockaddr *)&from.ss,
++				       fromlen, abuf, from_port);
++	}
++
++	if (!found)
++		wpa_printf(MSG_DEBUG, "DAS: Drop message from unknown client");
++}
++
+ 
+ static int radius_das_open_socket(int port)
+ {
+@@ -533,6 +572,49 @@ static int radius_das_open_socket(int po
+ }
+ 
+ 
++static struct radius_das_port *
++radius_das_open_port(int port)
++{
++	struct radius_das_port *p;
++
++	dl_list_for_each(p, &das_ports, struct radius_das_port, list) {
++		if (p->port == port)
++			return p;
++	}
++
++	p = os_zalloc(sizeof(*p));
++	if (p == NULL)
++		return NULL;
++
++	dl_list_init(&p->das_data);
++	p->port = port;
++	p->sock = radius_das_open_socket(port);
++	if (p->sock < 0)
++		goto free_port;
++
++	if (eloop_register_read_sock(p->sock, radius_das_receive, p, NULL))
++		goto close_port;
++
++	dl_list_add(&das_ports, &p->list);
++
++	return p;
++
++close_port:
++	close(p->sock);
++free_port:
++	os_free(p);
++
++	return NULL;
++}
++
++static void radius_das_close_port(struct radius_das_port *p)
++{
++	dl_list_del(&p->list);
++	eloop_unregister_read_sock(p->sock);
++	close(p->sock);
++	free(p);
++}
++
+ struct radius_das_data *
+ radius_das_init(struct radius_das_conf *conf)
+ {
+@@ -553,6 +635,8 @@ radius_das_init(struct radius_das_conf *
+ 	das->ctx = conf->ctx;
+ 	das->disconnect = conf->disconnect;
+ 	das->coa = conf->coa;
++	if (conf->nas_identifier)
++		das->nas_identifier = os_strdup(conf->nas_identifier);
+ 
+ 	os_memcpy(&das->client_addr, conf->client_addr,
+ 		  sizeof(das->client_addr));
+@@ -565,19 +649,15 @@ radius_das_init(struct radius_das_conf *
+ 	}
+ 	das->shared_secret_len = conf->shared_secret_len;
+ 
+-	das->sock = radius_das_open_socket(conf->port);
+-	if (das->sock < 0) {
++	das->port = radius_das_open_port(conf->port);
++	if (!das->port) {
+ 		wpa_printf(MSG_ERROR, "Failed to open UDP socket for RADIUS "
+ 			   "DAS");
+ 		radius_das_deinit(das);
+ 		return NULL;
+ 	}
+ 
+-	if (eloop_register_read_sock(das->sock, radius_das_receive, das, NULL))
+-	{
+-		radius_das_deinit(das);
+-		return NULL;
+-	}
++	dl_list_add(&das->port->das_data, &das->list);
+ 
+ 	return das;
+ }
+@@ -588,11 +668,14 @@ void radius_das_deinit(struct radius_das
+ 	if (das == NULL)
+ 		return;
+ 
+-	if (das->sock >= 0) {
+-		eloop_unregister_read_sock(das->sock);
+-		close(das->sock);
++	if (das->port) {
++		dl_list_del(&das->list);
++
++		if (dl_list_empty(&das->port->das_data))
++			radius_das_close_port(das->port);
+ 	}
+ 
++	os_free(das->nas_identifier);
+ 	os_free(das->shared_secret);
+ 	os_free(das);
+ }
diff --git a/recipes-wifi/wpa-supplicant/files/patches-2.10.3/patches.inc b/recipes-wifi/wpa-supplicant/files/patches-2.10.3/patches.inc
index 90f3cb0..500c124 100644
--- a/recipes-wifi/wpa-supplicant/files/patches-2.10.3/patches.inc
+++ b/recipes-wifi/wpa-supplicant/files/patches-2.10.3/patches.inc
@@ -58,6 +58,7 @@
     file://750-qos_map_set_without_interworking.patch \
     file://751-qos_map_ignore_when_unsupported.patch \
     file://760-dynamic_own_ip.patch \
+    file://761-shared_das_port.patch \
     file://800-acs-don-t-select-indoor-channel-on-outdoor-operation.patch \
     file://990-ctrl-make-WNM_AP-functions-dependant-on-CONFIG_AP.patch \
     file://992-openssl-include-rsa.patch \
diff --git a/recipes-wifi/wpa-supplicant/files/patches/761-shared_das_port.patch b/recipes-wifi/wpa-supplicant/files/patches/761-shared_das_port.patch
new file mode 100644
index 0000000..7516b73
--- /dev/null
+++ b/recipes-wifi/wpa-supplicant/files/patches/761-shared_das_port.patch
@@ -0,0 +1,298 @@
+--- a/src/radius/radius_das.h
++++ b/src/radius/radius_das.h
+@@ -44,6 +44,7 @@ struct radius_das_attrs {
+ struct radius_das_conf {
+ 	int port;
+ 	const u8 *shared_secret;
++	const u8 *nas_identifier;
+ 	size_t shared_secret_len;
+ 	const struct hostapd_ip_addr *client_addr;
+ 	unsigned int time_window;
+--- a/src/ap/hostapd.c
++++ b/src/ap/hostapd.c
+@@ -1367,6 +1367,7 @@ static int hostapd_setup_bss(struct host
+ 		struct radius_das_conf das_conf;
+ 		os_memset(&das_conf, 0, sizeof(das_conf));
+ 		das_conf.port = conf->radius_das_port;
++		das_conf.nas_identifier = conf->nas_identifier;
+ 		das_conf.shared_secret = conf->radius_das_shared_secret;
+ 		das_conf.shared_secret_len =
+ 			conf->radius_das_shared_secret_len;
+--- a/src/radius/radius_das.c
++++ b/src/radius/radius_das.c
+@@ -12,13 +12,26 @@
+ #include "utils/common.h"
+ #include "utils/eloop.h"
+ #include "utils/ip_addr.h"
++#include "utils/list.h"
+ #include "radius.h"
+ #include "radius_das.h"
+ 
+ 
+-struct radius_das_data {
++static struct dl_list das_ports = DL_LIST_HEAD_INIT(das_ports);
++
++struct radius_das_port {
++	struct dl_list list;
++	struct dl_list das_data;
++
++	int port;
+ 	int sock;
++};
++
++struct radius_das_data {
++	struct dl_list list;
++	struct radius_das_port *port;
+ 	u8 *shared_secret;
++	u8 *nas_identifier;
+ 	size_t shared_secret_len;
+ 	struct hostapd_ip_addr client_addr;
+ 	unsigned int time_window;
+@@ -378,56 +391,17 @@ fail:
+ }
+ 
+ 
+-static void radius_das_receive(int sock, void *eloop_ctx, void *sock_ctx)
++static void
++radius_das_receive_msg(struct radius_das_data *das, struct radius_msg *msg,
++		       struct sockaddr *from, socklen_t fromlen,
++		       char *abuf, int from_port)
+ {
+-	struct radius_das_data *das = eloop_ctx;
+-	u8 buf[1500];
+-	union {
+-		struct sockaddr_storage ss;
+-		struct sockaddr_in sin;
+-#ifdef CONFIG_IPV6
+-		struct sockaddr_in6 sin6;
+-#endif /* CONFIG_IPV6 */
+-	} from;
+-	char abuf[50];
+-	int from_port = 0;
+-	socklen_t fromlen;
+-	int len;
+-	struct radius_msg *msg, *reply = NULL;
++	struct radius_msg *reply = NULL;
+ 	struct radius_hdr *hdr;
+ 	struct wpabuf *rbuf;
++	struct os_time now;
+ 	u32 val;
+ 	int res;
+-	struct os_time now;
+-
+-	fromlen = sizeof(from);
+-	len = recvfrom(sock, buf, sizeof(buf), 0,
+-		       (struct sockaddr *) &from.ss, &fromlen);
+-	if (len < 0) {
+-		wpa_printf(MSG_ERROR, "DAS: recvfrom: %s", strerror(errno));
+-		return;
+-	}
+-
+-	os_strlcpy(abuf, inet_ntoa(from.sin.sin_addr), sizeof(abuf));
+-	from_port = ntohs(from.sin.sin_port);
+-
+-	wpa_printf(MSG_DEBUG, "DAS: Received %d bytes from %s:%d",
+-		   len, abuf, from_port);
+-	if (das->client_addr.u.v4.s_addr &&
+-	    das->client_addr.u.v4.s_addr != from.sin.sin_addr.s_addr) {
+-		wpa_printf(MSG_DEBUG, "DAS: Drop message from unknown client");
+-		return;
+-	}
+-
+-	msg = radius_msg_parse(buf, len);
+-	if (msg == NULL) {
+-		wpa_printf(MSG_DEBUG, "DAS: Parsing incoming RADIUS packet "
+-			   "from %s:%d failed", abuf, from_port);
+-		return;
+-	}
+-
+-	if (wpa_debug_level <= MSG_MSGDUMP)
+-		radius_msg_dump(msg);
+ 
+ 	if (radius_msg_verify_das_req(msg, das->shared_secret,
+ 				       das->shared_secret_len,
+@@ -494,9 +468,8 @@ static void radius_das_receive(int sock,
+ 			radius_msg_dump(reply);
+ 
+ 		rbuf = radius_msg_get_buf(reply);
+-		res = sendto(das->sock, wpabuf_head(rbuf),
+-			     wpabuf_len(rbuf), 0,
+-			     (struct sockaddr *) &from.ss, fromlen);
++		res = sendto(das->port->sock, wpabuf_head(rbuf),
++			     wpabuf_len(rbuf), 0, from, fromlen);
+ 		if (res < 0) {
+ 			wpa_printf(MSG_ERROR, "DAS: sendto(to %s:%d): %s",
+ 				   abuf, from_port, strerror(errno));
+@@ -508,6 +481,72 @@ fail:
+ 	radius_msg_free(reply);
+ }
+ 
++static void radius_das_receive(int sock, void *eloop_ctx, void *sock_ctx)
++{
++	struct radius_das_port *p = eloop_ctx;
++	struct radius_das_data *das;
++	u8 buf[1500];
++	union {
++		struct sockaddr_storage ss;
++		struct sockaddr_in sin;
++#ifdef CONFIG_IPV6
++		struct sockaddr_in6 sin6;
++#endif /* CONFIG_IPV6 */
++	} from;
++	struct radius_msg *msg;
++	size_t nasid_len = 0;
++	u8 *nasid_buf = NULL;
++	char abuf[50];
++	int from_port = 0;
++	socklen_t fromlen;
++	int found = 0;
++	int len;
++
++	fromlen = sizeof(from);
++	len = recvfrom(sock, buf, sizeof(buf), 0,
++		       (struct sockaddr *) &from.ss, &fromlen);
++	if (len < 0) {
++		wpa_printf(MSG_ERROR, "DAS: recvfrom: %s", strerror(errno));
++		return;
++	}
++
++	os_strlcpy(abuf, inet_ntoa(from.sin.sin_addr), sizeof(abuf));
++	from_port = ntohs(from.sin.sin_port);
++
++	msg = radius_msg_parse(buf, len);
++	if (msg == NULL) {
++		wpa_printf(MSG_DEBUG, "DAS: Parsing incoming RADIUS packet "
++			   "from %s:%d failed", abuf, from_port);
++		return;
++	}
++
++	wpa_printf(MSG_DEBUG, "DAS: Received %d bytes from %s:%d",
++		   len, abuf, from_port);
++
++	if (wpa_debug_level <= MSG_MSGDUMP)
++		radius_msg_dump(msg);
++
++	radius_msg_get_attr_ptr(msg, RADIUS_ATTR_NAS_IDENTIFIER,
++				&nasid_buf, &nasid_len, NULL);
++	dl_list_for_each(das, &p->das_data, struct radius_das_data, list) {
++		if (das->client_addr.u.v4.s_addr &&
++		    das->client_addr.u.v4.s_addr != from.sin.sin_addr.s_addr)
++			continue;
++
++		if (das->nas_identifier && nasid_buf &&
++		    (nasid_len != os_strlen(das->nas_identifier) ||
++		     os_memcmp(das->nas_identifier, nasid_buf, nasid_len) != 0))
++			continue;
++
++		found = 1;
++		radius_das_receive_msg(das, msg, (struct sockaddr *)&from.ss,
++				       fromlen, abuf, from_port);
++	}
++
++	if (!found)
++		wpa_printf(MSG_DEBUG, "DAS: Drop message from unknown client");
++}
++
+ 
+ static int radius_das_open_socket(int port)
+ {
+@@ -533,6 +572,49 @@ static int radius_das_open_socket(int po
+ }
+ 
+ 
++static struct radius_das_port *
++radius_das_open_port(int port)
++{
++	struct radius_das_port *p;
++
++	dl_list_for_each(p, &das_ports, struct radius_das_port, list) {
++		if (p->port == port)
++			return p;
++	}
++
++	p = os_zalloc(sizeof(*p));
++	if (p == NULL)
++		return NULL;
++
++	dl_list_init(&p->das_data);
++	p->port = port;
++	p->sock = radius_das_open_socket(port);
++	if (p->sock < 0)
++		goto free_port;
++
++	if (eloop_register_read_sock(p->sock, radius_das_receive, p, NULL))
++		goto close_port;
++
++	dl_list_add(&das_ports, &p->list);
++
++	return p;
++
++close_port:
++	close(p->sock);
++free_port:
++	os_free(p);
++
++	return NULL;
++}
++
++static void radius_das_close_port(struct radius_das_port *p)
++{
++	dl_list_del(&p->list);
++	eloop_unregister_read_sock(p->sock);
++	close(p->sock);
++	free(p);
++}
++
+ struct radius_das_data *
+ radius_das_init(struct radius_das_conf *conf)
+ {
+@@ -553,6 +635,8 @@ radius_das_init(struct radius_das_conf *
+ 	das->ctx = conf->ctx;
+ 	das->disconnect = conf->disconnect;
+ 	das->coa = conf->coa;
++	if (conf->nas_identifier)
++		das->nas_identifier = os_strdup(conf->nas_identifier);
+ 
+ 	os_memcpy(&das->client_addr, conf->client_addr,
+ 		  sizeof(das->client_addr));
+@@ -565,19 +649,15 @@ radius_das_init(struct radius_das_conf *
+ 	}
+ 	das->shared_secret_len = conf->shared_secret_len;
+ 
+-	das->sock = radius_das_open_socket(conf->port);
+-	if (das->sock < 0) {
++	das->port = radius_das_open_port(conf->port);
++	if (!das->port) {
+ 		wpa_printf(MSG_ERROR, "Failed to open UDP socket for RADIUS "
+ 			   "DAS");
+ 		radius_das_deinit(das);
+ 		return NULL;
+ 	}
+ 
+-	if (eloop_register_read_sock(das->sock, radius_das_receive, das, NULL))
+-	{
+-		radius_das_deinit(das);
+-		return NULL;
+-	}
++	dl_list_add(&das->port->das_data, &das->list);
+ 
+ 	return das;
+ }
+@@ -588,11 +668,14 @@ void radius_das_deinit(struct radius_das
+ 	if (das == NULL)
+ 		return;
+ 
+-	if (das->sock >= 0) {
+-		eloop_unregister_read_sock(das->sock);
+-		close(das->sock);
++	if (das->port) {
++		dl_list_del(&das->list);
++
++		if (dl_list_empty(&das->port->das_data))
++			radius_das_close_port(das->port);
+ 	}
+ 
++	os_free(das->nas_identifier);
+ 	os_free(das->shared_secret);
+ 	os_free(das);
+ }
diff --git a/recipes-wifi/wpa-supplicant/files/patches/patches.inc b/recipes-wifi/wpa-supplicant/files/patches/patches.inc
index 5aa02e0..aeb1f6b 100644
--- a/recipes-wifi/wpa-supplicant/files/patches/patches.inc
+++ b/recipes-wifi/wpa-supplicant/files/patches/patches.inc
@@ -58,6 +58,7 @@
     file://750-qos_map_set_without_interworking.patch \
     file://751-qos_map_ignore_when_unsupported.patch \
     file://760-dynamic_own_ip.patch \
+    file://761-shared_das_port.patch \
     file://800-acs-don-t-select-indoor-channel-on-outdoor-operation.patch \
     file://990-ctrl-make-WNM_AP-functions-dependant-on-CONFIG_AP.patch \
     file://992-openssl-include-rsa.patch \