diff options
Diffstat (limited to 'net/dsa/dsa.c')
-rw-r--r-- | net/dsa/dsa.c | 233 |
1 files changed, 228 insertions, 5 deletions
diff --git a/net/dsa/dsa.c b/net/dsa/dsa.c index 2bc62ea857c8..0eb5d5e76dfb 100644 --- a/net/dsa/dsa.c +++ b/net/dsa/dsa.c | |||
@@ -1,6 +1,7 @@ | |||
1 | /* | 1 | /* |
2 | * net/dsa/dsa.c - Hardware switch handling | 2 | * net/dsa/dsa.c - Hardware switch handling |
3 | * Copyright (c) 2008-2009 Marvell Semiconductor | 3 | * Copyright (c) 2008-2009 Marvell Semiconductor |
4 | * Copyright (c) 2013 Florian Fainelli <florian@openwrt.org> | ||
4 | * | 5 | * |
5 | * This program is free software; you can redistribute it and/or modify | 6 | * This program is free software; you can redistribute it and/or modify |
6 | * it under the terms of the GNU General Public License as published by | 7 | * it under the terms of the GNU General Public License as published by |
@@ -14,6 +15,9 @@ | |||
14 | #include <linux/slab.h> | 15 | #include <linux/slab.h> |
15 | #include <linux/module.h> | 16 | #include <linux/module.h> |
16 | #include <net/dsa.h> | 17 | #include <net/dsa.h> |
18 | #include <linux/of.h> | ||
19 | #include <linux/of_mdio.h> | ||
20 | #include <linux/of_platform.h> | ||
17 | #include "dsa_priv.h" | 21 | #include "dsa_priv.h" |
18 | 22 | ||
19 | char dsa_driver_version[] = "0.1"; | 23 | char dsa_driver_version[] = "0.1"; |
@@ -287,34 +291,239 @@ static struct net_device *dev_to_net_device(struct device *dev) | |||
287 | return NULL; | 291 | return NULL; |
288 | } | 292 | } |
289 | 293 | ||
294 | #ifdef CONFIG_OF | ||
295 | static int dsa_of_setup_routing_table(struct dsa_platform_data *pd, | ||
296 | struct dsa_chip_data *cd, | ||
297 | int chip_index, | ||
298 | struct device_node *link) | ||
299 | { | ||
300 | int ret; | ||
301 | const __be32 *reg; | ||
302 | int link_port_addr; | ||
303 | int link_sw_addr; | ||
304 | struct device_node *parent_sw; | ||
305 | int len; | ||
306 | |||
307 | parent_sw = of_get_parent(link); | ||
308 | if (!parent_sw) | ||
309 | return -EINVAL; | ||
310 | |||
311 | reg = of_get_property(parent_sw, "reg", &len); | ||
312 | if (!reg || (len != sizeof(*reg) * 2)) | ||
313 | return -EINVAL; | ||
314 | |||
315 | link_sw_addr = be32_to_cpup(reg + 1); | ||
316 | |||
317 | if (link_sw_addr >= pd->nr_chips) | ||
318 | return -EINVAL; | ||
319 | |||
320 | /* First time routing table allocation */ | ||
321 | if (!cd->rtable) { | ||
322 | cd->rtable = kmalloc(pd->nr_chips * sizeof(s8), GFP_KERNEL); | ||
323 | if (!cd->rtable) | ||
324 | return -ENOMEM; | ||
325 | |||
326 | /* default to no valid uplink/downlink */ | ||
327 | memset(cd->rtable, -1, pd->nr_chips * sizeof(s8)); | ||
328 | } | ||
329 | |||
330 | reg = of_get_property(link, "reg", NULL); | ||
331 | if (!reg) { | ||
332 | ret = -EINVAL; | ||
333 | goto out; | ||
334 | } | ||
335 | |||
336 | link_port_addr = be32_to_cpup(reg); | ||
337 | |||
338 | cd->rtable[link_sw_addr] = link_port_addr; | ||
339 | |||
340 | return 0; | ||
341 | out: | ||
342 | kfree(cd->rtable); | ||
343 | return ret; | ||
344 | } | ||
345 | |||
346 | static void dsa_of_free_platform_data(struct dsa_platform_data *pd) | ||
347 | { | ||
348 | int i; | ||
349 | int port_index; | ||
350 | |||
351 | for (i = 0; i < pd->nr_chips; i++) { | ||
352 | port_index = 0; | ||
353 | while (port_index < DSA_MAX_PORTS) { | ||
354 | if (pd->chip[i].port_names[port_index]) | ||
355 | kfree(pd->chip[i].port_names[port_index]); | ||
356 | port_index++; | ||
357 | } | ||
358 | kfree(pd->chip[i].rtable); | ||
359 | } | ||
360 | kfree(pd->chip); | ||
361 | } | ||
362 | |||
363 | static int dsa_of_probe(struct platform_device *pdev) | ||
364 | { | ||
365 | struct device_node *np = pdev->dev.of_node; | ||
366 | struct device_node *child, *mdio, *ethernet, *port, *link; | ||
367 | struct mii_bus *mdio_bus; | ||
368 | struct platform_device *ethernet_dev; | ||
369 | struct dsa_platform_data *pd; | ||
370 | struct dsa_chip_data *cd; | ||
371 | const char *port_name; | ||
372 | int chip_index, port_index; | ||
373 | const unsigned int *sw_addr, *port_reg; | ||
374 | int ret; | ||
375 | |||
376 | mdio = of_parse_phandle(np, "dsa,mii-bus", 0); | ||
377 | if (!mdio) | ||
378 | return -EINVAL; | ||
379 | |||
380 | mdio_bus = of_mdio_find_bus(mdio); | ||
381 | if (!mdio_bus) | ||
382 | return -EINVAL; | ||
383 | |||
384 | ethernet = of_parse_phandle(np, "dsa,ethernet", 0); | ||
385 | if (!ethernet) | ||
386 | return -EINVAL; | ||
387 | |||
388 | ethernet_dev = of_find_device_by_node(ethernet); | ||
389 | if (!ethernet_dev) | ||
390 | return -ENODEV; | ||
391 | |||
392 | pd = kzalloc(sizeof(*pd), GFP_KERNEL); | ||
393 | if (!pd) | ||
394 | return -ENOMEM; | ||
395 | |||
396 | pdev->dev.platform_data = pd; | ||
397 | pd->netdev = ðernet_dev->dev; | ||
398 | pd->nr_chips = of_get_child_count(np); | ||
399 | if (pd->nr_chips > DSA_MAX_SWITCHES) | ||
400 | pd->nr_chips = DSA_MAX_SWITCHES; | ||
401 | |||
402 | pd->chip = kzalloc(pd->nr_chips * sizeof(struct dsa_chip_data), | ||
403 | GFP_KERNEL); | ||
404 | if (!pd->chip) { | ||
405 | ret = -ENOMEM; | ||
406 | goto out_free; | ||
407 | } | ||
408 | |||
409 | chip_index = 0; | ||
410 | for_each_available_child_of_node(np, child) { | ||
411 | cd = &pd->chip[chip_index]; | ||
412 | |||
413 | cd->mii_bus = &mdio_bus->dev; | ||
414 | |||
415 | sw_addr = of_get_property(child, "reg", NULL); | ||
416 | if (!sw_addr) | ||
417 | continue; | ||
418 | |||
419 | cd->sw_addr = be32_to_cpup(sw_addr); | ||
420 | if (cd->sw_addr > PHY_MAX_ADDR) | ||
421 | continue; | ||
422 | |||
423 | for_each_available_child_of_node(child, port) { | ||
424 | port_reg = of_get_property(port, "reg", NULL); | ||
425 | if (!port_reg) | ||
426 | continue; | ||
427 | |||
428 | port_index = be32_to_cpup(port_reg); | ||
429 | |||
430 | port_name = of_get_property(port, "label", NULL); | ||
431 | if (!port_name) | ||
432 | continue; | ||
433 | |||
434 | cd->port_names[port_index] = kstrdup(port_name, | ||
435 | GFP_KERNEL); | ||
436 | if (!cd->port_names[port_index]) { | ||
437 | ret = -ENOMEM; | ||
438 | goto out_free_chip; | ||
439 | } | ||
440 | |||
441 | link = of_parse_phandle(port, "link", 0); | ||
442 | |||
443 | if (!strcmp(port_name, "dsa") && link && | ||
444 | pd->nr_chips > 1) { | ||
445 | ret = dsa_of_setup_routing_table(pd, cd, | ||
446 | chip_index, link); | ||
447 | if (ret) | ||
448 | goto out_free_chip; | ||
449 | } | ||
450 | |||
451 | if (port_index == DSA_MAX_PORTS) | ||
452 | break; | ||
453 | } | ||
454 | } | ||
455 | |||
456 | return 0; | ||
457 | |||
458 | out_free_chip: | ||
459 | dsa_of_free_platform_data(pd); | ||
460 | out_free: | ||
461 | kfree(pd); | ||
462 | pdev->dev.platform_data = NULL; | ||
463 | return ret; | ||
464 | } | ||
465 | |||
466 | static void dsa_of_remove(struct platform_device *pdev) | ||
467 | { | ||
468 | struct dsa_platform_data *pd = pdev->dev.platform_data; | ||
469 | |||
470 | if (!pdev->dev.of_node) | ||
471 | return; | ||
472 | |||
473 | dsa_of_free_platform_data(pd); | ||
474 | kfree(pd); | ||
475 | } | ||
476 | #else | ||
477 | static inline int dsa_of_probe(struct platform_device *pdev) | ||
478 | { | ||
479 | return 0; | ||
480 | } | ||
481 | |||
482 | static inline void dsa_of_remove(struct platform_device *pdev) | ||
483 | { | ||
484 | } | ||
485 | #endif | ||
486 | |||
290 | static int dsa_probe(struct platform_device *pdev) | 487 | static int dsa_probe(struct platform_device *pdev) |
291 | { | 488 | { |
292 | static int dsa_version_printed; | 489 | static int dsa_version_printed; |
293 | struct dsa_platform_data *pd = pdev->dev.platform_data; | 490 | struct dsa_platform_data *pd = pdev->dev.platform_data; |
294 | struct net_device *dev; | 491 | struct net_device *dev; |
295 | struct dsa_switch_tree *dst; | 492 | struct dsa_switch_tree *dst; |
296 | int i; | 493 | int i, ret; |
297 | 494 | ||
298 | if (!dsa_version_printed++) | 495 | if (!dsa_version_printed++) |
299 | printk(KERN_NOTICE "Distributed Switch Architecture " | 496 | printk(KERN_NOTICE "Distributed Switch Architecture " |
300 | "driver version %s\n", dsa_driver_version); | 497 | "driver version %s\n", dsa_driver_version); |
301 | 498 | ||
499 | if (pdev->dev.of_node) { | ||
500 | ret = dsa_of_probe(pdev); | ||
501 | if (ret) | ||
502 | return ret; | ||
503 | |||
504 | pd = pdev->dev.platform_data; | ||
505 | } | ||
506 | |||
302 | if (pd == NULL || pd->netdev == NULL) | 507 | if (pd == NULL || pd->netdev == NULL) |
303 | return -EINVAL; | 508 | return -EINVAL; |
304 | 509 | ||
305 | dev = dev_to_net_device(pd->netdev); | 510 | dev = dev_to_net_device(pd->netdev); |
306 | if (dev == NULL) | 511 | if (dev == NULL) { |
307 | return -EINVAL; | 512 | ret = -EINVAL; |
513 | goto out; | ||
514 | } | ||
308 | 515 | ||
309 | if (dev->dsa_ptr != NULL) { | 516 | if (dev->dsa_ptr != NULL) { |
310 | dev_put(dev); | 517 | dev_put(dev); |
311 | return -EEXIST; | 518 | ret = -EEXIST; |
519 | goto out; | ||
312 | } | 520 | } |
313 | 521 | ||
314 | dst = kzalloc(sizeof(*dst), GFP_KERNEL); | 522 | dst = kzalloc(sizeof(*dst), GFP_KERNEL); |
315 | if (dst == NULL) { | 523 | if (dst == NULL) { |
316 | dev_put(dev); | 524 | dev_put(dev); |
317 | return -ENOMEM; | 525 | ret = -ENOMEM; |
526 | goto out; | ||
318 | } | 527 | } |
319 | 528 | ||
320 | platform_set_drvdata(pdev, dst); | 529 | platform_set_drvdata(pdev, dst); |
@@ -366,6 +575,11 @@ static int dsa_probe(struct platform_device *pdev) | |||
366 | } | 575 | } |
367 | 576 | ||
368 | return 0; | 577 | return 0; |
578 | |||
579 | out: | ||
580 | dsa_of_remove(pdev); | ||
581 | |||
582 | return ret; | ||
369 | } | 583 | } |
370 | 584 | ||
371 | static int dsa_remove(struct platform_device *pdev) | 585 | static int dsa_remove(struct platform_device *pdev) |
@@ -385,6 +599,8 @@ static int dsa_remove(struct platform_device *pdev) | |||
385 | dsa_switch_destroy(ds); | 599 | dsa_switch_destroy(ds); |
386 | } | 600 | } |
387 | 601 | ||
602 | dsa_of_remove(pdev); | ||
603 | |||
388 | return 0; | 604 | return 0; |
389 | } | 605 | } |
390 | 606 | ||
@@ -392,6 +608,12 @@ static void dsa_shutdown(struct platform_device *pdev) | |||
392 | { | 608 | { |
393 | } | 609 | } |
394 | 610 | ||
611 | static const struct of_device_id dsa_of_match_table[] = { | ||
612 | { .compatible = "marvell,dsa", }, | ||
613 | {} | ||
614 | }; | ||
615 | MODULE_DEVICE_TABLE(of, dsa_of_match_table); | ||
616 | |||
395 | static struct platform_driver dsa_driver = { | 617 | static struct platform_driver dsa_driver = { |
396 | .probe = dsa_probe, | 618 | .probe = dsa_probe, |
397 | .remove = dsa_remove, | 619 | .remove = dsa_remove, |
@@ -399,6 +621,7 @@ static struct platform_driver dsa_driver = { | |||
399 | .driver = { | 621 | .driver = { |
400 | .name = "dsa", | 622 | .name = "dsa", |
401 | .owner = THIS_MODULE, | 623 | .owner = THIS_MODULE, |
624 | .of_match_table = dsa_of_match_table, | ||
402 | }, | 625 | }, |
403 | }; | 626 | }; |
404 | 627 | ||