Linux Audio

Check our new training course

Loading...
Note: File does not exist in v4.17.
  1// SPDX-License-Identifier: (GPL-2.0 OR MIT)
  2/*
  3 * Hardware library for MAC Merge Layer and Frame Preemption on TSN-capable
  4 * switches (VSC9959)
  5 *
  6 * Copyright 2022-2023 NXP
  7 */
  8#include <linux/ethtool.h>
  9#include <soc/mscc/ocelot.h>
 10#include <soc/mscc/ocelot_dev.h>
 11#include <soc/mscc/ocelot_qsys.h>
 12
 13#include "ocelot.h"
 14
 15static const char *
 16mm_verify_state_to_string(enum ethtool_mm_verify_status state)
 17{
 18	switch (state) {
 19	case ETHTOOL_MM_VERIFY_STATUS_INITIAL:
 20		return "INITIAL";
 21	case ETHTOOL_MM_VERIFY_STATUS_VERIFYING:
 22		return "VERIFYING";
 23	case ETHTOOL_MM_VERIFY_STATUS_SUCCEEDED:
 24		return "SUCCEEDED";
 25	case ETHTOOL_MM_VERIFY_STATUS_FAILED:
 26		return "FAILED";
 27	case ETHTOOL_MM_VERIFY_STATUS_DISABLED:
 28		return "DISABLED";
 29	default:
 30		return "UNKNOWN";
 31	}
 32}
 33
 34static enum ethtool_mm_verify_status ocelot_mm_verify_status(u32 val)
 35{
 36	switch (DEV_MM_STAT_MM_STATUS_PRMPT_VERIFY_STATE_X(val)) {
 37	case 0:
 38		return ETHTOOL_MM_VERIFY_STATUS_INITIAL;
 39	case 1:
 40		return ETHTOOL_MM_VERIFY_STATUS_VERIFYING;
 41	case 2:
 42		return ETHTOOL_MM_VERIFY_STATUS_SUCCEEDED;
 43	case 3:
 44		return ETHTOOL_MM_VERIFY_STATUS_FAILED;
 45	case 4:
 46		return ETHTOOL_MM_VERIFY_STATUS_DISABLED;
 47	default:
 48		return ETHTOOL_MM_VERIFY_STATUS_UNKNOWN;
 49	}
 50}
 51
 52void ocelot_port_update_active_preemptible_tcs(struct ocelot *ocelot, int port)
 53{
 54	struct ocelot_port *ocelot_port = ocelot->ports[port];
 55	struct ocelot_mm_state *mm = &ocelot->mm[port];
 56	u32 val = 0;
 57
 58	lockdep_assert_held(&ocelot->fwd_domain_lock);
 59
 60	/* Only commit preemptible TCs when MAC Merge is active.
 61	 * On NXP LS1028A, when using QSGMII, the port hangs if transmitting
 62	 * preemptible frames at any other link speed than gigabit, so avoid
 63	 * preemption at lower speeds in this PHY mode.
 64	 */
 65	if ((ocelot_port->phy_mode != PHY_INTERFACE_MODE_QSGMII ||
 66	     ocelot_port->speed == SPEED_1000) && mm->tx_active)
 67		val = mm->preemptible_tcs;
 68
 69	/* Cut through switching doesn't work for preemptible priorities,
 70	 * so first make sure it is disabled. Also, changing the preemptible
 71	 * TCs affects the oversized frame dropping logic, so that needs to be
 72	 * re-triggered. And since tas_guard_bands_update() also implicitly
 73	 * calls cut_through_fwd(), we don't need to explicitly call it.
 74	 */
 75	mm->active_preemptible_tcs = val;
 76	ocelot->ops->tas_guard_bands_update(ocelot, port);
 77
 78	dev_dbg(ocelot->dev,
 79		"port %d %s/%s, MM TX %s, preemptible TCs 0x%x, active 0x%x\n",
 80		port, phy_modes(ocelot_port->phy_mode),
 81		phy_speed_to_str(ocelot_port->speed),
 82		mm->tx_active ? "active" : "inactive", mm->preemptible_tcs,
 83		mm->active_preemptible_tcs);
 84
 85	ocelot_rmw_rix(ocelot, QSYS_PREEMPTION_CFG_P_QUEUES(val),
 86		       QSYS_PREEMPTION_CFG_P_QUEUES_M,
 87		       QSYS_PREEMPTION_CFG, port);
 88}
 89
 90void ocelot_port_change_fp(struct ocelot *ocelot, int port,
 91			   unsigned long preemptible_tcs)
 92{
 93	struct ocelot_mm_state *mm = &ocelot->mm[port];
 94
 95	lockdep_assert_held(&ocelot->fwd_domain_lock);
 96
 97	if (mm->preemptible_tcs == preemptible_tcs)
 98		return;
 99
100	mm->preemptible_tcs = preemptible_tcs;
101
102	ocelot_port_update_active_preemptible_tcs(ocelot, port);
103}
104
105static void ocelot_mm_update_port_status(struct ocelot *ocelot, int port)
106{
107	struct ocelot_port *ocelot_port = ocelot->ports[port];
108	struct ocelot_mm_state *mm = &ocelot->mm[port];
109	enum ethtool_mm_verify_status verify_status;
110	u32 val, ack = 0;
111
112	if (!mm->tx_enabled)
113		return;
114
115	val = ocelot_port_readl(ocelot_port, DEV_MM_STATUS);
116
117	verify_status = ocelot_mm_verify_status(val);
118	if (mm->verify_status != verify_status) {
119		dev_dbg(ocelot->dev,
120			"Port %d MAC Merge verification state %s\n",
121			port, mm_verify_state_to_string(verify_status));
122		mm->verify_status = verify_status;
123	}
124
125	if (val & DEV_MM_STAT_MM_STATUS_PRMPT_ACTIVE_STICKY) {
126		mm->tx_active = !!(val & DEV_MM_STAT_MM_STATUS_PRMPT_ACTIVE_STATUS);
127
128		dev_dbg(ocelot->dev, "Port %d TX preemption %s\n",
129			port, mm->tx_active ? "active" : "inactive");
130		ocelot_port_update_active_preemptible_tcs(ocelot, port);
131
132		ack |= DEV_MM_STAT_MM_STATUS_PRMPT_ACTIVE_STICKY;
133	}
134
135	if (val & DEV_MM_STAT_MM_STATUS_UNEXP_RX_PFRM_STICKY) {
136		dev_err(ocelot->dev,
137			"Unexpected P-frame received on port %d while verification was unsuccessful or not yet verified\n",
138			port);
139
140		ack |= DEV_MM_STAT_MM_STATUS_UNEXP_RX_PFRM_STICKY;
141	}
142
143	if (val & DEV_MM_STAT_MM_STATUS_UNEXP_TX_PFRM_STICKY) {
144		dev_err(ocelot->dev,
145			"Unexpected P-frame requested to be transmitted on port %d while verification was unsuccessful or not yet verified, or MM_TX_ENA=0\n",
146			port);
147
148		ack |= DEV_MM_STAT_MM_STATUS_UNEXP_TX_PFRM_STICKY;
149	}
150
151	if (ack)
152		ocelot_port_writel(ocelot_port, ack, DEV_MM_STATUS);
153}
154
155void ocelot_mm_irq(struct ocelot *ocelot)
156{
157	int port;
158
159	mutex_lock(&ocelot->fwd_domain_lock);
160
161	for (port = 0; port < ocelot->num_phys_ports; port++)
162		ocelot_mm_update_port_status(ocelot, port);
163
164	mutex_unlock(&ocelot->fwd_domain_lock);
165}
166EXPORT_SYMBOL_GPL(ocelot_mm_irq);
167
168int ocelot_port_set_mm(struct ocelot *ocelot, int port,
169		       struct ethtool_mm_cfg *cfg,
170		       struct netlink_ext_ack *extack)
171{
172	struct ocelot_port *ocelot_port = ocelot->ports[port];
173	u32 mm_enable = 0, verify_disable = 0, add_frag_size;
174	struct ocelot_mm_state *mm;
175	int err;
176
177	if (!ocelot->mm_supported)
178		return -EOPNOTSUPP;
179
180	mm = &ocelot->mm[port];
181
182	err = ethtool_mm_frag_size_min_to_add(cfg->tx_min_frag_size,
183					      &add_frag_size, extack);
184	if (err)
185		return err;
186
187	if (cfg->pmac_enabled)
188		mm_enable |= DEV_MM_CONFIG_ENABLE_CONFIG_MM_RX_ENA;
189
190	if (cfg->tx_enabled)
191		mm_enable |= DEV_MM_CONFIG_ENABLE_CONFIG_MM_TX_ENA;
192
193	if (!cfg->verify_enabled)
194		verify_disable = DEV_MM_CONFIG_VERIF_CONFIG_PRM_VERIFY_DIS;
195
196	mutex_lock(&ocelot->fwd_domain_lock);
197
198	ocelot_port_rmwl(ocelot_port, mm_enable,
199			 DEV_MM_CONFIG_ENABLE_CONFIG_MM_TX_ENA |
200			 DEV_MM_CONFIG_ENABLE_CONFIG_MM_RX_ENA,
201			 DEV_MM_ENABLE_CONFIG);
202
203	ocelot_port_rmwl(ocelot_port, verify_disable |
204			 DEV_MM_CONFIG_VERIF_CONFIG_PRM_VERIFY_TIME(cfg->verify_time),
205			 DEV_MM_CONFIG_VERIF_CONFIG_PRM_VERIFY_DIS |
206			 DEV_MM_CONFIG_VERIF_CONFIG_PRM_VERIFY_TIME_M,
207			 DEV_MM_VERIF_CONFIG);
208
209	ocelot_rmw_rix(ocelot,
210		       QSYS_PREEMPTION_CFG_MM_ADD_FRAG_SIZE(add_frag_size),
211		       QSYS_PREEMPTION_CFG_MM_ADD_FRAG_SIZE_M,
212		       QSYS_PREEMPTION_CFG,
213		       port);
214
215	/* The switch will emit an IRQ when TX is disabled, to notify that it
216	 * has become inactive. We optimize ocelot_mm_update_port_status() to
217	 * not bother processing MM IRQs at all for ports with TX disabled,
218	 * but we need to ACK this IRQ now, while mm->tx_enabled is still set,
219	 * otherwise we get an IRQ storm.
220	 */
221	if (mm->tx_enabled && !cfg->tx_enabled) {
222		ocelot_mm_update_port_status(ocelot, port);
223		WARN_ON(mm->tx_active);
224	}
225
226	mm->tx_enabled = cfg->tx_enabled;
227
228	mutex_unlock(&ocelot->fwd_domain_lock);
229
230	return 0;
231}
232EXPORT_SYMBOL_GPL(ocelot_port_set_mm);
233
234int ocelot_port_get_mm(struct ocelot *ocelot, int port,
235		       struct ethtool_mm_state *state)
236{
237	struct ocelot_port *ocelot_port = ocelot->ports[port];
238	struct ocelot_mm_state *mm;
239	u32 val, add_frag_size;
240
241	if (!ocelot->mm_supported)
242		return -EOPNOTSUPP;
243
244	mm = &ocelot->mm[port];
245
246	mutex_lock(&ocelot->fwd_domain_lock);
247
248	val = ocelot_port_readl(ocelot_port, DEV_MM_ENABLE_CONFIG);
249	state->pmac_enabled = !!(val & DEV_MM_CONFIG_ENABLE_CONFIG_MM_RX_ENA);
250	state->tx_enabled = !!(val & DEV_MM_CONFIG_ENABLE_CONFIG_MM_TX_ENA);
251
252	val = ocelot_port_readl(ocelot_port, DEV_MM_VERIF_CONFIG);
253	state->verify_enabled = !(val & DEV_MM_CONFIG_VERIF_CONFIG_PRM_VERIFY_DIS);
254	state->verify_time = DEV_MM_CONFIG_VERIF_CONFIG_PRM_VERIFY_TIME_X(val);
255	state->max_verify_time = 128;
256
257	val = ocelot_read_rix(ocelot, QSYS_PREEMPTION_CFG, port);
258	add_frag_size = QSYS_PREEMPTION_CFG_MM_ADD_FRAG_SIZE_X(val);
259	state->tx_min_frag_size = ethtool_mm_frag_size_add_to_min(add_frag_size);
260	state->rx_min_frag_size = ETH_ZLEN;
261
262	ocelot_mm_update_port_status(ocelot, port);
263	state->verify_status = mm->verify_status;
264	state->tx_active = mm->tx_active;
265
266	mutex_unlock(&ocelot->fwd_domain_lock);
267
268	return 0;
269}
270EXPORT_SYMBOL_GPL(ocelot_port_get_mm);
271
272int ocelot_mm_init(struct ocelot *ocelot)
273{
274	struct ocelot_port *ocelot_port;
275	struct ocelot_mm_state *mm;
276	int port;
277
278	if (!ocelot->mm_supported)
279		return 0;
280
281	ocelot->mm = devm_kcalloc(ocelot->dev, ocelot->num_phys_ports,
282				  sizeof(*ocelot->mm), GFP_KERNEL);
283	if (!ocelot->mm)
284		return -ENOMEM;
285
286	for (port = 0; port < ocelot->num_phys_ports; port++) {
287		u32 val;
288
289		mm = &ocelot->mm[port];
290		ocelot_port = ocelot->ports[port];
291
292		/* Update initial status variable for the
293		 * verification state machine
294		 */
295		val = ocelot_port_readl(ocelot_port, DEV_MM_STATUS);
296		mm->verify_status = ocelot_mm_verify_status(val);
297	}
298
299	return 0;
300}