/* * kirkwood SDIO / SDMem / MMC host interface */ #include "u.h" #include "../port/lib.h" #include "../port/error.h" #include "mem.h" #include "dat.h" #include "fns.h" #include "io.h" #include "../port/sd.h" #define TM(bits) ((bits)<<16) #define GETTM(bits) (((bits)>>16)&0xFFFF) #define GETCMD(bits) ((bits)&0xFFFF) typedef struct Ctlr Ctlr; enum { Clkfreq = 100000000, /* external clk frequency */ Initfreq= 400000, /* initialisation frequency for MMC */ SDfreq = 25000000, /* standard SD frequency */ PIOread = 0, /* use programmed i/o (not dma) for reading */ PIOwrite= 0, /* use programmed i/o (not dma) writing */ Polldone= 0, /* poll for Datadone status, don't use interrupt */ Pollread= 1, /* poll for reading blocks */ Pollwrite= 1, /* poll for writing blocks */ MMCSelect= 7, /* mmc/sd card select command */ Setbuswidth= 6, /* mmc/sd set bus width command */ }; enum { /* Controller registers */ DmaLSB = 0x0>>2, DmaMSB = 0x4>>2, Blksize = 0x8>>2, Blkcount = 0xc>>2, ArgLSB = 0x10>>2, ArgMSB = 0x14>>2, Tm = 0x18>>2, Cmd = 0x1c>>2, Resp0 = 0x20>>2, Resp1 = 0x24>>2, Resp2 = 0x28>>2, Resp3 = 0x2c>>2, Resp4 = 0x30>>2, Resp5 = 0x34>>2, Resp6 = 0x38>>2, Resp7 = 0x3c>>2, Data = 0x40>>2, Hoststat = 0x48>>2, Hostctl = 0x50>>2, Clockctl = 0x58>>2, Softreset = 0x5C>>2, Interrupt = 0x60>>2, ErrIntr = 0x64>>2, Irptmask = 0x68>>2, ErrIrptmask = 0x6C>>2, Irpten = 0x70>>2, ErrIrpten = 0x74>>2, Mbuslo = 0x100>>2, Mbushi = 0x104>>2, Win0ctl = 0x108>>2, Win0base = 0x10c>>2, Win1ctl = 0x110>>2, Win1base = 0x114>>2, Win2ctl = 0x118>>2, Win2base = 0x11c>>2, Win3ctl = 0x120>>2, Win3base = 0x124>>2, Clockdiv = 0x128>>2, /* Hostctl */ Timeouten = 1<<15, Datatoshift = 11, Datatomask = 0x7800, Hispeed = 1<<10, Dwidth4 = 1<<9, Dwidth1 = 0<<9, Bigendian = 1<<3, LSBfirst = 1<<4, Cardtypemask = 3<<1, Cardtypemem = 0<<1, Cardtypeio = 1<<1, Cardtypeiomem = 2<<1, Cardtypsdio = 3<<1, Pushpullen = 1<<0, /* Clockctl */ Sdclken = 1<<0, /* Softreset */ Swreset = 1<<8, /* Cmd */ Indexshift = 8, Isdata = 1<<5, Ixchken = 1<<4, Crcchken = 3<<2, Respmask = 3<<0, Respnone = 0<<0, Resp136 = 1<<0, Resp48 = 2<<0, Resp48busy = 3<<0, /* Tm */ Hostdma = 0<<6, Hostpio = 1<<6, Stopclken = 1<<5, Host2card = 0<<4, Card2host = 1<<4, Autocmd12 = 1<<2, Hwwrdata = 1<<1, Swwrdata = 1<<0, /* ErrIntr */ Crcstaterr = 1<<14, Crcstartbiterr = 1<<13, Crcendbiterr = 1<<12, Resptbiterr = 1<<11, Xfersizeerr = 1<<10, Cmdstarterr = 1<<9, Acmderr = 1<<8, Denderr = 1<<6, Dcrcerr = 1<<5, Dtoerr = 1<<4, Cbaderr = 1<<3, Cenderr = 1<<2, Ccrcerr = 1<<1, Ctoerr = 1<<0, /* Interrupt */ Err = 1<<15, Write8ready = 1<<11, Read8wready = 1<<10, Cardintr = 1<<8, Readrdy = 1<<5, Writerdy = 1<<4, Dmadone = 1<<3, Blockgap = 1<<2, Datadone = 1<<1, Cmddone = 1<<0, /* Hoststat */ Fifoempty = 1<<13, Fifofull = 1<<12, Rxactive = 1<<9, Txactive = 1<<8, Cardbusy = 1<<1, Cmdinhibit = 1<<0, }; int cmdinfo[64] = { [0] Ixchken, [2] Resp136, [3] Resp48 | Ixchken | Crcchken, [6] Resp48 | Ixchken | Crcchken, [7] Resp48busy | Ixchken | Crcchken, [8] Resp48 | Ixchken | Crcchken, [9] Resp136, [12] Resp48busy | Ixchken | Crcchken, [13] Resp48 | Ixchken | Crcchken, [16] Resp48, [17] Resp48 | Isdata | TM(Card2host) | Ixchken | Crcchken, [18] Resp48 | Isdata | TM(Card2host) | Ixchken | Crcchken, [24] Resp48 | Isdata | TM(Host2card | Hwwrdata) | Ixchken | Crcchken, [25] Resp48 | Isdata | TM(Host2card | Hwwrdata) | Ixchken | Crcchken, [41] Resp48, [55] Resp48 | Ixchken | Crcchken, }; struct Ctlr { Rendez r; int datadone; int fastclock; }; static Ctlr ctlr; static void sdiointerrupt(Ureg*, void*); void WR(int reg, u32int val) { u32int *r; r = (u32int*)AddrSdio; val &= 0xFFFF; if(0)iprint("WR %#4.4ux %#ux\n", reg<<2, val); r[reg] = val; } static uint clkdiv(uint d) { assert(d < 1<<11); return d; } static int datadone(void*) { return ctlr.datadone; } static int sdioinit(void) { u32int *r; r = (u32int*)AddrSdio; WR(Softreset, Swreset); while(r[Softreset] & Swreset) ; delay(10); return 0; } static int sdioinquiry(char *inquiry, int inqlen) { return snprint(inquiry, inqlen, "SDIO Host Controller"); } static void sdioenable(void) { u32int *r; r = (u32int*)AddrSdio; WR(Clockdiv, clkdiv(Clkfreq/Initfreq - 1)); delay(10); WR(Clockctl, r[Clockctl] & ~Sdclken); WR(Hostctl, Pushpullen|Bigendian|Cardtypemem); WR(Irpten, 0); WR(Interrupt, ~0); WR(ErrIntr, ~0); WR(Irptmask, ~0); WR(ErrIrptmask, ~Dtoerr); intrenable(Irqlo, IRQ0sdio, sdiointerrupt, &ctlr, "sdio"); } static int awaitdone(u32int *r, int bits, int ticks) { int i; ulong start; start = m->ticks; while(((i = r[Interrupt]) & (bits|Err)) == 0) if(m->ticks - start > ticks) break; return i; } static void ckerr(u32int *r, int i, int len, char *op) { int err; if(i & Err){ err = r[ErrIntr]; iprint("sdioio: (%d) %s error intr %#ux err %#ux stat %#ux\n", len, op, i, err, r[Hoststat]); WR(ErrIntr, err); WR(Interrupt, i); error(Eio); } } static void ckdmadone(u32int *r, int i, char *msg) { if((i & Dmadone) == 0){ iprint("sdioio: %s intr %#ux stat %#ux\n", msg, i, r[Hoststat]); WR(Interrupt, i); error(Eio); } } static void getresp(u32int *r, u32int *resp, int resptype) { switch(resptype){ case Resp136: resp[0] = r[Resp7]<<8 | r[Resp6]<<22; resp[1] = r[Resp6]>>10 | r[Resp5]<<6 | r[Resp4]<<22; resp[2] = r[Resp4]>>10 | r[Resp3]<<6 | r[Resp2]<<22; resp[3] = r[Resp2]>>10 | r[Resp1]<<6 | r[Resp0]<<22; break; case Resp48: case Resp48busy: resp[0] = r[Resp2] | r[Resp1]<<6 | r[Resp0]<<22; break; case Respnone: resp[0] = 0; break; } } static void awaitresp48data(u32int *r, u32int cmd) { int i; if(Polldone) i = awaitdone(r, Datadone, 3*HZ); else{ WR(Irpten, Datadone|Err); tsleep(&ctlr.r, datadone, 0, 3000); i = ctlr.datadone; ctlr.datadone = 0; WR(Irpten, 0); } if((i & Datadone) == 0) iprint("sdioio: no Datadone after CMD%d\n", cmd); if(i & Err) iprint("sdioio: CMD%d error interrupt %#ux %#ux\n", cmd, r[Interrupt], r[ErrIntr]); WR(Interrupt, i); } static void finishcmd(u32int cmd, u32int arg) { u32int *r; /* * Once card is selected, use faster clock. * If card bus width changes, change host bus width. */ r = (u32int*)AddrSdio; if(cmd == MMCSelect){ delay(10); WR(Clockdiv, clkdiv(Clkfreq/SDfreq - 1)); delay(10); ctlr.fastclock = 1; } else if(cmd == Setbuswidth) switch(arg){ case 0: WR(Hostctl, r[Hostctl] & ~Dwidth4); break; case 2: WR(Hostctl, r[Hostctl] | Dwidth4); break; } } static int sdiocmd(u32int cmd, u32int arg, u32int *resp) { int i, err; u32int c; u32int *r; assert(cmd < nelem(cmdinfo) && cmdinfo[cmd] != 0); i = GETTM(cmdinfo[cmd]); c = cmd<>16); WR(ErrIntr, ~0); WR(Cmd, c); r = (u32int*)AddrSdio; i = awaitdone(r, Cmddone, HZ); if((i & (Cmddone|Err)) != Cmddone){ if((err = r[ErrIntr]) != Ctoerr) iprint("sdio: cmd %#ux error intr %#ux %#ux stat %#ux\n", c, i, err, r[Hoststat]); WR(ErrIntr, err); WR(Interrupt, i); error(Eio); } WR(Interrupt, i & ~Datadone); c &= Respmask; getresp(r, resp, c); if(c == Resp48busy) awaitresp48data(r, cmd); finishcmd(cmd, arg); return 0; } static void sdioiosetup(int write, void *buf, int bsize, int bcount) { int len; uintptr pa; pa = PADDR(buf); if(write && !PIOwrite){ WR(DmaLSB, pa); WR(DmaMSB, pa>>16); len = bsize * bcount; cachedwbse(buf, len); l2cacheuwbse(buf, len); }else if(!write && !PIOread){ WR(DmaLSB, pa); WR(DmaMSB, pa>>16); len = bsize * bcount; cachedwbinvse(buf, len); l2cacheuwbinvse(buf, len); } WR(Blksize, bsize); WR(Blkcount, bcount); } static uchar * getdatas(u32int *r, uchar *buf) { ushort d; d = r[Data]; *buf++ = d; *buf++ = d>>8; return buf; } static int sdioread(uchar *buf, int *lenp) { int i, now, len; u32int *r; r = (u32int*)AddrSdio; i = 0; len = *lenp; while(len > 0){ if(Pollread){ now = m->ticks; i = awaitdone(r, Read8wready|Readrdy, 3*HZ); if(m->ticks - now > 3*HZ){ print("sdioio: (%d) no Readrdy intr %#ux stat %#ux\n", len, i, r[Hoststat]); error(Eio); } }else{ i = r[Interrupt]; if((i & (Read8wready|Readrdy|Err)) == 0){ WR(Irpten, (len > 8*4? Read8wready: Readrdy) | Err); tsleep(&ctlr.r, datadone, 0, 3000); WR(Irpten, 0); i = ctlr.datadone; ctlr.datadone = 0; if((i & (Read8wready|Readrdy|Err)) == 0){ print("sdioio: (%d) no Readrdy intr %#ux stat %#ux\n", len, i, r[Hoststat]); error(Eio); } } } if((i & Read8wready) && len >= 8*2*2){ for(i = 0; i < 8*2; i++) buf = getdatas(r, buf); len -= 8*2*2; }else if(i & Readrdy){ buf = getdatas(r, buf); buf = getdatas(r, buf); len -= 2*2; } else ckerr(r, i, len, "read"); } *lenp = len; return i; } static int sdiowrite(uchar *buf, int *lenp) { int i, now, len; u32int *r; r = (u32int*)AddrSdio; i = 0; len = *lenp; while(len > 0){ if(Pollwrite){ now = m->ticks; i = awaitdone(r, Writerdy, 8*HZ); if(m->ticks - now > 8*HZ){ print("sdioio: (%d) no Writerdy intr %#ux stat %#ux\n", len, i, r[Hoststat]); error(Eio); } }else{ i = r[Interrupt]; if((i & (Writerdy|Err)) == 0){ WR(Irpten, Writerdy | Err); tsleep(&ctlr.r, datadone, 0, 8000); WR(Irpten, 0); i = ctlr.datadone; ctlr.datadone = 0; if((i & (Writerdy|Err)) == 0){ print("sdioio: (%d) no Writerdy intr %#ux stat %#ux\n", len, i, r[Hoststat]); error(Eio); } } } if(i & Writerdy){ r[Data] = buf[0] | buf[1]<<8; r[Data] = buf[2] | buf[3]<<8; buf += 4; len -= 4; } else ckerr(r, i, len, "write"); } *lenp = len; return i; } static void sdioio(int write, uchar *buf, int len) { int i; u32int *r; assert((len & 3) == 0); r = (u32int*)AddrSdio; if(write && PIOwrite) i = sdiowrite(buf, &len); else if(!write && PIOread) i = sdioread(buf, &len); else{ WR(Irpten, Dmadone|Err); tsleep(&ctlr.r, datadone, 0, 3000); WR(Irpten, 0); i = ctlr.datadone; ctlr.datadone = 0; ckerr(r, i, len, "dma"); ckdmadone(r, i, "no dma done"); WR(Interrupt, Dmadone); } if(Polldone) i = awaitdone(r, Datadone, 3*HZ); else if((i & Datadone) == 0){ WR(Irpten, Datadone|Err); tsleep(&ctlr.r, datadone, 0, 3000); i = ctlr.datadone; ctlr.datadone = 0; WR(Irpten, 0); } ckerr(r, i, len, "IO"); ckdmadone(r, i, "IO timeout"); if(i) WR(Interrupt, i); } static void sdiointerrupt(Ureg*, void*) { u32int *r; r = (u32int*)AddrSdio; ctlr.datadone = r[Interrupt]; WR(Irpten, 0); wakeup(&ctlr.r); } SDio sdio = { "sdio", sdioinit, sdioenable, sdioinquiry, sdiocmd, sdioiosetup, sdioio, };