patch-2.2.18 linux/drivers/net/gmac.c

Next file: linux/drivers/net/gmac.h
Previous file: linux/drivers/net/fc/iph5526.c
Back to the patch index
Back to the overall index

diff -u --new-file --recursive --exclude-from /usr/src/exclude v2.2.17/drivers/net/gmac.c linux/drivers/net/gmac.c
@@ -6,6 +6,13 @@
  * 
  * portions based on sunhme.c by David S. Miller
  *
+ * Changes:
+ * Arnaldo Carvalho de Melo <acme@conectiva.com.br> - 08/06/2000
+ * - check init_etherdev return in gmac_probe1
+ * BenH <bh40@calva.net> - 03/09/2000
+ * - Add support for new PHYs
+ * - Add some PowerBook sleep code
+ * 
  */
 
 #include <linux/module.h>
@@ -21,18 +28,25 @@
 #include <linux/delay.h>
 #include <linux/string.h>
 #include <linux/timer.h>
+#include <linux/init.h>
 #include <linux/pci.h>
 #include <asm/prom.h>
 #include <asm/io.h>
 #include <asm/pgtable.h>
-#include <asm/bitops.h>
+#include <asm/feature.h>
+#include <asm/keylargo.h>
+#ifdef CONFIG_PMAC_PBOOK
+#include <asm/adb.h>
+#include <asm/pmu.h>
+#include <asm/irq.h>
+#endif
 
 #include "gmac.h"
 
 #define DEBUG_PHY
 
-/* Driver version 1.1, kernel 2.2.x */
-#define GMAC_VERSION	"v1.1k2"
+/* Driver version 1.3, kernel 2.2.x */
+#define GMAC_VERSION	"v1.3k2"
 
 static unsigned char dummy_buf[RX_BUF_ALLOC_SIZE + RX_OFFSET + GMAC_BUFFER_ALIGN];
 static struct device *gmacs = NULL;
@@ -45,9 +59,12 @@
 static void mii_interrupt(struct gmac *gm);
 static int mii_lookup_and_reset(struct gmac *gm);
 static void mii_setup_phy(struct gmac *gm);
+static int mii_do_reset_phy(struct gmac *gm, int phy_addr);
+static void mii_init_BCM5400(struct gmac *gm);
 
 static void gmac_set_power(struct gmac *gm, int power_up);
 static int gmac_powerup_and_reset(struct device *dev);
+static void gmac_set_gigabit_mode(struct gmac *gm, int gigabit);
 static void gmac_set_duplex_mode(struct gmac *gm, int full_duplex);
 static void gmac_mac_init(struct gmac *gm, unsigned char *mac_addr);
 static void gmac_init_rings(struct gmac *gm, int from_irq);
@@ -67,6 +84,13 @@
 extern int pci_device_loc(struct device_node *dev, unsigned char *bus_ptr,
 		   unsigned char *devfn_ptr);
 
+#ifdef CONFIG_PMAC_PBOOK
+int gmac_sleep_notify(struct pmu_sleep_notifier *self, int when);
+static struct pmu_sleep_notifier gmac_sleep_notifier = {
+	gmac_sleep_notify, SLEEP_LEVEL_NET,
+};
+#endif
+
 /* Stuff for talking to the physical-layer chip */
 static int
 mii_read(struct gmac *gm, int phy, int r)
@@ -136,6 +160,18 @@
 	udelay(GM_MIF_POLL_DELAY);
 }
 
+/* Link modes of the BCM5400 PHY */
+static int phy_BCM5400_link_table[8][3] = {
+	{ 0, 0, 0 },	/* No link */
+	{ 0, 0, 0 },	/* 10BT Half Duplex */
+	{ 1, 0, 0 },	/* 10BT Full Duplex */
+	{ 0, 1, 0 },	/* 100BT Half Duplex */
+	{ 0, 1, 0 },	/* 100BT Half Duplex */
+	{ 1, 1, 0 },	/* 100BT Full Duplex*/
+	{ 1, 0, 1 },	/* 1000BT */
+	{ 1, 0, 1 },	/* 1000BT */
+};
+
 static void
 mii_interrupt(struct gmac *gm)
 {
@@ -150,8 +186,9 @@
 	/* We read the Auxilliary Status Summary register */
 	phy_status = mii_read(gm, gm->phy_addr, MII_SR);
 	if ((phy_status ^ gm->phy_status) & (MII_SR_ASSC | MII_SR_LKS)) {
-		int		full_duplex;
-		int		link_100;
+		int		full_duplex = 0;
+		int		link_100 = 0;
+		int		gigabit = 0;
 #ifdef DEBUG_PHY
 		printk("Link state change, phy_status: 0x%04x\n", phy_status);
 #endif
@@ -163,8 +200,9 @@
 		else
 			GM_BIC(GM_MAC_CTRL_CONFIG, GM_MAC_CTRL_CONF_SND_PAUSE_EN);
 
-		/* Link ? For now we handle only the 5201 PHY */
+		/* Link ? Check for speed and duplex */
 		if ((phy_status & MII_SR_LKS) && (phy_status & MII_SR_ASSC)) {
+		    int restart = 0;
 		    if (gm->phy_type == PHY_B5201) {
 		    	int aux_stat = mii_read(gm, gm->phy_addr, MII_BCM5201_AUXCTLSTATUS);
 #ifdef DEBUG_PHY
@@ -172,19 +210,41 @@
 #endif
 		    	full_duplex = ((aux_stat & MII_BCM5201_AUXCTLSTATUS_DUPLEX) != 0);
 		    	link_100 = ((aux_stat & MII_BCM5201_AUXCTLSTATUS_SPEED) != 0);
-		    } else {
-		    	full_duplex = 1;
-		    	link_100 = 1;
+		    } else if (gm->phy_type == PHY_B5400) {
+		    	int aux_stat = mii_read(gm, gm->phy_addr, MII_BCM5400_AUXSTATUS);
+		    	int link = (aux_stat & MII_BCM5400_AUXSTATUS_LINKMODE_MASK) >>
+		    			MII_BCM5400_AUXSTATUS_LINKMODE_SHIFT;
+#ifdef DEBUG_PHY
+			printk("    Link up ! BCM5400 aux_stat: 0x%04x (link mode: %d)\n",
+				aux_stat, link);
+#endif
+		    	full_duplex = phy_BCM5400_link_table[link][0];
+		    	link_100 = phy_BCM5400_link_table[link][1];
+		    	gigabit = phy_BCM5400_link_table[link][2];
+		    } else if (gm->phy_type == PHY_LXT971) {
+		    	int stat2 = mii_read(gm, gm->phy_addr, MII_LXT971_STATUS2);
+#ifdef DEBUG_PHY
+			printk("    Link up ! LXT971 stat2: 0x%04x\n", stat2);
+#endif
+		    	full_duplex = ((stat2 & MII_LXT971_STATUS2_FULLDUPLEX) != 0);
+		    	link_100 = ((stat2 & MII_LXT971_STATUS2_SPEED) != 0);
 		    }
 #ifdef DEBUG_PHY
 		    printk("    full_duplex: %d, speed: %s\n", full_duplex,
-		    	link_100 ? "100" : "10");
+		    	gigabit ? "1000" : (link_100 ? "100" : "10"));
 #endif
+                    if (gigabit != gm->gigabit) {
+                    	gm->gigabit = gigabit;
+                    	gmac_set_gigabit_mode(gm, gm->gigabit);
+                    	restart = 1;
+                    }
 		    if (full_duplex != gm->full_duplex) {
 			gm->full_duplex = full_duplex;
 			gmac_set_duplex_mode(gm, gm->full_duplex);
-			gmac_start_dma(gm);
+			restart = 1;
 		    }
+		    if (restart)
+			gmac_start_dma(gm);
 		} else if (!(phy_status & MII_SR_LKS)) {
 #ifdef DEBUG_PHY
 		    printk("    Link down !\n");
@@ -194,16 +254,73 @@
 }
 
 static int
+mii_do_reset_phy(struct gmac *gm, int phy_addr)
+{
+	int mii_control, timeout;
+	
+	mii_control = mii_read(gm, phy_addr, MII_CR);
+	mii_write(gm, phy_addr, MII_CR, mii_control | MII_CR_RST);
+	mdelay(10);
+	for (timeout = 100; timeout > 0; --timeout) {
+		mii_control = mii_read(gm, phy_addr, MII_CR);
+		if (mii_control == -1) {
+			printk(KERN_ERR "%s PHY died after reset !\n",
+				gm->dev->name);
+			return 1;
+		}
+		if ((mii_control & MII_CR_RST) == 0)
+			break;
+		mdelay(10);
+	}
+	if (mii_control & MII_CR_RST) {
+		printk(KERN_ERR "%s PHY reset timeout !\n", gm->dev->name);
+		return 1;
+	}
+	mii_write(gm, phy_addr, MII_CR, mii_control & ~MII_CR_ISOL);
+	return 0;
+}
+
+static void
+mii_init_BCM5400(struct gmac *gm)
+{
+	int data;
+
+	data = mii_read(gm, gm->phy_addr, MII_BCM5400_AUXCONTROL);
+	data |= MII_BCM5400_AUXCONTROL_PWR10BASET;
+	mii_write(gm, gm->phy_addr, MII_BCM5400_AUXCONTROL, data);
+	
+	data = mii_read(gm, gm->phy_addr, MII_BCM5400_GB_CONTROL);
+	data |= MII_BCM5400_GB_CONTROL_FULLDUPLEXCAP;
+	mii_write(gm, gm->phy_addr, MII_BCM5400_GB_CONTROL, data);
+	
+	mdelay(10);
+	mii_do_reset_phy(gm, 0x1f);
+	
+	data = mii_read(gm, 0x1f, MII_BCM5201_MULTIPHY);
+	data |= MII_BCM5201_MULTIPHY_SERIALMODE;
+	mii_write(gm, 0x1f, MII_BCM5201_MULTIPHY, data);
+
+	data = mii_read(gm, gm->phy_addr, MII_BCM5400_AUXCONTROL);
+	data &= ~MII_BCM5400_AUXCONTROL_PWR10BASET;
+	mii_write(gm, gm->phy_addr, MII_BCM5400_AUXCONTROL, data);
+}
+
+static int
 mii_lookup_and_reset(struct gmac *gm)
 {
-	int	i, timeout;
-	int	mii_status, mii_control;
+	int	i, mii_status, mii_control;
 
-	/* Find the PHY */
 	gm->phy_addr = -1;
 	gm->phy_type = PHY_UNKNOWN;
+
+	/* Hard reset the PHY */
+	feature_set_gmac_phy_reset(gm->of_node, KL_GPIO_ETH_PHY_RESET_ASSERT);
+	mdelay(10);
+	feature_set_gmac_phy_reset(gm->of_node, KL_GPIO_ETH_PHY_RESET_RELEASE);
+	mdelay(10);
 	
-	for(i=31; i>0; --i) {
+	/* Find the PHY */
+	for(i=0; i<32; i++) {
 		mii_control = mii_read(gm, i, MII_CR);
 		mii_status = mii_read(gm, i, MII_SR);
 		if (mii_control != -1  && mii_status != -1 &&
@@ -211,29 +328,13 @@
 			break;
 	}
 	gm->phy_addr = i;
-	if (gm->phy_addr < 0)
+	if (gm->phy_addr > 31)
 		return 0;
 
 	/* Reset it */
-	mii_write(gm, gm->phy_addr, MII_CR, mii_control | MII_CR_RST);
-	mdelay(10);
-	for (timeout = 100; timeout > 0; --timeout) {
-		mii_control = mii_read(gm, gm->phy_addr, MII_CR);
-		if (mii_control == -1) {
-			printk(KERN_ERR "%s PHY died after reset !\n",
-				gm->dev->name);
-			goto fail;
-		}
-		if ((mii_control & MII_CR_RST) == 0)
-			break;
-		mdelay(10);
-	}
-	if (mii_control & MII_CR_RST) {
-		printk(KERN_ERR "%s PHY reset timeout !\n", gm->dev->name);
+	if (mii_do_reset_phy(gm, gm->phy_addr))
 		goto fail;
-	}
-	mii_write(gm, gm->phy_addr, MII_CR, mii_control & ~MII_CR_ISOL);
-
+	
 	/* Read the PHY ID */
 	gm->phy_id = (mii_read(gm, gm->phy_addr, MII_ID0) << 16) |
 		mii_read(gm, gm->phy_addr, MII_ID1);
@@ -242,10 +343,15 @@
 #endif
 	if ((gm->phy_id & MII_BCM5400_MASK) == MII_BCM5400_ID) {
 		gm->phy_type = PHY_B5400;
-		printk(KERN_ERR "%s Warning ! Unsupported BCM5400 PHY !\n",
+		printk(KERN_ERR "%s Found Broadcom BCM5400 PHY (Gigabit)\n",
 			gm->dev->name);
+		mii_init_BCM5400(gm);		
 	} else if ((gm->phy_id & MII_BCM5201_MASK) == MII_BCM5201_ID) {
 		gm->phy_type = PHY_B5201;
+		printk(KERN_INFO "%s Found Broadcom BCM5201 PHY\n", gm->dev->name);
+	} else if ((gm->phy_id & MII_LXT971_MASK) == MII_LXT971_ID) {
+		gm->phy_type = PHY_LXT971;
+		printk(KERN_INFO "%s Found LevelOne LX971 PHY\n", gm->dev->name);
 	} else {
 		printk(KERN_ERR "%s: Warning ! Unknown PHY ID 0x%08x !\n",
 			gm->dev->name, gm->phy_id);
@@ -288,9 +394,7 @@
 gmac_set_power(struct gmac *gm, int power_up)
 {
 	if (power_up) {
-		out_le32(gm->sysregs + 0x20/4,
-			in_le32(gm->sysregs + 0x20/4) | 0x02000000);
-		udelay(20);
+		feature_set_gmac_power(gm->of_node, 1);
 		if (gm->pci_devfn != 0xff) {
 			u16 cmd;
 			
@@ -308,9 +412,7 @@
 	} else {
 		/* FIXME: Add PHY power down */
 		gm->phy_type = 0;
-		out_le32(gm->sysregs + 0x20/4,
-			in_le32(gm->sysregs + 0x20/4) & ~0x02000000);
-		udelay(20);
+		feature_set_gmac_power(gm->of_node, 0);
 	}
 }
 
@@ -357,6 +459,22 @@
 	}
 }
 
+/* Set the MAC gigabit mode. Side effect: stops Tx MAC */
+static void
+gmac_set_gigabit_mode(struct gmac *gm, int gigabit)
+{
+	/* Stop Tx MAC */
+	GM_BIC(GM_MAC_TX_CONFIG, GM_MAC_TX_CONF_ENABLE);
+	while(GM_IN(GM_MAC_TX_CONFIG) & GM_MAC_TX_CONF_ENABLE)
+		;
+	
+	if (gigabit) {
+		GM_BIS(GM_MAC_XIF_CONFIG, GM_MAC_XIF_CONF_GMII_MODE);
+	} else {
+		GM_BIC(GM_MAC_XIF_CONFIG, GM_MAC_XIF_CONF_GMII_MODE);
+	}
+}
+
 static void
 gmac_mac_init(struct gmac *gm, unsigned char *mac_addr)
 {
@@ -388,12 +506,12 @@
 		(31 << GM_GCONF_TXDMA_LIMIT_SHIFT) |
 		(31 << GM_GCONF_RXDMA_LIMIT_SHIFT));
 	GM_OUT(GM_TX_CONF,
-		GM_TX_CONF_FIFO_THR_DEFAULT << GM_TX_CONF_FIFO_THR_SHIFT |
+		(GM_TX_CONF_FIFO_THR_DEFAULT << GM_TX_CONF_FIFO_THR_SHIFT) |
 		NTX_CONF);
-/* 34 byte offset for checksum computation.  This works because ip_input() will clear out
- * the skb->csum and skb->ip_summed fields and recompute the csum if IP options are
- * present in the header.  34 == (ethernet header len) + sizeof(struct iphdr)
- */
+	/* 34 byte offset for checksum computation.  This works because ip_input() will clear out
+	 * the skb->csum and skb->ip_summed fields and recompute the csum if IP options are
+	 * present in the header.  34 == (ethernet header len) + sizeof(struct iphdr)
+	 */
 	GM_OUT(GM_RX_CONF,
 		(RX_OFFSET << GM_RX_CONF_FBYTE_OFF_SHIFT) |
 		(0x22 << GM_RX_CONF_CHK_START_SHIFT) |
@@ -703,6 +821,65 @@
 	return 0;
 }
 
+#ifdef CONFIG_PMAC_PBOOK
+int
+gmac_sleep_notify(struct pmu_sleep_notifier *self, int when)
+{
+	struct gmac *gm;
+	int i;
+	
+	/* XXX should handle more than one */
+	if (gmacs == NULL)
+		return PBOOK_SLEEP_OK;
+
+	gm = (struct gmac *) gmacs->priv;
+	if (!gm->opened)
+		return PBOOK_SLEEP_OK;
+		
+	switch (when) {
+	case PBOOK_SLEEP_REQUEST:
+		break;
+	case PBOOK_SLEEP_REJECT:
+		break;
+	case PBOOK_SLEEP_NOW:
+		disable_irq(gm->dev->irq);
+		gm->dev->tbusy = 1;
+		gmac_stop_dma(gm);
+		mii_poll_stop(gm);
+		gmac_set_power(gm, 0);
+		for (i = 0; i < NRX; ++i) {
+			if (gm->rx_buff[i] != 0) {
+				dev_kfree_skb(gm->rx_buff[i]);
+				gm->rx_buff[i] = 0;
+			}
+		}
+		for (i = 0; i < NTX; ++i) {
+			if (gm->tx_buff[i] != 0) {
+				dev_kfree_skb(gm->tx_buff[i]);
+				gm->tx_buff[i] = 0;
+			}
+		}
+		break;
+	case PBOOK_WAKE:
+		/* see if this is enough */
+		gmac_powerup_and_reset(gm->dev);
+		gm->full_duplex = 0;
+		gm->phy_status = 0;
+		mii_lookup_and_reset(gm);
+		mii_setup_phy(gm);
+		gmac_init_rings(gm, 0);
+		gmac_mac_init(gm, gm->dev->dev_addr);
+		gmac_set_multicast(gm->dev);
+		mii_interrupt(gm);
+		gmac_start_dma(gm);
+		gm->dev->tbusy = 0;
+		enable_irq(gm->dev->irq);
+		break;
+	}
+	return PBOOK_SLEEP_OK;
+}
+#endif /* CONFIG_PMAC_PBOOK */
+
 /*
  * Handle a transmit timeout
  */
@@ -712,12 +889,11 @@
 	struct gmac *gm = (struct gmac *) dev->priv;
 	int i, timeout;
 	unsigned long flags;
-
-	save_flags(flags);
-	cli();
 	
 	printk (KERN_ERR "%s: transmit timed out, resetting\n", dev->name);
 
+	spin_lock_irqsave(&gm->lock, flags);
+
 	/*
 	 * Do something useful here
 	 * 
@@ -758,7 +934,7 @@
 	/* Restart chip */
 	gmac_start_dma(gm);
 
-	restore_flags(flags);
+	spin_unlock_irqrestore(&gm->lock, flags);
 	
 	dev->tbusy = 0;
 }
@@ -770,6 +946,7 @@
 	struct gmac *gm = (struct gmac *) dev->priv;
 	volatile struct gmac_dma_desc *dp;
 	int i;
+	unsigned long flags;
 
 	/* Check tbusy bit and handle eventual transmitter timeout */
 	if(test_and_set_bit(0, (void *) &dev->tbusy) != 0) {
@@ -780,9 +957,12 @@
 		return 1;
 	}
 
+	spin_lock_irqsave(&gm->lock, flags);
+
 	i = gm->next_tx;
 	if (gm->tx_buff[i] != 0) {
 		/* buffer is full, can't send this packet at the moment */
+		spin_unlock_irqrestore(&gm->lock, flags);
 		return 1;
 	}
 	gm->next_tx = (i + 1) & (NTX - 1);
@@ -804,6 +984,8 @@
 
 	dev->tbusy = (gm->tx_buff[gm->next_tx] != 0);
 
+	spin_unlock_irqrestore(&gm->lock, flags);
+
 	return 0;
 }
 
@@ -820,9 +1002,8 @@
 	int gone, i;
 
 	i = gm->tx_gone;
-	gone = GM_IN(GM_TX_COMP);
-	
-	while (force_cleanup || i != gone) {
+	do {
+		gone = GM_IN(GM_TX_COMP);
 		skb = gm->tx_buff[i];
 		if (skb == NULL)
 			break;
@@ -837,7 +1018,7 @@
 		dev_kfree_skb(skb);
 		if (++i >= NTX)
 			i = 0;
-	}
+	} while (force_cleanup || i != gone);
 	gm->tx_gone = i;
 
 	if (!force_cleanup && dev->tbusy &&
@@ -987,15 +1168,23 @@
 	}
 	
 	if (status & GM_IRQ_MIF) {
+		spin_lock(&gm->lock);
 		mii_interrupt(gm);
+		spin_unlock(&gm->lock);
 	}
 
-	if (status & GM_IRQ_RX_DONE)
+	if (status & GM_IRQ_RX_DONE) {
+		spin_lock(&gm->lock);
 		gmac_receive(dev);
-
-	if (status & (GM_IRQ_TX_INT_ME | GM_IRQ_TX_ALL))
+		spin_unlock(&gm->lock);
+	}
+	
+	if (status & (GM_IRQ_TX_INT_ME | GM_IRQ_TX_ALL)) {
+		spin_lock(&gm->lock);
 		gmac_tx_cleanup(dev, 0);
-
+		spin_unlock(&gm->lock);
+	}
+	
 	dev->interrupt = 0;
 }
 
@@ -1076,10 +1265,12 @@
 	dev->base_addr = gmac->addrs[0].address;
 	gm->regs = (volatile unsigned int *)
 		ioremap(gmac->addrs[0].address, 0x10000);
-	gm->sysregs = (volatile unsigned int *) ioremap(0xf8000000, 0x1000);
 	dev->irq = gmac->intrs[0].line;
 	gm->dev = dev;
-
+	gm->of_node = gmac;
+	
+	spin_lock_init(&gm->lock);
+	
 	if (pci_device_loc(gmac, &gm->pci_bus, &gm->pci_devfn)) {
 		gm->pci_bus = gm->pci_devfn = 0xff;
 		printk(KERN_ERR "Can't locate GMAC PCI entry\n");
@@ -1117,6 +1308,9 @@
 
 	gmacs = dev;
 
+#ifdef CONFIG_PMAC_PBOOK
+	pmu_register_sleep_notifier(&gmac_sleep_notifier);
+#endif
 	return 0;
 }
 

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)