diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /drivers/net/sunhme.c |
Linux-2.6.12-rc2v2.6.12-rc2
Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.
Let it rip!
Diffstat (limited to 'drivers/net/sunhme.c')
-rw-r--r-- | drivers/net/sunhme.c | 3426 |
1 files changed, 3426 insertions, 0 deletions
diff --git a/drivers/net/sunhme.c b/drivers/net/sunhme.c new file mode 100644 index 00000000000..d837b3c3572 --- /dev/null +++ b/drivers/net/sunhme.c @@ -0,0 +1,3426 @@ +/* $Id: sunhme.c,v 1.124 2002/01/15 06:25:51 davem Exp $ + * sunhme.c: Sparc HME/BigMac 10/100baseT half/full duplex auto switching, + * auto carrier detecting ethernet driver. Also known as the + * "Happy Meal Ethernet" found on SunSwift SBUS cards. + * + * Copyright (C) 1996, 1998, 1999, 2002, 2003 David S. Miller (davem@redhat.com) + * + * Changes : + * 2000/11/11 Willy Tarreau <willy AT meta-x.org> + * - port to non-sparc architectures. Tested only on x86 and + * only currently works with QFE PCI cards. + * - ability to specify the MAC address at module load time by passing this + * argument : macaddr=0x00,0x10,0x20,0x30,0x40,0x50 + */ + +static char version[] = + "sunhme.c:v2.02 24/Aug/2003 David S. Miller (davem@redhat.com)\n"; + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/fcntl.h> +#include <linux/interrupt.h> +#include <linux/ioport.h> +#include <linux/in.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/ethtool.h> +#include <linux/mii.h> +#include <linux/crc32.h> +#include <linux/random.h> +#include <linux/errno.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> +#include <linux/bitops.h> + +#include <asm/system.h> +#include <asm/io.h> +#include <asm/dma.h> +#include <asm/byteorder.h> + +#ifdef __sparc__ +#include <asm/idprom.h> +#include <asm/sbus.h> +#include <asm/openprom.h> +#include <asm/oplib.h> +#include <asm/auxio.h> +#ifndef __sparc_v9__ +#include <asm/io-unit.h> +#endif +#endif +#include <asm/uaccess.h> + +#include <asm/pgtable.h> +#include <asm/irq.h> + +#ifdef CONFIG_PCI +#include <linux/pci.h> +#ifdef __sparc__ +#include <asm/pbm.h> +#endif +#endif + +#include "sunhme.h" + + +#define DRV_NAME "sunhme" + +static int macaddr[6]; + +/* accept MAC address of the form macaddr=0x08,0x00,0x20,0x30,0x40,0x50 */ +module_param_array(macaddr, int, NULL, 0); +MODULE_PARM_DESC(macaddr, "Happy Meal MAC address to set"); +MODULE_LICENSE("GPL"); + +static struct happy_meal *root_happy_dev; + +#ifdef CONFIG_SBUS +static struct quattro *qfe_sbus_list; +#endif + +#ifdef CONFIG_PCI +static struct quattro *qfe_pci_list; +#endif + +#undef HMEDEBUG +#undef SXDEBUG +#undef RXDEBUG +#undef TXDEBUG +#undef TXLOGGING + +#ifdef TXLOGGING +struct hme_tx_logent { + unsigned int tstamp; + int tx_new, tx_old; + unsigned int action; +#define TXLOG_ACTION_IRQ 0x01 +#define TXLOG_ACTION_TXMIT 0x02 +#define TXLOG_ACTION_TBUSY 0x04 +#define TXLOG_ACTION_NBUFS 0x08 + unsigned int status; +}; +#define TX_LOG_LEN 128 +static struct hme_tx_logent tx_log[TX_LOG_LEN]; +static int txlog_cur_entry; +static __inline__ void tx_add_log(struct happy_meal *hp, unsigned int a, unsigned int s) +{ + struct hme_tx_logent *tlp; + unsigned long flags; + + save_and_cli(flags); + tlp = &tx_log[txlog_cur_entry]; + tlp->tstamp = (unsigned int)jiffies; + tlp->tx_new = hp->tx_new; + tlp->tx_old = hp->tx_old; + tlp->action = a; + tlp->status = s; + txlog_cur_entry = (txlog_cur_entry + 1) & (TX_LOG_LEN - 1); + restore_flags(flags); +} +static __inline__ void tx_dump_log(void) +{ + int i, this; + + this = txlog_cur_entry; + for (i = 0; i < TX_LOG_LEN; i++) { + printk("TXLOG[%d]: j[%08x] tx[N(%d)O(%d)] action[%08x] stat[%08x]\n", i, + tx_log[this].tstamp, + tx_log[this].tx_new, tx_log[this].tx_old, + tx_log[this].action, tx_log[this].status); + this = (this + 1) & (TX_LOG_LEN - 1); + } +} +static __inline__ void tx_dump_ring(struct happy_meal *hp) +{ + struct hmeal_init_block *hb = hp->happy_block; + struct happy_meal_txd *tp = &hb->happy_meal_txd[0]; + int i; + + for (i = 0; i < TX_RING_SIZE; i+=4) { + printk("TXD[%d..%d]: [%08x:%08x] [%08x:%08x] [%08x:%08x] [%08x:%08x]\n", + i, i + 4, + le32_to_cpu(tp[i].tx_flags), le32_to_cpu(tp[i].tx_addr), + le32_to_cpu(tp[i + 1].tx_flags), le32_to_cpu(tp[i + 1].tx_addr), + le32_to_cpu(tp[i + 2].tx_flags), le32_to_cpu(tp[i + 2].tx_addr), + le32_to_cpu(tp[i + 3].tx_flags), le32_to_cpu(tp[i + 3].tx_addr)); + } +} +#else +#define tx_add_log(hp, a, s) do { } while(0) +#define tx_dump_log() do { } while(0) +#define tx_dump_ring(hp) do { } while(0) +#endif + +#ifdef HMEDEBUG +#define HMD(x) printk x +#else +#define HMD(x) +#endif + +/* #define AUTO_SWITCH_DEBUG */ + +#ifdef AUTO_SWITCH_DEBUG +#define ASD(x) printk x +#else +#define ASD(x) +#endif + +#define DEFAULT_IPG0 16 /* For lance-mode only */ +#define DEFAULT_IPG1 8 /* For all modes */ +#define DEFAULT_IPG2 4 /* For all modes */ +#define DEFAULT_JAMSIZE 4 /* Toe jam */ + +#if defined(CONFIG_PCI) && defined(MODULE) +/* This happy_pci_ids is declared __initdata because it is only used + as an advisory to depmod. If this is ported to the new PCI interface + where it could be referenced at any time due to hot plugging, + the __initdata reference should be removed. */ + +static struct pci_device_id happymeal_pci_ids[] = { + { + .vendor = PCI_VENDOR_ID_SUN, + .device = PCI_DEVICE_ID_SUN_HAPPYMEAL, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + }, + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(pci, happymeal_pci_ids); + +#endif + +/* NOTE: In the descriptor writes one _must_ write the address + * member _first_. The card must not be allowed to see + * the updated descriptor flags until the address is + * correct. I've added a write memory barrier between + * the two stores so that I can sleep well at night... -DaveM + */ + +#if defined(CONFIG_SBUS) && defined(CONFIG_PCI) +static void sbus_hme_write32(void __iomem *reg, u32 val) +{ + sbus_writel(val, reg); +} + +static u32 sbus_hme_read32(void __iomem *reg) +{ + return sbus_readl(reg); +} + +static void sbus_hme_write_rxd(struct happy_meal_rxd *rxd, u32 flags, u32 addr) +{ + rxd->rx_addr = addr; + wmb(); + rxd->rx_flags = flags; +} + +static void sbus_hme_write_txd(struct happy_meal_txd *txd, u32 flags, u32 addr) +{ + txd->tx_addr = addr; + wmb(); + txd->tx_flags = flags; +} + +static u32 sbus_hme_read_desc32(u32 *p) +{ + return *p; +} + +static void pci_hme_write32(void __iomem *reg, u32 val) +{ + writel(val, reg); +} + +static u32 pci_hme_read32(void __iomem *reg) +{ + return readl(reg); +} + +static void pci_hme_write_rxd(struct happy_meal_rxd *rxd, u32 flags, u32 addr) +{ + rxd->rx_addr = cpu_to_le32(addr); + wmb(); + rxd->rx_flags = cpu_to_le32(flags); +} + +static void pci_hme_write_txd(struct happy_meal_txd *txd, u32 flags, u32 addr) +{ + txd->tx_addr = cpu_to_le32(addr); + wmb(); + txd->tx_flags = cpu_to_le32(flags); +} + +static u32 pci_hme_read_desc32(u32 *p) +{ + return cpu_to_le32p(p); +} + +#define hme_write32(__hp, __reg, __val) \ + ((__hp)->write32((__reg), (__val))) +#define hme_read32(__hp, __reg) \ + ((__hp)->read32(__reg)) +#define hme_write_rxd(__hp, __rxd, __flags, __addr) \ + ((__hp)->write_rxd((__rxd), (__flags), (__addr))) +#define hme_write_txd(__hp, __txd, __flags, __addr) \ + ((__hp)->write_txd((__txd), (__flags), (__addr))) +#define hme_read_desc32(__hp, __p) \ + ((__hp)->read_desc32(__p)) +#define hme_dma_map(__hp, __ptr, __size, __dir) \ + ((__hp)->dma_map((__hp)->happy_dev, (__ptr), (__size), (__dir))) +#define hme_dma_unmap(__hp, __addr, __size, __dir) \ + ((__hp)->dma_unmap((__hp)->happy_dev, (__addr), (__size), (__dir))) +#define hme_dma_sync_for_cpu(__hp, __addr, __size, __dir) \ + ((__hp)->dma_sync_for_cpu((__hp)->happy_dev, (__addr), (__size), (__dir))) +#define hme_dma_sync_for_device(__hp, __addr, __size, __dir) \ + ((__hp)->dma_sync_for_device((__hp)->happy_dev, (__addr), (__size), (__dir))) +#else +#ifdef CONFIG_SBUS +/* SBUS only compilation */ +#define hme_write32(__hp, __reg, __val) \ + sbus_writel((__val), (__reg)) +#define hme_read32(__hp, __reg) \ + sbus_readl(__reg) +#define hme_write_rxd(__hp, __rxd, __flags, __addr) \ +do { (__rxd)->rx_addr = (__addr); \ + wmb(); \ + (__rxd)->rx_flags = (__flags); \ +} while(0) +#define hme_write_txd(__hp, __txd, __flags, __addr) \ +do { (__txd)->tx_addr = (__addr); \ + wmb(); \ + (__txd)->tx_flags = (__flags); \ +} while(0) +#define hme_read_desc32(__hp, __p) (*(__p)) +#define hme_dma_map(__hp, __ptr, __size, __dir) \ + sbus_map_single((__hp)->happy_dev, (__ptr), (__size), (__dir)) +#define hme_dma_unmap(__hp, __addr, __size, __dir) \ + sbus_unmap_single((__hp)->happy_dev, (__addr), (__size), (__dir)) +#define hme_dma_sync_for_cpu(__hp, __addr, __size, __dir) \ + sbus_dma_sync_single_for_cpu((__hp)->happy_dev, (__addr), (__size), (__dir)) +#define hme_dma_sync_for_device(__hp, __addr, __size, __dir) \ + sbus_dma_sync_single_for_device((__hp)->happy_dev, (__addr), (__size), (__dir)) +#else +/* PCI only compilation */ +#define hme_write32(__hp, __reg, __val) \ + writel((__val), (__reg)) +#define hme_read32(__hp, __reg) \ + readl(__reg) +#define hme_write_rxd(__hp, __rxd, __flags, __addr) \ +do { (__rxd)->rx_addr = cpu_to_le32(__addr); \ + wmb(); \ + (__rxd)->rx_flags = cpu_to_le32(__flags); \ +} while(0) +#define hme_write_txd(__hp, __txd, __flags, __addr) \ +do { (__txd)->tx_addr = cpu_to_le32(__addr); \ + wmb(); \ + (__txd)->tx_flags = cpu_to_le32(__flags); \ +} while(0) +#define hme_read_desc32(__hp, __p) cpu_to_le32p(__p) +#define hme_dma_map(__hp, __ptr, __size, __dir) \ + pci_map_single((__hp)->happy_dev, (__ptr), (__size), (__dir)) +#define hme_dma_unmap(__hp, __addr, __size, __dir) \ + pci_unmap_single((__hp)->happy_dev, (__addr), (__size), (__dir)) +#define hme_dma_sync_for_cpu(__hp, __addr, __size, __dir) \ + pci_dma_sync_single_for_cpu((__hp)->happy_dev, (__addr), (__size), (__dir)) +#define hme_dma_sync_for_device(__hp, __addr, __size, __dir) \ + pci_dma_sync_single_for_device((__hp)->happy_dev, (__addr), (__size), (__dir)) +#endif +#endif + + +#ifdef SBUS_DMA_BIDIRECTIONAL +# define DMA_BIDIRECTIONAL SBUS_DMA_BIDIRECTIONAL +#else +# define DMA_BIDIRECTIONAL 0 +#endif + +#ifdef SBUS_DMA_FROMDEVICE +# define DMA_FROMDEVICE SBUS_DMA_FROMDEVICE +#else +# define DMA_TODEVICE 1 +#endif + +#ifdef SBUS_DMA_TODEVICE +# define DMA_TODEVICE SBUS_DMA_TODEVICE +#else +# define DMA_FROMDEVICE 2 +#endif + + +/* Oh yes, the MIF BitBang is mighty fun to program. BitBucket is more like it. */ +static void BB_PUT_BIT(struct happy_meal *hp, void __iomem *tregs, int bit) +{ + hme_write32(hp, tregs + TCVR_BBDATA, bit); + hme_write32(hp, tregs + TCVR_BBCLOCK, 0); + hme_write32(hp, tregs + TCVR_BBCLOCK, 1); +} + +#if 0 +static u32 BB_GET_BIT(struct happy_meal *hp, void __iomem *tregs, int internal) +{ + u32 ret; + + hme_write32(hp, tregs + TCVR_BBCLOCK, 0); + hme_write32(hp, tregs + TCVR_BBCLOCK, 1); + ret = hme_read32(hp, tregs + TCVR_CFG); + if (internal) + ret &= TCV_CFG_MDIO0; + else + ret &= TCV_CFG_MDIO1; + + return ret; +} +#endif + +static u32 BB_GET_BIT2(struct happy_meal *hp, void __iomem *tregs, int internal) +{ + u32 retval; + + hme_write32(hp, tregs + TCVR_BBCLOCK, 0); + udelay(1); + retval = hme_read32(hp, tregs + TCVR_CFG); + if (internal) + retval &= TCV_CFG_MDIO0; + else + retval &= TCV_CFG_MDIO1; + hme_write32(hp, tregs + TCVR_BBCLOCK, 1); + + return retval; +} + +#define TCVR_FAILURE 0x80000000 /* Impossible MIF read value */ + +static int happy_meal_bb_read(struct happy_meal *hp, + void __iomem *tregs, int reg) +{ + u32 tmp; + int retval = 0; + int i; + + ASD(("happy_meal_bb_read: reg=%d ", reg)); + + /* Enable the MIF BitBang outputs. */ + hme_write32(hp, tregs + TCVR_BBOENAB, 1); + + /* Force BitBang into the idle state. */ + for (i = 0; i < 32; i++) + BB_PUT_BIT(hp, tregs, 1); + + /* Give it the read sequence. */ + BB_PUT_BIT(hp, tregs, 0); + BB_PUT_BIT(hp, tregs, 1); + BB_PUT_BIT(hp, tregs, 1); + BB_PUT_BIT(hp, tregs, 0); + + /* Give it the PHY address. */ + tmp = hp->paddr & 0xff; + for (i = 4; i >= 0; i--) + BB_PUT_BIT(hp, tregs, ((tmp >> i) & 1)); + + /* Tell it what register we want to read. */ + tmp = (reg & 0xff); + for (i = 4; i >= 0; i--) + BB_PUT_BIT(hp, tregs, ((tmp >> i) & 1)); + + /* Close down the MIF BitBang outputs. */ + hme_write32(hp, tregs + TCVR_BBOENAB, 0); + + /* Now read in the value. */ + (void) BB_GET_BIT2(hp, tregs, (hp->tcvr_type == internal)); + for (i = 15; i >= 0; i--) + retval |= BB_GET_BIT2(hp, tregs, (hp->tcvr_type == internal)); + (void) BB_GET_BIT2(hp, tregs, (hp->tcvr_type == internal)); + (void) BB_GET_BIT2(hp, tregs, (hp->tcvr_type == internal)); + (void) BB_GET_BIT2(hp, tregs, (hp->tcvr_type == internal)); + ASD(("value=%x\n", retval)); + return retval; +} + +static void happy_meal_bb_write(struct happy_meal *hp, + void __iomem *tregs, int reg, + unsigned short value) +{ + u32 tmp; + int i; + + ASD(("happy_meal_bb_write: reg=%d value=%x\n", reg, value)); + + /* Enable the MIF BitBang outputs. */ + hme_write32(hp, tregs + TCVR_BBOENAB, 1); + + /* Force BitBang into the idle state. */ + for (i = 0; i < 32; i++) + BB_PUT_BIT(hp, tregs, 1); + + /* Give it write sequence. */ + BB_PUT_BIT(hp, tregs, 0); + BB_PUT_BIT(hp, tregs, 1); + BB_PUT_BIT(hp, tregs, 0); + BB_PUT_BIT(hp, tregs, 1); + + /* Give it the PHY address. */ + tmp = (hp->paddr & 0xff); + for (i = 4; i >= 0; i--) + BB_PUT_BIT(hp, tregs, ((tmp >> i) & 1)); + + /* Tell it what register we will be writing. */ + tmp = (reg & 0xff); + for (i = 4; i >= 0; i--) + BB_PUT_BIT(hp, tregs, ((tmp >> i) & 1)); + + /* Tell it to become ready for the bits. */ + BB_PUT_BIT(hp, tregs, 1); + BB_PUT_BIT(hp, tregs, 0); + + for (i = 15; i >= 0; i--) + BB_PUT_BIT(hp, tregs, ((value >> i) & 1)); + + /* Close down the MIF BitBang outputs. */ + hme_write32(hp, tregs + TCVR_BBOENAB, 0); +} + +#define TCVR_READ_TRIES 16 + +static int happy_meal_tcvr_read(struct happy_meal *hp, + void __iomem *tregs, int reg) +{ + int tries = TCVR_READ_TRIES; + int retval; + + ASD(("happy_meal_tcvr_read: reg=0x%02x ", reg)); + if (hp->tcvr_type == none) { + ASD(("no transceiver, value=TCVR_FAILURE\n")); + return TCVR_FAILURE; + } + + if (!(hp->happy_flags & HFLAG_FENABLE)) { + ASD(("doing bit bang\n")); + return happy_meal_bb_read(hp, tregs, reg); + } + + hme_write32(hp, tregs + TCVR_FRAME, + (FRAME_READ | (hp->paddr << 23) | ((reg & 0xff) << 18))); + while (!(hme_read32(hp, tregs + TCVR_FRAME) & 0x10000) && --tries) + udelay(20); + if (!tries) { + printk(KERN_ERR "happy meal: Aieee, transceiver MIF read bolixed\n"); + return TCVR_FAILURE; + } + retval = hme_read32(hp, tregs + TCVR_FRAME) & 0xffff; + ASD(("value=%04x\n", retval)); + return retval; +} + +#define TCVR_WRITE_TRIES 16 + +static void happy_meal_tcvr_write(struct happy_meal *hp, + void __iomem *tregs, int reg, + unsigned short value) +{ + int tries = TCVR_WRITE_TRIES; + + ASD(("happy_meal_tcvr_write: reg=0x%02x value=%04x\n", reg, value)); + + /* Welcome to Sun Microsystems, can I take your order please? */ + if (!(hp->happy_flags & HFLAG_FENABLE)) { + happy_meal_bb_write(hp, tregs, reg, value); + return; + } + + /* Would you like fries with that? */ + hme_write32(hp, tregs + TCVR_FRAME, + (FRAME_WRITE | (hp->paddr << 23) | + ((reg & 0xff) << 18) | (value & 0xffff))); + while (!(hme_read32(hp, tregs + TCVR_FRAME) & 0x10000) && --tries) + udelay(20); + + /* Anything else? */ + if (!tries) + printk(KERN_ERR "happy meal: Aieee, transceiver MIF write bolixed\n"); + + /* Fifty-two cents is your change, have a nice day. */ +} + +/* Auto negotiation. The scheme is very simple. We have a timer routine + * that keeps watching the auto negotiation process as it progresses. + * The DP83840 is first told to start doing it's thing, we set up the time + * and place the timer state machine in it's initial state. + * + * Here the timer peeks at the DP83840 status registers at each click to see + * if the auto negotiation has completed, we assume here that the DP83840 PHY + * will time out at some point and just tell us what (didn't) happen. For + * complete coverage we only allow so many of the ticks at this level to run, + * when this has expired we print a warning message and try another strategy. + * This "other" strategy is to force the interface into various speed/duplex + * configurations and we stop when we see a link-up condition before the + * maximum number of "peek" ticks have occurred. + * + * Once a valid link status has been detected we configure the BigMAC and + * the rest of the Happy Meal to speak the most efficient protocol we could + * get a clean link for. The priority for link configurations, highest first + * is: + * 100 Base-T Full Duplex + * 100 Base-T Half Duplex + * 10 Base-T Full Duplex + * 10 Base-T Half Duplex + * + * We start a new timer now, after a successful auto negotiation status has + * been detected. This timer just waits for the link-up bit to get set in + * the BMCR of the DP83840. When this occurs we print a kernel log message + * describing the link type in use and the fact that it is up. + * + * If a fatal error of some sort is signalled and detected in the interrupt + * service routine, and the chip is reset, or the link is ifconfig'd down + * and then back up, this entire process repeats itself all over again. + */ +static int try_next_permutation(struct happy_meal *hp, void __iomem *tregs) +{ + hp->sw_bmcr = happy_meal_tcvr_read(hp, tregs, MII_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, MII_BMCR, hp->sw_bmcr); + return 0; + } + + /* Downgrade from 100 to 10. */ + if (hp->sw_bmcr & BMCR_SPEED100) { + hp->sw_bmcr &= ~(BMCR_SPEED100); + happy_meal_tcvr_write(hp, tregs, MII_BMCR, hp->sw_bmcr); + return 0; + } + + /* We've tried everything. */ + return -1; +} + +static void display_link_mode(struct happy_meal *hp, void __iomem *tregs) +{ + printk(KERN_INFO "%s: Link is up using ", hp->dev->name); + if (hp->tcvr_type == external) + printk("external "); + else + printk("internal "); + printk("transceiver at "); + hp->sw_lpa = happy_meal_tcvr_read(hp, tregs, MII_LPA); + if (hp->sw_lpa & (LPA_100HALF | LPA_100FULL)) { + if (hp->sw_lpa & LPA_100FULL) + printk("100Mb/s, Full Duplex.\n"); + else + printk("100Mb/s, Half Duplex.\n"); + } else { + if (hp->sw_lpa & LPA_10FULL) + printk("10Mb/s, Full Duplex.\n"); + else + printk("10Mb/s, Half Duplex.\n"); + } +} + +static void display_forced_link_mode(struct happy_meal *hp, void __iomem *tregs) +{ + printk(KERN_INFO "%s: Link has been forced up using ", hp->dev->name); + if (hp->tcvr_type == external) + printk("external "); + else + printk("internal "); + printk("transceiver at "); + hp->sw_bmcr = happy_meal_tcvr_read(hp, tregs, MII_BMCR); + if (hp->sw_bmcr & BMCR_SPEED100) + printk("100Mb/s, "); + else + printk("10Mb/s, "); + if (hp->sw_bmcr & BMCR_FULLDPLX) + printk("Full Duplex.\n"); + else + printk("Half Duplex.\n"); +} + +static int set_happy_link_modes(struct happy_meal *hp, void __iomem *tregs) +{ + int full; + + /* All we care about is making sure the bigmac tx_cfg has a + * proper duplex setting. + */ + if (hp->timer_state == arbwait) { + hp->sw_lpa = happy_meal_tcvr_read(hp, tregs, MII_LPA); + if (!(hp->sw_lpa & (LPA_10HALF | LPA_10FULL | LPA_100HALF | LPA_100FULL))) + goto no_response; + if (hp->sw_lpa & LPA_100FULL) + full = 1; + else if (hp->sw_lpa & LPA_100HALF) + full = 0; + else if (hp->sw_lpa & LPA_10FULL) + full = 1; + else + full = 0; + } else { + /* Forcing a link mode. */ + hp->sw_bmcr = happy_meal_tcvr_read(hp, tregs, MII_BMCR); + if (hp->sw_bmcr & BMCR_FULLDPLX) + full = 1; + else + full = 0; + } + + /* Before changing other bits in the tx_cfg register, and in + * general any of other the TX config registers too, you + * must: + * 1) Clear Enable + * 2) Poll with reads until that bit reads back as zero + * 3) Make TX configuration changes + * 4) Set Enable once more + */ + hme_write32(hp, hp->bigmacregs + BMAC_TXCFG, + hme_read32(hp, hp->bigmacregs + BMAC_TXCFG) & + ~(BIGMAC_TXCFG_ENABLE)); + while (hme_read32(hp, hp->bigmacregs + BMAC_TXCFG) & BIGMAC_TXCFG_ENABLE) + barrier(); + if (full) { + hp->happy_flags |= HFLAG_FULL; + hme_write32(hp, hp->bigmacregs + BMAC_TXCFG, + hme_read32(hp, hp->bigmacregs + BMAC_TXCFG) | + BIGMAC_TXCFG_FULLDPLX); + } else { + hp->happy_flags &= ~(HFLAG_FULL); + hme_write32(hp, hp->bigmacregs + BMAC_TXCFG, + hme_read32(hp, hp->bigmacregs + BMAC_TXCFG) & + ~(BIGMAC_TXCFG_FULLDPLX)); + } + hme_write32(hp, hp->bigmacregs + BMAC_TXCFG, + hme_read32(hp, hp->bigmacregs + BMAC_TXCFG) | + BIGMAC_TXCFG_ENABLE); + return 0; +no_response: + return 1; +} + +static int happy_meal_init(struct happy_meal *hp); + +static int is_lucent_phy(struct happy_meal *hp) +{ + void __iomem *tregs = hp->tcvregs; + unsigned short mr2, mr3; + int ret = 0; + + mr2 = happy_meal_tcvr_read(hp, tregs, 2); + mr3 = happy_meal_tcvr_read(hp, tregs, 3); + if ((mr2 & 0xffff) == 0x0180 && + ((mr3 & 0xffff) >> 10) == 0x1d) + ret = 1; + + return ret; +} + +static void happy_meal_timer(unsigned long data) +{ + struct happy_meal *hp = (struct happy_meal *) data; + void __iomem *tregs = hp->tcvregs; + int restart_timer = 0; + + spin_lock_irq(&hp->happy_lock); + + hp->timer_ticks++; + switch(hp->timer_state) { + case arbwait: + /* Only allow for 5 ticks, thats 10 seconds and much too + * long to wait for arbitration to complete. + */ + if (hp->timer_ticks >= 10) { + /* Enter force mode. */ + do_force_mode: + hp->sw_bmcr = happy_meal_tcvr_read(hp, tregs, MII_BMCR); + printk(KERN_NOTICE "%s: Auto-Negotiation unsuccessful, trying force link mode\n", + hp->dev->name); + hp->sw_bmcr = BMCR_SPEED100; + happy_meal_tcvr_write(hp, tregs, MII_BMCR, hp->sw_bmcr); + + if (!is_lucent_phy(hp)) { + /* 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); + hp->sw_csconfig &= ~(CSCONFIG_TCVDISAB); + happy_meal_tcvr_write(hp, tregs, DP83840_CSCONFIG, hp->sw_csconfig); + } + hp->timer_state = ltrywait; + hp->timer_ticks = 0; + restart_timer = 1; + } else { + /* Anything interesting happen? */ + hp->sw_bmsr = happy_meal_tcvr_read(hp, tregs, MII_BMSR); + if (hp->sw_bmsr & BMSR_ANEGCOMPLETE) { + int ret; + + /* Just what we've been waiting for... */ + ret = set_happy_link_modes(hp, tregs); + if (ret) { + /* Ooops, something bad happened, go to force + * mode. + * + * XXX Broken hubs which don't support 802.3u + * XXX auto-negotiation make this happen as well. + */ + goto do_force_mode; + } + + /* Success, at least so far, advance our state engine. */ + hp->timer_state = lupwait; + restart_timer = 1; + } else { + restart_timer = 1; + } + } + break; + + case lupwait: + /* Auto negotiation was successful and we are awaiting a + * link up status. I have decided to let this timer run + * forever until some sort of error is signalled, reporting + * a message to the user at 10 second intervals. + */ + hp->sw_bmsr = happy_meal_tcvr_read(hp, tregs, MII_BMSR); + if (hp->sw_bmsr & BMSR_LSTATUS) { + /* Wheee, it's up, display the link mode in use and put + * the timer to sleep. + */ + display_link_mode(hp, tregs); + hp->timer_state = asleep; + restart_timer = 0; + } else { + if (hp->timer_ticks >= 10) { + printk(KERN_NOTICE "%s: Auto negotiation successful, link still " + "not completely up.\n", hp->dev->name); + hp->timer_ticks = 0; + restart_timer = 1; + } else { + restart_timer = 1; + } + } + break; + + case ltrywait: + /* Making the timeout here too long can make it take + * annoyingly long to attempt all of the link mode + * permutations, but then again this is essentially + * error recovery code for the most part. + */ + hp->sw_bmsr = happy_meal_tcvr_read(hp, tregs, MII_BMSR); + hp->sw_csconfig = happy_meal_tcvr_read(hp, tregs, DP83840_CSCONFIG); + if (hp->timer_ticks == 1) { + if (!is_lucent_phy(hp)) { + /* Re-enable transceiver, we'll re-enable the transceiver next + * tick, then check link state on the following tick. + */ + hp->sw_csconfig |= CSCONFIG_TCVDISAB; + happy_meal_tcvr_write(hp, tregs, + DP83840_CSCONFIG, hp->sw_csconfig); + } + restart_timer = 1; + break; + } + if (hp->timer_ticks == 2) { + if (!is_lucent_phy(hp)) { + hp->sw_csconfig &= ~(CSCONFIG_TCVDISAB); + happy_meal_tcvr_write(hp, tregs, + DP83840_CSCONFIG, hp->sw_csconfig); + } + restart_timer = 1; + break; + } + if (hp->sw_bmsr & BMSR_LSTATUS) { + /* Force mode selection success. */ + display_forced_link_mode(hp, tregs); + set_happy_link_modes(hp, tregs); /* XXX error? then what? */ + hp->timer_state = asleep; + restart_timer = 0; + } else { + if (hp->timer_ticks >= 4) { /* 6 seconds or so... */ + int ret; + + ret = try_next_permutation(hp, tregs); + if (ret == -1) { + /* Aieee, tried them all, reset the + * chip and try all over again. + */ + + /* Let the user know... */ + printk(KERN_NOTICE "%s: Link down, cable problem?\n", + hp->dev->name); + + ret = happy_meal_init(hp); + if (ret) { + /* ho hum... */ + printk(KERN_ERR "%s: Error, cannot re-init the " + "Happy Meal.\n", hp->dev->name); + } + goto out; + } + if (!is_lucent_phy(hp)) { + 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); + } + hp->timer_ticks = 0; + restart_timer = 1; + } else { + restart_timer = 1; + } + } + break; + + case asleep: + default: + /* Can't happens.... */ + printk(KERN_ERR "%s: Aieee, link timer is asleep but we got one anyways!\n", + hp->dev->name); + restart_timer = 0; + hp->timer_ticks = 0; + hp->timer_state = asleep; /* foo on you */ + break; + }; + + if (restart_timer) { + hp->happy_timer.expires = jiffies + ((12 * HZ)/10); /* 1.2 sec. */ + add_timer(&hp->happy_timer); + } + +out: + spin_unlock_irq(&hp->happy_lock); +} + +#define TX_RESET_TRIES 32 +#define RX_RESET_TRIES 32 + +/* hp->happy_lock must be held */ +static void happy_meal_tx_reset(struct happy_meal *hp, void __iomem *bregs) +{ + int tries = TX_RESET_TRIES; + + HMD(("happy_meal_tx_reset: reset, ")); + + /* Would you like to try our SMCC Delux? */ + hme_write32(hp, bregs + BMAC_TXSWRESET, 0); + while ((hme_read32(hp, bregs + BMAC_TXSWRESET) & 1) && --tries) + udelay(20); + + /* Lettuce, tomato, buggy hardware (no extra charge)? */ + if (!tries) + printk(KERN_ERR "happy meal: Transceiver BigMac ATTACK!"); + + /* Take care. */ + HMD(("done\n")); +} + +/* hp->happy_lock must be held */ +static void happy_meal_rx_reset(struct happy_meal *hp, void __iomem *bregs) +{ + int tries = RX_RESET_TRIES; + + HMD(("happy_meal_rx_reset: reset, ")); + + /* We have a special on GNU/Viking hardware bugs today. */ + hme_write32(hp, bregs + BMAC_RXSWRESET, 0); + while ((hme_read32(hp, bregs + BMAC_RXSWRESET) & 1) && --tries) + udelay(20); + + /* Will that be all? */ + if (!tries) + printk(KERN_ERR "happy meal: Receiver BigMac ATTACK!"); + + /* Don't forget your vik_1137125_wa. Have a nice day. */ + HMD(("done\n")); +} + +#define STOP_TRIES 16 + +/* hp->happy_lock must be held */ +static void happy_meal_stop(struct happy_meal *hp, void __iomem *gregs) +{ + int tries = STOP_TRIES; + + HMD(("happy_meal_stop: reset, ")); + + /* We're consolidating our STB products, it's your lucky day. */ + hme_write32(hp, gregs + GREG_SWRESET, GREG_RESET_ALL); + while (hme_read32(hp, gregs + GREG_SWRESET) && --tries) + udelay(20); + + /* Come back next week when we are "Sun Microelectronics". */ + if (!tries) + printk(KERN_ERR "happy meal: Fry guys."); + + /* Remember: "Different name, same old buggy as shit hardware." */ + HMD(("done\n")); +} + +/* hp->happy_lock must be held */ +static void happy_meal_get_counters(struct happy_meal *hp, void __iomem *bregs) +{ + struct net_device_stats *stats = &hp->net_stats; + + stats->rx_crc_errors += hme_read32(hp, bregs + BMAC_RCRCECTR); + hme_write32(hp, bregs + BMAC_RCRCECTR, 0); + + stats->rx_frame_errors += hme_read32(hp, bregs + BMAC_UNALECTR); + hme_write32(hp, bregs + BMAC_UNALECTR, 0); + + stats->rx_length_errors += hme_read32(hp, bregs + BMAC_GLECTR); + hme_write32(hp, bregs + BMAC_GLECTR, 0); + + stats->tx_aborted_errors += hme_read32(hp, bregs + BMAC_EXCTR); + + stats->collisions += + (hme_read32(hp, bregs + BMAC_EXCTR) + + hme_read32(hp, bregs + BMAC_LTCTR)); + hme_write32(hp, bregs + BMAC_EXCTR, 0); + hme_write32(hp, bregs + BMAC_LTCTR, 0); +} + +/* hp->happy_lock must be held */ +static void happy_meal_poll_stop(struct happy_meal *hp, void __iomem *tregs) +{ + ASD(("happy_meal_poll_stop: ")); + + /* If polling disabled or not polling already, nothing to do. */ + if ((hp->happy_flags & (HFLAG_POLLENABLE | HFLAG_POLL)) != + (HFLAG_POLLENABLE | HFLAG_POLL)) { + HMD(("not polling, return\n")); + return; + } + + /* Shut up the MIF. */ + ASD(("were polling, mif ints off, ")); + hme_write32(hp, tregs + TCVR_IMASK, 0xffff); + + /* Turn off polling. */ + ASD(("polling off, ")); + hme_write32(hp, tregs + TCVR_CFG, + hme_read32(hp, tregs + TCVR_CFG) & ~(TCV_CFG_PENABLE)); + + /* We are no longer polling. */ + hp->happy_flags &= ~(HFLAG_POLL); + + /* Let the bits set. */ + udelay(200); + ASD(("done\n")); +} + +/* Only Sun can take such nice parts and fuck up the programming interface + * like this. Good job guys... + */ +#define TCVR_RESET_TRIES 16 /* It should reset quickly */ +#define TCVR_UNISOLATE_TRIES 32 /* Dis-isolation can take longer. */ + +/* hp->happy_lock must be held */ +static int happy_meal_tcvr_reset(struct happy_meal *hp, void __iomem *tregs) +{ + u32 tconfig; + int result, tries = TCVR_RESET_TRIES; + + tconfig = hme_read32(hp, tregs + TCVR_CFG); + ASD(("happy_meal_tcvr_reset: tcfg<%08lx> ", tconfig)); + if (hp->tcvr_type == external) { + ASD(("external<")); + hme_write32(hp, tregs + TCVR_CFG, tconfig & ~(TCV_CFG_PSELECT)); + hp->tcvr_type = internal; + hp->paddr = TCV_PADDR_ITX; + ASD(("ISOLATE,")); + happy_meal_tcvr_write(hp, tregs, MII_BMCR, + (BMCR_LOOPBACK|BMCR_PDOWN|BMCR_ISOLATE)); + result = happy_meal_tcvr_read(hp, tregs, MII_BMCR); + if (result == TCVR_FAILURE) { + ASD(("phyread_fail>\n")); + return -1; + } + ASD(("phyread_ok,PSELECT>")); + hme_write32(hp, tregs + TCVR_CFG, tconfig | TCV_CFG_PSELECT); + hp->tcvr_type = external; + hp->paddr = TCV_PADDR_ETX; + } else { + if (tconfig & TCV_CFG_MDIO1) { + ASD(("internal<PSELECT,")); + hme_write32(hp, tregs + TCVR_CFG, (tconfig | TCV_CFG_PSELECT)); + ASD(("ISOLATE,")); + happy_meal_tcvr_write(hp, tregs, MII_BMCR, + (BMCR_LOOPBACK|BMCR_PDOWN|BMCR_ISOLATE)); + result = happy_meal_tcvr_read(hp, tregs, MII_BMCR); + if (result == TCVR_FAILURE) { + ASD(("phyread_fail>\n")); + return -1; + } + ASD(("phyread_ok,~PSELECT>")); + hme_write32(hp, tregs + TCVR_CFG, (tconfig & ~(TCV_CFG_PSELECT))); + hp->tcvr_type = internal; + hp->paddr = TCV_PADDR_ITX; + } + } + + ASD(("BMCR_RESET ")); + happy_meal_tcvr_write(hp, tregs, MII_BMCR, BMCR_RESET); + + while (--tries) { + result = happy_meal_tcvr_read(hp, tregs, MII_BMCR); + if (result == TCVR_FAILURE) + return -1; + hp->sw_bmcr = result; + if (!(result & BMCR_RESET)) + break; + udelay(20); + } + if (!tries) { + ASD(("BMCR RESET FAILED!\n")); + return -1; + } + ASD(("RESET_OK\n")); + + /* Get fresh copies of the PHY registers. */ + hp->sw_bmsr = happy_meal_tcvr_read(hp, tregs, MII_BMSR); + hp->sw_physid1 = happy_meal_tcvr_read(hp, tregs, MII_PHYSID1); + hp->sw_physid2 = happy_meal_tcvr_read(hp, tregs, MII_PHYSID2); + hp->sw_advertise = happy_meal_tcvr_read(hp, tregs, MII_ADVERTISE); + + ASD(("UNISOLATE")); + hp->sw_bmcr &= ~(BMCR_ISOLATE); + happy_meal_tcvr_write(hp, tregs, MII_BMCR, hp->sw_bmcr); + + tries = TCVR_UNISOLATE_TRIES; + while (--tries) { + result = happy_meal_tcvr_read(hp, tregs, MII_BMCR); + if (result == TCVR_FAILURE) + return -1; + if (!(result & BMCR_ISOLATE)) + break; + udelay(20); + } + if (!tries) { + ASD((" FAILED!\n")); + return -1; + } + ASD((" SUCCESS and CSCONFIG_DFBYPASS\n")); + if (!is_lucent_phy(hp)) { + result = happy_meal_tcvr_read(hp, tregs, + DP83840_CSCONFIG); |