code: plan9front

Download patch

ref: a44a54bf2f64b69b2289d658fd3c205491c7ac5d
parent: 7f3498b6e12123e8b963c9263651149b81a1a2a1
author: cinap_lenrek <cinap_lenrek@felloff.net>
date: Tue Dec 27 14:34:58 EST 2022

sdmmc: new interface for SDio

We want to support the internal emmc device on the reform,
which has a different initialization sequence from SDcard.

The problem is, all mmc controller drivers where using
the command index to derive the command properties.
The command index's interpretation depends on the
command set. And MMC has different commands then SD.
Also, there are the APP_CMD escaped commands which
drivers then tried to recover the state...

All of this is total nonsense... The controller drivers
should not care and the command properties should
be maintained by port/sdmmc.c.

So we pass the command as a struct SDiocmd, which
has all the properties (command index, response type,
data transfer mode... and even a string for debugging).

The controller just converts this into register values
and just executes the commands.

Next, the controller drivers shouldnt snoop on the
commands and then try to apply bus and frequency
switching on their own. This is now made explicit by
having SDio.bus(io, width, speed) function.

--- a/sys/src/9/bcm/emmc.c
+++ b/sys/src/9/bcm/emmc.c
@@ -26,20 +26,6 @@
 enum {
 	Extfreq		= 100*Mhz,	/* guess external clock frequency if */
 					/* not available from vcore */
-	Initfreq	= 400000,	/* initialisation frequency for MMC */
-	SDfreq		= 25*Mhz,	/* standard SD frequency */
-	SDfreqhs	= 50*Mhz,	/* high speed frequency */
-	DTO		= 14,		/* data timeout exponent (guesswork) */
-
-	GoIdle		= 0,		/* mmc/sdio go idle state */
-	MMCSelect	= 7,		/* mmc/sd card select command */
-	Setbuswidth	= 6,		/* mmc/sd set bus width command */
-
-	Switchfunc	= 6,		/* mmc/sd switch function command */
-	Voltageswitch	= 11,		/* md/sdio switch to 1.8V */
-	IORWdirect	= 52,		/* sdio read/write direct command */
-	IORWextended	= 53,		/* sdio read/write extended command */
-	Appcmd		= 55,		/* mmc/sd application command prefix */
 };
 
 enum {
@@ -75,6 +61,7 @@
 	Hispeed			= 1<<2,	
 	Dwidth4			= 1<<1,
 	Dwidth1			= 0<<1,
+	DwidthMask		= Dwidth4,
 
 	/* Control1 */
 	Srstdata		= 1<<26,	/* reset data circuit */
@@ -81,6 +68,7 @@
 	Srstcmd			= 1<<25,	/* reset command circuit */
 	Srsthc			= 1<<24,	/* reset complete host controller */
 	Datatoshift		= 16,		/* data timeout unit exponent */
+		DTO		= 14,		/* data timeout exponent (guesswork) */
 	Datatomask		= 0xF0000,
 	Clkfreq8shift		= 8,		/* SD clock base divider LSBs */
 	Clkfreq8mask		= 0xFF00,
@@ -139,41 +127,16 @@
 	Cmdinhibit	= 1<<0,
 };
 
-static int cmdinfo[64] = {
-[0]  Ixchken,
-[2]  Resp136,
-[3]  Resp48 | Ixchken | Crcchken,
-[5]  Resp48,
-[6]  Resp48 | Ixchken | Crcchken,
-[7]  Resp48busy | Ixchken | Crcchken,
-[8]  Resp48 | Ixchken | Crcchken,
-[9]  Resp136,
-[11] Resp48 | Ixchken | Crcchken,
-[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,
-[52] Resp48 | Ixchken | Crcchken,
-[53] Resp48 | Ixchken | Crcchken | Isdata,
-[55] Resp48 | Ixchken | Crcchken,
-};
-
 typedef struct Ctlr Ctlr;
-
 struct Ctlr {
 	Rendez	r;
 	int	fastclock;
 	ulong	extclk;
-	int	appcmd;
 };
 
 static Ctlr emmc;
 
-static void mmcinterrupt(Ureg*, void*);
+static void emmcinterrupt(Ureg*, void*);
 
 static void
 WR(int reg, u32int val)
@@ -200,16 +163,16 @@
 static void
 emmcclk(uint freq)
 {
-	u32int *r;
+	u32int *r = (u32int*)EMMCREGS;
 	uint div;
 	int i;
 
-	r = (u32int*)EMMCREGS;
 	div = emmc.extclk / (freq<<1);
 	if(emmc.extclk / (div<<1) > freq)
 		div++;
 	WR(Control1, clkdiv(div) |
 		DTO<<Datatoshift | Clkgendiv | Clken | Clkintlen);
+	emmc.fastclock = freq > 400000;
 	for(i = 0; i < 1000; i++){
 		delay(1);
 		if(r[Control1] & Clkstable)
@@ -219,12 +182,30 @@
 		print("emmc: can't set clock to %ud\n", freq);
 }
 
+static void
+emmcbus(SDio*, int width, int speed)
+{
+	u32int *r = (u32int*)EMMCREGS;
+
+	switch(width){
+	case 1:
+		WR(Control0, (r[Control0] & ~DwidthMask) | Dwidth1);
+		break;
+	case 4:
+		WR(Control0, (r[Control0] & ~DwidthMask) | Dwidth4);
+		break;
+	}
+
+	if(speed)
+		emmcclk(speed);
+}
+
 static int
 datadone(void*)
 {
+	u32int *r = (u32int*)EMMCREGS;
 	int i;
 
-	u32int *r = (u32int*)EMMCREGS;
 	i = r[Interrupt];
 	return i & (Datadone|Err);
 }
@@ -232,9 +213,9 @@
 static int
 cardintready(void*)
 {
+	u32int *r = (u32int*)EMMCREGS;
 	int i;
 
-	u32int *r = (u32int*)EMMCREGS;
 	i = r[Interrupt];
 	return i & Cardintr;
 }
@@ -242,7 +223,7 @@
 static int
 emmcinit(SDio*)
 {
-	u32int *r;
+	u32int *r = (u32int*)EMMCREGS;
 	ulong clk;
 
 	clk = getclkrate(ClkEmmc);
@@ -251,7 +232,6 @@
 		print("emmc: assuming external clock %lud Mhz\n", clk/1000000);
 	}
 	emmc.extclk = clk;
-	r = (u32int*)EMMCREGS;
 	if(0)print("emmc control %8.8ux %8.8ux %8.8ux\n",
 		r[Control0], r[Control1], r[Control2]);
 	WR(Control1, Srsthc);
@@ -267,10 +247,9 @@
 static int
 emmcinquiry(SDio*, char *inquiry, int inqlen)
 {
-	u32int *r;
+	u32int *r = (u32int*)EMMCREGS;
 	uint ver;
 
-	r = (u32int*)EMMCREGS;
 	ver = r[Slotisrver] >> 16;
 	return snprint(inquiry, inqlen,
 		"Arasan eMMC SD Host Controller %2.2x Version %2.2x",
@@ -278,46 +257,52 @@
 }
 
 static void
-emmcenable(SDio*)
+emmcenable(SDio *io)
 {
-	emmcclk(Initfreq);
+	emmcclk(400000);
 	WR(Irpten, 0);
 	WR(Irptmask, ~0);
 	WR(Interrupt, ~0);
-	intrenable(IRQmmc, mmcinterrupt, nil, BUSUNKNOWN, "mmc");
+	intrenable(IRQmmc, emmcinterrupt, nil, BUSUNKNOWN, io->name);
 }
 
 static int
-emmccmd(SDio*, u32int cmd, u32int arg, u32int *resp)
+emmccmd(SDio*, SDiocmd *cmd, u32int arg, u32int *resp)
 {
-	u32int *r;
+	u32int *r = (u32int*)EMMCREGS;
 	u32int c;
 	int i;
 	ulong now;
 
-	r = (u32int*)EMMCREGS;
-	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)
-		c |= Isdata|Card2host;
-	if(cmd == IORWextended){
-		if(arg & (1<<31))
-			c |= Host2card;
+	c = (u32int)cmd->index << Indexshift;
+	switch(cmd->resp){
+	case 0:
+		c |= Respnone;
+		break;
+	case 1:
+		if(cmd->busy){
+			c |= Resp48busy | Ixchken | Crcchken;
+			break;
+		}
+	default:
+		c |= Resp48 | Ixchken | Crcchken;
+		break;
+	case 2:
+		c |= Resp136 | Crcchken;
+		break;
+	case 3:
+		c |= Resp48;
+		break;
+	}
+	if(cmd->data){
+		if(cmd->data & 1)
+			c |= Isdata | Card2host;
 		else
-			c |= Card2host;
-		if((r[Blksizecnt]&0xFFFF0000) != 0x10000)
+			c |= Isdata | Host2card;
+		if(cmd->data > 2)
 			c |= Multiblock | Blkcnten;
 	}
-	/*
-	 * GoIdle indicates new card insertion: reset bus width & speed
-	 */
-	if(cmd == GoIdle){
-		WR(Control0, r[Control0] & ~(Dwidth4|Hispeed));
-		emmcclk(Initfreq);
-	}
+
 	if((r[Status] & Datinhibit) &&
 	   ((c & Isdata) || (c & Respmask) == Resp48busy)){
 		print("emmccmd: need to reset Cmdinhibit intr %ux stat %ux\n",
@@ -351,7 +336,8 @@
 			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("emmc: %s cmd %ux arg %ux error intr %ux stat %ux\n",
+				cmd->name, c, arg, i, r[Status]);
 		WR(Interrupt, i);
 		if(r[Status]&Cmdinhibit){
 			WR(Control1, r[Control1]|Srstcmd);
@@ -381,56 +367,12 @@
 		tsleep(&emmc.r, datadone, 0, 3000);
 		i = r[Interrupt];
 		if((i & Datadone) == 0)
-			print("emmcio: no Datadone after CMD%d\n", cmd);
+			print("emmcio: no Datadone after %s\n", cmd->name);
 		if(i & Err)
-			print("emmcio: CMD%d error interrupt %ux\n",
-				cmd, r[Interrupt]);
+			print("emmcio: %s error interrupt %ux\n",
+				cmd->name, r[Interrupt]);
 		WR(Interrupt, i);
 	}
-	/*
-	 * Once card is selected, use faster clock
-	 */
-	if(cmd == MMCSelect){
-		delay(1);
-		emmcclk(SDfreq);
-		delay(1);
-		emmc.fastclock = 1;
-	}
-	if(cmd == Setbuswidth){
-		if(emmc.appcmd){
-			/*
-			 * If card bus width changes, change host bus width
-			 */
-			switch(arg){
-			case 0:
-				WR(Control0, r[Control0] & ~Dwidth4);
-				break;
-			case 2:
-				WR(Control0, r[Control0] | Dwidth4);
-				break;
-			}
-		}else{
-			/*
-			 * If card switched into high speed mode, increase clock speed
-			 */
-			if((arg&0x8000000F) == 0x80000001){
-				delay(1);
-				emmcclk(SDfreqhs);
-				delay(1);
-			}
-		}
-	}else if(cmd == IORWdirect && (arg & ~0xFF) == (1<<31|0<<28|7<<9)){
-		switch(arg & 0x3){
-		case 0:
-			WR(Control0, r[Control0] & ~Dwidth4);
-			break;
-		case 2:
-			WR(Control0, r[Control0] | Dwidth4);
-			//WR(Control0, r[Control0] | Hispeed);
-			break;
-		}
-	}
-	emmc.appcmd = (cmd == Appcmd);
 	return 0;
 }
 
@@ -445,10 +387,9 @@
 static void
 emmcio(SDio*, int write, uchar *buf, int len)
 {
-	u32int *r;
+	u32int *r = (u32int*)EMMCREGS;
 	int i;
 
-	r = (u32int*)EMMCREGS;
 	assert((len&3) == 0);
 	okay(1);
 	if(waserror()){
@@ -485,12 +426,11 @@
 }
 
 static void
-mmcinterrupt(Ureg*, void*)
+emmcinterrupt(Ureg*, void*)
 {	
-	u32int *r;
+	u32int *r = (u32int*)EMMCREGS;
 	int i;
 
-	r = (u32int*)EMMCREGS;
 	i = r[Interrupt];
 	if(i&(Datadone|Err))
 		wakeup(&emmc.r);
@@ -508,7 +448,7 @@
 		emmccmd,
 		emmciosetup,
 		emmcio,
-		.highspeed = 1,
+		emmcbus,
 	};
 	addmmcio(&io);
 }
--- a/sys/src/9/bcm64/sdhc.c
+++ b/sys/src/9/bcm64/sdhc.c
@@ -19,20 +19,6 @@
 
 enum {
 	Extfreq		= 100*Mhz,	/* guess external clock frequency if */
-					/* not available from vcore */
-	Initfreq	= 400000,	/* initialisation frequency for MMC */
-	SDfreq		= 25*Mhz,	/* standard SD frequency */
-	SDfreqhs	= 50*Mhz,	/* high speed frequency */
-	DTO		= 14,		/* data timeout exponent (guesswork) */
-
-	GoIdle		= 0,		/* mmc/sdio go idle state */
-	MMCSelect	= 7,		/* mmc/sd card select command */
-	Setbuswidth	= 6,		/* mmc/sd set bus width command */
-	Switchfunc	= 6,		/* mmc/sd switch function command */
-	Voltageswitch = 11,		/* md/sdio switch to 1.8V */
-	IORWdirect = 52,		/* sdio read/write direct command */
-	IORWextended = 53,		/* sdio read/write extended command */
-	Appcmd = 55,			/* mmc/sd application command prefix */
 };
 
 enum {
@@ -80,6 +66,7 @@
 	Hispeed			= 1<<2,
 	Dwidth4			= 1<<1,
 	Dwidth1			= 0<<1,
+	DwidthMask		= Dwidth4|Dwidth8,
 	LED			= 1<<0,
 
 	/* Control1 */
@@ -87,6 +74,7 @@
 	Srstcmd			= 1<<25,	/* reset command circuit */
 	Srsthc			= 1<<24,	/* reset complete host controller */
 	Datatoshift		= 16,		/* data timeout unit exponent */
+		DTO		= 14,		/* data timeout exponent (guesswork) */
 	Datatomask		= 0xF0000,
 	Clkfreq8shift		= 8,		/* SD clock base divider LSBs */
 	Clkfreq8mask		= 0xFF00,
@@ -148,37 +136,12 @@
 	Cmdinhibit	= 1<<0,
 };
 
-static int cmdinfo[64] = {
-[0]  Ixchken,
-[2]  Resp136,
-[3]  Resp48 | Ixchken | Crcchken,
-[5]  Resp48,
-[6]  Resp48 | Ixchken | Crcchken,
-[7]  Resp48busy | Ixchken | Crcchken,
-[8]  Resp48 | Ixchken | Crcchken,
-[9]  Resp136,
-[11] Resp48 | Ixchken | Crcchken,
-[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,
-[52] Resp48 | Ixchken | Crcchken,
-[53] Resp48	| Ixchken | Crcchken | Isdata,
-[55] Resp48 | Ixchken | Crcchken,
-};
-
-typedef struct Adma Adma;
-typedef struct Ctlr Ctlr;
-
 /*
  * ADMA2 descriptor
  *	See SD Host Controller Simplified Specification Version 2.00
  */
 
+typedef struct Adma Adma;
 struct Adma {
 	u32int	desc;
 	u32int	addr;
@@ -197,11 +160,10 @@
 	Maxdma		= ((1<<16) - 4),
 };
 
+typedef struct Ctlr Ctlr;
 struct Ctlr {
 	Rendez	r;
-	int	fastclock;
 	ulong	extclk;
-	int	appcmd;
 	Adma	*dma;
 	uintptr	busdram;
 };
@@ -208,7 +170,7 @@
 
 static Ctlr sdhc;
 
-static void mmcinterrupt(Ureg*, void*);
+static void sdhcinterrupt(Ureg*, void*);
 
 static void
 WR(int reg, u32int val)
@@ -259,11 +221,10 @@
 static void
 sdhcclk(uint freq)
 {
-	u32int *r;
+	u32int *r = (u32int*)SDHCREGS;
 	uint div;
 	int i;
 
-	r = (u32int*)SDHCREGS;
 	div = sdhc.extclk / (freq<<1);
 	if(sdhc.extclk / (div<<1) > freq)
 		div++;
@@ -278,12 +239,33 @@
 		print("sdhc: can't set clock to %ud\n", freq);
 }
 
+static void
+sdhcbus(SDio*, int width, int speed)
+{
+	u32int *r = (u32int*)SDHCREGS;
+
+	switch(width){
+	case 1:
+		WR(Control0, (r[Control0] & ~DwidthMask) | Dwidth1);
+		break;
+	case 4:
+		WR(Control0, (r[Control0] & ~DwidthMask) | Dwidth4);
+		break;
+	case 8:
+		WR(Control0, (r[Control0] & ~DwidthMask) | Dwidth8);
+		break;
+	}
+
+	if(speed)
+		sdhcclk(speed);
+}
+
 static int
 datadone(void*)
 {
+	u32int *r = (u32int*)SDHCREGS;
 	int i;
 
-	u32int *r = (u32int*)SDHCREGS;
 	i = r[Interrupt];
 	return i & (Datadone|Err);
 }
@@ -291,7 +273,7 @@
 static int
 sdhcinit(SDio *io)
 {
-	u32int *r;
+	u32int *r = (u32int*)SDHCREGS;
 	ulong clk;
 	char *s;
 
@@ -304,7 +286,6 @@
 		print("%s: assuming external clock %lud Mhz\n", io->name, clk/1000000);
 	}
 	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);
@@ -320,10 +301,9 @@
 static int
 sdhcinquiry(SDio *, char *inquiry, int inqlen)
 {
-	u32int *r;
+	u32int *r = (u32int*)SDHCREGS;
 	uint ver;
 
-	r = (u32int*)SDHCREGS;
 	ver = r[Slotisrver] >> 16;
 	return snprint(inquiry, inqlen,
 		"BCM SD Host Controller %2.2x Version %2.2x",
@@ -333,52 +313,55 @@
 static void
 sdhcenable(SDio *io)
 {
-
 	WR(Control0, 0);
 	delay(1);
 	WR(Control0, V3_3 | Buspower | Dwidth1 | DmaADMA2);
 	WR(Control1, 0);
 	delay(1);
-	sdhcclk(Initfreq);
+	sdhcclk(400000);
 	WR(Irpten, 0);
 	WR(Irptmask, ~(Cardintr|Dmaintr));
 	WR(Interrupt, ~0);
-	intrenable(IRQmmc, mmcinterrupt, nil, BUSUNKNOWN, io->name);
+	intrenable(IRQmmc, sdhcinterrupt, nil, BUSUNKNOWN, io->name);
 }
 
 static int
-sdhccmd(SDio*, u32int cmd, u32int arg, u32int *resp)
+sdhccmd(SDio*, SDiocmd *cmd, u32int arg, u32int *resp)
 {
-	u32int *r;
+	u32int *r = (u32int*)SDHCREGS;
 	u32int c;
 	int i;
 	ulong now;
 
-	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 && !sdhc.appcmd)
-		c |= Isdata|Card2host;
-	if(c & Isdata)
-		c |= Dmaen;
-	if(cmd == IORWextended){
-		if(arg & (1<<31))
-			c |= Host2card;
+	c = (u32int)cmd->index << Indexshift;
+	switch(cmd->resp){
+	case 0:
+		c |= Respnone;
+		break;
+	case 1:
+		if(cmd->busy){
+			c |= Resp48busy | Ixchken | Crcchken;
+			break;
+		}
+	default:
+		c |= Resp48 | Ixchken | Crcchken;
+		break;
+	case 2:
+		c |= Resp136 | Crcchken;
+		break;
+	case 3:
+		c |= Resp48;
+		break;
+	}
+	if(cmd->data){
+		if(cmd->data & 1)
+			c |= Isdata | Card2host | Dmaen;
 		else
-			c |= Card2host;
-		if((r[Blksizecnt]&0xFFFF0000) != 0x10000)
+			c |= Isdata | Host2card | Dmaen;
+		if(cmd->data > 2)
 			c |= Multiblock | Blkcnten;
 	}
-	/*
-	 * GoIdle indicates new card insertion: reset bus width & speed
-	 */
-	if(cmd == GoIdle){
-		WR(Control0, r[Control0] & ~(Dwidth4|Hispeed));
-		sdhcclk(Initfreq);
-	}
+
 	if(r[Status] & Cmdinhibit){
 		print("sdhccmd: need to reset Cmdinhibit intr %ux stat %ux\n",
 			r[Interrupt], r[Status]);
@@ -411,7 +394,8 @@
 			break;
 	if((i&(Cmddone|Err)) != Cmddone){
 		if((i&~(Err|Cardintr)) != Ctoerr)
-			print("sdhc: cmd %ux arg %ux error intr %ux stat %ux\n", c, arg, i, r[Status]);
+			print("sdhc: %s cmd %ux arg %ux error intr %ux stat %ux\n",
+				cmd->name, c, arg, i, r[Status]);
 		WR(Interrupt, i);
 		if(r[Status]&Cmdinhibit){
 			WR(Control1, r[Control1]|Srstcmd);
@@ -441,56 +425,12 @@
 		tsleep(&sdhc.r, datadone, 0, 3000);
 		i = r[Interrupt];
 		if((i & Datadone) == 0)
-			print("sdhcio: no Datadone after CMD%d\n", cmd);
+			print("sdhcio: no Datadone after %s\n", cmd->name);
 		if(i & Err)
-			print("sdhcio: CMD%d error interrupt %ux\n",
-				cmd, r[Interrupt]);
+			print("sdhcio: %s error interrupt %ux\n",
+				cmd->name, r[Interrupt]);
 		WR(Interrupt, i);
 	}
-	/*
-	 * Once card is selected, use faster clock
-	 */
-	if(cmd == MMCSelect){
-		delay(1);
-		sdhcclk(SDfreq);
-		delay(1);
-		sdhc.fastclock = 1;
-	}
-	if(cmd == Setbuswidth){
-		if(sdhc.appcmd){
-			/*
-			 * If card bus width changes, change host bus width
-			 */
-			switch(arg){
-			case 0:
-				WR(Control0, r[Control0] & ~Dwidth4);
-				break;
-			case 2:
-				WR(Control0, r[Control0] | Dwidth4);
-				break;
-			}
-		}else{
-			/*
-			 * If card switched into high speed mode, increase clock speed
-			 */
-			if((arg&0x8000000F) == 0x80000001){
-				delay(1);
-				sdhcclk(SDfreqhs);
-				delay(1);
-			}
-		}
-	}else if(cmd == IORWdirect && (arg & ~0xFF) == (1<<31|0<<28|7<<9)){
-		switch(arg & 0x3){
-		case 0:
-			WR(Control0, r[Control0] & ~Dwidth4);
-			break;
-		case 2:
-			WR(Control0, r[Control0] | Dwidth4);
-			//WR(Control0, r[Control0] | Hispeed);
-			break;
-		}
-	}
-	sdhc.appcmd = (cmd == Appcmd);
 	return 0;
 }
 
@@ -518,10 +458,9 @@
 static void
 sdhcio(SDio*, int write, uchar *buf, int len)
 {
-	u32int *r;
+	u32int *r = (u32int*)SDHCREGS;
 	int i;
 
-	r = (u32int*)SDHCREGS;
 	if(waserror()){
 		okay(0);
 		nexterror();
@@ -544,12 +483,11 @@
 }
 
 static void
-mmcinterrupt(Ureg*, void*)
+sdhcinterrupt(Ureg*, void*)
 {	
-	u32int *r;
+	u32int *r = (u32int*)SDHCREGS;
 	int i;
 
-	r = (u32int*)SDHCREGS;
 	i = r[Interrupt];
 	if(i&(Datadone|Err))
 		wakeup(&sdhc.r);
@@ -567,7 +505,7 @@
 		sdhccmd,
 		sdhciosetup,
 		sdhcio,
-		.highspeed = 1,
+		sdhcbus,
 	};
 	addmmcio(&io);
 }
--- a/sys/src/9/imx8/reform
+++ b/sys/src/9/imx8/reform
@@ -52,8 +52,8 @@
 	lcd
 	uartimx
 	iomux
-	sdmmc	usdhc
 	sdnvme	pci
+	sdmmc	usdhc
 port
 	int cpuserver = 0;
 bootdir
--- a/sys/src/9/imx8/usdhc.c
+++ b/sys/src/9/imx8/usdhc.c
@@ -8,22 +8,6 @@
 #include "../port/sd.h"
 
 enum {
-	Initfreq	= 400000,	/* initialisation frequency for MMC */
-	SDfreq		= 25*Mhz,	/* standard SD frequency */
-	SDfreqhs	= 50*Mhz,	/* highspeed frequency */
-	DTO		= 14,		/* data timeout exponent (guesswork) */
-
-	GoIdle		= 0,		/* mmc/sdio go idle state */
-	MMCSelect	= 7,		/* mmc/sd card select command */
-	Setbuswidth	= 6,		/* mmc/sd set bus width command */
-	Switchfunc	= 6,		/* mmc/sd switch function command */
-	Voltageswitch	= 11,		/* md/sdio switch to 1.8V */
-	IORWdirect	= 52,		/* sdio read/write direct command */
-	IORWextended	= 53,		/* sdio read/write extended command */
-	Appcmd		= 55,		/* mmc/sd application command prefix */
-};
-
-enum {
 	/* Controller registers */
 	SDMAaddr		= 0x00>>2,
 	Blksizecnt		= 0x04>>2,
@@ -81,6 +65,7 @@
 	Srsthc			= 1<<24,	/* reset complete host controller */
 	Datatoshift		= 16,		/* data timeout unit exponent */
 	Datatomask		= 0xF0000,
+	DTO			= 14,		/* data timeout exponent (guesswork) */
 	SDCLKFSshift		= 8,
 	DVSshift		= 4,
 
@@ -142,35 +127,11 @@
 	Cmdinhibit	= 1<<0,
 };
 
-static int cmdinfo[64] = {
-[0]  Ixchken,
-[2]  Resp136,
-[3]  Resp48 | Ixchken | Crcchken,
-[5]  Resp48,
-[6]  Resp48 | Ixchken | Crcchken,
-[7]  Resp48 | Ixchken | Crcchken,
-[8]  Resp48 | Ixchken | Crcchken,
-[9]  Resp136,
-[11] Resp48 | Ixchken | Crcchken,
-[13] Resp48 | Ixchken | Crcchken,
-[16] Resp48,
-[17] Resp48 | Isdata | Card2host | Ixchken | Crcchken,
-[18] Resp48 | Isdata | Card2host | Multiblock | Blkcnten | Ixchken | Crcchken | Autocmd12,
-[24] Resp48 | Isdata | Host2card | Ixchken | Crcchken,
-[25] Resp48 | Isdata | Host2card | Multiblock | Blkcnten | Ixchken | Crcchken | Autocmd12,
-[41] Resp48,
-[52] Resp48 | Ixchken | Crcchken,
-[53] Resp48 | Ixchken | Crcchken | Isdata,
-[55] Resp48 | Ixchken | Crcchken,
-};
-
-typedef struct Adma Adma;
-typedef struct Ctlr Ctlr;
-
 /*
  * ADMA2 descriptor
  *	See SD Host Controller Simplified Specification Version 2.00
  */
+typedef struct Adma Adma;
 struct Adma {
 	u32int	desc;
 	u32int	addr;
@@ -189,13 +150,12 @@
 	Maxdma		= 1<<12,
 };
 
+typedef struct Ctlr Ctlr;
 struct Ctlr {
 	u32int	*regs;
 	int	irq;
 
-	int	fastclock;
 	uint	extclk;
-	int	appcmd;
 	Adma	*dma;
 
 	Rendez	r;
@@ -256,6 +216,27 @@
 		;
 }
 
+static void
+usdhcbus(SDio *io, int width, int speed)
+{
+	Ctlr *ctlr = io->aux;
+
+	switch(width){
+	case 1:
+		WR(ctlr, Control0, (RR(ctlr, Control0) & ~DwidthMask) | Dwidth1);
+		break;
+	case 4:
+		WR(ctlr, Control0, (RR(ctlr, Control0) & ~DwidthMask) | Dwidth4);
+		break;
+	case 8:
+		WR(ctlr, Control0, (RR(ctlr, Control0) & ~DwidthMask) | Dwidth8);
+		break;
+	}
+
+	if(speed)
+		usdhcclk(ctlr, speed);
+}
+
 static int
 datadone(void *arg)
 {
@@ -279,10 +260,49 @@
 }
 
 static int
-usdhc1init(SDio *)
+usdhc1init(SDio *io)
 {
-	/* TODO */
-	return -1;
+	static Ctlr ctlr[1] = {
+		.regs = (u32int*)(VIRTIO+0xB40000),	/* USDHC1 */
+		.irq = IRQusdhc1,
+	};
+
+	io->aux = ctlr;
+
+	iomuxpad("pad_sd1_reset_b", "gpio2_io10", "~LVTTL ~HYS PUE ~ODE SLOW 255_OHM");
+
+	/* assert reset */
+	gpioout(GPIO_PIN(2, 10), 0);
+
+	iomuxpad("pad_sd1_clk", "usdhc1_clk", "~LVTTL HYS PUE ~ODE FAST 45_OHM");
+	iomuxpad("pad_sd1_cmd", "usdhc1_cmd", "~LVTTL HYS PUE ~ODE FAST 45_OHM");
+	iomuxpad("pad_sd1_data0", "usdhc1_data0", "~LVTTL HYS PUE ~ODE FAST 45_OHM");
+	iomuxpad("pad_sd1_data1", "usdhc1_data1", "~LVTTL HYS PUE ~ODE FAST 45_OHM");
+	iomuxpad("pad_sd1_data2", "usdhc1_data2", "~LVTTL HYS PUE ~ODE FAST 45_OHM");
+	iomuxpad("pad_sd1_data3", "usdhc1_data3", "~LVTTL HYS PUE ~ODE FAST 45_OHM");
+	iomuxpad("pad_sd1_data4", "usdhc1_data4", "~LVTTL HYS PUE ~ODE FAST 45_OHM");
+	iomuxpad("pad_sd1_data5", "usdhc1_data5", "~LVTTL HYS PUE ~ODE FAST 45_OHM");
+	iomuxpad("pad_sd1_data6", "usdhc1_data6", "~LVTTL HYS PUE ~ODE FAST 45_OHM");
+	iomuxpad("pad_sd1_data7", "usdhc1_data7", "~LVTTL HYS PUE ~ODE FAST 45_OHM");
+
+	setclkgate("usdhc1.ipg_clk", 0);
+	setclkgate("usdhc1.ipg_clk_perclk", 0);
+	setclkrate("usdhc1.ipg_clk_perclk", "system_pll1_clk", 200*Mhz);
+	setclkgate("usdhc1.ipg_clk_perclk", 1);
+	setclkgate("usdhc1.ipg_clk", 1);
+
+	ctlr->extclk = getclkrate("usdhc1.ipg_clk_perclk");
+	if(ctlr->extclk <= 0){
+		print("%s: usdhc1.ipg_clk_perclk not enabled\n", io->name);
+		return -1;
+	}
+
+	/* release reset */
+	gpioout(GPIO_PIN(2, 10), 1);
+
+	usdhcreset(ctlr);
+
+	return 0;
 }
 
 static int
@@ -337,7 +357,7 @@
 	WR(ctlr, Control1, 0);
 	delay(1);
 	WR(ctlr, Vendorspec, RR(ctlr, Vendorspec) | HclkEn | IpgEn);
-	usdhcclk(ctlr, Initfreq);
+	usdhcclk(ctlr, 400000);
 	WR(ctlr, Irpten, 0);
 	WR(ctlr, Irptmask, ~(Cardintr|Dmaintr));
 	WR(ctlr, Interrupt, ~0);
@@ -344,8 +364,10 @@
 	intrenable(ctlr->irq, usdhcinterrupt, ctlr, BUSUNKNOWN, io->name);
 }
 
+extern SDiocmd STOP_TRANSMISSION;
+
 static int
-usdhccmd(SDio *io, u32int cmd, u32int arg, u32int *resp)
+usdhccmd(SDio *io, SDiocmd *cmd, u32int arg, u32int *resp)
 {
 	Ctlr *ctlr = io->aux;
 	u32int c;
@@ -353,33 +375,38 @@
 	ulong now;
 
 	/* using Autocmd12 */
-	if(cmd == 12)
+	if(cmd == &STOP_TRANSMISSION)
 		return 0;
 
-	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 && !ctlr->appcmd)
-		c |= Isdata|Card2host;
-	if(c & Isdata)
-		c |= Dmaen;
-	if(cmd == IORWextended){
-		if(arg & (1<<31))
-			c |= Host2card;
+	c = (u32int)cmd->index << Indexshift;
+	switch(cmd->resp){
+	case 0:
+		c |= Respnone;
+		break;
+	case 1:
+		if(cmd->busy){
+			c |= Resp48busy | Ixchken | Crcchken;
+			break;
+		}
+	default:
+		c |= Resp48 | Ixchken | Crcchken;
+		break;
+	case 2:
+		c |= Resp136 | Crcchken;
+		break;
+	case 3:
+		c |= Resp48;
+		break;
+	}
+	if(cmd->data){
+		if(cmd->data & 1)
+			c |= Isdata | Card2host | Dmaen;
 		else
-			c |= Card2host;
-		if((RR(ctlr, Blksizecnt)&0xFFFF0000) != 0x10000)
-			c |= Multiblock | Blkcnten;
+			c |= Isdata | Host2card | Dmaen;
+		if(cmd->data > 2)
+			c |= Multiblock | Blkcnten | Autocmd12;
 	}
-	/*
-	 * GoIdle indicates new card insertion: reset bus width & speed
-	 */
-	if(cmd == GoIdle){
-		WR(ctlr, Control0, (RR(ctlr, Control0) & ~DwidthMask) | Dwidth1);
-		usdhcclk(ctlr, Initfreq);
-	}
+
 	if(RR(ctlr, Status) & Cmdinhibit){
 		print("usdhccmd: need to reset Cmdinhibit intr %ux stat %ux\n",
 			RR(ctlr, Interrupt), RR(ctlr, Status));
@@ -416,7 +443,8 @@
 			break;
 	if((i&(Cmddone|Err)) != Cmddone){
 		if((i&~(Err|Cardintr)) != Ctoerr)
-			print("usdhccmd: cmd %ux arg %ux error intr %ux stat %ux\n", c, arg, i, RR(ctlr, Status));
+			print("usdhccmd: %s cmd %ux arg %ux error intr %ux stat %ux\n",
+				cmd->name, c, arg, i, RR(ctlr, Status));
 		WR(ctlr, Interrupt, i);
 		if(RR(ctlr, Status)&Cmdinhibit){
 			WR(ctlr, Control1, RR(ctlr, Control1)|Srstcmd);
@@ -446,53 +474,13 @@
 		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);
+			print("usdhcio: no Datadone in %x after %s\n",
+				i, cmd->name);
 		if(i & Err)
-			print("usdhcio: CMD%d error interrupt %ux\n",
-				cmd, RR(ctlr, Interrupt));
+			print("usdhcio: %s error interrupt %ux\n",
+				cmd->name, RR(ctlr, Interrupt));
 		if(i != 0) WR(ctlr, Interrupt, i);
 	}
-	/*
-	 * Once card is selected, use faster clock
-	 */
-	if(cmd == MMCSelect){
-		usdhcclk(ctlr, SDfreq);
-		ctlr->fastclock = 1;
-	}
-	if(cmd == Setbuswidth){
-		if(ctlr->appcmd){
-			/*
-			 * If card bus width changes, change host bus width
-			 */
-			switch(arg){
-			case 0:
-				WR(ctlr, Control0, (RR(ctlr, Control0) & ~DwidthMask) | Dwidth1);
-				break;
-			case 2:
-				WR(ctlr, Control0, (RR(ctlr, Control0) & ~DwidthMask) | Dwidth4);
-				break;
-			}
-		} else {
-			/*
-			 * If card switched into high speed mode, increase clock speed
-			 */
-			if((arg&0x8000000F) == 0x80000001){
-				delay(1);
-				usdhcclk(ctlr, SDfreqhs);
-				delay(1);
-			}
-		}
-	}else if(cmd == IORWdirect && (arg & ~0xFF) == (1<<31|0<<28|7<<9)){
-		switch(arg & 0x3){
-		case 0:
-			WR(ctlr, Control0, (RR(ctlr, Control0) & ~DwidthMask) | Dwidth1);
-			break;
-		case 2:
-			WR(ctlr, Control0, (RR(ctlr, Control0) & ~DwidthMask) | Dwidth4);
-			break;
-		}
-	}
-	ctlr->appcmd = (cmd == Appcmd);
 	return 0;
 }
 
@@ -559,7 +547,7 @@
 		usdhccmd,
 		usdhciosetup,
 		usdhcio,
-		.highspeed = 1,
+		usdhcbus,
 		.nomultiwrite = 1,
 	};
 	static SDio usdhc2 = {
@@ -570,10 +558,10 @@
 		usdhccmd,
 		usdhciosetup,
 		usdhcio,
-		.highspeed = 1,
+		usdhcbus,
 		.nomultiwrite = 1,
 	};
 
-	addmmcio(&usdhc1);
 	addmmcio(&usdhc2);
+	addmmcio(&usdhc1);
 }
--- a/sys/src/9/pc/pmmc.c
+++ b/sys/src/9/pc/pmmc.c
@@ -2,7 +2,6 @@
  * 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/
  */
@@ -135,33 +134,6 @@
 	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;
@@ -278,10 +250,10 @@
 
 	m = all ? 1 : 6;
 	CR8(c, Rsrst) = m;
-	for(i=10; i>=0; i--){
+	for(i=100; i>=0; i--){
 		if((CR8(c, Rsrst) & m) == 0)
 			break;
-		delay(10);
+		delay(1);
 		CR8(c, Rsrst) = 0;
 	}
 	if(i < 0) iprint("mmc: didnt reset\n");
@@ -307,26 +279,25 @@
 }
 
 static void
-setclkfreq(Ctlr *c, int khz)
+setclkfreq(Ctlr *c, uint hz)
 {
-	u32int caps, intfreq;
-	int i, div;
+	u32int caps, clk, div;
+	int i;
 
-	if(khz == 0){
+	if(hz == 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;
+	clk = 1000000*((caps >> 8) & 0xff);
+	for(div = 2; div < 256; div *= 2){
+		if((clk / div) <= hz)
 			break;
-		}
 	}
+	// iprint("setclkfreq %ud = %ud / %d = %ud Hz\n", hz, clk, div, clk / div);
 	CR16(c, Rclc) = 0;
-	CR16(c, Rclc) = div<<8;
+	CR16(c, Rclc) = (div/2)<<8;
 	CR16(c, Rclc) |= 1;	/* int clock enable */
 	for(i=1000; i>=0; i--){
 		if(CR16(c, Rclc) & 2)	/* int clock stable */
@@ -359,7 +330,7 @@
 	CR16(c, Reise) = Emask;
 
 	setpower(c, 1);	
-	setclkfreq(c, 400);
+	setclkfreq(c, 400000);
 	iunlock(c);
 }
 
@@ -415,16 +386,43 @@
 }
 
 static int
-pmmccmd(SDio *io, u32int cmd, u32int arg, u32int *resp)
+pmmccmd(SDio *io, SDiocmd *iocmd, u32int arg, u32int *resp)
 {
 	Ctlr *c = io->aux;
-	u32int status;
-	int i, mode;
+	u32int cmd, mode, status;
+	int i;
 
-	if(cmd >= nelem(cmdinfo) || cmdinfo[cmd] == 0)
-		error(Egreg);
-	mode = cmdinfo[cmd] >> 8;
-	cmd = (cmd << 8) | (cmdinfo[cmd] & 0xFF);
+// print("pmmccmd: %s (%ux)\n", iocmd->name, arg);
+	cmd = (u32int)iocmd->index << 8;
+	switch(iocmd->resp){
+	case 0:
+		cmd |= Respnone;
+		break;
+	case 1:
+		if(iocmd->busy){
+			cmd |= Resp48busy | Ixchken | Crcchken;
+			break;
+		}
+	default:
+		cmd |= Resp48 | Ixchken | Crcchken;
+		break;
+	case 2:
+		cmd |= Resp136 | Crcchken;
+		break;
+	case 3:
+		cmd |= Resp48;
+		break;
+	}
+	mode = 0;
+	if(iocmd->data){
+		cmd |= Isdata;
+		if(iocmd->data & 1)
+			mode |= Mrd;
+		else
+			mode |= Mwr;
+		if(iocmd->data > 2)
+			mode |= Mcnt | Mblk;
+	}
 
 	if(c->change)
 		resetctlr(c);
@@ -434,8 +432,8 @@
 	status = Pinhbc;
 	if((cmd & Isdata) != 0 || (cmd & Respmask) == Resp48busy)
 		status |= Pinhbd;
-	for(i=1000; (CR32(c, Rpres) & status) != 0 && i>=0; i--)
-		delay(1);
+	for(i=100; (CR32(c, Rpres) & status) != 0 && i>=0; i--)
+		tsleep(&up->sleep, return0, nil, 10);
 	if(i < 0)
 		error(Eio);
 
@@ -471,19 +469,6 @@
 		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;
 }
 
@@ -545,6 +530,25 @@
 		error(Eio);
 }
 
+static void
+pmmcbus(SDio *io, int width, int speed)
+{
+	Ctlr *c = io->aux;
+
+	ilock(c);
+	switch(width){
+	case 1:
+		CR8(c, Rhc) &= ~2;
+		break;
+	case 4:
+		CR8(c, Rhc) |= 2;
+		break;
+	}
+	if(speed)
+		setclkfreq(c, speed);
+	iunlock(c);
+}
+
 void
 pmmclink(void)
 {
@@ -556,6 +560,7 @@
 		pmmccmd,
 		pmmciosetup,
 		pmmcio,
+		pmmcbus,
 	};
 	addmmcio(&io);
 }
--- a/sys/src/9/port/portmkfile
+++ b/sys/src/9/port/portmkfile
@@ -78,6 +78,7 @@
 devsd.$O:	../port/sd.h /$objtype/include/ureg.h
 sdscsi.$O:	../port/sd.h /$objtype/include/ureg.h
 sdaoe.$O:	../port/sd.h /$objtype/include/ureg.h
+sdmmc.$O:	../port/sd.h /$objtype/include/ureg.h
 trap.$O:	/$objtype/include/ureg.h
 proc.$O:	/$objtype/include/ureg.h
 devproc.$O:	/$objtype/include/ureg.h
--- a/sys/src/9/port/sd.h
+++ b/sys/src/9/port/sd.h
@@ -5,6 +5,7 @@
 typedef struct SDfile SDfile;
 typedef struct SDifc SDifc;
 typedef struct SDio SDio;
+typedef struct SDiocmd SDiocmd;
 typedef struct SDpart SDpart;
 typedef struct SDperm SDperm;
 typedef struct SDreq SDreq;
@@ -149,19 +150,29 @@
 #define sdmalloc(n)	mallocalign(n, BY2PG, 0, 0)
 #define sdfree(p)	free(p)
 
+
 /*
  * mmc/sd/sdio host controller interface
  */
 
+struct SDiocmd {
+	uchar	index;
+	uchar	resp;	/* 0 = none, 1 = R1, 2 = R2, 3 = R3 ... */
+	uchar	busy;
+	uchar	data;	/* 1 = read, 2 = write, 3 = multi read, 4 = multi write */
+
+	char	*name;
+};
+
 struct SDio {
 	char	*name;
 	int	(*init)(SDio*);
 	void	(*enable)(SDio*);
 	int	(*inquiry)(SDio*, char*, int);
-	int	(*cmd)(SDio*, u32int, u32int, u32int*);
+	int	(*cmd)(SDio*, SDiocmd*, u32int, u32int*);
 	void	(*iosetup)(SDio*, int, void*, int, int);
 	void	(*io)(SDio*, int, uchar*, int);
-	char	highspeed;
+	void	(*bus)(SDio*, int, int);
 	char	nomultiwrite;	/* quirk for usdhc */
 	void	*aux;
 };
--- a/sys/src/9/port/sdmmc.c
+++ b/sys/src/9/port/sdmmc.c
@@ -15,34 +15,31 @@
 
 #include "../port/sd.h"
 
-#define CSD(end, start)	rbits(csd, start, (end)-(start)+1)
+/* Commands */
+SDiocmd	GO_IDLE_STATE		= { 0, 0, 0, 0, "GO_IDLE_STATE" };
+SDiocmd SEND_OP_COND		= { 1, 3, 0, 0, "SEND_OP_COND" };
+SDiocmd ALL_SEND_CID		= { 2, 2, 0, 0, "ALL_SEND_CID" };
+SDiocmd SET_RELATIVE_ADDR	= { 3, 1, 0, 0, "SET_RELATIVE_ADDR" };
+SDiocmd SWITCH_FUNC		= { 6, 1, 0, 1, "SWITCH_FUNC" };
+SDiocmd SELECT_CARD		= { 7, 1, 1, 0, "SELECT_CARD" };
+SDiocmd SEND_EXT_CSD		= { 8, 1, 0, 1, "SEND_EXT_CSD" };
+SDiocmd SD_SEND_IF_COND		= { 8, 1, 0, 0, "SD_SEND_IF_COND" };
+SDiocmd SEND_CSD		= { 9, 2, 0, 0, "SEND_CSD" };
+SDiocmd STOP_TRANSMISSION	= {12, 1, 1, 0, "STOP_TRANSMISSION" };
+SDiocmd SEND_STATUS		= {13, 1, 0, 0, "SEND_STATUS" };
+SDiocmd SET_BLOCKLEN		= {16, 1, 0, 0, "SET_BLOCKLEN" };
+SDiocmd READ_SINGLE_BLOCK	= {17, 1, 0, 1, "READ_SINGLE_BLOCK" };
+SDiocmd READ_MULTIPLE_BLOCK	= {18, 1, 0, 3, "READ_MULTIPLE_BLOCK" };
+SDiocmd WRITE_SINGLE_BLOCK	= {24, 1, 0, 2, "WRITE_SINGLE_BLOCK" };
+SDiocmd WRITE_MULTIPLE_BLOCK	= {25, 1, 0, 4, "WRITE_MULTIPLE_BLOCK" };
 
-typedef struct Ctlr Ctlr;
+/* prefix for following app-specific commands */
+SDiocmd APP_CMD			= {55, 1, 0, 0, "APP_CMD" };
+SDiocmd  SD_SET_BUS_WIDTH	= { 6, 1, 0, 0, "SD_SET_BUS_WIDTH" };
+SDiocmd  SD_SEND_OP_COND	= {41, 3, 0, 0, "SD_SEND_OP_COND" };
 
+/* Command arguments */
 enum {
-	Inittimeout	= 15,
-	Multiblock	= 1,
-
-	/* Commands */
-	GO_IDLE_STATE	= 0,
-	ALL_SEND_CID	= 2,
-	SEND_RELATIVE_ADDR= 3,
-	SWITCH_FUNC	= 6,
-	SELECT_CARD	= 7,
-	SD_SEND_IF_COND	= 8,
-	SEND_CSD	= 9,
-	STOP_TRANSMISSION= 12,
-	SEND_STATUS	= 13,
-	SET_BLOCKLEN	= 16,
-	READ_SINGLE_BLOCK= 17,
-	READ_MULTIPLE_BLOCK= 18,
-	WRITE_BLOCK	= 24,
-	WRITE_MULTIPLE_BLOCK= 25,
-	APP_CMD		= 55,	/* prefix for following app-specific commands */
-	SET_BUS_WIDTH	= 6,
-	SD_SEND_OP_COND	= 41,
-
-	/* Command arguments */
 	/* SD_SEND_IF_COND */
 	Voltage		= 1<<8,
 	Checkpattern	= 0x42,
@@ -55,7 +52,7 @@
 	Ccs	= 1<<30,	/* card is SDHC or SDXC */
 	V3_3	= 3<<20,	/* 3.2-3.4 volts */
 
-	/* SET_BUS_WIDTH */
+	/* SD_SET_BUS_WIDTH */
 	Width1	= 0<<0,
 	Width4	= 2<<0,
 
@@ -70,16 +67,33 @@
 	Powerup	= 1<<31,
 };
 
+enum {
+	Multiblock	= 1,
+	Inittimeout	= 15,
+
+	Initfreq	= 400000,	/* initialisation frequency for MMC */
+	SDfreq		= 25000000,	/* standard SD frequency */
+	SDfreqhs	= 50000000,	/* highspeed frequency */
+};
+
+typedef struct Ctlr Ctlr;
 struct Ctlr {
 	SDev	*dev;
 	SDio	*io;
 
+	int	ismmc;
+
+	int	buswidth;
+	int	busspeed;
+
 	/* SD card registers */
-	u16int	rca;
+	u32int	rca;
 	u32int	ocr;
 	u32int	cid[4];
 	u32int	csd[4];
 
+	u8int	ext_csd[512];
+
 	int	retry;
 };
 
@@ -157,11 +171,31 @@
 	return list;
 }
 
+static void
+readextcsd(Ctlr *ctlr)
+{
+	SDio *io = ctlr->io;
+	u32int r[4];
+	uchar *buf;
+
+	buf = sdmalloc(512);
+	if(waserror()){
+		sdfree(buf);
+		nexterror();
+	}
+	(*io->iosetup)(io, 0, buf, 512, 1);
+	(*io->cmd)(io, &SEND_EXT_CSD, 0, r);
+	(*io->io)(io, 0, buf, 512);
+	memmove(ctlr->ext_csd, buf, sizeof ctlr->ext_csd);
+	sdfree(buf);
+	poperror();
+}
+
 static uint
 rbits(u32int *p, uint start, uint len)
 {
 	uint w, off, v;
-
+ 
 	w   = start / 32;
 	off = start % 32;
 	if(off == 0)
@@ -174,11 +208,25 @@
 		return v;
 }
 
+static uvlong
+rbytes(uchar *p, uint start, uint len)
+{
+	uvlong v = 0;
+	uint i;
+
+	p += start;
+	for(i = 0; i < len; i++)
+		v |= (uvlong)p[i] << 8*i;
+	return v;
+}
+
 static void
-identify(SDunit *unit, u32int *csd)
+identify(SDunit *unit)
 {
+	Ctlr *ctlr = unit->dev->ctlr;
 	uint csize, mult;
 
+#define CSD(end, start)	rbits(ctlr->csd, start, (end)-(start)+1)
 	unit->secsize = 1 << CSD(83, 80);
 	switch(CSD(127, 126)){
 	case 0:				/* CSD version 1 */
@@ -190,6 +238,11 @@
 		csize = CSD(69, 48);
 		unit->sectors = (csize+1) * 0x80000LL / unit->secsize;
 		break;
+	default:
+		readextcsd(ctlr);
+#define EXT_CSD(end, start) rbytes(ctlr->ext_csd, start, (end)-(start)+1)
+		unit->sectors = EXT_CSD(215, 212);
+		unit->secsize = 512;
 	}
 	if(unit->secsize == 1024){
 		unit->sectors <<= 1;
@@ -223,8 +276,9 @@
 }
 
 static void
-mmcswitchfunc(SDio *io, int arg)
+switchfunc(Ctlr *ctlr, int arg)
 {
+	SDio *io = ctlr->io;
 	u32int r[4];
 	uchar *buf;
 	int n;
@@ -232,12 +286,19 @@
 	n = Funcbytes;
 	buf = sdmalloc(n);
 	if(waserror()){
-		print("%s: mmcswitchfunc error\n", io->name);
 		sdfree(buf);
 		nexterror();
 	}
 	(*io->iosetup)(io, 0, buf, n, 1);
-	(*io->cmd)(io, SWITCH_FUNC, arg, r);
+	(*io->cmd)(io, &SWITCH_FUNC, arg, r);
+	if((arg & 0xFFFFFFF0) == Setfunc){
+		ctlr->busspeed = (arg & Hispeed) != 0? SDfreqhs: SDfreq;
+		if(io->bus != nil){
+			tsleep(&up->sleep, return0, nil, 10);
+			(*io->bus)(io, 0, ctlr->busspeed);
+			tsleep(&up->sleep, return0, nil, 10);
+		}
+	}
 	(*io->io)(io, 0, buf, n);
 	sdfree(buf);
 	poperror();
@@ -248,31 +309,59 @@
 {
 	SDio *io = ctlr->io;
 	u32int r[4];
-	int hcs, i;
+	int i, hcs;
 
-	(*io->cmd)(io, GO_IDLE_STATE, 0, r);
-	hcs = 0;
-	if(!waserror()){
-		(*io->cmd)(io, SD_SEND_IF_COND, Voltage|Checkpattern, r);
-		if(r[0] == (Voltage|Checkpattern))	/* SD 2.0 or above */
-			hcs = Hcs;
+	ctlr->buswidth = 1;
+	ctlr->busspeed = Initfreq;
+	if(io->bus != nil)
+		(*io->bus)(io, ctlr->buswidth, ctlr->busspeed);
+
+	(*io->cmd)(io, &GO_IDLE_STATE, 0, r);
+
+	/* card type unknown */
+	ctlr->ismmc = -1;
+
+	if(!waserror()){	/* try SD card first */
+		hcs = 0;
+		if(!waserror()){
+			(*io->cmd)(io, &SD_SEND_IF_COND, Voltage|Checkpattern, r);
+			if(r[0] == (Voltage|Checkpattern))	/* SD 2.0 or above */
+				hcs = Hcs;
+
+			ctlr->ismmc = 0;	/* this is SD card */
+			poperror();
+		}
+		for(i = 0; i < Inittimeout; i++){
+			tsleep(&up->sleep, return0, nil, 100);
+			(*io->cmd)(io, &APP_CMD, 0, r);
+			(*io->cmd)(io, &SD_SEND_OP_COND, hcs|V3_3, r);
+			if(r[0] & Powerup)
+				break;
+		}
+		ctlr->ismmc = 0;	/* this is SD card */
 		poperror();
+		if(i == Inittimeout)
+			return 2;
+	} else if(ctlr->ismmc) {	/* try MMC if not ruled out */
+		(*io->cmd)(io, &GO_IDLE_STATE, 0, r);
+
+		for(i = 0; i < Inittimeout; i++){
+			tsleep(&up->sleep, return0, nil, 100);
+			(*io->cmd)(io, &SEND_OP_COND, 0x80ff8080, r);
+			if(r[0] & Powerup)
+				break;
+		}
+		ctlr->ismmc = 1;	/* this is MMC */
+		if(i == Inittimeout)
+			return 2;
 	}
-	for(i = 0; i < Inittimeout; i++){
-		tsleep(&up->sleep, return0, nil, 100);
-		(*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;
+
 	ctlr->ocr = r[0];
-	(*io->cmd)(io, ALL_SEND_CID, 0, r);
+	(*io->cmd)(io, &ALL_SEND_CID, 0, r);
 	memmove(ctlr->cid, r, sizeof ctlr->cid);
-	(*io->cmd)(io, SEND_RELATIVE_ADDR, 0, r);
+	(*io->cmd)(io, &SET_RELATIVE_ADDR, 0, r);
 	ctlr->rca = r[0]>>16;
-	(*io->cmd)(io, SEND_CSD, ctlr->rca<<Rcashift, r);
+	(*io->cmd)(io, &SEND_CSD, ctlr->rca<<Rcashift, r);
 	memmove(ctlr->csd, r, sizeof ctlr->csd);
 	return 1;
 }
@@ -309,7 +398,7 @@
 		return 0;
 	}
 	if(unit->sectors != 0){
-		(*io->cmd)(io, SEND_STATUS, ctlr->rca<<Rcashift, r);
+		(*io->cmd)(io, &SEND_STATUS, ctlr->rca<<Rcashift, r);
 		poperror();
 		return 1;
 	}
@@ -317,14 +406,28 @@
 		poperror();
 		return 2;
 	}
-	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){
+	(*io->cmd)(io, &SELECT_CARD, ctlr->rca<<Rcashift, r);
+
+	ctlr->busspeed = SDfreq;
+	if(io->bus != nil){
+		tsleep(&up->sleep, return0, nil, 10);
+		(*io->bus)(io, 0, ctlr->busspeed);
+		tsleep(&up->sleep, return0, nil, 10);
+	}
+
+	identify(unit);
+
+	(*io->cmd)(io, &SET_BLOCKLEN, unit->secsize, r);	
+
+	if(!ctlr->ismmc){
+		(*io->cmd)(io, &APP_CMD, ctlr->rca<<Rcashift, r);
+		(*io->cmd)(io, &SD_SET_BUS_WIDTH, Width4, r);
+		ctlr->buswidth = 4;
+		if(io->bus != nil)
+			(*io->bus)(io, ctlr->buswidth, 0);
+
 		if(!waserror()){
-			mmcswitchfunc(io, Hispeed|Setfunc);
+			switchfunc(ctlr, Hispeed|Setfunc);
 			poperror();
 		}
 	}
@@ -344,12 +447,16 @@
 		if(unit->sectors == 0)
 			return 0;
 	}
-	n = snprint(p, l, "rca %4.4ux ocr %8.8ux\ncid ", ctlr->rca, ctlr->ocr);
+	n = snprint(p, l, "rca %4.4ux ocr %8.8ux", ctlr->rca, ctlr->ocr);
+
+	n += snprint(p+n, l-n, "\ncid ");
 	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 ");
+
+	n += snprint(p+n, l-n, "\ncsd ");
 	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;
@@ -379,20 +486,20 @@
 				nexterror();
 		(*io->iosetup)(io, write, buf, len, nb);
 		if(waserror()){
-			(*io->cmd)(io, STOP_TRANSMISSION, 0, r);
+			(*io->cmd)(io, &STOP_TRANSMISSION, 0, r);
 			nexterror();
 		}
-		(*io->cmd)(io, write? WRITE_MULTIPLE_BLOCK: READ_MULTIPLE_BLOCK,
+		(*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)(io, STOP_TRANSMISSION, 0, r);
+		(*io->cmd)(io, &STOP_TRANSMISSION, 0, r);
 		poperror();
 		b += nb;
 	}else{
 		for(b = bno; b < bno + nb; b++){
 			(*io->iosetup)(io, write, buf, len, 1);
-			(*io->cmd)(io, write? WRITE_BLOCK : READ_SINGLE_BLOCK,
+			(*io->cmd)(io, write? &WRITE_SINGLE_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
@@ -14,15 +14,6 @@
 #include "../port/sd.h"
 
 enum {
-	Initfreq	= 400000,	/* initialisation frequency for MMC */
-	SDfreq		= 25000000,	/* standard SD frequency */
-	DTO		= 14,		/* data timeout exponent (guesswork) */
-
-	MMCSelect	= 7,		/* mmc/sd card select command */
-	Setbuswidth	= 6,		/* mmc/sd set bus width command */
-};
-
-enum {
 	/* Controller registers */
 	Sysaddr			= 0x00>>2,
 	Blksizecnt		= 0x04>>2,
@@ -56,6 +47,7 @@
 	Srsthc			= 1<<24,	/* reset complete host controller */
 	Datatoshift		= 16,		/* data timeout unit exponent */
 	Datatomask		= 0xF0000,
+		DTO		= 14,		/* data timeout exponent (guesswork) */
 	Clkfreq8shift		= 8,		/* SD clock base divider LSBs */
 	Clkfreq8mask		= 0xFF00,
 	Clkfreqms2shift		= 6,		/* SD clock base divider MSBs */
@@ -116,31 +108,11 @@
 	Cmdinhibit	= 1<<0,
 };
 
-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 | Dmaen,
-[18] Resp48 | Isdata | Card2host | Multiblock | Blkcnten | Ixchken | Crcchken | Dmaen,
-[24] Resp48 | Isdata | Host2card | Ixchken | Crcchken | Dmaen,
-[25] Resp48 | Isdata | Host2card | Multiblock | Blkcnten | Ixchken | Crcchken | Dmaen,
-[41] Resp48,
-[55] Resp48 | Ixchken | Crcchken,
-};
-
 typedef struct Ctlr Ctlr;
 struct Ctlr {
 	Rendez	r;
 	u32int	*regs;
 	int	datadone;
-	int	fastclock;
 	ulong	extclk;
 	int	irq;
 };
@@ -209,25 +181,32 @@
 }
 
 static void
-emmcenable(SDio *io)
+emmcclk(uint freq)
 {
+	u32int *r;
 	int i;
 
-	emmc.regs[Control1] = clkdiv(emmc.extclk / Initfreq - 1) | DTO << Datatoshift |
-		Clkgendiv | Clken | Clkintlen;
+	r = emmc.regs;
+	r[Control1] = clkdiv(emmc.extclk / freq - 1) |
+			DTO << Datatoshift | Clkgendiv | Clken | Clkintlen;
 	for(i = 0; i < 1000; i++){
 		delay(1);
-		if(emmc.regs[Control1] & Clkstable)
-			break;
+		if(r[Control1] & Clkstable)
+			return;
 	}
-	if(i == 1000)
-		print("SD clock won't initialise!\n");
+	print("SD clock won't initialise!\n");
+}
+
+static void
+emmcenable(SDio *io)
+{
+	emmcclk(400000);
 	emmc.regs[Irptmask] = ~(Dtoerr|Cardintr|Dmaintr);
 	intrenable(emmc.irq, interrupt, nil, LEVEL, io->name);
 }
 
 static int
-emmccmd(SDio*, u32int cmd, u32int arg, u32int *resp)
+emmccmd(SDio*, SDiocmd *cmd, u32int arg, u32int *resp)
 {
 	ulong now;
 	u32int *r;
@@ -234,8 +213,34 @@
 	u32int c;
 	int i;
 
-	assert(cmd < nelem(cmdinfo) && cmdinfo[cmd] != 0);
-	c = (cmd << Indexshift) | cmdinfo[cmd];
+	c = (u32int)cmd->index << Indexshift;
+	switch(cmd->resp){
+	case 0:
+		c |= Respnone;
+		break;
+	case 1:
+		if(cmd->busy){
+			c |= Resp48busy | Ixchken | Crcchken;
+			break;
+		}
+	default:
+		c |= Resp48 | Ixchken | Crcchken;
+		break;
+	case 2:
+		c |= Resp136 | Crcchken;
+		break;
+	case 3:
+		c |= Resp48;
+		break;
+	}
+	if(cmd->data){
+		if(cmd->data & 1)
+			c |= Isdata | Card2host | Dmaen;
+		else
+			c |= Isdata | Host2card | Dmaen;
+		if(cmd->data > 2)
+			c |= Multiblock | Blkcnten;
+	}
 
 	r = emmc.regs;
 	if(r[Status] & Cmdinhibit){
@@ -308,33 +313,6 @@
 				cmd, r[Interrupt]);
 		r[Interrupt] = i;
 	}
-	/*
-	 * Once card is selected, use faster clock
-	 */
-	if(cmd == MMCSelect){
-		delay(10);
-		r[Control1] = clkdiv(emmc.extclk / SDfreq - 1) |
-			DTO << Datatoshift | Clkgendiv | Clken | Clkintlen;
-		for(i = 0; i < 1000; i++){
-			delay(1);
-			if(r[Control1] & Clkstable)
-				break;
-		}
-		delay(10);
-		emmc.fastclock = 1;
-	}
-	/*
-	 * If card bus width changes, change host bus width
-	 */
-	if(cmd == Setbuswidth)
-		switch(arg){
-		case 0:
-			r[Control0] &= ~Dwidth4;
-			break;
-		case 2:
-			r[Control0] |= Dwidth4;
-			break;
-		}
 	return 0;
 }
 
@@ -393,6 +371,24 @@
 	}
 }
 
+static void
+emmcbus(SDio*, int width, int speed)
+{
+	u32int *r;
+
+	r = emmc.regs;
+	switch(width){
+	case 1:
+		r[Control0] &= ~Dwidth4;
+		break;
+	case 4:
+		r[Control0] |= Dwidth4;
+		break;
+	}
+	if(speed)
+		emmcclk(speed);
+}
+
 void
 emmclink(void)
 {
@@ -404,6 +400,7 @@
 		emmccmd,
 		emmciosetup,
 		emmcio,
+		emmcbus,
 	};
 	addmmcio(&io);
 }