plan9fox/sys/src/9/pc/pmmc.c

559 lines
9 KiB
C

/*
* pci mmc controller.
*
* initially written for X230 Ricoh MMC controller.
* cmdinfo[] table stolen from bcm/emmc.c, thanks richard.
*
* for sdhc documentation see: https://www.sdcard.org/
*/
#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"
/* registers */
enum {
Rsdma = 0x00,
Rbsize = 0x04,
Rbcount = 0x06,
Rarg = 0x08,
Rmode = 0x0C,
Rcmd = 0x0E,
Rresp0 = 0x10,
Rresp1 = 0x14,
Rresp2 = 0x18,
Rresp3 = 0x1C,
Rdat0 = 0x20,
Rdat1 = 0x22,
Rpres = 0x24,
Rhc = 0x28,
Rpwr = 0x29,
Rbgc = 0x2A,
Rwkc = 0x2B,
Rclc = 0x2C,
Rtmc = 0x2E,
Rsrst = 0x2F,
Rnis = 0x30,
Reis = 0x32,
Rnie = 0x34,
Reie = 0x36,
Rnise = 0x38,
Reise = 0x3A,
Ra12 = 0x3C,
Rcap = 0x40,
Rrcap = 0x44,
Rxcap = 0x48,
Rrxcap = 0x4C,
Rfea12 = 0x50,
Rfeei = 0x52,
Radmasr = 0x54,
Radmaba = 0x58,
Rsists = 0xFC,
Rhcver = 0xFE,
};
/* sts bits */
enum {
Seint = 1<<15,
Snint = 1<<8,
Srem = 1<<7,
Sins = 1<<6,
Srrdy = 1<<5,
Swrdy = 1<<4,
Sdint = 1<<3,
Sbge = 1<<2,
Strac = 1<<1,
Scmdc = 1<<0,
Smask = 0x81ff,
};
/* err bits */
enum {
Ea12 = 1<<8,
Elimit = 1<<7,
Edebit = 1<<6,
Edcrc = 1<<5,
Edtmo = 1<<4,
Ecidx = 1<<3,
Ecebit = 1<<2,
Eccrc = 1<<1,
Ectmo = 1<<0,
Emask = 0x1ff,
};
/* present bits */
enum {
Plsig = 1<<24,
Pldat3 = 1<<23,
Pldat2 = 1<<22,
Pldat1 = 1<<21,
Pldat0 = 1<<20,
Ppswit = 1<<19,
Pcrddt = 1<<18,
Pcrdst = 1<<17,
Pcrdin = 1<<16,
Pbufrd = 1<<11,
Pbufwr = 1<<10,
Ptrard = 1<<9,
Ptrawr = 1<<8,
Pdat = 1<<2,
Pinhbc = 1<<1,
Pinhbd = 1<<0,
};
/* Rmode bits */
enum {
Mblk = 1<<5,
Mrd = 1<<4,
Mwr = 0<<4,
Ma12 = 1<<2,
Mcnt = 1<<1,
Mdma = 1<<0,
};
/* command bits */
enum {
Abort = 3<<6,
Isdata = 1<<5,
Ixchken = 1<<4,
Crcchken = 1<<3,
Respmask = 3,
Respnone = 0,
Resp48busy = 3,
Resp48 = 2,
Resp136 = 1,
};
/* fake for cmdinfo */
enum {
Blkcnten = Mblk << 8,
Multiblock = Mcnt << 8,
Card2host = Mrd << 8,
Host2card = Mwr << 8,
};
static 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 | Card2host | Ixchken | Crcchken,
[18] Resp48 | Isdata | Card2host | Multiblock | Blkcnten | Ixchken | Crcchken,
[24] Resp48 | Isdata | Host2card | Ixchken | Crcchken,
[25] Resp48 | Isdata | Host2card | Multiblock | Blkcnten | Ixchken | Crcchken,
[41] Resp48,
[55] Resp48 | Ixchken | Crcchken,
};
typedef struct Ctlr Ctlr;
struct Ctlr {
Lock;
Pcidev *pdev;
u8int *mmio;
int change;
u32int waitsts;
u32int waitmsk;
Rendez r;
struct {
int bcount;
int bsize;
} io;
};
static Ctlr pmmc[1];
#define CR8(c, off) *((u8int*)(c->mmio + off))
#define CR16(c, off) *((u16int*)(c->mmio + off))
#define CR32(c, off) *((u32int*)(c->mmio + off))
static void
mmcinterrupt(Ureg*, void *arg)
{
u16int nis, eis;
Ctlr *c;
c = arg;
nis = CR16(c, Rnis);
if((nis & Smask) == 0)
return; /* not for us */
CR16(c, Rnis) = nis; /* ack */
ilock(c);
eis = 0;
if((nis & Seint) != 0){
eis = CR16(c, Reis);
CR16(c, Reis) = eis; /* ack */
}
if((nis & Snint) != 0)
CR16(c, Rnie) |= Snint; /* ack */
if((nis & (Srem|Sins)) != 0)
c->change = 1;
c->waitsts |= nis | (eis << 16);
if((c->waitsts & c->waitmsk) != 0)
wakeup(&c->r);
iunlock(c);
}
static int
pmmcinit(void)
{
Pcidev *p;
p = nil;
while((p = pcimatch(p, 0, 0)) != nil){
if(p->ccrb == 8 && p->ccru == 5)
break;
if(p->vid == 0x1180){ /* Ricoh */
if(p->did == 0xe822) /* 5U822 SD/MMC */
break;
if(p->did == 0xe823) /* 5U823 SD/MMC */
break;
}
}
if(p == nil || p->mem[0].size < 256)
return -1;
pmmc->mmio = vmap(p->mem[0].bar & ~0x0F, p->mem[0].size);
if(pmmc->mmio == nil)
return -1;
pmmc->pdev = p;
pcienable(p);
if(p->did == 0x1180 && p->vid == 0xe823){ /* Ricoh */
/* Enable SD2.0 mode. */
pcicfgw8(p, 0xf9, 0xfc);
pcicfgw8(p, 0x150, 0x10);
pcicfgw8(p, 0xf9, 0x00);
/*
* Some SD/MMC cards don't work with the default base
* clock frequency of 200MHz. Lower it to 50Hz.
*/
pcicfgw8(p, 0xfc, 0x01);
pcicfgw8(p, 0xe1, 50);
pcicfgw8(p, 0xfc, 0x00);
}
return 0;
}
static int
pmmcinquiry(char *inquiry, int inqlen)
{
return snprint(inquiry, inqlen, "MMC Host Controller");
}
static void
softreset(Ctlr *c, int all)
{
int i, m;
m = all ? 1 : 6;
CR8(c, Rsrst) = m;
for(i=10; i>=0; i--){
if((CR8(c, Rsrst) & m) == 0)
break;
delay(10);
CR8(c, Rsrst) = 0;
}
if(i < 0) iprint("mmc: didnt reset\n");
}
static void
setpower(Ctlr *c, int on)
{
enum {
Vcap18 = 1<<26, Vset18 = 0x05,
Vcap30 = 1<<25, Vset30 = 0x06,
Vcap33 = 1<<24, Vset33 = 0x07,
};
u32int cap, v;
cap = CR32(c, Rcap);
v = Vset18;
if(cap & Vcap30)
v = Vset30;
if(cap & Vcap33)
v = Vset33;
CR8(c, Rpwr) = on ? ((v<<1) | 1) : 0;
}
static void
setclkfreq(Ctlr *c, int khz)
{
u32int caps, intfreq;
int i, div;
if(khz == 0){
CR16(c, Rclc) |= ~4; /* sd clock disable */
return;
}
caps = CR32(c, Rcap);
intfreq = 1000*((caps >> 8) & 0x3f);
for(div = 1; div <= 256; div <<= 1){
if((intfreq / div) <= khz){
div >>= 1;
break;
}
}
CR16(c, Rclc) = 0;
CR16(c, Rclc) = div<<8;
CR16(c, Rclc) |= 1; /* int clock enable */
for(i=1000; i>=0; i--){
if(CR16(c, Rclc) & 2) /* int clock stable */
break;
delay(10);
}
if(i < 0) iprint("mmc: clock didnt stabilize\n");
CR16(c, Rclc) |= 4; /* sd clock enable */
}
static void
resetctlr(Ctlr *c)
{
u32int m;
ilock(c);
CR16(c, Rnise) = 0; /* interrupts off */
c->change = 0;
c->waitmsk = c->waitsts = 0;
softreset(c, 1);
/* set timeout */
CR8(c, Rtmc) = 0x0e;
m = Srem | Sins | Srrdy | Swrdy | Sdint | Sbge | Strac | Scmdc;
CR16(c, Rnie) = m;
CR16(c, Reie) = Emask;
CR16(c, Rnise) = m;
CR16(c, Reise) = Emask;
setpower(c, 1);
setclkfreq(c, 400);
iunlock(c);
}
static int
waitcond(void *arg)
{
Ctlr *c;
c = arg;
return (c->waitsts & c->waitmsk) != 0;
}
static u32int
intrwait(Ctlr *c, u32int mask, int tmo)
{
u32int status;
ilock(c);
c->waitmsk = Seint | mask;
iunlock(c);
do {
if(!waserror()){
tsleep(&c->r, waitcond, c, 100);
poperror();
}
mmcinterrupt(nil, c);
if(waitcond(c))
break;
tmo -= 100;
} while(tmo > 0);
ilock(c);
c->waitmsk = 0;
status = c->waitsts;
c->waitsts &= ~(status & mask);
if((status & mask) == 0 || (status & Seint) != 0){
/* abort command on timeout/error interrupt */
softreset(c, 0);
c->waitsts = 0;
status = 0;
}
iunlock(c);
return status & mask;
}
static void
pmmcenable(void)
{
Pcidev *p;
Ctlr *c;
c = pmmc;
p = c->pdev;
resetctlr(c);
intrenable(p->intl, mmcinterrupt, c, p->tbdf, "mmc");
}
static int
pmmccmd(u32int cmd, u32int arg, u32int *resp)
{
u32int status;
int i, mode;
Ctlr *c;
if(cmd >= nelem(cmdinfo) || cmdinfo[cmd] == 0)
error(Egreg);
mode = cmdinfo[cmd] >> 8;
cmd = (cmd << 8) | (cmdinfo[cmd] & 0xFF);
c = pmmc;
if(c->change)
resetctlr(c);
if((CR32(c, Rpres) & Pcrdin) == 0)
error("no card");
status = Pinhbc;
if((cmd & Isdata) != 0 || (cmd & Respmask) == Resp48busy)
status |= Pinhbd;
for(i=1000; (CR32(c, Rpres) & status) != 0 && i>=0; i--)
delay(1);
if(i < 0)
error(Eio);
ilock(c);
if((mode & (Mcnt|Mblk)) == (Mcnt|Mblk)){
CR16(c, Rbsize) = c->io.bsize;
CR16(c, Rbcount) = c->io.bcount;
}
CR32(c, Rarg) = arg;
CR16(c, Rmode) = mode;
CR16(c, Rcmd) = cmd;
iunlock(c);
if(!intrwait(c, Scmdc, 1000))
error(Eio);
switch(cmd & Respmask){
case Resp48busy:
resp[0] = CR32(c, Rresp0);
if(!intrwait(c, Strac, 3000))
error(Eio);
break;
case Resp48:
resp[0] = CR32(c, Rresp0);
break;
case Resp136:
resp[0] = CR32(c, Rresp0)<<8;
resp[1] = CR32(c, Rresp0)>>24 | CR32(c, Rresp1)<<8;
resp[2] = CR32(c, Rresp1)>>24 | CR32(c, Rresp2)<<8;
resp[3] = CR32(c, Rresp2)>>24 | CR32(c, Rresp3)<<8;
break;
case Respnone:
resp[0] = 0;
break;
}
cmd >>= 8;
if(cmd == 0x06){ /* buswidth */
switch(arg){
case 0:
CR8(c, Rhc) &= ~2;
break;
case 2:
CR8(c, Rhc) |= 2;
break;
}
}
return 0;
}
static void
pmmciosetup(int write, void *buf, int bsize, int bcount)
{
Ctlr *c;
USED(write);
USED(buf);
if(bsize == 0 || (bsize & 3) != 0)
error(Egreg);
c = pmmc;
c->io.bsize = bsize;
c->io.bcount = bcount;
}
static void
readblock(Ctlr *c, uchar *buf, int len)
{
for(len >>= 2; len > 0; len--){
*((u32int*)buf) = CR32(c, Rdat0);
buf += 4;
}
}
static void
writeblock(Ctlr *c, uchar *buf, int len)
{
for(len >>= 2; len > 0; len--){
CR32(c, Rdat0) = *((u32int*)buf);
buf += 4;
}
}
static void
pmmcio(int write, uchar *buf, int len)
{
Ctlr *c;
int n;
c = pmmc;
if(len != c->io.bsize*c->io.bcount)
error(Egreg);
while(len > 0){
if(!intrwait(c, write ? Swrdy : Srrdy, 3000))
error(Eio);
n = len;
if(n > c->io.bsize)
n = c->io.bsize;
if(write)
writeblock(c, buf, n);
else
readblock(c, buf, n);
len -= n;
buf += n;
}
if(!intrwait(c, Strac, 1000))
error(Eio);
}
SDio sdio = {
"pmmc",
pmmcinit,
pmmcenable,
pmmcinquiry,
pmmccmd,
pmmciosetup,
pmmcio,
};