439 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			439 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/*
 | 
						|
 * 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 = "vid",
 | 
						|
	 .description = "VLAN ID (0-4094)",
 | 
						|
	 .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");
 |