Mercurial > ecos
view packages/devs/eth/powerpc/fec/current/src/if_fec.c @ 159:511f4dc167f6
Merge from eCos master repository on 2001-05-18-01:30:05-BST
| author | jlarmour |
|---|---|
| date | Fri, 18 May 2001 06:32:28 +0000 |
| parents | |
| children | d63db767121d |
line wrap: on
line source
//========================================================================== // // dev/if_fec.c // // Fast ethernet device driver for PowerPC MPC8xxT 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, 2001 Red Hat, Inc. // All Rights Reserved. // ------------------------------------------- // //####COPYRIGHTEND#### //========================================================================== //#####DESCRIPTIONBEGIN#### // // Author(s): gthomas // Contributors: gthomas // Date: 2001-01-21 // Purpose: // Description: hardware driver for MPC8xxT FEC // // //####DESCRIPTIONEND#### // //========================================================================== // Ethernet device driver for MPC8xx FEC #include <pkgconf/system.h> #include <pkgconf/devs_eth_powerpc_fec.h> #ifdef CYGPKG_NET #include <pkgconf/net.h> #endif #include <cyg/infra/cyg_type.h> #include <cyg/infra/diag.h> #include <cyg/hal/hal_arch.h> #include <cyg/hal/hal_cache.h> #include <cyg/hal/hal_intr.h> #include <cyg/hal/drv_api.h> #include <cyg/hal/hal_if.h> #include <netdev.h> #include <eth_drv.h> #include "fec.h" static unsigned char fec_eth_rxbufs[CYGNUM_DEVS_ETH_POWERPC_FEC_RxNUM+1] [CYGNUM_DEVS_ETH_POWERPC_FEC_BUFSIZE]; static struct fec_bd fec_eth_rxring[CYGNUM_DEVS_ETH_POWERPC_FEC_RxNUM]; static unsigned char fec_eth_txbufs[CYGNUM_DEVS_ETH_POWERPC_FEC_TxNUM+1] [CYGNUM_DEVS_ETH_POWERPC_FEC_BUFSIZE]; static struct fec_bd fec_eth_txring[CYGNUM_DEVS_ETH_POWERPC_FEC_TxNUM]; static struct fec_eth_info fec_eth0_info; static unsigned char _default_enaddr[] = { 0x08, 0x00, 0x3E, 0x28, 0x7A, 0xBA}; static unsigned char enaddr[6]; #ifdef CYGPKG_REDBOOT #include <pkgconf/redboot.h> #ifdef CYGSEM_REDBOOT_FLASH_CONFIG #include <redboot.h> #include <flash_config.h> RedBoot_config_option("Network hardware address [MAC]", fec_esa, ALWAYS_ENABLED, true, CONFIG_ESA, 0 ); #endif #endif #ifdef CYGPKG_REDBOOT #define os_printf printf #else #define os_printf diag_printf #endif // For fetching the ESA from RedBoot #include <cyg/hal/hal_if.h> #ifndef CONFIG_ESA #define CONFIG_ESA 6 #endif ETH_DRV_SC(fec_eth0_sc, &fec_eth0_info, // Driver specific data "eth0", // Name for this interface fec_eth_start, fec_eth_stop, fec_eth_control, fec_eth_can_send, fec_eth_send, fec_eth_recv, fec_eth_deliver, fec_eth_int, fec_eth_int_vector); NETDEVTAB_ENTRY(fec_netdev, "fec_eth", fec_eth_init, &fec_eth0_sc); #ifdef CYGPKG_NET static cyg_interrupt fec_eth_interrupt; static cyg_handle_t fec_eth_interrupt_handle; #endif static void fec_eth_int(struct eth_drv_sc *data); #define FEC_ETH_INT CYGNUM_HAL_INTERRUPT_SIU_LVL1 #ifdef CYGPKG_NET // This ISR is called when the ethernet interrupt occurs static int fec_eth_isr(cyg_vector_t vector, cyg_addrword_t data, HAL_SavedRegisters *regs) { cyg_drv_interrupt_mask(FEC_ETH_INT); return (CYG_ISR_HANDLED|CYG_ISR_CALL_DSR); // Run the DSR } #endif // Deliver function (ex-DSR) handles the ethernet [logical] processing static void fec_eth_deliver(struct eth_drv_sc * sc) { fec_eth_int(sc); #ifdef CYGPKG_NET // Allow interrupts to happen again cyg_drv_interrupt_acknowledge(FEC_ETH_INT); cyg_drv_interrupt_unmask(FEC_ETH_INT); #endif } // // PHY unit access (via MII channel) // static void phy_write(int reg, int addr, unsigned short data) { volatile EPPC *eppc = (volatile EPPC *)eppc_base(); volatile struct fec *fec = (volatile struct fec *)((unsigned char *)eppc + FEC_OFFSET); fec->iEvent = iEvent_MII; fec->MiiData = MII_Start | MII_Write | MII_Phy(addr) | MII_Reg(reg) | MII_TA | data; while (!(fec->iEvent & iEvent_MII)) ; } static unsigned short phy_read(int reg, int addr) { volatile EPPC *eppc = (volatile EPPC *)eppc_base(); volatile struct fec *fec = (volatile struct fec *)((unsigned char *)eppc + FEC_OFFSET); fec->iEvent = iEvent_MII; fec->MiiData = MII_Start | MII_Read | MII_Phy(addr) | MII_Reg(reg) | MII_TA; while (!(fec->iEvent & iEvent_MII)) ; return fec->MiiData & 0x0000FFFF; } // // 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 fec_eth_init(struct cyg_netdevtab_entry *tab) { struct eth_drv_sc *sc = (struct eth_drv_sc *)tab->device_instance; struct fec_eth_info *qi = (struct fec_eth_info *)sc->driver_private; volatile EPPC *eppc = (volatile EPPC *)eppc_base(); volatile struct fec *fec = (volatile struct fec *)((unsigned char *)eppc + FEC_OFFSET); struct fec_bd *rxbd, *txbd; unsigned char *RxBUF, *TxBUF; unsigned short phy_state = 0; int cache_state; int i; unsigned long proc_rev; bool esa_ok; // Ensure consistent state between cache and what the FEC sees HAL_DCACHE_IS_ENABLED(cache_state); HAL_DCACHE_SYNC(); HAL_DCACHE_DISABLE(); qi->fec = fec; #ifdef CYGPKG_NET // Set up to handle interrupts cyg_drv_interrupt_create(FEC_ETH_INT, CYGARC_SIU_PRIORITY_HIGH, (cyg_addrword_t)sc, // Data item passed to interrupt handler (cyg_ISR_t *)fec_eth_isr, (cyg_DSR_t *)eth_drv_dsr, &fec_eth_interrupt_handle, &fec_eth_interrupt); cyg_drv_interrupt_attach(fec_eth_interrupt_handle); cyg_drv_interrupt_acknowledge(FEC_ETH_INT); cyg_drv_interrupt_unmask(FEC_ETH_INT); #endif // Shut down ethernet, in case it is already running fec->eControl = eControl_RESET; eppc->pip_pbdat &= ~0x00004000; // Reset PHY chip CYGACC_CALL_IF_DELAY_US(1000); // 1ms eppc->pip_pbdat |= 0x00004000; // Enable PHY chip fec->iMask = 0x0000000; // Disables all interrupts fec->iEvent = 0xFFFFFFFF; // Clear all interrupts fec->iVector = (1<<29); // Caution - must match FEC_ETH_INT above txbd = fec_eth_txring; rxbd = fec_eth_rxring; qi->tbase = txbd; qi->txbd = txbd; qi->tnext = txbd; qi->rbase = rxbd; qi->rxbd = rxbd; qi->rnext = rxbd; #define ROUNDUP(b,s) (((unsigned long)(b) + (s-1)) & ~(s-1)) RxBUF = (unsigned char *)ROUNDUP(&fec_eth_rxbufs[0][0], 32); TxBUF = (unsigned char *)ROUNDUP(&fec_eth_txbufs[0][0], 32); // setup buffer descriptors for (i = 0; i < CYGNUM_DEVS_ETH_POWERPC_FEC_RxNUM; i++) { rxbd->length = 0; rxbd->buffer = RxBUF; rxbd->ctrl = FEC_BD_Rx_Empty; RxBUF += CYGNUM_DEVS_ETH_POWERPC_FEC_BUFSIZE; rxbd++; } rxbd--; rxbd->ctrl |= FEC_BD_Rx_Wrap; // Last buffer for (i = 0; i < CYGNUM_DEVS_ETH_POWERPC_FEC_TxNUM; i++) { txbd->length = 0; txbd->buffer = TxBUF; txbd->ctrl = 0; TxBUF += CYGNUM_DEVS_ETH_POWERPC_FEC_BUFSIZE; txbd++; } txbd--; txbd->ctrl |= FEC_BD_Tx_Wrap; // Last buffer // Set up parallel port for connection to ethernet tranceiver eppc->pio_pdpar = 0x1FFF; asm volatile ("mfspr %0,638 \n\t" : "=r" (proc_rev) : ); #define PROC_REVB 0x0020 if ((proc_rev & 0x0000FFFF) == PROC_REVB) { eppc->pio_pddir = 0x1C58; } else { eppc->pio_pddir = 0x1FFF; } // Reset interrupts fec->iMask = 0x00000000; // No interrupts enabled fec->iEvent = 0xFFFFFFFF; // Clear all interrupts // Initialize shared PRAM fec->RxRing = fec_eth_rxring; fec->TxRing = fec_eth_txring; // Size of receive buffers fec->RxBufSize = CYGNUM_DEVS_ETH_POWERPC_FEC_BUFSIZE; // Receiver control fec->RxControl = RxControl_MII | RxControl_DRT; // fec->RxControl = RxControl_MII | RxControl_LOOP | RxControl_PROM; fec->RxHash = IEEE_8023_MAX_FRAME; // Largest possible ethernet frame // Transmit control fec->TxControl = 0; // Use largest possible Tx FIFO fec->TxWater = 3; // DMA control fec->FunCode = ((2<<29) | (2<<27) | (0<<24)); // MII speed control (50MHz) fec->MiiSpeed = 0x14; // Group address hash fec->hash[0] = 0; fec->hash[1] = 0; // Device physical address #ifdef CYGPKG_REDBOOT esa_ok = flash_get_config("fec_esa", enaddr, CONFIG_ESA); #else esa_ok = CYGACC_CALL_IF_FLASH_CFG_OP(CYGNUM_CALL_IF_FLASH_CFG_GET, "fec_esa", enaddr, CONFIG_ESA); #endif if (!esa_ok) { // Can't figure out ESA diag_printf("FEC_ETH - Warning! ESA unknown\n"); memcpy(&enaddr, &_default_enaddr, sizeof(enaddr)); } memcpy((void *)&fec->addr[0], &enaddr[0], sizeof(enaddr)); // os_printf("FEC ESA = %08x/%08x\n", fec->addr[0], fec->addr[1]); // Enable device fec->eControl = eControl_EN | eControl_MUX; // Enable transceiver (PHY) phy_write(PHY_BMCR, 0, PHY_BMCR_RESET); for (i = 0; i < 10; i++) { phy_state = phy_read(PHY_BMCR, 0); if (!(phy_state & PHY_BMCR_RESET)) break; } if (phy_state & PHY_BMCR_RESET) { os_printf("FEC: Can't get PHY unit to reset: %x\n", phy_state); return false; } phy_write(PHY_BMCR, 0, PHY_BMCR_AUTO_NEG); // os_printf("MII: %x/%x\n", phy_state, phy_read(PHY_BMCR, 0)); #ifdef CYGPKG_NET // Set up for interrupts fec->iMask = iEvent_all & ~iEvent_MII; #endif if (cache_state) HAL_DCACHE_ENABLE(); // Initialize upper level driver (sc->funs->eth_drv->init)(sc, (unsigned char *)&enaddr); return true; } // // This function is called to shut down the interface. // static void fec_eth_stop(struct eth_drv_sc *sc) { struct fec_eth_info *qi = (struct fec_eth_info *)sc->driver_private; // Disable the device! qi->fec->eControl &= ~eControl_EN; } // // 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 fec_eth_start(struct eth_drv_sc *sc, unsigned char *enaddr, int flags) { struct fec_eth_info *qi = (struct fec_eth_info *)sc->driver_private; // Enable the device! qi->fec->eControl |= eControl_EN; qi->fec->RxUpdate = 0x0F0F0F0F; // Any write tells machine to look for work } // // This function is called for low level "control" operations // static int fec_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 fec_eth_can_send(struct eth_drv_sc *sc) { struct fec_eth_info *qi = (struct fec_eth_info *)sc->driver_private; volatile struct fec_bd *txbd = qi->txbd; return ((txbd->ctrl & FEC_BD_Tx_Ready) == 0); } // // This routine is called to send data to the hardware. static void fec_eth_send(struct eth_drv_sc *sc, struct eth_drv_sg *sg_list, int sg_len, int total_len, unsigned long key) { struct fec_eth_info *qi = (struct fec_eth_info *)sc->driver_private; volatile struct fec_bd *txbd, *txfirst; volatile char *bp; int i, txindex, cache_state; // Find a free buffer txbd = txfirst = qi->txbd; while (txbd->ctrl & FEC_BD_Tx_Ready) { // This buffer is busy, move to next one if (txbd->ctrl & FEC_BD_Tx_Wrap) { txbd = qi->tbase; } else { txbd++; } if (txbd == txfirst) { #ifdef CYGPKG_NET panic ("No free xmit buffers"); #else os_printf("FEC Ethernet: No free xmit buffers\n"); #endif } } // Remember the next buffer to try if (txbd->ctrl & FEC_BD_Tx_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++) { memcpy((void *)bp, (void *)sg_list[i].buf, 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_FLUSH(txbd->buffer, txbd->length); // Make sure no stale data } // Send it on it's way txbd->ctrl |= FEC_BD_Tx_Ready | FEC_BD_Tx_Last | FEC_BD_Tx_TC; if (cache_state) { HAL_DCACHE_FLUSH(fec_eth_txring, sizeof(fec_eth_txring)); // Make sure no stale data } qi->fec->TxUpdate = 0x0F0F0F0F; // Any write tells machine to look for work } // // 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 // 'fec_eth_recv' will be called to actually fetch it from the hardware. // static void fec_eth_RxEvent(struct eth_drv_sc *sc) { struct fec_eth_info *qi = (struct fec_eth_info *)sc->driver_private; volatile struct fec_bd *rxbd; int cache_state; // Note: the MBX860 does not seem to snoop/invalidate the data cache properly! HAL_DCACHE_IS_ENABLED(cache_state); if (cache_state) { HAL_DCACHE_FLUSH(fec_eth_rxring, sizeof(fec_eth_rxring)); // Make sure no stale data } rxbd = qi->rnext; while ((rxbd->ctrl & FEC_BD_Rx_Empty) == 0) { qi->rxbd = rxbd; // Save for callback (sc->funs->eth_drv->recv)(sc, rxbd->length); rxbd->ctrl |= FEC_BD_Rx_Empty; if (rxbd->ctrl & FEC_BD_Rx_Wrap) { rxbd = qi->rbase; } else { rxbd++; } } // Remember where we left off qi->rnext = (struct fec_bd *)rxbd; if (cache_state) { HAL_DCACHE_FLUSH(fec_eth_rxring, sizeof(fec_eth_rxring)); // Make sure no stale data } qi->fec->RxUpdate = 0x0F0F0F0F; // Any write tells machine to look for work } // // 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 fec_eth_recv(struct eth_drv_sc *sc, struct eth_drv_sg *sg_list, int sg_len) { struct fec_eth_info *qi = (struct fec_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) { memcpy((void *)sg_list[i].buf, bp, sg_list[i].len); bp += sg_list[i].len; } } } static void fec_eth_TxEvent(struct eth_drv_sc *sc, int stat) { struct fec_eth_info *qi = (struct fec_eth_info *)sc->driver_private; volatile struct fec_bd *txbd; int txindex, cache_state; HAL_DCACHE_IS_ENABLED(cache_state); if (cache_state) { HAL_DCACHE_FLUSH(fec_eth_txring, sizeof(fec_eth_txring)); // Make sure no stale data } txbd = qi->tnext; // Note: TC field is used to indicate the buffer has/had data in it while ((txbd->ctrl & (FEC_BD_Tx_Ready|FEC_BD_Tx_TC)) == FEC_BD_Tx_TC) { txindex = ((unsigned long)txbd - (unsigned long)qi->tbase) / sizeof(*txbd); (sc->funs->eth_drv->tx_done)(sc, qi->txkey[txindex], 0); txbd->ctrl &= ~FEC_BD_Tx_TC; if (txbd->ctrl & FEC_BD_Tx_Wrap) { txbd = qi->tbase; } else { txbd++; } } // Remember where we left off qi->tnext = (struct fec_bd *)txbd; if (cache_state) { HAL_DCACHE_FLUSH(fec_eth_txring, sizeof(fec_eth_txring)); // Make sure no stale data } } // // Interrupt processing // static void fec_eth_int(struct eth_drv_sc *sc) { struct fec_eth_info *qi = (struct fec_eth_info *)sc->driver_private; unsigned long event; while ((event = qi->fec->iEvent) != 0) { if ((event & iEvent_TFINT) != 0) { fec_eth_TxEvent(sc, event); } if ((event & iEvent_RFINT) != 0) { fec_eth_RxEvent(sc); } qi->fec->iEvent = event; // Reset the bits we handled } } // // Interrupt vector // static int fec_eth_int_vector(struct eth_drv_sc *sc) { return (FEC_ETH_INT); }
