Mercurial > ecos
diff packages/devs/eth/cl/cs8900a/current/src/if_cs8900a.c @ 196:9b55e9c69693
Merge from eCos master repository on 2001-11-23-15:32:03-GMT
| author | jlarmour |
|---|---|
| date | Fri, 23 Nov 2001 17:12:52 +0000 |
| parents | |
| children | 51b34619b677 |
line wrap: on
line diff
new file mode 100644 --- /dev/null +++ b/packages/devs/eth/cl/cs8900a/current/src/if_cs8900a.c @@ -0,0 +1,578 @@ +//========================================================================== +// +// dev/if_cs8900a.c +// +// Device driver for Cirrus Logic CS8900A ethernet controller +// +//========================================================================== +//####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#### +//####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, jskov +// Date: 2001-11-02 +// Purpose: +// Description: Driver for CS8900 ethernet controller +// +// Note: Platform can define CYGSEM_DEVS_ETH_CL_CS8900A_NOINTS +// to get a timer thread polling instead of interupt based +// operation. +// +// Note: Driver will need some changes to support multiple instances +// +//####DESCRIPTIONEND#### +// +//========================================================================== + +#include <pkgconf/system.h> +#ifdef CYGPKG_NET +#include <pkgconf/net.h> +#include <cyg/kernel/kapi.h> +#endif +#include <pkgconf/io_eth_drivers.h> +#include <cyg/infra/cyg_type.h> +#include <cyg/hal/hal_arch.h> +#include <cyg/hal/hal_intr.h> +#include <cyg/infra/diag.h> +#include <cyg/hal/drv_api.h> +#undef __ECOS +#define __ECOS +#include <eth_drv.h> +#include <netdev.h> + +#include <cyg/io/cs8900.h> + +#define __WANT_DEVS +#include CYGDAT_DEVS_ETH_CL_CS8900A_INL +#undef __WANT_DEVS + +// NOINTS operation only relevant when the NET package is loaded +#ifndef CYGPKG_NET +# undef CYGSEM_DEVS_ETH_CL_CS8900A_NOINTS +#endif + +#ifdef CYGSEM_DEVS_ETH_CL_CS8900A_NOINTS +#define STACK_SIZE CYGNUM_HAL_STACK_SIZE_MINIMUM +static char cs8900a_fake_int_stack[STACK_SIZE]; +static cyg_thread cs8900a_fake_int_thread_data; +static cyg_handle_t cs8900a_fake_int_thread_handle; +static void cs8900a_fake_int(cyg_addrword_t); +#endif + +#ifdef CYGDBG_IO_ETH_DRIVERS_DEBUG +extern int cyg_io_eth_net_debug; +#endif + +static void cs8900a_poll(struct eth_drv_sc *sc); +#ifdef CYGPKG_NET +// This ISR is called when the ethernet interrupt occurs +static int +cs8900a_isr(cyg_vector_t vector, cyg_addrword_t data, HAL_SavedRegisters *regs) +{ + cs8900a_priv_data_t* cpd = (cs8900a_priv_data_t *)data; + cyg_drv_interrupt_mask(cpd->interrupt); + return (CYG_ISR_HANDLED|CYG_ISR_CALL_DSR); // Run the DSR +} + +static void +cs8900a_dsr(cyg_vector_t vector, cyg_ucount32 count, cyg_addrword_t data) +{ + // This conditioning out is necessary because of explicit calls to this + // DSR - which would not ever be called in the case of a polled mode + // usage ie. in RedBoot. +#ifdef CYGPKG_IO_ETH_DRIVERS_NET + cs8900a_priv_data_t* cpd = (cs8900a_priv_data_t *)data; + struct cyg_netdevtab_entry *ndp = (struct cyg_netdevtab_entry *)(cpd->tab); + struct eth_drv_sc *sc = (struct eth_drv_sc *)(ndp->device_instance); + + DEBUG_FUNCTION(); + + // but here, it must be a *sc: + eth_drv_dsr( vector, count, (cyg_addrword_t)sc ); +#else +# ifndef CYGPKG_REDBOOT +# error Empty CS8900A ethernet DSR is compiled. Is this what you want? +# endif +#endif +} +#endif + +// The deliver function (ex-DSR) handles the ethernet [logical] processing +static void +cs8900a_deliver(struct eth_drv_sc *sc) +{ + cs8900a_poll(sc); +#ifdef CYGPKG_NET + { + cs8900a_priv_data_t *cpd = (cs8900a_priv_data_t *)sc->driver_private; + // Allow interrupts to happen again + cyg_drv_interrupt_acknowledge(cpd->interrupt); + cyg_drv_interrupt_unmask(cpd->interrupt); + } +#endif +} + +static int +cs8900a_int_vector(struct eth_drv_sc *sc) +{ + cs8900a_priv_data_t *cpd = (cs8900a_priv_data_t *)sc->driver_private; + return (cpd->interrupt) ? 1 : 0; +} + +static bool +cs8900a_init(struct cyg_netdevtab_entry *tab) +{ + struct eth_drv_sc *sc = (struct eth_drv_sc *)tab->device_instance; + cs8900a_priv_data_t *cpd = (cs8900a_priv_data_t *)sc->driver_private; + cyg_addrword_t base = cpd->base; + cyg_uint16 chip_type, chip_rev, chip_status; + int i; + long timeout = 500000; + + cpd->tab = tab; + + CYGHWR_CL_CS8900A_PLF_INIT(cpd); + +#ifdef CYGPKG_NET + // Initialize environment, setup interrupt handler + cyg_drv_interrupt_create(cpd->interrupt, + 0, // Priority - what goes here? + (cyg_addrword_t)cpd, // Data item passed to interrupt handler + (cyg_ISR_t *)cs8900a_isr, + (cyg_DSR_t *)cs8900a_dsr, + &cpd->interrupt_handle, + &cpd->interrupt_object); + cyg_drv_interrupt_attach(cpd->interrupt_handle); + cyg_drv_interrupt_acknowledge(cpd->interrupt); + cyg_drv_interrupt_unmask(cpd->interrupt); + +#ifdef CYGSEM_DEVS_ETH_CL_CS8900A_NOINTS + cyg_thread_create(1, // Priority + cs8900a_fake_int, // entry + (cyg_addrword_t)sc, // entry parameter + "CS8900 int", // Name + &cs8900a_fake_int_stack[0], // Stack + STACK_SIZE, // Size + &cs8900a_fake_int_thread_handle, // Handle + &cs8900a_fake_int_thread_data // Thread data structure + ); + cyg_thread_resume(cs8900a_fake_int_thread_handle); // Start it +#endif +#endif + + // Read controller ID - the first is a dummy read, since (on some + // platforms) the first access to the controller seems to skip the + // MSB 8 bits. + get_reg(base, PP_ChipID); + chip_type = get_reg(base, PP_ChipID); + chip_rev = get_reg(base, PP_ChipRev); +#if DEBUG & 8 + diag_printf("CS8900A[%p] - type: 0x%04x, rev: 0x%04x\n", base, chip_type, chip_rev); +#endif + if (chip_type != 0x630e) { +#if DEBUG & 8 + diag_printf("CS8900 - invalid type (0x%04x), must be 0x630e\n", chip_type); +#endif + return false; + } + + CYGHWR_CL_CS8900A_PLF_RESET(base); + put_reg(base, PP_SelfCtl, PP_SelfCtl_Reset); // Reset chip + while ((get_reg(base, PP_SelfStat) & PP_SelfStat_InitD) == 0) { + if (--timeout <= 0) { +#if DEBUG & 8 + diag_printf("CS8900 didn't reset - abort!\n"); +#endif + return false; + } + } + + chip_status = get_reg(base, PP_SelfStat); +#if DEBUG & 8 + diag_printf("CS8900 - status: 0x%04x (%sEEPROM present)\n", chip_status, + chip_status & PP_SelfStat_EEPROM ? "" : "no "); +#endif + + + // Disable reception whilst finding the ESA + put_reg(base, PP_LineCTL, 0 ); + // Find ESA + if (!cpd->hardwired_esa) { + cyg_bool esa_configured = false; + if (NULL != cpd->provide_esa) { + esa_configured = cpd->provide_esa(cpd); +# if DEBUG & 8 + if (esa_configured) + diag_printf("Got ESA from RedBoot option\n"); +# endif + } + if (!esa_configured && (chip_status & PP_SelfStat_EEPROM)) { + // Get ESA from EEPROM - via the PP_IA registers + cyg_uint16 esa_word; + for (i = 0; i < sizeof(cpd->esa); i += 2) { + esa_word = get_reg(base, PP_IA+i); + cpd->esa[i] = (esa_word & 0xFF); + cpd->esa[i+1] = (esa_word >> 8) & 0xFF; + } + esa_configured = true; + } + if (!esa_configured) { +# if DEBUG & 8 + diag_printf("CS8900 - no EEPROM, static ESA or RedBoot config option.\n"); +# endif + return false; + } + } + // Tell the chip what ESA to use + for (i = 0; i < sizeof(cpd->esa); i += 2) { + put_reg(base, PP_IA+i, cpd->esa[i] | (cpd->esa[i+1] << 8)); + } + // Set logical address mask + for (i = 0; i < 8; i += 2) { + put_reg(base, PP_LAF+i, 0xFFFF); + } +# if DEBUG & 8 + diag_printf("ESA %02x:%02x:%02x:%02x:%02x:%02x\n", + cpd->esa[0], cpd->esa[1], cpd->esa[2], + cpd->esa[3], cpd->esa[4], cpd->esa[5]); +# endif + + // Initialize upper level driver + (sc->funs->eth_drv->init)(sc, cpd->esa); + + return true; +} + +static void +cs8900a_stop(struct eth_drv_sc *sc) +{ + cs8900a_priv_data_t *cpd = (cs8900a_priv_data_t *)sc->driver_private; + cyg_addrword_t base = cpd->base; + + put_reg(base, PP_LineCTL, 0); +} + +// 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 +cs8900a_start(struct eth_drv_sc *sc, cyg_uint8 *esa, int flags) +{ + cyg_uint16 stat; + cs8900a_priv_data_t *cpd = (cs8900a_priv_data_t *)sc->driver_private; + cyg_addrword_t base = cpd->base; + + put_reg(base, PP_BusCtl, PP_BusCtl_MemoryE); // Disable interrupts, memory mode + put_reg(base, PP_IntReg, PP_IntReg_IRQ0); // Only possibility + put_reg(base, PP_RxCFG, PP_RxCFG_RxOK | PP_RxCFG_CRC | + PP_RxCFG_RUNT | PP_RxCFG_EXTRA); + put_reg(base, PP_RxCTL, PP_RxCTL_RxOK | PP_RxCTL_Broadcast | + PP_RxCTL_IA); + put_reg(base, PP_TxCFG, PP_TxCFG_TxOK | PP_TxCFG_Collision | + PP_TxCFG_CRS | PP_TxCFG_SQE | PP_TxCFG_Late | + PP_TxCFG_Jabber | PP_TxCFG_16Collisions); + put_reg(base, PP_BufCFG, PP_BufCFG_TxRDY | PP_BufCFG_TxUE | PP_BufCFG_RxMiss | + PP_BufCFG_TxCol | PP_BufCFG_Miss | PP_BufCFG_SWI); + put_reg(base, PP_IntReg, PP_IntReg_IRQ0); // Only possibility + put_reg(base, PP_LineCTL, PP_LineCTL_Rx | PP_LineCTL_Tx); + // Clear Interrupt Status Queue before enabling interrupts + do { + HAL_READ_UINT16(cpd->base+CS8900A_ISQ, stat); + } while (stat != 0) ; + cpd->txbusy = false; + put_reg(base, PP_BusCtl, PP_BusCtl_EnableIRQ); +} + +// This routine is called to perform special "control" opertions +static int +cs8900a_control(struct eth_drv_sc *sc, unsigned long key, void *data, int data_length) +{ + switch (key) { + case ETH_DRV_SET_MAC_ADDRESS: + return 0; + break; + default: + return 1; + break; + } +} + +// This routine is called to see if it is possible to send another packet. +// It will return non-zero if a transmit is possible, zero otherwise. +static int +cs8900a_can_send(struct eth_drv_sc *sc) +{ + cs8900a_priv_data_t *cpd = (cs8900a_priv_data_t *)sc->driver_private; + cyg_addrword_t base = cpd->base; + cyg_uint16 stat; + + stat = get_reg(base, PP_LineStat); + if ((stat & PP_LineStat_LinkOK) == 0) { + return false; // Link not connected + } +#ifdef CYGPKG_NET + // Horrible hack! + if (cpd->txbusy) { + cyg_tick_count_t now = cyg_current_time(); + if ((now - cpd->txstart) > 25) { + // 250ms is more than enough to transmit one frame +#if DEBUG & 1 + diag_printf("CS8900: Tx interrupt lost\n"); +#endif + cpd->txbusy = false; + // Free up the buffer (with error indication) + (sc->funs->eth_drv->tx_done)(sc, cpd->txkey, 1); + } + } +#endif + return (cpd->txbusy == false); +} + +// This routine is called to send data to the hardware. +static void +cs8900a_send(struct eth_drv_sc *sc, struct eth_drv_sg *sg_list, int sg_len, + int total_len, unsigned long key) +{ + cs8900a_priv_data_t *cpd = (cs8900a_priv_data_t *)sc->driver_private; + cyg_addrword_t base = cpd->base; + int i; + int len; + cyg_uint8 *data; + cyg_uint16 saved_data = 0, *sdata; + cyg_uint16 stat; + bool odd_byte = false; + + // Mark xmitter busy + cpd->txbusy = true; + cpd->txkey = key; +#ifdef CYGPKG_NET + cpd->txstart = cyg_current_time(); +#endif + // Start the xmit sequence + + // The hardware indicates that there are options as to when the actual + // packet transmission will start wrt moving of data into the transmit + // buffer. However, impirical results seem to indicate that if the + // packet is large and transmission is allowed to start before the + // entire packet has been pushed into the buffer, the hardware gets + // confused and the packet is lost, along with a "lost" Tx interrupt. + // This may be a case of the copy loop below being interrupted, e.g. + // a system timer interrupt, and the hardware getting unhappy that + // not all of the data was provided before the transmission should + // have completed (i.e. buffer underrun). + // For now, the solution is to not allow this overlap. + //HAL_WRITE_UINT16(cpd->base+CS8900A_TxCMD, PP_TxCmd_TxStart_5) + + // Start only when all data sent to chip + HAL_WRITE_UINT16(cpd->base+CS8900A_TxCMD, PP_TxCmd_TxStart_Full); + + HAL_WRITE_UINT16(cpd->base+CS8900A_TxLEN, total_len); + // Wait for controller ready signal + do { + stat = get_reg(base, PP_BusStat); + } while (!(stat & PP_BusStat_TxRDY)); + + // Put data into buffer + for (i = 0; i < sg_len; i++) { + data = (cyg_uint8 *)sg_list[i].buf; + len = sg_list[i].len; + + if (len > 0) { + /* Finish the last word. */ + if (odd_byte) { + saved_data |= ((cyg_uint16)*data++) << 8; + HAL_WRITE_UINT16(cpd->base+CS8900A_RTDATA, saved_data); + len--; + odd_byte = false; + } + /* Output contiguous words. */ + sdata = (cyg_uint16 *)data; + while (len > 1) { + HAL_WRITE_UINT16(cpd->base+CS8900A_RTDATA, *sdata++); + len -= sizeof(cyg_uint16); + } + /* Save last byte, if necessary. */ + if (len == 1) { + data = (cyg_uint8 *)sdata; + saved_data = (cyg_uint16)*data; + odd_byte = true; + } + } + } + if (odd_byte) { + HAL_WRITE_UINT16(cpd->base+CS8900A_RTDATA, saved_data); + } +} + +// 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 +// 'cs8900a_recv' will be called to actually fetch it from the hardware. +static void +cs8900a_RxEvent(struct eth_drv_sc *sc) +{ + cs8900a_priv_data_t *cpd = (cs8900a_priv_data_t *)sc->driver_private; + cyg_addrword_t base = cpd->base; + cyg_uint16 stat, len; + + HAL_READ_UINT16(base+CS8900A_RTDATA, stat); + HAL_READ_UINT16(base+CS8900A_RTDATA, len); +#ifdef CYGDBG_IO_ETH_DRIVERS_DEBUG + if (cyg_io_eth_net_debug) { + diag_printf("RxEvent - stat: %x, len: %d\n", stat, len); + } +#endif + (sc->funs->eth_drv->recv)(sc, len); +} + +// 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 +cs8900a_recv(struct eth_drv_sc *sc, struct eth_drv_sg *sg_list, int sg_len) +{ + cs8900a_priv_data_t *cpd = (cs8900a_priv_data_t *)sc->driver_private; + cyg_addrword_t base = cpd->base; + int i, mlen; + cyg_uint16 *data, val; + cyg_uint8 *cp, cval; + + for (i = 0; i < sg_len; i++) { + data = (cyg_uint16 *)sg_list[i].buf; + mlen = sg_list[i].len; + while (mlen >= sizeof(*data)) { + HAL_READ_UINT16(base+CS8900A_RTDATA, val); + if (data) { + *data++ = val; + } + mlen -= sizeof(*data); + } + if (mlen) { + // Fetch last odd byte + HAL_READ_UINT16(base+CS8900A_RTDATA, cval); + cval &= 0xFF; + if ((cp = (cyg_uint8 *)data) != 0) { + *cp = cval; + } + } + } +} + +static void +cs8900a_TxEvent(struct eth_drv_sc *sc, int stat) +{ + cs8900a_priv_data_t *cpd = (cs8900a_priv_data_t *)sc->driver_private; + cyg_addrword_t base = cpd->base; + + stat = get_reg(base, PP_TER); +#ifdef CYGDBG_IO_ETH_DRIVERS_DEBUG + if (cyg_io_eth_net_debug) { + diag_printf("Tx event: %x\n", stat); + } +#endif + cpd->txbusy = false; + (sc->funs->eth_drv->tx_done)(sc, cpd->txkey, 0); +} + +static void +cs8900a_BufEvent(struct eth_drv_sc *sc, int stat) +{ + if (stat & PP_BufCFG_RxMiss) { + } + if (stat & PP_BufCFG_TxUE) { + } +} + +static void +cs8900a_poll(struct eth_drv_sc *sc) +{ + cyg_uint16 event; + cs8900a_priv_data_t *cpd = (cs8900a_priv_data_t *)sc->driver_private; + cyg_addrword_t base = cpd->base; + + HAL_READ_UINT16(base+CS8900A_ISQ, event); + while (event != 0) { + switch (event & ISQ_EventMask) { + case ISQ_RxEvent: + cs8900a_RxEvent(sc); + break; + case ISQ_TxEvent: + cs8900a_TxEvent(sc, event); + break; + case ISQ_BufEvent: + cs8900a_BufEvent(sc, event); + break; + case ISQ_RxMissEvent: + // Receive miss counter has overflowed + break; + case ISQ_TxColEvent: + // Transmit collision counter has overflowed + break; + default: +#if DEBUG & 1 + diag_printf("%s: Unknown event: %x\n", __FUNCTION__, event); +#endif + break; + } + HAL_READ_UINT16(base+CS8900A_ISQ, event); + } + + CYGHWR_CL_CS8900A_PLF_INT_CLEAR(cpd); +} + +#ifdef CYGSEM_DEVS_ETH_CL_CS8900A_NOINTS +void +cs8900a_fake_int(cyg_addrword_t param) +{ + struct eth_drv_sc *sc = (struct eth_drv_sc *) param; + int s; + +#if DEBUG & 1 + diag_printf("cs8900a_fake_int()\n"); +#endif + + while (true) { + cyg_thread_delay(5); + s = splnet(); + cs8900a_poll(sc); + splx(s); + } +} +#endif
