[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