[PATCH] pcnet32: support boards with multiple phys
authorDon Fry <brazilnut@us.ibm.com>
Mon, 20 Mar 2006 23:26:03 +0000 (15:26 -0800)
committerJeff Garzik <jeff@garzik.org>
Tue, 21 Mar 2006 21:00:53 +0000 (16:00 -0500)
Boards with multiple PHYs were not being handled properly by the pcnet32
driver.  This patch by Thomas Bogendoerfer with changes by me will allow
Allied Telesyn 2700FTX and 2701FTX boards to use either the copper or
the fiber interfaces.  It has been tested on ia32 and ppc64 hardware.
Philippe Seewer also tested and improved the patch.
ethtool for pcnet32 already supports multiple phys.

See also bugzilla bug 4219.

Please apply to 2.6.16

Signed-off-by: Don Fry <brazilnut@us.ibm.com>
Signed-off-by: Jeff Garzik <jeff@garzik.org>
drivers/net/pcnet32.c

index 7e900572eaf83f93168fa91eb27b346fb69d5829..1bc3f5bffb95007e276a4acf40e60b94c5663113 100644 (file)
@@ -22,8 +22,8 @@
  *************************************************************************/
 
 #define DRV_NAME       "pcnet32"
-#define DRV_VERSION    "1.31c"
-#define DRV_RELDATE    "01.Nov.2005"
+#define DRV_VERSION    "1.32"
+#define DRV_RELDATE    "18.Mar.2006"
 #define PFX            DRV_NAME ": "
 
 static const char * const version =
@@ -133,7 +133,7 @@ static const char pcnet32_gstrings_test[][ETH_GSTRING_LEN] = {
 };
 #define PCNET32_TEST_LEN (sizeof(pcnet32_gstrings_test) / ETH_GSTRING_LEN)
 
-#define PCNET32_NUM_REGS 168
+#define PCNET32_NUM_REGS 136
 
 #define MAX_UNITS 8    /* More are supported, limit only on options */
 static int options[MAX_UNITS];
@@ -265,6 +265,9 @@ static int homepna[MAX_UNITS];
  * v1.31c  01 Nov 2005 Don Fry Allied Telesyn 2700/2701 FX are 100Mbit only.
  *        Force 100Mbit FD if Auto (ASEL) is selected.
  *        See Bugzilla 2669 and 4551.
+ * v1.32   18 Mar2006 Thomas Bogendoerfer and Don Fry added Multi-Phy
+ *        handling for supporting AT-270x FTX cards with FX and Tx PHYs.
+ *        Philippe Seewer assisted with auto negotiation and testing.
  */
 
 
@@ -375,6 +378,7 @@ struct pcnet32_private {
     unsigned int       dirty_rx, dirty_tx; /* The ring entries to be free()ed. */
     struct net_device_stats stats;
     char               tx_full;
+    char               phycount;       /* number of phys found */
     int                        options;
     unsigned int       shared_irq:1,   /* shared irq possible */
                        dxsuflo:1,      /* disable transmit stop on uflo */
@@ -384,6 +388,9 @@ struct pcnet32_private {
     struct timer_list  watchdog_timer;
     struct timer_list  blink_timer;
     u32                        msg_enable;     /* debug message level */
+
+    /* each bit indicates an available PHY */
+    u32                        phymask;
 };
 
 static void pcnet32_probe_vlbus(void);
@@ -415,6 +422,7 @@ static void pcnet32_get_regs(struct net_device *dev, struct ethtool_regs *regs,
 static void pcnet32_purge_tx_ring(struct net_device *dev);
 static int pcnet32_alloc_ring(struct net_device *dev, char *name);
 static void pcnet32_free_ring(struct net_device *dev);
+static void pcnet32_check_media(struct net_device *dev, int verbose);
 
 
 enum pci_flags_bit {
@@ -936,9 +944,14 @@ static int pcnet32_phys_id(struct net_device *dev, u32 data)
     return 0;
 }
 
+#define PCNET32_REGS_PER_PHY   32
+#define PCNET32_MAX_PHYS       32
 static int pcnet32_get_regs_len(struct net_device *dev)
 {
-    return(PCNET32_NUM_REGS * sizeof(u16));
+    struct pcnet32_private *lp = dev->priv;
+    int j = lp->phycount * PCNET32_REGS_PER_PHY;
+
+    return((PCNET32_NUM_REGS + j) * sizeof(u16));
 }
 
 static void pcnet32_get_regs(struct net_device *dev, struct ethtool_regs *regs,
@@ -998,9 +1011,14 @@ static void pcnet32_get_regs(struct net_device *dev, struct ethtool_regs *regs,
 
     /* read mii phy registers */
     if (lp->mii) {
-       for (i=0; i<32; i++) {
-           lp->a.write_bcr(ioaddr, 33, ((lp->mii_if.phy_id) << 5) | i);
-           *buff++ = lp->a.read_bcr(ioaddr, 34);
+       int j;
+       for (j=0; j<PCNET32_MAX_PHYS; j++) {
+           if (lp->phymask & (1 << j)) {
+               for (i=0; i<PCNET32_REGS_PER_PHY; i++) {
+                   lp->a.write_bcr(ioaddr, 33, (j << 5) | i);
+                   *buff++ = lp->a.read_bcr(ioaddr, 34);
+               }
+           }
        }
     }
 
@@ -1009,10 +1027,6 @@ static void pcnet32_get_regs(struct net_device *dev, struct ethtool_regs *regs,
        a->write_csr(ioaddr, 5, 0x0000);
     }
 
-    i = buff - (u16 *)ptr;
-    for (; i < PCNET32_NUM_REGS; i++)
-       *buff++ = 0;
-
     spin_unlock_irqrestore(&lp->lock, flags);
 }
 
@@ -1185,7 +1199,7 @@ pcnet32_probe1(unsigned long ioaddr, int shared, struct pci_dev *pdev)
        if (cards_found < MAX_UNITS && homepna[cards_found])
            media |= 1;         /* switch to home wiring mode */
        if (pcnet32_debug & NETIF_MSG_PROBE)
-           printk(KERN_DEBUG PFX "media set to %sMbit mode.\n", 
+           printk(KERN_DEBUG PFX "media set to %sMbit mode.\n",
                    (media & 1) ? "1" : "10");
        a->write_bcr(ioaddr, 49, media);
        break;
@@ -1401,8 +1415,34 @@ pcnet32_probe1(unsigned long ioaddr, int shared, struct pci_dev *pdev)
     }
 
     /* Set the mii phy_id so that we can query the link state */
-    if (lp->mii)
+    if (lp->mii) {
+       /* lp->phycount and lp->phymask are set to 0 by memset above */
+
        lp->mii_if.phy_id = ((lp->a.read_bcr (ioaddr, 33)) >> 5) & 0x1f;
+       /* scan for PHYs */
+       for (i=0; i<PCNET32_MAX_PHYS; i++) {
+           unsigned short id1, id2;
+
+           id1 = mdio_read(dev, i, MII_PHYSID1);
+           if (id1 == 0xffff)
+               continue;
+           id2 = mdio_read(dev, i, MII_PHYSID2);
+           if (id2 == 0xffff)
+               continue;
+           if (i == 31 && ((chip_version + 1) & 0xfffe) == 0x2624)
+               continue;       /* 79C971 & 79C972 have phantom phy at id 31 */
+           lp->phycount++;
+           lp->phymask |= (1 << i);
+           lp->mii_if.phy_id = i;
+           if (pcnet32_debug & NETIF_MSG_PROBE)
+               printk(KERN_INFO PFX "Found PHY %04x:%04x at address %d.\n",
+                       id1, id2, i);
+       }
+       lp->a.write_bcr(ioaddr, 33, (lp->mii_if.phy_id) << 5);
+       if (lp->phycount > 1) {
+           lp->options |= PCNET32_PORT_MII;
+       }
+    }
 
     init_timer (&lp->watchdog_timer);
     lp->watchdog_timer.data = (unsigned long) dev;
@@ -1625,7 +1665,7 @@ pcnet32_open(struct net_device *dev)
                        dev->name);
        }
     }
-    {
+    if (lp->phycount < 2) {
        /*
         * 24 Jun 2004 according AMD, in order to change the PHY,
         * DANAS (or DISPM for 79C976) must be set; then select the speed,
@@ -1651,6 +1691,62 @@ pcnet32_open(struct net_device *dev)
                lp->a.write_bcr(ioaddr, 32, val);
            }
        }
+    } else {
+       int first_phy = -1;
+       u16 bmcr;
+       u32 bcr9;
+       struct ethtool_cmd ecmd;
+
+       /*
+        * There is really no good other way to handle multiple PHYs
+        * other than turning off all automatics
+        */
+       val = lp->a.read_bcr(ioaddr, 2);
+       lp->a.write_bcr(ioaddr, 2, val & ~2);
+       val = lp->a.read_bcr(ioaddr, 32);
+       lp->a.write_bcr(ioaddr, 32, val & ~(1 << 7)); /* stop MII manager */
+
+       if (!(lp->options & PCNET32_PORT_ASEL)) {
+           /* setup ecmd */
+           ecmd.port = PORT_MII;
+           ecmd.transceiver = XCVR_INTERNAL;
+           ecmd.autoneg = AUTONEG_DISABLE;
+           ecmd.speed = lp->options & PCNET32_PORT_100 ? SPEED_100 : SPEED_10;
+           bcr9 = lp->a.read_bcr(ioaddr, 9);
+
+           if (lp->options & PCNET32_PORT_FD) {
+               ecmd.duplex = DUPLEX_FULL;
+               bcr9 |= (1 << 0);
+           } else {
+               ecmd.duplex = DUPLEX_HALF;
+               bcr9 |= ~(1 << 0);
+           }
+           lp->a.write_bcr(ioaddr, 9, bcr9);
+       }
+
+       for (i=0; i<PCNET32_MAX_PHYS; i++) {
+           if (lp->phymask & (1 << i)) {
+               /* isolate all but the first PHY */
+               bmcr = mdio_read(dev, i, MII_BMCR);
+               if (first_phy == -1) {
+                   first_phy = i;
+                   mdio_write(dev, i, MII_BMCR, bmcr & ~BMCR_ISOLATE);
+               } else {
+                   mdio_write(dev, i, MII_BMCR, bmcr | BMCR_ISOLATE);
+               }
+               /* use mii_ethtool_sset to setup PHY */
+               lp->mii_if.phy_id = i;
+               ecmd.phy_address = i;
+               if (lp->options & PCNET32_PORT_ASEL) {
+                   mii_ethtool_gset(&lp->mii_if, &ecmd);
+                   ecmd.autoneg = AUTONEG_ENABLE;
+               }
+               mii_ethtool_sset(&lp->mii_if, &ecmd);
+           }
+       }
+       lp->mii_if.phy_id = first_phy;
+       if (netif_msg_link(lp))
+           printk(KERN_INFO "%s: Using PHY number %d.\n", dev->name, first_phy);
     }
 
 #ifdef DO_DXSUFLO
@@ -1680,11 +1776,9 @@ pcnet32_open(struct net_device *dev)
 
     netif_start_queue(dev);
 
-    /* If we have mii, print the link status and start the watchdog */
-    if (lp->mii) {
-       mii_check_media (&lp->mii_if, netif_msg_link(lp), 1);
-       mod_timer (&(lp->watchdog_timer), PCNET32_WATCHDOG_TIMEOUT);
-    }
+    /* Print the link status and start the watchdog */
+    pcnet32_check_media (dev, 1);
+    mod_timer (&(lp->watchdog_timer), PCNET32_WATCHDOG_TIMEOUT);
 
     i = 0;
     while (i++ < 100)
@@ -2430,17 +2524,111 @@ static int pcnet32_ioctl(struct net_device *dev, struct ifreq *rq, int cmd)
     return rc;
 }
 
+static int pcnet32_check_otherphy(struct net_device *dev)
+{
+    struct pcnet32_private *lp = dev->priv;
+    struct mii_if_info mii = lp->mii_if;
+    u16 bmcr;
+    int i;
+
+    for (i = 0; i < PCNET32_MAX_PHYS; i++) {
+       if (i == lp->mii_if.phy_id)
+           continue; /* skip active phy */
+       if (lp->phymask & (1 << i)) {
+           mii.phy_id = i;
+           if (mii_link_ok(&mii)) {
+               /* found PHY with active link */
+               if (netif_msg_link(lp))
+                   printk(KERN_INFO "%s: Using PHY number %d.\n", dev->name, i);
+
+               /* isolate inactive phy */
+               bmcr = mdio_read(dev, lp->mii_if.phy_id, MII_BMCR);
+               mdio_write(dev, lp->mii_if.phy_id, MII_BMCR, bmcr | BMCR_ISOLATE);
+
+               /* de-isolate new phy */
+               bmcr = mdio_read(dev, i, MII_BMCR);
+               mdio_write(dev, i, MII_BMCR, bmcr & ~BMCR_ISOLATE);
+
+               /* set new phy address */
+               lp->mii_if.phy_id = i;
+               return 1;
+           }
+       }
+    }
+    return 0;
+}
+
+/*
+ * Show the status of the media.  Similar to mii_check_media however it
+ * correctly shows the link speed for all (tested) pcnet32 variants.
+ * Devices with no mii just report link state without speed.
+ *
+ * Caller is assumed to hold and release the lp->lock.
+ */
+
+static void pcnet32_check_media(struct net_device *dev, int verbose)
+{
+    struct pcnet32_private *lp = dev->priv;
+    int curr_link;
+    int prev_link = netif_carrier_ok(dev) ? 1 : 0;
+    u32 bcr9;
+
+    if (lp->mii) {
+       curr_link = mii_link_ok(&lp->mii_if);
+    } else {
+       ulong ioaddr = dev->base_addr;  /* card base I/O address */
+       curr_link = (lp->a.read_bcr(ioaddr, 4) != 0xc0);
+    }
+    if (!curr_link) {
+       if (prev_link || verbose) {
+           netif_carrier_off(dev);
+           if (netif_msg_link(lp))
+               printk(KERN_INFO "%s: link down\n", dev->name);
+       }
+       if (lp->phycount > 1) {
+           curr_link = pcnet32_check_otherphy(dev);
+           prev_link = 0;
+       }
+    } else if (verbose || !prev_link) {
+       netif_carrier_on(dev);
+       if (lp->mii) {
+           if (netif_msg_link(lp)) {
+               struct ethtool_cmd ecmd;
+               mii_ethtool_gset(&lp->mii_if, &ecmd);
+               printk(KERN_INFO "%s: link up, %sMbps, %s-duplex\n",
+                   dev->name,
+                   (ecmd.speed == SPEED_100) ? "100" : "10",
+                   (ecmd.duplex == DUPLEX_FULL) ? "full" : "half");
+           }
+           bcr9 = lp->a.read_bcr(dev->base_addr, 9);
+           if ((bcr9 & (1 << 0)) != lp->mii_if.full_duplex)   {
+               if (lp->mii_if.full_duplex)
+                   bcr9 |= (1 << 0);
+               else
+                   bcr9 &= ~(1 << 0);
+               lp->a.write_bcr(dev->base_addr, 9, bcr9);
+           }
+       } else {
+           if (netif_msg_link(lp))
+               printk(KERN_INFO "%s: link up\n", dev->name);
+       }
+    }
+}
+
+/*
+ * Check for loss of link and link establishment.
+ * Can not use mii_check_media because it does nothing if mode is forced.
+ */
+
 static void pcnet32_watchdog(struct net_device *dev)
 {
     struct pcnet32_private *lp = dev->priv;
     unsigned long flags;
 
     /* Print the link status if it has changed */
-    if (lp->mii) {
-       spin_lock_irqsave(&lp->lock, flags);
-       mii_check_media (&lp->mii_if, netif_msg_link(lp), 0);
-       spin_unlock_irqrestore(&lp->lock, flags);
-    }
+    spin_lock_irqsave(&lp->lock, flags);
+    pcnet32_check_media(dev, 0);
+    spin_unlock_irqrestore(&lp->lock, flags);
 
     mod_timer (&(lp->watchdog_timer), PCNET32_WATCHDOG_TIMEOUT);
 }