/*
 * diag_spi_flash.c
 *
 * Luuk Paulussen
 */


#include <common.h>
#include <diag.h>
#include <spi_flash.h>

#define CONFIG_BM_SPI_BUS	0
#define CONFIG_BM_SPI_CS	0
#define CONFIG_BM_SPI_MAX_HZ	1000000
#define CONFIG_BM_SPI_MODE	SPI_MODE_3

static struct spi_flash *spi_flash = NULL;

static int diag_get_spi_flash(void)
{
	unsigned int max_hz;

	if (getenv("spi_clock"))
		max_hz = simple_strtoul(getenv("spi_clock"), NULL, 16);
	else
		max_hz = CONFIG_BM_SPI_MAX_HZ;

	diag_printf("Probing SPI flash... ");
	if (!spi_flash)
		spi_flash = spi_flash_probe(CONFIG_BM_SPI_BUS, CONFIG_BM_SPI_CS,
					max_hz, CONFIG_BM_SPI_MODE);
	if (!spi_flash) {
		diag_printf("Probe failed!\n");
		return 1;
	}
	diag_printf("Complete\n");

	return 0;
}

static void diag_put_spi_flash(void)
{
	if (spi_flash)
		spi_flash_free(spi_flash);
	spi_flash = NULL;
}

static int diag_spi_flash_do_erased(struct spi_flash *spi_flash, int start, int *erased_flag)
{
	u32 *readPt = (u32 *)(DIAG_FLASHBUFF);
	u32 count = CONFIG_SYS_FLASH_SECT_SIZE / 4; // 4 bytes per u32

	// Always return not erased if something goes wrong.
	*erased_flag = 0;

	if (spi_flash_read(spi_flash, start, CONFIG_SYS_FLASH_SECT_SIZE, (void *)readPt)) {
		diag_printf("Read failed!\n");
		return 1;
	}

	while (count--)
	{
		if (*readPt++ != 0xFFFFFFFF)
		{
			*erased_flag = 0;
			return 0;
		}
	}
	
	*erased_flag = 1;
	return 0;
}

static int diag_spi_flash_erase(int full, int *quit)
{
	int start, size, block_count, blocks_erased, offset, erased_flag;

	if (diag_get_spi_flash())
		return 1;

	if (full) {
		start = 0;
		size = spi_flash->size;
	} else {
		start = DIAG_USER_FLASH_OFFSET;
		size = DIAG_USER_FLASH_SIZE;
	}
	block_count = size / CONFIG_SYS_FLASH_SECT_SIZE;
	blocks_erased = 0;

	diag_print_percentage("Erasing SPI flash... ");
	for (offset = 0; offset < size; offset += CONFIG_SYS_FLASH_SECT_SIZE) {
		diag_spi_flash_do_erased(spi_flash, start + offset, &erased_flag);

		if (!erased_flag) {
			if (spi_flash_erase(spi_flash, start + offset, CONFIG_SYS_FLASH_SECT_SIZE)) {
				diag_printf("Erase failed!\n");
				return 1;
			}
		}
		blocks_erased++;
		diag_update_percentage((blocks_erased * 100) / block_count);

		if (quit && diag_test_quit()) {
			*quit = 1;
			return 0;
		}
	}
	diag_printf("Complete\n");

	return 0;
}

int diag_do_flash_erase(int full, int *quit)
{
	int ret = 0;

	if (diag_get_spi_flash())
		return 1;

	ret = diag_spi_flash_erase(full, quit);

	diag_put_spi_flash();

	return ret;
}

static int diag_one_spi_flash_test(int test, int full, int *quit)
{
	int start, size, block_count, blocks_done, offset, i;
	u8 *testData;
	u8 *flashData;

	if (diag_get_spi_flash())
		return 1;

	if (full) {
		start = 0;
		size = spi_flash->size;
	} else {
		start = DIAG_USER_FLASH_OFFSET;
		size = DIAG_USER_FLASH_SIZE;
	}
	block_count = size / CONFIG_SYS_FLASH_SECT_SIZE;

	diag_spi_flash_erase(full, quit);
	if (quit && diag_test_quit()) {
		*quit = 1;
		return 0;
	}

	diag_memtest_init_make_data(test, 1);

	diag_print_percentage("Making test data:");
	//Just make one sector worth of data.
	diag_memtest_make_data((void *)DIAG_FLASHBUFF, CONFIG_SYS_FLASH_SECT_SIZE,
				test, 1, &diag_update_percentage, quit);

	diag_print_percentage("Writing test data:");
	blocks_done = 0;
	for (offset = 0; offset < size; offset += CONFIG_SYS_FLASH_SECT_SIZE) {
		if (spi_flash_write(spi_flash, start + offset, CONFIG_SYS_FLASH_SECT_SIZE, (void *)DIAG_FLASHBUFF)) {
			diag_printf("Write failed!\n");
			return 1;
		}
		blocks_done++;
		diag_update_percentage((blocks_done * 100) / block_count);
		if (quit && diag_test_quit())
		{
			*quit = 1;
			return 0;
		}
	}
	diag_printf("Complete\n");

	diag_print_percentage("Reading test data:");
	blocks_done = 0;
	for (offset = 0; offset < size; offset += CONFIG_SYS_FLASH_SECT_SIZE) {
		//read one sector of data into memory.
		if (spi_flash_read(spi_flash, start + offset, CONFIG_SYS_FLASH_SECT_SIZE,
		    (void *)(DIAG_FLASHBUFF + CONFIG_SYS_FLASH_SECT_SIZE))) {
			diag_printf("Read failed!\n");
			return 1;
		}

		testData = (u8 *)DIAG_FLASHBUFF;
		flashData = (u8 *)(DIAG_FLASHBUFF + CONFIG_SYS_FLASH_SECT_SIZE);
		for (i = 0; i < CONFIG_SYS_FLASH_SECT_SIZE; i++) {
			if (*testData != *flashData) {
				diag_printf("Error: Bad flash at 0x%08X testData:%02x flashData:%02x\n",
					    offset + i, *testData, *flashData);
				return 1;
			}
			testData++;
			flashData++;
		}
		blocks_done++;
		diag_update_percentage((blocks_done * 100) / block_count);

		if (quit && diag_test_quit())
		{
			*quit = 1;
			return 0;
		}
	}
	diag_printf("Complete\n");

	diag_printf("Re-erasing flash:\n");
	if (diag_spi_flash_erase(full, quit))
		return -1;

	return 0;
}

int diag_do_one_flash_test(int test, int full, int *quit)
{
	int ret;

	if (diag_get_spi_flash())
		return 1;

	ret = diag_one_spi_flash_test(test, full, quit);

	diag_put_spi_flash();

	return ret;
}
