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

Reply via email to