Patch tested on D-Link 584T DSL router with ADM6996M chip. Works.

Please commit.

Luka

On Mon, Apr 18, 2011 at 07:16:56PM +0200, Peter Lebbing wrote:
> 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>
> 
> ---
> 
> This is a respin of my April 14 submission. Trunk has moved on since
> then, and it no longer applied.
> 
>  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.36/620-phy_adm6996.patch       |    6 
>  patches-2.6.37/720-phy_adm6996.patch       |    6 
>  patches-2.6.38/720-phy_adm6996.patch       |    6 
>  patches-2.6.39/720-phy_adm6996.patch       |    6 
>  10 files changed, 791 insertions(+), 44 deletions(-)
> 
> Index: target/linux/generic/patches-2.6.36/620-phy_adm6996.patch
> ===================================================================
> --- target/linux/generic/patches-2.6.36/620-phy_adm6996.patch (revision 26717)
> +++ 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/720-phy_adm6996.patch
> ===================================================================
> --- target/linux/generic/patches-2.6.37/720-phy_adm6996.patch (revision 26717)
> +++ target/linux/generic/patches-2.6.37/720-phy_adm6996.patch (working copy)
> @@ -1,13 +1,15 @@
>  --- a/drivers/net/phy/Kconfig
>  +++ b/drivers/net/phy/Kconfig
> -@@ -98,6 +98,11 @@ config MICREL_PHY
> +@@ -98,6 +98,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/720-phy_adm6996.patch
> ===================================================================
> --- target/linux/generic/patches-2.6.38/720-phy_adm6996.patch (revision 26717)
> +++ target/linux/generic/patches-2.6.38/720-phy_adm6996.patch (working copy)
> @@ -1,13 +1,15 @@
>  --- a/drivers/net/phy/Kconfig
>  +++ b/drivers/net/phy/Kconfig
> -@@ -98,6 +98,11 @@ config MICREL_PHY
> +@@ -98,6 +98,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/720-phy_adm6996.patch
> ===================================================================
> --- target/linux/generic/patches-2.6.39/720-phy_adm6996.patch (revision 26717)
> +++ target/linux/generic/patches-2.6.39/720-phy_adm6996.patch (working copy)
> @@ -1,13 +1,15 @@
>  --- a/drivers/net/phy/Kconfig
>  +++ b/drivers/net/phy/Kconfig
> -@@ -97,6 +97,11 @@ config MICREL_PHY
> +@@ -97,6 +97,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 26717)
> +++ 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 26717)
> +++ 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 26717)
> +++ 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 26717)
> +++ 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 26717)
> +++ 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
_______________________________________________
openwrt-devel mailing list
openwrt-devel@lists.openwrt.org
https://lists.openwrt.org/mailman/listinfo/openwrt-devel

Reply via email to