AR8216: make ARL age time configurable
[openwrt.git] / target / linux / generic / files / drivers / net / phy / ar8216.c
index f4c7b8d..817aca5 100644 (file)
@@ -1076,10 +1076,25 @@ ar8216_set_mirror_regs(struct ar8xxx_priv *priv)
                           AR8216_PORT_CTRL_MIRROR_TX);
 }
 
+static inline u32
+ar8xxx_age_time_val(int age_time)
+{
+       return (age_time + AR8XXX_REG_ARL_CTRL_AGE_TIME_SECS / 2) /
+              AR8XXX_REG_ARL_CTRL_AGE_TIME_SECS;
+}
+
+static inline void
+ar8xxx_set_age_time(struct ar8xxx_priv *priv, int reg)
+{
+       u32 age_time = ar8xxx_age_time_val(priv->arl_age_time);
+       ar8xxx_rmw(priv, reg, AR8216_ATU_CTRL_AGE_TIME, age_time << AR8216_ATU_CTRL_AGE_TIME_S);
+}
+
 int
 ar8xxx_sw_hw_apply(struct switch_dev *dev)
 {
        struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev);
+       const struct ar8xxx_chip *chip = priv->chip;
        u8 portmask[AR8X16_MAX_PORTS];
        int i, j;
 
@@ -1103,8 +1118,8 @@ ar8xxx_sw_hw_apply(struct switch_dev *dev)
                                        portmask[i] |= vp & ~mask;
                        }
 
-                       priv->chip->vtu_load_vlan(priv, priv->vlan_id[j],
-                                                priv->vlan_table[j]);
+                       chip->vtu_load_vlan(priv, priv->vlan_id[j],
+                                           priv->vlan_table[j]);
                }
        } else {
                /* vlan disabled:
@@ -1120,10 +1135,14 @@ ar8xxx_sw_hw_apply(struct switch_dev *dev)
 
        /* update the port destination mask registers and tag settings */
        for (i = 0; i < dev->ports; i++) {
-               priv->chip->setup_port(priv, i, portmask[i]);
+               chip->setup_port(priv, i, portmask[i]);
        }
 
-       priv->chip->set_mirror_regs(priv);
+       chip->set_mirror_regs(priv);
+
+       /* set age time */
+       if (chip->reg_arl_ctrl)
+               ar8xxx_set_age_time(priv, chip->reg_arl_ctrl);
 
        mutex_unlock(&priv->reg_mutex);
        return 0;
@@ -1151,6 +1170,7 @@ ar8xxx_sw_reset_switch(struct switch_dev *dev)
        priv->mirror_tx = false;
        priv->source_port = 0;
        priv->monitor_port = 0;
+       priv->arl_age_time = AR8XXX_DEFAULT_ARL_AGE_TIME;
 
        chip->init_globals(priv);
 
@@ -1317,6 +1337,31 @@ unlock:
        return ret;
 }
 
+static void
+ar8xxx_byte_to_str(char *buf, int len, u64 byte)
+{
+       unsigned long b;
+       const char *unit;
+
+       if (byte >= 0x40000000) { /* 1 GiB */
+               b = byte * 10 / 0x40000000;
+               unit = "GiB";
+       } else if (byte >= 0x100000) { /* 1 MiB */
+               b = byte * 10 / 0x100000;
+               unit = "MiB";
+       } else if (byte >= 0x400) { /* 1 KiB */
+               b = byte * 10 / 0x400;
+               unit = "KiB";
+       } else {
+               b = byte;
+               unit = "Byte";
+       }
+       if (strcmp(unit, "Byte"))
+               snprintf(buf, len, "%lu.%lu %s", b / 10, b % 10, unit);
+       else
+               snprintf(buf, len, "%lu %s", b, unit);
+}
+
 int
 ar8xxx_sw_get_port_mib(struct switch_dev *dev,
                       const struct switch_attr *attr,
@@ -1324,11 +1369,14 @@ ar8xxx_sw_get_port_mib(struct switch_dev *dev,
 {
        struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev);
        const struct ar8xxx_chip *chip = priv->chip;
-       u64 *mib_stats;
+       u64 *mib_stats, mib_data;
        int port;
        int ret;
        char *buf = priv->buf;
+       char buf1[64];
+       const char *mib_name;
        int i, len = 0;
+       bool mib_stats_empty = true;
 
        if (!ar8xxx_has_mib_counters(priv))
                return -EOPNOTSUPP;
@@ -1345,15 +1393,28 @@ ar8xxx_sw_get_port_mib(struct switch_dev *dev,
        ar8xxx_mib_fetch_port_stat(priv, port, false);
 
        len += snprintf(buf + len, sizeof(priv->buf) - len,
-                       "Port %d MIB counters\n",
-                       port);
+                       "MIB counters\n");
 
        mib_stats = &priv->mib_stats[port * chip->num_mibs];
-       for (i = 0; i < chip->num_mibs; i++)
+       for (i = 0; i < chip->num_mibs; i++) {
+               mib_name = chip->mib_decs[i].name;
+               mib_data = mib_stats[i];
                len += snprintf(buf + len, sizeof(priv->buf) - len,
-                               "%-12s: %llu\n",
-                               chip->mib_decs[i].name,
-                               mib_stats[i]);
+                               "%-12s: %llu\n", mib_name, mib_data);
+               if ((!strcmp(mib_name, "TxByte") ||
+                   !strcmp(mib_name, "RxGoodByte")) &&
+                   mib_data >= 1024) {
+                       ar8xxx_byte_to_str(buf1, sizeof(buf1), mib_data);
+                       --len; /* discard newline at the end of buf */
+                       len += snprintf(buf + len, sizeof(priv->buf) - len,
+                                       " (%s)\n", buf1);
+               }
+               if (mib_stats_empty && mib_data)
+                       mib_stats_empty = false;
+       }
+
+       if (mib_stats_empty)
+               len = snprintf(buf, sizeof(priv->buf), "No MIB data");
 
        val->value.s = buf;
        val->len = len;
@@ -1366,6 +1427,34 @@ unlock:
 }
 
 int
+ar8xxx_sw_set_arl_age_time(struct switch_dev *dev, const struct switch_attr *attr,
+                          struct switch_val *val)
+{
+       struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev);
+       int age_time = val->value.i;
+       u32 age_time_val;
+
+       if (age_time < 0)
+               return -EINVAL;
+
+       age_time_val = ar8xxx_age_time_val(age_time);
+       if (age_time_val == 0 || age_time_val > 0xffff)
+               return -EINVAL;
+
+       priv->arl_age_time = age_time;
+       return 0;
+}
+
+int
+ar8xxx_sw_get_arl_age_time(struct switch_dev *dev, const struct switch_attr *attr,
+                   struct switch_val *val)
+{
+       struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev);
+       val->value.i = priv->arl_age_time;
+       return 0;
+}
+
+int
 ar8xxx_sw_get_arl_table(struct switch_dev *dev,
                        const struct switch_attr *attr,
                        struct switch_val *val)
@@ -1436,6 +1525,40 @@ ar8xxx_sw_get_arl_table(struct switch_dev *dev,
        return 0;
 }
 
+int
+ar8xxx_sw_set_flush_arl_table(struct switch_dev *dev,
+                             const struct switch_attr *attr,
+                             struct switch_val *val)
+{
+       struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev);
+       int ret;
+
+       mutex_lock(&priv->reg_mutex);
+       ret = priv->chip->atu_flush(priv);
+       mutex_unlock(&priv->reg_mutex);
+
+       return ret;
+}
+
+int
+ar8xxx_sw_set_flush_port_arl_table(struct switch_dev *dev,
+                                  const struct switch_attr *attr,
+                                  struct switch_val *val)
+{
+       struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev);
+       int port, ret;
+
+       port = val->port_vlan;
+       if (port >= dev->ports)
+               return -EINVAL;
+
+       mutex_lock(&priv->reg_mutex);
+       ret = priv->chip->atu_flush_port(priv, port);
+       mutex_unlock(&priv->reg_mutex);
+
+       return ret;
+}
+
 static const struct switch_attr ar8xxx_sw_attr_globals[] = {
        {
                .type = SWITCH_TYPE_INT,
@@ -1490,9 +1613,15 @@ static const struct switch_attr ar8xxx_sw_attr_globals[] = {
                .set = NULL,
                .get = ar8xxx_sw_get_arl_table,
        },
+       {
+               .type = SWITCH_TYPE_NOVAL,
+               .name = "flush_arl_table",
+               .description = "Flush ARL table",
+               .set = ar8xxx_sw_set_flush_arl_table,
+       },
 };
 
-const struct switch_attr ar8xxx_sw_attr_port[2] = {
+const struct switch_attr ar8xxx_sw_attr_port[] = {
        {
                .type = SWITCH_TYPE_NOVAL,
                .name = "reset_mib",
@@ -1506,6 +1635,12 @@ const struct switch_attr ar8xxx_sw_attr_port[2] = {
                .set = NULL,
                .get = ar8xxx_sw_get_port_mib,
        },
+       {
+               .type = SWITCH_TYPE_NOVAL,
+               .name = "flush_arl_table",
+               .description = "Flush port's ARL table entries",
+               .set = ar8xxx_sw_set_flush_port_arl_table,
+       },
 };
 
 const struct switch_attr ar8xxx_sw_attr_vlan[1] = {
@@ -1546,6 +1681,7 @@ static const struct ar8xxx_chip ar8216_chip = {
 
        .reg_port_stats_start = 0x19000,
        .reg_port_stats_length = 0xa0,
+       .reg_arl_ctrl = AR8216_REG_ATU_CTRL,
 
        .name = "Atheros AR8216",
        .ports = AR8216_NUM_PORTS,
@@ -1575,6 +1711,7 @@ static const struct ar8xxx_chip ar8236_chip = {
 
        .reg_port_stats_start = 0x20000,
        .reg_port_stats_length = 0x100,
+       .reg_arl_ctrl = AR8216_REG_ATU_CTRL,
 
        .name = "Atheros AR8236",
        .ports = AR8216_NUM_PORTS,
@@ -1604,6 +1741,7 @@ static const struct ar8xxx_chip ar8316_chip = {
 
        .reg_port_stats_start = 0x20000,
        .reg_port_stats_length = 0x100,
+       .reg_arl_ctrl = AR8216_REG_ATU_CTRL,
 
        .name = "Atheros AR8316",
        .ports = AR8216_NUM_PORTS,
@@ -1882,13 +2020,13 @@ ar8xxx_check_link_states(struct ar8xxx_priv *priv)
 
                priv->link_up[i] = link_new;
                changed = true;
+               /* flush ARL entries for this port if it went down*/
+               if (!link_new)
+                       priv->chip->atu_flush_port(priv, i);
                dev_info(&priv->phy->dev, "Port %d is %s\n",
                         i, link_new ? "up" : "down");
        }
 
-       if (changed)
-               priv->chip->atu_flush(priv);
-
        mutex_unlock(&priv->reg_mutex);
 
        return changed;
@@ -1900,9 +2038,7 @@ ar8xxx_phy_read_status(struct phy_device *phydev)
        struct ar8xxx_priv *priv = phydev->priv;
        struct switch_port_link link;
 
-       /* check for link changes and flush ATU
-        * if a change was detected
-        */
+       /* check for switch port link changes */
        if (phydev->state == PHY_CHANGELINK)
                ar8xxx_check_link_states(priv);