net: phy: broadcom: Add IDDQ-SR mode
authorFlorian Fainelli <f.fainelli@gmail.com>
Mon, 20 Sep 2021 21:54:14 +0000 (14:54 -0700)
committerDom Cobley <popcornmix@gmail.com>
Fri, 24 Jun 2022 10:20:43 +0000 (11:20 +0100)
Add support for putting the PHY into IDDQ Soft Recovery mode by setting
the TOP_MISC register bits accordingly. This requires us to implement a
custom bcm54xx_suspend() routine which diverges from genphy_suspend() in
order to configure the PHY to enter IDDQ with software recovery as well
as avoid doing a read/modify/write on the BMCR register.

Doing a read/modify/write on the BMCR register means that the
auto-negotation bit may remain which interferes with the ability to put
the PHY into IDDQ-SR mode. We do software reset upon suspend in order to
put the PHY back into its state prior to suspend as recommended by the
datasheet.

Signed-off-by: Florian Fainelli <f.fainelli@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/phy/broadcom.c
include/linux/brcmphy.h

index d39a360..1bff74e 100644 (file)
@@ -405,10 +405,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
         */
@@ -421,6 +461,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);
 }
 
@@ -805,6 +854,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_BCM54213PE,
        .phy_id_mask    = 0xffffffff,
index 69510a7..2585e80 100644 (file)
@@ -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 */
 #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
  */