| // SPDX-License-Identifier: (GPL-2.0 OR MIT) | |
| /* | |
| * Hardware library for MAC Merge Layer and Frame Preemption on TSN-capable | |
| * switches (VSC9959) | |
| * | |
| * Copyright 2022-2023 NXP | |
| */ | |
| #include <linux/ethtool.h> | |
| #include <soc/mscc/ocelot.h> | |
| #include <soc/mscc/ocelot_dev.h> | |
| #include <soc/mscc/ocelot_qsys.h> | |
| #include "ocelot.h" | |
| static const char * | |
| mm_verify_state_to_string(enum ethtool_mm_verify_status state) | |
| { | |
| switch (state) { | |
| case ETHTOOL_MM_VERIFY_STATUS_INITIAL: | |
| return "INITIAL"; | |
| case ETHTOOL_MM_VERIFY_STATUS_VERIFYING: | |
| return "VERIFYING"; | |
| case ETHTOOL_MM_VERIFY_STATUS_SUCCEEDED: | |
| return "SUCCEEDED"; | |
| case ETHTOOL_MM_VERIFY_STATUS_FAILED: | |
| return "FAILED"; | |
| case ETHTOOL_MM_VERIFY_STATUS_DISABLED: | |
| return "DISABLED"; | |
| default: | |
| return "UNKNOWN"; | |
| } | |
| } | |
| static enum ethtool_mm_verify_status ocelot_mm_verify_status(u32 val) | |
| { | |
| switch (DEV_MM_STAT_MM_STATUS_PRMPT_VERIFY_STATE_X(val)) { | |
| case 0: | |
| return ETHTOOL_MM_VERIFY_STATUS_INITIAL; | |
| case 1: | |
| return ETHTOOL_MM_VERIFY_STATUS_VERIFYING; | |
| case 2: | |
| return ETHTOOL_MM_VERIFY_STATUS_SUCCEEDED; | |
| case 3: | |
| return ETHTOOL_MM_VERIFY_STATUS_FAILED; | |
| case 4: | |
| return ETHTOOL_MM_VERIFY_STATUS_DISABLED; | |
| default: | |
| return ETHTOOL_MM_VERIFY_STATUS_UNKNOWN; | |
| } | |
| } | |
| void ocelot_port_update_active_preemptible_tcs(struct ocelot *ocelot, int port) | |
| { | |
| struct ocelot_port *ocelot_port = ocelot->ports[port]; | |
| struct ocelot_mm_state *mm = &ocelot->mm[port]; | |
| u32 val = 0; | |
| lockdep_assert_held(&ocelot->fwd_domain_lock); | |
| /* Only commit preemptible TCs when MAC Merge is active. | |
| * On NXP LS1028A, when using QSGMII, the port hangs if transmitting | |
| * preemptible frames at any other link speed than gigabit, so avoid | |
| * preemption at lower speeds in this PHY mode. | |
| */ | |
| if ((ocelot_port->phy_mode != PHY_INTERFACE_MODE_QSGMII || | |
| ocelot_port->speed == SPEED_1000) && mm->tx_active) | |
| val = mm->preemptible_tcs; | |
| /* Cut through switching doesn't work for preemptible priorities, | |
| * so first make sure it is disabled. Also, changing the preemptible | |
| * TCs affects the oversized frame dropping logic, so that needs to be | |
| * re-triggered. And since tas_guard_bands_update() also implicitly | |
| * calls cut_through_fwd(), we don't need to explicitly call it. | |
| */ | |
| mm->active_preemptible_tcs = val; | |
| ocelot->ops->tas_guard_bands_update(ocelot, port); | |
| dev_dbg(ocelot->dev, | |
| "port %d %s/%s, MM TX %s, preemptible TCs 0x%x, active 0x%x\n", | |
| port, phy_modes(ocelot_port->phy_mode), | |
| phy_speed_to_str(ocelot_port->speed), | |
| mm->tx_active ? "active" : "inactive", mm->preemptible_tcs, | |
| mm->active_preemptible_tcs); | |
| ocelot_rmw_rix(ocelot, QSYS_PREEMPTION_CFG_P_QUEUES(val), | |
| QSYS_PREEMPTION_CFG_P_QUEUES_M, | |
| QSYS_PREEMPTION_CFG, port); | |
| } | |
| void ocelot_port_change_fp(struct ocelot *ocelot, int port, | |
| unsigned long preemptible_tcs) | |
| { | |
| struct ocelot_mm_state *mm = &ocelot->mm[port]; | |
| lockdep_assert_held(&ocelot->fwd_domain_lock); | |
| if (mm->preemptible_tcs == preemptible_tcs) | |
| return; | |
| mm->preemptible_tcs = preemptible_tcs; | |
| ocelot_port_update_active_preemptible_tcs(ocelot, port); | |
| } | |
| static void ocelot_mm_update_port_status(struct ocelot *ocelot, int port) | |
| { | |
| struct ocelot_port *ocelot_port = ocelot->ports[port]; | |
| struct ocelot_mm_state *mm = &ocelot->mm[port]; | |
| enum ethtool_mm_verify_status verify_status; | |
| u32 val, ack = 0; | |
| if (!mm->tx_enabled) | |
| return; | |
| val = ocelot_port_readl(ocelot_port, DEV_MM_STATUS); | |
| verify_status = ocelot_mm_verify_status(val); | |
| if (mm->verify_status != verify_status) { | |
| dev_dbg(ocelot->dev, | |
| "Port %d MAC Merge verification state %s\n", | |
| port, mm_verify_state_to_string(verify_status)); | |
| mm->verify_status = verify_status; | |
| } | |
| if (val & DEV_MM_STAT_MM_STATUS_PRMPT_ACTIVE_STICKY) { | |
| mm->tx_active = !!(val & DEV_MM_STAT_MM_STATUS_PRMPT_ACTIVE_STATUS); | |
| dev_dbg(ocelot->dev, "Port %d TX preemption %s\n", | |
| port, mm->tx_active ? "active" : "inactive"); | |
| ocelot_port_update_active_preemptible_tcs(ocelot, port); | |
| ack |= DEV_MM_STAT_MM_STATUS_PRMPT_ACTIVE_STICKY; | |
| } | |
| if (val & DEV_MM_STAT_MM_STATUS_UNEXP_RX_PFRM_STICKY) { | |
| dev_err(ocelot->dev, | |
| "Unexpected P-frame received on port %d while verification was unsuccessful or not yet verified\n", | |
| port); | |
| ack |= DEV_MM_STAT_MM_STATUS_UNEXP_RX_PFRM_STICKY; | |
| } | |
| if (val & DEV_MM_STAT_MM_STATUS_UNEXP_TX_PFRM_STICKY) { | |
| dev_err(ocelot->dev, | |
| "Unexpected P-frame requested to be transmitted on port %d while verification was unsuccessful or not yet verified, or MM_TX_ENA=0\n", | |
| port); | |
| ack |= DEV_MM_STAT_MM_STATUS_UNEXP_TX_PFRM_STICKY; | |
| } | |
| if (ack) | |
| ocelot_port_writel(ocelot_port, ack, DEV_MM_STATUS); | |
| } | |
| void ocelot_mm_irq(struct ocelot *ocelot) | |
| { | |
| int port; | |
| mutex_lock(&ocelot->fwd_domain_lock); | |
| for (port = 0; port < ocelot->num_phys_ports; port++) | |
| ocelot_mm_update_port_status(ocelot, port); | |
| mutex_unlock(&ocelot->fwd_domain_lock); | |
| } | |
| EXPORT_SYMBOL_GPL(ocelot_mm_irq); | |
| int ocelot_port_set_mm(struct ocelot *ocelot, int port, | |
| struct ethtool_mm_cfg *cfg, | |
| struct netlink_ext_ack *extack) | |
| { | |
| struct ocelot_port *ocelot_port = ocelot->ports[port]; | |
| u32 mm_enable = 0, verify_disable = 0, add_frag_size; | |
| struct ocelot_mm_state *mm; | |
| int err; | |
| if (!ocelot->mm_supported) | |
| return -EOPNOTSUPP; | |
| mm = &ocelot->mm[port]; | |
| err = ethtool_mm_frag_size_min_to_add(cfg->tx_min_frag_size, | |
| &add_frag_size, extack); | |
| if (err) | |
| return err; | |
| if (cfg->pmac_enabled) | |
| mm_enable |= DEV_MM_CONFIG_ENABLE_CONFIG_MM_RX_ENA; | |
| if (cfg->tx_enabled) | |
| mm_enable |= DEV_MM_CONFIG_ENABLE_CONFIG_MM_TX_ENA; | |
| if (!cfg->verify_enabled) | |
| verify_disable = DEV_MM_CONFIG_VERIF_CONFIG_PRM_VERIFY_DIS; | |
| mutex_lock(&ocelot->fwd_domain_lock); | |
| ocelot_port_rmwl(ocelot_port, mm_enable, | |
| DEV_MM_CONFIG_ENABLE_CONFIG_MM_TX_ENA | | |
| DEV_MM_CONFIG_ENABLE_CONFIG_MM_RX_ENA, | |
| DEV_MM_ENABLE_CONFIG); | |
| ocelot_port_rmwl(ocelot_port, verify_disable | | |
| DEV_MM_CONFIG_VERIF_CONFIG_PRM_VERIFY_TIME(cfg->verify_time), | |
| DEV_MM_CONFIG_VERIF_CONFIG_PRM_VERIFY_DIS | | |
| DEV_MM_CONFIG_VERIF_CONFIG_PRM_VERIFY_TIME_M, | |
| DEV_MM_VERIF_CONFIG); | |
| ocelot_rmw_rix(ocelot, | |
| QSYS_PREEMPTION_CFG_MM_ADD_FRAG_SIZE(add_frag_size), | |
| QSYS_PREEMPTION_CFG_MM_ADD_FRAG_SIZE_M, | |
| QSYS_PREEMPTION_CFG, | |
| port); | |
| /* The switch will emit an IRQ when TX is disabled, to notify that it | |
| * has become inactive. We optimize ocelot_mm_update_port_status() to | |
| * not bother processing MM IRQs at all for ports with TX disabled, | |
| * but we need to ACK this IRQ now, while mm->tx_enabled is still set, | |
| * otherwise we get an IRQ storm. | |
| */ | |
| if (mm->tx_enabled && !cfg->tx_enabled) { | |
| ocelot_mm_update_port_status(ocelot, port); | |
| WARN_ON(mm->tx_active); | |
| } | |
| mm->tx_enabled = cfg->tx_enabled; | |
| mutex_unlock(&ocelot->fwd_domain_lock); | |
| return 0; | |
| } | |
| EXPORT_SYMBOL_GPL(ocelot_port_set_mm); | |
| int ocelot_port_get_mm(struct ocelot *ocelot, int port, | |
| struct ethtool_mm_state *state) | |
| { | |
| struct ocelot_port *ocelot_port = ocelot->ports[port]; | |
| struct ocelot_mm_state *mm; | |
| u32 val, add_frag_size; | |
| if (!ocelot->mm_supported) | |
| return -EOPNOTSUPP; | |
| mm = &ocelot->mm[port]; | |
| mutex_lock(&ocelot->fwd_domain_lock); | |
| val = ocelot_port_readl(ocelot_port, DEV_MM_ENABLE_CONFIG); | |
| state->pmac_enabled = !!(val & DEV_MM_CONFIG_ENABLE_CONFIG_MM_RX_ENA); | |
| state->tx_enabled = !!(val & DEV_MM_CONFIG_ENABLE_CONFIG_MM_TX_ENA); | |
| val = ocelot_port_readl(ocelot_port, DEV_MM_VERIF_CONFIG); | |
| state->verify_enabled = !(val & DEV_MM_CONFIG_VERIF_CONFIG_PRM_VERIFY_DIS); | |
| state->verify_time = DEV_MM_CONFIG_VERIF_CONFIG_PRM_VERIFY_TIME_X(val); | |
| state->max_verify_time = 128; | |
| val = ocelot_read_rix(ocelot, QSYS_PREEMPTION_CFG, port); | |
| add_frag_size = QSYS_PREEMPTION_CFG_MM_ADD_FRAG_SIZE_X(val); | |
| state->tx_min_frag_size = ethtool_mm_frag_size_add_to_min(add_frag_size); | |
| state->rx_min_frag_size = ETH_ZLEN; | |
| ocelot_mm_update_port_status(ocelot, port); | |
| state->verify_status = mm->verify_status; | |
| state->tx_active = mm->tx_active; | |
| mutex_unlock(&ocelot->fwd_domain_lock); | |
| return 0; | |
| } | |
| EXPORT_SYMBOL_GPL(ocelot_port_get_mm); | |
| int ocelot_mm_init(struct ocelot *ocelot) | |
| { | |
| struct ocelot_port *ocelot_port; | |
| struct ocelot_mm_state *mm; | |
| int port; | |
| if (!ocelot->mm_supported) | |
| return 0; | |
| ocelot->mm = devm_kcalloc(ocelot->dev, ocelot->num_phys_ports, | |
| sizeof(*ocelot->mm), GFP_KERNEL); | |
| if (!ocelot->mm) | |
| return -ENOMEM; | |
| for (port = 0; port < ocelot->num_phys_ports; port++) { | |
| u32 val; | |
| mm = &ocelot->mm[port]; | |
| ocelot_port = ocelot->ports[port]; | |
| /* Update initial status variable for the | |
| * verification state machine | |
| */ | |
| val = ocelot_port_readl(ocelot_port, DEV_MM_STATUS); | |
| mm->verify_status = ocelot_mm_verify_status(val); | |
| } | |
| return 0; | |
| } |