diff --git a/drivers/net/ethernet/mscc/Makefile b/drivers/net/ethernet/mscc/Makefile
index 722c27694b21..d76a9b78b6ca 100644
--- a/drivers/net/ethernet/mscc/Makefile
+++ b/drivers/net/ethernet/mscc/Makefile
@@ -11,5 +11,6 @@ mscc_ocelot_switch_lib-y := \
mscc_ocelot_switch_lib-$(CONFIG_BRIDGE_MRP) += ocelot_mrp.o
obj-$(CONFIG_MSCC_OCELOT_SWITCH) += mscc_ocelot.o
mscc_ocelot-y := \
+ ocelot_fdma.o \
ocelot_vsc7514.o \
ocelot_net.o
diff --git a/drivers/net/ethernet/mscc/ocelot.h b/drivers/net/ethernet/mscc/ocelot.h
index 1952d6a1b98a..d06550c3610c 100644
--- a/drivers/net/ethernet/mscc/ocelot.h
+++ b/drivers/net/ethernet/mscc/ocelot.h
@@ -9,6 +9,7 @@
#define _MSCC_OCELOT_H_
#include <linux/bitops.h>
+#include <linux/dsa/ocelot.h>
#include <linux/etherdevice.h>
#include <linux/if_vlan.h>
#include <linux/net_tstamp.h>
@@ -45,6 +46,7 @@ struct ocelot_port_private {
struct phylink_config phylink_config;
u8 chip_port;
struct ocelot_port_tc tc;
+ u8 ifh[OCELOT_TAG_LEN];
};
struct ocelot_dump_ctx {diff --git a/drivers/net/ethernet/mscc/ocelot_fdma.c b/drivers/net/ethernet/mscc/ocelot_fdma.c
new file mode 100644
index 000000000000..4260bc1408d2
--- /dev/null
+++ b/drivers/net/ethernet/mscc/ocelot_fdma.c
@@ -0,0 +1,811 @@
+// SPDX-License-Identifier: (GPL-2.0 OR MIT)
+/*
+ * Microsemi SoCs FDMA driver
+ *
+ * Copyright (c) 2021 Microchip
+ */
+
+#include <linux/bitops.h>
+#include <linux/dmapool.h>
+#include <linux/dsa/ocelot.h>
+#include <linux/netdevice.h>
+#include <linux/of_platform.h>
+#include <linux/skbuff.h>
+
+#include "ocelot_fdma.h"
+#include "ocelot_qs.h"
+
+#define MSCC_FDMA_DCB_LLP(x) ((x) * 4 + 0x0)
+
+#define MSCC_FDMA_DCB_STAT_BLOCKO(x) (((x) << 20) & GENMASK(31, 20))
+#define MSCC_FDMA_DCB_STAT_BLOCKO_M GENMASK(31, 20)
+#define MSCC_FDMA_DCB_STAT_BLOCKO_X(x) (((x) & GENMASK(31, 20)) >> 20)
+#define MSCC_FDMA_DCB_STAT_PD BIT(19)
+#define MSCC_FDMA_DCB_STAT_ABORT BIT(18)
+#define MSCC_FDMA_DCB_STAT_EOF BIT(17)
+#define MSCC_FDMA_DCB_STAT_SOF BIT(16)
+#define MSCC_FDMA_DCB_STAT_BLOCKL_M GENMASK(15, 0)
+#define MSCC_FDMA_DCB_STAT_BLOCKL(x) ((x) & GENMASK(15, 0))
+
+#define MSCC_FDMA_CH_SAFE 0xcc
+
+#define MSCC_FDMA_CH_ACTIVATE 0xd0
+
+#define MSCC_FDMA_CH_DISABLE 0xd4
+
+#define MSCC_FDMA_EVT_ERR 0x164
+
+#define MSCC_FDMA_EVT_ERR_CODE 0x168
+
+#define MSCC_FDMA_INTR_LLP 0x16c
+
+#define MSCC_FDMA_INTR_LLP_ENA 0x170
+
+#define MSCC_FDMA_INTR_FRM 0x174
+
+#define MSCC_FDMA_INTR_FRM_ENA 0x178
+
+#define MSCC_FDMA_INTR_ENA 0x184
+
+#define MSCC_FDMA_INTR_IDENT 0x188
+
+#define MSCC_FDMA_INJ_CHAN 2
+#define MSCC_FDMA_XTR_CHAN 0
+
+#define FDMA_MAX_SKB 256
+#define FDMA_WEIGHT 32
+
+#define FDMA_RX_EXTRA_SIZE \
+ (OCELOT_TAG_LEN + ETH_FCS_LEN + ETH_HLEN + 4)
+
+struct ocelot_fdma_dcb_hw_v2 {
+ u32 llp;
+ u32 datap;
+ u32 datal;
+ u32 stat;
+};
+
+struct ocelot_fdma_dcb {
+ struct ocelot_fdma_dcb_hw_v2 hw;
+ struct list_head node;
+ struct sk_buff *skb;
+ dma_addr_t mapping;
+ size_t mapped_size;
+ dma_addr_t phys;
+};
+
+static int fdma_rx_compute_buffer_size(int mtu)
+{
+ return ALIGN(mtu + FDMA_RX_EXTRA_SIZE, 4);
+}
+
+static void fdma_writel(struct ocelot_fdma *fdma, u32 reg, u32 data)
+{
+ writel(data, fdma->base + reg);
+}
+
+static u32 fdma_readl(struct ocelot_fdma *fdma, u32 reg)
+{
+ return readl(fdma->base + reg);
+}
+
+static void fdma_activate_chan(struct ocelot_fdma *fdma,
+ struct ocelot_fdma_dcb *dcb, int chan)
+{
+ fdma_writel(fdma, MSCC_FDMA_DCB_LLP(chan), dcb->phys);
+ fdma_writel(fdma, MSCC_FDMA_CH_ACTIVATE, BIT(chan));
+}
+
+static void fdma_stop_channel(struct ocelot_fdma *fdma, int chan)
+{
+ u32 safe;
+
+ fdma_writel(fdma, MSCC_FDMA_CH_DISABLE, BIT(chan));
+ do {
+ safe = fdma_readl(fdma, MSCC_FDMA_CH_SAFE);
+ } while (!(safe & BIT(chan)));
+}
+
+static bool ocelot_fdma_dcb_set_data(struct ocelot_fdma *fdma,
+ struct ocelot_fdma_dcb *dcb, void *data,
+ size_t size, enum dma_data_direction dir)
+{
+ u32 offset;
+
+ dcb->mapped_size = size;
+ dcb->mapping = dma_map_single(fdma->dev, data, size, dir);
+ if (unlikely(dma_mapping_error(fdma->dev, dcb->mapping)))
+ return false;
+
+ offset = dcb->mapping & 0x3;
+
+ dcb->hw.llp = 0;
+ dcb->hw.datap = dcb->mapping & ~0x3;
+ dcb->hw.datal = ALIGN_DOWN(size - offset, 4);
+ dcb->hw.stat = MSCC_FDMA_DCB_STAT_BLOCKO(offset);
+
+ WARN_ON(!IS_ALIGNED(dcb->hw.datal, 4));
+
+ return true;
+}
+
+static bool ocelot_fdma_dcb_set_rx_skb(struct ocelot_fdma *fdma,
+ struct ocelot_fdma_dcb *dcb,
+ struct sk_buff *skb, size_t size)
+{
+ dcb->skb = skb;
+ return ocelot_fdma_dcb_set_data(fdma, dcb, skb->data, size,
+ DMA_FROM_DEVICE);
+}
+
+static bool ocelot_fdma_dcb_set_tx_skb(struct ocelot_fdma *fdma,
+ struct ocelot_fdma_dcb *dcb,
+ struct sk_buff *skb)
+{
+ if (!ocelot_fdma_dcb_set_data(fdma, dcb, skb->data, skb->len,
+ DMA_TO_DEVICE))
+ return false;
+
+ dcb->skb = skb;
+ dcb->hw.stat |= MSCC_FDMA_DCB_STAT_BLOCKL(skb->len);
+
+ return true;
+}
+
+static struct ocelot_fdma_dcb *fdma_dcb_alloc(struct ocelot_fdma *fdma)
+{
+ struct ocelot_fdma_dcb *dcb;
+ dma_addr_t phys;
+
+ dcb = dma_pool_zalloc(fdma->dcb_pool, GFP_KERNEL, &phys);
+ if (unlikely(!dcb))
+ return NULL;
+
+ dcb->phys = phys;
+
+ return dcb;
+}
+
+static struct net_device *fdma_get_port_netdev(struct ocelot_fdma *fdma,
+ int port_num)
+{
+ struct ocelot_port_private *port_priv;
+ struct ocelot *ocelot = fdma->ocelot;
+ struct ocelot_port *port;
+
+ if (port_num >= ocelot->num_phys_ports)
+ return NULL;
+
+ port = ocelot->ports[port_num];
+
+ if (!port)
+ return NULL;
+
+ port_priv = container_of(port, struct ocelot_port_private, port);
+
+ return port_priv->dev;
+}
+
+static bool ocelot_fdma_rx_process_skb(struct ocelot_fdma *fdma,
+ struct ocelot_fdma_dcb *dcb,
+ int budget)
+{
+ struct sk_buff *skb = dcb->skb;
+ struct net_device *ndev;
+ u64 src_port;
+ void *xfh;
+
+ dma_unmap_single(fdma->dev, dcb->mapping, dcb->mapped_size,
+ DMA_FROM_DEVICE);
+
+ xfh = skb->data;
+ ocelot_xfh_get_src_port(xfh, &src_port);
+
+ skb_put(skb, MSCC_FDMA_DCB_STAT_BLOCKL(dcb->hw.stat));
+ skb_pull(skb, OCELOT_TAG_LEN);
+
+ ndev = fdma_get_port_netdev(fdma, src_port);
+ if (unlikely(!ndev)) {
+ napi_consume_skb(dcb->skb, budget);
+ return false;
+ }
+
+ skb->dev = ndev;
+ skb->protocol = eth_type_trans(skb, skb->dev);
+ skb->dev->stats.rx_bytes += skb->len;
+ skb->dev->stats.rx_packets++;
+
+ netif_receive_skb(skb);
+
+ return true;
+}
+
+static void ocelot_fdma_rx_refill(struct ocelot_fdma *fdma)
+{
+ struct ocelot_fdma_dcb *dcb, *last_dcb;
+
+ WARN_ON(list_empty(&fdma->rx_sw));
+
+ dcb = list_first_entry(&fdma->rx_sw, struct ocelot_fdma_dcb, node);
+ /* Splice old hardware DCB list + new one */
+ if (!list_empty(&fdma->rx_hw)) {
+ last_dcb = list_last_entry(&fdma->rx_hw, struct ocelot_fdma_dcb,
+ node);
+ last_dcb->hw.llp = dcb->phys;
+ }
+
+ /* Move software list to hardware list */
+ list_splice_tail_init(&fdma->rx_sw, &fdma->rx_hw);
+
+ /* Finally reactivate the channel */
+ fdma_activate_chan(fdma, dcb, MSCC_FDMA_XTR_CHAN);
+}
+
+static void ocelot_fdma_list_add_dcb(struct list_head *list,
+ struct ocelot_fdma_dcb *dcb)
+{
+ struct ocelot_fdma_dcb *last_dcb;
+
+ if (!list_empty(list)) {
+ last_dcb = list_last_entry(list, struct ocelot_fdma_dcb, node);
+ last_dcb->hw.llp = dcb->phys;
+ }
+
+ list_add_tail(&dcb->node, list);
+}
+
+static bool ocelot_fdma_rx_add_dcb_sw(struct ocelot_fdma *fdma,
+ struct ocelot_fdma_dcb *dcb)
+{
+ struct sk_buff *new_skb;
+
+ /* Add DCB to end of list with new SKB */
+ new_skb = napi_alloc_skb(&fdma->napi, fdma->rx_buf_size);
+ if (unlikely(!new_skb)) {
+ pr_err("skb_alloc failed\n");
+ return false;
+ }
+
+ ocelot_fdma_dcb_set_rx_skb(fdma, dcb, new_skb, fdma->rx_buf_size);
+ ocelot_fdma_list_add_dcb(&fdma->rx_sw, dcb);
+
+ return true;
+}
+
+static bool ocelot_fdma_rx_get(struct ocelot_fdma *fdma, int budget)
+{
+ struct ocelot_fdma_dcb *dcb;
+ bool valid = true;
+ u32 stat;
+
+ dcb = list_first_entry_or_null(&fdma->rx_hw, struct ocelot_fdma_dcb,
+ node);
+ if (!dcb || MSCC_FDMA_DCB_STAT_BLOCKL(dcb->hw.stat) == 0)
+ return false;
+
+ list_del(&dcb->node);
+
+ stat = dcb->hw.stat;
+ if (stat & MSCC_FDMA_DCB_STAT_ABORT || stat & MSCC_FDMA_DCB_STAT_PD)
+ valid = false;
+
+ if (!(stat & MSCC_FDMA_DCB_STAT_SOF) ||
+ !(stat & MSCC_FDMA_DCB_STAT_EOF))
+ valid = false;
+
+ if (likely(valid)) {
+ if (!ocelot_fdma_rx_process_skb(fdma, dcb, budget))
+ pr_err("Process skb failed, stat %x\n", stat);
+ } else {
+ napi_consume_skb(dcb->skb, budget);
+ }
+
+ return ocelot_fdma_rx_add_dcb_sw(fdma, dcb);
+}
+
+static void ocelot_fdma_rx_check_stopped(struct ocelot_fdma *fdma)
+{
+ u32 llp = fdma_readl(fdma, MSCC_FDMA_DCB_LLP(MSCC_FDMA_XTR_CHAN));
+ /* LLP is non NULL, FDMA is still fetching packets */
+ if (llp)
+ return;
+
+ fdma_stop_channel(fdma, MSCC_FDMA_XTR_CHAN);
+ ocelot_fdma_rx_refill(fdma);
+}
+
+static void ocelot_fdma_tx_free_dcb(struct ocelot_fdma *fdma,
+ struct list_head *list)
+{
+ struct ocelot_fdma_dcb *dcb;
+
+ if (list_empty(list))
+ return;
+
+ /* Free all SKBs that have been used for TX */
+ list_for_each_entry(dcb, list, node) {
+ dma_unmap_single(fdma->dev, dcb->mapping, dcb->mapped_size,
+ DMA_TO_DEVICE);
+ dev_kfree_skb_any(dcb->skb);
+ dcb->skb = NULL;
+ }
+
+ /* All DCBs can now be given to free list */
+ spin_lock(&fdma->tx_free_lock);
+ list_splice_tail_init(list, &fdma->tx_free_dcb);
+ spin_unlock(&fdma->tx_free_lock);
+}
+
+/**
+ * ocelot_fdma_tx_cleanup - Free TX dcb that have been sent by the fdma
+ */
+static void ocelot_fdma_tx_cleanup(struct ocelot_fdma *fdma)
+{
+ struct list_head tx_done = LIST_HEAD_INIT(tx_done);
+ struct ocelot_fdma_dcb *dcb, *temp;
+
+ spin_lock(&fdma->tx_enqueue_lock);
+ if (list_empty(&fdma->tx_ongoing))
+ goto out_unlock;
+
+ /* TODO, use list_cut_before ? */
+ list_for_each_entry_safe(dcb, temp, &fdma->tx_ongoing, node) {
+ if (!(dcb->hw.stat & MSCC_FDMA_DCB_STAT_PD))
+ break;
+
+ list_move_tail(&dcb->node, &tx_done);
+ }
+
+out_unlock:
+ spin_unlock(&fdma->tx_enqueue_lock);
+
+ ocelot_fdma_tx_free_dcb(fdma, &tx_done);
+}
+
+void ocelot_fdma_tx_restart(struct ocelot_fdma *fdma)
+{
+ struct ocelot_fdma_dcb *dcb;
+ u32 safe;
+
+ spin_lock(&fdma->tx_enqueue_lock);
+
+ if (!list_empty(&fdma->tx_ongoing) || list_empty(&fdma->tx_queued))
+ goto out_unlock;
+
+ /* Ongoing list is empty, channel should be in safe mode */
+ do {
+ safe = fdma_readl(fdma, MSCC_FDMA_CH_SAFE);
+ } while (!(safe & BIT(MSCC_FDMA_INJ_CHAN)));
+
+ /* Move queued DCB to ongoing and restart the DMA */
+ list_splice_tail_init(&fdma->tx_queued, &fdma->tx_ongoing);
+ /* List can't be empty, no need to check */
+ dcb = list_first_entry(&fdma->tx_ongoing, struct ocelot_fdma_dcb, node);
+
+ fdma_activate_chan(fdma, dcb, MSCC_FDMA_INJ_CHAN);
+
+out_unlock:
+ spin_unlock(&fdma->tx_enqueue_lock);
+}
+
+static int ocelot_fdma_napi_poll(struct napi_struct *napi, int budget)
+{
+ struct ocelot_fdma *fdma = container_of(napi, struct ocelot_fdma, napi);
+ int work_done = 0;
+
+ ocelot_fdma_tx_cleanup(fdma);
+ ocelot_fdma_tx_restart(fdma);
+
+ while (work_done < budget) {
+ if (!ocelot_fdma_rx_get(fdma, budget))
+ break;
+
+ work_done++;
+ }
+
+ ocelot_fdma_rx_check_stopped(fdma);
+
+ if (work_done < budget) {
+ napi_complete(&fdma->napi);
+ fdma_writel(fdma, MSCC_FDMA_INTR_ENA,
+ BIT(MSCC_FDMA_INJ_CHAN) | BIT(MSCC_FDMA_XTR_CHAN));
+ }
+
+ return work_done;
+}
+
+static irqreturn_t ocelot_fdma_interrupt(int irq, void *dev_id)
+{
+ u32 ident, llp, frm, err, err_code;
+ struct ocelot_fdma *fdma = dev_id;
+
+ ident = fdma_readl(fdma, MSCC_FDMA_INTR_IDENT);
+ frm = fdma_readl(fdma, MSCC_FDMA_INTR_FRM);
+ llp = fdma_readl(fdma, MSCC_FDMA_INTR_LLP);
+
+ fdma_writel(fdma, MSCC_FDMA_INTR_LLP, llp & ident);
+ fdma_writel(fdma, MSCC_FDMA_INTR_FRM, frm & ident);
+ if (frm | llp) {
+ fdma_writel(fdma, MSCC_FDMA_INTR_ENA, 0);
+ napi_schedule(&fdma->napi);
+ }
+
+ err = fdma_readl(fdma, MSCC_FDMA_EVT_ERR);
+ if (unlikely(err)) {
+ err_code = fdma_readl(fdma, MSCC_FDMA_EVT_ERR_CODE);
+ dev_err_ratelimited(fdma->dev,
+ "Error ! chans mask: %#x, code: %#x\n",
+ err, err_code);
+
+ fdma_writel(fdma, MSCC_FDMA_EVT_ERR, err);
+ fdma_writel(fdma, MSCC_FDMA_EVT_ERR_CODE, err_code);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static struct ocelot_fdma_dcb *fdma_tx_get_dcb(struct ocelot_fdma *fdma)
+{
+ struct ocelot_fdma_dcb *dcb = NULL;
+
+ spin_lock_bh(&fdma->tx_free_lock);
+ dcb = list_first_entry_or_null(&fdma->tx_free_dcb,
+ struct ocelot_fdma_dcb, node);
+ if (dcb)
+ list_del(&dcb->node);
+
+ spin_unlock_bh(&fdma->tx_free_lock);
+
+ return dcb;
+}
+
+static void fdma_tx_set_ifh(void *ifh, u32 rew_op, struct sk_buff *skb)
+{
+ if (skb_vlan_tag_get(skb))
+ ocelot_ifh_set_vid_quirks(ifh, skb_vlan_tag_get(skb),
+ QUIRK_LITTLE_ENDIAN);
+ if (rew_op)
+ ocelot_ifh_set_rew_op_quirks(ifh, rew_op, QUIRK_LITTLE_ENDIAN);
+}
+
+int ocelot_fdma_inject_frame(struct ocelot_fdma *fdma, u32 rew_op,
+ struct sk_buff *skb, struct net_device *dev)
+{
+ struct ocelot_port_private *priv = netdev_priv(dev);
+ struct ocelot_fdma_dcb *dcb, *first_dcb, *last_dcb;
+ struct list_head dcbs = LIST_HEAD_INIT(dcbs);
+ struct sk_buff *new_skb;
+ u32 flags = 0;
+ void *ifh;
+ int i;
+
+ if (skb_headroom(skb) < OCELOT_TAG_LEN ||
+ skb_tailroom(skb) < ETH_FCS_LEN) {
+ new_skb = skb_copy_expand(skb, OCELOT_TAG_LEN, ETH_FCS_LEN,
+ GFP_ATOMIC);
+ dev_consume_skb_any(skb);
+ if (!new_skb)
+ return NETDEV_TX_OK;
+
+ skb = new_skb;
+ }
+
+ ifh = skb_push(skb, OCELOT_TAG_LEN);
+ memcpy(ifh, priv->ifh, OCELOT_TAG_LEN);
+ skb_put(skb, ETH_FCS_LEN);
+
+ fdma_tx_set_ifh(ifh, rew_op, skb);
+
+ first_dcb = fdma_tx_get_dcb(fdma);
+ if (unlikely(!first_dcb))
+ return NETDEV_TX_BUSY;
+
+ if (likely(skb_shinfo(skb)->nr_frags == 0))
+ flags = MSCC_FDMA_DCB_STAT_SOF | MSCC_FDMA_DCB_STAT_EOF;
+ else
+ flags = MSCC_FDMA_DCB_STAT_SOF;
+
+ if (!ocelot_fdma_dcb_set_tx_skb(fdma, first_dcb, skb))
+ goto abort_free_dcbs;
+
+ first_dcb->hw.stat |= flags;
+ ocelot_fdma_list_add_dcb(&dcbs, first_dcb);
+
+ for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
+ const skb_frag_t *frag = &skb_shinfo(skb)->frags[i];
+
+ flags = 0;
+ skb = netdev_alloc_skb(dev, skb_frag_size(frag) +
+ OCELOT_TAG_LEN + ETH_FCS_LEN);
+ if (!skb)
+ goto abort_free_dcbs;
+
+ dcb = fdma_tx_get_dcb(fdma);
+ if (!dcb) {
+ dev_kfree_skb_any(skb);
+ goto abort_free_dcbs;
+ }
+
+ /* Prepare SKB header and data */
+ ifh = skb_push(skb, OCELOT_TAG_LEN);
+ memcpy(ifh, priv->ifh, OCELOT_TAG_LEN);
+ fdma_tx_set_ifh(ifh, 0, skb);
+ skb_put(skb, ETH_FCS_LEN);
+
+ memcpy(skb->data + OCELOT_TAG_LEN, skb_frag_address(frag),
+ skb_frag_size(frag));
+
+ if (i == skb_shinfo(skb)->nr_frags - 1)
+ flags |= MSCC_FDMA_DCB_STAT_EOF;
+
+ if (ocelot_fdma_dcb_set_tx_skb(fdma, dcb, skb))
+ goto abort_free_dcbs;
+ dcb->hw.stat |= flags;
+ ocelot_fdma_list_add_dcb(&dcbs, dcb);
+ }
+
+ spin_lock_bh(&fdma->tx_enqueue_lock);
+
+ if (list_empty(&fdma->tx_ongoing)) {
+ list_splice_tail(&dcbs, &fdma->tx_ongoing);
+ fdma_activate_chan(fdma, first_dcb, MSCC_FDMA_INJ_CHAN);
+ } else {
+ if (!list_empty(&fdma->tx_queued)) {
+ last_dcb = list_last_entry(&fdma->tx_queued,
+ struct ocelot_fdma_dcb,
+ node);
+ last_dcb->hw.llp = first_dcb->phys;
+ }
+ list_splice_tail(&dcbs, &fdma->tx_queued);
+ }
+
+ spin_unlock_bh(&fdma->tx_enqueue_lock);
+ return NETDEV_TX_OK;
+
+abort_free_dcbs:
+ ocelot_fdma_tx_free_dcb(fdma, &dcbs);
+ return NETDEV_TX_OK;
+}
+
+static void fdma_free_skbs_list(struct ocelot_fdma *fdma,
+ struct list_head *list,
+ enum dma_data_direction dir)
+{
+ struct ocelot_fdma_dcb *dcb;
+
+ if (list_empty(list))
+ return;
+
+ list_for_each_entry(dcb, list, node) {
+ if (dcb->skb) {
+ dma_unmap_single(fdma->dev, dcb->mapping,
+ dcb->mapped_size, dir);
+ kfree_skb(dcb->skb);
+ }
+ }
+}
+
+int ocelot_fdma_change_mtu(struct net_device *dev, int new_mtu)
+{
+ struct ocelot_port_private *priv = netdev_priv(dev);
+ struct ocelot_port *port = &priv->port;
+ struct ocelot *ocelot = port->ocelot;
+ struct ocelot_fdma *fdma = ocelot->fdma;
+ struct ocelot_fdma_dcb *dcb, *dcb_temp;
+ struct list_head tmp = LIST_HEAD_INIT(tmp);
+ u8 port_num;
+
+ /* The FDMA RX list is shared amongst all the port, get the max MTU from
+ * all of them
+ */
+ for (port_num = 0; port_num < ocelot->num_phys_ports; port_num++) {
+ port = ocelot->ports[port_num];
+ if (!port)
+ continue;
+
+ priv = container_of(port, struct ocelot_port_private, port);
+
+ if (READ_ONCE(priv->dev->mtu) > new_mtu)
+ new_mtu = READ_ONCE(priv->dev->mtu);
+
+ /* All ports must be down to change the RX buffer length */
+ if (netif_running(priv->dev))
+ return -EBUSY;
+ }
+ priv = netdev_priv(dev);
+ fdma->rx_buf_size = fdma_rx_compute_buffer_size(new_mtu);
+
+ ocelot_port_set_maxlen(ocelot, priv->chip_port, fdma->rx_buf_size);
+
+ fdma_stop_channel(fdma, MSCC_FDMA_INJ_CHAN);
+
+ /* Discard all pending RX software and hardware descriptor */
+ fdma_free_skbs_list(fdma, &fdma->rx_hw, DMA_FROM_DEVICE);
+ fdma_free_skbs_list(fdma, &fdma->rx_sw, DMA_FROM_DEVICE);
+
+ /* Move all DCBs to a temporary list that will be injected in sw list */
+ if (!list_empty(&fdma->rx_hw))
+ list_splice_tail_init(&fdma->rx_hw, &tmp);
+ if (!list_empty(&fdma->rx_sw))
+ list_splice_tail_init(&fdma->rx_sw, &tmp);
+
+ list_for_each_entry_safe(dcb, dcb_temp, &tmp, node) {
+ list_del(&dcb->node);
+ ocelot_fdma_rx_add_dcb_sw(fdma, dcb);
+ }
+
+ ocelot_fdma_rx_refill(fdma);
+
+ return 0;
+}
+
+static int fdma_init_tx(struct ocelot_fdma *fdma)
+{
+ int i;
+ struct ocelot_fdma_dcb *dcb;
+
+ for (i = 0; i < FDMA_MAX_SKB; i++) {
+ dcb = fdma_dcb_alloc(fdma);
+ if (!dcb)
+ return -ENOMEM;
+
+ list_add_tail(&dcb->node, &fdma->tx_free_dcb);
+ }
+
+ return 0;
+}
+
+static int fdma_init_rx(struct ocelot_fdma *fdma)
+{
+ struct ocelot_port_private *port_priv;
+ struct ocelot *ocelot = fdma->ocelot;
+ struct ocelot_fdma_dcb *dcb;
+ struct ocelot_port *port;
+ struct net_device *ndev;
+ int max_mtu = 0;
+ int i;
+ u8 port_num;
+
+ for (port_num = 0; port_num < ocelot->num_phys_ports; port_num++) {
+ port = ocelot->ports[port_num];
+ if (!port)
+ continue;
+
+ port_priv = container_of(port, struct ocelot_port_private,
+ port);
+ ndev = port_priv->dev;
+
+ ndev->needed_headroom = OCELOT_TAG_LEN;
+ ndev->needed_tailroom = ETH_FCS_LEN;
+
+ ocelot_ifh_set_bypass_quirks(port_priv->ifh, 1,
+ QUIRK_LITTLE_ENDIAN);
+ ocelot_ifh_set_dest_quirks(port_priv->ifh, BIT_ULL(port_num),
+ QUIRK_LITTLE_ENDIAN);
+ ocelot_ifh_set_tag_type_quirks(port_priv->ifh, IFH_TAG_TYPE_C,
+ QUIRK_LITTLE_ENDIAN);
+
+ ndev->max_mtu = 9000 - FDMA_RX_EXTRA_SIZE;
+
+ if (READ_ONCE(ndev->mtu) > max_mtu)
+ max_mtu = READ_ONCE(ndev->mtu);
+ }
+
+ if (!ndev)
+ return -ENODEV;
+
+ fdma->rx_buf_size = fdma_rx_compute_buffer_size(max_mtu);
+ netif_napi_add(ndev, &fdma->napi, ocelot_fdma_napi_poll,
+ FDMA_WEIGHT);
+
+ for (i = 0; i < FDMA_MAX_SKB; i++) {
+ dcb = fdma_dcb_alloc(fdma);
+ if (!dcb)
+ return -ENOMEM;
+
+ ocelot_fdma_rx_add_dcb_sw(fdma, dcb);
+ }
+
+ napi_enable(&fdma->napi);
+
+ return 0;
+}
+
+struct ocelot_fdma *ocelot_fdma_init(struct platform_device *pdev,
+ struct ocelot *ocelot)
+{
+ struct ocelot_fdma *fdma;
+ int ret;
+
+ fdma = devm_kzalloc(&pdev->dev, sizeof(*fdma), GFP_KERNEL);
+ if (!fdma)
+ return ERR_PTR(-ENOMEM);
+
+ fdma->ocelot = ocelot;
+ fdma->base = devm_platform_ioremap_resource_byname(pdev, "fdma");
+ if (IS_ERR_OR_NULL(fdma->base))
+ return fdma->base;
+
+ fdma->dev = &pdev->dev;
+ fdma->dev->coherent_dma_mask = DMA_BIT_MASK(32);
+
+ spin_lock_init(&fdma->tx_enqueue_lock);
+ spin_lock_init(&fdma->tx_free_lock);
+
+ fdma_writel(fdma, MSCC_FDMA_INTR_ENA, 0);
+
+ fdma->irq = platform_get_irq_byname(pdev, "fdma");
+ ret = devm_request_irq(&pdev->dev, fdma->irq, ocelot_fdma_interrupt, 0,
+ dev_name(&pdev->dev), fdma);
+ if (ret)
+ return ERR_PTR(ret);
+
+ /* Create a pool of consistent memory blocks for hardware descriptors */
+ fdma->dcb_pool = dmam_pool_create("ocelot_fdma", &pdev->dev,
+ sizeof(struct ocelot_fdma_dcb),
+ __alignof__(struct ocelot_fdma_dcb),
+ 0);
+ if (!fdma->dcb_pool) {
+ dev_err(&pdev->dev, "unable to allocate DMA descriptor pool\n");
+ return ERR_PTR(-ENOMEM);
+ }
+
+ INIT_LIST_HEAD(&fdma->tx_ongoing);
+ INIT_LIST_HEAD(&fdma->tx_free_dcb);
+ INIT_LIST_HEAD(&fdma->tx_queued);
+ INIT_LIST_HEAD(&fdma->rx_sw);
+ INIT_LIST_HEAD(&fdma->rx_hw);
+
+ return fdma;
+}
+
+int ocelot_fdma_start(struct ocelot_fdma *fdma)
+{
+ struct ocelot *ocelot = fdma->ocelot;
+ int ret;
+
+ ret = fdma_init_tx(fdma);
+ if (ret)
+ return ret;
+
+ ret = fdma_init_rx(fdma);
+ if (ret)
+ return ret;
+
+ /* Reconfigure for extraction and injection using DMA */
+ ocelot_write_rix(ocelot, QS_INJ_GRP_CFG_MODE(2), QS_INJ_GRP_CFG, 0);
+ ocelot_write_rix(ocelot, QS_INJ_CTRL_GAP_SIZE(0), QS_INJ_CTRL, 0);
+
+ ocelot_write_rix(ocelot, QS_XTR_GRP_CFG_MODE(2), QS_XTR_GRP_CFG, 0);
+
+ fdma_writel(fdma, MSCC_FDMA_INTR_LLP, 0xffffffff);
+ fdma_writel(fdma, MSCC_FDMA_INTR_FRM, 0xffffffff);
+
+ fdma_writel(fdma, MSCC_FDMA_INTR_LLP_ENA,
+ BIT(MSCC_FDMA_INJ_CHAN) | BIT(MSCC_FDMA_XTR_CHAN));
+ fdma_writel(fdma, MSCC_FDMA_INTR_FRM_ENA, BIT(MSCC_FDMA_XTR_CHAN));
+ fdma_writel(fdma, MSCC_FDMA_INTR_ENA,
+ BIT(MSCC_FDMA_INJ_CHAN) | BIT(MSCC_FDMA_XTR_CHAN));
+
+ ocelot_fdma_rx_refill(fdma);
+
+ return 0;
+}
+
+int ocelot_fdma_stop(struct ocelot_fdma *fdma)
+{
+ fdma_writel(fdma, MSCC_FDMA_INTR_ENA, 0);
+
+ fdma_stop_channel(fdma, MSCC_FDMA_XTR_CHAN);
+ fdma_stop_channel(fdma, MSCC_FDMA_INJ_CHAN);
+
+ /* Free potentially pending SKBs in DCB lists */
+ fdma_free_skbs_list(fdma, &fdma->rx_hw, DMA_FROM_DEVICE);
+ fdma_free_skbs_list(fdma, &fdma->rx_sw, DMA_FROM_DEVICE);
+ fdma_free_skbs_list(fdma, &fdma->tx_ongoing, DMA_TO_DEVICE);
+ fdma_free_skbs_list(fdma, &fdma->tx_queued, DMA_TO_DEVICE);
+
+ netif_napi_del(&fdma->napi);
+
+ return 0;
+}diff --git a/drivers/net/ethernet/mscc/ocelot_fdma.h b/drivers/net/ethernet/mscc/ocelot_fdma.h
new file mode 100644
index 000000000000..9a43fa2a7dba
--- /dev/null
+++ b/drivers/net/ethernet/mscc/ocelot_fdma.h
@@ -0,0 +1,60 @@
+/* SPDX-License-Identifier: (GPL-2.0 OR MIT) */
+/*
+ * Microsemi SoCs FDMA driver
+ *
+ * Copyright (c) 2021 Microchip
+ */
+#ifndef _MSCC_OCELOT_FDMA_H_
+#define _MSCC_OCELOT_FDMA_H_
+
+#include "ocelot.h"
+
+/**
+ * struct ocelot_fdma - FMDA struct
+ *
+ * @ocelot: Pointer to ocelot struct
+ * @base: base address of FDMA registers
+ * @dcb_pool: Pool used for DCB allocation
+ * @irq: FDMA interrupt
+ * @dev: Ocelot device
+ * @napi: napi handle
+ * @rx_buf_size: Size of RX buffer
+ * @tx_ongoing: List of DCB handed out to the FDMA
+ * @tx_queued: pending list of DCBs to be given to the hardware
+ * @tx_enqueue_lock: Lock used for tx_queued and tx_ongoing
+ * @tx_free_dcb: List of DCB available for TX
+ * @tx_free_lock: Lock used to access tx_free_dcb list
+ * @rx_hw: RX DCBs currently owned by the hardware and not completed
+ * @rx_sw: RX DCBs completed
+ */
+struct ocelot_fdma {
+ struct ocelot *ocelot;
+ void __iomem *base;
+ struct dma_pool *dcb_pool;
+ int irq;
+ struct device *dev;
+ struct napi_struct napi;
+ size_t rx_buf_size;
+
+ struct list_head tx_ongoing;
+ struct list_head tx_queued;
+ /* Lock for tx_queued and tx_ongoing lists */
+ spinlock_t tx_enqueue_lock;
+
+ struct list_head tx_free_dcb;
+ /* Lock for tx_free_dcb list */
+ spinlock_t tx_free_lock;
+
+ struct list_head rx_hw;
+ struct list_head rx_sw;
+};
+
+struct ocelot_fdma *ocelot_fdma_init(struct platform_device *pdev,
+ struct ocelot *ocelot);
+int ocelot_fdma_start(struct ocelot_fdma *fdma);
+int ocelot_fdma_stop(struct ocelot_fdma *fdma);
+int ocelot_fdma_inject_frame(struct ocelot_fdma *fdma, u32 rew_op,
+ struct sk_buff *skb, struct net_device *dev);
+int ocelot_fdma_change_mtu(struct net_device *dev, int new_mtu);
+
+#endifdiff --git a/drivers/net/ethernet/mscc/ocelot_net.c b/drivers/net/ethernet/mscc/ocelot_net.c
index 2545727fd5b2..848618becd19 100644
--- a/drivers/net/ethernet/mscc/ocelot_net.c
+++ b/drivers/net/ethernet/mscc/ocelot_net.c
@@ -15,6 +15,7 @@
#include <net/pkt_cls.h>
#include "ocelot.h"
#include "ocelot_vcap.h"
+#include "ocelot_fdma.h"
#define OCELOT_MAC_QUIRKS OCELOT_QUIRK_QSGMII_PORTS_MUST_BE_UP
@@ -457,7 +458,7 @@ static netdev_tx_t ocelot_port_xmit(struct sk_buff *skb, struct net_device *dev)
int port = priv->chip_port;
u32 rew_op = 0;
- if (!ocelot_can_inject(ocelot, 0))
+ if (!ocelot->fdma && !ocelot_can_inject(ocelot, 0))
return NETDEV_TX_BUSY;
/* Check if timestamping is needed */
@@ -475,13 +476,35 @@ static netdev_tx_t ocelot_port_xmit(struct sk_buff *skb, struct net_device *dev)
rew_op = ocelot_ptp_rew_op(skb);
}
- ocelot_port_inject_frame(ocelot, port, 0, rew_op, skb);
+ if (ocelot->fdma) {
+ ocelot_fdma_inject_frame(ocelot->fdma, rew_op, skb, dev);
+ } else {
+ ocelot_port_inject_frame(ocelot, port, 0, rew_op, skb);
- kfree_skb(skb);
+ kfree_skb(skb);
+ }
return NETDEV_TX_OK;
}
+int ocelot_change_mtu(struct net_device *dev, int new_mtu)
+{
+ struct ocelot_port_private *priv = netdev_priv(dev);
+ struct ocelot_port *ocelot_port = &priv->port;
+ struct ocelot *ocelot = ocelot_port->ocelot;
+ int ret;
+
+ if (ocelot->fdma) {
+ ret = ocelot_fdma_change_mtu(dev, new_mtu);
+ if (ret)
+ return ret;
+ }
+
+ WRITE_ONCE(dev->mtu, new_mtu);
+
+ return 0;
+}
+
enum ocelot_action_type {
OCELOT_MACT_LEARN,
OCELOT_MACT_FORGET,@@ -768,6 +791,7 @@ static const struct net_device_ops ocelot_port_netdev_ops = {
.ndo_open = ocelot_port_open,
.ndo_stop = ocelot_port_stop,
.ndo_start_xmit = ocelot_port_xmit,
+ .ndo_change_mtu = ocelot_change_mtu,
.ndo_set_rx_mode = ocelot_set_rx_mode,
.ndo_set_mac_address = ocelot_port_set_mac_address,
.ndo_get_stats64 = ocelot_get_stats64,diff --git a/drivers/net/ethernet/mscc/ocelot_vsc7514.c b/drivers/net/ethernet/mscc/ocelot_vsc7514.c
index c39118e5b3ee..9fdda70a8457 100644
--- a/drivers/net/ethernet/mscc/ocelot_vsc7514.c
+++ b/drivers/net/ethernet/mscc/ocelot_vsc7514.c
@@ -18,6 +18,7 @@
#include <soc/mscc/ocelot_vcap.h>
#include <soc/mscc/ocelot_hsio.h>
+#include "ocelot_fdma.h"
#include "ocelot.h"
static const u32 ocelot_ana_regmap[] = {@@ -1083,6 +1084,10 @@ static int mscc_ocelot_probe(struct platform_device *pdev)
ocelot->targets[io_target[i].id] = target;
}
+ ocelot->fdma = ocelot_fdma_init(pdev, ocelot);
+ if (IS_ERR(ocelot->fdma))
+ ocelot->fdma = NULL;
+
hsio = syscon_regmap_lookup_by_compatible("mscc,ocelot-hsio");
if (IS_ERR(hsio)) {
dev_err(&pdev->dev, "missing hsio syscon\n");@@ -1146,6 +1151,12 @@ static int mscc_ocelot_probe(struct platform_device *pdev)
if (err)
goto out_ocelot_devlink_unregister;
+ if (ocelot->fdma) {
+ err = ocelot_fdma_start(ocelot->fdma);
+ if (err)
+ goto out_ocelot_devlink_unregister;
+ }
+
err = ocelot_devlink_sb_register(ocelot);
if (err)
goto out_ocelot_release_ports;@@ -1172,6 +1183,8 @@ static int mscc_ocelot_probe(struct platform_device *pdev)
out_ocelot_release_ports:
mscc_ocelot_release_ports(ocelot);
mscc_ocelot_teardown_devlink_ports(ocelot);
+ if (ocelot->fdma)
+ ocelot_fdma_stop(ocelot->fdma);
out_ocelot_devlink_unregister:
devlink_unregister(devlink);
out_ocelot_deinit:
@@ -1187,6 +1200,8 @@ static int mscc_ocelot_remove(struct platform_device *pdev)
{
struct ocelot *ocelot = platform_get_drvdata(pdev);
+ if (ocelot->fdma)
+ ocelot_fdma_stop(ocelot->fdma);
ocelot_deinit_timestamp(ocelot);
ocelot_devlink_sb_unregister(ocelot);
mscc_ocelot_release_ports(ocelot);diff --git a/include/linux/dsa/ocelot.h b/include/linux/dsa/ocelot.h
index 8ae999f587c4..885afdb98156 100644
--- a/include/linux/dsa/ocelot.h
+++ b/include/linux/dsa/ocelot.h
@@ -207,19 +207,37 @@ static inline void ocelot_xfh_get_vlan_tci(void *extraction, u64 *vlan_tci)
packing(extraction, vlan_tci, 15, 0, OCELOT_TAG_LEN, UNPACK, 0);
}
+static inline void ocelot_ifh_set_bypass_quirks(void *injection, u64 bypass,
+ u8 quirks)
+{
+ packing(injection, &bypass, 127, 127, OCELOT_TAG_LEN, PACK, quirks);
+}
+
static inline void ocelot_ifh_set_bypass(void *injection, u64 bypass)
{
- packing(injection, &bypass, 127, 127, OCELOT_TAG_LEN, PACK, 0);
+ ocelot_ifh_set_bypass_quirks(injection, bypass, 0);
+}
+
+static inline void ocelot_ifh_set_rew_op_quirks(void *injection, u64 rew_op,
+ u8 quirks)
+{
+ packing(injection, &rew_op, 125, 117, OCELOT_TAG_LEN, PACK, quirks);
}
static inline void ocelot_ifh_set_rew_op(void *injection, u64 rew_op)
{
- packing(injection, &rew_op, 125, 117, OCELOT_TAG_LEN, PACK, 0);
+ ocelot_ifh_set_rew_op_quirks(injection, rew_op, 0);
+}
+
+static inline void ocelot_ifh_set_dest_quirks(void *injection, u64 dest,
+ u8 quirks)
+{
+ packing(injection, &dest, 67, 56, OCELOT_TAG_LEN, PACK, quirks);
}
static inline void ocelot_ifh_set_dest(void *injection, u64 dest)
{
- packing(injection, &dest, 67, 56, OCELOT_TAG_LEN, PACK, 0);
+ ocelot_ifh_set_dest_quirks(injection, dest, 0);
}
static inline void ocelot_ifh_set_qos_class(void *injection, u64 qos_class)@@ -237,14 +255,26 @@ static inline void ocelot_ifh_set_src(void *injection, u64 src)
packing(injection, &src, 46, 43, OCELOT_TAG_LEN, PACK, 0);
}
+static inline void ocelot_ifh_set_tag_type_quirks(void *injection, u64 tag_type,
+ u8 quirks)
+{
+ packing(injection, &tag_type, 16, 16, OCELOT_TAG_LEN, PACK, quirks);
+}
+
static inline void ocelot_ifh_set_tag_type(void *injection, u64 tag_type)
{
- packing(injection, &tag_type, 16, 16, OCELOT_TAG_LEN, PACK, 0);
+ ocelot_ifh_set_tag_type_quirks(injection, tag_type, 0);
+}
+
+static inline void ocelot_ifh_set_vid_quirks(void *injection, u64 vid,
+ u8 quirks)
+{
+ packing(injection, &vid, 11, 0, OCELOT_TAG_LEN, PACK, quirks);
}
static inline void ocelot_ifh_set_vid(void *injection, u64 vid)
{
- packing(injection, &vid, 11, 0, OCELOT_TAG_LEN, PACK, 0);
+ ocelot_ifh_set_vid_quirks(injection, vid, 0);
}
/* Determine the PTP REW_OP to use for injecting the given skb */diff --git a/include/soc/mscc/ocelot.h b/include/soc/mscc/ocelot.h
index d7055b41982d..6756e13f69f7 100644
--- a/include/soc/mscc/ocelot.h
+++ b/include/soc/mscc/ocelot.h
@@ -679,6 +679,8 @@ struct ocelot {
/* Protects the PTP clock */
spinlock_t ptp_clock_lock;
struct ptp_pin_desc ptp_pins[OCELOT_PTP_PINS_NUM];
+
+ struct ocelot_fdma *fdma;
};
struct ocelot_policer {--
2.33.0