[PATCH 11/20] net: dsa: xilinx: implement port_stp_state_set
Nagadheeraj Rottela
nagadheeraj.rottela at amd.com
Fri Aug 7 03:44:22 PDT 2026
Wire up the bridge STP state machine to the Switch Port State
Control register at +0x004C. Each port has a 3-bit state field and
a self-clearing commit bit. The hardware applies the new state only
when the commit bit transitions from 0 to 1. Follow each write with
readl_poll_timeout() until the hardware acknowledges.
All five STP states map to distinct hardware encodings on the
MAC ports.
Put the CPU port in FORWARDING so host traffic always flows, and
the user ports in DISABLED so the bridge STP machine drives all
further transitions. On teardown, return the user ports to DISABLED.
The bridge STP machine does not manage the CPU port. Its forwarding
state does not change after setup().
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 | 148 ++++++++++++++++++++++++++++
drivers/net/dsa/xilinx/xilinx_tsn.h | 32 ++++++
2 files changed, 180 insertions(+)
diff --git a/drivers/net/dsa/xilinx/xilinx_tsn.c b/drivers/net/dsa/xilinx/xilinx_tsn.c
index 6b285c528724..020b1622670c 100644
--- a/drivers/net/dsa/xilinx/xilinx_tsn.c
+++ b/drivers/net/dsa/xilinx/xilinx_tsn.c
@@ -3,6 +3,10 @@
* AMD/Xilinx TSN Endpoint Ethernet MAC DSA switch driver.
*/
+#include <linux/bitfield.h>
+#include <linux/if_bridge.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
@@ -11,6 +15,86 @@
#include "xilinx_tsn.h"
+static void sw_iow(struct xlnx_tsn *sw, u32 off, u32 val)
+{
+ iowrite32(val, sw->sw_base + off);
+}
+
+static u32 sw_ior(struct xlnx_tsn *sw, u32 off)
+{
+ return ioread32(sw->sw_base + off);
+}
+
+static int xlnx_tsn_switch_status_ready(struct xlnx_tsn *sw)
+{
+ u32 reg;
+
+ return readl_poll_timeout(sw->sw_base + TSN_SW_STATUS_OFFSET, reg,
+ reg & TSN_SW_STATUS_READY,
+ TSN_SW_POLL_DELAY_US, TSN_SW_POLL_TIMEOUT_US);
+}
+
+static int xlnx_tsn_port_state_bits(int port, u32 *mask, u32 *chg_bit)
+{
+ switch (port) {
+ case XLNX_TSN_CPU_PORT:
+ *mask = EP_PORT_STATUS_MASK;
+ *chg_bit = EP_PORT_STATUS_CHG_BIT;
+ return 0;
+ case XLNX_TSN_PORT_MAC1:
+ *mask = MAC1_PORT_STATUS_MASK;
+ *chg_bit = MAC1_PORT_STATUS_CHG_BIT;
+ return 0;
+ case XLNX_TSN_PORT_MAC2:
+ *mask = MAC2_PORT_STATUS_MASK;
+ *chg_bit = MAC2_PORT_STATUS_CHG_BIT;
+ return 0;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int xlnx_tsn_set_port_state(struct xlnx_tsn *sw, int port,
+ enum tsn_port_state state)
+{
+ u32 chg_bit, mask, val, reg;
+ int err;
+
+ err = xlnx_tsn_port_state_bits(port, &mask, &chg_bit);
+ if (err)
+ return err;
+
+ err = xlnx_tsn_switch_status_ready(sw);
+ if (err) {
+ dev_err(sw->dev, "port %d: switch not ready for state change\n",
+ port);
+ return err;
+ }
+
+ /* Bit won't re-arm if a previous change never cleared it. */
+ val = sw_ior(sw, TSN_PORT_STATE_CTRL_OFFSET);
+ if (val & chg_bit) {
+ dev_err(sw->dev, "port %d: previous state change still pending\n",
+ port);
+ return -EBUSY;
+ }
+
+ val &= ~mask;
+ val |= (state << __ffs(mask)) & mask;
+ val |= chg_bit;
+ sw_iow(sw, TSN_PORT_STATE_CTRL_OFFSET, val);
+
+ err = readl_poll_timeout(sw->sw_base + TSN_PORT_STATE_CTRL_OFFSET, reg,
+ !(reg & chg_bit), TSN_SW_POLL_DELAY_US,
+ TSN_SW_POLL_TIMEOUT_US);
+ if (err) {
+ dev_err(sw->dev, "port %d: state change ack timed out\n", port);
+ return -ETIMEDOUT;
+ }
+
+ return 0;
+}
+
static enum dsa_tag_protocol xlnx_tsn_get_tag_protocol(struct dsa_switch *ds,
int port,
enum dsa_tag_protocol mp)
@@ -18,19 +102,83 @@ static enum dsa_tag_protocol xlnx_tsn_get_tag_protocol(struct dsa_switch *ds,
return DSA_TAG_PROTO_XLNX_TSN;
}
+static void xlnx_tsn_port_stp_state_set(struct dsa_switch *ds, int port,
+ u8 state)
+{
+ struct xlnx_tsn *sw = ds->priv;
+ enum tsn_port_state hw_state;
+
+ switch (state) {
+ case BR_STATE_DISABLED:
+ hw_state = TSN_PORT_STATE_DISABLED;
+ break;
+ case BR_STATE_BLOCKING:
+ hw_state = TSN_PORT_STATE_BLOCKING;
+ break;
+ case BR_STATE_LISTENING:
+ hw_state = TSN_PORT_STATE_LISTENING;
+ break;
+ case BR_STATE_LEARNING:
+ hw_state = TSN_PORT_STATE_LEARNING;
+ break;
+ case BR_STATE_FORWARDING:
+ hw_state = TSN_PORT_STATE_FORWARDING;
+ break;
+ default:
+ dev_warn(sw->dev, "port %d: unsupported STP state %u\n",
+ port, state);
+ return;
+ }
+
+ xlnx_tsn_set_port_state(sw, port, hw_state);
+}
+
static int xlnx_tsn_setup(struct dsa_switch *ds)
{
+ struct xlnx_tsn *sw = ds->priv;
+ struct dsa_port *dp;
+ int ret;
+
+ if (!dsa_is_user_port(ds, XLNX_TSN_PORT_MAC1) ||
+ !dsa_is_user_port(ds, XLNX_TSN_PORT_MAC2))
+ return dev_err_probe(sw->dev, -EINVAL,
+ "both MAC1 and MAC2 must be enabled as switch ports\n");
+
+ /* CPU port stays in FORWARDING so host traffic always flows.
+ * User ports start in DISABLED and transition from there under
+ * bridge STP control.
+ */
+ ret = xlnx_tsn_set_port_state(sw, XLNX_TSN_CPU_PORT,
+ TSN_PORT_STATE_FORWARDING);
+ if (ret)
+ return ret;
+
+ dsa_switch_for_each_user_port(dp, ds) {
+ ret = xlnx_tsn_set_port_state(sw, dp->index,
+ TSN_PORT_STATE_DISABLED);
+ if (ret)
+ return ret;
+ }
+
return 0;
}
static void xlnx_tsn_teardown(struct dsa_switch *ds)
{
+ struct xlnx_tsn *sw = ds->priv;
+ struct dsa_port *dp;
+
+ dsa_switch_for_each_user_port(dp, ds)
+ xlnx_tsn_set_port_state(sw, dp->index, TSN_PORT_STATE_DISABLED);
+
+ xlnx_tsn_set_port_state(sw, XLNX_TSN_CPU_PORT, TSN_PORT_STATE_DISABLED);
}
static const struct dsa_switch_ops xlnx_tsn_switch_ops = {
.get_tag_protocol = xlnx_tsn_get_tag_protocol,
.setup = xlnx_tsn_setup,
.teardown = xlnx_tsn_teardown,
+ .port_stp_state_set = xlnx_tsn_port_stp_state_set,
};
static int xlnx_tsn_map_reg(struct platform_device *pdev, const char *name,
diff --git a/drivers/net/dsa/xilinx/xilinx_tsn.h b/drivers/net/dsa/xilinx/xilinx_tsn.h
index 0f9af866bd18..1064cf1ce66e 100644
--- a/drivers/net/dsa/xilinx/xilinx_tsn.h
+++ b/drivers/net/dsa/xilinx/xilinx_tsn.h
@@ -5,6 +5,8 @@
#ifndef _XILINX_TSN_H
#define _XILINX_TSN_H
+#include <linux/bitfield.h>
+#include <linux/bits.h>
#include <linux/types.h>
#include <net/dsa.h>
@@ -13,6 +15,36 @@
#define XLNX_TSN_PORT_MAC1 1
#define XLNX_TSN_PORT_MAC2 2
+#define TSN_SW_STATUS_OFFSET 0x00000
+/* Poll this before changing port state. */
+#define TSN_SW_STATUS_READY BIT(0)
+
+/* Switch Port State Control register: packs per-port STP state and
+ * change-commit bits into one 32-bit word.
+ */
+#define TSN_PORT_STATE_CTRL_OFFSET 0x0004c
+
+#define EP_PORT_STATUS_CHG_BIT BIT(0)
+#define EP_PORT_STATUS_MASK GENMASK(3, 1)
+#define MAC1_PORT_STATUS_CHG_BIT BIT(8)
+#define MAC1_PORT_STATUS_MASK GENMASK(11, 9)
+#define MAC2_PORT_STATUS_CHG_BIT BIT(16)
+#define MAC2_PORT_STATUS_MASK GENMASK(19, 17)
+
+/* readl_poll_timeout() parameters (in microseconds): poll until
+ * the port-state change-commit bit self-clears.
+ */
+#define TSN_SW_POLL_DELAY_US 10
+#define TSN_SW_POLL_TIMEOUT_US 5000
+
+enum tsn_port_state {
+ TSN_PORT_STATE_DISABLED = 0,
+ TSN_PORT_STATE_BLOCKING,
+ TSN_PORT_STATE_LISTENING,
+ TSN_PORT_STATE_LEARNING,
+ TSN_PORT_STATE_FORWARDING,
+};
+
/**
* struct xlnx_tsn - per-IP switch state
* @ds: DSA switch
--
2.34.1
More information about the linux-arm-kernel
mailing list