summaryrefslogtreecommitdiff
path: root/drivers/net/ethernet/microchip
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/net/ethernet/microchip')
-rw-r--r--drivers/net/ethernet/microchip/fdma/Makefile4
-rw-r--r--drivers/net/ethernet/microchip/fdma/fdma_api.c65
-rw-r--r--drivers/net/ethernet/microchip/fdma/fdma_api.h42
-rw-r--r--drivers/net/ethernet/microchip/fdma/fdma_pci.c208
-rw-r--r--drivers/net/ethernet/microchip/fdma/fdma_pci.h57
-rw-r--r--drivers/net/ethernet/microchip/lan865x/lan865x.c4
-rw-r--r--drivers/net/ethernet/microchip/lan966x/Makefile4
-rw-r--r--drivers/net/ethernet/microchip/lan966x/lan966x_fdma.c97
-rw-r--r--drivers/net/ethernet/microchip/lan966x/lan966x_fdma_pci.c731
-rw-r--r--drivers/net/ethernet/microchip/lan966x/lan966x_main.c121
-rw-r--r--drivers/net/ethernet/microchip/lan966x/lan966x_main.h69
-rw-r--r--drivers/net/ethernet/microchip/lan966x/lan966x_regs.h25
-rw-r--r--drivers/net/ethernet/microchip/lan966x/lan966x_xdp.c6
-rw-r--r--drivers/net/ethernet/microchip/sparx5/Makefile2
-rw-r--r--drivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c2
-rw-r--r--drivers/net/ethernet/microchip/sparx5/lan969x/lan969x_calendar.c8
-rw-r--r--drivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c5
-rw-r--r--drivers/net/ethernet/microchip/sparx5/lan969x/lan969x_regs.c10
-rw-r--r--drivers/net/ethernet/microchip/sparx5/sparx5_main.c2
-rw-r--r--drivers/net/ethernet/microchip/sparx5/sparx5_main.h28
-rw-r--r--drivers/net/ethernet/microchip/sparx5/sparx5_main_regs.h204
-rw-r--r--drivers/net/ethernet/microchip/sparx5/sparx5_psfp.c10
-rw-r--r--drivers/net/ethernet/microchip/sparx5/sparx5_qos.c53
-rw-r--r--drivers/net/ethernet/microchip/sparx5/sparx5_qos.h1
-rw-r--r--drivers/net/ethernet/microchip/sparx5/sparx5_regs.c10
-rw-r--r--drivers/net/ethernet/microchip/sparx5/sparx5_regs.h10
-rw-r--r--drivers/net/ethernet/microchip/sparx5/sparx5_tas.c806
-rw-r--r--drivers/net/ethernet/microchip/sparx5/sparx5_tc.c17
-rw-r--r--drivers/net/ethernet/microchip/vcap/Kconfig1
29 files changed, 2464 insertions, 138 deletions
diff --git a/drivers/net/ethernet/microchip/fdma/Makefile b/drivers/net/ethernet/microchip/fdma/Makefile
index cc9a736be357..910a10b33fb9 100644
--- a/drivers/net/ethernet/microchip/fdma/Makefile
+++ b/drivers/net/ethernet/microchip/fdma/Makefile
@@ -5,3 +5,7 @@
obj-$(CONFIG_FDMA) += fdma.o
fdma-y += fdma_api.o
+
+ifneq ($(CONFIG_MCHP_LAN966X_PCI),)
+fdma-y += fdma_pci.o
+endif
diff --git a/drivers/net/ethernet/microchip/fdma/fdma_api.c b/drivers/net/ethernet/microchip/fdma/fdma_api.c
index e78c3590da9e..f7a42348e932 100644
--- a/drivers/net/ethernet/microchip/fdma/fdma_api.c
+++ b/drivers/net/ethernet/microchip/fdma/fdma_api.c
@@ -12,10 +12,18 @@ static int __fdma_db_add(struct fdma *fdma, int dcb_idx, int db_idx, u64 status,
int db_idx, u64 *dataptr))
{
struct fdma_db *db = fdma_db_get(fdma, dcb_idx, db_idx);
+ u64 dataptr;
+ int err;
- db->status = status;
+ db->status = cpu_to_le64(status);
- return cb(fdma, dcb_idx, db_idx, &db->dataptr);
+ err = cb(fdma, dcb_idx, db_idx, &dataptr);
+ if (unlikely(err))
+ return err;
+
+ db->dataptr = cpu_to_le64(dataptr);
+
+ return 0;
}
/* Add a DB to a DCB, using the callback set in the fdma_ops struct. */
@@ -35,6 +43,7 @@ int __fdma_dcb_add(struct fdma *fdma, int dcb_idx, u64 info, u64 status,
u64 *dataptr))
{
struct fdma_dcb *dcb = fdma_dcb_get(fdma, dcb_idx);
+ u64 nextptr;
int i, err;
for (i = 0; i < fdma->n_dbs; i++) {
@@ -43,14 +52,16 @@ int __fdma_dcb_add(struct fdma *fdma, int dcb_idx, u64 info, u64 status,
return err;
}
- err = dcb_cb(fdma, dcb_idx, &fdma->last_dcb->nextptr);
+ err = dcb_cb(fdma, dcb_idx, &nextptr);
if (unlikely(err))
return err;
+ fdma->last_dcb->nextptr = cpu_to_le64(nextptr);
+
fdma->last_dcb = dcb;
- dcb->nextptr = FDMA_DCB_INVALID_DATA;
- dcb->info = info;
+ dcb->nextptr = cpu_to_le64(FDMA_DCB_INVALID_DATA);
+ dcb->info = cpu_to_le64(info);
return 0;
}
@@ -127,6 +138,50 @@ void fdma_free_phys(struct fdma *fdma)
}
EXPORT_SYMBOL_GPL(fdma_free_phys);
+#if IS_ENABLED(CONFIG_MCHP_LAN966X_PCI)
+/* Allocate coherent DMA memory and map it in the ATU. */
+int fdma_alloc_coherent_and_map(struct device *dev, struct fdma *fdma,
+ struct fdma_pci_atu *atu)
+{
+ struct fdma_pci_atu_region *region;
+ int err;
+
+ if (WARN_ON(fdma->atu_region))
+ return -EBUSY;
+
+ /* The ATU cannot express a limit finer than the region granularity, so
+ * the hardware widens the programmed limit to that boundary. Pad the
+ * allocation to match, or the outbound window would extend past the
+ * memory we own.
+ */
+ fdma->size = ALIGN(fdma->size, FDMA_PCI_ATU_REGION_ALIGN);
+
+ err = fdma_alloc_coherent(dev, fdma);
+ if (err)
+ return err;
+
+ region = fdma_pci_atu_region_map(atu, fdma->dma, fdma->size);
+ if (IS_ERR(region)) {
+ fdma_free_coherent(dev, fdma);
+ return PTR_ERR(region);
+ }
+
+ fdma->atu_region = region;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(fdma_alloc_coherent_and_map);
+
+/* Free coherent DMA memory and unmap the memory in the ATU. */
+void fdma_free_coherent_and_unmap(struct device *dev, struct fdma *fdma)
+{
+ fdma_pci_atu_region_unmap(fdma->atu_region);
+ fdma->atu_region = NULL;
+ fdma_free_coherent(dev, fdma);
+}
+EXPORT_SYMBOL_GPL(fdma_free_coherent_and_unmap);
+#endif
+
/* Get the size of the FDMA memory */
u32 fdma_get_size(struct fdma *fdma)
{
diff --git a/drivers/net/ethernet/microchip/fdma/fdma_api.h b/drivers/net/ethernet/microchip/fdma/fdma_api.h
index d91affe8bd98..4e4f009b77cb 100644
--- a/drivers/net/ethernet/microchip/fdma/fdma_api.h
+++ b/drivers/net/ethernet/microchip/fdma/fdma_api.h
@@ -7,6 +7,8 @@
#include <linux/etherdevice.h>
#include <linux/types.h>
+#include "fdma_pci.h"
+
/* This provides a common set of functions and data structures for interacting
* with the Frame DMA engine on multiple Microchip switchcores.
*
@@ -64,13 +66,13 @@
struct fdma;
struct fdma_db {
- u64 dataptr;
- u64 status;
+ __le64 dataptr;
+ __le64 status;
};
struct fdma_dcb {
- u64 nextptr;
- u64 info;
+ __le64 nextptr;
+ __le64 info;
struct fdma_db db[FDMA_DB_MAX];
};
@@ -109,6 +111,11 @@ struct fdma {
u32 channel_id;
struct fdma_ops ops;
+
+#if IS_ENABLED(CONFIG_MCHP_LAN966X_PCI)
+ /* PCI ATU region for this FDMA instance. */
+ struct fdma_pci_atu_region *atu_region;
+#endif
};
/* Advance the DCB index and wrap if required. */
@@ -140,19 +147,25 @@ static inline bool fdma_dcb_is_reusable(struct fdma *fdma)
/* Check if the FDMA has marked this DB as done. */
static inline bool fdma_db_is_done(struct fdma_db *db)
{
- return db->status & FDMA_DCB_STATUS_DONE;
+ return le64_to_cpu(db->status) & FDMA_DCB_STATUS_DONE;
}
/* Get the length of a DB. */
static inline int fdma_db_len_get(struct fdma_db *db)
{
- return FDMA_DCB_STATUS_BLOCKL(db->status);
+ return FDMA_DCB_STATUS_BLOCKL(le64_to_cpu(db->status));
+}
+
+/* Get the dataptr of a DB. */
+static inline u64 fdma_db_dataptr_get(struct fdma_db *db)
+{
+ return le64_to_cpu(db->dataptr);
}
/* Set the length of a DB. */
static inline void fdma_dcb_len_set(struct fdma_dcb *dcb, u32 len)
{
- dcb->info = FDMA_DCB_INFO_DATAL(len);
+ dcb->info = cpu_to_le64(FDMA_DCB_INFO_DATAL(len));
}
/* Get a DB by index. */
@@ -197,8 +210,8 @@ static inline int fdma_nextptr_cb(struct fdma *fdma, int dcb_idx, u64 *nextptr)
* if the dataptr addresses and DCB's are in contiguous memory and the driver
* supports XDP.
*/
-static inline u64 fdma_dataptr_get_contiguous(struct fdma *fdma, int dcb_idx,
- int db_idx)
+static inline u64 fdma_dataptr_dma_addr_contiguous(struct fdma *fdma,
+ int dcb_idx, int db_idx)
{
return fdma->dma + (sizeof(struct fdma_dcb) * fdma->n_dcbs) +
(dcb_idx * fdma->n_dbs + db_idx) * fdma->db_size +
@@ -209,8 +222,8 @@ static inline u64 fdma_dataptr_get_contiguous(struct fdma *fdma, int dcb_idx,
* applicable if the dataptr addresses and DCB's are in contiguous memory and
* the driver supports XDP.
*/
-static inline void *fdma_dataptr_virt_get_contiguous(struct fdma *fdma,
- int dcb_idx, int db_idx)
+static inline void *fdma_dataptr_virt_addr_contiguous(struct fdma *fdma,
+ int dcb_idx, int db_idx)
{
return (u8 *)fdma->dcbs + (sizeof(struct fdma_dcb) * fdma->n_dcbs) +
(dcb_idx * fdma->n_dbs + db_idx) * fdma->db_size +
@@ -233,9 +246,16 @@ int __fdma_dcb_add(struct fdma *fdma, int dcb_idx, u64 info, u64 status,
int fdma_alloc_coherent(struct device *dev, struct fdma *fdma);
int fdma_alloc_phys(struct fdma *fdma);
+#if IS_ENABLED(CONFIG_MCHP_LAN966X_PCI)
+int fdma_alloc_coherent_and_map(struct device *dev, struct fdma *fdma,
+ struct fdma_pci_atu *atu);
+#endif
void fdma_free_coherent(struct device *dev, struct fdma *fdma);
void fdma_free_phys(struct fdma *fdma);
+#if IS_ENABLED(CONFIG_MCHP_LAN966X_PCI)
+void fdma_free_coherent_and_unmap(struct device *dev, struct fdma *fdma);
+#endif
u32 fdma_get_size(struct fdma *fdma);
u32 fdma_get_size_contiguous(struct fdma *fdma);
diff --git a/drivers/net/ethernet/microchip/fdma/fdma_pci.c b/drivers/net/ethernet/microchip/fdma/fdma_pci.c
new file mode 100644
index 000000000000..dd1dc46cbc9d
--- /dev/null
+++ b/drivers/net/ethernet/microchip/fdma/fdma_pci.c
@@ -0,0 +1,208 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+#include <linux/align.h>
+#include <linux/errno.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/types.h>
+
+#include "fdma_pci.h"
+
+/* When the switch operates as a PCIe endpoint, the FDMA engine needs to
+ * DMA to/from host memory. The FDMA writes to addresses within the endpoint's
+ * internal Outbound (OB) address space, and the PCIe ATU translates these to
+ * DMA addresses on the PCIe bus, targeting host memory.
+ *
+ * The ATU supports up to six outbound regions. This implementation divides
+ * the OB address space into six equally sized chunks.
+ *
+ * +-------------+------------+------------+-----+------------+
+ * | Index | Region 0 | Region 1 | ... | Region 5 |
+ * +-------------+------------+------------+-----+------------+
+ * | Base addr | 0x10000000 | 0x12aa0000 | ... | 0x1d520000 |
+ * | Limit addr | 0x12a9ffff | 0x1553ffff | ... | 0x1ffbffff |
+ * | Target addr | host dma | host dma | ... | host dma |
+ * +-------------+------------+------------+-----+------------+
+ *
+ * Base addr is the start address of the region within the OB address space.
+ * Limit addr is each region's own upper bound. The value actually
+ * programmed is set per-mapping as base_addr + mapped size - 1, and is
+ * usually smaller than this.
+ * Target addr is the host DMA address that the base addr translates to.
+ */
+
+#define FDMA_PCI_ATU_OB_START 0x10000000
+#define FDMA_PCI_ATU_OB_END 0x1fffffff
+
+#define FDMA_PCI_ATU_ADDR 0x300000
+#define FDMA_PCI_ATU_IDX_SIZE 0x200
+#define FDMA_PCI_ATU_ENA_REG 0x4
+#define FDMA_PCI_ATU_ENA_BIT BIT(31)
+#define FDMA_PCI_ATU_LWR_BASE_ADDR 0x8
+#define FDMA_PCI_ATU_UPP_BASE_ADDR 0xc
+#define FDMA_PCI_ATU_LIMIT_ADDR 0x10
+#define FDMA_PCI_ATU_LWR_TARGET_ADDR 0x14
+#define FDMA_PCI_ATU_UPP_TARGET_ADDR 0x18
+
+static u32 fdma_pci_atu_region_size(void)
+{
+ return round_down((FDMA_PCI_ATU_OB_END - FDMA_PCI_ATU_OB_START) /
+ FDMA_PCI_ATU_REGION_MAX, FDMA_PCI_ATU_REGION_ALIGN);
+}
+
+static void __iomem *fdma_pci_atu_addr_get(void __iomem *addr, int offset,
+ int idx)
+{
+ return addr + FDMA_PCI_ATU_ADDR + FDMA_PCI_ATU_IDX_SIZE * idx + offset;
+}
+
+static void fdma_pci_atu_region_enable(struct fdma_pci_atu_region *region)
+{
+ writel(FDMA_PCI_ATU_ENA_BIT,
+ fdma_pci_atu_addr_get(region->atu->addr, FDMA_PCI_ATU_ENA_REG,
+ region->idx));
+}
+
+static void fdma_pci_atu_region_disable(struct fdma_pci_atu_region *region)
+{
+ writel(0, fdma_pci_atu_addr_get(region->atu->addr, FDMA_PCI_ATU_ENA_REG,
+ region->idx));
+}
+
+/* Configure the address translation in the ATU. */
+static void
+fdma_pci_atu_configure_translation(struct fdma_pci_atu_region *region)
+{
+ struct fdma_pci_atu *atu = region->atu;
+ int idx = region->idx;
+
+ writel(lower_32_bits(region->base_addr),
+ fdma_pci_atu_addr_get(atu->addr,
+ FDMA_PCI_ATU_LWR_BASE_ADDR, idx));
+
+ writel(upper_32_bits(region->base_addr),
+ fdma_pci_atu_addr_get(atu->addr,
+ FDMA_PCI_ATU_UPP_BASE_ADDR, idx));
+
+ /* The OB address space lies entirely below 4GB, so the limit always
+ * fits the lower limit register and the upper one is left alone.
+ */
+ writel(region->limit_addr,
+ fdma_pci_atu_addr_get(atu->addr, FDMA_PCI_ATU_LIMIT_ADDR, idx));
+
+ writel(lower_32_bits(region->target_addr),
+ fdma_pci_atu_addr_get(atu->addr,
+ FDMA_PCI_ATU_LWR_TARGET_ADDR, idx));
+
+ writel(upper_32_bits(region->target_addr),
+ fdma_pci_atu_addr_get(atu->addr,
+ FDMA_PCI_ATU_UPP_TARGET_ADDR, idx));
+}
+
+/* Find an unused ATU region. */
+static struct fdma_pci_atu_region *
+fdma_pci_atu_region_get_free(struct fdma_pci_atu *atu)
+{
+ struct fdma_pci_atu_region *regions = atu->regions;
+
+ for (int i = 0; i < FDMA_PCI_ATU_REGION_MAX; i++) {
+ if (regions[i].in_use)
+ continue;
+
+ return &regions[i];
+ }
+
+ return ERR_PTR(-ENOSPC);
+}
+
+/* Unmap an ATU region, clearing its translation and disabling it. */
+void fdma_pci_atu_region_unmap(struct fdma_pci_atu_region *region)
+{
+ if (IS_ERR_OR_NULL(region))
+ return;
+
+ mutex_lock(&region->atu->lock);
+
+ region->target_addr = 0;
+ region->in_use = false;
+
+ fdma_pci_atu_region_disable(region);
+ fdma_pci_atu_configure_translation(region);
+
+ mutex_unlock(&region->atu->lock);
+}
+EXPORT_SYMBOL_GPL(fdma_pci_atu_region_unmap);
+
+/* Map a host DMA address into a free outbound region. */
+struct fdma_pci_atu_region *
+fdma_pci_atu_region_map(struct fdma_pci_atu *atu, u64 target_addr, int size)
+{
+ struct fdma_pci_atu_region *region;
+
+ if (!atu)
+ return ERR_PTR(-EINVAL);
+
+ if (size <= 0)
+ return ERR_PTR(-EINVAL);
+
+ if (size > fdma_pci_atu_region_size())
+ return ERR_PTR(-ERANGE);
+
+ /* The ATU region base is only ever aligned to FDMA_PCI_ATU_REGION_ALIGN;
+ * require the same alignment of the host target address, since the ATU
+ * translates addr - target_addr + base_addr and any misalignment here
+ * would shift every translated address by the same amount.
+ */
+ if (!IS_ALIGNED(target_addr, FDMA_PCI_ATU_REGION_ALIGN))
+ return ERR_PTR(-EINVAL);
+
+ mutex_lock(&atu->lock);
+
+ region = fdma_pci_atu_region_get_free(atu);
+ if (IS_ERR(region)) {
+ mutex_unlock(&atu->lock);
+ return region;
+ }
+
+ region->target_addr = target_addr;
+ region->limit_addr = region->base_addr + size - 1;
+ region->in_use = true;
+
+ fdma_pci_atu_configure_translation(region);
+ fdma_pci_atu_region_enable(region);
+
+ mutex_unlock(&atu->lock);
+
+ return region;
+}
+EXPORT_SYMBOL_GPL(fdma_pci_atu_region_map);
+
+/* Translate a host DMA address to the corresponding OB address. */
+u64 fdma_pci_atu_translate_addr(struct fdma_pci_atu_region *region, u64 addr)
+{
+ return region->base_addr + (addr - region->target_addr);
+}
+EXPORT_SYMBOL_GPL(fdma_pci_atu_translate_addr);
+
+/* Initialize ATU, dividing the OB space into equally sized regions. */
+void fdma_pci_atu_init(struct fdma_pci_atu *atu, void __iomem *addr)
+{
+ struct fdma_pci_atu_region *regions = atu->regions;
+ u32 region_size = fdma_pci_atu_region_size();
+
+ atu->addr = addr;
+ mutex_init(&atu->lock);
+
+ for (int i = 0; i < FDMA_PCI_ATU_REGION_MAX; i++) {
+ regions[i].base_addr =
+ FDMA_PCI_ATU_OB_START + (i * region_size);
+ regions[i].limit_addr = 0;
+ regions[i].target_addr = 0;
+ regions[i].idx = i;
+ regions[i].atu = atu;
+ regions[i].in_use = false;
+
+ fdma_pci_atu_region_disable(&regions[i]);
+ }
+}
+EXPORT_SYMBOL_GPL(fdma_pci_atu_init);
diff --git a/drivers/net/ethernet/microchip/fdma/fdma_pci.h b/drivers/net/ethernet/microchip/fdma/fdma_pci.h
new file mode 100644
index 000000000000..010bdb4d50b0
--- /dev/null
+++ b/drivers/net/ethernet/microchip/fdma/fdma_pci.h
@@ -0,0 +1,57 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+
+#ifndef _FDMA_PCI_H_
+#define _FDMA_PCI_H_
+
+#include <linux/align.h>
+#include <linux/bits.h>
+#include <linux/mutex.h>
+#include <linux/types.h>
+
+#define FDMA_PCI_ATU_REGION_MAX 6
+
+/* Outbound regions are 64KB granular (datasheet section 3.24.7.4.1), so both
+ * the region base and the mapped size must be aligned to this.
+ */
+#define FDMA_PCI_ATU_REGION_ALIGN BIT(16)
+
+#define FDMA_PCI_DB_ALIGN 128
+#define FDMA_PCI_DB_SIZE(mtu) ALIGN(mtu, FDMA_PCI_DB_ALIGN)
+
+struct fdma_pci_atu;
+
+struct fdma_pci_atu_region {
+ struct fdma_pci_atu *atu;
+ u64 base_addr; /* Base addr of the OB window */
+ u64 limit_addr; /* End addr of the active mapping (base_addr + size - 1) */
+ u64 target_addr; /* Host DMA address this region maps to */
+ int idx;
+ bool in_use;
+};
+
+struct fdma_pci_atu {
+ void __iomem *addr;
+ struct mutex lock; /* Protects region alloc/free and ATU register access */
+ struct fdma_pci_atu_region regions[FDMA_PCI_ATU_REGION_MAX];
+};
+
+/* Initialize ATU, dividing OB space into regions. */
+void fdma_pci_atu_init(struct fdma_pci_atu *atu, void __iomem *addr);
+
+/* Unmap an ATU region, clearing its translation and disabling it. */
+void fdma_pci_atu_region_unmap(struct fdma_pci_atu_region *region);
+
+/* Map a host DMA address into a free ATU region. target_addr and size must be
+ * FDMA_PCI_ATU_REGION_ALIGN aligned; a misaligned target_addr returns -EINVAL.
+ */
+struct fdma_pci_atu_region *fdma_pci_atu_region_map(struct fdma_pci_atu *atu,
+ u64 target_addr,
+ int size);
+
+/* Translate a host DMA address to the OB address space. Reads the region
+ * unlocked, so the caller must quiesce DMA and the descriptor paths before
+ * unmapping the region.
+ */
+u64 fdma_pci_atu_translate_addr(struct fdma_pci_atu_region *region, u64 addr);
+
+#endif
diff --git a/drivers/net/ethernet/microchip/lan865x/lan865x.c b/drivers/net/ethernet/microchip/lan865x/lan865x.c
index 633d00857774..d7e5dfc22396 100644
--- a/drivers/net/ethernet/microchip/lan865x/lan865x.c
+++ b/drivers/net/ethernet/microchip/lan865x/lan865x.c
@@ -349,8 +349,8 @@ static int lan865x_probe(struct spi_device *spi)
netdev->netdev_ops = &lan865x_netdev_ops;
priv->tc6 = oa_tc6_init(spi, netdev, NULL);
- if (!priv->tc6) {
- ret = -ENODEV;
+ if (IS_ERR(priv->tc6)) {
+ ret = PTR_ERR(priv->tc6);
goto free_netdev;
}
diff --git a/drivers/net/ethernet/microchip/lan966x/Makefile b/drivers/net/ethernet/microchip/lan966x/Makefile
index 4cdbe263502c..02db4d4d9826 100644
--- a/drivers/net/ethernet/microchip/lan966x/Makefile
+++ b/drivers/net/ethernet/microchip/lan966x/Makefile
@@ -18,6 +18,10 @@ lan966x-switch-objs := lan966x_main.o lan966x_phylink.o lan966x_port.o \
lan966x-switch-$(CONFIG_LAN966X_DCB) += lan966x_dcb.o
lan966x-switch-$(CONFIG_DEBUG_FS) += lan966x_vcap_debugfs.o
+ifneq ($(CONFIG_MCHP_LAN966X_PCI),)
+lan966x-switch-y += lan966x_fdma_pci.o
+endif
+
# Provide include files
ccflags-y += -I$(srctree)/drivers/net/ethernet/microchip/vcap
ccflags-y += -I$(srctree)/drivers/net/ethernet/microchip/fdma
diff --git a/drivers/net/ethernet/microchip/lan966x/lan966x_fdma.c b/drivers/net/ethernet/microchip/lan966x/lan966x_fdma.c
index 41d4ec7f2f57..a7940eca5df3 100644
--- a/drivers/net/ethernet/microchip/lan966x/lan966x_fdma.c
+++ b/drivers/net/ethernet/microchip/lan966x/lan966x_fdma.c
@@ -80,7 +80,7 @@ static int lan966x_fdma_rx_alloc_page_pool(struct lan966x_rx *rx)
.flags = PP_FLAG_DMA_MAP | PP_FLAG_DMA_SYNC_DEV,
.pool_size = rx->fdma.n_dcbs,
.nid = NUMA_NO_NODE,
- .dev = lan966x->dev,
+ .dev = lan966x->dma_dev,
.dma_dir = DMA_FROM_DEVICE,
.offset = XDP_PACKET_HEADROOM,
.max_len = rx->max_mtu -
@@ -109,6 +109,13 @@ static int lan966x_fdma_rx_alloc_page_pool(struct lan966x_rx *rx)
return 0;
}
+void lan966x_fdma_llp_configure(struct lan966x *lan966x, u64 addr,
+ u8 channel_id)
+{
+ lan_wr(lower_32_bits(addr), lan966x, FDMA_DCB_LLP(channel_id));
+ lan_wr(upper_32_bits(addr), lan966x, FDMA_DCB_LLP1(channel_id));
+}
+
static int lan966x_fdma_rx_alloc(struct lan966x_rx *rx)
{
struct lan966x *lan966x = rx->lan966x;
@@ -119,7 +126,7 @@ static int lan966x_fdma_rx_alloc(struct lan966x_rx *rx)
if (err)
return err;
- err = fdma_alloc_coherent(lan966x->dev, fdma);
+ err = fdma_alloc_coherent(lan966x->dma_dev, fdma);
if (err) {
page_pool_destroy(rx->page_pool);
return err;
@@ -128,22 +135,20 @@ static int lan966x_fdma_rx_alloc(struct lan966x_rx *rx)
fdma_dcbs_init(fdma, FDMA_DCB_INFO_DATAL(fdma->db_size),
FDMA_DCB_STATUS_INTR);
+ lan966x_fdma_llp_configure(lan966x, fdma->dma, fdma->channel_id);
+
return 0;
}
-static void lan966x_fdma_rx_start(struct lan966x_rx *rx)
+void lan966x_fdma_rx_start(struct lan966x_rx *rx)
{
struct lan966x *lan966x = rx->lan966x;
struct fdma *fdma = &rx->fdma;
u32 mask;
- /* When activating a channel, first is required to write the first DCB
- * address and then to activate it
- */
- lan_wr(lower_32_bits((u64)fdma->dma), lan966x,
- FDMA_DCB_LLP(fdma->channel_id));
- lan_wr(upper_32_bits((u64)fdma->dma), lan966x,
- FDMA_DCB_LLP1(fdma->channel_id));
+ lan_wr(FDMA_INTR_ENA_INTR_PORT_ENA_SET(GENMASK(1, 0)) |
+ FDMA_INTR_ENA_INTR_CH_ENA_SET(GENMASK(7, 0)),
+ lan966x, FDMA_INTR_ENA);
lan_wr(FDMA_CH_CFG_CH_DCB_DB_CNT_SET(fdma->n_dbs) |
FDMA_CH_CFG_CH_INTR_DB_EOF_ONLY_SET(1) |
@@ -170,7 +175,7 @@ static void lan966x_fdma_rx_start(struct lan966x_rx *rx)
lan966x, FDMA_CH_ACTIVATE);
}
-static void lan966x_fdma_rx_disable(struct lan966x_rx *rx)
+void lan966x_fdma_rx_disable(struct lan966x_rx *rx)
{
struct lan966x *lan966x = rx->lan966x;
struct fdma *fdma = &rx->fdma;
@@ -190,7 +195,7 @@ static void lan966x_fdma_rx_disable(struct lan966x_rx *rx)
lan966x, FDMA_CH_DB_DISCARD);
}
-static void lan966x_fdma_rx_reload(struct lan966x_rx *rx)
+void lan966x_fdma_rx_reload(struct lan966x_rx *rx)
{
struct lan966x *lan966x = rx->lan966x;
@@ -209,12 +214,14 @@ static int lan966x_fdma_tx_alloc(struct lan966x_tx *tx)
if (!tx->dcbs_buf)
return -ENOMEM;
- err = fdma_alloc_coherent(lan966x->dev, fdma);
+ err = fdma_alloc_coherent(lan966x->dma_dev, fdma);
if (err)
goto out;
fdma_dcbs_init(fdma, 0, 0);
+ lan966x_fdma_llp_configure(lan966x, fdma->dma, fdma->channel_id);
+
return 0;
out:
@@ -227,7 +234,7 @@ static void lan966x_fdma_tx_free(struct lan966x_tx *tx)
struct lan966x *lan966x = tx->lan966x;
kfree(tx->dcbs_buf);
- fdma_free_coherent(lan966x->dev, &tx->fdma);
+ fdma_free_coherent(lan966x->dma_dev, &tx->fdma);
}
static void lan966x_fdma_tx_activate(struct lan966x_tx *tx)
@@ -236,14 +243,6 @@ static void lan966x_fdma_tx_activate(struct lan966x_tx *tx)
struct fdma *fdma = &tx->fdma;
u32 mask;
- /* When activating a channel, first is required to write the first DCB
- * address and then to activate it
- */
- lan_wr(lower_32_bits((u64)fdma->dma), lan966x,
- FDMA_DCB_LLP(fdma->channel_id));
- lan_wr(upper_32_bits((u64)fdma->dma), lan966x,
- FDMA_DCB_LLP1(fdma->channel_id));
-
lan_wr(FDMA_CH_CFG_CH_DCB_DB_CNT_SET(fdma->n_dbs) |
FDMA_CH_CFG_CH_INTR_DB_EOF_ONLY_SET(1) |
FDMA_CH_CFG_CH_INJ_PORT_SET(0) |
@@ -269,7 +268,7 @@ static void lan966x_fdma_tx_activate(struct lan966x_tx *tx)
lan966x, FDMA_CH_ACTIVATE);
}
-static void lan966x_fdma_tx_disable(struct lan966x_tx *tx)
+void lan966x_fdma_tx_disable(struct lan966x_tx *tx)
{
struct lan966x *lan966x = tx->lan966x;
struct fdma *fdma = &tx->fdma;
@@ -301,7 +300,7 @@ static void lan966x_fdma_tx_reload(struct lan966x_tx *tx)
lan966x, FDMA_CH_RELOAD);
}
-static void lan966x_fdma_wakeup_netdev(struct lan966x *lan966x)
+void lan966x_fdma_wakeup_netdev(struct lan966x *lan966x)
{
struct lan966x_port *port;
int i;
@@ -360,7 +359,7 @@ static void lan966x_fdma_tx_clear_buf(struct lan966x *lan966x, int weight)
dcb_buf->used = false;
if (dcb_buf->use_skb) {
- dma_unmap_single(lan966x->dev,
+ dma_unmap_single(lan966x->dma_dev,
dcb_buf->dma_addr,
dcb_buf->len,
DMA_TO_DEVICE);
@@ -369,7 +368,7 @@ static void lan966x_fdma_tx_clear_buf(struct lan966x *lan966x, int weight)
napi_consume_skb(dcb_buf->data.skb, weight);
} else {
if (dcb_buf->xdp_ndo)
- dma_unmap_single(lan966x->dev,
+ dma_unmap_single(lan966x->dma_dev,
dcb_buf->dma_addr,
dcb_buf->len,
DMA_TO_DEVICE);
@@ -405,9 +404,10 @@ static int lan966x_fdma_rx_check_frame(struct lan966x_rx *rx, u64 *src_port)
if (unlikely(!page))
return FDMA_ERROR;
- dma_sync_single_for_cpu(lan966x->dev,
- (dma_addr_t)db->dataptr + XDP_PACKET_HEADROOM,
- FDMA_DCB_STATUS_BLOCKL(db->status),
+ dma_sync_single_for_cpu(lan966x->dma_dev,
+ (dma_addr_t)fdma_db_dataptr_get(db) +
+ XDP_PACKET_HEADROOM,
+ fdma_db_len_get(db),
DMA_FROM_DEVICE);
lan966x_ifh_get_src_port(page_address(page) + XDP_PACKET_HEADROOM,
@@ -419,7 +419,7 @@ static int lan966x_fdma_rx_check_frame(struct lan966x_rx *rx, u64 *src_port)
if (!lan966x_xdp_port_present(port))
return FDMA_PASS;
- return lan966x_xdp_run(port, page, FDMA_DCB_STATUS_BLOCKL(db->status));
+ return lan966x_xdp_run(port, page, fdma_db_len_get(db));
}
static struct sk_buff *lan966x_fdma_rx_get_frame(struct lan966x_rx *rx,
@@ -443,7 +443,7 @@ static struct sk_buff *lan966x_fdma_rx_get_frame(struct lan966x_rx *rx,
skb_mark_for_recycle(skb);
skb_reserve(skb, XDP_PACKET_HEADROOM);
- skb_put(skb, FDMA_DCB_STATUS_BLOCKL(db->status));
+ skb_put(skb, fdma_db_len_get(db));
lan966x_ifh_get_timestamp(skb->data, &timestamp);
@@ -475,7 +475,7 @@ free_page:
return NULL;
}
-static int lan966x_fdma_napi_poll(struct napi_struct *napi, int weight)
+int lan966x_fdma_napi_poll(struct napi_struct *napi, int weight)
{
struct lan966x *lan966x = container_of(napi, struct lan966x, napi);
struct lan966x_rx *rx = &lan966x->rx;
@@ -588,7 +588,7 @@ static int lan966x_fdma_get_next_dcb(struct lan966x_tx *tx)
return -1;
}
-static void lan966x_fdma_tx_start(struct lan966x_tx *tx)
+void lan966x_fdma_tx_start(struct lan966x_tx *tx)
{
struct lan966x *lan966x = tx->lan966x;
@@ -640,11 +640,11 @@ int lan966x_fdma_xmit_xdpf(struct lan966x_port *port, void *ptr, u32 len)
lan966x_ifh_set_bypass(ifh, 1);
lan966x_ifh_set_port(ifh, BIT_ULL(port->chip_port));
- dma_addr = dma_map_single(lan966x->dev,
+ dma_addr = dma_map_single(lan966x->dma_dev,
xdpf->data - IFH_LEN_BYTES,
xdpf->len + IFH_LEN_BYTES,
DMA_TO_DEVICE);
- if (dma_mapping_error(lan966x->dev, dma_addr)) {
+ if (dma_mapping_error(lan966x->dma_dev, dma_addr)) {
ret = NETDEV_TX_OK;
goto out;
}
@@ -660,7 +660,7 @@ int lan966x_fdma_xmit_xdpf(struct lan966x_port *port, void *ptr, u32 len)
lan966x_ifh_set_port(ifh, BIT_ULL(port->chip_port));
dma_addr = page_pool_get_dma_addr(page);
- dma_sync_single_for_device(lan966x->dev,
+ dma_sync_single_for_device(lan966x->dma_dev,
dma_addr + XDP_PACKET_HEADROOM,
len + IFH_LEN_BYTES,
DMA_TO_DEVICE);
@@ -739,9 +739,9 @@ int lan966x_fdma_xmit(struct sk_buff *skb, __be32 *ifh, struct net_device *dev)
memcpy(skb->data, ifh, IFH_LEN_BYTES);
skb_put(skb, 4);
- dma_addr = dma_map_single(lan966x->dev, skb->data, skb->len,
+ dma_addr = dma_map_single(lan966x->dma_dev, skb->data, skb->len,
DMA_TO_DEVICE);
- if (dma_mapping_error(lan966x->dev, dma_addr)) {
+ if (dma_mapping_error(lan966x->dma_dev, dma_addr)) {
dev->stats.tx_dropped++;
err = NETDEV_TX_OK;
goto release;
@@ -806,7 +806,7 @@ static int lan966x_fdma_get_max_mtu(struct lan966x *lan966x)
return max_mtu;
}
-static int lan966x_qsys_sw_status(struct lan966x *lan966x)
+int lan966x_qsys_sw_status(struct lan966x *lan966x)
{
return lan_rd(lan966x, QSYS_SW_STATUS(CPU_PORT));
}
@@ -847,7 +847,7 @@ static int lan966x_fdma_reload(struct lan966x *lan966x, int new_mtu)
page_pool_put_full_page(page_pool,
old_pages[i][j], false);
- fdma_free_coherent(lan966x->dev, &fdma_rx_old);
+ fdma_free_coherent(lan966x->dma_dev, &fdma_rx_old);
page_pool_destroy(page_pool);
@@ -876,6 +876,9 @@ restore:
MEM_TYPE_PAGE_POOL, page_pool);
}
+ lan966x_fdma_llp_configure(lan966x, lan966x->rx.fdma.dma,
+ lan966x->rx.fdma.channel_id);
+
lan966x_fdma_rx_start(&lan966x->rx);
lan966x_fdma_wakeup_netdev(lan966x);
@@ -885,13 +888,9 @@ restore:
return err;
}
-static int lan966x_fdma_get_max_frame(struct lan966x *lan966x)
+int lan966x_fdma_get_max_frame(struct lan966x *lan966x)
{
- return lan966x_fdma_get_max_mtu(lan966x) +
- IFH_LEN_BYTES +
- SKB_DATA_ALIGN(sizeof(struct skb_shared_info)) +
- VLAN_HLEN * 2 +
- XDP_PACKET_HEADROOM;
+ return lan966x_fdma_get_max_mtu(lan966x) + FDMA_OVERHEAD;
}
static int __lan966x_fdma_reload(struct lan966x *lan966x, int max_mtu)
@@ -949,7 +948,7 @@ void lan966x_fdma_netdev_init(struct lan966x *lan966x, struct net_device *dev)
return;
lan966x->fdma_ndev = dev;
- netif_napi_add(dev, &lan966x->napi, lan966x_fdma_napi_poll);
+ netif_napi_add(dev, &lan966x->napi, lan966x->ops->fdma_poll);
napi_enable(&lan966x->napi);
}
@@ -994,7 +993,7 @@ int lan966x_fdma_init(struct lan966x *lan966x)
err = lan966x_fdma_tx_alloc(&lan966x->tx);
if (err) {
- fdma_free_coherent(lan966x->dev, &lan966x->rx.fdma);
+ fdma_free_coherent(lan966x->dma_dev, &lan966x->rx.fdma);
page_pool_destroy(lan966x->rx.page_pool);
return err;
}
@@ -1016,7 +1015,7 @@ void lan966x_fdma_deinit(struct lan966x *lan966x)
napi_disable(&lan966x->napi);
lan966x_fdma_rx_free_pages(&lan966x->rx);
- fdma_free_coherent(lan966x->dev, &lan966x->rx.fdma);
+ fdma_free_coherent(lan966x->dma_dev, &lan966x->rx.fdma);
page_pool_destroy(lan966x->rx.page_pool);
lan966x_fdma_tx_free(&lan966x->tx);
}
diff --git a/drivers/net/ethernet/microchip/lan966x/lan966x_fdma_pci.c b/drivers/net/ethernet/microchip/lan966x/lan966x_fdma_pci.c
new file mode 100644
index 000000000000..949994874ed9
--- /dev/null
+++ b/drivers/net/ethernet/microchip/lan966x/lan966x_fdma_pci.c
@@ -0,0 +1,731 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+#include <linux/bpf_trace.h>
+#include <linux/ip.h>
+#include <net/addrconf.h>
+
+#include "fdma_api.h"
+#include "lan966x_main.h"
+
+static int lan966x_fdma_pci_dataptr_cb(struct fdma *fdma, int dcb, int db,
+ u64 *dataptr)
+{
+ u64 addr;
+
+ addr = fdma_dataptr_dma_addr_contiguous(fdma, dcb, db);
+
+ *dataptr = fdma_pci_atu_translate_addr(fdma->atu_region, addr);
+
+ return 0;
+}
+
+static int lan966x_fdma_pci_nextptr_cb(struct fdma *fdma, int dcb, u64 *nextptr)
+{
+ u64 addr;
+
+ fdma_nextptr_cb(fdma, dcb, &addr);
+
+ *nextptr = fdma_pci_atu_translate_addr(fdma->atu_region, addr);
+
+ return 0;
+}
+
+/* Stop the TX queues on every port, so nothing feeds the injection channel
+ * while it is torn down or resized.
+ */
+static void lan966x_fdma_tx_disable_netdev(struct lan966x *lan966x)
+{
+ struct lan966x_port *port;
+ int i;
+
+ for (i = 0; i < lan966x->num_phys_ports; ++i) {
+ port = lan966x->ports[i];
+ if (!port)
+ continue;
+
+ netif_tx_disable(port->dev);
+ }
+}
+
+static int lan966x_fdma_pci_rx_alloc(struct lan966x_rx *rx)
+{
+ struct lan966x *lan966x = rx->lan966x;
+ struct fdma *fdma = &rx->fdma;
+ int err;
+
+ err = fdma_alloc_coherent_and_map(lan966x->dma_dev, fdma,
+ &lan966x->atu);
+ if (err)
+ return err;
+
+ err = fdma_dcbs_init(fdma,
+ FDMA_DCB_INFO_DATAL(fdma->db_size - XDP_PACKET_HEADROOM),
+ FDMA_DCB_STATUS_INTR);
+ if (err) {
+ fdma_free_coherent_and_unmap(lan966x->dma_dev, fdma);
+ return err;
+ }
+
+ lan966x_fdma_llp_configure(lan966x,
+ fdma->atu_region->base_addr,
+ fdma->channel_id);
+
+ return 0;
+}
+
+static int lan966x_fdma_pci_tx_alloc(struct lan966x_tx *tx)
+{
+ struct lan966x *lan966x = tx->lan966x;
+ struct fdma *fdma = &tx->fdma;
+ int err;
+
+ err = fdma_alloc_coherent_and_map(lan966x->dma_dev, fdma,
+ &lan966x->atu);
+ if (err)
+ return err;
+
+ err = fdma_dcbs_init(fdma,
+ FDMA_DCB_INFO_DATAL(fdma->db_size),
+ FDMA_DCB_STATUS_DONE);
+ if (err) {
+ fdma_free_coherent_and_unmap(lan966x->dma_dev, fdma);
+ return err;
+ }
+
+ lan966x_fdma_llp_configure(lan966x,
+ fdma->atu_region->base_addr,
+ fdma->channel_id);
+
+ return 0;
+}
+
+static int lan966x_fdma_pci_get_next_dcb(struct fdma *fdma)
+{
+ struct fdma_db *db;
+
+ for (int i = 0; i < fdma->n_dcbs; i++) {
+ db = fdma_db_get(fdma, i, 0);
+
+ if (!fdma_db_is_done(db))
+ continue;
+ if (fdma_is_last(fdma, &fdma->dcbs[i]))
+ continue;
+
+ return i;
+ }
+
+ return -ENOSPC;
+}
+
+/* TX slot layout (sizes in bytes):
+ *
+ * +---------------------+-----+---------+-----+
+ * | XDP_PACKET_HEADROOM | IFH | payload | FCS |
+ * | 256 | 28 | len | 4 |
+ * +---------------------+-----+---------+-----+
+ * |<---------------- db_size ----------------->|
+ *
+ * Return true if the frame plus required overhead fits.
+ */
+static bool lan966x_fdma_pci_tx_size_fits(struct fdma *fdma, u32 len)
+{
+ return XDP_PACKET_HEADROOM + IFH_LEN_BYTES + len + ETH_FCS_LEN <=
+ fdma->db_size;
+}
+
+/* Return true if blockl is a valid RX frame size. */
+static bool lan966x_fdma_pci_rx_size_fits(struct fdma *fdma, u32 blockl)
+{
+ return blockl >= IFH_LEN_BYTES + ETH_HLEN + ETH_FCS_LEN &&
+ blockl <= fdma->db_size - XDP_PACKET_HEADROOM;
+}
+
+static int lan966x_fdma_pci_xmit_xdpf(struct lan966x_port *port,
+ void *ptr, u32 len)
+{
+ struct lan966x *lan966x = port->lan966x;
+ struct lan966x_tx *tx = &lan966x->tx;
+ struct fdma *fdma = &tx->fdma;
+ int next_to_use, ret = 0;
+ void *virt_addr;
+
+ spin_lock(&lan966x->tx_lock);
+
+ next_to_use = lan966x_fdma_pci_get_next_dcb(fdma);
+
+ if (next_to_use < 0) {
+ netif_stop_queue(port->dev);
+ port->dev->stats.tx_dropped++;
+ ret = NETDEV_TX_BUSY;
+ goto out;
+ }
+
+ /* Only the upper bound is enforced: XDP owns the frame contents and
+ * length, so a program that shrinks below ETH_ZLEN gets what it asked
+ * for.
+ */
+ if (!lan966x_fdma_pci_tx_size_fits(fdma, len)) {
+ port->dev->stats.tx_dropped++;
+ ret = -EINVAL;
+ goto out;
+ }
+
+ /* virt_addr points to the IFH. */
+ virt_addr = fdma_dataptr_virt_addr_contiguous(fdma, next_to_use, 0);
+
+ /* Construct a fresh IFH. */
+ memset(virt_addr, 0, IFH_LEN_BYTES);
+ lan966x_ifh_set_bypass(virt_addr, 1);
+ lan966x_ifh_set_port(virt_addr, BIT_ULL(port->chip_port));
+
+ /* Copy the (post-XDP) frame after the IFH. */
+ memcpy(virt_addr + IFH_LEN_BYTES, ptr, len);
+
+ /* Order frame write before DCB status write below. */
+ dma_wmb();
+
+ /* Reserve ETH_FCS_LEN for the HW-inserted FCS (len is FCS-stripped). */
+ fdma_dcb_add(fdma,
+ next_to_use,
+ 0,
+ FDMA_DCB_STATUS_INTR |
+ FDMA_DCB_STATUS_SOF |
+ FDMA_DCB_STATUS_EOF |
+ FDMA_DCB_STATUS_BLOCKO(0) |
+ FDMA_DCB_STATUS_BLOCKL(IFH_LEN_BYTES + len + ETH_FCS_LEN));
+
+ /* Start the transmission. */
+ lan966x_fdma_tx_start(tx);
+
+ port->dev->stats.tx_bytes += len;
+ port->dev->stats.tx_packets++;
+
+out:
+ spin_unlock(&lan966x->tx_lock);
+
+ return ret;
+}
+
+static int lan966x_xdp_pci_run(struct lan966x_port *port, void *data,
+ u32 data_len, void **xdp_data, u32 *xdp_len)
+{
+ /* Read once so the NULL check and bpf_prog_run_xdp() see the same
+ * pointer.
+ */
+ struct bpf_prog *xdp_prog = READ_ONCE(port->xdp_prog);
+ struct lan966x *lan966x = port->lan966x;
+ struct fdma *fdma = &lan966x->rx.fdma;
+ struct xdp_buff xdp;
+ u32 act;
+
+ if (!xdp_prog)
+ return FDMA_PASS;
+
+ xdp_init_buff(&xdp, fdma->db_size, &port->xdp_rxq);
+
+ /* hard_start is set to slot start (virt_addr is XDP_PACKET_HEADROOM
+ * into the slot). Headroom includes the IFH; BPF may grow into it
+ * via adjust_head. IFH is rebuilt on XDP_TX and unread on XDP_PASS.
+ */
+ xdp_prepare_buff(&xdp,
+ data - XDP_PACKET_HEADROOM,
+ XDP_PACKET_HEADROOM + IFH_LEN_BYTES,
+ data_len,
+ false);
+
+ act = bpf_prog_run_xdp(xdp_prog, &xdp);
+
+ *xdp_data = xdp.data;
+ *xdp_len = xdp.data_end - xdp.data;
+
+ switch (act) {
+ case XDP_PASS:
+ return FDMA_PASS;
+ case XDP_TX:
+ return lan966x_fdma_pci_xmit_xdpf(port, *xdp_data, *xdp_len) ?
+ FDMA_DROP : FDMA_TX;
+ default:
+ bpf_warn_invalid_xdp_action(port->dev, xdp_prog, act);
+ fallthrough;
+ case XDP_ABORTED:
+ trace_xdp_exception(port->dev, xdp_prog, act);
+ fallthrough;
+ case XDP_DROP:
+ return FDMA_DROP;
+ }
+}
+
+static int lan966x_fdma_pci_rx_check_frame(struct lan966x_rx *rx, u64 *src_port,
+ void **data, u32 *data_len)
+{
+ struct lan966x *lan966x = rx->lan966x;
+ struct fdma *fdma = &rx->fdma;
+ struct lan966x_port *port;
+ struct fdma_db *db;
+ void *virt_addr;
+ u32 blockl;
+
+ /* virt_addr points to the IFH. */
+ virt_addr = fdma_dataptr_virt_addr_contiguous(fdma,
+ fdma->dcb_index,
+ fdma->db_index);
+
+ lan966x_ifh_get_src_port(virt_addr, src_port);
+
+ if (*src_port >= lan966x->num_phys_ports)
+ return FDMA_ERROR;
+
+ port = lan966x->ports[*src_port];
+ if (!port)
+ return FDMA_ERROR;
+
+ db = fdma_db_next_get(fdma);
+
+ /* BLOCKL is a 16-bit HW-populated field; reject obviously-bad
+ * values before they feed memcpy/XDP sizes.
+ */
+ blockl = fdma_db_len_get(db);
+ if (!lan966x_fdma_pci_rx_size_fits(fdma, blockl))
+ return FDMA_ERROR;
+
+ /* Present the Ethernet frame (no IFH, no FCS). HW re-inserts the
+ * FCS on TX; see lan966x_fdma_pci_xmit_xdpf(). May be overridden
+ * by XDP. The FCS strip is unconditional because NETIF_F_RXFCS
+ * is not advertised in hw_features.
+ */
+ *data = virt_addr + IFH_LEN_BYTES;
+ *data_len = blockl - IFH_LEN_BYTES - ETH_FCS_LEN;
+
+ return lan966x_xdp_pci_run(port, virt_addr, *data_len, data, data_len);
+}
+
+static bool lan966x_fdma_pci_hw_offload(struct lan966x *lan966x, u32 port,
+ struct sk_buff **pskb)
+{
+ struct sk_buff *skb = *pskb;
+ u32 val;
+
+ val = lan_rd(lan966x, ANA_CPU_FWD_CFG(port));
+ if (!(val & (ANA_CPU_FWD_CFG_IGMP_REDIR_ENA |
+ ANA_CPU_FWD_CFG_MLD_REDIR_ENA)))
+ return true;
+
+ if (eth_type_vlan(skb->protocol)) {
+ skb = skb_vlan_untag(skb);
+ *pskb = skb;
+ if (unlikely(!skb))
+ return false;
+ }
+
+ if (skb->protocol == htons(ETH_P_IP) &&
+ ip_hdr(skb)->protocol == IPPROTO_IGMP)
+ return false;
+
+ if (IS_ENABLED(CONFIG_IPV6) &&
+ skb->protocol == htons(ETH_P_IPV6) &&
+ ipv6_addr_is_multicast(&ipv6_hdr(skb)->daddr) &&
+ !ipv6_mc_check_mld(skb))
+ return false;
+
+ return true;
+}
+
+static struct sk_buff *lan966x_fdma_pci_rx_get_frame(struct lan966x_rx *rx,
+ u64 src_port, void *data,
+ u32 data_len)
+{
+ struct lan966x *lan966x = rx->lan966x;
+ struct sk_buff *skb;
+
+ skb = napi_alloc_skb(&lan966x->napi, data_len);
+ if (unlikely(!skb))
+ return NULL;
+
+ memcpy(skb->data, data, data_len);
+
+ skb_put(skb, data_len);
+
+ skb->dev = lan966x->ports[src_port]->dev;
+
+ skb->protocol = eth_type_trans(skb, skb->dev);
+
+ if (lan966x->bridge_mask & BIT(src_port)) {
+ skb->offload_fwd_mark = 1;
+
+ skb_reset_network_header(skb);
+ if (!lan966x_fdma_pci_hw_offload(lan966x, src_port, &skb)) {
+ if (unlikely(!skb))
+ return NULL;
+ skb->offload_fwd_mark = 0;
+ }
+ }
+
+ skb->dev->stats.rx_bytes += skb->len;
+ skb->dev->stats.rx_packets++;
+
+ return skb;
+}
+
+static int lan966x_fdma_pci_xmit(struct sk_buff *skb, __be32 *ifh,
+ struct net_device *dev)
+{
+ struct lan966x_port *port = netdev_priv(dev);
+ struct lan966x *lan966x = port->lan966x;
+ struct lan966x_tx *tx = &lan966x->tx;
+ struct fdma *fdma = &tx->fdma;
+ int next_to_use;
+ void *virt_addr;
+
+ next_to_use = lan966x_fdma_pci_get_next_dcb(fdma);
+
+ if (next_to_use < 0) {
+ netif_stop_queue(dev);
+ return NETDEV_TX_BUSY;
+ }
+
+ if (skb_put_padto(skb, ETH_ZLEN)) {
+ dev->stats.tx_dropped++;
+ return NETDEV_TX_OK;
+ }
+
+ if (!lan966x_fdma_pci_tx_size_fits(fdma, skb->len)) {
+ dev_kfree_skb_any(skb);
+ dev->stats.tx_dropped++;
+ return NETDEV_TX_OK;
+ }
+
+ skb_tx_timestamp(skb);
+
+ /* virt_addr points to the IFH. */
+ virt_addr = fdma_dataptr_virt_addr_contiguous(fdma, next_to_use, 0);
+ memcpy(virt_addr, ifh, IFH_LEN_BYTES);
+ memcpy(virt_addr + IFH_LEN_BYTES, skb->data, skb->len);
+
+ /* Order frame write before DCB status write below. */
+ dma_wmb();
+
+ fdma_dcb_add(fdma,
+ next_to_use,
+ 0,
+ FDMA_DCB_STATUS_INTR |
+ FDMA_DCB_STATUS_SOF |
+ FDMA_DCB_STATUS_EOF |
+ FDMA_DCB_STATUS_BLOCKO(0) |
+ FDMA_DCB_STATUS_BLOCKL(IFH_LEN_BYTES + skb->len + ETH_FCS_LEN));
+
+ /* Start the transmission. */
+ lan966x_fdma_tx_start(tx);
+
+ dev->stats.tx_bytes += skb->len;
+ dev->stats.tx_packets++;
+
+ /* Safe to free: PTP is not supported on the PCIe path yet,
+ * so lan966x->ptp is always 0 here.
+ */
+ dev_consume_skb_any(skb);
+
+ return NETDEV_TX_OK;
+}
+
+static int lan966x_fdma_pci_napi_poll(struct napi_struct *napi, int weight)
+{
+ struct lan966x *lan966x = container_of(napi, struct lan966x, napi);
+ struct lan966x_rx *rx = &lan966x->rx;
+ struct fdma *fdma = &rx->fdma;
+ int dcb_reload, old_dcb;
+ struct sk_buff *skb;
+ int counter = 0;
+ u64 src_port;
+ u32 data_len;
+ void *data;
+
+ /* Wake any stopped TX queues if a TX DCB is available. */
+ spin_lock(&lan966x->tx_lock);
+ if (lan966x_fdma_pci_get_next_dcb(&lan966x->tx.fdma) >= 0)
+ lan966x_fdma_wakeup_netdev(lan966x);
+ spin_unlock(&lan966x->tx_lock);
+
+ dcb_reload = fdma->dcb_index;
+
+ /* Get all received skbs. */
+ while (counter < weight) {
+ if (!fdma_has_frames(fdma))
+ break;
+ /* Order DONE read before DCB/frame reads below. */
+ dma_rmb();
+ counter++;
+ switch (lan966x_fdma_pci_rx_check_frame(rx,
+ &src_port,
+ &data,
+ &data_len)) {
+ case FDMA_PASS:
+ break;
+ case FDMA_ERROR:
+ /* No rx_dropped increment here because src_port is
+ * invalid.
+ */
+ fdma_dcb_advance(fdma);
+ continue;
+ case FDMA_TX:
+ fdma_dcb_advance(fdma);
+ continue;
+ case FDMA_DROP:
+ fdma_dcb_advance(fdma);
+ continue;
+ }
+ skb = lan966x_fdma_pci_rx_get_frame(rx,
+ src_port,
+ data,
+ data_len);
+ fdma_dcb_advance(fdma);
+ if (!skb) {
+ lan966x->ports[src_port]->dev->stats.rx_dropped++;
+ continue;
+ }
+
+ napi_gro_receive(&lan966x->napi, skb);
+ }
+ while (dcb_reload != fdma->dcb_index) {
+ old_dcb = dcb_reload;
+ dcb_reload++;
+ dcb_reload &= fdma->n_dcbs - 1;
+
+ fdma_dcb_add(fdma,
+ old_dcb,
+ FDMA_DCB_INFO_DATAL(fdma->db_size - XDP_PACKET_HEADROOM),
+ FDMA_DCB_STATUS_INTR);
+
+ lan966x_fdma_rx_reload(rx);
+ }
+
+ if (counter < weight && napi_complete_done(napi, counter))
+ lan_wr(0xff, lan966x, FDMA_INTR_DB_ENA);
+
+ return counter;
+}
+
+static int lan966x_fdma_pci_init(struct lan966x *lan966x)
+{
+ struct fdma *rx_fdma = &lan966x->rx.fdma;
+ struct fdma *tx_fdma = &lan966x->tx.fdma;
+ int err;
+
+ if (!lan966x->fdma)
+ return 0;
+
+ lan_wr(FDMA_CTRL_NRESET_SET(0), lan966x, FDMA_CTRL);
+ lan_wr(FDMA_CTRL_NRESET_SET(1), lan966x, FDMA_CTRL);
+
+ fdma_pci_atu_init(&lan966x->atu, lan966x->regs[TARGET_PCIE_DBI]);
+
+ lan966x->rx.lan966x = lan966x;
+ lan966x->rx.max_mtu = lan966x_fdma_get_max_frame(lan966x);
+ rx_fdma->channel_id = FDMA_XTR_CHANNEL;
+ rx_fdma->n_dcbs = FDMA_PCI_DCB_MAX;
+ rx_fdma->n_dbs = FDMA_RX_DCB_MAX_DBS;
+ rx_fdma->priv = lan966x;
+ rx_fdma->db_size = FDMA_PCI_DB_SIZE(lan966x->rx.max_mtu);
+ rx_fdma->size = fdma_get_size_contiguous(rx_fdma);
+ rx_fdma->ops.nextptr_cb = &lan966x_fdma_pci_nextptr_cb;
+ rx_fdma->ops.dataptr_cb = &lan966x_fdma_pci_dataptr_cb;
+
+ lan966x->tx.lan966x = lan966x;
+ tx_fdma->channel_id = FDMA_INJ_CHANNEL;
+ tx_fdma->n_dcbs = FDMA_PCI_DCB_MAX;
+ tx_fdma->n_dbs = FDMA_TX_DCB_MAX_DBS;
+ tx_fdma->priv = lan966x;
+ tx_fdma->db_size = FDMA_PCI_DB_SIZE(lan966x->rx.max_mtu);
+ tx_fdma->size = fdma_get_size_contiguous(tx_fdma);
+ tx_fdma->ops.nextptr_cb = &lan966x_fdma_pci_nextptr_cb;
+ tx_fdma->ops.dataptr_cb = &lan966x_fdma_pci_dataptr_cb;
+
+ err = lan966x_fdma_pci_rx_alloc(&lan966x->rx);
+ if (err)
+ return err;
+
+ err = lan966x_fdma_pci_tx_alloc(&lan966x->tx);
+ if (err) {
+ fdma_free_coherent_and_unmap(lan966x->dma_dev, rx_fdma);
+ return err;
+ }
+
+ lan966x_fdma_rx_start(&lan966x->rx);
+
+ return 0;
+}
+
+/* Reset existing rx and tx buffers. */
+static void lan966x_fdma_pci_reset_mem(struct lan966x *lan966x)
+{
+ struct lan966x_rx *rx = &lan966x->rx;
+ struct lan966x_tx *tx = &lan966x->tx;
+
+ memset(rx->fdma.dcbs, 0, rx->fdma.size);
+ memset(tx->fdma.dcbs, 0, tx->fdma.size);
+
+ fdma_dcbs_init(&rx->fdma,
+ FDMA_DCB_INFO_DATAL(rx->fdma.db_size - XDP_PACKET_HEADROOM),
+ FDMA_DCB_STATUS_INTR);
+
+ fdma_dcbs_init(&tx->fdma,
+ FDMA_DCB_INFO_DATAL(tx->fdma.db_size),
+ FDMA_DCB_STATUS_DONE);
+
+ lan966x_fdma_llp_configure(lan966x,
+ tx->fdma.atu_region->base_addr,
+ tx->fdma.channel_id);
+ lan966x_fdma_llp_configure(lan966x,
+ rx->fdma.atu_region->base_addr,
+ rx->fdma.channel_id);
+}
+
+/* Wake all TX queues on every port (undoes lan966x_fdma_tx_disable_netdev). */
+static void lan966x_fdma_pci_wakeup_netdev(struct lan966x *lan966x)
+{
+ for (int i = 0; i < lan966x->num_phys_ports; ++i) {
+ struct lan966x_port *port = lan966x->ports[i];
+
+ if (port)
+ netif_tx_wake_all_queues(port->dev);
+ }
+}
+
+static int lan966x_fdma_pci_reload(struct lan966x *lan966x, int new_mtu)
+{
+ struct fdma tx_fdma_old = lan966x->tx.fdma;
+ struct fdma rx_fdma_old = lan966x->rx.fdma;
+ u32 old_mtu = lan966x->rx.max_mtu;
+ int err;
+
+ napi_disable(&lan966x->napi);
+ lan966x_fdma_tx_disable_netdev(lan966x);
+ lan966x_fdma_rx_disable(&lan966x->rx);
+ lan966x_fdma_tx_disable(&lan966x->tx);
+
+ lan966x->rx.max_mtu = new_mtu;
+
+ /* Must be NULL'ed in order to realloc them. */
+ lan966x->rx.fdma.atu_region = NULL;
+ lan966x->tx.fdma.atu_region = NULL;
+
+ lan966x->tx.fdma.db_size = FDMA_PCI_DB_SIZE(lan966x->rx.max_mtu);
+ lan966x->tx.fdma.size = fdma_get_size_contiguous(&lan966x->tx.fdma);
+ lan966x->rx.fdma.db_size = FDMA_PCI_DB_SIZE(lan966x->rx.max_mtu);
+ lan966x->rx.fdma.size = fdma_get_size_contiguous(&lan966x->rx.fdma);
+
+ err = lan966x_fdma_pci_rx_alloc(&lan966x->rx);
+ if (err)
+ goto restore;
+
+ err = lan966x_fdma_pci_tx_alloc(&lan966x->tx);
+ if (err) {
+ fdma_free_coherent_and_unmap(lan966x->dma_dev,
+ &lan966x->rx.fdma);
+ goto restore;
+ }
+
+ /* Free and unmap old memory. */
+ fdma_free_coherent_and_unmap(lan966x->dma_dev, &rx_fdma_old);
+ fdma_free_coherent_and_unmap(lan966x->dma_dev, &tx_fdma_old);
+
+ /* Order matters: napi_enable() must precede the wakes, or a TX that
+ * completes first clears FDMA_INTR_DB_ENA with nothing scheduled to
+ * restore it, leaving RX dead until the next reload.
+ */
+ napi_enable(&lan966x->napi);
+ lan966x_fdma_rx_start(&lan966x->rx);
+ lan966x_fdma_pci_wakeup_netdev(lan966x);
+
+ return err;
+restore:
+
+ /* No new buffers are allocated at this point. Use the old buffers,
+ * but reset them before starting the FDMA again.
+ */
+
+ memcpy(&lan966x->tx.fdma, &tx_fdma_old, sizeof(struct fdma));
+ memcpy(&lan966x->rx.fdma, &rx_fdma_old, sizeof(struct fdma));
+
+ lan966x->rx.max_mtu = old_mtu;
+
+ lan966x_fdma_pci_reset_mem(lan966x);
+
+ napi_enable(&lan966x->napi);
+ lan966x_fdma_rx_start(&lan966x->rx);
+ lan966x_fdma_pci_wakeup_netdev(lan966x);
+
+ return err;
+}
+
+static int __lan966x_fdma_pci_reload(struct lan966x *lan966x, int max_mtu)
+{
+ int err;
+ u32 val;
+
+ /* Disable the CPU port. */
+ lan_rmw(QSYS_SW_PORT_MODE_PORT_ENA_SET(0),
+ QSYS_SW_PORT_MODE_PORT_ENA,
+ lan966x, QSYS_SW_PORT_MODE(CPU_PORT));
+
+ /* Flush the CPU queues. */
+ readx_poll_timeout(lan966x_qsys_sw_status,
+ lan966x,
+ val,
+ !(QSYS_SW_STATUS_EQ_AVAIL_GET(val)),
+ READL_SLEEP_US, READL_TIMEOUT_US);
+
+ /* Add a sleep in case there are frames between the queues and the CPU
+ * port
+ */
+ usleep_range(USEC_PER_MSEC, 2 * USEC_PER_MSEC);
+
+ err = lan966x_fdma_pci_reload(lan966x, max_mtu);
+
+ /* Enable back the CPU port. */
+ lan_rmw(QSYS_SW_PORT_MODE_PORT_ENA_SET(1),
+ QSYS_SW_PORT_MODE_PORT_ENA,
+ lan966x, QSYS_SW_PORT_MODE(CPU_PORT));
+
+ return err;
+}
+
+static int lan966x_fdma_pci_resize(struct lan966x *lan966x)
+{
+ int max_mtu;
+
+ /* Nothing to resize until fdma_pci_init() has built the rings; it
+ * sizes them from DEV_MAC_MAXLEN_CFG, which the caller already set.
+ */
+ if (!lan966x->rx.lan966x)
+ return 0;
+
+ max_mtu = lan966x_fdma_get_max_frame(lan966x);
+ if (max_mtu == lan966x->rx.max_mtu)
+ return 0;
+
+ return __lan966x_fdma_pci_reload(lan966x, max_mtu);
+}
+
+static void lan966x_fdma_pci_deinit(struct lan966x *lan966x)
+{
+ if (!lan966x->fdma)
+ return;
+
+ if (lan966x->fdma_ndev)
+ napi_disable(&lan966x->napi);
+
+ lan966x_fdma_tx_disable_netdev(lan966x);
+ lan966x_fdma_rx_disable(&lan966x->rx);
+ lan966x_fdma_tx_disable(&lan966x->tx);
+
+ fdma_free_coherent_and_unmap(lan966x->dma_dev, &lan966x->rx.fdma);
+ fdma_free_coherent_and_unmap(lan966x->dma_dev, &lan966x->tx.fdma);
+}
+
+const struct lan966x_fdma_ops lan966x_fdma_pci_ops = {
+ .fdma_init = &lan966x_fdma_pci_init,
+ .fdma_deinit = &lan966x_fdma_pci_deinit,
+ .fdma_xmit = &lan966x_fdma_pci_xmit,
+ .fdma_poll = &lan966x_fdma_pci_napi_poll,
+ .fdma_resize = &lan966x_fdma_pci_resize,
+};
diff --git a/drivers/net/ethernet/microchip/lan966x/lan966x_main.c b/drivers/net/ethernet/microchip/lan966x/lan966x_main.c
index 1179a6e127c5..e4c9c5812cca 100644
--- a/drivers/net/ethernet/microchip/lan966x/lan966x_main.c
+++ b/drivers/net/ethernet/microchip/lan966x/lan966x_main.c
@@ -7,6 +7,7 @@
#include <linux/ip.h>
#include <linux/of.h>
#include <linux/of_net.h>
+#include <linux/pci.h>
#include <linux/phy/phy.h>
#include <linux/platform_device.h>
#include <linux/reset.h>
@@ -26,6 +27,14 @@
#define IO_RANGES 2
+static const struct lan966x_fdma_ops lan966x_fdma_ops = {
+ .fdma_init = &lan966x_fdma_init,
+ .fdma_deinit = &lan966x_fdma_deinit,
+ .fdma_xmit = &lan966x_fdma_xmit,
+ .fdma_poll = &lan966x_fdma_napi_poll,
+ .fdma_resize = &lan966x_fdma_change_mtu,
+};
+
static const struct of_device_id lan966x_match[] = {
{ .compatible = "microchip,lan966x-switch" },
{ }
@@ -41,6 +50,7 @@ struct lan966x_main_io_resource {
static const struct lan966x_main_io_resource lan966x_main_iomap[] = {
{ TARGET_CPU, 0xc0000, 0 }, /* 0xe00c0000 */
{ TARGET_FDMA, 0xc0400, 0 }, /* 0xe00c0400 */
+ { TARGET_PCIE_DBI, 0x400000, 0 }, /* 0xe0400000 */
{ TARGET_ORG, 0, 1 }, /* 0xe2000000 */
{ TARGET_GCB, 0x4000, 1 }, /* 0xe2004000 */
{ TARGET_QS, 0x8000, 1 }, /* 0xe2008000 */
@@ -391,7 +401,7 @@ static netdev_tx_t lan966x_port_xmit(struct sk_buff *skb,
spin_lock(&lan966x->tx_lock);
if (port->lan966x->fdma)
- err = lan966x_fdma_xmit(skb, ifh, dev);
+ err = lan966x->ops->fdma_xmit(skb, ifh, dev);
else
err = lan966x_port_ifh_xmit(skb, ifh, dev);
spin_unlock(&lan966x->tx_lock);
@@ -413,7 +423,7 @@ static int lan966x_port_change_mtu(struct net_device *dev, int new_mtu)
if (!lan966x->fdma)
return 0;
- err = lan966x_fdma_change_mtu(lan966x);
+ err = lan966x->ops->fdma_resize(lan966x);
if (err) {
lan_wr(DEV_MAC_MAXLEN_CFG_MAX_LEN_SET(LAN966X_HW_MTU(old_mtu)),
lan966x, DEV_MAC_MAXLEN_CFG(port->chip_port));
@@ -813,7 +823,8 @@ static int lan966x_probe_port(struct lan966x *lan966x, u32 p,
port->chip_port = p;
lan966x->ports[p] = port;
- dev->max_mtu = ETH_MAX_MTU;
+ dev->max_mtu = lan966x_is_pci(lan966x) && lan966x->fdma ?
+ FDMA_PCI_MAX_MTU : ETH_MAX_MTU;
dev->netdev_ops = &lan966x_port_netdev_ops;
dev->ethtool_ops = &lan966x_ethtool_ops;
@@ -864,10 +875,13 @@ static int lan966x_probe_port(struct lan966x *lan966x, u32 p,
port->phylink = phylink;
- if (lan966x->fdma)
- dev->xdp_features = NETDEV_XDP_ACT_BASIC |
- NETDEV_XDP_ACT_REDIRECT |
- NETDEV_XDP_ACT_NDO_XMIT;
+ if (lan966x->fdma) {
+ dev->xdp_features = NETDEV_XDP_ACT_BASIC;
+
+ if (!lan966x_is_pci(lan966x))
+ dev->xdp_features |= NETDEV_XDP_ACT_REDIRECT |
+ NETDEV_XDP_ACT_NDO_XMIT;
+ }
err = register_netdev(dev);
if (err) {
@@ -1058,6 +1072,15 @@ static int lan966x_reset_switch(struct lan966x *lan966x)
reset_control_reset(switch_reset);
+ /* When in PCI mode, the GCB soft reset issued by the reset
+ * controller can latch spurious bits in the FDMA error and
+ * data-block stickies. Clear them before request_irq hooks up the
+ * FDMA IRQ line, otherwise the handler fires immediately on probe.
+ */
+ lan_wr(lan_rd(lan966x, FDMA_ERRORS), lan966x, FDMA_ERRORS);
+ lan_wr(lan_rd(lan966x, FDMA_INTR_ERR), lan966x, FDMA_INTR_ERR);
+ lan_wr(lan_rd(lan966x, FDMA_INTR_DB), lan966x, FDMA_INTR_DB);
+
/* Don't reinitialize the switch core, if it is already initialized. In
* case it is initialized twice, some pointers inside the queue system
* in HW will get corrupted and then after a while the queue system gets
@@ -1081,6 +1104,20 @@ static int lan966x_reset_switch(struct lan966x *lan966x)
return 0;
}
+/* When enumerated over PCIe, dev is a platform device with no
+ * iommus/dma-ranges of its own, so DMA must target the PCIe endpoint instead.
+ * The result differs from dev only in that case; lan966x_is_pci() relies on it.
+ */
+static struct device *lan966x_get_dma_dev(struct device *dev)
+{
+ for (struct device *p = dev->parent; p; p = p->parent) {
+ if (dev_is_pci(p))
+ return p;
+ }
+
+ return dev;
+}
+
static int lan966x_probe(struct platform_device *pdev)
{
struct fwnode_handle *ports, *portnp;
@@ -1094,6 +1131,10 @@ static int lan966x_probe(struct platform_device *pdev)
platform_set_drvdata(pdev, lan966x);
lan966x->dev = &pdev->dev;
+ lan966x->dma_dev = lan966x_get_dma_dev(lan966x->dev);
+
+ lan966x->ops = lan966x_is_pci(lan966x) ? &lan966x_fdma_pci_ops :
+ &lan966x_fdma_ops;
if (!device_get_mac_address(&pdev->dev, mac_addr)) {
ether_addr_copy(lan966x->base_mac, mac_addr);
@@ -1152,7 +1193,9 @@ static int lan966x_probe(struct platform_device *pdev)
if (err)
return dev_err_probe(&pdev->dev, err, "Unable to use ptp irq");
- lan966x->ptp = 1;
+ /* PTP is not supported on the PCIe path yet. */
+ if (!lan966x_is_pci(lan966x))
+ lan966x->ptp = 1;
}
lan966x->fdma_irq = platform_get_irq_byname(pdev, "fdma");
@@ -1234,7 +1277,7 @@ static int lan966x_probe(struct platform_device *pdev)
if (err)
goto cleanup_fdb;
- err = lan966x_fdma_init(lan966x);
+ err = lan966x->ops->fdma_init(lan966x);
if (err)
goto cleanup_ptp;
@@ -1247,7 +1290,7 @@ static int lan966x_probe(struct platform_device *pdev)
return 0;
cleanup_fdma:
- lan966x_fdma_deinit(lan966x);
+ lan966x->ops->fdma_deinit(lan966x);
cleanup_ptp:
lan966x_ptp_deinit(lan966x);
@@ -1275,7 +1318,7 @@ static void lan966x_remove(struct platform_device *pdev)
lan966x_taprio_deinit(lan966x);
lan966x_vcap_deinit(lan966x);
- lan966x_fdma_deinit(lan966x);
+ lan966x->ops->fdma_deinit(lan966x);
lan966x_cleanup_ports(lan966x);
cancel_delayed_work_sync(&lan966x->stats_work);
@@ -1289,9 +1332,65 @@ static void lan966x_remove(struct platform_device *pdev)
debugfs_remove_recursive(lan966x->debugfs_root);
}
+static void lan966x_shutdown(struct platform_device *pdev)
+{
+ struct lan966x *lan966x = platform_get_drvdata(pdev);
+
+ /* As a PCIe endpoint the switch is not reset by the host reboot, so it
+ * has to be quiesced here:
+ *
+ * Free the irqs and mask the sources: no source can assert INTx.
+ * Disable NAPI: the teardown must not race a poll.
+ * Stop and detach the netdevs: drains xmit, closes ndo_open and MTU.
+ * Stop the FDMA channels: waits for the engine to go idle.
+ * Unmap the ATU windows: revokes the engine's access to host memory.
+ */
+ if (!lan966x_is_pci(lan966x))
+ return;
+
+ if (lan966x->xtr_irq > 0)
+ devm_free_irq(lan966x->dev, lan966x->xtr_irq, lan966x);
+ if (lan966x->ana_irq > 0)
+ devm_free_irq(lan966x->dev, lan966x->ana_irq, lan966x);
+ if (lan966x->fdma_irq > 0)
+ devm_free_irq(lan966x->dev, lan966x->fdma_irq, lan966x);
+
+ lan_wr(0, lan966x, ANA_ANAINTR);
+
+ if (!lan966x->fdma)
+ return;
+
+ rtnl_lock();
+
+ if (lan966x->fdma_ndev)
+ napi_disable(&lan966x->napi);
+
+ for (int p = 0; p < lan966x->num_phys_ports; p++) {
+ if (!lan966x->ports[p] || !lan966x->ports[p]->dev)
+ continue;
+
+ netif_tx_disable(lan966x->ports[p]->dev);
+ netif_device_detach(lan966x->ports[p]->dev);
+ }
+
+ lan966x_fdma_rx_disable(&lan966x->rx);
+ lan966x_fdma_tx_disable(&lan966x->tx);
+
+ lan_wr(0, lan966x, FDMA_INTR_ENA);
+ lan_wr(0, lan966x, FDMA_INTR_DB_ENA);
+
+#if IS_ENABLED(CONFIG_MCHP_LAN966X_PCI)
+ fdma_pci_atu_region_unmap(lan966x->rx.fdma.atu_region);
+ fdma_pci_atu_region_unmap(lan966x->tx.fdma.atu_region);
+#endif
+
+ rtnl_unlock();
+}
+
static struct platform_driver lan966x_driver = {
.probe = lan966x_probe,
.remove = lan966x_remove,
+ .shutdown = lan966x_shutdown,
.driver = {
.name = "lan966x-switch",
.of_match_table = lan966x_match,
diff --git a/drivers/net/ethernet/microchip/lan966x/lan966x_main.h b/drivers/net/ethernet/microchip/lan966x/lan966x_main.h
index eea286c29474..1877f1916d71 100644
--- a/drivers/net/ethernet/microchip/lan966x/lan966x_main.h
+++ b/drivers/net/ethernet/microchip/lan966x/lan966x_main.h
@@ -7,6 +7,7 @@
#include <linux/etherdevice.h>
#include <linux/if_vlan.h>
#include <linux/jiffies.h>
+#include <linux/mmzone.h>
#include <linux/phy.h>
#include <linux/phylink.h>
#include <linux/ptp_clock_kernel.h>
@@ -17,6 +18,7 @@
#include <net/xdp.h>
#include <fdma_api.h>
+#include <fdma_pci.h>
#include <vcap_api.h>
#include <vcap_api_client.h>
@@ -86,6 +88,33 @@
#define FDMA_INJ_CHANNEL 0
#define FDMA_DCB_MAX 512
+/* Ring must fit in one MAX_PAGE_ORDER DMA block; 512 DCBs overflows
+ * at jumbo MTU.
+ */
+#define FDMA_PCI_DCB_MAX 256
+
+#define FDMA_OVERHEAD \
+ (IFH_LEN_BYTES + \
+ SKB_DATA_ALIGN(sizeof(struct skb_shared_info)) + \
+ VLAN_HLEN * 2 + \
+ XDP_PACKET_HEADROOM)
+
+/* Largest db_size keeping the ATU-padded ring inside one MAX_PAGE_ORDER
+ * block and within the 16-bit DCB DATAL field. Inverts ALIGN(x, R) <= L
+ * into x <= ALIGN_DOWN(L, R) to bound x directly.
+ */
+#define FDMA_PCI_DB_SIZE_MAX \
+ MIN_T(u32, \
+ (ALIGN_DOWN(PAGE_SIZE << MAX_PAGE_ORDER, \
+ FDMA_PCI_ATU_REGION_ALIGN) - \
+ FDMA_PCI_DCB_MAX * sizeof(struct fdma_dcb)) / \
+ (FDMA_PCI_DCB_MAX * FDMA_RX_DCB_MAX_DBS), \
+ ALIGN_DOWN(GENMASK(15, 0), FDMA_PCI_DB_ALIGN))
+
+#define FDMA_PCI_MAX_MTU \
+ (FDMA_PCI_DB_SIZE_MAX - FDMA_OVERHEAD - \
+ (ETH_HLEN + ETH_FCS_LEN))
+
#define SE_IDX_QUEUE 0 /* 0-79 : Queue scheduler elements */
#define SE_IDX_PORT 80 /* 80-89 : Port schedular elements */
@@ -193,6 +222,17 @@ enum vcap_is1_port_sel_rt {
VCAP_IS1_PS_RT_FOLLOW_OTHER = 7,
};
+struct lan966x;
+
+struct lan966x_fdma_ops {
+ int (*fdma_init)(struct lan966x *lan966x);
+ void (*fdma_deinit)(struct lan966x *lan966x);
+ int (*fdma_xmit)(struct sk_buff *skb, __be32 *ifh,
+ struct net_device *dev);
+ int (*fdma_poll)(struct napi_struct *napi, int weight);
+ int (*fdma_resize)(struct lan966x *lan966x);
+};
+
struct lan966x_port;
struct lan966x_rx {
@@ -270,11 +310,20 @@ struct lan966x_skb_cb {
struct lan966x {
struct device *dev;
+ /* Device used for DMA; the PCIe endpoint when enumerated over PCIe. */
+ struct device *dma_dev;
+
+ const struct lan966x_fdma_ops *ops;
+
u8 num_phys_ports;
struct lan966x_port **ports;
void __iomem *regs[NUM_TARGETS];
+#if IS_ENABLED(CONFIG_MCHP_LAN966X_PCI)
+ struct fdma_pci_atu atu;
+#endif
+
int shared_queue_sz;
u8 base_mac[ETH_ALEN];
@@ -561,6 +610,26 @@ int lan966x_fdma_init(struct lan966x *lan966x);
void lan966x_fdma_deinit(struct lan966x *lan966x);
irqreturn_t lan966x_fdma_irq_handler(int irq, void *args);
int lan966x_fdma_reload_page_pool(struct lan966x *lan966x);
+int lan966x_fdma_napi_poll(struct napi_struct *napi, int weight);
+void lan966x_fdma_llp_configure(struct lan966x *lan966x, u64 addr,
+ u8 channel_id);
+void lan966x_fdma_rx_start(struct lan966x_rx *rx);
+void lan966x_fdma_rx_disable(struct lan966x_rx *rx);
+void lan966x_fdma_rx_reload(struct lan966x_rx *rx);
+void lan966x_fdma_tx_start(struct lan966x_tx *tx);
+void lan966x_fdma_tx_disable(struct lan966x_tx *tx);
+void lan966x_fdma_wakeup_netdev(struct lan966x *lan966x);
+int lan966x_fdma_get_max_frame(struct lan966x *lan966x);
+int lan966x_qsys_sw_status(struct lan966x *lan966x);
+
+extern const struct lan966x_fdma_ops lan966x_fdma_pci_ops;
+
+/* dma_dev differs from dev only on the PCIe path. */
+static inline bool lan966x_is_pci(struct lan966x *lan966x)
+{
+ return IS_ENABLED(CONFIG_MCHP_LAN966X_PCI) &&
+ lan966x->dma_dev != lan966x->dev;
+}
int lan966x_lag_port_join(struct lan966x_port *port,
struct net_device *brport_dev,
diff --git a/drivers/net/ethernet/microchip/lan966x/lan966x_regs.h b/drivers/net/ethernet/microchip/lan966x/lan966x_regs.h
index 4b553927d2e0..4778ea217673 100644
--- a/drivers/net/ethernet/microchip/lan966x/lan966x_regs.h
+++ b/drivers/net/ethernet/microchip/lan966x/lan966x_regs.h
@@ -20,6 +20,7 @@ enum lan966x_target {
TARGET_FDMA = 21,
TARGET_GCB = 27,
TARGET_ORG = 36,
+ TARGET_PCIE_DBI = 40,
TARGET_PTP = 41,
TARGET_QS = 42,
TARGET_QSYS = 46,
@@ -1009,6 +1010,15 @@ enum lan966x_target {
#define FDMA_CH_CFG_CH_MEM_GET(x)\
FIELD_GET(FDMA_CH_CFG_CH_MEM, x)
+/* FDMA:FDMA:FDMA_CTRL */
+#define FDMA_CTRL __REG(TARGET_FDMA, 0, 1, 8, 0, 1, 428, 424, 0, 1, 4)
+
+#define FDMA_CTRL_NRESET BIT(0)
+#define FDMA_CTRL_NRESET_SET(x)\
+ FIELD_PREP(FDMA_CTRL_NRESET, x)
+#define FDMA_CTRL_NRESET_GET(x)\
+ FIELD_GET(FDMA_CTRL_NRESET, x)
+
/* FDMA:FDMA:FDMA_PORT_CTRL */
#define FDMA_PORT_CTRL(r) __REG(TARGET_FDMA, 0, 1, 8, 0, 1, 428, 376, r, 2, 4)
@@ -1039,6 +1049,21 @@ enum lan966x_target {
/* FDMA:FDMA:FDMA_INTR_ERR */
#define FDMA_INTR_ERR __REG(TARGET_FDMA, 0, 1, 8, 0, 1, 428, 400, 0, 1, 4)
+/* FDMA:FDMA:FDMA_INTR_ENA */
+#define FDMA_INTR_ENA __REG(TARGET_FDMA, 0, 1, 8, 0, 1, 428, 404, 0, 1, 4)
+
+#define FDMA_INTR_ENA_INTR_PORT_ENA GENMASK(9, 8)
+#define FDMA_INTR_ENA_INTR_PORT_ENA_SET(x)\
+ FIELD_PREP(FDMA_INTR_ENA_INTR_PORT_ENA, x)
+#define FDMA_INTR_ENA_INTR_PORT_ENA_GET(x)\
+ FIELD_GET(FDMA_INTR_ENA_INTR_PORT_ENA, x)
+
+#define FDMA_INTR_ENA_INTR_CH_ENA GENMASK(7, 0)
+#define FDMA_INTR_ENA_INTR_CH_ENA_SET(x)\
+ FIELD_PREP(FDMA_INTR_ENA_INTR_CH_ENA, x)
+#define FDMA_INTR_ENA_INTR_CH_ENA_GET(x)\
+ FIELD_GET(FDMA_INTR_ENA_INTR_CH_ENA, x)
+
/* FDMA:FDMA:FDMA_ERRORS */
#define FDMA_ERRORS __REG(TARGET_FDMA, 0, 1, 8, 0, 1, 428, 412, 0, 1, 4)
diff --git a/drivers/net/ethernet/microchip/lan966x/lan966x_xdp.c b/drivers/net/ethernet/microchip/lan966x/lan966x_xdp.c
index 9ee61db8690b..b98426afc785 100644
--- a/drivers/net/ethernet/microchip/lan966x/lan966x_xdp.c
+++ b/drivers/net/ethernet/microchip/lan966x/lan966x_xdp.c
@@ -24,6 +24,12 @@ static int lan966x_xdp_setup(struct net_device *dev, struct netdev_bpf *xdp)
old_prog = xchg(&port->xdp_prog, xdp->prog);
new_xdp = lan966x_xdp_present(lan966x);
+ /* PCIe FDMA uses contiguous buffers, so no page_pool reload
+ * is needed.
+ */
+ if (lan966x_is_pci(lan966x))
+ goto out;
+
if (old_xdp == new_xdp)
goto out;
diff --git a/drivers/net/ethernet/microchip/sparx5/Makefile b/drivers/net/ethernet/microchip/sparx5/Makefile
index d447f9e84d92..06cf583effdb 100644
--- a/drivers/net/ethernet/microchip/sparx5/Makefile
+++ b/drivers/net/ethernet/microchip/sparx5/Makefile
@@ -11,7 +11,7 @@ sparx5-switch-y := sparx5_main.o sparx5_packet.o \
sparx5_ptp.o sparx5_pgid.o sparx5_tc.o sparx5_qos.o \
sparx5_vcap_impl.o sparx5_vcap_ag_api.o sparx5_tc_flower.o \
sparx5_tc_matchall.o sparx5_pool.o sparx5_sdlb.o sparx5_police.o \
- sparx5_psfp.o sparx5_mirror.o sparx5_regs.o
+ sparx5_psfp.o sparx5_mirror.o sparx5_regs.o sparx5_tas.o
sparx5-switch-$(CONFIG_SPARX5_DCB) += sparx5_dcb.o
sparx5-switch-$(CONFIG_DEBUG_FS) += sparx5_vcap_debugfs.o
diff --git a/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c b/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c
index f3a9c71bea36..c02499d1b297 100644
--- a/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c
+++ b/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x.c
@@ -318,6 +318,8 @@ static const struct sparx5_consts lan969x_consts = {
.n_gates = 256,
.n_sdlbs = 496,
.n_dsm_cal_taxis = 5,
+ .n_tas_gcl = 3000,
+ .n_tas_lists = 60,
.buf_size = 1572864,
.qres_max_prio_idx = 315,
.qres_max_colour_idx = 323,
diff --git a/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x_calendar.c b/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x_calendar.c
index e857640df185..4442c0c1a957 100644
--- a/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x_calendar.c
+++ b/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x_calendar.c
@@ -82,6 +82,9 @@ int lan969x_dsm_calendar_calc(struct sparx5 *sparx5, u32 taxi,
struct lan969x_dsm_cal_dev_speed *speed;
int err;
+ for (u32 i = 0; i < SPX5_DSM_CAL_LEN; i++)
+ data->schedule[i] = SPX5_DSM_CAL_EMPTY;
+
/* Maximum bandwidth for this taxi */
taxi_bw = (128 * 1000000) / sparx5_clk_period(sparx5->coreclock);
@@ -125,7 +128,7 @@ int lan969x_dsm_calendar_calc(struct sparx5 *sparx5, u32 taxi,
}
if (n_devs == 0) {
- data->schedule[0] = SPX5_DSM_CAL_EMPTY;
+ data->schedule[0] = SPX5_DSM_CAL_MAX_DEVS_PER_TAXI;
return 0;
}
@@ -167,9 +170,6 @@ int lan969x_dsm_calendar_calc(struct sparx5 *sparx5, u32 taxi,
return -EINVAL;
}
- for (u32 i = 0; i < SPX5_DSM_CAL_LEN; i++)
- data->schedule[i] = SPX5_DSM_CAL_EMPTY;
-
/* Place the remaining devices */
for (u32 i = 0; i < DSM_CAL_DEV_MAX; i++) {
speed = &dev_speeds[i];
diff --git a/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c b/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c
index c7e0e58ff0f7..053d125eda65 100644
--- a/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c
+++ b/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x_fdma.c
@@ -54,10 +54,9 @@ static void lan969x_fdma_tx_clear_buf(struct sparx5 *sparx5, int weight)
{
struct fdma *fdma = &sparx5->tx.fdma;
struct sparx5_tx_buf *db;
- unsigned long flags;
int i;
- spin_lock_irqsave(&sparx5->tx_lock, flags);
+ spin_lock(&sparx5->tx_lock);
for (i = 0; i < fdma->n_dcbs; ++i) {
db = &sparx5->tx.dbs[i];
@@ -83,7 +82,7 @@ static void lan969x_fdma_tx_clear_buf(struct sparx5 *sparx5, int weight)
db->used = false;
}
- spin_unlock_irqrestore(&sparx5->tx_lock, flags);
+ spin_unlock(&sparx5->tx_lock);
}
static void lan969x_fdma_free_pages(struct sparx5_rx *rx)
diff --git a/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x_regs.c b/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x_regs.c
index 3fc2c006ba12..175fcf8bdd61 100644
--- a/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x_regs.c
+++ b/drivers/net/ethernet/microchip/sparx5/lan969x/lan969x_regs.c
@@ -96,6 +96,9 @@ const unsigned int lan969x_gaddr[GADDR_LAST] = {
[GA_HSCH_MMGT] = 36260,
[GA_HSCH_TAS_CONFIG] = 37696,
[GA_HSCH_TAS_PROFILE_CFG] = 37712,
+ [GA_HSCH_TAS_LIST_CFG] = 36288,
+ [GA_HSCH_TAS_GCL_CFG] = 36240,
+ [GA_HSCH_HSCH_TAS_STATE] = 36256,
[GA_PTP_PTP_CFG] = 512,
[GA_PTP_PTP_TOD_DOMAINS] = 528,
[GA_PTP_PHASE_DETECTOR_CTRL] = 628,
@@ -147,6 +150,7 @@ const unsigned int lan969x_gsize[GSIZE_LAST] = {
[GW_GCB_CHIP_REGS] = 180,
[GW_HSCH_TAS_CONFIG] = 16,
[GW_HSCH_TAS_PROFILE_CFG] = 68,
+ [GW_HSCH_TAS_GCL_CFG] = 16,
[GW_PTP_PHASE_DETECTOR_CTRL] = 12,
[GW_QSYS_PAUSE_CFG] = 988,
};
@@ -201,6 +205,12 @@ const unsigned int lan969x_fsize[FSIZE_LAST] = {
[FW_HSCH_HSCH_LEAK_CFG_LEAK_FIRST] = 14,
[FW_HSCH_FLUSH_CTRL_FLUSH_PORT] = 6,
[FW_HSCH_FLUSH_CTRL_FLUSH_HIER] = 14,
+ [FW_HSCH_TAS_CFG_CTRL_LIST_NUM_MAX] = 6,
+ [FW_HSCH_TAS_CFG_CTRL_LIST_NUM] = 6,
+ [FW_HSCH_TAS_CFG_CTRL_GCL_ENTRY_NUM] = 12,
+ [FW_HSCH_TAS_GATE_STATE_CTRL_HSCH_POS] = 11,
+ [FW_HSCH_TAS_STARTUP_CFG_OBSOLETE_IDX] = 6,
+ [FW_HSCH_TAS_LIST_CFG_LIST_BASE_ADDR] = 12,
[FW_LRN_COMMON_ACCESS_CTRL_CPU_ACCESS_DIRECT_ROW] = 13,
[FW_LRN_MAC_ACCESS_CFG_3_MAC_ENTRY_ISDX_LIMIT_IDX] = 8,
[FW_LRN_AUTOAGE_CFG_2_NEXT_ROW] = 13,
diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_main.c b/drivers/net/ethernet/microchip/sparx5/sparx5_main.c
index b530fb6d3d0c..e039f5369b95 100644
--- a/drivers/net/ethernet/microchip/sparx5/sparx5_main.c
+++ b/drivers/net/ethernet/microchip/sparx5/sparx5_main.c
@@ -1064,6 +1064,8 @@ static const struct sparx5_consts sparx5_consts = {
.n_gates = 1024,
.n_sdlbs = 4096,
.n_dsm_cal_taxis = 8,
+ .n_tas_gcl = 10000,
+ .n_tas_lists = 128,
.buf_size = 4194280,
.qres_max_prio_idx = 630,
.qres_max_colour_idx = 638,
diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_main.h b/drivers/net/ethernet/microchip/sparx5/sparx5_main.h
index 0f7d8440f614..8dc3a500c1c9 100644
--- a/drivers/net/ethernet/microchip/sparx5/sparx5_main.h
+++ b/drivers/net/ethernet/microchip/sparx5/sparx5_main.h
@@ -108,6 +108,7 @@ enum sparx5_feature {
#define SPX5_PRIOS 8 /* Number of priority queues */
#define SPX5_BUFFER_CELL_SZ 184 /* Cell size */
#define SPX5_BUFFER_MEMORY 4194280 /* 22795 words * 184 bytes */
+#define SPX5_TAS_ENTRIES_PER_PORT 2 /* TAS lists per port */
#define XTR_QUEUE 0
#define INJ_QUEUE 0
@@ -236,6 +237,11 @@ struct sparx5_port {
struct sk_buff_head tx_skbs;
bool is_mrouter;
struct list_head tc_templates; /* list of TC templates on this port */
+ /* TAS GCL entries owned by each of the port's two TAS lists. */
+ struct {
+ int gcl_base;
+ int gcl_count;
+ } tas_lists[SPX5_TAS_ENTRIES_PER_PORT];
};
enum sparx5_core_clockfreq {
@@ -316,6 +322,8 @@ struct sparx5_consts {
u32 n_gates; /* Number of PSFP gates */
u32 n_sdlbs; /* Number of service dual leaky buckets */
u32 n_dsm_cal_taxis; /* Number of DSM calendar taxis */
+ u32 n_tas_gcl; /* Number of TAS gate control list entries */
+ u32 n_tas_lists; /* Number of TAS lists */
u32 buf_size; /* Amount of QLIM watermark memory */
u32 qres_max_prio_idx; /* Maximum QRES prio index */
u32 qres_max_colour_idx; /* Maximum QRES colour index */
@@ -374,6 +382,8 @@ struct sparx5 {
void __iomem *regs[NUM_TARGETS];
int port_count;
struct mutex lock; /* MAC reg lock */
+ struct mutex tas_lock; /* Time Aware Shaper lock */
+ unsigned long *tas_gcl_bitmap; /* Allocated TAS GCL entries */
/* port structures are in net device */
struct sparx5_port *ports[SPX5_PORTS];
enum sparx5_core_clockfreq coreclock;
@@ -707,6 +717,24 @@ void sparx5_psfp_init(struct sparx5 *sparx5);
void sparx5_new_base_time(struct sparx5 *sparx5, const u32 cycle_time,
const ktime_t org_base_time, ktime_t *new_base_time);
+/* sparx5_tas.c */
+/* TAS list states, mirrors the HSCH_TAS_LIST_STATE_LIST_STATE HW field */
+enum sparx5_tas_state {
+ SPX5_TAS_STATE_ADMIN,
+ SPX5_TAS_STATE_ADVANCING,
+ SPX5_TAS_STATE_PENDING,
+ SPX5_TAS_STATE_OPERATING,
+ SPX5_TAS_STATE_TERMINATING,
+ SPX5_NUM_TAS_STATE,
+};
+
+int sparx5_tas_init(struct sparx5 *sparx5);
+struct tc_taprio_qopt_offload;
+int sparx5_tas_enable(struct sparx5_port *port,
+ struct tc_taprio_qopt_offload *qopt);
+int sparx5_tas_disable(struct sparx5_port *port);
+void sparx5_tas_speed(struct sparx5_port *port, int speed);
+
/* sparx5_mirror.c */
int sparx5_mirror_add(struct sparx5_mall_entry *entry);
void sparx5_mirror_del(struct sparx5_mall_entry *entry);
diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_main_regs.h b/drivers/net/ethernet/microchip/sparx5/sparx5_main_regs.h
index d34467513648..1464fd1a1d44 100644
--- a/drivers/net/ethernet/microchip/sparx5/sparx5_main_regs.h
+++ b/drivers/net/ethernet/microchip/sparx5/sparx5_main_regs.h
@@ -5358,6 +5358,50 @@ extern const struct sparx5_regs *regs;
#define HSCH_RESET_CFG_CORE_ENA_GET(x)\
FIELD_GET(HSCH_RESET_CFG_CORE_ENA, x)
+/* HSCH:TAS_CONFIG:TAS_CFG_CTRL */
+#define HSCH_TAS_CFG_CTRL \
+ __REG(TARGET_HSCH, 0, 1, regs->gaddr[GA_HSCH_TAS_CONFIG], 0, 1, \
+ regs->gsize[GW_HSCH_TAS_CONFIG], 0, 0, 1, 4)
+
+#define HSCH_TAS_CFG_CTRL_LIST_NUM_MAX\
+ GENMASK(regs->fsize[FW_HSCH_TAS_CFG_CTRL_LIST_NUM_MAX] + 22 - 1, 22)
+#define HSCH_TAS_CFG_CTRL_LIST_NUM_MAX_SET(x)\
+ spx5_field_prep(HSCH_TAS_CFG_CTRL_LIST_NUM_MAX, x)
+#define HSCH_TAS_CFG_CTRL_LIST_NUM_MAX_GET(x)\
+ spx5_field_get(HSCH_TAS_CFG_CTRL_LIST_NUM_MAX, x)
+
+#define HSCH_TAS_CFG_CTRL_LIST_NUM\
+ GENMASK(regs->fsize[FW_HSCH_TAS_CFG_CTRL_LIST_NUM] + 15 - 1, 15)
+#define HSCH_TAS_CFG_CTRL_LIST_NUM_SET(x)\
+ spx5_field_prep(HSCH_TAS_CFG_CTRL_LIST_NUM, x)
+#define HSCH_TAS_CFG_CTRL_LIST_NUM_GET(x)\
+ spx5_field_get(HSCH_TAS_CFG_CTRL_LIST_NUM, x)
+
+#define HSCH_TAS_CFG_CTRL_ALWAYS_GUARD_BAND_SCH_Q BIT(14)
+#define HSCH_TAS_CFG_CTRL_ALWAYS_GUARD_BAND_SCH_Q_SET(x)\
+ FIELD_PREP(HSCH_TAS_CFG_CTRL_ALWAYS_GUARD_BAND_SCH_Q, x)
+#define HSCH_TAS_CFG_CTRL_ALWAYS_GUARD_BAND_SCH_Q_GET(x)\
+ FIELD_GET(HSCH_TAS_CFG_CTRL_ALWAYS_GUARD_BAND_SCH_Q, x)
+
+#define HSCH_TAS_CFG_CTRL_GCL_ENTRY_NUM\
+ GENMASK(regs->fsize[FW_HSCH_TAS_CFG_CTRL_GCL_ENTRY_NUM] + 0 - 1, 0)
+#define HSCH_TAS_CFG_CTRL_GCL_ENTRY_NUM_SET(x)\
+ spx5_field_prep(HSCH_TAS_CFG_CTRL_GCL_ENTRY_NUM, x)
+#define HSCH_TAS_CFG_CTRL_GCL_ENTRY_NUM_GET(x)\
+ spx5_field_get(HSCH_TAS_CFG_CTRL_GCL_ENTRY_NUM, x)
+
+/* HSCH:TAS_CONFIG:TAS_GATE_STATE_CTRL */
+#define HSCH_TAS_GATE_STATE_CTRL \
+ __REG(TARGET_HSCH, 0, 1, regs->gaddr[GA_HSCH_TAS_CONFIG], 0, 1, \
+ regs->gsize[GW_HSCH_TAS_CONFIG], 4, 0, 1, 4)
+
+#define HSCH_TAS_GATE_STATE_CTRL_HSCH_POS\
+ GENMASK(regs->fsize[FW_HSCH_TAS_GATE_STATE_CTRL_HSCH_POS] + 0 - 1, 0)
+#define HSCH_TAS_GATE_STATE_CTRL_HSCH_POS_SET(x)\
+ spx5_field_prep(HSCH_TAS_GATE_STATE_CTRL_HSCH_POS, x)
+#define HSCH_TAS_GATE_STATE_CTRL_HSCH_POS_GET(x)\
+ spx5_field_get(HSCH_TAS_GATE_STATE_CTRL_HSCH_POS, x)
+
/* HSCH:TAS_CONFIG:TAS_STATEMACHINE_CFG */
#define HSCH_TAS_STATEMACHINE_CFG \
__REG(TARGET_HSCH, 0, 1, regs->gaddr[GA_HSCH_TAS_CONFIG], 0, 1, \
@@ -5375,12 +5419,172 @@ extern const struct sparx5_regs *regs;
regs->gcnt[GC_HSCH_TAS_PROFILE_CFG], \
regs->gsize[GW_HSCH_TAS_PROFILE_CFG], 32, 0, 1, 4)
+/* SPARX5 ONLY */
+#define HSCH_TAS_PROFILE_CONFIG_PORT_NUM GENMASK(17, 11)
+#define HSCH_TAS_PROFILE_CONFIG_PORT_NUM_SET(x)\
+ FIELD_PREP(HSCH_TAS_PROFILE_CONFIG_PORT_NUM, x)
+#define HSCH_TAS_PROFILE_CONFIG_PORT_NUM_GET(x)\
+ FIELD_GET(HSCH_TAS_PROFILE_CONFIG_PORT_NUM, x)
+
#define HSCH_TAS_PROFILE_CONFIG_LINK_SPEED GENMASK(10, 8)
#define HSCH_TAS_PROFILE_CONFIG_LINK_SPEED_SET(x)\
FIELD_PREP(HSCH_TAS_PROFILE_CONFIG_LINK_SPEED, x)
#define HSCH_TAS_PROFILE_CONFIG_LINK_SPEED_GET(x)\
FIELD_GET(HSCH_TAS_PROFILE_CONFIG_LINK_SPEED, x)
+/* HSCH:TAS_LIST_CFG:TAS_BASE_TIME_NSEC */
+#define HSCH_TAS_BASE_TIME_NSEC \
+ __REG(TARGET_HSCH, 0, 1, regs->gaddr[GA_HSCH_TAS_LIST_CFG], 0, 1, 64, \
+ 0, 0, 1, 4)
+
+#define HSCH_TAS_BASE_TIME_NSEC_BASE_TIME_NSEC GENMASK(29, 0)
+#define HSCH_TAS_BASE_TIME_NSEC_BASE_TIME_NSEC_SET(x)\
+ FIELD_PREP(HSCH_TAS_BASE_TIME_NSEC_BASE_TIME_NSEC, x)
+#define HSCH_TAS_BASE_TIME_NSEC_BASE_TIME_NSEC_GET(x)\
+ FIELD_GET(HSCH_TAS_BASE_TIME_NSEC_BASE_TIME_NSEC, x)
+
+/* HSCH:TAS_LIST_CFG:TAS_BASE_TIME_SEC_LSB */
+#define HSCH_TAS_BASE_TIME_SEC_LSB \
+ __REG(TARGET_HSCH, 0, 1, regs->gaddr[GA_HSCH_TAS_LIST_CFG], 0, 1, 64, \
+ 4, 0, 1, 4)
+
+/* HSCH:TAS_LIST_CFG:TAS_BASE_TIME_SEC_MSB */
+#define HSCH_TAS_BASE_TIME_SEC_MSB \
+ __REG(TARGET_HSCH, 0, 1, regs->gaddr[GA_HSCH_TAS_LIST_CFG], 0, 1, 64, \
+ 8, 0, 1, 4)
+
+#define HSCH_TAS_BASE_TIME_SEC_MSB_BASE_TIME_SEC_MSB GENMASK(15, 0)
+#define HSCH_TAS_BASE_TIME_SEC_MSB_BASE_TIME_SEC_MSB_SET(x)\
+ FIELD_PREP(HSCH_TAS_BASE_TIME_SEC_MSB_BASE_TIME_SEC_MSB, x)
+#define HSCH_TAS_BASE_TIME_SEC_MSB_BASE_TIME_SEC_MSB_GET(x)\
+ FIELD_GET(HSCH_TAS_BASE_TIME_SEC_MSB_BASE_TIME_SEC_MSB, x)
+
+/* HSCH:TAS_LIST_CFG:TAS_CYCLE_TIME_CFG */
+#define HSCH_TAS_CYCLE_TIME_CFG \
+ __REG(TARGET_HSCH, 0, 1, regs->gaddr[GA_HSCH_TAS_LIST_CFG], 0, 1, 64, \
+ 24, 0, 1, 4)
+
+/* HSCH:TAS_LIST_CFG:TAS_STARTUP_CFG */
+#define HSCH_TAS_STARTUP_CFG \
+ __REG(TARGET_HSCH, 0, 1, regs->gaddr[GA_HSCH_TAS_LIST_CFG], 0, 1, 64, \
+ 28, 0, 1, 4)
+
+#define HSCH_TAS_STARTUP_CFG_STARTUP_TIME GENMASK(21, 0)
+#define HSCH_TAS_STARTUP_CFG_STARTUP_TIME_SET(x)\
+ FIELD_PREP(HSCH_TAS_STARTUP_CFG_STARTUP_TIME, x)
+#define HSCH_TAS_STARTUP_CFG_STARTUP_TIME_GET(x)\
+ FIELD_GET(HSCH_TAS_STARTUP_CFG_STARTUP_TIME, x)
+
+#define HSCH_TAS_STARTUP_CFG_OBSOLETE_IDX\
+ GENMASK(regs->fsize[FW_HSCH_TAS_STARTUP_CFG_OBSOLETE_IDX] + 23 - 1, 23)
+#define HSCH_TAS_STARTUP_CFG_OBSOLETE_IDX_SET(x)\
+ spx5_field_prep(HSCH_TAS_STARTUP_CFG_OBSOLETE_IDX, x)
+#define HSCH_TAS_STARTUP_CFG_OBSOLETE_IDX_GET(x)\
+ spx5_field_get(HSCH_TAS_STARTUP_CFG_OBSOLETE_IDX, x)
+
+#define HSCH_TAS_STARTUP_CFG_STARTUP_ERROR BIT(22)
+#define HSCH_TAS_STARTUP_CFG_STARTUP_ERROR_SET(x)\
+ FIELD_PREP(HSCH_TAS_STARTUP_CFG_STARTUP_ERROR, x)
+#define HSCH_TAS_STARTUP_CFG_STARTUP_ERROR_GET(x)\
+ FIELD_GET(HSCH_TAS_STARTUP_CFG_STARTUP_ERROR, x)
+
+/* HSCH:TAS_LIST_CFG:TAS_LIST_CFG */
+#define HSCH_TAS_LIST_CFG \
+ __REG(TARGET_HSCH, 0, 1, regs->gaddr[GA_HSCH_TAS_LIST_CFG], 0, 1, 64, \
+ 32, 0, 1, 4)
+
+#define HSCH_TAS_LIST_CFG_LIST_BASE_ADDR\
+ GENMASK(regs->fsize[FW_HSCH_TAS_LIST_CFG_LIST_BASE_ADDR] + 0 - 1, 0)
+#define HSCH_TAS_LIST_CFG_LIST_BASE_ADDR_SET(x)\
+ spx5_field_prep(HSCH_TAS_LIST_CFG_LIST_BASE_ADDR, x)
+#define HSCH_TAS_LIST_CFG_LIST_BASE_ADDR_GET(x)\
+ spx5_field_get(HSCH_TAS_LIST_CFG_LIST_BASE_ADDR, x)
+
+/* SPARX5 ONLY */
+#define HSCH_TAS_LIST_CFG_LIST_LENGTH GENMASK(29, 16)
+#define HSCH_TAS_LIST_CFG_LIST_LENGTH_SET(x)\
+ FIELD_PREP(HSCH_TAS_LIST_CFG_LIST_LENGTH, x)
+#define HSCH_TAS_LIST_CFG_LIST_LENGTH_GET(x)\
+ FIELD_GET(HSCH_TAS_LIST_CFG_LIST_LENGTH, x)
+
+/* LAN969X ONLY */
+#define HSCH_TAS_LIST_CFG_LIST_HSCH_POS GENMASK(31, 21)
+#define HSCH_TAS_LIST_CFG_LIST_HSCH_POS_SET(x)\
+ FIELD_PREP(HSCH_TAS_LIST_CFG_LIST_HSCH_POS, x)
+#define HSCH_TAS_LIST_CFG_LIST_HSCH_POS_GET(x)\
+ FIELD_GET(HSCH_TAS_LIST_CFG_LIST_HSCH_POS, x)
+
+/* LAN969X ONLY */
+#define HSCH_TAS_LIST_CFG_LIST_PORT_NUM GENMASK(20, 16)
+#define HSCH_TAS_LIST_CFG_LIST_PORT_NUM_SET(x)\
+ FIELD_PREP(HSCH_TAS_LIST_CFG_LIST_PORT_NUM, x)
+#define HSCH_TAS_LIST_CFG_LIST_PORT_NUM_GET(x)\
+ FIELD_GET(HSCH_TAS_LIST_CFG_LIST_PORT_NUM, x)
+
+/* HSCH:TAS_LIST_CFG:TAS_LIST_STATE */
+#define HSCH_TAS_LIST_STATE \
+ __REG(TARGET_HSCH, 0, 1, regs->gaddr[GA_HSCH_TAS_LIST_CFG], 0, 1, 64, \
+ 36, 0, 1, 4)
+
+#define HSCH_TAS_LIST_STATE_LIST_STATE GENMASK(2, 0)
+#define HSCH_TAS_LIST_STATE_LIST_STATE_SET(x)\
+ FIELD_PREP(HSCH_TAS_LIST_STATE_LIST_STATE, x)
+#define HSCH_TAS_LIST_STATE_LIST_STATE_GET(x)\
+ FIELD_GET(HSCH_TAS_LIST_STATE_LIST_STATE, x)
+
+/* HSCH:TAS_GCL_CFG:TAS_GCL_CTRL_CFG */
+#define HSCH_TAS_GCL_CTRL_CFG \
+ __REG(TARGET_HSCH, 0, 1, regs->gaddr[GA_HSCH_TAS_GCL_CFG], 0, 1, \
+ regs->gsize[GW_HSCH_TAS_GCL_CFG], 0, 0, 1, 4)
+
+#define HSCH_TAS_GCL_CTRL_CFG_GATE_STATE GENMASK(27, 20)
+#define HSCH_TAS_GCL_CTRL_CFG_GATE_STATE_SET(x)\
+ FIELD_PREP(HSCH_TAS_GCL_CTRL_CFG_GATE_STATE, x)
+#define HSCH_TAS_GCL_CTRL_CFG_GATE_STATE_GET(x)\
+ FIELD_GET(HSCH_TAS_GCL_CTRL_CFG_GATE_STATE, x)
+
+/* SPARX5 ONLY */
+#define HSCH_TAS_GCL_CTRL_CFG_PORT_PROFILE GENMASK(19, 13)
+#define HSCH_TAS_GCL_CTRL_CFG_PORT_PROFILE_SET(x)\
+ FIELD_PREP(HSCH_TAS_GCL_CTRL_CFG_PORT_PROFILE, x)
+#define HSCH_TAS_GCL_CTRL_CFG_PORT_PROFILE_GET(x)\
+ FIELD_GET(HSCH_TAS_GCL_CTRL_CFG_PORT_PROFILE, x)
+
+/* SPARX5 ONLY */
+#define HSCH_TAS_GCL_CTRL_CFG_HSCH_POS GENMASK(12, 0)
+#define HSCH_TAS_GCL_CTRL_CFG_HSCH_POS_SET(x)\
+ FIELD_PREP(HSCH_TAS_GCL_CTRL_CFG_HSCH_POS, x)
+#define HSCH_TAS_GCL_CTRL_CFG_HSCH_POS_GET(x)\
+ FIELD_GET(HSCH_TAS_GCL_CTRL_CFG_HSCH_POS, x)
+
+/* HSCH:TAS_GCL_CFG:TAS_GCL_TIME_CFG */
+#define HSCH_TAS_GCL_TIME_CFG \
+ __REG(TARGET_HSCH, 0, 1, regs->gaddr[GA_HSCH_TAS_GCL_CFG], 0, 1, \
+ regs->gsize[GW_HSCH_TAS_GCL_CFG], 4, 0, 1, 4)
+
+/* LAN969X ONLY */
+/* HSCH:TAS_GCL_CFG:TAS_GCL_CTRL_CFG2 */
+#define HSCH_TAS_GCL_CTRL_CFG2 \
+ __REG(TARGET_HSCH, 0, 1, regs->gaddr[GA_HSCH_TAS_GCL_CFG], 0, 1, \
+ regs->gsize[GW_HSCH_TAS_GCL_CFG], 8, 0, 1, 4)
+
+#define HSCH_TAS_GCL_CTRL_CFG2_NEXT_GCL GENMASK(11, 0)
+#define HSCH_TAS_GCL_CTRL_CFG2_NEXT_GCL_SET(x)\
+ FIELD_PREP(HSCH_TAS_GCL_CTRL_CFG2_NEXT_GCL, x)
+#define HSCH_TAS_GCL_CTRL_CFG2_NEXT_GCL_GET(x)\
+ FIELD_GET(HSCH_TAS_GCL_CTRL_CFG2_NEXT_GCL, x)
+
+/* HSCH:HSCH_TAS_STATE:TAS_GATE_STATE */
+#define HSCH_TAS_GATE_STATE \
+ __REG(TARGET_HSCH, 0, 1, regs->gaddr[GA_HSCH_HSCH_TAS_STATE], 0, 1, 4, \
+ 0, 0, 1, 4)
+
+#define HSCH_TAS_GATE_STATE_TAS_GATE_STATE GENMASK(7, 0)
+#define HSCH_TAS_GATE_STATE_TAS_GATE_STATE_SET(x)\
+ FIELD_PREP(HSCH_TAS_GATE_STATE_TAS_GATE_STATE, x)
+#define HSCH_TAS_GATE_STATE_TAS_GATE_STATE_GET(x)\
+ FIELD_GET(HSCH_TAS_GATE_STATE_TAS_GATE_STATE, x)
+
/* LAN969X ONLY */
/* HSIOWRAP:XMII_CFG:XMII_CFG */
#define HSIO_WRAP_XMII_CFG(g) \
diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_psfp.c b/drivers/net/ethernet/microchip/sparx5/sparx5_psfp.c
index 83b37f95ee46..fa19a48f941d 100644
--- a/drivers/net/ethernet/microchip/sparx5/sparx5_psfp.c
+++ b/drivers/net/ethernet/microchip/sparx5/sparx5_psfp.c
@@ -322,6 +322,7 @@ void sparx5_psfp_init(struct sparx5 *sparx5)
{
const struct sparx5_ops *ops = sparx5->data->ops;
const struct sparx5_sdlb_group *group;
+ u32 sg_ct_clks;
int i;
for (i = 0; i < sparx5->data->consts->n_lb_groups; i++) {
@@ -330,7 +331,14 @@ void sparx5_psfp_init(struct sparx5 *sparx5)
group->min_burst, group->frame_size, i);
}
- spx5_wr(ANA_AC_SG_CYCLETIME_UPDATE_PERIOD_SG_CT_UPDATE_ENA_SET(1),
+ /* Poll the stream gate cycle start times every 10 us. The core clock
+ * period is in picoseconds.
+ */
+ sg_ct_clks = 10 * NSEC_PER_USEC * PSEC_PER_NSEC /
+ sparx5_clk_period(sparx5->coreclock);
+
+ spx5_wr(ANA_AC_SG_CYCLETIME_UPDATE_PERIOD_SG_CT_CLKS_SET(sg_ct_clks) |
+ ANA_AC_SG_CYCLETIME_UPDATE_PERIOD_SG_CT_UPDATE_ENA_SET(1),
sparx5, ANA_AC_SG_CYCLETIME_UPDATE_PERIOD);
spx5_rmw(ANA_L2_FWD_CFG_ISDX_LOOKUP_ENA_SET(1),
diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_qos.c b/drivers/net/ethernet/microchip/sparx5/sparx5_qos.c
index 972da8a71f5a..7370533243b6 100644
--- a/drivers/net/ethernet/microchip/sparx5/sparx5_qos.c
+++ b/drivers/net/ethernet/microchip/sparx5/sparx5_qos.c
@@ -9,17 +9,6 @@
#include "sparx5_main.h"
#include "sparx5_qos.h"
-enum sparx5_tas_link_speed {
- TAS_SPEED_NO_GB,
- TAS_SPEED_10,
- TAS_SPEED_100,
- TAS_SPEED_1000,
- TAS_SPEED_2500,
- TAS_SPEED_5000,
- TAS_SPEED_10000,
- TAS_SPEED_25000,
-};
-
/* Calculate new base_time based on cycle_time.
*
* The hardware requires a base_time that is always in the future.
@@ -468,6 +457,10 @@ int sparx5_qos_init(struct sparx5 *sparx5)
if (ret < 0)
return ret;
+ ret = sparx5_tas_init(sparx5);
+ if (ret)
+ return ret;
+
sparx5_psfp_init(sparx5);
return 0;
@@ -592,41 +585,3 @@ int sparx5_tc_ets_del(struct sparx5_port *port)
return sparx5_dwrr_conf_set(port, &dwrr);
}
-
-void sparx5_tas_speed(struct sparx5_port *port, int speed)
-{
- struct sparx5 *sparx5 = port->sparx5;
- u8 spd;
-
- switch (speed) {
- case SPEED_10:
- spd = TAS_SPEED_10;
- break;
- case SPEED_100:
- spd = TAS_SPEED_100;
- break;
- case SPEED_1000:
- spd = TAS_SPEED_1000;
- break;
- case SPEED_2500:
- spd = TAS_SPEED_2500;
- break;
- case SPEED_5000:
- spd = TAS_SPEED_5000;
- break;
- case SPEED_10000:
- spd = TAS_SPEED_10000;
- break;
- case SPEED_25000:
- spd = TAS_SPEED_25000;
- break;
- default:
- netdev_err(port->ndev, "TAS: Unsupported speed: %d\n", speed);
- return;
- }
-
- spx5_rmw(HSCH_TAS_PROFILE_CONFIG_LINK_SPEED_SET(spd),
- HSCH_TAS_PROFILE_CONFIG_LINK_SPEED,
- sparx5,
- HSCH_TAS_PROFILE_CONFIG(port->portno));
-}
diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_qos.h b/drivers/net/ethernet/microchip/sparx5/sparx5_qos.h
index a92a699c551f..04f76f1e23f6 100644
--- a/drivers/net/ethernet/microchip/sparx5/sparx5_qos.h
+++ b/drivers/net/ethernet/microchip/sparx5/sparx5_qos.h
@@ -60,7 +60,6 @@ struct sparx5_dwrr {
};
int sparx5_qos_init(struct sparx5 *sparx5);
-void sparx5_tas_speed(struct sparx5_port *port, int speed);
/* Multi-Queue Priority */
int sparx5_tc_mqprio_add(struct net_device *ndev, u8 num_tc);
diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_regs.c b/drivers/net/ethernet/microchip/sparx5/sparx5_regs.c
index 3863f954bd83..75c1bd4f0053 100644
--- a/drivers/net/ethernet/microchip/sparx5/sparx5_regs.c
+++ b/drivers/net/ethernet/microchip/sparx5/sparx5_regs.c
@@ -96,6 +96,9 @@ const unsigned int sparx5_gaddr[GADDR_LAST] = {
[GA_HSCH_MMGT] = 162368,
[GA_HSCH_TAS_CONFIG] = 162384,
[GA_HSCH_TAS_PROFILE_CFG] = 188416,
+ [GA_HSCH_TAS_LIST_CFG] = 161600,
+ [GA_HSCH_TAS_GCL_CFG] = 161584,
+ [GA_HSCH_HSCH_TAS_STATE] = 161580,
[GA_PTP_PTP_CFG] = 320,
[GA_PTP_PTP_TOD_DOMAINS] = 336,
[GA_PTP_PHASE_DETECTOR_CTRL] = 420,
@@ -147,6 +150,7 @@ const unsigned int sparx5_gsize[GSIZE_LAST] = {
[GW_GCB_CHIP_REGS] = 424,
[GW_HSCH_TAS_CONFIG] = 12,
[GW_HSCH_TAS_PROFILE_CFG] = 64,
+ [GW_HSCH_TAS_GCL_CFG] = 8,
[GW_PTP_PHASE_DETECTOR_CTRL] = 8,
[GW_QSYS_PAUSE_CFG] = 1128,
};
@@ -201,6 +205,12 @@ const unsigned int sparx5_fsize[FSIZE_LAST] = {
[FW_HSCH_HSCH_LEAK_CFG_LEAK_FIRST] = 16,
[FW_HSCH_FLUSH_CTRL_FLUSH_PORT] = 7,
[FW_HSCH_FLUSH_CTRL_FLUSH_HIER] = 16,
+ [FW_HSCH_TAS_CFG_CTRL_LIST_NUM_MAX] = 7,
+ [FW_HSCH_TAS_CFG_CTRL_LIST_NUM] = 7,
+ [FW_HSCH_TAS_CFG_CTRL_GCL_ENTRY_NUM] = 14,
+ [FW_HSCH_TAS_GATE_STATE_CTRL_HSCH_POS] = 13,
+ [FW_HSCH_TAS_STARTUP_CFG_OBSOLETE_IDX] = 7,
+ [FW_HSCH_TAS_LIST_CFG_LIST_BASE_ADDR] = 14,
[FW_LRN_COMMON_ACCESS_CTRL_CPU_ACCESS_DIRECT_ROW] = 14,
[FW_LRN_MAC_ACCESS_CFG_3_MAC_ENTRY_ISDX_LIMIT_IDX] = 11,
[FW_LRN_AUTOAGE_CFG_2_NEXT_ROW] = 14,
diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_regs.h b/drivers/net/ethernet/microchip/sparx5/sparx5_regs.h
index 585589a31e90..97d496ebb750 100644
--- a/drivers/net/ethernet/microchip/sparx5/sparx5_regs.h
+++ b/drivers/net/ethernet/microchip/sparx5/sparx5_regs.h
@@ -105,6 +105,9 @@ enum sparx5_gaddr_enum {
GA_HSCH_MMGT,
GA_HSCH_TAS_CONFIG,
GA_HSCH_TAS_PROFILE_CFG,
+ GA_HSCH_TAS_LIST_CFG,
+ GA_HSCH_TAS_GCL_CFG,
+ GA_HSCH_HSCH_TAS_STATE,
GA_PTP_PTP_CFG,
GA_PTP_PTP_TOD_DOMAINS,
GA_PTP_PHASE_DETECTOR_CTRL,
@@ -158,6 +161,7 @@ enum sparx5_gsize_enum {
GW_GCB_CHIP_REGS,
GW_HSCH_TAS_CONFIG,
GW_HSCH_TAS_PROFILE_CFG,
+ GW_HSCH_TAS_GCL_CFG,
GW_PTP_PHASE_DETECTOR_CTRL,
GW_QSYS_PAUSE_CFG,
GSIZE_LAST,
@@ -214,6 +218,12 @@ enum sparx5_fsize_enum {
FW_HSCH_HSCH_LEAK_CFG_LEAK_FIRST,
FW_HSCH_FLUSH_CTRL_FLUSH_PORT,
FW_HSCH_FLUSH_CTRL_FLUSH_HIER,
+ FW_HSCH_TAS_CFG_CTRL_LIST_NUM_MAX,
+ FW_HSCH_TAS_CFG_CTRL_LIST_NUM,
+ FW_HSCH_TAS_CFG_CTRL_GCL_ENTRY_NUM,
+ FW_HSCH_TAS_GATE_STATE_CTRL_HSCH_POS,
+ FW_HSCH_TAS_STARTUP_CFG_OBSOLETE_IDX,
+ FW_HSCH_TAS_LIST_CFG_LIST_BASE_ADDR,
FW_LRN_COMMON_ACCESS_CTRL_CPU_ACCESS_DIRECT_ROW,
FW_LRN_MAC_ACCESS_CFG_3_MAC_ENTRY_ISDX_LIMIT_IDX,
FW_LRN_AUTOAGE_CFG_2_NEXT_ROW,
diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_tas.c b/drivers/net/ethernet/microchip/sparx5/sparx5_tas.c
new file mode 100644
index 000000000000..ec80bc69371a
--- /dev/null
+++ b/drivers/net/ethernet/microchip/sparx5/sparx5_tas.c
@@ -0,0 +1,806 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* Microchip Sparx5 Switch driver
+ *
+ * Copyright (c) 2026 Microchip Technology Inc. and its subsidiaries.
+ */
+
+#include <net/pkt_sched.h>
+
+#include "sparx5_main.h"
+#include "sparx5_qos.h"
+
+enum sparx5_tas_link_speed {
+ TAS_SPEED_NO_GB,
+ TAS_SPEED_10,
+ TAS_SPEED_100,
+ TAS_SPEED_1000,
+ TAS_SPEED_2500,
+ TAS_SPEED_5000,
+ TAS_SPEED_10000,
+ TAS_SPEED_25000,
+};
+
+/* Maximum time in milliseconds to wait for TAS state transitions. */
+#define TAS_TIMEOUT_MS 2000
+
+/* Minimum supported cycle time in nanoseconds */
+#define TAS_MIN_CYCLE_TIME_NS (1 * NSEC_PER_USEC) /* 1 usec */
+
+/* Maximum supported cycle time in nanoseconds */
+#define TAS_MAX_CYCLE_TIME_NS ((1 * NSEC_PER_SEC) - 1) /* 999.999.999 nsec */
+
+/* STARTUP_TIME in units of 256 ns, must be non-zero for a list to start */
+#define TAS_STARTUP_TIME_DEFAULT 8
+
+/* We use 2 TAS lists per present port, as far as the chip has lists:
+ * num_tas_lists = min(port_count * SPX5_TAS_ENTRIES_PER_PORT, n_tas_lists);
+ *
+ * The index for the 2 entries of the n'th present port is calculated as:
+ * index_1 = n * SPX5_TAS_ENTRIES_PER_PORT;
+ * index_2 = index_1 + 1;
+ *
+ * GCL entries are allocated from a bitmap shared by all ports and sized to the
+ * chip's GCL pool. Each list records the range it owns. The range is freed
+ * once hardware reports the list back in ADMIN, which for a list obsoleted by
+ * a replace happens some time after the replace itself. Entry 0 is reserved:
+ * unwritten NEXT_GCL fields read as 0, so a list must never own it.
+ *
+ * We use 1 TAS profile per port, shared by both of its lists:
+ * index = portno;
+ *
+ * We do not yet support frame preemption and the default guard band
+ * of 1536 bytes is always used on all queues.
+ *
+ * The gate mask of each entry is written as is to the port's 8 queues,
+ * where bit n gates queue (QoS class) n. The mqprio parameters of the
+ * taprio qdisc (prio_tc_map, queue ranges) are not used, so a schedule
+ * must use 8 traffic classes mapped 1:1 to the queues, as with the
+ * mqprio offload. Other values of num_tc are rejected.
+ *
+ * These are the possible combinations of states for the two TAS lists in a
+ * running system.
+ *
+ * ADMIN/ADMIN: No schedules are currently operating or pending.
+ *
+ * ADMIN/PENDING: No schedule is currently operating but one is pending and
+ * when current time exceeds the configured base time it will automatically
+ * enter the OPERATING state. The state is now ADMIN/OPERATING.
+ *
+ * ADMIN/OPERATING: A schedule is currently operating and will run until it is
+ * terminated manually.
+ *
+ * OPERATING/PENDING: A schedule is currently operating and another is
+ * pending. When current time exceeds the configured base time for the
+ * pending schedule it will automatically stop the operating schedule and
+ * enter the OPERATING state. The state is now ADMIN/OPERATING.
+ *
+ * When we want to disable TAS on a port, we must stop schedules that are in
+ * state PENDING or OPERATING.
+ * Pending schedules are stopped first and then operating schedules.
+ * The gate state for the port must be restored to "all-queues-open" manually
+ * in case the schedule was stopped with one or more of the queues closed.
+ *
+ * When we add a schedule we always use a base time in the future, where base
+ * time is at least current time + (2 * cycle time). This is a requirement
+ * from the hardware. This means that a schedule will always start in state
+ * PENDING. It also means that we always use the new schedule to stop an
+ * eventually operating schedule.
+ *
+ * When we want to add a new schedule we must consider the current state of
+ * the two TAS list entries:
+ *
+ * ADMIN/ADMIN: Just add the new schedule in one of the TAS lists.
+ *
+ * ADMIN/PENDING: Stop the current pending schedule and add the new one.
+ *
+ * ADMIN/OPERATING: Add the new schedule in the TAS list that is currently in
+ * admin state and configure it to stop the currently operating schedule when
+ * current time exceeds the configured base time in the new schedule.
+ *
+ * OPERATING/PENDING: Stop the current pending schedule, add the new one and
+ * configure it to stop the currently operating schedule when
+ * current time exceeds the configured base time in the new schedule.
+ */
+
+/* Scheduler element index (HSCH_POS) for this port's per-port TAS gate,
+ * i.e. its position at the layer-2 boundary of the scheduling hierarchy.
+ */
+static int sparx5_tas_scheduler_get(struct sparx5 *sparx5, int portno)
+{
+ if (is_sparx5(sparx5))
+ return SPX5_HSCH_L0_SE_CNT + SPX5_HSCH_L1_SE_CNT + portno;
+ else
+ return 1120 + portno;
+}
+
+static int sparx5_tas_list_index(struct sparx5_port *port, u8 tas_entry)
+{
+ const struct sparx5_consts *consts = port->sparx5->data->consts;
+ int portno, pidx = 0;
+
+ /* Limit the index to available ports: the n'th present port uses
+ * lists 2n and 2n + 1, so the lists of all ports fit in
+ * port_count * SPX5_TAS_ENTRIES_PER_PORT.
+ */
+ for (portno = 0; portno < consts->n_ports; ++portno) {
+ if (portno == port->portno)
+ return (pidx * SPX5_TAS_ENTRIES_PER_PORT) + tas_entry;
+ if (port->sparx5->ports[portno])
+ pidx++;
+ }
+
+ return 0;
+}
+
+/* Number of TAS lists in use: two per present port, as far as the chip has
+ * lists for. A port whose lists fall outside this range has no TAS.
+ */
+static int sparx5_tas_num_lists(struct sparx5 *sparx5)
+{
+ return min_t(int, sparx5->port_count * SPX5_TAS_ENTRIES_PER_PORT,
+ sparx5->data->consts->n_tas_lists);
+}
+
+static bool sparx5_tas_port_has_lists(struct sparx5_port *port)
+{
+ return sparx5_tas_list_index(port, SPX5_TAS_ENTRIES_PER_PORT - 1) <
+ sparx5_tas_num_lists(port->sparx5);
+}
+
+static const char *sparx5_tas_state_to_str(int state)
+{
+ switch (state) {
+ case SPX5_TAS_STATE_ADMIN:
+ return "ADMIN";
+ case SPX5_TAS_STATE_ADVANCING:
+ return "ADVANCING";
+ case SPX5_TAS_STATE_PENDING:
+ return "PENDING";
+ case SPX5_TAS_STATE_OPERATING:
+ return "OPERATING";
+ case SPX5_TAS_STATE_TERMINATING:
+ return "TERMINATING";
+ default:
+ return "??";
+ }
+}
+
+static void sparx5_tas_select_list(struct sparx5 *sparx5, int list)
+{
+ spx5_rmw(HSCH_TAS_CFG_CTRL_LIST_NUM_SET(list),
+ HSCH_TAS_CFG_CTRL_LIST_NUM,
+ sparx5,
+ HSCH_TAS_CFG_CTRL);
+}
+
+static int sparx5_tas_list_state_get(struct sparx5 *sparx5)
+{
+ return HSCH_TAS_LIST_STATE_LIST_STATE_GET(spx5_rd(sparx5,
+ HSCH_TAS_LIST_STATE));
+}
+
+static int sparx5_tas_gcl_alloc(struct sparx5 *sparx5, int count)
+{
+ u32 n_gcl = sparx5->data->consts->n_tas_gcl;
+ unsigned long base;
+
+ base = bitmap_find_next_zero_area(sparx5->tas_gcl_bitmap,
+ n_gcl,
+ 0,
+ count,
+ 0);
+ if (base >= n_gcl)
+ return -ENOSPC;
+
+ bitmap_set(sparx5->tas_gcl_bitmap, base, count);
+
+ return base;
+}
+
+static void sparx5_tas_gcl_free(struct sparx5_port *port, int slot)
+{
+ if (port->tas_lists[slot].gcl_count)
+ bitmap_clear(port->sparx5->tas_gcl_bitmap,
+ port->tas_lists[slot].gcl_base,
+ port->tas_lists[slot].gcl_count);
+
+ port->tas_lists[slot].gcl_base = 0;
+ port->tas_lists[slot].gcl_count = 0;
+}
+
+/* Free the GCL entries of every list that hardware has returned to ADMIN */
+static void sparx5_tas_gcl_flush(struct sparx5 *sparx5)
+{
+ struct sparx5_port *port;
+ int i, j;
+
+ lockdep_assert_held(&sparx5->tas_lock);
+
+ for (i = 0; i < sparx5->data->consts->n_ports; i++) {
+ port = sparx5->ports[i];
+ if (!port)
+ continue;
+
+ for (j = 0; j < SPX5_TAS_ENTRIES_PER_PORT; j++) {
+ if (!port->tas_lists[j].gcl_count)
+ continue;
+
+ sparx5_tas_select_list(sparx5,
+ sparx5_tas_list_index(port, j));
+ if (sparx5_tas_list_state_get(sparx5) ==
+ SPX5_TAS_STATE_ADMIN)
+ sparx5_tas_gcl_free(port, j);
+ }
+ }
+}
+
+/* Request state 'req' (or only wait if 'req' < 0) on the selected list until
+ * it reaches a state in the 'done' mask.
+ */
+static int sparx5_tas_list_state_wait(struct sparx5_port *port, int list,
+ int req, u32 done)
+{
+ struct sparx5 *sparx5 = port->sparx5;
+ unsigned long end;
+ int state;
+
+ /* Do not wait forever for the state change */
+ end = jiffies + msecs_to_jiffies(TAS_TIMEOUT_MS);
+ do {
+ if (req >= 0)
+ spx5_rmw(HSCH_TAS_LIST_STATE_LIST_STATE_SET(req),
+ HSCH_TAS_LIST_STATE_LIST_STATE,
+ sparx5,
+ HSCH_TAS_LIST_STATE);
+
+ state = sparx5_tas_list_state_get(sparx5);
+ if (BIT(state) & done)
+ return 0;
+
+ cond_resched();
+ } while (!time_after(jiffies, end));
+
+ netdev_err(port->ndev, "Timeout switching TAS state %s in list %d\n",
+ sparx5_tas_state_to_str(state), list);
+
+ return -ETIME;
+}
+
+/* Wait for hardware to bring the selected list back to ADMIN */
+static int sparx5_tas_wait_admin(struct sparx5_port *port, int list)
+{
+ return sparx5_tas_list_state_wait(port, list, -1,
+ BIT(SPX5_TAS_STATE_ADMIN));
+}
+
+static int sparx5_tas_shutdown_pending(struct sparx5_port *port)
+{
+ struct sparx5 *sparx5 = port->sparx5;
+ int i, list, state, err;
+
+ for (i = 0; i < SPX5_TAS_ENTRIES_PER_PORT; i++) {
+ list = sparx5_tas_list_index(port, i);
+ spx5_rmw(HSCH_TAS_CFG_CTRL_LIST_NUM_SET(list),
+ HSCH_TAS_CFG_CTRL_LIST_NUM,
+ sparx5,
+ HSCH_TAS_CFG_CTRL);
+
+ state = sparx5_tas_list_state_get(sparx5);
+ if (state != SPX5_TAS_STATE_ADVANCING &&
+ state != SPX5_TAS_STATE_PENDING)
+ continue;
+
+ err = sparx5_tas_list_state_wait(port, list,
+ SPX5_TAS_STATE_ADMIN,
+ BIT(SPX5_TAS_STATE_ADMIN));
+ if (err)
+ return err;
+
+ sparx5_tas_gcl_free(port, i);
+ }
+
+ return 0;
+}
+
+static int sparx5_tas_shutdown_operating(struct sparx5_port *port)
+{
+ struct sparx5 *sparx5 = port->sparx5;
+ int i, list, state, err = 0;
+ u32 sched;
+
+ sched = sparx5_tas_scheduler_get(sparx5, port->portno);
+
+ for (i = 0; i < SPX5_TAS_ENTRIES_PER_PORT; i++) {
+ list = sparx5_tas_list_index(port, i);
+ spx5_rmw(HSCH_TAS_CFG_CTRL_LIST_NUM_SET(list),
+ HSCH_TAS_CFG_CTRL_LIST_NUM,
+ sparx5,
+ HSCH_TAS_CFG_CTRL);
+
+ /* A list being stopped still runs its gate operations until
+ * the end of its cycle, so wait for it before opening the
+ * gates.
+ */
+ state = sparx5_tas_list_state_get(sparx5);
+ if (state != SPX5_TAS_STATE_OPERATING &&
+ state != SPX5_TAS_STATE_TERMINATING)
+ continue;
+
+ if (state == SPX5_TAS_STATE_OPERATING &&
+ sparx5_tas_list_state_wait(port, list,
+ SPX5_TAS_STATE_TERMINATING,
+ BIT(SPX5_TAS_STATE_TERMINATING) |
+ BIT(SPX5_TAS_STATE_ADMIN))) {
+ err = -ETIME;
+ continue;
+ }
+
+ if (sparx5_tas_wait_admin(port, list)) {
+ err = -ETIME;
+ continue;
+ }
+
+ sparx5_tas_gcl_free(port, i);
+ }
+
+ /* Restore gate state to "all-queues-open". */
+
+ /* Select port n on layer 2 of Hierarchical Scheduler */
+ spx5_wr(HSCH_TAS_GATE_STATE_CTRL_HSCH_POS_SET(sched),
+ sparx5,
+ HSCH_TAS_GATE_STATE_CTRL);
+
+ /* Set gate state to "all-queues-open" */
+ spx5_wr(HSCH_TAS_GATE_STATE_TAS_GATE_STATE_SET(0xff),
+ sparx5,
+ HSCH_TAS_GATE_STATE);
+
+ return err;
+}
+
+/* Find an ADMIN list for a new schedule, after stopping a pending one. An
+ * operating list is returned in obsolete, to be stopped by the new list.
+ */
+static int sparx5_tas_list_find(struct sparx5_port *port, int *new,
+ int *obsolete)
+{
+ int i, err, oper = -1, state_cnt[SPX5_NUM_TAS_STATE] = {0};
+ struct sparx5 *sparx5 = port->sparx5;
+ int state[SPX5_TAS_ENTRIES_PER_PORT];
+ int list[SPX5_TAS_ENTRIES_PER_PORT];
+ bool valid = false;
+
+ err = sparx5_tas_shutdown_pending(port);
+ if (err)
+ return err;
+
+ for (i = 0; i < SPX5_TAS_ENTRIES_PER_PORT; i++) {
+ list[i] = sparx5_tas_list_index(port, i);
+ spx5_rmw(HSCH_TAS_CFG_CTRL_LIST_NUM_SET(list[i]),
+ HSCH_TAS_CFG_CTRL_LIST_NUM,
+ sparx5,
+ HSCH_TAS_CFG_CTRL);
+
+ state[i] = sparx5_tas_list_state_get(sparx5);
+
+ if (state[i] == SPX5_TAS_STATE_TERMINATING) {
+ err = sparx5_tas_wait_admin(port, list[i]);
+ if (err)
+ return err;
+ state[i] = SPX5_TAS_STATE_ADMIN;
+ }
+
+ if (state[i] >= SPX5_NUM_TAS_STATE) {
+ netdev_err(port->ndev,
+ "Invalid tas list state %u %u %d\n",
+ state[i], port->portno, i);
+ return -EINVAL;
+ }
+
+ if (state[i] == SPX5_TAS_STATE_OPERATING)
+ oper = list[i];
+
+ state_cnt[state[i]]++;
+ }
+
+ if (state_cnt[SPX5_TAS_STATE_ADMIN] == 2)
+ valid = true;
+
+ if (state_cnt[SPX5_TAS_STATE_ADMIN] == 1 &&
+ state_cnt[SPX5_TAS_STATE_OPERATING] == 1)
+ valid = true;
+
+ if (!valid) {
+ netdev_err(port->ndev,
+ "Invalid tas state combination: %d %d %d %d %d\n",
+ state_cnt[SPX5_TAS_STATE_ADMIN],
+ state_cnt[SPX5_TAS_STATE_ADVANCING],
+ state_cnt[SPX5_TAS_STATE_PENDING],
+ state_cnt[SPX5_TAS_STATE_OPERATING],
+ state_cnt[SPX5_TAS_STATE_TERMINATING]);
+
+ return -EINVAL;
+ }
+
+ for (i = 0; i < SPX5_TAS_ENTRIES_PER_PORT; i++) {
+ if (state[i] == SPX5_TAS_STATE_ADMIN) {
+ *new = list[i];
+ *obsolete = (oper == -1) ? *new : oper;
+ return 0;
+ }
+ }
+
+ return -EBUSY; /* No suitable list found */
+}
+
+/* Program the list and its GCL entries [base, base + num_entries) */
+static void sparx5_tas_gcl_setup(struct sparx5_port *port, int list, int base,
+ struct tc_taprio_qopt_offload *qopt)
+{
+ struct sparx5 *sparx5 = port->sparx5;
+ u32 sched, entry, prof;
+ int i;
+
+ sched = sparx5_tas_scheduler_get(sparx5, port->portno);
+ /* Each port uses the TAS profile of its own number */
+ prof = port->portno;
+
+ /* Program only the chosen list; the sibling slot stays in ADMIN. */
+ sparx5_tas_select_list(sparx5, list);
+
+ spx5_rmw(HSCH_TAS_LIST_CFG_LIST_BASE_ADDR_SET(base),
+ HSCH_TAS_LIST_CFG_LIST_BASE_ADDR,
+ sparx5,
+ HSCH_TAS_LIST_CFG);
+
+ if (is_sparx5(sparx5)) {
+ spx5_rmw(HSCH_TAS_LIST_CFG_LIST_LENGTH_SET(qopt->num_entries),
+ HSCH_TAS_LIST_CFG_LIST_LENGTH,
+ sparx5,
+ HSCH_TAS_LIST_CFG);
+ } else {
+ /* Associate TAS list with physical port number and
+ * scheduler element.
+ */
+ spx5_rmw(HSCH_TAS_LIST_CFG_LIST_PORT_NUM_SET(port->portno),
+ HSCH_TAS_LIST_CFG_LIST_PORT_NUM, sparx5,
+ HSCH_TAS_LIST_CFG);
+ spx5_rmw(HSCH_TAS_LIST_CFG_LIST_HSCH_POS_SET(sched),
+ HSCH_TAS_LIST_CFG_LIST_HSCH_POS, sparx5,
+ HSCH_TAS_LIST_CFG);
+ }
+
+ for (i = 0; i < qopt->num_entries; i++) {
+ u32 gcl_next = (i >= qopt->num_entries - 1) ? base :
+ base + i + 1;
+ u32 gate_mask = qopt->entries[i].gate_mask;
+
+ /* On sparx5 GCL_ENTRY_NUM is relative to the list's BASE_ADDR.
+ * On lan969x the GCL is a linked list and GCL_ENTRY_NUM is an
+ * absolute index into the pool, as is NEXT_GCL.
+ */
+ entry = is_sparx5(sparx5) ? i : base + i;
+
+ spx5_rmw(HSCH_TAS_CFG_CTRL_GCL_ENTRY_NUM_SET(entry),
+ HSCH_TAS_CFG_CTRL_GCL_ENTRY_NUM,
+ sparx5,
+ HSCH_TAS_CFG_CTRL);
+
+ /* Set gate states for this GCL */
+ spx5_rmw(HSCH_TAS_GCL_CTRL_CFG_GATE_STATE_SET(gate_mask),
+ HSCH_TAS_GCL_CTRL_CFG_GATE_STATE, sparx5,
+ HSCH_TAS_GCL_CTRL_CFG);
+
+ if (is_sparx5(sparx5)) {
+ spx5_rmw(HSCH_TAS_GCL_CTRL_CFG_HSCH_POS_SET(sched) |
+ HSCH_TAS_GCL_CTRL_CFG_PORT_PROFILE_SET(prof),
+ HSCH_TAS_GCL_CTRL_CFG_HSCH_POS |
+ HSCH_TAS_GCL_CTRL_CFG_PORT_PROFILE, sparx5,
+ HSCH_TAS_GCL_CTRL_CFG);
+ } else {
+ /* The GCL list is a linked list on lan969x */
+ spx5_wr(HSCH_TAS_GCL_CTRL_CFG2_NEXT_GCL_SET(gcl_next),
+ sparx5, HSCH_TAS_GCL_CTRL_CFG2);
+ }
+
+ spx5_wr(qopt->entries[i].interval,
+ sparx5,
+ HSCH_TAS_GCL_TIME_CFG);
+ }
+}
+
+int sparx5_tas_enable(struct sparx5_port *port,
+ struct tc_taprio_qopt_offload *qopt)
+{
+ u64 cycle_time = qopt->cycle_time, calculated_cycle_time = 0;
+ int i, err, base, slot, state, new_list = -1, obsolete = -1;
+ struct sparx5 *sparx5 = port->sparx5;
+ struct timespec64 ts;
+ ktime_t base_time;
+ unsigned long end;
+ u32 startup, val;
+
+ if (qopt->cycle_time_extension) {
+ netdev_err(port->ndev,
+ "cycle_time_extension is not supported\n");
+ return -EOPNOTSUPP;
+ }
+
+ /* The base time is taken from the PTP clock */
+ if (!sparx5->ptp)
+ return -EOPNOTSUPP;
+
+ if (qopt->base_time < 0) {
+ netdev_err(port->ndev, "Invalid base_time %lld\n",
+ qopt->base_time);
+ return -EINVAL;
+ }
+
+ if (!sparx5_tas_port_has_lists(port)) {
+ netdev_err(port->ndev, "No TAS lists left for this port\n");
+ return -EOPNOTSUPP;
+ }
+
+ if (qopt->mqprio.qopt.num_tc != SPX5_PRIOS) {
+ netdev_err(port->ndev, "Only %d traffic classes supported\n",
+ SPX5_PRIOS);
+ return -EOPNOTSUPP;
+ }
+
+ for (i = 0; i < qopt->num_entries; i++) {
+ if (qopt->entries[i].command != TC_TAPRIO_CMD_SET_GATES) {
+ netdev_err(port->ndev, "Unsupported GCL command: %d\n",
+ qopt->entries[i].command);
+ return -EOPNOTSUPP;
+ }
+ }
+
+ mutex_lock(&sparx5->tas_lock);
+
+ sparx5_tas_gcl_flush(sparx5);
+
+ if (cycle_time > TAS_MAX_CYCLE_TIME_NS) {
+ netdev_err(port->ndev, "Invalid cycle_time %llu\n", cycle_time);
+ err = -EINVAL;
+ goto out;
+ }
+
+ for (i = 0; i < qopt->num_entries; i++) {
+ u32 interval = qopt->entries[i].interval;
+
+ if (interval < TAS_MIN_CYCLE_TIME_NS) {
+ netdev_err(port->ndev,
+ "Invalid minimum cycle time %u\n", interval);
+ err = -EINVAL;
+ goto out;
+ }
+
+ if (interval > TAS_MAX_CYCLE_TIME_NS) {
+ netdev_err(port->ndev,
+ "Invalid maximum cycle time %u\n", interval);
+ err = -EINVAL;
+ goto out;
+ }
+
+ calculated_cycle_time += interval;
+ }
+
+ if (calculated_cycle_time > TAS_MAX_CYCLE_TIME_NS ||
+ cycle_time < calculated_cycle_time) {
+ netdev_err(port->ndev, "Invalid cycle_time %llu\n", cycle_time);
+ err = -EINVAL;
+ goto out;
+ }
+
+ sparx5_new_base_time(sparx5, cycle_time, qopt->base_time, &base_time);
+
+ /* Allocate before sparx5_tas_list_find() stops a pending schedule, so
+ * running out of GCL entries fails the replace without touching
+ * hardware.
+ */
+ base = sparx5_tas_gcl_alloc(sparx5, qopt->num_entries);
+ if (base < 0) {
+ netdev_err(port->ndev, "No room for %zu GCL entries\n",
+ qopt->num_entries);
+ err = base;
+ goto out;
+ }
+
+ /* Select an appropriate entry to use */
+ err = sparx5_tas_list_find(port, &new_list, &obsolete);
+ if (err) {
+ bitmap_clear(sparx5->tas_gcl_bitmap, base, qopt->num_entries);
+ goto out;
+ }
+
+ /* The new list is in ADMIN, so a range it still owns can go */
+ slot = new_list - sparx5_tas_list_index(port, 0);
+ sparx5_tas_gcl_free(port, slot);
+
+ sparx5_tas_gcl_setup(port, new_list, base, qopt);
+
+ /* The obsolete list keeps its entries until it reaches ADMIN */
+ port->tas_lists[slot].gcl_base = base;
+ port->tas_lists[slot].gcl_count = qopt->num_entries;
+
+ /* Setup TAS list */
+ ts = ktime_to_timespec64(base_time);
+ spx5_wr(HSCH_TAS_BASE_TIME_NSEC_BASE_TIME_NSEC_SET(ts.tv_nsec),
+ sparx5,
+ HSCH_TAS_BASE_TIME_NSEC);
+
+ spx5_wr((ts.tv_sec & GENMASK(31, 0)),
+ sparx5,
+ HSCH_TAS_BASE_TIME_SEC_LSB);
+
+ val = ts.tv_sec >> 32;
+ spx5_wr(HSCH_TAS_BASE_TIME_SEC_MSB_BASE_TIME_SEC_MSB_SET(val),
+ sparx5,
+ HSCH_TAS_BASE_TIME_SEC_MSB);
+
+ spx5_wr(cycle_time,
+ sparx5,
+ HSCH_TAS_CYCLE_TIME_CFG);
+
+ /* Full write, to also clear a sticky STARTUP_ERROR from a previous use
+ * of this list.
+ */
+ spx5_wr(HSCH_TAS_STARTUP_CFG_OBSOLETE_IDX_SET(obsolete) |
+ HSCH_TAS_STARTUP_CFG_STARTUP_TIME_SET(TAS_STARTUP_TIME_DEFAULT),
+ sparx5,
+ HSCH_TAS_STARTUP_CFG);
+
+ /* Start list processing */
+ spx5_rmw(HSCH_TAS_LIST_STATE_LIST_STATE_SET(SPX5_TAS_STATE_ADVANCING),
+ HSCH_TAS_LIST_STATE_LIST_STATE,
+ sparx5,
+ HSCH_TAS_LIST_STATE);
+
+ /* Do not wait forever for the startup error flag to be reported */
+ end = jiffies + msecs_to_jiffies(TAS_TIMEOUT_MS);
+ do {
+ /* Read the state first, so an error that ended ADVANCING is
+ * already latched when STARTUP_CFG is read.
+ */
+ val = spx5_rd(sparx5, HSCH_TAS_LIST_STATE);
+ state = HSCH_TAS_LIST_STATE_LIST_STATE_GET(val);
+ startup = spx5_rd(sparx5, HSCH_TAS_STARTUP_CFG);
+ if (HSCH_TAS_STARTUP_CFG_STARTUP_ERROR_GET(startup) ||
+ state != SPX5_TAS_STATE_ADVANCING)
+ break;
+
+ cond_resched();
+ } while (!time_after(jiffies, end));
+
+ if (HSCH_TAS_STARTUP_CFG_STARTUP_ERROR_GET(startup)) {
+ netdev_err(port->ndev, "TAS startup error in list %d\n",
+ new_list);
+ err = -EIO;
+ } else if (state == SPX5_TAS_STATE_ADVANCING) {
+ netdev_err(port->ndev, "Timeout starting TAS list %d\n",
+ new_list);
+ err = -ETIME;
+ }
+
+ /* taprio sends no DESTROY for a failed offload, so do not leave a
+ * list behind that may still start.
+ */
+ if (err)
+ sparx5_tas_shutdown_pending(port);
+
+out:
+ mutex_unlock(&sparx5->tas_lock);
+ return err;
+}
+
+int sparx5_tas_disable(struct sparx5_port *port)
+{
+ int err, ret;
+
+ /* Nothing was offloaded on a port without lists */
+ if (!sparx5_tas_port_has_lists(port))
+ return 0;
+
+ mutex_lock(&port->sparx5->tas_lock);
+
+ sparx5_tas_gcl_flush(port->sparx5);
+
+ /* Stop the operating list and restore the gates even if a pending list
+ * could not be stopped, as taprio does not retry a failed destroy.
+ */
+ err = sparx5_tas_shutdown_pending(port);
+ ret = sparx5_tas_shutdown_operating(port);
+ if (!err)
+ err = ret;
+
+ mutex_unlock(&port->sparx5->tas_lock);
+
+ return err;
+}
+
+int sparx5_tas_init(struct sparx5 *sparx5)
+{
+ const struct sparx5_consts *consts = sparx5->data->consts;
+ int i, num_ports, list_num_max;
+
+ /* There are only 128 (sparx5) / 60 (lan969x) TAS lists, not enough
+ * for the whole port range. LIST_NUM_MAX is the highest list index
+ * processed, and the datasheet gives 24 as its minimum value.
+ */
+ num_ports = consts->n_ports;
+ list_num_max = max(sparx5_tas_num_lists(sparx5) - 1, 24);
+
+ sparx5->tas_gcl_bitmap = devm_bitmap_zalloc(sparx5->dev,
+ consts->n_tas_gcl,
+ GFP_KERNEL);
+ if (!sparx5->tas_gcl_bitmap)
+ return -ENOMEM;
+
+ /* Unwritten NEXT_GCL fields read as 0, so no list may own entry 0 */
+ bitmap_set(sparx5->tas_gcl_bitmap, 0, 1);
+
+ mutex_init(&sparx5->tas_lock);
+
+ /* Apply the guard band to gate closes of all traffic classes, not only
+ * the classes not marked as scheduled in SCH_TRAFFIC_QUEUES.
+ */
+ spx5_rmw(HSCH_TAS_CFG_CTRL_LIST_NUM_MAX_SET(list_num_max) |
+ HSCH_TAS_CFG_CTRL_ALWAYS_GUARD_BAND_SCH_Q_SET(1),
+ HSCH_TAS_CFG_CTRL_LIST_NUM_MAX |
+ HSCH_TAS_CFG_CTRL_ALWAYS_GUARD_BAND_SCH_Q,
+ sparx5,
+ HSCH_TAS_CFG_CTRL);
+
+ /* Associate profile with port (profile idx = port on lan969x) */
+ if (is_sparx5(sparx5)) {
+ for (i = 0; i < num_ports; i++) {
+ if (!sparx5->ports[i])
+ continue;
+
+ spx5_rmw(HSCH_TAS_PROFILE_CONFIG_PORT_NUM_SET(i),
+ HSCH_TAS_PROFILE_CONFIG_PORT_NUM,
+ sparx5,
+ HSCH_TAS_PROFILE_CONFIG(i));
+ }
+ }
+
+ return 0;
+}
+
+void sparx5_tas_speed(struct sparx5_port *port, int speed)
+{
+ struct sparx5 *sparx5 = port->sparx5;
+ u8 spd;
+
+ switch (speed) {
+ case SPEED_10:
+ spd = TAS_SPEED_10;
+ break;
+ case SPEED_100:
+ spd = TAS_SPEED_100;
+ break;
+ case SPEED_1000:
+ spd = TAS_SPEED_1000;
+ break;
+ case SPEED_2500:
+ spd = TAS_SPEED_2500;
+ break;
+ case SPEED_5000:
+ spd = TAS_SPEED_5000;
+ break;
+ case SPEED_10000:
+ spd = TAS_SPEED_10000;
+ break;
+ case SPEED_25000:
+ spd = TAS_SPEED_25000;
+ break;
+ default:
+ netdev_err(port->ndev, "TAS: Unsupported speed: %d\n", speed);
+ return;
+ }
+
+ spx5_rmw(HSCH_TAS_PROFILE_CONFIG_LINK_SPEED_SET(spd),
+ HSCH_TAS_PROFILE_CONFIG_LINK_SPEED,
+ sparx5,
+ HSCH_TAS_PROFILE_CONFIG(port->portno));
+}
diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_tc.c b/drivers/net/ethernet/microchip/sparx5/sparx5_tc.c
index 28b2514c8330..30da5db57ef3 100644
--- a/drivers/net/ethernet/microchip/sparx5/sparx5_tc.c
+++ b/drivers/net/ethernet/microchip/sparx5/sparx5_tc.c
@@ -154,6 +154,21 @@ static int sparx5_tc_setup_qdisc_ets(struct net_device *ndev,
return -EOPNOTSUPP;
}
+static int sparx5_tc_setup_qdisc_taprio(struct net_device *ndev,
+ struct tc_taprio_qopt_offload *qopt)
+{
+ struct sparx5_port *port = netdev_priv(ndev);
+
+ switch (qopt->cmd) {
+ case TAPRIO_CMD_REPLACE:
+ return sparx5_tas_enable(port, qopt);
+ case TAPRIO_CMD_DESTROY:
+ return sparx5_tas_disable(port);
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
int sparx5_port_setup_tc(struct net_device *ndev, enum tc_setup_type type,
void *type_data)
{
@@ -166,6 +181,8 @@ int sparx5_port_setup_tc(struct net_device *ndev, enum tc_setup_type type,
return sparx5_tc_setup_qdisc_tbf(ndev, type_data);
case TC_SETUP_QDISC_ETS:
return sparx5_tc_setup_qdisc_ets(ndev, type_data);
+ case TC_SETUP_QDISC_TAPRIO:
+ return sparx5_tc_setup_qdisc_taprio(ndev, type_data);
default:
return -EOPNOTSUPP;
}
diff --git a/drivers/net/ethernet/microchip/vcap/Kconfig b/drivers/net/ethernet/microchip/vcap/Kconfig
index 97f43fd4473f..1af30a358a15 100644
--- a/drivers/net/ethernet/microchip/vcap/Kconfig
+++ b/drivers/net/ethernet/microchip/vcap/Kconfig
@@ -40,7 +40,6 @@ config VCAP_KUNIT_TEST
bool "KUnit test for VCAP library" if !KUNIT_ALL_TESTS
depends on KUNIT
depends on KUNIT=y && VCAP=y && y
- select DEBUG_FS
default KUNIT_ALL_TESTS
help
This builds unit tests for the VCAP library.