diff mbox series

[net-next] net: ethernet: rtsn: Add support for Renesas Ethernet-TSN

Message ID 20240414135937.1139611-1-niklas.soderlund+renesas@ragnatech.se (mailing list archive)
State Changes Requested
Delegated to: Netdev Maintainers
Headers show
Series [net-next] net: ethernet: rtsn: Add support for Renesas Ethernet-TSN | expand

Checks

Context Check Description
netdev/series_format success Single patches do not need cover letters
netdev/tree_selection success Clearly marked for net-next
netdev/ynl success Generated files up to date; no warnings/errors; no diff in generated;
netdev/fixes_present success Fixes tag not required for -next series
netdev/header_inline success No static functions without inline keyword in header files
netdev/build_32bit fail Errors and warnings before: 932 this patch: 22
netdev/build_tools success No tools touched, skip
netdev/cc_maintainers warning 7 maintainers not CCed: wsa+renesas@sang-engineering.com s.shtylyov@omp.ru magnus.damm@gmail.com geert+renesas@glider.be richardcochran@gmail.com p.zabel@pengutronix.de niklas.soderlund@ragnatech.se
netdev/build_clang fail Errors and warnings before: 938 this patch: 18
netdev/verify_signedoff success Signed-off-by tag matches author and committer
netdev/deprecated_api success None detected
netdev/check_selftest success No net selftest shell script
netdev/verify_fixes success No Fixes tag
netdev/build_allmodconfig_warn fail Errors and warnings before: 944 this patch: 23
netdev/checkpatch warning WARNING: line length of 81 exceeds 80 columns WARNING: line length of 83 exceeds 80 columns WARNING: line length of 84 exceeds 80 columns WARNING: line length of 85 exceeds 80 columns WARNING: line length of 88 exceeds 80 columns WARNING: line length of 90 exceeds 80 columns WARNING: line length of 91 exceeds 80 columns WARNING: please write a help paragraph that fully describes the config symbol
netdev/build_clang_rust success No Rust files in patch. Skipping build
netdev/kdoc success Errors and warnings before: 0 this patch: 0
netdev/source_inline success Was 0 now: 0

Commit Message

Niklas Söderlund April 14, 2024, 1:59 p.m. UTC
Add initial support for Renesas Ethernet-TSN End-station device of R-Car
V4H. The Ethernet End-station can connect to an Ethernet network using a
10 Mbps, 100 Mbps, or 1 Gbps full-duplex link via MII/GMII/RMII/RGMII.
Depending on the connected PHY.

Signed-off-by: Niklas Söderlund <niklas.soderlund+renesas@ragnatech.se>
---
* Changes since RFC
- Fix issues in MDIO communication.
- Use a dedicated OF node for the MDIO bus.
---
 MAINTAINERS                           |    8 +
 drivers/net/ethernet/renesas/Kconfig  |   11 +
 drivers/net/ethernet/renesas/Makefile |    2 +
 drivers/net/ethernet/renesas/rtsn.c   | 1421 +++++++++++++++++++++++++
 drivers/net/ethernet/renesas/rtsn.h   |  464 ++++++++
 5 files changed, 1906 insertions(+)
 create mode 100644 drivers/net/ethernet/renesas/rtsn.c
 create mode 100644 drivers/net/ethernet/renesas/rtsn.h

Comments

Paul Barker April 15, 2024, 7:34 a.m. UTC | #1
On 14/04/2024 14:59, Niklas Söderlund wrote:
> Add initial support for Renesas Ethernet-TSN End-station device of R-Car
> V4H. The Ethernet End-station can connect to an Ethernet network using a
> 10 Mbps, 100 Mbps, or 1 Gbps full-duplex link via MII/GMII/RMII/RGMII.
> Depending on the connected PHY.
> 
> Signed-off-by: Niklas Söderlund <niklas.soderlund+renesas@ragnatech.se>
> ---
> * Changes since RFC
> - Fix issues in MDIO communication.
> - Use a dedicated OF node for the MDIO bus.
> ---
>  MAINTAINERS                           |    8 +
>  drivers/net/ethernet/renesas/Kconfig  |   11 +
>  drivers/net/ethernet/renesas/Makefile |    2 +
>  drivers/net/ethernet/renesas/rtsn.c   | 1421 +++++++++++++++++++++++++
>  drivers/net/ethernet/renesas/rtsn.h   |  464 ++++++++
>  5 files changed, 1906 insertions(+)
>  create mode 100644 drivers/net/ethernet/renesas/rtsn.c
>  create mode 100644 drivers/net/ethernet/renesas/rtsn.h

<snip>

> diff --git a/drivers/net/ethernet/renesas/rtsn.c b/drivers/net/ethernet/renesas/rtsn.c
> new file mode 100644
> index 000000000000..291ab421d68f
> --- /dev/null
> +++ b/drivers/net/ethernet/renesas/rtsn.c

<snip>

> +static bool rtsn_rx(struct net_device *ndev, int *quota)
> +{
> +	struct rtsn_ext_ts_desc *desc;
> +	struct rtsn_private *priv;
> +	struct sk_buff *skb;
> +	dma_addr_t dma_addr;
> +	int boguscnt;

I find the variable name `boguscnt` very unclear, I'm not sure if it
means the count is bogus, or it is counting bogus items?

I don't think you need to match what I've done in ravb_main.c exactly,
but I'd prefer to see a better variable name here.

> +	u16 pkt_len;
> +	u32 get_ts;
> +	int entry;
> +	int limit;
> +
> +	priv = netdev_priv(ndev);
> +
> +	entry = priv->cur_rx % priv->num_rx_ring;
> +	desc = &priv->rx_ring[entry];
> +
> +	boguscnt = priv->dirty_rx + priv->num_rx_ring - priv->cur_rx;
> +	boguscnt = min(boguscnt, *quota);
> +	limit = boguscnt;
> +
> +	while ((desc->die_dt & DT_MASK) != DT_FEMPTY) {
> +		dma_rmb();
> +		pkt_len = le16_to_cpu(desc->info_ds) & RX_DS;
> +		if (--boguscnt < 0)
> +			break;
> +
> +		skb = priv->rx_skb[entry];
> +		priv->rx_skb[entry] = NULL;
> +		dma_addr = le32_to_cpu(desc->dptr);
> +		dma_unmap_single(ndev->dev.parent, dma_addr, PKT_BUF_SZ,
> +				 DMA_FROM_DEVICE);
> +
> +		get_ts = priv->ptp_priv->tstamp_rx_ctrl &
> +			RCAR_GEN4_RXTSTAMP_TYPE_V2_L2_EVENT;
> +		if (get_ts) {
> +			struct skb_shared_hwtstamps *shhwtstamps;
> +			struct timespec64 ts;
> +
> +			shhwtstamps = skb_hwtstamps(skb);
> +			memset(shhwtstamps, 0, sizeof(*shhwtstamps));
> +
> +			ts.tv_sec = (u64)le32_to_cpu(desc->ts_sec);
> +			ts.tv_nsec = le32_to_cpu(desc->ts_nsec & cpu_to_le32(0x3fffffff));
> +
> +			shhwtstamps->hwtstamp = timespec64_to_ktime(ts);
> +		}
> +
> +		skb_put(skb, pkt_len);
> +		skb->protocol = eth_type_trans(skb, ndev);
> +		netif_receive_skb(skb);
> +		ndev->stats.rx_packets++;
> +		ndev->stats.rx_bytes += pkt_len;
> +
> +		entry = (++priv->cur_rx) % priv->num_rx_ring;
> +		desc = &priv->rx_ring[entry];
> +	}
> +
> +	/* Refill the RX ring buffers */
> +	for (; priv->cur_rx - priv->dirty_rx > 0; priv->dirty_rx++) {
> +		entry = priv->dirty_rx % priv->num_rx_ring;
> +		desc = &priv->rx_ring[entry];
> +		desc->info_ds = cpu_to_le16(PKT_BUF_SZ);
> +
> +		if (!priv->rx_skb[entry]) {
> +			skb = netdev_alloc_skb(ndev,
> +					       PKT_BUF_SZ + RTSN_ALIGN - 1);

I'll send my work using a page pool today as an RFC so you can see if it
would be beneficial to use that here as well. I was going to hold off
until the bugfix patches have merged so that I don't need to go through
another RFC round, but it will be good to get some more review on the
series anyway.

> +			if (!skb)
> +				break;
> +			skb_reserve(skb, NET_IP_ALIGN);
> +			dma_addr = dma_map_single(ndev->dev.parent, skb->data,
> +						  le16_to_cpu(desc->info_ds),
> +						  DMA_FROM_DEVICE);
> +			if (dma_mapping_error(ndev->dev.parent, dma_addr))
> +				desc->info_ds = cpu_to_le16(0);
> +			desc->dptr = cpu_to_le32(dma_addr);
> +			skb_checksum_none_assert(skb);
> +			priv->rx_skb[entry] = skb;
> +		}
> +		dma_wmb();
> +		desc->die_dt = DT_FEMPTY | D_DIE;
> +	}
> +
> +	desc = &priv->rx_ring[priv->num_rx_ring];
> +	desc->die_dt = DT_LINK;
> +
> +	*quota -= limit - (++boguscnt);
> +
> +	return boguscnt <= 0;
> +}
> +
> +static int rtsn_poll(struct napi_struct *napi, int budget)
> +{
> +	struct rtsn_private *priv;
> +	struct net_device *ndev;
> +	unsigned long flags;
> +	int quota = budget;
> +
> +	ndev = napi->dev;
> +	priv = netdev_priv(ndev);
> +
> +	/* Processing RX Descriptor Ring */
> +	if (rtsn_rx(ndev, &quota))
> +		goto out;
> +
> +	/* Processing TX Descriptor Ring */
> +	spin_lock_irqsave(&priv->lock, flags);
> +	rtsn_tx_free(ndev, true);
> +	netif_wake_subqueue(ndev, 0);
> +	spin_unlock_irqrestore(&priv->lock, flags);
> +
> +	napi_complete(napi);

We should use napi_complete_done() here as described in
Documentation/networking/napi.rst. That will require rtsn_rx() to return
the number of packets received so that it can be passed as the work_done
argument to napi_complete_done().

> +
> +	/* Re-enable TX/RX interrupts */
> +	spin_lock_irqsave(&priv->lock, flags);
> +	rtsn_ctrl_data_irq(priv, true);
> +	__iowmb();
> +	spin_unlock_irqrestore(&priv->lock, flags);
> +out:
> +	return budget - quota;
> +}

<snip>

> +static int rtsn_probe(struct platform_device *pdev)
> +{
> +	struct rtsn_private *priv;
> +	struct net_device *ndev;
> +	struct resource *res;
> +	int ret;
> +
> +	ndev = alloc_etherdev_mqs(sizeof(struct rtsn_private), TX_NUM_CHAINS,
> +				  RX_NUM_CHAINS);
> +	if (!ndev)
> +		return -ENOMEM;
> +
> +	priv = netdev_priv(ndev);
> +	priv->pdev = pdev;
> +	priv->ndev = ndev;
> +	priv->ptp_priv = rcar_gen4_ptp_alloc(pdev);
> +
> +	spin_lock_init(&priv->lock);
> +	platform_set_drvdata(pdev, priv);
> +
> +	priv->clk = devm_clk_get(&pdev->dev, NULL);
> +	if (IS_ERR(priv->clk)) {
> +		ret = -PTR_ERR(priv->clk);
> +		goto error_alloc;
> +	}
> +
> +	priv->reset = devm_reset_control_get(&pdev->dev, NULL);
> +	if (IS_ERR(priv->reset)) {
> +		ret = -PTR_ERR(priv->reset);
> +		goto error_alloc;
> +	}
> +
> +	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "tsnes");
> +	if (!res) {
> +		dev_err(&pdev->dev, "Can't find tsnes resource\n");
> +		ret = -EINVAL;
> +		goto error_alloc;
> +	}
> +
> +	priv->base = devm_ioremap_resource(&pdev->dev, res);
> +	if (IS_ERR(priv->base)) {
> +		ret = PTR_ERR(priv->base);
> +		goto error_alloc;
> +	}
> +
> +	SET_NETDEV_DEV(ndev, &pdev->dev);
> +	ether_setup(ndev);
> +
> +	ndev->features = NETIF_F_RXCSUM;
> +	ndev->hw_features = NETIF_F_RXCSUM;

A quick skim of the datasheet suggests that TX checksum calculation is
also supported. It's probably worth listing which hardware features this
driver supports/does not support in the commit message.

Thanks,
Andrew Lunn April 15, 2024, 10:55 p.m. UTC | #2
> +static int rtsn_get_phy_params(struct rtsn_private *priv)
> +{
> +	struct device_node *np = priv->ndev->dev.parent->of_node;
> +
> +	of_get_phy_mode(np, &priv->iface);
> +	switch (priv->iface) {
> +	case PHY_INTERFACE_MODE_MII:
> +		priv->speed = 100;
> +		break;
> +	case PHY_INTERFACE_MODE_RGMII:

There are 4 different RGMII modes, and you probably should be using
PHY_INTERFACE_MODE_RGMII_ID with the PHY. So you should list them all
here.

> +		priv->speed = 1000;
> +		break;
> +	default:
> +		return -EOPNOTSUPP;
> +	}
> +
> +	return 0;
> +}
> +
> +static void rtsn_set_phy_interface(struct rtsn_private *priv)
> +{
> +	u32 val;
> +
> +	switch (priv->iface) {
> +	case PHY_INTERFACE_MODE_MII:
> +		val = MPIC_PIS_MII;
> +		break;
> +	case PHY_INTERFACE_MODE_RGMII:

And here.

> +		val = MPIC_PIS_GMII;
> +		break;
> +	default:
> +		return;
> +	}
> +
> +	rtsn_modify(priv, MPIC, MPIC_PIS_MASK, val);
> +}
> +
> +static void rtsn_set_delay_mode(struct rtsn_private *priv)
> +{
> +	struct device_node *np = priv->ndev->dev.parent->of_node;
> +	u32 delay;
> +	u32 val;
> +
> +	val = 0;
> +
> +	/* Valid values are 0 and 1800, according to DT bindings */

The bindings should not matter. It is what the hardware supports. The
bindings should match the hardware, since it is hard to modify the
hardware to make it match the binding.

> +	if (!of_property_read_u32(np, "rx-internal-delay-ps", &delay))
> +		if (delay)
> +			val |= GPOUT_RDM;
> +
> +	/* Valid values are 0 and 2000, according to DT bindings */
> +	if (!of_property_read_u32(np, "tx-internal-delay-ps", &delay))
> +		if (delay)
> +			val |= GPOUT_TDM;
> +
> +	rtsn_write(priv, GPOUT, val);

So you seem to be using it as bool? That is wrong. It is a number of
pico seconds!

> +static int rtsn_mii_access_indirect(struct mii_bus *bus, bool read, int phyad,
> +				    int devnum, int regnum, u16 data)
> +{
> +	int ret;
> +
> +	ret = rtsn_mii_access(bus, false, phyad, MII_MMD_CTRL, devnum);
> +	if (ret)
> +		return ret;
> +
> +	ret = rtsn_mii_access(bus, false, phyad, MII_MMD_DATA, regnum);
> +	if (ret)
> +		return ret;
> +
> +	ret = rtsn_mii_access(bus, false, phyad, MII_MMD_CTRL,
> +			      devnum | MII_MMD_CTRL_NOINCR);

This looks to be C45 over C22. phylib core knows how to do this, since
it should be the same for all PHYs which implement C45 over C22. So
there is no need for you to implement it again.

> +static int rtsn_mii_register(struct rtsn_private *priv)
> +{
> +	struct platform_device *pdev = priv->pdev;
> +	struct device *dev = &pdev->dev;
> +	struct device_node *mdio_node;
> +	struct mii_bus *mii;
> +	int ret;
> +
> +	mii = mdiobus_alloc();
> +	if (!mii)
> +		return -ENOMEM;
> +
> +	mdio_node = of_get_child_by_name(dev->of_node, "mdio");
> +	if (!mdio_node) {
> +		ret = -ENODEV;
> +		goto out_free_bus;
> +	};
> +
> +	mii->name = "rtsn_mii";
> +	sprintf(mii->id, "%s-%x", pdev->name, pdev->id);
> +	mii->priv = priv;
> +	mii->read = rtsn_mii_read;
> +	mii->write = rtsn_mii_write;
> +	mii->read_c45 = rtsn_mii_read_c45;
> +	mii->write_c45 = rtsn_mii_write_c45;

Just leave these two empty, and the core will do C45 over C22 for you.

> +static void rtsn_phy_deinit(struct rtsn_private *priv)
> +{
> +	phy_stop(priv->ndev->phydev);

I would normally expect rtsn_phy_init() and rtsn_phy_deinit() to be
mirrors. You don't call phy_start() in rtsn_phy_init(), so why do you
call phy_stop() here? It probably should be somewhere else.

> +	phy_disconnect(priv->ndev->phydev);
> +	priv->ndev->phydev = NULL;
> +}


> +static int rtsn_open(struct net_device *ndev)
> +{
> +	struct rtsn_private *priv = netdev_priv(ndev);
> +	int ret;
> +
> +	napi_enable(&priv->napi);
> +
> +	ret = rtsn_init(priv);
> +	if (ret) {
> +		napi_disable(&priv->napi);
> +		return ret;
> +	}
> +
> +	phy_start(ndev->phydev);
> +
> +	netif_start_queue(ndev);
> +
> +	return 0;
> +}
> +
> +static int rtsn_stop(struct net_device *ndev)
> +{
> +	struct rtsn_private *priv = netdev_priv(ndev);

This is probably where your phy_stop() belongs.

> +
> +	napi_disable(&priv->napi);
> +	rtsn_change_mode(priv, OCR_OPC_DISABLE);
> +	rtsn_deinit(priv);
> +
> +	return 0;
> +}

> +
> +static int rtsn_do_ioctl(struct net_device *ndev, struct ifreq *req, int cmd)
> +{
> +	if (!netif_running(ndev))
> +		return -EINVAL;
> +
> +	switch (cmd) {
> +	case SIOCGHWTSTAMP:
> +		return rtsn_hwstamp_get(ndev, req);
> +	case SIOCSHWTSTAMP:
> +		return rtsn_hwstamp_set(ndev, req);
> +	default:
> +		break;
> +	}
> +
> +	return 0;

Call phy_do_ioctl() rather than return 0. That allows the PHY driver
to handle its IOCTLs.

> +static int rtsn_probe(struct platform_device *pdev)
> +{
> +	struct rtsn_private *priv;
> +	struct net_device *ndev;
> +	struct resource *res;
> +	int ret;
> +
> +	ndev = alloc_etherdev_mqs(sizeof(struct rtsn_private), TX_NUM_CHAINS,
> +				  RX_NUM_CHAINS);
> +	if (!ndev)
> +		return -ENOMEM;
> +
> +	priv = netdev_priv(ndev);
> +	priv->pdev = pdev;
> +	priv->ndev = ndev;
> +	priv->ptp_priv = rcar_gen4_ptp_alloc(pdev);
> +
> +	spin_lock_init(&priv->lock);
> +	platform_set_drvdata(pdev, priv);
> +
> +	priv->clk = devm_clk_get(&pdev->dev, NULL);
> +	if (IS_ERR(priv->clk)) {
> +		ret = -PTR_ERR(priv->clk);
> +		goto error_alloc;
> +	}
> +
> +	priv->reset = devm_reset_control_get(&pdev->dev, NULL);
> +	if (IS_ERR(priv->reset)) {
> +		ret = -PTR_ERR(priv->reset);
> +		goto error_alloc;
> +	}
> +
> +	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "tsnes");
> +	if (!res) {
> +		dev_err(&pdev->dev, "Can't find tsnes resource\n");
> +		ret = -EINVAL;
> +		goto error_alloc;
> +	}
> +
> +	priv->base = devm_ioremap_resource(&pdev->dev, res);
> +	if (IS_ERR(priv->base)) {
> +		ret = PTR_ERR(priv->base);
> +		goto error_alloc;
> +	}
> +
> +	SET_NETDEV_DEV(ndev, &pdev->dev);
> +	ether_setup(ndev);
> +
> +	ndev->features = NETIF_F_RXCSUM;
> +	ndev->hw_features = NETIF_F_RXCSUM;
> +	ndev->base_addr = res->start;
> +	ndev->netdev_ops = &rtsn_netdev_ops;
> +	ndev->ethtool_ops = &rtsn_ethtool_ops;
> +
> +	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "gptp");
> +	if (!res) {
> +		dev_err(&pdev->dev, "Can't find gptp resource\n");
> +		ret = -EINVAL;
> +		goto error_alloc;
> +	}
> +	priv->ptp_priv->addr = devm_ioremap_resource(&pdev->dev, res);
> +	if (IS_ERR(priv->ptp_priv->addr)) {
> +		ret = -PTR_ERR(priv->ptp_priv->addr);
> +		goto error_alloc;
> +	}
> +
> +	pm_runtime_enable(&pdev->dev);
> +	pm_runtime_get_sync(&pdev->dev);
> +
> +	netif_napi_add(ndev, &priv->napi, rtsn_poll);
> +
> +	rtsn_parse_mac_address(pdev->dev.of_node, ndev);
> +
> +	ret = register_netdev(ndev);
> +	if (ret)
> +		goto error_pm;
> +
> +	dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32));

You need to be careful what you put after register_netdev(). The
kernel can be sending packets before register_netdev() even
returns. This can happen with NFS root, when the kernel will
immediately try to mount the root file system. Is it safe to handle
packets with the DMA mask set wrong?

	Andrew
Niklas Söderlund April 16, 2024, 8:36 a.m. UTC | #3
Hi Paul,

Thanks for your review!

On 2024-04-15 08:34:09 +0100, Paul Barker wrote:
> On 14/04/2024 14:59, Niklas Söderlund wrote:
> > Add initial support for Renesas Ethernet-TSN End-station device of R-Car
> > V4H. The Ethernet End-station can connect to an Ethernet network using a
> > 10 Mbps, 100 Mbps, or 1 Gbps full-duplex link via MII/GMII/RMII/RGMII.
> > Depending on the connected PHY.
> > 
> > Signed-off-by: Niklas Söderlund <niklas.soderlund+renesas@ragnatech.se>
> > ---
> > * Changes since RFC
> > - Fix issues in MDIO communication.
> > - Use a dedicated OF node for the MDIO bus.
> > ---
> >  MAINTAINERS                           |    8 +
> >  drivers/net/ethernet/renesas/Kconfig  |   11 +
> >  drivers/net/ethernet/renesas/Makefile |    2 +
> >  drivers/net/ethernet/renesas/rtsn.c   | 1421 +++++++++++++++++++++++++
> >  drivers/net/ethernet/renesas/rtsn.h   |  464 ++++++++
> >  5 files changed, 1906 insertions(+)
> >  create mode 100644 drivers/net/ethernet/renesas/rtsn.c
> >  create mode 100644 drivers/net/ethernet/renesas/rtsn.h
> 
> <snip>
> 
> > diff --git a/drivers/net/ethernet/renesas/rtsn.c b/drivers/net/ethernet/renesas/rtsn.c
> > new file mode 100644
> > index 000000000000..291ab421d68f
> > --- /dev/null
> > +++ b/drivers/net/ethernet/renesas/rtsn.c
> 
> <snip>
> 
> > +static bool rtsn_rx(struct net_device *ndev, int *quota)
> > +{
> > +	struct rtsn_ext_ts_desc *desc;
> > +	struct rtsn_private *priv;
> > +	struct sk_buff *skb;
> > +	dma_addr_t dma_addr;
> > +	int boguscnt;
> 
> I find the variable name `boguscnt` very unclear, I'm not sure if it
> means the count is bogus, or it is counting bogus items?
> 
> I don't think you need to match what I've done in ravb_main.c exactly,
> but I'd prefer to see a better variable name here.

I like the changes you did in this area for RAVB, I will reuse some of 
it in v2 of this.

> 
> > +	u16 pkt_len;
> > +	u32 get_ts;
> > +	int entry;
> > +	int limit;
> > +
> > +	priv = netdev_priv(ndev);
> > +
> > +	entry = priv->cur_rx % priv->num_rx_ring;
> > +	desc = &priv->rx_ring[entry];
> > +
> > +	boguscnt = priv->dirty_rx + priv->num_rx_ring - priv->cur_rx;
> > +	boguscnt = min(boguscnt, *quota);
> > +	limit = boguscnt;
> > +
> > +	while ((desc->die_dt & DT_MASK) != DT_FEMPTY) {
> > +		dma_rmb();
> > +		pkt_len = le16_to_cpu(desc->info_ds) & RX_DS;
> > +		if (--boguscnt < 0)
> > +			break;
> > +
> > +		skb = priv->rx_skb[entry];
> > +		priv->rx_skb[entry] = NULL;
> > +		dma_addr = le32_to_cpu(desc->dptr);
> > +		dma_unmap_single(ndev->dev.parent, dma_addr, PKT_BUF_SZ,
> > +				 DMA_FROM_DEVICE);
> > +
> > +		get_ts = priv->ptp_priv->tstamp_rx_ctrl &
> > +			RCAR_GEN4_RXTSTAMP_TYPE_V2_L2_EVENT;
> > +		if (get_ts) {
> > +			struct skb_shared_hwtstamps *shhwtstamps;
> > +			struct timespec64 ts;
> > +
> > +			shhwtstamps = skb_hwtstamps(skb);
> > +			memset(shhwtstamps, 0, sizeof(*shhwtstamps));
> > +
> > +			ts.tv_sec = (u64)le32_to_cpu(desc->ts_sec);
> > +			ts.tv_nsec = le32_to_cpu(desc->ts_nsec & cpu_to_le32(0x3fffffff));
> > +
> > +			shhwtstamps->hwtstamp = timespec64_to_ktime(ts);
> > +		}
> > +
> > +		skb_put(skb, pkt_len);
> > +		skb->protocol = eth_type_trans(skb, ndev);
> > +		netif_receive_skb(skb);
> > +		ndev->stats.rx_packets++;
> > +		ndev->stats.rx_bytes += pkt_len;
> > +
> > +		entry = (++priv->cur_rx) % priv->num_rx_ring;
> > +		desc = &priv->rx_ring[entry];
> > +	}
> > +
> > +	/* Refill the RX ring buffers */
> > +	for (; priv->cur_rx - priv->dirty_rx > 0; priv->dirty_rx++) {
> > +		entry = priv->dirty_rx % priv->num_rx_ring;
> > +		desc = &priv->rx_ring[entry];
> > +		desc->info_ds = cpu_to_le16(PKT_BUF_SZ);
> > +
> > +		if (!priv->rx_skb[entry]) {
> > +			skb = netdev_alloc_skb(ndev,
> > +					       PKT_BUF_SZ + RTSN_ALIGN - 1);
> 
> I'll send my work using a page pool today as an RFC so you can see if it
> would be beneficial to use that here as well. I was going to hold off
> until the bugfix patches have merged so that I don't need to go through
> another RFC round, but it will be good to get some more review on the
> series anyway.

I like the page pool idea, but there is no real benefit for it in this 
driver at the moment. I would like to play and learn a bit more with it 
in RAVB. And once I know more I can convert this driver too if it fits.

> 
> > +			if (!skb)
> > +				break;
> > +			skb_reserve(skb, NET_IP_ALIGN);
> > +			dma_addr = dma_map_single(ndev->dev.parent, skb->data,
> > +						  le16_to_cpu(desc->info_ds),
> > +						  DMA_FROM_DEVICE);
> > +			if (dma_mapping_error(ndev->dev.parent, dma_addr))
> > +				desc->info_ds = cpu_to_le16(0);
> > +			desc->dptr = cpu_to_le32(dma_addr);
> > +			skb_checksum_none_assert(skb);
> > +			priv->rx_skb[entry] = skb;
> > +		}
> > +		dma_wmb();
> > +		desc->die_dt = DT_FEMPTY | D_DIE;
> > +	}
> > +
> > +	desc = &priv->rx_ring[priv->num_rx_ring];
> > +	desc->die_dt = DT_LINK;
> > +
> > +	*quota -= limit - (++boguscnt);
> > +
> > +	return boguscnt <= 0;
> > +}
> > +
> > +static int rtsn_poll(struct napi_struct *napi, int budget)
> > +{
> > +	struct rtsn_private *priv;
> > +	struct net_device *ndev;
> > +	unsigned long flags;
> > +	int quota = budget;
> > +
> > +	ndev = napi->dev;
> > +	priv = netdev_priv(ndev);
> > +
> > +	/* Processing RX Descriptor Ring */
> > +	if (rtsn_rx(ndev, &quota))
> > +		goto out;
> > +
> > +	/* Processing TX Descriptor Ring */
> > +	spin_lock_irqsave(&priv->lock, flags);
> > +	rtsn_tx_free(ndev, true);
> > +	netif_wake_subqueue(ndev, 0);
> > +	spin_unlock_irqrestore(&priv->lock, flags);
> > +
> > +	napi_complete(napi);
> 
> We should use napi_complete_done() here as described in
> Documentation/networking/napi.rst. That will require rtsn_rx() to return
> the number of packets received so that it can be passed as the work_done
> argument to napi_complete_done().

Good point will update in v2.

> 
> > +
> > +	/* Re-enable TX/RX interrupts */
> > +	spin_lock_irqsave(&priv->lock, flags);
> > +	rtsn_ctrl_data_irq(priv, true);
> > +	__iowmb();
> > +	spin_unlock_irqrestore(&priv->lock, flags);
> > +out:
> > +	return budget - quota;
> > +}
> 
> <snip>
> 
> > +static int rtsn_probe(struct platform_device *pdev)
> > +{
> > +	struct rtsn_private *priv;
> > +	struct net_device *ndev;
> > +	struct resource *res;
> > +	int ret;
> > +
> > +	ndev = alloc_etherdev_mqs(sizeof(struct rtsn_private), TX_NUM_CHAINS,
> > +				  RX_NUM_CHAINS);
> > +	if (!ndev)
> > +		return -ENOMEM;
> > +
> > +	priv = netdev_priv(ndev);
> > +	priv->pdev = pdev;
> > +	priv->ndev = ndev;
> > +	priv->ptp_priv = rcar_gen4_ptp_alloc(pdev);
> > +
> > +	spin_lock_init(&priv->lock);
> > +	platform_set_drvdata(pdev, priv);
> > +
> > +	priv->clk = devm_clk_get(&pdev->dev, NULL);
> > +	if (IS_ERR(priv->clk)) {
> > +		ret = -PTR_ERR(priv->clk);
> > +		goto error_alloc;
> > +	}
> > +
> > +	priv->reset = devm_reset_control_get(&pdev->dev, NULL);
> > +	if (IS_ERR(priv->reset)) {
> > +		ret = -PTR_ERR(priv->reset);
> > +		goto error_alloc;
> > +	}
> > +
> > +	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "tsnes");
> > +	if (!res) {
> > +		dev_err(&pdev->dev, "Can't find tsnes resource\n");
> > +		ret = -EINVAL;
> > +		goto error_alloc;
> > +	}
> > +
> > +	priv->base = devm_ioremap_resource(&pdev->dev, res);
> > +	if (IS_ERR(priv->base)) {
> > +		ret = PTR_ERR(priv->base);
> > +		goto error_alloc;
> > +	}
> > +
> > +	SET_NETDEV_DEV(ndev, &pdev->dev);
> > +	ether_setup(ndev);
> > +
> > +	ndev->features = NETIF_F_RXCSUM;
> > +	ndev->hw_features = NETIF_F_RXCSUM;
> 
> A quick skim of the datasheet suggests that TX checksum calculation is
> also supported. It's probably worth listing which hardware features this
> driver supports/does not support in the commit message.
> 
> Thanks,
> 
> -- 
> Paul Barker
Niklas Söderlund April 16, 2024, 8:58 a.m. UTC | #4
Hi Andrew,

Thanks for your thorough review, much appreciated.

I agree with all suggestions except one and will fix those for v2.

On 2024-04-16 00:55:12 +0200, Andrew Lunn wrote:

<snip>

> > +static void rtsn_set_delay_mode(struct rtsn_private *priv)
> > +{
> > +	struct device_node *np = priv->ndev->dev.parent->of_node;
> > +	u32 delay;
> > +	u32 val;
> > +
> > +	val = 0;
> > +
> > +	/* Valid values are 0 and 1800, according to DT bindings */
> 
> The bindings should not matter. It is what the hardware supports. The
> bindings should match the hardware, since it is hard to modify the
> hardware to make it match the binding.

I agree the comment could be improved. It should likely point to the 
datasheet instead. See below for why.

> 
> > +	if (!of_property_read_u32(np, "rx-internal-delay-ps", &delay))
> > +		if (delay)
> > +			val |= GPOUT_RDM;
> > +
> > +	/* Valid values are 0 and 2000, according to DT bindings */
> > +	if (!of_property_read_u32(np, "tx-internal-delay-ps", &delay))
> > +		if (delay)
> > +			val |= GPOUT_TDM;
> > +
> > +	rtsn_write(priv, GPOUT, val);
> 
> So you seem to be using it as bool?

Yes.

> That is wrong. It is a number of pico seconds!

The issue is that the hardware only supports no delay or a fixed delay 
that can depend on electric properties of the board. The datasheets 
states that the typical Rx delay is 1800 ps while the typical Tx delay 
is 2000 ps. The hardware register implementation for this is a single 
bit for each delay, on or off.

To model this in the bindings after some discussions [1] the standard 
property was picked over a vendor specific bool variant of it. Here in 
the driver I tried to document that the binding will enforce the value 
to either be 0 or {1800,2000}, but that for the hardware it should be 
treated as a on/off switch.

<snip>

1. https://lore.kernel.org/linux-renesas-soc/ZVzbigCtv2q_2-Bx@oden.dyn.berto.se/
Andrew Lunn April 16, 2024, 1:05 p.m. UTC | #5
> > > +	if (!of_property_read_u32(np, "rx-internal-delay-ps", &delay))
> > > +		if (delay)
> > > +			val |= GPOUT_RDM;
> > > +
> > > +	/* Valid values are 0 and 2000, according to DT bindings */
> > > +	if (!of_property_read_u32(np, "tx-internal-delay-ps", &delay))
> > > +		if (delay)
> > > +			val |= GPOUT_TDM;
> > > +
> > > +	rtsn_write(priv, GPOUT, val);
> > 
> > So you seem to be using it as bool?
> 
> Yes.
> 
> > That is wrong. It is a number of pico seconds!
> 
> The issue is that the hardware only supports no delay or a fixed delay 
> that can depend on electric properties of the board.

The general convention is that the MAC does not add delays, it leaves
it to the PHY. Probably 95% of boards are like this, and many MAC
drivers don't even add support for configuring their hardware delays,
it is not needed. Those that do, it is generally for fine tuning the
delays, being able to add/remove 100s of pico seconds, not the full
2us. This hardware cannot do that.

So i suggest you drop all this code, and just hard code the delay to
0ps.

	Andrew
Simon Horman April 18, 2024, 6:32 p.m. UTC | #6
+ Arnd

On Sun, Apr 14, 2024 at 03:59:37PM +0200, Niklas Söderlund wrote:
> Add initial support for Renesas Ethernet-TSN End-station device of R-Car
> V4H. The Ethernet End-station can connect to an Ethernet network using a
> 10 Mbps, 100 Mbps, or 1 Gbps full-duplex link via MII/GMII/RMII/RGMII.
> Depending on the connected PHY.
> 
> Signed-off-by: Niklas Söderlund <niklas.soderlund+renesas@ragnatech.se>

...

> diff --git a/drivers/net/ethernet/renesas/Kconfig b/drivers/net/ethernet/renesas/Kconfig
> index b03fae7a0f72..ea4aca5f406f 100644
> --- a/drivers/net/ethernet/renesas/Kconfig
> +++ b/drivers/net/ethernet/renesas/Kconfig
> @@ -58,4 +58,15 @@ config RENESAS_GEN4_PTP
>  	help
>  	  Renesas R-Car Gen4 gPTP device driver.
>  
> +config RTSN
> +	tristate "Renesas Ethernet-TSN support"
> +	depends on ARCH_RENESAS || COMPILE_TEST

Hi Niklas,

I think that the use of __iowbm() means that this will not
compile for many architectures: grep indicates it
is only defined for arm, arm64, and arc.

Perhaps COMPILE_TEST should be qualified somehow?

> +	depends on PTP_1588_CLOCK
> +	select CRC32
> +	select MII
> +	select PHYLIB
> +	select RENESAS_GEN4_PTP
> +	help
> +	  Renesas Ethernet-TSN device driver.
> +
>  endif # NET_VENDOR_RENESAS

...

> diff --git a/drivers/net/ethernet/renesas/rtsn.c b/drivers/net/ethernet/renesas/rtsn.c
>

...

> +static int rtsn_poll(struct napi_struct *napi, int budget)
> +{
> +	struct rtsn_private *priv;
> +	struct net_device *ndev;
> +	unsigned long flags;
> +	int quota = budget;
> +
> +	ndev = napi->dev;
> +	priv = netdev_priv(ndev);
> +
> +	/* Processing RX Descriptor Ring */
> +	if (rtsn_rx(ndev, &quota))
> +		goto out;
> +
> +	/* Processing TX Descriptor Ring */
> +	spin_lock_irqsave(&priv->lock, flags);
> +	rtsn_tx_free(ndev, true);
> +	netif_wake_subqueue(ndev, 0);
> +	spin_unlock_irqrestore(&priv->lock, flags);
> +
> +	napi_complete(napi);
> +
> +	/* Re-enable TX/RX interrupts */
> +	spin_lock_irqsave(&priv->lock, flags);
> +	rtsn_ctrl_data_irq(priv, true);
> +	__iowmb();
> +	spin_unlock_irqrestore(&priv->lock, flags);
> +out:
> +	return budget - quota;
> +}

...
Simon Horman April 18, 2024, 6:35 p.m. UTC | #7
On Sun, Apr 14, 2024 at 03:59:37PM +0200, Niklas Söderlund wrote:
> Add initial support for Renesas Ethernet-TSN End-station device of R-Car
> V4H. The Ethernet End-station can connect to an Ethernet network using a
> 10 Mbps, 100 Mbps, or 1 Gbps full-duplex link via MII/GMII/RMII/RGMII.
> Depending on the connected PHY.
> 
> Signed-off-by: Niklas Söderlund <niklas.soderlund+renesas@ragnatech.se>

...

> +static int rtsn_mii_register(struct rtsn_private *priv)
> +{
> +	struct platform_device *pdev = priv->pdev;
> +	struct device *dev = &pdev->dev;
> +	struct device_node *mdio_node;
> +	struct mii_bus *mii;
> +	int ret;
> +
> +	mii = mdiobus_alloc();
> +	if (!mii)
> +		return -ENOMEM;
> +
> +	mdio_node = of_get_child_by_name(dev->of_node, "mdio");
> +	if (!mdio_node) {
> +		ret = -ENODEV;
> +		goto out_free_bus;
> +	};

nit: the ';' is not needed on the line above.

Flagged by Coccinelle.

> +
> +	mii->name = "rtsn_mii";
> +	sprintf(mii->id, "%s-%x", pdev->name, pdev->id);
> +	mii->priv = priv;
> +	mii->read = rtsn_mii_read;
> +	mii->write = rtsn_mii_write;
> +	mii->read_c45 = rtsn_mii_read_c45;
> +	mii->write_c45 = rtsn_mii_write_c45;
> +	mii->parent = dev;
> +
> +	ret = of_mdiobus_register(mii, mdio_node);
> +	of_node_put(mdio_node);
> +	if (ret)
> +		goto out_free_bus;
> +
> +	priv->mii = mii;
> +
> +	return 0;
> +
> +out_free_bus:
> +	mdiobus_free(mii);
> +	return ret;
> +}
Arnd Bergmann April 18, 2024, 7:03 p.m. UTC | #8
On Thu, Apr 18, 2024, at 20:32, Simon Horman wrote:
>
> Hi Niklas,
>
> I think that the use of __iowbm() means that this will not
> compile for many architectures: grep indicates it
> is only defined for arm, arm64, and arc.
>
> Perhaps COMPILE_TEST should be qualified somehow?


>> +	/* Re-enable TX/RX interrupts */
>> +	spin_lock_irqsave(&priv->lock, flags);
>> +	rtsn_ctrl_data_irq(priv, true);
>> +	__iowmb();
>> +	spin_unlock_irqrestore(&priv->lock, flags);

I think this needs a comment anyway: what is this trying
to serialize?

The arm64 __iowmb() usually tries to ensure that a memory
write to a coherent buffer is complete before a following
writel() is sent to the bus, but this one appears to be
after the writel() where it has no effect because the
transaction may still be in flight on the bus after it
has left the store buffer.

      Arnd
Geert Uytterhoeven April 19, 2024, 8:16 a.m. UTC | #9
Hi Andrew,

On Tue, Apr 16, 2024 at 3:05 PM Andrew Lunn <andrew@lunn.ch> wrote:
> > > > + if (!of_property_read_u32(np, "rx-internal-delay-ps", &delay))
> > > > +         if (delay)
> > > > +                 val |= GPOUT_RDM;
> > > > +
> > > > + /* Valid values are 0 and 2000, according to DT bindings */
> > > > + if (!of_property_read_u32(np, "tx-internal-delay-ps", &delay))
> > > > +         if (delay)
> > > > +                 val |= GPOUT_TDM;
> > > > +
> > > > + rtsn_write(priv, GPOUT, val);
> > >
> > > So you seem to be using it as bool?
> >
> > Yes.
> >
> > > That is wrong. It is a number of pico seconds!
> >
> > The issue is that the hardware only supports no delay or a fixed delay
> > that can depend on electric properties of the board.
>
> The general convention is that the MAC does not add delays, it leaves
> it to the PHY. Probably 95% of boards are like this, and many MAC
> drivers don't even add support for configuring their hardware delays,
> it is not needed. Those that do, it is generally for fine tuning the
> delays, being able to add/remove 100s of pico seconds, not the full
> 2us. This hardware cannot do that.
>
> So i suggest you drop all this code, and just hard code the delay to
> 0ps.

IIRC (from users of RAVB, which have similar delay bits), the issue
is that some boards require a larger delay than the maximum delay
supported by the Micrel KSZ9031 PHY (960 ps).  Hence these
boards need to enable the MAC delay.

Gr{oetje,eeting}s,

                        Geert
Andrew Lunn April 19, 2024, 12:56 p.m. UTC | #10
> IIRC (from users of RAVB, which have similar delay bits), the issue
> is that some boards require a larger delay than the maximum delay
> supported by the Micrel KSZ9031 PHY (960 ps).  Hence these
> boards need to enable the MAC delay.

Which is fine, just comment in on.

Whoever, some MAC drivers which implement the delay get the phy-mode
passed to the PHY wrong. The board requires rgmii-id, since there is
no extra long clock lines. So i would expect the DT to have
rmgii-id. If the MAC then implements the delays, it then needs to mask
the PHY mode and pass rgmii to the PHY.

	Andrew
Niklas Söderlund May 3, 2024, 8:50 a.m. UTC | #11
Hi Arnd and Simon,

Thanks for your comments.

On 2024-04-18 21:03:51 +0200, Arnd Bergmann wrote:
> On Thu, Apr 18, 2024, at 20:32, Simon Horman wrote:
> >
> > Hi Niklas,
> >
> > I think that the use of __iowbm() means that this will not
> > compile for many architectures: grep indicates it
> > is only defined for arm, arm64, and arc.
> >
> > Perhaps COMPILE_TEST should be qualified somehow?
> 
> 
> >> +	/* Re-enable TX/RX interrupts */
> >> +	spin_lock_irqsave(&priv->lock, flags);
> >> +	rtsn_ctrl_data_irq(priv, true);
> >> +	__iowmb();
> >> +	spin_unlock_irqrestore(&priv->lock, flags);
> 
> I think this needs a comment anyway: what is this trying
> to serialize?
> 
> The arm64 __iowmb() usually tries to ensure that a memory
> write to a coherent buffer is complete before a following
> writel() is sent to the bus, but this one appears to be
> after the writel() where it has no effect because the
> transaction may still be in flight on the bus after it
> has left the store buffer.

Indeed, this is a leftover from development. Thanks for catching it, 
will drop for v2.

> 
>       Arnd
Niklas Söderlund May 3, 2024, 10:20 a.m. UTC | #12
Hi Andrew,

Thanks for your review.

On 2024-04-16 00:55:12 +0200, Andrew Lunn wrote:

<snip>

> > +static int rtsn_mii_access_indirect(struct mii_bus *bus, bool read, 
> > int phyad,
> > +				    int devnum, int regnum, u16 data)
> > +{
> > +	int ret;
> > +
> > +	ret = rtsn_mii_access(bus, false, phyad, MII_MMD_CTRL, devnum);
> > +	if (ret)
> > +		return ret;
> > +
> > +	ret = rtsn_mii_access(bus, false, phyad, MII_MMD_DATA, regnum);
> > +	if (ret)
> > +		return ret;
> > +
> > +	ret = rtsn_mii_access(bus, false, phyad, MII_MMD_CTRL,
> > +			      devnum | MII_MMD_CTRL_NOINCR);
> 
> This looks to be C45 over C22. phylib core knows how to do this, since
> it should be the same for all PHYs which implement C45 over C22. So
> there is no need for you to implement it again.
> 
> > +static int rtsn_mii_register(struct rtsn_private *priv)
> > +{
> > +	struct platform_device *pdev = priv->pdev;
> > +	struct device *dev = &pdev->dev;
> > +	struct device_node *mdio_node;
> > +	struct mii_bus *mii;
> > +	int ret;
> > +
> > +	mii = mdiobus_alloc();
> > +	if (!mii)
> > +		return -ENOMEM;
> > +
> > +	mdio_node = of_get_child_by_name(dev->of_node, "mdio");
> > +	if (!mdio_node) {
> > +		ret = -ENODEV;
> > +		goto out_free_bus;
> > +	};
> > +
> > +	mii->name = "rtsn_mii";
> > +	sprintf(mii->id, "%s-%x", pdev->name, pdev->id);
> > +	mii->priv = priv;
> > +	mii->read = rtsn_mii_read;
> > +	mii->write = rtsn_mii_write;
> > +	mii->read_c45 = rtsn_mii_read_c45;
> > +	mii->write_c45 = rtsn_mii_write_c45;
> 
> Just leave these two empty, and the core will do C45 over C22 for you.

Does this not require the bus to be created/allocated with an 
implementation that support this, for example mdio_i2c_alloc() or 
alloc_mdio_bitbang()?  This bus is allocated with mdiobus_alloc() which 
do not implement this. Removing the C45 functions here result in 
__mdiobus_c45_read() returning -EOPNOTSUPP as bus->read_c45 is not set.

The allocator in question here could possibly be alloc_mdio_bitbang(), 
but I see no easy way for me to implement the mdiobb_ops struct. Is 
there a different bus implementation I should use that is able to talk 
C45 over C22 ?
Andrew Lunn May 3, 2024, 11:59 a.m. UTC | #13
> > > +static int rtsn_mii_register(struct rtsn_private *priv)
> > > +{
> > > +	struct platform_device *pdev = priv->pdev;
> > > +	struct device *dev = &pdev->dev;
> > > +	struct device_node *mdio_node;
> > > +	struct mii_bus *mii;
> > > +	int ret;
> > > +
> > > +	mii = mdiobus_alloc();
> > > +	if (!mii)
> > > +		return -ENOMEM;
> > > +
> > > +	mdio_node = of_get_child_by_name(dev->of_node, "mdio");
> > > +	if (!mdio_node) {
> > > +		ret = -ENODEV;
> > > +		goto out_free_bus;
> > > +	};
> > > +
> > > +	mii->name = "rtsn_mii";
> > > +	sprintf(mii->id, "%s-%x", pdev->name, pdev->id);
> > > +	mii->priv = priv;
> > > +	mii->read = rtsn_mii_read;
> > > +	mii->write = rtsn_mii_write;
> > > +	mii->read_c45 = rtsn_mii_read_c45;
> > > +	mii->write_c45 = rtsn_mii_write_c45;
> > 
> > Just leave these two empty, and the core will do C45 over C22 for you.
> 
> Does this not require the bus to be created/allocated with an 
> implementation that support this, for example mdio_i2c_alloc() or 
> alloc_mdio_bitbang()?  This bus is allocated with mdiobus_alloc() which 
> do not implement this. Removing the C45 functions here result in 
> __mdiobus_c45_read() returning -EOPNOTSUPP as bus->read_c45 is not set.

phy_read_mmd():
  __phy_read_mmd():
      mmd_phy_read():

So is is_c45 is true?

I would expect it to be false, so that it then uses

	mmd_phy_indirect(bus, phy_addr, devad, regnum);
	/* Write the data into MMD's selected register */
	return __mdiobus_write(bus, phy_addr, MII_MMD_DATA, val);

which is C45 over C22.

If is_c45 is true, what is setting it true?

      Andrew
Niklas Söderlund May 3, 2024, 1:30 p.m. UTC | #14
On 2024-05-03 13:59:52 +0200, Andrew Lunn wrote:
> > > > +static int rtsn_mii_register(struct rtsn_private *priv)
> > > > +{
> > > > +	struct platform_device *pdev = priv->pdev;
> > > > +	struct device *dev = &pdev->dev;
> > > > +	struct device_node *mdio_node;
> > > > +	struct mii_bus *mii;
> > > > +	int ret;
> > > > +
> > > > +	mii = mdiobus_alloc();
> > > > +	if (!mii)
> > > > +		return -ENOMEM;
> > > > +
> > > > +	mdio_node = of_get_child_by_name(dev->of_node, "mdio");
> > > > +	if (!mdio_node) {
> > > > +		ret = -ENODEV;
> > > > +		goto out_free_bus;
> > > > +	};
> > > > +
> > > > +	mii->name = "rtsn_mii";
> > > > +	sprintf(mii->id, "%s-%x", pdev->name, pdev->id);
> > > > +	mii->priv = priv;
> > > > +	mii->read = rtsn_mii_read;
> > > > +	mii->write = rtsn_mii_write;
> > > > +	mii->read_c45 = rtsn_mii_read_c45;
> > > > +	mii->write_c45 = rtsn_mii_write_c45;
> > > 
> > > Just leave these two empty, and the core will do C45 over C22 for you.
> > 
> > Does this not require the bus to be created/allocated with an 
> > implementation that support this, for example mdio_i2c_alloc() or 
> > alloc_mdio_bitbang()?  This bus is allocated with mdiobus_alloc() which 
> > do not implement this. Removing the C45 functions here result in 
> > __mdiobus_c45_read() returning -EOPNOTSUPP as bus->read_c45 is not set.
> 
> phy_read_mmd():
>   __phy_read_mmd():
>       mmd_phy_read():
> 
> So is is_c45 is true?

Not sure, I never get that far. The function __mdiobus_c45_read() is 
called directly from of_mdiobus_register() call-chain.

The call chain is:

  rtsn_open()
    of_mdiobus_register() <- This fails and RTSN can't talk to the PHY
      __of_mdiobus_register()
        __of_mdiobus_parse_phys()
          of_mdiobus_register_phy()
            fwnode_mdiobus_register_phy() <- See [1]
              get_phy_device()
                get_phy_c45_ids()
                  mdiobus_c45_read()
                    __mdiobus_c45_read() <- Returns -EOPNOTSUPP [2]

1. Here is_c45 is set as it checks the compatible value is checked.

     is_c45 = fwnode_device_is_compatible(child, "ethernet-phy-ieee802.3-c45");

2. The struct mii_bus have no read_c45() callback and returns  
   -EOPNOTSUPP.

> 
> I would expect it to be false, so that it then uses
> 
> 	mmd_phy_indirect(bus, phy_addr, devad, regnum);
> 	/* Write the data into MMD's selected register */
> 	return __mdiobus_write(bus, phy_addr, MII_MMD_DATA, val);

Cool, I was not aware of that code-path I was only looking at the above 
when trying to remove the implementation in RTSN driver.

> 
> which is C45 over C22.
> 
> If is_c45 is true, what is setting it true?
> 
>       Andrew
Andrew Lunn May 6, 2024, 1:51 a.m. UTC | #15
> > phy_read_mmd():
> >   __phy_read_mmd():
> >       mmd_phy_read():
> > 
> > So is is_c45 is true?
> 
> Not sure, I never get that far. The function __mdiobus_c45_read() is 
> called directly from of_mdiobus_register() call-chain.
> 
> The call chain is:
> 
>   rtsn_open()
>     of_mdiobus_register() <- This fails and RTSN can't talk to the PHY
>       __of_mdiobus_register()
>         __of_mdiobus_parse_phys()
>           of_mdiobus_register_phy()
>             fwnode_mdiobus_register_phy() <- See [1]
>               get_phy_device()
>                 get_phy_c45_ids()
>                   mdiobus_c45_read()
>                     __mdiobus_c45_read() <- Returns -EOPNOTSUPP [2]
> 
> 1. Here is_c45 is set as it checks the compatible value is checked.
> 
>      is_c45 = fwnode_device_is_compatible(child, "ethernet-phy-ieee802.3-c45");

Ah, O.K.

What PHY is this? Does it have C22 registers? Can it be identified via
C22 registers 2 and 3?

I suspect we in falling into the cracks with what is_c45 means. And
what ethernet-phy-ieee802.3-c45 means.

is_c45 is a bad mis-mash of responds to C45 bus transfers and has C45
registers.

Your MDIO bus master appears to be unable to perform C45 bus
transfers. So you probably don't want is_c45 set, so that C45 over C22
is performed. However, you are using ethernet-phy-ieee802.3-c45 which
suggests to me the PHY has C45 registers.

A PHY driver itself mostly does not care about is_c45. It knows the
device has C45 registers and will use phy_read_mmd() to access
them. If that uses C45 bus transfers or C45 over C22 is left to the
core, based on is_c45.

Where it gets a little problematic is determining the correct driver
for the PHY. By default it will look at registers 2 and 3 of C22. If
it finds an usable ID, that will be used to load the driver. If there
is no ID in C22 registers, and the MDIO bus supports C45 bus
transfers, it will try the various places there can be IDs in the C45
register space.

What the core will not do is use C45 over C22 to look for ID
registers, because it has no idea what is actually there, and C45 over
C22 means performing an write, which could destroy the configuration
of something which is not a PHY, e.g. a switch, or a GPIO controller
etc.

However, by specifying "ethernet-phy-ieee802.3-c45", you have short
cut the default, it goes direct to C45 bus transfers which your
hardware cannot actually do.

So i would drop the compatible. See if C22 is sufficient to get the
correct driver loaded.

	Andrew
Niklas Söderlund May 6, 2024, 2:05 p.m. UTC | #16
Hi Andrew,

Thanks for this explanation, it helps understand what's going on.

On 2024-05-06 03:51:45 +0200, Andrew Lunn wrote:

> What PHY is this? Does it have C22 registers? Can it be identified via
> C22 registers 2 and 3?

The PHY in question is mv88q2110 (drivers/net/phy/marvell-88q2xxx.c), 
unfortunately I do not have a datasheet for it so I can't tell you much 
about it.

<snip>

> 
> So i would drop the compatible. See if C22 is sufficient to get the
> correct driver loaded.

- Remove C45 compatible; Remove C45 read/write in driver

  The PHY is identified as "Generic PHY", and the correct PHY driver is 
  not used. I can't test more than that as the generic PHY driver do not 
  implement some quirks I need to get the link up.

- Remove C45 compatible; Keep C45 read/write in driver

  The correct PHY driver is used and everything works.

- Keep C45 compatible; Remove C45 read/write in driver

  As described earlier in this thread, the MAC driver can't connect to 
  the PHY as the call-chain described earlier fails.

How would you suggest I move forward here? Shall I keep the C45 over C22 
read/write in the driver or can I do something else more clever?
Geert Uytterhoeven May 6, 2024, 5:43 p.m. UTC | #17
Hi Niklas,

On Mon, May 6, 2024 at 4:05 PM Niklas Söderlund
<niklas.soderlund+renesas@ragnatech.se> wrote:
> On 2024-05-06 03:51:45 +0200, Andrew Lunn wrote:
> > What PHY is this? Does it have C22 registers? Can it be identified via
> > C22 registers 2 and 3?
>
> The PHY in question is mv88q2110 (drivers/net/phy/marvell-88q2xxx.c),
> unfortunately I do not have a datasheet for it so I can't tell you much
> about it.
>
> <snip>
>
> >
> > So i would drop the compatible. See if C22 is sufficient to get the
> > correct driver loaded.
>
> - Remove C45 compatible; Remove C45 read/write in driver
>
>   The PHY is identified as "Generic PHY", and the correct PHY driver is
>   not used. I can't test more than that as the generic PHY driver do not
>   implement some quirks I need to get the link up.
>
> - Remove C45 compatible; Keep C45 read/write in driver
>
>   The correct PHY driver is used and everything works.

Does it still work after kexec, or after device unbind/rebind?
According to [1], the PHY node has a reset-gpios property, so you may
need to specify the exact PHY model to identify the PHY model at any
time, regardless of the state of the PHY reset line.

Perhaps that issue does not happen when using the mdio subnode, cfr. [3]?

[1] https://lore.kernel.org/all/20240122160441.759620-3-niklas.soderlund+renesas@ragnatech.se/
[2] 722d55f3a9bd810f ("arm64: dts: renesas: Add compatible properties
to KSZ9031Ethernet PHYs")
[3] 8da891720cd407ed ("dt-bindings: net: renesas,ethertsn: Create
child-node for MDIO bus") in net-next (next-20240405 and later).

Gr{oetje,eeting}s,

                        Geert

--
Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- geert@linux-m68k.org

In personal conversations with technical people, I call myself a hacker. But
when I'm talking to journalists I just say "programmer" or something like that.
                                -- Linus Torvalds
Niklas Söderlund May 6, 2024, 6:26 p.m. UTC | #18
Hi Geert,

On 2024-05-06 19:43:55 +0200, Geert Uytterhoeven wrote:
> Hi Niklas,
> 
> On Mon, May 6, 2024 at 4:05 PM Niklas Söderlund
> <niklas.soderlund+renesas@ragnatech.se> wrote:
> > On 2024-05-06 03:51:45 +0200, Andrew Lunn wrote:
> > > What PHY is this? Does it have C22 registers? Can it be identified via
> > > C22 registers 2 and 3?
> >
> > The PHY in question is mv88q2110 (drivers/net/phy/marvell-88q2xxx.c),
> > unfortunately I do not have a datasheet for it so I can't tell you much
> > about it.
> >
> > <snip>
> >
> > >
> > > So i would drop the compatible. See if C22 is sufficient to get the
> > > correct driver loaded.
> >
> > - Remove C45 compatible; Remove C45 read/write in driver
> >
> >   The PHY is identified as "Generic PHY", and the correct PHY driver is
> >   not used. I can't test more than that as the generic PHY driver do not
> >   implement some quirks I need to get the link up.
> >
> > - Remove C45 compatible; Keep C45 read/write in driver
> >
> >   The correct PHY driver is used and everything works.
> 
> Does it still work after kexec, or after device unbind/rebind?
> According to [1], the PHY node has a reset-gpios property, so you may
> need to specify the exact PHY model to identify the PHY model at any
> time, regardless of the state of the PHY reset line.

With the "ethernet-phy-ieee802.3-c45" compat in place (and C45 
read/write in the driver) I can bind/unbind the device without issue and 
then operate it as expected. My preferred way forward is to keep the 
compatible for C45 in place so I have not tested without it. However I 
see no reason why it should not work with "Remove C45 compatible; Keep 
C45 read/write in driver" mode.

Note that this driver differs a bit from RAVB for which [2] targets. The 
RAVB driver creates the MDIO bus at probe time, this drivers creates and 
destroys the MDIO bus in sync with the interface state using ndo_open 
and ndo_stop. So the reset is issued correctly no matter what happened 
before and the PHY can be probed.

> 
> Perhaps that issue does not happen when using the mdio subnode, cfr. [3]?
> 
> [1] https://lore.kernel.org/all/20240122160441.759620-3-niklas.soderlund+renesas@ragnatech.se/
> [2] 722d55f3a9bd810f ("arm64: dts: renesas: Add compatible properties
> to KSZ9031Ethernet PHYs")
> [3] 8da891720cd407ed ("dt-bindings: net: renesas,ethertsn: Create
> child-node for MDIO bus") in net-next (next-20240405 and later).
> 
> Gr{oetje,eeting}s,
> 
>                         Geert
> 
> --
> Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- geert@linux-m68k.org
> 
> In personal conversations with technical people, I call myself a hacker. But
> when I'm talking to journalists I just say "programmer" or something like that.
>                                 -- Linus Torvalds
Andrew Lunn May 6, 2024, 7:53 p.m. UTC | #19
On Mon, May 06, 2024 at 04:05:33PM +0200, Niklas Söderlund wrote:
> Hi Andrew,
> 
> Thanks for this explanation, it helps understand what's going on.
> 
> On 2024-05-06 03:51:45 +0200, Andrew Lunn wrote:
> 
> > What PHY is this? Does it have C22 registers? Can it be identified via
> > C22 registers 2 and 3?
> 
> The PHY in question is mv88q2110 (drivers/net/phy/marvell-88q2xxx.c), 
> unfortunately I do not have a datasheet for it so I can't tell you much 
> about it.

The mv88q2110 declares itself as 0x002b0b20.

> 
> <snip>
> 
> > 
> > So i would drop the compatible. See if C22 is sufficient to get the
> > correct driver loaded.
> 
> - Remove C45 compatible; Remove C45 read/write in driver
> 
>   The PHY is identified as "Generic PHY", and the correct PHY driver is 
>   not used. I can't test more than that as the generic PHY driver do not 
>   implement some quirks I need to get the link up.

What ID does it return in registers 2 and 3? Anything like 0x002b0b20?
If there is some usable value there, we can probably extend the driver
to accept that ID as well.

Another option it to use a compatible conforming to:

      - pattern: "^ethernet-phy-id[a-f0-9]{4}\\.[a-f0-9]{4}$"

ethernet-phy-id002b.0b20

That will bypass reading ID registers for the ID, and just use the ID
in the compatible. However, it would be better to read them from the
PHY.

	Andrew
Andrew Lunn May 6, 2024, 8 p.m. UTC | #20
> Note that this driver differs a bit from RAVB for which [2] targets. The 
> RAVB driver creates the MDIO bus at probe time, this drivers creates and 
> destroys the MDIO bus in sync with the interface state using ndo_open 
> and ndo_stop.

That is not a good idea. It is better to create the MDIO bus at probe
time. There are a few reasons for this.

1) It takes while for the machinary to load the PHY driver, when it is
a module. If it does not get time to do that, you end up with the
generic PHY driver, even though there is a specific PHY driver, which
becomes usable a little later in time. But your network is dead
because the generic PHY drive is not sufficient.

2) It is possible that creating the MDIO bus fails with
EPROBE_DEFER. If this happens at ndo_open(), there is nothing you can
do about it, other than return the error to user space. If it happens
during probe, the driver core will handle it, and try to probe the
driver again sometime later.

       Andrew
Niklas Söderlund May 7, 2024, 11:14 a.m. UTC | #21
On 2024-05-06 21:53:58 +0200, Andrew Lunn wrote:
> On Mon, May 06, 2024 at 04:05:33PM +0200, Niklas Söderlund wrote:
> > Hi Andrew,
> > 
> > Thanks for this explanation, it helps understand what's going on.
> > 
> > On 2024-05-06 03:51:45 +0200, Andrew Lunn wrote:
> > 
> > > What PHY is this? Does it have C22 registers? Can it be identified via
> > > C22 registers 2 and 3?
> > 
> > The PHY in question is mv88q2110 (drivers/net/phy/marvell-88q2xxx.c), 
> > unfortunately I do not have a datasheet for it so I can't tell you much 
> > about it.
> 
> The mv88q2110 declares itself as 0x002b0b20.
> 
> > 
> > <snip>
> > 
> > > 
> > > So i would drop the compatible. See if C22 is sufficient to get the
> > > correct driver loaded.
> > 
> > - Remove C45 compatible; Remove C45 read/write in driver
> > 
> >   The PHY is identified as "Generic PHY", and the correct PHY driver is 
> >   not used. I can't test more than that as the generic PHY driver do not 
> >   implement some quirks I need to get the link up.
> 
> What ID does it return in registers 2 and 3? Anything like 0x002b0b20?
> If there is some usable value there, we can probably extend the driver
> to accept that ID as well.
> 
> Another option it to use a compatible conforming to:
> 
>       - pattern: "^ethernet-phy-id[a-f0-9]{4}\\.[a-f0-9]{4}$"
> 
> ethernet-phy-id002b.0b20
> 
> That will bypass reading ID registers for the ID, and just use the ID
> in the compatible. However, it would be better to read them from the
> PHY.

Thanks for these hints. Using a compatible to indicate which PHY to use 
allows the RTSN driver to not implement C45 read/write functions and 
still operate.

I will look into if I can extend the PHY driver with identification from 
register 2 and 3, but that will be a separate work item.

Unrelated note: I use the ID 0x002b0980 instead of 0x002b0b20 as I have 
a 88Q2110 and not a 88Q2220 PHY, but both are handled by the same PHY
driver.

> 
> 	Andrew
> 
>
Niklas Söderlund May 7, 2024, 11:18 a.m. UTC | #22
HI Andrew and Geert,

On 2024-05-06 22:00:46 +0200, Andrew Lunn wrote:
> > Note that this driver differs a bit from RAVB for which [2] targets. The 
> > RAVB driver creates the MDIO bus at probe time, this drivers creates and 
> > destroys the MDIO bus in sync with the interface state using ndo_open 
> > and ndo_stop.
> 
> That is not a good idea. It is better to create the MDIO bus at probe
> time. There are a few reasons for this.
> 
> 1) It takes while for the machinary to load the PHY driver, when it is
> a module. If it does not get time to do that, you end up with the
> generic PHY driver, even though there is a specific PHY driver, which
> becomes usable a little later in time. But your network is dead
> because the generic PHY drive is not sufficient.
> 
> 2) It is possible that creating the MDIO bus fails with
> EPROBE_DEFER. If this happens at ndo_open(), there is nothing you can
> do about it, other than return the error to user space. If it happens
> during probe, the driver core will handle it, and try to probe the
> driver again sometime later.

Thanks to both of you for your feedback in this review. I now have a way 
forward for this work.

I will drop the C45 read/write functions in the RTSN driver and rework 
the it to register the MDIO bus at probe time. As Geert then suggest I 
will need to state a PHY specific compatible which will solve my initial 
issue.

After this is done I will look to see if the PHY driver can be extended 
with more identification numbers so it can be probed on C22. But this 
will be done separate from my effort to enable the RTSN device.

Thanks again for all your help.
diff mbox series

Patch

diff --git a/MAINTAINERS b/MAINTAINERS
index 5ba3fe6ac09c..965ac018c7f5 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -18761,6 +18761,14 @@  F:	drivers/net/ethernet/renesas/Makefile
 F:	drivers/net/ethernet/renesas/rcar_gen4*
 F:	drivers/net/ethernet/renesas/rswitch*
 
+RENESAS ETHERNET TSN DRIVER
+M:	Niklas Söderlund <niklas.soderlund@ragnatech.se>
+L:	netdev@vger.kernel.org
+L:	linux-renesas-soc@vger.kernel.org
+S:	Supported
+F:	Documentation/devicetree/bindings/net/renesas,ethertsn.yaml
+F:	drivers/net/ethernet/renesas/rtsn.*
+
 RENESAS IDT821034 ASoC CODEC
 M:	Herve Codina <herve.codina@bootlin.com>
 L:	alsa-devel@alsa-project.org (moderated for non-subscribers)
diff --git a/drivers/net/ethernet/renesas/Kconfig b/drivers/net/ethernet/renesas/Kconfig
index b03fae7a0f72..ea4aca5f406f 100644
--- a/drivers/net/ethernet/renesas/Kconfig
+++ b/drivers/net/ethernet/renesas/Kconfig
@@ -58,4 +58,15 @@  config RENESAS_GEN4_PTP
 	help
 	  Renesas R-Car Gen4 gPTP device driver.
 
+config RTSN
+	tristate "Renesas Ethernet-TSN support"
+	depends on ARCH_RENESAS || COMPILE_TEST
+	depends on PTP_1588_CLOCK
+	select CRC32
+	select MII
+	select PHYLIB
+	select RENESAS_GEN4_PTP
+	help
+	  Renesas Ethernet-TSN device driver.
+
 endif # NET_VENDOR_RENESAS
diff --git a/drivers/net/ethernet/renesas/Makefile b/drivers/net/ethernet/renesas/Makefile
index 9070acfd6aaf..f65fc76f8b4d 100644
--- a/drivers/net/ethernet/renesas/Makefile
+++ b/drivers/net/ethernet/renesas/Makefile
@@ -11,3 +11,5 @@  obj-$(CONFIG_RAVB) += ravb.o
 obj-$(CONFIG_RENESAS_ETHER_SWITCH) += rswitch.o
 
 obj-$(CONFIG_RENESAS_GEN4_PTP) += rcar_gen4_ptp.o
+
+obj-$(CONFIG_RTSN) += rtsn.o
diff --git a/drivers/net/ethernet/renesas/rtsn.c b/drivers/net/ethernet/renesas/rtsn.c
new file mode 100644
index 000000000000..291ab421d68f
--- /dev/null
+++ b/drivers/net/ethernet/renesas/rtsn.c
@@ -0,0 +1,1421 @@ 
+// SPDX-License-Identifier: GPL-2.0
+
+/* Renesas Ethernet-TSN device driver
+ *
+ * Copyright (C) 2022 Renesas Electronics Corporation
+ * Copyright (C) 2023 Niklas Söderlund <niklas.soderlund@ragnatech.se>
+ */
+
+#include <linux/clk.h>
+#include <linux/dma-mapping.h>
+#include <linux/etherdevice.h>
+#include <linux/ethtool.h>
+#include <linux/module.h>
+#include <linux/net_tstamp.h>
+#include <linux/of.h>
+#include <linux/of_mdio.h>
+#include <linux/of_net.h>
+#include <linux/phy.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/reset.h>
+#include <linux/spinlock.h>
+
+#include "rtsn.h"
+#include "rcar_gen4_ptp.h"
+
+struct rtsn_private {
+	struct net_device *ndev;
+	struct platform_device *pdev;
+	void __iomem *base;
+	struct rcar_gen4_ptp_private *ptp_priv;
+	struct clk *clk;
+	struct reset_control *reset;
+
+	u32 num_tx_ring;
+	u32 num_rx_ring;
+	u32 tx_desc_bat_size;
+	dma_addr_t tx_desc_bat_dma;
+	struct rtsn_desc *tx_desc_bat;
+	u32 rx_desc_bat_size;
+	dma_addr_t rx_desc_bat_dma;
+	struct rtsn_desc *rx_desc_bat;
+	dma_addr_t tx_desc_dma;
+	dma_addr_t rx_desc_dma;
+	struct rtsn_ext_desc *tx_ring;
+	struct rtsn_ext_ts_desc *rx_ring;
+	struct sk_buff **tx_skb;
+	struct sk_buff **rx_skb;
+	spinlock_t lock;	/* Register access lock */
+	u32 cur_tx;
+	u32 dirty_tx;
+	u32 cur_rx;
+	u32 dirty_rx;
+	u8 ts_tag;
+	struct napi_struct napi;
+
+	struct mii_bus *mii;
+	phy_interface_t iface;
+	int link;
+	int speed;
+
+	int tx_data_irq;
+	int rx_data_irq;
+};
+
+static u32 rtsn_read(struct rtsn_private *priv, enum rtsn_reg reg)
+{
+	return ioread32(priv->base + reg);
+}
+
+static void rtsn_write(struct rtsn_private *priv, enum rtsn_reg reg, u32 data)
+{
+	iowrite32(data, priv->base + reg);
+}
+
+static void rtsn_modify(struct rtsn_private *priv, enum rtsn_reg reg,
+			u32 clear, u32 set)
+{
+	rtsn_write(priv, reg, (rtsn_read(priv, reg) & ~clear) | set);
+}
+
+static int rtsn_reg_wait(struct rtsn_private *priv, enum rtsn_reg reg,
+			 u32 mask, u32 expected)
+{
+	u32 val;
+
+	return readl_poll_timeout(priv->base + reg, val,
+				  (val & mask) == expected,
+				  RTSN_INTERVAL_US, RTSN_TIMEOUT_US);
+}
+
+static void rtsn_ctrl_data_irq(struct rtsn_private *priv, bool enable)
+{
+	if (enable) {
+		rtsn_write(priv, TDIE0, TDIE_TDID_TDX(TX_CHAIN_IDX));
+		rtsn_write(priv, RDIE0, RDIE_RDID_RDX(RX_CHAIN_IDX));
+	} else {
+		rtsn_write(priv, TDID0, TDIE_TDID_TDX(TX_CHAIN_IDX));
+		rtsn_write(priv, RDID0, RDIE_RDID_RDX(RX_CHAIN_IDX));
+	}
+}
+
+static void rtsn_get_timestamp(struct rtsn_private *priv, struct timespec64 *ts)
+{
+	struct rcar_gen4_ptp_private *ptp_priv = priv->ptp_priv;
+
+	ptp_priv->info.gettime64(&ptp_priv->info, ts);
+}
+
+static int rtsn_tx_free(struct net_device *ndev, bool free_txed_only)
+{
+	struct rtsn_private *priv = netdev_priv(ndev);
+	struct rtsn_ext_desc *desc;
+	struct sk_buff *skb;
+	int free_num = 0;
+	int entry, size;
+
+	for (; priv->cur_tx - priv->dirty_tx > 0; priv->dirty_tx++) {
+		entry = priv->dirty_tx % priv->num_tx_ring;
+		desc = &priv->tx_ring[entry];
+		if (free_txed_only && (desc->die_dt & DT_MASK) != DT_FEMPTY)
+			break;
+
+		dma_rmb();
+		size = le16_to_cpu(desc->info_ds) & TX_DS;
+		skb = priv->tx_skb[entry];
+		if (skb) {
+			if (skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP) {
+				struct skb_shared_hwtstamps shhwtstamps;
+				struct timespec64 ts;
+
+				rtsn_get_timestamp(priv, &ts);
+				memset(&shhwtstamps, 0, sizeof(shhwtstamps));
+				shhwtstamps.hwtstamp = timespec64_to_ktime(ts);
+				skb_tstamp_tx(skb, &shhwtstamps);
+			}
+			dma_unmap_single(ndev->dev.parent,
+					 le32_to_cpu(desc->dptr),
+					 size, DMA_TO_DEVICE);
+			dev_kfree_skb_any(priv->tx_skb[entry]);
+			free_num++;
+		}
+		desc->die_dt = DT_EEMPTY;
+		ndev->stats.tx_packets++;
+		ndev->stats.tx_bytes += size;
+	}
+
+	desc = &priv->tx_ring[priv->num_tx_ring];
+	desc->die_dt = DT_LINK;
+
+	return free_num;
+}
+
+static bool rtsn_rx(struct net_device *ndev, int *quota)
+{
+	struct rtsn_ext_ts_desc *desc;
+	struct rtsn_private *priv;
+	struct sk_buff *skb;
+	dma_addr_t dma_addr;
+	int boguscnt;
+	u16 pkt_len;
+	u32 get_ts;
+	int entry;
+	int limit;
+
+	priv = netdev_priv(ndev);
+
+	entry = priv->cur_rx % priv->num_rx_ring;
+	desc = &priv->rx_ring[entry];
+
+	boguscnt = priv->dirty_rx + priv->num_rx_ring - priv->cur_rx;
+	boguscnt = min(boguscnt, *quota);
+	limit = boguscnt;
+
+	while ((desc->die_dt & DT_MASK) != DT_FEMPTY) {
+		dma_rmb();
+		pkt_len = le16_to_cpu(desc->info_ds) & RX_DS;
+		if (--boguscnt < 0)
+			break;
+
+		skb = priv->rx_skb[entry];
+		priv->rx_skb[entry] = NULL;
+		dma_addr = le32_to_cpu(desc->dptr);
+		dma_unmap_single(ndev->dev.parent, dma_addr, PKT_BUF_SZ,
+				 DMA_FROM_DEVICE);
+
+		get_ts = priv->ptp_priv->tstamp_rx_ctrl &
+			RCAR_GEN4_RXTSTAMP_TYPE_V2_L2_EVENT;
+		if (get_ts) {
+			struct skb_shared_hwtstamps *shhwtstamps;
+			struct timespec64 ts;
+
+			shhwtstamps = skb_hwtstamps(skb);
+			memset(shhwtstamps, 0, sizeof(*shhwtstamps));
+
+			ts.tv_sec = (u64)le32_to_cpu(desc->ts_sec);
+			ts.tv_nsec = le32_to_cpu(desc->ts_nsec & cpu_to_le32(0x3fffffff));
+
+			shhwtstamps->hwtstamp = timespec64_to_ktime(ts);
+		}
+
+		skb_put(skb, pkt_len);
+		skb->protocol = eth_type_trans(skb, ndev);
+		netif_receive_skb(skb);
+		ndev->stats.rx_packets++;
+		ndev->stats.rx_bytes += pkt_len;
+
+		entry = (++priv->cur_rx) % priv->num_rx_ring;
+		desc = &priv->rx_ring[entry];
+	}
+
+	/* Refill the RX ring buffers */
+	for (; priv->cur_rx - priv->dirty_rx > 0; priv->dirty_rx++) {
+		entry = priv->dirty_rx % priv->num_rx_ring;
+		desc = &priv->rx_ring[entry];
+		desc->info_ds = cpu_to_le16(PKT_BUF_SZ);
+
+		if (!priv->rx_skb[entry]) {
+			skb = netdev_alloc_skb(ndev,
+					       PKT_BUF_SZ + RTSN_ALIGN - 1);
+			if (!skb)
+				break;
+			skb_reserve(skb, NET_IP_ALIGN);
+			dma_addr = dma_map_single(ndev->dev.parent, skb->data,
+						  le16_to_cpu(desc->info_ds),
+						  DMA_FROM_DEVICE);
+			if (dma_mapping_error(ndev->dev.parent, dma_addr))
+				desc->info_ds = cpu_to_le16(0);
+			desc->dptr = cpu_to_le32(dma_addr);
+			skb_checksum_none_assert(skb);
+			priv->rx_skb[entry] = skb;
+		}
+		dma_wmb();
+		desc->die_dt = DT_FEMPTY | D_DIE;
+	}
+
+	desc = &priv->rx_ring[priv->num_rx_ring];
+	desc->die_dt = DT_LINK;
+
+	*quota -= limit - (++boguscnt);
+
+	return boguscnt <= 0;
+}
+
+static int rtsn_poll(struct napi_struct *napi, int budget)
+{
+	struct rtsn_private *priv;
+	struct net_device *ndev;
+	unsigned long flags;
+	int quota = budget;
+
+	ndev = napi->dev;
+	priv = netdev_priv(ndev);
+
+	/* Processing RX Descriptor Ring */
+	if (rtsn_rx(ndev, &quota))
+		goto out;
+
+	/* Processing TX Descriptor Ring */
+	spin_lock_irqsave(&priv->lock, flags);
+	rtsn_tx_free(ndev, true);
+	netif_wake_subqueue(ndev, 0);
+	spin_unlock_irqrestore(&priv->lock, flags);
+
+	napi_complete(napi);
+
+	/* Re-enable TX/RX interrupts */
+	spin_lock_irqsave(&priv->lock, flags);
+	rtsn_ctrl_data_irq(priv, true);
+	__iowmb();
+	spin_unlock_irqrestore(&priv->lock, flags);
+out:
+	return budget - quota;
+}
+
+static int rtsn_desc_alloc(struct rtsn_private *priv)
+{
+	struct device *dev = &priv->pdev->dev;
+	int i;
+
+	priv->tx_desc_bat_size = sizeof(struct rtsn_desc) * TX_NUM_CHAINS;
+	priv->tx_desc_bat = dma_alloc_coherent(dev, priv->tx_desc_bat_size,
+					       &priv->tx_desc_bat_dma,
+					       GFP_KERNEL);
+
+	if (!priv->tx_desc_bat)
+		return -ENOMEM;
+
+	for (i = 0; i < TX_NUM_CHAINS; i++)
+		priv->tx_desc_bat[i].die_dt = DT_EOS;
+
+	priv->rx_desc_bat_size = sizeof(struct rtsn_desc) * RX_NUM_CHAINS;
+	priv->rx_desc_bat = dma_alloc_coherent(dev, priv->rx_desc_bat_size,
+					       &priv->rx_desc_bat_dma,
+					       GFP_KERNEL);
+
+	if (!priv->rx_desc_bat)
+		return -ENOMEM;
+
+	for (i = 0; i < RX_NUM_CHAINS; i++)
+		priv->rx_desc_bat[i].die_dt = DT_EOS;
+
+	return 0;
+}
+
+static void rtsn_desc_free(struct rtsn_private *priv)
+{
+	if (priv->tx_desc_bat)
+		dma_free_coherent(&priv->pdev->dev, priv->tx_desc_bat_size,
+				  priv->tx_desc_bat, priv->tx_desc_bat_dma);
+	priv->tx_desc_bat = NULL;
+
+	if (priv->rx_desc_bat)
+		dma_free_coherent(&priv->pdev->dev, priv->rx_desc_bat_size,
+				  priv->rx_desc_bat, priv->rx_desc_bat_dma);
+	priv->rx_desc_bat = NULL;
+}
+
+static void rtsn_chain_free(struct rtsn_private *priv)
+{
+	struct device *dev = &priv->pdev->dev;
+
+	dma_free_coherent(dev,
+			  sizeof(struct rtsn_ext_desc) * (priv->num_tx_ring + 1),
+			  priv->tx_ring, priv->tx_desc_dma);
+	priv->tx_ring = NULL;
+
+	dma_free_coherent(dev,
+			  sizeof(struct rtsn_ext_ts_desc) * (priv->num_rx_ring + 1),
+			  priv->rx_ring, priv->rx_desc_dma);
+	priv->rx_ring = NULL;
+
+	kfree(priv->tx_skb);
+	priv->tx_skb = NULL;
+
+	kfree(priv->rx_skb);
+	priv->rx_skb = NULL;
+}
+
+static int rtsn_chain_init(struct rtsn_private *priv, int tx_size, int rx_size)
+{
+	struct net_device *ndev = priv->ndev;
+	struct sk_buff *skb;
+	int i;
+
+	priv->num_tx_ring = tx_size;
+	priv->num_rx_ring = rx_size;
+
+	priv->tx_skb = kcalloc(tx_size, sizeof(*priv->tx_skb), GFP_KERNEL);
+	priv->rx_skb = kcalloc(rx_size, sizeof(*priv->rx_skb), GFP_KERNEL);
+
+	if (!priv->rx_skb || !priv->tx_skb)
+		goto error;
+
+	for (i = 0; i < rx_size; i++) {
+		skb = netdev_alloc_skb(ndev, PKT_BUF_SZ + RTSN_ALIGN - 1);
+		if (!skb)
+			goto error;
+		skb_reserve(skb, NET_IP_ALIGN);
+		priv->rx_skb[i] = skb;
+	}
+
+	/* Allocate TX, RX descriptors */
+	priv->tx_ring = dma_alloc_coherent(ndev->dev.parent,
+					   sizeof(struct rtsn_ext_desc) * (tx_size + 1),
+					   &priv->tx_desc_dma, GFP_KERNEL);
+	priv->rx_ring = dma_alloc_coherent(ndev->dev.parent,
+					   sizeof(struct rtsn_ext_ts_desc) * (rx_size + 1),
+					   &priv->rx_desc_dma, GFP_KERNEL);
+
+	if (!priv->tx_ring || !priv->rx_ring)
+		goto error;
+
+	return 0;
+error:
+	rtsn_chain_free(priv);
+
+	return -ENOMEM;
+}
+
+static void rtsn_chain_format(struct rtsn_private *priv)
+{
+	struct net_device *ndev = priv->ndev;
+	struct rtsn_ext_ts_desc *rx_desc;
+	struct rtsn_ext_desc *tx_desc;
+	struct rtsn_desc *bat_desc;
+	dma_addr_t dma_addr;
+	unsigned int i;
+
+	priv->cur_tx = 0;
+	priv->cur_rx = 0;
+	priv->dirty_rx = 0;
+	priv->dirty_tx = 0;
+
+	/* TX */
+	memset(priv->tx_ring, 0, sizeof(*tx_desc) * priv->num_tx_ring);
+	for (i = 0, tx_desc = priv->tx_ring; i < priv->num_tx_ring; i++, tx_desc++)
+		tx_desc->die_dt = DT_EEMPTY | D_DIE;
+
+	tx_desc->dptr = cpu_to_le32((u32)priv->tx_desc_dma);
+	tx_desc->die_dt = DT_LINK;
+
+	bat_desc = &priv->tx_desc_bat[TX_CHAIN_IDX];
+	bat_desc->die_dt = DT_LINK;
+	bat_desc->dptr = cpu_to_le32((u32)priv->tx_desc_dma);
+
+	/* RX */
+	memset(priv->rx_ring, 0, sizeof(*rx_desc) * priv->num_rx_ring);
+	for (i = 0, rx_desc = priv->rx_ring; i < priv->num_rx_ring; i++, rx_desc++) {
+		dma_addr = dma_map_single(ndev->dev.parent,
+					  priv->rx_skb[i]->data, PKT_BUF_SZ,
+					  DMA_FROM_DEVICE);
+		if (!dma_mapping_error(ndev->dev.parent, dma_addr))
+			rx_desc->info_ds = cpu_to_le16(PKT_BUF_SZ);
+		rx_desc->dptr = cpu_to_le32((u32)dma_addr);
+		rx_desc->die_dt = DT_FEMPTY | D_DIE;
+	}
+	rx_desc->dptr = cpu_to_le32((u32)priv->rx_desc_dma);
+	rx_desc->die_dt = DT_LINK;
+
+	bat_desc = &priv->rx_desc_bat[RX_CHAIN_IDX];
+	bat_desc->die_dt = DT_LINK;
+	bat_desc->dptr = cpu_to_le32((u32)priv->rx_desc_dma);
+}
+
+static int rtsn_dmac_init(struct rtsn_private *priv)
+{
+	int ret;
+
+	ret = rtsn_chain_init(priv, TX_CHAIN_SIZE, RX_CHAIN_SIZE);
+	if (ret)
+		return ret;
+
+	rtsn_chain_format(priv);
+
+	return 0;
+}
+
+static enum rtsn_mode rtsn_read_mode(struct rtsn_private *priv)
+{
+	return (rtsn_read(priv, OSR) & OSR_OPS) >> 1;
+}
+
+static int rtsn_wait_mode(struct rtsn_private *priv, enum rtsn_mode mode)
+{
+	unsigned int i;
+
+	/* Need to busy loop as mode changes can happen in atomic context. */
+	for (i = 0; i < RTSN_TIMEOUT_US / RTSN_INTERVAL_US; i++) {
+		if (rtsn_read_mode(priv) == mode)
+			return 0;
+
+		udelay(RTSN_INTERVAL_US);
+	}
+
+	return -ETIMEDOUT;
+}
+
+static int rtsn_change_mode(struct rtsn_private *priv, enum rtsn_mode mode)
+{
+	int ret;
+
+	rtsn_write(priv, OCR, mode);
+	ret = rtsn_wait_mode(priv, mode);
+	if (ret)
+		netdev_err(priv->ndev, "Failed to switch operation mode\n");
+	return ret;
+}
+
+static int rtsn_get_data_irq_status(struct rtsn_private *priv)
+{
+	u32 val;
+
+	val = rtsn_read(priv, TDIS0) | TDIS_TDS(TX_CHAIN_IDX);
+	val |= rtsn_read(priv, RDIS0) | RDIS_RDS(RX_CHAIN_IDX);
+
+	return val;
+}
+
+static irqreturn_t rtsn_irq(int irq, void *dev_id)
+{
+	struct rtsn_private *priv = dev_id;
+	int ret = IRQ_NONE;
+
+	spin_lock(&priv->lock);
+
+	if (rtsn_get_data_irq_status(priv)) {
+		/* Clear TX/RX irq status */
+		rtsn_write(priv, TDIS0, TDIS_TDS(TX_CHAIN_IDX));
+		rtsn_write(priv, RDIS0, RDIS_RDS(RX_CHAIN_IDX));
+
+		if (napi_schedule_prep(&priv->napi)) {
+			/* Disable TX/RX interrupts */
+			rtsn_ctrl_data_irq(priv, false);
+
+			__napi_schedule(&priv->napi);
+		}
+
+		ret = IRQ_HANDLED;
+	}
+
+	spin_unlock(&priv->lock);
+
+	return ret;
+}
+
+static int rtsn_request_irq(unsigned int irq, irq_handler_t handler,
+			    unsigned long flags, struct rtsn_private *priv,
+			    const char *ch)
+{
+	char *name;
+	int ret;
+
+	name = devm_kasprintf(&priv->pdev->dev, GFP_KERNEL, "%s:%s",
+			      priv->ndev->name, ch);
+	if (!name)
+		return -ENOMEM;
+
+	ret = request_irq(irq, handler, flags, name, priv);
+	if (ret) {
+		netdev_err(priv->ndev, "Cannot request IRQ %s\n", name);
+		free_irq(irq, priv);
+	}
+
+	return ret;
+}
+
+static void rtsn_free_irqs(struct rtsn_private *priv)
+{
+	free_irq(priv->tx_data_irq, priv);
+	free_irq(priv->rx_data_irq, priv);
+}
+
+static int rtsn_request_irqs(struct rtsn_private *priv)
+{
+	int ret;
+
+	priv->rx_data_irq = platform_get_irq_byname(priv->pdev, "rx");
+	if (priv->rx_data_irq < 0)
+		return priv->rx_data_irq;
+
+	priv->tx_data_irq = platform_get_irq_byname(priv->pdev, "tx");
+	if (priv->tx_data_irq < 0)
+		return priv->tx_data_irq;
+
+	ret = rtsn_request_irq(priv->tx_data_irq, rtsn_irq, 0, priv, "tx");
+	if (ret)
+		goto error;
+
+	ret = rtsn_request_irq(priv->rx_data_irq, rtsn_irq, 0, priv, "rx");
+	if (ret)
+		goto error;
+
+	return 0;
+error:
+	rtsn_free_irqs(priv);
+	return ret;
+}
+
+static int rtsn_reset(struct rtsn_private *priv)
+{
+	reset_control_reset(priv->reset);
+	mdelay(1);
+
+	return rtsn_wait_mode(priv, OCR_OPC_DISABLE);
+}
+
+static int rtsn_axibmi_init(struct rtsn_private *priv)
+{
+	int ret;
+
+	ret = rtsn_reg_wait(priv, RR, RR_RST, RR_RST_COMPLETE);
+	if (ret)
+		return ret;
+
+	/* Set AXIWC */
+	rtsn_write(priv, AXIWC, AXIWC_DEFAULT);
+
+	/* Set AXIRC */
+	rtsn_write(priv, AXIRC, AXIRC_DEFAULT);
+
+	/* TX Descriptor chain setting */
+	rtsn_write(priv, TATLS0, TATLS0_TEDE | TATLS0_TATEN(TX_CHAIN_IDX));
+	rtsn_write(priv, TATLS1, priv->tx_desc_bat_dma + TX_CHAIN_ADDR_OFFSET);
+	rtsn_write(priv, TATLR, TATLR_TATL);
+
+	ret = rtsn_reg_wait(priv, TATLR, TATLR_TATL, 0);
+	if (ret)
+		return ret;
+
+	/* RX Descriptor chain setting */
+	rtsn_write(priv, RATLS0,
+		   RATLS0_RETS | RATLS0_REDE | RATLS0_RATEN(RX_CHAIN_IDX));
+	rtsn_write(priv, RATLS1, priv->rx_desc_bat_dma + RX_CHAIN_ADDR_OFFSET);
+	rtsn_write(priv, RATLR, RATLR_RATL);
+
+	ret = rtsn_reg_wait(priv, RATLR, RATLR_RATL, 0);
+	if (ret)
+		return ret;
+
+	/* Enable TX/RX interrupts */
+	rtsn_ctrl_data_irq(priv, true);
+
+	return 0;
+}
+
+static void rtsn_mhd_init(struct rtsn_private *priv)
+{
+	/* TX General setting */
+	rtsn_write(priv, TGC1, TGC1_STTV_DEFAULT | TGC1_TQTM_SFM);
+	rtsn_write(priv, TMS0, TMS_MFS_MAX);
+
+	/* RX Filter IP */
+	rtsn_write(priv, CFCR0, CFCR_SDID(RX_CHAIN_IDX));
+	rtsn_write(priv, FMSCR, FMSCR_FMSIE(RX_CHAIN_IDX));
+}
+
+static int rtsn_get_phy_params(struct rtsn_private *priv)
+{
+	struct device_node *np = priv->ndev->dev.parent->of_node;
+
+	of_get_phy_mode(np, &priv->iface);
+	switch (priv->iface) {
+	case PHY_INTERFACE_MODE_MII:
+		priv->speed = 100;
+		break;
+	case PHY_INTERFACE_MODE_RGMII:
+		priv->speed = 1000;
+		break;
+	default:
+		return -EOPNOTSUPP;
+	}
+
+	return 0;
+}
+
+static void rtsn_set_phy_interface(struct rtsn_private *priv)
+{
+	u32 val;
+
+	switch (priv->iface) {
+	case PHY_INTERFACE_MODE_MII:
+		val = MPIC_PIS_MII;
+		break;
+	case PHY_INTERFACE_MODE_RGMII:
+		val = MPIC_PIS_GMII;
+		break;
+	default:
+		return;
+	}
+
+	rtsn_modify(priv, MPIC, MPIC_PIS_MASK, val);
+}
+
+static void rtsn_set_rate(struct rtsn_private *priv)
+{
+	u32 val;
+
+	switch (priv->speed) {
+	case 10:
+		val = MPIC_LSC_10M;
+		break;
+	case 100:
+		val = MPIC_LSC_100M;
+		break;
+	case 1000:
+		val = MPIC_LSC_1G;
+		break;
+	default:
+		return;
+	}
+
+	rtsn_modify(priv, MPIC, MPIC_LSC_MASK, val);
+}
+
+static int rtsn_rmac_init(struct rtsn_private *priv)
+{
+	const u8 *mac_addr = priv->ndev->dev_addr;
+	int ret;
+
+	ret = rtsn_get_phy_params(priv);
+	if (ret)
+		return ret;
+
+	/* Set MAC address */
+	rtsn_write(priv, MRMAC0, (mac_addr[0] << 8) | mac_addr[1]);
+	rtsn_write(priv, MRMAC1, (mac_addr[2] << 24) | (mac_addr[3] << 16) |
+		   (mac_addr[4] << 8) | mac_addr[5]);
+
+	/* Set xMII type */
+	rtsn_set_phy_interface(priv);
+	rtsn_set_rate(priv);
+
+	/* Enable MII */
+	rtsn_modify(priv, MPIC, MPIC_PSMCS_MASK | MPIC_PSMHT_MASK,
+		    MPIC_PSMCS_DEFAULT | MPIC_PSMHT_DEFAULT);
+
+	/* Link verification */
+	rtsn_modify(priv, MLVC, MLVC_PLV, MLVC_PLV);
+	ret = rtsn_reg_wait(priv, MLVC, MLVC_PLV, 0);
+	if (ret)
+		return ret;
+
+	return ret;
+}
+
+static void rtsn_set_delay_mode(struct rtsn_private *priv)
+{
+	struct device_node *np = priv->ndev->dev.parent->of_node;
+	u32 delay;
+	u32 val;
+
+	val = 0;
+
+	/* Valid values are 0 and 1800, according to DT bindings */
+	if (!of_property_read_u32(np, "rx-internal-delay-ps", &delay))
+		if (delay)
+			val |= GPOUT_RDM;
+
+	/* Valid values are 0 and 2000, according to DT bindings */
+	if (!of_property_read_u32(np, "tx-internal-delay-ps", &delay))
+		if (delay)
+			val |= GPOUT_TDM;
+
+	rtsn_write(priv, GPOUT, val);
+}
+
+static int rtsn_hw_init(struct rtsn_private *priv)
+{
+	int ret;
+
+	ret = rtsn_reset(priv);
+	if (ret)
+		return ret;
+
+	/* Change to CONFIG mode */
+	ret = rtsn_change_mode(priv, OCR_OPC_CONFIG);
+	if (ret)
+		return ret;
+
+	ret = rtsn_axibmi_init(priv);
+	if (ret)
+		return ret;
+
+	rtsn_mhd_init(priv);
+
+	ret = rtsn_rmac_init(priv);
+	if (ret)
+		return ret;
+
+	rtsn_set_delay_mode(priv);
+
+	ret = rtsn_change_mode(priv, OCR_OPC_DISABLE);
+	if (ret)
+		return ret;
+
+	/* Change to OPERATION mode */
+	ret = rtsn_change_mode(priv, OCR_OPC_OPERATION);
+
+	return ret;
+}
+
+static int rtsn_mii_access(struct mii_bus *bus, bool read, int phyad,
+			   int regad, u16 data)
+{
+	struct rtsn_private *priv = bus->priv;
+	u32 val;
+	int ret;
+
+	val = MPSM_PDA(phyad) | MPSM_PRA(regad) | MPSM_PSME;
+
+	if (!read)
+		val |= MPSM_PSMAD | MPSM_PRD_SET(data);
+
+	rtsn_write(priv, MPSM, val);
+
+	ret = rtsn_reg_wait(priv, MPSM, MPSM_PSME, 0);
+	if (ret)
+		return ret;
+
+	if (read)
+		ret = MPSM_PRD_GET(rtsn_read(priv, MPSM));
+
+	return ret;
+}
+
+static int rtsn_mii_read(struct mii_bus *bus, int addr, int regnum)
+{
+	return rtsn_mii_access(bus, true, addr, regnum, 0);
+}
+
+static int rtsn_mii_write(struct mii_bus *bus, int addr, int regnum, u16 val)
+{
+	return rtsn_mii_access(bus, false, addr, regnum, val);
+}
+
+static int rtsn_mii_access_indirect(struct mii_bus *bus, bool read, int phyad,
+				    int devnum, int regnum, u16 data)
+{
+	int ret;
+
+	ret = rtsn_mii_access(bus, false, phyad, MII_MMD_CTRL, devnum);
+	if (ret)
+		return ret;
+
+	ret = rtsn_mii_access(bus, false, phyad, MII_MMD_DATA, regnum);
+	if (ret)
+		return ret;
+
+	ret = rtsn_mii_access(bus, false, phyad, MII_MMD_CTRL,
+			      devnum | MII_MMD_CTRL_NOINCR);
+	if (ret)
+		return ret;
+
+	if (read)
+		ret = rtsn_mii_access(bus, true, phyad, MII_MMD_DATA, 0);
+	else
+		ret = rtsn_mii_access(bus, false, phyad, MII_MMD_DATA, data);
+
+	return ret;
+}
+
+static int rtsn_mii_read_c45(struct mii_bus *bus, int addr, int devnum,
+			     int regnum)
+{
+	return rtsn_mii_access_indirect(bus, true, addr, devnum, regnum, 0);
+}
+
+static int rtsn_mii_write_c45(struct mii_bus *bus, int addr, int devnum,
+			      int regnum, u16 val)
+{
+	return rtsn_mii_access_indirect(bus, false, addr, devnum, regnum, val);
+}
+
+static int rtsn_mii_register(struct rtsn_private *priv)
+{
+	struct platform_device *pdev = priv->pdev;
+	struct device *dev = &pdev->dev;
+	struct device_node *mdio_node;
+	struct mii_bus *mii;
+	int ret;
+
+	mii = mdiobus_alloc();
+	if (!mii)
+		return -ENOMEM;
+
+	mdio_node = of_get_child_by_name(dev->of_node, "mdio");
+	if (!mdio_node) {
+		ret = -ENODEV;
+		goto out_free_bus;
+	};
+
+	mii->name = "rtsn_mii";
+	sprintf(mii->id, "%s-%x", pdev->name, pdev->id);
+	mii->priv = priv;
+	mii->read = rtsn_mii_read;
+	mii->write = rtsn_mii_write;
+	mii->read_c45 = rtsn_mii_read_c45;
+	mii->write_c45 = rtsn_mii_write_c45;
+	mii->parent = dev;
+
+	ret = of_mdiobus_register(mii, mdio_node);
+	of_node_put(mdio_node);
+	if (ret)
+		goto out_free_bus;
+
+	priv->mii = mii;
+
+	return 0;
+
+out_free_bus:
+	mdiobus_free(mii);
+	return ret;
+}
+
+static void rtsn_mii_unregister(struct rtsn_private *priv)
+{
+	if (priv->mii) {
+		mdiobus_unregister(priv->mii);
+		mdiobus_free(priv->mii);
+		priv->mii = NULL;
+	}
+}
+
+static void rtsn_adjust_link(struct net_device *ndev)
+{
+	struct rtsn_private *priv = netdev_priv(ndev);
+	struct phy_device *phydev = ndev->phydev;
+	bool new_state = false;
+	unsigned long flags;
+
+	spin_lock_irqsave(&priv->lock, flags);
+
+	if (phydev->link) {
+		if (phydev->speed != priv->speed) {
+			new_state = true;
+			priv->speed = phydev->speed;
+		}
+
+		if (!priv->link) {
+			new_state = true;
+			priv->link = phydev->link;
+		}
+	} else if (priv->link) {
+		new_state = true;
+		priv->link = 0;
+		priv->speed = 0;
+	}
+
+	if (new_state) {
+		/* Need to transition to CONFIG mode before reconfiguring and
+		 * then back to the original mode. Any state change to/from
+		 * CONFIG or OPERATION must go over DISABLED to stop Rx/Tx.
+		 */
+		enum rtsn_mode orgmode = rtsn_read_mode(priv);
+
+		/* Transit to CONFIG */
+		if (orgmode != OCR_OPC_CONFIG) {
+			if (orgmode != OCR_OPC_DISABLE &&
+			    rtsn_change_mode(priv, OCR_OPC_DISABLE))
+				goto out;
+			if (rtsn_change_mode(priv, OCR_OPC_CONFIG))
+				goto out;
+		}
+
+		rtsn_set_rate(priv);
+
+		/* Transition to original mode */
+		if (orgmode != OCR_OPC_CONFIG) {
+			if (rtsn_change_mode(priv, OCR_OPC_DISABLE))
+				goto out;
+			if (orgmode != OCR_OPC_DISABLE &&
+			    rtsn_change_mode(priv, orgmode))
+				goto out;
+		}
+	}
+out:
+	spin_unlock_irqrestore(&priv->lock, flags);
+
+	if (new_state)
+		phy_print_status(phydev);
+}
+
+static int rtsn_phy_init(struct rtsn_private *priv)
+{
+	struct device_node *np = priv->ndev->dev.parent->of_node;
+	struct phy_device *phydev;
+	struct device_node *phy;
+
+	priv->link = 0;
+
+	phy = of_parse_phandle(np, "phy-handle", 0);
+	if (!phy)
+		return -ENOENT;
+
+	phydev = of_phy_connect(priv->ndev, phy, rtsn_adjust_link, 0,
+				priv->iface);
+	if (!phydev) {
+		of_node_put(phy);
+		return -ENOENT;
+	}
+
+	/* Only support full-duplex mode */
+	phy_remove_link_mode(phydev, ETHTOOL_LINK_MODE_10baseT_Half_BIT);
+	phy_remove_link_mode(phydev, ETHTOOL_LINK_MODE_100baseT_Half_BIT);
+	phy_remove_link_mode(phydev, ETHTOOL_LINK_MODE_1000baseT_Half_BIT);
+
+	phy_attached_info(phydev);
+
+	return 0;
+}
+
+static void rtsn_phy_deinit(struct rtsn_private *priv)
+{
+	phy_stop(priv->ndev->phydev);
+	phy_disconnect(priv->ndev->phydev);
+	priv->ndev->phydev = NULL;
+}
+
+static int rtsn_init(struct rtsn_private *priv)
+{
+	int ret;
+
+	ret = rtsn_desc_alloc(priv);
+	if (ret)
+		return ret;
+
+	ret = rtsn_dmac_init(priv);
+	if (ret)
+		goto error_free_desc;
+
+	/* HW initialization */
+	ret = rtsn_hw_init(priv);
+	if (ret)
+		goto error_free_chain;
+
+	ret = rtsn_mii_register(priv);
+	if (ret)
+		goto error_free_desc;
+
+	ret = rtsn_phy_init(priv);
+	if (ret)
+		goto error_unregister_mii;
+
+	ret = rtsn_request_irqs(priv);
+	if (ret)
+		goto error_unregister_mii;
+
+	return 0;
+error_unregister_mii:
+	rtsn_mii_unregister(priv);
+error_free_chain:
+	rtsn_chain_free(priv);
+error_free_desc:
+	rtsn_desc_free(priv);
+	return ret;
+}
+
+static void rtsn_deinit(struct rtsn_private *priv)
+{
+	rtsn_free_irqs(priv);
+	rtsn_phy_deinit(priv);
+	rtsn_mii_unregister(priv);
+	rtsn_chain_free(priv);
+	rtsn_desc_free(priv);
+}
+
+static void rtsn_parse_mac_address(struct device_node *np,
+				   struct net_device *ndev)
+{
+	struct rtsn_private *priv = netdev_priv(ndev);
+	u8 addr[ETH_ALEN];
+	u32 mrmac0;
+	u32 mrmac1;
+
+	/* Try to read address from Device Tree. */
+	if (!of_get_mac_address(np, addr)) {
+		eth_hw_addr_set(ndev, addr);
+		return;
+	}
+
+	/* Try to read address from device. */
+	mrmac0 = rtsn_read(priv, MRMAC0);
+	mrmac1 = rtsn_read(priv, MRMAC1);
+
+	addr[0] = (mrmac0 >>  8) & 0xff;
+	addr[1] = (mrmac0 >>  0) & 0xff;
+	addr[2] = (mrmac1 >> 24) & 0xff;
+	addr[3] = (mrmac1 >> 16) & 0xff;
+	addr[4] = (mrmac1 >>  8) & 0xff;
+	addr[5] = (mrmac1 >>  0) & 0xff;
+
+	if (is_valid_ether_addr(addr)) {
+		eth_hw_addr_set(ndev, addr);
+		return;
+	}
+
+	/* Fallback to a random address */
+	eth_hw_addr_random(ndev);
+}
+
+static int rtsn_open(struct net_device *ndev)
+{
+	struct rtsn_private *priv = netdev_priv(ndev);
+	int ret;
+
+	napi_enable(&priv->napi);
+
+	ret = rtsn_init(priv);
+	if (ret) {
+		napi_disable(&priv->napi);
+		return ret;
+	}
+
+	phy_start(ndev->phydev);
+
+	netif_start_queue(ndev);
+
+	return 0;
+}
+
+static int rtsn_stop(struct net_device *ndev)
+{
+	struct rtsn_private *priv = netdev_priv(ndev);
+
+	napi_disable(&priv->napi);
+	rtsn_change_mode(priv, OCR_OPC_DISABLE);
+	rtsn_deinit(priv);
+
+	return 0;
+}
+
+static netdev_tx_t rtsn_start_xmit(struct sk_buff *skb, struct net_device *ndev)
+{
+	struct rtsn_private *priv = netdev_priv(ndev);
+	struct rtsn_ext_desc *desc;
+	int ret = NETDEV_TX_OK;
+	unsigned long flags;
+	dma_addr_t dma_addr;
+	int entry;
+
+	spin_lock_irqsave(&priv->lock, flags);
+
+	if (priv->cur_tx - priv->dirty_tx > priv->num_tx_ring) {
+		netif_stop_subqueue(ndev, 0);
+		ret = NETDEV_TX_BUSY;
+		goto out;
+	}
+
+	if (skb_put_padto(skb, ETH_ZLEN))
+		goto out;
+
+	dma_addr = dma_map_single(ndev->dev.parent, skb->data, skb->len,
+				  DMA_TO_DEVICE);
+	if (dma_mapping_error(ndev->dev.parent, dma_addr)) {
+		dev_kfree_skb_any(skb);
+		goto out;
+	}
+
+	entry = priv->cur_tx % priv->num_tx_ring;
+	priv->tx_skb[entry] = skb;
+	desc = &priv->tx_ring[entry];
+	desc->dptr = cpu_to_le32(dma_addr);
+	desc->info_ds = cpu_to_le16(skb->len);
+	desc->info1 = cpu_to_le64(skb->len);
+
+	if (skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP) {
+		skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS;
+		priv->ts_tag++;
+		desc->info_ds |= cpu_to_le16(TXC);
+		desc->info = priv->ts_tag;
+	}
+
+	skb_tx_timestamp(skb);
+	dma_wmb();
+
+	desc->die_dt = DT_FSINGLE | D_DIE;
+	priv->cur_tx++;
+
+	/* Start xmit */
+	rtsn_write(priv, TRCR0, BIT(TX_CHAIN_IDX));
+out:
+	spin_unlock_irqrestore(&priv->lock, flags);
+	return ret;
+}
+
+static struct net_device_stats *rtsn_get_stats(struct net_device *ndev)
+{
+	return &ndev->stats;
+}
+
+static int rtsn_hwstamp_get(struct net_device *ndev, struct ifreq *req)
+{
+	struct rcar_gen4_ptp_private *ptp_priv;
+	struct hwtstamp_config config;
+	struct rtsn_private *priv;
+
+	priv = netdev_priv(ndev);
+	ptp_priv = priv->ptp_priv;
+
+	config.flags = 0;
+
+	config.tx_type =
+		ptp_priv->tstamp_tx_ctrl ? HWTSTAMP_TX_ON : HWTSTAMP_TX_OFF;
+
+	switch (ptp_priv->tstamp_rx_ctrl & RCAR_GEN4_RXTSTAMP_TYPE) {
+	case RCAR_GEN4_RXTSTAMP_TYPE_V2_L2_EVENT:
+		config.rx_filter = HWTSTAMP_FILTER_PTP_V2_L2_EVENT;
+		break;
+	case RCAR_GEN4_RXTSTAMP_TYPE_ALL:
+		config.rx_filter = HWTSTAMP_FILTER_ALL;
+		break;
+	default:
+		config.rx_filter = HWTSTAMP_FILTER_NONE;
+	}
+
+	return copy_to_user(req->ifr_data, &config,
+			    sizeof(config)) ? -EFAULT : 0;
+}
+
+static int rtsn_hwstamp_set(struct net_device *ndev, struct ifreq *req)
+{
+	struct rcar_gen4_ptp_private *ptp_priv;
+	struct hwtstamp_config config;
+	struct rtsn_private *priv;
+	u32 tstamp_rx_ctrl;
+	u32 tstamp_tx_ctrl;
+
+	priv = netdev_priv(ndev);
+	ptp_priv = priv->ptp_priv;
+
+	if (copy_from_user(&config, req->ifr_data, sizeof(config)))
+		return -EFAULT;
+
+	if (config.flags)
+		return -EINVAL;
+
+	switch (config.tx_type) {
+	case HWTSTAMP_TX_OFF:
+		tstamp_tx_ctrl = 0;
+		break;
+	case HWTSTAMP_TX_ON:
+		tstamp_tx_ctrl = RCAR_GEN4_TXTSTAMP_ENABLED;
+		break;
+	default:
+		return -ERANGE;
+	}
+
+	switch (config.rx_filter) {
+	case HWTSTAMP_FILTER_NONE:
+		tstamp_rx_ctrl = 0;
+		break;
+	case HWTSTAMP_FILTER_PTP_V2_L2_EVENT:
+		tstamp_rx_ctrl = RCAR_GEN4_RXTSTAMP_ENABLED |
+			RCAR_GEN4_RXTSTAMP_TYPE_V2_L2_EVENT;
+		break;
+	default:
+		config.rx_filter = HWTSTAMP_FILTER_ALL;
+		tstamp_rx_ctrl = RCAR_GEN4_RXTSTAMP_ENABLED |
+			RCAR_GEN4_RXTSTAMP_TYPE_ALL;
+	}
+
+	ptp_priv->tstamp_tx_ctrl = tstamp_tx_ctrl;
+	ptp_priv->tstamp_rx_ctrl = tstamp_rx_ctrl;
+
+	return copy_to_user(req->ifr_data, &config,
+			    sizeof(config)) ? -EFAULT : 0;
+}
+
+static int rtsn_do_ioctl(struct net_device *ndev, struct ifreq *req, int cmd)
+{
+	if (!netif_running(ndev))
+		return -EINVAL;
+
+	switch (cmd) {
+	case SIOCGHWTSTAMP:
+		return rtsn_hwstamp_get(ndev, req);
+	case SIOCSHWTSTAMP:
+		return rtsn_hwstamp_set(ndev, req);
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static const struct net_device_ops rtsn_netdev_ops = {
+	.ndo_open		= rtsn_open,
+	.ndo_stop		= rtsn_stop,
+	.ndo_start_xmit		= rtsn_start_xmit,
+	.ndo_get_stats		= rtsn_get_stats,
+	.ndo_eth_ioctl		= rtsn_do_ioctl,
+	.ndo_validate_addr	= eth_validate_addr,
+	.ndo_set_mac_address	= eth_mac_addr,
+};
+
+static int rtsn_get_ts_info(struct net_device *ndev,
+			    struct ethtool_ts_info *info)
+{
+	struct rtsn_private *priv = netdev_priv(ndev);
+
+	info->phc_index = ptp_clock_index(priv->ptp_priv->clock);
+	info->so_timestamping = SOF_TIMESTAMPING_TX_SOFTWARE |
+		SOF_TIMESTAMPING_RX_SOFTWARE |
+		SOF_TIMESTAMPING_SOFTWARE |
+		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);
+
+	return 0;
+}
+
+static const struct ethtool_ops rtsn_ethtool_ops = {
+	.nway_reset		= phy_ethtool_nway_reset,
+	.get_link		= ethtool_op_get_link,
+	.get_ts_info		= rtsn_get_ts_info,
+	.get_link_ksettings	= phy_ethtool_get_link_ksettings,
+	.set_link_ksettings	= phy_ethtool_set_link_ksettings,
+};
+
+static const struct of_device_id rtsn_match_table[] = {
+	{.compatible = "renesas,r8a779g0-ethertsn", },
+	{ /* Sentinel */ }
+};
+
+MODULE_DEVICE_TABLE(of, rtsn_match_table);
+
+static int rtsn_probe(struct platform_device *pdev)
+{
+	struct rtsn_private *priv;
+	struct net_device *ndev;
+	struct resource *res;
+	int ret;
+
+	ndev = alloc_etherdev_mqs(sizeof(struct rtsn_private), TX_NUM_CHAINS,
+				  RX_NUM_CHAINS);
+	if (!ndev)
+		return -ENOMEM;
+
+	priv = netdev_priv(ndev);
+	priv->pdev = pdev;
+	priv->ndev = ndev;
+	priv->ptp_priv = rcar_gen4_ptp_alloc(pdev);
+
+	spin_lock_init(&priv->lock);
+	platform_set_drvdata(pdev, priv);
+
+	priv->clk = devm_clk_get(&pdev->dev, NULL);
+	if (IS_ERR(priv->clk)) {
+		ret = -PTR_ERR(priv->clk);
+		goto error_alloc;
+	}
+
+	priv->reset = devm_reset_control_get(&pdev->dev, NULL);
+	if (IS_ERR(priv->reset)) {
+		ret = -PTR_ERR(priv->reset);
+		goto error_alloc;
+	}
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "tsnes");
+	if (!res) {
+		dev_err(&pdev->dev, "Can't find tsnes resource\n");
+		ret = -EINVAL;
+		goto error_alloc;
+	}
+
+	priv->base = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(priv->base)) {
+		ret = PTR_ERR(priv->base);
+		goto error_alloc;
+	}
+
+	SET_NETDEV_DEV(ndev, &pdev->dev);
+	ether_setup(ndev);
+
+	ndev->features = NETIF_F_RXCSUM;
+	ndev->hw_features = NETIF_F_RXCSUM;
+	ndev->base_addr = res->start;
+	ndev->netdev_ops = &rtsn_netdev_ops;
+	ndev->ethtool_ops = &rtsn_ethtool_ops;
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "gptp");
+	if (!res) {
+		dev_err(&pdev->dev, "Can't find gptp resource\n");
+		ret = -EINVAL;
+		goto error_alloc;
+	}
+	priv->ptp_priv->addr = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(priv->ptp_priv->addr)) {
+		ret = -PTR_ERR(priv->ptp_priv->addr);
+		goto error_alloc;
+	}
+
+	pm_runtime_enable(&pdev->dev);
+	pm_runtime_get_sync(&pdev->dev);
+
+	netif_napi_add(ndev, &priv->napi, rtsn_poll);
+
+	rtsn_parse_mac_address(pdev->dev.of_node, ndev);
+
+	ret = register_netdev(ndev);
+	if (ret)
+		goto error_pm;
+
+	dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32));
+
+	device_set_wakeup_capable(&pdev->dev, 1);
+
+	ret = rcar_gen4_ptp_register(priv->ptp_priv, RCAR_GEN4_PTP_REG_LAYOUT,
+				     clk_get_rate(priv->clk));
+	if (ret)
+		goto error_ndev;
+
+	netdev_info(ndev, "MAC address %pM\n", ndev->dev_addr);
+
+	return 0;
+
+error_ndev:
+	netif_napi_del(&priv->napi);
+	unregister_netdev(ndev);
+error_pm:
+	pm_runtime_put_sync(&pdev->dev);
+	pm_runtime_disable(&pdev->dev);
+error_alloc:
+	free_netdev(ndev);
+
+	return ret;
+}
+
+static int rtsn_remove(struct platform_device *pdev)
+{
+	struct rtsn_private *priv = platform_get_drvdata(pdev);
+
+	rcar_gen4_ptp_unregister(priv->ptp_priv);
+	rtsn_change_mode(priv, OCR_OPC_DISABLE);
+	netif_napi_del(&priv->napi);
+	unregister_netdev(priv->ndev);
+
+	pm_runtime_put_sync(&pdev->dev);
+	pm_runtime_disable(&pdev->dev);
+
+	free_netdev(priv->ndev);
+
+	return 0;
+}
+
+static struct platform_driver rtsn_driver = {
+	.probe		= rtsn_probe,
+	.remove		= rtsn_remove,
+	.driver	= {
+		.name	= "rtsn",
+		.of_match_table	= rtsn_match_table,
+	}
+};
+module_platform_driver(rtsn_driver);
+
+MODULE_AUTHOR("Phong Hoang, Niklas Söderlund");
+MODULE_DESCRIPTION("Renesas Ethernet-TSN device driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/ethernet/renesas/rtsn.h b/drivers/net/ethernet/renesas/rtsn.h
new file mode 100644
index 000000000000..3183e80d7e6b
--- /dev/null
+++ b/drivers/net/ethernet/renesas/rtsn.h
@@ -0,0 +1,464 @@ 
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/* Renesas Ethernet-TSN device driver
+ *
+ * Copyright (C) 2022 Renesas Electronics Corporation
+ * Copyright (C) 2023 Niklas Söderlund <niklas.soderlund@ragnatech.se>
+ */
+
+#ifndef __RTSN_H__
+#define __RTSN_H__
+
+#include <linux/types.h>
+
+#define AXIBMI	0x0000
+#define TSNMHD	0x1000
+#define RMSO	0x2000
+#define RMRO	0x3800
+
+enum rtsn_reg {
+	AXIWC		= AXIBMI + 0x0000,
+	AXIRC		= AXIBMI + 0x0004,
+	TDPC0		= AXIBMI + 0x0010,
+	TFT		= AXIBMI + 0x0090,
+	TATLS0		= AXIBMI + 0x00a0,
+	TATLS1		= AXIBMI + 0x00a4,
+	TATLR		= AXIBMI + 0x00a8,
+	RATLS0		= AXIBMI + 0x00b0,
+	RATLS1		= AXIBMI + 0x00b4,
+	RATLR		= AXIBMI + 0x00b8,
+	TSA0		= AXIBMI + 0x00c0,
+	TSS0		= AXIBMI + 0x00c4,
+	TRCR0		= AXIBMI + 0x0140,
+	RIDAUAS0	= AXIBMI + 0x0180,
+	RR		= AXIBMI + 0x0200,
+	TATS		= AXIBMI + 0x0210,
+	TATSR0		= AXIBMI + 0x0214,
+	TATSR1		= AXIBMI + 0x0218,
+	TATSR2		= AXIBMI + 0x021c,
+	RATS		= AXIBMI + 0x0220,
+	RATSR0		= AXIBMI + 0x0224,
+	RATSR1		= AXIBMI + 0x0228,
+	RATSR2		= AXIBMI + 0x022c,
+	RIDASM0		= AXIBMI + 0x0240,
+	RIDASAM0	= AXIBMI + 0x0244,
+	RIDACAM0	= AXIBMI + 0x0248,
+	EIS0		= AXIBMI + 0x0300,
+	EIE0		= AXIBMI + 0x0304,
+	EID0		= AXIBMI + 0x0308,
+	EIS1		= AXIBMI + 0x0310,
+	EIE1		= AXIBMI + 0x0314,
+	EID1		= AXIBMI + 0x0318,
+	TCEIS0		= AXIBMI + 0x0340,
+	TCEIE0		= AXIBMI + 0x0344,
+	TCEID0		= AXIBMI + 0x0348,
+	RFSEIS0		= AXIBMI + 0x04c0,
+	RFSEIE0		= AXIBMI + 0x04c4,
+	RFSEID0		= AXIBMI + 0x04c8,
+	RFEIS0		= AXIBMI + 0x0540,
+	RFEIE0		= AXIBMI + 0x0544,
+	RFEID0		= AXIBMI + 0x0548,
+	RCEIS0		= AXIBMI + 0x05c0,
+	RCEIE0		= AXIBMI + 0x05c4,
+	RCEID0		= AXIBMI + 0x05c8,
+	RIDAOIS		= AXIBMI + 0x0640,
+	RIDAOIE		= AXIBMI + 0x0644,
+	RIDAOID		= AXIBMI + 0x0648,
+	TSFEIS		= AXIBMI + 0x06c0,
+	TSFEIE		= AXIBMI + 0x06c4,
+	TSFEID		= AXIBMI + 0x06c8,
+	TSCEIS		= AXIBMI + 0x06d0,
+	TSCEIE		= AXIBMI + 0x06d4,
+	TSCEID		= AXIBMI + 0x06d8,
+	DIS		= AXIBMI + 0x0b00,
+	DIE		= AXIBMI + 0x0b04,
+	DID		= AXIBMI + 0x0b08,
+	TDIS0		= AXIBMI + 0x0b10,
+	TDIE0		= AXIBMI + 0x0b14,
+	TDID0		= AXIBMI + 0x0b18,
+	RDIS0		= AXIBMI + 0x0b90,
+	RDIE0		= AXIBMI + 0x0b94,
+	RDID0		= AXIBMI + 0x0b98,
+	TSDIS		= AXIBMI + 0x0c10,
+	TSDIE		= AXIBMI + 0x0c14,
+	TSDID		= AXIBMI + 0x0c18,
+	GPOUT		= AXIBMI + 0x6000,
+
+	OCR		= TSNMHD + 0x0000,
+	OSR		= TSNMHD + 0x0004,
+	SWR		= TSNMHD + 0x0008,
+	SIS		= TSNMHD + 0x000c,
+	GIS		= TSNMHD + 0x0010,
+	GIE		= TSNMHD + 0x0014,
+	GID		= TSNMHD + 0x0018,
+	TIS1		= TSNMHD + 0x0020,
+	TIE1		= TSNMHD + 0x0024,
+	TID1		= TSNMHD + 0x0028,
+	TIS2		= TSNMHD + 0x0030,
+	TIE2		= TSNMHD + 0x0034,
+	TID2		= TSNMHD + 0x0038,
+	RIS		= TSNMHD + 0x0040,
+	RIE		= TSNMHD + 0x0044,
+	RID		= TSNMHD + 0x0048,
+	TGC1		= TSNMHD + 0x0050,
+	TGC2		= TSNMHD + 0x0054,
+	TFS0		= TSNMHD + 0x0060,
+	TCF0		= TSNMHD + 0x0070,
+	TCR1		= TSNMHD + 0x0080,
+	TCR2		= TSNMHD + 0x0084,
+	TCR3		= TSNMHD + 0x0088,
+	TCR4		= TSNMHD + 0x008c,
+	TMS0		= TSNMHD + 0x0090,
+	TSR1		= TSNMHD + 0x00b0,
+	TSR2		= TSNMHD + 0x00b4,
+	TSR3		= TSNMHD + 0x00b8,
+	TSR4		= TSNMHD + 0x00bc,
+	TSR5		= TSNMHD + 0x00c0,
+	RGC		= TSNMHD + 0x00d0,
+	RDFCR		= TSNMHD + 0x00d4,
+	RCFCR		= TSNMHD + 0x00d8,
+	REFCNCR		= TSNMHD + 0x00dc,
+	RSR1		= TSNMHD + 0x00e0,
+	RSR2		= TSNMHD + 0x00e4,
+	RSR3		= TSNMHD + 0x00e8,
+	TCIS		= TSNMHD + 0x01e0,
+	TCIE		= TSNMHD + 0x01e4,
+	TCID		= TSNMHD + 0x01e8,
+	TPTPC		= TSNMHD + 0x01f0,
+	TTML		= TSNMHD + 0x01f4,
+	TTJ		= TSNMHD + 0x01f8,
+	TCC		= TSNMHD + 0x0200,
+	TCS		= TSNMHD + 0x0204,
+	TGS		= TSNMHD + 0x020c,
+	TACST0		= TSNMHD + 0x0210,
+	TACST1		= TSNMHD + 0x0214,
+	TACST2		= TSNMHD + 0x0218,
+	TALIT0		= TSNMHD + 0x0220,
+	TALIT1		= TSNMHD + 0x0224,
+	TALIT2		= TSNMHD + 0x0228,
+	TAEN0		= TSNMHD + 0x0230,
+	TAEN1		= TSNMHD + 0x0234,
+	TASFE		= TSNMHD + 0x0240,
+	TACLL0		= TSNMHD + 0x0250,
+	TACLL1		= TSNMHD + 0x0254,
+	TACLL2		= TSNMHD + 0x0258,
+	CACC		= TSNMHD + 0x0260,
+	CCS		= TSNMHD + 0x0264,
+	CAIV0		= TSNMHD + 0x0270,
+	CAUL0		= TSNMHD + 0x0290,
+	TOCST0		= TSNMHD + 0x0300,
+	TOCST1		= TSNMHD + 0x0304,
+	TOCST2		= TSNMHD + 0x0308,
+	TOLIT0		= TSNMHD + 0x0310,
+	TOLIT1		= TSNMHD + 0x0314,
+	TOLIT2		= TSNMHD + 0x0318,
+	TOEN0		= TSNMHD + 0x0320,
+	TOEN1		= TSNMHD + 0x0324,
+	TOSFE		= TSNMHD + 0x0330,
+	TCLR0		= TSNMHD + 0x0340,
+	TCLR1		= TSNMHD + 0x0344,
+	TCLR2		= TSNMHD + 0x0348,
+	TSMS		= TSNMHD + 0x0350,
+	COCC		= TSNMHD + 0x0360,
+	COIV0		= TSNMHD + 0x03b0,
+	COUL0		= TSNMHD + 0x03d0,
+	QSTMACU0	= TSNMHD + 0x0400,
+	QSTMACD0	= TSNMHD + 0x0404,
+	QSTMAMU0	= TSNMHD + 0x0408,
+	QSTMAMD0	= TSNMHD + 0x040c,
+	QSFTVL0		= TSNMHD + 0x0410,
+	QSFTVLM0	= TSNMHD + 0x0414,
+	QSFTMSD0	= TSNMHD + 0x0418,
+	QSFTGMI0	= TSNMHD + 0x041c,
+	QSFTLS		= TSNMHD + 0x0600,
+	QSFTLIS		= TSNMHD + 0x0604,
+	QSFTLIE		= TSNMHD + 0x0608,
+	QSFTLID		= TSNMHD + 0x060c,
+	QSMSMC		= TSNMHD + 0x0610,
+	QSGTMC		= TSNMHD + 0x0614,
+	QSEIS		= TSNMHD + 0x0618,
+	QSEIE		= TSNMHD + 0x061c,
+	QSEID		= TSNMHD + 0x0620,
+	QGACST0		= TSNMHD + 0x0630,
+	QGACST1		= TSNMHD + 0x0634,
+	QGACST2		= TSNMHD + 0x0638,
+	QGALIT1		= TSNMHD + 0x0640,
+	QGALIT2		= TSNMHD + 0x0644,
+	QGAEN0		= TSNMHD + 0x0648,
+	QGAEN1		= TSNMHD + 0x074c,
+	QGIGS		= TSNMHD + 0x0650,
+	QGGC		= TSNMHD + 0x0654,
+	QGATL0		= TSNMHD + 0x0664,
+	QGATL1		= TSNMHD + 0x0668,
+	QGATL2		= TSNMHD + 0x066c,
+	QGOCST0		= TSNMHD + 0x0670,
+	QGOCST1		= TSNMHD + 0x0674,
+	QGOCST2		= TSNMHD + 0x0678,
+	QGOLIT0		= TSNMHD + 0x067c,
+	QGOLIT1		= TSNMHD + 0x0680,
+	QGOLIT2		= TSNMHD + 0x0684,
+	QGOEN0		= TSNMHD + 0x0688,
+	QGOEN1		= TSNMHD + 0x068c,
+	QGTRO		= TSNMHD + 0x0690,
+	QGTR1		= TSNMHD + 0x0694,
+	QGTR2		= TSNMHD + 0x0698,
+	QGFSMS		= TSNMHD + 0x069c,
+	QTMIS		= TSNMHD + 0x06e0,
+	QTMIE		= TSNMHD + 0x06e4,
+	QTMID		= TSNMHD + 0x06e8,
+	QMEC		= TSNMHD + 0x0700,
+	QMMC		= TSNMHD + 0x0704,
+	QRFDC		= TSNMHD + 0x0708,
+	QYFDC		= TSNMHD + 0x070c,
+	QVTCMC0		= TSNMHD + 0x0710,
+	QMCBSC0		= TSNMHD + 0x0750,
+	QMCIRC0		= TSNMHD + 0x0790,
+	QMEBSC0		= TSNMHD + 0x07d0,
+	QMEIRC0		= TSNMHD + 0x0710,
+	QMCFC		= TSNMHD + 0x0850,
+	QMEIS		= TSNMHD + 0x0860,
+	QMEIE		= TSNMHD + 0x0864,
+	QMEID		= TSNMHD + 0x086c,
+	QSMFC0		= TSNMHD + 0x0870,
+	QMSPPC0		= TSNMHD + 0x08b0,
+	QMSRPC0		= TSNMHD + 0x08f0,
+	QGPPC0		= TSNMHD + 0x0930,
+	QGRPC0		= TSNMHD + 0x0950,
+	QMDPC0		= TSNMHD + 0x0970,
+	QMGPC0		= TSNMHD + 0x09b0,
+	QMYPC0		= TSNMHD + 0x09f0,
+	QMRPC0		= TSNMHD + 0x0a30,
+	MQSTMACU	= TSNMHD + 0x0a70,
+	MQSTMACD	= TSNMHD + 0x0a74,
+	MQSTMAMU	= TSNMHD + 0x0a78,
+	MQSTMAMD	= TSNMHD + 0x0a7c,
+	MQSFTVL		= TSNMHD + 0x0a80,
+	MQSFTVLM	= TSNMHD + 0x0a84,
+	MQSFTMSD	= TSNMHD + 0x0a88,
+	MQSFTGMI	= TSNMHD + 0x0a8c,
+
+	CFCR0		= RMSO + 0x0800,
+	FMSCR		= RMSO + 0x0c10,
+
+	MMC		= RMRO + 0x0000,
+	MPSM		= RMRO + 0x0010,
+	MPIC		= RMRO + 0x0014,
+	MTFFC		= RMRO + 0x0020,
+	MTPFC		= RMRO + 0x0024,
+	MTATC0		= RMRO + 0x0040,
+	MRGC		= RMRO + 0x0080,
+	MRMAC0		= RMRO + 0x0084,
+	MRMAC1		= RMRO + 0x0088,
+	MRAFC		= RMRO + 0x008c,
+	MRSCE		= RMRO + 0x0090,
+	MRSCP		= RMRO + 0x0094,
+	MRSCC		= RMRO + 0x0098,
+	MRFSCE		= RMRO + 0x009c,
+	MRFSCP		= RMRO + 0x00a0,
+	MTRC		= RMRO + 0x00a4,
+	MPFC		= RMRO + 0x0100,
+	MLVC		= RMRO + 0x0340,
+	MEEEC		= RMRO + 0x0350,
+	MLBC		= RMRO + 0x0360,
+	MGMR		= RMRO + 0x0400,
+	MMPFTCT		= RMRO + 0x0410,
+	MAPFTCT		= RMRO + 0x0414,
+	MPFRCT		= RMRO + 0x0418,
+	MFCICT		= RMRO + 0x041c,
+	MEEECT		= RMRO + 0x0420,
+	MEIS		= RMRO + 0x0500,
+	MEIE		= RMRO + 0x0504,
+	MEID		= RMRO + 0x0508,
+	MMIS0		= RMRO + 0x0510,
+	MMIE0		= RMRO + 0x0514,
+	MMID0		= RMRO + 0x0518,
+	MMIS1		= RMRO + 0x0520,
+	MMIE1		= RMRO + 0x0524,
+	MMID1		= RMRO + 0x0528,
+	MMIS2		= RMRO + 0x0530,
+	MMIE2		= RMRO + 0x0534,
+	MMID2		= RMRO + 0x0538,
+	MXMS		= RMRO + 0x0600,
+
+};
+
+/* AXIBMI */
+#define RR_RATRR		BIT(0)
+#define RR_TATRR		BIT(1)
+#define RR_RST			(RR_RATRR | RR_TATRR)
+#define RR_RST_COMPLETE		0x03
+
+#define AXIWC_DEFAULT		0xffff
+#define AXIRC_DEFAULT		0xffff
+
+#define TATLS0_TEDE		BIT(1)
+#define TATLS0_TATEN_SHIFT	24
+#define TATLS0_TATEN(n)		((n) << TATLS0_TATEN_SHIFT)
+#define TATLR_TATL		BIT(31)
+
+#define RATLS0_RETS		BIT(2)
+#define RATLS0_REDE		BIT(3)
+#define RATLS0_RATEN_SHIFT	24
+#define RATLS0_RATEN(n)		((n) << RATLS0_RATEN_SHIFT)
+#define RATLR_RATL		BIT(31)
+
+#define DIE_DID_TDICX(n)	BIT((n))
+#define DIE_DID_RDICX(n)	BIT((n) + 8)
+#define TDIE_TDID_TDX(n)	BIT(n)
+#define RDIE_RDID_RDX(n)	BIT(n)
+#define TDIS_TDS(n)		BIT(n)
+#define RDIS_RDS(n)		BIT(n)
+
+/* MHD */
+#define OSR_OPS			0x07
+#define SWR_SWR			BIT(0)
+
+#define TGC1_TQTM_SFM		0xff00
+#define TGC1_STTV_DEFAULT	0x03
+
+#define TMS_MFS_MAX		0x2800
+
+/* RMAC System */
+#define CFCR_SDID(n)		((n) << 16)
+#define FMSCR_FMSIE(n)		((n) << 0)
+
+/* RMAC */
+#define MPIC_PIS_MASK		GENMASK(1, 0)
+#define MPIC_PIS_MII		0
+#define MPIC_PIS_RMII		0x01
+#define MPIC_PIS_GMII		0x02
+#define MPIC_PIS_RGMII		0x03
+#define MPIC_LSC_SHIFT		2
+#define MPIC_LSC_MASK		GENMASK(3, MPIC_LSC_SHIFT)
+#define MPIC_LSC_10M		(0 << MPIC_LSC_SHIFT)
+#define MPIC_LSC_100M		(0x01 << MPIC_LSC_SHIFT)
+#define MPIC_LSC_1G		(0x02 << MPIC_LSC_SHIFT)
+#define MPIC_PSMCS_SHIFT	16
+#define MPIC_PSMCS_MASK		GENMASK(21, MPIC_PSMCS_SHIFT)
+#define MPIC_PSMCS_DEFAULT	(0x0a << MPIC_PSMCS_SHIFT)
+#define MPIC_PSMHT_SHIFT	24
+#define MPIC_PSMHT_MASK		GENMASK(26, MPIC_PSMHT_SHIFT)
+#define MPIC_PSMHT_DEFAULT	(0x07 << MPIC_PSMHT_SHIFT)
+
+#define MLVC_PASE		BIT(8)
+#define MLVC_PSE		BIT(16)
+#define MLVC_PLV		BIT(17)
+
+#define MPSM_PSME		BIT(0)
+#define MPSM_PSMAD		BIT(1)
+#define MPSM_PDA_SHIFT		3
+#define MPSM_PDA_MASK		GENMASK(7, 3)
+#define MPSM_PDA(n)		(((n) << MPSM_PDA_SHIFT) & MPSM_PDA_MASK)
+#define MPSM_PRA_SHIFT		8
+#define MPSM_PRA_MASK		GENMASK(12, 8)
+#define MPSM_PRA(n)		(((n) << MPSM_PRA_SHIFT) & MPSM_PRA_MASK)
+#define MPSM_PRD_SHIFT		16
+#define MPSM_PRD_SET(n)		((n) << MPSM_PRD_SHIFT)
+#define MPSM_PRD_GET(n)		((n) >> MPSM_PRD_SHIFT)
+
+#define GPOUT_RDM		BIT(13)
+#define GPOUT_TDM		BIT(14)
+
+/* RTSN */
+#define RTSN_INTERVAL_US	1000
+#define RTSN_TIMEOUT_US		1000000
+
+#define TX_NUM_CHAINS		1
+#define RX_NUM_CHAINS		1
+
+#define TX_CHAIN_SIZE		1024
+#define RX_CHAIN_SIZE		1024
+
+#define TX_CHAIN_IDX		0
+#define RX_CHAIN_IDX		0
+
+#define TX_CHAIN_ADDR_OFFSET	(sizeof(struct rtsn_desc) * TX_CHAIN_IDX)
+#define RX_CHAIN_ADDR_OFFSET	(sizeof(struct rtsn_desc) * RX_CHAIN_IDX)
+
+#define PKT_BUF_SZ		1584
+#define RTSN_ALIGN		128
+
+enum rtsn_mode {
+	OCR_OPC_DISABLE,
+	OCR_OPC_CONFIG,
+	OCR_OPC_OPERATION,
+};
+
+/* Descriptors */
+enum RX_DS_CC_BIT {
+	RX_DS	= 0x0fff, /* Data size */
+	RX_TR	= 0x1000, /* Truncation indication */
+	RX_EI	= 0x2000, /* Error indication */
+	RX_PS	= 0xc000, /* Padding selection */
+};
+
+enum TX_FS_TAGL_BIT {
+	TX_DS	= 0x0fff, /* Data size */
+	TX_TAGL	= 0xf000, /* Frame tag LSBs */
+};
+
+enum DIE_DT {
+	/* HW/SW arbitration */
+	DT_FEMPTY_IS	= 0x10,
+	DT_FEMPTY_IC	= 0x20,
+	DT_FEMPTY_ND	= 0x30,
+	DT_FEMPTY	= 0x40,
+	DT_FEMPTY_START	= 0x50,
+	DT_FEMPTY_MID	= 0x60,
+	DT_FEMPTY_END	= 0x70,
+
+	/* Frame data */
+	DT_FSINGLE	= 0x80,
+	DT_FSTART	= 0x90,
+	DT_FMID		= 0xa0,
+	DT_FEND		= 0xb0,
+
+	/* Chain control */
+	DT_LEMPTY	= 0xc0,
+	DT_EEMPTY	= 0xd0,
+	DT_LINK		= 0xe0,
+	DT_EOS		= 0xf0,
+
+	DT_MASK		= 0xf0,
+	D_DIE		= 0x08,
+};
+
+struct rtsn_desc {
+	__le16 info_ds;
+	__u8 info;
+	u8 die_dt;
+	__le32 dptr;
+} __packed;
+
+struct rtsn_ts_desc {
+	__le16 info_ds;
+	__u8 info;
+	u8 die_dt;
+	__le32 dptr;
+	__le32 ts_nsec;
+	__le32 ts_sec;
+} __packed;
+
+struct rtsn_ext_desc {
+	__le16 info_ds;
+	__u8 info;
+	u8 die_dt;
+	__le32 dptr;
+	__le64 info1;
+} __packed;
+
+struct rtsn_ext_ts_desc {
+	__le16 info_ds;
+	__u8 info;
+	u8 die_dt;
+	__le32 dptr;
+	__le64 info1;
+	__le32 ts_nsec;
+	__le32 ts_sec;
+} __packed;
+
+enum EXT_INFO_DS_BIT {
+	TXC = 0x4000,
+};
+
+#endif