blob: 58c2a7180d7d966313a307a019f3b014ca963c14 [file] [log] [blame] [raw]
/*
Tulip and clone Etherboot Driver
By Marty Connor (mdc@thinguin.org)
This software may be used and distributed according to the terms
of the GNU Public License, incorporated herein by reference.
Based on Ken Yap's Tulip Etherboot Driver and Donald Becker's
Linux Tulip Driver. Supports N-Way speed auto-configuration on
MX98715, MX98715A and MX98725. Support inexpensive PCI 10/100 cards
based on the Macronix MX987x5 chip, such as the SOHOware Fast
model SFA110A, and the LinkSYS model LNE100TX. The NetGear
model FA310X, based on the LC82C168 chip is also supported.
Documentation and source code used:
Source for Etherboot driver at
http://www.slug.org.au/etherboot/
MX98715A Data Sheet and MX98715A Application Note
on http://www.macronix.com/ (PDF format files)
Source for Linux tulip driver at
http://cesdis.gsfc.nasa.gov/linux/drivers/tulip.html
Adapted by Ken Yap from
FreeBSD netboot DEC 21143 driver
Author: David Sharp
date: Nov/98
Some code fragments were taken from verious places, Ken Yap's
etherboot, FreeBSD's if_de.c, and various Linux related files.
DEC's manuals for the 21143 and SROM format were very helpful.
The Linux de driver development page has a number of links to
useful related information. Have a look at:
ftp://cesdis.gsfc.nasa.gov/pub/linux/drivers/tulip-devel.html
*/
/*********************************************************************/
/* Revision History */
/*********************************************************************/
/*
11 Jan 2000 mdc 0.75b4
Added support for NetGear FA310TX card based on the LC82C168
chip. This should also support Lite-On LC82C168 boards.
Added simple MII support. Re-arranged code to better modularize
initializations.
04 Dec 1999 mdc 0.75b3
Added preliminary support for LNE100TX PCI cards. Should work for
PNIC2 cards. No MII support, but single interface (RJ45) tulip
cards seem to not care.
03 Dec 1999 mdc 0.75b2
Renamed from mx987x5 to ntulip, merged in original tulip init code
from tulip.c to support other tulip compatible cards.
02 Dec 1999 mdc 0.75b1
Released Beta MX987x5 Driver for code review and testing to netboot
and thinguin mailing lists.
*/
#include "etherboot.h"
#include "nic.h"
#include "pci.h"
/*********************************************************************/
/* Declarations */
/*********************************************************************/
typedef unsigned char u8;
typedef signed char s8;
typedef unsigned short u16;
typedef signed short s16;
typedef unsigned int u32;
typedef signed int s32;
/* Register offsets for tulip device */
enum ntulip_offsets {
CSR0=0, CSR1=0x08, CSR2=0x10, CSR3=0x18, CSR4=0x20, CSR5=0x28,
CSR6=0x30, CSR7=0x38, CSR8=0x40, CSR9=0x48, CSR10=0x50, CSR11=0x58,
CSR12=0x60, CSR13=0x68, CSR14=0x70, CSR15=0x78, CSR16=0x80, CSR20=0xA0
};
#define DEC_21142_CSR6_TTM 0x00400000 /* Transmit Threshold Mode */
#define DEC_21142_CSR6_HBD 0x00080000 /* Heartbeat Disable */
#define DEC_21142_CSR6_PS 0x00040000 /* Port Select */
/* EEPROM Address width definitions */
#define EEPROM_ADDRLEN 6
#define EEPROM_SIZE 128 /* 2 << EEPROM_ADDRLEN */
/* Data Read from the EEPROM */
static unsigned char ee_data[EEPROM_SIZE];
/* The EEPROM commands include the alway-set leading bit. */
#define EE_WRITE_CMD (5 << addr_len)
#define EE_READ_CMD (6 << addr_len)
#define EE_ERASE_CMD (7 << addr_len)
/* EEPROM_Ctrl bits. */
#define EE_SHIFT_CLK 0x02 /* EEPROM shift clock. */
#define EE_CS 0x01 /* EEPROM chip select. */
#define EE_DATA_WRITE 0x04 /* EEPROM chip data in. */
#define EE_WRITE_0 0x01
#define EE_WRITE_1 0x05
#define EE_DATA_READ 0x08 /* EEPROM chip data out. */
#define EE_ENB (0x4800 | EE_CS)
/* Delay between EEPROM clock transitions. Even at 33Mhz current PCI
implementations don't overrun the EEPROM clock. We add a bus
turn-around to insure that this remains true. */
#define eeprom_delay() inl(ee_addr)
/* helpful macro if on a big_endian machine for changing byte order.
not strictly needed on Intel */
#define le16_to_cpu(val) (val)
/* Calibration constant for udelay loop. Very approximate */
#define UADJUST 870
/* transmit and receive descriptor format */
struct txrxdesc {
unsigned long status; /* owner, status */
unsigned long buf1sz:11, /* size of buffer 1 */
buf2sz:11, /* size of buffer 2 */
control:10; /* control bits */
unsigned char *buf1addr; /* buffer 1 address */
unsigned char *buf2addr; /* buffer 2 address */
};
/* Size of transmit and receive buffers */
#define BUFLEN 1600
/* Note: transmit and receive buffers must be longword aligned and
longword divisable */
/* transmit descriptor and buffer */
static struct txrxdesc txd;
static unsigned char txb[BUFLEN];
/* receive descriptor(s) and buffer(s) */
#define NRXD 4
static struct txrxdesc rxd[NRXD];
static int rxd_tail = 0;
static unsigned char rxb[NRXD][BUFLEN];
/* PCI Bus parameters */
static unsigned short vendor, dev_id;
static unsigned long ioaddr;
static unsigned long devfn;
/* buffer for ethernet header */
static unsigned char ehdr[ETHER_HDR_SIZE];
/* MIIs found */
static int mii_cnt;
/* Temporary CSR6 storage */
static int csr6;
/*********************************************************************/
/* Utility Routines */
/*********************************************************************/
#undef NTULIP_DEBUG
#undef NTULIP_DEBUG_WHERE
static void inline whereami (char *str)
{
#ifdef NTULIP_DEBUG_WHERE
printf("%s\n", str);
// sleep(2);
#endif
}
/*********************************************************************/
/* Delay Code */
/*********************************************************************/
/* Assumes 33MHz PCI bus. This is not very accurate and should be used
only with gross over estimations of required delay times unless
UADJUST is tuned to your specific processor and I/O subsystem. */
static void udelay(unsigned long usec)
{
unsigned long i;
for (i=((usec*UADJUST)/33)+1; i>0; i--)
(void) inl(ioaddr + CSR0);
}
/*********************************************************************/
/* Media Descriptor Code */
/*********************************************************************/
static int lc82c168_mdio_read(int phy_id, int location)
{
int i = 1000;
int read_cmd = (0xf6 << 10) | (phy_id << 5) | location;
int retval = 0;
whereami("mdio_read\n");
outl(0x60020000 | (phy_id<<23) | (location<<18), ioaddr + 0xA0);
inl(ioaddr + 0xA0);
inl(ioaddr + 0xA0);
while (--i > 0)
if ( ! ((retval = inl(ioaddr + 0xA0)) & 0x80000000))
return retval & 0xffff;
if (i == 0) printf("mdio read timeout!\n");
return 0xffff;
}
static void lc82c168_mdio_write(int phy_id, int location, int value)
{
int i = 1000;
int cmd = (0x5002 << 16) | (phy_id << 23) | (location<<18) | value;
whereami("mdio_write\n");
outl(cmd, ioaddr + 0xA0);
do
if ( ! (inl(ioaddr + 0xA0) & 0x80000000))
break;
while (--i > 0);
if (i == 0) printf("mdio write timeout!\n");
return;
}
static void lc82c168_do_mii()
{
int phy, phy_idx;
whereami("do_mii\n");
mii_cnt = 0;
for (phy = 0, phy_idx = 0; phy < 32 && phy_idx < 4; phy++) {
int mii_status = lc82c168_mdio_read(phy, 1);
if ((mii_status & 0x8301) == 0x8001 ||
((mii_status & 0x8000) == 0 && (mii_status & 0x7800) != 0)) {
int mii_reg0 = lc82c168_mdio_read(phy, 0);
int mii_advert = lc82c168_mdio_read(phy, 4);
int mii_reg4 = ((mii_status >> 6) & 0x01E1) | 1;
phy_idx++;
printf("%s: MII trcvr #%d "
"config %x status %x advertising %x reg4 %x.\n",
"LC82C168", phy, mii_reg0, mii_status, mii_advert, mii_reg4);
lc82c168_mdio_write(phy, 0, mii_reg0 | 0x1000);
if (mii_advert != mii_reg4)
lc82c168_mdio_write(phy, 4, mii_reg4);
}
}
mii_cnt = phy_idx;
#ifdef NTULIP_DEBUG
printf("mii_cnt = %d\n", mii_cnt);
#endif
}
/*********************************************************************/
/* EEPROM Reading Code */
/*********************************************************************/
/* EEPROM routines adapted from the Linux Tulip Code */
/* Reading a serial EEPROM is a "bit" grungy, but we work our way
through:->.
*/
static int read_eeprom(unsigned long ioaddr, int location, int addr_len)
{
int i;
unsigned short retval = 0;
long ee_addr = ioaddr + CSR9;
int read_cmd = location | EE_READ_CMD;
whereami("read_eeprom\n");
outl(EE_ENB & ~EE_CS, ee_addr);
outl(EE_ENB, ee_addr);
/* Shift the read command bits out. */
for (i = 4 + addr_len; i >= 0; i--) {
short dataval = (read_cmd & (1 << i)) ? EE_DATA_WRITE : 0;
outl(EE_ENB | dataval, ee_addr);
eeprom_delay();
outl(EE_ENB | dataval | EE_SHIFT_CLK, ee_addr);
eeprom_delay();
}
outl(EE_ENB, ee_addr);
for (i = 16; i > 0; i--) {
outl(EE_ENB | EE_SHIFT_CLK, ee_addr);
eeprom_delay();
retval = (retval << 1) | ((inl(ee_addr) & EE_DATA_READ) ? 1 : 0);
outl(EE_ENB, ee_addr);
eeprom_delay();
}
/* Terminate the EEPROM access. */
outl(EE_ENB & ~EE_CS, ee_addr);
return retval;
}
/*********************************************************************/
/* ntulip_init_ring - setup the tx and rx descriptors */
/*********************************************************************/
static void ntulip_init_ring(struct nic *nic)
{
int i;
/* setup the transmit descriptor */
memset(&txd, 0, sizeof(struct txrxdesc));
txd.buf1addr = &txb[0];
txd.buf2addr = &txb[0]; /* just in case */
txd.buf1sz = 192; /* setup packet must be 192 bytes */
txd.buf2sz = 0;
txd.control = 0x020; /* setup packet */
txd.status = 0x80000000; /* give ownership to device */
/* construct perfect filter frame with mac address as first match
and broadcast address for all others */
for (i=0; i<192; i++) txb[i] = 0xff;
txb[0] = nic->node_addr[0];
txb[1] = nic->node_addr[1];
txb[4] = nic->node_addr[2];
txb[5] = nic->node_addr[3];
txb[8] = nic->node_addr[4];
txb[9] = nic->node_addr[5];
/* setup receive descriptor */
for (i=0; i<NRXD; i++) {
memset(&rxd[i], 0, sizeof(struct txrxdesc));
rxd[i].buf1addr = &rxb[i][0];
rxd[i].buf2addr = 0; /* not used */
rxd[i].buf1sz = BUFLEN;
rxd[i].buf2sz = 0; /* not used */
rxd[i].control = 0x0;
rxd[i].status = 0x80000000; /* give ownership it to device */
}
/* Set Receive end of ring on last descriptor */
rxd[NRXD - 1].control = 0x008;
rxd_tail = 0;
}
/*********************************************************************/
/* eth_reset - Reset adapter */
/*********************************************************************/
static void ntulip_reset(struct nic *nic)
{
whereami("ntulip_reset\n");
if (vendor == PCI_VENDOR_ID_MACRONIX && dev_id == PCI_DEVICE_ID_MX987x5) {
/* set up 10-BASE-T Control Port */
outl(0xFFFFFFFF, ioaddr + CSR14);
/* set up 10-BASE-T Status Port */
outl(0x00001000, ioaddr + CSR12);
/* Set Operation Control Register (CSR6) for MX987x5
to allow N-Way Active Speed selection, and
start the chip's Tx to process setup frame.
While it is possible to force speed selection,
this is probably more useful most of the time.
*/
outl(0x01A80200, ioaddr + CSR6);
} else if (vendor == PCI_VENDOR_ID_LINKSYS && dev_id == PCI_DEVICE_ID_LC82C115) {
/* This is MX987x5 init code. It seems to work for the LNE100TX
but should be replaced when we figure out the right way
to do this initialization
*/
outl(0xFFFFFFFF, ioaddr + CSR14);
outl(0x00001000, ioaddr + CSR12);
outl(0x01A80200, ioaddr + CSR6);
} else if (vendor == PCI_VENDOR_ID_LINKSYS && dev_id == PCI_DEVICE_ID_DEC_TULIP) {
lc82c168_do_mii();
} else {
/* Set to 10Mbps half-duplex */
outl(0x00000000, ioaddr + CSR13);
outl(0x7F3F0000, ioaddr + CSR14);
outl(0x08000008, ioaddr + CSR15);
outl(0x00000000, ioaddr + CSR13);
outl(0x00000001, ioaddr + CSR13);
outl(0x02404000, ioaddr + CSR6);
outl(0x08AF0008, ioaddr + CSR15);
outl(0x00050008, ioaddr + CSR15);
}
/* Reset the chip, holding bit 0 set at least 50 PCI cycles. */
outl(0x00000001, ioaddr + CSR0);
udelay(50000);
udelay(50000);
/* turn off reset and set cache align=16lword, burst=unlimit */
outl(0x01A08000, ioaddr + CSR0);
/* set up transmit and receive descriptors */
ntulip_init_ring(nic);
/* Point to receive descriptor */
outl((unsigned long)&rxd[0], ioaddr + CSR3);
outl((unsigned long)&txd , ioaddr + CSR4);
csr6 = 0x02404000;
/* Chip specific init code */
if (vendor == PCI_VENDOR_ID_MACRONIX && dev_id == PCI_DEVICE_ID_MX987x5) {
csr6 = 0x01880200;
/* Set CSR16 and CSR20 to values that allow device modification */
outl(0x0B3C0000 | inl(ioaddr + CSR16), ioaddr + CSR16);
outl(0x00011000 | inl(ioaddr + CSR20), ioaddr + CSR20);
} else if (vendor == PCI_VENDOR_ID_LINKSYS && dev_id == PCI_DEVICE_ID_LC82C115) {
/* This is MX987x5 init code. It seems to work for the LNE100TX
but should be replaced when we figure out the right way
to do this initialization.
*/
csr6 = 0x01880200;
outl(0x0B3C0000 | inl(ioaddr + CSR16), ioaddr + CSR16);
outl(0x00011000 | inl(ioaddr + CSR20), ioaddr + CSR20);
} else if (vendor == PCI_VENDOR_ID_LINKSYS && dev_id == PCI_DEVICE_ID_DEC_TULIP) {
csr6 = 0x814C0000;
outl(0x00000001, ioaddr + CSR15);
} else if (vendor == PCI_VENDOR_ID_DEC && dev_id == PCI_DEVICE_ID_DEC_21142) {
/* check SROM for evidence of an MII interface */
/* get Controller_0 Info Leaf Offset from SROM - assume already in ee_data */
int offset = ee_data [27] + (ee_data [28] << 8);
/* check offset range and if we have an extended type 3 Info Block */
if ((offset >= 30) && (offset < 120) && (ee_data [offset + 3] > 128) &&
(ee_data [offset + 4] == 3)) {
/* must have an MII interface - disable heartbeat, select port etc. */
csr6 |= (DEC_21142_CSR6_HBD | DEC_21142_CSR6_PS);
csr6 &= ~(DEC_21142_CSR6_TTM);
}
}
/* Start the chip's Tx to process setup frame. */
outl(csr6, ioaddr + CSR6);
outl(csr6 | 0x00002000, ioaddr + CSR6);
outl(csr6 | 0x00002002, ioaddr + CSR6);
}
/*********************************************************************/
/* eth_transmit - Transmit a frame */
/*********************************************************************/
static void ntulip_transmit(struct nic *nic, char *d, unsigned int t,
unsigned int s, char *p)
{
unsigned int len;
int pad;
int status;
unsigned char c;
whereami("ntulip_transmit\n");
/* Stop Tx */
outl(inl(ioaddr + CSR6) & ~0x00002000, ioaddr + CSR6);
/* setup ethernet header */
memcpy(ehdr, d, ETHER_ADDR_SIZE);
memcpy(&ehdr[ETHER_ADDR_SIZE], nic->node_addr, ETHER_ADDR_SIZE);
ehdr[ETHER_ADDR_SIZE*2] = (t >> 8) & 0xff;
ehdr[ETHER_ADDR_SIZE*2+1] = t & 0xff;
/* setup the transmit descriptor */
memset(&txd, 0, sizeof(struct txrxdesc));
txd.buf1addr = &ehdr[0]; /* ethernet header */
txd.buf1sz = ETHER_HDR_SIZE;
txd.buf2addr = p; /* packet to transmit */
txd.buf2sz = s;
txd.control = 0x00000188; /* LS+FS+TER */
txd.status = 0x80000000; /* give it the device */
/* Point to transmit descriptor */
outl((unsigned long)&txd, ioaddr + CSR4);
/* Start Tx */
outl(inl(ioaddr + CSR6) | 0x00002000, ioaddr + CSR6);
udelay(300);
}
/*********************************************************************/
/* eth_poll - Wait for a frame */
/*********************************************************************/
static int ntulip_poll(struct nic *nic)
{
int x;
whereami("ntulip_poll\n");
if (rxd[rxd_tail].status & 0x80000000)
return 0;
whereami("ntulip_poll got one\n");
nic->packetlen = (rxd[rxd_tail].status & 0x3FFF0000) >> 16;
/* copy packet to working buffer */
/* XXX - this copy could be avoided with a little more work
but for now we are content with it because the optimised
memcpy is quite fast */
memcpy(nic->packet, &rxb[rxd_tail][0], nic->packetlen);
/* return the descriptor and buffer to receive ring */
rxd[rxd_tail].status = 0x80000000;
rxd_tail++;
if (rxd_tail == NRXD) rxd_tail = 0;
return 1;
}
/*********************************************************************/
/* eth_disable - Disable the interface */
/*********************************************************************/
static void ntulip_disable(struct nic *nic)
{
whereami("ntulip_disable\n");
/* disable interrupts */
outl(0x00000000, ioaddr + CSR7);
/* Stop the chip's Tx and Rx processes. */
outl(inl(ioaddr + CSR6) & ~0x00002002, ioaddr + CSR6);
/* Clear the missed-packet counter. */
(volatile unsigned long)inl(ioaddr + CSR8);
}
/*********************************************************************/
/* eth_probe - Look for an adapter */
/*********************************************************************/
struct nic *ntulip_probe(struct nic *nic, unsigned short *io_addrs,
struct pci_device *pci)
{
int i;
whereami("ntulip_probe\n");
if (io_addrs == 0 || *io_addrs == 0)
return 0;
vendor = pci->vendor;
dev_id = pci->dev_id;
ioaddr = *io_addrs;
/* wakeup chip */
pcibios_write_config_dword(0, pci->devfn, 0x40, 0x00000000);
/* Stop the chip's Tx and Rx processes. */
outl(inl(ioaddr + CSR6) & ~0x00002002, ioaddr + CSR6);
/* Clear the missed-packet counter. */
(volatile unsigned long)inl(ioaddr + CSR8);
/* Get MAC Address */
/* Hardware Address retrieval method for LC82C168 */
if (vendor == PCI_VENDOR_ID_LINKSYS && dev_id == PCI_DEVICE_ID_DEC_TULIP) {
for (i = 0; i < 3; i++) {
int value, boguscnt = 100000;
outl(0x600 | i, ioaddr + 0x98);
do
value = inl(ioaddr + CSR9);
while (value < 0 && --boguscnt > 0);
nic->node_addr[i*2] = (u8)((value >> 8) & 0xff);
nic->node_addr[i*2 + 1] = (u8)( value & 0xff);
}
} else {
/* read EEPROM data */
for (i = 0; i < sizeof(ee_data)/2; i++)
((unsigned short *)ee_data)[i] =
le16_to_cpu(read_eeprom(ioaddr, i, EEPROM_ADDRLEN));
/* extract MAC address from EEPROM buffer */
for (i=0; i<6; i++)
nic->node_addr[i] = ee_data[20+i];
}
printf("NTulip %b:%b:%b:%b:%b:%b at ioaddr 0x%x\n",
nic->node_addr[0],nic->node_addr[1],nic->node_addr[2],nic->node_addr[3],
nic->node_addr[4],nic->node_addr[5],ioaddr);
/* initialize device */
ntulip_reset(nic);
nic->reset = ntulip_reset;
nic->poll = ntulip_poll;
nic->transmit = ntulip_transmit;
nic->disable = ntulip_disable;
return nic;
}