/* $Id: flash.c,v 1.18 2008/12/11 12:18:17 ecd Exp $
 */

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <errno.h>

#include <x49gp.h>
#include <memory.h>
#include <byteorder.h>

#define FLASH_STATE_NORMAL		0

#define FLASH_STATE_UNLOCK1		1
#define FLASH_STATE_UNLOCK2		2

#define FLASH_STATE_ERASE1		3
#define FLASH_STATE_ERASE2		4
#define FLASH_STATE_ERASE3		5

#define FLASH_STATE_SOFTWARE_EXIT1	6
#define FLASH_STATE_SOFTWARE_EXIT2	7
#define FLASH_STATE_CFI_QUERY_EXIT1	8
#define FLASH_STATE_CFI_QUERY_EXIT2	9

#define FLASH_STATE_SOFTWARE_ID		10
#define FLASH_STATE_CFI_QUERY		11
#define FLASH_STATE_WORD_PROG		12

typedef struct {
	void			*data;
	int			state;
	unsigned short		vendor_ID;
	unsigned short		device_ID;
	const unsigned short	*cfi_data;
	uint32_t		cfi_size;
	uint32_t		sector_size;
	uint32_t		block_size;
	int			fd;
	size_t			size;

	uint32_t		iotype;
	uint32_t		offset;
} x49gp_flash_t;

#define SST29VF160_VENDOR_ID	0x00bf
#define SST29VF160_DEVICE_ID	0x2782

#define SST29VF160_SECTOR_SIZE	0x00001000
#define SST29VF160_BLOCK_SIZE	0x00010000
#define SST29VF160_SIZE		0x00200000

static const unsigned short sst29vf160_cfi_data[] =
{
	[0x10] = 0x0051,
	[0x11] = 0x0052,
	[0x12] = 0x0059,
	[0x13] = 0x0001,
	[0x14] = 0x0007,
	[0x15] = 0x0000,
	[0x16] = 0x0000,
	[0x17] = 0x0000,
	[0x18] = 0x0000,
	[0x19] = 0x0000,
	[0x1a] = 0x0000,

	[0x1b] = 0x0027,
	[0x1c] = 0x0036,
	[0x1d] = 0x0000,
	[0x1e] = 0x0000,
	[0x1f] = 0x0004,
	[0x20] = 0x0000,
	[0x21] = 0x0004,
	[0x22] = 0x0006,
	[0x23] = 0x0001,
	[0x24] = 0x0000,
	[0x25] = 0x0001,
	[0x26] = 0x0001,

	[0x27] = 0x0015,
	[0x28] = 0x0001,
	[0x29] = 0x0000,
	[0x2a] = 0x0000,
	[0x2b] = 0x0000,
	[0x2c] = 0x0002,
	[0x2d] = 0x00ff,
	[0x2e] = 0x0001,
	[0x2f] = 0x0010,
	[0x30] = 0x0000,
	[0x31] = 0x003f,
	[0x32] = 0x0000,
	[0x33] = 0x0000,
	[0x34] = 0x0001
};
#define SST29VF160_CFI_SIZE (sizeof(sst29vf160_cfi_data) / sizeof(sst29vf160_cfi_data[0]))

static void
flash_state_reset(x49gp_flash_t *flash)
{
	if (flash->state != FLASH_STATE_NORMAL) {
		cpu_register_physical_memory(0x00000000, SST29VF160_SIZE,
					     flash->offset | flash->iotype | IO_MEM_ROMD);
		flash->state = FLASH_STATE_NORMAL;
	}
}

static uint32_t
flash_get_halfword(x49gp_flash_t *flash, uint32_t offset)
{
	uint8_t *datap = flash->data;
	uint16_t data;

	switch (flash->state) {
	default:
		flash_state_reset(flash);
		/* fall through */

	case FLASH_STATE_NORMAL:
		data = lduw_p(datap + offset);
		break;

	case FLASH_STATE_SOFTWARE_ID:
		if (offset & 2) {
			data = flash->device_ID;
		} else {
			data = flash->vendor_ID;
		}
		break;

	case FLASH_STATE_CFI_QUERY:
		if (offset < flash->cfi_size) {
			data = flash->cfi_data[offset >> 1];
		} else {
			data = 0x0000;
		}
		break;
	}

	return data;
}

static void
flash_put_halfword(x49gp_flash_t *flash, uint32_t offset, uint32_t data)
{
	uint8_t *datap = flash->data;
	uint16_t temp;

	data &= 0xffff;

	switch (flash->state) {
	default:
		flash_state_reset(flash);
		/* fall through */

	case FLASH_STATE_NORMAL:
		if (((offset >> 1) == 0x5555) && ((data & 0xff) == 0xaa)) {
			flash->state = FLASH_STATE_UNLOCK1;
			cpu_register_physical_memory(0x00000000, SST29VF160_SIZE, flash->iotype);
		}
		break;

	case FLASH_STATE_UNLOCK1:
		if (((offset >> 1) == 0x2aaa) && ((data & 0xff) == 0x55)) {
			flash->state = FLASH_STATE_UNLOCK2;
		} else {
			flash_state_reset(flash);
		}
		break;

	case FLASH_STATE_UNLOCK2:
		if ((offset >> 1) == 0x5555) {
			switch (data & 0xff) {
			case 0xa0:
				flash->state = FLASH_STATE_WORD_PROG;
				break;
			case 0x80:
				flash->state = FLASH_STATE_ERASE1;
				break;
			case 0x90:
				flash->state = FLASH_STATE_SOFTWARE_ID;
				break;
			case 0x98:
				flash->state = FLASH_STATE_CFI_QUERY;
				break;
			default:
				flash_state_reset(flash);
				break;
			}
		} else {
			flash_state_reset(flash);
		}
		break;

	case FLASH_STATE_ERASE1:
		if (((offset >> 1) == 0x5555) && ((data & 0xff) == 0xaa)) {
			flash->state = FLASH_STATE_ERASE2;
		} else {
			flash_state_reset(flash);
		}
		break;

	case FLASH_STATE_ERASE2:
		if (((offset >> 1) == 0x2aaa) && ((data & 0xff) == 0x55)) {
			flash->state = FLASH_STATE_ERASE3;
		} else {
			flash_state_reset(flash);
		}
		break;

	case FLASH_STATE_SOFTWARE_EXIT1:
		if (((offset >> 1) == 0x2aaa) && ((data & 0xff) == 0x55)) {
			flash->state = FLASH_STATE_SOFTWARE_EXIT2;
		} else {
			flash->state = FLASH_STATE_SOFTWARE_ID;
		}
		break;

	case FLASH_STATE_SOFTWARE_EXIT2:
		if (((offset >> 1) == 0x5555) && ((data & 0xff) == 0xf0)) {
			flash_state_reset(flash);
		} else {
			flash->state = FLASH_STATE_SOFTWARE_ID;
		}
		break;

	case FLASH_STATE_CFI_QUERY_EXIT1:
		if (((offset >> 1) == 0x2aaa) && ((data & 0xff) == 0x55)) {
			flash->state = FLASH_STATE_CFI_QUERY_EXIT2;
		} else {
			flash->state = FLASH_STATE_CFI_QUERY;
		}
		break;

	case FLASH_STATE_CFI_QUERY_EXIT2:
		if (((offset >> 1) == 0x5555) && ((data & 0xff) == 0xf0)) {
			flash_state_reset(flash);
		} else {
			flash->state = FLASH_STATE_CFI_QUERY;
		}
		break;

	case FLASH_STATE_SOFTWARE_ID:
		if (((offset >> 1) == 0x5555) && ((data & 0xff) == 0xaa)) {
			flash->state = FLASH_STATE_SOFTWARE_EXIT1;
		} else if ((data & 0xff) == 0xf0) {
			flash_state_reset(flash);
		}
		break;

	case FLASH_STATE_CFI_QUERY:
		if (((offset >> 1) == 0x5555) && ((data & 0xff) == 0xaa)) {
			flash->state = FLASH_STATE_CFI_QUERY_EXIT1;
		} else if ((data & 0xff) == 0xf0) {
			flash_state_reset(flash);
		}
		break;

	case FLASH_STATE_WORD_PROG:
		temp = lduw_p(datap + offset);
		stw_p(datap + offset, data & temp);

#ifdef DEBUG_X49GP_FLASH_WRITE
		printf("write FLASH 2 (state %u) at offset %08x: %04x, result: %04x\n",
			flash->state, offset, data, lduw_p(datap + offset));
#endif

		flash_state_reset(flash);
		break;

	case FLASH_STATE_ERASE3:
		switch (data & 0xff) {
		case 0x10:	/* Chip Erase */
#ifdef DEBUG_X49GP_FLASH_WRITE
			printf("erase FLASH %08x %08x\n", 0, SST29VF160_SIZE);
#endif
			memset(datap, 0xff, SST29VF160_SIZE);
			break;

		case 0x30:	/* Sector Erase */
#ifdef DEBUG_X49GP_FLASH_WRITE
			printf("erase FLASH %08x %08x\n",
				(offset & ~(flash->sector_size - 1)),
				flash->sector_size);
#endif
			memset(datap + (offset & ~(flash->sector_size - 1)), 0xff, flash->sector_size);

#ifdef DEBUG_X49GP_FLASH_WRITE
			printf("erase FLASH %08x: %04x, %08x: %04x, %08x: %04x\n",
				offset, lduw_p(datap + offset),
				offset + 0x800, lduw_p(datap + offset + 0x800),
				offset + 0xffc, lduw_p(datap + offset + 0xffc));
#endif
			break;

		case 0x50:	/* Block Erase */
#ifdef DEBUG_X49GP_FLASH_WRITE
			printf("erase FLASH %08x %08x\n",
				(offset & ~(flash->block_size - 1)),
				flash->block_size);
#endif
			memset(datap + (offset & ~(flash->block_size - 1)), 0xff, flash->block_size);
			break;
		default:
			break;
		}

		flash_state_reset(flash);
		break;
	}
}

static uint32_t
flash_readb(void *opaque, target_phys_addr_t offset)
{
	x49gp_flash_t *flash = opaque;
	uint8_t *datap = flash->data;
	unsigned short temp;
	uint32_t shift;
	unsigned char data;

#ifdef QEMU_OLD
	offset -= (target_phys_addr_t) phys_ram_base + flash->offset;
#endif

	if (flash->state == FLASH_STATE_NORMAL) {
		data = *(datap + offset);
	} else {
		temp = flash_get_halfword(flash, offset & ~(1));
		shift = (offset & 1) << 3;
		data = (temp >> shift) & 0xff;
	}

#ifdef DEBUG_X49GP_FLASH_READ
	printf("read  FLASH 1 (state %u) at offset %08lx: %02x\n",
		flash->state, (unsigned long) offset, data);
#endif

	return data;
}

static uint32_t
flash_readw(void *opaque, target_phys_addr_t offset)
{
	x49gp_flash_t *flash = opaque;
	uint8_t *datap = flash->data;
	uint32_t data;

#ifdef QEMU_OLD
	offset -= (target_phys_addr_t) phys_ram_base + flash->offset;
#endif

	if (flash->state == FLASH_STATE_NORMAL) {
		data = lduw_p(datap + offset);
	} else {
		data = flash_get_halfword(flash, offset);
	}

#ifdef DEBUG_X49GP_FLASH_READ
	printf("read  FLASH 2 (state %u) at offset %08lx: %04x\n",
		flash->state, (unsigned long) offset, data);
#endif

	return data;
}

static uint32_t
flash_readl(void *opaque, target_phys_addr_t offset)
{
	x49gp_flash_t *flash = opaque;
	uint8_t *datap = flash->data;
	uint32_t data;

#ifdef QEMU_OLD
	offset -= (target_phys_addr_t) phys_ram_base + flash->offset;
#endif

	if (flash->state == FLASH_STATE_NORMAL) {
		data = ldl_p(datap + offset);
	} else {
		data = (flash_get_halfword(flash, offset + 2) << 16) |
		       (flash_get_halfword(flash, offset + 0) <<  0);
	}

#ifdef DEBUG_X49GP_FLASH_READ
	printf("read  FLASH 4 (state %u) at offset %08lx: %08x\n",
		flash->state, (unsigned long) offset, data);
#endif

	return data;
}

static void
flash_writeb(void *opaque, target_phys_addr_t offset, uint32_t data)
{
	x49gp_flash_t *flash = opaque;
	uint32_t shift;

#ifdef QEMU_OLD
	offset -= (target_phys_addr_t) phys_ram_base + flash->offset;
#endif

	data &= 0xff;

#ifdef DEBUG_X49GP_FLASH_WRITE
	printf("write FLASH 1 (state %u) at offset %08lx: %02x\n",
		flash->state, offset, data);
#endif

	/*
	 * This does not issue read-modify-write, i.e. you will get
	 * broken data in FLASH memory. This would be the case with
	 * real hardware, too.
	 */
	shift = (offset & 1) << 3;
	flash_put_halfword(flash, offset & ~(1), data << shift);
}

static void
flash_writew(void *opaque, target_phys_addr_t offset, uint32_t data)
{
	x49gp_flash_t *flash = opaque;

#ifdef QEMU_OLD
	offset -= (target_phys_addr_t) phys_ram_base + flash->offset;
#endif

	data &= 0xffff;

#ifdef DEBUG_X49GP_FLASH_WRITE
	printf("write FLASH 2 (state %u) at offset %08lx: %04x\n",
		flash->state, offset, data);
#endif

	flash_put_halfword(flash, offset, data);
}

static void
flash_writel(void *opaque, target_phys_addr_t offset, uint32_t data)
{
	x49gp_flash_t *flash = opaque;

#ifdef QEMU_OLD
	offset -= (target_phys_addr_t) phys_ram_base + flash->offset;
#endif

#ifdef DEBUG_X49GP_FLASH_WRITE
	printf("write FLASH 4 (state %u) at offset %08lx: %08x\n",
		flash->state, offset, data);
#endif

	flash_put_halfword(flash, offset + 2, (data >> 16) & 0xffff);
	flash_put_halfword(flash, offset + 0, (data >>  0) & 0xffff);
}

static int
flash_load(x49gp_module_t *module, GKeyFile *key)
{
	x49gp_flash_t *flash = module->user_data;
	char *filename;

#ifdef DEBUG_X49GP_MODULES
	printf("%s: %s:%u\n", module->name, __FUNCTION__, __LINE__);
#endif

	filename = x49gp_module_get_filename(module, key, "filename");
	if (NULL == filename) {
		fprintf(stderr, "%s: %s:%u: key \"filename\" not found\n",
			module->name, __FUNCTION__, __LINE__);
		return -1;
	}

	flash->fd = open(filename, O_RDWR);
	if (flash->fd < 0) {
		fprintf(stderr, "%s: %s:%u: open %s: %s\n",
			module->name, __FUNCTION__, __LINE__,
			filename, strerror(errno));
		g_free(filename);
		return -1;
	}

	flash->data = mmap(phys_ram_base + flash->offset, SST29VF160_SIZE,
			   PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED,
			   flash->fd, 0);
	if (flash->data == (void *) -1) {
		fprintf(stderr, "%s: %s:%u: mmap %s: %s\n",
			module->name, __FUNCTION__, __LINE__,
			filename, strerror(errno));
		g_free(filename);
		close(flash->fd);
		flash->fd = -1;
		return -1;
	}
	flash->size = SST29VF160_SIZE;

	g_free(filename);
	return 0;
}

static int
flash_save(x49gp_module_t *module, GKeyFile *config)
{
	x49gp_flash_t *flash = module->user_data;
	int error;

#ifdef DEBUG_X49GP_MODULES
	printf("%s: %s:%u\n", module->name, __FUNCTION__, __LINE__);
#endif

	error = msync(flash->data, flash->size, MS_ASYNC);
	if (error) {
		fprintf(stderr, "%s:%u: msync: %s\n",
			__FUNCTION__, __LINE__, strerror(errno));
		return error;
	}

	error = fsync(flash->fd);
	if (error) {
		fprintf(stderr, "%s:%u: fsync: %s\n",
			__FUNCTION__, __LINE__, strerror(errno));
		return error;
	}

	return 0;
}

static int
flash_reset(x49gp_module_t *module, x49gp_reset_t reset)
{
	x49gp_flash_t *flash = module->user_data;

#ifdef DEBUG_X49GP_MODULES
	printf("%s: %s:%u\n", module->name, __FUNCTION__, __LINE__);
#endif

	flash_state_reset(flash);
	return 0;
}

static CPUReadMemoryFunc *flash_readfn[] =
{
	flash_readb,
	flash_readw,
	flash_readl
};

static CPUWriteMemoryFunc *flash_writefn[] =
{
	flash_writeb,
	flash_writew,
	flash_writel
};

static int
flash_init(x49gp_module_t *module)
{
	x49gp_flash_t *flash;

#ifdef DEBUG_X49GP_MODULES
	printf("%s: %s:%u\n", module->name, __FUNCTION__, __LINE__);
#endif

	flash = malloc(sizeof(x49gp_flash_t));
	if (NULL == flash) {
		fprintf(stderr, "%s: %s:%u: Out of memory\n",
			module->name, __FUNCTION__, __LINE__);
		return -1;
	}
	memset(flash, 0, sizeof(x49gp_flash_t));

	flash->vendor_ID = SST29VF160_VENDOR_ID;
	flash->device_ID = SST29VF160_DEVICE_ID;
	flash->cfi_data = sst29vf160_cfi_data;
	flash->cfi_size = SST29VF160_CFI_SIZE;
	flash->sector_size = SST29VF160_SECTOR_SIZE;
	flash->block_size = SST29VF160_BLOCK_SIZE;
	flash->fd = -1;

	module->user_data = flash;

#ifdef QEMU_OLD
	flash->iotype = cpu_register_io_memory(0, flash_readfn,
					       flash_writefn, flash);
#else
	flash->iotype = cpu_register_io_memory(flash_readfn,
					       flash_writefn, flash);
#endif

	flash->data = (void *) -1;
	flash->offset = phys_ram_size;
	phys_ram_size += SST29VF160_SIZE;

	cpu_register_physical_memory(0x00000000, SST29VF160_SIZE,
				     flash->offset | flash->iotype | IO_MEM_ROMD);

	return 0;
}

static int
flash_exit(x49gp_module_t *module)
{
	x49gp_flash_t *flash;

#ifdef DEBUG_X49GP_MODULES
	printf("%s: %s:%u\n", module->name, __FUNCTION__, __LINE__);
#endif

	if (module->user_data) {
		flash = module->user_data;

		if (flash->data != (void *) -1) {
			munmap(flash->data, flash->size);
		}
		if (flash->fd) {
			close(flash->fd);
		}

		free(flash);
	}

	x49gp_module_unregister(module);
	free(module);

	return 0;
}

int
x49gp_flash_init(x49gp_t *x49gp)
{
	x49gp_module_t *module;

	if (x49gp_module_init(x49gp, "flash", flash_init, flash_exit,
			      flash_reset, flash_load, flash_save, NULL,
			      &module)) {
		return -1;
	}

	return x49gp_module_register(module);
}