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(port: ocelot_port, reg: 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(port: ocelot_port, val: ack, reg: 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(lock: &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(val_min: cfg->tx_min_frag_size,
183 val_add: &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(port: ocelot_port, val: mm_enable,
199 DEV_MM_CONFIG_ENABLE_CONFIG_MM_TX_ENA |
200 DEV_MM_CONFIG_ENABLE_CONFIG_MM_RX_ENA,
201 reg: DEV_MM_ENABLE_CONFIG);
202
203 ocelot_port_rmwl(port: ocelot_port, val: 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 reg: 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(lock: &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(port: ocelot_port, reg: 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(port: ocelot_port, reg: 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(val_add: 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(lock: &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(dev: ocelot->dev, n: ocelot->num_phys_ports,
282 size: 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(port: ocelot_port, reg: DEV_MM_STATUS);
296 mm->verify_status = ocelot_mm_verify_status(val);
297 }
298
299 return 0;
300}
301

source code of linux/drivers/net/ethernet/mscc/ocelot_mm.c