/* * 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(); } 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)) { if (Debug) print("flashkw: ctlr %#p not ready\n", pa); 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; } print("flashkw: unknown chip: vid %#ux did %#ux\n", maker, device); 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); }