xref: /linux/drivers/net/ethernet/microchip/sparx5/sparx5_tas.c (revision a52629f85a3a97f76a12b9cb97717a36318a708e)
196628508SDaniel Machon // SPDX-License-Identifier: GPL-2.0+
296628508SDaniel Machon /* Microchip Sparx5 Switch driver
396628508SDaniel Machon  *
496628508SDaniel Machon  * Copyright (c) 2026 Microchip Technology Inc. and its subsidiaries.
596628508SDaniel Machon  */
696628508SDaniel Machon 
7*a52629f8SDaniel Machon #include <net/pkt_sched.h>
8*a52629f8SDaniel Machon 
996628508SDaniel Machon #include "sparx5_main.h"
10*a52629f8SDaniel Machon #include "sparx5_qos.h"
1196628508SDaniel Machon 
1296628508SDaniel Machon enum sparx5_tas_link_speed {
1396628508SDaniel Machon 	TAS_SPEED_NO_GB,
1496628508SDaniel Machon 	TAS_SPEED_10,
1596628508SDaniel Machon 	TAS_SPEED_100,
1696628508SDaniel Machon 	TAS_SPEED_1000,
1796628508SDaniel Machon 	TAS_SPEED_2500,
1896628508SDaniel Machon 	TAS_SPEED_5000,
1996628508SDaniel Machon 	TAS_SPEED_10000,
2096628508SDaniel Machon 	TAS_SPEED_25000,
2196628508SDaniel Machon };
2296628508SDaniel Machon 
23*a52629f8SDaniel Machon /* Maximum time in milliseconds to wait for TAS state transitions. */
24*a52629f8SDaniel Machon #define TAS_TIMEOUT_MS 2000
25*a52629f8SDaniel Machon 
26*a52629f8SDaniel Machon /* Minimum supported cycle time in nanoseconds */
27*a52629f8SDaniel Machon #define TAS_MIN_CYCLE_TIME_NS (1 * NSEC_PER_USEC) /* 1 usec */
28*a52629f8SDaniel Machon 
29*a52629f8SDaniel Machon /* Maximum supported cycle time in nanoseconds */
30*a52629f8SDaniel Machon #define TAS_MAX_CYCLE_TIME_NS ((1 * NSEC_PER_SEC) - 1) /* 999.999.999 nsec */
31*a52629f8SDaniel Machon 
32*a52629f8SDaniel Machon /* STARTUP_TIME in units of 256 ns, must be non-zero for a list to start */
33*a52629f8SDaniel Machon #define TAS_STARTUP_TIME_DEFAULT 8
34*a52629f8SDaniel Machon 
35*a52629f8SDaniel Machon /* We use 2 TAS lists per present port, as far as the chip has lists:
36*a52629f8SDaniel Machon  * num_tas_lists = min(port_count * SPX5_TAS_ENTRIES_PER_PORT, n_tas_lists);
37*a52629f8SDaniel Machon  *
38*a52629f8SDaniel Machon  * The index for the 2 entries of the n'th present port is calculated as:
39*a52629f8SDaniel Machon  * index_1 = n * SPX5_TAS_ENTRIES_PER_PORT;
40*a52629f8SDaniel Machon  * index_2 = index_1 + 1;
41*a52629f8SDaniel Machon  *
42*a52629f8SDaniel Machon  * GCL entries are allocated from a bitmap shared by all ports and sized to the
43*a52629f8SDaniel Machon  * chip's GCL pool. Each list records the range it owns. The range is freed
44*a52629f8SDaniel Machon  * once hardware reports the list back in ADMIN, which for a list obsoleted by
45*a52629f8SDaniel Machon  * a replace happens some time after the replace itself. Entry 0 is reserved:
46*a52629f8SDaniel Machon  * unwritten NEXT_GCL fields read as 0, so a list must never own it.
47*a52629f8SDaniel Machon  *
48*a52629f8SDaniel Machon  * We use 1 TAS profile per port, shared by both of its lists:
49*a52629f8SDaniel Machon  * index = portno;
50*a52629f8SDaniel Machon  *
51*a52629f8SDaniel Machon  * We do not yet support frame preemption and the default guard band
52*a52629f8SDaniel Machon  * of 1536 bytes is always used on all queues.
53*a52629f8SDaniel Machon  *
54*a52629f8SDaniel Machon  * The gate mask of each entry is written as is to the port's 8 queues,
55*a52629f8SDaniel Machon  * where bit n gates queue (QoS class) n. The mqprio parameters of the
56*a52629f8SDaniel Machon  * taprio qdisc (prio_tc_map, queue ranges) are not used, so a schedule
57*a52629f8SDaniel Machon  * must use 8 traffic classes mapped 1:1 to the queues, as with the
58*a52629f8SDaniel Machon  * mqprio offload. Other values of num_tc are rejected.
59*a52629f8SDaniel Machon  *
60*a52629f8SDaniel Machon  * These are the possible combinations of states for the two TAS lists in a
61*a52629f8SDaniel Machon  * running system.
62*a52629f8SDaniel Machon  *
63*a52629f8SDaniel Machon  * ADMIN/ADMIN: No schedules are currently operating or pending.
64*a52629f8SDaniel Machon  *
65*a52629f8SDaniel Machon  * ADMIN/PENDING: No schedule is currently operating but one is pending and
66*a52629f8SDaniel Machon  * when current time exceeds the configured base time it will automatically
67*a52629f8SDaniel Machon  * enter the OPERATING state. The state is now ADMIN/OPERATING.
68*a52629f8SDaniel Machon  *
69*a52629f8SDaniel Machon  * ADMIN/OPERATING: A schedule is currently operating and will run until it is
70*a52629f8SDaniel Machon  * terminated manually.
71*a52629f8SDaniel Machon  *
72*a52629f8SDaniel Machon  * OPERATING/PENDING: A schedule is currently operating and another is
73*a52629f8SDaniel Machon  * pending. When current time exceeds the configured base time for the
74*a52629f8SDaniel Machon  * pending schedule it will automatically stop the operating schedule and
75*a52629f8SDaniel Machon  * enter the OPERATING state. The state is now ADMIN/OPERATING.
76*a52629f8SDaniel Machon  *
77*a52629f8SDaniel Machon  * When we want to disable TAS on a port, we must stop schedules that are in
78*a52629f8SDaniel Machon  * state PENDING or OPERATING.
79*a52629f8SDaniel Machon  * Pending schedules are stopped first and then operating schedules.
80*a52629f8SDaniel Machon  * The gate state for the port must be restored to "all-queues-open" manually
81*a52629f8SDaniel Machon  * in case the schedule was stopped with one or more of the queues closed.
82*a52629f8SDaniel Machon  *
83*a52629f8SDaniel Machon  * When we add a schedule we always use a base time in the future, where base
84*a52629f8SDaniel Machon  * time is at least current time + (2 * cycle time). This is a requirement
85*a52629f8SDaniel Machon  * from the hardware. This means that a schedule will always start in state
86*a52629f8SDaniel Machon  * PENDING. It also means that we always use the new schedule to stop an
87*a52629f8SDaniel Machon  * eventually operating schedule.
88*a52629f8SDaniel Machon  *
89*a52629f8SDaniel Machon  * When we want to add a new schedule we must consider the current state of
90*a52629f8SDaniel Machon  * the two TAS list entries:
91*a52629f8SDaniel Machon  *
92*a52629f8SDaniel Machon  * ADMIN/ADMIN: Just add the new schedule in one of the TAS lists.
93*a52629f8SDaniel Machon  *
94*a52629f8SDaniel Machon  * ADMIN/PENDING: Stop the current pending schedule and add the new one.
95*a52629f8SDaniel Machon  *
96*a52629f8SDaniel Machon  * ADMIN/OPERATING: Add the new schedule in the TAS list that is currently in
97*a52629f8SDaniel Machon  * admin state and configure it to stop the currently operating schedule when
98*a52629f8SDaniel Machon  * current time exceeds the configured base time in the new schedule.
99*a52629f8SDaniel Machon  *
100*a52629f8SDaniel Machon  * OPERATING/PENDING: Stop the current pending schedule, add the new one and
101*a52629f8SDaniel Machon  * configure it to stop the currently operating schedule when
102*a52629f8SDaniel Machon  * current time exceeds the configured base time in the new schedule.
103*a52629f8SDaniel Machon  */
104*a52629f8SDaniel Machon 
105*a52629f8SDaniel Machon /* Scheduler element index (HSCH_POS) for this port's per-port TAS gate,
106*a52629f8SDaniel Machon  * i.e. its position at the layer-2 boundary of the scheduling hierarchy.
107*a52629f8SDaniel Machon  */
108*a52629f8SDaniel Machon static int sparx5_tas_scheduler_get(struct sparx5 *sparx5, int portno)
109*a52629f8SDaniel Machon {
110*a52629f8SDaniel Machon 	if (is_sparx5(sparx5))
111*a52629f8SDaniel Machon 		return SPX5_HSCH_L0_SE_CNT + SPX5_HSCH_L1_SE_CNT + portno;
112*a52629f8SDaniel Machon 	else
113*a52629f8SDaniel Machon 		return 1120 + portno;
114*a52629f8SDaniel Machon }
115*a52629f8SDaniel Machon 
116*a52629f8SDaniel Machon static int sparx5_tas_list_index(struct sparx5_port *port, u8 tas_entry)
117*a52629f8SDaniel Machon {
118*a52629f8SDaniel Machon 	const struct sparx5_consts *consts = port->sparx5->data->consts;
119*a52629f8SDaniel Machon 	int portno, pidx = 0;
120*a52629f8SDaniel Machon 
121*a52629f8SDaniel Machon 	/* Limit the index to available ports: the n'th present port uses
122*a52629f8SDaniel Machon 	 * lists 2n and 2n + 1, so the lists of all ports fit in
123*a52629f8SDaniel Machon 	 * port_count * SPX5_TAS_ENTRIES_PER_PORT.
124*a52629f8SDaniel Machon 	 */
125*a52629f8SDaniel Machon 	for (portno = 0; portno < consts->n_ports; ++portno) {
126*a52629f8SDaniel Machon 		if (portno == port->portno)
127*a52629f8SDaniel Machon 			return (pidx * SPX5_TAS_ENTRIES_PER_PORT) + tas_entry;
128*a52629f8SDaniel Machon 		if (port->sparx5->ports[portno])
129*a52629f8SDaniel Machon 			pidx++;
130*a52629f8SDaniel Machon 	}
131*a52629f8SDaniel Machon 
132*a52629f8SDaniel Machon 	return 0;
133*a52629f8SDaniel Machon }
134*a52629f8SDaniel Machon 
135*a52629f8SDaniel Machon /* Number of TAS lists in use: two per present port, as far as the chip has
136*a52629f8SDaniel Machon  * lists for. A port whose lists fall outside this range has no TAS.
137*a52629f8SDaniel Machon  */
138*a52629f8SDaniel Machon static int sparx5_tas_num_lists(struct sparx5 *sparx5)
139*a52629f8SDaniel Machon {
140*a52629f8SDaniel Machon 	return min_t(int, sparx5->port_count * SPX5_TAS_ENTRIES_PER_PORT,
141*a52629f8SDaniel Machon 		     sparx5->data->consts->n_tas_lists);
142*a52629f8SDaniel Machon }
143*a52629f8SDaniel Machon 
144*a52629f8SDaniel Machon static bool sparx5_tas_port_has_lists(struct sparx5_port *port)
145*a52629f8SDaniel Machon {
146*a52629f8SDaniel Machon 	return sparx5_tas_list_index(port, SPX5_TAS_ENTRIES_PER_PORT - 1) <
147*a52629f8SDaniel Machon 	       sparx5_tas_num_lists(port->sparx5);
148*a52629f8SDaniel Machon }
149*a52629f8SDaniel Machon 
150*a52629f8SDaniel Machon static const char *sparx5_tas_state_to_str(int state)
151*a52629f8SDaniel Machon {
152*a52629f8SDaniel Machon 	switch (state) {
153*a52629f8SDaniel Machon 	case SPX5_TAS_STATE_ADMIN:
154*a52629f8SDaniel Machon 		return "ADMIN";
155*a52629f8SDaniel Machon 	case SPX5_TAS_STATE_ADVANCING:
156*a52629f8SDaniel Machon 		return "ADVANCING";
157*a52629f8SDaniel Machon 	case SPX5_TAS_STATE_PENDING:
158*a52629f8SDaniel Machon 		return "PENDING";
159*a52629f8SDaniel Machon 	case SPX5_TAS_STATE_OPERATING:
160*a52629f8SDaniel Machon 		return "OPERATING";
161*a52629f8SDaniel Machon 	case SPX5_TAS_STATE_TERMINATING:
162*a52629f8SDaniel Machon 		return "TERMINATING";
163*a52629f8SDaniel Machon 	default:
164*a52629f8SDaniel Machon 		return "??";
165*a52629f8SDaniel Machon 	}
166*a52629f8SDaniel Machon }
167*a52629f8SDaniel Machon 
168*a52629f8SDaniel Machon static void sparx5_tas_select_list(struct sparx5 *sparx5, int list)
169*a52629f8SDaniel Machon {
170*a52629f8SDaniel Machon 	spx5_rmw(HSCH_TAS_CFG_CTRL_LIST_NUM_SET(list),
171*a52629f8SDaniel Machon 		 HSCH_TAS_CFG_CTRL_LIST_NUM,
172*a52629f8SDaniel Machon 		 sparx5,
173*a52629f8SDaniel Machon 		 HSCH_TAS_CFG_CTRL);
174*a52629f8SDaniel Machon }
175*a52629f8SDaniel Machon 
176*a52629f8SDaniel Machon static int sparx5_tas_list_state_get(struct sparx5 *sparx5)
177*a52629f8SDaniel Machon {
178*a52629f8SDaniel Machon 	return HSCH_TAS_LIST_STATE_LIST_STATE_GET(spx5_rd(sparx5,
179*a52629f8SDaniel Machon 							  HSCH_TAS_LIST_STATE));
180*a52629f8SDaniel Machon }
181*a52629f8SDaniel Machon 
182*a52629f8SDaniel Machon static int sparx5_tas_gcl_alloc(struct sparx5 *sparx5, int count)
183*a52629f8SDaniel Machon {
184*a52629f8SDaniel Machon 	u32 n_gcl = sparx5->data->consts->n_tas_gcl;
185*a52629f8SDaniel Machon 	unsigned long base;
186*a52629f8SDaniel Machon 
187*a52629f8SDaniel Machon 	base = bitmap_find_next_zero_area(sparx5->tas_gcl_bitmap,
188*a52629f8SDaniel Machon 					  n_gcl,
189*a52629f8SDaniel Machon 					  0,
190*a52629f8SDaniel Machon 					  count,
191*a52629f8SDaniel Machon 					  0);
192*a52629f8SDaniel Machon 	if (base >= n_gcl)
193*a52629f8SDaniel Machon 		return -ENOSPC;
194*a52629f8SDaniel Machon 
195*a52629f8SDaniel Machon 	bitmap_set(sparx5->tas_gcl_bitmap, base, count);
196*a52629f8SDaniel Machon 
197*a52629f8SDaniel Machon 	return base;
198*a52629f8SDaniel Machon }
199*a52629f8SDaniel Machon 
200*a52629f8SDaniel Machon static void sparx5_tas_gcl_free(struct sparx5_port *port, int slot)
201*a52629f8SDaniel Machon {
202*a52629f8SDaniel Machon 	if (port->tas_lists[slot].gcl_count)
203*a52629f8SDaniel Machon 		bitmap_clear(port->sparx5->tas_gcl_bitmap,
204*a52629f8SDaniel Machon 			     port->tas_lists[slot].gcl_base,
205*a52629f8SDaniel Machon 			     port->tas_lists[slot].gcl_count);
206*a52629f8SDaniel Machon 
207*a52629f8SDaniel Machon 	port->tas_lists[slot].gcl_base = 0;
208*a52629f8SDaniel Machon 	port->tas_lists[slot].gcl_count = 0;
209*a52629f8SDaniel Machon }
210*a52629f8SDaniel Machon 
211*a52629f8SDaniel Machon /* Free the GCL entries of every list that hardware has returned to ADMIN */
212*a52629f8SDaniel Machon static void sparx5_tas_gcl_flush(struct sparx5 *sparx5)
213*a52629f8SDaniel Machon {
214*a52629f8SDaniel Machon 	struct sparx5_port *port;
215*a52629f8SDaniel Machon 	int i, j;
216*a52629f8SDaniel Machon 
217*a52629f8SDaniel Machon 	lockdep_assert_held(&sparx5->tas_lock);
218*a52629f8SDaniel Machon 
219*a52629f8SDaniel Machon 	for (i = 0; i < sparx5->data->consts->n_ports; i++) {
220*a52629f8SDaniel Machon 		port = sparx5->ports[i];
221*a52629f8SDaniel Machon 		if (!port)
222*a52629f8SDaniel Machon 			continue;
223*a52629f8SDaniel Machon 
224*a52629f8SDaniel Machon 		for (j = 0; j < SPX5_TAS_ENTRIES_PER_PORT; j++) {
225*a52629f8SDaniel Machon 			if (!port->tas_lists[j].gcl_count)
226*a52629f8SDaniel Machon 				continue;
227*a52629f8SDaniel Machon 
228*a52629f8SDaniel Machon 			sparx5_tas_select_list(sparx5,
229*a52629f8SDaniel Machon 					       sparx5_tas_list_index(port, j));
230*a52629f8SDaniel Machon 			if (sparx5_tas_list_state_get(sparx5) ==
231*a52629f8SDaniel Machon 			    SPX5_TAS_STATE_ADMIN)
232*a52629f8SDaniel Machon 				sparx5_tas_gcl_free(port, j);
233*a52629f8SDaniel Machon 		}
234*a52629f8SDaniel Machon 	}
235*a52629f8SDaniel Machon }
236*a52629f8SDaniel Machon 
237*a52629f8SDaniel Machon /* Request state 'req' (or only wait if 'req' < 0) on the selected list until
238*a52629f8SDaniel Machon  * it reaches a state in the 'done' mask.
239*a52629f8SDaniel Machon  */
240*a52629f8SDaniel Machon static int sparx5_tas_list_state_wait(struct sparx5_port *port, int list,
241*a52629f8SDaniel Machon 				      int req, u32 done)
242*a52629f8SDaniel Machon {
243*a52629f8SDaniel Machon 	struct sparx5 *sparx5 = port->sparx5;
244*a52629f8SDaniel Machon 	unsigned long end;
245*a52629f8SDaniel Machon 	int state;
246*a52629f8SDaniel Machon 
247*a52629f8SDaniel Machon 	/* Do not wait forever for the state change */
248*a52629f8SDaniel Machon 	end = jiffies + msecs_to_jiffies(TAS_TIMEOUT_MS);
249*a52629f8SDaniel Machon 	do {
250*a52629f8SDaniel Machon 		if (req >= 0)
251*a52629f8SDaniel Machon 			spx5_rmw(HSCH_TAS_LIST_STATE_LIST_STATE_SET(req),
252*a52629f8SDaniel Machon 				 HSCH_TAS_LIST_STATE_LIST_STATE,
253*a52629f8SDaniel Machon 				 sparx5,
254*a52629f8SDaniel Machon 				 HSCH_TAS_LIST_STATE);
255*a52629f8SDaniel Machon 
256*a52629f8SDaniel Machon 		state = sparx5_tas_list_state_get(sparx5);
257*a52629f8SDaniel Machon 		if (BIT(state) & done)
258*a52629f8SDaniel Machon 			return 0;
259*a52629f8SDaniel Machon 
260*a52629f8SDaniel Machon 		cond_resched();
261*a52629f8SDaniel Machon 	} while (!time_after(jiffies, end));
262*a52629f8SDaniel Machon 
263*a52629f8SDaniel Machon 	netdev_err(port->ndev, "Timeout switching TAS state %s in list %d\n",
264*a52629f8SDaniel Machon 		   sparx5_tas_state_to_str(state), list);
265*a52629f8SDaniel Machon 
266*a52629f8SDaniel Machon 	return -ETIME;
267*a52629f8SDaniel Machon }
268*a52629f8SDaniel Machon 
269*a52629f8SDaniel Machon /* Wait for hardware to bring the selected list back to ADMIN */
270*a52629f8SDaniel Machon static int sparx5_tas_wait_admin(struct sparx5_port *port, int list)
271*a52629f8SDaniel Machon {
272*a52629f8SDaniel Machon 	return sparx5_tas_list_state_wait(port, list, -1,
273*a52629f8SDaniel Machon 					  BIT(SPX5_TAS_STATE_ADMIN));
274*a52629f8SDaniel Machon }
275*a52629f8SDaniel Machon 
276*a52629f8SDaniel Machon static int sparx5_tas_shutdown_pending(struct sparx5_port *port)
277*a52629f8SDaniel Machon {
278*a52629f8SDaniel Machon 	struct sparx5 *sparx5 = port->sparx5;
279*a52629f8SDaniel Machon 	int i, list, state, err;
280*a52629f8SDaniel Machon 
281*a52629f8SDaniel Machon 	for (i = 0; i < SPX5_TAS_ENTRIES_PER_PORT; i++) {
282*a52629f8SDaniel Machon 		list = sparx5_tas_list_index(port, i);
283*a52629f8SDaniel Machon 		spx5_rmw(HSCH_TAS_CFG_CTRL_LIST_NUM_SET(list),
284*a52629f8SDaniel Machon 			 HSCH_TAS_CFG_CTRL_LIST_NUM,
285*a52629f8SDaniel Machon 			 sparx5,
286*a52629f8SDaniel Machon 			 HSCH_TAS_CFG_CTRL);
287*a52629f8SDaniel Machon 
288*a52629f8SDaniel Machon 		state = sparx5_tas_list_state_get(sparx5);
289*a52629f8SDaniel Machon 		if (state != SPX5_TAS_STATE_ADVANCING &&
290*a52629f8SDaniel Machon 		    state != SPX5_TAS_STATE_PENDING)
291*a52629f8SDaniel Machon 			continue;
292*a52629f8SDaniel Machon 
293*a52629f8SDaniel Machon 		err = sparx5_tas_list_state_wait(port, list,
294*a52629f8SDaniel Machon 						 SPX5_TAS_STATE_ADMIN,
295*a52629f8SDaniel Machon 						 BIT(SPX5_TAS_STATE_ADMIN));
296*a52629f8SDaniel Machon 		if (err)
297*a52629f8SDaniel Machon 			return err;
298*a52629f8SDaniel Machon 
299*a52629f8SDaniel Machon 		sparx5_tas_gcl_free(port, i);
300*a52629f8SDaniel Machon 	}
301*a52629f8SDaniel Machon 
302*a52629f8SDaniel Machon 	return 0;
303*a52629f8SDaniel Machon }
304*a52629f8SDaniel Machon 
305*a52629f8SDaniel Machon static int sparx5_tas_shutdown_operating(struct sparx5_port *port)
306*a52629f8SDaniel Machon {
307*a52629f8SDaniel Machon 	struct sparx5 *sparx5 = port->sparx5;
308*a52629f8SDaniel Machon 	int i, list, state, err = 0;
309*a52629f8SDaniel Machon 	u32 sched;
310*a52629f8SDaniel Machon 
311*a52629f8SDaniel Machon 	sched = sparx5_tas_scheduler_get(sparx5, port->portno);
312*a52629f8SDaniel Machon 
313*a52629f8SDaniel Machon 	for (i = 0; i < SPX5_TAS_ENTRIES_PER_PORT; i++) {
314*a52629f8SDaniel Machon 		list = sparx5_tas_list_index(port, i);
315*a52629f8SDaniel Machon 		spx5_rmw(HSCH_TAS_CFG_CTRL_LIST_NUM_SET(list),
316*a52629f8SDaniel Machon 			 HSCH_TAS_CFG_CTRL_LIST_NUM,
317*a52629f8SDaniel Machon 			 sparx5,
318*a52629f8SDaniel Machon 			 HSCH_TAS_CFG_CTRL);
319*a52629f8SDaniel Machon 
320*a52629f8SDaniel Machon 		/* A list being stopped still runs its gate operations until
321*a52629f8SDaniel Machon 		 * the end of its cycle, so wait for it before opening the
322*a52629f8SDaniel Machon 		 * gates.
323*a52629f8SDaniel Machon 		 */
324*a52629f8SDaniel Machon 		state = sparx5_tas_list_state_get(sparx5);
325*a52629f8SDaniel Machon 		if (state != SPX5_TAS_STATE_OPERATING &&
326*a52629f8SDaniel Machon 		    state != SPX5_TAS_STATE_TERMINATING)
327*a52629f8SDaniel Machon 			continue;
328*a52629f8SDaniel Machon 
329*a52629f8SDaniel Machon 		if (state == SPX5_TAS_STATE_OPERATING &&
330*a52629f8SDaniel Machon 		    sparx5_tas_list_state_wait(port, list,
331*a52629f8SDaniel Machon 					       SPX5_TAS_STATE_TERMINATING,
332*a52629f8SDaniel Machon 					       BIT(SPX5_TAS_STATE_TERMINATING) |
333*a52629f8SDaniel Machon 					       BIT(SPX5_TAS_STATE_ADMIN))) {
334*a52629f8SDaniel Machon 			err = -ETIME;
335*a52629f8SDaniel Machon 			continue;
336*a52629f8SDaniel Machon 		}
337*a52629f8SDaniel Machon 
338*a52629f8SDaniel Machon 		if (sparx5_tas_wait_admin(port, list)) {
339*a52629f8SDaniel Machon 			err = -ETIME;
340*a52629f8SDaniel Machon 			continue;
341*a52629f8SDaniel Machon 		}
342*a52629f8SDaniel Machon 
343*a52629f8SDaniel Machon 		sparx5_tas_gcl_free(port, i);
344*a52629f8SDaniel Machon 	}
345*a52629f8SDaniel Machon 
346*a52629f8SDaniel Machon 	/* Restore gate state to "all-queues-open". */
347*a52629f8SDaniel Machon 
348*a52629f8SDaniel Machon 	/* Select port n on layer 2 of Hierarchical Scheduler */
349*a52629f8SDaniel Machon 	spx5_wr(HSCH_TAS_GATE_STATE_CTRL_HSCH_POS_SET(sched),
350*a52629f8SDaniel Machon 		sparx5,
351*a52629f8SDaniel Machon 		HSCH_TAS_GATE_STATE_CTRL);
352*a52629f8SDaniel Machon 
353*a52629f8SDaniel Machon 	/* Set gate state to "all-queues-open" */
354*a52629f8SDaniel Machon 	spx5_wr(HSCH_TAS_GATE_STATE_TAS_GATE_STATE_SET(0xff),
355*a52629f8SDaniel Machon 		sparx5,
356*a52629f8SDaniel Machon 		HSCH_TAS_GATE_STATE);
357*a52629f8SDaniel Machon 
358*a52629f8SDaniel Machon 	return err;
359*a52629f8SDaniel Machon }
360*a52629f8SDaniel Machon 
361*a52629f8SDaniel Machon /* Find an ADMIN list for a new schedule, after stopping a pending one. An
362*a52629f8SDaniel Machon  * operating list is returned in obsolete, to be stopped by the new list.
363*a52629f8SDaniel Machon  */
364*a52629f8SDaniel Machon static int sparx5_tas_list_find(struct sparx5_port *port, int *new,
365*a52629f8SDaniel Machon 				int *obsolete)
366*a52629f8SDaniel Machon {
367*a52629f8SDaniel Machon 	int i, err, oper = -1, state_cnt[SPX5_NUM_TAS_STATE] = {0};
368*a52629f8SDaniel Machon 	struct sparx5 *sparx5 = port->sparx5;
369*a52629f8SDaniel Machon 	int state[SPX5_TAS_ENTRIES_PER_PORT];
370*a52629f8SDaniel Machon 	int list[SPX5_TAS_ENTRIES_PER_PORT];
371*a52629f8SDaniel Machon 	bool valid = false;
372*a52629f8SDaniel Machon 
373*a52629f8SDaniel Machon 	err = sparx5_tas_shutdown_pending(port);
374*a52629f8SDaniel Machon 	if (err)
375*a52629f8SDaniel Machon 		return err;
376*a52629f8SDaniel Machon 
377*a52629f8SDaniel Machon 	for (i = 0; i < SPX5_TAS_ENTRIES_PER_PORT; i++) {
378*a52629f8SDaniel Machon 		list[i] = sparx5_tas_list_index(port, i);
379*a52629f8SDaniel Machon 		spx5_rmw(HSCH_TAS_CFG_CTRL_LIST_NUM_SET(list[i]),
380*a52629f8SDaniel Machon 			 HSCH_TAS_CFG_CTRL_LIST_NUM,
381*a52629f8SDaniel Machon 			 sparx5,
382*a52629f8SDaniel Machon 			 HSCH_TAS_CFG_CTRL);
383*a52629f8SDaniel Machon 
384*a52629f8SDaniel Machon 		state[i] = sparx5_tas_list_state_get(sparx5);
385*a52629f8SDaniel Machon 
386*a52629f8SDaniel Machon 		if (state[i] == SPX5_TAS_STATE_TERMINATING) {
387*a52629f8SDaniel Machon 			err = sparx5_tas_wait_admin(port, list[i]);
388*a52629f8SDaniel Machon 			if (err)
389*a52629f8SDaniel Machon 				return err;
390*a52629f8SDaniel Machon 			state[i] = SPX5_TAS_STATE_ADMIN;
391*a52629f8SDaniel Machon 		}
392*a52629f8SDaniel Machon 
393*a52629f8SDaniel Machon 		if (state[i] >= SPX5_NUM_TAS_STATE) {
394*a52629f8SDaniel Machon 			netdev_err(port->ndev,
395*a52629f8SDaniel Machon 				   "Invalid tas list state %u %u %d\n",
396*a52629f8SDaniel Machon 				   state[i], port->portno, i);
397*a52629f8SDaniel Machon 			return -EINVAL;
398*a52629f8SDaniel Machon 		}
399*a52629f8SDaniel Machon 
400*a52629f8SDaniel Machon 		if (state[i] == SPX5_TAS_STATE_OPERATING)
401*a52629f8SDaniel Machon 			oper = list[i];
402*a52629f8SDaniel Machon 
403*a52629f8SDaniel Machon 		state_cnt[state[i]]++;
404*a52629f8SDaniel Machon 	}
405*a52629f8SDaniel Machon 
406*a52629f8SDaniel Machon 	if (state_cnt[SPX5_TAS_STATE_ADMIN] == 2)
407*a52629f8SDaniel Machon 		valid = true;
408*a52629f8SDaniel Machon 
409*a52629f8SDaniel Machon 	if (state_cnt[SPX5_TAS_STATE_ADMIN] == 1 &&
410*a52629f8SDaniel Machon 	    state_cnt[SPX5_TAS_STATE_OPERATING] == 1)
411*a52629f8SDaniel Machon 		valid = true;
412*a52629f8SDaniel Machon 
413*a52629f8SDaniel Machon 	if (!valid) {
414*a52629f8SDaniel Machon 		netdev_err(port->ndev,
415*a52629f8SDaniel Machon 			   "Invalid tas state combination: %d %d %d %d %d\n",
416*a52629f8SDaniel Machon 			   state_cnt[SPX5_TAS_STATE_ADMIN],
417*a52629f8SDaniel Machon 			   state_cnt[SPX5_TAS_STATE_ADVANCING],
418*a52629f8SDaniel Machon 			   state_cnt[SPX5_TAS_STATE_PENDING],
419*a52629f8SDaniel Machon 			   state_cnt[SPX5_TAS_STATE_OPERATING],
420*a52629f8SDaniel Machon 			   state_cnt[SPX5_TAS_STATE_TERMINATING]);
421*a52629f8SDaniel Machon 
422*a52629f8SDaniel Machon 		return -EINVAL;
423*a52629f8SDaniel Machon 	}
424*a52629f8SDaniel Machon 
425*a52629f8SDaniel Machon 	for (i = 0; i < SPX5_TAS_ENTRIES_PER_PORT; i++) {
426*a52629f8SDaniel Machon 		if (state[i] == SPX5_TAS_STATE_ADMIN) {
427*a52629f8SDaniel Machon 			*new = list[i];
428*a52629f8SDaniel Machon 			*obsolete = (oper == -1) ? *new : oper;
429*a52629f8SDaniel Machon 			return 0;
430*a52629f8SDaniel Machon 		}
431*a52629f8SDaniel Machon 	}
432*a52629f8SDaniel Machon 
433*a52629f8SDaniel Machon 	return -EBUSY; /* No suitable list found */
434*a52629f8SDaniel Machon }
435*a52629f8SDaniel Machon 
436*a52629f8SDaniel Machon /* Program the list and its GCL entries [base, base + num_entries) */
437*a52629f8SDaniel Machon static void sparx5_tas_gcl_setup(struct sparx5_port *port, int list, int base,
438*a52629f8SDaniel Machon 				 struct tc_taprio_qopt_offload *qopt)
439*a52629f8SDaniel Machon {
440*a52629f8SDaniel Machon 	struct sparx5 *sparx5 = port->sparx5;
441*a52629f8SDaniel Machon 	u32 sched, entry, prof;
442*a52629f8SDaniel Machon 	int i;
443*a52629f8SDaniel Machon 
444*a52629f8SDaniel Machon 	sched = sparx5_tas_scheduler_get(sparx5, port->portno);
445*a52629f8SDaniel Machon 	/* Each port uses the TAS profile of its own number */
446*a52629f8SDaniel Machon 	prof = port->portno;
447*a52629f8SDaniel Machon 
448*a52629f8SDaniel Machon 	/* Program only the chosen list; the sibling slot stays in ADMIN. */
449*a52629f8SDaniel Machon 	sparx5_tas_select_list(sparx5, list);
450*a52629f8SDaniel Machon 
451*a52629f8SDaniel Machon 	spx5_rmw(HSCH_TAS_LIST_CFG_LIST_BASE_ADDR_SET(base),
452*a52629f8SDaniel Machon 		 HSCH_TAS_LIST_CFG_LIST_BASE_ADDR,
453*a52629f8SDaniel Machon 		 sparx5,
454*a52629f8SDaniel Machon 		 HSCH_TAS_LIST_CFG);
455*a52629f8SDaniel Machon 
456*a52629f8SDaniel Machon 	if (is_sparx5(sparx5)) {
457*a52629f8SDaniel Machon 		spx5_rmw(HSCH_TAS_LIST_CFG_LIST_LENGTH_SET(qopt->num_entries),
458*a52629f8SDaniel Machon 			 HSCH_TAS_LIST_CFG_LIST_LENGTH,
459*a52629f8SDaniel Machon 			 sparx5,
460*a52629f8SDaniel Machon 			 HSCH_TAS_LIST_CFG);
461*a52629f8SDaniel Machon 	} else {
462*a52629f8SDaniel Machon 		/* Associate TAS list with physical port number and
463*a52629f8SDaniel Machon 		 * scheduler element.
464*a52629f8SDaniel Machon 		 */
465*a52629f8SDaniel Machon 		spx5_rmw(HSCH_TAS_LIST_CFG_LIST_PORT_NUM_SET(port->portno),
466*a52629f8SDaniel Machon 			 HSCH_TAS_LIST_CFG_LIST_PORT_NUM, sparx5,
467*a52629f8SDaniel Machon 			 HSCH_TAS_LIST_CFG);
468*a52629f8SDaniel Machon 		spx5_rmw(HSCH_TAS_LIST_CFG_LIST_HSCH_POS_SET(sched),
469*a52629f8SDaniel Machon 			 HSCH_TAS_LIST_CFG_LIST_HSCH_POS, sparx5,
470*a52629f8SDaniel Machon 			 HSCH_TAS_LIST_CFG);
471*a52629f8SDaniel Machon 	}
472*a52629f8SDaniel Machon 
473*a52629f8SDaniel Machon 	for (i = 0; i < qopt->num_entries; i++) {
474*a52629f8SDaniel Machon 		u32 gcl_next = (i >= qopt->num_entries - 1) ? base :
475*a52629f8SDaniel Machon 							       base + i + 1;
476*a52629f8SDaniel Machon 		u32 gate_mask = qopt->entries[i].gate_mask;
477*a52629f8SDaniel Machon 
478*a52629f8SDaniel Machon 		/* On sparx5 GCL_ENTRY_NUM is relative to the list's BASE_ADDR.
479*a52629f8SDaniel Machon 		 * On lan969x the GCL is a linked list and GCL_ENTRY_NUM is an
480*a52629f8SDaniel Machon 		 * absolute index into the pool, as is NEXT_GCL.
481*a52629f8SDaniel Machon 		 */
482*a52629f8SDaniel Machon 		entry = is_sparx5(sparx5) ? i : base + i;
483*a52629f8SDaniel Machon 
484*a52629f8SDaniel Machon 		spx5_rmw(HSCH_TAS_CFG_CTRL_GCL_ENTRY_NUM_SET(entry),
485*a52629f8SDaniel Machon 			 HSCH_TAS_CFG_CTRL_GCL_ENTRY_NUM,
486*a52629f8SDaniel Machon 			 sparx5,
487*a52629f8SDaniel Machon 			 HSCH_TAS_CFG_CTRL);
488*a52629f8SDaniel Machon 
489*a52629f8SDaniel Machon 		/* Set gate states for this GCL */
490*a52629f8SDaniel Machon 		spx5_rmw(HSCH_TAS_GCL_CTRL_CFG_GATE_STATE_SET(gate_mask),
491*a52629f8SDaniel Machon 			 HSCH_TAS_GCL_CTRL_CFG_GATE_STATE, sparx5,
492*a52629f8SDaniel Machon 			 HSCH_TAS_GCL_CTRL_CFG);
493*a52629f8SDaniel Machon 
494*a52629f8SDaniel Machon 		if (is_sparx5(sparx5)) {
495*a52629f8SDaniel Machon 			spx5_rmw(HSCH_TAS_GCL_CTRL_CFG_HSCH_POS_SET(sched) |
496*a52629f8SDaniel Machon 				 HSCH_TAS_GCL_CTRL_CFG_PORT_PROFILE_SET(prof),
497*a52629f8SDaniel Machon 				 HSCH_TAS_GCL_CTRL_CFG_HSCH_POS |
498*a52629f8SDaniel Machon 				 HSCH_TAS_GCL_CTRL_CFG_PORT_PROFILE, sparx5,
499*a52629f8SDaniel Machon 				 HSCH_TAS_GCL_CTRL_CFG);
500*a52629f8SDaniel Machon 		} else {
501*a52629f8SDaniel Machon 			/* The GCL list is a linked list on lan969x */
502*a52629f8SDaniel Machon 			spx5_wr(HSCH_TAS_GCL_CTRL_CFG2_NEXT_GCL_SET(gcl_next),
503*a52629f8SDaniel Machon 				sparx5, HSCH_TAS_GCL_CTRL_CFG2);
504*a52629f8SDaniel Machon 		}
505*a52629f8SDaniel Machon 
506*a52629f8SDaniel Machon 		spx5_wr(qopt->entries[i].interval,
507*a52629f8SDaniel Machon 			sparx5,
508*a52629f8SDaniel Machon 			HSCH_TAS_GCL_TIME_CFG);
509*a52629f8SDaniel Machon 	}
510*a52629f8SDaniel Machon }
511*a52629f8SDaniel Machon 
512*a52629f8SDaniel Machon int sparx5_tas_enable(struct sparx5_port *port,
513*a52629f8SDaniel Machon 		      struct tc_taprio_qopt_offload *qopt)
514*a52629f8SDaniel Machon {
515*a52629f8SDaniel Machon 	u64 cycle_time = qopt->cycle_time, calculated_cycle_time = 0;
516*a52629f8SDaniel Machon 	int i, err, base, slot, state, new_list = -1, obsolete = -1;
517*a52629f8SDaniel Machon 	struct sparx5 *sparx5 = port->sparx5;
518*a52629f8SDaniel Machon 	struct timespec64 ts;
519*a52629f8SDaniel Machon 	ktime_t base_time;
520*a52629f8SDaniel Machon 	unsigned long end;
521*a52629f8SDaniel Machon 	u32 startup, val;
522*a52629f8SDaniel Machon 
523*a52629f8SDaniel Machon 	if (qopt->cycle_time_extension) {
524*a52629f8SDaniel Machon 		netdev_err(port->ndev,
525*a52629f8SDaniel Machon 			   "cycle_time_extension is not supported\n");
526*a52629f8SDaniel Machon 		return -EOPNOTSUPP;
527*a52629f8SDaniel Machon 	}
528*a52629f8SDaniel Machon 
529*a52629f8SDaniel Machon 	/* The base time is taken from the PTP clock */
530*a52629f8SDaniel Machon 	if (!sparx5->ptp)
531*a52629f8SDaniel Machon 		return -EOPNOTSUPP;
532*a52629f8SDaniel Machon 
533*a52629f8SDaniel Machon 	if (qopt->base_time < 0) {
534*a52629f8SDaniel Machon 		netdev_err(port->ndev, "Invalid base_time %lld\n",
535*a52629f8SDaniel Machon 			   qopt->base_time);
536*a52629f8SDaniel Machon 		return -EINVAL;
537*a52629f8SDaniel Machon 	}
538*a52629f8SDaniel Machon 
539*a52629f8SDaniel Machon 	if (!sparx5_tas_port_has_lists(port)) {
540*a52629f8SDaniel Machon 		netdev_err(port->ndev, "No TAS lists left for this port\n");
541*a52629f8SDaniel Machon 		return -EOPNOTSUPP;
542*a52629f8SDaniel Machon 	}
543*a52629f8SDaniel Machon 
544*a52629f8SDaniel Machon 	if (qopt->mqprio.qopt.num_tc != SPX5_PRIOS) {
545*a52629f8SDaniel Machon 		netdev_err(port->ndev, "Only %d traffic classes supported\n",
546*a52629f8SDaniel Machon 			   SPX5_PRIOS);
547*a52629f8SDaniel Machon 		return -EOPNOTSUPP;
548*a52629f8SDaniel Machon 	}
549*a52629f8SDaniel Machon 
550*a52629f8SDaniel Machon 	for (i = 0; i < qopt->num_entries; i++) {
551*a52629f8SDaniel Machon 		if (qopt->entries[i].command != TC_TAPRIO_CMD_SET_GATES) {
552*a52629f8SDaniel Machon 			netdev_err(port->ndev, "Unsupported GCL command: %d\n",
553*a52629f8SDaniel Machon 				   qopt->entries[i].command);
554*a52629f8SDaniel Machon 			return -EOPNOTSUPP;
555*a52629f8SDaniel Machon 		}
556*a52629f8SDaniel Machon 	}
557*a52629f8SDaniel Machon 
558*a52629f8SDaniel Machon 	mutex_lock(&sparx5->tas_lock);
559*a52629f8SDaniel Machon 
560*a52629f8SDaniel Machon 	sparx5_tas_gcl_flush(sparx5);
561*a52629f8SDaniel Machon 
562*a52629f8SDaniel Machon 	if (cycle_time > TAS_MAX_CYCLE_TIME_NS) {
563*a52629f8SDaniel Machon 		netdev_err(port->ndev, "Invalid cycle_time %llu\n", cycle_time);
564*a52629f8SDaniel Machon 		err = -EINVAL;
565*a52629f8SDaniel Machon 		goto out;
566*a52629f8SDaniel Machon 	}
567*a52629f8SDaniel Machon 
568*a52629f8SDaniel Machon 	for (i = 0; i < qopt->num_entries; i++) {
569*a52629f8SDaniel Machon 		u32 interval = qopt->entries[i].interval;
570*a52629f8SDaniel Machon 
571*a52629f8SDaniel Machon 		if (interval < TAS_MIN_CYCLE_TIME_NS) {
572*a52629f8SDaniel Machon 			netdev_err(port->ndev,
573*a52629f8SDaniel Machon 				   "Invalid minimum cycle time %u\n", interval);
574*a52629f8SDaniel Machon 			err = -EINVAL;
575*a52629f8SDaniel Machon 			goto out;
576*a52629f8SDaniel Machon 		}
577*a52629f8SDaniel Machon 
578*a52629f8SDaniel Machon 		if (interval > TAS_MAX_CYCLE_TIME_NS) {
579*a52629f8SDaniel Machon 			netdev_err(port->ndev,
580*a52629f8SDaniel Machon 				   "Invalid maximum cycle time %u\n", interval);
581*a52629f8SDaniel Machon 			err = -EINVAL;
582*a52629f8SDaniel Machon 			goto out;
583*a52629f8SDaniel Machon 		}
584*a52629f8SDaniel Machon 
585*a52629f8SDaniel Machon 		calculated_cycle_time += interval;
586*a52629f8SDaniel Machon 	}
587*a52629f8SDaniel Machon 
588*a52629f8SDaniel Machon 	if (calculated_cycle_time > TAS_MAX_CYCLE_TIME_NS ||
589*a52629f8SDaniel Machon 	    cycle_time < calculated_cycle_time) {
590*a52629f8SDaniel Machon 		netdev_err(port->ndev, "Invalid cycle_time %llu\n", cycle_time);
591*a52629f8SDaniel Machon 		err = -EINVAL;
592*a52629f8SDaniel Machon 		goto out;
593*a52629f8SDaniel Machon 	}
594*a52629f8SDaniel Machon 
595*a52629f8SDaniel Machon 	sparx5_new_base_time(sparx5, cycle_time, qopt->base_time, &base_time);
596*a52629f8SDaniel Machon 
597*a52629f8SDaniel Machon 	/* Allocate before sparx5_tas_list_find() stops a pending schedule, so
598*a52629f8SDaniel Machon 	 * running out of GCL entries fails the replace without touching
599*a52629f8SDaniel Machon 	 * hardware.
600*a52629f8SDaniel Machon 	 */
601*a52629f8SDaniel Machon 	base = sparx5_tas_gcl_alloc(sparx5, qopt->num_entries);
602*a52629f8SDaniel Machon 	if (base < 0) {
603*a52629f8SDaniel Machon 		netdev_err(port->ndev, "No room for %zu GCL entries\n",
604*a52629f8SDaniel Machon 			   qopt->num_entries);
605*a52629f8SDaniel Machon 		err = base;
606*a52629f8SDaniel Machon 		goto out;
607*a52629f8SDaniel Machon 	}
608*a52629f8SDaniel Machon 
609*a52629f8SDaniel Machon 	/* Select an appropriate entry to use */
610*a52629f8SDaniel Machon 	err = sparx5_tas_list_find(port, &new_list, &obsolete);
611*a52629f8SDaniel Machon 	if (err) {
612*a52629f8SDaniel Machon 		bitmap_clear(sparx5->tas_gcl_bitmap, base, qopt->num_entries);
613*a52629f8SDaniel Machon 		goto out;
614*a52629f8SDaniel Machon 	}
615*a52629f8SDaniel Machon 
616*a52629f8SDaniel Machon 	/* The new list is in ADMIN, so a range it still owns can go */
617*a52629f8SDaniel Machon 	slot = new_list - sparx5_tas_list_index(port, 0);
618*a52629f8SDaniel Machon 	sparx5_tas_gcl_free(port, slot);
619*a52629f8SDaniel Machon 
620*a52629f8SDaniel Machon 	sparx5_tas_gcl_setup(port, new_list, base, qopt);
621*a52629f8SDaniel Machon 
622*a52629f8SDaniel Machon 	/* The obsolete list keeps its entries until it reaches ADMIN */
623*a52629f8SDaniel Machon 	port->tas_lists[slot].gcl_base = base;
624*a52629f8SDaniel Machon 	port->tas_lists[slot].gcl_count = qopt->num_entries;
625*a52629f8SDaniel Machon 
626*a52629f8SDaniel Machon 	/* Setup TAS list */
627*a52629f8SDaniel Machon 	ts = ktime_to_timespec64(base_time);
628*a52629f8SDaniel Machon 	spx5_wr(HSCH_TAS_BASE_TIME_NSEC_BASE_TIME_NSEC_SET(ts.tv_nsec),
629*a52629f8SDaniel Machon 		sparx5,
630*a52629f8SDaniel Machon 		HSCH_TAS_BASE_TIME_NSEC);
631*a52629f8SDaniel Machon 
632*a52629f8SDaniel Machon 	spx5_wr((ts.tv_sec & GENMASK(31, 0)),
633*a52629f8SDaniel Machon 		sparx5,
634*a52629f8SDaniel Machon 		HSCH_TAS_BASE_TIME_SEC_LSB);
635*a52629f8SDaniel Machon 
636*a52629f8SDaniel Machon 	val = ts.tv_sec >> 32;
637*a52629f8SDaniel Machon 	spx5_wr(HSCH_TAS_BASE_TIME_SEC_MSB_BASE_TIME_SEC_MSB_SET(val),
638*a52629f8SDaniel Machon 		sparx5,
639*a52629f8SDaniel Machon 		HSCH_TAS_BASE_TIME_SEC_MSB);
640*a52629f8SDaniel Machon 
641*a52629f8SDaniel Machon 	spx5_wr(cycle_time,
642*a52629f8SDaniel Machon 		sparx5,
643*a52629f8SDaniel Machon 		HSCH_TAS_CYCLE_TIME_CFG);
644*a52629f8SDaniel Machon 
645*a52629f8SDaniel Machon 	/* Full write, to also clear a sticky STARTUP_ERROR from a previous use
646*a52629f8SDaniel Machon 	 * of this list.
647*a52629f8SDaniel Machon 	 */
648*a52629f8SDaniel Machon 	spx5_wr(HSCH_TAS_STARTUP_CFG_OBSOLETE_IDX_SET(obsolete) |
649*a52629f8SDaniel Machon 		HSCH_TAS_STARTUP_CFG_STARTUP_TIME_SET(TAS_STARTUP_TIME_DEFAULT),
650*a52629f8SDaniel Machon 		sparx5,
651*a52629f8SDaniel Machon 		HSCH_TAS_STARTUP_CFG);
652*a52629f8SDaniel Machon 
653*a52629f8SDaniel Machon 	/* Start list processing */
654*a52629f8SDaniel Machon 	spx5_rmw(HSCH_TAS_LIST_STATE_LIST_STATE_SET(SPX5_TAS_STATE_ADVANCING),
655*a52629f8SDaniel Machon 		 HSCH_TAS_LIST_STATE_LIST_STATE,
656*a52629f8SDaniel Machon 		 sparx5,
657*a52629f8SDaniel Machon 		 HSCH_TAS_LIST_STATE);
658*a52629f8SDaniel Machon 
659*a52629f8SDaniel Machon 	/* Do not wait forever for the startup error flag to be reported */
660*a52629f8SDaniel Machon 	end = jiffies + msecs_to_jiffies(TAS_TIMEOUT_MS);
661*a52629f8SDaniel Machon 	do {
662*a52629f8SDaniel Machon 		/* Read the state first, so an error that ended ADVANCING is
663*a52629f8SDaniel Machon 		 * already latched when STARTUP_CFG is read.
664*a52629f8SDaniel Machon 		 */
665*a52629f8SDaniel Machon 		val = spx5_rd(sparx5, HSCH_TAS_LIST_STATE);
666*a52629f8SDaniel Machon 		state = HSCH_TAS_LIST_STATE_LIST_STATE_GET(val);
667*a52629f8SDaniel Machon 		startup = spx5_rd(sparx5, HSCH_TAS_STARTUP_CFG);
668*a52629f8SDaniel Machon 		if (HSCH_TAS_STARTUP_CFG_STARTUP_ERROR_GET(startup) ||
669*a52629f8SDaniel Machon 		    state != SPX5_TAS_STATE_ADVANCING)
670*a52629f8SDaniel Machon 			break;
671*a52629f8SDaniel Machon 
672*a52629f8SDaniel Machon 		cond_resched();
673*a52629f8SDaniel Machon 	} while (!time_after(jiffies, end));
674*a52629f8SDaniel Machon 
675*a52629f8SDaniel Machon 	if (HSCH_TAS_STARTUP_CFG_STARTUP_ERROR_GET(startup)) {
676*a52629f8SDaniel Machon 		netdev_err(port->ndev, "TAS startup error in list %d\n",
677*a52629f8SDaniel Machon 			   new_list);
678*a52629f8SDaniel Machon 		err = -EIO;
679*a52629f8SDaniel Machon 	} else if (state == SPX5_TAS_STATE_ADVANCING) {
680*a52629f8SDaniel Machon 		netdev_err(port->ndev, "Timeout starting TAS list %d\n",
681*a52629f8SDaniel Machon 			   new_list);
682*a52629f8SDaniel Machon 		err = -ETIME;
683*a52629f8SDaniel Machon 	}
684*a52629f8SDaniel Machon 
685*a52629f8SDaniel Machon 	/* taprio sends no DESTROY for a failed offload, so do not leave a
686*a52629f8SDaniel Machon 	 * list behind that may still start.
687*a52629f8SDaniel Machon 	 */
688*a52629f8SDaniel Machon 	if (err)
689*a52629f8SDaniel Machon 		sparx5_tas_shutdown_pending(port);
690*a52629f8SDaniel Machon 
691*a52629f8SDaniel Machon out:
692*a52629f8SDaniel Machon 	mutex_unlock(&sparx5->tas_lock);
693*a52629f8SDaniel Machon 	return err;
694*a52629f8SDaniel Machon }
695*a52629f8SDaniel Machon 
696*a52629f8SDaniel Machon int sparx5_tas_disable(struct sparx5_port *port)
697*a52629f8SDaniel Machon {
698*a52629f8SDaniel Machon 	int err, ret;
699*a52629f8SDaniel Machon 
700*a52629f8SDaniel Machon 	/* Nothing was offloaded on a port without lists */
701*a52629f8SDaniel Machon 	if (!sparx5_tas_port_has_lists(port))
702*a52629f8SDaniel Machon 		return 0;
703*a52629f8SDaniel Machon 
704*a52629f8SDaniel Machon 	mutex_lock(&port->sparx5->tas_lock);
705*a52629f8SDaniel Machon 
706*a52629f8SDaniel Machon 	sparx5_tas_gcl_flush(port->sparx5);
707*a52629f8SDaniel Machon 
708*a52629f8SDaniel Machon 	/* Stop the operating list and restore the gates even if a pending list
709*a52629f8SDaniel Machon 	 * could not be stopped, as taprio does not retry a failed destroy.
710*a52629f8SDaniel Machon 	 */
711*a52629f8SDaniel Machon 	err = sparx5_tas_shutdown_pending(port);
712*a52629f8SDaniel Machon 	ret = sparx5_tas_shutdown_operating(port);
713*a52629f8SDaniel Machon 	if (!err)
714*a52629f8SDaniel Machon 		err = ret;
715*a52629f8SDaniel Machon 
716*a52629f8SDaniel Machon 	mutex_unlock(&port->sparx5->tas_lock);
717*a52629f8SDaniel Machon 
718*a52629f8SDaniel Machon 	return err;
719*a52629f8SDaniel Machon }
720*a52629f8SDaniel Machon 
721*a52629f8SDaniel Machon int sparx5_tas_init(struct sparx5 *sparx5)
722*a52629f8SDaniel Machon {
723*a52629f8SDaniel Machon 	const struct sparx5_consts *consts = sparx5->data->consts;
724*a52629f8SDaniel Machon 	int i, num_ports, list_num_max;
725*a52629f8SDaniel Machon 
726*a52629f8SDaniel Machon 	/* There are only 128 (sparx5) / 60 (lan969x) TAS lists, not enough
727*a52629f8SDaniel Machon 	 * for the whole port range. LIST_NUM_MAX is the highest list index
728*a52629f8SDaniel Machon 	 * processed, and the datasheet gives 24 as its minimum value.
729*a52629f8SDaniel Machon 	 */
730*a52629f8SDaniel Machon 	num_ports = consts->n_ports;
731*a52629f8SDaniel Machon 	list_num_max = max(sparx5_tas_num_lists(sparx5) - 1, 24);
732*a52629f8SDaniel Machon 
733*a52629f8SDaniel Machon 	sparx5->tas_gcl_bitmap = devm_bitmap_zalloc(sparx5->dev,
734*a52629f8SDaniel Machon 						    consts->n_tas_gcl,
735*a52629f8SDaniel Machon 						    GFP_KERNEL);
736*a52629f8SDaniel Machon 	if (!sparx5->tas_gcl_bitmap)
737*a52629f8SDaniel Machon 		return -ENOMEM;
738*a52629f8SDaniel Machon 
739*a52629f8SDaniel Machon 	/* Unwritten NEXT_GCL fields read as 0, so no list may own entry 0 */
740*a52629f8SDaniel Machon 	bitmap_set(sparx5->tas_gcl_bitmap, 0, 1);
741*a52629f8SDaniel Machon 
742*a52629f8SDaniel Machon 	mutex_init(&sparx5->tas_lock);
743*a52629f8SDaniel Machon 
744*a52629f8SDaniel Machon 	/* Apply the guard band to gate closes of all traffic classes, not only
745*a52629f8SDaniel Machon 	 * the classes not marked as scheduled in SCH_TRAFFIC_QUEUES.
746*a52629f8SDaniel Machon 	 */
747*a52629f8SDaniel Machon 	spx5_rmw(HSCH_TAS_CFG_CTRL_LIST_NUM_MAX_SET(list_num_max) |
748*a52629f8SDaniel Machon 		 HSCH_TAS_CFG_CTRL_ALWAYS_GUARD_BAND_SCH_Q_SET(1),
749*a52629f8SDaniel Machon 		 HSCH_TAS_CFG_CTRL_LIST_NUM_MAX |
750*a52629f8SDaniel Machon 		 HSCH_TAS_CFG_CTRL_ALWAYS_GUARD_BAND_SCH_Q,
751*a52629f8SDaniel Machon 		 sparx5,
752*a52629f8SDaniel Machon 		 HSCH_TAS_CFG_CTRL);
753*a52629f8SDaniel Machon 
754*a52629f8SDaniel Machon 	/* Associate profile with port (profile idx = port on lan969x) */
755*a52629f8SDaniel Machon 	if (is_sparx5(sparx5)) {
756*a52629f8SDaniel Machon 		for (i = 0; i < num_ports; i++) {
757*a52629f8SDaniel Machon 			if (!sparx5->ports[i])
758*a52629f8SDaniel Machon 				continue;
759*a52629f8SDaniel Machon 
760*a52629f8SDaniel Machon 			spx5_rmw(HSCH_TAS_PROFILE_CONFIG_PORT_NUM_SET(i),
761*a52629f8SDaniel Machon 				 HSCH_TAS_PROFILE_CONFIG_PORT_NUM,
762*a52629f8SDaniel Machon 				 sparx5,
763*a52629f8SDaniel Machon 				 HSCH_TAS_PROFILE_CONFIG(i));
764*a52629f8SDaniel Machon 		}
765*a52629f8SDaniel Machon 	}
766*a52629f8SDaniel Machon 
767*a52629f8SDaniel Machon 	return 0;
768*a52629f8SDaniel Machon }
769*a52629f8SDaniel Machon 
77096628508SDaniel Machon void sparx5_tas_speed(struct sparx5_port *port, int speed)
77196628508SDaniel Machon {
77296628508SDaniel Machon 	struct sparx5 *sparx5 = port->sparx5;
77396628508SDaniel Machon 	u8 spd;
77496628508SDaniel Machon 
77596628508SDaniel Machon 	switch (speed) {
77696628508SDaniel Machon 	case SPEED_10:
77796628508SDaniel Machon 		spd = TAS_SPEED_10;
77896628508SDaniel Machon 		break;
77996628508SDaniel Machon 	case SPEED_100:
78096628508SDaniel Machon 		spd = TAS_SPEED_100;
78196628508SDaniel Machon 		break;
78296628508SDaniel Machon 	case SPEED_1000:
78396628508SDaniel Machon 		spd = TAS_SPEED_1000;
78496628508SDaniel Machon 		break;
78596628508SDaniel Machon 	case SPEED_2500:
78696628508SDaniel Machon 		spd = TAS_SPEED_2500;
78796628508SDaniel Machon 		break;
78896628508SDaniel Machon 	case SPEED_5000:
78996628508SDaniel Machon 		spd = TAS_SPEED_5000;
79096628508SDaniel Machon 		break;
79196628508SDaniel Machon 	case SPEED_10000:
79296628508SDaniel Machon 		spd = TAS_SPEED_10000;
79396628508SDaniel Machon 		break;
79496628508SDaniel Machon 	case SPEED_25000:
79596628508SDaniel Machon 		spd = TAS_SPEED_25000;
79696628508SDaniel Machon 		break;
79796628508SDaniel Machon 	default:
79896628508SDaniel Machon 		netdev_err(port->ndev, "TAS: Unsupported speed: %d\n", speed);
79996628508SDaniel Machon 		return;
80096628508SDaniel Machon 	}
80196628508SDaniel Machon 
80296628508SDaniel Machon 	spx5_rmw(HSCH_TAS_PROFILE_CONFIG_LINK_SPEED_SET(spd),
80396628508SDaniel Machon 		 HSCH_TAS_PROFILE_CONFIG_LINK_SPEED,
80496628508SDaniel Machon 		 sparx5,
80596628508SDaniel Machon 		 HSCH_TAS_PROFILE_CONFIG(port->portno));
80696628508SDaniel Machon }
807