Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,33 @@
config SPARX5_SWITCH
tristate "Sparx5 switch driver"
depends on NET_SWITCHDEV
depends on HAS_IOMEM
depends on OF
depends on ARCH_SPARX5 || ARCH_LAN969X || COMPILE_TEST
depends on PTP_1588_CLOCK_OPTIONAL
depends on BRIDGE || BRIDGE=n
select PHYLINK
select PHY_SPARX5_SERDES
select RESET_CONTROLLER
select VCAP
select FDMA
help
This driver supports the Sparx5 network switch device.
config SPARX5_DCB
bool "Data Center Bridging (DCB) support"
depends on SPARX5_SWITCH && DCB
default y
help
Say Y here if you want to use Data Center Bridging (DCB) in the
driver. This can be used to assign priority to traffic, based on
DSCP and PCP.
If unsure, set to Y.
config LAN969X_SWITCH
bool "Lan969x switch driver"
depends on SPARX5_SWITCH
select PAGE_POOL
help
This driver supports the lan969x family of network switch devices.
@@ -0,0 +1,29 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# Makefile for the Microchip Sparx5 network device drivers.
#
obj-$(CONFIG_SPARX5_SWITCH) += sparx5-switch.o
sparx5-switch-y := sparx5_main.o sparx5_packet.o \
sparx5_netdev.o sparx5_phylink.o sparx5_port.o sparx5_mactable.o sparx5_vlan.o \
sparx5_switchdev.o sparx5_calendar.o sparx5_ethtool.o sparx5_fdma.o \
sparx5_ptp.o sparx5_pgid.o sparx5_tc.o sparx5_qos.o \
sparx5_vcap_impl.o sparx5_vcap_ag_api.o sparx5_tc_flower.o \
sparx5_tc_matchall.o sparx5_pool.o sparx5_sdlb.o sparx5_police.o \
sparx5_psfp.o sparx5_mirror.o sparx5_regs.o
sparx5-switch-$(CONFIG_SPARX5_DCB) += sparx5_dcb.o
sparx5-switch-$(CONFIG_DEBUG_FS) += sparx5_vcap_debugfs.o
sparx5-switch-$(CONFIG_LAN969X_SWITCH) += lan969x/lan969x_regs.o \
lan969x/lan969x.o \
lan969x/lan969x_calendar.o \
lan969x/lan969x_vcap_ag_api.o \
lan969x/lan969x_vcap_impl.o \
lan969x/lan969x_rgmii.o \
lan969x/lan969x_fdma.o
# Provide include files
ccflags-y += -I$(srctree)/drivers/net/ethernet/microchip/vcap
ccflags-y += -I$(srctree)/drivers/net/ethernet/microchip/fdma
@@ -0,0 +1,357 @@
// SPDX-License-Identifier: GPL-2.0+
/* Microchip lan969x Switch driver
*
* Copyright (c) 2024 Microchip Technology Inc. and its subsidiaries.
*/
#include "lan969x.h"
#define LAN969X_SDLB_GRP_CNT 5
#define LAN969X_HSCH_LEAK_GRP_CNT 4
static const struct sparx5_main_io_resource lan969x_main_iomap[] = {
{ TARGET_CPU, 0xc0000, 0 }, /* 0xe00c0000 */
{ TARGET_FDMA, 0xc0400, 0 }, /* 0xe00c0400 */
{ TARGET_GCB, 0x2010000, 1 }, /* 0xe2010000 */
{ TARGET_QS, 0x2030000, 1 }, /* 0xe2030000 */
{ TARGET_PTP, 0x2040000, 1 }, /* 0xe2040000 */
{ TARGET_ANA_ACL, 0x2050000, 1 }, /* 0xe2050000 */
{ TARGET_LRN, 0x2060000, 1 }, /* 0xe2060000 */
{ TARGET_VCAP_SUPER, 0x2080000, 1 }, /* 0xe2080000 */
{ TARGET_QSYS, 0x20a0000, 1 }, /* 0xe20a0000 */
{ TARGET_QFWD, 0x20b0000, 1 }, /* 0xe20b0000 */
{ TARGET_XQS, 0x20c0000, 1 }, /* 0xe20c0000 */
{ TARGET_VCAP_ES2, 0x20d0000, 1 }, /* 0xe20d0000 */
{ TARGET_VCAP_ES0, 0x20e0000, 1 }, /* 0xe20e0000 */
{ TARGET_ANA_AC_POL, 0x2200000, 1 }, /* 0xe2200000 */
{ TARGET_QRES, 0x2280000, 1 }, /* 0xe2280000 */
{ TARGET_EACL, 0x22c0000, 1 }, /* 0xe22c0000 */
{ TARGET_ANA_CL, 0x2400000, 1 }, /* 0xe2400000 */
{ TARGET_ANA_L3, 0x2480000, 1 }, /* 0xe2480000 */
{ TARGET_ANA_AC_SDLB, 0x2500000, 1 }, /* 0xe2500000 */
{ TARGET_HSCH, 0x2580000, 1 }, /* 0xe2580000 */
{ TARGET_REW, 0x2600000, 1 }, /* 0xe2600000 */
{ TARGET_ANA_L2, 0x2800000, 1 }, /* 0xe2800000 */
{ TARGET_ANA_AC, 0x2900000, 1 }, /* 0xe2900000 */
{ TARGET_VOP, 0x2a00000, 1 }, /* 0xe2a00000 */
{ TARGET_DEV2G5, 0x3004000, 1 }, /* 0xe3004000 */
{ TARGET_DEV10G, 0x3008000, 1 }, /* 0xe3008000 */
{ TARGET_PCS10G_BR, 0x300c000, 1 }, /* 0xe300c000 */
{ TARGET_DEV2G5 + 1, 0x3010000, 1 }, /* 0xe3010000 */
{ TARGET_DEV2G5 + 2, 0x3014000, 1 }, /* 0xe3014000 */
{ TARGET_DEV2G5 + 3, 0x3018000, 1 }, /* 0xe3018000 */
{ TARGET_DEV2G5 + 4, 0x301c000, 1 }, /* 0xe301c000 */
{ TARGET_DEV10G + 1, 0x3020000, 1 }, /* 0xe3020000 */
{ TARGET_PCS10G_BR + 1, 0x3024000, 1 }, /* 0xe3024000 */
{ TARGET_DEV2G5 + 5, 0x3028000, 1 }, /* 0xe3028000 */
{ TARGET_DEV2G5 + 6, 0x302c000, 1 }, /* 0xe302c000 */
{ TARGET_DEV2G5 + 7, 0x3030000, 1 }, /* 0xe3030000 */
{ TARGET_DEV2G5 + 8, 0x3034000, 1 }, /* 0xe3034000 */
{ TARGET_DEV10G + 2, 0x3038000, 1 }, /* 0xe3038000 */
{ TARGET_PCS10G_BR + 2, 0x303c000, 1 }, /* 0xe303c000 */
{ TARGET_DEV2G5 + 9, 0x3040000, 1 }, /* 0xe3040000 */
{ TARGET_DEV5G, 0x3044000, 1 }, /* 0xe3044000 */
{ TARGET_PCS5G_BR, 0x3048000, 1 }, /* 0xe3048000 */
{ TARGET_DEV2G5 + 10, 0x304c000, 1 }, /* 0xe304c000 */
{ TARGET_DEV2G5 + 11, 0x3050000, 1 }, /* 0xe3050000 */
{ TARGET_DEV2G5 + 12, 0x3054000, 1 }, /* 0xe3054000 */
{ TARGET_DEV10G + 3, 0x3058000, 1 }, /* 0xe3058000 */
{ TARGET_PCS10G_BR + 3, 0x305c000, 1 }, /* 0xe305c000 */
{ TARGET_DEV2G5 + 13, 0x3060000, 1 }, /* 0xe3060000 */
{ TARGET_DEV5G + 1, 0x3064000, 1 }, /* 0xe3064000 */
{ TARGET_PCS5G_BR + 1, 0x3068000, 1 }, /* 0xe3068000 */
{ TARGET_DEV2G5 + 14, 0x306c000, 1 }, /* 0xe306c000 */
{ TARGET_DEV2G5 + 15, 0x3070000, 1 }, /* 0xe3070000 */
{ TARGET_DEV2G5 + 16, 0x3074000, 1 }, /* 0xe3074000 */
{ TARGET_DEV10G + 4, 0x3078000, 1 }, /* 0xe3078000 */
{ TARGET_PCS10G_BR + 4, 0x307c000, 1 }, /* 0xe307c000 */
{ TARGET_DEV2G5 + 17, 0x3080000, 1 }, /* 0xe3080000 */
{ TARGET_DEV5G + 2, 0x3084000, 1 }, /* 0xe3084000 */
{ TARGET_PCS5G_BR + 2, 0x3088000, 1 }, /* 0xe3088000 */
{ TARGET_DEV2G5 + 18, 0x308c000, 1 }, /* 0xe308c000 */
{ TARGET_DEV2G5 + 19, 0x3090000, 1 }, /* 0xe3090000 */
{ TARGET_DEV2G5 + 20, 0x3094000, 1 }, /* 0xe3094000 */
{ TARGET_DEV10G + 5, 0x3098000, 1 }, /* 0xe3098000 */
{ TARGET_PCS10G_BR + 5, 0x309c000, 1 }, /* 0xe309c000 */
{ TARGET_DEV2G5 + 21, 0x30a0000, 1 }, /* 0xe30a0000 */
{ TARGET_DEV5G + 3, 0x30a4000, 1 }, /* 0xe30a4000 */
{ TARGET_PCS5G_BR + 3, 0x30a8000, 1 }, /* 0xe30a8000 */
{ TARGET_DEV2G5 + 22, 0x30ac000, 1 }, /* 0xe30ac000 */
{ TARGET_DEV2G5 + 23, 0x30b0000, 1 }, /* 0xe30b0000 */
{ TARGET_DEV2G5 + 24, 0x30b4000, 1 }, /* 0xe30b4000 */
{ TARGET_DEV10G + 6, 0x30b8000, 1 }, /* 0xe30b8000 */
{ TARGET_PCS10G_BR + 6, 0x30bc000, 1 }, /* 0xe30bc000 */
{ TARGET_DEV2G5 + 25, 0x30c0000, 1 }, /* 0xe30c0000 */
{ TARGET_DEV10G + 7, 0x30c4000, 1 }, /* 0xe30c4000 */
{ TARGET_PCS10G_BR + 7, 0x30c8000, 1 }, /* 0xe30c8000 */
{ TARGET_DEV2G5 + 26, 0x30cc000, 1 }, /* 0xe30cc000 */
{ TARGET_DEV10G + 8, 0x30d0000, 1 }, /* 0xe30d0000 */
{ TARGET_PCS10G_BR + 8, 0x30d4000, 1 }, /* 0xe30d4000 */
{ TARGET_DEV2G5 + 27, 0x30d8000, 1 }, /* 0xe30d8000 */
{ TARGET_DEV10G + 9, 0x30dc000, 1 }, /* 0xe30dc000 */
{ TARGET_PCS10G_BR + 9, 0x30e0000, 1 }, /* 0xe30e0000 */
{ TARGET_DEVRGMII, 0x30e4000, 1 }, /* 0xe30e4000 */
{ TARGET_DEVRGMII + 1, 0x30e8000, 1 }, /* 0xe30e8000 */
{ TARGET_DSM, 0x30ec000, 1 }, /* 0xe30ec000 */
{ TARGET_PORT_CONF, 0x30f0000, 1 }, /* 0xe30f0000 */
{ TARGET_ASM, 0x3200000, 1 }, /* 0xe3200000 */
{ TARGET_HSIO_WRAP, 0x3408000, 1 }, /* 0xe3408000 */
};
static struct sparx5_sdlb_group lan969x_sdlb_groups[LAN969X_SDLB_GRP_CNT] = {
{ 1000000000, 8192 / 2, 64 }, /* 1 G */
{ 500000000, 8192 / 2, 64 }, /* 500 M */
{ 100000000, 8192 / 4, 64 }, /* 100 M */
{ 50000000, 8192 / 4, 64 }, /* 50 M */
{ 5000000, 8192 / 8, 64 }, /* 10 M */
};
static u32 lan969x_hsch_max_group_rate[LAN969X_HSCH_LEAK_GRP_CNT] = {
655355, 1048568, 6553550, 10485680
};
static struct sparx5_sdlb_group *lan969x_get_sdlb_group(int idx)
{
return &lan969x_sdlb_groups[idx];
}
static u32 lan969x_get_hsch_max_group_rate(int grp)
{
return lan969x_hsch_max_group_rate[grp];
}
static u32 lan969x_get_dev_mode_bit(struct sparx5 *sparx5, int port)
{
if (lan969x_port_is_2g5(port) || lan969x_port_is_5g(port))
return port;
/* 10G */
switch (port) {
case 0:
return 12;
case 4:
return 13;
case 8:
return 14;
case 12:
return 0;
default:
return port;
}
}
static u32 lan969x_port_dev_mapping(struct sparx5 *sparx5, int port)
{
if (lan969x_port_is_5g(port)) {
switch (port) {
case 9:
return 0;
case 13:
return 1;
case 17:
return 2;
case 21:
return 3;
}
}
if (lan969x_port_is_10g(port)) {
switch (port) {
case 0:
return 0;
case 4:
return 1;
case 8:
return 2;
case 12:
return 3;
case 16:
return 4;
case 20:
return 5;
case 24:
return 6;
case 25:
return 7;
case 26:
return 8;
case 27:
return 9;
}
}
/* 2g5 port */
return port;
}
static int lan969x_port_mux_set(struct sparx5 *sparx5, struct sparx5_port *port,
struct sparx5_port_config *conf)
{
u32 portno = port->portno;
u32 inst;
if (port->conf.portmode == conf->portmode)
return 0; /* Nothing to do */
switch (conf->portmode) {
case PHY_INTERFACE_MODE_QSGMII: /* QSGMII: 4x2G5 devices. Mode Q' */
inst = (portno - portno % 4) / 4;
spx5_rmw(BIT(inst), BIT(inst), sparx5, PORT_CONF_QSGMII_ENA);
break;
default:
break;
}
return 0;
}
static irqreturn_t lan969x_ptp_irq_handler(int irq, void *args)
{
int budget = SPARX5_MAX_PTP_ID;
struct sparx5 *sparx5 = args;
while (budget--) {
struct sk_buff *skb, *skb_tmp, *skb_match = NULL;
struct skb_shared_hwtstamps shhwtstamps;
struct sparx5_port *port;
struct timespec64 ts;
unsigned long flags;
u32 val, id, txport;
u32 delay;
val = spx5_rd(sparx5, PTP_TWOSTEP_CTRL);
/* Check if a timestamp can be retrieved */
if (!(val & PTP_TWOSTEP_CTRL_PTP_VLD))
break;
WARN_ON(val & PTP_TWOSTEP_CTRL_PTP_OVFL);
if (!(val & PTP_TWOSTEP_CTRL_STAMP_TX))
continue;
/* Retrieve the ts Tx port */
txport = PTP_TWOSTEP_CTRL_STAMP_PORT_GET(val);
/* Retrieve its associated skb */
port = sparx5->ports[txport];
/* Retrieve the delay */
delay = spx5_rd(sparx5, PTP_TWOSTEP_STAMP_NSEC);
delay = PTP_TWOSTEP_STAMP_NSEC_NS_GET(delay);
/* Get next timestamp from fifo, which needs to be the
* rx timestamp which represents the id of the frame
*/
spx5_rmw(PTP_TWOSTEP_CTRL_PTP_NXT_SET(1),
PTP_TWOSTEP_CTRL_PTP_NXT,
sparx5, PTP_TWOSTEP_CTRL);
val = spx5_rd(sparx5, PTP_TWOSTEP_CTRL);
/* Check if a timestamp can be retrieved */
if (!(val & PTP_TWOSTEP_CTRL_PTP_VLD))
break;
/* Read RX timestamping to get the ID */
id = spx5_rd(sparx5, PTP_TWOSTEP_STAMP_NSEC);
id <<= 8;
id |= spx5_rd(sparx5, PTP_TWOSTEP_STAMP_SUBNS);
spin_lock_irqsave(&port->tx_skbs.lock, flags);
skb_queue_walk_safe(&port->tx_skbs, skb, skb_tmp) {
if (SPARX5_SKB_CB(skb)->ts_id != id)
continue;
__skb_unlink(skb, &port->tx_skbs);
skb_match = skb;
break;
}
spin_unlock_irqrestore(&port->tx_skbs.lock, flags);
/* Next ts */
spx5_rmw(PTP_TWOSTEP_CTRL_PTP_NXT_SET(1),
PTP_TWOSTEP_CTRL_PTP_NXT,
sparx5, PTP_TWOSTEP_CTRL);
if (WARN_ON(!skb_match))
continue;
spin_lock_irqsave(&sparx5->ptp_ts_id_lock, flags);
sparx5->ptp_skbs--;
spin_unlock_irqrestore(&sparx5->ptp_ts_id_lock, flags);
/* Get the h/w timestamp */
sparx5_get_hwtimestamp(sparx5, &ts, delay);
/* Set the timestamp in the skb */
shhwtstamps.hwtstamp = ktime_set(ts.tv_sec, ts.tv_nsec);
skb_tstamp_tx(skb_match, &shhwtstamps);
dev_kfree_skb_any(skb_match);
}
return IRQ_HANDLED;
}
static const struct sparx5_regs lan969x_regs = {
.tsize = lan969x_tsize,
.gaddr = lan969x_gaddr,
.gcnt = lan969x_gcnt,
.gsize = lan969x_gsize,
.raddr = lan969x_raddr,
.rcnt = lan969x_rcnt,
.fpos = lan969x_fpos,
.fsize = lan969x_fsize,
};
static const struct sparx5_consts lan969x_consts = {
.n_ports = 30,
.n_ports_all = 35,
.n_hsch_l1_elems = 32,
.n_hsch_queues = 4,
.n_lb_groups = 5,
.n_pgids = 1054, /* (1024 + n_ports) */
.n_sio_clks = 1,
.n_own_upsids = 1,
.n_auto_cals = 4,
.n_filters = 256,
.n_gates = 256,
.n_sdlbs = 496,
.n_dsm_cal_taxis = 5,
.buf_size = 1572864,
.qres_max_prio_idx = 315,
.qres_max_colour_idx = 323,
.tod_pin = 4,
.vcaps = lan969x_vcaps,
.vcap_stats = &lan969x_vcap_stats,
.vcaps_cfg = lan969x_vcap_inst_cfg,
};
static const struct sparx5_ops lan969x_ops = {
.is_port_2g5 = &lan969x_port_is_2g5,
.is_port_5g = &lan969x_port_is_5g,
.is_port_10g = &lan969x_port_is_10g,
.is_port_25g = &lan969x_port_is_25g,
.is_port_rgmii = &lan969x_port_is_rgmii,
.get_port_dev_index = &lan969x_port_dev_mapping,
.get_port_dev_bit = &lan969x_get_dev_mode_bit,
.get_hsch_max_group_rate = &lan969x_get_hsch_max_group_rate,
.get_sdlb_group = &lan969x_get_sdlb_group,
.set_port_mux = &lan969x_port_mux_set,
.ptp_irq_handler = &lan969x_ptp_irq_handler,
.dsm_calendar_calc = &lan969x_dsm_calendar_calc,
.port_config_rgmii = &lan969x_port_config_rgmii,
.fdma_init = &lan969x_fdma_init,
.fdma_deinit = &lan969x_fdma_deinit,
.fdma_poll = &lan969x_fdma_napi_poll,
.fdma_xmit = &lan969x_fdma_xmit,
};
const struct sparx5_match_data lan969x_desc = {
.iomap = lan969x_main_iomap,
.iomap_size = ARRAY_SIZE(lan969x_main_iomap),
.ioranges = 2,
.regs = &lan969x_regs,
.consts = &lan969x_consts,
.ops = &lan969x_ops,
};
@@ -0,0 +1,82 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/* Microchip lan969x Switch driver
*
* Copyright (c) 2024 Microchip Technology Inc. and its subsidiaries.
*/
#ifndef __LAN969X_H__
#define __LAN969X_H__
#include "../sparx5/sparx5_main.h"
#include "../sparx5/sparx5_regs.h"
#include "../sparx5/sparx5_vcap_impl.h"
/* lan969x.c */
extern const struct sparx5_match_data lan969x_desc;
/* lan969x_vcap_ag_api.c */
extern const struct vcap_statistics lan969x_vcap_stats;
extern const struct vcap_info lan969x_vcaps[];
/* lan969x_vcap_impl.c */
extern const struct sparx5_vcap_inst lan969x_vcap_inst_cfg[];
/* lan969x_regs.c */
extern const unsigned int lan969x_tsize[TSIZE_LAST];
extern const unsigned int lan969x_raddr[RADDR_LAST];
extern const unsigned int lan969x_rcnt[RCNT_LAST];
extern const unsigned int lan969x_gaddr[GADDR_LAST];
extern const unsigned int lan969x_gcnt[GCNT_LAST];
extern const unsigned int lan969x_gsize[GSIZE_LAST];
extern const unsigned int lan969x_fpos[FPOS_LAST];
extern const unsigned int lan969x_fsize[FSIZE_LAST];
static inline bool lan969x_port_is_2g5(int portno)
{
return portno == 1 || portno == 2 || portno == 3 ||
portno == 5 || portno == 6 || portno == 7 ||
portno == 10 || portno == 11 || portno == 14 ||
portno == 15 || portno == 18 || portno == 19 ||
portno == 22 || portno == 23;
}
static inline bool lan969x_port_is_5g(int portno)
{
return portno == 9 || portno == 13 || portno == 17 ||
portno == 21;
}
static inline bool lan969x_port_is_10g(int portno)
{
return portno == 0 || portno == 4 || portno == 8 ||
portno == 12 || portno == 16 || portno == 20 ||
portno == 24 || portno == 25 || portno == 26 ||
portno == 27;
}
static inline bool lan969x_port_is_25g(int portno)
{
return false;
}
static inline bool lan969x_port_is_rgmii(int portno)
{
return portno == 28 || portno == 29;
}
/* lan969x_calendar.c */
int lan969x_dsm_calendar_calc(struct sparx5 *sparx5, u32 taxi,
struct sparx5_calendar_data *data);
/* lan969x_rgmii.c */
int lan969x_port_config_rgmii(struct sparx5_port *port,
struct sparx5_port_config *conf);
/* lan969x_fdma.c */
int lan969x_fdma_init(struct sparx5 *sparx5);
int lan969x_fdma_deinit(struct sparx5 *sparx5);
int lan969x_fdma_napi_poll(struct napi_struct *napi, int weight);
int lan969x_fdma_xmit(struct sparx5 *sparx5, u32 *ifh, struct sk_buff *skb,
struct net_device *dev);
#endif
@@ -0,0 +1,191 @@
// SPDX-License-Identifier: GPL-2.0+
/* Microchip lan969x Switch driver
*
* Copyright (c) 2024 Microchip Technology Inc. and its subsidiaries.
*/
#include "lan969x.h"
#define LAN969X_DSM_CAL_DEVS_PER_TAXI 10
#define LAN969X_DSM_CAL_TAXIS 5
enum lan969x_dsm_cal_dev {
DSM_CAL_DEV_2G5,
DSM_CAL_DEV_5G,
DSM_CAL_DEV_10G,
DSM_CAL_DEV_OTHER, /* 1G or less */
DSM_CAL_DEV_MAX
};
/* Each entry in the following struct defines properties for a given speed
* (10G, 5G, 2.5G, or 1G or less).
*/
struct lan969x_dsm_cal_dev_speed {
/* Number of devices that requires this speed. */
u32 n_devs;
/* Array of devices that requires this speed. */
u32 devs[LAN969X_DSM_CAL_DEVS_PER_TAXI];
/* Number of slots required for one device running this speed. */
u32 n_slots;
/* Gap between two slots for one device running this speed. */
u32 gap;
};
static u32
lan969x_taxi_ports[LAN969X_DSM_CAL_TAXIS][LAN969X_DSM_CAL_DEVS_PER_TAXI] = {
{ 0, 4, 1, 2, 3, 5, 6, 7, 28, 29 },
{ 8, 12, 9, 13, 10, 11, 14, 15, 99, 99 },
{ 16, 20, 17, 21, 18, 19, 22, 23, 99, 99 },
{ 24, 25, 99, 99, 99, 99, 99, 99, 99, 99 },
{ 26, 27, 99, 99, 99, 99, 99, 99, 99, 99 }
};
static int lan969x_dsm_cal_idx_get(u32 *calendar, u32 cal_len, u32 *cal_idx)
{
if (*cal_idx >= cal_len)
return -EINVAL;
do {
if (calendar[*cal_idx] == SPX5_DSM_CAL_EMPTY)
return 0;
(*cal_idx)++;
} while (*cal_idx < cal_len);
return -ENOENT;
}
static enum lan969x_dsm_cal_dev lan969x_dsm_cal_get_dev(int speed)
{
return (speed == 10000 ? DSM_CAL_DEV_10G :
speed == 5000 ? DSM_CAL_DEV_5G :
speed == 2500 ? DSM_CAL_DEV_2G5 :
DSM_CAL_DEV_OTHER);
}
static int lan969x_dsm_cal_get_speed(enum lan969x_dsm_cal_dev dev)
{
return (dev == DSM_CAL_DEV_10G ? 10000 :
dev == DSM_CAL_DEV_5G ? 5000 :
dev == DSM_CAL_DEV_2G5 ? 2500 :
1000);
}
int lan969x_dsm_calendar_calc(struct sparx5 *sparx5, u32 taxi,
struct sparx5_calendar_data *data)
{
struct lan969x_dsm_cal_dev_speed dev_speeds[DSM_CAL_DEV_MAX] = {};
u32 cal_len, n_slots, taxi_bw, n_devs = 0, required_bw = 0;
struct lan969x_dsm_cal_dev_speed *speed;
int err;
/* Maximum bandwidth for this taxi */
taxi_bw = (128 * 1000000) / sparx5_clk_period(sparx5->coreclock);
memcpy(data->taxi_ports, &lan969x_taxi_ports[taxi],
LAN969X_DSM_CAL_DEVS_PER_TAXI * sizeof(u32));
for (int i = 0; i < LAN969X_DSM_CAL_DEVS_PER_TAXI; i++) {
u32 portno = data->taxi_ports[i];
enum sparx5_cal_bw bw;
bw = sparx5_get_port_cal_speed(sparx5, portno);
if (portno < sparx5->data->consts->n_ports_all)
data->taxi_speeds[i] = sparx5_cal_speed_to_value(bw);
else
data->taxi_speeds[i] = 0;
}
/* Determine the different port types (10G, 5G, 2.5G, <= 1G) in the
* this taxi map.
*/
for (int i = 0; i < LAN969X_DSM_CAL_DEVS_PER_TAXI; i++) {
u32 taxi_speed = data->taxi_speeds[i];
enum lan969x_dsm_cal_dev dev;
if (taxi_speed == 0)
continue;
required_bw += taxi_speed;
dev = lan969x_dsm_cal_get_dev(taxi_speed);
speed = &dev_speeds[dev];
speed->devs[speed->n_devs++] = i;
n_devs++;
}
if (required_bw > taxi_bw) {
pr_err("Required bandwidth: %u is higher than total taxi bandwidth: %u",
required_bw, taxi_bw);
return -EINVAL;
}
if (n_devs == 0) {
data->schedule[0] = SPX5_DSM_CAL_EMPTY;
return 0;
}
cal_len = n_devs;
/* Search for a calendar length that fits all active devices. */
while (cal_len < SPX5_DSM_CAL_LEN) {
u32 bw_per_slot = taxi_bw / cal_len;
n_slots = 0;
for (int i = 0; i < DSM_CAL_DEV_MAX; i++) {
speed = &dev_speeds[i];
if (speed->n_devs == 0)
continue;
required_bw = lan969x_dsm_cal_get_speed(i);
speed->n_slots = DIV_ROUND_UP(required_bw, bw_per_slot);
if (speed->n_slots)
speed->gap = DIV_ROUND_UP(cal_len,
speed->n_slots);
else
speed->gap = 0;
n_slots += speed->n_slots * speed->n_devs;
}
if (n_slots <= cal_len)
break; /* Found a suitable calendar length. */
/* Not good enough yet. */
cal_len = n_slots;
}
if (cal_len > SPX5_DSM_CAL_LEN) {
pr_err("Invalid length: %u for taxi: %u", cal_len, taxi);
return -EINVAL;
}
for (u32 i = 0; i < SPX5_DSM_CAL_LEN; i++)
data->schedule[i] = SPX5_DSM_CAL_EMPTY;
/* Place the remaining devices */
for (u32 i = 0; i < DSM_CAL_DEV_MAX; i++) {
speed = &dev_speeds[i];
for (u32 dev = 0; dev < speed->n_devs; dev++) {
u32 idx = 0;
for (n_slots = 0; n_slots < speed->n_slots; n_slots++) {
err = lan969x_dsm_cal_idx_get(data->schedule,
cal_len, &idx);
if (err)
return err;
data->schedule[idx] = speed->devs[dev];
idx += speed->gap;
}
}
}
return 0;
}
@@ -0,0 +1,404 @@
// SPDX-License-Identifier: GPL-2.0+
/* Microchip lan969x Switch driver
*
* Copyright (c) 2025 Microchip Technology Inc. and its subsidiaries.
*/
#include <net/page_pool/helpers.h>
#include "../sparx5_main.h"
#include "../sparx5_main_regs.h"
#include "../sparx5_port.h"
#include "fdma_api.h"
#include "lan969x.h"
#define FDMA_PRIV(fdma) ((struct sparx5 *)((fdma)->priv))
static int lan969x_fdma_tx_dataptr_cb(struct fdma *fdma, int dcb, int db,
u64 *dataptr)
{
*dataptr = FDMA_PRIV(fdma)->tx.dbs[dcb].dma_addr;
return 0;
}
static int lan969x_fdma_rx_dataptr_cb(struct fdma *fdma, int dcb, int db,
u64 *dataptr)
{
struct sparx5_rx *rx = &FDMA_PRIV(fdma)->rx;
struct page *page;
page = page_pool_dev_alloc_pages(rx->page_pool);
if (unlikely(!page))
return -ENOMEM;
rx->page[dcb][db] = page;
*dataptr = page_pool_get_dma_addr(page);
return 0;
}
static int lan969x_fdma_get_next_dcb(struct sparx5_tx *tx)
{
struct fdma *fdma = &tx->fdma;
for (int i = 0; i < fdma->n_dcbs; ++i)
if (!tx->dbs[i].used && !fdma_is_last(fdma, &fdma->dcbs[i]))
return i;
return -ENOSPC;
}
static void lan969x_fdma_tx_clear_buf(struct sparx5 *sparx5, int weight)
{
struct fdma *fdma = &sparx5->tx.fdma;
struct sparx5_tx_buf *db;
unsigned long flags;
int i;
spin_lock_irqsave(&sparx5->tx_lock, flags);
for (i = 0; i < fdma->n_dcbs; ++i) {
db = &sparx5->tx.dbs[i];
if (!db->used)
continue;
if (!fdma_db_is_done(fdma_db_get(fdma, i, 0)))
continue;
db->dev->stats.tx_bytes += db->skb->len;
db->dev->stats.tx_packets++;
sparx5->tx.packets++;
dma_unmap_single(sparx5->dev,
db->dma_addr,
db->skb->len,
DMA_TO_DEVICE);
if (!db->ptp)
napi_consume_skb(db->skb, weight);
db->used = false;
}
spin_unlock_irqrestore(&sparx5->tx_lock, flags);
}
static void lan969x_fdma_free_pages(struct sparx5_rx *rx)
{
struct fdma *fdma = &rx->fdma;
for (int i = 0; i < fdma->n_dcbs; ++i) {
for (int j = 0; j < fdma->n_dbs; ++j)
page_pool_put_full_page(rx->page_pool,
rx->page[i][j], false);
}
}
static struct sk_buff *lan969x_fdma_rx_get_frame(struct sparx5 *sparx5,
struct sparx5_rx *rx)
{
const struct sparx5_consts *consts = sparx5->data->consts;
struct fdma *fdma = &rx->fdma;
struct sparx5_port *port;
struct frame_info fi;
struct sk_buff *skb;
struct fdma_db *db;
struct page *page;
db = &fdma->dcbs[fdma->dcb_index].db[fdma->db_index];
page = rx->page[fdma->dcb_index][fdma->db_index];
sparx5_ifh_parse(sparx5, page_address(page), &fi);
port = fi.src_port < consts->n_ports ? sparx5->ports[fi.src_port] :
NULL;
if (WARN_ON(!port))
goto free_page;
skb = build_skb(page_address(page), fdma->db_size);
if (unlikely(!skb))
goto free_page;
skb_mark_for_recycle(skb);
skb_put(skb, fdma_db_len_get(db));
skb_pull(skb, IFH_LEN * sizeof(u32));
skb->dev = port->ndev;
if (likely(!(skb->dev->features & NETIF_F_RXFCS)))
skb_trim(skb, skb->len - ETH_FCS_LEN);
sparx5_ptp_rxtstamp(sparx5, skb, fi.timestamp);
skb->protocol = eth_type_trans(skb, skb->dev);
if (test_bit(port->portno, sparx5->bridge_mask))
skb->offload_fwd_mark = 1;
skb->dev->stats.rx_bytes += skb->len;
skb->dev->stats.rx_packets++;
return skb;
free_page:
page_pool_recycle_direct(rx->page_pool, page);
return NULL;
}
static int lan969x_fdma_rx_alloc(struct sparx5 *sparx5)
{
struct sparx5_rx *rx = &sparx5->rx;
struct fdma *fdma = &rx->fdma;
int err;
struct page_pool_params pp_params = {
.order = 0,
.flags = PP_FLAG_DMA_MAP | PP_FLAG_DMA_SYNC_DEV,
.pool_size = fdma->n_dcbs * fdma->n_dbs,
.nid = NUMA_NO_NODE,
.dev = sparx5->dev,
.dma_dir = DMA_FROM_DEVICE,
.offset = 0,
.max_len = fdma->db_size -
SKB_DATA_ALIGN(sizeof(struct skb_shared_info)),
};
rx->page_pool = page_pool_create(&pp_params);
if (IS_ERR(rx->page_pool))
return PTR_ERR(rx->page_pool);
err = fdma_alloc_coherent(sparx5->dev, fdma);
if (err)
return err;
fdma_dcbs_init(fdma,
FDMA_DCB_INFO_DATAL(fdma->db_size),
FDMA_DCB_STATUS_INTR);
return 0;
}
static int lan969x_fdma_tx_alloc(struct sparx5 *sparx5)
{
struct sparx5_tx *tx = &sparx5->tx;
struct fdma *fdma = &tx->fdma;
int err;
tx->dbs = kzalloc_objs(struct sparx5_tx_buf, fdma->n_dcbs);
if (!tx->dbs)
return -ENOMEM;
err = fdma_alloc_coherent(sparx5->dev, fdma);
if (err) {
kfree(tx->dbs);
return err;
}
fdma_dcbs_init(fdma,
FDMA_DCB_INFO_DATAL(fdma->db_size),
FDMA_DCB_STATUS_DONE);
return 0;
}
static void lan969x_fdma_rx_init(struct sparx5 *sparx5)
{
struct fdma *fdma = &sparx5->rx.fdma;
fdma->channel_id = FDMA_XTR_CHANNEL;
fdma->n_dcbs = FDMA_DCB_MAX;
fdma->n_dbs = 1;
fdma->priv = sparx5;
fdma->size = fdma_get_size(fdma);
fdma->db_size = PAGE_SIZE;
fdma->ops.dataptr_cb = &lan969x_fdma_rx_dataptr_cb;
fdma->ops.nextptr_cb = &fdma_nextptr_cb;
/* Fetch a netdev for SKB and NAPI use, any will do */
for (int idx = 0; idx < sparx5->data->consts->n_ports; ++idx) {
struct sparx5_port *port = sparx5->ports[idx];
if (port && port->ndev) {
sparx5->rx.ndev = port->ndev;
break;
}
}
}
static void lan969x_fdma_tx_init(struct sparx5 *sparx5)
{
struct fdma *fdma = &sparx5->tx.fdma;
fdma->channel_id = FDMA_INJ_CHANNEL;
fdma->n_dcbs = FDMA_DCB_MAX;
fdma->n_dbs = 1;
fdma->priv = sparx5;
fdma->size = fdma_get_size(fdma);
fdma->db_size = PAGE_SIZE;
fdma->ops.dataptr_cb = &lan969x_fdma_tx_dataptr_cb;
fdma->ops.nextptr_cb = &fdma_nextptr_cb;
}
int lan969x_fdma_napi_poll(struct napi_struct *napi, int weight)
{
struct sparx5_rx *rx = container_of(napi, struct sparx5_rx, napi);
struct sparx5 *sparx5 = container_of(rx, struct sparx5, rx);
int old_dcb, dcb_reload, counter = 0;
struct fdma *fdma = &rx->fdma;
struct sk_buff *skb;
dcb_reload = fdma->dcb_index;
lan969x_fdma_tx_clear_buf(sparx5, weight);
/* Process RX data */
while (counter < weight) {
if (!fdma_has_frames(fdma))
break;
skb = lan969x_fdma_rx_get_frame(sparx5, rx);
if (!skb)
break;
napi_gro_receive(&rx->napi, skb);
fdma_db_advance(fdma);
counter++;
/* Check if the DCB can be reused */
if (fdma_dcb_is_reusable(fdma))
continue;
fdma_db_reset(fdma);
fdma_dcb_advance(fdma);
}
/* Allocate new pages and map them */
while (dcb_reload != fdma->dcb_index) {
old_dcb = dcb_reload;
dcb_reload++;
/* n_dcbs must be a power of 2 */
dcb_reload &= fdma->n_dcbs - 1;
fdma_dcb_add(fdma,
old_dcb,
FDMA_DCB_INFO_DATAL(fdma->db_size),
FDMA_DCB_STATUS_INTR);
sparx5_fdma_reload(sparx5, fdma);
}
if (counter < weight && napi_complete_done(napi, counter))
spx5_wr(0xff, sparx5, FDMA_INTR_DB_ENA);
return counter;
}
int lan969x_fdma_xmit(struct sparx5 *sparx5, u32 *ifh, struct sk_buff *skb,
struct net_device *dev)
{
int next_dcb, needed_headroom, needed_tailroom, err;
struct sparx5_tx *tx = &sparx5->tx;
struct fdma *fdma = &tx->fdma;
struct sparx5_tx_buf *db_buf;
u64 status;
next_dcb = lan969x_fdma_get_next_dcb(tx);
if (next_dcb < 0)
return -EBUSY;
needed_headroom = max_t(int, IFH_LEN * 4 - skb_headroom(skb), 0);
needed_tailroom = max_t(int, ETH_FCS_LEN - skb_tailroom(skb), 0);
if (needed_headroom || needed_tailroom || skb_header_cloned(skb)) {
err = pskb_expand_head(skb, needed_headroom, needed_tailroom,
GFP_ATOMIC);
if (unlikely(err))
return err;
}
skb_push(skb, IFH_LEN * 4);
memcpy(skb->data, ifh, IFH_LEN * 4);
skb_put(skb, ETH_FCS_LEN);
db_buf = &tx->dbs[next_dcb];
db_buf->dma_addr = dma_map_single(sparx5->dev,
skb->data,
skb->len,
DMA_TO_DEVICE);
if (dma_mapping_error(sparx5->dev, db_buf->dma_addr))
return -ENOMEM;
db_buf->dev = dev;
db_buf->skb = skb;
db_buf->ptp = false;
db_buf->used = true;
if (skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP &&
SPARX5_SKB_CB(skb)->rew_op == IFH_REW_OP_TWO_STEP_PTP)
db_buf->ptp = true;
status = FDMA_DCB_STATUS_SOF |
FDMA_DCB_STATUS_EOF |
FDMA_DCB_STATUS_BLOCKO(0) |
FDMA_DCB_STATUS_BLOCKL(skb->len) |
FDMA_DCB_STATUS_INTR;
fdma_dcb_advance(fdma);
fdma_dcb_add(fdma, next_dcb, 0, status);
sparx5_fdma_reload(sparx5, fdma);
return NETDEV_TX_OK;
}
int lan969x_fdma_init(struct sparx5 *sparx5)
{
struct sparx5_rx *rx = &sparx5->rx;
int err;
lan969x_fdma_rx_init(sparx5);
lan969x_fdma_tx_init(sparx5);
sparx5_fdma_injection_mode(sparx5);
err = dma_set_mask_and_coherent(sparx5->dev, DMA_BIT_MASK(64));
if (err) {
dev_err(sparx5->dev, "Failed to set 64-bit FDMA mask");
return err;
}
err = lan969x_fdma_rx_alloc(sparx5);
if (err) {
dev_err(sparx5->dev, "Failed to allocate RX buffers: %d\n",
err);
return err;
}
err = lan969x_fdma_tx_alloc(sparx5);
if (err) {
fdma_free_coherent(sparx5->dev, &rx->fdma);
dev_err(sparx5->dev, "Failed to allocate TX buffers: %d\n",
err);
return err;
}
/* Reset FDMA state */
spx5_wr(FDMA_CTRL_NRESET_SET(0), sparx5, FDMA_CTRL);
spx5_wr(FDMA_CTRL_NRESET_SET(1), sparx5, FDMA_CTRL);
return err;
}
int lan969x_fdma_deinit(struct sparx5 *sparx5)
{
struct sparx5_rx *rx = &sparx5->rx;
struct sparx5_tx *tx = &sparx5->tx;
sparx5_fdma_stop(sparx5);
fdma_free_coherent(sparx5->dev, &tx->fdma);
fdma_free_coherent(sparx5->dev, &rx->fdma);
lan969x_fdma_free_pages(rx);
page_pool_destroy(rx->page_pool);
return 0;
}
@@ -0,0 +1,222 @@
// SPDX-License-Identifier: GPL-2.0+
/* Microchip lan969x Switch driver
*
* Copyright (c) 2024 Microchip Technology Inc.
*/
/* This file is autogenerated by cml-utils 2024-09-30 11:48:29 +0200.
* Commit ID: 9d07b8d19363f3cd3590ddb3f7a2e2768e16524b
*/
#include "lan969x.h"
const unsigned int lan969x_tsize[TSIZE_LAST] = {
[TC_DEV10G] = 10,
[TC_DEV2G5] = 28,
[TC_DEV5G] = 4,
[TC_PCS10G_BR] = 10,
[TC_PCS5G_BR] = 4,
};
const unsigned int lan969x_raddr[RADDR_LAST] = {
[RA_CPU_PROC_CTRL] = 160,
[RA_GCB_SOFT_RST] = 12,
[RA_GCB_HW_SGPIO_TO_SD_MAP_CFG] = 20,
};
const unsigned int lan969x_rcnt[RCNT_LAST] = {
[RC_ANA_AC_OWN_UPSID] = 1,
[RC_ANA_ACL_VCAP_S2_CFG] = 35,
[RC_ANA_ACL_OWN_UPSID] = 1,
[RC_ANA_CL_OWN_UPSID] = 1,
[RC_ANA_L2_OWN_UPSID] = 1,
[RC_ASM_PORT_CFG] = 32,
[RC_DSM_BUF_CFG] = 32,
[RC_DSM_DEV_TX_STOP_WM_CFG] = 32,
[RC_DSM_RX_PAUSE_CFG] = 32,
[RC_DSM_MAC_CFG] = 32,
[RC_DSM_MAC_ADDR_BASE_HIGH_CFG] = 30,
[RC_DSM_MAC_ADDR_BASE_LOW_CFG] = 30,
[RC_DSM_TAXI_CAL_CFG] = 6,
[RC_GCB_HW_SGPIO_TO_SD_MAP_CFG] = 30,
[RC_HSCH_PORT_MODE] = 35,
[RC_QFWD_SWITCH_PORT_MODE] = 35,
[RC_QSYS_PAUSE_CFG] = 35,
[RC_QSYS_ATOP] = 35,
[RC_QSYS_FWD_PRESSURE] = 35,
[RC_QSYS_CAL_AUTO] = 4,
[RC_REW_OWN_UPSID] = 1,
[RC_REW_RTAG_ETAG_CTRL] = 35,
};
const unsigned int lan969x_gaddr[GADDR_LAST] = {
[GA_ANA_AC_RAM_CTRL] = 202000,
[GA_ANA_AC_PS_COMMON] = 202880,
[GA_ANA_AC_MIRROR_PROBE] = 203232,
[GA_ANA_AC_SRC] = 201728,
[GA_ANA_AC_PGID] = 131072,
[GA_ANA_AC_TSN_SF] = 202028,
[GA_ANA_AC_TSN_SF_CFG] = 148480,
[GA_ANA_AC_TSN_SF_STATUS] = 147936,
[GA_ANA_AC_SG_ACCESS] = 202032,
[GA_ANA_AC_SG_CONFIG] = 202752,
[GA_ANA_AC_SG_STATUS] = 147952,
[GA_ANA_AC_SG_STATUS_STICKY] = 202044,
[GA_ANA_AC_STAT_GLOBAL_CFG_PORT] = 202048,
[GA_ANA_AC_STAT_CNT_CFG_PORT] = 204800,
[GA_ANA_AC_STAT_GLOBAL_CFG_ACL] = 202068,
[GA_ANA_ACL_COMMON] = 8192,
[GA_ANA_ACL_KEY_SEL] = 9204,
[GA_ANA_ACL_CNT_B] = 4096,
[GA_ANA_ACL_STICKY] = 10852,
[GA_ANA_AC_POL_POL_ALL_CFG] = 17504,
[GA_ANA_AC_POL_COMMON_BDLB] = 19464,
[GA_ANA_AC_POL_COMMON_BUM_SLB] = 19472,
[GA_ANA_AC_SDLB_LBGRP_TBL] = 31788,
[GA_ANA_CL_PORT] = 65536,
[GA_ANA_CL_COMMON] = 87040,
[GA_ANA_L2_COMMON] = 561928,
[GA_ANA_L3_COMMON] = 370752,
[GA_ANA_L3_VLAN_ARP_L3MC_STICKY] = 368580,
[GA_ASM_CFG] = 18304,
[GA_ASM_PFC_TIMER_CFG] = 15568,
[GA_ASM_LBK_WM_CFG] = 15596,
[GA_ASM_LBK_MISC_CFG] = 15608,
[GA_ASM_RAM_CTRL] = 15684,
[GA_EACL_ES2_KEY_SELECT_PROFILE] = 36864,
[GA_EACL_CNT_TBL] = 30720,
[GA_EACL_POL_CFG] = 38400,
[GA_EACL_ES2_STICKY] = 29072,
[GA_EACL_RAM_CTRL] = 29112,
[GA_GCB_SIO_CTRL] = 560,
[GA_HSCH_HSCH_DWRR] = 36480,
[GA_HSCH_HSCH_MISC] = 36608,
[GA_HSCH_HSCH_LEAK_LISTS] = 37256,
[GA_HSCH_SYSTEM] = 37384,
[GA_HSCH_MMGT] = 36260,
[GA_HSCH_TAS_CONFIG] = 37696,
[GA_PTP_PTP_CFG] = 512,
[GA_PTP_PTP_TOD_DOMAINS] = 528,
[GA_PTP_PHASE_DETECTOR_CTRL] = 628,
[GA_QSYS_CALCFG] = 2164,
[GA_QSYS_RAM_CTRL] = 2204,
[GA_REW_COMMON] = 98304,
[GA_REW_PORT] = 49152,
[GA_REW_VOE_PORT_LM_CNT] = 90112,
[GA_REW_RAM_CTRL] = 93992,
[GA_VOP_RAM_CTRL] = 16368,
[GA_XQS_SYSTEM] = 5744,
[GA_XQS_QLIMIT_SHR] = 6912,
};
const unsigned int lan969x_gcnt[GCNT_LAST] = {
[GC_ANA_AC_SRC] = 67,
[GC_ANA_AC_PGID] = 1054,
[GC_ANA_AC_TSN_SF_CFG] = 256,
[GC_ANA_AC_STAT_CNT_CFG_PORT] = 35,
[GC_ANA_ACL_KEY_SEL] = 99,
[GC_ANA_ACL_CNT_A] = 1024,
[GC_ANA_ACL_CNT_B] = 1024,
[GC_ANA_AC_SDLB_LBGRP_TBL] = 5,
[GC_ANA_AC_SDLB_LBSET_TBL] = 496,
[GC_ANA_CL_PORT] = 35,
[GC_ANA_L2_ISDX_LIMIT] = 256,
[GC_ANA_L2_ISDX] = 1024,
[GC_ANA_L3_VLAN] = 4608,
[GC_ASM_DEV_STATISTICS] = 30,
[GC_EACL_ES2_KEY_SELECT_PROFILE] = 68,
[GC_EACL_CNT_TBL] = 512,
[GC_GCB_SIO_CTRL] = 1,
[GC_HSCH_HSCH_CFG] = 1120,
[GC_HSCH_HSCH_DWRR] = 32,
[GC_PTP_PTP_PINS] = 8,
[GC_PTP_PHASE_DETECTOR_CTRL] = 8,
[GC_REW_PORT] = 35,
[GC_REW_VOE_PORT_LM_CNT] = 240,
};
const unsigned int lan969x_gsize[GSIZE_LAST] = {
[GW_ANA_AC_SRC] = 4,
[GW_ANA_L2_COMMON] = 712,
[GW_ASM_CFG] = 1092,
[GW_CPU_CPU_REGS] = 180,
[GW_DEV2G5_PHASE_DETECTOR_CTRL] = 12,
[GW_FDMA_FDMA] = 448,
[GW_GCB_CHIP_REGS] = 180,
[GW_HSCH_TAS_CONFIG] = 16,
[GW_PTP_PHASE_DETECTOR_CTRL] = 12,
[GW_QSYS_PAUSE_CFG] = 988,
};
const unsigned int lan969x_fpos[FPOS_LAST] = {
[FP_CPU_PROC_CTRL_AARCH64_MODE_ENA] = 7,
[FP_CPU_PROC_CTRL_L2_RST_INVALIDATE_DIS] = 6,
[FP_CPU_PROC_CTRL_L1_RST_INVALIDATE_DIS] = 5,
[FP_CPU_PROC_CTRL_BE_EXCEP_MODE] = 4,
[FP_CPU_PROC_CTRL_VINITHI] = 3,
[FP_CPU_PROC_CTRL_CFGTE] = 2,
[FP_CPU_PROC_CTRL_CP15S_DISABLE] = 1,
[FP_CPU_PROC_CTRL_PROC_CRYPTO_DISABLE] = 0,
[FP_CPU_PROC_CTRL_L2_FLUSH_REQ] = 8,
[FP_DEV2G5_PHAD_CTRL_PHAD_ENA] = 5,
[FP_DEV2G5_PHAD_CTRL_PHAD_FAILED] = 3,
[FP_FDMA_CH_CFG_CH_XTR_STATUS_MODE] = 5,
[FP_FDMA_CH_CFG_CH_INTR_DB_EOF_ONLY] = 4,
[FP_FDMA_CH_CFG_CH_INJ_PORT] = 3,
[FP_PTP_PTP_PIN_CFG_PTP_PIN_ACTION] = 27,
[FP_PTP_PTP_PIN_CFG_PTP_PIN_SYNC] = 25,
[FP_PTP_PTP_PIN_CFG_PTP_PIN_INV_POL] = 24,
[FP_PTP_PHAD_CTRL_PHAD_ENA] = 5,
[FP_PTP_PHAD_CTRL_PHAD_FAILED] = 3,
};
const unsigned int lan969x_fsize[FSIZE_LAST] = {
[FW_ANA_AC_PROBE_PORT_CFG_PROBE_PORT_MASK] = 30,
[FW_ANA_AC_SRC_CFG_PORT_MASK] = 30,
[FW_ANA_AC_PGID_CFG_PORT_MASK] = 30,
[FW_ANA_AC_TSN_SF_PORT_NUM] = 7,
[FW_ANA_AC_TSN_SF_CFG_TSN_SGID] = 8,
[FW_ANA_AC_TSN_SF_STATUS_TSN_SFID] = 8,
[FW_ANA_AC_SG_ACCESS_CTRL_SGID] = 8,
[FW_ANA_AC_PORT_SGE_CFG_MASK] = 17,
[FW_ANA_AC_SDLB_XLB_START_LBSET_START] = 9,
[FW_ANA_AC_SDLB_LBGRP_MISC_THRES_SHIFT] = 3,
[FW_ANA_AC_SDLB_LBGRP_STATE_TBL_PUP_LBSET_NEXT] = 9,
[FW_ANA_AC_SDLB_XLB_NEXT_LBSET_NEXT] = 9,
[FW_ANA_AC_SDLB_XLB_NEXT_LBGRP] = 3,
[FW_ANA_AC_SDLB_INH_LBSET_ADDR_INH_LBSET_ADDR] = 9,
[FW_ANA_L2_AUTO_LRN_CFG_AUTO_LRN_ENA] = 30,
[FW_ANA_L2_DLB_CFG_DLB_IDX] = 9,
[FW_ANA_L2_TSN_CFG_TSN_SFID] = 8,
[FW_ANA_L3_VLAN_MASK_CFG_VLAN_PORT_MASK] = 30,
[FW_FDMA_CH_CFG_CH_DCB_DB_CNT] = 2,
[FW_GCB_HW_SGPIO_TO_SD_MAP_CFG_SGPIO_TO_SD_SEL] = 7,
[FW_HSCH_SE_CFG_SE_DWRR_CNT] = 5,
[FW_HSCH_SE_CONNECT_SE_LEAK_LINK] = 14,
[FW_HSCH_SE_DLB_SENSE_SE_DLB_DPORT] = 6,
[FW_HSCH_HSCH_CFG_CFG_CFG_SE_IDX] = 11,
[FW_HSCH_HSCH_LEAK_CFG_LEAK_FIRST] = 14,
[FW_HSCH_FLUSH_CTRL_FLUSH_PORT] = 6,
[FW_HSCH_FLUSH_CTRL_FLUSH_HIER] = 14,
[FW_LRN_COMMON_ACCESS_CTRL_CPU_ACCESS_DIRECT_ROW] = 13,
[FW_LRN_MAC_ACCESS_CFG_3_MAC_ENTRY_ISDX_LIMIT_IDX] = 8,
[FW_LRN_AUTOAGE_CFG_2_NEXT_ROW] = 13,
[FW_PTP_PTP_PIN_INTR_INTR_PTP] = 8,
[FW_PTP_PTP_PIN_INTR_ENA_INTR_PTP_ENA] = 8,
[FW_PTP_PTP_INTR_IDENT_INTR_PTP_IDENT] = 8,
[FW_PTP_PTP_PIN_CFG_PTP_PIN_SELECT] = 3,
[FW_QFWD_FRAME_COPY_CFG_FRMC_PORT_VAL] = 6,
[FW_QRES_RES_CFG_WM_HIGH] = 11,
[FW_QRES_RES_STAT_MAXUSE] = 19,
[FW_QRES_RES_STAT_CUR_INUSE] = 19,
[FW_QSYS_PAUSE_CFG_PAUSE_START] = 11,
[FW_QSYS_PAUSE_CFG_PAUSE_STOP] = 11,
[FW_QSYS_ATOP_ATOP] = 11,
[FW_QSYS_ATOP_TOT_CFG_ATOP_TOT] = 11,
[FW_REW_RTAG_ETAG_CTRL_IPE_TBL] = 6,
[FW_XQS_STAT_CFG_STAT_VIEW] = 10,
[FW_XQS_QLIMIT_SHR_TOP_CFG_QLIMIT_SHR_TOP] = 14,
[FW_XQS_QLIMIT_SHR_ATOP_CFG_QLIMIT_SHR_ATOP] = 14,
[FW_XQS_QLIMIT_SHR_CTOP_CFG_QLIMIT_SHR_CTOP] = 14,
[FW_XQS_QLIMIT_SHR_QLIM_CFG_QLIMIT_SHR_QLIM] = 14,
};
@@ -0,0 +1,224 @@
// SPDX-License-Identifier: GPL-2.0+
/* Microchip lan969x Switch driver
*
* Copyright (c) 2024 Microchip Technology Inc. and its subsidiaries.
*/
#include "lan969x.h"
/* Tx clock selectors */
#define LAN969X_RGMII_TX_CLK_SEL_125MHZ 1 /* 1000Mbps */
#define LAN969X_RGMII_TX_CLK_SEL_25MHZ 2 /* 100Mbps */
#define LAN969X_RGMII_TX_CLK_SEL_2M5MHZ 3 /* 10Mbps */
/* Port speed selectors */
#define LAN969X_RGMII_SPEED_SEL_10 0 /* Select 10Mbps speed */
#define LAN969X_RGMII_SPEED_SEL_100 1 /* Select 100Mbps speed */
#define LAN969X_RGMII_SPEED_SEL_1000 2 /* Select 1000Mbps speed */
/* Clock delay selectors */
#define LAN969X_RGMII_CLK_DELAY_SEL_1_0_NS 2 /* Phase shift 45deg */
#define LAN969X_RGMII_CLK_DELAY_SEL_1_7_NS 3 /* Phase shift 77deg */
#define LAN969X_RGMII_CLK_DELAY_SEL_2_0_NS 4 /* Phase shift 90deg */
#define LAN969X_RGMII_CLK_DELAY_SEL_2_5_NS 5 /* Phase shift 112deg */
#define LAN969X_RGMII_CLK_DELAY_SEL_3_0_NS 6 /* Phase shift 135deg */
#define LAN969X_RGMII_CLK_DELAY_SEL_3_3_NS 7 /* Phase shift 147deg */
#define LAN969X_RGMII_PORT_START_IDX 28 /* Index of the first RGMII port */
#define LAN969X_RGMII_IFG_TX 4 /* TX Inter Frame Gap value */
#define LAN969X_RGMII_IFG_RX1 5 /* RX1 Inter Frame Gap value */
#define LAN969X_RGMII_IFG_RX2 1 /* RX2 Inter Frame Gap value */
#define RGMII_PORT_IDX(port) ((port)->portno - LAN969X_RGMII_PORT_START_IDX)
/* Get the tx clock selector based on the port speed. */
static int lan969x_rgmii_get_clk_sel(int speed)
{
return (speed == SPEED_10 ? LAN969X_RGMII_TX_CLK_SEL_2M5MHZ :
speed == SPEED_100 ? LAN969X_RGMII_TX_CLK_SEL_25MHZ :
LAN969X_RGMII_TX_CLK_SEL_125MHZ);
}
/* Get the port speed selector based on the port speed. */
static int lan969x_rgmii_get_speed_sel(int speed)
{
return (speed == SPEED_10 ? LAN969X_RGMII_SPEED_SEL_10 :
speed == SPEED_100 ? LAN969X_RGMII_SPEED_SEL_100 :
LAN969X_RGMII_SPEED_SEL_1000);
}
/* Get the clock delay selector based on the clock delay in picoseconds. */
static int lan969x_rgmii_get_clk_delay_sel(struct sparx5_port *port,
u32 delay_ps, u32 *clk_delay_sel)
{
switch (delay_ps) {
case 0:
/* Hardware default selector. */
*clk_delay_sel = LAN969X_RGMII_CLK_DELAY_SEL_2_5_NS;
break;
case 1000:
*clk_delay_sel = LAN969X_RGMII_CLK_DELAY_SEL_1_0_NS;
break;
case 1700:
*clk_delay_sel = LAN969X_RGMII_CLK_DELAY_SEL_1_7_NS;
break;
case 2000:
*clk_delay_sel = LAN969X_RGMII_CLK_DELAY_SEL_2_0_NS;
break;
case 2500:
*clk_delay_sel = LAN969X_RGMII_CLK_DELAY_SEL_2_5_NS;
break;
case 3000:
*clk_delay_sel = LAN969X_RGMII_CLK_DELAY_SEL_3_0_NS;
break;
case 3300:
*clk_delay_sel = LAN969X_RGMII_CLK_DELAY_SEL_3_3_NS;
break;
default:
dev_err(port->sparx5->dev, "Invalid RGMII delay: %u", delay_ps);
return -EINVAL;
}
return 0;
}
/* Configure the RGMII tx clock frequency. */
static void lan969x_rgmii_tx_clk_config(struct sparx5_port *port,
struct sparx5_port_config *conf)
{
u32 clk_sel = lan969x_rgmii_get_clk_sel(conf->speed);
u32 idx = RGMII_PORT_IDX(port);
/* Take the RGMII clock domain out of reset and set tx clock
* frequency.
*/
spx5_rmw(HSIO_WRAP_RGMII_CFG_TX_CLK_CFG_SET(clk_sel) |
HSIO_WRAP_RGMII_CFG_RGMII_TX_RST_SET(0) |
HSIO_WRAP_RGMII_CFG_RGMII_RX_RST_SET(0),
HSIO_WRAP_RGMII_CFG_TX_CLK_CFG |
HSIO_WRAP_RGMII_CFG_RGMII_TX_RST |
HSIO_WRAP_RGMII_CFG_RGMII_RX_RST,
port->sparx5, HSIO_WRAP_RGMII_CFG(idx));
}
/* Configure the RGMII port device. */
static void lan969x_rgmii_port_device_config(struct sparx5_port *port,
struct sparx5_port_config *conf)
{
u32 dtag, dotag, etype, speed_sel, idx = RGMII_PORT_IDX(port);
speed_sel = lan969x_rgmii_get_speed_sel(conf->speed);
etype = (port->vlan_type == SPX5_VLAN_PORT_TYPE_S_CUSTOM ?
port->custom_etype :
port->vlan_type == SPX5_VLAN_PORT_TYPE_C ?
ETH_P_8021Q : ETH_P_8021AD);
dtag = port->max_vlan_tags == SPX5_PORT_MAX_TAGS_TWO;
dotag = port->max_vlan_tags != SPX5_PORT_MAX_TAGS_NONE;
/* Enable the MAC. */
spx5_wr(DEVRGMII_MAC_ENA_CFG_RX_ENA_SET(1) |
DEVRGMII_MAC_ENA_CFG_TX_ENA_SET(1),
port->sparx5, DEVRGMII_MAC_ENA_CFG(idx));
/* Configure the Inter Frame Gap. */
spx5_wr(DEVRGMII_MAC_IFG_CFG_TX_IFG_SET(LAN969X_RGMII_IFG_TX) |
DEVRGMII_MAC_IFG_CFG_RX_IFG1_SET(LAN969X_RGMII_IFG_RX1) |
DEVRGMII_MAC_IFG_CFG_RX_IFG2_SET(LAN969X_RGMII_IFG_RX2),
port->sparx5, DEVRGMII_MAC_IFG_CFG(idx));
/* Configure port data rate. */
spx5_wr(DEVRGMII_DEV_RST_CTRL_SPEED_SEL_SET(speed_sel),
port->sparx5, DEVRGMII_DEV_RST_CTRL(idx));
/* Configure VLAN awareness. */
spx5_wr(DEVRGMII_MAC_TAGS_CFG_TAG_ID_SET(etype) |
DEVRGMII_MAC_TAGS_CFG_PB_ENA_SET(dtag) |
DEVRGMII_MAC_TAGS_CFG_VLAN_AWR_ENA_SET(dotag) |
DEVRGMII_MAC_TAGS_CFG_VLAN_LEN_AWR_ENA_SET(dotag),
port->sparx5,
DEVRGMII_MAC_TAGS_CFG(idx));
}
/* Configure the RGMII delay lines in the MAC.
*
* We use the rx-internal-delay-ps" and "tx-internal-delay-ps" properties to
* configure the rx and tx delays for the MAC. If these properties are missing
* or set to zero, the MAC will not apply any delay.
*
* The PHY side delays are determined by the PHY mode
* (e.g. PHY_INTERFACE_MODE_RGMII_{ID, RXID, TXID}), and ignored by the MAC side
* entirely.
*/
static int lan969x_rgmii_delay_config(struct sparx5_port *port,
struct sparx5_port_config *conf)
{
u32 tx_clk_sel, rx_clk_sel, tx_delay_ps = 0, rx_delay_ps = 0;
u32 idx = RGMII_PORT_IDX(port);
int err;
of_property_read_u32(port->of_node, "rx-internal-delay-ps",
&rx_delay_ps);
of_property_read_u32(port->of_node, "tx-internal-delay-ps",
&tx_delay_ps);
err = lan969x_rgmii_get_clk_delay_sel(port, rx_delay_ps, &rx_clk_sel);
if (err)
return err;
err = lan969x_rgmii_get_clk_delay_sel(port, tx_delay_ps, &tx_clk_sel);
if (err)
return err;
/* Configure rx delay. */
spx5_rmw(HSIO_WRAP_DLL_CFG_DLL_RST_SET(0) |
HSIO_WRAP_DLL_CFG_DLL_ENA_SET(1) |
HSIO_WRAP_DLL_CFG_DLL_CLK_ENA_SET(!!rx_delay_ps) |
HSIO_WRAP_DLL_CFG_DLL_CLK_SEL_SET(rx_clk_sel),
HSIO_WRAP_DLL_CFG_DLL_RST |
HSIO_WRAP_DLL_CFG_DLL_ENA |
HSIO_WRAP_DLL_CFG_DLL_CLK_ENA |
HSIO_WRAP_DLL_CFG_DLL_CLK_SEL,
port->sparx5, HSIO_WRAP_DLL_CFG(idx, 0));
/* Configure tx delay. */
spx5_rmw(HSIO_WRAP_DLL_CFG_DLL_RST_SET(0) |
HSIO_WRAP_DLL_CFG_DLL_ENA_SET(1) |
HSIO_WRAP_DLL_CFG_DLL_CLK_ENA_SET(!!tx_delay_ps) |
HSIO_WRAP_DLL_CFG_DLL_CLK_SEL_SET(tx_clk_sel),
HSIO_WRAP_DLL_CFG_DLL_RST |
HSIO_WRAP_DLL_CFG_DLL_ENA |
HSIO_WRAP_DLL_CFG_DLL_CLK_ENA |
HSIO_WRAP_DLL_CFG_DLL_CLK_SEL,
port->sparx5, HSIO_WRAP_DLL_CFG(idx, 1));
return 0;
}
/* Configure GPIO's to be used as RGMII interface. */
static void lan969x_rgmii_gpio_config(struct sparx5_port *port)
{
u32 idx = RGMII_PORT_IDX(port);
/* Enable the RGMII on the GPIOs. */
spx5_wr(HSIO_WRAP_XMII_CFG_GPIO_XMII_CFG_SET(1), port->sparx5,
HSIO_WRAP_XMII_CFG(!idx));
}
int lan969x_port_config_rgmii(struct sparx5_port *port,
struct sparx5_port_config *conf)
{
int err;
err = lan969x_rgmii_delay_config(port, conf);
if (err)
return err;
lan969x_rgmii_tx_clk_config(port, conf);
lan969x_rgmii_gpio_config(port);
lan969x_rgmii_port_device_config(port, conf);
return 0;
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,85 @@
// SPDX-License-Identifier: GPL-2.0+
#include "vcap_api.h"
#include "lan969x.h"
const struct sparx5_vcap_inst lan969x_vcap_inst_cfg[] = {
{
.vtype = VCAP_TYPE_IS0, /* CLM-0 */
.vinst = 0,
.map_id = 1,
.lookups = SPARX5_IS0_LOOKUPS,
.lookups_per_instance = SPARX5_IS0_LOOKUPS / 3,
.first_cid = SPARX5_VCAP_CID_IS0_L0,
.last_cid = SPARX5_VCAP_CID_IS0_L2 - 1,
.blockno = 2,
.blocks = 1,
.ingress = true,
},
{
.vtype = VCAP_TYPE_IS0, /* CLM-1 */
.vinst = 1,
.map_id = 2,
.lookups = SPARX5_IS0_LOOKUPS,
.lookups_per_instance = SPARX5_IS0_LOOKUPS / 3,
.first_cid = SPARX5_VCAP_CID_IS0_L2,
.last_cid = SPARX5_VCAP_CID_IS0_L4 - 1,
.blockno = 3,
.blocks = 1,
.ingress = true,
},
{
.vtype = VCAP_TYPE_IS0, /* CLM-2 */
.vinst = 2,
.map_id = 3,
.lookups = SPARX5_IS0_LOOKUPS,
.lookups_per_instance = SPARX5_IS0_LOOKUPS / 3,
.first_cid = SPARX5_VCAP_CID_IS0_L4,
.last_cid = SPARX5_VCAP_CID_IS0_MAX,
.blockno = 4,
.blocks = 1,
.ingress = true,
},
{
.vtype = VCAP_TYPE_IS2, /* IS2-0 */
.vinst = 0,
.map_id = 4,
.lookups = SPARX5_IS2_LOOKUPS,
.lookups_per_instance = SPARX5_IS2_LOOKUPS / 2,
.first_cid = SPARX5_VCAP_CID_IS2_L0,
.last_cid = SPARX5_VCAP_CID_IS2_L2 - 1,
.blockno = 0,
.blocks = 1,
.ingress = true,
},
{
.vtype = VCAP_TYPE_IS2, /* IS2-1 */
.vinst = 1,
.map_id = 5,
.lookups = SPARX5_IS2_LOOKUPS,
.lookups_per_instance = SPARX5_IS2_LOOKUPS / 2,
.first_cid = SPARX5_VCAP_CID_IS2_L2,
.last_cid = SPARX5_VCAP_CID_IS2_MAX,
.blockno = 1,
.blocks = 1,
.ingress = true,
},
{
.vtype = VCAP_TYPE_ES0,
.lookups = SPARX5_ES0_LOOKUPS,
.lookups_per_instance = SPARX5_ES0_LOOKUPS,
.first_cid = SPARX5_VCAP_CID_ES0_L0,
.last_cid = SPARX5_VCAP_CID_ES0_MAX,
.count = 1536,
.ingress = false,
},
{
.vtype = VCAP_TYPE_ES2,
.lookups = SPARX5_ES2_LOOKUPS,
.lookups_per_instance = SPARX5_ES2_LOOKUPS,
.first_cid = SPARX5_VCAP_CID_ES2_L0,
.last_cid = SPARX5_VCAP_CID_ES2_MAX,
.count = 1024,
.ingress = false,
},
};
@@ -0,0 +1,623 @@
// 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_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},
};
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;
case SPX5_TARGET_CT_LAN9691VAO:
return 46000;
case SPX5_TARGET_CT_LAN9694RED:
case SPX5_TARGET_CT_LAN9694TSN:
case SPX5_TARGET_CT_LAN9694:
return 68000;
case SPX5_TARGET_CT_LAN9696RED:
case SPX5_TARGET_CT_LAN9696TSN:
case SPX5_TARGET_CT_LAN9692VAO:
case SPX5_TARGET_CT_LAN9696:
return 88000;
case SPX5_TARGET_CT_LAN9698RED:
case SPX5_TARGET_CT_LAN9698TSN:
case SPX5_TARGET_CT_LAN9693VAO:
case SPX5_TARGET_CT_LAN9698:
return 101000;
default:
return 0;
}
}
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_328MHZ: return 109375; /* 328000 / 3 */
case SPX5_CORE_CLOCK_500MHZ: return 166000; /* 500000 / 3 */
case SPX5_CORE_CLOCK_625MHZ: return 208000; /* 625000 / 3 */
default: return 0;
}
return 0;
}
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;
}
}
enum sparx5_cal_bw sparx5_get_port_cal_speed(struct sparx5 *sparx5, u32 portno)
{
struct sparx5_port *port;
if (portno >= sparx5->data->consts->n_ports) {
/* Internal ports */
if (portno ==
sparx5_get_internal_port(sparx5, SPX5_PORT_CPU_0) ||
portno ==
sparx5_get_internal_port(sparx5, SPX5_PORT_CPU_1)) {
/* Equals 1.25G */
return SPX5_CAL_SPEED_2G5;
} else if (portno ==
sparx5_get_internal_port(sparx5, SPX5_PORT_VD0)) {
/* IPMC only idle BW */
return SPX5_CAL_SPEED_NONE;
} else if (portno ==
sparx5_get_internal_port(sparx5, SPX5_PORT_VD1)) {
/* OAM only idle BW */
return SPX5_CAL_SPEED_NONE;
} else if (portno ==
sparx5_get_internal_port(sparx5, 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 */
static int sparx5_config_auto_calendar(struct sparx5 *sparx5)
{
const struct sparx5_consts *consts = sparx5->data->consts;
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 < consts->n_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 < consts->n_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 */
if (is_sparx5(sparx5))
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 < consts->n_auto_cals; 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;
}
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 < sparx5->data->consts->n_ports_all) {
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 cal_len = sparx5_dsm_cal_len(data->schedule), len, idx;
if (!is_sparx5(sparx5)) {
u32 val, act;
val = spx5_rd(sparx5, DSM_TAXI_CAL_CFG(taxi));
act = DSM_TAXI_CAL_CFG_CAL_SEL_STAT_GET(val);
spx5_rmw(DSM_TAXI_CAL_CFG_CAL_PGM_SEL_SET(!act),
DSM_TAXI_CAL_CFG_CAL_PGM_SEL,
sparx5, DSM_TAXI_CAL_CFG(taxi));
}
spx5_rmw(DSM_TAXI_CAL_CFG_CAL_PGM_ENA_SET(1),
DSM_TAXI_CAL_CFG_CAL_PGM_ENA,
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_rmw(DSM_TAXI_CAL_CFG_CAL_PGM_ENA_SET(0),
DSM_TAXI_CAL_CFG_CAL_PGM_ENA,
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;
if (!is_sparx5(sparx5)) {
spx5_rmw(DSM_TAXI_CAL_CFG_CAL_SWITCH_SET(1),
DSM_TAXI_CAL_CFG_CAL_SWITCH,
sparx5, DSM_TAXI_CAL_CFG(taxi));
}
return 0;
update_err:
dev_err(sparx5->dev, "Incorrect calendar length: %u\n", len);
return -EINVAL;
}
/* Configure the DSM calendar based on port configuration */
static int sparx5_config_dsm_calendar(struct sparx5 *sparx5)
{
const struct sparx5_ops *ops = sparx5->data->ops;
int taxi;
struct sparx5_calendar_data *data;
int err = 0;
data = kzalloc_obj(*data);
if (!data)
return -ENOMEM;
for (taxi = 0; taxi < sparx5->data->consts->n_dsm_cal_taxis; ++taxi) {
err = ops->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;
}
int sparx5_calendar_init(struct sparx5 *sparx5)
{
int err;
err = sparx5_config_auto_calendar(sparx5);
if (err)
return err;
return sparx5_config_dsm_calendar(sparx5);
}
@@ -0,0 +1,408 @@
// SPDX-License-Identifier: GPL-2.0+
/* Microchip Sparx5 Switch driver
*
* Copyright (c) 2022 Microchip Technology Inc. and its subsidiaries.
*/
#include <net/dcbnl.h>
#include "sparx5_port.h"
enum sparx5_dcb_apptrust_values {
SPARX5_DCB_APPTRUST_EMPTY,
SPARX5_DCB_APPTRUST_DSCP,
SPARX5_DCB_APPTRUST_PCP,
SPARX5_DCB_APPTRUST_DSCP_PCP,
__SPARX5_DCB_APPTRUST_MAX
};
static const struct sparx5_dcb_apptrust {
u8 selectors[IEEE_8021QAZ_APP_SEL_MAX + 1];
int nselectors;
} *sparx5_port_apptrust[SPX5_PORTS];
static const char *sparx5_dcb_apptrust_names[__SPARX5_DCB_APPTRUST_MAX] = {
[SPARX5_DCB_APPTRUST_EMPTY] = "empty",
[SPARX5_DCB_APPTRUST_DSCP] = "dscp",
[SPARX5_DCB_APPTRUST_PCP] = "pcp",
[SPARX5_DCB_APPTRUST_DSCP_PCP] = "dscp pcp"
};
/* Sparx5 supported apptrust policies */
static const struct sparx5_dcb_apptrust
sparx5_dcb_apptrust_policies[__SPARX5_DCB_APPTRUST_MAX] = {
/* Empty *must* be first */
[SPARX5_DCB_APPTRUST_EMPTY] = { { 0 }, 0 },
[SPARX5_DCB_APPTRUST_DSCP] = { { IEEE_8021QAZ_APP_SEL_DSCP }, 1 },
[SPARX5_DCB_APPTRUST_PCP] = { { DCB_APP_SEL_PCP }, 1 },
[SPARX5_DCB_APPTRUST_DSCP_PCP] = { { IEEE_8021QAZ_APP_SEL_DSCP,
DCB_APP_SEL_PCP }, 2 },
};
/* Validate app entry.
*
* Check for valid selectors and valid protocol and priority ranges.
*/
static int sparx5_dcb_app_validate(struct net_device *dev,
const struct dcb_app *app)
{
int err = 0;
switch (app->selector) {
/* Default priority checks */
case IEEE_8021QAZ_APP_SEL_ETHERTYPE:
if (app->protocol != 0)
err = -EINVAL;
else if (app->priority >= SPX5_PRIOS)
err = -ERANGE;
break;
/* Dscp checks */
case IEEE_8021QAZ_APP_SEL_DSCP:
if (app->protocol >= SPARX5_PORT_QOS_DSCP_COUNT)
err = -EINVAL;
else if (app->priority >= SPX5_PRIOS)
err = -ERANGE;
break;
/* Pcp checks */
case DCB_APP_SEL_PCP:
if (app->protocol >= SPARX5_PORT_QOS_PCP_DEI_COUNT)
err = -EINVAL;
else if (app->priority >= SPX5_PRIOS)
err = -ERANGE;
break;
default:
err = -EINVAL;
break;
}
if (err)
netdev_err(dev, "Invalid entry: %d:%d\n", app->protocol,
app->priority);
return err;
}
/* Validate apptrust configuration.
*
* Return index of supported apptrust configuration if valid, otherwise return
* error.
*/
static int sparx5_dcb_apptrust_validate(struct net_device *dev, u8 *selectors,
int nselectors, int *err)
{
bool match = false;
int i, ii;
for (i = 0; i < ARRAY_SIZE(sparx5_dcb_apptrust_policies); i++) {
if (sparx5_dcb_apptrust_policies[i].nselectors != nselectors)
continue;
match = true;
for (ii = 0; ii < nselectors; ii++) {
if (sparx5_dcb_apptrust_policies[i].selectors[ii] !=
*(selectors + ii)) {
match = false;
break;
}
}
if (match)
break;
}
/* Requested trust configuration is not supported */
if (!match) {
netdev_err(dev, "Valid apptrust configurations are:\n");
for (i = 0; i < ARRAY_SIZE(sparx5_dcb_apptrust_names); i++)
pr_info("order: %s\n", sparx5_dcb_apptrust_names[i]);
*err = -EOPNOTSUPP;
}
return i;
}
static bool sparx5_dcb_apptrust_contains(int portno, u8 selector)
{
const struct sparx5_dcb_apptrust *conf = sparx5_port_apptrust[portno];
int i;
for (i = 0; i < conf->nselectors; i++)
if (conf->selectors[i] == selector)
return true;
return false;
}
static int sparx5_dcb_app_update(struct net_device *dev)
{
struct dcb_ieee_app_prio_map dscp_rewr_map = {0};
struct dcb_rewr_prio_pcp_map pcp_rewr_map = {0};
struct sparx5_port *port = netdev_priv(dev);
struct sparx5_port_qos_dscp_map *dscp_map;
struct sparx5_port_qos_pcp_map *pcp_map;
struct sparx5_port_qos qos = {0};
struct dcb_app app_itr = {0};
int portno = port->portno;
bool dscp_rewr = false;
bool pcp_rewr = false;
u16 dscp;
int i;
dscp_map = &qos.dscp.map;
pcp_map = &qos.pcp.map;
/* Get default prio. */
qos.default_prio = dcb_ieee_getapp_default_prio_mask(dev);
if (qos.default_prio)
qos.default_prio = fls(qos.default_prio) - 1;
/* Get dscp ingress mapping */
for (i = 0; i < ARRAY_SIZE(dscp_map->map); i++) {
app_itr.selector = IEEE_8021QAZ_APP_SEL_DSCP;
app_itr.protocol = i;
dscp_map->map[i] = dcb_getapp(dev, &app_itr);
}
/* Get pcp ingress mapping */
for (i = 0; i < ARRAY_SIZE(pcp_map->map); i++) {
app_itr.selector = DCB_APP_SEL_PCP;
app_itr.protocol = i;
pcp_map->map[i] = dcb_getapp(dev, &app_itr);
}
/* Get pcp rewrite mapping */
dcb_getrewr_prio_pcp_mask_map(dev, &pcp_rewr_map);
for (i = 0; i < ARRAY_SIZE(pcp_rewr_map.map); i++) {
if (!pcp_rewr_map.map[i])
continue;
pcp_rewr = true;
qos.pcp_rewr.map.map[i] = fls(pcp_rewr_map.map[i]) - 1;
}
/* Get dscp rewrite mapping */
dcb_getrewr_prio_dscp_mask_map(dev, &dscp_rewr_map);
for (i = 0; i < ARRAY_SIZE(dscp_rewr_map.map); i++) {
if (!dscp_rewr_map.map[i])
continue;
/* The rewrite table of the switch has 32 entries; one for each
* priority for each DP level. Currently, the rewrite map does
* not indicate DP level, so we map classified QoS class to
* classified DSCP, for each classified DP level. Rewrite of
* DSCP is only enabled, if we have active mappings.
*/
dscp_rewr = true;
dscp = fls64(dscp_rewr_map.map[i]) - 1;
qos.dscp_rewr.map.map[i] = dscp; /* DP 0 */
qos.dscp_rewr.map.map[i + 8] = dscp; /* DP 1 */
qos.dscp_rewr.map.map[i + 16] = dscp; /* DP 2 */
qos.dscp_rewr.map.map[i + 24] = dscp; /* DP 3 */
}
/* Enable use of pcp for queue classification ? */
if (sparx5_dcb_apptrust_contains(portno, DCB_APP_SEL_PCP)) {
qos.pcp.qos_enable = true;
qos.pcp.dp_enable = qos.pcp.qos_enable;
/* Enable rewrite of PCP and DEI if PCP is trusted *and* rewrite
* table is not empty.
*/
if (pcp_rewr)
qos.pcp_rewr.enable = true;
}
/* Enable use of dscp for queue classification ? */
if (sparx5_dcb_apptrust_contains(portno, IEEE_8021QAZ_APP_SEL_DSCP)) {
qos.dscp.qos_enable = true;
qos.dscp.dp_enable = qos.dscp.qos_enable;
if (dscp_rewr)
/* Do not enable rewrite if no mappings are active, as
* classified DSCP will then be zero for all classified
* QoS class and DP combinations.
*/
qos.dscp_rewr.enable = true;
}
return sparx5_port_qos_set(port, &qos);
}
/* Set or delete DSCP app entry.
*
* DSCP mapping is global for all ports, so set and delete app entries are
* replicated for each port.
*/
static int sparx5_dcb_ieee_dscp_setdel(struct net_device *dev,
struct dcb_app *app,
int (*setdel)(struct net_device *,
struct dcb_app *))
{
struct sparx5_port *port = netdev_priv(dev);
struct sparx5 *sparx5 = port->sparx5;
struct sparx5_port *port_itr;
int err, i;
for (i = 0; i < sparx5->data->consts->n_ports; i++) {
port_itr = port->sparx5->ports[i];
if (!port_itr)
continue;
err = setdel(port_itr->ndev, app);
if (err)
return err;
}
return 0;
}
static int sparx5_dcb_ieee_delapp(struct net_device *dev, struct dcb_app *app)
{
int err;
if (app->selector == IEEE_8021QAZ_APP_SEL_DSCP)
err = sparx5_dcb_ieee_dscp_setdel(dev, app, dcb_ieee_delapp);
else
err = dcb_ieee_delapp(dev, app);
if (err < 0)
return err;
return sparx5_dcb_app_update(dev);
}
static int sparx5_dcb_ieee_setapp(struct net_device *dev, struct dcb_app *app)
{
struct dcb_app app_itr;
int err = 0;
u8 prio;
err = sparx5_dcb_app_validate(dev, app);
if (err)
goto out;
/* Delete current mapping, if it exists */
prio = dcb_getapp(dev, app);
if (prio) {
app_itr = *app;
app_itr.priority = prio;
sparx5_dcb_ieee_delapp(dev, &app_itr);
}
if (app->selector == IEEE_8021QAZ_APP_SEL_DSCP)
err = sparx5_dcb_ieee_dscp_setdel(dev, app, dcb_ieee_setapp);
else
err = dcb_ieee_setapp(dev, app);
if (err)
goto out;
sparx5_dcb_app_update(dev);
out:
return err;
}
static int sparx5_dcb_setapptrust(struct net_device *dev, u8 *selectors,
int nselectors)
{
struct sparx5_port *port = netdev_priv(dev);
int err = 0, idx;
idx = sparx5_dcb_apptrust_validate(dev, selectors, nselectors, &err);
if (err < 0)
return err;
sparx5_port_apptrust[port->portno] = &sparx5_dcb_apptrust_policies[idx];
return sparx5_dcb_app_update(dev);
}
static int sparx5_dcb_getapptrust(struct net_device *dev, u8 *selectors,
int *nselectors)
{
struct sparx5_port *port = netdev_priv(dev);
const struct sparx5_dcb_apptrust *trust;
trust = sparx5_port_apptrust[port->portno];
memcpy(selectors, trust->selectors, trust->nselectors);
*nselectors = trust->nselectors;
return 0;
}
static int sparx5_dcb_delrewr(struct net_device *dev, struct dcb_app *app)
{
int err;
if (app->selector == IEEE_8021QAZ_APP_SEL_DSCP)
err = sparx5_dcb_ieee_dscp_setdel(dev, app, dcb_delrewr);
else
err = dcb_delrewr(dev, app);
if (err < 0)
return err;
return sparx5_dcb_app_update(dev);
}
static int sparx5_dcb_setrewr(struct net_device *dev, struct dcb_app *app)
{
struct dcb_app app_itr;
int err = 0;
u16 proto;
err = sparx5_dcb_app_validate(dev, app);
if (err)
goto out;
/* Delete current mapping, if it exists. */
proto = dcb_getrewr(dev, app);
if (proto) {
app_itr = *app;
app_itr.protocol = proto;
sparx5_dcb_delrewr(dev, &app_itr);
}
if (app->selector == IEEE_8021QAZ_APP_SEL_DSCP)
err = sparx5_dcb_ieee_dscp_setdel(dev, app, dcb_setrewr);
else
err = dcb_setrewr(dev, app);
if (err)
goto out;
sparx5_dcb_app_update(dev);
out:
return err;
}
const struct dcbnl_rtnl_ops sparx5_dcbnl_ops = {
.ieee_setapp = sparx5_dcb_ieee_setapp,
.ieee_delapp = sparx5_dcb_ieee_delapp,
.dcbnl_setapptrust = sparx5_dcb_setapptrust,
.dcbnl_getapptrust = sparx5_dcb_getapptrust,
.dcbnl_setrewr = sparx5_dcb_setrewr,
.dcbnl_delrewr = sparx5_dcb_delrewr,
};
int sparx5_dcb_init(struct sparx5 *sparx5)
{
struct sparx5_port *port;
int i;
for (i = 0; i < sparx5->data->consts->n_ports; i++) {
port = sparx5->ports[i];
if (!port)
continue;
port->ndev->dcbnl_ops = &sparx5_dcbnl_ops;
/* Initialize [dscp, pcp] default trust */
sparx5_port_apptrust[port->portno] =
&sparx5_dcb_apptrust_policies
[SPARX5_DCB_APPTRUST_DSCP_PCP];
/* Enable DSCP classification based on classified QoS class and
* DP, for all DSCP values, for all ports.
*/
sparx5_port_qos_dscp_rewr_mode_set(port,
SPARX5_PORT_REW_DSCP_ALL);
}
return 0;
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,486 @@
// SPDX-License-Identifier: GPL-2.0+
/* Microchip Sparx5 Switch driver
*
* Copyright (c) 2021 Microchip Technology Inc. and its subsidiaries.
*
* The Sparx5 Chip Register Model can be browsed at this location:
* https://github.com/microchip-ung/sparx-5_reginfo
*/
#include <linux/types.h>
#include <linux/skbuff.h>
#include <linux/netdevice.h>
#include <linux/interrupt.h>
#include <linux/ip.h>
#include <linux/dma-mapping.h>
#include "sparx5_main_regs.h"
#include "sparx5_main.h"
#include "sparx5_port.h"
#define FDMA_XTR_BUFFER_SIZE 2048
#define FDMA_WEIGHT 4
static int sparx5_fdma_tx_dataptr_cb(struct fdma *fdma, int dcb, int db,
u64 *dataptr)
{
*dataptr = fdma->dma + (sizeof(struct fdma_dcb) * fdma->n_dcbs) +
((dcb * fdma->n_dbs + db) * fdma->db_size);
return 0;
}
static int sparx5_fdma_rx_dataptr_cb(struct fdma *fdma, int dcb, int db,
u64 *dataptr)
{
struct sparx5 *sparx5 = fdma->priv;
struct sparx5_rx *rx = &sparx5->rx;
struct sk_buff *skb;
skb = __netdev_alloc_skb(rx->ndev, fdma->db_size, GFP_ATOMIC);
if (unlikely(!skb))
return -ENOMEM;
*dataptr = virt_to_phys(skb->data);
rx->skb[dcb][db] = skb;
return 0;
}
static void sparx5_fdma_rx_activate(struct sparx5 *sparx5, struct sparx5_rx *rx)
{
struct fdma *fdma = &rx->fdma;
/* Write the buffer address in the LLP and LLP1 regs */
spx5_wr(((u64)fdma->dma) & GENMASK(31, 0), sparx5,
FDMA_DCB_LLP(fdma->channel_id));
spx5_wr(((u64)fdma->dma) >> 32, sparx5,
FDMA_DCB_LLP1(fdma->channel_id));
/* Set the number of RX DBs to be used, and DB end-of-frame interrupt */
spx5_wr(FDMA_CH_CFG_CH_DCB_DB_CNT_SET(fdma->n_dbs) |
FDMA_CH_CFG_CH_INTR_DB_EOF_ONLY_SET(1) |
FDMA_CH_CFG_CH_INJ_PORT_SET(XTR_QUEUE),
sparx5, FDMA_CH_CFG(fdma->channel_id));
/* Set the RX Watermark to max */
spx5_rmw(FDMA_XTR_CFG_XTR_FIFO_WM_SET(31), FDMA_XTR_CFG_XTR_FIFO_WM,
sparx5,
FDMA_XTR_CFG);
/* Start RX fdma */
spx5_rmw(FDMA_PORT_CTRL_XTR_STOP_SET(0), FDMA_PORT_CTRL_XTR_STOP,
sparx5, FDMA_PORT_CTRL(0));
/* Enable RX channel DB interrupt */
spx5_rmw(BIT(fdma->channel_id),
BIT(fdma->channel_id) & FDMA_INTR_DB_ENA_INTR_DB_ENA,
sparx5, FDMA_INTR_DB_ENA);
/* Activate the RX channel */
spx5_wr(BIT(fdma->channel_id), sparx5, FDMA_CH_ACTIVATE);
}
static void sparx5_fdma_rx_deactivate(struct sparx5 *sparx5, struct sparx5_rx *rx)
{
struct fdma *fdma = &rx->fdma;
/* Deactivate the RX channel */
spx5_rmw(0, BIT(fdma->channel_id) & FDMA_CH_ACTIVATE_CH_ACTIVATE,
sparx5, FDMA_CH_ACTIVATE);
/* Disable RX channel DB interrupt */
spx5_rmw(0, BIT(fdma->channel_id) & FDMA_INTR_DB_ENA_INTR_DB_ENA,
sparx5, FDMA_INTR_DB_ENA);
/* Stop RX fdma */
spx5_rmw(FDMA_PORT_CTRL_XTR_STOP_SET(1), FDMA_PORT_CTRL_XTR_STOP,
sparx5, FDMA_PORT_CTRL(0));
}
static void sparx5_fdma_tx_activate(struct sparx5 *sparx5, struct sparx5_tx *tx)
{
struct fdma *fdma = &tx->fdma;
/* Write the buffer address in the LLP and LLP1 regs */
spx5_wr(((u64)fdma->dma) & GENMASK(31, 0), sparx5,
FDMA_DCB_LLP(fdma->channel_id));
spx5_wr(((u64)fdma->dma) >> 32, sparx5,
FDMA_DCB_LLP1(fdma->channel_id));
/* Set the number of TX DBs to be used, and DB end-of-frame interrupt */
spx5_wr(FDMA_CH_CFG_CH_DCB_DB_CNT_SET(fdma->n_dbs) |
FDMA_CH_CFG_CH_INTR_DB_EOF_ONLY_SET(1) |
FDMA_CH_CFG_CH_INJ_PORT_SET(INJ_QUEUE),
sparx5, FDMA_CH_CFG(fdma->channel_id));
/* Start TX fdma */
spx5_rmw(FDMA_PORT_CTRL_INJ_STOP_SET(0), FDMA_PORT_CTRL_INJ_STOP,
sparx5, FDMA_PORT_CTRL(0));
/* Activate the channel */
spx5_wr(BIT(fdma->channel_id), sparx5, FDMA_CH_ACTIVATE);
}
static void sparx5_fdma_tx_deactivate(struct sparx5 *sparx5, struct sparx5_tx *tx)
{
/* Disable the channel */
spx5_rmw(0, BIT(tx->fdma.channel_id) & FDMA_CH_ACTIVATE_CH_ACTIVATE,
sparx5, FDMA_CH_ACTIVATE);
}
void sparx5_fdma_reload(struct sparx5 *sparx5, struct fdma *fdma)
{
/* Reload the RX channel */
spx5_wr(BIT(fdma->channel_id), sparx5, FDMA_CH_RELOAD);
}
static bool sparx5_fdma_rx_get_frame(struct sparx5 *sparx5, struct sparx5_rx *rx)
{
struct fdma *fdma = &rx->fdma;
struct sparx5_port *port;
struct fdma_db *db_hw;
struct frame_info fi;
struct sk_buff *skb;
/* Check if the DCB is done */
db_hw = fdma_db_next_get(fdma);
if (unlikely(!fdma_db_is_done(db_hw)))
return false;
skb = rx->skb[fdma->dcb_index][fdma->db_index];
skb_put(skb, fdma_db_len_get(db_hw));
/* Now do the normal processing of the skb */
sparx5_ifh_parse(sparx5, (u32 *)skb->data, &fi);
/* Map to port netdev */
port = fi.src_port < sparx5->data->consts->n_ports ?
sparx5->ports[fi.src_port] :
NULL;
if (!port || !port->ndev) {
dev_err(sparx5->dev, "Data on inactive port %d\n", fi.src_port);
sparx5_xtr_flush(sparx5, XTR_QUEUE);
return false;
}
skb->dev = port->ndev;
skb_pull(skb, IFH_LEN * sizeof(u32));
if (likely(!(skb->dev->features & NETIF_F_RXFCS)))
skb_trim(skb, skb->len - ETH_FCS_LEN);
sparx5_ptp_rxtstamp(sparx5, skb, fi.timestamp);
skb->protocol = eth_type_trans(skb, skb->dev);
/* Everything we see on an interface that is in the HW bridge
* has already been forwarded
*/
if (test_bit(port->portno, sparx5->bridge_mask))
skb->offload_fwd_mark = 1;
skb->dev->stats.rx_bytes += skb->len;
skb->dev->stats.rx_packets++;
rx->packets++;
netif_receive_skb(skb);
return true;
}
int sparx5_fdma_napi_callback(struct napi_struct *napi, int weight)
{
struct sparx5_rx *rx = container_of(napi, struct sparx5_rx, napi);
struct sparx5 *sparx5 = container_of(rx, struct sparx5, rx);
struct fdma *fdma = &rx->fdma;
int counter = 0;
while (counter < weight && sparx5_fdma_rx_get_frame(sparx5, rx)) {
fdma_db_advance(fdma);
counter++;
/* Check if the DCB can be reused */
if (fdma_dcb_is_reusable(fdma))
continue;
fdma_dcb_add(fdma, fdma->dcb_index,
FDMA_DCB_INFO_DATAL(fdma->db_size),
FDMA_DCB_STATUS_INTR);
fdma_db_reset(fdma);
fdma_dcb_advance(fdma);
}
if (counter < weight) {
napi_complete_done(&rx->napi, counter);
spx5_rmw(BIT(fdma->channel_id),
BIT(fdma->channel_id) & FDMA_INTR_DB_ENA_INTR_DB_ENA,
sparx5, FDMA_INTR_DB_ENA);
}
if (counter)
sparx5_fdma_reload(sparx5, fdma);
return counter;
}
int sparx5_fdma_xmit(struct sparx5 *sparx5, u32 *ifh, struct sk_buff *skb,
struct net_device *dev)
{
struct sparx5_tx *tx = &sparx5->tx;
struct fdma *fdma = &tx->fdma;
void *virt_addr;
fdma_dcb_advance(fdma);
if (!fdma_db_is_done(fdma_db_get(fdma, fdma->dcb_index, 0)))
return -EINVAL;
/* Get the virtual address of the dataptr for the next DB */
virt_addr = ((u8 *)fdma->dcbs +
(sizeof(struct fdma_dcb) * fdma->n_dcbs) +
((fdma->dcb_index * fdma->n_dbs) * fdma->db_size));
memcpy(virt_addr, ifh, IFH_LEN * 4);
memcpy(virt_addr + IFH_LEN * 4, skb->data, skb->len);
fdma_dcb_add(fdma, fdma->dcb_index, 0,
FDMA_DCB_STATUS_SOF |
FDMA_DCB_STATUS_EOF |
FDMA_DCB_STATUS_BLOCKO(0) |
FDMA_DCB_STATUS_BLOCKL(skb->len + IFH_LEN * 4 + 4));
sparx5_fdma_reload(sparx5, fdma);
return NETDEV_TX_OK;
}
static int sparx5_fdma_rx_alloc(struct sparx5 *sparx5)
{
struct sparx5_rx *rx = &sparx5->rx;
struct fdma *fdma = &rx->fdma;
int err;
err = fdma_alloc_phys(fdma);
if (err)
return err;
fdma_dcbs_init(fdma, FDMA_DCB_INFO_DATAL(fdma->db_size),
FDMA_DCB_STATUS_INTR);
return 0;
}
static int sparx5_fdma_tx_alloc(struct sparx5 *sparx5)
{
struct sparx5_tx *tx = &sparx5->tx;
struct fdma *fdma = &tx->fdma;
int err;
err = fdma_alloc_phys(fdma);
if (err)
return err;
fdma_dcbs_init(fdma, FDMA_DCB_INFO_DATAL(fdma->db_size),
FDMA_DCB_STATUS_DONE);
return 0;
}
static void sparx5_fdma_rx_init(struct sparx5 *sparx5,
struct sparx5_rx *rx, int channel)
{
struct fdma *fdma = &rx->fdma;
int idx;
fdma->channel_id = channel;
fdma->n_dcbs = FDMA_DCB_MAX;
fdma->n_dbs = FDMA_RX_DCB_MAX_DBS;
fdma->priv = sparx5;
fdma->db_size = ALIGN(FDMA_XTR_BUFFER_SIZE, PAGE_SIZE);
fdma->size = fdma_get_size(&sparx5->rx.fdma);
fdma->ops.dataptr_cb = &sparx5_fdma_rx_dataptr_cb;
fdma->ops.nextptr_cb = &fdma_nextptr_cb;
/* Fetch a netdev for SKB and NAPI use, any will do */
for (idx = 0; idx < sparx5->data->consts->n_ports; ++idx) {
struct sparx5_port *port = sparx5->ports[idx];
if (port && port->ndev) {
rx->ndev = port->ndev;
break;
}
}
}
static void sparx5_fdma_tx_init(struct sparx5 *sparx5,
struct sparx5_tx *tx, int channel)
{
struct fdma *fdma = &tx->fdma;
fdma->channel_id = channel;
fdma->n_dcbs = FDMA_DCB_MAX;
fdma->n_dbs = FDMA_TX_DCB_MAX_DBS;
fdma->priv = sparx5;
fdma->db_size = ALIGN(FDMA_XTR_BUFFER_SIZE, PAGE_SIZE);
fdma->size = fdma_get_size_contiguous(&sparx5->tx.fdma);
fdma->ops.dataptr_cb = &sparx5_fdma_tx_dataptr_cb;
fdma->ops.nextptr_cb = &fdma_nextptr_cb;
}
irqreturn_t sparx5_fdma_handler(int irq, void *args)
{
struct sparx5 *sparx5 = args;
u32 db = 0, err = 0;
db = spx5_rd(sparx5, FDMA_INTR_DB);
err = spx5_rd(sparx5, FDMA_INTR_ERR);
/* Clear interrupt */
if (db) {
spx5_wr(0, sparx5, FDMA_INTR_DB_ENA);
spx5_wr(db, sparx5, FDMA_INTR_DB);
napi_schedule(&sparx5->rx.napi);
}
if (err) {
u32 err_type = spx5_rd(sparx5, FDMA_ERRORS);
dev_err_ratelimited(sparx5->dev,
"ERR: int: %#x, type: %#x\n",
err, err_type);
spx5_wr(err, sparx5, FDMA_INTR_ERR);
spx5_wr(err_type, sparx5, FDMA_ERRORS);
}
return IRQ_HANDLED;
}
void sparx5_fdma_injection_mode(struct sparx5 *sparx5)
{
const int byte_swap = 1;
int portno;
int urgency;
/* Change mode to fdma extraction and injection */
spx5_wr(QS_XTR_GRP_CFG_MODE_SET(2) |
QS_XTR_GRP_CFG_STATUS_WORD_POS_SET(1) |
QS_XTR_GRP_CFG_BYTE_SWAP_SET(byte_swap),
sparx5, QS_XTR_GRP_CFG(XTR_QUEUE));
spx5_wr(QS_INJ_GRP_CFG_MODE_SET(2) |
QS_INJ_GRP_CFG_BYTE_SWAP_SET(byte_swap),
sparx5, QS_INJ_GRP_CFG(INJ_QUEUE));
/* CPU ports capture setup */
for (portno = sparx5_get_internal_port(sparx5, SPX5_PORT_CPU_0);
portno <= sparx5_get_internal_port(sparx5, SPX5_PORT_CPU_1);
portno++) {
/* ASM CPU port: No preamble, IFH, enable padding */
spx5_wr(ASM_PORT_CFG_PAD_ENA_SET(1) |
ASM_PORT_CFG_NO_PREAMBLE_ENA_SET(1) |
ASM_PORT_CFG_INJ_FORMAT_CFG_SET(1), /* 1 = IFH */
sparx5, ASM_PORT_CFG(portno));
/* Reset WM cnt to unclog queued frames */
spx5_rmw(DSM_DEV_TX_STOP_WM_CFG_DEV_TX_CNT_CLR_SET(1),
DSM_DEV_TX_STOP_WM_CFG_DEV_TX_CNT_CLR,
sparx5,
DSM_DEV_TX_STOP_WM_CFG(portno));
/* Set Disassembler Stop Watermark level */
spx5_rmw(DSM_DEV_TX_STOP_WM_CFG_DEV_TX_STOP_WM_SET(100),
DSM_DEV_TX_STOP_WM_CFG_DEV_TX_STOP_WM,
sparx5,
DSM_DEV_TX_STOP_WM_CFG(portno));
/* Enable port in queue system */
urgency = sparx5_port_fwd_urg(sparx5, SPEED_2500);
spx5_rmw(QFWD_SWITCH_PORT_MODE_PORT_ENA_SET(1) |
QFWD_SWITCH_PORT_MODE_FWD_URGENCY_SET(urgency),
QFWD_SWITCH_PORT_MODE_PORT_ENA |
QFWD_SWITCH_PORT_MODE_FWD_URGENCY,
sparx5,
QFWD_SWITCH_PORT_MODE(portno));
/* Disable Disassembler buffer underrun watchdog
* to avoid truncated packets in XTR
*/
spx5_rmw(DSM_BUF_CFG_UNDERFLOW_WATCHDOG_DIS_SET(1),
DSM_BUF_CFG_UNDERFLOW_WATCHDOG_DIS,
sparx5,
DSM_BUF_CFG(portno));
/* Disabling frame aging */
spx5_rmw(HSCH_PORT_MODE_AGE_DIS_SET(1),
HSCH_PORT_MODE_AGE_DIS,
sparx5,
HSCH_PORT_MODE(portno));
}
}
int sparx5_fdma_init(struct sparx5 *sparx5)
{
int err;
/* Reset FDMA state */
spx5_wr(FDMA_CTRL_NRESET_SET(0), sparx5, FDMA_CTRL);
spx5_wr(FDMA_CTRL_NRESET_SET(1), sparx5, FDMA_CTRL);
/* Force ACP caching but disable read/write allocation */
spx5_rmw(CPU_PROC_CTRL_ACP_CACHE_FORCE_ENA_SET(1) |
CPU_PROC_CTRL_ACP_AWCACHE_SET(0) |
CPU_PROC_CTRL_ACP_ARCACHE_SET(0),
CPU_PROC_CTRL_ACP_CACHE_FORCE_ENA |
CPU_PROC_CTRL_ACP_AWCACHE |
CPU_PROC_CTRL_ACP_ARCACHE,
sparx5, CPU_PROC_CTRL);
sparx5_fdma_injection_mode(sparx5);
sparx5_fdma_rx_init(sparx5, &sparx5->rx, FDMA_XTR_CHANNEL);
sparx5_fdma_tx_init(sparx5, &sparx5->tx, FDMA_INJ_CHANNEL);
err = sparx5_fdma_rx_alloc(sparx5);
if (err) {
dev_err(sparx5->dev, "Could not allocate RX buffers: %d\n", err);
return err;
}
err = sparx5_fdma_tx_alloc(sparx5);
if (err) {
dev_err(sparx5->dev, "Could not allocate TX buffers: %d\n", err);
return err;
}
return err;
}
int sparx5_fdma_deinit(struct sparx5 *sparx5)
{
sparx5_fdma_stop(sparx5);
fdma_free_phys(&sparx5->rx.fdma);
fdma_free_phys(&sparx5->tx.fdma);
return 0;
}
static u32 sparx5_fdma_port_ctrl(struct sparx5 *sparx5)
{
return spx5_rd(sparx5, FDMA_PORT_CTRL(0));
}
int sparx5_fdma_start(struct sparx5 *sparx5)
{
const struct sparx5_ops *ops = sparx5->data->ops;
struct sparx5_rx *rx = &sparx5->rx;
struct sparx5_tx *tx = &sparx5->tx;
netif_napi_add_weight(rx->ndev,
&rx->napi,
ops->fdma_poll,
FDMA_WEIGHT);
napi_enable(&rx->napi);
sparx5_fdma_rx_activate(sparx5, rx);
sparx5_fdma_tx_activate(sparx5, tx);
return 0;
}
int sparx5_fdma_stop(struct sparx5 *sparx5)
{
struct sparx5_rx *rx = &sparx5->rx;
struct sparx5_tx *tx = &sparx5->tx;
u32 val;
napi_disable(&rx->napi);
/* Stop the fdma and channel interrupts */
sparx5_fdma_rx_deactivate(sparx5, rx);
sparx5_fdma_tx_deactivate(sparx5, tx);
/* Wait for the RX channel to stop */
read_poll_timeout(sparx5_fdma_port_ctrl, val,
FDMA_PORT_CTRL_XTR_BUF_IS_EMPTY_GET(val) == 0,
500, 10000, 0, sparx5);
return 0;
}
@@ -0,0 +1,535 @@
// SPDX-License-Identifier: GPL-2.0+
/* Microchip Sparx5 Switch driver
*
* Copyright (c) 2021 Microchip Technology Inc. and its subsidiaries.
*/
#include <net/switchdev.h>
#include <linux/if_bridge.h>
#include <linux/iopoll.h>
#include "sparx5_main_regs.h"
#include "sparx5_main.h"
/* Commands for Mac Table Command register */
#define MAC_CMD_LEARN 0 /* Insert (Learn) 1 entry */
#define MAC_CMD_UNLEARN 1 /* Unlearn (Forget) 1 entry */
#define MAC_CMD_LOOKUP 2 /* Look up 1 entry */
#define MAC_CMD_READ 3 /* Read entry at Mac Table Index */
#define MAC_CMD_WRITE 4 /* Write entry at Mac Table Index */
#define MAC_CMD_SCAN 5 /* Scan (Age or find next) */
#define MAC_CMD_FIND_SMALLEST 6 /* Get next entry */
#define MAC_CMD_CLEAR_ALL 7 /* Delete all entries in table */
/* Commands for MAC_ENTRY_ADDR_TYPE */
#define MAC_ENTRY_ADDR_TYPE_UPSID_PN 0
#define MAC_ENTRY_ADDR_TYPE_UPSID_CPU_OR_INT 1
#define MAC_ENTRY_ADDR_TYPE_GLAG 2
#define MAC_ENTRY_ADDR_TYPE_MC_IDX 3
#define TABLE_UPDATE_SLEEP_US 10
#define TABLE_UPDATE_TIMEOUT_US 100000
struct sparx5_mact_entry {
struct list_head list;
unsigned char mac[ETH_ALEN];
u32 flags;
#define MAC_ENT_ALIVE BIT(0)
#define MAC_ENT_MOVED BIT(1)
#define MAC_ENT_LOCK BIT(2)
u16 vid;
u16 port;
};
static int sparx5_mact_get_status(struct sparx5 *sparx5)
{
return spx5_rd(sparx5, LRN_COMMON_ACCESS_CTRL);
}
static int sparx5_mact_wait_for_completion(struct sparx5 *sparx5)
{
u32 val;
return readx_poll_timeout(sparx5_mact_get_status,
sparx5, val,
LRN_COMMON_ACCESS_CTRL_MAC_TABLE_ACCESS_SHOT_GET(val) == 0,
TABLE_UPDATE_SLEEP_US, TABLE_UPDATE_TIMEOUT_US);
}
static void sparx5_mact_select(struct sparx5 *sparx5,
const unsigned char mac[ETH_ALEN],
u16 vid)
{
u32 macl = 0, mach = 0;
/* Set the MAC address to handle and the vlan associated in a format
* understood by the hardware.
*/
mach |= vid << 16;
mach |= mac[0] << 8;
mach |= mac[1] << 0;
macl |= mac[2] << 24;
macl |= mac[3] << 16;
macl |= mac[4] << 8;
macl |= mac[5] << 0;
spx5_wr(mach, sparx5, LRN_MAC_ACCESS_CFG_0);
spx5_wr(macl, sparx5, LRN_MAC_ACCESS_CFG_1);
}
int sparx5_mact_learn(struct sparx5 *sparx5, int pgid,
const unsigned char mac[ETH_ALEN], u16 vid)
{
const struct sparx5_consts *consts = sparx5->data->consts;
int addr, type, ret;
if (pgid < consts->n_ports) {
type = MAC_ENTRY_ADDR_TYPE_UPSID_PN;
addr = pgid % 32;
addr += (pgid / 32) << 5; /* Add upsid */
} else {
type = MAC_ENTRY_ADDR_TYPE_MC_IDX;
addr = pgid - consts->n_ports;
}
mutex_lock(&sparx5->lock);
sparx5_mact_select(sparx5, mac, vid);
/* MAC entry properties */
spx5_wr(LRN_MAC_ACCESS_CFG_2_MAC_ENTRY_ADDR_SET(addr) |
LRN_MAC_ACCESS_CFG_2_MAC_ENTRY_ADDR_TYPE_SET(type) |
LRN_MAC_ACCESS_CFG_2_MAC_ENTRY_VLD_SET(1) |
LRN_MAC_ACCESS_CFG_2_MAC_ENTRY_LOCKED_SET(1),
sparx5, LRN_MAC_ACCESS_CFG_2);
spx5_wr(0, sparx5, LRN_MAC_ACCESS_CFG_3);
/* Insert/learn new entry */
spx5_wr(LRN_COMMON_ACCESS_CTRL_CPU_ACCESS_CMD_SET(MAC_CMD_LEARN) |
LRN_COMMON_ACCESS_CTRL_MAC_TABLE_ACCESS_SHOT_SET(1),
sparx5, LRN_COMMON_ACCESS_CTRL);
ret = sparx5_mact_wait_for_completion(sparx5);
mutex_unlock(&sparx5->lock);
return ret;
}
int sparx5_mc_unsync(struct net_device *dev, const unsigned char *addr)
{
struct sparx5_port *port = netdev_priv(dev);
struct sparx5 *sparx5 = port->sparx5;
return sparx5_mact_forget(sparx5, addr, port->pvid);
}
int sparx5_mc_sync(struct net_device *dev, const unsigned char *addr)
{
struct sparx5_port *port = netdev_priv(dev);
struct sparx5 *sparx5 = port->sparx5;
return sparx5_mact_learn(sparx5, sparx5_get_pgid(sparx5, PGID_CPU),
addr, port->pvid);
}
static int sparx5_mact_get(struct sparx5 *sparx5,
unsigned char mac[ETH_ALEN],
u16 *vid, u32 *pcfg2)
{
u32 mach, macl, cfg2;
int ret = -ENOENT;
cfg2 = spx5_rd(sparx5, LRN_MAC_ACCESS_CFG_2);
if (LRN_MAC_ACCESS_CFG_2_MAC_ENTRY_VLD_GET(cfg2)) {
mach = spx5_rd(sparx5, LRN_MAC_ACCESS_CFG_0);
macl = spx5_rd(sparx5, LRN_MAC_ACCESS_CFG_1);
mac[0] = ((mach >> 8) & 0xff);
mac[1] = ((mach >> 0) & 0xff);
mac[2] = ((macl >> 24) & 0xff);
mac[3] = ((macl >> 16) & 0xff);
mac[4] = ((macl >> 8) & 0xff);
mac[5] = ((macl >> 0) & 0xff);
*vid = mach >> 16;
*pcfg2 = cfg2;
ret = 0;
}
return ret;
}
bool sparx5_mact_getnext(struct sparx5 *sparx5,
unsigned char mac[ETH_ALEN], u16 *vid, u32 *pcfg2)
{
u32 cfg2;
int ret;
mutex_lock(&sparx5->lock);
sparx5_mact_select(sparx5, mac, *vid);
spx5_wr(LRN_SCAN_NEXT_CFG_SCAN_NEXT_IGNORE_LOCKED_ENA_SET(1) |
LRN_SCAN_NEXT_CFG_SCAN_NEXT_UNTIL_FOUND_ENA_SET(1),
sparx5, LRN_SCAN_NEXT_CFG);
spx5_wr(LRN_COMMON_ACCESS_CTRL_CPU_ACCESS_CMD_SET
(MAC_CMD_FIND_SMALLEST) |
LRN_COMMON_ACCESS_CTRL_MAC_TABLE_ACCESS_SHOT_SET(1),
sparx5, LRN_COMMON_ACCESS_CTRL);
ret = sparx5_mact_wait_for_completion(sparx5);
if (ret == 0) {
ret = sparx5_mact_get(sparx5, mac, vid, &cfg2);
if (ret == 0)
*pcfg2 = cfg2;
}
mutex_unlock(&sparx5->lock);
return ret == 0;
}
int sparx5_mact_find(struct sparx5 *sparx5,
const unsigned char mac[ETH_ALEN], u16 vid, u32 *pcfg2)
{
int ret;
u32 cfg2;
mutex_lock(&sparx5->lock);
sparx5_mact_select(sparx5, mac, vid);
/* Issue a lookup command */
spx5_wr(LRN_COMMON_ACCESS_CTRL_CPU_ACCESS_CMD_SET(MAC_CMD_LOOKUP) |
LRN_COMMON_ACCESS_CTRL_MAC_TABLE_ACCESS_SHOT_SET(1),
sparx5, LRN_COMMON_ACCESS_CTRL);
ret = sparx5_mact_wait_for_completion(sparx5);
if (ret == 0) {
cfg2 = spx5_rd(sparx5, LRN_MAC_ACCESS_CFG_2);
if (LRN_MAC_ACCESS_CFG_2_MAC_ENTRY_VLD_GET(cfg2))
*pcfg2 = cfg2;
else
ret = -ENOENT;
}
mutex_unlock(&sparx5->lock);
return ret;
}
int sparx5_mact_forget(struct sparx5 *sparx5,
const unsigned char mac[ETH_ALEN], u16 vid)
{
int ret;
mutex_lock(&sparx5->lock);
sparx5_mact_select(sparx5, mac, vid);
/* Issue an unlearn command */
spx5_wr(LRN_COMMON_ACCESS_CTRL_CPU_ACCESS_CMD_SET(MAC_CMD_UNLEARN) |
LRN_COMMON_ACCESS_CTRL_MAC_TABLE_ACCESS_SHOT_SET(1),
sparx5, LRN_COMMON_ACCESS_CTRL);
ret = sparx5_mact_wait_for_completion(sparx5);
mutex_unlock(&sparx5->lock);
return ret;
}
static struct sparx5_mact_entry *alloc_mact_entry(struct sparx5 *sparx5,
const unsigned char *mac,
u16 vid, u16 port_index)
{
struct sparx5_mact_entry *mact_entry;
mact_entry = devm_kzalloc(sparx5->dev,
sizeof(*mact_entry), GFP_ATOMIC);
if (!mact_entry)
return NULL;
memcpy(mact_entry->mac, mac, ETH_ALEN);
mact_entry->vid = vid;
mact_entry->port = port_index;
return mact_entry;
}
static struct sparx5_mact_entry *find_mact_entry(struct sparx5 *sparx5,
const unsigned char *mac,
u16 vid, u16 port_index)
{
struct sparx5_mact_entry *mact_entry;
struct sparx5_mact_entry *res = NULL;
mutex_lock(&sparx5->mact_lock);
list_for_each_entry(mact_entry, &sparx5->mact_entries, list) {
if (mact_entry->vid == vid &&
ether_addr_equal(mac, mact_entry->mac) &&
mact_entry->port == port_index) {
res = mact_entry;
break;
}
}
mutex_unlock(&sparx5->mact_lock);
return res;
}
static void sparx5_fdb_call_notifiers(enum switchdev_notifier_type type,
const char *mac, u16 vid,
struct net_device *dev, bool offloaded)
{
struct switchdev_notifier_fdb_info info = {};
info.addr = mac;
info.vid = vid;
info.offloaded = offloaded;
call_switchdev_notifiers(type, dev, &info.info, NULL);
}
int sparx5_add_mact_entry(struct sparx5 *sparx5,
struct net_device *dev,
u16 portno,
const unsigned char *addr, u16 vid)
{
struct sparx5_mact_entry *mact_entry;
int ret;
u32 cfg2;
ret = sparx5_mact_find(sparx5, addr, vid, &cfg2);
if (!ret)
return 0;
/* In case the entry already exists, don't add it again to SW,
* just update HW, but we need to look in the actual HW because
* it is possible for an entry to be learn by HW and before the
* mact thread to start the frame will reach CPU and the CPU will
* add the entry but without the extern_learn flag.
*/
mact_entry = find_mact_entry(sparx5, addr, vid, portno);
if (mact_entry)
goto update_hw;
/* Add the entry in SW MAC table not to get the notification when
* SW is pulling again
*/
mact_entry = alloc_mact_entry(sparx5, addr, vid, portno);
if (!mact_entry)
return -ENOMEM;
mutex_lock(&sparx5->mact_lock);
list_add_tail(&mact_entry->list, &sparx5->mact_entries);
mutex_unlock(&sparx5->mact_lock);
update_hw:
ret = sparx5_mact_learn(sparx5, portno, addr, vid);
/* New entry? */
if (mact_entry->flags == 0) {
mact_entry->flags |= MAC_ENT_LOCK; /* Don't age this */
sparx5_fdb_call_notifiers(SWITCHDEV_FDB_ADD_TO_BRIDGE, addr, vid,
dev, true);
}
return ret;
}
int sparx5_del_mact_entry(struct sparx5 *sparx5,
const unsigned char *addr,
u16 vid)
{
struct sparx5_mact_entry *mact_entry, *tmp;
/* Delete the entry in SW MAC table not to get the notification when
* SW is pulling again
*/
mutex_lock(&sparx5->mact_lock);
list_for_each_entry_safe(mact_entry, tmp, &sparx5->mact_entries,
list) {
if ((vid == 0 || mact_entry->vid == vid) &&
ether_addr_equal(addr, mact_entry->mac)) {
sparx5_mact_forget(sparx5, addr, mact_entry->vid);
list_del(&mact_entry->list);
devm_kfree(sparx5->dev, mact_entry);
}
}
mutex_unlock(&sparx5->mact_lock);
return 0;
}
static void sparx5_mact_handle_entry(struct sparx5 *sparx5,
unsigned char mac[ETH_ALEN],
u16 vid, u32 cfg2)
{
struct sparx5_mact_entry *mact_entry;
bool found = false;
u16 port;
if (LRN_MAC_ACCESS_CFG_2_MAC_ENTRY_ADDR_TYPE_GET(cfg2) !=
MAC_ENTRY_ADDR_TYPE_UPSID_PN)
return;
port = LRN_MAC_ACCESS_CFG_2_MAC_ENTRY_ADDR_GET(cfg2);
if (port >= sparx5->data->consts->n_ports)
return;
if (!test_bit(port, sparx5->bridge_mask))
return;
mutex_lock(&sparx5->mact_lock);
list_for_each_entry(mact_entry, &sparx5->mact_entries, list) {
if (mact_entry->vid == vid &&
ether_addr_equal(mac, mact_entry->mac)) {
found = true;
mact_entry->flags |= MAC_ENT_ALIVE;
if (mact_entry->port != port) {
dev_warn(sparx5->dev, "Entry move: %d -> %d\n",
mact_entry->port, port);
mact_entry->port = port;
mact_entry->flags |= MAC_ENT_MOVED;
}
/* Entry handled */
break;
}
}
mutex_unlock(&sparx5->mact_lock);
if (found && !(mact_entry->flags & MAC_ENT_MOVED))
/* Present, not moved */
return;
if (!found) {
/* Entry not found - now add */
mact_entry = alloc_mact_entry(sparx5, mac, vid, port);
if (!mact_entry)
return;
mact_entry->flags |= MAC_ENT_ALIVE;
mutex_lock(&sparx5->mact_lock);
list_add_tail(&mact_entry->list, &sparx5->mact_entries);
mutex_unlock(&sparx5->mact_lock);
}
/* New or moved entry - notify bridge */
sparx5_fdb_call_notifiers(SWITCHDEV_FDB_ADD_TO_BRIDGE,
mac, vid, sparx5->ports[port]->ndev,
true);
}
static void sparx5_mact_pull_work(struct work_struct *work)
{
struct delayed_work *del_work = to_delayed_work(work);
struct sparx5 *sparx5 = container_of(del_work, struct sparx5,
mact_work);
struct sparx5_mact_entry *mact_entry, *tmp;
unsigned char mac[ETH_ALEN];
u32 cfg2;
u16 vid;
int ret;
/* Reset MAC entry flags */
mutex_lock(&sparx5->mact_lock);
list_for_each_entry(mact_entry, &sparx5->mact_entries, list)
mact_entry->flags &= MAC_ENT_LOCK;
mutex_unlock(&sparx5->mact_lock);
/* MAIN mac address processing loop */
vid = 0;
memset(mac, 0, sizeof(mac));
do {
mutex_lock(&sparx5->lock);
sparx5_mact_select(sparx5, mac, vid);
spx5_wr(LRN_SCAN_NEXT_CFG_SCAN_NEXT_UNTIL_FOUND_ENA_SET(1),
sparx5, LRN_SCAN_NEXT_CFG);
spx5_wr(LRN_COMMON_ACCESS_CTRL_CPU_ACCESS_CMD_SET
(MAC_CMD_FIND_SMALLEST) |
LRN_COMMON_ACCESS_CTRL_MAC_TABLE_ACCESS_SHOT_SET(1),
sparx5, LRN_COMMON_ACCESS_CTRL);
ret = sparx5_mact_wait_for_completion(sparx5);
if (ret == 0)
ret = sparx5_mact_get(sparx5, mac, &vid, &cfg2);
mutex_unlock(&sparx5->lock);
if (ret == 0)
sparx5_mact_handle_entry(sparx5, mac, vid, cfg2);
} while (ret == 0);
mutex_lock(&sparx5->mact_lock);
list_for_each_entry_safe(mact_entry, tmp, &sparx5->mact_entries,
list) {
/* If the entry is in HW or permanent, then skip */
if (mact_entry->flags & (MAC_ENT_ALIVE | MAC_ENT_LOCK))
continue;
sparx5_fdb_call_notifiers(SWITCHDEV_FDB_DEL_TO_BRIDGE,
mact_entry->mac, mact_entry->vid,
sparx5->ports[mact_entry->port]->ndev,
true);
list_del(&mact_entry->list);
devm_kfree(sparx5->dev, mact_entry);
}
mutex_unlock(&sparx5->mact_lock);
queue_delayed_work(sparx5->mact_queue, &sparx5->mact_work,
SPX5_MACT_PULL_DELAY);
}
void sparx5_set_ageing(struct sparx5 *sparx5, int msecs)
{
int value = max(1, msecs / 10); /* unit 10 ms */
spx5_rmw(LRN_AUTOAGE_CFG_UNIT_SIZE_SET(2) | /* 10 ms */
LRN_AUTOAGE_CFG_PERIOD_VAL_SET(value / 2), /* one bit ageing */
LRN_AUTOAGE_CFG_UNIT_SIZE |
LRN_AUTOAGE_CFG_PERIOD_VAL,
sparx5,
LRN_AUTOAGE_CFG(0));
}
int sparx5_mact_init(struct sparx5 *sparx5)
{
char queue_name[32];
mutex_init(&sparx5->lock);
/* Flush MAC table */
spx5_wr(LRN_COMMON_ACCESS_CTRL_CPU_ACCESS_CMD_SET(MAC_CMD_CLEAR_ALL) |
LRN_COMMON_ACCESS_CTRL_MAC_TABLE_ACCESS_SHOT_SET(1),
sparx5, LRN_COMMON_ACCESS_CTRL);
if (sparx5_mact_wait_for_completion(sparx5) != 0)
dev_warn(sparx5->dev, "MAC flush error\n");
sparx5_set_ageing(sparx5, BR_DEFAULT_AGEING_TIME / HZ * 1000);
/* Add host mode BC address (points only to CPU) */
sparx5_mact_learn(sparx5, sparx5_get_pgid(sparx5, PGID_CPU),
(unsigned char[]){0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
NULL_VID);
mutex_init(&sparx5->mdb_lock);
INIT_LIST_HEAD(&sparx5->mdb_entries);
mutex_init(&sparx5->mact_lock);
INIT_LIST_HEAD(&sparx5->mact_entries);
snprintf(queue_name, sizeof(queue_name), "%s-mact",
dev_name(sparx5->dev));
sparx5->mact_queue = create_singlethread_workqueue(queue_name);
if (!sparx5->mact_queue)
return -ENOMEM;
INIT_DELAYED_WORK(&sparx5->mact_work, sparx5_mact_pull_work);
queue_delayed_work(sparx5->mact_queue, &sparx5->mact_work,
SPX5_MACT_PULL_DELAY);
return 0;
}
void sparx5_mact_deinit(struct sparx5 *sparx5)
{
cancel_delayed_work_sync(&sparx5->mact_work);
destroy_workqueue(sparx5->mact_queue);
mutex_destroy(&sparx5->mact_lock);
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,867 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/* Microchip Sparx5 Switch driver
*
* Copyright (c) 2021 Microchip Technology Inc. and its subsidiaries.
*/
#ifndef __SPARX5_MAIN_H__
#define __SPARX5_MAIN_H__
#include <linux/types.h>
#include <linux/phy/phy.h>
#include <linux/netdevice.h>
#include <linux/phy.h>
#include <linux/if_vlan.h>
#include <linux/bitmap.h>
#include <linux/phylink.h>
#include <linux/net_tstamp.h>
#include <linux/ptp_clock_kernel.h>
#include <linux/hrtimer.h>
#include <linux/debugfs.h>
#include <net/flow_offload.h>
#include <fdma_api.h>
#include "sparx5_main_regs.h"
/* Target chip type */
enum spx5_target_chiptype {
SPX5_TARGET_CT_7546 = 0x7546, /* SparX-5-64 Enterprise */
SPX5_TARGET_CT_7549 = 0x7549, /* SparX-5-90 Enterprise */
SPX5_TARGET_CT_7552 = 0x7552, /* SparX-5-128 Enterprise */
SPX5_TARGET_CT_7556 = 0x7556, /* SparX-5-160 Enterprise */
SPX5_TARGET_CT_7558 = 0x7558, /* SparX-5-200 Enterprise */
SPX5_TARGET_CT_7546TSN = 0x0546, /* SparX-5-64i Industrial */
SPX5_TARGET_CT_7549TSN = 0x0549, /* SparX-5-90i Industrial */
SPX5_TARGET_CT_7552TSN = 0x0552, /* SparX-5-128i Industrial */
SPX5_TARGET_CT_7556TSN = 0x0556, /* SparX-5-160i Industrial */
SPX5_TARGET_CT_7558TSN = 0x0558, /* SparX-5-200i Industrial */
SPX5_TARGET_CT_LAN9694 = 0x9694, /* lan969x-40 */
SPX5_TARGET_CT_LAN9691VAO = 0x9691, /* lan969x-40-VAO */
SPX5_TARGET_CT_LAN9694TSN = 0x9695, /* lan969x-40-TSN */
SPX5_TARGET_CT_LAN9694RED = 0x969A, /* lan969x-40-RED */
SPX5_TARGET_CT_LAN9696 = 0x9696, /* lan969x-60 */
SPX5_TARGET_CT_LAN9692VAO = 0x9692, /* lan969x-65-VAO */
SPX5_TARGET_CT_LAN9696TSN = 0x9697, /* lan969x-60-TSN */
SPX5_TARGET_CT_LAN9696RED = 0x969B, /* lan969x-60-RED */
SPX5_TARGET_CT_LAN9698 = 0x9698, /* lan969x-100 */
SPX5_TARGET_CT_LAN9693VAO = 0x9693, /* lan969x-100-VAO */
SPX5_TARGET_CT_LAN9698TSN = 0x9699, /* lan969x-100-TSN */
SPX5_TARGET_CT_LAN9698RED = 0x969C, /* lan969x-100-RED */
};
enum sparx5_port_max_tags {
SPX5_PORT_MAX_TAGS_NONE, /* No extra tags allowed */
SPX5_PORT_MAX_TAGS_ONE, /* Single tag allowed */
SPX5_PORT_MAX_TAGS_TWO /* Single and double tag allowed */
};
enum sparx5_vlan_port_type {
SPX5_VLAN_PORT_TYPE_UNAWARE, /* VLAN unaware port */
SPX5_VLAN_PORT_TYPE_C, /* C-port */
SPX5_VLAN_PORT_TYPE_S, /* S-port */
SPX5_VLAN_PORT_TYPE_S_CUSTOM /* S-port using custom type */
};
/* 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
};
enum sparx5_feature {
SPX5_FEATURE_PSFP = BIT(0),
SPX5_FEATURE_PTP = BIT(1),
};
#define SPX5_PORTS 65
#define SPX5_PORTS_ALL 70 /* Total number of ports */
#define SPX5_PORT_CPU_0 0 /* CPU Port 0 */
#define SPX5_PORT_CPU_1 1 /* CPU Port 1 */
#define SPX5_PORT_VD0 2 /* VD0/Port used for IPMC */
#define SPX5_PORT_VD1 3 /* VD1/Port used for AFI/OAM */
#define SPX5_PORT_VD2 4 /* VD2/Port used for IPinIP*/
#define PGID_UC_FLOOD 0
#define PGID_MC_FLOOD 1
#define PGID_IPV4_MC_DATA 2
#define PGID_IPV4_MC_CTRL 3
#define PGID_IPV6_MC_DATA 4
#define PGID_IPV6_MC_CTRL 5
#define PGID_BCAST 6
#define PGID_CPU 7
#define PGID_MCAST_START 8
#define PGID_TABLE_SIZE 3290
#define IFH_LEN 9 /* 36 bytes */
#define NULL_VID 0
#define SPX5_MACT_PULL_DELAY (2 * HZ)
#define SPX5_STATS_CHECK_DELAY (1 * HZ)
#define SPX5_PRIOS 8 /* Number of priority queues */
#define SPX5_BUFFER_CELL_SZ 184 /* Cell size */
#define SPX5_BUFFER_MEMORY 4194280 /* 22795 words * 184 bytes */
#define XTR_QUEUE 0
#define INJ_QUEUE 0
#define FDMA_XTR_CHANNEL 6
#define FDMA_INJ_CHANNEL 0
#define FDMA_DCB_MAX 64
#define FDMA_RX_DCB_MAX_DBS 15
#define FDMA_TX_DCB_MAX_DBS 1
#define SPARX5_PHC_COUNT 3
#define SPARX5_PHC_PORT 0
#define IFH_REW_OP_NOOP 0x0
#define IFH_REW_OP_ONE_STEP_PTP 0x3
#define IFH_REW_OP_TWO_STEP_PTP 0x4
#define IFH_PDU_TYPE_NONE 0x0
#define IFH_PDU_TYPE_PTP 0x5
#define IFH_PDU_TYPE_IPV4_UDP_PTP 0x6
#define IFH_PDU_TYPE_IPV6_UDP_PTP 0x7
#define SPX5_DSM_CAL_LEN 64
#define SPX5_DSM_CAL_MAX_DEVS_PER_TAXI 13
#define SPX5_DSM_CAL_EMPTY 0xFFFF
#define SPARX5_MAX_PTP_ID 512
struct sparx5;
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];
};
/* Frame DMA receive state:
* For each DB, there is a SKB, and the skb data pointer is mapped in
* the DB. Once a frame is received the skb is given to the upper layers
* and a new skb is added to the dcb.
* When the db_index reached FDMA_RX_DCB_MAX_DBS the DB is reused.
*/
struct sparx5_rx {
struct fdma fdma;
struct page_pool *page_pool;
union {
struct sk_buff *skb[FDMA_DCB_MAX][FDMA_RX_DCB_MAX_DBS];
struct page *page[FDMA_DCB_MAX][FDMA_RX_DCB_MAX_DBS];
};
dma_addr_t dma;
struct napi_struct napi;
struct net_device *ndev;
u64 packets;
u8 page_order;
};
/* Used to store information about TX buffers. */
struct sparx5_tx_buf {
struct net_device *dev;
struct sk_buff *skb;
dma_addr_t dma_addr;
bool used;
bool ptp;
};
/* Frame DMA transmit state:
* DCBs are chained using the DCBs nextptr field.
*/
struct sparx5_tx {
struct fdma fdma;
struct sparx5_tx_buf *dbs;
u64 packets;
u64 dropped;
};
struct sparx5_port_config {
phy_interface_t portmode;
u32 bandwidth;
int speed;
int duplex;
enum phy_media media;
bool inband;
bool power_down;
bool autoneg;
bool serdes_reset;
u32 pause;
u32 pause_adv;
phy_interface_t phy_mode;
u32 sd_sgpio;
};
struct sparx5_port {
struct net_device *ndev;
struct sparx5 *sparx5;
struct device_node *of_node;
struct phy *serdes;
struct sparx5_port_config conf;
struct phylink_config phylink_config;
struct phylink *phylink;
struct phylink_pcs phylink_pcs;
struct flow_stats mirror_stats;
u16 portno;
/* Ingress default VLAN (pvid) */
u16 pvid;
/* Egress default VLAN (vid) */
u16 vid;
bool signd_internal;
bool signd_active_high;
bool signd_enable;
bool flow_control;
enum sparx5_port_max_tags max_vlan_tags;
enum sparx5_vlan_port_type vlan_type;
u32 custom_etype;
bool vlan_aware;
struct hrtimer inj_timer;
/* ptp */
u8 ptp_cmd;
u16 ts_id;
struct sk_buff_head tx_skbs;
bool is_mrouter;
struct list_head tc_templates; /* list of TC templates on this port */
};
enum sparx5_core_clockfreq {
SPX5_CORE_CLOCK_DEFAULT, /* Defaults to the highest supported frequency */
SPX5_CORE_CLOCK_250MHZ, /* 250MHZ core clock frequency */
SPX5_CORE_CLOCK_328MHZ, /* 328MHZ core clock frequency */
SPX5_CORE_CLOCK_500MHZ, /* 500MHZ core clock frequency */
SPX5_CORE_CLOCK_625MHZ, /* 625MHZ core clock frequency */
};
struct sparx5_phc {
struct ptp_clock *clock;
struct ptp_clock_info info;
struct kernel_hwtstamp_config hwtstamp_config;
struct sparx5 *sparx5;
u8 index;
};
struct sparx5_skb_cb {
u8 rew_op;
u8 pdu_type;
u8 pdu_w16_offset;
u16 ts_id;
unsigned long jiffies;
};
struct sparx5_mdb_entry {
struct list_head list;
DECLARE_BITMAP(port_mask, SPX5_PORTS);
unsigned char addr[ETH_ALEN];
bool cpu_copy;
u16 vid;
u16 pgid_idx;
};
struct sparx5_mall_mirror_entry {
u32 idx;
struct sparx5_port *port;
};
struct sparx5_mall_entry {
struct list_head list;
struct sparx5_port *port;
unsigned long cookie;
enum flow_action_id type;
bool ingress;
union {
struct sparx5_mall_mirror_entry mirror;
};
};
#define SPARX5_PTP_TIMEOUT msecs_to_jiffies(10)
#define SPARX5_SKB_CB(skb) \
((struct sparx5_skb_cb *)((skb)->cb))
struct sparx5_regs {
const unsigned int *tsize;
const unsigned int *gaddr;
const unsigned int *gcnt;
const unsigned int *gsize;
const unsigned int *raddr;
const unsigned int *rcnt;
const unsigned int *fpos;
const unsigned int *fsize;
};
struct sparx5_consts {
u32 n_ports; /* Number of front ports */
u32 n_ports_all; /* Number of front ports + internal ports */
u32 n_hsch_l1_elems; /* Number of HSCH layer 1 elements */
u32 n_hsch_queues; /* Number of HSCH queues */
u32 n_lb_groups; /* Number of leacky bucket groupd */
u32 n_pgids; /* Number of PGID's */
u32 n_sio_clks; /* Number of serial IO clocks */
u32 n_own_upsids; /* Number of own UPSID's */
u32 n_auto_cals; /* Number of auto calendars */
u32 n_filters; /* Number of PSFP filters */
u32 n_gates; /* Number of PSFP gates */
u32 n_sdlbs; /* Number of service dual leaky buckets */
u32 n_dsm_cal_taxis; /* Number of DSM calendar taxis */
u32 buf_size; /* Amount of QLIM watermark memory */
u32 qres_max_prio_idx; /* Maximum QRES prio index */
u32 qres_max_colour_idx; /* Maximum QRES colour index */
u32 tod_pin; /* PTP TOD pin */
const struct sparx5_vcap_inst *vcaps_cfg;
const struct vcap_info *vcaps;
const struct vcap_statistics *vcap_stats;
};
struct sparx5_ops {
bool (*is_port_2g5)(int portno);
bool (*is_port_5g)(int portno);
bool (*is_port_10g)(int portno);
bool (*is_port_25g)(int portno);
bool (*is_port_rgmii)(int portno);
u32 (*get_port_dev_index)(struct sparx5 *sparx5, int port);
u32 (*get_port_dev_bit)(struct sparx5 *sparx5, int port);
u32 (*get_hsch_max_group_rate)(int grp);
struct sparx5_sdlb_group *(*get_sdlb_group)(int idx);
int (*set_port_mux)(struct sparx5 *sparx5, struct sparx5_port *port,
struct sparx5_port_config *conf);
irqreturn_t (*ptp_irq_handler)(int irq, void *args);
int (*dsm_calendar_calc)(struct sparx5 *sparx5, u32 taxi,
struct sparx5_calendar_data *data);
int (*port_config_rgmii)(struct sparx5_port *port,
struct sparx5_port_config *conf);
int (*fdma_init)(struct sparx5 *sparx5);
int (*fdma_deinit)(struct sparx5 *sparx5);
int (*fdma_poll)(struct napi_struct *napi, int weight);
int (*fdma_xmit)(struct sparx5 *sparx5, u32 *ifh, struct sk_buff *skb,
struct net_device *dev);
};
struct sparx5_main_io_resource {
enum sparx5_target id;
phys_addr_t offset;
int range;
};
struct sparx5_match_data {
const struct sparx5_regs *regs;
const struct sparx5_consts *consts;
const struct sparx5_ops *ops;
const struct sparx5_main_io_resource *iomap;
int ioranges;
int iomap_size;
};
struct sparx5 {
struct platform_device *pdev;
struct device *dev;
u32 chip_id;
enum spx5_target_chiptype target_ct;
u32 features;
void __iomem *regs[NUM_TARGETS];
int port_count;
struct mutex lock; /* MAC reg lock */
/* port structures are in net device */
struct sparx5_port *ports[SPX5_PORTS];
enum sparx5_core_clockfreq coreclock;
/* Statistics */
u32 num_stats;
u32 num_ethtool_stats;
const char * const *stats_layout;
u64 *stats;
/* Workqueue for reading stats */
struct mutex queue_stats_lock;
struct delayed_work stats_work;
struct workqueue_struct *stats_queue;
/* Notifiers */
struct notifier_block netdevice_nb;
struct notifier_block switchdev_nb;
struct notifier_block switchdev_blocking_nb;
/* Switch state */
u8 base_mac[ETH_ALEN];
/* Associated bridge device (when bridged) */
struct net_device *hw_bridge_dev;
/* Bridged interfaces */
DECLARE_BITMAP(bridge_mask, SPX5_PORTS);
DECLARE_BITMAP(bridge_fwd_mask, SPX5_PORTS);
DECLARE_BITMAP(bridge_lrn_mask, SPX5_PORTS);
DECLARE_BITMAP(vlan_mask[VLAN_N_VID], SPX5_PORTS);
/* SW MAC table */
struct list_head mact_entries;
/* mac table list (mact_entries) mutex */
struct mutex mact_lock;
/* SW MDB table */
struct list_head mdb_entries;
/* mdb list mutex */
struct mutex mdb_lock;
struct delayed_work mact_work;
struct workqueue_struct *mact_queue;
/* Board specifics */
bool sd_sgpio_remapping;
/* Register based inj/xtr */
int xtr_irq;
/* Frame DMA */
int fdma_irq;
spinlock_t tx_lock; /* lock for frame transmission */
struct sparx5_rx rx;
struct sparx5_tx tx;
/* PTP */
bool ptp;
struct sparx5_phc phc[SPARX5_PHC_COUNT];
spinlock_t ptp_clock_lock; /* lock for phc */
spinlock_t ptp_ts_id_lock; /* lock for ts_id */
struct mutex ptp_lock; /* lock for ptp interface state */
u16 ptp_skbs;
int ptp_irq;
/* VCAP */
struct vcap_control *vcap_ctrl;
/* PGID allocation map */
u8 pgid_map[PGID_TABLE_SIZE];
struct list_head mall_entries;
/* Common root for debugfs */
struct dentry *debugfs_root;
const struct sparx5_match_data *data;
};
/* sparx5_main.c */
bool is_sparx5(struct sparx5 *sparx5);
bool sparx5_has_feature(struct sparx5 *sparx5, enum sparx5_feature feature);
/* sparx5_switchdev.c */
int sparx5_register_notifier_blocks(struct sparx5 *sparx5);
void sparx5_unregister_notifier_blocks(struct sparx5 *sparx5);
/* sparx5_packet.c */
struct frame_info {
int src_port;
u32 timestamp;
};
void sparx5_xtr_flush(struct sparx5 *sparx5, u8 grp);
void sparx5_ifh_parse(struct sparx5 *sparx5, u32 *ifh, struct frame_info *info);
irqreturn_t sparx5_xtr_handler(int irq, void *_priv);
netdev_tx_t sparx5_port_xmit_impl(struct sk_buff *skb, struct net_device *dev);
int sparx5_manual_injection_mode(struct sparx5 *sparx5);
void sparx5_port_inj_timer_setup(struct sparx5_port *port);
/* sparx5_fdma.c */
int sparx5_fdma_init(struct sparx5 *sparx5);
int sparx5_fdma_deinit(struct sparx5 *sparx5);
int sparx5_fdma_start(struct sparx5 *sparx5);
int sparx5_fdma_stop(struct sparx5 *sparx5);
int sparx5_fdma_napi_callback(struct napi_struct *napi, int weight);
int sparx5_fdma_xmit(struct sparx5 *sparx5, u32 *ifh, struct sk_buff *skb,
struct net_device *dev);
irqreturn_t sparx5_fdma_handler(int irq, void *args);
void sparx5_fdma_reload(struct sparx5 *sparx5, struct fdma *fdma);
void sparx5_fdma_injection_mode(struct sparx5 *sparx5);
/* sparx5_mactable.c */
int sparx5_mact_learn(struct sparx5 *sparx5, int port,
const unsigned char mac[ETH_ALEN], u16 vid);
bool sparx5_mact_getnext(struct sparx5 *sparx5,
unsigned char mac[ETH_ALEN], u16 *vid, u32 *pcfg2);
int sparx5_mact_find(struct sparx5 *sparx5,
const unsigned char mac[ETH_ALEN], u16 vid, u32 *pcfg2);
int sparx5_mact_forget(struct sparx5 *sparx5,
const unsigned char mac[ETH_ALEN], u16 vid);
int sparx5_add_mact_entry(struct sparx5 *sparx5,
struct net_device *dev,
u16 portno,
const unsigned char *addr, u16 vid);
int sparx5_del_mact_entry(struct sparx5 *sparx5,
const unsigned char *addr,
u16 vid);
int sparx5_mc_sync(struct net_device *dev, const unsigned char *addr);
int sparx5_mc_unsync(struct net_device *dev, const unsigned char *addr);
void sparx5_set_ageing(struct sparx5 *sparx5, int msecs);
int sparx5_mact_init(struct sparx5 *sparx5);
void sparx5_mact_deinit(struct sparx5 *sparx5);
/* sparx5_vlan.c */
void sparx5_pgid_update_mask(struct sparx5_port *port, int pgid, bool enable);
void sparx5_pgid_clear(struct sparx5 *spx5, int pgid);
void sparx5_pgid_read_mask(struct sparx5 *sparx5, int pgid, u32 portmask[3]);
void sparx5_update_fwd(struct sparx5 *sparx5);
void sparx5_vlan_init(struct sparx5 *sparx5);
void sparx5_vlan_port_setup(struct sparx5 *sparx5, int portno);
int sparx5_vlan_vid_add(struct sparx5_port *port, u16 vid, bool pvid,
bool untagged);
int sparx5_vlan_vid_del(struct sparx5_port *port, u16 vid);
void sparx5_vlan_port_apply(struct sparx5 *sparx5, struct sparx5_port *port);
/* sparx5_calendar.c */
int sparx5_calendar_init(struct sparx5 *sparx5);
int sparx5_dsm_calendar_calc(struct sparx5 *sparx5, u32 taxi,
struct sparx5_calendar_data *data);
u32 sparx5_cal_speed_to_value(enum sparx5_cal_bw speed);
enum sparx5_cal_bw sparx5_get_port_cal_speed(struct sparx5 *sparx5, u32 portno);
/* sparx5_ethtool.c */
void sparx5_get_stats64(struct net_device *ndev, struct rtnl_link_stats64 *stats);
int sparx5_stats_init(struct sparx5 *sparx5);
void sparx5_stats_deinit(struct sparx5 *sparx5);
/* sparx5_dcb.c */
#ifdef CONFIG_SPARX5_DCB
int sparx5_dcb_init(struct sparx5 *sparx5);
#else
static inline int sparx5_dcb_init(struct sparx5 *sparx5)
{
return 0;
}
#endif
/* sparx5_netdev.c */
void sparx5_set_port_ifh_timestamp(struct sparx5 *sparx5, void *ifh_hdr,
u64 timestamp);
void sparx5_set_port_ifh_rew_op(void *ifh_hdr, u32 rew_op);
void sparx5_set_port_ifh_pdu_type(struct sparx5 *sparx5, void *ifh_hdr,
u32 pdu_type);
void sparx5_set_port_ifh_pdu_w16_offset(struct sparx5 *sparx5, void *ifh_hdr,
u32 pdu_w16_offset);
void sparx5_set_port_ifh(struct sparx5 *sparx5, void *ifh_hdr, u16 portno);
bool sparx5_netdevice_check(const struct net_device *dev);
struct net_device *sparx5_create_netdev(struct sparx5 *sparx5, u32 portno);
int sparx5_register_netdevs(struct sparx5 *sparx5);
void sparx5_destroy_netdevs(struct sparx5 *sparx5);
void sparx5_unregister_netdevs(struct sparx5 *sparx5);
/* sparx5_ptp.c */
int sparx5_ptp_init(struct sparx5 *sparx5);
void sparx5_ptp_deinit(struct sparx5 *sparx5);
int sparx5_ptp_hwtstamp_set(struct sparx5_port *port,
struct kernel_hwtstamp_config *cfg,
struct netlink_ext_ack *extack);
void sparx5_ptp_hwtstamp_get(struct sparx5_port *port,
struct kernel_hwtstamp_config *cfg);
void sparx5_ptp_rxtstamp(struct sparx5 *sparx5, struct sk_buff *skb,
u64 timestamp);
int sparx5_ptp_txtstamp_request(struct sparx5_port *port,
struct sk_buff *skb);
void sparx5_ptp_txtstamp_release(struct sparx5_port *port,
struct sk_buff *skb);
irqreturn_t sparx5_ptp_irq_handler(int irq, void *args);
int sparx5_ptp_gettime64(struct ptp_clock_info *ptp, struct timespec64 *ts);
void sparx5_get_hwtimestamp(struct sparx5 *sparx5,
struct timespec64 *ts,
u32 nsec);
/* sparx5_vcap_impl.c */
int sparx5_vcap_init(struct sparx5 *sparx5);
void sparx5_vcap_deinit(struct sparx5 *sparx5);
/* sparx5_pgid.c */
enum sparx5_pgid_type {
SPX5_PGID_FREE,
SPX5_PGID_RESERVED,
SPX5_PGID_MULTICAST,
};
void sparx5_pgid_init(struct sparx5 *spx5);
int sparx5_pgid_alloc_mcast(struct sparx5 *spx5, u16 *idx);
int sparx5_pgid_free(struct sparx5 *spx5, u16 idx);
int sparx5_get_pgid(struct sparx5 *sparx5, int pgid);
/* sparx5_pool.c */
struct sparx5_pool_entry {
u16 ref_cnt;
u32 idx; /* tc index */
};
u32 sparx5_pool_idx_to_id(u32 idx);
int sparx5_pool_put(struct sparx5_pool_entry *pool, int size, u32 id);
int sparx5_pool_get(struct sparx5_pool_entry *pool, int size, u32 *id);
int sparx5_pool_get_with_idx(struct sparx5_pool_entry *pool, int size, u32 idx,
u32 *id);
/* sparx5_port.c */
int sparx5_port_mux_set(struct sparx5 *sparx5, struct sparx5_port *port,
struct sparx5_port_config *conf);
int sparx5_get_internal_port(struct sparx5 *sparx5, int port);
/* sparx5_sdlb.c */
#define SPX5_SDLB_PUP_TOKEN_DISABLE 0x1FFF
#define SPX5_SDLB_PUP_TOKEN_MAX (SPX5_SDLB_PUP_TOKEN_DISABLE - 1)
#define SPX5_SDLB_GROUP_RATE_MAX 25000000000ULL
#define SPX5_SDLB_2CYCLES_TYPE2_THRES_OFFSET 13
#define SPX5_SDLB_CNT 4096
#define SPX5_SDLB_GROUP_CNT 10
#define SPX5_CLK_PER_100PS_DEFAULT 16
struct sparx5_sdlb_group {
u64 max_rate;
u32 min_burst;
u32 frame_size;
u32 pup_interval;
u32 nsets;
};
extern struct sparx5_sdlb_group sdlb_groups[SPX5_SDLB_GROUP_CNT];
struct sparx5_sdlb_group *sparx5_get_sdlb_group(int idx);
int sparx5_sdlb_pup_token_get(struct sparx5 *sparx5, u32 pup_interval,
u64 rate);
u64 sparx5_sdlb_clk_hz_get(struct sparx5 *sparx5);
int sparx5_sdlb_group_get_by_rate(struct sparx5 *sparx5, u32 rate, u32 burst);
int sparx5_sdlb_group_get_by_index(struct sparx5 *sparx5, u32 idx, u32 *group);
int sparx5_sdlb_group_add(struct sparx5 *sparx5, u32 group, u32 idx);
int sparx5_sdlb_group_del(struct sparx5 *sparx5, u32 group, u32 idx);
void sparx5_sdlb_group_init(struct sparx5 *sparx5, u64 max_rate, u32 min_burst,
u32 frame_size, u32 idx);
/* sparx5_police.c */
enum {
/* More policer types will be added later */
SPX5_POL_SERVICE
};
struct sparx5_policer {
u32 type;
u32 idx;
u64 rate;
u32 burst;
u32 group;
u8 event_mask;
};
int sparx5_policer_conf_set(struct sparx5 *sparx5, struct sparx5_policer *pol);
/* sparx5_psfp.c */
#define SPX5_PSFP_GCE_CNT 4
#define SPX5_PSFP_SG_CNT 1024
#define SPX5_PSFP_SG_MIN_CYCLE_TIME_NS (1 * NSEC_PER_USEC)
#define SPX5_PSFP_SG_MAX_CYCLE_TIME_NS ((1 * NSEC_PER_SEC) - 1)
#define SPX5_PSFP_SG_MAX_IPV (SPX5_PRIOS - 1)
#define SPX5_PSFP_SG_OPEN (SPX5_PSFP_SG_CNT - 1)
#define SPX5_PSFP_SG_CYCLE_TIME_DEFAULT 1000000
#define SPX5_PSFP_SF_MAX_SDU 16383
struct sparx5_psfp_fm {
struct sparx5_policer pol;
};
struct sparx5_psfp_gce {
bool gate_state; /* StreamGateState */
u32 interval; /* TimeInterval */
u32 ipv; /* InternalPriorityValue */
u32 maxoctets; /* IntervalOctetMax */
};
struct sparx5_psfp_sg {
bool gate_state; /* PSFPAdminGateStates */
bool gate_enabled; /* PSFPGateEnabled */
u32 ipv; /* PSFPAdminIPV */
struct timespec64 basetime; /* PSFPAdminBaseTime */
u32 cycletime; /* PSFPAdminCycleTime */
u32 cycletimeext; /* PSFPAdminCycleTimeExtension */
u32 num_entries; /* PSFPAdminControlListLength */
struct sparx5_psfp_gce gce[SPX5_PSFP_GCE_CNT];
};
struct sparx5_psfp_sf {
bool sblock_osize_ena;
bool sblock_osize;
u32 max_sdu;
u32 sgid; /* Gate id */
u32 fmid; /* Flow meter id */
};
int sparx5_psfp_fm_add(struct sparx5 *sparx5, u32 uidx,
struct sparx5_psfp_fm *fm, u32 *id);
int sparx5_psfp_fm_del(struct sparx5 *sparx5, u32 id);
int sparx5_psfp_sg_add(struct sparx5 *sparx5, u32 uidx,
struct sparx5_psfp_sg *sg, u32 *id);
int sparx5_psfp_sg_del(struct sparx5 *sparx5, u32 id);
int sparx5_psfp_sf_add(struct sparx5 *sparx5, const struct sparx5_psfp_sf *sf,
u32 *id);
int sparx5_psfp_sf_del(struct sparx5 *sparx5, u32 id);
u32 sparx5_psfp_isdx_get_sf(struct sparx5 *sparx5, u32 isdx);
u32 sparx5_psfp_isdx_get_fm(struct sparx5 *sparx5, u32 isdx);
u32 sparx5_psfp_sf_get_sg(struct sparx5 *sparx5, u32 sfid);
void sparx5_isdx_conf_set(struct sparx5 *sparx5, u32 isdx, u32 sfid, u32 fmid);
void sparx5_psfp_init(struct sparx5 *sparx5);
/* sparx5_qos.c */
void sparx5_new_base_time(struct sparx5 *sparx5, const u32 cycle_time,
const ktime_t org_base_time, ktime_t *new_base_time);
/* sparx5_mirror.c */
int sparx5_mirror_add(struct sparx5_mall_entry *entry);
void sparx5_mirror_del(struct sparx5_mall_entry *entry);
void sparx5_mirror_stats(struct sparx5_mall_entry *entry,
struct flow_stats *fstats);
/* Clock period in picoseconds */
static inline u32 sparx5_clk_period(enum sparx5_core_clockfreq cclock)
{
switch (cclock) {
case SPX5_CORE_CLOCK_250MHZ:
return 4000;
case SPX5_CORE_CLOCK_328MHZ:
return 3048;
case SPX5_CORE_CLOCK_500MHZ:
return 2000;
case SPX5_CORE_CLOCK_625MHZ:
default:
return 1600;
}
}
static inline bool sparx5_is_baser(phy_interface_t interface)
{
return interface == PHY_INTERFACE_MODE_5GBASER ||
interface == PHY_INTERFACE_MODE_10GBASER ||
interface == PHY_INTERFACE_MODE_25GBASER;
}
extern const struct phylink_mac_ops sparx5_phylink_mac_ops;
extern const struct phylink_pcs_ops sparx5_phylink_pcs_ops;
extern const struct ethtool_ops sparx5_ethtool_ops;
extern const struct dcbnl_rtnl_ops sparx5_dcbnl_ops;
/* Calculate raw offset */
static inline __pure int spx5_offset(int id, int tinst, int tcnt,
int gbase, int ginst,
int gcnt, int gwidth,
int raddr, int rinst,
int rcnt, int rwidth)
{
WARN_ON((tinst) >= tcnt);
WARN_ON((ginst) >= gcnt);
WARN_ON((rinst) >= rcnt);
return gbase + ((ginst) * gwidth) +
raddr + ((rinst) * rwidth);
}
/* Read, Write and modify registers content.
* The register definition macros start at the id
*/
static inline void __iomem *spx5_addr(void __iomem *base[],
int id, int tinst, int tcnt,
int gbase, int ginst,
int gcnt, int gwidth,
int raddr, int rinst,
int rcnt, int rwidth)
{
WARN_ON((tinst) >= tcnt);
WARN_ON((ginst) >= gcnt);
WARN_ON((rinst) >= rcnt);
return base[id + (tinst)] +
gbase + ((ginst) * gwidth) +
raddr + ((rinst) * rwidth);
}
static inline void __iomem *spx5_inst_addr(void __iomem *base,
int gbase, int ginst,
int gcnt, int gwidth,
int raddr, int rinst,
int rcnt, int rwidth)
{
WARN_ON((ginst) >= gcnt);
WARN_ON((rinst) >= rcnt);
return base +
gbase + ((ginst) * gwidth) +
raddr + ((rinst) * rwidth);
}
static inline u32 spx5_rd(struct sparx5 *sparx5, int id, int tinst, int tcnt,
int gbase, int ginst, int gcnt, int gwidth,
int raddr, int rinst, int rcnt, int rwidth)
{
return readl(spx5_addr(sparx5->regs, id, tinst, tcnt, gbase, ginst,
gcnt, gwidth, raddr, rinst, rcnt, rwidth));
}
static inline u32 spx5_inst_rd(void __iomem *iomem, int id, int tinst, int tcnt,
int gbase, int ginst, int gcnt, int gwidth,
int raddr, int rinst, int rcnt, int rwidth)
{
return readl(spx5_inst_addr(iomem, gbase, ginst,
gcnt, gwidth, raddr, rinst, rcnt, rwidth));
}
static inline void spx5_wr(u32 val, struct sparx5 *sparx5,
int id, int tinst, int tcnt,
int gbase, int ginst, int gcnt, int gwidth,
int raddr, int rinst, int rcnt, int rwidth)
{
writel(val, spx5_addr(sparx5->regs, id, tinst, tcnt,
gbase, ginst, gcnt, gwidth,
raddr, rinst, rcnt, rwidth));
}
static inline void spx5_inst_wr(u32 val, void __iomem *iomem,
int id, int tinst, int tcnt,
int gbase, int ginst, int gcnt, int gwidth,
int raddr, int rinst, int rcnt, int rwidth)
{
writel(val, spx5_inst_addr(iomem,
gbase, ginst, gcnt, gwidth,
raddr, rinst, rcnt, rwidth));
}
static inline void spx5_rmw(u32 val, u32 mask, struct sparx5 *sparx5,
int id, int tinst, int tcnt,
int gbase, int ginst, int gcnt, int gwidth,
int raddr, int rinst, int rcnt, int rwidth)
{
u32 nval;
nval = readl(spx5_addr(sparx5->regs, id, tinst, tcnt, gbase, ginst,
gcnt, gwidth, raddr, rinst, rcnt, rwidth));
nval = (nval & ~mask) | (val & mask);
writel(nval, spx5_addr(sparx5->regs, id, tinst, tcnt, gbase, ginst,
gcnt, gwidth, raddr, rinst, rcnt, rwidth));
}
static inline void spx5_inst_rmw(u32 val, u32 mask, void __iomem *iomem,
int id, int tinst, int tcnt,
int gbase, int ginst, int gcnt, int gwidth,
int raddr, int rinst, int rcnt, int rwidth)
{
u32 nval;
nval = readl(spx5_inst_addr(iomem, gbase, ginst, gcnt, gwidth, raddr,
rinst, rcnt, rwidth));
nval = (nval & ~mask) | (val & mask);
writel(nval, spx5_inst_addr(iomem, gbase, ginst, gcnt, gwidth, raddr,
rinst, rcnt, rwidth));
}
static inline void __iomem *spx5_inst_get(struct sparx5 *sparx5, int id, int tinst)
{
return sparx5->regs[id + tinst];
}
static inline void __iomem *spx5_reg_get(struct sparx5 *sparx5,
int id, int tinst, int tcnt,
int gbase, int ginst, int gcnt, int gwidth,
int raddr, int rinst, int rcnt, int rwidth)
{
return spx5_addr(sparx5->regs, id, tinst, tcnt,
gbase, ginst, gcnt, gwidth,
raddr, rinst, rcnt, rwidth);
}
#endif /* __SPARX5_MAIN_H__ */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,240 @@
// SPDX-License-Identifier: GPL-2.0+
/* Microchip Sparx5 Switch driver
*
* Copyright (c) 2024 Microchip Technology Inc. and its subsidiaries.
*/
#include "sparx5_main.h"
#include "sparx5_main_regs.h"
#include "sparx5_tc.h"
#define SPX5_MIRROR_PROBE_MAX 3
#define SPX5_MIRROR_DISABLED 0
#define SPX5_MIRROR_EGRESS 1
#define SPX5_MIRROR_INGRESS 2
#define SPX5_QFWD_MP_OFFSET 9 /* Mirror port offset in the QFWD register */
/* Convert from bool ingress/egress to mirror direction */
static u32 sparx5_mirror_to_dir(bool ingress)
{
return ingress ? SPX5_MIRROR_INGRESS : SPX5_MIRROR_EGRESS;
}
/* Get ports belonging to this mirror */
static u64 sparx5_mirror_port_get(struct sparx5 *sparx5, u32 idx)
{
u64 val;
val = spx5_rd(sparx5, ANA_AC_PROBE_PORT_CFG(idx));
if (is_sparx5(sparx5))
val |= (u64)spx5_rd(sparx5, ANA_AC_PROBE_PORT_CFG1(idx)) << 32;
return val;
}
/* Add port to mirror (only front ports) */
static void sparx5_mirror_port_add(struct sparx5 *sparx5, u32 idx, u32 portno)
{
u64 reg = portno;
u32 val;
val = BIT(do_div(reg, 32));
if (reg == 0)
return spx5_rmw(val, val, sparx5, ANA_AC_PROBE_PORT_CFG(idx));
else
return spx5_rmw(val, val, sparx5, ANA_AC_PROBE_PORT_CFG1(idx));
}
/* Delete port from mirror (only front ports) */
static void sparx5_mirror_port_del(struct sparx5 *sparx5, u32 idx, u32 portno)
{
u64 reg = portno;
u32 val;
val = BIT(do_div(reg, 32));
if (reg == 0)
return spx5_rmw(0, val, sparx5, ANA_AC_PROBE_PORT_CFG(idx));
else
return spx5_rmw(0, val, sparx5, ANA_AC_PROBE_PORT_CFG1(idx));
}
/* Check if mirror contains port */
static bool sparx5_mirror_contains(struct sparx5 *sparx5, u32 idx, u32 portno)
{
return (sparx5_mirror_port_get(sparx5, idx) & BIT_ULL(portno)) != 0;
}
/* Check if mirror is empty */
static bool sparx5_mirror_is_empty(struct sparx5 *sparx5, u32 idx)
{
return sparx5_mirror_port_get(sparx5, idx) == 0;
}
/* Get direction of mirror */
static u32 sparx5_mirror_dir_get(struct sparx5 *sparx5, u32 idx)
{
u32 val = spx5_rd(sparx5, ANA_AC_PROBE_CFG(idx));
return ANA_AC_PROBE_CFG_PROBE_DIRECTION_GET(val);
}
/* Set direction of mirror */
static void sparx5_mirror_dir_set(struct sparx5 *sparx5, u32 idx, u32 dir)
{
spx5_rmw(ANA_AC_PROBE_CFG_PROBE_DIRECTION_SET(dir),
ANA_AC_PROBE_CFG_PROBE_DIRECTION, sparx5,
ANA_AC_PROBE_CFG(idx));
}
/* Set the monitor port for this mirror */
static void sparx5_mirror_monitor_set(struct sparx5 *sparx5, u32 idx,
u32 portno)
{
spx5_rmw(QFWD_FRAME_COPY_CFG_FRMC_PORT_VAL_SET(portno),
QFWD_FRAME_COPY_CFG_FRMC_PORT_VAL, sparx5,
QFWD_FRAME_COPY_CFG(idx + SPX5_QFWD_MP_OFFSET));
}
/* Get the monitor port of this mirror */
static u32 sparx5_mirror_monitor_get(struct sparx5 *sparx5, u32 idx)
{
u32 val = spx5_rd(sparx5,
QFWD_FRAME_COPY_CFG(idx + SPX5_QFWD_MP_OFFSET));
return QFWD_FRAME_COPY_CFG_FRMC_PORT_VAL_GET(val);
}
/* Check if port is the monitor port of this mirror */
static bool sparx5_mirror_has_monitor(struct sparx5 *sparx5, u32 idx,
u32 portno)
{
return sparx5_mirror_monitor_get(sparx5, idx) == portno;
}
/* Get a suitable mirror for this port */
static int sparx5_mirror_get(struct sparx5_port *sport,
struct sparx5_port *mport, u32 dir, u32 *idx)
{
struct sparx5 *sparx5 = sport->sparx5;
u32 i;
/* Check if this port is already used as a monitor port */
for (i = 0; i < SPX5_MIRROR_PROBE_MAX; i++)
if (sparx5_mirror_has_monitor(sparx5, i, sport->portno))
return -EINVAL;
/* Check if existing mirror can be reused
* (same direction and monitor port).
*/
for (i = 0; i < SPX5_MIRROR_PROBE_MAX; i++) {
if (sparx5_mirror_dir_get(sparx5, i) == dir &&
sparx5_mirror_has_monitor(sparx5, i, mport->portno)) {
*idx = i;
return 0;
}
}
/* Return free mirror */
for (i = 0; i < SPX5_MIRROR_PROBE_MAX; i++) {
if (sparx5_mirror_is_empty(sparx5, i)) {
*idx = i;
return 0;
}
}
return -ENOENT;
}
int sparx5_mirror_add(struct sparx5_mall_entry *entry)
{
u32 mirror_idx, dir = sparx5_mirror_to_dir(entry->ingress);
struct sparx5_port *sport, *mport;
struct sparx5 *sparx5;
int err;
/* Source port */
sport = entry->port;
/* monitor port */
mport = entry->mirror.port;
sparx5 = sport->sparx5;
if (sport->portno == mport->portno)
return -EINVAL;
err = sparx5_mirror_get(sport, mport, dir, &mirror_idx);
if (err)
return err;
if (sparx5_mirror_contains(sparx5, mirror_idx, sport->portno))
return -EEXIST;
/* Add port to mirror */
sparx5_mirror_port_add(sparx5, mirror_idx, sport->portno);
/* Set direction of mirror */
sparx5_mirror_dir_set(sparx5, mirror_idx, dir);
/* Set monitor port for mirror */
sparx5_mirror_monitor_set(sparx5, mirror_idx, mport->portno);
entry->mirror.idx = mirror_idx;
return 0;
}
void sparx5_mirror_del(struct sparx5_mall_entry *entry)
{
struct sparx5_port *port = entry->port;
struct sparx5 *sparx5 = port->sparx5;
u32 mirror_idx = entry->mirror.idx;
sparx5_mirror_port_del(sparx5, mirror_idx, port->portno);
if (!sparx5_mirror_is_empty(sparx5, mirror_idx))
return;
sparx5_mirror_dir_set(sparx5, mirror_idx, SPX5_MIRROR_DISABLED);
sparx5_mirror_monitor_set(sparx5,
mirror_idx,
sparx5->data->consts->n_ports);
}
void sparx5_mirror_stats(struct sparx5_mall_entry *entry,
struct flow_stats *fstats)
{
struct sparx5_port *port = entry->port;
struct rtnl_link_stats64 new_stats;
struct flow_stats *old_stats;
old_stats = &entry->port->mirror_stats;
sparx5_get_stats64(port->ndev, &new_stats);
if (entry->ingress) {
flow_stats_update(fstats,
new_stats.rx_bytes - old_stats->bytes,
new_stats.rx_packets - old_stats->pkts,
new_stats.rx_dropped - old_stats->drops,
old_stats->lastused,
FLOW_ACTION_HW_STATS_IMMEDIATE);
old_stats->bytes = new_stats.rx_bytes;
old_stats->pkts = new_stats.rx_packets;
old_stats->drops = new_stats.rx_dropped;
old_stats->lastused = jiffies;
} else {
flow_stats_update(fstats,
new_stats.tx_bytes - old_stats->bytes,
new_stats.tx_packets - old_stats->pkts,
new_stats.tx_dropped - old_stats->drops,
old_stats->lastused,
FLOW_ACTION_HW_STATS_IMMEDIATE);
old_stats->bytes = new_stats.tx_bytes;
old_stats->pkts = new_stats.tx_packets;
old_stats->drops = new_stats.tx_dropped;
old_stats->lastused = jiffies;
}
}
@@ -0,0 +1,341 @@
// SPDX-License-Identifier: GPL-2.0+
/* Microchip Sparx5 Switch driver
*
* Copyright (c) 2021 Microchip Technology Inc. and its subsidiaries.
*/
#include "sparx5_main_regs.h"
#include "sparx5_main.h"
#include "sparx5_port.h"
#include "sparx5_tc.h"
/* The IFH bit position of the first VSTAX bit. This is because the
* VSTAX bit positions in Data sheet is starting from zero.
*/
#define VSTAX 73
#define ifh_encode_bitfield(ifh, value, pos, _width) \
({ \
u32 width = (_width); \
\
/* Max width is 5 bytes - 40 bits. In worst case this will
* spread over 6 bytes - 48 bits
*/ \
compiletime_assert(width <= 40, \
"Unsupported width, must be <= 40"); \
__ifh_encode_bitfield((ifh), (value), (pos), width); \
})
static void __ifh_encode_bitfield(void *ifh, u64 value, u32 pos, u32 width)
{
u8 *ifh_hdr = ifh;
/* Calculate the Start IFH byte position of this IFH bit position */
u32 byte = (35 - (pos / 8));
/* Calculate the Start bit position in the Start IFH byte */
u32 bit = (pos % 8);
u64 encode = GENMASK_ULL(bit + width - 1, bit) & (value << bit);
/* The b0-b7 goes into the start IFH byte */
if (encode & 0xFF)
ifh_hdr[byte] |= (u8)((encode & 0xFF));
/* The b8-b15 goes into the next IFH byte */
if (encode & 0xFF00)
ifh_hdr[byte - 1] |= (u8)((encode & 0xFF00) >> 8);
/* The b16-b23 goes into the next IFH byte */
if (encode & 0xFF0000)
ifh_hdr[byte - 2] |= (u8)((encode & 0xFF0000) >> 16);
/* The b24-b31 goes into the next IFH byte */
if (encode & 0xFF000000)
ifh_hdr[byte - 3] |= (u8)((encode & 0xFF000000) >> 24);
/* The b32-b39 goes into the next IFH byte */
if (encode & 0xFF00000000)
ifh_hdr[byte - 4] |= (u8)((encode & 0xFF00000000) >> 32);
/* The b40-b47 goes into the next IFH byte */
if (encode & 0xFF0000000000)
ifh_hdr[byte - 5] |= (u8)((encode & 0xFF0000000000) >> 40);
}
void sparx5_set_port_ifh(struct sparx5 *sparx5, void *ifh_hdr, u16 portno)
{
/* VSTAX.RSV = 1. MSBit must be 1 */
ifh_encode_bitfield(ifh_hdr, 1, VSTAX + 79, 1);
/* VSTAX.INGR_DROP_MODE = Enable. Don't make head-of-line blocking */
ifh_encode_bitfield(ifh_hdr, 1, VSTAX + 55, 1);
/* MISC.CPU_MASK/DPORT = Destination port */
ifh_encode_bitfield(ifh_hdr, portno, 29, 8);
/* MISC.PIPELINE_PT */
ifh_encode_bitfield(ifh_hdr, is_sparx5(sparx5) ? 16 : 17, 37, 5);
/* MISC.PIPELINE_ACT */
ifh_encode_bitfield(ifh_hdr, 1, 42, 3);
/* FWD.SRC_PORT = CPU */
ifh_encode_bitfield(ifh_hdr, sparx5_get_pgid(sparx5, SPX5_PORT_CPU_0),
46, is_sparx5(sparx5) ? 7 : 6);
/* FWD.SFLOW_ID (disable SFlow sampling) */
ifh_encode_bitfield(ifh_hdr, 124, is_sparx5(sparx5) ? 57 : 56, 7);
/* FWD.UPDATE_FCS = Enable. Enforce update of FCS. */
ifh_encode_bitfield(ifh_hdr, 1, is_sparx5(sparx5) ? 67 : 66, 1);
}
void sparx5_set_port_ifh_rew_op(void *ifh_hdr, u32 rew_op)
{
ifh_encode_bitfield(ifh_hdr, rew_op, VSTAX + 32, 10);
}
void sparx5_set_port_ifh_pdu_type(struct sparx5 *sparx5, void *ifh_hdr,
u32 pdu_type)
{
ifh_encode_bitfield(ifh_hdr, pdu_type, is_sparx5(sparx5) ? 191 : 190,
4);
}
void sparx5_set_port_ifh_pdu_w16_offset(struct sparx5 *sparx5, void *ifh_hdr,
u32 pdu_w16_offset)
{
ifh_encode_bitfield(ifh_hdr, pdu_w16_offset,
is_sparx5(sparx5) ? 195 : 194, 6);
}
void sparx5_set_port_ifh_timestamp(struct sparx5 *sparx5, void *ifh_hdr,
u64 timestamp)
{
ifh_encode_bitfield(ifh_hdr, timestamp, 232,
is_sparx5(sparx5) ? 40 : 38);
}
static int sparx5_port_open(struct net_device *ndev)
{
struct sparx5_port *port = netdev_priv(ndev);
int err = 0;
sparx5_port_enable(port, true);
err = phylink_of_phy_connect(port->phylink, port->of_node, 0);
if (err) {
netdev_err(ndev, "Could not attach to PHY\n");
goto err_connect;
}
phylink_start(port->phylink);
if (!ndev->phydev) {
/* power up serdes */
port->conf.power_down = false;
if (port->conf.serdes_reset)
err = sparx5_serdes_set(port->sparx5, port, &port->conf);
else
err = phy_power_on(port->serdes);
if (err) {
netdev_err(ndev, "%s failed\n", __func__);
goto out_power;
}
}
return 0;
out_power:
phylink_stop(port->phylink);
phylink_disconnect_phy(port->phylink);
err_connect:
sparx5_port_enable(port, false);
return err;
}
static int sparx5_port_stop(struct net_device *ndev)
{
struct sparx5_port *port = netdev_priv(ndev);
int err = 0;
sparx5_port_enable(port, false);
phylink_stop(port->phylink);
phylink_disconnect_phy(port->phylink);
if (!ndev->phydev) {
/* power down serdes */
port->conf.power_down = true;
if (port->conf.serdes_reset)
err = sparx5_serdes_set(port->sparx5, port, &port->conf);
else
err = phy_power_off(port->serdes);
if (err)
netdev_err(ndev, "%s failed\n", __func__);
}
return 0;
}
static void sparx5_set_rx_mode(struct net_device *dev)
{
struct sparx5_port *port = netdev_priv(dev);
struct sparx5 *sparx5 = port->sparx5;
if (!test_bit(port->portno, sparx5->bridge_mask))
__dev_mc_sync(dev, sparx5_mc_sync, sparx5_mc_unsync);
}
static int sparx5_port_get_phys_port_name(struct net_device *dev,
char *buf, size_t len)
{
struct sparx5_port *port = netdev_priv(dev);
int ret;
ret = snprintf(buf, len, "p%d", port->portno);
if (ret >= len)
return -EINVAL;
return 0;
}
static int sparx5_set_mac_address(struct net_device *dev, void *p)
{
struct sparx5_port *port = netdev_priv(dev);
struct sparx5 *sparx5 = port->sparx5;
const struct sockaddr *addr = p;
if (!is_valid_ether_addr(addr->sa_data))
return -EADDRNOTAVAIL;
/* Remove current */
sparx5_mact_forget(sparx5, dev->dev_addr, port->pvid);
/* Add new */
sparx5_mact_learn(sparx5, sparx5_get_pgid(sparx5, PGID_CPU),
addr->sa_data, port->pvid);
/* Record the address */
eth_hw_addr_set(dev, addr->sa_data);
return 0;
}
static int sparx5_get_port_parent_id(struct net_device *dev,
struct netdev_phys_item_id *ppid)
{
struct sparx5_port *sparx5_port = netdev_priv(dev);
struct sparx5 *sparx5 = sparx5_port->sparx5;
ppid->id_len = sizeof(sparx5->base_mac);
memcpy(&ppid->id, &sparx5->base_mac, ppid->id_len);
return 0;
}
static int sparx5_port_hwtstamp_get(struct net_device *dev,
struct kernel_hwtstamp_config *cfg)
{
struct sparx5_port *sparx5_port = netdev_priv(dev);
struct sparx5 *sparx5 = sparx5_port->sparx5;
if (!sparx5->ptp)
return -EOPNOTSUPP;
sparx5_ptp_hwtstamp_get(sparx5_port, cfg);
return 0;
}
static int sparx5_port_hwtstamp_set(struct net_device *dev,
struct kernel_hwtstamp_config *cfg,
struct netlink_ext_ack *extack)
{
struct sparx5_port *sparx5_port = netdev_priv(dev);
struct sparx5 *sparx5 = sparx5_port->sparx5;
if (!sparx5->ptp)
return -EOPNOTSUPP;
return sparx5_ptp_hwtstamp_set(sparx5_port, cfg, extack);
}
static const struct net_device_ops sparx5_port_netdev_ops = {
.ndo_open = sparx5_port_open,
.ndo_stop = sparx5_port_stop,
.ndo_start_xmit = sparx5_port_xmit_impl,
.ndo_set_rx_mode = sparx5_set_rx_mode,
.ndo_get_phys_port_name = sparx5_port_get_phys_port_name,
.ndo_set_mac_address = sparx5_set_mac_address,
.ndo_validate_addr = eth_validate_addr,
.ndo_get_stats64 = sparx5_get_stats64,
.ndo_get_port_parent_id = sparx5_get_port_parent_id,
.ndo_eth_ioctl = phy_do_ioctl,
.ndo_setup_tc = sparx5_port_setup_tc,
.ndo_hwtstamp_get = sparx5_port_hwtstamp_get,
.ndo_hwtstamp_set = sparx5_port_hwtstamp_set,
};
bool sparx5_netdevice_check(const struct net_device *dev)
{
return dev && (dev->netdev_ops == &sparx5_port_netdev_ops);
}
struct net_device *sparx5_create_netdev(struct sparx5 *sparx5, u32 portno)
{
struct sparx5_port *spx5_port;
struct net_device *ndev;
ndev = devm_alloc_etherdev_mqs(sparx5->dev, sizeof(struct sparx5_port),
SPX5_PRIOS, 1);
if (!ndev)
return ERR_PTR(-ENOMEM);
ndev->hw_features |= NETIF_F_HW_TC;
ndev->features |= NETIF_F_HW_TC;
SET_NETDEV_DEV(ndev, sparx5->dev);
spx5_port = netdev_priv(ndev);
spx5_port->ndev = ndev;
spx5_port->sparx5 = sparx5;
spx5_port->portno = portno;
ndev->netdev_ops = &sparx5_port_netdev_ops;
ndev->ethtool_ops = &sparx5_ethtool_ops;
eth_hw_addr_gen(ndev, sparx5->base_mac, portno + 1);
return ndev;
}
int sparx5_register_netdevs(struct sparx5 *sparx5)
{
int portno;
int err;
for (portno = 0; portno < sparx5->data->consts->n_ports; portno++)
if (sparx5->ports[portno]) {
err = register_netdev(sparx5->ports[portno]->ndev);
if (err) {
dev_err(sparx5->dev,
"port: %02u: netdev registration failed\n",
portno);
return err;
}
sparx5_port_inj_timer_setup(sparx5->ports[portno]);
}
return 0;
}
void sparx5_destroy_netdevs(struct sparx5 *sparx5)
{
struct sparx5_port *port;
int portno;
for (portno = 0; portno < sparx5->data->consts->n_ports; portno++) {
port = sparx5->ports[portno];
if (port && port->phylink) {
/* Disconnect the phy */
rtnl_lock();
sparx5_port_stop(port->ndev);
phylink_disconnect_phy(port->phylink);
rtnl_unlock();
phylink_destroy(port->phylink);
port->phylink = NULL;
}
}
}
void sparx5_unregister_netdevs(struct sparx5 *sparx5)
{
int portno;
for (portno = 0; portno < sparx5->data->consts->n_ports; portno++)
if (sparx5->ports[portno])
unregister_netdev(sparx5->ports[portno]->ndev);
}
@@ -0,0 +1,380 @@
// SPDX-License-Identifier: GPL-2.0+
/* Microchip Sparx5 Switch driver
*
* Copyright (c) 2021 Microchip Technology Inc. and its subsidiaries.
*/
#include "sparx5_main_regs.h"
#include "sparx5_main.h"
#define XTR_EOF_0 ntohl((__force __be32)0x80000000u)
#define XTR_EOF_1 ntohl((__force __be32)0x80000001u)
#define XTR_EOF_2 ntohl((__force __be32)0x80000002u)
#define XTR_EOF_3 ntohl((__force __be32)0x80000003u)
#define XTR_PRUNED ntohl((__force __be32)0x80000004u)
#define XTR_ABORT ntohl((__force __be32)0x80000005u)
#define XTR_ESCAPE ntohl((__force __be32)0x80000006u)
#define XTR_NOT_READY ntohl((__force __be32)0x80000007u)
#define XTR_VALID_BYTES(x) (4 - ((x) & 3))
#define INJ_TIMEOUT_NS 50000
void sparx5_xtr_flush(struct sparx5 *sparx5, u8 grp)
{
/* Start flush */
spx5_wr(QS_XTR_FLUSH_FLUSH_SET(BIT(grp)), sparx5, QS_XTR_FLUSH);
/* Allow to drain */
mdelay(1);
/* All Queues normal */
spx5_wr(0, sparx5, QS_XTR_FLUSH);
}
void sparx5_ifh_parse(struct sparx5 *sparx5, u32 *ifh, struct frame_info *info)
{
u8 *xtr_hdr = (u8 *)ifh;
/* FWD is bit 45-72 (28 bits), but we only read the 27 LSB for now */
u32 fwd =
((u32)xtr_hdr[27] << 24) |
((u32)xtr_hdr[28] << 16) |
((u32)xtr_hdr[29] << 8) |
((u32)xtr_hdr[30] << 0);
fwd = (fwd >> 5);
info->src_port = spx5_field_get(GENMASK(is_sparx5(sparx5) ? 7 : 6, 1),
fwd);
/*
* Bit 270-271 are occasionally unexpectedly set by the hardware,
* clear bits before extracting timestamp
*/
info->timestamp =
((u64)(xtr_hdr[2] & GENMASK(5, 0)) << 24) |
((u64)xtr_hdr[3] << 16) |
((u64)xtr_hdr[4] << 8) |
((u64)xtr_hdr[5] << 0);
}
static void sparx5_xtr_grp(struct sparx5 *sparx5, u8 grp, bool byte_swap)
{
bool eof_flag = false, pruned_flag = false, abort_flag = false;
struct net_device *netdev;
struct sparx5_port *port;
struct frame_info fi;
int i, byte_cnt = 0;
struct sk_buff *skb;
u32 ifh[IFH_LEN];
u32 *rxbuf;
/* Get IFH */
for (i = 0; i < IFH_LEN; i++)
ifh[i] = spx5_rd(sparx5, QS_XTR_RD(grp));
/* Decode IFH (what's needed) */
sparx5_ifh_parse(sparx5, ifh, &fi);
/* Map to port netdev */
port = fi.src_port < sparx5->data->consts->n_ports ?
sparx5->ports[fi.src_port] : NULL;
if (!port || !port->ndev) {
dev_err(sparx5->dev, "Data on inactive port %d\n", fi.src_port);
sparx5_xtr_flush(sparx5, grp);
return;
}
/* Have netdev, get skb */
netdev = port->ndev;
skb = netdev_alloc_skb(netdev, netdev->mtu + ETH_HLEN);
if (!skb) {
sparx5_xtr_flush(sparx5, grp);
dev_err(sparx5->dev, "No skb allocated\n");
netdev->stats.rx_dropped++;
return;
}
rxbuf = (u32 *)skb->data;
/* Now, pull frame data */
while (!eof_flag) {
u32 val = spx5_rd(sparx5, QS_XTR_RD(grp));
u32 cmp = val;
if (byte_swap)
cmp = ntohl((__force __be32)val);
switch (cmp) {
case XTR_NOT_READY:
break;
case XTR_ABORT:
/* No accompanying data */
abort_flag = true;
eof_flag = true;
break;
case XTR_EOF_0:
case XTR_EOF_1:
case XTR_EOF_2:
case XTR_EOF_3:
/* This assumes STATUS_WORD_POS == 1, Status
* just after last data
*/
if (!byte_swap)
val = ntohl((__force __be32)val);
byte_cnt -= (4 - XTR_VALID_BYTES(val));
eof_flag = true;
break;
case XTR_PRUNED:
/* But get the last 4 bytes as well */
eof_flag = true;
pruned_flag = true;
fallthrough;
case XTR_ESCAPE:
*rxbuf = spx5_rd(sparx5, QS_XTR_RD(grp));
byte_cnt += 4;
rxbuf++;
break;
default:
*rxbuf = val;
byte_cnt += 4;
rxbuf++;
}
}
if (abort_flag || pruned_flag || !eof_flag) {
netdev_err(netdev, "Discarded frame: abort:%d pruned:%d eof:%d\n",
abort_flag, pruned_flag, eof_flag);
kfree_skb(skb);
netdev->stats.rx_dropped++;
return;
}
/* Everything we see on an interface that is in the HW bridge
* has already been forwarded
*/
if (test_bit(port->portno, sparx5->bridge_mask))
skb->offload_fwd_mark = 1;
/* Finish up skb */
skb_put(skb, byte_cnt - ETH_FCS_LEN);
eth_skb_pad(skb);
sparx5_ptp_rxtstamp(sparx5, skb, fi.timestamp);
skb->protocol = eth_type_trans(skb, netdev);
netdev->stats.rx_bytes += skb->len;
netdev->stats.rx_packets++;
netif_rx(skb);
}
static int sparx5_inject(struct sparx5 *sparx5,
u32 *ifh,
struct sk_buff *skb,
struct net_device *ndev)
{
int grp = INJ_QUEUE;
u32 val, w, count;
u8 *buf;
val = spx5_rd(sparx5, QS_INJ_STATUS);
if (!(QS_INJ_STATUS_FIFO_RDY_GET(val) & BIT(grp))) {
pr_err_ratelimited("Injection: Queue not ready: 0x%lx\n",
QS_INJ_STATUS_FIFO_RDY_GET(val));
return -EBUSY;
}
/* Indicate SOF */
spx5_wr(QS_INJ_CTRL_SOF_SET(1) |
QS_INJ_CTRL_GAP_SIZE_SET(1),
sparx5, QS_INJ_CTRL(grp));
/* Write the IFH to the chip. */
for (w = 0; w < IFH_LEN; w++)
spx5_wr(ifh[w], sparx5, QS_INJ_WR(grp));
/* Write words, round up */
count = DIV_ROUND_UP(skb->len, 4);
buf = skb->data;
for (w = 0; w < count; w++, buf += 4) {
val = get_unaligned((const u32 *)buf);
spx5_wr(val, sparx5, QS_INJ_WR(grp));
}
/* Add padding */
while (w < (60 / 4)) {
spx5_wr(0, sparx5, QS_INJ_WR(grp));
w++;
}
/* Indicate EOF and valid bytes in last word */
spx5_wr(QS_INJ_CTRL_GAP_SIZE_SET(1) |
QS_INJ_CTRL_VLD_BYTES_SET(skb->len < 60 ? 0 : skb->len % 4) |
QS_INJ_CTRL_EOF_SET(1),
sparx5, QS_INJ_CTRL(grp));
/* Add dummy CRC */
spx5_wr(0, sparx5, QS_INJ_WR(grp));
w++;
val = spx5_rd(sparx5, QS_INJ_STATUS);
if (QS_INJ_STATUS_WMARK_REACHED_GET(val) & BIT(grp)) {
struct sparx5_port *port = netdev_priv(ndev);
pr_err_ratelimited("Injection: Watermark reached: 0x%lx\n",
QS_INJ_STATUS_WMARK_REACHED_GET(val));
netif_stop_queue(ndev);
hrtimer_start(&port->inj_timer, INJ_TIMEOUT_NS,
HRTIMER_MODE_REL);
}
return NETDEV_TX_OK;
}
netdev_tx_t sparx5_port_xmit_impl(struct sk_buff *skb, struct net_device *dev)
{
struct net_device_stats *stats = &dev->stats;
struct sparx5_port *port = netdev_priv(dev);
struct sparx5 *sparx5 = port->sparx5;
const struct sparx5_ops *ops;
u32 ifh[IFH_LEN];
netdev_tx_t ret;
ops = sparx5->data->ops;
memset(ifh, 0, IFH_LEN * 4);
sparx5_set_port_ifh(sparx5, ifh, port->portno);
if (sparx5->ptp && skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP) {
if (sparx5_ptp_txtstamp_request(port, skb) < 0)
return NETDEV_TX_BUSY;
sparx5_set_port_ifh_rew_op(ifh, SPARX5_SKB_CB(skb)->rew_op);
sparx5_set_port_ifh_pdu_type(sparx5, ifh,
SPARX5_SKB_CB(skb)->pdu_type);
sparx5_set_port_ifh_pdu_w16_offset(sparx5, ifh,
SPARX5_SKB_CB(skb)->pdu_w16_offset);
sparx5_set_port_ifh_timestamp(sparx5, ifh,
SPARX5_SKB_CB(skb)->ts_id);
}
skb_tx_timestamp(skb);
spin_lock(&sparx5->tx_lock);
if (sparx5->fdma_irq > 0)
ret = ops->fdma_xmit(sparx5, ifh, skb, dev);
else
ret = sparx5_inject(sparx5, ifh, skb, dev);
spin_unlock(&sparx5->tx_lock);
if (ret == -EBUSY)
goto busy;
if (ret < 0)
goto drop;
if (!is_sparx5(sparx5))
/* When lan969x and TX_OK, stats and SKB consumption is handled
* in the TX completion loop, so dont go any further.
*/
return NETDEV_TX_OK;
stats->tx_bytes += skb->len;
stats->tx_packets++;
sparx5->tx.packets++;
if (skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP &&
SPARX5_SKB_CB(skb)->rew_op == IFH_REW_OP_TWO_STEP_PTP)
return NETDEV_TX_OK;
dev_consume_skb_any(skb);
return NETDEV_TX_OK;
drop:
stats->tx_dropped++;
sparx5->tx.dropped++;
dev_kfree_skb_any(skb);
return NETDEV_TX_OK;
busy:
if (skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP &&
SPARX5_SKB_CB(skb)->rew_op == IFH_REW_OP_TWO_STEP_PTP)
sparx5_ptp_txtstamp_release(port, skb);
return NETDEV_TX_BUSY;
}
static enum hrtimer_restart sparx5_injection_timeout(struct hrtimer *tmr)
{
struct sparx5_port *port = container_of(tmr, struct sparx5_port,
inj_timer);
int grp = INJ_QUEUE;
u32 val;
val = spx5_rd(port->sparx5, QS_INJ_STATUS);
if (QS_INJ_STATUS_WMARK_REACHED_GET(val) & BIT(grp)) {
pr_err_ratelimited("Injection: Reset watermark count\n");
/* Reset Watermark count to restart */
spx5_rmw(DSM_DEV_TX_STOP_WM_CFG_DEV_TX_CNT_CLR_SET(1),
DSM_DEV_TX_STOP_WM_CFG_DEV_TX_CNT_CLR,
port->sparx5,
DSM_DEV_TX_STOP_WM_CFG(port->portno));
}
netif_wake_queue(port->ndev);
return HRTIMER_NORESTART;
}
int sparx5_manual_injection_mode(struct sparx5 *sparx5)
{
const int byte_swap = 1;
int portno;
/* Change mode to manual extraction and injection */
spx5_wr(QS_XTR_GRP_CFG_MODE_SET(1) |
QS_XTR_GRP_CFG_STATUS_WORD_POS_SET(1) |
QS_XTR_GRP_CFG_BYTE_SWAP_SET(byte_swap),
sparx5, QS_XTR_GRP_CFG(XTR_QUEUE));
spx5_wr(QS_INJ_GRP_CFG_MODE_SET(1) |
QS_INJ_GRP_CFG_BYTE_SWAP_SET(byte_swap),
sparx5, QS_INJ_GRP_CFG(INJ_QUEUE));
/* CPU ports capture setup */
for (portno = sparx5_get_internal_port(sparx5, SPX5_PORT_CPU_0);
portno <= sparx5_get_internal_port(sparx5, SPX5_PORT_CPU_1);
portno++) {
/* ASM CPU port: No preamble, IFH, enable padding */
spx5_wr(ASM_PORT_CFG_PAD_ENA_SET(1) |
ASM_PORT_CFG_NO_PREAMBLE_ENA_SET(1) |
ASM_PORT_CFG_INJ_FORMAT_CFG_SET(1), /* 1 = IFH */
sparx5, ASM_PORT_CFG(portno));
/* Reset WM cnt to unclog queued frames */
spx5_rmw(DSM_DEV_TX_STOP_WM_CFG_DEV_TX_CNT_CLR_SET(1),
DSM_DEV_TX_STOP_WM_CFG_DEV_TX_CNT_CLR,
sparx5,
DSM_DEV_TX_STOP_WM_CFG(portno));
/* Set Disassembler Stop Watermark level */
spx5_rmw(DSM_DEV_TX_STOP_WM_CFG_DEV_TX_STOP_WM_SET(0),
DSM_DEV_TX_STOP_WM_CFG_DEV_TX_STOP_WM,
sparx5,
DSM_DEV_TX_STOP_WM_CFG(portno));
/* Enable Disassembler buffer underrun watchdog
*/
spx5_rmw(DSM_BUF_CFG_UNDERFLOW_WATCHDOG_DIS_SET(0),
DSM_BUF_CFG_UNDERFLOW_WATCHDOG_DIS,
sparx5,
DSM_BUF_CFG(portno));
}
return 0;
}
irqreturn_t sparx5_xtr_handler(int irq, void *_sparx5)
{
struct sparx5 *s5 = _sparx5;
int poll = 64;
/* Check data in queue */
while (spx5_rd(s5, QS_XTR_DATA_PRESENT) & BIT(XTR_QUEUE) && poll-- > 0)
sparx5_xtr_grp(s5, XTR_QUEUE, false);
return IRQ_HANDLED;
}
void sparx5_port_inj_timer_setup(struct sparx5_port *port)
{
hrtimer_setup(&port->inj_timer, sparx5_injection_timeout, CLOCK_MONOTONIC,
HRTIMER_MODE_REL);
}
@@ -0,0 +1,53 @@
// SPDX-License-Identifier: GPL-2.0+
#include "sparx5_main.h"
void sparx5_pgid_init(struct sparx5 *spx5)
{
int i;
for (i = 0; i < spx5->data->consts->n_pgids; i++)
spx5->pgid_map[i] = SPX5_PGID_FREE;
/* Reserved for unicast, flood control, broadcast, and CPU.
* These cannot be freed.
*/
for (i = 0; i <= sparx5_get_pgid(spx5, PGID_CPU); i++)
spx5->pgid_map[i] = SPX5_PGID_RESERVED;
}
int sparx5_pgid_alloc_mcast(struct sparx5 *spx5, u16 *idx)
{
int i;
/* The multicast area starts at index 65, but the first 7
* are reserved for flood masks and CPU. Start alloc after that.
*/
for (i = sparx5_get_pgid(spx5, PGID_MCAST_START);
i < spx5->data->consts->n_pgids; i++) {
if (spx5->pgid_map[i] == SPX5_PGID_FREE) {
spx5->pgid_map[i] = SPX5_PGID_MULTICAST;
*idx = i;
return 0;
}
}
return -EBUSY;
}
int sparx5_pgid_free(struct sparx5 *spx5, u16 idx)
{
if (idx <= sparx5_get_pgid(spx5, PGID_CPU) ||
idx >= spx5->data->consts->n_pgids)
return -EINVAL;
if (spx5->pgid_map[idx] == SPX5_PGID_FREE)
return -EINVAL;
spx5->pgid_map[idx] = SPX5_PGID_FREE;
return 0;
}
int sparx5_get_pgid(struct sparx5 *sparx5, int pgid)
{
return sparx5->data->consts->n_ports + pgid;
}
@@ -0,0 +1,157 @@
// 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/phylink.h>
#include <linux/device.h>
#include <linux/netdevice.h>
#include <linux/sfp.h>
#include "sparx5_main_regs.h"
#include "sparx5_main.h"
#include "sparx5_port.h"
static bool port_conf_has_changed(struct sparx5_port_config *a, struct sparx5_port_config *b)
{
if (a->speed != b->speed ||
a->portmode != b->portmode ||
a->autoneg != b->autoneg ||
a->pause_adv != b->pause_adv ||
a->power_down != b->power_down ||
a->media != b->media)
return true;
return false;
}
static struct phylink_pcs *
sparx5_phylink_mac_select_pcs(struct phylink_config *config,
phy_interface_t interface)
{
struct sparx5_port *port = netdev_priv(to_net_dev(config->dev));
/* Return the PCS for all the modes that require it. */
switch (interface) {
case PHY_INTERFACE_MODE_SGMII:
case PHY_INTERFACE_MODE_QSGMII:
case PHY_INTERFACE_MODE_1000BASEX:
case PHY_INTERFACE_MODE_2500BASEX:
case PHY_INTERFACE_MODE_5GBASER:
case PHY_INTERFACE_MODE_10GBASER:
case PHY_INTERFACE_MODE_25GBASER:
return &port->phylink_pcs;
default:
return NULL;
}
}
static void sparx5_phylink_mac_config(struct phylink_config *config,
unsigned int mode,
const struct phylink_link_state *state)
{
/* Currently not used */
}
static void sparx5_phylink_mac_link_up(struct phylink_config *config,
struct phy_device *phy,
unsigned int mode,
phy_interface_t interface,
int speed, int duplex,
bool tx_pause, bool rx_pause)
{
struct sparx5_port *port = netdev_priv(to_net_dev(config->dev));
struct sparx5_port_config conf;
int err;
conf = port->conf;
conf.duplex = duplex;
conf.pause = 0;
conf.pause |= tx_pause ? MLO_PAUSE_TX : 0;
conf.pause |= rx_pause ? MLO_PAUSE_RX : 0;
conf.speed = speed;
/* Configure the port to speed/duplex/pause */
err = sparx5_port_config(port->sparx5, port, &conf);
if (err)
netdev_err(port->ndev, "port config failed: %d\n", err);
}
static void sparx5_phylink_mac_link_down(struct phylink_config *config,
unsigned int mode,
phy_interface_t interface)
{
/* Currently not used */
}
static struct sparx5_port *sparx5_pcs_to_port(struct phylink_pcs *pcs)
{
return container_of(pcs, struct sparx5_port, phylink_pcs);
}
static void sparx5_pcs_get_state(struct phylink_pcs *pcs, unsigned int neg_mode,
struct phylink_link_state *state)
{
struct sparx5_port *port = sparx5_pcs_to_port(pcs);
struct sparx5_port_status status;
sparx5_get_port_status(port->sparx5, port, &status);
state->link = status.link && !status.link_down;
state->an_complete = status.an_complete;
state->speed = status.speed;
state->duplex = status.duplex;
state->pause = status.pause;
}
static int sparx5_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode,
phy_interface_t interface,
const unsigned long *advertising,
bool permit_pause_to_mac)
{
struct sparx5_port *port = sparx5_pcs_to_port(pcs);
struct sparx5_port_config conf;
int ret = 0;
conf = port->conf;
conf.power_down = false;
conf.portmode = interface;
conf.inband = neg_mode == PHYLINK_PCS_NEG_INBAND_DISABLED ||
neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED;
conf.autoneg = neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED;
conf.pause_adv = 0;
if (phylink_test(advertising, Pause))
conf.pause_adv |= ADVERTISE_1000XPAUSE;
if (phylink_test(advertising, Asym_Pause))
conf.pause_adv |= ADVERTISE_1000XPSE_ASYM;
if (sparx5_is_baser(interface)) {
if (phylink_test(advertising, FIBRE))
conf.media = PHY_MEDIA_SR;
else
conf.media = PHY_MEDIA_DAC;
}
if (!port_conf_has_changed(&port->conf, &conf))
return ret;
/* Enable the PCS matching this interface type */
ret = sparx5_port_pcs_set(port->sparx5, port, &conf);
if (ret)
netdev_err(port->ndev, "port PCS config failed: %d\n", ret);
return ret;
}
static void sparx5_pcs_aneg_restart(struct phylink_pcs *pcs)
{
/* Currently not used */
}
const struct phylink_pcs_ops sparx5_phylink_pcs_ops = {
.pcs_get_state = sparx5_pcs_get_state,
.pcs_config = sparx5_pcs_config,
.pcs_an_restart = sparx5_pcs_aneg_restart,
};
const struct phylink_mac_ops sparx5_phylink_mac_ops = {
.mac_select_pcs = sparx5_phylink_mac_select_pcs,
.mac_config = sparx5_phylink_mac_config,
.mac_link_down = sparx5_phylink_mac_link_down,
.mac_link_up = sparx5_phylink_mac_link_up,
};
@@ -0,0 +1,54 @@
// SPDX-License-Identifier: GPL-2.0+
/* Microchip Sparx5 Switch driver
*
* Copyright (c) 2023 Microchip Technology Inc. and its subsidiaries.
*/
#include "sparx5_main_regs.h"
#include "sparx5_main.h"
static int sparx5_policer_service_conf_set(struct sparx5 *sparx5,
struct sparx5_policer *pol)
{
u32 idx, pup_tokens, max_pup_tokens, burst, thres;
const struct sparx5_ops *ops = sparx5->data->ops;
struct sparx5_sdlb_group *g;
u64 rate;
g = ops->get_sdlb_group(pol->group);
idx = pol->idx;
rate = pol->rate * 1000;
burst = pol->burst;
pup_tokens = sparx5_sdlb_pup_token_get(sparx5, g->pup_interval, rate);
max_pup_tokens =
sparx5_sdlb_pup_token_get(sparx5, g->pup_interval, g->max_rate);
thres = DIV_ROUND_UP(burst, g->min_burst);
spx5_wr(ANA_AC_SDLB_PUP_TOKENS_PUP_TOKENS_SET(pup_tokens), sparx5,
ANA_AC_SDLB_PUP_TOKENS(idx, 0));
spx5_rmw(ANA_AC_SDLB_INH_CTRL_PUP_TOKENS_MAX_SET(max_pup_tokens),
ANA_AC_SDLB_INH_CTRL_PUP_TOKENS_MAX, sparx5,
ANA_AC_SDLB_INH_CTRL(idx, 0));
spx5_rmw(ANA_AC_SDLB_THRES_THRES_SET(thres), ANA_AC_SDLB_THRES_THRES,
sparx5, ANA_AC_SDLB_THRES(idx, 0));
return 0;
}
int sparx5_policer_conf_set(struct sparx5 *sparx5, struct sparx5_policer *pol)
{
/* More policer types will be added later */
switch (pol->type) {
case SPX5_POL_SERVICE:
return sparx5_policer_service_conf_set(sparx5, pol);
default:
break;
}
return 0;
}
@@ -0,0 +1,81 @@
// SPDX-License-Identifier: GPL-2.0+
/* Microchip Sparx5 Switch driver
*
* Copyright (c) 2023 Microchip Technology Inc. and its subsidiaries.
*/
#include "sparx5_main_regs.h"
#include "sparx5_main.h"
static u32 sparx5_pool_id_to_idx(u32 id)
{
return --id;
}
u32 sparx5_pool_idx_to_id(u32 idx)
{
return ++idx;
}
/* Release resource from pool.
* Return reference count on success, otherwise return error.
*/
int sparx5_pool_put(struct sparx5_pool_entry *pool, int size, u32 id)
{
struct sparx5_pool_entry *e_itr;
e_itr = (pool + sparx5_pool_id_to_idx(id));
if (e_itr->ref_cnt == 0)
return -EINVAL;
return --e_itr->ref_cnt;
}
/* Get resource from pool.
* Return reference count on success, otherwise return error.
*/
int sparx5_pool_get(struct sparx5_pool_entry *pool, int size, u32 *id)
{
struct sparx5_pool_entry *e_itr;
int i;
for (i = 0, e_itr = pool; i < size; i++, e_itr++) {
if (e_itr->ref_cnt == 0) {
*id = sparx5_pool_idx_to_id(i);
return ++e_itr->ref_cnt;
}
}
return -ENOSPC;
}
/* Get resource from pool that matches index.
* Return reference count on success, otherwise return error.
*/
int sparx5_pool_get_with_idx(struct sparx5_pool_entry *pool, int size, u32 idx,
u32 *id)
{
struct sparx5_pool_entry *e_itr;
int i, ret = -ENOSPC;
for (i = 0, e_itr = pool; i < size; i++, e_itr++) {
/* Pool index of first free entry */
if (e_itr->ref_cnt == 0 && ret == -ENOSPC)
ret = i;
/* Tc index already in use ? */
if (e_itr->idx == idx && e_itr->ref_cnt > 0) {
ret = i;
break;
}
}
/* Did we find a free entry? */
if (ret >= 0) {
*id = sparx5_pool_idx_to_id(ret);
e_itr = (pool + ret);
e_itr->idx = idx;
return ++e_itr->ref_cnt;
}
return ret;
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,191 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/* Microchip Sparx5 Switch driver
*
* Copyright (c) 2021 Microchip Technology Inc. and its subsidiaries.
*/
#ifndef __SPARX5_PORT_H__
#define __SPARX5_PORT_H__
#include "sparx5_main.h"
/* Port PCP rewrite mode */
#define SPARX5_PORT_REW_TAG_CTRL_CLASSIFIED 0
#define SPARX5_PORT_REW_TAG_CTRL_DEFAULT 1
#define SPARX5_PORT_REW_TAG_CTRL_MAPPED 2
/* Port DSCP rewrite mode */
#define SPARX5_PORT_REW_DSCP_NONE 0
#define SPARX5_PORT_REW_DSCP_IF_ZERO 1
#define SPARX5_PORT_REW_DSCP_SELECTED 2
#define SPARX5_PORT_REW_DSCP_ALL 3
static inline bool sparx5_port_is_2g5(int portno)
{
return portno >= 16 && portno <= 47;
}
static inline bool sparx5_port_is_5g(int portno)
{
return portno <= 11 || portno == 64;
}
static inline bool sparx5_port_is_10g(int portno)
{
return (portno >= 12 && portno <= 15) || (portno >= 48 && portno <= 55);
}
static inline bool sparx5_port_is_25g(int portno)
{
return portno >= 56 && portno <= 63;
}
static inline bool sparx5_port_is_rgmii(int portno)
{
return false;
}
static inline u32 sparx5_to_high_dev(struct sparx5 *sparx5, int port)
{
const struct sparx5_ops *ops = sparx5->data->ops;
if (ops->is_port_5g(port))
return TARGET_DEV5G;
if (ops->is_port_10g(port))
return TARGET_DEV10G;
return TARGET_DEV25G;
}
static inline u32 sparx5_to_pcs_dev(struct sparx5 *sparx5, int port)
{
const struct sparx5_ops *ops = sparx5->data->ops;
if (ops->is_port_5g(port))
return TARGET_PCS5G_BR;
if (ops->is_port_10g(port))
return TARGET_PCS10G_BR;
return TARGET_PCS25G_BR;
}
static inline u32 sparx5_port_dev_mapping(struct sparx5 *sparx5, int port)
{
if (sparx5_port_is_2g5(port))
return port;
if (sparx5_port_is_5g(port))
return (port <= 11 ? port : 12);
if (sparx5_port_is_10g(port))
return (port >= 12 && port <= 15) ?
port - 12 : port - 44;
return (port - 56);
}
static inline u32 sparx5_port_dev_index(struct sparx5 *sparx5, int port)
{
return sparx5->data->ops->get_port_dev_index(sparx5, port);
}
int sparx5_port_init(struct sparx5 *sparx5,
struct sparx5_port *spx5_port,
struct sparx5_port_config *conf);
int sparx5_port_config(struct sparx5 *sparx5,
struct sparx5_port *spx5_port,
struct sparx5_port_config *conf);
int sparx5_port_pcs_set(struct sparx5 *sparx5,
struct sparx5_port *port,
struct sparx5_port_config *conf);
int sparx5_serdes_set(struct sparx5 *sparx5,
struct sparx5_port *spx5_port,
struct sparx5_port_config *conf);
struct sparx5_port_status {
bool link;
bool link_down;
int speed;
bool an_complete;
int duplex;
int pause;
};
int sparx5_get_port_status(struct sparx5 *sparx5,
struct sparx5_port *port,
struct sparx5_port_status *status);
void sparx5_port_enable(struct sparx5_port *port, bool enable);
int sparx5_port_fwd_urg(struct sparx5 *sparx5, u32 speed);
#define SPARX5_PORT_QOS_PCP_COUNT 8
#define SPARX5_PORT_QOS_DEI_COUNT 8
#define SPARX5_PORT_QOS_PCP_DEI_COUNT \
(SPARX5_PORT_QOS_PCP_COUNT + SPARX5_PORT_QOS_DEI_COUNT)
struct sparx5_port_qos_pcp_map {
u8 map[SPARX5_PORT_QOS_PCP_DEI_COUNT];
};
struct sparx5_port_qos_pcp_rewr_map {
u16 map[SPX5_PRIOS];
};
#define SPARX5_PORT_QOS_DP_NUM 4
struct sparx5_port_qos_dscp_rewr_map {
u16 map[SPX5_PRIOS * SPARX5_PORT_QOS_DP_NUM];
};
#define SPARX5_PORT_QOS_DSCP_COUNT 64
struct sparx5_port_qos_dscp_map {
u8 map[SPARX5_PORT_QOS_DSCP_COUNT];
};
struct sparx5_port_qos_pcp {
struct sparx5_port_qos_pcp_map map;
bool qos_enable;
bool dp_enable;
};
struct sparx5_port_qos_pcp_rewr {
struct sparx5_port_qos_pcp_rewr_map map;
bool enable;
};
struct sparx5_port_qos_dscp {
struct sparx5_port_qos_dscp_map map;
bool qos_enable;
bool dp_enable;
};
struct sparx5_port_qos_dscp_rewr {
struct sparx5_port_qos_dscp_rewr_map map;
bool enable;
};
struct sparx5_port_qos {
struct sparx5_port_qos_pcp pcp;
struct sparx5_port_qos_pcp_rewr pcp_rewr;
struct sparx5_port_qos_dscp dscp;
struct sparx5_port_qos_dscp_rewr dscp_rewr;
u8 default_prio;
};
int sparx5_port_qos_set(struct sparx5_port *port, struct sparx5_port_qos *qos);
int sparx5_port_qos_pcp_set(const struct sparx5_port *port,
struct sparx5_port_qos_pcp *qos);
int sparx5_port_qos_pcp_rewr_set(const struct sparx5_port *port,
struct sparx5_port_qos_pcp_rewr *qos);
int sparx5_port_qos_dscp_set(const struct sparx5_port *port,
struct sparx5_port_qos_dscp *qos);
void sparx5_port_qos_dscp_rewr_mode_set(const struct sparx5_port *port,
int mode);
int sparx5_port_qos_dscp_rewr_set(const struct sparx5_port *port,
struct sparx5_port_qos_dscp_rewr *qos);
int sparx5_port_qos_default_set(const struct sparx5_port *port,
const struct sparx5_port_qos *qos);
#endif /* __SPARX5_PORT_H__ */
@@ -0,0 +1,338 @@
// SPDX-License-Identifier: GPL-2.0+
/* Microchip Sparx5 Switch driver
*
* Copyright (c) 2023 Microchip Technology Inc. and its subsidiaries.
*/
#include "sparx5_main_regs.h"
#include "sparx5_main.h"
#define SPX5_PSFP_SF_CNT 1024
#define SPX5_PSFP_SG_CONFIG_CHANGE_SLEEP 1000
#define SPX5_PSFP_SG_CONFIG_CHANGE_TIMEO 100000
/* Pool of available service policers */
static struct sparx5_pool_entry sparx5_psfp_fm_pool[SPX5_SDLB_CNT];
/* Pool of available stream gates */
static struct sparx5_pool_entry sparx5_psfp_sg_pool[SPX5_PSFP_SG_CNT];
/* Pool of available stream filters */
static struct sparx5_pool_entry sparx5_psfp_sf_pool[SPX5_PSFP_SF_CNT];
static int sparx5_psfp_sf_get(struct sparx5 *sparx5, u32 *id)
{
return sparx5_pool_get(sparx5_psfp_sf_pool,
sparx5->data->consts->n_filters, id);
}
static int sparx5_psfp_sf_put(struct sparx5 *sparx5, u32 id)
{
return sparx5_pool_put(sparx5_psfp_sf_pool,
sparx5->data->consts->n_filters, id);
}
static int sparx5_psfp_sg_get(struct sparx5 *sparx5, u32 idx, u32 *id)
{
return sparx5_pool_get_with_idx(sparx5_psfp_sg_pool,
sparx5->data->consts->n_gates, idx, id);
}
static int sparx5_psfp_sg_put(struct sparx5 *sparx5, u32 id)
{
return sparx5_pool_put(sparx5_psfp_sg_pool,
sparx5->data->consts->n_gates, id);
}
static int sparx5_psfp_fm_get(struct sparx5 *sparx5, u32 idx, u32 *id)
{
return sparx5_pool_get_with_idx(sparx5_psfp_fm_pool,
sparx5->data->consts->n_sdlbs, idx, id);
}
static int sparx5_psfp_fm_put(struct sparx5 *sparx5, u32 id)
{
return sparx5_pool_put(sparx5_psfp_fm_pool,
sparx5->data->consts->n_sdlbs, id);
}
u32 sparx5_psfp_isdx_get_sf(struct sparx5 *sparx5, u32 isdx)
{
return ANA_L2_TSN_CFG_TSN_SFID_GET(spx5_rd(sparx5,
ANA_L2_TSN_CFG(isdx)));
}
u32 sparx5_psfp_isdx_get_fm(struct sparx5 *sparx5, u32 isdx)
{
return ANA_L2_DLB_CFG_DLB_IDX_GET(spx5_rd(sparx5,
ANA_L2_DLB_CFG(isdx)));
}
u32 sparx5_psfp_sf_get_sg(struct sparx5 *sparx5, u32 sfid)
{
return ANA_AC_TSN_SF_CFG_TSN_SGID_GET(spx5_rd(sparx5,
ANA_AC_TSN_SF_CFG(sfid)));
}
void sparx5_isdx_conf_set(struct sparx5 *sparx5, u32 isdx, u32 sfid, u32 fmid)
{
spx5_rmw(ANA_L2_TSN_CFG_TSN_SFID_SET(sfid), ANA_L2_TSN_CFG_TSN_SFID,
sparx5, ANA_L2_TSN_CFG(isdx));
spx5_rmw(ANA_L2_DLB_CFG_DLB_IDX_SET(fmid), ANA_L2_DLB_CFG_DLB_IDX,
sparx5, ANA_L2_DLB_CFG(isdx));
}
/* Internal priority value to internal priority selector */
static u32 sparx5_psfp_ipv_to_ips(s32 ipv)
{
return ipv > 0 ? (ipv | BIT(3)) : 0;
}
static int sparx5_psfp_sgid_get_status(struct sparx5 *sparx5)
{
return spx5_rd(sparx5, ANA_AC_SG_ACCESS_CTRL);
}
static int sparx5_psfp_sgid_wait_for_completion(struct sparx5 *sparx5)
{
u32 val;
return readx_poll_timeout(sparx5_psfp_sgid_get_status, sparx5, val,
!ANA_AC_SG_ACCESS_CTRL_CONFIG_CHANGE_GET(val),
SPX5_PSFP_SG_CONFIG_CHANGE_SLEEP,
SPX5_PSFP_SG_CONFIG_CHANGE_TIMEO);
}
static void sparx5_psfp_sg_config_change(struct sparx5 *sparx5, u32 id)
{
spx5_wr(ANA_AC_SG_ACCESS_CTRL_SGID_SET(id), sparx5,
ANA_AC_SG_ACCESS_CTRL);
spx5_wr(ANA_AC_SG_ACCESS_CTRL_CONFIG_CHANGE_SET(1) |
ANA_AC_SG_ACCESS_CTRL_SGID_SET(id),
sparx5, ANA_AC_SG_ACCESS_CTRL);
if (sparx5_psfp_sgid_wait_for_completion(sparx5) < 0)
pr_debug("%s:%d timed out waiting for sgid completion",
__func__, __LINE__);
}
static void sparx5_psfp_sf_set(struct sparx5 *sparx5, u32 id,
const struct sparx5_psfp_sf *sf)
{
/* Configure stream gate*/
spx5_rmw(ANA_AC_TSN_SF_CFG_TSN_SGID_SET(sf->sgid) |
ANA_AC_TSN_SF_CFG_TSN_MAX_SDU_SET(sf->max_sdu) |
ANA_AC_TSN_SF_CFG_BLOCK_OVERSIZE_STATE_SET(sf->sblock_osize) |
ANA_AC_TSN_SF_CFG_BLOCK_OVERSIZE_ENA_SET(sf->sblock_osize_ena),
ANA_AC_TSN_SF_CFG_TSN_SGID | ANA_AC_TSN_SF_CFG_TSN_MAX_SDU |
ANA_AC_TSN_SF_CFG_BLOCK_OVERSIZE_STATE |
ANA_AC_TSN_SF_CFG_BLOCK_OVERSIZE_ENA,
sparx5, ANA_AC_TSN_SF_CFG(id));
}
static int sparx5_psfp_sg_set(struct sparx5 *sparx5, u32 id,
const struct sparx5_psfp_sg *sg)
{
u32 ips, base_lsb, base_msb, accum_time_interval = 0;
const struct sparx5_psfp_gce *gce;
int i;
ips = sparx5_psfp_ipv_to_ips(sg->ipv);
base_lsb = sg->basetime.tv_sec & 0xffffffff;
base_msb = sg->basetime.tv_sec >> 32;
/* Set stream gate id */
spx5_wr(ANA_AC_SG_ACCESS_CTRL_SGID_SET(id), sparx5,
ANA_AC_SG_ACCESS_CTRL);
/* Write AdminPSFP values */
spx5_wr(sg->basetime.tv_nsec, sparx5, ANA_AC_SG_CONFIG_REG_1);
spx5_wr(base_lsb, sparx5, ANA_AC_SG_CONFIG_REG_2);
spx5_rmw(ANA_AC_SG_CONFIG_REG_3_BASE_TIME_SEC_MSB_SET(base_msb) |
ANA_AC_SG_CONFIG_REG_3_INIT_IPS_SET(ips) |
ANA_AC_SG_CONFIG_REG_3_LIST_LENGTH_SET(sg->num_entries) |
ANA_AC_SG_CONFIG_REG_3_INIT_GATE_STATE_SET(sg->gate_state) |
ANA_AC_SG_CONFIG_REG_3_GATE_ENABLE_SET(1),
ANA_AC_SG_CONFIG_REG_3_BASE_TIME_SEC_MSB |
ANA_AC_SG_CONFIG_REG_3_INIT_IPS |
ANA_AC_SG_CONFIG_REG_3_LIST_LENGTH |
ANA_AC_SG_CONFIG_REG_3_INIT_GATE_STATE |
ANA_AC_SG_CONFIG_REG_3_GATE_ENABLE,
sparx5, ANA_AC_SG_CONFIG_REG_3);
spx5_wr(sg->cycletime, sparx5, ANA_AC_SG_CONFIG_REG_4);
spx5_wr(sg->cycletimeext, sparx5, ANA_AC_SG_CONFIG_REG_5);
/* For each scheduling entry */
for (i = 0; i < sg->num_entries; i++) {
gce = &sg->gce[i];
ips = sparx5_psfp_ipv_to_ips(gce->ipv);
/* hardware needs TimeInterval to be cumulative */
accum_time_interval += gce->interval;
/* Set gate state */
spx5_wr(ANA_AC_SG_GCL_GS_CONFIG_IPS_SET(ips) |
ANA_AC_SG_GCL_GS_CONFIG_GATE_STATE_SET(gce->gate_state),
sparx5, ANA_AC_SG_GCL_GS_CONFIG(i));
/* Set time interval */
spx5_wr(accum_time_interval, sparx5,
ANA_AC_SG_GCL_TI_CONFIG(i));
/* Set maximum octets */
spx5_wr(gce->maxoctets, sparx5, ANA_AC_SG_GCL_OCT_CONFIG(i));
}
return 0;
}
static int sparx5_sdlb_conf_set(struct sparx5 *sparx5,
struct sparx5_psfp_fm *fm)
{
int (*sparx5_sdlb_group_action)(struct sparx5 *sparx5, u32 group,
u32 idx);
if (!fm->pol.rate && !fm->pol.burst)
sparx5_sdlb_group_action = &sparx5_sdlb_group_del;
else
sparx5_sdlb_group_action = &sparx5_sdlb_group_add;
sparx5_policer_conf_set(sparx5, &fm->pol);
return sparx5_sdlb_group_action(sparx5, fm->pol.group, fm->pol.idx);
}
int sparx5_psfp_sf_add(struct sparx5 *sparx5, const struct sparx5_psfp_sf *sf,
u32 *id)
{
int ret;
ret = sparx5_psfp_sf_get(sparx5, id);
if (ret < 0)
return ret;
sparx5_psfp_sf_set(sparx5, *id, sf);
return 0;
}
int sparx5_psfp_sf_del(struct sparx5 *sparx5, u32 id)
{
const struct sparx5_psfp_sf sf = { 0 };
sparx5_psfp_sf_set(sparx5, id, &sf);
return sparx5_psfp_sf_put(sparx5, id);
}
int sparx5_psfp_sg_add(struct sparx5 *sparx5, u32 uidx,
struct sparx5_psfp_sg *sg, u32 *id)
{
ktime_t basetime;
int ret;
ret = sparx5_psfp_sg_get(sparx5, uidx, id);
if (ret < 0)
return ret;
/* Was already in use, no need to reconfigure */
if (ret > 1)
return 0;
/* Calculate basetime for this stream gate */
sparx5_new_base_time(sparx5, sg->cycletime, 0, &basetime);
sg->basetime = ktime_to_timespec64(basetime);
sparx5_psfp_sg_set(sparx5, *id, sg);
/* Signal hardware to copy AdminPSFP values into OperPSFP values */
sparx5_psfp_sg_config_change(sparx5, *id);
return 0;
}
int sparx5_psfp_sg_del(struct sparx5 *sparx5, u32 id)
{
const struct sparx5_psfp_sg sg = { 0 };
int ret;
ret = sparx5_psfp_sg_put(sparx5, id);
if (ret < 0)
return ret;
/* Stream gate still in use ? */
if (ret > 0)
return 0;
return sparx5_psfp_sg_set(sparx5, id, &sg);
}
int sparx5_psfp_fm_add(struct sparx5 *sparx5, u32 uidx,
struct sparx5_psfp_fm *fm, u32 *id)
{
struct sparx5_policer *pol = &fm->pol;
int ret;
/* Get flow meter */
ret = sparx5_psfp_fm_get(sparx5, uidx, &fm->pol.idx);
if (ret < 0)
return ret;
*id = fm->pol.idx;
/* Was already in use, no need to reconfigure */
if (ret > 1)
return 0;
ret = sparx5_sdlb_group_get_by_rate(sparx5, pol->rate, pol->burst);
if (ret < 0)
return ret;
fm->pol.group = ret;
ret = sparx5_sdlb_conf_set(sparx5, fm);
if (ret < 0)
return ret;
return 0;
}
int sparx5_psfp_fm_del(struct sparx5 *sparx5, u32 id)
{
struct sparx5_psfp_fm fm = { .pol.idx = id,
.pol.type = SPX5_POL_SERVICE };
int ret;
/* Find the group that this lb belongs to */
ret = sparx5_sdlb_group_get_by_index(sparx5, id, &fm.pol.group);
if (ret < 0)
return ret;
ret = sparx5_psfp_fm_put(sparx5, id);
if (ret < 0)
return ret;
/* Do not reset flow-meter if still in use. */
if (ret > 0)
return 0;
return sparx5_sdlb_conf_set(sparx5, &fm);
}
void sparx5_psfp_init(struct sparx5 *sparx5)
{
const struct sparx5_ops *ops = sparx5->data->ops;
const struct sparx5_sdlb_group *group;
int i;
for (i = 0; i < sparx5->data->consts->n_lb_groups; i++) {
group = ops->get_sdlb_group(i);
sparx5_sdlb_group_init(sparx5, group->max_rate,
group->min_burst, group->frame_size, i);
}
spx5_wr(ANA_AC_SG_CYCLETIME_UPDATE_PERIOD_SG_CT_UPDATE_ENA_SET(1),
sparx5, ANA_AC_SG_CYCLETIME_UPDATE_PERIOD);
spx5_rmw(ANA_L2_FWD_CFG_ISDX_LOOKUP_ENA_SET(1),
ANA_L2_FWD_CFG_ISDX_LOOKUP_ENA, sparx5, ANA_L2_FWD_CFG);
}
@@ -0,0 +1,714 @@
// SPDX-License-Identifier: GPL-2.0+
/* Microchip Sparx5 Switch driver
*
* Copyright (c) 2021 Microchip Technology Inc. and its subsidiaries.
*
* The Sparx5 Chip Register Model can be browsed at this location:
* https://github.com/microchip-ung/sparx-5_reginfo
*/
#include <linux/ptp_classify.h>
#include "sparx5_main_regs.h"
#include "sparx5_main.h"
#define TOD_ACC_PIN 0x4
enum {
PTP_PIN_ACTION_IDLE = 0,
PTP_PIN_ACTION_LOAD,
PTP_PIN_ACTION_SAVE,
PTP_PIN_ACTION_CLOCK,
PTP_PIN_ACTION_DELTA,
PTP_PIN_ACTION_TOD
};
static u64 sparx5_ptp_get_1ppm(struct sparx5 *sparx5)
{
/* Represents 1ppm adjustment in 2^59 format with 1.59687500000(625)
* 1.99609375000(500), 3.99218750000(250) as reference
* The value is calculated as following:
* (1/1000000)/((2^-59)/X)
*/
u64 res = 0;
switch (sparx5->coreclock) {
case SPX5_CORE_CLOCK_250MHZ:
res = 2301339409586;
break;
case SPX5_CORE_CLOCK_328MHZ:
res = 1756832768924;
break;
case SPX5_CORE_CLOCK_500MHZ:
res = 1150669704793;
break;
case SPX5_CORE_CLOCK_625MHZ:
res = 920535763834;
break;
default:
WARN(1, "Invalid core clock");
break;
}
return res;
}
static u64 sparx5_ptp_get_nominal_value(struct sparx5 *sparx5)
{
u64 res = 0;
switch (sparx5->coreclock) {
case SPX5_CORE_CLOCK_250MHZ:
res = 0x1FF0000000000000;
break;
case SPX5_CORE_CLOCK_328MHZ:
res = 0x18604697DD0F9B5B;
break;
case SPX5_CORE_CLOCK_500MHZ:
res = 0x0FF8000000000000;
break;
case SPX5_CORE_CLOCK_625MHZ:
res = 0x0CC6666666666666;
break;
default:
WARN(1, "Invalid core clock");
break;
}
return res;
}
int sparx5_ptp_hwtstamp_set(struct sparx5_port *port,
struct kernel_hwtstamp_config *cfg,
struct netlink_ext_ack *extack)
{
struct sparx5 *sparx5 = port->sparx5;
struct sparx5_phc *phc;
/* For now don't allow to run ptp on ports that are part of a bridge,
* because in case of transparent clock the HW will still forward the
* frames, so there would be duplicate frames
*/
if (test_bit(port->portno, sparx5->bridge_mask))
return -EINVAL;
switch (cfg->tx_type) {
case HWTSTAMP_TX_ON:
port->ptp_cmd = IFH_REW_OP_TWO_STEP_PTP;
break;
case HWTSTAMP_TX_ONESTEP_SYNC:
port->ptp_cmd = IFH_REW_OP_ONE_STEP_PTP;
break;
case HWTSTAMP_TX_OFF:
port->ptp_cmd = IFH_REW_OP_NOOP;
break;
default:
return -ERANGE;
}
switch (cfg->rx_filter) {
case HWTSTAMP_FILTER_NONE:
break;
case HWTSTAMP_FILTER_ALL:
case HWTSTAMP_FILTER_PTP_V1_L4_EVENT:
case HWTSTAMP_FILTER_PTP_V1_L4_SYNC:
case HWTSTAMP_FILTER_PTP_V1_L4_DELAY_REQ:
case HWTSTAMP_FILTER_PTP_V2_L4_EVENT:
case HWTSTAMP_FILTER_PTP_V2_L4_SYNC:
case HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ:
case HWTSTAMP_FILTER_PTP_V2_L2_EVENT:
case HWTSTAMP_FILTER_PTP_V2_L2_SYNC:
case HWTSTAMP_FILTER_PTP_V2_L2_DELAY_REQ:
case HWTSTAMP_FILTER_PTP_V2_EVENT:
case HWTSTAMP_FILTER_PTP_V2_SYNC:
case HWTSTAMP_FILTER_PTP_V2_DELAY_REQ:
case HWTSTAMP_FILTER_NTP_ALL:
cfg->rx_filter = HWTSTAMP_FILTER_ALL;
break;
default:
return -ERANGE;
}
/* Commit back the result & save it */
mutex_lock(&sparx5->ptp_lock);
phc = &sparx5->phc[SPARX5_PHC_PORT];
phc->hwtstamp_config = *cfg;
mutex_unlock(&sparx5->ptp_lock);
return 0;
}
void sparx5_ptp_hwtstamp_get(struct sparx5_port *port,
struct kernel_hwtstamp_config *cfg)
{
struct sparx5 *sparx5 = port->sparx5;
struct sparx5_phc *phc;
phc = &sparx5->phc[SPARX5_PHC_PORT];
*cfg = phc->hwtstamp_config;
}
static void sparx5_ptp_classify(struct sparx5_port *port, struct sk_buff *skb,
u8 *rew_op, u8 *pdu_type, u8 *pdu_w16_offset)
{
struct ptp_header *header;
u8 msgtype;
int type;
if (port->ptp_cmd == IFH_REW_OP_NOOP) {
*rew_op = IFH_REW_OP_NOOP;
*pdu_type = IFH_PDU_TYPE_NONE;
*pdu_w16_offset = 0;
return;
}
type = ptp_classify_raw(skb);
if (type == PTP_CLASS_NONE) {
*rew_op = IFH_REW_OP_NOOP;
*pdu_type = IFH_PDU_TYPE_NONE;
*pdu_w16_offset = 0;
return;
}
header = ptp_parse_header(skb, type);
if (!header) {
*rew_op = IFH_REW_OP_NOOP;
*pdu_type = IFH_PDU_TYPE_NONE;
*pdu_w16_offset = 0;
return;
}
*pdu_w16_offset = 7;
if (type & PTP_CLASS_L2)
*pdu_type = IFH_PDU_TYPE_PTP;
if (type & PTP_CLASS_IPV4)
*pdu_type = IFH_PDU_TYPE_IPV4_UDP_PTP;
if (type & PTP_CLASS_IPV6)
*pdu_type = IFH_PDU_TYPE_IPV6_UDP_PTP;
if (port->ptp_cmd == IFH_REW_OP_TWO_STEP_PTP) {
*rew_op = IFH_REW_OP_TWO_STEP_PTP;
return;
}
/* If it is sync and run 1 step then set the correct operation,
* otherwise run as 2 step
*/
msgtype = ptp_get_msgtype(header, type);
if ((msgtype & 0xf) == 0) {
*rew_op = IFH_REW_OP_ONE_STEP_PTP;
return;
}
*rew_op = IFH_REW_OP_TWO_STEP_PTP;
}
static void sparx5_ptp_txtstamp_old_release(struct sparx5_port *port)
{
struct sk_buff *skb, *skb_tmp;
unsigned long flags;
spin_lock_irqsave(&port->tx_skbs.lock, flags);
skb_queue_walk_safe(&port->tx_skbs, skb, skb_tmp) {
if time_after(SPARX5_SKB_CB(skb)->jiffies + SPARX5_PTP_TIMEOUT,
jiffies)
break;
__skb_unlink(skb, &port->tx_skbs);
dev_kfree_skb_any(skb);
}
spin_unlock_irqrestore(&port->tx_skbs.lock, flags);
}
int sparx5_ptp_txtstamp_request(struct sparx5_port *port,
struct sk_buff *skb)
{
struct sparx5 *sparx5 = port->sparx5;
u8 rew_op, pdu_type, pdu_w16_offset;
unsigned long flags;
sparx5_ptp_classify(port, skb, &rew_op, &pdu_type, &pdu_w16_offset);
SPARX5_SKB_CB(skb)->rew_op = rew_op;
SPARX5_SKB_CB(skb)->pdu_type = pdu_type;
SPARX5_SKB_CB(skb)->pdu_w16_offset = pdu_w16_offset;
if (rew_op != IFH_REW_OP_TWO_STEP_PTP)
return 0;
sparx5_ptp_txtstamp_old_release(port);
spin_lock_irqsave(&sparx5->ptp_ts_id_lock, flags);
if (sparx5->ptp_skbs == SPARX5_MAX_PTP_ID) {
spin_unlock_irqrestore(&sparx5->ptp_ts_id_lock, flags);
return -EBUSY;
}
skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS;
skb_queue_tail(&port->tx_skbs, skb);
SPARX5_SKB_CB(skb)->ts_id = port->ts_id;
SPARX5_SKB_CB(skb)->jiffies = jiffies;
sparx5->ptp_skbs++;
port->ts_id++;
if (port->ts_id == SPARX5_MAX_PTP_ID)
port->ts_id = 0;
spin_unlock_irqrestore(&sparx5->ptp_ts_id_lock, flags);
return 0;
}
void sparx5_ptp_txtstamp_release(struct sparx5_port *port,
struct sk_buff *skb)
{
struct sparx5 *sparx5 = port->sparx5;
unsigned long flags;
spin_lock_irqsave(&sparx5->ptp_ts_id_lock, flags);
port->ts_id--;
sparx5->ptp_skbs--;
skb_unlink(skb, &port->tx_skbs);
spin_unlock_irqrestore(&sparx5->ptp_ts_id_lock, flags);
}
void sparx5_get_hwtimestamp(struct sparx5 *sparx5,
struct timespec64 *ts,
u32 nsec)
{
/* Read current PTP time to get seconds */
const struct sparx5_consts *consts = sparx5->data->consts;
unsigned long flags;
u32 curr_nsec;
spin_lock_irqsave(&sparx5->ptp_clock_lock, flags);
spx5_rmw(PTP_PTP_PIN_CFG_PTP_PIN_ACTION_SET(PTP_PIN_ACTION_SAVE) |
PTP_PTP_PIN_CFG_PTP_PIN_DOM_SET(SPARX5_PHC_PORT) |
PTP_PTP_PIN_CFG_PTP_PIN_SYNC_SET(0),
PTP_PTP_PIN_CFG_PTP_PIN_ACTION |
PTP_PTP_PIN_CFG_PTP_PIN_DOM |
PTP_PTP_PIN_CFG_PTP_PIN_SYNC,
sparx5, PTP_PTP_PIN_CFG(consts->tod_pin));
ts->tv_sec = spx5_rd(sparx5, PTP_PTP_TOD_SEC_LSB(consts->tod_pin));
curr_nsec = spx5_rd(sparx5, PTP_PTP_TOD_NSEC(consts->tod_pin));
ts->tv_nsec = nsec;
/* Sec has incremented since the ts was registered */
if (curr_nsec < nsec)
ts->tv_sec--;
spin_unlock_irqrestore(&sparx5->ptp_clock_lock, flags);
}
irqreturn_t sparx5_ptp_irq_handler(int irq, void *args)
{
int budget = SPARX5_MAX_PTP_ID;
struct sparx5 *sparx5 = args;
while (budget--) {
struct sk_buff *skb, *skb_tmp, *skb_match = NULL;
struct skb_shared_hwtstamps shhwtstamps;
struct sparx5_port *port;
struct timespec64 ts;
unsigned long flags;
u32 val, id, txport;
u32 delay;
val = spx5_rd(sparx5, REW_PTP_TWOSTEP_CTRL);
/* Check if a timestamp can be retrieved */
if (!(val & REW_PTP_TWOSTEP_CTRL_PTP_VLD))
break;
WARN_ON(val & REW_PTP_TWOSTEP_CTRL_PTP_OVFL);
if (!(val & REW_PTP_TWOSTEP_CTRL_STAMP_TX))
continue;
/* Retrieve the ts Tx port */
txport = REW_PTP_TWOSTEP_CTRL_STAMP_PORT_GET(val);
/* Retrieve its associated skb */
port = sparx5->ports[txport];
/* Retrieve the delay */
delay = spx5_rd(sparx5, REW_PTP_TWOSTEP_STAMP);
delay = REW_PTP_TWOSTEP_STAMP_STAMP_NSEC_GET(delay);
/* Get next timestamp from fifo, which needs to be the
* rx timestamp which represents the id of the frame
*/
spx5_rmw(REW_PTP_TWOSTEP_CTRL_PTP_NXT_SET(1),
REW_PTP_TWOSTEP_CTRL_PTP_NXT,
sparx5, REW_PTP_TWOSTEP_CTRL);
val = spx5_rd(sparx5, REW_PTP_TWOSTEP_CTRL);
/* Check if a timestamp can be retried */
if (!(val & REW_PTP_TWOSTEP_CTRL_PTP_VLD))
break;
/* Read RX timestamping to get the ID */
id = spx5_rd(sparx5, REW_PTP_TWOSTEP_STAMP);
id <<= 8;
id |= spx5_rd(sparx5, REW_PTP_TWOSTEP_STAMP_SUBNS);
spin_lock_irqsave(&port->tx_skbs.lock, flags);
skb_queue_walk_safe(&port->tx_skbs, skb, skb_tmp) {
if (SPARX5_SKB_CB(skb)->ts_id != id)
continue;
__skb_unlink(skb, &port->tx_skbs);
skb_match = skb;
break;
}
spin_unlock_irqrestore(&port->tx_skbs.lock, flags);
/* Next ts */
spx5_rmw(REW_PTP_TWOSTEP_CTRL_PTP_NXT_SET(1),
REW_PTP_TWOSTEP_CTRL_PTP_NXT,
sparx5, REW_PTP_TWOSTEP_CTRL);
if (WARN_ON(!skb_match))
continue;
spin_lock(&sparx5->ptp_ts_id_lock);
sparx5->ptp_skbs--;
spin_unlock(&sparx5->ptp_ts_id_lock);
/* Get the h/w timestamp */
sparx5_get_hwtimestamp(sparx5, &ts, delay);
/* Set the timestamp into the skb */
shhwtstamps.hwtstamp = ktime_set(ts.tv_sec, ts.tv_nsec);
skb_tstamp_tx(skb_match, &shhwtstamps);
dev_kfree_skb_any(skb_match);
}
return IRQ_HANDLED;
}
static int sparx5_ptp_adjfine(struct ptp_clock_info *ptp, long scaled_ppm)
{
struct sparx5_phc *phc = container_of(ptp, struct sparx5_phc, info);
struct sparx5 *sparx5 = phc->sparx5;
unsigned long flags;
bool neg_adj = 0;
u64 tod_inc;
u64 ref;
if (!scaled_ppm)
return 0;
if (scaled_ppm < 0) {
neg_adj = 1;
scaled_ppm = -scaled_ppm;
}
tod_inc = sparx5_ptp_get_nominal_value(sparx5);
/* The multiplication is split in 2 separate additions because of
* overflow issues. If scaled_ppm with 16bit fractional part was bigger
* than 20ppm then we got overflow.
*/
ref = sparx5_ptp_get_1ppm(sparx5) * (scaled_ppm >> 16);
ref += (sparx5_ptp_get_1ppm(sparx5) * (0xffff & scaled_ppm)) >> 16;
tod_inc = neg_adj ? tod_inc - ref : tod_inc + ref;
spin_lock_irqsave(&sparx5->ptp_clock_lock, flags);
spx5_rmw(PTP_PTP_DOM_CFG_PTP_CLKCFG_DIS_SET(1 << BIT(phc->index)),
PTP_PTP_DOM_CFG_PTP_CLKCFG_DIS,
sparx5, PTP_PTP_DOM_CFG);
spx5_wr((u32)tod_inc & 0xFFFFFFFF, sparx5,
PTP_CLK_PER_CFG(phc->index, 0));
spx5_wr((u32)(tod_inc >> 32), sparx5,
PTP_CLK_PER_CFG(phc->index, 1));
spx5_rmw(PTP_PTP_DOM_CFG_PTP_CLKCFG_DIS_SET(0),
PTP_PTP_DOM_CFG_PTP_CLKCFG_DIS, sparx5,
PTP_PTP_DOM_CFG);
spin_unlock_irqrestore(&sparx5->ptp_clock_lock, flags);
return 0;
}
static int sparx5_ptp_settime64(struct ptp_clock_info *ptp,
const struct timespec64 *ts)
{
struct sparx5_phc *phc = container_of(ptp, struct sparx5_phc, info);
struct sparx5 *sparx5 = phc->sparx5;
const struct sparx5_consts *consts;
unsigned long flags;
consts = sparx5->data->consts;
spin_lock_irqsave(&sparx5->ptp_clock_lock, flags);
/* Must be in IDLE mode before the time can be loaded */
spx5_rmw(PTP_PTP_PIN_CFG_PTP_PIN_ACTION_SET(PTP_PIN_ACTION_IDLE) |
PTP_PTP_PIN_CFG_PTP_PIN_DOM_SET(phc->index) |
PTP_PTP_PIN_CFG_PTP_PIN_SYNC_SET(0),
PTP_PTP_PIN_CFG_PTP_PIN_ACTION |
PTP_PTP_PIN_CFG_PTP_PIN_DOM |
PTP_PTP_PIN_CFG_PTP_PIN_SYNC,
sparx5, PTP_PTP_PIN_CFG(consts->tod_pin));
/* Set new value */
spx5_wr(PTP_PTP_TOD_SEC_MSB_PTP_TOD_SEC_MSB_SET(upper_32_bits(ts->tv_sec)),
sparx5, PTP_PTP_TOD_SEC_MSB(consts->tod_pin));
spx5_wr(lower_32_bits(ts->tv_sec),
sparx5, PTP_PTP_TOD_SEC_LSB(consts->tod_pin));
spx5_wr(ts->tv_nsec, sparx5, PTP_PTP_TOD_NSEC(consts->tod_pin));
/* Apply new values */
spx5_rmw(PTP_PTP_PIN_CFG_PTP_PIN_ACTION_SET(PTP_PIN_ACTION_LOAD) |
PTP_PTP_PIN_CFG_PTP_PIN_DOM_SET(phc->index) |
PTP_PTP_PIN_CFG_PTP_PIN_SYNC_SET(0),
PTP_PTP_PIN_CFG_PTP_PIN_ACTION |
PTP_PTP_PIN_CFG_PTP_PIN_DOM |
PTP_PTP_PIN_CFG_PTP_PIN_SYNC,
sparx5, PTP_PTP_PIN_CFG(consts->tod_pin));
spin_unlock_irqrestore(&sparx5->ptp_clock_lock, flags);
return 0;
}
int sparx5_ptp_gettime64(struct ptp_clock_info *ptp, struct timespec64 *ts)
{
struct sparx5_phc *phc = container_of(ptp, struct sparx5_phc, info);
struct sparx5 *sparx5 = phc->sparx5;
const struct sparx5_consts *consts;
unsigned long flags;
time64_t s;
s64 ns;
consts = sparx5->data->consts;
spin_lock_irqsave(&sparx5->ptp_clock_lock, flags);
spx5_rmw(PTP_PTP_PIN_CFG_PTP_PIN_ACTION_SET(PTP_PIN_ACTION_SAVE) |
PTP_PTP_PIN_CFG_PTP_PIN_DOM_SET(phc->index) |
PTP_PTP_PIN_CFG_PTP_PIN_SYNC_SET(0),
PTP_PTP_PIN_CFG_PTP_PIN_ACTION |
PTP_PTP_PIN_CFG_PTP_PIN_DOM |
PTP_PTP_PIN_CFG_PTP_PIN_SYNC,
sparx5, PTP_PTP_PIN_CFG(consts->tod_pin));
s = spx5_rd(sparx5, PTP_PTP_TOD_SEC_MSB(consts->tod_pin));
s <<= 32;
s |= spx5_rd(sparx5, PTP_PTP_TOD_SEC_LSB(consts->tod_pin));
ns = spx5_rd(sparx5, PTP_PTP_TOD_NSEC(consts->tod_pin));
ns &= PTP_PTP_TOD_NSEC_PTP_TOD_NSEC;
spin_unlock_irqrestore(&sparx5->ptp_clock_lock, flags);
/* Deal with negative values */
if ((ns & 0xFFFFFFF0) == 0x3FFFFFF0) {
s--;
ns &= 0xf;
ns += 999999984;
}
set_normalized_timespec64(ts, s, ns);
return 0;
}
static int sparx5_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta)
{
struct sparx5_phc *phc = container_of(ptp, struct sparx5_phc, info);
struct sparx5 *sparx5 = phc->sparx5;
const struct sparx5_consts *consts;
consts = sparx5->data->consts;
if (delta > -(NSEC_PER_SEC / 2) && delta < (NSEC_PER_SEC / 2)) {
unsigned long flags;
spin_lock_irqsave(&sparx5->ptp_clock_lock, flags);
/* Must be in IDLE mode before the time can be loaded */
spx5_rmw(PTP_PTP_PIN_CFG_PTP_PIN_ACTION_SET(PTP_PIN_ACTION_IDLE) |
PTP_PTP_PIN_CFG_PTP_PIN_DOM_SET(phc->index) |
PTP_PTP_PIN_CFG_PTP_PIN_SYNC_SET(0),
PTP_PTP_PIN_CFG_PTP_PIN_ACTION |
PTP_PTP_PIN_CFG_PTP_PIN_DOM |
PTP_PTP_PIN_CFG_PTP_PIN_SYNC,
sparx5, PTP_PTP_PIN_CFG(consts->tod_pin));
spx5_wr(PTP_PTP_TOD_NSEC_PTP_TOD_NSEC_SET(delta),
sparx5, PTP_PTP_TOD_NSEC(consts->tod_pin));
/* Adjust time with the value of PTP_TOD_NSEC */
spx5_rmw(PTP_PTP_PIN_CFG_PTP_PIN_ACTION_SET(PTP_PIN_ACTION_DELTA) |
PTP_PTP_PIN_CFG_PTP_PIN_DOM_SET(phc->index) |
PTP_PTP_PIN_CFG_PTP_PIN_SYNC_SET(0),
PTP_PTP_PIN_CFG_PTP_PIN_ACTION |
PTP_PTP_PIN_CFG_PTP_PIN_DOM |
PTP_PTP_PIN_CFG_PTP_PIN_SYNC,
sparx5, PTP_PTP_PIN_CFG(consts->tod_pin));
spin_unlock_irqrestore(&sparx5->ptp_clock_lock, flags);
} else {
/* Fall back using sparx5_ptp_settime64 which is not exact */
struct timespec64 ts;
u64 now;
sparx5_ptp_gettime64(ptp, &ts);
now = ktime_to_ns(timespec64_to_ktime(ts));
ts = ns_to_timespec64(now + delta);
sparx5_ptp_settime64(ptp, &ts);
}
return 0;
}
static struct ptp_clock_info sparx5_ptp_clock_info = {
.owner = THIS_MODULE,
.name = "sparx5 ptp",
.max_adj = 10000000,
.gettime64 = sparx5_ptp_gettime64,
.settime64 = sparx5_ptp_settime64,
.adjtime = sparx5_ptp_adjtime,
.adjfine = sparx5_ptp_adjfine,
};
static int sparx5_ptp_phc_init(struct sparx5 *sparx5,
int index,
struct ptp_clock_info *clock_info)
{
struct sparx5_phc *phc = &sparx5->phc[index];
phc->info = *clock_info;
phc->clock = ptp_clock_register(&phc->info, sparx5->dev);
if (IS_ERR(phc->clock))
return PTR_ERR(phc->clock);
phc->index = index;
phc->sparx5 = sparx5;
/* PTP Rx stamping is always enabled. */
phc->hwtstamp_config.rx_filter = HWTSTAMP_FILTER_PTP_V2_EVENT;
return 0;
}
int sparx5_ptp_init(struct sparx5 *sparx5)
{
u64 tod_adj = sparx5_ptp_get_nominal_value(sparx5);
const struct sparx5_ops *ops = sparx5->data->ops;
struct sparx5_port *port;
int err, i;
if (sparx5->ptp_irq >= 0 &&
sparx5_has_feature(sparx5, SPX5_FEATURE_PTP)) {
err = devm_request_threaded_irq(sparx5->dev, sparx5->ptp_irq,
NULL, ops->ptp_irq_handler,
IRQF_ONESHOT, "sparx5-ptp",
sparx5);
if (err)
sparx5->ptp_irq = -ENXIO;
sparx5->ptp = 1;
}
if (!sparx5->ptp)
return 0;
for (i = 0; i < SPARX5_PHC_COUNT; ++i) {
err = sparx5_ptp_phc_init(sparx5, i, &sparx5_ptp_clock_info);
if (err)
return err;
}
spin_lock_init(&sparx5->ptp_clock_lock);
spin_lock_init(&sparx5->ptp_ts_id_lock);
mutex_init(&sparx5->ptp_lock);
/* Disable master counters */
spx5_wr(PTP_PTP_DOM_CFG_PTP_ENA_SET(0), sparx5, PTP_PTP_DOM_CFG);
/* Configure the nominal TOD increment per clock cycle */
spx5_rmw(PTP_PTP_DOM_CFG_PTP_CLKCFG_DIS_SET(0x7),
PTP_PTP_DOM_CFG_PTP_CLKCFG_DIS,
sparx5, PTP_PTP_DOM_CFG);
for (i = 0; i < SPARX5_PHC_COUNT; ++i) {
spx5_wr((u32)tod_adj & 0xFFFFFFFF, sparx5,
PTP_CLK_PER_CFG(i, 0));
spx5_wr((u32)(tod_adj >> 32), sparx5,
PTP_CLK_PER_CFG(i, 1));
}
spx5_rmw(PTP_PTP_DOM_CFG_PTP_CLKCFG_DIS_SET(0),
PTP_PTP_DOM_CFG_PTP_CLKCFG_DIS,
sparx5, PTP_PTP_DOM_CFG);
/* Enable master counters */
spx5_wr(PTP_PTP_DOM_CFG_PTP_ENA_SET(0x7), sparx5, PTP_PTP_DOM_CFG);
for (i = 0; i < sparx5->data->consts->n_ports; i++) {
port = sparx5->ports[i];
if (!port)
continue;
skb_queue_head_init(&port->tx_skbs);
}
return 0;
}
void sparx5_ptp_deinit(struct sparx5 *sparx5)
{
struct sparx5_port *port;
int i;
if (sparx5->ptp_irq >= 0) {
disable_irq(sparx5->ptp_irq);
sparx5->ptp_irq = -ENXIO;
}
for (i = 0; i < sparx5->data->consts->n_ports; i++) {
port = sparx5->ports[i];
if (!port)
continue;
skb_queue_purge(&port->tx_skbs);
}
for (i = 0; i < SPARX5_PHC_COUNT; ++i)
ptp_clock_unregister(sparx5->phc[i].clock);
}
void sparx5_ptp_rxtstamp(struct sparx5 *sparx5, struct sk_buff *skb,
u64 timestamp)
{
struct skb_shared_hwtstamps *shhwtstamps;
struct sparx5_phc *phc;
struct timespec64 ts;
u64 full_ts_in_ns;
if (!sparx5->ptp)
return;
phc = &sparx5->phc[SPARX5_PHC_PORT];
sparx5_ptp_gettime64(&phc->info, &ts);
if (ts.tv_nsec < timestamp)
ts.tv_sec--;
ts.tv_nsec = timestamp;
full_ts_in_ns = ktime_set(ts.tv_sec, ts.tv_nsec);
shhwtstamps = skb_hwtstamps(skb);
shhwtstamps->hwtstamp = full_ts_in_ns;
}
@@ -0,0 +1,583 @@
// SPDX-License-Identifier: GPL-2.0+
/* Microchip Sparx5 Switch driver
*
* Copyright (c) 2022 Microchip Technology Inc. and its subsidiaries.
*/
#include <net/pkt_cls.h>
#include "sparx5_main.h"
#include "sparx5_qos.h"
/* Calculate new base_time based on cycle_time.
*
* The hardware requires a base_time that is always in the future.
* We define threshold_time as current_time + (2 * cycle_time).
* If base_time is below threshold_time this function recalculates it to be in
* the interval:
* threshold_time <= base_time < (threshold_time + cycle_time)
*
* A very simple algorithm could be like this:
* new_base_time = org_base_time + N * cycle_time
* using the lowest N so (new_base_time >= threshold_time
*/
void sparx5_new_base_time(struct sparx5 *sparx5, const u32 cycle_time,
const ktime_t org_base_time, ktime_t *new_base_time)
{
ktime_t current_time, threshold_time, new_time;
struct timespec64 ts;
u64 nr_of_cycles_p2;
u64 nr_of_cycles;
u64 diff_time;
new_time = org_base_time;
sparx5_ptp_gettime64(&sparx5->phc[SPARX5_PHC_PORT].info, &ts);
current_time = timespec64_to_ktime(ts);
threshold_time = current_time + (2 * cycle_time);
diff_time = threshold_time - new_time;
nr_of_cycles = div_u64(diff_time, cycle_time);
nr_of_cycles_p2 = 1; /* Use 2^0 as start value */
if (new_time >= threshold_time) {
*new_base_time = new_time;
return;
}
/* Calculate the smallest power of 2 (nr_of_cycles_p2)
* that is larger than nr_of_cycles.
*/
while (nr_of_cycles_p2 < nr_of_cycles)
nr_of_cycles_p2 <<= 1; /* Next (higher) power of 2 */
/* Add as big chunks (power of 2 * cycle_time)
* as possible for each power of 2
*/
while (nr_of_cycles_p2) {
if (new_time < threshold_time) {
new_time += cycle_time * nr_of_cycles_p2;
while (new_time < threshold_time)
new_time += cycle_time * nr_of_cycles_p2;
new_time -= cycle_time * nr_of_cycles_p2;
}
nr_of_cycles_p2 >>= 1; /* Next (lower) power of 2 */
}
new_time += cycle_time;
*new_base_time = new_time;
}
/* Max rates for leak groups */
static const u32 spx5_hsch_max_group_rate[SPX5_HSCH_LEAK_GRP_CNT] = {
1048568, /* 1.049 Gbps */
2621420, /* 2.621 Gbps */
10485680, /* 10.486 Gbps */
26214200 /* 26.214 Gbps */
};
u32 sparx5_get_hsch_max_group_rate(int grp)
{
return spx5_hsch_max_group_rate[grp];
}
static struct sparx5_layer layers[SPX5_HSCH_LAYER_CNT];
static u32 sparx5_lg_get_leak_time(struct sparx5 *sparx5, u32 layer, u32 group)
{
u32 value;
value = spx5_rd(sparx5, HSCH_HSCH_TIMER_CFG(layer, group));
return HSCH_HSCH_TIMER_CFG_LEAK_TIME_GET(value);
}
static void sparx5_lg_set_leak_time(struct sparx5 *sparx5, u32 layer, u32 group,
u32 leak_time)
{
spx5_wr(HSCH_HSCH_TIMER_CFG_LEAK_TIME_SET(leak_time), sparx5,
HSCH_HSCH_TIMER_CFG(layer, group));
}
static u32 sparx5_lg_get_first(struct sparx5 *sparx5, u32 layer, u32 group)
{
u32 value;
value = spx5_rd(sparx5, HSCH_HSCH_LEAK_CFG(layer, group));
return HSCH_HSCH_LEAK_CFG_LEAK_FIRST_GET(value);
}
static u32 sparx5_lg_get_next(struct sparx5 *sparx5, u32 layer, u32 group,
u32 idx)
{
u32 value;
value = spx5_rd(sparx5, HSCH_SE_CONNECT(idx));
return HSCH_SE_CONNECT_SE_LEAK_LINK_GET(value);
}
static u32 sparx5_lg_get_last(struct sparx5 *sparx5, u32 layer, u32 group)
{
u32 itr, next;
itr = sparx5_lg_get_first(sparx5, layer, group);
for (;;) {
next = sparx5_lg_get_next(sparx5, layer, group, itr);
if (itr == next)
return itr;
itr = next;
}
}
static bool sparx5_lg_is_last(struct sparx5 *sparx5, u32 layer, u32 group,
u32 idx)
{
return idx == sparx5_lg_get_next(sparx5, layer, group, idx);
}
static bool sparx5_lg_is_first(struct sparx5 *sparx5, u32 layer, u32 group,
u32 idx)
{
return idx == sparx5_lg_get_first(sparx5, layer, group);
}
static bool sparx5_lg_is_empty(struct sparx5 *sparx5, u32 layer, u32 group)
{
return sparx5_lg_get_leak_time(sparx5, layer, group) == 0;
}
static bool sparx5_lg_is_singular(struct sparx5 *sparx5, u32 layer, u32 group)
{
if (sparx5_lg_is_empty(sparx5, layer, group))
return false;
return sparx5_lg_get_first(sparx5, layer, group) ==
sparx5_lg_get_last(sparx5, layer, group);
}
static void sparx5_lg_enable(struct sparx5 *sparx5, u32 layer, u32 group,
u32 leak_time)
{
sparx5_lg_set_leak_time(sparx5, layer, group, leak_time);
}
static void sparx5_lg_disable(struct sparx5 *sparx5, u32 layer, u32 group)
{
sparx5_lg_set_leak_time(sparx5, layer, group, 0);
}
static int sparx5_lg_get_group_by_index(struct sparx5 *sparx5, u32 layer,
u32 idx, u32 *group)
{
u32 itr, next;
int i;
for (i = 0; i < SPX5_HSCH_LEAK_GRP_CNT; i++) {
if (sparx5_lg_is_empty(sparx5, layer, i))
continue;
itr = sparx5_lg_get_first(sparx5, layer, i);
for (;;) {
next = sparx5_lg_get_next(sparx5, layer, i, itr);
if (itr == idx) {
*group = i;
return 0; /* Found it */
}
if (itr == next)
break; /* Was not found */
itr = next;
}
}
return -1;
}
static int sparx5_lg_get_group_by_rate(u32 layer, u32 rate, u32 *group)
{
struct sparx5_layer *l = &layers[layer];
struct sparx5_lg *lg;
u32 i;
for (i = 0; i < SPX5_HSCH_LEAK_GRP_CNT; i++) {
lg = &l->leak_groups[i];
if (rate <= lg->max_rate) {
*group = i;
return 0;
}
}
return -1;
}
static int sparx5_lg_get_adjacent(struct sparx5 *sparx5, u32 layer, u32 group,
u32 idx, u32 *prev, u32 *next, u32 *first)
{
u32 itr;
*first = sparx5_lg_get_first(sparx5, layer, group);
*prev = *first;
*next = *first;
itr = *first;
for (;;) {
*next = sparx5_lg_get_next(sparx5, layer, group, itr);
if (itr == idx)
return 0; /* Found it */
if (itr == *next)
return -1; /* Was not found */
*prev = itr;
itr = *next;
}
return -1;
}
static int sparx5_lg_conf_set(struct sparx5 *sparx5, u32 layer, u32 group,
u32 se_first, u32 idx, u32 idx_next, bool empty)
{
u32 leak_time = layers[layer].leak_groups[group].leak_time;
/* Stop leaking */
sparx5_lg_disable(sparx5, layer, group);
if (empty)
return 0;
/* Select layer */
spx5_rmw(HSCH_HSCH_CFG_CFG_HSCH_LAYER_SET(layer),
HSCH_HSCH_CFG_CFG_HSCH_LAYER, sparx5, HSCH_HSCH_CFG_CFG);
/* Link elements */
spx5_wr(HSCH_SE_CONNECT_SE_LEAK_LINK_SET(idx_next), sparx5,
HSCH_SE_CONNECT(idx));
/* Set the first element. */
spx5_rmw(HSCH_HSCH_LEAK_CFG_LEAK_FIRST_SET(se_first),
HSCH_HSCH_LEAK_CFG_LEAK_FIRST, sparx5,
HSCH_HSCH_LEAK_CFG(layer, group));
/* Start leaking */
sparx5_lg_enable(sparx5, layer, group, leak_time);
return 0;
}
static int sparx5_lg_del(struct sparx5 *sparx5, u32 layer, u32 group, u32 idx)
{
u32 first, next, prev;
bool empty = false;
/* idx *must* be present in the leak group */
WARN_ON(sparx5_lg_get_adjacent(sparx5, layer, group, idx, &prev, &next,
&first) < 0);
if (sparx5_lg_is_singular(sparx5, layer, group)) {
empty = true;
} else if (sparx5_lg_is_last(sparx5, layer, group, idx)) {
/* idx is removed, prev is now last */
idx = prev;
next = prev;
} else if (sparx5_lg_is_first(sparx5, layer, group, idx)) {
/* idx is removed and points to itself, first is next */
first = next;
next = idx;
} else {
/* Next is not touched */
idx = prev;
}
return sparx5_lg_conf_set(sparx5, layer, group, first, idx, next,
empty);
}
static int sparx5_lg_add(struct sparx5 *sparx5, u32 layer, u32 new_group,
u32 idx)
{
u32 first, next, old_group;
pr_debug("ADD: layer: %d, new_group: %d, idx: %d", layer, new_group,
idx);
/* Is this SE already shaping ? */
if (sparx5_lg_get_group_by_index(sparx5, layer, idx, &old_group) >= 0) {
if (old_group != new_group) {
/* Delete from old group */
sparx5_lg_del(sparx5, layer, old_group, idx);
} else {
/* Nothing to do here */
return 0;
}
}
/* We always add to head of the list */
first = idx;
if (sparx5_lg_is_empty(sparx5, layer, new_group))
next = idx;
else
next = sparx5_lg_get_first(sparx5, layer, new_group);
return sparx5_lg_conf_set(sparx5, layer, new_group, first, idx, next,
false);
}
static int sparx5_shaper_conf_set(struct sparx5_port *port,
const struct sparx5_shaper *sh, u32 layer,
u32 idx, u32 group)
{
int (*sparx5_lg_action)(struct sparx5 *, u32, u32, u32);
struct sparx5 *sparx5 = port->sparx5;
if (!sh->rate && !sh->burst)
sparx5_lg_action = &sparx5_lg_del;
else
sparx5_lg_action = &sparx5_lg_add;
/* Select layer */
spx5_rmw(HSCH_HSCH_CFG_CFG_HSCH_LAYER_SET(layer),
HSCH_HSCH_CFG_CFG_HSCH_LAYER, sparx5, HSCH_HSCH_CFG_CFG);
/* Set frame mode */
spx5_rmw(HSCH_SE_CFG_SE_FRM_MODE_SET(sh->mode), HSCH_SE_CFG_SE_FRM_MODE,
sparx5, HSCH_SE_CFG(idx));
/* Set committed rate and burst */
spx5_wr(HSCH_CIR_CFG_CIR_RATE_SET(sh->rate) |
HSCH_CIR_CFG_CIR_BURST_SET(sh->burst),
sparx5, HSCH_CIR_CFG(idx));
/* This has to be done after the shaper configuration has been set */
sparx5_lg_action(sparx5, layer, group, idx);
return 0;
}
static u32 sparx5_weight_to_hw_cost(u32 weight_min, u32 weight)
{
return ((((SPX5_DWRR_COST_MAX << 4) * weight_min / weight) + 8) >> 4) -
1;
}
static int sparx5_dwrr_conf_set(struct sparx5_port *port,
struct sparx5_dwrr *dwrr)
{
u32 layer = is_sparx5(port->sparx5) ? 2 : 1;
int i;
spx5_rmw(HSCH_HSCH_CFG_CFG_HSCH_LAYER_SET(layer) |
HSCH_HSCH_CFG_CFG_CFG_SE_IDX_SET(port->portno),
HSCH_HSCH_CFG_CFG_HSCH_LAYER | HSCH_HSCH_CFG_CFG_CFG_SE_IDX,
port->sparx5, HSCH_HSCH_CFG_CFG);
/* Number of *lower* indexes that are arbitrated dwrr */
spx5_rmw(HSCH_SE_CFG_SE_DWRR_CNT_SET(dwrr->count),
HSCH_SE_CFG_SE_DWRR_CNT, port->sparx5,
HSCH_SE_CFG(port->portno));
for (i = 0; i < dwrr->count; i++) {
spx5_rmw(HSCH_DWRR_ENTRY_DWRR_COST_SET(dwrr->cost[i]),
HSCH_DWRR_ENTRY_DWRR_COST, port->sparx5,
HSCH_DWRR_ENTRY(i));
}
return 0;
}
static int sparx5_leak_groups_init(struct sparx5 *sparx5)
{
const struct sparx5_ops *ops = sparx5->data->ops;
struct sparx5_layer *layer;
u32 sys_clk_per_100ps;
struct sparx5_lg *lg;
u32 leak_time_us;
int i, ii;
sys_clk_per_100ps = spx5_rd(sparx5, HSCH_SYS_CLK_PER);
for (i = 0; i < SPX5_HSCH_LAYER_CNT; i++) {
layer = &layers[i];
for (ii = 0; ii < SPX5_HSCH_LEAK_GRP_CNT; ii++) {
lg = &layer->leak_groups[ii];
lg->max_rate = ops->get_hsch_max_group_rate(i);
/* Calculate the leak time in us, to serve a maximum
* rate of 'max_rate' for this group
*/
leak_time_us = (SPX5_SE_RATE_MAX * 1000) / lg->max_rate;
/* Hardware wants leak time in ns */
lg->leak_time = 1000 * leak_time_us;
/* Calculate resolution */
lg->resolution = 1000 / leak_time_us;
/* Maximum number of shapers that can be served by
* this leak group
*/
lg->max_ses = (1000 * leak_time_us) / sys_clk_per_100ps;
/* Example:
* Wanted bandwidth is 100Mbit:
*
* 100 mbps can be served by leak group zero.
*
* leak_time is 125000 ns.
* resolution is: 8
*
* cir = 100000 / 8 = 12500
* leaks_pr_sec = 125000 / 10^9 = 8000
* bw = 12500 * 8000 = 10^8 (100 Mbit)
*/
/* Disable by default - this also indicates an empty
* leak group
*/
sparx5_lg_disable(sparx5, i, ii);
}
}
return 0;
}
int sparx5_qos_init(struct sparx5 *sparx5)
{
int ret;
ret = sparx5_leak_groups_init(sparx5);
if (ret < 0)
return ret;
ret = sparx5_dcb_init(sparx5);
if (ret < 0)
return ret;
sparx5_psfp_init(sparx5);
return 0;
}
int sparx5_tc_mqprio_add(struct net_device *ndev, u8 num_tc)
{
int i;
if (num_tc != SPX5_PRIOS) {
netdev_err(ndev, "Only %d traffic classes supported\n",
SPX5_PRIOS);
return -EINVAL;
}
netdev_set_num_tc(ndev, num_tc);
for (i = 0; i < num_tc; i++)
netdev_set_tc_queue(ndev, i, 1, i);
netdev_dbg(ndev, "dev->num_tc %u dev->real_num_tx_queues %u\n",
ndev->num_tc, ndev->real_num_tx_queues);
return 0;
}
int sparx5_tc_mqprio_del(struct net_device *ndev)
{
netdev_reset_tc(ndev);
netdev_dbg(ndev, "dev->num_tc %u dev->real_num_tx_queues %u\n",
ndev->num_tc, ndev->real_num_tx_queues);
return 0;
}
int sparx5_tc_tbf_add(struct sparx5_port *port,
struct tc_tbf_qopt_offload_replace_params *params,
u32 layer, u32 idx)
{
struct sparx5_shaper sh = {
.mode = SPX5_SE_MODE_DATARATE,
.rate = div_u64(params->rate.rate_bytes_ps, 1000) * 8,
.burst = params->max_size,
};
struct sparx5_lg *lg;
u32 group;
/* Find suitable group for this se */
if (sparx5_lg_get_group_by_rate(layer, sh.rate, &group) < 0) {
pr_debug("Could not find leak group for se with rate: %d",
sh.rate);
return -EINVAL;
}
lg = &layers[layer].leak_groups[group];
pr_debug("Found matching group (speed: %d)\n", lg->max_rate);
if (sh.rate < SPX5_SE_RATE_MIN || sh.burst < SPX5_SE_BURST_MIN)
return -EINVAL;
/* Calculate committed rate and burst */
sh.rate = DIV_ROUND_UP(sh.rate, lg->resolution);
sh.burst = DIV_ROUND_UP(sh.burst, SPX5_SE_BURST_UNIT);
if (sh.rate > SPX5_SE_RATE_MAX || sh.burst > SPX5_SE_BURST_MAX)
return -EINVAL;
return sparx5_shaper_conf_set(port, &sh, layer, idx, group);
}
int sparx5_tc_tbf_del(struct sparx5_port *port, u32 layer, u32 idx)
{
struct sparx5_shaper sh = {0};
u32 group;
sparx5_lg_get_group_by_index(port->sparx5, layer, idx, &group);
return sparx5_shaper_conf_set(port, &sh, layer, idx, group);
}
int sparx5_tc_ets_add(struct sparx5_port *port,
struct tc_ets_qopt_offload_replace_params *params)
{
struct sparx5_dwrr dwrr = {0};
/* Minimum weight for each iteration */
unsigned int w_min = 100;
int i;
/* Find minimum weight for all dwrr bands */
for (i = 0; i < SPX5_PRIOS; i++) {
if (params->quanta[i] == 0)
continue;
w_min = min(w_min, params->weights[i]);
}
for (i = 0; i < SPX5_PRIOS; i++) {
/* Strict band; skip */
if (params->quanta[i] == 0)
continue;
dwrr.count++;
/* On the sparx5, bands with higher indexes are preferred and
* arbitrated strict. Strict bands are put in the lower indexes,
* by tc, so we reverse the bands here.
*
* Also convert the weight to something the hardware
* understands.
*/
dwrr.cost[SPX5_PRIOS - i - 1] =
sparx5_weight_to_hw_cost(w_min, params->weights[i]);
}
return sparx5_dwrr_conf_set(port, &dwrr);
}
int sparx5_tc_ets_del(struct sparx5_port *port)
{
struct sparx5_dwrr dwrr = {0};
return sparx5_dwrr_conf_set(port, &dwrr);
}
@@ -0,0 +1,84 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/* Microchip Sparx5 Switch driver
*
* Copyright (c) 2022 Microchip Technology Inc. and its subsidiaries.
*/
#ifndef __SPARX5_QOS_H__
#define __SPARX5_QOS_H__
#include <linux/netdevice.h>
/* Number of Layers */
#define SPX5_HSCH_LAYER_CNT 3
/* Scheduling elements per layer */
#define SPX5_HSCH_L0_SE_CNT 5040
#define SPX5_HSCH_L1_SE_CNT 64
#define SPX5_HSCH_L2_SE_CNT 64
/* Calculate Layer 0 Scheduler Element when using normal hierarchy */
#define SPX5_HSCH_L0_GET_IDX(port, queue) ((64 * (port)) + (8 * (queue)))
/* Number of leak groups */
#define SPX5_HSCH_LEAK_GRP_CNT 4
/* Scheduler modes */
#define SPX5_SE_MODE_LINERATE 0
#define SPX5_SE_MODE_DATARATE 1
/* Rate and burst */
#define SPX5_SE_RATE_MAX 262143
#define SPX5_SE_BURST_MAX 127
#define SPX5_SE_RATE_MIN 1
#define SPX5_SE_BURST_MIN 1
#define SPX5_SE_BURST_UNIT 4096
/* Dwrr */
#define SPX5_DWRR_COST_MAX 31
struct sparx5_shaper {
u32 mode;
u32 rate;
u32 burst;
};
struct sparx5_lg {
u32 max_rate;
u32 resolution;
u32 leak_time;
u32 max_ses;
};
struct sparx5_layer {
struct sparx5_lg leak_groups[SPX5_HSCH_LEAK_GRP_CNT];
};
struct sparx5_dwrr {
u32 count; /* Number of inputs running dwrr */
u8 cost[SPX5_PRIOS];
};
int sparx5_qos_init(struct sparx5 *sparx5);
/* Multi-Queue Priority */
int sparx5_tc_mqprio_add(struct net_device *ndev, u8 num_tc);
int sparx5_tc_mqprio_del(struct net_device *ndev);
/* Token Bucket Filter */
struct tc_tbf_qopt_offload_replace_params;
int sparx5_tc_tbf_add(struct sparx5_port *port,
struct tc_tbf_qopt_offload_replace_params *params,
u32 layer, u32 idx);
int sparx5_tc_tbf_del(struct sparx5_port *port, u32 layer, u32 idx);
/* Enhanced Transmission Selection */
struct tc_ets_qopt_offload_replace_params;
int sparx5_tc_ets_add(struct sparx5_port *port,
struct tc_ets_qopt_offload_replace_params *params);
int sparx5_tc_ets_del(struct sparx5_port *port);
u32 sparx5_get_hsch_max_group_rate(int grp);
#endif /* __SPARX5_QOS_H__ */
@@ -0,0 +1,222 @@
// SPDX-License-Identifier: GPL-2.0+
/* Microchip Sparx5 Switch driver
*
* Copyright (c) 2024 Microchip Technology Inc.
*/
/* This file is autogenerated by cml-utils 2024-09-30 11:48:29 +0200.
* Commit ID: 9d07b8d19363f3cd3590ddb3f7a2e2768e16524b
*/
#include "sparx5_regs.h"
const unsigned int sparx5_tsize[TSIZE_LAST] = {
[TC_DEV10G] = 12,
[TC_DEV2G5] = 65,
[TC_DEV5G] = 13,
[TC_PCS10G_BR] = 12,
[TC_PCS5G_BR] = 13,
};
const unsigned int sparx5_raddr[RADDR_LAST] = {
[RA_CPU_PROC_CTRL] = 176,
[RA_GCB_SOFT_RST] = 8,
[RA_GCB_HW_SGPIO_TO_SD_MAP_CFG] = 24,
};
const unsigned int sparx5_rcnt[RCNT_LAST] = {
[RC_ANA_AC_OWN_UPSID] = 3,
[RC_ANA_ACL_VCAP_S2_CFG] = 70,
[RC_ANA_ACL_OWN_UPSID] = 3,
[RC_ANA_CL_OWN_UPSID] = 3,
[RC_ANA_L2_OWN_UPSID] = 3,
[RC_ASM_PORT_CFG] = 67,
[RC_DSM_BUF_CFG] = 67,
[RC_DSM_DEV_TX_STOP_WM_CFG] = 67,
[RC_DSM_RX_PAUSE_CFG] = 67,
[RC_DSM_MAC_CFG] = 67,
[RC_DSM_MAC_ADDR_BASE_HIGH_CFG] = 65,
[RC_DSM_MAC_ADDR_BASE_LOW_CFG] = 65,
[RC_DSM_TAXI_CAL_CFG] = 9,
[RC_GCB_HW_SGPIO_TO_SD_MAP_CFG] = 65,
[RC_HSCH_PORT_MODE] = 70,
[RC_QFWD_SWITCH_PORT_MODE] = 70,
[RC_QSYS_PAUSE_CFG] = 70,
[RC_QSYS_ATOP] = 70,
[RC_QSYS_FWD_PRESSURE] = 70,
[RC_QSYS_CAL_AUTO] = 7,
[RC_REW_OWN_UPSID] = 3,
[RC_REW_RTAG_ETAG_CTRL] = 70,
};
const unsigned int sparx5_gaddr[GADDR_LAST] = {
[GA_ANA_AC_RAM_CTRL] = 839108,
[GA_ANA_AC_PS_COMMON] = 894472,
[GA_ANA_AC_MIRROR_PROBE] = 893696,
[GA_ANA_AC_SRC] = 849920,
[GA_ANA_AC_PGID] = 786432,
[GA_ANA_AC_TSN_SF] = 839136,
[GA_ANA_AC_TSN_SF_CFG] = 839680,
[GA_ANA_AC_TSN_SF_STATUS] = 839072,
[GA_ANA_AC_SG_ACCESS] = 839140,
[GA_ANA_AC_SG_CONFIG] = 851584,
[GA_ANA_AC_SG_STATUS] = 839088,
[GA_ANA_AC_SG_STATUS_STICKY] = 839152,
[GA_ANA_AC_STAT_GLOBAL_CFG_PORT] = 851552,
[GA_ANA_AC_STAT_CNT_CFG_PORT] = 843776,
[GA_ANA_AC_STAT_GLOBAL_CFG_ACL] = 893792,
[GA_ANA_ACL_COMMON] = 32768,
[GA_ANA_ACL_KEY_SEL] = 34200,
[GA_ANA_ACL_CNT_B] = 16384,
[GA_ANA_ACL_STICKY] = 36408,
[GA_ANA_AC_POL_POL_ALL_CFG] = 75968,
[GA_ANA_AC_POL_COMMON_BDLB] = 79048,
[GA_ANA_AC_POL_COMMON_BUM_SLB] = 79056,
[GA_ANA_AC_SDLB_LBGRP_TBL] = 295468,
[GA_ANA_CL_PORT] = 131072,
[GA_ANA_CL_COMMON] = 166912,
[GA_ANA_L2_COMMON] = 566024,
[GA_ANA_L3_COMMON] = 493632,
[GA_ANA_L3_VLAN_ARP_L3MC_STICKY] = 491460,
[GA_ASM_CFG] = 33280,
[GA_ASM_PFC_TIMER_CFG] = 34716,
[GA_ASM_LBK_WM_CFG] = 34744,
[GA_ASM_LBK_MISC_CFG] = 34756,
[GA_ASM_RAM_CTRL] = 34832,
[GA_EACL_ES2_KEY_SELECT_PROFILE] = 149504,
[GA_EACL_CNT_TBL] = 122880,
[GA_EACL_POL_CFG] = 150608,
[GA_EACL_ES2_STICKY] = 118696,
[GA_EACL_RAM_CTRL] = 118736,
[GA_GCB_SIO_CTRL] = 876,
[GA_HSCH_HSCH_DWRR] = 162816,
[GA_HSCH_HSCH_MISC] = 163104,
[GA_HSCH_HSCH_LEAK_LISTS] = 161664,
[GA_HSCH_SYSTEM] = 184000,
[GA_HSCH_MMGT] = 162368,
[GA_HSCH_TAS_CONFIG] = 162384,
[GA_PTP_PTP_CFG] = 320,
[GA_PTP_PTP_TOD_DOMAINS] = 336,
[GA_PTP_PHASE_DETECTOR_CTRL] = 420,
[GA_QSYS_CALCFG] = 2304,
[GA_QSYS_RAM_CTRL] = 2344,
[GA_REW_COMMON] = 387264,
[GA_REW_PORT] = 360448,
[GA_REW_VOE_PORT_LM_CNT] = 393216,
[GA_REW_RAM_CTRL] = 378696,
[GA_VOP_RAM_CTRL] = 279176,
[GA_XQS_SYSTEM] = 6768,
[GA_XQS_QLIMIT_SHR] = 7936,
};
const unsigned int sparx5_gcnt[GCNT_LAST] = {
[GC_ANA_AC_SRC] = 102,
[GC_ANA_AC_PGID] = 3290,
[GC_ANA_AC_TSN_SF_CFG] = 1024,
[GC_ANA_AC_STAT_CNT_CFG_PORT] = 70,
[GC_ANA_ACL_KEY_SEL] = 134,
[GC_ANA_ACL_CNT_A] = 4096,
[GC_ANA_ACL_CNT_B] = 4096,
[GC_ANA_AC_SDLB_LBGRP_TBL] = 10,
[GC_ANA_AC_SDLB_LBSET_TBL] = 4616,
[GC_ANA_CL_PORT] = 70,
[GC_ANA_L2_ISDX_LIMIT] = 1536,
[GC_ANA_L2_ISDX] = 4096,
[GC_ANA_L3_VLAN] = 5120,
[GC_ASM_DEV_STATISTICS] = 65,
[GC_EACL_ES2_KEY_SELECT_PROFILE] = 138,
[GC_EACL_CNT_TBL] = 2048,
[GC_GCB_SIO_CTRL] = 3,
[GC_HSCH_HSCH_CFG] = 5040,
[GC_HSCH_HSCH_DWRR] = 72,
[GC_PTP_PTP_PINS] = 5,
[GC_PTP_PHASE_DETECTOR_CTRL] = 5,
[GC_REW_PORT] = 70,
[GC_REW_VOE_PORT_LM_CNT] = 520,
};
const unsigned int sparx5_gsize[GSIZE_LAST] = {
[GW_ANA_AC_SRC] = 16,
[GW_ANA_L2_COMMON] = 700,
[GW_ASM_CFG] = 1088,
[GW_CPU_CPU_REGS] = 204,
[GW_DEV2G5_PHASE_DETECTOR_CTRL] = 8,
[GW_FDMA_FDMA] = 428,
[GW_GCB_CHIP_REGS] = 424,
[GW_HSCH_TAS_CONFIG] = 12,
[GW_PTP_PHASE_DETECTOR_CTRL] = 8,
[GW_QSYS_PAUSE_CFG] = 1128,
};
const unsigned int sparx5_fpos[FPOS_LAST] = {
[FP_CPU_PROC_CTRL_AARCH64_MODE_ENA] = 12,
[FP_CPU_PROC_CTRL_L2_RST_INVALIDATE_DIS] = 11,
[FP_CPU_PROC_CTRL_L1_RST_INVALIDATE_DIS] = 10,
[FP_CPU_PROC_CTRL_BE_EXCEP_MODE] = 9,
[FP_CPU_PROC_CTRL_VINITHI] = 8,
[FP_CPU_PROC_CTRL_CFGTE] = 7,
[FP_CPU_PROC_CTRL_CP15S_DISABLE] = 6,
[FP_CPU_PROC_CTRL_PROC_CRYPTO_DISABLE] = 5,
[FP_CPU_PROC_CTRL_L2_FLUSH_REQ] = 1,
[FP_DEV2G5_PHAD_CTRL_PHAD_ENA] = 7,
[FP_DEV2G5_PHAD_CTRL_PHAD_FAILED] = 6,
[FP_FDMA_CH_CFG_CH_XTR_STATUS_MODE] = 7,
[FP_FDMA_CH_CFG_CH_INTR_DB_EOF_ONLY] = 6,
[FP_FDMA_CH_CFG_CH_INJ_PORT] = 5,
[FP_PTP_PTP_PIN_CFG_PTP_PIN_ACTION] = 26,
[FP_PTP_PTP_PIN_CFG_PTP_PIN_SYNC] = 24,
[FP_PTP_PTP_PIN_CFG_PTP_PIN_INV_POL] = 23,
[FP_PTP_PHAD_CTRL_PHAD_ENA] = 7,
[FP_PTP_PHAD_CTRL_PHAD_FAILED] = 6,
};
const unsigned int sparx5_fsize[FSIZE_LAST] = {
[FW_ANA_AC_PROBE_PORT_CFG_PROBE_PORT_MASK] = 32,
[FW_ANA_AC_SRC_CFG_PORT_MASK] = 32,
[FW_ANA_AC_PGID_CFG_PORT_MASK] = 32,
[FW_ANA_AC_TSN_SF_PORT_NUM] = 9,
[FW_ANA_AC_TSN_SF_CFG_TSN_SGID] = 10,
[FW_ANA_AC_TSN_SF_STATUS_TSN_SFID] = 10,
[FW_ANA_AC_SG_ACCESS_CTRL_SGID] = 10,
[FW_ANA_AC_PORT_SGE_CFG_MASK] = 16,
[FW_ANA_AC_SDLB_XLB_START_LBSET_START] = 13,
[FW_ANA_AC_SDLB_LBGRP_MISC_THRES_SHIFT] = 5,
[FW_ANA_AC_SDLB_LBGRP_STATE_TBL_PUP_LBSET_NEXT] = 13,
[FW_ANA_AC_SDLB_XLB_NEXT_LBSET_NEXT] = 13,
[FW_ANA_AC_SDLB_XLB_NEXT_LBGRP] = 4,
[FW_ANA_AC_SDLB_INH_LBSET_ADDR_INH_LBSET_ADDR] = 13,
[FW_ANA_L2_AUTO_LRN_CFG_AUTO_LRN_ENA] = 32,
[FW_ANA_L2_DLB_CFG_DLB_IDX] = 13,
[FW_ANA_L2_TSN_CFG_TSN_SFID] = 10,
[FW_ANA_L3_VLAN_MASK_CFG_VLAN_PORT_MASK] = 32,
[FW_FDMA_CH_CFG_CH_DCB_DB_CNT] = 4,
[FW_GCB_HW_SGPIO_TO_SD_MAP_CFG_SGPIO_TO_SD_SEL] = 9,
[FW_HSCH_SE_CFG_SE_DWRR_CNT] = 7,
[FW_HSCH_SE_CONNECT_SE_LEAK_LINK] = 16,
[FW_HSCH_SE_DLB_SENSE_SE_DLB_DPORT] = 7,
[FW_HSCH_HSCH_CFG_CFG_CFG_SE_IDX] = 13,
[FW_HSCH_HSCH_LEAK_CFG_LEAK_FIRST] = 16,
[FW_HSCH_FLUSH_CTRL_FLUSH_PORT] = 7,
[FW_HSCH_FLUSH_CTRL_FLUSH_HIER] = 16,
[FW_LRN_COMMON_ACCESS_CTRL_CPU_ACCESS_DIRECT_ROW] = 14,
[FW_LRN_MAC_ACCESS_CFG_3_MAC_ENTRY_ISDX_LIMIT_IDX] = 11,
[FW_LRN_AUTOAGE_CFG_2_NEXT_ROW] = 14,
[FW_PTP_PTP_PIN_INTR_INTR_PTP] = 5,
[FW_PTP_PTP_PIN_INTR_ENA_INTR_PTP_ENA] = 5,
[FW_PTP_PTP_INTR_IDENT_INTR_PTP_IDENT] = 5,
[FW_PTP_PTP_PIN_CFG_PTP_PIN_SELECT] = 2,
[FW_QFWD_FRAME_COPY_CFG_FRMC_PORT_VAL] = 7,
[FW_QRES_RES_CFG_WM_HIGH] = 12,
[FW_QRES_RES_STAT_MAXUSE] = 21,
[FW_QRES_RES_STAT_CUR_INUSE] = 21,
[FW_QSYS_PAUSE_CFG_PAUSE_START] = 12,
[FW_QSYS_PAUSE_CFG_PAUSE_STOP] = 12,
[FW_QSYS_ATOP_ATOP] = 12,
[FW_QSYS_ATOP_TOT_CFG_ATOP_TOT] = 12,
[FW_REW_RTAG_ETAG_CTRL_IPE_TBL] = 7,
[FW_XQS_STAT_CFG_STAT_VIEW] = 13,
[FW_XQS_QLIMIT_SHR_TOP_CFG_QLIMIT_SHR_TOP] = 15,
[FW_XQS_QLIMIT_SHR_ATOP_CFG_QLIMIT_SHR_ATOP] = 15,
[FW_XQS_QLIMIT_SHR_CTOP_CFG_QLIMIT_SHR_CTOP] = 15,
[FW_XQS_QLIMIT_SHR_QLIM_CFG_QLIMIT_SHR_QLIM] = 15,
};
@@ -0,0 +1,247 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/* Microchip Sparx5 Switch driver
*
* Copyright (c) 2024 Microchip Technology Inc.
*/
/* This file is autogenerated by cml-utils 2024-09-30 11:48:29 +0200.
* Commit ID: 9d07b8d19363f3cd3590ddb3f7a2e2768e16524b
*/
#ifndef _SPARX5_REGS_H_
#define _SPARX5_REGS_H_
/* These enumerated values are used to index the platform specific structs
* containing the addresses, counts, size and positions, of register groups,
* registers and fields.
*/
enum sparx5_tsize_enum {
TC_DEV10G,
TC_DEV2G5,
TC_DEV5G,
TC_PCS10G_BR,
TC_PCS5G_BR,
TSIZE_LAST,
};
enum sparx5_raddr_enum {
RA_CPU_PROC_CTRL,
RA_GCB_SOFT_RST,
RA_GCB_HW_SGPIO_TO_SD_MAP_CFG,
RADDR_LAST,
};
enum sparx5_rcnt_enum {
RC_ANA_AC_OWN_UPSID,
RC_ANA_ACL_VCAP_S2_CFG,
RC_ANA_ACL_OWN_UPSID,
RC_ANA_CL_OWN_UPSID,
RC_ANA_L2_OWN_UPSID,
RC_ASM_PORT_CFG,
RC_DSM_BUF_CFG,
RC_DSM_DEV_TX_STOP_WM_CFG,
RC_DSM_RX_PAUSE_CFG,
RC_DSM_MAC_CFG,
RC_DSM_MAC_ADDR_BASE_HIGH_CFG,
RC_DSM_MAC_ADDR_BASE_LOW_CFG,
RC_DSM_TAXI_CAL_CFG,
RC_GCB_HW_SGPIO_TO_SD_MAP_CFG,
RC_HSCH_PORT_MODE,
RC_QFWD_SWITCH_PORT_MODE,
RC_QSYS_PAUSE_CFG,
RC_QSYS_ATOP,
RC_QSYS_FWD_PRESSURE,
RC_QSYS_CAL_AUTO,
RC_REW_OWN_UPSID,
RC_REW_RTAG_ETAG_CTRL,
RCNT_LAST,
};
enum sparx5_gaddr_enum {
GA_ANA_AC_RAM_CTRL,
GA_ANA_AC_PS_COMMON,
GA_ANA_AC_MIRROR_PROBE,
GA_ANA_AC_SRC,
GA_ANA_AC_PGID,
GA_ANA_AC_TSN_SF,
GA_ANA_AC_TSN_SF_CFG,
GA_ANA_AC_TSN_SF_STATUS,
GA_ANA_AC_SG_ACCESS,
GA_ANA_AC_SG_CONFIG,
GA_ANA_AC_SG_STATUS,
GA_ANA_AC_SG_STATUS_STICKY,
GA_ANA_AC_STAT_GLOBAL_CFG_PORT,
GA_ANA_AC_STAT_CNT_CFG_PORT,
GA_ANA_AC_STAT_GLOBAL_CFG_ACL,
GA_ANA_ACL_COMMON,
GA_ANA_ACL_KEY_SEL,
GA_ANA_ACL_CNT_B,
GA_ANA_ACL_STICKY,
GA_ANA_AC_POL_POL_ALL_CFG,
GA_ANA_AC_POL_COMMON_BDLB,
GA_ANA_AC_POL_COMMON_BUM_SLB,
GA_ANA_AC_SDLB_LBGRP_TBL,
GA_ANA_CL_PORT,
GA_ANA_CL_COMMON,
GA_ANA_L2_COMMON,
GA_ANA_L3_COMMON,
GA_ANA_L3_VLAN_ARP_L3MC_STICKY,
GA_ASM_CFG,
GA_ASM_PFC_TIMER_CFG,
GA_ASM_LBK_WM_CFG,
GA_ASM_LBK_MISC_CFG,
GA_ASM_RAM_CTRL,
GA_EACL_ES2_KEY_SELECT_PROFILE,
GA_EACL_CNT_TBL,
GA_EACL_POL_CFG,
GA_EACL_ES2_STICKY,
GA_EACL_RAM_CTRL,
GA_GCB_SIO_CTRL,
GA_HSCH_HSCH_DWRR,
GA_HSCH_HSCH_MISC,
GA_HSCH_HSCH_LEAK_LISTS,
GA_HSCH_SYSTEM,
GA_HSCH_MMGT,
GA_HSCH_TAS_CONFIG,
GA_PTP_PTP_CFG,
GA_PTP_PTP_TOD_DOMAINS,
GA_PTP_PHASE_DETECTOR_CTRL,
GA_QSYS_CALCFG,
GA_QSYS_RAM_CTRL,
GA_REW_COMMON,
GA_REW_PORT,
GA_REW_VOE_PORT_LM_CNT,
GA_REW_RAM_CTRL,
GA_VOP_RAM_CTRL,
GA_XQS_SYSTEM,
GA_XQS_QLIMIT_SHR,
GADDR_LAST,
};
enum sparx5_gcnt_enum {
GC_ANA_AC_SRC,
GC_ANA_AC_PGID,
GC_ANA_AC_TSN_SF_CFG,
GC_ANA_AC_STAT_CNT_CFG_PORT,
GC_ANA_ACL_KEY_SEL,
GC_ANA_ACL_CNT_A,
GC_ANA_ACL_CNT_B,
GC_ANA_AC_SDLB_LBGRP_TBL,
GC_ANA_AC_SDLB_LBSET_TBL,
GC_ANA_CL_PORT,
GC_ANA_L2_ISDX_LIMIT,
GC_ANA_L2_ISDX,
GC_ANA_L3_VLAN,
GC_ASM_DEV_STATISTICS,
GC_EACL_ES2_KEY_SELECT_PROFILE,
GC_EACL_CNT_TBL,
GC_GCB_SIO_CTRL,
GC_HSCH_HSCH_CFG,
GC_HSCH_HSCH_DWRR,
GC_PTP_PTP_PINS,
GC_PTP_PHASE_DETECTOR_CTRL,
GC_REW_PORT,
GC_REW_VOE_PORT_LM_CNT,
GCNT_LAST,
};
enum sparx5_gsize_enum {
GW_ANA_AC_SRC,
GW_ANA_L2_COMMON,
GW_ASM_CFG,
GW_CPU_CPU_REGS,
GW_DEV2G5_PHASE_DETECTOR_CTRL,
GW_FDMA_FDMA,
GW_GCB_CHIP_REGS,
GW_HSCH_TAS_CONFIG,
GW_PTP_PHASE_DETECTOR_CTRL,
GW_QSYS_PAUSE_CFG,
GSIZE_LAST,
};
enum sparx5_fpos_enum {
FP_CPU_PROC_CTRL_AARCH64_MODE_ENA,
FP_CPU_PROC_CTRL_L2_RST_INVALIDATE_DIS,
FP_CPU_PROC_CTRL_L1_RST_INVALIDATE_DIS,
FP_CPU_PROC_CTRL_BE_EXCEP_MODE,
FP_CPU_PROC_CTRL_VINITHI,
FP_CPU_PROC_CTRL_CFGTE,
FP_CPU_PROC_CTRL_CP15S_DISABLE,
FP_CPU_PROC_CTRL_PROC_CRYPTO_DISABLE,
FP_CPU_PROC_CTRL_L2_FLUSH_REQ,
FP_DEV2G5_PHAD_CTRL_PHAD_ENA,
FP_DEV2G5_PHAD_CTRL_PHAD_FAILED,
FP_FDMA_CH_CFG_CH_XTR_STATUS_MODE,
FP_FDMA_CH_CFG_CH_INTR_DB_EOF_ONLY,
FP_FDMA_CH_CFG_CH_INJ_PORT,
FP_PTP_PTP_PIN_CFG_PTP_PIN_ACTION,
FP_PTP_PTP_PIN_CFG_PTP_PIN_SYNC,
FP_PTP_PTP_PIN_CFG_PTP_PIN_INV_POL,
FP_PTP_PHAD_CTRL_PHAD_ENA,
FP_PTP_PHAD_CTRL_PHAD_FAILED,
FPOS_LAST,
};
enum sparx5_fsize_enum {
FW_ANA_AC_PROBE_PORT_CFG_PROBE_PORT_MASK,
FW_ANA_AC_SRC_CFG_PORT_MASK,
FW_ANA_AC_PGID_CFG_PORT_MASK,
FW_ANA_AC_TSN_SF_PORT_NUM,
FW_ANA_AC_TSN_SF_CFG_TSN_SGID,
FW_ANA_AC_TSN_SF_STATUS_TSN_SFID,
FW_ANA_AC_SG_ACCESS_CTRL_SGID,
FW_ANA_AC_PORT_SGE_CFG_MASK,
FW_ANA_AC_SDLB_XLB_START_LBSET_START,
FW_ANA_AC_SDLB_LBGRP_MISC_THRES_SHIFT,
FW_ANA_AC_SDLB_LBGRP_STATE_TBL_PUP_LBSET_NEXT,
FW_ANA_AC_SDLB_XLB_NEXT_LBSET_NEXT,
FW_ANA_AC_SDLB_XLB_NEXT_LBGRP,
FW_ANA_AC_SDLB_INH_LBSET_ADDR_INH_LBSET_ADDR,
FW_ANA_L2_AUTO_LRN_CFG_AUTO_LRN_ENA,
FW_ANA_L2_DLB_CFG_DLB_IDX,
FW_ANA_L2_TSN_CFG_TSN_SFID,
FW_ANA_L3_VLAN_MASK_CFG_VLAN_PORT_MASK,
FW_FDMA_CH_CFG_CH_DCB_DB_CNT,
FW_GCB_HW_SGPIO_TO_SD_MAP_CFG_SGPIO_TO_SD_SEL,
FW_HSCH_SE_CFG_SE_DWRR_CNT,
FW_HSCH_SE_CONNECT_SE_LEAK_LINK,
FW_HSCH_SE_DLB_SENSE_SE_DLB_DPORT,
FW_HSCH_HSCH_CFG_CFG_CFG_SE_IDX,
FW_HSCH_HSCH_LEAK_CFG_LEAK_FIRST,
FW_HSCH_FLUSH_CTRL_FLUSH_PORT,
FW_HSCH_FLUSH_CTRL_FLUSH_HIER,
FW_LRN_COMMON_ACCESS_CTRL_CPU_ACCESS_DIRECT_ROW,
FW_LRN_MAC_ACCESS_CFG_3_MAC_ENTRY_ISDX_LIMIT_IDX,
FW_LRN_AUTOAGE_CFG_2_NEXT_ROW,
FW_PTP_PTP_PIN_INTR_INTR_PTP,
FW_PTP_PTP_PIN_INTR_ENA_INTR_PTP_ENA,
FW_PTP_PTP_INTR_IDENT_INTR_PTP_IDENT,
FW_PTP_PTP_PIN_CFG_PTP_PIN_SELECT,
FW_QFWD_FRAME_COPY_CFG_FRMC_PORT_VAL,
FW_QRES_RES_CFG_WM_HIGH,
FW_QRES_RES_STAT_MAXUSE,
FW_QRES_RES_STAT_CUR_INUSE,
FW_QSYS_PAUSE_CFG_PAUSE_START,
FW_QSYS_PAUSE_CFG_PAUSE_STOP,
FW_QSYS_ATOP_ATOP,
FW_QSYS_ATOP_TOT_CFG_ATOP_TOT,
FW_REW_RTAG_ETAG_CTRL_IPE_TBL,
FW_XQS_STAT_CFG_STAT_VIEW,
FW_XQS_QLIMIT_SHR_TOP_CFG_QLIMIT_SHR_TOP,
FW_XQS_QLIMIT_SHR_ATOP_CFG_QLIMIT_SHR_ATOP,
FW_XQS_QLIMIT_SHR_CTOP_CFG_QLIMIT_SHR_CTOP,
FW_XQS_QLIMIT_SHR_QLIM_CFG_QLIMIT_SHR_QLIM,
FSIZE_LAST,
};
extern const unsigned int sparx5_tsize[TSIZE_LAST];
extern const unsigned int sparx5_raddr[RADDR_LAST];
extern const unsigned int sparx5_rcnt[RCNT_LAST];
extern const unsigned int sparx5_gaddr[GADDR_LAST];
extern const unsigned int sparx5_gcnt[GCNT_LAST];
extern const unsigned int sparx5_gsize[GSIZE_LAST];
extern const unsigned int sparx5_fpos[FPOS_LAST];
extern const unsigned int sparx5_fsize[FSIZE_LAST];
#endif /* _SPARX5_REGS_H_ */
@@ -0,0 +1,338 @@
// SPDX-License-Identifier: GPL-2.0+
/* Microchip Sparx5 Switch driver
*
* Copyright (c) 2023 Microchip Technology Inc. and its subsidiaries.
*/
#include "sparx5_main_regs.h"
#include "sparx5_main.h"
struct sparx5_sdlb_group sdlb_groups[SPX5_SDLB_GROUP_CNT] = {
{ SPX5_SDLB_GROUP_RATE_MAX, 8192 / 1, 64 }, /* 25 G */
{ 15000000000ULL, 8192 / 1, 64 }, /* 15 G */
{ 10000000000ULL, 8192 / 1, 64 }, /* 10 G */
{ 5000000000ULL, 8192 / 1, 64 }, /* 5 G */
{ 2500000000ULL, 8192 / 1, 64 }, /* 2.5 G */
{ 1000000000ULL, 8192 / 2, 64 }, /* 1 G */
{ 500000000ULL, 8192 / 2, 64 }, /* 500 M */
{ 100000000ULL, 8192 / 4, 64 }, /* 100 M */
{ 50000000ULL, 8192 / 4, 64 }, /* 50 M */
{ 5000000ULL, 8192 / 8, 64 } /* 5 M */
};
struct sparx5_sdlb_group *sparx5_get_sdlb_group(int idx)
{
return &sdlb_groups[idx];
}
u64 sparx5_sdlb_clk_hz_get(struct sparx5 *sparx5)
{
u64 clk_hz;
clk_hz = (10 * 1000 * 1000) /
(sparx5_clk_period(sparx5->coreclock) / 100);
return clk_hz *= 1000;
}
static int sparx5_sdlb_pup_interval_get(struct sparx5 *sparx5, u32 max_token,
u64 max_rate)
{
u64 clk_hz;
clk_hz = sparx5_sdlb_clk_hz_get(sparx5);
return div64_u64((8 * clk_hz * max_token), max_rate);
}
int sparx5_sdlb_pup_token_get(struct sparx5 *sparx5, u32 pup_interval, u64 rate)
{
u64 clk_hz;
if (!rate)
return SPX5_SDLB_PUP_TOKEN_DISABLE;
clk_hz = sparx5_sdlb_clk_hz_get(sparx5);
return DIV64_U64_ROUND_UP((rate * pup_interval), (clk_hz * 8));
}
static void sparx5_sdlb_group_disable(struct sparx5 *sparx5, u32 group)
{
spx5_rmw(ANA_AC_SDLB_PUP_CTRL_PUP_ENA_SET(0),
ANA_AC_SDLB_PUP_CTRL_PUP_ENA, sparx5,
ANA_AC_SDLB_PUP_CTRL(group));
}
static void sparx5_sdlb_group_enable(struct sparx5 *sparx5, u32 group)
{
spx5_rmw(ANA_AC_SDLB_PUP_CTRL_PUP_ENA_SET(1),
ANA_AC_SDLB_PUP_CTRL_PUP_ENA, sparx5,
ANA_AC_SDLB_PUP_CTRL(group));
}
static u32 sparx5_sdlb_group_get_first(struct sparx5 *sparx5, u32 group)
{
u32 val;
val = spx5_rd(sparx5, ANA_AC_SDLB_XLB_START(group));
return ANA_AC_SDLB_XLB_START_LBSET_START_GET(val);
}
static u32 sparx5_sdlb_group_get_next(struct sparx5 *sparx5, u32 group,
u32 lb)
{
u32 val;
val = spx5_rd(sparx5, ANA_AC_SDLB_XLB_NEXT(lb));
return ANA_AC_SDLB_XLB_NEXT_LBSET_NEXT_GET(val);
}
static bool sparx5_sdlb_group_is_first(struct sparx5 *sparx5, u32 group,
u32 lb)
{
return lb == sparx5_sdlb_group_get_first(sparx5, group);
}
static bool sparx5_sdlb_group_is_last(struct sparx5 *sparx5, u32 group,
u32 lb)
{
return lb == sparx5_sdlb_group_get_next(sparx5, group, lb);
}
static bool sparx5_sdlb_group_is_empty(struct sparx5 *sparx5, u32 group)
{
u32 val;
val = spx5_rd(sparx5, ANA_AC_SDLB_PUP_CTRL(group));
return ANA_AC_SDLB_PUP_CTRL_PUP_ENA_GET(val) == 0;
}
static u32 sparx5_sdlb_group_get_last(struct sparx5 *sparx5, u32 group)
{
u32 itr, next;
itr = sparx5_sdlb_group_get_first(sparx5, group);
for (;;) {
next = sparx5_sdlb_group_get_next(sparx5, group, itr);
if (itr == next)
return itr;
itr = next;
}
}
static bool sparx5_sdlb_group_is_singular(struct sparx5 *sparx5, u32 group)
{
if (sparx5_sdlb_group_is_empty(sparx5, group))
return false;
return sparx5_sdlb_group_get_first(sparx5, group) ==
sparx5_sdlb_group_get_last(sparx5, group);
}
static int sparx5_sdlb_group_get_adjacent(struct sparx5 *sparx5, u32 group,
u32 idx, u32 *prev, u32 *next,
u32 *first)
{
u32 itr;
*first = sparx5_sdlb_group_get_first(sparx5, group);
*prev = *first;
*next = *first;
itr = *first;
for (;;) {
*next = sparx5_sdlb_group_get_next(sparx5, group, itr);
if (itr == idx)
return 0; /* Found it */
if (itr == *next)
return -EINVAL; /* Was not found */
*prev = itr;
itr = *next;
}
}
static int sparx5_sdlb_group_get_count(struct sparx5 *sparx5, u32 group)
{
u32 itr, next;
int count = 0;
itr = sparx5_sdlb_group_get_first(sparx5, group);
for (;;) {
next = sparx5_sdlb_group_get_next(sparx5, group, itr);
if (itr == next)
return count;
itr = next;
count++;
}
}
int sparx5_sdlb_group_get_by_rate(struct sparx5 *sparx5, u32 rate, u32 burst)
{
const struct sparx5_ops *ops = sparx5->data->ops;
const struct sparx5_sdlb_group *group;
u64 rate_bps;
int i, count;
rate_bps = rate * 1000;
for (i = sparx5->data->consts->n_lb_groups - 1; i >= 0; i--) {
group = ops->get_sdlb_group(i);
count = sparx5_sdlb_group_get_count(sparx5, i);
/* Check that this group is not full.
* According to LB group configuration rules: the number of XLBs
* in a group must not exceed PUP_INTERVAL/4 - 1.
*/
if (count > ((group->pup_interval / 4) - 1))
continue;
if (rate_bps < group->max_rate)
return i;
}
return -ENOSPC;
}
int sparx5_sdlb_group_get_by_index(struct sparx5 *sparx5, u32 idx, u32 *group)
{
u32 itr, next;
int i;
for (i = 0; i < sparx5->data->consts->n_lb_groups; i++) {
if (sparx5_sdlb_group_is_empty(sparx5, i))
continue;
itr = sparx5_sdlb_group_get_first(sparx5, i);
for (;;) {
next = sparx5_sdlb_group_get_next(sparx5, i, itr);
if (itr == idx) {
*group = i;
return 0; /* Found it */
}
if (itr == next)
break; /* Was not found */
itr = next;
}
}
return -EINVAL;
}
static int sparx5_sdlb_group_link(struct sparx5 *sparx5, u32 group, u32 idx,
u32 first, u32 next, bool empty)
{
/* Stop leaking */
sparx5_sdlb_group_disable(sparx5, group);
if (empty)
return 0;
/* Link insertion lb to next lb */
spx5_wr(ANA_AC_SDLB_XLB_NEXT_LBSET_NEXT_SET(next) |
ANA_AC_SDLB_XLB_NEXT_LBGRP_SET(group),
sparx5, ANA_AC_SDLB_XLB_NEXT(idx));
/* Set the first lb */
spx5_wr(ANA_AC_SDLB_XLB_START_LBSET_START_SET(first), sparx5,
ANA_AC_SDLB_XLB_START(group));
/* Start leaking */
sparx5_sdlb_group_enable(sparx5, group);
return 0;
};
int sparx5_sdlb_group_add(struct sparx5 *sparx5, u32 group, u32 idx)
{
u32 first, next;
/* We always add to head of the list */
first = idx;
if (sparx5_sdlb_group_is_empty(sparx5, group))
next = idx;
else
next = sparx5_sdlb_group_get_first(sparx5, group);
return sparx5_sdlb_group_link(sparx5, group, idx, first, next, false);
}
int sparx5_sdlb_group_del(struct sparx5 *sparx5, u32 group, u32 idx)
{
u32 first, next, prev;
bool empty = false;
if (sparx5_sdlb_group_get_adjacent(sparx5, group, idx, &prev, &next,
&first) < 0) {
pr_err("%s:%d Could not find idx: %d in group: %d", __func__,
__LINE__, idx, group);
return -EINVAL;
}
if (sparx5_sdlb_group_is_singular(sparx5, group)) {
empty = true;
} else if (sparx5_sdlb_group_is_last(sparx5, group, idx)) {
/* idx is removed, prev is now last */
idx = prev;
next = prev;
} else if (sparx5_sdlb_group_is_first(sparx5, group, idx)) {
/* idx is removed and points to itself, first is next */
first = next;
next = idx;
} else {
/* Next is not touched */
idx = prev;
}
return sparx5_sdlb_group_link(sparx5, group, idx, first, next, empty);
}
void sparx5_sdlb_group_init(struct sparx5 *sparx5, u64 max_rate, u32 min_burst,
u32 frame_size, u32 idx)
{
const struct sparx5_ops *ops = sparx5->data->ops;
u32 thres_shift, mask = 0x01, power = 0;
struct sparx5_sdlb_group *group;
u64 max_token;
group = ops->get_sdlb_group(idx);
/* Number of positions to right-shift LB's threshold value. */
while ((min_burst & mask) == 0) {
power++;
mask <<= 1;
}
thres_shift = SPX5_SDLB_2CYCLES_TYPE2_THRES_OFFSET - power;
max_token = (min_burst > SPX5_SDLB_PUP_TOKEN_MAX) ?
SPX5_SDLB_PUP_TOKEN_MAX :
min_burst;
group->pup_interval =
sparx5_sdlb_pup_interval_get(sparx5, max_token, max_rate);
group->frame_size = frame_size;
spx5_wr(ANA_AC_SDLB_PUP_INTERVAL_PUP_INTERVAL_SET(group->pup_interval),
sparx5, ANA_AC_SDLB_PUP_INTERVAL(idx));
spx5_wr(ANA_AC_SDLB_FRM_RATE_TOKENS_FRM_RATE_TOKENS_SET(frame_size),
sparx5, ANA_AC_SDLB_FRM_RATE_TOKENS(idx));
spx5_wr(ANA_AC_SDLB_LBGRP_MISC_THRES_SHIFT_SET(thres_shift), sparx5,
ANA_AC_SDLB_LBGRP_MISC(idx));
}
@@ -0,0 +1,790 @@
// SPDX-License-Identifier: GPL-2.0+
/* Microchip Sparx5 Switch driver
*
* Copyright (c) 2021 Microchip Technology Inc. and its subsidiaries.
*/
#include <linux/if_bridge.h>
#include <net/switchdev.h>
#include "sparx5_main_regs.h"
#include "sparx5_main.h"
static struct workqueue_struct *sparx5_owq;
struct sparx5_switchdev_event_work {
struct work_struct work;
struct switchdev_notifier_fdb_info fdb_info;
struct net_device *dev;
struct sparx5 *sparx5;
unsigned long event;
};
static int sparx5_port_attr_pre_bridge_flags(struct sparx5_port *port,
struct switchdev_brport_flags flags)
{
if (flags.mask & ~(BR_FLOOD | BR_MCAST_FLOOD | BR_BCAST_FLOOD))
return -EINVAL;
return 0;
}
static void sparx5_port_update_mcast_ip_flood(struct sparx5_port *port, bool flood_flag)
{
bool should_flood = flood_flag || port->is_mrouter;
struct sparx5 *sparx5 = port->sparx5;
int pgid;
for (pgid = sparx5_get_pgid(sparx5, PGID_IPV4_MC_DATA);
pgid <= sparx5_get_pgid(sparx5, PGID_IPV6_MC_CTRL); pgid++)
sparx5_pgid_update_mask(port, pgid, should_flood);
}
static void sparx5_port_attr_bridge_flags(struct sparx5_port *port,
struct switchdev_brport_flags flags)
{
struct sparx5 *sparx5 = port->sparx5;
if (flags.mask & BR_MCAST_FLOOD) {
sparx5_pgid_update_mask(port,
sparx5_get_pgid(sparx5, PGID_MC_FLOOD),
!!(flags.val & BR_MCAST_FLOOD));
sparx5_port_update_mcast_ip_flood(port, !!(flags.val & BR_MCAST_FLOOD));
}
if (flags.mask & BR_FLOOD)
sparx5_pgid_update_mask(port,
sparx5_get_pgid(sparx5, PGID_UC_FLOOD),
!!(flags.val & BR_FLOOD));
if (flags.mask & BR_BCAST_FLOOD)
sparx5_pgid_update_mask(port,
sparx5_get_pgid(sparx5, PGID_BCAST),
!!(flags.val & BR_BCAST_FLOOD));
}
static void sparx5_attr_stp_state_set(struct sparx5_port *port,
u8 state)
{
struct sparx5 *sparx5 = port->sparx5;
if (!test_bit(port->portno, sparx5->bridge_mask)) {
netdev_err(port->ndev,
"Controlling non-bridged port %d?\n", port->portno);
return;
}
switch (state) {
case BR_STATE_FORWARDING:
set_bit(port->portno, sparx5->bridge_fwd_mask);
fallthrough;
case BR_STATE_LEARNING:
set_bit(port->portno, sparx5->bridge_lrn_mask);
break;
default:
/* All other states treated as blocking */
clear_bit(port->portno, sparx5->bridge_fwd_mask);
clear_bit(port->portno, sparx5->bridge_lrn_mask);
break;
}
/* apply the bridge_fwd_mask to all the ports */
sparx5_update_fwd(sparx5);
}
static void sparx5_port_attr_ageing_set(struct sparx5_port *port,
unsigned long ageing_clock_t)
{
unsigned long ageing_jiffies = clock_t_to_jiffies(ageing_clock_t);
u32 ageing_time = jiffies_to_msecs(ageing_jiffies);
sparx5_set_ageing(port->sparx5, ageing_time);
}
static void sparx5_port_attr_mrouter_set(struct sparx5_port *port,
struct net_device *orig_dev,
bool enable)
{
struct sparx5 *sparx5 = port->sparx5;
struct sparx5_mdb_entry *e;
bool flood_flag;
if ((enable && port->is_mrouter) || (!enable && !port->is_mrouter))
return;
/* Add/del mrouter port on all active mdb entries in HW.
* Don't change entry port mask, since that represents
* ports that actually joined that group.
*/
mutex_lock(&sparx5->mdb_lock);
list_for_each_entry(e, &sparx5->mdb_entries, list) {
if (!test_bit(port->portno, e->port_mask) &&
ether_addr_is_ip_mcast(e->addr))
sparx5_pgid_update_mask(port, e->pgid_idx, enable);
}
mutex_unlock(&sparx5->mdb_lock);
/* Enable/disable flooding depending on if port is mrouter port
* or if mcast flood is enabled.
*/
port->is_mrouter = enable;
flood_flag = br_port_flag_is_set(port->ndev, BR_MCAST_FLOOD);
sparx5_port_update_mcast_ip_flood(port, flood_flag);
}
static int sparx5_port_attr_set(struct net_device *dev, const void *ctx,
const struct switchdev_attr *attr,
struct netlink_ext_ack *extack)
{
struct sparx5_port *port = netdev_priv(dev);
switch (attr->id) {
case SWITCHDEV_ATTR_ID_PORT_PRE_BRIDGE_FLAGS:
return sparx5_port_attr_pre_bridge_flags(port,
attr->u.brport_flags);
case SWITCHDEV_ATTR_ID_PORT_BRIDGE_FLAGS:
sparx5_port_attr_bridge_flags(port, attr->u.brport_flags);
break;
case SWITCHDEV_ATTR_ID_PORT_STP_STATE:
sparx5_attr_stp_state_set(port, attr->u.stp_state);
break;
case SWITCHDEV_ATTR_ID_BRIDGE_AGEING_TIME:
sparx5_port_attr_ageing_set(port, attr->u.ageing_time);
break;
case SWITCHDEV_ATTR_ID_BRIDGE_VLAN_FILTERING:
/* Used PVID 1 when default_pvid is 0, to avoid
* collision with non-bridged ports.
*/
if (port->pvid == 0)
port->pvid = 1;
port->vlan_aware = attr->u.vlan_filtering;
sparx5_vlan_port_apply(port->sparx5, port);
break;
case SWITCHDEV_ATTR_ID_PORT_MROUTER:
sparx5_port_attr_mrouter_set(port,
attr->orig_dev,
attr->u.mrouter);
break;
default:
return -EOPNOTSUPP;
}
return 0;
}
static int sparx5_port_bridge_join(struct sparx5_port *port,
struct net_device *bridge,
struct netlink_ext_ack *extack)
{
struct switchdev_brport_flags flags = {0};
struct sparx5 *sparx5 = port->sparx5;
struct net_device *ndev = port->ndev;
int err;
if (bitmap_empty(sparx5->bridge_mask, SPX5_PORTS))
/* First bridged port */
sparx5->hw_bridge_dev = bridge;
else
if (sparx5->hw_bridge_dev != bridge)
/* This is adding the port to a second bridge, this is
* unsupported
*/
return -ENODEV;
set_bit(port->portno, sparx5->bridge_mask);
err = switchdev_bridge_port_offload(ndev, ndev, NULL, NULL, NULL,
false, extack);
if (err)
goto err_switchdev_offload;
/* Remove standalone port entry */
sparx5_mact_forget(sparx5, ndev->dev_addr, 0);
/* Port enters in bridge mode therefore don't need to copy to CPU
* frames for multicast in case the bridge is not requesting them
*/
__dev_mc_unsync(ndev, sparx5_mc_unsync);
/* Enable uc/mc/bc flooding */
flags.mask = BR_FLOOD | BR_MCAST_FLOOD | BR_BCAST_FLOOD;
flags.val = flags.mask;
sparx5_port_attr_bridge_flags(port, flags);
return 0;
err_switchdev_offload:
clear_bit(port->portno, sparx5->bridge_mask);
return err;
}
static void sparx5_port_bridge_leave(struct sparx5_port *port,
struct net_device *bridge)
{
struct switchdev_brport_flags flags = {0};
struct sparx5 *sparx5 = port->sparx5;
switchdev_bridge_port_unoffload(port->ndev, NULL, NULL, NULL);
clear_bit(port->portno, sparx5->bridge_mask);
if (bitmap_empty(sparx5->bridge_mask, SPX5_PORTS))
sparx5->hw_bridge_dev = NULL;
/* Clear bridge vlan settings before updating the port settings */
port->vlan_aware = 0;
port->pvid = NULL_VID;
port->vid = NULL_VID;
/* Forward frames to CPU */
sparx5_mact_learn(sparx5, sparx5_get_pgid(sparx5, PGID_CPU),
port->ndev->dev_addr, 0);
/* Port enters in host more therefore restore mc list */
__dev_mc_sync(port->ndev, sparx5_mc_sync, sparx5_mc_unsync);
/* Disable uc/mc/bc flooding */
flags.mask = BR_FLOOD | BR_MCAST_FLOOD | BR_BCAST_FLOOD;
flags.val = 0;
sparx5_port_attr_bridge_flags(port, flags);
}
static int sparx5_port_changeupper(struct net_device *dev,
struct netdev_notifier_changeupper_info *info)
{
struct sparx5_port *port = netdev_priv(dev);
struct netlink_ext_ack *extack;
int err = 0;
extack = netdev_notifier_info_to_extack(&info->info);
if (netif_is_bridge_master(info->upper_dev)) {
if (info->linking)
err = sparx5_port_bridge_join(port, info->upper_dev,
extack);
else
sparx5_port_bridge_leave(port, info->upper_dev);
sparx5_vlan_port_apply(port->sparx5, port);
}
return err;
}
static int sparx5_port_add_addr(struct net_device *dev, bool up)
{
struct sparx5_port *port = netdev_priv(dev);
struct sparx5 *sparx5 = port->sparx5;
u16 vid = port->pvid;
if (up)
sparx5_mact_learn(sparx5, sparx5_get_pgid(sparx5, PGID_CPU),
port->ndev->dev_addr, vid);
else
sparx5_mact_forget(sparx5, port->ndev->dev_addr, vid);
return 0;
}
static int sparx5_netdevice_port_event(struct net_device *dev,
struct notifier_block *nb,
unsigned long event, void *ptr)
{
int err = 0;
if (!sparx5_netdevice_check(dev))
return 0;
switch (event) {
case NETDEV_CHANGEUPPER:
err = sparx5_port_changeupper(dev, ptr);
break;
case NETDEV_PRE_UP:
err = sparx5_port_add_addr(dev, true);
break;
case NETDEV_DOWN:
err = sparx5_port_add_addr(dev, false);
break;
}
return err;
}
static int sparx5_netdevice_event(struct notifier_block *nb,
unsigned long event, void *ptr)
{
struct net_device *dev = netdev_notifier_info_to_dev(ptr);
int ret = 0;
ret = sparx5_netdevice_port_event(dev, nb, event, ptr);
return notifier_from_errno(ret);
}
static void sparx5_switchdev_bridge_fdb_event_work(struct work_struct *work)
{
struct sparx5_switchdev_event_work *switchdev_work =
container_of(work, struct sparx5_switchdev_event_work, work);
struct net_device *dev = switchdev_work->dev;
struct switchdev_notifier_fdb_info *fdb_info;
struct sparx5_port *port;
struct sparx5 *sparx5;
bool host_addr;
u16 vid;
rtnl_lock();
if (!sparx5_netdevice_check(dev)) {
host_addr = true;
sparx5 = switchdev_work->sparx5;
} else {
host_addr = false;
sparx5 = switchdev_work->sparx5;
port = netdev_priv(dev);
}
fdb_info = &switchdev_work->fdb_info;
/* Used PVID 1 when default_pvid is 0, to avoid
* collision with non-bridged ports.
*/
if (fdb_info->vid == 0)
vid = 1;
else
vid = fdb_info->vid;
switch (switchdev_work->event) {
case SWITCHDEV_FDB_ADD_TO_DEVICE:
if (host_addr)
sparx5_add_mact_entry(sparx5, dev,
sparx5_get_pgid(sparx5, PGID_CPU),
fdb_info->addr, vid);
else
sparx5_add_mact_entry(sparx5, port->ndev, port->portno,
fdb_info->addr, vid);
break;
case SWITCHDEV_FDB_DEL_TO_DEVICE:
sparx5_del_mact_entry(sparx5, fdb_info->addr, vid);
break;
}
rtnl_unlock();
kfree(switchdev_work->fdb_info.addr);
kfree(switchdev_work);
dev_put(dev);
}
static void sparx5_schedule_work(struct work_struct *work)
{
queue_work(sparx5_owq, work);
}
static int sparx5_switchdev_event(struct notifier_block *nb,
unsigned long event, void *ptr)
{
struct net_device *dev = switchdev_notifier_info_to_dev(ptr);
struct sparx5_switchdev_event_work *switchdev_work;
struct switchdev_notifier_fdb_info *fdb_info;
struct switchdev_notifier_info *info = ptr;
struct sparx5 *spx5;
int err;
spx5 = container_of(nb, struct sparx5, switchdev_nb);
switch (event) {
case SWITCHDEV_PORT_ATTR_SET:
err = switchdev_handle_port_attr_set(dev, ptr,
sparx5_netdevice_check,
sparx5_port_attr_set);
return notifier_from_errno(err);
case SWITCHDEV_FDB_ADD_TO_DEVICE:
fallthrough;
case SWITCHDEV_FDB_DEL_TO_DEVICE:
switchdev_work = kzalloc_obj(*switchdev_work, GFP_ATOMIC);
if (!switchdev_work)
return NOTIFY_BAD;
switchdev_work->dev = dev;
switchdev_work->event = event;
switchdev_work->sparx5 = spx5;
fdb_info = container_of(info,
struct switchdev_notifier_fdb_info,
info);
INIT_WORK(&switchdev_work->work,
sparx5_switchdev_bridge_fdb_event_work);
memcpy(&switchdev_work->fdb_info, ptr,
sizeof(switchdev_work->fdb_info));
switchdev_work->fdb_info.addr = kzalloc(ETH_ALEN, GFP_ATOMIC);
if (!switchdev_work->fdb_info.addr)
goto err_addr_alloc;
ether_addr_copy((u8 *)switchdev_work->fdb_info.addr,
fdb_info->addr);
dev_hold(dev);
sparx5_schedule_work(&switchdev_work->work);
break;
}
return NOTIFY_DONE;
err_addr_alloc:
kfree(switchdev_work);
return NOTIFY_BAD;
}
static int sparx5_handle_port_vlan_add(struct net_device *dev,
struct notifier_block *nb,
const struct switchdev_obj_port_vlan *v)
{
struct sparx5_port *port = netdev_priv(dev);
if (netif_is_bridge_master(dev)) {
struct sparx5 *sparx5 =
container_of(nb, struct sparx5,
switchdev_blocking_nb);
/* Flood broadcast to CPU */
sparx5_mact_learn(sparx5, sparx5_get_pgid(sparx5, PGID_BCAST),
dev->broadcast, v->vid);
return 0;
}
if (!sparx5_netdevice_check(dev))
return -EOPNOTSUPP;
return sparx5_vlan_vid_add(port, v->vid,
v->flags & BRIDGE_VLAN_INFO_PVID,
v->flags & BRIDGE_VLAN_INFO_UNTAGGED);
}
static int sparx5_alloc_mdb_entry(struct sparx5 *sparx5,
const unsigned char *addr,
u16 vid,
struct sparx5_mdb_entry **entry_out)
{
struct sparx5_mdb_entry *entry;
u16 pgid_idx;
int err;
entry = kzalloc_obj(*entry);
if (!entry)
return -ENOMEM;
err = sparx5_pgid_alloc_mcast(sparx5, &pgid_idx);
if (err) {
kfree(entry);
return err;
}
memcpy(entry->addr, addr, ETH_ALEN);
entry->vid = vid;
entry->pgid_idx = pgid_idx;
mutex_lock(&sparx5->mdb_lock);
list_add_tail(&entry->list, &sparx5->mdb_entries);
mutex_unlock(&sparx5->mdb_lock);
*entry_out = entry;
return 0;
}
static void sparx5_free_mdb_entry(struct sparx5 *sparx5,
const unsigned char *addr,
u16 vid)
{
struct sparx5_mdb_entry *entry, *tmp;
mutex_lock(&sparx5->mdb_lock);
list_for_each_entry_safe(entry, tmp, &sparx5->mdb_entries, list) {
if ((vid == 0 || entry->vid == vid) &&
ether_addr_equal(addr, entry->addr)) {
list_del(&entry->list);
sparx5_pgid_free(sparx5, entry->pgid_idx);
kfree(entry);
goto out;
}
}
out:
mutex_unlock(&sparx5->mdb_lock);
}
static struct sparx5_mdb_entry *sparx5_mdb_get_entry(struct sparx5 *sparx5,
const unsigned char *addr,
u16 vid)
{
struct sparx5_mdb_entry *e, *found = NULL;
mutex_lock(&sparx5->mdb_lock);
list_for_each_entry(e, &sparx5->mdb_entries, list) {
if (ether_addr_equal(e->addr, addr) && e->vid == vid) {
found = e;
goto out;
}
}
out:
mutex_unlock(&sparx5->mdb_lock);
return found;
}
static void sparx5_cpu_copy_ena(struct sparx5 *spx5, u16 pgid, bool enable)
{
spx5_rmw(ANA_AC_PGID_MISC_CFG_PGID_CPU_COPY_ENA_SET(enable),
ANA_AC_PGID_MISC_CFG_PGID_CPU_COPY_ENA, spx5,
ANA_AC_PGID_MISC_CFG(pgid));
}
static int sparx5_handle_port_mdb_add(struct net_device *dev,
struct notifier_block *nb,
const struct switchdev_obj_port_mdb *v)
{
struct sparx5_port *port = netdev_priv(dev);
struct sparx5 *spx5 = port->sparx5;
struct sparx5_mdb_entry *entry;
bool is_host, is_new;
int err, i;
u16 vid;
if (!sparx5_netdevice_check(dev))
return -EOPNOTSUPP;
is_host = netif_is_bridge_master(v->obj.orig_dev);
/* When VLAN unaware the vlan value is not parsed and we receive vid 0.
* Fall back to bridge vid 1.
*/
if (!br_vlan_enabled(spx5->hw_bridge_dev))
vid = 1;
else
vid = v->vid;
is_new = false;
entry = sparx5_mdb_get_entry(spx5, v->addr, vid);
if (!entry) {
err = sparx5_alloc_mdb_entry(spx5, v->addr, vid, &entry);
is_new = true;
if (err)
return err;
}
mutex_lock(&spx5->mdb_lock);
/* Add any mrouter ports to the new entry */
if (is_new && ether_addr_is_ip_mcast(v->addr))
for (i = 0; i < spx5->data->consts->n_ports; i++)
if (spx5->ports[i] && spx5->ports[i]->is_mrouter)
sparx5_pgid_update_mask(spx5->ports[i],
entry->pgid_idx,
true);
if (is_host && !entry->cpu_copy) {
sparx5_cpu_copy_ena(spx5, entry->pgid_idx, true);
entry->cpu_copy = true;
} else if (!is_host) {
sparx5_pgid_update_mask(port, entry->pgid_idx, true);
set_bit(port->portno, entry->port_mask);
}
mutex_unlock(&spx5->mdb_lock);
sparx5_mact_learn(spx5, entry->pgid_idx, entry->addr, entry->vid);
return 0;
}
static int sparx5_handle_port_mdb_del(struct net_device *dev,
struct notifier_block *nb,
const struct switchdev_obj_port_mdb *v)
{
struct sparx5_port *port = netdev_priv(dev);
struct sparx5 *spx5 = port->sparx5;
struct sparx5_mdb_entry *entry;
bool is_host;
u16 vid;
if (!sparx5_netdevice_check(dev))
return -EOPNOTSUPP;
is_host = netif_is_bridge_master(v->obj.orig_dev);
if (!br_vlan_enabled(spx5->hw_bridge_dev))
vid = 1;
else
vid = v->vid;
entry = sparx5_mdb_get_entry(spx5, v->addr, vid);
if (!entry)
return 0;
mutex_lock(&spx5->mdb_lock);
if (is_host && entry->cpu_copy) {
sparx5_cpu_copy_ena(spx5, entry->pgid_idx, false);
entry->cpu_copy = false;
} else if (!is_host) {
clear_bit(port->portno, entry->port_mask);
/* Port not mrouter port or addr is L2 mcast, remove port from mask. */
if (!port->is_mrouter || !ether_addr_is_ip_mcast(v->addr))
sparx5_pgid_update_mask(port, entry->pgid_idx, false);
}
mutex_unlock(&spx5->mdb_lock);
if (bitmap_empty(entry->port_mask, SPX5_PORTS) && !entry->cpu_copy) {
/* Clear pgid in case mrouter ports exists
* that are not part of the group.
*/
sparx5_pgid_clear(spx5, entry->pgid_idx);
sparx5_mact_forget(spx5, entry->addr, entry->vid);
sparx5_free_mdb_entry(spx5, entry->addr, entry->vid);
}
return 0;
}
static int sparx5_handle_port_obj_add(struct net_device *dev,
struct notifier_block *nb,
struct switchdev_notifier_port_obj_info *info)
{
const struct switchdev_obj *obj = info->obj;
int err;
switch (obj->id) {
case SWITCHDEV_OBJ_ID_PORT_VLAN:
err = sparx5_handle_port_vlan_add(dev, nb,
SWITCHDEV_OBJ_PORT_VLAN(obj));
break;
case SWITCHDEV_OBJ_ID_PORT_MDB:
case SWITCHDEV_OBJ_ID_HOST_MDB:
err = sparx5_handle_port_mdb_add(dev, nb,
SWITCHDEV_OBJ_PORT_MDB(obj));
break;
default:
err = -EOPNOTSUPP;
break;
}
info->handled = true;
return err;
}
static int sparx5_handle_port_vlan_del(struct net_device *dev,
struct notifier_block *nb,
u16 vid)
{
struct sparx5_port *port = netdev_priv(dev);
int ret;
/* Master bridge? */
if (netif_is_bridge_master(dev)) {
struct sparx5 *sparx5 =
container_of(nb, struct sparx5,
switchdev_blocking_nb);
sparx5_mact_forget(sparx5, dev->broadcast, vid);
return 0;
}
if (!sparx5_netdevice_check(dev))
return -EOPNOTSUPP;
ret = sparx5_vlan_vid_del(port, vid);
if (ret)
return ret;
return 0;
}
static int sparx5_handle_port_obj_del(struct net_device *dev,
struct notifier_block *nb,
struct switchdev_notifier_port_obj_info *info)
{
const struct switchdev_obj *obj = info->obj;
int err;
switch (obj->id) {
case SWITCHDEV_OBJ_ID_PORT_VLAN:
err = sparx5_handle_port_vlan_del(dev, nb,
SWITCHDEV_OBJ_PORT_VLAN(obj)->vid);
break;
case SWITCHDEV_OBJ_ID_PORT_MDB:
case SWITCHDEV_OBJ_ID_HOST_MDB:
err = sparx5_handle_port_mdb_del(dev, nb,
SWITCHDEV_OBJ_PORT_MDB(obj));
break;
default:
err = -EOPNOTSUPP;
break;
}
info->handled = true;
return err;
}
static int sparx5_switchdev_blocking_event(struct notifier_block *nb,
unsigned long event,
void *ptr)
{
struct net_device *dev = switchdev_notifier_info_to_dev(ptr);
int err;
switch (event) {
case SWITCHDEV_PORT_OBJ_ADD:
err = sparx5_handle_port_obj_add(dev, nb, ptr);
return notifier_from_errno(err);
case SWITCHDEV_PORT_OBJ_DEL:
err = sparx5_handle_port_obj_del(dev, nb, ptr);
return notifier_from_errno(err);
case SWITCHDEV_PORT_ATTR_SET:
err = switchdev_handle_port_attr_set(dev, ptr,
sparx5_netdevice_check,
sparx5_port_attr_set);
return notifier_from_errno(err);
}
return NOTIFY_DONE;
}
int sparx5_register_notifier_blocks(struct sparx5 *s5)
{
int err;
s5->netdevice_nb.notifier_call = sparx5_netdevice_event;
err = register_netdevice_notifier(&s5->netdevice_nb);
if (err)
return err;
s5->switchdev_nb.notifier_call = sparx5_switchdev_event;
err = register_switchdev_notifier(&s5->switchdev_nb);
if (err)
goto err_switchdev_nb;
s5->switchdev_blocking_nb.notifier_call = sparx5_switchdev_blocking_event;
err = register_switchdev_blocking_notifier(&s5->switchdev_blocking_nb);
if (err)
goto err_switchdev_blocking_nb;
sparx5_owq = alloc_ordered_workqueue("sparx5_order", 0);
if (!sparx5_owq) {
err = -ENOMEM;
goto err_alloc_workqueue;
}
return 0;
err_alloc_workqueue:
unregister_switchdev_blocking_notifier(&s5->switchdev_blocking_nb);
err_switchdev_blocking_nb:
unregister_switchdev_notifier(&s5->switchdev_nb);
err_switchdev_nb:
unregister_netdevice_notifier(&s5->netdevice_nb);
return err;
}
void sparx5_unregister_notifier_blocks(struct sparx5 *s5)
{
destroy_workqueue(sparx5_owq);
unregister_switchdev_blocking_notifier(&s5->switchdev_blocking_nb);
unregister_switchdev_notifier(&s5->switchdev_nb);
unregister_netdevice_notifier(&s5->netdevice_nb);
}
@@ -0,0 +1,174 @@
// SPDX-License-Identifier: GPL-2.0+
/* Microchip Sparx5 Switch driver
*
* Copyright (c) 2022 Microchip Technology Inc. and its subsidiaries.
*/
#include <net/pkt_cls.h>
#include <net/pkt_sched.h>
#include "sparx5_tc.h"
#include "sparx5_main.h"
#include "sparx5_qos.h"
/* tc block handling */
static LIST_HEAD(sparx5_block_cb_list);
static int sparx5_tc_block_cb(enum tc_setup_type type,
void *type_data,
void *cb_priv, bool ingress)
{
struct net_device *ndev = cb_priv;
switch (type) {
case TC_SETUP_CLSMATCHALL:
return sparx5_tc_matchall(ndev, type_data, ingress);
case TC_SETUP_CLSFLOWER:
return sparx5_tc_flower(ndev, type_data, ingress);
default:
return -EOPNOTSUPP;
}
}
static int sparx5_tc_block_cb_ingress(enum tc_setup_type type,
void *type_data,
void *cb_priv)
{
return sparx5_tc_block_cb(type, type_data, cb_priv, true);
}
static int sparx5_tc_block_cb_egress(enum tc_setup_type type,
void *type_data,
void *cb_priv)
{
return sparx5_tc_block_cb(type, type_data, cb_priv, false);
}
static int sparx5_tc_setup_block(struct net_device *ndev,
struct flow_block_offload *fbo)
{
flow_setup_cb_t *cb;
if (fbo->binder_type == FLOW_BLOCK_BINDER_TYPE_CLSACT_INGRESS)
cb = sparx5_tc_block_cb_ingress;
else if (fbo->binder_type == FLOW_BLOCK_BINDER_TYPE_CLSACT_EGRESS)
cb = sparx5_tc_block_cb_egress;
else
return -EOPNOTSUPP;
return flow_block_cb_setup_simple(fbo, &sparx5_block_cb_list,
cb, ndev, ndev, false);
}
static void sparx5_tc_get_layer_and_idx(struct sparx5 *sparx5, u32 parent,
u32 portno, u32 *layer, u32 *idx)
{
if (parent == TC_H_ROOT) {
*layer = 2;
*idx = portno;
} else {
u32 queue = TC_H_MIN(parent) - 1;
*layer = 0;
*idx = SPX5_HSCH_L0_GET_IDX(portno, queue);
}
}
static int sparx5_tc_setup_qdisc_mqprio(struct net_device *ndev,
struct tc_mqprio_qopt_offload *m)
{
m->qopt.hw = TC_MQPRIO_HW_OFFLOAD_TCS;
if (m->qopt.num_tc == 0)
return sparx5_tc_mqprio_del(ndev);
else
return sparx5_tc_mqprio_add(ndev, m->qopt.num_tc);
}
static int sparx5_tc_setup_qdisc_tbf(struct net_device *ndev,
struct tc_tbf_qopt_offload *qopt)
{
struct sparx5_port *port = netdev_priv(ndev);
u32 layer, se_idx;
sparx5_tc_get_layer_and_idx(port->sparx5, qopt->parent, port->portno,
&layer, &se_idx);
switch (qopt->command) {
case TC_TBF_REPLACE:
return sparx5_tc_tbf_add(port, &qopt->replace_params, layer,
se_idx);
case TC_TBF_DESTROY:
return sparx5_tc_tbf_del(port, layer, se_idx);
case TC_TBF_STATS:
return -EOPNOTSUPP;
default:
return -EOPNOTSUPP;
}
return -EOPNOTSUPP;
}
static int sparx5_tc_setup_qdisc_ets(struct net_device *ndev,
struct tc_ets_qopt_offload *qopt)
{
struct tc_ets_qopt_offload_replace_params *params =
&qopt->replace_params;
struct sparx5_port *port = netdev_priv(ndev);
int i;
/* Only allow ets on ports */
if (qopt->parent != TC_H_ROOT)
return -EOPNOTSUPP;
switch (qopt->command) {
case TC_ETS_REPLACE:
/* We support eight priorities */
if (params->bands != SPX5_PRIOS)
return -EOPNOTSUPP;
/* Sanity checks */
for (i = 0; i < SPX5_PRIOS; ++i) {
/* Priority map is *always* reverse e.g: 7 6 5 .. 0 */
if (params->priomap[i] != (7 - i))
return -EOPNOTSUPP;
/* Throw an error if we receive zero weights by tc */
if (params->quanta[i] && params->weights[i] == 0) {
pr_err("Invalid ets configuration; band %d has weight zero",
i);
return -EINVAL;
}
}
return sparx5_tc_ets_add(port, params);
case TC_ETS_DESTROY:
return sparx5_tc_ets_del(port);
case TC_ETS_GRAFT:
return -EOPNOTSUPP;
default:
return -EOPNOTSUPP;
}
return -EOPNOTSUPP;
}
int sparx5_port_setup_tc(struct net_device *ndev, enum tc_setup_type type,
void *type_data)
{
switch (type) {
case TC_SETUP_BLOCK:
return sparx5_tc_setup_block(ndev, type_data);
case TC_SETUP_QDISC_MQPRIO:
return sparx5_tc_setup_qdisc_mqprio(ndev, type_data);
case TC_SETUP_QDISC_TBF:
return sparx5_tc_setup_qdisc_tbf(ndev, type_data);
case TC_SETUP_QDISC_ETS:
return sparx5_tc_setup_qdisc_ets(ndev, type_data);
default:
return -EOPNOTSUPP;
}
return 0;
}
@@ -0,0 +1,108 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/* Microchip Sparx5 Switch driver
*
* Copyright (c) 2022 Microchip Technology Inc. and its subsidiaries.
*/
#ifndef __SPARX5_TC_H__
#define __SPARX5_TC_H__
#include <net/flow_offload.h>
#include <net/pkt_cls.h>
#include <linux/netdevice.h>
/* Controls how PORT_MASK is applied */
enum SPX5_PORT_MASK_MODE {
SPX5_PMM_OR_DSTMASK,
SPX5_PMM_AND_VLANMASK,
SPX5_PMM_REPLACE_PGID,
SPX5_PMM_REPLACE_ALL,
SPX5_PMM_REDIR_PGID,
SPX5_PMM_OR_PGID_MASK,
};
/* Controls ES0 forwarding */
enum SPX5_FORWARDING_SEL {
SPX5_FWSEL_NO_ACTION,
SPX5_FWSEL_COPY_TO_LOOPBACK,
SPX5_FWSEL_REDIRECT_TO_LOOPBACK,
SPX5_FWSEL_DISCARD,
};
/* Controls tag A (outer tagging) */
enum SPX5_OUTER_TAG_SEL {
SPX5_OTAG_PORT,
SPX5_OTAG_TAG_A,
SPX5_OTAG_FORCED_PORT,
SPX5_OTAG_UNTAG,
};
/* Selects TPID for ES0 tag A */
enum SPX5_TPID_A_SEL {
SPX5_TPID_A_8100,
SPX5_TPID_A_88A8,
SPX5_TPID_A_CUST1,
SPX5_TPID_A_CUST2,
SPX5_TPID_A_CUST3,
SPX5_TPID_A_CLASSIFIED,
};
/* Selects VID for ES0 tag A */
enum SPX5_VID_A_SEL {
SPX5_VID_A_CLASSIFIED,
SPX5_VID_A_VAL,
SPX5_VID_A_IFH,
SPX5_VID_A_RESERVED,
};
/* Select PCP source for ES0 tag A */
enum SPX5_PCP_A_SEL {
SPX5_PCP_A_CLASSIFIED,
SPX5_PCP_A_VAL,
SPX5_PCP_A_RESERVED,
SPX5_PCP_A_POPPED,
SPX5_PCP_A_MAPPED_0,
SPX5_PCP_A_MAPPED_1,
SPX5_PCP_A_MAPPED_2,
SPX5_PCP_A_MAPPED_3,
};
/* Select DEI source for ES0 tag A */
enum SPX5_DEI_A_SEL {
SPX5_DEI_A_CLASSIFIED,
SPX5_DEI_A_VAL,
SPX5_DEI_A_REW,
SPX5_DEI_A_POPPED,
SPX5_DEI_A_MAPPED_0,
SPX5_DEI_A_MAPPED_1,
SPX5_DEI_A_MAPPED_2,
SPX5_DEI_A_MAPPED_3,
};
/* Controls tag B (inner tagging) */
enum SPX5_INNER_TAG_SEL {
SPX5_ITAG_NO_PUSH,
SPX5_ITAG_PUSH_B_TAG,
};
/* Selects TPID for ES0 tag B. */
enum SPX5_TPID_B_SEL {
SPX5_TPID_B_8100,
SPX5_TPID_B_88A8,
SPX5_TPID_B_CUST1,
SPX5_TPID_B_CUST2,
SPX5_TPID_B_CUST3,
SPX5_TPID_B_CLASSIFIED,
};
int sparx5_port_setup_tc(struct net_device *ndev, enum tc_setup_type type,
void *type_data);
int sparx5_tc_matchall(struct net_device *ndev,
struct tc_cls_matchall_offload *tmo,
bool ingress);
int sparx5_tc_flower(struct net_device *ndev, struct flow_cls_offload *fco,
bool ingress);
#endif /* __SPARX5_TC_H__ */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,204 @@
// SPDX-License-Identifier: GPL-2.0+
/* Microchip VCAP API
*
* Copyright (c) 2022 Microchip Technology Inc. and its subsidiaries.
*/
#include "sparx5_tc.h"
#include "vcap_api.h"
#include "vcap_api_client.h"
#include "sparx5_main_regs.h"
#include "sparx5_main.h"
#include "sparx5_vcap_impl.h"
static struct sparx5_mall_entry *
sparx5_tc_matchall_entry_find(struct list_head *entries, unsigned long cookie)
{
struct sparx5_mall_entry *entry;
list_for_each_entry(entry, entries, list) {
if (entry->cookie == cookie)
return entry;
}
return NULL;
}
static void sparx5_tc_matchall_parse_action(struct sparx5_port *port,
struct sparx5_mall_entry *entry,
struct flow_action_entry *action,
bool ingress,
unsigned long cookie)
{
entry->port = port;
entry->type = action->id;
entry->ingress = ingress;
entry->cookie = cookie;
}
static void
sparx5_tc_matchall_parse_mirror_action(struct sparx5_mall_entry *entry,
struct flow_action_entry *action)
{
entry->mirror.port = netdev_priv(action->dev);
}
static int sparx5_tc_matchall_replace(struct net_device *ndev,
struct tc_cls_matchall_offload *tmo,
bool ingress)
{
struct sparx5_port *port = netdev_priv(ndev);
struct sparx5_mall_entry *mall_entry;
struct flow_action_entry *action;
struct sparx5 *sparx5;
int err;
if (!flow_offload_has_one_action(&tmo->rule->action)) {
NL_SET_ERR_MSG_MOD(tmo->common.extack,
"Only one action per filter is supported");
return -EOPNOTSUPP;
}
action = &tmo->rule->action.entries[0];
mall_entry = kzalloc_obj(*mall_entry);
if (!mall_entry)
return -ENOMEM;
sparx5_tc_matchall_parse_action(port,
mall_entry,
action,
ingress,
tmo->cookie);
sparx5 = port->sparx5;
switch (action->id) {
case FLOW_ACTION_MIRRED:
sparx5_tc_matchall_parse_mirror_action(mall_entry, action);
err = sparx5_mirror_add(mall_entry);
if (err) {
switch (err) {
case -EEXIST:
NL_SET_ERR_MSG_MOD(tmo->common.extack,
"Mirroring already exists");
break;
case -EINVAL:
NL_SET_ERR_MSG_MOD(tmo->common.extack,
"Cannot mirror a monitor port");
break;
case -ENOENT:
NL_SET_ERR_MSG_MOD(tmo->common.extack,
"No more mirror probes available");
break;
default:
NL_SET_ERR_MSG_MOD(tmo->common.extack,
"Unknown error");
break;
}
return err;
}
/* Get baseline stats for this port */
sparx5_mirror_stats(mall_entry, &tmo->stats);
break;
case FLOW_ACTION_GOTO:
err = vcap_enable_lookups(sparx5->vcap_ctrl, ndev,
tmo->common.chain_index,
action->chain_index, tmo->cookie,
true);
if (err == -EFAULT) {
NL_SET_ERR_MSG_MOD(tmo->common.extack,
"Unsupported goto chain");
return -EOPNOTSUPP;
}
if (err == -EADDRINUSE) {
NL_SET_ERR_MSG_MOD(tmo->common.extack,
"VCAP already enabled");
return -EOPNOTSUPP;
}
if (err == -EADDRNOTAVAIL) {
NL_SET_ERR_MSG_MOD(tmo->common.extack,
"Already matching this chain");
return -EOPNOTSUPP;
}
if (err) {
NL_SET_ERR_MSG_MOD(tmo->common.extack,
"Could not enable VCAP lookups");
return err;
}
break;
default:
NL_SET_ERR_MSG_MOD(tmo->common.extack, "Unsupported action");
return -EOPNOTSUPP;
}
list_add_tail(&mall_entry->list, &sparx5->mall_entries);
return 0;
}
static int sparx5_tc_matchall_destroy(struct net_device *ndev,
struct tc_cls_matchall_offload *tmo,
bool ingress)
{
struct sparx5_port *port = netdev_priv(ndev);
struct sparx5 *sparx5 = port->sparx5;
struct sparx5_mall_entry *entry;
int err = 0;
entry = sparx5_tc_matchall_entry_find(&sparx5->mall_entries,
tmo->cookie);
if (!entry)
return -ENOENT;
if (entry->type == FLOW_ACTION_MIRRED) {
sparx5_mirror_del(entry);
} else if (entry->type == FLOW_ACTION_GOTO) {
err = vcap_enable_lookups(sparx5->vcap_ctrl, ndev,
0, 0, tmo->cookie, false);
} else {
NL_SET_ERR_MSG_MOD(tmo->common.extack, "Unsupported action");
err = -EOPNOTSUPP;
}
list_del(&entry->list);
return err;
}
static int sparx5_tc_matchall_stats(struct net_device *ndev,
struct tc_cls_matchall_offload *tmo,
bool ingress)
{
struct sparx5_port *port = netdev_priv(ndev);
struct sparx5 *sparx5 = port->sparx5;
struct sparx5_mall_entry *entry;
entry = sparx5_tc_matchall_entry_find(&sparx5->mall_entries,
tmo->cookie);
if (!entry)
return -ENOENT;
if (entry->type == FLOW_ACTION_MIRRED) {
sparx5_mirror_stats(entry, &tmo->stats);
} else {
NL_SET_ERR_MSG_MOD(tmo->common.extack, "Unsupported action");
return -EOPNOTSUPP;
}
return 0;
}
int sparx5_tc_matchall(struct net_device *ndev,
struct tc_cls_matchall_offload *tmo,
bool ingress)
{
switch (tmo->command) {
case TC_CLSMATCHALL_REPLACE:
return sparx5_tc_matchall_replace(ndev, tmo, ingress);
case TC_CLSMATCHALL_DESTROY:
return sparx5_tc_matchall_destroy(ndev, tmo, ingress);
case TC_CLSMATCHALL_STATS:
return sparx5_tc_matchall_stats(ndev, tmo, ingress);
default:
return -EOPNOTSUPP;
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,20 @@
/* SPDX-License-Identifier: BSD-3-Clause */
/* Copyright (C) 2022 Microchip Technology Inc. and its subsidiaries.
* Microchip VCAP API
*/
/* This file is autogenerated by cml-utils 2022-10-13 10:04:41 +0200.
* Commit ID: fd7cafd175899f0672c73afb3a30fc872500ae86
*/
#ifndef __SPARX5_VCAP_AG_API_H__
#define __SPARX5_VCAP_AG_API_H__
#include "vcap_api.h"
/* VCAPs */
extern const struct vcap_info sparx5_vcaps[];
extern const struct vcap_statistics sparx5_vcap_stats;
#endif /* __SPARX5_VCAP_AG_API_H__ */
@@ -0,0 +1,471 @@
// SPDX-License-Identifier: GPL-2.0+
/* Microchip Sparx5 Switch driver VCAP debugFS implementation
*
* Copyright (c) 2022 Microchip Technology Inc. and its subsidiaries.
*/
#include <linux/types.h>
#include <linux/list.h>
#include "sparx5_vcap_debugfs.h"
#include "sparx5_main_regs.h"
#include "sparx5_main.h"
#include "sparx5_vcap_impl.h"
#include "sparx5_vcap_ag_api.h"
static const char *sparx5_vcap_is0_etype_str(u32 value)
{
switch (value) {
case VCAP_IS0_PS_ETYPE_DEFAULT:
return "default";
case VCAP_IS0_PS_ETYPE_NORMAL_7TUPLE:
return "normal_7tuple";
case VCAP_IS0_PS_ETYPE_NORMAL_5TUPLE_IP4:
return "normal_5tuple_ip4";
case VCAP_IS0_PS_ETYPE_MLL:
return "mll";
case VCAP_IS0_PS_ETYPE_LL_FULL:
return "ll_full";
case VCAP_IS0_PS_ETYPE_PURE_5TUPLE_IP4:
return "pure_5tuple_ip4";
case VCAP_IS0_PS_ETYPE_ETAG:
return "etag";
case VCAP_IS0_PS_ETYPE_NO_LOOKUP:
return "no lookup";
default:
return "unknown";
}
}
static const char *sparx5_vcap_is0_mpls_str(u32 value)
{
switch (value) {
case VCAP_IS0_PS_MPLS_FOLLOW_ETYPE:
return "follow_etype";
case VCAP_IS0_PS_MPLS_NORMAL_7TUPLE:
return "normal_7tuple";
case VCAP_IS0_PS_MPLS_NORMAL_5TUPLE_IP4:
return "normal_5tuple_ip4";
case VCAP_IS0_PS_MPLS_MLL:
return "mll";
case VCAP_IS0_PS_MPLS_LL_FULL:
return "ll_full";
case VCAP_IS0_PS_MPLS_PURE_5TUPLE_IP4:
return "pure_5tuple_ip4";
case VCAP_IS0_PS_MPLS_ETAG:
return "etag";
case VCAP_IS0_PS_MPLS_NO_LOOKUP:
return "no lookup";
default:
return "unknown";
}
}
static const char *sparx5_vcap_is0_mlbs_str(u32 value)
{
switch (value) {
case VCAP_IS0_PS_MLBS_FOLLOW_ETYPE:
return "follow_etype";
case VCAP_IS0_PS_MLBS_NO_LOOKUP:
return "no lookup";
default:
return "unknown";
}
}
static void sparx5_vcap_is0_port_keys(struct sparx5 *sparx5,
struct vcap_admin *admin,
struct sparx5_port *port,
struct vcap_output_print *out)
{
int lookup;
u32 value, val;
out->prf(out->dst, " port[%02d] (%s): ", port->portno,
netdev_name(port->ndev));
for (lookup = 0; lookup < admin->lookups; ++lookup) {
out->prf(out->dst, "\n Lookup %d: ", lookup);
/* Get lookup state */
value = spx5_rd(sparx5,
ANA_CL_ADV_CL_CFG(port->portno, lookup));
out->prf(out->dst, "\n state: ");
if (ANA_CL_ADV_CL_CFG_LOOKUP_ENA_GET(value))
out->prf(out->dst, "on");
else
out->prf(out->dst, "off");
val = ANA_CL_ADV_CL_CFG_ETYPE_CLM_KEY_SEL_GET(value);
out->prf(out->dst, "\n etype: %s",
sparx5_vcap_is0_etype_str(val));
val = ANA_CL_ADV_CL_CFG_IP4_CLM_KEY_SEL_GET(value);
out->prf(out->dst, "\n ipv4: %s",
sparx5_vcap_is0_etype_str(val));
val = ANA_CL_ADV_CL_CFG_IP6_CLM_KEY_SEL_GET(value);
out->prf(out->dst, "\n ipv6: %s",
sparx5_vcap_is0_etype_str(val));
val = ANA_CL_ADV_CL_CFG_MPLS_UC_CLM_KEY_SEL_GET(value);
out->prf(out->dst, "\n mpls_uc: %s",
sparx5_vcap_is0_mpls_str(val));
val = ANA_CL_ADV_CL_CFG_MPLS_MC_CLM_KEY_SEL_GET(value);
out->prf(out->dst, "\n mpls_mc: %s",
sparx5_vcap_is0_mpls_str(val));
val = ANA_CL_ADV_CL_CFG_MLBS_CLM_KEY_SEL_GET(value);
out->prf(out->dst, "\n mlbs: %s",
sparx5_vcap_is0_mlbs_str(val));
}
out->prf(out->dst, "\n");
}
static void sparx5_vcap_is2_port_keys(struct sparx5 *sparx5,
struct vcap_admin *admin,
struct sparx5_port *port,
struct vcap_output_print *out)
{
int lookup;
u32 value;
out->prf(out->dst, " port[%02d] (%s): ", port->portno,
netdev_name(port->ndev));
for (lookup = 0; lookup < admin->lookups; ++lookup) {
out->prf(out->dst, "\n Lookup %d: ", lookup);
/* Get lookup state */
value = spx5_rd(sparx5, ANA_ACL_VCAP_S2_CFG(port->portno));
out->prf(out->dst, "\n state: ");
if (ANA_ACL_VCAP_S2_CFG_SEC_ENA_GET(value) & BIT(lookup))
out->prf(out->dst, "on");
else
out->prf(out->dst, "off");
/* Get key selection state */
value = spx5_rd(sparx5,
ANA_ACL_VCAP_S2_KEY_SEL(port->portno, lookup));
out->prf(out->dst, "\n noneth: ");
switch (ANA_ACL_VCAP_S2_KEY_SEL_NON_ETH_KEY_SEL_GET(value)) {
case VCAP_IS2_PS_NONETH_MAC_ETYPE:
out->prf(out->dst, "mac_etype");
break;
case VCAP_IS2_PS_NONETH_CUSTOM_1:
out->prf(out->dst, "custom1");
break;
case VCAP_IS2_PS_NONETH_CUSTOM_2:
out->prf(out->dst, "custom2");
break;
case VCAP_IS2_PS_NONETH_NO_LOOKUP:
out->prf(out->dst, "none");
break;
}
out->prf(out->dst, "\n ipv4_mc: ");
switch (ANA_ACL_VCAP_S2_KEY_SEL_IP4_MC_KEY_SEL_GET(value)) {
case VCAP_IS2_PS_IPV4_MC_MAC_ETYPE:
out->prf(out->dst, "mac_etype");
break;
case VCAP_IS2_PS_IPV4_MC_IP4_TCP_UDP_OTHER:
out->prf(out->dst, "ip4_tcp_udp ip4_other");
break;
case VCAP_IS2_PS_IPV4_MC_IP_7TUPLE:
out->prf(out->dst, "ip_7tuple");
break;
case VCAP_IS2_PS_IPV4_MC_IP4_VID:
out->prf(out->dst, "ip4_vid");
break;
}
out->prf(out->dst, "\n ipv4_uc: ");
switch (ANA_ACL_VCAP_S2_KEY_SEL_IP4_UC_KEY_SEL_GET(value)) {
case VCAP_IS2_PS_IPV4_UC_MAC_ETYPE:
out->prf(out->dst, "mac_etype");
break;
case VCAP_IS2_PS_IPV4_UC_IP4_TCP_UDP_OTHER:
out->prf(out->dst, "ip4_tcp_udp ip4_other");
break;
case VCAP_IS2_PS_IPV4_UC_IP_7TUPLE:
out->prf(out->dst, "ip_7tuple");
break;
}
out->prf(out->dst, "\n ipv6_mc: ");
switch (ANA_ACL_VCAP_S2_KEY_SEL_IP6_MC_KEY_SEL_GET(value)) {
case VCAP_IS2_PS_IPV6_MC_MAC_ETYPE:
out->prf(out->dst, "mac_etype");
break;
case VCAP_IS2_PS_IPV6_MC_IP_7TUPLE:
out->prf(out->dst, "ip_7tuple");
break;
case VCAP_IS2_PS_IPV6_MC_IP6_VID:
out->prf(out->dst, "ip6_vid");
break;
case VCAP_IS2_PS_IPV6_MC_IP6_STD:
out->prf(out->dst, "ip6_std");
break;
case VCAP_IS2_PS_IPV6_MC_IP4_TCP_UDP_OTHER:
out->prf(out->dst, "ip4_tcp_udp ip4_other");
break;
}
out->prf(out->dst, "\n ipv6_uc: ");
switch (ANA_ACL_VCAP_S2_KEY_SEL_IP6_UC_KEY_SEL_GET(value)) {
case VCAP_IS2_PS_IPV6_UC_MAC_ETYPE:
out->prf(out->dst, "mac_etype");
break;
case VCAP_IS2_PS_IPV6_UC_IP_7TUPLE:
out->prf(out->dst, "ip_7tuple");
break;
case VCAP_IS2_PS_IPV6_UC_IP6_STD:
out->prf(out->dst, "ip6_std");
break;
case VCAP_IS2_PS_IPV6_UC_IP4_TCP_UDP_OTHER:
out->prf(out->dst, "ip4_tcp_udp ip4_other");
break;
}
out->prf(out->dst, "\n arp: ");
switch (ANA_ACL_VCAP_S2_KEY_SEL_ARP_KEY_SEL_GET(value)) {
case VCAP_IS2_PS_ARP_MAC_ETYPE:
out->prf(out->dst, "mac_etype");
break;
case VCAP_IS2_PS_ARP_ARP:
out->prf(out->dst, "arp");
break;
}
}
out->prf(out->dst, "\n");
}
static void sparx5_vcap_is2_port_stickies(struct sparx5 *sparx5,
struct vcap_admin *admin,
struct vcap_output_print *out)
{
int lookup;
u32 value;
out->prf(out->dst, " Sticky bits: ");
for (lookup = 0; lookup < admin->lookups; ++lookup) {
out->prf(out->dst, "\n Lookup %d: ", lookup);
/* Get lookup sticky bits */
value = spx5_rd(sparx5, ANA_ACL_SEC_LOOKUP_STICKY(lookup));
if (ANA_ACL_SEC_LOOKUP_STICKY_KEY_SEL_CLM_STICKY_GET(value))
out->prf(out->dst, " sel_clm");
if (ANA_ACL_SEC_LOOKUP_STICKY_KEY_SEL_IRLEG_STICKY_GET(value))
out->prf(out->dst, " sel_irleg");
if (ANA_ACL_SEC_LOOKUP_STICKY_KEY_SEL_ERLEG_STICKY_GET(value))
out->prf(out->dst, " sel_erleg");
if (ANA_ACL_SEC_LOOKUP_STICKY_KEY_SEL_PORT_STICKY_GET(value))
out->prf(out->dst, " sel_port");
if (ANA_ACL_SEC_LOOKUP_STICKY_SEC_TYPE_CUSTOM2_STICKY_GET(value))
out->prf(out->dst, " custom2");
if (ANA_ACL_SEC_LOOKUP_STICKY_SEC_TYPE_CUSTOM1_STICKY_GET(value))
out->prf(out->dst, " custom1");
if (ANA_ACL_SEC_LOOKUP_STICKY_SEC_TYPE_OAM_STICKY_GET(value))
out->prf(out->dst, " oam");
if (ANA_ACL_SEC_LOOKUP_STICKY_SEC_TYPE_IP6_VID_STICKY_GET(value))
out->prf(out->dst, " ip6_vid");
if (ANA_ACL_SEC_LOOKUP_STICKY_SEC_TYPE_IP6_STD_STICKY_GET(value))
out->prf(out->dst, " ip6_std");
if (ANA_ACL_SEC_LOOKUP_STICKY_SEC_TYPE_IP6_TCPUDP_STICKY_GET(value))
out->prf(out->dst, " ip6_tcpudp");
if (ANA_ACL_SEC_LOOKUP_STICKY_SEC_TYPE_IP_7TUPLE_STICKY_GET(value))
out->prf(out->dst, " ip_7tuple");
if (ANA_ACL_SEC_LOOKUP_STICKY_SEC_TYPE_IP4_VID_STICKY_GET(value))
out->prf(out->dst, " ip4_vid");
if (ANA_ACL_SEC_LOOKUP_STICKY_SEC_TYPE_IP4_TCPUDP_STICKY_GET(value))
out->prf(out->dst, " ip4_tcpudp");
if (ANA_ACL_SEC_LOOKUP_STICKY_SEC_TYPE_IP4_OTHER_STICKY_GET(value))
out->prf(out->dst, " ip4_other");
if (ANA_ACL_SEC_LOOKUP_STICKY_SEC_TYPE_ARP_STICKY_GET(value))
out->prf(out->dst, " arp");
if (ANA_ACL_SEC_LOOKUP_STICKY_SEC_TYPE_MAC_SNAP_STICKY_GET(value))
out->prf(out->dst, " mac_snap");
if (ANA_ACL_SEC_LOOKUP_STICKY_SEC_TYPE_MAC_LLC_STICKY_GET(value))
out->prf(out->dst, " mac_llc");
if (ANA_ACL_SEC_LOOKUP_STICKY_SEC_TYPE_MAC_ETYPE_STICKY_GET(value))
out->prf(out->dst, " mac_etype");
/* Clear stickies */
spx5_wr(value, sparx5, ANA_ACL_SEC_LOOKUP_STICKY(lookup));
}
out->prf(out->dst, "\n");
}
static void sparx5_vcap_es0_port_keys(struct sparx5 *sparx5,
struct vcap_admin *admin,
struct sparx5_port *port,
struct vcap_output_print *out)
{
u32 value;
out->prf(out->dst, " port[%02d] (%s): ", port->portno,
netdev_name(port->ndev));
out->prf(out->dst, "\n Lookup 0: ");
/* Get lookup state */
value = spx5_rd(sparx5, REW_ES0_CTRL);
out->prf(out->dst, "\n state: ");
if (REW_ES0_CTRL_ES0_LU_ENA_GET(value))
out->prf(out->dst, "on");
else
out->prf(out->dst, "off");
out->prf(out->dst, "\n keyset: ");
value = spx5_rd(sparx5, REW_RTAG_ETAG_CTRL(port->portno));
switch (REW_RTAG_ETAG_CTRL_ES0_ISDX_KEY_ENA_GET(value)) {
case VCAP_ES0_PS_NORMAL_SELECTION:
out->prf(out->dst, "normal");
break;
case VCAP_ES0_PS_FORCE_ISDX_LOOKUPS:
out->prf(out->dst, "isdx");
break;
case VCAP_ES0_PS_FORCE_VID_LOOKUPS:
out->prf(out->dst, "vid");
break;
case VCAP_ES0_PS_RESERVED:
out->prf(out->dst, "reserved");
break;
}
out->prf(out->dst, "\n");
}
static void sparx5_vcap_es2_port_keys(struct sparx5 *sparx5,
struct vcap_admin *admin,
struct sparx5_port *port,
struct vcap_output_print *out)
{
int lookup;
u32 value;
out->prf(out->dst, " port[%02d] (%s): ", port->portno,
netdev_name(port->ndev));
for (lookup = 0; lookup < admin->lookups; ++lookup) {
out->prf(out->dst, "\n Lookup %d: ", lookup);
/* Get lookup state */
value = spx5_rd(sparx5, EACL_VCAP_ES2_KEY_SEL(port->portno,
lookup));
out->prf(out->dst, "\n state: ");
if (EACL_VCAP_ES2_KEY_SEL_KEY_ENA_GET(value))
out->prf(out->dst, "on");
else
out->prf(out->dst, "off");
out->prf(out->dst, "\n arp: ");
switch (EACL_VCAP_ES2_KEY_SEL_ARP_KEY_SEL_GET(value)) {
case VCAP_ES2_PS_ARP_MAC_ETYPE:
out->prf(out->dst, "mac_etype");
break;
case VCAP_ES2_PS_ARP_ARP:
out->prf(out->dst, "arp");
break;
}
out->prf(out->dst, "\n ipv4: ");
switch (EACL_VCAP_ES2_KEY_SEL_IP4_KEY_SEL_GET(value)) {
case VCAP_ES2_PS_IPV4_MAC_ETYPE:
out->prf(out->dst, "mac_etype");
break;
case VCAP_ES2_PS_IPV4_IP_7TUPLE:
out->prf(out->dst, "ip_7tuple");
break;
case VCAP_ES2_PS_IPV4_IP4_TCP_UDP_VID:
out->prf(out->dst, "ip4_tcp_udp ip4_vid");
break;
case VCAP_ES2_PS_IPV4_IP4_TCP_UDP_OTHER:
out->prf(out->dst, "ip4_tcp_udp ip4_other");
break;
case VCAP_ES2_PS_IPV4_IP4_VID:
out->prf(out->dst, "ip4_vid");
break;
case VCAP_ES2_PS_IPV4_IP4_OTHER:
out->prf(out->dst, "ip4_other");
break;
}
out->prf(out->dst, "\n ipv6: ");
switch (EACL_VCAP_ES2_KEY_SEL_IP6_KEY_SEL_GET(value)) {
case VCAP_ES2_PS_IPV6_MAC_ETYPE:
out->prf(out->dst, "mac_etype");
break;
case VCAP_ES2_PS_IPV6_IP_7TUPLE:
out->prf(out->dst, "ip_7tuple");
break;
case VCAP_ES2_PS_IPV6_IP_7TUPLE_VID:
out->prf(out->dst, "ip_7tuple ip6_vid");
break;
case VCAP_ES2_PS_IPV6_IP_7TUPLE_STD:
out->prf(out->dst, "ip_7tuple ip6_std");
break;
case VCAP_ES2_PS_IPV6_IP6_VID:
out->prf(out->dst, "ip6_vid");
break;
case VCAP_ES2_PS_IPV6_IP6_STD:
out->prf(out->dst, "ip6_std");
break;
case VCAP_ES2_PS_IPV6_IP4_DOWNGRADE:
out->prf(out->dst, "ip4_downgrade");
break;
}
}
out->prf(out->dst, "\n");
}
static void sparx5_vcap_es2_port_stickies(struct sparx5 *sparx5,
struct vcap_admin *admin,
struct vcap_output_print *out)
{
int lookup;
u32 value;
out->prf(out->dst, " Sticky bits: ");
for (lookup = 0; lookup < admin->lookups; ++lookup) {
value = spx5_rd(sparx5, EACL_SEC_LOOKUP_STICKY(lookup));
out->prf(out->dst, "\n Lookup %d: ", lookup);
if (EACL_SEC_LOOKUP_STICKY_SEC_TYPE_IP_7TUPLE_STICKY_GET(value))
out->prf(out->dst, " ip_7tuple");
if (EACL_SEC_LOOKUP_STICKY_SEC_TYPE_IP6_VID_STICKY_GET(value))
out->prf(out->dst, " ip6_vid");
if (EACL_SEC_LOOKUP_STICKY_SEC_TYPE_IP6_STD_STICKY_GET(value))
out->prf(out->dst, " ip6_std");
if (EACL_SEC_LOOKUP_STICKY_SEC_TYPE_IP4_TCPUDP_STICKY_GET(value))
out->prf(out->dst, " ip4_tcp_udp");
if (EACL_SEC_LOOKUP_STICKY_SEC_TYPE_IP4_VID_STICKY_GET(value))
out->prf(out->dst, " ip4_vid");
if (EACL_SEC_LOOKUP_STICKY_SEC_TYPE_IP4_OTHER_STICKY_GET(value))
out->prf(out->dst, " ip4_other");
if (EACL_SEC_LOOKUP_STICKY_SEC_TYPE_ARP_STICKY_GET(value))
out->prf(out->dst, " arp");
if (EACL_SEC_LOOKUP_STICKY_SEC_TYPE_MAC_ETYPE_STICKY_GET(value))
out->prf(out->dst, " mac_etype");
/* Clear stickies */
spx5_wr(value, sparx5, EACL_SEC_LOOKUP_STICKY(lookup));
}
out->prf(out->dst, "\n");
}
/* Provide port information via a callback interface */
int sparx5_port_info(struct net_device *ndev,
struct vcap_admin *admin,
struct vcap_output_print *out)
{
struct sparx5_port *port = netdev_priv(ndev);
struct sparx5 *sparx5 = port->sparx5;
const struct vcap_info *vcap;
struct vcap_control *vctrl;
vctrl = sparx5->vcap_ctrl;
vcap = &vctrl->vcaps[admin->vtype];
out->prf(out->dst, "%s:\n", vcap->name);
switch (admin->vtype) {
case VCAP_TYPE_IS0:
sparx5_vcap_is0_port_keys(sparx5, admin, port, out);
break;
case VCAP_TYPE_IS2:
sparx5_vcap_is2_port_keys(sparx5, admin, port, out);
sparx5_vcap_is2_port_stickies(sparx5, admin, out);
break;
case VCAP_TYPE_ES0:
sparx5_vcap_es0_port_keys(sparx5, admin, port, out);
break;
case VCAP_TYPE_ES2:
sparx5_vcap_es2_port_keys(sparx5, admin, port, out);
sparx5_vcap_es2_port_stickies(sparx5, admin, out);
break;
default:
out->prf(out->dst, " no info\n");
break;
}
return 0;
}
@@ -0,0 +1,33 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/* Microchip Sparx5 Switch driver VCAP implementation
*
* Copyright (c) 2022 Microchip Technology Inc. and its subsidiaries.
*/
#ifndef __SPARX5_VCAP_DEBUGFS_H__
#define __SPARX5_VCAP_DEBUGFS_H__
#include <linux/netdevice.h>
#include <vcap_api.h>
#include <vcap_api_client.h>
#if defined(CONFIG_DEBUG_FS)
/* Provide port information via a callback interface */
int sparx5_port_info(struct net_device *ndev,
struct vcap_admin *admin,
struct vcap_output_print *out);
#else
static inline int sparx5_port_info(struct net_device *ndev,
struct vcap_admin *admin,
struct vcap_output_print *out)
{
return 0;
}
#endif
#endif /* __SPARX5_VCAP_DEBUGFS_H__ */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,228 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/* Microchip Sparx5 Switch driver VCAP implementation
*
* Copyright (c) 2022 Microchip Technology Inc. and its subsidiaries.
*
* The Sparx5 Chip Register Model can be browsed at this location:
* https://github.com/microchip-ung/sparx-5_reginfo
*/
#ifndef __SPARX5_VCAP_IMPL_H__
#define __SPARX5_VCAP_IMPL_H__
#include <linux/types.h>
#include <linux/list.h>
#include "vcap_api.h"
#include "vcap_api_client.h"
#define SPARX5_IS2_LOOKUPS 4
#define SPARX5_IS0_LOOKUPS 6
#define SPARX5_ES0_LOOKUPS 1
#define SPARX5_ES2_LOOKUPS 2
#define SPARX5_VCAP_CID_IS0_L0 VCAP_CID_INGRESS_L0 /* IS0/CLM lookup 0 */
#define SPARX5_VCAP_CID_IS0_L1 VCAP_CID_INGRESS_L1 /* IS0/CLM lookup 1 */
#define SPARX5_VCAP_CID_IS0_L2 VCAP_CID_INGRESS_L2 /* IS0/CLM lookup 2 */
#define SPARX5_VCAP_CID_IS0_L3 VCAP_CID_INGRESS_L3 /* IS0/CLM lookup 3 */
#define SPARX5_VCAP_CID_IS0_L4 VCAP_CID_INGRESS_L4 /* IS0/CLM lookup 4 */
#define SPARX5_VCAP_CID_IS0_L5 VCAP_CID_INGRESS_L5 /* IS0/CLM lookup 5 */
#define SPARX5_VCAP_CID_IS0_MAX \
(VCAP_CID_INGRESS_L5 + VCAP_CID_LOOKUP_SIZE - 1) /* IS0/CLM Max */
#define SPARX5_VCAP_CID_IS2_L0 VCAP_CID_INGRESS_STAGE2_L0 /* IS2 lookup 0 */
#define SPARX5_VCAP_CID_IS2_L1 VCAP_CID_INGRESS_STAGE2_L1 /* IS2 lookup 1 */
#define SPARX5_VCAP_CID_IS2_L2 VCAP_CID_INGRESS_STAGE2_L2 /* IS2 lookup 2 */
#define SPARX5_VCAP_CID_IS2_L3 VCAP_CID_INGRESS_STAGE2_L3 /* IS2 lookup 3 */
#define SPARX5_VCAP_CID_IS2_MAX \
(VCAP_CID_INGRESS_STAGE2_L3 + VCAP_CID_LOOKUP_SIZE - 1) /* IS2 Max */
#define SPARX5_VCAP_CID_ES0_L0 VCAP_CID_EGRESS_L0 /* ES0 lookup 0 */
#define SPARX5_VCAP_CID_ES0_MAX (VCAP_CID_EGRESS_L1 - 1) /* ES0 Max */
#define SPARX5_VCAP_CID_ES2_L0 VCAP_CID_EGRESS_STAGE2_L0 /* ES2 lookup 0 */
#define SPARX5_VCAP_CID_ES2_L1 VCAP_CID_EGRESS_STAGE2_L1 /* ES2 lookup 1 */
#define SPARX5_VCAP_CID_ES2_MAX \
(VCAP_CID_EGRESS_STAGE2_L1 + VCAP_CID_LOOKUP_SIZE - 1) /* ES2 Max */
struct sparx5_vcap_inst {
enum vcap_type vtype; /* type of vcap */
int vinst; /* instance number within the same type */
int lookups; /* number of lookups in this vcap type */
int lookups_per_instance; /* number of lookups in this instance */
int first_cid; /* first chain id in this vcap */
int last_cid; /* last chain id in this vcap */
int count; /* number of available addresses, not in super vcap */
int map_id; /* id in the super vcap block mapping (if applicable) */
int blockno; /* starting block in super vcap (if applicable) */
int blocks; /* number of blocks in super vcap (if applicable) */
bool ingress; /* is vcap in the ingress path */
};
extern const struct sparx5_vcap_inst sparx5_vcap_inst_cfg[];
/* IS0 port keyset selection control */
/* IS0 ethernet, IPv4, IPv6 traffic type keyset generation */
enum vcap_is0_port_sel_etype {
VCAP_IS0_PS_ETYPE_DEFAULT, /* None or follow depending on class */
VCAP_IS0_PS_ETYPE_MLL,
VCAP_IS0_PS_ETYPE_SGL_MLBS,
VCAP_IS0_PS_ETYPE_DBL_MLBS,
VCAP_IS0_PS_ETYPE_TRI_MLBS,
VCAP_IS0_PS_ETYPE_TRI_VID,
VCAP_IS0_PS_ETYPE_LL_FULL,
VCAP_IS0_PS_ETYPE_NORMAL_SRC,
VCAP_IS0_PS_ETYPE_NORMAL_DST,
VCAP_IS0_PS_ETYPE_NORMAL_7TUPLE,
VCAP_IS0_PS_ETYPE_NORMAL_5TUPLE_IP4,
VCAP_IS0_PS_ETYPE_PURE_5TUPLE_IP4,
VCAP_IS0_PS_ETYPE_DBL_VID_IDX,
VCAP_IS0_PS_ETYPE_ETAG,
VCAP_IS0_PS_ETYPE_NO_LOOKUP,
};
/* IS0 MPLS traffic type keyset generation */
enum vcap_is0_port_sel_mpls_uc_mc {
VCAP_IS0_PS_MPLS_FOLLOW_ETYPE,
VCAP_IS0_PS_MPLS_MLL,
VCAP_IS0_PS_MPLS_SGL_MLBS,
VCAP_IS0_PS_MPLS_DBL_MLBS,
VCAP_IS0_PS_MPLS_TRI_MLBS,
VCAP_IS0_PS_MPLS_TRI_VID,
VCAP_IS0_PS_MPLS_LL_FULL,
VCAP_IS0_PS_MPLS_NORMAL_SRC,
VCAP_IS0_PS_MPLS_NORMAL_DST,
VCAP_IS0_PS_MPLS_NORMAL_7TUPLE,
VCAP_IS0_PS_MPLS_NORMAL_5TUPLE_IP4,
VCAP_IS0_PS_MPLS_PURE_5TUPLE_IP4,
VCAP_IS0_PS_MPLS_DBL_VID_IDX,
VCAP_IS0_PS_MPLS_ETAG,
VCAP_IS0_PS_MPLS_NO_LOOKUP,
};
/* IS0 MBLS traffic type keyset generation */
enum vcap_is0_port_sel_mlbs {
VCAP_IS0_PS_MLBS_FOLLOW_ETYPE,
VCAP_IS0_PS_MLBS_SGL_MLBS,
VCAP_IS0_PS_MLBS_DBL_MLBS,
VCAP_IS0_PS_MLBS_TRI_MLBS,
VCAP_IS0_PS_MLBS_NO_LOOKUP = 17,
};
/* IS2 port keyset selection control */
/* IS2 non-ethernet traffic type keyset generation */
enum vcap_is2_port_sel_noneth {
VCAP_IS2_PS_NONETH_MAC_ETYPE,
VCAP_IS2_PS_NONETH_CUSTOM_1,
VCAP_IS2_PS_NONETH_CUSTOM_2,
VCAP_IS2_PS_NONETH_NO_LOOKUP
};
/* IS2 IPv4 unicast traffic type keyset generation */
enum vcap_is2_port_sel_ipv4_uc {
VCAP_IS2_PS_IPV4_UC_MAC_ETYPE,
VCAP_IS2_PS_IPV4_UC_IP4_TCP_UDP_OTHER,
VCAP_IS2_PS_IPV4_UC_IP_7TUPLE,
};
/* IS2 IPv4 multicast traffic type keyset generation */
enum vcap_is2_port_sel_ipv4_mc {
VCAP_IS2_PS_IPV4_MC_MAC_ETYPE,
VCAP_IS2_PS_IPV4_MC_IP4_TCP_UDP_OTHER,
VCAP_IS2_PS_IPV4_MC_IP_7TUPLE,
VCAP_IS2_PS_IPV4_MC_IP4_VID,
};
/* IS2 IPv6 unicast traffic type keyset generation */
enum vcap_is2_port_sel_ipv6_uc {
VCAP_IS2_PS_IPV6_UC_MAC_ETYPE,
VCAP_IS2_PS_IPV6_UC_IP_7TUPLE,
VCAP_IS2_PS_IPV6_UC_IP6_STD,
VCAP_IS2_PS_IPV6_UC_IP4_TCP_UDP_OTHER,
};
/* IS2 IPv6 multicast traffic type keyset generation */
enum vcap_is2_port_sel_ipv6_mc {
VCAP_IS2_PS_IPV6_MC_MAC_ETYPE,
VCAP_IS2_PS_IPV6_MC_IP_7TUPLE,
VCAP_IS2_PS_IPV6_MC_IP6_VID,
VCAP_IS2_PS_IPV6_MC_IP6_STD,
VCAP_IS2_PS_IPV6_MC_IP4_TCP_UDP_OTHER,
};
/* IS2 ARP traffic type keyset generation */
enum vcap_is2_port_sel_arp {
VCAP_IS2_PS_ARP_MAC_ETYPE,
VCAP_IS2_PS_ARP_ARP,
};
/* ES0 port keyset selection control */
/* ES0 Egress port traffic type classification */
enum vcap_es0_port_sel {
VCAP_ES0_PS_NORMAL_SELECTION,
VCAP_ES0_PS_FORCE_ISDX_LOOKUPS,
VCAP_ES0_PS_FORCE_VID_LOOKUPS,
VCAP_ES0_PS_RESERVED,
};
/* ES2 port keyset selection control */
/* ES2 IPv4 traffic type keyset generation */
enum vcap_es2_port_sel_ipv4 {
VCAP_ES2_PS_IPV4_MAC_ETYPE,
VCAP_ES2_PS_IPV4_IP_7TUPLE,
VCAP_ES2_PS_IPV4_IP4_TCP_UDP_VID,
VCAP_ES2_PS_IPV4_IP4_TCP_UDP_OTHER,
VCAP_ES2_PS_IPV4_IP4_VID,
VCAP_ES2_PS_IPV4_IP4_OTHER,
};
/* ES2 IPv6 traffic type keyset generation */
enum vcap_es2_port_sel_ipv6 {
VCAP_ES2_PS_IPV6_MAC_ETYPE,
VCAP_ES2_PS_IPV6_IP_7TUPLE,
VCAP_ES2_PS_IPV6_IP_7TUPLE_VID,
VCAP_ES2_PS_IPV6_IP_7TUPLE_STD,
VCAP_ES2_PS_IPV6_IP6_VID,
VCAP_ES2_PS_IPV6_IP6_STD,
VCAP_ES2_PS_IPV6_IP4_DOWNGRADE,
};
/* ES2 ARP traffic type keyset generation */
enum vcap_es2_port_sel_arp {
VCAP_ES2_PS_ARP_MAC_ETYPE,
VCAP_ES2_PS_ARP_ARP,
};
/* Selects TPID for ES0 matching */
enum SPX5_TPID_SEL {
SPX5_TPID_SEL_UNTAGGED,
SPX5_TPID_SEL_8100,
SPX5_TPID_SEL_UNUSED_0,
SPX5_TPID_SEL_UNUSED_1,
SPX5_TPID_SEL_88A8,
SPX5_TPID_SEL_TPIDCFG_1,
SPX5_TPID_SEL_TPIDCFG_2,
SPX5_TPID_SEL_TPIDCFG_3,
};
/* Get the port keyset for the vcap lookup */
int sparx5_vcap_get_port_keyset(struct net_device *ndev,
struct vcap_admin *admin,
int cid,
u16 l3_proto,
struct vcap_keyset_list *kslist);
/* Change the port keyset for the lookup and protocol */
void sparx5_vcap_set_port_keyset(struct net_device *ndev,
struct vcap_admin *admin, int cid,
u16 l3_proto, enum vcap_keyfield_set keyset,
struct vcap_keyset_list *orig);
/* Check if the ethertype is supported by the vcap port classification */
bool sparx5_vcap_is_known_etype(struct vcap_admin *admin, u16 etype);
#endif /* __SPARX5_VCAP_IMPL_H__ */
@@ -0,0 +1,243 @@
// SPDX-License-Identifier: GPL-2.0+
/* Microchip Sparx5 Switch driver
*
* Copyright (c) 2021 Microchip Technology Inc. and its subsidiaries.
*/
#include "sparx5_main_regs.h"
#include "sparx5_main.h"
static int sparx5_vlant_set_mask(struct sparx5 *sparx5, u16 vid)
{
u32 mask[3];
/* Divide up mask in 32 bit words */
bitmap_to_arr32(mask, sparx5->vlan_mask[vid], SPX5_PORTS);
/* Output mask to respective registers */
spx5_wr(mask[0], sparx5, ANA_L3_VLAN_MASK_CFG(vid));
if (is_sparx5(sparx5)) {
spx5_wr(mask[1], sparx5, ANA_L3_VLAN_MASK_CFG1(vid));
spx5_wr(mask[2], sparx5, ANA_L3_VLAN_MASK_CFG2(vid));
}
return 0;
}
void sparx5_vlan_init(struct sparx5 *sparx5)
{
u16 vid;
spx5_rmw(ANA_L3_VLAN_CTRL_VLAN_ENA_SET(1),
ANA_L3_VLAN_CTRL_VLAN_ENA,
sparx5,
ANA_L3_VLAN_CTRL);
/* Map VLAN = FID */
for (vid = NULL_VID; vid < VLAN_N_VID; vid++)
spx5_rmw(ANA_L3_VLAN_CFG_VLAN_FID_SET(vid),
ANA_L3_VLAN_CFG_VLAN_FID,
sparx5,
ANA_L3_VLAN_CFG(vid));
}
void sparx5_vlan_port_setup(struct sparx5 *sparx5, int portno)
{
struct sparx5_port *port = sparx5->ports[portno];
/* Configure PVID */
spx5_rmw(ANA_CL_VLAN_CTRL_VLAN_AWARE_ENA_SET(0) |
ANA_CL_VLAN_CTRL_PORT_VID_SET(port->pvid),
ANA_CL_VLAN_CTRL_VLAN_AWARE_ENA |
ANA_CL_VLAN_CTRL_PORT_VID,
sparx5,
ANA_CL_VLAN_CTRL(port->portno));
}
int sparx5_vlan_vid_add(struct sparx5_port *port, u16 vid, bool pvid,
bool untagged)
{
struct sparx5 *sparx5 = port->sparx5;
int ret;
/* Untagged egress vlan classification */
if (untagged && port->vid != vid) {
if (port->vid) {
netdev_err(port->ndev,
"Port already has a native VLAN: %d\n",
port->vid);
return -EBUSY;
}
port->vid = vid;
}
/* Make the port a member of the VLAN */
set_bit(port->portno, sparx5->vlan_mask[vid]);
ret = sparx5_vlant_set_mask(sparx5, vid);
if (ret)
return ret;
/* Default ingress vlan classification */
if (pvid)
port->pvid = vid;
sparx5_vlan_port_apply(sparx5, port);
return 0;
}
int sparx5_vlan_vid_del(struct sparx5_port *port, u16 vid)
{
struct sparx5 *sparx5 = port->sparx5;
int ret;
/* 8021q removes VID 0 on module unload for all interfaces
* with VLAN filtering feature. We need to keep it to receive
* untagged traffic.
*/
if (vid == 0)
return 0;
/* Stop the port from being a member of the vlan */
clear_bit(port->portno, sparx5->vlan_mask[vid]);
ret = sparx5_vlant_set_mask(sparx5, vid);
if (ret)
return ret;
/* Ingress */
if (port->pvid == vid)
port->pvid = 0;
/* Egress */
if (port->vid == vid)
port->vid = 0;
sparx5_vlan_port_apply(sparx5, port);
return 0;
}
void sparx5_pgid_update_mask(struct sparx5_port *port, int pgid, bool enable)
{
struct sparx5 *sparx5 = port->sparx5;
u32 val, mask;
/* mask is spread across 3 registers x 32 bit */
if (port->portno < 32) {
mask = BIT(port->portno);
val = enable ? mask : 0;
spx5_rmw(val, mask, sparx5, ANA_AC_PGID_CFG(pgid));
} else if (port->portno < 64) {
mask = BIT(port->portno - 32);
val = enable ? mask : 0;
spx5_rmw(val, mask, sparx5, ANA_AC_PGID_CFG1(pgid));
} else if (port->portno < SPX5_PORTS) {
mask = BIT(port->portno - 64);
val = enable ? mask : 0;
spx5_rmw(val, mask, sparx5, ANA_AC_PGID_CFG2(pgid));
} else {
netdev_err(port->ndev, "Invalid port no: %d\n", port->portno);
}
}
void sparx5_pgid_clear(struct sparx5 *spx5, int pgid)
{
spx5_wr(0, spx5, ANA_AC_PGID_CFG(pgid));
if (is_sparx5(spx5)) {
spx5_wr(0, spx5, ANA_AC_PGID_CFG1(pgid));
spx5_wr(0, spx5, ANA_AC_PGID_CFG2(pgid));
}
}
void sparx5_pgid_read_mask(struct sparx5 *spx5, int pgid, u32 portmask[3])
{
portmask[0] = spx5_rd(spx5, ANA_AC_PGID_CFG(pgid));
if (is_sparx5(spx5)) {
portmask[1] = spx5_rd(spx5, ANA_AC_PGID_CFG1(pgid));
portmask[2] = spx5_rd(spx5, ANA_AC_PGID_CFG2(pgid));
}
}
void sparx5_update_fwd(struct sparx5 *sparx5)
{
DECLARE_BITMAP(workmask, SPX5_PORTS);
u32 mask[3];
int port;
/* Divide up fwd mask in 32 bit words */
bitmap_to_arr32(mask, sparx5->bridge_fwd_mask, SPX5_PORTS);
/* Update SRC masks */
for (port = 0; port < sparx5->data->consts->n_ports; port++) {
if (test_bit(port, sparx5->bridge_fwd_mask)) {
/* Allow to send to all bridged but self */
bitmap_copy(workmask, sparx5->bridge_fwd_mask, SPX5_PORTS);
clear_bit(port, workmask);
bitmap_to_arr32(mask, workmask, SPX5_PORTS);
spx5_wr(mask[0], sparx5, ANA_AC_SRC_CFG(port));
if (is_sparx5(sparx5)) {
spx5_wr(mask[1], sparx5, ANA_AC_SRC_CFG1(port));
spx5_wr(mask[2], sparx5, ANA_AC_SRC_CFG2(port));
}
} else {
spx5_wr(0, sparx5, ANA_AC_SRC_CFG(port));
if (is_sparx5(sparx5)) {
spx5_wr(0, sparx5, ANA_AC_SRC_CFG1(port));
spx5_wr(0, sparx5, ANA_AC_SRC_CFG2(port));
}
}
}
/* Learning enabled only for bridged ports */
bitmap_and(workmask, sparx5->bridge_fwd_mask,
sparx5->bridge_lrn_mask, SPX5_PORTS);
bitmap_to_arr32(mask, workmask, SPX5_PORTS);
/* Apply learning mask */
spx5_wr(mask[0], sparx5, ANA_L2_AUTO_LRN_CFG);
if (is_sparx5(sparx5)) {
spx5_wr(mask[1], sparx5, ANA_L2_AUTO_LRN_CFG1);
spx5_wr(mask[2], sparx5, ANA_L2_AUTO_LRN_CFG2);
}
}
void sparx5_vlan_port_apply(struct sparx5 *sparx5,
struct sparx5_port *port)
{
u32 val;
/* Configure PVID, vlan aware */
val = ANA_CL_VLAN_CTRL_VLAN_AWARE_ENA_SET(port->vlan_aware) |
ANA_CL_VLAN_CTRL_VLAN_POP_CNT_SET(port->vlan_aware) |
ANA_CL_VLAN_CTRL_PORT_VID_SET(port->pvid);
spx5_wr(val, sparx5, ANA_CL_VLAN_CTRL(port->portno));
val = 0;
if (port->vlan_aware && !port->pvid)
/* If port is vlan-aware and tagged, drop untagged and
* priority tagged frames.
*/
val = ANA_CL_VLAN_FILTER_CTRL_TAG_REQUIRED_ENA_SET(1) |
ANA_CL_VLAN_FILTER_CTRL_PRIO_CTAG_DIS_SET(1) |
ANA_CL_VLAN_FILTER_CTRL_PRIO_STAG_DIS_SET(1);
spx5_wr(val, sparx5,
ANA_CL_VLAN_FILTER_CTRL(port->portno, 0));
/* Egress configuration (REW_TAG_CFG): VLAN tag selected via IFH */
val = REW_TAG_CTRL_TAG_TPID_CFG_SET(5);
if (port->vlan_aware) {
if (port->vid)
/* Tag all frames except when VID == DEFAULT_VLAN */
val |= REW_TAG_CTRL_TAG_CFG_SET(1);
else
val |= REW_TAG_CTRL_TAG_CFG_SET(3);
}
spx5_wr(val, sparx5, REW_TAG_CTRL(port->portno));
/* Egress VID */
spx5_rmw(REW_PORT_VLAN_CFG_PORT_VID_SET(port->vid),
REW_PORT_VLAN_CFG_PORT_VID,
sparx5,
REW_PORT_VLAN_CFG(port->portno));
}