From 9b5f5839066ff8958c0bb468623db3a82a9806be Mon Sep 17 00:00:00 2001 From: John Crispin Date: Mon, 1 Dec 2014 21:30:35 +0000 Subject: [PATCH] kernel: add driver for Marvell 88E6171 switch This is a swconfig driver for the Marvell 88E6171 switch, which is a 7-port GigE switch with two CPU ports and 64 802.1q VLANs. Signed-off-by: Claudio Leite SVN-Revision: 43486 --- target/linux/generic/config-3.14 | 1 + target/linux/generic/config-3.18 | 1 + .../generic/files/drivers/net/phy/mvsw6171.c | 866 ++++++++++++++++++ .../generic/files/drivers/net/phy/mvsw6171.h | 230 +++++ .../patches-3.14/733-phy_mvsw6171.patch | 23 + .../patches-3.18/733-phy_mvsw6171.patch | 23 + 6 files changed, 1144 insertions(+) create mode 100644 target/linux/generic/files/drivers/net/phy/mvsw6171.c create mode 100644 target/linux/generic/files/drivers/net/phy/mvsw6171.h create mode 100644 target/linux/generic/patches-3.14/733-phy_mvsw6171.patch create mode 100644 target/linux/generic/patches-3.18/733-phy_mvsw6171.patch diff --git a/target/linux/generic/config-3.14 b/target/linux/generic/config-3.14 index 8d11376cb4..9485ea1ece 100644 --- a/target/linux/generic/config-3.14 +++ b/target/linux/generic/config-3.14 @@ -2143,6 +2143,7 @@ CONFIG_MTD_SPLIT_SQUASHFS_ROOT=y # CONFIG_MUTEX_SPIN_ON_OWNER is not set # CONFIG_MV643XX_ETH is not set # CONFIG_MVMDIO is not set +# CONFIG_MVSW6171_PHY is not set # CONFIG_MVSWITCH_PHY is not set # CONFIG_MWAVE is not set # CONFIG_MWL8K is not set diff --git a/target/linux/generic/config-3.18 b/target/linux/generic/config-3.18 index 801ebc141f..e0e2a0b55f 100644 --- a/target/linux/generic/config-3.18 +++ b/target/linux/generic/config-3.18 @@ -2234,6 +2234,7 @@ CONFIG_MTD_SPLIT_SQUASHFS_ROOT=y # CONFIG_MUTEX_SPIN_ON_OWNER is not set # CONFIG_MV643XX_ETH is not set # CONFIG_MVMDIO is not set +# CONFIG_MVSW6171_PHY is not set # CONFIG_MVSWITCH_PHY is not set # CONFIG_MWAVE is not set # CONFIG_MWL8K is not set diff --git a/target/linux/generic/files/drivers/net/phy/mvsw6171.c b/target/linux/generic/files/drivers/net/phy/mvsw6171.c new file mode 100644 index 0000000000..48bb3fcb27 --- /dev/null +++ b/target/linux/generic/files/drivers/net/phy/mvsw6171.c @@ -0,0 +1,866 @@ +/* + * Marvell 88E6171 switch driver + * + * Copyright (c) 2014 Claudio Leite + * + * Based on code (c) 2008 Felix Fietkau + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License v2 as published by the + * Free Software Foundation + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mvsw6171.h" + +MODULE_DESCRIPTION("Marvell 88E6171 Switch driver"); +MODULE_AUTHOR("Claudio Leite "); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:mvsw6171"); + +/* + * Register access is done through direct or indirect addressing, + * depending on how the switch is physically connected. + * + * Direct addressing: all port and global registers directly + * accessible via an address/register pair + * + * Indirect addressing: switch is mapped at a single address, + * port and global registers accessible via a single command/data + * register pair + */ + +static int +mvsw6171_wait_mask_raw(struct mii_bus *bus, int addr, + int reg, u16 mask, u16 val) +{ + int i = 100; + u16 r; + + do { + r = bus->read(bus, addr, reg); + if ((r & mask) == val) + return 0; + } while (--i > 0); + + return -ETIMEDOUT; +} + +static u16 +r16(struct mii_bus *bus, bool indirect, int base_addr, int addr, int reg) +{ + u16 ind_addr; + + if (!indirect) + return bus->read(bus, addr, reg); + + /* Indirect read: First, make sure switch is free */ + mvsw6171_wait_mask_raw(bus, base_addr, MV_INDIRECT_REG_CMD, + MV_INDIRECT_INPROGRESS, 0); + + /* Load address and request read */ + ind_addr = MV_INDIRECT_READ | (addr << MV_INDIRECT_ADDR_S) | reg; + bus->write(bus, base_addr, MV_INDIRECT_REG_CMD, + ind_addr); + + /* Wait until it's ready */ + mvsw6171_wait_mask_raw(bus, base_addr, MV_INDIRECT_REG_CMD, + MV_INDIRECT_INPROGRESS, 0); + + /* Read the requested data */ + return bus->read(bus, base_addr, MV_INDIRECT_REG_DATA); +} + +static void +w16(struct mii_bus *bus, bool indirect, int base_addr, int addr, + int reg, u16 val) +{ + u16 ind_addr; + + if (!indirect) { + bus->write(bus, addr, reg, val); + return; + } + + /* Indirect write: First, make sure switch is free */ + mvsw6171_wait_mask_raw(bus, base_addr, MV_INDIRECT_REG_CMD, + MV_INDIRECT_INPROGRESS, 0); + + /* Load the data to be written */ + bus->write(bus, base_addr, MV_INDIRECT_REG_DATA, val); + + /* Wait again for switch to be free */ + mvsw6171_wait_mask_raw(bus, base_addr, MV_INDIRECT_REG_CMD, + MV_INDIRECT_INPROGRESS, 0); + + /* Load address, and issue write command */ + ind_addr = MV_INDIRECT_WRITE | (addr << MV_INDIRECT_ADDR_S) | reg; + bus->write(bus, base_addr, MV_INDIRECT_REG_CMD, + ind_addr); +} + +/* swconfig support */ + +static inline u16 +sr16(struct switch_dev *dev, int addr, int reg) +{ + struct mvsw6171_state *state = get_state(dev); + + return r16(state->bus, state->is_indirect, state->base_addr, addr, reg); +} + +static inline void +sw16(struct switch_dev *dev, int addr, int reg, u16 val) +{ + struct mvsw6171_state *state = get_state(dev); + + w16(state->bus, state->is_indirect, state->base_addr, addr, reg, val); +} + +static int +mvsw6171_wait_mask_s(struct switch_dev *dev, int addr, + int reg, u16 mask, u16 val) +{ + int i = 100; + u16 r; + + do { + r = sr16(dev, addr, reg) & mask; + if (r == val) + return 0; + } while (--i > 0); + + return -ETIMEDOUT; +} + +static int +mvsw6171_get_port_mask(struct switch_dev *dev, + const struct switch_attr *attr, struct switch_val *val) +{ + struct mvsw6171_state *state = get_state(dev); + char *buf = state->buf; + int port, len, i; + u16 reg; + + port = val->port_vlan; + reg = sr16(dev, MV_PORTREG(VLANMAP, port)) & MV_PORTS_MASK; + + len = sprintf(buf, "0x%04x: ", reg); + + for (i = 0; i < MV_PORTS; i++) { + if (reg & (1 << i)) + len += sprintf(buf + len, "%d ", i); + else if (i == port) + len += sprintf(buf + len, "(%d) ", i); + } + + val->value.s = buf; + + return 0; +} + +static int +mvsw6171_get_port_qmode(struct switch_dev *dev, + const struct switch_attr *attr, struct switch_val *val) +{ + struct mvsw6171_state *state = get_state(dev); + + val->value.i = state->ports[val->port_vlan].qmode; + + return 0; +} + +static int +mvsw6171_set_port_qmode(struct switch_dev *dev, + const struct switch_attr *attr, struct switch_val *val) +{ + struct mvsw6171_state *state = get_state(dev); + + state->ports[val->port_vlan].qmode = val->value.i; + + return 0; +} + +static int +mvsw6171_get_pvid(struct switch_dev *dev, int port, int *val) +{ + struct mvsw6171_state *state = get_state(dev); + + *val = state->ports[port].pvid; + + return 0; +} + +static int +mvsw6171_set_pvid(struct switch_dev *dev, int port, int val) +{ + struct mvsw6171_state *state = get_state(dev); + + if (val < 0 || val >= MV_VLANS) + return -EINVAL; + + state->ports[port].pvid = (u16)val; + + return 0; +} + +static int +mvsw6171_get_port_status(struct switch_dev *dev, + const struct switch_attr *attr, struct switch_val *val) +{ + struct mvsw6171_state *state = get_state(dev); + char *buf = state->buf; + u16 status, speed; + int len; + + status = sr16(dev, MV_PORTREG(STATUS, val->port_vlan)); + speed = (status & MV_PORT_STATUS_SPEED_MASK) >> + MV_PORT_STATUS_SPEED_SHIFT; + + len = sprintf(buf, "link: "); + if (status & MV_PORT_STATUS_LINK) { + len += sprintf(buf + len, "up, speed: "); + + switch (speed) { + case MV_PORT_STATUS_SPEED_10: + len += sprintf(buf + len, "10"); + break; + case MV_PORT_STATUS_SPEED_100: + len += sprintf(buf + len, "100"); + break; + case MV_PORT_STATUS_SPEED_1000: + len += sprintf(buf + len, "1000"); + break; + } + + len += sprintf(buf + len, " Mbps, duplex: "); + + if (status & MV_PORT_STATUS_FDX) + len += sprintf(buf + len, "full"); + else + len += sprintf(buf + len, "half"); + } else { + len += sprintf(buf + len, "down"); + } + + val->value.s = buf; + + return 0; +} + +static int +mvsw6171_get_port_speed(struct switch_dev *dev, + const struct switch_attr *attr, struct switch_val *val) +{ + u16 status, speed; + + status = sr16(dev, MV_PORTREG(STATUS, val->port_vlan)); + speed = (status & MV_PORT_STATUS_SPEED_MASK) >> + MV_PORT_STATUS_SPEED_SHIFT; + + val->value.i = 0; + + if (status & MV_PORT_STATUS_LINK) { + switch (speed) { + case MV_PORT_STATUS_SPEED_10: + val->value.i = 10; + break; + case MV_PORT_STATUS_SPEED_100: + val->value.i = 100; + break; + case MV_PORT_STATUS_SPEED_1000: + val->value.i = 1000; + break; + } + } + + return 0; +} + +static int mvsw6171_get_vlan_ports(struct switch_dev *dev, + struct switch_val *val) +{ + struct mvsw6171_state *state = get_state(dev); + int i, j, mode, vno; + + vno = val->port_vlan; + + if (vno <= 0 || vno >= dev->vlans) + return -EINVAL; + + for (i = 0, j = 0; i < dev->ports; i++) { + if (state->vlans[vno].mask & (1 << i)) { + val->value.ports[j].id = i; + + mode = (state->vlans[vno].port_mode >> (i * 4)) & 0xf; + if (mode == MV_VTUCTL_EGRESS_TAGGED) + val->value.ports[j].flags = + (1 << SWITCH_PORT_FLAG_TAGGED); + else + val->value.ports[j].flags = 0; + + j++; + } + } + + val->len = j; + + return 0; +} + +static int mvsw6171_set_vlan_ports(struct switch_dev *dev, + struct switch_val *val) +{ + struct mvsw6171_state *state = get_state(dev); + int i, mode, pno, vno; + + vno = val->port_vlan; + + if (vno <= 0 || vno >= dev->vlans) + return -EINVAL; + + state->vlans[vno].mask = 0; + state->vlans[vno].port_mode = 0; + + if(state->vlans[vno].vid == 0) + state->vlans[vno].vid = vno; + + for (i = 0; i < val->len; i++) { + pno = val->value.ports[i].id; + + state->vlans[vno].mask |= (1 << pno); + if (val->value.ports[i].flags & + (1 << SWITCH_PORT_FLAG_TAGGED)) + mode = MV_VTUCTL_EGRESS_TAGGED; + else + mode = MV_VTUCTL_EGRESS_UNTAGGED; + + state->vlans[vno].port_mode |= mode << (pno * 4); + } + + /* + * DISCARD is nonzero, so it must be explicitly + * set on ports not in the VLAN. + */ + for (i = 0; i < dev->ports; i++) + if (!(state->vlans[vno].mask & (1 << i))) + state->vlans[vno].port_mode |= + MV_VTUCTL_DISCARD << (i * 4); + + return 0; +} + +static int mvsw6171_get_vlan_port_based(struct switch_dev *dev, + const struct switch_attr *attr, struct switch_val *val) +{ + struct mvsw6171_state *state = get_state(dev); + int vno = val->port_vlan; + + if (vno <= 0 || vno >= dev->vlans) + return -EINVAL; + + if (state->vlans[vno].port_based) + val->value.i = 1; + else + val->value.i = 0; + + return 0; +} + +static int mvsw6171_set_vlan_port_based(struct switch_dev *dev, + const struct switch_attr *attr, struct switch_val *val) +{ + struct mvsw6171_state *state = get_state(dev); + int vno = val->port_vlan; + + if (vno <= 0 || vno >= dev->vlans) + return -EINVAL; + + if (val->value.i == 1) + state->vlans[vno].port_based = true; + else + state->vlans[vno].port_based = false; + + return 0; +} + +static int mvsw6171_get_vid(struct switch_dev *dev, + const struct switch_attr *attr, struct switch_val *val) +{ + struct mvsw6171_state *state = get_state(dev); + int vno = val->port_vlan; + + if (vno <= 0 || vno >= dev->vlans) + return -EINVAL; + + val->value.i = state->vlans[vno].vid; + + return 0; +} + +static int mvsw6171_set_vid(struct switch_dev *dev, + const struct switch_attr *attr, struct switch_val *val) +{ + struct mvsw6171_state *state = get_state(dev); + int vno = val->port_vlan; + + if (vno <= 0 || vno >= dev->vlans) + return -EINVAL; + + state->vlans[vno].vid = val->value.i; + + return 0; +} + +static int mvsw6171_get_enable_vlan(struct switch_dev *dev, + const struct switch_attr *attr, struct switch_val *val) +{ + struct mvsw6171_state *state = get_state(dev); + + val->value.i = state->vlan_enabled; + + return 0; +} + +static int mvsw6171_set_enable_vlan(struct switch_dev *dev, + const struct switch_attr *attr, struct switch_val *val) +{ + struct mvsw6171_state *state = get_state(dev); + + state->vlan_enabled = val->value.i; + + return 0; +} + +static int mvsw6171_vtu_program(struct switch_dev *dev) +{ + struct mvsw6171_state *state = get_state(dev); + u16 v1, v2; + int i; + + /* Flush */ + mvsw6171_wait_mask_s(dev, MV_GLOBALREG(VTU_OP), + MV_VTUOP_INPROGRESS, 0); + sw16(dev, MV_GLOBALREG(VTU_OP), + MV_VTUOP_INPROGRESS | MV_VTUOP_VALID); + + /* Write VLAN table */ + for (i = 1; i < dev->vlans; i++) { + if (state->vlans[i].mask == 0 || + state->vlans[i].vid == 0 || + state->vlans[i].port_based == true) + continue; + + mvsw6171_wait_mask_s(dev, MV_GLOBALREG(VTU_OP), + MV_VTUOP_INPROGRESS, 0); + + sw16(dev, MV_GLOBALREG(VTU_VID), + MV_VTUOP_VALID | state->vlans[i].vid); + + v1 = (u16)(state->vlans[i].port_mode & 0xffff); + v2 = (u16)((state->vlans[i].port_mode >> 16) & 0xffff); + + sw16(dev, MV_GLOBALREG(VTU_DATA1), v1); + sw16(dev, MV_GLOBALREG(VTU_DATA2), v2); + + sw16(dev, MV_GLOBALREG(VTU_OP), + MV_VTUOP_INPROGRESS | MV_VTUOP_LOAD); + mvsw6171_wait_mask_s(dev, MV_GLOBALREG(VTU_OP), + MV_VTUOP_INPROGRESS, 0); + } + + return 0; +} + +static void mvsw6171_vlan_port_config(struct switch_dev *dev, int vno) +{ + struct mvsw6171_state *state = get_state(dev); + int i, mode; + + for (i = 0; i < dev->ports; i++) { + if (!(state->vlans[vno].mask & (1 << i))) + continue; + + mode = (state->vlans[vno].port_mode >> (i * 4)) & 0xf; + + if(mode != MV_VTUCTL_EGRESS_TAGGED) + state->ports[i].pvid = state->vlans[vno].vid; + + if (state->vlans[vno].port_based) + state->ports[i].mask |= state->vlans[vno].mask; + else + state->ports[i].qmode = MV_8021Q_MODE_SECURE; + } +} + +static int mvsw6171_update_state(struct switch_dev *dev) +{ + struct mvsw6171_state *state = get_state(dev); + int i; + u16 reg; + + if (!state->registered) + return -EINVAL; + + mvsw6171_vtu_program(dev); + + /* + * Set 802.1q-only mode if vlan_enabled is true. + * + * Without this, even if 802.1q is enabled for + * a port/VLAN, it still depends on the port-based + * VLAN mask being set. + * + * With this setting, port-based VLANs are still + * functional, provided the VID is not in the VTU. + */ + reg = sr16(dev, MV_GLOBAL2REG(SDET_POLARITY)); + + if (state->vlan_enabled) + reg |= MV_8021Q_VLAN_ONLY; + else + reg &= ~MV_8021Q_VLAN_ONLY; + + sw16(dev, MV_GLOBAL2REG(SDET_POLARITY), reg); + + /* + * Set port-based VLAN masks on each port + * based only on VLAN definitions known to + * the driver (i.e. in state). + * + * This means any pre-existing port mapping is + * wiped out once our driver is initialized. + */ + for (i = 0; i < dev->ports; i++) { + state->ports[i].mask = 0; + state->ports[i].qmode = MV_8021Q_MODE_DISABLE; + } + + for (i = 0; i < dev->vlans; i++) + mvsw6171_vlan_port_config(dev, i); + + for (i = 0; i < dev->ports; i++) { + reg = sr16(dev, MV_PORTREG(VLANID, i)) & ~MV_PVID_MASK; + reg |= state->ports[i].pvid; + sw16(dev, MV_PORTREG(VLANID, i), reg); + + state->ports[i].mask &= ~(1 << i); + + reg = sr16(dev, MV_PORTREG(VLANMAP, i)) & ~MV_PORTS_MASK; + reg |= state->ports[i].mask; + sw16(dev, MV_PORTREG(VLANMAP, i), reg); + + reg = sr16(dev, MV_PORTREG(CONTROL2, i)) & + ~MV_8021Q_MODE_MASK; + reg |= state->ports[i].qmode << MV_8021Q_MODE_SHIFT; + sw16(dev, MV_PORTREG(CONTROL2, i), reg); + } + + return 0; +} + +static int mvsw6171_apply(struct switch_dev *dev) +{ + return mvsw6171_update_state(dev); +} + +static int mvsw6171_reset(struct switch_dev *dev) +{ + struct mvsw6171_state *state = get_state(dev); + int i; + u16 reg; + + /* Disable all ports before reset */ + for (i = 0; i < dev->ports; i++) { + reg = sr16(dev, MV_PORTREG(CONTROL, i)) & + ~MV_PORTCTRL_ENABLED; + sw16(dev, MV_PORTREG(CONTROL, i), reg); + } + + reg = sr16(dev, MV_GLOBALREG(CONTROL)) | MV_CONTROL_RESET; + + sw16(dev, MV_GLOBALREG(CONTROL), reg); + if (mvsw6171_wait_mask_s(dev, MV_GLOBALREG(CONTROL), + MV_CONTROL_RESET, 0) < 0) + return -ETIMEDOUT; + + for (i = 0; i < dev->ports; i++) { + state->ports[i].qmode = 0; + state->ports[i].mask = 0; + state->ports[i].pvid = 0; + + /* Force flow control off */ + reg = sr16(dev, MV_PORTREG(FORCE, i)) & ~MV_FORCE_FC_MASK; + reg |= MV_FORCE_FC_DISABLE; + sw16(dev, MV_PORTREG(FORCE, i), reg); + + /* Set port association vector */ + sw16(dev, MV_PORTREG(ASSOC, i), (1 << i)); + } + + for (i = 0; i < dev->vlans; i++) { + state->vlans[i].port_based = false; + state->vlans[i].mask = 0; + state->vlans[i].vid = 0; + state->vlans[i].port_mode = 0; + } + + state->vlan_enabled = 0; + + mvsw6171_update_state(dev); + + /* Re-enable ports */ + for (i = 0; i < dev->ports; i++) { + reg = sr16(dev, MV_PORTREG(CONTROL, i)) | + MV_PORTCTRL_ENABLED; + sw16(dev, MV_PORTREG(CONTROL, i), reg); + } + + return 0; +} + +enum { + MVSW6171_ENABLE_VLAN, +}; + +enum { + MVSW6171_VLAN_PORT_BASED, + MVSW6171_VLAN_ID, +}; + +enum { + MVSW6171_PORT_MASK, + MVSW6171_PORT_QMODE, + MVSW6171_PORT_STATUS, + MVSW6171_PORT_LINK, +}; + +static const struct switch_attr mvsw6171_global[] = { + [MVSW6171_ENABLE_VLAN] = { + .id = MVSW6171_ENABLE_VLAN, + .type = SWITCH_TYPE_INT, + .name = "enable_vlan", + .description = "Enable 802.1q VLAN support", + .get = mvsw6171_get_enable_vlan, + .set = mvsw6171_set_enable_vlan, + }, +}; + +static const struct switch_attr mvsw6171_vlan[] = { + [MVSW6171_VLAN_PORT_BASED] = { + .id = MVSW6171_VLAN_PORT_BASED, + .type = SWITCH_TYPE_INT, + .name = "port_based", + .description = "Use port-based (non-802.1q) VLAN only", + .get = mvsw6171_get_vlan_port_based, + .set = mvsw6171_set_vlan_port_based, + }, + [MVSW6171_VLAN_ID] = { + .id = MVSW6171_VLAN_ID, + .type = SWITCH_TYPE_INT, + .name = "vid", + .description = "Get/set VLAN ID", + .get = mvsw6171_get_vid, + .set = mvsw6171_set_vid, + }, +}; + +static const struct switch_attr mvsw6171_port[] = { + [MVSW6171_PORT_MASK] = { + .id = MVSW6171_PORT_MASK, + .type = SWITCH_TYPE_STRING, + .description = "Port-based VLAN mask", + .name = "mask", + .get = mvsw6171_get_port_mask, + .set = NULL, + }, + [MVSW6171_PORT_QMODE] = { + .id = MVSW6171_PORT_QMODE, + .type = SWITCH_TYPE_INT, + .description = "802.1q mode: 0=off/1=fallback/2=check/3=secure", + .name = "qmode", + .get = mvsw6171_get_port_qmode, + .set = mvsw6171_set_port_qmode, + }, + [MVSW6171_PORT_STATUS] = { + .id = MVSW6171_PORT_STATUS, + .type = SWITCH_TYPE_STRING, + .description = "Return port status", + .name = "status", + .get = mvsw6171_get_port_status, + .set = NULL, + }, + [MVSW6171_PORT_LINK] = { + .id = MVSW6171_PORT_LINK, + .type = SWITCH_TYPE_INT, + .description = "Get link speed", + .name = "link", + .get = mvsw6171_get_port_speed, + .set = NULL, + }, +}; + +static const struct switch_dev_ops mvsw6171_ops = { + .attr_global = { + .attr = mvsw6171_global, + .n_attr = ARRAY_SIZE(mvsw6171_global), + }, + .attr_vlan = { + .attr = mvsw6171_vlan, + .n_attr = ARRAY_SIZE(mvsw6171_vlan), + }, + .attr_port = { + .attr = mvsw6171_port, + .n_attr = ARRAY_SIZE(mvsw6171_port), + }, + .get_port_pvid = mvsw6171_get_pvid, + .set_port_pvid = mvsw6171_set_pvid, + .get_vlan_ports = mvsw6171_get_vlan_ports, + .set_vlan_ports = mvsw6171_set_vlan_ports, + .apply_config = mvsw6171_apply, + .reset_switch = mvsw6171_reset, +}; + +/* end swconfig stuff */ + +static int mvsw6171_probe(struct platform_device *pdev) +{ + struct mvsw6171_state *state; + struct device_node *np = pdev->dev.of_node; + struct device_node *mdio; + u32 val; + u16 reg; + int err; + + state = kzalloc(sizeof(*state), GFP_KERNEL); + if (!state) + return -ENOMEM; + + mdio = of_parse_phandle(np, "mii-bus", 0); + if (!mdio) { + dev_err(&pdev->dev, "Couldn't get MII bus handle\n"); + err = -ENODEV; + goto out_err; + } + + state->bus = of_mdio_find_bus(mdio); + if (!state->bus) { + dev_err(&pdev->dev, "Couldn't find MII bus from handle\n"); + err = -ENODEV; + goto out_err; + } + + state->is_indirect = of_property_read_bool(np, "is-indirect"); + + if (state->is_indirect) { + if (of_property_read_u32(np, "reg", &val)) { + dev_err(&pdev->dev, "Switch address not specified\n"); + err = -ENODEV; + goto out_err; + } + + state->base_addr = val; + } else { + state->base_addr = MV_BASE; + } + + reg = r16(state->bus, state->is_indirect, state->base_addr, + MV_PORTREG(IDENT, 0)) & MV_IDENT_MASK; + if (reg != MV_IDENT_VALUE) { + dev_err(&pdev->dev, "No switch found at 0x%02x\n", + state->base_addr); + err = -ENODEV; + goto out_err; + } + + platform_set_drvdata(pdev, state); + dev_info(&pdev->dev, "Found %s at %s:%02x\n", MV_IDENT_STR, + state->bus->id, state->base_addr); + + dev_info(&pdev->dev, "Using %sdirect addressing\n", + (state->is_indirect ? "in" : "")); + + if (of_property_read_u32(np, "cpu-port-0", &val)) { + dev_err(&pdev->dev, "CPU port not set\n"); + err = -ENODEV; + goto out_err; + } + + state->cpu_port0 = val; + + if (!of_property_read_u32(np, "cpu-port-1", &val)) + state->cpu_port1 = val; + else + state->cpu_port1 = -1; + + state->dev.vlans = MV_VLANS; + state->dev.cpu_port = state->cpu_port0; + state->dev.ports = MV_PORTS; + state->dev.name = MV_IDENT_STR; + state->dev.ops = &mvsw6171_ops; + state->dev.alias = dev_name(&pdev->dev); + + err = register_switch(&state->dev, NULL); + if (err < 0) + goto out_err; + + state->registered = true; + + return 0; +out_err: + kfree(state); + return err; +} + +static int +mvsw6171_remove(struct platform_device *pdev) +{ + struct mvsw6171_state *state = platform_get_drvdata(pdev); + + if (state->registered) + unregister_switch(&state->dev); + + kfree(state); + + return 0; +} + +static const struct of_device_id mvsw6171_match[] = { + { .compatible = "marvell,88e6171" }, + { } +}; +MODULE_DEVICE_TABLE(of, mvsw6171_match); + +static struct platform_driver mvsw6171_driver = { + .probe = mvsw6171_probe, + .remove = mvsw6171_remove, + .driver = { + .name = "mvsw6171", + .of_match_table = of_match_ptr(mvsw6171_match), + .owner = THIS_MODULE, + }, +}; + +static int __init mvsw6171_module_init(void) +{ + return platform_driver_register(&mvsw6171_driver); +} +late_initcall(mvsw6171_module_init); + +static void __exit mvsw6171_module_exit(void) +{ + platform_driver_unregister(&mvsw6171_driver); +} +module_exit(mvsw6171_module_exit); diff --git a/target/linux/generic/files/drivers/net/phy/mvsw6171.h b/target/linux/generic/files/drivers/net/phy/mvsw6171.h new file mode 100644 index 0000000000..2b9ef97a6d --- /dev/null +++ b/target/linux/generic/files/drivers/net/phy/mvsw6171.h @@ -0,0 +1,230 @@ +/* + * Marvell 88E6171 switch driver + * + * Copyright (c) 2014 Claudio Leite + * + * Based on code (c) 2008 Felix Fietkau + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License v2 as published by the + * Free Software Foundation + */ + +#ifndef __MVSW6171_H +#define __MVSW6171_H + +#define MV_PORTS 7 +#define MV_PORTS_MASK ((1 << MV_PORTS) - 1) + +#define MV_CPUPORT 6 + +#define MV_BASE 0x10 + +#define MV_SWITCHPORT_BASE 0x10 +#define MV_SWITCHPORT(_n) (MV_SWITCHPORT_BASE + (_n)) +#define MV_SWITCHREGS (MV_BASE + 0xb) + +#define MV_VLANS 64 + +enum { + MV_PORT_STATUS = 0x00, + MV_PORT_FORCE = 0x01, + MV_PORT_PAUSE = 0x02, + MV_PORT_IDENT = 0x03, + MV_PORT_CONTROL = 0x04, + MV_PORT_CONTROL1 = 0x05, + MV_PORT_VLANMAP = 0x06, + MV_PORT_VLANID = 0x07, + MV_PORT_CONTROL2 = 0x08, + MV_PORT_ASSOC = 0x0b, + MV_PORT_RXCOUNT = 0x10, + MV_PORT_TXCOUNT = 0x11, +}; +#define MV_PORTREG(_type, _port) MV_SWITCHPORT(_port), MV_PORT_##_type + +enum { + MV_PORT_STATUS_FDX = (1 << 10), + MV_PORT_STATUS_LINK = (1 << 11), +}; + +enum { + MV_PORT_STATUS_SPEED_10 = 0x00, + MV_PORT_STATUS_SPEED_100 = 0x01, + MV_PORT_STATUS_SPEED_1000 = 0x02, +}; +#define MV_PORT_STATUS_SPEED_SHIFT 8 +#define MV_PORT_STATUS_SPEED_MASK (3 << 8) + +enum { + MV_PORTCTRL_BLOCK = (1 << 0), + MV_PORTCTRL_LEARN = (2 << 0), + MV_PORTCTRL_ENABLED = (3 << 0), + MV_PORTCTRL_VLANTUN = (1 << 7), + MV_PORTCTRL_EGRESS = (1 << 12), +}; + +#define MV_FORCE_FC_MASK (3 << 6) + +enum { + MV_FORCE_FC_ENABLE = (3 << 6), + MV_FORCE_FC_DISABLE = (1 << 6), +}; + +enum { + MV_8021Q_EGRESS_UNMODIFIED = 0x00, + MV_8021Q_EGRESS_UNTAGGED = 0x01, + MV_8021Q_EGRESS_TAGGED = 0x02, + MV_8021Q_EGRESS_ADDTAG = 0x03, +}; + +#define MV_8021Q_MODE_SHIFT 10 +#define MV_8021Q_MODE_MASK (0x3 << MV_8021Q_MODE_SHIFT) + +enum { + MV_8021Q_MODE_DISABLE = 0x00, + MV_8021Q_MODE_FALLBACK = 0x01, + MV_8021Q_MODE_CHECK = 0x02, + MV_8021Q_MODE_SECURE = 0x03, +}; + +#define MV_PORTASSOC_MONITOR (1 << 15) + +enum { + MV_SWITCH_MAC0 = 0x01, + MV_SWITCH_MAC1 = 0x02, + MV_SWITCH_MAC2 = 0x03, + MV_SWITCH_CTRL = 0x04, + MV_SWITCH_ATU_CTRL = 0x0a, + MV_SWITCH_ATU_OP = 0x0b, + MV_SWITCH_ATU_DATA = 0x0c, + MV_SWITCH_ATU_MAC0 = 0x0d, + MV_SWITCH_ATU_MAC1 = 0x0e, + MV_SWITCH_ATU_MAC2 = 0x0f, + MV_SWITCH_GLOBAL = 0x1b, + MV_SWITCH_GLOBAL2 = 0x1c, +}; +#define MV_SWITCHREG(_type) MV_SWITCHREGS, MV_SWITCH_##_type + +enum { + MV_SWITCHCTL_EEIE = (1 << 0), + MV_SWITCHCTL_PHYIE = (1 << 1), + MV_SWITCHCTL_ATUDONE = (1 << 2), + MV_SWITCHCTL_ATUIE = (1 << 3), + MV_SWITCHCTL_CTRMODE = (1 << 8), + MV_SWITCHCTL_RELOAD = (1 << 9), + MV_SWITCHCTL_MSIZE = (1 << 10), + MV_SWITCHCTL_DROP = (1 << 13), +}; + +enum { +#define MV_ATUCTL_AGETIME_MIN 16 +#define MV_ATUCTL_AGETIME_MAX 4080 +#define MV_ATUCTL_AGETIME(_n) ((((_n) / 16) & 0xff) << 4) + MV_ATUCTL_ATU_256 = (0 << 12), + MV_ATUCTL_ATU_512 = (1 << 12), + MV_ATUCTL_ATU_1K = (2 << 12), + MV_ATUCTL_ATUMASK = (3 << 12), + MV_ATUCTL_NO_LEARN = (1 << 14), + MV_ATUCTL_RESET = (1 << 15), +}; + +enum { +#define MV_ATUOP_DBNUM(_n) ((_n) & 0x0f) + MV_ATUOP_NOOP = (0 << 12), + MV_ATUOP_FLUSH_ALL = (1 << 12), + MV_ATUOP_FLUSH_U = (2 << 12), + MV_ATUOP_LOAD_DB = (3 << 12), + MV_ATUOP_GET_NEXT = (4 << 12), + MV_ATUOP_FLUSH_DB = (5 << 12), + MV_ATUOP_FLUSH_DB_UU = (6 << 12), + MV_ATUOP_INPROGRESS = (1 << 15), +}; + +enum { + MV_GLOBAL_STATUS = 0x00, + MV_GLOBAL_CONTROL = 0x04, + MV_GLOBAL_VTU_OP = 0x05, + MV_GLOBAL_VTU_VID = 0x06, + MV_GLOBAL_VTU_DATA1 = 0x07, + MV_GLOBAL_VTU_DATA2 = 0x08, + MV_GLOBAL_VTU_DATA3 = 0x09, +}; +#define MV_GLOBALREG(_type) MV_SWITCH_GLOBAL, MV_GLOBAL_##_type + +enum { + MV_GLOBAL2_SDET_POLARITY = 0x1D, +}; +#define MV_GLOBAL2REG(_type) MV_SWITCH_GLOBAL2, MV_GLOBAL2_##_type + +enum { + MV_VTUOP_VALID = (1 << 12), + MV_VTUOP_LOAD = (3 << 12), + MV_VTUOP_INPROGRESS = (1 << 15), +}; + +enum { + MV_CONTROL_RESET = (1 << 15), + MV_CONTROL_PPU_ENABLE = (1 << 14), +}; + +enum { + MV_VTUCTL_EGRESS_UNMODIFIED = 0x00, + MV_VTUCTL_EGRESS_UNTAGGED = 0x01, + MV_VTUCTL_EGRESS_TAGGED = 0x02, + MV_VTUCTL_DISCARD = 0x03, +}; + +enum { + MV_8021Q_VLAN_ONLY = (1 << 15), +}; + +enum { + MV_INDIRECT_REG_CMD = 0, + MV_INDIRECT_REG_DATA = 1, +}; + +enum { + MV_INDIRECT_INPROGRESS = 0x8000, + MV_INDIRECT_WRITE = 0x9400, + MV_INDIRECT_READ = 0x9800, +}; +#define MV_INDIRECT_ADDR_S 5 + +#define MV_IDENT_MASK 0xffc0 +#define MV_IDENT_VALUE 0x1700 +#define MV_IDENT_STR "MV88E6171" + +#define MV_PVID_MASK 0x0fff + +struct mvsw6171_state { + struct switch_dev dev; + struct mii_bus *bus; + int base_addr; + + bool registered; + bool is_indirect; + + int cpu_port0; + int cpu_port1; + + int vlan_enabled; + struct port_state { + u16 pvid; + u16 mask; + u8 qmode; + } ports[MV_PORTS]; + + struct vlan_state { + bool port_based; + + u16 mask; + u16 vid; + u32 port_mode; + } vlans[MV_VLANS]; + + char buf[128]; +}; + +#define get_state(_dev) container_of((_dev), struct mvsw6171_state, dev) + +#endif diff --git a/target/linux/generic/patches-3.14/733-phy_mvsw6171.patch b/target/linux/generic/patches-3.14/733-phy_mvsw6171.patch new file mode 100644 index 0000000000..991828da32 --- /dev/null +++ b/target/linux/generic/patches-3.14/733-phy_mvsw6171.patch @@ -0,0 +1,23 @@ +--- a/drivers/net/phy/Kconfig ++++ b/drivers/net/phy/Kconfig +@@ -131,6 +131,10 @@ config MVSWITCH_PHY + tristate "Driver for Marvell 88E6060 switches" + select ETHERNET_PACKET_MANGLE + ++config MVSW6171_PHY ++ tristate "Driver for Marvell 88E6171 switches" ++ select SWCONFIG ++ + config IP17XX_PHY + tristate "Driver for IC+ IP17xx switches" + select SWCONFIG +--- a/drivers/net/phy/Makefile ++++ b/drivers/net/phy/Makefile +@@ -19,6 +19,7 @@ obj-$(CONFIG_BCM87XX_PHY) += bcm87xx.o + obj-$(CONFIG_ICPLUS_PHY) += icplus.o + obj-$(CONFIG_ADM6996_PHY) += adm6996.o + obj-$(CONFIG_MVSWITCH_PHY) += mvswitch.o ++obj-$(CONFIG_MVSW6171_PHY) += mvsw6171.o + obj-$(CONFIG_IP17XX_PHY) += ip17xx.o + obj-$(CONFIG_REALTEK_PHY) += realtek.o + obj-$(CONFIG_AR8216_PHY) += ar8216.o diff --git a/target/linux/generic/patches-3.18/733-phy_mvsw6171.patch b/target/linux/generic/patches-3.18/733-phy_mvsw6171.patch new file mode 100644 index 0000000000..291164791f --- /dev/null +++ b/target/linux/generic/patches-3.18/733-phy_mvsw6171.patch @@ -0,0 +1,23 @@ +--- a/drivers/net/phy/Kconfig ++++ b/drivers/net/phy/Kconfig +@@ -143,6 +143,10 @@ config MVSWITCH_PHY + tristate "Driver for Marvell 88E6060 switches" + select ETHERNET_PACKET_MANGLE + ++config MVSW6171_PHY ++ tristate "Driver for Marvell 88E6171 switches" ++ select SWCONFIG ++ + config IP17XX_PHY + tristate "Driver for IC+ IP17xx switches" + select SWCONFIG +--- a/drivers/net/phy/Makefile ++++ b/drivers/net/phy/Makefile +@@ -20,6 +20,7 @@ obj-$(CONFIG_BCM87XX_PHY) += bcm87xx.o + obj-$(CONFIG_ICPLUS_PHY) += icplus.o + obj-$(CONFIG_ADM6996_PHY) += adm6996.o + obj-$(CONFIG_MVSWITCH_PHY) += mvswitch.o ++obj-$(CONFIG_MVSW6171_PHY) += mvsw6171.o + obj-$(CONFIG_IP17XX_PHY) += ip17xx.o + obj-$(CONFIG_REALTEK_PHY) += realtek.o + obj-$(CONFIG_AR8216_PHY) += ar8216.o -- 2.30.2