linux/generic: add MIB counters and port status to ADM6996 switch

This patch adds port status information and MIB counters to the ADM6996
switch driver.

The driver supports also the older ADM6996L-variant, but I'm not able to
test this patch on that chip. According to the datasheet the same
registers exist there as well, so I think it should work, but any
feedback is appreciated.

Signed-off-by: Matti Laakso <malaakso at elisanet.fi>

git-svn-id: svn://svn.openwrt.org/openwrt/trunk@40542 3c298f89-4303-0410-b956-a3cf2f4a3e73
master
John Crispin 2014-04-22 08:08:02 +00:00
parent 3e12489885
commit 4fb275842c
2 changed files with 144 additions and 0 deletions

View File

@ -6,6 +6,7 @@
* Copyright (c) 2008 Felix Fietkau <nbd@openwrt.org> * Copyright (c) 2008 Felix Fietkau <nbd@openwrt.org>
* VLAN support Copyright (c) 2010, 2011 Peter Lebbing <peter@digitalbrains.com> * VLAN support Copyright (c) 2010, 2011 Peter Lebbing <peter@digitalbrains.com>
* Copyright (c) 2013 Hauke Mehrtens <hauke@hauke-m.de> * Copyright (c) 2013 Hauke Mehrtens <hauke@hauke-m.de>
* Copyright (c) 2014 Matti Laakso <malaakso@elisanet.fi>
* *
* This program is free software; you can redistribute it and/or modify it * This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License v2 as published by the * under the terms of the GNU General Public License v2 as published by the
@ -54,6 +55,11 @@ static const char * const adm6996_model_name[] =
"ADM6996L" "ADM6996L"
}; };
struct adm6996_mib_desc {
unsigned int offset;
const char *name;
};
struct adm6996_priv { struct adm6996_priv {
struct switch_dev dev; struct switch_dev dev;
void *priv; void *priv;
@ -79,6 +85,9 @@ struct adm6996_priv {
u8 vlan_table[ADM_NUM_VLANS]; /* bitmap, 1 = port is member */ u8 vlan_table[ADM_NUM_VLANS]; /* bitmap, 1 = port is member */
u8 vlan_tagged[ADM_NUM_VLANS]; /* bitmap, 1 = tagged member */ u8 vlan_tagged[ADM_NUM_VLANS]; /* bitmap, 1 = tagged member */
struct mutex mib_lock;
char buf[2048];
struct mutex reg_mutex; struct mutex reg_mutex;
/* use abstraction for regops, we want to add gpio support in the future */ /* use abstraction for regops, we want to add gpio support in the future */
@ -89,6 +98,21 @@ struct adm6996_priv {
#define to_adm(_dev) container_of(_dev, struct adm6996_priv, dev) #define to_adm(_dev) container_of(_dev, struct adm6996_priv, dev)
#define phy_to_adm(_phy) ((struct adm6996_priv *) (_phy)->priv) #define phy_to_adm(_phy) ((struct adm6996_priv *) (_phy)->priv)
#define MIB_DESC(_o, _n) \
{ \
.offset = (_o), \
.name = (_n), \
}
static const struct adm6996_mib_desc adm6996_mibs[] = {
MIB_DESC(ADM_CL0, "RxPacket"),
MIB_DESC(ADM_CL6, "RxByte"),
MIB_DESC(ADM_CL12, "TxPacket"),
MIB_DESC(ADM_CL18, "TxByte"),
MIB_DESC(ADM_CL24, "Collision"),
MIB_DESC(ADM_CL30, "Error"),
};
static inline u16 static inline u16
r16(struct adm6996_priv *priv, enum admreg reg) r16(struct adm6996_priv *priv, enum admreg reg)
{ {
@ -773,6 +797,99 @@ adm6996_reset_switch(struct switch_dev *dev)
return 0; return 0;
} }
static int
adm6996_get_port_link(struct switch_dev *dev, int port,
struct switch_port_link *link)
{
struct adm6996_priv *priv = to_adm(dev);
u16 reg = 0;
u32 speed;
if (port >= ADM_NUM_PORTS)
return -EINVAL;
switch (port) {
case 0:
reg = r16(priv, ADM_PS0);
break;
case 1:
reg = r16(priv, ADM_PS0);
reg = reg >> 8;
break;
case 2:
reg = r16(priv, ADM_PS1);
break;
case 3:
reg = r16(priv, ADM_PS1);
reg = reg >> 8;
break;
case 4:
reg = r16(priv, ADM_PS1);
reg = reg >> 12;
break;
case 5:
reg = r16(priv, ADM_PS2);
/* Bits 0, 1, 3 and 4. */
reg = (reg & 3) | ((reg & 24) >> 1);
break;
default:
return -EINVAL;
}
link->link = reg & ADM_PS_LS;
if (!link->link)
return 0;
link->aneg = true;
link->duplex = reg & ADM_PS_DS;
link->tx_flow = reg & ADM_PS_FCS;
link->rx_flow = reg & ADM_PS_FCS;
if (reg & ADM_PS_SS)
link->speed = SWITCH_PORT_SPEED_100;
else
link->speed = SWITCH_PORT_SPEED_10;
return 0;
}
static int
adm6996_sw_get_port_mib(struct switch_dev *dev,
const struct switch_attr *attr,
struct switch_val *val)
{
struct adm6996_priv *priv = to_adm(dev);
int port;
char *buf = priv->buf;
int i, len = 0;
u32 reg = 0;
port = val->port_vlan;
if (port >= ADM_NUM_PORTS)
return -EINVAL;
mutex_lock(&priv->mib_lock);
len += snprintf(buf + len, sizeof(priv->buf) - len,
"Port %d MIB counters\n",
port);
for (i = 0; i < ARRAY_SIZE(adm6996_mibs); i++) {
reg = r16(priv, adm6996_mibs[i].offset + ADM_OFFSET_PORT(port));
reg += r16(priv, adm6996_mibs[i].offset + ADM_OFFSET_PORT(port) + 1) << 16;
len += snprintf(buf + len, sizeof(priv->buf) - len,
"%-12s: %lu\n",
adm6996_mibs[i].name,
reg);
}
mutex_unlock(&priv->mib_lock);
val->value.s = buf;
val->len = len;
return 0;
}
static struct switch_attr adm6996_globals[] = { static struct switch_attr adm6996_globals[] = {
{ {
.type = SWITCH_TYPE_INT, .type = SWITCH_TYPE_INT,
@ -802,6 +919,13 @@ static struct switch_attr adm6996_globals[] = {
}; };
static struct switch_attr adm6996_port[] = { static struct switch_attr adm6996_port[] = {
{
.type = SWITCH_TYPE_STRING,
.name = "mib",
.description = "Get port's MIB counters",
.set = NULL,
.get = adm6996_sw_get_port_mib,
},
}; };
static struct switch_attr adm6996_vlan[] = { static struct switch_attr adm6996_vlan[] = {
@ -833,6 +957,7 @@ static const struct switch_dev_ops adm6996_ops = {
.set_vlan_ports = adm6996_set_ports, .set_vlan_ports = adm6996_set_ports,
.apply_config = adm6996_hw_apply, .apply_config = adm6996_hw_apply,
.reset_switch = adm6996_reset_switch, .reset_switch = adm6996_reset_switch,
.get_port_link = adm6996_get_port_link,
}; };
static int adm6996_switch_init(struct adm6996_priv *priv, const char *alias, struct net_device *netdev) static int adm6996_switch_init(struct adm6996_priv *priv, const char *alias, struct net_device *netdev)
@ -899,6 +1024,7 @@ static int adm6996_config_init(struct phy_device *pdev)
return -ENOMEM; return -ENOMEM;
mutex_init(&priv->reg_mutex); mutex_init(&priv->reg_mutex);
mutex_init(&priv->mib_lock);
priv->priv = pdev; priv->priv = pdev;
priv->read = adm6996_read_mii_reg; priv->read = adm6996_read_mii_reg;
priv->write = adm6996_write_mii_reg; priv->write = adm6996_write_mii_reg;

View File

@ -50,6 +50,16 @@ enum admreg {
ADM_COUNTER_BASE = 0xa0, ADM_COUNTER_BASE = 0xa0,
ADM_SIG0 = ADM_COUNTER_BASE + 0, ADM_SIG0 = ADM_COUNTER_BASE + 0,
ADM_SIG1 = ADM_COUNTER_BASE + 1, ADM_SIG1 = ADM_COUNTER_BASE + 1,
ADM_PS0 = ADM_COUNTER_BASE + 2,
ADM_PS1 = ADM_COUNTER_BASE + 3,
ADM_PS2 = ADM_COUNTER_BASE + 4,
ADM_CL0 = ADM_COUNTER_BASE + 8, /* RxPacket */
ADM_CL6 = ADM_COUNTER_BASE + 0x1a, /* RxByte */
ADM_CL12 = ADM_COUNTER_BASE + 0x2c, /* TxPacket */
ADM_CL18 = ADM_COUNTER_BASE + 0x3e, /* TxByte */
ADM_CL24 = ADM_COUNTER_BASE + 0x50, /* Coll */
ADM_CL30 = ADM_COUNTER_BASE + 0x62, /* Err */
#define ADM_OFFSET_PORT(n) ((n * 4) - (n / 4) * 2 - (n / 5) * 2)
ADM_PHY_BASE = 0x200, ADM_PHY_BASE = 0x200,
#define ADM_PHY_PORT(n) (ADM_PHY_BASE + (0x20 * n)) #define ADM_PHY_PORT(n) (ADM_PHY_BASE + (0x20 * n))
}; };
@ -159,6 +169,14 @@ static const u8 adm_portcfg[] = {
((ports & 0x04) << 2) | ((ports & 0x08) << 3) | \ ((ports & 0x04) << 2) | ((ports & 0x08) << 3) | \
((ports & 0x10) << 3) | ((ports & 0x20) << 3)) ((ports & 0x10) << 3) | ((ports & 0x20) << 3))
/* Port status register */
enum {
ADM_PS_LS = (1 << 0), /* Link status */
ADM_PS_SS = (1 << 1), /* Speed status */
ADM_PS_DS = (1 << 2), /* Duplex status */
ADM_PS_FCS = (1 << 3) /* Flow control status */
};
/* /*
* Split the register address in phy id and register * Split the register address in phy id and register
* it will get combined again by the mdio bus op * it will get combined again by the mdio bus op