From 5e95329b701c4edf6c4d72487ec0369fa148c0bd Mon Sep 17 00:00:00 2001 From: Florian Fainelli Date: Fri, 22 Mar 2013 10:50:50 +0000 Subject: dsa: add device tree bindings to register DSA switches This patch adds support for registering DSA switches using Device Tree bindings. Note that we support programming the switch routing table even though no in-tree user seems to require it. I tested this on Armada 370 with a Marvell 88E6172 (not supported by mainline yet). Signed-off-by: Florian Fainelli Signed-off-by: David S. Miller --- net/dsa/dsa.c | 233 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 228 insertions(+), 5 deletions(-) (limited to 'net/dsa/dsa.c') diff --git a/net/dsa/dsa.c b/net/dsa/dsa.c index 2bc62ea857c8..908bc11082db 100644 --- a/net/dsa/dsa.c +++ b/net/dsa/dsa.c @@ -1,6 +1,7 @@ /* * net/dsa/dsa.c - Hardware switch handling * Copyright (c) 2008-2009 Marvell Semiconductor + * Copyright (c) 2013 Florian Fainelli * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -14,6 +15,9 @@ #include #include #include +#include +#include +#include #include "dsa_priv.h" char dsa_driver_version[] = "0.1"; @@ -287,34 +291,239 @@ static struct net_device *dev_to_net_device(struct device *dev) return NULL; } +#ifdef CONFIG_OF +static int dsa_of_setup_routing_table(struct dsa_platform_data *pd, + struct dsa_chip_data *cd, + int chip_index, + struct device_node *link) +{ + int ret; + const __be32 *reg; + int link_port_addr; + int link_sw_addr; + struct device_node *parent_sw; + int len; + + parent_sw = of_get_parent(link); + if (!parent_sw) + return -EINVAL; + + reg = of_get_property(parent_sw, "reg", &len); + if (!reg || (len != sizeof(*reg) * 2)) + return -EINVAL; + + link_sw_addr = be32_to_cpup(reg + 1); + + if (link_sw_addr >= pd->nr_chips) + return -EINVAL; + + /* First time routing table allocation */ + if (!cd->rtable) { + cd->rtable = kmalloc(pd->nr_chips * sizeof(s8), GFP_KERNEL); + if (!cd->rtable) + return -ENOMEM; + + /* default to no valid uplink/downlink */ + memset(cd->rtable, -1, pd->nr_chips * sizeof(s8)); + } + + reg = of_get_property(link, "reg", NULL); + if (!reg) { + ret = -EINVAL; + goto out; + } + + link_port_addr = be32_to_cpup(reg); + + cd->rtable[link_sw_addr] = link_port_addr; + + return 0; +out: + kfree(cd->rtable); + return ret; +} + +static int dsa_of_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct device_node *child, *mdio, *ethernet, *port, *link; + struct mii_bus *mdio_bus; + struct platform_device *ethernet_dev; + struct dsa_platform_data *pd; + struct dsa_chip_data *cd; + const char *port_name; + int chip_index, port_index; + const unsigned int *sw_addr, *port_reg; + int ret, i; + + mdio = of_parse_phandle(np, "dsa,mii-bus", 0); + if (!mdio) + return -EINVAL; + + mdio_bus = of_mdio_find_bus(mdio); + if (!mdio_bus) + return -EINVAL; + + ethernet = of_parse_phandle(np, "dsa,ethernet", 0); + if (!ethernet) + return -EINVAL; + + ethernet_dev = of_find_device_by_node(ethernet); + if (!ethernet_dev) + return -ENODEV; + + pd = kzalloc(sizeof(*pd), GFP_KERNEL); + if (!pd) + return -ENOMEM; + + pdev->dev.platform_data = pd; + pd->netdev = ðernet_dev->dev; + pd->nr_chips = of_get_child_count(np); + if (pd->nr_chips > DSA_MAX_SWITCHES) + pd->nr_chips = DSA_MAX_SWITCHES; + + pd->chip = kzalloc(pd->nr_chips * sizeof(struct dsa_chip_data), + GFP_KERNEL); + if (!pd->chip) { + ret = -ENOMEM; + goto out_free; + } + + chip_index = 0; + for_each_available_child_of_node(np, child) { + cd = &pd->chip[chip_index]; + + cd->mii_bus = &mdio_bus->dev; + + sw_addr = of_get_property(child, "reg", NULL); + if (!sw_addr) + continue; + + cd->sw_addr = be32_to_cpup(sw_addr); + if (cd->sw_addr > PHY_MAX_ADDR) + continue; + + for_each_available_child_of_node(child, port) { + port_reg = of_get_property(port, "reg", NULL); + if (!port_reg) + continue; + + port_index = be32_to_cpup(port_reg); + + port_name = of_get_property(port, "label", NULL); + if (!port_name) + continue; + + cd->port_names[port_index] = kstrdup(port_name, + GFP_KERNEL); + if (!cd->port_names[port_index]) { + ret = -ENOMEM; + goto out_free_chip; + } + + link = of_parse_phandle(port, "link", 0); + + if (!strcmp(port_name, "dsa") && link && + pd->nr_chips > 1) { + ret = dsa_of_setup_routing_table(pd, cd, + chip_index, link); + if (ret) + goto out_free_chip; + } + + if (port_index == DSA_MAX_PORTS) + break; + } + } + + return 0; + +out_free_chip: + for (i = 0; i < pd->nr_chips; i++) { + port_index = 0; + while (pd->chip[i].port_names && + pd->chip[i].port_names[++port_index]) + kfree(pd->chip[i].port_names[port_index]); + kfree(pd->chip[i].rtable); + } + kfree(pd->chip); +out_free: + kfree(pd); + pdev->dev.platform_data = NULL; + return ret; +} + +static void dsa_of_remove(struct platform_device *pdev) +{ + struct dsa_platform_data *pd = pdev->dev.platform_data; + int i; + int port_index; + + if (!pdev->dev.of_node) + return; + + for (i = 0; i < pd->nr_chips; i++) { + port_index = 0; + while (pd->chip[i].port_names && + pd->chip[i].port_names[++port_index]) + kfree(pd->chip[i].port_names[port_index]); + kfree(pd->chip[i].rtable); + } + + kfree(pd->chip); + kfree(pd); +} +#else +static inline int dsa_of_probe(struct platform_device *pdev) +{ + return 0; +} + +static inline void dsa_of_remove(struct platform_device *pdev) +{ +} +#endif + static int dsa_probe(struct platform_device *pdev) { static int dsa_version_printed; struct dsa_platform_data *pd = pdev->dev.platform_data; struct net_device *dev; struct dsa_switch_tree *dst; - int i; + int i, ret; if (!dsa_version_printed++) printk(KERN_NOTICE "Distributed Switch Architecture " "driver version %s\n", dsa_driver_version); + if (pdev->dev.of_node) { + ret = dsa_of_probe(pdev); + if (ret) + return ret; + + pd = pdev->dev.platform_data; + } + if (pd == NULL || pd->netdev == NULL) return -EINVAL; dev = dev_to_net_device(pd->netdev); - if (dev == NULL) - return -EINVAL; + if (dev == NULL) { + ret = -EINVAL; + goto out; + } if (dev->dsa_ptr != NULL) { dev_put(dev); - return -EEXIST; + ret = -EEXIST; + goto out; } dst = kzalloc(sizeof(*dst), GFP_KERNEL); if (dst == NULL) { dev_put(dev); - return -ENOMEM; + ret = -ENOMEM; + goto out; } platform_set_drvdata(pdev, dst); @@ -366,6 +575,11 @@ static int dsa_probe(struct platform_device *pdev) } return 0; + +out: + dsa_of_remove(pdev); + + return ret; } static int dsa_remove(struct platform_device *pdev) @@ -385,6 +599,8 @@ static int dsa_remove(struct platform_device *pdev) dsa_switch_destroy(ds); } + dsa_of_remove(pdev); + return 0; } @@ -392,6 +608,12 @@ static void dsa_shutdown(struct platform_device *pdev) { } +static const struct of_device_id dsa_of_match_table[] = { + { .compatible = "marvell,dsa", }, + {} +}; +MODULE_DEVICE_TABLE(of, dsa_of_match_table); + static struct platform_driver dsa_driver = { .probe = dsa_probe, .remove = dsa_remove, @@ -399,6 +621,7 @@ static struct platform_driver dsa_driver = { .driver = { .name = "dsa", .owner = THIS_MODULE, + .of_match_table = dsa_of_match_table, }, }; -- cgit v1.2.3 From 21168245031062212c0b805d0bd466ee6dd4a16f Mon Sep 17 00:00:00 2001 From: Florian Fainelli Date: Mon, 25 Mar 2013 05:03:39 +0000 Subject: dsa: factor freeing of dsa_platform_data This patch factors the freeing of the struct dsa_platform_data manipulated by the driver identically in two places to a single function. Signed-off-by: Florian Fainelli Signed-off-by: David S. Miller --- net/dsa/dsa.c | 38 ++++++++++++++++++-------------------- 1 file changed, 18 insertions(+), 20 deletions(-) (limited to 'net/dsa/dsa.c') diff --git a/net/dsa/dsa.c b/net/dsa/dsa.c index 908bc11082db..aa2ff583b7ed 100644 --- a/net/dsa/dsa.c +++ b/net/dsa/dsa.c @@ -343,6 +343,21 @@ out: return ret; } +static void dsa_of_free_platform_data(struct dsa_platform_data *pd) +{ + int i; + int port_index; + + for (i = 0; i < pd->nr_chips; i++) { + port_index = 0; + while (pd->chip[i].port_names && + pd->chip[i].port_names[++port_index]) + kfree(pd->chip[i].port_names[port_index]); + kfree(pd->chip[i].rtable); + } + kfree(pd->chip); +} + static int dsa_of_probe(struct platform_device *pdev) { struct device_node *np = pdev->dev.of_node; @@ -354,7 +369,7 @@ static int dsa_of_probe(struct platform_device *pdev) const char *port_name; int chip_index, port_index; const unsigned int *sw_addr, *port_reg; - int ret, i; + int ret; mdio = of_parse_phandle(np, "dsa,mii-bus", 0); if (!mdio) @@ -439,14 +454,7 @@ static int dsa_of_probe(struct platform_device *pdev) return 0; out_free_chip: - for (i = 0; i < pd->nr_chips; i++) { - port_index = 0; - while (pd->chip[i].port_names && - pd->chip[i].port_names[++port_index]) - kfree(pd->chip[i].port_names[port_index]); - kfree(pd->chip[i].rtable); - } - kfree(pd->chip); + dsa_of_free_platform_data(pd); out_free: kfree(pd); pdev->dev.platform_data = NULL; @@ -456,21 +464,11 @@ out_free: static void dsa_of_remove(struct platform_device *pdev) { struct dsa_platform_data *pd = pdev->dev.platform_data; - int i; - int port_index; if (!pdev->dev.of_node) return; - for (i = 0; i < pd->nr_chips; i++) { - port_index = 0; - while (pd->chip[i].port_names && - pd->chip[i].port_names[++port_index]) - kfree(pd->chip[i].port_names[port_index]); - kfree(pd->chip[i].rtable); - } - - kfree(pd->chip); + dsa_of_free_platform_data(pd); kfree(pd); } #else -- cgit v1.2.3 From 5f64a7dbf593c2317f132c8252d04cdfe8d4b104 Mon Sep 17 00:00:00 2001 From: Florian Fainelli Date: Mon, 25 Mar 2013 05:03:40 +0000 Subject: dsa: fix freeing of sparse port allocation If we have defined a sparse port allocation which is non-contiguous and contains gaps, the code freeing port_names will just stop when it encouters a first NULL port_names, which is not right, we should iterate over all possible number of ports (DSA_MAX_PORTS) until we are done. Signed-off-by: Florian Fainelli Signed-off-by: David S. Miller --- net/dsa/dsa.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'net/dsa/dsa.c') diff --git a/net/dsa/dsa.c b/net/dsa/dsa.c index aa2ff583b7ed..0eb5d5e76dfb 100644 --- a/net/dsa/dsa.c +++ b/net/dsa/dsa.c @@ -350,9 +350,11 @@ static void dsa_of_free_platform_data(struct dsa_platform_data *pd) for (i = 0; i < pd->nr_chips; i++) { port_index = 0; - while (pd->chip[i].port_names && - pd->chip[i].port_names[++port_index]) - kfree(pd->chip[i].port_names[port_index]); + while (port_index < DSA_MAX_PORTS) { + if (pd->chip[i].port_names[port_index]) + kfree(pd->chip[i].port_names[port_index]); + port_index++; + } kfree(pd->chip[i].rtable); } kfree(pd->chip); -- cgit v1.2.3