
The new pci code is moved to port/pci.[hc] and shared by all ports. Each port has its own PCI controller implementation, providing the pcicfgrw*() functions for low level pci config space access. The locking for pcicfgrw*() is now done by the caller (only port/pci.c). Device drivers now need to include "../port/pci.h" in addition to "io.h". The new code now checks bridge windows and membars, while enumerating the bus, giving the pc driver a chance to re-assign them. This is needed because some UEFI implementations fail to assign the bars for some devices, so we need to do it outselfs. (See pcireservemem()). While working on this, it was discovered that the pci code assimed the smallest I/O bar size is 16 (pcibarsize()), which is wrong. I/O bars can be as small as 4 bytes. Bit 1 in an I/O bar is also reserved and should be masked off, making the port mask: port = bar & ~3;
559 lines
9.1 KiB
C
559 lines
9.1 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/pci.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 || (p->mem[0].bar & 1) != 0)
|
|
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,
|
|
};
|