diff options
| author | florian <florian@3c298f89-4303-0410-b956-a3cf2f4a3e73> | 2010-09-04 18:06:01 +0000 | 
|---|---|---|
| committer | florian <florian@3c298f89-4303-0410-b956-a3cf2f4a3e73> | 2010-09-04 18:06:01 +0000 | 
| commit | 14d155196f66542c7b91aece78bc0eac5e2f017f (patch) | |
| tree | f8acea433f24b13f4eb4542a10a1f6628ef6f7c2 /target/linux/generic/files/drivers/net/phy | |
| parent | c6d4a05eeb9a303115b012fa048003a6f516070c (diff) | |
[kernel] add switch driver for Lantiq PSB6970
Tested on ifxmips based board. The PSB6970 is also known as the Infineon Tantos switch.
Signed-off-by: Ithamar R. Adema <ithamar.adema@team-embedded.nl>
git-svn-id: svn://svn.openwrt.org/openwrt/trunk@22914 3c298f89-4303-0410-b956-a3cf2f4a3e73
Diffstat (limited to 'target/linux/generic/files/drivers/net/phy')
| -rw-r--r-- | target/linux/generic/files/drivers/net/phy/psb6970.c | 438 | 
1 files changed, 438 insertions, 0 deletions
diff --git a/target/linux/generic/files/drivers/net/phy/psb6970.c b/target/linux/generic/files/drivers/net/phy/psb6970.c new file mode 100644 index 000000000..b9280540c --- /dev/null +++ b/target/linux/generic/files/drivers/net/phy/psb6970.c @@ -0,0 +1,438 @@ +/* + * Lantiq PSB6970 (Tantos) Switch driver + * + * Copyright (c) 2009,2010 Team Embedded. + * + * 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. + * + * The switch programming done in this driver follows the  + * "Ethernet Traffic Separation using VLAN" Application Note as + * published by Lantiq. + */ + +#include <linux/module.h> +#include <linux/netdevice.h> +#include <linux/switch.h> +#include <linux/phy.h> + +#define PSB6970_MAX_VLANS		16 +#define PSB6970_NUM_PORTS		7 +#define PSB6970_DEFAULT_PORT_CPU	6 +#define PSB6970_IS_CPU_PORT(x)		((x) > 4) + +#define PHYADDR(_reg)		((_reg >> 5) & 0xff), (_reg & 0x1f) + +/* --- Identification --- */ +#define PSB6970_CI0		0x0100 +#define PSB6970_CI0_MASK	0x000f +#define PSB6970_CI1		0x0101 +#define PSB6970_CI1_VAL		0x2599 +#define PSB6970_CI1_MASK	0xffff + +/* --- VLAN filter table --- */ +#define PSB6970_VFxL(i)		((i)*2+0x10)	/* VLAN Filter Low */ +#define PSB6970_VFxL_VV		(1 << 15)	/* VLAN_Valid */ + +#define PSB6970_VFxH(i)		((i)*2+0x11)	/* VLAN Filter High */ +#define PSB6970_VFxH_TM_SHIFT	7		/* Tagged Member */ + +/* --- Port registers --- */ +#define PSB6970_EC(p)		((p)*0x20+2)	/* Extended Control */ +#define PSB6970_EC_IFNTE	(1 << 1)	/* Input Force No Tag Enable */ + +#define PSB6970_PBVM(p)		((p)*0x20+3)	/* Port Base VLAN Map */ +#define PSB6970_PBVM_VMCE	(1 << 8) +#define PSB6970_PBVM_AOVTP	(1 << 9) +#define PSB6970_PBVM_VSD	(1 << 10) +#define PSB6970_PBVM_VC		(1 << 11)	/* VID Check with VID table */ +#define PSB6970_PBVM_TBVE	(1 << 13)	/* Tag-Based VLAN enable */ + +#define PSB6970_DVID(p)		((p)*0x20+4)	/* Default VLAN ID & Priority */ + +struct psb6970_priv { +	struct switch_dev dev; +	struct phy_device *phy; +	u16 (*read) (struct phy_device* phydev, int reg); +	void (*write) (struct phy_device* phydev, int reg, u16 val); +	struct mutex reg_mutex; + +	/* all fields below are cleared on reset */ +	bool vlan; +	u16 vlan_id[PSB6970_MAX_VLANS]; +	u8 vlan_table[PSB6970_MAX_VLANS]; +	u8 vlan_tagged; +	u16 pvid[PSB6970_NUM_PORTS]; +}; + +#define to_psb6970(_dev) container_of(_dev, struct psb6970_priv, dev) + +static u16 psb6970_mii_read(struct phy_device *phydev, int reg) +{ +	return phydev->bus->read(phydev->bus, PHYADDR(reg)); +} + +static void psb6970_mii_write(struct phy_device *phydev, int reg, u16 val) +{ +	phydev->bus->write(phydev->bus, PHYADDR(reg), val); +} + +static int +psb6970_set_vlan(struct switch_dev *dev, const struct switch_attr *attr, +		 struct switch_val *val) +{ +	struct psb6970_priv *priv = to_psb6970(dev); +	priv->vlan = !!val->value.i; +	return 0; +} + +static int +psb6970_get_vlan(struct switch_dev *dev, const struct switch_attr *attr, +		 struct switch_val *val) +{ +	struct psb6970_priv *priv = to_psb6970(dev); +	val->value.i = priv->vlan; +	return 0; +} + +static int psb6970_set_pvid(struct switch_dev *dev, int port, int vlan) +{ +	struct psb6970_priv *priv = to_psb6970(dev); + +	/* make sure no invalid PVIDs get set */ +	if (vlan >= dev->vlans) +		return -EINVAL; + +	priv->pvid[port] = vlan; +	return 0; +} + +static int psb6970_get_pvid(struct switch_dev *dev, int port, int *vlan) +{ +	struct psb6970_priv *priv = to_psb6970(dev); +	*vlan = priv->pvid[port]; +	return 0; +} + +static int +psb6970_set_vid(struct switch_dev *dev, const struct switch_attr *attr, +		struct switch_val *val) +{ +	struct psb6970_priv *priv = to_psb6970(dev); +	priv->vlan_id[val->port_vlan] = val->value.i; +	return 0; +} + +static int +psb6970_get_vid(struct switch_dev *dev, const struct switch_attr *attr, +		struct switch_val *val) +{ +	struct psb6970_priv *priv = to_psb6970(dev); +	val->value.i = priv->vlan_id[val->port_vlan]; +	return 0; +} + +static struct switch_attr psb6970_globals[] = { +	{ +	 .type = SWITCH_TYPE_INT, +	 .name = "enable_vlan", +	 .description = "Enable VLAN mode", +	 .set = psb6970_set_vlan, +	 .get = psb6970_get_vlan, +	 .max = 1}, +}; + +static struct switch_attr psb6970_port[] = { +}; + +static struct switch_attr psb6970_vlan[] = { +	{ +	 .type = SWITCH_TYPE_INT, +	 .name = "pvid", +	 .description = "VLAN ID", +	 .set = psb6970_set_vid, +	 .get = psb6970_get_vid, +	 .max = 4094, +	 }, +}; + +static int psb6970_get_ports(struct switch_dev *dev, struct switch_val *val) +{ +	struct psb6970_priv *priv = to_psb6970(dev); +	u8 ports = priv->vlan_table[val->port_vlan]; +	int i; + +	val->len = 0; +	for (i = 0; i < PSB6970_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 psb6970_set_ports(struct switch_dev *dev, struct switch_val *val) +{ +	struct psb6970_priv *priv = to_psb6970(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 < PSB6970_MAX_VLANS; j++) { +				if (j == val->port_vlan) +					continue; +				priv->vlan_table[j] &= ~(1 << p->id); +			} +		} + +		*vt |= 1 << p->id; +	} +	return 0; +} + +static int psb6970_hw_apply(struct switch_dev *dev) +{ +	struct psb6970_priv *priv = to_psb6970(dev); +	int i, j; + +	mutex_lock(&priv->reg_mutex); + +	if (priv->vlan) { +		/* into the vlan translation unit */ +		for (j = 0; j < PSB6970_MAX_VLANS; j++) { +			u8 vp = priv->vlan_table[j]; + +			if (vp) { +				priv->write(priv->phy, PSB6970_VFxL(j), +					    PSB6970_VFxL_VV | priv->vlan_id[j]); +				priv->write(priv->phy, PSB6970_VFxH(j), +					    ((vp & priv-> +					      vlan_tagged) << +					     PSB6970_VFxH_TM_SHIFT) | vp); +			} else	/* clear VLAN Valid flag for unused vlans */ +				priv->write(priv->phy, PSB6970_VFxL(j), 0); + +		} +	} + +	/* update the port destination mask registers and tag settings */ +	for (i = 0; i < PSB6970_NUM_PORTS; i++) { +		int dvid = 1, pbvm = 0x7f | PSB6970_PBVM_VSD, ec = 0; + +		if (priv->vlan) { +			ec = PSB6970_EC_IFNTE; +			dvid = priv->vlan_id[priv->pvid[i]]; +			pbvm |= PSB6970_PBVM_TBVE | PSB6970_PBVM_VMCE; + +			if ((i << 1) & priv->vlan_tagged) +				pbvm |= PSB6970_PBVM_AOVTP | PSB6970_PBVM_VC; +		} + +		priv->write(priv->phy, PSB6970_PBVM(i), pbvm); + +		if (!PSB6970_IS_CPU_PORT(i)) { +			priv->write(priv->phy, PSB6970_EC(i), ec); +			priv->write(priv->phy, PSB6970_DVID(i), dvid); +		} +	} + +	mutex_unlock(&priv->reg_mutex); +	return 0; +} + +static int psb6970_reset_switch(struct switch_dev *dev) +{ +	struct psb6970_priv *priv = to_psb6970(dev); +	int i; + +	mutex_lock(&priv->reg_mutex); + +	memset(&priv->vlan, 0, sizeof(struct psb6970_priv) - +	       offsetof(struct psb6970_priv, vlan)); + +	for (i = 0; i < PSB6970_MAX_VLANS; i++) +		priv->vlan_id[i] = i; + +	mutex_unlock(&priv->reg_mutex); + +	return psb6970_hw_apply(dev); +} + +static const struct switch_dev_ops psb6970_ops = { +	.attr_global = { +			.attr = psb6970_globals, +			.n_attr = ARRAY_SIZE(psb6970_globals), +			}, +	.attr_port = { +		      .attr = psb6970_port, +		      .n_attr = ARRAY_SIZE(psb6970_port), +		      }, +	.attr_vlan = { +		      .attr = psb6970_vlan, +		      .n_attr = ARRAY_SIZE(psb6970_vlan), +		      }, +	.get_port_pvid = psb6970_get_pvid, +	.set_port_pvid = psb6970_set_pvid, +	.get_vlan_ports = psb6970_get_ports, +	.set_vlan_ports = psb6970_set_ports, +	.apply_config = psb6970_hw_apply, +	.reset_switch = psb6970_reset_switch, +}; + +static int psb6970_config_init(struct phy_device *pdev) +{ +	struct psb6970_priv *priv; +	struct net_device *dev = pdev->attached_dev; +	struct switch_dev *swdev; +	int ret; + +	priv = kzalloc(sizeof(struct psb6970_priv), GFP_KERNEL); +	if (priv == NULL) +		return -ENOMEM; + +	priv->phy = pdev; + +	if (pdev->addr == 0) +		printk(KERN_INFO "%s: psb6970 switch driver attached.\n", +		       pdev->attached_dev->name); + +	if (pdev->addr != 0) { +		kfree(priv); +		return 0; +	} + +	pdev->supported = pdev->advertising = SUPPORTED_100baseT_Full; + +	mutex_init(&priv->reg_mutex); +	priv->read = psb6970_mii_read; +	priv->write = psb6970_mii_write; + +	pdev->priv = priv; + +	swdev = &priv->dev; +	swdev->cpu_port = PSB6970_DEFAULT_PORT_CPU; +	swdev->ops = &psb6970_ops; + +	swdev->name = "Lantiq PSB6970"; +	swdev->vlans = PSB6970_MAX_VLANS; +	swdev->ports = PSB6970_NUM_PORTS; + +	if ((ret = register_switch(&priv->dev, pdev->attached_dev)) < 0) { +		kfree(priv); +		goto done; +	} + +	ret = psb6970_reset_switch(&priv->dev); +	if (ret) { +		kfree(priv); +		goto done; +	} + +	dev->phy_ptr = priv; + +done: +	return ret; +} + +static int psb6970_read_status(struct phy_device *phydev) +{ +	phydev->speed = SPEED_100; +	phydev->duplex = DUPLEX_FULL; +	phydev->link = 1; + +	phydev->state = PHY_RUNNING; +	netif_carrier_on(phydev->attached_dev); +	phydev->adjust_link(phydev->attached_dev); + +	return 0; +} + +static int psb6970_config_aneg(struct phy_device *phydev) +{ +	return 0; +} + +static int psb6970_probe(struct phy_device *pdev) +{ +	return 0; +} + +static void psb6970_remove(struct phy_device *pdev) +{ +	struct psb6970_priv *priv = pdev->priv; + +	if (!priv) +		return; + +	if (pdev->addr == 0) +		unregister_switch(&priv->dev); +	kfree(priv); +} + +static int psb6970_fixup(struct phy_device *dev) +{ +	struct mii_bus *bus = dev->bus; +	u16 reg; + +	/* look for the switch on the bus */ +	reg = bus->read(bus, PHYADDR(PSB6970_CI1)) & PSB6970_CI1_MASK; +	if (reg != PSB6970_CI1_VAL) +		return 0; + +	dev->phy_id = (reg << 16); +	dev->phy_id |= bus->read(bus, PHYADDR(PSB6970_CI0)) & PSB6970_CI0_MASK; + +	return 0; +} + +static struct phy_driver psb6970_driver = { +	.name = "Lantiq PSB6970", +	.phy_id = PSB6970_CI1_VAL << 16, +	.phy_id_mask = 0xffff0000, +	.features = PHY_BASIC_FEATURES, +	.probe = psb6970_probe, +	.remove = psb6970_remove, +	.config_init = &psb6970_config_init, +	.config_aneg = &psb6970_config_aneg, +	.read_status = &psb6970_read_status, +	.driver = {.owner = THIS_MODULE}, +}; + +int __init psb6970_init(void) +{ +	phy_register_fixup_for_id(PHY_ANY_ID, psb6970_fixup); +	return phy_driver_register(&psb6970_driver); +} + +module_init(psb6970_init); + +void __exit psb6970_exit(void) +{ +	phy_driver_unregister(&psb6970_driver); +} + +module_exit(psb6970_exit); + +MODULE_DESCRIPTION("Lantiq PSB6970 Switch"); +MODULE_AUTHOR("Ithamar R. Adema <ithamar.adema@team-embedded.nl>"); +MODULE_LICENSE("GPL");  | 
