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 * |
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 | |
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 | |
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 | |
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 | |
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(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 | |
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(lock: &ocelot->fwd_domain_lock); |
165 | } |
166 | EXPORT_SYMBOL_GPL(ocelot_mm_irq); |
167 | |
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(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 | } |
232 | EXPORT_SYMBOL_GPL(ocelot_port_set_mm); |
233 | |
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(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 | } |
270 | EXPORT_SYMBOL_GPL(ocelot_port_get_mm); |
271 | |
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(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 | |