diff options
| -rw-r--r-- | target/linux/generic-2.6/files-2.6.30/drivers/net/phy/ar8216.c | 651 | ||||
| -rw-r--r-- | target/linux/generic-2.6/files-2.6.30/drivers/net/phy/mvswitch.c | 467 | 
2 files changed, 1118 insertions, 0 deletions
| diff --git a/target/linux/generic-2.6/files-2.6.30/drivers/net/phy/ar8216.c b/target/linux/generic-2.6/files-2.6.30/drivers/net/phy/ar8216.c new file mode 100644 index 000000000..93e4b33d9 --- /dev/null +++ b/target/linux/generic-2.6/files-2.6.30/drivers/net/phy/ar8216.c @@ -0,0 +1,651 @@ +/* + * ar8216.c: AR8216 switch driver + * + * Copyright (C) 2009 Felix Fietkau <nbd@openwrt.org> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + */ + +#include <linux/if.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/list.h> +#include <linux/if_ether.h> +#include <linux/skbuff.h> +#include <linux/netdevice.h> +#include <linux/netlink.h> +#include <linux/bitops.h> +#include <net/genetlink.h> +#include <linux/switch.h> +#include <linux/delay.h> +#include <linux/phy.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include "ar8216.h" + + +struct ar8216_priv { +	struct switch_dev dev; +	struct phy_device *phy; +	u32 (*read)(struct ar8216_priv *priv, int reg); +	void (*write)(struct ar8216_priv *priv, int reg, u32 val); +	const struct net_device_ops *ndo_old; +	struct net_device_ops ndo; + +	/* all fields below are cleared on reset */ +	bool vlan; +	u8 vlan_id[AR8216_NUM_VLANS]; +	u8 vlan_table[AR8216_NUM_VLANS]; +	u8 vlan_tagged; +	u16 pvid[AR8216_NUM_PORTS]; +}; +static struct switch_dev athdev; + +#define to_ar8216(_dev) container_of(_dev, struct ar8216_priv, dev) + +static inline void +split_addr(u32 regaddr, u16 *r1, u16 *r2, u16 *page) +{ +	regaddr >>= 1; +	*r1 = regaddr & 0x1e; + +	regaddr >>= 5; +	*r2 = regaddr & 0x7; + +	regaddr >>= 3; +	*page = regaddr & 0x1ff; +} + +static u32 +ar8216_mii_read(struct ar8216_priv *priv, int reg) +{ +	struct phy_device *phy = priv->phy; +	u16 r1, r2, page; +	u16 lo, hi; + +	split_addr((u32) reg, &r1, &r2, &page); +	phy->bus->write(phy->bus, 0x18, 0, page); +	msleep(1); /* wait for the page switch to propagate */ +	lo = phy->bus->read(phy->bus, 0x10 | r2, r1); +	hi = phy->bus->read(phy->bus, 0x10 | r2, r1 + 1); + +	return (hi << 16) | lo; +} + +static void +ar8216_mii_write(struct ar8216_priv *priv, int reg, u32 val) +{ +	struct phy_device *phy = priv->phy; +	u16 r1, r2, r3; +	u16 lo, hi; + +	split_addr((u32) reg, &r1, &r2, &r3); +	phy->bus->write(phy->bus, 0x18, 0, r3); +	msleep(1); /* wait for the page switch to propagate */ + +	lo = val & 0xffff; +	hi = (u16) (val >> 16); +	phy->bus->write(phy->bus, 0x10 | r2, r1 + 1, hi); +	phy->bus->write(phy->bus, 0x10 | r2, r1, lo); +} + +static u32 +ar8216_rmw(struct ar8216_priv *priv, int reg, u32 mask, u32 val) +{ +	u32 v; + +	v = priv->read(priv, reg); +	v &= ~mask; +	v |= val; +	priv->write(priv, reg, v); + +	return v; +} + +static int +ar8216_set_vlan(struct switch_dev *dev, const struct switch_attr *attr, +                struct switch_val *val) +{ +	struct ar8216_priv *priv = to_ar8216(dev); +	priv->vlan = !!val->value.i; +	return 0; +} + +static int +ar8216_get_vlan(struct switch_dev *dev, const struct switch_attr *attr, +                struct switch_val *val) +{ +	struct ar8216_priv *priv = to_ar8216(dev); +	val->value.i = priv->vlan; +	return 0; +} + + +static int +ar8216_set_pvid(struct switch_dev *dev, int port, int vlan) +{ +	struct ar8216_priv *priv = to_ar8216(dev); +	priv->pvid[port] = vlan; +	return 0; +} + +static int +ar8216_get_pvid(struct switch_dev *dev, int port, int *vlan) +{ +	struct ar8216_priv *priv = to_ar8216(dev); +	*vlan = priv->pvid[port]; +	return 0; +} + +static int +ar8216_set_vid(struct switch_dev *dev, const struct switch_attr *attr, +                struct switch_val *val) +{ +	struct ar8216_priv *priv = to_ar8216(dev); +	priv->vlan_id[val->port_vlan] = val->value.i; +	return 0; +} + +static int +ar8216_get_vid(struct switch_dev *dev, const struct switch_attr *attr, +                struct switch_val *val) +{ +	struct ar8216_priv *priv = to_ar8216(dev); +	val->value.i = priv->vlan_id[val->port_vlan]; +	return 0; +} + + +static int +ar8216_mangle_tx(struct sk_buff *skb, struct net_device *dev) +{ +	struct ar8216_priv *priv = dev->phy_ptr; +	unsigned char *buf; + +    if (unlikely(!priv)) +        goto error; + +	if (!priv->vlan) +		goto send; + +	if (unlikely(skb_headroom(skb) < 2)) { +		if (pskb_expand_head(skb, 2, 0, GFP_ATOMIC) < 0) +			goto error; +	} + +	buf = skb_push(skb, 2); +	buf[0] = 0x10; +	buf[1] = 0x80; + +send: +	return priv->ndo_old->ndo_start_xmit(skb, dev); + +error: +	dev_kfree_skb_any(skb); +	return 0; +} + +static int +ar8216_mangle_rx(struct sk_buff *skb, int napi) +{ +	struct ar8216_priv *priv; +	struct net_device *dev; +	unsigned char *buf; +	int port, vlan; + +	dev = skb->dev; +	if (!dev) +		goto error; + +	priv = dev->phy_ptr; +	if (!priv) +		goto error; + +	/* don't strip the header if vlan mode is disabled */ +	if (!priv->vlan) +		goto recv; + +	/* strip header, get vlan id */ +	buf = skb->data; +	skb_pull(skb, 2); + +	/* check for vlan header presence */ +	if ((buf[12 + 2] != 0x81) || (buf[13 + 2] != 0x00)) +		goto recv; + +	port = buf[0] & 0xf; + +	/* no need to fix up packets coming from a tagged source */ +	if (priv->vlan_tagged & (1 << port)) +		goto recv; + +	/* lookup port vid from local table, the switch passes an invalid vlan id */ +	vlan = priv->pvid[port]; + +	buf[14 + 2] &= 0xf0; +	buf[14 + 2] |= vlan >> 8; +	buf[15 + 2] = vlan & 0xff; + +recv: +	skb->protocol = eth_type_trans(skb, skb->dev); + +	if (napi) +		return netif_receive_skb(skb); +	else +		return netif_rx(skb); + +error: +	/* no vlan? eat the packet! */ +	dev_kfree_skb_any(skb); +	return 0; +} + +static int +ar8216_netif_rx(struct sk_buff *skb) +{ +	return ar8216_mangle_rx(skb, 0); +} + +static int +ar8216_netif_receive_skb(struct sk_buff *skb) +{ +	return ar8216_mangle_rx(skb, 1); +} + + +static struct switch_attr ar8216_globals[] = { +	{ +		.type = SWITCH_TYPE_INT, +		.name = "enable_vlan", +		.description = "Enable VLAN mode", +		.set = ar8216_set_vlan, +		.get = ar8216_get_vlan, +		.max = 1 +	}, +}; + +static struct switch_attr ar8216_port[] = { +}; + +static struct switch_attr ar8216_vlan[] = { +	{ +		.type = SWITCH_TYPE_INT, +		.name = "pvid", +		.description = "VLAN ID", +		.set = ar8216_set_vid, +		.get = ar8216_get_vid, +		.max = 4095, +	}, +}; + + +static int +ar8216_get_ports(struct switch_dev *dev, struct switch_val *val) +{ +	struct ar8216_priv *priv = to_ar8216(dev); +	u8 ports = priv->vlan_table[val->port_vlan]; +	int i; + +	val->len = 0; +	for (i = 0; i < AR8216_NUM_PORTS; i++) { +		struct switch_port *p; + +		if (!(ports & (1 << i))) +			continue; + +		p = &val->value.ports[val->len++]; +		p->id = i; +		if (priv->vlan_tagged & (1 << i)) +			p->flags = (1 << SWITCH_PORT_FLAG_TAGGED); +		else +			p->flags = 0; +	} +	return 0; +} + +static int +ar8216_set_ports(struct switch_dev *dev, struct switch_val *val) +{ +	struct ar8216_priv *priv = to_ar8216(dev); +	u8 *vt = &priv->vlan_table[val->port_vlan]; +	int i, j; + +	*vt = 0; +	for (i = 0; i < val->len; i++) { +		struct switch_port *p = &val->value.ports[i]; + +		if (p->flags & (1 << SWITCH_PORT_FLAG_TAGGED)) +			priv->vlan_tagged |= (1 << p->id); +		else { +			priv->vlan_tagged &= ~(1 << p->id); +			priv->pvid[p->id] = val->port_vlan; + +			/* make sure that an untagged port does not +			 * appear in other vlans */ +			for (j = 0; j < AR8216_NUM_VLANS; j++) { +				if (j == val->port_vlan) +					continue; +				priv->vlan_table[j] &= ~(1 << p->id); +			} +		} + +		*vt |= 1 << p->id; +	} +	return 0; +} + +static int +ar8216_wait_bit(struct ar8216_priv *priv, int reg, u32 mask, u32 val) +{ +	int timeout = 20; + +	while ((priv->read(priv, reg) & mask) != val) { +		if (timeout-- <= 0) { +			printk(KERN_ERR "ar8216: timeout waiting for operation to complete\n"); +			return 1; +		} +	} +	return 0; +} + +static void +ar8216_vtu_op(struct ar8216_priv *priv, u32 op, u32 val) +{ +	if (ar8216_wait_bit(priv, AR8216_REG_VTU, AR8216_VTU_ACTIVE, 0)) +		return; +	if ((op & AR8216_VTU_OP) == AR8216_VTU_OP_LOAD) { +		val &= AR8216_VTUDATA_MEMBER; +		val |= AR8216_VTUDATA_VALID; +		priv->write(priv, AR8216_REG_VTU_DATA, val); +	} +	op |= AR8216_VTU_ACTIVE; +	priv->write(priv, AR8216_REG_VTU, op); +} + +static int +ar8216_hw_apply(struct switch_dev *dev) +{ +	struct ar8216_priv *priv = to_ar8216(dev); +	u8 portmask[AR8216_NUM_PORTS]; +	int i, j; + +	/* flush all vlan translation unit entries */ +	ar8216_vtu_op(priv, AR8216_VTU_OP_FLUSH, 0); + +	memset(portmask, 0, sizeof(portmask)); +	if (priv->vlan) { +		/* calculate the port destination masks and load vlans +		 * into the vlan translation unit */ +		for (j = 0; j < AR8216_NUM_VLANS; j++) { +			u8 vp = priv->vlan_table[j]; + +			if (!vp) +				continue; + +			for (i = 0; i < AR8216_NUM_PORTS; i++) { +				u8 mask = (1 << i); +				if (vp & mask) +					portmask[i] |= vp & ~mask; +			} + +			if (!priv->vlan_table[j]) +				continue; + +			ar8216_vtu_op(priv, +				AR8216_VTU_OP_LOAD | +				(priv->vlan_id[j] << AR8216_VTU_VID_S), +				priv->vlan_table[j]); +		} +	} else { +		/* vlan disabled: +		 * isolate all ports, but connect them to the cpu port */ +		for (i = 0; i < AR8216_NUM_PORTS; i++) { +			if (i == AR8216_PORT_CPU) +				continue; + +			portmask[i] = 1 << AR8216_PORT_CPU; +			portmask[AR8216_PORT_CPU] |= (1 << i); +		} +	} + +	/* update the port destination mask registers and tag settings */ +	for (i = 0; i < AR8216_NUM_PORTS; i++) { +		int egress, ingress; +		int pvid; + +		if (priv->vlan) { +			pvid = priv->vlan_id[priv->pvid[i]]; +		} else { +			pvid = i; +		} + +		if (priv->vlan && (priv->vlan_tagged & (1 << i))) { +			egress = AR8216_OUT_ADD_VLAN; +		} else { +			egress = AR8216_OUT_STRIP_VLAN; +		} +		ingress = AR8216_IN_SECURE; + +		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 ? +			   AR8216_PORT_CTRL_HEADER : 0) | +			  (egress << AR8216_PORT_CTRL_VLAN_MODE_S) | +			  (AR8216_PORT_STATE_FORWARD << AR8216_PORT_CTRL_STATE_S)); + +		ar8216_rmw(priv, AR8216_REG_PORT_VLAN(i), +			AR8216_PORT_VLAN_DEST_PORTS | AR8216_PORT_VLAN_MODE | +			  AR8216_PORT_VLAN_DEFAULT_ID, +			(portmask[i] << AR8216_PORT_VLAN_DEST_PORTS_S) | +			  (ingress << AR8216_PORT_VLAN_MODE_S) | +			  (pvid << AR8216_PORT_VLAN_DEFAULT_ID_S)); +	} + +	return 0; +} + +static int +ar8216_reset_switch(struct switch_dev *dev) +{ +	struct ar8216_priv *priv = to_ar8216(dev); +	int i; + +	memset(&priv->vlan, 0, sizeof(struct ar8216_priv) - +		offsetof(struct ar8216_priv, vlan)); +	for (i = 0; i < AR8216_NUM_VLANS; i++) { +		priv->vlan_id[i] = i; +	} +	for (i = 0; i < AR8216_NUM_PORTS; i++) { +		/* Enable port learning and tx */ +		priv->write(priv, AR8216_REG_PORT_CTRL(i), +			AR8216_PORT_CTRL_LEARN | +			(4 << AR8216_PORT_CTRL_STATE_S)); + +		priv->write(priv, AR8216_REG_PORT_VLAN(i), 0); + +		/* Configure all PHYs */ +		if (i == AR8216_PORT_CPU) { +			priv->write(priv, AR8216_REG_PORT_STATUS(i), +				AR8216_PORT_STATUS_LINK_UP | +				AR8216_PORT_STATUS_SPEED | +				AR8216_PORT_STATUS_TXMAC | +				AR8216_PORT_STATUS_RXMAC | +				AR8216_PORT_STATUS_DUPLEX); +		} else { +			priv->write(priv, AR8216_REG_PORT_STATUS(i), +				AR8216_PORT_STATUS_LINK_AUTO); +		} +	} +	/* XXX: undocumented magic from atheros, required! */ +	priv->write(priv, 0x38, 0xc000050e); + +	ar8216_rmw(priv, AR8216_REG_GLOBAL_CTRL, +		AR8216_GCTRL_MTU, 1518 + 8 + 2); + +	return ar8216_hw_apply(dev); +} + +static int +ar8216_config_init(struct phy_device *pdev) +{ +	struct ar8216_priv *priv; +	struct net_device *dev = pdev->attached_dev; +	int ret; + +	printk("%s: AR8216 PHY driver attached.\n", pdev->attached_dev->name); +	pdev->supported = ADVERTISED_100baseT_Full; +	pdev->advertising = ADVERTISED_100baseT_Full; + +	priv = kzalloc(sizeof(struct ar8216_priv), GFP_KERNEL); +	if (priv == NULL) +		return -ENOMEM; + +	priv->phy = pdev; +	priv->read = ar8216_mii_read; +	priv->write = ar8216_mii_write; +	memcpy(&priv->dev, &athdev, sizeof(struct switch_dev)); +	pdev->priv = priv; +	if ((ret = register_switch(&priv->dev, pdev->attached_dev)) < 0) { +		kfree(priv); +		goto done; +	} + +	ret = ar8216_reset_switch(&priv->dev); +	if (ret) +		goto done; + +	dev->phy_ptr = priv; +	pdev->pkt_align = 2; +	pdev->netif_receive_skb = ar8216_netif_receive_skb; +	pdev->netif_rx = ar8216_netif_rx; + +	priv->ndo_old = dev->netdev_ops; +	memcpy(&priv->ndo, priv->ndo_old, sizeof(struct net_device_ops)); +	priv->ndo.ndo_start_xmit = ar8216_mangle_tx; +	dev->netdev_ops = &priv->ndo; + +done: +	return ret; +} + +static int +ar8216_read_status(struct phy_device *phydev) +{ +	struct ar8216_priv *priv = phydev->priv; + +	phydev->speed = SPEED_100; +	phydev->duplex = DUPLEX_FULL; +	phydev->state = PHY_UP; + +	/* flush the address translation unit */ +	if (ar8216_wait_bit(priv, AR8216_REG_ATU, AR8216_ATU_ACTIVE, 0)) +		return -ETIMEDOUT; + +	priv->write(priv, AR8216_REG_ATU, AR8216_ATU_OP_FLUSH); + +	return 0; +} + +static int +ar8216_config_aneg(struct phy_device *phydev) +{ +	return 0; +} + +static int +ar8216_probe(struct phy_device *pdev) +{ +	struct ar8216_priv priv; + +	u8 id, rev; +	u32 val; + +	priv.phy = pdev; +	val = ar8216_mii_read(&priv, AR8216_REG_CTRL); +	rev = val & 0xff; +	id = (val >> 8) & 0xff; +	if ((id != 1) || (rev != 1)) +		return -ENODEV; + +	return 0; +} + +static void +ar8216_remove(struct phy_device *pdev) +{ +	struct ar8216_priv *priv = pdev->priv; +	struct net_device *dev = pdev->attached_dev; + +	if (!priv) +		return; + +	if (priv->ndo_old && dev) +		dev->netdev_ops = priv->ndo_old; +	unregister_switch(&priv->dev); +	kfree(priv); +} + +/* template */ +static struct switch_dev athdev = { +	.name = "Atheros AR8216", +	.cpu_port = AR8216_PORT_CPU, +	.ports = AR8216_NUM_PORTS, +	.vlans = AR8216_NUM_VLANS, +	.attr_global = { +		.attr = ar8216_globals, +		.n_attr = ARRAY_SIZE(ar8216_globals), +	}, +	.attr_port = { +		.attr = ar8216_port, +		.n_attr = ARRAY_SIZE(ar8216_port), +	}, +	.attr_vlan = { +		.attr = ar8216_vlan, +		.n_attr = ARRAY_SIZE(ar8216_vlan), +	}, +	.get_port_pvid = ar8216_get_pvid, +	.set_port_pvid = ar8216_set_pvid, +	.get_vlan_ports = ar8216_get_ports, +	.set_vlan_ports = ar8216_set_ports, +	.apply_config = ar8216_hw_apply, +	.reset_switch = ar8216_reset_switch, +}; + +static struct phy_driver ar8216_driver = { +	.name		= "Atheros AR8216", +	.features	= PHY_BASIC_FEATURES, +	.probe		= ar8216_probe, +	.remove		= ar8216_remove, +	.config_init	= &ar8216_config_init, +	.config_aneg	= &ar8216_config_aneg, +	.read_status	= &ar8216_read_status, +	.driver		= { .owner = THIS_MODULE }, +}; + +int __init +ar8216_init(void) +{ +	return phy_driver_register(&ar8216_driver); +} + +void __exit +ar8216_exit(void) +{ +	phy_driver_unregister(&ar8216_driver); +} + +module_init(ar8216_init); +module_exit(ar8216_exit); +MODULE_LICENSE("GPL"); + diff --git a/target/linux/generic-2.6/files-2.6.30/drivers/net/phy/mvswitch.c b/target/linux/generic-2.6/files-2.6.30/drivers/net/phy/mvswitch.c new file mode 100644 index 000000000..7aed70d47 --- /dev/null +++ b/target/linux/generic-2.6/files-2.6.30/drivers/net/phy/mvswitch.c @@ -0,0 +1,467 @@ +/* + * Marvell 88E6060 switch driver + * Copyright (c) 2008 Felix Fietkau <nbd@openwrt.org> + * + * 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 <linux/kernel.h> +#include <linux/string.h> +#include <linux/errno.h> +#include <linux/unistd.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> +#include <linux/spinlock.h> +#include <linux/mm.h> +#include <linux/module.h> +#include <linux/mii.h> +#include <linux/ethtool.h> +#include <linux/phy.h> +#include <linux/if_vlan.h> + +#include <asm/io.h> +#include <asm/irq.h> +#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 { +	const struct net_device_ops *ndo_old; +	struct net_device_ops ndo; +	struct vlan_group *grp; +	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 int +mvswitch_mangle_tx(struct sk_buff *skb, struct net_device *dev) +{ +	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 priv->ndo_old->ndo_start_xmit(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); +	return 0; +} + +static int +mvswitch_mangle_rx(struct sk_buff *skb, int napi) +{ +	struct mvswitch_priv *priv; +	struct net_device *dev; +	int vlan = -1; +	unsigned char *buf; +	int i; + +	dev = skb->dev; +	if (!dev) +		goto error; + +	priv = dev->phy_ptr; +	if (!priv) +		goto error; + +	if (!priv->grp) +		goto error; + +#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 ((1 << buf[1]) & priv->vlans[i]) +			vlan = 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 +		return vlan_hwaccel_rx(skb, priv->grp, vlan); + +error: +	/* no vlan? eat the packet! */ +	dev_kfree_skb_any(skb); +	return 0; +} + + +static int +mvswitch_netif_rx(struct sk_buff *skb) +{ +	return mvswitch_mangle_rx(skb, 0); +} + +static int +mvswitch_netif_receive_skb(struct sk_buff *skb) +{ +	return mvswitch_mangle_rx(skb, 1); +} + + +static void +mvswitch_vlan_rx_register(struct net_device *dev, struct vlan_group *grp) +{ +	struct mvswitch_priv *priv = dev->phy_ptr; +	priv->grp = 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) +{ +	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; +	dev->irq = PHY_POLL; + +	/* 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 +	); + +	/* hook into the tx function */ +	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; +#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 *pdev) +{ +	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; +} + +static int +mvswitch_config_aneg(struct phy_device *phydev) +{ +	return 0; +} + +static void +mvswitch_remove(struct phy_device *pdev) +{ +	struct mvswitch_priv *priv = to_mvsw(pdev); +	struct net_device *dev = pdev->attached_dev; + +	/* restore old netdev ops */ +	if (priv->ndo_old && dev) +		dev->netdev_ops = priv->ndo_old; +	dev->vlan_rx_register = NULL; +	dev->vlan_rx_kill_vid = NULL; +	dev->phy_ptr = NULL; +	dev->features &= ~NETIF_F_HW_VLAN_RX; +	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, +	.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); | 
