162306a36Sopenharmony_ci// SPDX-License-Identifier: (GPL-2.0 OR MIT) 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Hardware library for MAC Merge Layer and Frame Preemption on TSN-capable 462306a36Sopenharmony_ci * switches (VSC9959) 562306a36Sopenharmony_ci * 662306a36Sopenharmony_ci * Copyright 2022-2023 NXP 762306a36Sopenharmony_ci */ 862306a36Sopenharmony_ci#include <linux/ethtool.h> 962306a36Sopenharmony_ci#include <soc/mscc/ocelot.h> 1062306a36Sopenharmony_ci#include <soc/mscc/ocelot_dev.h> 1162306a36Sopenharmony_ci#include <soc/mscc/ocelot_qsys.h> 1262306a36Sopenharmony_ci 1362306a36Sopenharmony_ci#include "ocelot.h" 1462306a36Sopenharmony_ci 1562306a36Sopenharmony_cistatic const char * 1662306a36Sopenharmony_cimm_verify_state_to_string(enum ethtool_mm_verify_status state) 1762306a36Sopenharmony_ci{ 1862306a36Sopenharmony_ci switch (state) { 1962306a36Sopenharmony_ci case ETHTOOL_MM_VERIFY_STATUS_INITIAL: 2062306a36Sopenharmony_ci return "INITIAL"; 2162306a36Sopenharmony_ci case ETHTOOL_MM_VERIFY_STATUS_VERIFYING: 2262306a36Sopenharmony_ci return "VERIFYING"; 2362306a36Sopenharmony_ci case ETHTOOL_MM_VERIFY_STATUS_SUCCEEDED: 2462306a36Sopenharmony_ci return "SUCCEEDED"; 2562306a36Sopenharmony_ci case ETHTOOL_MM_VERIFY_STATUS_FAILED: 2662306a36Sopenharmony_ci return "FAILED"; 2762306a36Sopenharmony_ci case ETHTOOL_MM_VERIFY_STATUS_DISABLED: 2862306a36Sopenharmony_ci return "DISABLED"; 2962306a36Sopenharmony_ci default: 3062306a36Sopenharmony_ci return "UNKNOWN"; 3162306a36Sopenharmony_ci } 3262306a36Sopenharmony_ci} 3362306a36Sopenharmony_ci 3462306a36Sopenharmony_cistatic enum ethtool_mm_verify_status ocelot_mm_verify_status(u32 val) 3562306a36Sopenharmony_ci{ 3662306a36Sopenharmony_ci switch (DEV_MM_STAT_MM_STATUS_PRMPT_VERIFY_STATE_X(val)) { 3762306a36Sopenharmony_ci case 0: 3862306a36Sopenharmony_ci return ETHTOOL_MM_VERIFY_STATUS_INITIAL; 3962306a36Sopenharmony_ci case 1: 4062306a36Sopenharmony_ci return ETHTOOL_MM_VERIFY_STATUS_VERIFYING; 4162306a36Sopenharmony_ci case 2: 4262306a36Sopenharmony_ci return ETHTOOL_MM_VERIFY_STATUS_SUCCEEDED; 4362306a36Sopenharmony_ci case 3: 4462306a36Sopenharmony_ci return ETHTOOL_MM_VERIFY_STATUS_FAILED; 4562306a36Sopenharmony_ci case 4: 4662306a36Sopenharmony_ci return ETHTOOL_MM_VERIFY_STATUS_DISABLED; 4762306a36Sopenharmony_ci default: 4862306a36Sopenharmony_ci return ETHTOOL_MM_VERIFY_STATUS_UNKNOWN; 4962306a36Sopenharmony_ci } 5062306a36Sopenharmony_ci} 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_civoid ocelot_port_update_active_preemptible_tcs(struct ocelot *ocelot, int port) 5362306a36Sopenharmony_ci{ 5462306a36Sopenharmony_ci struct ocelot_port *ocelot_port = ocelot->ports[port]; 5562306a36Sopenharmony_ci struct ocelot_mm_state *mm = &ocelot->mm[port]; 5662306a36Sopenharmony_ci u32 val = 0; 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_ci lockdep_assert_held(&ocelot->fwd_domain_lock); 5962306a36Sopenharmony_ci 6062306a36Sopenharmony_ci /* Only commit preemptible TCs when MAC Merge is active. 6162306a36Sopenharmony_ci * On NXP LS1028A, when using QSGMII, the port hangs if transmitting 6262306a36Sopenharmony_ci * preemptible frames at any other link speed than gigabit, so avoid 6362306a36Sopenharmony_ci * preemption at lower speeds in this PHY mode. 6462306a36Sopenharmony_ci */ 6562306a36Sopenharmony_ci if ((ocelot_port->phy_mode != PHY_INTERFACE_MODE_QSGMII || 6662306a36Sopenharmony_ci ocelot_port->speed == SPEED_1000) && mm->tx_active) 6762306a36Sopenharmony_ci val = mm->preemptible_tcs; 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_ci /* Cut through switching doesn't work for preemptible priorities, 7062306a36Sopenharmony_ci * so first make sure it is disabled. Also, changing the preemptible 7162306a36Sopenharmony_ci * TCs affects the oversized frame dropping logic, so that needs to be 7262306a36Sopenharmony_ci * re-triggered. And since tas_guard_bands_update() also implicitly 7362306a36Sopenharmony_ci * calls cut_through_fwd(), we don't need to explicitly call it. 7462306a36Sopenharmony_ci */ 7562306a36Sopenharmony_ci mm->active_preemptible_tcs = val; 7662306a36Sopenharmony_ci ocelot->ops->tas_guard_bands_update(ocelot, port); 7762306a36Sopenharmony_ci 7862306a36Sopenharmony_ci dev_dbg(ocelot->dev, 7962306a36Sopenharmony_ci "port %d %s/%s, MM TX %s, preemptible TCs 0x%x, active 0x%x\n", 8062306a36Sopenharmony_ci port, phy_modes(ocelot_port->phy_mode), 8162306a36Sopenharmony_ci phy_speed_to_str(ocelot_port->speed), 8262306a36Sopenharmony_ci mm->tx_active ? "active" : "inactive", mm->preemptible_tcs, 8362306a36Sopenharmony_ci mm->active_preemptible_tcs); 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_ci ocelot_rmw_rix(ocelot, QSYS_PREEMPTION_CFG_P_QUEUES(val), 8662306a36Sopenharmony_ci QSYS_PREEMPTION_CFG_P_QUEUES_M, 8762306a36Sopenharmony_ci QSYS_PREEMPTION_CFG, port); 8862306a36Sopenharmony_ci} 8962306a36Sopenharmony_ci 9062306a36Sopenharmony_civoid ocelot_port_change_fp(struct ocelot *ocelot, int port, 9162306a36Sopenharmony_ci unsigned long preemptible_tcs) 9262306a36Sopenharmony_ci{ 9362306a36Sopenharmony_ci struct ocelot_mm_state *mm = &ocelot->mm[port]; 9462306a36Sopenharmony_ci 9562306a36Sopenharmony_ci lockdep_assert_held(&ocelot->fwd_domain_lock); 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_ci if (mm->preemptible_tcs == preemptible_tcs) 9862306a36Sopenharmony_ci return; 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_ci mm->preemptible_tcs = preemptible_tcs; 10162306a36Sopenharmony_ci 10262306a36Sopenharmony_ci ocelot_port_update_active_preemptible_tcs(ocelot, port); 10362306a36Sopenharmony_ci} 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_cistatic void ocelot_mm_update_port_status(struct ocelot *ocelot, int port) 10662306a36Sopenharmony_ci{ 10762306a36Sopenharmony_ci struct ocelot_port *ocelot_port = ocelot->ports[port]; 10862306a36Sopenharmony_ci struct ocelot_mm_state *mm = &ocelot->mm[port]; 10962306a36Sopenharmony_ci enum ethtool_mm_verify_status verify_status; 11062306a36Sopenharmony_ci u32 val, ack = 0; 11162306a36Sopenharmony_ci 11262306a36Sopenharmony_ci if (!mm->tx_enabled) 11362306a36Sopenharmony_ci return; 11462306a36Sopenharmony_ci 11562306a36Sopenharmony_ci val = ocelot_port_readl(ocelot_port, DEV_MM_STATUS); 11662306a36Sopenharmony_ci 11762306a36Sopenharmony_ci verify_status = ocelot_mm_verify_status(val); 11862306a36Sopenharmony_ci if (mm->verify_status != verify_status) { 11962306a36Sopenharmony_ci dev_dbg(ocelot->dev, 12062306a36Sopenharmony_ci "Port %d MAC Merge verification state %s\n", 12162306a36Sopenharmony_ci port, mm_verify_state_to_string(verify_status)); 12262306a36Sopenharmony_ci mm->verify_status = verify_status; 12362306a36Sopenharmony_ci } 12462306a36Sopenharmony_ci 12562306a36Sopenharmony_ci if (val & DEV_MM_STAT_MM_STATUS_PRMPT_ACTIVE_STICKY) { 12662306a36Sopenharmony_ci mm->tx_active = !!(val & DEV_MM_STAT_MM_STATUS_PRMPT_ACTIVE_STATUS); 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_ci dev_dbg(ocelot->dev, "Port %d TX preemption %s\n", 12962306a36Sopenharmony_ci port, mm->tx_active ? "active" : "inactive"); 13062306a36Sopenharmony_ci ocelot_port_update_active_preemptible_tcs(ocelot, port); 13162306a36Sopenharmony_ci 13262306a36Sopenharmony_ci ack |= DEV_MM_STAT_MM_STATUS_PRMPT_ACTIVE_STICKY; 13362306a36Sopenharmony_ci } 13462306a36Sopenharmony_ci 13562306a36Sopenharmony_ci if (val & DEV_MM_STAT_MM_STATUS_UNEXP_RX_PFRM_STICKY) { 13662306a36Sopenharmony_ci dev_err(ocelot->dev, 13762306a36Sopenharmony_ci "Unexpected P-frame received on port %d while verification was unsuccessful or not yet verified\n", 13862306a36Sopenharmony_ci port); 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_ci ack |= DEV_MM_STAT_MM_STATUS_UNEXP_RX_PFRM_STICKY; 14162306a36Sopenharmony_ci } 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_ci if (val & DEV_MM_STAT_MM_STATUS_UNEXP_TX_PFRM_STICKY) { 14462306a36Sopenharmony_ci dev_err(ocelot->dev, 14562306a36Sopenharmony_ci "Unexpected P-frame requested to be transmitted on port %d while verification was unsuccessful or not yet verified, or MM_TX_ENA=0\n", 14662306a36Sopenharmony_ci port); 14762306a36Sopenharmony_ci 14862306a36Sopenharmony_ci ack |= DEV_MM_STAT_MM_STATUS_UNEXP_TX_PFRM_STICKY; 14962306a36Sopenharmony_ci } 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_ci if (ack) 15262306a36Sopenharmony_ci ocelot_port_writel(ocelot_port, ack, DEV_MM_STATUS); 15362306a36Sopenharmony_ci} 15462306a36Sopenharmony_ci 15562306a36Sopenharmony_civoid ocelot_mm_irq(struct ocelot *ocelot) 15662306a36Sopenharmony_ci{ 15762306a36Sopenharmony_ci int port; 15862306a36Sopenharmony_ci 15962306a36Sopenharmony_ci mutex_lock(&ocelot->fwd_domain_lock); 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_ci for (port = 0; port < ocelot->num_phys_ports; port++) 16262306a36Sopenharmony_ci ocelot_mm_update_port_status(ocelot, port); 16362306a36Sopenharmony_ci 16462306a36Sopenharmony_ci mutex_unlock(&ocelot->fwd_domain_lock); 16562306a36Sopenharmony_ci} 16662306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(ocelot_mm_irq); 16762306a36Sopenharmony_ci 16862306a36Sopenharmony_ciint ocelot_port_set_mm(struct ocelot *ocelot, int port, 16962306a36Sopenharmony_ci struct ethtool_mm_cfg *cfg, 17062306a36Sopenharmony_ci struct netlink_ext_ack *extack) 17162306a36Sopenharmony_ci{ 17262306a36Sopenharmony_ci struct ocelot_port *ocelot_port = ocelot->ports[port]; 17362306a36Sopenharmony_ci u32 mm_enable = 0, verify_disable = 0, add_frag_size; 17462306a36Sopenharmony_ci struct ocelot_mm_state *mm; 17562306a36Sopenharmony_ci int err; 17662306a36Sopenharmony_ci 17762306a36Sopenharmony_ci if (!ocelot->mm_supported) 17862306a36Sopenharmony_ci return -EOPNOTSUPP; 17962306a36Sopenharmony_ci 18062306a36Sopenharmony_ci mm = &ocelot->mm[port]; 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_ci err = ethtool_mm_frag_size_min_to_add(cfg->tx_min_frag_size, 18362306a36Sopenharmony_ci &add_frag_size, extack); 18462306a36Sopenharmony_ci if (err) 18562306a36Sopenharmony_ci return err; 18662306a36Sopenharmony_ci 18762306a36Sopenharmony_ci if (cfg->pmac_enabled) 18862306a36Sopenharmony_ci mm_enable |= DEV_MM_CONFIG_ENABLE_CONFIG_MM_RX_ENA; 18962306a36Sopenharmony_ci 19062306a36Sopenharmony_ci if (cfg->tx_enabled) 19162306a36Sopenharmony_ci mm_enable |= DEV_MM_CONFIG_ENABLE_CONFIG_MM_TX_ENA; 19262306a36Sopenharmony_ci 19362306a36Sopenharmony_ci if (!cfg->verify_enabled) 19462306a36Sopenharmony_ci verify_disable = DEV_MM_CONFIG_VERIF_CONFIG_PRM_VERIFY_DIS; 19562306a36Sopenharmony_ci 19662306a36Sopenharmony_ci mutex_lock(&ocelot->fwd_domain_lock); 19762306a36Sopenharmony_ci 19862306a36Sopenharmony_ci ocelot_port_rmwl(ocelot_port, mm_enable, 19962306a36Sopenharmony_ci DEV_MM_CONFIG_ENABLE_CONFIG_MM_TX_ENA | 20062306a36Sopenharmony_ci DEV_MM_CONFIG_ENABLE_CONFIG_MM_RX_ENA, 20162306a36Sopenharmony_ci DEV_MM_ENABLE_CONFIG); 20262306a36Sopenharmony_ci 20362306a36Sopenharmony_ci ocelot_port_rmwl(ocelot_port, verify_disable | 20462306a36Sopenharmony_ci DEV_MM_CONFIG_VERIF_CONFIG_PRM_VERIFY_TIME(cfg->verify_time), 20562306a36Sopenharmony_ci DEV_MM_CONFIG_VERIF_CONFIG_PRM_VERIFY_DIS | 20662306a36Sopenharmony_ci DEV_MM_CONFIG_VERIF_CONFIG_PRM_VERIFY_TIME_M, 20762306a36Sopenharmony_ci DEV_MM_VERIF_CONFIG); 20862306a36Sopenharmony_ci 20962306a36Sopenharmony_ci ocelot_rmw_rix(ocelot, 21062306a36Sopenharmony_ci QSYS_PREEMPTION_CFG_MM_ADD_FRAG_SIZE(add_frag_size), 21162306a36Sopenharmony_ci QSYS_PREEMPTION_CFG_MM_ADD_FRAG_SIZE_M, 21262306a36Sopenharmony_ci QSYS_PREEMPTION_CFG, 21362306a36Sopenharmony_ci port); 21462306a36Sopenharmony_ci 21562306a36Sopenharmony_ci /* The switch will emit an IRQ when TX is disabled, to notify that it 21662306a36Sopenharmony_ci * has become inactive. We optimize ocelot_mm_update_port_status() to 21762306a36Sopenharmony_ci * not bother processing MM IRQs at all for ports with TX disabled, 21862306a36Sopenharmony_ci * but we need to ACK this IRQ now, while mm->tx_enabled is still set, 21962306a36Sopenharmony_ci * otherwise we get an IRQ storm. 22062306a36Sopenharmony_ci */ 22162306a36Sopenharmony_ci if (mm->tx_enabled && !cfg->tx_enabled) { 22262306a36Sopenharmony_ci ocelot_mm_update_port_status(ocelot, port); 22362306a36Sopenharmony_ci WARN_ON(mm->tx_active); 22462306a36Sopenharmony_ci } 22562306a36Sopenharmony_ci 22662306a36Sopenharmony_ci mm->tx_enabled = cfg->tx_enabled; 22762306a36Sopenharmony_ci 22862306a36Sopenharmony_ci mutex_unlock(&ocelot->fwd_domain_lock); 22962306a36Sopenharmony_ci 23062306a36Sopenharmony_ci return 0; 23162306a36Sopenharmony_ci} 23262306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(ocelot_port_set_mm); 23362306a36Sopenharmony_ci 23462306a36Sopenharmony_ciint ocelot_port_get_mm(struct ocelot *ocelot, int port, 23562306a36Sopenharmony_ci struct ethtool_mm_state *state) 23662306a36Sopenharmony_ci{ 23762306a36Sopenharmony_ci struct ocelot_port *ocelot_port = ocelot->ports[port]; 23862306a36Sopenharmony_ci struct ocelot_mm_state *mm; 23962306a36Sopenharmony_ci u32 val, add_frag_size; 24062306a36Sopenharmony_ci 24162306a36Sopenharmony_ci if (!ocelot->mm_supported) 24262306a36Sopenharmony_ci return -EOPNOTSUPP; 24362306a36Sopenharmony_ci 24462306a36Sopenharmony_ci mm = &ocelot->mm[port]; 24562306a36Sopenharmony_ci 24662306a36Sopenharmony_ci mutex_lock(&ocelot->fwd_domain_lock); 24762306a36Sopenharmony_ci 24862306a36Sopenharmony_ci val = ocelot_port_readl(ocelot_port, DEV_MM_ENABLE_CONFIG); 24962306a36Sopenharmony_ci state->pmac_enabled = !!(val & DEV_MM_CONFIG_ENABLE_CONFIG_MM_RX_ENA); 25062306a36Sopenharmony_ci state->tx_enabled = !!(val & DEV_MM_CONFIG_ENABLE_CONFIG_MM_TX_ENA); 25162306a36Sopenharmony_ci 25262306a36Sopenharmony_ci val = ocelot_port_readl(ocelot_port, DEV_MM_VERIF_CONFIG); 25362306a36Sopenharmony_ci state->verify_enabled = !(val & DEV_MM_CONFIG_VERIF_CONFIG_PRM_VERIFY_DIS); 25462306a36Sopenharmony_ci state->verify_time = DEV_MM_CONFIG_VERIF_CONFIG_PRM_VERIFY_TIME_X(val); 25562306a36Sopenharmony_ci state->max_verify_time = 128; 25662306a36Sopenharmony_ci 25762306a36Sopenharmony_ci val = ocelot_read_rix(ocelot, QSYS_PREEMPTION_CFG, port); 25862306a36Sopenharmony_ci add_frag_size = QSYS_PREEMPTION_CFG_MM_ADD_FRAG_SIZE_X(val); 25962306a36Sopenharmony_ci state->tx_min_frag_size = ethtool_mm_frag_size_add_to_min(add_frag_size); 26062306a36Sopenharmony_ci state->rx_min_frag_size = ETH_ZLEN; 26162306a36Sopenharmony_ci 26262306a36Sopenharmony_ci ocelot_mm_update_port_status(ocelot, port); 26362306a36Sopenharmony_ci state->verify_status = mm->verify_status; 26462306a36Sopenharmony_ci state->tx_active = mm->tx_active; 26562306a36Sopenharmony_ci 26662306a36Sopenharmony_ci mutex_unlock(&ocelot->fwd_domain_lock); 26762306a36Sopenharmony_ci 26862306a36Sopenharmony_ci return 0; 26962306a36Sopenharmony_ci} 27062306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(ocelot_port_get_mm); 27162306a36Sopenharmony_ci 27262306a36Sopenharmony_ciint ocelot_mm_init(struct ocelot *ocelot) 27362306a36Sopenharmony_ci{ 27462306a36Sopenharmony_ci struct ocelot_port *ocelot_port; 27562306a36Sopenharmony_ci struct ocelot_mm_state *mm; 27662306a36Sopenharmony_ci int port; 27762306a36Sopenharmony_ci 27862306a36Sopenharmony_ci if (!ocelot->mm_supported) 27962306a36Sopenharmony_ci return 0; 28062306a36Sopenharmony_ci 28162306a36Sopenharmony_ci ocelot->mm = devm_kcalloc(ocelot->dev, ocelot->num_phys_ports, 28262306a36Sopenharmony_ci sizeof(*ocelot->mm), GFP_KERNEL); 28362306a36Sopenharmony_ci if (!ocelot->mm) 28462306a36Sopenharmony_ci return -ENOMEM; 28562306a36Sopenharmony_ci 28662306a36Sopenharmony_ci for (port = 0; port < ocelot->num_phys_ports; port++) { 28762306a36Sopenharmony_ci u32 val; 28862306a36Sopenharmony_ci 28962306a36Sopenharmony_ci mm = &ocelot->mm[port]; 29062306a36Sopenharmony_ci ocelot_port = ocelot->ports[port]; 29162306a36Sopenharmony_ci 29262306a36Sopenharmony_ci /* Update initial status variable for the 29362306a36Sopenharmony_ci * verification state machine 29462306a36Sopenharmony_ci */ 29562306a36Sopenharmony_ci val = ocelot_port_readl(ocelot_port, DEV_MM_STATUS); 29662306a36Sopenharmony_ci mm->verify_status = ocelot_mm_verify_status(val); 29762306a36Sopenharmony_ci } 29862306a36Sopenharmony_ci 29962306a36Sopenharmony_ci return 0; 30062306a36Sopenharmony_ci} 301