shithub: riscv

Download patch

ref: a8c50a79437006bdde702d09408df8749f48a45a
parent: 60ec886191b15c638deaee27a161bbe872590e38
author: cinap_lenrek <[email protected]>
date: Sun Aug 25 14:45:29 EDT 2019

bcm64: replace emmc2 driver with richard millers sdhc driver

the new driver supports 50MHz highspeed bus mode
and uses ADMA instead of SDMA.

--- a/sys/src/9/bcm64/emmc2.c
+++ /dev/null
@@ -1,413 +1,0 @@
-/*
- * external mass media controller (mmc / sd host interface)
- *
- * derived from Richard Miller's bcm/emmc.c
- */
-
-#include "u.h"
-#include "../port/lib.h"
-#include "../port/error.h"
-#include "mem.h"
-#include "dat.h"
-#include "fns.h"
-#include "io.h"
-#include "../port/sd.h"
-
-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,
-	Arg1			= 0x08>>2,
-	Cmdtm			= 0x0c>>2,
-	Resp0			= 0x10>>2,
-	Resp1			= 0x14>>2,
-	Resp2			= 0x18>>2,
-	Resp3			= 0x1c>>2,
-	Data			= 0x20>>2,
-	Status			= 0x24>>2,
-	Control0		= 0x28>>2,
-	Control1		= 0x2c>>2,
-	Interrupt		= 0x30>>2,
-	Irptmask		= 0x34>>2,
-	Irpten			= 0x38>>2,
-	Control2		= 0x3c>>2,
-	Capabilities		= 0x40>>2,
-	Forceirpt		= 0x50>>2,
-	Boottimeout		= 0x60>>2,
-	Dbgsel			= 0x64>>2,
-	Spiintspt		= 0xf0>>2,
-	Slotisrver		= 0xfc>>2,
-
-	/* Control0 */
-	Dwidth4			= 1<<1,
-	Dwidth1			= 0<<1,
-
-	/* Control1 */
-	Srstdata		= 1<<26,	/* reset data circuit */
-	Srstcmd			= 1<<25,	/* reset command circuit */
-	Srsthc			= 1<<24,	/* reset complete host controller */
-	Datatoshift		= 16,		/* data timeout unit exponent */
-	Datatomask		= 0xF0000,
-	Clkfreq8shift		= 8,		/* SD clock base divider LSBs */
-	Clkfreq8mask		= 0xFF00,
-	Clkfreqms2shift		= 6,		/* SD clock base divider MSBs */
-	Clkfreqms2mask		= 0xC0,
-	Clkgendiv		= 0<<5,		/* SD clock divided */
-	Clkgenprog		= 1<<5,		/* SD clock programmable */
-	Clken			= 1<<2,		/* SD clock enable */
-	Pllen			= 1<<3,
-	Clkstable		= 1<<1,	
-	Clkintlen		= 1<<0,		/* enable internal EMMC clocks */
-
-	/* Cmdtm */
-	Indexshift		= 24,
-	Suspend			= 1<<22,
-	Resume			= 2<<22,
-	Abort			= 3<<22,
-	Isdata			= 1<<21,
-	Ixchken			= 1<<20,
-	Crcchken		= 1<<19,
-	Respmask		= 3<<16,
-	Respnone		= 0<<16,
-	Resp136			= 1<<16,
-	Resp48			= 2<<16,
-	Resp48busy		= 3<<16,
-	Multiblock		= 1<<5,
-	Host2card		= 0<<4,
-	Card2host		= 1<<4,
-	Autocmd12		= 1<<2,
-	Autocmd23		= 2<<2,
-	Blkcnten		= 1<<1,
-	Dmaen			= 1<<0,
-
-	/* Interrupt */
-	Acmderr		= 1<<24,
-	Denderr		= 1<<22,
-	Dcrcerr		= 1<<21,
-	Dtoerr		= 1<<20,
-	Cbaderr		= 1<<19,
-	Cenderr		= 1<<18,
-	Ccrcerr		= 1<<17,
-	Ctoerr		= 1<<16,
-	Err		= 1<<15,
-	Cardintr	= 1<<8,
-	Cardinsert	= 1<<6,
-	Readrdy		= 1<<5,
-	Writerdy	= 1<<4,
-	Dmaintr		= 1<<3,
-	Datadone	= 1<<1,
-	Cmddone		= 1<<0,
-
-	/* Status */
-	Present		= 1<<18,
-	Bufread		= 1<<11,
-	Bufwrite	= 1<<10,
-	Readtrans	= 1<<9,
-	Writetrans	= 1<<8,
-	Datactive	= 1<<2,
-	Datinhibit	= 1<<1,
-	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;
-};
-
-static Ctlr emmc;
-
-static uint
-clkdiv(uint d)
-{
-	uint v;
-
-	assert(d < 1<<10);
-	v = (d << Clkfreq8shift) & Clkfreq8mask;
-	v |= ((d >> 8) << Clkfreqms2shift) & Clkfreqms2mask;
-	return v;
-}
-
-static void
-interrupt(Ureg*, void*)
-{	
-	u32int *r;
-	u32int i;
-
-	r = emmc.regs;
-	i = r[Interrupt];
-	r[Interrupt] = i & (Datadone|Err);
-	emmc.datadone = i;
-	wakeup(&emmc.r);
-}
-
-static int
-datadone(void*)
-{
-	return emmc.datadone;
-}
-
-static int
-emmcinit(void)
-{
-	u32int *r;
-	int i;
-
-	emmc.extclk = getclkrate(ClkEmmc2);
-	emmc.irq = IRQmmc;
-	r = (u32int*)(VIRTIO + 0x340000);
-	emmc.regs = r;
-	r[Control1] = Srsthc;
-	for(i = 0; i < 100; i++){
-		delay(10);
-		if((r[Control1] & Srsthc) == 0)
-			return 0;
-	}
-	print("emmc: reset timeout!\n");
-	return -1;
-}
-
-static int
-emmcinquiry(char *inquiry, int inqlen)
-{
-	uint ver;
-
-	ver = emmc.regs[Slotisrver] >> 16;
-	return snprint(inquiry, inqlen,
-		"eMMC SD Host Controller %2.2x Version %2.2x",
-		ver&0xFF, ver>>8);
-}
-
-static void
-emmcenable(void)
-{
-	int i;
-
-	emmc.regs[Control1] = clkdiv(emmc.extclk / Initfreq - 1) | DTO << Datatoshift |
-		Clkgendiv | Clken | Clkintlen;
-	for(i = 0; i < 1000; i++){
-		delay(1);
-		if(emmc.regs[Control1] & Clkstable)
-			break;
-	}
-	if(i == 1000)
-		print("SD clock won't initialise!\n");
-
-	emmc.regs[Control1] |= Pllen;
-	for(i = 0; i < 1000; i++){
-		delay(1);
-		if(emmc.regs[Control1] & Clkstable)
-			break;
-	}
-	if(i == 1000)
-		print("PLL clock won't initialise!\n");
-
-	emmc.regs[Control0] = (emmc.regs[Control0] & ~0xFF00) | 0xF00;	// VDD1 bus power to 3.3V
-	emmc.regs[Irptmask] = ~(Dtoerr|Cardintr|Dmaintr);
-	intrenable(emmc.irq, interrupt, nil, BUSUNKNOWN, sdio.name);
-}
-
-static int
-emmccmd(u32int cmd, u32int arg, u32int *resp)
-{
-	ulong now;
-	u32int *r;
-	u32int c;
-	u32int i;
-
-	assert(cmd < nelem(cmdinfo) && cmdinfo[cmd] != 0);
-	c = (cmd << Indexshift) | cmdinfo[cmd];
-
-	r = emmc.regs;
-	if(r[Status] & Cmdinhibit){
-		print("emmccmd: need to reset Cmdinhibit intr %ux stat %ux\n",
-			r[Interrupt], r[Status]);
-		r[Control1] |= Srstcmd;
-		while(r[Control1] & Srstcmd)
-			;
-		while(r[Status] & Cmdinhibit)
-			;
-	}
-	if((c & Isdata || (c & Respmask) == Resp48busy) &&
-	    r[Status] & Datinhibit){
-		print("emmccmd: need to reset Datinhibit intr %ux stat %ux\n",
-			r[Interrupt], r[Status]);
-		r[Control1] |= Srstdata;
-		while(r[Control1] & Srstdata)
-			;
-		while(r[Status] & Datinhibit)
-			;
-	}
-	r[Arg1] = arg;
-	if((i = r[Interrupt]) != 0){
-		if(i != Cardinsert)
-			print("emmc: before command, intr was %ux\n", i);
-		r[Interrupt] = i;
-	}
-	coherence();
-	r[Cmdtm] = c;
-	coherence();
-	now = m->ticks;
-	while(((i=r[Interrupt])&(Cmddone|Err)) == 0)
-		if((long)(m->ticks-now) > HZ)
-			break;
-	if((i&(Cmddone|Err)) != Cmddone){
-		if((i&~Err) != Ctoerr)
-			print("emmc: cmd %ux error intr %ux stat %ux\n", c, i, r[Status]);
-		r[Interrupt] = i;
-		if(r[Status]&Cmdinhibit){
-			r[Control1] |= Srstcmd;
-			while(r[Control1]&Srstcmd)
-				;
-		}
-		error(Eio);
-	}
-	r[Interrupt] = i & ~(Datadone|Readrdy|Writerdy);
-	switch(c & Respmask){
-	case Resp136:
-		resp[0] = r[Resp0]<<8;
-		resp[1] = r[Resp0]>>24 | r[Resp1]<<8;
-		resp[2] = r[Resp1]>>24 | r[Resp2]<<8;
-		resp[3] = r[Resp2]>>24 | r[Resp3]<<8;
-		break;
-	case Resp48:
-	case Resp48busy:
-		resp[0] = r[Resp0];
-		break;
-	case Respnone:
-		resp[0] = 0;
-		break;
-	}
-	if((c & Respmask) == Resp48busy){
-		r[Irpten] = Datadone|Err;
-		tsleep(&emmc.r, datadone, 0, 3000);
-		i = emmc.datadone;
-		emmc.datadone = 0;
-		r[Irpten] = 0;
-		if((i & Datadone) == 0)
-			print("emmcio: no Datadone after CMD%d\n", cmd);
-		if(i & Err)
-			print("emmcio: CMD%d error interrupt %ux\n",
-				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;
-}
-
-static void
-emmciosetup(int, void *buf, int bsize, int bcount)
-{
-	u32int *r;
-	int len;
-
-	len = bsize*bcount;
-	if(len > (0x1000<<7))
-		error(Etoobig);
-
-	dmaflush(1, buf, len);
-
-	r = emmc.regs;
-	r[Sysaddr] = dmaaddr(buf);
-	r[Blksizecnt] = 7<<12 | bcount<<16 | bsize;
-	r[Irpten] = Datadone|Err;
-}
-
-static void
-emmcio(int write, uchar *buf, int len)
-{
-	u32int *r;
-	int i;
-
-	tsleep(&emmc.r, datadone, 0, 3000);
-	i = emmc.datadone;
-	emmc.datadone = 0;
-
-	r = emmc.regs;
-	r[Irpten] = 0;
-	if((i & Datadone) == 0){
-		print("emmcio: %d timeout intr %ux stat %ux\n",
-			write, i, r[Status]);
-		r[Interrupt] = i;
-		error(Eio);
-	}
-	if(i & Err){
-		print("emmcio: %d error intr %ux stat %ux\n",
-			write, r[Interrupt], r[Status]);
-		r[Interrupt] = i;
-		error(Eio);
-	}
-	if(i)
-		r[Interrupt] = i;
-
-	if(!write)
-		dmaflush(0, buf, len);
-}
-
-SDio sdio = {
-	"emmc2",
-	emmcinit,
-	emmcenable,
-	emmcinquiry,
-	emmccmd,
-	emmciosetup,
-	emmcio,
-};
--- a/sys/src/9/bcm64/pi4
+++ b/sys/src/9/bcm64/pi4
@@ -45,7 +45,7 @@
 misc
 	uartmini
 	uartpl011
-	sdmmc	emmc2
+	sdmmc	sdhc
 	dma
 	gic
 	vcore
--- /dev/null
+++ b/sys/src/9/bcm64/sdhc.c
@@ -1,0 +1,565 @@
+/*
+ * bcm2711 sd host controller
+ *
+ * Copyright © 2012,2019 Richard Miller <[email protected]>
+ *
+ * adapted from emmc.c - the two should really be merged
+ */
+
+#include "u.h"
+#include "../port/lib.h"
+#include "../port/error.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "io.h"
+#include "../port/sd.h"
+
+#define EMMCREGS	(VIRTIO+0x340000)
+
+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 {
+	/* Controller registers */
+	SDMAaddr		= 0x00>>2,
+	Blksizecnt		= 0x04>>2,
+	Arg1			= 0x08>>2,
+	Cmdtm			= 0x0c>>2,
+	Resp0			= 0x10>>2,
+	Resp1			= 0x14>>2,
+	Resp2			= 0x18>>2,
+	Resp3			= 0x1c>>2,
+	Data			= 0x20>>2,
+	Status			= 0x24>>2,
+	Control0		= 0x28>>2,
+	Control1		= 0x2c>>2,
+	Interrupt		= 0x30>>2,
+	Irptmask		= 0x34>>2,
+	Irpten			= 0x38>>2,
+	Control2		= 0x3c>>2,
+	Capability		= 0x40>>2,
+	Forceirpt		= 0x50>>2,
+	Dmadesc			= 0x58>>2,
+	Boottimeout		= 0x70>>2,
+	Dbgsel			= 0x74>>2,
+	Exrdfifocfg		= 0x80>>2,
+	Exrdfifoen		= 0x84>>2,
+	Tunestep		= 0x88>>2,
+	Tunestepsstd		= 0x8c>>2,
+	Tunestepsddr		= 0x90>>2,
+	Spiintspt		= 0xf0>>2,
+	Slotisrver		= 0xfc>>2,
+
+	/* Control0 */
+	Busvoltage		= 7<<9,
+		V1_8		= 5<<9,
+		V3_0		= 6<<9,
+		V3_3		= 7<<9,
+	Buspower		= 1<<8,
+	Dwidth8			= 1<<5,
+	Dmaselect		= 3<<3,
+		DmaSDMA		= 0<<3,
+		DmaADMA1	= 1<<3,
+		DmaADMA2	= 2<<3,
+	Hispeed			= 1<<2,
+	Dwidth4			= 1<<1,
+	Dwidth1			= 0<<1,
+	LED			= 1<<0,
+
+	/* Control1 */
+	Srstdata		= 1<<26,	/* reset data circuit */
+	Srstcmd			= 1<<25,	/* reset command circuit */
+	Srsthc			= 1<<24,	/* reset complete host controller */
+	Datatoshift		= 16,		/* data timeout unit exponent */
+	Datatomask		= 0xF0000,
+	Clkfreq8shift		= 8,		/* SD clock base divider LSBs */
+	Clkfreq8mask		= 0xFF00,
+	Clkfreqms2shift		= 6,		/* SD clock base divider MSBs */
+	Clkfreqms2mask		= 0xC0,
+	Clkgendiv		= 0<<5,		/* SD clock divided */
+	Clkgenprog		= 1<<5,		/* SD clock programmable */
+	Clken			= 1<<2,		/* SD clock enable */
+	Clkstable		= 1<<1,	
+	Clkintlen		= 1<<0,		/* enable internal EMMC clocks */
+
+	/* Cmdtm */
+	Indexshift		= 24,
+	Suspend			= 1<<22,
+	Resume			= 2<<22,
+	Abort			= 3<<22,
+	Isdata			= 1<<21,
+	Ixchken			= 1<<20,
+	Crcchken		= 1<<19,
+	Respmask		= 3<<16,
+	Respnone		= 0<<16,
+	Resp136			= 1<<16,
+	Resp48			= 2<<16,
+	Resp48busy		= 3<<16,
+	Multiblock		= 1<<5,
+	Host2card		= 0<<4,
+	Card2host		= 1<<4,
+	Autocmd12		= 1<<2,
+	Autocmd23		= 2<<2,
+	Blkcnten		= 1<<1,
+	Dmaen			= 1<<0,
+
+	/* Interrupt */
+	Admaerr		= 1<<25,
+	Acmderr		= 1<<24,
+	Denderr		= 1<<22,
+	Dcrcerr		= 1<<21,
+	Dtoerr		= 1<<20,
+	Cbaderr		= 1<<19,
+	Cenderr		= 1<<18,
+	Ccrcerr		= 1<<17,
+	Ctoerr		= 1<<16,
+	Err		= 1<<15,
+	Cardintr	= 1<<8,
+	Cardinsert	= 1<<6,		/* not in Broadcom datasheet */
+	Readrdy		= 1<<5,
+	Writerdy	= 1<<4,
+	Dmaintr		= 1<<3,
+	Datadone	= 1<<1,
+	Cmddone		= 1<<0,
+
+	/* Status */
+	Bufread		= 1<<11,	/* not in Broadcom datasheet */
+	Bufwrite	= 1<<10,	/* not in Broadcom datasheet */
+	Readtrans	= 1<<9,
+	Writetrans	= 1<<8,
+	Datactive	= 1<<2,
+	Datinhibit	= 1<<1,
+	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
+ */
+
+struct Adma {
+	u32int	desc;
+	u32int	addr;
+};
+
+enum {
+	/* desc fields */
+	Valid		= 1<<0,
+	End			= 1<<1,
+	Int			= 1<<2,
+	Nop			= 0<<4,
+	Tran		= 2<<4,
+	Link		= 3<<4,
+	OLength		= 16,
+	/* maximum value for Length field */
+	Maxdma		= ((1<<16) - 4),
+};
+
+struct Ctlr {
+	Rendez	r;
+	Rendez	cardr;
+	int	fastclock;
+	ulong	extclk;
+	int	appcmd;
+	Adma	*dma;
+};
+
+static Ctlr emmc;
+
+static void mmcinterrupt(Ureg*, void*);
+
+static void
+WR(int reg, u32int val)
+{
+	u32int *r = (u32int*)EMMCREGS;
+
+	if(0)print("WR %2.2ux %ux\n", reg<<2, val);
+	coherence();
+	r[reg] = val;
+}
+
+static uint
+clkdiv(uint d)
+{
+	uint v;
+
+	assert(d < 1<<10);
+	v = (d << Clkfreq8shift) & Clkfreq8mask;
+	v |= ((d >> 8) << Clkfreqms2shift) & Clkfreqms2mask;
+	return v;
+}
+
+static Adma*
+dmaalloc(void *addr, int len)
+{
+	int n;
+	uintptr a;
+	Adma *adma, *p;
+
+	a = (uintptr)addr;
+	n = (len + Maxdma-1) / Maxdma;
+	adma = sdmalloc(n * sizeof(Adma));
+	for(p = adma; len > 0; p++){
+		p->desc = Valid | Tran;
+		if(n == 1)
+			p->desc |= len<<OLength | End | Int;
+		else
+			p->desc |= Maxdma<<OLength;
+		p->addr = dmaaddr((void*)a);
+		a += Maxdma;
+		len -= Maxdma;
+		n--;
+	}
+	cachedwbse(adma, (char*)p - (char*)adma);
+	return adma;
+}
+
+static void
+emmcclk(uint freq)
+{
+	u32int *r;
+	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);
+	for(i = 0; i < 1000; i++){
+		delay(1);
+		if(r[Control1] & Clkstable)
+			break;
+	}
+	if(i == 1000)
+		print("emmc: can't set clock to %ud\n", freq);
+}
+
+static int
+datadone(void*)
+{
+	int i;
+
+	u32int *r = (u32int*)EMMCREGS;
+	i = r[Interrupt];
+	return i & (Datadone|Err);
+}
+
+static int
+emmcinit(void)
+{
+	u32int *r;
+	ulong clk;
+
+	clk = getclkrate(ClkEmmc2);
+	if(clk == 0){
+		clk = Extfreq;
+		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);
+	delay(10);
+	while(r[Control1] & Srsthc)
+		;
+	WR(Control1, Srstdata);
+	delay(10);
+	WR(Control1, 0);
+	return 0;
+}
+
+static int
+emmcinquiry(char *inquiry, int inqlen)
+{
+	u32int *r;
+	uint ver;
+
+	r = (u32int*)EMMCREGS;
+	ver = r[Slotisrver] >> 16;
+	return snprint(inquiry, inqlen,
+		"BCM SD Host Controller %2.2x Version %2.2x",
+		ver&0xFF, ver>>8);
+}
+
+static void
+emmcenable(void)
+{
+
+	WR(Control0, 0);
+	delay(1);
+	WR(Control0, V3_3 | Buspower | Dwidth1 | DmaADMA2);
+	WR(Control1, 0);
+	delay(1);
+	emmcclk(Initfreq);
+	WR(Irpten, 0);
+	WR(Irptmask, ~(Cardintr|Dmaintr));
+	WR(Interrupt, ~0);
+	intrenable(IRQmmc, mmcinterrupt, nil, BUSUNKNOWN, "sdhc");
+}
+
+static int
+emmccmd(u32int cmd, u32int arg, u32int *resp)
+{
+	u32int *r;
+	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(c & Isdata)
+		c |= Dmaen;
+	if(cmd == IORWextended){
+		if(arg & (1<<31))
+			c |= Host2card;
+		else
+			c |= Card2host;
+		if((r[Blksizecnt]&0xFFFF0000) != 0x10000)
+			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] & Cmdinhibit){
+		print("emmccmd: need to reset Cmdinhibit intr %ux stat %ux\n",
+			r[Interrupt], r[Status]);
+		WR(Control1, r[Control1] | Srstcmd);
+		while(r[Control1] & Srstcmd)
+			;
+		while(r[Status] & Cmdinhibit)
+			;
+	}
+	if((r[Status] & Datinhibit) &&
+	   ((c & Isdata) || (c & Respmask) == Resp48busy)){
+		print("emmccmd: need to reset Datinhibit intr %ux stat %ux\n",
+			r[Interrupt], r[Status]);
+		WR(Control1, r[Control1] | Srstdata);
+		while(r[Control1] & Srstdata)
+			;
+		while(r[Status] & Datinhibit)
+			;
+	}
+	WR(Arg1, arg);
+	if((i = (r[Interrupt] & ~Cardintr)) != 0){
+		if(i != Cardinsert)
+			print("emmc: before command, intr was %ux\n", i);
+		WR(Interrupt, i);
+	}
+	WR(Cmdtm, c);
+	now = m->ticks;
+	while(((i=r[Interrupt])&(Cmddone|Err)) == 0)
+		if(m->ticks-now > HZ)
+			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]);
+		WR(Interrupt, i);
+		if(r[Status]&Cmdinhibit){
+			WR(Control1, r[Control1]|Srstcmd);
+			while(r[Control1]&Srstcmd)
+				;
+		}
+		error(Eio);
+	}
+	WR(Interrupt, i & ~(Datadone|Readrdy|Writerdy));
+	switch(c & Respmask){
+	case Resp136:
+		resp[0] = r[Resp0]<<8;
+		resp[1] = r[Resp0]>>24 | r[Resp1]<<8;
+		resp[2] = r[Resp1]>>24 | r[Resp2]<<8;
+		resp[3] = r[Resp2]>>24 | r[Resp3]<<8;
+		break;
+	case Resp48:
+	case Resp48busy:
+		resp[0] = r[Resp0];
+		break;
+	case Respnone:
+		resp[0] = 0;
+		break;
+	}
+	if((c & Respmask) == Resp48busy){
+		WR(Irpten, r[Irpten]|Datadone|Err);
+		tsleep(&emmc.r, datadone, 0, 3000);
+		i = r[Interrupt];
+		if((i & Datadone) == 0)
+			print("emmcio: no Datadone after CMD%d\n", cmd);
+		if(i & Err)
+			print("emmcio: CMD%d error interrupt %ux\n",
+				cmd, 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;
+}
+
+static void
+emmciosetup(int write, void *buf, int bsize, int bcount)
+{
+	int len;
+
+	len = bsize * bcount;
+	assert(((uintptr)buf&3) == 0);
+	assert((len&3) == 0);
+	assert(bsize <= 2048);
+	WR(Blksizecnt, bcount<<16 | bsize);
+	if(emmc.dma)
+		sdfree(emmc.dma);
+	emmc.dma = dmaalloc(buf, len);
+	if(write)
+		cachedwbse(buf, len);
+	else
+		cachedwbinvse(buf, len);
+	WR(Dmadesc, dmaaddr(emmc.dma));
+	okay(1);
+}
+
+static void
+emmcio(int write, uchar *buf, int len)
+{
+	u32int *r;
+	int i;
+
+	r = (u32int*)EMMCREGS;
+	if(waserror()){
+		okay(0);
+		nexterror();
+	}
+	WR(Irpten, r[Irpten] | Datadone|Err);
+	tsleep(&emmc.r, datadone, 0, 3000);
+	WR(Irpten, r[Irpten] & ~(Datadone|Err));
+	i = r[Interrupt];
+	if((i & (Datadone|Err)) != Datadone){
+		print("sdhc: %s error intr %ux stat %ux\n",
+			write? "write" : "read", i, r[Status]);
+		WR(Interrupt, i);
+		error(Eio);
+	}
+	WR(Interrupt, i);
+	if(!write)
+		cachedinvse(buf, len);
+	poperror();
+	okay(0);
+}
+
+static void
+mmcinterrupt(Ureg*, void*)
+{	
+	u32int *r;
+	int i;
+
+	r = (u32int*)EMMCREGS;
+	i = r[Interrupt];
+	if(i&(Datadone|Err))
+		wakeup(&emmc.r);
+	if(i&Cardintr)
+		wakeup(&emmc.cardr);
+	WR(Irpten, r[Irpten] & ~i);
+}
+
+SDio sdio = {
+	"sdhc",
+	emmcinit,
+	emmcenable,
+	emmcinquiry,
+	emmccmd,
+	emmciosetup,
+	emmcio,
+};
--- a/sys/src/9/port/sdmmc.c
+++ b/sys/src/9/port/sdmmc.c
@@ -28,6 +28,7 @@
 	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,
@@ -59,6 +60,13 @@
 	Width1	= 0<<0,
 	Width4	= 2<<0,
 
+	/* SWITCH_FUNC */
+	Dfltspeed	= 0<<0,
+	Hispeed		= 1<<0,
+	Checkfunc	= 0x00FFFFF0,
+	Setfunc		= 0x80FFFFF0,
+	Funcbytes	= 64,
+
 	/* OCR (operating conditions register) */
 	Powerup	= 1<<31,
 };
@@ -167,6 +175,27 @@
 	return 1;
 }
 
+static void
+mmcswitchfunc(SDio *io, int arg)
+{
+	uchar *buf;
+	int n;
+	u32int r[4];
+
+	n = Funcbytes;
+	buf = sdmalloc(n);
+	if(waserror()){
+		print("mmcswitchfunc error\n");
+		sdfree(buf);
+		nexterror();
+	}
+	io->iosetup(0, buf, n, 1);
+	io->cmd(SWITCH_FUNC, arg, r);
+	io->io(0, buf, n);
+	sdfree(buf);
+	poperror();
+}
+
 static int
 mmconline(SDunit *unit)
 {
@@ -220,6 +249,12 @@
 	io->cmd(SET_BLOCKLEN, unit->secsize, r);
 	io->cmd(APP_CMD, ctl->rca<<Rcashift, r);
 	io->cmd(SET_BUS_WIDTH, Width4, r);
+	if(strcmp(io->name, "sdhc") == 0){
+		if(!waserror()){
+			mmcswitchfunc(io, Hispeed|Setfunc);
+			poperror();
+		}
+	}
 	poperror();
 	return 1;
 }