/* ----------------------------------------------------------------------------
 *         SAM Software Package License
 * ----------------------------------------------------------------------------
 * Copyright (c) 2012, Atmel Corporation
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * - Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the disclaimer below.
 *
 * Atmel's name may not be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * DISCLAIMER: THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE
 * DISCLAIMED. IN NO EVENT SHALL ATMEL BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * ----------------------------------------------------------------------------
 */

/** \file */

/*----------------------------------------------------------------------------
 *        Headers
 *----------------------------------------------------------------------------*/

#include "chip.h"
#include "trace.h"

#include "peripherals/pmecc.h"
#include "peripherals/pmecc_gallois_field_512.h"
#include "peripherals/pmecc_gallois_field_1024.h"

/*--------------------------------------------------------------------------- */
/*         Local types                                                        */
/*--------------------------------------------------------------------------- */

/** PMECC configuration descriptor */
struct _pmecc_descriptor {
	/** Number of Sectors in one Page */
	uint32_t page_size;

	/** The spare area size is equal to (SPARESIZE+1) bytes */
	uint32_t spare_size;

	/** 0 for 512, 1 for 1024 bytes, like in PMECCFG register */
	uint32_t sector_size;

	/** Coded value of ECC bit number correction
	  * 0 (2 bits), 1 (4 bits), 2 (8 bits), 3 (12 bits), 4 (24 bits), 5 (NU)) */
	uint32_t err_bit_nbr_capability;

	/** Real size in bytes of ECC in spare */
	uint32_t ecc_size_byte;

	/** The first byte address of the ECC area */
	uint32_t ecc_start_address;

	/** The last byte address of the ECC area */
	uint32_t ecc_end_address;

	/** NAND Write Access*/
	uint32_t nand_wr;

	/** Spare Enable */
	uint32_t spare_ena;

	/** Automatic Mode */
	uint32_t mode_auto;

	/** The PMECC Module data path Setup Time is set to CLKCTRL+1. */
	uint32_t clk_ctrl;

	/** */
	uint32_t interrupt;

	/** defines the error correcting capability selected at encoding/decoding time */
	int32_t tt;

	/** degree of the remainders, GF(2**mm) */
	int32_t mm;

	/** length of codeword =  nn=2**mm -1 */
	int32_t nn;

	/** Gallois field table */
	const int16_t *alpha_to;

	/** Index of Gallois field table */
	const int16_t *index_of;

	/** */
	int16_t partial_syn[100];

	/** Holds the current syndrome value, an element of that table belongs to the field.*/
	int16_t si[100];

	/** sigma table */
	int16_t smu[PMECC_NB_ERROR_MAX + 2][2 * PMECC_NB_ERROR_MAX + 1];

	/** polynom order */
	int16_t lmu[PMECC_NB_ERROR_MAX + 1];
};

/*--------------------------------------------------------------------------- */
/*         Local variables                                                    */
/*--------------------------------------------------------------------------- */

/** Pmecc decriptor instance */
struct _pmecc_descriptor pmecc_desc;

/*----------------------------------------------------------------------------
 *        Local functions
 *----------------------------------------------------------------------------*/

 /**
 * \brief Build the pseudo syndromes table
 * \param sector Targetted sector.
 */
static void gen_syn(uint32_t sector)
{
	int16_t *remainer;
	uint32_t index;

	remainer = (int16_t*)&HSMC->SMC_REM[sector];
	for (index = 0; index < (uint32_t)pmecc_desc.tt; index++) {
		/* Fill odd syndromes */
		pmecc_desc.partial_syn[1 + (2 * index)] = remainer[index];
	}
}

/**
 * \brief The substitute function evaluates the polynomial remainder,
 * with different values of the field primitive elements.
 */
static uint32_t substitute(void)
{
	int32_t i, j;
	int16_t *si;
	int16_t *p_partial_syn = pmecc_desc.partial_syn;
	const int16_t *alpha_to = pmecc_desc.alpha_to;
	const int16_t *index_of = pmecc_desc.index_of;

	/* si[] is a table that holds the current syndrome value, an element of that table belongs to the field.*/
	si = pmecc_desc.si;

	for (i = 1; i < 2 * PMECC_NB_ERROR_MAX; i++)
		si[i] = 0;

	/* Computation 2t syndromes based on S(x) */
	/* Odd syndromes */
	for (i = 1; i <= 2 * pmecc_desc.tt - 1; i = i + 2) {
		si[i] = 0;
		for (j = 0; j < pmecc_desc.mm; j++) {
			if (p_partial_syn[i] & ((uint16_t)0x1 << j))
				si[i] = alpha_to[(i * j)] ^ si[i];
		}
	}
	/* Even syndrome = (Odd syndrome) ** 2 */
	for (i = 2; i <= 2 * pmecc_desc.tt; i = i + 2) {
		j = i / 2;
		if (si[j] == 0) {
			si[i] = 0;
		} else {
			si[i] = alpha_to[(2 * index_of[si[j]]) % pmecc_desc.nn];
		}
	}
	return 0;
}

/**
 * \brief The substitute function finding the value of the error
 * location polynomial.
 */
static uint32_t get_sigma(void)
{
	uint32_t dmu_0_count;
	int32_t i, j, k;
	int16_t *lmu = pmecc_desc.lmu;
	int16_t *si = pmecc_desc.si;
	int16_t tt = pmecc_desc.tt;

	int32_t mu[PMECC_NB_ERROR_MAX+1]; /* mu */
	int32_t dmu[PMECC_NB_ERROR_MAX+1]; /* discrepancy */
	int32_t delta[PMECC_NB_ERROR_MAX+1]; /* delta order */
	int32_t ro; /* index of largest delta */
	int32_t largest;
	int32_t diff;

	dmu_0_count = 0;

	/* -- First Row -- */

	/* Mu */
	mu[0]  = -1;
	/* Actually -1/2 */
	/* Sigma(x) set to 1 */

	for (i = 0; i < (2 * PMECC_NB_ERROR_MAX + 1); i++)
		pmecc_desc.smu[0][i] = 0;
	pmecc_desc.smu[0][0] = 1;

	/* discrepancy set to 1 */
	dmu[0] = 1;

	/* polynom order set to 0 */
	lmu[0] = 0;

	/* delta set to -1 */
	delta[0]  = (mu[0] * 2 - lmu[0]) >> 1;

	/* -- Second Row -- */

	/* Mu */
	mu[1] = 0;

	/* Sigma(x) set to 1 */
	for (i = 0; i < (2 * PMECC_NB_ERROR_MAX + 1); i++)
		pmecc_desc.smu[1][i] = 0;
	pmecc_desc.smu[1][0] = 1;

	/* discrepancy set to S1 */
	dmu[1] = si[1];

	/* polynom order set to 0 */
	lmu[1] = 0;

	/* delta set to 0 */
	delta[1]  = (mu[1] * 2 - lmu[1]) >> 1;

	/* Init the Sigma(x) last row */
	for (i = 0; i < (2 * PMECC_NB_ERROR_MAX + 1); i++)
		pmecc_desc.smu[tt + 1][i] = 0;

	for (i = 1; i <= tt; i++) {
		mu[i+1] = i << 1;

		/* Compute Sigma (Mu+1)             */
		/* And L(mu)                        */
		/* check if discrepancy is set to 0 */
		if ( dmu[i] == 0) {
			dmu_0_count++;
			if (( tt - (lmu[i] >> 1) - 1) & 0x1) {
				if (dmu_0_count == (uint32_t)((tt - (lmu[i] >> 1) - 1) / 2) + 2) {
					for (j = 0; j <= (lmu[i] >> 1) + 1; j++)
						pmecc_desc.smu[tt+1][j] = pmecc_desc.smu[i][j];
					lmu[tt + 1] = lmu[i];
					return 0;
				}
			} else {
				if (dmu_0_count == (uint32_t)((tt - (lmu[i] >> 1) - 1) / 2) + 1) {
					for (j = 0; j <= (lmu[i] >> 1) + 1; j++)
						pmecc_desc.smu[tt + 1][j] = pmecc_desc.smu[i][j];
					lmu[tt + 1] = lmu[i];
					return 0;
				}
			}

			/* copy polynom */
			for (j = 0; j <= (lmu[i] >> 1); j++)
				pmecc_desc.smu[i + 1][j] = pmecc_desc.smu[i][j];

			/* copy previous polynom order to the next */
			lmu[i + 1] = lmu[i];
		} else {
			/* find largest delta with dmu != 0 */
			ro = 0;
			largest = -1;
			for (j = 0; j < i; j++) {
				if (dmu[j]) {
					if (delta[j] > largest) {
						largest = delta[j];
						ro = j;
					}
				}
			}

			/* compute difference */
			diff = (mu[i] - mu[ro]);

			/* Compute degree of the new smu polynomial */
			if ((lmu[i] >> 1) > ((lmu[ro] >> 1) + diff))
				lmu[i + 1] = lmu[i];
			else
				lmu[i + 1] = ((lmu[ro] >> 1) + diff) * 2;

			/* Init smu[i+1] with 0 */
			for (k = 0; k < (2 * PMECC_NB_ERROR_MAX + 1); k++)
				pmecc_desc.smu[i+1][k] = 0;

			/* Compute smu[i+1] */
			for (k = 0; k <= (lmu[ro] >> 1); k++) {
				if (pmecc_desc.smu[ro][k] && dmu[i])
					pmecc_desc.smu[i + 1][k + diff] = pmecc_desc.alpha_to[(pmecc_desc.index_of[dmu[i]] +
							(pmecc_desc.nn - pmecc_desc.index_of[dmu[ro]]) +
							pmecc_desc.index_of[pmecc_desc.smu[ro][k]]) % pmecc_desc.nn];
			}
			for (k = 0; k <= (lmu[i] >> 1); k++)
				pmecc_desc.smu[i+1][k] ^= pmecc_desc.smu[i][k];
		}

		/*************************************************/
		/*      End Compute Sigma (Mu+1)                 */
		/*      And L(mu)                                */
		/*************************************************/
		/* In either case compute delta */
		delta[i + 1] = (mu[i + 1] * 2 - lmu[i + 1]) >> 1;

		/* Do not compute discrepancy for the last iteration */
		if (i < tt) {
			for (k = 0 ; k <= (lmu[i + 1] >> 1); k++) {
				if (k == 0)
					dmu[i + 1] = si[2 * (i - 1) + 3];
				/* check if one operand of the multiplier is null, its index is -1 */
				else if (pmecc_desc.smu[i+1][k] && si[2 * (i - 1) + 3 - k])
					dmu[i + 1] = pmecc_desc.alpha_to[(pmecc_desc.index_of[pmecc_desc.smu[i + 1][k]] +
							pmecc_desc.index_of[si[2 * (i - 1) + 3 - k]]) % pmecc_desc.nn] ^ dmu[i + 1];
			}
		}
	}
	return 0;
}

/**
 * \brief Init the PMECC Error Location peripheral and start the error
 *        location processing
 * \param sector_size_in_bits Size of the sector in bits.
 * \return Number of errors
 */
static int32_t error_location (uint32_t sector_size_in_bits)
{
	uint32_t alphax;
	uint32_t *sigma;
	uint32_t error_number;
	uint32_t nbr_of_roots;

	/* Disable PMECC Error Location IP */
	HSMC->HSMC_ELDIS |= 0xFFFFFFFF;
	error_number = 0;
	alphax = 0;

	sigma = (uint32_t*)&HSMC->HSMC_SIGMA0;

	for (alphax = 0; alphax <= (uint32_t)(pmecc_desc.lmu[pmecc_desc.tt + 1] >> 1); alphax++) {
		*sigma++ = pmecc_desc.smu[pmecc_desc.tt + 1][alphax];
		error_number++;
	}

	/* Enable error location process */
	HSMC->HSMC_ELCFG |= ((error_number - 1) << 16);
	HSMC->HSMC_ELEN = sector_size_in_bits;

	while ((HSMC->HSMC_ELISR & HSMC_ELISR_DONE) == 0);

	nbr_of_roots = (HSMC->HSMC_ELISR & HSMC_ELISR_ERR_CNT_Msk) >> 8;
	/* Number of roots == degree of smu hence <= tt */
	if (nbr_of_roots == (uint32_t)(pmecc_desc.lmu[pmecc_desc.tt + 1] >> 1))
		return (error_number - 1);

	/* Number of roots not match the degree of smu ==> unable to correct error */
	return -1;
}

/**
 * \brief Correct errors indicated in the PMECCEL error location registers.
 * \param sector_base_address Base address of the sector.
 * \param extra_bytes Number of extra bytes of the sector.(encoded Spare Area, only for the last sector)
 * \param error_nbr Number of error to correct
 * \return Number of errors
 */
static uint32_t error_correction(uint32_t sector_base_address, uint32_t extra_bytes, uint32_t error_nbr)
{
	uint32_t *error_pos;
	uint32_t byte_pos;
	uint32_t bit_pos;
	uint32_t sector_size;
	uint32_t ecc_size;
	uint32_t ecc_end_addr;

	error_pos = (uint32_t*)&HSMC->HSMC_ERRLOC0;

	sector_size = 512 * (((HSMC->HSMC_PMECCFG & HSMC_PMECCFG_SECTORSZ) >> 4) + 1);

	/* Get number of ECC bytes */
	ecc_end_addr = HSMC->HSMC_PMECCEADDR;
	ecc_size = (ecc_end_addr - HSMC->HSMC_PMECCSADDR) + 1;

	while (error_nbr) {
		byte_pos = (*error_pos - 1) / 8;
		bit_pos = (*error_pos - 1) % 8;

		/* If error is located in the data area(not in ECC) */
		if ( byte_pos < (sector_size + extra_bytes)) {
			uint8_t *data_ptr = NULL;

			/* If the error position is before ECC area */
			if ( byte_pos < sector_size + HSMC->HSMC_PMECCSADDR) {
				data_ptr = (uint8_t*)(sector_base_address + byte_pos);
			} else {
				data_ptr = (uint8_t*)(sector_base_address + byte_pos + ecc_size);
			}

			trace_info("Correct error bit @[#Byte %u,Bit# %u]\n\r",
					(unsigned)byte_pos, (unsigned)bit_pos);

			if (*data_ptr & (1 << bit_pos))
				*data_ptr &= (0xFF ^ (1 << bit_pos));
			else
				*data_ptr |= (1 << bit_pos);
		}
		error_pos++;
		error_nbr--;
	}
	return 0;
}

/**
 * \brief Configure the PMECC peripheral
 * \param pPmeccDescriptor Pointer to a PmeccDescriptor instance.
 */
static void _pmecc_configure(void)
{
	/* Disable ECC module */
	HSMC->HSMC_PMECCTRL |= HSMC_PMECCTRL_DISABLE;

	/* Reset the ECC module */
	HSMC->HSMC_PMECCTRL = HSMC_PMECCTRL_RST;
	HSMC->HSMC_PMECCFG = pmecc_desc.err_bit_nbr_capability |
		pmecc_desc.sector_size |
		pmecc_desc.page_size |
		pmecc_desc.nand_wr |
		pmecc_desc.spare_ena |
		pmecc_desc.mode_auto;
	HSMC->HSMC_PMECCSAREA = pmecc_desc.spare_size - 1;
	HSMC->HSMC_PMECCSADDR = pmecc_desc.ecc_start_address;
	HSMC->HSMC_PMECCEADDR = pmecc_desc.ecc_end_address - 1;

	/* Disable all interrupts */
	HSMC->HSMC_PMECCIDR = 0xFF;

	/* Enable ECC module */
	HSMC->HSMC_PMECCTRL |= HSMC_PMECCTRL_ENABLE;
}


/*----------------------------------------------------------------------------
 *        Export functions
 *----------------------------------------------------------------------------*/

/**
 * \brief This function is able to build Galois Field.
 * \param mm degree of the remainders.
 * \param index_of Pointer to a buffer for index_of table.
 * \param alpha_to Pointer to a buffer for alpha_to table.
 */
void build_gf(uint32_t mm, int32_t* index_of, int32_t* alpha_to)
{
	uint32_t i;
	uint32_t mask;
	uint32_t nn;
	uint32_t p[15];

	nn = (1 << mm) - 1;
	/* set default value */
	for (i = 1; i < mm; i++)
		p[i] = 0;

	/*  1 + X^mm */
	p[0]  = 1;
	p[mm] = 1;

	/*  others  */
	if (mm == 3)
		p[1] = 1;
	else if (mm == 4)
		p[1] = 1;
	else if (mm == 5)
		p[2] = 1;
	else if (mm == 6)
		p[1] = 1;
	else if (mm == 7)
		p[3] = 1;
	else if (mm == 8)
		p[2] = p[3] = p[4] = 1;
	else if (mm == 9)
		p[4] = 1;
	else if (mm == 10)
		p[3] = 1;
	else if (mm == 11)
		p[2] = 1;
	else if (mm == 12)
		p[1] = p[4] = p[6] = 1;
	else if (mm == 13)
		p[1] = p[3] = p[4] = 1;
	else if (mm == 14)
		p[1] = p[6] = p[10] = 1;
	else if (mm == 15)
		p[1] = 1;

	/*-- First of All */
	/*-- build alpha ^ mm it will help to generate the field (primitiv) */
	alpha_to[mm] = 0;
	for (i = 0; i < mm; i++)
		if (p[i])
			alpha_to[mm] |= 1 << i;

	/* Secondly */
	/* Build elements from 0 to mm - 1 */
	/* very easy because degree is less than mm so it is */
	/* just a logical shift ! (only the remainder) */
	mask = 1;
	for (i = 0; i < mm; i++) {
		alpha_to[i] = mask;
		index_of[alpha_to[i]] = i;
		mask <<= 1;
	}

	index_of[alpha_to[mm]] = mm;

	/* use a mask to select the MSB bit of the */
	/* LFSR ! */
	mask >>= 1; /* previous value moust be decremented */

	/* then finish the building */
	for (i = mm + 1; i <= nn; i++) {
		/* check if the msb bit of the lfsr is set */
		if (alpha_to[i-1] & mask)
			/* feedback loop is set */
			alpha_to[i] = alpha_to[mm] ^ ((alpha_to[i-1] ^ mask) << 1);
		else
			/*  only shift is enabled */
			alpha_to[i] = alpha_to[i-1] << 1;
		/*  lookup table */
		//index_of[alpha_to[i]] = i ;
		index_of[alpha_to[i]] = i%nn ;
	}
	/* of course index of 0 is undefined in a multiplicative field */
	index_of[0] = -1;
}

/**
 * \brief Initialize the PMECC peripheral
 * \param sector_size 0 for 512, 1 for 1024.
 * \param ecc_errors_per_sector Coded value of ECC bit number correction(2,4,8,12,24).
 * \param page_data_size Data area size in byte.
 * \param page_spare_size Spare area size in byte.
 * \param ecc_offset_in_spare offset of the first ecc byte in spare zone.
 * \param spare_protected 1: The spare area is protected with the last sector of data.
 *                       0: The spare area is skipped in read or write mode.
 * \return 0 if successful; otherwise returns 1.
 */
uint8_t pmecc_initialize(uint8_t sector_size , uint8_t ecc_errors_per_sector,
		uint32_t page_data_size, uint32_t page_spare_size,
		uint16_t ecc_offset_in_spare, uint8_t spare_protected)
{
	uint8_t nb_sectors_per_page = 0;

	if (ecc_errors_per_sector == 0xFF) {
		/* ONFI 2.2 : a value of 0xff indaicate we must apply a correction on sector > 512 bytes,
		   so we set at the maximum allowed by PMECC 24 bits on 1024 sectors. */
		ecc_errors_per_sector = 24;
		sector_size = 1;  /* 1 for 1024 bytes per sector */
	}

	/* Number of Sectors in one Page */
	switch (sector_size) {
	/* 512 bytes per sector */
	case 0:
		pmecc_desc.sector_size = 0;
		nb_sectors_per_page = page_data_size / 512;
		pmecc_desc.mm = 13;
		pmecc_get_gf_512_tables(&pmecc_desc.alpha_to, &pmecc_desc.index_of);
		break;

	/* 1024 bytes per sector */
	case 1:
		pmecc_desc.sector_size = HSMC_PMECCFG_SECTORSZ;
		nb_sectors_per_page = page_data_size / 1024;
		pmecc_desc.mm = 14;
		pmecc_get_gf_1024_tables(&pmecc_desc.alpha_to, &pmecc_desc.index_of);
		break;
	}

	switch (nb_sectors_per_page) {
	case 1:
		pmecc_desc.page_size = HSMC_PMECCFG_PAGESIZE_PAGESIZE_1SEC;
		break;
	case 2:
		pmecc_desc.page_size = HSMC_PMECCFG_PAGESIZE_PAGESIZE_2SEC;
		break;
	case 4:
		pmecc_desc.page_size = HSMC_PMECCFG_PAGESIZE_PAGESIZE_4SEC;
		break;
	case 8:
		pmecc_desc.page_size = HSMC_PMECCFG_PAGESIZE_PAGESIZE_8SEC;
		break;
	default :
		pmecc_desc.page_size = HSMC_PMECCFG_PAGESIZE_PAGESIZE_1SEC;
		break;
	}

	pmecc_desc.nn = (1 << pmecc_desc.mm) - 1;

	/* Coded value of ECC bit number correction (0 (2 bits), 1 (4 bits), 2 (8 bits), 3 (12 bits), 4 (24 bits), 5 (NU)) */
	switch (ecc_errors_per_sector) {
	case 2:
		pmecc_desc.err_bit_nbr_capability = HSMC_PMECCFG_BCH_ERR_BCH_ERR2;
		break;
	case 4:
		pmecc_desc.err_bit_nbr_capability = HSMC_PMECCFG_BCH_ERR_BCH_ERR4;
		break;
	case 8:
		pmecc_desc.err_bit_nbr_capability = HSMC_PMECCFG_BCH_ERR_BCH_ERR8;
		break;
	case 12:
		pmecc_desc.err_bit_nbr_capability = HSMC_PMECCFG_BCH_ERR_BCH_ERR12;
		break;
	case 24:
		pmecc_desc.err_bit_nbr_capability = HSMC_PMECCFG_BCH_ERR_BCH_ERR24;
		break;
	default:
		pmecc_desc.err_bit_nbr_capability = HSMC_PMECCFG_BCH_ERR_BCH_ERR2;
		ecc_errors_per_sector = 2;
		break;
	}

	/* Real value of ECC bit number correction (2, 4, 8, 12, 24) */
	pmecc_desc.tt = ecc_errors_per_sector;
	if (((pmecc_desc.mm * ecc_errors_per_sector ) % 8 ) == 0) {
		pmecc_desc.ecc_size_byte = ((pmecc_desc.mm * ecc_errors_per_sector ) / 8) * nb_sectors_per_page;
	} else  {
		pmecc_desc.ecc_size_byte = (((pmecc_desc.mm * ecc_errors_per_sector ) / 8 ) + 1 ) * nb_sectors_per_page;
	}
	if (ecc_offset_in_spare <= 2) {
		pmecc_desc.ecc_start_address = PMECC_ECC_DEFAULT_START_ADDR;
	} else {
		pmecc_desc.ecc_start_address = ecc_offset_in_spare;
	}
	pmecc_desc.ecc_end_address = pmecc_desc.ecc_start_address + pmecc_desc.ecc_size_byte;
	if (pmecc_desc.ecc_end_address > page_spare_size) {
		return 1;
	}
	pmecc_desc.spare_size = pmecc_desc.ecc_end_address;

	//pmecc_desc.nand_wr = PMECC_CFG_NANDWR;  /* NAND write access */
	pmecc_desc.nand_wr = 0;  /* NAND Read access */
	if (spare_protected) {
		pmecc_desc.spare_ena = HSMC_PMECCFG_SPAREEN;
	} else {
		pmecc_desc.spare_ena = 0;
	}
	/* PMECC_CFG_AUTO indicates that the spare is error protected. In this case, the ECC computation takes into account the whole spare area
	   minus the ECC area in the ECC computation operation */
	pmecc_desc.mode_auto = 0;
	/* At 133 Mhz, this field must be programmed with 2,
	   indicating that the setup time is 3 clock cycles.*/
	pmecc_desc.clk_ctrl = 2;
	pmecc_desc.interrupt = 0;
	_pmecc_configure();
	return 0;
}

/**
 * \brief Return PMECC page size.
 */
uint32_t pmecc_get_page_size(void)
{
	return pmecc_desc.page_size;
}

/**
 * \brief Return PMECC ecc size.
 */
uint32_t pmecc_get_ecc_bytes(void)
{
	return pmecc_desc.ecc_size_byte;
}

/**
 * \brief Return PMECC ecc start address.
 */
uint32_t pmecc_get_ecc_start_address(void)
{
	return pmecc_desc.ecc_start_address;
}

/**
 * \brief Return PMECC ecc end address.
 */
uint32_t pmecc_get_ecc_end_address(void)
{
	return pmecc_desc.ecc_end_address;
}


typedef uint32_t (*pmecc_correction_algo_t)(Smc *, struct _pmecc_descriptor *, uint32_t, uint32_t);

/**
 * \brief Launch error detection functions and correct corrupted bits.
 * \param pmecc_status Value of the PMECC status register.
 * \param page_buffer Base address of the buffer containing the page to be corrected.
 * \return 0 if all errors have been corrected, 1 if too many errors detected
 */
uint32_t pmecc_correction(uint32_t pmecc_status, uint32_t page_buffer)
{
	uint32_t sector_number = 0;
	uint32_t sector_base_address;
	volatile int32_t error_nbr;

	/* Set the sector size (512 or 1024 bytes) */
	HSMC->HSMC_ELCFG = pmecc_desc.sector_size >> 4;

	while (sector_number < (uint32_t)((1 << ((HSMC->HSMC_PMECCFG & HSMC_PMECCFG_PAGESIZE_Msk) >> 8)))) {
		error_nbr = 0;
		if (pmecc_status & 0x1) {
			sector_base_address = page_buffer + (sector_number * ((pmecc_desc.sector_size >> 4) + 1) * 512);
			gen_syn(sector_number);
			substitute();
			get_sigma();
			error_nbr = error_location((((pmecc_desc.sector_size >> 4) + 1) * 512 * 8) +
					(pmecc_desc.tt * (13 + (pmecc_desc.sector_size >> 4)))); /* number of bits of the sector + ecc */

			if (error_nbr == -1)
				return 1;
			else
				error_correction(sector_base_address, 0, error_nbr); /* Extra byte is 0 */
		}
		sector_number++;
		pmecc_status = pmecc_status >> 1;
	}
	return 0;
}

/**
 * \brief Disable pmecc.
 */
void pmecc_disable(void)
{
	/* Disable ECC module */
	HSMC->HSMC_PMECCTRL |= HSMC_PMECCTRL_DISABLE;
}
