/* * bcm2835 sdhost controller * * Copyright © 2016 Richard Miller */ #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" extern SDio *sdcardlink; #define SDHOSTREGS (VIRTIO+0x202000) enum { Extfreq = 250*Mhz, /* guess external clock frequency if vcore doesn't say */ Initfreq = 400000, /* initialisation frequency for MMC */ SDfreq = 25*Mhz, /* standard SD frequency */ SDfreqhs = 50*Mhz, /* SD high speed frequency */ FifoDepth = 4, /* "Limit fifo usage due to silicon bug" (linux driver) */ MMCSelect = 7, /* mmc/sd card select command */ Setbuswidth = 6, /* mmc/sd set bus width command */ Switchfunc = 6, /* mmc/sd switch function command */ Stoptransmission = 12, /* mmc/sd stop transmission command */ Appcmd = 55, /* mmc/sd application command prefix */ }; enum { /* Controller registers */ Cmd = 0x00>>2, Arg = 0x04>>2, Timeout = 0x08>>2, Clkdiv = 0x0c>>2, Resp0 = 0x10>>2, Resp1 = 0x14>>2, Resp2 = 0x18>>2, Resp3 = 0x1c>>2, Status = 0x20>>2, Poweron = 0x30>>2, Dbgmode = 0x34>>2, Hconfig = 0x38>>2, Blksize = 0x3c>>2, Data = 0x40>>2, Blkcount = 0x50>>2, /* Cmd */ Start = 1<<15, Failed = 1<<14, Respmask = 7<<9, Resp48busy = 4<<9, Respnone = 2<<9, Resp136 = 1<<9, Resp48 = 0<<9, Host2card = 1<<7, Card2host = 1<<6, /* Status */ Busyint = 1<<10, Blkint = 1<<9, Sdioint = 1<<8, Rewtimeout = 1<<7, Cmdtimeout = 1<<6, Crcerror = 3<<4, Fifoerror = 1<<3, Dataflag = 1<<0, Intstatus = (Busyint|Blkint|Sdioint|Dataflag), Errstatus = (Rewtimeout|Cmdtimeout|Crcerror|Fifoerror), /* Hconfig */ BusyintEn = 1<<10, BlkintEn = 1<<8, SdiointEn = 1<<5, DataintEn = 1<<4, Slowcard = 1<<3, Extbus4 = 1<<2, Intbuswide = 1<<1, Cmdrelease = 1<<0, }; static int cmdinfo[64] = { [0] Start | Respnone, [2] Start | Resp136, [3] Start | Resp48, [5] Start | Resp48, [6] Start | Resp48, [63] Start | Resp48 | Card2host, [7] Start | Resp48busy, [8] Start | Resp48, [9] Start | Resp136, [11] Start | Resp48, [12] Start | Resp48busy, [13] Start | Resp48, [16] Start | Resp48, [17] Start | Resp48 | Card2host, [18] Start | Resp48 | Card2host, [24] Start | Resp48 | Host2card, [25] Start | Resp48 | Host2card, [41] Start | Resp48, [52] Start | Resp48, [53] Start | Resp48, [55] Start | Resp48, }; typedef struct Ctlr Ctlr; struct Ctlr { Rendez r; int bcount; int done; ulong extclk; int appcmd; }; static Ctlr sdhost; static void sdhostinterrupt(Ureg*, void*); static void WR(int reg, u32int val) { u32int *r = (u32int*)SDHOSTREGS; if(0)print("WR %2.2ux %ux\n", reg<<2, val); r[reg] = val; } static int datadone(void*) { return sdhost.done; } static void sdhostclock(uint freq) { uint div; div = sdhost.extclk / freq; if(sdhost.extclk / freq > freq) div++; if(div < 2) div = 2; WR(Clkdiv, div - 2); } static int sdhostinit(void) { u32int *r; char *s; ulong clk; int i; /* disconnect emmc and connect sdhost to SD card gpio pins */ for(i = 48; i <= 53; i++) gpiosel(i, Alt0); clk = getclkrate(ClkCore); s = ""; if(clk == 0){ s = "Assuming "; clk = Extfreq; } sdhost.extclk = clk; print("%ssdhost external clock %lud Mhz\n", s, clk/1000000); sdhostclock(Initfreq); r = (u32int*)SDHOSTREGS; WR(Poweron, 0); WR(Timeout, 0xF00000); WR(Dbgmode, FINS(r[Dbgmode], 9, 10, (FifoDepth | FifoDepth<<5))); return 0; } static int sdhostinquiry(char *inquiry, int inqlen) { return snprint(inquiry, inqlen, "BCM SDHost Controller"); } static void sdhostenable(void) { u32int *r; r = (u32int*)SDHOSTREGS; USED(r); WR(Poweron, 1); delay(10); WR(Hconfig, Intbuswide | Slowcard | BusyintEn); WR(Clkdiv, 0x7FF); intrenable(IRQsdhost, sdhostinterrupt, nil, 0, "sdhost"); } static int sdhostcmd(u32int cmd, u32int arg, u32int *resp) { u32int *r; u32int c; int i; ulong now; r = (u32int*)SDHOSTREGS; assert(cmd < nelem(cmdinfo) && cmdinfo[cmd] != 0); c = cmd | cmdinfo[cmd]; /* * CMD6 may be Setbuswidth or Switchfunc depending on Appcmd prefix */ if(cmd == Switchfunc && !sdhost.appcmd) c |= Card2host; if(cmd != Stoptransmission && (i = (r[Dbgmode] & 0xF)) > 2){ print("sdhost: previous command stuck: Dbg=%d Cmd=%ux\n", i, r[Cmd]); error(Eio); } if(r[Status] & (Errstatus|Dataflag)) WR(Status, Errstatus|Dataflag); sdhost.done = 0; WR(Arg, arg); WR(Cmd, c); now = m->ticks; while(((i=r[Cmd])&(Start|Failed)) == Start) if(m->ticks-now > HZ) break; if((i&(Start|Failed)) != 0){ if(r[Status] != Cmdtimeout) print("sdhost: cmd %ux arg %ux error stat %ux\n", i, arg, r[Status]); i = r[Status]; WR(Status, i); error(Eio); } switch(c & Respmask){ case Resp136: resp[0] = r[Resp0]; resp[1] = r[Resp1]; resp[2] = r[Resp2]; resp[3] = r[Resp3]; break; case Resp48: case Resp48busy: resp[0] = r[Resp0]; break; case Respnone: resp[0] = 0; break; } if((c & Respmask) == Resp48busy){ tsleep(&sdhost.r, datadone, 0, 3000); } switch(cmd) { case MMCSelect: /* * Once card is selected, use faster clock */ delay(1); sdhostclock(SDfreq); delay(1); break; case Setbuswidth: if(sdhost.appcmd){ /* * If card bus width changes, change host bus width */ switch(arg){ case 0: WR(Hconfig, r[Hconfig] & ~Extbus4); break; case 2: WR(Hconfig, r[Hconfig] | Extbus4); break; } }else{ /* * If card switched into high speed mode, increase clock speed */ if((arg&0x8000000F) == 0x80000001){ delay(1); sdhostclock(SDfreqhs); delay(1); } } break; } sdhost.appcmd = (cmd == Appcmd); return 0; } void sdhostiosetup(int write, void *buf, int bsize, int bcount) { USED(write); USED(buf); sdhost.bcount = bcount; WR(Blksize, bsize); WR(Blkcount, bcount); } static void sdhostio(int write, uchar *buf, int len) { u32int *r; int piolen; u32int w; r = (u32int*)SDHOSTREGS; assert((len&3) == 0); assert((PTR2UINT(buf)&3) == 0); okay(1); if(waserror()){ okay(0); nexterror(); } /* * According to comments in the linux driver, the hardware "doesn't * manage the FIFO DREQs properly for multi-block transfers" on input, * so the dma must be stopped early and the last 3 words fetched with pio */ piolen = 0; if(!write && sdhost.bcount > 1){ piolen = (FifoDepth-1) * sizeof(u32int); len -= piolen; } if(write) dmastart(DmaChanSdhost, DmaDevSdhost, DmaM2D, buf, &r[Data], len); else dmastart(DmaChanSdhost, DmaDevSdhost, DmaD2M, &r[Data], buf, len); if(dmawait(DmaChanSdhost) < 0) error(Eio); if(!write){ cachedinvse(buf, len); buf += len; for(; piolen > 0; piolen -= sizeof(u32int)){ if((r[Dbgmode] & 0x1F00) == 0){ print("sdhost: FIFO empty after short dma read\n"); error(Eio); } w = r[Data]; *((u32int*)buf) = w; buf += sizeof(u32int); } } poperror(); okay(0); } static void sdhostinterrupt(Ureg*, void*) { u32int *r; int i; r = (u32int*)SDHOSTREGS; i = r[Status]; WR(Status, i); if(i & Busyint){ sdhost.done = 1; wakeup(&sdhost.r); } } SDio sdiohost = { "sdhost", sdhostinit, sdhostenable, sdhostinquiry, sdhostcmd, sdhostiosetup, sdhostio, }; void sdhostlink(void) { sdcardlink = &sdiohost; }