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 /arch/ppc/8260_io/fcc_enet.c | |
download | kernel_samsung_smdk4412-1da177e4c3f41524e886b7f1b8a0c1fc7321cac2.zip kernel_samsung_smdk4412-1da177e4c3f41524e886b7f1b8a0c1fc7321cac2.tar.gz kernel_samsung_smdk4412-1da177e4c3f41524e886b7f1b8a0c1fc7321cac2.tar.bz2 |
Linux-2.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 'arch/ppc/8260_io/fcc_enet.c')
-rw-r--r-- | arch/ppc/8260_io/fcc_enet.c | 2395 |
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 0000000..2086c6a --- /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 }, + + /* parse cr and anar to get some info */ + + { 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_QS6612_IMR, 0x003a), NULL }, + { mk_mii_write(MII_BMCR, 0x1200), NULL }, /* autonegotiate */ + { mk_mii_end, } + }, + (const phy_cmd_t []) { /* ack_int */ + + /* we need to read ISR, SR and ANER to acknowledge */ + + { mk_mii_read(MII_QS6612_ISR), NULL }, + { mk_mii_read(MII_BMSR), mii_parse_sr }, + { mk_mii_read(MII_EXPANSION), NULL }, + + /* read pcr to get info */ + + { mk_mii_read(MII_QS6612_PCR), mii_parse_qs6612_pcr }, + { mk_mii_end, } + }, + (const phy_cmd_t []) { /* shutdown - disable interrupts */ + { mk_mii_write(MII_QS6612_IMR, 0x0000), NULL }, + { mk_mii_end, } + }, +}; + + +#endif /* CONFIG_FEC_QS6612 */ + + +/* ------------------------------------------------------------------------- */ +/* The Davicom DM9131 is used on the HYMOD board */ + +#ifdef CONFIG_FCC_DM9131 + +/* register definitions */ + +#define MII_DM9131_ACR 16 /* Aux. Config Register */ +#define MII_DM9131_ACSR 17 /* Aux. Config/Status Register */ +#define MII_DM9131_10TCSR 18 /* 10BaseT Config/Status Reg. */ +#define MII_DM9131_INTR 21 /* Interrupt Register */ +#define MII_DM9131_RECR 22 /* Receive Error Counter Reg. */ +#define MII_DM9131_DISCR 23 /* Disconnect Counter Register */ + +static void mii_parse_dm9131_acsr(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 >> 12) & 0xf) { + case 1: s |= PHY_STAT_10HDX; break; + case 2: s |= PHY_STAT_10FDX; break; + case 4: s |= PHY_STAT_100HDX; break; + case 8: s |= PHY_STAT_100FDX; break; + } + + fep->phy_status = s; +} + +static phy_info_t phy_info_dm9131 = { + 0x00181b80, + "DM9131", + + (const phy_cmd_t []) { /* config */ + /* parse cr and anar to get some info */ + { 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_DM9131_INTR, 0x0002), NULL }, + { mk_mii_write(MII_BMCR, 0x1200), NULL }, /* autonegotiate */ + { mk_mii_end, } + }, + (const phy_cmd_t []) { /* ack_int */ + + /* we need to read INTR, SR and ANER to acknowledge */ + + { mk_mii_read(MII_DM9131_INTR), NULL }, + { mk_mii_read(MII_BMSR), mii_parse_sr }, + { mk_mii_read(MII_EXPANSION), NULL }, + + /* read acsr to get info */ + + { mk_mii_read(MII_DM9131_ACSR), mii_parse_dm9131_acsr }, + { mk_mii_end, } + }, + (const phy_cmd_t []) { /* shutdown - disable interrupts */ + { mk_mii_write(MII_DM9131_INTR, 0x0f00), NULL }, + { mk_mii_end, } + }, +}; + + +#endif /* CONFIG_FEC_DM9131 */ +#ifdef CONFIG_FCC_DM9161 +/* ------------------------------------------------------------------------- */ +/* DM9161 Control register values */ +#define MIIM_DM9161_CR_STOP 0x0400 +#define MIIM_DM9161_CR_RSTAN 0x1200 + +#define MIIM_DM9161_SCR 0x10 +#define MIIM_DM9161_SCR_INIT 0x0610 + +/* DM9161 Specified Configuration and Status Register */ +#define MIIM_DM9161_SCSR 0x11 +#define MIIM_DM9161_SCSR_100F 0x8000 +#define MIIM_DM9161_SCSR_100H 0x4000 +#define MIIM_DM9161_SCSR_10F 0x2000 +#define MIIM_DM9161_SCSR_10H 0x1000 +/* DM9161 10BT register */ +#define MIIM_DM9161_10BTCSR 0x12 +#define MIIM_DM9161_10BTCSR_INIT 0x7800 +/* DM9161 Interrupt Register */ +#define MIIM_DM9161_INTR 0x15 +#define MIIM_DM9161_INTR_PEND 0x8000 +#define MIIM_DM9161_INTR_DPLX_MASK 0x0800 +#define MIIM_DM9161_INTR_SPD_MASK 0x0400 +#define MIIM_DM9161_INTR_LINK_MASK 0x0200 +#define MIIM_DM9161_INTR_MASK 0x0100 +#define MIIM_DM9161_INTR_DPLX_CHANGE 0x0010 +#define MIIM_DM9161_INTR_SPD_CHANGE 0x0008 +#define MIIM_DM9161_INTR_LINK_CHANGE 0x0004 +#define MIIM_DM9161_INTR_INIT 0x0000 +#define MIIM_DM9161_INTR_STOP \ +(MIIM_DM9161_INTR_DPLX_MASK | MIIM_DM9161_INTR_SPD_MASK \ + | MIIM_DM9161_INTR_LINK_MASK | MIIM_DM9161_INTR_MASK) + +static void mii_parse_dm9161_sr(uint mii_reg, struct net_device * dev) +{ + volatile struct fcc_enet_private *fep = dev->priv; + uint regstat, timeout=0xffff; + + while(!(mii_reg & 0x0020) && timeout--) + { + regstat=mk_mii_read(MII_BMSR); + regstat |= fep->phy_addr <<23; + mii_reg = mii_send_receive(fep->fip,regstat); + } + + mii_parse_sr(mii_reg, dev); +} + +static void mii_parse_dm9161_scsr(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 >>12) & 0xf) { + case 1: + { + s |= PHY_STAT_10HDX; + printk("10BaseT Half Duplex\n"); + break; + } + case 2: + { + s |= PHY_STAT_10FDX; + printk("10BaseT Full Duplex\n"); + break; + } + case 4: + { + s |= PHY_STAT_100HDX; + printk("100BaseT Half Duplex\n"); + break; + } + case 8: + { + s |= PHY_STAT_100FDX; + printk("100BaseT Full Duplex\n"); + break; + } + } + + fep->phy_status = s; + +} + +static void mii_dm9161_wait(uint mii_reg, struct net_device *dev) +{ + int timeout = HZ; + + /* Davicom takes a bit to come up after a reset, + * so wait here for a bit */ + set_current_state(TASK_UNINTERRUPTIBLE); + schedule_timeout(timeout); +} + +static phy_info_t phy_info_dm9161 = { + 0x00181b88, + "Davicom DM9161E", + (const phy_cmd_t[]) { /* config */ + { mk_mii_write(MII_BMCR, MIIM_DM9161_CR_STOP), NULL}, + /* Do not bypass the scrambler/descrambler */ + { mk_mii_write(MIIM_DM9161_SCR, MIIM_DM9161_SCR_INIT), NULL}, + /* Configure 10BTCSR register */ + { mk_mii_write(MIIM_DM9161_10BTCSR, MIIM_DM9161_10BTCSR_INIT),NULL}, + /* Configure some basic stuff */ + { mk_mii_write(MII_BMCR, 0x1000), NULL}, + { mk_mii_read(MII_BMCR), mii_parse_cr }, + { mk_mii_read(MII_ADVERTISE), mii_parse_anar }, + { mk_mii_end,} + }, + (const phy_cmd_t[]) { /* startup */ + /* Restart Auto Negotiation */ + { mk_mii_write(MII_BMCR, MIIM_DM9161_CR_RSTAN), NULL}, + /* Status is read once to clear old link state */ + { mk_mii_read(MII_BMSR), mii_dm9161_wait}, + /* Auto-negotiate */ + { mk_mii_read(MII_BMSR), mii_parse_dm9161_sr}, + /* Read the status */ + { mk_mii_read(MIIM_DM9161_SCSR), mii_parse_dm9161_scsr}, + /* Clear any pending interrupts */ + { mk_mii_read(MIIM_DM9161_INTR), NULL}, + /* Enable Interrupts */ + { mk_mii_write(MIIM_DM9161_INTR, MIIM_DM9161_INTR_INIT), NULL}, + { mk_mii_end,} + }, + (const phy_cmd_t[]) { /* ack_int */ + { mk_mii_read(MIIM_DM9161_INTR), NULL}, +#if 0 + { mk_mii_read(MII_BMSR), NULL}, + { mk_mii_read(MII_BMSR), mii_parse_dm9161_sr}, + { mk_mii_read(MIIM_DM9161_SCSR), mii_parse_dm9161_scsr}, +#endif + { mk_mii_end,} + }, + (const phy_cmd_t[]) { /* shutdown */ + { mk_mii_read(MIIM_DM9161_INTR),NULL}, + { mk_mii_write(MIIM_DM9161_INTR, MIIM_DM9161_INTR_STOP), NULL}, + { mk_mii_end,} + }, +}; +#endif /* CONFIG_FCC_DM9161 */ + +static phy_info_t *phy_info[] = { + +#ifdef CONFIG_FCC_LXT970 + &phy_info_lxt970, +#endif /* CONFIG_FEC_LXT970 */ + +#ifdef CONFIG_FCC_LXT971 + &phy_info_lxt971, +#endif /* CONFIG_FEC_LXT971 */ + +#ifdef CONFIG_FCC_QS6612 + &phy_info_qs6612, +#endif /* CONFIG_FEC_QS6612 */ + +#ifdef CONFIG_FCC_DM9131 + &phy_info_dm9131, +#endif /* CONFIG_FEC_DM9131 */ + +#ifdef CONFIG_FCC_DM9161 + &phy_info_dm9161, +#endif /* CONFIG_FCC_DM9161 */ + +#ifdef CONFIG_FCC_GENERIC_PHY + /* Generic PHY support. This must be the last PHY in the table. + * It will be used to support any PHY that doesn't match a previous + * entry in the table. + */ + &phy_info_generic, +#endif /* CONFIG_FCC_GENERIC_PHY */ + + NULL +}; + +static void mii_display_status(void *data) +{ + struct net_device *dev = data; + volatile struct fcc_enet_private *fep = dev->priv; + uint s = fep->phy_status; + + if (!fep->link && !fep->old_link) { + /* Link is still down - don't print anything */ + return; + } + + printk("%s: status: ", dev->name); + + if (!fep->link) { + printk("link down"); + } else { + printk("link up"); + + switch(s & PHY_STAT_SPMASK) { + case PHY_STAT_100FDX: printk(", 100 Mbps Full Duplex"); break; + case PHY_STAT_100HDX: printk(", 100 Mbps Half Duplex"); break; + case PHY_STAT_10FDX: printk(", 10 Mbps Full Duplex"); break; + case PHY_STAT_10HDX: printk(", 10 Mbps Half Duplex"); break; + default: + printk(", Unknown speed/duplex"); + } + + if (s & PHY_STAT_ANC) + printk(", auto-negotiation complete"); + } + + if (s & PHY_STAT_FAULT) + printk(", remote fault"); + + printk(".\n"); +} + +static void mii_display_config(void *data) +{ + struct net_device *dev = data; + volatile struct fcc_enet_private *fep = dev->priv; + uint s = fep->phy_status; + + printk("%s: config: auto-negotiation ", dev->name); + + if (s & PHY_CONF_ANE) + printk("on"); + else + printk("off"); + + if (s & PHY_CONF_100FDX) + printk(", 100FDX"); + if (s & PHY_CONF_100HDX) + printk(", 100HDX"); + if (s & PHY_CONF_10FDX) + printk(", 10FDX"); + if (s & PHY_CONF_10HDX) + printk(", 10HDX"); + if (!(s & PHY_CONF_SPMASK)) + printk(", No speed/duplex selected?"); + + if (s & PHY_CONF_LOOP) + printk(", loopback enabled"); + + printk(".\n"); + + fep->sequence_done = 1; +} + +static void mii_relink(struct net_device *dev) +{ + struct fcc_enet_private *fep = dev->priv; + int duplex = 0; + + fep->old_link = fep->link; + fep->link = (fep->phy_status & PHY_STAT_LINK) ? 1 : 0; + +#ifdef MDIO_DEBUG + printk(" mii_relink: link=%d\n", fep->link); +#endif + + if (fep->link) { + if (fep->phy_status + & (PHY_STAT_100FDX | PHY_STAT_10FDX)) + duplex = 1; + fcc_restart(dev, duplex); +#ifdef MDIO_DEBUG + printk(" mii_relink: duplex=%d\n", duplex); +#endif + } +} + +static void mii_queue_relink(uint mii_reg, struct net_device *dev) +{ + struct fcc_enet_private *fep = dev->priv; + + mii_relink(dev); + + schedule_work(&fep->phy_relink); +} + +static void mii_queue_config(uint mii_reg, struct net_device *dev) +{ + struct fcc_enet_private *fep = dev->priv; + + schedule_work(&fep->phy_display_config); +} + +phy_cmd_t phy_cmd_relink[] = { { mk_mii_read(MII_BMCR), mii_queue_relink }, + { mk_mii_end, } }; +phy_cmd_t phy_cmd_config[] = { { mk_mii_read(MII_BMCR), mii_queue_config }, + { mk_mii_end, } }; + + +/* Read remainder of PHY ID. +*/ +static void +mii_discover_phy3(uint mii_reg, struct net_device *dev) +{ + struct fcc_enet_private *fep; + int i; + + fep = dev->priv; + printk("mii_reg: %08x\n", mii_reg); + fep->phy_id |= (mii_reg & 0xffff); + + for(i = 0; phy_info[i]; i++) + if((phy_info[i]->id == (fep->phy_id >> 4)) || !phy_info[i]->id) + break; + + if(!phy_info[i]) + panic("%s: PHY id 0x%08x is not supported!\n", + dev->name, fep->phy_id); + + fep->phy = phy_info[i]; + fep->phy_id_done = 1; + + printk("%s: Phy @ 0x%x, type %s (0x%08x)\n", + dev->name, fep->phy_addr, fep->phy->name, fep->phy_id); +} + +/* Scan all of the MII PHY addresses looking for someone to respond + * with a valid ID. This usually happens quickly. + */ +static void +mii_discover_phy(uint mii_reg, struct net_device *dev) +{ + struct fcc_enet_private *fep; + uint phytype; + + fep = dev->priv; + + if ((phytype = (mii_reg & 0xffff)) != 0xffff) { + + /* Got first part of ID, now get remainder. */ + fep->phy_id = phytype << 16; + mii_queue(dev, mk_mii_read(MII_PHYSID2), mii_discover_phy3); + } else { + fep->phy_addr++; + if (fep->phy_addr < 32) { + mii_queue(dev, mk_mii_read(MII_PHYSID1), + mii_discover_phy); + } else { + printk("fec: No PHY device found.\n"); + } + } +} +#endif /* CONFIG_USE_MDIO */ + +#ifdef PHY_INTERRUPT +/* This interrupt occurs when the PHY detects a link change. */ +static irqreturn_t +mii_link_interrupt(int irq, void * dev_id, struct pt_regs * regs) +{ + struct net_device *dev = dev_id; + struct fcc_enet_private *fep = dev->priv; + fcc_info_t *fip = fep->fip; + + if (fep->phy) { + /* We don't want to be interrupted by an FCC + * interrupt here. + */ + disable_irq_nosync(fip->fc_interrupt); + + mii_do_cmd(dev, fep->phy->ack_int); + /* restart and display status */ + mii_do_cmd(dev, phy_cmd_relink); + + enable_irq(fip->fc_interrupt); + } + return IRQ_HANDLED; +} +#endif /* ifdef PHY_INTERRUPT */ + +#if 0 /* This should be fixed someday */ +/* Set or clear the multicast filter for this adaptor. + * Skeleton taken from sunlance driver. + * The CPM Ethernet implementation allows Multicast as well as individual + * MAC address filtering. Some of the drivers check to make sure it is + * a group multicast address, and discard those that are not. I guess I + * will do the same for now, but just remove the test if you want + * individual filtering as well (do the upper net layers want or support + * this kind of feature?). + */ +static void +set_multicast_list(struct net_device *dev) +{ + struct fcc_enet_private *cep; + struct dev_mc_list *dmi; + u_char *mcptr, *tdptr; + volatile fcc_enet_t *ep; + int i, j; + + cep = (struct fcc_enet_private *)dev->priv; + +return; + /* Get pointer to FCC area in parameter RAM. + */ + ep = (fcc_enet_t *)dev->base_addr; + + if (dev->flags&IFF_PROMISC) { + + /* Log any net taps. */ + printk("%s: Promiscuous mode enabled.\n", dev->name); + cep->fccp->fcc_fpsmr |= FCC_PSMR_PRO; + } else { + + cep->fccp->fcc_fpsmr &= ~FCC_PSMR_PRO; + + if (dev->flags & IFF_ALLMULTI) { + /* Catch all multicast addresses, so set the + * filter to all 1's. + */ + ep->fen_gaddrh = 0xffffffff; + ep->fen_gaddrl = 0xffffffff; + } + else { + /* Clear filter and add the addresses in the list. + */ + ep->fen_gaddrh = 0; + ep->fen_gaddrl = 0; + + dmi = dev->mc_list; + + for (i=0; i<dev->mc_count; i++, dmi = dmi->next) { + + /* Only support group multicast for now. + */ + if (!(dmi->dmi_addr[0] & 1)) + continue; + + /* The address in dmi_addr is LSB first, + * and taddr is MSB first. We have to + * copy bytes MSB first from dmi_addr. + */ + mcptr = (u_char *)dmi->dmi_addr + 5; + tdptr = (u_char *)&ep->fen_taddrh; + for (j=0; j<6; j++) + *tdptr++ = *mcptr--; + + /* Ask CPM to run CRC and set bit in + * filter mask. + */ + cpmp->cp_cpcr = mk_cr_cmd(cep->fip->fc_cpmpage, + cep->fip->fc_cpmblock, 0x0c, + CPM_CR_SET_GADDR) | CPM_CR_FLG; + udelay(10); + while (cpmp->cp_cpcr & CPM_CR_FLG); + } + } + } +} +#endif /* if 0 */ + + +/* Set the individual MAC address. + */ +int fcc_enet_set_mac_address(struct net_device *dev, void *p) +{ + struct sockaddr *addr= (struct sockaddr *) p; + struct fcc_enet_private *cep; + volatile fcc_enet_t *ep; + unsigned char *eap; + int i; + + cep = (struct fcc_enet_private *)(dev->priv); + ep = cep->ep; + + if (netif_running(dev)) + return -EBUSY; + + memcpy(dev->dev_addr, addr->sa_data, dev->addr_len); + + eap = (unsigned char *) &(ep->fen_paddrh); + for (i=5; i>=0; i--) + *eap++ = addr->sa_data[i]; + + return 0; +} + + +/* Initialize the CPM Ethernet on FCC. + */ +static int __init fec_enet_init(void) +{ + struct net_device *dev; + struct fcc_enet_private *cep; + fcc_info_t *fip; + int i, np, err; + volatile cpm2_map_t *immap; + volatile iop_cpm2_t *io; + + immap = (cpm2_map_t *)CPM_MAP_ADDR; /* and to internal registers */ + io = &immap->im_ioport; + + np = sizeof(fcc_ports) / sizeof(fcc_info_t); + fip = fcc_ports; + + while (np-- > 0) { + /* Create an Ethernet device instance. + */ + dev = alloc_etherdev(sizeof(*cep)); + if (!dev) + return -ENOMEM; + + cep = dev->priv; + spin_lock_init(&cep->lock); + cep->fip = fip; + + init_fcc_shutdown(fip, cep, immap); + init_fcc_ioports(fip, io, immap); + init_fcc_param(fip, dev, immap); + + dev->base_addr = (unsigned long)(cep->ep); + + /* The CPM Ethernet specific entries in the device + * structure. + */ + dev->open = fcc_enet_open; + dev->hard_start_xmit = fcc_enet_start_xmit; + dev->tx_timeout = fcc_enet_timeout; + dev->watchdog_timeo = TX_TIMEOUT; + dev->stop = fcc_enet_close; + dev->get_stats = fcc_enet_get_stats; + /* dev->set_multicast_list = set_multicast_list; */ + dev->set_mac_address = fcc_enet_set_mac_address; + + init_fcc_startup(fip, dev); + + err = register_netdev(dev); + if (err) { + free_netdev(dev); + return err; + } + + printk("%s: FCC ENET Version 0.3, ", dev->name); + for (i=0; i<5; i++) + printk("%02x:", dev->dev_addr[i]); + printk("%02x\n", dev->dev_addr[5]); + +#ifdef CONFIG_USE_MDIO + /* Queue up command to detect the PHY and initialize the + * remainder of the interface. + */ + cep->phy_id_done = 0; + cep->phy_addr = fip->fc_phyaddr; + mii_queue(dev, mk_mii_read(MII_PHYSID1), mii_discover_phy); + INIT_WORK(&cep->phy_relink, mii_display_status, dev); + INIT_WORK(&cep->phy_display_config, mii_display_config, dev); +#endif /* CONFIG_USE_MDIO */ + + fip++; + } + + return 0; +} +module_init(fec_enet_init); + +/* Make sure the device is shut down during initialization. +*/ +static void __init +init_fcc_shutdown(fcc_info_t *fip, struct fcc_enet_private *cep, + volatile cpm2_map_t *immap) +{ + volatile fcc_enet_t *ep; + volatile fcc_t *fccp; + + /* Get pointer to FCC area in parameter RAM. + */ + ep = (fcc_enet_t *)(&immap->im_dprambase[fip->fc_proff]); + + /* And another to the FCC register area. + */ + fccp = (volatile fcc_t *)(&immap->im_fcc[fip->fc_fccnum]); + cep->fccp = fccp; /* Keep the pointers handy */ + cep->ep = ep; + + /* Disable receive and transmit in case someone left it running. + */ + fccp->fcc_gfmr &= ~(FCC_GFMR_ENR | FCC_GFMR_ENT); +} + +/* Initialize the I/O pins for the FCC Ethernet. +*/ +static void __init +init_fcc_ioports(fcc_info_t *fip, volatile iop_cpm2_t *io, + volatile cpm2_map_t *immap) +{ + + /* FCC1 pins are on port A/C. FCC2/3 are port B/C. + */ + if (fip->fc_proff == PROFF_FCC1) { + /* Configure port A and C pins for FCC1 Ethernet. + */ + io->iop_pdira &= ~PA1_DIRA_BOUT; + io->iop_pdira |= PA1_DIRA_BIN; + io->iop_psora &= ~PA1_PSORA_BOUT; + io->iop_psora |= PA1_PSORA_BIN; + io->iop_ppara |= (PA1_DIRA_BOUT | PA1_DIRA_BIN); + } + if (fip->fc_proff == PROFF_FCC2) { + /* Configure port B and C pins for FCC Ethernet. + */ + io->iop_pdirb &= ~PB2_DIRB_BOUT; + io->iop_pdirb |= PB2_DIRB_BIN; + io->iop_psorb &= ~PB2_PSORB_BOUT; + io->iop_psorb |= PB2_PSORB_BIN; + io->iop_pparb |= (PB2_DIRB_BOUT | PB2_DIRB_BIN); + } + if (fip->fc_proff == PROFF_FCC3) { + /* Configure port B and C pins for FCC Ethernet. + */ + io->iop_pdirb &= ~PB3_DIRB_BOUT; + io->iop_pdirb |= PB3_DIRB_BIN; + io->iop_psorb &= ~PB3_PSORB_BOUT; + io->iop_psorb |= PB3_PSORB_BIN; + io->iop_pparb |= (PB3_DIRB_BOUT | PB3_DIRB_BIN); + + io->iop_pdirc &= ~PC3_DIRC_BOUT; + io->iop_pdirc |= PC3_DIRC_BIN; + io->iop_psorc &= ~PC3_PSORC_BOUT; + io->iop_psorc |= PC3_PSORC_BIN; + io->iop_pparc |= (PC3_DIRC_BOUT | PC3_DIRC_BIN); + + } + + /* Port C has clocks...... + */ + io->iop_psorc &= ~(fip->fc_trxclocks); + io->iop_pdirc &= ~(fip->fc_trxclocks); + io->iop_pparc |= fip->fc_trxclocks; + +#ifdef CONFIG_USE_MDIO + /* ....and the MII serial clock/data. + */ + io->iop_pdatc |= (fip->fc_mdio | fip->fc_mdck); + io->iop_podrc &= ~(fip->fc_mdio | fip->fc_mdck); + io->iop_pdirc |= (fip->fc_mdio | fip->fc_mdck); + io->iop_pparc &= ~(fip->fc_mdio | fip->fc_mdck); +#endif /* CONFIG_USE_MDIO */ + + /* Configure Serial Interface clock routing. + * First, clear all FCC bits to zero, + * then set the ones we want. + */ + immap->im_cpmux.cmx_fcr &= ~(fip->fc_clockmask); + immap->im_cpmux.cmx_fcr |= fip->fc_clockroute; +} + +static void __init +init_fcc_param(fcc_info_t *fip, struct net_device *dev, + volatile cpm2_map_t *immap) +{ + unsigned char *eap; + unsigned long mem_addr; + bd_t *bd; + int i, j; + struct fcc_enet_private *cep; + volatile fcc_enet_t *ep; + volatile cbd_t *bdp; + volatile cpm_cpm2_t *cp; + + cep = (struct fcc_enet_private *)(dev->priv); + ep = cep->ep; + cp = cpmp; + + bd = (bd_t *)__res; + + /* Zero the whole thing.....I must have missed some individually. + * It works when I do this. + */ + memset((char *)ep, 0, sizeof(fcc_enet_t)); + + /* Allocate space for the buffer descriptors from regular memory. + * Initialize base addresses for the buffer descriptors. + */ + cep->rx_bd_base = (cbd_t *)kmalloc(sizeof(cbd_t) * RX_RING_SIZE, + GFP_KERNEL | GFP_DMA); + ep->fen_genfcc.fcc_rbase = __pa(cep->rx_bd_base); + cep->tx_bd_base = (cbd_t *)kmalloc(sizeof(cbd_t) * TX_RING_SIZE, + GFP_KERNEL | GFP_DMA); + ep->fen_genfcc.fcc_tbase = __pa(cep->tx_bd_base); + + cep->dirty_tx = cep->cur_tx = cep->tx_bd_base; + cep->cur_rx = cep->rx_bd_base; + + ep->fen_genfcc.fcc_rstate = (CPMFCR_GBL | CPMFCR_EB) << 24; + ep->fen_genfcc.fcc_tstate = (CPMFCR_GBL | CPMFCR_EB) << 24; + + /* Set maximum bytes per receive buffer. + * It must be a multiple of 32. + */ + ep->fen_genfcc.fcc_mrblr = PKT_MAXBLR_SIZE; + + /* Allocate space in the reserved FCC area of DPRAM for the + * internal buffers. No one uses this space (yet), so we + * can do this. Later, we will add resource management for + * this area. + */ + mem_addr = CPM_FCC_SPECIAL_BASE + (fip->fc_fccnum * 128); + ep->fen_genfcc.fcc_riptr = mem_addr; + ep->fen_genfcc.fcc_tiptr = mem_addr+32; + ep->fen_padptr = mem_addr+64; + memset((char *)(&(immap->im_dprambase[(mem_addr+64)])), 0x88, 32); + + ep->fen_genfcc.fcc_rbptr = 0; + ep->fen_genfcc.fcc_tbptr = 0; + ep->fen_genfcc.fcc_rcrc = 0; + ep->fen_genfcc.fcc_tcrc = 0; + ep->fen_genfcc.fcc_res1 = 0; + ep->fen_genfcc.fcc_res2 = 0; + + ep->fen_camptr = 0; /* CAM isn't used in this driver */ + + /* Set CRC preset and mask. + */ + ep->fen_cmask = 0xdebb20e3; + ep->fen_cpres = 0xffffffff; + + ep->fen_crcec = 0; /* CRC Error counter */ + ep->fen_alec = 0; /* alignment error counter */ + ep->fen_disfc = 0; /* discard frame counter */ + ep->fen_retlim = 15; /* Retry limit threshold */ + ep->fen_pper = 0; /* Normal persistence */ + + /* Clear hash filter tables. + */ + ep->fen_gaddrh = 0; + ep->fen_gaddrl = 0; + ep->fen_iaddrh = 0; + ep->fen_iaddrl = 0; + + /* Clear the Out-of-sequence TxBD. + */ + ep->fen_tfcstat = 0; + ep->fen_tfclen = 0; + ep->fen_tfcptr = 0; + + ep->fen_mflr = PKT_MAXBUF_SIZE; /* maximum frame length register */ + ep->fen_minflr = PKT_MINBUF_SIZE; /* minimum frame length register */ + + /* Set Ethernet station address. + * + * This is supplied in the board information structure, so we + * copy that into the controller. + * So, far we have only been given one Ethernet address. We make + * it unique by setting a few bits in the upper byte of the + * non-static part of the address. + */ + eap = (unsigned char *)&(ep->fen_paddrh); + for (i=5; i>=0; i--) { + +/* + * The EP8260 only uses FCC3, so we can safely give it the real + * MAC address. + */ +#ifdef CONFIG_SBC82xx + if (i == 5) { + /* bd->bi_enetaddr holds the SCC0 address; the FCC + devices count up from there */ + dev->dev_addr[i] = bd->bi_enetaddr[i] & ~3; + dev->dev_addr[i] += 1 + fip->fc_fccnum; + *eap++ = dev->dev_addr[i]; + } +#else +#ifndef CONFIG_RPX8260 + if (i == 3) { + dev->dev_addr[i] = bd->bi_enetaddr[i]; + dev->dev_addr[i] |= (1 << (7 - fip->fc_fccnum)); + *eap++ = dev->dev_addr[i]; + } else +#endif + { + *eap++ = dev->dev_addr[i] = bd->bi_enetaddr[i]; + } +#endif + } + + ep->fen_taddrh = 0; + ep->fen_taddrm = 0; + ep->fen_taddrl = 0; + + ep->fen_maxd1 = PKT_MAXDMA_SIZE; /* maximum DMA1 length */ + ep->fen_maxd2 = PKT_MAXDMA_SIZE; /* maximum DMA2 length */ + + /* Clear stat counters, in case we ever enable RMON. + */ + ep->fen_octc = 0; + ep->fen_colc = 0; + ep->fen_broc = 0; + ep->fen_mulc = 0; + ep->fen_uspc = 0; + ep->fen_frgc = 0; + ep->fen_ospc = 0; + ep->fen_jbrc = 0; + ep->fen_p64c = 0; + ep->fen_p65c = 0; + ep->fen_p128c = 0; + ep->fen_p256c = 0; + ep->fen_p512c = 0; + ep->fen_p1024c = 0; + + ep->fen_rfthr = 0; /* Suggested by manual */ + ep->fen_rfcnt = 0; + ep->fen_cftype = 0; + + /* Now allocate the host memory pages and initialize the + * buffer descriptors. + */ + bdp = cep->tx_bd_base; + for (i=0; i<TX_RING_SIZE; i++) { + + /* Initialize the BD for every fragment in the page. + */ + bdp->cbd_sc = 0; + bdp->cbd_datlen = 0; + bdp->cbd_bufaddr = 0; + bdp++; + } + + /* Set the last buffer to wrap. + */ + bdp--; + bdp->cbd_sc |= BD_SC_WRAP; + + bdp = cep->rx_bd_base; + for (i=0; i<FCC_ENET_RX_PAGES; i++) { + + /* Allocate a page. + */ + mem_addr = __get_free_page(GFP_KERNEL); + + /* Initialize the BD for every fragment in the page. + */ + for (j=0; j<FCC_ENET_RX_FRPPG; j++) { + bdp->cbd_sc = BD_ENET_RX_EMPTY | BD_ENET_RX_INTR; + bdp->cbd_datlen = 0; + bdp->cbd_bufaddr = __pa(mem_addr); + mem_addr += FCC_ENET_RX_FRSIZE; + bdp++; + } + } + + /* Set the last buffer to wrap. + */ + bdp--; + bdp->cbd_sc |= BD_SC_WRAP; + + /* Let's re-initialize the channel now. We have to do it later + * than the manual describes because we have just now finished + * the BD initialization. + */ + cp->cp_cpcr = mk_cr_cmd(fip->fc_cpmpage, fip->fc_cpmblock, 0x0c, + CPM_CR_INIT_TRX) | CPM_CR_FLG; + while (cp->cp_cpcr & CPM_CR_FLG); + + cep->skb_cur = cep->skb_dirty = 0; +} + +/* Let 'er rip. +*/ +static void __init +init_fcc_startup(fcc_info_t *fip, struct net_device *dev) +{ + volatile fcc_t *fccp; + struct fcc_enet_private *cep; + + cep = (struct fcc_enet_private *)(dev->priv); + fccp = cep->fccp; + +#ifdef CONFIG_RPX8260 +#ifdef PHY_INTERRUPT + /* Route PHY interrupt to IRQ. The following code only works for + * IRQ1 - IRQ7. It does not work for Port C interrupts. + */ + *((volatile u_char *) (RPX_CSR_ADDR + 13)) &= ~BCSR13_FETH_IRQMASK; + *((volatile u_char *) (RPX_CSR_ADDR + 13)) |= + ((PHY_INTERRUPT - SIU_INT_IRQ1 + 1) << 4); +#endif + /* Initialize MDIO pins. */ + *((volatile u_char *) (RPX_CSR_ADDR + 4)) &= ~BCSR4_MII_MDC; + *((volatile u_char *) (RPX_CSR_ADDR + 4)) |= + BCSR4_MII_READ | BCSR4_MII_MDIO; + /* Enable external LXT971 PHY. */ + *((volatile u_char *) (RPX_CSR_ADDR + 4)) |= BCSR4_EN_PHY; + udelay(1000); + *((volatile u_char *) (RPX_CSR_ADDR+ 4)) |= BCSR4_EN_MII; + udelay(1000); +#endif /* ifdef CONFIG_RPX8260 */ + + fccp->fcc_fcce = 0xffff; /* Clear any pending events */ + + /* Leave FCC interrupts masked for now. Will be unmasked by + * fcc_restart(). + */ + fccp->fcc_fccm = 0; + + /* Install our interrupt handler. + */ + if (request_irq(fip->fc_interrupt, fcc_enet_interrupt, 0, "fenet", + dev) < 0) + printk("Can't get FCC IRQ %d\n", fip->fc_interrupt); + +#ifdef PHY_INTERRUPT +#ifdef CONFIG_ADS8272 + if (request_irq(PHY_INTERRUPT, mii_link_interrupt, SA_SHIRQ, + "mii", dev) < 0) + printk(KERN_CRIT "Can't get MII IRQ %d\n", PHY_INTERRUPT); +#else + /* Make IRQn edge triggered. This does not work if PHY_INTERRUPT is + * on Port C. + */ + ((volatile cpm2_map_t *) CPM_MAP_ADDR)->im_intctl.ic_siexr |= + (1 << (14 - (PHY_INTERRUPT - SIU_INT_IRQ1))); + + if (request_irq(PHY_INTERRUPT, mii_link_interrupt, 0, + "mii", dev) < 0) + printk(KERN_CRIT "Can't get MII IRQ %d\n", PHY_INTERRUPT); +#endif +#endif /* PHY_INTERRUPT */ + + /* Set GFMR to enable Ethernet operating mode. + */ + fccp->fcc_gfmr = (FCC_GFMR_TCI | FCC_GFMR_MODE_ENET); + + /* Set sync/delimiters. + */ + fccp->fcc_fdsr = 0xd555; + + /* Set protocol specific processing mode for Ethernet. + * This has to be adjusted for Full Duplex operation after we can + * determine how to detect that. + */ + fccp->fcc_fpsmr = FCC_PSMR_ENCRC; + +#ifdef CONFIG_PQ2ADS + /* Enable the PHY. */ + *(volatile uint *)(BCSR_ADDR + 4) &= ~BCSR1_FETHIEN; + *(volatile uint *)(BCSR_ADDR + 4) |= BCSR1_FETH_RST; +#endif +#if defined(CONFIG_PQ2ADS) || defined(CONFIG_PQ2FADS) + /* Enable the 2nd PHY. */ + *(volatile uint *)(BCSR_ADDR + 12) &= ~BCSR3_FETHIEN2; + *(volatile uint *)(BCSR_ADDR + 12) |= BCSR3_FETH2_RST; +#endif + +#if defined(CONFIG_USE_MDIO) || defined(CONFIG_TQM8260) + /* start in full duplex mode, and negotiate speed + */ + fcc_restart (dev, 1); +#else + /* start in half duplex mode + */ + fcc_restart (dev, 0); +#endif +} + +#ifdef CONFIG_USE_MDIO +/* MII command/status interface. + * I'm not going to describe all of the details. You can find the + * protocol definition in many other places, including the data sheet + * of most PHY parts. + * I wonder what "they" were thinking (maybe weren't) when they leave + * the I2C in the CPM but I have to toggle these bits...... + */ +#ifdef CONFIG_RPX8260 + /* The EP8260 has the MDIO pins in a BCSR instead of on Port C + * like most other boards. + */ +#define MDIO_ADDR ((volatile u_char *)(RPX_CSR_ADDR + 4)) +#define MAKE_MDIO_OUTPUT *MDIO_ADDR &= ~BCSR4_MII_READ +#define MAKE_MDIO_INPUT *MDIO_ADDR |= BCSR4_MII_READ | BCSR4_MII_MDIO +#define OUT_MDIO(bit) \ + if (bit) \ + *MDIO_ADDR |= BCSR4_MII_MDIO; \ + else \ + *MDIO_ADDR &= ~BCSR4_MII_MDIO; +#define IN_MDIO (*MDIO_ADDR & BCSR4_MII_MDIO) +#define OUT_MDC(bit) \ + if (bit) \ + *MDIO_ADDR |= BCSR4_MII_MDC; \ + else \ + *MDIO_ADDR &= ~BCSR4_MII_MDC; +#else /* ifdef CONFIG_RPX8260 */ + /* This is for the usual case where the MDIO pins are on Port C. + */ +#define MDIO_ADDR (((volatile cpm2_map_t *)CPM_MAP_ADDR)->im_ioport) +#define MAKE_MDIO_OUTPUT MDIO_ADDR.iop_pdirc |= fip->fc_mdio +#define MAKE_MDIO_INPUT MDIO_ADDR.iop_pdirc &= ~fip->fc_mdio +#define OUT_MDIO(bit) \ + if (bit) \ + MDIO_ADDR.iop_pdatc |= fip->fc_mdio; \ + else \ + MDIO_ADDR.iop_pdatc &= ~fip->fc_mdio; +#define IN_MDIO ((MDIO_ADDR.iop_pdatc) & fip->fc_mdio) +#define OUT_MDC(bit) \ + if (bit) \ + MDIO_ADDR.iop_pdatc |= fip->fc_mdck; \ + else \ + MDIO_ADDR.iop_pdatc &= ~fip->fc_mdck; +#endif /* ifdef CONFIG_RPX8260 */ + +static uint +mii_send_receive(fcc_info_t *fip, uint cmd) +{ + uint retval; + int read_op, i, off; + const int us = 1; + + read_op = ((cmd & 0xf0000000) == 0x60000000); + + /* Write preamble + */ + OUT_MDIO(1); + MAKE_MDIO_OUTPUT; + OUT_MDIO(1); + for (i = 0; i < 32; i++) + { + udelay(us); + OUT_MDC(1); + udelay(us); + OUT_MDC(0); + } + + /* Write data + */ + for (i = 0, off = 31; i < (read_op ? 14 : 32); i++, --off) + { + OUT_MDIO((cmd >> off) & 0x00000001); + udelay(us); + OUT_MDC(1); + udelay(us); + OUT_MDC(0); + } + + retval = cmd; + + if (read_op) + { + retval >>= 16; + + MAKE_MDIO_INPUT; + udelay(us); + OUT_MDC(1); + udelay(us); + OUT_MDC(0); + + for (i = 0; i < 16; i++) + { + udelay(us); + OUT_MDC(1); + udelay(us); + retval <<= 1; + if (IN_MDIO) + retval++; + OUT_MDC(0); + } + } + + MAKE_MDIO_INPUT; + udelay(us); + OUT_MDC(1); + udelay(us); + OUT_MDC(0); + + return retval; +} +#endif /* CONFIG_USE_MDIO */ + +static void +fcc_stop(struct net_device *dev) +{ + struct fcc_enet_private *fep= (struct fcc_enet_private *)(dev->priv); + volatile fcc_t *fccp = fep->fccp; + fcc_info_t *fip = fep->fip; + volatile fcc_enet_t *ep = fep->ep; + volatile cpm_cpm2_t *cp = cpmp; + volatile cbd_t *bdp; + int i; + + if ((fccp->fcc_gfmr & (FCC_GFMR_ENR | FCC_GFMR_ENT)) == 0) + return; /* already down */ + + fccp->fcc_fccm = 0; + + /* issue the graceful stop tx command */ + while (cp->cp_cpcr & CPM_CR_FLG); + cp->cp_cpcr = mk_cr_cmd(fip->fc_cpmpage, fip->fc_cpmblock, + 0x0c, CPM_CR_GRA_STOP_TX) | CPM_CR_FLG; + while (cp->cp_cpcr & CPM_CR_FLG); + + /* Disable transmit/receive */ + fccp->fcc_gfmr &= ~(FCC_GFMR_ENR | FCC_GFMR_ENT); + + /* issue the restart tx command */ + fccp->fcc_fcce = FCC_ENET_GRA; + while (cp->cp_cpcr & CPM_CR_FLG); + cp->cp_cpcr = mk_cr_cmd(fip->fc_cpmpage, fip->fc_cpmblock, + 0x0c, CPM_CR_RESTART_TX) | CPM_CR_FLG; + while (cp->cp_cpcr & CPM_CR_FLG); + + /* free tx buffers */ + fep->skb_cur = fep->skb_dirty = 0; + for (i=0; i<=TX_RING_MOD_MASK; i++) { + if (fep->tx_skbuff[i] != NULL) { + dev_kfree_skb(fep->tx_skbuff[i]); + fep->tx_skbuff[i] = NULL; + } + } + fep->dirty_tx = fep->cur_tx = fep->tx_bd_base; + fep->tx_free = TX_RING_SIZE; + ep->fen_genfcc.fcc_tbptr = ep->fen_genfcc.fcc_tbase; + + /* Initialize the tx buffer descriptors. */ + bdp = fep->tx_bd_base; + for (i=0; i<TX_RING_SIZE; i++) { + bdp->cbd_sc = 0; + bdp->cbd_datlen = 0; + bdp->cbd_bufaddr = 0; + bdp++; + } + /* Set the last buffer to wrap. */ + bdp--; + bdp->cbd_sc |= BD_SC_WRAP; +} + +static void +fcc_restart(struct net_device *dev, int duplex) +{ + struct fcc_enet_private *fep = (struct fcc_enet_private *)(dev->priv); + volatile fcc_t *fccp = fep->fccp; + + /* stop any transmissions in progress */ + fcc_stop(dev); + + if (duplex) + fccp->fcc_fpsmr |= FCC_PSMR_FDE | FCC_PSMR_LPB; + else + fccp->fcc_fpsmr &= ~(FCC_PSMR_FDE | FCC_PSMR_LPB); + + /* Enable interrupts for transmit error, complete frame + * received, and any transmit buffer we have also set the + * interrupt flag. + */ + fccp->fcc_fccm = (FCC_ENET_TXE | FCC_ENET_RXF | FCC_ENET_TXB); + + /* Enable transmit/receive */ + fccp->fcc_gfmr |= FCC_GFMR_ENR | FCC_GFMR_ENT; +} + +static int +fcc_enet_open(struct net_device *dev) +{ + struct fcc_enet_private *fep = dev->priv; + +#ifdef CONFIG_USE_MDIO + fep->sequence_done = 0; + fep->link = 0; + + if (fep->phy) { + fcc_restart(dev, 0); /* always start in half-duplex */ + mii_do_cmd(dev, fep->phy->ack_int); + mii_do_cmd(dev, fep->phy->config); + mii_do_cmd(dev, phy_cmd_config); /* display configuration */ + while(!fep->sequence_done) + schedule(); + + mii_do_cmd(dev, fep->phy->startup); + netif_start_queue(dev); + return 0; /* Success */ + } + return -ENODEV; /* No PHY we understand */ +#else + fep->link = 1; + fcc_restart(dev, 0); /* always start in half-duplex */ + netif_start_queue(dev); + return 0; /* Always succeed */ +#endif /* CONFIG_USE_MDIO */ +} + |