phy: SMSC: fix autonegotiation
authorLothar Waßmann <LW@KARO-electronics.de>
Wed, 29 Oct 2014 07:38:29 +0000 (08:38 +0100)
committerLothar Waßmann <LW@KARO-electronics.de>
Wed, 29 Oct 2014 07:38:29 +0000 (08:38 +0100)
drivers/net/phy/smsc.c

index f64eb8f..23da3da 100644 (file)
  * Copyright (c) 2006 Herbert Valerio Riedel <hvr@gnu.org>
  */
 #include <miiphy.h>
+#include <errno.h>
+
+#define MII_LAN83C185_CTRL_STATUS      17 /* Mode/Status Register */
+#define MII_LAN83C185_EDPWRDOWN                (1 << 13) /* EDPWRDOWN */
+#define MII_LAN83C185_ENERGYON         (1 << 1)  /* ENERGYON */
 
 static int smsc_parse_status(struct phy_device *phydev)
 {
-       int mii_reg;
+       int bmcr;
+       int aneg_exp;
+       int mii_adv;
+       int lpa;
+
+       aneg_exp = phy_read(phydev, MDIO_DEVAD_NONE, MII_EXPANSION);
+       if (aneg_exp < 0)
+               return aneg_exp;
+
+       if (aneg_exp & EXPANSION_MFAULTS) {
+               /* second read to clear latched status */
+               aneg_exp = phy_read(phydev, MDIO_DEVAD_NONE, MII_EXPANSION);
+               if (aneg_exp & EXPANSION_MFAULTS)
+                       return -EIO;
+       }
 
-       mii_reg = phy_read(phydev, MDIO_DEVAD_NONE, MII_BMSR);
+       bmcr = phy_read(phydev, MDIO_DEVAD_NONE, MII_BMCR);
+       if (bmcr < 0)
+               return bmcr;
+       if (bmcr & BMCR_ANENABLE) {
+               lpa = phy_read(phydev, MDIO_DEVAD_NONE, MII_LPA);
+               if (lpa < 0)
+                       return lpa;
+               mii_adv = phy_read(phydev, MDIO_DEVAD_NONE, MII_ADVERTISE);
+               if (mii_adv < 0)
+                       return mii_adv;
+               lpa &= mii_adv;
 
-       if (mii_reg & (BMSR_100FULL | BMSR_100HALF))
-               phydev->speed = SPEED_100;
-       else
-               phydev->speed = SPEED_10;
+               if (!(aneg_exp & EXPANSION_NWAY)) {
+                       /* parallel detection */
+                       phydev->duplex = DUPLEX_HALF;
+                       if (lpa & (LPA_100HALF | LPA_100FULL))
+                               phydev->speed = SPEED_100;
+                       else
+                               phydev->speed = SPEED_10;
+               }
 
-       if (mii_reg & (BMSR_10FULL | BMSR_100FULL))
-               phydev->duplex = DUPLEX_FULL;
-       else
-               phydev->duplex = DUPLEX_HALF;
+               if (lpa & (LPA_100FULL | LPA_100HALF)) {
+                       phydev->speed = SPEED_100;
+                       if (lpa & LPA_100FULL)
+                               phydev->duplex = DUPLEX_FULL;
+                       else
+                               phydev->duplex = DUPLEX_HALF;
+               } else if (lpa & (LPA_10FULL | LPA_10HALF)) {
+                       phydev->speed = SPEED_10;
+                       if (lpa & LPA_10FULL)
+                               phydev->duplex = DUPLEX_FULL;
+                       else
+                               phydev->duplex = DUPLEX_HALF;
+               } else {
+                       return -EINVAL;
+               }
+       } else {
+               if (bmcr & BMCR_SPEED100)
+                       phydev->speed = SPEED_100;
+               else
+                       phydev->speed = SPEED_10;
 
+               if (bmcr & BMCR_FULLDPLX)
+                       phydev->duplex = DUPLEX_FULL;
+               else
+                       phydev->duplex = DUPLEX_HALF;
+       }
        return 0;
 }
 
 static int smsc_startup(struct phy_device *phydev)
 {
-       genphy_update_link(phydev);
-       smsc_parse_status(phydev);
-       return 0;
+       int ret;
+
+       if (!phydev->link) {
+               /* Disable EDPD to wake up PHY */
+               ret = phy_read(phydev, MDIO_DEVAD_NONE, MII_LAN83C185_CTRL_STATUS);
+
+               if (ret < 0)
+                       return ret;
+
+               if (ret & MII_LAN83C185_EDPWRDOWN) {
+                       ret = phy_write(phydev, MDIO_DEVAD_NONE, MII_LAN83C185_CTRL_STATUS,
+                               ret & ~MII_LAN83C185_EDPWRDOWN);
+                       if (ret < 0)
+                               return ret;
+
+                       /* Sleep 64 ms to allow ~5 link test pulses to be sent */
+                       udelay(64 * 1000);
+               }
+       }
+
+       ret = genphy_update_link(phydev);
+       if (ret < 0)
+               return ret;
+       return smsc_parse_status(phydev);
 }
 
 static struct phy_driver lan8700_driver = {