mirror of
https://github.com/openwrt/openwrt.git
synced 2024-12-23 15:32:33 +00:00
1a52d11d38
add backport patches for older kernels Signed-off-by: Felix Fietkau <nbd@nbd.name>
442 lines
10 KiB
C
442 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)
|
|
{
|
|
struct mii_bus *bus = phydev->mdio.bus;
|
|
|
|
return bus->read(bus, PHYADDR(reg));
|
|
}
|
|
|
|
static void psb6970_mii_write(struct phy_device *phydev, int reg, u16 val)
|
|
{
|
|
struct mii_bus *bus = phydev->mdio.bus;
|
|
|
|
bus->write(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->mdio.addr == 0)
|
|
printk(KERN_INFO "%s: psb6970 switch driver attached.\n",
|
|
pdev->attached_dev->name);
|
|
|
|
if (pdev->mdio.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->mdio.addr == 0)
|
|
unregister_switch(&priv->dev);
|
|
kfree(priv);
|
|
}
|
|
|
|
static int psb6970_fixup(struct phy_device *dev)
|
|
{
|
|
struct mii_bus *bus = dev->mdio.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,
|
|
};
|
|
|
|
int __init psb6970_init(void)
|
|
{
|
|
phy_register_fixup_for_id(PHY_ANY_ID, psb6970_fixup);
|
|
return phy_driver_register(&psb6970_driver, THIS_MODULE);
|
|
}
|
|
|
|
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");
|