/*
* 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<<Indexshift | GETCMD(cmdinfo[cmd]);
if(c & Isdata)
if(i & Card2host)
i |= PIOread? Hostpio: Hostdma;
else
i |= PIOwrite? Hostpio: Hostdma;
WR(Tm, i);
WR(ArgLSB, arg);
WR(ArgMSB, arg>>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,
};
|