16505b680SVladimir Oltean // SPDX-License-Identifier: (GPL-2.0 OR MIT) 26505b680SVladimir Oltean /* 36505b680SVladimir Oltean * Hardware library for MAC Merge Layer and Frame Preemption on TSN-capable 46505b680SVladimir Oltean * switches (VSC9959) 56505b680SVladimir Oltean * 66505b680SVladimir Oltean * Copyright 2022-2023 NXP 76505b680SVladimir Oltean */ 86505b680SVladimir Oltean #include <linux/ethtool.h> 96505b680SVladimir Oltean #include <soc/mscc/ocelot.h> 106505b680SVladimir Oltean #include <soc/mscc/ocelot_dev.h> 116505b680SVladimir Oltean #include <soc/mscc/ocelot_qsys.h> 126505b680SVladimir Oltean 136505b680SVladimir Oltean #include "ocelot.h" 146505b680SVladimir Oltean 156505b680SVladimir Oltean static const char * 166505b680SVladimir Oltean mm_verify_state_to_string(enum ethtool_mm_verify_status state) 176505b680SVladimir Oltean { 186505b680SVladimir Oltean switch (state) { 196505b680SVladimir Oltean case ETHTOOL_MM_VERIFY_STATUS_INITIAL: 206505b680SVladimir Oltean return "INITIAL"; 216505b680SVladimir Oltean case ETHTOOL_MM_VERIFY_STATUS_VERIFYING: 226505b680SVladimir Oltean return "VERIFYING"; 236505b680SVladimir Oltean case ETHTOOL_MM_VERIFY_STATUS_SUCCEEDED: 246505b680SVladimir Oltean return "SUCCEEDED"; 256505b680SVladimir Oltean case ETHTOOL_MM_VERIFY_STATUS_FAILED: 266505b680SVladimir Oltean return "FAILED"; 276505b680SVladimir Oltean case ETHTOOL_MM_VERIFY_STATUS_DISABLED: 286505b680SVladimir Oltean return "DISABLED"; 296505b680SVladimir Oltean default: 306505b680SVladimir Oltean return "UNKNOWN"; 316505b680SVladimir Oltean } 326505b680SVladimir Oltean } 336505b680SVladimir Oltean 346505b680SVladimir Oltean static enum ethtool_mm_verify_status ocelot_mm_verify_status(u32 val) 356505b680SVladimir Oltean { 366505b680SVladimir Oltean switch (DEV_MM_STAT_MM_STATUS_PRMPT_VERIFY_STATE_X(val)) { 376505b680SVladimir Oltean case 0: 386505b680SVladimir Oltean return ETHTOOL_MM_VERIFY_STATUS_INITIAL; 396505b680SVladimir Oltean case 1: 406505b680SVladimir Oltean return ETHTOOL_MM_VERIFY_STATUS_VERIFYING; 416505b680SVladimir Oltean case 2: 426505b680SVladimir Oltean return ETHTOOL_MM_VERIFY_STATUS_SUCCEEDED; 436505b680SVladimir Oltean case 3: 446505b680SVladimir Oltean return ETHTOOL_MM_VERIFY_STATUS_FAILED; 456505b680SVladimir Oltean case 4: 466505b680SVladimir Oltean return ETHTOOL_MM_VERIFY_STATUS_DISABLED; 476505b680SVladimir Oltean default: 486505b680SVladimir Oltean return ETHTOOL_MM_VERIFY_STATUS_UNKNOWN; 496505b680SVladimir Oltean } 506505b680SVladimir Oltean } 516505b680SVladimir Oltean 52*403ffc2cSVladimir Oltean void ocelot_port_update_active_preemptible_tcs(struct ocelot *ocelot, int port) 53*403ffc2cSVladimir Oltean { 54*403ffc2cSVladimir Oltean struct ocelot_port *ocelot_port = ocelot->ports[port]; 55*403ffc2cSVladimir Oltean struct ocelot_mm_state *mm = &ocelot->mm[port]; 56*403ffc2cSVladimir Oltean u32 val = 0; 57*403ffc2cSVladimir Oltean 58*403ffc2cSVladimir Oltean lockdep_assert_held(&ocelot->fwd_domain_lock); 59*403ffc2cSVladimir Oltean 60*403ffc2cSVladimir Oltean /* Only commit preemptible TCs when MAC Merge is active. 61*403ffc2cSVladimir Oltean * On NXP LS1028A, when using QSGMII, the port hangs if transmitting 62*403ffc2cSVladimir Oltean * preemptible frames at any other link speed than gigabit, so avoid 63*403ffc2cSVladimir Oltean * preemption at lower speeds in this PHY mode. 64*403ffc2cSVladimir Oltean */ 65*403ffc2cSVladimir Oltean if ((ocelot_port->phy_mode != PHY_INTERFACE_MODE_QSGMII || 66*403ffc2cSVladimir Oltean ocelot_port->speed == SPEED_1000) && mm->tx_active) 67*403ffc2cSVladimir Oltean val = mm->preemptible_tcs; 68*403ffc2cSVladimir Oltean 69*403ffc2cSVladimir Oltean /* Cut through switching doesn't work for preemptible priorities, 70*403ffc2cSVladimir Oltean * so first make sure it is disabled. 71*403ffc2cSVladimir Oltean */ 72*403ffc2cSVladimir Oltean mm->active_preemptible_tcs = val; 73*403ffc2cSVladimir Oltean ocelot->ops->cut_through_fwd(ocelot); 74*403ffc2cSVladimir Oltean 75*403ffc2cSVladimir Oltean dev_dbg(ocelot->dev, 76*403ffc2cSVladimir Oltean "port %d %s/%s, MM TX %s, preemptible TCs 0x%x, active 0x%x\n", 77*403ffc2cSVladimir Oltean port, phy_modes(ocelot_port->phy_mode), 78*403ffc2cSVladimir Oltean phy_speed_to_str(ocelot_port->speed), 79*403ffc2cSVladimir Oltean mm->tx_active ? "active" : "inactive", mm->preemptible_tcs, 80*403ffc2cSVladimir Oltean mm->active_preemptible_tcs); 81*403ffc2cSVladimir Oltean 82*403ffc2cSVladimir Oltean ocelot_rmw_rix(ocelot, QSYS_PREEMPTION_CFG_P_QUEUES(val), 83*403ffc2cSVladimir Oltean QSYS_PREEMPTION_CFG_P_QUEUES_M, 84*403ffc2cSVladimir Oltean QSYS_PREEMPTION_CFG, port); 85*403ffc2cSVladimir Oltean } 86*403ffc2cSVladimir Oltean 87*403ffc2cSVladimir Oltean void ocelot_port_change_fp(struct ocelot *ocelot, int port, 88*403ffc2cSVladimir Oltean unsigned long preemptible_tcs) 89*403ffc2cSVladimir Oltean { 90*403ffc2cSVladimir Oltean struct ocelot_mm_state *mm = &ocelot->mm[port]; 91*403ffc2cSVladimir Oltean 92*403ffc2cSVladimir Oltean mutex_lock(&ocelot->fwd_domain_lock); 93*403ffc2cSVladimir Oltean 94*403ffc2cSVladimir Oltean if (mm->preemptible_tcs == preemptible_tcs) 95*403ffc2cSVladimir Oltean goto out_unlock; 96*403ffc2cSVladimir Oltean 97*403ffc2cSVladimir Oltean mm->preemptible_tcs = preemptible_tcs; 98*403ffc2cSVladimir Oltean 99*403ffc2cSVladimir Oltean ocelot_port_update_active_preemptible_tcs(ocelot, port); 100*403ffc2cSVladimir Oltean 101*403ffc2cSVladimir Oltean out_unlock: 102*403ffc2cSVladimir Oltean mutex_unlock(&ocelot->fwd_domain_lock); 103*403ffc2cSVladimir Oltean } 104*403ffc2cSVladimir Oltean 10515f93f46SVladimir Oltean static void ocelot_mm_update_port_status(struct ocelot *ocelot, int port) 1066505b680SVladimir Oltean { 1076505b680SVladimir Oltean struct ocelot_port *ocelot_port = ocelot->ports[port]; 1086505b680SVladimir Oltean struct ocelot_mm_state *mm = &ocelot->mm[port]; 1096505b680SVladimir Oltean enum ethtool_mm_verify_status verify_status; 1107bf4a5b0SVladimir Oltean u32 val, ack = 0; 1117bf4a5b0SVladimir Oltean 1127bf4a5b0SVladimir Oltean if (!mm->tx_enabled) 1137bf4a5b0SVladimir Oltean return; 1146505b680SVladimir Oltean 1156505b680SVladimir Oltean val = ocelot_port_readl(ocelot_port, DEV_MM_STATUS); 1166505b680SVladimir Oltean 1176505b680SVladimir Oltean verify_status = ocelot_mm_verify_status(val); 1186505b680SVladimir Oltean if (mm->verify_status != verify_status) { 1196505b680SVladimir Oltean dev_dbg(ocelot->dev, 1206505b680SVladimir Oltean "Port %d MAC Merge verification state %s\n", 1216505b680SVladimir Oltean port, mm_verify_state_to_string(verify_status)); 1226505b680SVladimir Oltean mm->verify_status = verify_status; 1236505b680SVladimir Oltean } 1246505b680SVladimir Oltean 1256505b680SVladimir Oltean if (val & DEV_MM_STAT_MM_STATUS_PRMPT_ACTIVE_STICKY) { 1266505b680SVladimir Oltean mm->tx_active = !!(val & DEV_MM_STAT_MM_STATUS_PRMPT_ACTIVE_STATUS); 1276505b680SVladimir Oltean 1286505b680SVladimir Oltean dev_dbg(ocelot->dev, "Port %d TX preemption %s\n", 1296505b680SVladimir Oltean port, mm->tx_active ? "active" : "inactive"); 130*403ffc2cSVladimir Oltean ocelot_port_update_active_preemptible_tcs(ocelot, port); 1317bf4a5b0SVladimir Oltean 1327bf4a5b0SVladimir Oltean ack |= DEV_MM_STAT_MM_STATUS_PRMPT_ACTIVE_STICKY; 1336505b680SVladimir Oltean } 1346505b680SVladimir Oltean 1356505b680SVladimir Oltean if (val & DEV_MM_STAT_MM_STATUS_UNEXP_RX_PFRM_STICKY) { 1366505b680SVladimir Oltean dev_err(ocelot->dev, 1376505b680SVladimir Oltean "Unexpected P-frame received on port %d while verification was unsuccessful or not yet verified\n", 1386505b680SVladimir Oltean port); 1397bf4a5b0SVladimir Oltean 1407bf4a5b0SVladimir Oltean ack |= DEV_MM_STAT_MM_STATUS_UNEXP_RX_PFRM_STICKY; 1416505b680SVladimir Oltean } 1426505b680SVladimir Oltean 1436505b680SVladimir Oltean if (val & DEV_MM_STAT_MM_STATUS_UNEXP_TX_PFRM_STICKY) { 1446505b680SVladimir Oltean dev_err(ocelot->dev, 1456505b680SVladimir Oltean "Unexpected P-frame requested to be transmitted on port %d while verification was unsuccessful or not yet verified, or MM_TX_ENA=0\n", 1466505b680SVladimir Oltean port); 1477bf4a5b0SVladimir Oltean 1487bf4a5b0SVladimir Oltean ack |= DEV_MM_STAT_MM_STATUS_UNEXP_TX_PFRM_STICKY; 1496505b680SVladimir Oltean } 1506505b680SVladimir Oltean 1517bf4a5b0SVladimir Oltean if (ack) 1527bf4a5b0SVladimir Oltean ocelot_port_writel(ocelot_port, ack, DEV_MM_STATUS); 1536505b680SVladimir Oltean } 15415f93f46SVladimir Oltean 15515f93f46SVladimir Oltean void ocelot_mm_irq(struct ocelot *ocelot) 15615f93f46SVladimir Oltean { 15715f93f46SVladimir Oltean int port; 15815f93f46SVladimir Oltean 1593ff468efSVladimir Oltean mutex_lock(&ocelot->fwd_domain_lock); 1603ff468efSVladimir Oltean 16115f93f46SVladimir Oltean for (port = 0; port < ocelot->num_phys_ports; port++) 16215f93f46SVladimir Oltean ocelot_mm_update_port_status(ocelot, port); 1633ff468efSVladimir Oltean 1643ff468efSVladimir Oltean mutex_unlock(&ocelot->fwd_domain_lock); 16515f93f46SVladimir Oltean } 16615f93f46SVladimir Oltean EXPORT_SYMBOL_GPL(ocelot_mm_irq); 1676505b680SVladimir Oltean 1686505b680SVladimir Oltean int ocelot_port_set_mm(struct ocelot *ocelot, int port, 1696505b680SVladimir Oltean struct ethtool_mm_cfg *cfg, 1706505b680SVladimir Oltean struct netlink_ext_ack *extack) 1716505b680SVladimir Oltean { 1726505b680SVladimir Oltean struct ocelot_port *ocelot_port = ocelot->ports[port]; 1736505b680SVladimir Oltean u32 mm_enable = 0, verify_disable = 0, add_frag_size; 1747bf4a5b0SVladimir Oltean struct ocelot_mm_state *mm; 1756505b680SVladimir Oltean int err; 1766505b680SVladimir Oltean 1776505b680SVladimir Oltean if (!ocelot->mm_supported) 1786505b680SVladimir Oltean return -EOPNOTSUPP; 1796505b680SVladimir Oltean 1807bf4a5b0SVladimir Oltean mm = &ocelot->mm[port]; 1817bf4a5b0SVladimir Oltean 1826505b680SVladimir Oltean err = ethtool_mm_frag_size_min_to_add(cfg->tx_min_frag_size, 1836505b680SVladimir Oltean &add_frag_size, extack); 1846505b680SVladimir Oltean if (err) 1856505b680SVladimir Oltean return err; 1866505b680SVladimir Oltean 1876505b680SVladimir Oltean if (cfg->pmac_enabled) 1886505b680SVladimir Oltean mm_enable |= DEV_MM_CONFIG_ENABLE_CONFIG_MM_RX_ENA; 1896505b680SVladimir Oltean 1906505b680SVladimir Oltean if (cfg->tx_enabled) 1916505b680SVladimir Oltean mm_enable |= DEV_MM_CONFIG_ENABLE_CONFIG_MM_TX_ENA; 1926505b680SVladimir Oltean 1936505b680SVladimir Oltean if (!cfg->verify_enabled) 1946505b680SVladimir Oltean verify_disable = DEV_MM_CONFIG_VERIF_CONFIG_PRM_VERIFY_DIS; 1956505b680SVladimir Oltean 1963ff468efSVladimir Oltean mutex_lock(&ocelot->fwd_domain_lock); 1976505b680SVladimir Oltean 1986505b680SVladimir Oltean ocelot_port_rmwl(ocelot_port, mm_enable, 1996505b680SVladimir Oltean DEV_MM_CONFIG_ENABLE_CONFIG_MM_TX_ENA | 2006505b680SVladimir Oltean DEV_MM_CONFIG_ENABLE_CONFIG_MM_RX_ENA, 2016505b680SVladimir Oltean DEV_MM_ENABLE_CONFIG); 2026505b680SVladimir Oltean 2036505b680SVladimir Oltean ocelot_port_rmwl(ocelot_port, verify_disable | 2046505b680SVladimir Oltean DEV_MM_CONFIG_VERIF_CONFIG_PRM_VERIFY_TIME(cfg->verify_time), 2056505b680SVladimir Oltean DEV_MM_CONFIG_VERIF_CONFIG_PRM_VERIFY_DIS | 2066505b680SVladimir Oltean DEV_MM_CONFIG_VERIF_CONFIG_PRM_VERIFY_TIME_M, 2076505b680SVladimir Oltean DEV_MM_VERIF_CONFIG); 2086505b680SVladimir Oltean 2096505b680SVladimir Oltean ocelot_rmw_rix(ocelot, 2106505b680SVladimir Oltean QSYS_PREEMPTION_CFG_MM_ADD_FRAG_SIZE(add_frag_size), 2116505b680SVladimir Oltean QSYS_PREEMPTION_CFG_MM_ADD_FRAG_SIZE_M, 2126505b680SVladimir Oltean QSYS_PREEMPTION_CFG, 2136505b680SVladimir Oltean port); 2146505b680SVladimir Oltean 2157bf4a5b0SVladimir Oltean /* The switch will emit an IRQ when TX is disabled, to notify that it 2167bf4a5b0SVladimir Oltean * has become inactive. We optimize ocelot_mm_update_port_status() to 2177bf4a5b0SVladimir Oltean * not bother processing MM IRQs at all for ports with TX disabled, 2187bf4a5b0SVladimir Oltean * but we need to ACK this IRQ now, while mm->tx_enabled is still set, 2197bf4a5b0SVladimir Oltean * otherwise we get an IRQ storm. 2207bf4a5b0SVladimir Oltean */ 2217bf4a5b0SVladimir Oltean if (mm->tx_enabled && !cfg->tx_enabled) { 2227bf4a5b0SVladimir Oltean ocelot_mm_update_port_status(ocelot, port); 2237bf4a5b0SVladimir Oltean WARN_ON(mm->tx_active); 2247bf4a5b0SVladimir Oltean } 2257bf4a5b0SVladimir Oltean 2267bf4a5b0SVladimir Oltean mm->tx_enabled = cfg->tx_enabled; 2277bf4a5b0SVladimir Oltean 2283ff468efSVladimir Oltean mutex_unlock(&ocelot->fwd_domain_lock); 2296505b680SVladimir Oltean 2306505b680SVladimir Oltean return 0; 2316505b680SVladimir Oltean } 2326505b680SVladimir Oltean EXPORT_SYMBOL_GPL(ocelot_port_set_mm); 2336505b680SVladimir Oltean 2346505b680SVladimir Oltean int ocelot_port_get_mm(struct ocelot *ocelot, int port, 2356505b680SVladimir Oltean struct ethtool_mm_state *state) 2366505b680SVladimir Oltean { 2376505b680SVladimir Oltean struct ocelot_port *ocelot_port = ocelot->ports[port]; 2386505b680SVladimir Oltean struct ocelot_mm_state *mm; 2396505b680SVladimir Oltean u32 val, add_frag_size; 2406505b680SVladimir Oltean 2416505b680SVladimir Oltean if (!ocelot->mm_supported) 2426505b680SVladimir Oltean return -EOPNOTSUPP; 2436505b680SVladimir Oltean 2446505b680SVladimir Oltean mm = &ocelot->mm[port]; 2456505b680SVladimir Oltean 2463ff468efSVladimir Oltean mutex_lock(&ocelot->fwd_domain_lock); 2476505b680SVladimir Oltean 2486505b680SVladimir Oltean val = ocelot_port_readl(ocelot_port, DEV_MM_ENABLE_CONFIG); 2496505b680SVladimir Oltean state->pmac_enabled = !!(val & DEV_MM_CONFIG_ENABLE_CONFIG_MM_RX_ENA); 2506505b680SVladimir Oltean state->tx_enabled = !!(val & DEV_MM_CONFIG_ENABLE_CONFIG_MM_TX_ENA); 2516505b680SVladimir Oltean 2526505b680SVladimir Oltean val = ocelot_port_readl(ocelot_port, DEV_MM_VERIF_CONFIG); 25328113cfaSVladimir Oltean state->verify_enabled = !(val & DEV_MM_CONFIG_VERIF_CONFIG_PRM_VERIFY_DIS); 2546505b680SVladimir Oltean state->verify_time = DEV_MM_CONFIG_VERIF_CONFIG_PRM_VERIFY_TIME_X(val); 2556505b680SVladimir Oltean state->max_verify_time = 128; 2566505b680SVladimir Oltean 2576505b680SVladimir Oltean val = ocelot_read_rix(ocelot, QSYS_PREEMPTION_CFG, port); 2586505b680SVladimir Oltean add_frag_size = QSYS_PREEMPTION_CFG_MM_ADD_FRAG_SIZE_X(val); 2596505b680SVladimir Oltean state->tx_min_frag_size = ethtool_mm_frag_size_add_to_min(add_frag_size); 2606505b680SVladimir Oltean state->rx_min_frag_size = ETH_ZLEN; 2616505b680SVladimir Oltean 262bddd96ddSVladimir Oltean ocelot_mm_update_port_status(ocelot, port); 2636505b680SVladimir Oltean state->verify_status = mm->verify_status; 2646505b680SVladimir Oltean state->tx_active = mm->tx_active; 2656505b680SVladimir Oltean 2663ff468efSVladimir Oltean mutex_unlock(&ocelot->fwd_domain_lock); 2676505b680SVladimir Oltean 2686505b680SVladimir Oltean return 0; 2696505b680SVladimir Oltean } 2706505b680SVladimir Oltean EXPORT_SYMBOL_GPL(ocelot_port_get_mm); 2716505b680SVladimir Oltean 2726505b680SVladimir Oltean int ocelot_mm_init(struct ocelot *ocelot) 2736505b680SVladimir Oltean { 2746505b680SVladimir Oltean struct ocelot_port *ocelot_port; 2756505b680SVladimir Oltean struct ocelot_mm_state *mm; 2766505b680SVladimir Oltean int port; 2776505b680SVladimir Oltean 2786505b680SVladimir Oltean if (!ocelot->mm_supported) 2796505b680SVladimir Oltean return 0; 2806505b680SVladimir Oltean 2816505b680SVladimir Oltean ocelot->mm = devm_kcalloc(ocelot->dev, ocelot->num_phys_ports, 2826505b680SVladimir Oltean sizeof(*ocelot->mm), GFP_KERNEL); 2836505b680SVladimir Oltean if (!ocelot->mm) 2846505b680SVladimir Oltean return -ENOMEM; 2856505b680SVladimir Oltean 2866505b680SVladimir Oltean for (port = 0; port < ocelot->num_phys_ports; port++) { 2876505b680SVladimir Oltean u32 val; 2886505b680SVladimir Oltean 2896505b680SVladimir Oltean mm = &ocelot->mm[port]; 2906505b680SVladimir Oltean ocelot_port = ocelot->ports[port]; 2916505b680SVladimir Oltean 2926505b680SVladimir Oltean /* Update initial status variable for the 2936505b680SVladimir Oltean * verification state machine 2946505b680SVladimir Oltean */ 2956505b680SVladimir Oltean val = ocelot_port_readl(ocelot_port, DEV_MM_STATUS); 2966505b680SVladimir Oltean mm->verify_status = ocelot_mm_verify_status(val); 2976505b680SVladimir Oltean } 2986505b680SVladimir Oltean 2996505b680SVladimir Oltean return 0; 3006505b680SVladimir Oltean } 301