diff --git a/drivers/net/dsa/ocelot/Kconfig b/drivers/net/dsa/ocelot/Kconfig index 9948544ba1c4b0080ce41d231983335182872bd7..aa588116be5f054d73ae8f4b0eff8c13cf7f4e49 100644 --- a/drivers/net/dsa/ocelot/Kconfig +++ b/drivers/net/dsa/ocelot/Kconfig @@ -28,3 +28,11 @@ config NET_DSA_MSCC_SEVILLE help This driver supports the VSC9953 (Seville) switch, which is embedded as a platform device on the NXP T1040 SoC. + +config MSCC_FELIX_SWITCH_TSN + bool "TSN support on Felix switch" + depends on NET_DSA_MSCC_FELIX + depends on TSN + help + This driver supports TSN which is using tsntool netlink + infrastructure on felix switch. diff --git a/drivers/net/dsa/ocelot/Makefile b/drivers/net/dsa/ocelot/Makefile index f6dd131e7491a3e2d71bdb7c50c5a21415b9c51f..86243f4a17a49b678c06ea127a42e277a0750fd5 100644 --- a/drivers/net/dsa/ocelot/Makefile +++ b/drivers/net/dsa/ocelot/Makefile @@ -9,3 +9,7 @@ mscc_felix-objs := \ mscc_seville-objs := \ felix.o \ seville_vsc9953.o + +ifdef CONFIG_MSCC_FELIX_SWITCH_TSN +mscc_felix-objs += felix_tsn.o +endif diff --git a/drivers/net/dsa/ocelot/felix.c b/drivers/net/dsa/ocelot/felix.c index e5288c41542c85c904843cfc450ddd70073a35cc..6b84c817aa91b8502f8d5c898a1424aba6c5976d 100644 --- a/drivers/net/dsa/ocelot/felix.c +++ b/drivers/net/dsa/ocelot/felix.c @@ -1246,6 +1246,10 @@ static int felix_init_structs(struct felix *felix, int num_phys_ports) ocelot_port->phy_mode = port_phy_modes[port]; ocelot_port->ocelot = ocelot; ocelot_port->target = target; + /* Enable cut-through forwarding on all traffic classes by + * default, to be compatible with the upstream kernel. + */ + ocelot_port->cut_thru = GENMASK(7, 0); ocelot->ports[port] = ocelot_port; } diff --git a/drivers/net/dsa/ocelot/felix.h b/drivers/net/dsa/ocelot/felix.h index 9395ac119d33787b11ce654edf45106f2dcb682d..b50240f8c88e03fcc3ee6a4d066a00189e4c2dac 100644 --- a/drivers/net/dsa/ocelot/felix.h +++ b/drivers/net/dsa/ocelot/felix.h @@ -49,7 +49,7 @@ struct felix_info { int (*port_setup_tc)(struct dsa_switch *ds, int port, enum tc_setup_type type, void *type_data); void (*port_sched_speed_set)(struct ocelot *ocelot, int port, - u32 speed); + int speed); struct regmap *(*init_regmap)(struct ocelot *ocelot, struct resource *res); }; diff --git a/drivers/net/dsa/ocelot/felix_tsn.c b/drivers/net/dsa/ocelot/felix_tsn.c new file mode 100644 index 0000000000000000000000000000000000000000..eee7c726a3aabcd74eb18ce09224d2fbc60c070c --- /dev/null +++ b/drivers/net/dsa/ocelot/felix_tsn.c @@ -0,0 +1,1759 @@ +// SPDX-License-Identifier: (GPL-2.0 OR MIT) +/* Felix Switch TSN driver + * + * Copyright 2020-2022 NXP + */ + +#include <soc/mscc/ocelot_qsys.h> +#include <soc/mscc/ocelot_ana.h> +#include <soc/mscc/ocelot_dev.h> +#include <soc/mscc/ocelot_sys.h> +#include <soc/mscc/ocelot_ptp.h> +#include <soc/mscc/ocelot.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/pcs-lynx.h> +#include <linux/io.h> +#include <net/dsa.h> +#include <net/tsn.h> +#include "felix_tsn.h" +#include "felix.h" + +#define ETH_P_8021CB 0x2345 +#define FELIX_QSYS_HSCH_NUM 72 +/* MSCC TSN parameters limited */ +#define FELIX_PSFP_SFID_NUM 176 +#define FELIX_FRER_SSID_NUM 128 +#define FELIX_STREAM_NUM 2048 + +struct felix_switch_capa { + u8 num_tas_gcl; + u32 tas_ct_min; + u32 tas_ct_max; + u32 tas_cte_max; + u32 tas_it_max; + u32 tas_it_min; + u8 num_hsch; + u8 num_psfp_sfid; + u8 num_frer_ssid; + u8 num_psfp_sgid; + u16 psfp_fmi_max; + u16 psfp_fmi_min; + u8 num_sgi_gcl; + u32 sgi_ct_min; + u32 sgi_ct_max; + u32 sgi_cte_max; + u16 qos_pol_max; + u8 pol_cbs_max; + u8 pol_pbs_max; + u8 frer_seq_len_min; + u8 frer_seq_len_max; + u8 frer_his_len_min; + u8 frer_his_len_max; + u8 qos_dscp_max; + u8 qos_cos_max; + u8 qos_dp_max; +}; + +struct stream_filter { + struct list_head list; + unsigned char mac[ETH_ALEN]; + u16 vid; + u32 index; + u8 handle; + u8 dst_idx; +}; + +static struct list_head streamtable; +static int hsch_bw[FELIX_QSYS_HSCH_NUM] = {0}; + +static const struct felix_switch_capa capa = { + .num_tas_gcl = 64, + .tas_ct_min = 100, + .tas_ct_max = 1000000000, + .tas_cte_max = 999999999, + .tas_it_max = 999999999, + .tas_it_min = 1000, + .num_hsch = 72, + .num_psfp_sfid = FELIX_PSFP_SFID_NUM, + .num_psfp_sgid = 184, + .psfp_fmi_max = 246, + .psfp_fmi_min = 63, + .num_sgi_gcl = 4, + .sgi_ct_min = 5000, + .sgi_ct_max = 1000000000, + .sgi_cte_max = 999999999, + .qos_pol_max = 383, + /* Maximum allowed value of committed burst size(CBS) is 240 KB */ + .pol_cbs_max = 60, + /* Maximum allowed value of excess burst size(EBS) is 240 KB */ + .pol_pbs_max = 60, + .num_frer_ssid = FELIX_FRER_SSID_NUM, + .frer_seq_len_min = 1, + .frer_seq_len_max = 28, + .frer_his_len_min = 1, + .frer_his_len_max = 32, + .qos_dscp_max = 63, + .qos_cos_max = OCELOT_NUM_TC - 1, + .qos_dp_max = 1, +}; + +static u32 felix_tsn_get_cap(struct net_device *ndev) +{ + return TSN_CAP_QBV | TSN_CAP_QCI | TSN_CAP_QBU | TSN_CAP_CBS | + TSN_CAP_CB | TSN_CAP_TBS | TSN_CAP_CTH; +} + +static void felix_get_basetime(struct ocelot *ocelot, ptptime_t basetime, + u64 cycle_time, struct timespec64 *ts_base) +{ + ptptime_t new_basetime; + ptptime_t cur_time; + + ocelot_ptp_gettime64(&ocelot->ptp_info, ts_base); + cur_time = ts_base->tv_sec * NSEC_PER_SEC + ts_base->tv_nsec; + + new_basetime = basetime; + if (cur_time > basetime) { + u64 nr_of_cycles = cur_time - basetime; + + do_div(nr_of_cycles, cycle_time); + new_basetime += cycle_time * (nr_of_cycles + 1); + } + + ts_base->tv_sec = new_basetime / NSEC_PER_SEC; + ts_base->tv_nsec = new_basetime % NSEC_PER_SEC; +} + +static int felix_tas_gcl_set(struct ocelot *ocelot, const u8 gcl_ix, + struct tsn_qbv_entry *control_list) +{ + if (gcl_ix >= capa.num_tas_gcl) { + dev_err(ocelot->dev, "Invalid gcl ix %u\n", gcl_ix); + return -EINVAL; + } + if (control_list->time_interval < capa.tas_it_min || + control_list->time_interval > capa.tas_it_max) { + dev_err(ocelot->dev, "Invalid time_interval %u\n", + control_list->time_interval); + + return -EINVAL; + } + + ocelot_write(ocelot, + QSYS_GCL_CFG_REG_1_GCL_ENTRY_NUM(gcl_ix) | + QSYS_GCL_CFG_REG_1_GATE_STATE(control_list->gate_state), + QSYS_GCL_CFG_REG_1); + + ocelot_write(ocelot, + control_list->time_interval, + QSYS_GCL_CFG_REG_2); + + return 0; +} + +static u32 felix_tas_read_status(struct ocelot *ocelot) +{ + return ocelot_read(ocelot, QSYS_TAS_PARAM_CFG_CTRL); +} + +static int felix_qbv_set(struct net_device *ndev, + struct tsn_qbv_conf *shaper_config) +{ + struct tsn_qbv_basic *admin_basic = &shaper_config->admin; + struct tsn_qbv_entry *control_list = admin_basic->control_list; + struct ocelot_port *ocelot_port; + struct timespec64 ts_base; + struct ocelot *ocelot; + struct dsa_port *dp; + int ret = 0; + int i, port; + u32 val; + + dp = dsa_port_from_netdev(ndev); + ocelot = dp->ds->priv; + port = dp->index; + + if (admin_basic->control_list_length > capa.num_tas_gcl) { + dev_err(ocelot->dev, + "Invalid admin_control_list_length %u\n", + admin_basic->control_list_length); + return -EINVAL; + } + + if ((admin_basic->cycle_time < capa.tas_ct_min || + admin_basic->cycle_time > capa.tas_ct_max) && + shaper_config->gate_enabled) { + dev_err(ocelot->dev, "Invalid admin_cycle_time %u ns\n", + admin_basic->cycle_time); + return -EINVAL; + } + if (admin_basic->cycle_time_extension > capa.tas_cte_max) { + dev_err(ocelot->dev, + "Invalid admin_cycle_time_extension %u\n", + admin_basic->cycle_time_extension); + return -EINVAL; + } + + ocelot_port = ocelot->ports[port]; + ocelot_port->base_time = admin_basic->base_time; + + mutex_lock(&ocelot->tas_lock); + + felix_get_basetime(ocelot, admin_basic->base_time, + admin_basic->cycle_time, &ts_base); + + /* Select port */ + ocelot_rmw(ocelot, + QSYS_TAS_PARAM_CFG_CTRL_PORT_NUM(port), + QSYS_TAS_PARAM_CFG_CTRL_ALWAYS_GUARD_BAND_SCH_Q | + QSYS_TAS_PARAM_CFG_CTRL_PORT_NUM_M, + QSYS_TAS_PARAM_CFG_CTRL); + + val = ocelot_read(ocelot, QSYS_PARAM_STATUS_REG_8); + if (val & QSYS_PARAM_STATUS_REG_8_CONFIG_PENDING) { + ocelot_rmw_rix(ocelot, 0, QSYS_TAG_CONFIG_ENABLE, + QSYS_TAG_CONFIG, port); + } + + if (!shaper_config->gate_enabled) + admin_basic->gate_states = 0xff; + + ocelot_rmw_rix(ocelot, + (shaper_config->gate_enabled ? QSYS_TAG_CONFIG_ENABLE : 0) | + QSYS_TAG_CONFIG_INIT_GATE_STATE(admin_basic->gate_states) | + QSYS_TAG_CONFIG_SCH_TRAFFIC_QUEUES(0xff), + QSYS_TAG_CONFIG_ENABLE | + QSYS_TAG_CONFIG_INIT_GATE_STATE_M | + QSYS_TAG_CONFIG_SCH_TRAFFIC_QUEUES_M, + QSYS_TAG_CONFIG, + port); + + if (shaper_config->maxsdu) { + ocelot_write_rix(ocelot, shaper_config->maxsdu, + QSYS_QMAXSDU_CFG_0, port); + ocelot_write_rix(ocelot, shaper_config->maxsdu, + QSYS_QMAXSDU_CFG_1, port); + ocelot_write_rix(ocelot, shaper_config->maxsdu, + QSYS_QMAXSDU_CFG_2, port); + ocelot_write_rix(ocelot, shaper_config->maxsdu, + QSYS_QMAXSDU_CFG_3, port); + ocelot_write_rix(ocelot, shaper_config->maxsdu, + QSYS_QMAXSDU_CFG_4, port); + ocelot_write_rix(ocelot, shaper_config->maxsdu, + QSYS_QMAXSDU_CFG_5, port); + ocelot_write_rix(ocelot, shaper_config->maxsdu, + QSYS_QMAXSDU_CFG_6, port); + ocelot_write_rix(ocelot, shaper_config->maxsdu, + QSYS_QMAXSDU_CFG_7, port); + } + + if (shaper_config->gate_enabled) { + ocelot_write(ocelot, ts_base.tv_nsec, + QSYS_PARAM_CFG_REG_1); + + ocelot_write(ocelot, lower_32_bits(ts_base.tv_sec), + QSYS_PARAM_CFG_REG_2); + + val = upper_32_bits(ts_base.tv_sec); + ocelot_write(ocelot, + QSYS_PARAM_CFG_REG_3_BASE_TIME_SEC_MSB(val) | + QSYS_PARAM_CFG_REG_3_LIST_LENGTH(admin_basic->control_list_length), + QSYS_PARAM_CFG_REG_3); + + ocelot_write(ocelot, admin_basic->cycle_time, + QSYS_PARAM_CFG_REG_4); + + ocelot_write(ocelot, admin_basic->cycle_time_extension, + QSYS_PARAM_CFG_REG_5); + + for (i = 0; i < admin_basic->control_list_length; i++) { + felix_tas_gcl_set(ocelot, i, control_list); + control_list++; + } + + /* Start configuration change */ + ocelot_rmw(ocelot, + QSYS_TAS_PARAM_CFG_CTRL_CONFIG_CHANGE, + QSYS_TAS_PARAM_CFG_CTRL_CONFIG_CHANGE, + QSYS_TAS_PARAM_CFG_CTRL); + + ret = readx_poll_timeout(felix_tas_read_status, ocelot, val, + !(QSYS_TAS_PARAM_CFG_CTRL_CONFIG_CHANGE + & val), 10, 100000); + } + + mutex_unlock(&ocelot->tas_lock); + + return ret; +} + +static int felix_qbv_get(struct net_device *ndev, struct tsn_qbv_conf *shaper_config) +{ + struct tsn_qbv_basic *admin = &shaper_config->admin; + struct tsn_qbv_entry *list; + u32 base_timel, base_timeh; + struct ocelot *ocelot; + struct dsa_port *dp; + u32 val, reg; + int i, port; + + dp = dsa_port_from_netdev(ndev); + ocelot = dp->ds->priv; + port = dp->index; + + mutex_lock(&ocelot->tas_lock); + + ocelot_rmw(ocelot, + QSYS_TAS_PARAM_CFG_CTRL_PORT_NUM(port), + QSYS_TAS_PARAM_CFG_CTRL_PORT_NUM_M, + QSYS_TAS_PARAM_CFG_CTRL); + + shaper_config->maxsdu = ocelot_read_rix(ocelot, QSYS_QMAXSDU_CFG_0, port); + + val = ocelot_read_rix(ocelot, QSYS_TAG_CONFIG, port); + shaper_config->gate_enabled = (val & QSYS_TAG_CONFIG_ENABLE); + admin->gate_states = QSYS_TAG_CONFIG_INIT_GATE_STATE_X(val); + + base_timel = ocelot_read(ocelot, QSYS_PARAM_CFG_REG_1); + base_timeh = ocelot_read(ocelot, QSYS_PARAM_CFG_REG_2); + reg = ocelot_read(ocelot, QSYS_PARAM_CFG_REG_3); + admin->base_time = base_timeh | + (((u64)QSYS_PARAM_CFG_REG_3_BASE_TIME_SEC_MSB(reg)) << 32); + + admin->base_time = (admin->base_time * 1000000000) + base_timel; + + admin->control_list_length = + QSYS_PARAM_CFG_REG_3_LIST_LENGTH_X(reg); + + admin->cycle_time = ocelot_read(ocelot, QSYS_PARAM_CFG_REG_4); + admin->cycle_time_extension = + ocelot_read(ocelot, QSYS_PARAM_CFG_REG_5); + + list = kmalloc_array(admin->control_list_length, + sizeof(struct tsn_qbv_entry), GFP_KERNEL); + if (!list) { + mutex_unlock(&ocelot->tas_lock); + return -ENOMEM; + } + + admin->control_list = list; + + for (i = 0; i < admin->control_list_length; i++) { + ocelot_rmw(ocelot, + QSYS_GCL_CFG_REG_1_GCL_ENTRY_NUM(i), + QSYS_GCL_CFG_REG_1_GCL_ENTRY_NUM_M, + QSYS_GCL_CFG_REG_1); + + list->time_interval = + ocelot_read(ocelot, QSYS_GCL_CFG_REG_2); + + reg = ocelot_read(ocelot, QSYS_GCL_CFG_REG_1); + list->gate_state = QSYS_GCL_CFG_REG_1_GATE_STATE_X(reg); + + list++; + } + + mutex_unlock(&ocelot->tas_lock); + + return 0; +} + +static int felix_qbv_get_gatelist(struct ocelot *ocelot, + struct tsn_qbv_basic *oper) +{ + u32 base_timel; + u32 base_timeh; + u32 val; + struct tsn_qbv_entry *glist; + int i; + + base_timel = ocelot_read(ocelot, QSYS_PARAM_STATUS_REG_1); + base_timeh = ocelot_read(ocelot, QSYS_PARAM_STATUS_REG_2); + val = ocelot_read(ocelot, QSYS_PARAM_STATUS_REG_3); + oper->base_time = base_timeh; + oper->base_time += + ((u64)QSYS_PARAM_STATUS_REG_3_BASE_TIME_SEC_MSB(val)) << + 32; + oper->base_time = (oper->base_time * 1000000000) + base_timel; + + oper->control_list_length = + QSYS_PARAM_STATUS_REG_3_LIST_LENGTH_X(val); + + oper->cycle_time = ocelot_read(ocelot, QSYS_PARAM_STATUS_REG_4); + oper->cycle_time_extension = ocelot_read(ocelot, + QSYS_PARAM_STATUS_REG_5); + + val = ocelot_read(ocelot, QSYS_PARAM_STATUS_REG_8); + oper->gate_states = QSYS_PARAM_STATUS_REG_8_OPER_GATE_STATE_X(val); + + glist = kmalloc_array(oper->control_list_length, + sizeof(struct tsn_qbv_entry), GFP_KERNEL); + if (!glist) + return -ENOMEM; + + oper->control_list = glist; + + for (i = 0; i < oper->control_list_length; i++) { + ocelot_rmw(ocelot, + QSYS_GCL_STATUS_REG_1_GCL_ENTRY_NUM(i), + QSYS_GCL_STATUS_REG_1_GCL_ENTRY_NUM_M, + QSYS_GCL_STATUS_REG_1); + + val = ocelot_read(ocelot, QSYS_GCL_STATUS_REG_2); + glist->time_interval = val; + val = ocelot_read(ocelot, QSYS_GCL_STATUS_REG_1); + glist->gate_state = + QSYS_GCL_STATUS_REG_1_GATE_STATE_X(val); + + glist++; + } + + return 0; +} + +static int felix_qbv_get_status(struct net_device *ndev, + struct tsn_qbv_status *qbvstatus) +{ + struct tsn_qbv_basic *oper = &qbvstatus->oper; + struct ocelot *ocelot; + struct timespec64 ts; + struct dsa_port *dp; + ptptime_t cur_time; + int port; + u32 val; + + dp = dsa_port_from_netdev(ndev); + ocelot = dp->ds->priv; + port = dp->index; + + mutex_lock(&ocelot->tas_lock); + + ocelot_rmw(ocelot, + QSYS_TAS_PARAM_CFG_CTRL_PORT_NUM(port), + QSYS_TAS_PARAM_CFG_CTRL_PORT_NUM_M, + QSYS_TAS_PARAM_CFG_CTRL); + + qbvstatus->supported_list_max = capa.num_tas_gcl; + + val = ocelot_read(ocelot, QSYS_PARAM_STATUS_REG_8); + qbvstatus->config_pending = + (val & QSYS_PARAM_STATUS_REG_8_CONFIG_PENDING) ? 1 : 0; + + qbvstatus->config_change_time = + ocelot_read(ocelot, QSYS_PARAM_STATUS_REG_7); + + qbvstatus->config_change_time += + ((u64)QSYS_PARAM_STATUS_REG_8_CFG_CHG_TIME_SEC_MSB(val)) << + 32; + + qbvstatus->config_change_time = + (qbvstatus->config_change_time * 1000000000) + + ocelot_read(ocelot, QSYS_PARAM_STATUS_REG_6); + + qbvstatus->config_change_error = + ocelot_read(ocelot, QSYS_PARAM_STATUS_REG_9); + + ocelot_ptp_gettime64(&ocelot->ptp_info, &ts); + cur_time = ts.tv_sec * NSEC_PER_SEC + ts.tv_nsec; + + qbvstatus->current_time = cur_time; + felix_qbv_get_gatelist(ocelot, oper); + + mutex_unlock(&ocelot->tas_lock); + + return 0; +} + +static int felix_qbu_set(struct net_device *ndev, u8 preemptible) +{ + struct ocelot_port *ocelot_port; + struct ocelot *ocelot; + struct dsa_port *dp; + int port; + + dp = dsa_port_from_netdev(ndev); + ocelot = dp->ds->priv; + port = dp->index; + + ocelot_port = ocelot->ports[port]; + + ocelot_port_rmwl(ocelot_port, + DEV_MM_CONFIG_ENABLE_CONFIG_MM_RX_ENA | + DEV_MM_CONFIG_ENABLE_CONFIG_MM_TX_ENA, + DEV_MM_CONFIG_ENABLE_CONFIG_MM_RX_ENA | + DEV_MM_CONFIG_ENABLE_CONFIG_MM_TX_ENA, + DEV_MM_ENABLE_CONFIG); + + ocelot_rmw_rix(ocelot, + QSYS_PREEMPTION_CFG_P_QUEUES(preemptible), + QSYS_PREEMPTION_CFG_P_QUEUES_M, + QSYS_PREEMPTION_CFG, + port); + + return 0; +} + +static int felix_qbu_get(struct net_device *ndev, struct tsn_preempt_status *c) +{ + struct ocelot_port *ocelot_port; + struct ocelot *ocelot; + struct dsa_port *dp; + int port; + u32 val; + + dp = dsa_port_from_netdev(ndev); + ocelot = dp->ds->priv; + port = dp->index; + + ocelot_port = ocelot->ports[port]; + + val = ocelot_read_rix(ocelot, QSYS_PREEMPTION_CFG, port); + + c->admin_state = QSYS_PREEMPTION_CFG_P_QUEUES(val); + c->hold_advance = QSYS_PREEMPTION_CFG_HOLD_ADVANCE_X(val); + + val = ocelot_port_readl(ocelot_port, DEV_MM_STATUS); + c->preemption_active = + DEV_MM_STATISTICS_MM_STATUS_PRMPT_ACTIVE_STATUS & val; + + return 0; +} + +static int felix_stream_table_add(u32 index, const unsigned char mac[ETH_ALEN], + int vid, u8 dst_idx, u8 handle) +{ + struct stream_filter *stream, *tmp; + struct list_head *pos, *q; + + list_for_each_safe(pos, q, &streamtable) { + tmp = list_entry(pos, struct stream_filter, list); + if (tmp->index == index) { + ether_addr_copy(tmp->mac, mac); + tmp->vid = vid; + tmp->dst_idx = dst_idx; + tmp->handle = handle; + return 0; + } + if (tmp->index > index) + break; + } + stream = kzalloc(sizeof(*stream), GFP_KERNEL); + if (!stream) + return -ENOMEM; + + stream->index = index; + ether_addr_copy(stream->mac, mac); + stream->vid = vid; + stream->dst_idx = dst_idx; + stream->handle = handle; + list_add(&stream->list, pos->prev); + + return 0; +} + +static void felix_stream_table_del(u32 index) +{ + struct stream_filter *tmp; + struct list_head *pos, *q; + + list_for_each_safe(pos, q, &streamtable) { + tmp = list_entry(pos, struct stream_filter, list); + if (tmp->index == index) { + list_del(pos); + kfree(tmp); + break; + } + } +} + +static struct stream_filter *felix_stream_table_get(u32 index) +{ + struct stream_filter *tmp; + + list_for_each_entry(tmp, &streamtable, list) + if (tmp->index == index) + return tmp; + + return NULL; +} + +static int felix_streamid_force_forward_clear(struct ocelot *ocelot, u8 port) +{ + struct stream_filter *tmp; + + if (port >= ocelot->num_phys_ports) + return 0; + + list_for_each_entry(tmp, &streamtable, list) + if (tmp->dst_idx == port) + return 0; + + ocelot_bridge_force_forward_port(ocelot, port, false); + + return 0; +} + +static int felix_cb_streamid_set(struct net_device *ndev, u32 index, bool enable, + struct tsn_cb_streamid *streamid) +{ + enum macaccess_entry_type type; + struct stream_filter *stream; + unsigned char mac[ETH_ALEN]; + struct ocelot *ocelot; + int sfid, ssid, port; + struct dsa_port *dp; + u32 dst_idx; + u16 vid; + int ret; + + dp = dsa_port_from_netdev(ndev); + ocelot = dp->ds->priv; + port = dp->index; + + if (index >= FELIX_STREAM_NUM) { + dev_err(ocelot->dev, "Invalid index %u, maximum:%u\n", + index, FELIX_STREAM_NUM - 1); + return -EINVAL; + } + + if (!enable) { + stream = felix_stream_table_get(index); + if (!stream) + return -EINVAL; + + ocelot_mact_forget(ocelot, stream->mac, stream->vid); + felix_stream_table_del(index); + + felix_streamid_force_forward_clear(ocelot, stream->dst_idx); + + return 0; + } + + if (streamid->type != 1) { + dev_err(ocelot->dev, "Invalid stream type\n"); + return -EINVAL; + } + + if (streamid->handle >= FELIX_PSFP_SFID_NUM) { + dev_err(ocelot->dev, + "Invalid stream handle %u, maximum:%u\n", + streamid->handle, FELIX_PSFP_SFID_NUM - 1); + return -EINVAL; + } + + sfid = streamid->handle; + ssid = (streamid->handle < FELIX_FRER_SSID_NUM ? + streamid->handle : (FELIX_FRER_SSID_NUM - 1)); + + u64_to_ether_addr(streamid->para.nid.dmac, mac); + vid = streamid->para.nid.vid; + + ret = ocelot_mact_lookup(ocelot, &dst_idx, mac, vid, &type); + if (ret && ret != -ENOENT) + return ret; + + if (ret == -ENOENT) { + /* The MAC table doesn't contain this entry, learn it as static + * and annotate it with a SSID and a SFID. + */ + ret = ocelot_mact_learn_streamdata(ocelot, port, mac, vid, + ENTRYTYPE_LOCKED, sfid, + ssid); + if (ret) + return ret; + + return felix_stream_table_add(index, mac, vid, port, + streamid->handle); + } + + if (type == ENTRYTYPE_NORMAL) + type = ENTRYTYPE_LOCKED; + + ret = ocelot_mact_learn_streamdata(ocelot, dst_idx, mac, vid, type, + sfid, ssid); + if (ret) + return ret; + + return felix_stream_table_add(index, mac, vid, dst_idx, + streamid->handle); +} + +static int felix_cb_streamid_get(struct net_device *ndev, u32 index, + struct tsn_cb_streamid *streamid) +{ + enum macaccess_entry_type type; + struct stream_filter *stream; + struct ocelot *ocelot; + struct dsa_port *dp; + u32 dst, fwdmask; + int ret; + + dp = dsa_port_from_netdev(ndev); + ocelot = dp->ds->priv; + + if (index >= FELIX_STREAM_NUM) { + dev_err(ocelot->dev, + "Invalid stream handle %u, maximum:%u\n", + index, FELIX_STREAM_NUM - 1); + return -EINVAL; + } + + stream = felix_stream_table_get(index); + if (!stream) + return -EINVAL; + + ret = ocelot_mact_lookup(ocelot, &dst, stream->mac, stream->vid, &type); + if (ret) + return ret; + + streamid->type = type; + + fwdmask = ocelot_read_rix(ocelot, ANA_PGID_PGID, dst); + streamid->ofac_oport = ANA_PGID_PGID_PGID(fwdmask); + + streamid->para.nid.dmac = ether_addr_to_u64(stream->mac); + streamid->para.nid.vid = stream->vid; + + streamid->handle = stream->handle; + + return 0; +} + +static int felix_cb_streamid_counters_get(struct net_device *ndev, u32 index, + struct tsn_cb_streamid_counters *sc) +{ + return 0; +} + +static int felix_qci_sfi_set(struct net_device *ndev, u32 index, bool enable, + struct tsn_qci_psfp_sfi_conf *sfi) +{ + int igr_prio = sfi->priority_spec; + u16 sgid = sfi->stream_gate_instance_id; + int fmid = sfi->stream_filter.flow_meter_instance_id; + u16 max_sdu_len = sfi->stream_filter.maximum_sdu_size; + struct ocelot *ocelot; + struct dsa_port *dp; + int sfid = index; + int i, port; + u16 pol_idx; + u32 val; + + dp = dsa_port_from_netdev(ndev); + ocelot = dp->ds->priv; + port = dp->index; + + if (fmid == -1) + pol_idx = capa.psfp_fmi_max; + else + pol_idx = (u16)fmid; + + if (!enable) { + val = ANA_TABLES_SFIDACCESS_SFID_TBL_CMD(SFIDACCESS_CMD_WRITE); + ocelot_write(ocelot, + ANA_TABLES_SFIDTIDX_SFID_INDEX(sfid), + ANA_TABLES_SFIDTIDX); + ocelot_write(ocelot, val, ANA_TABLES_SFIDACCESS); + return 0; + } + + /* Port default SFID set */ + if (sfi->stream_handle_spec < 0) { + val = ANA_PORT_SFID_CFG_SFID_VALID | + ANA_PORT_SFID_CFG_SFID(sfid); + if (igr_prio < 0) { + for (i = 0; i < OCELOT_NUM_TC; i++) + ocelot_write_ix(ocelot, val, ANA_PORT_SFID_CFG, + port, i); + } else { + ocelot_write_ix(ocelot, val, ANA_PORT_SFID_CFG, + port, igr_prio & 0x7); + } + } else if (index != sfi->stream_handle_spec) { + dev_err(ocelot->dev, "Index must equal to streamHandle\n"); + return -EINVAL; + } + + if (sgid >= capa.num_psfp_sgid) { + dev_err(ocelot->dev, "Invalid sgid %u, maximum:%u\n", + sgid, capa.num_psfp_sgid); + return -EINVAL; + } + if (pol_idx > capa.psfp_fmi_max || pol_idx < capa.psfp_fmi_min) { + dev_err(ocelot->dev, "Invalid pol_idx %u, range:%d~%d\n", + pol_idx, capa.psfp_fmi_min, capa.psfp_fmi_max); + return -EINVAL; + } + + ocelot_write(ocelot, ANA_TABLES_SFIDTIDX_SGID_VALID | + ANA_TABLES_SFIDTIDX_SGID(sgid) | + ((fmid != -1) ? ANA_TABLES_SFIDTIDX_POL_ENA : 0) | + ANA_TABLES_SFIDTIDX_POL_IDX(pol_idx) | + ANA_TABLES_SFIDTIDX_SFID_INDEX(sfid), + ANA_TABLES_SFIDTIDX); + + ocelot_write(ocelot, + ((igr_prio >= 0) ? + ANA_TABLES_SFIDACCESS_IGR_PRIO_MATCH_ENA : 0) | + ANA_TABLES_SFIDACCESS_IGR_PRIO(igr_prio) | + ANA_TABLES_SFIDACCESS_MAX_SDU_LEN(max_sdu_len) | + ANA_TABLES_SFIDACCESS_SFID_TBL_CMD(SFIDACCESS_CMD_WRITE), + ANA_TABLES_SFIDACCESS); + + return 0; +} + +static int felix_qci_sfi_get(struct net_device *ndev, u32 index, + struct tsn_qci_psfp_sfi_conf *sfi) +{ + u32 val, reg, fmeter_id, max_sdu; + struct ocelot *ocelot; + struct dsa_port *dp; + u32 sfid = index; + int enable = 1; + int i, port; + + dp = dsa_port_from_netdev(ndev); + ocelot = dp->ds->priv; + port = dp->index; + + if (sfid >= capa.num_psfp_sfid) { + dev_err(ocelot->dev, "Invalid index %u, maximum:%u\n", + sfid, capa.num_psfp_sfid); + return -EINVAL; + } + + ocelot_rmw(ocelot, + ANA_TABLES_SFIDTIDX_SFID_INDEX(sfid), + ANA_TABLES_SFIDTIDX_SFID_INDEX_M, + ANA_TABLES_SFIDTIDX); + + ocelot_write(ocelot, + ANA_TABLES_SFIDACCESS_SFID_TBL_CMD(SFIDACCESS_CMD_READ), + ANA_TABLES_SFIDACCESS); + + val = ocelot_read(ocelot, ANA_TABLES_SFIDTIDX); + if (!(val & ANA_TABLES_SFIDTIDX_SGID_VALID)) { + enable = 0; + return enable; + } + + sfi->stream_gate_instance_id = ANA_TABLES_SFIDTIDX_SGID_X(val); + fmeter_id = ANA_TABLES_SFIDTIDX_POL_IDX_X(val); + sfi->stream_filter.flow_meter_instance_id = fmeter_id; + + reg = ocelot_read(ocelot, ANA_TABLES_SFIDACCESS); + max_sdu = ANA_TABLES_SFIDACCESS_MAX_SDU_LEN_X(reg); + sfi->stream_filter.maximum_sdu_size = max_sdu; + + if (reg & ANA_TABLES_SFIDACCESS_IGR_PRIO_MATCH_ENA) + sfi->priority_spec = ANA_TABLES_SFIDACCESS_IGR_PRIO_X(reg); + else + dev_err(ocelot->dev, "priority not enable\n"); + + for (i = 0; i < OCELOT_NUM_TC; i++) { + val = ocelot_read_ix(ocelot, ANA_PORT_SFID_CFG, port, i); + if ((val & ANA_PORT_SFID_CFG_SFID_VALID) && + sfid == ANA_PORT_SFID_CFG_SFID(val)) { + sfi->stream_handle_spec = -1; + return enable; + } + } + + sfi->stream_handle_spec = sfid; + return enable; +} + +static int felix_qci_sfi_counters_get(struct net_device *ndev, u32 index, + struct tsn_qci_psfp_sfi_counters *sfi_cnt) +{ + u32 match, not_pass, not_pass_sdu, red; + struct ocelot *ocelot; + struct dsa_port *dp; + u32 sfid = index; + + dp = dsa_port_from_netdev(ndev); + ocelot = dp->ds->priv; + + if (sfid >= capa.num_psfp_sfid) { + dev_err(ocelot->dev, "Invalid index %u, maximum:%u\n", + sfid, capa.num_psfp_sfid); + return -EINVAL; + } + + ocelot_rmw(ocelot, + SYS_STAT_CFG_STAT_VIEW(sfid), + SYS_STAT_CFG_STAT_VIEW_M, + SYS_STAT_CFG); + + match = ocelot_read_gix(ocelot, SYS_CNT, 0x200); + not_pass = ocelot_read_gix(ocelot, SYS_CNT, 0x201); + not_pass_sdu = ocelot_read_gix(ocelot, SYS_CNT, 0x202); + red = ocelot_read_gix(ocelot, SYS_CNT, 0x203); + + sfi_cnt->matching_frames_count = match; + sfi_cnt->not_passing_frames_count = not_pass; + sfi_cnt->not_passing_sdu_count = not_pass_sdu; + sfi_cnt->red_frames_count = red; + + sfi_cnt->passing_frames_count = match - not_pass; + sfi_cnt->passing_sdu_count = match - not_pass - not_pass_sdu; + + return 0; +} + +static int felix_qci_max_cap_get(struct net_device *ndev, + struct tsn_qci_psfp_stream_param *stream_para) +{ + /* MaxStreamFilterInstances */ + stream_para->max_sf_instance = capa.num_psfp_sfid; + /* MaxStreamGateInstances */ + stream_para->max_sg_instance = capa.num_psfp_sgid; + /* MaxFlowMeterInstances */ + stream_para->max_fm_instance = capa.psfp_fmi_max - + capa.psfp_fmi_min + 1; + /* SupportedListMax */ + stream_para->supported_list_max = capa.num_sgi_gcl; + + return 0; +} + +static int felix_sgi_set_glist(struct ocelot *ocelot, + struct tsn_qci_psfp_gcl *gcl, uint32_t num) +{ + u32 time_sum = 0; + int i; + + if (num > capa.num_sgi_gcl) + return -EINVAL; + + for (i = 0; i < num; i++) { + u32 val = ANA_SG_GCL_GS_CONFIG_IPS((gcl->ipv < 0) ? + 0 : gcl->ipv + 8); + val |= (gcl->gate_state ? ANA_SG_GCL_GS_CONFIG_GATE_STATE : 0); + ocelot_write_rix(ocelot, val, ANA_SG_GCL_GS_CONFIG, i); + + time_sum += gcl->time_interval; + ocelot_write_rix(ocelot, time_sum, ANA_SG_GCL_TI_CONFIG, i); + + gcl++; + } + + return 0; +} + +static u32 felix_sgi_read_status(struct ocelot *ocelot) +{ + return ocelot_read(ocelot, ANA_SG_ACCESS_CTRL); +} + +static int felix_qci_sgi_set(struct net_device *ndev, u32 index, + struct tsn_qci_psfp_sgi_conf *sgi_conf) +{ + struct tsn_qci_sg_control *admin_list = &sgi_conf->admin; + u32 list_length = sgi_conf->admin.control_list_length; + u32 cycle_time = sgi_conf->admin.cycle_time; + u32 cycle_time_ex = sgi_conf->admin.cycle_time_extension; + struct timespec64 ts_base; + struct ocelot *ocelot; + struct dsa_port *dp; + u32 sgid = index; + int ret; + u32 val; + + dp = dsa_port_from_netdev(ndev); + ocelot = dp->ds->priv; + + if (sgid >= capa.num_psfp_sgid) { + dev_err(ocelot->dev, "Invalid sgid %u, maximum:%u\n", + sgid, capa.num_psfp_sgid); + return -EINVAL; + } + if ((cycle_time < capa.sgi_ct_min || + cycle_time > capa.sgi_ct_max) && + sgi_conf->gate_enabled) { + dev_err(ocelot->dev, "Invalid cycle_time %u ns\n", + cycle_time); + return -EINVAL; + } + if (cycle_time_ex > capa.sgi_cte_max) { + dev_err(ocelot->dev, + "Invalid cycle_time_extension %u\n", + cycle_time_ex); + return -EINVAL; + } + if (list_length > capa.num_sgi_gcl) { + dev_err(ocelot->dev, + "Invalid sgi_gcl len %u, maximum:%u\n", + list_length, capa.num_sgi_gcl); + return -EINVAL; + } + + /* configure SGID */ + ocelot_rmw(ocelot, + ANA_SG_ACCESS_CTRL_SGID(sgid), + ANA_SG_ACCESS_CTRL_SGID_M, + ANA_SG_ACCESS_CTRL); + + /* Disable SG */ + if (!sgi_conf->gate_enabled) { + ocelot_rmw(ocelot, + ANA_SG_CONFIG_REG_3_INIT_GATE_STATE, + ANA_SG_CONFIG_REG_3_INIT_GATE_STATE | + ANA_SG_CONFIG_REG_3_GATE_ENABLE, + ANA_SG_CONFIG_REG_3); + return 0; + } + + felix_get_basetime(ocelot, sgi_conf->admin.base_time, + sgi_conf->admin.cycle_time, &ts_base); + + ocelot_write(ocelot, ts_base.tv_nsec, ANA_SG_CONFIG_REG_1); + ocelot_write(ocelot, lower_32_bits(ts_base.tv_sec), + ANA_SG_CONFIG_REG_2); + + val = upper_32_bits(ts_base.tv_sec); + ocelot_write(ocelot, + (sgi_conf->admin.init_ipv < 0 ? + 0 : ANA_SG_CONFIG_REG_3_IPV_VALID) | + ANA_SG_CONFIG_REG_3_INIT_IPV(sgi_conf->admin.init_ipv) | + ANA_SG_CONFIG_REG_3_GATE_ENABLE | + ANA_SG_CONFIG_REG_3_LIST_LENGTH(list_length) | + (sgi_conf->admin.gate_states > 0 ? + ANA_SG_CONFIG_REG_3_INIT_GATE_STATE : 0) | + ANA_SG_CONFIG_REG_3_BASE_TIME_SEC_MSB(val), + ANA_SG_CONFIG_REG_3); + + ocelot_write(ocelot, cycle_time, ANA_SG_CONFIG_REG_4); + ocelot_write(ocelot, cycle_time_ex, ANA_SG_CONFIG_REG_5); + + ret = felix_sgi_set_glist(ocelot, admin_list->gcl, list_length); + if (ret < 0) + return ret; + + /* Start configuration change */ + ocelot_rmw(ocelot, + ANA_SG_ACCESS_CTRL_CONFIG_CHANGE, + ANA_SG_ACCESS_CTRL_CONFIG_CHANGE, + ANA_SG_ACCESS_CTRL); + + ret = readx_poll_timeout(felix_sgi_read_status, ocelot, val, + (!(ANA_SG_ACCESS_CTRL_CONFIG_CHANGE & val)), + 10, 100000); + + return ret; +} + +static int felix_sgi_get_glist(struct ocelot *ocelot, + struct tsn_qci_psfp_gcl *gcl, + uint32_t num) +{ + u32 time = 0; + u32 reg; + u16 val; + int i; + + if (num > capa.num_sgi_gcl) + return -EINVAL; + + for (i = 0; i < num; i++) { + val = ocelot_read_rix(ocelot, ANA_SG_GCL_GS_CONFIG, i); + gcl->gate_state = (val & ANA_SG_GCL_GS_CONFIG_GATE_STATE); + + if (val & ANA_SG_GCL_GS_CONFIG_IPV_VALID) + gcl->ipv = ANA_SG_GCL_GS_CONFIG_IPV(val); + else + gcl->ipv = -1; + + reg = ocelot_read_rix(ocelot, ANA_SG_GCL_TI_CONFIG, i); + gcl->time_interval = (reg - time); + time = reg; + + gcl++; + } + + return 0; +} + +static int felix_qci_sgi_get(struct net_device *ndev, u32 index, + struct tsn_qci_psfp_sgi_conf *sgi_conf) +{ + struct tsn_qci_sg_control *admin = &sgi_conf->admin; + struct tsn_qci_psfp_gcl *glist; + u32 val, reg, list_num; + struct ocelot *ocelot; + struct dsa_port *dp; + + dp = dsa_port_from_netdev(ndev); + ocelot = dp->ds->priv; + + if (index >= capa.num_psfp_sgid) { + dev_err(ocelot->dev, "Invalid sgid %u, maximum:%u\n", + index, capa.num_psfp_sgid); + return -EINVAL; + } + + ocelot_rmw(ocelot, + ANA_SG_ACCESS_CTRL_SGID(index), + ANA_SG_ACCESS_CTRL_SGID_M, + ANA_SG_ACCESS_CTRL); + + admin->cycle_time = ocelot_read(ocelot, ANA_SG_CONFIG_REG_4); + admin->cycle_time_extension = + ocelot_read(ocelot, ANA_SG_CONFIG_REG_5); + + val = ocelot_read(ocelot, ANA_SG_CONFIG_REG_2); + admin->base_time = val; + + reg = ocelot_read(ocelot, ANA_SG_CONFIG_REG_1); + val = ocelot_read(ocelot, ANA_SG_CONFIG_REG_3); + + admin->base_time += + ANA_SG_CONFIG_REG_3_BASE_TIME_SEC_MSB(val) << 32; + + admin->base_time = admin->base_time * 1000000000 + reg; + + if (val & ANA_SG_CONFIG_REG_3_IPV_VALID) + admin->init_ipv = ANA_SG_CONFIG_REG_3_INIT_IPV_X(val); + else + admin->init_ipv = -1; + + if (val & ANA_SG_CONFIG_REG_3_GATE_ENABLE) + sgi_conf->gate_enabled = 1; + + admin->control_list_length = ANA_SG_CONFIG_REG_3_LIST_LENGTH_X(val); + + list_num = admin->control_list_length; + + glist = kmalloc_array(list_num, sizeof(struct tsn_qci_psfp_gcl), + GFP_KERNEL); + if (!glist) + return -ENOMEM; + + admin->gcl = glist; + + return felix_sgi_get_glist(ocelot, glist, list_num); +} + +static int felix_qci_sgi_status_get(struct net_device *ndev, u16 index, + struct tsn_psfp_sgi_status *sgi_status) +{ + struct ocelot *ocelot; + struct dsa_port *dp; + u32 val, reg; + + dp = dsa_port_from_netdev(ndev); + ocelot = dp->ds->priv; + + if (index >= capa.num_psfp_sgid) { + dev_err(ocelot->dev, "Invalid sgid %u, maximum:%u\n", + index, capa.num_psfp_sgid); + return -EINVAL; + } + + ocelot_rmw(ocelot, + ANA_SG_ACCESS_CTRL_SGID(index), + ANA_SG_ACCESS_CTRL_SGID_M, + ANA_SG_ACCESS_CTRL); + + val = ocelot_read(ocelot, ANA_SG_STATUS_REG_2); + sgi_status->config_change_time = val; + + reg = ocelot_read(ocelot, ANA_SG_STATUS_REG_1); + val = ocelot_read(ocelot, ANA_SG_STATUS_REG_3); + sgi_status->config_change_time += + ANA_SG_STATUS_REG_3_CFG_CHG_TIME_SEC_MSB(val) << 32; + sgi_status->config_change_time = + sgi_status->config_change_time * 1000000000 + reg; + + if (val & ANA_SG_STATUS_REG_3_CONFIG_PENDING) + sgi_status->config_pending = 1; + else + sgi_status->config_pending = 0; + + if (val & ANA_SG_STATUS_REG_3_GATE_STATE) + sgi_status->oper.gate_states = 1; + else + sgi_status->oper.gate_states = 0; + /*bit 3 encoding 0:IPV [0:2]is invalid . 1:IPV[0:2] is valid*/ + if (val & ANA_SG_STATUS_REG_3_IPV_VALID) + sgi_status->oper.init_ipv = ANA_SG_STATUS_REG_3_IPV_X(val); + else + sgi_status->oper.init_ipv = -1; + + return 0; +} + +static int felix_qci_fmi_set(struct net_device *ndev, u32 index, + bool enable, struct tsn_qci_psfp_fmi *fmi) +{ + u32 cir = 0, cbs = 0, pir = 0, pbs = 0; + bool cir_discard = 0, pir_discard = 0; + u32 pbs_max = 0, cbs_max = 0; + struct ocelot *ocelot; + struct dsa_port *dp; + u32 cir_ena = 0; + + dp = dsa_port_from_netdev(ndev); + ocelot = dp->ds->priv; + + if (index > capa.qos_pol_max) { + dev_err(ocelot->dev, "Invalid pol_idx %u, maximum: %u\n", + index, capa.qos_pol_max); + return -EINVAL; + } + + if (fmi->mark_red_enable && fmi->mark_red) { + fmi->eir = 0; + fmi->ebs = 0; + fmi->cir = 0; + fmi->cbs = 0; + } + + pir = fmi->eir; + pbs = fmi->ebs; + + if (!fmi->drop_on_yellow) + cir_ena = 1; + + if (cir_ena) { + cir = fmi->cir; + cbs = fmi->cbs; + if (cir == 0 && cbs == 0) { + cir_discard = 1; + } else { + cir = DIV_ROUND_UP(cir, 100); + cir *= 3; /* Rate unit is 33 1/3 kbps */ + cbs = DIV_ROUND_UP(cbs, 4096); + cbs = (cbs ? cbs : 1); + cbs_max = capa.pol_cbs_max; + if (fmi->cf) + pir += fmi->cir; + } + } + + if (pir == 0 && pbs == 0) { + pir_discard = 1; + } else { + pir = DIV_ROUND_UP(pir, 100); + pir *= 3; /* Rate unit is 33 1/3 kbps */ + pbs = DIV_ROUND_UP(pbs, 4096); + pbs = (pbs ? pbs : 1); + pbs_max = capa.pol_pbs_max; + } + pir = min_t(u32, GENMASK(15, 0), pir); + cir = min_t(u32, GENMASK(15, 0), cir); + pbs = min(pbs_max, pbs); + cbs = min(cbs_max, cbs); + + ocelot_write_gix(ocelot, (ANA_POL_MODE_CFG_IPG_SIZE(20) | + ANA_POL_MODE_CFG_FRM_MODE(1) | + (fmi->cf ? ANA_POL_MODE_CFG_DLB_COUPLED : 0) | + (cir_ena ? ANA_POL_MODE_CFG_CIR_ENA : 0) | + ANA_POL_MODE_CFG_OVERSHOOT_ENA), + ANA_POL_MODE_CFG, index); + + ocelot_write_gix(ocelot, ANA_POL_PIR_CFG_PIR_RATE(pir) | + ANA_POL_PIR_CFG_PIR_BURST(pbs), + ANA_POL_PIR_CFG, index); + + ocelot_write_gix(ocelot, + (pir_discard ? GENMASK(22, 0) : 0), + ANA_POL_PIR_STATE, index); + + ocelot_write_gix(ocelot, ANA_POL_CIR_CFG_CIR_RATE(cir) | + ANA_POL_CIR_CFG_CIR_BURST(cbs), + ANA_POL_CIR_CFG, index); + + ocelot_write_gix(ocelot, + (cir_discard ? GENMASK(22, 0) : 0), + ANA_POL_CIR_STATE, index); + + return 0; +} + +static int felix_qci_fmi_get(struct net_device *ndev, u32 index, + struct tsn_qci_psfp_fmi *fmi, + struct tsn_qci_psfp_fmi_counters *counters) +{ + struct ocelot *ocelot; + struct dsa_port *dp; + u32 val, reg; + + dp = dsa_port_from_netdev(ndev); + ocelot = dp->ds->priv; + + if (index > capa.qos_pol_max) { + dev_err(ocelot->dev, "Invalid pol_idx %u, maximum: %u\n", + index, capa.qos_pol_max); + return -EINVAL; + } + + val = ocelot_read_gix(ocelot, ANA_POL_PIR_CFG, index); + reg = ocelot_read_gix(ocelot, ANA_POL_CIR_CFG, index); + + fmi->eir = ANA_POL_PIR_CFG_PIR_RATE_X(val); + fmi->eir = fmi->eir * 100 / 3; + fmi->ebs = ANA_POL_PIR_CFG_PIR_BURST(val); + fmi->ebs *= 4096; + fmi->cir = ANA_POL_CIR_CFG_CIR_RATE_X(reg); + fmi->cir = fmi->cir * 100 / 3; + fmi->cbs = ANA_POL_CIR_CFG_CIR_BURST(reg); + fmi->cbs *= 4096; + if (!(fmi->eir | fmi->ebs | fmi->cir | fmi->cbs)) + fmi->mark_red = 1; + else + fmi->mark_red = 0; + + val = ocelot_read_gix(ocelot, ANA_POL_MODE_CFG, index); + if (val & ANA_POL_MODE_CFG_DLB_COUPLED) + fmi->cf = 1; + else + fmi->cf = 0; + if (val & ANA_POL_MODE_CFG_CIR_ENA) + fmi->drop_on_yellow = 0; + else + fmi->drop_on_yellow = 1; + + return 0; +} + +static int felix_qos_shaper_conf_set(struct ocelot *ocelot, int idx, + u8 percent, int speed) +{ + u32 cbs = 0; + u32 cir = 0; + + if (percent > 100) { + dev_err(ocelot->dev, "percentage %d larger than 100\n", + percent); + return -EINVAL; + } + if (idx >= capa.num_hsch) { + dev_err(ocelot->dev, + "CIR_CFG: id %d is exceed num of HSCH instance\n", + idx); + return -EINVAL; + } + + switch (speed) { + case SPEED_10: + cir = 10000; + break; + case SPEED_100: + cir = 100000; + break; + case SPEED_1000: + cir = 1000000; + break; + case SPEED_2500: + cir = 2500000; + break; + } + + cir = cir * percent / 100; + cir = DIV_ROUND_UP(cir, 100); /* Rate unit is 100 kbps */ + cir = (cir ? cir : 1); /* Avoid using zero rate */ + cbs = DIV_ROUND_UP(cbs, 4096); /* Burst unit is 4kB */ + cbs = (cbs ? cbs : 1); /* Avoid using zero burst size */ + cir = min_t(u32, GENMASK(15, 0), cir); + cbs = min_t(u32, GENMASK(6, 0), cbs); + + if (!percent) { + cir = 0; + cbs = 0; + } + + ocelot_write_gix(ocelot, + QSYS_CIR_CFG_CIR_RATE(cir) | + QSYS_CIR_CFG_CIR_BURST(cbs), + QSYS_CIR_CFG, + idx); + + return 0; +} + +static int felix_cbs_set(struct net_device *ndev, u8 tc, u8 bw) +{ + struct ocelot_port *ocelot_port; + struct phylink_link_state state; + struct phylink_pcs *pcs; + struct ocelot *ocelot; + struct felix *felix; + struct dsa_port *dp; + int port, speed; + + dp = dsa_port_from_netdev(ndev); + ocelot = dp->ds->priv; + port = dp->index; + ocelot_port = ocelot->ports[port]; + + if (tc > capa.qos_cos_max) { + dev_err(ocelot->dev, "Invalid tc: %u\n", tc); + return -EINVAL; + } + + memset(&state, 0, sizeof(state)); + state.interface = ocelot_port->phy_mode; + + felix = ocelot_to_felix(ocelot); + pcs = felix->pcs[port]; + pcs->ops->pcs_get_state(pcs, &state); + + speed = state.speed; + + felix_qos_shaper_conf_set(ocelot, port * 8 + tc, bw, speed); + + ocelot_rmw_gix(ocelot, + QSYS_SE_CFG_SE_AVB_ENA, + QSYS_SE_CFG_SE_AVB_ENA, + QSYS_SE_CFG, + port * 8 + tc); + + hsch_bw[port * 8 + tc] = bw; + + return 0; +} + +void felix_cbs_reset(struct ocelot *ocelot, int port, int speed) +{ + int i, idx; + + for (i = 0; i < OCELOT_NUM_TC; i++) { + idx = port * 8 + i; + if (hsch_bw[idx] > 0) + felix_qos_shaper_conf_set(ocelot, idx, hsch_bw[idx], + speed); + } +} + +static int felix_cbs_get(struct net_device *ndev, u8 tc) +{ + struct ocelot *ocelot; + struct dsa_port *dp; + int port; + + dp = dsa_port_from_netdev(ndev); + ocelot = dp->ds->priv; + port = dp->index; + + if (tc > capa.qos_cos_max) { + dev_err(ocelot->dev, "Invalid tc: %u\n", tc); + return -EINVAL; + } + + return hsch_bw[port * 8 + tc]; +} + +static int felix_cut_thru_set(struct net_device *ndev, u8 cut_thru) +{ + struct ocelot_port *ocelot_port; + struct ocelot *ocelot; + struct dsa_port *dp; + int port; + + dp = dsa_port_from_netdev(ndev); + ocelot = dp->ds->priv; + port = dp->index; + ocelot_port = ocelot->ports[port]; + + mutex_lock(&ocelot->fwd_domain_lock); + + ocelot_port->cut_thru = cut_thru; + ocelot->ops->cut_through_fwd(ocelot); + + mutex_unlock(&ocelot->fwd_domain_lock); + + return 0; +} + +static int felix_rtag_parse_enable(struct ocelot *ocelot, u8 port) +{ + ocelot_rmw_rix(ocelot, + ANA_PORT_MODE_REDTAG_PARSE_CFG, + ANA_PORT_MODE_REDTAG_PARSE_CFG, + ANA_PORT_MODE, + port); + + ocelot_write(ocelot, ETH_P_8021CB, SYS_SR_ETYPE_CFG); + + /* No harm, no foul: we are telling the switch to adjust maximum frame + * length for double-tagged VLANs lying that the EtherType for S-Tags + * is the one for 802.1CB. This is not an issue because with 802.1CB + * traffic, the switch will not parse more than 2 tags anyway, so + * either it doesn't support 802.1CB or the second VLAN tag. + */ + ocelot_port_writel(ocelot->ports[port], + DEV_MAC_TAGS_CFG_TAG_ID(ETH_P_8021CB) | + DEV_MAC_TAGS_CFG_VLAN_AWR_ENA | + DEV_MAC_TAGS_CFG_VLAN_DBL_AWR_ENA | + DEV_MAC_TAGS_CFG_VLAN_LEN_AWR_ENA, + DEV_MAC_TAGS_CFG); + + return 0; +} + +static int felix_seq_gen_set(struct net_device *ndev, u32 index, + struct tsn_seq_gen_conf *sg_conf) +{ + u8 iport_mask = sg_conf->iport_mask; + u8 split_mask = sg_conf->split_mask; + u8 seq_len = sg_conf->seq_len; + u32 seq_num = sg_conf->seq_num; + struct stream_filter *tmp; + struct ocelot *ocelot; + struct dsa_port *dp; + + dp = dsa_port_from_netdev(ndev); + ocelot = dp->ds->priv; + + if (index >= capa.num_frer_ssid) { + dev_err(ocelot->dev, "Invalid SSID %u, maximum:%u\n", + index, capa.num_frer_ssid - 1); + return -EINVAL; + } + if (seq_len < capa.frer_seq_len_min || + seq_len > capa.frer_seq_len_max) { + dev_err(ocelot->dev, + "Invalid seq_space_bits num %u,range:%d~%d\n", + seq_len, + capa.frer_seq_len_min, + capa.frer_seq_len_max); + return -EINVAL; + } + + list_for_each_entry(tmp, &streamtable, list) + if (tmp->handle == index && + tmp->dst_idx < ocelot->num_phys_ports) + ocelot_bridge_force_forward_port(ocelot, tmp->dst_idx, true); + + ocelot_write(ocelot, + ANA_TABLES_SEQ_MASK_SPLIT_MASK(split_mask) | + ANA_TABLES_SEQ_MASK_INPUT_PORT_MASK(iport_mask), + ANA_TABLES_SEQ_MASK); + + ocelot_write(ocelot, + ANA_TABLES_STREAMTIDX_S_INDEX(index) | + ANA_TABLES_STREAMTIDX_STREAM_SPLIT | + ANA_TABLES_STREAMTIDX_SEQ_SPACE_LOG2(seq_len), + ANA_TABLES_STREAMTIDX); + + ocelot_write(ocelot, + ANA_TABLES_STREAMACCESS_GEN_REC_SEQ_NUM(seq_num) | + ANA_TABLES_STREAMACCESS_SEQ_GEN_REC_ENA | + ANA_TABLES_STREAMACCESS_STREAM_TBL_CMD(SFIDACCESS_CMD_WRITE), + ANA_TABLES_STREAMACCESS); + + return 0; +} + +static int felix_seq_rec_set(struct net_device *ndev, u32 index, + struct tsn_seq_rec_conf *sr_conf) +{ + u8 seq_len = sr_conf->seq_len; + u8 hislen = sr_conf->his_len; + struct ocelot *ocelot; + struct dsa_port *dp; + int i; + + dp = dsa_port_from_netdev(ndev); + ocelot = dp->ds->priv; + + if (index >= capa.num_frer_ssid) { + dev_err(ocelot->dev, "Invalid SSID %u, maximum:%u\n", + index, capa.num_frer_ssid - 1); + return -EINVAL; + } + if (seq_len < capa.frer_seq_len_min || + seq_len > capa.frer_seq_len_max) { + dev_err(ocelot->dev, + "Invalid seq_space_bits num %u,range:%d~%d\n", + seq_len, + capa.frer_seq_len_min, + capa.frer_seq_len_max); + return -EINVAL; + } + if (hislen < capa.frer_his_len_min || + hislen > capa.frer_his_len_max) { + dev_err(ocelot->dev, + "Invalid history_bits num %u,range:%d~%d\n", + hislen, + capa.frer_his_len_min, + capa.frer_his_len_max); + return -EINVAL; + } + + for (i = 0; i < ocelot->num_phys_ports; i++) + felix_rtag_parse_enable(ocelot, i); + + ocelot_write(ocelot, + ANA_TABLES_STREAMTIDX_S_INDEX(index) | + ANA_TABLES_STREAMTIDX_FORCE_SF_BEHAVIOUR | + ANA_TABLES_STREAMTIDX_SEQ_HISTORY_LEN(hislen) | + ANA_TABLES_STREAMTIDX_RESET_ON_ROGUE | + (sr_conf->rtag_pop_en ? + ANA_TABLES_STREAMTIDX_REDTAG_POP : 0) | + ANA_TABLES_STREAMTIDX_SEQ_SPACE_LOG2(seq_len), + ANA_TABLES_STREAMTIDX); + + ocelot_write(ocelot, + ANA_TABLES_STREAMACCESS_SEQ_GEN_REC_ENA | + ANA_TABLES_STREAMACCESS_GEN_REC_TYPE | + ANA_TABLES_STREAMACCESS_STREAM_TBL_CMD(SFIDACCESS_CMD_WRITE), + ANA_TABLES_STREAMACCESS); + + return 0; +} + +static int felix_cb_get(struct net_device *ndev, u32 index, + struct tsn_cb_status *c) +{ + struct ocelot *ocelot; + struct dsa_port *dp; + u32 val; + + dp = dsa_port_from_netdev(ndev); + ocelot = dp->ds->priv; + + if (index >= capa.num_frer_ssid) { + dev_err(ocelot->dev, "Invalid SSID %u, maximum:%u\n", + index, capa.num_frer_ssid - 1); + return -EINVAL; + } + + ocelot_write(ocelot, + ANA_TABLES_STREAMTIDX_S_INDEX(index), + ANA_TABLES_STREAMTIDX); + + ocelot_write(ocelot, + ANA_TABLES_STREAMACCESS_STREAM_TBL_CMD(SFIDACCESS_CMD_READ), + ANA_TABLES_STREAMACCESS); + + val = ocelot_read(ocelot, ANA_TABLES_STREAMACCESS); + c->gen_rec = (ANA_TABLES_STREAMACCESS_GEN_REC_TYPE & val) >> 2; + c->seq_num = ANA_TABLES_STREAMACCESS_GEN_REC_SEQ_NUM_X(val); + + val = ocelot_read(ocelot, ANA_TABLES_STREAMTIDX); + c->err = ANA_TABLES_STREAMTIDX_SEQ_GEN_ERR_STATUS_X(val); + c->his_len = ANA_TABLES_STREAMTIDX_SEQ_HISTORY_LEN_X(val); + c->seq_len = ANA_TABLES_STREAMTIDX_SEQ_SPACE_LOG2(val); + + val = ocelot_read(ocelot, ANA_TABLES_SEQ_MASK); + c->split_mask = ANA_TABLES_SEQ_MASK_SPLIT_MASK_X(val); + c->iport_mask = ANA_TABLES_SEQ_MASK_INPUT_PORT_MASK(val); + + c->seq_his = ocelot_read(ocelot, ANA_TABLES_SEQ_HISTORY); + + return 0; +} + +static int felix_dscp_set(struct net_device *ndev, bool enable, const u8 dscp_ix, + struct tsn_qos_switch_dscp_conf *c) +{ + struct ocelot *ocelot; + struct dsa_port *dp; + u32 ri = dscp_ix; + u32 val; + int port; + + dp = dsa_port_from_netdev(ndev); + ocelot = dp->ds->priv; + port = dp->index; + + c->dscp = 0; + c->trust = 1; + c->remark = 0; + + if (dscp_ix > capa.qos_dscp_max) { + dev_err(ocelot->dev, "Invalid dscp_ix %u\n", dscp_ix); + return -EINVAL; + } + if (c->cos > capa.qos_cos_max) { + dev_err(ocelot->dev, "Invalid cos %d\n", c->cos); + return -EINVAL; + } + if (c->dpl > capa.qos_dp_max) { + dev_err(ocelot->dev, "Invalid dpl %d\n", c->dpl); + return -EINVAL; + } + + ocelot_rmw_gix(ocelot, + (enable ? ANA_PORT_QOS_CFG_QOS_DSCP_ENA : 0) | + (c->dscp ? ANA_PORT_QOS_CFG_DSCP_TRANSLATE_ENA : 0), + ANA_PORT_QOS_CFG_QOS_DSCP_ENA | + ANA_PORT_QOS_CFG_DSCP_TRANSLATE_ENA, + ANA_PORT_QOS_CFG, + port); + + val = (c->dpl ? ANA_DSCP_CFG_DP_DSCP_VAL : 0) | + ANA_DSCP_CFG_QOS_DSCP_VAL(c->cos) | + ANA_DSCP_CFG_DSCP_TRANSLATE_VAL(c->dscp) | + (c->trust ? ANA_DSCP_CFG_DSCP_TRUST_ENA : 0) | + (c->remark ? ANA_DSCP_CFG_DSCP_REWR_ENA : 0); + + ocelot_write_rix(ocelot, val, ANA_DSCP_CFG, ri); + + return 0; +} + +void felix_preempt_irq_clean(struct ocelot *ocelot) +{ + struct ocelot_port *ocelot_port; + int port; + u32 val; + + val = DEV_MM_STATISTICS_MM_STATUS_PRMPT_ACTIVE_STICKY; + for (port = 0; port < ocelot->num_phys_ports; port++) { + ocelot_port = ocelot->ports[port]; + ocelot_port_rmwl(ocelot_port, val, val, DEV_MM_STATUS); + } +} + +static const struct tsn_ops felix_tsn_ops = { + .get_capability = felix_tsn_get_cap, + .qbv_set = felix_qbv_set, + .qbv_get = felix_qbv_get, + .qbv_get_status = felix_qbv_get_status, + .qbu_set = felix_qbu_set, + .qbu_get = felix_qbu_get, + .cb_streamid_set = felix_cb_streamid_set, + .cb_streamid_get = felix_cb_streamid_get, + .cb_streamid_counters_get = felix_cb_streamid_counters_get, + .qci_sfi_set = felix_qci_sfi_set, + .qci_sfi_get = felix_qci_sfi_get, + .qci_sfi_counters_get = felix_qci_sfi_counters_get, + .qci_get_maxcap = felix_qci_max_cap_get, + .qci_sgi_set = felix_qci_sgi_set, + .qci_sgi_get = felix_qci_sgi_get, + .qci_sgi_status_get = felix_qci_sgi_status_get, + .qci_fmi_set = felix_qci_fmi_set, + .qci_fmi_get = felix_qci_fmi_get, + .cbs_set = felix_cbs_set, + .cbs_get = felix_cbs_get, + .ct_set = felix_cut_thru_set, + .cbgen_set = felix_seq_gen_set, + .cbrec_set = felix_seq_rec_set, + .cb_get = felix_cb_get, + .dscp_set = felix_dscp_set, +}; + +int felix_tsn_enable(struct dsa_switch *ds) +{ + struct net_device *dev; + struct dsa_port *dp; + int port; + + for (port = 0; port < ds->num_ports; port++) { + dp = dsa_to_port(ds, port); + if (dp->type == DSA_PORT_TYPE_USER) { + dev = dp->slave; + tsn_port_register(dev, (struct tsn_ops *)&felix_tsn_ops, + GROUP_OFFSET_SWITCH); + } + } + + INIT_LIST_HEAD(&streamtable); + + return 0; +} diff --git a/drivers/net/dsa/ocelot/felix_tsn.h b/drivers/net/dsa/ocelot/felix_tsn.h new file mode 100644 index 0000000000000000000000000000000000000000..9895b85de8d904fb019b7bd74a7c203528d1d02b --- /dev/null +++ b/drivers/net/dsa/ocelot/felix_tsn.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: (GPL-2.0 OR MIT) + * + * Felix Switch TSN driver + * + * Copyright 2020-2021 NXP Semiconductors + */ + +#ifndef _MSCC_FELIX_SWITCH_TSN_H_ +#define _MSCC_FELIX_SWITCH_TSN_H_ + +#include <soc/mscc/ocelot.h> +#include <net/dsa.h> + +void felix_preempt_irq_clean(struct ocelot *ocelot); +void felix_cbs_reset(struct ocelot *ocelot, int port, int speed); +int felix_tsn_enable(struct dsa_switch *ds); +#endif diff --git a/drivers/net/dsa/ocelot/felix_vsc9959.c b/drivers/net/dsa/ocelot/felix_vsc9959.c index a58956ffa4db9414a7ce40693e80502cc0f9d903..84bf75cdf7c6049c2e3946e97d26c7b4b7b14272 100644 --- a/drivers/net/dsa/ocelot/felix_vsc9959.c +++ b/drivers/net/dsa/ocelot/felix_vsc9959.c @@ -16,6 +16,7 @@ #include <linux/iopoll.h> #include <linux/mdio.h> #include <linux/pci.h> +#include "felix_tsn.h" #include "felix.h" #define VSC9959_TAS_GCL_ENTRY_MAX 63 @@ -346,6 +347,9 @@ static const u32 vsc9959_dev_gmii_regmap[] = { REG(DEV_MAC_FC_MAC_LOW_CFG, 0x3c), REG(DEV_MAC_FC_MAC_HIGH_CFG, 0x40), REG(DEV_MAC_STICKY, 0x44), + REG(DEV_MM_ENABLE_CONFIG, 0x48), + REG(DEV_MM_VERIF_CONFIG, 0x4C), + REG(DEV_MM_STATUS, 0x50), REG_RESERVED(PCS1G_CFG), REG_RESERVED(PCS1G_MODE_CFG), REG_RESERVED(PCS1G_SD_CFG), @@ -1139,7 +1143,7 @@ static void vsc9959_mdio_bus_free(struct ocelot *ocelot) } static void vsc9959_sched_speed_set(struct ocelot *ocelot, int port, - u32 speed) + int speed) { u8 tas_speed; @@ -1165,6 +1169,10 @@ static void vsc9959_sched_speed_set(struct ocelot *ocelot, int port, QSYS_TAG_CONFIG_LINK_SPEED(tas_speed), QSYS_TAG_CONFIG_LINK_SPEED_M, QSYS_TAG_CONFIG, port); + +#ifdef CONFIG_MSCC_FELIX_SWITCH_TSN + felix_cbs_reset(ocelot, port, speed); +#endif } static void vsc9959_new_base_time(struct ocelot *ocelot, ktime_t base_time, @@ -2257,9 +2265,11 @@ static void vsc9959_cut_through_fwd(struct ocelot *ocelot) min_speed = other_ocelot_port->speed; } - /* Enable cut-through forwarding for all traffic classes. */ + /* Enable cut-through forwarding for the traffic classes + * selected by tsntool. + */ if (ocelot_port->speed == min_speed) - val = GENMASK(7, 0); + val = ocelot_port->cut_thru; set: tmp = ocelot_read_rix(ocelot, ANA_CUT_THRU_CFG, port); @@ -2326,10 +2336,13 @@ static irqreturn_t felix_irq_handler(int irq, void *data) * and preemption status change interrupt on each port. * * - Get txtstamp if have - * - TODO: handle preemption. Without handling it, driver may get - * interrupt storm. + * - Handle preemption if it's preemption IRQ. Without handling it, + * driver may get interrupt storm. */ +#ifdef CONFIG_MSCC_FELIX_SWITCH_TSN + felix_preempt_irq_clean(ocelot); +#endif ocelot_get_txtstamp(ocelot); return IRQ_HANDLED; @@ -2402,6 +2415,10 @@ static int felix_pci_probe(struct pci_dev *pdev, goto err_register_ds; } +#ifdef CONFIG_MSCC_FELIX_SWITCH_TSN + felix_tsn_enable(ds); +#endif + return 0; err_register_ds: diff --git a/drivers/net/ethernet/mscc/ocelot.c b/drivers/net/ethernet/mscc/ocelot.c index b039b4dba6ac9076bea13f0ffeb48d7c2bfdbc53..934775f8c3656f13cfeb7a2b1bf9e56de6cec763 100644 --- a/drivers/net/ethernet/mscc/ocelot.c +++ b/drivers/net/ethernet/mscc/ocelot.c @@ -15,12 +15,6 @@ #define TABLE_UPDATE_TIMEOUT_US 100000 #define OCELOT_RSV_VLAN_RANGE_START 4000 -struct ocelot_mact_entry { - u8 mac[ETH_ALEN]; - u16 vid; - enum macaccess_entry_type type; -}; - /* Caller must hold &ocelot->mact_lock */ static inline u32 ocelot_mact_read_macaccess(struct ocelot *ocelot) { @@ -1401,10 +1395,10 @@ int ocelot_port_fdb_do_dump(const unsigned char *addr, u16 vid, EXPORT_SYMBOL(ocelot_port_fdb_do_dump); /* Caller must hold &ocelot->mact_lock */ -static int ocelot_mact_read(struct ocelot *ocelot, int port, int row, int col, - struct ocelot_mact_entry *entry) +int ocelot_mact_read(struct ocelot *ocelot, int row, int col, int *dst, + struct ocelot_mact_entry *entry) { - u32 val, dst, macl, mach; + u32 val, macl, mach; char mac[ETH_ALEN]; /* Set row and column to read from */ @@ -1424,12 +1418,9 @@ static int ocelot_mact_read(struct ocelot *ocelot, int port, int row, int col, if (!(val & ANA_TABLES_MACACCESS_VALID)) return -EINVAL; - /* If the entry read has another port configured as its destination, - * do not report it. - */ - dst = (val & ANA_TABLES_MACACCESS_DEST_IDX_M) >> 3; - if (dst != port) - return -EINVAL; + *dst = (val & ANA_TABLES_MACACCESS_DEST_IDX_M) >> 3; + + entry->type = ANA_TABLES_MACACCESS_ENTRYTYPE_X(val); /* Get the entry's MAC address and VLAN id */ macl = ocelot_read(ocelot, ANA_TABLES_MACLDATA); @@ -1447,6 +1438,7 @@ static int ocelot_mact_read(struct ocelot *ocelot, int port, int row, int col, return 0; } +EXPORT_SYMBOL(ocelot_mact_read); int ocelot_mact_flush(struct ocelot *ocelot, int port) { @@ -1501,16 +1493,18 @@ int ocelot_fdb_dump(struct ocelot *ocelot, int port, for (j = 0; j < 4; j++) { struct ocelot_mact_entry entry; bool is_static; + int dst; - err = ocelot_mact_read(ocelot, port, i, j, &entry); - /* If the entry is invalid (wrong port, invalid...), - * skip it. - */ + err = ocelot_mact_read(ocelot, i, j, &dst, &entry); + /* If the entry is invalid, skip it. */ if (err == -EINVAL) continue; else if (err) break; + if (dst != port) + continue; + is_static = (entry.type == ENTRYTYPE_LOCKED); /* Hide the reserved VLANs used for @@ -2184,6 +2178,42 @@ void ocelot_port_unset_dsa_8021q_cpu(struct ocelot *ocelot, int port) } EXPORT_SYMBOL_GPL(ocelot_port_unset_dsa_8021q_cpu); +void ocelot_bridge_force_forward_port(struct ocelot *ocelot, int port, bool en) +{ + struct ocelot_port *ocelot_port = ocelot->ports[port]; + u32 mask; + int i; + + mutex_lock(&ocelot->fwd_domain_lock); + + if (!en) { + if (ocelot_port->force_forward) { + ocelot_apply_bridge_fwd_mask(ocelot, false); + ocelot_port->force_forward = 0; + } + mutex_unlock(&ocelot->fwd_domain_lock); + return; + } + + if (ocelot_port->force_forward) { + mutex_unlock(&ocelot->fwd_domain_lock); + return; + } + + ocelot_port->force_forward = 1; + for (i = 0; i < ocelot->num_phys_ports; i++) { + if (i == port) + continue; + + mask = ocelot_read_rix(ocelot, ANA_PGID_PGID, PGID_SRC + i); + mask |= BIT(port); + ocelot_write_rix(ocelot, mask, ANA_PGID_PGID, PGID_SRC + i); + } + + mutex_unlock(&ocelot->fwd_domain_lock); +} +EXPORT_SYMBOL(ocelot_bridge_force_forward_port); + void ocelot_bridge_stp_state_set(struct ocelot *ocelot, int port, u8 state) { struct ocelot_port *ocelot_port = ocelot->ports[port]; diff --git a/include/soc/mscc/ocelot.h b/include/soc/mscc/ocelot.h index 8e9c3a085327d5c15cb2874562106d0c950b004e..19985c4c07492c1fc166f22cc7ff0f5b41c10a79 100644 --- a/include/soc/mscc/ocelot.h +++ b/include/soc/mscc/ocelot.h @@ -51,6 +51,7 @@ */ /* Reserve some destination PGIDs at the end of the range: + * PGID_FRER: Destinations for multicast traffic in 802.1CB redundant network. * PGID_BLACKHOLE: used for not forwarding the frames * PGID_CPU: used for whitelisting certain MAC addresses, such as the addresses * of the switch port net devices, towards the CPU port module. @@ -60,6 +61,7 @@ * PGID_MCIPV6: the flooding destinations for IPv6 multicast traffic. * PGID_BC: the flooding destinations for broadcast traffic. */ +#define PGID_FRER 56 #define PGID_BLACKHOLE 57 #define PGID_CPU 58 #define PGID_UC 59 @@ -75,7 +77,7 @@ #define for_each_nonreserved_multicast_dest_pgid(ocelot, pgid) \ for ((pgid) = (ocelot)->num_phys_ports + 1; \ - (pgid) < PGID_BLACKHOLE; \ + (pgid) < PGID_FRER; \ (pgid)++) #define for_each_aggr_pgid(ocelot, pgid) \ @@ -414,6 +416,9 @@ enum ocelot_reg { DEV_MAC_FC_MAC_LOW_CFG, DEV_MAC_FC_MAC_HIGH_CFG, DEV_MAC_STICKY, + DEV_MM_ENABLE_CONFIG, + DEV_MM_VERIF_CONFIG, + DEV_MM_STATUS, PCS1G_CFG, PCS1G_MODE_CFG, PCS1G_SD_CFG, @@ -632,6 +637,12 @@ enum macaccess_entry_type { ENTRYTYPE_MACv6, }; +struct ocelot_mact_entry { + u8 mac[ETH_ALEN]; + u16 vid; + enum macaccess_entry_type type; +}; + #define OCELOT_QUIRK_PCS_PERFORMS_RATE_ADAPTATION BIT(0) #define OCELOT_QUIRK_QSGMII_PORTS_MUST_BE_UP BIT(1) @@ -671,6 +682,9 @@ struct ocelot_port { int bridge_num; u8 stp_state; + bool force_forward; + u8 cut_thru; + int speed; s64 base_time; @@ -762,6 +776,22 @@ struct ocelot_policer { u32 burst; /* bytes */ }; +int ocelot_mact_read(struct ocelot *ocelot, int row, int col, int *dst, + struct ocelot_mact_entry *entry); +int ocelot_mact_learn(struct ocelot *ocelot, int port, + const unsigned char mac[ETH_ALEN], + unsigned int vid, enum macaccess_entry_type type); +int ocelot_mact_forget(struct ocelot *ocelot, const unsigned char mac[ETH_ALEN], + unsigned int vid); +int ocelot_mact_lookup(struct ocelot *ocelot, int *dst_idx, + const unsigned char mac[ETH_ALEN], + unsigned int vid, enum macaccess_entry_type *type); +int ocelot_mact_learn_streamdata(struct ocelot *ocelot, int dst_idx, + const unsigned char mac[ETH_ALEN], + unsigned int vid, + enum macaccess_entry_type type, + int sfid, int ssid); + #define ocelot_bulk_read_rix(ocelot, reg, ri, buf, count) \ __ocelot_bulk_read_ix(ocelot, reg, reg##_RSZ * (ri), buf, count) @@ -860,6 +890,7 @@ int ocelot_get_ts_info(struct ocelot *ocelot, int port, void ocelot_set_ageing_time(struct ocelot *ocelot, unsigned int msecs); int ocelot_port_vlan_filtering(struct ocelot *ocelot, int port, bool enabled, struct netlink_ext_ack *extack); +void ocelot_bridge_force_forward_port(struct ocelot *ocelot, int port, bool en); void ocelot_bridge_stp_state_set(struct ocelot *ocelot, int port, u8 state); u32 ocelot_get_dsa_8021q_cpu_mask(struct ocelot *ocelot); u32 ocelot_get_bridge_fwd_mask(struct ocelot *ocelot, int src_port); @@ -967,15 +998,6 @@ void ocelot_phylink_mac_link_up(struct ocelot *ocelot, int port, bool tx_pause, bool rx_pause, unsigned long quirks); -int ocelot_mact_lookup(struct ocelot *ocelot, int *dst_idx, - const unsigned char mac[ETH_ALEN], - unsigned int vid, enum macaccess_entry_type *type); -int ocelot_mact_learn_streamdata(struct ocelot *ocelot, int dst_idx, - const unsigned char mac[ETH_ALEN], - unsigned int vid, - enum macaccess_entry_type type, - int sfid, int ssid); - int ocelot_vcap_policer_add(struct ocelot *ocelot, u32 pol_ix, struct ocelot_policer *pol); int ocelot_vcap_policer_del(struct ocelot *ocelot, u32 pol_ix); diff --git a/include/soc/mscc/ocelot_ana.h b/include/soc/mscc/ocelot_ana.h index 67e0ae05a5ab6ac79262675c701661a2904d5dc1..ff2b20e9af08d1a8fdf8007e66803406c2fed085 100644 --- a/include/soc/mscc/ocelot_ana.h +++ b/include/soc/mscc/ocelot_ana.h @@ -138,6 +138,9 @@ #define MACACCESS_CMD_READ 6 #define MACACCESS_CMD_WRITE 7 +#define MACACCESS_ENTRY_TYPE_NORMAL 0 +#define MACACCESS_ENTRY_TYPE_LOCKED 1 + #define ANA_TABLES_VLANACCESS_VLAN_PORT_MASK(x) (((x) << 2) & GENMASK(13, 2)) #define ANA_TABLES_VLANACCESS_VLAN_PORT_MASK_M GENMASK(13, 2) #define ANA_TABLES_VLANACCESS_VLAN_PORT_MASK_X(x) (((x) & GENMASK(13, 2)) >> 2) @@ -271,6 +274,9 @@ #define ANA_SG_GCL_GS_CONFIG_IPS(x) ((x) & GENMASK(3, 0)) #define ANA_SG_GCL_GS_CONFIG_IPS_M GENMASK(3, 0) +#define ANA_SG_GCL_GS_CONFIG_IPV_VALID BIT(3) +#define ANA_SG_GCL_GS_CONFIG_IPV(x) ((x) & GENMASK(2, 0)) +#define ANA_SG_GCL_GS_CONFIG_IPV_M GENMASK(2, 0) #define ANA_SG_GCL_GS_CONFIG_GATE_STATE BIT(4) #define ANA_SG_GCL_TI_CONFIG_RSZ 0x4 @@ -281,6 +287,10 @@ #define ANA_SG_STATUS_REG_3_IPS(x) (((x) << 20) & GENMASK(23, 20)) #define ANA_SG_STATUS_REG_3_IPS_M GENMASK(23, 20) #define ANA_SG_STATUS_REG_3_IPS_X(x) (((x) & GENMASK(23, 20)) >> 20) +#define ANA_SG_STATUS_REG_3_IPV_VALID BIT(23) +#define ANA_SG_STATUS_REG_3_IPV(x) (((x) << 20) & GENMASK(22, 20)) +#define ANA_SG_STATUS_REG_3_IPV_M GENMASK(22, 20) +#define ANA_SG_STATUS_REG_3_IPV_X(x) (((x) & GENMASK(22, 20)) >> 20) #define ANA_SG_STATUS_REG_3_CONFIG_PENDING BIT(24) #define ANA_PORT_VLAN_CFG_GSZ 0x100 diff --git a/include/soc/mscc/ocelot_dev.h b/include/soc/mscc/ocelot_dev.h index 0c6021f02fee675e0b86dbe49a6007c1460db387..cb1d8f5a62ee0f3078e9b8166bb539804f4e4c1c 100644 --- a/include/soc/mscc/ocelot_dev.h +++ b/include/soc/mscc/ocelot_dev.h @@ -93,6 +93,29 @@ #define DEV_MAC_STICKY_TX_FRM_LEN_OVR_STICKY BIT(1) #define DEV_MAC_STICKY_TX_ABORT_STICKY BIT(0) +#define DEV_MM_CONFIG_ENABLE_CONFIG_MM_RX_ENA BIT(0) +#define DEV_MM_CONFIG_ENABLE_CONFIG_MM_TX_ENA BIT(4) +#define DEV_MM_CONFIG_ENABLE_CONFIG_KEEP_S_AFTER_D BIT(8) + +#define DEV_MM_CONFIG_VERIF_CONFIG_PRM_VERIFY_DIS BIT(0) +#define DEV_MM_CONFIG_VERIF_CONFIG_PRM_VERIFY_TIME(x) (((x) << 4) & GENMASK(11, 4)) +#define DEV_MM_CONFIG_VERIF_CONFIG_PRM_VERIFY_TIME_M GENMASK(11, 4) +#define DEV_MM_CONFIG_VERIF_CONFIG_PRM_VERIFY_TIME_X(x) (((x) & GENMASK(11, 4)) >> 4) +#define DEV_MM_CONFIG_VERIF_CONFIG_VERIF_TIMER_UNITS(x) (((x) << 12) & GENMASK(13, 12)) +#define DEV_MM_CONFIG_VERIF_CONFIG_VERIF_TIMER_UNITS_M GENMASK(13, 12) +#define DEV_MM_CONFIG_VERIF_CONFIG_VERIF_TIMER_UNITS_X(x) (((x) & GENMASK(13, 12)) >> 12) + +#define DEV_MM_STATISTICS_MM_STATUS_PRMPT_ACTIVE_STATUS BIT(0) +#define DEV_MM_STATISTICS_MM_STATUS_PRMPT_ACTIVE_STICKY BIT(4) +#define DEV_MM_STATISTICS_MM_STATUS_PRMPT_VERIFY_STATE(x) (((x) << 8) & GENMASK(10, 8)) +#define DEV_MM_STATISTICS_MM_STATUS_PRMPT_VERIFY_STATE_M GENMASK(10, 8) +#define DEV_MM_STATISTICS_MM_STATUS_PRMPT_VERIFY_STATE_X(x) (((x) & GENMASK(10, 8)) >> 8) +#define DEV_MM_STATISTICS_MM_STATUS_UNEXP_RX_PFRM_STICKY BIT(12) +#define DEV_MM_STATISTICS_MM_STATUS_UNEXP_TX_PFRM_STICKY BIT(16) +#define DEV_MM_STATISTICS_MM_STATUS_MM_RX_FRAME_STATUS BIT(20) +#define DEV_MM_STATISTICS_MM_STATUS_MM_TX_FRAME_STATUS BIT(24) +#define DEV_MM_STATISTICS_MM_STATUS_MM_TX_PRMPT_STATUS BIT(28) + #define PCS1G_CFG_LINK_STATUS_TYPE BIT(4) #define PCS1G_CFG_AN_LINK_CTRL_ENA BIT(1) #define PCS1G_CFG_PCS_ENA BIT(0)