aboutsummaryrefslogtreecommitdiff
path: root/arch/ppc/8260_io/fcc_enet.c
diff options
context:
space:
mode:
Diffstat (limited to 'arch/ppc/8260_io/fcc_enet.c')
-rw-r--r--arch/ppc/8260_io/fcc_enet.c2395
1 files changed, 2395 insertions, 0 deletions
diff --git a/arch/ppc/8260_io/fcc_enet.c b/arch/ppc/8260_io/fcc_enet.c
new file mode 100644
index 00000000000..2086c6ad114
--- /dev/null
+++ b/arch/ppc/8260_io/fcc_enet.c
@@ -0,0 +1,2395 @@
+/*
+ * Fast Ethernet Controller (FCC) driver for Motorola MPC8260.
+ * Copyright (c) 2000 MontaVista Software, Inc. Dan Malek (dmalek@jlc.net)
+ *
+ * This version of the driver is a combination of the 8xx fec and
+ * 8260 SCC Ethernet drivers. This version has some additional
+ * configuration options, which should probably be moved out of
+ * here. This driver currently works for the EST SBC8260,
+ * SBS Diablo/BCM, Embedded Planet RPX6, TQM8260, and others.
+ *
+ * Right now, I am very watseful with the buffers. I allocate memory
+ * pages and then divide them into 2K frame buffers. This way I know I
+ * have buffers large enough to hold one frame within one buffer descriptor.
+ * Once I get this working, I will use 64 or 128 byte CPM buffers, which
+ * will be much more memory efficient and will easily handle lots of
+ * small packets. Since this is a cache coherent processor and CPM,
+ * I could also preallocate SKB's and use them directly on the interface.
+ *
+ * 2004-12 Leo Li (leoli@freescale.com)
+ * - Rework the FCC clock configuration part, make it easier to configure.
+ *
+ */
+
+#include <linux/config.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/string.h>
+#include <linux/ptrace.h>
+#include <linux/errno.h>
+#include <linux/ioport.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/skbuff.h>
+#include <linux/spinlock.h>
+#include <linux/mii.h>
+#include <linux/workqueue.h>
+#include <linux/bitops.h>
+
+#include <asm/immap_cpm2.h>
+#include <asm/pgtable.h>
+#include <asm/mpc8260.h>
+#include <asm/irq.h>
+#include <asm/uaccess.h>
+#include <asm/signal.h>
+
+/* We can't use the PHY interrupt if we aren't using MDIO. */
+#if !defined(CONFIG_USE_MDIO)
+#undef PHY_INTERRUPT
+#endif
+
+/* If we have a PHY interrupt, we will advertise both full-duplex and half-
+ * duplex capabilities. If we don't have a PHY interrupt, then we will only
+ * advertise half-duplex capabilities.
+ */
+#define MII_ADVERTISE_HALF (ADVERTISE_100HALF | ADVERTISE_10HALF | \
+ ADVERTISE_CSMA)
+#define MII_ADVERTISE_ALL (ADVERTISE_100FULL | ADVERTISE_10FULL | \
+ MII_ADVERTISE_HALF)
+#ifdef PHY_INTERRUPT
+#define MII_ADVERTISE_DEFAULT MII_ADVERTISE_ALL
+#else
+#define MII_ADVERTISE_DEFAULT MII_ADVERTISE_HALF
+#endif
+#include <asm/cpm2.h>
+
+/* The transmitter timeout
+ */
+#define TX_TIMEOUT (2*HZ)
+
+#ifdef CONFIG_USE_MDIO
+/* Forward declarations of some structures to support different PHYs */
+
+typedef struct {
+ uint mii_data;
+ void (*funct)(uint mii_reg, struct net_device *dev);
+} phy_cmd_t;
+
+typedef struct {
+ uint id;
+ char *name;
+
+ const phy_cmd_t *config;
+ const phy_cmd_t *startup;
+ const phy_cmd_t *ack_int;
+ const phy_cmd_t *shutdown;
+} phy_info_t;
+
+/* values for phy_status */
+
+#define PHY_CONF_ANE 0x0001 /* 1 auto-negotiation enabled */
+#define PHY_CONF_LOOP 0x0002 /* 1 loopback mode enabled */
+#define PHY_CONF_SPMASK 0x00f0 /* mask for speed */
+#define PHY_CONF_10HDX 0x0010 /* 10 Mbit half duplex supported */
+#define PHY_CONF_10FDX 0x0020 /* 10 Mbit full duplex supported */
+#define PHY_CONF_100HDX 0x0040 /* 100 Mbit half duplex supported */
+#define PHY_CONF_100FDX 0x0080 /* 100 Mbit full duplex supported */
+
+#define PHY_STAT_LINK 0x0100 /* 1 up - 0 down */
+#define PHY_STAT_FAULT 0x0200 /* 1 remote fault */
+#define PHY_STAT_ANC 0x0400 /* 1 auto-negotiation complete */
+#define PHY_STAT_SPMASK 0xf000 /* mask for speed */
+#define PHY_STAT_10HDX 0x1000 /* 10 Mbit half duplex selected */
+#define PHY_STAT_10FDX 0x2000 /* 10 Mbit full duplex selected */
+#define PHY_STAT_100HDX 0x4000 /* 100 Mbit half duplex selected */
+#define PHY_STAT_100FDX 0x8000 /* 100 Mbit full duplex selected */
+#endif /* CONFIG_USE_MDIO */
+
+/* The number of Tx and Rx buffers. These are allocated from the page
+ * pool. The code may assume these are power of two, so it is best
+ * to keep them that size.
+ * We don't need to allocate pages for the transmitter. We just use
+ * the skbuffer directly.
+ */
+#define FCC_ENET_RX_PAGES 16
+#define FCC_ENET_RX_FRSIZE 2048
+#define FCC_ENET_RX_FRPPG (PAGE_SIZE / FCC_ENET_RX_FRSIZE)
+#define RX_RING_SIZE (FCC_ENET_RX_FRPPG * FCC_ENET_RX_PAGES)
+#define TX_RING_SIZE 16 /* Must be power of two */
+#define TX_RING_MOD_MASK 15 /* for this to work */
+
+/* The FCC stores dest/src/type, data, and checksum for receive packets.
+ * size includes support for VLAN
+ */
+#define PKT_MAXBUF_SIZE 1522
+#define PKT_MINBUF_SIZE 64
+
+/* Maximum input DMA size. Must be a should(?) be a multiple of 4.
+ * size includes support for VLAN
+ */
+#define PKT_MAXDMA_SIZE 1524
+
+/* Maximum input buffer size. Must be a multiple of 32.
+*/
+#define PKT_MAXBLR_SIZE 1536
+
+static int fcc_enet_open(struct net_device *dev);
+static int fcc_enet_start_xmit(struct sk_buff *skb, struct net_device *dev);
+static int fcc_enet_rx(struct net_device *dev);
+static irqreturn_t fcc_enet_interrupt(int irq, void *dev_id, struct pt_regs *);
+static int fcc_enet_close(struct net_device *dev);
+static struct net_device_stats *fcc_enet_get_stats(struct net_device *dev);
+/* static void set_multicast_list(struct net_device *dev); */
+static void fcc_restart(struct net_device *dev, int duplex);
+static void fcc_stop(struct net_device *dev);
+static int fcc_enet_set_mac_address(struct net_device *dev, void *addr);
+
+/* These will be configurable for the FCC choice.
+ * Multiple ports can be configured. There is little choice among the
+ * I/O pins to the PHY, except the clocks. We will need some board
+ * dependent clock selection.
+ * Why in the hell did I put these inside #ifdef's? I dunno, maybe to
+ * help show what pins are used for each device.
+ */
+
+/* Since the CLK setting changes greatly from board to board, I changed
+ * it to a easy way. You just need to specify which CLK number to use.
+ * Note that only limited choices can be make on each port.
+ */
+
+/* FCC1 Clock Source Configuration. There are board specific.
+ Can only choose from CLK9-12 */
+#ifdef CONFIG_SBC82xx
+#define F1_RXCLK 9
+#define F1_TXCLK 10
+#elif defined(CONFIG_ADS8272)
+#define F1_RXCLK 11
+#define F1_TXCLK 10
+#else
+#define F1_RXCLK 12
+#define F1_TXCLK 11
+#endif
+
+/* FCC2 Clock Source Configuration. There are board specific.
+ Can only choose from CLK13-16 */
+#ifdef CONFIG_ADS8272
+#define F2_RXCLK 15
+#define F2_TXCLK 16
+#else
+#define F2_RXCLK 13
+#define F2_TXCLK 14
+#endif
+
+/* FCC3 Clock Source Configuration. There are board specific.
+ Can only choose from CLK13-16 */
+#define F3_RXCLK 15
+#define F3_TXCLK 16
+
+/* Automatically generates register configurations */
+#define PC_CLK(x) ((uint)(1<<(x-1))) /* FCC CLK I/O ports */
+
+#define CMXFCR_RF1CS(x) ((uint)((x-5)<<27)) /* FCC1 Receive Clock Source */
+#define CMXFCR_TF1CS(x) ((uint)((x-5)<<24)) /* FCC1 Transmit Clock Source */
+#define CMXFCR_RF2CS(x) ((uint)((x-9)<<19)) /* FCC2 Receive Clock Source */
+#define CMXFCR_TF2CS(x) ((uint)((x-9)<<16)) /* FCC2 Transmit Clock Source */
+#define CMXFCR_RF3CS(x) ((uint)((x-9)<<11)) /* FCC3 Receive Clock Source */
+#define CMXFCR_TF3CS(x) ((uint)((x-9)<<8)) /* FCC3 Transmit Clock Source */
+
+#define PC_F1RXCLK PC_CLK(F1_RXCLK)
+#define PC_F1TXCLK PC_CLK(F1_TXCLK)
+#define CMX1_CLK_ROUTE (CMXFCR_RF1CS(F1_RXCLK) | CMXFCR_TF1CS(F1_TXCLK))
+#define CMX1_CLK_MASK ((uint)0xff000000)
+
+#define PC_F2RXCLK PC_CLK(F2_RXCLK)
+#define PC_F2TXCLK PC_CLK(F2_TXCLK)
+#define CMX2_CLK_ROUTE (CMXFCR_RF2CS(F2_RXCLK) | CMXFCR_TF2CS(F2_TXCLK))
+#define CMX2_CLK_MASK ((uint)0x00ff0000)
+
+#define PC_F3RXCLK PC_CLK(F3_RXCLK)
+#define PC_F3TXCLK PC_CLK(F3_TXCLK)
+#define CMX3_CLK_ROUTE (CMXFCR_RF3CS(F3_RXCLK) | CMXFCR_TF3CS(F3_TXCLK))
+#define CMX3_CLK_MASK ((uint)0x0000ff00)
+
+
+/* I/O Pin assignment for FCC1. I don't yet know the best way to do this,
+ * but there is little variation among the choices.
+ */
+#define PA1_COL ((uint)0x00000001)
+#define PA1_CRS ((uint)0x00000002)
+#define PA1_TXER ((uint)0x00000004)
+#define PA1_TXEN ((uint)0x00000008)
+#define PA1_RXDV ((uint)0x00000010)
+#define PA1_RXER ((uint)0x00000020)
+#define PA1_TXDAT ((uint)0x00003c00)
+#define PA1_RXDAT ((uint)0x0003c000)
+#define PA1_PSORA_BOUT (PA1_RXDAT | PA1_TXDAT)
+#define PA1_PSORA_BIN (PA1_COL | PA1_CRS | PA1_TXER | PA1_TXEN | \
+ PA1_RXDV | PA1_RXER)
+#define PA1_DIRA_BOUT (PA1_RXDAT | PA1_CRS | PA1_COL | PA1_RXER | PA1_RXDV)
+#define PA1_DIRA_BIN (PA1_TXDAT | PA1_TXEN | PA1_TXER)
+
+
+/* I/O Pin assignment for FCC2. I don't yet know the best way to do this,
+ * but there is little variation among the choices.
+ */
+#define PB2_TXER ((uint)0x00000001)
+#define PB2_RXDV ((uint)0x00000002)
+#define PB2_TXEN ((uint)0x00000004)
+#define PB2_RXER ((uint)0x00000008)
+#define PB2_COL ((uint)0x00000010)
+#define PB2_CRS ((uint)0x00000020)
+#define PB2_TXDAT ((uint)0x000003c0)
+#define PB2_RXDAT ((uint)0x00003c00)
+#define PB2_PSORB_BOUT (PB2_RXDAT | PB2_TXDAT | PB2_CRS | PB2_COL | \
+ PB2_RXER | PB2_RXDV | PB2_TXER)
+#define PB2_PSORB_BIN (PB2_TXEN)
+#define PB2_DIRB_BOUT (PB2_RXDAT | PB2_CRS | PB2_COL | PB2_RXER | PB2_RXDV)
+#define PB2_DIRB_BIN (PB2_TXDAT | PB2_TXEN | PB2_TXER)
+
+
+/* I/O Pin assignment for FCC3. I don't yet know the best way to do this,
+ * but there is little variation among the choices.
+ */
+#define PB3_RXDV ((uint)0x00004000)
+#define PB3_RXER ((uint)0x00008000)
+#define PB3_TXER ((uint)0x00010000)
+#define PB3_TXEN ((uint)0x00020000)
+#define PB3_COL ((uint)0x00040000)
+#define PB3_CRS ((uint)0x00080000)
+#ifndef CONFIG_RPX8260
+#define PB3_TXDAT ((uint)0x0f000000)
+#define PC3_TXDAT ((uint)0x00000000)
+#else
+#define PB3_TXDAT ((uint)0x0f000000)
+#define PC3_TXDAT 0
+#endif
+#define PB3_RXDAT ((uint)0x00f00000)
+#define PB3_PSORB_BOUT (PB3_RXDAT | PB3_TXDAT | PB3_CRS | PB3_COL | \
+ PB3_RXER | PB3_RXDV | PB3_TXER | PB3_TXEN)
+#define PB3_PSORB_BIN (0)
+#define PB3_DIRB_BOUT (PB3_RXDAT | PB3_CRS | PB3_COL | PB3_RXER | PB3_RXDV)
+#define PB3_DIRB_BIN (PB3_TXDAT | PB3_TXEN | PB3_TXER)
+
+#define PC3_PSORC_BOUT (PC3_TXDAT)
+#define PC3_PSORC_BIN (0)
+#define PC3_DIRC_BOUT (0)
+#define PC3_DIRC_BIN (PC3_TXDAT)
+
+
+/* MII status/control serial interface.
+*/
+#if defined(CONFIG_RPX8260)
+/* The EP8260 doesn't use Port C for MDIO */
+#define PC_MDIO ((uint)0x00000000)
+#define PC_MDCK ((uint)0x00000000)
+#elif defined(CONFIG_TQM8260)
+/* TQM8260 has MDIO and MDCK on PC30 and PC31 respectively */
+#define PC_MDIO ((uint)0x00000002)
+#define PC_MDCK ((uint)0x00000001)
+#elif defined(CONFIG_ADS8272)
+#define PC_MDIO ((uint)0x00002000)
+#define PC_MDCK ((uint)0x00001000)
+#elif defined(CONFIG_EST8260) || defined(CONFIG_ADS8260) || defined(CONFIG_PQ2FADS)
+#define PC_MDIO ((uint)0x00400000)
+#define PC_MDCK ((uint)0x00200000)
+#else
+#define PC_MDIO ((uint)0x00000004)
+#define PC_MDCK ((uint)0x00000020)
+#endif
+
+#if defined(CONFIG_USE_MDIO) && (!defined(PC_MDIO) || !defined(PC_MDCK))
+#error "Must define PC_MDIO and PC_MDCK if using MDIO"
+#endif
+
+/* PHY addresses */
+/* default to dynamic config of phy addresses */
+#define FCC1_PHY_ADDR 0
+#ifdef CONFIG_PQ2FADS
+#define FCC2_PHY_ADDR 0
+#else
+#define FCC2_PHY_ADDR 2
+#endif
+#define FCC3_PHY_ADDR 3
+
+/* A table of information for supporting FCCs. This does two things.
+ * First, we know how many FCCs we have and they are always externally
+ * numbered from zero. Second, it holds control register and I/O
+ * information that could be different among board designs.
+ */
+typedef struct fcc_info {
+ uint fc_fccnum;
+ uint fc_phyaddr;
+ uint fc_cpmblock;
+ uint fc_cpmpage;
+ uint fc_proff;
+ uint fc_interrupt;
+ uint fc_trxclocks;
+ uint fc_clockroute;
+ uint fc_clockmask;
+ uint fc_mdio;
+ uint fc_mdck;
+} fcc_info_t;
+
+static fcc_info_t fcc_ports[] = {
+#ifdef CONFIG_FCC1_ENET
+ { 0, FCC1_PHY_ADDR, CPM_CR_FCC1_SBLOCK, CPM_CR_FCC1_PAGE, PROFF_FCC1, SIU_INT_FCC1,
+ (PC_F1RXCLK | PC_F1TXCLK), CMX1_CLK_ROUTE, CMX1_CLK_MASK,
+ PC_MDIO, PC_MDCK },
+#endif
+#ifdef CONFIG_FCC2_ENET
+ { 1, FCC2_PHY_ADDR, CPM_CR_FCC2_SBLOCK, CPM_CR_FCC2_PAGE, PROFF_FCC2, SIU_INT_FCC2,
+ (PC_F2RXCLK | PC_F2TXCLK), CMX2_CLK_ROUTE, CMX2_CLK_MASK,
+ PC_MDIO, PC_MDCK },
+#endif
+#ifdef CONFIG_FCC3_ENET
+ { 2, FCC3_PHY_ADDR, CPM_CR_FCC3_SBLOCK, CPM_CR_FCC3_PAGE, PROFF_FCC3, SIU_INT_FCC3,
+ (PC_F3RXCLK | PC_F3TXCLK), CMX3_CLK_ROUTE, CMX3_CLK_MASK,
+ PC_MDIO, PC_MDCK },
+#endif
+};
+
+/* The FCC buffer descriptors track the ring buffers. The rx_bd_base and
+ * tx_bd_base always point to the base of the buffer descriptors. The
+ * cur_rx and cur_tx point to the currently available buffer.
+ * The dirty_tx tracks the current buffer that is being sent by the
+ * controller. The cur_tx and dirty_tx are equal under both completely
+ * empty and completely full conditions. The empty/ready indicator in
+ * the buffer descriptor determines the actual condition.
+ */
+struct fcc_enet_private {
+ /* The saved address of a sent-in-place packet/buffer, for skfree(). */
+ struct sk_buff* tx_skbuff[TX_RING_SIZE];
+ ushort skb_cur;
+ ushort skb_dirty;
+
+ /* CPM dual port RAM relative addresses.
+ */
+ cbd_t *rx_bd_base; /* Address of Rx and Tx buffers. */
+ cbd_t *tx_bd_base;
+ cbd_t *cur_rx, *cur_tx; /* The next free ring entry */
+ cbd_t *dirty_tx; /* The ring entries to be free()ed. */
+ volatile fcc_t *fccp;
+ volatile fcc_enet_t *ep;
+ struct net_device_stats stats;
+ uint tx_free;
+ spinlock_t lock;
+
+#ifdef CONFIG_USE_MDIO
+ uint phy_id;
+ uint phy_id_done;
+ uint phy_status;
+ phy_info_t *phy;
+ struct work_struct phy_relink;
+ struct work_struct phy_display_config;
+
+ uint sequence_done;
+
+ uint phy_addr;
+#endif /* CONFIG_USE_MDIO */
+
+ int link;
+ int old_link;
+ int full_duplex;
+
+ fcc_info_t *fip;
+};
+
+static void init_fcc_shutdown(fcc_info_t *fip, struct fcc_enet_private *cep,
+ volatile cpm2_map_t *immap);
+static void init_fcc_startup(fcc_info_t *fip, struct net_device *dev);
+static void init_fcc_ioports(fcc_info_t *fip, volatile iop_cpm2_t *io,
+ volatile cpm2_map_t *immap);
+static void init_fcc_param(fcc_info_t *fip, struct net_device *dev,
+ volatile cpm2_map_t *immap);
+
+#ifdef CONFIG_USE_MDIO
+static int mii_queue(struct net_device *dev, int request, void (*func)(uint, struct net_device *));
+static uint mii_send_receive(fcc_info_t *fip, uint cmd);
+static void mii_do_cmd(struct net_device *dev, const phy_cmd_t *c);
+
+/* Make MII read/write commands for the FCC.
+*/
+#define mk_mii_read(REG) (0x60020000 | (((REG) & 0x1f) << 18))
+#define mk_mii_write(REG, VAL) (0x50020000 | (((REG) & 0x1f) << 18) | \
+ ((VAL) & 0xffff))
+#define mk_mii_end 0
+#endif /* CONFIG_USE_MDIO */
+
+
+static int
+fcc_enet_start_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+ struct fcc_enet_private *cep = (struct fcc_enet_private *)dev->priv;
+ volatile cbd_t *bdp;
+
+ /* Fill in a Tx ring entry */
+ bdp = cep->cur_tx;
+
+#ifndef final_version
+ if (!cep->tx_free || (bdp->cbd_sc & BD_ENET_TX_READY)) {
+ /* Ooops. All transmit buffers are full. Bail out.
+ * This should not happen, since the tx queue should be stopped.
+ */
+ printk("%s: tx queue full!.\n", dev->name);
+ return 1;
+ }
+#endif
+
+ /* Clear all of the status flags. */
+ bdp->cbd_sc &= ~BD_ENET_TX_STATS;
+
+ /* If the frame is short, tell CPM to pad it. */
+ if (skb->len <= ETH_ZLEN)
+ bdp->cbd_sc |= BD_ENET_TX_PAD;
+ else
+ bdp->cbd_sc &= ~BD_ENET_TX_PAD;
+
+ /* Set buffer length and buffer pointer. */
+ bdp->cbd_datlen = skb->len;
+ bdp->cbd_bufaddr = __pa(skb->data);
+
+ spin_lock_irq(&cep->lock);
+
+ /* Save skb pointer. */
+ cep->tx_skbuff[cep->skb_cur] = skb;
+
+ cep->stats.tx_bytes += skb->len;
+ cep->skb_cur = (cep->skb_cur+1) & TX_RING_MOD_MASK;
+
+ /* Send it on its way. Tell CPM its ready, interrupt when done,
+ * its the last BD of the frame, and to put the CRC on the end.
+ */
+ bdp->cbd_sc |= (BD_ENET_TX_READY | BD_ENET_TX_INTR | BD_ENET_TX_LAST | BD_ENET_TX_TC);
+
+#if 0
+ /* Errata says don't do this. */
+ cep->fccp->fcc_ftodr = 0x8000;
+#endif
+ dev->trans_start = jiffies;
+
+ /* If this was the last BD in the ring, start at the beginning again. */
+ if (bdp->cbd_sc & BD_ENET_TX_WRAP)
+ bdp = cep->tx_bd_base;
+ else
+ bdp++;
+
+ if (!--cep->tx_free)
+ netif_stop_queue(dev);
+
+ cep->cur_tx = (cbd_t *)bdp;
+
+ spin_unlock_irq(&cep->lock);
+
+ return 0;
+}
+
+
+static void
+fcc_enet_timeout(struct net_device *dev)
+{
+ struct fcc_enet_private *cep = (struct fcc_enet_private *)dev->priv;
+
+ printk("%s: transmit timed out.\n", dev->name);
+ cep->stats.tx_errors++;
+#ifndef final_version
+ {
+ int i;
+ cbd_t *bdp;
+ printk(" Ring data dump: cur_tx %p tx_free %d cur_rx %p.\n",
+ cep->cur_tx, cep->tx_free,
+ cep->cur_rx);
+ bdp = cep->tx_bd_base;
+ printk(" Tx @base %p :\n", bdp);
+ for (i = 0 ; i < TX_RING_SIZE; i++, bdp++)
+ printk("%04x %04x %08x\n",
+ bdp->cbd_sc,
+ bdp->cbd_datlen,
+ bdp->cbd_bufaddr);
+ bdp = cep->rx_bd_base;
+ printk(" Rx @base %p :\n", bdp);
+ for (i = 0 ; i < RX_RING_SIZE; i++, bdp++)
+ printk("%04x %04x %08x\n",
+ bdp->cbd_sc,
+ bdp->cbd_datlen,
+ bdp->cbd_bufaddr);
+ }
+#endif
+ if (cep->tx_free)
+ netif_wake_queue(dev);
+}
+
+/* The interrupt handler. */
+static irqreturn_t
+fcc_enet_interrupt(int irq, void * dev_id, struct pt_regs * regs)
+{
+ struct net_device *dev = dev_id;
+ volatile struct fcc_enet_private *cep;
+ volatile cbd_t *bdp;
+ ushort int_events;
+ int must_restart;
+
+ cep = (struct fcc_enet_private *)dev->priv;
+
+ /* Get the interrupt events that caused us to be here.
+ */
+ int_events = cep->fccp->fcc_fcce;
+ cep->fccp->fcc_fcce = (int_events & cep->fccp->fcc_fccm);
+ must_restart = 0;
+
+#ifdef PHY_INTERRUPT
+ /* We have to be careful here to make sure that we aren't
+ * interrupted by a PHY interrupt.
+ */
+ disable_irq_nosync(PHY_INTERRUPT);
+#endif
+
+ /* Handle receive event in its own function.
+ */
+ if (int_events & FCC_ENET_RXF)
+ fcc_enet_rx(dev_id);
+
+ /* Check for a transmit error. The manual is a little unclear
+ * about this, so the debug code until I get it figured out. It
+ * appears that if TXE is set, then TXB is not set. However,
+ * if carrier sense is lost during frame transmission, the TXE
+ * bit is set, "and continues the buffer transmission normally."
+ * I don't know if "normally" implies TXB is set when the buffer
+ * descriptor is closed.....trial and error :-).
+ */
+
+ /* Transmit OK, or non-fatal error. Update the buffer descriptors.
+ */
+ if (int_events & (FCC_ENET_TXE | FCC_ENET_TXB)) {
+ spin_lock(&cep->lock);
+ bdp = cep->dirty_tx;
+ while ((bdp->cbd_sc&BD_ENET_TX_READY)==0) {
+ if (cep->tx_free == TX_RING_SIZE)
+ break;
+
+ if (bdp->cbd_sc & BD_ENET_TX_HB) /* No heartbeat */
+ cep->stats.tx_heartbeat_errors++;
+ if (bdp->cbd_sc & BD_ENET_TX_LC) /* Late collision */
+ cep->stats.tx_window_errors++;
+ if (bdp->cbd_sc & BD_ENET_TX_RL) /* Retrans limit */
+ cep->stats.tx_aborted_errors++;
+ if (bdp->cbd_sc & BD_ENET_TX_UN) /* Underrun */
+ cep->stats.tx_fifo_errors++;
+ if (bdp->cbd_sc & BD_ENET_TX_CSL) /* Carrier lost */
+ cep->stats.tx_carrier_errors++;
+
+
+ /* No heartbeat or Lost carrier are not really bad errors.
+ * The others require a restart transmit command.
+ */
+ if (bdp->cbd_sc &
+ (BD_ENET_TX_LC | BD_ENET_TX_RL | BD_ENET_TX_UN)) {
+ must_restart = 1;
+ cep->stats.tx_errors++;
+ }
+
+ cep->stats.tx_packets++;
+
+ /* Deferred means some collisions occurred during transmit,
+ * but we eventually sent the packet OK.
+ */
+ if (bdp->cbd_sc & BD_ENET_TX_DEF)
+ cep->stats.collisions++;
+
+ /* Free the sk buffer associated with this last transmit. */
+ dev_kfree_skb_irq(cep->tx_skbuff[cep->skb_dirty]);
+ cep->tx_skbuff[cep->skb_dirty] = NULL;
+ cep->skb_dirty = (cep->skb_dirty + 1) & TX_RING_MOD_MASK;
+
+ /* Update pointer to next buffer descriptor to be transmitted. */
+ if (bdp->cbd_sc & BD_ENET_TX_WRAP)
+ bdp = cep->tx_bd_base;
+ else
+ bdp++;
+
+ /* I don't know if we can be held off from processing these
+ * interrupts for more than one frame time. I really hope
+ * not. In such a case, we would now want to check the
+ * currently available BD (cur_tx) and determine if any
+ * buffers between the dirty_tx and cur_tx have also been
+ * sent. We would want to process anything in between that
+ * does not have BD_ENET_TX_READY set.
+ */
+
+ /* Since we have freed up a buffer, the ring is no longer
+ * full.
+ */
+ if (!cep->tx_free++) {
+ if (netif_queue_stopped(dev)) {
+ netif_wake_queue(dev);
+ }
+ }
+
+ cep->dirty_tx = (cbd_t *)bdp;
+ }
+
+ if (must_restart) {
+ volatile cpm_cpm2_t *cp;
+
+ /* Some transmit errors cause the transmitter to shut
+ * down. We now issue a restart transmit. Since the
+ * errors close the BD and update the pointers, the restart
+ * _should_ pick up without having to reset any of our
+ * pointers either. Also, To workaround 8260 device erratum
+ * CPM37, we must disable and then re-enable the transmitter
+ * following a Late Collision, Underrun, or Retry Limit error.
+ */
+ cep->fccp->fcc_gfmr &= ~FCC_GFMR_ENT;
+ udelay(10); /* wait a few microseconds just on principle */
+ cep->fccp->fcc_gfmr |= FCC_GFMR_ENT;
+
+ cp = cpmp;
+ cp->cp_cpcr =
+ mk_cr_cmd(cep->fip->fc_cpmpage, cep->fip->fc_cpmblock,
+ 0x0c, CPM_CR_RESTART_TX) | CPM_CR_FLG;
+ while (cp->cp_cpcr & CPM_CR_FLG);
+ }
+ spin_unlock(&cep->lock);
+ }
+
+ /* Check for receive busy, i.e. packets coming but no place to
+ * put them.
+ */
+ if (int_events & FCC_ENET_BSY) {
+ cep->fccp->fcc_fcce = FCC_ENET_BSY;
+ cep->stats.rx_dropped++;
+ }
+
+#ifdef PHY_INTERRUPT
+ enable_irq(PHY_INTERRUPT);
+#endif
+ return IRQ_HANDLED;
+}
+
+/* During a receive, the cur_rx points to the current incoming buffer.
+ * When we update through the ring, if the next incoming buffer has
+ * not been given to the system, we just set the empty indicator,
+ * effectively tossing the packet.
+ */
+static int
+fcc_enet_rx(struct net_device *dev)
+{
+ struct fcc_enet_private *cep;
+ volatile cbd_t *bdp;
+ struct sk_buff *skb;
+ ushort pkt_len;
+
+ cep = (struct fcc_enet_private *)dev->priv;
+
+ /* First, grab all of the stats for the incoming packet.
+ * These get messed up if we get called due to a busy condition.
+ */
+ bdp = cep->cur_rx;
+
+for (;;) {
+ if (bdp->cbd_sc & BD_ENET_RX_EMPTY)
+ break;
+
+#ifndef final_version
+ /* Since we have allocated space to hold a complete frame, both
+ * the first and last indicators should be set.
+ */
+ if ((bdp->cbd_sc & (BD_ENET_RX_FIRST | BD_ENET_RX_LAST)) !=
+ (BD_ENET_RX_FIRST | BD_ENET_RX_LAST))
+ printk("CPM ENET: rcv is not first+last\n");
+#endif
+
+ /* Frame too long or too short. */
+ if (bdp->cbd_sc & (BD_ENET_RX_LG | BD_ENET_RX_SH))
+ cep->stats.rx_length_errors++;
+ if (bdp->cbd_sc & BD_ENET_RX_NO) /* Frame alignment */
+ cep->stats.rx_frame_errors++;
+ if (bdp->cbd_sc & BD_ENET_RX_CR) /* CRC Error */
+ cep->stats.rx_crc_errors++;
+ if (bdp->cbd_sc & BD_ENET_RX_OV) /* FIFO overrun */
+ cep->stats.rx_crc_errors++;
+ if (bdp->cbd_sc & BD_ENET_RX_CL) /* Late Collision */
+ cep->stats.rx_frame_errors++;
+
+ if (!(bdp->cbd_sc &
+ (BD_ENET_RX_LG | BD_ENET_RX_SH | BD_ENET_RX_NO | BD_ENET_RX_CR
+ | BD_ENET_RX_OV | BD_ENET_RX_CL)))
+ {
+ /* Process the incoming frame. */
+ cep->stats.rx_packets++;
+
+ /* Remove the FCS from the packet length. */
+ pkt_len = bdp->cbd_datlen - 4;
+ cep->stats.rx_bytes += pkt_len;
+
+ /* This does 16 byte alignment, much more than we need. */
+ skb = dev_alloc_skb(pkt_len);
+
+ if (skb == NULL) {
+ printk("%s: Memory squeeze, dropping packet.\n", dev->name);
+ cep->stats.rx_dropped++;
+ }
+ else {
+ skb->dev = dev;
+ skb_put(skb,pkt_len); /* Make room */
+ eth_copy_and_sum(skb,
+ (unsigned char *)__va(bdp->cbd_bufaddr),
+ pkt_len, 0);
+ skb->protocol=eth_type_trans(skb,dev);
+ netif_rx(skb);
+ }
+ }
+
+ /* Clear the status flags for this buffer. */
+ bdp->cbd_sc &= ~BD_ENET_RX_STATS;
+
+ /* Mark the buffer empty. */
+ bdp->cbd_sc |= BD_ENET_RX_EMPTY;
+
+ /* Update BD pointer to next entry. */
+ if (bdp->cbd_sc & BD_ENET_RX_WRAP)
+ bdp = cep->rx_bd_base;
+ else
+ bdp++;
+
+ }
+ cep->cur_rx = (cbd_t *)bdp;
+
+ return 0;
+}
+
+static int
+fcc_enet_close(struct net_device *dev)
+{
+#ifdef CONFIG_USE_MDIO
+ struct fcc_enet_private *fep = dev->priv;
+#endif
+
+ netif_stop_queue(dev);
+ fcc_stop(dev);
+#ifdef CONFIG_USE_MDIO
+ if (fep->phy)
+ mii_do_cmd(dev, fep->phy->shutdown);
+#endif
+
+ return 0;
+}
+
+static struct net_device_stats *fcc_enet_get_stats(struct net_device *dev)
+{
+ struct fcc_enet_private *cep = (struct fcc_enet_private *)dev->priv;
+
+ return &cep->stats;
+}
+
+#ifdef CONFIG_USE_MDIO
+
+/* NOTE: Most of the following comes from the FEC driver for 860. The
+ * overall structure of MII code has been retained (as it's proved stable
+ * and well-tested), but actual transfer requests are processed "at once"
+ * instead of being queued (there's no interrupt-driven MII transfer
+ * mechanism, one has to toggle the data/clock bits manually).
+ */
+static int
+mii_queue(struct net_device *dev, int regval, void (*func)(uint, struct net_device *))
+{
+ struct fcc_enet_private *fep;
+ int retval, tmp;
+
+ /* Add PHY address to register command. */
+ fep = dev->priv;
+ regval |= fep->phy_addr << 23;
+
+ retval = 0;
+
+ tmp = mii_send_receive(fep->fip, regval);
+ if (func)
+ func(tmp, dev);
+
+ return retval;
+}
+
+static void mii_do_cmd(struct net_device *dev, const phy_cmd_t *c)
+{
+ int k;
+
+ if(!c)
+ return;
+
+ for(k = 0; (c+k)->mii_data != mk_mii_end; k++)
+ mii_queue(dev, (c+k)->mii_data, (c+k)->funct);
+}
+
+static void mii_parse_sr(uint mii_reg, struct net_device *dev)
+{
+ volatile struct fcc_enet_private *fep = dev->priv;
+ uint s = fep->phy_status;
+
+ s &= ~(PHY_STAT_LINK | PHY_STAT_FAULT | PHY_STAT_ANC);
+
+ if (mii_reg & BMSR_LSTATUS)
+ s |= PHY_STAT_LINK;
+ if (mii_reg & BMSR_RFAULT)
+ s |= PHY_STAT_FAULT;
+ if (mii_reg & BMSR_ANEGCOMPLETE)
+ s |= PHY_STAT_ANC;
+
+ fep->phy_status = s;
+}
+
+static void mii_parse_cr(uint mii_reg, struct net_device *dev)
+{
+ volatile struct fcc_enet_private *fep = dev->priv;
+ uint s = fep->phy_status;
+
+ s &= ~(PHY_CONF_ANE | PHY_CONF_LOOP);
+
+ if (mii_reg & BMCR_ANENABLE)
+ s |= PHY_CONF_ANE;
+ if (mii_reg & BMCR_LOOPBACK)
+ s |= PHY_CONF_LOOP;
+
+ fep->phy_status = s;
+}
+
+static void mii_parse_anar(uint mii_reg, struct net_device *dev)
+{
+ volatile struct fcc_enet_private *fep = dev->priv;
+ uint s = fep->phy_status;
+
+ s &= ~(PHY_CONF_SPMASK);
+
+ if (mii_reg & ADVERTISE_10HALF)
+ s |= PHY_CONF_10HDX;
+ if (mii_reg & ADVERTISE_10FULL)
+ s |= PHY_CONF_10FDX;
+ if (mii_reg & ADVERTISE_100HALF)
+ s |= PHY_CONF_100HDX;
+ if (mii_reg & ADVERTISE_100FULL)
+ s |= PHY_CONF_100FDX;
+
+ fep->phy_status = s;
+}
+
+/* ------------------------------------------------------------------------- */
+/* Generic PHY support. Should work for all PHYs, but does not support link
+ * change interrupts.
+ */
+#ifdef CONFIG_FCC_GENERIC_PHY
+
+static phy_info_t phy_info_generic = {
+ 0x00000000, /* 0-->match any PHY */
+ "GENERIC",
+
+ (const phy_cmd_t []) { /* config */
+ /* advertise only half-duplex capabilities */
+ { mk_mii_write(MII_ADVERTISE, MII_ADVERTISE_HALF),
+ mii_parse_anar },
+
+ /* enable auto-negotiation */
+ { mk_mii_write(MII_BMCR, BMCR_ANENABLE), mii_parse_cr },
+ { mk_mii_end, }
+ },
+ (const phy_cmd_t []) { /* startup */
+ /* restart auto-negotiation */
+ { mk_mii_write(MII_BMCR, BMCR_ANENABLE | BMCR_ANRESTART),
+ NULL },
+ { mk_mii_end, }
+ },
+ (const phy_cmd_t []) { /* ack_int */
+ /* We don't actually use the ack_int table with a generic
+ * PHY, but putting a reference to mii_parse_sr here keeps
+ * us from getting a compiler warning about unused static
+ * functions in the case where we only compile in generic
+ * PHY support.
+ */
+ { mk_mii_read(MII_BMSR), mii_parse_sr },
+ { mk_mii_end, }
+ },
+ (const phy_cmd_t []) { /* shutdown */
+ { mk_mii_end, }
+ },
+};
+#endif /* ifdef CONFIG_FCC_GENERIC_PHY */
+
+/* ------------------------------------------------------------------------- */
+/* The Level one LXT970 is used by many boards */
+
+#ifdef CONFIG_FCC_LXT970
+
+#define MII_LXT970_MIRROR 16 /* Mirror register */
+#define MII_LXT970_IER 17 /* Interrupt Enable Register */
+#define MII_LXT970_ISR 18 /* Interrupt Status Register */
+#define MII_LXT970_CONFIG 19 /* Configuration Register */
+#define MII_LXT970_CSR 20 /* Chip Status Register */
+
+static void mii_parse_lxt970_csr(uint mii_reg, struct net_device *dev)
+{
+ volatile struct fcc_enet_private *fep = dev->priv;
+ uint s = fep->phy_status;
+
+ s &= ~(PHY_STAT_SPMASK);
+
+ if (mii_reg & 0x0800) {
+ if (mii_reg & 0x1000)
+ s |= PHY_STAT_100FDX;
+ else
+ s |= PHY_STAT_100HDX;
+ } else {
+ if (mii_reg & 0x1000)
+ s |= PHY_STAT_10FDX;
+ else
+ s |= PHY_STAT_10HDX;
+ }
+
+ fep->phy_status = s;
+}
+
+static phy_info_t phy_info_lxt970 = {
+ 0x07810000,
+ "LXT970",
+
+ (const phy_cmd_t []) { /* config */
+#if 0
+// { mk_mii_write(MII_ADVERTISE, 0x0021), NULL },
+
+ /* Set default operation of 100-TX....for some reason
+ * some of these bits are set on power up, which is wrong.
+ */
+ { mk_mii_write(MII_LXT970_CONFIG, 0), NULL },
+#endif
+ { mk_mii_read(MII_BMCR), mii_parse_cr },
+ { mk_mii_read(MII_ADVERTISE), mii_parse_anar },
+ { mk_mii_end, }
+ },
+ (const phy_cmd_t []) { /* startup - enable interrupts */
+ { mk_mii_write(MII_LXT970_IER, 0x0002), NULL },
+ { mk_mii_write(MII_BMCR, 0x1200), NULL }, /* autonegotiate */
+ { mk_mii_end, }
+ },
+ (const phy_cmd_t []) { /* ack_int */
+ /* read SR and ISR to acknowledge */
+
+ { mk_mii_read(MII_BMSR), mii_parse_sr },
+ { mk_mii_read(MII_LXT970_ISR), NULL },
+
+ /* find out the current status */
+
+ { mk_mii_read(MII_LXT970_CSR), mii_parse_lxt970_csr },
+ { mk_mii_end, }
+ },
+ (const phy_cmd_t []) { /* shutdown - disable interrupts */
+ { mk_mii_write(MII_LXT970_IER, 0x0000), NULL },
+ { mk_mii_end, }
+ },
+};
+
+#endif /* CONFIG_FEC_LXT970 */
+
+/* ------------------------------------------------------------------------- */
+/* The Level one LXT971 is used on some of my custom boards */
+
+#ifdef CONFIG_FCC_LXT971
+
+/* register definitions for the 971 */
+
+#define MII_LXT971_PCR 16 /* Port Control Register */
+#define MII_LXT971_SR2 17 /* Status Register 2 */
+#define MII_LXT971_IER 18 /* Interrupt Enable Register */
+#define MII_LXT971_ISR 19 /* Interrupt Status Register */
+#define MII_LXT971_LCR 20 /* LED Control Register */
+#define MII_LXT971_TCR 30 /* Transmit Control Register */
+
+/*
+ * I had some nice ideas of running the MDIO faster...
+ * The 971 should support 8MHz and I tried it, but things acted really
+ * weird, so 2.5 MHz ought to be enough for anyone...
+ */
+
+static void mii_parse_lxt971_sr2(uint mii_reg, struct net_device *dev)
+{
+ volatile struct fcc_enet_private *fep = dev->priv;
+ uint s = fep->phy_status;
+
+ s &= ~(PHY_STAT_SPMASK);
+
+ if (mii_reg & 0x4000) {
+ if (mii_reg & 0x0200)
+ s |= PHY_STAT_100FDX;
+ else
+ s |= PHY_STAT_100HDX;
+ } else {
+ if (mii_reg & 0x0200)
+ s |= PHY_STAT_10FDX;
+ else
+ s |= PHY_STAT_10HDX;
+ }
+ if (mii_reg & 0x0008)
+ s |= PHY_STAT_FAULT;
+
+ fep->phy_status = s;
+}
+
+static phy_info_t phy_info_lxt971 = {
+ 0x0001378e,
+ "LXT971",
+
+ (const phy_cmd_t []) { /* config */
+ /* configure link capabilities to advertise */
+ { mk_mii_write(MII_ADVERTISE, MII_ADVERTISE_DEFAULT),
+ mii_parse_anar },
+
+ /* enable auto-negotiation */
+ { mk_mii_write(MII_BMCR, BMCR_ANENABLE), mii_parse_cr },
+ { mk_mii_end, }
+ },
+ (const phy_cmd_t []) { /* startup - enable interrupts */
+ { mk_mii_write(MII_LXT971_IER, 0x00f2), NULL },
+
+ /* restart auto-negotiation */
+ { mk_mii_write(MII_BMCR, BMCR_ANENABLE | BMCR_ANRESTART),
+ NULL },
+ { mk_mii_end, }
+ },
+ (const phy_cmd_t []) { /* ack_int */
+ /* find out the current status */
+ { mk_mii_read(MII_BMSR), NULL },
+ { mk_mii_read(MII_BMSR), mii_parse_sr },
+ { mk_mii_read(MII_LXT971_SR2), mii_parse_lxt971_sr2 },
+
+ /* we only need to read ISR to acknowledge */
+ { mk_mii_read(MII_LXT971_ISR), NULL },
+ { mk_mii_end, }
+ },
+ (const phy_cmd_t []) { /* shutdown - disable interrupts */
+ { mk_mii_write(MII_LXT971_IER, 0x0000), NULL },
+ { mk_mii_end, }
+ },
+};
+
+#endif /* CONFIG_FCC_LXT971 */
+
+/* ------------------------------------------------------------------------- */
+/* The Quality Semiconductor QS6612 is used on the RPX CLLF */
+
+#ifdef CONFIG_FCC_QS6612
+
+/* register definitions */
+
+#define MII_QS6612_MCR 17 /* Mode Control Register */
+#define MII_QS6612_FTR 27 /* Factory Test Register */
+#define MII_QS6612_MCO 28 /* Misc. Control Register */
+#define MII_QS6612_ISR 29 /* Interrupt Source Register */
+#define MII_QS6612_IMR 30 /* Interrupt Mask Register */
+#define MII_QS6612_PCR 31 /* 100BaseTx PHY Control Reg. */
+
+static void mii_parse_qs6612_pcr(uint mii_reg, struct net_device *dev)
+{
+ volatile struct fcc_enet_private *fep = dev->priv;
+ uint s = fep->phy_status;
+
+ s &= ~(PHY_STAT_SPMASK);
+
+ switch((mii_reg >> 2) & 7) {
+ case 1: s |= PHY_STAT_10HDX; break;
+ case 2: s |= PHY_STAT_100HDX; break;
+ case 5: s |= PHY_STAT_10FDX; break;
+ case 6: s |= PHY_STAT_100FDX; break;
+ }
+
+ fep->phy_status = s;
+}
+
+static phy_info_t phy_info_qs6612 = {
+ 0x00181440,
+ "QS6612",
+
+ (const phy_cmd_t []) { /* config */
+// { mk_mii_write(MII_ADVERTISE, 0x061), NULL }, /* 10 Mbps */
+
+ /* The PHY powers up isolated on the RPX,
+ * so send a command to allow operation.
+ */
+
+ { mk_mii_write(MII_QS6612_PCR, 0x0dc0), NULL },</