shithub: riscv

Download patch

ref: b818f48f41895754dd9b470be407ba85cfea45bd
parent: 2d0ede468ddd363243fd31580e3e86c39b51f7be
author: cinap_lenrek <cinap_lenrek@felloff.net>
date: Sun Dec 11 14:08:52 EST 2022

sdmmc: multiple controller support

remove the global sdio struct and add a addmmcio() function
for drivers to register their controllers.

there is a SDio.aux pointer now where controller drivers
can stash a pointer to their private data.

SDio.init() also can signal that it wants to be called
again for more controllers by returning > 0 (see pc/pmmc.c).

this is in preparation for supporting the internal mmc
device in the mnt-reform which is hooked up to usdhc1.

--- a/sys/src/9/bcm/emmc.c
+++ b/sys/src/9/bcm/emmc.c
@@ -240,7 +240,7 @@
 }
 
 static int
-emmcinit(void)
+emmcinit(SDio*)
 {
 	u32int *r;
 	ulong clk;
@@ -265,7 +265,7 @@
 }
 
 static int
-emmcinquiry(char *inquiry, int inqlen)
+emmcinquiry(SDio*, char *inquiry, int inqlen)
 {
 	u32int *r;
 	uint ver;
@@ -278,7 +278,7 @@
 }
 
 static void
-emmcenable(void)
+emmcenable(SDio*)
 {
 	emmcclk(Initfreq);
 	WR(Irpten, 0);
@@ -288,7 +288,7 @@
 }
 
 static int
-emmccmd(u32int cmd, u32int arg, u32int *resp)
+emmccmd(SDio*, u32int cmd, u32int arg, u32int *resp)
 {
 	u32int *r;
 	u32int c;
@@ -435,7 +435,7 @@
 }
 
 static void
-emmciosetup(int write, void *buf, int bsize, int bcount)
+emmciosetup(SDio*, int write, void *buf, int bsize, int bcount)
 {
 	USED(write);
 	USED(buf);
@@ -443,7 +443,7 @@
 }
 
 static void
-emmcio(int write, uchar *buf, int len)
+emmcio(SDio*, int write, uchar *buf, int len)
 {
 	u32int *r;
 	int i;
@@ -497,13 +497,18 @@
 	WR(Irpten, r[Irpten] & ~i);
 }
 
-SDio sdio = {
-	"emmc",
-	emmcinit,
-	emmcenable,
-	emmcinquiry,
-	emmccmd,
-	emmciosetup,
-	emmcio,
-	.highspeed = 1,
-};
+void
+emmclink(void)
+{
+	static SDio io = {
+		"emmc",
+		emmcinit,
+		emmcenable,
+		emmcinquiry,
+		emmccmd,
+		emmciosetup,
+		emmcio,
+		.highspeed = 1,
+	};
+	addmmcio(&io);
+}
--- a/sys/src/9/bcm/pi
+++ b/sys/src/9/bcm/pi
@@ -28,6 +28,7 @@
 	ethermedium
 	loopbackmedium
 	netdevmedium
+	emmc
 
 ip
 	tcp
--- a/sys/src/9/bcm/pi2
+++ b/sys/src/9/bcm/pi2
@@ -28,6 +28,7 @@
 	ethermedium
 	loopbackmedium
 	netdevmedium
+	emmc
 
 ip
 	tcp
--- a/sys/src/9/bcm64/pi3
+++ b/sys/src/9/bcm64/pi3
@@ -29,6 +29,7 @@
 	ethermedium
 	loopbackmedium 
 	netdevmedium
+	emmc
 
 ip
 	tcp
--- a/sys/src/9/bcm64/pi4
+++ b/sys/src/9/bcm64/pi4
@@ -34,6 +34,7 @@
 	ethermedium
 	loopbackmedium
 	netdevmedium
+	sdhc
 
 ip
 	tcp
--- a/sys/src/9/bcm64/sdhc.c
+++ b/sys/src/9/bcm64/sdhc.c
@@ -15,7 +15,7 @@
 #include "io.h"
 #include "../port/sd.h"
 
-#define EMMCREGS	(VIRTIO+0x340000)
+#define SDHCREGS	(VIRTIO+0x340000)
 
 enum {
 	Extfreq		= 100*Mhz,	/* guess external clock frequency if */
@@ -206,7 +206,7 @@
 	uintptr	busdram;
 };
 
-static Ctlr emmc;
+static Ctlr sdhc;
 
 static void mmcinterrupt(Ureg*, void*);
 
@@ -213,7 +213,7 @@
 static void
 WR(int reg, u32int val)
 {
-	u32int *r = (u32int*)EMMCREGS;
+	u32int *r = (u32int*)SDHCREGS;
 
 	if(0)print("WR %2.2ux %ux\n", reg<<2, val);
 	coherence();
@@ -247,7 +247,7 @@
 			p->desc |= len<<OLength | End | Int;
 		else
 			p->desc |= Maxdma<<OLength;
-		p->addr = emmc.busdram + (PADDR(a) - PHYSDRAM);
+		p->addr = sdhc.busdram + (PADDR(a) - PHYSDRAM);
 		a += Maxdma;
 		len -= Maxdma;
 		n--;
@@ -257,15 +257,15 @@
 }
 
 static void
-emmcclk(uint freq)
+sdhcclk(uint freq)
 {
 	u32int *r;
 	uint div;
 	int i;
 
-	r = (u32int*)EMMCREGS;
-	div = emmc.extclk / (freq<<1);
-	if(emmc.extclk / (div<<1) > freq)
+	r = (u32int*)SDHCREGS;
+	div = sdhc.extclk / (freq<<1);
+	if(sdhc.extclk / (div<<1) > freq)
 		div++;
 	WR(Control1, clkdiv(div) |
 		DTO<<Datatoshift | Clkgendiv | Clken | Clkintlen);
@@ -275,7 +275,7 @@
 			break;
 	}
 	if(i == 1000)
-		print("emmc: can't set clock to %ud\n", freq);
+		print("sdhc: can't set clock to %ud\n", freq);
 }
 
 static int
@@ -283,29 +283,29 @@
 {
 	int i;
 
-	u32int *r = (u32int*)EMMCREGS;
+	u32int *r = (u32int*)SDHCREGS;
 	i = r[Interrupt];
 	return i & (Datadone|Err);
 }
 
 static int
-emmcinit(void)
+sdhcinit(SDio *io)
 {
 	u32int *r;
 	ulong clk;
 	char *s;
 
-	emmc.busdram = soc.busdram;
+	sdhc.busdram = soc.busdram;
 	if((s = getconf("*emmc2bus")) != nil)
-		emmc.busdram = strtoull(s, nil, 16);
+		sdhc.busdram = strtoull(s, nil, 16);
 	clk = getclkrate(ClkEmmc2);
 	if(clk == 0){
 		clk = Extfreq;
-		print("emmc: assuming external clock %lud Mhz\n", clk/1000000);
+		print("%s: assuming external clock %lud Mhz\n", io->name, clk/1000000);
 	}
-	emmc.extclk = clk;
-	r = (u32int*)EMMCREGS;
-	if(0)print("emmc control %8.8ux %8.8ux %8.8ux\n",
+	sdhc.extclk = clk;
+	r = (u32int*)SDHCREGS;
+	if(0)print("sdhc control %8.8ux %8.8ux %8.8ux\n",
 		r[Control0], r[Control1], r[Control2]);
 	WR(Control1, Srsthc);
 	delay(10);
@@ -318,12 +318,12 @@
 }
 
 static int
-emmcinquiry(char *inquiry, int inqlen)
+sdhcinquiry(SDio *, char *inquiry, int inqlen)
 {
 	u32int *r;
 	uint ver;
 
-	r = (u32int*)EMMCREGS;
+	r = (u32int*)SDHCREGS;
 	ver = r[Slotisrver] >> 16;
 	return snprint(inquiry, inqlen,
 		"BCM SD Host Controller %2.2x Version %2.2x",
@@ -331,7 +331,7 @@
 }
 
 static void
-emmcenable(void)
+sdhcenable(SDio *io)
 {
 
 	WR(Control0, 0);
@@ -339,15 +339,15 @@
 	WR(Control0, V3_3 | Buspower | Dwidth1 | DmaADMA2);
 	WR(Control1, 0);
 	delay(1);
-	emmcclk(Initfreq);
+	sdhcclk(Initfreq);
 	WR(Irpten, 0);
 	WR(Irptmask, ~(Cardintr|Dmaintr));
 	WR(Interrupt, ~0);
-	intrenable(IRQmmc, mmcinterrupt, nil, BUSUNKNOWN, "sdhc");
+	intrenable(IRQmmc, mmcinterrupt, nil, BUSUNKNOWN, io->name);
 }
 
 static int
-emmccmd(u32int cmd, u32int arg, u32int *resp)
+sdhccmd(SDio*, u32int cmd, u32int arg, u32int *resp)
 {
 	u32int *r;
 	u32int c;
@@ -354,13 +354,13 @@
 	int i;
 	ulong now;
 
-	r = (u32int*)EMMCREGS;
+	r = (u32int*)SDHCREGS;
 	assert(cmd < nelem(cmdinfo) && cmdinfo[cmd] != 0);
 	c = (cmd << Indexshift) | cmdinfo[cmd];
 	/*
 	 * CMD6 may be Setbuswidth or Switchfunc depending on Appcmd prefix
 	 */
-	if(cmd == Switchfunc && !emmc.appcmd)
+	if(cmd == Switchfunc && !sdhc.appcmd)
 		c |= Isdata|Card2host;
 	if(c & Isdata)
 		c |= Dmaen;
@@ -377,10 +377,10 @@
 	 */
 	if(cmd == GoIdle){
 		WR(Control0, r[Control0] & ~(Dwidth4|Hispeed));
-		emmcclk(Initfreq);
+		sdhcclk(Initfreq);
 	}
 	if(r[Status] & Cmdinhibit){
-		print("emmccmd: need to reset Cmdinhibit intr %ux stat %ux\n",
+		print("sdhccmd: need to reset Cmdinhibit intr %ux stat %ux\n",
 			r[Interrupt], r[Status]);
 		WR(Control1, r[Control1] | Srstcmd);
 		while(r[Control1] & Srstcmd)
@@ -390,7 +390,7 @@
 	}
 	if((r[Status] & Datinhibit) &&
 	   ((c & Isdata) || (c & Respmask) == Resp48busy)){
-		print("emmccmd: need to reset Datinhibit intr %ux stat %ux\n",
+		print("sdhccmd: need to reset Datinhibit intr %ux stat %ux\n",
 			r[Interrupt], r[Status]);
 		WR(Control1, r[Control1] | Srstdata);
 		while(r[Control1] & Srstdata)
@@ -401,7 +401,7 @@
 	WR(Arg1, arg);
 	if((i = (r[Interrupt] & ~Cardintr)) != 0){
 		if(i != Cardinsert)
-			print("emmc: before command, intr was %ux\n", i);
+			print("sdhc: before command, intr was %ux\n", i);
 		WR(Interrupt, i);
 	}
 	WR(Cmdtm, c);
@@ -411,7 +411,7 @@
 			break;
 	if((i&(Cmddone|Err)) != Cmddone){
 		if((i&~(Err|Cardintr)) != Ctoerr)
-			print("emmc: cmd %ux arg %ux error intr %ux stat %ux\n", c, arg, i, r[Status]);
+			print("sdhc: cmd %ux arg %ux error intr %ux stat %ux\n", c, arg, i, r[Status]);
 		WR(Interrupt, i);
 		if(r[Status]&Cmdinhibit){
 			WR(Control1, r[Control1]|Srstcmd);
@@ -438,12 +438,12 @@
 	}
 	if((c & Respmask) == Resp48busy){
 		WR(Irpten, r[Irpten]|Datadone|Err);
-		tsleep(&emmc.r, datadone, 0, 3000);
+		tsleep(&sdhc.r, datadone, 0, 3000);
 		i = r[Interrupt];
 		if((i & Datadone) == 0)
-			print("emmcio: no Datadone after CMD%d\n", cmd);
+			print("sdhcio: no Datadone after CMD%d\n", cmd);
 		if(i & Err)
-			print("emmcio: CMD%d error interrupt %ux\n",
+			print("sdhcio: CMD%d error interrupt %ux\n",
 				cmd, r[Interrupt]);
 		WR(Interrupt, i);
 	}
@@ -452,12 +452,12 @@
 	 */
 	if(cmd == MMCSelect){
 		delay(1);
-		emmcclk(SDfreq);
+		sdhcclk(SDfreq);
 		delay(1);
-		emmc.fastclock = 1;
+		sdhc.fastclock = 1;
 	}
 	if(cmd == Setbuswidth){
-		if(emmc.appcmd){
+		if(sdhc.appcmd){
 			/*
 			 * If card bus width changes, change host bus width
 			 */
@@ -475,7 +475,7 @@
 			 */
 			if((arg&0x8000000F) == 0x80000001){
 				delay(1);
-				emmcclk(SDfreqhs);
+				sdhcclk(SDfreqhs);
 				delay(1);
 			}
 		}
@@ -490,12 +490,12 @@
 			break;
 		}
 	}
-	emmc.appcmd = (cmd == Appcmd);
+	sdhc.appcmd = (cmd == Appcmd);
 	return 0;
 }
 
 static void
-emmciosetup(int write, void *buf, int bsize, int bcount)
+sdhciosetup(SDio*, int write, void *buf, int bsize, int bcount)
 {
 	int len;
 
@@ -504,30 +504,30 @@
 	assert((len&3) == 0);
 	assert(bsize <= 2048);
 	WR(Blksizecnt, bcount<<16 | bsize);
-	if(emmc.dma)
-		sdfree(emmc.dma);
-	emmc.dma = dmaalloc(buf, len);
+	if(sdhc.dma)
+		sdfree(sdhc.dma);
+	sdhc.dma = dmaalloc(buf, len);
 	if(write)
 		cachedwbse(buf, len);
 	else
 		cachedwbinvse(buf, len);
-	WR(Dmadesc, emmc.busdram + (PADDR(emmc.dma) - PHYSDRAM));
+	WR(Dmadesc, sdhc.busdram + (PADDR(sdhc.dma) - PHYSDRAM));
 	okay(1);
 }
 
 static void
-emmcio(int write, uchar *buf, int len)
+sdhcio(SDio*, int write, uchar *buf, int len)
 {
 	u32int *r;
 	int i;
 
-	r = (u32int*)EMMCREGS;
+	r = (u32int*)SDHCREGS;
 	if(waserror()){
 		okay(0);
 		nexterror();
 	}
 	WR(Irpten, r[Irpten] | Datadone|Err);
-	tsleep(&emmc.r, datadone, 0, 3000);
+	tsleep(&sdhc.r, datadone, 0, 3000);
 	WR(Irpten, r[Irpten] & ~(Datadone|Err));
 	i = r[Interrupt];
 	if((i & (Datadone|Err)) != Datadone){
@@ -549,20 +549,25 @@
 	u32int *r;
 	int i;
 
-	r = (u32int*)EMMCREGS;
+	r = (u32int*)SDHCREGS;
 	i = r[Interrupt];
 	if(i&(Datadone|Err))
-		wakeup(&emmc.r);
+		wakeup(&sdhc.r);
 	WR(Irpten, r[Irpten] & ~i);
 }
 
-SDio sdio = {
-	"sdhc",
-	emmcinit,
-	emmcenable,
-	emmcinquiry,
-	emmccmd,
-	emmciosetup,
-	emmcio,
-	.highspeed = 1,
-};
+void
+sdhclink(void)
+{
+	static SDio io = {
+		"sdhc",
+		sdhcinit,
+		sdhcenable,
+		sdhcinquiry,
+		sdhccmd,
+		sdhciosetup,
+		sdhcio,
+		.highspeed = 1,
+	};
+	addmmcio(&io);
+}
--- a/sys/src/9/imx8/reform
+++ b/sys/src/9/imx8/reform
@@ -34,6 +34,7 @@
 	i2cimx		devi2c
 	pciimx		pci
 	sai
+	usdhc
 
 ip
 	tcp
--- a/sys/src/9/imx8/usdhc.c
+++ b/sys/src/9/imx8/usdhc.c
@@ -190,26 +190,27 @@
 };
 
 struct Ctlr {
-	Rendez	r;
+	u32int	*regs;
+	int	irq;
+
 	int	fastclock;
 	uint	extclk;
 	int	appcmd;
 	Adma	*dma;
+
+	Rendez	r;
 };
 
-static Ctlr usdhc;
-
 static void usdhcinterrupt(Ureg*, void*);
 
-static u32int *regs = (u32int*)(VIRTIO+0xB50000);	/* USDHC2 */
-#define RR(reg)	(regs[reg])
+#define RR(ctlr, reg)	((ctlr)->regs[reg])
 
 static void
-WR(int reg, u32int val)
+WR(Ctlr *ctlr, int reg, u32int val)
 {
 	if(0)print("WR %2.2ux %ux\n", reg<<2, val);
 	coherence();
-	regs[reg] = val;
+	ctlr->regs[reg] = val;
 }
 
 static Adma*
@@ -237,9 +238,9 @@
 }
 
 static void
-usdhcclk(uint freq)
+usdhcclk(Ctlr *ctlr, uint freq)
 {
-	uint pre_div = 1, post_div = 1, clk = usdhc.extclk;
+	uint pre_div = 1, post_div = 1, clk = ctlr->extclk;
 
 	while(clk / (pre_div * 16) > freq && pre_div < 256)
 		pre_div <<= 1;
@@ -247,23 +248,53 @@
 	while(clk / (pre_div * post_div) > freq && post_div < 16)
 		post_div++;
 
-	WR(Vendorspec, RR(Vendorspec) & ~ClkEn);
-	WR(Control1, (pre_div>>1)<<SDCLKFSshift | (post_div-1)<<DVSshift | DTO<<Datatoshift);
+	WR(ctlr, Vendorspec, RR(ctlr, Vendorspec) & ~ClkEn);
+	WR(ctlr, Control1, (pre_div>>1)<<SDCLKFSshift | (post_div-1)<<DVSshift | DTO<<Datatoshift);
 	delay(10);
-	WR(Vendorspec, RR(Vendorspec) | ClkEn | PerEn);
-	while((RR(Status) & Clkstable) == 0)
+	WR(ctlr, Vendorspec, RR(ctlr, Vendorspec) | ClkEn | PerEn);
+	while((RR(ctlr, Status) & Clkstable) == 0)
 		;
 }
 
 static int
-datadone(void*)
+datadone(void *arg)
 {
-	return RR(Interrupt) & (Datadone|Err);
+	Ctlr *ctlr = arg;
+	return RR(ctlr, Interrupt) & (Datadone|Err);
 }
 
+static void
+usdhcreset(Ctlr *ctlr)
+{
+	if(0)print("usdhc control %8.8ux %8.8ux %8.8ux\n",
+		RR(ctlr, Control0), RR(ctlr, Control1), RR(ctlr, Control2));
+
+	WR(ctlr, Control1, Srsthc);
+	delay(10);
+	while(RR(ctlr, Control1) & Srsthc)
+		;
+	WR(ctlr, Control1, Srstdata);
+	delay(10);
+	WR(ctlr, Control1, 0);
+}
+
 static int
-usdhcinit(void)
+usdhc1init(SDio *)
 {
+	/* TODO */
+	return -1;
+}
+
+static int
+usdhc2init(SDio *io)
+{
+	static Ctlr ctlr[1] = {
+		.regs = (u32int*)(VIRTIO+0xB50000),	/* USDHC2 */
+		.irq = IRQusdhc2,
+	};
+
+	io->aux = ctlr;
+
 	iomuxpad("pad_sd2_clk", "usdhc2_clk", "~LVTTL ~HYS ~PUE ~ODE SLOW 75_OHM");
 	iomuxpad("pad_sd2_cmd", "usdhc2_cmd", "~LVTTL HYS PUE ~ODE SLOW 75_OHM");
 	iomuxpad("pad_sd2_data0", "usdhc2_data0", "~LVTTL HYS PUE ~ODE SLOW 75_OHM");
@@ -277,51 +308,46 @@
 	setclkgate("usdhc2.ipg_clk_perclk", 1);
 	setclkgate("usdhc2.ipg_clk", 1);
 
-	usdhc.extclk = getclkrate("usdhc2.ipg_clk_perclk");
-	if(usdhc.extclk <= 0){
-		print("usdhc: usdhc2.ipg_clk_perclk not enabled\n");
+	ctlr->extclk = getclkrate("usdhc2.ipg_clk_perclk");
+	if(ctlr->extclk <= 0){
+		print("%s: usdhc2.ipg_clk_perclk not enabled\n", io->name);
 		return -1;
 	}
 
-	if(0)print("usdhc control %8.8ux %8.8ux %8.8ux\n",
-		RR(Control0), RR(Control1), RR(Control2));
+	usdhcreset(ctlr);
 
-	WR(Control1, Srsthc);
-	delay(10);
-	while(RR(Control1) & Srsthc)
-		;
-	WR(Control1, Srstdata);
-	delay(10);
-	WR(Control1, 0);
 	return 0;
 }
 
 static int
-usdhcinquiry(char *inquiry, int inqlen)
+usdhcinquiry(SDio*, char *inquiry, int inqlen)
 {
 	return snprint(inquiry, inqlen, "USDHC Host Controller");
 }
 
 static void
-usdhcenable(void)
+usdhcenable(SDio *io)
 {
-	WR(Control0, 0);
+	Ctlr *ctlr = io->aux;
+
+	WR(ctlr, Control0, 0);
 	delay(1);
-	WR(Vendorspec, RR(Vendorspec) & ~Vsel);
-	WR(Control0, LE | Dwidth1 | DmaADMA2);
-	WR(Control1, 0);
+	WR(ctlr, Vendorspec, RR(ctlr, Vendorspec) & ~Vsel);
+	WR(ctlr, Control0, LE | Dwidth1 | DmaADMA2);
+	WR(ctlr, Control1, 0);
 	delay(1);
-	WR(Vendorspec, RR(Vendorspec) | HclkEn | IpgEn);
-	usdhcclk(Initfreq);
-	WR(Irpten, 0);
-	WR(Irptmask, ~(Cardintr|Dmaintr));
-	WR(Interrupt, ~0);
-	intrenable(IRQusdhc2, usdhcinterrupt, nil, BUSUNKNOWN, "usdhc2");
+	WR(ctlr, Vendorspec, RR(ctlr, Vendorspec) | HclkEn | IpgEn);
+	usdhcclk(ctlr, Initfreq);
+	WR(ctlr, Irpten, 0);
+	WR(ctlr, Irptmask, ~(Cardintr|Dmaintr));
+	WR(ctlr, Interrupt, ~0);
+	intrenable(ctlr->irq, usdhcinterrupt, ctlr, BUSUNKNOWN, io->name);
 }
 
 static int
-usdhccmd(u32int cmd, u32int arg, u32int *resp)
+usdhccmd(SDio *io, u32int cmd, u32int arg, u32int *resp)
 {
+	Ctlr *ctlr = io->aux;
 	u32int c;
 	int i;
 	ulong now;
@@ -335,7 +361,7 @@
 	/*
 	 * CMD6 may be Setbuswidth or Switchfunc depending on Appcmd prefix
 	 */
-	if(cmd == Switchfunc && !usdhc.appcmd)
+	if(cmd == Switchfunc && !ctlr->appcmd)
 		c |= Isdata|Card2host;
 	if(c & Isdata)
 		c |= Dmaen;
@@ -344,7 +370,7 @@
 			c |= Host2card;
 		else
 			c |= Card2host;
-		if((RR(Blksizecnt)&0xFFFF0000) != 0x10000)
+		if((RR(ctlr, Blksizecnt)&0xFFFF0000) != 0x10000)
 			c |= Multiblock | Blkcnten;
 	}
 	/*
@@ -351,65 +377,65 @@
 	 * GoIdle indicates new card insertion: reset bus width & speed
 	 */
 	if(cmd == GoIdle){
-		WR(Control0, (RR(Control0) & ~DwidthMask) | Dwidth1);
-		usdhcclk(Initfreq);
+		WR(ctlr, Control0, (RR(ctlr, Control0) & ~DwidthMask) | Dwidth1);
+		usdhcclk(ctlr, Initfreq);
 	}
-	if(RR(Status) & Cmdinhibit){
+	if(RR(ctlr, Status) & Cmdinhibit){
 		print("usdhccmd: need to reset Cmdinhibit intr %ux stat %ux\n",
-			RR(Interrupt), RR(Status));
-		WR(Control1, RR(Control1) | Srstcmd);
-		while(RR(Control1) & Srstcmd)
+			RR(ctlr, Interrupt), RR(ctlr, Status));
+		WR(ctlr, Control1, RR(ctlr, Control1) | Srstcmd);
+		while(RR(ctlr, Control1) & Srstcmd)
 			;
-		while(RR(Status) & Cmdinhibit)
+		while(RR(ctlr, Status) & Cmdinhibit)
 			;
 	}
-	if((RR(Status) & Datinhibit) &&
+	if((RR(ctlr, Status) & Datinhibit) &&
 	   ((c & Isdata) || (c & Respmask) == Resp48busy)){
 		print("usdhccmd: need to reset Datinhibit intr %ux stat %ux\n",
-			RR(Interrupt), RR(Status));
-		WR(Control1, RR(Control1) | Srstdata);
-		while(RR(Control1) & Srstdata)
+			RR(ctlr, Interrupt), RR(ctlr, Status));
+		WR(ctlr, Control1, RR(ctlr, Control1) | Srstdata);
+		while(RR(ctlr, Control1) & Srstdata)
 			;
-		while(RR(Status) & Datinhibit)
+		while(RR(ctlr, Status) & Datinhibit)
 			;
 	}
-	while(RR(Status) & Datactive)
+	while(RR(ctlr, Status) & Datactive)
 		;
-	WR(Arg1, arg);
-	if((i = (RR(Interrupt) & ~Cardintr)) != 0){
+	WR(ctlr, Arg1, arg);
+	if((i = (RR(ctlr, Interrupt) & ~Cardintr)) != 0){
 		if(i != Cardinsert)
-			print("usdhc: before command, intr was %ux\n", i);
-		WR(Interrupt, i);
+			print("usdhccmd: before command, intr was %ux\n", i);
+		WR(ctlr, Interrupt, i);
 	}
-	WR(Mixctrl, (RR(Mixctrl) & ~MixCmdMask) | (c & MixCmdMask));
-	WR(Cmdtm, c & ~0xFFFF);
+	WR(ctlr, Mixctrl, (RR(ctlr, Mixctrl) & ~MixCmdMask) | (c & MixCmdMask));
+	WR(ctlr, Cmdtm, c & ~0xFFFF);
 
 	now = MACHP(0)->ticks;
-	while(((i=RR(Interrupt))&(Cmddone|Err)) == 0)
+	while(((i=RR(ctlr, Interrupt))&(Cmddone|Err)) == 0)
 		if(MACHP(0)->ticks - now > HZ)
 			break;
 	if((i&(Cmddone|Err)) != Cmddone){
 		if((i&~(Err|Cardintr)) != Ctoerr)
-			print("usdhc: cmd %ux arg %ux error intr %ux stat %ux\n", c, arg, i, RR(Status));
-		WR(Interrupt, i);
-		if(RR(Status)&Cmdinhibit){
-			WR(Control1, RR(Control1)|Srstcmd);
-			while(RR(Control1)&Srstcmd)
+			print("usdhccmd: cmd %ux arg %ux error intr %ux stat %ux\n", c, arg, i, RR(ctlr, Status));
+		WR(ctlr, Interrupt, i);
+		if(RR(ctlr, Status)&Cmdinhibit){
+			WR(ctlr, Control1, RR(ctlr, Control1)|Srstcmd);
+			while(RR(ctlr, Control1)&Srstcmd)
 				;
 		}
 		error(Eio);
 	}
-	WR(Interrupt, i & ~(Datadone|Readrdy|Writerdy));
+	WR(ctlr, Interrupt, i & ~(Datadone|Readrdy|Writerdy));
 	switch(c & Respmask){
 	case Resp136:
-		resp[0] = RR(Resp0)<<8;
-		resp[1] = RR(Resp0)>>24 | RR(Resp1)<<8;
-		resp[2] = RR(Resp1)>>24 | RR(Resp2)<<8;
-		resp[3] = RR(Resp2)>>24 | RR(Resp3)<<8;
+		resp[0] = RR(ctlr, Resp0)<<8;
+		resp[1] = RR(ctlr, Resp0)>>24 | RR(ctlr, Resp1)<<8;
+		resp[2] = RR(ctlr, Resp1)>>24 | RR(ctlr, Resp2)<<8;
+		resp[3] = RR(ctlr, Resp2)>>24 | RR(ctlr, Resp3)<<8;
 		break;
 	case Resp48:
 	case Resp48busy:
-		resp[0] = RR(Resp0);
+		resp[0] = RR(ctlr, Resp0);
 		break;
 	case Respnone:
 		resp[0] = 0;
@@ -416,34 +442,34 @@
 		break;
 	}
 	if((c & Respmask) == Resp48busy){
-		WR(Irpten, RR(Irpten)|Datadone|Err);
-		tsleep(&usdhc.r, datadone, 0, 1000);
-		i = RR(Interrupt);
+		WR(ctlr, Irpten, RR(ctlr, Irpten)|Datadone|Err);
+		tsleep(&ctlr->r, datadone, ctlr, 1000);
+		i = RR(ctlr, Interrupt);
 		if((i & Datadone) == 0)
 			print("usdhcio: no Datadone in %x after CMD%d\n", i, cmd);
 		if(i & Err)
 			print("usdhcio: CMD%d error interrupt %ux\n",
-				cmd, RR(Interrupt));
-		if(i != 0) WR(Interrupt, i);
+				cmd, RR(ctlr, Interrupt));
+		if(i != 0) WR(ctlr, Interrupt, i);
 	}
 	/*
 	 * Once card is selected, use faster clock
 	 */
 	if(cmd == MMCSelect){
-		usdhcclk(SDfreq);
-		usdhc.fastclock = 1;
+		usdhcclk(ctlr, SDfreq);
+		ctlr->fastclock = 1;
 	}
 	if(cmd == Setbuswidth){
-		if(usdhc.appcmd){
+		if(ctlr->appcmd){
 			/*
 			 * If card bus width changes, change host bus width
 			 */
 			switch(arg){
 			case 0:
-				WR(Control0, (RR(Control0) & ~DwidthMask) | Dwidth1);
+				WR(ctlr, Control0, (RR(ctlr, Control0) & ~DwidthMask) | Dwidth1);
 				break;
 			case 2:
-				WR(Control0, (RR(Control0) & ~DwidthMask) | Dwidth4);
+				WR(ctlr, Control0, (RR(ctlr, Control0) & ~DwidthMask) | Dwidth4);
 				break;
 			}
 		} else {
@@ -452,7 +478,7 @@
 			 */
 			if((arg&0x8000000F) == 0x80000001){
 				delay(1);
-				usdhcclk(SDfreqhs);
+				usdhcclk(ctlr, SDfreqhs);
 				delay(1);
 			}
 		}
@@ -459,74 +485,95 @@
 	}else if(cmd == IORWdirect && (arg & ~0xFF) == (1<<31|0<<28|7<<9)){
 		switch(arg & 0x3){
 		case 0:
-			WR(Control0, (RR(Control0) & ~DwidthMask) | Dwidth1);
+			WR(ctlr, Control0, (RR(ctlr, Control0) & ~DwidthMask) | Dwidth1);
 			break;
 		case 2:
-			WR(Control0, (RR(Control0) & ~DwidthMask) | Dwidth4);
+			WR(ctlr, Control0, (RR(ctlr, Control0) & ~DwidthMask) | Dwidth4);
 			break;
 		}
 	}
-	usdhc.appcmd = (cmd == Appcmd);
+	ctlr->appcmd = (cmd == Appcmd);
 	return 0;
 }
 
 static void
-usdhciosetup(int write, void *buf, int bsize, int bcount)
+usdhciosetup(SDio *io, int write, void *buf, int bsize, int bcount)
 {
+	Ctlr *ctlr = io->aux;
 	int len = bsize * bcount;
 	assert(((uintptr)buf&3) == 0);
 	assert((len&3) == 0);
 	assert(bsize <= 2048);
-	WR(Blksizecnt, bcount<<16 | bsize);
-	if(usdhc.dma)
-		sdfree(usdhc.dma);
-	usdhc.dma = dmaalloc(buf, len);
+	WR(ctlr, Blksizecnt, bcount<<16 | bsize);
+	if(ctlr->dma)
+		sdfree(ctlr->dma);
+	ctlr->dma = dmaalloc(buf, len);
 	if(write)
 		cachedwbse(buf, len);
 	else
 		cachedwbinvse(buf, len);
-	WR(Dmadesc, PADDR(usdhc.dma));
+	WR(ctlr, Dmadesc, PADDR(ctlr->dma));
 }
 
 static void
-usdhcio(int write, uchar *buf, int len)
+usdhcio(SDio *io, int write, uchar *buf, int len)
 {
+	Ctlr *ctlr = io->aux;
 	u32int i;
 
-	WR(Irpten, RR(Irpten) | Datadone|Err);
-	tsleep(&usdhc.r, datadone, 0, 3000);
-	WR(Irpten, RR(Irpten) & ~(Datadone|Err));
-	i = RR(Interrupt);
+	WR(ctlr, Irpten, RR(ctlr, Irpten) | Datadone|Err);
+	tsleep(&ctlr->r, datadone, ctlr, 3000);
+	WR(ctlr, Irpten, RR(ctlr, Irpten) & ~(Datadone|Err));
+	i = RR(ctlr, Interrupt);
 	if((i & (Datadone|Err)) != Datadone){
-		print("sdhc: %s error intr %ux stat %ux\n",
-			write? "write" : "read", i, RR(Status));
-		WR(Interrupt, i);
+		print("%s: %s error intr %ux stat %ux\n", io->name,
+			write? "write" : "read", i, RR(ctlr, Status));
+		WR(ctlr, Interrupt, i);
 		error(Eio);
 	}
-	WR(Interrupt, i);
+	WR(ctlr, Interrupt, i);
 	if(!write)
 		cachedinvse(buf, len);
 }
 
 static void
-usdhcinterrupt(Ureg*, void*)
-{	
+usdhcinterrupt(Ureg*, void *arg)
+{
+	Ctlr *ctlr = arg;
 	u32int i;
 
-	i = RR(Interrupt);
+	i = RR(ctlr, Interrupt);
 	if(i&(Datadone|Err))
-		wakeup(&usdhc.r);
-	WR(Irpten, RR(Irpten) & ~i);
+		wakeup(&ctlr->r);
+	WR(ctlr, Irpten, RR(ctlr, Irpten) & ~i);
 }
 
-SDio sdio = {
-	"usdhc",
-	usdhcinit,
-	usdhcenable,
-	usdhcinquiry,
-	usdhccmd,
-	usdhciosetup,
-	usdhcio,
-	.highspeed = 1,
-	.nomultiwrite = 1,
-};
+void
+usdhclink(void)
+{
+	static SDio usdhc1 = {
+		"usdhc1",
+		usdhc1init,
+		usdhcenable,
+		usdhcinquiry,
+		usdhccmd,
+		usdhciosetup,
+		usdhcio,
+		.highspeed = 1,
+		.nomultiwrite = 1,
+	};
+	static SDio usdhc2 = {
+		"usdhc2",
+		usdhc2init,
+		usdhcenable,
+		usdhcinquiry,
+		usdhccmd,
+		usdhciosetup,
+		usdhcio,
+		.highspeed = 1,
+		.nomultiwrite = 1,
+	};
+
+	addmmcio(&usdhc1);
+	addmmcio(&usdhc2);
+}
--- a/sys/src/9/pc/pc
+++ b/sys/src/9/pc/pc
@@ -93,6 +93,8 @@
 	audioac97	pci audioac97mix
 	audiohda	pci
 
+	pmmc		pci
+
 misc
 	pci		pcipc
 
--- a/sys/src/9/pc/pmmc.c
+++ b/sys/src/9/pc/pmmc.c
@@ -181,8 +181,6 @@
 	} 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))
@@ -216,12 +214,14 @@
 }
 
 static int
-pmmcinit(void)
+pmmcinit(SDio *io)
 {
-	Pcidev *p;
+	static Pcidev *p;
+	Ctlr *c;
 
-	p = nil;
 	while((p = pcimatch(p, 0, 0)) != nil){
+		if(p->mem[0].size < 256 || (p->mem[0].bar & 1) != 0)
+			continue;
 		if(p->ccrb == 8 && p->ccru == 5)
 			break;
 		if(p->vid == 0x1180){	/* Ricoh */
@@ -231,15 +231,19 @@
 				break;
 		}
 	}
-
-	if(p == nil || p->mem[0].size < 256 || (p->mem[0].bar & 1) != 0)
+	if(p == nil)
 		return -1;
-
-	pmmc->mmio = vmap(p->mem[0].bar & ~0x0F, p->mem[0].size);
-	if(pmmc->mmio == nil)
+	c = malloc(sizeof(Ctlr));
+	if(c == nil)
 		return -1;
+	c->mmio = vmap(p->mem[0].bar & ~0x0F, p->mem[0].size);
+	if(c->mmio == nil){
+		free(c);
+		return -1;
+	}
+	c->pdev = p;
+	io->aux = c;
 
-	pmmc->pdev = p;
 	pcienable(p);
 
 	if(p->did == 0x1180 && p->vid == 0xe823){	/* Ricoh */
@@ -257,11 +261,12 @@
 		pcicfgw8(p, 0xfc, 0x00);
 	}
 
-	return 0;
+	/* probe again for next device */
+	return 1;
 }
 
 static int
-pmmcinquiry(char *inquiry, int inqlen)
+pmmcinquiry(SDio*, char *inquiry, int inqlen)
 {
 	return snprint(inquiry, inqlen, "MMC Host Controller");
 }
@@ -361,9 +366,7 @@
 static int
 waitcond(void *arg)
 {
-	Ctlr *c;
-
-	c = arg;
+	Ctlr *c = arg;
 	return (c->waitsts & c->waitmsk) != 0;
 }
 
@@ -402,23 +405,21 @@
 
 
 static void
-pmmcenable(void)
+pmmcenable(SDio *io)
 {
-	Pcidev *p;
-	Ctlr *c;
+	Ctlr *c = io->aux;
+	Pcidev *p = c->pdev;
 
-	c = pmmc;
-	p = c->pdev;
 	resetctlr(c);
-	intrenable(p->intl, mmcinterrupt, c, p->tbdf, "mmc");
+	intrenable(p->intl, mmcinterrupt, c, p->tbdf, io->name);
 }
 
 static int
-pmmccmd(u32int cmd, u32int arg, u32int *resp)
+pmmccmd(SDio *io, u32int cmd, u32int arg, u32int *resp)
 {
+	Ctlr *c = io->aux;
 	u32int status;
 	int i, mode;
-	Ctlr *c;
 
 	if(cmd >= nelem(cmdinfo) || cmdinfo[cmd] == 0)
 		error(Egreg);
@@ -425,8 +426,6 @@
 	mode = cmdinfo[cmd] >> 8;
 	cmd = (cmd << 8) | (cmdinfo[cmd] & 0xFF);
 
-	c = pmmc;
-
 	if(c->change)
 		resetctlr(c);
 	if((CR32(c, Rpres) & Pcrdin) == 0)
@@ -489,9 +488,9 @@
 }
 
 static void
-pmmciosetup(int write, void *buf, int bsize, int bcount)
+pmmciosetup(SDio *io, int write, void *buf, int bsize, int bcount)
 {
-	Ctlr *c;
+	Ctlr *c = io->aux;
 
 	USED(write);
 	USED(buf);
@@ -499,7 +498,6 @@
 	if(bsize == 0 || (bsize & 3) != 0)
 		error(Egreg);
 
-	c = pmmc;
 	c->io.bsize = bsize;
 	c->io.bcount = bcount;
 }
@@ -523,12 +521,11 @@
 }
 
 static void
-pmmcio(int write, uchar *buf, int len)
+pmmcio(SDio *io, int write, uchar *buf, int len)
 {
-	Ctlr *c;
+	Ctlr *c = io->aux;
 	int n;
 
-	c = pmmc;
 	if(len != c->io.bsize*c->io.bcount)
 		error(Egreg);
 	while(len > 0){
@@ -548,12 +545,17 @@
 		error(Eio);
 }
 
-SDio sdio = {
-	"pmmc",
-	pmmcinit,
-	pmmcenable,
-	pmmcinquiry,
-	pmmccmd,
-	pmmciosetup,
-	pmmcio,
-};
+void
+pmmclink(void)
+{
+	static SDio io = {
+		"pmmc",
+		pmmcinit,
+		pmmcenable,
+		pmmcinquiry,
+		pmmccmd,
+		pmmciosetup,
+		pmmcio,
+	};
+	addmmcio(&io);
+}
--- a/sys/src/9/pc64/pc64
+++ b/sys/src/9/pc64/pc64
@@ -91,6 +91,8 @@
 #	audioac97	pci audioac97mix
 	audiohda	pci
 
+	pmmc		pci
+
 misc
 	pci		pcipc
 	archgeneric	devkbd i8259 i8253
--- a/sys/src/9/port/sd.h
+++ b/sys/src/9/port/sd.h
@@ -155,17 +155,18 @@
 
 struct SDio {
 	char	*name;
-	int	(*init)(void);
-	void	(*enable)(void);
-	int	(*inquiry)(char*, int);
-	int	(*cmd)(u32int, u32int, u32int*);
-	void	(*iosetup)(int, void*, int, int);
-	void	(*io)(int, uchar*, int);
+	int	(*init)(SDio*);
+	void	(*enable)(SDio*);
+	int	(*inquiry)(SDio*, char*, int);
+	int	(*cmd)(SDio*, u32int, u32int, u32int*);
+	void	(*iosetup)(SDio*, int, void*, int, int);
+	void	(*io)(SDio*, int, uchar*, int);
 	char	highspeed;
 	char	nomultiwrite;	/* quirk for usdhc */
+	void	*aux;
 };
 
-extern SDio sdio;
+extern void addmmcio(SDio *io);
 
 /* devsd.c */
 extern void sdadddevs(SDev*);
--- a/sys/src/9/port/sdmmc.c
+++ b/sys/src/9/port/sdmmc.c
@@ -3,7 +3,6 @@
  *
  * Copyright © 2012 Richard Miller <r.miller@acm.org>
  *
- * Assumes only one card on the bus
  */
 
 #include "u.h"
@@ -74,6 +73,7 @@
 struct Ctlr {
 	SDev	*dev;
 	SDio	*io;
+
 	/* SD card registers */
 	u16int	rca;
 	u32int	ocr;
@@ -84,8 +84,79 @@
 };
 
 extern SDifc sdmmcifc;
-extern SDio sdio;
 
+static SDio *sdio[8];
+static int nsdio, isdio;
+
+void
+addmmcio(SDio *io)
+{
+	assert(io != nil);
+	assert(isdio == 0);
+	if(nsdio >= nelem(sdio)){
+		print("addmmcio: out of slots for %s\n", io->name);
+		return;
+	}
+	sdio[nsdio++] = io;
+}
+
+static SDev*
+init1(void)
+{
+	SDev *sdev;
+	Ctlr *ctlr;
+	SDio *io;
+	int more;
+
+	if(isdio >= nsdio)
+		return nil;
+	if((sdev = malloc(sizeof(SDev))) == nil)
+		return nil;
+	if((ctlr = malloc(sizeof(Ctlr))) == nil){
+		free(sdev);
+		return nil;
+	}
+	if((io = malloc(sizeof(SDio))) == nil){
+		free(ctlr);
+		free(sdev);
+		return nil;
+	}
+Next:
+	memmove(io, sdio[isdio++], sizeof(SDio));
+	if(io->init != nil){
+		more = (*io->init)(io);
+		if(more < 0){
+			if(isdio < nsdio)
+				goto Next;
+
+			free(io);
+			free(ctlr);
+			free(sdev);
+			return nil;
+		}
+		if(more > 0)
+			isdio--;	/* try again */
+	}
+	sdev->idno = 'M';
+	sdev->ifc = &sdmmcifc;
+	sdev->nunit = 1;
+	sdev->ctlr = ctlr;
+	ctlr->dev = sdev;
+	ctlr->io = io;
+	return sdev;
+}
+
+static SDev*
+mmcpnp(void)
+{
+	SDev *list = nil, **link = &list;
+
+	while((*link = init1()) != nil)
+		link = &(*link)->next;
+
+	return list;
+}
+
 static uint
 rbits(u32int *p, uint start, uint len)
 {
@@ -126,39 +197,14 @@
 	}
 }
 
-static SDev*
-mmcpnp(void)
-{
-	SDev *sdev;
-	Ctlr *ctl;
-
-	if(sdio.init() < 0)
-		return nil;
-	sdev = malloc(sizeof(SDev));
-	if(sdev == nil)
-		return nil;
-	ctl = malloc(sizeof(Ctlr));
-	if(ctl == nil){
-		free(sdev);
-		return nil;
-	}
-	sdev->idno = 'M';
-	sdev->ifc = &sdmmcifc;
-	sdev->nunit = 1;
-	sdev->ctlr = ctl;
-	ctl->dev = sdev;
-	ctl->io = &sdio;
-	return sdev;
-}
-
 static int
 mmcverify(SDunit *unit)
 {
+	Ctlr *ctlr = unit->dev->ctlr;
+	SDio *io = ctlr->io;
 	int n;
-	Ctlr *ctl;
 
-	ctl = unit->dev->ctlr;
-	n = ctl->io->inquiry((char*)&unit->inquiry[8], sizeof(unit->inquiry)-8);
+	n = (*io->inquiry)(io, (char*)&unit->inquiry[8], sizeof(unit->inquiry)-8);
 	if(n < 0)
 		return 0;
 	unit->inquiry[0] = 0x00;	/* direct access (disk) */
@@ -170,10 +216,9 @@
 static int
 mmcenable(SDev* dev)
 {
-	Ctlr *ctl;
-
-	ctl = dev->ctlr;
-	ctl->io->enable();
+	Ctlr *ctlr = dev->ctlr;
+	SDio *io = ctlr->io;
+	(*io->enable)(io);
 	return 1;
 }
 
@@ -180,35 +225,35 @@
 static void
 mmcswitchfunc(SDio *io, int arg)
 {
+	u32int r[4];
 	uchar *buf;
 	int n;
-	u32int r[4];
 
 	n = Funcbytes;
 	buf = sdmalloc(n);
 	if(waserror()){
-		print("mmcswitchfunc error\n");
+		print("%s: mmcswitchfunc error\n", io->name);
 		sdfree(buf);
 		nexterror();
 	}
-	io->iosetup(0, buf, n, 1);
-	io->cmd(SWITCH_FUNC, arg, r);
-	io->io(0, buf, n);
+	(*io->iosetup)(io, 0, buf, n, 1);
+	(*io->cmd)(io, SWITCH_FUNC, arg, r);
+	(*io->io)(io, 0, buf, n);
 	sdfree(buf);
 	poperror();
 }
 
 static int
-cardinit(Ctlr *ctl)
+cardinit(Ctlr *ctlr)
 {
+	SDio *io = ctlr->io;
 	u32int r[4];
 	int hcs, i;
-	SDio *io = ctl->io;
 
-	io->cmd(GO_IDLE_STATE, 0, r);
+	(*io->cmd)(io, GO_IDLE_STATE, 0, r);
 	hcs = 0;
 	if(!waserror()){
-		io->cmd(SD_SEND_IF_COND, Voltage|Checkpattern, r);
+		(*io->cmd)(io, SD_SEND_IF_COND, Voltage|Checkpattern, r);
 		if(r[0] == (Voltage|Checkpattern))	/* SD 2.0 or above */
 			hcs = Hcs;
 		poperror();
@@ -215,20 +260,20 @@
 	}
 	for(i = 0; i < Inittimeout; i++){
 		tsleep(&up->sleep, return0, nil, 100);
-		io->cmd(APP_CMD, 0, r);
-		io->cmd(SD_SEND_OP_COND, hcs|V3_3, r);
+		(*io->cmd)(io, APP_CMD, 0, r);
+		(*io->cmd)(io, SD_SEND_OP_COND, hcs|V3_3, r);
 		if(r[0] & Powerup)
 			break;
 	}
 	if(i == Inittimeout)
 		return 2;
-	ctl->ocr = r[0];
-	io->cmd(ALL_SEND_CID, 0, r);
-	memmove(ctl->cid, r, sizeof ctl->cid);
-	io->cmd(SEND_RELATIVE_ADDR, 0, r);
-	ctl->rca = r[0]>>16;
-	io->cmd(SEND_CSD, ctl->rca<<Rcashift, r);
-	memmove(ctl->csd, r, sizeof ctl->csd);
+	ctlr->ocr = r[0];
+	(*io->cmd)(io, ALL_SEND_CID, 0, r);
+	memmove(ctlr->cid, r, sizeof ctlr->cid);
+	(*io->cmd)(io, SEND_RELATIVE_ADDR, 0, r);
+	ctlr->rca = r[0]>>16;
+	(*io->cmd)(io, SEND_CSD, ctlr->rca<<Rcashift, r);
+	memmove(ctlr->csd, r, sizeof ctlr->csd);
 	return 1;
 }
 
@@ -235,15 +280,15 @@
 static void
 retryproc(void *arg)
 {
-	Ctlr *ctl = arg;
+	Ctlr *ctlr = arg;
 	int i = 0;
 
 	while(waserror())
 		;
-	if(i++ < ctl->retry)
-		cardinit(ctl);
+	if(i++ < ctlr->retry)
+		cardinit(ctlr);
 	USED(i);
-	ctl->retry = 0;
+	ctlr->retry = 0;
 	pexit("", 1);
 }
 
@@ -250,36 +295,33 @@
 static int
 mmconline(SDunit *unit)
 {
+	Ctlr *ctlr = unit->dev->ctlr;
+	SDio *io = ctlr->io;
 	u32int r[4];
-	Ctlr *ctl;
-	SDio *io;
 
 	assert(unit->subno == 0);
-	ctl = unit->dev->ctlr;
-	io = ctl->io;
-
-	if(ctl->retry)
+	if(ctlr->retry)
 		return 0;
 	if(waserror()){
 		unit->sectors = 0;
-		if(ctl->retry++ == 0)
-			kproc(unit->name, retryproc, ctl);
+		if(ctlr->retry++ == 0)
+			kproc(unit->name, retryproc, ctlr);
 		return 0;
 	}
 	if(unit->sectors != 0){
-		io->cmd(SEND_STATUS, ctl->rca<<Rcashift, r);
+		(*io->cmd)(io, SEND_STATUS, ctlr->rca<<Rcashift, r);
 		poperror();
 		return 1;
 	}
-	if(cardinit(ctl) != 1){
+	if(cardinit(ctlr) != 1){
 		poperror();
 		return 2;
 	}
-	identify(unit, ctl->csd);
-	io->cmd(SELECT_CARD, ctl->rca<<Rcashift, r);
-	io->cmd(SET_BLOCKLEN, unit->secsize, r);
-	io->cmd(APP_CMD, ctl->rca<<Rcashift, r);
-	io->cmd(SET_BUS_WIDTH, Width4, r);
+	identify(unit, ctlr->csd);
+	(*io->cmd)(io, SELECT_CARD, ctlr->rca<<Rcashift, r);
+	(*io->cmd)(io, SET_BLOCKLEN, unit->secsize, r);
+	(*io->cmd)(io, APP_CMD, ctlr->rca<<Rcashift, r);
+	(*io->cmd)(io, SET_BUS_WIDTH, Width4, r);
 	if(io->highspeed){
 		if(!waserror()){
 			mmcswitchfunc(io, Hispeed|Setfunc);
@@ -293,7 +335,7 @@
 static int
 mmcrctl(SDunit *unit, char *p, int l)
 {
-	Ctlr *ctl;
+	Ctlr *ctlr = unit->dev->ctlr;
 	int i, n;
 
 	assert(unit->subno == 0);
@@ -302,13 +344,12 @@
 		if(unit->sectors == 0)
 			return 0;
 	}
-	ctl = unit->dev->ctlr;
-	n = snprint(p, l, "rca %4.4ux ocr %8.8ux\ncid ", ctl->rca, ctl->ocr);
-	for(i = nelem(ctl->cid)-1; i >= 0; i--)
-		n += snprint(p+n, l-n, "%8.8ux", ctl->cid[i]);
+	n = snprint(p, l, "rca %4.4ux ocr %8.8ux\ncid ", ctlr->rca, ctlr->ocr);
+	for(i = nelem(ctlr->cid)-1; i >= 0; i--)
+		n += snprint(p+n, l-n, "%8.8ux", ctlr->cid[i]);
 	n += snprint(p+n, l-n, " csd ");
-	for(i = nelem(ctl->csd)-1; i >= 0; i--)
-		n += snprint(p+n, l-n, "%8.8ux", ctl->csd[i]);
+	for(i = nelem(ctlr->csd)-1; i >= 0; i--)
+		n += snprint(p+n, l-n, "%8.8ux", ctlr->csd[i]);
 	n += snprint(p+n, l-n, "\ngeometry %llud %ld\n",
 		unit->sectors, unit->secsize);
 	return n;
@@ -317,16 +358,14 @@
 static long
 mmcbio(SDunit *unit, int lun, int write, void *data, long nb, uvlong bno)
 {
+	Ctlr *ctlr = unit->dev->ctlr;
+	SDio *io = ctlr->io;
 	int len, tries;
-	ulong b;
 	u32int r[4];
 	uchar *buf;
-	Ctlr *ctl;
-	SDio *io;
+	ulong b;
 
 	USED(lun);
-	ctl = unit->dev->ctlr;
-	io = ctl->io;
 	assert(unit->subno == 0);
 	if(unit->sectors == 0)
 		error(Echange);
@@ -338,24 +377,24 @@
 		while(waserror())
 			if(++tries == 3)
 				nexterror();
-		io->iosetup(write, buf, len, nb);
+		(*io->iosetup)(io, write, buf, len, nb);
 		if(waserror()){
-			io->cmd(STOP_TRANSMISSION, 0, r);
+			(*io->cmd)(io, STOP_TRANSMISSION, 0, r);
 			nexterror();
 		}
-		io->cmd(write? WRITE_MULTIPLE_BLOCK: READ_MULTIPLE_BLOCK,
-			ctl->ocr & Ccs? b: b * len, r);
-		io->io(write, buf, nb * len);
+		(*io->cmd)(io, write? WRITE_MULTIPLE_BLOCK: READ_MULTIPLE_BLOCK,
+			ctlr->ocr & Ccs? b: b * len, r);
+		(*io->io)(io, write, buf, nb * len);
 		poperror();
-		io->cmd(STOP_TRANSMISSION, 0, r);
+		(*io->cmd)(io, STOP_TRANSMISSION, 0, r);
 		poperror();
 		b += nb;
 	}else{
 		for(b = bno; b < bno + nb; b++){
-			io->iosetup(write, buf, len, 1);
-			io->cmd(write? WRITE_BLOCK : READ_SINGLE_BLOCK,
-				ctl->ocr & Ccs? b: b * len, r);
-			io->io(write, buf, len);
+			(*io->iosetup)(io, write, buf, len, 1);
+			(*io->cmd)(io, write? WRITE_BLOCK : READ_SINGLE_BLOCK,
+				ctlr->ocr & Ccs? b: b * len, r);
+			(*io->io)(io, write, buf, len);
 			buf += len;
 		}
 	}
--- a/sys/src/9/zynq/emmc.c
+++ b/sys/src/9/zynq/emmc.c
@@ -178,7 +178,7 @@
 }
 
 static int
-emmcinit(void)
+emmcinit(SDio*)
 {
 	u32int *r;
 	int i;
@@ -198,7 +198,7 @@
 }
 
 static int
-emmcinquiry(char *inquiry, int inqlen)
+emmcinquiry(SDio*, char *inquiry, int inqlen)
 {
 	uint ver;
 
@@ -209,7 +209,7 @@
 }
 
 static void
-emmcenable(void)
+emmcenable(SDio *io)
 {
 	int i;
 
@@ -223,11 +223,11 @@
 	if(i == 1000)
 		print("SD clock won't initialise!\n");
 	emmc.regs[Irptmask] = ~(Dtoerr|Cardintr|Dmaintr);
-	intrenable(emmc.irq, interrupt, nil, LEVEL, sdio.name);
+	intrenable(emmc.irq, interrupt, nil, LEVEL, io->name);
 }
 
 static int
-emmccmd(u32int cmd, u32int arg, u32int *resp)
+emmccmd(SDio*, u32int cmd, u32int arg, u32int *resp)
 {
 	ulong now;
 	u32int *r;
@@ -339,7 +339,7 @@
 }
 
 static void
-emmciosetup(int, void *buf, int bsize, int bcount)
+emmciosetup(SDio*, int, void *buf, int bsize, int bcount)
 {
 	u32int *r;
 	uintptr pa;
@@ -360,7 +360,7 @@
 }
 
 static void
-emmcio(int write, uchar *buf, int len)
+emmcio(SDio*, int write, uchar *buf, int len)
 {
 	u32int *r;
 	int i;
@@ -393,12 +393,17 @@
 	}
 }
 
-SDio sdio = {
-	"emmc",
-	emmcinit,
-	emmcenable,
-	emmcinquiry,
-	emmccmd,
-	emmciosetup,
-	emmcio,
-};
+void
+emmclink(void)
+{
+	static SDio io = {
+		"emmc",
+		emmcinit,
+		emmcenable,
+		emmcinquiry,
+		emmccmd,
+		emmciosetup,
+		emmcio,
+	};
+	addmmcio(&io);
+}
--- a/sys/src/9/zynq/zynq
+++ b/sys/src/9/zynq/zynq
@@ -30,6 +30,7 @@
 	loopbackmedium
 	netdevmedium
 	usbehci usbehcizynq
+	emmc
 
 misc
 	uartzynq