diff options
author | Taru Karttunen <taruti@taruti.net> | 2011-03-30 15:46:40 +0300 |
---|---|---|
committer | Taru Karttunen <taruti@taruti.net> | 2011-03-30 15:46:40 +0300 |
commit | e5888a1ffdae813d7575f5fb02275c6bb07e5199 (patch) | |
tree | d8d51eac403f07814b9e936eed0c9a79195e2450 /sys/src/9/kw/flashkw.c |
Import sources from 2011-03-30 iso image
Diffstat (limited to 'sys/src/9/kw/flashkw.c')
-rwxr-xr-x | sys/src/9/kw/flashkw.c | 671 |
1 files changed, 671 insertions, 0 deletions
diff --git a/sys/src/9/kw/flashkw.c b/sys/src/9/kw/flashkw.c new file mode 100755 index 000000000..e45bc29de --- /dev/null +++ b/sys/src/9/kw/flashkw.c @@ -0,0 +1,671 @@ +/* + * sheevaplug nand flash driver + * + * for now separate from (inferno's) os/port/flashnand.c because the flash + * seems newer, and has different commands, but that is nand-chip specific, + * not sheevaplug-specific. they should be merged in future. + * + * the sheevaplug has a hynix 4gbit flash chip: hy27uf084g2m. + * 2048 byte pages, with 64 spare bytes each; erase block size is 128k. + * + * it has a "glueless" interface, at 0xf9000000. that's the address + * of the data register. the command and address registers are those + * or'ed with 1 and 2 respectively. + * + * linux uses this layout for the nand flash (from address 0 onwards): + * 1mb for u-boot + * 4mb for kernel + * 507mb for file system + * + * this is not so relevant here except for ecc. the first two areas + * (u-boot and kernel) are expected to have 4-bit ecc per 512 bytes + * (but calculated from last byte to first), bad erase blocks skipped. + * the file system area has 1-bit ecc per 256 bytes. + */ + +#include "u.h" +#include "../port/lib.h" +#include "mem.h" +#include "dat.h" +#include "fns.h" +#include "io.h" +#include "../port/error.h" + +#include "../port/flashif.h" +#include "../port/nandecc.h" + +enum { + Debug = 0, + + Nopage = ~0ul, /* cache is empty */ + + /* vendors */ + Hynix = 0xad, + Samsung = 0xec, + + /* chips */ + Hy27UF084G2M = 0xdc, + + NandActCEBoot = 1<<1, +}; + +typedef struct Nandreg Nandreg; +typedef struct Nandtab Nandtab; +typedef struct Cache Cache; + +struct Nandreg { /* hw registers */ + ulong rdparms; + ulong wrparms; + uchar _pad0[0x70 - 0x20]; + ulong ctl; +}; + +struct Nandtab { + int vid; + int did; + vlong size; + char* name; +}; + +struct Cache { + Flash *flif; + ulong pageno; + ulong pgsize; /* r->pagesize */ + char *page; /* of pgsize bytes */ +}; + +enum { + /* commands */ + Readstatus = 0x70, + Readid = 0x90, /* needs 1 0-address write */ + Resetf = 0xff, + + /* + * needs 5 address writes followed by Readstart, + * Readstartcache or Restartcopy. + */ + Read = 0x00, + Readstart = 0x30, + Readstartcache = 0x31, + Readstartcopy = 0x35, + /* after Readstartcache, to stop reading next pages */ + Readstopcache = 0x34, + + /* needs 5 address writes, the data, and -start or -cache */ + Program = 0x80, + Programstart = 0x10, + Programcache = 0x15, + + Copyback = 0x85, /* followed by Programstart */ + + /* 3 address writes for block followed by Erasestart */ + Erase = 0x60, + Erasestart = 0xd0, + + Randomread = 0x85, + Randomwrite = 0x05, + Randomwritestart= 0xe0, + + /* status bits */ + SFail = 1<<0, + SCachefail = 1<<1, + SIdle = 1<<5, /* doesn't seem to come on ever */ + SReady = 1<<6, + SNotprotected = 1<<7, + + Srdymask = SReady, /* was SIdle|SReady */ +}; + +Nandtab nandtab[] = { + {Hynix, Hy27UF084G2M, 512*MB, "Hy27UF084G2M"}, + {Samsung, 0xdc, 512*MB, "Samsung 2Gb"}, +}; + +static Cache cache; + +static void +nandcmd(Flash *f, uchar b) +{ + uchar *p = (uchar *)((ulong)f->addr|1); + + *p = b; + coherence(); +} + +static void +nandaddr(Flash *f, uchar b) +{ + uchar *p = (uchar *)((ulong)f->addr|2); + + *p = b; + coherence(); +} + +static uchar +nandread(Flash *f) +{ + return *(uchar *)f->addr; +} + +static void +nandreadn(Flash *f, uchar *buf, long n) +{ + uchar *p = f->addr; + + while(n-- > 0) + *buf++ = *p; +} + +static void +nandwrite(Flash *f, uchar b) +{ + *(uchar *)f->addr = b; + coherence(); +} + +static void +nandwriten(Flash *f, uchar *buf, long n) +{ + uchar *p = f->addr; + + while(n-- > 0) + *p = *buf++; + coherence(); +} + +static void +nandclaim(Flash*) +{ + Nandreg *nand = (Nandreg*)soc.nand; + + nand->ctl |= NandActCEBoot; + coherence(); +} + +static void +nandunclaim(Flash*) +{ + Nandreg *nand = (Nandreg*)soc.nand; + + nand->ctl &= ~NandActCEBoot; + coherence(); +} + + +void mmuidmap(uintptr phys, int mbs); + +Nandtab * +findflash(Flash *f, uintptr pa, uchar *id4p) +{ + int i; + ulong sts; + uchar maker, device, id3, id4; + Nandtab *chip; + + mmuidmap(pa, 16); + f->addr = (void *)pa; + + /* make sure controller is idle */ + nandclaim(f); + nandcmd(f, Resetf); + nandunclaim(f); + + nandclaim(f); + nandcmd(f, Readstatus); + sts = nandread(f); + nandunclaim(f); + for (i = 10; i > 0 && !(sts & SReady); i--) { + delay(50); + nandclaim(f); + nandcmd(f, Readstatus); + sts = nandread(f); + nandunclaim(f); + } + if(!(sts & SReady)) + return nil; + + nandclaim(f); + nandcmd(f, Readid); + nandaddr(f, 0); + maker = nandread(f); + device = nandread(f); + id3 = nandread(f); + USED(id3); + id4 = nandread(f); + nandunclaim(f); + if (id4p) + *id4p = id4; + + for(i = 0; i < nelem(nandtab); i++) { + chip = &nandtab[i]; + if(chip->vid == maker && chip->did == device) + return chip; + } + return nil; +} + +int +flashat(Flash *f, uintptr pa) +{ + return findflash(f, pa, nil) != nil; +} + +static int +idchip(Flash *f) +{ + uchar id4; + Flashregion *r; + Nandtab *chip; + static int blocksizes[4] = { 64*1024, 128*1024, 256*1024, 0 }; + static int pagesizes[4] = { 1024, 2*1024, 0, 0 }; + static int spares[2] = { 8, 16 }; /* per 512 bytes */ + + f->id = 0; + f->devid = 0; + f->width = 1; + chip = findflash(f, (uintptr)f->addr, &id4); + if (chip == nil) + return -1; + f->id = chip->vid; + f->devid = chip->did; + f->size = chip->size; + f->width = 1; + f->nr = 1; + + r = &f->regions[0]; + r->pagesize = pagesizes[id4 & MASK(2)]; + r->erasesize = blocksizes[(id4 >> 4) & MASK(2)]; + if (r->pagesize == 0 || r->erasesize == 0) { + iprint("flashkw: bogus flash sizes\n"); + return -1; + } + r->n = f->size / r->erasesize; + r->start = 0; + r->end = f->size; + assert(ispow2(r->pagesize)); + r->pageshift = log2(r->pagesize); + assert(ispow2(r->erasesize)); + r->eraseshift = log2(r->erasesize); + assert(r->eraseshift >= r->pageshift); + if (cache.page == nil) { + cache.pgsize = r->pagesize; + cache.page = smalloc(r->pagesize); + } + + r->spares = r->pagesize / 512 * spares[(id4 >> 2) & 1]; + print("#F0: kwnand: %s %,lud bytes pagesize %lud erasesize %,lud" + " spares per page %lud\n", chip->name, f->size, r->pagesize, + r->erasesize, r->spares); + return 0; +} + +static int +ctlrwait(Flash *f) +{ + int sts, cnt; + + nandclaim(f); + for (;;) { + nandcmd(f, Readstatus); + for(cnt = 100; cnt > 0 && (nandread(f) & Srdymask) != Srdymask; + cnt--) + microdelay(50); + nandcmd(f, Readstatus); + sts = nandread(f); + if((sts & Srdymask) == Srdymask) + break; + print("flashkw: flash ctlr busy, sts %#ux: resetting\n", sts); + nandcmd(f, Resetf); + } + nandunclaim(f); + return 0; +} + +static int +erasezone(Flash *f, Flashregion *r, ulong offset) +{ + int i; + ulong page, block; + uchar s; + + if (Debug) { + print("flashkw: erasezone: offset %#lux, region nblocks %d," + " start %#lux, end %#lux\n", offset, r->n, r->start, + r->end); + print(" erasesize %lud, pagesize %lud\n", + r->erasesize, r->pagesize); + } + assert(r->erasesize != 0); + if(offset & (r->erasesize - 1)) { + print("flashkw: erase offset %lud not block aligned\n", offset); + return -1; + } + page = offset >> r->pageshift; + block = page >> (r->eraseshift - r->pageshift); + if (Debug) + print("flashkw: erase: block %#lux\n", block); + + /* make sure controller is idle */ + if(ctlrwait(f) < 0) { + print("flashkw: erase: flash busy\n"); + return -1; + } + + /* start erasing */ + nandclaim(f); + nandcmd(f, Erase); + nandaddr(f, page>>0); + nandaddr(f, page>>8); + nandaddr(f, page>>16); + nandcmd(f, Erasestart); + + /* invalidate cache on any erasure (slight overkill) */ + cache.pageno = Nopage; + + /* have to wait until flash is done. typically ~2ms */ + delay(1); + nandcmd(f, Readstatus); + for(i = 0; i < 100; i++) { + s = nandread(f); + if(s & SReady) { + nandunclaim(f); + if(s & SFail) { + print("flashkw: erase: failed, block %#lux\n", + block); + return -1; + } + return 0; + } + microdelay(50); + } + print("flashkw: erase timeout, block %#lux\n", block); + nandunclaim(f); + return -1; +} + +static void +flcachepage(Flash *f, ulong page, uchar *buf) +{ + Flashregion *r = &f->regions[0]; + + assert(cache.pgsize == r->pagesize); + cache.flif = f; + cache.pageno = page; + /* permit i/o directly to or from the cache */ + if (buf != (uchar *)cache.page) + memmove(cache.page, buf, cache.pgsize); +} + +static int +write1page(Flash *f, ulong offset, void *buf) +{ + int i; + ulong page, v; + uchar s; + uchar *eccp, *p; + Flashregion *r = &f->regions[0]; + static uchar *oob; + + if (oob == nil) + oob = smalloc(r->spares); + + page = offset >> r->pageshift; + if (Debug) + print("flashkw: write nand offset %#lux page %#lux\n", + offset, page); + + if(offset & (r->pagesize - 1)) { + print("flashkw: write offset %lud not page aligned\n", offset); + return -1; + } + + p = buf; + memset(oob, 0xff, r->spares); + assert(r->spares >= 24); + eccp = oob + r->spares - 24; + for(i = 0; i < r->pagesize / 256; i++) { + v = nandecc(p); + *eccp++ = v>>8; + *eccp++ = v>>0; + *eccp++ = v>>16; + p += 256; + } + + if(ctlrwait(f) < 0) { + print("flashkw: write: nand not ready & idle\n"); + return -1; + } + + /* write, only whole pages for now, no sub-pages */ + nandclaim(f); + nandcmd(f, Program); + nandaddr(f, 0); + nandaddr(f, 0); + nandaddr(f, page>>0); + nandaddr(f, page>>8); + nandaddr(f, page>>16); + nandwriten(f, buf, r->pagesize); + nandwriten(f, oob, r->spares); + nandcmd(f, Programstart); + + microdelay(100); + nandcmd(f, Readstatus); + for(i = 0; i < 100; i++) { + s = nandread(f); + if(s & SReady) { + nandunclaim(f); + if(s & SFail) { + print("flashkw: write failed, page %#lux\n", + page); + return -1; + } + return 0; + } + microdelay(10); + } + + nandunclaim(f); + flcachepage(f, page, buf); + print("flashkw: write timeout for page %#lux\n", page); + return -1; +} + +static int +read1page(Flash *f, ulong offset, void *buf) +{ + int i; + ulong addr, page, w; + uchar *eccp, *p; + Flashregion *r = &f->regions[0]; + static uchar *oob; + + if (oob == nil) + oob = smalloc(r->spares); + + assert(r->pagesize != 0); + addr = offset & (r->pagesize - 1); + page = offset >> r->pageshift; + if(addr != 0) { + print("flashkw: read1page: read addr %#lux:" + " must read aligned page\n", addr); + return -1; + } + + /* satisfy request from cache if possible */ + if (f == cache.flif && page == cache.pageno && + r->pagesize == cache.pgsize) { + memmove(buf, cache.page, r->pagesize); + return 0; + } + + if (Debug) + print("flashkw: read offset %#lux addr %#lux page %#lux\n", + offset, addr, page); + + nandclaim(f); + nandcmd(f, Read); + nandaddr(f, addr>>0); + nandaddr(f, addr>>8); + nandaddr(f, page>>0); + nandaddr(f, page>>8); + nandaddr(f, page>>16); + nandcmd(f, Readstart); + + microdelay(50); + + nandreadn(f, buf, r->pagesize); + nandreadn(f, oob, r->spares); + + nandunclaim(f); + + /* verify/correct data. last 8*3 bytes is ecc, per 256 bytes. */ + p = buf; + assert(r->spares >= 24); + eccp = oob + r->spares - 24; + for(i = 0; i < r->pagesize / 256; i++) { + w = eccp[0] << 8 | eccp[1] << 0 | eccp[2] << 16; + eccp += 3; + switch(nandecccorrect(p, nandecc(p), &w, 1)) { + case NandEccErrorBad: + print("(page %d)\n", i); + return -1; + case NandEccErrorOneBit: + case NandEccErrorOneBitInEcc: + print("(page %d)\n", i); + /* fall through */ + case NandEccErrorGood: + break; + } + p += 256; + } + + flcachepage(f, page, buf); + return 0; +} + +/* + * read a page at offset into cache, copy fragment from buf into it + * at pagoff, and rewrite that page. + */ +static int +rewrite(Flash *f, ulong offset, ulong pagoff, void *buf, ulong size) +{ + if (read1page(f, offset, cache.page) < 0) + return -1; + memmove(&cache.page[pagoff], buf, size); + return write1page(f, offset, cache.page); +} + +/* there are no alignment constraints on offset, buf, nor n */ +static int +write(Flash *f, ulong offset, void *buf, long n) +{ + uint un, frag, pagoff; + ulong pgsize; + uchar *p; + Flashregion *r = &f->regions[0]; + + if(n <= 0) + panic("flashkw: write: non-positive count %ld", n); + un = n; + assert(r->pagesize != 0); + pgsize = r->pagesize; + + /* if a partial first page exists, update the first page with it. */ + p = buf; + pagoff = offset % pgsize; + if (pagoff != 0) { + frag = pgsize - pagoff; + if (frag > un) /* might not extend to end of page */ + frag = un; + if (rewrite(f, offset - pagoff, pagoff, p, frag) < 0) + return -1; + offset += frag; + p += frag; + un -= frag; + } + + /* copy whole pages */ + while (un >= pgsize) { + if (write1page(f, offset, p) < 0) + return -1; + offset += pgsize; + p += pgsize; + un -= pgsize; + } + + /* if a partial last page exists, update the last page with it. */ + if (un > 0) + return rewrite(f, offset, 0, p, un); + return 0; +} + +/* there are no alignment constraints on offset, buf, nor n */ +static int +read(Flash *f, ulong offset, void *buf, long n) +{ + uint un, frag, pagoff; + ulong pgsize; + uchar *p; + Flashregion *r = &f->regions[0]; + + if(n <= 0) + panic("flashkw: read: non-positive count %ld", n); + un = n; + assert(r->pagesize != 0); + pgsize = r->pagesize; + + /* if partial 1st page, read it into cache & copy fragment to buf */ + p = buf; + pagoff = offset % pgsize; + if (pagoff != 0) { + frag = pgsize - pagoff; + if (frag > un) /* might not extend to end of page */ + frag = un; + if (read1page(f, offset - pagoff, cache.page) < 0) + return -1; + offset += frag; + memmove(p, &cache.page[pagoff], frag); + p += frag; + un -= frag; + } + + /* copy whole pages */ + while (un >= pgsize) { + if (read1page(f, offset, p) < 0) + return -1; + offset += pgsize; + p += pgsize; + un -= pgsize; + } + + /* if partial last page, read into cache & copy initial fragment to buf */ + if (un > 0) { + if (read1page(f, offset, cache.page) < 0) + return -1; + memmove(p, cache.page, un); + } + return 0; +} + +static int +reset(Flash *f) +{ + if(f->data != nil) + return 1; + f->write = write; + f->read = read; + f->eraseall = nil; + f->erasezone = erasezone; + f->suspend = nil; + f->resume = nil; + f->sort = "nand"; + cache.pageno = Nopage; + return idchip(f); +} + +void +flashkwlink(void) +{ + addflashcard("nand", reset); +} |