mirror of
https://git.savannah.gnu.org/git/grub.git
synced 2026-10-21 14:59:34 +00:00
377 lines
12 KiB
C
377 lines
12 KiB
C
/*
|
|
Etherboot DEC Tulip driver
|
|
adapted by Ken Yap from
|
|
|
|
FreeBSD netboot DEC 21143 driver
|
|
|
|
Author: David Sharp
|
|
date: Nov/98
|
|
|
|
Known to work on DEC DE500 using 21143-PC chipset.
|
|
Even on cards with the same chipset there can be
|
|
incompatablity problems with the way media selection
|
|
and status LED settings are done. See comments below.
|
|
|
|
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
|
|
|
|
*/
|
|
|
|
#include "etherboot.h"
|
|
#include "nic.h"
|
|
#include "pci.h"
|
|
#include "cards.h"
|
|
#include "otulip.h"
|
|
|
|
static unsigned short vendor, dev_id;
|
|
static unsigned short ioaddr;
|
|
static unsigned int *membase;
|
|
static unsigned char srom[1024];
|
|
|
|
#define BUFLEN 1536 /* must be longword divisable */
|
|
/* buffers must be longword aligned */
|
|
|
|
/* transmit descriptor and buffer */
|
|
static struct txdesc txd;
|
|
|
|
/* receive descriptor(s) and buffer(s) */
|
|
#define NRXD 4
|
|
static struct rxdesc rxd[NRXD];
|
|
static int rxd_tail = 0;
|
|
#ifndef USE_INTERNAL_BUFFER
|
|
#define rxb ((char *)0x10000 - NRXD * BUFLEN)
|
|
#define txb ((char *)0x10000 - NRXD * BUFLEN - BUFLEN)
|
|
#else
|
|
static unsigned char rxb[NRXD * BUFLEN];
|
|
static unsigned char txb[BUFLEN];
|
|
#endif
|
|
|
|
static unsigned char ehdr[ETHER_HDR_SIZE]; /* buffer for ethernet header */
|
|
|
|
enum tulip_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 };
|
|
|
|
|
|
/***************************************************************************/
|
|
/* 21143 specific stuff */
|
|
/***************************************************************************/
|
|
|
|
/* XXX assume 33MHz PCI bus, this is not very accurate and should be
|
|
used only with gross over estimations of required delay times unless
|
|
you tune UADJUST to your specific processor and I/O subsystem */
|
|
|
|
#define UADJUST 870
|
|
static void udelay(unsigned long usec) {
|
|
unsigned long i;
|
|
for (i=((usec*UADJUST)/33)+1; i>0; i--) (void) TULIP_CSR_READ(csr_0);
|
|
}
|
|
|
|
/* The following srom related code was taken from FreeBSD's if_de.c */
|
|
/* with minor alterations to make it work here. the Linux code is */
|
|
/* better but this was easier to use */
|
|
|
|
static void delay_300ns(void)
|
|
{
|
|
int idx;
|
|
for (idx = (300 / 33) + 1; idx > 0; idx--)
|
|
(void) TULIP_CSR_READ(csr_busmode);
|
|
}
|
|
|
|
#define EMIT do { TULIP_CSR_WRITE(csr_srom_mii, csr); delay_300ns(); } while (0)
|
|
|
|
static void srom_idle(void)
|
|
{
|
|
unsigned bit, csr;
|
|
|
|
csr = SROMSEL ; EMIT;
|
|
csr = SROMSEL | SROMRD; EMIT;
|
|
csr ^= SROMCS; EMIT;
|
|
csr ^= SROMCLKON; EMIT;
|
|
/*
|
|
* Write 25 cycles of 0 which will force the SROM to be idle.
|
|
*/
|
|
for (bit = 3 + SROM_BITWIDTH + 16; bit > 0; bit--) {
|
|
csr ^= SROMCLKOFF; EMIT; /* clock low; data not valid */
|
|
csr ^= SROMCLKON; EMIT; /* clock high; data valid */
|
|
}
|
|
csr ^= SROMCLKOFF; EMIT;
|
|
csr ^= SROMCS; EMIT;
|
|
csr = 0; EMIT;
|
|
}
|
|
|
|
static void srom_read(void)
|
|
{
|
|
unsigned idx;
|
|
const unsigned bitwidth = SROM_BITWIDTH;
|
|
const unsigned cmdmask = (SROMCMD_RD << bitwidth);
|
|
const unsigned msb = 1 << (bitwidth + 3 - 1);
|
|
unsigned lastidx = (1 << bitwidth) - 1;
|
|
|
|
srom_idle();
|
|
|
|
for (idx = 0; idx <= lastidx; idx++) {
|
|
unsigned lastbit, data, bits, bit, csr;
|
|
csr = SROMSEL ; EMIT;
|
|
csr = SROMSEL | SROMRD; EMIT;
|
|
csr ^= SROMCSON; EMIT;
|
|
csr ^= SROMCLKON; EMIT;
|
|
|
|
lastbit = 0;
|
|
for (bits = idx|cmdmask, bit = bitwidth + 3; bit > 0; bit--, bits <<= 1)
|
|
{
|
|
const unsigned thisbit = bits & msb;
|
|
csr ^= SROMCLKOFF; EMIT; /* clock low; data not valid */
|
|
if (thisbit != lastbit) {
|
|
csr ^= SROMDOUT; EMIT; /* clock low; invert data */
|
|
} else {
|
|
EMIT;
|
|
}
|
|
csr ^= SROMCLKON; EMIT; /* clock high; data valid */
|
|
lastbit = thisbit;
|
|
}
|
|
csr ^= SROMCLKOFF; EMIT;
|
|
|
|
for (data = 0, bits = 0; bits < 16; bits++) {
|
|
data <<= 1;
|
|
csr ^= SROMCLKON; EMIT; /* clock high; data valid */
|
|
data |= TULIP_CSR_READ(csr_srom_mii) & SROMDIN ? 1 : 0;
|
|
csr ^= SROMCLKOFF; EMIT; /* clock low; data not valid */
|
|
}
|
|
srom[idx*2] = data & 0xFF;
|
|
srom[idx*2+1] = data >> 8;
|
|
csr = SROMSEL | SROMRD; EMIT;
|
|
csr = 0; EMIT;
|
|
}
|
|
srom_idle();
|
|
}
|
|
|
|
/**************************************************************************
|
|
ETH_RESET - Reset adapter
|
|
***************************************************************************/
|
|
static void tulip_reset(struct nic *nic)
|
|
{
|
|
int x,cnt=2;
|
|
|
|
outl(0x00000001, ioaddr + CSR0);
|
|
udelay(1000);
|
|
/* turn off reset and set cache align=16lword, burst=unlimit */
|
|
outl(0x01A08000, ioaddr + CSR0);
|
|
|
|
/* for some reason the media selection does not take
|
|
the first time se it is repeated. */
|
|
|
|
while(cnt--) {
|
|
/* stop TX,RX processes */
|
|
if (cnt == 1)
|
|
outl(0x32404000, ioaddr + CSR6);
|
|
else
|
|
outl(0x32000040, ioaddr + CSR6);
|
|
|
|
/* XXX - media selection is vendor specific and hard coded right
|
|
here. This should be fixed to use the hints in the SROM and
|
|
allow media selection by the user at runtime. MII support
|
|
should also be added. Support for chips other than the
|
|
21143 should be added here as well */
|
|
|
|
/* start set to 10Mbps half-duplex */
|
|
|
|
/* setup SIA */
|
|
outl(0x0, ioaddr + CSR13); /* reset SIA */
|
|
outl(0x7f3f, ioaddr + CSR14);
|
|
outl(0x8000008, ioaddr + CSR15);
|
|
outl(0x0, ioaddr + CSR13);
|
|
outl(0x1, ioaddr + CSR13);
|
|
outl(0x2404000, ioaddr + CSR6);
|
|
|
|
/* initalize GP */
|
|
outl(0x8af0008, ioaddr + CSR15);
|
|
outl(0x50008, ioaddr + CSR15);
|
|
|
|
/* end set to 10Mbps half-duplex */
|
|
|
|
if (vendor == PCI_VENDOR_ID_MACRONIX && dev_id == PCI_DEVICE_ID_MX987x5) {
|
|
/* do stuff for MX98715 */
|
|
outl(0x01a80000, ioaddr + CSR6);
|
|
outl(0xFFFFFFFF, ioaddr + CSR14);
|
|
outl(0x00001000, ioaddr + CSR12);
|
|
}
|
|
|
|
outl(0x0, ioaddr + CSR7); /* disable interrupts */
|
|
|
|
/* construct setup packet which is used by the 21143 to
|
|
program its CAM to recognize interesting MAC addresses */
|
|
|
|
memset(&txd, 0, sizeof(struct txdesc));
|
|
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 21143 */
|
|
|
|
/* construct perfect filter frame */
|
|
/* with mac address as first match */
|
|
/* and broadcast address for all others */
|
|
|
|
for(x=0;x<192;x++) txb[x] = 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];
|
|
outl((unsigned long)&txd, ioaddr + CSR4); /* set xmit buf */
|
|
outl(0x2406000, ioaddr + CSR6); /* start transmiter */
|
|
|
|
udelay(50000); /* wait for the setup packet to be processed */
|
|
|
|
}
|
|
|
|
/* setup receive descriptor */
|
|
{
|
|
int x;
|
|
for(x=0;x<NRXD;x++) {
|
|
memset(&rxd[x], 0, sizeof(struct rxdesc));
|
|
rxd[x].buf1addr = &rxb[x * BUFLEN];
|
|
rxd[x].buf2addr = 0; /* not used */
|
|
rxd[x].buf1sz = BUFLEN;
|
|
rxd[x].buf2sz = 0; /* not used */
|
|
rxd[x].control = 0x0;
|
|
rxd[x].status = 0x80000000; /* give ownership it to 21143 */
|
|
}
|
|
rxd[NRXD - 1].control = 0x008; /* Set Receive end of ring on la
|
|
st descriptor */
|
|
rxd_tail = 0;
|
|
}
|
|
|
|
/* tell DC211XX where to find rx descriptor list */
|
|
outl((unsigned long)&rxd[0], ioaddr + CSR3);
|
|
/* start the receiver */
|
|
outl(0x2406002, ioaddr + CSR6);
|
|
|
|
}
|
|
|
|
/**************************************************************************
|
|
ETH_TRANSMIT - Transmit a frame
|
|
***************************************************************************/
|
|
static const char padmap[] = {
|
|
0, 3, 2, 1};
|
|
|
|
static void tulip_transmit(struct nic *nic, const char *d, unsigned int t, unsigned int s, const char *p)
|
|
{
|
|
unsigned long time;
|
|
|
|
/* 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 txdesc));
|
|
|
|
txd.buf1addr = &ehdr[0]; /* ethernet header */
|
|
txd.buf1sz = ETHER_HDR_SIZE;
|
|
|
|
txd.buf2addr = p; /* packet to transmit */
|
|
txd.buf2sz = s;
|
|
|
|
txd.control = 0x188; /* LS+FS+TER */
|
|
|
|
txd.status = 0x80000000; /* give it to 21143 */
|
|
|
|
outl(inl(ioaddr + CSR6) & ~0x00004000, ioaddr + CSR6);
|
|
outl((unsigned long)&txd, ioaddr + CSR4);
|
|
outl(inl(ioaddr + CSR6) | 0x00004000, ioaddr + CSR6);
|
|
|
|
/* Wait for transmit to complete before returning. not well tested.
|
|
|
|
time = currticks();
|
|
while(txd.status & 0x80000000) {
|
|
if (currticks() - time > 20) {
|
|
printf("transmit timeout.\n");
|
|
break;
|
|
}
|
|
}
|
|
*/
|
|
|
|
}
|
|
|
|
/**************************************************************************
|
|
ETH_POLL - Wait for a frame
|
|
***************************************************************************/
|
|
static int tulip_poll(struct nic *nic)
|
|
{
|
|
if (rxd[rxd_tail].status & 0x80000000) return 0;
|
|
|
|
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 * BUFLEN, nic->packetlen);
|
|
|
|
/* return the descriptor and buffer to recieve ring */
|
|
rxd[rxd_tail].status = 0x80000000;
|
|
rxd_tail++;
|
|
if (rxd_tail == NRXD) rxd_tail = 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
static void tulip_disable(struct nic *nic)
|
|
{
|
|
/* nothing for the moment */
|
|
}
|
|
|
|
/**************************************************************************
|
|
ETH_PROBE - Look for an adapter
|
|
***************************************************************************/
|
|
struct nic *otulip_probe(struct nic *nic, unsigned short *io_addrs, struct pci_device *pci)
|
|
{
|
|
int i;
|
|
|
|
if (io_addrs == 0 || *io_addrs == 0)
|
|
return (0);
|
|
vendor = pci->vendor;
|
|
dev_id = pci->dev_id;
|
|
ioaddr = *io_addrs;
|
|
membase = (unsigned int *)pci->membase;
|
|
|
|
/* wakeup chip */
|
|
pcibios_write_config_dword(0,pci->devfn,0x40,0x00000000);
|
|
|
|
/* Stop the chip's Tx and Rx processes. */
|
|
/* outl(inl(ioaddr + CSR6) & ~0x2002, ioaddr + CSR6); */
|
|
/* Clear the missed-packet counter. */
|
|
/* (volatile int)inl(ioaddr + CSR8); */
|
|
|
|
srom_read();
|
|
|
|
for (i=0; i < 6; i++)
|
|
nic->node_addr[i] = srom[20+i];
|
|
|
|
printf("Tulip %b:%b:%b:%b:%b:%b at ioaddr 0x%x\n",
|
|
srom[20],srom[21],srom[22],srom[23],srom[24],srom[25],
|
|
ioaddr);
|
|
|
|
tulip_reset(nic);
|
|
|
|
nic->reset = tulip_reset;
|
|
nic->poll = tulip_poll;
|
|
nic->transmit = tulip_transmit;
|
|
nic->disable = tulip_disable;
|
|
return nic;
|
|
}
|