#include <common.h>
#include <stdio.h>
#include "cfi_flash.h"

/*-----------------------------------------------------------------------
 * Reverse the order of the erase regions in the CFI QRY structure.
 * This is needed for chips that are either a) correctly detected as
 * top-boot, or b) buggy.
 */
static void cfi_reverse_geometry(struct cfi_qry *qry)
{
	unsigned int i, j;
	u32 tmp;

	for (i = 0, j = qry->num_erase_regions - 1; i < j; i++, j--) {
		tmp = qry->erase_region_info[i];
		qry->erase_region_info[i] = qry->erase_region_info[j];
		qry->erase_region_info[j] = tmp;
	}
}

static void flash_unlock_seq(struct flash_info *info)
{
	flash_write_cmd (info, 0, info->addr_unlock1, AMD_CMD_UNLOCK_START);
	flash_write_cmd (info, 0, info->addr_unlock2, AMD_CMD_UNLOCK_ACK);
}

/*
 * read jedec ids from device and set corresponding fields in info struct
 *
 * Note: assume cfi->vendor, cfi->portwidth and cfi->chipwidth are correct
 *
*/
static void amd_read_jedec_ids(struct flash_info *info)
{
	info->cmd_reset		= AMD_CMD_RESET;
	info->manufacturer_id = 0;
	info->device_id       = 0;
	info->device_id2      = 0;

	/* calculate command offsets as in the Linux driver */
	info->addr_unlock1 = 0x555;
	info->addr_unlock2 = 0x2AA;

	/*
	 * modify the unlock address if we are in compatibility mode
	 */
	if (	/* x8/x16 in x8 mode */
		((info->chipwidth == FLASH_CFI_BY8) &&
			(info->interface == FLASH_CFI_X8X16)) ||
		/* x16/x32 in x16 mode */
		((info->chipwidth == FLASH_CFI_BY16) &&
			(info->interface == FLASH_CFI_X16X32)))
	{
		info->addr_unlock1 = 0xaaa;
		info->addr_unlock2 = 0x555;
	}

	flash_write_cmd(info, 0, 0, info->cmd_reset);
	flash_unlock_seq(info);
	flash_write_cmd(info, 0, info->addr_unlock1, FLASH_CMD_READ_ID);
	udelay(1000); /* some flash are slow to respond */

	info->manufacturer_id = jedec_read_mfr(info);
	info->device_id = flash_read_uchar(info,
					FLASH_OFFSET_DEVICE_ID);
	if (info->device_id == 0x7E) {
		/* AMD 3-byte (expanded) device ids */
		info->device_id2 = flash_read_uchar(info,
					FLASH_OFFSET_DEVICE_ID2);
		info->device_id2 <<= 8;
		info->device_id2 |= flash_read_uchar(info,
					FLASH_OFFSET_DEVICE_ID3);
	}
	flash_write_cmd(info, 0, 0, info->cmd_reset);
}

static int flash_toggle(struct flash_info *info, flash_sect_t sect,
		unsigned int offset, u8 cmd)
{
	void *addr;
	cfiword_t cword;
	int retval;

	addr = flash_make_addr (info, sect, offset);
	flash_make_cmd (info, cmd, &cword);

	if (bankwidth_is_1(info)) {
		retval = flash_read8(addr) != flash_read8(addr);
	} else if (bankwidth_is_2(info)) {
		retval = flash_read16(addr) != flash_read16(addr);
	} else if (bankwidth_is_4(info)) {
		retval = flash_read32(addr) != flash_read32(addr);
	} else if (bankwidth_is_8(info)) {
		retval = ( (flash_read32( addr ) != flash_read32( addr )) ||
			   (flash_read32(addr+4) != flash_read32(addr+4)) );
	} else {
		retval = 0;
	}

	return retval;
}

/*
 * flash_is_busy - check to see if the flash is busy
 * This routine checks the status of the chip and returns true if the chip is busy
 */
static int amd_flash_is_busy(struct flash_info *info, flash_sect_t sect)
{
	return flash_toggle (info, sect, 0, AMD_STATUS_TOGGLE);
}

static int amd_flash_erase_one(struct flash_info *info, long sect)
{
	flash_unlock_seq(info);
	flash_write_cmd (info, 0, info->addr_unlock1, AMD_CMD_ERASE_START);
	flash_unlock_seq(info);
	flash_write_cmd (info, sect, 0, AMD_CMD_ERASE_SECTOR);

	return flash_status_check(info, sect, info->erase_blk_tout, "erase");
}

static void amd_flash_prepare_write(struct flash_info *info)
{
	flash_unlock_seq(info);
	flash_write_cmd (info, 0, info->addr_unlock1, AMD_CMD_WRITE);
}

#ifdef CONFIG_CFI_BUFFER_WRITE
static int amd_flash_write_cfibuffer(struct flash_info *info, unsigned long dest,
		const u8 *cp, int len)
{
	flash_sect_t sector;
	int cnt;
	void *src = (void *)cp;
	void *dst = (void *)dest;
	cfiword_t cword;

	sector = find_sector (info, dest);

	flash_unlock_seq(info);
	flash_make_cmd (info, AMD_CMD_WRITE_TO_BUFFER, &cword);
	flash_write_word(info, cword, (void *)dest);

	if (bankwidth_is_1(info)) {
		cnt = len;
		flash_write_cmd(info, sector, 0, (u32)cnt - 1);
		while (cnt-- > 0) {
			flash_write8(flash_read8(src), dst);
			src += 1, dst += 1;
		}
	} else if (bankwidth_is_2(info)) {
		cnt = len >> 1;
		flash_write_cmd(info, sector, 0, (u32)cnt - 1);
		while (cnt-- > 0) {
			flash_write16(flash_read16(src), dst);
			src += 2, dst += 2;
		}
	} else if (bankwidth_is_4(info)) {
		cnt = len >> 2;
		flash_write_cmd(info, sector, 0, (u32)cnt - 1);
		while (cnt-- > 0) {
			flash_write32(flash_read32(src), dst);
			src += 4, dst += 4;
		}
	} else if (bankwidth_is_8(info)) {
		cnt = len >> 3;
		flash_write_cmd(info, sector, 0, (u32)cnt - 1);
		while (cnt-- > 0) {
			flash_write64(flash_read64(src), dst);
			src += 8, dst += 8;
		}
	}

	flash_write_cmd(info, sector, 0, AMD_CMD_WRITE_BUFFER_CONFIRM);

	return flash_status_check(info, sector, info->buffer_write_tout,
					   "buffer write");
}
#else
#define amd_flash_write_cfibuffer NULL
#endif /* CONFIG_CFI_BUFFER_WRITE */

static int amd_flash_real_protect(struct flash_info *info, long sector, int prot)
{
	if (info->manufacturer_id != (u8)ATM_MANUFACT)
		return 0;

	if (prot) {
		flash_unlock_seq (info);
		flash_write_cmd (info, 0, info->addr_unlock1,
				 ATM_CMD_SOFTLOCK_START);
		flash_unlock_seq (info);
		flash_write_cmd (info, sector, 0, ATM_CMD_LOCK_SECT);
	} else {
		flash_write_cmd (info, 0, info->addr_unlock1,
				 AMD_CMD_UNLOCK_START);
		if (info->device_id == ATM_ID_BV6416)
			flash_write_cmd (info, sector, 0,
					 ATM_CMD_UNLOCK_SECT);
	}

	return 0;
}

/*
 * Manufacturer-specific quirks. Add workarounds for geometry
 * reversal, etc. here.
 */
static void flash_fixup_amd(struct flash_info *info, struct cfi_qry *qry)
{
	/* check if flash geometry needs reversal */
	if (qry->num_erase_regions > 1) {
		/* reverse geometry if top boot part */
		if (info->cfi_version < 0x3131) {
			/* CFI < 1.1, try to guess from device id */
			if ((info->device_id & 0x80) != 0)
				cfi_reverse_geometry(qry);
		} else if (flash_read_uchar(info, info->ext_addr + 0xf) == 3) {
			/* CFI >= 1.1, deduct from top/bottom flag */
			/* note: ext_addr is valid since cfi_version > 0 */
			cfi_reverse_geometry(qry);
		}
	}
}

static void flash_fixup_atmel(struct flash_info *info, struct cfi_qry *qry)
{
	int reverse_geometry = 0;

	/* Check the "top boot" bit in the PRI */
	if (info->ext_addr && !(flash_read_uchar(info, info->ext_addr + 6) & 1))
		reverse_geometry = 1;

	/* AT49BV6416(T) list the erase regions in the wrong order.
	 * However, the device ID is identical with the non-broken
	 * AT49BV642D since u-boot only reads the low byte (they
	 * differ in the high byte.) So leave out this fixup for now.
	 */
	if (info->device_id == 0xd6 || info->device_id == 0xd2)
		reverse_geometry = !reverse_geometry;

	if (reverse_geometry)
		cfi_reverse_geometry(qry);
}

static void amd_flash_fixup(struct flash_info *info, struct cfi_qry *qry)
{
	/* Do manufacturer-specific fixups */
	switch (info->manufacturer_id) {
	case 0x0001:
		flash_fixup_amd(info, qry);
		break;
	case 0x001f:
		flash_fixup_atmel(info, qry);
		break;
        }
}

struct cfi_cmd_set cfi_cmd_set_amd = {
	.flash_write_cfibuffer = amd_flash_write_cfibuffer,
	.flash_erase_one = amd_flash_erase_one,
	.flash_is_busy = amd_flash_is_busy,
	.flash_read_jedec_ids = amd_read_jedec_ids,
	.flash_prepare_write = amd_flash_prepare_write,
	.flash_status_check = flash_generic_status_check,
	.flash_real_protect = amd_flash_real_protect,
	.flash_fixup = amd_flash_fixup,
};
