1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (C) 2022 Schneider-Electric |
4 | * |
5 | * Clément Léger <clement.leger@bootlin.com> |
6 | */ |
7 | |
8 | #include <linux/clk.h> |
9 | #include <linux/etherdevice.h> |
10 | #include <linux/if_bridge.h> |
11 | #include <linux/if_ether.h> |
12 | #include <linux/kernel.h> |
13 | #include <linux/module.h> |
14 | #include <linux/of.h> |
15 | #include <linux/of_mdio.h> |
16 | #include <net/dsa.h> |
17 | |
18 | #include "rzn1_a5psw.h" |
19 | |
20 | struct a5psw_stats { |
21 | u16 offset; |
22 | const char name[ETH_GSTRING_LEN]; |
23 | }; |
24 | |
25 | #define STAT_DESC(_offset) { \ |
26 | .offset = A5PSW_##_offset, \ |
27 | .name = __stringify(_offset), \ |
28 | } |
29 | |
30 | static const struct a5psw_stats a5psw_stats[] = { |
31 | STAT_DESC(aFramesTransmittedOK), |
32 | STAT_DESC(aFramesReceivedOK), |
33 | STAT_DESC(aFrameCheckSequenceErrors), |
34 | STAT_DESC(aAlignmentErrors), |
35 | STAT_DESC(aOctetsTransmittedOK), |
36 | STAT_DESC(aOctetsReceivedOK), |
37 | STAT_DESC(aTxPAUSEMACCtrlFrames), |
38 | STAT_DESC(aRxPAUSEMACCtrlFrames), |
39 | STAT_DESC(ifInErrors), |
40 | STAT_DESC(ifOutErrors), |
41 | STAT_DESC(ifInUcastPkts), |
42 | STAT_DESC(ifInMulticastPkts), |
43 | STAT_DESC(ifInBroadcastPkts), |
44 | STAT_DESC(ifOutDiscards), |
45 | STAT_DESC(ifOutUcastPkts), |
46 | STAT_DESC(ifOutMulticastPkts), |
47 | STAT_DESC(ifOutBroadcastPkts), |
48 | STAT_DESC(etherStatsDropEvents), |
49 | STAT_DESC(etherStatsOctets), |
50 | STAT_DESC(etherStatsPkts), |
51 | STAT_DESC(etherStatsUndersizePkts), |
52 | STAT_DESC(etherStatsOversizePkts), |
53 | STAT_DESC(etherStatsPkts64Octets), |
54 | STAT_DESC(etherStatsPkts65to127Octets), |
55 | STAT_DESC(etherStatsPkts128to255Octets), |
56 | STAT_DESC(etherStatsPkts256to511Octets), |
57 | STAT_DESC(etherStatsPkts1024to1518Octets), |
58 | STAT_DESC(etherStatsPkts1519toXOctets), |
59 | STAT_DESC(etherStatsJabbers), |
60 | STAT_DESC(etherStatsFragments), |
61 | STAT_DESC(VLANReceived), |
62 | STAT_DESC(VLANTransmitted), |
63 | STAT_DESC(aDeferred), |
64 | STAT_DESC(aMultipleCollisions), |
65 | STAT_DESC(aSingleCollisions), |
66 | STAT_DESC(aLateCollisions), |
67 | STAT_DESC(aExcessiveCollisions), |
68 | STAT_DESC(aCarrierSenseErrors), |
69 | }; |
70 | |
71 | static void a5psw_reg_writel(struct a5psw *a5psw, int offset, u32 value) |
72 | { |
73 | writel(val: value, addr: a5psw->base + offset); |
74 | } |
75 | |
76 | static u32 a5psw_reg_readl(struct a5psw *a5psw, int offset) |
77 | { |
78 | return readl(addr: a5psw->base + offset); |
79 | } |
80 | |
81 | static void a5psw_reg_rmw(struct a5psw *a5psw, int offset, u32 mask, u32 val) |
82 | { |
83 | u32 reg; |
84 | |
85 | spin_lock(lock: &a5psw->reg_lock); |
86 | |
87 | reg = a5psw_reg_readl(a5psw, offset); |
88 | reg &= ~mask; |
89 | reg |= val; |
90 | a5psw_reg_writel(a5psw, offset, value: reg); |
91 | |
92 | spin_unlock(lock: &a5psw->reg_lock); |
93 | } |
94 | |
95 | static enum dsa_tag_protocol a5psw_get_tag_protocol(struct dsa_switch *ds, |
96 | int port, |
97 | enum dsa_tag_protocol mp) |
98 | { |
99 | return DSA_TAG_PROTO_RZN1_A5PSW; |
100 | } |
101 | |
102 | static void a5psw_port_pattern_set(struct a5psw *a5psw, int port, int pattern, |
103 | bool enable) |
104 | { |
105 | u32 rx_match = 0; |
106 | |
107 | if (enable) |
108 | rx_match |= A5PSW_RXMATCH_CONFIG_PATTERN(pattern); |
109 | |
110 | a5psw_reg_rmw(a5psw, A5PSW_RXMATCH_CONFIG(port), |
111 | A5PSW_RXMATCH_CONFIG_PATTERN(pattern), val: rx_match); |
112 | } |
113 | |
114 | static void a5psw_port_mgmtfwd_set(struct a5psw *a5psw, int port, bool enable) |
115 | { |
116 | /* Enable "management forward" pattern matching, this will forward |
117 | * packets from this port only towards the management port and thus |
118 | * isolate the port. |
119 | */ |
120 | a5psw_port_pattern_set(a5psw, port, A5PSW_PATTERN_MGMTFWD, enable); |
121 | } |
122 | |
123 | static void a5psw_port_tx_enable(struct a5psw *a5psw, int port, bool enable) |
124 | { |
125 | u32 mask = A5PSW_PORT_ENA_TX(port); |
126 | u32 reg = enable ? mask : 0; |
127 | |
128 | /* Even though the port TX is disabled through TXENA bit in the |
129 | * PORT_ENA register, it can still send BPDUs. This depends on the tag |
130 | * configuration added when sending packets from the CPU port to the |
131 | * switch port. Indeed, when using forced forwarding without filtering, |
132 | * even disabled ports will be able to send packets that are tagged. |
133 | * This allows to implement STP support when ports are in a state where |
134 | * forwarding traffic should be stopped but BPDUs should still be sent. |
135 | */ |
136 | a5psw_reg_rmw(a5psw, A5PSW_PORT_ENA, mask, val: reg); |
137 | } |
138 | |
139 | static void a5psw_port_enable_set(struct a5psw *a5psw, int port, bool enable) |
140 | { |
141 | u32 port_ena = 0; |
142 | |
143 | if (enable) |
144 | port_ena |= A5PSW_PORT_ENA_TX_RX(port); |
145 | |
146 | a5psw_reg_rmw(a5psw, A5PSW_PORT_ENA, A5PSW_PORT_ENA_TX_RX(port), |
147 | val: port_ena); |
148 | } |
149 | |
150 | static int a5psw_lk_execute_ctrl(struct a5psw *a5psw, u32 *ctrl) |
151 | { |
152 | int ret; |
153 | |
154 | a5psw_reg_writel(a5psw, A5PSW_LK_ADDR_CTRL, value: *ctrl); |
155 | |
156 | ret = readl_poll_timeout(a5psw->base + A5PSW_LK_ADDR_CTRL, *ctrl, |
157 | !(*ctrl & A5PSW_LK_ADDR_CTRL_BUSY), |
158 | A5PSW_LK_BUSY_USEC_POLL, A5PSW_CTRL_TIMEOUT); |
159 | if (ret) |
160 | dev_err(a5psw->dev, "LK_CTRL timeout waiting for BUSY bit\n" ); |
161 | |
162 | return ret; |
163 | } |
164 | |
165 | static void a5psw_port_fdb_flush(struct a5psw *a5psw, int port) |
166 | { |
167 | u32 ctrl = A5PSW_LK_ADDR_CTRL_DELETE_PORT | BIT(port); |
168 | |
169 | mutex_lock(&a5psw->lk_lock); |
170 | a5psw_lk_execute_ctrl(a5psw, ctrl: &ctrl); |
171 | mutex_unlock(lock: &a5psw->lk_lock); |
172 | } |
173 | |
174 | static void a5psw_port_authorize_set(struct a5psw *a5psw, int port, |
175 | bool authorize) |
176 | { |
177 | u32 reg = a5psw_reg_readl(a5psw, A5PSW_AUTH_PORT(port)); |
178 | |
179 | if (authorize) |
180 | reg |= A5PSW_AUTH_PORT_AUTHORIZED; |
181 | else |
182 | reg &= ~A5PSW_AUTH_PORT_AUTHORIZED; |
183 | |
184 | a5psw_reg_writel(a5psw, A5PSW_AUTH_PORT(port), value: reg); |
185 | } |
186 | |
187 | static void a5psw_port_disable(struct dsa_switch *ds, int port) |
188 | { |
189 | struct a5psw *a5psw = ds->priv; |
190 | |
191 | a5psw_port_authorize_set(a5psw, port, authorize: false); |
192 | a5psw_port_enable_set(a5psw, port, enable: false); |
193 | } |
194 | |
195 | static int a5psw_port_enable(struct dsa_switch *ds, int port, |
196 | struct phy_device *phy) |
197 | { |
198 | struct a5psw *a5psw = ds->priv; |
199 | |
200 | a5psw_port_authorize_set(a5psw, port, authorize: true); |
201 | a5psw_port_enable_set(a5psw, port, enable: true); |
202 | |
203 | return 0; |
204 | } |
205 | |
206 | static int a5psw_port_change_mtu(struct dsa_switch *ds, int port, int new_mtu) |
207 | { |
208 | struct a5psw *a5psw = ds->priv; |
209 | |
210 | new_mtu += ETH_HLEN + A5PSW_EXTRA_MTU_LEN + ETH_FCS_LEN; |
211 | a5psw_reg_writel(a5psw, A5PSW_FRM_LENGTH(port), value: new_mtu); |
212 | |
213 | return 0; |
214 | } |
215 | |
216 | static int a5psw_port_max_mtu(struct dsa_switch *ds, int port) |
217 | { |
218 | return A5PSW_MAX_MTU; |
219 | } |
220 | |
221 | static void a5psw_phylink_get_caps(struct dsa_switch *ds, int port, |
222 | struct phylink_config *config) |
223 | { |
224 | unsigned long *intf = config->supported_interfaces; |
225 | |
226 | config->mac_capabilities = MAC_1000FD; |
227 | |
228 | if (dsa_is_cpu_port(ds, p: port)) { |
229 | /* GMII is used internally and GMAC2 is connected to the switch |
230 | * using 1000Mbps Full-Duplex mode only (cf ethernet manual) |
231 | */ |
232 | __set_bit(PHY_INTERFACE_MODE_GMII, intf); |
233 | } else { |
234 | config->mac_capabilities |= MAC_100 | MAC_10; |
235 | phy_interface_set_rgmii(intf); |
236 | __set_bit(PHY_INTERFACE_MODE_RMII, intf); |
237 | __set_bit(PHY_INTERFACE_MODE_MII, intf); |
238 | } |
239 | } |
240 | |
241 | static struct phylink_pcs * |
242 | a5psw_phylink_mac_select_pcs(struct dsa_switch *ds, int port, |
243 | phy_interface_t interface) |
244 | { |
245 | struct dsa_port *dp = dsa_to_port(ds, p: port); |
246 | struct a5psw *a5psw = ds->priv; |
247 | |
248 | if (!dsa_port_is_cpu(port: dp) && a5psw->pcs[port]) |
249 | return a5psw->pcs[port]; |
250 | |
251 | return NULL; |
252 | } |
253 | |
254 | static void a5psw_phylink_mac_link_down(struct dsa_switch *ds, int port, |
255 | unsigned int mode, |
256 | phy_interface_t interface) |
257 | { |
258 | struct a5psw *a5psw = ds->priv; |
259 | u32 cmd_cfg; |
260 | |
261 | cmd_cfg = a5psw_reg_readl(a5psw, A5PSW_CMD_CFG(port)); |
262 | cmd_cfg &= ~(A5PSW_CMD_CFG_RX_ENA | A5PSW_CMD_CFG_TX_ENA); |
263 | a5psw_reg_writel(a5psw, A5PSW_CMD_CFG(port), value: cmd_cfg); |
264 | } |
265 | |
266 | static void a5psw_phylink_mac_link_up(struct dsa_switch *ds, int port, |
267 | unsigned int mode, |
268 | phy_interface_t interface, |
269 | struct phy_device *phydev, int speed, |
270 | int duplex, bool tx_pause, bool rx_pause) |
271 | { |
272 | u32 cmd_cfg = A5PSW_CMD_CFG_RX_ENA | A5PSW_CMD_CFG_TX_ENA | |
273 | A5PSW_CMD_CFG_TX_CRC_APPEND; |
274 | struct a5psw *a5psw = ds->priv; |
275 | |
276 | if (speed == SPEED_1000) |
277 | cmd_cfg |= A5PSW_CMD_CFG_ETH_SPEED; |
278 | |
279 | if (duplex == DUPLEX_HALF) |
280 | cmd_cfg |= A5PSW_CMD_CFG_HD_ENA; |
281 | |
282 | cmd_cfg |= A5PSW_CMD_CFG_CNTL_FRM_ENA; |
283 | |
284 | if (!rx_pause) |
285 | cmd_cfg &= ~A5PSW_CMD_CFG_PAUSE_IGNORE; |
286 | |
287 | a5psw_reg_writel(a5psw, A5PSW_CMD_CFG(port), value: cmd_cfg); |
288 | } |
289 | |
290 | static int a5psw_set_ageing_time(struct dsa_switch *ds, unsigned int msecs) |
291 | { |
292 | struct a5psw *a5psw = ds->priv; |
293 | unsigned long rate; |
294 | u64 max, tmp; |
295 | u32 agetime; |
296 | |
297 | rate = clk_get_rate(clk: a5psw->clk); |
298 | max = div64_ul(((u64)A5PSW_LK_AGETIME_MASK * A5PSW_TABLE_ENTRIES * 1024), |
299 | rate) * 1000; |
300 | if (msecs > max) |
301 | return -EINVAL; |
302 | |
303 | tmp = div_u64(dividend: rate, MSEC_PER_SEC); |
304 | agetime = div_u64(dividend: msecs * tmp, divisor: 1024 * A5PSW_TABLE_ENTRIES); |
305 | |
306 | a5psw_reg_writel(a5psw, A5PSW_LK_AGETIME, value: agetime); |
307 | |
308 | return 0; |
309 | } |
310 | |
311 | static void a5psw_port_learning_set(struct a5psw *a5psw, int port, bool learn) |
312 | { |
313 | u32 mask = A5PSW_INPUT_LEARN_DIS(port); |
314 | u32 reg = !learn ? mask : 0; |
315 | |
316 | a5psw_reg_rmw(a5psw, A5PSW_INPUT_LEARN, mask, val: reg); |
317 | } |
318 | |
319 | static void a5psw_port_rx_block_set(struct a5psw *a5psw, int port, bool block) |
320 | { |
321 | u32 mask = A5PSW_INPUT_LEARN_BLOCK(port); |
322 | u32 reg = block ? mask : 0; |
323 | |
324 | a5psw_reg_rmw(a5psw, A5PSW_INPUT_LEARN, mask, val: reg); |
325 | } |
326 | |
327 | static void a5psw_flooding_set_resolution(struct a5psw *a5psw, int port, |
328 | bool set) |
329 | { |
330 | u8 offsets[] = {A5PSW_UCAST_DEF_MASK, A5PSW_BCAST_DEF_MASK, |
331 | A5PSW_MCAST_DEF_MASK}; |
332 | int i; |
333 | |
334 | for (i = 0; i < ARRAY_SIZE(offsets); i++) |
335 | a5psw_reg_rmw(a5psw, offset: offsets[i], BIT(port), |
336 | val: set ? BIT(port) : 0); |
337 | } |
338 | |
339 | static void a5psw_port_set_standalone(struct a5psw *a5psw, int port, |
340 | bool standalone) |
341 | { |
342 | a5psw_port_learning_set(a5psw, port, learn: !standalone); |
343 | a5psw_flooding_set_resolution(a5psw, port, set: !standalone); |
344 | a5psw_port_mgmtfwd_set(a5psw, port, enable: standalone); |
345 | } |
346 | |
347 | static int a5psw_port_bridge_join(struct dsa_switch *ds, int port, |
348 | struct dsa_bridge bridge, |
349 | bool *tx_fwd_offload, |
350 | struct netlink_ext_ack *extack) |
351 | { |
352 | struct a5psw *a5psw = ds->priv; |
353 | |
354 | /* We only support 1 bridge device */ |
355 | if (a5psw->br_dev && bridge.dev != a5psw->br_dev) { |
356 | NL_SET_ERR_MSG_MOD(extack, |
357 | "Forwarding offload supported for a single bridge" ); |
358 | return -EOPNOTSUPP; |
359 | } |
360 | |
361 | a5psw->br_dev = bridge.dev; |
362 | a5psw_port_set_standalone(a5psw, port, standalone: false); |
363 | |
364 | a5psw->bridged_ports |= BIT(port); |
365 | |
366 | return 0; |
367 | } |
368 | |
369 | static void a5psw_port_bridge_leave(struct dsa_switch *ds, int port, |
370 | struct dsa_bridge bridge) |
371 | { |
372 | struct a5psw *a5psw = ds->priv; |
373 | |
374 | a5psw->bridged_ports &= ~BIT(port); |
375 | |
376 | a5psw_port_set_standalone(a5psw, port, standalone: true); |
377 | |
378 | /* No more ports bridged */ |
379 | if (a5psw->bridged_ports == BIT(A5PSW_CPU_PORT)) |
380 | a5psw->br_dev = NULL; |
381 | } |
382 | |
383 | static int a5psw_port_pre_bridge_flags(struct dsa_switch *ds, int port, |
384 | struct switchdev_brport_flags flags, |
385 | struct netlink_ext_ack *extack) |
386 | { |
387 | if (flags.mask & ~(BR_LEARNING | BR_FLOOD | BR_MCAST_FLOOD | |
388 | BR_BCAST_FLOOD)) |
389 | return -EINVAL; |
390 | |
391 | return 0; |
392 | } |
393 | |
394 | static int |
395 | a5psw_port_bridge_flags(struct dsa_switch *ds, int port, |
396 | struct switchdev_brport_flags flags, |
397 | struct netlink_ext_ack *extack) |
398 | { |
399 | struct a5psw *a5psw = ds->priv; |
400 | u32 val; |
401 | |
402 | /* If a port is set as standalone, we do not want to be able to |
403 | * configure flooding nor learning which would result in joining the |
404 | * unique bridge. This can happen when a port leaves the bridge, in |
405 | * which case the DSA core will try to "clear" all flags for the |
406 | * standalone port (ie enable flooding, disable learning). In that case |
407 | * do not fail but do not apply the flags. |
408 | */ |
409 | if (!(a5psw->bridged_ports & BIT(port))) |
410 | return 0; |
411 | |
412 | if (flags.mask & BR_LEARNING) { |
413 | val = flags.val & BR_LEARNING ? 0 : A5PSW_INPUT_LEARN_DIS(port); |
414 | a5psw_reg_rmw(a5psw, A5PSW_INPUT_LEARN, |
415 | A5PSW_INPUT_LEARN_DIS(port), val); |
416 | } |
417 | |
418 | if (flags.mask & BR_FLOOD) { |
419 | val = flags.val & BR_FLOOD ? BIT(port) : 0; |
420 | a5psw_reg_rmw(a5psw, A5PSW_UCAST_DEF_MASK, BIT(port), val); |
421 | } |
422 | |
423 | if (flags.mask & BR_MCAST_FLOOD) { |
424 | val = flags.val & BR_MCAST_FLOOD ? BIT(port) : 0; |
425 | a5psw_reg_rmw(a5psw, A5PSW_MCAST_DEF_MASK, BIT(port), val); |
426 | } |
427 | |
428 | if (flags.mask & BR_BCAST_FLOOD) { |
429 | val = flags.val & BR_BCAST_FLOOD ? BIT(port) : 0; |
430 | a5psw_reg_rmw(a5psw, A5PSW_BCAST_DEF_MASK, BIT(port), val); |
431 | } |
432 | |
433 | return 0; |
434 | } |
435 | |
436 | static void a5psw_port_stp_state_set(struct dsa_switch *ds, int port, u8 state) |
437 | { |
438 | bool learning_enabled, rx_enabled, tx_enabled; |
439 | struct dsa_port *dp = dsa_to_port(ds, p: port); |
440 | struct a5psw *a5psw = ds->priv; |
441 | |
442 | switch (state) { |
443 | case BR_STATE_DISABLED: |
444 | case BR_STATE_BLOCKING: |
445 | case BR_STATE_LISTENING: |
446 | rx_enabled = false; |
447 | tx_enabled = false; |
448 | learning_enabled = false; |
449 | break; |
450 | case BR_STATE_LEARNING: |
451 | rx_enabled = false; |
452 | tx_enabled = false; |
453 | learning_enabled = dp->learning; |
454 | break; |
455 | case BR_STATE_FORWARDING: |
456 | rx_enabled = true; |
457 | tx_enabled = true; |
458 | learning_enabled = dp->learning; |
459 | break; |
460 | default: |
461 | dev_err(ds->dev, "invalid STP state: %d\n" , state); |
462 | return; |
463 | } |
464 | |
465 | a5psw_port_learning_set(a5psw, port, learn: learning_enabled); |
466 | a5psw_port_rx_block_set(a5psw, port, block: !rx_enabled); |
467 | a5psw_port_tx_enable(a5psw, port, enable: tx_enabled); |
468 | } |
469 | |
470 | static void a5psw_port_fast_age(struct dsa_switch *ds, int port) |
471 | { |
472 | struct a5psw *a5psw = ds->priv; |
473 | |
474 | a5psw_port_fdb_flush(a5psw, port); |
475 | } |
476 | |
477 | static int a5psw_lk_execute_lookup(struct a5psw *a5psw, union lk_data *lk_data, |
478 | u16 *entry) |
479 | { |
480 | u32 ctrl; |
481 | int ret; |
482 | |
483 | a5psw_reg_writel(a5psw, A5PSW_LK_DATA_LO, value: lk_data->lo); |
484 | a5psw_reg_writel(a5psw, A5PSW_LK_DATA_HI, value: lk_data->hi); |
485 | |
486 | ctrl = A5PSW_LK_ADDR_CTRL_LOOKUP; |
487 | ret = a5psw_lk_execute_ctrl(a5psw, ctrl: &ctrl); |
488 | if (ret) |
489 | return ret; |
490 | |
491 | *entry = ctrl & A5PSW_LK_ADDR_CTRL_ADDRESS; |
492 | |
493 | return 0; |
494 | } |
495 | |
496 | static int a5psw_port_fdb_add(struct dsa_switch *ds, int port, |
497 | const unsigned char *addr, u16 vid, |
498 | struct dsa_db db) |
499 | { |
500 | struct a5psw *a5psw = ds->priv; |
501 | union lk_data lk_data = {0}; |
502 | bool inc_learncount = false; |
503 | int ret = 0; |
504 | u16 entry; |
505 | u32 reg; |
506 | |
507 | ether_addr_copy(dst: lk_data.entry.mac, src: addr); |
508 | lk_data.entry.port_mask = BIT(port); |
509 | |
510 | mutex_lock(&a5psw->lk_lock); |
511 | |
512 | /* Set the value to be written in the lookup table */ |
513 | ret = a5psw_lk_execute_lookup(a5psw, lk_data: &lk_data, entry: &entry); |
514 | if (ret) |
515 | goto lk_unlock; |
516 | |
517 | lk_data.hi = a5psw_reg_readl(a5psw, A5PSW_LK_DATA_HI); |
518 | if (!lk_data.entry.valid) { |
519 | inc_learncount = true; |
520 | /* port_mask set to 0x1f when entry is not valid, clear it */ |
521 | lk_data.entry.port_mask = 0; |
522 | lk_data.entry.prio = 0; |
523 | } |
524 | |
525 | lk_data.entry.port_mask |= BIT(port); |
526 | lk_data.entry.is_static = 1; |
527 | lk_data.entry.valid = 1; |
528 | |
529 | a5psw_reg_writel(a5psw, A5PSW_LK_DATA_HI, value: lk_data.hi); |
530 | |
531 | reg = A5PSW_LK_ADDR_CTRL_WRITE | entry; |
532 | ret = a5psw_lk_execute_ctrl(a5psw, ctrl: ®); |
533 | if (ret) |
534 | goto lk_unlock; |
535 | |
536 | if (inc_learncount) { |
537 | reg = A5PSW_LK_LEARNCOUNT_MODE_INC; |
538 | a5psw_reg_writel(a5psw, A5PSW_LK_LEARNCOUNT, value: reg); |
539 | } |
540 | |
541 | lk_unlock: |
542 | mutex_unlock(lock: &a5psw->lk_lock); |
543 | |
544 | return ret; |
545 | } |
546 | |
547 | static int a5psw_port_fdb_del(struct dsa_switch *ds, int port, |
548 | const unsigned char *addr, u16 vid, |
549 | struct dsa_db db) |
550 | { |
551 | struct a5psw *a5psw = ds->priv; |
552 | union lk_data lk_data = {0}; |
553 | bool clear = false; |
554 | u16 entry; |
555 | u32 reg; |
556 | int ret; |
557 | |
558 | ether_addr_copy(dst: lk_data.entry.mac, src: addr); |
559 | |
560 | mutex_lock(&a5psw->lk_lock); |
561 | |
562 | ret = a5psw_lk_execute_lookup(a5psw, lk_data: &lk_data, entry: &entry); |
563 | if (ret) |
564 | goto lk_unlock; |
565 | |
566 | lk_data.hi = a5psw_reg_readl(a5psw, A5PSW_LK_DATA_HI); |
567 | |
568 | /* Our hardware does not associate any VID to the FDB entries so this |
569 | * means that if two entries were added for the same mac but for |
570 | * different VID, then, on the deletion of the first one, we would also |
571 | * delete the second one. Since there is unfortunately nothing we can do |
572 | * about that, do not return an error... |
573 | */ |
574 | if (!lk_data.entry.valid) |
575 | goto lk_unlock; |
576 | |
577 | lk_data.entry.port_mask &= ~BIT(port); |
578 | /* If there is no more port in the mask, clear the entry */ |
579 | if (lk_data.entry.port_mask == 0) |
580 | clear = true; |
581 | |
582 | a5psw_reg_writel(a5psw, A5PSW_LK_DATA_HI, value: lk_data.hi); |
583 | |
584 | reg = entry; |
585 | if (clear) |
586 | reg |= A5PSW_LK_ADDR_CTRL_CLEAR; |
587 | else |
588 | reg |= A5PSW_LK_ADDR_CTRL_WRITE; |
589 | |
590 | ret = a5psw_lk_execute_ctrl(a5psw, ctrl: ®); |
591 | if (ret) |
592 | goto lk_unlock; |
593 | |
594 | /* Decrement LEARNCOUNT */ |
595 | if (clear) { |
596 | reg = A5PSW_LK_LEARNCOUNT_MODE_DEC; |
597 | a5psw_reg_writel(a5psw, A5PSW_LK_LEARNCOUNT, value: reg); |
598 | } |
599 | |
600 | lk_unlock: |
601 | mutex_unlock(lock: &a5psw->lk_lock); |
602 | |
603 | return ret; |
604 | } |
605 | |
606 | static int a5psw_port_fdb_dump(struct dsa_switch *ds, int port, |
607 | dsa_fdb_dump_cb_t *cb, void *data) |
608 | { |
609 | struct a5psw *a5psw = ds->priv; |
610 | union lk_data lk_data; |
611 | int i = 0, ret = 0; |
612 | u32 reg; |
613 | |
614 | mutex_lock(&a5psw->lk_lock); |
615 | |
616 | for (i = 0; i < A5PSW_TABLE_ENTRIES; i++) { |
617 | reg = A5PSW_LK_ADDR_CTRL_READ | A5PSW_LK_ADDR_CTRL_WAIT | i; |
618 | |
619 | ret = a5psw_lk_execute_ctrl(a5psw, ctrl: ®); |
620 | if (ret) |
621 | goto out_unlock; |
622 | |
623 | lk_data.hi = a5psw_reg_readl(a5psw, A5PSW_LK_DATA_HI); |
624 | /* If entry is not valid or does not contain the port, skip */ |
625 | if (!lk_data.entry.valid || |
626 | !(lk_data.entry.port_mask & BIT(port))) |
627 | continue; |
628 | |
629 | lk_data.lo = a5psw_reg_readl(a5psw, A5PSW_LK_DATA_LO); |
630 | |
631 | ret = cb(lk_data.entry.mac, 0, lk_data.entry.is_static, data); |
632 | if (ret) |
633 | goto out_unlock; |
634 | } |
635 | |
636 | out_unlock: |
637 | mutex_unlock(lock: &a5psw->lk_lock); |
638 | |
639 | return ret; |
640 | } |
641 | |
642 | static int a5psw_port_vlan_filtering(struct dsa_switch *ds, int port, |
643 | bool vlan_filtering, |
644 | struct netlink_ext_ack *extack) |
645 | { |
646 | u32 mask = BIT(port + A5PSW_VLAN_VERI_SHIFT) | |
647 | BIT(port + A5PSW_VLAN_DISC_SHIFT); |
648 | u32 val = vlan_filtering ? mask : 0; |
649 | struct a5psw *a5psw = ds->priv; |
650 | |
651 | /* Disable/enable vlan tagging */ |
652 | a5psw_reg_rmw(a5psw, A5PSW_VLAN_IN_MODE_ENA, BIT(port), |
653 | val: vlan_filtering ? BIT(port) : 0); |
654 | |
655 | /* Disable/enable vlan input filtering */ |
656 | a5psw_reg_rmw(a5psw, A5PSW_VLAN_VERIFY, mask, val); |
657 | |
658 | return 0; |
659 | } |
660 | |
661 | static int a5psw_find_vlan_entry(struct a5psw *a5psw, u16 vid) |
662 | { |
663 | u32 vlan_res; |
664 | int i; |
665 | |
666 | /* Find vlan for this port */ |
667 | for (i = 0; i < A5PSW_VLAN_COUNT; i++) { |
668 | vlan_res = a5psw_reg_readl(a5psw, A5PSW_VLAN_RES(i)); |
669 | if (FIELD_GET(A5PSW_VLAN_RES_VLANID, vlan_res) == vid) |
670 | return i; |
671 | } |
672 | |
673 | return -1; |
674 | } |
675 | |
676 | static int a5psw_new_vlan_res_entry(struct a5psw *a5psw, u16 newvid) |
677 | { |
678 | u32 vlan_res; |
679 | int i; |
680 | |
681 | /* Find a free VLAN entry */ |
682 | for (i = 0; i < A5PSW_VLAN_COUNT; i++) { |
683 | vlan_res = a5psw_reg_readl(a5psw, A5PSW_VLAN_RES(i)); |
684 | if (!(FIELD_GET(A5PSW_VLAN_RES_PORTMASK, vlan_res))) { |
685 | vlan_res = FIELD_PREP(A5PSW_VLAN_RES_VLANID, newvid); |
686 | a5psw_reg_writel(a5psw, A5PSW_VLAN_RES(i), value: vlan_res); |
687 | return i; |
688 | } |
689 | } |
690 | |
691 | return -1; |
692 | } |
693 | |
694 | static void a5psw_port_vlan_tagged_cfg(struct a5psw *a5psw, |
695 | unsigned int vlan_res_id, int port, |
696 | bool set) |
697 | { |
698 | u32 mask = A5PSW_VLAN_RES_WR_PORTMASK | A5PSW_VLAN_RES_RD_TAGMASK | |
699 | BIT(port); |
700 | u32 vlan_res_off = A5PSW_VLAN_RES(vlan_res_id); |
701 | u32 val = A5PSW_VLAN_RES_WR_TAGMASK, reg; |
702 | |
703 | if (set) |
704 | val |= BIT(port); |
705 | |
706 | /* Toggle tag mask read */ |
707 | a5psw_reg_writel(a5psw, offset: vlan_res_off, A5PSW_VLAN_RES_RD_TAGMASK); |
708 | reg = a5psw_reg_readl(a5psw, offset: vlan_res_off); |
709 | a5psw_reg_writel(a5psw, offset: vlan_res_off, A5PSW_VLAN_RES_RD_TAGMASK); |
710 | |
711 | reg &= ~mask; |
712 | reg |= val; |
713 | a5psw_reg_writel(a5psw, offset: vlan_res_off, value: reg); |
714 | } |
715 | |
716 | static void a5psw_port_vlan_cfg(struct a5psw *a5psw, unsigned int vlan_res_id, |
717 | int port, bool set) |
718 | { |
719 | u32 mask = A5PSW_VLAN_RES_WR_TAGMASK | BIT(port); |
720 | u32 reg = A5PSW_VLAN_RES_WR_PORTMASK; |
721 | |
722 | if (set) |
723 | reg |= BIT(port); |
724 | |
725 | a5psw_reg_rmw(a5psw, A5PSW_VLAN_RES(vlan_res_id), mask, val: reg); |
726 | } |
727 | |
728 | static int a5psw_port_vlan_add(struct dsa_switch *ds, int port, |
729 | const struct switchdev_obj_port_vlan *vlan, |
730 | struct netlink_ext_ack *extack) |
731 | { |
732 | bool tagged = !(vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED); |
733 | bool pvid = vlan->flags & BRIDGE_VLAN_INFO_PVID; |
734 | struct a5psw *a5psw = ds->priv; |
735 | u16 vid = vlan->vid; |
736 | int vlan_res_id; |
737 | |
738 | vlan_res_id = a5psw_find_vlan_entry(a5psw, vid); |
739 | if (vlan_res_id < 0) { |
740 | vlan_res_id = a5psw_new_vlan_res_entry(a5psw, newvid: vid); |
741 | if (vlan_res_id < 0) |
742 | return -ENOSPC; |
743 | } |
744 | |
745 | a5psw_port_vlan_cfg(a5psw, vlan_res_id, port, set: true); |
746 | if (tagged) |
747 | a5psw_port_vlan_tagged_cfg(a5psw, vlan_res_id, port, set: true); |
748 | |
749 | /* Configure port to tag with corresponding VID, but do not enable it |
750 | * yet: wait for vlan filtering to be enabled to enable vlan port |
751 | * tagging |
752 | */ |
753 | if (pvid) |
754 | a5psw_reg_writel(a5psw, A5PSW_SYSTEM_TAGINFO(port), value: vid); |
755 | |
756 | return 0; |
757 | } |
758 | |
759 | static int a5psw_port_vlan_del(struct dsa_switch *ds, int port, |
760 | const struct switchdev_obj_port_vlan *vlan) |
761 | { |
762 | struct a5psw *a5psw = ds->priv; |
763 | u16 vid = vlan->vid; |
764 | int vlan_res_id; |
765 | |
766 | vlan_res_id = a5psw_find_vlan_entry(a5psw, vid); |
767 | if (vlan_res_id < 0) |
768 | return -EINVAL; |
769 | |
770 | a5psw_port_vlan_cfg(a5psw, vlan_res_id, port, set: false); |
771 | a5psw_port_vlan_tagged_cfg(a5psw, vlan_res_id, port, set: false); |
772 | |
773 | return 0; |
774 | } |
775 | |
776 | static u64 a5psw_read_stat(struct a5psw *a5psw, u32 offset, int port) |
777 | { |
778 | u32 reg_lo, reg_hi; |
779 | |
780 | reg_lo = a5psw_reg_readl(a5psw, offset: offset + A5PSW_PORT_OFFSET(port)); |
781 | /* A5PSW_STATS_HIWORD is latched on stat read */ |
782 | reg_hi = a5psw_reg_readl(a5psw, A5PSW_STATS_HIWORD); |
783 | |
784 | return ((u64)reg_hi << 32) | reg_lo; |
785 | } |
786 | |
787 | static void a5psw_get_strings(struct dsa_switch *ds, int port, u32 stringset, |
788 | uint8_t *data) |
789 | { |
790 | unsigned int u; |
791 | |
792 | if (stringset != ETH_SS_STATS) |
793 | return; |
794 | |
795 | for (u = 0; u < ARRAY_SIZE(a5psw_stats); u++) { |
796 | memcpy(data + u * ETH_GSTRING_LEN, a5psw_stats[u].name, |
797 | ETH_GSTRING_LEN); |
798 | } |
799 | } |
800 | |
801 | static void a5psw_get_ethtool_stats(struct dsa_switch *ds, int port, |
802 | uint64_t *data) |
803 | { |
804 | struct a5psw *a5psw = ds->priv; |
805 | unsigned int u; |
806 | |
807 | for (u = 0; u < ARRAY_SIZE(a5psw_stats); u++) |
808 | data[u] = a5psw_read_stat(a5psw, offset: a5psw_stats[u].offset, port); |
809 | } |
810 | |
811 | static int a5psw_get_sset_count(struct dsa_switch *ds, int port, int sset) |
812 | { |
813 | if (sset != ETH_SS_STATS) |
814 | return 0; |
815 | |
816 | return ARRAY_SIZE(a5psw_stats); |
817 | } |
818 | |
819 | static void a5psw_get_eth_mac_stats(struct dsa_switch *ds, int port, |
820 | struct ethtool_eth_mac_stats *mac_stats) |
821 | { |
822 | struct a5psw *a5psw = ds->priv; |
823 | |
824 | #define RD(name) a5psw_read_stat(a5psw, A5PSW_##name, port) |
825 | mac_stats->FramesTransmittedOK = RD(aFramesTransmittedOK); |
826 | mac_stats->SingleCollisionFrames = RD(aSingleCollisions); |
827 | mac_stats->MultipleCollisionFrames = RD(aMultipleCollisions); |
828 | mac_stats->FramesReceivedOK = RD(aFramesReceivedOK); |
829 | mac_stats->FrameCheckSequenceErrors = RD(aFrameCheckSequenceErrors); |
830 | mac_stats->AlignmentErrors = RD(aAlignmentErrors); |
831 | mac_stats->OctetsTransmittedOK = RD(aOctetsTransmittedOK); |
832 | mac_stats->FramesWithDeferredXmissions = RD(aDeferred); |
833 | mac_stats->LateCollisions = RD(aLateCollisions); |
834 | mac_stats->FramesAbortedDueToXSColls = RD(aExcessiveCollisions); |
835 | mac_stats->FramesLostDueToIntMACXmitError = RD(ifOutErrors); |
836 | mac_stats->CarrierSenseErrors = RD(aCarrierSenseErrors); |
837 | mac_stats->OctetsReceivedOK = RD(aOctetsReceivedOK); |
838 | mac_stats->FramesLostDueToIntMACRcvError = RD(ifInErrors); |
839 | mac_stats->MulticastFramesXmittedOK = RD(ifOutMulticastPkts); |
840 | mac_stats->BroadcastFramesXmittedOK = RD(ifOutBroadcastPkts); |
841 | mac_stats->FramesWithExcessiveDeferral = RD(aDeferred); |
842 | mac_stats->MulticastFramesReceivedOK = RD(ifInMulticastPkts); |
843 | mac_stats->BroadcastFramesReceivedOK = RD(ifInBroadcastPkts); |
844 | #undef RD |
845 | } |
846 | |
847 | static const struct ethtool_rmon_hist_range a5psw_rmon_ranges[] = { |
848 | { 0, 64 }, |
849 | { 65, 127 }, |
850 | { 128, 255 }, |
851 | { 256, 511 }, |
852 | { 512, 1023 }, |
853 | { 1024, 1518 }, |
854 | { 1519, A5PSW_MAX_MTU }, |
855 | {} |
856 | }; |
857 | |
858 | static void a5psw_get_rmon_stats(struct dsa_switch *ds, int port, |
859 | struct ethtool_rmon_stats *rmon_stats, |
860 | const struct ethtool_rmon_hist_range **ranges) |
861 | { |
862 | struct a5psw *a5psw = ds->priv; |
863 | |
864 | #define RD(name) a5psw_read_stat(a5psw, A5PSW_##name, port) |
865 | rmon_stats->undersize_pkts = RD(etherStatsUndersizePkts); |
866 | rmon_stats->oversize_pkts = RD(etherStatsOversizePkts); |
867 | rmon_stats->fragments = RD(etherStatsFragments); |
868 | rmon_stats->jabbers = RD(etherStatsJabbers); |
869 | rmon_stats->hist[0] = RD(etherStatsPkts64Octets); |
870 | rmon_stats->hist[1] = RD(etherStatsPkts65to127Octets); |
871 | rmon_stats->hist[2] = RD(etherStatsPkts128to255Octets); |
872 | rmon_stats->hist[3] = RD(etherStatsPkts256to511Octets); |
873 | rmon_stats->hist[4] = RD(etherStatsPkts512to1023Octets); |
874 | rmon_stats->hist[5] = RD(etherStatsPkts1024to1518Octets); |
875 | rmon_stats->hist[6] = RD(etherStatsPkts1519toXOctets); |
876 | #undef RD |
877 | |
878 | *ranges = a5psw_rmon_ranges; |
879 | } |
880 | |
881 | static void a5psw_get_eth_ctrl_stats(struct dsa_switch *ds, int port, |
882 | struct ethtool_eth_ctrl_stats *ctrl_stats) |
883 | { |
884 | struct a5psw *a5psw = ds->priv; |
885 | u64 stat; |
886 | |
887 | stat = a5psw_read_stat(a5psw, A5PSW_aTxPAUSEMACCtrlFrames, port); |
888 | ctrl_stats->MACControlFramesTransmitted = stat; |
889 | stat = a5psw_read_stat(a5psw, A5PSW_aRxPAUSEMACCtrlFrames, port); |
890 | ctrl_stats->MACControlFramesReceived = stat; |
891 | } |
892 | |
893 | static void a5psw_vlan_setup(struct a5psw *a5psw, int port) |
894 | { |
895 | u32 reg; |
896 | |
897 | /* Enable TAG always mode for the port, this is actually controlled |
898 | * by VLAN_IN_MODE_ENA field which will be used for PVID insertion |
899 | */ |
900 | reg = A5PSW_VLAN_IN_MODE_TAG_ALWAYS; |
901 | reg <<= A5PSW_VLAN_IN_MODE_PORT_SHIFT(port); |
902 | a5psw_reg_rmw(a5psw, A5PSW_VLAN_IN_MODE, A5PSW_VLAN_IN_MODE_PORT(port), |
903 | val: reg); |
904 | |
905 | /* Set transparent mode for output frame manipulation, this will depend |
906 | * on the VLAN_RES configuration mode |
907 | */ |
908 | reg = A5PSW_VLAN_OUT_MODE_TRANSPARENT; |
909 | reg <<= A5PSW_VLAN_OUT_MODE_PORT_SHIFT(port); |
910 | a5psw_reg_rmw(a5psw, A5PSW_VLAN_OUT_MODE, |
911 | A5PSW_VLAN_OUT_MODE_PORT(port), val: reg); |
912 | } |
913 | |
914 | static int a5psw_setup(struct dsa_switch *ds) |
915 | { |
916 | struct a5psw *a5psw = ds->priv; |
917 | int port, vlan, ret; |
918 | struct dsa_port *dp; |
919 | u32 reg; |
920 | |
921 | /* Validate that there is only 1 CPU port with index A5PSW_CPU_PORT */ |
922 | dsa_switch_for_each_cpu_port(dp, ds) { |
923 | if (dp->index != A5PSW_CPU_PORT) { |
924 | dev_err(a5psw->dev, "Invalid CPU port\n" ); |
925 | return -EINVAL; |
926 | } |
927 | } |
928 | |
929 | /* Configure management port */ |
930 | reg = A5PSW_CPU_PORT | A5PSW_MGMT_CFG_ENABLE; |
931 | a5psw_reg_writel(a5psw, A5PSW_MGMT_CFG, value: reg); |
932 | |
933 | /* Set pattern 0 to forward all frame to mgmt port */ |
934 | a5psw_reg_writel(a5psw, A5PSW_PATTERN_CTRL(A5PSW_PATTERN_MGMTFWD), |
935 | A5PSW_PATTERN_CTRL_MGMTFWD); |
936 | |
937 | /* Enable port tagging */ |
938 | reg = FIELD_PREP(A5PSW_MGMT_TAG_CFG_TAGFIELD, ETH_P_DSA_A5PSW); |
939 | reg |= A5PSW_MGMT_TAG_CFG_ENABLE | A5PSW_MGMT_TAG_CFG_ALL_FRAMES; |
940 | a5psw_reg_writel(a5psw, A5PSW_MGMT_TAG_CFG, value: reg); |
941 | |
942 | /* Enable normal switch operation */ |
943 | reg = A5PSW_LK_ADDR_CTRL_BLOCKING | A5PSW_LK_ADDR_CTRL_LEARNING | |
944 | A5PSW_LK_ADDR_CTRL_AGEING | A5PSW_LK_ADDR_CTRL_ALLOW_MIGR | |
945 | A5PSW_LK_ADDR_CTRL_CLEAR_TABLE; |
946 | a5psw_reg_writel(a5psw, A5PSW_LK_CTRL, value: reg); |
947 | |
948 | ret = readl_poll_timeout(a5psw->base + A5PSW_LK_CTRL, reg, |
949 | !(reg & A5PSW_LK_ADDR_CTRL_CLEAR_TABLE), |
950 | A5PSW_LK_BUSY_USEC_POLL, A5PSW_CTRL_TIMEOUT); |
951 | if (ret) { |
952 | dev_err(a5psw->dev, "Failed to clear lookup table\n" ); |
953 | return ret; |
954 | } |
955 | |
956 | /* Reset learn count to 0 */ |
957 | reg = A5PSW_LK_LEARNCOUNT_MODE_SET; |
958 | a5psw_reg_writel(a5psw, A5PSW_LK_LEARNCOUNT, value: reg); |
959 | |
960 | /* Clear VLAN resource table */ |
961 | reg = A5PSW_VLAN_RES_WR_PORTMASK | A5PSW_VLAN_RES_WR_TAGMASK; |
962 | for (vlan = 0; vlan < A5PSW_VLAN_COUNT; vlan++) |
963 | a5psw_reg_writel(a5psw, A5PSW_VLAN_RES(vlan), value: reg); |
964 | |
965 | /* Reset all ports */ |
966 | dsa_switch_for_each_port(dp, ds) { |
967 | port = dp->index; |
968 | |
969 | /* Reset the port */ |
970 | a5psw_reg_writel(a5psw, A5PSW_CMD_CFG(port), |
971 | A5PSW_CMD_CFG_SW_RESET); |
972 | |
973 | /* Enable only CPU port */ |
974 | a5psw_port_enable_set(a5psw, port, enable: dsa_port_is_cpu(port: dp)); |
975 | |
976 | if (dsa_port_is_unused(dp)) |
977 | continue; |
978 | |
979 | /* Enable egress flooding and learning for CPU port */ |
980 | if (dsa_port_is_cpu(port: dp)) { |
981 | a5psw_flooding_set_resolution(a5psw, port, set: true); |
982 | a5psw_port_learning_set(a5psw, port, learn: true); |
983 | } |
984 | |
985 | /* Enable standalone mode for user ports */ |
986 | if (dsa_port_is_user(dp)) |
987 | a5psw_port_set_standalone(a5psw, port, standalone: true); |
988 | |
989 | a5psw_vlan_setup(a5psw, port); |
990 | } |
991 | |
992 | return 0; |
993 | } |
994 | |
995 | static const struct dsa_switch_ops a5psw_switch_ops = { |
996 | .get_tag_protocol = a5psw_get_tag_protocol, |
997 | .setup = a5psw_setup, |
998 | .port_disable = a5psw_port_disable, |
999 | .port_enable = a5psw_port_enable, |
1000 | .phylink_get_caps = a5psw_phylink_get_caps, |
1001 | .phylink_mac_select_pcs = a5psw_phylink_mac_select_pcs, |
1002 | .phylink_mac_link_down = a5psw_phylink_mac_link_down, |
1003 | .phylink_mac_link_up = a5psw_phylink_mac_link_up, |
1004 | .port_change_mtu = a5psw_port_change_mtu, |
1005 | .port_max_mtu = a5psw_port_max_mtu, |
1006 | .get_sset_count = a5psw_get_sset_count, |
1007 | .get_strings = a5psw_get_strings, |
1008 | .get_ethtool_stats = a5psw_get_ethtool_stats, |
1009 | .get_eth_mac_stats = a5psw_get_eth_mac_stats, |
1010 | .get_eth_ctrl_stats = a5psw_get_eth_ctrl_stats, |
1011 | .get_rmon_stats = a5psw_get_rmon_stats, |
1012 | .set_ageing_time = a5psw_set_ageing_time, |
1013 | .port_bridge_join = a5psw_port_bridge_join, |
1014 | .port_bridge_leave = a5psw_port_bridge_leave, |
1015 | .port_pre_bridge_flags = a5psw_port_pre_bridge_flags, |
1016 | .port_bridge_flags = a5psw_port_bridge_flags, |
1017 | .port_stp_state_set = a5psw_port_stp_state_set, |
1018 | .port_fast_age = a5psw_port_fast_age, |
1019 | .port_vlan_filtering = a5psw_port_vlan_filtering, |
1020 | .port_vlan_add = a5psw_port_vlan_add, |
1021 | .port_vlan_del = a5psw_port_vlan_del, |
1022 | .port_fdb_add = a5psw_port_fdb_add, |
1023 | .port_fdb_del = a5psw_port_fdb_del, |
1024 | .port_fdb_dump = a5psw_port_fdb_dump, |
1025 | }; |
1026 | |
1027 | static int a5psw_mdio_wait_busy(struct a5psw *a5psw) |
1028 | { |
1029 | u32 status; |
1030 | int err; |
1031 | |
1032 | err = readl_poll_timeout(a5psw->base + A5PSW_MDIO_CFG_STATUS, status, |
1033 | !(status & A5PSW_MDIO_CFG_STATUS_BUSY), 10, |
1034 | 1000 * USEC_PER_MSEC); |
1035 | if (err) |
1036 | dev_err(a5psw->dev, "MDIO command timeout\n" ); |
1037 | |
1038 | return err; |
1039 | } |
1040 | |
1041 | static int a5psw_mdio_read(struct mii_bus *bus, int phy_id, int phy_reg) |
1042 | { |
1043 | struct a5psw *a5psw = bus->priv; |
1044 | u32 cmd, status; |
1045 | int ret; |
1046 | |
1047 | cmd = A5PSW_MDIO_COMMAND_READ; |
1048 | cmd |= FIELD_PREP(A5PSW_MDIO_COMMAND_REG_ADDR, phy_reg); |
1049 | cmd |= FIELD_PREP(A5PSW_MDIO_COMMAND_PHY_ADDR, phy_id); |
1050 | |
1051 | a5psw_reg_writel(a5psw, A5PSW_MDIO_COMMAND, value: cmd); |
1052 | |
1053 | ret = a5psw_mdio_wait_busy(a5psw); |
1054 | if (ret) |
1055 | return ret; |
1056 | |
1057 | ret = a5psw_reg_readl(a5psw, A5PSW_MDIO_DATA) & A5PSW_MDIO_DATA_MASK; |
1058 | |
1059 | status = a5psw_reg_readl(a5psw, A5PSW_MDIO_CFG_STATUS); |
1060 | if (status & A5PSW_MDIO_CFG_STATUS_READERR) |
1061 | return -EIO; |
1062 | |
1063 | return ret; |
1064 | } |
1065 | |
1066 | static int a5psw_mdio_write(struct mii_bus *bus, int phy_id, int phy_reg, |
1067 | u16 phy_data) |
1068 | { |
1069 | struct a5psw *a5psw = bus->priv; |
1070 | u32 cmd; |
1071 | |
1072 | cmd = FIELD_PREP(A5PSW_MDIO_COMMAND_REG_ADDR, phy_reg); |
1073 | cmd |= FIELD_PREP(A5PSW_MDIO_COMMAND_PHY_ADDR, phy_id); |
1074 | |
1075 | a5psw_reg_writel(a5psw, A5PSW_MDIO_COMMAND, value: cmd); |
1076 | a5psw_reg_writel(a5psw, A5PSW_MDIO_DATA, value: phy_data); |
1077 | |
1078 | return a5psw_mdio_wait_busy(a5psw); |
1079 | } |
1080 | |
1081 | static int a5psw_mdio_config(struct a5psw *a5psw, u32 mdio_freq) |
1082 | { |
1083 | unsigned long rate; |
1084 | unsigned long div; |
1085 | u32 cfgstatus; |
1086 | |
1087 | rate = clk_get_rate(clk: a5psw->hclk); |
1088 | div = ((rate / mdio_freq) / 2); |
1089 | if (div > FIELD_MAX(A5PSW_MDIO_CFG_STATUS_CLKDIV) || |
1090 | div < A5PSW_MDIO_CLK_DIV_MIN) { |
1091 | dev_err(a5psw->dev, "MDIO clock div %ld out of range\n" , div); |
1092 | return -ERANGE; |
1093 | } |
1094 | |
1095 | cfgstatus = FIELD_PREP(A5PSW_MDIO_CFG_STATUS_CLKDIV, div); |
1096 | |
1097 | a5psw_reg_writel(a5psw, A5PSW_MDIO_CFG_STATUS, value: cfgstatus); |
1098 | |
1099 | return 0; |
1100 | } |
1101 | |
1102 | static int a5psw_probe_mdio(struct a5psw *a5psw, struct device_node *node) |
1103 | { |
1104 | struct device *dev = a5psw->dev; |
1105 | struct mii_bus *bus; |
1106 | u32 mdio_freq; |
1107 | int ret; |
1108 | |
1109 | if (of_property_read_u32(np: node, propname: "clock-frequency" , out_value: &mdio_freq)) |
1110 | mdio_freq = A5PSW_MDIO_DEF_FREQ; |
1111 | |
1112 | ret = a5psw_mdio_config(a5psw, mdio_freq); |
1113 | if (ret) |
1114 | return ret; |
1115 | |
1116 | bus = devm_mdiobus_alloc(dev); |
1117 | if (!bus) |
1118 | return -ENOMEM; |
1119 | |
1120 | bus->name = "a5psw_mdio" ; |
1121 | bus->read = a5psw_mdio_read; |
1122 | bus->write = a5psw_mdio_write; |
1123 | bus->priv = a5psw; |
1124 | bus->parent = dev; |
1125 | snprintf(buf: bus->id, MII_BUS_ID_SIZE, fmt: "%s" , dev_name(dev)); |
1126 | |
1127 | a5psw->mii_bus = bus; |
1128 | |
1129 | return devm_of_mdiobus_register(dev, mdio: bus, np: node); |
1130 | } |
1131 | |
1132 | static void a5psw_pcs_free(struct a5psw *a5psw) |
1133 | { |
1134 | int i; |
1135 | |
1136 | for (i = 0; i < ARRAY_SIZE(a5psw->pcs); i++) { |
1137 | if (a5psw->pcs[i]) |
1138 | miic_destroy(pcs: a5psw->pcs[i]); |
1139 | } |
1140 | } |
1141 | |
1142 | static int a5psw_pcs_get(struct a5psw *a5psw) |
1143 | { |
1144 | struct device_node *ports, *port, *pcs_node; |
1145 | struct phylink_pcs *pcs; |
1146 | int ret; |
1147 | u32 reg; |
1148 | |
1149 | ports = of_get_child_by_name(node: a5psw->dev->of_node, name: "ethernet-ports" ); |
1150 | if (!ports) |
1151 | return -EINVAL; |
1152 | |
1153 | for_each_available_child_of_node(ports, port) { |
1154 | pcs_node = of_parse_phandle(np: port, phandle_name: "pcs-handle" , index: 0); |
1155 | if (!pcs_node) |
1156 | continue; |
1157 | |
1158 | if (of_property_read_u32(np: port, propname: "reg" , out_value: ®)) { |
1159 | ret = -EINVAL; |
1160 | goto free_pcs; |
1161 | } |
1162 | |
1163 | if (reg >= ARRAY_SIZE(a5psw->pcs)) { |
1164 | ret = -ENODEV; |
1165 | goto free_pcs; |
1166 | } |
1167 | |
1168 | pcs = miic_create(dev: a5psw->dev, np: pcs_node); |
1169 | if (IS_ERR(ptr: pcs)) { |
1170 | dev_err(a5psw->dev, "Failed to create PCS for port %d\n" , |
1171 | reg); |
1172 | ret = PTR_ERR(ptr: pcs); |
1173 | goto free_pcs; |
1174 | } |
1175 | |
1176 | a5psw->pcs[reg] = pcs; |
1177 | of_node_put(node: pcs_node); |
1178 | } |
1179 | of_node_put(node: ports); |
1180 | |
1181 | return 0; |
1182 | |
1183 | free_pcs: |
1184 | of_node_put(node: pcs_node); |
1185 | of_node_put(node: port); |
1186 | of_node_put(node: ports); |
1187 | a5psw_pcs_free(a5psw); |
1188 | |
1189 | return ret; |
1190 | } |
1191 | |
1192 | static int a5psw_probe(struct platform_device *pdev) |
1193 | { |
1194 | struct device *dev = &pdev->dev; |
1195 | struct device_node *mdio; |
1196 | struct dsa_switch *ds; |
1197 | struct a5psw *a5psw; |
1198 | int ret; |
1199 | |
1200 | a5psw = devm_kzalloc(dev, size: sizeof(*a5psw), GFP_KERNEL); |
1201 | if (!a5psw) |
1202 | return -ENOMEM; |
1203 | |
1204 | a5psw->dev = dev; |
1205 | mutex_init(&a5psw->lk_lock); |
1206 | spin_lock_init(&a5psw->reg_lock); |
1207 | a5psw->base = devm_platform_ioremap_resource(pdev, index: 0); |
1208 | if (IS_ERR(ptr: a5psw->base)) |
1209 | return PTR_ERR(ptr: a5psw->base); |
1210 | |
1211 | a5psw->bridged_ports = BIT(A5PSW_CPU_PORT); |
1212 | |
1213 | ret = a5psw_pcs_get(a5psw); |
1214 | if (ret) |
1215 | return ret; |
1216 | |
1217 | a5psw->hclk = devm_clk_get(dev, id: "hclk" ); |
1218 | if (IS_ERR(ptr: a5psw->hclk)) { |
1219 | dev_err(dev, "failed get hclk clock\n" ); |
1220 | ret = PTR_ERR(ptr: a5psw->hclk); |
1221 | goto free_pcs; |
1222 | } |
1223 | |
1224 | a5psw->clk = devm_clk_get(dev, id: "clk" ); |
1225 | if (IS_ERR(ptr: a5psw->clk)) { |
1226 | dev_err(dev, "failed get clk_switch clock\n" ); |
1227 | ret = PTR_ERR(ptr: a5psw->clk); |
1228 | goto free_pcs; |
1229 | } |
1230 | |
1231 | ret = clk_prepare_enable(clk: a5psw->clk); |
1232 | if (ret) |
1233 | goto free_pcs; |
1234 | |
1235 | ret = clk_prepare_enable(clk: a5psw->hclk); |
1236 | if (ret) |
1237 | goto clk_disable; |
1238 | |
1239 | mdio = of_get_child_by_name(node: dev->of_node, name: "mdio" ); |
1240 | if (of_device_is_available(device: mdio)) { |
1241 | ret = a5psw_probe_mdio(a5psw, node: mdio); |
1242 | if (ret) { |
1243 | of_node_put(node: mdio); |
1244 | dev_err(dev, "Failed to register MDIO: %d\n" , ret); |
1245 | goto hclk_disable; |
1246 | } |
1247 | } |
1248 | |
1249 | of_node_put(node: mdio); |
1250 | |
1251 | ds = &a5psw->ds; |
1252 | ds->dev = dev; |
1253 | ds->num_ports = A5PSW_PORTS_NUM; |
1254 | ds->ops = &a5psw_switch_ops; |
1255 | ds->priv = a5psw; |
1256 | |
1257 | ret = dsa_register_switch(ds); |
1258 | if (ret) { |
1259 | dev_err(dev, "Failed to register DSA switch: %d\n" , ret); |
1260 | goto hclk_disable; |
1261 | } |
1262 | |
1263 | return 0; |
1264 | |
1265 | hclk_disable: |
1266 | clk_disable_unprepare(clk: a5psw->hclk); |
1267 | clk_disable: |
1268 | clk_disable_unprepare(clk: a5psw->clk); |
1269 | free_pcs: |
1270 | a5psw_pcs_free(a5psw); |
1271 | |
1272 | return ret; |
1273 | } |
1274 | |
1275 | static void a5psw_remove(struct platform_device *pdev) |
1276 | { |
1277 | struct a5psw *a5psw = platform_get_drvdata(pdev); |
1278 | |
1279 | if (!a5psw) |
1280 | return; |
1281 | |
1282 | dsa_unregister_switch(ds: &a5psw->ds); |
1283 | a5psw_pcs_free(a5psw); |
1284 | clk_disable_unprepare(clk: a5psw->hclk); |
1285 | clk_disable_unprepare(clk: a5psw->clk); |
1286 | } |
1287 | |
1288 | static void a5psw_shutdown(struct platform_device *pdev) |
1289 | { |
1290 | struct a5psw *a5psw = platform_get_drvdata(pdev); |
1291 | |
1292 | if (!a5psw) |
1293 | return; |
1294 | |
1295 | dsa_switch_shutdown(ds: &a5psw->ds); |
1296 | |
1297 | platform_set_drvdata(pdev, NULL); |
1298 | } |
1299 | |
1300 | static const struct of_device_id a5psw_of_mtable[] = { |
1301 | { .compatible = "renesas,rzn1-a5psw" , }, |
1302 | { /* sentinel */ }, |
1303 | }; |
1304 | MODULE_DEVICE_TABLE(of, a5psw_of_mtable); |
1305 | |
1306 | static struct platform_driver a5psw_driver = { |
1307 | .driver = { |
1308 | .name = "rzn1_a5psw" , |
1309 | .of_match_table = a5psw_of_mtable, |
1310 | }, |
1311 | .probe = a5psw_probe, |
1312 | .remove_new = a5psw_remove, |
1313 | .shutdown = a5psw_shutdown, |
1314 | }; |
1315 | module_platform_driver(a5psw_driver); |
1316 | |
1317 | MODULE_LICENSE("GPL" ); |
1318 | MODULE_DESCRIPTION("Renesas RZ/N1 Advanced 5-port Switch driver" ); |
1319 | MODULE_AUTHOR("Clément Léger <clement.leger@bootlin.com>" ); |
1320 | |