This patch contains code from Thomas Bogendoerfer which I have modified
somewhat. It works fine for me in my testing, but I would like
additional exposure to this code before submitting to mainline.
It should fix bugzilla bug #4219 as well.
The patch is based on 2.6.14-git11. The ethtool version at
rsync://rsync.kernel.org/pub/scm/linux/kernel/git/jgarzik/ethtool.git
supports the multiple phys (ethtool -d ethX). The ethtool version 3
will also work, just not as 'pretty'.
Please let me know if it works or not for you.
Especially if it breaks something.
Thanks.
--- linux-2.6.14-git11/drivers/net/pcnet32.c.orig Tue Nov 8 09:31:20 2005
+++ linux-2.6.14-git11/drivers/net/pcnet32.c Tue Nov 8 09:56:30 2005
@@ -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 "02.Nov.2005"
#define PFX DRV_NAME ": "
static const char *version =
@@ -133,7 +133,7 @@ static const char pcnet32_gstrings_test[
};
#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,8 @@ 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 02 Nov 2005 Thomas Bogendoerfer and Don Fry added Multi-Phy
+ * handling for supporting AT-270x FTX cards with FX and Tx PHYs
*/
@@ -384,6 +386,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, only valid with more than 1 PHY */
+ u32 multiphymask;
};
static void pcnet32_probe_vlbus(void);
@@ -936,9 +941,23 @@ static int pcnet32_phys_id(struct net_de
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 = 0;
+
+ if (lp->multiphymask) {
+ int i;
+
+ for (i=0; i<PCNET32_MAX_PHYS; i++)
+ if (lp->multiphymask & (1 << i))
+ j += PCNET32_REGS_PER_PHY;
+ } else if (lp->mii) {
+ j = 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 +1017,21 @@ static void pcnet32_get_regs(struct net_
/* 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);
+ if (lp->multiphymask) {
+ int j;
+ for (j=0; j<PCNET32_MAX_PHYS; j++) {
+ if (lp->multiphymask & (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);
+ }
+ }
+ }
+ } else {
+ for (i=0; i<PCNET32_REGS_PER_PHY; i++) {
+ lp->a.write_bcr(ioaddr, 33, ((lp->mii_if.phy_id) << 5) | i);
+ *buff++ = lp->a.read_bcr(ioaddr, 34);
+ }
}
}
@@ -1009,10 +1040,6 @@ static void pcnet32_get_regs(struct net_
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 +1212,7 @@ pcnet32_probe1(unsigned long ioaddr, int
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;
@@ -1406,8 +1433,38 @@ pcnet32_probe1(unsigned long ioaddr, int
}
/* Set the mii phy_id so that we can query the link state */
- if (lp->mii)
+ if (lp->mii) {
+ int cnt = 0;
+ u32 phymask = 0;
+
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 */
+ cnt++;
+ 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 (cnt > 1) {
+ lp->multiphymask = phymask;
+ lp->options = PCNET32_PORT_MII;
+ } else {
+ lp->multiphymask = 0;
+ }
+ }
init_timer (&lp->watchdog_timer);
lp->watchdog_timer.data = (unsigned long) dev;
@@ -1630,7 +1687,7 @@ pcnet32_open(struct net_device *dev)
dev->name);
}
}
- {
+ if (lp->multiphymask == 0) {
/*
* 24 Jun 2004 according AMD, in order to change the PHY,
* DANAS (or DISPM for 79C976) must be set; then select the speed,
@@ -1656,6 +1713,54 @@ 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, 32);
+ lp->a.write_bcr(ioaddr, 32, val & ~(1 << 7)); /* stop MII manager */
+
+ /* setup ecmd */
+ ecmd.port = PORT_MII;
+ ecmd.port = 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->multiphymask & (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;
+ 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
@@ -2435,15 +2540,89 @@ static int pcnet32_ioctl(struct net_devi
return rc;
}
+static int pcnet32_check_otherphy(struct net_device *dev)
+{
+ struct pcnet32_private *lp = dev->priv;
+ struct mii_if_info mii;
+ u16 bmcr;
+ int i;
+
+ mii = lp->mii_if;
+
+ for (i = 0; i < PCNET32_MAX_PHYS; i++) {
+ if (i == lp->mii_if.phy_id)
+ continue; /* skip active phy */
+ if (lp->multiphymask & (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;
+}
+
+/*
+ * 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;
+ int curr_link, prev_link;
+ u32 bcr9;
/* 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);
+ curr_link = mii_link_ok(&lp->mii_if);
+ prev_link = netif_carrier_ok(dev) ? 1 : 0;
+ if (!curr_link) {
+ if (prev_link) {
+ netif_carrier_off(dev);
+ if (netif_msg_link(lp))
+ printk(KERN_INFO "%s: link down\n", dev->name);
+ }
+ if (lp->multiphymask) {
+ curr_link = pcnet32_check_otherphy(dev);
+ prev_link = 0;
+ }
+ }
+ if (curr_link && !prev_link) {
+ netif_carrier_on(dev);
+ 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);
+ }
+ }
spin_unlock_irqrestore(&lp->lock, flags);
}
--
Don Fry
[EMAIL PROTECTED]
-
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to [EMAIL PROTECTED]
More majordomo info at http://vger.kernel.org/majordomo-info.html