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 
15 static const char *
mm_verify_state_to_string(enum ethtool_mm_verify_status state)16 mm_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 
ocelot_mm_verify_status(u32 val)34 static 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 
ocelot_port_update_active_preemptible_tcs(struct ocelot * ocelot,int port)52 void 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 
ocelot_port_change_fp(struct ocelot * ocelot,int port,unsigned long preemptible_tcs)90 void 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 
ocelot_mm_update_port_status(struct ocelot * ocelot,int port)105 static 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 
ocelot_mm_irq(struct ocelot * ocelot)155 void 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 }
166 EXPORT_SYMBOL_GPL(ocelot_mm_irq);
167 
ocelot_port_set_mm(struct ocelot * ocelot,int port,struct ethtool_mm_cfg * cfg,struct netlink_ext_ack * extack)168 int 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 }
232 EXPORT_SYMBOL_GPL(ocelot_port_set_mm);
233 
ocelot_port_get_mm(struct ocelot * ocelot,int port,struct ethtool_mm_state * state)234 int 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 }
270 EXPORT_SYMBOL_GPL(ocelot_port_get_mm);
271 
ocelot_mm_init(struct ocelot * ocelot)272 int 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 }
301