[kernel] add switch driver for Lantiq PSB6970
authorFlorian Fainelli <florian@openwrt.org>
Sat, 4 Sep 2010 18:06:01 +0000 (18:06 +0000)
committerFlorian Fainelli <florian@openwrt.org>
Sat, 4 Sep 2010 18:06:01 +0000 (18:06 +0000)
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>
SVN-Revision: 22914

target/linux/generic/config-2.6.33
target/linux/generic/config-2.6.34
target/linux/generic/config-2.6.35
target/linux/generic/files/drivers/net/phy/psb6970.c [new file with mode: 0644]
target/linux/generic/patches-2.6.33/692-phy_psb6970.patch [new file with mode: 0644]
target/linux/generic/patches-2.6.34/692-phy_psb6970.patch [new file with mode: 0644]
target/linux/generic/patches-2.6.35/692-phy_psb6970.patch [new file with mode: 0644]

index 15832bb..30027e6 100644 (file)
@@ -1824,6 +1824,7 @@ CONFIG_PROC_KCORE=y
 CONFIG_PROC_SYSCTL=y
 # CONFIG_PROFILING is not set
 # CONFIG_PROVE_LOCKING is not set
+# CONFIG_PSB6970_PHY is not set
 # CONFIG_QEMU is not set
 # CONFIG_QLA3XXX is not set
 # CONFIG_QNX4FS_FS is not set
index 3b58a1a..d08c344 100644 (file)
@@ -1858,6 +1858,7 @@ CONFIG_PROC_KCORE=y
 CONFIG_PROC_SYSCTL=y
 # CONFIG_PROFILING is not set
 # CONFIG_PROVE_LOCKING is not set
+# CONFIG_PSB6970_PHY is not set
 # CONFIG_QEMU is not set
 # CONFIG_QLA3XXX is not set
 # CONFIG_QNX4FS_FS is not set
index 6b02f43..048134f 100644 (file)
@@ -1917,6 +1917,7 @@ CONFIG_PROC_KCORE=y
 CONFIG_PROC_SYSCTL=y
 # CONFIG_PROFILING is not set
 # CONFIG_PROVE_LOCKING is not set
+# CONFIG_PSB6970_PHY is not set
 # CONFIG_QEMU is not set
 # CONFIG_QLA3XXX is not set
 # CONFIG_QLCNIC is not set
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 (file)
index 0000000..b928054
--- /dev/null
@@ -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");
diff --git a/target/linux/generic/patches-2.6.33/692-phy_psb6970.patch b/target/linux/generic/patches-2.6.33/692-phy_psb6970.patch
new file mode 100644 (file)
index 0000000..a2f0989
--- /dev/null
@@ -0,0 +1,23 @@
+--- a/drivers/net/phy/Makefile
++++ b/drivers/net/phy/Makefile
+@@ -15,6 +15,7 @@ obj-$(CONFIG_BROADCOM_PHY)   += broadcom.o
+ obj-$(CONFIG_BCM63XX_PHY)     += bcm63xx.o
+ obj-$(CONFIG_ICPLUS_PHY)      += icplus.o
+ obj-$(CONFIG_ADM6996_PHY)     += adm6996.o
++obj-$(CONFIG_PSB6970_PHY)     += psb6970.o
+ obj-$(CONFIG_MVSWITCH_PHY)    += mvswitch.o
+ obj-$(CONFIG_IP17XX_PHY)      += ip17xx.o
+ obj-$(CONFIG_REALTEK_PHY)     += realtek.o
+--- a/drivers/net/phy/Kconfig
++++ b/drivers/net/phy/Kconfig
+@@ -99,6 +99,10 @@ config ADM6996_PHY
+       ---help---
+         Currently supports the ADM6996F switch
++config PSB6970_PHY
++      tristate "Driver for Lantiq PSB6970 (Tantos) switch"
++      select SWCONFIG
++
+ config MVSWITCH_PHY
+       tristate "Driver for Marvell 88E6060 switches"
diff --git a/target/linux/generic/patches-2.6.34/692-phy_psb6970.patch b/target/linux/generic/patches-2.6.34/692-phy_psb6970.patch
new file mode 100644 (file)
index 0000000..365e5ac
--- /dev/null
@@ -0,0 +1,23 @@
+--- a/drivers/net/phy/Makefile
++++ b/drivers/net/phy/Makefile
+@@ -15,6 +15,7 @@ obj-$(CONFIG_BROADCOM_PHY)   += broadcom.o
+ obj-$(CONFIG_BCM63XX_PHY)     += bcm63xx.o
+ obj-$(CONFIG_ICPLUS_PHY)      += icplus.o
+ obj-$(CONFIG_ADM6996_PHY)     += adm6996.o
++obj-$(CONFIG_PSB6970_PHY)     += psb6970.o
+ obj-$(CONFIG_MVSWITCH_PHY)    += mvswitch.o
+ obj-$(CONFIG_IP17XX_PHY)      += ip17xx.o
+ obj-$(CONFIG_REALTEK_PHY)     += realtek.o
+--- a/drivers/net/phy/Kconfig
++++ b/drivers/net/phy/Kconfig
+@@ -104,6 +104,10 @@ config ADM6996_PHY
+       ---help---
+         Currently supports the ADM6996F switch
++config PSB6970_PHY
++      tristate "Driver for Lantiq PSB6970 (Tantos) switch"
++      select SWCONFIG
++
+ config MVSWITCH_PHY
+       tristate "Driver for Marvell 88E6060 switches"
diff --git a/target/linux/generic/patches-2.6.35/692-phy_psb6970.patch b/target/linux/generic/patches-2.6.35/692-phy_psb6970.patch
new file mode 100644 (file)
index 0000000..365e5ac
--- /dev/null
@@ -0,0 +1,23 @@
+--- a/drivers/net/phy/Makefile
++++ b/drivers/net/phy/Makefile
+@@ -15,6 +15,7 @@ obj-$(CONFIG_BROADCOM_PHY)   += broadcom.o
+ obj-$(CONFIG_BCM63XX_PHY)     += bcm63xx.o
+ obj-$(CONFIG_ICPLUS_PHY)      += icplus.o
+ obj-$(CONFIG_ADM6996_PHY)     += adm6996.o
++obj-$(CONFIG_PSB6970_PHY)     += psb6970.o
+ obj-$(CONFIG_MVSWITCH_PHY)    += mvswitch.o
+ obj-$(CONFIG_IP17XX_PHY)      += ip17xx.o
+ obj-$(CONFIG_REALTEK_PHY)     += realtek.o
+--- a/drivers/net/phy/Kconfig
++++ b/drivers/net/phy/Kconfig
+@@ -104,6 +104,10 @@ config ADM6996_PHY
+       ---help---
+         Currently supports the ADM6996F switch
++config PSB6970_PHY
++      tristate "Driver for Lantiq PSB6970 (Tantos) switch"
++      select SWCONFIG
++
+ config MVSWITCH_PHY
+       tristate "Driver for Marvell 88E6060 switches"