Fedora kernel-2.6.17-1.2142_FC4 patched with stable patch-2.6.17.4-vs2.0.2-rc26.diff
[linux-2.6.git] / drivers / net / ibm_emac / ibm_emac_phy.c
index 14213f0..67935dd 100644 (file)
@@ -1,96 +1,80 @@
 /*
- * ibm_ocp_phy.c
+ * drivers/net/ibm_emac/ibm_emac_phy.c
  *
- * PHY drivers for the ibm ocp ethernet driver. Borrowed
- * from sungem_phy.c, though I only kept the generic MII
+ * Driver for PowerPC 4xx on-chip ethernet controller, PHY support.
+ * Borrowed from sungem_phy.c, though I only kept the generic MII
  * driver for now.
  * 
  * This file should be shared with other drivers or eventually
  * merged as the "low level" part of miilib
  * 
  * (c) 2003, Benjamin Herrenscmidt (benh@kernel.crashing.org)
+ * (c) 2004-2005, Eugene Surovegin <ebs@ebshome.net>
  *
  */
-
 #include <linux/config.h>
-
 #include <linux/module.h>
-
 #include <linux/kernel.h>
-#include <linux/sched.h>
 #include <linux/types.h>
 #include <linux/netdevice.h>
-#include <linux/etherdevice.h>
 #include <linux/mii.h>
 #include <linux/ethtool.h>
 #include <linux/delay.h>
 
+#include <asm/ocp.h>
+
 #include "ibm_emac_phy.h"
 
-static int reset_one_mii_phy(struct mii_phy *phy, int phy_id)
+static inline int phy_read(struct mii_phy *phy, int reg)
+{
+       return phy->mdio_read(phy->dev, phy->address, reg);
+}
+
+static inline void phy_write(struct mii_phy *phy, int reg, int val)
 {
-       u16 val;
+       phy->mdio_write(phy->dev, phy->address, reg, val);
+}
+
+int mii_reset_phy(struct mii_phy *phy)
+{
+       int val;
        int limit = 10000;
 
-       val = __phy_read(phy, phy_id, MII_BMCR);
+       val = phy_read(phy, MII_BMCR);
        val &= ~BMCR_ISOLATE;
        val |= BMCR_RESET;
-       __phy_write(phy, phy_id, MII_BMCR, val);
+       phy_write(phy, MII_BMCR, val);
 
-       udelay(100);
+       udelay(300);
 
        while (limit--) {
-               val = __phy_read(phy, phy_id, MII_BMCR);
-               if ((val & BMCR_RESET) == 0)
+               val = phy_read(phy, MII_BMCR);
+               if (val >= 0 && (val & BMCR_RESET) == 0)
                        break;
                udelay(10);
        }
        if ((val & BMCR_ISOLATE) && limit > 0)
-               __phy_write(phy, phy_id, MII_BMCR, val & ~BMCR_ISOLATE);
+               phy_write(phy, MII_BMCR, val & ~BMCR_ISOLATE);
 
-       return (limit <= 0);
-}
-
-static int cis8201_init(struct mii_phy *phy)
-{
-       u16 epcr;
-
-       epcr = phy_read(phy, MII_CIS8201_EPCR);
-       epcr &= ~EPCR_MODE_MASK;
-
-       switch (phy->mode) {
-       case PHY_MODE_TBI:
-               epcr |= EPCR_TBI_MODE;
-               break;
-       case PHY_MODE_RTBI:
-               epcr |= EPCR_RTBI_MODE;
-               break;
-       case PHY_MODE_GMII:
-               epcr |= EPCR_GMII_MODE;
-               break;
-       case PHY_MODE_RGMII:
-       default:
-               epcr |= EPCR_RGMII_MODE;
-       }
-
-       phy_write(phy, MII_CIS8201_EPCR, epcr);
-
-       return 0;
+       return limit <= 0;
 }
 
 static int genmii_setup_aneg(struct mii_phy *phy, u32 advertise)
 {
-       u16 ctl, adv;
+       int ctl, adv;
 
-       phy->autoneg = 1;
+       phy->autoneg = AUTONEG_ENABLE;
        phy->speed = SPEED_10;
        phy->duplex = DUPLEX_HALF;
-       phy->pause = 0;
+       phy->pause = phy->asym_pause = 0;
        phy->advertising = advertise;
 
        /* Setup standard advertise */
        adv = phy_read(phy, MII_ADVERTISE);
-       adv &= ~(ADVERTISE_ALL | ADVERTISE_100BASE4);
+       if (adv < 0)
+               return adv;
+       adv &= ~(ADVERTISE_ALL | ADVERTISE_100BASE4 | ADVERTISE_PAUSE_CAP |
+                ADVERTISE_PAUSE_ASYM);
        if (advertise & ADVERTISED_10baseT_Half)
                adv |= ADVERTISE_10HALF;
        if (advertise & ADVERTISED_10baseT_Full)
@@ -99,8 +83,25 @@ static int genmii_setup_aneg(struct mii_phy *phy, u32 advertise)
                adv |= ADVERTISE_100HALF;
        if (advertise & ADVERTISED_100baseT_Full)
                adv |= ADVERTISE_100FULL;
+       if (advertise & ADVERTISED_Pause)
+               adv |= ADVERTISE_PAUSE_CAP;
+       if (advertise & ADVERTISED_Asym_Pause)
+               adv |= ADVERTISE_PAUSE_ASYM;
        phy_write(phy, MII_ADVERTISE, adv);
 
+       if (phy->features &
+           (SUPPORTED_1000baseT_Full | SUPPORTED_1000baseT_Half)) {
+               adv = phy_read(phy, MII_CTRL1000);
+               if (adv < 0)
+                       return adv;
+               adv &= ~(ADVERTISE_1000FULL | ADVERTISE_1000HALF);
+               if (advertise & ADVERTISED_1000baseT_Full)
+                       adv |= ADVERTISE_1000FULL;
+               if (advertise & ADVERTISED_1000baseT_Half)
+                       adv |= ADVERTISE_1000HALF;
+               phy_write(phy, MII_CTRL1000, adv);
+       }
+
        /* Start/Restart aneg */
        ctl = phy_read(phy, MII_BMCR);
        ctl |= (BMCR_ANENABLE | BMCR_ANRESTART);
@@ -111,14 +112,16 @@ static int genmii_setup_aneg(struct mii_phy *phy, u32 advertise)
 
 static int genmii_setup_forced(struct mii_phy *phy, int speed, int fd)
 {
-       u16 ctl;
+       int ctl;
 
-       phy->autoneg = 0;
+       phy->autoneg = AUTONEG_DISABLE;
        phy->speed = speed;
        phy->duplex = fd;
-       phy->pause = 0;
+       phy->pause = phy->asym_pause = 0;
 
        ctl = phy_read(phy, MII_BMCR);
+       if (ctl < 0)
+               return ctl;
        ctl &= ~(BMCR_FULLDPLX | BMCR_SPEED100 | BMCR_ANENABLE);
 
        /* First reset the PHY */
@@ -132,6 +135,8 @@ static int genmii_setup_forced(struct mii_phy *phy, int speed, int fd)
                ctl |= BMCR_SPEED100;
                break;
        case SPEED_1000:
+               ctl |= BMCR_SPEED1000;
+               break;
        default:
                return -EINVAL;
        }
@@ -144,112 +149,155 @@ static int genmii_setup_forced(struct mii_phy *phy, int speed, int fd)
 
 static int genmii_poll_link(struct mii_phy *phy)
 {
-       u16 status;
+       int status;
 
-       (void)phy_read(phy, MII_BMSR);
+       /* Clear latched value with dummy read */
+       phy_read(phy, MII_BMSR);
        status = phy_read(phy, MII_BMSR);
-       if ((status & BMSR_LSTATUS) == 0)
+       if (status < 0 || (status & BMSR_LSTATUS) == 0)
                return 0;
-       if (phy->autoneg && !(status & BMSR_ANEGCOMPLETE))
+       if (phy->autoneg == AUTONEG_ENABLE && !(status & BMSR_ANEGCOMPLETE))
                return 0;
        return 1;
 }
 
-#define        MII_CIS8201_ACSR        0x1c
-#define  ACSR_DUPLEX_STATUS    0x0020
-#define  ACSR_SPEED_1000BASET  0x0010
-#define  ACSR_SPEED_100BASET   0x0008
-
-static int cis8201_read_link(struct mii_phy *phy)
+static int genmii_read_link(struct mii_phy *phy)
 {
-       u16 acsr;
+       if (phy->autoneg == AUTONEG_ENABLE) {
+               int glpa = 0;
+               int lpa = phy_read(phy, MII_LPA) & phy_read(phy, MII_ADVERTISE);
+               if (lpa < 0)
+                       return lpa;
+
+               if (phy->features &
+                   (SUPPORTED_1000baseT_Full | SUPPORTED_1000baseT_Half)) {
+                       int adv = phy_read(phy, MII_CTRL1000);
+                       glpa = phy_read(phy, MII_STAT1000);
 
-       if (phy->autoneg) {
-               acsr = phy_read(phy, MII_CIS8201_ACSR);
+                       if (glpa < 0 || adv < 0)
+                               return adv;
 
-               if (acsr & ACSR_DUPLEX_STATUS)
+                       glpa &= adv << 2;
+               }
+
+               phy->speed = SPEED_10;
+               phy->duplex = DUPLEX_HALF;
+               phy->pause = phy->asym_pause = 0;
+
+               if (glpa & (LPA_1000FULL | LPA_1000HALF)) {
+                       phy->speed = SPEED_1000;
+                       if (glpa & LPA_1000FULL)
+                               phy->duplex = DUPLEX_FULL;
+               } else if (lpa & (LPA_100FULL | LPA_100HALF)) {
+                       phy->speed = SPEED_100;
+                       if (lpa & LPA_100FULL)
+                               phy->duplex = DUPLEX_FULL;
+               } else if (lpa & LPA_10FULL)
+                       phy->duplex = DUPLEX_FULL;
+
+               if (phy->duplex == DUPLEX_FULL) {
+                       phy->pause = lpa & LPA_PAUSE_CAP ? 1 : 0;
+                       phy->asym_pause = lpa & LPA_PAUSE_ASYM ? 1 : 0;
+               }
+       } else {
+               int bmcr = phy_read(phy, MII_BMCR);
+               if (bmcr < 0)
+                       return bmcr;
+
+               if (bmcr & BMCR_FULLDPLX)
                        phy->duplex = DUPLEX_FULL;
                else
                        phy->duplex = DUPLEX_HALF;
-               if (acsr & ACSR_SPEED_1000BASET) {
+               if (bmcr & BMCR_SPEED1000)
                        phy->speed = SPEED_1000;
-               } else if (acsr & ACSR_SPEED_100BASET)
+               else if (bmcr & BMCR_SPEED100)
                        phy->speed = SPEED_100;
                else
                        phy->speed = SPEED_10;
-               phy->pause = 0;
-       }
-       /* On non-aneg, we assume what we put in BMCR is the speed,
-        * though magic-aneg shouldn't prevent this case from occurring
-        */
 
+               phy->pause = phy->asym_pause = 0;
+       }
        return 0;
 }
 
-static int genmii_read_link(struct mii_phy *phy)
+/* Generic implementation for most 10/100/1000 PHYs */
+static struct mii_phy_ops generic_phy_ops = {
+       .setup_aneg     = genmii_setup_aneg,
+       .setup_forced   = genmii_setup_forced,
+       .poll_link      = genmii_poll_link,
+       .read_link      = genmii_read_link
+};
+
+static struct mii_phy_def genmii_phy_def = {
+       .phy_id         = 0x00000000,
+       .phy_id_mask    = 0x00000000,
+       .name           = "Generic MII",
+       .ops            = &generic_phy_ops
+};
+
+/* CIS8201 */
+#define MII_CIS8201_10BTCSR    0x16
+#define  TENBTCSR_ECHO_DISABLE 0x2000
+#define MII_CIS8201_EPCR       0x17
+#define  EPCR_MODE_MASK                0x3000
+#define  EPCR_GMII_MODE                0x0000
+#define  EPCR_RGMII_MODE       0x1000
+#define  EPCR_TBI_MODE         0x2000
+#define  EPCR_RTBI_MODE                0x3000
+#define MII_CIS8201_ACSR       0x1c
+#define  ACSR_PIN_PRIO_SELECT  0x0004
+
+static int cis8201_init(struct mii_phy *phy)
 {
-       u16 lpa;
+       int epcr;
 
-       if (phy->autoneg) {
-               lpa = phy_read(phy, MII_LPA) & phy_read(phy, MII_ADVERTISE);
+       epcr = phy_read(phy, MII_CIS8201_EPCR);
+       if (epcr < 0)
+               return epcr;
 
-               phy->speed = SPEED_10;
-               phy->duplex = DUPLEX_HALF;
-               phy->pause = 0;
+       epcr &= ~EPCR_MODE_MASK;
 
-               if (lpa & (LPA_100FULL | LPA_100HALF)) {
-                       phy->speed = SPEED_100;
-                       if (lpa & LPA_100FULL)
-                               phy->duplex = DUPLEX_FULL;
-               } else if (lpa & LPA_10FULL)
-                       phy->duplex = DUPLEX_FULL;
+       switch (phy->mode) {
+       case PHY_MODE_TBI:
+               epcr |= EPCR_TBI_MODE;
+               break;
+       case PHY_MODE_RTBI:
+               epcr |= EPCR_RTBI_MODE;
+               break;
+       case PHY_MODE_GMII:
+               epcr |= EPCR_GMII_MODE;
+               break;
+       case PHY_MODE_RGMII:
+       default:
+               epcr |= EPCR_RGMII_MODE;
        }
-       /* On non-aneg, we assume what we put in BMCR is the speed,
-        * though magic-aneg shouldn't prevent this case from occurring
-        */
+
+       phy_write(phy, MII_CIS8201_EPCR, epcr);
+       
+       /* MII regs override strap pins */
+       phy_write(phy, MII_CIS8201_ACSR, 
+                 phy_read(phy, MII_CIS8201_ACSR) | ACSR_PIN_PRIO_SELECT);
+
+       /* Disable TX_EN -> CRS echo mode, otherwise 10/HDX doesn't work */
+       phy_write(phy, MII_CIS8201_10BTCSR,
+                 phy_read(phy, MII_CIS8201_10BTCSR) | TENBTCSR_ECHO_DISABLE);
 
        return 0;
 }
 
-#define MII_BASIC_FEATURES     (SUPPORTED_10baseT_Half | SUPPORTED_10baseT_Full | \
-                                SUPPORTED_100baseT_Half | SUPPORTED_100baseT_Full | \
-                                SUPPORTED_Autoneg | SUPPORTED_TP | SUPPORTED_MII)
-#define MII_GBIT_FEATURES      (MII_BASIC_FEATURES | \
-                                SUPPORTED_1000baseT_Half | SUPPORTED_1000baseT_Full)
-
-/* CIS8201 phy ops */
 static struct mii_phy_ops cis8201_phy_ops = {
-       init:cis8201_init,
-       setup_aneg:genmii_setup_aneg,
-       setup_forced:genmii_setup_forced,
-       poll_link:genmii_poll_link,
-       read_link:cis8201_read_link
-};
-
-/* Generic implementation for most 10/100 PHYs */
-static struct mii_phy_ops generic_phy_ops = {
-       setup_aneg:genmii_setup_aneg,
-       setup_forced:genmii_setup_forced,
-       poll_link:genmii_poll_link,
-       read_link:genmii_read_link
+       .init           = cis8201_init,
+       .setup_aneg     = genmii_setup_aneg,
+       .setup_forced   = genmii_setup_forced,
+       .poll_link      = genmii_poll_link,
+       .read_link      = genmii_read_link
 };
 
 static struct mii_phy_def cis8201_phy_def = {
-       phy_id:0x000fc410,
-       phy_id_mask:0x000ffff0,
-       name:"CIS8201 Gigabit Ethernet",
-       features:MII_GBIT_FEATURES,
-       magic_aneg:0,
-       ops:&cis8201_phy_ops
-};
-
-static struct mii_phy_def genmii_phy_def = {
-       phy_id:0x00000000,
-       phy_id_mask:0x00000000,
-       name:"Generic MII",
-       features:MII_BASIC_FEATURES,
-       magic_aneg:0,
-       ops:&generic_phy_ops
+       .phy_id         = 0x000fc410,
+       .phy_id_mask    = 0x000ffff0,
+       .name           = "CIS8201 Gigabit Ethernet",
+       .ops            = &cis8201_phy_ops
 };
 
 static struct mii_phy_def *mii_phy_table[] = {
@@ -258,39 +306,60 @@ static struct mii_phy_def *mii_phy_table[] = {
        NULL
 };
 
-int mii_phy_probe(struct mii_phy *phy, int mii_id)
+int mii_phy_probe(struct mii_phy *phy, int address)
 {
-       int rc;
-       u32 id;
        struct mii_phy_def *def;
        int i;
+       u32 id;
 
-       phy->autoneg = 0;
+       phy->autoneg = AUTONEG_DISABLE;
        phy->advertising = 0;
-       phy->mii_id = mii_id;
-       phy->speed = 0;
-       phy->duplex = 0;
-       phy->pause = 0;
-
-       /* Take PHY out of isloate mode and reset it. */
-       rc = reset_one_mii_phy(phy, mii_id);
-       if (rc)
+       phy->address = address;
+       phy->speed = SPEED_10;
+       phy->duplex = DUPLEX_HALF;
+       phy->pause = phy->asym_pause = 0;
+
+       /* Take PHY out of isolate mode and reset it. */
+       if (mii_reset_phy(phy))
                return -ENODEV;
 
        /* Read ID and find matching entry */
-       id = (phy_read(phy, MII_PHYSID1) << 16 | phy_read(phy, MII_PHYSID2))
-           & 0xfffffff0;
+       id = (phy_read(phy, MII_PHYSID1) << 16) | phy_read(phy, MII_PHYSID2);
        for (i = 0; (def = mii_phy_table[i]) != NULL; i++)
                if ((id & def->phy_id_mask) == def->phy_id)
                        break;
        /* Should never be NULL (we have a generic entry), but... */
-       if (def == NULL)
+       if (!def)
                return -ENODEV;
 
        phy->def = def;
 
+       /* Determine PHY features if needed */
+       phy->features = def->features;
+       if (!phy->features) {
+               u16 bmsr = phy_read(phy, MII_BMSR);
+               if (bmsr & BMSR_ANEGCAPABLE)
+                       phy->features |= SUPPORTED_Autoneg;
+               if (bmsr & BMSR_10HALF)
+                       phy->features |= SUPPORTED_10baseT_Half;
+               if (bmsr & BMSR_10FULL)
+                       phy->features |= SUPPORTED_10baseT_Full;
+               if (bmsr & BMSR_100HALF)
+                       phy->features |= SUPPORTED_100baseT_Half;
+               if (bmsr & BMSR_100FULL)
+                       phy->features |= SUPPORTED_100baseT_Full;
+               if (bmsr & BMSR_ESTATEN) {
+                       u16 esr = phy_read(phy, MII_ESTATUS);
+                       if (esr & ESTATUS_1000_TFULL)
+                               phy->features |= SUPPORTED_1000baseT_Full;
+                       if (esr & ESTATUS_1000_THALF)
+                               phy->features |= SUPPORTED_1000baseT_Half;
+               }
+               phy->features |= SUPPORTED_MII;
+       }
+
        /* Setup default advertising */
-       phy->advertising = def->features;
+       phy->advertising = phy->features;
 
        return 0;
 }