162306a36Sopenharmony_ci// SPDX-License-Identifier: (GPL-2.0 OR MIT)
262306a36Sopenharmony_ci/* Microsemi Ocelot Switch driver
362306a36Sopenharmony_ci *
462306a36Sopenharmony_ci * Copyright (c) 2019 Microsemi Corporation
562306a36Sopenharmony_ci */
662306a36Sopenharmony_ci
762306a36Sopenharmony_ci#include <soc/mscc/ocelot.h>
862306a36Sopenharmony_ci#include "ocelot_police.h"
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ci/* Types for ANA:POL[0-192]:POL_MODE_CFG.FRM_MODE */
1162306a36Sopenharmony_ci#define POL_MODE_LINERATE   0 /* Incl IPG. Unit: 33 1/3 kbps, 4096 bytes */
1262306a36Sopenharmony_ci#define POL_MODE_DATARATE   1 /* Excl IPG. Unit: 33 1/3 kbps, 4096 bytes  */
1362306a36Sopenharmony_ci#define POL_MODE_FRMRATE_HI 2 /* Unit: 33 1/3 fps, 32.8 frames */
1462306a36Sopenharmony_ci#define POL_MODE_FRMRATE_LO 3 /* Unit: 1/3 fps, 0.3 frames */
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_ci/* Policer indexes */
1762306a36Sopenharmony_ci#define POL_IX_PORT    0    /* 0-11    : Port policers */
1862306a36Sopenharmony_ci#define POL_IX_QUEUE   32   /* 32-127  : Queue policers  */
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_ci/* Default policer order */
2162306a36Sopenharmony_ci#define POL_ORDER 0x1d3 /* Ocelot policer order: Serial (QoS -> Port -> VCAP) */
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_ciint qos_policer_conf_set(struct ocelot *ocelot, u32 pol_ix,
2462306a36Sopenharmony_ci			 struct qos_policer_conf *conf)
2562306a36Sopenharmony_ci{
2662306a36Sopenharmony_ci	u32 cf = 0, cir_ena = 0, frm_mode = POL_MODE_LINERATE;
2762306a36Sopenharmony_ci	u32 cir = 0, cbs = 0, pir = 0, pbs = 0;
2862306a36Sopenharmony_ci	bool cir_discard = 0, pir_discard = 0;
2962306a36Sopenharmony_ci	u32 pbs_max = 0, cbs_max = 0;
3062306a36Sopenharmony_ci	u8 ipg = 20;
3162306a36Sopenharmony_ci	u32 value;
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_ci	pir = conf->pir;
3462306a36Sopenharmony_ci	pbs = conf->pbs;
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_ci	switch (conf->mode) {
3762306a36Sopenharmony_ci	case MSCC_QOS_RATE_MODE_LINE:
3862306a36Sopenharmony_ci	case MSCC_QOS_RATE_MODE_DATA:
3962306a36Sopenharmony_ci		if (conf->mode == MSCC_QOS_RATE_MODE_LINE) {
4062306a36Sopenharmony_ci			frm_mode = POL_MODE_LINERATE;
4162306a36Sopenharmony_ci			ipg = min_t(u8, GENMASK(4, 0), conf->ipg);
4262306a36Sopenharmony_ci		} else {
4362306a36Sopenharmony_ci			frm_mode = POL_MODE_DATARATE;
4462306a36Sopenharmony_ci		}
4562306a36Sopenharmony_ci		if (conf->dlb) {
4662306a36Sopenharmony_ci			cir_ena = 1;
4762306a36Sopenharmony_ci			cir = conf->cir;
4862306a36Sopenharmony_ci			cbs = conf->cbs;
4962306a36Sopenharmony_ci			if (cir == 0 && cbs == 0) {
5062306a36Sopenharmony_ci				/* Discard cir frames */
5162306a36Sopenharmony_ci				cir_discard = 1;
5262306a36Sopenharmony_ci			} else {
5362306a36Sopenharmony_ci				cir = DIV_ROUND_UP(cir, 100);
5462306a36Sopenharmony_ci				cir *= 3; /* 33 1/3 kbps */
5562306a36Sopenharmony_ci				cbs = DIV_ROUND_UP(cbs, 4096);
5662306a36Sopenharmony_ci				cbs = (cbs ? cbs : 1); /* No zero burst size */
5762306a36Sopenharmony_ci				cbs_max = 60; /* Limit burst size */
5862306a36Sopenharmony_ci				cf = conf->cf;
5962306a36Sopenharmony_ci				if (cf)
6062306a36Sopenharmony_ci					pir += conf->cir;
6162306a36Sopenharmony_ci			}
6262306a36Sopenharmony_ci		}
6362306a36Sopenharmony_ci		if (pir == 0 && pbs == 0) {
6462306a36Sopenharmony_ci			/* Discard PIR frames */
6562306a36Sopenharmony_ci			pir_discard = 1;
6662306a36Sopenharmony_ci		} else {
6762306a36Sopenharmony_ci			pir = DIV_ROUND_UP(pir, 100);
6862306a36Sopenharmony_ci			pir *= 3;  /* 33 1/3 kbps */
6962306a36Sopenharmony_ci			pbs = DIV_ROUND_UP(pbs, 4096);
7062306a36Sopenharmony_ci			pbs = (pbs ? pbs : 1); /* No zero burst size */
7162306a36Sopenharmony_ci			pbs_max = 60; /* Limit burst size */
7262306a36Sopenharmony_ci		}
7362306a36Sopenharmony_ci		break;
7462306a36Sopenharmony_ci	case MSCC_QOS_RATE_MODE_FRAME:
7562306a36Sopenharmony_ci		if (pir >= 100) {
7662306a36Sopenharmony_ci			frm_mode = POL_MODE_FRMRATE_HI;
7762306a36Sopenharmony_ci			pir = DIV_ROUND_UP(pir, 100);
7862306a36Sopenharmony_ci			pir *= 3;  /* 33 1/3 fps */
7962306a36Sopenharmony_ci			pbs = (pbs * 10) / 328; /* 32.8 frames */
8062306a36Sopenharmony_ci			pbs = (pbs ? pbs : 1); /* No zero burst size */
8162306a36Sopenharmony_ci			pbs_max = GENMASK(6, 0); /* Limit burst size */
8262306a36Sopenharmony_ci		} else {
8362306a36Sopenharmony_ci			frm_mode = POL_MODE_FRMRATE_LO;
8462306a36Sopenharmony_ci			if (pir == 0 && pbs == 0) {
8562306a36Sopenharmony_ci				/* Discard all frames */
8662306a36Sopenharmony_ci				pir_discard = 1;
8762306a36Sopenharmony_ci				cir_discard = 1;
8862306a36Sopenharmony_ci			} else {
8962306a36Sopenharmony_ci				pir *= 3; /* 1/3 fps */
9062306a36Sopenharmony_ci				pbs = (pbs * 10) / 3; /* 0.3 frames */
9162306a36Sopenharmony_ci				pbs = (pbs ? pbs : 1); /* No zero burst size */
9262306a36Sopenharmony_ci				pbs_max = 61; /* Limit burst size */
9362306a36Sopenharmony_ci			}
9462306a36Sopenharmony_ci		}
9562306a36Sopenharmony_ci		break;
9662306a36Sopenharmony_ci	default: /* MSCC_QOS_RATE_MODE_DISABLED */
9762306a36Sopenharmony_ci		/* Disable policer using maximum rate and zero burst */
9862306a36Sopenharmony_ci		pir = GENMASK(15, 0);
9962306a36Sopenharmony_ci		pbs = 0;
10062306a36Sopenharmony_ci		break;
10162306a36Sopenharmony_ci	}
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_ci	/* Check limits */
10462306a36Sopenharmony_ci	if (pir > GENMASK(15, 0)) {
10562306a36Sopenharmony_ci		dev_err(ocelot->dev,
10662306a36Sopenharmony_ci			"Invalid pir for policer %u: %u (max %lu)\n",
10762306a36Sopenharmony_ci			pol_ix, pir, GENMASK(15, 0));
10862306a36Sopenharmony_ci		return -EINVAL;
10962306a36Sopenharmony_ci	}
11062306a36Sopenharmony_ci
11162306a36Sopenharmony_ci	if (cir > GENMASK(15, 0)) {
11262306a36Sopenharmony_ci		dev_err(ocelot->dev,
11362306a36Sopenharmony_ci			"Invalid cir for policer %u: %u (max %lu)\n",
11462306a36Sopenharmony_ci			pol_ix, cir, GENMASK(15, 0));
11562306a36Sopenharmony_ci		return -EINVAL;
11662306a36Sopenharmony_ci	}
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_ci	if (pbs > pbs_max) {
11962306a36Sopenharmony_ci		dev_err(ocelot->dev,
12062306a36Sopenharmony_ci			"Invalid pbs for policer %u: %u (max %u)\n",
12162306a36Sopenharmony_ci			pol_ix, pbs, pbs_max);
12262306a36Sopenharmony_ci		return -EINVAL;
12362306a36Sopenharmony_ci	}
12462306a36Sopenharmony_ci
12562306a36Sopenharmony_ci	if (cbs > cbs_max) {
12662306a36Sopenharmony_ci		dev_err(ocelot->dev,
12762306a36Sopenharmony_ci			"Invalid cbs for policer %u: %u (max %u)\n",
12862306a36Sopenharmony_ci			pol_ix, cbs, cbs_max);
12962306a36Sopenharmony_ci		return -EINVAL;
13062306a36Sopenharmony_ci	}
13162306a36Sopenharmony_ci
13262306a36Sopenharmony_ci	value = (ANA_POL_MODE_CFG_IPG_SIZE(ipg) |
13362306a36Sopenharmony_ci		 ANA_POL_MODE_CFG_FRM_MODE(frm_mode) |
13462306a36Sopenharmony_ci		 (cf ? ANA_POL_MODE_CFG_DLB_COUPLED : 0) |
13562306a36Sopenharmony_ci		 (cir_ena ? ANA_POL_MODE_CFG_CIR_ENA : 0) |
13662306a36Sopenharmony_ci		 ANA_POL_MODE_CFG_OVERSHOOT_ENA);
13762306a36Sopenharmony_ci
13862306a36Sopenharmony_ci	ocelot_write_gix(ocelot, value, ANA_POL_MODE_CFG, pol_ix);
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_ci	ocelot_write_gix(ocelot,
14162306a36Sopenharmony_ci			 ANA_POL_PIR_CFG_PIR_RATE(pir) |
14262306a36Sopenharmony_ci			 ANA_POL_PIR_CFG_PIR_BURST(pbs),
14362306a36Sopenharmony_ci			 ANA_POL_PIR_CFG, pol_ix);
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_ci	ocelot_write_gix(ocelot,
14662306a36Sopenharmony_ci			 (pir_discard ? GENMASK(22, 0) : 0),
14762306a36Sopenharmony_ci			 ANA_POL_PIR_STATE, pol_ix);
14862306a36Sopenharmony_ci
14962306a36Sopenharmony_ci	ocelot_write_gix(ocelot,
15062306a36Sopenharmony_ci			 ANA_POL_CIR_CFG_CIR_RATE(cir) |
15162306a36Sopenharmony_ci			 ANA_POL_CIR_CFG_CIR_BURST(cbs),
15262306a36Sopenharmony_ci			 ANA_POL_CIR_CFG, pol_ix);
15362306a36Sopenharmony_ci
15462306a36Sopenharmony_ci	ocelot_write_gix(ocelot,
15562306a36Sopenharmony_ci			 (cir_discard ? GENMASK(22, 0) : 0),
15662306a36Sopenharmony_ci			 ANA_POL_CIR_STATE, pol_ix);
15762306a36Sopenharmony_ci
15862306a36Sopenharmony_ci	return 0;
15962306a36Sopenharmony_ci}
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_ciint ocelot_policer_validate(const struct flow_action *action,
16262306a36Sopenharmony_ci			    const struct flow_action_entry *a,
16362306a36Sopenharmony_ci			    struct netlink_ext_ack *extack)
16462306a36Sopenharmony_ci{
16562306a36Sopenharmony_ci	if (a->police.exceed.act_id != FLOW_ACTION_DROP) {
16662306a36Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack,
16762306a36Sopenharmony_ci				   "Offload not supported when exceed action is not drop");
16862306a36Sopenharmony_ci		return -EOPNOTSUPP;
16962306a36Sopenharmony_ci	}
17062306a36Sopenharmony_ci
17162306a36Sopenharmony_ci	if (a->police.notexceed.act_id != FLOW_ACTION_PIPE &&
17262306a36Sopenharmony_ci	    a->police.notexceed.act_id != FLOW_ACTION_ACCEPT) {
17362306a36Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack,
17462306a36Sopenharmony_ci				   "Offload not supported when conform action is not pipe or ok");
17562306a36Sopenharmony_ci		return -EOPNOTSUPP;
17662306a36Sopenharmony_ci	}
17762306a36Sopenharmony_ci
17862306a36Sopenharmony_ci	if (a->police.notexceed.act_id == FLOW_ACTION_ACCEPT &&
17962306a36Sopenharmony_ci	    !flow_action_is_last_entry(action, a)) {
18062306a36Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack,
18162306a36Sopenharmony_ci				   "Offload not supported when conform action is ok, but police action is not last");
18262306a36Sopenharmony_ci		return -EOPNOTSUPP;
18362306a36Sopenharmony_ci	}
18462306a36Sopenharmony_ci
18562306a36Sopenharmony_ci	if (a->police.peakrate_bytes_ps ||
18662306a36Sopenharmony_ci	    a->police.avrate || a->police.overhead) {
18762306a36Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack,
18862306a36Sopenharmony_ci				   "Offload not supported when peakrate/avrate/overhead is configured");
18962306a36Sopenharmony_ci		return -EOPNOTSUPP;
19062306a36Sopenharmony_ci	}
19162306a36Sopenharmony_ci
19262306a36Sopenharmony_ci	if (a->police.rate_pkt_ps) {
19362306a36Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack,
19462306a36Sopenharmony_ci				   "Offload does not support packets per second");
19562306a36Sopenharmony_ci		return -EOPNOTSUPP;
19662306a36Sopenharmony_ci	}
19762306a36Sopenharmony_ci
19862306a36Sopenharmony_ci	return 0;
19962306a36Sopenharmony_ci}
20062306a36Sopenharmony_ciEXPORT_SYMBOL(ocelot_policer_validate);
20162306a36Sopenharmony_ci
20262306a36Sopenharmony_ciint ocelot_port_policer_add(struct ocelot *ocelot, int port,
20362306a36Sopenharmony_ci			    struct ocelot_policer *pol)
20462306a36Sopenharmony_ci{
20562306a36Sopenharmony_ci	struct qos_policer_conf pp = { 0 };
20662306a36Sopenharmony_ci	int err;
20762306a36Sopenharmony_ci
20862306a36Sopenharmony_ci	if (!pol)
20962306a36Sopenharmony_ci		return -EINVAL;
21062306a36Sopenharmony_ci
21162306a36Sopenharmony_ci	pp.mode = MSCC_QOS_RATE_MODE_DATA;
21262306a36Sopenharmony_ci	pp.pir = pol->rate;
21362306a36Sopenharmony_ci	pp.pbs = pol->burst;
21462306a36Sopenharmony_ci
21562306a36Sopenharmony_ci	dev_dbg(ocelot->dev, "%s: port %u pir %u kbps, pbs %u bytes\n",
21662306a36Sopenharmony_ci		__func__, port, pp.pir, pp.pbs);
21762306a36Sopenharmony_ci
21862306a36Sopenharmony_ci	err = qos_policer_conf_set(ocelot, POL_IX_PORT + port, &pp);
21962306a36Sopenharmony_ci	if (err)
22062306a36Sopenharmony_ci		return err;
22162306a36Sopenharmony_ci
22262306a36Sopenharmony_ci	ocelot_rmw_gix(ocelot,
22362306a36Sopenharmony_ci		       ANA_PORT_POL_CFG_PORT_POL_ENA |
22462306a36Sopenharmony_ci		       ANA_PORT_POL_CFG_POL_ORDER(POL_ORDER),
22562306a36Sopenharmony_ci		       ANA_PORT_POL_CFG_PORT_POL_ENA |
22662306a36Sopenharmony_ci		       ANA_PORT_POL_CFG_POL_ORDER_M,
22762306a36Sopenharmony_ci		       ANA_PORT_POL_CFG, port);
22862306a36Sopenharmony_ci
22962306a36Sopenharmony_ci	return 0;
23062306a36Sopenharmony_ci}
23162306a36Sopenharmony_ciEXPORT_SYMBOL(ocelot_port_policer_add);
23262306a36Sopenharmony_ci
23362306a36Sopenharmony_ciint ocelot_port_policer_del(struct ocelot *ocelot, int port)
23462306a36Sopenharmony_ci{
23562306a36Sopenharmony_ci	struct qos_policer_conf pp = { 0 };
23662306a36Sopenharmony_ci	int err;
23762306a36Sopenharmony_ci
23862306a36Sopenharmony_ci	dev_dbg(ocelot->dev, "%s: port %u\n", __func__, port);
23962306a36Sopenharmony_ci
24062306a36Sopenharmony_ci	pp.mode = MSCC_QOS_RATE_MODE_DISABLED;
24162306a36Sopenharmony_ci
24262306a36Sopenharmony_ci	err = qos_policer_conf_set(ocelot, POL_IX_PORT + port, &pp);
24362306a36Sopenharmony_ci	if (err)
24462306a36Sopenharmony_ci		return err;
24562306a36Sopenharmony_ci
24662306a36Sopenharmony_ci	ocelot_rmw_gix(ocelot,
24762306a36Sopenharmony_ci		       ANA_PORT_POL_CFG_POL_ORDER(POL_ORDER),
24862306a36Sopenharmony_ci		       ANA_PORT_POL_CFG_PORT_POL_ENA |
24962306a36Sopenharmony_ci		       ANA_PORT_POL_CFG_POL_ORDER_M,
25062306a36Sopenharmony_ci		       ANA_PORT_POL_CFG, port);
25162306a36Sopenharmony_ci
25262306a36Sopenharmony_ci	return 0;
25362306a36Sopenharmony_ci}
25462306a36Sopenharmony_ciEXPORT_SYMBOL(ocelot_port_policer_del);
255