X-Git-Url: https://git.rohieb.name/openwrt.git/blobdiff_plain/0f2df09b10ca2adf5391dbf1d95c1f1b0b54d4ea..08b66183fa7aa740e87a509ea5030fb5557e7eee:/target/linux/generic-2.6/files/drivers/net/phy/mvswitch.c?ds=sidebyside diff --git a/target/linux/generic-2.6/files/drivers/net/phy/mvswitch.c b/target/linux/generic-2.6/files/drivers/net/phy/mvswitch.c index cb0d377d2..b98699e17 100644 --- a/target/linux/generic-2.6/files/drivers/net/phy/mvswitch.c +++ b/target/linux/generic-2.6/files/drivers/net/phy/mvswitch.c @@ -30,10 +30,16 @@ #include #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,10 +247,11 @@ 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++) - 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++) @@ -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 */ - ); + /* put the ATU in reset */ + w16(pdev, MV_SWITCHREG(ATU_CTRL), MV_ATUCTL_RESET); - i = 100; /* timeout */ - do { - if (!(r16(pdev, MV_SWITCHREG(ATU_CTRL)) & MV_ATUCTL_RESET)) - break; - msleep(1); - } while (--i > 0); - - 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,28 @@ 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, @@ -397,6 +450,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); }