On some ar71xx devices the MII register access is unreliable, so we need to
use special atheros packets for accessing the switch registers.
---
.../linux/generic/files/drivers/net/phy/ar8216.c | 222 +++++++++++++++++---
.../linux/generic/files/drivers/net/phy/ar8216.h | 26 +++
2 files changed, 218 insertions(+), 30 deletions(-)
diff --git a/target/linux/generic/files/drivers/net/phy/ar8216.c
b/target/linux/generic/files/drivers/net/phy/ar8216.c
index 587f41f..875196f 100644
--- a/target/linux/generic/files/drivers/net/phy/ar8216.c
+++ b/target/linux/generic/files/drivers/net/phy/ar8216.c
@@ -29,6 +29,8 @@
#include <linux/phy.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
+#include <linux/sched.h>
+#include <linux/wait.h>
#include "ar8216.h"
/* size of the vlan table */
@@ -46,6 +48,13 @@ struct ar8216_priv {
int chip;
bool initialized;
bool port4_phy;
+ bool hdr_reg_acc;
+
+ /* fields used for register access through packets */
+ wait_queue_head_t wait;
+ bool finished;
+ u32 seq;
+ u32 val;
/* all fields below are cleared on reset */
bool vlan;
@@ -103,6 +112,102 @@ ar8216_mii_write(struct ar8216_priv *priv, int reg, u32
val)
phy->bus->write(phy->bus, 0x10 | r2, r1, lo);
}
+static inline struct sk_buff *
+ar8216_create_rw_reg_skb(u8 *data, int seq, int cmd, u32 reg)
+{
+ struct sk_buff *skb = dev_alloc_skb(ETH_ZLEN + ETH_FCS_LEN);
+
+ if (!skb)
+ return ERR_PTR(-ENOMEM);
+
+ skb_put(skb, ETH_ZLEN);
+
+ skb->data[0] = AR8216_HDR_FROM_CPU | AR8216_HDR_VER_1;
+ skb->data[1] = AR8216_HDR_TYPE_RW_REG;
+
+ skb->data[2] = reg & 0xff;
+ skb->data[3] = (reg >> 8) & 0xff;
+ skb->data[4] = (reg >> 16) & 0x3;
+ skb->data[4] |= (4 << AR8216_HDR_DATA_LEN_S);
+ skb->data[5] = cmd | AR8216_HDR_CHECK_CODE;
+
+ skb->data[6] = (seq & 0x7f) << 1;
+ skb->data[7] = (seq >> 7) & 0xff;
+ skb->data[8] = (seq >> 15) & 0xff;
+ skb->data[9] = (seq >> 23) & 0xff;
+
+ return skb;
+}
+
+static u32
+ar8216_header_read(struct ar8216_priv *priv, int reg)
+{
+ struct sk_buff *skb;
+ int ret;
+
+ skb = ar8216_create_rw_reg_skb(skb->data, ++priv->seq,
+ AR8216_HDR_READ_REG, reg);
+
+ if (IS_ERR(skb)) {
+ pr_err("skb allocation failed: %li\n", PTR_ERR(skb));
+ return ~0;
+ }
+
+ skb->dev = priv->phy->attached_dev;
+
+ priv->finished = 0;
+
+ ret = priv->ndo_old->ndo_start_xmit(skb, priv->phy->attached_dev);
+
+ if (ret) {
+ pr_err("xmit failed: %d\n", ret);
+ return ~0;
+ }
+
+ if (!wait_event_timeout(priv->wait, priv->finished == 1,
+ msecs_to_jiffies(1000))) {
+ pr_err("reading register 0x%08x timed out!\n", reg);
+ return ~0;
+ }
+
+ return priv->val;
+}
+
+static void
+ar8216_header_write(struct ar8216_priv *priv, int reg, u32 val)
+{
+ struct sk_buff *skb;
+ int ret;
+ u32 tmp;
+
+ skb = ar8216_create_rw_reg_skb(skb->data, ++priv->seq,
+ AR8216_HDR_WRITE_REG, reg);
+
+ if (IS_ERR(skb)) {
+ pr_err("skb allocation failed: %li\n", PTR_ERR(skb));
+ return;
+ }
+
+
+ tmp = cpu_to_le32(val);
+ memcpy(skb->data + 10, &tmp, sizeof(u32));
+
+ skb->dev = priv->phy->attached_dev;
+
+ priv->finished = 0;
+
+ ret = priv->ndo_old->ndo_start_xmit(skb, priv->phy->attached_dev);
+
+ if (ret) {
+ pr_err("xmit failed: %d\n", ret);
+ return;
+ }
+
+ if (!wait_event_timeout(priv->wait, priv->finished == 1,
+ msecs_to_jiffies(1000)))
+ pr_err("writing register %08x timed out!\n", reg);
+}
+
static u32
ar8216_rmw(struct ar8216_priv *priv, int reg, u32 mask, u32 val)
{
@@ -124,20 +229,28 @@ ar8216_id_chip(struct ar8216_priv *priv)
int i;
val = ar8216_mii_read(priv, AR8216_REG_CTRL);
- if (val == ~0)
- return UNKNOWN;
+
+ /* AR8216 connected with a too high frequency will fail on register
+ * accesses most of the time. In that case fall back to register access
+ * through special atheros packets.
+ */
id = val & (AR8216_CTRL_REVISION | AR8216_CTRL_VERSION);
+
for (i = 0; i < AR8X16_PROBE_RETRIES; i++) {
u16 t;
val = ar8216_mii_read(priv, AR8216_REG_CTRL);
- if (val == ~0)
- return UNKNOWN;
+ if (val == ~0) {
+ priv->hdr_reg_acc = true;
+ return AR8216;
+ }
t = val & (AR8216_CTRL_REVISION | AR8216_CTRL_VERSION);
- if (t != id)
- return UNKNOWN;
+ if (t != id) {
+ priv->hdr_reg_acc = true;
+ return AR8216;
+ }
}
switch (id) {
@@ -227,7 +340,7 @@ ar8216_mangle_tx(struct sk_buff *skb, struct net_device
*dev)
if (unlikely(!priv))
goto error;
- if (!priv->vlan)
+ if (!priv->vlan && !priv->hdr_reg_acc)
goto send;
if (unlikely(skb_headroom(skb) < 2)) {
@@ -263,19 +376,50 @@ ar8216_mangle_rx(struct sk_buff *skb, int napi)
if (!priv)
goto error;
- /* don't strip the header if vlan mode is disabled */
- if (!priv->vlan)
+ /* don't strip the header if vlan mode is disabled and are using MII
+ * bus switch register access */
+ if (!priv->vlan && !priv->hdr_reg_acc)
goto recv;
- /* strip header, get vlan id */
buf = skb->data;
+
+ switch (buf[1] & AR8216_HDR_TYPE) {
+ case AR8216_HDR_TYPE_NORMAL:
+ break;
+ case AR8216_HDR_TYPE_RW_REG_ACK: {
+ /* Sanity check: make sure the sequence number is the same
+ * we sent. */
+
+ u32 seq = (buf[6] >> 1) | (buf[7] << 7) | (buf[8] << 15) |
+ (buf[9] << 23);
+
+ if (seq != (priv->seq & 0x7fffffff)) {
+ pr_warn("invalid sequence counter: expected 0x%08x, got
0x%08x\n",
+ priv->seq, seq);
+ } else {
+ if ((buf[4] >> AR8216_HDR_DATA_LEN_S) == 4)
+ priv->val = le32_to_cpu(*((u32 *)(buf + 10)));
+ priv->finished = 1;
+ wake_up(&priv->wait);
+ }
+
+ dev_kfree_skb_any(skb);
+ return NET_RX_SUCCESS;
+ }
+ default:
+ pr_err("ar8216: unexpected packet type: %d\n",
+ buf[1] & AR8216_HDR_TYPE);
+ goto error;
+ }
+
+ /* strip header, get vlan id */
skb_pull(skb, 2);
/* check for vlan header presence */
if ((buf[12 + 2] != 0x81) || (buf[13 + 2] != 0x00))
goto recv;
- port = buf[0] & 0xf;
+ port = buf[0] & AR8216_HDR_PORT;
/* no need to fix up packets coming from a tagged source */
if (priv->vlan_tagged & (1 << port))
@@ -472,6 +616,7 @@ ar8216_hw_apply(struct switch_dev *dev)
for (i = 0; i < AR8216_NUM_PORTS; i++) {
int egress, ingress;
int pvid;
+ u32 port_ctrl;
if (priv->vlan) {
pvid = priv->vlan_id[priv->pvid[i]];
@@ -490,15 +635,19 @@ ar8216_hw_apply(struct switch_dev *dev)
ingress = AR8216_IN_PORT_ONLY;
}
+ port_ctrl = AR8216_PORT_CTRL_LEARN |
+ (egress << AR8216_PORT_CTRL_VLAN_MODE_S) |
+ (AR8216_PORT_STATE_FORWARD << AR8216_PORT_CTRL_STATE_S);
+
+ if (i == AR8216_PORT_CPU && priv->chip == AR8216 &&
+ (priv->vlan || priv->hdr_reg_acc))
+ port_ctrl |= AR8216_PORT_CTRL_HEADER;
+
ar8216_rmw(priv, AR8216_REG_PORT_CTRL(i),
- 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 |
- (priv->vlan && i == AR8216_PORT_CPU && (priv->chip ==
AR8216) ?
- AR8216_PORT_CTRL_HEADER : 0) |
- (egress << AR8216_PORT_CTRL_VLAN_MODE_S) |
- (AR8216_PORT_STATE_FORWARD <<
AR8216_PORT_CTRL_STATE_S));
+ 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, port_ctrl);
ar8216_rmw(priv, AR8216_REG_PORT_VLAN(i),
AR8216_PORT_VLAN_DEST_PORTS | AR8216_PORT_VLAN_MODE |
@@ -591,6 +740,7 @@ ar8216_reset_switch(struct switch_dev *dev)
for (i = 0; i < AR8216_NUM_PORTS; i++) {
/* Enable port learning and tx */
priv->write(priv, AR8216_REG_PORT_CTRL(i),
+ ((i == 0 && priv->hdr_reg_acc) ?
AR8216_PORT_CTRL_HEADER : 0) |
AR8216_PORT_CTRL_LEARN |
(4 << AR8216_PORT_CTRL_STATE_S));
@@ -714,8 +864,14 @@ ar8216_config_init(struct phy_device *pdev)
pdev->advertising = pdev->supported;
mutex_init(&priv->reg_mutex);
- priv->read = ar8216_mii_read;
- priv->write = ar8216_mii_write;
+ if (priv->hdr_reg_acc) {
+ priv->read = ar8216_header_read;
+ priv->write = ar8216_header_write;
+ init_waitqueue_head(&priv->wait);
+ } else {
+ priv->read = ar8216_mii_read;
+ priv->write = ar8216_mii_write;
+ }
pdev->priv = priv;
@@ -750,7 +906,10 @@ ar8216_config_init(struct phy_device *pdev)
}
}
- ret = ar8216_reset_switch(&priv->dev);
+ /* we can't send packets at this stage, so skip reset */
+ if (!priv->hdr_reg_acc)
+ ret = ar8216_reset_switch(&priv->dev);
+
if (ret) {
kfree(priv);
goto done;
@@ -777,7 +936,7 @@ static int
ar8216_read_status(struct phy_device *phydev)
{
struct ar8216_priv *priv = phydev->priv;
- int ret;
+ int ret = 0;
if (phydev->addr != 0) {
return genphy_read_status(phydev);
}
@@ -787,14 +946,17 @@ ar8216_read_status(struct phy_device *phydev)
phydev->link = 1;
/* flush the address translation unit */
- mutex_lock(&priv->reg_mutex);
- ret = ar8216_wait_bit(priv, AR8216_REG_ATU, AR8216_ATU_ACTIVE, 0);
+ if (!priv->hdr_reg_acc ||
+ phydev->attached_dev->operstate == IF_OPER_UP) {
+ mutex_lock(&priv->reg_mutex);
+ ret = ar8216_wait_bit(priv, AR8216_REG_ATU, AR8216_ATU_ACTIVE,
0);
- if (!ret)
- priv->write(priv, AR8216_REG_ATU, AR8216_ATU_OP_FLUSH);
- else
- ret = -ETIMEDOUT;
- mutex_unlock(&priv->reg_mutex);
+ if (!ret)
+ priv->write(priv, AR8216_REG_ATU, AR8216_ATU_OP_FLUSH);
+ else
+ ret = -ETIMEDOUT;
+ mutex_unlock(&priv->reg_mutex);
+ }
phydev->state = PHY_RUNNING;
netif_carrier_on(phydev->attached_dev);
diff --git a/target/linux/generic/files/drivers/net/phy/ar8216.h
b/target/linux/generic/files/drivers/net/phy/ar8216.h
index 5a8fa3c..db1b7ad 100644
--- a/target/linux/generic/files/drivers/net/phy/ar8216.h
+++ b/target/linux/generic/files/drivers/net/phy/ar8216.h
@@ -184,4 +184,30 @@ enum {
AR8316 = 8316
};
+/* Atheros header format */
+
+/* byte 0 */
+#define AR8216_HDR_PORT 0x0f
+#define AR8216_HDR_VER_1 (1 << 4)
+#define AR8216_HDR_FROM_CPU BIT(6)
+
+/* byte 1 */
+#define AR8216_HDR_TYPE 0x0f
+
+/* byte 4 */
+#define AR8216_HDR_DATA_LEN_S 4
+
+/* byte 5 */
+#define AR8216_HDR_READ_REG BIT(4)
+#define AR8216_HDR_WRITE_REG 0
+#define AR8216_HDR_CHECK_CODE (5 << 5)
+
+
+/* atheros header packet type */
+enum {
+ AR8216_HDR_TYPE_NORMAL = 0,
+ AR8216_HDR_TYPE_RW_REG = 5,
+ AR8216_HDR_TYPE_RW_REG_ACK = 6
+};
+
#endif
--
1.7.2.5
_______________________________________________
openwrt-devel mailing list
[email protected]
https://lists.openwrt.org/mailman/listinfo/openwrt-devel