Merge branch 'Convert-mv88e6060-to-mdio-device'
Andrew Lunn says: ==================== Convert mv88e6060 to mdio device This patchset builds upon the previous patches to mv88e6060. It adds support for probing the switch as an MDIO device and then removes the legacy probe method. Since this is the last device supporting legacy probe, this allows legacy probe to be removed, originally planned to be removed in 4.17, but took a bit longer. This change to the mv88e6060 is more risky than the previous patchset. Some attempts to test it have been made, by hacking the driver to match on an mv88e6352 so that it probes. These changes are all about probe, so it is a reasonable test. But testing on a real mv88e6060 would be great. ==================== Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
Коммит
b4d081df03
|
@ -1,12 +1,6 @@
|
|||
Distributed Switch Architecture Device Tree Bindings
|
||||
----------------------------------------------------
|
||||
|
||||
Two bindings exist, one of which has been deprecated due to
|
||||
limitations.
|
||||
|
||||
Current Binding
|
||||
---------------
|
||||
|
||||
Switches are true Linux devices and can be probed by any means. Once
|
||||
probed, they register to the DSA framework, passing a node
|
||||
pointer. This node is expected to fulfil the following binding, and
|
||||
|
@ -262,152 +256,3 @@ linked into one DSA cluster.
|
|||
};
|
||||
};
|
||||
};
|
||||
|
||||
Deprecated Binding
|
||||
------------------
|
||||
|
||||
The deprecated binding makes use of a platform device to represent the
|
||||
switches. The switches themselves are not Linux devices, and make use
|
||||
of an MDIO bus for management.
|
||||
|
||||
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
|
||||
|
||||
Optional 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 : Contains two fields. The first one describes the
|
||||
address on the MII bus. The second is the switch
|
||||
number that must be unique in cascaded configurations
|
||||
- #address-cells : Must be 1
|
||||
- #size-cells : Must be 0
|
||||
|
||||
A switch child node has the following optional property:
|
||||
|
||||
- eeprom-length : Set to the length of an EEPROM connected to the
|
||||
switch. Must be set if the switch can not detect
|
||||
the presence and/or size of a connected EEPROM,
|
||||
otherwise optional.
|
||||
|
||||
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.
|
||||
|
||||
Optional property:
|
||||
- link : Should be a list of phandles to another switch's DSA port.
|
||||
This property is only used when switches are being
|
||||
chained/cascaded together. This port is used as outgoing port
|
||||
towards the phandle port, which can be more than one hop away.
|
||||
|
||||
- phy-handle : Phandle to a PHY on an external MDIO bus, not the
|
||||
switch internal one. See
|
||||
Documentation/devicetree/bindings/net/ethernet.txt
|
||||
for details.
|
||||
|
||||
- phy-mode : String representing the connection to the designated
|
||||
PHY node specified by the 'phy-handle' property. See
|
||||
Documentation/devicetree/bindings/net/ethernet.txt
|
||||
for details.
|
||||
|
||||
- mii-bus : Should be a phandle to a valid MDIO bus device node.
|
||||
This mii-bus will be used in preference to the
|
||||
global dsa,mii-bus defined above, for this switch.
|
||||
|
||||
Optional subnodes:
|
||||
- fixed-link : Fixed-link subnode describing a link to a non-MDIO
|
||||
managed entity. See
|
||||
Documentation/devicetree/bindings/net/fixed-link.txt
|
||||
for details.
|
||||
|
||||
Example:
|
||||
|
||||
dsa@0 {
|
||||
compatible = "marvell,dsa";
|
||||
#address-cells = <2>;
|
||||
#size-cells = <0>;
|
||||
|
||||
interrupts = <10>;
|
||||
dsa,ethernet = <ðernet0>;
|
||||
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";
|
||||
phy-handle = <&phy0>;
|
||||
};
|
||||
|
||||
port@1 {
|
||||
reg = <1>;
|
||||
label = "lan2";
|
||||
};
|
||||
|
||||
port@5 {
|
||||
reg = <5>;
|
||||
label = "cpu";
|
||||
};
|
||||
|
||||
switch0port6: port@6 {
|
||||
reg = <6>;
|
||||
label = "dsa";
|
||||
link = <&switch1port0
|
||||
&switch2port0>;
|
||||
};
|
||||
};
|
||||
|
||||
switch@1 {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
reg = <17 1>; /* MDIO address 17, switch 1 in tree */
|
||||
mii-bus = <&mii_bus1>;
|
||||
reset-gpios = <&gpio5 1 GPIO_ACTIVE_LOW>;
|
||||
|
||||
switch1port0: port@0 {
|
||||
reg = <0>;
|
||||
label = "dsa";
|
||||
link = <&switch0port6>;
|
||||
};
|
||||
switch1port1: port@1 {
|
||||
reg = <1>;
|
||||
label = "dsa";
|
||||
link = <&switch2port1>;
|
||||
};
|
||||
};
|
||||
|
||||
switch@2 {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
reg = <18 2>; /* MDIO address 18, switch 2 in tree */
|
||||
mii-bus = <&mii_bus1>;
|
||||
|
||||
switch2port0: port@0 {
|
||||
reg = <0>;
|
||||
label = "dsa";
|
||||
link = <&switch1port1
|
||||
&switch0port6>;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
|
@ -41,7 +41,7 @@ config NET_DSA_MT7530
|
|||
|
||||
config NET_DSA_MV88E6060
|
||||
tristate "Marvell 88E6060 ethernet switch chip support"
|
||||
depends on NET_DSA && NET_DSA_LEGACY
|
||||
depends on NET_DSA
|
||||
select NET_DSA_TAG_TRAILER
|
||||
---help---
|
||||
This enables support for the Marvell 88E6060 ethernet switch
|
||||
|
|
|
@ -48,27 +48,6 @@ static enum dsa_tag_protocol mv88e6060_get_tag_protocol(struct dsa_switch *ds,
|
|||
return DSA_TAG_PROTO_TRAILER;
|
||||
}
|
||||
|
||||
static const char *mv88e6060_drv_probe(struct device *dsa_dev,
|
||||
struct device *host_dev, int sw_addr,
|
||||
void **_priv)
|
||||
{
|
||||
struct mii_bus *bus = dsa_host_dev_to_mii_bus(host_dev);
|
||||
struct mv88e6060_priv *priv;
|
||||
const char *name;
|
||||
|
||||
name = mv88e6060_get_name(bus, sw_addr);
|
||||
if (name) {
|
||||
priv = devm_kzalloc(dsa_dev, sizeof(*priv), GFP_KERNEL);
|
||||
if (!priv)
|
||||
return NULL;
|
||||
*_priv = priv;
|
||||
priv->bus = bus;
|
||||
priv->sw_addr = sw_addr;
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
static int mv88e6060_switch_reset(struct mv88e6060_priv *priv)
|
||||
{
|
||||
int i;
|
||||
|
@ -266,28 +245,68 @@ mv88e6060_phy_write(struct dsa_switch *ds, int port, int regnum, u16 val)
|
|||
|
||||
static const struct dsa_switch_ops mv88e6060_switch_ops = {
|
||||
.get_tag_protocol = mv88e6060_get_tag_protocol,
|
||||
.probe = mv88e6060_drv_probe,
|
||||
.setup = mv88e6060_setup,
|
||||
.phy_read = mv88e6060_phy_read,
|
||||
.phy_write = mv88e6060_phy_write,
|
||||
};
|
||||
|
||||
static struct dsa_switch_driver mv88e6060_switch_drv = {
|
||||
.ops = &mv88e6060_switch_ops,
|
||||
static int mv88e6060_probe(struct mdio_device *mdiodev)
|
||||
{
|
||||
struct device *dev = &mdiodev->dev;
|
||||
struct mv88e6060_priv *priv;
|
||||
struct dsa_switch *ds;
|
||||
const char *name;
|
||||
|
||||
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
|
||||
if (!priv)
|
||||
return -ENOMEM;
|
||||
|
||||
priv->bus = mdiodev->bus;
|
||||
priv->sw_addr = mdiodev->addr;
|
||||
|
||||
name = mv88e6060_get_name(priv->bus, priv->sw_addr);
|
||||
if (!name)
|
||||
return -ENODEV;
|
||||
|
||||
dev_info(dev, "switch %s detected\n", name);
|
||||
|
||||
ds = dsa_switch_alloc(dev, MV88E6060_PORTS);
|
||||
if (!ds)
|
||||
return -ENOMEM;
|
||||
|
||||
ds->priv = priv;
|
||||
ds->dev = dev;
|
||||
ds->ops = &mv88e6060_switch_ops;
|
||||
|
||||
dev_set_drvdata(dev, ds);
|
||||
|
||||
return dsa_register_switch(ds);
|
||||
}
|
||||
|
||||
static void mv88e6060_remove(struct mdio_device *mdiodev)
|
||||
{
|
||||
struct dsa_switch *ds = dev_get_drvdata(&mdiodev->dev);
|
||||
|
||||
dsa_unregister_switch(ds);
|
||||
}
|
||||
|
||||
static const struct of_device_id mv88e6060_of_match[] = {
|
||||
{
|
||||
.compatible = "marvell,mv88e6060",
|
||||
},
|
||||
{ /* sentinel */ },
|
||||
};
|
||||
|
||||
static int __init mv88e6060_init(void)
|
||||
{
|
||||
register_switch_driver(&mv88e6060_switch_drv);
|
||||
return 0;
|
||||
}
|
||||
module_init(mv88e6060_init);
|
||||
static struct mdio_driver mv88e6060_driver = {
|
||||
.probe = mv88e6060_probe,
|
||||
.remove = mv88e6060_remove,
|
||||
.mdiodrv.driver = {
|
||||
.name = "mv88e6060",
|
||||
.of_match_table = mv88e6060_of_match,
|
||||
},
|
||||
};
|
||||
|
||||
static void __exit mv88e6060_cleanup(void)
|
||||
{
|
||||
unregister_switch_driver(&mv88e6060_switch_drv);
|
||||
}
|
||||
module_exit(mv88e6060_cleanup);
|
||||
mdio_module_driver(mv88e6060_driver);
|
||||
|
||||
MODULE_AUTHOR("Lennert Buytenhek <buytenh@wantstofly.org>");
|
||||
MODULE_DESCRIPTION("Driver for Marvell 88E6060 ethernet switch chip");
|
||||
|
|
|
@ -318,15 +318,6 @@ static inline bool dsa_port_is_vlan_filtering(const struct dsa_port *dp)
|
|||
typedef int dsa_fdb_dump_cb_t(const unsigned char *addr, u16 vid,
|
||||
bool is_static, void *data);
|
||||
struct dsa_switch_ops {
|
||||
#if IS_ENABLED(CONFIG_NET_DSA_LEGACY)
|
||||
/*
|
||||
* Legacy probing.
|
||||
*/
|
||||
const char *(*probe)(struct device *dsa_dev,
|
||||
struct device *host_dev, int sw_addr,
|
||||
void **priv);
|
||||
#endif
|
||||
|
||||
enum dsa_tag_protocol (*get_tag_protocol)(struct dsa_switch *ds,
|
||||
int port);
|
||||
|
||||
|
@ -516,20 +507,6 @@ struct dsa_switch_driver {
|
|||
const struct dsa_switch_ops *ops;
|
||||
};
|
||||
|
||||
#if IS_ENABLED(CONFIG_NET_DSA_LEGACY)
|
||||
/* Legacy driver registration */
|
||||
void register_switch_driver(struct dsa_switch_driver *type);
|
||||
void unregister_switch_driver(struct dsa_switch_driver *type);
|
||||
struct mii_bus *dsa_host_dev_to_mii_bus(struct device *dev);
|
||||
|
||||
#else
|
||||
static inline void register_switch_driver(struct dsa_switch_driver *type) { }
|
||||
static inline void unregister_switch_driver(struct dsa_switch_driver *type) { }
|
||||
static inline struct mii_bus *dsa_host_dev_to_mii_bus(struct device *dev)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
struct net_device *dsa_dev_to_net_device(struct device *dev);
|
||||
|
||||
/* Keep inline for faster access in hot path */
|
||||
|
|
|
@ -17,15 +17,6 @@ menuconfig NET_DSA
|
|||
|
||||
if NET_DSA
|
||||
|
||||
config NET_DSA_LEGACY
|
||||
bool "Support for older platform device and Device Tree registration"
|
||||
default y
|
||||
---help---
|
||||
Say Y if you want to enable support for the older platform device and
|
||||
deprecated Device Tree binding registration.
|
||||
|
||||
This feature is scheduled for removal in 4.17.
|
||||
|
||||
config NET_DSA_TAG_BRCM_COMMON
|
||||
tristate
|
||||
default n
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
# the core
|
||||
obj-$(CONFIG_NET_DSA) += dsa_core.o
|
||||
dsa_core-y += dsa.o dsa2.o master.o port.o slave.o switch.o
|
||||
dsa_core-$(CONFIG_NET_DSA_LEGACY) += legacy.o
|
||||
|
||||
# tagging formats
|
||||
obj-$(CONFIG_NET_DSA_TAG_BRCM_COMMON) += tag_brcm.o
|
||||
|
|
|
@ -346,10 +346,6 @@ static int __init dsa_init_module(void)
|
|||
if (rc)
|
||||
return rc;
|
||||
|
||||
rc = dsa_legacy_register();
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
dev_add_pack(&dsa_pack_type);
|
||||
|
||||
dsa_tag_driver_register(&DSA_TAG_DRIVER_NAME(none_ops),
|
||||
|
@ -365,7 +361,6 @@ static void __exit dsa_cleanup_module(void)
|
|||
|
||||
dsa_slave_unregister_notifier();
|
||||
dev_remove_pack(&dsa_pack_type);
|
||||
dsa_legacy_unregister();
|
||||
destroy_workqueue(dsa_owq);
|
||||
}
|
||||
module_exit(dsa_cleanup_module);
|
||||
|
|
|
@ -90,18 +90,6 @@ void dsa_tag_driver_put(const struct dsa_device_ops *ops);
|
|||
bool dsa_schedule_work(struct work_struct *work);
|
||||
const char *dsa_tag_protocol_to_str(const struct dsa_device_ops *ops);
|
||||
|
||||
/* legacy.c */
|
||||
#if IS_ENABLED(CONFIG_NET_DSA_LEGACY)
|
||||
int dsa_legacy_register(void);
|
||||
void dsa_legacy_unregister(void);
|
||||
#else
|
||||
static inline int dsa_legacy_register(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void dsa_legacy_unregister(void) { }
|
||||
#endif
|
||||
int dsa_legacy_fdb_add(struct ndmsg *ndm, struct nlattr *tb[],
|
||||
struct net_device *dev,
|
||||
const unsigned char *addr, u16 vid,
|
||||
|
|
747
net/dsa/legacy.c
747
net/dsa/legacy.c
|
@ -1,747 +0,0 @@
|
|||
/*
|
||||
* net/dsa/legacy.c - Hardware switch handling
|
||||
* 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
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*/
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_mdio.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/of_net.h>
|
||||
#include <linux/netdevice.h>
|
||||
#include <linux/sysfs.h>
|
||||
#include <linux/phy_fixed.h>
|
||||
#include <linux/etherdevice.h>
|
||||
|
||||
#include "dsa_priv.h"
|
||||
|
||||
/* switch driver registration ***********************************************/
|
||||
static DEFINE_MUTEX(dsa_switch_drivers_mutex);
|
||||
static LIST_HEAD(dsa_switch_drivers);
|
||||
|
||||
void register_switch_driver(struct dsa_switch_driver *drv)
|
||||
{
|
||||
mutex_lock(&dsa_switch_drivers_mutex);
|
||||
list_add_tail(&drv->list, &dsa_switch_drivers);
|
||||
mutex_unlock(&dsa_switch_drivers_mutex);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(register_switch_driver);
|
||||
|
||||
void unregister_switch_driver(struct dsa_switch_driver *drv)
|
||||
{
|
||||
mutex_lock(&dsa_switch_drivers_mutex);
|
||||
list_del_init(&drv->list);
|
||||
mutex_unlock(&dsa_switch_drivers_mutex);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(unregister_switch_driver);
|
||||
|
||||
static const struct dsa_switch_ops *
|
||||
dsa_switch_probe(struct device *parent, struct device *host_dev, int sw_addr,
|
||||
const char **_name, void **priv)
|
||||
{
|
||||
const struct dsa_switch_ops *ret;
|
||||
struct list_head *list;
|
||||
const char *name;
|
||||
|
||||
ret = NULL;
|
||||
name = NULL;
|
||||
|
||||
mutex_lock(&dsa_switch_drivers_mutex);
|
||||
list_for_each(list, &dsa_switch_drivers) {
|
||||
const struct dsa_switch_ops *ops;
|
||||
struct dsa_switch_driver *drv;
|
||||
|
||||
drv = list_entry(list, struct dsa_switch_driver, list);
|
||||
ops = drv->ops;
|
||||
|
||||
name = ops->probe(parent, host_dev, sw_addr, priv);
|
||||
if (name != NULL) {
|
||||
ret = ops;
|
||||
break;
|
||||
}
|
||||
}
|
||||
mutex_unlock(&dsa_switch_drivers_mutex);
|
||||
|
||||
*_name = name;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* basic switch operations **************************************************/
|
||||
static int dsa_cpu_dsa_setups(struct dsa_switch *ds)
|
||||
{
|
||||
int ret, port;
|
||||
|
||||
for (port = 0; port < ds->num_ports; port++) {
|
||||
if (!(dsa_is_cpu_port(ds, port) || dsa_is_dsa_port(ds, port)))
|
||||
continue;
|
||||
|
||||
ret = dsa_port_link_register_of(&ds->ports[port]);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dsa_switch_setup_one(struct dsa_switch *ds,
|
||||
struct net_device *master)
|
||||
{
|
||||
const struct dsa_switch_ops *ops = ds->ops;
|
||||
struct dsa_switch_tree *dst = ds->dst;
|
||||
struct dsa_chip_data *cd = ds->cd;
|
||||
bool valid_name_found = false;
|
||||
int index = ds->index;
|
||||
struct dsa_port *dp;
|
||||
int i, ret;
|
||||
|
||||
/*
|
||||
* Validate supplied switch configuration.
|
||||
*/
|
||||
for (i = 0; i < ds->num_ports; i++) {
|
||||
char *name;
|
||||
|
||||
dp = &ds->ports[i];
|
||||
|
||||
name = cd->port_names[i];
|
||||
if (name == NULL)
|
||||
continue;
|
||||
dp->name = name;
|
||||
|
||||
if (!strcmp(name, "cpu")) {
|
||||
if (dst->cpu_dp) {
|
||||
netdev_err(master,
|
||||
"multiple cpu ports?!\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
dst->cpu_dp = &ds->ports[i];
|
||||
dst->cpu_dp->master = master;
|
||||
dp->type = DSA_PORT_TYPE_CPU;
|
||||
} else if (!strcmp(name, "dsa")) {
|
||||
dp->type = DSA_PORT_TYPE_DSA;
|
||||
} else {
|
||||
dp->type = DSA_PORT_TYPE_USER;
|
||||
}
|
||||
valid_name_found = true;
|
||||
}
|
||||
|
||||
if (!valid_name_found && i == ds->num_ports)
|
||||
return -EINVAL;
|
||||
|
||||
/* Make the built-in MII bus mask match the number of ports,
|
||||
* switch drivers can override this later
|
||||
*/
|
||||
ds->phys_mii_mask |= dsa_user_ports(ds);
|
||||
|
||||
/*
|
||||
* If the CPU connects to this switch, set the switch tree
|
||||
* tagging protocol to the preferred tagging format of this
|
||||
* switch.
|
||||
*/
|
||||
if (dst->cpu_dp->ds == ds) {
|
||||
const struct dsa_device_ops *tag_ops;
|
||||
enum dsa_tag_protocol tag_protocol;
|
||||
|
||||
tag_protocol = ops->get_tag_protocol(ds, dst->cpu_dp->index);
|
||||
tag_ops = dsa_tag_driver_get(tag_protocol);
|
||||
if (IS_ERR(tag_ops))
|
||||
return PTR_ERR(tag_ops);
|
||||
|
||||
dst->cpu_dp->tag_ops = tag_ops;
|
||||
|
||||
/* Few copies for faster access in master receive hot path */
|
||||
dst->cpu_dp->rcv = dst->cpu_dp->tag_ops->rcv;
|
||||
dst->cpu_dp->dst = dst;
|
||||
}
|
||||
|
||||
dsa_tag_driver_put(dst->cpu_dp->tag_ops);
|
||||
|
||||
memcpy(ds->rtable, cd->rtable, sizeof(ds->rtable));
|
||||
|
||||
/*
|
||||
* Do basic register setup.
|
||||
*/
|
||||
ret = ops->setup(ds);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = dsa_switch_register_notifier(ds);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (!ds->slave_mii_bus && ops->phy_read) {
|
||||
ds->slave_mii_bus = devm_mdiobus_alloc(ds->dev);
|
||||
if (!ds->slave_mii_bus)
|
||||
return -ENOMEM;
|
||||
dsa_slave_mii_bus_init(ds);
|
||||
|
||||
ret = mdiobus_register(ds->slave_mii_bus);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Create network devices for physical switch ports.
|
||||
*/
|
||||
for (i = 0; i < ds->num_ports; i++) {
|
||||
ds->ports[i].dn = cd->port_dn[i];
|
||||
ds->ports[i].cpu_dp = dst->cpu_dp;
|
||||
|
||||
if (!dsa_is_user_port(ds, i))
|
||||
continue;
|
||||
|
||||
ret = dsa_slave_create(&ds->ports[i]);
|
||||
if (ret < 0)
|
||||
netdev_err(master, "[%d]: can't create dsa slave device for port %d(%s): %d\n",
|
||||
index, i, cd->port_names[i], ret);
|
||||
}
|
||||
|
||||
/* Perform configuration of the CPU and DSA ports */
|
||||
ret = dsa_cpu_dsa_setups(ds);
|
||||
if (ret < 0)
|
||||
netdev_err(master, "[%d] : can't configure CPU and DSA ports\n",
|
||||
index);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct dsa_switch *
|
||||
dsa_switch_setup(struct dsa_switch_tree *dst, struct net_device *master,
|
||||
int index, struct device *parent, struct device *host_dev)
|
||||
{
|
||||
struct dsa_chip_data *cd = dst->pd->chip + index;
|
||||
const struct dsa_switch_ops *ops;
|
||||
struct dsa_switch *ds;
|
||||
int ret;
|
||||
const char *name;
|
||||
void *priv;
|
||||
|
||||
/*
|
||||
* Probe for switch model.
|
||||
*/
|
||||
ops = dsa_switch_probe(parent, host_dev, cd->sw_addr, &name, &priv);
|
||||
if (!ops) {
|
||||
netdev_err(master, "[%d]: could not detect attached switch\n",
|
||||
index);
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
netdev_info(master, "[%d]: detected a %s switch\n",
|
||||
index, name);
|
||||
|
||||
|
||||
/*
|
||||
* Allocate and initialise switch state.
|
||||
*/
|
||||
ds = dsa_switch_alloc(parent, DSA_MAX_PORTS);
|
||||
if (!ds)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
ds->dst = dst;
|
||||
ds->index = index;
|
||||
ds->cd = cd;
|
||||
ds->ops = ops;
|
||||
ds->priv = priv;
|
||||
|
||||
ret = dsa_switch_setup_one(ds, master);
|
||||
if (ret)
|
||||
return ERR_PTR(ret);
|
||||
|
||||
return ds;
|
||||
}
|
||||
|
||||
static void dsa_switch_destroy(struct dsa_switch *ds)
|
||||
{
|
||||
int port;
|
||||
|
||||
/* Destroy network devices for physical switch ports. */
|
||||
for (port = 0; port < ds->num_ports; port++) {
|
||||
if (!dsa_is_user_port(ds, port))
|
||||
continue;
|
||||
|
||||
if (!ds->ports[port].slave)
|
||||
continue;
|
||||
|
||||
dsa_slave_destroy(ds->ports[port].slave);
|
||||
}
|
||||
|
||||
/* Disable configuration of the CPU and DSA ports */
|
||||
for (port = 0; port < ds->num_ports; port++) {
|
||||
if (!(dsa_is_cpu_port(ds, port) || dsa_is_dsa_port(ds, port)))
|
||||
continue;
|
||||
dsa_port_link_unregister_of(&ds->ports[port]);
|
||||
}
|
||||
|
||||
if (ds->slave_mii_bus && ds->ops->phy_read)
|
||||
mdiobus_unregister(ds->slave_mii_bus);
|
||||
|
||||
dsa_switch_unregister_notifier(ds);
|
||||
}
|
||||
|
||||
/* platform driver init and cleanup *****************************************/
|
||||
static int dev_is_class(struct device *dev, void *class)
|
||||
{
|
||||
if (dev->class != NULL && !strcmp(dev->class->name, class))
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct device *dev_find_class(struct device *parent, char *class)
|
||||
{
|
||||
if (dev_is_class(parent, class)) {
|
||||
get_device(parent);
|
||||
return parent;
|
||||
}
|
||||
|
||||
return device_find_child(parent, class, dev_is_class);
|
||||
}
|
||||
|
||||
struct mii_bus *dsa_host_dev_to_mii_bus(struct device *dev)
|
||||
{
|
||||
struct device *d;
|
||||
|
||||
d = dev_find_class(dev, "mdio_bus");
|
||||
if (d != NULL) {
|
||||
struct mii_bus *bus;
|
||||
|
||||
bus = to_mii_bus(d);
|
||||
put_device(d);
|
||||
|
||||
return bus;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(dsa_host_dev_to_mii_bus);
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
static int dsa_of_setup_routing_table(struct dsa_platform_data *pd,
|
||||
struct dsa_chip_data *cd,
|
||||
int chip_index, int port_index,
|
||||
struct device_node *link)
|
||||
{
|
||||
const __be32 *reg;
|
||||
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;
|
||||
|
||||
/*
|
||||
* Get the destination switch number from the second field of its 'reg'
|
||||
* property, i.e. for "reg = <0x19 1>" sw_addr is '1'.
|
||||
*/
|
||||
link_sw_addr = be32_to_cpup(reg + 1);
|
||||
|
||||
if (link_sw_addr >= pd->nr_chips)
|
||||
return -EINVAL;
|
||||
|
||||
cd->rtable[link_sw_addr] = port_index;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dsa_of_probe_links(struct dsa_platform_data *pd,
|
||||
struct dsa_chip_data *cd,
|
||||
int chip_index, int port_index,
|
||||
struct device_node *port,
|
||||
const char *port_name)
|
||||
{
|
||||
struct device_node *link;
|
||||
int link_index;
|
||||
int ret;
|
||||
|
||||
for (link_index = 0;; link_index++) {
|
||||
link = of_parse_phandle(port, "link", link_index);
|
||||
if (!link)
|
||||
break;
|
||||
|
||||
if (!strcmp(port_name, "dsa") && pd->nr_chips > 1) {
|
||||
ret = dsa_of_setup_routing_table(pd, cd, chip_index,
|
||||
port_index, link);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
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 (port_index < DSA_MAX_PORTS) {
|
||||
kfree(pd->chip[i].port_names[port_index]);
|
||||
port_index++;
|
||||
}
|
||||
|
||||
/* Drop our reference to the MDIO bus device */
|
||||
put_device(pd->chip[i].host_dev);
|
||||
}
|
||||
kfree(pd->chip);
|
||||
}
|
||||
|
||||
static int dsa_of_probe(struct device *dev)
|
||||
{
|
||||
struct device_node *np = dev->of_node;
|
||||
struct device_node *child, *mdio, *ethernet, *port;
|
||||
struct mii_bus *mdio_bus, *mdio_bus_switch;
|
||||
struct net_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;
|
||||
u32 eeprom_len;
|
||||
int ret;
|
||||
|
||||
mdio = of_parse_phandle(np, "dsa,mii-bus", 0);
|
||||
if (!mdio)
|
||||
return -EINVAL;
|
||||
|
||||
mdio_bus = of_mdio_find_bus(mdio);
|
||||
if (!mdio_bus)
|
||||
return -EPROBE_DEFER;
|
||||
|
||||
ethernet = of_parse_phandle(np, "dsa,ethernet", 0);
|
||||
if (!ethernet) {
|
||||
ret = -EINVAL;
|
||||
goto out_put_mdio;
|
||||
}
|
||||
|
||||
ethernet_dev = of_find_net_device_by_node(ethernet);
|
||||
if (!ethernet_dev) {
|
||||
ret = -EPROBE_DEFER;
|
||||
goto out_put_mdio;
|
||||
}
|
||||
|
||||
pd = kzalloc(sizeof(*pd), GFP_KERNEL);
|
||||
if (!pd) {
|
||||
ret = -ENOMEM;
|
||||
goto out_put_ethernet;
|
||||
}
|
||||
|
||||
dev->platform_data = pd;
|
||||
pd->of_netdev = ethernet_dev;
|
||||
pd->nr_chips = of_get_available_child_count(np);
|
||||
if (pd->nr_chips > DSA_MAX_SWITCHES)
|
||||
pd->nr_chips = DSA_MAX_SWITCHES;
|
||||
|
||||
pd->chip = kcalloc(pd->nr_chips, sizeof(struct dsa_chip_data),
|
||||
GFP_KERNEL);
|
||||
if (!pd->chip) {
|
||||
ret = -ENOMEM;
|
||||
goto out_free;
|
||||
}
|
||||
|
||||
chip_index = -1;
|
||||
for_each_available_child_of_node(np, child) {
|
||||
int i;
|
||||
|
||||
chip_index++;
|
||||
cd = &pd->chip[chip_index];
|
||||
|
||||
cd->of_node = child;
|
||||
|
||||
/* Initialize the routing table */
|
||||
for (i = 0; i < DSA_MAX_SWITCHES; ++i)
|
||||
cd->rtable[i] = DSA_RTABLE_NONE;
|
||||
|
||||
/* When assigning the host device, increment its refcount */
|
||||
cd->host_dev = get_device(&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;
|
||||
|
||||
if (!of_property_read_u32(child, "eeprom-length", &eeprom_len))
|
||||
cd->eeprom_len = eeprom_len;
|
||||
|
||||
mdio = of_parse_phandle(child, "mii-bus", 0);
|
||||
if (mdio) {
|
||||
mdio_bus_switch = of_mdio_find_bus(mdio);
|
||||
if (!mdio_bus_switch) {
|
||||
ret = -EPROBE_DEFER;
|
||||
goto out_free_chip;
|
||||
}
|
||||
|
||||
/* Drop the mdio_bus device ref, replacing the host
|
||||
* device with the mdio_bus_switch device, keeping
|
||||
* the refcount from of_mdio_find_bus() above.
|
||||
*/
|
||||
put_device(cd->host_dev);
|
||||
cd->host_dev = &mdio_bus_switch->dev;
|
||||
}
|
||||
|
||||
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);
|
||||
if (port_index >= DSA_MAX_PORTS)
|
||||
break;
|
||||
|
||||
port_name = of_get_property(port, "label", NULL);
|
||||
if (!port_name)
|
||||
continue;
|
||||
|
||||
cd->port_dn[port_index] = port;
|
||||
|
||||
cd->port_names[port_index] = kstrdup(port_name,
|
||||
GFP_KERNEL);
|
||||
if (!cd->port_names[port_index]) {
|
||||
ret = -ENOMEM;
|
||||
goto out_free_chip;
|
||||
}
|
||||
|
||||
ret = dsa_of_probe_links(pd, cd, chip_index,
|
||||
port_index, port, port_name);
|
||||
if (ret)
|
||||
goto out_free_chip;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/* The individual chips hold their own refcount on the mdio bus,
|
||||
* so drop ours */
|
||||
put_device(&mdio_bus->dev);
|
||||
|
||||
return 0;
|
||||
|
||||
out_free_chip:
|
||||
dsa_of_free_platform_data(pd);
|
||||
out_free:
|
||||
kfree(pd);
|
||||
dev->platform_data = NULL;
|
||||
out_put_ethernet:
|
||||
put_device(ðernet_dev->dev);
|
||||
out_put_mdio:
|
||||
put_device(&mdio_bus->dev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void dsa_of_remove(struct device *dev)
|
||||
{
|
||||
struct dsa_platform_data *pd = dev->platform_data;
|
||||
|
||||
if (!dev->of_node)
|
||||
return;
|
||||
|
||||
dsa_of_free_platform_data(pd);
|
||||
put_device(&pd->of_netdev->dev);
|
||||
kfree(pd);
|
||||
}
|
||||
#else
|
||||
static inline int dsa_of_probe(struct device *dev)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void dsa_of_remove(struct device *dev)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
static int dsa_setup_dst(struct dsa_switch_tree *dst, struct net_device *dev,
|
||||
struct device *parent, struct dsa_platform_data *pd)
|
||||
{
|
||||
int i;
|
||||
unsigned configured = 0;
|
||||
|
||||
dst->pd = pd;
|
||||
|
||||
for (i = 0; i < pd->nr_chips; i++) {
|
||||
struct dsa_switch *ds;
|
||||
|
||||
ds = dsa_switch_setup(dst, dev, i, parent, pd->chip[i].host_dev);
|
||||
if (IS_ERR(ds)) {
|
||||
netdev_err(dev, "[%d]: couldn't create dsa switch instance (error %ld)\n",
|
||||
i, PTR_ERR(ds));
|
||||
continue;
|
||||
}
|
||||
|
||||
dst->ds[i] = ds;
|
||||
|
||||
++configured;
|
||||
}
|
||||
|
||||
/*
|
||||
* If no switch was found, exit cleanly
|
||||
*/
|
||||
if (!configured)
|
||||
return -EPROBE_DEFER;
|
||||
|
||||
return dsa_master_setup(dst->cpu_dp->master, dst->cpu_dp);
|
||||
}
|
||||
|
||||
static int dsa_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct dsa_platform_data *pd = pdev->dev.platform_data;
|
||||
struct net_device *dev;
|
||||
struct dsa_switch_tree *dst;
|
||||
int ret;
|
||||
|
||||
if (pdev->dev.of_node) {
|
||||
ret = dsa_of_probe(&pdev->dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
pd = pdev->dev.platform_data;
|
||||
}
|
||||
|
||||
if (pd == NULL || (pd->netdev == NULL && pd->of_netdev == NULL))
|
||||
return -EINVAL;
|
||||
|
||||
if (pd->of_netdev) {
|
||||
dev = pd->of_netdev;
|
||||
dev_hold(dev);
|
||||
} else {
|
||||
dev = dsa_dev_to_net_device(pd->netdev);
|
||||
}
|
||||
if (dev == NULL) {
|
||||
ret = -EPROBE_DEFER;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (dev->dsa_ptr != NULL) {
|
||||
dev_put(dev);
|
||||
ret = -EEXIST;
|
||||
goto out;
|
||||
}
|
||||
|
||||
dst = devm_kzalloc(&pdev->dev, sizeof(*dst), GFP_KERNEL);
|
||||
if (dst == NULL) {
|
||||
dev_put(dev);
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, dst);
|
||||
|
||||
ret = dsa_setup_dst(dst, dev, &pdev->dev, pd);
|
||||
if (ret) {
|
||||
dev_put(dev);
|
||||
goto out;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
out:
|
||||
dsa_of_remove(&pdev->dev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void dsa_remove_dst(struct dsa_switch_tree *dst)
|
||||
{
|
||||
int i;
|
||||
|
||||
dsa_master_teardown(dst->cpu_dp->master);
|
||||
|
||||
for (i = 0; i < dst->pd->nr_chips; i++) {
|
||||
struct dsa_switch *ds = dst->ds[i];
|
||||
|
||||
if (ds)
|
||||
dsa_switch_destroy(ds);
|
||||
}
|
||||
|
||||
dev_put(dst->cpu_dp->master);
|
||||
}
|
||||
|
||||
static int dsa_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct dsa_switch_tree *dst = platform_get_drvdata(pdev);
|
||||
|
||||
dsa_remove_dst(dst);
|
||||
dsa_of_remove(&pdev->dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void dsa_shutdown(struct platform_device *pdev)
|
||||
{
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static int dsa_suspend(struct device *d)
|
||||
{
|
||||
struct dsa_switch_tree *dst = dev_get_drvdata(d);
|
||||
int i, ret = 0;
|
||||
|
||||
for (i = 0; i < dst->pd->nr_chips; i++) {
|
||||
struct dsa_switch *ds = dst->ds[i];
|
||||
|
||||
if (ds != NULL)
|
||||
ret = dsa_switch_suspend(ds);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int dsa_resume(struct device *d)
|
||||
{
|
||||
struct dsa_switch_tree *dst = dev_get_drvdata(d);
|
||||
int i, ret = 0;
|
||||
|
||||
for (i = 0; i < dst->pd->nr_chips; i++) {
|
||||
struct dsa_switch *ds = dst->ds[i];
|
||||
|
||||
if (ds != NULL)
|
||||
ret = dsa_switch_resume(ds);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
static SIMPLE_DEV_PM_OPS(dsa_pm_ops, dsa_suspend, dsa_resume);
|
||||
|
||||
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,
|
||||
.shutdown = dsa_shutdown,
|
||||
.driver = {
|
||||
.name = "dsa",
|
||||
.of_match_table = dsa_of_match_table,
|
||||
.pm = &dsa_pm_ops,
|
||||
},
|
||||
};
|
||||
|
||||
int dsa_legacy_register(void)
|
||||
{
|
||||
return platform_driver_register(&dsa_driver);
|
||||
}
|
||||
|
||||
void dsa_legacy_unregister(void)
|
||||
{
|
||||
platform_driver_unregister(&dsa_driver);
|
||||
}
|
Загрузка…
Ссылка в новой задаче