/*
* 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.
*/
/* 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 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 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;
}