/* * Marvell 88E6060 switch driver * Copyright (c) 2008 Felix Fietkau * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License v2 as published by the * Free Software Foundation */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 { netdev_features_t orig_features; u8 vlans[16]; }; #define to_mvsw(_phy) ((struct mvswitch_priv *) (_phy)->priv) static inline u16 r16(struct phy_device *phydev, int addr, int reg) { return phydev->bus->read(phydev->bus, addr, reg); } static inline void w16(struct phy_device *phydev, int addr, int reg, u16 val) { phydev->bus->write(phydev->bus, addr, reg, val); } static struct sk_buff * mvswitch_mangle_tx(struct net_device *dev, struct sk_buff *skb) { struct mvswitch_priv *priv; char *buf = NULL; u16 vid; priv = dev->phy_ptr; if (unlikely(!priv)) goto error; if (unlikely(skb->len < 16)) goto error; #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; if (unlikely((vid > 15 || !priv->vlans[vid]))) goto error; if (skb->len <= 64) { if (pskb_expand_head(skb, 0, 64 + MV_TRAILER_SIZE - skb->len, GFP_ATOMIC)) goto error_expand; buf = skb->data + 64; 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)) goto error_expand; } buf = skb_put(skb, 4); } /* move the ethernet header 4 bytes forward, overwriting the vlan tag */ memmove(skb->data + 4, skb->data, 12); skb->data += 4; skb->len -= 4; skb->mac_header += 4; #endif if (!buf) goto error; #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 skb; 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); return NULL; } static void mvswitch_mangle_rx(struct net_device *dev, struct sk_buff *skb) { struct mvswitch_priv *priv; unsigned char *buf; int vlan = -1; int i; priv = dev->phy_ptr; if (WARN_ON_ONCE(!priv)) return; #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) return; #endif /* look for the vlan matching the incoming port */ for (i = 0; i < ARRAY_SIZE(priv->vlans); i++) { if ((1 << buf[1]) & priv->vlans[i]) vlan = i; } if (vlan == -1) return; __vlan_hwaccel_put_tag(skb, htons(ETH_P_8021Q), vlan); } 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) { struct mvswitch_priv *priv = to_mvsw(pdev); struct net_device *dev = pdev->attached_dev; u8 vlmap = 0; int i; if (!dev) return -EINVAL; printk("%s: Marvell 88E6060 PHY driver attached.\n", dev->name); pdev->supported = ADVERTISED_100baseT_Full; pdev->advertising = ADVERTISED_100baseT_Full; dev->phy_ptr = priv; pdev->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 ? 2 : 1)] |= (1 << i); /* before entering reset, disable all ports */ for (i = 0; i < MV_PORTS; i++) w16(pdev, MV_PORTREG(CONTROL, i), 0x00); msleep(2); /* wait for the status change to settle in */ /* put the ATU in reset */ w16(pdev, MV_SWITCHREG(ATU_CTRL), MV_ATUCTL_RESET); 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 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), #ifdef HEADER_MODE MV_PORTCTRL_HEADER | #else MV_PORTCTRL_RXTR | MV_PORTCTRL_TXTR | #endif MV_PORTCTRL_ENABLED ); /* wait for the phy change to settle in */ msleep(2); for (i = 0; i < MV_PORTS; i++) { u8 pvid = 0; int j; vlmap = 0; /* look for the matching vlan */ for (j = 0; j < ARRAY_SIZE(priv->vlans); j++) { if (priv->vlans[j] & (1 << i)) { vlmap = priv->vlans[j]; pvid = j; } } /* leave port unconfigured if it's not part of a vlan */ if (!vlmap) continue; /* add the cpu port to the allowed destinations list */ vlmap |= (1 << MV_CPUPORT); /* take port out of its own vlan destination map */ vlmap &= ~(1 << i); /* apply vlan settings */ w16(pdev, MV_PORTREG(VLANMAP, i), MV_PORTVLAN_PORTS(vlmap) | MV_PORTVLAN_ID(i) ); /* re-enable port */ w16(pdev, MV_PORTREG(CONTROL, i), MV_PORTCTRL_ENABLED ); } w16(pdev, MV_PORTREG(VLANMAP, MV_CPUPORT), MV_PORTVLAN_ID(MV_CPUPORT) ); /* set the port association vector */ for (i = 0; i <= MV_PORTS; i++) { w16(pdev, MV_PORTREG(ASSOC, i), MV_PORTASSOC_PORTS(1 << i) ); } /* init switch control */ w16(pdev, MV_SWITCHREG(CTRL), MV_SWITCHCTL_MSIZE | MV_SWITCHCTL_DROP ); dev->eth_mangle_rx = mvswitch_mangle_rx; dev->eth_mangle_tx = mvswitch_mangle_tx; priv->orig_features = dev->features; #ifdef HEADER_MODE dev->priv_flags |= IFF_NO_IP_ALIGN; dev->features |= NETIF_F_HW_VLAN_CTAG_RX | NETIF_F_HW_VLAN_CTAG_TX; #else dev->features |= NETIF_F_HW_VLAN_CTAG_RX; #endif return 0; } static int mvswitch_read_status(struct phy_device *pdev) { 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; } static int mvswitch_config_aneg(struct phy_device *phydev) { return 0; } static void mvswitch_detach(struct phy_device *pdev) { struct mvswitch_priv *priv = to_mvsw(pdev); struct net_device *dev = pdev->attached_dev; if (!dev) return; dev->phy_ptr = NULL; dev->eth_mangle_rx = NULL; dev->eth_mangle_tx = NULL; dev->features = priv->orig_features; dev->priv_flags &= ~IFF_NO_IP_ALIGN; } static void mvswitch_remove(struct phy_device *pdev) { struct mvswitch_priv *priv = to_mvsw(pdev); kfree(priv); } static int mvswitch_probe(struct phy_device *pdev) { struct mvswitch_priv *priv; priv = kzalloc(sizeof(struct mvswitch_priv), GFP_KERNEL); if (priv == NULL) return -ENOMEM; pdev->priv = priv; 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, .probe = &mvswitch_probe, .remove = &mvswitch_remove, .detach = &mvswitch_detach, .config_init = &mvswitch_config_init, .config_aneg = &mvswitch_config_aneg, .read_status = &mvswitch_read_status, .driver = { .owner = THIS_MODULE,}, }; static int __init mvswitch_init(void) { phy_register_fixup_for_id(PHY_ANY_ID, mvswitch_fixup); return phy_driver_register(&mvswitch_driver); } static void __exit mvswitch_exit(void) { phy_driver_unregister(&mvswitch_driver); } module_init(mvswitch_init); module_exit(mvswitch_exit);