mvsw61xx: add support for MV88E6352
[openwrt/openwrt.git] / target / linux / generic / files / drivers / net / phy / mvsw61xx.c
index 7199de0eb21b05b6afc76f8a5379bd73fdd7e044..9a689e6c81b69cbe29995c929a0de9d35344663d 100644 (file)
@@ -2,8 +2,9 @@
  * 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
@@ -27,6 +28,7 @@
 
 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");
 
@@ -145,6 +147,52 @@ mvsw61xx_wait_mask_s(struct switch_dev *dev, int addr,
        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)
@@ -194,7 +242,7 @@ mvsw61xx_set_port_qmode(struct switch_dev *dev,
 }
 
 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);
 
@@ -204,7 +252,7 @@ mvsw61xx_get_pvid(struct switch_dev *dev, int port, int *val)
 }
 
 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);
 
@@ -217,73 +265,32 @@ mvsw61xx_set_pvid(struct switch_dev *dev, int port, int val)
 }
 
 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;
@@ -333,6 +340,7 @@ static int mvsw61xx_set_vlan_ports(struct switch_dev *dev,
 
        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;
@@ -348,6 +356,8 @@ static int mvsw61xx_set_vlan_ports(struct switch_dev *dev,
                        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);
        }
 
        /*
@@ -447,14 +457,14 @@ static int mvsw61xx_set_enable_vlan(struct switch_dev *dev,
 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++) {
@@ -466,14 +476,32 @@ static int mvsw61xx_vtu_program(struct switch_dev *dev)
                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);
@@ -498,8 +526,10 @@ static void mvsw61xx_vlan_port_config(struct switch_dev *dev, int vno)
                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;
        }
@@ -514,8 +544,6 @@ static int mvsw61xx_update_state(struct switch_dev *dev)
        if (!state->registered)
                return -EINVAL;
 
-       mvsw61xx_vtu_program(dev);
-
        /*
         * Set 802.1q-only mode if vlan_enabled is true.
         *
@@ -558,8 +586,14 @@ static int mvsw61xx_update_state(struct switch_dev *dev)
 
                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)) &
@@ -568,6 +602,8 @@ static int mvsw61xx_update_state(struct switch_dev *dev)
                sw16(dev, MV_PORTREG(CONTROL2, i), reg);
        }
 
+       mvsw61xx_vtu_program(dev);
+
        return 0;
 }
 
@@ -576,7 +612,20 @@ static int mvsw61xx_apply(struct switch_dev *dev)
        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;
@@ -585,7 +634,7 @@ static int mvsw61xx_reset(struct switch_dev *dev)
        /* 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);
        }
 
@@ -597,17 +646,41 @@ static int mvsw61xx_reset(struct switch_dev *dev)
                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++) {
@@ -615,6 +688,7 @@ static int mvsw61xx_reset(struct switch_dev *dev)
                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;
@@ -624,13 +698,18 @@ static int mvsw61xx_reset(struct switch_dev *dev)
        /* 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,
 };
@@ -643,8 +722,6 @@ enum {
 enum {
        MVSW61XX_PORT_MASK,
        MVSW61XX_PORT_QMODE,
-       MVSW61XX_PORT_STATUS,
-       MVSW61XX_PORT_LINK,
 };
 
 static const struct switch_attr mvsw61xx_global[] = {
@@ -694,22 +771,6 @@ static const struct switch_attr mvsw61xx_port[] = {
                .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 = {
@@ -725,8 +786,9 @@ 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,
@@ -740,8 +802,8 @@ static int mvsw61xx_probe(struct platform_device *pdev)
        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);
@@ -776,17 +838,31 @@ static int mvsw61xx_probe(struct platform_device *pdev)
                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",
@@ -808,10 +884,12 @@ static int mvsw61xx_probe(struct platform_device *pdev)
        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;
@@ -839,6 +917,9 @@ mvsw61xx_remove(struct platform_device *pdev)
 
 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);