Hello developers,

I have written a switch driver for the Infineon ADM6996 switch chip, which in my
case is in a D-Link DSL-G624T modem/router. It implements 802.1Q based VLAN
support, via the swconfig interface. Other fancy features of the switch chip are
not (yet) implemented.

Because the datasheet isn't that well written, it took some experimentation to
get the functionality right. The real functionality of the driver is obviously
in the setting of the necessary bits in the configuration registers of the chip.
Everything else is pretty much boilerplate code, and can easily be changed if
something is deemed not to be the best way.

So let's move to one such design decision which really is debatable.

The switch chip allows 16 VLAN definitions. But any such definition can be for
any 12-bits VLAN ID. The swconfig infrastructure seems to unify the concept of
an entry in the list of VLAN definitions and it's VLAN ID. This shows strongest
in the fact that when you do the following invocation, for example:

# swconfig dev eth0 vlan 2 set ports '0 1 2 3t 4t 5t'

It will not only invoke the responsible handler "set_vlan_ports" from the struct
switch_dev_ops, but also automatically the handler "set_port_pvid" from same
struct, setting the Primary VLAN ID for the untagged ports 0, 1 and 2 to VLAN ID
2. Mind you, that is the actual 12-bit identifier from 802.1Q, which is
independent of the fact that it is entry number 2 in the table of VLAN 
definitions.

This is all fine if all the VLAN IDs we need are 0-15, but it is a perfectly
valid use case to have VLAN IDs with much higher numbers. The ADM6996 chip can
handle that use case fine, so I did not want to exclude it.

I've seen similar things in other switch chips in OpenWRT while browsing, where
it was coined "4k VLANs". It wasn't completely clear to me how it was handled
there, however.

I chose to handle this case as follows:

In addition to the standard attributes, vlan in switch_dev_ops has an attribute
named "vid", which gives the VLAN ID for that entry in the table of VLAN
definitions. By default it is set to the number of the entry. So by default it 
is:

# swconfig dev eth0 show

...
VLAN 0:
        vid: 0
        ports:
VLAN 1:
        vid: 1
        ports:
VLAN 2:
        vid: 2
        ports:
...

VLAN 15:
        vid: 15
        ports:

This means that as long as people don't change the "vid", presumably because
they don't need more than the 16 lowest VLAN IDs, it all works completely
intuitively, and Primary VLAN IDs for the ports are set correctly automatically.
Once you change the "vid" for a vlan, you become responsible for correcting the
mistakes the automatic invocation of "set_port_pvid" makes.

But as I said, if there is a more elegant solution, it is easily changed in the
code.

A change with respect to the original code (which only detected the switch chip,
but had almost no functionality), is that I moved allocation of the
phy_device->priv structure from adm6996_probe() to adm6996_config_init(). The
reason is that config_init() now has a chance to fail (registering swconfig
interface), and I wasn't sure about free()ing of the private structure. So I
simply copied the behaviour from ar8216.c.

Another change is that I changed the number of ports from 5 to 6. The thing is,
it has 6 ports, of which number 5 (zero-based counting) is connected to the CPU.
You want to be able to config VLANs for that port as well. On the D-Link modem,
port number 4 is not connected to anything. The datasheet of the ADM6996 chip
suggests using that port as WAN port, and the D-Link does not have an Ethernet
WAN port, so it makes sense.

The original code sets the Primary VLAN ID of port 0, which it assumes is the
WAN port, to 1, and the others to 0. I have left it like this; I also initialize
the swconfig Primary VLAN ID to that. But I wonder why this was done; in the
original code, it was effectively a no-op, since it would not make a difference
unless a bunch of other registers were also changed.

Finally, I'd like to make a little personal note. To my great regret, I have
very little free time. So it might take a while before I get back to you when
you ask something or wish me to make an adjustment or improvement. This is very
unfortunate, and obviously I do not ask you to wait for me on anything; I only
wish to contribute, not stall. I cannot change my circumstances, and simply have
to accept them. Nonetheless, I hope to give the community useful code.

Oh, and this is my first code contribution to a community project. Do not
hesitate to point out what I should do differently, if I got something wrong.
Thank you for OpenWRT!

Signed-off-by: Peter Lebbing <pe...@digitalbrains.com>

---

Index: backfire/target/linux/generic-2.6/files/drivers/net/phy/adm6996.c
===================================================================
--- backfire/target/linux/generic-2.6/files/drivers/net/phy/adm6996.c   
(revision 24930)
+++ backfire/target/linux/generic-2.6/files/drivers/net/phy/adm6996.c   
(working copy)
@@ -1,12 +1,18 @@
 /*
  * ADM6996 switch driver
  *
+ * swconfig interface based on ar8216.c
+ *
  * Copyright (c) 2008 Felix Fietkau <n...@openwrt.org>
+ * VLAN support Copyright (c) 2010, 2011 Peter Lebbing 
<pe...@digitalbrains.com>
  *
  * 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
  */
+
+/*#define DEBUG 1 */
+
 #include <linux/kernel.h>
 #include <linux/string.h>
 #include <linux/errno.h>
@@ -24,6 +30,7 @@
 #include <linux/mii.h>
 #include <linux/ethtool.h>
 #include <linux/phy.h>
+#include <linux/switch.h>
 
 #include <asm/io.h>
 #include <asm/irq.h>
@@ -35,24 +42,42 @@
 MODULE_LICENSE("GPL");
 
 struct adm6996_priv {
+       struct switch_dev dev;
+       struct phy_device *phydev;
+
+       bool enable_vlan;
+       bool vlan_enabled;      /* Current hardware state */
+
+#ifdef DEBUG
+       u16 addr;               /* Debugging: register address to operate on */
+#endif
+
+       u16 pvid[ADM_PHY_PORTS];        /* Primary VLAN ID */
+
+       u16 vlan_id[ADM_NUM_VLANS];
+       u8 vlan_table[ADM_NUM_VLANS];   /* bitmap, 1 = port is member */
+       u8 vlan_tagged[ADM_NUM_VLANS];  /* bitmap, 1 = tagged member */
+
+       struct mutex reg_mutex;
+
        /* use abstraction for regops, we want to add gpio support in the 
future */
-       u16 (*read)(struct phy_device *phydev, enum admreg reg);
-       void (*write)(struct phy_device *phydev, enum admreg reg, u16 val);
+       u16 (*read) (struct phy_device *phydev, enum admreg reg);
+       void (*write) (struct phy_device *phydev, enum admreg reg, u16 val);
 };
 
-#define to_adm(_phy) ((struct adm6996_priv *) (_phy)->priv)
+#define to_adm(_dev) container_of(_dev, struct adm6996_priv, dev)
+#define phy_to_adm(_phy) ((struct adm6996_priv *) (_phy)->priv)
 
-
 static inline u16
 r16(struct phy_device *pdev, enum admreg reg)
 {
-       return to_adm(pdev)->read(pdev, reg);
+       return phy_to_adm(pdev)->read(pdev, reg);
 }
 
 static inline void
 w16(struct phy_device *pdev, enum admreg reg, u16 val)
 {
-       to_adm(pdev)->write(pdev, reg, val);
+       phy_to_adm(pdev)->write(pdev, reg, val);
 }
 
 
@@ -68,27 +93,473 @@
        phydev->bus->write(phydev->bus, PHYADDR(reg), val);
 }
 
+static int
+adm6996_set_enable_vlan(struct switch_dev *dev, const struct switch_attr *attr,
+                       struct switch_val *val)
+{
+       struct adm6996_priv *priv = to_adm(dev);
 
-static int adm6996_config_init(struct phy_device *pdev)
+       if (val->value.i > 1)
+               return -EINVAL;
+
+       priv->enable_vlan = val->value.i;
+
+       return 0;
+};
+
+static int
+adm6996_get_enable_vlan(struct switch_dev *dev, const struct switch_attr *attr,
+                       struct switch_val *val)
 {
+       struct adm6996_priv *priv = to_adm(dev);
+
+       val->value.i = priv->enable_vlan;
+
+       return 0;
+};
+
+#ifdef DEBUG
+
+static int
+adm6996_set_addr(struct switch_dev *dev, const struct switch_attr *attr,
+                struct switch_val *val)
+{
+       struct adm6996_priv *priv = to_adm(dev);
+
+       if (val->value.i > 1023)
+               return -EINVAL;
+
+       priv->addr = val->value.i;
+
+       return 0;
+};
+
+static int
+adm6996_get_addr(struct switch_dev *dev, const struct switch_attr *attr,
+                struct switch_val *val)
+{
+       struct adm6996_priv *priv = to_adm(dev);
+
+       val->value.i = priv->addr;
+
+       return 0;
+};
+
+static int
+adm6996_set_data(struct switch_dev *dev, const struct switch_attr *attr,
+                struct switch_val *val)
+{
+       struct adm6996_priv *priv = to_adm(dev);
+
+       if (val->value.i > 65535)
+               return -EINVAL;
+
+       w16(priv->phydev, priv->addr, val->value.i);
+
+       return 0;
+};
+
+static int
+adm6996_get_data(struct switch_dev *dev, const struct switch_attr *attr,
+                struct switch_val *val)
+{
+       struct adm6996_priv *priv = to_adm(dev);
+
+       val->value.i = r16(priv->phydev, priv->addr);
+
+       return 0;
+};
+
+#endif
+
+static int
+adm6996_set_pvid(struct switch_dev *dev, int port, int vlan)
+{
+       struct adm6996_priv *priv = to_adm(dev);
+
+       dev_dbg (&priv->phydev->dev, "set_pvid port %d vlan %d\n", port
+                       , vlan);
+
+       if (vlan > ADM_VLAN_MAX_ID)
+               return -EINVAL;
+
+       priv->pvid[port] = vlan;
+
+       return 0;
+}
+
+static int
+adm6996_get_pvid(struct switch_dev *dev, int port, int *vlan)
+{
+       struct adm6996_priv *priv = to_adm(dev);
+
+       dev_dbg (&priv->phydev->dev, "get_pvid port %d\n", port);
+       *vlan = priv->pvid[port];
+
+       return 0;
+}
+
+static int
+adm6996_set_vid(struct switch_dev *dev, const struct switch_attr *attr,
+               struct switch_val *val)
+{
+       struct adm6996_priv *priv = to_adm(dev);
+
+       dev_dbg (&priv->phydev->dev, "set_vid port %d vid %d\n", val->port_vlan,
+                       val->value.i);
+
+       if (val->value.i > ADM_VLAN_MAX_ID)
+               return -EINVAL;
+
+       priv->vlan_id[val->port_vlan] = val->value.i;
+
+       return 0;
+};
+
+static int
+adm6996_get_vid(struct switch_dev *dev, const struct switch_attr *attr,
+               struct switch_val *val)
+{
+       struct adm6996_priv *priv = to_adm(dev);
+
+       dev_dbg (&priv->phydev->dev, "get_vid port %d\n", val->port_vlan);
+
+       val->value.i = priv->vlan_id[val->port_vlan];
+
+       return 0;
+};
+
+static int
+adm6996_get_ports(struct switch_dev *dev, struct switch_val *val)
+{
+       struct adm6996_priv *priv = to_adm(dev);
+       u8 ports = priv->vlan_table[val->port_vlan];
+       u8 tagged = priv->vlan_tagged[val->port_vlan];
        int i;
 
+       dev_dbg (&priv->phydev->dev, "get_ports port_vlan %d\n",
+                       val->port_vlan);
+
+       val->len = 0;
+
+       for (i = 0; i < ADM_PHY_PORTS; i++) {
+               struct switch_port *p;
+
+               if (!(ports & (1 << i)))
+                       continue;
+
+               p = &val->value.ports[val->len++];
+               p->id = i;
+               if (tagged & (1 << i))
+                       p->flags = (1 << SWITCH_PORT_FLAG_TAGGED);
+               else
+                       p->flags = 0;
+       }
+
+       return 0;
+};
+
+static int
+adm6996_set_ports(struct switch_dev *dev, struct switch_val *val)
+{
+       struct adm6996_priv *priv = to_adm(dev);
+       u8 *ports = &priv->vlan_table[val->port_vlan];
+       u8 *tagged = &priv->vlan_tagged[val->port_vlan];
+       int i;
+
+       dev_dbg (&priv->phydev->dev, "set_ports port_vlan %d ports",
+                       val->port_vlan);
+
+       *ports = 0;
+       *tagged = 0;
+
+       for (i = 0; i < val->len; i++) {
+               struct switch_port *p = &val->value.ports[i];
+
+#ifdef DEBUG
+               printk(" %d%s", p->id,
+                      ((p->flags & (1 << SWITCH_PORT_FLAG_TAGGED)) ? "T" :
+                       ""));
+#endif
+
+               if (p->flags & (1 << SWITCH_PORT_FLAG_TAGGED))
+                       *tagged |= (1 << p->id);
+
+               *ports |= (1 << p->id);
+       }
+
+#ifdef DEBUG
+       printk("\n");
+#endif
+
+       return 0;
+};
+
+/*
+ * Precondition: reg_mutex must be held
+ */
+static void
+adm6996_enable_vlan(struct adm6996_priv *priv)
+{
+       u16 reg;
+
+       reg = r16(priv->phydev, ADM_OTBE_P2_PVID);
+       reg &= ~(ADM_OTBE_MASK);
+       w16(priv->phydev, ADM_OTBE_P2_PVID, reg);
+       reg = r16(priv->phydev, ADM_IFNTE);
+       reg &= ~(ADM_IFNTE_MASK);
+       w16(priv->phydev, ADM_IFNTE, reg);
+       reg = r16(priv->phydev, ADM_VID_CHECK);
+       reg |= ADM_VID_CHECK_MASK;
+       w16(priv->phydev, ADM_VID_CHECK, reg);
+       reg = r16(priv->phydev, ADM_SYSC0);
+       reg |= ADM_NTTE;
+       reg &= ~(ADM_RVID1);
+       w16(priv->phydev, ADM_SYSC0, reg);
+       reg = r16(priv->phydev, ADM_SYSC3);
+       reg |= ADM_TBV;
+       w16(priv->phydev, ADM_SYSC3, reg);
+
+};
+
+/*
+ * Disable VLANs
+ *
+ * Sets VLAN mapping for port-based VLAN with all ports connected to
+ * eachother (this is also the power-on default).
+ *
+ * Precondition: reg_mutex must be held
+ */
+static void
+adm6996_disable_vlan(struct adm6996_priv *priv)
+{
+       u16 reg;
+       int i;
+
+       for (i = 0; i < ADM_PHY_PORTS; i++) {
+               reg = ADM_VLAN_FILT_MEMBER_MASK;
+               w16(priv->phydev, ADM_VLAN_FILT_L(i), reg);
+               reg = ADM_VLAN_FILT_VALID | ADM_VLAN_FILT_VID(1);
+               w16(priv->phydev, ADM_VLAN_FILT_H(i), reg);
+       }
+
+       reg = r16(priv->phydev, ADM_OTBE_P2_PVID);
+       reg |= ADM_OTBE_MASK;
+       w16(priv->phydev, ADM_OTBE_P2_PVID, reg);
+       reg = r16(priv->phydev, ADM_IFNTE);
+       reg |= ADM_IFNTE_MASK;
+       w16(priv->phydev, ADM_IFNTE, reg);
+       reg = r16(priv->phydev, ADM_VID_CHECK);
+       reg &= ~(ADM_VID_CHECK_MASK);
+       w16(priv->phydev, ADM_VID_CHECK, reg);
+       reg = r16(priv->phydev, ADM_SYSC0);
+       reg &= ~(ADM_NTTE);
+       reg |= ADM_RVID1;
+       w16(priv->phydev, ADM_SYSC0, reg);
+       reg = r16(priv->phydev, ADM_SYSC3);
+       reg &= ~(ADM_TBV);
+       w16(priv->phydev, ADM_SYSC3, reg);
+}
+
+/*
+ * Precondition: reg_mutex must be held
+ */
+static void
+adm6996_apply_port_pvids(struct adm6996_priv *priv)
+{
+       u16 reg;
+       int i;
+
+       for (i = 0; i < ADM_PHY_PORTS; i++) {
+               reg = r16(priv->phydev, adm_portcfg[i]);
+               reg &= ~(ADM_PORTCFG_PVID_MASK);
+               reg |= ADM_PORTCFG_PVID(priv->pvid[i]);
+               w16(priv->phydev, adm_portcfg[i], reg);
+       }
+
+       w16(priv->phydev, ADM_P0_PVID, ADM_P0_PVID_VAL(priv->pvid[0]));
+       w16(priv->phydev, ADM_P1_PVID, ADM_P1_PVID_VAL(priv->pvid[1]));
+       reg = r16(priv->phydev, ADM_OTBE_P2_PVID);
+       reg &= ~(ADM_P2_PVID_MASK);
+       reg |= ADM_P2_PVID_VAL(priv->pvid[2]);
+       w16(priv->phydev, ADM_OTBE_P2_PVID, reg);
+       reg = ADM_P3_PVID_VAL(priv->pvid[3]);
+       reg |= ADM_P4_PVID_VAL(priv->pvid[4]);
+       w16(priv->phydev, ADM_P3_P4_PVID, reg);
+       w16(priv->phydev, ADM_P5_PVID, ADM_P5_PVID_VAL(priv->pvid[5]));
+}
+
+/*
+ * Precondition: reg_mutex must be held
+ */
+static void
+adm6996_apply_vlan_filters(struct adm6996_priv *priv)
+{
+       u8 ports, tagged;
+       u16 vid, reg;
+       int i;
+
+       for (i = 0; i < ADM_NUM_VLANS; i++) {
+               vid = priv->vlan_id[i];
+               ports = priv->vlan_table[i];
+               tagged = priv->vlan_tagged[i];
+
+               if (ports == 0) {
+                       /* Disable VLAN entry */
+                       w16(priv->phydev, ADM_VLAN_FILT_H(i), 0);
+                       w16(priv->phydev, ADM_VLAN_FILT_L(i), 0);
+                       continue;
+               }
+
+               reg = ADM_VLAN_FILT_MEMBER(ports);
+               reg |= ADM_VLAN_FILT_TAGGED(tagged);
+               w16(priv->phydev, ADM_VLAN_FILT_L(i), reg);
+               reg = ADM_VLAN_FILT_VALID | ADM_VLAN_FILT_VID(vid);
+               w16(priv->phydev, ADM_VLAN_FILT_H(i), reg);
+       }
+}
+
+static int
+adm6996_hw_apply(struct switch_dev *dev)
+{
+       struct adm6996_priv *priv = to_adm(dev);
+
+       dev_dbg (&priv->phydev->dev, "hw_apply\n");
+
+       mutex_lock(&priv->reg_mutex);
+
+       if (!priv->enable_vlan && priv->vlan_enabled) {
+               adm6996_disable_vlan(priv);
+               priv->vlan_enabled = 0;
+               return 0;
+       } else if (priv->enable_vlan && !priv->vlan_enabled) {
+               adm6996_enable_vlan(priv);
+               priv->vlan_enabled = 1;
+       }
+
+       adm6996_apply_port_pvids (priv);
+       adm6996_apply_vlan_filters (priv);
+
+       mutex_unlock(&priv->reg_mutex);
+
+       return 0;
+}
+
+static struct switch_attr adm6996_globals[] = {
+       {
+        .type = SWITCH_TYPE_INT,
+        .name = "enable_vlan",
+        .description = "Enable VLANs",
+        .set = adm6996_set_enable_vlan,
+        .get = adm6996_get_enable_vlan,
+        },
+#ifdef DEBUG
+       {
+        .type = SWITCH_TYPE_INT,
+        .name = "addr",
+        .description =
+        "Direct register access: set register address (0 - 1023)",
+        .set = adm6996_set_addr,
+        .get = adm6996_get_addr,
+        },
+       {
+        .type = SWITCH_TYPE_INT,
+        .name = "data",
+        .description =
+        "Direct register access: read/write to register (0 - 65535)",
+        .set = adm6996_set_data,
+        .get = adm6996_get_data,
+        },
+#endif
+};
+
+static struct switch_attr adm6996_port[] = {
+};
+
+static struct switch_attr adm6996_vlan[] = {
+       {
+        .type = SWITCH_TYPE_INT,
+        .name = "vid",
+        .description = "VLAN ID",
+        .set = adm6996_set_vid,
+        .get = adm6996_get_vid,
+        },
+};
+
+static const struct switch_dev_ops adm6996_ops = {
+       .attr_global = {
+                       .attr = adm6996_globals,
+                       .n_attr = ARRAY_SIZE(adm6996_globals),
+                       },
+       .attr_port = {
+                     .attr = adm6996_port,
+                     .n_attr = ARRAY_SIZE(adm6996_port),
+                     },
+       .attr_vlan = {
+                     .attr = adm6996_vlan,
+                     .n_attr = ARRAY_SIZE(adm6996_vlan),
+                     },
+       .get_port_pvid = adm6996_get_pvid,
+       .set_port_pvid = adm6996_set_pvid,
+       .get_vlan_ports = adm6996_get_ports,
+       .set_vlan_ports = adm6996_set_ports,
+       .apply_config = adm6996_hw_apply,
+};
+
+static int adm6996_config_init(struct phy_device *pdev)
+{
+       struct adm6996_priv *priv;
+       struct switch_dev *swdev;
+
+       int i, ret;
+
+       priv = kzalloc(sizeof(struct adm6996_priv), GFP_KERNEL);
+       if (priv == NULL)
+               return -ENOMEM;
+
+       mutex_init(&priv->reg_mutex);
+       priv->phydev = pdev;
+       priv->read = adm6996_read_mii_reg;
+       priv->write = adm6996_write_mii_reg;
+       pdev->priv = priv;
+
        printk("%s: ADM6996 PHY driver attached.\n", pdev->attached_dev->name);
        pdev->supported = ADVERTISED_100baseT_Full;
        pdev->advertising = ADVERTISED_100baseT_Full;
 
        /* initialize port and vlan settings */
-       for (i = 0; i < ADM_PHY_PORTS; i++) {
+       for (i = 0; i < ADM_PHY_PORTS - 1; i++) {
                w16(pdev, adm_portcfg[i], ADM_PORTCFG_INIT |
                        ADM_PORTCFG_PVID((i == ADM_WAN_PORT) ? 1 : 0));
        }
        w16(pdev, adm_portcfg[5], ADM_PORTCFG_CPU);
 
        /* reset all ports */
-       for (i = 0; i < ADM_PHY_PORTS; i++) {
+       for (i = 0; i < ADM_PHY_PORTS - 1; i++) {
                w16(pdev, ADM_PHY_PORT(i), ADM_PHYCFG_INIT);
        }
 
+       /* Clear VLAN priority map so prio's are unused */
+       w16 (pdev, ADM_VLAN_PRIOMAP, 0);
+
+       swdev = &priv->dev;
+       swdev->name = "ADM6996";
+       swdev->cpu_port = ADM_CPU_PORT;
+       swdev->ports = ADM_PHY_PORTS;
+       swdev->vlans = ADM_NUM_VLANS;
+       swdev->ops = &adm6996_ops;
+
+       priv->pvid[ADM_WAN_PORT] = 1;
+
+       for (i = 0; i < ADM_NUM_VLANS; i++) {
+               priv->vlan_id[i] = i;
+       }
+
+       if ((ret = register_switch(swdev, pdev->attached_dev)) < 0) {
+               kfree(priv);
+               return ret;
+       }
+
        return 0;
 }
 
@@ -125,15 +596,6 @@
 
 static int adm6996_probe(struct phy_device *pdev)
 {
-       struct adm6996_priv *priv;
-
-       priv = kzalloc(sizeof(struct adm6996_priv), GFP_KERNEL);
-       if (priv == NULL)
-               return -ENOMEM;
-
-       priv->read = adm6996_read_mii_reg;
-       priv->write = adm6996_write_mii_reg;
-       pdev->priv = priv;
        return 0;
 }
 
Index: backfire/target/linux/generic-2.6/files/drivers/net/phy/adm6996.h
===================================================================
--- backfire/target/linux/generic-2.6/files/drivers/net/phy/adm6996.h   
(revision 24930)
+++ backfire/target/linux/generic-2.6/files/drivers/net/phy/adm6996.h   
(working copy)
@@ -10,10 +10,13 @@
 #ifndef __ADM6996_H
 #define __ADM6996_H
 
-#define ADM_PHY_PORTS  5
+#define ADM_PHY_PORTS  6
 #define ADM_CPU_PORT   5
 #define ADM_WAN_PORT   0 /* FIXME: dynamic ? */
 
+#define ADM_NUM_VLANS 16
+#define ADM_VLAN_MAX_ID 4094
+
 enum admreg {
        ADM_EEPROM_BASE         = 0x0,
                ADM_P0_CFG              = ADM_EEPROM_BASE + 1,
@@ -22,7 +25,21 @@
                ADM_P3_CFG              = ADM_EEPROM_BASE + 7,
                ADM_P4_CFG              = ADM_EEPROM_BASE + 8,
                ADM_P5_CFG              = ADM_EEPROM_BASE + 9,
+               ADM_SYSC0               = ADM_EEPROM_BASE + 0xa,
+               ADM_VLAN_PRIOMAP        = ADM_EEPROM_BASE + 0xe,
+               ADM_SYSC3               = ADM_EEPROM_BASE + 0x11,
+               /* Input Force No Tag Enable */
+               ADM_IFNTE               = ADM_EEPROM_BASE + 0x20,
+               ADM_VID_CHECK           = ADM_EEPROM_BASE + 0x26,
+               ADM_P0_PVID             = ADM_EEPROM_BASE + 0x28,
+               ADM_P1_PVID             = ADM_EEPROM_BASE + 0x29,
+               /* Output Tag Bypass Enable and P2 PVID */
+               ADM_OTBE_P2_PVID        = ADM_EEPROM_BASE + 0x2a,
+               ADM_P3_P4_PVID          = ADM_EEPROM_BASE + 0x2b,
+               ADM_P5_PVID             = ADM_EEPROM_BASE + 0x2c,
        ADM_EEPROM_EXT_BASE     = 0x40,
+#define ADM_VLAN_FILT_L(n) (ADM_EEPROM_EXT_BASE + 2 * (n))
+#define ADM_VLAN_FILT_H(n) (ADM_EEPROM_EXT_BASE + 1 + 2 * (n))
        ADM_COUNTER_BASE        = 0xa0,
                ADM_SIG0                = ADM_COUNTER_BASE + 0,
                ADM_SIG1                = ADM_COUNTER_BASE + 1,
@@ -84,9 +101,33 @@
        ),
 };
 
-#define ADM_PORTCFG_PPID(N) ((n & 0x3) << 8)
+#define ADM_PORTCFG_PPID(n) ((n & 0x3) << 8)
 #define ADM_PORTCFG_PVID(n) ((n & 0xf) << 10)
+#define ADM_PORTCFG_PVID_MASK (0xf << 10)
 
+#define ADM_IFNTE_MASK (0x3f << 9)
+#define ADM_VID_CHECK_MASK (0x3f << 6)
+
+#define ADM_P0_PVID_VAL(n) ((((n) & 0xff0) >> 4) << 0)
+#define ADM_P1_PVID_VAL(n) ((((n) & 0xff0) >> 4) << 0)
+#define ADM_P2_PVID_VAL(n) ((((n) & 0xff0) >> 4) << 0)
+#define ADM_P3_PVID_VAL(n) ((((n) & 0xff0) >> 4) << 0)
+#define ADM_P4_PVID_VAL(n) ((((n) & 0xff0) >> 4) << 8)
+#define ADM_P5_PVID_VAL(n) ((((n) & 0xff0) >> 4) << 0)
+#define ADM_P2_PVID_MASK 0xff
+
+#define ADM_OTBE(n) (((n) & 0x3f) << 8)
+#define ADM_OTBE_MASK (0x3f << 8)
+
+/* ADM_SYSC0 */
+enum {
+       ADM_NTTE        = (1 << 2),     /* New Tag Transmit Enable */
+       ADM_RVID1       = (1 << 8)      /* Replace VLAN ID 1 */
+};
+
+/* Tag Based VLAN in ADM_SYSC3 */
+#define ADM_TBV (1 << 5)
+
 static const u8 adm_portcfg[] = {
        [0] = ADM_P0_CFG,
        [1] = ADM_P1_CFG,
@@ -96,6 +137,16 @@
        [5] = ADM_P5_CFG,
 };
 
+/* Fields in ADM_VLAN_FILT_L(x) */
+#define ADM_VLAN_FILT_FID(n) (((n) & 0xf) << 12)
+#define ADM_VLAN_FILT_TAGGED(n) (((n) & 0x3f) << 6)
+#define ADM_VLAN_FILT_MEMBER(n) (((n) & 0x3f) << 0)
+#define ADM_VLAN_FILT_MEMBER_MASK 0x3f
+/* Fields in ADM_VLAN_FILT_H(x) */
+#define ADM_VLAN_FILT_VALID (1 << 15)
+#define ADM_VLAN_FILT_VID(n) (((n) & 0xfff) << 0)
+
+
 /*
  * Split the register address in phy id and register
  * it will get combined again by the mdio bus op
_______________________________________________
openwrt-devel mailing list
openwrt-devel@lists.openwrt.org
https://lists.openwrt.org/mailman/listinfo/openwrt-devel

Reply via email to