Aquantia PHY's of the AQR107 family support the downshift feature.
Add support for it as standard PHY tunable so that it can be controlled
via ethtool.
The AQCS109 supports a proprietary 2-pair 1Gbps mode. If two such PHY's
are connected to each other with a 2-pair cable, they may not be able
to establish a link if both advertise modes >= 1Gbps. Therefore enable
downshift per default on this model.

Signed-off-by: Heiner Kallweit <hkallwe...@gmail.com>
---
 drivers/net/phy/aquantia_main.c | 73 ++++++++++++++++++++++++++++++++-
 1 file changed, 72 insertions(+), 1 deletion(-)

diff --git a/drivers/net/phy/aquantia_main.c b/drivers/net/phy/aquantia_main.c
index 034b82d41..86000c044 100644
--- a/drivers/net/phy/aquantia_main.c
+++ b/drivers/net/phy/aquantia_main.c
@@ -33,6 +33,9 @@
 #define MDIO_AN_VEND_PROV                      0xc400
 #define MDIO_AN_VEND_PROV_1000BASET_FULL       BIT(15)
 #define MDIO_AN_VEND_PROV_1000BASET_HALF       BIT(14)
+#define MDIO_AN_VEND_PROV_DOWNSHIFT_EN         BIT(4)
+#define MDIO_AN_VEND_PROV_DOWNSHIFT_MASK       GENMASK(3, 0)
+#define MDIO_AN_VEND_PROV_DOWNSHIFT_DFLT       4
 
 #define MDIO_AN_TX_VEND_STATUS1                        0xc800
 #define MDIO_AN_TX_VEND_STATUS1_10BASET                (0x0 << 1)
@@ -220,6 +223,61 @@ static int aqr107_read_status(struct phy_device *phydev)
        return 0;
 }
 
+static int aqr107_get_downshift(struct phy_device *phydev, u8 *data)
+{
+       int val, cnt, enable;
+
+       val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_VEND_PROV);
+       if (val < 0)
+               return val;
+
+       enable = FIELD_GET(MDIO_AN_VEND_PROV_DOWNSHIFT_EN, val);
+       cnt = FIELD_GET(MDIO_AN_VEND_PROV_DOWNSHIFT_MASK, val);
+
+       *data = enable && cnt ? cnt : DOWNSHIFT_DEV_DISABLE;
+
+       return 0;
+}
+
+static int aqr107_set_downshift(struct phy_device *phydev, u8 cnt)
+{
+       int val = 0;
+
+       if (!FIELD_FIT(MDIO_AN_VEND_PROV_DOWNSHIFT_MASK, cnt))
+               return -E2BIG;
+
+       if (cnt != DOWNSHIFT_DEV_DISABLE) {
+               val = MDIO_AN_VEND_PROV_DOWNSHIFT_EN;
+               val |= FIELD_PREP(MDIO_AN_VEND_PROV_DOWNSHIFT_MASK, cnt);
+       }
+
+       return phy_modify_mmd(phydev, MDIO_MMD_AN, MDIO_AN_VEND_PROV,
+                             MDIO_AN_VEND_PROV_DOWNSHIFT_EN |
+                             MDIO_AN_VEND_PROV_DOWNSHIFT_MASK, val);
+}
+
+static int aqr107_get_tunable(struct phy_device *phydev,
+                             struct ethtool_tunable *tuna, void *data)
+{
+       switch (tuna->id) {
+       case ETHTOOL_PHY_DOWNSHIFT:
+               return aqr107_get_downshift(phydev, data);
+       default:
+               return -EOPNOTSUPP;
+       }
+}
+
+static int aqr107_set_tunable(struct phy_device *phydev,
+                             struct ethtool_tunable *tuna, const void *data)
+{
+       switch (tuna->id) {
+       case ETHTOOL_PHY_DOWNSHIFT:
+               return aqr107_set_downshift(phydev, *(const u8 *)data);
+       default:
+               return -EOPNOTSUPP;
+       }
+}
+
 static int aqr107_config_init(struct phy_device *phydev)
 {
        /* Check that the PHY interface type is compatible */
@@ -233,6 +291,8 @@ static int aqr107_config_init(struct phy_device *phydev)
 
 static int aqcs109_config_init(struct phy_device *phydev)
 {
+       int ret;
+
        /* Check that the PHY interface type is compatible */
        if (phydev->interface != PHY_INTERFACE_MODE_SGMII &&
            phydev->interface != PHY_INTERFACE_MODE_2500BASEX)
@@ -242,7 +302,14 @@ static int aqcs109_config_init(struct phy_device *phydev)
         * PMA speed ability bits are the same for all members of the family,
         * AQCS109 however supports speeds up to 2.5G only.
         */
-       return phy_set_max_speed(phydev, SPEED_2500);
+       ret = phy_set_max_speed(phydev, SPEED_2500);
+       if (ret)
+               return ret;
+
+       /* AQCS109 supports a proprietary 2-pair 1Gbps mode, therefore enable
+        * downshift per default.
+        */
+       return aqr107_set_downshift(phydev, MDIO_AN_VEND_PROV_DOWNSHIFT_DFLT);
 }
 
 static struct phy_driver aqr_driver[] = {
@@ -285,6 +352,8 @@ static struct phy_driver aqr_driver[] = {
        .config_intr    = aqr_config_intr,
        .ack_interrupt  = aqr_ack_interrupt,
        .read_status    = aqr_read_status,
+       .get_tunable    = aqr107_get_tunable,
+       .set_tunable    = aqr107_set_tunable,
 },
 {
        PHY_ID_MATCH_MODEL(PHY_ID_AQR107),
@@ -319,6 +388,8 @@ static struct phy_driver aqr_driver[] = {
        .config_intr    = aqr_config_intr,
        .ack_interrupt  = aqr_ack_interrupt,
        .read_status    = aqr_read_status,
+       .get_tunable    = aqr107_get_tunable,
+       .set_tunable    = aqr107_set_tunable,
 },
 };
 
-- 
2.21.0

Reply via email to