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

/*
 * 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 intel_read_jedec_ids(struct flash_info *info)
{
	info->cmd_reset		= FLASH_CMD_RESET;
	info->manufacturer_id = 0;
	info->device_id       = 0;
	info->device_id2      = 0;

	flash_write_cmd(info, 0, 0, info->cmd_reset);
	flash_write_cmd(info, 0, 0, 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);
	flash_write_cmd(info, 0, 0, info->cmd_reset);
}

/*
 * 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 intel_flash_is_busy(struct flash_info *info, flash_sect_t sect)
{
	return !flash_isset (info, sect, 0, FLASH_STATUS_DONE);
}

static int intel_flash_erase_one(struct flash_info *info, long sect)
{
	flash_write_cmd(info, sect, 0, FLASH_CMD_CLEAR_STATUS);
	flash_write_cmd(info, sect, 0, FLASH_CMD_BLOCK_ERASE);
	flash_write_cmd(info, sect, 0, FLASH_CMD_ERASE_CONFIRM);

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

static void intel_flash_prepare_write(struct flash_info *info)
{
	flash_write_cmd (info, 0, 0, FLASH_CMD_CLEAR_STATUS);
	flash_write_cmd (info, 0, 0, FLASH_CMD_WRITE);
}

#ifdef CONFIG_CFI_BUFFER_WRITE
static int intel_flash_write_cfibuffer(struct flash_info *info,
		unsigned long dest, const u8 *cp, int len)
{
	flash_sect_t sector;
	int cnt;
	int ret;
	void *src = (void*)cp;
	void *dst = (void *)dest;
	/* reduce width due to possible alignment problems */
	const unsigned long ptr = (unsigned long)dest | (unsigned long)cp | info->portwidth;
	const int width = ptr & -ptr;

	sector = find_sector(info, dest);
	flash_write_cmd(info, sector, 0, FLASH_CMD_CLEAR_STATUS);
	flash_write_cmd(info, sector, 0, FLASH_CMD_WRITE_TO_BUFFER);

	ret = flash_generic_status_check(info, sector, info->buffer_write_tout,
					   "write to buffer");
	if (ret)
		return ret;

	/* reduce the number of loops by the width of the port	*/
	cnt = len / width;

	flash_write_cmd(info, sector, 0, cnt - 1);
	while (cnt-- > 0) {
		switch (width) {
		case 1:
			flash_write8(flash_read8(src), dst);
			src += 1, dst += 1;
			break;
		case 2:
			flash_write16(flash_read16(src), dst);
			src += 2, dst += 2;
			break;
		case 4:
			flash_write32(flash_read32(src), dst);
			src += 4, dst += 4;
			break;
		case 8:
			flash_write64(flash_read64(src), dst);
			src += 8, dst += 8;
			break;
		}
	}

	flash_write_cmd(info, sector, 0, FLASH_CMD_WRITE_BUFFER_CONFIRM);
	ret = flash_status_check(info, sector,
					   info->buffer_write_tout,
					   "buffer write");
	return ret;
}
#else
#define intel_flash_write_cfibuffer NULL
#endif /* CONFIG_CFI_BUFFER_WRITE */

static int intel_flash_status_check(struct flash_info *info, flash_sect_t sector,
				    u64 tout, char *prompt)
{
	int ret;

	ret = flash_generic_status_check(info, sector, tout, prompt);
	if (!ret && !flash_isequal(info, sector, 0, FLASH_STATUS_DONE)) {
		ret = -EINVAL;
		printf("Flash %s error at address %lx\n", prompt,
			info->start[sector]);

		if (flash_isset(info, sector, 0, FLASH_STATUS_ECLBS | FLASH_STATUS_PSLBS)) {
			printf("Command Sequence Error.\n");
		} else if (flash_isset (info, sector, 0, FLASH_STATUS_ECLBS)) {
			printf("Block Erase Error.\n");
			ret = -EIO;
		} else if (flash_isset(info, sector, 0, FLASH_STATUS_PSLBS)) {
			printf("Locking Error\n");
		}

		if (flash_isset(info, sector, 0, FLASH_STATUS_DPS)) {
			printf("Block locked.\n");
			ret = -EROFS;
		}

		if (flash_isset(info, sector, 0, FLASH_STATUS_VPENS))
			printf("Vpp Low Error.\n");
	}

	flash_write_cmd(info, sector, 0, info->cmd_reset);

	return ret;
}

static int intel_flash_real_protect(struct flash_info *info, long sector, int prot)
{
	flash_write_cmd(info, sector, 0, FLASH_CMD_CLEAR_STATUS);
	flash_write_cmd(info, sector, 0, FLASH_CMD_PROTECT);

	if (prot)
		flash_write_cmd(info, sector, 0, FLASH_CMD_PROTECT_SET);
	else
		flash_write_cmd(info, sector, 0, FLASH_CMD_PROTECT_CLEAR);

	return 0;
}

static void intel_flash_fixup(struct flash_info *info, struct cfi_qry *qry)
{
}

struct cfi_cmd_set cfi_cmd_set_intel = {
	.flash_write_cfibuffer = intel_flash_write_cfibuffer,
	.flash_erase_one = intel_flash_erase_one,
	.flash_is_busy = intel_flash_is_busy,
	.flash_read_jedec_ids = intel_read_jedec_ids,
	.flash_prepare_write = intel_flash_prepare_write,
	.flash_status_check = intel_flash_status_check,
	.flash_real_protect = intel_flash_real_protect,
	.flash_fixup = intel_flash_fixup,
};
