| // SPDX-License-Identifier: GPL-2.0 |
| |
| /* Renesas Ethernet-TSN device driver |
| * |
| * Copyright (C) 2022 Renesas Electronics Corporation |
| * Copyright (C) 2023 Niklas Söderlund <niklas.soderlund@ragnatech.se> |
| */ |
| |
| #include <linux/clk.h> |
| #include <linux/dma-mapping.h> |
| #include <linux/etherdevice.h> |
| #include <linux/ethtool.h> |
| #include <linux/module.h> |
| #include <linux/net_tstamp.h> |
| #include <linux/of.h> |
| #include <linux/of_mdio.h> |
| #include <linux/of_net.h> |
| #include <linux/phy.h> |
| #include <linux/platform_device.h> |
| #include <linux/pm_runtime.h> |
| #include <linux/reset.h> |
| #include <linux/spinlock.h> |
| |
| #include "rtsn.h" |
| #include "rcar_gen4_ptp.h" |
| |
| struct rtsn_private { |
| struct net_device *ndev; |
| struct platform_device *pdev; |
| void __iomem *base; |
| struct rcar_gen4_ptp_private *ptp_priv; |
| struct clk *clk; |
| struct reset_control *reset; |
| |
| u32 num_tx_ring; |
| u32 num_rx_ring; |
| u32 tx_desc_bat_size; |
| dma_addr_t tx_desc_bat_dma; |
| struct rtsn_desc *tx_desc_bat; |
| u32 rx_desc_bat_size; |
| dma_addr_t rx_desc_bat_dma; |
| struct rtsn_desc *rx_desc_bat; |
| dma_addr_t tx_desc_dma; |
| dma_addr_t rx_desc_dma; |
| struct rtsn_ext_desc *tx_ring; |
| struct rtsn_ext_ts_desc *rx_ring; |
| struct sk_buff **tx_skb; |
| struct sk_buff **rx_skb; |
| spinlock_t lock; /* Register access lock */ |
| u32 cur_tx; |
| u32 dirty_tx; |
| u32 cur_rx; |
| u32 dirty_rx; |
| u8 ts_tag; |
| struct napi_struct napi; |
| struct rtnl_link_stats64 stats; |
| |
| struct mii_bus *mii; |
| phy_interface_t iface; |
| int link; |
| int speed; |
| |
| int tx_data_irq; |
| int rx_data_irq; |
| }; |
| |
| static u32 rtsn_read(struct rtsn_private *priv, enum rtsn_reg reg) |
| { |
| return ioread32(priv->base + reg); |
| } |
| |
| static void rtsn_write(struct rtsn_private *priv, enum rtsn_reg reg, u32 data) |
| { |
| iowrite32(data, priv->base + reg); |
| } |
| |
| static void rtsn_modify(struct rtsn_private *priv, enum rtsn_reg reg, |
| u32 clear, u32 set) |
| { |
| rtsn_write(priv, reg, (rtsn_read(priv, reg) & ~clear) | set); |
| } |
| |
| static int rtsn_reg_wait(struct rtsn_private *priv, enum rtsn_reg reg, |
| u32 mask, u32 expected) |
| { |
| u32 val; |
| |
| return readl_poll_timeout(priv->base + reg, val, |
| (val & mask) == expected, |
| RTSN_INTERVAL_US, RTSN_TIMEOUT_US); |
| } |
| |
| static void rtsn_ctrl_data_irq(struct rtsn_private *priv, bool enable) |
| { |
| if (enable) { |
| rtsn_write(priv, TDIE0, TDIE_TDID_TDX(TX_CHAIN_IDX)); |
| rtsn_write(priv, RDIE0, RDIE_RDID_RDX(RX_CHAIN_IDX)); |
| } else { |
| rtsn_write(priv, TDID0, TDIE_TDID_TDX(TX_CHAIN_IDX)); |
| rtsn_write(priv, RDID0, RDIE_RDID_RDX(RX_CHAIN_IDX)); |
| } |
| } |
| |
| static void rtsn_get_timestamp(struct rtsn_private *priv, struct timespec64 *ts) |
| { |
| struct rcar_gen4_ptp_private *ptp_priv = priv->ptp_priv; |
| |
| ptp_priv->info.gettime64(&ptp_priv->info, ts); |
| } |
| |
| static int rtsn_tx_free(struct net_device *ndev, bool free_txed_only) |
| { |
| struct rtsn_private *priv = netdev_priv(ndev); |
| struct rtsn_ext_desc *desc; |
| struct sk_buff *skb; |
| int free_num = 0; |
| int entry, size; |
| |
| for (; priv->cur_tx - priv->dirty_tx > 0; priv->dirty_tx++) { |
| entry = priv->dirty_tx % priv->num_tx_ring; |
| desc = &priv->tx_ring[entry]; |
| if (free_txed_only && (desc->die_dt & DT_MASK) != DT_FEMPTY) |
| break; |
| |
| dma_rmb(); |
| size = le16_to_cpu(desc->info_ds) & TX_DS; |
| skb = priv->tx_skb[entry]; |
| if (skb) { |
| if (skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP) { |
| struct skb_shared_hwtstamps shhwtstamps; |
| struct timespec64 ts; |
| |
| rtsn_get_timestamp(priv, &ts); |
| memset(&shhwtstamps, 0, sizeof(shhwtstamps)); |
| shhwtstamps.hwtstamp = timespec64_to_ktime(ts); |
| skb_tstamp_tx(skb, &shhwtstamps); |
| } |
| dma_unmap_single(ndev->dev.parent, |
| le32_to_cpu(desc->dptr), |
| size, DMA_TO_DEVICE); |
| dev_kfree_skb_any(priv->tx_skb[entry]); |
| free_num++; |
| |
| priv->stats.tx_packets++; |
| priv->stats.tx_bytes += size; |
| } |
| |
| desc->die_dt = DT_EEMPTY; |
| } |
| |
| desc = &priv->tx_ring[priv->num_tx_ring]; |
| desc->die_dt = DT_LINK; |
| |
| return free_num; |
| } |
| |
| static int rtsn_rx(struct net_device *ndev, int budget) |
| { |
| struct rtsn_private *priv = netdev_priv(ndev); |
| unsigned int ndescriptors; |
| unsigned int rx_packets; |
| unsigned int i; |
| bool get_ts; |
| |
| get_ts = priv->ptp_priv->tstamp_rx_ctrl & |
| RCAR_GEN4_RXTSTAMP_TYPE_V2_L2_EVENT; |
| |
| ndescriptors = priv->dirty_rx + priv->num_rx_ring - priv->cur_rx; |
| rx_packets = 0; |
| for (i = 0; i < ndescriptors; i++) { |
| const unsigned int entry = priv->cur_rx % priv->num_rx_ring; |
| struct rtsn_ext_ts_desc *desc = &priv->rx_ring[entry]; |
| struct sk_buff *skb; |
| dma_addr_t dma_addr; |
| u16 pkt_len; |
| |
| /* Stop processing descriptors if budget is consumed. */ |
| if (rx_packets >= budget) |
| break; |
| |
| /* Stop processing descriptors on first empty. */ |
| if ((desc->die_dt & DT_MASK) == DT_FEMPTY) |
| break; |
| |
| dma_rmb(); |
| pkt_len = le16_to_cpu(desc->info_ds) & RX_DS; |
| |
| skb = priv->rx_skb[entry]; |
| priv->rx_skb[entry] = NULL; |
| dma_addr = le32_to_cpu(desc->dptr); |
| dma_unmap_single(ndev->dev.parent, dma_addr, PKT_BUF_SZ, |
| DMA_FROM_DEVICE); |
| |
| /* Get timestamp if enabled. */ |
| if (get_ts) { |
| struct skb_shared_hwtstamps *shhwtstamps; |
| struct timespec64 ts; |
| |
| shhwtstamps = skb_hwtstamps(skb); |
| memset(shhwtstamps, 0, sizeof(*shhwtstamps)); |
| |
| ts.tv_sec = (u64)le32_to_cpu(desc->ts_sec); |
| ts.tv_nsec = le32_to_cpu(desc->ts_nsec & cpu_to_le32(0x3fffffff)); |
| |
| shhwtstamps->hwtstamp = timespec64_to_ktime(ts); |
| } |
| |
| skb_put(skb, pkt_len); |
| skb->protocol = eth_type_trans(skb, ndev); |
| napi_gro_receive(&priv->napi, skb); |
| |
| /* Update statistics. */ |
| priv->stats.rx_packets++; |
| priv->stats.rx_bytes += pkt_len; |
| |
| /* Update counters. */ |
| priv->cur_rx++; |
| rx_packets++; |
| } |
| |
| /* Refill the RX ring buffers */ |
| for (; priv->cur_rx - priv->dirty_rx > 0; priv->dirty_rx++) { |
| const unsigned int entry = priv->dirty_rx % priv->num_rx_ring; |
| struct rtsn_ext_ts_desc *desc = &priv->rx_ring[entry]; |
| struct sk_buff *skb; |
| dma_addr_t dma_addr; |
| |
| desc->info_ds = cpu_to_le16(PKT_BUF_SZ); |
| |
| if (!priv->rx_skb[entry]) { |
| skb = napi_alloc_skb(&priv->napi, |
| PKT_BUF_SZ + RTSN_ALIGN - 1); |
| if (!skb) |
| break; |
| skb_reserve(skb, NET_IP_ALIGN); |
| dma_addr = dma_map_single(ndev->dev.parent, skb->data, |
| le16_to_cpu(desc->info_ds), |
| DMA_FROM_DEVICE); |
| if (dma_mapping_error(ndev->dev.parent, dma_addr)) |
| desc->info_ds = cpu_to_le16(0); |
| desc->dptr = cpu_to_le32(dma_addr); |
| skb_checksum_none_assert(skb); |
| priv->rx_skb[entry] = skb; |
| } |
| |
| dma_wmb(); |
| desc->die_dt = DT_FEMPTY | D_DIE; |
| } |
| |
| priv->rx_ring[priv->num_rx_ring].die_dt = DT_LINK; |
| |
| return rx_packets; |
| } |
| |
| static int rtsn_poll(struct napi_struct *napi, int budget) |
| { |
| struct rtsn_private *priv; |
| struct net_device *ndev; |
| unsigned long flags; |
| int work_done; |
| |
| ndev = napi->dev; |
| priv = netdev_priv(ndev); |
| |
| /* Processing RX Descriptor Ring */ |
| work_done = rtsn_rx(ndev, budget); |
| |
| /* Processing TX Descriptor Ring */ |
| spin_lock_irqsave(&priv->lock, flags); |
| rtsn_tx_free(ndev, true); |
| netif_wake_subqueue(ndev, 0); |
| spin_unlock_irqrestore(&priv->lock, flags); |
| |
| /* Re-enable TX/RX interrupts */ |
| if (work_done < budget && napi_complete_done(napi, work_done)) { |
| spin_lock_irqsave(&priv->lock, flags); |
| rtsn_ctrl_data_irq(priv, true); |
| spin_unlock_irqrestore(&priv->lock, flags); |
| } |
| |
| return work_done; |
| } |
| |
| static int rtsn_desc_alloc(struct rtsn_private *priv) |
| { |
| struct device *dev = &priv->pdev->dev; |
| unsigned int i; |
| |
| priv->tx_desc_bat_size = sizeof(struct rtsn_desc) * TX_NUM_CHAINS; |
| priv->tx_desc_bat = dma_alloc_coherent(dev, priv->tx_desc_bat_size, |
| &priv->tx_desc_bat_dma, |
| GFP_KERNEL); |
| |
| if (!priv->tx_desc_bat) |
| return -ENOMEM; |
| |
| for (i = 0; i < TX_NUM_CHAINS; i++) |
| priv->tx_desc_bat[i].die_dt = DT_EOS; |
| |
| priv->rx_desc_bat_size = sizeof(struct rtsn_desc) * RX_NUM_CHAINS; |
| priv->rx_desc_bat = dma_alloc_coherent(dev, priv->rx_desc_bat_size, |
| &priv->rx_desc_bat_dma, |
| GFP_KERNEL); |
| |
| if (!priv->rx_desc_bat) |
| return -ENOMEM; |
| |
| for (i = 0; i < RX_NUM_CHAINS; i++) |
| priv->rx_desc_bat[i].die_dt = DT_EOS; |
| |
| return 0; |
| } |
| |
| static void rtsn_desc_free(struct rtsn_private *priv) |
| { |
| if (priv->tx_desc_bat) |
| dma_free_coherent(&priv->pdev->dev, priv->tx_desc_bat_size, |
| priv->tx_desc_bat, priv->tx_desc_bat_dma); |
| priv->tx_desc_bat = NULL; |
| |
| if (priv->rx_desc_bat) |
| dma_free_coherent(&priv->pdev->dev, priv->rx_desc_bat_size, |
| priv->rx_desc_bat, priv->rx_desc_bat_dma); |
| priv->rx_desc_bat = NULL; |
| } |
| |
| static void rtsn_chain_free(struct rtsn_private *priv) |
| { |
| struct device *dev = &priv->pdev->dev; |
| |
| dma_free_coherent(dev, |
| sizeof(struct rtsn_ext_desc) * (priv->num_tx_ring + 1), |
| priv->tx_ring, priv->tx_desc_dma); |
| priv->tx_ring = NULL; |
| |
| dma_free_coherent(dev, |
| sizeof(struct rtsn_ext_ts_desc) * (priv->num_rx_ring + 1), |
| priv->rx_ring, priv->rx_desc_dma); |
| priv->rx_ring = NULL; |
| |
| kfree(priv->tx_skb); |
| priv->tx_skb = NULL; |
| |
| kfree(priv->rx_skb); |
| priv->rx_skb = NULL; |
| } |
| |
| static int rtsn_chain_init(struct rtsn_private *priv, int tx_size, int rx_size) |
| { |
| struct net_device *ndev = priv->ndev; |
| struct sk_buff *skb; |
| int i; |
| |
| priv->num_tx_ring = tx_size; |
| priv->num_rx_ring = rx_size; |
| |
| priv->tx_skb = kcalloc(tx_size, sizeof(*priv->tx_skb), GFP_KERNEL); |
| priv->rx_skb = kcalloc(rx_size, sizeof(*priv->rx_skb), GFP_KERNEL); |
| |
| if (!priv->rx_skb || !priv->tx_skb) |
| goto error; |
| |
| for (i = 0; i < rx_size; i++) { |
| skb = netdev_alloc_skb(ndev, PKT_BUF_SZ + RTSN_ALIGN - 1); |
| if (!skb) |
| goto error; |
| skb_reserve(skb, NET_IP_ALIGN); |
| priv->rx_skb[i] = skb; |
| } |
| |
| /* Allocate TX, RX descriptors */ |
| priv->tx_ring = dma_alloc_coherent(ndev->dev.parent, |
| sizeof(struct rtsn_ext_desc) * (tx_size + 1), |
| &priv->tx_desc_dma, GFP_KERNEL); |
| priv->rx_ring = dma_alloc_coherent(ndev->dev.parent, |
| sizeof(struct rtsn_ext_ts_desc) * (rx_size + 1), |
| &priv->rx_desc_dma, GFP_KERNEL); |
| |
| if (!priv->tx_ring || !priv->rx_ring) |
| goto error; |
| |
| return 0; |
| error: |
| rtsn_chain_free(priv); |
| |
| return -ENOMEM; |
| } |
| |
| static void rtsn_chain_format(struct rtsn_private *priv) |
| { |
| struct net_device *ndev = priv->ndev; |
| struct rtsn_ext_ts_desc *rx_desc; |
| struct rtsn_ext_desc *tx_desc; |
| struct rtsn_desc *bat_desc; |
| dma_addr_t dma_addr; |
| unsigned int i; |
| |
| priv->cur_tx = 0; |
| priv->cur_rx = 0; |
| priv->dirty_rx = 0; |
| priv->dirty_tx = 0; |
| |
| /* TX */ |
| memset(priv->tx_ring, 0, sizeof(*tx_desc) * priv->num_tx_ring); |
| for (i = 0, tx_desc = priv->tx_ring; i < priv->num_tx_ring; i++, tx_desc++) |
| tx_desc->die_dt = DT_EEMPTY | D_DIE; |
| |
| tx_desc->dptr = cpu_to_le32((u32)priv->tx_desc_dma); |
| tx_desc->die_dt = DT_LINK; |
| |
| bat_desc = &priv->tx_desc_bat[TX_CHAIN_IDX]; |
| bat_desc->die_dt = DT_LINK; |
| bat_desc->dptr = cpu_to_le32((u32)priv->tx_desc_dma); |
| |
| /* RX */ |
| memset(priv->rx_ring, 0, sizeof(*rx_desc) * priv->num_rx_ring); |
| for (i = 0, rx_desc = priv->rx_ring; i < priv->num_rx_ring; i++, rx_desc++) { |
| dma_addr = dma_map_single(ndev->dev.parent, |
| priv->rx_skb[i]->data, PKT_BUF_SZ, |
| DMA_FROM_DEVICE); |
| if (!dma_mapping_error(ndev->dev.parent, dma_addr)) |
| rx_desc->info_ds = cpu_to_le16(PKT_BUF_SZ); |
| rx_desc->dptr = cpu_to_le32((u32)dma_addr); |
| rx_desc->die_dt = DT_FEMPTY | D_DIE; |
| } |
| rx_desc->dptr = cpu_to_le32((u32)priv->rx_desc_dma); |
| rx_desc->die_dt = DT_LINK; |
| |
| bat_desc = &priv->rx_desc_bat[RX_CHAIN_IDX]; |
| bat_desc->die_dt = DT_LINK; |
| bat_desc->dptr = cpu_to_le32((u32)priv->rx_desc_dma); |
| } |
| |
| static int rtsn_dmac_init(struct rtsn_private *priv) |
| { |
| int ret; |
| |
| ret = rtsn_chain_init(priv, TX_CHAIN_SIZE, RX_CHAIN_SIZE); |
| if (ret) |
| return ret; |
| |
| rtsn_chain_format(priv); |
| |
| return 0; |
| } |
| |
| static enum rtsn_mode rtsn_read_mode(struct rtsn_private *priv) |
| { |
| return (rtsn_read(priv, OSR) & OSR_OPS) >> 1; |
| } |
| |
| static int rtsn_wait_mode(struct rtsn_private *priv, enum rtsn_mode mode) |
| { |
| unsigned int i; |
| |
| /* Need to busy loop as mode changes can happen in atomic context. */ |
| for (i = 0; i < RTSN_TIMEOUT_US / RTSN_INTERVAL_US; i++) { |
| if (rtsn_read_mode(priv) == mode) |
| return 0; |
| |
| udelay(RTSN_INTERVAL_US); |
| } |
| |
| return -ETIMEDOUT; |
| } |
| |
| static int rtsn_change_mode(struct rtsn_private *priv, enum rtsn_mode mode) |
| { |
| int ret; |
| |
| rtsn_write(priv, OCR, mode); |
| ret = rtsn_wait_mode(priv, mode); |
| if (ret) |
| netdev_err(priv->ndev, "Failed to switch operation mode\n"); |
| return ret; |
| } |
| |
| static int rtsn_get_data_irq_status(struct rtsn_private *priv) |
| { |
| u32 val; |
| |
| val = rtsn_read(priv, TDIS0) | TDIS_TDS(TX_CHAIN_IDX); |
| val |= rtsn_read(priv, RDIS0) | RDIS_RDS(RX_CHAIN_IDX); |
| |
| return val; |
| } |
| |
| static irqreturn_t rtsn_irq(int irq, void *dev_id) |
| { |
| struct rtsn_private *priv = dev_id; |
| int ret = IRQ_NONE; |
| |
| spin_lock(&priv->lock); |
| |
| if (rtsn_get_data_irq_status(priv)) { |
| /* Clear TX/RX irq status */ |
| rtsn_write(priv, TDIS0, TDIS_TDS(TX_CHAIN_IDX)); |
| rtsn_write(priv, RDIS0, RDIS_RDS(RX_CHAIN_IDX)); |
| |
| if (napi_schedule_prep(&priv->napi)) { |
| /* Disable TX/RX interrupts */ |
| rtsn_ctrl_data_irq(priv, false); |
| |
| __napi_schedule(&priv->napi); |
| } |
| |
| ret = IRQ_HANDLED; |
| } |
| |
| spin_unlock(&priv->lock); |
| |
| return ret; |
| } |
| |
| static int rtsn_request_irq(unsigned int irq, irq_handler_t handler, |
| unsigned long flags, struct rtsn_private *priv, |
| const char *ch) |
| { |
| char *name; |
| int ret; |
| |
| name = devm_kasprintf(&priv->pdev->dev, GFP_KERNEL, "%s:%s", |
| priv->ndev->name, ch); |
| if (!name) |
| return -ENOMEM; |
| |
| ret = request_irq(irq, handler, flags, name, priv); |
| if (ret) |
| netdev_err(priv->ndev, "Cannot request IRQ %s\n", name); |
| |
| return ret; |
| } |
| |
| static void rtsn_free_irqs(struct rtsn_private *priv) |
| { |
| free_irq(priv->tx_data_irq, priv); |
| free_irq(priv->rx_data_irq, priv); |
| } |
| |
| static int rtsn_request_irqs(struct rtsn_private *priv) |
| { |
| int ret; |
| |
| priv->rx_data_irq = platform_get_irq_byname(priv->pdev, "rx"); |
| if (priv->rx_data_irq < 0) |
| return priv->rx_data_irq; |
| |
| priv->tx_data_irq = platform_get_irq_byname(priv->pdev, "tx"); |
| if (priv->tx_data_irq < 0) |
| return priv->tx_data_irq; |
| |
| ret = rtsn_request_irq(priv->tx_data_irq, rtsn_irq, 0, priv, "tx"); |
| if (ret) |
| return ret; |
| |
| ret = rtsn_request_irq(priv->rx_data_irq, rtsn_irq, 0, priv, "rx"); |
| if (ret) { |
| free_irq(priv->tx_data_irq, priv); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static int rtsn_reset(struct rtsn_private *priv) |
| { |
| reset_control_reset(priv->reset); |
| mdelay(1); |
| |
| return rtsn_wait_mode(priv, OCR_OPC_DISABLE); |
| } |
| |
| static int rtsn_axibmi_init(struct rtsn_private *priv) |
| { |
| int ret; |
| |
| ret = rtsn_reg_wait(priv, RR, RR_RST, RR_RST_COMPLETE); |
| if (ret) |
| return ret; |
| |
| /* Set AXIWC */ |
| rtsn_write(priv, AXIWC, AXIWC_DEFAULT); |
| |
| /* Set AXIRC */ |
| rtsn_write(priv, AXIRC, AXIRC_DEFAULT); |
| |
| /* TX Descriptor chain setting */ |
| rtsn_write(priv, TATLS0, TATLS0_TEDE | TATLS0_TATEN(TX_CHAIN_IDX)); |
| rtsn_write(priv, TATLS1, priv->tx_desc_bat_dma + TX_CHAIN_ADDR_OFFSET); |
| rtsn_write(priv, TATLR, TATLR_TATL); |
| |
| ret = rtsn_reg_wait(priv, TATLR, TATLR_TATL, 0); |
| if (ret) |
| return ret; |
| |
| /* RX Descriptor chain setting */ |
| rtsn_write(priv, RATLS0, |
| RATLS0_RETS | RATLS0_REDE | RATLS0_RATEN(RX_CHAIN_IDX)); |
| rtsn_write(priv, RATLS1, priv->rx_desc_bat_dma + RX_CHAIN_ADDR_OFFSET); |
| rtsn_write(priv, RATLR, RATLR_RATL); |
| |
| ret = rtsn_reg_wait(priv, RATLR, RATLR_RATL, 0); |
| if (ret) |
| return ret; |
| |
| /* Enable TX/RX interrupts */ |
| rtsn_ctrl_data_irq(priv, true); |
| |
| return 0; |
| } |
| |
| static void rtsn_mhd_init(struct rtsn_private *priv) |
| { |
| /* TX General setting */ |
| rtsn_write(priv, TGC1, TGC1_STTV_DEFAULT | TGC1_TQTM_SFM); |
| rtsn_write(priv, TMS0, TMS_MFS_MAX); |
| |
| /* RX Filter IP */ |
| rtsn_write(priv, CFCR0, CFCR_SDID(RX_CHAIN_IDX)); |
| rtsn_write(priv, FMSCR, FMSCR_FMSIE(RX_CHAIN_IDX)); |
| } |
| |
| static int rtsn_get_phy_params(struct rtsn_private *priv) |
| { |
| int ret; |
| |
| ret = of_get_phy_mode(priv->pdev->dev.of_node, &priv->iface); |
| if (ret) |
| return ret; |
| |
| switch (priv->iface) { |
| case PHY_INTERFACE_MODE_MII: |
| priv->speed = 100; |
| break; |
| case PHY_INTERFACE_MODE_RGMII: |
| case PHY_INTERFACE_MODE_RGMII_ID: |
| case PHY_INTERFACE_MODE_RGMII_RXID: |
| case PHY_INTERFACE_MODE_RGMII_TXID: |
| priv->speed = 1000; |
| break; |
| default: |
| return -EOPNOTSUPP; |
| } |
| |
| return 0; |
| } |
| |
| static void rtsn_set_phy_interface(struct rtsn_private *priv) |
| { |
| u32 val; |
| |
| switch (priv->iface) { |
| case PHY_INTERFACE_MODE_MII: |
| val = MPIC_PIS_MII; |
| break; |
| case PHY_INTERFACE_MODE_RGMII: |
| case PHY_INTERFACE_MODE_RGMII_ID: |
| case PHY_INTERFACE_MODE_RGMII_RXID: |
| case PHY_INTERFACE_MODE_RGMII_TXID: |
| val = MPIC_PIS_GMII; |
| break; |
| default: |
| return; |
| } |
| |
| rtsn_modify(priv, MPIC, MPIC_PIS_MASK, val); |
| } |
| |
| static void rtsn_set_rate(struct rtsn_private *priv) |
| { |
| u32 val; |
| |
| switch (priv->speed) { |
| case 10: |
| val = MPIC_LSC_10M; |
| break; |
| case 100: |
| val = MPIC_LSC_100M; |
| break; |
| case 1000: |
| val = MPIC_LSC_1G; |
| break; |
| default: |
| return; |
| } |
| |
| rtsn_modify(priv, MPIC, MPIC_LSC_MASK, val); |
| } |
| |
| static int rtsn_rmac_init(struct rtsn_private *priv) |
| { |
| const u8 *mac_addr = priv->ndev->dev_addr; |
| int ret; |
| |
| /* Set MAC address */ |
| rtsn_write(priv, MRMAC0, (mac_addr[0] << 8) | mac_addr[1]); |
| rtsn_write(priv, MRMAC1, (mac_addr[2] << 24) | (mac_addr[3] << 16) | |
| (mac_addr[4] << 8) | mac_addr[5]); |
| |
| /* Set xMII type */ |
| rtsn_set_phy_interface(priv); |
| rtsn_set_rate(priv); |
| |
| /* Enable MII */ |
| rtsn_modify(priv, MPIC, MPIC_PSMCS_MASK | MPIC_PSMHT_MASK, |
| MPIC_PSMCS_DEFAULT | MPIC_PSMHT_DEFAULT); |
| |
| /* Link verification */ |
| rtsn_modify(priv, MLVC, MLVC_PLV, MLVC_PLV); |
| ret = rtsn_reg_wait(priv, MLVC, MLVC_PLV, 0); |
| if (ret) |
| return ret; |
| |
| return ret; |
| } |
| |
| static int rtsn_hw_init(struct rtsn_private *priv) |
| { |
| int ret; |
| |
| ret = rtsn_reset(priv); |
| if (ret) |
| return ret; |
| |
| /* Change to CONFIG mode */ |
| ret = rtsn_change_mode(priv, OCR_OPC_CONFIG); |
| if (ret) |
| return ret; |
| |
| ret = rtsn_axibmi_init(priv); |
| if (ret) |
| return ret; |
| |
| rtsn_mhd_init(priv); |
| |
| ret = rtsn_rmac_init(priv); |
| if (ret) |
| return ret; |
| |
| ret = rtsn_change_mode(priv, OCR_OPC_DISABLE); |
| if (ret) |
| return ret; |
| |
| /* Change to OPERATION mode */ |
| ret = rtsn_change_mode(priv, OCR_OPC_OPERATION); |
| |
| return ret; |
| } |
| |
| static int rtsn_mii_access(struct mii_bus *bus, bool read, int phyad, |
| int regad, u16 data) |
| { |
| struct rtsn_private *priv = bus->priv; |
| u32 val; |
| int ret; |
| |
| val = MPSM_PDA(phyad) | MPSM_PRA(regad) | MPSM_PSME; |
| |
| if (!read) |
| val |= MPSM_PSMAD | MPSM_PRD_SET(data); |
| |
| rtsn_write(priv, MPSM, val); |
| |
| ret = rtsn_reg_wait(priv, MPSM, MPSM_PSME, 0); |
| if (ret) |
| return ret; |
| |
| if (read) |
| ret = MPSM_PRD_GET(rtsn_read(priv, MPSM)); |
| |
| return ret; |
| } |
| |
| static int rtsn_mii_read(struct mii_bus *bus, int addr, int regnum) |
| { |
| return rtsn_mii_access(bus, true, addr, regnum, 0); |
| } |
| |
| static int rtsn_mii_write(struct mii_bus *bus, int addr, int regnum, u16 val) |
| { |
| return rtsn_mii_access(bus, false, addr, regnum, val); |
| } |
| |
| static int rtsn_mdio_alloc(struct rtsn_private *priv) |
| { |
| struct platform_device *pdev = priv->pdev; |
| struct device *dev = &pdev->dev; |
| struct device_node *mdio_node; |
| struct mii_bus *mii; |
| int ret; |
| |
| mii = mdiobus_alloc(); |
| if (!mii) |
| return -ENOMEM; |
| |
| mdio_node = of_get_child_by_name(dev->of_node, "mdio"); |
| if (!mdio_node) { |
| ret = -ENODEV; |
| goto out_free_bus; |
| } |
| |
| /* Enter config mode before registering the MDIO bus */ |
| ret = rtsn_reset(priv); |
| if (ret) |
| goto out_free_bus; |
| |
| ret = rtsn_change_mode(priv, OCR_OPC_CONFIG); |
| if (ret) |
| goto out_free_bus; |
| |
| rtsn_modify(priv, MPIC, MPIC_PSMCS_MASK | MPIC_PSMHT_MASK, |
| MPIC_PSMCS_DEFAULT | MPIC_PSMHT_DEFAULT); |
| |
| /* Register the MDIO bus */ |
| mii->name = "rtsn_mii"; |
| snprintf(mii->id, MII_BUS_ID_SIZE, "%s-%x", |
| pdev->name, pdev->id); |
| mii->priv = priv; |
| mii->read = rtsn_mii_read; |
| mii->write = rtsn_mii_write; |
| mii->parent = dev; |
| |
| ret = of_mdiobus_register(mii, mdio_node); |
| of_node_put(mdio_node); |
| if (ret) |
| goto out_free_bus; |
| |
| priv->mii = mii; |
| |
| return 0; |
| |
| out_free_bus: |
| mdiobus_free(mii); |
| return ret; |
| } |
| |
| static void rtsn_mdio_free(struct rtsn_private *priv) |
| { |
| mdiobus_unregister(priv->mii); |
| mdiobus_free(priv->mii); |
| priv->mii = NULL; |
| } |
| |
| static void rtsn_adjust_link(struct net_device *ndev) |
| { |
| struct rtsn_private *priv = netdev_priv(ndev); |
| struct phy_device *phydev = ndev->phydev; |
| bool new_state = false; |
| unsigned long flags; |
| |
| spin_lock_irqsave(&priv->lock, flags); |
| |
| if (phydev->link) { |
| if (phydev->speed != priv->speed) { |
| new_state = true; |
| priv->speed = phydev->speed; |
| } |
| |
| if (!priv->link) { |
| new_state = true; |
| priv->link = phydev->link; |
| } |
| } else if (priv->link) { |
| new_state = true; |
| priv->link = 0; |
| priv->speed = 0; |
| } |
| |
| if (new_state) { |
| /* Need to transition to CONFIG mode before reconfiguring and |
| * then back to the original mode. Any state change to/from |
| * CONFIG or OPERATION must go over DISABLED to stop Rx/Tx. |
| */ |
| enum rtsn_mode orgmode = rtsn_read_mode(priv); |
| |
| /* Transit to CONFIG */ |
| if (orgmode != OCR_OPC_CONFIG) { |
| if (orgmode != OCR_OPC_DISABLE && |
| rtsn_change_mode(priv, OCR_OPC_DISABLE)) |
| goto out; |
| if (rtsn_change_mode(priv, OCR_OPC_CONFIG)) |
| goto out; |
| } |
| |
| rtsn_set_rate(priv); |
| |
| /* Transition to original mode */ |
| if (orgmode != OCR_OPC_CONFIG) { |
| if (rtsn_change_mode(priv, OCR_OPC_DISABLE)) |
| goto out; |
| if (orgmode != OCR_OPC_DISABLE && |
| rtsn_change_mode(priv, orgmode)) |
| goto out; |
| } |
| } |
| out: |
| spin_unlock_irqrestore(&priv->lock, flags); |
| |
| if (new_state) |
| phy_print_status(phydev); |
| } |
| |
| static int rtsn_phy_init(struct rtsn_private *priv) |
| { |
| struct device_node *np = priv->ndev->dev.parent->of_node; |
| struct phy_device *phydev; |
| struct device_node *phy; |
| |
| priv->link = 0; |
| |
| phy = of_parse_phandle(np, "phy-handle", 0); |
| if (!phy) |
| return -ENOENT; |
| |
| phydev = of_phy_connect(priv->ndev, phy, rtsn_adjust_link, 0, |
| priv->iface); |
| of_node_put(phy); |
| if (!phydev) |
| return -ENOENT; |
| |
| /* Only support full-duplex mode */ |
| phy_remove_link_mode(phydev, ETHTOOL_LINK_MODE_10baseT_Half_BIT); |
| phy_remove_link_mode(phydev, ETHTOOL_LINK_MODE_100baseT_Half_BIT); |
| phy_remove_link_mode(phydev, ETHTOOL_LINK_MODE_1000baseT_Half_BIT); |
| |
| phy_attached_info(phydev); |
| |
| return 0; |
| } |
| |
| static void rtsn_phy_deinit(struct rtsn_private *priv) |
| { |
| phy_disconnect(priv->ndev->phydev); |
| priv->ndev->phydev = NULL; |
| } |
| |
| static int rtsn_init(struct rtsn_private *priv) |
| { |
| int ret; |
| |
| ret = rtsn_desc_alloc(priv); |
| if (ret) |
| return ret; |
| |
| ret = rtsn_dmac_init(priv); |
| if (ret) |
| goto error_free_desc; |
| |
| ret = rtsn_hw_init(priv); |
| if (ret) |
| goto error_free_chain; |
| |
| ret = rtsn_phy_init(priv); |
| if (ret) |
| goto error_free_chain; |
| |
| ret = rtsn_request_irqs(priv); |
| if (ret) |
| goto error_free_phy; |
| |
| return 0; |
| error_free_phy: |
| rtsn_phy_deinit(priv); |
| error_free_chain: |
| rtsn_chain_free(priv); |
| error_free_desc: |
| rtsn_desc_free(priv); |
| return ret; |
| } |
| |
| static void rtsn_deinit(struct rtsn_private *priv) |
| { |
| rtsn_free_irqs(priv); |
| rtsn_phy_deinit(priv); |
| rtsn_chain_free(priv); |
| rtsn_desc_free(priv); |
| } |
| |
| static void rtsn_parse_mac_address(struct device_node *np, |
| struct net_device *ndev) |
| { |
| struct rtsn_private *priv = netdev_priv(ndev); |
| u8 addr[ETH_ALEN]; |
| u32 mrmac0; |
| u32 mrmac1; |
| |
| /* Try to read address from Device Tree. */ |
| if (!of_get_mac_address(np, addr)) { |
| eth_hw_addr_set(ndev, addr); |
| return; |
| } |
| |
| /* Try to read address from device. */ |
| mrmac0 = rtsn_read(priv, MRMAC0); |
| mrmac1 = rtsn_read(priv, MRMAC1); |
| |
| addr[0] = (mrmac0 >> 8) & 0xff; |
| addr[1] = (mrmac0 >> 0) & 0xff; |
| addr[2] = (mrmac1 >> 24) & 0xff; |
| addr[3] = (mrmac1 >> 16) & 0xff; |
| addr[4] = (mrmac1 >> 8) & 0xff; |
| addr[5] = (mrmac1 >> 0) & 0xff; |
| |
| if (is_valid_ether_addr(addr)) { |
| eth_hw_addr_set(ndev, addr); |
| return; |
| } |
| |
| /* Fallback to a random address */ |
| eth_hw_addr_random(ndev); |
| } |
| |
| static int rtsn_open(struct net_device *ndev) |
| { |
| struct rtsn_private *priv = netdev_priv(ndev); |
| int ret; |
| |
| napi_enable(&priv->napi); |
| |
| ret = rtsn_init(priv); |
| if (ret) { |
| napi_disable(&priv->napi); |
| return ret; |
| } |
| |
| phy_start(ndev->phydev); |
| |
| netif_start_queue(ndev); |
| |
| return 0; |
| } |
| |
| static int rtsn_stop(struct net_device *ndev) |
| { |
| struct rtsn_private *priv = netdev_priv(ndev); |
| |
| phy_stop(priv->ndev->phydev); |
| napi_disable(&priv->napi); |
| rtsn_change_mode(priv, OCR_OPC_DISABLE); |
| rtsn_deinit(priv); |
| |
| return 0; |
| } |
| |
| static netdev_tx_t rtsn_start_xmit(struct sk_buff *skb, struct net_device *ndev) |
| { |
| struct rtsn_private *priv = netdev_priv(ndev); |
| struct rtsn_ext_desc *desc; |
| int ret = NETDEV_TX_OK; |
| unsigned long flags; |
| dma_addr_t dma_addr; |
| int entry; |
| |
| spin_lock_irqsave(&priv->lock, flags); |
| |
| /* Drop packet if it won't fit in a single descriptor. */ |
| if (skb->len >= TX_DS) { |
| priv->stats.tx_dropped++; |
| priv->stats.tx_errors++; |
| goto out; |
| } |
| |
| if (priv->cur_tx - priv->dirty_tx > priv->num_tx_ring) { |
| netif_stop_subqueue(ndev, 0); |
| ret = NETDEV_TX_BUSY; |
| goto out; |
| } |
| |
| if (skb_put_padto(skb, ETH_ZLEN)) |
| goto out; |
| |
| dma_addr = dma_map_single(ndev->dev.parent, skb->data, skb->len, |
| DMA_TO_DEVICE); |
| if (dma_mapping_error(ndev->dev.parent, dma_addr)) { |
| dev_kfree_skb_any(skb); |
| goto out; |
| } |
| |
| entry = priv->cur_tx % priv->num_tx_ring; |
| priv->tx_skb[entry] = skb; |
| desc = &priv->tx_ring[entry]; |
| desc->dptr = cpu_to_le32(dma_addr); |
| desc->info_ds = cpu_to_le16(skb->len); |
| desc->info1 = cpu_to_le64(skb->len); |
| |
| if (skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP) { |
| skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS; |
| priv->ts_tag++; |
| desc->info_ds |= cpu_to_le16(TXC); |
| desc->info = priv->ts_tag; |
| } |
| |
| skb_tx_timestamp(skb); |
| dma_wmb(); |
| |
| desc->die_dt = DT_FSINGLE | D_DIE; |
| priv->cur_tx++; |
| |
| /* Start xmit */ |
| rtsn_write(priv, TRCR0, BIT(TX_CHAIN_IDX)); |
| out: |
| spin_unlock_irqrestore(&priv->lock, flags); |
| return ret; |
| } |
| |
| static void rtsn_get_stats64(struct net_device *ndev, |
| struct rtnl_link_stats64 *storage) |
| { |
| struct rtsn_private *priv = netdev_priv(ndev); |
| *storage = priv->stats; |
| } |
| |
| static int rtsn_do_ioctl(struct net_device *ndev, struct ifreq *ifr, int cmd) |
| { |
| if (!netif_running(ndev)) |
| return -ENODEV; |
| |
| return phy_do_ioctl_running(ndev, ifr, cmd); |
| } |
| |
| static int rtsn_hwtstamp_get(struct net_device *ndev, |
| struct kernel_hwtstamp_config *config) |
| { |
| struct rcar_gen4_ptp_private *ptp_priv; |
| struct rtsn_private *priv; |
| |
| if (!netif_running(ndev)) |
| return -ENODEV; |
| |
| priv = netdev_priv(ndev); |
| ptp_priv = priv->ptp_priv; |
| |
| config->flags = 0; |
| |
| config->tx_type = |
| ptp_priv->tstamp_tx_ctrl ? HWTSTAMP_TX_ON : HWTSTAMP_TX_OFF; |
| |
| switch (ptp_priv->tstamp_rx_ctrl & RCAR_GEN4_RXTSTAMP_TYPE) { |
| case RCAR_GEN4_RXTSTAMP_TYPE_V2_L2_EVENT: |
| config->rx_filter = HWTSTAMP_FILTER_PTP_V2_L2_EVENT; |
| break; |
| case RCAR_GEN4_RXTSTAMP_TYPE_ALL: |
| config->rx_filter = HWTSTAMP_FILTER_ALL; |
| break; |
| default: |
| config->rx_filter = HWTSTAMP_FILTER_NONE; |
| break; |
| } |
| |
| return 0; |
| } |
| |
| static int rtsn_hwtstamp_set(struct net_device *ndev, |
| struct kernel_hwtstamp_config *config, |
| struct netlink_ext_ack *extack) |
| { |
| struct rcar_gen4_ptp_private *ptp_priv; |
| struct rtsn_private *priv; |
| u32 tstamp_rx_ctrl; |
| u32 tstamp_tx_ctrl; |
| |
| if (!netif_running(ndev)) |
| return -ENODEV; |
| |
| priv = netdev_priv(ndev); |
| ptp_priv = priv->ptp_priv; |
| |
| if (config->flags) |
| return -EINVAL; |
| |
| switch (config->tx_type) { |
| case HWTSTAMP_TX_OFF: |
| tstamp_tx_ctrl = 0; |
| break; |
| case HWTSTAMP_TX_ON: |
| tstamp_tx_ctrl = RCAR_GEN4_TXTSTAMP_ENABLED; |
| break; |
| default: |
| return -ERANGE; |
| } |
| |
| switch (config->rx_filter) { |
| case HWTSTAMP_FILTER_NONE: |
| tstamp_rx_ctrl = 0; |
| break; |
| case HWTSTAMP_FILTER_PTP_V2_L2_EVENT: |
| tstamp_rx_ctrl = RCAR_GEN4_RXTSTAMP_ENABLED | |
| RCAR_GEN4_RXTSTAMP_TYPE_V2_L2_EVENT; |
| break; |
| default: |
| config->rx_filter = HWTSTAMP_FILTER_ALL; |
| tstamp_rx_ctrl = RCAR_GEN4_RXTSTAMP_ENABLED | |
| RCAR_GEN4_RXTSTAMP_TYPE_ALL; |
| break; |
| } |
| |
| ptp_priv->tstamp_tx_ctrl = tstamp_tx_ctrl; |
| ptp_priv->tstamp_rx_ctrl = tstamp_rx_ctrl; |
| |
| return 0; |
| } |
| |
| static const struct net_device_ops rtsn_netdev_ops = { |
| .ndo_open = rtsn_open, |
| .ndo_stop = rtsn_stop, |
| .ndo_start_xmit = rtsn_start_xmit, |
| .ndo_get_stats64 = rtsn_get_stats64, |
| .ndo_eth_ioctl = rtsn_do_ioctl, |
| .ndo_validate_addr = eth_validate_addr, |
| .ndo_set_mac_address = eth_mac_addr, |
| .ndo_hwtstamp_set = rtsn_hwtstamp_set, |
| .ndo_hwtstamp_get = rtsn_hwtstamp_get, |
| }; |
| |
| static int rtsn_get_ts_info(struct net_device *ndev, |
| struct kernel_ethtool_ts_info *info) |
| { |
| struct rtsn_private *priv = netdev_priv(ndev); |
| |
| info->phc_index = ptp_clock_index(priv->ptp_priv->clock); |
| info->so_timestamping = SOF_TIMESTAMPING_TX_SOFTWARE | |
| SOF_TIMESTAMPING_TX_HARDWARE | |
| SOF_TIMESTAMPING_RX_HARDWARE | |
| SOF_TIMESTAMPING_RAW_HARDWARE; |
| info->tx_types = BIT(HWTSTAMP_TX_OFF) | BIT(HWTSTAMP_TX_ON); |
| info->rx_filters = BIT(HWTSTAMP_FILTER_NONE) | BIT(HWTSTAMP_FILTER_ALL); |
| |
| return 0; |
| } |
| |
| static const struct ethtool_ops rtsn_ethtool_ops = { |
| .nway_reset = phy_ethtool_nway_reset, |
| .get_link = ethtool_op_get_link, |
| .get_ts_info = rtsn_get_ts_info, |
| .get_link_ksettings = phy_ethtool_get_link_ksettings, |
| .set_link_ksettings = phy_ethtool_set_link_ksettings, |
| }; |
| |
| static const struct of_device_id rtsn_match_table[] = { |
| { .compatible = "renesas,r8a779g0-ethertsn", }, |
| { /* Sentinel */ } |
| }; |
| |
| MODULE_DEVICE_TABLE(of, rtsn_match_table); |
| |
| static int rtsn_probe(struct platform_device *pdev) |
| { |
| struct rtsn_private *priv; |
| struct net_device *ndev; |
| struct resource *res; |
| int ret; |
| |
| ndev = alloc_etherdev_mqs(sizeof(struct rtsn_private), TX_NUM_CHAINS, |
| RX_NUM_CHAINS); |
| if (!ndev) |
| return -ENOMEM; |
| |
| priv = netdev_priv(ndev); |
| priv->pdev = pdev; |
| priv->ndev = ndev; |
| priv->ptp_priv = rcar_gen4_ptp_alloc(pdev); |
| |
| spin_lock_init(&priv->lock); |
| platform_set_drvdata(pdev, priv); |
| |
| priv->clk = devm_clk_get(&pdev->dev, NULL); |
| if (IS_ERR(priv->clk)) { |
| ret = PTR_ERR(priv->clk); |
| goto error_free; |
| } |
| |
| priv->reset = devm_reset_control_get(&pdev->dev, NULL); |
| if (IS_ERR(priv->reset)) { |
| ret = PTR_ERR(priv->reset); |
| goto error_free; |
| } |
| |
| res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "tsnes"); |
| if (!res) { |
| dev_err(&pdev->dev, "Can't find tsnes resource\n"); |
| ret = -EINVAL; |
| goto error_free; |
| } |
| |
| priv->base = devm_ioremap_resource(&pdev->dev, res); |
| if (IS_ERR(priv->base)) { |
| ret = PTR_ERR(priv->base); |
| goto error_free; |
| } |
| |
| SET_NETDEV_DEV(ndev, &pdev->dev); |
| |
| ndev->features = NETIF_F_RXCSUM; |
| ndev->hw_features = NETIF_F_RXCSUM; |
| ndev->base_addr = res->start; |
| ndev->netdev_ops = &rtsn_netdev_ops; |
| ndev->ethtool_ops = &rtsn_ethtool_ops; |
| |
| res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "gptp"); |
| if (!res) { |
| dev_err(&pdev->dev, "Can't find gptp resource\n"); |
| ret = -EINVAL; |
| goto error_free; |
| } |
| |
| priv->ptp_priv->addr = devm_ioremap_resource(&pdev->dev, res); |
| if (IS_ERR(priv->ptp_priv->addr)) { |
| ret = PTR_ERR(priv->ptp_priv->addr); |
| goto error_free; |
| } |
| |
| ret = rtsn_get_phy_params(priv); |
| if (ret) |
| goto error_free; |
| |
| pm_runtime_enable(&pdev->dev); |
| pm_runtime_get_sync(&pdev->dev); |
| |
| netif_napi_add(ndev, &priv->napi, rtsn_poll); |
| |
| rtsn_parse_mac_address(pdev->dev.of_node, ndev); |
| |
| dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32)); |
| |
| device_set_wakeup_capable(&pdev->dev, 1); |
| |
| ret = rcar_gen4_ptp_register(priv->ptp_priv, RCAR_GEN4_PTP_REG_LAYOUT, |
| clk_get_rate(priv->clk)); |
| if (ret) |
| goto error_pm; |
| |
| ret = rtsn_mdio_alloc(priv); |
| if (ret) |
| goto error_ptp; |
| |
| ret = register_netdev(ndev); |
| if (ret) |
| goto error_mdio; |
| |
| netdev_info(ndev, "MAC address %pM\n", ndev->dev_addr); |
| |
| return 0; |
| |
| error_mdio: |
| rtsn_mdio_free(priv); |
| error_ptp: |
| rcar_gen4_ptp_unregister(priv->ptp_priv); |
| error_pm: |
| netif_napi_del(&priv->napi); |
| rtsn_change_mode(priv, OCR_OPC_DISABLE); |
| pm_runtime_put_sync(&pdev->dev); |
| pm_runtime_disable(&pdev->dev); |
| error_free: |
| free_netdev(ndev); |
| |
| return ret; |
| } |
| |
| static void rtsn_remove(struct platform_device *pdev) |
| { |
| struct rtsn_private *priv = platform_get_drvdata(pdev); |
| |
| unregister_netdev(priv->ndev); |
| rtsn_mdio_free(priv); |
| rcar_gen4_ptp_unregister(priv->ptp_priv); |
| rtsn_change_mode(priv, OCR_OPC_DISABLE); |
| netif_napi_del(&priv->napi); |
| |
| pm_runtime_put_sync(&pdev->dev); |
| pm_runtime_disable(&pdev->dev); |
| |
| free_netdev(priv->ndev); |
| } |
| |
| static struct platform_driver rtsn_driver = { |
| .probe = rtsn_probe, |
| .remove = rtsn_remove, |
| .driver = { |
| .name = "rtsn", |
| .of_match_table = rtsn_match_table, |
| } |
| }; |
| module_platform_driver(rtsn_driver); |
| |
| MODULE_AUTHOR("Phong Hoang, Niklas Söderlund"); |
| MODULE_DESCRIPTION("Renesas Ethernet-TSN device driver"); |
| MODULE_LICENSE("GPL"); |