This driver becomes the first user of the kernel's `ETHTOOL_PHY_EDPD`
phy-tunable feature.
EDPD is also enabled by default on PHY config_init, but can be disabled via
the phy-tunable control.

When enabling EDPD, it's also a good idea (for the ADIN PHYs) to enable TX
periodic pulses, so that in case the other PHY is also on EDPD mode, there
is no lock-up situation where both sides are waiting for the other to
transmit.

Via the phy-tunable control, TX pulses can be disabled if specifying 0
`tx-interval` via ethtool.

The ADIN PHY supports only fixed 1 second intervals; they cannot be
configured. That is why the acceptable values are 1,
ETHTOOL_PHY_EDPD_DFLT_TX_INTERVAL and ETHTOOL_PHY_EDPD_NO_TX (which
disables TX pulses).

Signed-off-by: Alexandru Ardelean <alexandru.ardel...@analog.com>
---
 drivers/net/phy/adin.c | 61 ++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 61 insertions(+)

diff --git a/drivers/net/phy/adin.c b/drivers/net/phy/adin.c
index 4dec83df048d..5f5f73a58122 100644
--- a/drivers/net/phy/adin.c
+++ b/drivers/net/phy/adin.c
@@ -26,6 +26,11 @@
 
 #define ADIN1300_RX_ERR_CNT                    0x0014
 
+#define ADIN1300_PHY_CTRL_STATUS2              0x0015
+#define   ADIN1300_NRG_PD_EN                   BIT(3)
+#define   ADIN1300_NRG_PD_TX_EN                        BIT(2)
+#define   ADIN1300_NRG_PD_STATUS               BIT(1)
+
 #define ADIN1300_PHY_CTRL2                     0x0016
 #define   ADIN1300_DOWNSPEED_AN_100_EN         BIT(11)
 #define   ADIN1300_DOWNSPEED_AN_10_EN          BIT(10)
@@ -328,12 +333,62 @@ static int adin_set_downshift(struct phy_device *phydev, 
u8 cnt)
                            ADIN1300_DOWNSPEEDS_EN);
 }
 
+static int adin_get_edpd(struct phy_device *phydev, u16 *tx_interval)
+{
+       int val;
+
+       val = phy_read(phydev, ADIN1300_PHY_CTRL_STATUS2);
+       if (val < 0)
+               return val;
+
+       if (ADIN1300_NRG_PD_EN & val) {
+               if (val & ADIN1300_NRG_PD_TX_EN)
+                       /* default is 1 second */
+                       *tx_interval = ETHTOOL_PHY_EDPD_DFLT_TX_INTERVAL;
+               else
+                       *tx_interval = ETHTOOL_PHY_EDPD_NO_TX;
+       } else {
+               *tx_interval = ETHTOOL_PHY_EDPD_DISABLE;
+       }
+
+       return 0;
+}
+
+static int adin_set_edpd(struct phy_device *phydev, u16 tx_interval)
+{
+       u16 val;
+
+       if (tx_interval == ETHTOOL_PHY_EDPD_DISABLE)
+               return phy_clear_bits(phydev, ADIN1300_PHY_CTRL_STATUS2,
+                               (ADIN1300_NRG_PD_EN | ADIN1300_NRG_PD_TX_EN));
+
+       val = ADIN1300_NRG_PD_EN;
+
+       switch (tx_interval) {
+       case 1: /* second */
+               /* fallthrough */
+       case ETHTOOL_PHY_EDPD_DFLT_TX_INTERVAL:
+               val |= ADIN1300_NRG_PD_TX_EN;
+               /* fallthrough */
+       case ETHTOOL_PHY_EDPD_NO_TX:
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       return phy_modify(phydev, ADIN1300_PHY_CTRL_STATUS2,
+                         (ADIN1300_NRG_PD_EN | ADIN1300_NRG_PD_TX_EN),
+                         val);
+}
+
 static int adin_get_tunable(struct phy_device *phydev,
                            struct ethtool_tunable *tuna, void *data)
 {
        switch (tuna->id) {
        case ETHTOOL_PHY_DOWNSHIFT:
                return adin_get_downshift(phydev, data);
+       case ETHTOOL_PHY_EDPD:
+               return adin_get_edpd(phydev, data);
        default:
                return -EOPNOTSUPP;
        }
@@ -345,6 +400,8 @@ static int adin_set_tunable(struct phy_device *phydev,
        switch (tuna->id) {
        case ETHTOOL_PHY_DOWNSHIFT:
                return adin_set_downshift(phydev, *(const u8 *)data);
+       case ETHTOOL_PHY_EDPD:
+               return adin_set_edpd(phydev, *(const u16 *)data);
        default:
                return -EOPNOTSUPP;
        }
@@ -368,6 +425,10 @@ static int adin_config_init(struct phy_device *phydev)
        if (rc < 0)
                return rc;
 
+       rc = adin_set_edpd(phydev, 1);
+       if (rc < 0)
+               return rc;
+
        phydev_dbg(phydev, "PHY is using mode '%s'\n",
                   phy_modes(phydev->interface));
 
-- 
2.20.1

Reply via email to