/branches/18.06.1/target/linux/generic/files/drivers/net/phy/ar8216.c |
@@ -0,0 +1,2325 @@ |
/* |
* ar8216.c: AR8216 switch driver |
* |
* 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 |
* modify it under the terms of the GNU General Public License |
* as published by the Free Software Foundation; either version 2 |
* of the License, or (at your option) any later version. |
* |
* This program is distributed in the hope that it will be useful, |
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
* GNU General Public License for more details. |
*/ |
|
#include <linux/if.h> |
#include <linux/module.h> |
#include <linux/init.h> |
#include <linux/list.h> |
#include <linux/if_ether.h> |
#include <linux/skbuff.h> |
#include <linux/netdevice.h> |
#include <linux/netlink.h> |
#include <linux/bitops.h> |
#include <net/genetlink.h> |
#include <linux/switch.h> |
#include <linux/delay.h> |
#include <linux/phy.h> |
#include <linux/netdevice.h> |
#include <linux/etherdevice.h> |
#include <linux/lockdep.h> |
#include <linux/ar8216_platform.h> |
#include <linux/workqueue.h> |
#include <linux/version.h> |
|
#include "ar8216.h" |
|
extern const struct ar8xxx_chip ar8327_chip; |
extern const struct ar8xxx_chip ar8337_chip; |
|
#define AR8XXX_MIB_WORK_DELAY 2000 /* msecs */ |
|
#define MIB_DESC(_s , _o, _n) \ |
{ \ |
.size = (_s), \ |
.offset = (_o), \ |
.name = (_n), \ |
} |
|
static const struct ar8xxx_mib_desc ar8216_mibs[] = { |
MIB_DESC(1, AR8216_STATS_RXBROAD, "RxBroad"), |
MIB_DESC(1, AR8216_STATS_RXPAUSE, "RxPause"), |
MIB_DESC(1, AR8216_STATS_RXMULTI, "RxMulti"), |
MIB_DESC(1, AR8216_STATS_RXFCSERR, "RxFcsErr"), |
MIB_DESC(1, AR8216_STATS_RXALIGNERR, "RxAlignErr"), |
MIB_DESC(1, AR8216_STATS_RXRUNT, "RxRunt"), |
MIB_DESC(1, AR8216_STATS_RXFRAGMENT, "RxFragment"), |
MIB_DESC(1, AR8216_STATS_RX64BYTE, "Rx64Byte"), |
MIB_DESC(1, AR8216_STATS_RX128BYTE, "Rx128Byte"), |
MIB_DESC(1, AR8216_STATS_RX256BYTE, "Rx256Byte"), |
MIB_DESC(1, AR8216_STATS_RX512BYTE, "Rx512Byte"), |
MIB_DESC(1, AR8216_STATS_RX1024BYTE, "Rx1024Byte"), |
MIB_DESC(1, AR8216_STATS_RXMAXBYTE, "RxMaxByte"), |
MIB_DESC(1, AR8216_STATS_RXTOOLONG, "RxTooLong"), |
MIB_DESC(2, AR8216_STATS_RXGOODBYTE, "RxGoodByte"), |
MIB_DESC(2, AR8216_STATS_RXBADBYTE, "RxBadByte"), |
MIB_DESC(1, AR8216_STATS_RXOVERFLOW, "RxOverFlow"), |
MIB_DESC(1, AR8216_STATS_FILTERED, "Filtered"), |
MIB_DESC(1, AR8216_STATS_TXBROAD, "TxBroad"), |
MIB_DESC(1, AR8216_STATS_TXPAUSE, "TxPause"), |
MIB_DESC(1, AR8216_STATS_TXMULTI, "TxMulti"), |
MIB_DESC(1, AR8216_STATS_TXUNDERRUN, "TxUnderRun"), |
MIB_DESC(1, AR8216_STATS_TX64BYTE, "Tx64Byte"), |
MIB_DESC(1, AR8216_STATS_TX128BYTE, "Tx128Byte"), |
MIB_DESC(1, AR8216_STATS_TX256BYTE, "Tx256Byte"), |
MIB_DESC(1, AR8216_STATS_TX512BYTE, "Tx512Byte"), |
MIB_DESC(1, AR8216_STATS_TX1024BYTE, "Tx1024Byte"), |
MIB_DESC(1, AR8216_STATS_TXMAXBYTE, "TxMaxByte"), |
MIB_DESC(1, AR8216_STATS_TXOVERSIZE, "TxOverSize"), |
MIB_DESC(2, AR8216_STATS_TXBYTE, "TxByte"), |
MIB_DESC(1, AR8216_STATS_TXCOLLISION, "TxCollision"), |
MIB_DESC(1, AR8216_STATS_TXABORTCOL, "TxAbortCol"), |
MIB_DESC(1, AR8216_STATS_TXMULTICOL, "TxMultiCol"), |
MIB_DESC(1, AR8216_STATS_TXSINGLECOL, "TxSingleCol"), |
MIB_DESC(1, AR8216_STATS_TXEXCDEFER, "TxExcDefer"), |
MIB_DESC(1, AR8216_STATS_TXDEFER, "TxDefer"), |
MIB_DESC(1, AR8216_STATS_TXLATECOL, "TxLateCol"), |
}; |
|
const struct ar8xxx_mib_desc ar8236_mibs[39] = { |
MIB_DESC(1, AR8236_STATS_RXBROAD, "RxBroad"), |
MIB_DESC(1, AR8236_STATS_RXPAUSE, "RxPause"), |
MIB_DESC(1, AR8236_STATS_RXMULTI, "RxMulti"), |
MIB_DESC(1, AR8236_STATS_RXFCSERR, "RxFcsErr"), |
MIB_DESC(1, AR8236_STATS_RXALIGNERR, "RxAlignErr"), |
MIB_DESC(1, AR8236_STATS_RXRUNT, "RxRunt"), |
MIB_DESC(1, AR8236_STATS_RXFRAGMENT, "RxFragment"), |
MIB_DESC(1, AR8236_STATS_RX64BYTE, "Rx64Byte"), |
MIB_DESC(1, AR8236_STATS_RX128BYTE, "Rx128Byte"), |
MIB_DESC(1, AR8236_STATS_RX256BYTE, "Rx256Byte"), |
MIB_DESC(1, AR8236_STATS_RX512BYTE, "Rx512Byte"), |
MIB_DESC(1, AR8236_STATS_RX1024BYTE, "Rx1024Byte"), |
MIB_DESC(1, AR8236_STATS_RX1518BYTE, "Rx1518Byte"), |
MIB_DESC(1, AR8236_STATS_RXMAXBYTE, "RxMaxByte"), |
MIB_DESC(1, AR8236_STATS_RXTOOLONG, "RxTooLong"), |
MIB_DESC(2, AR8236_STATS_RXGOODBYTE, "RxGoodByte"), |
MIB_DESC(2, AR8236_STATS_RXBADBYTE, "RxBadByte"), |
MIB_DESC(1, AR8236_STATS_RXOVERFLOW, "RxOverFlow"), |
MIB_DESC(1, AR8236_STATS_FILTERED, "Filtered"), |
MIB_DESC(1, AR8236_STATS_TXBROAD, "TxBroad"), |
MIB_DESC(1, AR8236_STATS_TXPAUSE, "TxPause"), |
MIB_DESC(1, AR8236_STATS_TXMULTI, "TxMulti"), |
MIB_DESC(1, AR8236_STATS_TXUNDERRUN, "TxUnderRun"), |
MIB_DESC(1, AR8236_STATS_TX64BYTE, "Tx64Byte"), |
MIB_DESC(1, AR8236_STATS_TX128BYTE, "Tx128Byte"), |
MIB_DESC(1, AR8236_STATS_TX256BYTE, "Tx256Byte"), |
MIB_DESC(1, AR8236_STATS_TX512BYTE, "Tx512Byte"), |
MIB_DESC(1, AR8236_STATS_TX1024BYTE, "Tx1024Byte"), |
MIB_DESC(1, AR8236_STATS_TX1518BYTE, "Tx1518Byte"), |
MIB_DESC(1, AR8236_STATS_TXMAXBYTE, "TxMaxByte"), |
MIB_DESC(1, AR8236_STATS_TXOVERSIZE, "TxOverSize"), |
MIB_DESC(2, AR8236_STATS_TXBYTE, "TxByte"), |
MIB_DESC(1, AR8236_STATS_TXCOLLISION, "TxCollision"), |
MIB_DESC(1, AR8236_STATS_TXABORTCOL, "TxAbortCol"), |
MIB_DESC(1, AR8236_STATS_TXMULTICOL, "TxMultiCol"), |
MIB_DESC(1, AR8236_STATS_TXSINGLECOL, "TxSingleCol"), |
MIB_DESC(1, AR8236_STATS_TXEXCDEFER, "TxExcDefer"), |
MIB_DESC(1, AR8236_STATS_TXDEFER, "TxDefer"), |
MIB_DESC(1, AR8236_STATS_TXLATECOL, "TxLateCol"), |
}; |
|
static DEFINE_MUTEX(ar8xxx_dev_list_lock); |
static LIST_HEAD(ar8xxx_dev_list); |
|
/* inspired by phy_poll_reset in drivers/net/phy/phy_device.c */ |
static int |
ar8xxx_phy_poll_reset(struct mii_bus *bus) |
{ |
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 < AR8XXX_NUM_PHYS; i++) { |
ret = mdiobus_read(bus, i, MII_BMCR); |
if (ret < 0) |
return ret; |
if (ret & BMCR_RESET) |
break; |
if (i == AR8XXX_NUM_PHYS - 1) { |
usleep_range(1000, 2000); |
return 0; |
} |
} |
} |
return -ETIMEDOUT; |
} |
|
static int |
ar8xxx_phy_check_aneg(struct phy_device *phydev) |
{ |
int ret; |
|
if (phydev->autoneg != AUTONEG_ENABLE) |
return 0; |
/* |
* BMCR_ANENABLE might have been cleared |
* by phy_init_hw in certain kernel versions |
* therefore check for it |
*/ |
ret = phy_read(phydev, MII_BMCR); |
if (ret < 0) |
return ret; |
if (ret & BMCR_ANENABLE) |
return 0; |
|
dev_info(&phydev->mdio.dev, "ANEG disabled, re-enabling ...\n"); |
ret |= BMCR_ANENABLE | BMCR_ANRESTART; |
return phy_write(phydev, MII_BMCR, ret); |
} |
|
void |
ar8xxx_phy_init(struct ar8xxx_priv *priv) |
{ |
int i; |
struct mii_bus *bus; |
|
bus = priv->mii_bus; |
for (i = 0; i < AR8XXX_NUM_PHYS; i++) { |
if (priv->chip->phy_fixup) |
priv->chip->phy_fixup(priv, i); |
|
/* initialize the port itself */ |
mdiobus_write(bus, i, MII_ADVERTISE, |
ADVERTISE_ALL | ADVERTISE_PAUSE_CAP | ADVERTISE_PAUSE_ASYM); |
if (ar8xxx_has_gige(priv)) |
mdiobus_write(bus, i, MII_CTRL1000, ADVERTISE_1000FULL); |
mdiobus_write(bus, i, MII_BMCR, BMCR_RESET | BMCR_ANENABLE); |
} |
|
ar8xxx_phy_poll_reset(bus); |
} |
|
u32 |
ar8xxx_mii_read32(struct ar8xxx_priv *priv, int phy_id, int regnum) |
{ |
struct mii_bus *bus = priv->mii_bus; |
u16 lo, hi; |
|
lo = bus->read(bus, phy_id, regnum); |
hi = bus->read(bus, phy_id, regnum + 1); |
|
return (hi << 16) | lo; |
} |
|
void |
ar8xxx_mii_write32(struct ar8xxx_priv *priv, int phy_id, int regnum, u32 val) |
{ |
struct mii_bus *bus = priv->mii_bus; |
u16 lo, hi; |
|
lo = val & 0xffff; |
hi = (u16) (val >> 16); |
|
if (priv->chip->mii_lo_first) |
{ |
bus->write(bus, phy_id, regnum, lo); |
bus->write(bus, phy_id, regnum + 1, hi); |
} else { |
bus->write(bus, phy_id, regnum + 1, hi); |
bus->write(bus, phy_id, regnum, lo); |
} |
} |
|
u32 |
ar8xxx_read(struct ar8xxx_priv *priv, int reg) |
{ |
struct mii_bus *bus = priv->mii_bus; |
u16 r1, r2, page; |
u32 val; |
|
split_addr((u32) reg, &r1, &r2, &page); |
|
mutex_lock(&bus->mdio_lock); |
|
bus->write(bus, 0x18, 0, page); |
wait_for_page_switch(); |
val = ar8xxx_mii_read32(priv, 0x10 | r2, r1); |
|
mutex_unlock(&bus->mdio_lock); |
|
return val; |
} |
|
void |
ar8xxx_write(struct ar8xxx_priv *priv, int reg, u32 val) |
{ |
struct mii_bus *bus = priv->mii_bus; |
u16 r1, r2, page; |
|
split_addr((u32) reg, &r1, &r2, &page); |
|
mutex_lock(&bus->mdio_lock); |
|
bus->write(bus, 0x18, 0, page); |
wait_for_page_switch(); |
ar8xxx_mii_write32(priv, 0x10 | r2, r1, val); |
|
mutex_unlock(&bus->mdio_lock); |
} |
|
u32 |
ar8xxx_rmw(struct ar8xxx_priv *priv, int reg, u32 mask, u32 val) |
{ |
struct mii_bus *bus = priv->mii_bus; |
u16 r1, r2, page; |
u32 ret; |
|
split_addr((u32) reg, &r1, &r2, &page); |
|
mutex_lock(&bus->mdio_lock); |
|
bus->write(bus, 0x18, 0, page); |
wait_for_page_switch(); |
|
ret = ar8xxx_mii_read32(priv, 0x10 | r2, r1); |
ret &= ~mask; |
ret |= val; |
ar8xxx_mii_write32(priv, 0x10 | r2, r1, ret); |
|
mutex_unlock(&bus->mdio_lock); |
|
return ret; |
} |
void |
ar8xxx_phy_dbg_read(struct ar8xxx_priv *priv, int phy_addr, |
u16 dbg_addr, u16 *dbg_data) |
{ |
struct mii_bus *bus = priv->mii_bus; |
|
mutex_lock(&bus->mdio_lock); |
bus->write(bus, phy_addr, MII_ATH_DBG_ADDR, dbg_addr); |
*dbg_data = bus->read(bus, phy_addr, MII_ATH_DBG_DATA); |
mutex_unlock(&bus->mdio_lock); |
} |
|
void |
ar8xxx_phy_dbg_write(struct ar8xxx_priv *priv, int phy_addr, |
u16 dbg_addr, u16 dbg_data) |
{ |
struct mii_bus *bus = priv->mii_bus; |
|
mutex_lock(&bus->mdio_lock); |
bus->write(bus, phy_addr, MII_ATH_DBG_ADDR, dbg_addr); |
bus->write(bus, phy_addr, MII_ATH_DBG_DATA, dbg_data); |
mutex_unlock(&bus->mdio_lock); |
} |
|
static inline void |
ar8xxx_phy_mmd_prep(struct mii_bus *bus, int phy_addr, u16 addr, u16 reg) |
{ |
bus->write(bus, phy_addr, MII_ATH_MMD_ADDR, addr); |
bus->write(bus, phy_addr, MII_ATH_MMD_DATA, reg); |
bus->write(bus, phy_addr, MII_ATH_MMD_ADDR, addr | 0x4000); |
} |
|
void |
ar8xxx_phy_mmd_write(struct ar8xxx_priv *priv, int phy_addr, u16 addr, u16 reg, u16 data) |
{ |
struct mii_bus *bus = priv->mii_bus; |
|
mutex_lock(&bus->mdio_lock); |
ar8xxx_phy_mmd_prep(bus, phy_addr, addr, reg); |
bus->write(bus, phy_addr, MII_ATH_MMD_DATA, data); |
mutex_unlock(&bus->mdio_lock); |
} |
|
u16 |
ar8xxx_phy_mmd_read(struct ar8xxx_priv *priv, int phy_addr, u16 addr, u16 reg) |
{ |
struct mii_bus *bus = priv->mii_bus; |
u16 data; |
|
mutex_lock(&bus->mdio_lock); |
ar8xxx_phy_mmd_prep(bus, phy_addr, addr, reg); |
data = bus->read(bus, phy_addr, MII_ATH_MMD_DATA); |
mutex_unlock(&bus->mdio_lock); |
|
return data; |
} |
|
static int |
ar8xxx_reg_wait(struct ar8xxx_priv *priv, u32 reg, u32 mask, u32 val, |
unsigned timeout) |
{ |
int i; |
|
for (i = 0; i < timeout; i++) { |
u32 t; |
|
t = ar8xxx_read(priv, reg); |
if ((t & mask) == val) |
return 0; |
|
usleep_range(1000, 2000); |
cond_resched(); |
} |
|
return -ETIMEDOUT; |
} |
|
static int |
ar8xxx_mib_op(struct ar8xxx_priv *priv, u32 op) |
{ |
unsigned mib_func = priv->chip->mib_func; |
int ret; |
|
lockdep_assert_held(&priv->mib_lock); |
|
/* Capture the hardware statistics for all ports */ |
ar8xxx_rmw(priv, mib_func, AR8216_MIB_FUNC, (op << AR8216_MIB_FUNC_S)); |
|
/* Wait for the capturing to complete. */ |
ret = ar8xxx_reg_wait(priv, mib_func, AR8216_MIB_BUSY, 0, 10); |
if (ret) |
goto out; |
|
ret = 0; |
|
out: |
return ret; |
} |
|
static int |
ar8xxx_mib_capture(struct ar8xxx_priv *priv) |
{ |
return ar8xxx_mib_op(priv, AR8216_MIB_FUNC_CAPTURE); |
} |
|
static int |
ar8xxx_mib_flush(struct ar8xxx_priv *priv) |
{ |
return ar8xxx_mib_op(priv, AR8216_MIB_FUNC_FLUSH); |
} |
|
static void |
ar8xxx_mib_fetch_port_stat(struct ar8xxx_priv *priv, int port, bool flush) |
{ |
unsigned int base; |
u64 *mib_stats; |
int i; |
|
WARN_ON(port >= priv->dev.ports); |
|
lockdep_assert_held(&priv->mib_lock); |
|
base = priv->chip->reg_port_stats_start + |
priv->chip->reg_port_stats_length * port; |
|
mib_stats = &priv->mib_stats[port * priv->chip->num_mibs]; |
for (i = 0; i < priv->chip->num_mibs; i++) { |
const struct ar8xxx_mib_desc *mib; |
u64 t; |
|
mib = &priv->chip->mib_decs[i]; |
t = ar8xxx_read(priv, base + mib->offset); |
if (mib->size == 2) { |
u64 hi; |
|
hi = ar8xxx_read(priv, base + mib->offset + 4); |
t |= hi << 32; |
} |
|
if (flush) |
mib_stats[i] = 0; |
else |
mib_stats[i] += t; |
cond_resched(); |
} |
} |
|
static void |
ar8216_read_port_link(struct ar8xxx_priv *priv, int port, |
struct switch_port_link *link) |
{ |
u32 status; |
u32 speed; |
|
memset(link, '\0', sizeof(*link)); |
|
status = priv->chip->read_port_status(priv, port); |
|
link->aneg = !!(status & AR8216_PORT_STATUS_LINK_AUTO); |
if (link->aneg) { |
link->link = !!(status & AR8216_PORT_STATUS_LINK_UP); |
} else { |
link->link = true; |
|
if (priv->get_port_link) { |
int err; |
|
err = priv->get_port_link(port); |
if (err >= 0) |
link->link = !!err; |
} |
} |
|
if (!link->link) |
return; |
|
link->duplex = !!(status & AR8216_PORT_STATUS_DUPLEX); |
link->tx_flow = !!(status & AR8216_PORT_STATUS_TXFLOW); |
link->rx_flow = !!(status & AR8216_PORT_STATUS_RXFLOW); |
|
if (link->aneg && link->duplex && priv->chip->read_port_eee_status) |
link->eee = priv->chip->read_port_eee_status(priv, port); |
|
speed = (status & AR8216_PORT_STATUS_SPEED) >> |
AR8216_PORT_STATUS_SPEED_S; |
|
switch (speed) { |
case AR8216_PORT_SPEED_10M: |
link->speed = SWITCH_PORT_SPEED_10; |
break; |
case AR8216_PORT_SPEED_100M: |
link->speed = SWITCH_PORT_SPEED_100; |
break; |
case AR8216_PORT_SPEED_1000M: |
link->speed = SWITCH_PORT_SPEED_1000; |
break; |
default: |
link->speed = SWITCH_PORT_SPEED_UNKNOWN; |
break; |
} |
} |
|
static struct sk_buff * |
ar8216_mangle_tx(struct net_device *dev, struct sk_buff *skb) |
{ |
struct ar8xxx_priv *priv = dev->phy_ptr; |
unsigned char *buf; |
|
if (unlikely(!priv)) |
goto error; |
|
if (!priv->vlan) |
goto send; |
|
if (unlikely(skb_headroom(skb) < 2)) { |
if (pskb_expand_head(skb, 2, 0, GFP_ATOMIC) < 0) |
goto error; |
} |
|
buf = skb_push(skb, 2); |
buf[0] = 0x10; |
buf[1] = 0x80; |
|
send: |
return skb; |
|
error: |
dev_kfree_skb_any(skb); |
return NULL; |
} |
|
static void |
ar8216_mangle_rx(struct net_device *dev, struct sk_buff *skb) |
{ |
struct ar8xxx_priv *priv; |
unsigned char *buf; |
int port, vlan; |
|
priv = dev->phy_ptr; |
if (!priv) |
return; |
|
/* don't strip the header if vlan mode is disabled */ |
if (!priv->vlan) |
return; |
|
/* strip header, get vlan id */ |
buf = skb->data; |
skb_pull(skb, 2); |
|
/* check for vlan header presence */ |
if ((buf[12 + 2] != 0x81) || (buf[13 + 2] != 0x00)) |
return; |
|
port = buf[0] & 0x7; |
|
/* no need to fix up packets coming from a tagged source */ |
if (priv->vlan_tagged & (1 << port)) |
return; |
|
/* lookup port vid from local table, the switch passes an invalid vlan id */ |
vlan = priv->vlan_id[priv->pvid[port]]; |
|
buf[14 + 2] &= 0xf0; |
buf[14 + 2] |= vlan >> 8; |
buf[15 + 2] = vlan & 0xff; |
} |
|
int |
ar8216_wait_bit(struct ar8xxx_priv *priv, int reg, u32 mask, u32 val) |
{ |
int timeout = 20; |
u32 t = 0; |
|
while (1) { |
t = ar8xxx_read(priv, reg); |
if ((t & mask) == val) |
return 0; |
|
if (timeout-- <= 0) |
break; |
|
udelay(10); |
cond_resched(); |
} |
|
pr_err("ar8216: timeout on reg %08x: %08x & %08x != %08x\n", |
(unsigned int) reg, t, mask, val); |
return -ETIMEDOUT; |
} |
|
static void |
ar8216_vtu_op(struct ar8xxx_priv *priv, u32 op, u32 val) |
{ |
if (ar8216_wait_bit(priv, AR8216_REG_VTU, AR8216_VTU_ACTIVE, 0)) |
return; |
if ((op & AR8216_VTU_OP) == AR8216_VTU_OP_LOAD) { |
val &= AR8216_VTUDATA_MEMBER; |
val |= AR8216_VTUDATA_VALID; |
ar8xxx_write(priv, AR8216_REG_VTU_DATA, val); |
} |
op |= AR8216_VTU_ACTIVE; |
ar8xxx_write(priv, AR8216_REG_VTU, op); |
} |
|
static void |
ar8216_vtu_flush(struct ar8xxx_priv *priv) |
{ |
ar8216_vtu_op(priv, AR8216_VTU_OP_FLUSH, 0); |
} |
|
static void |
ar8216_vtu_load_vlan(struct ar8xxx_priv *priv, u32 vid, u32 port_mask) |
{ |
u32 op; |
|
op = AR8216_VTU_OP_LOAD | (vid << AR8216_VTU_VID_S); |
ar8216_vtu_op(priv, op, port_mask); |
} |
|
static int |
ar8216_atu_flush(struct ar8xxx_priv *priv) |
{ |
int ret; |
|
ret = ar8216_wait_bit(priv, AR8216_REG_ATU_FUNC0, AR8216_ATU_ACTIVE, 0); |
if (!ret) |
ar8xxx_write(priv, AR8216_REG_ATU_FUNC0, AR8216_ATU_OP_FLUSH | |
AR8216_ATU_ACTIVE); |
|
return ret; |
} |
|
static int |
ar8216_atu_flush_port(struct ar8xxx_priv *priv, int port) |
{ |
u32 t; |
int ret; |
|
ret = ar8216_wait_bit(priv, AR8216_REG_ATU_FUNC0, AR8216_ATU_ACTIVE, 0); |
if (!ret) { |
t = (port << AR8216_ATU_PORT_NUM_S) | AR8216_ATU_OP_FLUSH_PORT; |
t |= AR8216_ATU_ACTIVE; |
ar8xxx_write(priv, AR8216_REG_ATU_FUNC0, t); |
} |
|
return ret; |
} |
|
static u32 |
ar8216_read_port_status(struct ar8xxx_priv *priv, int port) |
{ |
return ar8xxx_read(priv, AR8216_REG_PORT_STATUS(port)); |
} |
|
static void |
ar8216_setup_port(struct ar8xxx_priv *priv, int port, u32 members) |
{ |
u32 header; |
u32 egress, ingress; |
u32 pvid; |
|
if (priv->vlan) { |
pvid = priv->vlan_id[priv->pvid[port]]; |
if (priv->vlan_tagged & (1 << port)) |
egress = AR8216_OUT_ADD_VLAN; |
else |
egress = AR8216_OUT_STRIP_VLAN; |
ingress = AR8216_IN_SECURE; |
} else { |
pvid = port; |
egress = AR8216_OUT_KEEP; |
ingress = AR8216_IN_PORT_ONLY; |
} |
|
if (chip_is_ar8216(priv) && priv->vlan && port == AR8216_PORT_CPU) |
header = AR8216_PORT_CTRL_HEADER; |
else |
header = 0; |
|
ar8xxx_rmw(priv, AR8216_REG_PORT_CTRL(port), |
AR8216_PORT_CTRL_LEARN | AR8216_PORT_CTRL_VLAN_MODE | |
AR8216_PORT_CTRL_SINGLE_VLAN | AR8216_PORT_CTRL_STATE | |
AR8216_PORT_CTRL_HEADER | AR8216_PORT_CTRL_LEARN_LOCK, |
AR8216_PORT_CTRL_LEARN | header | |
(egress << AR8216_PORT_CTRL_VLAN_MODE_S) | |
(AR8216_PORT_STATE_FORWARD << AR8216_PORT_CTRL_STATE_S)); |
|
ar8xxx_rmw(priv, AR8216_REG_PORT_VLAN(port), |
AR8216_PORT_VLAN_DEST_PORTS | AR8216_PORT_VLAN_MODE | |
AR8216_PORT_VLAN_DEFAULT_ID, |
(members << AR8216_PORT_VLAN_DEST_PORTS_S) | |
(ingress << AR8216_PORT_VLAN_MODE_S) | |
(pvid << AR8216_PORT_VLAN_DEFAULT_ID_S)); |
} |
|
static int |
ar8216_hw_init(struct ar8xxx_priv *priv) |
{ |
if (priv->initialized) |
return 0; |
|
ar8xxx_phy_init(priv); |
|
priv->initialized = true; |
return 0; |
} |
|
static void |
ar8216_init_globals(struct ar8xxx_priv *priv) |
{ |
/* standard atheros magic */ |
ar8xxx_write(priv, 0x38, 0xc000050e); |
|
ar8xxx_rmw(priv, AR8216_REG_GLOBAL_CTRL, |
AR8216_GCTRL_MTU, 1518 + 8 + 2); |
} |
|
static void |
ar8216_init_port(struct ar8xxx_priv *priv, int port) |
{ |
/* Enable port learning and tx */ |
ar8xxx_write(priv, AR8216_REG_PORT_CTRL(port), |
AR8216_PORT_CTRL_LEARN | |
(4 << AR8216_PORT_CTRL_STATE_S)); |
|
ar8xxx_write(priv, AR8216_REG_PORT_VLAN(port), 0); |
|
if (port == AR8216_PORT_CPU) { |
ar8xxx_write(priv, AR8216_REG_PORT_STATUS(port), |
AR8216_PORT_STATUS_LINK_UP | |
(ar8xxx_has_gige(priv) ? |
AR8216_PORT_SPEED_1000M : AR8216_PORT_SPEED_100M) | |
AR8216_PORT_STATUS_TXMAC | |
AR8216_PORT_STATUS_RXMAC | |
(chip_is_ar8316(priv) ? AR8216_PORT_STATUS_RXFLOW : 0) | |
(chip_is_ar8316(priv) ? AR8216_PORT_STATUS_TXFLOW : 0) | |
AR8216_PORT_STATUS_DUPLEX); |
} else { |
ar8xxx_write(priv, AR8216_REG_PORT_STATUS(port), |
AR8216_PORT_STATUS_LINK_AUTO); |
} |
} |
|
static void |
ar8216_wait_atu_ready(struct ar8xxx_priv *priv, u16 r2, u16 r1) |
{ |
int timeout = 20; |
|
while (ar8xxx_mii_read32(priv, r2, r1) & AR8216_ATU_ACTIVE && --timeout) { |
udelay(10); |
cond_resched(); |
} |
|
if (!timeout) |
pr_err("ar8216: timeout waiting for atu to become ready\n"); |
} |
|
static void ar8216_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_func0, r1_func1, r1_func2; |
u32 t, val0, val1, val2; |
|
split_addr(AR8216_REG_ATU_FUNC0, &r1_func0, &r2, &page); |
r2 |= 0x10; |
|
r1_func1 = (AR8216_REG_ATU_FUNC1 >> 1) & 0x1e; |
r1_func2 = (AR8216_REG_ATU_FUNC2 >> 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(); |
|
ar8216_wait_atu_ready(priv, r2, r1_func0); |
|
ar8xxx_mii_write32(priv, r2, r1_func0, AR8216_ATU_OP_GET_NEXT); |
ar8xxx_mii_write32(priv, r2, r1_func1, 0); |
ar8xxx_mii_write32(priv, r2, r1_func2, 0); |
break; |
case AR8XXX_ARL_GET_NEXT: |
t = ar8xxx_mii_read32(priv, r2, r1_func0); |
t |= AR8216_ATU_ACTIVE; |
ar8xxx_mii_write32(priv, r2, r1_func0, t); |
ar8216_wait_atu_ready(priv, r2, r1_func0); |
|
val0 = ar8xxx_mii_read32(priv, r2, r1_func0); |
val1 = ar8xxx_mii_read32(priv, r2, r1_func1); |
val2 = ar8xxx_mii_read32(priv, r2, r1_func2); |
|
*status = (val2 & AR8216_ATU_STATUS) >> AR8216_ATU_STATUS_S; |
if (!*status) |
break; |
|
a->portmap = (val2 & AR8216_ATU_PORTS) >> AR8216_ATU_PORTS_S; |
a->mac[0] = (val0 & AR8216_ATU_ADDR5) >> AR8216_ATU_ADDR5_S; |
a->mac[1] = (val0 & AR8216_ATU_ADDR4) >> AR8216_ATU_ADDR4_S; |
a->mac[2] = (val1 & AR8216_ATU_ADDR3) >> AR8216_ATU_ADDR3_S; |
a->mac[3] = (val1 & AR8216_ATU_ADDR2) >> AR8216_ATU_ADDR2_S; |
a->mac[4] = (val1 & AR8216_ATU_ADDR1) >> AR8216_ATU_ADDR1_S; |
a->mac[5] = (val1 & AR8216_ATU_ADDR0) >> AR8216_ATU_ADDR0_S; |
break; |
} |
} |
|
static void |
ar8236_setup_port(struct ar8xxx_priv *priv, int port, u32 members) |
{ |
u32 egress, ingress; |
u32 pvid; |
|
if (priv->vlan) { |
pvid = priv->vlan_id[priv->pvid[port]]; |
if (priv->vlan_tagged & (1 << port)) |
egress = AR8216_OUT_ADD_VLAN; |
else |
egress = AR8216_OUT_STRIP_VLAN; |
ingress = AR8216_IN_SECURE; |
} else { |
pvid = port; |
egress = AR8216_OUT_KEEP; |
ingress = AR8216_IN_PORT_ONLY; |
} |
|
ar8xxx_rmw(priv, AR8216_REG_PORT_CTRL(port), |
AR8216_PORT_CTRL_LEARN | AR8216_PORT_CTRL_VLAN_MODE | |
AR8216_PORT_CTRL_SINGLE_VLAN | AR8216_PORT_CTRL_STATE | |
AR8216_PORT_CTRL_HEADER | AR8216_PORT_CTRL_LEARN_LOCK, |
AR8216_PORT_CTRL_LEARN | |
(egress << AR8216_PORT_CTRL_VLAN_MODE_S) | |
(AR8216_PORT_STATE_FORWARD << AR8216_PORT_CTRL_STATE_S)); |
|
ar8xxx_rmw(priv, AR8236_REG_PORT_VLAN(port), |
AR8236_PORT_VLAN_DEFAULT_ID, |
(pvid << AR8236_PORT_VLAN_DEFAULT_ID_S)); |
|
ar8xxx_rmw(priv, AR8236_REG_PORT_VLAN2(port), |
AR8236_PORT_VLAN2_VLAN_MODE | |
AR8236_PORT_VLAN2_MEMBER, |
(ingress << AR8236_PORT_VLAN2_VLAN_MODE_S) | |
(members << AR8236_PORT_VLAN2_MEMBER_S)); |
} |
|
static void |
ar8236_init_globals(struct ar8xxx_priv *priv) |
{ |
/* enable jumbo frames */ |
ar8xxx_rmw(priv, AR8216_REG_GLOBAL_CTRL, |
AR8316_GCTRL_MTU, 9018 + 8 + 2); |
|
/* enable cpu port to receive arp frames */ |
ar8xxx_reg_set(priv, AR8216_REG_ATU_CTRL, |
AR8236_ATU_CTRL_RES); |
|
/* enable cpu port to receive multicast and broadcast frames */ |
ar8xxx_reg_set(priv, AR8216_REG_FLOOD_MASK, |
AR8236_FM_CPU_BROADCAST_EN | AR8236_FM_CPU_BCAST_FWD_EN); |
|
/* Enable MIB counters */ |
ar8xxx_rmw(priv, AR8216_REG_MIB_FUNC, AR8216_MIB_FUNC | AR8236_MIB_EN, |
(AR8216_MIB_FUNC_NO_OP << AR8216_MIB_FUNC_S) | |
AR8236_MIB_EN); |
} |
|
static int |
ar8316_hw_init(struct ar8xxx_priv *priv) |
{ |
u32 val, newval; |
|
val = ar8xxx_read(priv, AR8316_REG_POSTRIP); |
|
if (priv->phy->interface == PHY_INTERFACE_MODE_RGMII) { |
if (priv->port4_phy) { |
/* value taken from Ubiquiti RouterStation Pro */ |
newval = 0x81461bea; |
pr_info("ar8316: Using port 4 as PHY\n"); |
} else { |
newval = 0x01261be2; |
pr_info("ar8316: Using port 4 as switch port\n"); |
} |
} else if (priv->phy->interface == PHY_INTERFACE_MODE_GMII) { |
/* value taken from AVM Fritz!Box 7390 sources */ |
newval = 0x010e5b71; |
} else { |
/* no known value for phy interface */ |
pr_err("ar8316: unsupported mii mode: %d.\n", |
priv->phy->interface); |
return -EINVAL; |
} |
|
if (val == newval) |
goto out; |
|
ar8xxx_write(priv, AR8316_REG_POSTRIP, newval); |
|
if (priv->port4_phy && |
priv->phy->interface == PHY_INTERFACE_MODE_RGMII) { |
/* work around for phy4 rgmii mode */ |
ar8xxx_phy_dbg_write(priv, 4, 0x12, 0x480c); |
/* rx delay */ |
ar8xxx_phy_dbg_write(priv, 4, 0x0, 0x824e); |
/* tx delay */ |
ar8xxx_phy_dbg_write(priv, 4, 0x5, 0x3d47); |
msleep(1000); |
} |
|
ar8xxx_phy_init(priv); |
|
out: |
priv->initialized = true; |
return 0; |
} |
|
static void |
ar8316_init_globals(struct ar8xxx_priv *priv) |
{ |
/* standard atheros magic */ |
ar8xxx_write(priv, 0x38, 0xc000050e); |
|
/* enable cpu port to receive multicast and broadcast frames */ |
ar8xxx_write(priv, AR8216_REG_FLOOD_MASK, 0x003f003f); |
|
/* enable jumbo frames */ |
ar8xxx_rmw(priv, AR8216_REG_GLOBAL_CTRL, |
AR8316_GCTRL_MTU, 9018 + 8 + 2); |
|
/* Enable MIB counters */ |
ar8xxx_rmw(priv, AR8216_REG_MIB_FUNC, AR8216_MIB_FUNC | AR8236_MIB_EN, |
(AR8216_MIB_FUNC_NO_OP << AR8216_MIB_FUNC_S) | |
AR8236_MIB_EN); |
} |
|
int |
ar8xxx_sw_set_vlan(struct switch_dev *dev, const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); |
priv->vlan = !!val->value.i; |
return 0; |
} |
|
int |
ar8xxx_sw_get_vlan(struct switch_dev *dev, const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); |
val->value.i = priv->vlan; |
return 0; |
} |
|
|
int |
ar8xxx_sw_set_pvid(struct switch_dev *dev, int port, int vlan) |
{ |
struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); |
|
/* make sure no invalid PVIDs get set */ |
|
if (vlan < 0 || vlan >= dev->vlans || |
port < 0 || port >= AR8X16_MAX_PORTS) |
return -EINVAL; |
|
priv->pvid[port] = vlan; |
return 0; |
} |
|
int |
ar8xxx_sw_get_pvid(struct switch_dev *dev, int port, int *vlan) |
{ |
struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); |
|
if (port < 0 || port >= AR8X16_MAX_PORTS) |
return -EINVAL; |
|
*vlan = priv->pvid[port]; |
return 0; |
} |
|
static int |
ar8xxx_sw_set_vid(struct switch_dev *dev, const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); |
|
if (val->port_vlan >= AR8X16_MAX_VLANS) |
return -EINVAL; |
|
priv->vlan_id[val->port_vlan] = val->value.i; |
return 0; |
} |
|
static int |
ar8xxx_sw_get_vid(struct switch_dev *dev, const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); |
val->value.i = priv->vlan_id[val->port_vlan]; |
return 0; |
} |
|
int |
ar8xxx_sw_get_port_link(struct switch_dev *dev, int port, |
struct switch_port_link *link) |
{ |
struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); |
|
ar8216_read_port_link(priv, port, link); |
return 0; |
} |
|
static int |
ar8xxx_sw_get_ports(struct switch_dev *dev, struct switch_val *val) |
{ |
struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); |
u8 ports; |
int i; |
|
if (val->port_vlan >= AR8X16_MAX_VLANS) |
return -EINVAL; |
|
ports = priv->vlan_table[val->port_vlan]; |
val->len = 0; |
for (i = 0; i < dev->ports; i++) { |
struct switch_port *p; |
|
if (!(ports & (1 << i))) |
continue; |
|
p = &val->value.ports[val->len++]; |
p->id = i; |
if (priv->vlan_tagged & (1 << i)) |
p->flags = (1 << SWITCH_PORT_FLAG_TAGGED); |
else |
p->flags = 0; |
} |
return 0; |
} |
|
static int |
ar8xxx_sw_set_ports(struct switch_dev *dev, struct switch_val *val) |
{ |
struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); |
u8 *vt = &priv->vlan_table[val->port_vlan]; |
int i, j; |
|
*vt = 0; |
for (i = 0; i < val->len; i++) { |
struct switch_port *p = &val->value.ports[i]; |
|
if (p->flags & (1 << SWITCH_PORT_FLAG_TAGGED)) { |
priv->vlan_tagged |= (1 << p->id); |
} else { |
priv->vlan_tagged &= ~(1 << p->id); |
priv->pvid[p->id] = val->port_vlan; |
|
/* make sure that an untagged port does not |
* appear in other vlans */ |
for (j = 0; j < AR8X16_MAX_VLANS; j++) { |
if (j == val->port_vlan) |
continue; |
priv->vlan_table[j] &= ~(1 << p->id); |
} |
} |
|
*vt |= 1 << p->id; |
} |
return 0; |
} |
|
static void |
ar8216_set_mirror_regs(struct ar8xxx_priv *priv) |
{ |
int port; |
|
/* reset all mirror registers */ |
ar8xxx_rmw(priv, AR8216_REG_GLOBAL_CPUPORT, |
AR8216_GLOBAL_CPUPORT_MIRROR_PORT, |
(0xF << AR8216_GLOBAL_CPUPORT_MIRROR_PORT_S)); |
for (port = 0; port < AR8216_NUM_PORTS; port++) { |
ar8xxx_reg_clear(priv, AR8216_REG_PORT_CTRL(port), |
AR8216_PORT_CTRL_MIRROR_RX); |
|
ar8xxx_reg_clear(priv, AR8216_REG_PORT_CTRL(port), |
AR8216_PORT_CTRL_MIRROR_TX); |
} |
|
/* now enable mirroring if necessary */ |
if (priv->source_port >= AR8216_NUM_PORTS || |
priv->monitor_port >= AR8216_NUM_PORTS || |
priv->source_port == priv->monitor_port) { |
return; |
} |
|
ar8xxx_rmw(priv, AR8216_REG_GLOBAL_CPUPORT, |
AR8216_GLOBAL_CPUPORT_MIRROR_PORT, |
(priv->monitor_port << AR8216_GLOBAL_CPUPORT_MIRROR_PORT_S)); |
|
if (priv->mirror_rx) |
ar8xxx_reg_set(priv, AR8216_REG_PORT_CTRL(priv->source_port), |
AR8216_PORT_CTRL_MIRROR_RX); |
|
if (priv->mirror_tx) |
ar8xxx_reg_set(priv, AR8216_REG_PORT_CTRL(priv->source_port), |
AR8216_PORT_CTRL_MIRROR_TX); |
} |
|
static inline u32 |
ar8xxx_age_time_val(int age_time) |
{ |
return (age_time + AR8XXX_REG_ARL_CTRL_AGE_TIME_SECS / 2) / |
AR8XXX_REG_ARL_CTRL_AGE_TIME_SECS; |
} |
|
static inline void |
ar8xxx_set_age_time(struct ar8xxx_priv *priv, int reg) |
{ |
u32 age_time = ar8xxx_age_time_val(priv->arl_age_time); |
ar8xxx_rmw(priv, reg, AR8216_ATU_CTRL_AGE_TIME, age_time << AR8216_ATU_CTRL_AGE_TIME_S); |
} |
|
int |
ar8xxx_sw_hw_apply(struct switch_dev *dev) |
{ |
struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); |
const struct ar8xxx_chip *chip = priv->chip; |
u8 portmask[AR8X16_MAX_PORTS]; |
int i, j; |
|
mutex_lock(&priv->reg_mutex); |
/* flush all vlan translation unit entries */ |
priv->chip->vtu_flush(priv); |
|
memset(portmask, 0, sizeof(portmask)); |
if (!priv->init) { |
/* calculate the port destination masks and load vlans |
* into the vlan translation unit */ |
for (j = 0; j < AR8X16_MAX_VLANS; j++) { |
u8 vp = priv->vlan_table[j]; |
|
if (!vp) |
continue; |
|
for (i = 0; i < dev->ports; i++) { |
u8 mask = (1 << i); |
if (vp & mask) |
portmask[i] |= vp & ~mask; |
} |
|
chip->vtu_load_vlan(priv, priv->vlan_id[j], |
priv->vlan_table[j]); |
} |
} else { |
/* vlan disabled: |
* isolate all ports, but connect them to the cpu port */ |
for (i = 0; i < dev->ports; i++) { |
if (i == AR8216_PORT_CPU) |
continue; |
|
portmask[i] = 1 << AR8216_PORT_CPU; |
portmask[AR8216_PORT_CPU] |= (1 << i); |
} |
} |
|
/* update the port destination mask registers and tag settings */ |
for (i = 0; i < dev->ports; i++) { |
chip->setup_port(priv, i, portmask[i]); |
} |
|
chip->set_mirror_regs(priv); |
|
/* set age time */ |
if (chip->reg_arl_ctrl) |
ar8xxx_set_age_time(priv, chip->reg_arl_ctrl); |
|
mutex_unlock(&priv->reg_mutex); |
return 0; |
} |
|
int |
ar8xxx_sw_reset_switch(struct switch_dev *dev) |
{ |
struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); |
const struct ar8xxx_chip *chip = priv->chip; |
int i; |
|
mutex_lock(&priv->reg_mutex); |
memset(&priv->vlan, 0, sizeof(struct ar8xxx_priv) - |
offsetof(struct ar8xxx_priv, vlan)); |
|
for (i = 0; i < AR8X16_MAX_VLANS; i++) |
priv->vlan_id[i] = i; |
|
/* Configure all ports */ |
for (i = 0; i < dev->ports; i++) |
chip->init_port(priv, i); |
|
priv->mirror_rx = false; |
priv->mirror_tx = false; |
priv->source_port = 0; |
priv->monitor_port = 0; |
priv->arl_age_time = AR8XXX_DEFAULT_ARL_AGE_TIME; |
|
chip->init_globals(priv); |
chip->atu_flush(priv); |
|
mutex_unlock(&priv->reg_mutex); |
|
return chip->sw_hw_apply(dev); |
} |
|
int |
ar8xxx_sw_set_reset_mibs(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); |
unsigned int len; |
int ret; |
|
if (!ar8xxx_has_mib_counters(priv)) |
return -EOPNOTSUPP; |
|
mutex_lock(&priv->mib_lock); |
|
len = priv->dev.ports * priv->chip->num_mibs * |
sizeof(*priv->mib_stats); |
memset(priv->mib_stats, '\0', len); |
ret = ar8xxx_mib_flush(priv); |
if (ret) |
goto unlock; |
|
ret = 0; |
|
unlock: |
mutex_unlock(&priv->mib_lock); |
return ret; |
} |
|
int |
ar8xxx_sw_set_mirror_rx_enable(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); |
priv->mirror_rx = !!val->value.i; |
priv->chip->set_mirror_regs(priv); |
mutex_unlock(&priv->reg_mutex); |
|
return 0; |
} |
|
int |
ar8xxx_sw_get_mirror_rx_enable(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); |
val->value.i = priv->mirror_rx; |
return 0; |
} |
|
int |
ar8xxx_sw_set_mirror_tx_enable(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); |
priv->mirror_tx = !!val->value.i; |
priv->chip->set_mirror_regs(priv); |
mutex_unlock(&priv->reg_mutex); |
|
return 0; |
} |
|
int |
ar8xxx_sw_get_mirror_tx_enable(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); |
val->value.i = priv->mirror_tx; |
return 0; |
} |
|
int |
ar8xxx_sw_set_mirror_monitor_port(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); |
priv->monitor_port = val->value.i; |
priv->chip->set_mirror_regs(priv); |
mutex_unlock(&priv->reg_mutex); |
|
return 0; |
} |
|
int |
ar8xxx_sw_get_mirror_monitor_port(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); |
val->value.i = priv->monitor_port; |
return 0; |
} |
|
int |
ar8xxx_sw_set_mirror_source_port(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); |
priv->source_port = val->value.i; |
priv->chip->set_mirror_regs(priv); |
mutex_unlock(&priv->reg_mutex); |
|
return 0; |
} |
|
int |
ar8xxx_sw_get_mirror_source_port(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); |
val->value.i = priv->source_port; |
return 0; |
} |
|
int |
ar8xxx_sw_set_port_reset_mib(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); |
int port; |
int ret; |
|
if (!ar8xxx_has_mib_counters(priv)) |
return -EOPNOTSUPP; |
|
port = val->port_vlan; |
if (port >= dev->ports) |
return -EINVAL; |
|
mutex_lock(&priv->mib_lock); |
ret = ar8xxx_mib_capture(priv); |
if (ret) |
goto unlock; |
|
ar8xxx_mib_fetch_port_stat(priv, port, true); |
|
ret = 0; |
|
unlock: |
mutex_unlock(&priv->mib_lock); |
return ret; |
} |
|
static void |
ar8xxx_byte_to_str(char *buf, int len, u64 byte) |
{ |
unsigned long b; |
const char *unit; |
|
if (byte >= 0x40000000) { /* 1 GiB */ |
b = byte * 10 / 0x40000000; |
unit = "GiB"; |
} else if (byte >= 0x100000) { /* 1 MiB */ |
b = byte * 10 / 0x100000; |
unit = "MiB"; |
} else if (byte >= 0x400) { /* 1 KiB */ |
b = byte * 10 / 0x400; |
unit = "KiB"; |
} else { |
b = byte; |
unit = "Byte"; |
} |
if (strcmp(unit, "Byte")) |
snprintf(buf, len, "%lu.%lu %s", b / 10, b % 10, unit); |
else |
snprintf(buf, len, "%lu %s", b, unit); |
} |
|
int |
ar8xxx_sw_get_port_mib(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); |
const struct ar8xxx_chip *chip = priv->chip; |
u64 *mib_stats, mib_data; |
unsigned int port; |
int ret; |
char *buf = priv->buf; |
char buf1[64]; |
const char *mib_name; |
int i, len = 0; |
bool mib_stats_empty = true; |
|
if (!ar8xxx_has_mib_counters(priv)) |
return -EOPNOTSUPP; |
|
port = val->port_vlan; |
if (port >= dev->ports) |
return -EINVAL; |
|
mutex_lock(&priv->mib_lock); |
ret = ar8xxx_mib_capture(priv); |
if (ret) |
goto unlock; |
|
ar8xxx_mib_fetch_port_stat(priv, port, false); |
|
len += snprintf(buf + len, sizeof(priv->buf) - len, |
"MIB counters\n"); |
|
mib_stats = &priv->mib_stats[port * chip->num_mibs]; |
for (i = 0; i < chip->num_mibs; i++) { |
mib_name = chip->mib_decs[i].name; |
mib_data = mib_stats[i]; |
len += snprintf(buf + len, sizeof(priv->buf) - len, |
"%-12s: %llu\n", mib_name, mib_data); |
if ((!strcmp(mib_name, "TxByte") || |
!strcmp(mib_name, "RxGoodByte")) && |
mib_data >= 1024) { |
ar8xxx_byte_to_str(buf1, sizeof(buf1), mib_data); |
--len; /* discard newline at the end of buf */ |
len += snprintf(buf + len, sizeof(priv->buf) - len, |
" (%s)\n", buf1); |
} |
if (mib_stats_empty && mib_data) |
mib_stats_empty = false; |
} |
|
if (mib_stats_empty) |
len = snprintf(buf, sizeof(priv->buf), "No MIB data"); |
|
val->value.s = buf; |
val->len = len; |
|
ret = 0; |
|
unlock: |
mutex_unlock(&priv->mib_lock); |
return ret; |
} |
|
int |
ar8xxx_sw_set_arl_age_time(struct switch_dev *dev, const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); |
int age_time = val->value.i; |
u32 age_time_val; |
|
if (age_time < 0) |
return -EINVAL; |
|
age_time_val = ar8xxx_age_time_val(age_time); |
if (age_time_val == 0 || age_time_val > 0xffff) |
return -EINVAL; |
|
priv->arl_age_time = age_time; |
return 0; |
} |
|
int |
ar8xxx_sw_get_arl_age_time(struct switch_dev *dev, const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); |
val->value.i = priv->arl_age_time; |
return 0; |
} |
|
int |
ar8xxx_sw_get_arl_table(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); |
struct mii_bus *bus = priv->mii_bus; |
const struct ar8xxx_chip *chip = priv->chip; |
char *buf = priv->arl_buf; |
int i, j, k, len = 0; |
struct arl_entry *a, *a1; |
u32 status; |
|
if (!chip->get_arl_entry) |
return -EOPNOTSUPP; |
|
mutex_lock(&priv->reg_mutex); |
mutex_lock(&bus->mdio_lock); |
|
chip->get_arl_entry(priv, NULL, NULL, AR8XXX_ARL_INITIALIZE); |
|
for(i = 0; i < AR8XXX_NUM_ARL_RECORDS; ++i) { |
a = &priv->arl_table[i]; |
duplicate: |
chip->get_arl_entry(priv, a, &status, AR8XXX_ARL_GET_NEXT); |
|
if (!status) |
break; |
|
/* avoid duplicates |
* ARL table can include multiple valid entries |
* per MAC, just with differing status codes |
*/ |
for (j = 0; j < i; ++j) { |
a1 = &priv->arl_table[j]; |
if (!memcmp(a->mac, a1->mac, sizeof(a->mac))) { |
/* ignore ports already seen in former entry */ |
a->portmap &= ~a1->portmap; |
if (!a->portmap) |
goto duplicate; |
} |
} |
} |
|
mutex_unlock(&bus->mdio_lock); |
|
len += snprintf(buf + len, sizeof(priv->arl_buf) - len, |
"address resolution table\n"); |
|
if (i == AR8XXX_NUM_ARL_RECORDS) |
len += snprintf(buf + len, sizeof(priv->arl_buf) - len, |
"Too many entries found, displaying the first %d only!\n", |
AR8XXX_NUM_ARL_RECORDS); |
|
for (j = 0; j < priv->dev.ports; ++j) { |
for (k = 0; k < i; ++k) { |
a = &priv->arl_table[k]; |
if (!(a->portmap & BIT(j))) |
continue; |
len += snprintf(buf + len, sizeof(priv->arl_buf) - len, |
"Port %d: MAC %02x:%02x:%02x:%02x:%02x:%02x\n", |
j, |
a->mac[5], a->mac[4], a->mac[3], |
a->mac[2], a->mac[1], a->mac[0]); |
} |
} |
|
val->value.s = buf; |
val->len = len; |
|
mutex_unlock(&priv->reg_mutex); |
|
return 0; |
} |
|
int |
ar8xxx_sw_set_flush_arl_table(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); |
int ret; |
|
mutex_lock(&priv->reg_mutex); |
ret = priv->chip->atu_flush(priv); |
mutex_unlock(&priv->reg_mutex); |
|
return ret; |
} |
|
int |
ar8xxx_sw_set_flush_port_arl_table(struct switch_dev *dev, |
const struct switch_attr *attr, |
struct switch_val *val) |
{ |
struct ar8xxx_priv *priv = swdev_to_ar8xxx(dev); |
int port, ret; |
|
port = val->port_vlan; |
if (port >= dev->ports) |
return -EINVAL; |
|
mutex_lock(&priv->reg_mutex); |
ret = priv->chip->atu_flush_port(priv, port); |
mutex_unlock(&priv->reg_mutex); |
|
return ret; |
} |
|
static const struct switch_attr ar8xxx_sw_attr_globals[] = { |
{ |
.type = SWITCH_TYPE_INT, |
.name = "enable_vlan", |
.description = "Enable VLAN mode", |
.set = ar8xxx_sw_set_vlan, |
.get = ar8xxx_sw_get_vlan, |
.max = 1 |
}, |
{ |
.type = SWITCH_TYPE_NOVAL, |
.name = "reset_mibs", |
.description = "Reset all MIB counters", |
.set = ar8xxx_sw_set_reset_mibs, |
}, |
{ |
.type = SWITCH_TYPE_INT, |
.name = "enable_mirror_rx", |
.description = "Enable mirroring of RX packets", |
.set = ar8xxx_sw_set_mirror_rx_enable, |
.get = ar8xxx_sw_get_mirror_rx_enable, |
.max = 1 |
}, |
{ |
.type = SWITCH_TYPE_INT, |
.name = "enable_mirror_tx", |
.description = "Enable mirroring of TX packets", |
.set = ar8xxx_sw_set_mirror_tx_enable, |
.get = ar8xxx_sw_get_mirror_tx_enable, |
.max = 1 |
}, |
{ |
.type = SWITCH_TYPE_INT, |
.name = "mirror_monitor_port", |
.description = "Mirror monitor port", |
.set = ar8xxx_sw_set_mirror_monitor_port, |
.get = ar8xxx_sw_get_mirror_monitor_port, |
.max = AR8216_NUM_PORTS - 1 |
}, |
{ |
.type = SWITCH_TYPE_INT, |
.name = "mirror_source_port", |
.description = "Mirror source port", |
.set = ar8xxx_sw_set_mirror_source_port, |
.get = ar8xxx_sw_get_mirror_source_port, |
.max = AR8216_NUM_PORTS - 1 |
}, |
{ |
.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, |
}, |
}; |
|
const struct switch_attr ar8xxx_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_NOVAL, |
.name = "flush_arl_table", |
.description = "Flush port's ARL table entries", |
.set = ar8xxx_sw_set_flush_port_arl_table, |
}, |
}; |
|
const struct switch_attr ar8xxx_sw_attr_vlan[1] = { |
{ |
.type = SWITCH_TYPE_INT, |
.name = "vid", |
.description = "VLAN ID (0-4094)", |
.set = ar8xxx_sw_set_vid, |
.get = ar8xxx_sw_get_vid, |
.max = 4094, |
}, |
}; |
|
static const struct switch_dev_ops ar8xxx_sw_ops = { |
.attr_global = { |
.attr = ar8xxx_sw_attr_globals, |
.n_attr = ARRAY_SIZE(ar8xxx_sw_attr_globals), |
}, |
.attr_port = { |
.attr = ar8xxx_sw_attr_port, |
.n_attr = ARRAY_SIZE(ar8xxx_sw_attr_port), |
}, |
.attr_vlan = { |
.attr = ar8xxx_sw_attr_vlan, |
.n_attr = ARRAY_SIZE(ar8xxx_sw_attr_vlan), |
}, |
.get_port_pvid = ar8xxx_sw_get_pvid, |
.set_port_pvid = ar8xxx_sw_set_pvid, |
.get_vlan_ports = ar8xxx_sw_get_ports, |
.set_vlan_ports = ar8xxx_sw_set_ports, |
.apply_config = ar8xxx_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 |
}; |
|
static const struct ar8xxx_chip ar8216_chip = { |
.caps = AR8XXX_CAP_MIB_COUNTERS, |
|
.reg_port_stats_start = 0x19000, |
.reg_port_stats_length = 0xa0, |
.reg_arl_ctrl = AR8216_REG_ATU_CTRL, |
|
.name = "Atheros AR8216", |
.ports = AR8216_NUM_PORTS, |
.vlans = AR8216_NUM_VLANS, |
.swops = &ar8xxx_sw_ops, |
|
.hw_init = ar8216_hw_init, |
.init_globals = ar8216_init_globals, |
.init_port = ar8216_init_port, |
.setup_port = ar8216_setup_port, |
.read_port_status = ar8216_read_port_status, |
.atu_flush = ar8216_atu_flush, |
.atu_flush_port = ar8216_atu_flush_port, |
.vtu_flush = ar8216_vtu_flush, |
.vtu_load_vlan = ar8216_vtu_load_vlan, |
.set_mirror_regs = ar8216_set_mirror_regs, |
.get_arl_entry = ar8216_get_arl_entry, |
.sw_hw_apply = ar8xxx_sw_hw_apply, |
|
.num_mibs = ARRAY_SIZE(ar8216_mibs), |
.mib_decs = ar8216_mibs, |
.mib_func = AR8216_REG_MIB_FUNC |
}; |
|
static const struct ar8xxx_chip ar8236_chip = { |
.caps = AR8XXX_CAP_MIB_COUNTERS, |
|
.reg_port_stats_start = 0x20000, |
.reg_port_stats_length = 0x100, |
.reg_arl_ctrl = AR8216_REG_ATU_CTRL, |
|
.name = "Atheros AR8236", |
.ports = AR8216_NUM_PORTS, |
.vlans = AR8216_NUM_VLANS, |
.swops = &ar8xxx_sw_ops, |
|
.hw_init = ar8216_hw_init, |
.init_globals = ar8236_init_globals, |
.init_port = ar8216_init_port, |
.setup_port = ar8236_setup_port, |
.read_port_status = ar8216_read_port_status, |
.atu_flush = ar8216_atu_flush, |
.atu_flush_port = ar8216_atu_flush_port, |
.vtu_flush = ar8216_vtu_flush, |
.vtu_load_vlan = ar8216_vtu_load_vlan, |
.set_mirror_regs = ar8216_set_mirror_regs, |
.get_arl_entry = ar8216_get_arl_entry, |
.sw_hw_apply = ar8xxx_sw_hw_apply, |
|
.num_mibs = ARRAY_SIZE(ar8236_mibs), |
.mib_decs = ar8236_mibs, |
.mib_func = AR8216_REG_MIB_FUNC |
}; |
|
static const struct ar8xxx_chip ar8316_chip = { |
.caps = AR8XXX_CAP_GIGE | AR8XXX_CAP_MIB_COUNTERS, |
|
.reg_port_stats_start = 0x20000, |
.reg_port_stats_length = 0x100, |
.reg_arl_ctrl = AR8216_REG_ATU_CTRL, |
|
.name = "Atheros AR8316", |
.ports = AR8216_NUM_PORTS, |
.vlans = AR8X16_MAX_VLANS, |
.swops = &ar8xxx_sw_ops, |
|
.hw_init = ar8316_hw_init, |
.init_globals = ar8316_init_globals, |
.init_port = ar8216_init_port, |
.setup_port = ar8216_setup_port, |
.read_port_status = ar8216_read_port_status, |
.atu_flush = ar8216_atu_flush, |
.atu_flush_port = ar8216_atu_flush_port, |
.vtu_flush = ar8216_vtu_flush, |
.vtu_load_vlan = ar8216_vtu_load_vlan, |
.set_mirror_regs = ar8216_set_mirror_regs, |
.get_arl_entry = ar8216_get_arl_entry, |
.sw_hw_apply = ar8xxx_sw_hw_apply, |
|
.num_mibs = ARRAY_SIZE(ar8236_mibs), |
.mib_decs = ar8236_mibs, |
.mib_func = AR8216_REG_MIB_FUNC |
}; |
|
static int |
ar8xxx_id_chip(struct ar8xxx_priv *priv) |
{ |
u32 val; |
u16 id; |
int i; |
|
val = ar8xxx_read(priv, AR8216_REG_CTRL); |
if (val == ~0) |
return -ENODEV; |
|
id = val & (AR8216_CTRL_REVISION | AR8216_CTRL_VERSION); |
for (i = 0; i < AR8X16_PROBE_RETRIES; i++) { |
u16 t; |
|
val = ar8xxx_read(priv, AR8216_REG_CTRL); |
if (val == ~0) |
return -ENODEV; |
|
t = val & (AR8216_CTRL_REVISION | AR8216_CTRL_VERSION); |
if (t != id) |
return -ENODEV; |
} |
|
priv->chip_ver = (id & AR8216_CTRL_VERSION) >> AR8216_CTRL_VERSION_S; |
priv->chip_rev = (id & AR8216_CTRL_REVISION); |
|
switch (priv->chip_ver) { |
case AR8XXX_VER_AR8216: |
priv->chip = &ar8216_chip; |
break; |
case AR8XXX_VER_AR8236: |
priv->chip = &ar8236_chip; |
break; |
case AR8XXX_VER_AR8316: |
priv->chip = &ar8316_chip; |
break; |
case AR8XXX_VER_AR8327: |
priv->chip = &ar8327_chip; |
break; |
case AR8XXX_VER_AR8337: |
priv->chip = &ar8337_chip; |
break; |
default: |
pr_err("ar8216: Unknown Atheros device [ver=%d, rev=%d]\n", |
priv->chip_ver, priv->chip_rev); |
|
return -ENODEV; |
} |
|
return 0; |
} |
|
static void |
ar8xxx_mib_work_func(struct work_struct *work) |
{ |
struct ar8xxx_priv *priv; |
int err; |
|
priv = container_of(work, struct ar8xxx_priv, mib_work.work); |
|
mutex_lock(&priv->mib_lock); |
|
err = ar8xxx_mib_capture(priv); |
if (err) |
goto next_port; |
|
ar8xxx_mib_fetch_port_stat(priv, priv->mib_next_port, false); |
|
next_port: |
priv->mib_next_port++; |
if (priv->mib_next_port >= priv->dev.ports) |
priv->mib_next_port = 0; |
|
mutex_unlock(&priv->mib_lock); |
schedule_delayed_work(&priv->mib_work, |
msecs_to_jiffies(AR8XXX_MIB_WORK_DELAY)); |
} |
|
static int |
ar8xxx_mib_init(struct ar8xxx_priv *priv) |
{ |
unsigned int len; |
|
if (!ar8xxx_has_mib_counters(priv)) |
return 0; |
|
BUG_ON(!priv->chip->mib_decs || !priv->chip->num_mibs); |
|
len = priv->dev.ports * priv->chip->num_mibs * |
sizeof(*priv->mib_stats); |
priv->mib_stats = kzalloc(len, GFP_KERNEL); |
|
if (!priv->mib_stats) |
return -ENOMEM; |
|
return 0; |
} |
|
static void |
ar8xxx_mib_start(struct ar8xxx_priv *priv) |
{ |
if (!ar8xxx_has_mib_counters(priv)) |
return; |
|
schedule_delayed_work(&priv->mib_work, |
msecs_to_jiffies(AR8XXX_MIB_WORK_DELAY)); |
} |
|
static void |
ar8xxx_mib_stop(struct ar8xxx_priv *priv) |
{ |
if (!ar8xxx_has_mib_counters(priv)) |
return; |
|
cancel_delayed_work_sync(&priv->mib_work); |
} |
|
static struct ar8xxx_priv * |
ar8xxx_create(void) |
{ |
struct ar8xxx_priv *priv; |
|
priv = kzalloc(sizeof(struct ar8xxx_priv), GFP_KERNEL); |
if (priv == NULL) |
return NULL; |
|
mutex_init(&priv->reg_mutex); |
mutex_init(&priv->mib_lock); |
INIT_DELAYED_WORK(&priv->mib_work, ar8xxx_mib_work_func); |
|
return priv; |
} |
|
static void |
ar8xxx_free(struct ar8xxx_priv *priv) |
{ |
if (priv->chip && priv->chip->cleanup) |
priv->chip->cleanup(priv); |
|
kfree(priv->chip_data); |
kfree(priv->mib_stats); |
kfree(priv); |
} |
|
static int |
ar8xxx_probe_switch(struct ar8xxx_priv *priv) |
{ |
const struct ar8xxx_chip *chip; |
struct switch_dev *swdev; |
int ret; |
|
ret = ar8xxx_id_chip(priv); |
if (ret) |
return ret; |
|
chip = priv->chip; |
|
swdev = &priv->dev; |
swdev->cpu_port = AR8216_PORT_CPU; |
swdev->name = chip->name; |
swdev->vlans = chip->vlans; |
swdev->ports = chip->ports; |
swdev->ops = chip->swops; |
|
ret = ar8xxx_mib_init(priv); |
if (ret) |
return ret; |
|
return 0; |
} |
|
static int |
ar8xxx_start(struct ar8xxx_priv *priv) |
{ |
int ret; |
|
priv->init = true; |
|
ret = priv->chip->hw_init(priv); |
if (ret) |
return ret; |
|
ret = ar8xxx_sw_reset_switch(&priv->dev); |
if (ret) |
return ret; |
|
priv->init = false; |
|
ar8xxx_mib_start(priv); |
|
return 0; |
} |
|
static int |
ar8xxx_phy_config_init(struct phy_device *phydev) |
{ |
struct ar8xxx_priv *priv = phydev->priv; |
struct net_device *dev = phydev->attached_dev; |
int ret; |
|
if (WARN_ON(!priv)) |
return -ENODEV; |
|
if (priv->chip->config_at_probe) |
return ar8xxx_phy_check_aneg(phydev); |
|
priv->phy = phydev; |
|
if (phydev->mdio.addr != 0) { |
if (chip_is_ar8316(priv)) { |
/* switch device has been initialized, reinit */ |
priv->dev.ports = (AR8216_NUM_PORTS - 1); |
priv->initialized = false; |
priv->port4_phy = true; |
ar8316_hw_init(priv); |
return 0; |
} |
|
return 0; |
} |
|
ret = ar8xxx_start(priv); |
if (ret) |
return ret; |
|
/* VID fixup only needed on ar8216 */ |
if (chip_is_ar8216(priv)) { |
dev->phy_ptr = priv; |
dev->priv_flags |= IFF_NO_IP_ALIGN; |
dev->eth_mangle_rx = ar8216_mangle_rx; |
dev->eth_mangle_tx = ar8216_mangle_tx; |
} |
|
return 0; |
} |
|
static bool |
ar8xxx_check_link_states(struct ar8xxx_priv *priv) |
{ |
bool link_new, changed = false; |
u32 status; |
int i; |
|
mutex_lock(&priv->reg_mutex); |
|
for (i = 0; i < priv->dev.ports; i++) { |
status = priv->chip->read_port_status(priv, i); |
link_new = !!(status & AR8216_PORT_STATUS_LINK_UP); |
if (link_new == priv->link_up[i]) |
continue; |
|
priv->link_up[i] = link_new; |
changed = true; |
/* flush ARL entries for this port if it went down*/ |
if (!link_new) |
priv->chip->atu_flush_port(priv, i); |
dev_info(&priv->phy->mdio.dev, "Port %d is %s\n", |
i, link_new ? "up" : "down"); |
} |
|
mutex_unlock(&priv->reg_mutex); |
|
return changed; |
} |
|
static int |
ar8xxx_phy_read_status(struct phy_device *phydev) |
{ |
struct ar8xxx_priv *priv = phydev->priv; |
struct switch_port_link link; |
|
/* check for switch port link changes */ |
if (phydev->state == PHY_CHANGELINK) |
ar8xxx_check_link_states(priv); |
|
if (phydev->mdio.addr != 0) |
return genphy_read_status(phydev); |
|
ar8216_read_port_link(priv, phydev->mdio.addr, &link); |
phydev->link = !!link.link; |
if (!phydev->link) |
return 0; |
|
switch (link.speed) { |
case SWITCH_PORT_SPEED_10: |
phydev->speed = SPEED_10; |
break; |
case SWITCH_PORT_SPEED_100: |
phydev->speed = SPEED_100; |
break; |
case SWITCH_PORT_SPEED_1000: |
phydev->speed = SPEED_1000; |
break; |
default: |
phydev->speed = 0; |
} |
phydev->duplex = link.duplex ? DUPLEX_FULL : DUPLEX_HALF; |
|
phydev->state = PHY_RUNNING; |
netif_carrier_on(phydev->attached_dev); |
if (phydev->adjust_link) |
phydev->adjust_link(phydev->attached_dev); |
|
return 0; |
} |
|
static int |
ar8xxx_phy_config_aneg(struct phy_device *phydev) |
{ |
if (phydev->mdio.addr == 0) |
return 0; |
|
return genphy_config_aneg(phydev); |
} |
|
static const u32 ar8xxx_phy_ids[] = { |
0x004dd033, |
0x004dd034, /* AR8327 */ |
0x004dd036, /* AR8337 */ |
0x004dd041, |
0x004dd042, |
0x004dd043, /* AR8236 */ |
}; |
|
static bool |
ar8xxx_phy_match(u32 phy_id) |
{ |
int i; |
|
for (i = 0; i < ARRAY_SIZE(ar8xxx_phy_ids); i++) |
if (phy_id == ar8xxx_phy_ids[i]) |
return true; |
|
return false; |
} |
|
static bool |
ar8xxx_is_possible(struct mii_bus *bus) |
{ |
unsigned int i, found_phys = 0; |
|
for (i = 0; i < 5; i++) { |
u32 phy_id; |
|
phy_id = mdiobus_read(bus, i, MII_PHYSID1) << 16; |
phy_id |= mdiobus_read(bus, i, MII_PHYSID2); |
if (ar8xxx_phy_match(phy_id)) { |
found_phys++; |
} else if (phy_id) { |
pr_debug("ar8xxx: unknown PHY at %s:%02x id:%08x\n", |
dev_name(&bus->dev), i, phy_id); |
} |
} |
return !!found_phys; |
} |
|
static int |
ar8xxx_phy_probe(struct phy_device *phydev) |
{ |
struct ar8xxx_priv *priv; |
struct switch_dev *swdev; |
int ret; |
|
/* skip PHYs at unused adresses */ |
if (phydev->mdio.addr != 0 && phydev->mdio.addr != 3 && phydev->mdio.addr != 4) |
return -ENODEV; |
|
if (!ar8xxx_is_possible(phydev->mdio.bus)) |
return -ENODEV; |
|
mutex_lock(&ar8xxx_dev_list_lock); |
list_for_each_entry(priv, &ar8xxx_dev_list, list) |
if (priv->mii_bus == phydev->mdio.bus) |
goto found; |
|
priv = ar8xxx_create(); |
if (priv == NULL) { |
ret = -ENOMEM; |
goto unlock; |
} |
|
priv->mii_bus = phydev->mdio.bus; |
|
ret = ar8xxx_probe_switch(priv); |
if (ret) |
goto free_priv; |
|
swdev = &priv->dev; |
swdev->alias = dev_name(&priv->mii_bus->dev); |
ret = register_switch(swdev, NULL); |
if (ret) |
goto free_priv; |
|
pr_info("%s: %s rev. %u switch registered on %s\n", |
swdev->devname, swdev->name, priv->chip_rev, |
dev_name(&priv->mii_bus->dev)); |
|
list_add(&priv->list, &ar8xxx_dev_list); |
|
found: |
priv->use_count++; |
|
if (phydev->mdio.addr == 0) { |
if (ar8xxx_has_gige(priv)) { |
phydev->supported = SUPPORTED_1000baseT_Full; |
phydev->advertising = ADVERTISED_1000baseT_Full; |
} else { |
phydev->supported = SUPPORTED_100baseT_Full; |
phydev->advertising = ADVERTISED_100baseT_Full; |
} |
|
if (priv->chip->config_at_probe) { |
priv->phy = phydev; |
|
ret = ar8xxx_start(priv); |
if (ret) |
goto err_unregister_switch; |
} |
} else { |
if (ar8xxx_has_gige(priv)) { |
phydev->supported |= SUPPORTED_1000baseT_Full; |
phydev->advertising |= ADVERTISED_1000baseT_Full; |
} |
if (priv->chip->phy_rgmii_set) |
priv->chip->phy_rgmii_set(priv, phydev); |
} |
|
phydev->priv = priv; |
|
mutex_unlock(&ar8xxx_dev_list_lock); |
|
return 0; |
|
err_unregister_switch: |
if (--priv->use_count) |
goto unlock; |
|
unregister_switch(&priv->dev); |
|
free_priv: |
ar8xxx_free(priv); |
unlock: |
mutex_unlock(&ar8xxx_dev_list_lock); |
return ret; |
} |
|
static void |
ar8xxx_phy_detach(struct phy_device *phydev) |
{ |
struct net_device *dev = phydev->attached_dev; |
|
if (!dev) |
return; |
|
dev->phy_ptr = NULL; |
dev->priv_flags &= ~IFF_NO_IP_ALIGN; |
dev->eth_mangle_rx = NULL; |
dev->eth_mangle_tx = NULL; |
} |
|
static void |
ar8xxx_phy_remove(struct phy_device *phydev) |
{ |
struct ar8xxx_priv *priv = phydev->priv; |
|
if (WARN_ON(!priv)) |
return; |
|
phydev->priv = NULL; |
|
mutex_lock(&ar8xxx_dev_list_lock); |
|
if (--priv->use_count > 0) { |
mutex_unlock(&ar8xxx_dev_list_lock); |
return; |
} |
|
list_del(&priv->list); |
mutex_unlock(&ar8xxx_dev_list_lock); |
|
unregister_switch(&priv->dev); |
ar8xxx_mib_stop(priv); |
ar8xxx_free(priv); |
} |
|
static int |
ar8xxx_phy_soft_reset(struct phy_device *phydev) |
{ |
/* we don't need an extra reset */ |
return 0; |
} |
|
static struct phy_driver ar8xxx_phy_driver[] = { |
{ |
.phy_id = 0x004d0000, |
.name = "Atheros AR8216/AR8236/AR8316", |
.phy_id_mask = 0xffff0000, |
.features = PHY_BASIC_FEATURES, |
.probe = ar8xxx_phy_probe, |
.remove = ar8xxx_phy_remove, |
.detach = ar8xxx_phy_detach, |
.config_init = ar8xxx_phy_config_init, |
.config_aneg = ar8xxx_phy_config_aneg, |
.read_status = ar8xxx_phy_read_status, |
.soft_reset = ar8xxx_phy_soft_reset, |
} |
}; |
|
module_phy_driver(ar8xxx_phy_driver); |
MODULE_LICENSE("GPL"); |