[PATCH 12/20] net: dsa: xilinx: register per-MAC MDIO buses
Nagadheeraj Rottela
nagadheeraj.rottela at amd.com
Fri Aug 7 03:44:23 PDT 2026
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 <srinivas.neeli at amd.com>
Signed-off-by: Srinivas Neeli <srinivas.neeli at amd.com>
Signed-off-by: Nagadheeraj Rottela <nagadheeraj.rottela at amd.com>
---
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
More information about the linux-arm-kernel
mailing list