#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);
+ const struct net_device_ops *ndo_old;
+ struct net_device_ops ndo;
struct vlan_group *grp;
u8 vlans[16];
};
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;
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);
}
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->ndo_old->ndo_start_xmit(skb, dev);
- 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! */
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++) {
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
}
+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)
{
pdev->supported = ADVERTISED_100baseT_Full;
pdev->advertising = ADVERTISED_100baseT_Full;
dev->phy_ptr = priv;
+ dev->irq = PHY_POLL;
+#ifdef HEADER_MODE
+ dev->flags |= IFF_PROMISC;
+#endif
/* initialize default vlans */
for (i = 0; i < MV_PORTS; i++)
- priv->vlans[(i == MV_WANPORT ? 1 : 0)] |= (1 << i);
+ priv->vlans[(i == MV_WANPORT ? 2 : 1)] |= (1 << i);
/* before entering reset, disable all ports */
for (i = 0; i < MV_PORTS; i++)
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);
}
/* 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);
/* 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, vlmap = 0; i < ARRAY_SIZE(priv->vlans); i++)
- vlmap |= priv->vlans[i];
-
w16(pdev, MV_PORTREG(VLANMAP, MV_CPUPORT),
- MV_PORTVLAN_PORTS(vlmap)
+ MV_PORTVLAN_ID(MV_CPUPORT)
);
/* set the port association vector */
);
}
+ /* init switch control */
+ w16(pdev, MV_SWITCHREG(CTRL),
+ MV_SWITCHCTL_MSIZE |
+ MV_SWITCHCTL_DROP
+ );
+
/* hook into the tx function */
- priv->hardstart = dev->hard_start_xmit;
+ priv->ndo_old = dev->netdev_ops;
+ memcpy(&priv->ndo, priv->ndo_old, sizeof(struct net_device_ops));
+ priv->ndo.ndo_start_xmit = mvswitch_mangle_tx;
+ priv->ndo.ndo_vlan_rx_register = mvswitch_vlan_rx_register;
+ dev->netdev_ops = &priv->ndo;
+
+ pdev->pkt_align = 2;
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->link = 1;
+
+ /* 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;
}
struct mvswitch_priv *priv = to_mvsw(pdev);
struct net_device *dev = pdev->attached_dev;
- /* restore old xmit handler */
- if (priv->hardstart && dev)
- dev->hard_start_xmit = priv->hardstart;
- dev->vlan_rx_register = NULL;
- dev->vlan_rx_kill_vid = NULL;
+ /* restore old netdev ops */
+ if (priv->ndo_old && dev)
+ dev->netdev_ops = priv->ndo_old;
dev->phy_ptr = NULL;
dev->features &= ~NETIF_F_HW_VLAN_RX;
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)
{
return 0;
}
+static int
+mvswitch_fixup(struct phy_device *dev)
+{
+ u16 reg;
+
+ if (dev->addr != 0x10)
+ return 0;
+
+ 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,
static int __init
mvswitch_init(void)
{
+ phy_register_fixup_for_id(PHY_ANY_ID, mvswitch_fixup);
return phy_driver_register(&mvswitch_driver);
}