Commit bf9ce385 authored by Oleksij Rempel's avatar Oleksij Rempel Committed by Jakub Kicinski

net: dsa: qca: ar9331: export stats64

Add stats support for the ar9331 switch.
Signed-off-by: default avatarOleksij Rempel <o.rempel@pengutronix.de>
Signed-off-by: default avatarJakub Kicinski <kuba@kernel.org>
parent c2ec5f2e
......@@ -101,6 +101,9 @@
AR9331_SW_PORT_STATUS_RX_FLOW_EN | AR9331_SW_PORT_STATUS_TX_FLOW_EN | \
AR9331_SW_PORT_STATUS_SPEED_M)
/* MIB registers */
#define AR9331_MIB_COUNTER(x) (0x20000 + ((x) * 0x100))
/* Phy bypass mode
* ------------------------------------------------------------------------
* Bit: | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |10 |11 |12 |13 |14 |15 |
......@@ -154,6 +157,66 @@
#define AR9331_SW_MDIO_POLL_SLEEP_US 1
#define AR9331_SW_MDIO_POLL_TIMEOUT_US 20
/* The interval should be small enough to avoid overflow of 32bit MIBs */
/*
* FIXME: until we can read MIBs from stats64 call directly (i.e. sleep
* there), we have to poll stats more frequently then it is actually needed.
* For overflow protection, normally, 100 sec interval should have been OK.
*/
#define STATS_INTERVAL_JIFFIES (3 * HZ)
struct ar9331_sw_stats_raw {
u32 rxbroad; /* 0x00 */
u32 rxpause; /* 0x04 */
u32 rxmulti; /* 0x08 */
u32 rxfcserr; /* 0x0c */
u32 rxalignerr; /* 0x10 */
u32 rxrunt; /* 0x14 */
u32 rxfragment; /* 0x18 */
u32 rx64byte; /* 0x1c */
u32 rx128byte; /* 0x20 */
u32 rx256byte; /* 0x24 */
u32 rx512byte; /* 0x28 */
u32 rx1024byte; /* 0x2c */
u32 rx1518byte; /* 0x30 */
u32 rxmaxbyte; /* 0x34 */
u32 rxtoolong; /* 0x38 */
u32 rxgoodbyte; /* 0x3c */
u32 rxgoodbyte_hi;
u32 rxbadbyte; /* 0x44 */
u32 rxbadbyte_hi;
u32 rxoverflow; /* 0x4c */
u32 filtered; /* 0x50 */
u32 txbroad; /* 0x54 */
u32 txpause; /* 0x58 */
u32 txmulti; /* 0x5c */
u32 txunderrun; /* 0x60 */
u32 tx64byte; /* 0x64 */
u32 tx128byte; /* 0x68 */
u32 tx256byte; /* 0x6c */
u32 tx512byte; /* 0x70 */
u32 tx1024byte; /* 0x74 */
u32 tx1518byte; /* 0x78 */
u32 txmaxbyte; /* 0x7c */
u32 txoversize; /* 0x80 */
u32 txbyte; /* 0x84 */
u32 txbyte_hi;
u32 txcollision; /* 0x8c */
u32 txabortcol; /* 0x90 */
u32 txmulticol; /* 0x94 */
u32 txsinglecol; /* 0x98 */
u32 txexcdefer; /* 0x9c */
u32 txdefer; /* 0xa0 */
u32 txlatecol; /* 0xa4 */
};
struct ar9331_sw_port {
int idx;
struct delayed_work mib_read;
struct rtnl_link_stats64 stats;
struct spinlock stats_lock;
};
struct ar9331_sw_priv {
struct device *dev;
struct dsa_switch ds;
......@@ -165,8 +228,17 @@ struct ar9331_sw_priv {
struct mii_bus *sbus; /* mdio slave */
struct regmap *regmap;
struct reset_control *sw_reset;
struct ar9331_sw_port port[AR9331_SW_PORTS];
};
static struct ar9331_sw_priv *ar9331_sw_port_to_priv(struct ar9331_sw_port *port)
{
struct ar9331_sw_port *p = port - port->idx;
return (struct ar9331_sw_priv *)((void *)p -
offsetof(struct ar9331_sw_priv, port));
}
/* Warning: switch reset will reset last AR9331_SW_MDIO_PHY_MODE_PAGE request
* If some kind of optimization is used, the request should be repeated.
*/
......@@ -424,6 +496,7 @@ static void ar9331_sw_phylink_mac_link_down(struct dsa_switch *ds, int port,
phy_interface_t interface)
{
struct ar9331_sw_priv *priv = (struct ar9331_sw_priv *)ds->priv;
struct ar9331_sw_port *p = &priv->port[port];
struct regmap *regmap = priv->regmap;
int ret;
......@@ -431,6 +504,8 @@ static void ar9331_sw_phylink_mac_link_down(struct dsa_switch *ds, int port,
AR9331_SW_PORT_STATUS_MAC_MASK, 0);
if (ret)
dev_err_ratelimited(priv->dev, "%s: %i\n", __func__, ret);
cancel_delayed_work_sync(&p->mib_read);
}
static void ar9331_sw_phylink_mac_link_up(struct dsa_switch *ds, int port,
......@@ -441,10 +516,13 @@ static void ar9331_sw_phylink_mac_link_up(struct dsa_switch *ds, int port,
bool tx_pause, bool rx_pause)
{
struct ar9331_sw_priv *priv = (struct ar9331_sw_priv *)ds->priv;
struct ar9331_sw_port *p = &priv->port[port];
struct regmap *regmap = priv->regmap;
u32 val;
int ret;
schedule_delayed_work(&p->mib_read, 0);
val = AR9331_SW_PORT_STATUS_MAC_MASK;
switch (speed) {
case SPEED_1000:
......@@ -477,6 +555,73 @@ static void ar9331_sw_phylink_mac_link_up(struct dsa_switch *ds, int port,
dev_err_ratelimited(priv->dev, "%s: %i\n", __func__, ret);
}
static void ar9331_read_stats(struct ar9331_sw_port *port)
{
struct ar9331_sw_priv *priv = ar9331_sw_port_to_priv(port);
struct rtnl_link_stats64 *stats = &port->stats;
struct ar9331_sw_stats_raw raw;
int ret;
/* Do the slowest part first, to avoid needless locking for long time */
ret = regmap_bulk_read(priv->regmap, AR9331_MIB_COUNTER(port->idx),
&raw, sizeof(raw) / sizeof(u32));
if (ret) {
dev_err_ratelimited(priv->dev, "%s: %i\n", __func__, ret);
return;
}
/* All MIB counters are cleared automatically on read */
spin_lock(&port->stats_lock);
stats->rx_bytes += raw.rxgoodbyte;
stats->tx_bytes += raw.txbyte;
stats->rx_packets += raw.rx64byte + raw.rx128byte + raw.rx256byte +
raw.rx512byte + raw.rx1024byte + raw.rx1518byte + raw.rxmaxbyte;
stats->tx_packets += raw.tx64byte + raw.tx128byte + raw.tx256byte +
raw.tx512byte + raw.tx1024byte + raw.tx1518byte + raw.txmaxbyte;
stats->rx_length_errors += raw.rxrunt + raw.rxfragment + raw.rxtoolong;
stats->rx_crc_errors += raw.rxfcserr;
stats->rx_frame_errors += raw.rxalignerr;
stats->rx_missed_errors += raw.rxoverflow;
stats->rx_dropped += raw.filtered;
stats->rx_errors += raw.rxfcserr + raw.rxalignerr + raw.rxrunt +
raw.rxfragment + raw.rxoverflow + raw.rxtoolong;
stats->tx_window_errors += raw.txlatecol;
stats->tx_fifo_errors += raw.txunderrun;
stats->tx_aborted_errors += raw.txabortcol;
stats->tx_errors += raw.txoversize + raw.txabortcol + raw.txunderrun +
raw.txlatecol;
stats->multicast += raw.rxmulti;
stats->collisions += raw.txcollision;
spin_unlock(&port->stats_lock);
}
static void ar9331_do_stats_poll(struct work_struct *work)
{
struct ar9331_sw_port *port = container_of(work, struct ar9331_sw_port,
mib_read.work);
ar9331_read_stats(port);
schedule_delayed_work(&port->mib_read, STATS_INTERVAL_JIFFIES);
}
static void ar9331_get_stats64(struct dsa_switch *ds, int port,
struct rtnl_link_stats64 *s)
{
struct ar9331_sw_priv *priv = (struct ar9331_sw_priv *)ds->priv;
struct ar9331_sw_port *p = &priv->port[port];
spin_lock(&p->stats_lock);
memcpy(s, &p->stats, sizeof(*s));
spin_unlock(&p->stats_lock);
}
static const struct dsa_switch_ops ar9331_sw_ops = {
.get_tag_protocol = ar9331_sw_get_tag_protocol,
.setup = ar9331_sw_setup,
......@@ -485,6 +630,7 @@ static const struct dsa_switch_ops ar9331_sw_ops = {
.phylink_mac_config = ar9331_sw_phylink_mac_config,
.phylink_mac_link_down = ar9331_sw_phylink_mac_link_down,
.phylink_mac_link_up = ar9331_sw_phylink_mac_link_up,
.get_stats64 = ar9331_get_stats64,
};
static irqreturn_t ar9331_sw_irq(int irq, void *data)
......@@ -796,7 +942,7 @@ static int ar9331_sw_probe(struct mdio_device *mdiodev)
{
struct ar9331_sw_priv *priv;
struct dsa_switch *ds;
int ret;
int ret, i;
priv = devm_kzalloc(&mdiodev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
......@@ -831,6 +977,14 @@ static int ar9331_sw_probe(struct mdio_device *mdiodev)
ds->ops = &priv->ops;
dev_set_drvdata(&mdiodev->dev, priv);
for (i = 0; i < ARRAY_SIZE(priv->port); i++) {
struct ar9331_sw_port *port = &priv->port[i];
port->idx = i;
spin_lock_init(&port->stats_lock);
INIT_DELAYED_WORK(&port->mib_read, ar9331_do_stats_poll);
}
ret = dsa_register_switch(ds);
if (ret)
goto err_remove_irq;
......@@ -846,6 +1000,13 @@ static int ar9331_sw_probe(struct mdio_device *mdiodev)
static void ar9331_sw_remove(struct mdio_device *mdiodev)
{
struct ar9331_sw_priv *priv = dev_get_drvdata(&mdiodev->dev);
unsigned int i;
for (i = 0; i < ARRAY_SIZE(priv->port); i++) {
struct ar9331_sw_port *port = &priv->port[i];
cancel_delayed_work_sync(&port->mib_read);
}
irq_domain_remove(priv->irqdomain);
mdiobus_unregister(priv->mbus);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment