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