/*
 * Modified verion of yramsim.c.
 */
#include "yaffs-rtems-flashsim.h"

#include <string.h>
#include "../../yaffs_guts.h"



#define N_RAM_SIM_DEVS	1

#define DATA_SIZE	2048
#define SPARE_SIZE	64
#define PAGE_SIZE	(DATA_SIZE + SPARE_SIZE)
#define PAGES_PER_BLOCK	64


#if 0
#define dout printf
#else
#define dout(...) do { } while(0)
#endif



uint32_t stats_reads;
uint32_t stats_writes;
uint32_t stats_erases;

typedef struct {
	unsigned char page[PAGES_PER_BLOCK][PAGE_SIZE];
	unsigned blockOk;
} Block;

typedef struct {
	Block **blockList;
	int nBlocks;
} SimData;


SimData *simDevs[N_RAM_SIM_DEVS];

static SimData *DevToSim(struct yaffs_dev *dev)
{
	return (SimData*)(dev->driver_context);
}


static void CheckInitialised(void)
{

}

static int yramsim_erase_internal(SimData *sim, unsigned blockId,int force)
{
	if(blockId < 0 || blockId >= sim->nBlocks){
		return 0;
	}

	if(!sim->blockList[blockId]){
		return 0;
	}

	if(!force && !sim->blockList[blockId]->blockOk){
		return 0;
	}

	memset(sim->blockList[blockId],0xff,sizeof(Block));
	sim->blockList[blockId]->blockOk = 1;

	return 1;
}




static int yramsim_initialise(struct yaffs_dev *dev)
{
	SimData *sim = DevToSim(dev);
	Block **blockList = sim->blockList;
	return blockList != NULL;
}


static int yramsim_deinitialise(struct yaffs_dev *dev)
{
	return 1;
}

static int yramsim_rd_chunk (struct yaffs_dev *dev, int pageId,
					  u8 *data, int dataLength,
					  u8 *spare, int spareLength,
					  enum yaffs_ecc_result *ecc_result)
{
	SimData *sim = DevToSim(dev);
	Block **blockList = sim->blockList;

	unsigned blockId = pageId / PAGES_PER_BLOCK;
	unsigned pageOffset = pageId % PAGES_PER_BLOCK;
	unsigned char * d;
	unsigned char *s;

	stats_reads++;

	if(blockId >= sim->nBlocks ||
	   pageOffset >= PAGES_PER_BLOCK ||
	   dataLength >DATA_SIZE ||
	   spareLength > SPARE_SIZE ||
	   !blockList[blockId]->blockOk){
		   return YAFFS_FAIL;
	}

	d = blockList[blockId]->page[pageOffset];
	s = d + DATA_SIZE;

	if(data)
		memcpy(data,d,dataLength);

	if(spare)
		memcpy(spare,s,spareLength);

	if (ecc_result)
		*ecc_result  = YAFFS_ECC_RESULT_NO_ERROR;

	return YAFFS_OK;
}

static int yramsim_wr_chunk (struct yaffs_dev *dev, int pageId,
					   const u8 *data, int dataLength,
					   const u8 *spare, int spareLength)
{
	SimData *sim = DevToSim(dev);
	Block **blockList = sim->blockList;

	unsigned blockId = pageId / PAGES_PER_BLOCK;
	unsigned pageOffset = pageId % PAGES_PER_BLOCK;
	unsigned char * d;
	unsigned char *s;

	dout("wr_chunk\n");

	stats_writes++;

	if(blockId >= sim->nBlocks ||
	   pageOffset >= PAGES_PER_BLOCK ||
	   dataLength >DATA_SIZE ||
	   spareLength > SPARE_SIZE ||
	   !blockList[blockId]->blockOk){
		   return YAFFS_FAIL;
	}

	d = blockList[blockId]->page[pageOffset];
	s = d + DATA_SIZE;

	if(data)
		memcpy(d,data,dataLength);

	if(spare)
		memcpy(s,spare,spareLength);

	return YAFFS_OK;
}


static int yramsim_erase(struct yaffs_dev *dev, int blockId)
{
	SimData *sim = DevToSim(dev);

	stats_erases++;

	CheckInitialised();
	return yramsim_erase_internal(sim,blockId,0);
}

static int yramsim_check_block_bad(struct yaffs_dev *dev, int blockId)
{
	SimData *sim = DevToSim(dev);
	Block **blockList = sim->blockList;
	if(blockId >= sim->nBlocks){
		return YAFFS_FAIL;
	}

	return blockList[blockId]->blockOk ? YAFFS_OK : YAFFS_FAIL;
}

static int yramsim_mark_block_bad(struct yaffs_dev *dev, int blockId)
{
	SimData *sim = DevToSim(dev);
	Block **blockList = sim->blockList;
	if(blockId >= sim->nBlocks){
		return YAFFS_FAIL;
	}

	blockList[blockId]->blockOk = 0;

	return YAFFS_OK;
}


static SimData *yramsim_alloc_sim_data(u32 devId, u32 nBlocks)
{
	int ok = 1;

	Block **blockList;
	SimData *sim;
	Block *b;
	u32 i;

	if(devId >= N_RAM_SIM_DEVS)
		return NULL;

	sim = simDevs[devId];

	if(sim)
		return sim;

	sim = malloc(sizeof (SimData));
	if(!sim)
		return NULL;

	simDevs[devId] = sim;

	blockList = malloc(nBlocks * sizeof(Block *));

	sim->blockList = blockList;
	sim->nBlocks = nBlocks;
	if(!blockList){
		free(sim);
		return NULL;
	}

	for(i = 0; i < nBlocks; i++)
		blockList[i] = NULL;

	for(i = 0; i < nBlocks && ok; i++){
		b=  malloc(sizeof(Block));
		if(b){
			blockList[i] = b;
			yramsim_erase_internal(sim,i,1);
		}
		else
			ok = 0;
	}

	if(!ok){
		for(i = 0; i < nBlocks; i++)
			if(blockList[i]){
				free(blockList[i]);
				blockList[i] = NULL;
			}
		free(blockList);
		blockList = NULL;
		free(sim);
		sim = NULL;
	}

	return sim;
}


struct yaffs_dev *yramsim_CreateRamSim(const YCHAR *name,
				u32 devId, u32 nBlocks,
				u32 start_block, u32 end_block)
{
	SimData *sim;
	struct yaffs_dev *dev;
	struct yaffs_param *p;
	struct yaffs_driver *d;

	sim = yramsim_alloc_sim_data(devId, nBlocks);

	dev = malloc(sizeof(*dev));

	if(!sim || !dev){
		free(sim);
		free(dev);
		printf("Flash Sim creation failed. sim = %p, dev = %p\n",
				sim, dev);
		return NULL;
	}

	memset(dev, 0, sizeof(*dev));

	if(start_block >= sim->nBlocks)
		start_block = 0;
	if(end_block == 0 || end_block >= sim->nBlocks)
		end_block = sim->nBlocks - 1;

	p = &dev->param;
	p->name = strdup(name);
	p->start_block = start_block;
	p->end_block = end_block;
	p->total_bytes_per_chunk = DATA_SIZE;
	p->spare_bytes_per_chunk= SPARE_SIZE;
	p->chunks_per_block = PAGES_PER_BLOCK;
	p->n_reserved_blocks = 2;
	p->use_nand_ecc = 1;
	p->inband_tags = 0;
	p->is_yaffs2 = 1;

	d= &dev->drv;
	d->drv_initialise_fn = yramsim_initialise;
	d->drv_deinitialise_fn = yramsim_deinitialise;
	d->drv_read_chunk_fn = yramsim_rd_chunk;
	d->drv_write_chunk_fn = yramsim_wr_chunk;
	d->drv_erase_fn = yramsim_erase;
	d->drv_check_bad_fn = yramsim_check_block_bad;
	d->drv_mark_bad_fn = yramsim_mark_block_bad;

	dev->driver_context= (void *)sim;

	printf("Created simulated flash device %p\n", dev);
	return dev;
}

struct yaffs_dev *yaffs_rtems_flashsim_setup(void)
{
	return yramsim_CreateRamSim("ramsim",
				0, 100,
				0, 99);
}


void yaffs_rtems_flashsim_dump_status(void)
{
	printf("\nFlashsim stats\n");
	printf("reads.....%d\n", stats_reads);
	printf("writes....%d\n", stats_writes);
	printf("erases....%d\n", stats_erases);
}
