OSDN Git Service

net: phy: at803x: add fiber support
authorRobert Hancock <robert.hancock@calian.com>
Tue, 25 Jan 2022 16:54:09 +0000 (10:54 -0600)
committerDavid S. Miller <davem@davemloft.net>
Thu, 27 Jan 2022 13:28:20 +0000 (13:28 +0000)
Previously this driver always forced the copper page to be selected,
however for AR8031 in 100Base-FX or 1000Base-X modes, the fiber page
needs to be selected. Set the appropriate mode based on the hardware
mode_cfg strap selection.

Enable the appropriate interrupt bits to detect fiber-side link up
or down events.

Update config_aneg and read_status methods to use the appropriate
Clause 37 calls when fiber mode is in use.

Signed-off-by: Robert Hancock <robert.hancock@calian.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/phy/at803x.c

index 052b2eb..3f3d4c1 100644 (file)
@@ -51,6 +51,8 @@
 #define AT803X_INTR_ENABLE_PAGE_RECEIVED       BIT(12)
 #define AT803X_INTR_ENABLE_LINK_FAIL           BIT(11)
 #define AT803X_INTR_ENABLE_LINK_SUCCESS                BIT(10)
+#define AT803X_INTR_ENABLE_LINK_FAIL_BX                BIT(8)
+#define AT803X_INTR_ENABLE_LINK_SUCCESS_BX     BIT(7)
 #define AT803X_INTR_ENABLE_WIRESPEED_DOWNGRADE BIT(5)
 #define AT803X_INTR_ENABLE_POLARITY_CHANGED    BIT(1)
 #define AT803X_INTR_ENABLE_WOL                 BIT(0)
 #define AT803X_DEBUG_DATA                      0x1E
 
 #define AT803X_MODE_CFG_MASK                   0x0F
-#define AT803X_MODE_CFG_SGMII                  0x01
+#define AT803X_MODE_CFG_BASET_RGMII            0x00
+#define AT803X_MODE_CFG_BASET_SGMII            0x01
+#define AT803X_MODE_CFG_BX1000_RGMII_50OHM     0x02
+#define AT803X_MODE_CFG_BX1000_RGMII_75OHM     0x03
+#define AT803X_MODE_CFG_BX1000_CONV_50OHM      0x04
+#define AT803X_MODE_CFG_BX1000_CONV_75OHM      0x05
+#define AT803X_MODE_CFG_FX100_RGMII_50OHM      0x06
+#define AT803X_MODE_CFG_FX100_CONV_50OHM       0x07
+#define AT803X_MODE_CFG_RGMII_AUTO_MDET                0x0B
+#define AT803X_MODE_CFG_FX100_RGMII_75OHM      0x0E
+#define AT803X_MODE_CFG_FX100_CONV_75OHM       0x0F
 
 #define AT803X_PSSR                            0x11    /*PHY-Specific Status Register*/
 #define AT803X_PSSR_MR_AN_COMPLETE             0x0200
@@ -283,6 +295,8 @@ struct at803x_priv {
        u16 clk_25m_mask;
        u8 smarteee_lpi_tw_1g;
        u8 smarteee_lpi_tw_100m;
+       bool is_fiber;
+       bool is_1000basex;
        struct regulator_dev *vddio_rdev;
        struct regulator_dev *vddh_rdev;
        struct regulator *vddio;
@@ -784,7 +798,33 @@ static int at803x_probe(struct phy_device *phydev)
                        return ret;
        }
 
+       if (phydev->drv->phy_id == ATH8031_PHY_ID) {
+               int ccr = phy_read(phydev, AT803X_REG_CHIP_CONFIG);
+               int mode_cfg;
+
+               if (ccr < 0)
+                       goto err;
+               mode_cfg = ccr & AT803X_MODE_CFG_MASK;
+
+               switch (mode_cfg) {
+               case AT803X_MODE_CFG_BX1000_RGMII_50OHM:
+               case AT803X_MODE_CFG_BX1000_RGMII_75OHM:
+                       priv->is_1000basex = true;
+                       fallthrough;
+               case AT803X_MODE_CFG_FX100_RGMII_50OHM:
+               case AT803X_MODE_CFG_FX100_RGMII_75OHM:
+                       priv->is_fiber = true;
+                       break;
+               }
+       }
+
        return 0;
+
+err:
+       if (priv->vddio)
+               regulator_disable(priv->vddio);
+
+       return ret;
 }
 
 static void at803x_remove(struct phy_device *phydev)
@@ -797,6 +837,7 @@ static void at803x_remove(struct phy_device *phydev)
 
 static int at803x_get_features(struct phy_device *phydev)
 {
+       struct at803x_priv *priv = phydev->priv;
        int err;
 
        err = genphy_read_abilities(phydev);
@@ -823,12 +864,13 @@ static int at803x_get_features(struct phy_device *phydev)
         * As a result of that, ESTATUS_1000_XFULL is set
         * to 1 even when operating in copper TP mode.
         *
-        * Remove this mode from the supported link modes,
-        * as this driver currently only supports copper
-        * operation.
+        * Remove this mode from the supported link modes
+        * when not operating in 1000BaseX mode.
         */
-       linkmode_clear_bit(ETHTOOL_LINK_MODE_1000baseX_Full_BIT,
-                          phydev->supported);
+       if (!priv->is_1000basex)
+               linkmode_clear_bit(ETHTOOL_LINK_MODE_1000baseX_Full_BIT,
+                                  phydev->supported);
+
        return 0;
 }
 
@@ -892,15 +934,18 @@ static int at8031_pll_config(struct phy_device *phydev)
 
 static int at803x_config_init(struct phy_device *phydev)
 {
+       struct at803x_priv *priv = phydev->priv;
        int ret;
 
        if (phydev->drv->phy_id == ATH8031_PHY_ID) {
                /* Some bootloaders leave the fiber page selected.
-                * Switch to the copper page, as otherwise we read
-                * the PHY capabilities from the fiber side.
+                * Switch to the appropriate page (fiber or copper), as otherwise we
+                * read the PHY capabilities from the wrong page.
                 */
                phy_lock_mdio_bus(phydev);
-               ret = at803x_write_page(phydev, AT803X_PAGE_COPPER);
+               ret = at803x_write_page(phydev,
+                                       priv->is_fiber ? AT803X_PAGE_FIBER :
+                                                        AT803X_PAGE_COPPER);
                phy_unlock_mdio_bus(phydev);
                if (ret)
                        return ret;
@@ -959,6 +1004,7 @@ static int at803x_ack_interrupt(struct phy_device *phydev)
 
 static int at803x_config_intr(struct phy_device *phydev)
 {
+       struct at803x_priv *priv = phydev->priv;
        int err;
        int value;
 
@@ -975,6 +1021,10 @@ static int at803x_config_intr(struct phy_device *phydev)
                value |= AT803X_INTR_ENABLE_DUPLEX_CHANGED;
                value |= AT803X_INTR_ENABLE_LINK_FAIL;
                value |= AT803X_INTR_ENABLE_LINK_SUCCESS;
+               if (priv->is_fiber) {
+                       value |= AT803X_INTR_ENABLE_LINK_FAIL_BX;
+                       value |= AT803X_INTR_ENABLE_LINK_SUCCESS_BX;
+               }
 
                err = phy_write(phydev, AT803X_INTR_ENABLE, value);
        } else {
@@ -1107,8 +1157,12 @@ static int at803x_read_specific_status(struct phy_device *phydev)
 
 static int at803x_read_status(struct phy_device *phydev)
 {
+       struct at803x_priv *priv = phydev->priv;
        int err, old_link = phydev->link;
 
+       if (priv->is_1000basex)
+               return genphy_c37_read_status(phydev);
+
        /* Update the link, but return if there was an error */
        err = genphy_update_link(phydev);
        if (err)
@@ -1162,6 +1216,7 @@ static int at803x_config_mdix(struct phy_device *phydev, u8 ctrl)
 
 static int at803x_config_aneg(struct phy_device *phydev)
 {
+       struct at803x_priv *priv = phydev->priv;
        int ret;
 
        ret = at803x_config_mdix(phydev, phydev->mdix_ctrl);
@@ -1178,6 +1233,9 @@ static int at803x_config_aneg(struct phy_device *phydev)
                        return ret;
        }
 
+       if (priv->is_1000basex)
+               return genphy_c37_config_aneg(phydev);
+
        /* Do not restart auto-negotiation by setting ret to 0 defautly,
         * when calling __genphy_config_aneg later.
         */