wireguard-tools: add tunlink option for hostroute
[openwrt/staging/mkresin.git] / target / linux / ar71xx / files / drivers / net / ethernet / atheros / ag71xx / ag71xx_ar7240.c
index 25ee7d4f14e626e31f9b1c62255ad70268173e44..2859c0dbe1641f6215006063248bc8a3810cbd4c 100644 (file)
@@ -1,7 +1,7 @@
 /*
  *  Driver for the built-in ethernet switch of the Atheros AR7240 SoC
  *  Copyright (c) 2010 Gabor Juhos <juhosg@openwrt.org>
- *  Copyright (c) 2010 Felix Fietkau <nbd@openwrt.org>
+ *  Copyright (c) 2010 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 version 2 as published
@@ -36,7 +36,8 @@
 #define AR7240_FLOOD_MASK_BROAD_TO_CPU BIT(26)
 
 #define AR7240_REG_GLOBAL_CTRL         0x30
-#define AR7240_GLOBAL_CTRL_MTU_M       BITM(12)
+#define AR7240_GLOBAL_CTRL_MTU_M       BITM(11)
+#define AR9340_GLOBAL_CTRL_MTU_M       BITM(14)
 
 #define AR7240_REG_VTU                 0x0040
 #define   AR7240_VTU_OP                        BITM(3)
@@ -76,6 +77,7 @@
 
 #define AR7240_REG_CPU_PORT            0x78
 #define AR7240_MIRROR_PORT_S           4
+#define AR7240_MIRROR_PORT_M           BITM(4)
 #define AR7240_CPU_PORT_EN             BIT(8)
 
 #define AR7240_REG_MIB_FUNCTION0       0x80
@@ -83,6 +85,7 @@
 #define AR7240_MIB_AT_HALF_EN          BIT(16)
 #define AR7240_MIB_BUSY                        BIT(17)
 #define AR7240_MIB_FUNC_S              24
+#define AR7240_MIB_FUNC_M              BITM(3)
 #define AR7240_MIB_FUNC_NO_OP          0x0
 #define AR7240_MIB_FUNC_FLUSH          0x1
 #define AR7240_MIB_FUNC_CAPTURE                0x3
 #define AR934X_REG_OPER_MODE1          0x08
 #define   AR934X_REG_OPER_MODE1_PHY4_MII_EN    BIT(28)
 
+#define AR934X_REG_FLOOD_MASK          0x2c
+#define   AR934X_FLOOD_MASK_MC_DP(_p)  BIT(16 + (_p))
+#define   AR934X_FLOOD_MASK_BC_DP(_p)  BIT(25 + (_p))
+
+#define AR934X_REG_QM_CTRL             0x3c
+#define   AR934X_QM_CTRL_ARP_EN                BIT(15)
+
+#define AR934X_REG_AT_CTRL             0x5c
+#define   AR934X_AT_CTRL_AGE_TIME      BITS(0, 15)
+#define   AR934X_AT_CTRL_AGE_EN                BIT(17)
+#define   AR934X_AT_CTRL_LEARN_CHANGE  BIT(18)
+
+#define AR934X_MIB_ENABLE              BIT(30)
+
 #define AR934X_REG_PORT_BASE(_port)    (0x100 + (_port) * 0x100)
 
 #define AR934X_REG_PORT_VLAN1(_port)   (AR934X_REG_PORT_BASE((_port)) + 0x08)
@@ -429,7 +446,7 @@ static int __ar7240sw_reg_wait(struct mii_bus *mii, u32 reg, u32 mask, u32 val,
                if ((t & mask) == val)
                        return 0;
 
-               msleep(1);
+               usleep_range(1000, 2000);
        }
 
        return -ETIMEDOUT;
@@ -506,8 +523,9 @@ static int ar7240sw_capture_stats(struct ar7240sw *as)
        write_lock(&as->stats_lock);
 
        /* Capture the hardware statistics for all ports */
-       ar7240sw_reg_write(mii, AR7240_REG_MIB_FUNCTION0,
-                          (AR7240_MIB_FUNC_CAPTURE << AR7240_MIB_FUNC_S));
+       ar7240sw_reg_rmw(mii, AR7240_REG_MIB_FUNCTION0,
+                        (AR7240_MIB_FUNC_M << AR7240_MIB_FUNC_S),
+                        (AR7240_MIB_FUNC_CAPTURE << AR7240_MIB_FUNC_S));
 
        /* Wait for the capturing to complete. */
        ret = ar7240sw_reg_wait(mii, AR7240_REG_MIB_FUNCTION0,
@@ -556,26 +574,76 @@ static void ar7240sw_setup(struct ar7240sw *as)
        /* Setup TAG priority mapping */
        ar7240sw_reg_write(mii, AR7240_REG_TAG_PRIORITY, 0xfa50);
 
-       /* Enable ARP frame acknowledge, aging, MAC replacing */
-       ar7240sw_reg_write(mii, AR7240_REG_AT_CTRL,
-               AR7240_AT_CTRL_RESERVED |
-               0x2b /* 5 min age time */ |
-               AR7240_AT_CTRL_AGE_EN |
-               AR7240_AT_CTRL_ARP_EN |
-               AR7240_AT_CTRL_LEARN_CHANGE);
-
-       /* Enable Broadcast frames transmitted to the CPU */
-       ar7240sw_reg_set(mii, AR7240_REG_FLOOD_MASK,
-                        AR7240_FLOOD_MASK_BROAD_TO_CPU);
+       if (sw_is_ar934x(as)) {
+               /* Enable aging, MAC replacing */
+               ar7240sw_reg_write(mii, AR934X_REG_AT_CTRL,
+                       0x2b /* 5 min age time */ |
+                       AR934X_AT_CTRL_AGE_EN |
+                       AR934X_AT_CTRL_LEARN_CHANGE);
+               /* Enable ARP frame acknowledge */
+               ar7240sw_reg_set(mii, AR934X_REG_QM_CTRL,
+                                AR934X_QM_CTRL_ARP_EN);
+               /* Enable Broadcast/Multicast frames transmitted to the CPU */
+               ar7240sw_reg_set(mii, AR934X_REG_FLOOD_MASK,
+                                AR934X_FLOOD_MASK_BC_DP(0) |
+                                AR934X_FLOOD_MASK_MC_DP(0));
+
+               /* setup MTU */
+               ar7240sw_reg_rmw(mii, AR7240_REG_GLOBAL_CTRL,
+                                AR9340_GLOBAL_CTRL_MTU_M,
+                                AR9340_GLOBAL_CTRL_MTU_M);
+
+               /* Enable MIB counters */
+               ar7240sw_reg_set(mii, AR7240_REG_MIB_FUNCTION0,
+                                AR934X_MIB_ENABLE);
 
-       /* setup MTU */
-       ar7240sw_reg_rmw(mii, AR7240_REG_GLOBAL_CTRL, AR7240_GLOBAL_CTRL_MTU_M,
-                        1536);
+       } else {
+               /* Enable ARP frame acknowledge, aging, MAC replacing */
+               ar7240sw_reg_write(mii, AR7240_REG_AT_CTRL,
+                       AR7240_AT_CTRL_RESERVED |
+                       0x2b /* 5 min age time */ |
+                       AR7240_AT_CTRL_AGE_EN |
+                       AR7240_AT_CTRL_ARP_EN |
+                       AR7240_AT_CTRL_LEARN_CHANGE);
+               /* Enable Broadcast frames transmitted to the CPU */
+               ar7240sw_reg_set(mii, AR7240_REG_FLOOD_MASK,
+                                AR7240_FLOOD_MASK_BROAD_TO_CPU);
+
+               /* setup MTU */
+               ar7240sw_reg_rmw(mii, AR7240_REG_GLOBAL_CTRL,
+                                AR7240_GLOBAL_CTRL_MTU_M,
+                                AR7240_GLOBAL_CTRL_MTU_M);
+       }
 
        /* setup Service TAG */
        ar7240sw_reg_rmw(mii, AR7240_REG_SERVICE_TAG, AR7240_SERVICE_TAG_M, 0);
 }
 
+/* inspired by phy_poll_reset in drivers/net/phy/phy_device.c */
+static int
+ar7240sw_phy_poll_reset(struct mii_bus *bus)
+{
+       const unsigned int sleep_msecs = 20;
+       int ret, elapsed, i;
+
+       for (elapsed = sleep_msecs; elapsed <= 600;
+            elapsed += sleep_msecs) {
+               msleep(sleep_msecs);
+               for (i = 0; i < AR7240_NUM_PHYS; i++) {
+                       ret = ar7240sw_phy_read(bus, i, MII_BMCR);
+                       if (ret < 0)
+                               return ret;
+                       if (ret & BMCR_RESET)
+                               break;
+                       if (i == AR7240_NUM_PHYS - 1) {
+                               usleep_range(1000, 2000);
+                               return 0;
+                       }
+               }
+       }
+       return -ETIMEDOUT;
+}
+
 static int ar7240sw_reset(struct ar7240sw *as)
 {
        struct mii_bus *mii = as->mii_bus;
@@ -587,7 +655,7 @@ static int ar7240sw_reset(struct ar7240sw *as)
                ar7240sw_disable_port(as, i);
 
        /* Wait for transmit queues to drain. */
-       msleep(2);
+       usleep_range(2000, 3000);
 
        /* Reset the switch. */
        ar7240sw_reg_write(mii, AR7240_REG_MASK_CTRL,
@@ -596,6 +664,18 @@ static int ar7240sw_reset(struct ar7240sw *as)
        ret = ar7240sw_reg_wait(mii, AR7240_REG_MASK_CTRL,
                                AR7240_MASK_CTRL_SOFT_RESET, 0, 1000);
 
+       /* setup PHYs */
+       for (i = 0; i < AR7240_NUM_PHYS; i++) {
+               ar7240sw_phy_write(mii, i, MII_ADVERTISE,
+                                  ADVERTISE_ALL | ADVERTISE_PAUSE_CAP |
+                                  ADVERTISE_PAUSE_ASYM);
+               ar7240sw_phy_write(mii, i, MII_BMCR,
+                                  BMCR_RESET | BMCR_ANENABLE);
+       }
+       ret = ar7240sw_phy_poll_reset(mii);
+       if (ret)
+               return ret;
+
        ar7240sw_setup(as);
        return ret;
 }
@@ -650,6 +730,10 @@ static void ar7240sw_setup_port(struct ar7240sw *as, unsigned port, u8 portmask)
                        portmask = ar7240sw_port_mask(as, AR7240_PORT_CPU);
        }
 
+       /* preserve mirror rx&tx flags */
+       ctrl |= ar7240sw_reg_read(mii, AR7240_REG_PORT_CTRL(port)) &
+               (AR7240_PORT_CTRL_MIRROR_RX | AR7240_PORT_CTRL_MIRROR_TX);
+
        /* allow the port to talk to all other ports, but exclude its
         * own ID to prevent frames from being reflected back to the
         * port that they came from */
@@ -884,7 +968,7 @@ ar7240_get_port_link(struct switch_dev *dev, int port,
        struct mii_bus *mii = as->mii_bus;
        u32 status;
 
-       if (port > AR7240_NUM_PORTS)
+       if (port >= AR7240_NUM_PORTS)
                return -EINVAL;
 
        status = ar7240sw_reg_read(mii, AR7240_REG_PORT_STATUS(port));
@@ -921,7 +1005,7 @@ ar7240_get_port_stats(struct switch_dev *dev, int port,
 {
        struct ar7240sw *as = sw_to_ar7240(dev);
 
-       if (port > AR7240_NUM_PORTS)
+       if (port >= AR7240_NUM_PORTS)
                return -EINVAL;
 
        ar7240sw_capture_stats(as);
@@ -934,6 +1018,134 @@ ar7240_get_port_stats(struct switch_dev *dev, int port,
        return 0;
 }
 
+static int
+ar7240_set_mirror_monitor_port(struct switch_dev *dev,
+                               const struct switch_attr *attr,
+                               struct switch_val *val)
+{
+       struct ar7240sw *as = sw_to_ar7240(dev);
+       struct mii_bus *mii = as->mii_bus;
+
+       int port = val->value.i;
+
+       if (port > 15)
+               return -EINVAL;
+
+       ar7240sw_reg_rmw(mii, AR7240_REG_CPU_PORT,
+               AR7240_MIRROR_PORT_M << AR7240_MIRROR_PORT_S,
+               port << AR7240_MIRROR_PORT_S);
+
+       return 0;
+}
+
+static int
+ar7240_get_mirror_monitor_port(struct switch_dev *dev,
+                               const struct switch_attr *attr,
+                               struct switch_val *val)
+{
+       struct ar7240sw *as = sw_to_ar7240(dev);
+       struct mii_bus *mii = as->mii_bus;
+
+       u32 ret;
+
+       ret = ar7240sw_reg_read(mii, AR7240_REG_CPU_PORT);
+       val->value.i = (ret >> AR7240_MIRROR_PORT_S) & AR7240_MIRROR_PORT_M;
+
+       return 0;
+}
+
+static int
+ar7240_set_mirror_rx(struct switch_dev *dev, const struct switch_attr *attr,
+                     struct switch_val *val)
+{
+       struct ar7240sw *as = sw_to_ar7240(dev);
+       struct mii_bus *mii = as->mii_bus;
+
+       int port = val->port_vlan;
+
+       if (port >= dev->ports)
+               return -EINVAL;
+
+       if (val && val->value.i == 1)
+               ar7240sw_reg_set(mii, AR7240_REG_PORT_CTRL(port),
+                       AR7240_PORT_CTRL_MIRROR_RX);
+       else
+               ar7240sw_reg_rmw(mii, AR7240_REG_PORT_CTRL(port),
+                       AR7240_PORT_CTRL_MIRROR_RX, 0);
+
+       return 0;
+}
+
+static int
+ar7240_get_mirror_rx(struct switch_dev *dev, const struct switch_attr *attr,
+                     struct switch_val *val)
+{
+       struct ar7240sw *as = sw_to_ar7240(dev);
+       struct mii_bus *mii = as->mii_bus;
+
+       u32 ctrl;
+
+       int port = val->port_vlan;
+
+       if (port >= dev->ports)
+               return -EINVAL;
+
+       ctrl = ar7240sw_reg_read(mii, AR7240_REG_PORT_CTRL(port));
+
+       if ((ctrl & AR7240_PORT_CTRL_MIRROR_RX) == AR7240_PORT_CTRL_MIRROR_RX)
+               val->value.i = 1;
+       else
+               val->value.i = 0;
+
+       return 0;
+}
+
+static int
+ar7240_set_mirror_tx(struct switch_dev *dev, const struct switch_attr *attr,
+                     struct switch_val *val)
+{
+       struct ar7240sw *as = sw_to_ar7240(dev);
+       struct mii_bus *mii = as->mii_bus;
+
+       int port = val->port_vlan;
+
+       if (port >= dev->ports)
+               return -EINVAL;
+
+       if (val && val->value.i == 1)
+               ar7240sw_reg_set(mii, AR7240_REG_PORT_CTRL(port),
+                       AR7240_PORT_CTRL_MIRROR_TX);
+       else
+               ar7240sw_reg_rmw(mii, AR7240_REG_PORT_CTRL(port),
+                       AR7240_PORT_CTRL_MIRROR_TX, 0);
+
+       return 0;
+}
+
+static int
+ar7240_get_mirror_tx(struct switch_dev *dev, const struct switch_attr *attr,
+                     struct switch_val *val)
+{
+       struct ar7240sw *as = sw_to_ar7240(dev);
+       struct mii_bus *mii = as->mii_bus;
+
+       u32 ctrl;
+
+       int port = val->port_vlan;
+
+       if (port >= dev->ports)
+               return -EINVAL;
+
+       ctrl = ar7240sw_reg_read(mii, AR7240_REG_PORT_CTRL(port));
+
+       if ((ctrl & AR7240_PORT_CTRL_MIRROR_TX) == AR7240_PORT_CTRL_MIRROR_TX)
+               val->value.i = 1;
+       else
+               val->value.i = 0;
+
+       return 0;
+}
+
 static struct switch_attr ar7240_globals[] = {
        {
                .type = SWITCH_TYPE_INT,
@@ -943,9 +1155,33 @@ static struct switch_attr ar7240_globals[] = {
                .get = ar7240_get_vlan,
                .max = 1
        },
+       {
+               .type = SWITCH_TYPE_INT,
+               .name = "mirror_monitor_port",
+               .description = "Mirror monitor port",
+               .set = ar7240_set_mirror_monitor_port,
+               .get = ar7240_get_mirror_monitor_port,
+               .max = 15
+       },
 };
 
 static struct switch_attr ar7240_port[] = {
+       {
+               .type = SWITCH_TYPE_INT,
+               .name = "enable_mirror_rx",
+               .description = "Enable mirroring of RX packets",
+               .set = ar7240_set_mirror_rx,
+               .get = ar7240_get_mirror_rx,
+               .max = 1
+       },
+       {
+               .type = SWITCH_TYPE_INT,
+               .name = "enable_mirror_tx",
+               .description = "Enable mirroring of TX packets",
+               .set = ar7240_set_mirror_tx,
+               .get = ar7240_get_mirror_tx,
+               .max = 1
+       },
 };
 
 static struct switch_attr ar7240_vlan[] = {
@@ -998,7 +1234,7 @@ static struct ar7240sw *ar7240_probe(struct ag71xx *ag)
        if ((phy_id1 != AR7240_PHY_ID1 || phy_id2 != AR7240_PHY_ID2) &&
            (phy_id1 != AR934X_PHY_ID1 || phy_id2 != AR934X_PHY_ID2)) {
                pr_err("%s: unknown phy id '%04x:%04x'\n",
-                      ag->dev->name, phy_id1, phy_id2);
+                      dev_name(&mii->dev), phy_id1, phy_id2);
                return NULL;
        }
 
@@ -1029,7 +1265,7 @@ static struct ar7240sw *ar7240_probe(struct ag71xx *ag)
                                         AR934X_OPER_MODE0_PHY_MII_EN);
                } else {
                        pr_err("%s: invalid PHY interface mode\n",
-                              ag->dev->name);
+                              dev_name(&mii->dev));
                        goto err_free;
                }
 
@@ -1042,7 +1278,7 @@ static struct ar7240sw *ar7240_probe(struct ag71xx *ag)
                }
        } else {
                pr_err("%s: unsupported chip, ctrl=%08x\n",
-                       ag->dev->name, ctrl);
+                       dev_name(&mii->dev), ctrl);
                goto err_free;
        }
 
@@ -1053,7 +1289,7 @@ static struct ar7240sw *ar7240_probe(struct ag71xx *ag)
        if (register_switch(&as->swdev, ag->dev) < 0)
                goto err_free;
 
-       pr_info("%s: Found an %s\n", ag->dev->name, swdev->name);
+       pr_info("%s: Found an %s\n", dev_name(&mii->dev), swdev->name);
 
        /* initialize defaults */
        for (i = 0; i < AR7240_MAX_VLANS; i++)
@@ -1070,20 +1306,28 @@ err_free:
 
 static void link_function(struct work_struct *work) {
        struct ag71xx *ag = container_of(work, struct ag71xx, link_work.work);
+       struct ar7240sw *as = ag->phy_priv;
        unsigned long flags;
+       u8 mask;
        int i;
        int status = 0;
 
-       for (i = 0; i < 4; i++) {
-               int link = ar7240sw_phy_read(ag->mii_bus, i, MII_BMSR);
-               if(link & BMSR_LSTATUS) {
+       mask = ~as->swdata->phy_poll_mask;
+       for (i = 0; i < AR7240_NUM_PHYS; i++) {
+               int link;
+
+               if (!(mask & BIT(i)))
+                       continue;
+
+               link = ar7240sw_phy_read(ag->mii_bus, i, MII_BMSR);
+               if (link & BMSR_LSTATUS) {
                        status = 1;
                        break;
                }
        }
 
        spin_lock_irqsave(&ag->lock, flags);
-       if(status != ag->link) {
+       if (status != ag->link) {
                ag->link = status;
                ag71xx_link_adjust(ag);
        }
@@ -1112,7 +1356,7 @@ void ag71xx_ar7240_stop(struct ag71xx *ag)
        cancel_delayed_work_sync(&ag->link_work);
 }
 
-int __devinit ag71xx_ar7240_init(struct ag71xx *ag)
+int ag71xx_ar7240_init(struct ag71xx *ag)
 {
        struct ar7240sw *as;