From: Wei Fang <wei.fang@nxp.com>
To: claudiu.manoil@nxp.com, vladimir.oltean@nxp.com,
xiaoning.wang@nxp.com, andrew+netdev@lunn.ch,
davem@davemloft.net, edumazet@google.com, kuba@kernel.org,
pabeni@redhat.com, robh@kernel.org, krzk+dt@kernel.org,
conor+dt@kernel.org, f.fainelli@gmail.com, frank.li@nxp.com,
chleroy@kernel.org, horms@kernel.org, linux@armlinux.org.uk,
maxime.chevallier@bootlin.com, andrew@lunn.ch, olteanv@gmail.com
Cc: netdev@vger.kernel.org, linux-kernel@vger.kernel.org,
devicetree@vger.kernel.org, linuxppc-dev@lists.ozlabs.org,
linux-arm-kernel@lists.infradead.org, imx@lists.linux.dev
Subject: [PATCH v7 net-next 11/15] net: dsa: netc: add phylink MAC operations
Date: Wed, 13 May 2026 11:04:50 +0800 [thread overview]
Message-ID: <20260513030454.1666570-12-wei.fang@nxp.com> (raw)
In-Reply-To: <20260513030454.1666570-1-wei.fang@nxp.com>
Different versions of NETC switches have different numbers of ports and
MAC capabilities. Add .phylink_get_caps() to struct netc_switch_info,
allowing each NETC switch version to implement its own callback for
obtaining MAC capabilities.
Implement the phylink_mac_ops callbacks: .mac_config(), .mac_link_up(),
and .mac_link_down(). Note that flow-control configuration is not yet
supported in .mac_link_up(), but will be implemented in a subsequent
patch.
Signed-off-by: Wei Fang <wei.fang@nxp.com>
Reviewed-by: Maxime Chevallier <maxime.chevallier@bootlin.com>
---
drivers/net/dsa/netc/netc_main.c | 247 ++++++++++++++++++++++++++
drivers/net/dsa/netc/netc_platform.c | 38 ++++
drivers/net/dsa/netc/netc_switch.h | 4 +
drivers/net/dsa/netc/netc_switch_hw.h | 26 +++
4 files changed, 315 insertions(+)
diff --git a/drivers/net/dsa/netc/netc_main.c b/drivers/net/dsa/netc/netc_main.c
index 8e3a3230226c..2141b3aa96b7 100644
--- a/drivers/net/dsa/netc/netc_main.c
+++ b/drivers/net/dsa/netc/netc_main.c
@@ -43,6 +43,30 @@ static void netc_mac_port_wr(struct netc_port *np, u32 reg, u32 val)
netc_port_wr(np, reg + NETC_PMAC_OFFSET, val);
}
+/* netc_mac_port_rmw() is used to synchronize the configurations of eMAC
+ * and pMAC to maintain consistency. This function should not be used if
+ * differentiated settings are required.
+ */
+static void netc_mac_port_rmw(struct netc_port *np, u32 reg,
+ u32 mask, u32 val)
+{
+ u32 old, new;
+
+ if (is_netc_pseudo_port(np))
+ return;
+
+ WARN_ON((mask | val) != mask);
+
+ old = netc_port_rd(np, reg);
+ new = (old & ~mask) | val;
+ if (new == old)
+ return;
+
+ netc_port_wr(np, reg, new);
+ if (np->caps.pmac)
+ netc_port_wr(np, reg + NETC_PMAC_OFFSET, new);
+}
+
static void netc_port_get_capability(struct netc_port *np)
{
u32 val;
@@ -507,10 +531,232 @@ static void netc_switch_get_ip_revision(struct netc_switch *priv)
priv->revision = FIELD_GET(IPBRR0_IP_REV, val);
}
+static void netc_phylink_get_caps(struct dsa_switch *ds, int port,
+ struct phylink_config *config)
+{
+ struct netc_switch *priv = ds->priv;
+
+ priv->info->phylink_get_caps(port, config);
+}
+
+static void netc_port_set_mac_mode(struct netc_port *np,
+ unsigned int mode,
+ phy_interface_t phy_mode)
+{
+ u32 mask = PM_IF_MODE_IFMODE | PM_IF_MODE_REVMII;
+ u32 val = 0;
+
+ switch (phy_mode) {
+ case PHY_INTERFACE_MODE_RGMII:
+ case PHY_INTERFACE_MODE_RGMII_ID:
+ case PHY_INTERFACE_MODE_RGMII_RXID:
+ case PHY_INTERFACE_MODE_RGMII_TXID:
+ val |= IFMODE_RGMII;
+ break;
+ case PHY_INTERFACE_MODE_RMII:
+ val |= IFMODE_RMII;
+ break;
+ case PHY_INTERFACE_MODE_REVMII:
+ val |= PM_IF_MODE_REVMII;
+ fallthrough;
+ case PHY_INTERFACE_MODE_MII:
+ val |= IFMODE_MII;
+ break;
+ case PHY_INTERFACE_MODE_SGMII:
+ case PHY_INTERFACE_MODE_2500BASEX:
+ val |= IFMODE_SGMII;
+ break;
+ default:
+ break;
+ }
+
+ netc_mac_port_rmw(np, NETC_PM_IF_MODE(0), mask, val);
+}
+
+static void netc_mac_config(struct phylink_config *config, unsigned int mode,
+ const struct phylink_link_state *state)
+{
+ struct dsa_port *dp = dsa_phylink_to_port(config);
+
+ netc_port_set_mac_mode(NETC_PORT(dp->ds, dp->index), mode,
+ state->interface);
+}
+
+static void netc_port_set_speed(struct netc_port *np, int speed)
+{
+ netc_port_rmw(np, NETC_PCR, PCR_PSPEED, PSPEED_SET_VAL(speed));
+}
+
+static void netc_port_set_rgmii_mac(struct netc_port *np,
+ int speed, int duplex)
+{
+ u32 mask, val;
+
+ mask = PM_IF_MODE_SSP | PM_IF_MODE_HD | PM_IF_MODE_M10;
+
+ switch (speed) {
+ default:
+ case SPEED_1000:
+ val = FIELD_PREP(PM_IF_MODE_SSP, SSP_1G);
+ break;
+ case SPEED_100:
+ val = FIELD_PREP(PM_IF_MODE_SSP, SSP_100M);
+ break;
+ case SPEED_10:
+ val = FIELD_PREP(PM_IF_MODE_SSP, SSP_10M);
+ break;
+ }
+
+ if (duplex != DUPLEX_FULL)
+ val |= PM_IF_MODE_HD;
+
+ netc_mac_port_rmw(np, NETC_PM_IF_MODE(0), mask, val);
+}
+
+static void netc_port_set_rmii_mii_mac(struct netc_port *np,
+ int speed, int duplex)
+{
+ u32 mask, val = 0;
+
+ mask = PM_IF_MODE_SSP | PM_IF_MODE_HD | PM_IF_MODE_M10;
+
+ if (speed == SPEED_10)
+ val |= PM_IF_MODE_M10;
+
+ if (duplex != DUPLEX_FULL)
+ val |= PM_IF_MODE_HD;
+
+ netc_mac_port_rmw(np, NETC_PM_IF_MODE(0), mask, val);
+}
+
+static void netc_port_mac_rx_enable(struct netc_port *np)
+{
+ netc_port_rmw(np, NETC_POR, POR_RXDIS, 0);
+ netc_mac_port_rmw(np, NETC_PM_CMD_CFG(0), PM_CMD_CFG_RX_EN,
+ PM_CMD_CFG_RX_EN);
+}
+
+static void netc_port_wait_rx_empty(struct netc_port *np, int mac)
+{
+ u32 val;
+
+ /* PM_IEVENT_RX_EMPTY is a read-only bit, it is automatically set by
+ * hardware if RX FIFO is empty and no RX packet receive in process.
+ * And it is automatically cleared if RX FIFO is not empty or RX
+ * packet receive in process.
+ */
+ if (read_poll_timeout(netc_port_rd, val, val & PM_IEVENT_RX_EMPTY,
+ 100, 10000, false, np, NETC_PM_IEVENT(mac)))
+ dev_warn(np->switch_priv->dev,
+ "swp%d MAC%d: RX is not idle\n", np->dp->index, mac);
+}
+
+static void netc_port_mac_rx_graceful_stop(struct netc_port *np)
+{
+ u32 val;
+
+ if (is_netc_pseudo_port(np))
+ goto rx_disable;
+
+ if (np->caps.pmac) {
+ netc_port_rmw(np, NETC_PM_CMD_CFG(1), PM_CMD_CFG_RX_EN, 0);
+ netc_port_wait_rx_empty(np, 1);
+ }
+
+ netc_port_rmw(np, NETC_PM_CMD_CFG(0), PM_CMD_CFG_RX_EN, 0);
+ netc_port_wait_rx_empty(np, 0);
+
+ if (read_poll_timeout(netc_port_rd, val, !(val & PSR_RX_BUSY),
+ 100, 10000, false, np, NETC_PSR))
+ dev_warn(np->switch_priv->dev, "swp%d RX is busy\n",
+ np->dp->index);
+
+rx_disable:
+ netc_port_rmw(np, NETC_POR, POR_RXDIS, POR_RXDIS);
+}
+
+static void netc_port_mac_tx_enable(struct netc_port *np)
+{
+ netc_mac_port_rmw(np, NETC_PM_CMD_CFG(0), PM_CMD_CFG_TX_EN,
+ PM_CMD_CFG_TX_EN);
+ netc_port_rmw(np, NETC_POR, POR_TXDIS, 0);
+}
+
+static void netc_port_wait_tx_empty(struct netc_port *np, int mac)
+{
+ u32 val;
+
+ /* PM_IEVENT_TX_EMPTY is a read-only bit, it is automatically set by
+ * hardware if TX FIFO is empty. And it is automatically cleared if
+ * TX FIFO is not empty.
+ */
+ if (read_poll_timeout(netc_port_rd, val, val & PM_IEVENT_TX_EMPTY,
+ 100, 10000, false, np, NETC_PM_IEVENT(mac)))
+ dev_warn(np->switch_priv->dev,
+ "swp%d MAC%d: TX FIFO is not empty\n",
+ np->dp->index, mac);
+}
+
+static void netc_port_mac_tx_graceful_stop(struct netc_port *np)
+{
+ netc_port_rmw(np, NETC_POR, POR_TXDIS, POR_TXDIS);
+
+ if (is_netc_pseudo_port(np))
+ return;
+
+ netc_port_wait_tx_empty(np, 0);
+ if (np->caps.pmac)
+ netc_port_wait_tx_empty(np, 1);
+
+ netc_mac_port_rmw(np, NETC_PM_CMD_CFG(0), PM_CMD_CFG_TX_EN, 0);
+}
+
+static void netc_mac_link_up(struct phylink_config *config,
+ struct phy_device *phy, unsigned int mode,
+ phy_interface_t interface, int speed,
+ int duplex, bool tx_pause, bool rx_pause)
+{
+ struct dsa_port *dp = dsa_phylink_to_port(config);
+ struct netc_port *np;
+
+ np = NETC_PORT(dp->ds, dp->index);
+ netc_port_set_speed(np, speed);
+
+ if (phy_interface_mode_is_rgmii(interface))
+ netc_port_set_rgmii_mac(np, speed, duplex);
+
+ if (interface == PHY_INTERFACE_MODE_RMII ||
+ interface == PHY_INTERFACE_MODE_REVMII ||
+ interface == PHY_INTERFACE_MODE_MII)
+ netc_port_set_rmii_mii_mac(np, speed, duplex);
+
+ netc_port_mac_tx_enable(np);
+ netc_port_mac_rx_enable(np);
+}
+
+static void netc_mac_link_down(struct phylink_config *config,
+ unsigned int mode,
+ phy_interface_t interface)
+{
+ struct dsa_port *dp = dsa_phylink_to_port(config);
+ struct netc_port *np;
+
+ np = NETC_PORT(dp->ds, dp->index);
+ netc_port_mac_rx_graceful_stop(np);
+ netc_port_mac_tx_graceful_stop(np);
+}
+
+static const struct phylink_mac_ops netc_phylink_mac_ops = {
+ .mac_config = netc_mac_config,
+ .mac_link_up = netc_mac_link_up,
+ .mac_link_down = netc_mac_link_down,
+};
+
static const struct dsa_switch_ops netc_switch_ops = {
.get_tag_protocol = netc_get_tag_protocol,
.setup = netc_setup,
.teardown = netc_teardown,
+ .phylink_get_caps = netc_phylink_get_caps,
};
static int netc_switch_probe(struct pci_dev *pdev,
@@ -549,6 +795,7 @@ static int netc_switch_probe(struct pci_dev *pdev,
ds->num_ports = priv->info->num_ports;
ds->num_tx_queues = NETC_TC_NUM;
ds->ops = &netc_switch_ops;
+ ds->phylink_mac_ops = &netc_phylink_mac_ops;
ds->priv = priv;
priv->ds = ds;
diff --git a/drivers/net/dsa/netc/netc_platform.c b/drivers/net/dsa/netc/netc_platform.c
index abd599ea9c8d..bb4f92d238cb 100644
--- a/drivers/net/dsa/netc/netc_platform.c
+++ b/drivers/net/dsa/netc/netc_platform.c
@@ -11,8 +11,46 @@ struct netc_switch_platform {
const struct netc_switch_info *info;
};
+static void imx94_switch_phylink_get_caps(int port,
+ struct phylink_config *config)
+{
+ config->mac_capabilities = MAC_1000FD;
+
+ switch (port) {
+ case 0 ... 1:
+ __set_bit(PHY_INTERFACE_MODE_SGMII,
+ config->supported_interfaces);
+ __set_bit(PHY_INTERFACE_MODE_2500BASEX,
+ config->supported_interfaces);
+ config->mac_capabilities |= MAC_2500FD;
+ fallthrough;
+ case 2:
+ config->mac_capabilities |= MAC_10 | MAC_100;
+ __set_bit(PHY_INTERFACE_MODE_MII,
+ config->supported_interfaces);
+ __set_bit(PHY_INTERFACE_MODE_RMII,
+ config->supported_interfaces);
+ /* Port 0 and 1 do not support REVMII */
+ if (port == 2)
+ __set_bit(PHY_INTERFACE_MODE_REVMII,
+ config->supported_interfaces);
+
+ phy_interface_set_rgmii(config->supported_interfaces);
+ break;
+ case 3: /* CPU port */
+ __set_bit(PHY_INTERFACE_MODE_INTERNAL,
+ config->supported_interfaces);
+ config->mac_capabilities |= MAC_10FD | MAC_100FD |
+ MAC_2500FD;
+ break;
+ default:
+ break;
+ }
+}
+
static const struct netc_switch_info imx94_info = {
.num_ports = 4,
+ .phylink_get_caps = imx94_switch_phylink_get_caps,
};
static const struct netc_switch_platform netc_platforms[] = {
diff --git a/drivers/net/dsa/netc/netc_switch.h b/drivers/net/dsa/netc/netc_switch.h
index a6d36dcebc6d..ac9743da2a1e 100644
--- a/drivers/net/dsa/netc/netc_switch.h
+++ b/drivers/net/dsa/netc/netc_switch.h
@@ -35,6 +35,7 @@ struct netc_switch;
struct netc_switch_info {
u32 num_ports;
+ void (*phylink_get_caps)(int port, struct phylink_config *config);
};
struct netc_port_caps {
@@ -70,6 +71,9 @@ struct netc_switch {
struct ntmp_user ntmp;
};
+#define NETC_PRIV(ds) ((struct netc_switch *)((ds)->priv))
+#define NETC_PORT(ds, port_id) (NETC_PRIV(ds)->ports[(port_id)])
+
/* Write/Read Switch base registers */
#define netc_base_rd(r, o) netc_read((r)->base + (o))
#define netc_base_wr(r, o, v) netc_write((r)->base + (o), v)
diff --git a/drivers/net/dsa/netc/netc_switch_hw.h b/drivers/net/dsa/netc/netc_switch_hw.h
index 0419f7f9207e..7d9afb493053 100644
--- a/drivers/net/dsa/netc/netc_switch_hw.h
+++ b/drivers/net/dsa/netc/netc_switch_hw.h
@@ -67,6 +67,14 @@
#define PQOSMR_VQMP GENMASK(19, 16)
#define PQOSMR_QVMP GENMASK(23, 20)
+#define NETC_POR 0x100
+#define POR_TXDIS BIT(0)
+#define POR_RXDIS BIT(1)
+
+#define NETC_PSR 0x104
+#define PSR_TX_BUSY BIT(0)
+#define PSR_RX_BUSY BIT(1)
+
#define NETC_PTCTMSDUR(a) (0x208 + (a) * 0x20)
#define PTCTMSDUR_MAXSDU GENMASK(15, 0)
#define PTCTMSDUR_SDU_TYPE GENMASK(17, 16)
@@ -123,6 +131,24 @@ enum netc_mfo {
#define NETC_PM_MAXFRM(a) (0x1014 + (a) * 0x400)
#define PM_MAXFRAM GENMASK(15, 0)
+#define NETC_PM_IEVENT(a) (0x1040 + (a) * 0x400)
+#define PM_IEVENT_TX_EMPTY BIT(5)
+#define PM_IEVENT_RX_EMPTY BIT(6)
+
+#define NETC_PM_IF_MODE(a) (0x1300 + (a) * 0x400)
+#define PM_IF_MODE_IFMODE GENMASK(2, 0)
+#define IFMODE_MII 1
+#define IFMODE_RMII 3
+#define IFMODE_RGMII 4
+#define IFMODE_SGMII 5
+#define PM_IF_MODE_REVMII BIT(3)
+#define PM_IF_MODE_M10 BIT(4)
+#define PM_IF_MODE_HD BIT(6)
+#define PM_IF_MODE_SSP GENMASK(14, 13)
+#define SSP_100M 0
+#define SSP_10M 1
+#define SSP_1G 2
+
#define NETC_PEMDIOCR 0x1c00
#define NETC_EMDIO_BASE NETC_PEMDIOCR
--
2.34.1
next prev parent reply other threads:[~2026-05-13 3:03 UTC|newest]
Thread overview: 23+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-05-13 3:04 [PATCH v7 net-next 00/15] Add preliminary NETC switch support for i.MX94 Wei Fang
2026-05-13 3:04 ` [PATCH v7 net-next 01/15] dt-bindings: net: dsa: update the description of 'dsa,member' property Wei Fang
2026-05-13 3:04 ` [PATCH v7 net-next 02/15] dt-bindings: net: dsa: add NETC switch Wei Fang
2026-05-13 3:04 ` [PATCH v7 net-next 03/15] net: enetc: add pre-boot initialization for i.MX94 switch Wei Fang
2026-05-13 3:04 ` [PATCH v7 net-next 04/15] net: enetc: add basic operations to the FDB table Wei Fang
2026-05-13 3:04 ` [PATCH v7 net-next 05/15] net: enetc: add support for the "Add" operation to VLAN filter table Wei Fang
2026-05-13 3:04 ` [PATCH v7 net-next 06/15] net: enetc: add support for the "Update" operation to buffer pool table Wei Fang
2026-05-13 3:04 ` [PATCH v7 net-next 07/15] net: enetc: add support for "Add" and "Delete" operations to IPFT Wei Fang
2026-05-13 3:04 ` [PATCH v7 net-next 08/15] net: enetc: add multiple command BD rings support Wei Fang
2026-05-13 3:04 ` [PATCH v7 net-next 09/15] net: dsa: add NETC switch tag support Wei Fang
2026-05-14 5:22 ` sashiko-bot
2026-05-15 2:55 ` Wei Fang
2026-05-13 3:04 ` [PATCH v7 net-next 10/15] net: dsa: netc: introduce NXP NETC switch driver for i.MX94 Wei Fang
2026-05-14 5:57 ` sashiko-bot
2026-05-15 3:36 ` Wei Fang
2026-05-13 3:04 ` Wei Fang [this message]
2026-05-13 3:04 ` [PATCH v7 net-next 12/15] net: dsa: netc: add FDB, STP, MTU, port setup and host flooding support Wei Fang
2026-05-14 8:21 ` sashiko-bot
2026-05-13 3:04 ` [PATCH v7 net-next 13/15] net: dsa: netc: initialize buffer pool table and implement flow-control Wei Fang
2026-05-14 8:51 ` sashiko-bot
2026-05-13 3:04 ` [PATCH v7 net-next 14/15] net: dsa: netc: add support for the standardized counters Wei Fang
2026-05-13 3:04 ` [PATCH v7 net-next 15/15] net: dsa: netc: add support for ethtool private statistics Wei Fang
2026-05-14 10:27 ` sashiko-bot
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260513030454.1666570-12-wei.fang@nxp.com \
--to=wei.fang@nxp.com \
--cc=andrew+netdev@lunn.ch \
--cc=andrew@lunn.ch \
--cc=chleroy@kernel.org \
--cc=claudiu.manoil@nxp.com \
--cc=conor+dt@kernel.org \
--cc=davem@davemloft.net \
--cc=devicetree@vger.kernel.org \
--cc=edumazet@google.com \
--cc=f.fainelli@gmail.com \
--cc=frank.li@nxp.com \
--cc=horms@kernel.org \
--cc=imx@lists.linux.dev \
--cc=krzk+dt@kernel.org \
--cc=kuba@kernel.org \
--cc=linux-arm-kernel@lists.infradead.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux@armlinux.org.uk \
--cc=linuxppc-dev@lists.ozlabs.org \
--cc=maxime.chevallier@bootlin.com \
--cc=netdev@vger.kernel.org \
--cc=olteanv@gmail.com \
--cc=pabeni@redhat.com \
--cc=robh@kernel.org \
--cc=vladimir.oltean@nxp.com \
--cc=xiaoning.wang@nxp.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.