Linux Audio

Check our new training course

Loading...
Note: File does not exist in v5.4.
  1// SPDX-License-Identifier: GPL-2.0+
  2/* Microchip Sparx5 Switch driver
  3 *
  4 * Copyright (c) 2021 Microchip Technology Inc. and its subsidiaries.
  5 */
  6
  7#include <linux/module.h>
  8#include <linux/device.h>
  9
 10#include "sparx5_main_regs.h"
 11#include "sparx5_main.h"
 12
 13/* QSYS calendar information */
 14#define SPX5_PORTS_PER_CALREG          10  /* Ports mapped in a calendar register */
 15#define SPX5_CALBITS_PER_PORT          3   /* Bit per port in calendar register */
 16
 17/* DSM calendar information */
 18#define SPX5_DSM_CAL_TAXIS             8
 19#define SPX5_DSM_CAL_BW_LOSS           553
 20
 21#define SPX5_TAXI_PORT_MAX             70
 22
 23#define SPEED_12500                    12500
 24
 25/* Maps from taxis to port numbers */
 26static u32 sparx5_taxi_ports[SPX5_DSM_CAL_TAXIS][SPX5_DSM_CAL_MAX_DEVS_PER_TAXI] = {
 27	{57, 12, 0, 1, 2, 16, 17, 18, 19, 20, 21, 22, 23},
 28	{58, 13, 3, 4, 5, 24, 25, 26, 27, 28, 29, 30, 31},
 29	{59, 14, 6, 7, 8, 32, 33, 34, 35, 36, 37, 38, 39},
 30	{60, 15, 9, 10, 11, 40, 41, 42, 43, 44, 45, 46, 47},
 31	{61, 48, 49, 50, 99, 99, 99, 99, 99, 99, 99, 99, 99},
 32	{62, 51, 52, 53, 99, 99, 99, 99, 99, 99, 99, 99, 99},
 33	{56, 63, 54, 55, 99, 99, 99, 99, 99, 99, 99, 99, 99},
 34	{64, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99},
 35};
 36
 37static u32 sparx5_target_bandwidth(struct sparx5 *sparx5)
 38{
 39	switch (sparx5->target_ct) {
 40	case SPX5_TARGET_CT_7546:
 41	case SPX5_TARGET_CT_7546TSN:
 42		return 65000;
 43	case SPX5_TARGET_CT_7549:
 44	case SPX5_TARGET_CT_7549TSN:
 45		return 91000;
 46	case SPX5_TARGET_CT_7552:
 47	case SPX5_TARGET_CT_7552TSN:
 48		return 129000;
 49	case SPX5_TARGET_CT_7556:
 50	case SPX5_TARGET_CT_7556TSN:
 51		return 161000;
 52	case SPX5_TARGET_CT_7558:
 53	case SPX5_TARGET_CT_7558TSN:
 54		return 201000;
 55	case SPX5_TARGET_CT_LAN9691VAO:
 56		return 46000;
 57	case SPX5_TARGET_CT_LAN9694RED:
 58	case SPX5_TARGET_CT_LAN9694TSN:
 59	case SPX5_TARGET_CT_LAN9694:
 60		return 68000;
 61	case SPX5_TARGET_CT_LAN9696RED:
 62	case SPX5_TARGET_CT_LAN9696TSN:
 63	case SPX5_TARGET_CT_LAN9692VAO:
 64	case SPX5_TARGET_CT_LAN9696:
 65		return 88000;
 66	case SPX5_TARGET_CT_LAN9698RED:
 67	case SPX5_TARGET_CT_LAN9698TSN:
 68	case SPX5_TARGET_CT_LAN9693VAO:
 69	case SPX5_TARGET_CT_LAN9698:
 70		return 101000;
 71	default:
 72		return 0;
 73	}
 74}
 75
 76static u32 sparx5_clk_to_bandwidth(enum sparx5_core_clockfreq cclock)
 77{
 78	switch (cclock) {
 79	case SPX5_CORE_CLOCK_250MHZ: return 83000; /* 250000 / 3 */
 80	case SPX5_CORE_CLOCK_328MHZ: return 109375; /* 328000 / 3 */
 81	case SPX5_CORE_CLOCK_500MHZ: return 166000; /* 500000 / 3 */
 82	case SPX5_CORE_CLOCK_625MHZ: return  208000; /* 625000 / 3 */
 83	default: return 0;
 84	}
 85	return 0;
 86}
 87
 88u32 sparx5_cal_speed_to_value(enum sparx5_cal_bw speed)
 89{
 90	switch (speed) {
 91	case SPX5_CAL_SPEED_1G:   return 1000;
 92	case SPX5_CAL_SPEED_2G5:  return 2500;
 93	case SPX5_CAL_SPEED_5G:   return 5000;
 94	case SPX5_CAL_SPEED_10G:  return 10000;
 95	case SPX5_CAL_SPEED_25G:  return 25000;
 96	case SPX5_CAL_SPEED_0G5:  return 500;
 97	case SPX5_CAL_SPEED_12G5: return 12500;
 98	default: return 0;
 99	}
100}
101
102static u32 sparx5_bandwidth_to_calendar(u32 bw)
103{
104	switch (bw) {
105	case SPEED_10:      return SPX5_CAL_SPEED_0G5;
106	case SPEED_100:     return SPX5_CAL_SPEED_0G5;
107	case SPEED_1000:    return SPX5_CAL_SPEED_1G;
108	case SPEED_2500:    return SPX5_CAL_SPEED_2G5;
109	case SPEED_5000:    return SPX5_CAL_SPEED_5G;
110	case SPEED_10000:   return SPX5_CAL_SPEED_10G;
111	case SPEED_12500:   return SPX5_CAL_SPEED_12G5;
112	case SPEED_25000:   return SPX5_CAL_SPEED_25G;
113	case SPEED_UNKNOWN: return SPX5_CAL_SPEED_1G;
114	default:            return SPX5_CAL_SPEED_NONE;
115	}
116}
117
118enum sparx5_cal_bw sparx5_get_port_cal_speed(struct sparx5 *sparx5, u32 portno)
119{
120	struct sparx5_port *port;
121
122	if (portno >= sparx5->data->consts->n_ports) {
123		/* Internal ports */
124		if (portno ==
125			    sparx5_get_internal_port(sparx5, SPX5_PORT_CPU_0) ||
126		    portno ==
127			    sparx5_get_internal_port(sparx5, SPX5_PORT_CPU_1)) {
128			/* Equals 1.25G */
129			return SPX5_CAL_SPEED_2G5;
130		} else if (portno ==
131			   sparx5_get_internal_port(sparx5, SPX5_PORT_VD0)) {
132			/* IPMC only idle BW */
133			return SPX5_CAL_SPEED_NONE;
134		} else if (portno ==
135			   sparx5_get_internal_port(sparx5, SPX5_PORT_VD1)) {
136			/* OAM only idle BW */
137			return SPX5_CAL_SPEED_NONE;
138		} else if (portno ==
139			   sparx5_get_internal_port(sparx5, SPX5_PORT_VD2)) {
140			/* IPinIP gets only idle BW */
141			return SPX5_CAL_SPEED_NONE;
142		}
143		/* not in port map */
144		return SPX5_CAL_SPEED_NONE;
145	}
146	/* Front ports - may be used */
147	port = sparx5->ports[portno];
148	if (!port)
149		return SPX5_CAL_SPEED_NONE;
150	return sparx5_bandwidth_to_calendar(port->conf.bandwidth);
151}
152
153/* Auto configure the QSYS calendar based on port configuration */
154int sparx5_config_auto_calendar(struct sparx5 *sparx5)
155{
156	const struct sparx5_consts *consts = sparx5->data->consts;
157	u32 cal[7], value, idx, portno;
158	u32 max_core_bw;
159	u32 total_bw = 0, used_port_bw = 0;
160	int err = 0;
161	enum sparx5_cal_bw spd;
162
163	memset(cal, 0, sizeof(cal));
164
165	max_core_bw = sparx5_clk_to_bandwidth(sparx5->coreclock);
166	if (max_core_bw == 0) {
167		dev_err(sparx5->dev, "Core clock not supported");
168		return -EINVAL;
169	}
170
171	/* Setup the calendar with the bandwidth to each port */
172	for (portno = 0; portno < consts->n_ports_all; portno++) {
173		u64 reg, offset, this_bw;
174
175		spd = sparx5_get_port_cal_speed(sparx5, portno);
176		if (spd == SPX5_CAL_SPEED_NONE)
177			continue;
178
179		this_bw = sparx5_cal_speed_to_value(spd);
180		if (portno < consts->n_ports)
181			used_port_bw += this_bw;
182		else
183			/* Internal ports are granted half the value */
184			this_bw = this_bw / 2;
185		total_bw += this_bw;
186		reg = portno;
187		offset = do_div(reg, SPX5_PORTS_PER_CALREG);
188		cal[reg] |= spd << (offset * SPX5_CALBITS_PER_PORT);
189	}
190
191	if (used_port_bw > sparx5_target_bandwidth(sparx5)) {
192		dev_err(sparx5->dev,
193			"Port BW %u above target BW %u\n",
194			used_port_bw, sparx5_target_bandwidth(sparx5));
195		return -EINVAL;
196	}
197
198	if (total_bw > max_core_bw) {
199		dev_err(sparx5->dev,
200			"Total BW %u above switch core BW %u\n",
201			total_bw, max_core_bw);
202		return -EINVAL;
203	}
204
205	/* Halt the calendar while changing it */
206	if (is_sparx5(sparx5))
207		spx5_rmw(QSYS_CAL_CTRL_CAL_MODE_SET(10),
208			 QSYS_CAL_CTRL_CAL_MODE,
209			 sparx5, QSYS_CAL_CTRL);
210
211	/* Assign port bandwidth to auto calendar */
212	for (idx = 0; idx < consts->n_auto_cals; idx++)
213		spx5_wr(cal[idx], sparx5, QSYS_CAL_AUTO(idx));
214
215	/* Increase grant rate of all ports to account for
216	 * core clock ppm deviations
217	 */
218	spx5_rmw(QSYS_CAL_CTRL_CAL_AUTO_GRANT_RATE_SET(671), /* 672->671 */
219		 QSYS_CAL_CTRL_CAL_AUTO_GRANT_RATE,
220		 sparx5,
221		 QSYS_CAL_CTRL);
222
223	/* Grant idle usage to VD 0-2 */
224	for (idx = 2; idx < 5; idx++)
225		spx5_wr(HSCH_OUTB_SHARE_ENA_OUTB_SHARE_ENA_SET(12),
226			sparx5,
227			HSCH_OUTB_SHARE_ENA(idx));
228
229	/* Enable Auto mode */
230	spx5_rmw(QSYS_CAL_CTRL_CAL_MODE_SET(8),
231		 QSYS_CAL_CTRL_CAL_MODE,
232		 sparx5, QSYS_CAL_CTRL);
233
234	/* Verify successful calendar config */
235	value = spx5_rd(sparx5, QSYS_CAL_CTRL);
236	if (QSYS_CAL_CTRL_CAL_AUTO_ERROR_GET(value)) {
237		dev_err(sparx5->dev, "QSYS calendar error\n");
238		err = -EINVAL;
239	}
240	return err;
241}
242
243static u32 sparx5_dsm_exb_gcd(u32 a, u32 b)
244{
245	if (b == 0)
246		return a;
247	return sparx5_dsm_exb_gcd(b, a % b);
248}
249
250static u32 sparx5_dsm_cal_len(u32 *cal)
251{
252	u32 idx = 0, len = 0;
253
254	while (idx < SPX5_DSM_CAL_LEN) {
255		if (cal[idx] != SPX5_DSM_CAL_EMPTY)
256			len++;
257		idx++;
258	}
259	return len;
260}
261
262static u32 sparx5_dsm_cp_cal(u32 *sched)
263{
264	u32 idx = 0, tmp;
265
266	while (idx < SPX5_DSM_CAL_LEN) {
267		if (sched[idx] != SPX5_DSM_CAL_EMPTY) {
268			tmp = sched[idx];
269			sched[idx] = SPX5_DSM_CAL_EMPTY;
270			return tmp;
271		}
272		idx++;
273	}
274	return SPX5_DSM_CAL_EMPTY;
275}
276
277int sparx5_dsm_calendar_calc(struct sparx5 *sparx5, u32 taxi,
278			     struct sparx5_calendar_data *data)
279{
280	bool slow_mode;
281	u32 gcd, idx, sum, min, factor;
282	u32 num_of_slots, slot_spd, empty_slots;
283	u32 taxi_bw, clk_period_ps;
284
285	clk_period_ps = sparx5_clk_period(sparx5->coreclock);
286	taxi_bw = 128 * 1000000 / clk_period_ps;
287	slow_mode = !!(clk_period_ps > 2000);
288	memcpy(data->taxi_ports, &sparx5_taxi_ports[taxi],
289	       sizeof(data->taxi_ports));
290
291	for (idx = 0; idx < SPX5_DSM_CAL_LEN; idx++) {
292		data->new_slots[idx] = SPX5_DSM_CAL_EMPTY;
293		data->schedule[idx] = SPX5_DSM_CAL_EMPTY;
294		data->temp_sched[idx] = SPX5_DSM_CAL_EMPTY;
295	}
296	/* Default empty calendar */
297	data->schedule[0] = SPX5_DSM_CAL_MAX_DEVS_PER_TAXI;
298
299	/* Map ports to taxi positions */
300	for (idx = 0; idx < SPX5_DSM_CAL_MAX_DEVS_PER_TAXI; idx++) {
301		u32 portno = data->taxi_ports[idx];
302
303		if (portno < sparx5->data->consts->n_ports_all) {
304			data->taxi_speeds[idx] = sparx5_cal_speed_to_value
305				(sparx5_get_port_cal_speed(sparx5, portno));
306		} else {
307			data->taxi_speeds[idx] = 0;
308		}
309	}
310
311	sum = 0;
312	min = 25000;
313	for (idx = 0; idx < ARRAY_SIZE(data->taxi_speeds); idx++) {
314		u32 jdx;
315
316		sum += data->taxi_speeds[idx];
317		if (data->taxi_speeds[idx] && data->taxi_speeds[idx] < min)
318			min = data->taxi_speeds[idx];
319		gcd = min;
320		for (jdx = 0; jdx < ARRAY_SIZE(data->taxi_speeds); jdx++)
321			gcd = sparx5_dsm_exb_gcd(gcd, data->taxi_speeds[jdx]);
322	}
323	if (sum == 0) /* Empty calendar */
324		return 0;
325	/* Make room for overhead traffic */
326	factor = 100 * 100 * 1000 / (100 * 100 - SPX5_DSM_CAL_BW_LOSS);
327
328	if (sum * factor > (taxi_bw * 1000)) {
329		dev_err(sparx5->dev,
330			"Taxi %u, Requested BW %u above available BW %u\n",
331			taxi, sum, taxi_bw);
332		return -EINVAL;
333	}
334	for (idx = 0; idx < 4; idx++) {
335		u32 raw_spd;
336
337		if (idx == 0)
338			raw_spd = gcd / 5;
339		else if (idx == 1)
340			raw_spd = gcd / 2;
341		else if (idx == 2)
342			raw_spd = gcd;
343		else
344			raw_spd = min;
345		slot_spd = raw_spd * factor / 1000;
346		num_of_slots = taxi_bw / slot_spd;
347		if (num_of_slots <= 64)
348			break;
349	}
350
351	num_of_slots = num_of_slots > 64 ? 64 : num_of_slots;
352	slot_spd = taxi_bw / num_of_slots;
353
354	sum = 0;
355	for (idx = 0; idx < ARRAY_SIZE(data->taxi_speeds); idx++) {
356		u32 spd = data->taxi_speeds[idx];
357		u32 adjusted_speed = data->taxi_speeds[idx] * factor / 1000;
358
359		if (adjusted_speed > 0) {
360			data->avg_dist[idx] = (128 * 1000000 * 10) /
361				(adjusted_speed * clk_period_ps);
362		} else {
363			data->avg_dist[idx] = -1;
364		}
365		data->dev_slots[idx] = ((spd * factor / slot_spd) + 999) / 1000;
366		if (spd != 25000 && (spd != 10000 || !slow_mode)) {
367			if (num_of_slots < (5 * data->dev_slots[idx])) {
368				dev_err(sparx5->dev,
369					"Taxi %u, speed %u, Low slot sep.\n",
370					taxi, spd);
371				return -EINVAL;
372			}
373		}
374		sum += data->dev_slots[idx];
375		if (sum > num_of_slots) {
376			dev_err(sparx5->dev,
377				"Taxi %u with overhead factor %u\n",
378				taxi, factor);
379			return -EINVAL;
380		}
381	}
382
383	empty_slots = num_of_slots - sum;
384
385	for (idx = 0; idx < empty_slots; idx++)
386		data->schedule[idx] = SPX5_DSM_CAL_MAX_DEVS_PER_TAXI;
387
388	for (idx = 1; idx < num_of_slots; idx++) {
389		u32 indices_len = 0;
390		u32 slot, jdx, kdx, ts;
391		s32 cnt;
392		u32 num_of_old_slots, num_of_new_slots, tgt_score;
393
394		for (slot = 0; slot < ARRAY_SIZE(data->dev_slots); slot++) {
395			if (data->dev_slots[slot] == idx) {
396				data->indices[indices_len] = slot;
397				indices_len++;
398			}
399		}
400		if (indices_len == 0)
401			continue;
402		kdx = 0;
403		for (slot = 0; slot < idx; slot++) {
404			for (jdx = 0; jdx < indices_len; jdx++, kdx++)
405				data->new_slots[kdx] = data->indices[jdx];
406		}
407
408		for (slot = 0; slot < SPX5_DSM_CAL_LEN; slot++) {
409			if (data->schedule[slot] == SPX5_DSM_CAL_EMPTY)
410				break;
411		}
412
413		num_of_old_slots =  slot;
414		num_of_new_slots =  kdx;
415		cnt = 0;
416		ts = 0;
417
418		if (num_of_new_slots > num_of_old_slots) {
419			memcpy(data->short_list, data->schedule,
420			       sizeof(data->short_list));
421			memcpy(data->long_list, data->new_slots,
422			       sizeof(data->long_list));
423			tgt_score = 100000 * num_of_old_slots /
424				num_of_new_slots;
425		} else {
426			memcpy(data->short_list, data->new_slots,
427			       sizeof(data->short_list));
428			memcpy(data->long_list, data->schedule,
429			       sizeof(data->long_list));
430			tgt_score = 100000 * num_of_new_slots /
431				num_of_old_slots;
432		}
433
434		while (sparx5_dsm_cal_len(data->short_list) > 0 ||
435		       sparx5_dsm_cal_len(data->long_list) > 0) {
436			u32 act = 0;
437
438			if (sparx5_dsm_cal_len(data->short_list) > 0) {
439				data->temp_sched[ts] =
440					sparx5_dsm_cp_cal(data->short_list);
441				ts++;
442				cnt += 100000;
443				act = 1;
444			}
445			while (sparx5_dsm_cal_len(data->long_list) > 0 &&
446			       cnt > 0) {
447				data->temp_sched[ts] =
448					sparx5_dsm_cp_cal(data->long_list);
449				ts++;
450				cnt -= tgt_score;
451				act = 1;
452			}
453			if (act == 0) {
454				dev_err(sparx5->dev,
455					"Error in DSM calendar calculation\n");
456				return -EINVAL;
457			}
458		}
459
460		for (slot = 0; slot < SPX5_DSM_CAL_LEN; slot++) {
461			if (data->temp_sched[slot] == SPX5_DSM_CAL_EMPTY)
462				break;
463		}
464		for (slot = 0; slot < SPX5_DSM_CAL_LEN; slot++) {
465			data->schedule[slot] = data->temp_sched[slot];
466			data->temp_sched[slot] = SPX5_DSM_CAL_EMPTY;
467			data->new_slots[slot] = SPX5_DSM_CAL_EMPTY;
468		}
469	}
470	return 0;
471}
472
473static int sparx5_dsm_calendar_check(struct sparx5 *sparx5,
474				     struct sparx5_calendar_data *data)
475{
476	u32 num_of_slots, idx, port;
477	int cnt, max_dist;
478	u32 slot_indices[SPX5_DSM_CAL_LEN], distances[SPX5_DSM_CAL_LEN];
479	u32 cal_length = sparx5_dsm_cal_len(data->schedule);
480
481	for (port = 0; port < SPX5_DSM_CAL_MAX_DEVS_PER_TAXI; port++) {
482		num_of_slots = 0;
483		max_dist = data->avg_dist[port];
484		for (idx = 0; idx < SPX5_DSM_CAL_LEN; idx++) {
485			slot_indices[idx] = SPX5_DSM_CAL_EMPTY;
486			distances[idx] = SPX5_DSM_CAL_EMPTY;
487		}
488
489		for (idx = 0; idx < cal_length; idx++) {
490			if (data->schedule[idx] == port) {
491				slot_indices[num_of_slots] = idx;
492				num_of_slots++;
493			}
494		}
495
496		slot_indices[num_of_slots] = slot_indices[0] + cal_length;
497
498		for (idx = 0; idx < num_of_slots; idx++) {
499			distances[idx] = (slot_indices[idx + 1] -
500					  slot_indices[idx]) * 10;
501		}
502
503		for (idx = 0; idx < num_of_slots; idx++) {
504			u32 jdx, kdx;
505
506			cnt = distances[idx] - max_dist;
507			if (cnt < 0)
508				cnt = -cnt;
509			kdx = 0;
510			for (jdx = (idx + 1) % num_of_slots;
511			     jdx != idx;
512			     jdx = (jdx + 1) % num_of_slots, kdx++) {
513				cnt =  cnt + distances[jdx] - max_dist;
514				if (cnt < 0)
515					cnt = -cnt;
516				if (cnt > max_dist)
517					goto check_err;
518			}
519		}
520	}
521	return 0;
522check_err:
523	dev_err(sparx5->dev,
524		"Port %u: distance %u above limit %d\n",
525		port, cnt, max_dist);
526	return -EINVAL;
527}
528
529static int sparx5_dsm_calendar_update(struct sparx5 *sparx5, u32 taxi,
530				      struct sparx5_calendar_data *data)
531{
532	u32 cal_len = sparx5_dsm_cal_len(data->schedule), len, idx;
533
534	if (!is_sparx5(sparx5)) {
535		u32 val, act;
536
537		val = spx5_rd(sparx5, DSM_TAXI_CAL_CFG(taxi));
538		act = DSM_TAXI_CAL_CFG_CAL_SEL_STAT_GET(val);
539
540		spx5_rmw(DSM_TAXI_CAL_CFG_CAL_PGM_SEL_SET(!act),
541			 DSM_TAXI_CAL_CFG_CAL_PGM_SEL,
542			 sparx5, DSM_TAXI_CAL_CFG(taxi));
543	}
544
545	spx5_rmw(DSM_TAXI_CAL_CFG_CAL_PGM_ENA_SET(1),
546		 DSM_TAXI_CAL_CFG_CAL_PGM_ENA,
547		 sparx5,
548		 DSM_TAXI_CAL_CFG(taxi));
549	for (idx = 0; idx < cal_len; idx++) {
550		spx5_rmw(DSM_TAXI_CAL_CFG_CAL_IDX_SET(idx),
551			 DSM_TAXI_CAL_CFG_CAL_IDX,
552			 sparx5,
553			 DSM_TAXI_CAL_CFG(taxi));
554		spx5_rmw(DSM_TAXI_CAL_CFG_CAL_PGM_VAL_SET(data->schedule[idx]),
555			 DSM_TAXI_CAL_CFG_CAL_PGM_VAL,
556			 sparx5,
557			 DSM_TAXI_CAL_CFG(taxi));
558	}
559	spx5_rmw(DSM_TAXI_CAL_CFG_CAL_PGM_ENA_SET(0),
560		 DSM_TAXI_CAL_CFG_CAL_PGM_ENA,
561		 sparx5,
562		 DSM_TAXI_CAL_CFG(taxi));
563	len = DSM_TAXI_CAL_CFG_CAL_CUR_LEN_GET(spx5_rd(sparx5,
564						       DSM_TAXI_CAL_CFG(taxi)));
565	if (len != cal_len - 1)
566		goto update_err;
567
568	if (!is_sparx5(sparx5)) {
569		spx5_rmw(DSM_TAXI_CAL_CFG_CAL_SWITCH_SET(1),
570			 DSM_TAXI_CAL_CFG_CAL_SWITCH,
571			 sparx5, DSM_TAXI_CAL_CFG(taxi));
572	}
573
574	return 0;
575update_err:
576	dev_err(sparx5->dev, "Incorrect calendar length: %u\n", len);
577	return -EINVAL;
578}
579
580/* Configure the DSM calendar based on port configuration */
581int sparx5_config_dsm_calendar(struct sparx5 *sparx5)
582{
583	const struct sparx5_ops *ops = sparx5->data->ops;
584	int taxi;
585	struct sparx5_calendar_data *data;
586	int err = 0;
587
588	data = kzalloc(sizeof(*data), GFP_KERNEL);
589	if (!data)
590		return -ENOMEM;
591
592	for (taxi = 0; taxi < sparx5->data->consts->n_dsm_cal_taxis; ++taxi) {
593		err = ops->dsm_calendar_calc(sparx5, taxi, data);
594		if (err) {
595			dev_err(sparx5->dev, "DSM calendar calculation failed\n");
596			goto cal_out;
597		}
598		err = sparx5_dsm_calendar_check(sparx5, data);
599		if (err) {
600			dev_err(sparx5->dev, "DSM calendar check failed\n");
601			goto cal_out;
602		}
603		err = sparx5_dsm_calendar_update(sparx5, taxi, data);
604		if (err) {
605			dev_err(sparx5->dev, "DSM calendar update failed\n");
606			goto cal_out;
607		}
608	}
609cal_out:
610	kfree(data);
611	return err;
612}