diff options
author | Ralf Baechle <ralf@linux-mips.org> | 1997-01-07 02:33:00 +0000 |
---|---|---|
committer | <ralf@linux-mips.org> | 1997-01-07 02:33:00 +0000 |
commit | beb116954b9b7f3bb56412b2494b562f02b864b1 (patch) | |
tree | 120e997879884e1b9d93b265221b939d2ef1ade1 /drivers/net/dgrs.c | |
parent | 908d4681a1dc3792ecafbe64265783a86c4cccb6 (diff) |
Import of Linux/MIPS 2.1.14
Diffstat (limited to 'drivers/net/dgrs.c')
-rw-r--r-- | drivers/net/dgrs.c | 1360 |
1 files changed, 1360 insertions, 0 deletions
diff --git a/drivers/net/dgrs.c b/drivers/net/dgrs.c new file mode 100644 index 000000000..352652ff0 --- /dev/null +++ b/drivers/net/dgrs.c @@ -0,0 +1,1360 @@ +/* + * Digi RightSwitch SE-X loadable device driver for Linux + * + * The RightSwitch is a 4 (EISA) or 6 (PCI) port etherswitch and + * a NIC on an internal board. + * + * Author: Rick Richardson, rick@dgii.com, rick_richardson@dgii.com + * Derived from the SVR4.2 (UnixWare) driver for the same card. + * + * Copyright 1995-1996 Digi International Inc. + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * For information on purchasing a RightSwitch SE-4 or SE-6 + * board, please contact Digi's sales department at 1-612-912-3444 + * or 1-800-DIGIBRD. Outside the U.S., please check our Web page + * at http://www.dgii.com for sales offices worldwide. + * + */ + +static char *version = "$Id: dgrs.c,v 1.8 1996/04/18 03:11:14 rick Exp $"; + +#include <linux/module.h> + +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/string.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/ioport.h> +#include <linux/malloc.h> +#include <linux/interrupt.h> +#include <linux/pci.h> +#include <linux/bios32.h> +#include <asm/bitops.h> +#include <asm/io.h> +#include <asm/byteorder.h> +#include <asm/uaccess.h> + +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> + +#include <linux/types.h> + +/* + * DGRS include files + */ +typedef unsigned char uchar; +typedef unsigned int bool; +#define vol volatile + +#include "dgrs.h" +#include "dgrs_es4h.h" +#include "dgrs_plx9060.h" +#include "dgrs_i82596.h" +#include "dgrs_ether.h" +#include "dgrs_asstruct.h" +#include "dgrs_bcomm.h" + +/* + * Firmware. Compiled separately for local compilation, + * but #included for Linux distribution. + */ +#ifndef NOFW + #include "dgrs_firmware.c" +#else + extern int dgrs_firmnum; + extern char dgrs_firmver[]; + extern char dgrs_firmdate[]; + extern uchar dgrs_code[]; + extern int dgrs_ncode; +#endif + +/* + * Linux out*() is backwards from all other operating systems + */ +#define OUTB(ADDR, VAL) outb(VAL, ADDR) +#define OUTW(ADDR, VAL) outw(VAL, ADDR) +#define OUTL(ADDR, VAL) outl(VAL, ADDR) + +/* + * Macros to convert switch to host and host to switch addresses + * (assumes a local variable priv points to board dependent struct) + */ +#define S2H(A) ( ((unsigned long)(A)&0x00ffffff) + priv->vmem ) +#define H2S(A) ( ((char *) (A) - priv->vmem) + 0xA3000000 ) + +/* + * Convert a switch address to a "safe" address for use with the + * PLX 9060 DMA registers and the associated HW kludge that allows + * for host access of the DMA registers. + */ +#define S2DMA(A) ( (unsigned long)(A) & 0x00ffffff) + +/* + * "Space.c" variables, now settable from module interface + * Use the name below, minus the "dgrs_" prefix. See init_module(). + */ +int dgrs_debug = 1; +int dgrs_dma = 1; +int dgrs_spantree = -1; +int dgrs_hashexpire = -1; +uchar dgrs_ipaddr[4] = { 0xff, 0xff, 0xff, 0xff}; +long dgrs_ipxnet = -1; + +/* + * Chain of device structures + */ +#ifdef MODULE + static struct device *dgrs_root_dev = NULL; +#endif + +/* + * Private per-board data structure (dev->priv) + */ +typedef struct +{ + /* + * Stuff for generic ethercard I/F + */ + char devname[8]; /* "ethN" string */ + struct device *next_dev; + struct enet_statistics stats; + + /* + * DGRS specific data + */ + char *vmem; + + struct bios_comm *bcomm; /* Firmware BIOS comm structure */ + PORT *port; /* Ptr to PORT[0] struct in VM */ + I596_SCB *scbp; /* Ptr to SCB struct in VM */ + I596_RFD *rfdp; /* Current RFD list */ + I596_RBD *rbdp; /* Current RBD list */ + + int intrcnt; /* Count of interrupts */ + + /* + * SE-4 (EISA) board variables + */ + uchar is_reg; /* EISA: Value for ES4H_IS reg */ + + /* + * SE-6 (PCI) board variables + * + * The PLX "expansion rom" space is used for DMA register + * access from the host on the SE-6. These are the physical + * and virtual addresses of that space. + */ + ulong plxreg; /* Phys address of PLX chip */ + char *vplxreg; /* Virtual address of PLX chip */ + ulong plxdma; /* Phys addr of PLX "expansion rom" */ + ulong volatile *vplxdma; /* Virtual addr of "expansion rom" */ + int use_dma; /* Flag: use DMA */ + DMACHAIN *dmadesc_s; /* area for DMA chains (SW addr.) */ + DMACHAIN *dmadesc_h; /* area for DMA chains (Host Virtual) */ + +} DGRS_PRIV; + + +/* + * reset or un-reset the IDT processor + */ +static void +proc_reset(struct device *dev, int reset) +{ + DGRS_PRIV *priv = (DGRS_PRIV *) dev->priv; + + if (priv->plxreg) + { + ulong val; + val = inl(dev->base_addr + PLX_MISC_CSR); + if (reset) + val |= SE6_RESET; + else + val &= ~SE6_RESET; + OUTL(dev->base_addr + PLX_MISC_CSR, val); + } + else + { + OUTB(dev->base_addr + ES4H_PC, reset ? ES4H_PC_RESET : 0); + } +} + +/* + * See if the board supports bus master DMA + */ +static int +check_board_dma(struct device *dev) +{ + DGRS_PRIV *priv = (DGRS_PRIV *) dev->priv; + ulong x; + + /* + * If Space.c says not to use DMA, or if it's not a PLX based + * PCI board, or if the expansion ROM space is not PCI + * configured, then return false. + */ + if (!dgrs_dma || !priv->plxreg || !priv->plxdma) + return (0); + + /* + * Set the local address remap register of the "expansion rom" + * area to 0x80000000 so that we can use it to access the DMA + * registers from the host side. + */ + OUTL(dev->base_addr + PLX_ROM_BASE_ADDR, 0x80000000); + + /* + * Set the PCI region descriptor to: + * Space 0: + * disable read-prefetch + * enable READY + * enable BURST + * 0 internal wait states + * Expansion ROM: (used for host DMA register access) + * disable read-prefetch + * enable READY + * disable BURST + * 0 internal wait states + */ + OUTL(dev->base_addr + PLX_BUS_REGION, 0x49430343); + + /* + * Now map the DMA registers into our virtual space + */ + priv->vplxdma = (ulong *) ioremap (priv->plxdma, 256); + if (!priv->vplxdma) + { + printk("%s: can't ioremap() the DMA regs", dev->name); + return (0); + } + + /* + * Now test to see if we can access the DMA registers + * If we write -1 and get back 1FFF, then we accessed the + * DMA register. Otherwise, we probably have an old board + * and wrote into regular RAM. + */ + priv->vplxdma[PLX_DMA0_MODE/4] = 0xFFFFFFFF; + x = priv->vplxdma[PLX_DMA0_MODE/4]; + if (x != 0x00001FFF) + return (0); + + return (1); +} + +/* + * Initiate DMA using PLX part on PCI board. Spin the + * processor until completed. All addresses are physical! + * + * If pciaddr is NULL, then it's a chaining DMA, and lcladdr is + * the address of the first DMA descriptor in the chain. + * + * If pciaddr is not NULL, then it's a single DMA. + * + * In either case, "lcladdr" must have been fixed up to make + * sure the MSB isn't set using the S2DMA macro before passing + * the address to this routine. + */ +static int +do_plx_dma( + struct device *dev, + ulong pciaddr, + ulong lcladdr, + int len, + int to_host +) +{ + int i; + ulong csr; + DGRS_PRIV *priv = (DGRS_PRIV *) dev->priv; + + if (pciaddr) + { + /* + * Do a single, non-chain DMA + */ + priv->vplxdma[PLX_DMA0_PCI_ADDR/4] = pciaddr; + priv->vplxdma[PLX_DMA0_LCL_ADDR/4] = lcladdr; + priv->vplxdma[PLX_DMA0_SIZE/4] = len; + priv->vplxdma[PLX_DMA0_DESCRIPTOR/4] = to_host + ? PLX_DMA_DESC_TO_HOST + : PLX_DMA_DESC_TO_BOARD; + priv->vplxdma[PLX_DMA0_MODE/4] = + PLX_DMA_MODE_WIDTH32 + | PLX_DMA_MODE_WAITSTATES(0) + | PLX_DMA_MODE_READY + | PLX_DMA_MODE_NOBTERM + | PLX_DMA_MODE_BURST + | PLX_DMA_MODE_NOCHAIN; + } + else + { + /* + * Do a chaining DMA + */ + priv->vplxdma[PLX_DMA0_MODE/4] = + PLX_DMA_MODE_WIDTH32 + | PLX_DMA_MODE_WAITSTATES(0) + | PLX_DMA_MODE_READY + | PLX_DMA_MODE_NOBTERM + | PLX_DMA_MODE_BURST + | PLX_DMA_MODE_CHAIN; + priv->vplxdma[PLX_DMA0_DESCRIPTOR/4] = lcladdr; + } + + priv->vplxdma[PLX_DMA_CSR/4] = + PLX_DMA_CSR_0_ENABLE | PLX_DMA_CSR_0_START; + + /* + * Wait for DMA to complete + */ + for (i = 0; i < 1000000; ++i) + { + /* + * Spin the host CPU for 1 usec, so we don't thrash + * the PCI bus while the PLX 9060 is doing DMA. + */ + udelay(1); + + csr = (volatile) priv->vplxdma[PLX_DMA_CSR/4]; + + if (csr & PLX_DMA_CSR_0_DONE) + break; + } + + if ( ! (csr & PLX_DMA_CSR_0_DONE) ) + { + printk("%s: DMA done never occurred. DMA disabled.", dev->name); + priv->use_dma = 0; + return 1; + } + return 0; +} + +/* + * Process a received frame + */ +void +dgrs_rcv_frame( + struct device *dev, + DGRS_PRIV *priv, + I596_CB *cbp +) +{ + int len; + I596_TBD *tbdp; + struct sk_buff *skb; + uchar *putp; + uchar *p; + + if (0) printk("%s: rcv len=%ld", dev->name, cbp->xmit.count); + + /* + * Allocate a message block big enough to hold the whole frame + */ + len = cbp->xmit.count; + if ((skb = dev_alloc_skb(len+5)) == NULL) + { + printk("%s: dev_alloc_skb failed for rcv buffer", dev->name); + ++priv->stats.rx_dropped; + /* discarding the frame */ + goto out; + } + skb->dev = dev; + skb_reserve(skb, 2); /* Align IP header */ + +again: + putp = p = skb_put(skb, len); + + /* + * There are three modes here for doing the packet copy. + * If we have DMA, and the packet is "long", we use the + * chaining mode of DMA. If it's shorter, we use single + * DMA's. Otherwise, we use memcpy(). + */ + if (priv->use_dma && priv->dmadesc_h && len > 64) + { + /* + * If we can use DMA and it's a long frame, copy it using + * DMA chaining. + */ + DMACHAIN *ddp_h; /* Host virtual DMA desc. pointer */ + DMACHAIN *ddp_s; /* Switch physical DMA desc. pointer */ + uchar *phys_p; + + /* + * Get the physical address of the STREAMS buffer. + * NOTE: allocb() guarantees that the whole buffer + * is in a single page if the length < 4096. + */ + phys_p = (uchar *) virt_to_phys(putp); + + ddp_h = priv->dmadesc_h; + ddp_s = priv->dmadesc_s; + tbdp = (I596_TBD *) S2H(cbp->xmit.tbdp); + for (;;) + { + int count; + int amt; + + count = tbdp->count; + amt = count & 0x3fff; + if (amt == 0) + break; /* For safety */ + if ( (p-putp) >= len) + { + printk("%s: cbp = %x", dev->name, H2S(cbp)); + proc_reset(dev, 1); /* Freeze IDT */ + break; /* For Safety */ + } + + ddp_h->pciaddr = (ulong) phys_p; + ddp_h->lcladdr = S2DMA(tbdp->buf); + ddp_h->len = amt; + + phys_p += amt; + p += amt; + + if (count & I596_TBD_EOF) + { + ddp_h->next = PLX_DMA_DESC_TO_HOST + | PLX_DMA_DESC_EOC; + ++ddp_h; + break; + } + else + { + ++ddp_s; + ddp_h->next = PLX_DMA_DESC_TO_HOST + | (ulong) ddp_s; + tbdp = (I596_TBD *) S2H(tbdp->next); + ++ddp_h; + } + } + if (ddp_h - priv->dmadesc_h) + { + int rc; + + rc = do_plx_dma(dev, + 0, (ulong) priv->dmadesc_s, len, 0); + if (rc) + { + printk("%s: Chained DMA failure\n", dev->name); + goto again; + } + } + } + else if (priv->use_dma) + { + /* + * If we can use DMA and it's a shorter frame, copy it + * using single DMA transfers. + */ + uchar *phys_p; + + /* + * Get the physical address of the STREAMS buffer. + * NOTE: allocb() guarantees that the whole buffer + * is in a single page if the length < 4096. + */ + phys_p = (uchar *) virt_to_phys(putp); + + tbdp = (I596_TBD *) S2H(cbp->xmit.tbdp); + for (;;) + { + int count; + int amt; + int rc; + + count = tbdp->count; + amt = count & 0x3fff; + if (amt == 0) + break; /* For safety */ + if ( (p-putp) >= len) + { + printk("%s: cbp = %x", dev->name, H2S(cbp)); + proc_reset(dev, 1); /* Freeze IDT */ + break; /* For Safety */ + } + rc = do_plx_dma(dev, (ulong) phys_p, + S2DMA(tbdp->buf), amt, 1); + if (rc) + { + memcpy(p, S2H(tbdp->buf), amt); + printk("%s: Single DMA failed\n", dev->name); + } + phys_p += amt; + p += amt; + if (count & I596_TBD_EOF) + break; + tbdp = (I596_TBD *) S2H(tbdp->next); + } + } + else + { + /* + * Otherwise, copy it piece by piece using memcpy() + */ + tbdp = (I596_TBD *) S2H(cbp->xmit.tbdp); + for (;;) + { + int count; + int amt; + + count = tbdp->count; + amt = count & 0x3fff; + if (amt == 0) + break; /* For safety */ + if ( (p-putp) >= len) + { + printk("%s: cbp = %x", dev->name, H2S(cbp)); + proc_reset(dev, 1); /* Freeze IDT */ + break; /* For Safety */ + } + memcpy(p, S2H(tbdp->buf), amt); + p += amt; + if (count & I596_TBD_EOF) + break; + tbdp = (I596_TBD *) S2H(tbdp->next); + } + } + + /* + * Pass the frame to upper half + */ + skb->protocol = eth_type_trans(skb, dev); + netif_rx(skb); + ++priv->stats.rx_packets; + +out: + cbp->xmit.status = I596_CB_STATUS_C | I596_CB_STATUS_OK; +} + +/* + * Start transmission of a frame + * + * The interface to the board is simple: we pretend that we are + * a fifth 82596 ethernet controller 'receiving' data, and copy the + * data into the same structures that a real 82596 would. This way, + * the board firmware handles the host 'port' the same as any other. + * + * NOTE: we do not use Bus master DMA for this routine. Turns out + * that it is not needed. Slave writes over the PCI bus are about + * as fast as DMA, due to the fact that the PLX part can do burst + * writes. The same is not true for data being read from the board. + */ +static int +dgrs_start_xmit(struct sk_buff *skb, struct device *dev) +{ + DGRS_PRIV *priv = (DGRS_PRIV *) dev->priv; + I596_RBD *rbdp; + int count; + int i, len, amt; +# define mymin(A,B) ( (A) < (B) ? (A) : (B) ) + + if (dgrs_debug > 1) printk("%s: xmit len=%ld\n", dev->name, skb->len); + + dev->trans_start = jiffies; + dev->tbusy = 0; + + if (priv->rfdp->cmd & I596_RFD_EL) + { /* Out of RFD's */ + if (0) printk("%s: NO RFD's", dev->name); + goto no_resources; + } + + rbdp = priv->rbdp; + count = 0; + priv->rfdp->rbdp = (I596_RBD *) H2S(rbdp); + + i = 0; len = skb->len; + for (;;) + { + if (rbdp->size & I596_RBD_EL) + { /* Out of RBD's */ + if (0) printk("%s: NO RBD's", dev->name); + goto no_resources; + } + + amt = mymin(len, rbdp->size - count); + memcpy( (char *) S2H(rbdp->buf) + count, skb->data + i, amt); + i += amt; + count += amt; + len -= amt; + if (len == 0) + { + if (skb->len < 60) + rbdp->count = 60 | I596_RBD_EOF; + else + rbdp->count = count | I596_RBD_EOF; + rbdp = (I596_RBD *) S2H(rbdp->next); + goto frame_done; + } + else if (count < 32) + { + /* More data to come, but we used less than 32 + * bytes of this RBD. Keep filling this RBD. + */ + {} /* Yes, we do nothing here */ + } + else + { + rbdp->count = count; + rbdp = (I596_RBD *) S2H(rbdp->next); + count = 0; + } + } + +frame_done: + priv->rbdp = rbdp; + priv->rfdp->status = I596_RFD_C | I596_RFD_OK; + priv->rfdp = (I596_RFD *) S2H(priv->rfdp->next); + + ++priv->stats.tx_packets; + + dev_kfree_skb (skb, FREE_WRITE); + return (0); + +no_resources: + priv->scbp->status |= I596_SCB_RNR; /* simulate I82596 */ + return (-EAGAIN); +} + +/* + * Open the interface + */ +static int +dgrs_open( struct device *dev ) +{ + dev->tbusy = 0; + dev->interrupt = 0; + dev->start = 1; + + #ifdef MODULE + MOD_INC_USE_COUNT; + #endif + + return (0); +} + +/* + * Close the interface + */ +static int +dgrs_close( struct device *dev ) +{ + dev->start = 0; + dev->tbusy = 1; + + #ifdef MODULE + MOD_DEC_USE_COUNT; + #endif + + return (0); +} + +/* + * Get statistics + */ +static struct enet_statistics * +dgrs_get_stats( struct device *dev ) +{ + DGRS_PRIV *priv = (DGRS_PRIV *) dev->priv; + + return (&priv->stats); +} + +/* + * Set multicast list and/or promiscuous mode + */ +static void +dgrs_set_multicast_list( struct device *dev) +{ + DGRS_PRIV *priv = (DGRS_PRIV *) dev->priv; + + priv->port->is_promisc = (dev->flags & IFF_PROMISC) ? 1 : 0; +} + +/* + * Unique ioctl's + */ +static int +dgrs_ioctl(struct device *dev, struct ifreq *ifr, int cmd) +{ + DGRS_PRIV *priv = (DGRS_PRIV *) dev->priv; + DGRS_IOCTL ioc; + int i, rc; + + rc = verify_area(VERIFY_WRITE, ifr->ifr_data, sizeof(DGRS_IOCTL)); + if (rc) return (rc); + if (cmd != DGRSIOCTL) return -EINVAL; + + copy_from_user(&ioc, ifr->ifr_data, sizeof(DGRS_IOCTL)); + + switch (ioc.cmd) + { + case DGRS_GETMEM: + if (ioc.len != sizeof(ulong)) + return -EINVAL; + rc = verify_area(VERIFY_WRITE, (void *) ioc.data, ioc.len); + if (rc) return (rc); + copy_to_user(ioc.data, &dev->mem_start, ioc.len); + return (0); + case DGRS_SETFILTER: + rc = verify_area(VERIFY_READ, (void *) ioc.data, ioc.len); + if (rc) return (rc); + if (ioc.port > priv->bcomm->bc_nports) + return -EINVAL; + if (ioc.filter >= NFILTERS) + return -EINVAL; + if (ioc.len > priv->bcomm->bc_filter_area_len) + return -EINVAL; + + /* Wait for old command to finish */ + for (i = 0; i < 1000; ++i) + { + if ( (volatile) priv->bcomm->bc_filter_cmd <= 0 ) + break; + udelay(1); + } + if (i >= 1000) + return -EIO; + + priv->bcomm->bc_filter_port = ioc.port; + priv->bcomm->bc_filter_num = ioc.filter; + priv->bcomm->bc_filter_len = ioc.len; + + if (ioc.len) + { + copy_from_user(S2H(priv->bcomm->bc_filter_area), + ioc.data, ioc.len); + priv->bcomm->bc_filter_cmd = BC_FILTER_SET; + } + else + priv->bcomm->bc_filter_cmd = BC_FILTER_CLR; + return(0); + default: + return -EOPNOTSUPP; + } +} + +/* + * Process interrupts + */ +static void +dgrs_intr(int irq, void *dev_id, struct pt_regs *regs) +{ + struct device *dev = (struct device *) dev_id; + DGRS_PRIV *priv = (DGRS_PRIV *) dev->priv; + I596_CB *cbp; + int cmd; + + ++priv->intrcnt; + if (1) ++priv->bcomm->bc_cnt[4]; + if (0) printk("%s: interrupt: irq %d", dev->name, irq); + + /* + * Get 596 command + */ + cmd = priv->scbp->cmd; + + /* + * See if RU has been restarted + */ + if ( (cmd & I596_SCB_RUC) == I596_SCB_RUC_START) + { + if (0) printk("%s: RUC start", dev->name); + priv->rfdp = (I596_RFD *) S2H(priv->scbp->rfdp); + priv->rbdp = (I596_RBD *) S2H(priv->rfdp->rbdp); + dev->tbusy = 0; /* tell upper half */ + priv->scbp->status &= ~(I596_SCB_RNR|I596_SCB_RUS); + /* if (bd->flags & TX_QUEUED) + DL_sched(bd, bdd); */ + } + + /* + * See if any CU commands to process + */ + if ( (cmd & I596_SCB_CUC) != I596_SCB_CUC_START) + { + priv->scbp->cmd = 0; /* Ignore all other commands */ + goto ack_intr; + } + priv->scbp->status &= ~(I596_SCB_CNA|I596_SCB_CUS); + + /* + * Process a command + */ + cbp = (I596_CB *) S2H(priv->scbp->cbp); + priv->scbp->cmd = 0; /* Safe to clear the command */ + for (;;) + { + switch (cbp->nop.cmd & I596_CB_CMD) + { + case I596_CB_CMD_XMIT: + dgrs_rcv_frame(dev, priv, cbp); + break; + default: + cbp->nop.status = I596_CB_STATUS_C | I596_CB_STATUS_OK; + break; + } + if (cbp->nop.cmd & I596_CB_CMD_EL) + break; + cbp = (I596_CB *) S2H(cbp->nop.next); + } + priv->scbp->status |= I596_SCB_CNA; + + /* + * Ack the interrupt + */ +ack_intr: + if (priv->plxreg) + OUTL(dev->base_addr + PLX_LCL2PCI_DOORBELL, 1); +} + +/* + * Download the board firmware + */ +static int +dgrs_download(struct device *dev) +{ + DGRS_PRIV *priv = (DGRS_PRIV *) dev->priv; + int is; + int i; + + static int iv2is[16] = { + 0, 0, 0, ES4H_IS_INT3, + 0, ES4H_IS_INT5, 0, ES4H_IS_INT7, + 0, 0, ES4H_IS_INT10, ES4H_IS_INT11, + ES4H_IS_INT12, 0, 0, ES4H_IS_INT15 }; + + /* + * Map in the dual port memory + */ + priv->vmem = ioremap(dev->mem_start, 2048*1024); + if (!priv->vmem) + { + printk("%s: cannot map in board memory\n", dev->name); + return -ENXIO; + } + + /* + * Hold the processor and configure the board addresses + */ + if (priv->plxreg) + { /* PCI bus */ + proc_reset(dev, 1); + } + else + { /* EISA bus */ + is = iv2is[dev->irq & 0x0f]; + if (!is) + { + printk("%s: Illegal IRQ %d\n", dev->name, dev->irq); + return -ENXIO; + } + OUTB(dev->base_addr + ES4H_AS_31_24, + (uchar) (dev->mem_start >> 24) ); + OUTB(dev->base_addr + ES4H_AS_23_16, + (uchar) (dev->mem_start >> 16) ); + priv->is_reg = ES4H_IS_LINEAR | is | + ((uchar) (dev->mem_start >> 8) & ES4H_IS_AS15); + OUTB(dev->base_addr + ES4H_IS, priv->is_reg); + OUTB(dev->base_addr + ES4H_EC, ES4H_EC_ENABLE); + OUTB(dev->base_addr + ES4H_PC, ES4H_PC_RESET); + OUTB(dev->base_addr + ES4H_MW, ES4H_MW_ENABLE | 0x00); + } + + /* + * See if we can do DMA on the SE-6 + */ + priv->use_dma = check_board_dma(dev); + if (priv->use_dma) + printk("%s: Bus Master DMA is enabled.\n", dev->name); + + /* + * Load and verify the code at the desired address + */ + memcpy(priv->vmem, dgrs_code, dgrs_ncode); /* Load code */ + if (memcmp(priv->vmem, dgrs_code, dgrs_ncode)) + { + iounmap(priv->vmem); + priv->vmem = NULL; + printk("%s: download compare failed\n", dev->name); + return -ENXIO; + } + + /* + * Configurables + */ + priv->bcomm = (struct bios_comm *) (priv->vmem + 0x0100); + priv->bcomm->bc_nowait = 1; /* Tell board to make printf not wait */ + priv->bcomm->bc_host = 1; /* Tell board there is a host port */ + priv->bcomm->bc_squelch = 0; /* Flag from Space.c */ + priv->bcomm->bc_150ohm = 0; /* Flag from Space.c */ + + priv->bcomm->bc_spew = 0; /* Debug flag from Space.c */ + priv->bcomm->bc_maxrfd = 0; /* Debug flag from Space.c */ + priv->bcomm->bc_maxrbd = 0; /* Debug flag from Space.c */ + + /* + * Request memory space on board for DMA chains + */ + if (priv->use_dma) + priv->bcomm->bc_hostarea_len = (2048/64) * 16; + + /* + * NVRAM configurables from Space.c + */ + priv->bcomm->bc_spantree = dgrs_spantree; + priv->bcomm->bc_hashexpire = dgrs_hashexpire; + memcpy(priv->bcomm->bc_ipaddr, dgrs_ipaddr, 4); + memcpy(priv->bcomm->bc_ipxnet, &dgrs_ipxnet, 4); + + /* + * Release processor, wait 5 seconds for board to initialize + */ + proc_reset(dev, 0); + + for (i = jiffies + 5 * HZ; i > jiffies; ) + { + if (priv->bcomm->bc_status >= BC_RUN) + break; + } + + if (priv->bcomm->bc_status < BC_RUN) + { + printk("%s: board not operating", dev->name); + return -ENXIO; + } + + priv->port = (PORT *) S2H(priv->bcomm->bc_port); + priv->scbp = (I596_SCB *) S2H(priv->port->scbp); + #if 0 /* These two methods are identical, but the 2nd is better */ + priv->rfdp = (I596_RFD *) S2H(priv->port->rfd_head); + priv->rbdp = (I596_RBD *) S2H(priv->port->rbd_head); + #else + priv->rfdp = (I596_RFD *) S2H(priv->scbp->rfdp); + priv->rbdp = (I596_RBD *) S2H(priv->rfdp->rbdp); + #endif + + priv->scbp->status = I596_SCB_CNA; /* CU is idle */ + + /* + * Get switch physical and host virtual pointers to DMA + * chaining area. NOTE: the MSB of the switch physical + * address *must* be turned off. Otherwise, the HW kludge + * that allows host access of the PLX DMA registers will + * erroneously select the PLX registers. + */ + priv->dmadesc_s = (DMACHAIN *) S2DMA(priv->bcomm->bc_hostarea); + if (priv->dmadesc_s) + priv->dmadesc_h = (DMACHAIN *) S2H(priv->dmadesc_s); + else + priv->dmadesc_h = NULL; + + /* + * Enable board interrupts + */ + if (priv->plxreg) + { /* PCI bus */ + OUTL(dev->base_addr + PLX_INT_CSR, + inl(dev->base_addr + PLX_INT_CSR) + | PLX_PCI_DOORBELL_IE); /* Enable intr to host */ + OUTL(dev->base_addr + PLX_LCL2PCI_DOORBELL, 1); + } + else + { /* EISA bus */ + } + + return (0); +} + +/* + * Probe (init) a board + */ +int +dgrs_probe1(struct device *dev) +{ + DGRS_PRIV *priv = (DGRS_PRIV *) dev->priv; + int i; + int rc; + + printk("%s: Digi RightSwitch at io=%lx mem=%lx irq=%d plx=%lx dma=%lx ", + dev->name, dev->base_addr, dev->mem_start, dev->irq, + priv->plxreg, priv->plxdma); + + /* + * Download the firmware and light the processor + */ + rc = dgrs_download(dev); + if (rc) + { + printk("\n"); + return rc; + } + + /* + * Get ether address of board + */ + memcpy(dev->dev_addr, priv->port->ethaddr, 6); + for (i = 0; i < 6; ++i) + printk("%c%2.2x", i ? ':' : ' ', dev->dev_addr[i]); + printk("\n"); + + if (dev->dev_addr[0] & 1) + { + printk("%s: Illegal Ethernet Address", dev->name); + return (-ENXIO); + } + + /* + * ACK outstanding interrupts, hook the interrupt, + * and verify that we are getting interrupts from the board. + */ + if (priv->plxreg) + OUTL(dev->base_addr + PLX_LCL2PCI_DOORBELL, 1); + rc = request_irq(dev->irq, &dgrs_intr, 0, "RightSwitch", dev); + if (rc) + return (rc); + + priv->intrcnt = 0; + for (i = jiffies + 2*HZ + HZ/2; i > jiffies; ) + if (priv->intrcnt >= 2) + break; + if (priv->intrcnt < 2) + { + printk("%s: Not interrupting on IRQ %d (%d)", + dev->name, dev->irq, priv->intrcnt); + return (-ENXIO); + } + + /* + * Register the /proc/ioports information... + */ + request_region(dev->base_addr, 256, "RightSwitch"); + + /* + * Entry points... + */ + dev->open = &dgrs_open; + dev->stop = &dgrs_close; + dev->get_stats = &dgrs_get_stats; + dev->hard_start_xmit = &dgrs_start_xmit; + dev->set_multicast_list = &dgrs_set_multicast_list; + dev->do_ioctl = &dgrs_ioctl; + + return (0); +} + +static int +dgrs_found_device( + struct device *dev, + int io, + ulong mem, + int irq, + ulong plxreg, + ulong plxdma +) +{ + DGRS_PRIV *priv; + + #ifdef MODULE + { + /* Allocate and fill new device structure. */ + int dev_size = sizeof(struct device) + sizeof(DGRS_PRIV); + + dev = (struct device *) kmalloc(dev_size, GFP_KERNEL); + memset(dev, 0, dev_size); + dev->priv = ((void *)dev) + sizeof(struct device); + priv = (DGRS_PRIV *)dev->priv; + + dev->name = priv->devname; /* An empty string. */ + dev->base_addr = io; + dev->mem_start = mem; + dev->mem_end = mem + 2048 * 1024 - 1; + dev->irq = irq; + priv->plxreg = plxreg; + priv->plxdma = plxdma; + priv->vplxdma = NULL; + + dev->init = dgrs_probe1; + + ether_setup(dev); + priv->next_dev = dgrs_root_dev; + dgrs_root_dev = dev; + if (register_netdev(dev) != 0) + return -EIO; + } + #else + { + if (dev) + { + dev->priv = kmalloc(sizeof (DGRS_PRIV), GFP_KERNEL); + memset(dev->priv, 0, sizeof (DGRS_PRIV)); + } + dev = init_etherdev(dev, sizeof(DGRS_PRIV)); + priv = (DGRS_PRIV *)dev->priv; + + dev->base_addr = io; + dev->mem_start = mem; + dev->mem_end = mem + 2048 * 1024; + dev->irq = irq; + priv->plxreg = plxreg; + priv->plxdma = plxdma; + priv->vplxdma = NULL; + + dgrs_probe1(dev); + } + #endif + + return (0); +} + +/* + * Scan for all boards + */ +static int +dgrs_scan(struct device *dev) +{ + int cards_found = 0; + uint io; + uint mem; + uint irq; + uint plxreg; + uint plxdma; + + /* + * First, check for PCI boards + */ + if (pcibios_present()) + { + int pci_index = 0; + + for (; pci_index < 8; pci_index++) + { + uchar pci_bus, pci_device_fn; + uchar pci_irq; + uchar pci_latency; + ushort pci_command; + + if (pcibios_find_device(SE6_PCI_VENDOR_ID, + SE6_PCI_DEVICE_ID, + pci_index, &pci_bus, + &pci_device_fn)) + break; + + pcibios_read_config_byte(pci_bus, pci_device_fn, + PCI_INTERRUPT_LINE, &pci_irq); + pcibios_read_config_dword(pci_bus, pci_device_fn, + PCI_BASE_ADDRESS_0, &plxreg); + pcibios_read_config_dword(pci_bus, pci_device_fn, + PCI_BASE_ADDRESS_1, &io); + pcibios_read_config_dword(pci_bus, pci_device_fn, + PCI_BASE_ADDRESS_2, &mem); + pcibios_read_config_dword(pci_bus, pci_device_fn, + 0x30, &plxdma); + irq = pci_irq; + plxreg &= ~15; + io &= ~3; + mem &= ~15; + plxdma &= ~15; + + /* + * On some BIOSES, the PLX "expansion rom" (used for DMA) + * address comes up as "0". This is probably because + * the BIOS doesn't see a valid 55 AA ROM signature at + * the "ROM" start and zeroes the address. To get + * around this problem the SE-6 is configured to ask + * for 4 MB of space for the dual port memory. We then + * must set its range back to 2 MB, and use the upper + * half for DMA register access + */ + OUTL(io + PLX_SPACE0_RANGE, 0xFFE00000L); + if (plxdma == 0) + plxdma = mem + (2048L * 1024L); + pcibios_write_config_dword(pci_bus, pci_device_fn, + 0x30, plxdma + 1); + pcibios_read_config_dword(pci_bus, pci_device_fn, + 0x30, &plxdma); + plxdma &= ~15; + + /* + * Get and check the bus-master and latency values. + * Some PCI BIOSes fail to set the master-enable bit, + * and the latency timer must be set to the maximum + * value to avoid data corruption that occurs when the + * timer expires during a transfer. Yes, it's a bug. + */ + pcibios_read_config_word(pci_bus, pci_device_fn, + PCI_COMMAND, &pci_command); + if ( ! (pci_command & PCI_COMMAND_MASTER)) + { + printk(" Setting the PCI Master Bit!\n"); + pci_command |= PCI_COMMAND_MASTER; + pcibios_write_config_word(pci_bus, + pci_device_fn, + PCI_COMMAND, pci_command); + } + pcibios_read_config_byte(pci_bus, pci_device_fn, + PCI_LATENCY_TIMER, &pci_latency); + if (pci_latency != 255) + { + printk(" Overriding PCI latency timer: " + "was %d, now is 255.\n", pci_latency); + pcibios_write_config_byte(pci_bus, + pci_device_fn, + PCI_LATENCY_TIMER, 255); + } + + dgrs_found_device(dev, io, mem, irq, plxreg, plxdma); + + dev = 0; + cards_found++; + } + } + + /* + * Second, check for EISA boards + */ + if (EISA_bus) + { + static int is2iv[8] = { 0, 3, 5, 7, 10, 11, 12, 15 }; + + for (io = 0x1000; io < 0x9000; io += 0x1000) + { + if (inb(io+ES4H_MANUFmsb) != 0x10 + || inb(io+ES4H_MANUFlsb) != 0x49 + || inb(io+ES4H_PRODUCT) != ES4H_PRODUCT_CODE) + continue; + + if ( ! (inb(io+ES4H_EC) & ES4H_EC_ENABLE) ) + continue; /* Not EISA configured */ + + mem = (inb(io+ES4H_AS_31_24) << 24) + + (inb(io+ES4H_AS_23_16) << 16); + + irq = is2iv[ inb(io+ES4H_IS) & ES4H_IS_INTMASK ]; + + dgrs_found_device(dev, io, mem, irq, 0L, 0L); + + dev = 0; + ++cards_found; + } + } + + return cards_found; +} + +/* + * Module/driver initialization points. Two ways, depending on + * whether we are a module or statically linked, ala Don Becker's + * 3c59x driver. + */ + +#ifdef MODULE + +/* + * Variables that can be overridden from command line + */ +static int debug = -1; +static int dma = -1; +static int hashexpire = -1; +static int spantree = -1; +static int ipaddr[4] = { -1 }; +static long ipxnet = -1; + +int +init_module(void) +{ + int cards_found; + + /* + * Command line variable overrides + * debug=NNN + * dma=0/1 + * spantree=0/1 + * hashexpire=NNN + * ipaddr=A,B,C,D + * ipxnet=NNN + */ + if (debug >= 0) + dgrs_debug = debug; + if (dma >= 0) + dgrs_dma = dma; + if (hashexpire >= 0) + dgrs_hashexpire = hashexpire; + if (spantree >= 0) + dgrs_spantree = spantree; + if (ipaddr[0] != -1) + { + int i; + + for (i = 0; i < 4; ++i) + dgrs_ipaddr[i] = ipaddr[i]; + } + if (ipxnet != -1) + dgrs_ipxnet = htonl( ipxnet ); + + if (dgrs_debug) + { + printk("dgrs: SW=%s FW=Build %d %s\n", + version, dgrs_firmnum, dgrs_firmdate); + } + + /* + * Find and configure all the cards + */ + dgrs_root_dev = NULL; + cards_found = dgrs_scan(0); + + return cards_found ? 0 : -ENODEV; +} + +void +cleanup_module(void) +{ + while (dgrs_root_dev) + { + struct device *next_dev; + DGRS_PRIV *priv; + + priv = (DGRS_PRIV *) dgrs_root_dev->priv; + next_dev = priv->next_dev; + unregister_netdev(dgrs_root_dev); + + proc_reset(dgrs_root_dev, 1); + + if (priv->vmem) + iounmap(priv->vmem); + if (priv->vplxdma) + iounmap((uchar *) priv->vplxdma); + + release_region(dgrs_root_dev->base_addr, 256); + + free_irq(dgrs_root_dev->irq, dgrs_root_dev); + + kfree(dgrs_root_dev); + dgrs_root_dev = next_dev; + } +} + +#else + +int +dgrs_probe(struct device *dev) +{ + int cards_found; + + cards_found = dgrs_scan(dev); + if (dgrs_debug && cards_found) + printk("dgrs: SW=%s FW=Build %d %s\n", + version, dgrs_firmnum, dgrs_firmdate); + return cards_found ? 0 : -ENODEV; +} +#endif |