This patch adds 802.1Q VLAN support for the ADM6996M chip.

The driver is loaded for both the FC and M model. It will detect which of the
two chips is connected. The FC model is initialised, but no further
functionality is offered.

The PHY driver will always report "100 Mbit/s, link up", for both the M and FC
models. This reflects the fact that the link between switch chip and Ethernet
MAC is always on[1].

Further documentation can be found in the kernel's
Documentation/networking/adm6996.txt

Changes compared to the original driver:

- Don't mask the "Version Number" (least significant nibble from Chip Identifier
0, register 0xA0) from the matching code. Since I already had trouble telling
the FC and M chips apart, both Version Number 3, I included this field in the
match again.

- Don't handle port 0 as a WAN port because in my opinion it just makes stuff
more complicated to understand and explain. At least on the D-Link DSL-G624T and
the Ubiquiti RouterStation, it's just a LAN port anyway.

- Don't reset port 4. I changed ADM_PHY_PORTS so only ports 0 through 3 are
reset. Port 4 is a special port, the datasheet says its "most popular" use is as
a WAN port :), connected to a second MAC. It can also be used as a regular port.
But for instance the Ubiquiti RouterStation indeed uses it connected to a second
MAC, and the Generic PHY driver on address 20 decimal fits this purpose well. In
that case, it should be the Generic PHY driver handling and thus resetting that
port, and the ADM6996 driver should just leave it alone.

- Match ONLY on PHY addresses 0-10 inclusive instead of all addresses. The
ADM6996 has custom registers in that range, so matching there somewhat prevents
register corruption by other drivers (specifically the Generic PHY driver). The
driver will only offer VLAN functionality when bound as address 0. Note that the
ADM6996 exports standard PHY registers (Generic PHY compatible) on addresses 16
through 20 for ports 0 through 4. Especially address 20 is useful. Ports 0
through 3 are always switch ports and never directly connected to the MII bus of
a MAC.

- The allocation and initialisation of the adm6996_priv structure is now in
config_init() instead of probe(). It is only done for PHY address 0, where it is
useful. Also, register_switch() is an additional failure point where the
structure should be freed.

- Added myself to MODULE_AUTHOR

[1] The switch chip can set the link state to down using a custom register bit.
I suppose this is for power-saving, but it is not implemented in the driver.

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

---

Compared to version 2 of April 3 (which wasn't explicitly called version 2), the
changes are:

- Detect and tell apart M and FC chip. Thanks everybody!

- Handling of PHY addresses as explained above

- Added Documentation/networking/adm6996.txt

- Don't set PVID of port 0 to ID 1 ("handle port 0 as WAN port" from above)

- Use ADM6996_PHY_PORTS as the number of PHYs of the switch chip to handle
instead of the total number of ports (I suppose I initially misunderstood its
use, this seems to make more sense)

- Additionally define ADM6996_NUM_PORTS to mean the total number of ports

- Fixed bug where an "apply" invocation with enable_vlan = 0 and vlan_enabled =
0 would still set VLAN filters.

- Implement "reset"; the ADM6996 chip doesn't seem to offer a software-initiated
reset, see adm6996.txt.

- Invoke unregister_switch() in adm6996_remove

 files/Documentation/networking/adm6996.txt |  110 +++++
 files/drivers/net/phy/adm6996.c            |  616 +++++++++++++++++++++++++++--
 files/drivers/net/phy/adm6996.h            |   67 ++-
 patches-2.6.30/620-phy_adm6996.patch       |    6
 patches-2.6.31/620-phy_adm6996.patch       |    6
 patches-2.6.32/620-phy_adm6996.patch       |    6
 patches-2.6.35/620-phy_adm6996.patch       |    6
 patches-2.6.36/620-phy_adm6996.patch       |    6
 patches-2.6.37/620-phy_adm6996.patch       |    6
 patches-2.6.38/620-phy_adm6996.patch       |    6
 patches-2.6.39/620-phy_adm6996.patch       |    6
 11 files changed, 795 insertions(+), 46 deletions(-)

Index: target/linux/generic/patches-2.6.35/620-phy_adm6996.patch
===================================================================
--- target/linux/generic/patches-2.6.35/620-phy_adm6996.patch   (revision 26657)
+++ target/linux/generic/patches-2.6.35/620-phy_adm6996.patch   (working copy)
@@ -1,13 +1,15 @@
 --- a/drivers/net/phy/Kconfig
 +++ b/drivers/net/phy/Kconfig
-@@ -93,6 +93,11 @@ config MICREL_PHY
+@@ -93,6 +93,13 @@ config MICREL_PHY
        ---help---
          Supports the KSZ9021, VSC8201, KS8001 PHYs.
  
 +config ADM6996_PHY
 +      tristate "Driver for ADM6996 switches"
++      select SWCONFIG
 +      ---help---
-+        Currently supports the ADM6996F switch
++        Currently supports the ADM6996FC and ADM6996M switches.
++        Support for FC is very limited.
 +
  config FIXED_PHY
        bool "Driver for MDIO Bus/PHY emulation with fixed speed/link PHYs"
Index: target/linux/generic/patches-2.6.36/620-phy_adm6996.patch
===================================================================
--- target/linux/generic/patches-2.6.36/620-phy_adm6996.patch   (revision 26657)
+++ target/linux/generic/patches-2.6.36/620-phy_adm6996.patch   (working copy)
@@ -1,13 +1,15 @@
 --- a/drivers/net/phy/Kconfig
 +++ b/drivers/net/phy/Kconfig
-@@ -93,6 +93,11 @@ config MICREL_PHY
+@@ -93,6 +93,13 @@ config MICREL_PHY
        ---help---
          Supports the KSZ9021, VSC8201, KS8001 PHYs.
  
 +config ADM6996_PHY
 +      tristate "Driver for ADM6996 switches"
++      select SWCONFIG
 +      ---help---
-+        Currently supports the ADM6996F switch
++        Currently supports the ADM6996FC and ADM6996M switches.
++        Support for FC is very limited.
 +
  config FIXED_PHY
        bool "Driver for MDIO Bus/PHY emulation with fixed speed/link PHYs"
Index: target/linux/generic/patches-2.6.37/620-phy_adm6996.patch
===================================================================
--- target/linux/generic/patches-2.6.37/620-phy_adm6996.patch   (revision 26657)
+++ target/linux/generic/patches-2.6.37/620-phy_adm6996.patch   (working copy)
@@ -1,13 +1,15 @@
 --- a/drivers/net/phy/Kconfig
 +++ b/drivers/net/phy/Kconfig
-@@ -92,6 +92,11 @@ config MICREL_PHY
+@@ -92,6 +92,13 @@ config MICREL_PHY
        ---help---
          Supports the KSZ9021, VSC8201, KS8001 PHYs.
  
 +config ADM6996_PHY
 +      tristate "Driver for ADM6996 switches"
++      select SWCONFIG
 +      ---help---
-+        Currently supports the ADM6996F switch
++        Currently supports the ADM6996FC and ADM6996M switches.
++        Support for FC is very limited.
 +
  config FIXED_PHY
        bool "Driver for MDIO Bus/PHY emulation with fixed speed/link PHYs"
Index: target/linux/generic/patches-2.6.38/620-phy_adm6996.patch
===================================================================
--- target/linux/generic/patches-2.6.38/620-phy_adm6996.patch   (revision 26657)
+++ target/linux/generic/patches-2.6.38/620-phy_adm6996.patch   (working copy)
@@ -1,13 +1,15 @@
 --- a/drivers/net/phy/Kconfig
 +++ b/drivers/net/phy/Kconfig
-@@ -92,6 +92,11 @@ config MICREL_PHY
+@@ -92,6 +92,13 @@ config MICREL_PHY
        ---help---
          Supports the KSZ9021, VSC8201, KS8001 PHYs.
  
 +config ADM6996_PHY
 +      tristate "Driver for ADM6996 switches"
++      select SWCONFIG
 +      ---help---
-+        Currently supports the ADM6996F switch
++        Currently supports the ADM6996FC and ADM6996M switches.
++        Support for FC is very limited.
 +
  config FIXED_PHY
        bool "Driver for MDIO Bus/PHY emulation with fixed speed/link PHYs"
Index: target/linux/generic/patches-2.6.39/620-phy_adm6996.patch
===================================================================
--- target/linux/generic/patches-2.6.39/620-phy_adm6996.patch   (revision 26657)
+++ target/linux/generic/patches-2.6.39/620-phy_adm6996.patch   (working copy)
@@ -1,13 +1,15 @@
 --- a/drivers/net/phy/Kconfig
 +++ b/drivers/net/phy/Kconfig
-@@ -91,6 +91,11 @@ config MICREL_PHY
+@@ -91,6 +91,13 @@ config MICREL_PHY
        ---help---
          Supports the KSZ9021, VSC8201, KS8001 PHYs.
  
 +config ADM6996_PHY
 +      tristate "Driver for ADM6996 switches"
++      select SWCONFIG
 +      ---help---
-+        Currently supports the ADM6996F switch
++        Currently supports the ADM6996FC and ADM6996M switches.
++        Support for FC is very limited.
 +
  config FIXED_PHY
        bool "Driver for MDIO Bus/PHY emulation with fixed speed/link PHYs"
Index: target/linux/generic/files/Documentation/networking/adm6996.txt
===================================================================
--- target/linux/generic/files/Documentation/networking/adm6996.txt     
(revision 0)
+++ target/linux/generic/files/Documentation/networking/adm6996.txt     
(revision 0)
@@ -0,0 +1,110 @@
+------- 
+
+ADM6996FC / ADM6996M switch chip driver
+
+
+1. General information
+
+  This driver supports the FC and M models only. The ADM6996F and L are
+  completely different chips.
+  
+  Support for the FC model is extremely limited at the moment. There is no VLAN
+  support as of yet. The driver will not offer an swconfig interface for the FC
+  chip.
+ 
+1.1 VLAN IDs
+
+  It is possible to define 16 different VLANs. Every VLAN has an identifier, 
its
+  VLAN ID. It is easiest if you use at most VLAN IDs 0-15. In that case, the
+  swconfig based configuration is very straightforward. To define two VLANs 
with
+  IDs 4 and 5, you can invoke, for example:
+  
+      # swconfig dev ethX vlan 4 set ports '0 1t 2 5t' 
+      # swconfig dev ethX vlan 5 set ports '0t 1t 5t'
+  
+  The swconfig framework will automatically invoke 'port Y set pvid Z' for 
every
+  port that is an untagged member of VLAN Y, setting its Primary VLAN ID. In
+  this example, ports 0 and 2 would get "pvid 4". The Primary VLAN ID of a port
+  is the VLAN ID associated with untagged packets coming in on that port.
+  
+  But if you wish to use VLAN IDs outside the range 0-15, this automatic
+  behaviour of the swconfig framework becomes a problem. The 16 VLANs that
+  swconfig can configure on the ADM6996 also have a "vid" setting. By default,
+  this is the same as the number of the VLAN entry, to make the simple 
behaviour
+  above possible. To still support a VLAN with a VLAN ID higher than 15
+  (presumably because you are in a network where such VLAN IDs are already in
+  use), you can change the "vid" setting of the VLAN to anything in the range
+  0-1023. But suppose you did the following:
+  
+      # swconfig dev ethX vlan 0 set vid 998 
+      # swconfig dev ethX vlan 0 set ports '0 2 5t'
+ 
+  Now the swconfig framework will issue 'port 0 set pvid 0' and 'port 2 set 
pvid
+  0'. But the "pvid" should be set to 998, so you are responsible for manually
+  fixing this!
+
+1.2 VLAN filtering
+
+  The switch is configured to apply source port filtering. This means that
+  packets are only accepted when the port the packets came in on is a member of
+  the VLAN the packet should go to.
+
+  Only membership of a VLAN is tested, it does not matter whether it is a 
tagged
+  or untagged membership.
+
+  For untagged packets, the destination VLAN is the Primary VLAN ID of the
+  incoming port. So if the PVID of a port is 0, but that port is not a member 
of
+  the VLAN with ID 0, this means that untagged packets on that port are 
dropped.
+  This can be used as a roundabout way of dropping untagged packets from a 
port,
+  a mode often referred to as "Admit only tagged packets".
+
+1.3 Reset
+
+  The two supported chip models do not have a sofware-initiated reset. When the
+  driver is initialised, as well as when the 'reset' swconfig option is 
invoked,
+  the driver will set those registers it knows about and supports to the 
correct
+  default value. But there are a lot of registers in the chip that the driver
+  does not support. If something changed those registers, invoking 'reset' or
+  performing a warm reboot might still leave the chip in a "broken" state. Only
+  a hardware reset will bring it back in the default state.
+
+2. Technical details on PHYs and the ADM6996
+
+  From the viewpoint of the Linux kernel, it is common that an Ethernet adapter
+  can be seen as a separate MAC entity and a separate PHY entity. The PHY 
entity
+  can be queried and set through registers accessible via an MDIO bus. A PHY
+  normally has a single address on that bus, in the range 0 through 31.
+
+  The ADM6996 has special-purpose registers in the range of PHYs 0 through 10.
+  Even though all these registers control a single ADM6996 chip, the Linux
+  kernel treats this as 11 separate PHYs.  The driver will bind to these
+  addresses to prevent a different PHY driver from binding and corrupting these
+  registers.
+
+  What Linux sees as the PHY on address 0 is meant for the Ethernet MAC
+  connected to the CPU port of the ADM6996 switch chip (port 5). This is the
+  Ethernet MAC you will use to send and receive data through the switch.
+
+  The PHYs at addresses 16 through 20 map to the PHYs on ports 0 through 4 of
+  the switch chip. These can be accessed with the Generic PHY driver, as the
+  registers have the common layout.
+
+  If a second Ethernet MAC on your board is wired to the port 4 PHY, that MAC
+  needs to bind to PHY address 20 for the port to work correctly.
+
+  The ADM6996 switch driver will reset the ports 0 through 3 on startup and 
when
+  'reset' is invoked. This could clash with a different PHY driver if the 
kernel
+  binds a PHY driver to address 16 through 19.
+
+  If Linux binds a PHY on addresses 1 through 10 to an Ethernet MAC, the 
ADM6996
+  driver will simply always report a connected 100 Mbit/s full-duplex link for
+  that PHY, and provide no other functionality. This is most likely not what 
you
+  want. So if you see a message in your log
+
+       ethX: PHY overlaps ADM6996, providing fixed PHY yy.
+
+  This is most likely an indication that ethX will not work properly, and your
+  kernel needs to be configured to attach a different PHY to that Ethernet MAC.
+
+  Controlling the mapping between MACs and PHYs is usually done in platform- or
+  board-specific fixup code. The ADM6996 driver has no influence over this.
Index: target/linux/generic/files/drivers/net/phy/adm6996.c
===================================================================
--- target/linux/generic/files/drivers/net/phy/adm6996.c        (revision 26657)
+++ target/linux/generic/files/drivers/net/phy/adm6996.c        (working copy)
@@ -1,12 +1,17 @@
 /*
  * 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 +29,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>
@@ -31,28 +37,59 @@
 #include "adm6996.h"
 
 MODULE_DESCRIPTION("Infineon ADM6996 Switch");
-MODULE_AUTHOR("Felix Fietkau");
+MODULE_AUTHOR("Felix Fietkau, Peter Lebbing <pe...@digitalbrains.com>");
 MODULE_LICENSE("GPL");
 
+enum adm6996_model {
+       ADM6996FC,
+       ADM6996M
+};
+
+static const char * const adm6996_model_name[] =
+{
+       "ADM6996FC",
+       "ADM6996M"
+};
+
 struct adm6996_priv {
+       struct switch_dev dev;
+       struct phy_device *phydev;
+
+       enum adm6996_model model;
+
+       bool enable_vlan;
+       bool vlan_enabled;      /* Current hardware state */
+
+#ifdef DEBUG
+       u16 addr;               /* Debugging: register address to operate on */
+#endif
+
+       u16 pvid[ADM_NUM_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);
 };
 
-#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,30 +105,555 @@
        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 /* def DEBUG */
+
+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;
 
-       printk("%s: ADM6996 PHY driver attached.\n", pdev->attached_dev->name);
-       pdev->supported = ADVERTISED_100baseT_Full;
-       pdev->advertising = ADVERTISED_100baseT_Full;
+       dev_dbg (&priv->phydev->dev, "get_ports port_vlan %d\n",
+                       val->port_vlan);
 
+       val->len = 0;
+
+       for (i = 0; i < ADM_NUM_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
+               pr_cont(" %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
+       pr_cont("\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_NUM_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_NUM_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) {
+               if (priv->vlan_enabled) {
+                       adm6996_disable_vlan(priv);
+                       priv->vlan_enabled = 0;
+               }
+               goto out;
+       }
+
+       if (!priv->vlan_enabled) {
+               adm6996_enable_vlan(priv);
+               priv->vlan_enabled = 1;
+       }
+
+       adm6996_apply_port_pvids(priv);
+       adm6996_apply_vlan_filters(priv);
+
+out:
+       mutex_unlock(&priv->reg_mutex);
+
+       return 0;
+}
+
+/*
+ * Reset the switch
+ *
+ * The ADM6996 can't do a software-initiated reset, so we just initialise the
+ * registers we support in this driver.
+ *
+ * Precondition: reg_mutex must be held
+ */
+static void
+adm6996_perform_reset (struct adm6996_priv *priv)
+{
+       int i;
+
        /* initialize port and vlan settings */
-       for (i = 0; i < ADM_PHY_PORTS; i++) {
-               w16(pdev, adm_portcfg[i], ADM_PORTCFG_INIT |
-                       ADM_PORTCFG_PVID((i == ADM_WAN_PORT) ? 1 : 0));
+       for (i = 0; i < ADM_NUM_PORTS - 1; i++) {
+               w16(priv->phydev, adm_portcfg[i], ADM_PORTCFG_INIT |
+                       ADM_PORTCFG_PVID(0));
        }
-       w16(pdev, adm_portcfg[5], ADM_PORTCFG_CPU);
+       w16(priv->phydev, adm_portcfg[5], ADM_PORTCFG_CPU);
 
-       /* reset all ports */
+       /* reset all PHY ports */
        for (i = 0; i < ADM_PHY_PORTS; i++) {
-               w16(pdev, ADM_PHY_PORT(i), ADM_PHYCFG_INIT);
+               w16(priv->phydev, ADM_PHY_PORT(i), ADM_PHYCFG_INIT);
        }
 
+       priv->enable_vlan = 0;
+       priv->vlan_enabled = 0;
+
+       for (i = 0; i < ADM_NUM_PORTS; i++) {
+               priv->pvid[i] = 0;
+       }
+
+       for (i = 0; i < ADM_NUM_VLANS; i++) {
+               priv->vlan_id[i] = i;
+               priv->vlan_table[i] = 0;
+               priv->vlan_tagged[i] = 0;
+       }
+
+       if (priv->model == ADM6996M) {
+               /* Clear VLAN priority map so prio's are unused */
+               w16 (priv->phydev, ADM_VLAN_PRIOMAP, 0);
+
+               adm6996_disable_vlan(priv);
+               adm6996_apply_port_pvids(priv);
+       }
+}
+
+static int
+adm6996_reset_switch(struct switch_dev *dev)
+{
+       struct adm6996_priv *priv = to_adm(dev);
+
+       dev_dbg (&priv->phydev->dev, "reset\n");
+       mutex_lock(&priv->reg_mutex);
+       adm6996_perform_reset (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 /* def DEBUG */
+};
+
+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,
+       .reset_switch = adm6996_reset_switch,
+};
+
+static int adm6996_config_init(struct phy_device *pdev)
+{
+       struct adm6996_priv *priv;
+       struct switch_dev *swdev;
+
+       int ret;
+       u16 test, old;
+
+       pdev->supported = ADVERTISED_100baseT_Full;
+       pdev->advertising = ADVERTISED_100baseT_Full;
+
+       if (pdev->addr != 0) {
+               pr_info ("%s: PHY overlaps ADM6996, providing fixed PHY 0x%x.\n"
+                               , pdev->attached_dev->name, pdev->addr);
+               return 0;
+       }
+
+       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;
+
+       /* Detect type of chip */
+       old = r16(pdev, ADM_VID_CHECK);
+       test = old ^ (1 << 12);
+       w16(pdev, ADM_VID_CHECK, test);
+       test ^= r16(pdev, ADM_VID_CHECK);
+       if (test & (1 << 12)) {
+               /* 
+                * Bit 12 of this register is read-only. 
+                * This is the FC model. 
+                */
+               priv->model = ADM6996FC;
+       } else {
+               /* Bit 12 is read-write. This is the M model. */
+               priv->model = ADM6996M;
+               w16(pdev, ADM_VID_CHECK, old);
+       }
+
+       swdev = &priv->dev;
+       swdev->name = (adm6996_model_name[priv->model]);
+       swdev->cpu_port = ADM_CPU_PORT;
+       swdev->ports = ADM_NUM_PORTS;
+       swdev->vlans = ADM_NUM_VLANS;
+       swdev->ops = &adm6996_ops;
+
+       pr_info ("%s: %s model PHY found.\n", pdev->attached_dev->name,
+                       swdev->name);
+
+       mutex_lock(&priv->reg_mutex);
+       adm6996_perform_reset (priv);
+       mutex_unlock(&priv->reg_mutex);
+
+       if (priv->model == ADM6996M) {
+               if ((ret = register_switch(swdev, pdev->attached_dev)) < 0) {
+                       kfree(priv);
+                       return ret;
+               }
+       }
+
+       return 0;
+}
+
+/*
+ * Warning: phydev->priv is NULL if phydev->addr != 0
+ */
 static int adm6996_read_status(struct phy_device *phydev)
 {
        phydev->speed = SPEED_100;
@@ -100,6 +662,9 @@
        return 0;
 }
 
+/*
+ * Warning: phydev->priv is NULL if phydev->addr != 0
+ */
 static int adm6996_config_aneg(struct phy_device *phydev)
 {
        return 0;
@@ -110,6 +675,10 @@
        struct mii_bus *bus = dev->bus;
        u16 reg;
 
+       /* Our custom registers are at PHY addresses 0-10. Claim those. */
+       if (dev->addr > 10)
+               return 0;
+
        /* look for the switch on the bus */
        reg = bus->read(bus, PHYADDR(ADM_SIG0)) & ADM_SIG0_MASK;
        if (reg != ADM_SIG0_VAL)
@@ -120,26 +689,23 @@
                return 0;
 
        dev->phy_id = (ADM_SIG0_VAL << 16) | ADM_SIG1_VAL;
+
        return 0;
 }
 
 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;
 }
 
 static void adm6996_remove(struct phy_device *pdev)
 {
-       kfree(pdev->priv);
+       struct adm6996_priv *priv = phy_to_adm(pdev);
+
+       if (priv != NULL && priv->model == ADM6996M)
+               unregister_switch(&priv->dev);
+
+       kfree(priv);
 }
 
 
Index: target/linux/generic/files/drivers/net/phy/adm6996.h
===================================================================
--- target/linux/generic/files/drivers/net/phy/adm6996.h        (revision 26657)
+++ target/linux/generic/files/drivers/net/phy/adm6996.h        (working copy)
@@ -2,6 +2,7 @@
  * ADM6996 switch driver
  *
  * Copyright (c) 2008 Felix Fietkau <n...@openwrt.org>
+ * 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
@@ -10,10 +11,18 @@
 #ifndef __ADM6996_H
 #define __ADM6996_H
 
-#define ADM_PHY_PORTS  5
+/*
+ * ADM_PHY_PORTS: Number of ports with a PHY.
+ * We only control ports 0 to 3, because if 4 is connected, it is most likely
+ * not connected to the switch but to a separate MII and MAC for the WAN port.
+ */
+#define ADM_PHY_PORTS  4
+#define ADM_NUM_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 +31,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,
@@ -31,8 +54,8 @@
 };
 
 /* Chip identification patterns */
-#define        ADM_SIG0_MASK   0xfff0
-#define ADM_SIG0_VAL   0x1020
+#define        ADM_SIG0_MASK   0xffff
+#define ADM_SIG0_VAL   0x1023
 #define ADM_SIG1_MASK  0xffff
 #define ADM_SIG1_VAL   0x0007
 
@@ -84,9 +107,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 +143,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
Index: target/linux/generic/patches-2.6.30/620-phy_adm6996.patch
===================================================================
--- target/linux/generic/patches-2.6.30/620-phy_adm6996.patch   (revision 26657)
+++ target/linux/generic/patches-2.6.30/620-phy_adm6996.patch   (working copy)
@@ -1,13 +1,15 @@
 --- a/drivers/net/phy/Kconfig
 +++ b/drivers/net/phy/Kconfig
-@@ -82,6 +82,11 @@ config LSI_ET1011C_PHY
+@@ -82,6 +82,13 @@ config LSI_ET1011C_PHY
        ---help---
          Supports the LSI ET1011C PHY.
  
 +config ADM6996_PHY
 +      tristate "Driver for ADM6996 switches"
++      select SWCONFIG
 +      ---help---
-+        Currently supports the ADM6996F switch
++        Currently supports the ADM6996FC and ADM6996M switches.
++        Support for FC is very limited.
 +
  config FIXED_PHY
        bool "Driver for MDIO Bus/PHY emulation with fixed speed/link PHYs"
Index: target/linux/generic/patches-2.6.31/620-phy_adm6996.patch
===================================================================
--- target/linux/generic/patches-2.6.31/620-phy_adm6996.patch   (revision 26657)
+++ target/linux/generic/patches-2.6.31/620-phy_adm6996.patch   (working copy)
@@ -1,13 +1,15 @@
 --- a/drivers/net/phy/Kconfig
 +++ b/drivers/net/phy/Kconfig
-@@ -82,6 +82,11 @@ config LSI_ET1011C_PHY
+@@ -82,6 +82,13 @@ config LSI_ET1011C_PHY
        ---help---
          Supports the LSI ET1011C PHY.
  
 +config ADM6996_PHY
 +      tristate "Driver for ADM6996 switches"
++      select SWCONFIG
 +      ---help---
-+        Currently supports the ADM6996F switch
++        Currently supports the ADM6996FC and ADM6996M switches.
++        Support for FC is very limited.
 +
  config FIXED_PHY
        bool "Driver for MDIO Bus/PHY emulation with fixed speed/link PHYs"
Index: target/linux/generic/patches-2.6.32/620-phy_adm6996.patch
===================================================================
--- target/linux/generic/patches-2.6.32/620-phy_adm6996.patch   (revision 26657)
+++ target/linux/generic/patches-2.6.32/620-phy_adm6996.patch   (working copy)
@@ -1,13 +1,15 @@
 --- a/drivers/net/phy/Kconfig
 +++ b/drivers/net/phy/Kconfig
-@@ -88,6 +88,11 @@ config LSI_ET1011C_PHY
+@@ -88,6 +88,13 @@ config LSI_ET1011C_PHY
        ---help---
          Supports the LSI ET1011C PHY.
  
 +config ADM6996_PHY
 +      tristate "Driver for ADM6996 switches"
++      select SWCONFIG
 +      ---help---
-+        Currently supports the ADM6996F switch
++        Currently supports the ADM6996FC and ADM6996M switches.
++        Support for FC is very limited.
 +
  config FIXED_PHY
        bool "Driver for MDIO Bus/PHY emulation with fixed speed/link PHYs"
_______________________________________________
openwrt-devel mailing list
openwrt-devel@lists.openwrt.org
https://lists.openwrt.org/mailman/listinfo/openwrt-devel

Reply via email to