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