Hi,

I hopefully have finished the two versions of the driver. One effectively only
supports the M model (though it detects and initialises the FC model), and the
second experimental one adds VLAN support for the FC model.

I don't have the time right now to do the official patch submission. But
attached is the diff against backfire for the experimental FC support. It also
includes a change to the Ubiquiti RouterStation board support code. Yeoh, please
try out the driver. I hope it will completely work for you.

It now even includes some documentation.

Please note that if you apply my patch to the RouterStation support code, but do
not include the ADM6996 driver, you will lose your LAN Ethernet connectivity.

The ADM6996 exports Generic PHY compatible, standard PHY register access on PHY
addresses 16 through 20 (for ports 0 through 4 of the ADM6996). The WAN Ethernet
is correctly wired to address 20.

The LAN is somewhat interestingly wired to address 16, port 0. This means the
MAC will work as if the PHY on the other side of its MII link is port 0, whereas
the MII link connects to the switching fabric of the ADM6996. When using the
ADM6996 switch driver, it will always report "link up, 100Mbit/s FD" for PHY
address 0, which is correct. Like I said earlier, it is as if the cable between
the Ethernet adapter and the switch is always present.

So I changed the PHY mask for the LAN Ethernet to use PHY address 0.

But if you do not include the ADM6996 driver, it will use the Generic PHY driver
for PHY address 0, and that leads to the special-purpose registers of the
ADM6996. This does not work.

I'd really like to know if chip detection now finally works!

Peter.

-- 
I use the GNU Privacy Guard (GnuPG) in combination with Enigmail.
You can send me encrypted mail if you want some privacy.
My key is available at http://wwwhome.cs.utwente.nl/~lebbing/pubkey.txt
Index: target/linux/generic-2.6/files/Documentation/networking/adm6996.txt
===================================================================
--- target/linux/generic-2.6/files/Documentation/networking/adm6996.txt	(revision 0)
+++ target/linux/generic-2.6/files/Documentation/networking/adm6996.txt	(revision 0)
@@ -0,0 +1,120 @@
+------- 
+
+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.
+  
+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. ADM6996FC specific information
+
+  A port can be:
+    - either an untagged member of exactly one VLAN,
+    - or a tagged member of one or more VLANs.
+
+  So a mix of untagged and tagged like the M model supports is not possible.
+  When you isssue a 'vlan X set ports' swconfig command, any existing port
+  membership for the ports will be adjusted to meet these constraints. For
+  clarity and your own sanity, I strongly recommend you obey those constraints
+  yourself while defining membership. 'swconfig dev ethX show' will always show
+  the state that will be set once applied, so there it is What You See Is What
+  You Get.
+
+3. 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-2.6/files/drivers/net/phy/adm6996.c
===================================================================
--- target/linux/generic-2.6/files/drivers/net/phy/adm6996.c	(revision 26505)
+++ target/linux/generic-2.6/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,614 @@
 	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;
+};
+
+/*
+ * Clear VLAN memberships for a port. If @untagged, only clear untagged
+ * memberships. Otherwise clear all.
+ */
+static void
+adm6996_clear_memberships (struct adm6996_priv *priv, int port, bool untagged)
+{
+	int i;
+
+	if (untagged) {
+		for (i = 0; i < ADM_NUM_VLANS; i++)
+			if (!(priv->vlan_tagged[i] & (1 << port)))
+				priv->vlan_table[i] &= ~(1 << port);
+	} else {
+		for (i = 0; i < ADM_NUM_VLANS; i++) {
+			priv->vlan_table[i] &= ~(1 << port);
+			priv->vlan_tagged[i] &= ~(1 << port);
+		}
+	}
+}
+
+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 (priv->model == ADM6996FC) {
+			bool untagged_only;
+			untagged_only =
+				p ->flags & (1 << SWITCH_PORT_FLAG_TAGGED);
+			adm6996_clear_memberships (priv, p->id, untagged_only);
+		}
+
+		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);
+	}
+}
+
+/*
+ * Set "Output Tagged" for ports that are tagged members of a VLAN.
+ *
+ * The ADM6996FC seems to need this, but it should not be used for the M
+ * model. The M can support both tagged and untagged on the same port.
+ *
+ * Precondition: reg_mutex must be held
+ */
+static void adm6996_set_tagged_ports (struct adm6996_priv *priv)
+{
+	u8 tagged = 0;
+	u16 reg;
+	int i;
+
+	for (i = 0; i < ADM_NUM_VLANS; i++) {
+		tagged |= priv->vlan_tagged[i];
+	}
+
+	for (i = 0; i < ADM_NUM_PORTS; i++) {
+		reg = r16(priv->phydev, adm_portcfg[i]);
+
+		if (tagged & (1 << i)) {
+			dev_dbg(&priv->phydev->dev, "port %d tagged\n", i);
+			reg |= ADM_PORTCFG_OT;
+		} else {
+			reg &= ~(ADM_PORTCFG_OT);
+		}
+
+		w16(priv->phydev, adm_portcfg[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);
+
+	if (priv->model == ADM6996FC)
+		adm6996_set_tagged_ports(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;
+	}
+
+	/* 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 ((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 +721,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 +734,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 +748,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) {
+		unregister_switch(&priv->dev);
+		kfree(priv);
+	}
 }
 
 
Index: target/linux/generic-2.6/files/drivers/net/phy/adm6996.h
===================================================================
--- target/linux/generic-2.6/files/drivers/net/phy/adm6996.h	(revision 26505)
+++ target/linux/generic-2.6/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-2.6/patches-2.6.32/620-phy_adm6996.patch
===================================================================
--- target/linux/generic-2.6/patches-2.6.32/620-phy_adm6996.patch	(revision 26505)
+++ target/linux/generic-2.6/patches-2.6.32/620-phy_adm6996.patch	(working copy)
@@ -1,13 +1,14 @@
 --- a/drivers/net/phy/Kconfig
 +++ b/drivers/net/phy/Kconfig
-@@ -88,6 +88,11 @@ config LSI_ET1011C_PHY
+@@ -88,6 +88,12 @@ 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.
 +
  config FIXED_PHY
  	bool "Driver for MDIO Bus/PHY emulation with fixed speed/link PHYs"
Index: target/linux/ar71xx/files/arch/mips/ar71xx/mach-ubnt.c
===================================================================
--- target/linux/ar71xx/files/arch/mips/ar71xx/mach-ubnt.c	(revision 26505)
+++ target/linux/ar71xx/files/arch/mips/ar71xx/mach-ubnt.c	(working copy)
@@ -134,7 +134,7 @@
 }
 
 #define UBNT_RS_WAN_PHYMASK	(1 << 20)
-#define UBNT_RS_LAN_PHYMASK	((1 << 16) | (1 << 17) | (1 << 18) | (1 << 19))
+#define UBNT_RS_LAN_PHYMASK	(1 << 0)
 
 static void __init ubnt_rs_setup(void)
 {
@@ -146,6 +146,7 @@
 	ar71xx_eth0_data.phy_mask = UBNT_RS_WAN_PHYMASK;
 
 	ar71xx_eth1_data.phy_if_mode = PHY_INTERFACE_MODE_RMII;
+	ar71xx_eth1_data.phy_mask = UBNT_RS_LAN_PHYMASK;
 	ar71xx_eth1_data.speed = SPEED_100;
 	ar71xx_eth1_data.duplex = DUPLEX_FULL;
_______________________________________________
openwrt-devel mailing list
openwrt-devel@lists.openwrt.org
https://lists.openwrt.org/mailman/listinfo/openwrt-devel

Reply via email to