* Marvell 88E61xx switch driver
*
* Copyright (c) 2014 Claudio Leite <leitec@staticky.com>
+ * Copyright (c) 2014 Nikita Nazarenko <nnazarenko@radiofid.com>
*
- * Based on code (c) 2008 Felix Fietkau <nbd@openwrt.org>
+ * Based on code (c) 2008 Felix Fietkau <nbd@nbd.name>
*
* 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
MODULE_DESCRIPTION("Marvell 88E61xx Switch driver");
MODULE_AUTHOR("Claudio Leite <leitec@staticky.com>");
+MODULE_AUTHOR("Nikita Nazarenko <nnazarenko@radiofid.com>");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:mvsw61xx");
return -ETIMEDOUT;
}
+static int
+mvsw61xx_mdio_read(struct switch_dev *dev, int addr, int reg)
+{
+ sw16(dev, MV_GLOBAL2REG(SMI_OP),
+ MV_INDIRECT_READ | (addr << MV_INDIRECT_ADDR_S) | reg);
+
+ if (mvsw61xx_wait_mask_s(dev, MV_GLOBAL2REG(SMI_OP),
+ MV_INDIRECT_INPROGRESS, 0) < 0)
+ return -ETIMEDOUT;
+
+ return sr16(dev, MV_GLOBAL2REG(SMI_DATA));
+}
+
+static int
+mvsw61xx_mdio_write(struct switch_dev *dev, int addr, int reg, u16 val)
+{
+ sw16(dev, MV_GLOBAL2REG(SMI_DATA), val);
+
+ sw16(dev, MV_GLOBAL2REG(SMI_OP),
+ MV_INDIRECT_WRITE | (addr << MV_INDIRECT_ADDR_S) | reg);
+
+ return mvsw61xx_wait_mask_s(dev, MV_GLOBAL2REG(SMI_OP),
+ MV_INDIRECT_INPROGRESS, 0) < 0;
+}
+
+static int
+mvsw61xx_mdio_page_read(struct switch_dev *dev, int port, int page, int reg)
+{
+ int ret;
+
+ mvsw61xx_mdio_write(dev, port, MII_MV_PAGE, page);
+ ret = mvsw61xx_mdio_read(dev, port, reg);
+ mvsw61xx_mdio_write(dev, port, MII_MV_PAGE, 0);
+
+ return ret;
+}
+
+static void
+mvsw61xx_mdio_page_write(struct switch_dev *dev, int port, int page, int reg,
+ u16 val)
+{
+ mvsw61xx_mdio_write(dev, port, MII_MV_PAGE, page);
+ mvsw61xx_mdio_write(dev, port, reg, val);
+ mvsw61xx_mdio_write(dev, port, MII_MV_PAGE, 0);
+}
+
static int
mvsw61xx_get_port_mask(struct switch_dev *dev,
const struct switch_attr *attr, struct switch_val *val)
}
static int
-mvsw61xx_get_pvid(struct switch_dev *dev, int port, int *val)
+mvsw61xx_get_port_pvid(struct switch_dev *dev, int port, int *val)
{
struct mvsw61xx_state *state = get_state(dev);
}
static int
-mvsw61xx_set_pvid(struct switch_dev *dev, int port, int val)
+mvsw61xx_set_port_pvid(struct switch_dev *dev, int port, int val)
{
struct mvsw61xx_state *state = get_state(dev);
}
static int
-mvsw61xx_get_port_status(struct switch_dev *dev,
- const struct switch_attr *attr, struct switch_val *val)
+mvsw61xx_get_port_link(struct switch_dev *dev, int port,
+ struct switch_port_link *link)
{
- struct mvsw61xx_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;
+ status = sr16(dev, MV_PORTREG(STATUS, port));
- return 0;
-}
+ link->link = status & MV_PORT_STATUS_LINK;
+ if (!link->link)
+ return 0;
-static int
-mvsw61xx_get_port_speed(struct switch_dev *dev,
- const struct switch_attr *attr, struct switch_val *val)
-{
- u16 status, speed;
+ link->duplex = status & MV_PORT_STATUS_FDX;
- 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;
- }
+ switch (speed) {
+ case MV_PORT_STATUS_SPEED_10:
+ link->speed = SWITCH_PORT_SPEED_10;
+ break;
+ case MV_PORT_STATUS_SPEED_100:
+ link->speed = SWITCH_PORT_SPEED_100;
+ break;
+ case MV_PORT_STATUS_SPEED_1000:
+ link->speed = SWITCH_PORT_SPEED_1000;
+ break;
}
return 0;
state->vlans[vno].mask = 0;
state->vlans[vno].port_mode = 0;
+ state->vlans[vno].port_sstate = 0;
if(state->vlans[vno].vid == 0)
state->vlans[vno].vid = vno;
mode = MV_VTUCTL_EGRESS_UNTAGGED;
state->vlans[vno].port_mode |= mode << (pno * 4);
+ state->vlans[vno].port_sstate |=
+ MV_STUCTL_STATE_FORWARDING << (pno * 4 + 2);
}
/*
static int mvsw61xx_vtu_program(struct switch_dev *dev)
{
struct mvsw61xx_state *state = get_state(dev);
- u16 v1, v2;
+ u16 v1, v2, s1, s2;
int i;
/* Flush */
mvsw61xx_wait_mask_s(dev, MV_GLOBALREG(VTU_OP),
MV_VTUOP_INPROGRESS, 0);
sw16(dev, MV_GLOBALREG(VTU_OP),
- MV_VTUOP_INPROGRESS | MV_VTUOP_VALID);
+ MV_VTUOP_INPROGRESS | MV_VTUOP_PURGE);
/* Write VLAN table */
for (i = 1; i < dev->vlans; i++) {
mvsw61xx_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);
+ /* Write per-VLAN port state into STU */
+ s1 = (u16) (state->vlans[i].port_sstate & 0xffff);
+ s2 = (u16) ((state->vlans[i].port_sstate >> 16) & 0xffff);
+
+ sw16(dev, MV_GLOBALREG(VTU_VID), MV_VTU_VID_VALID);
+ sw16(dev, MV_GLOBALREG(VTU_SID), i);
+ sw16(dev, MV_GLOBALREG(VTU_DATA1), s1);
+ sw16(dev, MV_GLOBALREG(VTU_DATA2), s2);
+ sw16(dev, MV_GLOBALREG(VTU_DATA3), 0);
+
+ sw16(dev, MV_GLOBALREG(VTU_OP),
+ MV_VTUOP_INPROGRESS | MV_VTUOP_STULOAD);
+ mvsw61xx_wait_mask_s(dev, MV_GLOBALREG(VTU_OP),
+ MV_VTUOP_INPROGRESS, 0);
- v1 = (u16)(state->vlans[i].port_mode & 0xffff);
- v2 = (u16)((state->vlans[i].port_mode >> 16) & 0xffff);
+ /* Write VLAN information into VTU */
+ v1 = (u16) (state->vlans[i].port_mode & 0xffff);
+ v2 = (u16) ((state->vlans[i].port_mode >> 16) & 0xffff);
+ sw16(dev, MV_GLOBALREG(VTU_VID),
+ MV_VTU_VID_VALID | state->vlans[i].vid);
+ sw16(dev, MV_GLOBALREG(VTU_SID), i);
+ sw16(dev, MV_GLOBALREG(VTU_FID), i);
sw16(dev, MV_GLOBALREG(VTU_DATA1), v1);
sw16(dev, MV_GLOBALREG(VTU_DATA2), v2);
+ sw16(dev, MV_GLOBALREG(VTU_DATA3), 0);
sw16(dev, MV_GLOBALREG(VTU_OP),
MV_VTUOP_INPROGRESS | MV_VTUOP_LOAD);
if(mode != MV_VTUCTL_EGRESS_TAGGED)
state->ports[i].pvid = state->vlans[vno].vid;
- if (state->vlans[vno].port_based)
+ if (state->vlans[vno].port_based) {
state->ports[i].mask |= state->vlans[vno].mask;
+ state->ports[i].fdb = vno;
+ }
else
state->ports[i].qmode = MV_8021Q_MODE_SECURE;
}
if (!state->registered)
return -EINVAL;
- mvsw61xx_vtu_program(dev);
-
/*
* Set 802.1q-only mode if vlan_enabled is true.
*
state->ports[i].mask &= ~(1 << i);
- reg = sr16(dev, MV_PORTREG(VLANMAP, i)) & ~MV_PORTS_MASK;
- reg |= state->ports[i].mask;
+ /* set default forwarding DB number and port mask */
+ reg = sr16(dev, MV_PORTREG(CONTROL1, i)) & ~MV_FDB_HI_MASK;
+ reg |= (state->ports[i].fdb >> MV_FDB_HI_SHIFT) &
+ MV_FDB_HI_MASK;
+ sw16(dev, MV_PORTREG(CONTROL1, i), reg);
+
+ reg = ((state->ports[i].fdb & 0xf) << MV_FDB_LO_SHIFT) |
+ state->ports[i].mask;
sw16(dev, MV_PORTREG(VLANMAP, i), reg);
reg = sr16(dev, MV_PORTREG(CONTROL2, i)) &
sw16(dev, MV_PORTREG(CONTROL2, i), reg);
}
+ mvsw61xx_vtu_program(dev);
+
return 0;
}
return mvsw61xx_update_state(dev);
}
-static int mvsw61xx_reset(struct switch_dev *dev)
+static void mvsw61xx_enable_serdes(struct switch_dev *dev)
+{
+ int bmcr = mvsw61xx_mdio_page_read(dev, MV_REG_FIBER_SERDES,
+ MV_PAGE_FIBER_SERDES, MII_BMCR);
+ if (bmcr < 0)
+ return;
+
+ if (bmcr & BMCR_PDOWN)
+ mvsw61xx_mdio_page_write(dev, MV_REG_FIBER_SERDES,
+ MV_PAGE_FIBER_SERDES, MII_BMCR,
+ bmcr & ~BMCR_PDOWN);
+}
+
+static int _mvsw61xx_reset(struct switch_dev *dev, bool full)
{
struct mvsw61xx_state *state = get_state(dev);
int i;
/* Disable all ports before reset */
for (i = 0; i < dev->ports; i++) {
reg = sr16(dev, MV_PORTREG(CONTROL, i)) &
- ~MV_PORTCTRL_ENABLED;
+ ~MV_PORTCTRL_FORWARDING;
sw16(dev, MV_PORTREG(CONTROL, i), reg);
}
return -ETIMEDOUT;
for (i = 0; i < dev->ports; i++) {
+ state->ports[i].fdb = 0;
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);
+ reg = sr16(dev, MV_PORTREG(PHYCTL, i)) & ~MV_PHYCTL_FC_MASK;
+ reg |= MV_PHYCTL_FC_DISABLE;
+ sw16(dev, MV_PORTREG(PHYCTL, i), reg);
/* Set port association vector */
sw16(dev, MV_PORTREG(ASSOC, i), (1 << i));
+
+ /* power up phys */
+ if (full && i < 5) {
+ mvsw61xx_mdio_write(dev, i, MII_MV_SPEC_CTRL,
+ MV_SPEC_MDI_CROSS_AUTO |
+ MV_SPEC_ENERGY_DETECT |
+ MV_SPEC_DOWNSHIFT_COUNTER);
+ mvsw61xx_mdio_write(dev, i, MII_BMCR, BMCR_RESET |
+ BMCR_ANENABLE | BMCR_FULLDPLX |
+ BMCR_SPEED1000);
+ }
+
+ /* enable SerDes if necessary */
+ if (full && i >= 5 && state->model == MV_IDENT_VALUE_6176) {
+ u16 sts = sr16(dev, MV_PORTREG(STATUS, i));
+ u16 mode = sts & MV_PORT_STATUS_CMODE_MASK;
+
+ if (mode == MV_PORT_STATUS_CMODE_100BASE_X ||
+ mode == MV_PORT_STATUS_CMODE_1000BASE_X ||
+ mode == MV_PORT_STATUS_CMODE_SGMII) {
+ mvsw61xx_enable_serdes(dev);
+ }
+ }
}
for (i = 0; i < dev->vlans; i++) {
state->vlans[i].mask = 0;
state->vlans[i].vid = 0;
state->vlans[i].port_mode = 0;
+ state->vlans[i].port_sstate = 0;
}
state->vlan_enabled = 0;
/* Re-enable ports */
for (i = 0; i < dev->ports; i++) {
reg = sr16(dev, MV_PORTREG(CONTROL, i)) |
- MV_PORTCTRL_ENABLED;
+ MV_PORTCTRL_FORWARDING;
sw16(dev, MV_PORTREG(CONTROL, i), reg);
}
return 0;
}
+static int mvsw61xx_reset(struct switch_dev *dev)
+{
+ return _mvsw61xx_reset(dev, false);
+}
+
enum {
MVSW61XX_ENABLE_VLAN,
};
enum {
MVSW61XX_PORT_MASK,
MVSW61XX_PORT_QMODE,
- MVSW61XX_PORT_STATUS,
- MVSW61XX_PORT_LINK,
};
static const struct switch_attr mvsw61xx_global[] = {
.get = mvsw61xx_get_port_qmode,
.set = mvsw61xx_set_port_qmode,
},
- [MVSW61XX_PORT_STATUS] = {
- .id = MVSW61XX_PORT_STATUS,
- .type = SWITCH_TYPE_STRING,
- .description = "Return port status",
- .name = "status",
- .get = mvsw61xx_get_port_status,
- .set = NULL,
- },
- [MVSW61XX_PORT_LINK] = {
- .id = MVSW61XX_PORT_LINK,
- .type = SWITCH_TYPE_INT,
- .description = "Get link speed",
- .name = "link",
- .get = mvsw61xx_get_port_speed,
- .set = NULL,
- },
};
static const struct switch_dev_ops mvsw61xx_ops = {
.attr = mvsw61xx_port,
.n_attr = ARRAY_SIZE(mvsw61xx_port),
},
- .get_port_pvid = mvsw61xx_get_pvid,
- .set_port_pvid = mvsw61xx_set_pvid,
+ .get_port_link = mvsw61xx_get_port_link,
+ .get_port_pvid = mvsw61xx_get_port_pvid,
+ .set_port_pvid = mvsw61xx_set_port_pvid,
.get_vlan_ports = mvsw61xx_get_vlan_ports,
.set_vlan_ports = mvsw61xx_set_vlan_ports,
.apply_config = mvsw61xx_apply,
struct mvsw61xx_state *state;
struct device_node *np = pdev->dev.of_node;
struct device_node *mdio;
+ char *model_str;
u32 val;
- u16 reg;
int err;
state = kzalloc(sizeof(*state), GFP_KERNEL);
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->model = r16(state->bus, state->is_indirect, state->base_addr,
+ MV_PORTREG(IDENT, 0)) & MV_IDENT_MASK;
+
+ switch(state->model) {
+ case MV_IDENT_VALUE_6171:
+ model_str = MV_IDENT_STR_6171;
+ break;
+ case MV_IDENT_VALUE_6172:
+ model_str = MV_IDENT_STR_6172;
+ break;
+ case MV_IDENT_VALUE_6176:
+ model_str = MV_IDENT_STR_6176;
+ break;
+ case MV_IDENT_VALUE_6352:
+ model_str = MV_IDENT_STR_6352;
+ break;
+ default:
+ dev_err(&pdev->dev, "No compatible 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,
+ dev_info(&pdev->dev, "Found %s at %s:%02x\n", model_str,
state->bus->id, state->base_addr);
dev_info(&pdev->dev, "Using %sdirect addressing\n",
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.name = model_str;
state->dev.ops = &mvsw61xx_ops;
state->dev.alias = dev_name(&pdev->dev);
+ _mvsw61xx_reset(&state->dev, true);
+
err = register_switch(&state->dev, NULL);
if (err < 0)
goto out_err;
static const struct of_device_id mvsw61xx_match[] = {
{ .compatible = "marvell,88e6171" },
+ { .compatible = "marvell,88e6172" },
+ { .compatible = "marvell,88e6176" },
+ { .compatible = "marvell,88e6352" },
{ }
};
MODULE_DEVICE_TABLE(of, mvsw61xx_match);