diff mbox series

[3/5] net: ethernet: renesas: Add Ethernet Switch driver

Message ID 20220909132614.1967276-4-yoshihiro.shimoda.uh@renesas.com
State New
Headers show
Series [1/5] clk: renesas: r8a779f0: Add Ethernet Switch clocks | expand

Commit Message

Yoshihiro Shimoda Sept. 9, 2022, 1:26 p.m. UTC
Add Renesas Ethernet Switch driver for R-Car S4-8 to be used as an
ethernet controller.

Signed-off-by: Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com>
---
 drivers/net/ethernet/renesas/Kconfig          |   11 +
 drivers/net/ethernet/renesas/Makefile         |    4 +
 drivers/net/ethernet/renesas/rcar_gen4_ptp.c  |  154 ++
 drivers/net/ethernet/renesas/rcar_gen4_ptp.h  |   71 +
 drivers/net/ethernet/renesas/rswitch.c        | 1674 +++++++++++++++++
 drivers/net/ethernet/renesas/rswitch.h        |  971 ++++++++++
 drivers/net/ethernet/renesas/rswitch_serdes.c |  192 ++
 drivers/net/ethernet/renesas/rswitch_serdes.h |   16 +
 8 files changed, 3093 insertions(+)
 create mode 100644 drivers/net/ethernet/renesas/rcar_gen4_ptp.c
 create mode 100644 drivers/net/ethernet/renesas/rcar_gen4_ptp.h
 create mode 100644 drivers/net/ethernet/renesas/rswitch.c
 create mode 100644 drivers/net/ethernet/renesas/rswitch.h
 create mode 100644 drivers/net/ethernet/renesas/rswitch_serdes.c
 create mode 100644 drivers/net/ethernet/renesas/rswitch_serdes.h

Comments

Yoshihiro Shimoda Sept. 12, 2022, 12:41 p.m. UTC | #1
Hi kernel test robot,

> From: kernel test robot, Sent: Saturday, September 10, 2022 2:26 AM
> 
> Hi Yoshihiro,
> 
> I love your patch! Perhaps something to improve:
> 
> [auto build test WARNING on next-20220909]
> [also build test WARNING on v6.0-rc4]
> [cannot apply to geert-renesas-devel/next net-next/master net/master linus/master v6.0-rc4 v6.0-rc3 v6.0-rc2]
> [If your patch is applied to the wrong git tree, kindly drop us a note.
> And when submitting patch, we suggest to use '--base' as documented in
<snip>
>         git checkout 9c5c4dd0ca6beb269dd0a6ef12c386198e193c68
>         # save the config file
>         mkdir build_dir && cp config build_dir/.config
>         COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-12.1.0 make.cross W=1 O=build_dir ARCH=sh SHELL=/bin/bash
> M=drivers/net/ethernet/renesas
> 
> If you fix the issue, kindly add following tag where applicable
> Reported-by: kernel test robot <lkp@intel.com>
> 
> All warnings (new ones prefixed by >>):
> 
> >> drivers/net/ethernet/renesas/rswitch_serdes.c:16:6: warning: no previous prototype for 'rswitch_serdes_write32'
> [-Wmissing-prototypes]
>       16 | void rswitch_serdes_write32(void __iomem *addr, u32 offs,  u32 bank, u32 data)
>          |      ^~~~~~~~~~~~~~~~~~~~~~
> >> drivers/net/ethernet/renesas/rswitch_serdes.c:22:5: warning: no previous prototype for 'rswitch_serdes_read32'
> [-Wmissing-prototypes]
>       22 | u32 rswitch_serdes_read32(void __iomem *addr, u32 offs,  u32 bank)
>          |     ^~~~~~~~~~~~~~~~~~~~~

I'll fix this issue on v2.
- Add "static" to rswitch_serdes_write32().
- Remove rswitch_serdes_read32() because no one calls it.

Best regards,
Yoshihiro Shimoda
Andrew Lunn Sept. 12, 2022, 10:33 p.m. UTC | #2
On Fri, Sep 09, 2022 at 10:26:12PM +0900, Yoshihiro Shimoda wrote:
> Add Renesas Ethernet Switch driver for R-Car S4-8 to be used as an
> ethernet controller.
> 
> Signed-off-by: Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com>
> ---
>  drivers/net/ethernet/renesas/Kconfig          |   11 +
>  drivers/net/ethernet/renesas/Makefile         |    4 +
>  drivers/net/ethernet/renesas/rcar_gen4_ptp.c  |  154 ++
>  drivers/net/ethernet/renesas/rcar_gen4_ptp.h  |   71 +

Please split PTP into a patch of its own, and Cc: the PTP maintainer.

       Andrew
Andrew Lunn Sept. 12, 2022, 11:14 p.m. UTC | #3
> +static int default_rate = 1000;
> +module_param(default_rate, int, 0644);
> +MODULE_PARM_DESC(default_rate, "Default rate for both ETHA and GWCA");
> +
> +static int num_etha_ports = 1;
> +module_param(num_etha_ports, int, 0644);
> +MODULE_PARM_DESC(num_etha_ports, "Number of using ETHA ports");
> +
> +static int num_ndev = 1;
> +module_param(num_ndev, int, 0644);
> +MODULE_PARM_DESC(num_ndev, "Number of creating network devices");

No module parameters please. Find a different API for this.

+
> +static int rswitch_reg_wait(void __iomem *addr, u32 offs, u32 mask, u32 expected)
> +{
> +	int i;
> +
> +	for (i = 0; i < RSWITCH_TIMEOUT_US; i++) {
> +		if ((ioread32(addr + offs) & mask) == expected)
> +			return 0;
> +
> +		udelay(1);
> +	}
> +
> +	return -ETIMEDOUT;

iopoll.h

> +/* TOP */

Could you expand that acronym, for those of us how have no idea what
it means?

> +static void rswitch_top_init(struct rswitch_private *priv)
> +{
> +	int i;
> +
> +	for (i = 0; i < RSWITCH_MAX_NUM_CHAINS; i++)
> +		iowrite32((i / 16) << (GWCA_INDEX * 8), priv->addr + TPEMIMC7(i));
> +}
> +
> +/* MFWD */

Multicast Forward? 

> +static void rswitch_fwd_init(struct rswitch_private *priv)
> +{
> +	int i;
> +
> +	for (i = 0; i < RSWITCH_NUM_HW; i++) {
> +		iowrite32(FWPC0_DEFAULT, priv->addr + FWPC0(i));
> +		iowrite32(0, priv->addr + FWPBFC(i));
> +	}
> +
> +	for (i = 0; i < num_etha_ports; i++) {
> +		iowrite32(priv->rdev[i]->rx_chain->index,
> +			  priv->addr + FWPBFCSDC(GWCA_INDEX, i));
> +		iowrite32(BIT(priv->gwca.index), priv->addr + FWPBFC(i));
> +	}
> +	iowrite32(GENMASK(num_etha_ports - 1, 0), priv->addr + FWPBFC(3));
> +}
> +
> +/* gPTP */
> +static void rswitch_get_timestamp(struct rswitch_private *priv,
> +				  struct timespec64 *ts)
> +{
> +	struct rcar_gen4_ptp_private *ptp_priv = priv->ptp_priv;
> +
> +	ptp_priv->info.gettime64(&ptp_priv->info, ts);
> +}
> +
> +/* GWCA */
> +static int rswitch_gwca_change_mode(struct rswitch_private *priv,
> +				    enum rswitch_gwca_mode mode)
> +{
> +	int ret;
> +
> +	if (!rswitch_agent_clock_is_enabled(priv->addr, priv->gwca.index))
> +		rswitch_agent_clock_ctrl(priv->addr, priv->gwca.index, 1);
> +
> +	iowrite32(mode, priv->addr + GWMC);
> +
> +	ret = rswitch_reg_wait(priv->addr, GWMS, GWMS_OPS_MASK, mode);
> +
> +	if (mode == GWMC_OPC_DISABLE)
> +		rswitch_agent_clock_ctrl(priv->addr, priv->gwca.index, 0);
> +
> +	return ret;
> +}
> +
> +static int rswitch_gwca_mcast_table_reset(struct rswitch_private *priv)
> +{
> +	iowrite32(GWMTIRM_MTIOG, priv->addr + GWMTIRM);
> +
> +	return rswitch_reg_wait(priv->addr, GWMTIRM, GWMTIRM_MTR, GWMTIRM_MTR);
> +}
> +
> +static int rswitch_gwca_axi_ram_reset(struct rswitch_private *priv)
> +{
> +	iowrite32(GWARIRM_ARIOG, priv->addr + GWARIRM);
> +
> +	return rswitch_reg_wait(priv->addr, GWARIRM, GWARIRM_ARR, GWARIRM_ARR);
> +}
> +
> +static void rswitch_gwca_set_rate_limit(struct rswitch_private *priv, int rate)
> +{
> +	u32 gwgrlulc, gwgrlc;
> +
> +	switch (rate) {
> +	case 1000:
> +		gwgrlulc = 0x0000005f;
> +		gwgrlc = 0x00010260;
> +		break;
> +	default:
> +		dev_err(&priv->pdev->dev, "%s: This rate is not supported (%d)\n", __func__, rate);
> +		break;
> +	}
> +
> +	iowrite32(gwgrlulc, priv->addr + GWGRLULC);
> +	iowrite32(gwgrlc, priv->addr + GWGRLC);

So on error, you write random values to the hardware? At least change
the break to a return.


> +}
> +
> +static bool rswitch_is_any_data_irq(struct rswitch_private *priv, u32 *dis, bool tx)
> +{
> +	int i;
> +	u32 *mask = tx ? priv->gwca.tx_irq_bits : priv->gwca.rx_irq_bits;
> +
> +	for (i = 0; i < RSWITCH_NUM_IRQ_REGS; i++) {
> +		if (dis[i] & mask[i])
> +			return true;
> +	}
> +
> +	return false;
> +}
> +
> +static void rswitch_get_data_irq_status(struct rswitch_private *priv, u32 *dis)
> +{
> +	int i;
> +
> +	for (i = 0; i < RSWITCH_NUM_IRQ_REGS; i++) {
> +		dis[i] = ioread32(priv->addr + GWDIS(i));
> +		dis[i] &= ioread32(priv->addr + GWDIE(i));
> +	}
> +}
> +
> +static void rswitch_enadis_data_irq(struct rswitch_private *priv, int index, bool enable)
> +{
> +	u32 offs = enable ? GWDIE(index / 32) : GWDID(index / 32);
> +
> +	iowrite32(BIT(index % 32), priv->addr + offs);
> +}
> +
> +static void rswitch_ack_data_irq(struct rswitch_private *priv, int index)
> +{
> +	u32 offs = GWDIS(index / 32);
> +
> +	iowrite32(BIT(index % 32), priv->addr + offs);
> +}
> +
> +static bool rswitch_is_chain_rxed(struct rswitch_gwca_chain *c)
> +{
> +	int entry;
> +	struct rswitch_ext_ts_desc *desc;
> +
> +	entry = c->dirty % c->num_ring;
> +	desc = &c->ts_ring[entry];
> +
> +	if ((desc->die_dt & DT_MASK) != DT_FEMPTY)
> +		return true;
> +
> +	return false;
> +}
> +
> +static bool rswitch_rx(struct net_device *ndev, int *quota)
> +{
> +	struct rswitch_device *rdev = netdev_priv(ndev);
> +	struct rswitch_gwca_chain *c = rdev->rx_chain;
> +	int boguscnt = c->dirty + c->num_ring - c->cur;
> +	int entry = c->cur % c->num_ring;
> +	struct rswitch_ext_ts_desc *desc = &c->ts_ring[entry];
> +	int limit;
> +	u16 pkt_len;
> +	struct sk_buff *skb;
> +	dma_addr_t dma_addr;
> +	u32 get_ts;

Reverse Christmas tree. Please go through all the code.

> +
> +	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 = c->skb[entry];
> +		c->skb[entry] = NULL;
> +		dma_addr = le32_to_cpu(desc->dptrl) | ((__le64)le32_to_cpu(desc->dptrh) << 32);
> +		dma_unmap_single(ndev->dev.parent, dma_addr, PKT_BUF_SZ, DMA_FROM_DEVICE);
> +		get_ts = rdev->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 & 0x3FFFFFFF);
> +			shhwtstamps->hwtstamp = timespec64_to_ktime(ts);
> +		}
> +		skb_put(skb, pkt_len);
> +		skb->protocol = eth_type_trans(skb, ndev);
> +		netif_receive_skb(skb);
> +		rdev->ndev->stats.rx_packets++;
> +		rdev->ndev->stats.rx_bytes += pkt_len;
> +
> +		entry = (++c->cur) % c->num_ring;
> +		desc = &c->ts_ring[entry];
> +	}
> +
> +	/* Refill the RX ring buffers */
> +	for (; c->cur - c->dirty > 0; c->dirty++) {
> +		entry = c->dirty % c->num_ring;
> +		desc = &c->ts_ring[entry];
> +		desc->info_ds = cpu_to_le16(PKT_BUF_SZ);
> +
> +		if (!c->skb[entry]) {
> +			skb = dev_alloc_skb(PKT_BUF_SZ + RSWITCH_ALIGN - 1);
> +			if (!skb)
> +				break;	/* Better luck next round */
> +			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->dptrl = cpu_to_le32(lower_32_bits(dma_addr));
> +			desc->dptrh = cpu_to_le32(upper_32_bits(dma_addr));

If there has been a dma mapping error, is dma_addr valid?

> +			skb_checksum_none_assert(skb);
> +			c->skb[entry] = skb;
> +		}
> +		dma_wmb();
> +		desc->die_dt = DT_FEMPTY | DIE;
> +	}
> +
> +	*quota -= limit - (++boguscnt);
> +
> +	return boguscnt <= 0;
> +}
> +
> +static int rswitch_tx_free(struct net_device *ndev, bool free_txed_only)
> +{
> +	struct rswitch_device *rdev = netdev_priv(ndev);
> +	struct rswitch_ext_desc *desc;
> +	int free_num = 0;
> +	int entry, size;
> +	dma_addr_t dma_addr;
> +	struct rswitch_gwca_chain *c = rdev->tx_chain;
> +	struct sk_buff *skb;
> +
> +	for (; c->cur - c->dirty > 0; c->dirty++) {
> +		entry = c->dirty % c->num_ring;
> +		desc = &c->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 = c->skb[entry];
> +		if (skb) {
> +			if (skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP) {
> +				struct skb_shared_hwtstamps shhwtstamps;
> +				struct timespec64 ts;
> +
> +				rswitch_get_timestamp(rdev->priv, &ts);
> +				memset(&shhwtstamps, 0, sizeof(shhwtstamps));
> +				shhwtstamps.hwtstamp = timespec64_to_ktime(ts);
> +				skb_tstamp_tx(skb, &shhwtstamps);
> +			}
> +			dma_addr = le32_to_cpu(desc->dptrl) |
> +				   ((__le64)le32_to_cpu(desc->dptrh) << 32);
> +			dma_unmap_single(ndev->dev.parent, dma_addr,
> +					 size, DMA_TO_DEVICE);
> +			dev_kfree_skb_any(c->skb[entry]);
> +			c->skb[entry] = NULL;
> +			free_num++;
> +		}
> +		desc->die_dt = DT_EEMPTY;
> +		rdev->ndev->stats.tx_packets++;
> +		rdev->ndev->stats.tx_bytes += size;
> +	}
> +
> +	return free_num;
> +}
> +
> +static int rswitch_poll(struct napi_struct *napi, int budget)
> +{
> +	struct net_device *ndev = napi->dev;
> +	struct rswitch_device *rdev = netdev_priv(ndev);
> +	struct rswitch_private *priv = rdev->priv;
> +	int quota = budget;
> +
> +retry:
> +	rswitch_tx_free(ndev, true);
> +
> +	if (rswitch_rx(ndev, &quota))
> +		goto out;
> +	else if (rswitch_is_chain_rxed(rdev->rx_chain))
> +		goto retry;
> +
> +	netif_wake_subqueue(ndev, 0);
> +
> +	napi_complete(napi);
> +
> +	rswitch_enadis_data_irq(priv, rdev->tx_chain->index, true);
> +	rswitch_enadis_data_irq(priv, rdev->rx_chain->index, true);
> +
> +out:
> +	return budget - quota;
> +}
> +
> +static void rswitch_queue_interrupt(struct net_device *ndev)
> +{
> +	struct rswitch_device *rdev = netdev_priv(ndev);
> +
> +	if (napi_schedule_prep(&rdev->napi)) {
> +		rswitch_enadis_data_irq(rdev->priv, rdev->tx_chain->index, false);
> +		rswitch_enadis_data_irq(rdev->priv, rdev->rx_chain->index, false);
> +		__napi_schedule(&rdev->napi);
> +	}
> +}
> +
> +static irqreturn_t rswitch_data_irq(struct rswitch_private *priv, u32 *dis)
> +{
> +	struct rswitch_gwca_chain *c;
> +	int i;
> +	int index, bit;
> +
> +	for (i = 0; i < priv->gwca.num_chains; i++) {
> +		c = &priv->gwca.chains[i];
> +		index = c->index / 32;
> +		bit = BIT(c->index % 32);
> +		if (!(dis[index] & bit))
> +			continue;
> +
> +		rswitch_ack_data_irq(priv, c->index);
> +		rswitch_queue_interrupt(c->ndev);
> +	}
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static irqreturn_t rswitch_gwca_irq(int irq, void *dev_id)
> +{
> +	struct rswitch_private *priv = dev_id;
> +	irqreturn_t ret = IRQ_NONE;
> +	u32 dis[RSWITCH_NUM_IRQ_REGS];
> +
> +	rswitch_get_data_irq_status(priv, dis);
> +
> +	if (rswitch_is_any_data_irq(priv, dis, true) ||
> +	    rswitch_is_any_data_irq(priv, dis, false))
> +		ret = rswitch_data_irq(priv, dis);
> +
> +	return ret;
> +}
> +
> +static int rswitch_gwca_request_irqs(struct rswitch_private *priv)
> +{
> +	int i, ret;
> +	char *resource_name, *irq_name;
> +	struct rswitch_gwca *gwca = &priv->gwca;
> +
> +	for (i = 0; i < GWCA_NUM_IRQS; i++) {
> +		resource_name = kasprintf(GFP_KERNEL, GWCA_IRQ_RESOURCE_NAME, i);
> +		if (!resource_name) {
> +			ret = -ENOMEM;
> +			goto err;
> +		}
> +
> +		gwca->irq[i] = platform_get_irq_byname(priv->pdev, resource_name);
> +		kfree(resource_name);
> +		if (gwca->irq[i] < 0) {
> +			ret = gwca->irq[i];
> +			goto err;
> +		}
> +
> +		irq_name = devm_kasprintf(&priv->pdev->dev, GFP_KERNEL,
> +					  GWCA_IRQ_NAME, i);
> +		if (!irq_name) {
> +			ret = -ENOMEM;
> +			goto err;
> +		}
> +
> +		ret = request_irq(gwca->irq[i], rswitch_gwca_irq, 0, irq_name, priv);

devm_request_irq() ?

> +static int rswitch_etha_set_access(struct rswitch_etha *etha, bool read,
> +				   int phyad, int devad, int regad, int data)
> +{
> +	int pop = read ? MDIO_READ_C45 : MDIO_WRITE_C45;
> +	u32 val;
> +	int ret;
> +
> +	/* No match device */
> +	if (devad == 0xffffffff)
> +		return 0;
> +
> +	writel(MMIS1_CLEAR_FLAGS, etha->addr + MMIS1);
> +
> +	val = MPSM_PSME | MPSM_MFF_C45;
> +	iowrite32((regad << 16) | (devad << 8) | (phyad << 3) | val, etha->addr + MPSM);
> +
> +	ret = rswitch_reg_wait(etha->addr, MMIS1, MMIS1_PAACS, MMIS1_PAACS);
> +	if (ret)
> +		return ret;
> +
> +	rswitch_modify(etha->addr, MMIS1, MMIS1_PAACS, MMIS1_PAACS);
> +
> +	if (read) {
> +		writel((pop << 13) | (devad << 8) | (phyad << 3) | val, etha->addr + MPSM);
> +
> +		ret = rswitch_reg_wait(etha->addr, MMIS1, MMIS1_PRACS, MMIS1_PRACS);
> +		if (ret)
> +			return ret;
> +
> +		ret = (ioread32(etha->addr + MPSM) & MPSM_PRD_MASK) >> 16;
> +
> +		rswitch_modify(etha->addr, MMIS1, MMIS1_PRACS, MMIS1_PRACS);
> +	} else {
> +		iowrite32((data << 16) | (pop << 13) | (devad << 8) | (phyad << 3) | val,
> +			  etha->addr + MPSM);
> +
> +		ret = rswitch_reg_wait(etha->addr, MMIS1, MMIS1_PWACS, MMIS1_PWACS);
> +	}
> +
> +	return ret;
> +}
> +
> +static int rswitch_etha_mii_read(struct mii_bus *bus, int addr, int regnum)
> +{
> +	struct rswitch_etha *etha = bus->priv;
> +	int mode, devad, regad;
> +
> +	mode = regnum & MII_ADDR_C45;
> +	devad = (regnum >> MII_DEVADDR_C45_SHIFT) & 0x1f;
> +	regad = regnum & MII_REGADDR_C45_MASK;
> +
> +	/* Not support Clause 22 access method */
> +	if (!mode)
> +		return 0;

-EOPNOTSUPP.

> +
> +	return rswitch_etha_set_access(etha, true, addr, devad, regad, 0);
> +}
> +
> +static int rswitch_etha_mii_write(struct mii_bus *bus, int addr, int regnum, u16 val)
> +{
> +	struct rswitch_etha *etha = bus->priv;
> +	int mode, devad, regad;
> +
> +	mode = regnum & MII_ADDR_C45;
> +	devad = (regnum >> MII_DEVADDR_C45_SHIFT) & 0x1f;
> +	regad = regnum & MII_REGADDR_C45_MASK;
> +
> +	/* Not support Clause 22 access method */
> +	if (!mode)
> +		return 0;

Same here.

> +
> +	return rswitch_etha_set_access(etha, false, addr, devad, regad, val);
> +}
> +
> +/* Call of_node_put(port) after done */
> +static struct device_node *rswitch_get_port_node(struct rswitch_device *rdev)
> +{
> +	struct device_node *ports, *port;
> +	int err = 0;
> +	u32 index;
> +
> +	ports = of_get_child_by_name(rdev->ndev->dev.parent->of_node, "ports");
> +	if (!ports)
> +		return NULL;
> +
> +	for_each_child_of_node(ports, port) {
> +		err = of_property_read_u32(port, "reg", &index);
> +		if (err < 0) {
> +			port = NULL;
> +			goto out;
> +		}
> +		if (index == rdev->etha->index)
> +			break;
> +	}
> +
> +out:
> +	of_node_put(ports);
> +
> +	return port;
> +}
> +
> +/* Call of_node_put(phy) after done */
> +static struct device_node *rswitch_get_phy_node(struct rswitch_device *rdev)
> +{
> +	struct device_node *port, *phy = NULL;
> +	int err = 0;
> +
> +	port = rswitch_get_port_node(rdev);
> +	if (!port)
> +		return NULL;
> +
> +	err = of_get_phy_mode(port, &rdev->etha->phy_interface);
> +	if (err < 0)
> +		goto out;
> +
> +	phy = of_parse_phandle(port, "phy-handle", 0);
> +
> +out:
> +	of_node_put(port);
> +
> +	return phy;
> +}
> +
> +static int rswitch_mii_register(struct rswitch_device *rdev)
> +{
> +	struct mii_bus *mii_bus;
> +	struct device_node *port;
> +	int err;
> +
> +	mii_bus = mdiobus_alloc();
> +	if (!mii_bus)
> +		return -ENOMEM;
> +
> +	mii_bus->name = "rswitch_mii";
> +	sprintf(mii_bus->id, "etha%d", rdev->etha->index);
> +	mii_bus->priv = rdev->etha;
> +	mii_bus->read = rswitch_etha_mii_read;
> +	mii_bus->write = rswitch_etha_mii_write;
> +	mii_bus->parent = &rdev->ndev->dev;
> +
> +	port = rswitch_get_port_node(rdev);
> +	err = of_mdiobus_register(mii_bus, port);
> +	if (err < 0) {
> +		mdiobus_free(mii_bus);
> +		goto out;
> +	}
> +
> +	rdev->etha->mii = mii_bus;
> +
> +out:
> +	of_node_put(port);
> +
> +	return err;
> +}
> +
> +static void rswitch_mii_unregister(struct rswitch_device *rdev)
> +{
> +	if (rdev->etha && rdev->etha->mii) {
> +		mdiobus_unregister(rdev->etha->mii);
> +		mdiobus_free(rdev->etha->mii);
> +		rdev->etha->mii = NULL;
> +	}
> +}
> +
> +static void rswitch_adjust_link(struct net_device *ndev)
> +{
> +	struct rswitch_device *rdev = netdev_priv(ndev);
> +	struct phy_device *phydev = ndev->phydev;
> +
> +	if (phydev->link != rdev->etha->link) {
> +		phy_print_status(phydev);
> +		rdev->etha->link = phydev->link;
> +	}

Given that the SERDES supports 100 and 1G, it seems odd you don't need
to do anything here.

> +}
> +
> +static int rswitch_phy_init(struct rswitch_device *rdev, struct device_node *phy)
> +{
> +	struct phy_device *phydev;
> +	int err = 0;
> +
> +	phydev = of_phy_connect(rdev->ndev, phy, rswitch_adjust_link, 0,
> +				rdev->etha->phy_interface);
> +	if (!phydev) {
> +		err = -ENOENT;
> +		goto out;
> +	}
> +
> +	phy_attached_info(phydev);
> +
> +out:
> +	return err;
> +}
> +
> +static void rswitch_phy_deinit(struct rswitch_device *rdev)
> +{
> +	if (rdev->ndev->phydev) {
> +		phy_disconnect(rdev->ndev->phydev);
> +		rdev->ndev->phydev = NULL;
> +	}
> +}
> +
> +static int rswitch_open(struct net_device *ndev)
> +{
> +	struct rswitch_device *rdev = netdev_priv(ndev);
> +	struct device_node *phy;
> +	int err = 0;
> +
> +	if (rdev->etha) {
> +		if (!rdev->etha->operated) {
> +			phy = rswitch_get_phy_node(rdev);
> +			if (!phy)
> +				return -EINVAL;
> +			err = rswitch_etha_hw_init(rdev->etha, ndev->dev_addr);
> +			if (err < 0)
> +				goto err_hw_init;
> +			err = rswitch_mii_register(rdev);
> +			if (err < 0)
> +				goto err_mii_register;

Each port has its own MDIO bus? Not one bus shared by all ports? That
is unusual.

> +			err = rswitch_phy_init(rdev, phy);
> +			if (err < 0)
> +				goto err_phy_init;
> +		}
> +
> +		phy_start(ndev->phydev);
> +
> +		if (!rdev->etha->operated) {
> +			err = rswitch_serdes_init(rdev->etha->serdes_addr,
> +						  rdev->etha->serdes_addr0,
> +						  rdev->etha->phy_interface,
> +						  rdev->etha->speed);
> +			if (err < 0)
> +				goto err_serdes_init;
> +			of_node_put(phy);
> +		}
> +
> +		rdev->etha->operated = true;
> +	}
> +
> +	napi_enable(&rdev->napi);
> +	netif_start_queue(ndev);
> +
> +	rswitch_enadis_data_irq(rdev->priv, rdev->tx_chain->index, true);
> +	rswitch_enadis_data_irq(rdev->priv, rdev->rx_chain->index, true);
> +
> +	return err;
> +
> +err_serdes_init:
> +	phy_stop(ndev->phydev);
> +	rswitch_phy_deinit(rdev);
> +
> +err_phy_init:
> +	rswitch_mii_unregister(rdev);
> +
> +err_mii_register:
> +err_hw_init:
> +	of_node_put(phy);
> +
> +	return err;
> +};
> +
> +static int rswitch_stop(struct net_device *ndev)
> +{
> +	struct rswitch_device *rdev = netdev_priv(ndev);
> +
> +	netif_tx_stop_all_queues(ndev);
> +
> +	rswitch_enadis_data_irq(rdev->priv, rdev->tx_chain->index, false);
> +	rswitch_enadis_data_irq(rdev->priv, rdev->rx_chain->index, false);
> +
> +	if (rdev->etha && ndev->phydev)
> +		phy_stop(ndev->phydev);
> +
> +	napi_disable(&rdev->napi);
> +
> +	return 0;
> +};
> +
> +static int rswitch_start_xmit(struct sk_buff *skb, struct net_device *ndev)
> +{
> +	struct rswitch_device *rdev = netdev_priv(ndev);
> +	int ret = NETDEV_TX_OK;
> +	int entry;
> +	dma_addr_t dma_addr;
> +	struct rswitch_ext_desc *desc;
> +	struct rswitch_gwca_chain *c = rdev->tx_chain;
> +
> +	if (c->cur - c->dirty > c->num_ring - 1) {
> +		netif_stop_subqueue(ndev, 0);
> +		return ret;
> +	}
> +
> +	if (skb_put_padto(skb, ETH_ZLEN))
> +		return ret;
> +
> +	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);
> +		return ret;
> +	}
> +
> +	entry = c->cur % c->num_ring;
> +	c->skb[entry] = skb;
> +	desc = &c->ring[entry];
> +	desc->dptrl = cpu_to_le32(lower_32_bits(dma_addr));
> +	desc->dptrh = cpu_to_le32(upper_32_bits(dma_addr));
> +	desc->info_ds = cpu_to_le16(skb->len);
> +
> +	if (skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP) {
> +		skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS;
> +		rdev->ts_tag++;
> +		desc->info1 = (rdev->ts_tag << 8) | BIT(3);
> +	}
> +
> +	skb_tx_timestamp(skb);
> +	dma_wmb();
> +
> +	desc->die_dt = DT_FSINGLE | DIE;
> +	wmb();	/* c->cur must be incremented after die_dt was set */
> +
> +	c->cur++;
> +	rswitch_modify(rdev->addr, GWTRC(c->index), 0, BIT(c->index % 32));
> +
> +	return ret;
> +}
> +
> +static struct net_device_stats *rswitch_get_stats(struct net_device *ndev)
> +{
> +	return &ndev->stats;
> +}
> +
> +static int rswitch_hwstamp_get(struct net_device *ndev, struct ifreq *req)
> +{
> +	struct rswitch_device *rdev = netdev_priv(ndev);
> +	struct rswitch_private *priv = rdev->priv;
> +	struct rcar_gen4_ptp_private *ptp_priv = priv->ptp_priv;
> +	struct hwtstamp_config config;
> +
> +	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;
> +		break;
> +	}
> +
> +	return copy_to_user(req->ifr_data, &config, sizeof(config)) ? -EFAULT : 0;
> +}
> +
> +static int rswitch_hwstamp_set(struct net_device *ndev, struct ifreq *req)
> +{
> +	struct rswitch_device *rdev = netdev_priv(ndev);
> +	struct rswitch_private *priv = rdev->priv;
> +	struct rcar_gen4_ptp_private *ptp_priv = priv->ptp_priv;
> +	struct hwtstamp_config config;
> +	u32 tstamp_rx_ctrl = RCAR_GEN4_RXTSTAMP_ENABLED;
> +	u32 tstamp_tx_ctrl;
> +
> +	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_TYPE_V2_L2_EVENT;
> +		break;
> +	default:
> +		config.rx_filter = HWTSTAMP_FILTER_ALL;
> +		tstamp_rx_ctrl |= RCAR_GEN4_RXTSTAMP_TYPE_ALL;
> +		break;
> +	}
> +
> +	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 rswitch_eth_ioctl(struct net_device *ndev, struct ifreq *req, int cmd)
> +{
> +	if (!netif_running(ndev))
> +		return -EINVAL;
> +
> +	switch (cmd) {
> +	case SIOCGHWTSTAMP:
> +		return rswitch_hwstamp_get(ndev, req);
> +	case SIOCSHWTSTAMP:
> +		return rswitch_hwstamp_set(ndev, req);
> +	default:
> +		break;
> +	}

You should call phy_mii_ioctl() here.

> +static int rswitch_serdes_reg_wait(void __iomem *addr, u32 offs, u32 bank, u32 mask, u32 expected)
> +{
> +	int i;
> +
> +	iowrite32(bank, addr + RSWITCH_SERDES_BANK_SELECT);
> +	mdelay(1);
> +
> +	for (i = 0; i < RSWITCH_TIMEOUT_US; i++) {
> +		if ((ioread32(addr + offs) & mask) == expected)
> +			return 0;
> +		udelay(1);
> +	}

iopoll.h

> +
> +	return -ETIMEDOUT;
> +}
> +

> +static int rswitch_serdes_common_setting(void __iomem *addr0,
> +					 enum rswitch_serdes_mode mode)
> +{
> +	switch (mode) {
> +	case SGMII:
> +		rswitch_serdes_write32(addr0, 0x0244, 0x180, 0x97);
> +		rswitch_serdes_write32(addr0, 0x01d0, 0x180, 0x60);
> +		rswitch_serdes_write32(addr0, 0x01d8, 0x180, 0x2200);
> +		rswitch_serdes_write32(addr0, 0x01d4, 0x180, 0);
> +		rswitch_serdes_write32(addr0, 0x01e0, 0x180, 0x3d);

Please add #defines for all these magic numbers.

       Andrew
Yoshihiro Shimoda Sept. 14, 2022, 4:38 a.m. UTC | #4
Hi Andrew,

Thank you for your review!

> From: Andrew Lunn, Sent: Tuesday, September 13, 2022 8:14 AM
> 
> > +static int default_rate = 1000;
> > +module_param(default_rate, int, 0644);
> > +MODULE_PARM_DESC(default_rate, "Default rate for both ETHA and GWCA");
> > +
> > +static int num_etha_ports = 1;
> > +module_param(num_etha_ports, int, 0644);
> > +MODULE_PARM_DESC(num_etha_ports, "Number of using ETHA ports");
> > +
> > +static int num_ndev = 1;
> > +module_param(num_ndev, int, 0644);
> > +MODULE_PARM_DESC(num_ndev, "Number of creating network devices");
> 
> No module parameters please. Find a different API for this.

I got it. I'll find a different API somehow.

> > +static int rswitch_reg_wait(void __iomem *addr, u32 offs, u32 mask, u32 expected)
> > +{
> > +	int i;
> > +
> > +	for (i = 0; i < RSWITCH_TIMEOUT_US; i++) {
> > +		if ((ioread32(addr + offs) & mask) == expected)
> > +			return 0;
> > +
> > +		udelay(1);
> > +	}
> > +
> > +	return -ETIMEDOUT;
> 
> iopoll.h

I'll use iopoll.h API.

> > +/* TOP */
> 
> Could you expand that acronym, for those of us how have no idea what
> it means?

Sorry for lacking information. This is an abbreviation name of the block.
Complete name of that is "R-Switch-2". So, I'll modify this comment as the following:

/* R-Swtich-2 block (TOP) */

> > +static void rswitch_top_init(struct rswitch_private *priv)
> > +{
> > +	int i;
> > +
> > +	for (i = 0; i < RSWITCH_MAX_NUM_CHAINS; i++)
> > +		iowrite32((i / 16) << (GWCA_INDEX * 8), priv->addr + TPEMIMC7(i));
> > +}
> > +
> > +/* MFWD */
> 
> Multicast Forward?

/* Forwarding engine block (MFWD) */

# The manual doesn't mention why 'M' though....

> > +static void rswitch_fwd_init(struct rswitch_private *priv)
> > +{
> > +	int i;
> > +
> > +	for (i = 0; i < RSWITCH_NUM_HW; i++) {
> > +		iowrite32(FWPC0_DEFAULT, priv->addr + FWPC0(i));
> > +		iowrite32(0, priv->addr + FWPBFC(i));
> > +	}
> > +
> > +	for (i = 0; i < num_etha_ports; i++) {
> > +		iowrite32(priv->rdev[i]->rx_chain->index,
> > +			  priv->addr + FWPBFCSDC(GWCA_INDEX, i));
> > +		iowrite32(BIT(priv->gwca.index), priv->addr + FWPBFC(i));
> > +	}
> > +	iowrite32(GENMASK(num_etha_ports - 1, 0), priv->addr + FWPBFC(3));
> > +}
> > +
> > +/* gPTP */
> > +static void rswitch_get_timestamp(struct rswitch_private *priv,
> > +				  struct timespec64 *ts)
> > +{
> > +	struct rcar_gen4_ptp_private *ptp_priv = priv->ptp_priv;
> > +
> > +	ptp_priv->info.gettime64(&ptp_priv->info, ts);
> > +}
> > +
> > +/* GWCA */
> > +static int rswitch_gwca_change_mode(struct rswitch_private *priv,
> > +				    enum rswitch_gwca_mode mode)
> > +{
> > +	int ret;
> > +
> > +	if (!rswitch_agent_clock_is_enabled(priv->addr, priv->gwca.index))
> > +		rswitch_agent_clock_ctrl(priv->addr, priv->gwca.index, 1);
> > +
> > +	iowrite32(mode, priv->addr + GWMC);
> > +
> > +	ret = rswitch_reg_wait(priv->addr, GWMS, GWMS_OPS_MASK, mode);
> > +
> > +	if (mode == GWMC_OPC_DISABLE)
> > +		rswitch_agent_clock_ctrl(priv->addr, priv->gwca.index, 0);
> > +
> > +	return ret;
> > +}
> > +
> > +static int rswitch_gwca_mcast_table_reset(struct rswitch_private *priv)
> > +{
> > +	iowrite32(GWMTIRM_MTIOG, priv->addr + GWMTIRM);
> > +
> > +	return rswitch_reg_wait(priv->addr, GWMTIRM, GWMTIRM_MTR, GWMTIRM_MTR);
> > +}
> > +
> > +static int rswitch_gwca_axi_ram_reset(struct rswitch_private *priv)
> > +{
> > +	iowrite32(GWARIRM_ARIOG, priv->addr + GWARIRM);
> > +
> > +	return rswitch_reg_wait(priv->addr, GWARIRM, GWARIRM_ARR, GWARIRM_ARR);
> > +}
> > +
> > +static void rswitch_gwca_set_rate_limit(struct rswitch_private *priv, int rate)
> > +{
> > +	u32 gwgrlulc, gwgrlc;
> > +
> > +	switch (rate) {
> > +	case 1000:
> > +		gwgrlulc = 0x0000005f;
> > +		gwgrlc = 0x00010260;
> > +		break;
> > +	default:
> > +		dev_err(&priv->pdev->dev, "%s: This rate is not supported (%d)\n", __func__, rate);
> > +		break;
> > +	}
> > +
> > +	iowrite32(gwgrlulc, priv->addr + GWGRLULC);
> > +	iowrite32(gwgrlc, priv->addr + GWGRLC);
> 
> So on error, you write random values to the hardware? At least change
> the break to a return.

Oops. I should use return instead. I'll fix it.

> > +}
> > +
> > +static bool rswitch_is_any_data_irq(struct rswitch_private *priv, u32 *dis, bool tx)
> > +{
> > +	int i;
> > +	u32 *mask = tx ? priv->gwca.tx_irq_bits : priv->gwca.rx_irq_bits;
> > +
> > +	for (i = 0; i < RSWITCH_NUM_IRQ_REGS; i++) {
> > +		if (dis[i] & mask[i])
> > +			return true;
> > +	}
> > +
> > +	return false;
> > +}
> > +
> > +static void rswitch_get_data_irq_status(struct rswitch_private *priv, u32 *dis)
> > +{
> > +	int i;
> > +
> > +	for (i = 0; i < RSWITCH_NUM_IRQ_REGS; i++) {
> > +		dis[i] = ioread32(priv->addr + GWDIS(i));
> > +		dis[i] &= ioread32(priv->addr + GWDIE(i));
> > +	}
> > +}
> > +
> > +static void rswitch_enadis_data_irq(struct rswitch_private *priv, int index, bool enable)
> > +{
> > +	u32 offs = enable ? GWDIE(index / 32) : GWDID(index / 32);
> > +
> > +	iowrite32(BIT(index % 32), priv->addr + offs);
> > +}
> > +
> > +static void rswitch_ack_data_irq(struct rswitch_private *priv, int index)
> > +{
> > +	u32 offs = GWDIS(index / 32);
> > +
> > +	iowrite32(BIT(index % 32), priv->addr + offs);
> > +}
> > +
> > +static bool rswitch_is_chain_rxed(struct rswitch_gwca_chain *c)
> > +{
> > +	int entry;
> > +	struct rswitch_ext_ts_desc *desc;
> > +
> > +	entry = c->dirty % c->num_ring;
> > +	desc = &c->ts_ring[entry];
> > +
> > +	if ((desc->die_dt & DT_MASK) != DT_FEMPTY)
> > +		return true;
> > +
> > +	return false;
> > +}
> > +
> > +static bool rswitch_rx(struct net_device *ndev, int *quota)
> > +{
> > +	struct rswitch_device *rdev = netdev_priv(ndev);
> > +	struct rswitch_gwca_chain *c = rdev->rx_chain;
> > +	int boguscnt = c->dirty + c->num_ring - c->cur;
> > +	int entry = c->cur % c->num_ring;
> > +	struct rswitch_ext_ts_desc *desc = &c->ts_ring[entry];
> > +	int limit;
> > +	u16 pkt_len;
> > +	struct sk_buff *skb;
> > +	dma_addr_t dma_addr;
> > +	u32 get_ts;
> 
> Reverse Christmas tree. Please go through all the code.

I got it. I'll fix all the code.

> > +
> > +	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 = c->skb[entry];
> > +		c->skb[entry] = NULL;
> > +		dma_addr = le32_to_cpu(desc->dptrl) | ((__le64)le32_to_cpu(desc->dptrh) << 32);
> > +		dma_unmap_single(ndev->dev.parent, dma_addr, PKT_BUF_SZ, DMA_FROM_DEVICE);
> > +		get_ts = rdev->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 & 0x3FFFFFFF);
> > +			shhwtstamps->hwtstamp = timespec64_to_ktime(ts);
> > +		}
> > +		skb_put(skb, pkt_len);
> > +		skb->protocol = eth_type_trans(skb, ndev);
> > +		netif_receive_skb(skb);
> > +		rdev->ndev->stats.rx_packets++;
> > +		rdev->ndev->stats.rx_bytes += pkt_len;
> > +
> > +		entry = (++c->cur) % c->num_ring;
> > +		desc = &c->ts_ring[entry];
> > +	}
> > +
> > +	/* Refill the RX ring buffers */
> > +	for (; c->cur - c->dirty > 0; c->dirty++) {
> > +		entry = c->dirty % c->num_ring;
> > +		desc = &c->ts_ring[entry];
> > +		desc->info_ds = cpu_to_le16(PKT_BUF_SZ);
> > +
> > +		if (!c->skb[entry]) {
> > +			skb = dev_alloc_skb(PKT_BUF_SZ + RSWITCH_ALIGN - 1);
> > +			if (!skb)
> > +				break;	/* Better luck next round */
> > +			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->dptrl = cpu_to_le32(lower_32_bits(dma_addr));
> > +			desc->dptrh = cpu_to_le32(upper_32_bits(dma_addr));
> 
> If there has been a dma mapping error, is dma_addr valid?

No, the dma_addr is not valid. So, I'll fix error handling around this.

> > +			skb_checksum_none_assert(skb);
> > +			c->skb[entry] = skb;
> > +		}
> > +		dma_wmb();
> > +		desc->die_dt = DT_FEMPTY | DIE;
> > +	}
> > +
> > +	*quota -= limit - (++boguscnt);
> > +
> > +	return boguscnt <= 0;
> > +}
> > +
> > +static int rswitch_tx_free(struct net_device *ndev, bool free_txed_only)
> > +{
> > +	struct rswitch_device *rdev = netdev_priv(ndev);
> > +	struct rswitch_ext_desc *desc;
> > +	int free_num = 0;
> > +	int entry, size;
> > +	dma_addr_t dma_addr;
> > +	struct rswitch_gwca_chain *c = rdev->tx_chain;
> > +	struct sk_buff *skb;
> > +
> > +	for (; c->cur - c->dirty > 0; c->dirty++) {
> > +		entry = c->dirty % c->num_ring;
> > +		desc = &c->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 = c->skb[entry];
> > +		if (skb) {
> > +			if (skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP) {
> > +				struct skb_shared_hwtstamps shhwtstamps;
> > +				struct timespec64 ts;
> > +
> > +				rswitch_get_timestamp(rdev->priv, &ts);
> > +				memset(&shhwtstamps, 0, sizeof(shhwtstamps));
> > +				shhwtstamps.hwtstamp = timespec64_to_ktime(ts);
> > +				skb_tstamp_tx(skb, &shhwtstamps);
> > +			}
> > +			dma_addr = le32_to_cpu(desc->dptrl) |
> > +				   ((__le64)le32_to_cpu(desc->dptrh) << 32);
> > +			dma_unmap_single(ndev->dev.parent, dma_addr,
> > +					 size, DMA_TO_DEVICE);
> > +			dev_kfree_skb_any(c->skb[entry]);
> > +			c->skb[entry] = NULL;
> > +			free_num++;
> > +		}
> > +		desc->die_dt = DT_EEMPTY;
> > +		rdev->ndev->stats.tx_packets++;
> > +		rdev->ndev->stats.tx_bytes += size;
> > +	}
> > +
> > +	return free_num;
> > +}
> > +
> > +static int rswitch_poll(struct napi_struct *napi, int budget)
> > +{
> > +	struct net_device *ndev = napi->dev;
> > +	struct rswitch_device *rdev = netdev_priv(ndev);
> > +	struct rswitch_private *priv = rdev->priv;
> > +	int quota = budget;
> > +
> > +retry:
> > +	rswitch_tx_free(ndev, true);
> > +
> > +	if (rswitch_rx(ndev, &quota))
> > +		goto out;
> > +	else if (rswitch_is_chain_rxed(rdev->rx_chain))
> > +		goto retry;
> > +
> > +	netif_wake_subqueue(ndev, 0);
> > +
> > +	napi_complete(napi);
> > +
> > +	rswitch_enadis_data_irq(priv, rdev->tx_chain->index, true);
> > +	rswitch_enadis_data_irq(priv, rdev->rx_chain->index, true);
> > +
> > +out:
> > +	return budget - quota;
> > +}
> > +
> > +static void rswitch_queue_interrupt(struct net_device *ndev)
> > +{
> > +	struct rswitch_device *rdev = netdev_priv(ndev);
> > +
> > +	if (napi_schedule_prep(&rdev->napi)) {
> > +		rswitch_enadis_data_irq(rdev->priv, rdev->tx_chain->index, false);
> > +		rswitch_enadis_data_irq(rdev->priv, rdev->rx_chain->index, false);
> > +		__napi_schedule(&rdev->napi);
> > +	}
> > +}
> > +
> > +static irqreturn_t rswitch_data_irq(struct rswitch_private *priv, u32 *dis)
> > +{
> > +	struct rswitch_gwca_chain *c;
> > +	int i;
> > +	int index, bit;
> > +
> > +	for (i = 0; i < priv->gwca.num_chains; i++) {
> > +		c = &priv->gwca.chains[i];
> > +		index = c->index / 32;
> > +		bit = BIT(c->index % 32);
> > +		if (!(dis[index] & bit))
> > +			continue;
> > +
> > +		rswitch_ack_data_irq(priv, c->index);
> > +		rswitch_queue_interrupt(c->ndev);
> > +	}
> > +
> > +	return IRQ_HANDLED;
> > +}
> > +
> > +static irqreturn_t rswitch_gwca_irq(int irq, void *dev_id)
> > +{
> > +	struct rswitch_private *priv = dev_id;
> > +	irqreturn_t ret = IRQ_NONE;
> > +	u32 dis[RSWITCH_NUM_IRQ_REGS];
> > +
> > +	rswitch_get_data_irq_status(priv, dis);
> > +
> > +	if (rswitch_is_any_data_irq(priv, dis, true) ||
> > +	    rswitch_is_any_data_irq(priv, dis, false))
> > +		ret = rswitch_data_irq(priv, dis);
> > +
> > +	return ret;
> > +}
> > +
> > +static int rswitch_gwca_request_irqs(struct rswitch_private *priv)
> > +{
> > +	int i, ret;
> > +	char *resource_name, *irq_name;
> > +	struct rswitch_gwca *gwca = &priv->gwca;
> > +
> > +	for (i = 0; i < GWCA_NUM_IRQS; i++) {
> > +		resource_name = kasprintf(GFP_KERNEL, GWCA_IRQ_RESOURCE_NAME, i);
> > +		if (!resource_name) {
> > +			ret = -ENOMEM;
> > +			goto err;
> > +		}
> > +
> > +		gwca->irq[i] = platform_get_irq_byname(priv->pdev, resource_name);
> > +		kfree(resource_name);
> > +		if (gwca->irq[i] < 0) {
> > +			ret = gwca->irq[i];
> > +			goto err;
> > +		}
> > +
> > +		irq_name = devm_kasprintf(&priv->pdev->dev, GFP_KERNEL,
> > +					  GWCA_IRQ_NAME, i);
> > +		if (!irq_name) {
> > +			ret = -ENOMEM;
> > +			goto err;
> > +		}
> > +
> > +		ret = request_irq(gwca->irq[i], rswitch_gwca_irq, 0, irq_name, priv);
> 
> devm_request_irq() ?

Yes, I'll fix it.

> > +static int rswitch_etha_set_access(struct rswitch_etha *etha, bool read,
> > +				   int phyad, int devad, int regad, int data)
> > +{
> > +	int pop = read ? MDIO_READ_C45 : MDIO_WRITE_C45;
> > +	u32 val;
> > +	int ret;
> > +
> > +	/* No match device */
> > +	if (devad == 0xffffffff)
> > +		return 0;
> > +
> > +	writel(MMIS1_CLEAR_FLAGS, etha->addr + MMIS1);
> > +
> > +	val = MPSM_PSME | MPSM_MFF_C45;
> > +	iowrite32((regad << 16) | (devad << 8) | (phyad << 3) | val, etha->addr + MPSM);
> > +
> > +	ret = rswitch_reg_wait(etha->addr, MMIS1, MMIS1_PAACS, MMIS1_PAACS);
> > +	if (ret)
> > +		return ret;
> > +
> > +	rswitch_modify(etha->addr, MMIS1, MMIS1_PAACS, MMIS1_PAACS);
> > +
> > +	if (read) {
> > +		writel((pop << 13) | (devad << 8) | (phyad << 3) | val, etha->addr + MPSM);
> > +
> > +		ret = rswitch_reg_wait(etha->addr, MMIS1, MMIS1_PRACS, MMIS1_PRACS);
> > +		if (ret)
> > +			return ret;
> > +
> > +		ret = (ioread32(etha->addr + MPSM) & MPSM_PRD_MASK) >> 16;
> > +
> > +		rswitch_modify(etha->addr, MMIS1, MMIS1_PRACS, MMIS1_PRACS);
> > +	} else {
> > +		iowrite32((data << 16) | (pop << 13) | (devad << 8) | (phyad << 3) | val,
> > +			  etha->addr + MPSM);
> > +
> > +		ret = rswitch_reg_wait(etha->addr, MMIS1, MMIS1_PWACS, MMIS1_PWACS);
> > +	}
> > +
> > +	return ret;
> > +}
> > +
> > +static int rswitch_etha_mii_read(struct mii_bus *bus, int addr, int regnum)
> > +{
> > +	struct rswitch_etha *etha = bus->priv;
> > +	int mode, devad, regad;
> > +
> > +	mode = regnum & MII_ADDR_C45;
> > +	devad = (regnum >> MII_DEVADDR_C45_SHIFT) & 0x1f;
> > +	regad = regnum & MII_REGADDR_C45_MASK;
> > +
> > +	/* Not support Clause 22 access method */
> > +	if (!mode)
> > +		return 0;
> 
> -EOPNOTSUPP.

I'll fix it.

> > +
> > +	return rswitch_etha_set_access(etha, true, addr, devad, regad, 0);
> > +}
> > +
> > +static int rswitch_etha_mii_write(struct mii_bus *bus, int addr, int regnum, u16 val)
> > +{
> > +	struct rswitch_etha *etha = bus->priv;
> > +	int mode, devad, regad;
> > +
> > +	mode = regnum & MII_ADDR_C45;
> > +	devad = (regnum >> MII_DEVADDR_C45_SHIFT) & 0x1f;
> > +	regad = regnum & MII_REGADDR_C45_MASK;
> > +
> > +	/* Not support Clause 22 access method */
> > +	if (!mode)
> > +		return 0;
> 
> Same here.

I'll fix it.

> > +
> > +	return rswitch_etha_set_access(etha, false, addr, devad, regad, val);
> > +}
> > +
> > +/* Call of_node_put(port) after done */
> > +static struct device_node *rswitch_get_port_node(struct rswitch_device *rdev)
> > +{
> > +	struct device_node *ports, *port;
> > +	int err = 0;
> > +	u32 index;
> > +
> > +	ports = of_get_child_by_name(rdev->ndev->dev.parent->of_node, "ports");
> > +	if (!ports)
> > +		return NULL;
> > +
> > +	for_each_child_of_node(ports, port) {
> > +		err = of_property_read_u32(port, "reg", &index);
> > +		if (err < 0) {
> > +			port = NULL;
> > +			goto out;
> > +		}
> > +		if (index == rdev->etha->index)
> > +			break;
> > +	}
> > +
> > +out:
> > +	of_node_put(ports);
> > +
> > +	return port;
> > +}
> > +
> > +/* Call of_node_put(phy) after done */
> > +static struct device_node *rswitch_get_phy_node(struct rswitch_device *rdev)
> > +{
> > +	struct device_node *port, *phy = NULL;
> > +	int err = 0;
> > +
> > +	port = rswitch_get_port_node(rdev);
> > +	if (!port)
> > +		return NULL;
> > +
> > +	err = of_get_phy_mode(port, &rdev->etha->phy_interface);
> > +	if (err < 0)
> > +		goto out;
> > +
> > +	phy = of_parse_phandle(port, "phy-handle", 0);
> > +
> > +out:
> > +	of_node_put(port);
> > +
> > +	return phy;
> > +}
> > +
> > +static int rswitch_mii_register(struct rswitch_device *rdev)
> > +{
> > +	struct mii_bus *mii_bus;
> > +	struct device_node *port;
> > +	int err;
> > +
> > +	mii_bus = mdiobus_alloc();
> > +	if (!mii_bus)
> > +		return -ENOMEM;
> > +
> > +	mii_bus->name = "rswitch_mii";
> > +	sprintf(mii_bus->id, "etha%d", rdev->etha->index);
> > +	mii_bus->priv = rdev->etha;
> > +	mii_bus->read = rswitch_etha_mii_read;
> > +	mii_bus->write = rswitch_etha_mii_write;
> > +	mii_bus->parent = &rdev->ndev->dev;
> > +
> > +	port = rswitch_get_port_node(rdev);
> > +	err = of_mdiobus_register(mii_bus, port);
> > +	if (err < 0) {
> > +		mdiobus_free(mii_bus);
> > +		goto out;
> > +	}
> > +
> > +	rdev->etha->mii = mii_bus;
> > +
> > +out:
> > +	of_node_put(port);
> > +
> > +	return err;
> > +}
> > +
> > +static void rswitch_mii_unregister(struct rswitch_device *rdev)
> > +{
> > +	if (rdev->etha && rdev->etha->mii) {
> > +		mdiobus_unregister(rdev->etha->mii);
> > +		mdiobus_free(rdev->etha->mii);
> > +		rdev->etha->mii = NULL;
> > +	}
> > +}
> > +
> > +static void rswitch_adjust_link(struct net_device *ndev)
> > +{
> > +	struct rswitch_device *rdev = netdev_priv(ndev);
> > +	struct phy_device *phydev = ndev->phydev;
> > +
> > +	if (phydev->link != rdev->etha->link) {
> > +		phy_print_status(phydev);
> > +		rdev->etha->link = phydev->link;
> > +	}
> 
> Given that the SERDES supports 100 and 1G, it seems odd you don't need
> to do anything here.

Indeed. However, unfortunately, the current hardware cannot change the speed at runtime...
So, I'll add such comments here.

> > +}
> > +
> > +static int rswitch_phy_init(struct rswitch_device *rdev, struct device_node *phy)
> > +{
> > +	struct phy_device *phydev;
> > +	int err = 0;
> > +
> > +	phydev = of_phy_connect(rdev->ndev, phy, rswitch_adjust_link, 0,
> > +				rdev->etha->phy_interface);
> > +	if (!phydev) {
> > +		err = -ENOENT;
> > +		goto out;
> > +	}
> > +
> > +	phy_attached_info(phydev);
> > +
> > +out:
> > +	return err;
> > +}
> > +
> > +static void rswitch_phy_deinit(struct rswitch_device *rdev)
> > +{
> > +	if (rdev->ndev->phydev) {
> > +		phy_disconnect(rdev->ndev->phydev);
> > +		rdev->ndev->phydev = NULL;
> > +	}
> > +}
> > +
> > +static int rswitch_open(struct net_device *ndev)
> > +{
> > +	struct rswitch_device *rdev = netdev_priv(ndev);
> > +	struct device_node *phy;
> > +	int err = 0;
> > +
> > +	if (rdev->etha) {
> > +		if (!rdev->etha->operated) {
> > +			phy = rswitch_get_phy_node(rdev);
> > +			if (!phy)
> > +				return -EINVAL;
> > +			err = rswitch_etha_hw_init(rdev->etha, ndev->dev_addr);
> > +			if (err < 0)
> > +				goto err_hw_init;
> > +			err = rswitch_mii_register(rdev);
> > +			if (err < 0)
> > +				goto err_mii_register;
> 
> Each port has its own MDIO bus? Not one bus shared by all ports? That
> is unusual.

Each port has its own MDIO bus.

> > +			err = rswitch_phy_init(rdev, phy);
> > +			if (err < 0)
> > +				goto err_phy_init;
> > +		}
> > +
> > +		phy_start(ndev->phydev);
> > +
> > +		if (!rdev->etha->operated) {
> > +			err = rswitch_serdes_init(rdev->etha->serdes_addr,
> > +						  rdev->etha->serdes_addr0,
> > +						  rdev->etha->phy_interface,
> > +						  rdev->etha->speed);
> > +			if (err < 0)
> > +				goto err_serdes_init;
> > +			of_node_put(phy);
> > +		}
> > +
> > +		rdev->etha->operated = true;
> > +	}
> > +
> > +	napi_enable(&rdev->napi);
> > +	netif_start_queue(ndev);
> > +
> > +	rswitch_enadis_data_irq(rdev->priv, rdev->tx_chain->index, true);
> > +	rswitch_enadis_data_irq(rdev->priv, rdev->rx_chain->index, true);
> > +
> > +	return err;
> > +
> > +err_serdes_init:
> > +	phy_stop(ndev->phydev);
> > +	rswitch_phy_deinit(rdev);
> > +
> > +err_phy_init:
> > +	rswitch_mii_unregister(rdev);
> > +
> > +err_mii_register:
> > +err_hw_init:
> > +	of_node_put(phy);
> > +
> > +	return err;
> > +};
> > +
> > +static int rswitch_stop(struct net_device *ndev)
> > +{
> > +	struct rswitch_device *rdev = netdev_priv(ndev);
> > +
> > +	netif_tx_stop_all_queues(ndev);
> > +
> > +	rswitch_enadis_data_irq(rdev->priv, rdev->tx_chain->index, false);
> > +	rswitch_enadis_data_irq(rdev->priv, rdev->rx_chain->index, false);
> > +
> > +	if (rdev->etha && ndev->phydev)
> > +		phy_stop(ndev->phydev);
> > +
> > +	napi_disable(&rdev->napi);
> > +
> > +	return 0;
> > +};
> > +
> > +static int rswitch_start_xmit(struct sk_buff *skb, struct net_device *ndev)
> > +{
> > +	struct rswitch_device *rdev = netdev_priv(ndev);
> > +	int ret = NETDEV_TX_OK;
> > +	int entry;
> > +	dma_addr_t dma_addr;
> > +	struct rswitch_ext_desc *desc;
> > +	struct rswitch_gwca_chain *c = rdev->tx_chain;
> > +
> > +	if (c->cur - c->dirty > c->num_ring - 1) {
> > +		netif_stop_subqueue(ndev, 0);
> > +		return ret;
> > +	}
> > +
> > +	if (skb_put_padto(skb, ETH_ZLEN))
> > +		return ret;
> > +
> > +	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);
> > +		return ret;
> > +	}
> > +
> > +	entry = c->cur % c->num_ring;
> > +	c->skb[entry] = skb;
> > +	desc = &c->ring[entry];
> > +	desc->dptrl = cpu_to_le32(lower_32_bits(dma_addr));
> > +	desc->dptrh = cpu_to_le32(upper_32_bits(dma_addr));
> > +	desc->info_ds = cpu_to_le16(skb->len);
> > +
> > +	if (skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP) {
> > +		skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS;
> > +		rdev->ts_tag++;
> > +		desc->info1 = (rdev->ts_tag << 8) | BIT(3);
> > +	}
> > +
> > +	skb_tx_timestamp(skb);
> > +	dma_wmb();
> > +
> > +	desc->die_dt = DT_FSINGLE | DIE;
> > +	wmb();	/* c->cur must be incremented after die_dt was set */
> > +
> > +	c->cur++;
> > +	rswitch_modify(rdev->addr, GWTRC(c->index), 0, BIT(c->index % 32));
> > +
> > +	return ret;
> > +}
> > +
> > +static struct net_device_stats *rswitch_get_stats(struct net_device *ndev)
> > +{
> > +	return &ndev->stats;
> > +}
> > +
> > +static int rswitch_hwstamp_get(struct net_device *ndev, struct ifreq *req)
> > +{
> > +	struct rswitch_device *rdev = netdev_priv(ndev);
> > +	struct rswitch_private *priv = rdev->priv;
> > +	struct rcar_gen4_ptp_private *ptp_priv = priv->ptp_priv;
> > +	struct hwtstamp_config config;
> > +
> > +	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;
> > +		break;
> > +	}
> > +
> > +	return copy_to_user(req->ifr_data, &config, sizeof(config)) ? -EFAULT : 0;
> > +}
> > +
> > +static int rswitch_hwstamp_set(struct net_device *ndev, struct ifreq *req)
> > +{
> > +	struct rswitch_device *rdev = netdev_priv(ndev);
> > +	struct rswitch_private *priv = rdev->priv;
> > +	struct rcar_gen4_ptp_private *ptp_priv = priv->ptp_priv;
> > +	struct hwtstamp_config config;
> > +	u32 tstamp_rx_ctrl = RCAR_GEN4_RXTSTAMP_ENABLED;
> > +	u32 tstamp_tx_ctrl;
> > +
> > +	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_TYPE_V2_L2_EVENT;
> > +		break;
> > +	default:
> > +		config.rx_filter = HWTSTAMP_FILTER_ALL;
> > +		tstamp_rx_ctrl |= RCAR_GEN4_RXTSTAMP_TYPE_ALL;
> > +		break;
> > +	}
> > +
> > +	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 rswitch_eth_ioctl(struct net_device *ndev, struct ifreq *req, int cmd)
> > +{
> > +	if (!netif_running(ndev))
> > +		return -EINVAL;
> > +
> > +	switch (cmd) {
> > +	case SIOCGHWTSTAMP:
> > +		return rswitch_hwstamp_get(ndev, req);
> > +	case SIOCSHWTSTAMP:
> > +		return rswitch_hwstamp_set(ndev, req);
> > +	default:
> > +		break;
> > +	}
> 
> You should call phy_mii_ioctl() here.

Thank you. I'll call phy_mii_ioctl() here.

> > +static int rswitch_serdes_reg_wait(void __iomem *addr, u32 offs, u32 bank, u32 mask, u32 expected)
> > +{
> > +	int i;
> > +
> > +	iowrite32(bank, addr + RSWITCH_SERDES_BANK_SELECT);
> > +	mdelay(1);
> > +
> > +	for (i = 0; i < RSWITCH_TIMEOUT_US; i++) {
> > +		if ((ioread32(addr + offs) & mask) == expected)
> > +			return 0;
> > +		udelay(1);
> > +	}
> 
> iopoll.h

I'll use iopoll.h API.

> > +
> > +	return -ETIMEDOUT;
> > +}
> > +
> 
> > +static int rswitch_serdes_common_setting(void __iomem *addr0,
> > +					 enum rswitch_serdes_mode mode)
> > +{
> > +	switch (mode) {
> > +	case SGMII:
> > +		rswitch_serdes_write32(addr0, 0x0244, 0x180, 0x97);
> > +		rswitch_serdes_write32(addr0, 0x01d0, 0x180, 0x60);
> > +		rswitch_serdes_write32(addr0, 0x01d8, 0x180, 0x2200);
> > +		rswitch_serdes_write32(addr0, 0x01d4, 0x180, 0);
> > +		rswitch_serdes_write32(addr0, 0x01e0, 0x180, 0x3d);
> 
> Please add #defines for all these magic numbers.

I should have added comments before though, the datasheet also describes
such magic numbers like below...
Step S.4.1	bank 0x180	address = 0x0244		data = 0x00000097
Step S.4.2	bank 0x180	address = 0x01d0		data = 0x00000060
...

So, perhaps we can define like the followings:
#define	SERDES_BANK_180		0x180

#define	SERDES_STEP_S_4_1_ADDR	0x0244
#define	SERDES_STEP_S_4_1_DATA	0x00000097
...

And,

		rswitch_serdes_write32(addr0, SERDES_STEP_S_4_1_ADDR, SERDES_BANK_180, SERDES_STEP_S_4_1_DATA);
		rswitch_serdes_write32(addr0, SERDES_STEP_S_4_2_ADDR, SERDES_BANK_180, SERDES_STEP_S_4_2_DATA);
...

Is it acceptable?
Or, perhaps, adding the reason (The datasheet describes magic numbers)
is better than adding the #defines.

Best regards,
Yoshihiro Shimoda
Yoshihiro Shimoda Sept. 14, 2022, 4:48 a.m. UTC | #5
Hi Andrew,

> From: Andrew Lunn, Sent: Tuesday, September 13, 2022 7:34 AM
> 
> On Fri, Sep 09, 2022 at 10:26:12PM +0900, Yoshihiro Shimoda wrote:
> > Add Renesas Ethernet Switch driver for R-Car S4-8 to be used as an
> > ethernet controller.
> >
> > Signed-off-by: Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com>
> > ---
> >  drivers/net/ethernet/renesas/Kconfig          |   11 +
> >  drivers/net/ethernet/renesas/Makefile         |    4 +
> >  drivers/net/ethernet/renesas/rcar_gen4_ptp.c  |  154 ++
> >  drivers/net/ethernet/renesas/rcar_gen4_ptp.h  |   71 +
> 
> Please split PTP into a patch of its own, and Cc: the PTP maintainer.

I got it. I'll modify the patches.

Best regards,
Yoshihiro Shimoda
diff mbox series

Patch

diff --git a/drivers/net/ethernet/renesas/Kconfig b/drivers/net/ethernet/renesas/Kconfig
index 8008b2f45934..4ae170056ac8 100644
--- a/drivers/net/ethernet/renesas/Kconfig
+++ b/drivers/net/ethernet/renesas/Kconfig
@@ -42,4 +42,15 @@  config RAVB
 	  This driver supports the following SoCs:
 		- R8A779x.
 
+config RENESAS_ETHER_SWITCH
+	tristate "Renesas Ethernet Switch support"
+	depends on ARCH_RENESAS || COMPILE_TEST
+	select CRC32
+	select MII
+	select PHYLIB
+	help
+	  Renesas Ethernet Switch device driver.
+	  This driver supports the following SoCs:
+		- R8A779Fx.
+
 endif # NET_VENDOR_RENESAS
diff --git a/drivers/net/ethernet/renesas/Makefile b/drivers/net/ethernet/renesas/Makefile
index f21ab8c02af0..ca3d3e02e16c 100644
--- a/drivers/net/ethernet/renesas/Makefile
+++ b/drivers/net/ethernet/renesas/Makefile
@@ -8,3 +8,7 @@  obj-$(CONFIG_SH_ETH) += sh_eth.o
 ravb-objs := ravb_main.o ravb_ptp.o
 
 obj-$(CONFIG_RAVB) += ravb.o
+
+rswitch_drv-objs := rswitch.o rswitch_serdes.o rcar_gen4_ptp.o
+
+obj-$(CONFIG_RENESAS_ETHER_SWITCH) += rswitch_drv.o
diff --git a/drivers/net/ethernet/renesas/rcar_gen4_ptp.c b/drivers/net/ethernet/renesas/rcar_gen4_ptp.c
new file mode 100644
index 000000000000..211e1b58e290
--- /dev/null
+++ b/drivers/net/ethernet/renesas/rcar_gen4_ptp.c
@@ -0,0 +1,154 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/* Renesas R-Car Gen4 gPTP device driver
+ *
+ * Copyright (C) 2022 Renesas Electronics Corporation
+ */
+
+#include <linux/etherdevice.h>
+#include <linux/err.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#include "rcar_gen4_ptp.h"
+#define ptp_to_priv(ptp)	container_of(ptp, struct rcar_gen4_ptp_private, info)
+
+static const struct rcar_gen4_ptp_reg_offset s4_offs = {
+	.enable = PTPTMEC,
+	.disable = PTPTMDC,
+	.increment = PTPTIVC0,
+	.config_t0 = PTPTOVC00,
+	.config_t1 = PTPTOVC10,
+	.config_t2 = PTPTOVC20,
+	.monitor_t0 = PTPGPTPTM00,
+	.monitor_t1 = PTPGPTPTM10,
+	.monitor_t2 = PTPGPTPTM20,
+};
+
+static int rcar_gen4_ptp_adjfine(struct ptp_clock_info *ptp, long scaled_ppm)
+{
+	struct rcar_gen4_ptp_private *ptp_priv = ptp_to_priv(ptp);
+	const struct rcar_gen4_ptp_reg_offset *offs = ptp_priv->offs;
+	s64 addend = ptp_priv->default_addend;
+	s64 diff;
+	bool neg_adj = scaled_ppm < 0 ? true : false;
+
+	if (neg_adj)
+		scaled_ppm = -scaled_ppm;
+	diff = div_s64(addend * scaled_ppm_to_ppb(scaled_ppm), NSEC_PER_SEC);
+	addend = neg_adj ? addend - diff : addend + diff;
+
+	iowrite32(addend, ptp_priv->addr + offs->increment);
+
+	return 0;
+}
+
+static int rcar_gen4_ptp_gettime(struct ptp_clock_info *ptp,
+				 struct timespec64 *ts)
+{
+	struct rcar_gen4_ptp_private *ptp_priv = ptp_to_priv(ptp);
+	const struct rcar_gen4_ptp_reg_offset *offs = ptp_priv->offs;
+
+	ts->tv_nsec = ioread32(ptp_priv->addr + offs->monitor_t0);
+	ts->tv_sec = ioread32(ptp_priv->addr + offs->monitor_t1) |
+		     ((s64)ioread32(ptp_priv->addr + offs->monitor_t2) << 32);
+
+	return 0;
+}
+
+static int rcar_gen4_ptp_settime(struct ptp_clock_info *ptp,
+				 const struct timespec64 *ts)
+{
+	struct rcar_gen4_ptp_private *ptp_priv = ptp_to_priv(ptp);
+	const struct rcar_gen4_ptp_reg_offset *offs = ptp_priv->offs;
+
+	iowrite32(1, ptp_priv->addr + offs->disable);
+	iowrite32(0, ptp_priv->addr + offs->config_t2);
+	iowrite32(0, ptp_priv->addr + offs->config_t1);
+	iowrite32(0, ptp_priv->addr + offs->config_t0);
+	iowrite32(1, ptp_priv->addr + offs->enable);
+	iowrite32(ts->tv_sec >> 32, ptp_priv->addr + offs->config_t2);
+	iowrite32(ts->tv_sec, ptp_priv->addr + offs->config_t1);
+	iowrite32(ts->tv_nsec, ptp_priv->addr + offs->config_t0);
+
+	return 0;
+}
+
+static int rcar_gen4_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta)
+{
+	struct timespec64 ts;
+	s64 now;
+
+	rcar_gen4_ptp_gettime(ptp, &ts);
+	now = ktime_to_ns(timespec64_to_ktime(ts));
+	ts = ns_to_timespec64(now + delta);
+	rcar_gen4_ptp_settime(ptp, &ts);
+
+	return 0;
+}
+
+static int rcar_gen4_ptp_enable(struct ptp_clock_info *ptp,
+				struct ptp_clock_request *rq, int on)
+{
+	return -EOPNOTSUPP;
+}
+
+static struct ptp_clock_info rcar_gen4_ptp_info = {
+	.owner = THIS_MODULE,
+	.name = "rcar_gen4_ptp",
+	.max_adj = 50000000,
+	.adjfine = rcar_gen4_ptp_adjfine,
+	.adjtime = rcar_gen4_ptp_adjtime,
+	.gettime64 = rcar_gen4_ptp_gettime,
+	.settime64 = rcar_gen4_ptp_settime,
+	.enable = rcar_gen4_ptp_enable,
+};
+
+static void rcar_gen4_ptp_set_offs(struct rcar_gen4_ptp_private *ptp_priv,
+				   enum rcar_gen4_ptp_reg_layout layout)
+{
+	WARN_ON(layout != RCAR_GEN4_PTP_REG_LAYOUT_S4);
+
+	ptp_priv->offs = &s4_offs;
+}
+
+int rcar_gen4_ptp_register(struct rcar_gen4_ptp_private *ptp_priv,
+			   enum rcar_gen4_ptp_reg_layout layout, u32 clock)
+{
+	if (ptp_priv->initialized)
+		return 0;
+
+	rcar_gen4_ptp_set_offs(ptp_priv, layout);
+
+	ptp_priv->default_addend = clock;
+	iowrite32(ptp_priv->default_addend, ptp_priv->addr + ptp_priv->offs->increment);
+	ptp_priv->clock = ptp_clock_register(&ptp_priv->info, NULL);
+	if (IS_ERR(ptp_priv->clock))
+		return PTR_ERR(ptp_priv->clock);
+
+	iowrite32(0x01, ptp_priv->addr + ptp_priv->offs->enable);
+	ptp_priv->initialized = true;
+
+	return 0;
+}
+
+int rcar_gen4_ptp_unregister(struct rcar_gen4_ptp_private *ptp_priv)
+{
+	iowrite32(1, ptp_priv->addr + ptp_priv->offs->disable);
+
+	return ptp_clock_unregister(ptp_priv->clock);
+}
+
+struct rcar_gen4_ptp_private *rcar_gen4_ptp_alloc(struct platform_device *pdev)
+{
+	struct rcar_gen4_ptp_private *ptp;
+
+	ptp = devm_kzalloc(&pdev->dev, sizeof(*ptp), GFP_KERNEL);
+	if (!ptp)
+		return NULL;
+
+	ptp->info = rcar_gen4_ptp_info;
+
+	return ptp;
+}
diff --git a/drivers/net/ethernet/renesas/rcar_gen4_ptp.h b/drivers/net/ethernet/renesas/rcar_gen4_ptp.h
new file mode 100644
index 000000000000..aac1403b5ab3
--- /dev/null
+++ b/drivers/net/ethernet/renesas/rcar_gen4_ptp.h
@@ -0,0 +1,71 @@ 
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Renesas R-Car Gen4 gPTP device driver
+ *
+ * Copyright (C) 2022 Renesas Electronics Corporation
+ */
+
+#ifndef __RCAR_GEN4_PTP_H__
+#define __RCAR_GEN4_PTP_H__
+
+#include <linux/ptp_clock_kernel.h>
+
+#define PTPTIVC_INIT			0x19000000	/* 320MHz */
+#define RCAR_GEN4_PTP_CLOCK_S4		PTPTIVC_INIT
+#define RCAR_GEN4_GPTP_OFFSET_S4	0x00018000
+
+/* for rcar_gen4_ptp_init */
+enum rcar_gen4_ptp_reg_layout {
+	RCAR_GEN4_PTP_REG_LAYOUT_S4
+};
+
+/* driver's definitions */
+#define RCAR_GEN4_RXTSTAMP_ENABLED		BIT(0)
+#define RCAR_GEN4_RXTSTAMP_TYPE_V2_L2_EVENT	BIT(1)
+#define RCAR_GEN4_RXTSTAMP_TYPE_ALL		(RCAR_GEN4_RXTSTAMP_TYPE_V2_L2_EVENT | BIT(2))
+#define RCAR_GEN4_RXTSTAMP_TYPE			RCAR_GEN4_RXTSTAMP_TYPE_ALL
+
+#define RCAR_GEN4_TXTSTAMP_ENABLED		BIT(0)
+
+#define PTPRO				0
+
+enum rcar_gen4_ptp_reg_s4 {
+	PTPTMEC		= PTPRO + 0x0010,
+	PTPTMDC		= PTPRO + 0x0014,
+	PTPTIVC0	= PTPRO + 0x0020,
+	PTPTOVC00	= PTPRO + 0x0030,
+	PTPTOVC10	= PTPRO + 0x0034,
+	PTPTOVC20	= PTPRO + 0x0038,
+	PTPGPTPTM00	= PTPRO + 0x0050,
+	PTPGPTPTM10	= PTPRO + 0x0054,
+	PTPGPTPTM20	= PTPRO + 0x0058,
+};
+
+struct rcar_gen4_ptp_reg_offset {
+	u16 enable;
+	u16 disable;
+	u16 increment;
+	u16 config_t0;
+	u16 config_t1;
+	u16 config_t2;
+	u16 monitor_t0;
+	u16 monitor_t1;
+	u16 monitor_t2;
+};
+
+struct rcar_gen4_ptp_private {
+	void __iomem *addr;
+	struct ptp_clock *clock;
+	struct ptp_clock_info info;
+	const struct rcar_gen4_ptp_reg_offset *offs;
+	u32 tstamp_tx_ctrl;
+	u32 tstamp_rx_ctrl;
+	s64 default_addend;
+	bool initialized;
+};
+
+int rcar_gen4_ptp_register(struct rcar_gen4_ptp_private *ptp_priv,
+			   enum rcar_gen4_ptp_reg_layout layout, u32 clock);
+int rcar_gen4_ptp_unregister(struct rcar_gen4_ptp_private *ptp_priv);
+struct rcar_gen4_ptp_private *rcar_gen4_ptp_alloc(struct platform_device *pdev);
+
+#endif	/* #ifndef __RCAR_GEN4_PTP_H__ */
diff --git a/drivers/net/ethernet/renesas/rswitch.c b/drivers/net/ethernet/renesas/rswitch.c
new file mode 100644
index 000000000000..bb89ec3dbae3
--- /dev/null
+++ b/drivers/net/ethernet/renesas/rswitch.c
@@ -0,0 +1,1674 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/* Renesas Ethernet Switch device driver
+ *
+ * Copyright (C) 2022 Renesas Electronics Corporation
+ */
+
+#include <linux/clk.h>
+#include <linux/dma-mapping.h>
+#include <linux/etherdevice.h>
+#include <linux/ethtool.h>
+#include <linux/err.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/net_tstamp.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_irq.h>
+#include <linux/of_mdio.h>
+#include <linux/of_net.h>
+#include <linux/pm_runtime.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+
+#include "rswitch.h"
+#include "rswitch_serdes.h"
+
+static int default_rate = 1000;
+module_param(default_rate, int, 0644);
+MODULE_PARM_DESC(default_rate, "Default rate for both ETHA and GWCA");
+
+static int num_etha_ports = 1;
+module_param(num_etha_ports, int, 0644);
+MODULE_PARM_DESC(num_etha_ports, "Number of using ETHA ports");
+
+static int num_ndev = 1;
+module_param(num_ndev, int, 0644);
+MODULE_PARM_DESC(num_ndev, "Number of creating network devices");
+
+static int rswitch_reg_wait(void __iomem *addr, u32 offs, u32 mask, u32 expected)
+{
+	int i;
+
+	for (i = 0; i < RSWITCH_TIMEOUT_US; i++) {
+		if ((ioread32(addr + offs) & mask) == expected)
+			return 0;
+
+		udelay(1);
+	}
+
+	return -ETIMEDOUT;
+}
+
+static void rswitch_modify(void __iomem *addr, enum rswitch_reg reg, u32 clear, u32 set)
+{
+	iowrite32((ioread32(addr + reg) & ~clear) | set, addr + reg);
+}
+
+/* COMA */
+static void rswitch_reset(struct rswitch_private *priv)
+{
+	iowrite32(RRC_RR, priv->addr + RRC);
+	iowrite32(RRC_RR_CLR, priv->addr + RRC);
+}
+
+static void rswitch_clock_enable(struct rswitch_private *priv)
+{
+	iowrite32(GENMASK(RSWITCH_NUM_HW - 1, 0) | RCEC_RCE, priv->addr + RCEC);
+}
+
+static void rswitch_clock_disable(struct rswitch_private *priv)
+{
+	iowrite32(RCDC_RCD, priv->addr + RCDC);
+}
+
+static bool rswitch_agent_clock_is_enabled(void __iomem *coma_addr, int port)
+{
+	u32 val = ioread32(coma_addr + RCEC);
+
+	if (val & RCEC_RCE)
+		return (val & BIT(port)) ? true : false;
+	else
+		return false;
+}
+
+static void rswitch_agent_clock_ctrl(void __iomem *coma_addr, int port, int enable)
+{
+	u32 val;
+
+	if (enable) {
+		val = ioread32(coma_addr + RCEC);
+		iowrite32(val | RCEC_RCE | BIT(port), coma_addr + RCEC);
+	} else {
+		val = ioread32(coma_addr + RCDC);
+		iowrite32(val | BIT(port), coma_addr + RCDC);
+	}
+}
+
+static int rswitch_bpool_config(struct rswitch_private *priv)
+{
+	u32 val;
+
+	val = ioread32(priv->addr + CABPIRM);
+	if (val & CABPIRM_BPR)
+		return 0;
+
+	iowrite32(CABPIRM_BPIOG, priv->addr + CABPIRM);
+
+	return rswitch_reg_wait(priv->addr, CABPIRM, CABPIRM_BPR, CABPIRM_BPR);
+}
+
+/* TOP */
+static void rswitch_top_init(struct rswitch_private *priv)
+{
+	int i;
+
+	for (i = 0; i < RSWITCH_MAX_NUM_CHAINS; i++)
+		iowrite32((i / 16) << (GWCA_INDEX * 8), priv->addr + TPEMIMC7(i));
+}
+
+/* MFWD */
+static void rswitch_fwd_init(struct rswitch_private *priv)
+{
+	int i;
+
+	for (i = 0; i < RSWITCH_NUM_HW; i++) {
+		iowrite32(FWPC0_DEFAULT, priv->addr + FWPC0(i));
+		iowrite32(0, priv->addr + FWPBFC(i));
+	}
+
+	for (i = 0; i < num_etha_ports; i++) {
+		iowrite32(priv->rdev[i]->rx_chain->index,
+			  priv->addr + FWPBFCSDC(GWCA_INDEX, i));
+		iowrite32(BIT(priv->gwca.index), priv->addr + FWPBFC(i));
+	}
+	iowrite32(GENMASK(num_etha_ports - 1, 0), priv->addr + FWPBFC(3));
+}
+
+/* gPTP */
+static void rswitch_get_timestamp(struct rswitch_private *priv,
+				  struct timespec64 *ts)
+{
+	struct rcar_gen4_ptp_private *ptp_priv = priv->ptp_priv;
+
+	ptp_priv->info.gettime64(&ptp_priv->info, ts);
+}
+
+/* GWCA */
+static int rswitch_gwca_change_mode(struct rswitch_private *priv,
+				    enum rswitch_gwca_mode mode)
+{
+	int ret;
+
+	if (!rswitch_agent_clock_is_enabled(priv->addr, priv->gwca.index))
+		rswitch_agent_clock_ctrl(priv->addr, priv->gwca.index, 1);
+
+	iowrite32(mode, priv->addr + GWMC);
+
+	ret = rswitch_reg_wait(priv->addr, GWMS, GWMS_OPS_MASK, mode);
+
+	if (mode == GWMC_OPC_DISABLE)
+		rswitch_agent_clock_ctrl(priv->addr, priv->gwca.index, 0);
+
+	return ret;
+}
+
+static int rswitch_gwca_mcast_table_reset(struct rswitch_private *priv)
+{
+	iowrite32(GWMTIRM_MTIOG, priv->addr + GWMTIRM);
+
+	return rswitch_reg_wait(priv->addr, GWMTIRM, GWMTIRM_MTR, GWMTIRM_MTR);
+}
+
+static int rswitch_gwca_axi_ram_reset(struct rswitch_private *priv)
+{
+	iowrite32(GWARIRM_ARIOG, priv->addr + GWARIRM);
+
+	return rswitch_reg_wait(priv->addr, GWARIRM, GWARIRM_ARR, GWARIRM_ARR);
+}
+
+static void rswitch_gwca_set_rate_limit(struct rswitch_private *priv, int rate)
+{
+	u32 gwgrlulc, gwgrlc;
+
+	switch (rate) {
+	case 1000:
+		gwgrlulc = 0x0000005f;
+		gwgrlc = 0x00010260;
+		break;
+	default:
+		dev_err(&priv->pdev->dev, "%s: This rate is not supported (%d)\n", __func__, rate);
+		break;
+	}
+
+	iowrite32(gwgrlulc, priv->addr + GWGRLULC);
+	iowrite32(gwgrlc, priv->addr + GWGRLC);
+}
+
+static bool rswitch_is_any_data_irq(struct rswitch_private *priv, u32 *dis, bool tx)
+{
+	int i;
+	u32 *mask = tx ? priv->gwca.tx_irq_bits : priv->gwca.rx_irq_bits;
+
+	for (i = 0; i < RSWITCH_NUM_IRQ_REGS; i++) {
+		if (dis[i] & mask[i])
+			return true;
+	}
+
+	return false;
+}
+
+static void rswitch_get_data_irq_status(struct rswitch_private *priv, u32 *dis)
+{
+	int i;
+
+	for (i = 0; i < RSWITCH_NUM_IRQ_REGS; i++) {
+		dis[i] = ioread32(priv->addr + GWDIS(i));
+		dis[i] &= ioread32(priv->addr + GWDIE(i));
+	}
+}
+
+static void rswitch_enadis_data_irq(struct rswitch_private *priv, int index, bool enable)
+{
+	u32 offs = enable ? GWDIE(index / 32) : GWDID(index / 32);
+
+	iowrite32(BIT(index % 32), priv->addr + offs);
+}
+
+static void rswitch_ack_data_irq(struct rswitch_private *priv, int index)
+{
+	u32 offs = GWDIS(index / 32);
+
+	iowrite32(BIT(index % 32), priv->addr + offs);
+}
+
+static bool rswitch_is_chain_rxed(struct rswitch_gwca_chain *c)
+{
+	int entry;
+	struct rswitch_ext_ts_desc *desc;
+
+	entry = c->dirty % c->num_ring;
+	desc = &c->ts_ring[entry];
+
+	if ((desc->die_dt & DT_MASK) != DT_FEMPTY)
+		return true;
+
+	return false;
+}
+
+static bool rswitch_rx(struct net_device *ndev, int *quota)
+{
+	struct rswitch_device *rdev = netdev_priv(ndev);
+	struct rswitch_gwca_chain *c = rdev->rx_chain;
+	int boguscnt = c->dirty + c->num_ring - c->cur;
+	int entry = c->cur % c->num_ring;
+	struct rswitch_ext_ts_desc *desc = &c->ts_ring[entry];
+	int limit;
+	u16 pkt_len;
+	struct sk_buff *skb;
+	dma_addr_t dma_addr;
+	u32 get_ts;
+
+	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 = c->skb[entry];
+		c->skb[entry] = NULL;
+		dma_addr = le32_to_cpu(desc->dptrl) | ((__le64)le32_to_cpu(desc->dptrh) << 32);
+		dma_unmap_single(ndev->dev.parent, dma_addr, PKT_BUF_SZ, DMA_FROM_DEVICE);
+		get_ts = rdev->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 & 0x3FFFFFFF);
+			shhwtstamps->hwtstamp = timespec64_to_ktime(ts);
+		}
+		skb_put(skb, pkt_len);
+		skb->protocol = eth_type_trans(skb, ndev);
+		netif_receive_skb(skb);
+		rdev->ndev->stats.rx_packets++;
+		rdev->ndev->stats.rx_bytes += pkt_len;
+
+		entry = (++c->cur) % c->num_ring;
+		desc = &c->ts_ring[entry];
+	}
+
+	/* Refill the RX ring buffers */
+	for (; c->cur - c->dirty > 0; c->dirty++) {
+		entry = c->dirty % c->num_ring;
+		desc = &c->ts_ring[entry];
+		desc->info_ds = cpu_to_le16(PKT_BUF_SZ);
+
+		if (!c->skb[entry]) {
+			skb = dev_alloc_skb(PKT_BUF_SZ + RSWITCH_ALIGN - 1);
+			if (!skb)
+				break;	/* Better luck next round */
+			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->dptrl = cpu_to_le32(lower_32_bits(dma_addr));
+			desc->dptrh = cpu_to_le32(upper_32_bits(dma_addr));
+			skb_checksum_none_assert(skb);
+			c->skb[entry] = skb;
+		}
+		dma_wmb();
+		desc->die_dt = DT_FEMPTY | DIE;
+	}
+
+	*quota -= limit - (++boguscnt);
+
+	return boguscnt <= 0;
+}
+
+static int rswitch_tx_free(struct net_device *ndev, bool free_txed_only)
+{
+	struct rswitch_device *rdev = netdev_priv(ndev);
+	struct rswitch_ext_desc *desc;
+	int free_num = 0;
+	int entry, size;
+	dma_addr_t dma_addr;
+	struct rswitch_gwca_chain *c = rdev->tx_chain;
+	struct sk_buff *skb;
+
+	for (; c->cur - c->dirty > 0; c->dirty++) {
+		entry = c->dirty % c->num_ring;
+		desc = &c->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 = c->skb[entry];
+		if (skb) {
+			if (skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP) {
+				struct skb_shared_hwtstamps shhwtstamps;
+				struct timespec64 ts;
+
+				rswitch_get_timestamp(rdev->priv, &ts);
+				memset(&shhwtstamps, 0, sizeof(shhwtstamps));
+				shhwtstamps.hwtstamp = timespec64_to_ktime(ts);
+				skb_tstamp_tx(skb, &shhwtstamps);
+			}
+			dma_addr = le32_to_cpu(desc->dptrl) |
+				   ((__le64)le32_to_cpu(desc->dptrh) << 32);
+			dma_unmap_single(ndev->dev.parent, dma_addr,
+					 size, DMA_TO_DEVICE);
+			dev_kfree_skb_any(c->skb[entry]);
+			c->skb[entry] = NULL;
+			free_num++;
+		}
+		desc->die_dt = DT_EEMPTY;
+		rdev->ndev->stats.tx_packets++;
+		rdev->ndev->stats.tx_bytes += size;
+	}
+
+	return free_num;
+}
+
+static int rswitch_poll(struct napi_struct *napi, int budget)
+{
+	struct net_device *ndev = napi->dev;
+	struct rswitch_device *rdev = netdev_priv(ndev);
+	struct rswitch_private *priv = rdev->priv;
+	int quota = budget;
+
+retry:
+	rswitch_tx_free(ndev, true);
+
+	if (rswitch_rx(ndev, &quota))
+		goto out;
+	else if (rswitch_is_chain_rxed(rdev->rx_chain))
+		goto retry;
+
+	netif_wake_subqueue(ndev, 0);
+
+	napi_complete(napi);
+
+	rswitch_enadis_data_irq(priv, rdev->tx_chain->index, true);
+	rswitch_enadis_data_irq(priv, rdev->rx_chain->index, true);
+
+out:
+	return budget - quota;
+}
+
+static void rswitch_queue_interrupt(struct net_device *ndev)
+{
+	struct rswitch_device *rdev = netdev_priv(ndev);
+
+	if (napi_schedule_prep(&rdev->napi)) {
+		rswitch_enadis_data_irq(rdev->priv, rdev->tx_chain->index, false);
+		rswitch_enadis_data_irq(rdev->priv, rdev->rx_chain->index, false);
+		__napi_schedule(&rdev->napi);
+	}
+}
+
+static irqreturn_t rswitch_data_irq(struct rswitch_private *priv, u32 *dis)
+{
+	struct rswitch_gwca_chain *c;
+	int i;
+	int index, bit;
+
+	for (i = 0; i < priv->gwca.num_chains; i++) {
+		c = &priv->gwca.chains[i];
+		index = c->index / 32;
+		bit = BIT(c->index % 32);
+		if (!(dis[index] & bit))
+			continue;
+
+		rswitch_ack_data_irq(priv, c->index);
+		rswitch_queue_interrupt(c->ndev);
+	}
+
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t rswitch_gwca_irq(int irq, void *dev_id)
+{
+	struct rswitch_private *priv = dev_id;
+	irqreturn_t ret = IRQ_NONE;
+	u32 dis[RSWITCH_NUM_IRQ_REGS];
+
+	rswitch_get_data_irq_status(priv, dis);
+
+	if (rswitch_is_any_data_irq(priv, dis, true) ||
+	    rswitch_is_any_data_irq(priv, dis, false))
+		ret = rswitch_data_irq(priv, dis);
+
+	return ret;
+}
+
+static int rswitch_gwca_request_irqs(struct rswitch_private *priv)
+{
+	int i, ret;
+	char *resource_name, *irq_name;
+	struct rswitch_gwca *gwca = &priv->gwca;
+
+	for (i = 0; i < GWCA_NUM_IRQS; i++) {
+		resource_name = kasprintf(GFP_KERNEL, GWCA_IRQ_RESOURCE_NAME, i);
+		if (!resource_name) {
+			ret = -ENOMEM;
+			goto err;
+		}
+
+		gwca->irq[i] = platform_get_irq_byname(priv->pdev, resource_name);
+		kfree(resource_name);
+		if (gwca->irq[i] < 0) {
+			ret = gwca->irq[i];
+			goto err;
+		}
+
+		irq_name = devm_kasprintf(&priv->pdev->dev, GFP_KERNEL,
+					  GWCA_IRQ_NAME, i);
+		if (!irq_name) {
+			ret = -ENOMEM;
+			goto err;
+		}
+
+		ret = request_irq(gwca->irq[i], rswitch_gwca_irq, 0, irq_name, priv);
+		if (ret < 0)
+			goto err;
+	}
+
+	return 0;
+
+err:
+	for (i--; i >= 0; i--)
+		free_irq(gwca->irq[i], priv);
+
+	return ret;
+}
+
+static int rswitch_gwca_free_irqs(struct rswitch_private *priv)
+{
+	int i;
+	struct rswitch_gwca *gwca = &priv->gwca;
+
+	for (i = 0; i < GWCA_NUM_IRQS; i++)
+		free_irq(gwca->irq[i], priv);
+
+	return 0;
+}
+
+static void rswitch_gwca_chain_free(struct net_device *ndev,
+				    struct rswitch_gwca_chain *c)
+{
+	int i;
+
+	if (c->gptp) {
+		dma_free_coherent(ndev->dev.parent,
+				  sizeof(struct rswitch_ext_ts_desc) *
+				  (c->num_ring + 1), c->ts_ring, c->ring_dma);
+		c->ts_ring = NULL;
+	} else {
+		dma_free_coherent(ndev->dev.parent,
+				  sizeof(struct rswitch_ext_desc) *
+				  (c->num_ring + 1), c->ring, c->ring_dma);
+		c->ring = NULL;
+	}
+
+	if (!c->dir_tx) {
+		for (i = 0; i < c->num_ring; i++)
+			dev_kfree_skb(c->skb[i]);
+	}
+
+	kfree(c->skb);
+	c->skb = NULL;
+}
+
+static int rswitch_gwca_chain_alloc(struct net_device *ndev,
+				    struct rswitch_private *priv,
+				    struct rswitch_gwca_chain *c,
+				    bool dir_tx, bool gptp, int num_ring)
+{
+	int i, bit;
+	struct sk_buff *skb;
+
+	c->dir_tx = dir_tx;
+	c->gptp = gptp;
+	c->num_ring = num_ring;
+	c->ndev = ndev;
+
+	c->skb = kcalloc(c->num_ring, sizeof(*c->skb), GFP_KERNEL);
+	if (!c->skb)
+		return -ENOMEM;
+
+	if (!dir_tx) {
+		for (i = 0; i < c->num_ring; i++) {
+			skb = dev_alloc_skb(PKT_BUF_SZ + RSWITCH_ALIGN - 1);
+			if (!skb)
+				goto out;
+			skb_reserve(skb, NET_IP_ALIGN);
+			c->skb[i] = skb;
+		}
+	}
+
+	if (gptp)
+		c->ts_ring = dma_alloc_coherent(ndev->dev.parent,
+						sizeof(struct rswitch_ext_ts_desc) *
+						(c->num_ring + 1), &c->ring_dma, GFP_KERNEL);
+	else
+		c->ring = dma_alloc_coherent(ndev->dev.parent,
+					     sizeof(struct rswitch_ext_desc) *
+					     (c->num_ring + 1), &c->ring_dma, GFP_KERNEL);
+	if (!c->ts_ring && !c->ring)
+		goto out;
+
+	i = c->index / 32;
+	bit = BIT(c->index % 32);
+	if (dir_tx)
+		priv->gwca.tx_irq_bits[i] |= bit;
+	else
+		priv->gwca.rx_irq_bits[i] |= bit;
+
+	return 0;
+
+out:
+	rswitch_gwca_chain_free(ndev, c);
+
+	return -ENOMEM;
+}
+
+static int rswitch_gwca_chain_format(struct net_device *ndev,
+				     struct rswitch_private *priv,
+				     struct rswitch_gwca_chain *c)
+{
+	struct rswitch_ext_desc *ring;
+	struct rswitch_desc *desc;
+	int tx_ring_size = sizeof(*ring) * c->num_ring;
+	int i;
+	dma_addr_t dma_addr;
+
+	memset(c->ring, 0, tx_ring_size);
+	for (i = 0, ring = c->ring; i < c->num_ring; i++, ring++) {
+		if (!c->dir_tx) {
+			dma_addr = dma_map_single(ndev->dev.parent,
+						  c->skb[i]->data, PKT_BUF_SZ,
+						  DMA_FROM_DEVICE);
+			if (dma_mapping_error(ndev->dev.parent, dma_addr))
+				goto err;
+
+			ring->info_ds = cpu_to_le16(PKT_BUF_SZ);
+			ring->dptrl = cpu_to_le32(lower_32_bits(dma_addr));
+			ring->dptrh = cpu_to_le32(upper_32_bits(dma_addr));
+			ring->die_dt = DT_FEMPTY | DIE;
+		} else {
+			ring->die_dt = DT_EEMPTY | DIE;
+		}
+	}
+	ring->dptrl = cpu_to_le32(lower_32_bits(c->ring_dma));
+	ring->dptrh = cpu_to_le32(upper_32_bits(c->ring_dma));
+	ring->die_dt = DT_LINKFIX;
+
+	desc = &priv->desc_bat[c->index];
+	desc->die_dt = DT_LINKFIX;
+	desc->dptrl = cpu_to_le32(lower_32_bits(c->ring_dma));
+	desc->dptrh = cpu_to_le32(upper_32_bits(c->ring_dma));
+
+	iowrite32(GWDCC_BALR | (c->dir_tx ? GWDCC_DQT : 0) | GWDCC_EDE,
+		  priv->addr + GWDCC_OFFS(c->index));
+
+	return 0;
+
+err:
+	if (!c->dir_tx) {
+		for (i--, ring = c->ring; i >= 0; i--, ring++) {
+			dma_addr = le32_to_cpu(ring->dptrl) |
+				   ((__le64)le32_to_cpu(ring->dptrh) << 32);
+			dma_unmap_single(ndev->dev.parent, dma_addr, PKT_BUF_SZ,
+					 DMA_FROM_DEVICE);
+		}
+	}
+
+	return -ENOMEM;
+}
+
+static int rswitch_gwca_chain_ts_format(struct net_device *ndev,
+					struct rswitch_private *priv,
+					struct rswitch_gwca_chain *c)
+{
+	struct rswitch_ext_ts_desc *ring;
+	struct rswitch_desc *desc;
+	int tx_ts_ring_size = sizeof(*ring) * c->num_ring;
+	int i;
+	dma_addr_t dma_addr;
+
+	memset(c->ts_ring, 0, tx_ts_ring_size);
+	for (i = 0, ring = c->ts_ring; i < c->num_ring; i++, ring++) {
+		if (!c->dir_tx) {
+			dma_addr = dma_map_single(ndev->dev.parent,
+						  c->skb[i]->data, PKT_BUF_SZ,
+						  DMA_FROM_DEVICE);
+			if (!dma_mapping_error(ndev->dev.parent, dma_addr))
+				ring->info_ds = cpu_to_le16(PKT_BUF_SZ);
+			ring->dptrl = cpu_to_le32(lower_32_bits(dma_addr));
+			ring->dptrh = cpu_to_le32(upper_32_bits(dma_addr));
+			ring->die_dt = DT_FEMPTY | DIE;
+		} else {
+			ring->die_dt = DT_EEMPTY | DIE;
+		}
+	}
+	ring->dptrl = cpu_to_le32(lower_32_bits(c->ring_dma));
+	ring->dptrh = cpu_to_le32(upper_32_bits(c->ring_dma));
+	ring->die_dt = DT_LINKFIX;
+
+	desc = &priv->desc_bat[c->index];
+	desc->die_dt = DT_LINKFIX;
+	desc->dptrl = cpu_to_le32(lower_32_bits(c->ring_dma));
+	desc->dptrh = cpu_to_le32(upper_32_bits(c->ring_dma));
+
+	iowrite32(GWDCC_BALR | (c->dir_tx ? GWDCC_DQT : 0) | GWDCC_ETS | GWDCC_EDE,
+		  priv->addr + GWDCC_OFFS(c->index));
+
+	return 0;
+}
+
+static int rswitch_gwca_desc_alloc(struct rswitch_private *priv)
+{
+	struct device *dev = &priv->pdev->dev;
+	int i, num_chains = priv->gwca.num_chains;
+
+	priv->desc_bat_size = sizeof(struct rswitch_desc) * num_chains;
+	priv->desc_bat = dma_alloc_coherent(dev, priv->desc_bat_size,
+					    &priv->desc_bat_dma, GFP_KERNEL);
+	if (!priv->desc_bat)
+		return -ENOMEM;
+	for (i = 0; i < num_chains; i++)
+		priv->desc_bat[i].die_dt = DT_EOS;
+
+	return 0;
+}
+
+static void rswitch_gwca_desc_free(struct rswitch_private *priv)
+{
+	if (priv->desc_bat)
+		dma_free_coherent(&priv->pdev->dev, priv->desc_bat_size,
+				  priv->desc_bat, priv->desc_bat_dma);
+	priv->desc_bat = NULL;
+}
+
+static struct rswitch_gwca_chain *rswitch_gwca_get(struct rswitch_private *priv)
+{
+	int index;
+	struct rswitch_gwca_chain *c;
+
+	index = find_first_zero_bit(priv->gwca.used, priv->gwca.num_chains);
+	if (index >= priv->gwca.num_chains)
+		return NULL;
+	set_bit(index, priv->gwca.used);
+	c = &priv->gwca.chains[index];
+	memset(c, 0, sizeof(*c));
+	c->index = index;
+
+	return c;
+}
+
+static void rswitch_gwca_put(struct rswitch_private *priv,
+			     struct rswitch_gwca_chain *c)
+{
+	clear_bit(c->index, priv->gwca.used);
+}
+
+static int rswitch_txdmac_alloc(struct net_device *ndev)
+{
+	struct rswitch_device *rdev = netdev_priv(ndev);
+	struct rswitch_private *priv = rdev->priv;
+	int err;
+
+	rdev->tx_chain = rswitch_gwca_get(priv);
+	if (!rdev->tx_chain)
+		return -EBUSY;
+
+	err = rswitch_gwca_chain_alloc(ndev, priv, rdev->tx_chain, true, false,
+				       TX_RING_SIZE);
+	if (err < 0) {
+		rswitch_gwca_put(priv, rdev->tx_chain);
+		return err;
+	}
+
+	return 0;
+}
+
+static void rswitch_txdmac_free(struct net_device *ndev)
+{
+	struct rswitch_device *rdev = netdev_priv(ndev);
+
+	rswitch_gwca_chain_free(ndev, rdev->tx_chain);
+	rswitch_gwca_put(rdev->priv, rdev->tx_chain);
+}
+
+static int rswitch_txdmac_init(struct rswitch_private *priv, int index)
+{
+	struct rswitch_device *rdev = priv->rdev[index];
+
+	return rswitch_gwca_chain_format(rdev->ndev, priv, rdev->tx_chain);
+}
+
+static int rswitch_rxdmac_alloc(struct net_device *ndev)
+{
+	struct rswitch_device *rdev = netdev_priv(ndev);
+	struct rswitch_private *priv = rdev->priv;
+	int err;
+
+	rdev->rx_chain = rswitch_gwca_get(priv);
+	if (!rdev->rx_chain)
+		return -EBUSY;
+
+	err = rswitch_gwca_chain_alloc(ndev, priv, rdev->rx_chain, false, true,
+				       RX_RING_SIZE);
+	if (err < 0) {
+		rswitch_gwca_put(priv, rdev->rx_chain);
+		return err;
+	}
+
+	return 0;
+}
+
+static void rswitch_rxdmac_free(struct net_device *ndev)
+{
+	struct rswitch_device *rdev = netdev_priv(ndev);
+
+	rswitch_gwca_chain_free(ndev, rdev->rx_chain);
+	rswitch_gwca_put(rdev->priv, rdev->rx_chain);
+}
+
+static int rswitch_rxdmac_init(struct rswitch_private *priv, int index)
+{
+	struct rswitch_device *rdev = priv->rdev[index];
+	struct net_device *ndev = rdev->ndev;
+
+	return rswitch_gwca_chain_ts_format(ndev, priv, rdev->rx_chain);
+}
+
+static int rswitch_gwca_hw_init(struct rswitch_private *priv)
+{
+	int i, err;
+
+	err = rswitch_gwca_change_mode(priv, GWMC_OPC_DISABLE);
+	if (err < 0)
+		return err;
+	err = rswitch_gwca_change_mode(priv, GWMC_OPC_CONFIG);
+	if (err < 0)
+		return err;
+
+	err = rswitch_gwca_mcast_table_reset(priv);
+	if (err < 0)
+		return err;
+	err = rswitch_gwca_axi_ram_reset(priv);
+	if (err < 0)
+		return err;
+
+	iowrite32(GWVCC_VEM_SC_TAG, priv->addr + GWVCC);
+	iowrite32(0, priv->addr + GWTTFC);
+	iowrite32(lower_32_bits(priv->desc_bat_dma), priv->addr + GWDCBAC1);
+	iowrite32(upper_32_bits(priv->desc_bat_dma), priv->addr + GWDCBAC0);
+	rswitch_gwca_set_rate_limit(priv, default_rate);
+
+	for (i = 0; i < num_ndev; i++) {
+		err = rswitch_rxdmac_init(priv, i);
+		if (err < 0)
+			return err;
+		err = rswitch_txdmac_init(priv, i);
+		if (err < 0)
+			return err;
+	}
+
+	err = rswitch_gwca_change_mode(priv, GWMC_OPC_DISABLE);
+	if (err < 0)
+		return err;
+	return rswitch_gwca_change_mode(priv, GWMC_OPC_OPERATION);
+}
+
+static int rswitch_gwca_hw_deinit(struct rswitch_private *priv)
+{
+	int err;
+
+	err = rswitch_gwca_change_mode(priv, GWMC_OPC_DISABLE);
+	if (err < 0)
+		return err;
+	err = rswitch_gwca_change_mode(priv, GWMC_OPC_RESET);
+	if (err < 0)
+		return err;
+	return rswitch_gwca_change_mode(priv, GWMC_OPC_DISABLE);
+}
+
+/* ETHA and RMAC */
+static int rswitch_etha_change_mode(struct rswitch_etha *etha,
+				    enum rswitch_etha_mode mode)
+{
+	int ret;
+
+	if (!rswitch_agent_clock_is_enabled(etha->coma_addr, etha->index))
+		rswitch_agent_clock_ctrl(etha->coma_addr, etha->index, 1);
+
+	iowrite32(mode, etha->addr + EAMC);
+
+	ret = rswitch_reg_wait(etha->addr, EAMS, EAMS_OPS_MASK, mode);
+
+	if (mode == EAMC_OPC_DISABLE)
+		rswitch_agent_clock_ctrl(etha->coma_addr, etha->index, 0);
+
+	return ret;
+}
+
+static void rswitch_etha_read_mac_address(struct rswitch_etha *etha)
+{
+	u8 *mac = &etha->mac_addr[0];
+	u32 mrmac0 = ioread32(etha->addr + MRMAC0);
+	u32 mrmac1 = ioread32(etha->addr + MRMAC1);
+
+	mac[0] = (mrmac0 >>  8) & 0xFF;
+	mac[1] = (mrmac0 >>  0) & 0xFF;
+	mac[2] = (mrmac1 >> 24) & 0xFF;
+	mac[3] = (mrmac1 >> 16) & 0xFF;
+	mac[4] = (mrmac1 >>  8) & 0xFF;
+	mac[5] = (mrmac1 >>  0) & 0xFF;
+}
+
+static void rswitch_etha_write_mac_address(struct rswitch_etha *etha, const u8 *mac)
+{
+	iowrite32((mac[0] << 8) | mac[1], etha->addr + MRMAC0);
+	iowrite32((mac[2] << 24) | (mac[3] << 16) | (mac[4] << 8) | mac[5],
+		  etha->addr + MRMAC1);
+}
+
+static bool rswitch_etha_wait_link_verification(struct rswitch_etha *etha)
+{
+	iowrite32(MLVC_PLV, etha->addr + MLVC);
+
+	return rswitch_reg_wait(etha->addr, MLVC, MLVC_PLV, 0);
+}
+
+static void rswitch_rmac_setting(struct rswitch_etha *etha, const u8 *mac)
+{
+	u32 val;
+
+	rswitch_etha_write_mac_address(etha, mac);
+
+	switch (etha->speed) {
+	case 100:
+		val = MPIC_LSC_100M;
+		break;
+	case 1000:
+		val = MPIC_LSC_1G;
+		break;
+	case 2500:
+		val = MPIC_LSC_2_5G;
+		break;
+	default:
+		return;
+	}
+
+	iowrite32(MPIC_PIS_GMII | val, etha->addr + MPIC);
+}
+
+static void rswitch_etha_enable_mii(struct rswitch_etha *etha)
+{
+	rswitch_modify(etha->addr, MPIC, MPIC_PSMCS_MASK | MPIC_PSMHT_MASK,
+		       MPIC_PSMCS(0x05) | MPIC_PSMHT(0x06));
+	rswitch_modify(etha->addr, MPSM, 0, MPSM_MFF_C45);
+}
+
+static int rswitch_etha_hw_init(struct rswitch_etha *etha, const u8 *mac)
+{
+	int err;
+
+	err = rswitch_etha_change_mode(etha, EAMC_OPC_DISABLE);
+	if (err < 0)
+		return err;
+	err = rswitch_etha_change_mode(etha, EAMC_OPC_CONFIG);
+	if (err < 0)
+		return err;
+
+	etha->speed = default_rate;
+	rswitch_rmac_setting(etha, mac);
+	rswitch_etha_enable_mii(etha);
+
+	err = rswitch_etha_wait_link_verification(etha);
+	if (err < 0)
+		return err;
+
+	err = rswitch_etha_change_mode(etha, EAMC_OPC_DISABLE);
+	if (err < 0)
+		return err;
+	return rswitch_etha_change_mode(etha, EAMC_OPC_OPERATION);
+}
+
+static int rswitch_etha_set_access(struct rswitch_etha *etha, bool read,
+				   int phyad, int devad, int regad, int data)
+{
+	int pop = read ? MDIO_READ_C45 : MDIO_WRITE_C45;
+	u32 val;
+	int ret;
+
+	/* No match device */
+	if (devad == 0xffffffff)
+		return 0;
+
+	writel(MMIS1_CLEAR_FLAGS, etha->addr + MMIS1);
+
+	val = MPSM_PSME | MPSM_MFF_C45;
+	iowrite32((regad << 16) | (devad << 8) | (phyad << 3) | val, etha->addr + MPSM);
+
+	ret = rswitch_reg_wait(etha->addr, MMIS1, MMIS1_PAACS, MMIS1_PAACS);
+	if (ret)
+		return ret;
+
+	rswitch_modify(etha->addr, MMIS1, MMIS1_PAACS, MMIS1_PAACS);
+
+	if (read) {
+		writel((pop << 13) | (devad << 8) | (phyad << 3) | val, etha->addr + MPSM);
+
+		ret = rswitch_reg_wait(etha->addr, MMIS1, MMIS1_PRACS, MMIS1_PRACS);
+		if (ret)
+			return ret;
+
+		ret = (ioread32(etha->addr + MPSM) & MPSM_PRD_MASK) >> 16;
+
+		rswitch_modify(etha->addr, MMIS1, MMIS1_PRACS, MMIS1_PRACS);
+	} else {
+		iowrite32((data << 16) | (pop << 13) | (devad << 8) | (phyad << 3) | val,
+			  etha->addr + MPSM);
+
+		ret = rswitch_reg_wait(etha->addr, MMIS1, MMIS1_PWACS, MMIS1_PWACS);
+	}
+
+	return ret;
+}
+
+static int rswitch_etha_mii_read(struct mii_bus *bus, int addr, int regnum)
+{
+	struct rswitch_etha *etha = bus->priv;
+	int mode, devad, regad;
+
+	mode = regnum & MII_ADDR_C45;
+	devad = (regnum >> MII_DEVADDR_C45_SHIFT) & 0x1f;
+	regad = regnum & MII_REGADDR_C45_MASK;
+
+	/* Not support Clause 22 access method */
+	if (!mode)
+		return 0;
+
+	return rswitch_etha_set_access(etha, true, addr, devad, regad, 0);
+}
+
+static int rswitch_etha_mii_write(struct mii_bus *bus, int addr, int regnum, u16 val)
+{
+	struct rswitch_etha *etha = bus->priv;
+	int mode, devad, regad;
+
+	mode = regnum & MII_ADDR_C45;
+	devad = (regnum >> MII_DEVADDR_C45_SHIFT) & 0x1f;
+	regad = regnum & MII_REGADDR_C45_MASK;
+
+	/* Not support Clause 22 access method */
+	if (!mode)
+		return 0;
+
+	return rswitch_etha_set_access(etha, false, addr, devad, regad, val);
+}
+
+/* Call of_node_put(port) after done */
+static struct device_node *rswitch_get_port_node(struct rswitch_device *rdev)
+{
+	struct device_node *ports, *port;
+	int err = 0;
+	u32 index;
+
+	ports = of_get_child_by_name(rdev->ndev->dev.parent->of_node, "ports");
+	if (!ports)
+		return NULL;
+
+	for_each_child_of_node(ports, port) {
+		err = of_property_read_u32(port, "reg", &index);
+		if (err < 0) {
+			port = NULL;
+			goto out;
+		}
+		if (index == rdev->etha->index)
+			break;
+	}
+
+out:
+	of_node_put(ports);
+
+	return port;
+}
+
+/* Call of_node_put(phy) after done */
+static struct device_node *rswitch_get_phy_node(struct rswitch_device *rdev)
+{
+	struct device_node *port, *phy = NULL;
+	int err = 0;
+
+	port = rswitch_get_port_node(rdev);
+	if (!port)
+		return NULL;
+
+	err = of_get_phy_mode(port, &rdev->etha->phy_interface);
+	if (err < 0)
+		goto out;
+
+	phy = of_parse_phandle(port, "phy-handle", 0);
+
+out:
+	of_node_put(port);
+
+	return phy;
+}
+
+static int rswitch_mii_register(struct rswitch_device *rdev)
+{
+	struct mii_bus *mii_bus;
+	struct device_node *port;
+	int err;
+
+	mii_bus = mdiobus_alloc();
+	if (!mii_bus)
+		return -ENOMEM;
+
+	mii_bus->name = "rswitch_mii";
+	sprintf(mii_bus->id, "etha%d", rdev->etha->index);
+	mii_bus->priv = rdev->etha;
+	mii_bus->read = rswitch_etha_mii_read;
+	mii_bus->write = rswitch_etha_mii_write;
+	mii_bus->parent = &rdev->ndev->dev;
+
+	port = rswitch_get_port_node(rdev);
+	err = of_mdiobus_register(mii_bus, port);
+	if (err < 0) {
+		mdiobus_free(mii_bus);
+		goto out;
+	}
+
+	rdev->etha->mii = mii_bus;
+
+out:
+	of_node_put(port);
+
+	return err;
+}
+
+static void rswitch_mii_unregister(struct rswitch_device *rdev)
+{
+	if (rdev->etha && rdev->etha->mii) {
+		mdiobus_unregister(rdev->etha->mii);
+		mdiobus_free(rdev->etha->mii);
+		rdev->etha->mii = NULL;
+	}
+}
+
+static void rswitch_adjust_link(struct net_device *ndev)
+{
+	struct rswitch_device *rdev = netdev_priv(ndev);
+	struct phy_device *phydev = ndev->phydev;
+
+	if (phydev->link != rdev->etha->link) {
+		phy_print_status(phydev);
+		rdev->etha->link = phydev->link;
+	}
+}
+
+static int rswitch_phy_init(struct rswitch_device *rdev, struct device_node *phy)
+{
+	struct phy_device *phydev;
+	int err = 0;
+
+	phydev = of_phy_connect(rdev->ndev, phy, rswitch_adjust_link, 0,
+				rdev->etha->phy_interface);
+	if (!phydev) {
+		err = -ENOENT;
+		goto out;
+	}
+
+	phy_attached_info(phydev);
+
+out:
+	return err;
+}
+
+static void rswitch_phy_deinit(struct rswitch_device *rdev)
+{
+	if (rdev->ndev->phydev) {
+		phy_disconnect(rdev->ndev->phydev);
+		rdev->ndev->phydev = NULL;
+	}
+}
+
+static int rswitch_open(struct net_device *ndev)
+{
+	struct rswitch_device *rdev = netdev_priv(ndev);
+	struct device_node *phy;
+	int err = 0;
+
+	if (rdev->etha) {
+		if (!rdev->etha->operated) {
+			phy = rswitch_get_phy_node(rdev);
+			if (!phy)
+				return -EINVAL;
+			err = rswitch_etha_hw_init(rdev->etha, ndev->dev_addr);
+			if (err < 0)
+				goto err_hw_init;
+			err = rswitch_mii_register(rdev);
+			if (err < 0)
+				goto err_mii_register;
+			err = rswitch_phy_init(rdev, phy);
+			if (err < 0)
+				goto err_phy_init;
+		}
+
+		phy_start(ndev->phydev);
+
+		if (!rdev->etha->operated) {
+			err = rswitch_serdes_init(rdev->etha->serdes_addr,
+						  rdev->etha->serdes_addr0,
+						  rdev->etha->phy_interface,
+						  rdev->etha->speed);
+			if (err < 0)
+				goto err_serdes_init;
+			of_node_put(phy);
+		}
+
+		rdev->etha->operated = true;
+	}
+
+	napi_enable(&rdev->napi);
+	netif_start_queue(ndev);
+
+	rswitch_enadis_data_irq(rdev->priv, rdev->tx_chain->index, true);
+	rswitch_enadis_data_irq(rdev->priv, rdev->rx_chain->index, true);
+
+	return err;
+
+err_serdes_init:
+	phy_stop(ndev->phydev);
+	rswitch_phy_deinit(rdev);
+
+err_phy_init:
+	rswitch_mii_unregister(rdev);
+
+err_mii_register:
+err_hw_init:
+	of_node_put(phy);
+
+	return err;
+};
+
+static int rswitch_stop(struct net_device *ndev)
+{
+	struct rswitch_device *rdev = netdev_priv(ndev);
+
+	netif_tx_stop_all_queues(ndev);
+
+	rswitch_enadis_data_irq(rdev->priv, rdev->tx_chain->index, false);
+	rswitch_enadis_data_irq(rdev->priv, rdev->rx_chain->index, false);
+
+	if (rdev->etha && ndev->phydev)
+		phy_stop(ndev->phydev);
+
+	napi_disable(&rdev->napi);
+
+	return 0;
+};
+
+static int rswitch_start_xmit(struct sk_buff *skb, struct net_device *ndev)
+{
+	struct rswitch_device *rdev = netdev_priv(ndev);
+	int ret = NETDEV_TX_OK;
+	int entry;
+	dma_addr_t dma_addr;
+	struct rswitch_ext_desc *desc;
+	struct rswitch_gwca_chain *c = rdev->tx_chain;
+
+	if (c->cur - c->dirty > c->num_ring - 1) {
+		netif_stop_subqueue(ndev, 0);
+		return ret;
+	}
+
+	if (skb_put_padto(skb, ETH_ZLEN))
+		return ret;
+
+	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);
+		return ret;
+	}
+
+	entry = c->cur % c->num_ring;
+	c->skb[entry] = skb;
+	desc = &c->ring[entry];
+	desc->dptrl = cpu_to_le32(lower_32_bits(dma_addr));
+	desc->dptrh = cpu_to_le32(upper_32_bits(dma_addr));
+	desc->info_ds = cpu_to_le16(skb->len);
+
+	if (skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP) {
+		skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS;
+		rdev->ts_tag++;
+		desc->info1 = (rdev->ts_tag << 8) | BIT(3);
+	}
+
+	skb_tx_timestamp(skb);
+	dma_wmb();
+
+	desc->die_dt = DT_FSINGLE | DIE;
+	wmb();	/* c->cur must be incremented after die_dt was set */
+
+	c->cur++;
+	rswitch_modify(rdev->addr, GWTRC(c->index), 0, BIT(c->index % 32));
+
+	return ret;
+}
+
+static struct net_device_stats *rswitch_get_stats(struct net_device *ndev)
+{
+	return &ndev->stats;
+}
+
+static int rswitch_hwstamp_get(struct net_device *ndev, struct ifreq *req)
+{
+	struct rswitch_device *rdev = netdev_priv(ndev);
+	struct rswitch_private *priv = rdev->priv;
+	struct rcar_gen4_ptp_private *ptp_priv = priv->ptp_priv;
+	struct hwtstamp_config config;
+
+	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;
+		break;
+	}
+
+	return copy_to_user(req->ifr_data, &config, sizeof(config)) ? -EFAULT : 0;
+}
+
+static int rswitch_hwstamp_set(struct net_device *ndev, struct ifreq *req)
+{
+	struct rswitch_device *rdev = netdev_priv(ndev);
+	struct rswitch_private *priv = rdev->priv;
+	struct rcar_gen4_ptp_private *ptp_priv = priv->ptp_priv;
+	struct hwtstamp_config config;
+	u32 tstamp_rx_ctrl = RCAR_GEN4_RXTSTAMP_ENABLED;
+	u32 tstamp_tx_ctrl;
+
+	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_TYPE_V2_L2_EVENT;
+		break;
+	default:
+		config.rx_filter = HWTSTAMP_FILTER_ALL;
+		tstamp_rx_ctrl |= RCAR_GEN4_RXTSTAMP_TYPE_ALL;
+		break;
+	}
+
+	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 rswitch_eth_ioctl(struct net_device *ndev, struct ifreq *req, int cmd)
+{
+	if (!netif_running(ndev))
+		return -EINVAL;
+
+	switch (cmd) {
+	case SIOCGHWTSTAMP:
+		return rswitch_hwstamp_get(ndev, req);
+	case SIOCSHWTSTAMP:
+		return rswitch_hwstamp_set(ndev, req);
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static const struct net_device_ops rswitch_netdev_ops = {
+	.ndo_open = rswitch_open,
+	.ndo_stop = rswitch_stop,
+	.ndo_start_xmit = rswitch_start_xmit,
+	.ndo_get_stats = rswitch_get_stats,
+	.ndo_eth_ioctl = rswitch_eth_ioctl,
+	.ndo_validate_addr = eth_validate_addr,
+	.ndo_set_mac_address = eth_mac_addr,
+};
+
+static int rswitch_get_ts_info(struct net_device *ndev, struct ethtool_ts_info *info)
+{
+	struct rswitch_device *rdev = netdev_priv(ndev);
+
+	info->phc_index = ptp_clock_index(rdev->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 rswitch_ethtool_ops = {
+	.get_ts_info = rswitch_get_ts_info,
+};
+
+static const struct of_device_id renesas_eth_sw_of_table[] = {
+	{ .compatible = "renesas,r8a779f0-ether-switch", },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, renesas_eth_sw_of_table);
+
+static void rswitch_etha_init(struct rswitch_private *priv, int index)
+{
+	struct rswitch_etha *etha = &priv->etha[index];
+
+	memset(etha, 0, sizeof(*etha));
+	etha->index = index;
+	etha->addr = priv->addr + RSWITCH_ETHA_OFFSET + index * RSWITCH_ETHA_SIZE;
+	etha->coma_addr = priv->addr;
+	etha->serdes_addr = priv->serdes_addr + index * RSWITCH_SERDES_OFFSET;
+	etha->serdes_addr0 = priv->serdes_addr;
+}
+
+static int rswitch_device_alloc(struct rswitch_private *priv, int index)
+{
+	struct platform_device *pdev = priv->pdev;
+	struct net_device *ndev;
+	struct rswitch_device *rdev;
+	int err;
+
+	ndev = alloc_etherdev_mqs(sizeof(struct rswitch_device), 1, 1);
+	if (!ndev)
+		return -ENOMEM;
+
+	SET_NETDEV_DEV(ndev, &pdev->dev);
+	ether_setup(ndev);
+
+	rdev = netdev_priv(ndev);
+	rdev->ndev = ndev;
+	rdev->priv = priv;
+	priv->rdev[index] = rdev;
+	/* TODO: netdev instance : ETHA port is 1:1 mapping */
+	if (index < RSWITCH_MAX_NUM_ETHA) {
+		rdev->port = index;
+		rdev->etha = &priv->etha[index];
+	} else {
+		rdev->port = -1;
+		rdev->etha = NULL;
+	}
+	rdev->addr = priv->addr;
+
+	ndev->base_addr = (unsigned long)rdev->addr;
+	snprintf(ndev->name, IFNAMSIZ, "tsn%d", index);
+	ndev->netdev_ops = &rswitch_netdev_ops;
+	ndev->ethtool_ops = &rswitch_ethtool_ops;
+
+	netif_napi_add(ndev, &rdev->napi, rswitch_poll, 64);
+
+	err = of_get_ethdev_address(pdev->dev.of_node, ndev);
+	if (err) {
+		if (rdev->etha && is_valid_ether_addr(rdev->etha->mac_addr))
+			eth_hw_addr_set(ndev, rdev->etha->mac_addr);
+		else
+			eth_hw_addr_random(ndev);
+	}
+
+	err = rswitch_rxdmac_alloc(ndev);
+	if (err < 0)
+		goto out_rxdmac;
+
+	err = rswitch_txdmac_alloc(ndev);
+	if (err < 0)
+		goto out_txdmac;
+
+	return 0;
+
+out_txdmac:
+	rswitch_rxdmac_free(ndev);
+
+out_rxdmac:
+	netif_napi_del(&rdev->napi);
+	free_netdev(ndev);
+
+	return err;
+}
+
+static void rswitch_device_free(struct rswitch_private *priv, int index)
+{
+	struct rswitch_device *rdev = priv->rdev[index];
+	struct net_device *ndev = rdev->ndev;
+
+	rswitch_txdmac_free(ndev);
+	rswitch_rxdmac_free(ndev);
+	netif_napi_del(&rdev->napi);
+	free_netdev(ndev);
+}
+
+static int rswitch_init(struct rswitch_private *priv)
+{
+	int i;
+	int err;
+
+	for (i = 0; i < num_etha_ports; i++)
+		rswitch_etha_init(priv, i);
+
+	rswitch_clock_enable(priv);
+	for (i = 0; i < num_etha_ports; i++)
+		rswitch_etha_read_mac_address(&priv->etha[i]);
+
+	rswitch_reset(priv);
+
+	rswitch_clock_enable(priv);
+	rswitch_top_init(priv);
+	err = rswitch_bpool_config(priv);
+	if (err < 0)
+		return err;
+
+	err = rswitch_gwca_desc_alloc(priv);
+	if (err < 0)
+		return -ENOMEM;
+
+	for (i = 0; i < num_ndev; i++) {
+		err = rswitch_device_alloc(priv, i);
+		if (err < 0) {
+			for (i--; i >= 0; i--)
+				rswitch_device_free(priv, i);
+			goto err_device_alloc;
+		}
+	}
+
+	rswitch_fwd_init(priv);
+
+	err = rswitch_gwca_request_irqs(priv);
+	if (err < 0)
+		goto err_gwca_request_irq;
+
+	err = rswitch_gwca_hw_init(priv);
+	if (err < 0)
+		goto err_gwca_hw_init;
+
+	for (i = 0; i < num_ndev; i++) {
+		err = register_netdev(priv->rdev[i]->ndev);
+		if (err) {
+			for (i--; i >= 0; i--)
+				unregister_netdev(priv->rdev[i]->ndev);
+			goto err_register_netdev;
+		}
+
+		netdev_info(priv->rdev[i]->ndev, "MAC address %pMn",
+			    priv->rdev[i]->ndev->dev_addr);
+	}
+
+	err = rcar_gen4_ptp_register(priv->ptp_priv, RCAR_GEN4_PTP_REG_LAYOUT_S4,
+				     RCAR_GEN4_PTP_CLOCK_S4);
+	if (err < 0)
+		goto err_ptp_register;
+
+	return 0;
+
+err_ptp_register:
+	for (i = 0; i < num_ndev; i++)
+		unregister_netdev(priv->rdev[i]->ndev);
+
+err_register_netdev:
+	rswitch_gwca_hw_deinit(priv);
+
+err_gwca_hw_init:
+err_gwca_request_irq:
+	for (i = 0; i < num_ndev; i++)
+		rswitch_device_free(priv, i);
+
+err_device_alloc:
+	rswitch_gwca_desc_free(priv);
+
+	return err;
+}
+
+static int renesas_eth_sw_probe(struct platform_device *pdev)
+{
+	struct rswitch_private *priv;
+	struct resource *res, *res_serdes;
+	int ret;
+
+	if (num_ndev > RSWITCH_MAX_NUM_NDEV)
+		return -EINVAL;
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "secure_base");
+	res_serdes = platform_get_resource_byname(pdev, IORESOURCE_MEM, "serdes");
+	if (!res || !res_serdes) {
+		dev_err(&pdev->dev, "invalid resource\n");
+		return -EINVAL;
+	}
+
+	priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	priv->ptp_priv = rcar_gen4_ptp_alloc(pdev);
+	if (!priv->ptp_priv)
+		return -ENOMEM;
+
+	priv->tsn_clk = devm_clk_get(&pdev->dev, "tsn");
+	if (IS_ERR(priv->tsn_clk)) {
+		dev_err(&pdev->dev, "Failed to get tsn clock: %ld\n", PTR_ERR(priv->tsn_clk));
+		return -PTR_ERR(priv->tsn_clk);
+	}
+
+	platform_set_drvdata(pdev, priv);
+	priv->pdev = pdev;
+	priv->addr = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(priv->addr))
+		return PTR_ERR(priv->addr);
+
+	priv->ptp_priv->addr = priv->addr + RCAR_GEN4_GPTP_OFFSET_S4;
+	priv->serdes_addr = devm_ioremap_resource(&pdev->dev, res_serdes);
+	if (IS_ERR(priv->serdes_addr))
+		return PTR_ERR(priv->serdes_addr);
+
+	ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(40));
+	if (ret < 0) {
+		ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32));
+		if (ret < 0)
+			return ret;
+	}
+
+	priv->gwca.index = AGENT_INDEX_GWCA;
+	priv->gwca.num_chains = min(num_ndev * NUM_CHAINS_PER_NDEV,
+				    RSWITCH_MAX_NUM_CHAINS);
+	priv->gwca.chains = devm_kcalloc(&pdev->dev, priv->gwca.num_chains,
+					 sizeof(*priv->gwca.chains), GFP_KERNEL);
+	if (!priv->gwca.chains)
+		return -ENOMEM;
+
+	pm_runtime_enable(&pdev->dev);
+	pm_runtime_get_sync(&pdev->dev);
+	clk_prepare(priv->tsn_clk);
+	clk_enable(priv->tsn_clk);
+
+	ret = rswitch_init(priv);
+
+	device_set_wakeup_capable(&pdev->dev, 1);
+
+	return ret;
+}
+
+static void rswitch_deinit(struct rswitch_private *priv)
+{
+	int i;
+
+	rswitch_gwca_hw_deinit(priv);
+	rcar_gen4_ptp_unregister(priv->ptp_priv);
+
+	for (i = 0; i < num_ndev; i++) {
+		struct rswitch_device *rdev = priv->rdev[i];
+
+		rswitch_phy_deinit(rdev);
+		rswitch_mii_unregister(rdev);
+		unregister_netdev(rdev->ndev);
+		rswitch_device_free(priv, i);
+	}
+
+	rswitch_gwca_free_irqs(priv);
+	rswitch_gwca_desc_free(priv);
+
+	rswitch_clock_disable(priv);
+}
+
+static int renesas_eth_sw_remove(struct platform_device *pdev)
+{
+	struct rswitch_private *priv = platform_get_drvdata(pdev);
+
+	rswitch_deinit(priv);
+
+	pm_runtime_put(&pdev->dev);
+	pm_runtime_disable(&pdev->dev);
+	clk_disable(priv->tsn_clk);
+
+	platform_set_drvdata(pdev, NULL);
+
+	return 0;
+}
+
+static struct platform_driver renesas_eth_sw_driver_platform = {
+	.probe = renesas_eth_sw_probe,
+	.remove = renesas_eth_sw_remove,
+	.driver = {
+		.name = "renesas_eth_sw",
+		.of_match_table = renesas_eth_sw_of_table,
+	}
+};
+module_platform_driver(renesas_eth_sw_driver_platform);
+MODULE_AUTHOR("Yoshihiro Shimoda");
+MODULE_DESCRIPTION("Renesas Ethernet Switch device driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/net/ethernet/renesas/rswitch.h b/drivers/net/ethernet/renesas/rswitch.h
new file mode 100644
index 000000000000..8f332b870fb3
--- /dev/null
+++ b/drivers/net/ethernet/renesas/rswitch.h
@@ -0,0 +1,971 @@ 
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Renesas Ethernet Switch device driver
+ *
+ * Copyright (C) 2022 Renesas Electronics Corporation
+ */
+
+#ifndef __RSWITCH_H__
+#define __RSWITCH_H__
+
+#include <linux/platform_device.h>
+#include "rcar_gen4_ptp.h"
+
+#define RSWITCH_NUM_HW		5
+#define RSWITCH_MAX_NUM_ETHA	3
+#define RSWITCH_MAX_NUM_NDEV	8
+#define RSWITCH_MAX_NUM_CHAINS	128
+
+#define TX_RING_SIZE		1024
+#define RX_RING_SIZE		1024
+
+#define PKT_BUF_SZ		1584
+#define RSWITCH_ALIGN		128
+#define RSWITCH_MAX_CTAG_PCP	7
+
+#define RSWITCH_TIMEOUT_US	100000
+
+#define RSWITCH_TOP_OFFSET	0x00008000
+#define RSWITCH_COMA_OFFSET	0x00009000
+#define RSWITCH_ETHA_OFFSET	0x0000a000	/* with RMAC */
+#define RSWITCH_ETHA_SIZE	0x00002000	/* with RMAC */
+#define RSWITCH_GWCA0_OFFSET	0x00010000
+#define RSWITCH_GWCA1_OFFSET	0x00012000
+
+/* TODO: hardcoded GWCA settings for now */
+#define GWCA_IRQ_RESOURCE_NAME	"gwca0_rxtx%d"
+#define GWCA_IRQ_NAME		"rswitch: gwca0_rxtx%d"
+#define GWCA_NUM_IRQS		8
+#define GWCA_INDEX		0
+#define AGENT_INDEX_GWCA	3
+#define GWRO			RSWITCH_GWCA0_OFFSET
+
+#define FWRO	0
+#define TPRO	RSWITCH_TOP_OFFSET
+#define CARO	RSWITCH_COMA_OFFSET
+#define TARO	0
+#define RMRO	0x1000
+enum rswitch_reg {
+	FWGC		= FWRO + 0x0000,
+	FWTTC0		= FWRO + 0x0010,
+	FWTTC1		= FWRO + 0x0014,
+	FWLBMC		= FWRO + 0x0018,
+	FWCEPTC		= FWRO + 0x0020,
+	FWCEPRC0	= FWRO + 0x0024,
+	FWCEPRC1	= FWRO + 0x0028,
+	FWCEPRC2	= FWRO + 0x002c,
+	FWCLPTC		= FWRO + 0x0030,
+	FWCLPRC		= FWRO + 0x0034,
+	FWCMPTC		= FWRO + 0x0040,
+	FWEMPTC		= FWRO + 0x0044,
+	FWSDMPTC	= FWRO + 0x0050,
+	FWSDMPVC	= FWRO + 0x0054,
+	FWLBWMC0	= FWRO + 0x0080,
+	FWPC00		= FWRO + 0x0100,
+	FWPC10		= FWRO + 0x0104,
+	FWPC20		= FWRO + 0x0108,
+	FWCTGC00	= FWRO + 0x0400,
+	FWCTGC10	= FWRO + 0x0404,
+	FWCTTC00	= FWRO + 0x0408,
+	FWCTTC10	= FWRO + 0x040c,
+	FWCTTC200	= FWRO + 0x0410,
+	FWCTSC00	= FWRO + 0x0420,
+	FWCTSC10	= FWRO + 0x0424,
+	FWCTSC20	= FWRO + 0x0428,
+	FWCTSC30	= FWRO + 0x042c,
+	FWCTSC40	= FWRO + 0x0430,
+	FWTWBFC0	= FWRO + 0x1000,
+	FWTWBFVC0	= FWRO + 0x1004,
+	FWTHBFC0	= FWRO + 0x1400,
+	FWTHBFV0C0	= FWRO + 0x1404,
+	FWTHBFV1C0	= FWRO + 0x1408,
+	FWFOBFC0	= FWRO + 0x1800,
+	FWFOBFV0C0	= FWRO + 0x1804,
+	FWFOBFV1C0	= FWRO + 0x1808,
+	FWRFC0		= FWRO + 0x1c00,
+	FWRFVC0		= FWRO + 0x1c04,
+	FWCFC0		= FWRO + 0x2000,
+	FWCFMC00	= FWRO + 0x2004,
+	FWIP4SC		= FWRO + 0x4008,
+	FWIP6SC		= FWRO + 0x4018,
+	FWIP6OC		= FWRO + 0x401c,
+	FWL2SC		= FWRO + 0x4020,
+	FWSFHEC		= FWRO + 0x4030,
+	FWSHCR0		= FWRO + 0x4040,
+	FWSHCR1		= FWRO + 0x4044,
+	FWSHCR2		= FWRO + 0x4048,
+	FWSHCR3		= FWRO + 0x404c,
+	FWSHCR4		= FWRO + 0x4050,
+	FWSHCR5		= FWRO + 0x4054,
+	FWSHCR6		= FWRO + 0x4058,
+	FWSHCR7		= FWRO + 0x405c,
+	FWSHCR8		= FWRO + 0x4060,
+	FWSHCR9		= FWRO + 0x4064,
+	FWSHCR10	= FWRO + 0x4068,
+	FWSHCR11	= FWRO + 0x406c,
+	FWSHCR12	= FWRO + 0x4070,
+	FWSHCR13	= FWRO + 0x4074,
+	FWSHCRR		= FWRO + 0x4078,
+	FWLTHHEC	= FWRO + 0x4090,
+	FWLTHHC		= FWRO + 0x4094,
+	FWLTHTL0	= FWRO + 0x40a0,
+	FWLTHTL1	= FWRO + 0x40a4,
+	FWLTHTL2	= FWRO + 0x40a8,
+	FWLTHTL3	= FWRO + 0x40ac,
+	FWLTHTL4	= FWRO + 0x40b0,
+	FWLTHTL5	= FWRO + 0x40b4,
+	FWLTHTL6	= FWRO + 0x40b8,
+	FWLTHTL7	= FWRO + 0x40bc,
+	FWLTHTL80	= FWRO + 0x40c0,
+	FWLTHTL9	= FWRO + 0x40d0,
+	FWLTHTLR	= FWRO + 0x40d4,
+	FWLTHTIM	= FWRO + 0x40e0,
+	FWLTHTEM	= FWRO + 0x40e4,
+	FWLTHTS0	= FWRO + 0x4100,
+	FWLTHTS1	= FWRO + 0x4104,
+	FWLTHTS2	= FWRO + 0x4108,
+	FWLTHTS3	= FWRO + 0x410c,
+	FWLTHTS4	= FWRO + 0x4110,
+	FWLTHTSR0	= FWRO + 0x4120,
+	FWLTHTSR1	= FWRO + 0x4124,
+	FWLTHTSR2	= FWRO + 0x4128,
+	FWLTHTSR3	= FWRO + 0x412c,
+	FWLTHTSR40	= FWRO + 0x4130,
+	FWLTHTSR5	= FWRO + 0x4140,
+	FWLTHTR		= FWRO + 0x4150,
+	FWLTHTRR0	= FWRO + 0x4154,
+	FWLTHTRR1	= FWRO + 0x4158,
+	FWLTHTRR2	= FWRO + 0x415c,
+	FWLTHTRR3	= FWRO + 0x4160,
+	FWLTHTRR4	= FWRO + 0x4164,
+	FWLTHTRR5	= FWRO + 0x4168,
+	FWLTHTRR6	= FWRO + 0x416c,
+	FWLTHTRR7	= FWRO + 0x4170,
+	FWLTHTRR8	= FWRO + 0x4174,
+	FWLTHTRR9	= FWRO + 0x4180,
+	FWLTHTRR10	= FWRO + 0x4190,
+	FWIPHEC		= FWRO + 0x4214,
+	FWIPHC		= FWRO + 0x4218,
+	FWIPTL0		= FWRO + 0x4220,
+	FWIPTL1		= FWRO + 0x4224,
+	FWIPTL2		= FWRO + 0x4228,
+	FWIPTL3		= FWRO + 0x422c,
+	FWIPTL4		= FWRO + 0x4230,
+	FWIPTL5		= FWRO + 0x4234,
+	FWIPTL6		= FWRO + 0x4238,
+	FWIPTL7		= FWRO + 0x4240,
+	FWIPTL8		= FWRO + 0x4250,
+	FWIPTLR		= FWRO + 0x4254,
+	FWIPTIM		= FWRO + 0x4260,
+	FWIPTEM		= FWRO + 0x4264,
+	FWIPTS0		= FWRO + 0x4270,
+	FWIPTS1		= FWRO + 0x4274,
+	FWIPTS2		= FWRO + 0x4278,
+	FWIPTS3		= FWRO + 0x427c,
+	FWIPTS4		= FWRO + 0x4280,
+	FWIPTSR0	= FWRO + 0x4284,
+	FWIPTSR1	= FWRO + 0x4288,
+	FWIPTSR2	= FWRO + 0x428c,
+	FWIPTSR3	= FWRO + 0x4290,
+	FWIPTSR4	= FWRO + 0x42a0,
+	FWIPTR		= FWRO + 0x42b0,
+	FWIPTRR0	= FWRO + 0x42b4,
+	FWIPTRR1	= FWRO + 0x42b8,
+	FWIPTRR2	= FWRO + 0x42bc,
+	FWIPTRR3	= FWRO + 0x42c0,
+	FWIPTRR4	= FWRO + 0x42c4,
+	FWIPTRR5	= FWRO + 0x42c8,
+	FWIPTRR6	= FWRO + 0x42cc,
+	FWIPTRR7	= FWRO + 0x42d0,
+	FWIPTRR8	= FWRO + 0x42e0,
+	FWIPTRR9	= FWRO + 0x42f0,
+	FWIPHLEC	= FWRO + 0x4300,
+	FWIPAGUSPC	= FWRO + 0x4500,
+	FWIPAGC		= FWRO + 0x4504,
+	FWIPAGM0	= FWRO + 0x4510,
+	FWIPAGM1	= FWRO + 0x4514,
+	FWIPAGM2	= FWRO + 0x4518,
+	FWIPAGM3	= FWRO + 0x451c,
+	FWIPAGM4	= FWRO + 0x4520,
+	FWMACHEC	= FWRO + 0x4620,
+	FWMACHC		= FWRO + 0x4624,
+	FWMACTL0	= FWRO + 0x4630,
+	FWMACTL1	= FWRO + 0x4634,
+	FWMACTL2	= FWRO + 0x4638,
+	FWMACTL3	= FWRO + 0x463c,
+	FWMACTL4	= FWRO + 0x4640,
+	FWMACTL5	= FWRO + 0x4650,
+	FWMACTLR	= FWRO + 0x4654,
+	FWMACTIM	= FWRO + 0x4660,
+	FWMACTEM	= FWRO + 0x4664,
+	FWMACTS0	= FWRO + 0x4670,
+	FWMACTS1	= FWRO + 0x4674,
+	FWMACTSR0	= FWRO + 0x4678,
+	FWMACTSR1	= FWRO + 0x467c,
+	FWMACTSR2	= FWRO + 0x4680,
+	FWMACTSR3	= FWRO + 0x4690,
+	FWMACTR		= FWRO + 0x46a0,
+	FWMACTRR0	= FWRO + 0x46a4,
+	FWMACTRR1	= FWRO + 0x46a8,
+	FWMACTRR2	= FWRO + 0x46ac,
+	FWMACTRR3	= FWRO + 0x46b0,
+	FWMACTRR4	= FWRO + 0x46b4,
+	FWMACTRR5	= FWRO + 0x46c0,
+	FWMACTRR6	= FWRO + 0x46d0,
+	FWMACHLEC	= FWRO + 0x4700,
+	FWMACAGUSPC	= FWRO + 0x4880,
+	FWMACAGC	= FWRO + 0x4884,
+	FWMACAGM0	= FWRO + 0x4888,
+	FWMACAGM1	= FWRO + 0x488c,
+	FWVLANTEC	= FWRO + 0x4900,
+	FWVLANTL0	= FWRO + 0x4910,
+	FWVLANTL1	= FWRO + 0x4914,
+	FWVLANTL2	= FWRO + 0x4918,
+	FWVLANTL3	= FWRO + 0x4920,
+	FWVLANTL4	= FWRO + 0x4930,
+	FWVLANTLR	= FWRO + 0x4934,
+	FWVLANTIM	= FWRO + 0x4940,
+	FWVLANTEM	= FWRO + 0x4944,
+	FWVLANTS	= FWRO + 0x4950,
+	FWVLANTSR0	= FWRO + 0x4954,
+	FWVLANTSR1	= FWRO + 0x4958,
+	FWVLANTSR2	= FWRO + 0x4960,
+	FWVLANTSR3	= FWRO + 0x4970,
+	FWPBFC0		= FWRO + 0x4a00,
+	FWPBFCSDC00	= FWRO + 0x4a04,
+	FWL23URL0	= FWRO + 0x4e00,
+	FWL23URL1	= FWRO + 0x4e04,
+	FWL23URL2	= FWRO + 0x4e08,
+	FWL23URL3	= FWRO + 0x4e0c,
+	FWL23URLR	= FWRO + 0x4e10,
+	FWL23UTIM	= FWRO + 0x4e20,
+	FWL23URR	= FWRO + 0x4e30,
+	FWL23URRR0	= FWRO + 0x4e34,
+	FWL23URRR1	= FWRO + 0x4e38,
+	FWL23URRR2	= FWRO + 0x4e3c,
+	FWL23URRR3	= FWRO + 0x4e40,
+	FWL23URMC0	= FWRO + 0x4f00,
+	FWPMFGC0	= FWRO + 0x5000,
+	FWPGFC0		= FWRO + 0x5100,
+	FWPGFIGSC0	= FWRO + 0x5104,
+	FWPGFENC0	= FWRO + 0x5108,
+	FWPGFENM0	= FWRO + 0x510c,
+	FWPGFCSTC00	= FWRO + 0x5110,
+	FWPGFCSTC10	= FWRO + 0x5114,
+	FWPGFCSTM00	= FWRO + 0x5118,
+	FWPGFCSTM10	= FWRO + 0x511c,
+	FWPGFCTC0	= FWRO + 0x5120,
+	FWPGFCTM0	= FWRO + 0x5124,
+	FWPGFHCC0	= FWRO + 0x5128,
+	FWPGFSM0	= FWRO + 0x512c,
+	FWPGFGC0	= FWRO + 0x5130,
+	FWPGFGL0	= FWRO + 0x5500,
+	FWPGFGL1	= FWRO + 0x5504,
+	FWPGFGLR	= FWRO + 0x5518,
+	FWPGFGR		= FWRO + 0x5510,
+	FWPGFGRR0	= FWRO + 0x5514,
+	FWPGFGRR1	= FWRO + 0x5518,
+	FWPGFRIM	= FWRO + 0x5520,
+	FWPMTRFC0	= FWRO + 0x5600,
+	FWPMTRCBSC0	= FWRO + 0x5604,
+	FWPMTRC0RC0	= FWRO + 0x5608,
+	FWPMTREBSC0	= FWRO + 0x560c,
+	FWPMTREIRC0	= FWRO + 0x5610,
+	FWPMTRFM0	= FWRO + 0x5614,
+	FWFTL0		= FWRO + 0x6000,
+	FWFTL1		= FWRO + 0x6004,
+	FWFTLR		= FWRO + 0x6008,
+	FWFTOC		= FWRO + 0x6010,
+	FWFTOPC		= FWRO + 0x6014,
+	FWFTIM		= FWRO + 0x6020,
+	FWFTR		= FWRO + 0x6030,
+	FWFTRR0		= FWRO + 0x6034,
+	FWFTRR1		= FWRO + 0x6038,
+	FWFTRR2		= FWRO + 0x603c,
+	FWSEQNGC0	= FWRO + 0x6100,
+	FWSEQNGM0	= FWRO + 0x6104,
+	FWSEQNRC	= FWRO + 0x6200,
+	FWCTFDCN0	= FWRO + 0x6300,
+	FWLTHFDCN0	= FWRO + 0x6304,
+	FWIPFDCN0	= FWRO + 0x6308,
+	FWLTWFDCN0	= FWRO + 0x630c,
+	FWPBFDCN0	= FWRO + 0x6310,
+	FWMHLCN0	= FWRO + 0x6314,
+	FWIHLCN0	= FWRO + 0x6318,
+	FWICRDCN0	= FWRO + 0x6500,
+	FWWMRDCN0	= FWRO + 0x6504,
+	FWCTRDCN0	= FWRO + 0x6508,
+	FWLTHRDCN0	= FWRO + 0x650c,
+	FWIPRDCN0	= FWRO + 0x6510,
+	FWLTWRDCN0	= FWRO + 0x6514,
+	FWPBRDCN0	= FWRO + 0x6518,
+	FWPMFDCN0	= FWRO + 0x6700,
+	FWPGFDCN0	= FWRO + 0x6780,
+	FWPMGDCN0	= FWRO + 0x6800,
+	FWPMYDCN0	= FWRO + 0x6804,
+	FWPMRDCN0	= FWRO + 0x6808,
+	FWFRPPCN0	= FWRO + 0x6a00,
+	FWFRDPCN0	= FWRO + 0x6a04,
+	FWEIS00		= FWRO + 0x7900,
+	FWEIE00		= FWRO + 0x7904,
+	FWEID00		= FWRO + 0x7908,
+	FWEIS1		= FWRO + 0x7a00,
+	FWEIE1		= FWRO + 0x7a04,
+	FWEID1		= FWRO + 0x7a08,
+	FWEIS2		= FWRO + 0x7a10,
+	FWEIE2		= FWRO + 0x7a14,
+	FWEID2		= FWRO + 0x7a18,
+	FWEIS3		= FWRO + 0x7a20,
+	FWEIE3		= FWRO + 0x7a24,
+	FWEID3		= FWRO + 0x7a28,
+	FWEIS4		= FWRO + 0x7a30,
+	FWEIE4		= FWRO + 0x7a34,
+	FWEID4		= FWRO + 0x7a38,
+	FWEIS5		= FWRO + 0x7a40,
+	FWEIE5		= FWRO + 0x7a44,
+	FWEID5		= FWRO + 0x7a48,
+	FWEIS60		= FWRO + 0x7a50,
+	FWEIE60		= FWRO + 0x7a54,
+	FWEID60		= FWRO + 0x7a58,
+	FWEIS61		= FWRO + 0x7a60,
+	FWEIE61		= FWRO + 0x7a64,
+	FWEID61		= FWRO + 0x7a68,
+	FWEIS62		= FWRO + 0x7a70,
+	FWEIE62		= FWRO + 0x7a74,
+	FWEID62		= FWRO + 0x7a78,
+	FWEIS63		= FWRO + 0x7a80,
+	FWEIE63		= FWRO + 0x7a84,
+	FWEID63		= FWRO + 0x7a88,
+	FWEIS70		= FWRO + 0x7a90,
+	FWEIE70		= FWRO + 0x7A94,
+	FWEID70		= FWRO + 0x7a98,
+	FWEIS71		= FWRO + 0x7aa0,
+	FWEIE71		= FWRO + 0x7aa4,
+	FWEID71		= FWRO + 0x7aa8,
+	FWEIS72		= FWRO + 0x7ab0,
+	FWEIE72		= FWRO + 0x7ab4,
+	FWEID72		= FWRO + 0x7ab8,
+	FWEIS73		= FWRO + 0x7ac0,
+	FWEIE73		= FWRO + 0x7ac4,
+	FWEID73		= FWRO + 0x7ac8,
+	FWEIS80		= FWRO + 0x7ad0,
+	FWEIE80		= FWRO + 0x7ad4,
+	FWEID80		= FWRO + 0x7ad8,
+	FWEIS81		= FWRO + 0x7ae0,
+	FWEIE81		= FWRO + 0x7ae4,
+	FWEID81		= FWRO + 0x7ae8,
+	FWEIS82		= FWRO + 0x7af0,
+	FWEIE82		= FWRO + 0x7af4,
+	FWEID82		= FWRO + 0x7af8,
+	FWEIS83		= FWRO + 0x7b00,
+	FWEIE83		= FWRO + 0x7b04,
+	FWEID83		= FWRO + 0x7b08,
+	FWMIS0		= FWRO + 0x7c00,
+	FWMIE0		= FWRO + 0x7c04,
+	FWMID0		= FWRO + 0x7c08,
+	FWSCR0		= FWRO + 0x7d00,
+	FWSCR1		= FWRO + 0x7d04,
+	FWSCR2		= FWRO + 0x7d08,
+	FWSCR3		= FWRO + 0x7d0c,
+	FWSCR4		= FWRO + 0x7d10,
+	FWSCR5		= FWRO + 0x7d14,
+	FWSCR6		= FWRO + 0x7d18,
+	FWSCR7		= FWRO + 0x7d1c,
+	FWSCR8		= FWRO + 0x7d20,
+	FWSCR9		= FWRO + 0x7d24,
+	FWSCR10		= FWRO + 0x7d28,
+	FWSCR11		= FWRO + 0x7d2c,
+	FWSCR12		= FWRO + 0x7d30,
+	FWSCR13		= FWRO + 0x7d34,
+	FWSCR14		= FWRO + 0x7d38,
+	FWSCR15		= FWRO + 0x7d3c,
+	FWSCR16		= FWRO + 0x7d40,
+	FWSCR17		= FWRO + 0x7d44,
+	FWSCR18		= FWRO + 0x7d48,
+	FWSCR19		= FWRO + 0x7d4c,
+	FWSCR20		= FWRO + 0x7d50,
+	FWSCR21		= FWRO + 0x7d54,
+	FWSCR22		= FWRO + 0x7d58,
+	FWSCR23		= FWRO + 0x7d5c,
+	FWSCR24		= FWRO + 0x7d60,
+	FWSCR25		= FWRO + 0x7d64,
+	FWSCR26		= FWRO + 0x7d68,
+	FWSCR27		= FWRO + 0x7d6c,
+	FWSCR28		= FWRO + 0x7d70,
+	FWSCR29		= FWRO + 0x7d74,
+	FWSCR30		= FWRO + 0x7d78,
+	FWSCR31		= FWRO + 0x7d7c,
+	FWSCR32		= FWRO + 0x7d80,
+	FWSCR33		= FWRO + 0x7d84,
+	FWSCR34		= FWRO + 0x7d88,
+	FWSCR35		= FWRO + 0x7d8c,
+	FWSCR36		= FWRO + 0x7d90,
+	FWSCR37		= FWRO + 0x7d94,
+	FWSCR38		= FWRO + 0x7d98,
+	FWSCR39		= FWRO + 0x7d9c,
+	FWSCR40		= FWRO + 0x7da0,
+	FWSCR41		= FWRO + 0x7da4,
+	FWSCR42		= FWRO + 0x7da8,
+	FWSCR43		= FWRO + 0x7dac,
+	FWSCR44		= FWRO + 0x7db0,
+	FWSCR45		= FWRO + 0x7db4,
+	FWSCR46		= FWRO + 0x7db8,
+
+	TPEMIMC0	= TPRO + 0x0000,
+	TPEMIMC1	= TPRO + 0x0004,
+	TPEMIMC2	= TPRO + 0x0008,
+	TPEMIMC3	= TPRO + 0x000c,
+	TPEMIMC4	= TPRO + 0x0010,
+	TPEMIMC5	= TPRO + 0x0014,
+	TPEMIMC60	= TPRO + 0x0080,
+	TPEMIMC70	= TPRO + 0x0100,
+	TSIM		= TPRO + 0x0700,
+	TFIM		= TPRO + 0x0704,
+	TCIM		= TPRO + 0x0708,
+	TGIM0		= TPRO + 0x0710,
+	TGIM1		= TPRO + 0x0714,
+	TEIM0		= TPRO + 0x0720,
+	TEIM1		= TPRO + 0x0724,
+	TEIM2		= TPRO + 0x0728,
+
+	RIPV		= CARO + 0x0000,
+	RRC		= CARO + 0x0004,
+	RCEC		= CARO + 0x0008,
+	RCDC		= CARO + 0x000c,
+	RSSIS		= CARO + 0x0010,
+	RSSIE		= CARO + 0x0014,
+	RSSID		= CARO + 0x0018,
+	CABPIBWMC	= CARO + 0x0020,
+	CABPWMLC	= CARO + 0x0040,
+	CABPPFLC0	= CARO + 0x0050,
+	CABPPWMLC0	= CARO + 0x0060,
+	CABPPPFLC00	= CARO + 0x00a0,
+	CABPULC		= CARO + 0x0100,
+	CABPIRM		= CARO + 0x0140,
+	CABPPCM		= CARO + 0x0144,
+	CABPLCM		= CARO + 0x0148,
+	CABPCPM		= CARO + 0x0180,
+	CABPMCPM	= CARO + 0x0200,
+	CARDNM		= CARO + 0x0280,
+	CARDMNM		= CARO + 0x0284,
+	CARDCN		= CARO + 0x0290,
+	CAEIS0		= CARO + 0x0300,
+	CAEIE0		= CARO + 0x0304,
+	CAEID0		= CARO + 0x0308,
+	CAEIS1		= CARO + 0x0310,
+	CAEIE1		= CARO + 0x0314,
+	CAEID1		= CARO + 0x0318,
+	CAMIS0		= CARO + 0x0340,
+	CAMIE0		= CARO + 0x0344,
+	CAMID0		= CARO + 0x0348,
+	CAMIS1		= CARO + 0x0350,
+	CAMIE1		= CARO + 0x0354,
+	CAMID1		= CARO + 0x0358,
+	CASCR		= CARO + 0x0380,
+
+	EAMC		= TARO + 0x0000,
+	EAMS		= TARO + 0x0004,
+	EAIRC		= TARO + 0x0010,
+	EATDQSC		= TARO + 0x0014,
+	EATDQC		= TARO + 0x0018,
+	EATDQAC		= TARO + 0x001c,
+	EATPEC		= TARO + 0x0020,
+	EATMFSC0	= TARO + 0x0040,
+	EATDQDC0	= TARO + 0x0060,
+	EATDQM0		= TARO + 0x0080,
+	EATDQMLM0	= TARO + 0x00a0,
+	EACTQC		= TARO + 0x0100,
+	EACTDQDC	= TARO + 0x0104,
+	EACTDQM		= TARO + 0x0108,
+	EACTDQMLM	= TARO + 0x010c,
+	EAVCC		= TARO + 0x0130,
+	EAVTC		= TARO + 0x0134,
+	EATTFC		= TARO + 0x0138,
+	EACAEC		= TARO + 0x0200,
+	EACC		= TARO + 0x0204,
+	EACAIVC0	= TARO + 0x0220,
+	EACAULC0	= TARO + 0x0240,
+	EACOEM		= TARO + 0x0260,
+	EACOIVM0	= TARO + 0x0280,
+	EACOULM0	= TARO + 0x02a0,
+	EACGSM		= TARO + 0x02c0,
+	EATASC		= TARO + 0x0300,
+	EATASENC0	= TARO + 0x0320,
+	EATASCTENC	= TARO + 0x0340,
+	EATASENM0	= TARO + 0x0360,
+	EATASCTENM	= TARO + 0x0380,
+	EATASCSTC0	= TARO + 0x03a0,
+	EATASCSTC1	= TARO + 0x03a4,
+	EATASCSTM0	= TARO + 0x03a8,
+	EATASCSTM1	= TARO + 0x03ac,
+	EATASCTC	= TARO + 0x03b0,
+	EATASCTM	= TARO + 0x03b4,
+	EATASGL0	= TARO + 0x03c0,
+	EATASGL1	= TARO + 0x03c4,
+	EATASGLR	= TARO + 0x03c8,
+	EATASGR		= TARO + 0x03d0,
+	EATASGRR	= TARO + 0x03d4,
+	EATASHCC	= TARO + 0x03e0,
+	EATASRIRM	= TARO + 0x03e4,
+	EATASSM		= TARO + 0x03e8,
+	EAUSMFSECN	= TARO + 0x0400,
+	EATFECN		= TARO + 0x0404,
+	EAFSECN		= TARO + 0x0408,
+	EADQOECN	= TARO + 0x040c,
+	EADQSECN	= TARO + 0x0410,
+	EACKSECN	= TARO + 0x0414,
+	EAEIS0		= TARO + 0x0500,
+	EAEIE0		= TARO + 0x0504,
+	EAEID0		= TARO + 0x0508,
+	EAEIS1		= TARO + 0x0510,
+	EAEIE1		= TARO + 0x0514,
+	EAEID1		= TARO + 0x0518,
+	EAEIS2		= TARO + 0x0520,
+	EAEIE2		= TARO + 0x0524,
+	EAEID2		= TARO + 0x0528,
+	EASCR		= TARO + 0x0580,
+
+	MPSM		= RMRO + 0x0000,
+	MPIC		= RMRO + 0x0004,
+	MPIM		= RMRO + 0x0008,
+	MIOC		= RMRO + 0x0010,
+	MIOM		= RMRO + 0x0014,
+	MXMS		= RMRO + 0x0018,
+	MTFFC		= RMRO + 0x0020,
+	MTPFC		= RMRO + 0x0024,
+	MTPFC2		= RMRO + 0x0028,
+	MTPFC30		= RMRO + 0x0030,
+	MTATC0		= RMRO + 0x0050,
+	MTIM		= RMRO + 0x0060,
+	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,
+	MRIM		= RMRO + 0x00a8,
+	MRPFM		= RMRO + 0x00ac,
+	MPFC0		= RMRO + 0x0100,
+	MLVC		= RMRO + 0x0180,
+	MEEEC		= RMRO + 0x0184,
+	MLBC		= RMRO + 0x0188,
+	MXGMIIC		= RMRO + 0x0190,
+	MPCH		= RMRO + 0x0194,
+	MANC		= RMRO + 0x0198,
+	MANM		= RMRO + 0x019c,
+	MPLCA1		= RMRO + 0x01a0,
+	MPLCA2		= RMRO + 0x01a4,
+	MPLCA3		= RMRO + 0x01a8,
+	MPLCA4		= RMRO + 0x01ac,
+	MPLCAM		= RMRO + 0x01b0,
+	MHDC1		= RMRO + 0x01c0,
+	MHDC2		= RMRO + 0x01c4,
+	MEIS		= RMRO + 0x0200,
+	MEIE		= RMRO + 0x0204,
+	MEID		= RMRO + 0x0208,
+	MMIS0		= RMRO + 0x0210,
+	MMIE0		= RMRO + 0x0214,
+	MMID0		= RMRO + 0x0218,
+	MMIS1		= RMRO + 0x0220,
+	MMIE1		= RMRO + 0x0224,
+	MMID1		= RMRO + 0x0228,
+	MMIS2		= RMRO + 0x0230,
+	MMIE2		= RMRO + 0x0234,
+	MMID2		= RMRO + 0x0238,
+	MMPFTCT		= RMRO + 0x0300,
+	MAPFTCT		= RMRO + 0x0304,
+	MPFRCT		= RMRO + 0x0308,
+	MFCICT		= RMRO + 0x030c,
+	MEEECT		= RMRO + 0x0310,
+	MMPCFTCT0	= RMRO + 0x0320,
+	MAPCFTCT0	= RMRO + 0x0330,
+	MPCFRCT0	= RMRO + 0x0340,
+	MHDCC		= RMRO + 0x0350,
+	MROVFC		= RMRO + 0x0354,
+	MRHCRCEC	= RMRO + 0x0358,
+	MRXBCE		= RMRO + 0x0400,
+	MRXBCP		= RMRO + 0x0404,
+	MRGFCE		= RMRO + 0x0408,
+	MRGFCP		= RMRO + 0x040c,
+	MRBFC		= RMRO + 0x0410,
+	MRMFC		= RMRO + 0x0414,
+	MRUFC		= RMRO + 0x0418,
+	MRPEFC		= RMRO + 0x041c,
+	MRNEFC		= RMRO + 0x0420,
+	MRFMEFC		= RMRO + 0x0424,
+	MRFFMEFC	= RMRO + 0x0428,
+	MRCFCEFC	= RMRO + 0x042c,
+	MRFCEFC		= RMRO + 0x0430,
+	MRRCFEFC	= RMRO + 0x0434,
+	MRUEFC		= RMRO + 0x043c,
+	MROEFC		= RMRO + 0x0440,
+	MRBOEC		= RMRO + 0x0444,
+	MTXBCE		= RMRO + 0x0500,
+	MTXBCP		= RMRO + 0x0504,
+	MTGFCE		= RMRO + 0x0508,
+	MTGFCP		= RMRO + 0x050c,
+	MTBFC		= RMRO + 0x0510,
+	MTMFC		= RMRO + 0x0514,
+	MTUFC		= RMRO + 0x0518,
+	MTEFC		= RMRO + 0x051c,
+
+	GWMC		= GWRO + 0x0000,
+	GWMS		= GWRO + 0x0004,
+	GWIRC		= GWRO + 0x0010,
+	GWRDQSC		= GWRO + 0x0014,
+	GWRDQC		= GWRO + 0x0018,
+	GWRDQAC		= GWRO + 0x001c,
+	GWRGC		= GWRO + 0x0020,
+	GWRMFSC0	= GWRO + 0x0040,
+	GWRDQDC0	= GWRO + 0x0060,
+	GWRDQM0		= GWRO + 0x0080,
+	GWRDQMLM0	= GWRO + 0x00a0,
+	GWMTIRM		= GWRO + 0x0100,
+	GWMSTLS		= GWRO + 0x0104,
+	GWMSTLR		= GWRO + 0x0108,
+	GWMSTSS		= GWRO + 0x010c,
+	GWMSTSR		= GWRO + 0x0110,
+	GWMAC0		= GWRO + 0x0120,
+	GWMAC1		= GWRO + 0x0124,
+	GWVCC		= GWRO + 0x0130,
+	GWVTC		= GWRO + 0x0134,
+	GWTTFC		= GWRO + 0x0138,
+	GWTDCAC00	= GWRO + 0x0140,
+	GWTDCAC10	= GWRO + 0x0144,
+	GWTSDCC0	= GWRO + 0x0160,
+	GWTNM		= GWRO + 0x0180,
+	GWTMNM		= GWRO + 0x0184,
+	GWAC		= GWRO + 0x0190,
+	GWDCBAC0	= GWRO + 0x0194,
+	GWDCBAC1	= GWRO + 0x0198,
+	GWIICBSC	= GWRO + 0x019c,
+	GWMDNC		= GWRO + 0x01a0,
+	GWTRC0		= GWRO + 0x0200,
+	GWTPC0		= GWRO + 0x0300,
+	GWARIRM		= GWRO + 0x0380,
+	GWDCC0		= GWRO + 0x0400,
+	GWAARSS		= GWRO + 0x0800,
+	GWAARSR0	= GWRO + 0x0804,
+	GWAARSR1	= GWRO + 0x0808,
+	GWIDAUAS0	= GWRO + 0x0840,
+	GWIDASM0	= GWRO + 0x0880,
+	GWIDASAM00	= GWRO + 0x0900,
+	GWIDASAM10	= GWRO + 0x0904,
+	GWIDACAM00	= GWRO + 0x0980,
+	GWIDACAM10	= GWRO + 0x0984,
+	GWGRLC		= GWRO + 0x0a00,
+	GWGRLULC	= GWRO + 0x0a04,
+	GWRLIVC0	= GWRO + 0x0a80,
+	GWRLULC0	= GWRO + 0x0a84,
+	GWIDPC		= GWRO + 0x0b00,
+	GWIDC0		= GWRO + 0x0c00,
+	GWDIS0		= GWRO + 0x1100,
+	GWDIE0		= GWRO + 0x1104,
+	GWDID0		= GWRO + 0x1108,
+	GWTSDIS		= GWRO + 0x1180,
+	GWTSDIE		= GWRO + 0x1184,
+	GWTSDID		= GWRO + 0x1188,
+	GWEIS0		= GWRO + 0x1190,
+	GWEIE0		= GWRO + 0x1194,
+	GWEID0		= GWRO + 0x1198,
+	GWEIS1		= GWRO + 0x11a0,
+	GWEIE1		= GWRO + 0x11a4,
+	GWEID1		= GWRO + 0x11a8,
+	GWEIS20		= GWRO + 0x1200,
+	GWEIE20		= GWRO + 0x1204,
+	GWEID20		= GWRO + 0x1208,
+	GWEIS3		= GWRO + 0x1280,
+	GWEIE3		= GWRO + 0x1284,
+	GWEID3		= GWRO + 0x1288,
+	GWEIS4		= GWRO + 0x1290,
+	GWEIE4		= GWRO + 0x1294,
+	GWEID4		= GWRO + 0x1298,
+	GWEIS5		= GWRO + 0x12a0,
+	GWEIE5		= GWRO + 0x12a4,
+	GWEID5		= GWRO + 0x12a8,
+	GWSCR0		= GWRO + 0x1800,
+	GWSCR1		= GWRO + 0x1900,
+};
+
+/* ETHA/RMAC */
+enum rswitch_etha_mode {
+	EAMC_OPC_RESET,
+	EAMC_OPC_DISABLE,
+	EAMC_OPC_CONFIG,
+	EAMC_OPC_OPERATION,
+};
+
+#define EAMS_OPS_MASK		EAMC_OPC_OPERATION
+
+#define MPIC_PIS_MII		0x00
+#define MPIC_PIS_GMII		0x02
+#define MPIC_PIS_XGMII		0x04
+#define MPIC_LSC_SHIFT		3
+#define MPIC_LSC_100M		(1 << MPIC_LSC_SHIFT)
+#define MPIC_LSC_1G		(2 << MPIC_LSC_SHIFT)
+#define MPIC_LSC_2_5G		(3 << MPIC_LSC_SHIFT)
+
+#define MDIO_READ_C45		0x03
+#define MDIO_WRITE_C45		0x01
+
+#define MPSM_PSME		BIT(0)
+#define MPSM_MFF_C45		BIT(2)
+#define MPSM_PRD_SHIFT		16
+#define MPSM_PRD_MASK		GENMASK(31, MPSM_PRD_SHIFT)
+
+/* Completion flags */
+#define MMIS1_PAACS             BIT(2) /* Address */
+#define MMIS1_PWACS             BIT(1) /* Write */
+#define MMIS1_PRACS             BIT(0) /* Read */
+#define MMIS1_CLEAR_FLAGS       0xf
+
+#define MPIC_PSMCS_SHIFT	16
+#define MPIC_PSMCS_MASK		GENMASK(22, MPIC_PSMCS_SHIFT)
+#define MPIC_PSMCS(val)		((val) << MPIC_PSMCS_SHIFT)
+
+#define MPIC_PSMHT_SHIFT	24
+#define MPIC_PSMHT_MASK		GENMASK(26, MPIC_PSMHT_SHIFT)
+#define MPIC_PSMHT(val)		((val) << MPIC_PSMHT_SHIFT)
+
+#define MLVC_PLV		BIT(16)
+
+/* GWCA */
+enum rswitch_gwca_mode {
+	GWMC_OPC_RESET,
+	GWMC_OPC_DISABLE,
+	GWMC_OPC_CONFIG,
+	GWMC_OPC_OPERATION,
+};
+
+#define GWMS_OPS_MASK		GWMC_OPC_OPERATION
+
+#define GWMTIRM_MTIOG		BIT(0)
+#define GWMTIRM_MTR		BIT(1)
+
+#define GWVCC_VEM_SC_TAG	(0x3 << 16)
+
+#define GWARIRM_ARIOG		BIT(0)
+#define GWARIRM_ARR		BIT(1)
+
+#define GWDCC_BALR		BIT(24)
+#define GWDCC_DQT		BIT(11)
+#define GWDCC_ETS		BIT(9)
+#define GWDCC_EDE		BIT(8)
+
+#define GWTRC(chain)		(GWTRC0 + (chain) / 32 * 4)
+#define GWDCC_OFFS(chain)	(GWDCC0 + (chain) * 4)
+
+#define GWDIS(i)		(GWDIS0 + (i) * 0x10)
+#define GWDIE(i)		(GWDIE0 + (i) * 0x10)
+#define GWDID(i)		(GWDID0 + (i) * 0x10)
+
+/* COMA */
+#define RRC_RR			BIT(0)
+#define RRC_RR_CLR		0
+#define RCEC_RCE		BIT(16)
+#define RCDC_RCD		BIT(16)
+
+#define CABPIRM_BPIOG		BIT(0)
+#define CABPIRM_BPR		BIT(1)
+
+/* MFWD */
+#define FWPC0_LTHTA		BIT(0)
+#define FWPC0_IP4UE		BIT(3)
+#define FWPC0_IP4TE		BIT(4)
+#define FWPC0_IP4OE		BIT(5)
+#define FWPC0_L2SE		BIT(9)
+#define FWPC0_IP4EA		BIT(10)
+#define FWPC0_IPDSA		BIT(12)
+#define FWPC0_IPHLA		BIT(18)
+#define FWPC0_MACSDA		BIT(20)
+#define FWPC0_MACHLA		BIT(26)
+#define FWPC0_MACHMA		BIT(27)
+#define FWPC0_VLANSA		BIT(28)
+
+#define FWPC0(i)		(FWPC00 + (i) * 0x10)
+#define FWPC0_DEFAULT		(FWPC0_LTHTA | FWPC0_IP4UE | FWPC0_IP4TE | \
+				 FWPC0_IP4OE | FWPC0_L2SE | FWPC0_IP4EA | \
+				 FWPC0_IPDSA | FWPC0_IPHLA | FWPC0_MACSDA | \
+				 FWPC0_MACHLA |	FWPC0_MACHMA | FWPC0_VLANSA)
+
+#define	FWPBFC(i)		(FWPBFC0 + (i) * 0x10)
+
+#define FWPBFCSDC(j, i)         (FWPBFCSDC00 + (i) * 0x10 + (j) * 0x04)
+
+/* TOP */
+#define TPEMIMC7(chain)		(TPEMIMC70 + (chain) * 4)
+
+/* SerDes */
+enum rswitch_serdes_mode {
+	USXGMII,
+	SGMII,
+	COMBINATION,
+};
+
+/* 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_DS_TAGL_BIT {
+	TX_DS	= 0x0fff, /* Data size */
+	TX_TAGL	= 0xf000, /* Frame tag LSBs */
+};
+
+enum DIE_DT {
+	/* Frame data */
+	DT_FSINGLE	= 0x80,
+	DT_FSTART	= 0x90,
+	DT_FMID		= 0xa0,
+	DT_FEND		= 0xb8,
+
+	/* Chain control */
+	DT_LEMPTY	= 0xc0,
+	DT_EEMPTY	= 0xd0,
+	DT_LINKFIX	= 0x00,
+	DT_LINK		= 0xe0,
+	DT_EOS		= 0xf0,
+	/* HW/SW arbitration */
+	DT_FEMPTY	= 0x40,
+	DT_FEMPTY_IS	= 0x10,
+	DT_FEMPTY_IC	= 0x20,
+	DT_FEMPTY_ND	= 0x38,
+	DT_FEMPTY_START	= 0x50,
+	DT_FEMPTY_MID	= 0x60,
+	DT_FEMPTY_END	= 0x70,
+
+	DT_MASK		= 0xf0,
+	DIE		= 0x08,	/* Descriptor Interrupt Enable */
+};
+
+struct rswitch_desc {
+	__le16 info_ds;	/* Descriptor size */
+	u8 die_dt;	/* Descriptor interrupt enable and type */
+	__u8  dptrh;	/* Descriptor pointer MSB */
+	__le32 dptrl;	/* Descriptor pointer LSW */
+} __packed;
+
+struct rswitch_ts_desc {
+	__le16 info_ds;	/* Descriptor size */
+	u8 die_dt;	/* Descriptor interrupt enable and type */
+	__u8  dptrh;	/* Descriptor pointer MSB */
+	__le32 dptrl;	/* Descriptor pointer LSW */
+	__le32 ts_nsec;
+	__le32 ts_sec;
+} __packed;
+
+struct rswitch_ext_desc {
+	__le16 info_ds;	/* Descriptor size */
+	u8 die_dt;	/* Descriptor interrupt enable and type */
+	__u8  dptrh;	/* Descriptor pointer MSB */
+	__le32 dptrl;	/* Descriptor pointer LSW */
+	__le64 info1;
+} __packed;
+
+struct rswitch_ext_ts_desc {
+	__le16 info_ds;	/* Descriptor size */
+	u8 die_dt;	/* Descriptor interrupt enable and type */
+	__u8  dptrh;	/* Descriptor pointer MSB */
+	__le32 dptrl;	/* Descriptor pointer LSW */
+	__le64 info1;
+	__le32 ts_nsec;
+	__le32 ts_sec;
+} __packed;
+
+struct rswitch_etha {
+	int index;
+	void __iomem *addr;
+	void __iomem *coma_addr;
+	void __iomem *serdes_addr;
+	void __iomem *serdes_addr0;	/* lane 0 */
+	bool external_phy;
+	struct mii_bus *mii;
+	phy_interface_t phy_interface;
+	u8 mac_addr[MAX_ADDR_LEN];
+	int link;
+	int speed;
+
+	/* This hardware could not be initialized twice so that marked
+	 * this flag to avoid multiple initialization.
+	 */
+	bool operated;
+};
+
+struct rswitch_gwca_chain {
+	int index;
+	bool dir_tx;
+	bool gptp;
+	union {
+		struct rswitch_ext_desc *ring;
+		struct rswitch_ext_ts_desc *ts_ring;
+	};
+	dma_addr_t ring_dma;
+	u32 num_ring;
+	u32 cur;
+	u32 dirty;
+	struct sk_buff **skb;
+
+	struct net_device *ndev;	/* chain to ndev for irq */
+};
+
+#define RSWITCH_NUM_IRQ_REGS	(RSWITCH_MAX_NUM_CHAINS / BITS_PER_TYPE(u32))
+struct rswitch_gwca {
+	int index;
+	struct rswitch_gwca_chain *chains;
+	int num_chains;
+	DECLARE_BITMAP(used, RSWITCH_MAX_NUM_CHAINS);
+	u32 tx_irq_bits[RSWITCH_NUM_IRQ_REGS];
+	u32 rx_irq_bits[RSWITCH_NUM_IRQ_REGS];
+	int irq[GWCA_NUM_IRQS];
+};
+
+#define NUM_CHAINS_PER_NDEV	2
+struct rswitch_device {
+	struct rswitch_private *priv;
+	struct net_device *ndev;
+	struct napi_struct napi;
+	void __iomem *addr;
+	bool gptp_master;
+	struct rswitch_gwca_chain *tx_chain;
+	struct rswitch_gwca_chain *rx_chain;
+	u8 ts_tag;
+
+	int port;
+	struct rswitch_etha *etha;
+};
+
+struct rswitch_mfwd_mac_table_entry {
+	int chain_index;
+	unsigned char addr[MAX_ADDR_LEN];
+};
+
+struct rswitch_mfwd {
+	struct rswitch_mac_table_entry *mac_table_entries;
+	int num_mac_table_entries;
+};
+
+struct rswitch_private {
+	struct platform_device *pdev;
+	void __iomem *addr;
+	void __iomem *serdes_addr;
+	struct rcar_gen4_ptp_private *ptp_priv;
+	struct rswitch_desc *desc_bat;
+	dma_addr_t desc_bat_dma;
+	u32 desc_bat_size;
+
+	struct rswitch_device *rdev[RSWITCH_MAX_NUM_NDEV];
+
+	struct rswitch_gwca gwca;
+	struct rswitch_etha etha[RSWITCH_MAX_NUM_ETHA];
+	struct rswitch_mfwd mfwd;
+
+	struct clk *tsn_clk;
+};
+
+#endif	/* #ifndef __RSWITCH_H__ */
diff --git a/drivers/net/ethernet/renesas/rswitch_serdes.c b/drivers/net/ethernet/renesas/rswitch_serdes.c
new file mode 100644
index 000000000000..3671a5380a19
--- /dev/null
+++ b/drivers/net/ethernet/renesas/rswitch_serdes.c
@@ -0,0 +1,192 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/* Renesas Ethernet Serdes device driver
+ *
+ * Copyright (C) 2022 Renesas Electronics Corporation
+ */
+
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/kernel.h>
+#include <linux/netdevice.h>
+#include <linux/phy.h>
+
+#include "rswitch.h"
+#include "rswitch_serdes.h"
+
+void rswitch_serdes_write32(void __iomem *addr, u32 offs,  u32 bank, u32 data)
+{
+	iowrite32(bank, addr + RSWITCH_SERDES_BANK_SELECT);
+	iowrite32(data, addr + offs);
+}
+
+u32 rswitch_serdes_read32(void __iomem *addr, u32 offs,  u32 bank)
+{
+	iowrite32(bank, addr + RSWITCH_SERDES_BANK_SELECT);
+
+	return ioread32(addr + offs);
+}
+
+static int rswitch_serdes_reg_wait(void __iomem *addr, u32 offs, u32 bank, u32 mask, u32 expected)
+{
+	int i;
+
+	iowrite32(bank, addr + RSWITCH_SERDES_BANK_SELECT);
+	mdelay(1);
+
+	for (i = 0; i < RSWITCH_TIMEOUT_US; i++) {
+		if ((ioread32(addr + offs) & mask) == expected)
+			return 0;
+		udelay(1);
+	}
+
+	return -ETIMEDOUT;
+}
+
+static int rswitch_serdes_common_init_ram(void __iomem *addr, void __iomem *addr0)
+{
+	int ret;
+
+	ret = rswitch_serdes_reg_wait(addr0, 0x026c, 0x180, BIT(0), 0x01);
+	if (ret)
+		return ret;
+
+	rswitch_serdes_write32(addr, 0x026c, 0x180, 0x03);
+
+	return ret;
+}
+
+static int rswitch_serdes_common_setting(void __iomem *addr0,
+					 enum rswitch_serdes_mode mode)
+{
+	switch (mode) {
+	case SGMII:
+		rswitch_serdes_write32(addr0, 0x0244, 0x180, 0x97);
+		rswitch_serdes_write32(addr0, 0x01d0, 0x180, 0x60);
+		rswitch_serdes_write32(addr0, 0x01d8, 0x180, 0x2200);
+		rswitch_serdes_write32(addr0, 0x01d4, 0x180, 0);
+		rswitch_serdes_write32(addr0, 0x01e0, 0x180, 0x3d);
+		break;
+	default:
+		return -EOPNOTSUPP;
+	}
+
+	return 0;
+}
+
+static int rswitch_serdes_chan_setting(void __iomem *addr,
+				       enum rswitch_serdes_mode mode)
+{
+	int ret;
+
+	switch (mode) {
+	case SGMII:
+		rswitch_serdes_write32(addr, 0x0000, 0x380, 0x2000);
+		rswitch_serdes_write32(addr, 0x01c0, 0x180, 0x11);
+		rswitch_serdes_write32(addr, 0x0248, 0x180, 0x540);
+		rswitch_serdes_write32(addr, 0x0258, 0x180, 0x15);
+		rswitch_serdes_write32(addr, 0x0144, 0x180, 0x100);
+		rswitch_serdes_write32(addr, 0x01a0, 0x180, 0);
+		rswitch_serdes_write32(addr, 0x00d0, 0x180, 0x02);
+		rswitch_serdes_write32(addr, 0x0150, 0x180, 0x03);
+		rswitch_serdes_write32(addr, 0x00c8, 0x180, 0x100);
+		rswitch_serdes_write32(addr, 0x0148, 0x180, 0x100);
+		rswitch_serdes_write32(addr, 0x0174, 0x180, 0);
+		rswitch_serdes_write32(addr, 0x0160, 0x180, 0x07);
+		rswitch_serdes_write32(addr, 0x01ac, 0x180, 0);
+		rswitch_serdes_write32(addr, 0x00c4, 0x180, 0x310);
+		rswitch_serdes_write32(addr, 0x00c8, 0x380, 0x101);
+		ret = rswitch_serdes_reg_wait(addr, 0x00c8, 0x180, BIT(0), 0);
+		if (ret)
+			return ret;
+
+		rswitch_serdes_write32(addr, 0x0148, 0x180, 0x101);
+		ret = rswitch_serdes_reg_wait(addr, 0x0148, 0x180, BIT(0), 0);
+		if (ret)
+			return ret;
+
+		rswitch_serdes_write32(addr, 0x00c4, 0x180, 0x1310);
+		rswitch_serdes_write32(addr, 0x00d8, 0x180, 0x1800);
+		rswitch_serdes_write32(addr, 0x00dc, 0x180, 0);
+		rswitch_serdes_write32(addr, 0x001c, 0x300, 0x01);
+		rswitch_serdes_write32(addr, 0x0000, 0x380, 0x2100);
+		ret = rswitch_serdes_reg_wait(addr, 0x0000, 0x380, BIT(8), 0);
+		if (ret)
+			return ret;
+		break;
+	default:
+		return -EOPNOTSUPP;
+	}
+
+	return 0;
+}
+
+static int rswitch_serdes_set_speed(void __iomem *addr,
+				    enum rswitch_serdes_mode mode, int speed)
+{
+	switch (mode) {
+	case SGMII:
+		if (speed == 1000)
+			rswitch_serdes_write32(addr, 0x0000, 0x1f00, 0x140);
+		else if (speed == 100)
+			rswitch_serdes_write32(addr, 0x0000, 0x1f00, 0x2100);
+		break;
+	default:
+		return -EOPNOTSUPP;
+	}
+
+	return 0;
+}
+
+int rswitch_serdes_init(void __iomem *addr, void __iomem *addr0,
+			phy_interface_t phy_interface, int speed)
+{
+	int ret;
+	enum rswitch_serdes_mode mode;
+
+	switch (phy_interface) {
+	case PHY_INTERFACE_MODE_SGMII:
+		mode = SGMII;
+		break;
+	default:
+		return -EOPNOTSUPP;
+	}
+
+	ret = rswitch_serdes_common_init_ram(addr, addr0);
+	if (ret)
+		return ret;
+
+	ret = rswitch_serdes_reg_wait(addr, 0x0000, 0x300, BIT(15), 0);
+	if (ret)
+		return ret;
+
+	rswitch_serdes_write32(addr, 0x03d4, 0x380, 0x443);
+
+	ret = rswitch_serdes_common_setting(addr0, mode);
+	if (ret)
+		return ret;
+
+	rswitch_serdes_write32(addr, 0x03d0, 0x380, 0x01);
+
+	rswitch_serdes_write32(addr0, 0x0000, 0x380, 0x8000);
+
+	ret = rswitch_serdes_common_init_ram(addr, addr0);
+	if (ret)
+		return ret;
+
+	ret = rswitch_serdes_reg_wait(addr0, 0x0000, 0x380, BIT(15), 0);
+	if (ret)
+		return ret;
+
+	ret = rswitch_serdes_chan_setting(addr, mode);
+	if (ret)
+		return ret;
+
+	ret = rswitch_serdes_set_speed(addr, mode, speed);
+	if (ret)
+		return ret;
+
+	rswitch_serdes_write32(addr, 0x03c0, 0x380, 0);
+	rswitch_serdes_write32(addr, 0x03d0, 0x380, 0);
+
+	return rswitch_serdes_reg_wait(addr, 0x0004, 0x300, BIT(2), BIT(2));
+}
diff --git a/drivers/net/ethernet/renesas/rswitch_serdes.h b/drivers/net/ethernet/renesas/rswitch_serdes.h
new file mode 100644
index 000000000000..845fd53edcf9
--- /dev/null
+++ b/drivers/net/ethernet/renesas/rswitch_serdes.h
@@ -0,0 +1,16 @@ 
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Renesas Ethernet Serdes device driver
+ *
+ * Copyright (C) 2022 Renesas Electronics Corporation
+ */
+
+#ifndef __RSWITCH_SERDES_H__
+#define __RSWITCH_SERDES_H__
+
+#define RSWITCH_SERDES_OFFSET                   0x0400
+#define RSWITCH_SERDES_BANK_SELECT              0x03fc
+
+int rswitch_serdes_init(void __iomem *addr, void __iomem *addr0,
+			phy_interface_t phy_interface, int speed);
+
+#endif /* #ifndef __RSWITCH_SERDES_H__ */