From 1b96cf30a91d98f3e84aac12b19d2a2bbbe5d402 Mon Sep 17 00:00:00 2001
From: John Crispin <john@openwrt.org>
Date: Tue, 22 Apr 2014 08:08:02 +0000
Subject: [PATCH] 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>

SVN-Revision: 40542
---
 .../generic/files/drivers/net/phy/adm6996.c   | 126 ++++++++++++++++++
 .../generic/files/drivers/net/phy/adm6996.h   |  18 +++
 2 files changed, 144 insertions(+)

diff --git a/target/linux/generic/files/drivers/net/phy/adm6996.c b/target/linux/generic/files/drivers/net/phy/adm6996.c
index ce48b528cc..7e47f3dc1a 100644
--- a/target/linux/generic/files/drivers/net/phy/adm6996.c
+++ b/target/linux/generic/files/drivers/net/phy/adm6996.c
@@ -6,6 +6,7 @@
  * Copyright (c) 2008 Felix Fietkau <nbd@openwrt.org>
  * VLAN support Copyright (c) 2010, 2011 Peter Lebbing <peter@digitalbrains.com>
  * 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
  * 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"
 };
 
+struct adm6996_mib_desc {
+	unsigned int offset;
+	const char *name;
+};
+
 struct adm6996_priv {
 	struct switch_dev dev;
 	void *priv;
@@ -78,6 +84,9 @@ struct adm6996_priv {
 	u16 vlan_id[ADM_NUM_VLANS];
 	u8 vlan_table[ADM_NUM_VLANS];	/* bitmap, 1 = port is member */
 	u8 vlan_tagged[ADM_NUM_VLANS];	/* bitmap, 1 = tagged member */
+	
+	struct mutex mib_lock;
+	char buf[2048];
 
 	struct mutex reg_mutex;
 
@@ -89,6 +98,21 @@ struct adm6996_priv {
 #define to_adm(_dev) container_of(_dev, struct adm6996_priv, dev)
 #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
 r16(struct adm6996_priv *priv, enum admreg reg)
 {
@@ -773,6 +797,99 @@ adm6996_reset_switch(struct switch_dev *dev)
 	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[] = {
 	{
 	 .type = SWITCH_TYPE_INT,
@@ -802,6 +919,13 @@ static struct switch_attr adm6996_globals[] = {
 };
 
 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[] = {
@@ -833,6 +957,7 @@ static const struct switch_dev_ops adm6996_ops = {
 	.set_vlan_ports = adm6996_set_ports,
 	.apply_config = adm6996_hw_apply,
 	.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)
@@ -899,6 +1024,7 @@ static int adm6996_config_init(struct phy_device *pdev)
 		return -ENOMEM;
 
 	mutex_init(&priv->reg_mutex);
+	mutex_init(&priv->mib_lock);
 	priv->priv = pdev;
 	priv->read = adm6996_read_mii_reg;
 	priv->write = adm6996_write_mii_reg;
diff --git a/target/linux/generic/files/drivers/net/phy/adm6996.h b/target/linux/generic/files/drivers/net/phy/adm6996.h
index b30eceafdd..66c77a0968 100644
--- a/target/linux/generic/files/drivers/net/phy/adm6996.h
+++ b/target/linux/generic/files/drivers/net/phy/adm6996.h
@@ -50,6 +50,16 @@ enum admreg {
 	ADM_COUNTER_BASE	= 0xa0,
 		ADM_SIG0		= ADM_COUNTER_BASE + 0,
 		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,
 #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 & 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
  * it will get combined again by the mdio bus op
-- 
2.30.2