net: phy: mv88e61xx: rework to enable detection of 88E6071 devices

Extend the driver to init switch register offsets from variables
instead of compile time macros and enable detection of 88E6071 and
compatible devices. Ethernet transfer (e.g. tftp) does not work yet,
so enable the registration of the 'indirect mii' bus for easier PHY
register access by 'mii' command.

Signed-off-by: Anatolij Gustschin <agust@denx.de>
Acked-by: Joe Hershberger <joe.hershberger@ni.com>
diff --git a/drivers/net/phy/mv88e61xx.c b/drivers/net/phy/mv88e61xx.c
index c1e2860..9aba042 100644
--- a/drivers/net/phy/mv88e61xx.c
+++ b/drivers/net/phy/mv88e61xx.c
@@ -39,15 +39,11 @@
 
 #define PHY_AUTONEGOTIATE_TIMEOUT	5000
 
-#define PORT_COUNT			11
-#define PORT_MASK			((1 << PORT_COUNT) - 1)
+#define PORT_MASK(port_count)		((1 << (port_count)) - 1)
 
 /* Device addresses */
 #define DEVADDR_PHY(p)			(p)
-#define DEVADDR_PORT(p)			(0x10 + (p))
 #define DEVADDR_SERDES			0x0F
-#define DEVADDR_GLOBAL_1		0x1B
-#define DEVADDR_GLOBAL_2		0x1C
 
 /* SMI indirection registers for multichip addressing mode */
 #define SMI_CMD_REG			0x00
@@ -182,17 +178,26 @@
 #endif
 
 /* ID register values for different switch models */
+#define PORT_SWITCH_ID_6020		0x0200
+#define PORT_SWITCH_ID_6070		0x0700
+#define PORT_SWITCH_ID_6071		0x0710
 #define PORT_SWITCH_ID_6096		0x0980
 #define PORT_SWITCH_ID_6097		0x0990
 #define PORT_SWITCH_ID_6172		0x1720
 #define PORT_SWITCH_ID_6176		0x1760
+#define PORT_SWITCH_ID_6220		0x2200
 #define PORT_SWITCH_ID_6240		0x2400
+#define PORT_SWITCH_ID_6250		0x2500
 #define PORT_SWITCH_ID_6352		0x3520
 
 struct mv88e61xx_phy_priv {
 	struct mii_dev *mdio_bus;
 	int smi_addr;
 	int id;
+	int port_count;		/* Number of switch ports */
+	int port_reg_base;	/* Base of the switch port registers */
+	u8 global1;	/* Offset of Switch Global 1 registers */
+	u8 global2;	/* Offset of Switch Global 2 registers */
 };
 
 static inline int smi_cmd(int cmd, int addr, int reg)
@@ -329,11 +334,12 @@
 
 static int mv88e61xx_phy_wait(struct phy_device *phydev)
 {
+	struct mv88e61xx_phy_priv *priv = phydev->priv;
 	int val;
 	u32 timeout = 100;
 
 	do {
-		val = mv88e61xx_reg_read(phydev, DEVADDR_GLOBAL_2,
+		val = mv88e61xx_reg_read(phydev, priv->global2,
 					 GLOBAL2_REG_PHY_CMD);
 		if (val >= 0 && (val & SMI_BUSY) == 0)
 			return 0;
@@ -347,13 +353,15 @@
 static int mv88e61xx_phy_read_indirect(struct mii_dev *smi_wrapper, int dev,
 		int devad, int reg)
 {
+	struct mv88e61xx_phy_priv *priv;
 	struct phy_device *phydev;
 	int res;
 
 	phydev = (struct phy_device *)smi_wrapper->priv;
+	priv = phydev->priv;
 
 	/* Issue command to read */
-	res = mv88e61xx_reg_write(phydev, DEVADDR_GLOBAL_2,
+	res = mv88e61xx_reg_write(phydev, priv->global2,
 				  GLOBAL2_REG_PHY_CMD,
 				  smi_cmd_read(dev, reg));
 
@@ -363,25 +371,27 @@
 		return res;
 
 	/* Read retrieved data */
-	return mv88e61xx_reg_read(phydev, DEVADDR_GLOBAL_2,
+	return mv88e61xx_reg_read(phydev, priv->global2,
 				  GLOBAL2_REG_PHY_DATA);
 }
 
 static int mv88e61xx_phy_write_indirect(struct mii_dev *smi_wrapper, int dev,
 		int devad, int reg, u16 data)
 {
+	struct mv88e61xx_phy_priv *priv;
 	struct phy_device *phydev;
 	int res;
 
 	phydev = (struct phy_device *)smi_wrapper->priv;
+	priv = phydev->priv;
 
 	/* Set the data to write */
-	res = mv88e61xx_reg_write(phydev, DEVADDR_GLOBAL_2,
+	res = mv88e61xx_reg_write(phydev, priv->global2,
 				  GLOBAL2_REG_PHY_DATA, data);
 	if (res < 0)
 		return res;
 	/* Issue the write command */
-	res = mv88e61xx_reg_write(phydev, DEVADDR_GLOBAL_2,
+	res = mv88e61xx_reg_write(phydev, priv->global2,
 				  GLOBAL2_REG_PHY_CMD,
 				  smi_cmd_write(dev, reg));
 	if (res < 0)
@@ -408,13 +418,18 @@
 
 static int mv88e61xx_port_read(struct phy_device *phydev, u8 port, u8 reg)
 {
-	return mv88e61xx_reg_read(phydev, DEVADDR_PORT(port), reg);
+	struct mv88e61xx_phy_priv *priv = phydev->priv;
+
+	return mv88e61xx_reg_read(phydev, priv->port_reg_base + port, reg);
 }
 
 static int mv88e61xx_port_write(struct phy_device *phydev, u8 port, u8 reg,
 								u16 val)
 {
-	return mv88e61xx_reg_write(phydev, DEVADDR_PORT(port), reg, val);
+	struct mv88e61xx_phy_priv *priv = phydev->priv;
+
+	return mv88e61xx_reg_write(phydev, priv->port_reg_base + port,
+				   reg, val);
 }
 
 static int mv88e61xx_set_page(struct phy_device *phydev, u8 phy, u8 page)
@@ -515,12 +530,13 @@
 
 static int mv88e61xx_switch_reset(struct phy_device *phydev)
 {
+	struct mv88e61xx_phy_priv *priv = phydev->priv;
 	int time;
 	int val;
 	u8 port;
 
 	/* Disable all ports */
-	for (port = 0; port < PORT_COUNT; port++) {
+	for (port = 0; port < priv->port_count; port++) {
 		val = mv88e61xx_port_read(phydev, port, PORT_REG_CTRL);
 		if (val < 0)
 			return val;
@@ -536,19 +552,19 @@
 	udelay(2000);
 
 	/* Reset switch */
-	val = mv88e61xx_reg_read(phydev, DEVADDR_GLOBAL_1, GLOBAL1_CTRL);
+	val = mv88e61xx_reg_read(phydev, priv->global1, GLOBAL1_CTRL);
 	if (val < 0)
 		return val;
 	val |= GLOBAL1_CTRL_SWRESET;
-	val = mv88e61xx_reg_write(phydev, DEVADDR_GLOBAL_1,
-				     GLOBAL1_CTRL, val);
+	val = mv88e61xx_reg_write(phydev, priv->global1,
+				  GLOBAL1_CTRL, val);
 	if (val < 0)
 		return val;
 
 	/* Wait up to 1 second for switch reset complete */
 	for (time = 1000; time; time--) {
-		val = mv88e61xx_reg_read(phydev, DEVADDR_GLOBAL_1,
-					    GLOBAL1_CTRL);
+		val = mv88e61xx_reg_read(phydev, priv->global1,
+					 GLOBAL1_CTRL);
 		if (val >= 0 && ((val & GLOBAL1_CTRL_SWRESET) == 0))
 			break;
 		udelay(1000);
@@ -732,22 +748,23 @@
 
 static int mv88e61xx_set_cpu_port(struct phy_device *phydev)
 {
+	struct mv88e61xx_phy_priv *priv = phydev->priv;
 	int val;
 
 	/* Set CPUDest */
-	val = mv88e61xx_reg_read(phydev, DEVADDR_GLOBAL_1, GLOBAL1_MON_CTRL);
+	val = mv88e61xx_reg_read(phydev, priv->global1, GLOBAL1_MON_CTRL);
 	if (val < 0)
 		return val;
 	val = bitfield_replace(val, GLOBAL1_MON_CTRL_CPUDEST_SHIFT,
 			       GLOBAL1_MON_CTRL_CPUDEST_WIDTH,
 			       CONFIG_MV88E61XX_CPU_PORT);
-	val = mv88e61xx_reg_write(phydev, DEVADDR_GLOBAL_1,
-				     GLOBAL1_MON_CTRL, val);
+	val = mv88e61xx_reg_write(phydev, priv->global1,
+				  GLOBAL1_MON_CTRL, val);
 	if (val < 0)
 		return val;
 
 	/* Allow CPU to route to any port */
-	val = PORT_MASK & ~(1 << CONFIG_MV88E61XX_CPU_PORT);
+	val = PORT_MASK(priv->port_count) & ~(1 << CONFIG_MV88E61XX_CPU_PORT);
 	val = mv88e61xx_port_set_vlan(phydev, CONFIG_MV88E61XX_CPU_PORT, val);
 	if (val < 0)
 		return val;
@@ -856,6 +873,48 @@
 	return 0;
 }
 
+/*
+ * This function is used to pre-configure the required register
+ * offsets, so that the indirect register access to the PHY registers
+ * is possible. This is necessary to be able to read the PHY ID
+ * while driver probing or in get_phy_id(). The globalN register
+ * offsets must be initialized correctly for a detected switch,
+ * otherwise detection of the PHY ID won't work!
+ */
+static int mv88e61xx_priv_reg_offs_pre_init(struct phy_device *phydev)
+{
+	struct mv88e61xx_phy_priv *priv = phydev->priv;
+
+	/*
+	 * Initial 'port_reg_base' value must be an offset of existing
+	 * port register, then reading the ID should succeed. First, try
+	 * to read via port registers with device address 0x10 (88E6096
+	 * and compatible switches).
+	 */
+	priv->port_reg_base = 0x10;
+	priv->id = mv88e61xx_get_switch_id(phydev);
+	if (priv->id != 0xfff0) {
+		priv->global1 = 0x1B;
+		priv->global2 = 0x1C;
+		return 0;
+	}
+
+	/*
+	 * Now try via port registers with device address 0x08
+	 * (88E6020 and compatible switches).
+	 */
+	priv->port_reg_base = 0x08;
+	priv->id = mv88e61xx_get_switch_id(phydev);
+	if (priv->id != 0xfff0) {
+		priv->global1 = 0x0F;
+		priv->global2 = 0x07;
+		return 0;
+	}
+
+	debug("%s Unknown ID 0x%x\n", __func__, priv->id);
+	return -ENODEV;
+}
+
 static int mv88e61xx_probe(struct phy_device *phydev)
 {
 	struct mii_dev *smi_wrapper;
@@ -910,13 +969,43 @@
 
 	phydev->priv = priv;
 
-	priv->id = mv88e61xx_get_switch_id(phydev);
+	res = mv88e61xx_priv_reg_offs_pre_init(phydev);
+	if (res < 0)
+		return res;
+
+	debug("%s ID 0x%x\n", __func__, priv->id);
+
+	switch (priv->id) {
+	case PORT_SWITCH_ID_6096:
+	case PORT_SWITCH_ID_6097:
+	case PORT_SWITCH_ID_6172:
+	case PORT_SWITCH_ID_6176:
+	case PORT_SWITCH_ID_6240:
+	case PORT_SWITCH_ID_6352:
+		priv->port_count = 11;
+		break;
+	case PORT_SWITCH_ID_6020:
+	case PORT_SWITCH_ID_6070:
+	case PORT_SWITCH_ID_6071:
+	case PORT_SWITCH_ID_6220:
+	case PORT_SWITCH_ID_6250:
+		priv->port_count = 7;
+		break;
+	default:
+		free(priv);
+		return -ENODEV;
+	}
+
+	res = mdio_register(smi_wrapper);
+	if (res)
+		printf("Failed to register SMI bus\n");
 
 	return 0;
 }
 
 static int mv88e61xx_phy_config(struct phy_device *phydev)
 {
+	struct mv88e61xx_phy_priv *priv = phydev->priv;
 	int res;
 	int i;
 	int ret = -1;
@@ -925,7 +1014,7 @@
 	if (res < 0)
 		return res;
 
-	for (i = 0; i < PORT_COUNT; i++) {
+	for (i = 0; i < priv->port_count; i++) {
 		if ((1 << i) & CONFIG_MV88E61XX_PHY_PORTS) {
 			phydev->addr = i;
 
@@ -988,13 +1077,14 @@
 
 static int mv88e61xx_phy_startup(struct phy_device *phydev)
 {
+	struct mv88e61xx_phy_priv *priv = phydev->priv;
 	int i;
 	int link = 0;
 	int res;
 	int speed = phydev->speed;
 	int duplex = phydev->duplex;
 
-	for (i = 0; i < PORT_COUNT; i++) {
+	for (i = 0; i < priv->port_count; i++) {
 		if ((1 << i) & CONFIG_MV88E61XX_PHY_PORTS) {
 			phydev->addr = i;
 			if (!mv88e61xx_phy_is_connected(phydev))
@@ -1068,6 +1158,16 @@
 	temp_phy.priv = &temp_priv;
 	temp_mii.priv = &temp_phy;
 
+	/*
+	 * get_phy_id() can be called by framework before mv88e61xx driver
+	 * probing, in this case the global register offsets are not
+	 * initialized yet. Do this initialization here before indirect
+	 * PHY register access.
+	 */
+	val = mv88e61xx_priv_reg_offs_pre_init(&temp_phy);
+	if (val < 0)
+		return val;
+
 	val = mv88e61xx_phy_read_indirect(&temp_mii, 0, devad, MII_PHYSID1);
 	if (val < 0)
 		return -EIO;