xref: /rk3399_rockchip-uboot/drivers/net/phy/mv88e61xx.c (revision 4201223de8e6bac403213ad57769dfa723db36e3)
124ae3961SKevin Smith /*
224ae3961SKevin Smith  * (C) Copyright 2015
324ae3961SKevin Smith  * Elecsys Corporation <www.elecsyscorp.com>
424ae3961SKevin Smith  * Kevin Smith <kevin.smith@elecsyscorp.com>
524ae3961SKevin Smith  *
624ae3961SKevin Smith  * Original driver:
724ae3961SKevin Smith  * (C) Copyright 2009
824ae3961SKevin Smith  * Marvell Semiconductor <www.marvell.com>
924ae3961SKevin Smith  * Prafulla Wadaskar <prafulla@marvell.com>
1024ae3961SKevin Smith  *
1124ae3961SKevin Smith  * SPDX-License-Identifier:	GPL-2.0+
1224ae3961SKevin Smith  */
1324ae3961SKevin Smith 
1424ae3961SKevin Smith /*
1524ae3961SKevin Smith  * PHY driver for mv88e61xx ethernet switches.
1624ae3961SKevin Smith  *
1724ae3961SKevin Smith  * This driver configures the mv88e61xx for basic use as a PHY.  The switch
1824ae3961SKevin Smith  * supports a VLAN configuration that determines how traffic will be routed
1924ae3961SKevin Smith  * between the ports.  This driver uses a simple configuration that routes
2024ae3961SKevin Smith  * traffic from each PHY port only to the CPU port, and from the CPU port to
2124ae3961SKevin Smith  * any PHY port.
2224ae3961SKevin Smith  *
2324ae3961SKevin Smith  * The configuration determines which PHY ports to activate using the
2424ae3961SKevin Smith  * CONFIG_MV88E61XX_PHY_PORTS bitmask.  Setting bit 0 will activate port 0, bit
2524ae3961SKevin Smith  * 1 activates port 1, etc.  Do not set the bit for the port the CPU is
2624ae3961SKevin Smith  * connected to unless it is connected over a PHY interface (not MII).
2724ae3961SKevin Smith  *
2824ae3961SKevin Smith  * This driver was written for and tested on the mv88e6176 with an SGMII
2924ae3961SKevin Smith  * connection.  Other configurations should be supported, but some additions or
3024ae3961SKevin Smith  * changes may be required.
3124ae3961SKevin Smith  */
3224ae3961SKevin Smith 
3324ae3961SKevin Smith #include <common.h>
3424ae3961SKevin Smith 
3524ae3961SKevin Smith #include <bitfield.h>
3624ae3961SKevin Smith #include <errno.h>
3724ae3961SKevin Smith #include <malloc.h>
3824ae3961SKevin Smith #include <miiphy.h>
3924ae3961SKevin Smith #include <netdev.h>
4024ae3961SKevin Smith 
4124ae3961SKevin Smith #define PHY_AUTONEGOTIATE_TIMEOUT	5000
4224ae3961SKevin Smith 
4365d4d00aSChris Packham #define PORT_COUNT			11
4424ae3961SKevin Smith #define PORT_MASK			((1 << PORT_COUNT) - 1)
4524ae3961SKevin Smith 
4624ae3961SKevin Smith /* Device addresses */
4724ae3961SKevin Smith #define DEVADDR_PHY(p)			(p)
4824ae3961SKevin Smith #define DEVADDR_PORT(p)			(0x10 + (p))
4924ae3961SKevin Smith #define DEVADDR_SERDES			0x0F
5024ae3961SKevin Smith #define DEVADDR_GLOBAL_1		0x1B
5124ae3961SKevin Smith #define DEVADDR_GLOBAL_2		0x1C
5224ae3961SKevin Smith 
5324ae3961SKevin Smith /* SMI indirection registers for multichip addressing mode */
5424ae3961SKevin Smith #define SMI_CMD_REG			0x00
5524ae3961SKevin Smith #define SMI_DATA_REG			0x01
5624ae3961SKevin Smith 
5724ae3961SKevin Smith /* Global registers */
5824ae3961SKevin Smith #define GLOBAL1_STATUS			0x00
5924ae3961SKevin Smith #define GLOBAL1_CTRL			0x04
6024ae3961SKevin Smith #define GLOBAL1_MON_CTRL		0x1A
6124ae3961SKevin Smith 
6224ae3961SKevin Smith /* Global 2 registers */
6324ae3961SKevin Smith #define GLOBAL2_REG_PHY_CMD		0x18
6424ae3961SKevin Smith #define GLOBAL2_REG_PHY_DATA		0x19
6524ae3961SKevin Smith 
6624ae3961SKevin Smith /* Port registers */
6724ae3961SKevin Smith #define PORT_REG_STATUS			0x00
6824ae3961SKevin Smith #define PORT_REG_PHYS_CTRL		0x01
6924ae3961SKevin Smith #define PORT_REG_SWITCH_ID		0x03
7024ae3961SKevin Smith #define PORT_REG_CTRL			0x04
7124ae3961SKevin Smith #define PORT_REG_VLAN_MAP		0x06
7224ae3961SKevin Smith #define PORT_REG_VLAN_ID		0x07
7324ae3961SKevin Smith 
7424ae3961SKevin Smith /* Phy registers */
7524ae3961SKevin Smith #define PHY_REG_CTRL1			0x10
7624ae3961SKevin Smith #define PHY_REG_STATUS1			0x11
7724ae3961SKevin Smith #define PHY_REG_PAGE			0x16
7824ae3961SKevin Smith 
7924ae3961SKevin Smith /* Serdes registers */
8024ae3961SKevin Smith #define SERDES_REG_CTRL_1		0x10
8124ae3961SKevin Smith 
8224ae3961SKevin Smith /* Phy page numbers */
8324ae3961SKevin Smith #define PHY_PAGE_COPPER			0
8424ae3961SKevin Smith #define PHY_PAGE_SERDES			1
8524ae3961SKevin Smith 
8624ae3961SKevin Smith /* Register fields */
8724ae3961SKevin Smith #define GLOBAL1_CTRL_SWRESET		BIT(15)
8824ae3961SKevin Smith 
8924ae3961SKevin Smith #define GLOBAL1_MON_CTRL_CPUDEST_SHIFT	4
9024ae3961SKevin Smith #define GLOBAL1_MON_CTRL_CPUDEST_WIDTH	4
9124ae3961SKevin Smith 
9224ae3961SKevin Smith #define PORT_REG_STATUS_LINK		BIT(11)
9324ae3961SKevin Smith #define PORT_REG_STATUS_DUPLEX		BIT(10)
9424ae3961SKevin Smith 
9524ae3961SKevin Smith #define PORT_REG_STATUS_SPEED_SHIFT	8
9624ae3961SKevin Smith #define PORT_REG_STATUS_SPEED_WIDTH	2
9724ae3961SKevin Smith #define PORT_REG_STATUS_SPEED_10	0
9824ae3961SKevin Smith #define PORT_REG_STATUS_SPEED_100	1
9924ae3961SKevin Smith #define PORT_REG_STATUS_SPEED_1000	2
10024ae3961SKevin Smith 
10124ae3961SKevin Smith #define PORT_REG_STATUS_CMODE_MASK		0xF
10224ae3961SKevin Smith #define PORT_REG_STATUS_CMODE_100BASE_X		0x8
10324ae3961SKevin Smith #define PORT_REG_STATUS_CMODE_1000BASE_X	0x9
10424ae3961SKevin Smith #define PORT_REG_STATUS_CMODE_SGMII		0xa
10524ae3961SKevin Smith 
106b755abecSChris Packham #define PORT_REG_PHYS_CTRL_PCS_AN_EN	BIT(10)
107b755abecSChris Packham #define PORT_REG_PHYS_CTRL_PCS_AN_RST	BIT(9)
108b755abecSChris Packham #define PORT_REG_PHYS_CTRL_FC_VALUE	BIT(7)
109b755abecSChris Packham #define PORT_REG_PHYS_CTRL_FC_FORCE	BIT(6)
11024ae3961SKevin Smith #define PORT_REG_PHYS_CTRL_LINK_VALUE	BIT(5)
11124ae3961SKevin Smith #define PORT_REG_PHYS_CTRL_LINK_FORCE	BIT(4)
112b755abecSChris Packham #define PORT_REG_PHYS_CTRL_DUPLEX_VALUE	BIT(3)
113b755abecSChris Packham #define PORT_REG_PHYS_CTRL_DUPLEX_FORCE	BIT(2)
114b755abecSChris Packham #define PORT_REG_PHYS_CTRL_SPD1000	BIT(1)
115b755abecSChris Packham #define PORT_REG_PHYS_CTRL_SPD_MASK	(BIT(1) | BIT(0))
11624ae3961SKevin Smith 
11724ae3961SKevin Smith #define PORT_REG_CTRL_PSTATE_SHIFT	0
11824ae3961SKevin Smith #define PORT_REG_CTRL_PSTATE_WIDTH	2
11924ae3961SKevin Smith 
12024ae3961SKevin Smith #define PORT_REG_VLAN_ID_DEF_VID_SHIFT	0
12124ae3961SKevin Smith #define PORT_REG_VLAN_ID_DEF_VID_WIDTH	12
12224ae3961SKevin Smith 
12324ae3961SKevin Smith #define PORT_REG_VLAN_MAP_TABLE_SHIFT	0
12424ae3961SKevin Smith #define PORT_REG_VLAN_MAP_TABLE_WIDTH	11
12524ae3961SKevin Smith 
12624ae3961SKevin Smith #define SERDES_REG_CTRL_1_FORCE_LINK	BIT(10)
12724ae3961SKevin Smith 
12824ae3961SKevin Smith #define PHY_REG_CTRL1_ENERGY_DET_SHIFT	8
12924ae3961SKevin Smith #define PHY_REG_CTRL1_ENERGY_DET_WIDTH	2
13024ae3961SKevin Smith 
13124ae3961SKevin Smith /* Field values */
13224ae3961SKevin Smith #define PORT_REG_CTRL_PSTATE_DISABLED	0
13324ae3961SKevin Smith #define PORT_REG_CTRL_PSTATE_FORWARD	3
13424ae3961SKevin Smith 
13524ae3961SKevin Smith #define PHY_REG_CTRL1_ENERGY_DET_OFF	0
13624ae3961SKevin Smith #define PHY_REG_CTRL1_ENERGY_DET_SENSE_ONLY	2
13724ae3961SKevin Smith #define PHY_REG_CTRL1_ENERGY_DET_SENSE_XMIT	3
13824ae3961SKevin Smith 
13924ae3961SKevin Smith /* PHY Status Register */
14024ae3961SKevin Smith #define PHY_REG_STATUS1_SPEED		0xc000
14124ae3961SKevin Smith #define PHY_REG_STATUS1_GBIT		0x8000
14224ae3961SKevin Smith #define PHY_REG_STATUS1_100		0x4000
14324ae3961SKevin Smith #define PHY_REG_STATUS1_DUPLEX		0x2000
14424ae3961SKevin Smith #define PHY_REG_STATUS1_SPDDONE		0x0800
14524ae3961SKevin Smith #define PHY_REG_STATUS1_LINK		0x0400
14624ae3961SKevin Smith #define PHY_REG_STATUS1_ENERGY		0x0010
14724ae3961SKevin Smith 
14824ae3961SKevin Smith /*
14924ae3961SKevin Smith  * Macros for building commands for indirect addressing modes.  These are valid
15024ae3961SKevin Smith  * for both the indirect multichip addressing mode and the PHY indirection
15124ae3961SKevin Smith  * required for the writes to any PHY register.
15224ae3961SKevin Smith  */
15324ae3961SKevin Smith #define SMI_BUSY			BIT(15)
15424ae3961SKevin Smith #define SMI_CMD_CLAUSE_22		BIT(12)
15524ae3961SKevin Smith #define SMI_CMD_CLAUSE_22_OP_READ	(2 << 10)
15624ae3961SKevin Smith #define SMI_CMD_CLAUSE_22_OP_WRITE	(1 << 10)
15724ae3961SKevin Smith 
15824ae3961SKevin Smith #define SMI_CMD_READ			(SMI_BUSY | SMI_CMD_CLAUSE_22 | \
15924ae3961SKevin Smith 					 SMI_CMD_CLAUSE_22_OP_READ)
16024ae3961SKevin Smith #define SMI_CMD_WRITE			(SMI_BUSY | SMI_CMD_CLAUSE_22 | \
16124ae3961SKevin Smith 					 SMI_CMD_CLAUSE_22_OP_WRITE)
16224ae3961SKevin Smith 
16324ae3961SKevin Smith #define SMI_CMD_ADDR_SHIFT		5
16424ae3961SKevin Smith #define SMI_CMD_ADDR_WIDTH		5
16524ae3961SKevin Smith #define SMI_CMD_REG_SHIFT		0
16624ae3961SKevin Smith #define SMI_CMD_REG_WIDTH		5
16724ae3961SKevin Smith 
16824ae3961SKevin Smith /* Check for required macros */
16924ae3961SKevin Smith #ifndef CONFIG_MV88E61XX_PHY_PORTS
17024ae3961SKevin Smith #error Define CONFIG_MV88E61XX_PHY_PORTS to indicate which physical ports \
17124ae3961SKevin Smith 	to activate
17224ae3961SKevin Smith #endif
17324ae3961SKevin Smith #ifndef CONFIG_MV88E61XX_CPU_PORT
17424ae3961SKevin Smith #error Define CONFIG_MV88E61XX_CPU_PORT to the port the CPU is attached to
17524ae3961SKevin Smith #endif
17624ae3961SKevin Smith 
177b755abecSChris Packham /*
178b755abecSChris Packham  *  These are ports without PHYs that may be wired directly
179b755abecSChris Packham  * to other serdes interfaces
180b755abecSChris Packham  */
181b755abecSChris Packham #ifndef CONFIG_MV88E61XX_FIXED_PORTS
182b755abecSChris Packham #define CONFIG_MV88E61XX_FIXED_PORTS 0
183b755abecSChris Packham #endif
184b755abecSChris Packham 
18524ae3961SKevin Smith /* ID register values for different switch models */
18665d4d00aSChris Packham #define PORT_SWITCH_ID_6096		0x0980
18765d4d00aSChris Packham #define PORT_SWITCH_ID_6097		0x0990
18824ae3961SKevin Smith #define PORT_SWITCH_ID_6172		0x1720
18924ae3961SKevin Smith #define PORT_SWITCH_ID_6176		0x1760
19024ae3961SKevin Smith #define PORT_SWITCH_ID_6240		0x2400
19124ae3961SKevin Smith #define PORT_SWITCH_ID_6352		0x3520
19224ae3961SKevin Smith 
19324ae3961SKevin Smith struct mv88e61xx_phy_priv {
19424ae3961SKevin Smith 	struct mii_dev *mdio_bus;
19524ae3961SKevin Smith 	int smi_addr;
19624ae3961SKevin Smith 	int id;
19724ae3961SKevin Smith };
19824ae3961SKevin Smith 
smi_cmd(int cmd,int addr,int reg)19924ae3961SKevin Smith static inline int smi_cmd(int cmd, int addr, int reg)
20024ae3961SKevin Smith {
20124ae3961SKevin Smith 	cmd = bitfield_replace(cmd, SMI_CMD_ADDR_SHIFT, SMI_CMD_ADDR_WIDTH,
20224ae3961SKevin Smith 			       addr);
20324ae3961SKevin Smith 	cmd = bitfield_replace(cmd, SMI_CMD_REG_SHIFT, SMI_CMD_REG_WIDTH, reg);
20424ae3961SKevin Smith 	return cmd;
20524ae3961SKevin Smith }
20624ae3961SKevin Smith 
smi_cmd_read(int addr,int reg)20724ae3961SKevin Smith static inline int smi_cmd_read(int addr, int reg)
20824ae3961SKevin Smith {
20924ae3961SKevin Smith 	return smi_cmd(SMI_CMD_READ, addr, reg);
21024ae3961SKevin Smith }
21124ae3961SKevin Smith 
smi_cmd_write(int addr,int reg)21224ae3961SKevin Smith static inline int smi_cmd_write(int addr, int reg)
21324ae3961SKevin Smith {
21424ae3961SKevin Smith 	return smi_cmd(SMI_CMD_WRITE, addr, reg);
21524ae3961SKevin Smith }
21624ae3961SKevin Smith 
mv88e61xx_hw_reset(struct phy_device * phydev)21724ae3961SKevin Smith __weak int mv88e61xx_hw_reset(struct phy_device *phydev)
21824ae3961SKevin Smith {
21924ae3961SKevin Smith 	return 0;
22024ae3961SKevin Smith }
22124ae3961SKevin Smith 
22224ae3961SKevin Smith /* Wait for the current SMI indirect command to complete */
mv88e61xx_smi_wait(struct mii_dev * bus,int smi_addr)22324ae3961SKevin Smith static int mv88e61xx_smi_wait(struct mii_dev *bus, int smi_addr)
22424ae3961SKevin Smith {
22524ae3961SKevin Smith 	int val;
22624ae3961SKevin Smith 	u32 timeout = 100;
22724ae3961SKevin Smith 
22824ae3961SKevin Smith 	do {
22924ae3961SKevin Smith 		val = bus->read(bus, smi_addr, MDIO_DEVAD_NONE, SMI_CMD_REG);
23024ae3961SKevin Smith 		if (val >= 0 && (val & SMI_BUSY) == 0)
23124ae3961SKevin Smith 			return 0;
23224ae3961SKevin Smith 
23324ae3961SKevin Smith 		mdelay(1);
23424ae3961SKevin Smith 	} while (--timeout);
23524ae3961SKevin Smith 
23624ae3961SKevin Smith 	puts("SMI busy timeout\n");
23724ae3961SKevin Smith 	return -ETIMEDOUT;
23824ae3961SKevin Smith }
23924ae3961SKevin Smith 
24024ae3961SKevin Smith /*
24124ae3961SKevin Smith  * The mv88e61xx has three types of addresses: the smi bus address, the device
24224ae3961SKevin Smith  * address, and the register address.  The smi bus address distinguishes it on
24324ae3961SKevin Smith  * the smi bus from other PHYs or switches.  The device address determines
24424ae3961SKevin Smith  * which on-chip register set you are reading/writing (the various PHYs, their
24524ae3961SKevin Smith  * associated ports, or global configuration registers).  The register address
24624ae3961SKevin Smith  * is the offset of the register you are reading/writing.
24724ae3961SKevin Smith  *
24824ae3961SKevin Smith  * When the mv88e61xx is hardware configured to have address zero, it behaves in
24924ae3961SKevin Smith  * single-chip addressing mode, where it responds to all SMI addresses, using
25024ae3961SKevin Smith  * the smi address as its device address.  This obviously only works when this
25124ae3961SKevin Smith  * is the only chip on the SMI bus.  This allows the driver to access device
25224ae3961SKevin Smith  * registers without using indirection.  When the chip is configured to a
25324ae3961SKevin Smith  * non-zero address, it only responds to that SMI address and requires indirect
25424ae3961SKevin Smith  * writes to access the different device addresses.
25524ae3961SKevin Smith  */
mv88e61xx_reg_read(struct phy_device * phydev,int dev,int reg)25624ae3961SKevin Smith static int mv88e61xx_reg_read(struct phy_device *phydev, int dev, int reg)
25724ae3961SKevin Smith {
25824ae3961SKevin Smith 	struct mv88e61xx_phy_priv *priv = phydev->priv;
25924ae3961SKevin Smith 	struct mii_dev *mdio_bus = priv->mdio_bus;
26024ae3961SKevin Smith 	int smi_addr = priv->smi_addr;
26124ae3961SKevin Smith 	int res;
26224ae3961SKevin Smith 
26324ae3961SKevin Smith 	/* In single-chip mode, the device can be addressed directly */
26424ae3961SKevin Smith 	if (smi_addr == 0)
26524ae3961SKevin Smith 		return mdio_bus->read(mdio_bus, dev, MDIO_DEVAD_NONE, reg);
26624ae3961SKevin Smith 
26724ae3961SKevin Smith 	/* Wait for the bus to become free */
26824ae3961SKevin Smith 	res = mv88e61xx_smi_wait(mdio_bus, smi_addr);
26924ae3961SKevin Smith 	if (res < 0)
27024ae3961SKevin Smith 		return res;
27124ae3961SKevin Smith 
27224ae3961SKevin Smith 	/* Issue the read command */
27324ae3961SKevin Smith 	res = mdio_bus->write(mdio_bus, smi_addr, MDIO_DEVAD_NONE, SMI_CMD_REG,
27424ae3961SKevin Smith 			 smi_cmd_read(dev, reg));
27524ae3961SKevin Smith 	if (res < 0)
27624ae3961SKevin Smith 		return res;
27724ae3961SKevin Smith 
27824ae3961SKevin Smith 	/* Wait for the read command to complete */
27924ae3961SKevin Smith 	res = mv88e61xx_smi_wait(mdio_bus, smi_addr);
28024ae3961SKevin Smith 	if (res < 0)
28124ae3961SKevin Smith 		return res;
28224ae3961SKevin Smith 
28324ae3961SKevin Smith 	/* Read the data */
28424ae3961SKevin Smith 	res = mdio_bus->read(mdio_bus, smi_addr, MDIO_DEVAD_NONE, SMI_DATA_REG);
28524ae3961SKevin Smith 	if (res < 0)
28624ae3961SKevin Smith 		return res;
28724ae3961SKevin Smith 
28824ae3961SKevin Smith 	return bitfield_extract(res, 0, 16);
28924ae3961SKevin Smith }
29024ae3961SKevin Smith 
29124ae3961SKevin Smith /* See the comment above mv88e61xx_reg_read */
mv88e61xx_reg_write(struct phy_device * phydev,int dev,int reg,u16 val)29224ae3961SKevin Smith static int mv88e61xx_reg_write(struct phy_device *phydev, int dev, int reg,
29324ae3961SKevin Smith 			       u16 val)
29424ae3961SKevin Smith {
29524ae3961SKevin Smith 	struct mv88e61xx_phy_priv *priv = phydev->priv;
29624ae3961SKevin Smith 	struct mii_dev *mdio_bus = priv->mdio_bus;
29724ae3961SKevin Smith 	int smi_addr = priv->smi_addr;
29824ae3961SKevin Smith 	int res;
29924ae3961SKevin Smith 
30024ae3961SKevin Smith 	/* In single-chip mode, the device can be addressed directly */
30124ae3961SKevin Smith 	if (smi_addr == 0) {
30224ae3961SKevin Smith 		return mdio_bus->write(mdio_bus, dev, MDIO_DEVAD_NONE, reg,
30324ae3961SKevin Smith 				val);
30424ae3961SKevin Smith 	}
30524ae3961SKevin Smith 
30624ae3961SKevin Smith 	/* Wait for the bus to become free */
30724ae3961SKevin Smith 	res = mv88e61xx_smi_wait(mdio_bus, smi_addr);
30824ae3961SKevin Smith 	if (res < 0)
30924ae3961SKevin Smith 		return res;
31024ae3961SKevin Smith 
31124ae3961SKevin Smith 	/* Set the data to write */
31224ae3961SKevin Smith 	res = mdio_bus->write(mdio_bus, smi_addr, MDIO_DEVAD_NONE,
31324ae3961SKevin Smith 				SMI_DATA_REG, val);
31424ae3961SKevin Smith 	if (res < 0)
31524ae3961SKevin Smith 		return res;
31624ae3961SKevin Smith 
31724ae3961SKevin Smith 	/* Issue the write command */
31824ae3961SKevin Smith 	res = mdio_bus->write(mdio_bus, smi_addr, MDIO_DEVAD_NONE, SMI_CMD_REG,
31924ae3961SKevin Smith 				smi_cmd_write(dev, reg));
32024ae3961SKevin Smith 	if (res < 0)
32124ae3961SKevin Smith 		return res;
32224ae3961SKevin Smith 
32324ae3961SKevin Smith 	/* Wait for the write command to complete */
32424ae3961SKevin Smith 	res = mv88e61xx_smi_wait(mdio_bus, smi_addr);
32524ae3961SKevin Smith 	if (res < 0)
32624ae3961SKevin Smith 		return res;
32724ae3961SKevin Smith 
32824ae3961SKevin Smith 	return 0;
32924ae3961SKevin Smith }
33024ae3961SKevin Smith 
mv88e61xx_phy_wait(struct phy_device * phydev)33124ae3961SKevin Smith static int mv88e61xx_phy_wait(struct phy_device *phydev)
33224ae3961SKevin Smith {
33324ae3961SKevin Smith 	int val;
33424ae3961SKevin Smith 	u32 timeout = 100;
33524ae3961SKevin Smith 
33624ae3961SKevin Smith 	do {
33724ae3961SKevin Smith 		val = mv88e61xx_reg_read(phydev, DEVADDR_GLOBAL_2,
33824ae3961SKevin Smith 					 GLOBAL2_REG_PHY_CMD);
33924ae3961SKevin Smith 		if (val >= 0 && (val & SMI_BUSY) == 0)
34024ae3961SKevin Smith 			return 0;
34124ae3961SKevin Smith 
34224ae3961SKevin Smith 		mdelay(1);
34324ae3961SKevin Smith 	} while (--timeout);
34424ae3961SKevin Smith 
34524ae3961SKevin Smith 	return -ETIMEDOUT;
34624ae3961SKevin Smith }
34724ae3961SKevin Smith 
mv88e61xx_phy_read_indirect(struct mii_dev * smi_wrapper,int dev,int devad,int reg)34824ae3961SKevin Smith static int mv88e61xx_phy_read_indirect(struct mii_dev *smi_wrapper, int dev,
34924ae3961SKevin Smith 		int devad, int reg)
35024ae3961SKevin Smith {
35124ae3961SKevin Smith 	struct phy_device *phydev;
35224ae3961SKevin Smith 	int res;
35324ae3961SKevin Smith 
35424ae3961SKevin Smith 	phydev = (struct phy_device *)smi_wrapper->priv;
35524ae3961SKevin Smith 
35624ae3961SKevin Smith 	/* Issue command to read */
35724ae3961SKevin Smith 	res = mv88e61xx_reg_write(phydev, DEVADDR_GLOBAL_2,
35824ae3961SKevin Smith 				  GLOBAL2_REG_PHY_CMD,
35924ae3961SKevin Smith 				  smi_cmd_read(dev, reg));
36024ae3961SKevin Smith 
36124ae3961SKevin Smith 	/* Wait for data to be read */
36224ae3961SKevin Smith 	res = mv88e61xx_phy_wait(phydev);
36324ae3961SKevin Smith 	if (res < 0)
36424ae3961SKevin Smith 		return res;
36524ae3961SKevin Smith 
36624ae3961SKevin Smith 	/* Read retrieved data */
36724ae3961SKevin Smith 	return mv88e61xx_reg_read(phydev, DEVADDR_GLOBAL_2,
36824ae3961SKevin Smith 				  GLOBAL2_REG_PHY_DATA);
36924ae3961SKevin Smith }
37024ae3961SKevin Smith 
mv88e61xx_phy_write_indirect(struct mii_dev * smi_wrapper,int dev,int devad,int reg,u16 data)37124ae3961SKevin Smith static int mv88e61xx_phy_write_indirect(struct mii_dev *smi_wrapper, int dev,
37224ae3961SKevin Smith 		int devad, int reg, u16 data)
37324ae3961SKevin Smith {
37424ae3961SKevin Smith 	struct phy_device *phydev;
37524ae3961SKevin Smith 	int res;
37624ae3961SKevin Smith 
37724ae3961SKevin Smith 	phydev = (struct phy_device *)smi_wrapper->priv;
37824ae3961SKevin Smith 
37924ae3961SKevin Smith 	/* Set the data to write */
38024ae3961SKevin Smith 	res = mv88e61xx_reg_write(phydev, DEVADDR_GLOBAL_2,
38124ae3961SKevin Smith 				  GLOBAL2_REG_PHY_DATA, data);
38224ae3961SKevin Smith 	if (res < 0)
38324ae3961SKevin Smith 		return res;
38424ae3961SKevin Smith 	/* Issue the write command */
38524ae3961SKevin Smith 	res = mv88e61xx_reg_write(phydev, DEVADDR_GLOBAL_2,
38624ae3961SKevin Smith 				  GLOBAL2_REG_PHY_CMD,
38724ae3961SKevin Smith 				  smi_cmd_write(dev, reg));
38824ae3961SKevin Smith 	if (res < 0)
38924ae3961SKevin Smith 		return res;
39024ae3961SKevin Smith 
39124ae3961SKevin Smith 	/* Wait for command to complete */
39224ae3961SKevin Smith 	return mv88e61xx_phy_wait(phydev);
39324ae3961SKevin Smith }
39424ae3961SKevin Smith 
39524ae3961SKevin Smith /* Wrapper function to make calls to phy_read_indirect simpler */
mv88e61xx_phy_read(struct phy_device * phydev,int phy,int reg)39624ae3961SKevin Smith static int mv88e61xx_phy_read(struct phy_device *phydev, int phy, int reg)
39724ae3961SKevin Smith {
39824ae3961SKevin Smith 	return mv88e61xx_phy_read_indirect(phydev->bus, DEVADDR_PHY(phy),
39924ae3961SKevin Smith 					   MDIO_DEVAD_NONE, reg);
40024ae3961SKevin Smith }
40124ae3961SKevin Smith 
40224ae3961SKevin Smith /* Wrapper function to make calls to phy_read_indirect simpler */
mv88e61xx_phy_write(struct phy_device * phydev,int phy,int reg,u16 val)40324ae3961SKevin Smith static int mv88e61xx_phy_write(struct phy_device *phydev, int phy,
40424ae3961SKevin Smith 		int reg, u16 val)
40524ae3961SKevin Smith {
40624ae3961SKevin Smith 	return mv88e61xx_phy_write_indirect(phydev->bus, DEVADDR_PHY(phy),
40724ae3961SKevin Smith 					    MDIO_DEVAD_NONE, reg, val);
40824ae3961SKevin Smith }
40924ae3961SKevin Smith 
mv88e61xx_port_read(struct phy_device * phydev,u8 port,u8 reg)41024ae3961SKevin Smith static int mv88e61xx_port_read(struct phy_device *phydev, u8 port, u8 reg)
41124ae3961SKevin Smith {
41224ae3961SKevin Smith 	return mv88e61xx_reg_read(phydev, DEVADDR_PORT(port), reg);
41324ae3961SKevin Smith }
41424ae3961SKevin Smith 
mv88e61xx_port_write(struct phy_device * phydev,u8 port,u8 reg,u16 val)41524ae3961SKevin Smith static int mv88e61xx_port_write(struct phy_device *phydev, u8 port, u8 reg,
41624ae3961SKevin Smith 								u16 val)
41724ae3961SKevin Smith {
41824ae3961SKevin Smith 	return mv88e61xx_reg_write(phydev, DEVADDR_PORT(port), reg, val);
41924ae3961SKevin Smith }
42024ae3961SKevin Smith 
mv88e61xx_set_page(struct phy_device * phydev,u8 phy,u8 page)42124ae3961SKevin Smith static int mv88e61xx_set_page(struct phy_device *phydev, u8 phy, u8 page)
42224ae3961SKevin Smith {
42324ae3961SKevin Smith 	return mv88e61xx_phy_write(phydev, phy, PHY_REG_PAGE, page);
42424ae3961SKevin Smith }
42524ae3961SKevin Smith 
mv88e61xx_get_switch_id(struct phy_device * phydev)42624ae3961SKevin Smith static int mv88e61xx_get_switch_id(struct phy_device *phydev)
42724ae3961SKevin Smith {
42824ae3961SKevin Smith 	int res;
42924ae3961SKevin Smith 
43024ae3961SKevin Smith 	res = mv88e61xx_port_read(phydev, 0, PORT_REG_SWITCH_ID);
43124ae3961SKevin Smith 	if (res < 0)
43224ae3961SKevin Smith 		return res;
43324ae3961SKevin Smith 	return res & 0xfff0;
43424ae3961SKevin Smith }
43524ae3961SKevin Smith 
mv88e61xx_6352_family(struct phy_device * phydev)43624ae3961SKevin Smith static bool mv88e61xx_6352_family(struct phy_device *phydev)
43724ae3961SKevin Smith {
43824ae3961SKevin Smith 	struct mv88e61xx_phy_priv *priv = phydev->priv;
43924ae3961SKevin Smith 
44024ae3961SKevin Smith 	switch (priv->id) {
44124ae3961SKevin Smith 	case PORT_SWITCH_ID_6172:
44224ae3961SKevin Smith 	case PORT_SWITCH_ID_6176:
44324ae3961SKevin Smith 	case PORT_SWITCH_ID_6240:
44424ae3961SKevin Smith 	case PORT_SWITCH_ID_6352:
44524ae3961SKevin Smith 		return true;
44624ae3961SKevin Smith 	}
44724ae3961SKevin Smith 	return false;
44824ae3961SKevin Smith }
44924ae3961SKevin Smith 
mv88e61xx_get_cmode(struct phy_device * phydev,u8 port)45024ae3961SKevin Smith static int mv88e61xx_get_cmode(struct phy_device *phydev, u8 port)
45124ae3961SKevin Smith {
45224ae3961SKevin Smith 	int res;
45324ae3961SKevin Smith 
45424ae3961SKevin Smith 	res = mv88e61xx_port_read(phydev, port, PORT_REG_STATUS);
45524ae3961SKevin Smith 	if (res < 0)
45624ae3961SKevin Smith 		return res;
45724ae3961SKevin Smith 	return res & PORT_REG_STATUS_CMODE_MASK;
45824ae3961SKevin Smith }
45924ae3961SKevin Smith 
mv88e61xx_parse_status(struct phy_device * phydev)46024ae3961SKevin Smith static int mv88e61xx_parse_status(struct phy_device *phydev)
46124ae3961SKevin Smith {
46224ae3961SKevin Smith 	unsigned int speed;
46324ae3961SKevin Smith 	unsigned int mii_reg;
46424ae3961SKevin Smith 
46524ae3961SKevin Smith 	mii_reg = phy_read(phydev, MDIO_DEVAD_NONE, PHY_REG_STATUS1);
46624ae3961SKevin Smith 
46724ae3961SKevin Smith 	if ((mii_reg & PHY_REG_STATUS1_LINK) &&
46824ae3961SKevin Smith 	    !(mii_reg & PHY_REG_STATUS1_SPDDONE)) {
46924ae3961SKevin Smith 		int i = 0;
47024ae3961SKevin Smith 
47124ae3961SKevin Smith 		puts("Waiting for PHY realtime link");
47224ae3961SKevin Smith 		while (!(mii_reg & PHY_REG_STATUS1_SPDDONE)) {
47324ae3961SKevin Smith 			/* Timeout reached ? */
47424ae3961SKevin Smith 			if (i > PHY_AUTONEGOTIATE_TIMEOUT) {
47524ae3961SKevin Smith 				puts(" TIMEOUT !\n");
47624ae3961SKevin Smith 				phydev->link = 0;
47724ae3961SKevin Smith 				break;
47824ae3961SKevin Smith 			}
47924ae3961SKevin Smith 
48024ae3961SKevin Smith 			if ((i++ % 1000) == 0)
48124ae3961SKevin Smith 				putc('.');
48224ae3961SKevin Smith 			udelay(1000);
48324ae3961SKevin Smith 			mii_reg = phy_read(phydev, MDIO_DEVAD_NONE,
48424ae3961SKevin Smith 					PHY_REG_STATUS1);
48524ae3961SKevin Smith 		}
48624ae3961SKevin Smith 		puts(" done\n");
48724ae3961SKevin Smith 		udelay(500000);	/* another 500 ms (results in faster booting) */
48824ae3961SKevin Smith 	} else {
48924ae3961SKevin Smith 		if (mii_reg & PHY_REG_STATUS1_LINK)
49024ae3961SKevin Smith 			phydev->link = 1;
49124ae3961SKevin Smith 		else
49224ae3961SKevin Smith 			phydev->link = 0;
49324ae3961SKevin Smith 	}
49424ae3961SKevin Smith 
49524ae3961SKevin Smith 	if (mii_reg & PHY_REG_STATUS1_DUPLEX)
49624ae3961SKevin Smith 		phydev->duplex = DUPLEX_FULL;
49724ae3961SKevin Smith 	else
49824ae3961SKevin Smith 		phydev->duplex = DUPLEX_HALF;
49924ae3961SKevin Smith 
50024ae3961SKevin Smith 	speed = mii_reg & PHY_REG_STATUS1_SPEED;
50124ae3961SKevin Smith 
50224ae3961SKevin Smith 	switch (speed) {
50324ae3961SKevin Smith 	case PHY_REG_STATUS1_GBIT:
50424ae3961SKevin Smith 		phydev->speed = SPEED_1000;
50524ae3961SKevin Smith 		break;
50624ae3961SKevin Smith 	case PHY_REG_STATUS1_100:
50724ae3961SKevin Smith 		phydev->speed = SPEED_100;
50824ae3961SKevin Smith 		break;
50924ae3961SKevin Smith 	default:
51024ae3961SKevin Smith 		phydev->speed = SPEED_10;
51124ae3961SKevin Smith 		break;
51224ae3961SKevin Smith 	}
51324ae3961SKevin Smith 
51424ae3961SKevin Smith 	return 0;
51524ae3961SKevin Smith }
51624ae3961SKevin Smith 
mv88e61xx_switch_reset(struct phy_device * phydev)51724ae3961SKevin Smith static int mv88e61xx_switch_reset(struct phy_device *phydev)
51824ae3961SKevin Smith {
51924ae3961SKevin Smith 	int time;
52024ae3961SKevin Smith 	int val;
52124ae3961SKevin Smith 	u8 port;
52224ae3961SKevin Smith 
52324ae3961SKevin Smith 	/* Disable all ports */
52424ae3961SKevin Smith 	for (port = 0; port < PORT_COUNT; port++) {
52524ae3961SKevin Smith 		val = mv88e61xx_port_read(phydev, port, PORT_REG_CTRL);
52624ae3961SKevin Smith 		if (val < 0)
52724ae3961SKevin Smith 			return val;
52824ae3961SKevin Smith 		val = bitfield_replace(val, PORT_REG_CTRL_PSTATE_SHIFT,
52924ae3961SKevin Smith 				       PORT_REG_CTRL_PSTATE_WIDTH,
53024ae3961SKevin Smith 				       PORT_REG_CTRL_PSTATE_DISABLED);
53124ae3961SKevin Smith 		val = mv88e61xx_port_write(phydev, port, PORT_REG_CTRL, val);
53224ae3961SKevin Smith 		if (val < 0)
53324ae3961SKevin Smith 			return val;
53424ae3961SKevin Smith 	}
53524ae3961SKevin Smith 
53624ae3961SKevin Smith 	/* Wait 2 ms for queues to drain */
53724ae3961SKevin Smith 	udelay(2000);
53824ae3961SKevin Smith 
53924ae3961SKevin Smith 	/* Reset switch */
54024ae3961SKevin Smith 	val = mv88e61xx_reg_read(phydev, DEVADDR_GLOBAL_1, GLOBAL1_CTRL);
54124ae3961SKevin Smith 	if (val < 0)
54224ae3961SKevin Smith 		return val;
54324ae3961SKevin Smith 	val |= GLOBAL1_CTRL_SWRESET;
54424ae3961SKevin Smith 	val = mv88e61xx_reg_write(phydev, DEVADDR_GLOBAL_1,
54524ae3961SKevin Smith 				     GLOBAL1_CTRL, val);
54624ae3961SKevin Smith 	if (val < 0)
54724ae3961SKevin Smith 		return val;
54824ae3961SKevin Smith 
54924ae3961SKevin Smith 	/* Wait up to 1 second for switch reset complete */
55024ae3961SKevin Smith 	for (time = 1000; time; time--) {
55124ae3961SKevin Smith 		val = mv88e61xx_reg_read(phydev, DEVADDR_GLOBAL_1,
55224ae3961SKevin Smith 					    GLOBAL1_CTRL);
55324ae3961SKevin Smith 		if (val >= 0 && ((val & GLOBAL1_CTRL_SWRESET) == 0))
55424ae3961SKevin Smith 			break;
55524ae3961SKevin Smith 		udelay(1000);
55624ae3961SKevin Smith 	}
55724ae3961SKevin Smith 	if (!time)
55824ae3961SKevin Smith 		return -ETIMEDOUT;
55924ae3961SKevin Smith 
56024ae3961SKevin Smith 	return 0;
56124ae3961SKevin Smith }
56224ae3961SKevin Smith 
mv88e61xx_serdes_init(struct phy_device * phydev)56324ae3961SKevin Smith static int mv88e61xx_serdes_init(struct phy_device *phydev)
56424ae3961SKevin Smith {
56524ae3961SKevin Smith 	int val;
56624ae3961SKevin Smith 
56724ae3961SKevin Smith 	val = mv88e61xx_set_page(phydev, DEVADDR_SERDES, PHY_PAGE_SERDES);
56824ae3961SKevin Smith 	if (val < 0)
56924ae3961SKevin Smith 		return val;
57024ae3961SKevin Smith 
57124ae3961SKevin Smith 	/* Power up serdes module */
57224ae3961SKevin Smith 	val = mv88e61xx_phy_read(phydev, DEVADDR_SERDES, MII_BMCR);
57324ae3961SKevin Smith 	if (val < 0)
57424ae3961SKevin Smith 		return val;
57524ae3961SKevin Smith 	val &= ~(BMCR_PDOWN);
57624ae3961SKevin Smith 	val = mv88e61xx_phy_write(phydev, DEVADDR_SERDES, MII_BMCR, val);
57724ae3961SKevin Smith 	if (val < 0)
57824ae3961SKevin Smith 		return val;
57924ae3961SKevin Smith 
58024ae3961SKevin Smith 	return 0;
58124ae3961SKevin Smith }
58224ae3961SKevin Smith 
mv88e61xx_port_enable(struct phy_device * phydev,u8 port)58324ae3961SKevin Smith static int mv88e61xx_port_enable(struct phy_device *phydev, u8 port)
58424ae3961SKevin Smith {
58524ae3961SKevin Smith 	int val;
58624ae3961SKevin Smith 
58724ae3961SKevin Smith 	val = mv88e61xx_port_read(phydev, port, PORT_REG_CTRL);
58824ae3961SKevin Smith 	if (val < 0)
58924ae3961SKevin Smith 		return val;
59024ae3961SKevin Smith 	val = bitfield_replace(val, PORT_REG_CTRL_PSTATE_SHIFT,
59124ae3961SKevin Smith 			       PORT_REG_CTRL_PSTATE_WIDTH,
59224ae3961SKevin Smith 			       PORT_REG_CTRL_PSTATE_FORWARD);
59324ae3961SKevin Smith 	val = mv88e61xx_port_write(phydev, port, PORT_REG_CTRL, val);
59424ae3961SKevin Smith 	if (val < 0)
59524ae3961SKevin Smith 		return val;
59624ae3961SKevin Smith 
59724ae3961SKevin Smith 	return 0;
59824ae3961SKevin Smith }
59924ae3961SKevin Smith 
mv88e61xx_port_set_vlan(struct phy_device * phydev,u8 port,u16 mask)60024ae3961SKevin Smith static int mv88e61xx_port_set_vlan(struct phy_device *phydev, u8 port,
60165d4d00aSChris Packham 							u16 mask)
60224ae3961SKevin Smith {
60324ae3961SKevin Smith 	int val;
60424ae3961SKevin Smith 
60524ae3961SKevin Smith 	/* Set VID to port number plus one */
60624ae3961SKevin Smith 	val = mv88e61xx_port_read(phydev, port, PORT_REG_VLAN_ID);
60724ae3961SKevin Smith 	if (val < 0)
60824ae3961SKevin Smith 		return val;
60924ae3961SKevin Smith 	val = bitfield_replace(val, PORT_REG_VLAN_ID_DEF_VID_SHIFT,
61024ae3961SKevin Smith 			       PORT_REG_VLAN_ID_DEF_VID_WIDTH,
61124ae3961SKevin Smith 			       port + 1);
61224ae3961SKevin Smith 	val = mv88e61xx_port_write(phydev, port, PORT_REG_VLAN_ID, val);
61324ae3961SKevin Smith 	if (val < 0)
61424ae3961SKevin Smith 		return val;
61524ae3961SKevin Smith 
61624ae3961SKevin Smith 	/* Set VID mask */
61724ae3961SKevin Smith 	val = mv88e61xx_port_read(phydev, port, PORT_REG_VLAN_MAP);
61824ae3961SKevin Smith 	if (val < 0)
61924ae3961SKevin Smith 		return val;
62024ae3961SKevin Smith 	val = bitfield_replace(val, PORT_REG_VLAN_MAP_TABLE_SHIFT,
62124ae3961SKevin Smith 			       PORT_REG_VLAN_MAP_TABLE_WIDTH,
62224ae3961SKevin Smith 			       mask);
62324ae3961SKevin Smith 	val = mv88e61xx_port_write(phydev, port, PORT_REG_VLAN_MAP, val);
62424ae3961SKevin Smith 	if (val < 0)
62524ae3961SKevin Smith 		return val;
62624ae3961SKevin Smith 
62724ae3961SKevin Smith 	return 0;
62824ae3961SKevin Smith }
62924ae3961SKevin Smith 
mv88e61xx_read_port_config(struct phy_device * phydev,u8 port)63024ae3961SKevin Smith static int mv88e61xx_read_port_config(struct phy_device *phydev, u8 port)
63124ae3961SKevin Smith {
63224ae3961SKevin Smith 	int res;
63324ae3961SKevin Smith 	int val;
63424ae3961SKevin Smith 	bool forced = false;
63524ae3961SKevin Smith 
63624ae3961SKevin Smith 	val = mv88e61xx_port_read(phydev, port, PORT_REG_STATUS);
63724ae3961SKevin Smith 	if (val < 0)
63824ae3961SKevin Smith 		return val;
63924ae3961SKevin Smith 	if (!(val & PORT_REG_STATUS_LINK)) {
64024ae3961SKevin Smith 		/* Temporarily force link to read port configuration */
64124ae3961SKevin Smith 		u32 timeout = 100;
64224ae3961SKevin Smith 		forced = true;
64324ae3961SKevin Smith 
64424ae3961SKevin Smith 		val = mv88e61xx_port_read(phydev, port, PORT_REG_PHYS_CTRL);
64524ae3961SKevin Smith 		if (val < 0)
64624ae3961SKevin Smith 			return val;
64724ae3961SKevin Smith 		val |= (PORT_REG_PHYS_CTRL_LINK_FORCE |
64824ae3961SKevin Smith 				PORT_REG_PHYS_CTRL_LINK_VALUE);
64924ae3961SKevin Smith 		val = mv88e61xx_port_write(phydev, port, PORT_REG_PHYS_CTRL,
65024ae3961SKevin Smith 					   val);
65124ae3961SKevin Smith 		if (val < 0)
65224ae3961SKevin Smith 			return val;
65324ae3961SKevin Smith 
65424ae3961SKevin Smith 		/* Wait for status register to reflect forced link */
65524ae3961SKevin Smith 		do {
65624ae3961SKevin Smith 			val = mv88e61xx_port_read(phydev, port,
65724ae3961SKevin Smith 						  PORT_REG_STATUS);
658*4201223dSTom Rini 			if (val < 0) {
659*4201223dSTom Rini 				res = -EIO;
66024ae3961SKevin Smith 				goto unforce;
661*4201223dSTom Rini 			}
66224ae3961SKevin Smith 			if (val & PORT_REG_STATUS_LINK)
66324ae3961SKevin Smith 				break;
66424ae3961SKevin Smith 		} while (--timeout);
66524ae3961SKevin Smith 
66624ae3961SKevin Smith 		if (timeout == 0) {
66724ae3961SKevin Smith 			res = -ETIMEDOUT;
66824ae3961SKevin Smith 			goto unforce;
66924ae3961SKevin Smith 		}
67024ae3961SKevin Smith 	}
67124ae3961SKevin Smith 
67224ae3961SKevin Smith 	if (val & PORT_REG_STATUS_DUPLEX)
67324ae3961SKevin Smith 		phydev->duplex = DUPLEX_FULL;
67424ae3961SKevin Smith 	else
67524ae3961SKevin Smith 		phydev->duplex = DUPLEX_HALF;
67624ae3961SKevin Smith 
67724ae3961SKevin Smith 	val = bitfield_extract(val, PORT_REG_STATUS_SPEED_SHIFT,
67824ae3961SKevin Smith 			       PORT_REG_STATUS_SPEED_WIDTH);
67924ae3961SKevin Smith 	switch (val) {
68024ae3961SKevin Smith 	case PORT_REG_STATUS_SPEED_1000:
68124ae3961SKevin Smith 		phydev->speed = SPEED_1000;
68224ae3961SKevin Smith 		break;
68324ae3961SKevin Smith 	case PORT_REG_STATUS_SPEED_100:
68424ae3961SKevin Smith 		phydev->speed = SPEED_100;
68524ae3961SKevin Smith 		break;
68624ae3961SKevin Smith 	default:
68724ae3961SKevin Smith 		phydev->speed = SPEED_10;
68824ae3961SKevin Smith 		break;
68924ae3961SKevin Smith 	}
69024ae3961SKevin Smith 
69124ae3961SKevin Smith 	res = 0;
69224ae3961SKevin Smith 
69324ae3961SKevin Smith unforce:
69424ae3961SKevin Smith 	if (forced) {
69524ae3961SKevin Smith 		val = mv88e61xx_port_read(phydev, port, PORT_REG_PHYS_CTRL);
69624ae3961SKevin Smith 		if (val < 0)
69724ae3961SKevin Smith 			return val;
69824ae3961SKevin Smith 		val &= ~(PORT_REG_PHYS_CTRL_LINK_FORCE |
69924ae3961SKevin Smith 				PORT_REG_PHYS_CTRL_LINK_VALUE);
70024ae3961SKevin Smith 		val = mv88e61xx_port_write(phydev, port, PORT_REG_PHYS_CTRL,
70124ae3961SKevin Smith 					   val);
70224ae3961SKevin Smith 		if (val < 0)
70324ae3961SKevin Smith 			return val;
70424ae3961SKevin Smith 	}
70524ae3961SKevin Smith 
70624ae3961SKevin Smith 	return res;
70724ae3961SKevin Smith }
70824ae3961SKevin Smith 
mv88e61xx_set_cpu_port(struct phy_device * phydev)70924ae3961SKevin Smith static int mv88e61xx_set_cpu_port(struct phy_device *phydev)
71024ae3961SKevin Smith {
71124ae3961SKevin Smith 	int val;
71224ae3961SKevin Smith 
71324ae3961SKevin Smith 	/* Set CPUDest */
71424ae3961SKevin Smith 	val = mv88e61xx_reg_read(phydev, DEVADDR_GLOBAL_1, GLOBAL1_MON_CTRL);
71524ae3961SKevin Smith 	if (val < 0)
71624ae3961SKevin Smith 		return val;
71724ae3961SKevin Smith 	val = bitfield_replace(val, GLOBAL1_MON_CTRL_CPUDEST_SHIFT,
71824ae3961SKevin Smith 			       GLOBAL1_MON_CTRL_CPUDEST_WIDTH,
71924ae3961SKevin Smith 			       CONFIG_MV88E61XX_CPU_PORT);
72024ae3961SKevin Smith 	val = mv88e61xx_reg_write(phydev, DEVADDR_GLOBAL_1,
72124ae3961SKevin Smith 				     GLOBAL1_MON_CTRL, val);
72224ae3961SKevin Smith 	if (val < 0)
72324ae3961SKevin Smith 		return val;
72424ae3961SKevin Smith 
72524ae3961SKevin Smith 	/* Allow CPU to route to any port */
72624ae3961SKevin Smith 	val = PORT_MASK & ~(1 << CONFIG_MV88E61XX_CPU_PORT);
72724ae3961SKevin Smith 	val = mv88e61xx_port_set_vlan(phydev, CONFIG_MV88E61XX_CPU_PORT, val);
72824ae3961SKevin Smith 	if (val < 0)
72924ae3961SKevin Smith 		return val;
73024ae3961SKevin Smith 
73124ae3961SKevin Smith 	/* Enable CPU port */
73224ae3961SKevin Smith 	val = mv88e61xx_port_enable(phydev, CONFIG_MV88E61XX_CPU_PORT);
73324ae3961SKevin Smith 	if (val < 0)
73424ae3961SKevin Smith 		return val;
73524ae3961SKevin Smith 
73624ae3961SKevin Smith 	val = mv88e61xx_read_port_config(phydev, CONFIG_MV88E61XX_CPU_PORT);
73724ae3961SKevin Smith 	if (val < 0)
73824ae3961SKevin Smith 		return val;
73924ae3961SKevin Smith 
74024ae3961SKevin Smith 	/* If CPU is connected to serdes, initialize serdes */
74124ae3961SKevin Smith 	if (mv88e61xx_6352_family(phydev)) {
74224ae3961SKevin Smith 		val = mv88e61xx_get_cmode(phydev, CONFIG_MV88E61XX_CPU_PORT);
74324ae3961SKevin Smith 		if (val < 0)
74424ae3961SKevin Smith 			return val;
74524ae3961SKevin Smith 		if (val == PORT_REG_STATUS_CMODE_100BASE_X ||
74624ae3961SKevin Smith 		    val == PORT_REG_STATUS_CMODE_1000BASE_X ||
74724ae3961SKevin Smith 		    val == PORT_REG_STATUS_CMODE_SGMII) {
74824ae3961SKevin Smith 			val = mv88e61xx_serdes_init(phydev);
74924ae3961SKevin Smith 			if (val < 0)
75024ae3961SKevin Smith 				return val;
75124ae3961SKevin Smith 		}
75224ae3961SKevin Smith 	}
75324ae3961SKevin Smith 
75424ae3961SKevin Smith 	return 0;
75524ae3961SKevin Smith }
75624ae3961SKevin Smith 
mv88e61xx_switch_init(struct phy_device * phydev)75724ae3961SKevin Smith static int mv88e61xx_switch_init(struct phy_device *phydev)
75824ae3961SKevin Smith {
75924ae3961SKevin Smith 	static int init;
76024ae3961SKevin Smith 	int res;
76124ae3961SKevin Smith 
76224ae3961SKevin Smith 	if (init)
76324ae3961SKevin Smith 		return 0;
76424ae3961SKevin Smith 
76524ae3961SKevin Smith 	res = mv88e61xx_switch_reset(phydev);
76624ae3961SKevin Smith 	if (res < 0)
76724ae3961SKevin Smith 		return res;
76824ae3961SKevin Smith 
76924ae3961SKevin Smith 	res = mv88e61xx_set_cpu_port(phydev);
77024ae3961SKevin Smith 	if (res < 0)
77124ae3961SKevin Smith 		return res;
77224ae3961SKevin Smith 
77324ae3961SKevin Smith 	init = 1;
77424ae3961SKevin Smith 
77524ae3961SKevin Smith 	return 0;
77624ae3961SKevin Smith }
77724ae3961SKevin Smith 
mv88e61xx_phy_enable(struct phy_device * phydev,u8 phy)77824ae3961SKevin Smith static int mv88e61xx_phy_enable(struct phy_device *phydev, u8 phy)
77924ae3961SKevin Smith {
78024ae3961SKevin Smith 	int val;
78124ae3961SKevin Smith 
78224ae3961SKevin Smith 	val = mv88e61xx_phy_read(phydev, phy, MII_BMCR);
78324ae3961SKevin Smith 	if (val < 0)
78424ae3961SKevin Smith 		return val;
78524ae3961SKevin Smith 	val &= ~(BMCR_PDOWN);
78624ae3961SKevin Smith 	val = mv88e61xx_phy_write(phydev, phy, MII_BMCR, val);
78724ae3961SKevin Smith 	if (val < 0)
78824ae3961SKevin Smith 		return val;
78924ae3961SKevin Smith 
79024ae3961SKevin Smith 	return 0;
79124ae3961SKevin Smith }
79224ae3961SKevin Smith 
mv88e61xx_phy_setup(struct phy_device * phydev,u8 phy)79324ae3961SKevin Smith static int mv88e61xx_phy_setup(struct phy_device *phydev, u8 phy)
79424ae3961SKevin Smith {
79524ae3961SKevin Smith 	int val;
79624ae3961SKevin Smith 
79724ae3961SKevin Smith 	/*
79824ae3961SKevin Smith 	 * Enable energy-detect sensing on PHY, used to determine when a PHY
79924ae3961SKevin Smith 	 * port is physically connected
80024ae3961SKevin Smith 	 */
80124ae3961SKevin Smith 	val = mv88e61xx_phy_read(phydev, phy, PHY_REG_CTRL1);
80224ae3961SKevin Smith 	if (val < 0)
80324ae3961SKevin Smith 		return val;
80424ae3961SKevin Smith 	val = bitfield_replace(val, PHY_REG_CTRL1_ENERGY_DET_SHIFT,
80524ae3961SKevin Smith 			       PHY_REG_CTRL1_ENERGY_DET_WIDTH,
80624ae3961SKevin Smith 			       PHY_REG_CTRL1_ENERGY_DET_SENSE_XMIT);
80724ae3961SKevin Smith 	val = mv88e61xx_phy_write(phydev, phy, PHY_REG_CTRL1, val);
80824ae3961SKevin Smith 	if (val < 0)
80924ae3961SKevin Smith 		return val;
81024ae3961SKevin Smith 
81124ae3961SKevin Smith 	return 0;
81224ae3961SKevin Smith }
81324ae3961SKevin Smith 
mv88e61xx_fixed_port_setup(struct phy_device * phydev,u8 port)814b755abecSChris Packham static int mv88e61xx_fixed_port_setup(struct phy_device *phydev, u8 port)
815b755abecSChris Packham {
816b755abecSChris Packham 	int val;
817b755abecSChris Packham 
818b755abecSChris Packham 	val = mv88e61xx_port_read(phydev, port, PORT_REG_PHYS_CTRL);
819b755abecSChris Packham 	if (val < 0)
820b755abecSChris Packham 		return val;
821b755abecSChris Packham 
822b755abecSChris Packham 	val &= ~(PORT_REG_PHYS_CTRL_SPD_MASK |
823b755abecSChris Packham 		 PORT_REG_PHYS_CTRL_FC_VALUE);
824b755abecSChris Packham 	val |= PORT_REG_PHYS_CTRL_PCS_AN_EN |
825b755abecSChris Packham 	       PORT_REG_PHYS_CTRL_PCS_AN_RST |
826b755abecSChris Packham 	       PORT_REG_PHYS_CTRL_FC_FORCE |
827b755abecSChris Packham 	       PORT_REG_PHYS_CTRL_DUPLEX_VALUE |
828b755abecSChris Packham 	       PORT_REG_PHYS_CTRL_DUPLEX_FORCE |
829b755abecSChris Packham 	       PORT_REG_PHYS_CTRL_SPD1000;
830b755abecSChris Packham 
831b755abecSChris Packham 	return mv88e61xx_port_write(phydev, port, PORT_REG_PHYS_CTRL,
832b755abecSChris Packham 				   val);
833b755abecSChris Packham }
834b755abecSChris Packham 
mv88e61xx_phy_config_port(struct phy_device * phydev,u8 phy)83524ae3961SKevin Smith static int mv88e61xx_phy_config_port(struct phy_device *phydev, u8 phy)
83624ae3961SKevin Smith {
83724ae3961SKevin Smith 	int val;
83824ae3961SKevin Smith 
83924ae3961SKevin Smith 	val = mv88e61xx_port_enable(phydev, phy);
84024ae3961SKevin Smith 	if (val < 0)
84124ae3961SKevin Smith 		return val;
84224ae3961SKevin Smith 
84324ae3961SKevin Smith 	val = mv88e61xx_port_set_vlan(phydev, phy,
84424ae3961SKevin Smith 			1 << CONFIG_MV88E61XX_CPU_PORT);
84524ae3961SKevin Smith 	if (val < 0)
84624ae3961SKevin Smith 		return val;
84724ae3961SKevin Smith 
84824ae3961SKevin Smith 	return 0;
84924ae3961SKevin Smith }
85024ae3961SKevin Smith 
mv88e61xx_probe(struct phy_device * phydev)85124ae3961SKevin Smith static int mv88e61xx_probe(struct phy_device *phydev)
85224ae3961SKevin Smith {
85324ae3961SKevin Smith 	struct mii_dev *smi_wrapper;
85424ae3961SKevin Smith 	struct mv88e61xx_phy_priv *priv;
85524ae3961SKevin Smith 	int res;
85624ae3961SKevin Smith 
85724ae3961SKevin Smith 	res = mv88e61xx_hw_reset(phydev);
85824ae3961SKevin Smith 	if (res < 0)
85924ae3961SKevin Smith 		return res;
86024ae3961SKevin Smith 
86124ae3961SKevin Smith 	priv = malloc(sizeof(*priv));
86224ae3961SKevin Smith 	if (!priv)
86324ae3961SKevin Smith 		return -ENOMEM;
86424ae3961SKevin Smith 
86524ae3961SKevin Smith 	memset(priv, 0, sizeof(*priv));
86624ae3961SKevin Smith 
86724ae3961SKevin Smith 	/*
86824ae3961SKevin Smith 	 * This device requires indirect reads/writes to the PHY registers
86924ae3961SKevin Smith 	 * which the generic PHY code can't handle.  Make a wrapper MII device
87024ae3961SKevin Smith 	 * to handle reads/writes
87124ae3961SKevin Smith 	 */
87224ae3961SKevin Smith 	smi_wrapper = mdio_alloc();
87324ae3961SKevin Smith 	if (!smi_wrapper) {
87424ae3961SKevin Smith 		free(priv);
87524ae3961SKevin Smith 		return -ENOMEM;
87624ae3961SKevin Smith 	}
87724ae3961SKevin Smith 
87824ae3961SKevin Smith 	/*
87924ae3961SKevin Smith 	 * Store the mdio bus in the private data, as we are going to replace
88024ae3961SKevin Smith 	 * the bus with the wrapper bus
88124ae3961SKevin Smith 	 */
88224ae3961SKevin Smith 	priv->mdio_bus = phydev->bus;
88324ae3961SKevin Smith 
88424ae3961SKevin Smith 	/*
88524ae3961SKevin Smith 	 * Store the smi bus address in private data.  This lets us use the
88624ae3961SKevin Smith 	 * phydev addr field for device address instead, as the genphy code
88724ae3961SKevin Smith 	 * expects.
88824ae3961SKevin Smith 	 */
88924ae3961SKevin Smith 	priv->smi_addr = phydev->addr;
89024ae3961SKevin Smith 
89124ae3961SKevin Smith 	/*
89224ae3961SKevin Smith 	 * Store the phy_device in the wrapper mii device. This lets us get it
89324ae3961SKevin Smith 	 * back when genphy functions call phy_read/phy_write.
89424ae3961SKevin Smith 	 */
89524ae3961SKevin Smith 	smi_wrapper->priv = phydev;
89624ae3961SKevin Smith 	strncpy(smi_wrapper->name, "indirect mii", sizeof(smi_wrapper->name));
89724ae3961SKevin Smith 	smi_wrapper->read = mv88e61xx_phy_read_indirect;
89824ae3961SKevin Smith 	smi_wrapper->write = mv88e61xx_phy_write_indirect;
89924ae3961SKevin Smith 
90024ae3961SKevin Smith 	/* Replace the bus with the wrapper device */
90124ae3961SKevin Smith 	phydev->bus = smi_wrapper;
90224ae3961SKevin Smith 
90324ae3961SKevin Smith 	phydev->priv = priv;
90424ae3961SKevin Smith 
90524ae3961SKevin Smith 	priv->id = mv88e61xx_get_switch_id(phydev);
90624ae3961SKevin Smith 
90724ae3961SKevin Smith 	return 0;
90824ae3961SKevin Smith }
90924ae3961SKevin Smith 
mv88e61xx_phy_config(struct phy_device * phydev)91024ae3961SKevin Smith static int mv88e61xx_phy_config(struct phy_device *phydev)
91124ae3961SKevin Smith {
91224ae3961SKevin Smith 	int res;
91324ae3961SKevin Smith 	int i;
91424ae3961SKevin Smith 	int ret = -1;
91524ae3961SKevin Smith 
91624ae3961SKevin Smith 	res = mv88e61xx_switch_init(phydev);
91724ae3961SKevin Smith 	if (res < 0)
91824ae3961SKevin Smith 		return res;
91924ae3961SKevin Smith 
92024ae3961SKevin Smith 	for (i = 0; i < PORT_COUNT; i++) {
92124ae3961SKevin Smith 		if ((1 << i) & CONFIG_MV88E61XX_PHY_PORTS) {
92224ae3961SKevin Smith 			phydev->addr = i;
92324ae3961SKevin Smith 
92424ae3961SKevin Smith 			res = mv88e61xx_phy_enable(phydev, i);
92524ae3961SKevin Smith 			if (res < 0) {
92624ae3961SKevin Smith 				printf("Error enabling PHY %i\n", i);
92724ae3961SKevin Smith 				continue;
92824ae3961SKevin Smith 			}
92924ae3961SKevin Smith 			res = mv88e61xx_phy_setup(phydev, i);
93024ae3961SKevin Smith 			if (res < 0) {
93124ae3961SKevin Smith 				printf("Error setting up PHY %i\n", i);
93224ae3961SKevin Smith 				continue;
93324ae3961SKevin Smith 			}
93424ae3961SKevin Smith 			res = mv88e61xx_phy_config_port(phydev, i);
93524ae3961SKevin Smith 			if (res < 0) {
93624ae3961SKevin Smith 				printf("Error configuring PHY %i\n", i);
93724ae3961SKevin Smith 				continue;
93824ae3961SKevin Smith 			}
93924ae3961SKevin Smith 
94024ae3961SKevin Smith 			res = genphy_config_aneg(phydev);
94124ae3961SKevin Smith 			if (res < 0) {
94224ae3961SKevin Smith 				printf("Error setting PHY %i autoneg\n", i);
94324ae3961SKevin Smith 				continue;
94424ae3961SKevin Smith 			}
94524ae3961SKevin Smith 			res = phy_reset(phydev);
94624ae3961SKevin Smith 			if (res < 0) {
94724ae3961SKevin Smith 				printf("Error resetting PHY %i\n", i);
94824ae3961SKevin Smith 				continue;
94924ae3961SKevin Smith 			}
95024ae3961SKevin Smith 
95124ae3961SKevin Smith 			/* Return success if any PHY succeeds */
95224ae3961SKevin Smith 			ret = 0;
953b755abecSChris Packham 		} else if ((1 << i) & CONFIG_MV88E61XX_FIXED_PORTS) {
954b755abecSChris Packham 			res = mv88e61xx_fixed_port_setup(phydev, i);
955b755abecSChris Packham 			if (res < 0) {
956b755abecSChris Packham 				printf("Error configuring port %i\n", i);
957b755abecSChris Packham 				continue;
958b755abecSChris Packham 			}
95924ae3961SKevin Smith 		}
96024ae3961SKevin Smith 	}
96124ae3961SKevin Smith 
96224ae3961SKevin Smith 	return ret;
96324ae3961SKevin Smith }
96424ae3961SKevin Smith 
mv88e61xx_phy_is_connected(struct phy_device * phydev)96524ae3961SKevin Smith static int mv88e61xx_phy_is_connected(struct phy_device *phydev)
96624ae3961SKevin Smith {
96724ae3961SKevin Smith 	int val;
96824ae3961SKevin Smith 
96924ae3961SKevin Smith 	val = mv88e61xx_phy_read(phydev, phydev->addr, PHY_REG_STATUS1);
97024ae3961SKevin Smith 	if (val < 0)
97124ae3961SKevin Smith 		return 0;
97224ae3961SKevin Smith 
97324ae3961SKevin Smith 	/*
97424ae3961SKevin Smith 	 * After reset, the energy detect signal remains high for a few seconds
97524ae3961SKevin Smith 	 * regardless of whether a cable is connected.  This function will
97624ae3961SKevin Smith 	 * return false positives during this time.
97724ae3961SKevin Smith 	 */
97824ae3961SKevin Smith 	return (val & PHY_REG_STATUS1_ENERGY) == 0;
97924ae3961SKevin Smith }
98024ae3961SKevin Smith 
mv88e61xx_phy_startup(struct phy_device * phydev)98124ae3961SKevin Smith static int mv88e61xx_phy_startup(struct phy_device *phydev)
98224ae3961SKevin Smith {
98324ae3961SKevin Smith 	int i;
98424ae3961SKevin Smith 	int link = 0;
98524ae3961SKevin Smith 	int res;
98624ae3961SKevin Smith 	int speed = phydev->speed;
98724ae3961SKevin Smith 	int duplex = phydev->duplex;
98824ae3961SKevin Smith 
98924ae3961SKevin Smith 	for (i = 0; i < PORT_COUNT; i++) {
99024ae3961SKevin Smith 		if ((1 << i) & CONFIG_MV88E61XX_PHY_PORTS) {
99124ae3961SKevin Smith 			phydev->addr = i;
99224ae3961SKevin Smith 			if (!mv88e61xx_phy_is_connected(phydev))
99324ae3961SKevin Smith 				continue;
99424ae3961SKevin Smith 			res = genphy_update_link(phydev);
99524ae3961SKevin Smith 			if (res < 0)
99624ae3961SKevin Smith 				continue;
99724ae3961SKevin Smith 			res = mv88e61xx_parse_status(phydev);
99824ae3961SKevin Smith 			if (res < 0)
99924ae3961SKevin Smith 				continue;
100024ae3961SKevin Smith 			link = (link || phydev->link);
100124ae3961SKevin Smith 		}
100224ae3961SKevin Smith 	}
100324ae3961SKevin Smith 	phydev->link = link;
100424ae3961SKevin Smith 
100524ae3961SKevin Smith 	/* Restore CPU interface speed and duplex after it was changed for
100624ae3961SKevin Smith 	 * other ports */
100724ae3961SKevin Smith 	phydev->speed = speed;
100824ae3961SKevin Smith 	phydev->duplex = duplex;
100924ae3961SKevin Smith 
101024ae3961SKevin Smith 	return 0;
101124ae3961SKevin Smith }
101224ae3961SKevin Smith 
101324ae3961SKevin Smith static struct phy_driver mv88e61xx_driver = {
101424ae3961SKevin Smith 	.name = "Marvell MV88E61xx",
101524ae3961SKevin Smith 	.uid = 0x01410eb1,
101624ae3961SKevin Smith 	.mask = 0xfffffff0,
101724ae3961SKevin Smith 	.features = PHY_GBIT_FEATURES,
101824ae3961SKevin Smith 	.probe = mv88e61xx_probe,
101924ae3961SKevin Smith 	.config = mv88e61xx_phy_config,
102024ae3961SKevin Smith 	.startup = mv88e61xx_phy_startup,
102124ae3961SKevin Smith 	.shutdown = &genphy_shutdown,
102224ae3961SKevin Smith };
102324ae3961SKevin Smith 
102465d4d00aSChris Packham static struct phy_driver mv88e609x_driver = {
102565d4d00aSChris Packham 	.name = "Marvell MV88E609x",
102665d4d00aSChris Packham 	.uid = 0x1410c89,
102765d4d00aSChris Packham 	.mask = 0xfffffff0,
102865d4d00aSChris Packham 	.features = PHY_GBIT_FEATURES,
102965d4d00aSChris Packham 	.probe = mv88e61xx_probe,
103065d4d00aSChris Packham 	.config = mv88e61xx_phy_config,
103165d4d00aSChris Packham 	.startup = mv88e61xx_phy_startup,
103265d4d00aSChris Packham 	.shutdown = &genphy_shutdown,
103365d4d00aSChris Packham };
103465d4d00aSChris Packham 
phy_mv88e61xx_init(void)103524ae3961SKevin Smith int phy_mv88e61xx_init(void)
103624ae3961SKevin Smith {
103724ae3961SKevin Smith 	phy_register(&mv88e61xx_driver);
103865d4d00aSChris Packham 	phy_register(&mv88e609x_driver);
103924ae3961SKevin Smith 
104024ae3961SKevin Smith 	return 0;
104124ae3961SKevin Smith }
104224ae3961SKevin Smith 
104324ae3961SKevin Smith /*
104424ae3961SKevin Smith  * Overload weak get_phy_id definition since we need non-standard functions
104524ae3961SKevin Smith  * to read PHY registers
104624ae3961SKevin Smith  */
get_phy_id(struct mii_dev * bus,int smi_addr,int devad,u32 * phy_id)104724ae3961SKevin Smith int get_phy_id(struct mii_dev *bus, int smi_addr, int devad, u32 *phy_id)
104824ae3961SKevin Smith {
104924ae3961SKevin Smith 	struct phy_device temp_phy;
105024ae3961SKevin Smith 	struct mv88e61xx_phy_priv temp_priv;
105124ae3961SKevin Smith 	struct mii_dev temp_mii;
105224ae3961SKevin Smith 	int val;
105324ae3961SKevin Smith 
105424ae3961SKevin Smith 	/*
105524ae3961SKevin Smith 	 * Buid temporary data structures that the chip reading code needs to
105624ae3961SKevin Smith 	 * read the ID
105724ae3961SKevin Smith 	 */
105824ae3961SKevin Smith 	temp_priv.mdio_bus = bus;
105924ae3961SKevin Smith 	temp_priv.smi_addr = smi_addr;
106024ae3961SKevin Smith 	temp_phy.priv = &temp_priv;
106124ae3961SKevin Smith 	temp_mii.priv = &temp_phy;
106224ae3961SKevin Smith 
106324ae3961SKevin Smith 	val = mv88e61xx_phy_read_indirect(&temp_mii, 0, devad, MII_PHYSID1);
106424ae3961SKevin Smith 	if (val < 0)
106524ae3961SKevin Smith 		return -EIO;
106624ae3961SKevin Smith 
106724ae3961SKevin Smith 	*phy_id = val << 16;
106824ae3961SKevin Smith 
106924ae3961SKevin Smith 	val = mv88e61xx_phy_read_indirect(&temp_mii, 0, devad, MII_PHYSID2);
107024ae3961SKevin Smith 	if (val < 0)
107124ae3961SKevin Smith 		return -EIO;
107224ae3961SKevin Smith 
107324ae3961SKevin Smith 	*phy_id |= (val & 0xffff);
107424ae3961SKevin Smith 
107524ae3961SKevin Smith 	return 0;
107624ae3961SKevin Smith }
1077