mvswitch,adm6996: use phy fixups instead of a nonstandard patch for hardware detection
[openwrt/svn-archive/archive.git] / target / linux / generic-2.6 / files / drivers / net / phy / mvswitch.c
index cb0d377d2712db44e48644b8efbfbd2b47ebff75..427bad4050f426a8584c997ec77642c7ec244d20 100644 (file)
 #include <asm/uaccess.h>
 #include "mvswitch.h"
 
+/* Undefine this to use trailer mode instead.
+ * I don't know if header mode works with all chips */
+#define HEADER_MODE    1
+
 MODULE_DESCRIPTION("Marvell 88E6060 Switch driver");
 MODULE_AUTHOR("Felix Fietkau");
 MODULE_LICENSE("GPL");
 
+#define MVSWITCH_MAGIC 0x88E6060
+
 struct mvswitch_priv {
        /* the driver's tx function */
        int (*hardstart)(struct sk_buff *skb, struct net_device *dev);
@@ -55,11 +61,11 @@ w16(struct phy_device *phydev, int addr, int reg, u16 val)
        phydev->bus->write(phydev->bus, addr, reg, val);
 }
 
+
 static int
 mvswitch_mangle_tx(struct sk_buff *skb, struct net_device *dev)
 {
        struct mvswitch_priv *priv;
-       struct vlan_ethhdr *eh;
        char *buf = NULL;
        u16 vid;
 
@@ -70,30 +76,34 @@ mvswitch_mangle_tx(struct sk_buff *skb, struct net_device *dev)
        if (unlikely(skb->len < 16))
                goto error;
 
-       eh = (struct vlan_ethhdr *) skb->data;
-       if (be16_to_cpu(eh->h_vlan_proto) != 0x8100)
+#ifdef HEADER_MODE
+       if (__vlan_hwaccel_get_tag(skb, &vid))
+               goto error;
+
+       if (skb_cloned(skb) || (skb->len <= 62) || (skb_headroom(skb) < MV_HEADER_SIZE)) {
+               if (pskb_expand_head(skb, MV_HEADER_SIZE, (skb->len < 62 ? 62 - skb->len : 0), GFP_ATOMIC))
+                       goto error_expand;
+               if (skb->len < 62)
+                       skb->len = 62;
+       }
+       buf = skb_push(skb, MV_HEADER_SIZE);
+#else
+       if (__vlan_get_tag(skb, &vid))
                goto error;
 
-       vid = be16_to_cpu(eh->h_vlan_TCI) & VLAN_VID_MASK;
        if (unlikely((vid > 15 || !priv->vlans[vid])))
                goto error;
 
        if (skb->len <= 64) {
-               if (pskb_expand_head(skb, 0, 68 - skb->len, GFP_ATOMIC)) {
-                       if (net_ratelimit())
-                               printk("%s: failed to expand/update skb for the switch\n", dev->name);
-                       goto error;
-               }
+               if (pskb_expand_head(skb, 0, 64 + MV_TRAILER_SIZE - skb->len, GFP_ATOMIC))
+                       goto error_expand;
 
                buf = skb->data + 64;
-               skb->len = 68;
+               skb->len = 64 + MV_TRAILER_SIZE;
        } else {
                if (skb_cloned(skb) || unlikely(skb_tailroom(skb) < 4)) {
-                       if (pskb_expand_head(skb, 0, 4, GFP_ATOMIC)) {
-                               if (net_ratelimit())
-                                       printk("%s: failed to expand/update skb for the switch\n", dev->name);
-                               goto error;
-                       }
+                       if (pskb_expand_head(skb, 0, 4, GFP_ATOMIC))
+                               goto error_expand;
                }
                buf = skb_put(skb, 4);
        }
@@ -103,18 +113,32 @@ mvswitch_mangle_tx(struct sk_buff *skb, struct net_device *dev)
        skb->data += 4;
        skb->len -= 4;
        skb->mac_header += 4;
+#endif
 
        if (!buf)
                goto error;
 
-       /* append the tag */
-       *((u32 *) buf) = (
-               (0x80 << 24) |
-               ((priv->vlans[vid] & 0x1f) << 16)
+
+#ifdef HEADER_MODE
+       /* prepend the tag */
+       *((__be16 *) buf) = cpu_to_be16(
+               ((vid << MV_HEADER_VLAN_S) & MV_HEADER_VLAN_M) |
+               ((priv->vlans[vid] << MV_HEADER_PORTS_S) & MV_HEADER_PORTS_M)
        );
+#else
+       /* append the tag */
+       *((__be32 *) buf) = cpu_to_be32((
+               (MV_TRAILER_OVERRIDE << MV_TRAILER_FLAGS_S) |
+               ((priv->vlans[vid] & MV_TRAILER_PORTS_M) << MV_TRAILER_PORTS_S)
+       ));
+#endif
 
        return priv->hardstart(skb, dev);
 
+error_expand:
+       if (net_ratelimit())
+               printk("%s: failed to expand/update skb for the switch\n", dev->name);
+
 error:
        /* any errors? drop the packet! */
        dev_kfree_skb_any(skb);
@@ -141,9 +165,14 @@ mvswitch_mangle_rx(struct sk_buff *skb, int napi)
        if (!priv->grp)
                goto error;
 
-       buf = skb->data + skb->len - 4;
+#ifdef HEADER_MODE
+       buf = skb->data;
+       skb_pull(skb, MV_HEADER_SIZE);
+#else
+       buf = skb->data + skb->len - MV_TRAILER_SIZE;
        if (buf[0] != 0x80)
                goto error;
+#endif
 
        /* look for the vlan matching the incoming port */
        for (i = 0; i < ARRAY_SIZE(priv->vlans); i++) {
@@ -154,6 +183,8 @@ mvswitch_mangle_rx(struct sk_buff *skb, int napi)
        if (vlan == -1)
                goto error;
 
+       skb->protocol = eth_type_trans(skb, skb->dev);
+
        if (napi)
                return vlan_hwaccel_receive_skb(skb, priv->grp, vlan);
        else
@@ -187,6 +218,20 @@ mvswitch_vlan_rx_register(struct net_device *dev, struct vlan_group *grp)
 }
 
 
+static int
+mvswitch_wait_mask(struct phy_device *pdev, int addr, int reg, u16 mask, u16 val)
+{
+       int i = 100;
+       u16 r;
+
+       do {
+               r = r16(pdev, addr, reg) & mask;
+               if (r == val)
+                       return 0;
+       } while(--i > 0);
+       return -ETIMEDOUT;
+}
+
 static int
 mvswitch_config_init(struct phy_device *pdev)
 {
@@ -202,6 +247,7 @@ mvswitch_config_init(struct phy_device *pdev)
        pdev->supported = ADVERTISED_100baseT_Full;
        pdev->advertising = ADVERTISED_100baseT_Full;
        dev->phy_ptr = priv;
+       dev->irq = PHY_POLL;
 
        /* initialize default vlans */
        for (i = 0; i < MV_PORTS; i++)
@@ -213,30 +259,31 @@ mvswitch_config_init(struct phy_device *pdev)
 
        msleep(2); /* wait for the status change to settle in */
 
-       /* put the device in reset and set ATU flags */
-       w16(pdev, MV_SWITCHREG(ATU_CTRL),
-               MV_ATUCTL_RESET |
-               MV_ATUCTL_ATU_1K |
-               MV_ATUCTL_AGETIME(4080) /* maximum */
-       );
-
-       i = 100; /* timeout */
-       do {
-               if (!(r16(pdev, MV_SWITCHREG(ATU_CTRL)) & MV_ATUCTL_RESET))
-                       break;
-               msleep(1);
-       } while (--i > 0);
+       /* put the ATU in reset */
+       w16(pdev, MV_SWITCHREG(ATU_CTRL), MV_ATUCTL_RESET);
 
-       if (!i) {
+       i = mvswitch_wait_mask(pdev, MV_SWITCHREG(ATU_CTRL), MV_ATUCTL_RESET, 0);
+       if (i < 0) {
                printk("%s: Timeout waiting for the switch to reset.\n", dev->name);
-               return -ETIMEDOUT;
+               return i;
        }
 
+       /* set the ATU flags */
+       w16(pdev, MV_SWITCHREG(ATU_CTRL),
+               MV_ATUCTL_NO_LEARN |
+               MV_ATUCTL_ATU_1K |
+               MV_ATUCTL_AGETIME(MV_ATUCTL_AGETIME_MIN) /* minimum without disabling ageing */
+       );
+
        /* initialize the cpu port */
        w16(pdev, MV_PORTREG(CONTROL, MV_CPUPORT),
-               MV_PORTCTRL_ENABLED |
+#ifdef HEADER_MODE
+               MV_PORTCTRL_HEADER |
+#else
                MV_PORTCTRL_RXTR |
-               MV_PORTCTRL_TXTR
+               MV_PORTCTRL_TXTR |
+#endif
+               MV_PORTCTRL_ENABLED
        );
        /* wait for the phy change to settle in */
        msleep(2);
@@ -255,7 +302,7 @@ mvswitch_config_init(struct phy_device *pdev)
                }
                /* leave port unconfigured if it's not part of a vlan */
                if (!vlmap)
-                       break;
+                       continue;
 
                /* add the cpu port to the allowed destinations list */
                vlmap |= (1 << MV_CPUPORT);
@@ -266,19 +313,17 @@ mvswitch_config_init(struct phy_device *pdev)
                /* apply vlan settings */
                w16(pdev, MV_PORTREG(VLANMAP, i),
                        MV_PORTVLAN_PORTS(vlmap) |
-                       MV_PORTVLAN_ID(pvid)
+                       MV_PORTVLAN_ID(i)
                );
 
                /* re-enable port */
-               w16(pdev, MV_PORTREG(CONTROL, i), MV_PORTCTRL_ENABLED);
+               w16(pdev, MV_PORTREG(CONTROL, i),
+                       MV_PORTCTRL_ENABLED
+               );
        }
 
-       /* build the target list for the cpu port */
-       for (i = 0; i < MV_PORTS; i++)
-               vlmap |= (1 << i);
-
        w16(pdev, MV_PORTREG(VLANMAP, MV_CPUPORT),
-               MV_PORTVLAN_PORTS(vlmap)
+               MV_PORTVLAN_ID(MV_CPUPORT)
        );
 
        /* set the port association vector */
@@ -295,22 +340,44 @@ mvswitch_config_init(struct phy_device *pdev)
        );
 
        /* hook into the tx function */
+       pdev->pkt_align = 2;
        priv->hardstart = dev->hard_start_xmit;
        pdev->netif_receive_skb = mvswitch_netif_receive_skb;
        pdev->netif_rx = mvswitch_netif_rx;
        dev->hard_start_xmit = mvswitch_mangle_tx;
        dev->vlan_rx_register = mvswitch_vlan_rx_register;
+#ifdef HEADER_MODE
+       dev->features |= NETIF_F_HW_VLAN_RX | NETIF_F_HW_VLAN_TX;
+#else
        dev->features |= NETIF_F_HW_VLAN_RX;
+#endif
 
        return 0;
 }
 
 static int
-mvswitch_read_status(struct phy_device *phydev)
+mvswitch_read_status(struct phy_device *pdev)
 {
-       phydev->speed = SPEED_100;
-       phydev->duplex = DUPLEX_FULL;
-       phydev->state = PHY_UP;
+       pdev->speed = SPEED_100;
+       pdev->duplex = DUPLEX_FULL;
+       pdev->state = PHY_UP;
+
+       /* XXX ugly workaround: we can't force the switch
+        * to gracefully handle hosts moving from one port to another,
+        * so we have to regularly clear the ATU database */
+
+       /* wait for the ATU to become available */
+       mvswitch_wait_mask(pdev, MV_SWITCHREG(ATU_OP), MV_ATUOP_INPROGRESS, 0);
+
+       /* flush the ATU */
+       w16(pdev, MV_SWITCHREG(ATU_OP),
+               MV_ATUOP_INPROGRESS |
+               MV_ATUOP_FLUSH_ALL
+       );
+
+       /* wait for operation to complete */
+       mvswitch_wait_mask(pdev, MV_SWITCHREG(ATU_OP), MV_ATUOP_INPROGRESS, 0);
+
        return 0;
 }
 
@@ -336,37 +403,6 @@ mvswitch_remove(struct phy_device *pdev)
        kfree(priv);
 }
 
-static bool
-mvswitch_detect(struct mii_bus *bus, int addr)
-{
-       u16 reg;
-       int i;
-
-       /* we attach to phy id 31 to make sure that the late probe works */
-       if (addr != 31)
-               return false;
-
-       /* look for the switch on the bus */
-       reg = bus->read(bus, MV_PORTREG(IDENT, 0)) & MV_IDENT_MASK;
-       if (reg != MV_IDENT_VALUE)
-               return false;
-
-       /* 
-        * Now that we've established that the switch actually exists, let's 
-        * get rid of the competition :)
-        */
-       for (i = 0; i < 31; i++) {
-               if (!bus->phy_map[i])
-                       continue;
-
-               device_unregister(&bus->phy_map[i]->dev);
-               kfree(bus->phy_map[i]);
-               bus->phy_map[i] = NULL;
-       }
-
-       return true;
-}
-
 static int
 mvswitch_probe(struct phy_device *pdev)
 {
@@ -381,11 +417,26 @@ mvswitch_probe(struct phy_device *pdev)
        return 0;
 }
 
+static int
+mvswitch_fixup(struct phy_device *dev)
+{
+       u16 reg;
+
+       /* look for the switch on the bus */
+       reg = dev->bus->read(dev->bus, MV_PORTREG(IDENT, 0)) & MV_IDENT_MASK;
+       if (reg != MV_IDENT_VALUE)
+               return 0;
+
+       dev->phy_id = MVSWITCH_MAGIC;
+       return 0;
+}
+
 
 static struct phy_driver mvswitch_driver = {
        .name           = "Marvell 88E6060",
+       .phy_id         = MVSWITCH_MAGIC,
+       .phy_id_mask    = 0xffffffff,
        .features       = PHY_BASIC_FEATURES,
-       .detect         = &mvswitch_detect,
        .probe          = &mvswitch_probe,
        .remove         = &mvswitch_remove,
        .config_init    = &mvswitch_config_init,
@@ -397,6 +448,7 @@ static struct phy_driver mvswitch_driver = {
 static int __init
 mvswitch_init(void)
 {
+       phy_register_fixup_for_id(PHY_ANY_ID, mvswitch_fixup);
        return phy_driver_register(&mvswitch_driver);
 }