963 lines
19 KiB
C
963 lines
19 KiB
C
/*
|
|
* SMSC 9221 Ethernet driver
|
|
* specifically for the ISEE IGEPv2 board,
|
|
* where it is assigned to Chip Select 5,
|
|
* its registers are at 0x2c000000 (inherited from u-boot),
|
|
* and irq is 34 from gpio pin 176, thus gpio module 6.
|
|
*
|
|
* it's slow due to the use of fifos instead of buffer rings.
|
|
* the slow system dma just makes it worse.
|
|
*
|
|
* igepv2 u-boot uses pin 64 on gpio 3 as an output pin to reset the 9221.
|
|
*/
|
|
#include "u.h"
|
|
#include "../port/lib.h"
|
|
#include "mem.h"
|
|
#include "dat.h"
|
|
#include "fns.h"
|
|
#include "io.h"
|
|
#include "../port/error.h"
|
|
#include "../port/netif.h"
|
|
#include "../port/etherif.h"
|
|
|
|
/* currently using kprocs is a lot slower than not (87 s. to boot vs 60) */
|
|
#undef USE_KPROCS
|
|
|
|
enum {
|
|
Vid9221 = 0x9221,
|
|
Slop = 4, /* beyond ETHERMAXTU */
|
|
};
|
|
|
|
typedef struct Regs Regs;
|
|
struct Regs {
|
|
/* fifo ports */
|
|
ulong rxdata;
|
|
uchar _pad0[0x20 - 4];
|
|
ulong txdata;
|
|
uchar _pad1[0x40 - 0x24];
|
|
ulong rxsts;
|
|
ulong rxstspeek;
|
|
ulong txsts;
|
|
ulong txstspeek;
|
|
|
|
/* control & status */
|
|
ushort rev; /* chip revision */
|
|
ushort id; /* chip id, 0x9221 */
|
|
ulong irqcfg;
|
|
ulong intsts;
|
|
ulong inten;
|
|
ulong _pad2;
|
|
ulong bytetest;
|
|
ulong fifoint; /* fifo level interrupts */
|
|
ulong rxcfg;
|
|
ulong txcfg;
|
|
ulong hwcfg;
|
|
ulong rxdpctl; /* rx data path control */
|
|
ulong rxfifoinf;
|
|
ulong txfifoinf;
|
|
ulong pmtctl; /* power mgmt. control */
|
|
ulong gpiocfg;
|
|
ulong gptcfg; /* timer */
|
|
ulong gptcnt;
|
|
ulong _pad3;
|
|
ulong wordswap;
|
|
ulong freerun; /* counters */
|
|
ulong rxdrop;
|
|
|
|
/*
|
|
* mac registers are accessed indirectly via the mac csr registers.
|
|
* phy registers are doubly indirect, via the mac csr mii_acc &
|
|
* mii_data mac csr registers.
|
|
*/
|
|
ulong maccsrcmd; /* mac csr synchronizer */
|
|
ulong maccsrdata;
|
|
ulong afccfg; /* automatic flow control cfg. */
|
|
ulong eepcmd; /* eeprom */
|
|
ulong eepdata;
|
|
/* 0xb8 */
|
|
};
|
|
|
|
enum {
|
|
Nstatistics = 128,
|
|
};
|
|
|
|
enum {
|
|
/* txcmda bits */
|
|
Intcompl = 1<<31,
|
|
Bufendalign = 3<<24, /* mask */
|
|
Datastoff = 037<<16, /* mask */
|
|
Firstseg = 1<<13,
|
|
Lastseg = 1<<12,
|
|
Bufsize = MASK(11),
|
|
|
|
/* txcmdb bits */
|
|
Pkttag = MASK(16) << 16,
|
|
Txcksumen = 1<<14,
|
|
Addcrcdis = 1<<13,
|
|
Framepaddis = 1<<12,
|
|
Pktlen = (1<<1) - 1, /* mask */
|
|
|
|
/* txcfg bits */
|
|
Txsdump = 1<<15, /* flush tx status fifo */
|
|
Txddump = 1<<14, /* flush tx data fifo */
|
|
Txon = 1<<1,
|
|
Stoptx = 1<<0,
|
|
|
|
/* hwcfg bits */
|
|
Mbo = 1<<20, /* must be one */
|
|
Srstto = 1<<1, /* soft reset time-out */
|
|
Srst = 1<<0,
|
|
|
|
/* rxcfg bits */
|
|
Rxdmacntshift = 16, /* ulong count, 12 bits wide */
|
|
Rxdmacntmask = MASK(12) << Rxdmacntshift,
|
|
Rxdump = 1<<15, /* flush rx fifos */
|
|
|
|
/* rxsts bits */
|
|
Rxpktlenshift = 16, /* byte count */
|
|
Rxpktlenmask = MASK(14) << Rxpktlenshift,
|
|
Rxerr = 1<<15,
|
|
|
|
/* rxfifoinf bits */
|
|
Rxstsusedshift = 16, /* ulong count */
|
|
Rxstsusedmask = MASK(8) << Rxstsusedshift,
|
|
Rxdatausedmask = MASK(16), /* byte count */
|
|
|
|
/* txfifoinf bits */
|
|
Txstsusedshift = 16, /* ulong count */
|
|
Txstsusedmask = MASK(8) << Txstsusedshift,
|
|
Txdatafreemask = MASK(16), /* byte count */
|
|
|
|
/* pmtctl bits */
|
|
Dready = 1<<0,
|
|
|
|
/* maccsrcmd bits */
|
|
Csrbusy = 1<<31,
|
|
Csrread = 1<<30, /* not write */
|
|
Csraddrshift = 0,
|
|
Csraddrmask = MASK(8) - 1,
|
|
|
|
/* mac registers' indices */
|
|
Maccr = 1,
|
|
Macaddrh,
|
|
Macaddrl,
|
|
Machashh,
|
|
Machashl,
|
|
Macmiiacc, /* for doubly-indirect phy access */
|
|
Macmiidata,
|
|
Macflow,
|
|
Macvlan1,
|
|
Macvlan2,
|
|
Macwuff,
|
|
Macwucsr,
|
|
Maccoe,
|
|
|
|
/* Maccr bits */
|
|
Rxall = 1<<31,
|
|
Rcvown = 1<<23, /* don't receive own transmissions */
|
|
Fdpx = 1<<20, /* full duplex */
|
|
Mcpas = 1<<19, /* pass all multicast */
|
|
Prms = 1<<18, /* promiscuous */
|
|
Ho = 1<<15, /* hash-only filtering */
|
|
Hpfilt = 1<<13, /* hash/perfect filtering */
|
|
Padstr = 1<<8, /* strip padding & fcs (crc) */
|
|
Txen = 1<<3,
|
|
Rxen = 1<<2,
|
|
|
|
/* irqcfg bits */
|
|
Irqdeasclr = 1<<14, /* deassertion intv'l clear */
|
|
Irqdeassts = 1<<13, /* deassertion intv'l status */
|
|
Irqint = 1<<12, /* intr being asserted? (ro) */
|
|
Irqen = 1<<8,
|
|
Irqpol = 1<<4, /* irq output is active high */
|
|
Irqpushpull = 1<<0, /* irq output is push/pull driver */
|
|
|
|
/* intsts/inten bits */
|
|
Swint = 1<<31, /* generate an interrupt */
|
|
Txstop = 1<<25,
|
|
Rxstop = 1<<24,
|
|
Txioc = 1<<21,
|
|
Rxdma = 1<<20,
|
|
Gptimer = 1<<19,
|
|
Phy = 1<<18,
|
|
Rxe = 1<<14, /* errors */
|
|
Txe = 1<<13,
|
|
Tdfo = 1<<10, /* tx data fifo overrun */
|
|
Tdfa = 1<<9, /* tx data fifo available */
|
|
Tsff = 1<<8, /* tx status fifo full */
|
|
Tsfl = 1<<7, /* tx status fifo level */
|
|
Rsff = 1<<4, /* rx status fifo full */
|
|
Rsfl = 1<<3, /* rx status fifo level */
|
|
|
|
/* eepcmd bits */
|
|
Epcbusy = 1<<31,
|
|
Epccmdshift = 28, /* interesting one is Reload (7) */
|
|
Epctimeout = 1<<9,
|
|
Epcmacloaded = 1<<8,
|
|
Epcaddrshift = 0,
|
|
};
|
|
|
|
enum {
|
|
Rxintrs = Rsff | Rsfl | Rxe,
|
|
Txintrs = Tsff | Tsfl | Txe | Txioc,
|
|
};
|
|
|
|
/* wake-up frame filter */
|
|
struct Wakeup {
|
|
ulong bytemask[4]; /* index is filter # */
|
|
uchar filt0cmd; /* filter 0 command */
|
|
uchar _pad0;
|
|
uchar filt1cmd;
|
|
uchar _pad1;
|
|
uchar filt2cmd;
|
|
uchar _pad2;
|
|
uchar filt3cmd;
|
|
uchar _pad3;
|
|
uchar offset[4]; /* index is filter # */
|
|
ushort crc16[4]; /* " */
|
|
};
|
|
|
|
typedef struct Ctlr Ctlr;
|
|
struct Ctlr {
|
|
int port;
|
|
Ctlr* next;
|
|
Ether* edev;
|
|
Regs* regs;
|
|
int active;
|
|
int started;
|
|
int inited;
|
|
int id;
|
|
int cls;
|
|
ushort eeprom[0x40];
|
|
|
|
QLock alock; /* attach */
|
|
int nrb; /* how many this Ctlr has in the pool */
|
|
|
|
int* nic;
|
|
Lock imlock;
|
|
int im; /* interrupt mask */
|
|
|
|
// Mii* mii;
|
|
// Rendez lrendez;
|
|
int lim;
|
|
|
|
int link;
|
|
|
|
QLock slock;
|
|
uint statistics[Nstatistics];
|
|
uint lsleep;
|
|
uint lintr;
|
|
uint rsleep;
|
|
uint rintr;
|
|
int tsleep;
|
|
uint tintr;
|
|
|
|
uchar ra[Eaddrlen]; /* receive address */
|
|
ulong mta[128]; /* multicast table array */
|
|
|
|
Rendez rrendez;
|
|
int gotinput;
|
|
int rdcpydone;
|
|
|
|
Rendez trendez;
|
|
int gotoutput;
|
|
int wrcpydone;
|
|
|
|
Lock tlock;
|
|
};
|
|
|
|
#define csr32r(c, r) (*((c)->nic+((r)/4)))
|
|
#define csr32w(c, r, v) (*((c)->nic+((r)/4)) = (v))
|
|
|
|
static Ctlr *smcctlrhead, *smcctlrtail;
|
|
|
|
static char* statistics[Nstatistics] = { "dummy", };
|
|
|
|
static uchar mymac[] = { 0xb0, 0x0f, 0xba, 0xbe, 0x00, 0x00, };
|
|
|
|
static void etherclock(void);
|
|
static void smcreceive(Ether *edev);
|
|
static void smcinterrupt(Ureg*, void* arg);
|
|
|
|
static Ether *thisether;
|
|
static int attached;
|
|
|
|
static void
|
|
smconce(Ether *edev)
|
|
{
|
|
static int beenhere;
|
|
static Lock l;
|
|
|
|
ilock(&l);
|
|
if (!beenhere && edev != nil) {
|
|
beenhere = 1;
|
|
/* simulate interrupts if we don't know the irq */
|
|
if (edev->irq < 0) { /* poll as backup */
|
|
thisether = edev;
|
|
addclock0link(etherclock, 1000/HZ);
|
|
iprint(" polling");
|
|
}
|
|
}
|
|
iunlock(&l);
|
|
}
|
|
|
|
/*
|
|
* indirect (mac) register access
|
|
*/
|
|
|
|
static void
|
|
macwait(Regs *regs)
|
|
{
|
|
long bound;
|
|
|
|
for (bound = 400*Mhz; regs->maccsrcmd & Csrbusy && bound > 0; bound--)
|
|
;
|
|
if (bound <= 0)
|
|
iprint("smc: mac registers didn't come ready\n");
|
|
}
|
|
|
|
static ulong
|
|
macrd(Regs *regs, uchar index)
|
|
{
|
|
macwait(regs);
|
|
regs->maccsrcmd = Csrbusy | Csrread | index;
|
|
coherence(); /* back-to-back write/read delay per §6.2.1 */
|
|
macwait(regs);
|
|
return regs->maccsrdata;
|
|
}
|
|
|
|
static void
|
|
macwr(Regs *regs, uchar index, ulong val)
|
|
{
|
|
macwait(regs);
|
|
regs->maccsrdata = val;
|
|
regs->maccsrcmd = Csrbusy | index; /* fire */
|
|
macwait(regs);
|
|
}
|
|
|
|
|
|
static long
|
|
smcifstat(Ether* edev, void* a, long n, ulong offset)
|
|
{
|
|
Ctlr *ctlr;
|
|
char *p, *s;
|
|
int i, l, r;
|
|
|
|
ctlr = edev->ctlr;
|
|
qlock(&ctlr->slock);
|
|
p = malloc(READSTR);
|
|
l = 0;
|
|
for(i = 0; i < Nstatistics; i++){
|
|
// read regs->rxdrop TODO
|
|
r = 0;
|
|
if((s = statistics[i]) == nil)
|
|
continue;
|
|
switch(i){
|
|
default:
|
|
ctlr->statistics[i] += r;
|
|
if(ctlr->statistics[i] == 0)
|
|
continue;
|
|
l += snprint(p+l, READSTR-l, "%s: %ud %ud\n",
|
|
s, ctlr->statistics[i], r);
|
|
break;
|
|
}
|
|
}
|
|
|
|
l += snprint(p+l, READSTR-l, "lintr: %ud %ud\n",
|
|
ctlr->lintr, ctlr->lsleep);
|
|
l += snprint(p+l, READSTR-l, "rintr: %ud %ud\n",
|
|
ctlr->rintr, ctlr->rsleep);
|
|
l += snprint(p+l, READSTR-l, "tintr: %ud %ud\n",
|
|
ctlr->tintr, ctlr->tsleep);
|
|
|
|
l += snprint(p+l, READSTR-l, "eeprom:");
|
|
for(i = 0; i < 0x40; i++){
|
|
if(i && ((i & 0x07) == 0))
|
|
l += snprint(p+l, READSTR-l, "\n ");
|
|
l += snprint(p+l, READSTR-l, " %4.4uX", ctlr->eeprom[i]);
|
|
}
|
|
l += snprint(p+l, READSTR-l, "\n");
|
|
USED(l);
|
|
|
|
n = readstr(offset, a, n, p);
|
|
free(p);
|
|
qunlock(&ctlr->slock);
|
|
|
|
return n;
|
|
}
|
|
|
|
static void
|
|
smcpromiscuous(void* arg, int on)
|
|
{
|
|
int rctl;
|
|
Ctlr *ctlr;
|
|
Ether *edev;
|
|
Regs *regs;
|
|
|
|
edev = arg;
|
|
ctlr = edev->ctlr;
|
|
regs = ctlr->regs;
|
|
rctl = macrd(regs, Maccr);
|
|
if(on)
|
|
rctl |= Prms;
|
|
else
|
|
rctl &= ~Prms;
|
|
macwr(regs, Maccr, rctl);
|
|
}
|
|
|
|
static void
|
|
smcmulticast(void*, uchar*, int)
|
|
{
|
|
/* nothing to do, we allow all multicast packets in */
|
|
}
|
|
|
|
static int
|
|
iswrcpydone(void *arg)
|
|
{
|
|
return ((Ctlr *)arg)->wrcpydone;
|
|
}
|
|
|
|
static int
|
|
smctxstart(Ctlr *ctlr, uchar *ubuf, uint len)
|
|
{
|
|
uint wds, ruplen;
|
|
ulong *wdp, *txdp;
|
|
Regs *regs;
|
|
static ulong buf[ROUNDUP(ETHERMAXTU, sizeof(ulong)) / sizeof(ulong)];
|
|
|
|
if (!ctlr->inited) {
|
|
iprint("smctxstart: too soon to send\n");
|
|
return -1; /* toss it */
|
|
}
|
|
regs = ctlr->regs;
|
|
|
|
/* is there room for a packet in the tx data fifo? */
|
|
if (len < ETHERMINTU)
|
|
iprint("sending too-short (%d) pkt\n", len);
|
|
else if (len > ETHERMAXTU)
|
|
iprint("sending jumbo (%d) pkt\n", len);
|
|
|
|
ruplen = ROUNDUP(len, sizeof(ulong));
|
|
coherence(); /* back-to-back read/read delay per §6.2.2 */
|
|
if ((regs->txfifoinf & Txdatafreemask) < ruplen + 2*sizeof(ulong))
|
|
return -1; /* not enough room for data + command words */
|
|
|
|
if ((uintptr)ubuf & MASK(2)) { /* ensure word alignment */
|
|
memmove(buf, ubuf, len);
|
|
ubuf = (uchar *)buf;
|
|
}
|
|
|
|
/* tx cmd a: length is bytes in this buffer */
|
|
txdp = ®s->txdata;
|
|
*txdp = Intcompl | Firstseg | Lastseg | len;
|
|
/* tx cmd b: length is bytes in this packet (could be multiple buf.s) */
|
|
*txdp = len;
|
|
|
|
/* shovel pkt into tx fifo, which triggers transmission due to Txon */
|
|
wdp = (ulong *)ubuf;
|
|
for (wds = ruplen / sizeof(ulong) + 1; --wds > 0; )
|
|
*txdp = *wdp++;
|
|
|
|
regs->intsts = Txintrs; /* dismiss intr */
|
|
coherence();
|
|
regs->inten |= Txintrs;
|
|
coherence(); /* back-to-back write/read delay per §6.2.1 */
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
smctransmit(Ether* edev)
|
|
{
|
|
Block *bp;
|
|
Ctlr *ctlr;
|
|
|
|
ctlr = edev->ctlr;
|
|
if (ctlr == nil)
|
|
panic("smctransmit: nil ctlr");
|
|
ilock(&ctlr->tlock);
|
|
/*
|
|
* Try to fill the chip's buffers back up, via the tx fifo.
|
|
*/
|
|
while ((bp = qget(edev->oq)) != nil)
|
|
if (smctxstart(ctlr, bp->rp, BLEN(bp)) < 0) {
|
|
qputback(edev->oq, bp); /* retry the block later */
|
|
iprint("smctransmit: tx data fifo full\n");
|
|
break;
|
|
} else
|
|
freeb(bp);
|
|
iunlock(&ctlr->tlock);
|
|
}
|
|
|
|
static void
|
|
smctransmitcall(Ether *edev) /* called from devether.c */
|
|
{
|
|
Ctlr *ctlr;
|
|
|
|
ctlr = edev->ctlr;
|
|
ctlr->gotoutput = 1;
|
|
#ifdef USE_KPROCS
|
|
wakeup(&ctlr->trendez);
|
|
#else
|
|
smctransmit(edev);
|
|
#endif
|
|
}
|
|
|
|
static int
|
|
smcrim(void* ctlr)
|
|
{
|
|
return ((Ctlr*)ctlr)->gotinput;
|
|
}
|
|
|
|
static void
|
|
smcrproc(void* arg)
|
|
{
|
|
Ctlr *ctlr;
|
|
Ether *edev;
|
|
|
|
edev = arg;
|
|
ctlr = edev->ctlr;
|
|
for(;;){
|
|
ctlr->rsleep++;
|
|
sleep(&ctlr->rrendez, smcrim, ctlr);
|
|
|
|
/* process any newly-arrived packets and pass to etheriq */
|
|
ctlr->gotinput = 0;
|
|
smcreceive(edev);
|
|
}
|
|
}
|
|
|
|
static int
|
|
smcgotout(void* ctlr)
|
|
{
|
|
return ((Ctlr*)ctlr)->gotoutput;
|
|
}
|
|
|
|
static void
|
|
smctproc(void* arg)
|
|
{
|
|
Ctlr *ctlr;
|
|
Ether *edev;
|
|
|
|
edev = arg;
|
|
ctlr = edev->ctlr;
|
|
for(;;){
|
|
ctlr->tsleep++;
|
|
sleep(&ctlr->trendez, smcgotout, ctlr);
|
|
|
|
/* process any newly-arrived packets and pass to etheriq */
|
|
ctlr->gotoutput = 0;
|
|
smctransmit(edev);
|
|
}
|
|
}
|
|
|
|
void gpioirqclr(void);
|
|
|
|
static void
|
|
smcattach(Ether* edev)
|
|
{
|
|
#ifdef USE_KPROCS
|
|
char name[KNAMELEN];
|
|
#endif
|
|
Ctlr *ctlr;
|
|
|
|
ctlr = edev->ctlr;
|
|
qlock(&ctlr->alock);
|
|
if(waserror()){
|
|
qunlock(&ctlr->alock);
|
|
nexterror();
|
|
}
|
|
if (!ctlr->inited) {
|
|
ctlr->inited = 1;
|
|
#ifdef USE_KPROCS
|
|
snprint(name, KNAMELEN, "#l%drproc", edev->ctlrno);
|
|
kproc(name, smcrproc, edev);
|
|
|
|
snprint(name, KNAMELEN, "#l%dtproc", edev->ctlrno);
|
|
kproc(name, smctproc, edev);
|
|
#endif
|
|
|
|
iprint("smcattach:");
|
|
#ifdef USE_KPROCS
|
|
iprint(" with kprocs");
|
|
#else
|
|
iprint(" no kprocs");
|
|
#endif
|
|
iprint(", no dma");
|
|
/* can now accept real or simulated interrupts */
|
|
|
|
smconce(edev);
|
|
attached = 1;
|
|
iprint("\n");
|
|
}
|
|
qunlock(&ctlr->alock);
|
|
poperror();
|
|
}
|
|
|
|
static int
|
|
isrdcpydone(void *arg)
|
|
{
|
|
return ((Ctlr *)arg)->rdcpydone;
|
|
}
|
|
|
|
static void
|
|
smcreceive(Ether *edev)
|
|
{
|
|
uint wds, len, sts;
|
|
ulong *wdp, *rxdp;
|
|
Block *bp;
|
|
Ctlr *ctlr;
|
|
Regs *regs;
|
|
|
|
ctlr = edev->ctlr;
|
|
regs = ctlr->regs;
|
|
coherence(); /* back-to-back read/read delay per §6.2.2 */
|
|
/*
|
|
* is there a full packet in the rx data fifo?
|
|
*/
|
|
while (((regs->rxfifoinf & Rxstsusedmask) >> Rxstsusedshift) != 0) {
|
|
coherence();
|
|
sts = regs->rxsts; /* pop rx status */
|
|
if(sts & Rxerr)
|
|
iprint("smcreceive: rx error\n");
|
|
len = (sts & Rxpktlenmask) >> Rxpktlenshift;
|
|
if (len > ETHERMAXTU + Slop)
|
|
iprint("smcreceive: oversized rx pkt (%d)\n", len);
|
|
else if (len < ETHERMINTU)
|
|
iprint("smcreceive: too-short (%d) pkt\n", len);
|
|
wds = ROUNDUP(len, sizeof(ulong)) / sizeof(ulong);
|
|
if (wds > 0) {
|
|
/* copy aligned words from rx fifo into a Block */
|
|
bp = iallocb(len + sizeof(ulong) /* - 1 */);
|
|
if (bp == nil)
|
|
panic("smcreceive: nil Block*");
|
|
|
|
/* bp->rp should be 32-byte aligned, more than we need */
|
|
assert(((uintptr)bp->rp & (sizeof(ulong) - 1)) == 0);
|
|
wdp = (ulong *)bp->rp;
|
|
rxdp = ®s->rxdata;
|
|
wds = ROUNDUP(len, sizeof(ulong)) / sizeof(ulong) + 1;
|
|
while (--wds > 0)
|
|
*wdp++ = *rxdp;
|
|
bp->wp = bp->rp + len;
|
|
|
|
/* and push the Block upstream */
|
|
if (ctlr->inited)
|
|
etheriq(edev, bp);
|
|
else
|
|
freeb(bp);
|
|
|
|
regs->intsts = Rxintrs; /* dismiss intr */
|
|
coherence();
|
|
regs->inten |= Rxintrs;
|
|
}
|
|
coherence();
|
|
}
|
|
regs->inten |= Rxintrs;
|
|
coherence();
|
|
}
|
|
|
|
/*
|
|
* disable the stsclr bits in inten and write them to intsts to ack and dismiss
|
|
* the interrupt source.
|
|
*/
|
|
void
|
|
ackintr(Regs *regs, ulong stsclr)
|
|
{
|
|
if (stsclr == 0)
|
|
return;
|
|
|
|
regs->inten &= ~stsclr;
|
|
coherence();
|
|
|
|
// regs->intsts = stsclr; /* acknowledge & clear intr(s) */
|
|
// coherence();
|
|
}
|
|
|
|
static void
|
|
smcinterrupt(Ureg*, void* arg)
|
|
{
|
|
int junk;
|
|
unsigned intsts, intr;
|
|
Ctlr *ctlr;
|
|
Ether *edev;
|
|
Regs *regs;
|
|
|
|
edev = arg;
|
|
ctlr = edev->ctlr;
|
|
ilock(&ctlr->imlock);
|
|
regs = ctlr->regs;
|
|
|
|
gpioirqclr();
|
|
|
|
coherence(); /* back-to-back read/read delay per §6.2.2 */
|
|
intsts = regs->intsts;
|
|
coherence();
|
|
|
|
intsts &= ~MASK(3); /* ignore gpio bits */
|
|
if (0 && intsts == 0) {
|
|
coherence();
|
|
iprint("smc: interrupt without a cause; insts %#ux (vs inten %#lux)\n",
|
|
intsts, regs->inten);
|
|
}
|
|
|
|
intr = intsts & Rxintrs;
|
|
if(intr) {
|
|
/* disable interrupt sources; kproc/smcreceive will reenable */
|
|
ackintr(regs, intr);
|
|
|
|
ctlr->rintr++;
|
|
ctlr->gotinput = 1;
|
|
#ifdef USE_KPROCS
|
|
wakeup(&ctlr->rrendez);
|
|
#else
|
|
smcreceive(edev);
|
|
#endif
|
|
}
|
|
|
|
while(((regs->txfifoinf & Txstsusedmask) >> Txstsusedshift) != 0) {
|
|
/* probably indicates tx completion, just toss it */
|
|
junk = regs->txsts; /* pop tx sts */
|
|
USED(junk);
|
|
coherence();
|
|
}
|
|
|
|
intr = intsts & Txintrs;
|
|
if (ctlr->gotoutput || intr) {
|
|
/* disable interrupt sources; kproc/smctransmit will reenable */
|
|
ackintr(regs, intr);
|
|
|
|
ctlr->tintr++;
|
|
ctlr->gotoutput = 1;
|
|
#ifdef USE_KPROCS
|
|
wakeup(&ctlr->trendez);
|
|
#else
|
|
smctransmit(edev);
|
|
#endif
|
|
}
|
|
|
|
iunlock(&ctlr->imlock);
|
|
}
|
|
|
|
static void
|
|
etherclock(void)
|
|
{
|
|
smcinterrupt(nil, thisether);
|
|
}
|
|
|
|
static int
|
|
smcmii(Ctlr *)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
smcdetach(Ctlr* ctlr)
|
|
{
|
|
Regs *regs;
|
|
|
|
if (ctlr == nil || ctlr->regs == nil)
|
|
return -1;
|
|
regs = ctlr->regs;
|
|
/* verify that it's real by reading a few registers */
|
|
switch (regs->id) {
|
|
case Vid9221:
|
|
break;
|
|
default:
|
|
print("smc: unknown chip id %#ux\n", regs->id);
|
|
return -1;
|
|
}
|
|
regs->inten = 0; /* no interrupts */
|
|
regs->intsts = ~0; /* clear any pending */
|
|
regs->gptcfg = 0;
|
|
coherence();
|
|
regs->rxcfg = Rxdump;
|
|
regs->txcfg = Txsdump | Txddump;
|
|
regs->irqcfg &= ~Irqen;
|
|
coherence();
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
smcshutdown(Ether* ether)
|
|
{
|
|
smcdetach(ether->ctlr);
|
|
}
|
|
|
|
static void
|
|
powerwait(Regs *regs)
|
|
{
|
|
long bound;
|
|
|
|
regs->bytetest = 0; /* bring power on */
|
|
for (bound = 400*Mhz; !(regs->pmtctl & Dready) && bound > 0; bound--)
|
|
;
|
|
if (bound <= 0)
|
|
iprint("smc: pmtctl didn't come ready\n");
|
|
}
|
|
|
|
static int
|
|
smcreset(Ctlr* ctlr)
|
|
{
|
|
int r;
|
|
Regs *regs;
|
|
static char zea[Eaddrlen];
|
|
|
|
regs = ctlr->regs;
|
|
powerwait(regs);
|
|
|
|
if(smcdetach(ctlr))
|
|
return -1;
|
|
|
|
/* verify that it's real by reading a few registers */
|
|
switch (regs->id) {
|
|
case Vid9221:
|
|
break;
|
|
default:
|
|
print("smc: unknown chip id %#ux\n", regs->id);
|
|
return -1;
|
|
}
|
|
if (regs->bytetest != 0x87654321) {
|
|
print("smc: bytetest reg %#p (%#lux) != 0x87654321\n",
|
|
®s->bytetest, regs->bytetest);
|
|
return -1;
|
|
}
|
|
|
|
#ifdef TODO /* read MAC from EEPROM */
|
|
// int ctrl, i, pause, swdpio, txcw;
|
|
/*
|
|
* Snarf and set up the receive addresses.
|
|
* There are 16 addresses. The first should be the MAC address.
|
|
* The others are cleared and not marked valid (MS bit of Rah).
|
|
*/
|
|
for(i = Ea; i < Eaddrlen/2; i++){
|
|
ctlr->ra[2*i] = ctlr->eeprom[i];
|
|
ctlr->ra[2*i+1] = ctlr->eeprom[i]>>8;
|
|
}
|
|
|
|
/*
|
|
* Clear the Multicast Table Array.
|
|
* It's a 4096 bit vector accessed as 128 32-bit registers.
|
|
*/
|
|
memset(ctlr->mta, 0, sizeof(ctlr->mta));
|
|
for(i = 0; i < 128; i++)
|
|
csr32w(ctlr, Mta+i*4, 0);
|
|
#endif
|
|
regs->hwcfg |= Mbo;
|
|
|
|
/* don't overwrite existing ea */
|
|
// if (memcmp(edev->ea, zea, Eaddrlen) == 0)
|
|
// memmove(edev->ea, ctlr->ra, Eaddrlen);
|
|
|
|
r = ctlr->ra[3]<<24 | ctlr->ra[2]<<16 | ctlr->ra[1]<<8 | ctlr->ra[0];
|
|
macwr(regs, Macaddrl, r);
|
|
macwr(regs, Macaddrh, ctlr->ra[5]<<8 | ctlr->ra[4]);
|
|
|
|
/* turn on the controller */
|
|
macwr(regs, Maccoe, 0);
|
|
regs->inten = 0; /* no interrupts yet */
|
|
regs->intsts = ~0; /* clear any pending */
|
|
regs->gptcfg = 0;
|
|
coherence();
|
|
regs->rxcfg = Rxdump;
|
|
regs->txcfg = Txsdump | Txddump | Txon;
|
|
regs->fifoint = 72<<24; /* default values */
|
|
macwr(regs, Maccr, Rxall | Rcvown | Fdpx | Mcpas | Txen | Rxen);
|
|
coherence(); /* back-to-back write/read delay per §6.2.1 */
|
|
regs->irqcfg = 1<<24 | Irqen | Irqpushpull; /* deas for 10µs (linux) */
|
|
coherence(); /* back-to-back write/read delay per §6.2.1 */
|
|
regs->inten = Rxintrs | Txintrs;
|
|
coherence();
|
|
|
|
if(smcmii(ctlr) < 0)
|
|
return -1;
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
smcpci(void)
|
|
{
|
|
Ctlr *ctlr;
|
|
static int beenhere;
|
|
|
|
if (beenhere)
|
|
return;
|
|
beenhere = 1;
|
|
|
|
if (probeaddr(PHYSETHER) < 0)
|
|
return;
|
|
ctlr = malloc(sizeof(Ctlr));
|
|
ctlr->id = Vid9221<<16 | 0x0424; /* smsc 9221 */
|
|
ctlr->port = PHYSETHER;
|
|
ctlr->nic = (int *)PHYSETHER;
|
|
ctlr->regs = (Regs *)PHYSETHER;
|
|
|
|
if(smcreset(ctlr)){
|
|
free(ctlr);
|
|
return;
|
|
}
|
|
if(smcctlrhead != nil)
|
|
smcctlrtail->next = ctlr;
|
|
else
|
|
smcctlrhead = ctlr;
|
|
smcctlrtail = ctlr;
|
|
}
|
|
|
|
static int
|
|
smcpnp(Ether* edev)
|
|
{
|
|
Ctlr *ctlr;
|
|
static char zea[Eaddrlen];
|
|
|
|
if(smcctlrhead == nil)
|
|
smcpci();
|
|
|
|
/*
|
|
* Any adapter matches if no edev->port is supplied,
|
|
* otherwise the ports must match.
|
|
*/
|
|
for(ctlr = smcctlrhead; ctlr != nil; ctlr = ctlr->next){
|
|
if(ctlr->active)
|
|
continue;
|
|
if(edev->port == 0 || edev->port == ctlr->port){
|
|
ctlr->active = 1;
|
|
break;
|
|
}
|
|
}
|
|
if(ctlr == nil)
|
|
return -1;
|
|
|
|
edev->ctlr = ctlr;
|
|
ctlr->edev = edev; /* point back to Ether* */
|
|
edev->port = ctlr->port;
|
|
edev->irq = 34;
|
|
// TODO: verify speed (100Mb/s) and duplicity (full-duplex)
|
|
edev->mbps = 100;
|
|
|
|
/* don't overwrite existing ea */
|
|
if (memcmp(edev->ea, zea, Eaddrlen) == 0)
|
|
memmove(edev->ea, ctlr->ra, Eaddrlen);
|
|
|
|
/*
|
|
* Linkage to the generic ethernet driver.
|
|
*/
|
|
edev->attach = smcattach;
|
|
edev->transmit = smctransmitcall;
|
|
edev->ifstat = smcifstat;
|
|
/* edev->ctl = smcctl; /* no ctl msgs supported */
|
|
|
|
edev->arg = edev;
|
|
edev->promiscuous = smcpromiscuous;
|
|
edev->multicast = smcmulticast;
|
|
edev->shutdown = smcshutdown;
|
|
|
|
intrenable(edev->irq, smcinterrupt, edev, 0, edev->name);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
ether9221link(void)
|
|
{
|
|
addethercard("9221", smcpnp);
|
|
}
|