/* * mmc / sd memory card * * Copyright © 2012 Richard Miller * * Assumes only one card on the bus */ #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 CSD(end, start) rbits(csd, start, (end)-(start)+1) typedef struct Ctlr Ctlr; enum { Inittimeout = 15, Multiblock = 1, /* Commands */ GO_IDLE_STATE = 0, ALL_SEND_CID = 2, SEND_RELATIVE_ADDR= 3, 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, /* SELECT_CARD */ Rcashift = 16, /* SD_SEND_OP_COND */ Hcs = 1<<30, /* host supports SDHC & SDXC */ Ccs = 1<<30, /* card is SDHC or SDXC */ V3_3 = 3<<20, /* 3.2-3.4 volts */ /* SET_BUS_WIDTH */ Width1 = 0<<0, Width4 = 2<<0, /* OCR (operating conditions register) */ Powerup = 1<<31, }; struct Ctlr { SDev *dev; SDio *io; /* SD card registers */ u16int rca; u32int ocr; u32int cid[4]; u32int csd[4]; }; extern SDifc sdmmcifc; extern SDio sdio; static uint rbits(u32int *p, uint start, uint len) { uint w, off, v; w = start / 32; off = start % 32; if(off == 0) v = p[w]; else v = p[w] >> off | p[w+1] << (32-off); if(len < 32) return v & ((1<secsize = 1 << CSD(83, 80); switch(CSD(127, 126)){ case 0: /* CSD version 1 */ csize = CSD(73, 62); mult = CSD(49, 47); unit->sectors = (csize+1) * (1<<(mult+2)); break; case 1: /* CSD version 2 */ csize = CSD(69, 48); unit->sectors = (csize+1) * 512LL*KiB / unit->secsize; break; } if(unit->secsize == 1024){ unit->sectors <<= 1; unit->secsize = 512; } } 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) { int n; Ctlr *ctl; ctl = unit->dev->ctlr; n = ctl->io->inquiry((char*)&unit->inquiry[8], sizeof(unit->inquiry)-8); if(n < 0) return 0; unit->inquiry[0] = SDperdisk; unit->inquiry[1] = SDinq1removable; unit->inquiry[4] = sizeof(unit->inquiry)-4; return 1; } static int mmcenable(SDev* dev) { Ctlr *ctl; ctl = dev->ctlr; ctl->io->enable(); return 1; } static int mmconline(SDunit *unit) { int hcs, i; u32int r[4]; Ctlr *ctl; SDio *io; ctl = unit->dev->ctlr; io = ctl->io; assert(unit->subno == 0); if(waserror()){ unit->sectors = 0; return 0; } if(unit->sectors != 0){ io->cmd(SEND_STATUS, ctl->rca<cmd(GO_IDLE_STATE, 0, r); hcs = 0; if(!waserror()){ io->cmd(SD_SEND_IF_COND, Voltage|Checkpattern, r); if(r[0] == (Voltage|Checkpattern)) /* SD 2.0 or above */ hcs = Hcs; poperror(); } for(i = 0; i < Inittimeout; i++){ delay(100); io->cmd(APP_CMD, 0, r); io->cmd(SD_SEND_OP_COND, hcs|V3_3, r); if(r[0] & Powerup) break; } if(i == Inittimeout){ print("sdmmc: card won't power up\n"); poperror(); 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<csd, r, sizeof ctl->csd); identify(unit, ctl->csd); io->cmd(SELECT_CARD, ctl->rca<cmd(SET_BLOCKLEN, unit->secsize, r); io->cmd(APP_CMD, ctl->rca<cmd(SET_BUS_WIDTH, Width4, r); poperror(); return 1; } static int mmcrctl(SDunit *unit, char *p, int l) { Ctlr *ctl; int i, n; ctl = unit->dev->ctlr; assert(unit->subno == 0); if(unit->sectors == 0){ mmconline(unit); if(unit->sectors == 0) return 0; } 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+n, l-n, " csd "); for(i = nelem(ctl->csd)-1; i >= 0; i--) n += snprint(p+n, l-n, "%8.8ux", ctl->csd[i]); n += snprint(p+n, l-n, "\ngeometry %llud %ld\n", unit->sectors, unit->secsize); return n; } static long mmcbio(SDunit *unit, int lun, int write, void *data, long nb, uvlong bno) { int len, tries; ulong b; u32int r[4]; uchar *buf; Ctlr *ctl; SDio *io; USED(lun); ctl = unit->dev->ctlr; io = ctl->io; assert(unit->subno == 0); if(unit->sectors == 0) error("media change"); buf = data; len = unit->secsize; if(Multiblock){ b = bno; tries = 0; while(waserror()) if(++tries == 3) nexterror(); io->iosetup(write, buf, len, nb); if(waserror()){ io->cmd(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); poperror(); io->cmd(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); buf += len; } } return (b - bno) * len; } static int mmcrio(SDreq*) { return -1; } SDifc sdmmcifc = { .name = "mmc", .pnp = mmcpnp, .enable = mmcenable, .verify = mmcverify, .online = mmconline, .rctl = mmcrctl, .bio = mmcbio, .rio = mmcrio, };