597 lines
15 KiB
C
597 lines
15 KiB
C
|
// SPDX-License-Identifier: GPL-2.0+
|
||
|
/* Microchip Sparx5 Switch driver
|
||
|
*
|
||
|
* Copyright (c) 2021 Microchip Technology Inc. and its subsidiaries.
|
||
|
*/
|
||
|
|
||
|
#include <linux/module.h>
|
||
|
#include <linux/device.h>
|
||
|
|
||
|
#include "sparx5_main_regs.h"
|
||
|
#include "sparx5_main.h"
|
||
|
|
||
|
/* QSYS calendar information */
|
||
|
#define SPX5_PORTS_PER_CALREG 10 /* Ports mapped in a calendar register */
|
||
|
#define SPX5_CALBITS_PER_PORT 3 /* Bit per port in calendar register */
|
||
|
|
||
|
/* DSM calendar information */
|
||
|
#define SPX5_DSM_CAL_LEN 64
|
||
|
#define SPX5_DSM_CAL_EMPTY 0xFFFF
|
||
|
#define SPX5_DSM_CAL_MAX_DEVS_PER_TAXI 13
|
||
|
#define SPX5_DSM_CAL_TAXIS 8
|
||
|
#define SPX5_DSM_CAL_BW_LOSS 553
|
||
|
|
||
|
#define SPX5_TAXI_PORT_MAX 70
|
||
|
|
||
|
#define SPEED_12500 12500
|
||
|
|
||
|
/* Maps from taxis to port numbers */
|
||
|
static u32 sparx5_taxi_ports[SPX5_DSM_CAL_TAXIS][SPX5_DSM_CAL_MAX_DEVS_PER_TAXI] = {
|
||
|
{57, 12, 0, 1, 2, 16, 17, 18, 19, 20, 21, 22, 23},
|
||
|
{58, 13, 3, 4, 5, 24, 25, 26, 27, 28, 29, 30, 31},
|
||
|
{59, 14, 6, 7, 8, 32, 33, 34, 35, 36, 37, 38, 39},
|
||
|
{60, 15, 9, 10, 11, 40, 41, 42, 43, 44, 45, 46, 47},
|
||
|
{61, 48, 49, 50, 99, 99, 99, 99, 99, 99, 99, 99, 99},
|
||
|
{62, 51, 52, 53, 99, 99, 99, 99, 99, 99, 99, 99, 99},
|
||
|
{56, 63, 54, 55, 99, 99, 99, 99, 99, 99, 99, 99, 99},
|
||
|
{64, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99},
|
||
|
};
|
||
|
|
||
|
struct sparx5_calendar_data {
|
||
|
u32 schedule[SPX5_DSM_CAL_LEN];
|
||
|
u32 avg_dist[SPX5_DSM_CAL_MAX_DEVS_PER_TAXI];
|
||
|
u32 taxi_ports[SPX5_DSM_CAL_MAX_DEVS_PER_TAXI];
|
||
|
u32 taxi_speeds[SPX5_DSM_CAL_MAX_DEVS_PER_TAXI];
|
||
|
u32 dev_slots[SPX5_DSM_CAL_MAX_DEVS_PER_TAXI];
|
||
|
u32 new_slots[SPX5_DSM_CAL_LEN];
|
||
|
u32 temp_sched[SPX5_DSM_CAL_LEN];
|
||
|
u32 indices[SPX5_DSM_CAL_LEN];
|
||
|
u32 short_list[SPX5_DSM_CAL_LEN];
|
||
|
u32 long_list[SPX5_DSM_CAL_LEN];
|
||
|
};
|
||
|
|
||
|
static u32 sparx5_target_bandwidth(struct sparx5 *sparx5)
|
||
|
{
|
||
|
switch (sparx5->target_ct) {
|
||
|
case SPX5_TARGET_CT_7546:
|
||
|
case SPX5_TARGET_CT_7546TSN:
|
||
|
return 65000;
|
||
|
case SPX5_TARGET_CT_7549:
|
||
|
case SPX5_TARGET_CT_7549TSN:
|
||
|
return 91000;
|
||
|
case SPX5_TARGET_CT_7552:
|
||
|
case SPX5_TARGET_CT_7552TSN:
|
||
|
return 129000;
|
||
|
case SPX5_TARGET_CT_7556:
|
||
|
case SPX5_TARGET_CT_7556TSN:
|
||
|
return 161000;
|
||
|
case SPX5_TARGET_CT_7558:
|
||
|
case SPX5_TARGET_CT_7558TSN:
|
||
|
return 201000;
|
||
|
default:
|
||
|
return 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* This is used in calendar configuration */
|
||
|
enum sparx5_cal_bw {
|
||
|
SPX5_CAL_SPEED_NONE = 0,
|
||
|
SPX5_CAL_SPEED_1G = 1,
|
||
|
SPX5_CAL_SPEED_2G5 = 2,
|
||
|
SPX5_CAL_SPEED_5G = 3,
|
||
|
SPX5_CAL_SPEED_10G = 4,
|
||
|
SPX5_CAL_SPEED_25G = 5,
|
||
|
SPX5_CAL_SPEED_0G5 = 6,
|
||
|
SPX5_CAL_SPEED_12G5 = 7
|
||
|
};
|
||
|
|
||
|
static u32 sparx5_clk_to_bandwidth(enum sparx5_core_clockfreq cclock)
|
||
|
{
|
||
|
switch (cclock) {
|
||
|
case SPX5_CORE_CLOCK_250MHZ: return 83000; /* 250000 / 3 */
|
||
|
case SPX5_CORE_CLOCK_500MHZ: return 166000; /* 500000 / 3 */
|
||
|
case SPX5_CORE_CLOCK_625MHZ: return 208000; /* 625000 / 3 */
|
||
|
default: return 0;
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static u32 sparx5_cal_speed_to_value(enum sparx5_cal_bw speed)
|
||
|
{
|
||
|
switch (speed) {
|
||
|
case SPX5_CAL_SPEED_1G: return 1000;
|
||
|
case SPX5_CAL_SPEED_2G5: return 2500;
|
||
|
case SPX5_CAL_SPEED_5G: return 5000;
|
||
|
case SPX5_CAL_SPEED_10G: return 10000;
|
||
|
case SPX5_CAL_SPEED_25G: return 25000;
|
||
|
case SPX5_CAL_SPEED_0G5: return 500;
|
||
|
case SPX5_CAL_SPEED_12G5: return 12500;
|
||
|
default: return 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static u32 sparx5_bandwidth_to_calendar(u32 bw)
|
||
|
{
|
||
|
switch (bw) {
|
||
|
case SPEED_10: return SPX5_CAL_SPEED_0G5;
|
||
|
case SPEED_100: return SPX5_CAL_SPEED_0G5;
|
||
|
case SPEED_1000: return SPX5_CAL_SPEED_1G;
|
||
|
case SPEED_2500: return SPX5_CAL_SPEED_2G5;
|
||
|
case SPEED_5000: return SPX5_CAL_SPEED_5G;
|
||
|
case SPEED_10000: return SPX5_CAL_SPEED_10G;
|
||
|
case SPEED_12500: return SPX5_CAL_SPEED_12G5;
|
||
|
case SPEED_25000: return SPX5_CAL_SPEED_25G;
|
||
|
case SPEED_UNKNOWN: return SPX5_CAL_SPEED_1G;
|
||
|
default: return SPX5_CAL_SPEED_NONE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static enum sparx5_cal_bw sparx5_get_port_cal_speed(struct sparx5 *sparx5,
|
||
|
u32 portno)
|
||
|
{
|
||
|
struct sparx5_port *port;
|
||
|
|
||
|
if (portno >= SPX5_PORTS) {
|
||
|
/* Internal ports */
|
||
|
if (portno == SPX5_PORT_CPU_0 || portno == SPX5_PORT_CPU_1) {
|
||
|
/* Equals 1.25G */
|
||
|
return SPX5_CAL_SPEED_2G5;
|
||
|
} else if (portno == SPX5_PORT_VD0) {
|
||
|
/* IPMC only idle BW */
|
||
|
return SPX5_CAL_SPEED_NONE;
|
||
|
} else if (portno == SPX5_PORT_VD1) {
|
||
|
/* OAM only idle BW */
|
||
|
return SPX5_CAL_SPEED_NONE;
|
||
|
} else if (portno == SPX5_PORT_VD2) {
|
||
|
/* IPinIP gets only idle BW */
|
||
|
return SPX5_CAL_SPEED_NONE;
|
||
|
}
|
||
|
/* not in port map */
|
||
|
return SPX5_CAL_SPEED_NONE;
|
||
|
}
|
||
|
/* Front ports - may be used */
|
||
|
port = sparx5->ports[portno];
|
||
|
if (!port)
|
||
|
return SPX5_CAL_SPEED_NONE;
|
||
|
return sparx5_bandwidth_to_calendar(port->conf.bandwidth);
|
||
|
}
|
||
|
|
||
|
/* Auto configure the QSYS calendar based on port configuration */
|
||
|
int sparx5_config_auto_calendar(struct sparx5 *sparx5)
|
||
|
{
|
||
|
u32 cal[7], value, idx, portno;
|
||
|
u32 max_core_bw;
|
||
|
u32 total_bw = 0, used_port_bw = 0;
|
||
|
int err = 0;
|
||
|
enum sparx5_cal_bw spd;
|
||
|
|
||
|
memset(cal, 0, sizeof(cal));
|
||
|
|
||
|
max_core_bw = sparx5_clk_to_bandwidth(sparx5->coreclock);
|
||
|
if (max_core_bw == 0) {
|
||
|
dev_err(sparx5->dev, "Core clock not supported");
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
/* Setup the calendar with the bandwidth to each port */
|
||
|
for (portno = 0; portno < SPX5_PORTS_ALL; portno++) {
|
||
|
u64 reg, offset, this_bw;
|
||
|
|
||
|
spd = sparx5_get_port_cal_speed(sparx5, portno);
|
||
|
if (spd == SPX5_CAL_SPEED_NONE)
|
||
|
continue;
|
||
|
|
||
|
this_bw = sparx5_cal_speed_to_value(spd);
|
||
|
if (portno < SPX5_PORTS)
|
||
|
used_port_bw += this_bw;
|
||
|
else
|
||
|
/* Internal ports are granted half the value */
|
||
|
this_bw = this_bw / 2;
|
||
|
total_bw += this_bw;
|
||
|
reg = portno;
|
||
|
offset = do_div(reg, SPX5_PORTS_PER_CALREG);
|
||
|
cal[reg] |= spd << (offset * SPX5_CALBITS_PER_PORT);
|
||
|
}
|
||
|
|
||
|
if (used_port_bw > sparx5_target_bandwidth(sparx5)) {
|
||
|
dev_err(sparx5->dev,
|
||
|
"Port BW %u above target BW %u\n",
|
||
|
used_port_bw, sparx5_target_bandwidth(sparx5));
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
if (total_bw > max_core_bw) {
|
||
|
dev_err(sparx5->dev,
|
||
|
"Total BW %u above switch core BW %u\n",
|
||
|
total_bw, max_core_bw);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
/* Halt the calendar while changing it */
|
||
|
spx5_rmw(QSYS_CAL_CTRL_CAL_MODE_SET(10),
|
||
|
QSYS_CAL_CTRL_CAL_MODE,
|
||
|
sparx5, QSYS_CAL_CTRL);
|
||
|
|
||
|
/* Assign port bandwidth to auto calendar */
|
||
|
for (idx = 0; idx < ARRAY_SIZE(cal); idx++)
|
||
|
spx5_wr(cal[idx], sparx5, QSYS_CAL_AUTO(idx));
|
||
|
|
||
|
/* Increase grant rate of all ports to account for
|
||
|
* core clock ppm deviations
|
||
|
*/
|
||
|
spx5_rmw(QSYS_CAL_CTRL_CAL_AUTO_GRANT_RATE_SET(671), /* 672->671 */
|
||
|
QSYS_CAL_CTRL_CAL_AUTO_GRANT_RATE,
|
||
|
sparx5,
|
||
|
QSYS_CAL_CTRL);
|
||
|
|
||
|
/* Grant idle usage to VD 0-2 */
|
||
|
for (idx = 2; idx < 5; idx++)
|
||
|
spx5_wr(HSCH_OUTB_SHARE_ENA_OUTB_SHARE_ENA_SET(12),
|
||
|
sparx5,
|
||
|
HSCH_OUTB_SHARE_ENA(idx));
|
||
|
|
||
|
/* Enable Auto mode */
|
||
|
spx5_rmw(QSYS_CAL_CTRL_CAL_MODE_SET(8),
|
||
|
QSYS_CAL_CTRL_CAL_MODE,
|
||
|
sparx5, QSYS_CAL_CTRL);
|
||
|
|
||
|
/* Verify successful calendar config */
|
||
|
value = spx5_rd(sparx5, QSYS_CAL_CTRL);
|
||
|
if (QSYS_CAL_CTRL_CAL_AUTO_ERROR_GET(value)) {
|
||
|
dev_err(sparx5->dev, "QSYS calendar error\n");
|
||
|
err = -EINVAL;
|
||
|
}
|
||
|
return err;
|
||
|
}
|
||
|
|
||
|
static u32 sparx5_dsm_exb_gcd(u32 a, u32 b)
|
||
|
{
|
||
|
if (b == 0)
|
||
|
return a;
|
||
|
return sparx5_dsm_exb_gcd(b, a % b);
|
||
|
}
|
||
|
|
||
|
static u32 sparx5_dsm_cal_len(u32 *cal)
|
||
|
{
|
||
|
u32 idx = 0, len = 0;
|
||
|
|
||
|
while (idx < SPX5_DSM_CAL_LEN) {
|
||
|
if (cal[idx] != SPX5_DSM_CAL_EMPTY)
|
||
|
len++;
|
||
|
idx++;
|
||
|
}
|
||
|
return len;
|
||
|
}
|
||
|
|
||
|
static u32 sparx5_dsm_cp_cal(u32 *sched)
|
||
|
{
|
||
|
u32 idx = 0, tmp;
|
||
|
|
||
|
while (idx < SPX5_DSM_CAL_LEN) {
|
||
|
if (sched[idx] != SPX5_DSM_CAL_EMPTY) {
|
||
|
tmp = sched[idx];
|
||
|
sched[idx] = SPX5_DSM_CAL_EMPTY;
|
||
|
return tmp;
|
||
|
}
|
||
|
idx++;
|
||
|
}
|
||
|
return SPX5_DSM_CAL_EMPTY;
|
||
|
}
|
||
|
|
||
|
static int sparx5_dsm_calendar_calc(struct sparx5 *sparx5, u32 taxi,
|
||
|
struct sparx5_calendar_data *data)
|
||
|
{
|
||
|
bool slow_mode;
|
||
|
u32 gcd, idx, sum, min, factor;
|
||
|
u32 num_of_slots, slot_spd, empty_slots;
|
||
|
u32 taxi_bw, clk_period_ps;
|
||
|
|
||
|
clk_period_ps = sparx5_clk_period(sparx5->coreclock);
|
||
|
taxi_bw = 128 * 1000000 / clk_period_ps;
|
||
|
slow_mode = !!(clk_period_ps > 2000);
|
||
|
memcpy(data->taxi_ports, &sparx5_taxi_ports[taxi],
|
||
|
sizeof(data->taxi_ports));
|
||
|
|
||
|
for (idx = 0; idx < SPX5_DSM_CAL_LEN; idx++) {
|
||
|
data->new_slots[idx] = SPX5_DSM_CAL_EMPTY;
|
||
|
data->schedule[idx] = SPX5_DSM_CAL_EMPTY;
|
||
|
data->temp_sched[idx] = SPX5_DSM_CAL_EMPTY;
|
||
|
}
|
||
|
/* Default empty calendar */
|
||
|
data->schedule[0] = SPX5_DSM_CAL_MAX_DEVS_PER_TAXI;
|
||
|
|
||
|
/* Map ports to taxi positions */
|
||
|
for (idx = 0; idx < SPX5_DSM_CAL_MAX_DEVS_PER_TAXI; idx++) {
|
||
|
u32 portno = data->taxi_ports[idx];
|
||
|
|
||
|
if (portno < SPX5_TAXI_PORT_MAX) {
|
||
|
data->taxi_speeds[idx] = sparx5_cal_speed_to_value
|
||
|
(sparx5_get_port_cal_speed(sparx5, portno));
|
||
|
} else {
|
||
|
data->taxi_speeds[idx] = 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
sum = 0;
|
||
|
min = 25000;
|
||
|
for (idx = 0; idx < ARRAY_SIZE(data->taxi_speeds); idx++) {
|
||
|
u32 jdx;
|
||
|
|
||
|
sum += data->taxi_speeds[idx];
|
||
|
if (data->taxi_speeds[idx] && data->taxi_speeds[idx] < min)
|
||
|
min = data->taxi_speeds[idx];
|
||
|
gcd = min;
|
||
|
for (jdx = 0; jdx < ARRAY_SIZE(data->taxi_speeds); jdx++)
|
||
|
gcd = sparx5_dsm_exb_gcd(gcd, data->taxi_speeds[jdx]);
|
||
|
}
|
||
|
if (sum == 0) /* Empty calendar */
|
||
|
return 0;
|
||
|
/* Make room for overhead traffic */
|
||
|
factor = 100 * 100 * 1000 / (100 * 100 - SPX5_DSM_CAL_BW_LOSS);
|
||
|
|
||
|
if (sum * factor > (taxi_bw * 1000)) {
|
||
|
dev_err(sparx5->dev,
|
||
|
"Taxi %u, Requested BW %u above available BW %u\n",
|
||
|
taxi, sum, taxi_bw);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
for (idx = 0; idx < 4; idx++) {
|
||
|
u32 raw_spd;
|
||
|
|
||
|
if (idx == 0)
|
||
|
raw_spd = gcd / 5;
|
||
|
else if (idx == 1)
|
||
|
raw_spd = gcd / 2;
|
||
|
else if (idx == 2)
|
||
|
raw_spd = gcd;
|
||
|
else
|
||
|
raw_spd = min;
|
||
|
slot_spd = raw_spd * factor / 1000;
|
||
|
num_of_slots = taxi_bw / slot_spd;
|
||
|
if (num_of_slots <= 64)
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
num_of_slots = num_of_slots > 64 ? 64 : num_of_slots;
|
||
|
slot_spd = taxi_bw / num_of_slots;
|
||
|
|
||
|
sum = 0;
|
||
|
for (idx = 0; idx < ARRAY_SIZE(data->taxi_speeds); idx++) {
|
||
|
u32 spd = data->taxi_speeds[idx];
|
||
|
u32 adjusted_speed = data->taxi_speeds[idx] * factor / 1000;
|
||
|
|
||
|
if (adjusted_speed > 0) {
|
||
|
data->avg_dist[idx] = (128 * 1000000 * 10) /
|
||
|
(adjusted_speed * clk_period_ps);
|
||
|
} else {
|
||
|
data->avg_dist[idx] = -1;
|
||
|
}
|
||
|
data->dev_slots[idx] = ((spd * factor / slot_spd) + 999) / 1000;
|
||
|
if (spd != 25000 && (spd != 10000 || !slow_mode)) {
|
||
|
if (num_of_slots < (5 * data->dev_slots[idx])) {
|
||
|
dev_err(sparx5->dev,
|
||
|
"Taxi %u, speed %u, Low slot sep.\n",
|
||
|
taxi, spd);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
}
|
||
|
sum += data->dev_slots[idx];
|
||
|
if (sum > num_of_slots) {
|
||
|
dev_err(sparx5->dev,
|
||
|
"Taxi %u with overhead factor %u\n",
|
||
|
taxi, factor);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
empty_slots = num_of_slots - sum;
|
||
|
|
||
|
for (idx = 0; idx < empty_slots; idx++)
|
||
|
data->schedule[idx] = SPX5_DSM_CAL_MAX_DEVS_PER_TAXI;
|
||
|
|
||
|
for (idx = 1; idx < num_of_slots; idx++) {
|
||
|
u32 indices_len = 0;
|
||
|
u32 slot, jdx, kdx, ts;
|
||
|
s32 cnt;
|
||
|
u32 num_of_old_slots, num_of_new_slots, tgt_score;
|
||
|
|
||
|
for (slot = 0; slot < ARRAY_SIZE(data->dev_slots); slot++) {
|
||
|
if (data->dev_slots[slot] == idx) {
|
||
|
data->indices[indices_len] = slot;
|
||
|
indices_len++;
|
||
|
}
|
||
|
}
|
||
|
if (indices_len == 0)
|
||
|
continue;
|
||
|
kdx = 0;
|
||
|
for (slot = 0; slot < idx; slot++) {
|
||
|
for (jdx = 0; jdx < indices_len; jdx++, kdx++)
|
||
|
data->new_slots[kdx] = data->indices[jdx];
|
||
|
}
|
||
|
|
||
|
for (slot = 0; slot < SPX5_DSM_CAL_LEN; slot++) {
|
||
|
if (data->schedule[slot] == SPX5_DSM_CAL_EMPTY)
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
num_of_old_slots = slot;
|
||
|
num_of_new_slots = kdx;
|
||
|
cnt = 0;
|
||
|
ts = 0;
|
||
|
|
||
|
if (num_of_new_slots > num_of_old_slots) {
|
||
|
memcpy(data->short_list, data->schedule,
|
||
|
sizeof(data->short_list));
|
||
|
memcpy(data->long_list, data->new_slots,
|
||
|
sizeof(data->long_list));
|
||
|
tgt_score = 100000 * num_of_old_slots /
|
||
|
num_of_new_slots;
|
||
|
} else {
|
||
|
memcpy(data->short_list, data->new_slots,
|
||
|
sizeof(data->short_list));
|
||
|
memcpy(data->long_list, data->schedule,
|
||
|
sizeof(data->long_list));
|
||
|
tgt_score = 100000 * num_of_new_slots /
|
||
|
num_of_old_slots;
|
||
|
}
|
||
|
|
||
|
while (sparx5_dsm_cal_len(data->short_list) > 0 ||
|
||
|
sparx5_dsm_cal_len(data->long_list) > 0) {
|
||
|
u32 act = 0;
|
||
|
|
||
|
if (sparx5_dsm_cal_len(data->short_list) > 0) {
|
||
|
data->temp_sched[ts] =
|
||
|
sparx5_dsm_cp_cal(data->short_list);
|
||
|
ts++;
|
||
|
cnt += 100000;
|
||
|
act = 1;
|
||
|
}
|
||
|
while (sparx5_dsm_cal_len(data->long_list) > 0 &&
|
||
|
cnt > 0) {
|
||
|
data->temp_sched[ts] =
|
||
|
sparx5_dsm_cp_cal(data->long_list);
|
||
|
ts++;
|
||
|
cnt -= tgt_score;
|
||
|
act = 1;
|
||
|
}
|
||
|
if (act == 0) {
|
||
|
dev_err(sparx5->dev,
|
||
|
"Error in DSM calendar calculation\n");
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for (slot = 0; slot < SPX5_DSM_CAL_LEN; slot++) {
|
||
|
if (data->temp_sched[slot] == SPX5_DSM_CAL_EMPTY)
|
||
|
break;
|
||
|
}
|
||
|
for (slot = 0; slot < SPX5_DSM_CAL_LEN; slot++) {
|
||
|
data->schedule[slot] = data->temp_sched[slot];
|
||
|
data->temp_sched[slot] = SPX5_DSM_CAL_EMPTY;
|
||
|
data->new_slots[slot] = SPX5_DSM_CAL_EMPTY;
|
||
|
}
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int sparx5_dsm_calendar_check(struct sparx5 *sparx5,
|
||
|
struct sparx5_calendar_data *data)
|
||
|
{
|
||
|
u32 num_of_slots, idx, port;
|
||
|
int cnt, max_dist;
|
||
|
u32 slot_indices[SPX5_DSM_CAL_LEN], distances[SPX5_DSM_CAL_LEN];
|
||
|
u32 cal_length = sparx5_dsm_cal_len(data->schedule);
|
||
|
|
||
|
for (port = 0; port < SPX5_DSM_CAL_MAX_DEVS_PER_TAXI; port++) {
|
||
|
num_of_slots = 0;
|
||
|
max_dist = data->avg_dist[port];
|
||
|
for (idx = 0; idx < SPX5_DSM_CAL_LEN; idx++) {
|
||
|
slot_indices[idx] = SPX5_DSM_CAL_EMPTY;
|
||
|
distances[idx] = SPX5_DSM_CAL_EMPTY;
|
||
|
}
|
||
|
|
||
|
for (idx = 0; idx < cal_length; idx++) {
|
||
|
if (data->schedule[idx] == port) {
|
||
|
slot_indices[num_of_slots] = idx;
|
||
|
num_of_slots++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
slot_indices[num_of_slots] = slot_indices[0] + cal_length;
|
||
|
|
||
|
for (idx = 0; idx < num_of_slots; idx++) {
|
||
|
distances[idx] = (slot_indices[idx + 1] -
|
||
|
slot_indices[idx]) * 10;
|
||
|
}
|
||
|
|
||
|
for (idx = 0; idx < num_of_slots; idx++) {
|
||
|
u32 jdx, kdx;
|
||
|
|
||
|
cnt = distances[idx] - max_dist;
|
||
|
if (cnt < 0)
|
||
|
cnt = -cnt;
|
||
|
kdx = 0;
|
||
|
for (jdx = (idx + 1) % num_of_slots;
|
||
|
jdx != idx;
|
||
|
jdx = (jdx + 1) % num_of_slots, kdx++) {
|
||
|
cnt = cnt + distances[jdx] - max_dist;
|
||
|
if (cnt < 0)
|
||
|
cnt = -cnt;
|
||
|
if (cnt > max_dist)
|
||
|
goto check_err;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return 0;
|
||
|
check_err:
|
||
|
dev_err(sparx5->dev,
|
||
|
"Port %u: distance %u above limit %d\n",
|
||
|
port, cnt, max_dist);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
static int sparx5_dsm_calendar_update(struct sparx5 *sparx5, u32 taxi,
|
||
|
struct sparx5_calendar_data *data)
|
||
|
{
|
||
|
u32 idx;
|
||
|
u32 cal_len = sparx5_dsm_cal_len(data->schedule), len;
|
||
|
|
||
|
spx5_wr(DSM_TAXI_CAL_CFG_CAL_PGM_ENA_SET(1),
|
||
|
sparx5,
|
||
|
DSM_TAXI_CAL_CFG(taxi));
|
||
|
for (idx = 0; idx < cal_len; idx++) {
|
||
|
spx5_rmw(DSM_TAXI_CAL_CFG_CAL_IDX_SET(idx),
|
||
|
DSM_TAXI_CAL_CFG_CAL_IDX,
|
||
|
sparx5,
|
||
|
DSM_TAXI_CAL_CFG(taxi));
|
||
|
spx5_rmw(DSM_TAXI_CAL_CFG_CAL_PGM_VAL_SET(data->schedule[idx]),
|
||
|
DSM_TAXI_CAL_CFG_CAL_PGM_VAL,
|
||
|
sparx5,
|
||
|
DSM_TAXI_CAL_CFG(taxi));
|
||
|
}
|
||
|
spx5_wr(DSM_TAXI_CAL_CFG_CAL_PGM_ENA_SET(0),
|
||
|
sparx5,
|
||
|
DSM_TAXI_CAL_CFG(taxi));
|
||
|
len = DSM_TAXI_CAL_CFG_CAL_CUR_LEN_GET(spx5_rd(sparx5,
|
||
|
DSM_TAXI_CAL_CFG(taxi)));
|
||
|
if (len != cal_len - 1)
|
||
|
goto update_err;
|
||
|
return 0;
|
||
|
update_err:
|
||
|
dev_err(sparx5->dev, "Incorrect calendar length: %u\n", len);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
/* Configure the DSM calendar based on port configuration */
|
||
|
int sparx5_config_dsm_calendar(struct sparx5 *sparx5)
|
||
|
{
|
||
|
int taxi;
|
||
|
struct sparx5_calendar_data *data;
|
||
|
int err = 0;
|
||
|
|
||
|
data = kzalloc(sizeof(*data), GFP_KERNEL);
|
||
|
if (!data)
|
||
|
return -ENOMEM;
|
||
|
|
||
|
for (taxi = 0; taxi < SPX5_DSM_CAL_TAXIS; ++taxi) {
|
||
|
err = sparx5_dsm_calendar_calc(sparx5, taxi, data);
|
||
|
if (err) {
|
||
|
dev_err(sparx5->dev, "DSM calendar calculation failed\n");
|
||
|
goto cal_out;
|
||
|
}
|
||
|
err = sparx5_dsm_calendar_check(sparx5, data);
|
||
|
if (err) {
|
||
|
dev_err(sparx5->dev, "DSM calendar check failed\n");
|
||
|
goto cal_out;
|
||
|
}
|
||
|
err = sparx5_dsm_calendar_update(sparx5, taxi, data);
|
||
|
if (err) {
|
||
|
dev_err(sparx5->dev, "DSM calendar update failed\n");
|
||
|
goto cal_out;
|
||
|
}
|
||
|
}
|
||
|
cal_out:
|
||
|
kfree(data);
|
||
|
return err;
|
||
|
}
|