diff --git a/drivers/net/phy/broadcom.c b/drivers/net/phy/broadcom.c index add0c4e33425..f5868a0dee4b 100644 --- a/drivers/net/phy/broadcom.c +++ b/drivers/net/phy/broadcom.c @@ -392,10 +392,50 @@ static int bcm54xx_config_init(struct phy_device *phydev) return 0; } +static int bcm54xx_iddq_set(struct phy_device *phydev, bool enable) +{ + int ret = 0; + + if (!(phydev->dev_flags & PHY_BRCM_IDDQ_SUSPEND)) + return ret; + + ret = bcm_phy_read_exp(phydev, BCM54XX_TOP_MISC_IDDQ_CTRL); + if (ret < 0) + goto out; + + if (enable) + ret |= BCM54XX_TOP_MISC_IDDQ_SR | BCM54XX_TOP_MISC_IDDQ_LP; + else + ret &= ~(BCM54XX_TOP_MISC_IDDQ_SR | BCM54XX_TOP_MISC_IDDQ_LP); + + ret = bcm_phy_write_exp(phydev, BCM54XX_TOP_MISC_IDDQ_CTRL, ret); +out: + return ret; +} + +static int bcm54xx_suspend(struct phy_device *phydev) +{ + int ret; + + /* We cannot use a read/modify/write here otherwise the PHY gets into + * a bad state where its LEDs keep flashing, thus defeating the purpose + * of low power mode. + */ + ret = phy_write(phydev, MII_BMCR, BMCR_PDOWN); + if (ret < 0) + return ret; + + return bcm54xx_iddq_set(phydev, true); +} + static int bcm54xx_resume(struct phy_device *phydev) { int ret; + ret = bcm54xx_iddq_set(phydev, false); + if (ret < 0) + return ret; + /* Writes to register other than BMCR would be ignored * unless we clear the PDOWN bit first */ @@ -408,6 +448,15 @@ static int bcm54xx_resume(struct phy_device *phydev) */ fsleep(40); + /* Issue a soft reset after clearing the power down bit + * and before doing any other configuration. + */ + if (phydev->dev_flags & PHY_BRCM_IDDQ_SUSPEND) { + ret = genphy_soft_reset(phydev); + if (ret < 0) + return ret; + } + return bcm54xx_config_init(phydev); } @@ -772,6 +821,8 @@ static struct phy_driver broadcom_drivers[] = { .config_intr = bcm_phy_config_intr, .handle_interrupt = bcm_phy_handle_interrupt, .link_change_notify = bcm54xx_link_change_notify, + .suspend = bcm54xx_suspend, + .resume = bcm54xx_resume, }, { .phy_id = PHY_ID_BCM5461, .phy_id_mask = 0xfffffff0, diff --git a/include/linux/brcmphy.h b/include/linux/brcmphy.h index 07e1dfadbbdf..b119d6819d6c 100644 --- a/include/linux/brcmphy.h +++ b/include/linux/brcmphy.h @@ -67,6 +67,7 @@ #define PHY_BRCM_CLEAR_RGMII_MODE 0x00000004 #define PHY_BRCM_DIS_TXCRXC_NOENRGY 0x00000008 #define PHY_BRCM_EN_MASTER_MODE 0x00000010 +#define PHY_BRCM_IDDQ_SUSPEND 0x000000220 /* Broadcom BCM7xxx specific workarounds */ #define PHY_BRCM_7XXX_REV(x) (((x) >> 8) & 0xff) @@ -84,6 +85,7 @@ #define MII_BCM54XX_EXP_DATA 0x15 /* Expansion register data */ #define MII_BCM54XX_EXP_SEL 0x17 /* Expansion register select */ +#define MII_BCM54XX_EXP_SEL_TOP 0x0d00 /* TOP_MISC expansion register select */ #define MII_BCM54XX_EXP_SEL_SSD 0x0e00 /* Secondary SerDes select */ #define MII_BCM54XX_EXP_SEL_ER 0x0f00 /* Expansion register select */ #define MII_BCM54XX_EXP_SEL_ETC 0x0d00 /* Expansion register spare + 2k mem */ @@ -243,6 +245,12 @@ #define MII_BCM54XX_EXP_EXP97 0x0f97 #define MII_BCM54XX_EXP_EXP97_MYST 0x0c0c +/* Top-MISC expansion registers */ +#define BCM54XX_TOP_MISC_IDDQ_CTRL (MII_BCM54XX_EXP_SEL_TOP + 0x06) +#define BCM54XX_TOP_MISC_IDDQ_LP (1 << 0) +#define BCM54XX_TOP_MISC_IDDQ_SD (1 << 2) +#define BCM54XX_TOP_MISC_IDDQ_SR (1 << 3) + /* * BCM5482: Secondary SerDes registers */