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 <florian@openwrt.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
Florian Fainelli 2013-03-22 10:50:50 +00:00 committed by David S. Miller
parent eec2e6185f
commit 5e95329b70
2 changed files with 319 additions and 5 deletions

View File

@ -0,0 +1,91 @@
Marvell Distributed Switch Architecture Device Tree Bindings
------------------------------------------------------------
Required properties:
- compatible : Should be "marvell,dsa"
- #address-cells : Must be 2, first cell is the address on the MDIO bus
and second cell is the address in the switch tree.
Second cell is used only when cascading/chaining.
- #size-cells : Must be 0
- dsa,ethernet : Should be a phandle to a valid Ethernet device node
- dsa,mii-bus : Should be a phandle to a valid MDIO bus device node
Optionnal properties:
- interrupts : property with a value describing the switch
interrupt number (not supported by the driver)
A DSA node can contain multiple switch chips which are therefore child nodes of
the parent DSA node. The maximum number of allowed child nodes is 4
(DSA_MAX_SWITCHES).
Each of these switch child nodes should have the following required properties:
- reg : Describes the switch address on the MII bus
- #address-cells : Must be 1
- #size-cells : Must be 0
A switch may have multiple "port" children nodes
Each port children node must have the following mandatory properties:
- reg : Describes the port address in the switch
- label : Describes the label associated with this port, special
labels are "cpu" to indicate a CPU port and "dsa" to
indicate an uplink/downlink port.
Note that a port labelled "dsa" will imply checking for the uplink phandle
described below.
Optionnal property:
- link : Should be a phandle to another switch's DSA port.
This property is only used when switches are being
chained/cascaded together.
Example:
dsa@0 {
compatible = "marvell,dsa";
#address-cells = <1>;
#size-cells = <0>;
interrupts = <10>;
dsa,ethernet = <&ethernet0>;
dsa,mii-bus = <&mii_bus0>;
switch@0 {
#address-cells = <1>;
#size-cells = <0>;
reg = <16 0>; /* MDIO address 16, switch 0 in tree */
port@0 {
reg = <0>;
label = "lan1";
};
port@1 {
reg = <1>;
label = "lan2";
};
port@5 {
reg = <5>;
label = "cpu";
};
switch0uplink: port@6 {
reg = <6>;
label = "dsa";
link = <&switch1uplink>;
};
};
switch@1 {
#address-cells = <1>;
#size-cells = <0>;
reg = <17 1>; /* MDIO address 17, switch 1 in tree */
switch1uplink: port@0 {
reg = <0>;
label = "dsa";
link = <&switch0uplink>;
};
};
};

View File

@ -1,6 +1,7 @@
/* /*
* net/dsa/dsa.c - Hardware switch handling * net/dsa/dsa.c - Hardware switch handling
* Copyright (c) 2008-2009 Marvell Semiconductor * Copyright (c) 2008-2009 Marvell Semiconductor
* Copyright (c) 2013 Florian Fainelli <florian@openwrt.org>
* *
* This program is free software; you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -14,6 +15,9 @@
#include <linux/slab.h> #include <linux/slab.h>
#include <linux/module.h> #include <linux/module.h>
#include <net/dsa.h> #include <net/dsa.h>
#include <linux/of.h>
#include <linux/of_mdio.h>
#include <linux/of_platform.h>
#include "dsa_priv.h" #include "dsa_priv.h"
char dsa_driver_version[] = "0.1"; char dsa_driver_version[] = "0.1";
@ -287,34 +291,239 @@ static struct net_device *dev_to_net_device(struct device *dev)
return NULL; 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 = &ethernet_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_probe(struct platform_device *pdev)
{ {
static int dsa_version_printed; static int dsa_version_printed;
struct dsa_platform_data *pd = pdev->dev.platform_data; struct dsa_platform_data *pd = pdev->dev.platform_data;
struct net_device *dev; struct net_device *dev;
struct dsa_switch_tree *dst; struct dsa_switch_tree *dst;
int i; int i, ret;
if (!dsa_version_printed++) if (!dsa_version_printed++)
printk(KERN_NOTICE "Distributed Switch Architecture " printk(KERN_NOTICE "Distributed Switch Architecture "
"driver version %s\n", dsa_driver_version); "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) if (pd == NULL || pd->netdev == NULL)
return -EINVAL; return -EINVAL;
dev = dev_to_net_device(pd->netdev); dev = dev_to_net_device(pd->netdev);
if (dev == NULL) if (dev == NULL) {
return -EINVAL; ret = -EINVAL;
goto out;
}
if (dev->dsa_ptr != NULL) { if (dev->dsa_ptr != NULL) {
dev_put(dev); dev_put(dev);
return -EEXIST; ret = -EEXIST;
goto out;
} }
dst = kzalloc(sizeof(*dst), GFP_KERNEL); dst = kzalloc(sizeof(*dst), GFP_KERNEL);
if (dst == NULL) { if (dst == NULL) {
dev_put(dev); dev_put(dev);
return -ENOMEM; ret = -ENOMEM;
goto out;
} }
platform_set_drvdata(pdev, dst); platform_set_drvdata(pdev, dst);
@ -366,6 +575,11 @@ static int dsa_probe(struct platform_device *pdev)
} }
return 0; return 0;
out:
dsa_of_remove(pdev);
return ret;
} }
static int dsa_remove(struct platform_device *pdev) 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_switch_destroy(ds);
} }
dsa_of_remove(pdev);
return 0; 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 = { static struct platform_driver dsa_driver = {
.probe = dsa_probe, .probe = dsa_probe,
.remove = dsa_remove, .remove = dsa_remove,
@ -399,6 +621,7 @@ static struct platform_driver dsa_driver = {
.driver = { .driver = {
.name = "dsa", .name = "dsa",
.owner = THIS_MODULE, .owner = THIS_MODULE,
.of_match_table = dsa_of_match_table,
}, },
}; };