[][kernel][mt7988][eth][Add a link down recovery handler for the USXGMII]

[Description]
Add a link down recovery handler for the USXGMII.

We have recently observed a situation where the USXGMII's link is down.
In order to prevent any abnormal network behavior caused by this link
failure, we have added an error handler to recover the connection of
the USXGMII.

Without this patch, the driver cannot prevent any abnormal network
behavior caused by link failure of the USXGMII.

[Release-log]
N/A


Change-Id: Id2533e422fc1e3c14e8b9c3f319dca9d6a038f6e
Reviewed-on: https://gerrit.mediatek.inc/c/openwrt/feeds/mtk_openwrt_feeds/+/7961990
diff --git a/target/linux/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_eth_soc.c b/target/linux/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_eth_soc.c
index e804ef8..409b834 100644
--- a/target/linux/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_eth_soc.c
+++ b/target/linux/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_eth_soc.c
@@ -964,6 +964,8 @@
 {
 	struct mtk_mac *mac = container_of(config, struct mtk_mac,
 					   phylink_config);
+	struct mtk_eth *eth = mac->hw;
+	unsigned int id;
 	u32 mcr, sts;
 
 	mtk_pse_port_link_set(mac, false);
@@ -972,8 +974,9 @@
 		mcr &= ~(MAC_MCR_TX_EN | MAC_MCR_RX_EN | MAC_MCR_FORCE_LINK);
 		mtk_w32(mac->hw, mcr, MTK_MAC_MCR(mac->id));
 	} else if (mac->type == MTK_XGDM_TYPE && mac->id != MTK_GMAC1_ID) {
-		mcr = mtk_r32(mac->hw, MTK_XMAC_MCR(mac->id));
+		struct mtk_usxgmii_pcs *mpcs;
 
+		mcr = mtk_r32(mac->hw, MTK_XMAC_MCR(mac->id));
 		mcr &= 0xfffffff0;
 		mcr |= XMAC_MCR_TRX_DISABLE;
 		mtk_w32(mac->hw, mcr, MTK_XMAC_MCR(mac->id));
@@ -981,6 +984,10 @@
 		sts = mtk_r32(mac->hw, MTK_XGMAC_STS(mac->id));
 		sts &= ~MTK_XGMAC_FORCE_LINK(mac->id);
 		mtk_w32(mac->hw, sts, MTK_XGMAC_STS(mac->id));
+
+		id = mtk_mac2xgmii_id(eth, mac->id);
+		mpcs = &eth->usxgmii->pcs[id];
+		cancel_delayed_work_sync(&mpcs->link_poll);
 	}
 }
 
diff --git a/target/linux/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_eth_soc.h b/target/linux/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_eth_soc.h
old mode 100755
new mode 100644
index f87635c..668b77c
--- a/target/linux/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_eth_soc.h
+++ b/target/linux/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_eth_soc.h
@@ -945,6 +945,11 @@
 #define USXGMII_LPA_LINK	BIT(15)
 #define USXGMII_LPA_LATCH	BIT(31)
 
+/* Register to read PCS Link status */
+#define RG_PCS_RX_STATUS0	0x904
+#define RG_PCS_RX_STATUS_UPDATE	BIT(16)
+#define RG_PCS_RX_LINK_STATUS	BIT(2)
+
 /* Register to control USXGMII XFI PLL digital */
 #define XFI_PLL_DIG_GLB8	0x08
 #define RG_XFI_PLL_EN		BIT(31)
@@ -1715,7 +1720,9 @@
 	struct mtk_eth		*eth;
 	struct regmap		*regmap;
 	struct regmap		*regmap_pextp;
+	struct delayed_work	link_poll;
 	phy_interface_t		interface;
+	unsigned int		mode;
 	u8			id;
 	struct phylink_pcs	pcs;
 };
@@ -1895,6 +1902,7 @@
 int mtk_usxgmii_init(struct mtk_eth *eth, struct device_node *r);
 int mtk_toprgu_init(struct mtk_eth *eth, struct device_node *r);
 int mtk_dump_usxgmii(struct regmap *pmap, char *name, u32 offset, u32 range);
+void mtk_usxgmii_link_poll(struct work_struct *work);
 
 void mtk_eth_set_dma_device(struct mtk_eth *eth, struct device *dma_dev);
 u32 mtk_rss_indr_table(struct mtk_rss_params *rss_params, int index);
diff --git a/target/linux/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_usxgmii.c b/target/linux/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_usxgmii.c
index a181fc2..0cadc85 100644
--- a/target/linux/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_usxgmii.c
+++ b/target/linux/mediatek/files-5.4/drivers/net/ethernet/mediatek/mtk_usxgmii.c
@@ -468,6 +468,25 @@
 	udelay(400);
 }
 
+int mtk_usxgmii_link_status(struct mtk_usxgmii_pcs *mpcs)
+{
+	unsigned int val;
+
+	/* Refresh USXGMII link status by toggling RG_PCS_RX_STATUS_UPDATE */
+	regmap_read(mpcs->regmap, RG_PCS_RX_STATUS0, &val);
+	val |= RG_PCS_RX_STATUS_UPDATE;
+	regmap_write(mpcs->regmap, RG_PCS_RX_STATUS0, val);
+
+	regmap_read(mpcs->regmap, RG_PCS_RX_STATUS0, &val);
+	val &= ~RG_PCS_RX_STATUS_UPDATE;
+	regmap_write(mpcs->regmap, RG_PCS_RX_STATUS0, val);
+
+	/* Read USXGMII link status */
+	regmap_read(mpcs->regmap, RG_PCS_RX_STATUS0, &val);
+
+	return FIELD_GET(RG_PCS_RX_LINK_STATUS, val);
+}
+
 void mtk_usxgmii_reset(struct mtk_eth *eth, int id)
 {
 	u32 val = 0;
@@ -580,6 +599,7 @@
 
 	if (mpcs->interface != interface) {
 		mpcs->interface = interface;
+		mpcs->mode = mode;
 		mode_changed = true;
 	}
 
@@ -725,15 +745,34 @@
 	regmap_write(mpcs->regmap, RG_PCS_AN_CTRL0, val);
 }
 
+void mtk_usxgmii_link_poll(struct work_struct *work)
+{
+	struct delayed_work *dwork = to_delayed_work(work);
+	struct mtk_usxgmii_pcs *mpcs = container_of(dwork, struct mtk_usxgmii_pcs, link_poll);
+
+	if (!mtk_usxgmii_link_status(mpcs)) {
+		mtk_usxgmii_pcs_config(&mpcs->pcs, mpcs->mode,
+				       mpcs->interface, NULL, false);
+
+		queue_delayed_work(system_power_efficient_wq, &mpcs->link_poll,
+				   msecs_to_jiffies(1000));
+	}
+}
+
 static void mtk_usxgmii_pcs_link_up(struct phylink_pcs *pcs, unsigned int mode,
 				    phy_interface_t interface,
 				    int speed, int duplex)
 {
+	struct mtk_usxgmii_pcs *mpcs = pcs_to_mtk_usxgmii_pcs(pcs);
+
 	/* Reconfiguring USXGMII to ensure the quality of the RX signal
 	 * after the line side link up.
 	 */
 	mtk_usxgmii_pcs_config(pcs, mode,
 			       interface, NULL, false);
+
+	queue_delayed_work(system_power_efficient_wq, &mpcs->link_poll,
+			   msecs_to_jiffies(1000));
 }
 
 static const struct phylink_pcs_ops mtk_usxgmii_pcs_ops = {
@@ -765,6 +804,8 @@
 		ss->pcs[i].pcs.poll = true;
 		ss->pcs[i].interface = PHY_INTERFACE_MODE_NA;
 
+		INIT_DELAYED_WORK(&ss->pcs[i].link_poll, mtk_usxgmii_link_poll);
+
 		of_node_put(np);
 	}