ar8327: Add workarounds for AR8337 switch.
[openwrt/openwrt.git] / target / linux / generic / files / drivers / net / phy / ar8327.c
index 1802b9ead7e8e16bfb37e0d24491f9ea5ceed7e4..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
@@ -124,9 +124,6 @@ ar8327_get_pad_cfg(struct ar8327_pad_cfg *cfg)
                break;
        }
 
-       if (cfg->mac06_exchange_en)
-               t |= AR8337_PAD_MAC06_EXCHANGE_EN;
-
        return t;
 }
 
@@ -142,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);
@@ -229,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);
@@ -314,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;
 }
@@ -511,8 +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) && !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);
 }
@@ -783,6 +824,45 @@ ar8327_atu_flush_port(struct ar8xxx_priv *priv, int port)
        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)
 {
@@ -1084,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,
@@ -1131,6 +1315,13 @@ 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",
@@ -1144,6 +1335,22 @@ static const struct switch_attr ar8327_sw_attr_globals[] = {
                .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[] = {
@@ -1174,6 +1381,14 @@ static const struct switch_attr ar8327_sw_attr_port[] = {
                .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 = {
@@ -1210,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,
@@ -1222,7 +1438,6 @@ const struct ar8xxx_chip ar8327_chip = {
        .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,
@@ -1244,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,
@@ -1265,4 +1481,3 @@ const struct ar8xxx_chip ar8337_chip = {
        .mib_decs = ar8236_mibs,
        .mib_func = AR8327_REG_MIB_FUNC
 };
-