Mercurial > ecos-v3_0-branch
diff packages/devs/eth/powerpc/quicc/current/src/if_quicc.c @ 82:6736c52df507 ecos-sw-2000-04-14
Merge from eCos master repository on 2000-04-14-13:35:46-BST
| author | jlarmour |
|---|---|
| date | Tue, 18 Apr 2000 21:51:55 +0000 |
| parents | |
| children | 6ed91473a1cd |
line wrap: on
line diff
new file mode 100644 --- /dev/null +++ b/packages/devs/eth/powerpc/quicc/current/src/if_quicc.c @@ -0,0 +1,516 @@ +//========================================================================== +// +// dev/if_quicc.c +// +// Ethernet device driver for PowerPC QUICC (MPC8xx) boards +// +//========================================================================== +//####COPYRIGHTBEGIN#### +// +// ------------------------------------------- +// The contents of this file are subject to the Red Hat eCos Public License +// Version 1.1 (the "License"); you may not use this file except in +// compliance with the License. You may obtain a copy of the License at +// http://www.redhat.com/ +// +// Software distributed under the License is distributed on an "AS IS" +// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +// License for the specific language governing rights and limitations under +// the License. +// +// The Original Code is eCos - Embedded Configurable Operating System, +// released September 30, 1998. +// +// The Initial Developer of the Original Code is Red Hat. +// Portions created by Red Hat are +// Copyright (C) 1998, 1999, 2000 Red Hat, Inc. +// All Rights Reserved. +// ------------------------------------------- +// +//####COPYRIGHTEND#### +//####BSDCOPYRIGHTBEGIN#### +// +// ------------------------------------------- +// +// Portions of this software may have been derived from OpenBSD or other sources, +// and are covered by the appropriate copyright disclaimers included herein. +// +// ------------------------------------------- +// +//####BSDCOPYRIGHTEND#### +//========================================================================== +//#####DESCRIPTIONBEGIN#### +// +// Author(s): gthomas +// Contributors: gthomas +// Date: 2000-01-10 +// Purpose: +// Description: hardware driver for MPC8xx QUICC +// +// +//####DESCRIPTIONEND#### +// +//========================================================================== + +// Ethernet device driver for MPC8xx QUICC + +#include <pkgconf/net.h> +#include <pkgconf/devs_eth_powerpc_quicc.h> + +#include <cyg/infra/cyg_type.h> +#include <cyg/hal/hal_arch.h> +#include <cyg/infra/diag.h> +#include <cyg/hal/drv_api.h> +#include <cyg/hal/hal_cache.h> + +#include <netdev.h> +#include <eth_drv.h> + +#include "quicc_eth.h" + +static unsigned char quicc_eth_rxbufs[CYGNUM_DEVS_ETH_POWERPC_QUICC_RxNUM] + [CYGNUM_DEVS_ETH_POWERPC_QUICC_BUFSIZE]; +static unsigned char quicc_eth_txbufs[CYGNUM_DEVS_ETH_POWERPC_QUICC_TxNUM] + [CYGNUM_DEVS_ETH_POWERPC_QUICC_BUFSIZE]; + +static struct quicc_eth_info quicc_eth0_info; +static unsigned char enaddr[] = { 0x08, 0x00, 0x3E, 0x28, 0x79, 0xB8}; + +ETH_DRV_SC(quicc_eth0_sc, + &quicc_eth0_info, // Driver specific data + "eth0", // Name for this interface + quicc_eth_start, + quicc_eth_stop, + quicc_eth_control, + quicc_eth_can_send, + quicc_eth_send, + quicc_eth_recv); + +NETDEVTAB_ENTRY(quicc_netdev, + "quicc_eth", + quicc_eth_init, + &quicc_eth0_sc); + +static cyg_interrupt quicc_eth_interrupt; +static cyg_handle_t quicc_eth_interrupt_handle; +static void quicc_eth_int(struct eth_drv_sc *data); + +extern int _mbx_fetch_VPD(int, void *, int); + +// This ISR is called when the ethernet interrupt occurs +static int +quicc_eth_isr(cyg_vector_t vector, cyg_addrword_t data, HAL_SavedRegisters *regs) +{ + cyg_drv_interrupt_mask(CYGNUM_HAL_INTERRUPT_CPM_SCC1); + return (CYG_ISR_HANDLED|CYG_ISR_CALL_DSR); // Run the DSR +} + +// This DSR handles the ethernet [logical] processing +static void +quicc_eth_dsr(cyg_vector_t vector, cyg_ucount32 count, cyg_addrword_t data) +{ + quicc_eth_int((struct eth_drv_sc *)data); + // Allow interrupts to happen again + cyg_drv_interrupt_acknowledge(CYGNUM_HAL_INTERRUPT_CPM_SCC1); + cyg_drv_interrupt_unmask(CYGNUM_HAL_INTERRUPT_CPM_SCC1); +} + +// +// Initialize the interface - performed at system startup +// This function must set up the interface, including arranging to +// handle interrupts, etc, so that it may be "started" cheaply later. +// +static bool +quicc_eth_init(struct cyg_netdevtab_entry *tab) +{ + struct eth_drv_sc *sc = (struct eth_drv_sc *)tab->device_instance; + struct quicc_eth_info *qi = (struct quicc_eth_info *)sc->driver_private; + volatile EPPC *eppc = (volatile EPPC *)eppc_base(); + struct cp_bufdesc *rxbd, *txbd; + unsigned char *RxBUF, *TxBUF, *ep, *ap; + volatile struct ethernet_pram *enet_pram; + volatile struct scc_regs *scc; + int TxBD, RxBD; + int cache_state; + int i; + + // Fetch the board address from the VPD +#define VPD_ETHERNET_ADDRESS 0x08 + _mbx_fetch_VPD(VPD_ETHERNET_ADDRESS, enaddr, sizeof(enaddr)); + + // Ensure consistent state between cache and what the QUICC sees + HAL_DCACHE_IS_ENABLED(cache_state); + HAL_DCACHE_SYNC(); + HAL_DCACHE_DISABLE(); + + // Set up to handle interrupts + cyg_drv_interrupt_create(CYGNUM_HAL_INTERRUPT_CPM_SCC1, + CYGARC_SIU_PRIORITY_HIGH, + (cyg_addrword_t)sc, // Data item passed to interrupt handler + (cyg_ISR_t *)quicc_eth_isr, + (cyg_DSR_t *)quicc_eth_dsr, + &quicc_eth_interrupt_handle, + &quicc_eth_interrupt); + cyg_drv_interrupt_attach(quicc_eth_interrupt_handle); + cyg_drv_interrupt_acknowledge(CYGNUM_HAL_INTERRUPT_CPM_SCC1); + cyg_drv_interrupt_unmask(CYGNUM_HAL_INTERRUPT_CPM_SCC1); + + qi->pram = enet_pram = &eppc->pram[0].enet_scc; + qi->ctl = scc = &eppc->scc_regs[0]; // Use SCC1 + + // Shut down ethernet, in case it is already running + scc->scc_gsmr_l &= ~(QUICC_SCC_GSML_ENR | QUICC_SCC_GSML_ENT); + + bzero((void *)enet_pram, sizeof(*enet_pram)); + + TxBD = 0x2C00; // FIXME + RxBD = TxBD + CYGNUM_DEVS_ETH_POWERPC_QUICC_TxNUM * sizeof(struct cp_bufdesc); + + txbd = (struct cp_bufdesc *)((char *)eppc + TxBD); + rxbd = (struct cp_bufdesc *)((char *)eppc + RxBD); + qi->tbase = txbd; + qi->txbd = txbd; + qi->tnext = txbd; + qi->rbase = rxbd; + qi->rxbd = rxbd; + qi->rnext = rxbd; + + RxBUF = &quicc_eth_rxbufs[0][0]; + TxBUF = &quicc_eth_txbufs[0][0]; + + // setup buffer descriptors + for (i = 0; i < CYGNUM_DEVS_ETH_POWERPC_QUICC_RxNUM; i++) { + rxbd->length = 0; + rxbd->buffer = RxBUF; + rxbd->ctrl = QUICC_BD_CTL_Ready | QUICC_BD_CTL_Int; + RxBUF += CYGNUM_DEVS_ETH_POWERPC_QUICC_BUFSIZE; + rxbd++; + } + rxbd--; + rxbd->ctrl |= QUICC_BD_CTL_Wrap; // Last buffer + for (i = 0; i < CYGNUM_DEVS_ETH_POWERPC_QUICC_TxNUM; i++) { + txbd->length = 0; + txbd->buffer = TxBUF; + txbd->ctrl = 0; + TxBUF += CYGNUM_DEVS_ETH_POWERPC_QUICC_BUFSIZE; + txbd++; + } + txbd--; + txbd->ctrl |= QUICC_BD_CTL_Wrap; // Last buffer + + // Set up parallel ports for connection to MC68160 ethernet tranceiver + eppc->pio_papar |= (QUICC_MBX_PA_RXD | QUICC_MBX_PA_TXD); + eppc->pio_padir &= ~(QUICC_MBX_PA_RXD | QUICC_MBX_PA_TXD); + eppc->pio_paodr &= ~QUICC_MBX_PA_TXD; + + eppc->pio_pcpar &= ~(QUICC_MBX_PC_COLLISION | QUICC_MBX_PC_Rx_ENABLE); + eppc->pio_pcdir &= ~(QUICC_MBX_PC_COLLISION | QUICC_MBX_PC_Rx_ENABLE); + eppc->pio_pcso |= (QUICC_MBX_PC_COLLISION | QUICC_MBX_PC_Rx_ENABLE); + + eppc->pio_papar |= (QUICC_MBX_PA_Tx_CLOCK | QUICC_MBX_PA_Rx_CLOCK); + eppc->pio_padir &= ~(QUICC_MBX_PA_Tx_CLOCK | QUICC_MBX_PA_Rx_CLOCK); + + // Set up clock routing + eppc->si_sicr &= ~QUICC_MBX_SICR_MASK; + eppc->si_sicr |= QUICC_MBX_SICR_ENET; + eppc->si_sicr &= ~QUICC_MBX_SICR_SCC1_ENABLE; + + // Set up DMA mode + eppc->dma_sdcr = 0x0001; + + // Initialize shared PRAM + enet_pram->rbase = RxBD; + enet_pram->tbase = TxBD; + + // Set Big Endian mode + enet_pram->rfcr = QUICC_SCC_FCR_BE; + enet_pram->tfcr = QUICC_SCC_FCR_BE; + + // Size of receive buffers + enet_pram->mrblr = CYGNUM_DEVS_ETH_POWERPC_QUICC_BUFSIZE; + + // Initialize CRC calculations + enet_pram->c_pres = 0xFFFFFFFF; + enet_pram->c_mask = 0xDEBB20E3; // Actual CRC formula + enet_pram->crcec = 0; + enet_pram->alec = 0; + enet_pram->disfc = 0; + + // Frame padding + enet_pram->pads = 0x8888; + enet_pram->pads = 0x0000; + + // Retries + enet_pram->ret_lim = 15; + enet_pram->ret_cnt = 0; + + // Frame sizes + enet_pram->mflr = IEEE_8023_MAX_FRAME; + enet_pram->minflr = IEEE_8023_MIN_FRAME; + enet_pram->maxd1 = CYGNUM_DEVS_ETH_POWERPC_QUICC_BUFSIZE; + enet_pram->maxd2 = CYGNUM_DEVS_ETH_POWERPC_QUICC_BUFSIZE; + + // Group address hash + enet_pram->gaddr1 = 0; + enet_pram->gaddr2 = 0; + enet_pram->gaddr3 = 0; + enet_pram->gaddr4 = 0; + + // Device physical address + ep = &enaddr[sizeof(enaddr)]; + ap = (unsigned char *)&enet_pram->paddr_h; + for (i = 0; i < sizeof(enaddr); i++) { + *ap++ = *--ep; + } + + // Persistence counter + enet_pram->p_per = 0; + + // Individual address filter + enet_pram->iaddr1 = 0; + enet_pram->iaddr2 = 0; + enet_pram->iaddr3 = 0; + enet_pram->iaddr4 = 0; + + // Temp address + enet_pram->taddr_h = 0; + enet_pram->taddr_m = 0; + enet_pram->taddr_l = 0; + + // Initialize the CPM (set up buffer pointers, etc). + eppc->cp_cr = QUICC_CPM_SCC1 | QUICC_CPM_CR_INIT_TXRX | QUICC_CPM_CR_BUSY; + while (eppc->cp_cr & QUICC_CPM_CR_BUSY) ; + + // Clear any pending interrupt/exceptions + scc->scc_scce = 0xFFFF; + + // Enable interrupts + scc->scc_sccm = QUICC_SCCE_INTS; + + // Set up SCC1 to run in ethernet mode + scc->scc_gsmr_h = 0; + scc->scc_gsmr_l = QUICC_SCC_GSML_TCI | QUICC_SCC_GSML_TPL_48 | + QUICC_SCC_GSML_TPP_01 | QUICC_SCC_GSML_MODE_ENET; + + // Sync delimiters + scc->scc_dsr = 0xD555; + + // Protocol specifics (as if GSML wasn't enough) + scc->scc_psmr = QUICC_PMSR_ENET_CRC | QUICC_PMSR_SEARCH_AFTER_22 | + QUICC_PMSR_RCV_SHORT_FRAMES; + + // Configure board interface + *MBX_CTL1 = MBX_CTL1_ETEN | MBX_CTL1_TPEN; // Enable ethernet, TP mode + + // Enable ethernet interface + eppc->pio_pcpar |= QUICC_MBX_PC_Tx_ENABLE; + eppc->pio_pcdir &= ~QUICC_MBX_PC_Tx_ENABLE; + + if (cache_state) + HAL_DCACHE_ENABLE(); + + // Initialize upper level driver + eth_drv_init(sc, (unsigned char *)&enaddr); + + return true; +} + +// +// This function is called to shut down the interface. +// +static void +quicc_eth_stop(struct eth_drv_sc *sc) +{ + struct quicc_eth_info *qi = (struct quicc_eth_info *)sc->driver_private; + volatile struct scc_regs *scc = qi->ctl; + // Disable the device! + scc->scc_gsmr_l &= ~(QUICC_SCC_GSML_ENR | QUICC_SCC_GSML_ENT); +} + +// +// This function is called to "start up" the interface. It may be called +// multiple times, even when the hardware is already running. It will be +// called whenever something "hardware oriented" changes and should leave +// the hardware ready to send/receive packets. +// +static void +quicc_eth_start(struct eth_drv_sc *sc, unsigned char *enaddr, int flags) +{ + struct quicc_eth_info *qi = (struct quicc_eth_info *)sc->driver_private; + volatile struct scc_regs *scc = qi->ctl; + // Enable the device! + scc->scc_gsmr_l |= QUICC_SCC_GSML_ENR | QUICC_SCC_GSML_ENT; +} + +// +// This function is called for low level "control" operations +// +static int +quicc_eth_control(struct eth_drv_sc *sc, unsigned long key, + void *data, int length) +{ + switch (key) { + case ETH_DRV_SET_MAC_ADDRESS: + return 0; + break; + default: + return 1; + break; + } +} + +// +// This function is called to see if another packet can be sent. +// It should return the number of packets which can be handled. +// Zero should be returned if the interface is busy and can not send any more. +// +static int +quicc_eth_can_send(struct eth_drv_sc *sc) +{ + struct quicc_eth_info *qi = (struct quicc_eth_info *)sc->driver_private; + volatile struct cp_bufdesc *txbd = qi->txbd; + return ((txbd->ctrl & QUICC_BD_CTL_Ready) == 0); +} + +// +// This routine is called to send data to the hardware. +static void +quicc_eth_send(struct eth_drv_sc *sc, struct eth_drv_sg *sg_list, int sg_len, + int total_len, unsigned long key) +{ + struct quicc_eth_info *qi = (struct quicc_eth_info *)sc->driver_private; + volatile struct cp_bufdesc *txbd, *txfirst; + volatile char *bp; + int i, txindex, cache_state; + // Find a free buffer + txbd = txfirst = qi->txbd; + while (txbd->ctrl & QUICC_BD_CTL_Ready) { + // This buffer is busy, move to next one + if (txbd->ctrl & QUICC_BD_CTL_Wrap) { + txbd = qi->tbase; + } else { + txbd++; + } + if (txbd == txfirst) { + panic ("No free xmit buffers"); + } + } + // Remember the next buffer to try + if (txbd->ctrl & QUICC_BD_CTL_Wrap) { + qi->txbd = qi->tbase; + } else { + qi->txbd = txbd+1; + } + txindex = ((unsigned long)txbd - (unsigned long)qi->tbase) / sizeof(*txbd); + qi->txkey[txindex] = key; + // Set up buffer + txbd->length = total_len; + bp = txbd->buffer; + for (i = 0; i < sg_len; i++) { + bcopy((void *)sg_list[i].buf, (void *)bp, sg_list[i].len); + bp += sg_list[i].len; + } + // Note: the MBX860 does not seem to snoop/invalidate the data cache properly! + HAL_DCACHE_IS_ENABLED(cache_state); + if (cache_state) { + HAL_DCACHE_INVALIDATE(txbd->buffer, txbd->length); // Make sure no stale data + } + // Send it on it's way + txbd->ctrl |= QUICC_BD_CTL_Ready | QUICC_BD_CTL_Int | + QUICC_BD_TX_PAD | QUICC_BD_TX_LAST | QUICC_BD_TX_TC; +} + +// +// This function is called when a packet has been received. It's job is +// to prepare to unload the packet from the hardware. Once the length of +// the packet is known, the upper layer of the driver can be told. When +// the upper layer is ready to unload the packet, the internal function +// 'quicc_eth_recv' will be called to actually fetch it from the hardware. +// +static void +quicc_eth_RxEvent(struct eth_drv_sc *sc) +{ + struct quicc_eth_info *qi = (struct quicc_eth_info *)sc->driver_private; + volatile struct cp_bufdesc *rxbd; + rxbd = qi->rnext; + while ((rxbd->ctrl & (QUICC_BD_CTL_Ready | QUICC_BD_CTL_Int)) == QUICC_BD_CTL_Int) { + qi->rxbd = rxbd; // Save for callback + eth_drv_recv(sc, rxbd->length); + rxbd->ctrl |= QUICC_BD_CTL_Ready; + if (rxbd->ctrl & QUICC_BD_CTL_Wrap) { + rxbd = qi->rbase; + } else { + rxbd++; + } + } + // Remember where we left off + qi->rnext = (struct cp_bufdesc *)rxbd; +} + +// +// This function is called as a result of the "eth_drv_recv()" call above. +// It's job is to actually fetch data for a packet from the hardware once +// memory buffers have been allocated for the packet. Note that the buffers +// may come in pieces, using a scatter-gather list. This allows for more +// efficient processing in the upper layers of the stack. +// +static void +quicc_eth_recv(struct eth_drv_sc *sc, struct eth_drv_sg *sg_list, int sg_len) +{ + struct quicc_eth_info *qi = (struct quicc_eth_info *)sc->driver_private; + unsigned char *bp; + int i, cache_state; + bp = (unsigned char *)qi->rxbd->buffer; + // Note: the MBX860 does not seem to snoop/invalidate the data cache properly! + HAL_DCACHE_IS_ENABLED(cache_state); + if (cache_state) { + HAL_DCACHE_INVALIDATE(qi->rxbd->buffer, qi->rxbd->length); // Make sure no stale data + } + for (i = 0; i < sg_len; i++) { + if (sg_list[i].buf != 0) { + bcopy(bp, (void *)sg_list[i].buf, sg_list[i].len); + bp += sg_list[i].len; + } + } +} + +static void +quicc_eth_TxEvent(struct eth_drv_sc *sc, int stat) +{ + struct quicc_eth_info *qi = (struct quicc_eth_info *)sc->driver_private; + volatile struct cp_bufdesc *txbd; + int txindex; + txbd = qi->tnext; + while ((txbd->ctrl & (QUICC_BD_CTL_Ready | QUICC_BD_CTL_Int)) == QUICC_BD_CTL_Int) { + txindex = ((unsigned long)txbd - (unsigned long)qi->tbase) / sizeof(*txbd); + txbd->ctrl &= ~QUICC_BD_CTL_Int; // Reset int pending bit + eth_drv_tx_done(sc, qi->txkey[txindex], 0); + if (txbd->ctrl & QUICC_BD_CTL_Wrap) { + txbd = qi->tbase; + } else { + txbd++; + } + } + // Remember where we left off + qi->tnext = (struct cp_bufdesc *)txbd; +} + +// +// Interrupt processing +// +static void +quicc_eth_int(struct eth_drv_sc *sc) +{ + struct quicc_eth_info *qi = (struct quicc_eth_info *)sc->driver_private; + volatile struct scc_regs *scc = qi->ctl; + unsigned short scce; + while ((scce = (scc->scc_scce & QUICC_SCCE_INTS)) != 0) { + if ((scce & (QUICC_SCCE_TXE | QUICC_SCCE_TX)) != 0) { + quicc_eth_TxEvent(sc, scce); + } + if ((scce & QUICC_SCCE_RXF) != 0) { + quicc_eth_RxEvent(sc); + } + scc->scc_scce = scce; // Reset the bits we handled + } +}
