[PATCH 12/20] net: dsa: xilinx: register per-MAC MDIO buses

Nagadheeraj Rottela <[email protected]>
Newsgroups org.kernel.vger.linux-devicetree,org.infradead.lists.linux-arm-kernel,org.kernel.vger.linux-kernel,org.kernel.vger.netdev
Message-ID <[email protected]>
The TSN IP exposes one MDIO controller per MAC at +0x500 inside
each MAC's register window. Register each bus under the matching
mdio-mac1 / mdio-mac2 child of the switch node.

A new struct xlnx_tsn_mac groups the per-MAC register window and
the back-pointer to the parent switch. MDIO callbacks and log
helpers pull what they need from it. The clock divisor comes from
the IP-wide s_axi clock, owned by the parent IP wrapper node, so
read its rate from there rather than holding a private reference.

Co-developed-by: Srinivas Neeli <[email protected]>
Signed-off-by: Srinivas Neeli <[email protected]>
Signed-off-by: Nagadheeraj Rottela <[email protected]>
---
 drivers/net/dsa/xilinx/xilinx_tsn.c | 203 +++++++++++++++++++++++++++-
 drivers/net/dsa/xilinx/xilinx_tsn.h |  54 +++++++-
 2 files changed, 251 insertions(+), 6 deletions(-)

diff --git a/drivers/net/dsa/xilinx/xilinx_tsn.c b/drivers/net/dsa/xilinx/xilinx_tsn.c
index 020b1622670c..542b74514ed3 100644
--- a/drivers/net/dsa/xilinx/xilinx_tsn.c
+++ b/drivers/net/dsa/xilinx/xilinx_tsn.c
@@ -4,17 +4,24 @@
  */
 
 #include <linux/bitfield.h>
+#include <linux/clk.h>
 #include <linux/if_bridge.h>
 #include <linux/io.h>
 #include <linux/iopoll.h>
 #include <linux/kernel.h>
+#include <linux/mdio.h>
 #include <linux/module.h>
 #include <linux/of.h>
+#include <linux/of_mdio.h>
+#include <linux/phy.h>
 #include <linux/platform_device.h>
 #include <net/dsa.h>
 
 #include "xilinx_tsn.h"
 
+#define TSN_MDIO_MAX_FREQ_HZ		2500000
+#define TSN_MDIO_READY_TIMEOUT_US	20000
+
 static void sw_iow(struct xlnx_tsn *sw, u32 off, u32 val)
 {
 	iowrite32(val, sw->sw_base + off);
@@ -95,6 +102,190 @@ static int xlnx_tsn_set_port_state(struct xlnx_tsn *sw, int port,
 	return 0;
 }
 
+static int xlnx_tsn_mdio_wait_ready(struct xlnx_tsn_mac *m)
+{
+	u32 val;
+
+	return readl_poll_timeout(m->regs + TSN_MDIO_MCR_OFFSET, val,
+				  val & TSN_MDIO_MCR_READY, 1,
+				  TSN_MDIO_READY_TIMEOUT_US);
+}
+
+static int xlnx_tsn_mdio_read(struct mii_bus *bus, int phy_id, int reg)
+{
+	struct xlnx_tsn_mac *m = bus->priv;
+	int ret;
+
+	ret = xlnx_tsn_mdio_wait_ready(m);
+	if (ret < 0)
+		return ret;
+
+	mac_iow(m, TSN_MDIO_MCR_OFFSET,
+		FIELD_PREP(TSN_MDIO_MCR_PHYAD_MASK, phy_id) |
+		FIELD_PREP(TSN_MDIO_MCR_REGAD_MASK, reg) |
+		TSN_MDIO_MCR_INITIATE | TSN_MDIO_MCR_OP_READ);
+
+	ret = xlnx_tsn_mdio_wait_ready(m);
+	if (ret < 0)
+		return ret;
+
+	return FIELD_GET(TSN_MDIO_MRD_MASK,
+			 mac_ior(m, TSN_MDIO_MRD_OFFSET));
+}
+
+static int xlnx_tsn_mdio_write(struct mii_bus *bus, int phy_id, int reg,
+			       u16 val)
+{
+	struct xlnx_tsn_mac *m = bus->priv;
+	int ret;
+
+	ret = xlnx_tsn_mdio_wait_ready(m);
+	if (ret < 0)
+		return ret;
+
+	mac_iow(m, TSN_MDIO_MWD_OFFSET, val);
+	mac_iow(m, TSN_MDIO_MCR_OFFSET,
+		FIELD_PREP(TSN_MDIO_MCR_PHYAD_MASK, phy_id) |
+		FIELD_PREP(TSN_MDIO_MCR_REGAD_MASK, reg) |
+		TSN_MDIO_MCR_INITIATE | TSN_MDIO_MCR_OP_WRITE);
+
+	return xlnx_tsn_mdio_wait_ready(m);
+}
+
+/* Round up so the MDC frequency stays at or below TSN_MDIO_MAX_FREQ_HZ,
+ * then clamp to the 6-bit field maximum so the value stays within the
+ * field and does not corrupt TSN_MDIO_MC_MDIOEN.
+ */
+static u32 xlnx_tsn_mdio_clk_div(struct xlnx_tsn *sw, unsigned long host_hz)
+{
+	u32 div;
+
+	if (!host_hz) {
+		dev_warn(sw->dev,
+			 "s_axi clock rate unknown; clamping MDIO divisor to max\n");
+		return TSN_MDIO_MC_CLOCK_DIVIDE_MAX;
+	}
+
+	div = DIV_ROUND_UP(host_hz, TSN_MDIO_MAX_FREQ_HZ * 2) - 1;
+
+	/* HW ignores MDIO Enable when Clock Divide is 0 */
+	if (!div)
+		div = 1;
+
+	if (div > TSN_MDIO_MC_CLOCK_DIVIDE_MAX) {
+		dev_warn(sw->dev,
+			 "MDIO divisor %u exceeds max %u, clamping\n",
+			 div, TSN_MDIO_MC_CLOCK_DIVIDE_MAX);
+		div = TSN_MDIO_MC_CLOCK_DIVIDE_MAX;
+	}
+
+	return div;
+}
+
+static int xlnx_tsn_mdio_register_one(struct xlnx_tsn *sw, int port,
+				      const char *child_name,
+				      unsigned long host_hz)
+{
+	struct xlnx_tsn_mac *m = &sw->mac[port];
+	struct device_node *mdio_np;
+	struct mii_bus *bus;
+	int ret;
+
+	mdio_np = of_get_child_by_name(sw->dev->of_node, child_name);
+	if (!mdio_np)
+		return 0;
+
+	bus = devm_mdiobus_alloc(sw->dev);
+	if (!bus) {
+		of_node_put(mdio_np);
+		return -ENOMEM;
+	}
+
+	snprintf(bus->id, MII_BUS_ID_SIZE, "%s:%s",
+		 dev_name(sw->dev), child_name);
+	bus->name = "Xilinx TSN MDIO";
+	bus->priv = m;
+	bus->parent = sw->dev;
+	bus->read = xlnx_tsn_mdio_read;
+	bus->write = xlnx_tsn_mdio_write;
+
+	mac_iow(m, TSN_MDIO_MC_OFFSET,
+		xlnx_tsn_mdio_clk_div(sw, host_hz) | TSN_MDIO_MC_MDIOEN);
+
+	ret = xlnx_tsn_mdio_wait_ready(m);
+	if (ret) {
+		dev_err(sw->dev, "%s: MDIO controller not ready: %d\n",
+			child_name, ret);
+		goto err_put_np;
+	}
+
+	ret = of_mdiobus_register(bus, mdio_np);
+	if (ret) {
+		dev_err(sw->dev, "%s: failed to register MDIO bus: %d\n",
+			child_name, ret);
+		goto err_put_np;
+	}
+
+	m->mii_bus = bus;
+	of_node_put(mdio_np);
+	return 0;
+
+err_put_np:
+	of_node_put(mdio_np);
+	return ret;
+}
+
+static void xlnx_tsn_mdio_unregister_all(struct xlnx_tsn *sw)
+{
+	int port;
+
+	for (port = XLNX_TSN_PORT_MAC1; port <= XLNX_TSN_PORT_MAC2; port++) {
+		struct xlnx_tsn_mac *m = &sw->mac[port];
+
+		if (m->mii_bus) {
+			mdiobus_unregister(m->mii_bus);
+			m->mii_bus = NULL;
+		}
+
+		/* clear the enable bit even when no bus was registered (failed probe) */
+		mac_iow(m, TSN_MDIO_MC_OFFSET, 0);
+	}
+}
+
+static int xlnx_tsn_mdio_register_all(struct xlnx_tsn *sw)
+{
+	unsigned long host_hz;
+	struct clk *s_axi;
+	int ret;
+
+	/* per-MAC MDIO divisor comes from the wrapper node's s_axi
+	 * clock
+	 */
+	s_axi = clk_get(sw->dev->parent, "s_axi");
+	if (IS_ERR(s_axi))
+		return dev_err_probe(sw->dev, PTR_ERR(s_axi),
+				     "failed to get s_axi clock\n");
+
+	host_hz = clk_get_rate(s_axi);
+	clk_put(s_axi);
+
+	ret = xlnx_tsn_mdio_register_one(sw, XLNX_TSN_PORT_MAC1, "mdio-mac1",
+					 host_hz);
+	if (ret)
+		goto err_unregister;
+
+	ret = xlnx_tsn_mdio_register_one(sw, XLNX_TSN_PORT_MAC2, "mdio-mac2",
+					 host_hz);
+	if (ret)
+		goto err_unregister;
+
+	return 0;
+
+err_unregister:
+	xlnx_tsn_mdio_unregister_all(sw);
+	return ret;
+}
+
 static enum dsa_tag_protocol xlnx_tsn_get_tag_protocol(struct dsa_switch *ds,
 						       int port,
 						       enum dsa_tag_protocol mp)
@@ -160,7 +351,7 @@ static int xlnx_tsn_setup(struct dsa_switch *ds)
 			return ret;
 	}
 
-	return 0;
+	return xlnx_tsn_mdio_register_all(sw);
 }
 
 static void xlnx_tsn_teardown(struct dsa_switch *ds)
@@ -168,6 +359,8 @@ static void xlnx_tsn_teardown(struct dsa_switch *ds)
 	struct xlnx_tsn *sw = ds->priv;
 	struct dsa_port *dp;
 
+	xlnx_tsn_mdio_unregister_all(sw);
+
 	dsa_switch_for_each_user_port(dp, ds)
 		xlnx_tsn_set_port_state(sw, dp->index, TSN_PORT_STATE_DISABLED);
 
@@ -207,16 +400,20 @@ static int xlnx_tsn_probe(struct platform_device *pdev)
 		return -ENOMEM;
 
 	sw->dev = dev;
+	sw->mac[XLNX_TSN_PORT_MAC1].sw = sw;
+	sw->mac[XLNX_TSN_PORT_MAC2].sw = sw;
 
 	ret = xlnx_tsn_map_reg(pdev, "switch", &sw->sw_base);
 	if (ret)
 		return ret;
 
-	ret = xlnx_tsn_map_reg(pdev, "mac1", &sw->mac_base[XLNX_TSN_PORT_MAC1]);
+	ret = xlnx_tsn_map_reg(pdev, "mac1",
+			       &sw->mac[XLNX_TSN_PORT_MAC1].regs);
 	if (ret)
 		return ret;
 
-	ret = xlnx_tsn_map_reg(pdev, "mac2", &sw->mac_base[XLNX_TSN_PORT_MAC2]);
+	ret = xlnx_tsn_map_reg(pdev, "mac2",
+			       &sw->mac[XLNX_TSN_PORT_MAC2].regs);
 	if (ret)
 		return ret;
 
diff --git a/drivers/net/dsa/xilinx/xilinx_tsn.h b/drivers/net/dsa/xilinx/xilinx_tsn.h
index 1064cf1ce66e..025645a83992 100644
--- a/drivers/net/dsa/xilinx/xilinx_tsn.h
+++ b/drivers/net/dsa/xilinx/xilinx_tsn.h
@@ -7,6 +7,7 @@
 
 #include <linux/bitfield.h>
 #include <linux/bits.h>
+#include <linux/io.h>
 #include <linux/types.h>
 #include <net/dsa.h>
 
@@ -45,19 +46,66 @@ enum tsn_port_state {
 	TSN_PORT_STATE_FORWARDING,
 };
 
+/* Per-MAC MDIO controller register window, sitting at +0x500 inside
+ * each MAC's reg space owned via reg-names = "mac1", "mac2".
+ */
+#define TSN_MDIO_MC_OFFSET		0x00000500
+#define TSN_MDIO_MCR_OFFSET		0x00000504
+#define TSN_MDIO_MWD_OFFSET		0x00000508
+#define TSN_MDIO_MRD_OFFSET		0x0000050c
+
+#define TSN_MDIO_MC_MDIOEN		BIT(6)
+#define TSN_MDIO_MC_CLOCK_DIVIDE_MAX	0x3f
+
+#define TSN_MDIO_MCR_PHYAD_MASK		GENMASK(28, 24)
+#define TSN_MDIO_MCR_REGAD_MASK		GENMASK(20, 16)
+#define TSN_MDIO_MCR_OP_READ		BIT(15)
+#define TSN_MDIO_MCR_OP_WRITE		BIT(14)
+#define TSN_MDIO_MCR_INITIATE		BIT(11)
+#define TSN_MDIO_MCR_READY		BIT(7)
+
+#define TSN_MDIO_MRD_MASK		GENMASK(15, 0)
+
+struct mii_bus;
+struct xlnx_tsn;
+
+/**
+ * struct xlnx_tsn_mac - per-MAC switch-side state
+ * @sw: back-pointer to the parent switch (for dev_* logging in
+ *	bus callbacks)
+ * @regs: per-MAC register window, from reg-name "macN"
+ * @mii_bus: MDIO bus registered under the "mdio-macN" DT child,
+ *	     or NULL if absent
+ */
+struct xlnx_tsn_mac {
+	struct xlnx_tsn *sw;
+	void __iomem *regs;
+	struct mii_bus *mii_bus;
+};
+
 /**
  * struct xlnx_tsn - per-IP switch state
  * @ds: DSA switch
  * @dev: backing device
  * @sw_base: switch fabric register window
- * @mac_base: per-MAC register windows, indexed by user-port number
- *	      (index 0 unused; MAC1 at [1], MAC2 at [2])
+ * @mac: per-MAC state, indexed by user-port number (index 0 unused;
+ *	 MAC1 at [1], MAC2 at [2])
  */
 struct xlnx_tsn {
 	struct dsa_switch ds;
 	struct device *dev;
 	void __iomem *sw_base;
-	void __iomem *mac_base[XLNX_TSN_NUM_PORTS];
+	struct xlnx_tsn_mac mac[XLNX_TSN_NUM_PORTS];
 };
 
+static inline void mac_iow(struct xlnx_tsn_mac *m, u32 off, u32 val)
+{
+	iowrite32(val, m->regs + off);
+}
+
+static inline u32 mac_ior(struct xlnx_tsn_mac *m, u32 off)
+{
+	return ioread32(m->regs + off);
+}
+
 #endif /* _XILINX_TSN_H */
-- 
2.34.1
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.