[][kernel][common][eth][Add a Ethernet passive Mux support]

[Description]
Add a Ethernet passive Mux support.

If your board that XGMAC1 connects a 10G PHY and a SFP cage through a
passive Mux, please apply the patch as follows.
==============================================================
 			phy-handle = <&phy8>;
 		};
 	};
+
+	mux: mux-bus {
+		mux1: ethernet-mux@1 {
+			compatible = "mediatek,eth-mux";
+			reg = <1>;
+
+			chan-sel-gpios = <&pio 30 0>;
+			mod-def0-gpios = <&pio 82 1>;
+
+			channel0: channel@0 {
+				reg = <0>;
+				mac-type = "xgdm";
+				phy-mode = "usxgmii";
+				phy-handle = <&phy0>;
+			};
+
+			channel1: channel@1 {
+				reg = <1>;
+				mac-type = "xgdm";
+				phy-mode = "10gbase-kr";
+				managed = "in-band-status";
+				sfp = <&sfp_esp1>;
+			};
+		};
+	};

 	mdio: mdio-bus {
 		#address-cells = <1>;
 		#size-cells = <0>;
==============================================================

After applying this patch, the ETH driver has the ability to dynamically
switch the network interface via a passive Mux.

Without this patch, the ETH driver is unable to dynamically switch the
network interface between the 10G PHY and the SFP cage.

[Release-log]
N/A


Change-Id: I9d55370ca6952dd08d28e4151c1139737c4918a2
Reviewed-on: https://gerrit.mediatek.inc/c/openwrt/feeds/mtk_openwrt_feeds/+/7979960
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 409b834..b0cfc66 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
@@ -4575,6 +4575,151 @@
 #endif
 };
 
+static void mux_poll(struct work_struct *work)
+{
+	struct mtk_mux *mux = container_of(work, struct mtk_mux, poll.work);
+	struct mtk_mac *mac = mux->mac;
+	struct mtk_eth *eth = mac->hw;
+	struct net_device *dev = eth->netdev[mac->id];
+	unsigned int channel;
+
+	if (IS_ERR(mux->gpio[0]) || IS_ERR(mux->gpio[1]))
+		goto exit;
+
+	channel = gpiod_get_value_cansleep(mux->gpio[0]);
+	if (mux->channel == channel || !netif_running(dev))
+		goto exit;
+
+	rtnl_lock();
+
+	mtk_stop(dev);
+
+	if (channel == 0 || channel == 1) {
+		mac->of_node = mux->data[channel]->of_node;
+		mac->phylink = mux->data[channel]->phylink;
+	};
+
+	dev_info(eth->dev, "ethernet mux: switch to channel%d\n", channel);
+
+	gpiod_set_value_cansleep(mux->gpio[1], channel);
+
+	mtk_open(dev);
+
+	rtnl_unlock();
+
+	mux->channel = channel;
+
+exit:
+	mod_delayed_work(system_wq, &mux->poll, msecs_to_jiffies(100));
+}
+
+static int mtk_add_mux_channel(struct mtk_mux *mux, struct device_node *np)
+{
+	const __be32 *_id = of_get_property(np, "reg", NULL);
+	struct mtk_mac *mac = mux->mac;
+	struct mtk_eth *eth = mac->hw;
+	struct mtk_mux_data *data;
+	struct phylink *phylink;
+	int phy_mode, id;
+
+	if (!_id) {
+		dev_err(eth->dev, "missing mux channel id\n");
+		return -EINVAL;
+	}
+
+	id = be32_to_cpup(_id);
+	if (id < 0 || id > 1) {
+		dev_err(eth->dev, "%d is not a valid mux channel id\n", id);
+		return -EINVAL;
+	}
+
+	data = kmalloc(sizeof(*data), GFP_KERNEL);
+	if (unlikely(!data)) {
+		dev_err(eth->dev, "failed to create mux data structure\n");
+		return -ENOMEM;
+	}
+
+	mux->data[id] = data;
+
+	/* phylink create */
+	phy_mode = of_get_phy_mode(np);
+	if (phy_mode < 0) {
+		dev_err(eth->dev, "incorrect phy-mode\n");
+		return -EINVAL;
+	}
+
+	phylink = phylink_create(&mux->mac->phylink_config,
+				 of_fwnode_handle(np),
+				 phy_mode, &mtk_phylink_ops);
+	if (IS_ERR(phylink)) {
+		dev_err(eth->dev, "failed to create phylink structure\n");
+		return PTR_ERR(phylink);
+	}
+
+	data->of_node = np;
+	data->phylink = phylink;
+
+	return 0;
+}
+
+static int mtk_add_mux(struct mtk_eth *eth, struct device_node *np)
+{
+	const __be32 *_id = of_get_property(np, "reg", NULL);
+	struct device_node *child;
+	struct mtk_mux *mux;
+	unsigned int id;
+	int err;
+
+	if (!_id) {
+		dev_err(eth->dev, "missing attach mac id\n");
+		return -EINVAL;
+	}
+
+	id = be32_to_cpup(_id);
+	if (id < 0 || id >= MTK_MAX_DEVS) {
+		dev_err(eth->dev, "%d is not a valid attach mac id\n", id);
+		return -EINVAL;
+	}
+
+	mux = kmalloc(sizeof(struct mtk_mux), GFP_KERNEL);
+	if (unlikely(!mux)) {
+		dev_err(eth->dev, "failed to create mux structure\n");
+		return -ENOMEM;
+	}
+
+	eth->mux[id] = mux;
+
+	mux->mac = eth->mac[id];
+	mux->channel = 0;
+
+	mux->gpio[0] = fwnode_get_named_gpiod(of_fwnode_handle(np),
+					      "mod-def0-gpios", 0,
+					      GPIOD_IN, "?");
+	if (IS_ERR(mux->gpio[0]))
+		dev_err(eth->dev, "failed to requset gpio for mod-def0-gpios\n");
+
+	mux->gpio[1] = fwnode_get_named_gpiod(of_fwnode_handle(np),
+					      "chan-sel-gpios", 0,
+					      GPIOD_OUT_LOW, "?");
+	if (IS_ERR(mux->gpio[1]))
+		dev_err(eth->dev, "failed to requset gpio for chan-sel-gpios\n");
+
+	for_each_child_of_node(np, child) {
+		err = mtk_add_mux_channel(mux, child);
+		if (err) {
+			dev_err(eth->dev, "failed to add mtk_mux\n");
+			of_node_put(child);
+			return -ECHILD;
+		}
+		of_node_put(child);
+	}
+
+	INIT_DELAYED_WORK(&mux->poll, mux_poll);
+	mod_delayed_work(system_wq, &mux->poll, msecs_to_jiffies(3000));
+
+	return 0;
+}
+
 static int mtk_add_mac(struct mtk_eth *eth, struct device_node *np)
 {
 	const __be32 *_id = of_get_property(np, "reg", NULL);
@@ -4772,7 +4917,7 @@
 
 static int mtk_probe(struct platform_device *pdev)
 {
-	struct device_node *mac_np;
+	struct device_node *mac_np, *mux_np;
 	struct mtk_eth *eth;
 	int err, i;
 
@@ -4943,6 +5088,26 @@
 		}
 	}
 
+	mux_np = of_get_child_by_name(eth->dev->of_node, "mux-bus");
+	if (mux_np) {
+		struct device_node *child;
+
+		for_each_available_child_of_node(mux_np, child) {
+			if (!of_device_is_compatible(child,
+						     "mediatek,eth-mux"))
+				continue;
+
+			if (!of_device_is_available(child))
+				continue;
+
+			err = mtk_add_mux(eth, child);
+			if (err)
+				dev_err(&pdev->dev, "failed to add mux\n");
+
+			of_node_put(mux_np);
+		};
+	}
+
 	err = mtk_napi_init(eth);
 	if (err)
 		goto err_free_dev;
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
index 668b77c..88df3b2 100644
--- 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
@@ -1040,6 +1040,7 @@
 
 struct mtk_eth;
 struct mtk_mac;
+struct mtk_mux;
 
 /* struct mtk_hw_stats - the structure that holds the traffic statistics.
  * @stats_lock:		make sure that stats operations are atomic
@@ -1808,6 +1809,7 @@
 	struct net_device		dummy_dev;
 	struct net_device		*netdev[MTK_MAX_DEVS];
 	struct mtk_mac			*mac[MTK_MAX_DEVS];
+	struct mtk_mux			*mux[MTK_MAX_DEVS];
 	int				irq_fe[MTK_FE_IRQ_NUM];
 	int				irq_pdma[MTK_PDMA_IRQ_NUM];
 	u8				hwver;
@@ -1872,6 +1874,25 @@
 	u32				tx_lpi_timer;
 };
 
+/* struct mtk_mux_data -	the structure that holds the private data about the
+ *			 Passive MUXs of the SoC
+ */
+struct mtk_mux_data {
+	struct device_node		*of_node;
+	struct phylink			*phylink;
+};
+
+/* struct mtk_mux -	the structure that holds the info about the Passive MUXs of the
+ *			SoC
+ */
+struct mtk_mux {
+	struct delayed_work		poll;
+	struct gpio_desc		*gpio[2];
+	struct mtk_mux_data		*data[2];
+	struct mtk_mac			*mac;
+	unsigned int			channel;
+};
+
 /* the struct describing the SoC. these are declared in the soc_xyz.c files */
 extern struct mtk_eth *g_eth;
 extern const struct of_device_id of_mtk_match[];
diff --git a/target/linux/mediatek/patches-5.4/999-2730-net-phy-sfp-change-shared-mod-def0.patch b/target/linux/mediatek/patches-5.4/999-2730-net-phy-sfp-change-shared-mod-def0.patch
new file mode 100644
index 0000000..479f58b
--- /dev/null
+++ b/target/linux/mediatek/patches-5.4/999-2730-net-phy-sfp-change-shared-mod-def0.patch
@@ -0,0 +1,43 @@
+From c576ce67488bc8b0285933f315cb183c9171f199 Mon Sep 17 00:00:00 2001
+From: Bo-Cun Chen <bc-bocun.chen@mediatek.com>
+Date: Thu, 7 Sep 2023 12:01:57 +0800
+Subject: [PATCH] 999-2730-net-phy-sfp-change-shared-mod-def0
+
+---
+ drivers/net/phy/sfp.c | 6 +++---
+ 1 file changed, 3 insertions(+), 3 deletions(-)
+
+diff --git a/drivers/net/phy/sfp.c b/drivers/net/phy/sfp.c
+index 0c335b1..d49a825 100644
+--- a/drivers/net/phy/sfp.c
++++ b/drivers/net/phy/sfp.c
+@@ -152,7 +152,7 @@ static const char *gpio_of_names[] = {
+ };
+ 
+ static const enum gpiod_flags gpio_flags[] = {
+-	GPIOD_IN,
++	GPIOD_IN | GPIOD_FLAGS_BIT_NONEXCLUSIVE,
+ 	GPIOD_IN,
+ 	GPIOD_IN,
+ 	GPIOD_ASIS,
+@@ -512,7 +512,7 @@ static unsigned int sfp_gpio_get_state(struct sfp *sfp)
+ 	unsigned int i, state, v;
+ 
+ 	for (i = state = 0; i < GPIO_MAX; i++) {
+-		if (gpio_flags[i] != GPIOD_IN || !sfp->gpio[i])
++		if (((gpio_flags[i] & GPIOD_IN) != GPIOD_IN) || !sfp->gpio[i])
+ 			continue;
+ 
+ 		v = gpiod_get_value_cansleep(sfp->gpio[i]);
+@@ -2757,7 +2757,7 @@ static int sfp_probe(struct platform_device *pdev)
+ 	}
+ 
+ 	for (i = 0; i < GPIO_MAX; i++) {
+-		if (gpio_flags[i] != GPIOD_IN || !sfp->gpio[i])
++		if (((gpio_flags[i] & GPIOD_IN) != GPIOD_IN) || !sfp->gpio[i])
+ 			continue;
+ 
+ 		sfp->gpio_irq[i] = gpiod_to_irq(sfp->gpio[i]);
+-- 
+2.18.0
+