/*
*
* w89c840.c -- This file implements the winbond-840 driver for etherboot.
*
*/
/*
* Adapted by Igor V. Kovalenko
* -- <garrison@mail.ru>
* OR
* -- <iko@crec.mipt.ru>
* Initial adaptaion stage, including testing, completed 23 August 2000.
*/
/*
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/*
* date version by what
* Written: Aug 20 2000 V0.10 iko Initial revision.
* changes: Aug 22 2000 V0.90 iko Works!
* Aug 23 2000 V0.91 iko Cleanup, posted to etherboot
* maintainer.
* Aug 26 2000 V0.92 iko Fixed Rx ring handling.
* First Linux Kernel (TM)
* successfully loaded using
* this driver.
* Jan 07 2001 V0.93 iko Transmitter timeouts are handled
* using timer2 routines. Proposed
* by Ken Yap to eliminate CPU speed
* dependency.
* Dec 12 2003 V0.94 timlegge Fixed issues in 5.2, removed
* interrupt usage, enabled
* multicast support
*
* This is the etherboot driver for cards based on Winbond W89c840F chip.
*
* It was written from skeleton source, with Donald Becker's winbond-840.c
* kernel driver as a guideline. Mostly the w89c840 related definitions
* and the lower level routines have been cut-and-pasted into this source.
*
* Frankly speaking, about 90% of the code was obtained using cut'n'paste
* sequence :) while the remainder appeared while brainstorming
* Linux Kernel 2.4.0-testX source code. Thanks, Donald and Linus!
*
* There was a demand for using this card in a rather large
* remote boot environment at MSKP OVTI Lab of
* Moscow Institute for Physics and Technology (MIPT) -- http://www.mipt.ru/
* so you may count that for motivation.
*
*/
/*
* If you want to see debugging output then define W89C840_DEBUG
*/
/*
#define W89C840_DEBUG
*/
/*
* Keep using IO_OPS for Etherboot driver!
*/
#define USE_IO_OPS
#include "etherboot.h"
#include "nic.h"
#include "pci.h"
#include "timer.h"
typedef unsigned char u8;
typedef signed char s8;
typedef unsigned short u16;
typedef signed short s16;
typedef unsigned int u32;
typedef signed int s32;
/* Linux support functions */
/*
#define cpu_to_le32(val) (val)
#define le32_to_cpu(val) (val)
*/
/* Operational parameters that are set at compile time. */
/* Keep the ring sizes a power of two for compile efficiency.
The compiler will convert <unsigned>'%'<2^N> into a bit mask.
Making the Tx ring too large decreases the effectiveness of channel
bonding and packet priority.
There are no ill effects from too-large receive rings. */
/* The presumed FIFO size for working around the Tx-FIFO-overflow bug.
To avoid overflowing we don't queue again until we have room for a
full-size packet.
*/
/* Operational parameters that usually are not changed. */
/* Time in jiffies before concluding the transmitter is hung. */
/*
* Used to be this much CPU loops on Celeron@400 (?),
* now using real timer and TX_TIMEOUT!
* #define TX_LOOP_COUNT 10000000
*/
#if !defined(__OPTIMIZE__)
#endif
#ifdef USE_IO_OPS
#else
#endif
/* This driver was written to use PCI memory space, however some x86 systems
work only with I/O space accesses. Pass -DUSE_IO_OPS to use PCI I/O space
accesses instead of memory space. */
#ifdef USE_IO_OPS
#endif
/* Offsets to the Command and Status Registers, "CSRs".
While similar to the Tulip, these registers are longword aligned.
Note: It's not useful to define symbolic names for every register bit in
the device. The name can only partially document the semantics and make
the driver longer and more difficult to read.
*/
enum w840_offsets {
};
enum intr_status_bits {
};
/* Bits in the NetworkConfig register. */
enum rx_mode_bits {
};
enum mii_reg_bits {
};
/* The Tulip Rx and Tx buffer descriptors. */
struct w840_rx_desc {
};
struct w840_tx_desc {
};
/* Bits in network_desc.status */
enum desc_status_bits {
};
static struct winbond_private
{
/* Descriptor rings first for alignment. */
const char *product_name;
/* Frequently used values: keep some adjacent for cache effect. */
int csr6;
int tx_q_bytes;
/* These values are keep track of the transceiver/media in use. */
/* MII transceiver section. */
/* NIC specific static variables go here */
static int ioaddr;
#if 0
#endif
static void check_duplex(void);
static void set_rx_mode(void);
static void init_ring(void);
#if defined(W89C840_DEBUG)
{
printf("Interrupt status: ");
printf("\n");
/*sleep(1);*/
}
#endif
/**************************************************************************
w89c840_reset - Reset adapter
***************************************************************************/
{
int i;
/* Reset the chip to erase previous misconfiguration.
No hold time required! */
init_ring();
for (i = 0; i < ETH_ALEN; i++)
/* Initialize other registers. */
/* Configure the PCI bus bursts and FIFO thresholds.
486: Set 8 longword cache alignment, 8 longword burst.
586: Set 16 longword cache alignment, no burst limit.
Cache alignment bits 15:14 Burst length 13:8
0000 <not allowed> 0000 align to cache 0800 8 longwords
4000 8 longwords 0100 1 longword 1000 16 longwords
8000 16 longwords 0200 2 longwords 2000 32 longwords
C000 32 longwords 0400 4 longwords
Wait the specified 50 PCI cycles after a reset by initializing
Tx and Rx queues and the address filter list. */
check_duplex();
set_rx_mode();
/* Do not enable the interrupts Etherboot doesn't need them */
/*
writel(0x1A0F5, ioaddr + IntrStatus);
writel(0x1A0F5, ioaddr + IntrEnable);
*/
#if defined(W89C840_DEBUG)
printf("winbond-840 : Done reset.\n");
#endif
}
#if 0
{
/* we are polling, do not return now */
/*return 0;*/
} else {
/* Acknowledge all of the current interrupt sources ASAP. */
}
if (intr_stat & AbnormalIntr) {
/* There was an abnormal interrupt */
printf("\n-=- Abnormal interrupt.\n");
#if defined(W89C840_DEBUG)
#endif
/* There was an interrupt */
printf("-=- <=> No receive buffers available.\n");
}
}
}
#endif
/**************************************************************************
w89c840_poll - Wait for a frame
***************************************************************************/
{
/* return true if there's an ethernet packet ready to read */
/* nic->packet should contain data on return */
/* nic->packetlen should contain length of data */
int packet_received = 0;
#if defined(W89C840_DEBUG)
#endif
do {
/* Code from netdev_rx(dev) */
/* DescOwn bit is still set, we should wait for RX to complete */
packet_received = 0;
break;
}
if ( !retrieve ) {
packet_received = 1;
break;
}
/* Ingore earlier buffers. */
printf("winbond-840 : Oversized Ethernet frame spanned "
"multiple buffers, entry %d status %X !\n",
}
} else if (status & 0x8000) {
/* There was a fatal error. */
#if defined(W89C840_DEBUG)
if (status & 0x0890) {
printf(" RXLEN_ERROR");
}
if (status & 0x004C) {
printf(", FRAME_ERROR");
}
if (status & 0x0002) {
printf(", CRC_ERROR");
}
printf("\n");
#endif
/* Simpy do a reset now... */
packet_received = 0;
break;
}
} else {
/* Omit the four octet CRC from the length. */
#if defined(W89C840_DEBUG)
#endif
/* Check if the packet is long enough to accept without copying
to a minimally-sized skbuff. */
packet_received = 1;
/* Release buffer to NIC */
#if defined(W89C840_DEBUG)
/* You will want this info for the initial debug. */
printf(" Rx data %hhX:%hhX:%hhX:%hhX:%hhX:"
"%hhX %hhX:%hhX:%hhX:%hhX:%hhX:%hhX %hhX%hhX "
"%hhX.%hhX.%hhX.%hhX.\n",
#endif
}
} while (0);
return packet_received;
}
/**************************************************************************
w89c840_transmit - Transmit a frame
***************************************************************************/
static void w89c840_transmit(
const char *d, /* Destination */
unsigned int t, /* Type */
unsigned int s, /* size */
const char *p) /* Packet */
{
/* send the packet to destination */
unsigned entry;
int transmit_status;
/* Caution: the write order is important here, set the field
with the "ownership" bits last. */
/* Fill in our transmit buffer */
*((char *) tx_packet + 13) = t;
s += ETH_HLEN;
while (s < ETH_ZLEN)
/* Work around horrible bug in the chip by marking the queue as full
when we do not have FIFO room for a maximum sized packet. */
/* Actually this is left to help finding error tails later in debugging...
* See Linux kernel driver in winbond-840.c for details.
*/
}
#if defined(W89C840_DEBUG)
printf("winbond-840 : Transmit frame # %d size %d queued in slot %d.\n", w840private.cur_tx, s, entry);
#endif
/* Now wait for TX to complete. */
{
#if defined W89C840_DEBUG
#endif
while (1) {
#if defined(W89C840_DEBUG)
#endif
}
break;
}
}
if ((transmit_status & DescOwn) == 0) {
#if defined(W89C840_DEBUG)
printf("winbond-840 : transmission complete after wait loop iterations, status %X\n",
#endif
return;
}
/* Transmit timed out... */
return;
}
/**************************************************************************
w89c840_disable - Turn off ethernet interface
***************************************************************************/
{
/* merge reset and disable */
/* Don't know what to do to disable the board. Is this needed at all? */
/* Yes, a live NIC can corrupt the loaded memory later [Ken] */
/* Stop the chip's Tx and Rx processes. */
}
/**************************************************************************
w89c840_irq - Enable, Disable, or Force interrupts
***************************************************************************/
{
switch ( action ) {
case DISABLE :
break;
case ENABLE :
break;
case FORCE :
break;
}
}
/**************************************************************************
w89c840_probe - Look for an adapter, this routine's visible to the outside
***************************************************************************/
{
int i, j;
unsigned short value;
if (p->ioaddr == 0)
return 0;
#if defined(W89C840_DEBUG)
printf("winbond-840: PCI bus %hhX device function %hhX: I/O address: %hX\n", p->bus, p->devfn, ioaddr);
#endif
/* From Matt Hortman <mbhortman@acpthinclient.com> */
if (p->vendor == PCI_VENDOR_ID_WINBOND2
&& p->dev_id == PCI_DEVICE_ID_WINBOND2_89C840) {
/* detected "Winbond W89c840 Fast Ethernet PCI NIC" */
} else if ( p->vendor == PCI_VENDOR_ID_COMPEX
&& p->dev_id == PCI_DEVICE_ID_COMPEX_RL100ATX) {
/* detected "Compex RL100ATX Fast Ethernet PCI NIC" */
} else {
/* Gee, guess what? They missed again. */
return 0;
}
/* Ok. Got one. Read the eeprom. */
for (j = 0, i = 0; i < 0x40; i++) {
}
for (i=0;i<ETH_ALEN;i++) {
}
#if defined(W89C840_DEBUG)
#endif
/* Reset the chip to erase previous misconfiguration.
No hold time required! */
if (driver_flags & CanHaveMII) {
#if defined(W89C840_DEBUG)
printf("winbond-840 : MII PHY found at address %d, status "
#endif
}
}
if (phy_idx == 0) {
printf("winbond-840 : MII PHY not found -- this device may not operate correctly.\n");
}
}
/* point to NIC specific routines */
return 1;
}
/* Read the EEPROM and MII Management Data I/O (MDIO) interfaces. These are
often serial bit streams generated by the host processor.
The example below is for the common 93c46 EEPROM, 64 16 bit words. */
/* Delay between EEPROM clock transitions.
No extra delay is needed with 33Mhz PCI, but future 66Mhz access may need
a delay. Note that pre-2.0.34 kernels had a cache-alignment bug that
made udelay() unreliable.
The old method of using an ISA access as a delay, __SLOW_DOWN_IO__, is
depricated.
*/
enum EEPROM_Ctrl_Bits {
};
/* The EEPROM commands include the alway-set leading bit. */
enum EEPROM_Cmds {
};
{
int i;
int retval = 0;
/* Shift the read command bits out. */
for (i = 10; i >= 0; i--) {
}
for (i = 16; i > 0; i--) {
}
/* Terminate the EEPROM access. */
return retval;
}
/* MII transceiver control section.
Read and write the MII registers using software-generated serial
MDIO protocol. See the MII specifications or DP83840A data sheet
for details.
The maximum data clock rate is 2.5 Mhz. The minimum timing is usually
met by back-to-back 33Mhz PCI cycles. */
/* Set iff a MII transceiver on any interface requires mdio preamble.
This only set with older tranceivers, so the extra
code size of a per-interface flag is not worthwhile. */
/* Generate the preamble required for initial synchronization and
a few older transceivers. */
{
/* Establish sync by sending at least 32 logic ones. */
while (--bits >= 0) {
}
}
{
int i, retval = 0;
/* Shift the read command bits out. */
for (i = 15; i >= 0; i--) {
}
/* Read the two transition, 16 data, and wire-idle bits. */
for (i = 20; i > 0; i--) {
}
}
#if 0
{
int i;
/* Shift the command bits out. */
for (i = 31; i >= 0; i--) {
}
/* Clear out extra bits. */
for (i = 2; i > 0; i--) {
}
return;
}
#endif
static void check_duplex(void)
{
int duplex;
return;
#if defined(W89C840_DEBUG)
printf("winbond-840 : Setting %s-duplex based on MII # %d negotiated capability %X\n",
#endif
}
}
static void set_rx_mode(void)
{
/* Accept all multicasts from now on. */
/*
* works OK with multicast enabled.
*/
#if defined(W89C840_DEBUG)
printf("winbond-840 : Done setting RX mode.\n");
#endif
}
/* Initialize the Rx and Tx rings, along with various 'dev' bits. */
static void init_ring(void)
{
int i;
char * p;
w840private.tx_full = 0;
/* Initial all Rx descriptors. Fill in the Rx buffers. */
p = &rx_packet[0];
for (i = 0; i < RX_RING_SIZE; i++) {
}
/* Mark the last entry as wrapping the ring. */
for (i = 0; i < TX_RING_SIZE; i++) {
}
return;
}
};
.type = NIC_DRIVER,
.name = "W89C840F",
.probe = w89c840_probe,
.ids = w89c840_nics,
.class = 0,
};