/*
 * (C) Copyright 2008 Andre McCurdy, NXP Semiconductors
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of
 * the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 * MA 02111-1307 USA
 */

#include <common.h>

#if (CONFIG_COMMANDS & CFG_CMD_NAND) && ! defined (CFG_NAND_LEGACY)

#include <linux/mtd/mtd.h>

#if 0
#define UBOOT_ECC_BUG_COMPATIBLE
#endif

#if defined (CONFIG_STB225) && (CONFIG_STB225 == 1)
extern unsigned int nand_ecc_generate (const unsigned char *data);
#else
static unsigned int nand_ecc_generate (const unsigned char *data)
{
	static const unsigned char plut[256] =
	{
		0x00, 0x2b, 0x2d, 0x06, 0x33, 0x18, 0x1e, 0x35, 0x35, 0x1e, 0x18, 0x33, 0x06, 0x2d, 0x2b, 0x00,
		0x4b, 0x60, 0x66, 0x4d, 0x78, 0x53, 0x55, 0x7e, 0x7e, 0x55, 0x53, 0x78, 0x4d, 0x66, 0x60, 0x4b,
		0x4d, 0x66, 0x60, 0x4b, 0x7e, 0x55, 0x53, 0x78, 0x78, 0x53, 0x55, 0x7e, 0x4b, 0x60, 0x66, 0x4d,
		0x06, 0x2d, 0x2b, 0x00, 0x35, 0x1e, 0x18, 0x33, 0x33, 0x18, 0x1e, 0x35, 0x00, 0x2b, 0x2d, 0x06,
		0x53, 0x78, 0x7e, 0x55, 0x60, 0x4b, 0x4d, 0x66, 0x66, 0x4d, 0x4b, 0x60, 0x55, 0x7e, 0x78, 0x53,
		0x18, 0x33, 0x35, 0x1e, 0x2b, 0x00, 0x06, 0x2d, 0x2d, 0x06, 0x00, 0x2b, 0x1e, 0x35, 0x33, 0x18,
		0x1e, 0x35, 0x33, 0x18, 0x2d, 0x06, 0x00, 0x2b, 0x2b, 0x00, 0x06, 0x2d, 0x18, 0x33, 0x35, 0x1e,
		0x55, 0x7e, 0x78, 0x53, 0x66, 0x4d, 0x4b, 0x60, 0x60, 0x4b, 0x4d, 0x66, 0x53, 0x78, 0x7e, 0x55,
		0x55, 0x7e, 0x78, 0x53, 0x66, 0x4d, 0x4b, 0x60, 0x60, 0x4b, 0x4d, 0x66, 0x53, 0x78, 0x7e, 0x55,
		0x1e, 0x35, 0x33, 0x18, 0x2d, 0x06, 0x00, 0x2b, 0x2b, 0x00, 0x06, 0x2d, 0x18, 0x33, 0x35, 0x1e,
		0x18, 0x33, 0x35, 0x1e, 0x2b, 0x00, 0x06, 0x2d, 0x2d, 0x06, 0x00, 0x2b, 0x1e, 0x35, 0x33, 0x18,
		0x53, 0x78, 0x7e, 0x55, 0x60, 0x4b, 0x4d, 0x66, 0x66, 0x4d, 0x4b, 0x60, 0x55, 0x7e, 0x78, 0x53,
		0x06, 0x2d, 0x2b, 0x00, 0x35, 0x1e, 0x18, 0x33, 0x33, 0x18, 0x1e, 0x35, 0x00, 0x2b, 0x2d, 0x06,
		0x4d, 0x66, 0x60, 0x4b, 0x7e, 0x55, 0x53, 0x78, 0x78, 0x53, 0x55, 0x7e, 0x4b, 0x60, 0x66, 0x4d,
		0x4b, 0x60, 0x66, 0x4d, 0x78, 0x53, 0x55, 0x7e, 0x7e, 0x55, 0x53, 0x78, 0x4d, 0x66, 0x60, 0x4b,
		0x00, 0x2b, 0x2d, 0x06, 0x33, 0x18, 0x1e, 0x35, 0x35, 0x1e, 0x18, 0x33, 0x06, 0x2d, 0x2b, 0x00
	};

	int i;
	unsigned int d;
	unsigned int cp = 0;
	unsigned int lpo = 0;
	unsigned int lpe;
	unsigned int lp;

	for (i = 256; i < 512; i++) {
		d = *data++;
		cp ^= plut[d];
		if (plut[d] & 0x01)
			lpo ^= i;
	}

	lp = 0;
	lpe = (lpo >> 8) ? ~lpo : lpo;
	for (i = 0; i < 8; i++)
		lp |= (((1 << i) & lpe) << i) | (((1 << i) & lpo) << (i + 1));

	return ((lp << 8) | ((cp >> 1) << 2)) ^ 0x00FFFFFF;
}
#endif

int nand_calculate_ecc (struct mtd_info *mtd, const unsigned char *data, unsigned char *ecc)
{
	unsigned int ecc_calc = nand_ecc_generate (data);

	ecc[0] = (ecc_calc >> 16) & 0xFF;	/* lp upper */
	ecc[1] = (ecc_calc >> 8) & 0xFF;	/* lp lower */
	ecc[2] = (ecc_calc >> 0) & 0xFF;	/* cp */

	return 0;
}

static unsigned int popcount (unsigned int x)
{
	unsigned int n = 0;

	while (x) {
		x = x & (x - 1);
		n++;
	}

	return n;
}

int nand_correct_data (struct mtd_info *mtd, unsigned char *data, unsigned char *ecc_read_bytes, unsigned char *ecc_calc_bytes)
{
	unsigned int ep;
	unsigned int mask;
	unsigned int ecc_read;
	unsigned int ecc_calc;
	unsigned int ecc_xor;

	ecc_read = ecc_read_bytes[0];				/* lp upper */
	ecc_read = (ecc_read << 8) | ecc_read_bytes[1]; 	/* lp lower */
	ecc_read = (ecc_read << 8) | ecc_read_bytes[2]; 	/* cp */

	ecc_calc = ecc_calc_bytes[0];				/* lp upper */
	ecc_calc = (ecc_calc << 8) | ecc_calc_bytes[1]; 	/* lp lower */
	ecc_calc = (ecc_calc << 8) | ecc_calc_bytes[2]; 	/* cp */

#if defined (UBOOT_ECC_BUG_COMPATIBLE)

	ecc_xor = ecc_read ^ ecc_calc;

	if (ecc_xor == 0)
		return 0;					/* no errors */

	if (((ecc_xor ^ (ecc_xor >> 1)) & 0x00555554) == 0x00555554) {
		ep = 0;
		for (mask = 0x00800000; mask > 0x02; mask >>= 2) {
			ep <<= 1;
			if (ecc_xor & mask)
				ep |= 1;
		}
		data [ep >> 3] ^= (1 << (ep & 0x07));
		return 1;					/* single bit (ie correctable) error in data */
	}

#else

	ecc_xor = (ecc_read ^ ecc_calc) >> 2;

	if (ecc_xor == 0)
		return 0;					/* no errors */

	if (((ecc_xor ^ (ecc_xor >> 1)) & 0x00155555) == 0x00155555) {
		ep = 0;
		for (mask = 0x00200000; mask; mask >>= 2) {
			ep <<= 1;
			if (ecc_xor & mask)
				ep |= 1;
		}
		data [ep >> 3] ^= (1 << (ep & 0x07));
		return 1;					/* single bit (ie correctable) error in data */
	}

#endif

	if (popcount (ecc_xor) == 1) {
		memcpy (ecc_read_bytes, ecc_calc_bytes, 3);
		return 2;					/* error in ecc read from oob data */
	}

	return -1;						/* uncorrectable error */

}

#endif

