* [PATCH net-next 1/3] net: sparx5: Add registers used by TAS
2026-10-06 13:51 [PATCH net-next 0/3] net: sparx5: add TAS offload Daniel Machon
@ 2026-10-06 13:51 ` Daniel Machon
2026-10-06 13:51 ` [PATCH net-next 2/3] net: sparx5: Move TAS link speed configuration to sparx5_tas.c Daniel Machon
2026-10-06 13:51 ` [PATCH net-next 3/3] net: sparx5: Add TAS offload support Daniel Machon
2 siblings, 0 replies; 4+ messages in thread
From: Daniel Machon @ 2026-10-06 13:51 UTC (permalink / raw)
To: Daniel Machon, UNGLinuxDriver, Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Steen Hegelund,
Richard Cochran
Cc: netdev, linux-arm-kernel, linux-kernel, Robert Marko
Add the registers and offset definitions used to configure the Time
Aware Shaper (802.1Qbv) in hardware, on both sparx5 and lan969x.
Signed-off-by: Daniel Machon <daniel.machon@microchip.com>
---
.../microchip/sparx5/lan969x/lan969x_regs.c | 10 +
.../ethernet/microchip/sparx5/sparx5_main_regs.h | 204 +++++++++++++++++++++
.../net/ethernet/microchip/sparx5/sparx5_regs.c | 10 +
.../net/ethernet/microchip/sparx5/sparx5_regs.h | 10 +
4 files changed, 234 insertions(+)
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_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_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,
--
2.34.1
^ permalink raw reply [flat|nested] 4+ messages in thread* [PATCH net-next 2/3] net: sparx5: Move TAS link speed configuration to sparx5_tas.c
2026-10-06 13:51 [PATCH net-next 0/3] net: sparx5: add TAS offload Daniel Machon
2026-10-06 13:51 ` [PATCH net-next 1/3] net: sparx5: Add registers used by TAS Daniel Machon
@ 2026-10-06 13:51 ` Daniel Machon
2026-10-06 13:51 ` [PATCH net-next 3/3] net: sparx5: Add TAS offload support Daniel Machon
2 siblings, 0 replies; 4+ messages in thread
From: Daniel Machon @ 2026-10-06 13:51 UTC (permalink / raw)
To: Daniel Machon, UNGLinuxDriver, Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Steen Hegelund,
Richard Cochran
Cc: netdev, linux-arm-kernel, linux-kernel, Robert Marko
Move sparx5_tas_speed() and its link speed enum from sparx5_qos.c to a
new sparx5_tas.c, which the following TAS offload patch builds on.
No functional change.
Signed-off-by: Daniel Machon <daniel.machon@microchip.com>
---
drivers/net/ethernet/microchip/sparx5/Makefile | 2 +-
.../net/ethernet/microchip/sparx5/sparx5_main.h | 3 ++
drivers/net/ethernet/microchip/sparx5/sparx5_qos.c | 49 -------------------
drivers/net/ethernet/microchip/sparx5/sparx5_qos.h | 1 -
drivers/net/ethernet/microchip/sparx5/sparx5_tas.c | 56 ++++++++++++++++++++++
5 files changed, 60 insertions(+), 51 deletions(-)
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/sparx5_main.h b/drivers/net/ethernet/microchip/sparx5/sparx5_main.h
index 0f7d8440f614..41e0597a8d48 100644
--- a/drivers/net/ethernet/microchip/sparx5/sparx5_main.h
+++ b/drivers/net/ethernet/microchip/sparx5/sparx5_main.h
@@ -707,6 +707,9 @@ 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 */
+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_qos.c b/drivers/net/ethernet/microchip/sparx5/sparx5_qos.c
index 972da8a71f5a..e580670f3992 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.
@@ -592,41 +581,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_tas.c b/drivers/net/ethernet/microchip/sparx5/sparx5_tas.c
new file mode 100644
index 000000000000..3637d4b48ad6
--- /dev/null
+++ b/drivers/net/ethernet/microchip/sparx5/sparx5_tas.c
@@ -0,0 +1,56 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* Microchip Sparx5 Switch driver
+ *
+ * Copyright (c) 2026 Microchip Technology Inc. and its subsidiaries.
+ */
+
+#include "sparx5_main.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,
+};
+
+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));
+}
--
2.34.1
^ permalink raw reply [flat|nested] 4+ messages in thread* [PATCH net-next 3/3] net: sparx5: Add TAS offload support
2026-10-06 13:51 [PATCH net-next 0/3] net: sparx5: add TAS offload Daniel Machon
2026-10-06 13:51 ` [PATCH net-next 1/3] net: sparx5: Add registers used by TAS Daniel Machon
2026-10-06 13:51 ` [PATCH net-next 2/3] net: sparx5: Move TAS link speed configuration to sparx5_tas.c Daniel Machon
@ 2026-10-06 13:51 ` Daniel Machon
2 siblings, 0 replies; 4+ messages in thread
From: Daniel Machon @ 2026-10-06 13:51 UTC (permalink / raw)
To: Daniel Machon, UNGLinuxDriver, Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Steen Hegelund,
Richard Cochran
Cc: netdev, linux-arm-kernel, linux-kernel, Robert Marko
The Time Aware Shaper (TAS, IEEE 802.1Qbv) opens and closes the gates
of a port's egress queues according to a repeating schedule, so that
traffic classes are only sent in their own time windows.
In hardware, a schedule is run by a TAS list, which steps through gate
control list (GCL) entries taken from a pool shared by all ports. A
list is started with a base time, and it can stop another list when it
takes over, which allows a schedule to be replaced without a gap.
Add TAS offload for sparx5 and lan969x, configured through tc-taprio.
Use two TAS lists per port, so that a new schedule can be programmed in
one list while the other is operating. Allocate the GCL entries of a
schedule from the shared pool, and free them once their list is back
in ADMIN. On lan969x, program the GCL as a linked list and address its
entries by absolute index rather than relative to the list base.
sparx5 has 128 TAS lists for 65 ports, so with every port present the
last port gets no TAS offload.
Signed-off-by: Daniel Machon <daniel.machon@microchip.com>
---
.../ethernet/microchip/sparx5/lan969x/lan969x.c | 2 +
.../net/ethernet/microchip/sparx5/sparx5_main.c | 2 +
.../net/ethernet/microchip/sparx5/sparx5_main.h | 25 +
drivers/net/ethernet/microchip/sparx5/sparx5_qos.c | 4 +
drivers/net/ethernet/microchip/sparx5/sparx5_tas.c | 750 +++++++++++++++++++++
drivers/net/ethernet/microchip/sparx5/sparx5_tc.c | 17 +
6 files changed, 800 insertions(+)
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/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 41e0597a8d48..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;
@@ -708,6 +718,21 @@ 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 */
diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_qos.c b/drivers/net/ethernet/microchip/sparx5/sparx5_qos.c
index e580670f3992..7370533243b6 100644
--- a/drivers/net/ethernet/microchip/sparx5/sparx5_qos.c
+++ b/drivers/net/ethernet/microchip/sparx5/sparx5_qos.c
@@ -457,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;
diff --git a/drivers/net/ethernet/microchip/sparx5/sparx5_tas.c b/drivers/net/ethernet/microchip/sparx5/sparx5_tas.c
index 3637d4b48ad6..ec80bc69371a 100644
--- a/drivers/net/ethernet/microchip/sparx5/sparx5_tas.c
+++ b/drivers/net/ethernet/microchip/sparx5/sparx5_tas.c
@@ -4,7 +4,10 @@
* 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,
@@ -17,6 +20,753 @@ enum sparx5_tas_link_speed {
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;
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;
}
--
2.34.1
^ permalink raw reply [flat|nested] 4+ messages in thread