code: plan9front

ref: df04ea8d6c2e1e75307a77f2b086a836f480ab72
dir: /sys/src/9/pc/pmmc.c/

View raw version
/*
 * 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,
};