diff options
Diffstat (limited to 'cfe/cfe/dev/dev_dp83815.c')
-rw-r--r-- | cfe/cfe/dev/dev_dp83815.c | 2290 |
1 files changed, 2290 insertions, 0 deletions
diff --git a/cfe/cfe/dev/dev_dp83815.c b/cfe/cfe/dev/dev_dp83815.c new file mode 100644 index 0000000..b9fb644 --- /dev/null +++ b/cfe/cfe/dev/dev_dp83815.c @@ -0,0 +1,2290 @@ +/* ********************************************************************* + * Broadcom Common Firmware Environment (CFE) + * + * NS DP83815 Ethernet Driver File: dev_dp83815.c + * + ********************************************************************* + * + * Copyright 2000,2001,2002,2003 + * Broadcom Corporation. All rights reserved. + * + * This software is furnished under license and may be used and + * copied only in accordance with the following terms and + * conditions. Subject to these conditions, you may download, + * copy, install, use, modify and distribute modified or unmodified + * copies of this software in source and/or binary form. No title + * or ownership is transferred hereby. + * + * 1) Any source code used, modified or distributed must reproduce + * and retain this copyright notice and list of conditions + * as they appear in the source file. + * + * 2) No right is granted to use any trade name, trademark, or + * logo of Broadcom Corporation. The "Broadcom Corporation" + * name may not be used to endorse or promote products derived + * from this software without the prior written permission of + * Broadcom Corporation. + * + * 3) THIS SOFTWARE IS PROVIDED "AS-IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING BUT NOT LIMITED TO, ANY IMPLIED + * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + * PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT + * SHALL BROADCOM BE LIABLE FOR ANY DAMAGES WHATSOEVER, AND IN + * PARTICULAR, BROADCOM SHALL NOT BE LIABLE FOR DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR + * TORT (INCLUDING NEGLIGENCE OR OTHERWISE), EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + ********************************************************************* */ + +#include "sbmips.h" + +#ifndef _SB_MAKE64 +#define _SB_MAKE64(x) ((uint64_t)(x)) +#endif +#ifndef _SB_MAKEMASK1 +#define _SB_MAKEMASK1(n) (_SB_MAKE64(1) << _SB_MAKE64(n)) +#endif + +#include "lib_types.h" +#include "lib_hssubr.h" +#include "lib_malloc.h" +#include "lib_string.h" +#define blockcopy memcpy +#include "lib_printf.h" +#include "lib_queue.h" + +#include "cfe_iocb.h" +#include "cfe_device.h" +#include "cfe_ioctl.h" +#include "cfe_timer.h" +#include "cfe_error.h" +#include "cfe_irq.h" + +#include "pcivar.h" +#include "pcireg.h" + +#include "dp83815.h" +#include "mii.h" + +/* This is a driver for the National Semiconductor DP83815 (MacPhyter) + 10/100 MAC with integrated PHY. + + The current version has been developed for the Netgear FA311 and + FA312 NICs. These include an EEPROM with automatically loaded + setup information that includes station address filtering. + Operation without such an EEPROM has not been tested. + + This SB1250 version takes advantage of DMA coherence and uses + "preserve bit lanes" addresses for all accesses that cross the + ZBbus-PCI bridge. */ + +#ifndef MACPHYTER_DEBUG +#define MACPHYTER_DEBUG 0 +#endif +#ifndef MACPHYTER_TEST +#define MACPHYTER_TEST 0 +#endif + +/* Set IPOLL to drive processing through the pseudo-interrupt + dispatcher. Set XPOLL to drive processing by an external polling + agent. Setting both is ok. */ + +#ifndef IPOLL +#define IPOLL 0 +#endif +#ifndef XPOLL +#define XPOLL 1 +#endif + +#define ENET_ADDR_LEN 6 /* size of an ethernet address */ +#define MIN_ETHER_PACK 64 /* min size of a packet */ +#define MAX_ETHER_PACK 1518 /* max size of a packet */ +#define CRC_SIZE 4 /* size of CRC field */ + +/* Packet buffers. For the DP83815, an rx packet must be aligned to a + 32-bit word boundary, and we would like it aligned to a cache line + boundary for performance. Also, the buffers "should" be allocated + in 32 byte multiples (5.3.2). */ + +#define ETH_PKTBUF_LEN (((MAX_ETHER_PACK+31)/32)*32) + +#if __long64 +typedef struct eth_pkt_s { + queue_t next; /* 16 */ + uint8_t *buffer; /* 8 */ + uint32_t flags; /* 4 */ + int32_t length; /* 4 */ + uint8_t data[ETH_PKTBUF_LEN]; +} eth_pkt_t; +#else +typedef struct eth_pkt_s { + queue_t next; /* 8 */ + uint8_t *buffer; /* 4 */ + uint32_t flags; /* 4 */ + int32_t length; /* 4 */ + uint32_t unused[3]; /* 12 */ + uint8_t data[ETH_PKTBUF_LEN]; +} eth_pkt_t; +#endif + +#define CACHE_ALIGN 32 +#define ETH_PKTBUF_LINES ((sizeof(eth_pkt_t) + (CACHE_ALIGN-1))/CACHE_ALIGN) +#define ETH_PKTBUF_SIZE (ETH_PKTBUF_LINES*CACHE_ALIGN) +#define ETH_PKTBUF_OFFSET (offsetof(eth_pkt_t, data)) + +#define ETH_PKT_BASE(data) ((eth_pkt_t *)((data) - ETH_PKTBUF_OFFSET)) + +/* packet flags */ +#define ETH_TX_SETUP 1 /* assumes Perfect Filtering format */ + +static void +show_packet(char c, eth_pkt_t *pkt) +{ + int i; + int n = (pkt->length < 32 ? pkt->length : 32); + + xprintf("%c[%4d]:", c, pkt->length); + for (i = 0; i < n; i++) { + if (i % 4 == 0) + xprintf(" "); + xprintf("%02x", pkt->buffer[i]); + } + xprintf("\n"); +} + + +/* Descriptor structures. NOTE: To avoid having descriptors straddle + cache lines, we append a pad word, ignored by DMA, to each. */ + +typedef struct rx_dscr { + pci_addr_t rxd_link; + uint32_t rxd_cmdsts; + pci_addr_t rxd_bufptr; + uint32_t rxd_pad; +} rx_dscr; + +typedef struct tx_dscr { + pci_addr_t txd_link; + uint32_t txd_cmdsts; + pci_addr_t txd_bufptr; + uint32_t txd_pad; +} tx_dscr; + + +/* Driver data structures */ + +typedef enum { + eth_state_uninit, + eth_state_off, + eth_state_on, + eth_state_broken +} eth_state_t; + +#define ETH_PKTPOOL_SIZE 32 + +typedef struct dp83815_softc { + uint32_t membase; + uint8_t irq; /* interrupt mapping (used if IPOLL) */ + pcitag_t tag; /* tag for configuration registers */ + + uint8_t hwaddr[ENET_ADDR_LEN]; + uint16_t device; /* chip device code */ + uint8_t revision; /* chip revision and step */ + + eth_state_t state; /* current state */ + uint32_t intmask; /* interrupt mask */ + + /* These fields are set before calling dp83815_hwinit */ + int linkspeed; /* encodings from cfe_ioctl */ + int loopback; + + /* Packet free list */ + queue_t freelist; + uint8_t *pktpool; + queue_t rxqueue; + + /* The descriptor tables */ + uint8_t *rxdscrmem; /* receive descriptors */ + uint8_t *txdscrmem; /* transmit descriptors */ + + /* These fields keep track of where we are in tx/rx processing */ + volatile rx_dscr *rxdscr_start; /* beginning of ring */ + volatile rx_dscr *rxdscr_end; /* end of ring */ + volatile rx_dscr *rxdscr_remove; /* next one we expect DMA to use */ + volatile rx_dscr *rxdscr_add; /* next place to put a buffer */ + int rxdscr_onring; + + volatile tx_dscr *txdscr_start; /* beginning of ring */ + volatile tx_dscr *txdscr_end; /* end of ring */ + volatile tx_dscr *txdscr_remove; /* next one we will use for tx */ + volatile tx_dscr *txdscr_add; /* next place to put a buffer */ + + cfe_devctx_t *devctx; + + /* These fields describe the PHY */ + int phy_addr; + int phy_check; + uint32_t phy_status; + + /* Statistics */ + uint32_t inpkts; + uint32_t outpkts; + uint32_t interrupts; + uint32_t rx_interrupts; + uint32_t tx_interrupts; + uint32_t bus_errors; +} dp83815_softc; + + +/* Entry to and exit from critical sections (currently relative to + interrupts only, not SMP) */ + +#if CFG_INTERRUPTS +#define CS_ENTER(sc) cfe_disable_irq(sc->irq) +#define CS_EXIT(sc) cfe_enable_irq(sc->irq) +#else +#define CS_ENTER(sc) ((void)0) +#define CS_EXIT(sc) ((void)0) +#endif + + +/* Driver parameterization */ + +#define MAXRXDSCR 32 +#define MAXTXDSCR 32 +#define MINRXRING 8 + + +/* Prototypes */ + +static void dp83815_ether_probe(cfe_driver_t *drv, + unsigned long probe_a, unsigned long probe_b, + void *probe_ptr); + + +/* Address mapping macros */ + +/* Note that PTR_TO_PHYS only works with 32-bit addresses, but then + so does the dp83815. */ +#define PTR_TO_PHYS(x) (K0_TO_PHYS((uintptr_t)(x))) +#define PHYS_TO_PTR(a) ((uint8_t *)PHYS_TO_K0(a)) + +/* All mappings through the PCI host bridge use match bits mode. */ +#define PHYS_TO_PCI(a) ((uint32_t) (a) | 0x20000000) +#define PCI_TO_PHYS(a) ((uint32_t) (a) & 0x1FFFFFFF) + +#define PCI_TO_PTR(a) (PHYS_TO_PTR(PCI_TO_PHYS(a))) +#define PTR_TO_PCI(x) (PHYS_TO_PCI(PTR_TO_PHYS(x))) + +#if __long64 +#define READCSR(sc,csr) \ + (*((volatile uint32_t *) (PHYS_TO_XKSEG_UNCACHED((sc)->membase + (csr))))) + +#define WRITECSR(sc,csr,val) \ + (*((volatile uint32_t *) \ + (PHYS_TO_XKSEG_UNCACHED((sc)->membase + (csr)))) = (val)) + +#else +#define READCSR(sc,csr) \ + (hs_read32(PHYS_TO_XKSEG_UNCACHED((sc)->membase + (csr)))) + +#define WRITECSR(sc,csr,val) \ + (hs_write32(PHYS_TO_XKSEG_UNCACHED((sc)->membase + (csr)), (val))) + +#endif + +#define RESET_ADAPTER(sc) \ + { \ + /* XXX */ \ + } + + +/* Debugging */ + +static void +dumpstat(dp83815_softc *sc) +{ + xprintf("-- CR = %08X CFG = %08x\n", + READCSR(sc, R_CR), READCSR(sc, R_CFG)); +} + +static void +dumpcsrs(dp83815_softc *sc) +{ + int reg; + + xprintf("-------------\n"); + for (reg = 0; reg < R_MIBC; reg += 4) { + xprintf("CSR %02X = %08X\n", reg, READCSR(sc, reg)); + } + xprintf("-------------\n"); +} + + +/* Packet management */ + +/* ********************************************************************* + * ETH_ALLOC_PKT(sc) + * + * Allocate a packet from the free list. + * + * Input parameters: + * sc - eth structure + * + * Return value: + * pointer to packet structure, or NULL if none available + ********************************************************************* */ +static eth_pkt_t * +eth_alloc_pkt(dp83815_softc *sc) +{ + eth_pkt_t *pkt; + + CS_ENTER(sc); + pkt = (eth_pkt_t *) q_deqnext(&sc->freelist); + CS_EXIT(sc); + if (!pkt) return NULL; + + pkt->buffer = pkt->data; + pkt->length = ETH_PKTBUF_LEN; + pkt->flags = 0; + + return pkt; +} + + +/* ********************************************************************* + * ETH_FREE_PKT(sc,pkt) + * + * Return a packet to the free list + * + * Input parameters: + * sc - sbmac structure + * pkt - packet to return + * + * Return value: + * nothing + ********************************************************************* */ +static void +eth_free_pkt(dp83815_softc *sc, eth_pkt_t *pkt) +{ + CS_ENTER(sc); + q_enqueue(&sc->freelist, &pkt->next); + CS_EXIT(sc); +} + + +/* ********************************************************************* + * ETH_INITFREELIST(sc) + * + * Initialize the buffer free list for this mac. The memory + * allocated to the free list is carved up and placed on a linked + * list of buffers for use by the mac. + * + * Input parameters: + * sc - eth structure + * + * Return value: + * nothing + ********************************************************************* */ +static void +eth_initfreelist(dp83815_softc *sc) +{ + int idx; + uint8_t *ptr; + eth_pkt_t *pkt; + + q_init(&sc->freelist); + + ptr = sc->pktpool; + for (idx = 0; idx < ETH_PKTPOOL_SIZE; idx++) { + pkt = (eth_pkt_t *) ptr; + eth_free_pkt(sc, pkt); + ptr += ETH_PKTBUF_SIZE; + } +} + + +/* Utilities */ + +static const char * +dp83815_devname(dp83815_softc *sc) +{ + return (sc->devctx != NULL ? cfe_device_name(sc->devctx) : "eth?"); +} + + +/* Descriptor ring management */ + +static int +dp83815_add_rcvbuf(dp83815_softc *sc, eth_pkt_t *pkt) +{ + volatile rx_dscr *rxd; + volatile rx_dscr *nextrxd; + + rxd = sc->rxdscr_add; + + /* Figure out where the next descriptor will go */ + nextrxd = rxd+1; + if (nextrxd == sc->rxdscr_end) { + nextrxd = sc->rxdscr_start; + } + + /* + * If the next one is the same as our remove pointer, + * the ring is considered full. (it actually has room for + * one more, but we reserve the remove == add case for "empty") + */ + if (nextrxd == sc->rxdscr_remove) return -1; + + rxd->rxd_bufptr = PTR_TO_PCI(pkt->buffer); + rxd->rxd_cmdsts = M_DES1_INTR | V_DES1_SIZE(ETH_PKTBUF_LEN); + + /* success, advance the pointer */ + sc->rxdscr_add = nextrxd; + CS_ENTER(sc); + sc->rxdscr_onring++; + CS_EXIT(sc); + + return 0; +} + +static void +dp83815_fillrxring(dp83815_softc *sc) +{ + eth_pkt_t *pkt; + + while (1) { + CS_ENTER(sc); + if (sc->rxdscr_onring >= MINRXRING) { + CS_EXIT(sc); + break; + } + CS_EXIT(sc); + pkt = eth_alloc_pkt(sc); + if (pkt == NULL) { + /* could not allocate a buffer */ + break; + } + if (dp83815_add_rcvbuf(sc, pkt) != 0) { + /* could not add buffer to ring */ + eth_free_pkt(sc, pkt); + break; + } + } +} + + +/* ********************************************************************* + * DP83815_RX_CALLBACK(sc, pkt) + * + * Receive callback routine. This routine is invoked when a + * buffer queued for receives is filled. In this simple driver, + * all we do is add the packet to a per-MAC queue for later + * processing, and try to put a new packet in the place of the one + * that was removed from the queue. + * + * Input parameters: + * sc - interface + * ptk - packet context (eth_pkt structure) + * + * Return value: + * nothing + ********************************************************************* */ +static void +dp83815_rx_callback(dp83815_softc *sc, eth_pkt_t *pkt) +{ + if (MACPHYTER_DEBUG) show_packet('>', pkt); /* debug */ + + CS_ENTER(sc); + q_enqueue(&sc->rxqueue, &pkt->next); + CS_EXIT(sc); + sc->inpkts++; + + dp83815_fillrxring(sc); +} + + +static void +dp83815_procrxring(dp83815_softc *sc) +{ + volatile rx_dscr *rxd; + eth_pkt_t *pkt; + eth_pkt_t *newpkt; + uint32_t cmdsts; + + for (;;) { + rxd = (volatile rx_dscr *) sc->rxdscr_remove; + + cmdsts = rxd->rxd_cmdsts; + if ((cmdsts & M_DES1_OWN) == 0) { + /* end of ring, no more packets */ + break; + } + + pkt = ETH_PKT_BASE(PCI_TO_PTR(rxd->rxd_bufptr)); + pkt->length = G_DES1_SIZE(cmdsts) - CRC_SIZE; + + /* Drop error packets */ + if (cmdsts & M_DES1_RX_ERRORS) { +#if MACPHYTER_DEBUG + if (pkt->length >= MIN_ETHER_PACK - CRC_SIZE) + xprintf("%s: rx error %08X\n", dp83815_devname(sc), cmdsts); +#endif + dp83815_add_rcvbuf(sc, pkt); + goto next; + } + + /* Pass up the packet */ + dp83815_rx_callback(sc, pkt); + + /* put a buffer back on the ring to replace this one */ + newpkt = eth_alloc_pkt(sc); + if (newpkt) dp83815_add_rcvbuf(sc, newpkt); + +next: + /* update the pointer, accounting for buffer wrap. */ + rxd++; + if (rxd == sc->rxdscr_end) + rxd = sc->rxdscr_start; + + sc->rxdscr_remove = (rx_dscr *) rxd; + CS_ENTER(sc); + sc->rxdscr_onring--; + CS_EXIT(sc); + } +} + + +static int +dp83815_add_txbuf(dp83815_softc *sc, eth_pkt_t *pkt) +{ + volatile tx_dscr *txd; + volatile tx_dscr *nexttxd; + + txd = sc->txdscr_add; + + /* Figure out where the next descriptor will go */ + nexttxd = (txd+1); + if (nexttxd == sc->txdscr_end) { + nexttxd = sc->txdscr_start; + } + + /* If the next one is the same as our remove pointer, + the ring is considered full. (it actually has room for + one more, but we reserve the remove == add case for "empty") */ + + if (nexttxd == sc->txdscr_remove) return -1; + + txd->txd_bufptr = PTR_TO_PCI(pkt->buffer); + txd->txd_cmdsts = M_DES1_INTR | M_DES1_OWN | V_DES1_SIZE(pkt->length); + + /* success, advance the pointer */ + sc->txdscr_add = nexttxd; + + return 0; +} + + +static int +dp83815_transmit(dp83815_softc *sc,eth_pkt_t *pkt) +{ + int rv; + + if (MACPHYTER_DEBUG) show_packet('<', pkt); /* debug */ + + rv = dp83815_add_txbuf(sc, pkt); + sc->outpkts++; + + WRITECSR(sc, R_CR, M_CR_TXE | M_CR_RXE); + return rv; +} + + +static void +dp83815_proctxring(dp83815_softc *sc) +{ + volatile tx_dscr *txd; + eth_pkt_t *pkt; + uint32_t cmdsts; + + for (;;) { + txd = (volatile tx_dscr *) sc->txdscr_remove; + + if (txd == sc->txdscr_add) { + /* ring is empty, no buffers to process */ + break; + } + + cmdsts = txd->txd_cmdsts; + if (cmdsts & M_DES1_OWN) { + /* Reached a packet still being transmitted */ + break; + } + + /* Just free the packet */ + pkt = ETH_PKT_BASE(PCI_TO_PTR(txd->txd_bufptr)); + eth_free_pkt(sc, pkt); + + /* update the pointer, accounting for buffer wrap. */ + txd++; + if (txd == sc->txdscr_end) + txd = sc->txdscr_start; + + sc->txdscr_remove = (tx_dscr *) txd; + } +} + + +static void +dp83815_initrings(dp83815_softc *sc) +{ + volatile tx_dscr *txd, *txn; + volatile rx_dscr *rxd, *rxn; + + /* Claim ownership of all descriptors for the driver */ + + for (txd = sc->txdscr_start; txd != sc->txdscr_end; txd++) { + txn = txd + 1; + if (txn == sc->txdscr_end) txn = sc->txdscr_start; + txd->txd_link = PTR_TO_PCI(txn); + txd->txd_cmdsts = 0; + txd->txd_pad = 0; + } + for (rxd = sc->rxdscr_start; rxd != sc->rxdscr_end; rxd++) { + rxn = rxd + 1; + if (rxn == sc->rxdscr_end) rxn = sc->rxdscr_start; + rxd->rxd_link = PTR_TO_PCI(rxn); + rxd->rxd_cmdsts = M_DES1_OWN; + rxd->rxd_pad = 0; + } + + /* Init the ring pointers */ + + sc->txdscr_add = sc->txdscr_remove = sc->txdscr_start; + sc->rxdscr_add = sc->rxdscr_remove = sc->rxdscr_start; + sc->rxdscr_onring = 0; + + /* Add stuff to the receive ring */ + + dp83815_fillrxring(sc); +} + + +static int +dp83815_init(dp83815_softc *sc) +{ + /* Allocate descriptor rings */ + sc->rxdscrmem = KMALLOC(MAXRXDSCR*sizeof(rx_dscr), sizeof(rx_dscr)); + sc->txdscrmem = KMALLOC(MAXTXDSCR*sizeof(tx_dscr), sizeof(tx_dscr)); + + /* Allocate buffer pool */ + sc->pktpool = KMALLOC(ETH_PKTPOOL_SIZE*ETH_PKTBUF_SIZE, CACHE_ALIGN); + eth_initfreelist(sc); + q_init(&sc->rxqueue); + + /* Fill in pointers to the rings */ + sc->rxdscr_start = (rx_dscr *) (sc->rxdscrmem); + sc->rxdscr_end = sc->rxdscr_start + MAXRXDSCR; + sc->rxdscr_add = sc->rxdscr_start; + sc->rxdscr_remove = sc->rxdscr_start; + sc->rxdscr_onring = 0; + + sc->txdscr_start = (tx_dscr *) (sc->txdscrmem); + sc->txdscr_end = sc->txdscr_start + MAXTXDSCR; + sc->txdscr_add = sc->txdscr_start; + sc->txdscr_remove = sc->txdscr_start; + + dp83815_initrings(sc); + + return 0; +} + + +static void +dp83815_resetrings(dp83815_softc *sc) +{ + volatile tx_dscr *txd; + volatile rx_dscr *rxd; + eth_pkt_t *pkt; + + /* Free already-sent descriptors and buffers */ + dp83815_proctxring(sc); + + /* Free any pending but unsent */ + txd = (volatile tx_dscr *) sc->txdscr_remove; + while (txd != sc->txdscr_add) { + txd->txd_cmdsts &=~ M_DES1_OWN; + pkt = ETH_PKT_BASE(PCI_TO_PTR(txd->txd_bufptr)); + eth_free_pkt(sc, pkt); + + txd++; + if (txd == sc->txdscr_end) + txd = sc->txdscr_start; + } + sc->txdscr_add = sc->txdscr_remove; + + /* Discard any received packets as well as all free buffers */ + rxd = (volatile rx_dscr *) sc->rxdscr_remove; + while (rxd != sc->rxdscr_add) { + rxd->rxd_cmdsts |= M_DES1_OWN; + pkt = ETH_PKT_BASE(PCI_TO_PTR(rxd->rxd_bufptr)); + eth_free_pkt(sc, pkt); + + rxd++; + if (rxd == sc->rxdscr_end) + rxd = sc->rxdscr_start; + CS_ENTER(sc); + sc->rxdscr_onring--; + CS_EXIT(sc); + } + + /* Reestablish the initial state. */ + dp83815_initrings(sc); +} + + +#if 0 /* XXX Multicast filtering not yet implemented. */ +/* CRCs */ + +#define IEEE_CRC32_POLY 0xEDB88320UL /* CRC-32 Poly -- either endian */ + +static uint32_t +dp83815_crc32(const uint8_t *databuf, unsigned int datalen) +{ + unsigned int idx, bit, data; + uint32_t crc; + + crc = 0xFFFFFFFFUL; + for (idx = 0; idx < datalen; idx++) + for (data = *databuf++, bit = 0; bit < 8; bit++, data >>= 1) + crc = (crc >> 1) ^ (((crc ^ data) & 1) ? IEEE_CRC32_POLY : 0); + return crc; +} + +#define dp83815_mchash(mca) (dp83815_crc32((mca), 6) & 0x1FF) +#endif + + +#if MACPHYTER_TEST +/* EEPROM access */ + +/* Current NICs use the EEPROM auto-load feature and there is no need + for explicit EEPROM access. The following routines are included + for future applications and have been tested (Netgear FA311). */ + +/**************************************************************************** + * dp83815_spin(sc, ns) + * + * Spin for a short interval (nominally in nanoseconds) + * + * Input Parameters: ns - minimum required nsec. + * + * The delay loop uses uncached PCI reads, each of which requires + * at least 3 PCI bus clocks (45 ns at 66 MHz) to complete. The + * actual delay will be longer (much longer if preempted). + ***************************************************************************/ + +#define PCI_MIN_DELAY 45 + +static void +dp83815_spin(dp83815_softc *sc, long nanoseconds) +{ + long delay; + volatile uint32_t t; + + for (delay = nanoseconds; delay > 0; delay -= PCI_MIN_DELAY) + t = READCSR(sc, R_SRR); +} + + +/* + * The recommended EEPROM is the NM9306. + * Delays below are chosen to meet specs for NS93C64 (slow M variant). + * Current parts are faster. + * Reference: NS Memory Data Book, 1994 + */ + +#define EEPROM_SIZE (2*0x0C) +#define EEPROM_MAX_CYCLES 32 + +#define EEPROM_CMD_BITS 3 +#define EEPROM_ADDR_BITS 6 + +#define K_EEPROM_READ_CMD 06 +#define K_EEPROM_WRITE_CMD 05 + +#define EEPROM_CRC_INDEX (EEPROM_SIZE-2) + +#define EEPROM_WORD(rom,offset) ((rom)[offset] | ((rom)[offset+1] << 8)) + +static void +eeprom_idle_state(dp83815_softc *sc) +{ + uint32_t ctrl; + unsigned int i; + + ctrl = READCSR(sc, R_MEAR); + + ctrl |= M_MEAR_EESEL; + WRITECSR(sc, R_MEAR, ctrl); + dp83815_spin(sc, 100); /* CS setup (Tcss=100) */ + + /* Run the clock through the maximum number of pending read cycles */ + for (i = 0; i < EEPROM_MAX_CYCLES*2; i++) { + ctrl ^= M_MEAR_EECLK; + WRITECSR(sc, R_MEAR, ctrl); + dp83815_spin(sc, 1000); /* SK period (Fsk=0.5MHz) */ + } + + /* Deassert EEPROM Chip Select */ + ctrl &=~ M_MEAR_EESEL; + WRITECSR(sc, R_MEAR, ctrl); + dp83815_spin(sc, 50); /* CS recovery (Tsks=50) */ +} + +static void +eeprom_send_command_bit(dp83815_softc *sc, unsigned int data) +{ + uint32_t ctrl; + + ctrl = READCSR(sc, R_MEAR); + + /* Place the data bit on the bus */ + if (data == 1) + ctrl |= M_MEAR_EEDI; + else + ctrl &=~ M_MEAR_EEDI; + + WRITECSR(sc, R_MEAR, ctrl); + dp83815_spin(sc, 360); /* setup: Tdis=200 */ + + /* Now clock the data into the EEPROM */ + WRITECSR(sc, R_MEAR, ctrl | M_MEAR_EECLK); + dp83815_spin(sc, 900); /* clock high, Tskh=500 */ + WRITECSR(sc, R_MEAR, ctrl); + dp83815_spin(sc, 450); /* clock low, Tskl=250 */ + + /* Now clear the data bit */ + ctrl &=~ M_MEAR_EEDI; /* data invalid, Tidh=20 for SK^ */ + WRITECSR(sc, R_MEAR, ctrl); + dp83815_spin(sc, 270); /* min cycle, 1/Fsk=2000 */ +} + +static uint16_t +eeprom_read_bit(dp83815_softc *sc) +{ + uint32_t ctrl; + + ctrl = READCSR(sc, R_MEAR); + + /* Generate a clock cycle before doing a read */ + WRITECSR(sc, R_MEAR, ctrl | M_MEAR_EECLK); /* rising edge */ + dp83815_spin(sc, 1000); /* clock high, Tskh=500, Tpd=1000 */ + WRITECSR(sc, R_MEAR, ctrl); /* falling edge */ + dp83815_spin(sc, 1000); /* clock low, 1/Fsk=2000 */ + + ctrl = READCSR(sc, R_MEAR); + return ((ctrl & M_MEAR_EEDO) != 0 ? 1 : 0); +} + +#define CMD_BIT_MASK (1 << (EEPROM_CMD_BITS+EEPROM_ADDR_BITS-1)) + +static uint16_t +eeprom_read_word(dp83815_softc *sc, unsigned int index) +{ + uint16_t command, word; + uint32_t ctrl; + unsigned int i; + + ctrl = READCSR(sc, R_MEAR) | M_MEAR_EESEL; + + /* Assert the EEPROM CS line */ + WRITECSR(sc, R_MEAR, ctrl); + dp83815_spin(sc, 100); /* CS setup, Tcss = 100 */ + + /* Send the read command to the EEPROM */ + command = (K_EEPROM_READ_CMD << EEPROM_ADDR_BITS) | index; + for (i = 0; i < EEPROM_CMD_BITS+EEPROM_ADDR_BITS; i++) { + eeprom_send_command_bit(sc, (command & CMD_BIT_MASK) != 0 ? 1 : 0); + command <<= 1; + } + + /* Now read the bits from the EEPROM (MSB first) */ + word = 0; + for (i = 0; i < 16; ++i) { + word <<= 1; + word |= eeprom_read_bit(sc); + } + + /* Clear the EEPROM CS Line, CS hold, Tcsh = 0 */ + WRITECSR(sc, R_MEAR, ctrl &~ M_MEAR_EESEL); + + return word; +} + + +/**************************************************************************** + * eeprom_checksum() + * + * Calculate the checksum of the EEPROM and return it. See Section + * 4.2.4 for the algorithm. + ***************************************************************************/ + +static uint16_t +eeprom_checksum(const uint8_t rom[]) +{ + uint16_t sum; + int i; + + sum = 0; + for (i = 0; i < EEPROM_SIZE-1; i++) + sum += rom[i]; + sum ^= 0xFF; + return (((sum + 1) & 0xFF) << 8) | 0x55; +} + + +/**************************************************************************** + * eeprom_read_all(sc, uint8_t dest) + * + * Read the entire EEPROM into the srom array + * + * Input parameters: + * sc - dp83815 state + ***************************************************************************/ + +static int +eeprom_read_all(dp83815_softc *sc, uint8_t dest[]) +{ + int i; + uint16_t cksum, temp; + + WRITECSR(sc, R_MEAR, M_MEAR_EESEL); + + eeprom_idle_state(sc); + + for (i = 0; i < EEPROM_SIZE/2; i++) { + temp = eeprom_read_word(sc, i); + dest[2*i] = temp & 0xFF; + dest[2*i+1] = temp >> 8; + } + + WRITECSR(sc, R_MEAR, 0); /* CS hold, Tcsh=0 */ + + cksum = eeprom_checksum(dest);; + if (cksum != EEPROM_WORD(dest, EEPROM_CRC_INDEX)) { + xprintf("%s: Invalid EEPROM CHECKSUM, calc %04x, stored %04x\n", + dp83815_devname(sc), + cksum, EEPROM_WORD(dest, EEPROM_CRC_INDEX)); + return 0/*-1*/; + } + return 0; +} + +static int +eeprom_read_addr(const uint8_t rom[], uint8_t buf[]) +{ + uint16_t s; + unsigned offset, mask; + int i, j; + + if (eeprom_checksum(rom) != EEPROM_WORD(rom, EEPROM_SIZE-2)) + return -1; + + s = 0; + offset = 2*6; mask = 0x1; + i = j = 0; + do { + s >>= 1; + if ((EEPROM_WORD(rom, offset) & mask) != 0) s |= 0x8000; + mask >>= 1; + if (mask == 0) { + offset +=2; mask = 0x8000; + } + i++; + if (i % 16 == 0) { + buf[j++] = s & 0xFF; + buf[j++] = s >> 8; + s = 0; + } + } while (i < ENET_ADDR_LEN*8); + + return 0; +} +#endif /* MACPHYTER_TEST */ + +#if 0 +static void +eeprom_dump(uint8_t srom[]) +{ + int i; + + xprintf("DP83815: EEPROM data:"); + for (i = 0; i < EEPROM_SIZE; i++) { + if (i % 16 == 0) + xprintf("\n %02x: ", i); + xprintf(" %02x", srom[i]); + } + xprintf("\n"); +} +#else +#define eeprom_dump(srom) +#endif + + +static int +dp83815_get_pm_addr(dp83815_softc *sc, uint8_t buf[]) +{ + uint32_t rfcr; + unsigned rfaddr; + unsigned i; + uint32_t rfdata; + + rfcr = READCSR(sc, R_RFCR); + rfaddr = K_RFCR_PMATCH_ADDR; + + for (i = 0; i < ENET_ADDR_LEN/2; i++) { + rfcr &=~ M_RFCR_RFADDR; + rfcr |= V_RFCR_RFADDR(rfaddr); + WRITECSR(sc, R_RFCR, rfcr); + rfdata = READCSR(sc, R_RFDR); + buf[2*i] = rfdata & 0xFF; + buf[2*i+1] = (rfdata >> 8) & 0xFF; + rfaddr += 2; + } + + return 0; +} + + +#if MACPHYTER_TEST +/* MII access */ + +/* Current NICs use the internal PHY, which can be accessed more + simply via internal registers. The following routines are + primarily for management access to an external PHY and are retained + for future applications. They have been tested on a Netgear FA311. */ + +/**************************************************************************** + * MII access utility routines + ***************************************************************************/ + +/* MII clock limited to 2.5 MHz (DP83815 allows 25 MHz), transactions + end with MDIO tristated */ + +static void +mii_write_bits(dp83815_softc *sc, uint32_t data, unsigned int count) +{ + uint32_t ctrl; + uint32_t bitmask; + + ctrl = READCSR(sc, R_MEAR) & ~M_MEAR_MDC; + ctrl |= M_MEAR_MDDIR; + + for (bitmask = 1 << (count-1); bitmask != 0; bitmask >>= 1) { + ctrl &=~ M_MEAR_MDIO; + if ((data & bitmask) != 0) ctrl |= M_MEAR_MDIO; + WRITECSR(sc, R_MEAR, ctrl); + + dp83815_spin(sc, 2000); /* setup */ + WRITECSR(sc, R_MEAR, ctrl | M_MEAR_MDC); + dp83815_spin(sc, 2000); /* hold */ + WRITECSR(sc, R_MEAR, ctrl); + } +} + +static void +mii_turnaround(dp83815_softc *sc) +{ + uint32_t ctrl; + + ctrl = READCSR(sc, R_MEAR) &~ M_MEAR_MDDIR; + + /* stop driving data */ + WRITECSR(sc, R_MEAR, ctrl); + dp83815_spin(sc, 2000); /* setup */ + WRITECSR(sc, R_MEAR, ctrl | M_MEAR_MDC); + dp83815_spin(sc, 2000); /* clock high */ + WRITECSR(sc, R_MEAR, ctrl); + + /* read back and check for 0 here? */ +} + +/**************************************************************************** + * mii_read_register + * + * This routine reads a register from the PHY chip using the MII + * serial management interface. + * + * Input parameters: + * index - index of register to read (0-31) + * + * Return value: + * word read from register + ***************************************************************************/ + +static uint16_t +mii_read_register(dp83815_softc *sc, unsigned int index) +{ + /* Send the command and address to the PHY. The sequence is + a synchronization sequence (32 1 bits) + a "start" command (2 bits) + a "read" command (2 bits) + the PHY addr (5 bits) + the register index (5 bits) + */ + uint32_t ctrl; + uint16_t word; + int i; + + mii_write_bits(sc, 0xFF, 8); + mii_write_bits(sc, 0xFFFFFFFF, 32); + mii_write_bits(sc, MII_COMMAND_START, 2); + mii_write_bits(sc, MII_COMMAND_READ, 2); + mii_write_bits(sc, sc->phy_addr, 5); + mii_write_bits(sc, index, 5); + + mii_turnaround(sc); + + ctrl = READCSR(sc, R_MEAR) &~ (M_MEAR_MDC | M_MEAR_MDDIR); + word = 0; + + for (i = 0; i < 16; i++) { + WRITECSR(sc, R_MEAR, ctrl); + dp83815_spin(sc, 2000); /* clock width low */ + WRITECSR(sc, R_MEAR, ctrl | M_MEAR_MDC); + dp83815_spin(sc, 2000); /* clock width high */ + WRITECSR(sc, R_MEAR, ctrl); + dp83815_spin(sc, 1000); /* output delay */ + word <<= 1; + if ((READCSR(sc, R_MEAR) & M_MEAR_MDIO) != 0) + word |= 0x0001; + } + + return word; + + /* reset to output mode? */ +} + +/**************************************************************************** + * mii_write_register + * + * This routine writes a register in the PHY chip using the MII + * serial management interface. + * + * Input parameters: + * index - index of register to write (0-31) + * value - word to write + ***************************************************************************/ + +static void +mii_write_register(dp83815_softc *sc, unsigned int index, uint16_t value) +{ + mii_write_bits(sc, 0xFF, 8); + mii_write_bits(sc, 0xFFFFFFFF, 32); + mii_write_bits(sc, MII_COMMAND_START, 2); + mii_write_bits(sc, MII_COMMAND_WRITE, 2); + mii_write_bits(sc, sc->phy_addr, 5); + mii_write_bits(sc, index, 5); + mii_write_bits(sc, MII_COMMAND_ACK, 2); + mii_write_bits(sc, value, 16); + + /* reset to input mode? */ +} + + +static int +mii_probe(dp83815_softc *sc) +{ + int i; + uint16_t id1, id2; + + /* Empirically, bit-banged access will return register 0 of the + integrated PHY for all registers of all unpopulated PHY + addresses. */ + for (i = 0; i < 32; i++) { + sc->phy_addr = i; + id1 = mii_read_register(sc, MII_PHYIDR1); + id2 = mii_read_register(sc, MII_PHYIDR2); + if ((id1 != 0x0000 && id1 != 0xFFFF) || + (id2 != 0x0000 && id2 != 0xFFFF)) { + if (id1 != id2) return 0; + } + } + return -1; +} + +#if 0 +#define OUI_NAT_SEMI 0x080017 +#define IDR_NAT_SEMI 0x080017 /* NSC does not bit-reverse each byte */ +#define PART_83815 0x02 + +static void +mii_dump(dp83815_softc *sc, const char *label) +{ + int i; + uint16_t r; + uint32_t idr, part; + + xprintf("%s\n", label); + idr = 0; + for (i = 0; i <= 6; ++i) { + r = mii_read_register(sc, i); + xprintf("MII_REG%02x: %04x\n", i, r); + if (i == MII_PHYIDR1) { + idr |= r << 6; + } + else if (i == MII_PHYIDR2) { + idr |= (r >> 10) & 0x3F; + part = (r >> 4) & 0x3F; + } + } + if (idr == IDR_NAT_SEMI && part == PART_83815) { + r = mii_read_register(sc, 7); + xprintf("MII_REG%02x: %04x\n", i, r); + for (i = 0x10; i <= 0x16; ++i) { + r = mii_read_register(sc, i); + xprintf("MII_REG%02x: %04x\n", i, r); + } + for (i = 0x19; i <= 0x1A; ++i) { + r = mii_read_register(sc, i); + xprintf("MII_REG%02x: %04x\n", i, r); + } + } +} +#else +#define mii_dump(sc,label) +#endif + + +/* The following functions are suitable for explicit MII access. */ + +static void +mii_set_speed(dp83815_softc *sc, int speed) +{ + uint16_t control; + + control = mii_read_register(sc, MII_BMCR); + + control &=~ (BMCR_ANENABLE | BMCR_RESTARTAN); + mii_write_register(sc, MII_BMCR, control); + control &=~ (BMCR_SPEED0 | BMCR_SPEED1 | BMCR_DUPLEX); + + switch (speed) { + case ETHER_SPEED_10HDX: + default: + break; + case ETHER_SPEED_10FDX: + control |= BMCR_DUPLEX; + break; + case ETHER_SPEED_100HDX: + control |= BMCR_SPEED100; + break; + case ETHER_SPEED_100FDX: + control |= BMCR_SPEED100 | BMCR_DUPLEX ; + break; + } + + mii_write_register(sc, MII_BMCR, control); +} + +static void +mii_autonegotiate(dp83815_softc *sc) +{ + uint16_t control, status, cap; + int timeout; + int linkspeed; + + linkspeed = ETHER_SPEED_UNKNOWN; + + /* Read twice to clear latching bits */ + status = mii_read_register(sc, MII_BMSR); + status = mii_read_register(sc, MII_BMSR); + mii_dump(sc, "query PHY"); + + if ((status & (BMSR_AUTONEG | BMSR_LINKSTAT)) == + (BMSR_AUTONEG | BMSR_LINKSTAT)) + control = mii_read_register(sc, MII_BMCR); + else { + /* reset the PHY */ + mii_write_register(sc, MII_BMCR, BMCR_RESET); + timeout = 3*CFE_HZ; + for (;;) { + control = mii_read_register(sc, MII_BMCR); + if ((control && BMCR_RESET) == 0 || timeout <= 0) + break; + cfe_sleep(CFE_HZ/2); + timeout -= CFE_HZ/2; + } + if ((control & BMCR_RESET) != 0) { + xprintf("%s: PHY reset failed\n", dp83815_devname(sc)); + return; + } + + status = mii_read_register(sc, MII_BMSR); + cap = ((status >> 6) & (ANAR_TXFD | ANAR_TXHD | ANAR_10FD | ANAR_10HD)) + | PSB_802_3; + mii_write_register(sc, MII_ANAR, cap); + control |= (BMCR_ANENABLE | BMCR_RESTARTAN); + mii_write_register(sc, MII_BMCR, control); + + timeout = 3*CFE_HZ; + for (;;) { + status = mii_read_register(sc, MII_BMSR); + if ((status & BMSR_ANCOMPLETE) != 0 || timeout <= 0) + break; + cfe_sleep(CFE_HZ/2); + timeout -= CFE_HZ/2; + } + mii_dump(sc, "done PHY"); + } + + xprintf("%s: Link speed: ", dp83815_devname(sc)); + if ((status & BMSR_ANCOMPLETE) != 0) { + /* A link partner was negogiated... */ + + uint16_t remote = mii_read_register(sc, MII_ANLPAR); + + if ((remote & ANLPAR_TXFD) != 0) { + xprintf("100BaseT FDX\n"); + linkspeed = ETHER_SPEED_100FDX; + } + else if ((remote & ANLPAR_TXHD) != 0) { + xprintf("100BaseT HDX\n"); + linkspeed = ETHER_SPEED_100HDX; + } + else if ((remote & ANLPAR_10FD) != 0) { + xprintf("10BaseT FDX\n"); + linkspeed = ETHER_SPEED_10FDX; + } + else if ((remote & ANLPAR_10HD) != 0) { + xprintf("10BaseT HDX\n"); + linkspeed = ETHER_SPEED_10HDX; + } + } + else { + /* no link partner negotiation */ + control &=~ (BMCR_ANENABLE | BMCR_RESTARTAN); + mii_write_register(sc, MII_BMCR, control); + xprintf("10BaseT HDX (assumed)\n"); + linkspeed = ETHER_SPEED_10HDX; + if ((status & BMSR_LINKSTAT) == 0) + mii_write_register(sc, MII_BMCR, control); + mii_set_speed(sc, linkspeed); + } + + status = mii_read_register(sc, MII_BMSR); /* clear latching bits */ + mii_dump(sc, "final PHY"); +} +#endif /* MACPHYTER_TEST */ + + +static void +dp83815_phyupdate(dp83815_softc *sc, uint32_t status) +{ + xprintf("%s: Link speed: ", dp83815_devname(sc)); + if ((status & M_CFG_LNKSTS) != 0) { + switch (status & (M_CFG_SPEED100 | M_CFG_FDUP)) { + case (M_CFG_SPEED100 | M_CFG_FDUP): + sc->linkspeed = ETHER_SPEED_100FDX; + xprintf("100BaseT FDX\n"); + break; + case (M_CFG_SPEED100): + sc->linkspeed = ETHER_SPEED_100HDX; + xprintf("100BaseT HDX\n"); + break; + case (M_CFG_FDUP): + sc->linkspeed = ETHER_SPEED_10FDX; + xprintf("10BaseT FDX\n"); + break; + default: + sc->linkspeed = ETHER_SPEED_10HDX; + xprintf("10BaseT HDX\n"); + break; + } + if ((status & M_CFG_SPEED100) != 0) { + uint32_t t; + + /* This is a reputed fix that improves 100BT rx + performance on short cables with "a small number" + of DP83815 chips. It comes from Bruce at NatSemi + via the Soekris support web page (see appended + note). */ + + WRITECSR(sc, R_PGSEL, 0x0001); + (void)READCSR(sc, R_PGSEL); /* push */ + t = READCSR(sc, R_DSPCFG); + WRITECSR(sc, R_DSPCFG, (t & 0xFFF) | 0x1000); + cfe_sleep(1); + t = READCSR(sc, R_TSTDAT) & 0xFF; + if ((t & 0x0080) == 0 || ((t > 0x00D8) && (t <= 0x00FF))) { + WRITECSR(sc, R_TSTDAT, 0x00E8); + t = READCSR(sc, R_DSPCFG); + WRITECSR(sc, R_DSPCFG, t | 0x0020); + } + WRITECSR(sc, R_PGSEL, 0); + } + if ((status & M_CFG_FDUP) != (sc->phy_status & M_CFG_FDUP)) { + uint32_t txcfg; + + txcfg = READCSR(sc, R_TXCFG); + if (status & M_CFG_FDUP) + txcfg |= M_TXCFG_CSI; + else + txcfg &= ~M_TXCFG_CSI; + WRITECSR(sc, R_RXCFG, txcfg); + } + } + else { + xprintf("Unknown\n"); + } + + sc->phy_status = status; +} + +static void +dp83815_hwinit(dp83815_softc *sc) +{ + if (sc->state == eth_state_uninit) { + uint32_t cfg; + uint32_t txcfg, rxcfg; + uint32_t ready; + int timeout; + + /* RESET_ADAPTER(sc); */ + sc->state = eth_state_off; + sc->bus_errors = 0; + + cfg = READCSR(sc, R_CFG); + cfg |= M_CFG_BEM; /* We will use match bits */ + WRITECSR(sc, R_CFG, cfg); + + sc->phy_status = 0; + dp83815_phyupdate(sc, cfg & M_CFG_LNKSUMMARY); + + txcfg = READCSR(sc, R_TXCFG); + txcfg |= M_TXCFG_ATP; + /* XXX fix up FLTH, DRTH? */ + WRITECSR(sc, R_TXCFG, txcfg); + + rxcfg = READCSR(sc, R_RXCFG); + /* Set an aggressive rx drain threshhold of 16 (2*8) bytes */ + rxcfg &= ~M_RXCFG_DRTH; + rxcfg |= V_RXCFG_DRTH(2); + WRITECSR(sc, R_RXCFG, rxcfg); + +#if MACPHYTER_TEST + { + uint8_t srom[EEPROM_SIZE]; + uint8_t addr[ENET_ADDR_LEN]; + + eeprom_read_all(sc, srom); + eeprom_dump(srom); + xprintf(" checksum %04x\n", eeprom_checksum(srom)); + if (eeprom_read_addr(srom, addr) == 0) + xprintf(" addr: %02x-%02x-%02x-%02x-%02x-%02x\n", + addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]); + + mii_probe(sc); + xprintf("MII address %02x\n", sc->phy_addr); + mii_dump(sc, "DP83815 PHY:"); + (void)mii_autonegotiate; + } +#endif /* MACPHYTER_TEST */ + + /* XXX fix up rx filtering here. We are relying on the EEPROM. */ + + /* XXX This is inappropriate on a restart. */ + timeout = 2*CFE_HZ; + ready = 0; + for (;;) { + ready |= READCSR(sc, R_ISR); + if ((ready & (M_INT_TXRCMP | M_INT_RXRCMP)) + == (M_INT_TXRCMP | M_INT_RXRCMP) || timeout <= 0) + break; + cfe_sleep(CFE_HZ/10); + timeout -= CFE_HZ/10; + } + if ((ready & M_INT_TXRCMP) == 0) + xprintf("%s: tx reset failed\n", dp83815_devname(sc)); + if ((ready & M_INT_RXRCMP) == 0) + xprintf("%s: rx reset failed\n", dp83815_devname(sc)); + } +} + +static void +dp83815_setspeed(dp83815_softc *sc, int speed) +{ + /* XXX Not yet implemented - autonegotiation only. */ +} + +static void +dp83815_setloopback(dp83815_softc *sc, int mode) +{ + /* XXX Not yet implemented. */ +} + + +static void +dp83815_isr(void *arg) +{ + dp83815_softc *sc = (dp83815_softc *)arg; + uint32_t status; + uint32_t isr; + +#if IPOLL + sc->interrupts++; +#endif + + for (;;) { + + /* Read (and clear) the interrupt status. */ + isr = READCSR(sc, R_ISR); + status = isr & sc->intmask; + + /* if there are no more interrupts, leave now. */ + if (status == 0) break; + + /* Now, test each unmasked bit in the interrupt register and + handle each interrupt type appropriately. */ + + if (status & (M_INT_RTABT | M_INT_RMABT)) { + WRITECSR(sc, R_IER, 0); + + xprintf("%s: bus error\n", dp83815_devname(sc)); + dumpstat(sc); + sc->bus_errors++; + if (sc->bus_errors >= 2) { + dumpcsrs(sc); + RESET_ADAPTER(sc); + sc->state = eth_state_off; + sc->bus_errors = 0; + } +#if IPOLL + else + WRITECSR(sc, R_IMR, sc->intmask); +#endif + } + + if (status & M_INT_RXDESC) { +#if IPOLL + sc->rx_interrupts++; +#endif + dp83815_procrxring(sc); + } + + if (status & M_INT_TXDESC) { +#if IPOLL + sc->tx_interrupts++; +#endif + dp83815_proctxring(sc); + } + + if (status & (M_INT_TXURN | M_INT_RXORN)) { + if (status & M_INT_TXURN) { + xprintf("%s: tx underrun, %08x\n", dp83815_devname(sc), isr); + /* XXX Try to restart */ + } + if (status & M_INT_RXORN) { + xprintf("%s: tx overrun, %08x\n", dp83815_devname(sc), isr); + /* XXX Try to restart */ + } + } + + if (status & M_INT_PHY) { + sc->intmask &= ~ M_INT_PHY; + WRITECSR(sc, R_IMR, sc->intmask); + (void)READCSR(sc, R_MISR); /* Clear at PHY */ + sc->phy_check = 1; + } + + } +} + +static void +dp83815_checkphy(dp83815_softc *sc) +{ + uint32_t cfg; + uint32_t status; + + (void)READCSR(sc, R_MISR); /* Clear at PHY */ + cfg = READCSR(sc, R_CFG); + status = cfg & M_CFG_LNKSUMMARY; + if (status != sc->phy_status) { + /* XXX Can we really do the phy update with active rx and tx? */ + dp83815_phyupdate(sc, status); + } + + sc->intmask |= M_INT_PHY; + WRITECSR(sc, R_IMR, sc->intmask); +} + + +static void +dp83815_start(dp83815_softc *sc) +{ + dp83815_hwinit(sc); + + /* Set up loopback here */ + + sc->intmask = 0; + WRITECSR(sc, R_IER, 0); /* no interrupts */ + WRITECSR(sc, R_IMR, 0); + (void)READCSR(sc, R_ISR); /* clear any pending */ + + sc->phy_status = READCSR(sc, R_CFG) & M_CFG_LNKSUMMARY; + sc->phy_check = 0; + + sc->intmask = M_INT_RXDESC | M_INT_TXDESC; + sc->intmask |= M_INT_RTABT | M_INT_RMABT | M_INT_RXORN | M_INT_TXURN; + sc->intmask |= M_INT_PHY; + +#if IPOLL + cfe_request_irq(sc->irq, dp83815_isr, sc, CFE_IRQ_FLAGS_SHARED, 0); + WRITECSR(sc, R_IMR, sc->intmask); + WRITECSR(sc, R_IER, M_IER_IE); +#endif + + (void)READCSR(sc, R_MISR); /* clear any pending */ + WRITECSR(sc, R_MISR, MISR_MSKJAB | MISR_MSKRF | MISR_MSKFHF | MISR_MSKRHF); + WRITECSR(sc, R_MICR, MICR_INTEN); + + WRITECSR(sc, R_TXDP, PTR_TO_PCI(sc->txdscr_start)); + WRITECSR(sc, R_RXDP, PTR_TO_PCI(sc->rxdscr_start)); + + WRITECSR(sc, R_MIBC, M_MIBC_ACLR); /* zero hw MIB counters */ + + WRITECSR(sc, R_CR, M_CR_TXE | M_CR_RXE); + sc->state = eth_state_on; +} + +static void +dp83815_stop(dp83815_softc *sc) +{ + uint32_t status; + int count; + + /* Make sure that no further interrutps will be processed. */ + sc->intmask = 0; + WRITECSR(sc, R_IER, 0); + WRITECSR(sc, R_IMR, 0); + +#if IPOLL + (void)READCSR(sc, R_IER); /* Push */ + cfe_free_irq(sc->irq, 0); +#endif + + WRITECSR(sc, R_CR, M_CR_TXD | M_CR_RXD); + + /* wait for any DMA activity to terminate */ + for (count = 0; count <= 13; count++) { + status = READCSR(sc, R_CR); + if ((status & (M_CR_TXE | M_CR_RXE)) == 0) + break; + cfe_sleep(CFE_HZ/10); + } + if (count > 13) { + xprintf("%s: idle state not achieved\n", dp83815_devname(sc)); + dumpstat(sc); + RESET_ADAPTER(sc); + sc->state = eth_state_uninit; +#if 1 + sc->linkspeed = ETHER_SPEED_AUTO; +#endif + } +#if 0 /* XXX Not yet implemented. */ + else if (sc->loopback != ETHER_LOOPBACK_OFF) { + dp83815_setloopback(sc, ETHER_LOOPBACK_OFF); + } +#endif + + (void)READCSR(sc, R_ISR); /* Clear any stragglers. */ +} + + +/* ********************************************************************* + * ETH_PARSE_XDIGIT(c) + * + * Parse a hex digit, returning its value + * + * Input parameters: + * c - character + * + * Return value: + * hex value, or -1 if invalid + ********************************************************************* */ +static int +eth_parse_xdigit(char c) +{ + int digit; + + if ((c >= '0') && (c <= '9')) digit = c - '0'; + else if ((c >= 'a') && (c <= 'f')) digit = c - 'a' + 10; + else if ((c >= 'A') && (c <= 'F')) digit = c - 'A' + 10; + else digit = -1; + + return digit; +} + +/* ********************************************************************* + * ETH_PARSE_HWADDR(str,hwaddr) + * + * Convert a string in the form xx:xx:xx:xx:xx:xx into a 6-byte + * Ethernet address. + * + * Input parameters: + * str - string + * hwaddr - pointer to hardware address + * + * Return value: + * 0 if ok, else -1 + ********************************************************************* */ +static int +eth_parse_hwaddr(char *str, uint8_t *hwaddr) +{ + int digit1, digit2; + int idx = ENET_ADDR_LEN; + + while (*str && (idx > 0)) { + digit1 = eth_parse_xdigit(*str); + if (digit1 < 0) return -1; + str++; + if (!*str) return -1; + + if ((*str == ':') || (*str == '-')) { + digit2 = digit1; + digit1 = 0; + } + else { + digit2 = eth_parse_xdigit(*str); + if (digit2 < 0) return -1; + str++; + } + + *hwaddr++ = (digit1 << 4) | digit2; + idx--; + + if ((*str == ':') || (*str == '-')) + str++; + } + return 0; +} + +/* ********************************************************************* + * ETH_INCR_HWADDR(hwaddr,incr) + * + * Increment a 6-byte Ethernet hardware address, with carries + * + * Input parameters: + * hwaddr - pointer to hardware address + * incr - desired increment + * + * Return value: + * none + ********************************************************************* */ +static void +eth_incr_hwaddr(uint8_t *hwaddr, unsigned incr) +{ + int idx; + int carry; + + idx = 5; + carry = incr; + while (idx >= 0 && carry != 0) { + unsigned sum = hwaddr[idx] + carry; + + hwaddr[idx] = sum & 0xFF; + carry = sum >> 8; + idx--; + } +} + + +/* ********************************************************************* + * Declarations for CFE Device Driver Interface routines + ********************************************************************* */ + +static int dp83815_ether_open(cfe_devctx_t *ctx); +static int dp83815_ether_read(cfe_devctx_t *ctx,iocb_buffer_t *buffer); +static int dp83815_ether_inpstat(cfe_devctx_t *ctx,iocb_inpstat_t *inpstat); +static int dp83815_ether_write(cfe_devctx_t *ctx,iocb_buffer_t *buffer); +static int dp83815_ether_ioctl(cfe_devctx_t *ctx,iocb_buffer_t *buffer); +static int dp83815_ether_close(cfe_devctx_t *ctx); +static void dp83815_ether_poll(cfe_devctx_t *ctx, int64_t ticks); +static void dp83815_ether_reset(void *softc); + +/* ********************************************************************* + * CFE Device Driver dispatch structure + ********************************************************************* */ + +const static cfe_devdisp_t dp83815_ether_dispatch = { + dp83815_ether_open, + dp83815_ether_read, + dp83815_ether_inpstat, + dp83815_ether_write, + dp83815_ether_ioctl, + dp83815_ether_close, + dp83815_ether_poll, + dp83815_ether_reset +}; + +/* ********************************************************************* + * CFE Device Driver descriptor + ********************************************************************* */ + +const cfe_driver_t dp83815drv = { + "DP83815 Ethernet", + "eth", + CFE_DEV_NETWORK, + &dp83815_ether_dispatch, + dp83815_ether_probe +}; + + +static int +dp83815_ether_attach(cfe_driver_t *drv, + pcitag_t tag, int index, uint8_t hwaddr[]) +{ + dp83815_softc *sc; + uint32_t device; + uint32_t class; + phys_addr_t pa; + uint8_t promaddr[ENET_ADDR_LEN]; + char descr[100]; + uint32_t srr; + + device = pci_conf_read(tag, R_CFGID); + class = pci_conf_read(tag, R_CFGRID); + +#if 1 + /* Use memory space for the CSRs */ + pci_map_mem(tag, R_CFGMA, PCI_MATCH_BITS, &pa); +#else + /* Use i/o space for the CSRs */ + pci_map_io(tag, R_CFGIOA, PCI_MATCH_BITS, &pa); +#endif + + sc = (dp83815_softc *) KMALLOC(sizeof(dp83815_softc), 0); + + if (sc == NULL) { + xprintf("DP83815: No memory to complete probe\n"); + return 0; + } + memset(sc, 0, sizeof(*sc)); + + sc->membase = (uint32_t)pa; + sc->irq = pci_conf_read(tag, R_CFGINT) & 0xFF; + sc->tag = tag; + sc->device = PCI_PRODUCT(device); + sc->revision = PCI_REVISION(class); + sc->devctx = NULL; + +#if 1 + sc->linkspeed = ETHER_SPEED_AUTO; /* select autonegotiation */ +#else + sc->linkspeed = ETHER_SPEED_100FDX; /* 100 Mbps, full duplex */ +#endif + sc->loopback = ETHER_LOOPBACK_OFF; + memcpy(sc->hwaddr, hwaddr, ENET_ADDR_LEN); + + srr = READCSR(sc, R_SRR); +#if 0 + /* The NS data sheet recommends the following for "optimal + performance" of CVNG parts. Tested on a sample of one CVNG + part on an NS "Macphyter Demo II" eval board, it seemed to + produce slightly less reliable initial behavior. */ + if (G_SRR_REV(srr) == K_REV_CVNG) { + /* Update PHY DSP registers per data sheet. */ + WRITECSR(sc, R_PGSEL, 0x0001); + (void)READCSR(sc, R_PGSEL); /* push */ + WRITECSR(sc, R_PMDCSR, 0x189C); + WRITECSR(sc, R_TSTDAT, 0x0000); + WRITECSR(sc, R_DSPCFG, 0x5040); + WRITECSR(sc, R_SDCFG, 0x008C); + } +#endif + + dp83815_init(sc); + + /* Prefer the address in EEPROM. This will be read into the + PMATCH register upon power up. Unfortunately, how to test for + completion of the auto-load (but see PTSCR_EELOAD_EN). */ + if (dp83815_get_pm_addr(sc, promaddr) == 0) { + memcpy(sc->hwaddr, promaddr, ENET_ADDR_LEN); + } + + sc->state = eth_state_uninit; + + xsprintf(descr, "%s at 0x%X (%02x-%02x-%02x-%02x-%02x-%02x)", + drv->drv_description, sc->membase, + sc->hwaddr[0], sc->hwaddr[1], sc->hwaddr[2], + sc->hwaddr[3], sc->hwaddr[4], sc->hwaddr[5]); + + cfe_attach(drv, sc, NULL, descr); + return 1; +} + + +/* ********************************************************************* + * DP83815_ETHER_PROBE(drv,probe_a,probe_b,probe_ptr) + * + * Probe and install drivers for all dp83815 Ethernet controllers. + * For each, create a context structure and attach to the + * specified network device. + * + * Input parameters: + * drv - driver descriptor + * probe_a - not used + * probe_b - not used + * probe_ptr - string pointer to hardware address for the first + * MAC, in the form xx:xx:xx:xx:xx:xx + * + * Return value: + * nothing + ********************************************************************* */ +static void +dp83815_ether_probe(cfe_driver_t *drv, + unsigned long probe_a, unsigned long probe_b, + void *probe_ptr) +{ + int n; + uint8_t hwaddr[ENET_ADDR_LEN]; + + if (probe_ptr) + eth_parse_hwaddr((char *) probe_ptr, hwaddr); + else { + /* use default address 02-00-00-10-0B-00 */ + hwaddr[0] = 0x02; hwaddr[1] = 0x00; hwaddr[2] = 0x00; + hwaddr[3] = 0x10; hwaddr[4] = 0x0B; hwaddr[5] = 0x00; + } + + n = 0; + for (;;) { + pcitag_t tag; + + if (pci_find_device(K_PCI_VENDOR_NSC, K_PCI_ID_DP83815, n, &tag) != 0) + break; + dp83815_ether_attach(drv, tag, n, hwaddr); + n++; + eth_incr_hwaddr(hwaddr, 1); + } +} + + +/* The functions below are called via the dispatch vector for the 83815. */ + +/* ********************************************************************* + * DP83815_ETHER_OPEN(ctx) + * + * Open the Ethernet device. The MAC is reset, initialized, and + * prepared to receive and send packets. + * + * Input parameters: + * ctx - device context (includes ptr to our softc) + * + * Return value: + * status, 0 = ok + ********************************************************************* */ +static int +dp83815_ether_open(cfe_devctx_t *ctx) +{ + dp83815_softc *sc = ctx->dev_softc; + + if (sc->state == eth_state_on) + dp83815_stop(sc); + + sc->devctx = ctx; + + sc->inpkts = sc->outpkts = 0; + sc->interrupts = 0; + sc->rx_interrupts = sc->tx_interrupts = 0; + + dp83815_start(sc); + +#if XPOLL + dp83815_isr(sc); +#endif + + return 0; +} + +/* ********************************************************************* + * DP83815_ETHER_READ(ctx,buffer) + * + * Read a packet from the Ethernet device. If no packets are + * available, the read will succeed but return 0 bytes. + * + * Input parameters: + * ctx - device context (includes ptr to our softc) + * buffer - pointer to buffer descriptor. + * + * Return value: + * status, 0 = ok + ********************************************************************* */ +static int +dp83815_ether_read(cfe_devctx_t *ctx, iocb_buffer_t *buffer) +{ + dp83815_softc *sc = ctx->dev_softc; + eth_pkt_t *pkt; + int blen; + +#if XPOLL + dp83815_isr(sc); +#endif + + if (sc->state != eth_state_on) return -1; + + CS_ENTER(sc); + pkt = (eth_pkt_t *) q_deqnext(&(sc->rxqueue)); + CS_EXIT(sc); + + if (pkt == NULL) { + buffer->buf_retlen = 0; + return 0; + } + + blen = buffer->buf_length; + if (blen > pkt->length) blen = pkt->length; + + blockcopy(buffer->buf_ptr, pkt->buffer, blen); + buffer->buf_retlen = blen; + + eth_free_pkt(sc, pkt); + dp83815_fillrxring(sc); + +#if XPOLL + dp83815_isr(sc); +#endif + + return 0; +} + +/* ********************************************************************* + * DP83815_ETHER_INPSTAT(ctx,inpstat) + * + * Check for received packets on the Ethernet device + * + * Input parameters: + * ctx - device context (includes ptr to our softc) + * inpstat - pointer to input status structure + * + * Return value: + * status, 0 = ok + ********************************************************************* */ +static int +dp83815_ether_inpstat(cfe_devctx_t *ctx, iocb_inpstat_t *inpstat) +{ + dp83815_softc *sc = ctx->dev_softc; + +#if XPOLL + dp83815_isr(sc); +#endif + + if (sc->state != eth_state_on) return -1; + + /* We avoid an interlock here because the result is a hint and an + interrupt cannot turn a non-empty queue into an empty one. */ + inpstat->inp_status = (q_isempty(&(sc->rxqueue))) ? 0 : 1; + + return 0; +} + +/* ********************************************************************* + * DP83815_ETHER_WRITE(ctx,buffer) + * + * Write a packet to the Ethernet device. + * + * Input parameters: + * ctx - device context (includes ptr to our softc) + * buffer - pointer to buffer descriptor. + * + * Return value: + * status, 0 = ok + ********************************************************************* */ +static int +dp83815_ether_write(cfe_devctx_t *ctx, iocb_buffer_t *buffer) +{ + dp83815_softc *sc = ctx->dev_softc; + eth_pkt_t *pkt; + int blen; + +#if XPOLL + dp83815_isr(sc); +#endif + + if (sc->state != eth_state_on) return -1; + + pkt = eth_alloc_pkt(sc); + if (!pkt) return CFE_ERR_NOMEM; + + blen = buffer->buf_length; + if (blen > pkt->length) blen = pkt->length; + + blockcopy(pkt->buffer, buffer->buf_ptr, blen); + pkt->length = blen; + + if (dp83815_transmit(sc, pkt) != 0) { + eth_free_pkt(sc,pkt); + return CFE_ERR_IOERR; + } + +#if XPOLL + dp83815_isr(sc); +#endif + + return 0; +} + +/* ********************************************************************* + * DP83815_ETHER_IOCTL(ctx,buffer) + * + * Do device-specific I/O control operations for the device + * + * Input parameters: + * ctx - device context (includes ptr to our softc) + * buffer - pointer to buffer descriptor. + * + * Return value: + * status, 0 = ok + ********************************************************************* */ +static int +dp83815_ether_ioctl(cfe_devctx_t *ctx, iocb_buffer_t *buffer) +{ + dp83815_softc *sc = ctx->dev_softc; + int *argp; + int mode; + int speed; + + switch ((int)buffer->buf_ioctlcmd) { + case IOCTL_ETHER_GETHWADDR: + memcpy(buffer->buf_ptr, sc->hwaddr, sizeof(sc->hwaddr)); + return 0; + + case IOCTL_ETHER_SETHWADDR: + return -1; /* not supported */ + + case IOCTL_ETHER_GETSPEED: + argp = (int *) buffer->buf_ptr; + *argp = sc->linkspeed; + return 0; + + case IOCTL_ETHER_SETSPEED: + dp83815_stop(sc); + dp83815_resetrings(sc); + speed = *((int *) buffer->buf_ptr); + dp83815_setspeed(sc, speed); + dp83815_start(sc); + sc->state = eth_state_on; + return 0; + + case IOCTL_ETHER_GETLINK: + argp = (int *) buffer->buf_ptr; + *argp = sc->linkspeed; + return 0; + + case IOCTL_ETHER_GETLOOPBACK: + *((int *) buffer) = sc->loopback; + return 0; + + case IOCTL_ETHER_SETLOOPBACK: + dp83815_stop(sc); + dp83815_resetrings(sc); + mode = *((int *) buffer->buf_ptr); + sc->loopback = ETHER_LOOPBACK_OFF; /* default */ + if (mode == ETHER_LOOPBACK_INT || mode == ETHER_LOOPBACK_EXT) { + dp83815_setloopback(sc, mode); + } + dp83815_start(sc); + sc->state = eth_state_on; + return 0; + + default: + return -1; + } +} + +/* ********************************************************************* + * DP83815_ETHER_CLOSE(ctx) + * + * Close the Ethernet device. + * + * Input parameters: + * ctx - device context (includes ptr to our softc) + * + * Return value: + * status, 0 = ok + ********************************************************************* */ +static int +dp83815_ether_close(cfe_devctx_t *ctx) +{ + dp83815_softc *sc = ctx->dev_softc; + + sc->state = eth_state_off; + dp83815_stop(sc); + + /* resynchronize descriptor rings */ + dp83815_resetrings(sc); + + xprintf("%s: %d sent, %d received, %d interrupts\n", + dp83815_devname(sc), sc->outpkts, sc->inpkts, sc->interrupts); + xprintf(" %d rx interrupts, %d tx interrupts\n", + sc->rx_interrupts, sc->tx_interrupts); + + sc->devctx = NULL; + return 0; +} + + +/* ********************************************************************* + * DP83815_ETHER_POLL(ctx,ticks) + * + * TBD + * + * Input parameters: + * ctx - device context (includes ptr to our softc) + * ticks- current time in ticks + * + * Return value: + * nothing + ********************************************************************* */ + +static void +dp83815_ether_poll(cfe_devctx_t *ctx, int64_t ticks) +{ + dp83815_softc *sc = ctx->dev_softc; + + if (sc->phy_check) { + sc->phy_check = 0; + dp83815_checkphy(sc); + } +} + + +/* ********************************************************************* + * DP83815_ETHER_RESET(softc) + * + * This routine is called when CFE is restarted after a + * program exits. We can clean up pending I/Os here. + * + * Input parameters: + * softc - pointer to dp83815_softc + * + * Return value: + * nothing + ********************************************************************* */ + +static void +dp83815_ether_reset(void *softc) +{ + dp83815_softc *sc = (dp83815_softc *)softc; + + /* Turn off the Ethernet interface. */ + + /* RESET_ADAPTER(sc); */ + + sc->state = eth_state_uninit; +} |