Re: [RFC PATCH 2/8] net: xilinx: tsn: Introduce TSN core driver skeleton
From: Krzysztof Kozlowski
Date: Thu Feb 19 2026 - 02:33:57 EST
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@xxxxxxx>
> ---
> 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@xxxxxxx>");
> +MODULE_DESCRIPTION("Time Sensitive Networking (TSN) Ethernet MAC driver");
> +MODULE_LICENSE("GPL");
Best regards,
Krzysztof