| /* |
| * 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 <net/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"); |