/* error correcting code for nand flash */
#include	"u.h"
#include	"../port/lib.h"
#include	"mem.h"
#include	"dat.h"
#include	"fns.h"
#include	"io.h"
#include	"../port/error.h"
#include	"nandecc.h"

#define CORRECTABLEMASK 0x545555

static uchar ecctab[] = {
	0x00, 0x55, 0x59, 0x0c, 0x65, 0x30, 0x3c, 0x69,
	0x69, 0x3c, 0x30, 0x65, 0x0c, 0x59, 0x55, 0x00,
	0x95, 0xc0, 0xcc, 0x99, 0xf0, 0xa5, 0xa9, 0xfc,
	0xfc, 0xa9, 0xa5, 0xf0, 0x99, 0xcc, 0xc0, 0x95,
	0x99, 0xcc, 0xc0, 0x95, 0xfc, 0xa9, 0xa5, 0xf0,
	0xf0, 0xa5, 0xa9, 0xfc, 0x95, 0xc0, 0xcc, 0x99,
	0x0c, 0x59, 0x55, 0x00, 0x69, 0x3c, 0x30, 0x65,
	0x65, 0x30, 0x3c, 0x69, 0x00, 0x55, 0x59, 0x0c,
	0xa5, 0xf0, 0xfc, 0xa9, 0xc0, 0x95, 0x99, 0xcc,
	0xcc, 0x99, 0x95, 0xc0, 0xa9, 0xfc, 0xf0, 0xa5,
	0x30, 0x65, 0x69, 0x3c, 0x55, 0x00, 0x0c, 0x59,
	0x59, 0x0c, 0x00, 0x55, 0x3c, 0x69, 0x65, 0x30,
	0x3c, 0x69, 0x65, 0x30, 0x59, 0x0c, 0x00, 0x55,
	0x55, 0x00, 0x0c, 0x59, 0x30, 0x65, 0x69, 0x3c,
	0xa9, 0xfc, 0xf0, 0xa5, 0xcc, 0x99, 0x95, 0xc0,
	0xc0, 0x95, 0x99, 0xcc, 0xa5, 0xf0, 0xfc, 0xa9,
	0xa9, 0xfc, 0xf0, 0xa5, 0xcc, 0x99, 0x95, 0xc0,
	0xc0, 0x95, 0x99, 0xcc, 0xa5, 0xf0, 0xfc, 0xa9,
	0x3c, 0x69, 0x65, 0x30, 0x59, 0x0c, 0x00, 0x55,
	0x55, 0x00, 0x0c, 0x59, 0x30, 0x65, 0x69, 0x3c,
	0x30, 0x65, 0x69, 0x3c, 0x55, 0x00, 0x0c, 0x59,
	0x59, 0x0c, 0x00, 0x55, 0x3c, 0x69, 0x65, 0x30,
	0xa5, 0xf0, 0xfc, 0xa9, 0xc0, 0x95, 0x99, 0xcc,
	0xcc, 0x99, 0x95, 0xc0, 0xa9, 0xfc, 0xf0, 0xa5,
	0x0c, 0x59, 0x55, 0x00, 0x69, 0x3c, 0x30, 0x65,
	0x65, 0x30, 0x3c, 0x69, 0x00, 0x55, 0x59, 0x0c,
	0x99, 0xcc, 0xc0, 0x95, 0xfc, 0xa9, 0xa5, 0xf0,
	0xf0, 0xa5, 0xa9, 0xfc, 0x95, 0xc0, 0xcc, 0x99,
	0x95, 0xc0, 0xcc, 0x99, 0xf0, 0xa5, 0xa9, 0xfc,
	0xfc, 0xa9, 0xa5, 0xf0, 0x99, 0xcc, 0xc0, 0x95,
	0x00, 0x55, 0x59, 0x0c, 0x65, 0x30, 0x3c, 0x69,
	0x69, 0x3c, 0x30, 0x65, 0x0c, 0x59, 0x55, 0x00,
};

ulong
nandecc(uchar *buf)
{
	int cp, zeros, ones, im, lp, om, i;

	cp = 0xff;
	zeros = 0xff;
	ones = 0xff;
	for (i = 0; i < 256; i++) {
		int tabent = ecctab[buf[i]];

		cp ^= tabent;
		if (tabent & 1) {
			zeros ^= ~i;
			ones ^= i;
		}
	}
	lp = 0;
	for (im = 0x80, om = 0x8000; im; im >>= 1, om >>= 1) {
		if (ones & im)
			lp |= om;
		om >>= 1;
		if (zeros & im)
			lp |= om;
	}
	return (((cp & 0xff) | 3) << 16) | lp;
}

NandEccError
nandecccorrect(uchar *buf, ulong calcecc, ulong *storedecc, int reportbad)
{
	ulong xorecc, mask;
	int k;

	if (calcecc == *storedecc)
		return NandEccErrorGood;
	if (reportbad)
		print("nandecccorrect: calculated ecc %.8lux stored ecc %.8lux\n",
			calcecc, *storedecc);
	xorecc = calcecc ^ *storedecc;
	if (((xorecc ^ (xorecc >> 1)) & CORRECTABLEMASK) == CORRECTABLEMASK) {
		ulong imask;
		ushort out, omask;
		int line, col;

		for (imask = 0x800000, omask = 0x800, out = 0; imask;
		    imask >>= 2, omask >>= 1)
			if (xorecc & imask)
				out |= omask;
		line = out & 0xff;
		col = out >> 9;
		if (reportbad)
			print("nandecccorrect: single bit error line %d col %d\n",
				line, col);
		buf[line] ^= (1 << col);
		*storedecc = calcecc;
		return NandEccErrorOneBit;
	}
	for (mask = 0x800000, k = 0; mask; mask >>= 1)
		if (mask & xorecc)
			k++;
	if (k == 1) {
		if (reportbad)
			print("nandecccorrect: single bit error in ecc\n");
		/* assume the stored ecc was wrong */
		*storedecc = calcecc;
		return NandEccErrorOneBitInEcc;
	}
	if (reportbad)
		print("nandecccorrect: 2 bit error\n");
	return NandEccErrorBad;
}
