/*
 * RT-Thread Device Interface for uffs
 */

#include <rtthread.h>
#include <rtdevice.h>
#include "dfs_uffs.h"

static int nand_init_flash(uffs_Device *dev)
{
	return UFFS_FLASH_NO_ERR;
}

static int nand_release_flash(uffs_Device *dev)
{
	return UFFS_FLASH_NO_ERR;
}
static int nand_erase_block(uffs_Device *dev, unsigned block)
{
	int res;
	res = rt_mtd_nand_erase_block(RT_MTD_NAND_DEVICE(dev->_private), block);
	return res == RT_EOK ? UFFS_FLASH_NO_ERR : UFFS_FLASH_IO_ERR;
}

#if defined(RT_UFFS_USE_CHECK_MARK_FUNCITON)
static int nand_check_block(uffs_Device *dev, unsigned block)
{
	int res;
	res = rt_mtd_nand_check_block(RT_MTD_NAND_DEVICE(dev->_private), block);
	return res == RT_EOK ? UFFS_FLASH_NO_ERR : UFFS_FLASH_BAD_BLK;
}

static int nand_mark_badblock(uffs_Device *dev, unsigned block)
{
	int res;
	res = rt_mtd_nand_mark_badblock(RT_MTD_NAND_DEVICE(dev->_private), block);
	return res == RT_EOK ? UFFS_FLASH_NO_ERR : UFFS_FLASH_IO_ERR;
}
#endif

#if (RT_CONFIG_UFFS_ECC_MODE == UFFS_ECC_NONE) || (RT_CONFIG_UFFS_ECC_MODE == UFFS_ECC_SOFT)

static int nand_read_page(
    uffs_Device *dev,
    u32 block, u32 page,
    u8 *data, int data_len,
    u8 * ecc,
    rt_uint8_t *spare, int spare_len)
{
	int res;

	page = block * dev->attr->pages_per_block + page;
	if (data == NULL && spare == NULL)
	{
#if defined(RT_UFFS_USE_CHECK_MARK_FUNCITON)
		RT_ASSERT(0); //should not be here
#else
		/* check block status: bad or good */
		rt_uint8_t spare[UFFS_MAX_SPARE_SIZE];

		rt_memset(spare, 0, UFFS_MAX_SPARE_SIZE);

		rt_mtd_nand_read(RT_MTD_NAND_DEVICE(dev->_private),
		                 page, RT_NULL, 0,
		                 spare, dev->attr->spare_size);//dev->mem.spare_data_size

		res = spare[dev->attr->block_status_offs] == 0xFF ?
				               UFFS_FLASH_NO_ERR : UFFS_FLASH_BAD_BLK;
		return res;
#endif
	}

	rt_mtd_nand_read(RT_MTD_NAND_DEVICE(dev->_private),
	                   	   page, data, data_len, spare, spare_len);

	return UFFS_FLASH_NO_ERR;
}

static int nand_write_page(
    uffs_Device *dev,
    u32 block, u32 page,
    const u8 *data, int data_len,
    const u8 *spare, int spare_len)
{
	int res;

	RT_ASSERT(UFFS_MAX_SPARE_SIZE >= dev->attr->spare_size);

	page = block * dev->attr->pages_per_block + page;

	if (data == NULL && spare == NULL)
	{
#if defined(RT_UFFS_USE_CHECK_MARK_FUNCITON)
		RT_ASSERT(0); //should not be here
#else
		/* mark bad block  */
		rt_uint8_t spare[UFFS_MAX_SPARE_SIZE];

		rt_memset(spare, 0xFF, UFFS_MAX_SPARE_SIZE);
		spare[dev->attr->block_status_offs] =  0x00;

		res = rt_mtd_nand_write(RT_MTD_NAND_DEVICE(dev->_private),
		                        page, RT_NULL, 0,
		                        spare, dev->attr->spare_size);//dev->mem.spare_data_size
		if (res != RT_EOK)
			goto __error;
#endif
	}

	res = rt_mtd_nand_write(RT_MTD_NAND_DEVICE(dev->_private),
	                   	   page,  data, data_len, spare, spare_len);
	if (res != RT_EOK)
		goto __error;

	return UFFS_FLASH_NO_ERR;

__error:
	return UFFS_FLASH_IO_ERR;
}

const uffs_FlashOps nand_ops =
{
	nand_init_flash,	/* InitFlash() */
	nand_release_flash,	/* ReleaseFlash() */
	nand_read_page,		/* ReadPage() */
	NULL,				/* ReadPageWithLayout */
	nand_write_page,	/* WritePage() */
	NULL,				/* WirtePageWithLayout */
#if defined(RT_UFFS_USE_CHECK_MARK_FUNCITON)
	nand_check_block,
	nand_mark_badblock,
#else
	NULL,               /* IsBadBlock(), let UFFS take care of it. */
	NULL,               /* MarkBadBlock(), let UFFS take care of it. */
#endif
	nand_erase_block,	/* EraseBlock() */
};

void uffs_setup_storage(
    struct uffs_StorageAttrSt *attr,
    struct rt_mtd_nand_device * nand)
{
	rt_memset(attr, 0, sizeof(struct uffs_StorageAttrSt));

//	attr->total_blocks = nand->end_block - nand->start_block + 1;/* no use */
	attr->page_data_size = nand->page_size;                /* page data size */
	attr->pages_per_block = nand->pages_per_block;         /* pages per block */
	attr->spare_size = nand->oob_size;                     /* page spare size */
	attr->ecc_opt = RT_CONFIG_UFFS_ECC_MODE;               /* ecc option */
	attr->ecc_size = 0;                                    /* ecc size is 0 , the uffs will calculate the ecc size*/
	attr->block_status_offs = attr->ecc_size;              /* indicate block bad or good, offset in spare */
	attr->layout_opt = RT_CONFIG_UFFS_LAYOUT;              /* let UFFS do the spare layout */
}

#elif  RT_CONFIG_UFFS_ECC_MODE == UFFS_ECC_HW_AUTO

static int WritePageWithLayout(
	uffs_Device *dev, u32 block, u32 page,
	const u8 *data, int data_len,
	const u8 *ecc,  //NULL
	const uffs_TagStore *ts)
{
	int res;
	int spare_len;
	rt_uint8_t spare[UFFS_MAX_SPARE_SIZE];

	RT_ASSERT(UFFS_MAX_SPARE_SIZE >= dev->attr->spare_size);

	page = block * dev->attr->pages_per_block + page;
	spare_len = dev->mem.spare_data_size;

	if (data == NULL && ts == NULL)
	{
#if defined(RT_UFFS_USE_CHECK_MARK_FUNCITON)
		RT_ASSERT(0); //should not be here
#else
		/* mark bad block  */
		rt_memset(spare, 0xFF, UFFS_MAX_SPARE_SIZE);
		spare[dev->attr->block_status_offs] =  0x00;

		res = rt_mtd_nand_write(RT_MTD_NAND_DEVICE(dev->_private),
		                        page, RT_NULL, 0,
		                        spare, dev->attr->spare_size);//dev->mem.spare_data_size
		if (res != RT_EOK)
			goto __error;

		dev->st.io_write++;
		return UFFS_FLASH_NO_ERR;

#endif
	}

	if (data != NULL && data_len != 0)
	{
		RT_ASSERT(data_len == dev->attr->page_data_size);

		dev->st.page_write_count++;
		dev->st.io_write += data_len;
	}

	if (ts != RT_NULL)
	{
		uffs_FlashMakeSpare(dev, ts, RT_NULL, (u8 *)spare);
		dev->st.spare_write_count++;
		dev->st.io_write += spare_len;
	}

	res = rt_mtd_nand_write(RT_MTD_NAND_DEVICE(dev->_private),
	                   	   page,  data, data_len, spare, spare_len);
	if (res != RT_EOK)
		goto __error;

	return UFFS_FLASH_NO_ERR;

__error:
	return UFFS_FLASH_IO_ERR;
}

static URET ReadPageWithLayout(
	uffs_Device *dev, u32 block, u32 page,
	u8* data, int data_len,
	u8 *ecc,              //NULL
	uffs_TagStore *ts,
	u8 *ecc_store)        //NULL
{
	int res = UFFS_FLASH_NO_ERR;
	int spare_len;
	rt_uint8_t spare[UFFS_MAX_SPARE_SIZE];

	RT_ASSERT(UFFS_MAX_SPARE_SIZE >= dev->attr->spare_size);

	page = block * dev->attr->pages_per_block + page;
	spare_len = dev->mem.spare_data_size;

	if (data == RT_NULL && ts == RT_NULL)
	{
#if defined(RT_UFFS_USE_CHECK_MARK_FUNCITON)
		RT_ASSERT(0); //should not be here
#else
		/* check block good or bad */

		rt_mtd_nand_read(RT_MTD_NAND_DEVICE(dev->_private),
		                 page, RT_NULL, 0,
		                 spare, dev->attr->spare_size);//dev->mem.spare_data_size

		dev->st.io_read++;

		res = spare[dev->attr->block_status_offs] == 0xFF ?
				               UFFS_FLASH_NO_ERR : UFFS_FLASH_BAD_BLK;
		return res;
#endif
	}

	if (data != RT_NULL)
	{
		dev->st.io_read += data_len;
		dev->st.page_read_count++;
	}

	res = rt_mtd_nand_read(RT_MTD_NAND_DEVICE(dev->_private),
	                   	   page, data, data_len, spare, spare_len);
	if (res == 0)
		res = UFFS_FLASH_NO_ERR;
	else if (res == -1)
	{
		//TODO ecc correct, add code to use hardware do ecc correct
		res = UFFS_FLASH_ECC_OK;
	}
	else
		res = UFFS_FLASH_ECC_FAIL;

	if (ts != RT_NULL)
	{
		// unload ts and ecc from spare, you can modify it if you like
		uffs_FlashUnloadSpare(dev, (const u8 *)spare, ts, RT_NULL);

		if ((spare[spare_len - 1] == 0xFF) && (res == UFFS_FLASH_NO_ERR))
			res = UFFS_FLASH_NOT_SEALED;

		dev->st.io_read += spare_len;
		dev->st.spare_read_count++;
	}

	return res;
}

const uffs_FlashOps nand_ops =
{
	nand_init_flash,    /* InitFlash() */
	nand_release_flash, /* ReleaseFlash() */
	NULL,               /* ReadPage() */
	ReadPageWithLayout,	/* ReadPageWithLayout */
	NULL,               /* WritePage() */
	WritePageWithLayout,/* WirtePageWithLayout */

#if defined(RT_UFFS_USE_CHECK_MARK_FUNCITON)
	nand_check_block,
	nand_mark_badblock,
#else
	NULL,               /* IsBadBlock(), let UFFS take care of it. */
	NULL,               /* MarkBadBlock(), let UFFS take care of it. */
#endif
	nand_erase_block,   /* EraseBlock() */
};

static rt_uint8_t hw_flash_data_layout[UFFS_SPARE_LAYOUT_SIZE] =
{0x05, 0x08, 0xFF, 0x00};

static rt_uint8_t hw_flash_ecc_layout[UFFS_SPARE_LAYOUT_SIZE] =
{0x00, 0x04, 0xFF, 0x00};

void uffs_setup_storage(
    struct uffs_StorageAttrSt *attr,
    struct rt_mtd_nand_device * nand)
{
	rt_memset(attr, 0, sizeof(struct uffs_StorageAttrSt));

//	attr->total_blocks = nand->end_block - nand->start_block + 1;/* no use */
	attr->page_data_size = nand->page_size;                /* page data size */
	attr->pages_per_block = nand->pages_per_block;         /* pages per block */
	attr->spare_size = nand->oob_size;                     /* page spare size */
	attr->ecc_opt = RT_CONFIG_UFFS_ECC_MODE;               /* ecc option */
	attr->ecc_size = nand->oob_size-nand->oob_free;        /* ecc size */
	attr->block_status_offs = attr->ecc_size;              /* indicate block bad or good, offset in spare */
	attr->layout_opt = RT_CONFIG_UFFS_LAYOUT;              /* let UFFS do the spare layout */

	/* calculate the ecc layout array */
	hw_flash_data_layout[0] = attr->ecc_size + 1; /* ecc size + 1byte block status */
	hw_flash_data_layout[1] = 0x08;
	hw_flash_data_layout[2] = 0xFF;
	hw_flash_data_layout[3] = 0x00;

	hw_flash_ecc_layout[0] = 0;
	hw_flash_ecc_layout[1] = attr->ecc_size;
	hw_flash_ecc_layout[2] = 0xFF;
	hw_flash_ecc_layout[3] = 0x00;

	/* initialize  _uffs_data_layout and _uffs_ecc_layout */
	rt_memcpy(attr->_uffs_data_layout, hw_flash_data_layout, UFFS_SPARE_LAYOUT_SIZE);
	rt_memcpy(attr->_uffs_ecc_layout, hw_flash_ecc_layout, UFFS_SPARE_LAYOUT_SIZE);

	attr->data_layout = attr->_uffs_data_layout;
	attr->ecc_layout = attr->_uffs_ecc_layout;
}
#endif
