patch-2.2.4 linux/drivers/net/sunhme.c

Next file: linux/drivers/net/sunhme.h
Previous file: linux/drivers/net/sunbmac.h
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.2.3/linux/drivers/net/sunhme.c linux/drivers/net/sunhme.c
@@ -2,11 +2,11 @@
  *           auto carrier detecting ethernet driver.  Also known as the
  *           "Happy Meal Ethernet" found on SunSwift SBUS cards.
  *
- * Copyright (C) 1996 David S. Miller (davem@caipfs.rutgers.edu)
+ * Copyright (C) 1996, 1998 David S. Miller (davem@caipfs.rutgers.edu)
  */
 
 static char *version =
-        "sunhme.c:v1.2 10/Oct/96 David S. Miller (davem@caipfs.rutgers.edu)\n";
+        "sunhme.c:v1.10 27/Jan/99 David S. Miller (davem@caipfs.rutgers.edu)\n";
 
 #include <linux/module.h>
 
@@ -35,12 +35,13 @@
 #include <asm/openprom.h>
 #include <asm/oplib.h>
 #include <asm/auxio.h>
-#include <asm/system.h>
 #include <asm/pgtable.h>
 #include <asm/irq.h>
 #ifndef __sparc_v9__
 #include <asm/io-unit.h>
 #endif
+#include <asm/ethtool.h>
+#include <asm/uaccess.h>
 
 #include <linux/netdevice.h>
 #include <linux/etherdevice.h>
@@ -57,6 +58,11 @@
 static struct happy_meal *root_happy_dev = NULL;
 #endif
 
+static struct quattro *qfe_sbus_list = NULL;
+#ifdef CONFIG_PCI
+static struct quattro *qfe_pci_list = NULL;
+#endif
+
 #undef HMEDEBUG
 #undef SXDEBUG
 #undef RXDEBUG
@@ -363,6 +369,15 @@
 {
 	hp->sw_bmcr = happy_meal_tcvr_read(hp, tregs, DP83840_BMCR);
 
+	/* Downgrade from full to half duplex.  Only possible
+	 * via ethtool.
+	 */
+	if(hp->sw_bmcr & BMCR_FULLDPLX) {
+		hp->sw_bmcr &= ~(BMCR_FULLDPLX);
+		happy_meal_tcvr_write(hp, tregs, DP83840_BMCR, hp->sw_bmcr);
+		return 0;
+	}
+
 	/* Downgrade from 100 to 10. */
 	if(hp->sw_bmcr & BMCR_SPEED100) {
 		hp->sw_bmcr &= ~(BMCR_SPEED100);
@@ -1049,7 +1064,7 @@
 	for(i = 0; i < RX_RING_SIZE; i++) {
 		struct sk_buff *skb;
 
-		skb = happy_meal_alloc_skb(RX_BUF_ALLOC_SIZE, gfp_flags);
+		skb = happy_meal_alloc_skb(RX_BUF_ALLOC_SIZE, gfp_flags | GFP_DMA);
 		if(!skb)
 			continue;
 		hp->rx_skbs[i] = skb;
@@ -1077,7 +1092,7 @@
 		} else
 #endif
 		{
-			hb->happy_meal_rxd[i].rx_addr = (u32)((unsigned long) skb->data);
+			hb->happy_meal_rxd[i].rx_addr = sbus_dvma_addr(skb->data);
 			hb->happy_meal_rxd[i].rx_flags =
 				(RXFLAG_OWN | ((RX_BUF_ALLOC_SIZE - RX_OFFSET) << 16));
 		}
@@ -1115,7 +1130,8 @@
 #endif
 
 static void happy_meal_begin_auto_negotiation(struct happy_meal *hp,
-					      struct hmeal_tcvregs *tregs)
+					      struct hmeal_tcvregs *tregs,
+					      struct ethtool_cmd *ep)
 {
 	int timeout;
 
@@ -1124,93 +1140,108 @@
 	hp->sw_bmcr      = happy_meal_tcvr_read(hp, tregs, DP83840_BMCR);
 	hp->sw_physid1   = happy_meal_tcvr_read(hp, tregs, DP83840_PHYSID1);
 	hp->sw_physid2   = happy_meal_tcvr_read(hp, tregs, DP83840_PHYSID2);
-	hp->sw_advertise = happy_meal_tcvr_read(hp, tregs, DP83840_ADVERTISE);
 
 	/* XXX Check BMSR_ANEGCAPABLE, should not be necessary though. */
 
-	/* Advertise everything we can support. */
-	if(hp->sw_bmsr & BMSR_10HALF)
-		hp->sw_advertise |= (ADVERTISE_10HALF);
-	else
-		hp->sw_advertise &= ~(ADVERTISE_10HALF);
+	hp->sw_advertise = happy_meal_tcvr_read(hp, tregs, DP83840_ADVERTISE);
+	if(ep == NULL || ep->autoneg == AUTONEG_ENABLE) {
+		/* Advertise everything we can support. */
+		if(hp->sw_bmsr & BMSR_10HALF)
+			hp->sw_advertise |= (ADVERTISE_10HALF);
+		else
+			hp->sw_advertise &= ~(ADVERTISE_10HALF);
 
-	if(hp->sw_bmsr & BMSR_10FULL)
-		hp->sw_advertise |= (ADVERTISE_10FULL);
-	else
-		hp->sw_advertise &= ~(ADVERTISE_10FULL);
-	if(hp->sw_bmsr & BMSR_100HALF)
-		hp->sw_advertise |= (ADVERTISE_100HALF);
-	else
-		hp->sw_advertise &= ~(ADVERTISE_100HALF);
-	if(hp->sw_bmsr & BMSR_100FULL)
-		hp->sw_advertise |= (ADVERTISE_100FULL);
-	else
-		hp->sw_advertise &= ~(ADVERTISE_100FULL);
+		if(hp->sw_bmsr & BMSR_10FULL)
+			hp->sw_advertise |= (ADVERTISE_10FULL);
+		else
+			hp->sw_advertise &= ~(ADVERTISE_10FULL);
+		if(hp->sw_bmsr & BMSR_100HALF)
+			hp->sw_advertise |= (ADVERTISE_100HALF);
+		else
+			hp->sw_advertise &= ~(ADVERTISE_100HALF);
+		if(hp->sw_bmsr & BMSR_100FULL)
+			hp->sw_advertise |= (ADVERTISE_100FULL);
+		else
+			hp->sw_advertise &= ~(ADVERTISE_100FULL);
+		happy_meal_tcvr_write(hp, tregs, DP83840_ADVERTISE, hp->sw_advertise);
 
-	/* XXX Currently no Happy Meal cards I know off support 100BaseT4,
-	 * XXX and this is because the DP83840 does not support it, changes
-	 * XXX would need to be made to the tx/rx logic in the driver as well
-	 * XXX so I completely skip checking for it in the BMSR for now.
-	 */
+		/* XXX Currently no Happy Meal cards I know off support 100BaseT4,
+		 * XXX and this is because the DP83840 does not support it, changes
+		 * XXX would need to be made to the tx/rx logic in the driver as well
+		 * XXX so I completely skip checking for it in the BMSR for now.
+		 */
 
 #ifdef AUTO_SWITCH_DEBUG
-	ASD(("%s: Advertising [ ", hp->dev->name));
-	if(hp->sw_advertise & ADVERTISE_10HALF)
-		ASD(("10H "));
-	if(hp->sw_advertise & ADVERTISE_10FULL)
-		ASD(("10F "));
-	if(hp->sw_advertise & ADVERTISE_100HALF)
-		ASD(("100H "));
-	if(hp->sw_advertise & ADVERTISE_100FULL)
-		ASD(("100F "));
+		ASD(("%s: Advertising [ ", hp->dev->name));
+		if(hp->sw_advertise & ADVERTISE_10HALF)
+			ASD(("10H "));
+		if(hp->sw_advertise & ADVERTISE_10FULL)
+			ASD(("10F "));
+		if(hp->sw_advertise & ADVERTISE_100HALF)
+			ASD(("100H "));
+		if(hp->sw_advertise & ADVERTISE_100FULL)
+			ASD(("100F "));
 #endif
 
-	happy_meal_tcvr_write(hp, tregs, DP83840_ADVERTISE, hp->sw_advertise);
+		/* Enable Auto-Negotiation, this is usually on already... */
+		hp->sw_bmcr |= BMCR_ANENABLE;
+		happy_meal_tcvr_write(hp, tregs, DP83840_BMCR, hp->sw_bmcr);
 
-	/* Enable Auto-Negotiation, this is usually on already... */
-	hp->sw_bmcr |= BMCR_ANENABLE;
-	happy_meal_tcvr_write(hp, tregs, DP83840_BMCR, hp->sw_bmcr);
+		/* Restart it to make sure it is going. */
+		hp->sw_bmcr |= BMCR_ANRESTART;
+		happy_meal_tcvr_write(hp, tregs, DP83840_BMCR, hp->sw_bmcr);
 
-	/* Restart it to make sure it is going. */
-	hp->sw_bmcr |= BMCR_ANRESTART;
-	happy_meal_tcvr_write(hp, tregs, DP83840_BMCR, hp->sw_bmcr);
+		/* BMCR_ANRESTART self clears when the process has begun. */
 
-	/* BMCR_ANRESTART self clears when the process has begun. */
+		timeout = 64;  /* More than enough. */
+		while(--timeout) {
+			hp->sw_bmcr = happy_meal_tcvr_read(hp, tregs, DP83840_BMCR);
+			if(!(hp->sw_bmcr & BMCR_ANRESTART))
+				break; /* got it. */
+			udelay(10);
+		}
+		if(!timeout) {
+			printk("%s: Happy Meal would not start auto negotiation "
+			       "BMCR=0x%04x\n", hp->dev->name, hp->sw_bmcr);
+			printk("%s: Performing force link detection.\n",
+			       hp->dev->name);
+			goto force_link;
+		} else {
+			hp->timer_state = arbwait;
+		}
+	} else {
+force_link:
+		/* Force the link up, trying first a particular mode.
+		 * Either we are here at the request of ethtool or
+		 * because the Happy Meal would not start to autoneg.
+		 */
 
-	timeout = 64;  /* More than enough. */
-	while(--timeout) {
-		hp->sw_bmcr = happy_meal_tcvr_read(hp, tregs, DP83840_BMCR);
-		if(!(hp->sw_bmcr & BMCR_ANRESTART))
-			break; /* got it. */
-		udelay(10);
-	}
-	if(!timeout) {
-		printk("%s: Happy Meal would not start auto negotiation BMCR=0x%04x\n",
-		       hp->dev->name, hp->sw_bmcr);
-		printk("%s: Performing force link detection.\n", hp->dev->name);
-
-		/* Disable auto-negotiation in BMCR, enable FULL duplex and 100mb/s,
-		 * setup the timer state machine, and fire it off.
-		 *
-		 * XXX Should probably reset the DP83840 first
-		 * XXX as this is a gross fatal error...
+		/* Disable auto-negotiation in BMCR, enable the duplex and
+		 * speed setting, init the timer state machine, and fire it off.
 		 */
-		hp->sw_bmcr = BMCR_SPEED100;
+		if(ep == NULL || ep->autoneg == AUTONEG_ENABLE) {
+			hp->sw_bmcr = BMCR_SPEED100;
+		} else {
+			if(ep->speed == SPEED_100)
+				hp->sw_bmcr = BMCR_SPEED100;
+			else
+				hp->sw_bmcr = 0;
+			if(ep->duplex == DUPLEX_FULL)
+				hp->sw_bmcr |= BMCR_FULLDPLX;
+		}
 		happy_meal_tcvr_write(hp, tregs, DP83840_BMCR, hp->sw_bmcr);
 
 		/* OK, seems we need do disable the transceiver for the first
 		 * tick to make sure we get an accurate link state at the
 		 * second tick.
 		 */
-		hp->sw_csconfig = happy_meal_tcvr_read(hp, tregs, DP83840_CSCONFIG);
-		printk("%s: CSCONFIG [%04x], disabling transceiver\n", hp->dev->name,
-		       hp->sw_csconfig);
+		hp->sw_csconfig = happy_meal_tcvr_read(hp, tregs,
+						       DP83840_CSCONFIG);
 		hp->sw_csconfig &= ~(CSCONFIG_TCVDISAB);
-		happy_meal_tcvr_write(hp, tregs, DP83840_CSCONFIG, hp->sw_csconfig);
+		happy_meal_tcvr_write(hp, tregs, DP83840_CSCONFIG,
+				      hp->sw_csconfig);
 
 		hp->timer_state = ltrywait;
-	} else {
-		hp->timer_state = arbwait;
 	}
 
 	hp->timer_ticks = 0;
@@ -1220,6 +1251,9 @@
 	add_timer(&hp->happy_timer);
 }
 
+#define CRC_POLYNOMIAL_BE 0x04c11db7UL  /* Ethernet CRC, big endian */
+#define CRC_POLYNOMIAL_LE 0xedb88320UL  /* Ethernet CRC, little endian */
+
 static int happy_meal_init(struct happy_meal *hp, int from_irq)
 {
 	struct hmeal_gregs   *gregs        = hp->gregs;
@@ -1227,9 +1261,12 @@
 	struct hmeal_erxregs *erxregs      = hp->erxregs;
 	struct hmeal_bigmacregs *bregs     = hp->bigmacregs;
 	struct hmeal_tcvregs *tregs        = hp->tcvregs;
-	unsigned long regtmp;
+	unsigned long regtmp, rxcfg;
 	unsigned char *e = &hp->dev->dev_addr[0];
 
+	/* If auto-negotiation timer is running, kill it. */
+	del_timer(&hp->happy_timer);
+
 	HMD(("happy_meal_init: happy_flags[%08x] ",
 	     hp->happy_flags));
 	if(!(hp->happy_flags & HFLAG_INIT)) {
@@ -1322,12 +1359,54 @@
 	hme_write32(hp, &bregs->mac_addr1, ((e[2] << 8) | e[3]));
 	hme_write32(hp, &bregs->mac_addr0, ((e[0] << 8) | e[1]));
 
-	/* Ick, figure out how to properly program the hash table later... */
 	HMD(("htable, "));
-	hme_write32(hp, &bregs->htable3, 0);
-	hme_write32(hp, &bregs->htable2, 0);
-	hme_write32(hp, &bregs->htable1, 0);
-	hme_write32(hp, &bregs->htable0, 0);
+	if((hp->dev->flags & IFF_ALLMULTI) ||
+	   (hp->dev->mc_count > 64)) {
+		hme_write32(hp, &bregs->htable0, 0xffff);
+		hme_write32(hp, &bregs->htable1, 0xffff);
+		hme_write32(hp, &bregs->htable2, 0xffff);
+		hme_write32(hp, &bregs->htable3, 0xffff);
+	} else if((hp->dev->flags & IFF_PROMISC) == 0) {
+		u16 hash_table[4];
+		struct dev_mc_list *dmi = hp->dev->mc_list;
+		char *addrs;
+		int i, j, bit, byte;
+		u32 crc, poly = CRC_POLYNOMIAL_LE;
+
+		for(i = 0; i < 4; i++)
+			hash_table[i] = 0;
+
+		for(i = 0; i < hp->dev->mc_count; i++) {
+			addrs = dmi->dmi_addr;
+			dmi = dmi->next;
+
+			if(!(*addrs & 1))
+				continue;
+
+			crc = 0xffffffffU;
+			for(byte = 0; byte < 6; byte++) {
+				for(bit = *addrs++, j = 0; j < 8; j++, bit >>= 1) {
+					int test;
+
+					test = ((bit ^ crc) & 0x01);
+					crc >>= 1;
+					if(test)
+						crc = crc ^ poly;
+				}
+			}
+			crc >>= 26;
+			hash_table[crc >> 4] |= 1 << (crc & 0xf);
+		}
+		hme_write32(hp, &bregs->htable0, hash_table[0]);
+		hme_write32(hp, &bregs->htable1, hash_table[1]);
+		hme_write32(hp, &bregs->htable2, hash_table[2]);
+		hme_write32(hp, &bregs->htable3, hash_table[3]);
+	} else {
+		hme_write32(hp, &bregs->htable3, 0);
+		hme_write32(hp, &bregs->htable2, 0);
+		hme_write32(hp, &bregs->htable1, 0);
+		hme_write32(hp, &bregs->htable0, 0);
+	}
 
 	/* Set the RX and TX ring ptrs. */
 	HMD(("ring ptrs rxr[%08x] txr[%08x]\n",
@@ -1343,9 +1422,22 @@
 	     hme_read32(hp, &gregs->cfg)));
 
 #ifdef __sparc_v9__
+	/* XXX Can sun4d do these too? */
 	if(hp->happy_bursts & DMA_BURST64) {
+		u32 gcfg = GREG_CFG_BURST64;
+
+		/* I have no idea if I should set the extended
+		 * transfer mode bit for Cheerio, so for now I
+		 * do not.  -DaveM
+		 */
+		if((hp->happy_flags & HFLAG_PCI) == 0) {
+			mmu_set_sbus64(hp->happy_sbus_dev,
+				       hp->happy_bursts);
+			gcfg |= GREG_CFG_64BIT;
+		}
+
 		HMD(("64>"));
-		hme_write32(hp, &gregs->cfg, GREG_CFG_BURST64);
+		hme_write32(hp, &gregs->cfg, gcfg);
 	} else
 #endif
 	if(hp->happy_bursts & DMA_BURST32) {
@@ -1396,7 +1488,10 @@
 	/* Enable Big Mac hash table filter. */
 	HMD(("happy_meal_init: enable hash rx_cfg_old[%08x], ",
 	     hme_read32(hp, &bregs->rx_cfg)));
-	hme_write32(hp, &bregs->rx_cfg, BIGMAC_RXCFG_HENABLE);
+	rxcfg = BIGMAC_RXCFG_HENABLE;
+	if(hp->dev->flags & IFF_PROMISC)
+		rxcfg |= BIGMAC_RXCFG_PMISC;
+	hme_write32(hp, &bregs->rx_cfg, rxcfg);
 
 	/* Let the bits settle in the chip. */
 	udelay(10);
@@ -1433,7 +1528,7 @@
 		    hme_read32(hp, &bregs->rx_cfg) | BIGMAC_RXCFG_ENABLE);
 
 	/* Get the autonegotiation started, and the watch timer ticking. */
-	happy_meal_begin_auto_negotiation(hp, tregs);
+	happy_meal_begin_auto_negotiation(hp, tregs, NULL);
 
 	/* Success. */
 	return 0;
@@ -1505,7 +1600,7 @@
 
 	/* Only print messages for non-counter related interrupts. */
 	if(status & (GREG_STAT_RFIFOVF | GREG_STAT_STSTERR | GREG_STAT_TFIFO_UND |
-		     GREG_STAT_MAXPKTERR | GREG_STAT_NORXD | GREG_STAT_RXERR |
+		     GREG_STAT_MAXPKTERR | GREG_STAT_RXERR |
 		     GREG_STAT_RXPERR | GREG_STAT_RXTERR | GREG_STAT_EOPERR |
 		     GREG_STAT_MIFIRQ | GREG_STAT_TXEACK | GREG_STAT_TXLERR |
 		     GREG_STAT_TXPERR | GREG_STAT_TXTERR | GREG_STAT_SLVERR |
@@ -1541,13 +1636,14 @@
 	}
 
 	if(status & GREG_STAT_NORXD) {
-		/* AIEEE, out of receive descriptors.  Check out our drop
-		 * processing in happy_meal_rx to see how we try very hard
-		 * to avoid this situation.
+		/* This is harmless, it just means the system is
+		 * quite loaded and the incomming packet rate was
+		 * faster than the interrupt handler could keep up
+		 * with.
 		 */
-		printk("%s: Happy Meal out of receive descriptors, aieee!\n",
+		printk(KERN_INFO "%s: Happy Meal out of receive "
+		       "descriptors, packet dropped.\n",
 		       hp->dev->name);
-		reset = 1;
 	}
 
 	if(status & (GREG_STAT_RXERR|GREG_STAT_RXPERR|GREG_STAT_RXTERR)) {
@@ -1662,13 +1758,6 @@
 		hp->tx_skbs[elem] = NULL;
 		hp->net_stats.tx_bytes+=skb->len;
 		
-#ifdef NEED_DMA_SYNCHRONIZATION
-#ifdef CONFIG_PCI
-		if(!(hp->happy_flags & HFLAG_PCI))
-#endif
-			mmu_sync_dma(kva_to_hva(hp, skb->data),
-				     skb->len, hp->happy_sbus_dev->my_bus);
-#endif
 		dev_kfree_skb(skb);
 
 		hp->net_stats.tx_packets++;
@@ -1797,7 +1886,8 @@
 			struct sk_buff *new_skb;
 
 			/* Now refill the entry, if we can. */
-			new_skb = happy_meal_alloc_skb(RX_BUF_ALLOC_SIZE, GFP_ATOMIC);
+			new_skb = happy_meal_alloc_skb(RX_BUF_ALLOC_SIZE,
+						       (GFP_DMA|GFP_ATOMIC));
 			if(!new_skb) {
 				drops++;
 				goto drop_it;
@@ -1814,7 +1904,7 @@
 			/* Trim the original skb for the netif. */
 			skb_trim(skb, len);
 		} else {
-			struct sk_buff *copy_skb = dev_alloc_skb(len+2);
+			struct sk_buff *copy_skb = dev_alloc_skb(len + 2);
 
 			if(!copy_skb) {
 				drops++;
@@ -1908,7 +1998,8 @@
 			struct sk_buff *new_skb;
 
 			/* Now refill the entry, if we can. */
-			new_skb = happy_meal_alloc_skb(RX_BUF_ALLOC_SIZE, GFP_ATOMIC);
+			new_skb = happy_meal_alloc_skb(RX_BUF_ALLOC_SIZE,
+						       (GFP_DMA|GFP_ATOMIC));
 			if(!new_skb) {
 				drops++;
 				goto drop_it;
@@ -1925,7 +2016,7 @@
 			/* Trim the original skb for the netif. */
 			skb_trim(skb, len);
 		} else {
-			struct sk_buff *copy_skb = dev_alloc_skb(len+2);
+			struct sk_buff *copy_skb = dev_alloc_skb(len + 2);
 
 			if(!copy_skb) {
 				drops++;
@@ -2083,7 +2174,8 @@
 			struct sk_buff *new_skb;
 
 			/* Now refill the entry, if we can. */
-			new_skb = happy_meal_alloc_skb(RX_BUF_ALLOC_SIZE, GFP_ATOMIC);
+			new_skb = happy_meal_alloc_skb(RX_BUF_ALLOC_SIZE,
+						       (GFP_DMA | GFP_ATOMIC));
 			if(!new_skb) {
 				drops++;
 				goto drop_it;
@@ -2103,7 +2195,7 @@
 			/* Trim the original skb for the netif. */
 			skb_trim(skb, len);
 		} else {
-			struct sk_buff *copy_skb = dev_alloc_skb(len+2);
+			struct sk_buff *copy_skb = dev_alloc_skb(len + 2);
 
 			if(!copy_skb) {
 				drops++;
@@ -2328,12 +2420,39 @@
 }
 #endif
 
+static void quattro_sbus_interrupt(int irq, void *cookie, struct pt_regs *ptregs)
+{
+	struct quattro *qp = (struct quattro *)cookie;
+	int i;
+
+	for(i = 0; i < 4; i++) {
+		struct device *hdev = qp->happy_meals[i];
+		struct happy_meal *hp = (struct happy_meal *) hdev->priv;
+		volatile u32 *sreg = qp->irq_status[i];
+
+		if(sreg &&
+		   (hme_read32(hp, sreg) & (GREG_STAT_ERRORS |
+					    GREG_STAT_MIFIRQ |
+					    GREG_STAT_TXALL  |
+					    GREG_STAT_RXTOHOST)) != 0)
+			qp->handler(irq, hdev, ptregs);
+	}
+}
+
 static int happy_meal_open(struct device *dev)
 {
 	struct happy_meal *hp = (struct happy_meal *) dev->priv;
 	int res;
 
 	HMD(("happy_meal_open: "));
+
+	/* On SBUS Quattro QFE cards, all hme interrupts are concentrated
+	 * into a single source which we register handling at probe time.
+	 */
+	if((hp->happy_flags & (HFLAG_QUATTRO|HFLAG_PCI)) == HFLAG_QUATTRO) {
+		hp->qfe_parent->irq_status[hp->qfe_ent] = &hp->gregs->stat;
+		goto after_request_irq;
+	}
 #ifndef __sparc_v9__
 	if(sparc_cpu_model == sun4c) {
 		if(request_irq(dev->irq, &sun4c_happy_meal_interrupt,
@@ -2370,8 +2489,8 @@
 		       __irq_itoa(dev->irq));
 		return -EAGAIN;
 	}
-	HMD(("Init happy timer\n"));
-	init_timer(&hp->happy_timer);
+
+after_request_irq:
 	HMD(("to happy_meal_init\n"));
 	res = happy_meal_init(hp, 0);
 	if(!res) {
@@ -2390,7 +2509,17 @@
 	/* If auto-negotiation timer is running, kill it. */
 	del_timer(&hp->happy_timer);
 
-	free_irq(dev->irq, (void *)dev);
+	/* On Quattro QFE cards, all hme interrupts are concentrated
+	 * into a single source which we register handling at probe
+	 * time and never unregister.
+	 */
+	if((hp->happy_flags & (HFLAG_QUATTRO|HFLAG_PCI)) != HFLAG_QUATTRO) {
+		free_irq(dev->irq, (void *)dev);
+	} else {
+		/* Zap the status register pointer. */
+		hp->qfe_parent->irq_status[hp->qfe_ent] = NULL;
+	}
+
 	MOD_DEC_USE_COUNT;
 	return 0;
 }
@@ -2429,10 +2558,23 @@
 		tx_add_log(hp, TXLOG_ACTION_TXMIT|TXLOG_ACTION_NBUFS, 0);
 		return 1;
 	}
+#ifdef __sparc_v9__
+	if ((unsigned long)(skb->data + skb->len) >= MAX_DMA_ADDRESS) {
+		struct sk_buff *new_skb = skb_copy(skb, GFP_DMA | GFP_ATOMIC);
+		if (!new_skb)
+			return 1;
+		dev_kfree_skb(skb);
+		skb = new_skb;
+	}
+#endif
 	len = skb->len;
 	entry = hp->tx_new;
 
 	SXD(("SX<l[%d]e[%d]>", len, entry));
+#ifdef NEED_DMA_SYNCHRONIZATION
+	mmu_sync_dma(kva_to_hva(hp, skb->data),
+		     skb->len, hp->happy_sbus_dev->my_bus);
+#endif
 	hp->tx_skbs[entry] = skb;
 	hp->happy_block->happy_meal_txd[entry].tx_addr = kva_to_hva(hp, skb->data);
 	hp->happy_block->happy_meal_txd[entry].tx_flags =
@@ -2622,9 +2764,6 @@
 	return &hp->net_stats;
 }
 
-#define CRC_POLYNOMIAL_BE 0x04c11db7UL  /* Ethernet CRC, big endian */
-#define CRC_POLYNOMIAL_LE 0xedb88320UL  /* Ethernet CRC, little endian */
-
 static void happy_meal_set_multicast(struct device *dev)
 {
 	struct happy_meal *hp = (struct happy_meal *) dev->priv;
@@ -2634,10 +2773,6 @@
 	int i, j, bit, byte;
 	u32 crc, poly = CRC_POLYNOMIAL_LE;
 
-	/* Let the transmits drain. */
-	while(dev->tbusy)
-		schedule();
-
 	/* Lock out others. */
 	set_bit(0, (void *) &dev->tbusy);
 
@@ -2686,13 +2821,262 @@
 	dev->tbusy = 0;
 }
 
+/* Ethtool support... */
+static int happy_meal_ioctl(struct device *dev,
+			    struct ifreq *rq, int cmd)
+{
+	struct happy_meal *hp = (struct happy_meal *) dev->priv;
+	struct ethtool_cmd *ep_user = (struct ethtool_cmd *) rq->ifr_data;
+	struct ethtool_cmd ecmd;
+
+	if(cmd != SIOCETHTOOL)
+		return -EOPNOTSUPP;
+	if(copy_from_user(&ecmd, ep_user, sizeof(ecmd)))
+		return -EFAULT;
+
+	if(ecmd.cmd == SPARC_ETH_GSET) {
+		ecmd.supported =
+			(SUPPORTED_10baseT_Half | SUPPORTED_10baseT_Full |
+			 SUPPORTED_100baseT_Half | SUPPORTED_100baseT_Full |
+			 SUPPORTED_Autoneg | SUPPORTED_TP | SUPPORTED_MII);
+
+		/* XXX hardcoded stuff for now */
+		ecmd.port = PORT_TP; /* XXX no MII support */
+		ecmd.transceiver = XCVR_INTERNAL; /* XXX no external xcvr support */
+		ecmd.phy_address = 0; /* XXX fixed PHYAD */
+
+		/* Record PHY settings. */
+		hp->sw_bmcr = happy_meal_tcvr_read(hp, hp->tcvregs, DP83840_BMCR);
+		hp->sw_lpa = happy_meal_tcvr_read(hp, hp->tcvregs, DP83840_LPA);
+		if(hp->sw_bmcr & BMCR_ANENABLE) {
+			ecmd.autoneg = AUTONEG_ENABLE;
+			ecmd.speed =
+				(hp->sw_lpa & (LPA_100HALF | LPA_100FULL)) ?
+				SPEED_100 : SPEED_10;
+			if(ecmd.speed == SPEED_100)
+				ecmd.duplex =
+					(hp->sw_lpa & (LPA_100FULL)) ?
+					DUPLEX_FULL : DUPLEX_HALF;
+			else
+				ecmd.duplex =
+					(hp->sw_lpa & (LPA_10FULL)) ?
+					DUPLEX_FULL : DUPLEX_HALF;
+		} else {
+			ecmd.autoneg = AUTONEG_DISABLE;
+			ecmd.speed =
+				(hp->sw_bmcr & BMCR_SPEED100) ?
+				SPEED_100 : SPEED_10;
+			ecmd.duplex =
+				(hp->sw_bmcr & BMCR_FULLDPLX) ?
+				DUPLEX_FULL : DUPLEX_HALF;
+		}
+		if(copy_to_user(ep_user, &ecmd, sizeof(ecmd)))
+			return -EFAULT;
+		return 0;
+	} else if(ecmd.cmd == SPARC_ETH_SSET) {
+		if(!capable(CAP_NET_ADMIN))
+			return -EPERM;
+
+		/* Verify the settings we care about. */
+		if(ecmd.autoneg != AUTONEG_ENABLE &&
+		   ecmd.autoneg != AUTONEG_DISABLE)
+			return -EINVAL;
+		if(ecmd.autoneg == AUTONEG_DISABLE &&
+		   ((ecmd.speed != SPEED_100 &&
+		     ecmd.speed != SPEED_10) ||
+		    (ecmd.duplex != DUPLEX_HALF &&
+		     ecmd.duplex != DUPLEX_FULL)))
+			return -EINVAL;
+
+		/* Ok, do it to it. */
+		del_timer(&hp->happy_timer);
+		happy_meal_begin_auto_negotiation(hp,
+						  hp->tcvregs,
+						  &ecmd);
+
+		return 0;
+	} else
+		return -EOPNOTSUPP;
+}
+
+void __init quattro_get_ranges(struct quattro *qp)
+{
+	int err;
+
+	err = prom_getproperty(qp->quattro_sbus_dev->prom_node,
+			       "ranges",
+			       (char *)&qp->ranges[0],
+			       sizeof(qp->ranges));
+	if(err == 0 || err == -1) {
+		qp->nranges = 0;
+		return;
+	}
+	qp->nranges = (err / sizeof(struct linux_prom_ranges));
+}
+
+static void __init quattro_apply_ranges(struct quattro *qp, struct happy_meal *hp)
+{
+	struct linux_sbus_device *sdev = hp->happy_sbus_dev;
+	int rng;
+
+	for(rng = 0; rng < qp->nranges; rng++) {
+		struct linux_prom_ranges *rngp = &qp->ranges[rng];
+		int reg;
+
+		for(reg = 0; reg < 5; reg++) {
+			if(sdev->reg_addrs[reg].which_io ==
+			   rngp->ot_child_space)
+				break;
+		}
+		if(reg == 5)
+			continue;
+
+		sdev->reg_addrs[reg].which_io = rngp->ot_parent_space;
+		sdev->reg_addrs[reg].phys_addr += rngp->ot_parent_base;
+	}
+}
+
+/* Given a happy meal sbus device, find it's quattro parent.
+ * If none exist, allocate and return a new one.
+ *
+ * Return NULL on failure.
+ */
+static struct quattro * __init quattro_sbus_find(struct linux_sbus_device *goal_sdev)
+{
+	struct linux_sbus *sbus;
+	struct linux_sbus_device *sdev;
+	struct quattro *qp;
+
+	for(qp = qfe_sbus_list; qp != NULL; qp = qp->next) {
+		for(sdev = qp->quattro_sbus_dev->child;
+		    sdev != NULL;
+		    sdev = sdev->next) {
+			if(sdev == goal_sdev)
+				return qp;
+		}
+	}
+	for_each_sbus(sbus) {
+		for_each_sbusdev(sdev, sbus) {
+			if(sdev->child != NULL) {
+				struct linux_sbus_device *p;
+
+				for(p = sdev->child; p != NULL; p = p->next)
+					if(p == goal_sdev)
+						goto found;
+			}
+		}
+	}
+
+	/* Cannot find quattro parent, fail. */
+	return NULL;
+
+found:
+	qp = kmalloc(sizeof(struct quattro), GFP_KERNEL);
+	if(qp != NULL) {
+		int i;
+
+		for(i = 0; i < 4; i++) {
+			qp->irq_status[i] = NULL;
+			qp->happy_meals[i] = NULL;
+		}
+		qp->handler = NULL;
+		qp->quattro_sbus_dev = sdev;
+#ifdef CONFIG_PCI
+		qp->quattro_pci_dev = NULL;
+#endif
+		qp->next = qfe_sbus_list;
+		qfe_sbus_list = qp;
+		quattro_get_ranges(qp);
+	}
+	return qp;
+}
+
+#ifdef CONFIG_PCI
+static struct quattro * __init quattro_pci_find(struct pci_dev *pdev)
+{
+	struct pci_dev *bdev = pdev->bus->self;
+	struct quattro *qp;
+
+	if (!bdev) return NULL;
+	for(qp = qfe_pci_list; qp != NULL; qp = qp->next) {
+		if(qp->quattro_pci_dev == bdev)
+			return qp;
+	}
+	qp = kmalloc(sizeof(struct quattro), GFP_KERNEL);
+	if(qp != NULL) {
+		int i;
+
+		for(i = 0; i < 4; i++) {
+			qp->irq_status[i] = NULL;
+			qp->happy_meals[i] = NULL;
+		}
+		qp->handler = NULL;
+		qp->quattro_sbus_dev = NULL;
+		qp->quattro_pci_dev = bdev;
+		qp->next = qfe_pci_list;
+		qfe_pci_list = qp;
+
+		/* No range tricks necessary on PCI. */
+		qp->nranges = 0;
+	}
+	return qp;
+}
+#endif
+
+/* After all quattro cards have been probed, we call these functions
+ * to register the IRQ handlers.
+ */
+static void __init quattro_sbus_register_irqs(void)
+{
+	struct quattro *qp;
+
+	for(qp = qfe_sbus_list; qp != NULL; qp = qp->next) {
+		int err;
+
+		/* Set the handler. */
+#ifndef __sparc_v9__
+		if(sparc_cpu_model == sun4c)
+			qp->handler = sun4c_happy_meal_interrupt;
+		else if(sparc_cpu_model == sun4c)
+			qp->handler = sun4d_happy_meal_interrupt;
+		else
+#endif
+#ifdef CONFIG_PCI
+		if(qp->quattro_pci_dev != NULL)
+			panic("QFE: PCI qfe in sbus_register_irqs!");
+		else
+#endif
+			qp->handler = happy_meal_interrupt;
+
+		err = request_irq(qp->quattro_sbus_dev->irqs[0],
+				  quattro_sbus_interrupt,
+				  SA_SHIRQ, "Quattro",
+				  qp);
+		if(err != 0) {
+			printk("Quattro: Fatal IRQ registery error %d.\n", err);
+			panic("QFE request irq");
+		}
+	}
+}
+
 static unsigned hme_version_printed = 0;
 
-static inline int happy_meal_ether_init(struct device *dev, struct linux_sbus_device *sdev)
+static int __init happy_meal_ether_init(struct device *dev, struct linux_sbus_device *sdev, int is_qfe)
 {
+	struct quattro *qp = NULL;
 	struct happy_meal *hp;
-	int i;
+	int i, qfe_slot = -1;
 
+	if(is_qfe) {
+		qp = quattro_sbus_find(sdev);
+		if(qp == NULL)
+			return ENODEV;
+		for(qfe_slot = 0; qfe_slot < 4; qfe_slot++)
+			if(qp->happy_meals[qfe_slot] == NULL)
+				break;
+		if(qfe_slot == 4)
+			return ENODEV;
+	}
 	if(dev == NULL) {
 		dev = init_etherdev(0, sizeof(struct happy_meal));
 	} else {
@@ -2703,9 +3087,16 @@
 	if(hme_version_printed++ == 0)
 		printk(version);
 
-	printk("%s: HAPPY MEAL (SBUS) 10/100baseT Ethernet ", dev->name);
+	if(qfe_slot != -1)
+		printk("%s: Quattro HME slot %d (SBUS) 10/100baseT Ethernet",
+		       dev->name, qfe_slot);
+	else
+		printk("%s: HAPPY MEAL (SBUS) 10/100baseT Ethernet ",
+		       dev->name);
 
 	dev->base_addr = (long) sdev;
+
+	/* XXX Check for local-mac-address property on Quattro... -DaveM */
 	for(i = 0; i < 6; i++)
 		printk("%2.2x%c",
 		       dev->dev_addr[i] = idprom->id_ethaddr[i],
@@ -2727,6 +3118,13 @@
 		return ENODEV;
 	}
 
+	if(qp != NULL) {
+		hp->qfe_parent = qp;
+		hp->qfe_ent = qfe_slot;
+		qp->happy_meals[qfe_slot] = dev;
+		quattro_apply_ranges(qp, hp);
+	}
+
 	prom_apply_sbus_ranges(sdev->my_bus, &sdev->reg_addrs[0],
 			       sdev->num_registers, sdev);
 	hp->gregs = sparc_alloc_io(sdev->reg_addrs[0].phys_addr, 0,
@@ -2784,6 +3182,9 @@
 	else if(hp->hm_revision != 0xa0)
 		hp->happy_flags = HFLAG_NOT_A0;
 
+	if(qp != NULL)
+		hp->happy_flags |= HFLAG_QUATTRO;
+
 	/* Get the supported DVMA burst sizes from our Happy SBUS. */
 	hp->happy_bursts = prom_getintdefault(hp->happy_sbus_dev->my_bus->prom_node,
 					      "burst-sizes", 0x00);
@@ -2817,6 +3218,8 @@
 	 */
 	happy_meal_set_initial_advertisement(hp);
 
+	init_timer(&hp->happy_timer);
+
 	hp->dev = dev;
 	dev->open = &happy_meal_open;
 	dev->stop = &happy_meal_close;
@@ -2830,6 +3233,7 @@
 		dev->hard_start_xmit = &happy_meal_start_xmit;
 	dev->get_stats = &happy_meal_get_stats;
 	dev->set_multicast_list = &happy_meal_set_multicast;
+	dev->do_ioctl = &happy_meal_ioctl;
 
 	dev->irq = sdev->irqs[0];
 	dev->dma = 0;
@@ -2846,14 +3250,35 @@
 }
 
 #ifdef CONFIG_PCI
-__initfunc(int happy_meal_pci_init(struct device *dev, struct pci_dev *pdev))
+static int __init happy_meal_pci_init(struct device *dev, struct pci_dev *pdev)
 {
+	struct quattro *qp = NULL;
 	struct pcidev_cookie *pcp;
 	struct happy_meal *hp;
 	unsigned long hpreg_base;
 	unsigned short pci_command;
-	int i, node;
+	int i, node, qfe_slot = -1;
+	char prom_name[64];
 
+	/* Now make sure pci_dev cookie is there. */
+	pcp = pdev->sysdata;
+	if(pcp == NULL || pcp->prom_node == -1) {
+		printk("happymeal(PCI): Some PCI device info missing\n");
+		return ENODEV;
+	}
+	node = pcp->prom_node;
+	
+	prom_getstring(node, "name", prom_name, sizeof(prom_name));
+	if (!strcmp(prom_name, "SUNW,qfe") || !strcmp(prom_name, "qfe")) {
+		qp = quattro_pci_find(pdev);
+		if(qp == NULL)
+			return ENODEV;
+		for(qfe_slot = 0; qfe_slot < 4; qfe_slot++)
+			if(qp->happy_meals[qfe_slot] == NULL)
+				break;
+		if(qfe_slot == 4)
+			return ENODEV;
+	}
 	if(dev == NULL) {
 		dev = init_etherdev(0, sizeof(struct happy_meal));
 	} else {
@@ -2864,15 +3289,28 @@
 	if(hme_version_printed++ == 0)
 		printk(version);
 
-	printk("%s: HAPPY MEAL (PCI/CheerIO) 10/100BaseT Ethernet ", dev->name);
+	if (!qfe_slot) {
+		prom_name[0] = 0;
+		if (!strncmp(dev->name, "eth", 3)) {
+			int i = simple_strtoul(dev->name + 3, NULL, 10);
+			sprintf(prom_name, "-%d", i + 3);
+		}
+		printk("%s%s: Quattro HME (PCI/CheerIO) 10/100baseT Ethernet ", dev->name, prom_name);
+		if (qp->quattro_pci_dev->vendor == PCI_VENDOR_ID_DEC &&
+		    qp->quattro_pci_dev->device == PCI_DEVICE_ID_DEC_21153)
+			printk("DEC 21153 PCI Bridge\n");
+		else
+			printk("unknown bridge %04x.%04x\n", 
+				qp->quattro_pci_dev->vendor, qp->quattro_pci_dev->device);
+	}
+	if(qfe_slot != -1)
+		printk("%s: Quattro HME slot %d (PCI/CheerIO) 10/100baseT Ethernet ",
+		       dev->name, qfe_slot);
+	else
+		printk("%s: HAPPY MEAL (PCI/CheerIO) 10/100BaseT Ethernet ",
+		       dev->name);
 
 	dev->base_addr = (long) pdev;
-	for(i = 0; i < 6; i++)
-		printk("%2.2x%c",
-		       dev->dev_addr[i] = idprom->id_ethaddr[i],
-		       i == 5 ? ' ' : ':');
-
-	printk("\n");
 
 	hp = (struct happy_meal *)dev->priv;
 	memset(hp, 0, sizeof(*hp));
@@ -2880,6 +3318,12 @@
 	hp->happy_sbus_dev = NULL;
 	hp->happy_pci_dev = pdev;
 
+	if(qp != NULL) {
+		hp->qfe_parent = qp;
+		hp->qfe_ent = qfe_slot;
+		qp->happy_meals[qfe_slot] = dev;
+	}		
+
 	hpreg_base = pdev->base_address[0];
 	if((hpreg_base & PCI_BASE_ADDRESS_SPACE) != PCI_BASE_ADDRESS_SPACE_MEMORY) {
 		printk("happymeal(PCI): Cannot find proper PCI device base address.\n");
@@ -2887,13 +3331,14 @@
 	}
 	hpreg_base &= PCI_BASE_ADDRESS_MEM_MASK;
 
-	/* Now make sure pci_dev cookie is there. */
-	pcp = pdev->sysdata;
-	if(pcp == NULL || pcp->prom_node == -1) {
-		printk("happymeal(PCI): Some PCI device info missing\n");
-		return ENODEV;
-	}
-	node = pcp->prom_node;
+	if (qfe_slot != -1 && prom_getproplen(node, "local-mac-address") == 6)
+		prom_getproperty(node, "local-mac-address", dev->dev_addr, 6);
+	else
+		memcpy(dev->dev_addr, idprom->id_ethaddr, 6);
+	for(i = 0; i < 6; i++)
+		printk("%2.2x%c", dev->dev_addr[i], i == 5 ? ' ' : ':');
+
+	printk("\n");
 
 	/* Layout registers. */
 	hp->gregs      = (struct hmeal_gregs *)		(hpreg_base + 0x0000);
@@ -2912,6 +3357,9 @@
 	else if(hp->hm_revision != 0xa0)
 		hp->happy_flags = HFLAG_NOT_A0;
 
+	if(qp != NULL)
+		hp->happy_flags |= HFLAG_QUATTRO;
+
 	/* And of course, indicate this is PCI. */
 	hp->happy_flags |= HFLAG_PCI;
 
@@ -2937,29 +3385,32 @@
 	hp->timer_ticks = 0;
 	happy_meal_set_initial_advertisement(hp);
 
+	init_timer(&hp->happy_timer);
+
 	hp->dev = dev;
 	dev->open = &happy_meal_open;
 	dev->stop = &happy_meal_close;
 	dev->hard_start_xmit = &pci_happy_meal_start_xmit;
 	dev->get_stats = &happy_meal_get_stats;
 	dev->set_multicast_list = &happy_meal_set_multicast;
+	dev->do_ioctl = &happy_meal_ioctl;
 	dev->irq = pdev->irq;
 	dev->dma = 0;
 	ether_setup(dev);
 
 	/* If we don't do this, nothing works. */
-	pcibios_read_config_word(pdev->bus->number,
-				 pdev->devfn,
-				 PCI_COMMAND, &pci_command);
+	pci_read_config_word(pdev, PCI_COMMAND, &pci_command);
 	pci_command |= PCI_COMMAND_MASTER;
-	pcibios_write_config_word(pdev->bus->number,
-				  pdev->devfn,
-				  PCI_COMMAND, pci_command);
-
-	/* Set the latency timer as well, PROM leaves it at zero. */
-	pcibios_write_config_byte(pdev->bus->number,
-				  pdev->devfn,
-				  PCI_LATENCY_TIMER, 240);
+	pci_write_config_word(pdev, PCI_COMMAND, pci_command);
+
+	/* Set the latency timer and cache line size as well,
+	 * PROM leaves it at zero.
+	 */
+	pci_write_config_byte(pdev, PCI_LATENCY_TIMER, 128);
+#ifdef __sparc_v9__
+	/* NOTE: Cache line size is in 32-bit word units. */
+	pci_write_config_byte(pdev, PCI_CACHE_LINE_SIZE, 0x10);
+#endif
 
 #ifdef MODULE
 	/* We are home free at this point, link us in to the happy
@@ -2973,7 +3424,7 @@
 }
 #endif
 
-__initfunc(int happy_meal_probe(struct device *dev))
+int __init happy_meal_probe(struct device *dev)
 {
 	struct linux_sbus *bus;
 	struct linux_sbus_device *sdev = 0;
@@ -2990,11 +3441,18 @@
 				dev = NULL;
 			if(!strcmp(sdev->prom_name, "SUNW,hme")) {
 				cards++;
-				if((v = happy_meal_ether_init(dev, sdev)))
+				if((v = happy_meal_ether_init(dev, sdev, 0)))
+					return v;
+			} else if(!strcmp(sdev->prom_name, "qfe") ||
+				  !strcmp(sdev->prom_name, "SUNW,qfe")) {
+				cards++;
+				if((v = happy_meal_ether_init(dev, sdev, 1)))
 					return v;
 			}
 		}
 	}
+	if(cards != 0)
+		quattro_sbus_register_irqs();
 #ifdef CONFIG_PCI
 	if(pci_present()) {
 		struct pci_dev *pdev;

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