Commit 0ef9059d authored by David S. Miller's avatar David S. Miller

Merge branch 'dsa-LAN9303'

Juergen Borleis says:

====================
net: dsa: add SMSC/Microchip LAN9303 three port ethernet switch driver

The LAN9303 is a three port 10/100 ethernet switch with integrated phys
for the two external ethernet ports. The third port is an RMII/MII
interface to a host master network interface (e.g. fixed link).

While the LAN9303 device itself supports offload packet processing, this
driver does not make use of it yet. This driver just configures the device
to provide two separate network interfaces (which is the default state of
a DSA device).

Please note: the "MDIO managed mode" driver part isn't tested yet. I have
used and tested the "I2C managed mode" only.

Changes in v6:

- fix support to use the driver as a module (core, i2c and mdio)
- license info added in all parts of the driver (for module support)

Changes in v5:

- add missing include file to 'net/dsa/tag_lan9303.c'

Changes in v4:

- rebased on net-next, 'net/dsa/tag_lan9303.c' adapted to changed API

Changes in v3:

- 'ds_to_lan9303()' removed
- special PHY reg MII_LAN911X_SPECIAL_CONTROL_STATUS mapping removed
- compatible strings for I2C and MDIO are now different
- MDIO-managed-mode devicetree binding added (still compile time tested only)

Changes in v2:

- code moved to 'drivers/net/dsa'
- timeouts in completion wait loops
- macros instead of various magic numbers
- development code removed
- devicetree property names changed
- devicetree example adapted
- tried to avoid to mix 'switching' and 'forwarding'...

Comments are welcome.
====================
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents 8dc7d11f dc700583
SMSC/MicroChip LAN9303 three port ethernet switch
-------------------------------------------------
Required properties:
- compatible: should be
- "smsc,lan9303-i2c" for I2C managed mode
or
- "smsc,lan9303-mdio" for mdio managed mode
Optional properties:
- reset-gpios: GPIO to be used to reset the whole device
- reset-duration: reset duration in milliseconds, defaults to 200 ms
Subnodes:
The integrated switch subnode should be specified according to the binding
described in dsa/dsa.txt. The CPU port of this switch is always port 0.
Note: always use 'reg = <0/1/2>;' for the three DSA ports, even if the device is
configured to use 1/2/3 instead. This hardware configuration will be
auto-detected and mapped accordingly.
Example:
I2C managed mode:
master: masterdevice@X {
status = "okay";
fixed-link { /* RMII fixed link to LAN9303 */
speed = <100>;
full-duplex;
};
};
switch: switch@a {
compatible = "smsc,lan9303-i2c";
reg = <0xa>;
status = "okay";
reset-gpios = <&gpio7 6 GPIO_ACTIVE_LOW>;
reset-duration = <200>;
ports {
#address-cells = <1>;
#size-cells = <0>;
port@0 { /* RMII fixed link to master */
reg = <0>;
label = "cpu";
ethernet = <&master>;
};
port@1 { /* external port 1 */
reg = <1>;
label = "lan1;
};
port@2 { /* external port 2 */
reg = <2>;
label = "lan2";
};
};
};
MDIO managed mode:
master: masterdevice@X {
status = "okay";
phy-handle = <&switch>;
mdio {
#address-cells = <1>;
#size-cells = <0>;
switch: switch-phy@0 {
compatible = "smsc,lan9303-mdio";
reg = <0>;
reset-gpios = <&gpio7 6 GPIO_ACTIVE_LOW>;
reset-duration = <100>;
ports {
#address-cells = <1>;
#size-cells = <0>;
port@0 {
reg = <0>;
label = "cpu";
ethernet = <&master>;
};
port@1 { /* external port 1 */
reg = <1>;
label = "lan1;
};
port@2 { /* external port 2 */
reg = <2>;
label = "lan2";
};
};
};
};
};
......@@ -50,4 +50,28 @@ config NET_DSA_MT7530
This enables support for the Mediatek MT7530 Ethernet switch
chip.
config NET_DSA_SMSC_LAN9303
tristate
select NET_DSA_TAG_LAN9303
---help---
This enables support for the SMSC/Microchip LAN9303 3 port ethernet
switch chips.
config NET_DSA_SMSC_LAN9303_I2C
tristate "SMSC/Microchip LAN9303 3-ports 10/100 ethernet switch in I2C managed mode"
depends on NET_DSA
select NET_DSA_SMSC_LAN9303
select REGMAP_I2C
---help---
Enable access functions if the SMSC/Microchip LAN9303 is configured
for I2C managed mode.
config NET_DSA_SMSC_LAN9303_MDIO
tristate "SMSC/Microchip LAN9303 3-ports 10/100 ethernet switch in MDIO managed mode"
depends on NET_DSA
select NET_DSA_SMSC_LAN9303
---help---
Enable access functions if the SMSC/Microchip LAN9303 is configured
for MDIO managed mode.
endmenu
......@@ -3,6 +3,9 @@ obj-$(CONFIG_NET_DSA_BCM_SF2) += bcm-sf2.o
bcm-sf2-objs := bcm_sf2.o bcm_sf2_cfp.o
obj-$(CONFIG_NET_DSA_QCA8K) += qca8k.o
obj-$(CONFIG_NET_DSA_MT7530) += mt7530.o
obj-$(CONFIG_NET_DSA_SMSC_LAN9303) += lan9303-core.o
obj-$(CONFIG_NET_DSA_SMSC_LAN9303_I2C) += lan9303_i2c.o
obj-$(CONFIG_NET_DSA_SMSC_LAN9303_MDIO) += lan9303_mdio.o
obj-y += b53/
obj-y += mv88e6xxx/
obj-$(CONFIG_NET_DSA_LOOP) += dsa_loop.o dsa_loop_bdinfo.o
This diff is collapsed.
#include <linux/regmap.h>
#include <linux/device.h>
#include <net/dsa.h>
struct lan9303 {
struct device *dev;
struct regmap *regmap;
struct regmap_irq_chip_data *irq_data;
struct gpio_desc *reset_gpio;
u32 reset_duration; /* in [ms] */
bool phy_addr_sel_strap;
struct dsa_switch *ds;
struct mutex indirect_mutex; /* protect indexed register access */
};
extern const struct regmap_access_table lan9303_register_set;
int lan9303_probe(struct lan9303 *chip, struct device_node *np);
int lan9303_remove(struct lan9303 *chip);
/*
* Copyright (C) 2017 Pengutronix, Juergen Borleis <kernel@pengutronix.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/of.h>
#include "lan9303.h"
struct lan9303_i2c {
struct i2c_client *device;
struct lan9303 chip;
};
static const struct regmap_config lan9303_i2c_regmap_config = {
.reg_bits = 8,
.val_bits = 32,
.reg_stride = 1,
.can_multi_write = true,
.max_register = 0x0ff, /* address bits 0..1 are not used */
.reg_format_endian = REGMAP_ENDIAN_LITTLE,
.volatile_table = &lan9303_register_set,
.wr_table = &lan9303_register_set,
.rd_table = &lan9303_register_set,
.cache_type = REGCACHE_NONE,
};
static int lan9303_i2c_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct lan9303_i2c *sw_dev;
int ret;
sw_dev = devm_kzalloc(&client->dev, sizeof(struct lan9303_i2c),
GFP_KERNEL);
if (!sw_dev)
return -ENOMEM;
sw_dev->chip.regmap = devm_regmap_init_i2c(client,
&lan9303_i2c_regmap_config);
if (IS_ERR(sw_dev->chip.regmap)) {
ret = PTR_ERR(sw_dev->chip.regmap);
dev_err(&client->dev, "Failed to allocate register map: %d\n",
ret);
return ret;
}
/* link forward and backward */
sw_dev->device = client;
i2c_set_clientdata(client, sw_dev);
sw_dev->chip.dev = &client->dev;
ret = lan9303_probe(&sw_dev->chip, client->dev.of_node);
if (ret != 0)
return ret;
dev_info(&client->dev, "LAN9303 I2C driver loaded successfully\n");
return 0;
}
static int lan9303_i2c_remove(struct i2c_client *client)
{
struct lan9303_i2c *sw_dev;
sw_dev = i2c_get_clientdata(client);
if (!sw_dev)
return -ENODEV;
return lan9303_remove(&sw_dev->chip);
}
/*-------------------------------------------------------------------------*/
static const struct i2c_device_id lan9303_i2c_id[] = {
{ "lan9303", 0 },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(i2c, lan9303_i2c_id);
static const struct of_device_id lan9303_i2c_of_match[] = {
{ .compatible = "smsc,lan9303-i2c", },
{ /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, lan9303_i2c_of_match);
static struct i2c_driver lan9303_i2c_driver = {
.driver = {
.name = "LAN9303_I2C",
.of_match_table = of_match_ptr(lan9303_i2c_of_match),
},
.probe = lan9303_i2c_probe,
.remove = lan9303_i2c_remove,
.id_table = lan9303_i2c_id,
};
module_i2c_driver(lan9303_i2c_driver);
MODULE_AUTHOR("Juergen Borleis <kernel@pengutronix.de>");
MODULE_DESCRIPTION("Driver for SMSC/Microchip LAN9303 three port ethernet switch in I2C managed mode");
MODULE_LICENSE("GPL v2");
/*
* Copyright (C) 2017 Pengutronix, Juergen Borleis <kernel@pengutronix.de>
*
* Partially based on a patch from
* Copyright (c) 2014 Stefan Roese <sr@denx.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mdio.h>
#include <linux/phy.h>
#include <linux/of.h>
#include "lan9303.h"
/* Generate phy-addr and -reg from the input address */
#define PHY_ADDR(x) ((((x) >> 6) + 0x10) & 0x1f)
#define PHY_REG(x) (((x) >> 1) & 0x1f)
struct lan9303_mdio {
struct mdio_device *device;
struct lan9303 chip;
};
static void lan9303_mdio_real_write(struct mdio_device *mdio, int reg, u16 val)
{
mdio->bus->write(mdio->bus, PHY_ADDR(reg), PHY_REG(reg), val);
}
static int lan9303_mdio_write(void *ctx, uint32_t reg, uint32_t val)
{
struct lan9303_mdio *sw_dev = (struct lan9303_mdio *)ctx;
mutex_lock(&sw_dev->device->bus->mdio_lock);
lan9303_mdio_real_write(sw_dev->device, reg, val & 0xffff);
lan9303_mdio_real_write(sw_dev->device, reg + 2, (val >> 16) & 0xffff);
mutex_unlock(&sw_dev->device->bus->mdio_lock);
return 0;
}
static u16 lan9303_mdio_real_read(struct mdio_device *mdio, int reg)
{
return mdio->bus->read(mdio->bus, PHY_ADDR(reg), PHY_REG(reg));
}
static int lan9303_mdio_read(void *ctx, uint32_t reg, uint32_t *val)
{
struct lan9303_mdio *sw_dev = (struct lan9303_mdio *)ctx;
mutex_lock(&sw_dev->device->bus->mdio_lock);
*val = lan9303_mdio_real_read(sw_dev->device, reg);
*val |= (lan9303_mdio_real_read(sw_dev->device, reg + 2) << 16);
mutex_unlock(&sw_dev->device->bus->mdio_lock);
return 0;
}
static const struct regmap_config lan9303_mdio_regmap_config = {
.reg_bits = 8,
.val_bits = 32,
.reg_stride = 1,
.can_multi_write = true,
.max_register = 0x0ff, /* address bits 0..1 are not used */
.reg_format_endian = REGMAP_ENDIAN_LITTLE,
.volatile_table = &lan9303_register_set,
.wr_table = &lan9303_register_set,
.rd_table = &lan9303_register_set,
.reg_read = lan9303_mdio_read,
.reg_write = lan9303_mdio_write,
.cache_type = REGCACHE_NONE,
};
static int lan9303_mdio_probe(struct mdio_device *mdiodev)
{
struct lan9303_mdio *sw_dev;
int ret;
sw_dev = devm_kzalloc(&mdiodev->dev, sizeof(struct lan9303_mdio),
GFP_KERNEL);
if (!sw_dev)
return -ENOMEM;
sw_dev->chip.regmap = devm_regmap_init(&mdiodev->dev, NULL, sw_dev,
&lan9303_mdio_regmap_config);
if (IS_ERR(sw_dev->chip.regmap)) {
ret = PTR_ERR(sw_dev->chip.regmap);
dev_err(&mdiodev->dev, "regmap init failed: %d\n", ret);
return ret;
}
/* link forward and backward */
sw_dev->device = mdiodev;
dev_set_drvdata(&mdiodev->dev, sw_dev);
sw_dev->chip.dev = &mdiodev->dev;
ret = lan9303_probe(&sw_dev->chip, mdiodev->dev.of_node);
if (ret != 0)
return ret;
dev_info(&mdiodev->dev, "LAN9303 MDIO driver loaded successfully\n");
return 0;
}
static void lan9303_mdio_remove(struct mdio_device *mdiodev)
{
struct lan9303_mdio *sw_dev = dev_get_drvdata(&mdiodev->dev);
if (!sw_dev)
return;
lan9303_remove(&sw_dev->chip);
}
/*-------------------------------------------------------------------------*/
static const struct of_device_id lan9303_mdio_of_match[] = {
{ .compatible = "smsc,lan9303-mdio" },
{ /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, lan9303_mdio_of_match);
static struct mdio_driver lan9303_mdio_driver = {
.mdiodrv.driver = {
.name = "LAN9303_MDIO",
.of_match_table = of_match_ptr(lan9303_mdio_of_match),
},
.probe = lan9303_mdio_probe,
.remove = lan9303_mdio_remove,
};
mdio_module_driver(lan9303_mdio_driver);
MODULE_AUTHOR("Stefan Roese <sr@denx.de>, Juergen Borleis <kernel@pengutronix.de>");
MODULE_DESCRIPTION("Driver for SMSC/Microchip LAN9303 three port ethernet switch in MDIO managed mode");
MODULE_LICENSE("GPL v2");
......@@ -33,6 +33,7 @@ enum dsa_tag_protocol {
DSA_TAG_PROTO_BRCM,
DSA_TAG_PROTO_QCA,
DSA_TAG_PROTO_MTK,
DSA_TAG_PROTO_LAN9303,
DSA_TAG_LAST, /* MUST BE LAST */
};
......
......@@ -33,4 +33,8 @@ config NET_DSA_TAG_QCA
config NET_DSA_TAG_MTK
bool
config NET_DSA_TAG_LAN9303
bool
endif
......@@ -9,3 +9,4 @@ dsa_core-$(CONFIG_NET_DSA_TAG_EDSA) += tag_edsa.o
dsa_core-$(CONFIG_NET_DSA_TAG_TRAILER) += tag_trailer.o
dsa_core-$(CONFIG_NET_DSA_TAG_QCA) += tag_qca.o
dsa_core-$(CONFIG_NET_DSA_TAG_MTK) += tag_mtk.o
dsa_core-$(CONFIG_NET_DSA_TAG_LAN9303) += tag_lan9303.o
......@@ -57,6 +57,9 @@ const struct dsa_device_ops *dsa_device_ops[DSA_TAG_LAST] = {
#endif
#ifdef CONFIG_NET_DSA_TAG_MTK
[DSA_TAG_PROTO_MTK] = &mtk_netdev_ops,
#endif
#ifdef CONFIG_NET_DSA_TAG_LAN9303
[DSA_TAG_PROTO_LAN9303] = &lan9303_netdev_ops,
#endif
[DSA_TAG_PROTO_NONE] = &none_ops,
};
......
......@@ -93,4 +93,7 @@ extern const struct dsa_device_ops qca_netdev_ops;
/* tag_mtk.c */
extern const struct dsa_device_ops mtk_netdev_ops;
/* tag_lan9303.c */
extern const struct dsa_device_ops lan9303_netdev_ops;
#endif
/*
* Copyright (C) 2017 Pengutronix, Juergen Borleis <jbe@pengutronix.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#include <linux/etherdevice.h>
#include <linux/list.h>
#include <linux/slab.h>
#include <net/dsa.h>
#include "dsa_priv.h"
/* To define the outgoing port and to discover the incoming port a regular
* VLAN tag is used by the LAN9303. But its VID meaning is 'special':
*
* Dest MAC Src MAC TAG Type
* ...| 1 2 3 4 5 6 | 1 2 3 4 5 6 | 1 2 3 4 | 1 2 |...
* |<------->|
* TAG:
* |<------------->|
* | 1 2 | 3 4 |
* TPID VID
* 0x8100
*
* VID bit 3 indicates a request for an ALR lookup.
*
* If VID bit 3 is zero, then bits 0 and 1 specify the destination port
* (0, 1, 2) or broadcast (3) or the source port (1, 2).
*
* VID bit 4 is used to specify if the STP port state should be overridden.
* Required when no forwarding between the external ports should happen.
*/
#define LAN9303_TAG_LEN 4
#define LAN9303_MAX_PORTS 3
static struct sk_buff *lan9303_xmit(struct sk_buff *skb, struct net_device *dev)
{
struct dsa_slave_priv *p = netdev_priv(dev);
u16 *lan9303_tag;
/* insert a special VLAN tag between the MAC addresses
* and the current ethertype field.
*/
if (skb_cow_head(skb, LAN9303_TAG_LEN) < 0) {
dev_dbg(&dev->dev,
"Cannot make room for the special tag. Dropping packet\n");
goto out_free;
}
/* provide 'LAN9303_TAG_LEN' bytes additional space */
skb_push(skb, LAN9303_TAG_LEN);
/* make room between MACs and Ether-Type */
memmove(skb->data, skb->data + LAN9303_TAG_LEN, 2 * ETH_ALEN);
lan9303_tag = (u16 *)(skb->data + 2 * ETH_ALEN);
lan9303_tag[0] = htons(ETH_P_8021Q);
lan9303_tag[1] = htons(p->dp->index | BIT(4));
return skb;
out_free:
kfree_skb(skb);
return NULL;
}
static struct sk_buff *lan9303_rcv(struct sk_buff *skb, struct net_device *dev,
struct packet_type *pt, struct net_device *orig_dev)
{
u16 *lan9303_tag;
struct dsa_switch_tree *dst = dev->dsa_ptr;
struct dsa_switch *ds;
unsigned int source_port;
if (unlikely(!dst)) {
dev_warn_ratelimited(&dev->dev, "Dropping packet, due to missing switch tree device\n");
return NULL;
}
ds = dst->ds[0];
if (unlikely(!ds)) {
dev_warn_ratelimited(&dev->dev, "Dropping packet, due to missing DSA switch device\n");
return NULL;
}
if (unlikely(!pskb_may_pull(skb, LAN9303_TAG_LEN))) {
dev_warn_ratelimited(&dev->dev,
"Dropping packet, cannot pull\n");
return NULL;
}
/* '->data' points into the middle of our special VLAN tag information:
*
* ~ MAC src | 0x81 | 0x00 | 0xyy | 0xzz | ether type
* ^
* ->data
*/
lan9303_tag = (u16 *)(skb->data - 2);
if (lan9303_tag[0] != htons(ETH_P_8021Q)) {
dev_warn_ratelimited(&dev->dev, "Dropping packet due to invalid VLAN marker\n");
return NULL;
}
source_port = ntohs(lan9303_tag[1]) & 0x3;
if (source_port >= LAN9303_MAX_PORTS) {
dev_warn_ratelimited(&dev->dev, "Dropping packet due to invalid source port\n");
return NULL;
}
if (!ds->ports[source_port].netdev) {
dev_warn_ratelimited(&dev->dev, "Dropping packet due to invalid netdev or device\n");
return NULL;
}
/* remove the special VLAN tag between the MAC addresses
* and the current ethertype field.
*/
skb_pull_rcsum(skb, 2 + 2);
memmove(skb->data - ETH_HLEN, skb->data - (ETH_HLEN + LAN9303_TAG_LEN),
2 * ETH_ALEN);
/* forward the packet to the dedicated interface */
skb->dev = ds->ports[source_port].netdev;
return skb;
}
const struct dsa_device_ops lan9303_netdev_ops = {
.xmit = lan9303_xmit,
.rcv = lan9303_rcv,
};
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment