/*
* 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"
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);
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);
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;
}
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);
if (!priv->chip_data)
return -ENOMEM;
- if (priv->phy->dev.of_node)
- ret = ar8327_hw_config_of(priv, priv->phy->dev.of_node);
+ if (priv->phy->mdio.dev.of_node)
+ ret = ar8327_hw_config_of(priv, priv->phy->mdio.dev.of_node);
else
ret = ar8327_hw_config_pdata(priv,
- priv->phy->dev.platform_data);
+ priv->phy->mdio.dev.platform_data);
if (ret)
return ret;
/* 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
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);
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
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)
{
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)
{
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,
.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[] = {
.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 = {
.apply_config = ar8327_sw_hw_apply,
.reset_switch = ar8xxx_sw_reset_switch,
.get_port_link = ar8xxx_sw_get_port_link,
+/* The following op is disabled as it hogs the CPU and degrades performance.
+ An implementation has been attempted in 4d8a66d but reading MIB data is slow
+ on ar8xxx switches.
+
+ The high CPU load has been traced down to the ar8xxx_reg_wait() call in
+ ar8xxx_mib_op(), which has to usleep_range() till the MIB busy flag set by
+ the request to update the MIB counter is cleared. */
+#if 0
+ .get_port_stats = ar8xxx_sw_get_port_stats,
+#endif
};
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,
.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),
.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,
.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
};
-