ar8327: Add workarounds for AR8337 switch.
[openwrt/openwrt.git] / target / linux / generic / files / drivers / net / phy / ar8327.c
index fd5bcdc19b635213fd345e7a6b105e47942122e1..925730a8393d081c0ca3f807e5bed486edc95de4 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * ar8327.c: AR8216 switch driver
  *
- * Copyright (C) 2009 Felix Fietkau <nbd@openwrt.org>
+ * Copyright (C) 2009 Felix Fietkau <nbd@nbd.name>
  * Copyright (C) 2011-2012 Gabor Juhos <juhosg@openwrt.org>
  *
  * This program is free software; you can redistribute it and/or
@@ -139,13 +139,10 @@ ar8327_phy_fixup(struct ar8xxx_priv *priv, int phy)
                break;
 
        case 2:
-               ar8xxx_phy_mmd_write(priv, phy, 0x7, 0x3c);
-               ar8xxx_phy_mmd_write(priv, phy, 0x4007, 0x0);
+               ar8xxx_phy_mmd_write(priv, phy, 0x7, 0x3c, 0x0);
                /* fallthrough */
        case 4:
-               ar8xxx_phy_mmd_write(priv, phy, 0x3, 0x800d);
-               ar8xxx_phy_mmd_write(priv, phy, 0x4003, 0x803f);
-
+               ar8xxx_phy_mmd_write(priv, phy, 0x3, 0x800d, 0x803f);
                ar8xxx_phy_dbg_write(priv, phy, 0x3d, 0x6860);
                ar8xxx_phy_dbg_write(priv, phy, 0x5, 0x2c46);
                ar8xxx_phy_dbg_write(priv, phy, 0x3c, 0x6000);
@@ -226,9 +223,7 @@ ar8327_led_work_func(struct work_struct *work)
 
        aled = container_of(work, struct ar8327_led, led_work);
 
-       spin_lock(&aled->lock);
        pattern = aled->pattern;
-       spin_unlock(&aled->lock);
 
        ar8327_set_led_pattern(aled->sw_priv, aled->led_num,
                               pattern);
@@ -311,9 +306,7 @@ ar8327_led_enable_hw_mode_show(struct device *dev,
        struct ar8327_led *aled = led_cdev_to_ar8327_led(led_cdev);
        ssize_t ret = 0;
 
-       spin_lock(&aled->lock);
-       ret += sprintf(buf, "%d\n", aled->enable_hw_mode);
-       spin_unlock(&aled->lock);
+       ret += scnprintf(buf, PAGE_SIZE, "%d\n", aled->enable_hw_mode);
 
        return ret;
 }
@@ -508,11 +501,19 @@ ar8327_hw_config_pdata(struct ar8xxx_priv *priv,
        data->port6_status = ar8327_get_port_init_status(&pdata->port6_cfg);
 
        t = ar8327_get_pad_cfg(pdata->pad0_cfg);
-       if (chip_is_ar8337(priv))
-               t |= AR8337_PAD_MAC06_EXCHANGE_EN;
-
+       if (chip_is_ar8337(priv) && !pdata->pad0_cfg->mac06_exchange_dis)
+           t |= AR8337_PAD_MAC06_EXCHANGE_EN;
        ar8xxx_write(priv, AR8327_REG_PAD0_MODE, t);
+
        t = ar8327_get_pad_cfg(pdata->pad5_cfg);
+       if (chip_is_ar8337(priv)) {
+               /*
+                * Workaround: RGMII RX delay setting needs to be
+                * always specified for AR8337 to avoid port 5
+                * RX hang on high traffic / flood conditions
+                */
+               t |= AR8327_PAD_RGMII_RXCLK_DELAY_EN;
+       }
        ar8xxx_write(priv, AR8327_REG_PAD5_MODE, t);
        t = ar8327_get_pad_cfg(pdata->pad6_cfg);
        ar8xxx_write(priv, AR8327_REG_PAD6_MODE, t);
@@ -677,6 +678,39 @@ ar8327_init_globals(struct ar8xxx_priv *priv)
        /* Disable EEE on all phy's due to stability issues */
        for (i = 0; i < AR8XXX_NUM_PHYS; i++)
                data->eee[i] = false;
+
+       if (chip_is_ar8337(priv)) {
+               /* Update HOL registers with values suggested by QCA switch team */
+               for (i = 0; i < AR8327_NUM_PORTS; i++) {
+                       if (i == AR8216_PORT_CPU || i == 5 || i == 6) {
+                               t = 0x3 << AR8327_PORT_HOL_CTRL0_EG_PRI0_BUF_S;
+                               t |= 0x4 << AR8327_PORT_HOL_CTRL0_EG_PRI1_BUF_S;
+                               t |= 0x4 << AR8327_PORT_HOL_CTRL0_EG_PRI2_BUF_S;
+                               t |= 0x4 << AR8327_PORT_HOL_CTRL0_EG_PRI3_BUF_S;
+                               t |= 0x6 << AR8327_PORT_HOL_CTRL0_EG_PRI4_BUF_S;
+                               t |= 0x8 << AR8327_PORT_HOL_CTRL0_EG_PRI5_BUF_S;
+                               t |= 0x1e << AR8327_PORT_HOL_CTRL0_EG_PORT_BUF_S;
+                       } else {
+                               t = 0x3 << AR8327_PORT_HOL_CTRL0_EG_PRI0_BUF_S;
+                               t |= 0x4 << AR8327_PORT_HOL_CTRL0_EG_PRI1_BUF_S;
+                               t |= 0x6 << AR8327_PORT_HOL_CTRL0_EG_PRI2_BUF_S;
+                               t |= 0x8 << AR8327_PORT_HOL_CTRL0_EG_PRI3_BUF_S;
+                               t |= 0x19 << AR8327_PORT_HOL_CTRL0_EG_PORT_BUF_S;
+                       }
+                       ar8xxx_write(priv, AR8327_REG_PORT_HOL_CTRL0(i), t);
+
+                       t = 0x6 << AR8327_PORT_HOL_CTRL1_ING_BUF_S;
+                       t |= AR8327_PORT_HOL_CTRL1_EG_PRI_BUF_EN;
+                       t |= AR8327_PORT_HOL_CTRL1_EG_PORT_BUF_EN;
+                       t |= AR8327_PORT_HOL_CTRL1_WRED_EN;
+                       ar8xxx_rmw(priv, AR8327_REG_PORT_HOL_CTRL1(i),
+                                  AR8327_PORT_HOL_CTRL1_ING_BUF |
+                                  AR8327_PORT_HOL_CTRL1_EG_PRI_BUF_EN |
+                                  AR8327_PORT_HOL_CTRL1_EG_PORT_BUF_EN |
+                                  AR8327_PORT_HOL_CTRL1_WRED_EN,
+                                  t);
+               }
+       }
 }
 
 static void
@@ -692,12 +726,20 @@ ar8327_init_port(struct ar8xxx_priv *priv, int port)
        else
                t = AR8216_PORT_STATUS_LINK_AUTO;
 
-       ar8xxx_write(priv, AR8327_REG_PORT_STATUS(port), t);
+       if (port != AR8216_PORT_CPU && port != 6) {
+               /*hw limitation:if configure mac when there is traffic,
+               port MAC may work abnormal. Need disable lan&wan mac at fisrt*/
+               ar8xxx_write(priv, AR8327_REG_PORT_STATUS(port), 0);
+               msleep(100);
+               t |= AR8216_PORT_STATUS_FLOW_CONTROL;
+               ar8xxx_write(priv, AR8327_REG_PORT_STATUS(port), t);
+       } else {
+               ar8xxx_write(priv, AR8327_REG_PORT_STATUS(port), t);
+       }
+
        ar8xxx_write(priv, AR8327_REG_PORT_HEADER(port), 0);
 
-       t = 1 << AR8327_PORT_VLAN0_DEF_SVID_S;
-       t |= 1 << AR8327_PORT_VLAN0_DEF_CVID_S;
-       ar8xxx_write(priv, AR8327_REG_PORT_VLAN0(port), t);
+       ar8xxx_write(priv, AR8327_REG_PORT_VLAN0(port), 0);
 
        t = AR8327_PORT_VLAN1_OUT_MODE_UNTOUCH << AR8327_PORT_VLAN1_OUT_MODE_S;
        ar8xxx_write(priv, AR8327_REG_PORT_VLAN1(port), t);
@@ -744,8 +786,7 @@ ar8327_read_port_eee_status(struct ar8xxx_priv *priv, int port)
        phy = port - 1;
 
        /* EEE Ability Auto-negotiation Result */
-       ar8xxx_phy_mmd_write(priv, phy, 0x7, 0x8000);
-       t = ar8xxx_phy_mmd_read(priv, phy, 0x4007);
+       t = ar8xxx_phy_mmd_read(priv, phy, 0x7, 0x8000);
 
        return mmd_eee_adv_to_ethtool_adv_t(t);
 }
@@ -765,6 +806,63 @@ ar8327_atu_flush(struct ar8xxx_priv *priv)
        return ret;
 }
 
+static int
+ar8327_atu_flush_port(struct ar8xxx_priv *priv, int port)
+{
+       u32 t;
+       int ret;
+
+       ret = ar8216_wait_bit(priv, AR8327_REG_ATU_FUNC,
+                             AR8327_ATU_FUNC_BUSY, 0);
+       if (!ret) {
+               t = (port << AR8327_ATU_PORT_NUM_S);
+               t |= AR8327_ATU_FUNC_OP_FLUSH_PORT;
+               t |= AR8327_ATU_FUNC_BUSY;
+               ar8xxx_write(priv, AR8327_REG_ATU_FUNC, t);
+       }
+
+       return ret;
+}
+
+static int
+ar8327_get_port_igmp(struct ar8xxx_priv *priv, int port)
+{
+       u32 fwd_ctrl, frame_ack;
+
+       fwd_ctrl = (BIT(port) << AR8327_FWD_CTRL1_IGMP_S);
+       frame_ack = ((AR8327_FRAME_ACK_CTRL_IGMP_MLD |
+                     AR8327_FRAME_ACK_CTRL_IGMP_JOIN |
+                     AR8327_FRAME_ACK_CTRL_IGMP_LEAVE) <<
+                    AR8327_FRAME_ACK_CTRL_S(port));
+
+       return (ar8xxx_read(priv, AR8327_REG_FWD_CTRL1) &
+                       fwd_ctrl) == fwd_ctrl &&
+               (ar8xxx_read(priv, AR8327_REG_FRAME_ACK_CTRL(port)) &
+                       frame_ack) == frame_ack;
+}
+
+static void
+ar8327_set_port_igmp(struct ar8xxx_priv *priv, int port, int enable)
+{
+       int reg_frame_ack = AR8327_REG_FRAME_ACK_CTRL(port);
+       u32 val_frame_ack = (AR8327_FRAME_ACK_CTRL_IGMP_MLD |
+                         AR8327_FRAME_ACK_CTRL_IGMP_JOIN |
+                         AR8327_FRAME_ACK_CTRL_IGMP_LEAVE) <<
+                        AR8327_FRAME_ACK_CTRL_S(port);
+
+       if (enable) {
+               ar8xxx_rmw(priv, AR8327_REG_FWD_CTRL1,
+                          BIT(port) << AR8327_FWD_CTRL1_MC_FLOOD_S,
+                          BIT(port) << AR8327_FWD_CTRL1_IGMP_S);
+               ar8xxx_reg_set(priv, reg_frame_ack, val_frame_ack);
+       } else {
+               ar8xxx_rmw(priv, AR8327_REG_FWD_CTRL1,
+                          BIT(port) << AR8327_FWD_CTRL1_IGMP_S,
+                          BIT(port) << AR8327_FWD_CTRL1_MC_FLOOD_S);
+               ar8xxx_reg_clear(priv, reg_frame_ack, val_frame_ack);
+       }
+}
+
 static void
 ar8327_vtu_op(struct ar8xxx_priv *priv, u32 op, u32 val)
 {
@@ -971,6 +1069,78 @@ ar8327_sw_get_eee(struct switch_dev *dev,
        return 0;
 }
 
+static void
+ar8327_wait_atu_ready(struct ar8xxx_priv *priv, u16 r2, u16 r1)
+{
+       int timeout = 20;
+
+       while (ar8xxx_mii_read32(priv, r2, r1) & AR8327_ATU_FUNC_BUSY && --timeout)
+                udelay(10);
+
+       if (!timeout)
+               pr_err("ar8327: timeout waiting for atu to become ready\n");
+}
+
+static void ar8327_get_arl_entry(struct ar8xxx_priv *priv,
+                                struct arl_entry *a, u32 *status, enum arl_op op)
+{
+       struct mii_bus *bus = priv->mii_bus;
+       u16 r2, page;
+       u16 r1_data0, r1_data1, r1_data2, r1_func;
+       u32 t, val0, val1, val2;
+       int i;
+
+       split_addr(AR8327_REG_ATU_DATA0, &r1_data0, &r2, &page);
+       r2 |= 0x10;
+
+       r1_data1 = (AR8327_REG_ATU_DATA1 >> 1) & 0x1e;
+       r1_data2 = (AR8327_REG_ATU_DATA2 >> 1) & 0x1e;
+       r1_func  = (AR8327_REG_ATU_FUNC >> 1) & 0x1e;
+
+       switch (op) {
+       case AR8XXX_ARL_INITIALIZE:
+               /* all ATU registers are on the same page
+               * therefore set page only once
+               */
+               bus->write(bus, 0x18, 0, page);
+               wait_for_page_switch();
+
+               ar8327_wait_atu_ready(priv, r2, r1_func);
+
+               ar8xxx_mii_write32(priv, r2, r1_data0, 0);
+               ar8xxx_mii_write32(priv, r2, r1_data1, 0);
+               ar8xxx_mii_write32(priv, r2, r1_data2, 0);
+               break;
+       case AR8XXX_ARL_GET_NEXT:
+               ar8xxx_mii_write32(priv, r2, r1_func,
+                                  AR8327_ATU_FUNC_OP_GET_NEXT |
+                                  AR8327_ATU_FUNC_BUSY);
+               ar8327_wait_atu_ready(priv, r2, r1_func);
+
+               val0 = ar8xxx_mii_read32(priv, r2, r1_data0);
+               val1 = ar8xxx_mii_read32(priv, r2, r1_data1);
+               val2 = ar8xxx_mii_read32(priv, r2, r1_data2);
+
+               *status = val2 & AR8327_ATU_STATUS;
+               if (!*status)
+                       break;
+
+               i = 0;
+               t = AR8327_ATU_PORT0;
+               while (!(val1 & t) && ++i < AR8327_NUM_PORTS)
+                       t <<= 1;
+
+               a->port = i;
+               a->mac[0] = (val0 & AR8327_ATU_ADDR0) >> AR8327_ATU_ADDR0_S;
+               a->mac[1] = (val0 & AR8327_ATU_ADDR1) >> AR8327_ATU_ADDR1_S;
+               a->mac[2] = (val0 & AR8327_ATU_ADDR2) >> AR8327_ATU_ADDR2_S;
+               a->mac[3] = (val0 & AR8327_ATU_ADDR3) >> AR8327_ATU_ADDR3_S;
+               a->mac[4] = (val1 & AR8327_ATU_ADDR4) >> AR8327_ATU_ADDR4_S;
+               a->mac[5] = (val1 & AR8327_ATU_ADDR5) >> AR8327_ATU_ADDR5_S;
+               break;
+       }
+}
+
 static int
 ar8327_sw_hw_apply(struct switch_dev *dev)
 {
@@ -994,6 +1164,110 @@ ar8327_sw_hw_apply(struct switch_dev *dev)
        return 0;
 }
 
+int
+ar8327_sw_get_port_igmp_snooping(struct switch_dev *dev,
+                                const struct switch_attr *attr,
+                                struct switch_val *val)
+{
+       struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev);
+       int port = val->port_vlan;
+
+       if (port >= dev->ports)
+               return -EINVAL;
+
+       mutex_lock(&priv->reg_mutex);
+       val->value.i = ar8327_get_port_igmp(priv, port);
+       mutex_unlock(&priv->reg_mutex);
+
+       return 0;
+}
+
+int
+ar8327_sw_set_port_igmp_snooping(struct switch_dev *dev,
+                                const struct switch_attr *attr,
+                                struct switch_val *val)
+{
+       struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev);
+       int port = val->port_vlan;
+
+       if (port >= dev->ports)
+               return -EINVAL;
+
+       mutex_lock(&priv->reg_mutex);
+       ar8327_set_port_igmp(priv, port, val->value.i);
+       mutex_unlock(&priv->reg_mutex);
+
+       return 0;
+}
+
+int
+ar8327_sw_get_igmp_snooping(struct switch_dev *dev,
+                           const struct switch_attr *attr,
+                           struct switch_val *val)
+{
+       int port;
+
+       for (port = 0; port < dev->ports; port++) {
+               val->port_vlan = port;
+               if (ar8327_sw_get_port_igmp_snooping(dev, attr, val) ||
+                   !val->value.i)
+                       break;
+       }
+
+       return 0;
+}
+
+int
+ar8327_sw_set_igmp_snooping(struct switch_dev *dev,
+                           const struct switch_attr *attr,
+                           struct switch_val *val)
+{
+       int port;
+
+       for (port = 0; port < dev->ports; port++) {
+               val->port_vlan = port;
+               if (ar8327_sw_set_port_igmp_snooping(dev, attr, val))
+                       break;
+       }
+
+       return 0;
+}
+
+int
+ar8327_sw_get_igmp_v3(struct switch_dev *dev,
+                     const struct switch_attr *attr,
+                     struct switch_val *val)
+{
+       struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev);
+       u32 val_reg;
+
+       mutex_lock(&priv->reg_mutex);
+       val_reg = ar8xxx_read(priv, AR8327_REG_FRAME_ACK_CTRL1);
+       val->value.i = ((val_reg & AR8327_FRAME_ACK_CTRL_IGMP_V3_EN) != 0);
+       mutex_unlock(&priv->reg_mutex);
+
+       return 0;
+}
+
+int
+ar8327_sw_set_igmp_v3(struct switch_dev *dev,
+                     const struct switch_attr *attr,
+                     struct switch_val *val)
+{
+       struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev);
+
+       mutex_lock(&priv->reg_mutex);
+       if (val->value.i)
+               ar8xxx_reg_set(priv, AR8327_REG_FRAME_ACK_CTRL1,
+                              AR8327_FRAME_ACK_CTRL_IGMP_V3_EN);
+       else
+               ar8xxx_reg_clear(priv, AR8327_REG_FRAME_ACK_CTRL1,
+                                AR8327_FRAME_ACK_CTRL_IGMP_V3_EN);
+       mutex_unlock(&priv->reg_mutex);
+
+       return 0;
+}
+
 static const struct switch_attr ar8327_sw_attr_globals[] = {
        {
                .type = SWITCH_TYPE_INT,
@@ -1041,6 +1315,42 @@ static const struct switch_attr ar8327_sw_attr_globals[] = {
                .get = ar8xxx_sw_get_mirror_source_port,
                .max = AR8327_NUM_PORTS - 1
        },
+       {
+               .type = SWITCH_TYPE_INT,
+               .name = "arl_age_time",
+               .description = "ARL age time (secs)",
+               .set = ar8xxx_sw_set_arl_age_time,
+               .get = ar8xxx_sw_get_arl_age_time,
+       },
+       {
+               .type = SWITCH_TYPE_STRING,
+               .name = "arl_table",
+               .description = "Get ARL table",
+               .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,
+       },
+       {
+               .type = SWITCH_TYPE_INT,
+               .name = "igmp_snooping",
+               .description = "Enable IGMP Snooping",
+               .set = ar8327_sw_set_igmp_snooping,
+               .get = ar8327_sw_get_igmp_snooping,
+               .max = 1
+       },
+       {
+               .type = SWITCH_TYPE_INT,
+               .name = "igmp_v3",
+               .description = "Enable IGMPv3 support",
+               .set = ar8327_sw_set_igmp_v3,
+               .get = ar8327_sw_get_igmp_v3,
+               .max = 1
+       },
 };
 
 static const struct switch_attr ar8327_sw_attr_port[] = {
@@ -1065,6 +1375,20 @@ static const struct switch_attr ar8327_sw_attr_port[] = {
                .get = ar8327_sw_get_eee,
                .max = 1,
        },
+       {
+               .type = SWITCH_TYPE_NOVAL,
+               .name = "flush_arl_table",
+               .description = "Flush port's ARL table entries",
+               .set = ar8xxx_sw_set_flush_port_arl_table,
+       },
+       {
+               .type = SWITCH_TYPE_INT,
+               .name = "igmp_snooping",
+               .description = "Enable port's IGMP Snooping",
+               .set = ar8327_sw_set_port_igmp_snooping,
+               .get = ar8327_sw_get_port_igmp_snooping,
+               .max = 1
+       },
 };
 
 static const struct switch_dev_ops ar8327_sw_ops = {
@@ -1101,6 +1425,7 @@ const struct ar8xxx_chip ar8327_chip = {
 
        .reg_port_stats_start = 0x1000,
        .reg_port_stats_length = 0x100,
+       .reg_arl_ctrl = AR8327_REG_ARL_CTRL,
 
        .hw_init = ar8327_hw_init,
        .cleanup = ar8327_cleanup,
@@ -1110,10 +1435,11 @@ const struct ar8xxx_chip ar8327_chip = {
        .read_port_status = ar8327_read_port_status,
        .read_port_eee_status = ar8327_read_port_eee_status,
        .atu_flush = ar8327_atu_flush,
+       .atu_flush_port = ar8327_atu_flush_port,
        .vtu_flush = ar8327_vtu_flush,
        .vtu_load_vlan = ar8327_vtu_load_vlan,
-       .phy_fixup = ar8327_phy_fixup,
        .set_mirror_regs = ar8327_set_mirror_regs,
+       .get_arl_entry = ar8327_get_arl_entry,
        .sw_hw_apply = ar8327_sw_hw_apply,
 
        .num_mibs = ARRAY_SIZE(ar8236_mibs),
@@ -1133,6 +1459,7 @@ const struct ar8xxx_chip ar8337_chip = {
 
        .reg_port_stats_start = 0x1000,
        .reg_port_stats_length = 0x100,
+       .reg_arl_ctrl = AR8327_REG_ARL_CTRL,
 
        .hw_init = ar8327_hw_init,
        .cleanup = ar8327_cleanup,
@@ -1142,14 +1469,15 @@ const struct ar8xxx_chip ar8337_chip = {
        .read_port_status = ar8327_read_port_status,
        .read_port_eee_status = ar8327_read_port_eee_status,
        .atu_flush = ar8327_atu_flush,
+       .atu_flush_port = ar8327_atu_flush_port,
        .vtu_flush = ar8327_vtu_flush,
        .vtu_load_vlan = ar8327_vtu_load_vlan,
        .phy_fixup = ar8327_phy_fixup,
        .set_mirror_regs = ar8327_set_mirror_regs,
+       .get_arl_entry = ar8327_get_arl_entry,
        .sw_hw_apply = ar8327_sw_hw_apply,
 
        .num_mibs = ARRAY_SIZE(ar8236_mibs),
        .mib_decs = ar8236_mibs,
        .mib_func = AR8327_REG_MIB_FUNC
 };
-