aboutsummaryrefslogtreecommitdiff
path: root/drivers/net/sunhme.c
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@ppc970.osdl.org>2005-04-16 15:20:36 -0700
committerLinus Torvalds <torvalds@ppc970.osdl.org>2005-04-16 15:20:36 -0700
commit1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch)
tree0bba044c4ce775e45a88a51686b5d9f90697ea9d /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.c3426
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);