diff --git a/sys/src/9/imx8/io.h b/sys/src/9/imx8/io.h index ddd558ad9..38a1ce0f3 100644 --- a/sys/src/9/imx8/io.h +++ b/sys/src/9/imx8/io.h @@ -7,6 +7,9 @@ enum { IRQcntps = PPI+13, IRQcntpns = PPI+14, + IRQusdhc1 = SPI+22, + IRQusdhc2 = SPI+23, + IRQuart1 = SPI+26, IRQuart2 = SPI+27, IRQuart3 = SPI+28, diff --git a/sys/src/9/imx8/reform b/sys/src/9/imx8/reform index baedcac24..747451665 100644 --- a/sys/src/9/imx8/reform +++ b/sys/src/9/imx8/reform @@ -19,6 +19,7 @@ dev uart usb i2c + sd link usbxhciimx @@ -36,12 +37,13 @@ ip ipmux misc ccm + gic gpc gpio - gic - uartimx lcd + uartimx iomux + sdmmc usdhc port int cpuserver = 0; bootdir diff --git a/sys/src/9/imx8/usdhc.c b/sys/src/9/imx8/usdhc.c new file mode 100644 index 000000000..a6ed1f702 --- /dev/null +++ b/sys/src/9/imx8/usdhc.c @@ -0,0 +1,530 @@ +#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 USDHC1 (VIRTIO+0xB40000) +#define USDHC2 (VIRTIO+0xB50000) + +#define EMMCREGS USDHC2 + +enum { + Initfreq = 400000, /* initialisation frequency for MMC */ + SDfreq = 25*Mhz, /* standard SD 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, + + Mixctrl = 0x48>>2, + + Forceirpt = 0x50>>2, + Dmadesc = 0x58>>2, + + Vendorspec = 0xC0>>2, + + /* Vendorspec */ + ClkEn = 1<<14, + PerEn = 1<<13, + HclkEn = 1<<12, + IpgEn = 1<<11, + Vsel = 1<<1, + + /* Control0 (PROT_CTRL) */ + Dmaselect = 3<<8, + DmaSDMA = 0<<8, + DmaADMA1 = 1<<8, + DmaADMA2 = 2<<8, + + BE = 0<<4, + HBE = 1<<4, + LE = 2<<4, + + DwidthMask = 3<<1, + Dwidth8 = 2<<1, + Dwidth4 = 1<<1, + Dwidth1 = 0<<1, + LED = 1<<0, + + /* Control1 (SYS_CTRL) */ + 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, + SDCLKFSshift = 8, + DVSshift = 4, + + /* 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, + + /* Mixctrl */ + Autocmd23 = 1<<7, + Multiblock = 1<<5, + Host2card = 0<<4, + Card2host = 1<<4, + DdrEn = 1<<3, + Autocmd12 = 1<<2, + Blkcnten = 1<<1, + Dmaen = 1<<0, + MixCmdMask = 0xFF ^ DdrEn, + + /* Interrupt */ + Admaerr = 1<<28, + 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 = Admaerr|Acmderr|Denderr|Dcrcerr|Dtoerr|Cbaderr|Cenderr|Ccrcerr|Ctoerr, + + Cardintr = 1<<8, + Cardinsert = 1<<6, + Readrdy = 1<<5, + Writerdy = 1<<4, + Dmaintr = 1<<3, + Datadone = 1<<1, + Cmddone = 1<<0, + + /* Status */ + Bufread = 1<<11, + Bufwrite = 1<<10, + Readtrans = 1<<9, + Writetrans = 1<<8, + + Clkstable = 1<<3, + + 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] Resp48 | 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 | 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 + */ + +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<<12, +}; + +struct Ctlr { + Rendez r; + int fastclock; + uint 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 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<desc |= Maxdma<addr = PADDR(a); + a += Maxdma; + len -= Maxdma; + n--; + } + cachedwbse(adma, (char*)p - (char*)adma); + return adma; +} + +static void +emmcclk(uint freq) +{ + u32int *r = (u32int*)EMMCREGS; + uint pre_div = 1, post_div = 1, clk = emmc.extclk; + + while(clk / (pre_div * 16) > freq && pre_div < 256) + pre_div <<= 1; + + while(clk / (pre_div * post_div) > freq && post_div < 16) + post_div++; + + WR(Vendorspec, r[Vendorspec] & ~ClkEn); + WR(Control1, (pre_div>>1)<ticks; + while(((i=r[Interrupt])&(Cmddone|Err)) == 0) + if(MACHP(0)->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, 1000); + i = r[Interrupt]; + if((i & Datadone) == 0) + print("emmcio: no Datadone in %x after CMD%d\n", i, cmd); + if(i & Err) + print("emmcio: CMD%d error interrupt %ux\n", + cmd, r[Interrupt]); + if(i != 0) WR(Interrupt, i); + } + /* + * Once card is selected, use faster clock + */ + if(cmd == MMCSelect){ + emmcclk(SDfreq); + 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] & ~DwidthMask) | Dwidth1); + break; + case 2: + WR(Control0, (r[Control0] & ~DwidthMask) | Dwidth4); + break; + } + } + }else if(cmd == IORWdirect && (arg & ~0xFF) == (1<<31|0<<28|7<<9)){ + switch(arg & 0x3){ + case 0: + WR(Control0, (r[Control0] & ~DwidthMask) | Dwidth1); + break; + case 2: + WR(Control0, (r[Control0] & ~DwidthMask) | Dwidth4); + 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, PADDR(emmc.dma)); +} + +static void +emmcio(int write, uchar *buf, int len) +{ + u32int *r = (u32int*)EMMCREGS; + int i; + + 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); +} + +static void +mmcinterrupt(Ureg*, void*) +{ + u32int *r; + int i; + + r = (u32int*)EMMCREGS; + i = r[Interrupt]; + if(i&(Datadone|Err)) + wakeup(&emmc.r); + WR(Irpten, r[Irpten] & ~i); +} + +SDio sdio = { + "usdhc", + emmcinit, + emmcenable, + emmcinquiry, + emmccmd, + emmciosetup, + emmcio, +};