forked from mirrors/linux
		
	net: phy: consolidate PHY reset in phy_init_hw()
There are quite a lot of drivers touching a PHY device MII_BMCR register to reset the PHY without taking care of: 1) ensuring that BMCR_RESET is cleared after a given timeout 2) the PHY state machine resuming to the proper state and re-applying potentially changed settings such as auto-negotiation Introduce phy_poll_reset() which will take care of polling the MII_BMCR for the BMCR_RESET bit to be cleared after a given timeout or return a timeout error code. In order to make sure the PHY is in a correct state, phy_init_hw() first issues a software reset through MII_BMCR and then applies any fixups. Signed-off-by: Florian Fainelli <f.fainelli@gmail.com> Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
		
							parent
							
								
									06d87cec73
								
							
						
					
					
						commit
						87aa9f9c61
					
				
					 3 changed files with 60 additions and 4 deletions
				
			
		|  | @ -255,7 +255,8 @@ Writing a PHY driver | ||||||
| 
 | 
 | ||||||
|    config_init: configures PHY into a sane state after a reset. |    config_init: configures PHY into a sane state after a reset. | ||||||
|      For instance, a Davicom PHY requires descrambling disabled. |      For instance, a Davicom PHY requires descrambling disabled. | ||||||
|    probe: Does any setup needed by the driver |    probe: Allocate phy->priv, optionally refuse to bind. | ||||||
|  |    PHY may not have been reset or had fixups run yet. | ||||||
|    suspend/resume: power management |    suspend/resume: power management | ||||||
|    config_aneg: Changes the speed/duplex/negotiation settings |    config_aneg: Changes the speed/duplex/negotiation settings | ||||||
|    read_status: Reads the current speed/duplex/negotiation settings |    read_status: Reads the current speed/duplex/negotiation settings | ||||||
|  |  | ||||||
|  | @ -318,6 +318,7 @@ int phy_mii_ioctl(struct phy_device *phydev, | ||||||
| { | { | ||||||
| 	struct mii_ioctl_data *mii_data = if_mii(ifr); | 	struct mii_ioctl_data *mii_data = if_mii(ifr); | ||||||
| 	u16 val = mii_data->val_in; | 	u16 val = mii_data->val_in; | ||||||
|  | 	int ret = 0; | ||||||
| 
 | 
 | ||||||
| 	switch (cmd) { | 	switch (cmd) { | ||||||
| 	case SIOCGMIIPHY: | 	case SIOCGMIIPHY: | ||||||
|  | @ -362,7 +363,7 @@ int phy_mii_ioctl(struct phy_device *phydev, | ||||||
| 
 | 
 | ||||||
| 		if (mii_data->reg_num == MII_BMCR && | 		if (mii_data->reg_num == MII_BMCR && | ||||||
| 		    val & BMCR_RESET) | 		    val & BMCR_RESET) | ||||||
| 			phy_init_hw(phydev); | 			ret = phy_init_hw(phydev); | ||||||
| 		break; | 		break; | ||||||
| 
 | 
 | ||||||
| 	case SIOCSHWTSTAMP: | 	case SIOCSHWTSTAMP: | ||||||
|  | @ -374,7 +375,7 @@ int phy_mii_ioctl(struct phy_device *phydev, | ||||||
| 		return -EOPNOTSUPP; | 		return -EOPNOTSUPP; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return 0; | 	return ret; | ||||||
| } | } | ||||||
| EXPORT_SYMBOL(phy_mii_ioctl); | EXPORT_SYMBOL(phy_mii_ioctl); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -364,7 +364,11 @@ int phy_device_register(struct phy_device *phydev) | ||||||
| 	phydev->bus->phy_map[phydev->addr] = phydev; | 	phydev->bus->phy_map[phydev->addr] = phydev; | ||||||
| 
 | 
 | ||||||
| 	/* Run all of the fixups for this PHY */ | 	/* Run all of the fixups for this PHY */ | ||||||
| 	phy_scan_fixups(phydev); | 	err = phy_init_hw(phydev); | ||||||
|  | 	if (err) { | ||||||
|  | 		pr_err("PHY %d failed to initialize\n", phydev->addr); | ||||||
|  | 		goto out; | ||||||
|  | 	} | ||||||
| 
 | 
 | ||||||
| 	err = device_add(&phydev->dev); | 	err = device_add(&phydev->dev); | ||||||
| 	if (err) { | 	if (err) { | ||||||
|  | @ -497,6 +501,47 @@ void phy_disconnect(struct phy_device *phydev) | ||||||
| } | } | ||||||
| EXPORT_SYMBOL(phy_disconnect); | EXPORT_SYMBOL(phy_disconnect); | ||||||
| 
 | 
 | ||||||
|  | /**
 | ||||||
|  |  * phy_poll_reset - Safely wait until a PHY reset has properly completed | ||||||
|  |  * @phydev: The PHY device to poll | ||||||
|  |  * | ||||||
|  |  * Description: According to IEEE 802.3, Section 2, Subsection 22.2.4.1.1, as | ||||||
|  |  *   published in 2008, a PHY reset may take up to 0.5 seconds.  The MII BMCR | ||||||
|  |  *   register must be polled until the BMCR_RESET bit clears. | ||||||
|  |  * | ||||||
|  |  *   Furthermore, any attempts to write to PHY registers may have no effect | ||||||
|  |  *   or even generate MDIO bus errors until this is complete. | ||||||
|  |  * | ||||||
|  |  *   Some PHYs (such as the Marvell 88E1111) don't entirely conform to the | ||||||
|  |  *   standard and do not fully reset after the BMCR_RESET bit is set, and may | ||||||
|  |  *   even *REQUIRE* a soft-reset to properly restart autonegotiation.  In an | ||||||
|  |  *   effort to support such broken PHYs, this function is separate from the | ||||||
|  |  *   standard phy_init_hw() which will zero all the other bits in the BMCR | ||||||
|  |  *   and reapply all driver-specific and board-specific fixups. | ||||||
|  |  */ | ||||||
|  | static int phy_poll_reset(struct phy_device *phydev) | ||||||
|  | { | ||||||
|  | 	/* Poll until the reset bit clears (50ms per retry == 0.6 sec) */ | ||||||
|  | 	unsigned int retries = 12; | ||||||
|  | 	int ret; | ||||||
|  | 
 | ||||||
|  | 	do { | ||||||
|  | 		msleep(50); | ||||||
|  | 		ret = phy_read(phydev, MII_BMCR); | ||||||
|  | 		if (ret < 0) | ||||||
|  | 			return ret; | ||||||
|  | 	} while (ret & BMCR_RESET && --retries); | ||||||
|  | 	if (ret & BMCR_RESET) | ||||||
|  | 		return -ETIMEDOUT; | ||||||
|  | 
 | ||||||
|  | 	/*
 | ||||||
|  | 	 * Some chips (smsc911x) may still need up to another 1ms after the | ||||||
|  | 	 * BMCR_RESET bit is cleared before they are usable. | ||||||
|  | 	 */ | ||||||
|  | 	msleep(1); | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| int phy_init_hw(struct phy_device *phydev) | int phy_init_hw(struct phy_device *phydev) | ||||||
| { | { | ||||||
| 	int ret; | 	int ret; | ||||||
|  | @ -504,12 +549,21 @@ int phy_init_hw(struct phy_device *phydev) | ||||||
| 	if (!phydev->drv || !phydev->drv->config_init) | 	if (!phydev->drv || !phydev->drv->config_init) | ||||||
| 		return 0; | 		return 0; | ||||||
| 
 | 
 | ||||||
|  | 	ret = phy_write(phydev, MII_BMCR, BMCR_RESET); | ||||||
|  | 	if (ret < 0) | ||||||
|  | 		return ret; | ||||||
|  | 
 | ||||||
|  | 	ret = phy_poll_reset(phydev); | ||||||
|  | 	if (ret < 0) | ||||||
|  | 		return ret; | ||||||
|  | 
 | ||||||
| 	ret = phy_scan_fixups(phydev); | 	ret = phy_scan_fixups(phydev); | ||||||
| 	if (ret < 0) | 	if (ret < 0) | ||||||
| 		return ret; | 		return ret; | ||||||
| 
 | 
 | ||||||
| 	return phydev->drv->config_init(phydev); | 	return phydev->drv->config_init(phydev); | ||||||
| } | } | ||||||
|  | EXPORT_SYMBOL(phy_init_hw); | ||||||
| 
 | 
 | ||||||
| /**
 | /**
 | ||||||
|  * phy_attach_direct - attach a network device to a given PHY device pointer |  * phy_attach_direct - attach a network device to a given PHY device pointer | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue
	
	 Florian Fainelli
						Florian Fainelli