* [RFC PATCH 0/8] xilinx: tsn: Add TSN Endpoint Ethernet MAC driver support
@ 2026-02-19 5:49 Srinivas Neeli
2026-02-19 5:49 ` [RFC PATCH 1/8] dt-bindings: net: Add TSN Endpoint Ethernet MAC support Srinivas Neeli
` (9 more replies)
0 siblings, 10 replies; 27+ messages in thread
From: Srinivas Neeli @ 2026-02-19 5:49 UTC (permalink / raw)
To: andrew+netdev, davem, edumazet, kuba, pabeni, michal.simek, robh,
krzk+dt, conor+dt, richardcochran
Cc: netdev, linux-kernel, devicetree, linux-arm-kernel, git,
srinivas.neeli
Introduce a new network driver for the AMD LogiCORE 100M/1G TSN
Subsystem IP, also known as the TSN Endpoint Ethernet MAC, which
implements IEEE 802.1 Time-Sensitive Networking (TSN) features for
deterministic and low-latency Ethernet communication in real-time and
industrial automation use cases.
IP Core Overview:
The AMD LogiCORE 100M/1G TSN Subsystem IP solution (named as TSN Endpoint
Ethernet MAC IP in the IP catalog) implements IEEE 802.1 Time Sensitive
Networking (TSN) Standards and provides a low latency Bridged Endpoint or
Endpoint only solutions. The bridged endpoint solution consists of a 3-port
switch that connects to an endpoint including Linux software drivers. For
Bridged Endpoint (Switch Endpoint), two ports connects to the network and
one port connects to an internal Endpoint. It supports the use of
GMII/RGMII interfaces connecting to a physical-side interface (PHY) chip
with full duplex 100 Mb/s and 1 Gb/s operations.
Features:
- IP Customizable to generate TSN Endpoint or Bridged Endpoint solution
- Supports interface to network software stack
- Designed to comply with the following IEEE standards
- 802.1AS : Precision Time Protocol (PTP) synchronization
- 802.1Qav : Credit-Based Shaper for traffic shaping
- 802.1Qbv : Time-Aware Shaper for scheduled traffic
- 802.1Qbu : Frame Preemption for ultra-low latency
- 802.1CB : Frame Replication and Elimination for redundancy
- IP is customizable to enable various TSN feature-sets
- Supports three priority queues for scheduled, reserved (IEEE 802.1 Qav
Credit Based Shaper) and best effort traffic classes
- Provides feature rich Ethernet Switch that caters to various network
needs
* 3-port Switch (2-external, 1-internal)
* Programmable cut-through and store-forward operations
* 4-port Switch (2-external, 2-internal) extension through
'Endpoint Extension' and 'Endpoint Packet Switching' features
- Solution validated on ZCU102 & ZC702 AMD Evaluation boards.
Sample hardware architecture diagram for Bidge End Point like below:
+------------------+
| MCDMA |
+---------+--------+
Q0---Q7
|
+------------------------------------------------------------ +
| | TSN sub system(Bridge End Point) |
| | |
| +------+----+ Port 0 +-----------------------+ |
| | EndPoint |<--------->| TSN Switch | |
| | (EP) | +----+-------------+----+ |
| +-----------+ | | |
| | | |
| Port 1 Port 2 |
| | | |
| +-----------+ +-----------+ |
| | MAC-1 | | MAC-2 | |
| | (ETH1) | | (ETH2) | |
| +-----+-----+ +-----+-----+ |
| | | |
| | | |
+-------------------------------------------------------------+
| |
RGMII RGMII
| |
+-----------+ +-----------+
| PHY1 | | PHY2 |
| (Port 0) | | (Port 2) |
+-----------+ +-----------+
Software Driver Overivew:
This patch series targets for Bridge Endpoint design(Two EMAC's ,
one Endpoint and Switch block) with Multi channel DMA.
The TSN driver architecture mirrors the hardware structure by dividing
functionality into multiple sub-blocks(Endpoint, EMACs, Switch, MDIO, DMA).
Each sub-block includes its own init and exit routines, called from the
core probe and remove flow, enabling flexible configurations and clean
error unwind. Please check detailed explanation of each sub-block below.
Core Driver (xilinx_tsn.c):
- Acts as the central entry point for the driver, implementing platform
probe and remove callbacks.
- Handles device tree parsing to discover configuration parameters such as
DMA channel counts, queue priorities, and child hardware nodes.
- Manages clock acquisition, enablement, and disablement using the
kernel bulk clock APIs.
- Maps the top-level register space for the TSN subsystem and prepares it
for use by sub-blocks.
- Establishes initialization sequencing, ensuring that the endpoint is
initialized first, followed by EMACs and MDIO, and finally the switch
block.
- Provides a central driver context (struct tsn_priv), containing locks,
statistics counters, configuration parameters, and references to
instantiated functional components.
EndPoint(xilinx_tsn_ep.c):
- Implements the primary host-facing net_device interface (tsn_ep), which
acts as the main application access point to the TSN subsystem.
- Manages multi-queue TX and RX descriptor rings using the dmaengine
framework for high-performance packet transfer.
- Handles DMA submission and completion callbacks (tsn_dma_tx_cb /
tsn_dma_rx_cb) to ensure efficient packet flow and completion tracking.
- Includes a VLAN PCP-to-queue mapping mechanism, enabling priority-based
traffic classification and scheduling.
- Implements standard net_device operations including open, stop,
start_xmit, set_mac_address, and select_queue for seamless integration
with the Linux networking stack.
EMAC & MDIO (xilinx_tsn_emac.c and xilinx_tsn_mdio.c):
- Discovers and initializes individual Ethernet MAC instances by parsing
child "ethernet-mac" nodes from the device tree.
- Allocates and registers a dedicated net_device for each MAC port,
enabling independent interface management.
- Maps and configures register space for each MAC instance, including MAC
address programming through UAW0/UAW1 registers.
- Establishes PHY connectivity using of_phy_connect() and manages link
state changes.
- Updates MAC control registers dynamically based on PHY state transitions
to ensure correct operational parameters.
- Optionally sets up an integrated MDIO bus if a "mdio" child node is
present under the MAC node, enabling PHY access and management through
the standard PHY framewor.
Switch (xilinx_tsn_switch.c):
- Handles initialization of the optional TSN switch block by mapping the
"switch" child node from the device tree.
- Provides per-port state control (Disabled,Forwarding) via a hardware
change-bit handshake mechanism with polling and timeout handling.
- Adds frame filter configuration support based on unicast source and
destination MAC addresses.
- During driver initialization, all switch ports (Endpoint, MAC1, MAC2)
are configured into the Forwarding state to enable data flow across the
fabric.
- On driver removal, all ports are transitioned to the disabled state to
ensure clean shutdown and traffic isolation.
xilinx_tsn_ptp_clock.c:
- Implements PTP Hardware Clock (PHC) support for the TSN subsystem's
RTC/timer block.
- Provides ptp_clock_info operations (gettime64, settime64, adjtime,
adjfine) for time synchronization.
- Handles RTC offset and increment register programming for clock
adjustments.
- Implements PPS (Pulse Per Second) generation via timer interrupt
handler, generating events at 128 pulses/second and delivering them
to userspace.
- Registers PHC using ptp_clock_register() and exposes PHC index via
ethtool for use by PTP daemons (ptp4l, phc2sys).
- Initialization is performed only for EMAC1 (MAC ID 1), as the PTP timer
is shared globally across all MAC instances.
- Calculates RTC increment value based on fixed 125 MHz GTX clock
frequency as specified in TSN IP core documentation.
xilinx_tsn_ptp_xmit.c:
- Implements dedicated PTP packet transmission and timestamping path
using hardware PTP buffers.
- Manages 8 TX buffers and 16 RX buffers in hardware for PTP packet handling.
- Provides 32-bit aligned memory-mapped I/O helpers
(memcpy_toio_32, memcpy_fromio_32) for efficient buffer access.
- Implements TX path (tsn_ptp_xmit) that copies PTP packets to hardware
TX buffers and queues skbs for timestamp retrieval.
- Handles RX path using circular buffer mechanism with ptp_rx_hw_pointer
(hardware write position) and ptp_rx_sw_pointer (software read position)
for synchronized buffer access.
- Delivers hardware timestamps only for PTP event messages (Sync,
Delay_Req, Pdelay_Req, Pdelay_Resp) by checking message type field.
- Uses interrupt-driven approach: RX ISR calls tsn_ptp_recv() directly,
TX ISR schedules work queue (tsn_ptp_tx_tstamp) for timestamp delivery.
- Implements proper error handling including NULL checks for skb
allocation, TX buffer overflow detection with netdev statistics updates,
and cleanup paths for interrupt unregistration.
Future work:
- Plan to add hardware statistics support for EMACs.
- Plan to add support for PTP(1 step), QBV, preemption, FRER features.
- Adopting switch dev frame work for switch configurations.
Test information:
- Performed ping,iperf3 and 2 step ptp tests on the ZYNQMP platform.
NOTE:
- The changes depend on a few patches in the Xilinx DMA driver
(xilinx_dma.c). Some of these patches are already posted, while
others are still under review. The corresponding lore links are listed
below, and links for the remaining patches will be added once they are
submitted.
https://lore.kernel.org/all/DDB5J5V1IM0E.34WP32K550WIU@folker-schwesinger.de/
https://lore.kernel.org/all/DDB5IRSNB09F.3HRTZZOZQ7J6@folker-schwesinger.de/
https://lore.kernel.org/all/DDB5IDDEOVBT.NHJF03FYW2BN@folker-schwesinger.de/
This RFC is the initial submission of the driver and aims to gather
feedback on design, structure, and subsystem integration before
further upstreamization work.
Srinivas Neeli (8):
dt-bindings: net: Add TSN Endpoint Ethernet MAC support
net: xilinx: tsn: Introduce TSN core driver skeleton
net: xilinx: tsn: Add TSN endpoint and MCDMA support
xilinx: tsn: Add Ethernet MAC (EMAC) and MDIO support to the TSN
driver
net: xilinx: tsn: Add TSN switch support with port state and frame
filter control
dt-bindings: net: Add PTP interrupt support
net: xilinx: tsn: Add PTP hardware clock (PHC) and timer support
net: xilinx: tsn: Add PTP packet transmission support
.../net/xlnx,tsn-endpoint-ethernet-mac.yaml | 362 ++++++++
drivers/net/ethernet/xilinx/Kconfig | 1 +
drivers/net/ethernet/xilinx/Makefile | 1 +
drivers/net/ethernet/xilinx/tsn/Kconfig | 14 +
drivers/net/ethernet/xilinx/tsn/Makefile | 2 +
drivers/net/ethernet/xilinx/tsn/xilinx_tsn.h | 430 ++++++++++
.../net/ethernet/xilinx/tsn/xilinx_tsn_emac.c | 547 ++++++++++++
.../net/ethernet/xilinx/tsn/xilinx_tsn_ep.c | 211 +++++
.../net/ethernet/xilinx/tsn/xilinx_tsn_main.c | 794 ++++++++++++++++++
.../net/ethernet/xilinx/tsn/xilinx_tsn_mdio.c | 308 +++++++
.../xilinx/tsn/xilinx_tsn_ptp_clock.c | 386 +++++++++
.../ethernet/xilinx/tsn/xilinx_tsn_ptp_xmit.c | 451 ++++++++++
.../ethernet/xilinx/tsn/xilinx_tsn_switch.c | 546 ++++++++++++
13 files changed, 4053 insertions(+)
create mode 100644 Documentation/devicetree/bindings/net/xlnx,tsn-endpoint-ethernet-mac.yaml
create mode 100644 drivers/net/ethernet/xilinx/tsn/Kconfig
create mode 100644 drivers/net/ethernet/xilinx/tsn/Makefile
create mode 100644 drivers/net/ethernet/xilinx/tsn/xilinx_tsn.h
create mode 100644 drivers/net/ethernet/xilinx/tsn/xilinx_tsn_emac.c
create mode 100644 drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c
create mode 100644 drivers/net/ethernet/xilinx/tsn/xilinx_tsn_main.c
create mode 100644 drivers/net/ethernet/xilinx/tsn/xilinx_tsn_mdio.c
create mode 100644 drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ptp_clock.c
create mode 100644 drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ptp_xmit.c
create mode 100644 drivers/net/ethernet/xilinx/tsn/xilinx_tsn_switch.c
--
2.25.1
^ permalink raw reply [flat|nested] 27+ messages in thread
* [RFC PATCH 1/8] dt-bindings: net: Add TSN Endpoint Ethernet MAC support
2026-02-19 5:49 [RFC PATCH 0/8] xilinx: tsn: Add TSN Endpoint Ethernet MAC driver support Srinivas Neeli
@ 2026-02-19 5:49 ` Srinivas Neeli
2026-02-19 16:53 ` Andrew Lunn
2026-02-19 5:49 ` [RFC PATCH 2/8] net: xilinx: tsn: Introduce TSN core driver skeleton Srinivas Neeli
` (8 subsequent siblings)
9 siblings, 1 reply; 27+ messages in thread
From: Srinivas Neeli @ 2026-02-19 5:49 UTC (permalink / raw)
To: andrew+netdev, davem, edumazet, kuba, pabeni, michal.simek, robh,
krzk+dt, conor+dt, richardcochran
Cc: netdev, linux-kernel, devicetree, linux-arm-kernel, git,
srinivas.neeli
TSN Endpoint Ethernet MAC implements IEEE 802.1 Time-Sensitive Networking
(TSN) standards, providing deterministic, low-latency Ethernet
communication. It can operate in two configurations, either as
Endpoint-only or Bridged Endpoint mode. The Bridged Endpoint configuration
integrates a three-port switch, with two ports connected to the external
network and one port connected to an internal endpoint.
The IP supports GMII and RGMII interfaces for connection to external PHY
devices, enabling full-duplex operation at 100 Mb/s and 1 Gb/s.
Add devicetree binding documentation for the TSN Endpoint Ethernet MAC IP,
including support for multiple Ethernet MACs, a TSN switch, and an
endpoint block.
Signed-off-by: Srinivas Neeli <srinivas.neeli@amd.com>
---
NOTE:
The xlnx,tsn-endpoint-ethernet-mac-3.0 corresponds to the TSN Endpoint Ethernet MAC IP version.
The IP Product Guide is currently under review and will be published on
the AMD/Xilinx documentation portal (similar to other IP TRMs).
---
.../net/xlnx,tsn-endpoint-ethernet-mac.yaml | 287 ++++++++++++++++++
1 file changed, 287 insertions(+)
create mode 100644 Documentation/devicetree/bindings/net/xlnx,tsn-endpoint-ethernet-mac.yaml
diff --git a/Documentation/devicetree/bindings/net/xlnx,tsn-endpoint-ethernet-mac.yaml b/Documentation/devicetree/bindings/net/xlnx,tsn-endpoint-ethernet-mac.yaml
new file mode 100644
index 000000000000..0d61a911e1d1
--- /dev/null
+++ b/Documentation/devicetree/bindings/net/xlnx,tsn-endpoint-ethernet-mac.yaml
@@ -0,0 +1,287 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/net/xlnx,tsn-endpoint-ethernet-mac.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Xilinx TSN Endpoint Ethernet MAC
+
+description:
+ TSN Endpoint Ethernet MAC IP implements IEEE 802.1 Time-Sensitive Networking (TSN)
+ standards and provides low-latency network connectivity in either Endpoint-only or
+ Bridged Endpoint configurations. In the Bridged Endpoint mode, the IP integrates a
+ three-port switch, with two ports connected to the external network and one port
+ connected to an internal endpoint.It also supports GMII/RGMII interfaces for
+ connection to an external PHY, enabling full-duplex operation at 100 Mb/s and
+ 1 Gb/s speeds.
+
+maintainers:
+ - Neeli Srinivas <srinivas.neeli@amd.com>
+
+properties:
+ compatible:
+ enum:
+ - xlnx,tsn-endpoint-ethernet-mac-3.0
+ reg:
+ maxItems: 1
+
+ clocks:
+ minItems: 6
+
+ clock-names:
+ items:
+ - const: gtx
+ - const: gtx90
+ - const: host_rxfifo
+ - const: host_txfifo
+ - const: ref
+ - const: s_axi
+
+ dmas:
+ minItems: 2
+ maxItems: 32
+
+ dma-names:
+ items:
+ pattern: "^[tr]x_chan([0-9]|1[0-5])$"
+ description:
+ Should be "tx_chan0", "tx_chan1" ... "tx_chan15" for DMA Tx channel
+ Should be "rx_chan0", "rx_chan1" ... "rx_chan15" for DMA Rx channel
+ minItems: 2
+ maxItems: 32
+
+ ranges: true
+
+ xlnx,num-priorities:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ minimum: 2
+ maximum: 8
+ description:
+ Number of traffic classes (priorities) configured in the IP.
+ This is an IP configuration parameter that determines the number of
+ priority queues available for QoS scheduling. Traffic classes map to
+ IEEE 802.1Q priority levels (0-7).
+
+ xlnx,tsn-tx-config:
+ $ref: /schemas/types.yaml#/definitions/phandle
+ description:
+ Multiple TX Queue parameters. Phandle to a node that implements
+ the tx-queues-config.
+
+ tx-queues-config:
+ type: object
+ description:
+ TX queue configuration node that maps IP priority queues to
+ DMA TX channels. The TSN IP supports multiple priority queues for QoS
+ scheduling, and each queue can be connected to a specific DMA channel.
+ This mapping defines which DMA TX channel is used to transmit packets
+ for each priority queue. For example, queue0 with xlnx,dma-channel-num
+ set to 5 means priority queue 0 uses tx_chan5 for data transfer.
+
+ patternProperties:
+ "^queue[0-7]$":
+ description:
+ Each subnode represents a priority queue. The xlnx,dma-channel-num
+ property specifies which DMA TX channel (tx_chan0 to tx_chan15)
+ is connected to this queue for transmitting packets.
+ type: object
+ properties:
+ xlnx,dma-channel-num:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ description:
+ DMA TX channel number connected to this priority queue.
+ minimum: 0
+ maximum: 15
+
+ additionalProperties: false
+ additionalProperties: false
+
+ '#address-cells':
+ const: 1
+
+ '#size-cells':
+ const: 1
+
+patternProperties:
+ "^ethernet-mac@":
+ type: object
+ $ref: /schemas/net/ethernet-controller.yaml#
+ required:
+ - reg
+ - phy-mode
+ properties:
+ reg:
+ maxItems: 1
+
+ phy-mode:
+ enum:
+ - gmii
+ - rgmii
+ - rgmii-id
+
+ phy-handle:
+ $ref: /schemas/types.yaml#/definitions/phandle
+
+ mdio:
+ type: object
+ additionalProperties: false
+
+ "^ep-mac@":
+ type: object
+ $ref: /schemas/net/ethernet-controller.yaml#
+ properties:
+ reg:
+ maxItems: 1
+
+ additionalProperties: false
+
+ "^switch@":
+ type: object
+ $ref: /schemas/net/ethernet-switch.yaml#
+ unevaluatedProperties: false
+
+ properties:
+ reg:
+ maxItems: 1
+
+ ethernet-ports:
+ type: object
+ unevaluatedProperties: false
+
+ properties:
+ '#address-cells':
+ const: 1
+ '#size-cells':
+ const: 0
+
+ patternProperties:
+ "^port@[0-2]$":
+ type: object
+ $ref: ethernet-switch-port.yaml#
+ unevaluatedProperties: false
+
+ properties:
+ reg:
+ maximum: 2
+ ethernet:
+ description: Phandle to associated MAC or endpoint node
+ $ref: /schemas/types.yaml#/definitions/phandle
+ required:
+ - reg
+ - ethernet
+ required:
+ - "#address-cells"
+ - "#size-cells"
+ required:
+ - reg
+additionalProperties: false
+
+required:
+ - compatible
+ - reg
+ - clocks
+ - clock-names
+ - dmas
+ - dma-names
+ - xlnx,num-priorities
+ - ranges
+
+examples:
+ - |
+ tsn_ip: tsn@80040000 {
+ compatible = "xlnx,tsn-endpoint-ethernet-mac-3.0";
+ reg = <0x80040000 0x40000>;
+ clocks = <&misc_clk_2>, <&misc_clk_2>, <&misc_clk_1>, <&misc_clk_1>, <&misc_clk_3>, <&misc_clk_0>;
+ clock-names = "gtx", "gtx90", "host_rxfifo", "host_txfifo", "ref", "s_axi";
+ dmas = <&axi_mcdma_0 0>, <&axi_mcdma_0 1>, <&axi_mcdma_0 2>, <&axi_mcdma_0 3>,
+ <&axi_mcdma_0 4>, <&axi_mcdma_0 5>, <&axi_mcdma_0 6>, <&axi_mcdma_0 7>,
+ <&axi_mcdma_0 16>, <&axi_mcdma_0 17>, <&axi_mcdma_0 18>, <&axi_mcdma_0 19>,
+ <&axi_mcdma_0 20>, <&axi_mcdma_0 21>, <&axi_mcdma_0 22>, <&axi_mcdma_0 23>;
+ dma-names = "tx_chan0","tx_chan1","tx_chan2","tx_chan3","tx_chan4","tx_chan5","tx_chan6",
+ "tx_chan7","rx_chan0","rx_chan1","rx_chan2","rx_chan3","rx_chan4","rx_chan5",
+ "rx_chan6","rx_chan7";
+ xlnx,num-priorities = <8>;
+ #address-cells = <1>;
+ #size-cells = <1>;
+ ranges = <0x0 0x80040000 0x40000>;
+ xlnx,tsn-tx-config = <&tsn_tx_config>;
+ tsn_tx_config: tx-queues-config {
+ queue0 {
+ xlnx,dma-channel-num = <0x5>;
+ };
+ queue1 {
+ xlnx,dma-channel-num = <0x4>;
+ };
+ queue2 {
+ xlnx,dma-channel-num = <0x3>;
+ };
+ queue3 {
+ xlnx,dma-channel-num = <0x2>;
+ };
+ queue4 {
+ xlnx,dma-channel-num = <0x1>;
+ };
+ queue5 {
+ xlnx,dma-channel-num = <0x0>;
+ };
+ };
+ // MAC 1 Node
+ mac1: ethernet-mac@0 {
+ reg = <0x0 0x14000>;
+ phy-mode = "rgmii-id";
+ phy-handle = <&phy0>;
+ mdio {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ phy0: ethernet-phy@0 {
+ device_type = "ethernet-phy";
+ reg = <0>;
+ };
+ };
+ };
+
+ // MAC 2 Node
+ mac2: ethernet-mac@20000 {
+ reg = <0x20000 0x14000>;
+ phy-mode = "rgmii-id";
+ phy-handle = <&phy1>;
+ mdio {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ phy1: ethernet-phy@1 {
+ device_type = "ethernet-phy";
+ reg = <1>;
+ };
+ };
+ };
+
+ // Endpoint Node
+ ep_mac: ep-mac@16000 {
+ reg = <0x16000 0xa000>;
+ };
+
+ // Switch Node
+ tsn_switch: switch@38000 {
+ reg = <0x38000 0x8000>;
+
+ ethernet-ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@0 {
+ reg = <0>;
+ ethernet = <&ep_mac>;
+ };
+
+ port@1 {
+ reg = <1>;
+ ethernet = <&mac1>;
+ };
+
+ port@2 {
+ reg = <2>;
+ ethernet = <&mac2>;
+ };
+ };
+ };
+ };
--
2.25.1
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [RFC PATCH 2/8] net: xilinx: tsn: Introduce TSN core driver skeleton
2026-02-19 5:49 [RFC PATCH 0/8] xilinx: tsn: Add TSN Endpoint Ethernet MAC driver support Srinivas Neeli
2026-02-19 5:49 ` [RFC PATCH 1/8] dt-bindings: net: Add TSN Endpoint Ethernet MAC support Srinivas Neeli
@ 2026-02-19 5:49 ` Srinivas Neeli
2026-02-19 7:32 ` Krzysztof Kozlowski
2026-02-19 5:49 ` [RFC PATCH 3/8] net: xilinx: tsn: Add TSN endpoint and MCDMA support Srinivas Neeli
` (7 subsequent siblings)
9 siblings, 1 reply; 27+ messages in thread
From: Srinivas Neeli @ 2026-02-19 5:49 UTC (permalink / raw)
To: andrew+netdev, davem, edumazet, kuba, pabeni, michal.simek, robh,
krzk+dt, conor+dt, richardcochran
Cc: netdev, linux-kernel, devicetree, linux-arm-kernel, git,
srinivas.neeli
Introduce the initial skeleton for the AMD/Xilinx Time Sensitive
Networking (TSN) Ethernet IP driver. Adds the Kconfig entry
(CONFIG_XILINX_TSN), updates the Xilinx Ethernet Makefile,
and provides the base source file focused on device tree
parsing and clock acquisition.
Signed-off-by: Srinivas Neeli <srinivas.neeli@amd.com>
---
drivers/net/ethernet/xilinx/Kconfig | 1 +
drivers/net/ethernet/xilinx/Makefile | 1 +
drivers/net/ethernet/xilinx/tsn/Kconfig | 14 ++
drivers/net/ethernet/xilinx/tsn/Makefile | 2 +
drivers/net/ethernet/xilinx/tsn/xilinx_tsn.h | 60 +++++
.../net/ethernet/xilinx/tsn/xilinx_tsn_main.c | 218 ++++++++++++++++++
6 files changed, 296 insertions(+)
create mode 100644 drivers/net/ethernet/xilinx/tsn/Kconfig
create mode 100644 drivers/net/ethernet/xilinx/tsn/Makefile
create mode 100644 drivers/net/ethernet/xilinx/tsn/xilinx_tsn.h
create mode 100644 drivers/net/ethernet/xilinx/tsn/xilinx_tsn_main.c
diff --git a/drivers/net/ethernet/xilinx/Kconfig b/drivers/net/ethernet/xilinx/Kconfig
index 7502214cc7d5..c6d704c8d3d4 100644
--- a/drivers/net/ethernet/xilinx/Kconfig
+++ b/drivers/net/ethernet/xilinx/Kconfig
@@ -41,4 +41,5 @@ config XILINX_LL_TEMAC
This driver supports the Xilinx 10/100/1000 LocalLink TEMAC
core used in Xilinx Spartan and Virtex FPGAs
+source "drivers/net/ethernet/xilinx/tsn/Kconfig"
endif # NET_VENDOR_XILINX
diff --git a/drivers/net/ethernet/xilinx/Makefile b/drivers/net/ethernet/xilinx/Makefile
index 7d7dc1771423..66dab012650b 100644
--- a/drivers/net/ethernet/xilinx/Makefile
+++ b/drivers/net/ethernet/xilinx/Makefile
@@ -8,3 +8,4 @@ obj-$(CONFIG_XILINX_LL_TEMAC) += ll_temac.o
obj-$(CONFIG_XILINX_EMACLITE) += xilinx_emaclite.o
xilinx_emac-objs := xilinx_axienet_main.o xilinx_axienet_mdio.o
obj-$(CONFIG_XILINX_AXI_EMAC) += xilinx_emac.o
+obj-$(CONFIG_XILINX_TSN) += tsn/
diff --git a/drivers/net/ethernet/xilinx/tsn/Kconfig b/drivers/net/ethernet/xilinx/tsn/Kconfig
new file mode 100644
index 000000000000..53734842700b
--- /dev/null
+++ b/drivers/net/ethernet/xilinx/tsn/Kconfig
@@ -0,0 +1,14 @@
+config XILINX_TSN
+ tristate "Xilinx TSN Ethernet driver"
+ depends on OF && HAS_IOMEM
+ select PHYLIB
+ select NET_DEVLINK
+ select NET_DEV_STATS
+ help
+ This driver supports the Xilinx Time-Sensitive Networking (TSN)
+ Ethernet IP, which includes multiple Ethernet MACs, a TSN switch,
+ and an endpoint block. It provides support for scheduling,
+ traffic shaping, and time synchronization to meet real-time
+ requirements of industrial Ethernet applications.
+
+ If unsure, say N.
diff --git a/drivers/net/ethernet/xilinx/tsn/Makefile b/drivers/net/ethernet/xilinx/tsn/Makefile
new file mode 100644
index 000000000000..420497f2d402
--- /dev/null
+++ b/drivers/net/ethernet/xilinx/tsn/Makefile
@@ -0,0 +1,2 @@
+obj-$(CONFIG_XILINX_TSN) :=xilinx_tsn.o
+xilinx_tsn-objs := xilinx_tsn_main.o
diff --git a/drivers/net/ethernet/xilinx/tsn/xilinx_tsn.h b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn.h
new file mode 100644
index 000000000000..fe613f73044f
--- /dev/null
+++ b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn.h
@@ -0,0 +1,60 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Time Sensitive Networking (TSN) Ethernet MAC driver
+ *
+ * Copyright (C) 2025 Advanced Micro Devices, Inc.
+ */
+
+#ifndef XILINX_TSN_H
+#define XILINX_TSN_H
+
+#include <linux/clk.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/spinlock.h>
+
+#define TSN_NUM_CLOCKS 6
+
+#define TSN_DMA_CH_INVALID GENMASK(7, 0)
+#define TSN_DMA_MAX_TX_CH GENMASK(3, 0)
+#define TSN_MAX_TX_QUEUE 8
+#define TSN_MIN_PRIORITIES 2
+#define TSN_MAX_PRIORITIES 8
+/**
+ * struct tsn_priv - Main TSN private data structure
+ * @pdev: Platform device handle
+ * @dev: Device pointer for this TSN instance
+ * @res: Platform resource information
+ * @regs_start: Start address (physical) of mapped region
+ * @regs: ioremap()'d base pointer
+ * @clks: Bulk clock data for all required clocks
+ * @tx_lock: Spinlock protecting TX rings and related TX state
+ * @rx_lock: Spinlock protecting RX rings and related RX state
+ * @mdio_lock: Mutex placeholder for future MDIO serialization
+ * @num_priorities: Number of priority queues configured
+ * @num_tx_queues: Number of TX DMA queues
+ * @num_rx_queues: Number of RX DMA queues
+ * @tx_dma_chan_map: Logical TX queue index to DMA channel number mapping.
+ */
+struct tsn_priv {
+ struct platform_device *pdev;
+ struct device *dev;
+ struct resource *res;
+ resource_size_t regs_start;
+ void __iomem *regs;
+ struct clk_bulk_data clks[TSN_NUM_CLOCKS];
+ spinlock_t tx_lock; /* Protects TX ring buffers */
+ spinlock_t rx_lock; /* Protects RX ring buffers */
+ struct mutex mdio_lock; /* Serializes MDIO access across all EMACs */
+ u32 num_priorities;
+ u32 num_tx_queues;
+ u32 num_rx_queues;
+ u32 tx_dma_chan_map[TSN_MAX_TX_QUEUE];
+};
+
+#endif /* XILINX_TSN_H */
diff --git a/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_main.c b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_main.c
new file mode 100644
index 000000000000..2a7f5fbc5510
--- /dev/null
+++ b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_main.c
@@ -0,0 +1,218 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/*
+ * Time Sensitive Networking (TSN) Ethernet MAC driver
+ *
+ * Copyright (C) 2025 Advanced Micro Devices, Inc.
+ */
+
+#include "xilinx_tsn.h"
+
+static const char * const tsn_clk_names[TSN_NUM_CLOCKS] = {
+ "gtx",
+ "gtx90",
+ "host_rxfifo",
+ "host_txfifo",
+ "ref",
+ "s_axi",
+};
+
+/*
+ * Helper to parse TX queue config subnode referenced by
+ * xlnx,tsn-tx-config. This version enumerates child nodes in order and
+ * assigns DMA channels sequentially (queue0 == first child, etc.)
+ */
+static int tsn_parse_tx_queue_config(struct device *dev, struct tsn_priv *common,
+ struct device_node *txcfg_np)
+{
+ struct device_node *qnode;
+ unsigned int queue = 0;
+ int ret = 0;
+
+ for_each_available_child_of_node(txcfg_np, qnode) {
+ u32 chan;
+
+ if (queue >= common->num_tx_queues) {
+ dev_err(dev, "tx-config: extra child nodes beyond %u ignored\n",
+ common->num_tx_queues);
+ of_node_put(qnode);
+ return -EINVAL;
+ }
+
+ ret = of_property_read_u32(qnode, "xlnx,dma-channel-num", &chan);
+ if (ret) {
+ dev_err(dev, "tx-config: Q%u missing xlnx,dma-channel-num\n", queue);
+ of_node_put(qnode);
+ return ret;
+ }
+
+ if (chan > TSN_DMA_MAX_TX_CH) {
+ dev_err(dev, "tx-config: Q%u channel %u exceeds max %lu\n",
+ queue, chan, TSN_DMA_MAX_TX_CH);
+ of_node_put(qnode);
+ return -EINVAL;
+ }
+ common->tx_dma_chan_map[queue++] = chan;
+ }
+
+ if (queue != common->num_tx_queues) {
+ dev_err(dev, "tx-config: described %u queues but expected %u\n",
+ queue, common->num_tx_queues);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/**
+ * tsn_parse_device_tree - Parse device tree configuration for TSN device
+ * @pdev: Platform device pointer
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+static int tsn_parse_device_tree(struct platform_device *pdev)
+{
+ struct tsn_priv *common = platform_get_drvdata(pdev);
+ struct device_node *txcfg_np = NULL;
+ struct device *dev = &pdev->dev;
+ int i, ret;
+
+ /* Read number of priorities */
+ ret = of_property_read_u32(dev->of_node, "xlnx,num-priorities", &common->num_priorities);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to get xlnx,num-priorities\n");
+
+ if (common->num_priorities < TSN_MIN_PRIORITIES ||
+ common->num_priorities > TSN_MAX_PRIORITIES)
+ return dev_err_probe(dev, -EINVAL, "Invalid xlnx,num-priorities (%u)\n",
+ common->num_priorities);
+
+ /* Count TX and RX queues from dma-names property */
+ ret = of_property_count_strings(dev->of_node, "dma-names");
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "Failed to get dma-names\n");
+
+ common->num_tx_queues = 0;
+ common->num_rx_queues = 0;
+
+ for (i = 0; i < ret; i++) {
+ const char *dma_name;
+
+ if (of_property_read_string_index(dev->of_node, "dma-names", i, &dma_name))
+ continue;
+
+ if (strncmp(dma_name, "tx_chan", 7) == 0)
+ common->num_tx_queues++;
+ else if (strncmp(dma_name, "rx_chan", 7) == 0)
+ common->num_rx_queues++;
+ }
+
+ if (!common->num_tx_queues || common->num_tx_queues > TSN_MAX_TX_QUEUE)
+ return dev_err_probe(dev, -EINVAL,
+ "Invalid TX queue count (%u, max %u)\n",
+ common->num_tx_queues, TSN_MAX_TX_QUEUE);
+
+ if (!common->num_rx_queues)
+ return dev_err_probe(dev, -EINVAL, "No RX DMA channels found\n");
+
+ /* Setup clock IDs */
+ for (i = 0; i < TSN_NUM_CLOCKS; i++)
+ common->clks[i].id = tsn_clk_names[i];
+
+ /* Get all clocks */
+ ret = devm_clk_bulk_get(dev, TSN_NUM_CLOCKS, common->clks);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to get clocks\n");
+
+ /* Enable clocks */
+ ret = clk_bulk_prepare_enable(TSN_NUM_CLOCKS, common->clks);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to enable clocks\n");
+
+ for (i = 0; i < TSN_MAX_TX_QUEUE; i++)
+ common->tx_dma_chan_map[i] = TSN_DMA_CH_INVALID;
+
+ txcfg_np = of_parse_phandle(dev->of_node, "xlnx,tsn-tx-config", 0);
+ if (txcfg_np) {
+ ret = tsn_parse_tx_queue_config(dev, common, txcfg_np);
+ of_node_put(txcfg_np);
+ if (ret)
+ goto err_disable_clks;
+ }
+
+ return 0;
+
+err_disable_clks:
+ clk_bulk_disable_unprepare(TSN_NUM_CLOCKS, common->clks);
+ return ret;
+}
+
+/**
+ * tsn_ip_probe - Probe TSN IP core device
+ * @pdev: Platform device pointer
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+static int tsn_ip_probe(struct platform_device *pdev)
+{
+ struct tsn_priv *common;
+ int ret;
+
+ common = devm_kzalloc(&pdev->dev, sizeof(*common), GFP_KERNEL);
+ if (!common)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, common);
+ common->pdev = pdev;
+ common->dev = &pdev->dev;
+
+ /* Initialize synchronization primitives */
+ spin_lock_init(&common->tx_lock);
+ spin_lock_init(&common->rx_lock);
+ mutex_init(&common->mdio_lock);
+
+ common->res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!common->res)
+ return -ENODEV;
+ common->regs_start = common->res->start;
+ common->regs = devm_ioremap_resource(&pdev->dev, common->res);
+ if (IS_ERR(common->regs))
+ return PTR_ERR(common->regs);
+
+ ret = tsn_parse_device_tree(pdev);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+/**
+ * tsn_ip_remove - Remove TSN IP core device
+ * @pdev: Platform device pointer
+ */
+static void tsn_ip_remove(struct platform_device *pdev)
+{
+ struct tsn_priv *common = platform_get_drvdata(pdev);
+
+ clk_bulk_disable_unprepare(TSN_NUM_CLOCKS, common->clks);
+}
+
+static const struct of_device_id tsn_of_match[] = {
+ { .compatible = "xlnx,tsn-endpoint-ethernet-mac-3.0", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, tsn_of_match);
+
+static struct platform_driver tsn_driver = {
+ .probe = tsn_ip_probe,
+ .remove = tsn_ip_remove,
+ .driver = {
+ .name = "xilinx-tsn",
+ .of_match_table = tsn_of_match,
+ },
+};
+module_platform_driver(tsn_driver);
+
+MODULE_AUTHOR("Neeli Srinivas <srinivas.neeli@amd.com>");
+MODULE_DESCRIPTION("Time Sensitive Networking (TSN) Ethernet MAC driver");
+MODULE_LICENSE("GPL");
--
2.25.1
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [RFC PATCH 3/8] net: xilinx: tsn: Add TSN endpoint and MCDMA support
2026-02-19 5:49 [RFC PATCH 0/8] xilinx: tsn: Add TSN Endpoint Ethernet MAC driver support Srinivas Neeli
2026-02-19 5:49 ` [RFC PATCH 1/8] dt-bindings: net: Add TSN Endpoint Ethernet MAC support Srinivas Neeli
2026-02-19 5:49 ` [RFC PATCH 2/8] net: xilinx: tsn: Introduce TSN core driver skeleton Srinivas Neeli
@ 2026-02-19 5:49 ` Srinivas Neeli
2026-02-19 5:49 ` [RFC PATCH 4/8] xilinx: tsn: Add Ethernet MAC (EMAC) and MDIO support to the TSN driver Srinivas Neeli
` (6 subsequent siblings)
9 siblings, 0 replies; 27+ messages in thread
From: Srinivas Neeli @ 2026-02-19 5:49 UTC (permalink / raw)
To: andrew+netdev, davem, edumazet, kuba, pabeni, michal.simek, robh,
krzk+dt, conor+dt, richardcochran
Cc: netdev, linux-kernel, devicetree, linux-arm-kernel, git,
srinivas.neeli
Add initial support for the TSN endpoint (EP) network interface, including
basic DMA datapath integration and net_device registration.
Introduces a new driver component that registers an "ep" net_device with
support for multiple TX/RX queues and ethtool statistics. It sets up DMA
rings, manages TX/RX submission and completion callbacks, and provides
helper APIs for accessing endpoint registers.
Signed-off-by: Srinivas Neeli <srinivas.neeli@amd.com>
---
drivers/net/ethernet/xilinx/tsn/Makefile | 2 +-
drivers/net/ethernet/xilinx/tsn/xilinx_tsn.h | 113 ++++
.../net/ethernet/xilinx/tsn/xilinx_tsn_ep.c | 211 +++++++
.../net/ethernet/xilinx/tsn/xilinx_tsn_main.c | 558 ++++++++++++++++++
4 files changed, 883 insertions(+), 1 deletion(-)
create mode 100644 drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c
diff --git a/drivers/net/ethernet/xilinx/tsn/Makefile b/drivers/net/ethernet/xilinx/tsn/Makefile
index 420497f2d402..099526877948 100644
--- a/drivers/net/ethernet/xilinx/tsn/Makefile
+++ b/drivers/net/ethernet/xilinx/tsn/Makefile
@@ -1,2 +1,2 @@
obj-$(CONFIG_XILINX_TSN) :=xilinx_tsn.o
-xilinx_tsn-objs := xilinx_tsn_main.o
+xilinx_tsn-objs := xilinx_tsn_main.o xilinx_tsn_ep.o
diff --git a/drivers/net/ethernet/xilinx/tsn/xilinx_tsn.h b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn.h
index fe613f73044f..054f74b97a38 100644
--- a/drivers/net/ethernet/xilinx/tsn/xilinx_tsn.h
+++ b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn.h
@@ -8,15 +8,29 @@
#ifndef XILINX_TSN_H
#define XILINX_TSN_H
+#include <linux/bitfield.h>
+#include <linux/circ_buf.h>
#include <linux/clk.h>
+#include <linux/dma/xilinx_dma.h>
+#include <linux/dmaengine.h>
+#include <linux/etherdevice.h>
+#include <linux/ethtool.h>
+#include <linux/if_ether.h>
+#include <linux/if_vlan.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_dma.h>
+#include <linux/of_net.h>
+#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/spinlock.h>
+#include <linux/u64_stats_sync.h>
+#include <net/netdev_queues.h>
#define TSN_NUM_CLOCKS 6
@@ -25,6 +39,65 @@
#define TSN_MAX_TX_QUEUE 8
#define TSN_MIN_PRIORITIES 2
#define TSN_MAX_PRIORITIES 8
+/* Descriptors defines for Tx and Rx DMA */
+#define TX_BD_NUM_DEFAULT 64
+#define RX_BD_NUM_DEFAULT 128
+
+#define TSN_MAX_FRAME_SIZE (ETH_DATA_LEN + ETH_HLEN + ETH_FCS_LEN)
+#define TSN_MAX_VLAN_FRAME_SIZE (ETH_DATA_LEN + VLAN_ETH_HLEN + ETH_FCS_LEN)
+
+/* TUSER Input Port ID field definitions (bits [5:4]) */
+#define TSN_TUSER_PORT_ID_MASK GENMASK(5, 4)
+#define TSN_TUSER_PORT_EP 0x0 /* Endpoint Port */
+#define TSN_TUSER_PORT_MAC1 0x1 /* MAC-1 Port */
+#define TSN_TUSER_PORT_MAC2 0x2 /* MAC-2 Port */
+
+/*
+ * struct skbuf_dma_descriptor - skb for each dma descriptor
+ * @sgl: Pointer for sglist.
+ * @desc: Pointer to dma descriptor.
+ * @dma_address: dma address of sglist.
+ * @skb: Pointer to SKB transferred using DMA
+ * @sg_len: number of entries in the sglist.
+ */
+struct skbuf_dma_descriptor {
+ struct scatterlist sgl[MAX_SKB_FRAGS + 1];
+ struct dma_async_tx_descriptor *desc;
+ dma_addr_t dma_address;
+ struct sk_buff *skb;
+ int sg_len;
+};
+
+/**
+ * struct tsn_dma_chan - TSN DMA channel management structure
+ * @skb_ring: Ring buffer of SKB DMA descriptors
+ * @common: Pointer to common TSN private data
+ * @chan: DMA engine channel handle
+ * @ring_head: Head index of the ring buffer
+ * @ring_tail: Tail index of the ring buffer
+ * @is_tx: Flag indicating if this is a TX channel (true) or RX (false)
+ */
+struct tsn_dma_chan {
+ struct skbuf_dma_descriptor **skb_ring;
+ struct tsn_priv *common;
+ struct dma_chan *chan;
+ int ring_head;
+ int ring_tail;
+ bool is_tx;
+};
+
+/*
+ * struct tsn_endpoint - TSN endpoint configuration structure
+ * @ndev: Network device associated with the TSN endpoint
+ * @regs: Virtual address mapping of endpoint register space
+ * @common: Pointer to the main TSN private data structure
+ */
+struct tsn_endpoint {
+ struct net_device *ndev;
+ void __iomem *regs;
+ struct tsn_priv *common;
+};
+
/**
* struct tsn_priv - Main TSN private data structure
* @pdev: Platform device handle
@@ -32,6 +105,7 @@
* @res: Platform resource information
* @regs_start: Start address (physical) of mapped region
* @regs: ioremap()'d base pointer
+ * @ep: Pointer to TSN endpoint structure
* @clks: Bulk clock data for all required clocks
* @tx_lock: Spinlock protecting TX rings and related TX state
* @rx_lock: Spinlock protecting RX rings and related RX state
@@ -40,6 +114,9 @@
* @num_tx_queues: Number of TX DMA queues
* @num_rx_queues: Number of RX DMA queues
* @tx_dma_chan_map: Logical TX queue index to DMA channel number mapping.
+ * @max_frm_size: Maximum frame size supported
+ * @tx_chans: Array of TX DMA channels
+ * @rx_chans: Array of RX DMA channels
*/
struct tsn_priv {
struct platform_device *pdev;
@@ -47,6 +124,7 @@ struct tsn_priv {
struct resource *res;
resource_size_t regs_start;
void __iomem *regs;
+ struct tsn_endpoint *ep;
struct clk_bulk_data clks[TSN_NUM_CLOCKS];
spinlock_t tx_lock; /* Protects TX ring buffers */
spinlock_t rx_lock; /* Protects RX ring buffers */
@@ -55,6 +133,41 @@ struct tsn_priv {
u32 num_tx_queues;
u32 num_rx_queues;
u32 tx_dma_chan_map[TSN_MAX_TX_QUEUE];
+ u32 max_frm_size;
+ struct tsn_dma_chan **tx_chans;
+ struct tsn_dma_chan **rx_chans;
};
+/**
+ * tsn_ndo_set_mac_address - ndo_set_mac_address handler for TSN devices
+ * @ndev: Pointer to the net_device structure
+ * @p: Pointer to sockaddr structure containing MAC address
+ *
+ * This is the common implementation for net_device_ops.ndo_set_mac_address
+ * callback used by both EP and EMAC interfaces.
+ *
+ * Validates the provided MAC address and sets it only if valid.
+ *
+ * Return: 0 on success, -EADDRNOTAVAIL if address is invalid
+ */
+static inline int tsn_ndo_set_mac_address(struct net_device *ndev, void *p)
+{
+ struct sockaddr *addr = p;
+
+ /* Validate address before setting */
+ if (!addr || !is_valid_ether_addr(addr->sa_data)) {
+ netdev_err(ndev, "Invalid MAC address provided\n");
+ return -EADDRNOTAVAIL;
+ }
+
+ eth_hw_addr_set(ndev, addr->sa_data);
+
+ return 0;
+}
+
+netdev_tx_t tsn_start_xmit_dmaengine(struct tsn_priv *common,
+ struct sk_buff *skb,
+ struct net_device *ndev);
+int tsn_ep_init(struct platform_device *pdev);
+void tsn_ep_exit(struct platform_device *pdev);
#endif /* XILINX_TSN_H */
diff --git a/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c
new file mode 100644
index 000000000000..33a388a67d3b
--- /dev/null
+++ b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ep.c
@@ -0,0 +1,211 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "xilinx_tsn.h"
+
+#define DRIVER_NAME "xilinx_tsn_ep"
+#define DRIVER_DESCRIPTION "Xilinx TSN driver"
+#define DRIVER_VERSION "1.0"
+
+/**
+ * ep_iow - Memory mapped TSN endpoint register write
+ * @ep: Pointer to TSN endpoint structure
+ * @off: Address offset from the base address of endpoint registers
+ * @val: Value to be written into the endpoint register
+ *
+ * This function writes the desired value into the corresponding TSN
+ * endpoint register.
+ */
+static inline void ep_iow(struct tsn_endpoint *ep, off_t off, u32 val)
+{
+ iowrite32(val, ep->regs + off);
+}
+
+/**
+ * ep_ior - Memory mapped TSN endpoint register read
+ * @ep: Pointer to TSN endpoint structure
+ * @off: Address offset from the base address of endpoint registers
+ *
+ * This function reads a value from the corresponding TSN endpoint
+ * register.
+ *
+ * Return: Value read from the endpoint register
+ */
+static inline u32 ep_ior(struct tsn_endpoint *ep, u32 off)
+{
+ return ioread32(ep->regs + off);
+}
+
+/**
+ * tsn_ep_get_drvinfo - Get driver information for ethtool
+ * @ndev: Pointer to the net_device structure
+ * @ed: Pointer to ethtool_drvinfo structure
+ *
+ * This function populates driver name and version information
+ * for ethtool driver information display.
+ */
+static void tsn_ep_get_drvinfo(struct net_device *ndev,
+ struct ethtool_drvinfo *ed)
+{
+ strscpy(ed->driver, DRIVER_NAME, sizeof(ed->driver));
+ strscpy(ed->version, DRIVER_VERSION, sizeof(ed->version));
+}
+
+/**
+ * tsn_ep_start_xmit_dmaengine - Transmit packet using DMA engine
+ * @skb: Socket buffer containing packet data
+ * @ndev: Pointer to the net_device structure
+ *
+ * Return: NETDEV_TX_OK on success, NETDEV_TX_BUSY if ring is full
+ *
+ * This function handles packet transmission using DMA engine. It maps
+ * SKB to scatterlist, prepares DMA descriptor, and submits for transmission.
+ */
+static netdev_tx_t tsn_ep_start_xmit(struct sk_buff *skb,
+ struct net_device *ndev)
+{
+ struct tsn_endpoint *ep = netdev_priv(ndev);
+
+ return tsn_start_xmit_dmaengine(ep->common, skb, ndev);
+}
+
+/**
+ * tsn_ep_open - Open TSN endpoint network interface
+ * @ndev: Pointer to the net_device structure
+ *
+ * Return: 0 on success, negative error code on failure
+ *
+ * This function opens the network interface by initializing DMA engine,
+ * setting maximum frame size, and starting all transmit queues.
+ */
+static int tsn_ep_open(struct net_device *ndev)
+{
+ netif_tx_start_all_queues(ndev);
+
+ return 0;
+}
+
+/**
+ * tsn_ep_stop - Stop TSN endpoint network interface
+ * @ndev: Pointer to the net_device structure
+ *
+ * Return: 0 on success
+ *
+ * This function stops the network interface by stopping all transmit
+ * queues and cleaning up DMA engine resources.
+ */
+static int tsn_ep_stop(struct net_device *ndev)
+{
+ netif_tx_stop_all_queues(ndev);
+ netdev_info(ndev, "TSN endpoint stopped\n");
+
+ return 0;
+}
+
+static const struct net_device_ops ep_netdev_ops = {
+ .ndo_open = tsn_ep_open,
+ .ndo_stop = tsn_ep_stop,
+ .ndo_start_xmit = tsn_ep_start_xmit,
+ .ndo_set_mac_address = tsn_ndo_set_mac_address,
+};
+
+static const struct ethtool_ops ep_ethtool_ops = {
+ .get_drvinfo = tsn_ep_get_drvinfo,
+};
+
+/**
+ * tsn_ep_init - Initialize TSN endpoint subsystem
+ * @pdev: Platform device pointer
+ *
+ * Return: 0 on success, negative error code on failure
+ *
+ * This function initializes TSN endpoint by parsing device tree,
+ * allocating network device, configuring DMA channels, setting MAC
+ * address, and registering network interface.
+ */
+int tsn_ep_init(struct platform_device *pdev)
+{
+ struct tsn_priv *common = platform_get_drvdata(pdev);
+ struct device *dev = &pdev->dev;
+ struct device_node *ep_node;
+ struct tsn_endpoint *ep;
+ struct net_device *ndev;
+ u8 mac_addr[ETH_ALEN];
+ struct resource res;
+ int ret;
+
+ ep_node = of_get_child_by_name(dev->of_node, "ep-mac");
+ if (!ep_node)
+ return dev_err_probe(dev, -ENODEV, "missing ep-mac node\n");
+
+ ret = of_address_to_resource(ep_node, 0, &res);
+ if (ret) {
+ of_node_put(ep_node);
+ return dev_err_probe(dev, ret, "failed to get ep resource\n");
+ }
+
+ ndev = alloc_netdev_mqs(sizeof(struct tsn_endpoint), "ep", NET_NAME_UNKNOWN,
+ ether_setup, common->num_tx_queues,
+ common->num_rx_queues);
+ if (!ndev) {
+ of_node_put(ep_node);
+ return dev_err_probe(dev, -ENOMEM, "failed to alloc net_device\n");
+ }
+
+ ndev->netdev_ops = &ep_netdev_ops;
+ ndev->ethtool_ops = &ep_ethtool_ops;
+ ndev->features = NETIF_F_SG;
+ ndev->min_mtu = ETH_ZLEN - ETH_HLEN;
+ ndev->max_mtu = ETH_DATA_LEN;
+ ep = netdev_priv(ndev);
+ memset(ep, 0, sizeof(*ep));
+ ep->ndev = ndev;
+ ep->regs = common->regs + res.start;
+ ep->common = common;
+ common->ep = ep;
+ SET_NETDEV_DEV(ndev, common->dev);
+
+ /* Retrieve the MAC address */
+ ret = of_get_mac_address(ep_node, mac_addr);
+ if (ret == 0 && is_valid_ether_addr(mac_addr))
+ eth_hw_addr_set(ndev, mac_addr);
+
+ of_node_put(ep_node);
+
+ ret = register_netdev(ndev);
+ if (ret) {
+ dev_err(common->dev, "Failed to register net device\n");
+ free_netdev(ndev);
+ common->ep = NULL;
+ return ret;
+ }
+
+ dev_info(common->dev, "TSN endpoint registered with %d TX queues and %d RX queues\n",
+ common->num_tx_queues, common->num_rx_queues);
+
+ return 0;
+}
+
+/**
+ * tsn_ep_exit - Clean up TSN endpoint subsystem
+ * @pdev: Platform device pointer
+ *
+ * This function unregisters network device, frees network device
+ * memory, and cleans up TSN endpoint resources during driver removal.
+ */
+void tsn_ep_exit(struct platform_device *pdev)
+{
+ struct tsn_priv *common = platform_get_drvdata(pdev);
+ struct tsn_endpoint *ep;
+
+ if (!common || !common->ep)
+ return;
+
+ ep = common->ep;
+ if (ep->ndev) {
+ unregister_netdev(ep->ndev);
+ free_netdev(ep->ndev);
+ ep->ndev = NULL;
+ }
+ common->ep = NULL;
+}
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_main.c b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_main.c
index 2a7f5fbc5510..9e674f99d83f 100644
--- a/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_main.c
+++ b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_main.c
@@ -17,6 +17,317 @@ static const char * const tsn_clk_names[TSN_NUM_CLOCKS] = {
"s_axi",
};
+static void tsn_dma_rx_cb(void *data, const struct dmaengine_result *result);
+
+/* Ring accessor helpers */
+static inline struct skbuf_dma_descriptor *tsn_get_rx_desc(struct tsn_dma_chan *xchan, int idx)
+{
+ return xchan->skb_ring[idx];
+}
+
+static inline struct skbuf_dma_descriptor *tsn_get_tx_desc(struct tsn_dma_chan *xchan, int idx)
+{
+ return xchan->skb_ring[idx];
+}
+
+static void tsn_rx_submit_desc(struct tsn_dma_chan *xchan)
+{
+ struct dma_async_tx_descriptor *dma_rx_desc = NULL;
+ struct tsn_priv *common = xchan->common;
+ struct skbuf_dma_descriptor *skbuf_dma;
+ struct sk_buff *skb;
+ dma_addr_t addr;
+
+ scoped_guard(spinlock_irq, &common->rx_lock) {
+ skbuf_dma = tsn_get_rx_desc(xchan, xchan->ring_head);
+ if (!skbuf_dma)
+ return;
+
+ xchan->ring_head = (xchan->ring_head + 1) & (RX_BD_NUM_DEFAULT - 1);
+ }
+
+ skb = dev_alloc_skb(common->max_frm_size);
+ if (!skb)
+ goto rx_submit_err_revert_head;
+
+ sg_init_table(skbuf_dma->sgl, 1);
+ addr = dma_map_single(common->dev, skb->data, common->max_frm_size,
+ DMA_FROM_DEVICE);
+ if (unlikely(dma_mapping_error(common->dev, addr))) {
+ if (net_ratelimit())
+ dev_warn(common->dev, "DMA mapping error on RX submit\n");
+ goto rx_submit_err_free_skb;
+ }
+ sg_dma_address(skbuf_dma->sgl) = addr;
+ sg_dma_len(skbuf_dma->sgl) = common->max_frm_size;
+ dma_rx_desc = dmaengine_prep_slave_sg(xchan->chan, skbuf_dma->sgl,
+ 1, DMA_DEV_TO_MEM,
+ DMA_PREP_INTERRUPT);
+ if (!dma_rx_desc)
+ goto rx_submit_err_unmap_skb;
+ skbuf_dma->skb = skb;
+ skbuf_dma->dma_address = sg_dma_address(skbuf_dma->sgl);
+ skbuf_dma->desc = dma_rx_desc;
+ dma_rx_desc->callback_param = xchan;
+ dma_rx_desc->callback_result = tsn_dma_rx_cb;
+ /* Ensure descriptor is fully written before submission */
+ wmb();
+ dmaengine_submit(dma_rx_desc);
+
+ return;
+
+rx_submit_err_unmap_skb:
+ dma_unmap_single(common->dev, addr, common->max_frm_size, DMA_FROM_DEVICE);
+rx_submit_err_free_skb:
+ dev_kfree_skb(skb);
+rx_submit_err_revert_head:
+ scoped_guard(spinlock_irq, &common->rx_lock) {
+ xchan->ring_head = (xchan->ring_head - 1) & (RX_BD_NUM_DEFAULT - 1);
+ }
+}
+
+/**
+ * tsn_classify_rx_packet - Classify received packet by TUSER metadata
+ * @common: TSN common structure
+ * @tuser: TUSER metadata word from DMA descriptor
+ *
+ * Extract Input Port ID from TUSER bits[5:4] and return corresponding netdev.
+ * Currently only EP is supported; MAC ports will return NULL until implemented.
+ *
+ * Return: net_device pointer on success, NULL if port not available
+ */
+static inline struct net_device *tsn_classify_rx_packet(struct tsn_priv *common, u32 tuser)
+{
+ u32 port_id;
+
+ /* Extract Input Port ID from TUSER bits[5:4] */
+ port_id = FIELD_GET(TSN_TUSER_PORT_ID_MASK, tuser);
+ switch (port_id) {
+ case TSN_TUSER_PORT_EP:
+ if (unlikely(!common->ep)) {
+ dev_err_once(common->dev, "EP not initialized - dropping packets\n");
+ return NULL;
+ }
+ return common->ep->ndev;
+
+ case TSN_TUSER_PORT_MAC1:
+ case TSN_TUSER_PORT_MAC2:
+ /* MAC ports not yet implemented */
+ if (net_ratelimit())
+ dev_warn(common->dev, "RX from MAC port %u not yet supported\n", port_id);
+ return NULL;
+
+ default:
+ /* Invalid port ID */
+ if (net_ratelimit())
+ dev_warn(common->dev, "Invalid TUSER port ID: %u\n", port_id);
+ return NULL;
+ }
+}
+
+/**
+ * tsn_dma_rx_cb - DMA engine callback for RX channel completion
+ * @data: Pointer to the skbuf_dma_descriptor structure
+ * @result: Error reporting through dmaengine_result
+ *
+ * This function is called by dmaengine driver for RX channel to notify
+ * that a packet is received. It processes the received packet, updates
+ * statistics, and submits a new RX descriptor.
+ */
+static void tsn_dma_rx_cb(void *data, const struct dmaengine_result *result)
+{
+ struct skbuf_dma_descriptor *skbuf_dma;
+ size_t meta_len, meta_max_len, rx_len;
+ struct tsn_dma_chan *xchan = data;
+ struct tsn_priv *common = xchan->common;
+ struct net_device *ndev;
+ struct sk_buff *skb;
+ u32 *metadata;
+ u32 tuser;
+
+ scoped_guard(spinlock_irq, &common->rx_lock) {
+ skbuf_dma = tsn_get_rx_desc(xchan, xchan->ring_tail);
+ xchan->ring_tail = (xchan->ring_tail + 1) & (RX_BD_NUM_DEFAULT - 1);
+ skb = skbuf_dma->skb;
+ }
+
+ dma_unmap_single(common->dev, skbuf_dma->dma_address,
+ common->max_frm_size, DMA_FROM_DEVICE);
+ metadata = dmaengine_desc_get_metadata_ptr(skbuf_dma->desc,
+ &meta_len,
+ &meta_max_len);
+ if (IS_ERR_OR_NULL(metadata)) {
+ if (net_ratelimit())
+ dev_warn(common->dev, "Failed to get RX metadata pointer\n");
+ dev_kfree_skb_any(skb);
+ goto submit_new;
+ }
+
+ rx_len = metadata[0];
+ tuser = metadata[1];
+
+ if (rx_len > common->max_frm_size || rx_len < ETH_HLEN) {
+ if (net_ratelimit())
+ dev_warn(common->dev, "Invalid RX length %zu (max=%u, min=%u)\n",
+ rx_len, common->max_frm_size, ETH_HLEN);
+ dev_kfree_skb_any(skb);
+ goto submit_new;
+ }
+
+ ndev = tsn_classify_rx_packet(common, tuser);
+ if (unlikely(!ndev)) {
+ if (net_ratelimit())
+ dev_warn(common->dev, "RX packet from unknown port");
+ ndev->stats.rx_dropped++;
+ dev_kfree_skb_any(skb);
+ goto submit_new;
+ }
+ skb_put(skb, rx_len);
+ skb->dev = ndev;
+ skb->protocol = eth_type_trans(skb, ndev);
+ skb->ip_summed = CHECKSUM_NONE;
+ __netif_rx(skb);
+
+ ndev->stats.rx_packets++;
+ ndev->stats.rx_bytes += rx_len;
+
+submit_new:
+ tsn_rx_submit_desc(xchan);
+ dma_async_issue_pending(xchan->chan);
+}
+
+/**
+ * tsn_dma_tx_cb - DMA engine callback for TX channel completion
+ * @data: Pointer to the tsn_dma_chan structure
+ * @result: Error reporting through dmaengine_result
+ *
+ * This function is called by dmaengine driver for TX channel to notify
+ * that transmission is complete. It updates statistics, unmaps DMA,
+ * frees SKB, and wakes the transmit queue.
+ */
+static void tsn_dma_tx_cb(void *data, const struct dmaengine_result *result)
+{
+ struct tsn_dma_chan *xchan = data;
+ struct tsn_priv *common = xchan->common;
+ struct skbuf_dma_descriptor *skbuf_dma;
+ struct netdev_queue *txq;
+ int len;
+ struct net_device *ndev;
+
+ scoped_guard(spinlock_irq, &common->tx_lock) {
+ skbuf_dma = tsn_get_tx_desc(xchan, xchan->ring_tail);
+ if (!skbuf_dma || !skbuf_dma->skb)
+ return;
+ ndev = skbuf_dma->skb->dev;
+ if (unlikely(!ndev)) {
+ /* Drop silently if SKB lost device association */
+ dev_consume_skb_any(skbuf_dma->skb);
+ xchan->ring_tail = (xchan->ring_tail + 1) & (TX_BD_NUM_DEFAULT - 1);
+ return;
+ }
+ txq = netdev_get_tx_queue(ndev,
+ skb_get_queue_mapping(skbuf_dma->skb));
+ len = skbuf_dma->skb->len;
+ xchan->ring_tail = (xchan->ring_tail + 1) & (TX_BD_NUM_DEFAULT - 1);
+
+ ndev->stats.tx_packets++;
+ ndev->stats.tx_bytes += len;
+ }
+
+ dma_unmap_sg(common->dev, skbuf_dma->sgl, skbuf_dma->sg_len,
+ DMA_TO_DEVICE);
+ dev_consume_skb_any(skbuf_dma->skb);
+ netif_txq_completed_wake(txq, 1, len,
+ CIRC_SPACE(xchan->ring_head, xchan->ring_tail,
+ TX_BD_NUM_DEFAULT), 2);
+}
+
+netdev_tx_t tsn_start_xmit_dmaengine(struct tsn_priv *common,
+ struct sk_buff *skb,
+ struct net_device *ndev)
+{
+ struct dma_async_tx_descriptor *dma_tx_desc = NULL;
+ struct skbuf_dma_descriptor *skbuf_dma;
+ int queue = skb_get_queue_mapping(skb);
+ struct tsn_dma_chan *xchan;
+ struct dma_device *dma_dev;
+ struct netdev_queue *txq;
+ int sg_len, ret;
+ u32 phys_chan;
+
+ if (unlikely(queue >= common->num_tx_queues)) {
+ if (net_ratelimit())
+ netdev_warn(ndev, "Invalid TX queue %d (max %u)\n",
+ queue, common->num_tx_queues);
+ return NETDEV_TX_BUSY;
+ }
+
+ /* Map logical software TX queue index to physical DMA channel index */
+ phys_chan = common->tx_dma_chan_map[queue];
+ if (phys_chan == TSN_DMA_CH_INVALID) {
+ if (net_ratelimit())
+ netdev_warn(ndev, "Logical TX queue %d has invalid DMA mapping\n", queue);
+ return NETDEV_TX_BUSY;
+ }
+
+ xchan = common->tx_chans[phys_chan];
+ dma_dev = xchan->chan->device;
+
+ sg_len = skb_shinfo(skb)->nr_frags + 1;
+ txq = netdev_get_tx_queue(ndev, queue);
+
+ scoped_guard(spinlock_irq, &common->tx_lock) {
+ if (CIRC_SPACE(xchan->ring_head, xchan->ring_tail, TX_BD_NUM_DEFAULT) <= 1) {
+ netif_tx_stop_queue(txq);
+ if (net_ratelimit())
+ netdev_warn(ndev, "TSN TX ring full\n");
+ return NETDEV_TX_BUSY;
+ }
+
+ skbuf_dma = tsn_get_tx_desc(xchan, xchan->ring_head);
+ if (!skbuf_dma)
+ goto xmit_error_drop_skb;
+
+ xchan->ring_head = (xchan->ring_head + 1) & (TX_BD_NUM_DEFAULT - 1);
+ }
+
+ sg_init_table(skbuf_dma->sgl, sg_len);
+ ret = skb_to_sgvec(skb, skbuf_dma->sgl, 0, skb->len);
+ if (ret < 0)
+ goto xmit_error_drop_skb;
+
+ ret = dma_map_sg(common->dev, skbuf_dma->sgl, sg_len, DMA_TO_DEVICE);
+ if (!ret)
+ goto xmit_error_drop_skb;
+
+ dma_tx_desc = dma_dev->device_prep_slave_sg(xchan->chan, skbuf_dma->sgl,
+ sg_len, DMA_MEM_TO_DEV,
+ DMA_PREP_INTERRUPT, NULL);
+ if (!dma_tx_desc)
+ goto xmit_error_unmap_sg;
+
+ skbuf_dma->skb = skb;
+ skbuf_dma->sg_len = sg_len;
+ dma_tx_desc->callback_param = xchan;
+ dma_tx_desc->callback_result = tsn_dma_tx_cb;
+
+ netdev_tx_sent_queue(txq, skb->len);
+ if (CIRC_SPACE(xchan->ring_head, xchan->ring_tail, TX_BD_NUM_DEFAULT) < 2)
+ netif_tx_stop_queue(txq);
+ dmaengine_submit(dma_tx_desc);
+ dma_async_issue_pending(xchan->chan);
+
+ return NETDEV_TX_OK;
+
+xmit_error_unmap_sg:
+ dma_unmap_sg(common->dev, skbuf_dma->sgl, sg_len, DMA_TO_DEVICE);
+xmit_error_drop_skb:
+ dev_kfree_skb(skb);
+ ndev->stats.tx_dropped++;
+
+ return NETDEV_TX_OK;
+}
+
/*
* Helper to parse TX queue config subnode referenced by
* xlnx,tsn-tx-config. This version enumerates child nodes in order and
@@ -147,6 +458,224 @@ static int tsn_parse_device_tree(struct platform_device *pdev)
return ret;
}
+/**
+ * tsn_alloc_dma_chan - Allocate and initialize DMA channel
+ * @common: Pointer to TSN common structure
+ * @name: DMA channel name
+ * @is_tx: True for TX channel, false for RX channel
+ * @ring_size: Size of descriptor ring
+ *
+ * Return: Pointer to allocated TSN DMA channel, ERR_PTR on failure
+ *
+ * This function allocates DMA channel, creates descriptor ring, and
+ * initializes channel structure for packet transmission/reception.
+ */
+static struct tsn_dma_chan *tsn_alloc_dma_chan(struct tsn_priv *common,
+ const char *name, bool is_tx,
+ int ring_size)
+{
+ struct tsn_dma_chan *chan;
+ struct dma_chan *err_chan;
+ int i;
+
+ chan = kzalloc(sizeof(*chan), GFP_KERNEL);
+ if (!chan)
+ return ERR_PTR(-ENOMEM);
+
+ chan->chan = dma_request_chan(common->dev, name);
+ if (IS_ERR(chan->chan)) {
+ err_chan = chan->chan;
+ kfree(chan);
+ return ERR_CAST(err_chan);
+ }
+
+ chan->skb_ring = kcalloc(ring_size, sizeof(*chan->skb_ring), GFP_KERNEL);
+ if (!chan->skb_ring) {
+ err_chan = ERR_PTR(-ENOMEM);
+ dma_release_channel(chan->chan);
+ kfree(chan);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ for (i = 0; i < ring_size; i++) {
+ chan->skb_ring[i] = kzalloc(sizeof(*chan->skb_ring[i]),
+ GFP_KERNEL);
+ if (!chan->skb_ring[i]) {
+ /* Free already allocated descriptors */
+ while (--i >= 0)
+ kfree(chan->skb_ring[i]);
+ kfree(chan->skb_ring);
+ dma_release_channel(chan->chan);
+ kfree(chan);
+ return ERR_PTR(-ENOMEM);
+ }
+ }
+
+ chan->ring_head = 0;
+ chan->ring_tail = 0;
+ chan->is_tx = is_tx;
+ chan->common = common;
+
+ return chan;
+}
+
+/**
+ * tsn_free_dma_chan - Free DMA channel and associated resources
+ * @chan: Pointer to TSN DMA channel structure
+ *
+ * This function releases DMA channel, frees descriptor ring memory,
+ * and cleans up all associated resources.
+ */
+static void tsn_free_dma_chan(struct tsn_dma_chan *chan)
+{
+ int i;
+
+ if (!chan)
+ return;
+
+ if (chan->skb_ring) {
+ for (i = 0; i < (chan->is_tx ? TX_BD_NUM_DEFAULT : RX_BD_NUM_DEFAULT); i++)
+ kfree(chan->skb_ring[i]);
+ kfree(chan->skb_ring);
+ }
+
+ if (chan->chan)
+ dma_release_channel(chan->chan);
+
+ kfree(chan);
+}
+
+/**
+ * tsn_exit_dmaengine - Clean up DMA engine resources
+ * @pdev: Platform device pointer
+ *
+ * This function releases all TX and RX DMA channels and frees
+ * associated memory during driver shutdown.
+ */
+static void tsn_exit_dmaengine(struct platform_device *pdev)
+{
+ struct tsn_priv *common = platform_get_drvdata(pdev);
+ int i;
+
+ /* Free TX channels */
+ if (common->tx_chans) {
+ for (i = 0; i < common->num_tx_queues; i++) {
+ if (common->tx_chans[i])
+ tsn_free_dma_chan(common->tx_chans[i]);
+ }
+ kfree(common->tx_chans);
+ common->tx_chans = NULL;
+ }
+
+ /* Free RX channels */
+ if (common->rx_chans) {
+ for (i = 0; i < common->num_rx_queues; i++) {
+ if (common->rx_chans[i])
+ tsn_free_dma_chan(common->rx_chans[i]);
+ }
+ kfree(common->rx_chans);
+ common->rx_chans = NULL;
+ }
+}
+
+/**
+ * tsn_init_dmaengine - Initialize DMA engine for TSN endpoint
+ * @pdev: Platform device pointer
+ *
+ * Return: 0 on success, negative error code on failure
+ *
+ * This function allocates TX/RX DMA channels, creates descriptor rings,
+ * and submits initial RX descriptors for packet reception.
+ */
+static int tsn_init_dmaengine(struct platform_device *pdev)
+{
+ struct tsn_priv *common = platform_get_drvdata(pdev);
+ int tx_ring_allocated = 0, rx_ring_allocated = 0;
+ int i, j, ret = 0;
+
+ common->tx_chans = kcalloc(common->num_tx_queues,
+ sizeof(*common->tx_chans),
+ GFP_KERNEL);
+ if (!common->tx_chans)
+ return -ENOMEM;
+
+ common->rx_chans = kcalloc(common->num_rx_queues,
+ sizeof(*common->rx_chans),
+ GFP_KERNEL);
+ if (!common->rx_chans) {
+ ret = -ENOMEM;
+ goto err_free_tx;
+ }
+
+ // Allocate TX channels
+ for (i = 0; i < common->num_tx_queues; i++) {
+ char name[16];
+
+ snprintf(name, sizeof(name), "tx_chan%d", i);
+ common->tx_chans[i] = tsn_alloc_dma_chan(common, name, true, TX_BD_NUM_DEFAULT);
+ if (IS_ERR(common->tx_chans[i])) {
+ ret = PTR_ERR(common->tx_chans[i]);
+ goto err_free_tx_chans;
+ }
+ tx_ring_allocated++;
+ }
+
+ // Allocate RX channels
+ for (i = 0; i < common->num_rx_queues; i++) {
+ char name[16];
+
+ snprintf(name, sizeof(name), "rx_chan%d", i);
+ common->rx_chans[i] = tsn_alloc_dma_chan(common, name, false, RX_BD_NUM_DEFAULT);
+ if (IS_ERR(common->rx_chans[i])) {
+ ret = PTR_ERR(common->rx_chans[i]);
+ goto err_free_rx_chans;
+ }
+ rx_ring_allocated++;
+ }
+
+ // Submit initial RX descriptors
+ for (i = 0; i < common->num_rx_queues; i++) {
+ for (j = 0; j < RX_BD_NUM_DEFAULT; j++)
+ tsn_rx_submit_desc(common->rx_chans[i]);
+ dma_async_issue_pending(common->rx_chans[i]->chan);
+ }
+
+ return 0;
+
+err_free_rx_chans:
+ while (--rx_ring_allocated >= 0)
+ tsn_free_dma_chan(common->rx_chans[rx_ring_allocated]);
+err_free_tx_chans:
+ while (--tx_ring_allocated >= 0)
+ tsn_free_dma_chan(common->tx_chans[tx_ring_allocated]);
+ kfree(common->rx_chans);
+err_free_tx:
+ kfree(common->tx_chans);
+ return ret;
+}
+
+static int tsn_reset_dma_controller(struct tsn_priv *common)
+{
+ struct xilinx_vdma_config cfg = { .reset = 1 };
+ struct dma_chan *tx_chan0;
+ int ret;
+
+ tx_chan0 = dma_request_chan(common->dev, "tx_chan0");
+ if (IS_ERR(tx_chan0))
+ return dev_err_probe(common->dev, PTR_ERR(tx_chan0),
+ "Failed to request tx_chan0 for reset\n");
+
+ ret = xilinx_vdma_channel_set_config(tx_chan0, &cfg);
+ dma_release_channel(tx_chan0);
+
+ if (ret < 0)
+ return dev_err_probe(common->dev, ret,
+ "Failed to reset DMA controller\n");
+
+ dev_info(common->dev, "DMA controller reset successful\n");
+ return 0;
+}
+
/**
* tsn_ip_probe - Probe TSN IP core device
* @pdev: Platform device pointer
@@ -183,7 +712,32 @@ static int tsn_ip_probe(struct platform_device *pdev)
if (ret)
return ret;
+ common->max_frm_size = TSN_MAX_VLAN_FRAME_SIZE;
+
+ /* Reset DMA controller BEFORE any channel allocation */
+ ret = tsn_reset_dma_controller(common);
+ if (ret)
+ goto free_clk;
+
+ /* Initialize DMA engine - allocate channels */
+ ret = tsn_init_dmaengine(pdev);
+ if (ret) {
+ dev_err(common->dev, "Failed to initialize DMA engine: %d\n", ret);
+ goto free_clk;
+ }
+
+ /* Initialize EP - now safe to register because DMA is ready */
+ ret = tsn_ep_init(pdev);
+ if (ret)
+ goto exit_dma;
+
return 0;
+
+exit_dma:
+ tsn_exit_dmaengine(pdev);
+free_clk:
+ clk_bulk_disable_unprepare(TSN_NUM_CLOCKS, common->clks);
+ return ret;
}
/**
@@ -194,6 +748,10 @@ static void tsn_ip_remove(struct platform_device *pdev)
{
struct tsn_priv *common = platform_get_drvdata(pdev);
+ /* Tear down DMA channels and endpoint */
+ if (common->ep)
+ tsn_ep_exit(pdev);
+ tsn_exit_dmaengine(pdev);
clk_bulk_disable_unprepare(TSN_NUM_CLOCKS, common->clks);
}
--
2.25.1
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [RFC PATCH 4/8] xilinx: tsn: Add Ethernet MAC (EMAC) and MDIO support to the TSN driver
2026-02-19 5:49 [RFC PATCH 0/8] xilinx: tsn: Add TSN Endpoint Ethernet MAC driver support Srinivas Neeli
` (2 preceding siblings ...)
2026-02-19 5:49 ` [RFC PATCH 3/8] net: xilinx: tsn: Add TSN endpoint and MCDMA support Srinivas Neeli
@ 2026-02-19 5:49 ` Srinivas Neeli
2026-02-19 17:05 ` Andrew Lunn
2026-02-20 15:12 ` Andrew Lunn
2026-02-19 5:49 ` [RFC PATCH 5/8] net: xilinx: tsn: Add TSN switch support with port state and frame filter control Srinivas Neeli
` (5 subsequent siblings)
9 siblings, 2 replies; 27+ messages in thread
From: Srinivas Neeli @ 2026-02-19 5:49 UTC (permalink / raw)
To: andrew+netdev, davem, edumazet, kuba, pabeni, michal.simek, robh,
krzk+dt, conor+dt, richardcochran
Cc: netdev, linux-kernel, devicetree, linux-arm-kernel, git,
srinivas.neeli
Introduce support for the Ethernet MAC (EMAC) and MDIO controller used by
the TSN driver.
This patch adds:
- EMAC initialization, reset handling, and register access helpers
- MAC address configuration support
- MDIO read/write functions for PHY register access
- Basic PHY detection and link status handling
- Error handling and resource cleanup during initialization failures
- Build system updates to compile the new EMAC/MDIO support
Signed-off-by: Srinivas Neeli <srinivas.neeli@amd.com>
---
drivers/net/ethernet/xilinx/tsn/Makefile | 2 +-
drivers/net/ethernet/xilinx/tsn/xilinx_tsn.h | 124 +++++++
.../net/ethernet/xilinx/tsn/xilinx_tsn_emac.c | 326 ++++++++++++++++++
.../net/ethernet/xilinx/tsn/xilinx_tsn_main.c | 19 +-
.../net/ethernet/xilinx/tsn/xilinx_tsn_mdio.c | 308 +++++++++++++++++
5 files changed, 774 insertions(+), 5 deletions(-)
create mode 100644 drivers/net/ethernet/xilinx/tsn/xilinx_tsn_emac.c
create mode 100644 drivers/net/ethernet/xilinx/tsn/xilinx_tsn_mdio.c
diff --git a/drivers/net/ethernet/xilinx/tsn/Makefile b/drivers/net/ethernet/xilinx/tsn/Makefile
index 099526877948..5eb6dde67061 100644
--- a/drivers/net/ethernet/xilinx/tsn/Makefile
+++ b/drivers/net/ethernet/xilinx/tsn/Makefile
@@ -1,2 +1,2 @@
obj-$(CONFIG_XILINX_TSN) :=xilinx_tsn.o
-xilinx_tsn-objs := xilinx_tsn_main.o xilinx_tsn_ep.o
+xilinx_tsn-objs := xilinx_tsn_main.o xilinx_tsn_ep.o xilinx_tsn_emac.o xilinx_tsn_mdio.o
diff --git a/drivers/net/ethernet/xilinx/tsn/xilinx_tsn.h b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn.h
index 054f74b97a38..c8435c09ed2c 100644
--- a/drivers/net/ethernet/xilinx/tsn/xilinx_tsn.h
+++ b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn.h
@@ -25,6 +25,7 @@
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_dma.h>
+#include <linux/of_mdio.h>
#include <linux/of_net.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
@@ -52,6 +53,91 @@
#define TSN_TUSER_PORT_MAC1 0x1 /* MAC-1 Port */
#define TSN_TUSER_PORT_MAC2 0x2 /* MAC-2 Port */
+/* TSN MAC Registers */
+#define TSN_RAF_OFFSET 0x00000000 /* Reset and Address filter */
+#define TSN_STATS_OFFSET 0x00000200 /* Statistics counters */
+#define TSN_RCW0_OFFSET 0x00000400 /* Rx Configuration Word 0 */
+#define TSN_RCW1_OFFSET 0x00000404 /* Rx Configuration Word 1 */
+#define TSN_TC_OFFSET 0x00000408 /* Tx Configuration */
+#define TSN_FCC_OFFSET 0x0000040C /* Flow Control Configuration */
+#define TSN_EMMC_OFFSET 0x00000410 /* MAC speed configuration */
+#define TSN_PHYC_OFFSET 0x00000414 /* RX Max Frame Configuration */
+#define TSN_ID_OFFSET 0x000004F8 /* Identification register */
+#define TSN_ABILITY_OFFSET 0x000004FC /* Ability Register offset */
+#define TSN_MDIO_MC_OFFSET 0x00000500 /* MDIO Setup */
+#define TSN_MDIO_MCR_OFFSET 0x00000504 /* MDIO Control */
+#define TSN_MDIO_MWD_OFFSET 0x00000508 /* MDIO Write Data */
+#define TSN_MDIO_MRD_OFFSET 0x0000050C /* MDIO Read Data */
+
+/* Bit masks for TSN Ethernet MDIO interface MC register */
+#define TSN_MDIO_MC_MDIOEN BIT(6) /* MII management enable */
+#define TSN_MDIO_MC_CLOCK_DIVIDE_MAX 0x3F /* Maximum MDIO divisor */
+
+/* Bit masks for TSN Ethernet MDIO interface MCR register */
+#define TSN_MDIO_MCR_PHYAD_SHIFT 24 /* Phy Address Shift */
+#define TSN_MDIO_MCR_PHYAD_MASK GENMASK(28, 24) /* Phy Address Mask */
+#define TSN_MDIO_MCR_REGAD_SHIFT 16 /* Reg Address Shift */
+#define TSN_MDIO_MCR_REGAD_MASK GENMASK(20, 16) /* Reg Address Mask */
+#define TSN_MDIO_MCR_OP_SHIFT 14 /* Operation Code Shift */
+#define TSN_MDIO_MCR_OP_MASK GENMASK(15, 14) /* Operation Code Mask */
+#define TSN_MDIO_MCR_OP_READ BIT(15) /* Op Code Read */
+#define TSN_MDIO_MCR_OP_WRITE BIT(14) /* Op Code Write */
+#define TSN_MDIO_MCR_INITIATE BIT(11) /* Initiate MDIO transaction */
+#define TSN_MDIO_MCR_READY BIT(7) /* MDIO Ready */
+
+/* Bit masks for TSN Ethernet MDIO Write Data Register */
+#define TSN_MDIO_MWD_SHIFT 0 /* Write Data Shift */
+#define TSN_MDIO_MWD_MASK GENMASK(15, 0) /* Write Data Mask */
+
+/* Bit masks for TSN Ethernet MDIO Read Data Register */
+#define TSN_MDIO_MRD_SHIFT 0 /* Read Data Shift */
+#define TSN_MDIO_MRD_MASK GENMASK(15, 0) /* Read Data Mask */
+
+/* Bit masks for Ethernet UAW1 register */
+/* Station address bits [47:32]; Station address
+ * bits [31:0] are stored in register UAW0
+ */
+#define TSN_UAW1_UNICASTADDR_MASK GENMASK(15, 0)
+
+/* Bit masks for TSN Ethernet EMMC register */
+#define TSN_EMMC_LINKSPEED_SHIFT 30 /* Link speed shift */
+#define TSN_EMMC_LINKSPEED_MASK GENMASK(31, 30) /* Link speed mask */
+#define TSN_EMMC_LINKSPEED_10 0x0 /* 10 Mbit */
+#define TSN_EMMC_LINKSPEED_100 BIT(30) /* 100 Mbit */
+#define TSN_EMMC_LINKSPEED_1000 BIT(31) /* 1000 Mbit */
+
+#define TSN_MAX_EMAC_NO 2
+
+/*
+ * struct tsn_emac - TSN Ethernet MAC configuration structure
+ * @ndev: Network device associated with this EMAC instance
+ * @common: Pointer to the main TSN private data structure
+ * @phy_node: Device tree node for the connected PHY device
+ * @phy_mode: PHY interface mode (RGMII, SGMII, etc.)
+ * @phy_flags: PHY-specific configuration flags
+ * @regs: Virtual address mapping of EMAC register space
+ * @regs_start: Physical start address of EMAC register space
+ * @mii_bus: MDIO bus controller for PHY management
+ * @last_link: Previous link state for change detection
+ * @mii_clk_div: MDIO clock divider value
+ * @emac_num: EMAC instance number (1 or 2)
+ * @irq: Interrupt number for this EMAC
+ */
+struct tsn_emac {
+ struct net_device *ndev;
+ struct tsn_priv *common;
+ struct device_node *phy_node;
+ phy_interface_t phy_mode;
+ u32 phy_flags;
+ void __iomem *regs;
+ resource_size_t regs_start;
+ struct mii_bus *mii_bus;
+ u32 last_link;
+ u8 mii_clk_div;
+ int emac_num;
+ int irq;
+};
+
/*
* struct skbuf_dma_descriptor - skb for each dma descriptor
* @sgl: Pointer for sglist.
@@ -106,6 +192,7 @@ struct tsn_endpoint {
* @regs_start: Start address (physical) of mapped region
* @regs: ioremap()'d base pointer
* @ep: Pointer to TSN endpoint structure
+ * @emacs: Array of EMAC instances (up to 2)
* @clks: Bulk clock data for all required clocks
* @tx_lock: Spinlock protecting TX rings and related TX state
* @rx_lock: Spinlock protecting RX rings and related RX state
@@ -117,6 +204,7 @@ struct tsn_endpoint {
* @max_frm_size: Maximum frame size supported
* @tx_chans: Array of TX DMA channels
* @rx_chans: Array of RX DMA channels
+ * @num_emacs: Number of EMAC instances
*/
struct tsn_priv {
struct platform_device *pdev;
@@ -125,6 +213,7 @@ struct tsn_priv {
resource_size_t regs_start;
void __iomem *regs;
struct tsn_endpoint *ep;
+ struct tsn_emac *emacs[TSN_MAX_EMAC_NO];
struct clk_bulk_data clks[TSN_NUM_CLOCKS];
spinlock_t tx_lock; /* Protects TX ring buffers */
spinlock_t rx_lock; /* Protects RX ring buffers */
@@ -136,6 +225,7 @@ struct tsn_priv {
u32 max_frm_size;
struct tsn_dma_chan **tx_chans;
struct tsn_dma_chan **rx_chans;
+ u32 num_emacs;
};
/**
@@ -168,6 +258,40 @@ static inline int tsn_ndo_set_mac_address(struct net_device *ndev, void *p)
netdev_tx_t tsn_start_xmit_dmaengine(struct tsn_priv *common,
struct sk_buff *skb,
struct net_device *ndev);
+
+/**
+ * emac_iow - Memory mapped TSN EMAC register write
+ * @emac: Pointer to TSN EMAC structure
+ * @off: Address offset from the base address of EMAC registers
+ * @val: Value to be written into the EMAC register
+ *
+ * This function writes the desired value into the corresponding TSN
+ * EMAC register.
+ */
+static inline void emac_iow(struct tsn_emac *emac, off_t off, u32 val)
+{
+ iowrite32(val, emac->regs + off);
+}
+
+/**
+ * emac_ior - Memory mapped TSN EMAC register read
+ * @emac: Pointer to TSN EMAC structure
+ * @off: Address offset from the base address of EMAC registers
+ *
+ * This function reads a value from the corresponding TSN EMAC
+ * register.
+ *
+ * Return: Value read from the EMAC register
+ */
+static inline u32 emac_ior(struct tsn_emac *emac, u32 off)
+{
+ return ioread32(emac->regs + off);
+}
+
int tsn_ep_init(struct platform_device *pdev);
void tsn_ep_exit(struct platform_device *pdev);
+int tsn_emac_init(struct platform_device *pdev);
+void tsn_emac_exit(struct platform_device *pdev);
+int tsn_mdio_setup(struct tsn_emac *emac, struct device_node *mac_np);
+void tsn_mdio_teardown(struct tsn_emac *emac);
#endif /* XILINX_TSN_H */
diff --git a/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_emac.c b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_emac.c
new file mode 100644
index 000000000000..26a533e313a2
--- /dev/null
+++ b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_emac.c
@@ -0,0 +1,326 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "xilinx_tsn.h"
+
+#define DRIVER_NAME "xilinx_tsn_emac"
+#define DRIVER_DESCRIPTION "Xilinx TSN driver"
+#define DRIVER_VERSION "1.0"
+
+/**
+ * tsn_adjust_link_tsn - Adjust link parameters
+ * @ndev: Pointer to the net_device structure
+ *
+ * This function is called when the PHY link state changes. It configures
+ * the EMAC link speed register based on the current PHY settings and
+ * updates link status information.
+ */
+static void tsn_adjust_link_tsn(struct net_device *ndev)
+{
+ struct tsn_emac *emac = netdev_priv(ndev);
+ struct phy_device *phy = ndev->phydev;
+ u32 emmc_reg;
+
+ if (!phy || emac->last_link == phy->link)
+ return;
+
+ if (phy->link) {
+ emmc_reg = emac_ior(emac, TSN_EMMC_OFFSET);
+ emmc_reg &= ~TSN_EMMC_LINKSPEED_MASK;
+
+ switch (phy->speed) {
+ case SPEED_1000:
+ emmc_reg |= TSN_EMMC_LINKSPEED_1000;
+ break;
+ case SPEED_100:
+ emmc_reg |= TSN_EMMC_LINKSPEED_100;
+ break;
+ default:
+ dev_warn(&ndev->dev, "Unsupported speed: %d\n", phy->speed);
+ break;
+ }
+
+ emac_iow(emac, TSN_EMMC_OFFSET, emmc_reg);
+ dev_info(&ndev->dev, "Link up: %d Mbps, %s duplex\n",
+ phy->speed, phy->duplex ? "full" : "half");
+ } else {
+ dev_info(&ndev->dev, "Link down\n");
+ }
+
+ emac->last_link = phy->link;
+}
+
+/**
+ * emac_open - Open the network interface
+ * @ndev: Pointer to the net_device structure
+ *
+ * This function is called when the network interface is brought up.
+ * It connects to the PHY device and starts the PHY if available.
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+static int emac_open(struct net_device *ndev)
+{
+ struct tsn_emac *emac = netdev_priv(ndev);
+ struct phy_device *phydev = NULL;
+
+ if (emac->phy_node) {
+ phydev = of_phy_connect(emac->ndev, emac->phy_node,
+ tsn_adjust_link_tsn,
+ emac->phy_flags,
+ emac->phy_mode);
+ if (!phydev)
+ dev_err(emac->common->dev, "of_phy_connect() failed\n");
+ else
+ phy_start(phydev);
+ }
+
+ return 0;
+}
+
+/**
+ * emac_stop - Stop the network interface
+ * @ndev: Pointer to the net_device structure
+ *
+ * This function is called when the network interface is brought down.
+ * It disconnects the PHY device to stop link monitoring.
+ *
+ * Return: 0 on success
+ */
+static int emac_stop(struct net_device *ndev)
+{
+ if (ndev->phydev)
+ phy_disconnect(ndev->phydev);
+
+ return 0;
+}
+
+/**
+ * emac_validate_addr - Validate the MAC address
+ * @ndev: Pointer to the net_device structure
+ *
+ * This function validates the current MAC address of the device.
+ *
+ * Return: 0 if address is valid, negative error code otherwise
+ */
+static int emac_validate_addr(struct net_device *ndev)
+{
+ return eth_validate_addr(ndev);
+}
+
+/**
+ * emac_start_xmit - Transmit packet handler
+ * @skb: Socket buffer containing the packet
+ * @ndev: Pointer to the net_device structure
+ *
+ * This function handles packet transmission for EMAC interfaces.
+ * Currently drops packets and updates statistics as EMAC is not
+ * configured for actual transmission.
+ *
+ * Return: NETDEV_TX_OK always
+ */
+static netdev_tx_t emac_start_xmit(struct sk_buff *skb, struct net_device *ndev)
+{
+ struct tsn_emac *emac = netdev_priv(ndev);
+
+ return tsn_start_xmit_dmaengine(emac->common, skb, ndev);
+}
+
+static const struct net_device_ops emac_netdev_ops = {
+ .ndo_open = emac_open,
+ .ndo_stop = emac_stop,
+ .ndo_start_xmit = emac_start_xmit,
+ .ndo_set_mac_address = tsn_ndo_set_mac_address,
+ .ndo_validate_addr = emac_validate_addr,
+};
+
+/**
+ * emac_get_drvinfo - Get various TSN Ethernet driver information.
+ * @ndev: Pointer to net_device structure
+ * @ed: Pointer to ethtool_drvinfo structure
+ *
+ * This implements ethtool command for getting the driver information.
+ * Issue "ethtool -i ethX" under linux prompt to execute this function.
+ */
+static void emac_get_drvinfo(struct net_device *ndev,
+ struct ethtool_drvinfo *ed)
+{
+ strscpy(ed->driver, DRIVER_NAME, sizeof(ed->driver));
+ strscpy(ed->version, DRIVER_VERSION, sizeof(ed->version));
+}
+
+static const struct ethtool_ops emac_ethtool_ops = {
+ .get_drvinfo = emac_get_drvinfo,
+ .get_link = ethtool_op_get_link,
+ .get_link_ksettings = phy_ethtool_get_link_ksettings,
+ .set_link_ksettings = phy_ethtool_set_link_ksettings,
+};
+
+/**
+ * tsn_emac_init - Initialize TSN EMAC interfaces
+ * @pdev: Platform device pointer
+ *
+ * This function initializes all EMAC interfaces found in the device tree.
+ * For each EMAC, it allocates a network device, maps register regions,
+ * sets up PHY connections, configures MDIO bus, and registers the
+ * network interface with the kernel.
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+int tsn_emac_init(struct platform_device *pdev)
+{
+ struct tsn_priv *common = platform_get_drvdata(pdev);
+ struct device *dev = &pdev->dev;
+ struct device_node *emac_np;
+ int ret, array_idx = 0;
+
+ for_each_child_of_node(dev->of_node, emac_np) {
+ struct net_device *ndev;
+ struct tsn_emac *emac;
+ u8 mac_addr[ETH_ALEN];
+ struct resource res;
+ u32 mac_id = 0;
+
+ if (!of_node_name_eq(emac_np, "ethernet-mac"))
+ continue;
+
+ ret = of_property_read_u32(emac_np, "xlnx,mac-id", &mac_id);
+ if (ret) {
+ dev_err(dev, "Missing mandatory property 'xlnx,mac-id' for EMAC %d\n",
+ array_idx + 1);
+ of_node_put(emac_np);
+ goto err_cleanup_all;
+ }
+
+ ndev = alloc_etherdev(sizeof(*emac));
+ if (!ndev) {
+ ret = -ENOMEM;
+ of_node_put(emac_np);
+ goto err_cleanup_all;
+ }
+
+ ret = of_address_to_resource(emac_np, 0, &res);
+ if (ret) {
+ dev_err_probe(dev, ret, "failed to get emac resource\n");
+ goto err_free_ndev_put_node;
+ }
+
+ emac = netdev_priv(ndev);
+ memset(emac, 0, sizeof(*emac));
+ emac->ndev = ndev;
+ emac->common = common;
+ emac->regs_start = common->regs_start + res.start;
+ emac->regs = common->regs + res.start;
+ emac->emac_num = mac_id;
+ /* basic netdev config */
+ ndev->netdev_ops = &emac_netdev_ops;
+ ndev->ethtool_ops = &emac_ethtool_ops;
+ ndev->min_mtu = ETH_ZLEN - ETH_HLEN;
+ ndev->max_mtu = ETH_DATA_LEN;
+ SET_NETDEV_DEV(ndev, dev);
+
+ /* Retrieve the MAC address */
+ ret = of_get_mac_address(emac_np, mac_addr);
+ if (ret == 0 && is_valid_ether_addr(mac_addr))
+ eth_hw_addr_set(ndev, mac_addr);
+
+ emac->phy_node = of_parse_phandle(emac_np, "phy-handle", 0);
+ if (!emac->phy_node) {
+ dev_err(&pdev->dev, "Failed to get 'phy-handle' from device tree\n");
+
+ } else {
+ ret = tsn_mdio_setup(emac, emac_np);
+ if (ret) {
+ dev_warn(&pdev->dev, "error registering MDIO bus for EMAC %d: %d\n",
+ mac_id, ret);
+ goto err_put_phy_node;
+ }
+ }
+
+ ret = register_netdev(ndev);
+ if (ret) {
+ dev_err(dev, "Failed to register net device for MAC %d\n", mac_id);
+ goto err_teardown_mdio;
+ }
+
+ common->emacs[array_idx] = emac;
+ array_idx++;
+ common->num_emacs = array_idx;
+ continue;
+
+err_teardown_mdio:
+ if (emac->phy_node)
+ tsn_mdio_teardown(emac);
+err_put_phy_node:
+ if (emac->phy_node)
+ of_node_put(emac->phy_node);
+err_free_ndev_put_node:
+ free_netdev(ndev);
+ of_node_put(emac_np);
+ dev_warn(dev, "EMAC %d initialization failed, rolling back\n", mac_id);
+ goto err_cleanup_all;
+ }
+
+ if (array_idx == 0)
+ return -ENODEV;
+
+ return 0;
+
+err_cleanup_all:
+ /* Cleanup all initialized EMACs in reverse order */
+ while (array_idx > 0) {
+ struct tsn_emac *old = common->emacs[--array_idx];
+
+ if (!old)
+ continue;
+
+ dev_info(dev, "Cleaning up MAC %u (array[%d])\n", old->emac_num, array_idx);
+
+ unregister_netdev(old->ndev);
+
+ if (old->phy_node) {
+ tsn_mdio_teardown(old);
+ of_node_put(old->phy_node);
+ }
+
+ free_netdev(old->ndev);
+ common->emacs[array_idx] = NULL;
+ }
+
+ common->num_emacs = 0;
+
+ return ret;
+}
+
+/**
+ * tsn_emac_exit - Cleanup TSN EMAC interfaces
+ * @pdev: Platform device pointer
+ *
+ * This function performs cleanup for all initialized EMAC interfaces.
+ * It unregisters network devices, tears down MDIO buses, releases
+ * PHY connections, and frees allocated memory for each EMAC instance.
+ */
+void tsn_emac_exit(struct platform_device *pdev)
+{
+ struct tsn_priv *common = platform_get_drvdata(pdev);
+ struct device *dev = &pdev->dev;
+ int i;
+
+ /* Cleanup only the EMACs that were actually initialized */
+ for (i = 0; i < common->num_emacs; i++) {
+ struct tsn_emac *emac = common->emacs[i];
+
+ if (!emac)
+ continue;
+
+ dev_info(dev, "Cleaning up MAC %u (array[%d])\n", emac->emac_num, i);
+
+ unregister_netdev(emac->ndev);
+ if (emac->phy_node) {
+ tsn_mdio_teardown(emac);
+ of_node_put(emac->phy_node);
+ }
+ free_netdev(emac->ndev);
+ common->emacs[i] = NULL;
+ }
+
+ common->num_emacs = 0;
+}
diff --git a/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_main.c b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_main.c
index 9e674f99d83f..7cb07e330f57 100644
--- a/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_main.c
+++ b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_main.c
@@ -92,13 +92,14 @@ static void tsn_rx_submit_desc(struct tsn_dma_chan *xchan)
* @tuser: TUSER metadata word from DMA descriptor
*
* Extract Input Port ID from TUSER bits[5:4] and return corresponding netdev.
- * Currently only EP is supported; MAC ports will return NULL until implemented.
+ * Supports EP (endpoint) and MAC1/MAC2 (EMAC) ports.
*
* Return: net_device pointer on success, NULL if port not available
*/
static inline struct net_device *tsn_classify_rx_packet(struct tsn_priv *common, u32 tuser)
{
u32 port_id;
+ int i;
/* Extract Input Port ID from TUSER bits[5:4] */
port_id = FIELD_GET(TSN_TUSER_PORT_ID_MASK, tuser);
@@ -112,9 +113,13 @@ static inline struct net_device *tsn_classify_rx_packet(struct tsn_priv *common,
case TSN_TUSER_PORT_MAC1:
case TSN_TUSER_PORT_MAC2:
- /* MAC ports not yet implemented */
+ for (i = 0; i < common->num_emacs; i++) {
+ if (common->emacs[i] &&
+ common->emacs[i]->emac_num == port_id)
+ return common->emacs[i]->ndev;
+ }
if (net_ratelimit())
- dev_warn(common->dev, "RX from MAC port %u not yet supported\n", port_id);
+ dev_warn(common->dev, "RX from MAC port %u not found\n", port_id);
return NULL;
default:
@@ -726,13 +731,18 @@ static int tsn_ip_probe(struct platform_device *pdev)
goto free_clk;
}
- /* Initialize EP - now safe to register because DMA is ready */
ret = tsn_ep_init(pdev);
if (ret)
goto exit_dma;
+ ret = tsn_emac_init(pdev);
+ if (ret)
+ goto exit_ep;
+
return 0;
+exit_ep:
+ tsn_ep_exit(pdev);
exit_dma:
tsn_exit_dmaengine(pdev);
free_clk:
@@ -748,6 +758,7 @@ static void tsn_ip_remove(struct platform_device *pdev)
{
struct tsn_priv *common = platform_get_drvdata(pdev);
+ tsn_emac_exit(pdev);
/* Tear down DMA channels and endpoint */
if (common->ep)
tsn_ep_exit(pdev);
diff --git a/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_mdio.c b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_mdio.c
new file mode 100644
index 000000000000..a057378c9d22
--- /dev/null
+++ b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_mdio.c
@@ -0,0 +1,308 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * TSN MDIO bus driver
+ */
+
+#include "xilinx_tsn.h"
+
+#define MAX_MDIO_FREQ 2500000 /* 2.5 MHz */
+#define DEFAULT_AXI_CLK_FREQ 150000000 /* 150 MHz */
+
+/**
+ * emac_ior_read_mcr - Read MDIO Control Register
+ * @emac: Pointer to TSN EMAC structure
+ *
+ * This function reads the MDIO Control Register (MCR) and is used
+ * as a callback for polling operations.
+ *
+ * Return: Value of MCR register
+ */
+static inline u32 emac_ior_read_mcr(struct tsn_emac *emac)
+{
+ return emac_ior(emac, TSN_MDIO_MCR_OFFSET);
+}
+
+/**
+ * tsn_mdio_wait_until_ready - Wait for MDIO interface to be ready
+ * @emac: Pointer to TSN EMAC structure
+ *
+ * This function polls the MDIO Control Register until the READY bit
+ * is set, indicating the interface is ready for a new transaction.
+ *
+ * Return: 0 on success, -ETIMEDOUT on timeout
+ */
+static int tsn_mdio_wait_until_ready(struct tsn_emac *emac)
+{
+ u32 val;
+
+ return readx_poll_timeout(emac_ior_read_mcr, emac,
+ val, val & TSN_MDIO_MCR_READY,
+ 1, 20000);
+}
+
+/**
+ * tsn_mdio_mdc_enable - Enable MDIO MDC clock
+ * @emac: Pointer to TSN EMAC structure
+ *
+ * This function enables the MDIO Management Data Clock (MDC) by setting
+ * the appropriate bits in the MDIO Control register. Called prior to
+ * read/write operations.
+ */
+static void tsn_mdio_mdc_enable(struct tsn_emac *emac)
+{
+ emac_iow(emac, TSN_MDIO_MC_OFFSET,
+ ((u32)emac->mii_clk_div | TSN_MDIO_MC_MDIOEN));
+}
+
+/**
+ * tsn_mdio_mdc_disable - Disable MDIO MDC clock
+ * @emac: Pointer to TSN EMAC structure
+ *
+ * This function disables the MDIO Management Data Clock (MDC) by clearing
+ * the enable bit in the MDIO Control register. Called after read/write
+ * operations to save power.
+ */
+static void tsn_mdio_mdc_disable(struct tsn_emac *emac)
+{
+ u32 mc_reg;
+
+ mc_reg = emac_ior(emac, TSN_MDIO_MC_OFFSET);
+ emac_iow(emac, TSN_MDIO_MC_OFFSET,
+ (mc_reg & ~TSN_MDIO_MC_MDIOEN));
+}
+
+/**
+ * tsn_mdio_read - MDIO interface read function
+ * @bus: Pointer to mii bus structure
+ * @phy_id: Address of the PHY device
+ * @reg: PHY register to read
+ *
+ * Return: The register contents on success, -ETIMEDOUT on a timeout
+ *
+ * Reads the contents of the requested register from the requested PHY
+ * address by first writing the details into MCR register. After a while
+ * the register MRD is read to obtain the PHY register content.
+ */
+static int tsn_mdio_read(struct mii_bus *bus, int phy_id, int reg)
+{
+ u32 rc;
+ int ret;
+ struct tsn_emac *emac = bus->priv;
+ struct tsn_priv *common = emac->common;
+
+ scoped_guard(mutex, &common->mdio_lock) {
+ tsn_mdio_mdc_enable(emac);
+
+ ret = tsn_mdio_wait_until_ready(emac);
+ if (ret < 0) {
+ tsn_mdio_mdc_disable(emac);
+ return ret;
+ }
+
+ emac_iow(emac, 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 = tsn_mdio_wait_until_ready(emac);
+ if (ret < 0) {
+ tsn_mdio_mdc_disable(emac);
+ return ret;
+ }
+
+ rc = FIELD_GET(TSN_MDIO_MRD_MASK,
+ emac_ior(emac, TSN_MDIO_MRD_OFFSET));
+ tsn_mdio_mdc_disable(emac);
+ }
+ dev_dbg(common->dev, "%s (phy_id=%i, reg=%x) == %x\n",
+ __func__, phy_id, reg, rc);
+
+ return rc;
+}
+
+/**
+ * tsn_mdio_write - MDIO interface write function
+ * @bus: Pointer to mii bus structure
+ * @phy_id: Address of the PHY device
+ * @reg: PHY register to write to
+ * @val: Value to be written into the register
+ *
+ * Return: 0 on success, -ETIMEDOUT on a timeout
+ *
+ * Writes the value to the requested register by first writing the value
+ * into MWD register. The MCR register is then appropriately setup
+ * to finish the write operation.
+ */
+static int tsn_mdio_write(struct mii_bus *bus, int phy_id, int reg,
+ u16 val)
+{
+ struct tsn_emac *emac = bus->priv;
+ struct tsn_priv *common = emac->common;
+ int ret;
+
+ dev_dbg(common->dev, "%s (phy_id=%i, reg=%x, val=%x)\n",
+ __func__, phy_id, reg, val);
+ scoped_guard(mutex, &common->mdio_lock) {
+ tsn_mdio_mdc_enable(emac);
+
+ ret = tsn_mdio_wait_until_ready(emac);
+ if (ret < 0) {
+ tsn_mdio_mdc_disable(emac);
+ return ret;
+ }
+
+ emac_iow(emac, TSN_MDIO_MWD_OFFSET, (u32)val);
+ emac_iow(emac, 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);
+
+ ret = tsn_mdio_wait_until_ready(emac);
+ if (ret < 0) {
+ tsn_mdio_mdc_disable(emac);
+ return ret;
+ }
+ tsn_mdio_mdc_disable(emac);
+ }
+ return 0;
+}
+
+/**
+ * tsn_mdio_enable - Configure and enable MDIO controller
+ * @emac: Pointer to TSN EMAC structure
+ *
+ * This function calculates the appropriate clock divisor for MDIO timing
+ * based on the host clock frequency, programs the divisor, and enables
+ * the MDIO controller. It ensures MDIO frequency does not exceed 2.5 MHz.
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+static int tsn_mdio_enable(struct tsn_emac *emac)
+{
+ struct tsn_priv *common = emac->common;
+ u32 axi_clk_freq;
+ u32 clk_div;
+ int i;
+
+ emac->mii_clk_div = 0;
+
+ /* Pick the right clock for MDIO timing */
+ axi_clk_freq = 0;
+ for (i = 0; i < TSN_NUM_CLOCKS; i++) {
+ const char *id = common->clks[i].id;
+
+ if (id && !strcmp(id, "s_axi_aclk") && common->clks[i].clk) {
+ axi_clk_freq = clk_get_rate(common->clks[i].clk);
+ break;
+ }
+ }
+
+ if (!axi_clk_freq) {
+ dev_warn(common->dev,
+ "Could not get s_axi_aclk, assuming %d Hz\n",
+ DEFAULT_AXI_CLK_FREQ);
+ axi_clk_freq = DEFAULT_AXI_CLK_FREQ;
+ }
+
+ /* Equation: fMDIO = fHOST / ((1 + clk_div) * 2)
+ * Must ensure fMDIO <= 2.5 MHz
+ */
+ clk_div = (axi_clk_freq / (MAX_MDIO_FREQ * 2)) - 1;
+ if (axi_clk_freq % (MAX_MDIO_FREQ * 2))
+ clk_div++;
+
+ emac->mii_clk_div = clk_div;
+
+ dev_dbg(common->dev,
+ "MDIO: host_clk=%u Hz, clk_div=%u\n",
+ axi_clk_freq, clk_div);
+
+ /* Program divisor and enable MDIO controller */
+ dev_info(common->dev,
+ "MDIO: writing to offset=0x%x, value=0x%lx\n",
+ TSN_MDIO_MC_OFFSET,
+ (unsigned long)(emac->mii_clk_div | TSN_MDIO_MC_MDIOEN));
+
+ /* Program divisor and enable MDIO controller */
+ emac_iow(emac, TSN_MDIO_MC_OFFSET,
+ emac->mii_clk_div | TSN_MDIO_MC_MDIOEN);
+ return tsn_mdio_wait_until_ready(emac);
+}
+
+/**
+ * tsn_mdio_setup - Setup MDIO bus for TSN EMAC
+ * @emac: Pointer to TSN EMAC structure
+ * @mac_np: Device tree node for MAC
+ *
+ * This function initializes the MDIO bus for the TSN EMAC interface.
+ * It allocates an MII bus structure, configures MDIO timing, finds
+ * the MDIO device tree node, and registers the MDIO bus with the kernel.
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+int tsn_mdio_setup(struct tsn_emac *emac, struct device_node *mac_np)
+{
+ struct tsn_priv *common = emac->common;
+ struct device_node *mdio_node;
+ struct mii_bus *bus;
+ int ret;
+
+ bus = mdiobus_alloc();
+ if (!bus)
+ return -ENOMEM;
+
+ snprintf(bus->id, MII_BUS_ID_SIZE, "tsn-mac-%.8llx",
+ (unsigned long long)emac->regs_start);
+
+ bus->priv = emac;
+ bus->name = "Xilinx TSN Ethernet MDIO";
+ bus->read = tsn_mdio_read;
+ bus->write = tsn_mdio_write;
+ bus->parent = common->dev;
+ emac->mii_bus = bus;
+
+ mdio_node = of_get_child_by_name(mac_np, "mdio");
+ if (!mdio_node) {
+ dev_err(common->dev, "MAC%d: missing 'mdio' child node\n",
+ emac->emac_num);
+ ret = -ENODEV;
+ goto unregister;
+ }
+ ret = tsn_mdio_enable(emac);
+ if (ret < 0)
+ goto unregister;
+ ret = of_mdiobus_register(bus, mdio_node);
+ if (ret) {
+ dev_err(common->dev, "Failed to register MDIO bus for MAC%d\n",
+ emac->emac_num);
+ goto unregister_mdio_enabled;
+ }
+ of_node_put(mdio_node);
+ tsn_mdio_mdc_disable(emac);
+ return 0;
+
+unregister_mdio_enabled:
+ tsn_mdio_mdc_disable(emac);
+unregister:
+ of_node_put(mdio_node);
+ mdiobus_free(bus);
+ emac->mii_bus = NULL;
+ return ret;
+}
+
+/**
+ * tsn_mdio_teardown - Cleanup MDIO bus for TSN EMAC
+ * @emac: Pointer to TSN EMAC structure
+ *
+ * This function performs cleanup operations for the MDIO bus.
+ * It unregisters the MDIO bus from the kernel and frees any
+ * associated memory for the MII bus structure.
+ */
+void tsn_mdio_teardown(struct tsn_emac *emac)
+{
+ mdiobus_unregister(emac->mii_bus);
+ mdiobus_free(emac->mii_bus);
+ emac->mii_bus = NULL;
+}
--
2.25.1
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [RFC PATCH 5/8] net: xilinx: tsn: Add TSN switch support with port state and frame filter control
2026-02-19 5:49 [RFC PATCH 0/8] xilinx: tsn: Add TSN Endpoint Ethernet MAC driver support Srinivas Neeli
` (3 preceding siblings ...)
2026-02-19 5:49 ` [RFC PATCH 4/8] xilinx: tsn: Add Ethernet MAC (EMAC) and MDIO support to the TSN driver Srinivas Neeli
@ 2026-02-19 5:49 ` Srinivas Neeli
2026-02-19 5:49 ` [RFC PATCH 6/8] dt-bindings: net: Add PTP interrupt support Srinivas Neeli
` (4 subsequent siblings)
9 siblings, 0 replies; 27+ messages in thread
From: Srinivas Neeli @ 2026-02-19 5:49 UTC (permalink / raw)
To: andrew+netdev, davem, edumazet, kuba, pabeni, michal.simek, robh,
krzk+dt, conor+dt, richardcochran
Cc: netdev, linux-kernel, devicetree, linux-arm-kernel, git,
srinivas.neeli
Add support for the TSN hardware switch block, introducing port state
management and frame filter configuration.
It provides initialization and cleanup routines, register access
helpers, and APIs for port state and frame filter configuration.
Enables basic activation and controlled management of TSN switch ports,
allowing traffic forwarding between endpoint and MAC interfaces through
the switch.
Signed-off-by: Srinivas Neeli <srinivas.neeli@amd.com>
---
drivers/net/ethernet/xilinx/tsn/Makefile | 2 +-
drivers/net/ethernet/xilinx/tsn/xilinx_tsn.h | 16 +
.../net/ethernet/xilinx/tsn/xilinx_tsn_main.c | 7 +
.../ethernet/xilinx/tsn/xilinx_tsn_switch.c | 546 ++++++++++++++++++
4 files changed, 570 insertions(+), 1 deletion(-)
create mode 100644 drivers/net/ethernet/xilinx/tsn/xilinx_tsn_switch.c
diff --git a/drivers/net/ethernet/xilinx/tsn/Makefile b/drivers/net/ethernet/xilinx/tsn/Makefile
index 5eb6dde67061..fc1c0cda0843 100644
--- a/drivers/net/ethernet/xilinx/tsn/Makefile
+++ b/drivers/net/ethernet/xilinx/tsn/Makefile
@@ -1,2 +1,2 @@
obj-$(CONFIG_XILINX_TSN) :=xilinx_tsn.o
-xilinx_tsn-objs := xilinx_tsn_main.o xilinx_tsn_ep.o xilinx_tsn_emac.o xilinx_tsn_mdio.o
+xilinx_tsn-objs := xilinx_tsn_main.o xilinx_tsn_ep.o xilinx_tsn_emac.o xilinx_tsn_mdio.o xilinx_tsn_switch.o
diff --git a/drivers/net/ethernet/xilinx/tsn/xilinx_tsn.h b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn.h
index c8435c09ed2c..91d01313aada 100644
--- a/drivers/net/ethernet/xilinx/tsn/xilinx_tsn.h
+++ b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn.h
@@ -138,6 +138,18 @@ struct tsn_emac {
int irq;
};
+/*
+ * struct tsn_switch - TSN switch configuration structure
+ * @dev: Device pointer for this switch instance
+ * @regs: Virtual address mapping of switch register space
+ * @irq: Interrupt number for switch events
+ */
+struct tsn_switch {
+ struct device *dev;
+ void __iomem *regs;
+ int irq;
+};
+
/*
* struct skbuf_dma_descriptor - skb for each dma descriptor
* @sgl: Pointer for sglist.
@@ -193,6 +205,7 @@ struct tsn_endpoint {
* @regs: ioremap()'d base pointer
* @ep: Pointer to TSN endpoint structure
* @emacs: Array of EMAC instances (up to 2)
+ * @sw: Pointer to TSN switch structure
* @clks: Bulk clock data for all required clocks
* @tx_lock: Spinlock protecting TX rings and related TX state
* @rx_lock: Spinlock protecting RX rings and related RX state
@@ -214,6 +227,7 @@ struct tsn_priv {
void __iomem *regs;
struct tsn_endpoint *ep;
struct tsn_emac *emacs[TSN_MAX_EMAC_NO];
+ struct tsn_switch *sw;
struct clk_bulk_data clks[TSN_NUM_CLOCKS];
spinlock_t tx_lock; /* Protects TX ring buffers */
spinlock_t rx_lock; /* Protects RX ring buffers */
@@ -294,4 +308,6 @@ int tsn_emac_init(struct platform_device *pdev);
void tsn_emac_exit(struct platform_device *pdev);
int tsn_mdio_setup(struct tsn_emac *emac, struct device_node *mac_np);
void tsn_mdio_teardown(struct tsn_emac *emac);
+int tsn_switch_init(struct platform_device *pdev);
+void tsn_switch_exit(struct platform_device *pdev);
#endif /* XILINX_TSN_H */
diff --git a/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_main.c b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_main.c
index 7cb07e330f57..82c73fbcd83c 100644
--- a/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_main.c
+++ b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_main.c
@@ -739,8 +739,14 @@ static int tsn_ip_probe(struct platform_device *pdev)
if (ret)
goto exit_ep;
+ ret = tsn_switch_init(pdev);
+ if (ret)
+ goto exit_emac;
+
return 0;
+exit_emac:
+ tsn_emac_exit(pdev);
exit_ep:
tsn_ep_exit(pdev);
exit_dma:
@@ -758,6 +764,7 @@ static void tsn_ip_remove(struct platform_device *pdev)
{
struct tsn_priv *common = platform_get_drvdata(pdev);
+ tsn_switch_exit(pdev);
tsn_emac_exit(pdev);
/* Tear down DMA channels and endpoint */
if (common->ep)
diff --git a/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_switch.c b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_switch.c
new file mode 100644
index 000000000000..89fbc5bcc16f
--- /dev/null
+++ b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_switch.c
@@ -0,0 +1,546 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include "xilinx_tsn.h"
+
+#define TSN_SW_MAX_PORTS 3
+#define TSN_PORT_STATE_CTRL_OFFSET 0x0004c
+#define TSN_SW_MAC_LSB_OFFSET 0x0000c
+#define TSN_SW_MAC_MSB_OFFSET 0x00010
+
+#define TSN_SW_MAC_MSB_FF_MASK_SHIFT 16
+
+/* EP Port (Port 0) control bits */
+#define EP_PORT_STATUS_CHG_BIT BIT(0)
+#define EP_PORT_STATUS_SHIFT 1
+#define EP_PORT_STATUS_MASK GENMASK(3, 1)
+
+/* MAC1 Port (Port 1) control bits */
+#define MAC1_PORT_STATUS_CHG_BIT BIT(8)
+#define MAC1_PORT_STATUS_SHIFT 9
+#define MAC1_PORT_STATUS_MASK GENMASK(11, 9)
+
+/* MAC2 Port (Port 2) control bits */
+#define MAC2_PORT_STATUS_CHG_BIT BIT(16)
+#define MAC2_PORT_STATUS_SHIFT 17
+#define MAC2_PORT_STATUS_MASK GENMASK(19, 17)
+
+#define DELAY_OF_ONE_MILLISEC 1000
+#define DELAY_OF_FIVE_MILLISEC (5 * DELAY_OF_ONE_MILLISEC)
+
+/**
+ * enum tsn_port_state - TSN switch port STP states
+ * @TSN_PORT_STATE_DISABLED: Port disabled, no traffic forwarding
+ * @TSN_PORT_STATE_BLOCKING: Port blocking frames
+ * @TSN_PORT_STATE_LISTENING: Port listening for BPDU frames
+ * @TSN_PORT_STATE_LEARNING: Port learning MAC addresses
+ * @TSN_PORT_STATE_FORWARDING: Port forwarding frames normally
+ */
+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,
+};
+
+/**
+ * sw_iow - Memory mapped TSN switch register write
+ * @sw: Pointer to TSN switch structure
+ * @off: Address offset from the base address of switch registers
+ * @val: Value to be written into the switch register
+ *
+ * This function writes the desired value into the corresponding TSN
+ * switch register.
+ */
+static inline void sw_iow(struct tsn_switch *sw, off_t off, u32 val)
+{
+ iowrite32(val, sw->regs + off);
+}
+
+/**
+ * sw_ior - Memory mapped TSN switch register read
+ * @sw: Pointer to TSN switch structure
+ * @off: Address offset from the base address of switch registers
+ *
+ * This function reads a value from the corresponding TSN switch
+ * register.
+ *
+ * Return: Value read from the switch register
+ */
+static inline u32 sw_ior(struct tsn_switch *sw, u32 off)
+{
+ return ioread32(sw->regs + off);
+}
+
+/**
+ * enum switch_port - TSN switch port identifiers
+ * @PORT_EP: Endpoint port (port 1)
+ * @PORT_MAC1: MAC1 port (port 2)
+ * @PORT_MAC2: MAC2 port (port 3)
+ */
+enum switch_port {
+ PORT_EP = 1,
+ PORT_MAC1 = 2,
+ PORT_MAC2 = 3,
+};
+
+/**
+ * struct port_status - Port configuration structure
+ * @port_num: Port number identifier
+ * @port_status: STP state for the port
+ */
+struct port_status {
+ u8 port_num;
+ u8 port_status;
+};
+
+/**
+ * tsn_sw_set_port_mac - Set MAC address for a specific TSN port
+ * @common: TSN common private structure
+ * @ndev: Network device to update
+ * @base_mac: Base MAC address (first 44 bits)
+ * @port_id: Port identifier (0x01 for EP, 0x02 for EMAC0, 0x03 for EMAC1)
+ * @port_name: Human-readable port name for logging
+ *
+ * Helper function to assign a MAC address to a port with the specified
+ * port ID in the lower 4 bits while preserving the upper 44 bits from
+ * the base MAC address.
+ */
+static void tsn_sw_set_port_mac(struct tsn_priv *common,
+ struct net_device *ndev,
+ const u8 *base_mac, u8 port_id,
+ const char *port_name)
+{
+ u8 new_mac[ETH_ALEN];
+
+ memcpy(new_mac, base_mac, ETH_ALEN);
+ new_mac[5] = (base_mac[5] & 0xF0) | (port_id & 0x0F);
+
+ eth_hw_addr_set(ndev, new_mac);
+ dev_info(common->dev, "%s MAC: %pM\n", port_name, ndev->dev_addr);
+}
+
+/**
+ * tsn_sw_generate_consistent_macs - Generate consistent MAC addresses for all ports
+ * @common: TSN common private structure
+ * @base_mac: Base MAC address to use (first 44 bits preserved)
+ *
+ * Generates MAC addresses for EP, EMAC0, EMAC1 based on a common prefix.
+ * The last 4 bits are set to 0x1, 0x2, 0x3 respectively to distinguish ports.
+ *
+ * Example output:
+ * Base: 32:77:6a:ed:7a:35
+ * EP: 32:77:6a:ed:7a:31
+ * EMAC0: 32:77:6a:ed:7a:32
+ * EMAC1: 32:77:6a:ed:7a:33
+ */
+static void tsn_sw_generate_consistent_macs(struct tsn_priv *common,
+ const u8 *base_mac)
+{
+ struct net_device *emac0_ndev = (common->num_emacs >= 1 && common->emacs[0]) ?
+ common->emacs[0]->ndev : NULL;
+ struct net_device *emac1_ndev = (common->num_emacs >= 2 && common->emacs[1]) ?
+ common->emacs[1]->ndev : NULL;
+ struct net_device *ep_ndev = common->ep ? common->ep->ndev : NULL;
+
+ if (ep_ndev)
+ tsn_sw_set_port_mac(common, ep_ndev, base_mac, 0x01, "EP");
+
+ if (emac0_ndev)
+ tsn_sw_set_port_mac(common, emac0_ndev, base_mac, 0x02, "EMAC0");
+
+ if (emac1_ndev)
+ tsn_sw_set_port_mac(common, emac1_ndev, base_mac, 0x03, "EMAC1");
+}
+
+/**
+ * tsn_sw_is_mac_invalid - Check if MAC address is invalid or zero
+ * @addr: MAC address to validate
+ *
+ * Return: true if MAC is invalid/zero, false if valid
+ */
+static inline bool tsn_sw_is_mac_invalid(const u8 *addr)
+{
+ return !is_valid_ether_addr(addr) || is_zero_ether_addr(addr);
+}
+
+/**
+ * tsn_sw_check_mac_consistency - Validate MAC prefix consistency
+ * @common: TSN common private structure
+ * @ep_ndev: EP network device
+ * @emac0_ndev: EMAC0 network device
+ * @emac1_ndev: EMAC1 network device
+ * @mac_mask: Mask for comparing first 44 bits
+ *
+ * Checks if all active ports have matching 44-bit MAC prefixes.
+ *
+ * Return: true if mismatch found, false if all consistent
+ */
+static bool tsn_sw_check_mac_consistency(struct tsn_priv *common,
+ struct net_device *ep_ndev,
+ struct net_device *emac0_ndev,
+ struct net_device *emac1_ndev,
+ const u8 *mac_mask)
+{
+ bool mismatch = false;
+
+ /* Check EP vs EMAC0 */
+ if (ep_ndev && emac0_ndev &&
+ !ether_addr_equal_masked(ep_ndev->dev_addr,
+ emac0_ndev->dev_addr, mac_mask)) {
+ dev_warn(common->dev,
+ "MAC prefix mismatch: EP (%pM) vs EMAC0 (%pM)\n",
+ ep_ndev->dev_addr, emac0_ndev->dev_addr);
+ mismatch = true;
+ }
+
+ /* Check EP vs EMAC1 */
+ if (ep_ndev && emac1_ndev &&
+ !ether_addr_equal_masked(ep_ndev->dev_addr,
+ emac1_ndev->dev_addr, mac_mask)) {
+ dev_warn(common->dev,
+ "MAC prefix mismatch: EP (%pM) vs EMAC1 (%pM)\n",
+ ep_ndev->dev_addr, emac1_ndev->dev_addr);
+ mismatch = true;
+ }
+
+ /* Check EMAC0 vs EMAC1 */
+ if (emac0_ndev && emac1_ndev &&
+ !ether_addr_equal_masked(emac0_ndev->dev_addr,
+ emac1_ndev->dev_addr, mac_mask)) {
+ dev_warn(common->dev,
+ "MAC prefix mismatch: EMAC0 (%pM) vs EMAC1 (%pM)\n",
+ emac0_ndev->dev_addr, emac1_ndev->dev_addr);
+ mismatch = true;
+ }
+
+ return mismatch;
+}
+
+/**
+ * tsn_sw_select_base_mac - Select best available MAC as base
+ * @common: TSN common private structure
+ * @ep_ndev: EP network device
+ * @emac0_ndev: EMAC0 network device
+ * @emac1_ndev: EMAC1 network device
+ * @base_mac: Output buffer for selected base MAC
+ *
+ * Selects the best available MAC address to use as base for generating
+ * consistent MACs. Priority: EP > EMAC0 > EMAC1 > Random.
+ */
+static void tsn_sw_select_base_mac(struct tsn_priv *common,
+ struct net_device *ep_ndev,
+ struct net_device *emac0_ndev,
+ struct net_device *emac1_ndev,
+ u8 *base_mac)
+{
+ /* Try EP first */
+ if (ep_ndev && !tsn_sw_is_mac_invalid(ep_ndev->dev_addr)) {
+ ether_addr_copy(base_mac, ep_ndev->dev_addr);
+ dev_info(common->dev, "Using EP MAC as base: %pM\n", base_mac);
+ return;
+ }
+
+ /* Try EMAC0 */
+ if (emac0_ndev && !tsn_sw_is_mac_invalid(emac0_ndev->dev_addr)) {
+ ether_addr_copy(base_mac, emac0_ndev->dev_addr);
+ dev_info(common->dev, "Using EMAC0 MAC as base: %pM\n", base_mac);
+ return;
+ }
+
+ /* Try EMAC1 */
+ if (emac1_ndev && !tsn_sw_is_mac_invalid(emac1_ndev->dev_addr)) {
+ ether_addr_copy(base_mac, emac1_ndev->dev_addr);
+ dev_info(common->dev, "Using EMAC1 MAC as base: %pM\n", base_mac);
+ return;
+ }
+
+ /* Generate random locally administered MAC */
+ eth_random_addr(base_mac);
+ base_mac[0] = (base_mac[0] & 0xFE) | 0x02; /* Set local bit, clear multicast */
+ dev_info(common->dev, "Generated random MAC base: %pM\n", base_mac);
+}
+
+/**
+ * tsn_sw_frame_filter_config - Configure frame filtering and validate MAC addresses
+ * @common: TSN common private structure
+ *
+ * This function validates that all TSN ports (EP, EMAC0, EMAC1) share the same
+ * 44-bit MAC address prefix. If any mismatch is detected, it generates a new
+ * consistent set of MAC addresses with:
+ * - Same 44-bit prefix (OUI + 12 bits)
+ * - Last 4 bits set to 0x1, 0x2, 0x3 for EP, EMAC0, EMAC1 respectively
+ *
+ * This ensures proper frame forwarding in the TSN switch where all ports must
+ * appear to belong to the same logical bridge.
+ *
+ * The function uses ether_addr_equal_masked() with mask FF:FF:FF:FF:FF:F0
+ * to compare only the first 44 bits of MAC addresses.
+ */
+static void tsn_sw_frame_filter_config(struct tsn_priv *common)
+{
+ struct net_device *emac0_ndev = (common->num_emacs >= 1 && common->emacs[0]) ?
+ common->emacs[0]->ndev : NULL;
+ struct net_device *emac1_ndev = (common->num_emacs >= 2 && common->emacs[1]) ?
+ common->emacs[1]->ndev : NULL;
+ static const u8 mac_mask[ETH_ALEN] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0};
+ struct net_device *ep_ndev = common->ep ? common->ep->ndev : NULL;
+ struct tsn_switch *sw = common->sw;
+ bool need_new_macs = false;
+ bool mac_mismatch = false;
+ u8 base_mac[ETH_ALEN];
+ u32 mac_lsb, mac_msb;
+ int active_ports;
+
+ dev_info(common->dev, "Validating TSN switch MAC address consistency\n");
+
+ /* Count active ports */
+ active_ports = !!ep_ndev + !!emac0_ndev + !!emac1_ndev;
+ if (active_ports < 2) {
+ dev_info(common->dev, "Less than 2 ports active, skipping MAC validation\n");
+ return;
+ }
+
+ /* Check for invalid/zero MAC addresses */
+ if (ep_ndev && tsn_sw_is_mac_invalid(ep_ndev->dev_addr)) {
+ dev_info(common->dev, "EP has invalid/zero MAC: %pM\n",
+ ep_ndev->dev_addr);
+ need_new_macs = true;
+ }
+
+ if (emac0_ndev && tsn_sw_is_mac_invalid(emac0_ndev->dev_addr)) {
+ dev_info(common->dev, "EMAC0 has invalid/zero MAC: %pM\n",
+ emac0_ndev->dev_addr);
+ need_new_macs = true;
+ }
+
+ if (emac1_ndev && tsn_sw_is_mac_invalid(emac1_ndev->dev_addr)) {
+ dev_info(common->dev, "EMAC1 has invalid/zero MAC: %pM\n",
+ emac1_ndev->dev_addr);
+ need_new_macs = true;
+ }
+
+ /* If all MACs are valid, check consistency */
+ if (!need_new_macs)
+ mac_mismatch = tsn_sw_check_mac_consistency(common, ep_ndev,
+ emac0_ndev, emac1_ndev,
+ mac_mask);
+
+ /* Generate consistent MACs if needed */
+ if (need_new_macs || mac_mismatch) {
+ dev_info(common->dev, "Generating consistent MAC addresses\n");
+
+ tsn_sw_select_base_mac(common, ep_ndev, emac0_ndev,
+ emac1_ndev, base_mac);
+
+ tsn_sw_generate_consistent_macs(common, base_mac);
+
+ dev_info(common->dev, "MAC address synchronization completed\n");
+ } else {
+ dev_info(common->dev, "All MAC addresses have consistent 44-bit prefix\n");
+ }
+
+ /* Program switch frame filter registers.
+ * Use EMAC0 MAC address as the base for the switch filter.
+ * Network port MAC addresses must differ in last LSB nibble only.
+ * This is a hardware pre-requisite - we program the MAC per port basis.
+ */
+
+ ether_addr_copy(base_mac, emac0_ndev->dev_addr);
+
+ /* Program lower 32 bits (bytes 2-5) into TSN_MAC_LSB register */
+ mac_lsb = ((u32)base_mac[2] << 24) | ((u32)base_mac[3] << 16) |
+ ((u32)base_mac[4] << 8) | ((u32)base_mac[5]);
+ sw_iow(sw, TSN_SW_MAC_LSB_OFFSET, mac_lsb);
+
+ /* Program upper 16 bits (bytes 0-1) and 4-bit filter mask (0xF)
+ * into TSN_MAC_MSB register
+ */
+ mac_msb = (0xF << TSN_SW_MAC_MSB_FF_MASK_SHIFT) |
+ ((u32)base_mac[0] << 8) | ((u32)base_mac[1]);
+ sw_iow(sw, TSN_SW_MAC_MSB_OFFSET, mac_msb);
+
+ dev_info(common->dev, "Switch frame filter programmed with MAC: %pM (LSB=0x%08x, MSB=0x%08x)\n",
+ base_mac, mac_lsb, mac_msb);
+}
+
+/**
+ * tsn_switch_set_state - Set hardware port state for a TSN switch
+ * @sw: Pointer to TSN switch structure
+ * @port: Pointer to port status structure containing port number and desired state
+ *
+ * This function programs the desired state of a TSN switch port by writing
+ * to the port state control register. It supports all switch ports
+ * (endpoint, MAC1, MAC2) and updates the corresponding port state bits.
+ * After writing, it waits for the hardware to acknowledge the state change.
+ *
+ * Return: 0 on success, -ETIMEDOUT if the hardware does not acknowledge
+ * the change within the timeout period.
+ */
+static int tsn_switch_set_state(struct tsn_switch *sw,
+ struct port_status *port)
+{
+ u32 en_port_sts_chg_bit = 1;
+ u32 u_value, reg, err;
+
+ u_value = sw_ior(sw, TSN_PORT_STATE_CTRL_OFFSET);
+ switch (port->port_num) {
+ case PORT_EP:
+ if (!(u_value & EP_PORT_STATUS_CHG_BIT)) {
+ u_value &= ~EP_PORT_STATUS_MASK;
+ u_value |= FIELD_PREP(EP_PORT_STATUS_MASK, port->port_status);
+ en_port_sts_chg_bit = EP_PORT_STATUS_CHG_BIT;
+ }
+ break;
+ case PORT_MAC1:
+ if (!(u_value & MAC1_PORT_STATUS_CHG_BIT)) {
+ u_value &= ~MAC1_PORT_STATUS_MASK;
+ u_value |= FIELD_PREP(MAC1_PORT_STATUS_MASK, port->port_status);
+ en_port_sts_chg_bit = MAC1_PORT_STATUS_CHG_BIT;
+ }
+ break;
+ case PORT_MAC2:
+ if (!(u_value & MAC2_PORT_STATUS_CHG_BIT)) {
+ u_value &= ~MAC2_PORT_STATUS_MASK;
+ u_value |= FIELD_PREP(MAC2_PORT_STATUS_MASK, port->port_status);
+ en_port_sts_chg_bit = MAC2_PORT_STATUS_CHG_BIT;
+ }
+ break;
+ }
+
+ u_value |= en_port_sts_chg_bit;
+ sw_iow(sw, TSN_PORT_STATE_CTRL_OFFSET, u_value);
+
+ /* wait for write to complete */
+ err = readl_poll_timeout(sw->regs + TSN_PORT_STATE_CTRL_OFFSET, reg,
+ (!(reg & en_port_sts_chg_bit)), 10,
+ DELAY_OF_FIVE_MILLISEC);
+ if (err) {
+ pr_err("CAM write timed out\n");
+ return -ETIMEDOUT;
+ }
+
+ return 0;
+}
+
+/**
+ * tsn_sw_configure_forwarding - Initialize switch forwarding behavior
+ * @sw: TSN switch instance pointer
+ *
+ * Establishes basic packet forwarding configuration across all switch
+ * ports by setting each port to the forwarding state. This enables
+ * traffic flow between endpoint and MAC interfaces through the switch
+ * fabric.
+ */
+static void tsn_sw_configure_forwarding(struct tsn_switch *sw)
+{
+ struct port_status port;
+ int ret;
+
+ pr_info("Configuring TSN switch for basic forwarding\n");
+
+ /* Configure PORT_EP to forwarding state */
+ port.port_num = PORT_EP;
+ port.port_status = TSN_PORT_STATE_FORWARDING;
+ ret = tsn_switch_set_state(sw, &port);
+ if (ret)
+ pr_err("Failed to set PORT_EP state: %d\n", ret);
+ else
+ pr_info("PORT_EP configured for forwarding\n");
+
+ /* Configure PORT_MAC1 to forwarding state */
+ port.port_num = PORT_MAC1;
+ port.port_status = TSN_PORT_STATE_FORWARDING;
+ ret = tsn_switch_set_state(sw, &port);
+ if (ret)
+ pr_err("Failed to set PORT_MAC1 state: %d\n", ret);
+ else
+ pr_info("PORT_MAC1 configured for forwarding\n");
+
+ /* Configure PORT_MAC2 to forwarding state */
+ port.port_num = PORT_MAC2;
+ port.port_status = TSN_PORT_STATE_FORWARDING;
+ ret = tsn_switch_set_state(sw, &port);
+ if (ret)
+ pr_err("Failed to set PORT_MAC2 state: %d\n", ret);
+ else
+ pr_info("PORT_MAC2 configured for forwarding\n");
+
+ pr_info("TSN switch forwarding configuration completed\n");
+}
+
+/**
+ * tsn_switch_init - Initialize TSN switching subsystem
+ * @pdev: Platform device for the TSN controller
+ *
+ * Sets up the TSN switch component by parsing device tree configuration,
+ * mapping register regions, allocating switch data structures, and
+ * configuring initial forwarding behavior for all ports.
+ *
+ * Return: 0 on successful initialization, negative error code otherwise
+ */
+int tsn_switch_init(struct platform_device *pdev)
+{
+ struct tsn_priv *common = platform_get_drvdata(pdev);
+ struct device_node *switch_node;
+ struct device *dev = &pdev->dev;
+ struct tsn_switch *sw;
+ struct resource res;
+ u32 ret;
+
+ switch_node = of_get_child_by_name(dev->of_node, "switch");
+ if (!switch_node)
+ return dev_err_probe(dev, -ENODEV, "missing switch node\n");
+
+ ret = of_address_to_resource(switch_node, 0, &res);
+ if (ret) {
+ of_node_put(switch_node);
+ return dev_err_probe(dev, ret, "failed to get switch resource\n");
+ }
+
+ sw = devm_kzalloc(&pdev->dev, sizeof(*sw), GFP_KERNEL);
+ if (!sw)
+ return -ENOMEM;
+
+ sw->dev = &pdev->dev;
+ sw->regs = common->regs + res.start;
+ common->sw = sw;
+
+ /* Configure default forwarding */
+ tsn_sw_configure_forwarding(sw);
+
+ tsn_sw_frame_filter_config(common);
+
+ return 0;
+}
+
+/**
+ * tsn_switch_exit - Cleanup TSN switching subsystem
+ * @pdev: Platform device for the TSN controller
+ *
+ * Performs shutdown sequence for the TSN switch by disabling all port
+ * forwarding states and cleaning up allocated resources. This ensures
+ * proper isolation of switch ports during driver removal.
+ */
+void tsn_switch_exit(struct platform_device *pdev)
+{
+ struct tsn_priv *common = platform_get_drvdata(pdev);
+ struct tsn_switch *sw = common->sw;
+ struct port_status port;
+
+ if (!sw)
+ return;
+
+ port.port_status = TSN_PORT_STATE_DISABLED;
+
+ port.port_num = PORT_EP;
+ tsn_switch_set_state(sw, &port);
+
+ port.port_num = PORT_MAC1;
+ tsn_switch_set_state(sw, &port);
+
+ port.port_num = PORT_MAC2;
+ tsn_switch_set_state(sw, &port);
+
+ pr_info("TSN switch exited and all ports set to disabled\n");
+}
--
2.25.1
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [RFC PATCH 6/8] dt-bindings: net: Add PTP interrupt support
2026-02-19 5:49 [RFC PATCH 0/8] xilinx: tsn: Add TSN Endpoint Ethernet MAC driver support Srinivas Neeli
` (4 preceding siblings ...)
2026-02-19 5:49 ` [RFC PATCH 5/8] net: xilinx: tsn: Add TSN switch support with port state and frame filter control Srinivas Neeli
@ 2026-02-19 5:49 ` Srinivas Neeli
2026-02-20 15:17 ` Andrew Lunn
2026-02-19 5:49 ` [RFC PATCH 7/8] net: xilinx: tsn: Add PTP hardware clock (PHC) and timer support Srinivas Neeli
` (3 subsequent siblings)
9 siblings, 1 reply; 27+ messages in thread
From: Srinivas Neeli @ 2026-02-19 5:49 UTC (permalink / raw)
To: andrew+netdev, davem, edumazet, kuba, pabeni, michal.simek, robh,
krzk+dt, conor+dt, richardcochran
Cc: netdev, linux-kernel, devicetree, linux-arm-kernel, git,
srinivas.neeli
Add device tree bindings for PTP (Precision Time Protocol) interrupt
configuration in Xilinx TSN Endpoint Ethernet MAC IP. The MAC instances
within the TSN IP have asymmetric PTP capabilities based on their
hardware configuration.
MAC 1 (xlnx,mac-id = 1) provides complete PTP hardware support including
a dedicated PTP timer, requiring four interrupt lines:
- interrupt_ptp_rx: PTP receive packet interrupt
- interrupt_ptp_tx: PTP transmit packet interrupt
- mac_irq: General MAC interrupt
- interrupt_ptp_timer: PTP hardware timer interrupt
MAC 2 (xlnx,mac-id = 2) supports PTP packet processing but lacks the
hardware timer block, requiring only three interrupt lines:
- interrupt_ptp_rx: PTP receive packet interrupt
- interrupt_ptp_tx: PTP transmit packet interrupt
- mac_irq: General MAC interrupt
Signed-off-by: Srinivas Neeli <srinivas.neeli@amd.com>
---
.../net/xlnx,tsn-endpoint-ethernet-mac.yaml | 79 ++++++++++++++++++-
1 file changed, 77 insertions(+), 2 deletions(-)
diff --git a/Documentation/devicetree/bindings/net/xlnx,tsn-endpoint-ethernet-mac.yaml b/Documentation/devicetree/bindings/net/xlnx,tsn-endpoint-ethernet-mac.yaml
index 0d61a911e1d1..b1b0f4a03d11 100644
--- a/Documentation/devicetree/bindings/net/xlnx,tsn-endpoint-ethernet-mac.yaml
+++ b/Documentation/devicetree/bindings/net/xlnx,tsn-endpoint-ethernet-mac.yaml
@@ -113,6 +113,34 @@ patternProperties:
reg:
maxItems: 1
+ xlnx,mac-id:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ enum: [1, 2]
+ description:
+ MAC instance identifier. MAC ID 1 supports PTP timer functionality,
+ while MAC ID 2 does not have PTP timer support.
+
+ interrupts:
+ minItems: 1
+ maxItems: 4
+ description:
+ Interrupt specifiers for MAC interrupts. MAC 1 (with PTP support)
+ requires 4 interrupts (ptp_rx, ptp_tx, mac_irq, ptp_timer).
+ MAC 2 (without PTP support) requires 3 interrupts (ptp_rx, ptp_tx, mac_irq).
+
+ interrupt-names:
+ minItems: 1
+ maxItems: 4
+ items:
+ enum:
+ - interrupt_ptp_rx
+ - interrupt_ptp_tx
+ - mac_irq
+ - interrupt_ptp_timer
+ description:
+ Names for the interrupts. MAC 1 includes "interrupt_ptp_timer" for
+ PTP hardware timer, which is not present in MAC 2.
+
phy-mode:
enum:
- gmii
@@ -124,6 +152,44 @@ patternProperties:
mdio:
type: object
+
+ allOf:
+ - if:
+ properties:
+ xlnx,mac-id:
+ const: 1
+ then:
+ properties:
+ interrupts:
+ minItems: 4
+ maxItems: 4
+ interrupt-names:
+ items:
+ - const: interrupt_ptp_rx
+ - const: interrupt_ptp_tx
+ - const: mac_irq
+ - const: interrupt_ptp_timer
+ required:
+ - interrupts
+ - interrupt-names
+ - if:
+ properties:
+ xlnx,mac-id:
+ const: 2
+ then:
+ properties:
+ interrupts:
+ minItems: 3
+ maxItems: 3
+ interrupt-names:
+ items:
+ - const: interrupt_ptp_rx
+ - const: interrupt_ptp_tx
+ - const: mac_irq
+ required:
+ - interrupts
+ - interrupt-names
+
additionalProperties: false
"^ep-mac@":
@@ -225,9 +291,14 @@ examples:
xlnx,dma-channel-num = <0x0>;
};
};
- // MAC 1 Node
+ // MAC 1 Node (with PTP timer support)
mac1: ethernet-mac@0 {
reg = <0x0 0x14000>;
+ xlnx,mac-id = <1>;
+ interrupt-parent = <&intc>;
+ interrupt-names = "interrupt_ptp_rx", "interrupt_ptp_tx",
+ "mac_irq", "interrupt_ptp_timer";
+ interrupts = <0 2>, <2 2>, <4 2>, <6 2>;
phy-mode = "rgmii-id";
phy-handle = <&phy0>;
mdio {
@@ -240,9 +311,13 @@ examples:
};
};
- // MAC 2 Node
+ // MAC 2 Node (without PTP timer support)
mac2: ethernet-mac@20000 {
reg = <0x20000 0x14000>;
+ xlnx,mac-id = <2>;
+ interrupt-parent = <&intc>;
+ interrupt-names = "interrupt_ptp_rx", "interrupt_ptp_tx", "mac_irq";
+ interrupts = <1 2>, <3 2>, <5 2>;
phy-mode = "rgmii-id";
phy-handle = <&phy1>;
mdio {
--
2.25.1
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [RFC PATCH 7/8] net: xilinx: tsn: Add PTP hardware clock (PHC) and timer support
2026-02-19 5:49 [RFC PATCH 0/8] xilinx: tsn: Add TSN Endpoint Ethernet MAC driver support Srinivas Neeli
` (5 preceding siblings ...)
2026-02-19 5:49 ` [RFC PATCH 6/8] dt-bindings: net: Add PTP interrupt support Srinivas Neeli
@ 2026-02-19 5:49 ` Srinivas Neeli
2026-02-19 5:49 ` [RFC PATCH 8/8] net: xilinx: tsn: Add PTP packet transmission support Srinivas Neeli
` (2 subsequent siblings)
9 siblings, 0 replies; 27+ messages in thread
From: Srinivas Neeli @ 2026-02-19 5:49 UTC (permalink / raw)
To: andrew+netdev, davem, edumazet, kuba, pabeni, michal.simek, robh,
krzk+dt, conor+dt, richardcochran
Cc: netdev, linux-kernel, devicetree, linux-arm-kernel, git,
srinivas.neeli
Added PTP Hardware Clock (PHC) support to the Xilinx TSN
Ethernet driver by integrating the TSN IP's real-time clock (RTC) and
PTP timer logic.
A new tsn_ptp_timer abstraction is introduced to manage the PTP timer
registers, RTC offset/increment handling, timer interrupts, and PPS
generation. The implementation provides full support for PTP clock
operations including:
- gettime64 / settime64
- adjtime
- adjfine
- PPS enable/disable
- PHC registration via ptp_clock_register()
PTP timer interrupt handling is added to generate PPS output based on
the TSN RTC pulse counter. The PTP timer is shared globally across the
MAC instances and is initialized only for TEMAC1.
Support is also added to ethtool -T via get_ts_info to report hardware
timestamping capabilities and the PHC index.
This enables Linux PTP services such as ptp4l and phc2sys to synchronize
time using the TSN hardware clock.
Signed-off-by: Srinivas Neeli <srinivas.neeli@amd.com>
---
drivers/net/ethernet/xilinx/tsn/Makefile | 2 +-
drivers/net/ethernet/xilinx/tsn/xilinx_tsn.h | 59 +++
.../net/ethernet/xilinx/tsn/xilinx_tsn_emac.c | 55 ++-
.../xilinx/tsn/xilinx_tsn_ptp_clock.c | 386 ++++++++++++++++++
4 files changed, 500 insertions(+), 2 deletions(-)
create mode 100644 drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ptp_clock.c
diff --git a/drivers/net/ethernet/xilinx/tsn/Makefile b/drivers/net/ethernet/xilinx/tsn/Makefile
index fc1c0cda0843..0faa5233221b 100644
--- a/drivers/net/ethernet/xilinx/tsn/Makefile
+++ b/drivers/net/ethernet/xilinx/tsn/Makefile
@@ -1,2 +1,2 @@
obj-$(CONFIG_XILINX_TSN) :=xilinx_tsn.o
-xilinx_tsn-objs := xilinx_tsn_main.o xilinx_tsn_ep.o xilinx_tsn_emac.o xilinx_tsn_mdio.o xilinx_tsn_switch.o
+xilinx_tsn-objs := xilinx_tsn_main.o xilinx_tsn_ep.o xilinx_tsn_emac.o xilinx_tsn_mdio.o xilinx_tsn_switch.o xilinx_tsn_ptp_clock.o
diff --git a/drivers/net/ethernet/xilinx/tsn/xilinx_tsn.h b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn.h
index 91d01313aada..0cce916825ea 100644
--- a/drivers/net/ethernet/xilinx/tsn/xilinx_tsn.h
+++ b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn.h
@@ -18,6 +18,7 @@
#include <linux/if_ether.h>
#include <linux/if_vlan.h>
#include <linux/init.h>
+#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/module.h>
@@ -25,12 +26,15 @@
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_dma.h>
+#include <linux/of_irq.h>
#include <linux/of_mdio.h>
#include <linux/of_net.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
+#include <linux/ptp_clock_kernel.h>
#include <linux/spinlock.h>
#include <linux/u64_stats_sync.h>
+#include <linux/units.h>
#include <net/netdev_queues.h>
#define TSN_NUM_CLOCKS 6
@@ -107,6 +111,55 @@
#define TSN_EMMC_LINKSPEED_1000 BIT(31) /* 1000 Mbit */
#define TSN_MAX_EMAC_NO 2
+#define TSN_TEMAC1 1
+#define TSN_TEMAC2 2
+
+/* PTP Timer Register Offsets (relative to timer base) */
+#define TSN_TIMER_RTC_OFFSET_NS 0x00000 /* RTC Nanoseconds Offset */
+#define TSN_TIMER_RTC_OFFSET_SEC_L 0x00008 /* RTC Seconds Offset - Low */
+#define TSN_TIMER_RTC_OFFSET_SEC_H 0x0000C /* RTC Seconds Offset - High */
+#define TSN_TIMER_RTC_INCREMENT 0x00010 /* RTC Increment */
+#define TSN_TIMER_CURRENT_RTC_NS 0x00014 /* Current TOD Nanoseconds - RO */
+#define TSN_TIMER_CURRENT_RTC_SEC_L 0x00018 /* Current TOD Seconds Low - RO */
+#define TSN_TIMER_CURRENT_RTC_SEC_H 0x0001C /* Current TOD Seconds High - RO */
+#define TSN_TIMER_INTERRUPT 0x00020 /* Interrupt register */
+
+/* PTP Timer bit masks and constants */
+#define TSN_TIMER_MAX_NSEC_SIZE 30
+#define TSN_TIMER_MAX_NSEC_MASK GENMASK_ULL(TSN_TIMER_MAX_NSEC_SIZE - 1, 0)
+#define TSN_TIMER_MAX_SEC_SIZE 48
+#define TSN_TIMER_MAX_SEC_MASK GENMASK_ULL(TSN_TIMER_MAX_SEC_SIZE - 1, 0)
+#define TSN_TIMER_INT_SHIFT 0
+#define TSN_TIMER_RTC_NS_SHIFT 20
+#define PULSESIN1PPS 128
+#define TSN_TIMER_GTX_CLK_FREQ (125 * HZ_PER_MHZ) /* 125 MHz */
+
+/* PTP Timer Register Base Offset */
+#define TSN_PTP_TIMER_OFFSET 0x12800
+
+/**
+ * struct tsn_ptp_timer - PTP timer private data
+ * @dev: Device pointer
+ * @regs: Base address of PTP timer registers
+ * @ptp_clock: PTP clock instance
+ * @ptp_clock_info: PTP clock information
+ * @reg_lock: Register access spinlock
+ * @irq: PTP timer interrupt number
+ * @pps_enable: PPS output enable flag
+ * @countpulse: Pulse counter for PPS generation
+ * @rtc_value: RTC increment value
+ */
+struct tsn_ptp_timer {
+ struct device *dev;
+ void __iomem *regs;
+ struct ptp_clock *ptp_clock;
+ struct ptp_clock_info ptp_clock_info;
+ spinlock_t reg_lock; /* Protect ptp register access */
+ int irq;
+ int pps_enable;
+ int countpulse;
+ u32 rtc_value;
+};
/*
* struct tsn_emac - TSN Ethernet MAC configuration structure
@@ -218,6 +271,8 @@ struct tsn_endpoint {
* @tx_chans: Array of TX DMA channels
* @rx_chans: Array of RX DMA channels
* @num_emacs: Number of EMAC instances
+ * @ptp_timer: Global PTP timer shared by both EMACs
+ * @phc_index: PTP Hardware Clock index
*/
struct tsn_priv {
struct platform_device *pdev;
@@ -240,6 +295,8 @@ struct tsn_priv {
struct tsn_dma_chan **tx_chans;
struct tsn_dma_chan **rx_chans;
u32 num_emacs;
+ struct tsn_ptp_timer ptp_timer;
+ int phc_index;
};
/**
@@ -310,4 +367,6 @@ int tsn_mdio_setup(struct tsn_emac *emac, struct device_node *mac_np);
void tsn_mdio_teardown(struct tsn_emac *emac);
int tsn_switch_init(struct platform_device *pdev);
void tsn_switch_exit(struct platform_device *pdev);
+int tsn_ptp_timer_init(struct tsn_emac *emac, struct device_node *emac_np);
+void tsn_ptp_timer_exit(struct tsn_emac *emac);
#endif /* XILINX_TSN_H */
diff --git a/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_emac.c b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_emac.c
index 26a533e313a2..b7d7ba0de717 100644
--- a/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_emac.c
+++ b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_emac.c
@@ -147,11 +147,46 @@ static void emac_get_drvinfo(struct net_device *ndev,
strscpy(ed->version, DRIVER_VERSION, sizeof(ed->version));
}
+/**
+ * emac_get_ts_info - Get timestamping and PTP information
+ * @ndev: Pointer to net_device structure
+ * @info: Pointer to ethtool_ts_info structure
+ *
+ * This function provides hardware timestamping capabilities and
+ * PTP hardware clock index for ethtool -T command.
+ *
+ * Return: 0 on success
+ */
+static int emac_get_ts_info(struct net_device *ndev,
+ struct kernel_ethtool_ts_info *info)
+{
+ struct tsn_emac *emac = netdev_priv(ndev);
+ struct tsn_priv *common = emac->common;
+
+ info->so_timestamping = SOF_TIMESTAMPING_TX_HARDWARE |
+ SOF_TIMESTAMPING_RX_HARDWARE |
+ SOF_TIMESTAMPING_RAW_HARDWARE;
+
+ info->tx_types = BIT(HWTSTAMP_TX_OFF) |
+ BIT(HWTSTAMP_TX_ON);
+
+ info->rx_filters = BIT(HWTSTAMP_FILTER_NONE) |
+ BIT(HWTSTAMP_FILTER_ALL);
+
+ if (common->phc_index >= 0)
+ info->phc_index = common->phc_index;
+ else
+ info->phc_index = -1;
+
+ return 0;
+}
+
static const struct ethtool_ops emac_ethtool_ops = {
.get_drvinfo = emac_get_drvinfo,
.get_link = ethtool_op_get_link,
.get_link_ksettings = phy_ethtool_get_link_ksettings,
.set_link_ksettings = phy_ethtool_set_link_ksettings,
+ .get_ts_info = emac_get_ts_info,
};
/**
@@ -235,10 +270,19 @@ int tsn_emac_init(struct platform_device *pdev)
}
}
+ /* PTP timer initialization - ONLY for MAC 1 */
+ if (emac->emac_num == TSN_TEMAC1) {
+ ret = tsn_ptp_timer_init(emac, emac_np);
+ if (ret) {
+ dev_err(dev, "Failed to initialize PTP timer for EMAC %d: %d\n",
+ emac->emac_num, ret);
+ goto err_teardown_mdio;
+ }
+ }
ret = register_netdev(ndev);
if (ret) {
dev_err(dev, "Failed to register net device for MAC %d\n", mac_id);
- goto err_teardown_mdio;
+ goto err_remove_ptp;
}
common->emacs[array_idx] = emac;
@@ -246,6 +290,9 @@ int tsn_emac_init(struct platform_device *pdev)
common->num_emacs = array_idx;
continue;
+err_remove_ptp:
+ if (emac->emac_num == TSN_TEMAC1)
+ tsn_ptp_timer_exit(emac);
err_teardown_mdio:
if (emac->phy_node)
tsn_mdio_teardown(emac);
@@ -275,6 +322,8 @@ int tsn_emac_init(struct platform_device *pdev)
dev_info(dev, "Cleaning up MAC %u (array[%d])\n", old->emac_num, array_idx);
unregister_netdev(old->ndev);
+ if (old->emac_num == TSN_TEMAC1)
+ tsn_ptp_timer_exit(old);
if (old->phy_node) {
tsn_mdio_teardown(old);
@@ -314,6 +363,10 @@ void tsn_emac_exit(struct platform_device *pdev)
dev_info(dev, "Cleaning up MAC %u (array[%d])\n", emac->emac_num, i);
unregister_netdev(emac->ndev);
+
+ if (emac->emac_num == TSN_TEMAC1)
+ tsn_ptp_timer_exit(emac);
+
if (emac->phy_node) {
tsn_mdio_teardown(emac);
of_node_put(emac->phy_node);
diff --git a/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ptp_clock.c b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ptp_clock.c
new file mode 100644
index 000000000000..1f6bc932fc6b
--- /dev/null
+++ b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ptp_clock.c
@@ -0,0 +1,386 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "xilinx_tsn.h"
+
+/**
+ * rtc_iow - Write to PTP RTC timer register
+ * @timer: Pointer to TSN PTP timer structure
+ * @reg: Register offset
+ * @val: Value to write
+ *
+ * This function writes the desired value into the corresponding TSN
+ * PTP register.
+ */
+static inline void rtc_iow(struct tsn_ptp_timer *timer, u32 reg, u32 val)
+{
+ iowrite32(val, timer->regs + reg);
+}
+
+/**
+ * rtc_ior - Read from PTP RTC timer register
+ * @timer: Pointer to TSN PTP timer structure
+ * @reg: Register offset
+ *
+ * This function reads a value from the corresponding TSN PTP
+ * register.
+ *
+ * Return: Register value
+ */
+static inline u32 rtc_ior(struct tsn_ptp_timer *timer, u32 reg)
+{
+ return ioread32(timer->regs + reg);
+}
+
+/**
+ * tsn_tod_read - Read current time-of-day from RTC timer
+ * @timer: Pointer to TSN PTP timer structure
+ * @ts: Pointer to timespec64 to store current time
+ *
+ * Reads the 64-bit seconds (high + low) and nanoseconds from the RTC current
+ * time registers. Values are masked to valid ranges.
+ */
+static void tsn_tod_read(struct tsn_ptp_timer *timer,
+ struct timespec64 *ts)
+{
+ u32 secl, sech, nsec;
+
+ nsec = rtc_ior(timer, TSN_TIMER_CURRENT_RTC_NS);
+ secl = rtc_ior(timer, TSN_TIMER_CURRENT_RTC_SEC_L);
+ sech = rtc_ior(timer, TSN_TIMER_CURRENT_RTC_SEC_H);
+
+ ts->tv_sec = (((u64)sech << 32) | secl) & TSN_TIMER_MAX_SEC_MASK;
+ ts->tv_nsec = nsec & TSN_TIMER_MAX_NSEC_MASK;
+}
+
+/**
+ * tsn_rtc_offset_write - Write time offset to RTC offset registers
+ * @timer: Pointer to TSN PTP timer structure
+ * @ts: Pointer to timespec64 with offset value to write
+ *
+ */
+static void tsn_rtc_offset_write(struct tsn_ptp_timer *timer,
+ const struct timespec64 *ts)
+{
+ rtc_iow(timer, TSN_TIMER_RTC_OFFSET_SEC_H, upper_32_bits(ts->tv_sec));
+ rtc_iow(timer, TSN_TIMER_RTC_OFFSET_SEC_L, lower_32_bits(ts->tv_sec));
+ rtc_iow(timer, TSN_TIMER_RTC_OFFSET_NS, ts->tv_nsec);
+}
+
+/**
+ * tsn_rtc_offset_read - Read time offset from RTC offset registers
+ * @timer: Pointer to TSN PTP timer structure
+ * @ts: Pointer to timespec64 to store offset value
+ *
+ * Reads the current RTC offset value from the offset registers.
+ * Values are masked to valid ranges.
+ */
+static void tsn_rtc_offset_read(struct tsn_ptp_timer *timer,
+ struct timespec64 *ts)
+{
+ u32 secl, sech, nsec;
+
+ secl = rtc_ior(timer, TSN_TIMER_RTC_OFFSET_SEC_L);
+ sech = rtc_ior(timer, TSN_TIMER_RTC_OFFSET_SEC_H);
+ nsec = rtc_ior(timer, TSN_TIMER_RTC_OFFSET_NS);
+
+ ts->tv_sec = (((u64)sech << 32) | secl) & TSN_TIMER_MAX_SEC_MASK;
+ ts->tv_nsec = nsec & TSN_TIMER_MAX_NSEC_MASK;
+}
+
+/**
+ * tsn_ptp_adjfine - Adjust PTP clock frequency
+ * @ptp: Pointer to PTP clock info structure
+ * @scaled_ppm: Frequency adjustment in scaled parts-per-million
+ *
+ * Adjusts the RTC increment value to fine-tune the clock frequency.
+ * Uses adjust_by_scaled_ppm() helper to calculate the new increment value
+ * based on the base RTC value (calculated from 125 MHz GTX clock).
+ *
+ * Return: 0 on success
+ */
+static int tsn_ptp_adjfine(struct ptp_clock_info *ptp, long scaled_ppm)
+{
+ struct tsn_ptp_timer *timer = container_of(ptp,
+ struct tsn_ptp_timer,
+ ptp_clock_info);
+ u32 incval;
+
+ incval = adjust_by_scaled_ppm(timer->rtc_value, scaled_ppm);
+ rtc_iow(timer, TSN_TIMER_RTC_INCREMENT, incval);
+
+ return 0;
+}
+
+/**
+ * tsn_ptp_adjtime - Adjust PTP clock time by offset
+ * @ptp: Pointer to PTP clock info structure
+ * @delta: Time offset in nanoseconds (positive or negative)
+ *
+ * Adjusts the RTC time by adding the specified delta offset.
+ * Reads the current offset, adds the delta to it, and writes back.
+ *
+ * Return: 0 on success
+ */
+static int tsn_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta)
+{
+ struct tsn_ptp_timer *timer = container_of(ptp, struct tsn_ptp_timer,
+ ptp_clock_info);
+ struct timespec64 now, then = ns_to_timespec64(delta);
+
+ guard(spinlock_irqsave)(&timer->reg_lock);
+
+ tsn_rtc_offset_read(timer, &now);
+ now = timespec64_add(now, then);
+ tsn_rtc_offset_write(timer, &now);
+
+ return 0;
+}
+
+/**
+ * tsn_ptp_gettime - Get current PTP clock time
+ * @ptp: Pointer to PTP clock info structure
+ * @ts: Pointer to timespec64 to receive current time
+ *
+ * Reads the current time-of-day from the RTC timer.
+ *
+ * Return: 0 on success
+ */
+static int tsn_ptp_gettime(struct ptp_clock_info *ptp,
+ struct timespec64 *ts)
+{
+ struct tsn_ptp_timer *timer = container_of(ptp, struct tsn_ptp_timer,
+ ptp_clock_info);
+
+ guard(spinlock_irqsave)(&timer->reg_lock);
+ tsn_tod_read(timer, ts);
+
+ return 0;
+}
+
+/**
+ * tsn_ptp_settime - Set PTP clock time
+ * @ptp: Pointer to PTP clock info structure
+ * @ts: Pointer to timespec64 with new time to set
+ *
+ * Return: 0 on success, -EINVAL for invalid timestamp
+ */
+static int tsn_ptp_settime(struct ptp_clock_info *ptp,
+ const struct timespec64 *ts)
+{
+ struct tsn_ptp_timer *timer = container_of(ptp, struct tsn_ptp_timer,
+ ptp_clock_info);
+ struct timespec64 delta, tod, offset;
+
+ if (!ts || ts->tv_nsec < 0 || ts->tv_nsec >= NSEC_PER_SEC)
+ return -EINVAL;
+
+ guard(spinlock_irqsave)(&timer->reg_lock);
+
+ /* Zero the offset first */
+ offset.tv_sec = 0;
+ offset.tv_nsec = 0;
+ tsn_rtc_offset_write(timer, &offset);
+
+ /* Get current timer value */
+ tsn_tod_read(timer, &tod);
+
+ /* Calculate delta */
+ delta = timespec64_sub(*ts, tod);
+
+ /* Don't write negative offset */
+ if (delta.tv_sec < 0 || (delta.tv_sec == 0 && delta.tv_nsec < 0)) {
+ delta.tv_sec = 0;
+ delta.tv_nsec = 0;
+ }
+
+ tsn_rtc_offset_write(timer, &delta);
+
+ return 0;
+}
+
+/**
+ * tsn_ptp_enable - Enable or disable PPS output
+ * @ptp: Pointer to PTP clock info structure
+ * @rq: Pointer to PTP clock request
+ * @on: 1 to enable, 0 to disable
+ *
+ * Enables or disables the PPS (pulse-per-second) event delivery.
+ * The TSN IP generates 128 pulses per second, and this function controls
+ * whether those pulses are reported to the PTP subsystem via ptp_clock_event().
+ * Only supports PTP_CLK_REQ_PPS request type.
+ *
+ * Return: 0 on success, -EOPNOTSUPP for unsupported request types
+ */
+static int tsn_ptp_enable(struct ptp_clock_info *ptp,
+ struct ptp_clock_request *rq, int on)
+{
+ struct tsn_ptp_timer *timer = container_of(ptp, struct tsn_ptp_timer,
+ ptp_clock_info);
+
+ switch (rq->type) {
+ case PTP_CLK_REQ_PPS:
+ timer->pps_enable = on ? 1 : 0;
+ return 0;
+ default:
+ break;
+ }
+
+ return -EOPNOTSUPP;
+}
+
+/**
+ * tsn_ptp_timer_isr - PTP timer interrupt handler
+ * @irq: Interrupt number
+ * @priv: Pointer to tsn_ptp_timer structure
+ *
+ * Handles PTP timer interrupts for PPS (pulse-per-second) events.
+ * The TSN IP generates 128 pulses per second. This ISR counts those pulses
+ * and delivers a PTP_CLOCK_PPS event once per second (every 128 pulses) if
+ * PPS is enabled via tsn_ptp_enable().
+ *
+ * Return: IRQ_HANDLED
+ */
+static irqreturn_t tsn_ptp_timer_isr(int irq, void *priv)
+{
+ struct tsn_ptp_timer *timer = priv;
+ struct ptp_clock_event event;
+
+ event.type = PTP_CLOCK_PPS;
+
+ timer->countpulse++;
+ if (timer->countpulse >= PULSESIN1PPS) {
+ timer->countpulse = 0;
+ if (timer->ptp_clock && timer->pps_enable)
+ ptp_clock_event(timer->ptp_clock, &event);
+ }
+
+ /* Clear interrupt */
+ rtc_iow(timer, TSN_TIMER_INTERRUPT, BIT(TSN_TIMER_INT_SHIFT));
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * tsn_ptp_timer_init - Initialize PTP timer and register PHC
+ * @emac: Pointer to TSN EMAC structure
+ * @emac_np: Pointer to EMAC device tree node
+ *
+ * The PTP timer is shared globally - only initialized once for TEMAC1.
+ * TEMAC2 will skip initialization and share the same PHC index.
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+int tsn_ptp_timer_init(struct tsn_emac *emac, struct device_node *emac_np)
+{
+ struct tsn_priv *common = emac->common;
+ struct tsn_ptp_timer *timer = &common->ptp_timer;
+ struct device *dev = common->dev;
+ struct timespec64 ts;
+ int ret;
+
+ if (timer->ptp_clock) {
+ dev_info(dev, "PTP timer already initialized (PHC: %d)\n",
+ common->phc_index);
+ return 0;
+ }
+
+ memset(timer, 0, sizeof(*timer));
+ timer->dev = dev;
+ timer->irq = -1;
+
+ timer->regs = emac->regs + TSN_PTP_TIMER_OFFSET;
+
+ spin_lock_init(&timer->reg_lock);
+
+ timer->irq = of_irq_get_byname(emac_np, "interrupt_ptp_timer");
+ if (timer->irq < 0) {
+ timer->irq = platform_get_irq_byname(common->pdev, "interrupt_ptp_timer");
+ if (timer->irq < 0) {
+ dev_err(dev, "Failed to get PTP timer interrupt: %d\n",
+ timer->irq);
+ ret = timer->irq;
+ goto err_cleanup;
+ }
+ }
+
+ ret = devm_request_irq(dev, timer->irq, tsn_ptp_timer_isr, 0,
+ "tsn_ptp_timer", timer);
+ if (ret) {
+ dev_err(dev, "Failed to request PTP timer IRQ %d: %d\n",
+ timer->irq, ret);
+ goto err_cleanup;
+ }
+
+ /* Setup PTP clock info */
+ timer->ptp_clock_info.owner = THIS_MODULE;
+ snprintf(timer->ptp_clock_info.name,
+ sizeof(timer->ptp_clock_info.name), "TSN PTP");
+ timer->ptp_clock_info.max_adj = 999999999;
+ timer->ptp_clock_info.n_ext_ts = 0;
+ timer->ptp_clock_info.pps = 1;
+ timer->ptp_clock_info.adjfine = tsn_ptp_adjfine;
+ timer->ptp_clock_info.adjtime = tsn_ptp_adjtime;
+ timer->ptp_clock_info.gettime64 = tsn_ptp_gettime;
+ timer->ptp_clock_info.settime64 = tsn_ptp_settime;
+ timer->ptp_clock_info.enable = tsn_ptp_enable;
+
+ /* Register PTP clock */
+ timer->ptp_clock = ptp_clock_register(&timer->ptp_clock_info, dev);
+ if (IS_ERR(timer->ptp_clock)) {
+ ret = PTR_ERR(timer->ptp_clock);
+ dev_err(dev, "Failed to register PTP clock: %d\n", ret);
+ timer->ptp_clock = NULL;
+ goto err_cleanup;
+ }
+
+ /* In the TSN IP Core, RTC clock is connected to gtx_clk which is
+ * 125 MHz. This is specified in the TSN PG and is not configurable.
+ *
+ * Calculating the RTC Increment Value once and storing it in
+ * timer->rtc_value to prevent recalculating it each time the PTP
+ * frequency is adjusted in xlnx_ptp_adjfine()
+ */
+ timer->rtc_value = (div_u64(NSEC_PER_SEC, TSN_TIMER_GTX_CLK_FREQ) <<
+ TSN_TIMER_RTC_NS_SHIFT);
+
+ rtc_iow(timer, TSN_TIMER_RTC_INCREMENT, timer->rtc_value);
+
+ ts = ktime_to_timespec64(ktime_get_real());
+ tsn_ptp_settime(&timer->ptp_clock_info, &ts);
+
+ /* Store PHC index */
+ common->phc_index = ptp_clock_index(timer->ptp_clock);
+
+ dev_info(dev, "PTP timer initialized (PHC: %d, IRQ: %d, offset: 0x%x)\n",
+ common->phc_index, timer->irq, TSN_PTP_TIMER_OFFSET);
+
+ return 0;
+
+err_cleanup:
+ timer->irq = -1;
+ common->phc_index = -1;
+ return ret;
+}
+
+/**
+ * tsn_ptp_timer_exit - Cleanup PTP timer and unregister PHC
+ * @emac: Pointer to TSN EMAC structure
+ *
+ * Unregisters the PTP clock from the kernel PTP subsystem and
+ * cleans up the PTP timer state. Sets phc_index back to -1.
+ * The interrupt is automatically freed by devm_request_irq().
+ */
+void tsn_ptp_timer_exit(struct tsn_emac *emac)
+{
+ struct tsn_priv *common = emac->common;
+ struct tsn_ptp_timer *timer = &common->ptp_timer;
+
+ if (!timer->ptp_clock)
+ return;
+
+ ptp_clock_unregister(timer->ptp_clock);
+ dev_info(common->dev, "PTP timer unregistered (PHC: %d)\n",
+ common->phc_index);
+
+ timer->ptp_clock = NULL;
+ common->phc_index = -1;
+}
--
2.25.1
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [RFC PATCH 8/8] net: xilinx: tsn: Add PTP packet transmission support
2026-02-19 5:49 [RFC PATCH 0/8] xilinx: tsn: Add TSN Endpoint Ethernet MAC driver support Srinivas Neeli
` (6 preceding siblings ...)
2026-02-19 5:49 ` [RFC PATCH 7/8] net: xilinx: tsn: Add PTP hardware clock (PHC) and timer support Srinivas Neeli
@ 2026-02-19 5:49 ` Srinivas Neeli
2026-02-19 7:34 ` [RFC PATCH 0/8] xilinx: tsn: Add TSN Endpoint Ethernet MAC driver support Krzysztof Kozlowski
2026-02-19 16:42 ` Andrew Lunn
9 siblings, 0 replies; 27+ messages in thread
From: Srinivas Neeli @ 2026-02-19 5:49 UTC (permalink / raw)
To: andrew+netdev, davem, edumazet, kuba, pabeni, michal.simek, robh,
krzk+dt, conor+dt, richardcochran
Cc: netdev, linux-kernel, devicetree, linux-arm-kernel, git,
srinivas.neeli
Add support for PTP (Precision Time Protocol) packet transmission
and timestamping for Xilinx TSN Ethernet MAC. This implementation
provides hardware-assisted packet timestamping for IEEE 1588 PTP
synchronization.
Key features added:
- PTP TX/RX buffer management with 8 TX and 16 RX buffers
- Hardware timestamp extraction for transmitted and received packets
- Dedicated PTP packet queue and transmission path
- Interrupt-driven timestamp retrieval via work queues
- Support for 2-step PTP mode (HWTSTAMP_TX_ON)
- PTP packet filtering based on ETH_P_1588 ethertype
Signed-off-by: Srinivas Neeli <srinivas.neeli@amd.com>
---
drivers/net/ethernet/xilinx/tsn/Makefile | 2 +-
drivers/net/ethernet/xilinx/tsn/xilinx_tsn.h | 58 +++
.../net/ethernet/xilinx/tsn/xilinx_tsn_emac.c | 178 ++++++-
.../ethernet/xilinx/tsn/xilinx_tsn_ptp_xmit.c | 451 ++++++++++++++++++
4 files changed, 683 insertions(+), 6 deletions(-)
create mode 100644 drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ptp_xmit.c
diff --git a/drivers/net/ethernet/xilinx/tsn/Makefile b/drivers/net/ethernet/xilinx/tsn/Makefile
index 0faa5233221b..a39cc7ca1533 100644
--- a/drivers/net/ethernet/xilinx/tsn/Makefile
+++ b/drivers/net/ethernet/xilinx/tsn/Makefile
@@ -1,2 +1,2 @@
obj-$(CONFIG_XILINX_TSN) :=xilinx_tsn.o
-xilinx_tsn-objs := xilinx_tsn_main.o xilinx_tsn_ep.o xilinx_tsn_emac.o xilinx_tsn_mdio.o xilinx_tsn_switch.o xilinx_tsn_ptp_clock.o
+xilinx_tsn-objs := xilinx_tsn_main.o xilinx_tsn_ep.o xilinx_tsn_emac.o xilinx_tsn_mdio.o xilinx_tsn_switch.o xilinx_tsn_ptp_clock.o xilinx_tsn_ptp_xmit.o
diff --git a/drivers/net/ethernet/xilinx/tsn/xilinx_tsn.h b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn.h
index 0cce916825ea..c8339ecef2f6 100644
--- a/drivers/net/ethernet/xilinx/tsn/xilinx_tsn.h
+++ b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn.h
@@ -137,6 +137,37 @@
/* PTP Timer Register Base Offset */
#define TSN_PTP_TIMER_OFFSET 0x12800
+/* PTP register offsets */
+#define PTP_TX_CONTROL_OFFSET 0x00012000
+#define PTP_RX_CONTROL_OFFSET 0x00012004
+
+/* PTP RX buffer configuration */
+#define PTP_RX_BASE_OFFSET 0x00010000
+#define PTP_RX_PACKET_FIELD_MASK 0x00000F00
+#define PTP_RX_PACKET_CLEAR 0x00000001
+
+/* PTP TX buffer configuration */
+#define PTP_TX_BUFFER_OFFSET(index) (0x00011000 + (index) * 0x100)
+#define PTP_TX_CMD_FIELD_LEN 8
+#define PTP_TX_CMD_1STEP_SHIFT BIT(16)
+#define PTP_TX_BUFFER_CMD2_FIELD 0x4
+
+/* PTP TX control and status masks */
+#define PTP_TX_FRAME_WAITING_MASK 0x0000ff00
+#define PTP_TX_FRAME_WAITING_SHIFT 8
+#define PTP_TX_PACKET_FIELD_MASK 0x00070000
+#define PTP_TX_PACKET_FIELD_SHIFT 16
+
+/* PTP timestamp and buffer definitions */
+#define PTP_HW_TSTAMP_SIZE 8 /* 64 bit timestamp */
+#define PTP_RX_HWBUF_SIZE 256
+#define PTP_RX_FRAME_SIZE 252
+#define PTP_HW_TSTAMP_OFFSET (PTP_RX_HWBUF_SIZE - PTP_HW_TSTAMP_SIZE)
+
+/* PTP message type definitions */
+#define PTP_MSG_TYPE_MASK BIT(3)
+#define PTP_TYPE_SYNC 0x0
+
/**
* struct tsn_ptp_timer - PTP timer private data
* @dev: Device pointer
@@ -175,6 +206,16 @@ struct tsn_ptp_timer {
* @mii_clk_div: MDIO clock divider value
* @emac_num: EMAC instance number (1 or 2)
* @irq: Interrupt number for this EMAC
+ * @ptp_rx_irq: PTP RX interrupt number
+ * @ptp_tx_irq: PTP TX interrupt number
+ * @ptp_txq: PTP TX packet queue for timestamping
+ * @ptp_tx_lock: Spinlock for PTP TX queue
+ * @tx_tstamp_work: Work structure for TX timestamp processing
+ * @ptp_rx_hw_pointer: Hardware pointer for PTP RX packets
+ * @ptp_rx_sw_pointer: Software pointer for PTP RX packets
+ * @ptp_ts_type: PTP timestamp type configuration
+ * @tstamp_config: Hardware timestamp config structure
+ * @current_rx_filter : Current rx filter
*/
struct tsn_emac {
struct net_device *ndev;
@@ -189,6 +230,16 @@ struct tsn_emac {
u8 mii_clk_div;
int emac_num;
int irq;
+ int ptp_rx_irq;
+ int ptp_tx_irq;
+ struct sk_buff_head ptp_txq;
+ spinlock_t ptp_tx_lock; /* Protect PTP TX queue */
+ struct work_struct tx_tstamp_work;
+ u8 ptp_rx_hw_pointer;
+ u8 ptp_rx_sw_pointer;
+ int ptp_ts_type;
+ struct hwtstamp_config tstamp_config;
+ int current_rx_filter;
};
/*
@@ -369,4 +420,11 @@ int tsn_switch_init(struct platform_device *pdev);
void tsn_switch_exit(struct platform_device *pdev);
int tsn_ptp_timer_init(struct tsn_emac *emac, struct device_node *emac_np);
void tsn_ptp_timer_exit(struct tsn_emac *emac);
+int tsn_ptp_get_irq_info(struct tsn_emac *emac, struct device_node *emac_np);
+int tsn_ptp_init_and_register_irqs(struct tsn_emac *emac);
+void tsn_ptp_unregister_irqs(struct tsn_emac *emac);
+int tsn_ptp_xmit(struct sk_buff *skb, struct tsn_emac *emac);
+void tsn_ptp_tx_tstamp(struct work_struct *work);
+irqreturn_t tsn_ptp_rx_irq(int irq, void *data);
+irqreturn_t tsn_ptp_tx_irq(int irq, void *data);
#endif /* XILINX_TSN_H */
diff --git a/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_emac.c b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_emac.c
index b7d7ba0de717..4d0780a29fc9 100644
--- a/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_emac.c
+++ b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_emac.c
@@ -61,16 +61,28 @@ static int emac_open(struct net_device *ndev)
{
struct tsn_emac *emac = netdev_priv(ndev);
struct phy_device *phydev = NULL;
+ int ret;
+
+ /* Register PTP interrupts */
+ ret = tsn_ptp_init_and_register_irqs(emac);
+ if (ret) {
+ dev_err(emac->common->dev,
+ "EMAC %d: Failed to register PTP interrupts: %d\n",
+ emac->emac_num, ret);
+ return ret;
+ }
if (emac->phy_node) {
phydev = of_phy_connect(emac->ndev, emac->phy_node,
tsn_adjust_link_tsn,
emac->phy_flags,
emac->phy_mode);
- if (!phydev)
+ if (!phydev) {
dev_err(emac->common->dev, "of_phy_connect() failed\n");
- else
- phy_start(phydev);
+ tsn_ptp_unregister_irqs(emac);
+ return -ENODEV;
+ }
+ phy_start(phydev);
}
return 0;
@@ -87,9 +99,13 @@ static int emac_open(struct net_device *ndev)
*/
static int emac_stop(struct net_device *ndev)
{
+ struct tsn_emac *emac = netdev_priv(ndev);
+
if (ndev->phydev)
phy_disconnect(ndev->phydev);
+ tsn_ptp_unregister_irqs(emac);
+
return 0;
}
@@ -120,16 +136,158 @@ static int emac_validate_addr(struct net_device *ndev)
static netdev_tx_t emac_start_xmit(struct sk_buff *skb, struct net_device *ndev)
{
struct tsn_emac *emac = netdev_priv(ndev);
+ u16 queue = skb_get_queue_mapping(skb);
+
+ if (queue == emac->common->num_priorities)
+ return tsn_ptp_xmit(skb, emac);
return tsn_start_xmit_dmaengine(emac->common, skb, ndev);
}
+/**
+ * emac_select_queue - select queue for packet transmission
+ * @ndev: Pointer to net_device structure
+ * @skb: socket buffer containing the packet
+ * @sb_dev: fallback device (not used)
+ *
+ * Return: Queue index for PTP packets or default queue
+ *
+ * This function selects the appropriate queue for packet transmission.
+ * PTP packets (ETH_P_1588) are directed to a dedicated PTP queue.
+ */
+static u16 emac_select_queue(struct net_device *ndev,
+ struct sk_buff *skb,
+ struct net_device *sb_dev)
+{
+ struct tsn_emac *emac = netdev_priv(ndev);
+ struct tsn_priv *common = emac->common;
+ struct ethhdr *hdr = (struct ethhdr *)skb->data;
+
+ /* PTP over Ethernet (Layer 2) */
+ if (hdr->h_proto == htons(ETH_P_1588))
+ return common->num_priorities;
+ return netdev_pick_tx(ndev, skb, sb_dev);
+}
+
+/**
+ * emac_set_timestamp_mode - sets up the hardware for the requested mode
+ * @emac: Pointer to TSN EMAC structure
+ * @config: the hwtstamp configuration requested
+ *
+ * Return: 0 on success, Negative value on errors
+ */
+static int emac_set_timestamp_mode(struct tsn_emac *emac,
+ struct hwtstamp_config *config)
+{
+ /* reserved for future extensions */
+ if (config->flags)
+ return -EINVAL;
+
+ if (config->tx_type < HWTSTAMP_TX_OFF ||
+ config->tx_type > HWTSTAMP_TX_ON)
+ return -ERANGE;
+
+ emac->ptp_ts_type = config->tx_type;
+
+ /* On RX always timestamp everything */
+ switch (config->rx_filter) {
+ case HWTSTAMP_FILTER_NONE:
+ emac->current_rx_filter = HWTSTAMP_FILTER_NONE;
+ break;
+ case HWTSTAMP_FILTER_PTP_V2_L2_EVENT:
+ emac->current_rx_filter = HWTSTAMP_FILTER_PTP_V2_L2_EVENT;
+ config->rx_filter = HWTSTAMP_FILTER_PTP_V2_L2_EVENT;
+ break;
+ default:
+ return -ERANGE;
+ }
+ return 0;
+}
+
+/**
+ * emac_set_ts_config - user entry point for timestamp mode
+ * @emac: Pointer to TSN EMAC structure
+ * @ifr: ioctl data
+ *
+ * Set hardware to the requested more. If unsupported return an error
+ * with no changes. Otherwise, store the mode for future reference
+ *
+ * Return: 0 on success, Negative value on errors
+ */
+static int emac_set_ts_config(struct tsn_emac *emac, struct ifreq *ifr)
+{
+ struct hwtstamp_config config;
+ int err;
+
+ if (copy_from_user(&config, ifr->ifr_data, sizeof(config)))
+ return -EFAULT;
+
+ err = emac_set_timestamp_mode(emac, &config);
+ if (err)
+ return err;
+
+ /* save these settings for future reference */
+ memcpy(&emac->tstamp_config, &config, sizeof(emac->tstamp_config));
+
+ return copy_to_user(ifr->ifr_data, &config,
+ sizeof(config)) ? -EFAULT : 0;
+}
+
+/**
+ * emac_get_ts_config - return the current timestamp configuration
+ * to the user
+ * @emac: pointer to TSN EMAC structure
+ * @ifr: ioctl data
+ *
+ * Return: 0 on success, Negative value on errors
+ */
+static int emac_get_ts_config(struct tsn_emac *emac, struct ifreq *ifr)
+{
+ struct hwtstamp_config *config = &emac->tstamp_config;
+
+ return copy_to_user(ifr->ifr_data, config,
+ sizeof(*config)) ? -EFAULT : 0;
+}
+
+/**
+ * emac_ioctl - Ioctl MII Interface
+ * @dev: Pointer to net_device structure
+ * @rq: ioctl request structure
+ * @cmd: ioctl command
+ *
+ * Return: 0 on success, Negative value on errors
+ */
+static int emac_ioctl(struct net_device *dev, struct ifreq *rq, int cmd)
+{
+ struct tsn_emac *emac = netdev_priv(dev);
+
+ if (!netif_running(dev))
+ return -EINVAL;
+
+ switch (cmd) {
+ case SIOCGMIIPHY:
+ case SIOCGMIIREG:
+ case SIOCSMIIREG:
+ if (!dev->phydev)
+ return -EOPNOTSUPP;
+ return phy_mii_ioctl(dev->phydev, rq, cmd);
+ case SIOCSHWTSTAMP:
+ return emac_set_ts_config(emac, rq);
+ case SIOCGHWTSTAMP:
+ return emac_get_ts_config(emac, rq);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
static const struct net_device_ops emac_netdev_ops = {
.ndo_open = emac_open,
.ndo_stop = emac_stop,
.ndo_start_xmit = emac_start_xmit,
.ndo_set_mac_address = tsn_ndo_set_mac_address,
.ndo_validate_addr = emac_validate_addr,
+ .ndo_select_queue = emac_select_queue,
+ .ndo_eth_ioctl = emac_ioctl,
};
/**
@@ -171,7 +329,7 @@ static int emac_get_ts_info(struct net_device *ndev,
BIT(HWTSTAMP_TX_ON);
info->rx_filters = BIT(HWTSTAMP_FILTER_NONE) |
- BIT(HWTSTAMP_FILTER_ALL);
+ BIT(HWTSTAMP_FILTER_PTP_V2_L2_EVENT);
if (common->phc_index >= 0)
info->phc_index = common->phc_index;
@@ -225,7 +383,9 @@ int tsn_emac_init(struct platform_device *pdev)
goto err_cleanup_all;
}
- ndev = alloc_etherdev(sizeof(*emac));
+ ndev = alloc_etherdev_mqs(sizeof(*emac),
+ common->num_tx_queues + 1,
+ common->num_rx_queues);
if (!ndev) {
ret = -ENOMEM;
of_node_put(emac_np);
@@ -279,6 +439,14 @@ int tsn_emac_init(struct platform_device *pdev)
goto err_teardown_mdio;
}
}
+
+ ret = tsn_ptp_get_irq_info(emac, emac_np);
+ if (ret) {
+ dev_err(dev, "Failed to get PTP IRQ info for EMAC %d: %d\n",
+ emac->emac_num, ret);
+ goto err_remove_ptp;
+ }
+
ret = register_netdev(ndev);
if (ret) {
dev_err(dev, "Failed to register net device for MAC %d\n", mac_id);
diff --git a/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ptp_xmit.c b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ptp_xmit.c
new file mode 100644
index 000000000000..0a2850ed42ad
--- /dev/null
+++ b/drivers/net/ethernet/xilinx/tsn/xilinx_tsn_ptp_xmit.c
@@ -0,0 +1,451 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Xilinx FPGA Xilinx TSN PTP transfer protocol module.
+ *
+ */
+
+#include "xilinx_tsn.h"
+
+/**
+ * ptp_iow - write to PTP register
+ * @emac: Pointer to TSN EMAC structure
+ * @off: Register offset
+ * @val: Value to write
+ *
+ * This function writes to PTP control registers.
+ */
+static inline void ptp_iow(struct tsn_emac *emac, off_t off, u32 val)
+{
+ iowrite32(val, emac->regs + off);
+}
+
+/**
+ * ptp_ior - read from PTP register
+ * @emac: Pointer to TSN EMAC structure
+ * @off: Register offset
+ *
+ * Return: Register value
+ *
+ * This function reads from PTP control registers.
+ */
+static inline u32 ptp_ior(struct tsn_emac *emac, u32 off)
+{
+ return ioread32(emac->regs + off);
+}
+
+/**
+ * memcpy_fromio_32 - copy ptp buffer from HW
+ * @emac: Pointer to TSN EMAC structure
+ * @offset: Offset in the PTP buffer
+ * @data: Destination buffer
+ * @len: Length to copy
+ *
+ * This functions copies the data from PTP buffer to destination data buffer
+ */
+static void memcpy_fromio_32(struct tsn_emac *emac,
+ unsigned long offset, u8 *data, size_t len)
+{
+ while (len >= 4) {
+ *(u32 *)data = ptp_ior(emac, offset);
+ len -= 4;
+ offset += 4;
+ data += 4;
+ }
+
+ if (len > 0) {
+ u32 leftover = ptp_ior(emac, offset);
+ u8 *src = (u8 *)&leftover;
+
+ while (len) {
+ *data++ = *src++;
+ len--;
+ }
+ }
+}
+
+/**
+ * memcpy_toio_32 - copy ptp buffer to HW
+ * @emac: Pointer to TSN EMAC structure
+ * @offset: Offset in the PTP buffer
+ * @data: Source data
+ * @len: Length to copy
+ *
+ * This functions copies the source data to destination ptp buffer
+ */
+static void memcpy_toio_32(struct tsn_emac *emac,
+ unsigned long offset, u8 *data, size_t len)
+{
+ while (len >= 4) {
+ ptp_iow(emac, offset, *(u32 *)data);
+ len -= 4;
+ offset += 4;
+ data += 4;
+ }
+
+ if (len > 0) {
+ u32 leftover = 0;
+ u8 *dest = (u8 *)&leftover;
+
+ while (len) {
+ *dest++ = *data++;
+ len--;
+ }
+ ptp_iow(emac, offset, leftover);
+ }
+}
+
+/**
+ * tsn_ptp_xmit - xmit skb using PTP HW
+ * @skb: sk_buff pointer that contains data to be Txed.
+ * @emac: Pointer to TSN EMAC structure.
+ *
+ * Return: NETDEV_TX_OK, on success
+ * NETDEV_TX_BUSY, if any of the descriptors are not free
+ *
+ * This function is called to transmit a PTP skb. The function uses
+ * the free PTP TX buffer entry and sends the frame
+ */
+int tsn_ptp_xmit(struct sk_buff *skb, struct tsn_emac *emac)
+{
+ u16 queue = skb_get_queue_mapping(skb);
+ u8 tx_frame_waiting;
+ u32 cmd1_field = 0;
+ u32 cmd2_field = 0;
+ u8 free_index;
+
+ tx_frame_waiting = (ptp_ior(emac, PTP_TX_CONTROL_OFFSET) &
+ PTP_TX_FRAME_WAITING_MASK) >>
+ PTP_TX_FRAME_WAITING_SHIFT;
+
+ /* we reached last frame */
+ if (tx_frame_waiting & (1 << 7)) {
+ netif_stop_subqueue(emac->ndev, queue);
+ emac->ndev->stats.tx_dropped++;
+ netdev_dbg(emac->ndev, "PTP TX buffers full: 0x%x\n", tx_frame_waiting);
+ return NETDEV_TX_BUSY;
+ }
+
+ /* go to next available slot */
+ free_index = fls(tx_frame_waiting);
+
+ cmd1_field |= skb->len;
+
+ ptp_iow(emac, PTP_TX_BUFFER_OFFSET(free_index), cmd1_field);
+ ptp_iow(emac, PTP_TX_BUFFER_OFFSET(free_index) +
+ PTP_TX_BUFFER_CMD2_FIELD, cmd2_field);
+ memcpy_toio_32(emac,
+ (PTP_TX_BUFFER_OFFSET(free_index) +
+ PTP_TX_CMD_FIELD_LEN),
+ skb->data, skb->len);
+
+ /* send the frame */
+ ptp_iow(emac, PTP_TX_CONTROL_OFFSET, (1 << free_index));
+
+ scoped_guard(spinlock_irq, &emac->ptp_tx_lock) {
+ skb->cb[0] = free_index;
+ skb_queue_tail(&emac->ptp_txq, skb);
+
+ if (skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP)
+ skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS;
+ }
+ skb_tx_timestamp(skb);
+
+ return NETDEV_TX_OK;
+}
+
+/**
+ * tsn_set_timestamp - timestamp skb with HW timestamp
+ * @emac: Pointer to TSN EMAC structure
+ * @hwtstamps: Pointer to skb timestamp structure
+ * @offset: offset of the timestamp in the PTP buffer
+ *
+ * Return: None.
+ *
+ */
+static void tsn_set_timestamp(struct tsn_emac *emac,
+ struct skb_shared_hwtstamps *hwtstamps,
+ unsigned int offset)
+{
+ u32 captured_ns;
+ u32 captured_sec;
+
+ captured_ns = ptp_ior(emac, offset + 4);
+ captured_sec = ptp_ior(emac, offset);
+
+ hwtstamps->hwtstamp = ktime_set(captured_sec, captured_ns);
+}
+
+/**
+ * tsn_ptp_recv - receive ptp buffer in skb from HW
+ * @ndev: Pointer to net_device structure.
+ *
+ * This function is called from the ptp rx isr. It allocates skb, and
+ * copies the ptp rx buffer data to it and calls netif_rx for further
+ * processing.
+ *
+ */
+static void tsn_ptp_recv(struct net_device *ndev)
+{
+ struct tsn_emac *emac = netdev_priv(ndev);
+ unsigned long ptp_frame_base_addr = 0;
+ struct sk_buff *skb;
+ u16 msg_len;
+ u8 msg_type;
+ u32 bytes = 0;
+ u32 packets = 0;
+
+ if (!ndev || !netif_running(ndev))
+ return;
+
+ while (((emac->ptp_rx_hw_pointer & 0xf) !=
+ (emac->ptp_rx_sw_pointer & 0xf))) {
+ skb = netdev_alloc_skb(ndev, PTP_RX_FRAME_SIZE);
+ if (!skb) {
+ ndev->stats.rx_dropped++;
+ emac->ptp_rx_sw_pointer += 1;
+ continue;
+ }
+ emac->ptp_rx_sw_pointer += 1;
+
+ ptp_frame_base_addr = PTP_RX_BASE_OFFSET +
+ ((emac->ptp_rx_sw_pointer & 0xf) *
+ PTP_RX_HWBUF_SIZE);
+
+ memcpy_fromio_32(emac, ptp_frame_base_addr, skb->data,
+ PTP_RX_FRAME_SIZE);
+
+ msg_type = *(u8 *)(skb->data + ETH_HLEN) & 0xf;
+ msg_len = *(u16 *)(skb->data + ETH_HLEN + 2);
+
+ skb_put(skb, ntohs(msg_len) + ETH_HLEN);
+
+ bytes += skb->len;
+ packets++;
+
+ skb->protocol = eth_type_trans(skb, ndev);
+ skb->ip_summed = CHECKSUM_UNNECESSARY;
+
+ /* timestamp only event messages */
+ if (!(msg_type & PTP_MSG_TYPE_MASK)) {
+ tsn_set_timestamp(emac, skb_hwtstamps(skb),
+ (ptp_frame_base_addr +
+ PTP_HW_TSTAMP_OFFSET));
+ }
+
+ netif_rx(skb);
+ }
+ ndev->stats.rx_packets += packets;
+ ndev->stats.rx_bytes += bytes;
+}
+
+/**
+ * tsn_ptp_rx_irq - PTP RX ISR handler
+ * @irq: irq number
+ * @data: net_device pointer
+ *
+ * Return: IRQ_HANDLED for all cases.
+ */
+irqreturn_t tsn_ptp_rx_irq(int irq, void *data)
+{
+ struct tsn_emac *emac = data;
+
+ emac->ptp_rx_hw_pointer = (ptp_ior(emac, PTP_RX_CONTROL_OFFSET)
+ & PTP_RX_PACKET_FIELD_MASK) >> 8;
+
+ tsn_ptp_recv(emac->ndev);
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * tsn_ptp_tx_tstamp - timestamp skb on transmit path
+ * @work: Pointer to work_struct structure
+ *
+ * This adds TX timestamp to skb
+ */
+void tsn_ptp_tx_tstamp(struct work_struct *work)
+{
+ struct tsn_emac *emac = container_of(work, struct tsn_emac,
+ tx_tstamp_work);
+ struct net_device *ndev = emac->ndev;
+ struct skb_shared_hwtstamps hwtstamps;
+ struct sk_buff *skb;
+ unsigned long ts_reg_offset;
+ unsigned long flags;
+ u8 tx_packet;
+ u8 index;
+ u32 bytes = 0;
+ u32 packets = 0;
+
+ memset(&hwtstamps, 0, sizeof(struct skb_shared_hwtstamps));
+
+ spin_lock_irqsave(&emac->ptp_tx_lock, flags);
+
+ tx_packet = (ptp_ior(emac, PTP_TX_CONTROL_OFFSET) &
+ PTP_TX_PACKET_FIELD_MASK) >>
+ PTP_TX_PACKET_FIELD_SHIFT;
+
+ while ((skb = __skb_dequeue(&emac->ptp_txq)) != NULL) {
+ index = skb->cb[0];
+
+ /* dequeued packet yet to be xmited? */
+ if (index > tx_packet) {
+ /* enqueue it back and break */
+ skb_queue_tail(&emac->ptp_txq, skb);
+ break;
+ }
+ /* time stamp reg offset */
+ ts_reg_offset = PTP_TX_BUFFER_OFFSET(index) +
+ PTP_HW_TSTAMP_OFFSET;
+
+ if (skb_shinfo(skb)->tx_flags & SKBTX_IN_PROGRESS) {
+ tsn_set_timestamp(emac, &hwtstamps, ts_reg_offset);
+ skb_tstamp_tx(skb, &hwtstamps);
+ }
+
+ bytes += skb->len;
+ packets++;
+ dev_kfree_skb_any(skb);
+ }
+ ndev->stats.tx_packets += packets;
+ ndev->stats.tx_bytes += bytes;
+
+ spin_unlock_irqrestore(&emac->ptp_tx_lock, flags);
+}
+
+/**
+ * tsn_ptp_tx_irq - PTP TX irq handler
+ * @irq: irq number
+ * @data: net_device pointer
+ *
+ * Return: IRQ_HANDLED for all cases.
+ *
+ */
+irqreturn_t tsn_ptp_tx_irq(int irq, void *data)
+{
+ struct tsn_emac *emac = data;
+
+ if (!emac || !emac->ndev || !emac->common)
+ return IRQ_HANDLED;
+
+ /* read ctrl register to clear the interrupt */
+ ptp_ior(emac, PTP_TX_CONTROL_OFFSET);
+
+ schedule_work(&emac->tx_tstamp_work);
+ if (__netif_subqueue_stopped(emac->ndev, emac->common->num_priorities))
+ netif_wake_subqueue(emac->ndev, emac->common->num_priorities);
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * tsn_ptp_get_irq_info - Get PTP interrupt information from device tree
+ * @emac: Pointer to TSN EMAC structure
+ * @emac_np: Device tree node for EMAC
+ *
+ * Return: 0 on success, negative error code on failure
+ *
+ * This function retrieves PTP RX and TX interrupt numbers from device tree.
+ */
+int tsn_ptp_get_irq_info(struct tsn_emac *emac, struct device_node *emac_np)
+{
+ struct device *dev = emac->common->dev;
+
+ emac->ptp_rx_irq = of_irq_get_byname(emac_np, "interrupt_ptp_rx");
+ if (emac->ptp_rx_irq < 0) {
+ dev_err(dev,
+ "EMAC %d: Failed to get mandatory 'interrupt_ptp_rx': %d\n",
+ emac->emac_num, emac->ptp_rx_irq);
+ return emac->ptp_rx_irq;
+ }
+
+ emac->ptp_tx_irq = of_irq_get_byname(emac_np, "interrupt_ptp_tx");
+ if (emac->ptp_tx_irq < 0) {
+ dev_err(dev,
+ "EMAC %d: Failed to get mandatory 'interrupt_ptp_tx': %d\n",
+ emac->emac_num, emac->ptp_tx_irq);
+ return emac->ptp_tx_irq;
+ }
+
+ dev_info(dev, "EMAC %d: PTP IRQs - RX: %d, TX: %d\n",
+ emac->emac_num, emac->ptp_rx_irq, emac->ptp_tx_irq);
+
+ return 0;
+}
+
+/**
+ * tsn_ptp_init_and_register_irqs - Initialize PTP subsystem and register interrupts
+ * @emac: Pointer to TSN EMAC structure
+ *
+ * Return: 0 on success, negative error code on failure
+ *
+ * This function initializes the PTP packet handling subsystem and registers
+ * interrupt handlers for PTP RX and TX events.
+ */
+int tsn_ptp_init_and_register_irqs(struct tsn_emac *emac)
+{
+ struct device *dev = emac->common->dev;
+ int ret;
+
+ /* Initialize PTP TX queue and lock */
+ skb_queue_head_init(&emac->ptp_txq);
+ spin_lock_init(&emac->ptp_tx_lock);
+ INIT_WORK(&emac->tx_tstamp_work, tsn_ptp_tx_tstamp);
+
+ /* Initialize PTP RX pointers */
+ emac->current_rx_filter = HWTSTAMP_FILTER_PTP_V2_L2_EVENT;
+ emac->ptp_ts_type = HWTSTAMP_TX_ON;
+ emac->ptp_rx_hw_pointer = 0;
+ emac->ptp_rx_sw_pointer = 0xff;
+
+ /* Clear PTP RX control register */
+ ptp_iow(emac, PTP_RX_CONTROL_OFFSET, PTP_RX_PACKET_CLEAR);
+
+ /* Register PTP RX interrupt */
+ ret = request_irq(emac->ptp_rx_irq, tsn_ptp_rx_irq, 0,
+ "tsn_ptp_rx", emac);
+ if (ret) {
+ dev_err(dev, "EMAC %d: Failed to register PTP RX IRQ %d: %d\n",
+ emac->emac_num, emac->ptp_rx_irq, ret);
+ return ret;
+ }
+
+ /* Register PTP TX interrupt */
+ ret = request_irq(emac->ptp_tx_irq, tsn_ptp_tx_irq, 0,
+ "tsn_ptp_tx", emac);
+ if (ret) {
+ dev_err(dev, "EMAC %d: Failed to register PTP TX IRQ %d: %d\n",
+ emac->emac_num, emac->ptp_tx_irq, ret);
+ free_irq(emac->ptp_rx_irq, emac);
+ return ret;
+ }
+
+ dev_info(dev, "EMAC %d: PTP interrupts registered\n", emac->emac_num);
+ return 0;
+}
+
+/**
+ * tsn_ptp_unregister_irqs - Unregister PTP interrupts
+ * @emac: Pointer to TSN EMAC structure
+ *
+ * This function unregisters PTP RX and TX interrupt handlers and cleans up
+ * PTP TX queue. Called during interface close.
+ */
+void tsn_ptp_unregister_irqs(struct tsn_emac *emac)
+{
+ struct sk_buff *skb;
+
+ if (emac->ptp_tx_irq > 0)
+ free_irq(emac->ptp_tx_irq, emac);
+
+ if (emac->ptp_rx_irq > 0)
+ free_irq(emac->ptp_rx_irq, emac);
+
+ cancel_work_sync(&emac->tx_tstamp_work);
+
+ while ((skb = skb_dequeue(&emac->ptp_txq)) != NULL)
+ dev_kfree_skb_any(skb);
+
+ dev_info(emac->common->dev, "EMAC %d: PTP interrupts unregistered\n",
+ emac->emac_num);
+}
--
2.25.1
^ permalink raw reply related [flat|nested] 27+ messages in thread
* Re: [RFC PATCH 2/8] net: xilinx: tsn: Introduce TSN core driver skeleton
2026-02-19 5:49 ` [RFC PATCH 2/8] net: xilinx: tsn: Introduce TSN core driver skeleton Srinivas Neeli
@ 2026-02-19 7:32 ` Krzysztof Kozlowski
0 siblings, 0 replies; 27+ messages in thread
From: Krzysztof Kozlowski @ 2026-02-19 7:32 UTC (permalink / raw)
To: Srinivas Neeli, andrew+netdev, davem, edumazet, kuba, pabeni,
michal.simek, robh, krzk+dt, conor+dt, richardcochran
Cc: netdev, linux-kernel, devicetree, linux-arm-kernel, git
On 19/02/2026 06:49, Srinivas Neeli wrote:
> Introduce the initial skeleton for the AMD/Xilinx Time Sensitive
> Networking (TSN) Ethernet IP driver. Adds the Kconfig entry
> (CONFIG_XILINX_TSN), updates the Xilinx Ethernet Makefile,
> and provides the base source file focused on device tree
> parsing and clock acquisition.
>
> Signed-off-by: Srinivas Neeli <srinivas.neeli@amd.com>
> ---
> drivers/net/ethernet/xilinx/Kconfig | 1 +
> drivers/net/ethernet/xilinx/Makefile | 1 +
> drivers/net/ethernet/xilinx/tsn/Kconfig | 14 ++
> drivers/net/ethernet/xilinx/tsn/Makefile | 2 +
> drivers/net/ethernet/xilinx/tsn/xilinx_tsn.h | 60 +++++
> .../net/ethernet/xilinx/tsn/xilinx_tsn_main.c | 218 ++++++++++++++++++
> 6 files changed, 296 insertions(+)
> create mode 100644 drivers/net/ethernet/xilinx/tsn/Kconfig
> create mode 100644 drivers/net/ethernet/xilinx/tsn/Makefile
> create mode 100644 drivers/net/ethernet/xilinx/tsn/xilinx_tsn.h
> create mode 100644 drivers/net/ethernet/xilinx/tsn/xilinx_tsn_main.c
>
> diff --git a/drivers/net/ethernet/xilinx/Kconfig b/drivers/net/ethernet/xilinx/Kconfig
> index 7502214cc7d5..c6d704c8d3d4 100644
> --- a/drivers/net/ethernet/xilinx/Kconfig
> +++ b/drivers/net/ethernet/xilinx/Kconfig
> @@ -41,4 +41,5 @@ config XILINX_LL_TEMAC
> This driver supports the Xilinx 10/100/1000 LocalLink TEMAC
> core used in Xilinx Spartan and Virtex FPGAs
>
> +source "drivers/net/ethernet/xilinx/tsn/Kconfig"
> endif # NET_VENDOR_XILINX
> diff --git a/drivers/net/ethernet/xilinx/Makefile b/drivers/net/ethernet/xilinx/Makefile
> index 7d7dc1771423..66dab012650b 100644
> --- a/drivers/net/ethernet/xilinx/Makefile
> +++ b/drivers/net/ethernet/xilinx/Makefile
> @@ -8,3 +8,4 @@ obj-$(CONFIG_XILINX_LL_TEMAC) += ll_temac.o
> obj-$(CONFIG_XILINX_EMACLITE) += xilinx_emaclite.o
> xilinx_emac-objs := xilinx_axienet_main.o xilinx_axienet_mdio.o
> obj-$(CONFIG_XILINX_AXI_EMAC) += xilinx_emac.o
> +obj-$(CONFIG_XILINX_TSN) += tsn/
> diff --git a/drivers/net/ethernet/xilinx/tsn/Kconfig b/drivers/net/ethernet/xilinx/tsn/Kconfig
> new file mode 100644
> index 000000000000..53734842700b
> --- /dev/null
> +++ b/drivers/net/ethernet/xilinx/tsn/Kconfig
> @@ -0,0 +1,14 @@
> +config XILINX_TSN
> + tristate "Xilinx TSN Ethernet driver"
> + depends on OF && HAS_IOMEM
No ARCH_XILINX?
> + select PHYLIB
> + select NET_DEVLINK
> + select NET_DEV_STATS
> + help
> + This driver supports the Xilinx Time-Sensitive Networking (TSN)
> + Ethernet IP, which includes multiple Ethernet MACs, a TSN switch,
> + and an endpoint block. It provides support for scheduling,
> + traffic shaping, and time synchronization to meet real-time
> + requirements of industrial Ethernet applications.
> +
...
> +
> +/*
> + * Helper to parse TX queue config subnode referenced by
> + * xlnx,tsn-tx-config. This version enumerates child nodes in order and
> + * assigns DMA channels sequentially (queue0 == first child, etc.)
> + */
> +static int tsn_parse_tx_queue_config(struct device *dev, struct tsn_priv *common,
> + struct device_node *txcfg_np)
> +{
> + struct device_node *qnode;
> + unsigned int queue = 0;
> + int ret = 0;
> +
> + for_each_available_child_of_node(txcfg_np, qnode) {
> + u32 chan;
> +
> + if (queue >= common->num_tx_queues) {
> + dev_err(dev, "tx-config: extra child nodes beyond %u ignored\n",
> + common->num_tx_queues);
> + of_node_put(qnode);
Use scoped loop.
> + return -EINVAL;
> + }
> +
> + ret = of_property_read_u32(qnode, "xlnx,dma-channel-num", &chan);
> + if (ret) {
> + dev_err(dev, "tx-config: Q%u missing xlnx,dma-channel-num\n", queue);
> + of_node_put(qnode);
> + return ret;
> + }
> +
> + if (chan > TSN_DMA_MAX_TX_CH) {
> + dev_err(dev, "tx-config: Q%u channel %u exceeds max %lu\n",
> + queue, chan, TSN_DMA_MAX_TX_CH);
> + of_node_put(qnode);
> + return -EINVAL;
> + }
> + common->tx_dma_chan_map[queue++] = chan;
> + }
> +
> + if (queue != common->num_tx_queues) {
> + dev_err(dev, "tx-config: described %u queues but expected %u\n",
> + queue, common->num_tx_queues);
> + return -EINVAL;
> + }
> +
> + return 0;
> +}
> +
> +/**
> + * tsn_parse_device_tree - Parse device tree configuration for TSN device
> + * @pdev: Platform device pointer
> + *
> + * Return: 0 on success, negative error code on failure
> + */
> +static int tsn_parse_device_tree(struct platform_device *pdev)
> +{
> + struct tsn_priv *common = platform_get_drvdata(pdev);
> + struct device_node *txcfg_np = NULL;
> + struct device *dev = &pdev->dev;
> + int i, ret;
> +
> + /* Read number of priorities */
> + ret = of_property_read_u32(dev->of_node, "xlnx,num-priorities", &common->num_priorities);
> + if (ret)
> + return dev_err_probe(dev, ret, "Failed to get xlnx,num-priorities\n");
> +
> + if (common->num_priorities < TSN_MIN_PRIORITIES ||
> + common->num_priorities > TSN_MAX_PRIORITIES)
> + return dev_err_probe(dev, -EINVAL, "Invalid xlnx,num-priorities (%u)\n",
> + common->num_priorities);
> +
> + /* Count TX and RX queues from dma-names property */
> + ret = of_property_count_strings(dev->of_node, "dma-names");
> + if (ret < 0)
> + return dev_err_probe(dev, ret, "Failed to get dma-names\n");
> +
> + common->num_tx_queues = 0;
> + common->num_rx_queues = 0;
> +
> + for (i = 0; i < ret; i++) {
> + const char *dma_name;
> +
> + if (of_property_read_string_index(dev->of_node, "dma-names", i, &dma_name))
> + continue;
> +
> + if (strncmp(dma_name, "tx_chan", 7) == 0)
> + common->num_tx_queues++;
> + else if (strncmp(dma_name, "rx_chan", 7) == 0)
> + common->num_rx_queues++;
> + }
> +
> + if (!common->num_tx_queues || common->num_tx_queues > TSN_MAX_TX_QUEUE)
> + return dev_err_probe(dev, -EINVAL,
> + "Invalid TX queue count (%u, max %u)\n",
> + common->num_tx_queues, TSN_MAX_TX_QUEUE);
> +
> + if (!common->num_rx_queues)
> + return dev_err_probe(dev, -EINVAL, "No RX DMA channels found\n");
> +
> + /* Setup clock IDs */
> + for (i = 0; i < TSN_NUM_CLOCKS; i++)
> + common->clks[i].id = tsn_clk_names[i];
> +
> + /* Get all clocks */
> + ret = devm_clk_bulk_get(dev, TSN_NUM_CLOCKS, common->clks);
> + if (ret)
> + return dev_err_probe(dev, ret, "Failed to get clocks\n");
> +
> + /* Enable clocks */
> + ret = clk_bulk_prepare_enable(TSN_NUM_CLOCKS, common->clks);
> + if (ret)
> + return dev_err_probe(dev, ret, "Failed to enable clocks\n");
> +
> + for (i = 0; i < TSN_MAX_TX_QUEUE; i++)
> + common->tx_dma_chan_map[i] = TSN_DMA_CH_INVALID;
> +
> + txcfg_np = of_parse_phandle(dev->of_node, "xlnx,tsn-tx-config", 0);
> + if (txcfg_np) {
> + ret = tsn_parse_tx_queue_config(dev, common, txcfg_np);
> + of_node_put(txcfg_np);
> + if (ret)
> + goto err_disable_clks;
> + }
> +
> + return 0;
> +
> +err_disable_clks:
> + clk_bulk_disable_unprepare(TSN_NUM_CLOCKS, common->clks);
> + return ret;
> +}
> +
> +/**
> + * tsn_ip_probe - Probe TSN IP core device
Nooo...
> + * @pdev: Platform device pointer
> + *
> + * Return: 0 on success, negative error code on failure
> + */
> +static int tsn_ip_probe(struct platform_device *pdev)
> +{
> + struct tsn_priv *common;
> + int ret;
> +
> + common = devm_kzalloc(&pdev->dev, sizeof(*common), GFP_KERNEL);
> + if (!common)
> + return -ENOMEM;
> +
> + platform_set_drvdata(pdev, common);
> + common->pdev = pdev;
> + common->dev = &pdev->dev;
> +
> + /* Initialize synchronization primitives */
> + spin_lock_init(&common->tx_lock);
> + spin_lock_init(&common->rx_lock);
> + mutex_init(&common->mdio_lock);
> +
> + common->res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + if (!common->res)
> + return -ENODEV;
> + common->regs_start = common->res->start;
> + common->regs = devm_ioremap_resource(&pdev->dev, common->res);
> + if (IS_ERR(common->regs))
> + return PTR_ERR(common->regs);
> +
> + ret = tsn_parse_device_tree(pdev);
> + if (ret)
> + return ret;
> +
> + return 0;
> +}
> +
> +/**
> + * tsn_ip_remove - Remove TSN IP core device
> + * @pdev: Platform device pointer
Pointless comment. Can it be anything else than what you wrote? Why are
you adding comments to standard boilerplate calls?
> + */
> +static void tsn_ip_remove(struct platform_device *pdev)
> +{
> + struct tsn_priv *common = platform_get_drvdata(pdev);
> +
> + clk_bulk_disable_unprepare(TSN_NUM_CLOCKS, common->clks);
> +}
> +
> +static const struct of_device_id tsn_of_match[] = {
> + { .compatible = "xlnx,tsn-endpoint-ethernet-mac-3.0", },
> + { }
> +};
> +MODULE_DEVICE_TABLE(of, tsn_of_match);
> +
> +static struct platform_driver tsn_driver = {
> + .probe = tsn_ip_probe,
> + .remove = tsn_ip_remove,
> + .driver = {
> + .name = "xilinx-tsn",
> + .of_match_table = tsn_of_match,
> + },
> +};
> +module_platform_driver(tsn_driver);
> +
> +MODULE_AUTHOR("Neeli Srinivas <srinivas.neeli@amd.com>");
> +MODULE_DESCRIPTION("Time Sensitive Networking (TSN) Ethernet MAC driver");
> +MODULE_LICENSE("GPL");
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [RFC PATCH 0/8] xilinx: tsn: Add TSN Endpoint Ethernet MAC driver support
2026-02-19 5:49 [RFC PATCH 0/8] xilinx: tsn: Add TSN Endpoint Ethernet MAC driver support Srinivas Neeli
` (7 preceding siblings ...)
2026-02-19 5:49 ` [RFC PATCH 8/8] net: xilinx: tsn: Add PTP packet transmission support Srinivas Neeli
@ 2026-02-19 7:34 ` Krzysztof Kozlowski
2026-02-19 16:42 ` Andrew Lunn
9 siblings, 0 replies; 27+ messages in thread
From: Krzysztof Kozlowski @ 2026-02-19 7:34 UTC (permalink / raw)
To: Srinivas Neeli, andrew+netdev, davem, edumazet, kuba, pabeni,
michal.simek, robh, krzk+dt, conor+dt, richardcochran
Cc: netdev, linux-kernel, devicetree, linux-arm-kernel, git
On 19/02/2026 06:49, Srinivas Neeli wrote:
> https://lore.kernel.org/all/DDB5J5V1IM0E.34WP32K550WIU@folker-schwesinger.de/
> https://lore.kernel.org/all/DDB5IRSNB09F.3HRTZZOZQ7J6@folker-schwesinger.de/
> https://lore.kernel.org/all/DDB5IDDEOVBT.NHJF03FYW2BN@folker-schwesinger.de/
>
>
> This RFC is the initial submission of the driver and aims to gather
> feedback on design, structure, and subsystem integration before
> further upstreamization work.
Isn't every patch initial submission aiming to gather feedback on
design, structure and integration before further work?
I'll drop it from DT patchwork.
Anyway, remember about proper versioning so b4 diff work correctly.
Usually people doing RFC get it wrong making the RFC completely
pointless and counter productive :(
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [RFC PATCH 0/8] xilinx: tsn: Add TSN Endpoint Ethernet MAC driver support
2026-02-19 5:49 [RFC PATCH 0/8] xilinx: tsn: Add TSN Endpoint Ethernet MAC driver support Srinivas Neeli
` (8 preceding siblings ...)
2026-02-19 7:34 ` [RFC PATCH 0/8] xilinx: tsn: Add TSN Endpoint Ethernet MAC driver support Krzysztof Kozlowski
@ 2026-02-19 16:42 ` Andrew Lunn
2026-02-20 12:59 ` Neeli, Srinivas
9 siblings, 1 reply; 27+ messages in thread
From: Andrew Lunn @ 2026-02-19 16:42 UTC (permalink / raw)
To: Srinivas Neeli
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, michal.simek, robh,
krzk+dt, conor+dt, richardcochran, netdev, linux-kernel,
devicetree, linux-arm-kernel, git
On Thu, Feb 19, 2026 at 11:19:03AM +0530, Srinivas Neeli wrote:
> Introduce a new network driver for the AMD LogiCORE 100M/1G TSN
> Subsystem IP, also known as the TSN Endpoint Ethernet MAC, which
> implements IEEE 802.1 Time-Sensitive Networking (TSN) features for
> deterministic and low-latency Ethernet communication in real-time and
> industrial automation use cases.
>
> IP Core Overview:
> The AMD LogiCORE 100M/1G TSN Subsystem IP solution (named as TSN Endpoint
> Ethernet MAC IP in the IP catalog) implements IEEE 802.1 Time Sensitive
> Networking (TSN) Standards and provides a low latency Bridged Endpoint or
> Endpoint only solutions.
So an Endpoint only solution is not connected to the switch? It
outputs RGMII, can have a PHY connected to it, and so is a single
netdev interface? You would typically use this in a client?
But you can also instantiate the same MAC multiple times, connected to
an Ethernet switch? That would be the bridged endpoint?
> The bridged endpoint solution consists of a 3-port
> switch that connects to an endpoint including Linux software drivers. For
> Bridged Endpoint (Switch Endpoint), two ports connects to the network and
> one port connects to an internal Endpoint.
To the host, does the internal endpoint just look like a standard
netdev?
What i'm trying to do is get an answer to: Is this a DSA switch, or a
pure switchdev switch. If the host sees a netdev which is connected to
a port of the switch, it is probably a DSA switch. If the host only
sees the user ports, it is probably a pure switchdev switch.
> It supports the use of
> GMII/RGMII interfaces connecting to a physical-side interface (PHY) chip
> with full duplex 100 Mb/s and 1 Gb/s operations.
No 10Mbps support?
> - Provides feature rich Ethernet Switch that caters to various network
> needs
> * 3-port Switch (2-external, 1-internal)
> * Programmable cut-through and store-forward operations
> * 4-port Switch (2-external, 2-internal) extension through
> 'Endpoint Extension' and 'Endpoint Packet Switching' features
Why not N-ports? Is it really set to 3 or 4? It cannot be synthesised
for 5, 8?
> Sample hardware architecture diagram for Bidge End Point like below:
>
> +------------------+
> | MCDMA |
> +---------+--------+
> Q0---Q7
> |
> +------------------------------------------------------------ +
> | | TSN sub system(Bridge End Point) |
> | | |
> | +------+----+ Port 0 +-----------------------+ |
> | | EndPoint |<--------->| TSN Switch | |
> | | (EP) | +----+-------------+----+ |
> | +-----------+ | | |
> | | | |
> | Port 1 Port 2 |
> | | | |
> | +-----------+ +-----------+ |
> | | MAC-1 | | MAC-2 | |
> | | (ETH1) | | (ETH2) | |
> | +-----+-----+ +-----+-----+ |
> | | | |
> | | | |
> +-------------------------------------------------------------+
> | |
> RGMII RGMII
> | |
> +-----------+ +-----------+
> | PHY1 | | PHY2 |
> | (Port 0) | | (Port 2) |
> +-----------+ +-----------+
>
So how does the host send a frame out Port 2? Is there an extra header
on the frame sent by EndPoint, which the switch interprets?
FYI: Seems like PHY1 (port 0) is a typO.
> - During driver initialization, all switch ports (Endpoint, MAC1, MAC2)
> are configured into the Forwarding state to enable data flow across the
> fabric.
Which is wrong. The Linux model is that switch ports are just
netdevs. You configure them just like every other netdev in the
system. Newly created netdevs are standalone. They only allow frames
to pass between the wire and the host. If you want them to L2 forwards
frames between ports you need to add them to a bridge.
Andrew
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [RFC PATCH 1/8] dt-bindings: net: Add TSN Endpoint Ethernet MAC support
2026-02-19 5:49 ` [RFC PATCH 1/8] dt-bindings: net: Add TSN Endpoint Ethernet MAC support Srinivas Neeli
@ 2026-02-19 16:53 ` Andrew Lunn
2026-02-20 13:03 ` Neeli, Srinivas
0 siblings, 1 reply; 27+ messages in thread
From: Andrew Lunn @ 2026-02-19 16:53 UTC (permalink / raw)
To: Srinivas Neeli
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, michal.simek, robh,
krzk+dt, conor+dt, richardcochran, netdev, linux-kernel,
devicetree, linux-arm-kernel, git
> +examples:
> + - |
> + tsn_ip: tsn@80040000 {
> + compatible = "xlnx,tsn-endpoint-ethernet-mac-3.0";
> + reg = <0x80040000 0x40000>;
> + clocks = <&misc_clk_2>, <&misc_clk_2>, <&misc_clk_1>, <&misc_clk_1>, <&misc_clk_3>, <&misc_clk_0>;
> + clock-names = "gtx", "gtx90", "host_rxfifo", "host_txfifo", "ref", "s_axi";
> + dmas = <&axi_mcdma_0 0>, <&axi_mcdma_0 1>, <&axi_mcdma_0 2>, <&axi_mcdma_0 3>,
> + <&axi_mcdma_0 4>, <&axi_mcdma_0 5>, <&axi_mcdma_0 6>, <&axi_mcdma_0 7>,
> + <&axi_mcdma_0 16>, <&axi_mcdma_0 17>, <&axi_mcdma_0 18>, <&axi_mcdma_0 19>,
> + <&axi_mcdma_0 20>, <&axi_mcdma_0 21>, <&axi_mcdma_0 22>, <&axi_mcdma_0 23>;
> + dma-names = "tx_chan0","tx_chan1","tx_chan2","tx_chan3","tx_chan4","tx_chan5","tx_chan6",
> + "tx_chan7","rx_chan0","rx_chan1","rx_chan2","rx_chan3","rx_chan4","rx_chan5",
> + "rx_chan6","rx_chan7";
> + xlnx,num-priorities = <8>;
> + #address-cells = <1>;
> + #size-cells = <1>;
> + ranges = <0x0 0x80040000 0x40000>;
> + xlnx,tsn-tx-config = <&tsn_tx_config>;
> + tsn_tx_config: tx-queues-config {
> + queue0 {
> + xlnx,dma-channel-num = <0x5>;
> + };
> + queue1 {
> + xlnx,dma-channel-num = <0x4>;
> + };
> + queue2 {
> + xlnx,dma-channel-num = <0x3>;
> + };
> + queue3 {
> + xlnx,dma-channel-num = <0x2>;
> + };
> + queue4 {
> + xlnx,dma-channel-num = <0x1>;
> + };
> + queue5 {
> + xlnx,dma-channel-num = <0x0>;
> + };
> + };
> + // MAC 1 Node
> + mac1: ethernet-mac@0 {
> + reg = <0x0 0x14000>;
> + phy-mode = "rgmii-id";
> + phy-handle = <&phy0>;
> + mdio {
> + #address-cells = <1>;
> + #size-cells = <0>;
> + phy0: ethernet-phy@0 {
> + device_type = "ethernet-phy";
> + reg = <0>;
> + };
> + };
Two } at the same level means your indentation is broken.
So each MAC has an MDIO node?
> + };
> +
> + // MAC 2 Node
> + mac2: ethernet-mac@20000 {
> + reg = <0x20000 0x14000>;
> + phy-mode = "rgmii-id";
> + phy-handle = <&phy1>;
> + mdio {
> + #address-cells = <1>;
> + #size-cells = <0>;
> + phy1: ethernet-phy@1 {
> + device_type = "ethernet-phy";
> + reg = <1>;
> + };
> + };
> + };
> +
> + // Endpoint Node
> + ep_mac: ep-mac@16000 {
> + reg = <0x16000 0xa000>;
> + };
Except the Endpoint MAC does not have MDIO? Or does it have an MDIO
bus, and you have simply not listed it?
> +
> + // Switch Node
> + tsn_switch: switch@38000 {
> + reg = <0x38000 0x8000>;
> +
> + ethernet-ports {
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + port@0 {
> + reg = <0>;
> + ethernet = <&ep_mac>;
> + };
So this looks like a DSA switch.
> +
> + port@1 {
> + reg = <1>;
> + ethernet = <&mac1>;
If you look at Documentation/devicetree/bindings/net/dsa/dsa-port.yaml
you see this node is derives from ethernet-switch-port.yaml, and that
derives from ethernet-controller.yaml. All the MAC properties you have
above actually belong here.
Andrew
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [RFC PATCH 4/8] xilinx: tsn: Add Ethernet MAC (EMAC) and MDIO support to the TSN driver
2026-02-19 5:49 ` [RFC PATCH 4/8] xilinx: tsn: Add Ethernet MAC (EMAC) and MDIO support to the TSN driver Srinivas Neeli
@ 2026-02-19 17:05 ` Andrew Lunn
2026-02-20 13:08 ` Neeli, Srinivas
2026-02-20 15:12 ` Andrew Lunn
1 sibling, 1 reply; 27+ messages in thread
From: Andrew Lunn @ 2026-02-19 17:05 UTC (permalink / raw)
To: Srinivas Neeli
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, michal.simek, robh,
krzk+dt, conor+dt, richardcochran, netdev, linux-kernel,
devicetree, linux-arm-kernel, git
> +static int tsn_mdio_read(struct mii_bus *bus, int phy_id, int reg)
> +{
> + u32 rc;
> + int ret;
> + struct tsn_emac *emac = bus->priv;
> + struct tsn_priv *common = emac->common;
> +
> + scoped_guard(mutex, &common->mdio_lock) {
What is this mutex protecting?
> + tsn_mdio_mdc_enable(emac);
It is unusual to stop MDC. I suspect some PHYs will not like this.
What is your reason for doing this.
> +/**
> + * tsn_mdio_setup - Setup MDIO bus for TSN EMAC
> + * @emac: Pointer to TSN EMAC structure
> + * @mac_np: Device tree node for MAC
> + *
> + * This function initializes the MDIO bus for the TSN EMAC interface.
> + * It allocates an MII bus structure, configures MDIO timing, finds
> + * the MDIO device tree node, and registers the MDIO bus with the kernel.
> + *
> + * Return: 0 on success, negative error code on failure
> + */
> +int tsn_mdio_setup(struct tsn_emac *emac, struct device_node *mac_np)
> +{
> + struct tsn_priv *common = emac->common;
> + struct device_node *mdio_node;
> + struct mii_bus *bus;
> + int ret;
> +
> + bus = mdiobus_alloc();
> + if (!bus)
> + return -ENOMEM;
> +
> + snprintf(bus->id, MII_BUS_ID_SIZE, "tsn-mac-%.8llx",
> + (unsigned long long)emac->regs_start);
> +
> + bus->priv = emac;
> + bus->name = "Xilinx TSN Ethernet MDIO";
> + bus->read = tsn_mdio_read;
> + bus->write = tsn_mdio_write;
> + bus->parent = common->dev;
> + emac->mii_bus = bus;
> +
> + mdio_node = of_get_child_by_name(mac_np, "mdio");
> + if (!mdio_node) {
> + dev_err(common->dev, "MAC%d: missing 'mdio' child node\n",
> + emac->emac_num);
> + ret = -ENODEV;
> + goto unregister;
> + }
I forget, does the binding have a required: for the MDIO node?
> + ret = tsn_mdio_enable(emac);
> + if (ret < 0)
> + goto unregister;
> + ret = of_mdiobus_register(bus, mdio_node);
Having a node in DT is generally optional. You can pass NULL to
of_mdiobus_register() and it will do the right thing.
Andrew
^ permalink raw reply [flat|nested] 27+ messages in thread
* RE: [RFC PATCH 0/8] xilinx: tsn: Add TSN Endpoint Ethernet MAC driver support
2026-02-19 16:42 ` Andrew Lunn
@ 2026-02-20 12:59 ` Neeli, Srinivas
2026-02-20 13:36 ` Andrew Lunn
0 siblings, 1 reply; 27+ messages in thread
From: Neeli, Srinivas @ 2026-02-20 12:59 UTC (permalink / raw)
To: Andrew Lunn
Cc: andrew+netdev@lunn.ch, davem@davemloft.net, edumazet@google.com,
kuba@kernel.org, pabeni@redhat.com, Simek, Michal,
robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org,
richardcochran@gmail.com, netdev@vger.kernel.org,
linux-kernel@vger.kernel.org, devicetree@vger.kernel.org,
linux-arm-kernel@lists.infradead.org, git (AMD-Xilinx)
[AMD Official Use Only - AMD Internal Distribution Only]
Hi Andrew,
> -----Original Message-----
> From: Andrew Lunn <andrew@lunn.ch>
> Sent: Thursday, February 19, 2026 10:13 PM
> To: Neeli, Srinivas <srinivas.neeli@amd.com>
> Cc: andrew+netdev@lunn.ch; davem@davemloft.net;
> edumazet@google.com; kuba@kernel.org; pabeni@redhat.com; Simek,
> Michal <michal.simek@amd.com>; robh@kernel.org; krzk+dt@kernel.org;
> conor+dt@kernel.org; richardcochran@gmail.com; netdev@vger.kernel.org;
> linux-kernel@vger.kernel.org; devicetree@vger.kernel.org; linux-arm-
> kernel@lists.infradead.org; git (AMD-Xilinx) <git@amd.com>
> Subject: Re: [RFC PATCH 0/8] xilinx: tsn: Add TSN Endpoint Ethernet MAC
> driver support
>
> On Thu, Feb 19, 2026 at 11:19:03AM +0530, Srinivas Neeli wrote:
> > Introduce a new network driver for the AMD LogiCORE 100M/1G TSN
> > Subsystem IP, also known as the TSN Endpoint Ethernet MAC, which
> > implements IEEE 802.1 Time-Sensitive Networking (TSN) features for
> > deterministic and low-latency Ethernet communication in real-time and
> > industrial automation use cases.
> >
> > IP Core Overview:
> > The AMD LogiCORE 100M/1G TSN Subsystem IP solution (named as TSN
> Endpoint
> > Ethernet MAC IP in the IP catalog) implements IEEE 802.1 Time Sensitive
> > Networking (TSN) Standards and provides a low latency Bridged Endpoint or
> > Endpoint only solutions.
>
> So an Endpoint only solution is not connected to the switch? It
> outputs RGMII, can have a PHY connected to it, and so is a single
> netdev interface? You would typically use this in a client?
>
> But you can also instantiate the same MAC multiple times, connected to
> an Ethernet switch? That would be the bridged endpoint?
>
Yes, that understanding is correct.
In Endpoint only configuration, the TSN Endpoint is connected Ethernet MAC and operates as a standalone MAC, connected via GMII/RGMII to a PHY,
and is exposed to Linux as a single netdev, similar to a conventional Ethernet controller.
In Bridged Endpoint (Switch Endpoint), two ports connects to the network and one port connects to an internal Endpoint.
The internal endpoint connects to the CPU and external ports connect to PHYs.
> > The bridged endpoint solution consists of a 3-port
> > switch that connects to an endpoint including Linux software drivers. For
> > Bridged Endpoint (Switch Endpoint), two ports connects to the network and
> > one port connects to an internal Endpoint.
>
> To the host, does the internal endpoint just look like a standard
> netdev?
Yes. From the host’s perspective, the internal endpoint is exposed as a standard Linux netdev.
>
> What i'm trying to do is get an answer to: Is this a DSA switch, or a
> pure switchdev switch. If the host sees a netdev which is connected to
> a port of the switch, it is probably a DSA switch. If the host only
> sees the user ports, it is probably a pure switchdev switch.
>
We are planning to implement a pure switchdev framework for the switch, as PTP packets are sent directly from
the netdev interfaces that represent the external network ports.
Please let me know your thoughts or suggestions if you feel a different approach would be more appropriate.
> > It supports the use of
> > GMII/RGMII interfaces connecting to a physical-side interface (PHY) chip
> > with full duplex 100 Mb/s and 1 Gb/s operations.
>
> No 10Mbps support?
Yes, that is correct. The current TSN Endpoint Ethernet MAC supports 100 Mb/s and 1 Gb/s full‑duplex modes only. 10 Mb/s operation is not supported by the underlying hardware IP.
>
> > - Provides feature rich Ethernet Switch that caters to various network
> > needs
> > * 3-port Switch (2-external, 1-internal)
> > * Programmable cut-through and store-forward operations
> > * 4-port Switch (2-external, 2-internal) extension through
> > 'Endpoint Extension' and 'Endpoint Packet Switching' features
>
> Why not N-ports? Is it really set to 3 or 4? It cannot be synthesised
> for 5, 8?
>
The number of ports is fixed by the IP configuration. The standard TSN Subsystem IP supports 3 port operation (2 external + 1 internal),
with an optional extension to 4 ports using endpoint extension features.
Arbitrary N‑port synthesis (for example 5 or 8 ports) is not supported.
> > Sample hardware architecture diagram for Bidge End Point like below:
> >
> > +------------------+
> > | MCDMA |
> > +---------+--------+
> > Q0---Q7
> > |
> > +------------------------------------------------------------ +
> > | | TSN sub system(Bridge End Point) |
> > | | |
> > | +------+----+ Port 0 +-----------------------+ |
> > | | EndPoint |<--------->| TSN Switch | |
> > | | (EP) | +----+-------------+----+ |
> > | +-----------+ | | |
> > | | | |
> > | Port 1 Port 2 |
> > | | | |
> > | +-----------+ +-----------+ |
> > | | MAC-1 | | MAC-2 | |
> > | | (ETH1) | | (ETH2) | |
> > | +-----+-----+ +-----+-----+ |
> > | | | |
> > | | | |
> > +-------------------------------------------------------------+
> > | |
> > RGMII RGMII
> > | |
> > +-----------+ +-----------+
> > | PHY1 | | PHY2 |
> > | (Port 0) | | (Port 2) |
> > +-----------+ +-----------+
> >
>
> So how does the host send a frame out Port 2? Is there an extra header
> on the frame sent by EndPoint, which the switch interprets?
>
In this RFC, I configured all switch ports in forward mode. As a result, when a frame is sent from the internal endpoint, it is flooded to both external ports.
To forward packets to a specific port instead of flooding, either static switch CAM entries need to be configured or address learning should be enabled so the switch can learn CAM entries dynamically.
> FYI: Seems like PHY1 (port 0) is a typO.
>
Here, PHY1 (port 0) and PHY2 (port 2) represent the external daughter card ports that I connected to the IP for testing.
I realize this representation may be confusing, so I will remove these port references to avoid any confusion.
> > - During driver initialization, all switch ports (Endpoint, MAC1, MAC2)
> > are configured into the Forwarding state to enable data flow across the
> > fabric.
>
> Which is wrong. The Linux model is that switch ports are just
> netdevs. You configure them just like every other netdev in the
> system. Newly created netdevs are standalone. They only allow frames
> to pass between the wire and the host. If you want them to L2 forwards
> frames between ports you need to add them to a bridge.
>
> Andrew
Currently, I have validated the behavior using forward mode. Based on the feedback, I will implement the required switch configuration changes in the next patch series.
Thanks
Neeli Srinivas
^ permalink raw reply [flat|nested] 27+ messages in thread
* RE: [RFC PATCH 1/8] dt-bindings: net: Add TSN Endpoint Ethernet MAC support
2026-02-19 16:53 ` Andrew Lunn
@ 2026-02-20 13:03 ` Neeli, Srinivas
2026-02-20 13:39 ` Andrew Lunn
0 siblings, 1 reply; 27+ messages in thread
From: Neeli, Srinivas @ 2026-02-20 13:03 UTC (permalink / raw)
To: Andrew Lunn
Cc: andrew+netdev@lunn.ch, davem@davemloft.net, edumazet@google.com,
kuba@kernel.org, pabeni@redhat.com, Simek, Michal,
robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org,
richardcochran@gmail.com, netdev@vger.kernel.org,
linux-kernel@vger.kernel.org, devicetree@vger.kernel.org,
linux-arm-kernel@lists.infradead.org, git (AMD-Xilinx)
[AMD Official Use Only - AMD Internal Distribution Only]
Hi,
> -----Original Message-----
> From: Andrew Lunn <andrew@lunn.ch>
> Sent: Thursday, February 19, 2026 10:24 PM
> To: Neeli, Srinivas <srinivas.neeli@amd.com>
> Cc: andrew+netdev@lunn.ch; davem@davemloft.net;
> edumazet@google.com; kuba@kernel.org; pabeni@redhat.com; Simek,
> Michal <michal.simek@amd.com>; robh@kernel.org; krzk+dt@kernel.org;
> conor+dt@kernel.org; richardcochran@gmail.com; netdev@vger.kernel.org;
> linux-kernel@vger.kernel.org; devicetree@vger.kernel.org; linux-arm-
> kernel@lists.infradead.org; git (AMD-Xilinx) <git@amd.com>
> Subject: Re: [RFC PATCH 1/8] dt-bindings: net: Add TSN Endpoint Ethernet
> MAC support
>
> > +examples:
> > + - |
> > + tsn_ip: tsn@80040000 {
> > + compatible = "xlnx,tsn-endpoint-ethernet-mac-3.0";
> > + reg = <0x80040000 0x40000>;
> > + clocks = <&misc_clk_2>, <&misc_clk_2>, <&misc_clk_1>,
> <&misc_clk_1>, <&misc_clk_3>, <&misc_clk_0>;
> > + clock-names = "gtx", "gtx90", "host_rxfifo", "host_txfifo", "ref", "s_axi";
> > + dmas = <&axi_mcdma_0 0>, <&axi_mcdma_0 1>, <&axi_mcdma_0 2>,
> <&axi_mcdma_0 3>,
> > + <&axi_mcdma_0 4>, <&axi_mcdma_0 5>, <&axi_mcdma_0 6>,
> <&axi_mcdma_0 7>,
> > + <&axi_mcdma_0 16>, <&axi_mcdma_0 17>, <&axi_mcdma_0 18>,
> <&axi_mcdma_0 19>,
> > + <&axi_mcdma_0 20>, <&axi_mcdma_0 21>, <&axi_mcdma_0 22>,
> <&axi_mcdma_0 23>;
> > + dma-names =
> "tx_chan0","tx_chan1","tx_chan2","tx_chan3","tx_chan4","tx_chan5","tx_ch
> an6",
> > +
> "tx_chan7","rx_chan0","rx_chan1","rx_chan2","rx_chan3","rx_chan4","rx_ch
> an5",
> > + "rx_chan6","rx_chan7";
> > + xlnx,num-priorities = <8>;
> > + #address-cells = <1>;
> > + #size-cells = <1>;
> > + ranges = <0x0 0x80040000 0x40000>;
> > + xlnx,tsn-tx-config = <&tsn_tx_config>;
> > + tsn_tx_config: tx-queues-config {
> > + queue0 {
> > + xlnx,dma-channel-num = <0x5>;
> > + };
> > + queue1 {
> > + xlnx,dma-channel-num = <0x4>;
> > + };
> > + queue2 {
> > + xlnx,dma-channel-num = <0x3>;
> > + };
> > + queue3 {
> > + xlnx,dma-channel-num = <0x2>;
> > + };
> > + queue4 {
> > + xlnx,dma-channel-num = <0x1>;
> > + };
> > + queue5 {
> > + xlnx,dma-channel-num = <0x0>;
> > + };
> > + };
> > + // MAC 1 Node
> > + mac1: ethernet-mac@0 {
> > + reg = <0x0 0x14000>;
> > + phy-mode = "rgmii-id";
> > + phy-handle = <&phy0>;
> > + mdio {
> > + #address-cells = <1>;
> > + #size-cells = <0>;
> > + phy0: ethernet-phy@0 {
> > + device_type = "ethernet-phy";
> > + reg = <0>;
> > + };
> > + };
>
> Two } at the same level means your indentation is broken.
>
I will address in next series.
> So each MAC has an MDIO node?
>
Yes, in this hardware configuration each external Ethernet MAC instance owns its own MDIO bus used to manage its directly connected PHY.
> > + };
> > +
> > + // MAC 2 Node
> > + mac2: ethernet-mac@20000 {
> > + reg = <0x20000 0x14000>;
> > + phy-mode = "rgmii-id";
> > + phy-handle = <&phy1>;
> > + mdio {
> > + #address-cells = <1>;
> > + #size-cells = <0>;
> > + phy1: ethernet-phy@1 {
> > + device_type = "ethernet-phy";
> > + reg = <1>;
> > + };
> > + };
> > + };
> > +
> > + // Endpoint Node
> > + ep_mac: ep-mac@16000 {
> > + reg = <0x16000 0xa000>;
> > + };
>
> Except the Endpoint MAC does not have MDIO? Or does it have an MDIO bus,
> and you have simply not listed it?
The endpoint MAC does not connect to an external PHY and therefore does not expose an MDIO bus.
It is an internal endpoint, so no MDIO node is required.
>
> > +
> > + // Switch Node
> > + tsn_switch: switch@38000 {
> > + reg = <0x38000 0x8000>;
> > +
> > + ethernet-ports {
> > + #address-cells = <1>;
> > + #size-cells = <0>;
> > +
> > + port@0 {
> > + reg = <0>;
> > + ethernet = <&ep_mac>;
> > + };
>
> So this looks like a DSA switch.
>
> > +
> > + port@1 {
> > + reg = <1>;
> > + ethernet = <&mac1>;
>
> If you look at Documentation/devicetree/bindings/net/dsa/dsa-port.yaml
> you see this node is derives from ethernet-switch-port.yaml, and that derives
> from ethernet-controller.yaml. All the MAC properties you have above actually
> belong here.
>
> Andrew
We referred to ethernet-switch-port.yaml while implementing the switch node. Our intention is to implement the switch using a pure switchdev framework in a future patch series.
Please let us know your suggestions.
Thanks
Neeli Srinivas
^ permalink raw reply [flat|nested] 27+ messages in thread
* RE: [RFC PATCH 4/8] xilinx: tsn: Add Ethernet MAC (EMAC) and MDIO support to the TSN driver
2026-02-19 17:05 ` Andrew Lunn
@ 2026-02-20 13:08 ` Neeli, Srinivas
2026-02-20 15:03 ` Andrew Lunn
0 siblings, 1 reply; 27+ messages in thread
From: Neeli, Srinivas @ 2026-02-20 13:08 UTC (permalink / raw)
To: Andrew Lunn
Cc: andrew+netdev@lunn.ch, davem@davemloft.net, edumazet@google.com,
kuba@kernel.org, pabeni@redhat.com, Simek, Michal,
robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org,
richardcochran@gmail.com, netdev@vger.kernel.org,
linux-kernel@vger.kernel.org, devicetree@vger.kernel.org,
linux-arm-kernel@lists.infradead.org, git (AMD-Xilinx)
[AMD Official Use Only - AMD Internal Distribution Only]
Hi,
> -----Original Message-----
> From: Andrew Lunn <andrew@lunn.ch>
> Sent: Thursday, February 19, 2026 10:36 PM
> To: Neeli, Srinivas <srinivas.neeli@amd.com>
> Cc: andrew+netdev@lunn.ch; davem@davemloft.net;
> edumazet@google.com; kuba@kernel.org; pabeni@redhat.com; Simek,
> Michal <michal.simek@amd.com>; robh@kernel.org; krzk+dt@kernel.org;
> conor+dt@kernel.org; richardcochran@gmail.com; netdev@vger.kernel.org;
> linux-kernel@vger.kernel.org; devicetree@vger.kernel.org; linux-arm-
> kernel@lists.infradead.org; git (AMD-Xilinx) <git@amd.com>
> Subject: Re: [RFC PATCH 4/8] xilinx: tsn: Add Ethernet MAC (EMAC) and MDIO
> support to the TSN driver
>
> > +static int tsn_mdio_read(struct mii_bus *bus, int phy_id, int reg) {
> > + u32 rc;
> > + int ret;
> > + struct tsn_emac *emac = bus->priv;
> > + struct tsn_priv *common = emac->common;
> > +
> > + scoped_guard(mutex, &common->mdio_lock) {
>
> What is this mutex protecting?
>
The mutex serializes access to the MDIO controller register block. The MDIO core already serializes bus accesses via bus->mdio_lock,
so the additional driver level mutex is redundant.
I will remove the extra mutex from the driver and rely on the MDIO core locking instead.
> > + tsn_mdio_mdc_enable(emac);
>
> It is unusual to stop MDC. I suspect some PHYs will not like this.
> What is your reason for doing this.
>
The intention is to reduce power consumption by gating the clock when it is not required.
> > +/**
> > + * tsn_mdio_setup - Setup MDIO bus for TSN EMAC
> > + * @emac: Pointer to TSN EMAC structure
> > + * @mac_np: Device tree node for MAC
> > + *
> > + * This function initializes the MDIO bus for the TSN EMAC interface.
> > + * It allocates an MII bus structure, configures MDIO timing, finds
> > + * the MDIO device tree node, and registers the MDIO bus with the kernel.
> > + *
> > + * Return: 0 on success, negative error code on failure */ int
> > +tsn_mdio_setup(struct tsn_emac *emac, struct device_node *mac_np) {
> > + struct tsn_priv *common = emac->common;
> > + struct device_node *mdio_node;
> > + struct mii_bus *bus;
> > + int ret;
> > +
> > + bus = mdiobus_alloc();
> > + if (!bus)
> > + return -ENOMEM;
> > +
> > + snprintf(bus->id, MII_BUS_ID_SIZE, "tsn-mac-%.8llx",
> > + (unsigned long long)emac->regs_start);
> > +
> > + bus->priv = emac;
> > + bus->name = "Xilinx TSN Ethernet MDIO";
> > + bus->read = tsn_mdio_read;
> > + bus->write = tsn_mdio_write;
> > + bus->parent = common->dev;
> > + emac->mii_bus = bus;
> > +
> > + mdio_node = of_get_child_by_name(mac_np, "mdio");
> > + if (!mdio_node) {
> > + dev_err(common->dev, "MAC%d: missing 'mdio' child
> node\n",
> > + emac->emac_num);
> > + ret = -ENODEV;
> > + goto unregister;
> > + }
>
> I forget, does the binding have a required: for the MDIO node?
>
MDIO node is not mandatory in binding. I will update the driver to treat the mdio chaild node as optional.
> > + ret = tsn_mdio_enable(emac);
> > + if (ret < 0)
> > + goto unregister;
> > + ret = of_mdiobus_register(bus, mdio_node);
>
> Having a node in DT is generally optional. You can pass NULL to
> of_mdiobus_register() and it will do the right thing.
>
> Andrew
Yes, agreed. I will update the implementation to pass NULL to of_mdiobus_register() when the MDIO DT node is absent, instead of failing probe.
Thanks
Neeli Srinivas
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [RFC PATCH 0/8] xilinx: tsn: Add TSN Endpoint Ethernet MAC driver support
2026-02-20 12:59 ` Neeli, Srinivas
@ 2026-02-20 13:36 ` Andrew Lunn
2026-03-05 11:46 ` Neeli, Srinivas
0 siblings, 1 reply; 27+ messages in thread
From: Andrew Lunn @ 2026-02-20 13:36 UTC (permalink / raw)
To: Neeli, Srinivas
Cc: andrew+netdev@lunn.ch, davem@davemloft.net, edumazet@google.com,
kuba@kernel.org, pabeni@redhat.com, Simek, Michal,
robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org,
richardcochran@gmail.com, netdev@vger.kernel.org,
linux-kernel@vger.kernel.org, devicetree@vger.kernel.org,
linux-arm-kernel@lists.infradead.org, git (AMD-Xilinx)
On Fri, Feb 20, 2026 at 12:59:16PM +0000, Neeli, Srinivas wrote:
> [AMD Official Use Only - AMD Internal Distribution Only]
Sorry, i'm not part of AMD...
> > So how does the host send a frame out Port 2? Is there an extra header
> > on the frame sent by EndPoint, which the switch interprets?
> >
>
> In this RFC, I configured all switch ports in forward mode. As a
> result, when a frame is sent from the internal endpoint, it is
> flooded to both external ports. To forward packets to a specific
> port instead of flooding, either static switch CAM entries need to
> be configured or address learning should be enabled so the switch
> can learn CAM entries dynamically.
Despite not being part of AMD, this part is important.
I don't care about how the RFC works, i want to know how the hardware
works, to ensure you have the correct choice of DSA vs pure switchdev.
Take the example of running Spanning Tree Protocol. The bridge needs
to send the BPDU out a specific port. What mechanism is used to do
that? It also needs to know which port a BPDU ingressed.
Andrew
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [RFC PATCH 1/8] dt-bindings: net: Add TSN Endpoint Ethernet MAC support
2026-02-20 13:03 ` Neeli, Srinivas
@ 2026-02-20 13:39 ` Andrew Lunn
2026-02-24 11:08 ` Neeli, Srinivas
0 siblings, 1 reply; 27+ messages in thread
From: Andrew Lunn @ 2026-02-20 13:39 UTC (permalink / raw)
To: Neeli, Srinivas
Cc: andrew+netdev@lunn.ch, davem@davemloft.net, edumazet@google.com,
kuba@kernel.org, pabeni@redhat.com, Simek, Michal,
robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org,
richardcochran@gmail.com, netdev@vger.kernel.org,
linux-kernel@vger.kernel.org, devicetree@vger.kernel.org,
linux-arm-kernel@lists.infradead.org, git (AMD-Xilinx)
> > > + // Endpoint Node
> > > + ep_mac: ep-mac@16000 {
> > > + reg = <0x16000 0xa000>;
> > > + };
> >
> > Except the Endpoint MAC does not have MDIO? Or does it have an MDIO bus,
> > and you have simply not listed it?
> The endpoint MAC does not connect to an external PHY and therefore does not expose an MDIO bus.
> It is an internal endpoint, so no MDIO node is required.
It does not really matter if it is required. Does it physically exist?
Can MDC and MDIO be routed to pins? Could i hang an external switch
off it?
DT describes hardware. If the hardware exists, describe it.
Andrew
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [RFC PATCH 4/8] xilinx: tsn: Add Ethernet MAC (EMAC) and MDIO support to the TSN driver
2026-02-20 13:08 ` Neeli, Srinivas
@ 2026-02-20 15:03 ` Andrew Lunn
2026-02-24 11:11 ` Neeli, Srinivas
0 siblings, 1 reply; 27+ messages in thread
From: Andrew Lunn @ 2026-02-20 15:03 UTC (permalink / raw)
To: Neeli, Srinivas
Cc: andrew+netdev@lunn.ch, davem@davemloft.net, edumazet@google.com,
kuba@kernel.org, pabeni@redhat.com, Simek, Michal,
robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org,
richardcochran@gmail.com, netdev@vger.kernel.org,
linux-kernel@vger.kernel.org, devicetree@vger.kernel.org,
linux-arm-kernel@lists.infradead.org, git (AMD-Xilinx)
> > > + tsn_mdio_mdc_enable(emac);
> >
> > It is unusual to stop MDC. I suspect some PHYs will not like this.
> > What is your reason for doing this.
> >
> The intention is to reduce power consumption by gating the clock when it is not required.
There is only one other MDIO bus driver that i know of which disables
the clock between transactions. And it does it because the board has
ageing problems, and if the clock it left ticking all the time, the
magic smoke eventually escapes.
How many different PHYs have you tested this with? How good a feeling
do you have this is not going to cause problems? How much power do you
actually save?
Andrew
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [RFC PATCH 4/8] xilinx: tsn: Add Ethernet MAC (EMAC) and MDIO support to the TSN driver
2026-02-19 5:49 ` [RFC PATCH 4/8] xilinx: tsn: Add Ethernet MAC (EMAC) and MDIO support to the TSN driver Srinivas Neeli
2026-02-19 17:05 ` Andrew Lunn
@ 2026-02-20 15:12 ` Andrew Lunn
2026-02-24 11:15 ` Neeli, Srinivas
1 sibling, 1 reply; 27+ messages in thread
From: Andrew Lunn @ 2026-02-20 15:12 UTC (permalink / raw)
To: Srinivas Neeli
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, michal.simek, robh,
krzk+dt, conor+dt, richardcochran, netdev, linux-kernel,
devicetree, linux-arm-kernel, git
> +#define TSN_EMMC_LINKSPEED_10 0x0 /* 10 Mbit */
If the MAC cannot do 10Mbps, why have this?
> +static int emac_open(struct net_device *ndev)
> +{
> + struct tsn_emac *emac = netdev_priv(ndev);
> + struct phy_device *phydev = NULL;
> +
> + if (emac->phy_node) {
> + phydev = of_phy_connect(emac->ndev, emac->phy_node,
> + tsn_adjust_link_tsn,
> + emac->phy_flags,
> + emac->phy_mode);
> + if (!phydev)
> + dev_err(emac->common->dev, "of_phy_connect() failed\n");
> + else
> + phy_start(phydev);
> + }
Somewhere around here, i would expect you to tell phylib the MAC
cannot do 10Mbps. You don't want the PHY offering those speeds for
autoneg, otherwise it might actually negotiate 10Mbps.
I also think you should be using phylink, not phylib. But we first
need to finish the discussion about DSA vs pure switchdev. If this
ends up being a DSA driver, you will be using phylink anywhere. If it
is a pure switchdev driver, i would still recommend using phylink, you
are less likely to get things wrong, because the API is better
designed.
Andrew
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [RFC PATCH 6/8] dt-bindings: net: Add PTP interrupt support
2026-02-19 5:49 ` [RFC PATCH 6/8] dt-bindings: net: Add PTP interrupt support Srinivas Neeli
@ 2026-02-20 15:17 ` Andrew Lunn
0 siblings, 0 replies; 27+ messages in thread
From: Andrew Lunn @ 2026-02-20 15:17 UTC (permalink / raw)
To: Srinivas Neeli
Cc: andrew+netdev, davem, edumazet, kuba, pabeni, michal.simek, robh,
krzk+dt, conor+dt, richardcochran, netdev, linux-kernel,
devicetree, linux-arm-kernel, git
> + interrupts:
> + minItems: 1
> + maxItems: 4
> + description:
> + Interrupt specifiers for MAC interrupts. MAC 1 (with PTP support)
> + requires 4 interrupts (ptp_rx, ptp_tx, mac_irq, ptp_timer).
> + MAC 2 (without PTP support) requires 3 interrupts (ptp_rx, ptp_tx, mac_irq).
You say 3 or 4 are required, yet have minItems: 1? 3 seems more
appropriate.
You have restraints later, so i don't know if these are even needed
here? I will let the DT Maintainers comment about that.
Andrew
^ permalink raw reply [flat|nested] 27+ messages in thread
* RE: [RFC PATCH 1/8] dt-bindings: net: Add TSN Endpoint Ethernet MAC support
2026-02-20 13:39 ` Andrew Lunn
@ 2026-02-24 11:08 ` Neeli, Srinivas
0 siblings, 0 replies; 27+ messages in thread
From: Neeli, Srinivas @ 2026-02-24 11:08 UTC (permalink / raw)
To: Andrew Lunn
Cc: andrew+netdev@lunn.ch, davem@davemloft.net, edumazet@google.com,
kuba@kernel.org, pabeni@redhat.com, Simek, Michal,
robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org,
richardcochran@gmail.com, netdev@vger.kernel.org,
linux-kernel@vger.kernel.org, devicetree@vger.kernel.org,
linux-arm-kernel@lists.infradead.org, git (AMD-Xilinx)
[Public]
Hi,
> -----Original Message-----
> From: Andrew Lunn <andrew@lunn.ch>
> Sent: Friday, February 20, 2026 7:09 PM
> To: Neeli, Srinivas <srinivas.neeli@amd.com>
> Cc: andrew+netdev@lunn.ch; davem@davemloft.net;
> edumazet@google.com; kuba@kernel.org; pabeni@redhat.com; Simek,
> Michal <michal.simek@amd.com>; robh@kernel.org; krzk+dt@kernel.org;
> conor+dt@kernel.org; richardcochran@gmail.com; netdev@vger.kernel.org;
> linux-kernel@vger.kernel.org; devicetree@vger.kernel.org; linux-arm-
> kernel@lists.infradead.org; git (AMD-Xilinx) <git@amd.com>
> Subject: Re: [RFC PATCH 1/8] dt-bindings: net: Add TSN Endpoint Ethernet
> MAC support
>
> > > > + // Endpoint Node
> > > > + ep_mac: ep-mac@16000 {
> > > > + reg = <0x16000 0xa000>;
> > > > + };
> > >
> > > Except the Endpoint MAC does not have MDIO? Or does it have an MDIO
> > > bus, and you have simply not listed it?
> > The endpoint MAC does not connect to an external PHY and therefore does
> not expose an MDIO bus.
> > It is an internal endpoint, so no MDIO node is required.
>
> It does not really matter if it is required. Does it physically exist?
> Can MDC and MDIO be routed to pins? Could i hang an external switch off it?
>
> DT describes hardware. If the hardware exists, describe it.
>
> Andrew
The endpoint does not have an MDIO controller in hardware, and such functionality does not physically exist.
As a result, attaching any external PHY or switch is not possible.
The Endpoint connected to the CPU through the AXI bus.
Thanks
Neeli Srinivas
^ permalink raw reply [flat|nested] 27+ messages in thread
* RE: [RFC PATCH 4/8] xilinx: tsn: Add Ethernet MAC (EMAC) and MDIO support to the TSN driver
2026-02-20 15:03 ` Andrew Lunn
@ 2026-02-24 11:11 ` Neeli, Srinivas
0 siblings, 0 replies; 27+ messages in thread
From: Neeli, Srinivas @ 2026-02-24 11:11 UTC (permalink / raw)
To: Andrew Lunn
Cc: andrew+netdev@lunn.ch, davem@davemloft.net, edumazet@google.com,
kuba@kernel.org, pabeni@redhat.com, Simek, Michal,
robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org,
richardcochran@gmail.com, netdev@vger.kernel.org,
linux-kernel@vger.kernel.org, devicetree@vger.kernel.org,
linux-arm-kernel@lists.infradead.org, git (AMD-Xilinx)
[Public]
Hi,
> -----Original Message-----
> From: Andrew Lunn <andrew@lunn.ch>
> Sent: Friday, February 20, 2026 8:33 PM
> To: Neeli, Srinivas <srinivas.neeli@amd.com>
> Cc: andrew+netdev@lunn.ch; davem@davemloft.net;
> edumazet@google.com; kuba@kernel.org; pabeni@redhat.com; Simek,
> Michal <michal.simek@amd.com>; robh@kernel.org; krzk+dt@kernel.org;
> conor+dt@kernel.org; richardcochran@gmail.com; netdev@vger.kernel.org;
> linux-kernel@vger.kernel.org; devicetree@vger.kernel.org; linux-arm-
> kernel@lists.infradead.org; git (AMD-Xilinx) <git@amd.com>
> Subject: Re: [RFC PATCH 4/8] xilinx: tsn: Add Ethernet MAC (EMAC) and MDIO
> support to the TSN driver
>
> > > > + tsn_mdio_mdc_enable(emac);
> > >
> > > It is unusual to stop MDC. I suspect some PHYs will not like this.
> > > What is your reason for doing this.
> > >
> > The intention is to reduce power consumption by gating the clock when it is
> not required.
>
> There is only one other MDIO bus driver that i know of which disables the
> clock between transactions. And it does it because the board has ageing
> problems, and if the clock it left ticking all the time, the magic smoke eventually
> escapes.
>
> How many different PHYs have you tested this with? How good a feeling do
> you have this is not going to cause problems? How much power do you
> actually save?
>
> Andrew
>
This change was expected to provide some power savings when the clock is disabled.
Since I have not validated this through practical measurements and have tested it with only one PHY, I have decided to drop the change.
Thanks for the guidance.
Thanks
Neeli Srinivas
^ permalink raw reply [flat|nested] 27+ messages in thread
* RE: [RFC PATCH 4/8] xilinx: tsn: Add Ethernet MAC (EMAC) and MDIO support to the TSN driver
2026-02-20 15:12 ` Andrew Lunn
@ 2026-02-24 11:15 ` Neeli, Srinivas
0 siblings, 0 replies; 27+ messages in thread
From: Neeli, Srinivas @ 2026-02-24 11:15 UTC (permalink / raw)
To: Andrew Lunn
Cc: andrew+netdev@lunn.ch, davem@davemloft.net, edumazet@google.com,
kuba@kernel.org, pabeni@redhat.com, Simek, Michal,
robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org,
richardcochran@gmail.com, netdev@vger.kernel.org,
linux-kernel@vger.kernel.org, devicetree@vger.kernel.org,
linux-arm-kernel@lists.infradead.org, git (AMD-Xilinx)
[Public]
Hi,
> -----Original Message-----
> From: Andrew Lunn <andrew@lunn.ch>
> Sent: Friday, February 20, 2026 8:43 PM
> To: Neeli, Srinivas <srinivas.neeli@amd.com>
> Cc: andrew+netdev@lunn.ch; davem@davemloft.net;
> edumazet@google.com; kuba@kernel.org; pabeni@redhat.com; Simek,
> Michal <michal.simek@amd.com>; robh@kernel.org; krzk+dt@kernel.org;
> conor+dt@kernel.org; richardcochran@gmail.com; netdev@vger.kernel.org;
> linux-kernel@vger.kernel.org; devicetree@vger.kernel.org; linux-arm-
> kernel@lists.infradead.org; git (AMD-Xilinx) <git@amd.com>
> Subject: Re: [RFC PATCH 4/8] xilinx: tsn: Add Ethernet MAC (EMAC) and MDIO
> support to the TSN driver
>
> > +#define TSN_EMMC_LINKSPEED_10 0x0 /* 10 Mbit */
>
> If the MAC cannot do 10Mbps, why have this?
>
> > +static int emac_open(struct net_device *ndev) {
> > + struct tsn_emac *emac = netdev_priv(ndev);
> > + struct phy_device *phydev = NULL;
> > +
> > + if (emac->phy_node) {
> > + phydev = of_phy_connect(emac->ndev, emac->phy_node,
> > + tsn_adjust_link_tsn,
> > + emac->phy_flags,
> > + emac->phy_mode);
> > + if (!phydev)
> > + dev_err(emac->common->dev, "of_phy_connect()
> failed\n");
> > + else
> > + phy_start(phydev);
> > + }
>
> Somewhere around here, i would expect you to tell phylib the MAC cannot do
> 10Mbps. You don't want the PHY offering those speeds for autoneg, otherwise
> it might actually negotiate 10Mbps.
>
> I also think you should be using phylink, not phylib. But we first need to finish
> the discussion about DSA vs pure switchdev. If this ends up being a DSA driver,
> you will be using phylink anywhere. If it is a pure switchdev driver, i would still
> recommend using phylink, you are less likely to get things wrong, because the
> API is better designed.
>
> Andrew
Thank you for the detailed feedback.
I agree with your comments. I plan to rework this driver to use the phylink framework instead of phylib, as it is a better fit and avoids getting these details wrong.
In the next revision, I will also address the 10 Mbps handling and ensure that unsupported link modes are properly constrained, so they are not advertised or negotiated.
Thanks for the guidance.
Thanks
Neeli Srinivas
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [RFC PATCH 0/8] xilinx: tsn: Add TSN Endpoint Ethernet MAC driver support
2026-02-20 13:36 ` Andrew Lunn
@ 2026-03-05 11:46 ` Neeli, Srinivas
2026-03-26 10:11 ` Neeli, Srinivas
0 siblings, 1 reply; 27+ messages in thread
From: Neeli, Srinivas @ 2026-03-05 11:46 UTC (permalink / raw)
To: Andrew Lunn, Neeli, Srinivas
Cc: andrew+netdev@lunn.ch, davem@davemloft.net, edumazet@google.com,
kuba@kernel.org, pabeni@redhat.com, Simek, Michal,
robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org,
richardcochran@gmail.com, netdev@vger.kernel.org,
linux-kernel@vger.kernel.org, devicetree@vger.kernel.org,
linux-arm-kernel@lists.infradead.org, git (AMD-Xilinx)
Hi Andrew,
On 2/20/2026 7:06 PM, Andrew Lunn wrote:
> On Fri, Feb 20, 2026 at 12:59:16PM +0000, Neeli, Srinivas wrote:
>> [AMD Official Use Only - AMD Internal Distribution Only]
> Sorry, i'm not part of AMD...
>
>>> So how does the host send a frame out Port 2? Is there an extra header
>>> on the frame sent by EndPoint, which the switch interprets?
>>>
>> In this RFC, I configured all switch ports in forward mode. As a
>> result, when a frame is sent from the internal endpoint, it is
>> flooded to both external ports. To forward packets to a specific
>> port instead of flooding, either static switch CAM entries need to
>> be configured or address learning should be enabled so the switch
>> can learn CAM entries dynamically.
> Despite not being part of AMD, this part is important.
>
> I don't care about how the RFC works, i want to know how the hardware
> works, to ensure you have the correct choice of DSA vs pure switchdev.
>
> Take the example of running Spanning Tree Protocol. The bridge needs
> to send the BPDU out a specific port. What mechanism is used to do
> that? It also needs to know which port a BPDU ingressed.
>
> Andrew
Hi Andrew,
I would like to briefly share an overview of our TSN switch capabilities
and seek your guidance on the most appropriate Linux framework for the
driver implementation specifically whether switchdev or DSA would be the
better fit.
TSN Switch Capabilities
-----------------------
Our TSN subsystem supports the following IEEE TSN clauses:
IEEE 802.1Qbv – Time-Aware Shaper (scheduled traffic using gate control)
IEEE 802.1Qbu / IEEE 802.3br – Frame preemption
IEEE 802.1Qci – Per-Stream Filtering and Policing (PSFP), including:
SDU-based filtering and Meter-based policing
IEEE 802.1CB – Frame Replication and Elimination for Reliability (FRER)
IEEE 802.1AS / IEEE 1588 – Time synchronization (PTP / gPTP)
Hardware Architecture Overview
------------------------------
The switch consists of three ports:
Port 0: Connected to the CPU (control/endpoint port)
Port 1: Connected to MAC1
Port 2: Connected to MAC2
MAC1 and MAC2 are capable of transmitting and receiving PTP packets,
with received packets stored in internal BRAM. They will not be
forwarded by switch to the internal endpoint (EP) and MAC network
drivers xmit's and receives the PTP frames.
The switch forwards frames based on VLAN port membership and the CAM
entries and switch supports TSN features such as CBS, Qci (PSFP) and
802.1CB (FRER) through hardware configuration.
The CPU is intended to operate purely in the control plane and is not
part of the forwarding data path.
Thank you very much for your time and guidance. Please let us know if
any additional details would be helpful.
Best regards,
Neeli Srinivas
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [RFC PATCH 0/8] xilinx: tsn: Add TSN Endpoint Ethernet MAC driver support
2026-03-05 11:46 ` Neeli, Srinivas
@ 2026-03-26 10:11 ` Neeli, Srinivas
0 siblings, 0 replies; 27+ messages in thread
From: Neeli, Srinivas @ 2026-03-26 10:11 UTC (permalink / raw)
To: Andrew Lunn, Neeli, Srinivas
Cc: andrew+netdev@lunn.ch, davem@davemloft.net, edumazet@google.com,
kuba@kernel.org, pabeni@redhat.com, Simek, Michal,
robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org,
richardcochran@gmail.com, netdev@vger.kernel.org,
linux-kernel@vger.kernel.org, devicetree@vger.kernel.org,
linux-arm-kernel@lists.infradead.org, git (AMD-Xilinx)
On 3/5/2026 5:16 PM, Neeli, Srinivas wrote:
> Hi Andrew,
>
> On 2/20/2026 7:06 PM, Andrew Lunn wrote:
>> On Fri, Feb 20, 2026 at 12:59:16PM +0000, Neeli, Srinivas wrote:
>>> [AMD Official Use Only - AMD Internal Distribution Only]
>> Sorry, i'm not part of AMD...
>>
>>>> So how does the host send a frame out Port 2? Is there an extra header
>>>> on the frame sent by EndPoint, which the switch interprets?
>>>>
>>> In this RFC, I configured all switch ports in forward mode. As a
>>> result, when a frame is sent from the internal endpoint, it is
>>> flooded to both external ports. To forward packets to a specific
>>> port instead of flooding, either static switch CAM entries need to
>>> be configured or address learning should be enabled so the switch
>>> can learn CAM entries dynamically.
>> Despite not being part of AMD, this part is important.
>>
>> I don't care about how the RFC works, i want to know how the hardware
>> works, to ensure you have the correct choice of DSA vs pure switchdev.
>>
>> Take the example of running Spanning Tree Protocol. The bridge needs
>> to send the BPDU out a specific port. What mechanism is used to do
>> that? It also needs to know which port a BPDU ingressed.
>>
>> Andrew
>
>
> Hi Andrew,
>
> I would like to briefly share an overview of our TSN switch
> capabilities and seek your guidance on the most appropriate Linux
> framework for the driver implementation specifically whether switchdev
> or DSA would be the better fit.
>
> TSN Switch Capabilities
> -----------------------
> Our TSN subsystem supports the following IEEE TSN clauses:
>
> IEEE 802.1Qbv – Time-Aware Shaper (scheduled traffic using gate control)
> IEEE 802.1Qbu / IEEE 802.3br – Frame preemption
> IEEE 802.1Qci – Per-Stream Filtering and Policing (PSFP), including:
> SDU-based filtering and Meter-based policing
> IEEE 802.1CB – Frame Replication and Elimination for Reliability (FRER)
> IEEE 802.1AS / IEEE 1588 – Time synchronization (PTP / gPTP)
>
> Hardware Architecture Overview
> ------------------------------
> The switch consists of three ports:
>
> Port 0: Connected to the CPU (control/endpoint port)
> Port 1: Connected to MAC1
> Port 2: Connected to MAC2
>
> MAC1 and MAC2 are capable of transmitting and receiving PTP packets,
> with received packets stored in internal BRAM. They will not be
> forwarded by switch to the internal endpoint (EP) and MAC network
> drivers xmit's and receives the PTP frames.
> The switch forwards frames based on VLAN port membership and the CAM
> entries and switch supports TSN features such as CBS, Qci (PSFP) and
> 802.1CB (FRER) through hardware configuration.
> The CPU is intended to operate purely in the control plane and is not
> part of the forwarding data path.
>
> Thank you very much for your time and guidance. Please let us know if
> any additional details would be helpful.
>
>
> Best regards,
> Neeli Srinivas
>
Hi Andrew,
Based on the feedback so far, I am planning to proceed with a switchdev
based implementation for the next RFC series, as this appears to be a
better fit with the Linux networking model.
Please let me know if you have any concerns with this approach. If this
direction is acceptable, I will share the next version of the RFC series
accordingly.
Thank you for your guidance.
Best regards,
Neeli Srinivas
^ permalink raw reply [flat|nested] 27+ messages in thread
end of thread, other threads:[~2026-03-26 10:12 UTC | newest]
Thread overview: 27+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-02-19 5:49 [RFC PATCH 0/8] xilinx: tsn: Add TSN Endpoint Ethernet MAC driver support Srinivas Neeli
2026-02-19 5:49 ` [RFC PATCH 1/8] dt-bindings: net: Add TSN Endpoint Ethernet MAC support Srinivas Neeli
2026-02-19 16:53 ` Andrew Lunn
2026-02-20 13:03 ` Neeli, Srinivas
2026-02-20 13:39 ` Andrew Lunn
2026-02-24 11:08 ` Neeli, Srinivas
2026-02-19 5:49 ` [RFC PATCH 2/8] net: xilinx: tsn: Introduce TSN core driver skeleton Srinivas Neeli
2026-02-19 7:32 ` Krzysztof Kozlowski
2026-02-19 5:49 ` [RFC PATCH 3/8] net: xilinx: tsn: Add TSN endpoint and MCDMA support Srinivas Neeli
2026-02-19 5:49 ` [RFC PATCH 4/8] xilinx: tsn: Add Ethernet MAC (EMAC) and MDIO support to the TSN driver Srinivas Neeli
2026-02-19 17:05 ` Andrew Lunn
2026-02-20 13:08 ` Neeli, Srinivas
2026-02-20 15:03 ` Andrew Lunn
2026-02-24 11:11 ` Neeli, Srinivas
2026-02-20 15:12 ` Andrew Lunn
2026-02-24 11:15 ` Neeli, Srinivas
2026-02-19 5:49 ` [RFC PATCH 5/8] net: xilinx: tsn: Add TSN switch support with port state and frame filter control Srinivas Neeli
2026-02-19 5:49 ` [RFC PATCH 6/8] dt-bindings: net: Add PTP interrupt support Srinivas Neeli
2026-02-20 15:17 ` Andrew Lunn
2026-02-19 5:49 ` [RFC PATCH 7/8] net: xilinx: tsn: Add PTP hardware clock (PHC) and timer support Srinivas Neeli
2026-02-19 5:49 ` [RFC PATCH 8/8] net: xilinx: tsn: Add PTP packet transmission support Srinivas Neeli
2026-02-19 7:34 ` [RFC PATCH 0/8] xilinx: tsn: Add TSN Endpoint Ethernet MAC driver support Krzysztof Kozlowski
2026-02-19 16:42 ` Andrew Lunn
2026-02-20 12:59 ` Neeli, Srinivas
2026-02-20 13:36 ` Andrew Lunn
2026-03-05 11:46 ` Neeli, Srinivas
2026-03-26 10:11 ` Neeli, Srinivas
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox