ref: 2c5dcb6718dbdf59c38d7a97fa5a62c708b412c2
dir: /sys/src/9/port/sdmmc.c/
/*
* mmc / sd memory card
*
* Copyright © 2012 Richard Miller <r.miller@acm.org>
*
*/
#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,
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,
/* 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,
/* SWITCH_FUNC */
Dfltspeed = 0<<0,
Hispeed = 1<<0,
Checkfunc = 0x00FFFFF0,
Setfunc = 0x80FFFFF0,
Funcbytes = 64,
/* 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];
int retry;
};
extern SDifc sdmmcifc;
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)
{
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<<len) - 1);
else
return v;
}
static void
identify(SDunit *unit, u32int *csd)
{
uint csize, mult;
unit->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) * 0x80000LL / unit->secsize;
break;
}
if(unit->secsize == 1024){
unit->sectors <<= 1;
unit->secsize = 512;
}
}
static int
mmcverify(SDunit *unit)
{
Ctlr *ctlr = unit->dev->ctlr;
SDio *io = ctlr->io;
int n;
n = (*io->inquiry)(io, (char*)&unit->inquiry[8], sizeof(unit->inquiry)-8);
if(n < 0)
return 0;
unit->inquiry[0] = 0x00; /* direct access (disk) */
unit->inquiry[1] = 0x80; /* removable */
unit->inquiry[4] = sizeof(unit->inquiry)-4;
return 1;
}
static int
mmcenable(SDev* dev)
{
Ctlr *ctlr = dev->ctlr;
SDio *io = ctlr->io;
(*io->enable)(io);
return 1;
}
static void
mmcswitchfunc(SDio *io, int arg)
{
u32int r[4];
uchar *buf;
int n;
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->io)(io, 0, buf, n);
sdfree(buf);
poperror();
}
static int
cardinit(Ctlr *ctlr)
{
SDio *io = ctlr->io;
u32int r[4];
int hcs, i;
(*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;
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;
}
if(i == Inittimeout)
return 2;
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;
}
static void
retryproc(void *arg)
{
Ctlr *ctlr = arg;
int i = 0;
while(waserror())
;
if(i++ < ctlr->retry)
cardinit(ctlr);
USED(i);
ctlr->retry = 0;
pexit("", 1);
}
static int
mmconline(SDunit *unit)
{
Ctlr *ctlr = unit->dev->ctlr;
SDio *io = ctlr->io;
u32int r[4];
assert(unit->subno == 0);
if(ctlr->retry)
return 0;
if(waserror()){
unit->sectors = 0;
if(ctlr->retry++ == 0)
kproc(unit->name, retryproc, ctlr);
return 0;
}
if(unit->sectors != 0){
(*io->cmd)(io, SEND_STATUS, ctlr->rca<<Rcashift, r);
poperror();
return 1;
}
if(cardinit(ctlr) != 1){
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){
if(!waserror()){
mmcswitchfunc(io, Hispeed|Setfunc);
poperror();
}
}
poperror();
return 1;
}
static int
mmcrctl(SDunit *unit, char *p, int l)
{
Ctlr *ctlr = unit->dev->ctlr;
int i, n;
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 ", 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(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;
}
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;
u32int r[4];
uchar *buf;
ulong b;
USED(lun);
assert(unit->subno == 0);
if(unit->sectors == 0)
error(Echange);
buf = data;
len = unit->secsize;
if(Multiblock && (!write || !io->nomultiwrite)){
b = bno;
tries = 0;
while(waserror())
if(++tries == 3)
nexterror();
(*io->iosetup)(io, write, buf, len, nb);
if(waserror()){
(*io->cmd)(io, STOP_TRANSMISSION, 0, r);
nexterror();
}
(*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);
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,
ctlr->ocr & Ccs? b: b * len, r);
(*io->io)(io, write, buf, len);
buf += len;
}
}
return (b - bno) * len;
}
static int
mmcrio(SDreq *r)
{
int i, rw, count;
uvlong lba;
if((i = sdfakescsi(r)) != SDnostatus)
return r->status = i;
if((i = sdfakescsirw(r, &lba, &count, &rw)) != SDnostatus)
return i;
r->rlen = mmcbio(r->unit, r->lun, rw == SDwrite, r->data, count, lba);
return r->status = SDok;
}
SDifc sdmmcifc = {
.name = "mmc",
.pnp = mmcpnp,
.enable = mmcenable,
.verify = mmcverify,
.online = mmconline,
.rctl = mmcrctl,
.bio = mmcbio,
.rio = mmcrio,
};