Commit | Line | Data |
---|---|---|
a556c76a AB |
1 | // SPDX-License-Identifier: (GPL-2.0 OR MIT) |
2 | /* | |
3 | * Microsemi Ocelot Switch driver | |
4 | * | |
5 | * Copyright (c) 2017 Microsemi Corporation | |
6 | */ | |
40d3f295 | 7 | #include <linux/dsa/ocelot.h> |
a556c76a AB |
8 | #include <linux/interrupt.h> |
9 | #include <linux/module.h> | |
71e32a20 | 10 | #include <linux/of_net.h> |
a556c76a | 11 | #include <linux/netdevice.h> |
e6e12df6 | 12 | #include <linux/phylink.h> |
3d40aed8 | 13 | #include <linux/of.h> |
a556c76a | 14 | #include <linux/of_mdio.h> |
3d40aed8 | 15 | #include <linux/platform_device.h> |
19aedfbe | 16 | #include <linux/mfd/syscon.h> |
a556c76a | 17 | #include <linux/skbuff.h> |
0e332c85 | 18 | #include <net/switchdev.h> |
a556c76a | 19 | |
b67f5502 | 20 | #include <soc/mscc/ocelot.h> |
e0632940 | 21 | #include <soc/mscc/ocelot_vcap.h> |
32ecd22b | 22 | #include <soc/mscc/vsc7514_regs.h> |
753a026c | 23 | #include "ocelot_fdma.h" |
a556c76a AB |
24 | #include "ocelot.h" |
25 | ||
77043c37 XY |
26 | #define VSC7514_VCAP_POLICER_BASE 128 |
27 | #define VSC7514_VCAP_POLICER_MAX 191 | |
28 | ||
d9feb904 VO |
29 | static int ocelot_chip_init(struct ocelot *ocelot, const struct ocelot_ops *ops) |
30 | { | |
31 | int ret; | |
32 | ||
2efaca41 | 33 | ocelot->map = vsc7514_regmap; |
d9feb904 VO |
34 | ocelot->num_mact_rows = 1024; |
35 | ocelot->ops = ops; | |
36 | ||
728d8019 | 37 | ret = ocelot_regfields_init(ocelot, vsc7514_regfields); |
d9feb904 VO |
38 | if (ret) |
39 | return ret; | |
40 | ||
41 | ocelot_pll5_init(ocelot); | |
42 | ||
43 | eth_random_addr(ocelot->base_mac); | |
44 | ocelot->base_mac[5] &= 0xf0; | |
45 | ||
46 | return 0; | |
47 | } | |
48 | ||
a556c76a AB |
49 | static irqreturn_t ocelot_xtr_irq_handler(int irq, void *arg) |
50 | { | |
51 | struct ocelot *ocelot = arg; | |
924ee317 | 52 | int grp = 0, err; |
a556c76a | 53 | |
c5e12ac3 VO |
54 | ocelot_lock_xtr_grp(ocelot, grp); |
55 | ||
f833ca29 | 56 | while (ocelot_read(ocelot, QS_XTR_DATA_PRESENT) & BIT(grp)) { |
4e3b0468 | 57 | struct sk_buff *skb; |
a556c76a | 58 | |
924ee317 VO |
59 | err = ocelot_xtr_poll_frame(ocelot, grp, &skb); |
60 | if (err) | |
a94306ce | 61 | goto out; |
4e3b0468 | 62 | |
924ee317 VO |
63 | skb->dev->stats.rx_bytes += skb->len; |
64 | skb->dev->stats.rx_packets++; | |
a556c76a | 65 | |
b2e118f6 AT |
66 | if (!skb_defer_rx_timestamp(skb)) |
67 | netif_rx(skb); | |
f833ca29 | 68 | } |
a556c76a | 69 | |
a94306ce | 70 | out: |
d7795f8f | 71 | if (err < 0) |
0a6f17c6 | 72 | ocelot_drain_cpu_queue(ocelot, 0); |
a556c76a | 73 | |
c5e12ac3 VO |
74 | ocelot_unlock_xtr_grp(ocelot, grp); |
75 | ||
a556c76a AB |
76 | return IRQ_HANDLED; |
77 | } | |
78 | ||
4e3b0468 AT |
79 | static irqreturn_t ocelot_ptp_rdy_irq_handler(int irq, void *arg) |
80 | { | |
4e3b0468 AT |
81 | struct ocelot *ocelot = arg; |
82 | ||
e23a7b3e | 83 | ocelot_get_txtstamp(ocelot); |
4e3b0468 AT |
84 | |
85 | return IRQ_HANDLED; | |
86 | } | |
87 | ||
a556c76a AB |
88 | static const struct of_device_id mscc_ocelot_match[] = { |
89 | { .compatible = "mscc,vsc7514-switch" }, | |
90 | { } | |
91 | }; | |
92 | MODULE_DEVICE_TABLE(of, mscc_ocelot_match); | |
93 | ||
dc3de2a2 | 94 | static const struct ocelot_ops ocelot_ops = { |
3a77b593 | 95 | .reset = ocelot_reset, |
aa92d836 | 96 | .wm_enc = ocelot_wm_enc, |
703b7621 VO |
97 | .wm_dec = ocelot_wm_dec, |
98 | .wm_stat = ocelot_wm_stat, | |
319e4dd1 VO |
99 | .port_to_netdev = ocelot_port_to_netdev, |
100 | .netdev_to_port = ocelot_netdev_to_port, | |
dc3de2a2 CM |
101 | }; |
102 | ||
2b49d128 YL |
103 | static struct ptp_clock_info ocelot_ptp_clock_info = { |
104 | .owner = THIS_MODULE, | |
105 | .name = "ocelot ptp", | |
106 | .max_adj = 0x7fffffff, | |
107 | .n_alarm = 0, | |
108 | .n_ext_ts = 0, | |
aabb2bb0 YL |
109 | .n_per_out = OCELOT_PTP_PINS_NUM, |
110 | .n_pins = OCELOT_PTP_PINS_NUM, | |
2b49d128 YL |
111 | .pps = 0, |
112 | .gettime64 = ocelot_ptp_gettime64, | |
113 | .settime64 = ocelot_ptp_settime64, | |
114 | .adjtime = ocelot_ptp_adjtime, | |
115 | .adjfine = ocelot_ptp_adjfine, | |
aabb2bb0 YL |
116 | .verify = ocelot_ptp_verify, |
117 | .enable = ocelot_ptp_enable, | |
2b49d128 YL |
118 | }; |
119 | ||
6c30384e VO |
120 | static void mscc_ocelot_teardown_devlink_ports(struct ocelot *ocelot) |
121 | { | |
122 | int port; | |
123 | ||
124 | for (port = 0; port < ocelot->num_phys_ports; port++) | |
125 | ocelot_port_devlink_teardown(ocelot, port); | |
126 | } | |
127 | ||
22cdb493 VO |
128 | static void mscc_ocelot_release_ports(struct ocelot *ocelot) |
129 | { | |
130 | int port; | |
131 | ||
132 | for (port = 0; port < ocelot->num_phys_ports; port++) { | |
22cdb493 VO |
133 | struct ocelot_port *ocelot_port; |
134 | ||
135 | ocelot_port = ocelot->ports[port]; | |
136 | if (!ocelot_port) | |
137 | continue; | |
138 | ||
e5fb512d | 139 | ocelot_deinit_port(ocelot, port); |
e0c16233 | 140 | ocelot_release_port(ocelot_port); |
22cdb493 VO |
141 | } |
142 | } | |
143 | ||
7c411799 VO |
144 | static int mscc_ocelot_init_ports(struct platform_device *pdev, |
145 | struct device_node *ports) | |
146 | { | |
147 | struct ocelot *ocelot = platform_get_drvdata(pdev); | |
e0c16233 | 148 | u32 devlink_ports_registered = 0; |
7c411799 | 149 | struct device_node *portnp; |
6c30384e VO |
150 | int port, err; |
151 | u32 reg; | |
7c411799 VO |
152 | |
153 | ocelot->ports = devm_kcalloc(ocelot->dev, ocelot->num_phys_ports, | |
154 | sizeof(struct ocelot_port *), GFP_KERNEL); | |
155 | if (!ocelot->ports) | |
156 | return -ENOMEM; | |
157 | ||
6c30384e VO |
158 | ocelot->devlink_ports = devm_kcalloc(ocelot->dev, |
159 | ocelot->num_phys_ports, | |
160 | sizeof(*ocelot->devlink_ports), | |
161 | GFP_KERNEL); | |
162 | if (!ocelot->devlink_ports) | |
163 | return -ENOMEM; | |
164 | ||
7c411799 | 165 | for_each_available_child_of_node(ports, portnp) { |
7c411799 VO |
166 | struct regmap *target; |
167 | struct resource *res; | |
7c411799 | 168 | char res_name[8]; |
7c411799 | 169 | |
6c30384e | 170 | if (of_property_read_u32(portnp, "reg", ®)) |
7c411799 VO |
171 | continue; |
172 | ||
6c30384e | 173 | port = reg; |
e0c16233 DC |
174 | if (port < 0 || port >= ocelot->num_phys_ports) { |
175 | dev_err(ocelot->dev, | |
176 | "invalid port number: %d >= %d\n", port, | |
177 | ocelot->num_phys_ports); | |
178 | continue; | |
179 | } | |
6c30384e | 180 | |
7c411799 VO |
181 | snprintf(res_name, sizeof(res_name), "port%d", port); |
182 | ||
183 | res = platform_get_resource_byname(pdev, IORESOURCE_MEM, | |
184 | res_name); | |
185 | target = ocelot_regmap_init(ocelot, res); | |
e0c16233 DC |
186 | if (IS_ERR(target)) { |
187 | err = PTR_ERR(target); | |
d1a7b9e4 | 188 | of_node_put(portnp); |
e0c16233 DC |
189 | goto out_teardown; |
190 | } | |
7c411799 | 191 | |
6c30384e VO |
192 | err = ocelot_port_devlink_init(ocelot, port, |
193 | DEVLINK_PORT_FLAVOUR_PHYSICAL); | |
194 | if (err) { | |
195 | of_node_put(portnp); | |
196 | goto out_teardown; | |
197 | } | |
198 | ||
e6e12df6 | 199 | err = ocelot_probe_port(ocelot, port, target, portnp); |
7c411799 | 200 | if (err) { |
5c8bb71d VO |
201 | ocelot_port_devlink_teardown(ocelot, port); |
202 | continue; | |
7c411799 VO |
203 | } |
204 | ||
5c8bb71d | 205 | devlink_ports_registered |= BIT(port); |
7c411799 VO |
206 | } |
207 | ||
6c30384e VO |
208 | /* Initialize unused devlink ports at the end */ |
209 | for (port = 0; port < ocelot->num_phys_ports; port++) { | |
e0c16233 | 210 | if (devlink_ports_registered & BIT(port)) |
6c30384e VO |
211 | continue; |
212 | ||
213 | err = ocelot_port_devlink_init(ocelot, port, | |
214 | DEVLINK_PORT_FLAVOUR_UNUSED); | |
e0c16233 | 215 | if (err) |
6c30384e | 216 | goto out_teardown; |
6c30384e | 217 | |
e0c16233 DC |
218 | devlink_ports_registered |= BIT(port); |
219 | } | |
6c30384e | 220 | |
7c411799 | 221 | return 0; |
6c30384e VO |
222 | |
223 | out_teardown: | |
224 | /* Unregister the network interfaces */ | |
225 | mscc_ocelot_release_ports(ocelot); | |
226 | /* Tear down devlink ports for the registered network interfaces */ | |
227 | for (port = 0; port < ocelot->num_phys_ports; port++) { | |
e0c16233 DC |
228 | if (devlink_ports_registered & BIT(port)) |
229 | ocelot_port_devlink_teardown(ocelot, port); | |
6c30384e | 230 | } |
6c30384e | 231 | return err; |
7c411799 VO |
232 | } |
233 | ||
a556c76a AB |
234 | static int mscc_ocelot_probe(struct platform_device *pdev) |
235 | { | |
a556c76a | 236 | struct device_node *np = pdev->dev.of_node; |
4e3b0468 | 237 | int err, irq_xtr, irq_ptp_rdy; |
7c411799 | 238 | struct device_node *ports; |
6c30384e | 239 | struct devlink *devlink; |
a556c76a | 240 | struct ocelot *ocelot; |
19aedfbe | 241 | struct regmap *hsio; |
4e3b0468 | 242 | unsigned int i; |
a556c76a AB |
243 | |
244 | struct { | |
245 | enum ocelot_target id; | |
246 | char *name; | |
45bce171 | 247 | u8 optional:1; |
259630e0 | 248 | } io_target[] = { |
a556c76a AB |
249 | { SYS, "sys" }, |
250 | { REW, "rew" }, | |
251 | { QSYS, "qsys" }, | |
252 | { ANA, "ana" }, | |
253 | { QS, "qs" }, | |
e3aea296 | 254 | { S0, "s0" }, |
a61e365d | 255 | { S1, "s1" }, |
b5962294 | 256 | { S2, "s2" }, |
45bce171 | 257 | { PTP, "ptp", 1 }, |
753a026c | 258 | { FDMA, "fdma", 1 }, |
a556c76a AB |
259 | }; |
260 | ||
261 | if (!np && !pdev->dev.platform_data) | |
262 | return -ENODEV; | |
263 | ||
919d13a7 LR |
264 | devlink = |
265 | devlink_alloc(&ocelot_devlink_ops, sizeof(*ocelot), &pdev->dev); | |
6c30384e | 266 | if (!devlink) |
a556c76a AB |
267 | return -ENOMEM; |
268 | ||
6c30384e VO |
269 | ocelot = devlink_priv(devlink); |
270 | ocelot->devlink = priv_to_devlink(ocelot); | |
a556c76a AB |
271 | platform_set_drvdata(pdev, ocelot); |
272 | ocelot->dev = &pdev->dev; | |
273 | ||
259630e0 | 274 | for (i = 0; i < ARRAY_SIZE(io_target); i++) { |
a556c76a | 275 | struct regmap *target; |
259630e0 | 276 | struct resource *res; |
a556c76a | 277 | |
259630e0 CM |
278 | res = platform_get_resource_byname(pdev, IORESOURCE_MEM, |
279 | io_target[i].name); | |
280 | ||
281 | target = ocelot_regmap_init(ocelot, res); | |
45bce171 | 282 | if (IS_ERR(target)) { |
259630e0 CM |
283 | if (io_target[i].optional) { |
284 | ocelot->targets[io_target[i].id] = NULL; | |
45bce171 AT |
285 | continue; |
286 | } | |
6c30384e VO |
287 | err = PTR_ERR(target); |
288 | goto out_free_devlink; | |
45bce171 | 289 | } |
a556c76a | 290 | |
259630e0 | 291 | ocelot->targets[io_target[i].id] = target; |
a556c76a AB |
292 | } |
293 | ||
753a026c CL |
294 | if (ocelot->targets[FDMA]) |
295 | ocelot_fdma_init(pdev, ocelot); | |
296 | ||
19aedfbe QS |
297 | hsio = syscon_regmap_lookup_by_compatible("mscc,ocelot-hsio"); |
298 | if (IS_ERR(hsio)) { | |
299 | dev_err(&pdev->dev, "missing hsio syscon\n"); | |
6c30384e VO |
300 | err = PTR_ERR(hsio); |
301 | goto out_free_devlink; | |
19aedfbe QS |
302 | } |
303 | ||
304 | ocelot->targets[HSIO] = hsio; | |
305 | ||
dc3de2a2 | 306 | err = ocelot_chip_init(ocelot, &ocelot_ops); |
a556c76a | 307 | if (err) |
6c30384e | 308 | goto out_free_devlink; |
a556c76a | 309 | |
4e3b0468 | 310 | irq_xtr = platform_get_irq_byname(pdev, "xtr"); |
4160d9ec DC |
311 | if (irq_xtr < 0) { |
312 | err = irq_xtr; | |
6c30384e | 313 | goto out_free_devlink; |
4160d9ec | 314 | } |
a556c76a | 315 | |
4e3b0468 | 316 | err = devm_request_threaded_irq(&pdev->dev, irq_xtr, NULL, |
a556c76a AB |
317 | ocelot_xtr_irq_handler, IRQF_ONESHOT, |
318 | "frame extraction", ocelot); | |
319 | if (err) | |
6c30384e | 320 | goto out_free_devlink; |
a556c76a | 321 | |
4e3b0468 AT |
322 | irq_ptp_rdy = platform_get_irq_byname(pdev, "ptp_rdy"); |
323 | if (irq_ptp_rdy > 0 && ocelot->targets[PTP]) { | |
324 | err = devm_request_threaded_irq(&pdev->dev, irq_ptp_rdy, NULL, | |
325 | ocelot_ptp_rdy_irq_handler, | |
326 | IRQF_ONESHOT, "ptp ready", | |
327 | ocelot); | |
328 | if (err) | |
6c30384e | 329 | goto out_free_devlink; |
4e3b0468 AT |
330 | |
331 | /* Both the PTP interrupt and the PTP bank are available */ | |
332 | ocelot->ptp = 1; | |
333 | } | |
334 | ||
a556c76a AB |
335 | ports = of_get_child_by_name(np, "ethernet-ports"); |
336 | if (!ports) { | |
7c411799 | 337 | dev_err(ocelot->dev, "no ethernet-ports child node found\n"); |
6c30384e VO |
338 | err = -ENODEV; |
339 | goto out_free_devlink; | |
a556c76a AB |
340 | } |
341 | ||
342 | ocelot->num_phys_ports = of_get_child_count(ports); | |
edd2410b | 343 | ocelot->num_flooding_pgids = 1; |
a556c76a | 344 | |
8551cdeb | 345 | ocelot->vcap = vsc7514_vcap_props; |
77043c37 XY |
346 | |
347 | ocelot->vcap_pol.base = VSC7514_VCAP_POLICER_BASE; | |
348 | ocelot->vcap_pol.max = VSC7514_VCAP_POLICER_MAX; | |
349 | ||
2d44b097 | 350 | ocelot->npi = -1; |
e0632940 | 351 | |
d1cc0e93 VO |
352 | err = ocelot_init(ocelot); |
353 | if (err) | |
354 | goto out_put_ports; | |
355 | ||
6c30384e VO |
356 | err = mscc_ocelot_init_ports(pdev, ports); |
357 | if (err) | |
358 | goto out_ocelot_devlink_unregister; | |
359 | ||
753a026c CL |
360 | if (ocelot->fdma) |
361 | ocelot_fdma_start(ocelot); | |
362 | ||
f59fd9ca VO |
363 | err = ocelot_devlink_sb_register(ocelot); |
364 | if (err) | |
365 | goto out_ocelot_release_ports; | |
366 | ||
2b49d128 YL |
367 | if (ocelot->ptp) { |
368 | err = ocelot_init_timestamp(ocelot, &ocelot_ptp_clock_info); | |
369 | if (err) { | |
370 | dev_err(ocelot->dev, | |
371 | "Timestamp initialization failed\n"); | |
372 | ocelot->ptp = 0; | |
373 | } | |
374 | } | |
375 | ||
a556c76a | 376 | register_netdevice_notifier(&ocelot_netdevice_nb); |
56da64bc | 377 | register_switchdev_notifier(&ocelot_switchdev_nb); |
0e332c85 | 378 | register_switchdev_blocking_notifier(&ocelot_switchdev_blocking_nb); |
a556c76a | 379 | |
f87675b8 | 380 | of_node_put(ports); |
67d78e7f | 381 | devlink_register(devlink); |
f87675b8 | 382 | |
a556c76a AB |
383 | dev_info(&pdev->dev, "Ocelot switch probed\n"); |
384 | ||
f87675b8 CJ |
385 | return 0; |
386 | ||
f59fd9ca VO |
387 | out_ocelot_release_ports: |
388 | mscc_ocelot_release_ports(ocelot); | |
389 | mscc_ocelot_teardown_devlink_ports(ocelot); | |
6c30384e | 390 | out_ocelot_devlink_unregister: |
f87675b8 | 391 | ocelot_deinit(ocelot); |
d2c50b1c WY |
392 | out_put_ports: |
393 | of_node_put(ports); | |
6c30384e VO |
394 | out_free_devlink: |
395 | devlink_free(devlink); | |
a556c76a AB |
396 | return err; |
397 | } | |
398 | ||
0eaca7a9 | 399 | static void mscc_ocelot_remove(struct platform_device *pdev) |
a556c76a AB |
400 | { |
401 | struct ocelot *ocelot = platform_get_drvdata(pdev); | |
402 | ||
753a026c CL |
403 | if (ocelot->fdma) |
404 | ocelot_fdma_deinit(ocelot); | |
67d78e7f | 405 | devlink_unregister(ocelot->devlink); |
2b49d128 | 406 | ocelot_deinit_timestamp(ocelot); |
f59fd9ca | 407 | ocelot_devlink_sb_unregister(ocelot); |
22cdb493 | 408 | mscc_ocelot_release_ports(ocelot); |
6c30384e | 409 | mscc_ocelot_teardown_devlink_ports(ocelot); |
a556c76a | 410 | ocelot_deinit(ocelot); |
0e332c85 | 411 | unregister_switchdev_blocking_notifier(&ocelot_switchdev_blocking_nb); |
56da64bc | 412 | unregister_switchdev_notifier(&ocelot_switchdev_nb); |
a556c76a | 413 | unregister_netdevice_notifier(&ocelot_netdevice_nb); |
6c30384e | 414 | devlink_free(ocelot->devlink); |
a556c76a AB |
415 | } |
416 | ||
417 | static struct platform_driver mscc_ocelot_driver = { | |
418 | .probe = mscc_ocelot_probe, | |
0eaca7a9 | 419 | .remove_new = mscc_ocelot_remove, |
a556c76a AB |
420 | .driver = { |
421 | .name = "ocelot-switch", | |
422 | .of_match_table = mscc_ocelot_match, | |
423 | }, | |
424 | }; | |
425 | ||
426 | module_platform_driver(mscc_ocelot_driver); | |
427 | ||
428 | MODULE_DESCRIPTION("Microsemi Ocelot switch driver"); | |
429 | MODULE_AUTHOR("Alexandre Belloni <alexandre.belloni@bootlin.com>"); | |
430 | MODULE_LICENSE("Dual MIT/GPL"); |