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 *
mm_verify_state_to_string(enum ethtool_mm_verify_status state)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
ocelot_mm_verify_status(u32 val)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
ocelot_port_update_active_preemptible_tcs(struct ocelot * ocelot,int port)52403ffc2cSVladimir Oltean void ocelot_port_update_active_preemptible_tcs(struct ocelot *ocelot, int port)
53403ffc2cSVladimir Oltean {
54403ffc2cSVladimir Oltean struct ocelot_port *ocelot_port = ocelot->ports[port];
55403ffc2cSVladimir Oltean struct ocelot_mm_state *mm = &ocelot->mm[port];
56403ffc2cSVladimir Oltean u32 val = 0;
57403ffc2cSVladimir Oltean
58403ffc2cSVladimir Oltean lockdep_assert_held(&ocelot->fwd_domain_lock);
59403ffc2cSVladimir Oltean
60403ffc2cSVladimir Oltean /* Only commit preemptible TCs when MAC Merge is active.
61403ffc2cSVladimir Oltean * On NXP LS1028A, when using QSGMII, the port hangs if transmitting
62403ffc2cSVladimir Oltean * preemptible frames at any other link speed than gigabit, so avoid
63403ffc2cSVladimir Oltean * preemption at lower speeds in this PHY mode.
64403ffc2cSVladimir Oltean */
65403ffc2cSVladimir Oltean if ((ocelot_port->phy_mode != PHY_INTERFACE_MODE_QSGMII ||
66403ffc2cSVladimir Oltean ocelot_port->speed == SPEED_1000) && mm->tx_active)
67403ffc2cSVladimir Oltean val = mm->preemptible_tcs;
68403ffc2cSVladimir Oltean
69403ffc2cSVladimir Oltean /* Cut through switching doesn't work for preemptible priorities,
70*c6efb4aeSVladimir Oltean * so first make sure it is disabled. Also, changing the preemptible
71*c6efb4aeSVladimir Oltean * TCs affects the oversized frame dropping logic, so that needs to be
72*c6efb4aeSVladimir Oltean * re-triggered. And since tas_guard_bands_update() also implicitly
73*c6efb4aeSVladimir Oltean * calls cut_through_fwd(), we don't need to explicitly call it.
74403ffc2cSVladimir Oltean */
75403ffc2cSVladimir Oltean mm->active_preemptible_tcs = val;
76*c6efb4aeSVladimir Oltean ocelot->ops->tas_guard_bands_update(ocelot, port);
77403ffc2cSVladimir Oltean
78403ffc2cSVladimir Oltean dev_dbg(ocelot->dev,
79403ffc2cSVladimir Oltean "port %d %s/%s, MM TX %s, preemptible TCs 0x%x, active 0x%x\n",
80403ffc2cSVladimir Oltean port, phy_modes(ocelot_port->phy_mode),
81403ffc2cSVladimir Oltean phy_speed_to_str(ocelot_port->speed),
82403ffc2cSVladimir Oltean mm->tx_active ? "active" : "inactive", mm->preemptible_tcs,
83403ffc2cSVladimir Oltean mm->active_preemptible_tcs);
84403ffc2cSVladimir Oltean
85403ffc2cSVladimir Oltean ocelot_rmw_rix(ocelot, QSYS_PREEMPTION_CFG_P_QUEUES(val),
86403ffc2cSVladimir Oltean QSYS_PREEMPTION_CFG_P_QUEUES_M,
87403ffc2cSVladimir Oltean QSYS_PREEMPTION_CFG, port);
88403ffc2cSVladimir Oltean }
89403ffc2cSVladimir Oltean
ocelot_port_change_fp(struct ocelot * ocelot,int port,unsigned long preemptible_tcs)90403ffc2cSVladimir Oltean void ocelot_port_change_fp(struct ocelot *ocelot, int port,
91403ffc2cSVladimir Oltean unsigned long preemptible_tcs)
92403ffc2cSVladimir Oltean {
93403ffc2cSVladimir Oltean struct ocelot_mm_state *mm = &ocelot->mm[port];
94403ffc2cSVladimir Oltean
95009d30f1SVladimir Oltean lockdep_assert_held(&ocelot->fwd_domain_lock);
96403ffc2cSVladimir Oltean
97403ffc2cSVladimir Oltean if (mm->preemptible_tcs == preemptible_tcs)
98009d30f1SVladimir Oltean return;
99403ffc2cSVladimir Oltean
100403ffc2cSVladimir Oltean mm->preemptible_tcs = preemptible_tcs;
101403ffc2cSVladimir Oltean
102403ffc2cSVladimir Oltean ocelot_port_update_active_preemptible_tcs(ocelot, port);
103403ffc2cSVladimir Oltean }
104403ffc2cSVladimir Oltean
ocelot_mm_update_port_status(struct ocelot * ocelot,int port)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");
130403ffc2cSVladimir 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
ocelot_mm_irq(struct ocelot * ocelot)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
ocelot_port_set_mm(struct ocelot * ocelot,int port,struct ethtool_mm_cfg * cfg,struct netlink_ext_ack * extack)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
ocelot_port_get_mm(struct ocelot * ocelot,int port,struct ethtool_mm_state * state)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
ocelot_mm_init(struct ocelot * ocelot)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