/*
* Driver for the built-in ethernet switch of the Atheros AR7240 SoC
* Copyright (c) 2010 Gabor Juhos <juhosg@openwrt.org>
- * Copyright (c) 2010 Felix Fietkau <nbd@openwrt.org>
+ * Copyright (c) 2010 Felix Fietkau <nbd@nbd.name>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published
#define AR7240_FLOOD_MASK_BROAD_TO_CPU BIT(26)
#define AR7240_REG_GLOBAL_CTRL 0x30
-#define AR7240_GLOBAL_CTRL_MTU_M BITM(12)
+#define AR7240_GLOBAL_CTRL_MTU_M BITM(11)
+#define AR9340_GLOBAL_CTRL_MTU_M BITM(14)
#define AR7240_REG_VTU 0x0040
#define AR7240_VTU_OP BITM(3)
#define AR7240_REG_CPU_PORT 0x78
#define AR7240_MIRROR_PORT_S 4
+#define AR7240_MIRROR_PORT_M BITM(4)
#define AR7240_CPU_PORT_EN BIT(8)
#define AR7240_REG_MIB_FUNCTION0 0x80
#define AR7240_MIB_AT_HALF_EN BIT(16)
#define AR7240_MIB_BUSY BIT(17)
#define AR7240_MIB_FUNC_S 24
+#define AR7240_MIB_FUNC_M BITM(3)
#define AR7240_MIB_FUNC_NO_OP 0x0
#define AR7240_MIB_FUNC_FLUSH 0x1
#define AR7240_MIB_FUNC_CAPTURE 0x3
#define AR934X_REG_OPER_MODE1 0x08
#define AR934X_REG_OPER_MODE1_PHY4_MII_EN BIT(28)
+#define AR934X_REG_FLOOD_MASK 0x2c
+#define AR934X_FLOOD_MASK_MC_DP(_p) BIT(16 + (_p))
+#define AR934X_FLOOD_MASK_BC_DP(_p) BIT(25 + (_p))
+
+#define AR934X_REG_QM_CTRL 0x3c
+#define AR934X_QM_CTRL_ARP_EN BIT(15)
+
+#define AR934X_REG_AT_CTRL 0x5c
+#define AR934X_AT_CTRL_AGE_TIME BITS(0, 15)
+#define AR934X_AT_CTRL_AGE_EN BIT(17)
+#define AR934X_AT_CTRL_LEARN_CHANGE BIT(18)
+
+#define AR934X_MIB_ENABLE BIT(30)
+
#define AR934X_REG_PORT_BASE(_port) (0x100 + (_port) * 0x100)
#define AR934X_REG_PORT_VLAN1(_port) (AR934X_REG_PORT_BASE((_port)) + 0x08)
if ((t & mask) == val)
return 0;
- msleep(1);
+ usleep_range(1000, 2000);
}
return -ETIMEDOUT;
write_lock(&as->stats_lock);
/* Capture the hardware statistics for all ports */
- ar7240sw_reg_write(mii, AR7240_REG_MIB_FUNCTION0,
- (AR7240_MIB_FUNC_CAPTURE << AR7240_MIB_FUNC_S));
+ ar7240sw_reg_rmw(mii, AR7240_REG_MIB_FUNCTION0,
+ (AR7240_MIB_FUNC_M << AR7240_MIB_FUNC_S),
+ (AR7240_MIB_FUNC_CAPTURE << AR7240_MIB_FUNC_S));
/* Wait for the capturing to complete. */
ret = ar7240sw_reg_wait(mii, AR7240_REG_MIB_FUNCTION0,
/* Setup TAG priority mapping */
ar7240sw_reg_write(mii, AR7240_REG_TAG_PRIORITY, 0xfa50);
- /* Enable ARP frame acknowledge, aging, MAC replacing */
- ar7240sw_reg_write(mii, AR7240_REG_AT_CTRL,
- AR7240_AT_CTRL_RESERVED |
- 0x2b /* 5 min age time */ |
- AR7240_AT_CTRL_AGE_EN |
- AR7240_AT_CTRL_ARP_EN |
- AR7240_AT_CTRL_LEARN_CHANGE);
-
- /* Enable Broadcast frames transmitted to the CPU */
- ar7240sw_reg_set(mii, AR7240_REG_FLOOD_MASK,
- AR7240_FLOOD_MASK_BROAD_TO_CPU);
+ if (sw_is_ar934x(as)) {
+ /* Enable aging, MAC replacing */
+ ar7240sw_reg_write(mii, AR934X_REG_AT_CTRL,
+ 0x2b /* 5 min age time */ |
+ AR934X_AT_CTRL_AGE_EN |
+ AR934X_AT_CTRL_LEARN_CHANGE);
+ /* Enable ARP frame acknowledge */
+ ar7240sw_reg_set(mii, AR934X_REG_QM_CTRL,
+ AR934X_QM_CTRL_ARP_EN);
+ /* Enable Broadcast/Multicast frames transmitted to the CPU */
+ ar7240sw_reg_set(mii, AR934X_REG_FLOOD_MASK,
+ AR934X_FLOOD_MASK_BC_DP(0) |
+ AR934X_FLOOD_MASK_MC_DP(0));
+
+ /* setup MTU */
+ ar7240sw_reg_rmw(mii, AR7240_REG_GLOBAL_CTRL,
+ AR9340_GLOBAL_CTRL_MTU_M,
+ AR9340_GLOBAL_CTRL_MTU_M);
+
+ /* Enable MIB counters */
+ ar7240sw_reg_set(mii, AR7240_REG_MIB_FUNCTION0,
+ AR934X_MIB_ENABLE);
- /* setup MTU */
- ar7240sw_reg_rmw(mii, AR7240_REG_GLOBAL_CTRL, AR7240_GLOBAL_CTRL_MTU_M,
- 1536);
+ } else {
+ /* Enable ARP frame acknowledge, aging, MAC replacing */
+ ar7240sw_reg_write(mii, AR7240_REG_AT_CTRL,
+ AR7240_AT_CTRL_RESERVED |
+ 0x2b /* 5 min age time */ |
+ AR7240_AT_CTRL_AGE_EN |
+ AR7240_AT_CTRL_ARP_EN |
+ AR7240_AT_CTRL_LEARN_CHANGE);
+ /* Enable Broadcast frames transmitted to the CPU */
+ ar7240sw_reg_set(mii, AR7240_REG_FLOOD_MASK,
+ AR7240_FLOOD_MASK_BROAD_TO_CPU);
+
+ /* setup MTU */
+ ar7240sw_reg_rmw(mii, AR7240_REG_GLOBAL_CTRL,
+ AR7240_GLOBAL_CTRL_MTU_M,
+ AR7240_GLOBAL_CTRL_MTU_M);
+ }
/* setup Service TAG */
ar7240sw_reg_rmw(mii, AR7240_REG_SERVICE_TAG, AR7240_SERVICE_TAG_M, 0);
}
+/* inspired by phy_poll_reset in drivers/net/phy/phy_device.c */
+static int
+ar7240sw_phy_poll_reset(struct mii_bus *bus)
+{
+ const unsigned int sleep_msecs = 20;
+ int ret, elapsed, i;
+
+ for (elapsed = sleep_msecs; elapsed <= 600;
+ elapsed += sleep_msecs) {
+ msleep(sleep_msecs);
+ for (i = 0; i < AR7240_NUM_PHYS; i++) {
+ ret = ar7240sw_phy_read(bus, i, MII_BMCR);
+ if (ret < 0)
+ return ret;
+ if (ret & BMCR_RESET)
+ break;
+ if (i == AR7240_NUM_PHYS - 1) {
+ usleep_range(1000, 2000);
+ return 0;
+ }
+ }
+ }
+ return -ETIMEDOUT;
+}
+
static int ar7240sw_reset(struct ar7240sw *as)
{
struct mii_bus *mii = as->mii_bus;
ar7240sw_disable_port(as, i);
/* Wait for transmit queues to drain. */
- msleep(2);
+ usleep_range(2000, 3000);
/* Reset the switch. */
ar7240sw_reg_write(mii, AR7240_REG_MASK_CTRL,
ret = ar7240sw_reg_wait(mii, AR7240_REG_MASK_CTRL,
AR7240_MASK_CTRL_SOFT_RESET, 0, 1000);
+ /* setup PHYs */
+ for (i = 0; i < AR7240_NUM_PHYS; i++) {
+ ar7240sw_phy_write(mii, i, MII_ADVERTISE,
+ ADVERTISE_ALL | ADVERTISE_PAUSE_CAP |
+ ADVERTISE_PAUSE_ASYM);
+ ar7240sw_phy_write(mii, i, MII_BMCR,
+ BMCR_RESET | BMCR_ANENABLE);
+ }
+ ret = ar7240sw_phy_poll_reset(mii);
+ if (ret)
+ return ret;
+
ar7240sw_setup(as);
return ret;
}
portmask = ar7240sw_port_mask(as, AR7240_PORT_CPU);
}
+ /* preserve mirror rx&tx flags */
+ ctrl |= ar7240sw_reg_read(mii, AR7240_REG_PORT_CTRL(port)) &
+ (AR7240_PORT_CTRL_MIRROR_RX | AR7240_PORT_CTRL_MIRROR_TX);
+
/* allow the port to talk to all other ports, but exclude its
* own ID to prevent frames from being reflected back to the
* port that they came from */
struct mii_bus *mii = as->mii_bus;
u32 status;
- if (port > AR7240_NUM_PORTS)
+ if (port >= AR7240_NUM_PORTS)
return -EINVAL;
status = ar7240sw_reg_read(mii, AR7240_REG_PORT_STATUS(port));
{
struct ar7240sw *as = sw_to_ar7240(dev);
- if (port > AR7240_NUM_PORTS)
+ if (port >= AR7240_NUM_PORTS)
return -EINVAL;
ar7240sw_capture_stats(as);
return 0;
}
+static int
+ar7240_set_mirror_monitor_port(struct switch_dev *dev,
+ const struct switch_attr *attr,
+ struct switch_val *val)
+{
+ struct ar7240sw *as = sw_to_ar7240(dev);
+ struct mii_bus *mii = as->mii_bus;
+
+ int port = val->value.i;
+
+ if (port > 15)
+ return -EINVAL;
+
+ ar7240sw_reg_rmw(mii, AR7240_REG_CPU_PORT,
+ AR7240_MIRROR_PORT_M << AR7240_MIRROR_PORT_S,
+ port << AR7240_MIRROR_PORT_S);
+
+ return 0;
+}
+
+static int
+ar7240_get_mirror_monitor_port(struct switch_dev *dev,
+ const struct switch_attr *attr,
+ struct switch_val *val)
+{
+ struct ar7240sw *as = sw_to_ar7240(dev);
+ struct mii_bus *mii = as->mii_bus;
+
+ u32 ret;
+
+ ret = ar7240sw_reg_read(mii, AR7240_REG_CPU_PORT);
+ val->value.i = (ret >> AR7240_MIRROR_PORT_S) & AR7240_MIRROR_PORT_M;
+
+ return 0;
+}
+
+static int
+ar7240_set_mirror_rx(struct switch_dev *dev, const struct switch_attr *attr,
+ struct switch_val *val)
+{
+ struct ar7240sw *as = sw_to_ar7240(dev);
+ struct mii_bus *mii = as->mii_bus;
+
+ int port = val->port_vlan;
+
+ if (port >= dev->ports)
+ return -EINVAL;
+
+ if (val && val->value.i == 1)
+ ar7240sw_reg_set(mii, AR7240_REG_PORT_CTRL(port),
+ AR7240_PORT_CTRL_MIRROR_RX);
+ else
+ ar7240sw_reg_rmw(mii, AR7240_REG_PORT_CTRL(port),
+ AR7240_PORT_CTRL_MIRROR_RX, 0);
+
+ return 0;
+}
+
+static int
+ar7240_get_mirror_rx(struct switch_dev *dev, const struct switch_attr *attr,
+ struct switch_val *val)
+{
+ struct ar7240sw *as = sw_to_ar7240(dev);
+ struct mii_bus *mii = as->mii_bus;
+
+ u32 ctrl;
+
+ int port = val->port_vlan;
+
+ if (port >= dev->ports)
+ return -EINVAL;
+
+ ctrl = ar7240sw_reg_read(mii, AR7240_REG_PORT_CTRL(port));
+
+ if ((ctrl & AR7240_PORT_CTRL_MIRROR_RX) == AR7240_PORT_CTRL_MIRROR_RX)
+ val->value.i = 1;
+ else
+ val->value.i = 0;
+
+ return 0;
+}
+
+static int
+ar7240_set_mirror_tx(struct switch_dev *dev, const struct switch_attr *attr,
+ struct switch_val *val)
+{
+ struct ar7240sw *as = sw_to_ar7240(dev);
+ struct mii_bus *mii = as->mii_bus;
+
+ int port = val->port_vlan;
+
+ if (port >= dev->ports)
+ return -EINVAL;
+
+ if (val && val->value.i == 1)
+ ar7240sw_reg_set(mii, AR7240_REG_PORT_CTRL(port),
+ AR7240_PORT_CTRL_MIRROR_TX);
+ else
+ ar7240sw_reg_rmw(mii, AR7240_REG_PORT_CTRL(port),
+ AR7240_PORT_CTRL_MIRROR_TX, 0);
+
+ return 0;
+}
+
+static int
+ar7240_get_mirror_tx(struct switch_dev *dev, const struct switch_attr *attr,
+ struct switch_val *val)
+{
+ struct ar7240sw *as = sw_to_ar7240(dev);
+ struct mii_bus *mii = as->mii_bus;
+
+ u32 ctrl;
+
+ int port = val->port_vlan;
+
+ if (port >= dev->ports)
+ return -EINVAL;
+
+ ctrl = ar7240sw_reg_read(mii, AR7240_REG_PORT_CTRL(port));
+
+ if ((ctrl & AR7240_PORT_CTRL_MIRROR_TX) == AR7240_PORT_CTRL_MIRROR_TX)
+ val->value.i = 1;
+ else
+ val->value.i = 0;
+
+ return 0;
+}
+
static struct switch_attr ar7240_globals[] = {
{
.type = SWITCH_TYPE_INT,
.get = ar7240_get_vlan,
.max = 1
},
+ {
+ .type = SWITCH_TYPE_INT,
+ .name = "mirror_monitor_port",
+ .description = "Mirror monitor port",
+ .set = ar7240_set_mirror_monitor_port,
+ .get = ar7240_get_mirror_monitor_port,
+ .max = 15
+ },
};
static struct switch_attr ar7240_port[] = {
+ {
+ .type = SWITCH_TYPE_INT,
+ .name = "enable_mirror_rx",
+ .description = "Enable mirroring of RX packets",
+ .set = ar7240_set_mirror_rx,
+ .get = ar7240_get_mirror_rx,
+ .max = 1
+ },
+ {
+ .type = SWITCH_TYPE_INT,
+ .name = "enable_mirror_tx",
+ .description = "Enable mirroring of TX packets",
+ .set = ar7240_set_mirror_tx,
+ .get = ar7240_get_mirror_tx,
+ .max = 1
+ },
};
static struct switch_attr ar7240_vlan[] = {
if ((phy_id1 != AR7240_PHY_ID1 || phy_id2 != AR7240_PHY_ID2) &&
(phy_id1 != AR934X_PHY_ID1 || phy_id2 != AR934X_PHY_ID2)) {
pr_err("%s: unknown phy id '%04x:%04x'\n",
- ag->dev->name, phy_id1, phy_id2);
+ dev_name(&mii->dev), phy_id1, phy_id2);
return NULL;
}
AR934X_OPER_MODE0_PHY_MII_EN);
} else {
pr_err("%s: invalid PHY interface mode\n",
- ag->dev->name);
+ dev_name(&mii->dev));
goto err_free;
}
}
} else {
pr_err("%s: unsupported chip, ctrl=%08x\n",
- ag->dev->name, ctrl);
+ dev_name(&mii->dev), ctrl);
goto err_free;
}
if (register_switch(&as->swdev, ag->dev) < 0)
goto err_free;
- pr_info("%s: Found an %s\n", ag->dev->name, swdev->name);
+ pr_info("%s: Found an %s\n", dev_name(&mii->dev), swdev->name);
/* initialize defaults */
for (i = 0; i < AR7240_MAX_VLANS; i++)
static void link_function(struct work_struct *work) {
struct ag71xx *ag = container_of(work, struct ag71xx, link_work.work);
+ struct ar7240sw *as = ag->phy_priv;
unsigned long flags;
+ u8 mask;
int i;
int status = 0;
- for (i = 0; i < 4; i++) {
- int link = ar7240sw_phy_read(ag->mii_bus, i, MII_BMSR);
- if(link & BMSR_LSTATUS) {
+ mask = ~as->swdata->phy_poll_mask;
+ for (i = 0; i < AR7240_NUM_PHYS; i++) {
+ int link;
+
+ if (!(mask & BIT(i)))
+ continue;
+
+ link = ar7240sw_phy_read(ag->mii_bus, i, MII_BMSR);
+ if (link & BMSR_LSTATUS) {
status = 1;
break;
}
}
spin_lock_irqsave(&ag->lock, flags);
- if(status != ag->link) {
+ if (status != ag->link) {
ag->link = status;
ag71xx_link_adjust(ag);
}
cancel_delayed_work_sync(&ag->link_work);
}
-int __devinit ag71xx_ar7240_init(struct ag71xx *ag)
+int ag71xx_ar7240_init(struct ag71xx *ag)
{
struct ar7240sw *as;