ar8327: Add workarounds for AR8337 switch.
[openwrt/openwrt.git] / target / linux / generic / files / drivers / net / phy / ar8327.c
index b69c33b1233787da294aed817bc1b6a22a79faa7..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
 #include <linux/workqueue.h>
 #include <linux/of_device.h>
 #include <linux/leds.h>
+#include <linux/mdio.h>
 
 #include "ar8216.h"
 #include "ar8327.h"
 
 extern const struct ar8xxx_mib_desc ar8236_mibs[39];
-extern const struct switch_attr ar8xxx_sw_attr_port[2];
 extern const struct switch_attr ar8xxx_sw_attr_vlan[1];
 
 static u32
@@ -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);
@@ -651,7 +652,9 @@ ar8327_cleanup(struct ar8xxx_priv *priv)
 static void
 ar8327_init_globals(struct ar8xxx_priv *priv)
 {
+       struct ar8327_data *data = priv->chip_data;
        u32 t;
+       int i;
 
        /* enable CPU port and disable mirror port */
        t = AR8327_FWD_CTRL0_CPU_PORT_EN |
@@ -672,14 +675,42 @@ ar8327_init_globals(struct ar8xxx_priv *priv)
        ar8xxx_reg_set(priv, AR8327_REG_MODULE_EN,
                       AR8327_MODULE_EN_MIB);
 
-       /* Disable EEE on all ports due to stability issues */
-       t = ar8xxx_read(priv, AR8327_REG_EEE_CTRL);
-       t |= AR8327_EEE_CTRL_DISABLE_PHY(0) |
-            AR8327_EEE_CTRL_DISABLE_PHY(1) |
-            AR8327_EEE_CTRL_DISABLE_PHY(2) |
-            AR8327_EEE_CTRL_DISABLE_PHY(3) |
-            AR8327_EEE_CTRL_DISABLE_PHY(4);
-       ar8xxx_write(priv, AR8327_REG_EEE_CTRL, t);
+       /* 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
@@ -695,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);
@@ -713,7 +752,43 @@ ar8327_init_port(struct ar8xxx_priv *priv, int port)
 static u32
 ar8327_read_port_status(struct ar8xxx_priv *priv, int port)
 {
-       return ar8xxx_read(priv, AR8327_REG_PORT_STATUS(port));
+       u32 t;
+
+       t = ar8xxx_read(priv, AR8327_REG_PORT_STATUS(port));
+       /* map the flow control autoneg result bits to the flow control bits
+        * used in forced mode to allow ar8216_read_port_link detect
+        * flow control properly if autoneg is used
+        */
+       if (t & AR8216_PORT_STATUS_LINK_UP &&
+           t & AR8216_PORT_STATUS_LINK_AUTO) {
+               t &= ~(AR8216_PORT_STATUS_TXFLOW | AR8216_PORT_STATUS_RXFLOW);
+               if (t & AR8327_PORT_STATUS_TXFLOW_AUTO)
+                       t |= AR8216_PORT_STATUS_TXFLOW;
+               if (t & AR8327_PORT_STATUS_RXFLOW_AUTO)
+                       t |= AR8216_PORT_STATUS_RXFLOW;
+       }
+
+       return t;
+}
+
+static u32
+ar8327_read_port_eee_status(struct ar8xxx_priv *priv, int port)
+{
+       int phy;
+       u16 t;
+
+       if (port >= priv->dev.ports)
+               return 0;
+
+       if (port == 0 || port == 6)
+               return 0;
+
+       phy = port - 1;
+
+       /* EEE Ability Auto-negotiation Result */
+       t = ar8xxx_phy_mmd_read(priv, phy, 0x7, 0x8000);
+
+       return mmd_eee_adv_to_ethtool_adv_t(t);
 }
 
 static int
@@ -725,11 +800,69 @@ ar8327_atu_flush(struct ar8xxx_priv *priv)
                              AR8327_ATU_FUNC_BUSY, 0);
        if (!ret)
                ar8xxx_write(priv, AR8327_REG_ATU_FUNC,
-                           AR8327_ATU_FUNC_OP_FLUSH);
+                            AR8327_ATU_FUNC_OP_FLUSH |
+                            AR8327_ATU_FUNC_BUSY);
 
        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)
 {
@@ -865,13 +998,11 @@ ar8327_set_mirror_regs(struct ar8xxx_priv *priv)
                   AR8327_FWD_CTRL0_MIRROR_PORT,
                   (0xF << AR8327_FWD_CTRL0_MIRROR_PORT_S));
        for (port = 0; port < AR8327_NUM_PORTS; port++) {
-               ar8xxx_rmw(priv, AR8327_REG_PORT_LOOKUP(port),
-                          AR8327_PORT_LOOKUP_ING_MIRROR_EN,
-                          0);
+               ar8xxx_reg_clear(priv, AR8327_REG_PORT_LOOKUP(port),
+                          AR8327_PORT_LOOKUP_ING_MIRROR_EN);
 
-               ar8xxx_rmw(priv, AR8327_REG_PORT_HOL_CTRL1(port),
-                          AR8327_PORT_HOL_CTRL1_EG_MIRROR_EN,
-                          0);
+               ar8xxx_reg_clear(priv, AR8327_REG_PORT_HOL_CTRL1(port),
+                          AR8327_PORT_HOL_CTRL1_EG_MIRROR_EN);
        }
 
        /* now enable mirroring if necessary */
@@ -894,6 +1025,249 @@ ar8327_set_mirror_regs(struct ar8xxx_priv *priv)
                           AR8327_PORT_HOL_CTRL1_EG_MIRROR_EN);
 }
 
+static int
+ar8327_sw_set_eee(struct switch_dev *dev,
+                 const struct switch_attr *attr,
+                 struct switch_val *val)
+{
+       struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev);
+       struct ar8327_data *data = priv->chip_data;
+       int port = val->port_vlan;
+       int phy;
+
+       if (port >= dev->ports)
+               return -EINVAL;
+       if (port == 0 || port == 6)
+               return -EOPNOTSUPP;
+
+       phy = port - 1;
+
+       data->eee[phy] = !!(val->value.i);
+
+       return 0;
+}
+
+static int
+ar8327_sw_get_eee(struct switch_dev *dev,
+                 const struct switch_attr *attr,
+                 struct switch_val *val)
+{
+       struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev);
+       const struct ar8327_data *data = priv->chip_data;
+       int port = val->port_vlan;
+       int phy;
+
+       if (port >= dev->ports)
+               return -EINVAL;
+       if (port == 0 || port == 6)
+               return -EOPNOTSUPP;
+
+       phy = port - 1;
+
+       val->value.i = data->eee[phy];
+
+       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)
+{
+       struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev);
+       const struct ar8327_data *data = priv->chip_data;
+       int ret, i;
+
+       ret = ar8xxx_sw_hw_apply(dev);
+       if (ret)
+               return ret;
+
+       for (i=0; i < AR8XXX_NUM_PHYS; i++) {
+               if (data->eee[i])
+                       ar8xxx_reg_clear(priv, AR8327_REG_EEE_CTRL,
+                              AR8327_EEE_CTRL_DISABLE_PHY(i));
+               else
+                       ar8xxx_reg_set(priv, AR8327_REG_EEE_CTRL,
+                              AR8327_EEE_CTRL_DISABLE_PHY(i));
+       }
+
+       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,
@@ -941,6 +1315,80 @@ 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[] = {
+       {
+               .type = SWITCH_TYPE_NOVAL,
+               .name = "reset_mib",
+               .description = "Reset single port MIB counters",
+               .set = ar8xxx_sw_set_port_reset_mib,
+       },
+       {
+               .type = SWITCH_TYPE_STRING,
+               .name = "mib",
+               .description = "Get port's MIB counters",
+               .set = NULL,
+               .get = ar8xxx_sw_get_port_mib,
+       },
+       {
+               .type = SWITCH_TYPE_INT,
+               .name = "enable_eee",
+               .description = "Enable EEE PHY sleep mode",
+               .set = ar8327_sw_set_eee,
+               .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 = {
@@ -949,8 +1397,8 @@ static const struct switch_dev_ops ar8327_sw_ops = {
                .n_attr = ARRAY_SIZE(ar8327_sw_attr_globals),
        },
        .attr_port = {
-               .attr = ar8xxx_sw_attr_port,
-               .n_attr = ARRAY_SIZE(ar8xxx_sw_attr_port),
+               .attr = ar8327_sw_attr_port,
+               .n_attr = ARRAY_SIZE(ar8327_sw_attr_port),
        },
        .attr_vlan = {
                .attr = ar8xxx_sw_attr_vlan,
@@ -960,7 +1408,7 @@ static const struct switch_dev_ops ar8327_sw_ops = {
        .set_port_pvid = ar8xxx_sw_set_pvid,
        .get_vlan_ports = ar8327_sw_get_ports,
        .set_vlan_ports = ar8327_sw_set_ports,
-       .apply_config = ar8xxx_sw_hw_apply,
+       .apply_config = ar8327_sw_hw_apply,
        .reset_switch = ar8xxx_sw_reset_switch,
        .get_port_link = ar8xxx_sw_get_port_link,
 };
@@ -977,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,
@@ -984,11 +1433,14 @@ const struct ar8xxx_chip ar8327_chip = {
        .init_port = ar8327_init_port,
        .setup_port = ar8327_setup_port,
        .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,
@@ -1007,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,
@@ -1014,14 +1467,17 @@ const struct ar8xxx_chip ar8337_chip = {
        .init_port = ar8327_init_port,
        .setup_port = ar8327_setup_port,
        .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
 };
-