/*
 * Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 *
 * SPI flash driver for Chrome EC.
 */

#include "common.h"
#include "console.h"
#include "host_command.h"
#include "shared_mem.h"
#include "spi.h"
#include "spi_flash.h"
#include "spi_flash_reg.h"
#include "timer.h"
#include "util.h"
#include "watchdog.h"
#include "ec_commands.h"
#include "flash.h"

/*
 * Time to sleep when chip is busy
 */
#define SPI_FLASH_SLEEP_USEC	100

/*
 * This is the max time for 32kb flash erase
 */
#define SPI_FLASH_TIMEOUT_USEC	(800*MSEC)

/* Internal buffer used by SPI flash driver */
static uint8_t buf[SPI_FLASH_MAX_MESSAGE_SIZE];

/**
 * Waits for chip to finish current operation. Must be called after
 * erase/write operations to ensure successive commands are executed.
 *
 * @return EC_SUCCESS or error on timeout
 */
int spi_flash_wait(void)
{
	timestamp_t timeout;

	timeout.val = get_time().val + SPI_FLASH_TIMEOUT_USEC;
	/* Wait until chip is not busy */
	while (spi_flash_get_status1() & SPI_FLASH_SR1_BUSY) {
		usleep(SPI_FLASH_SLEEP_USEC);

		if (get_time().val > timeout.val)
			return EC_ERROR_TIMEOUT;
	}

	return EC_SUCCESS;
}

/**
 * Set the write enable latch
 */
static int spi_flash_write_enable(void)
{
	uint8_t cmd = SPI_FLASH_WRITE_ENABLE;
	return spi_transaction(SPI_FLASH_DEVICE, &cmd, 1, NULL, 0);
}

/**
 * Returns the contents of SPI flash status register 1
 * @return register contents or -1 on error
 */
uint8_t spi_flash_get_status1(void)
{
	uint8_t cmd = SPI_FLASH_READ_SR1;
	uint8_t resp;

	if (spi_transaction(SPI_FLASH_DEVICE, &cmd, 1, &resp, 1) != EC_SUCCESS)
		return -1;

	return resp;
}

/**
 * Returns the contents of SPI flash status register 2
 * @return register contents or -1 on error
 */
uint8_t spi_flash_get_status2(void)
{
	uint8_t cmd = SPI_FLASH_READ_SR2;
	uint8_t resp;

	/* Second status register not present */
#ifndef CONFIG_SPI_FLASH_HAS_SR2
	return 0;
#endif

	if (spi_transaction(SPI_FLASH_DEVICE, &cmd, 1, &resp, 1) != EC_SUCCESS)
		return -1;

	return resp;
}

/**
 * Sets the SPI flash status registers (non-volatile bits only)
 * Pass reg2 == -1 to only set reg1.
 *
 * @param reg1 Status register 1
 * @param reg2 Status register 2 (optional)
 *
 * @return EC_SUCCESS, or non-zero if any error.
 */
int spi_flash_set_status(int reg1, int reg2)
{
	uint8_t cmd[3] = {SPI_FLASH_WRITE_SR, reg1, reg2};
	int rv = EC_SUCCESS;

	/* fail if both HW pin is asserted and SRP(s) is 1 */
	if (spi_flash_check_wp() != SPI_WP_NONE &&
		(flash_get_protect() & EC_FLASH_PROTECT_GPIO_ASSERTED) != 0)
		return EC_ERROR_ACCESS_DENIED;

	/* Enable writing to SPI flash */
	rv = spi_flash_write_enable();
	if (rv)
		return rv;

	/* Second status register not present */
#ifndef CONFIG_SPI_FLASH_HAS_SR2
	reg2 = -1;
#endif

	if (reg2 == -1)
		rv = spi_transaction(SPI_FLASH_DEVICE, cmd, 2, NULL, 0);
	else
		rv = spi_transaction(SPI_FLASH_DEVICE, cmd, 3, NULL, 0);
	if (rv)
		return rv;

	/* SRP update takes up to 10 ms, so wait for transaction to finish */
	spi_flash_wait();

	return rv;
}

/**
 * Returns the content of SPI flash
 *
 * @param buf_usr Buffer to write flash contents
 * @param offset Flash offset to start reading from
 * @param bytes Number of bytes to read.
 *
 * @return EC_SUCCESS, or non-zero if any error.
 */
int spi_flash_read(uint8_t *buf_usr, unsigned int offset, unsigned int bytes)
{
	int i, read_size, ret;
	uint8_t cmd[4];
	if (offset + bytes > CONFIG_FLASH_SIZE)
		return EC_ERROR_INVAL;
	cmd[0] = SPI_FLASH_READ;
	for (i = 0; i < bytes; i += read_size) {
		offset += i;
		cmd[1] = (offset >> 16) & 0xFF;
		cmd[2] = (offset >> 8) & 0xFF;
		cmd[3] = offset & 0xFF;
		read_size = MIN((bytes - i), SPI_FLASH_MAX_READ_SIZE);
		ret = spi_transaction(SPI_FLASH_DEVICE,
			cmd,
			4,
			buf_usr + i,
			read_size);
		if (ret != EC_SUCCESS)
			break;
		msleep(1);
	}
	return ret;
}

/**
 * Erase a block of SPI flash.
 *
 * @param offset Flash offset to start erasing
 * @param block Block size in kb (4 or 32)
 *
 * @return EC_SUCCESS, or non-zero if any error.
 */
static int spi_flash_erase_block(unsigned int offset, unsigned int block)
{
	uint8_t cmd[4];
	int rv = EC_SUCCESS;

	/* Invalid block size */
	if (block != 4 && block != 32)
		return EC_ERROR_INVAL;

	/* Not block aligned */
	if ((offset % (block * 1024)) != 0)
		return EC_ERROR_INVAL;

	/* Enable writing to SPI flash */
	rv = spi_flash_write_enable();
	if (rv)
		return rv;

	/* Compose instruction */
	cmd[0] = (block == 4) ? SPI_FLASH_ERASE_4KB : SPI_FLASH_ERASE_32KB;
	cmd[1] = (offset >> 16) & 0xFF;
	cmd[2] = (offset >> 8) & 0xFF;
	cmd[3] = offset & 0xFF;

	rv = spi_transaction(SPI_FLASH_DEVICE, cmd, 4, NULL, 0);
	if (rv)
		return rv;

	/* Wait for previous operation to complete */
	return spi_flash_wait();
}

/**
 * Erase SPI flash.
 *
 * @param offset Flash offset to start erasing
 * @param bytes Number of bytes to erase
 *
 * @return EC_SUCCESS, or non-zero if any error.
 */
int spi_flash_erase(unsigned int offset, unsigned int bytes)
{
	int rv = EC_SUCCESS;

	/* Invalid input */
	if (offset + bytes > CONFIG_FLASH_SIZE)
		return EC_ERROR_INVAL;

	/* Not aligned to sector (4kb) */
	if (offset % 4096 || bytes % 4096)
		return EC_ERROR_INVAL;

	/* Largest unit is block (32kb) */
	if (offset % (32 * 1024) == 0) {
		while (bytes != (bytes % (32 * 1024))) {
			rv = spi_flash_erase_block(offset, 32);
			if (rv)
				return rv;

			bytes -= 32 * 1024;
			offset += 32 * 1024;
			/*
			 * Refresh watchdog since we may be erasing a large
			 * number of blocks.
			 */
			watchdog_reload();
		}
	}

	/* Largest unit is sector (4kb) */
	while (bytes != (bytes % (4 * 1024))) {
		rv = spi_flash_erase_block(offset, 4);
		if (rv)
			return rv;

		bytes -= 4 * 1024;
		offset += 4 * 1024;
	}

	return rv;
}

/**
 * Write to SPI flash. Assumes already erased.
 * Limited to SPI_FLASH_MAX_WRITE_SIZE by chip.
 *
 * @param offset Flash offset to write
 * @param bytes Number of bytes to write
 * @param data Data to write to flash
 *
 * @return EC_SUCCESS, or non-zero if any error.
 */
int spi_flash_write(unsigned int offset, unsigned int bytes,
	const uint8_t const *data)
{
	int rv, write_size;

	/* Invalid input */
	if (!data || offset + bytes > CONFIG_FLASH_SIZE ||
	    bytes > SPI_FLASH_MAX_WRITE_SIZE)
		return EC_ERROR_INVAL;

	while (bytes > 0) {
		watchdog_reload();
		/* Write length can not go beyond the end of the flash page */
		write_size = MIN(bytes, SPI_FLASH_MAX_WRITE_SIZE -
		(offset & (SPI_FLASH_MAX_WRITE_SIZE - 1)));

		/* Wait for previous operation to complete */
		rv = spi_flash_wait();
		if (rv)
			return rv;

		/* Enable writing to SPI flash */
		rv = spi_flash_write_enable();
		if (rv)
			return rv;

		/* Copy data to send buffer; buffers may overlap */
		memmove(buf + 4, data, write_size);

		/* Compose instruction */
		buf[0] = SPI_FLASH_PAGE_PRGRM;
		buf[1] = (offset) >> 16;
		buf[2] = (offset) >> 8;
		buf[3] = offset;

		rv = spi_transaction(SPI_FLASH_DEVICE,
				     buf, 4 + write_size, NULL, 0);
		if (rv)
			return rv;

		data += write_size;
		offset += write_size;
		bytes -= write_size;
	}

	/* Wait for previous operation to complete */
	return spi_flash_wait();
}

/**
 * Gets the SPI flash JEDEC ID (manufacturer ID, memory type, and capacity)
 *
 * @param dest		Destination buffer; must be 3 bytes long
 * @return EC_SUCCESS or non-zero on error
 */
int spi_flash_get_jedec_id(uint8_t *dest)
{
	uint8_t cmd = SPI_FLASH_JEDEC_ID;

	return spi_transaction(SPI_FLASH_DEVICE, &cmd, 1, dest, 3);
}

/**
 * Gets the SPI flash manufacturer and device ID
 *
 * @param dest		Destination buffer; must be 2 bytes long
 * @return EC_SUCCESS or non-zero on error
 */
int spi_flash_get_mfr_dev_id(uint8_t *dest)
{
	uint8_t cmd[4] = {SPI_FLASH_MFR_DEV_ID, 0, 0, 0};

	return spi_transaction(SPI_FLASH_DEVICE, cmd, sizeof(cmd), dest, 2);
}

/**
 * Gets the SPI flash unique ID (serial)
 *
 * @param dest		Destination buffer; must be 8 bytes long
 * @return EC_SUCCESS or non-zero on error
 */
int spi_flash_get_unique_id(uint8_t *dest)
{
	uint8_t cmd[5] = {SPI_FLASH_UNIQUE_ID, 0, 0, 0, 0};

	return spi_transaction(SPI_FLASH_DEVICE, cmd, sizeof(cmd), dest, 8);
}

/**
 * Check for SPI flash status register write protection
 * Cannot sample WP pin, so caller should sample it if necessary, if
 * SPI_WP_HARDWARE is returned.
 *
 * @return enum spi_flash_wp status based on protection
 */
enum spi_flash_wp spi_flash_check_wp(void)
{
	int sr1_prot = spi_flash_get_status1() & SPI_FLASH_SR1_SRP0;
	int sr2_prot = spi_flash_get_status2() & SPI_FLASH_SR2_SRP1;

	if (sr2_prot)
		return sr1_prot ? SPI_WP_PERMANENT : SPI_WP_POWER_CYCLE;
	else if (sr1_prot)
		return SPI_WP_HARDWARE;

	return SPI_WP_NONE;
}

/**
 * Set SPI flash status register write protection
 *
 * @param wp Status register write protection mode
 *
 * @return EC_SUCCESS for no protection, or non-zero if error.
 */
int spi_flash_set_wp(enum spi_flash_wp w)
{
	int sr1 = spi_flash_get_status1();
	int sr2 = spi_flash_get_status2();

	switch (w) {
	case SPI_WP_NONE:
		sr1 &= ~SPI_FLASH_SR1_SRP0;
		sr2 &= ~SPI_FLASH_SR2_SRP1;
		break;
	case SPI_WP_HARDWARE:
		sr1 |= SPI_FLASH_SR1_SRP0;
		sr2 &= ~SPI_FLASH_SR2_SRP1;
		break;
	case SPI_WP_POWER_CYCLE:
		sr1 &= ~SPI_FLASH_SR1_SRP0;
		sr2 |= SPI_FLASH_SR2_SRP1;
		break;
	case SPI_WP_PERMANENT:
		sr1 |= SPI_FLASH_SR1_SRP0;
		sr2 |= SPI_FLASH_SR2_SRP1;
		break;
	default:
		return EC_ERROR_INVAL;
	}

	return spi_flash_set_status(sr1, sr2);
}

/**
 * Check for SPI flash block write protection
 *
 * @param offset Flash block offset to check
 * @param bytes Flash block length to check
 *
 * @return EC_SUCCESS for no protection, or non-zero if error.
 */
int spi_flash_check_protect(unsigned int offset, unsigned int bytes)
{
	uint8_t sr1 = spi_flash_get_status1();
	uint8_t sr2 = spi_flash_get_status2();
	unsigned int start;
	unsigned int len;
	int rv = EC_SUCCESS;

	/* Invalid value */
	if (sr1 == -1 || sr2 == -1 || offset + bytes > CONFIG_FLASH_SIZE)
		return EC_ERROR_INVAL;

	/* Compute current protect range */
	rv = spi_flash_reg_to_protect(sr1, sr2, &start, &len);
	if (rv)
		return rv;

	/* Check if ranges overlap */
	if (MAX(start, offset) < MIN(start + len, offset + bytes))
		return EC_ERROR_ACCESS_DENIED;

	return EC_SUCCESS;
}

/**
 * Set SPI flash block write protection
 * If offset == bytes == 0, remove protection.
 *
 * @param offset Flash block offset to protect
 * @param bytes Flash block length to protect
 *
 * @return EC_SUCCESS, or non-zero if error.
 */
int spi_flash_set_protect(unsigned int offset, unsigned int bytes)
{
	int rv;
	uint8_t sr1 = spi_flash_get_status1();
	uint8_t sr2 = spi_flash_get_status2();

	/* Invalid values */
	if (sr1 == -1 || sr2 == -1 || offset + bytes > CONFIG_FLASH_SIZE)
		return EC_ERROR_INVAL;

	/* Compute desired protect range */
	rv = spi_flash_protect_to_reg(offset, bytes, &sr1, &sr2);
	if (rv)
		return rv;

	return spi_flash_set_status(sr1, sr2);
}

static int command_spi_flashinfo(int argc, char **argv)
{
	uint8_t jedec[3];
	uint8_t unique[8];
	int rv;

	spi_enable(CONFIG_SPI_FLASH_PORT, 1);

	/* Wait for previous operation to complete */
	rv = spi_flash_wait();
	if (rv)
		return rv;

	spi_flash_get_jedec_id(jedec);
	spi_flash_get_unique_id(unique);

	ccprintf("Manufacturer ID: %02x\nDevice ID: %02x %02x\n",
		 jedec[0], jedec[1], jedec[2]);
	ccprintf("Unique ID: %02x %02x %02x %02x %02x %02x %02x %02x\n",
		 unique[0], unique[1], unique[2], unique[3],
		 unique[4], unique[5], unique[6], unique[7]);
	ccprintf("Capacity: %4d kB\n", SPI_FLASH_SIZE(jedec[2]) / 1024);

	return rv;
}
DECLARE_CONSOLE_COMMAND(spi_flashinfo, command_spi_flashinfo,
	NULL,
	"Print SPI flash info");

#ifdef CONFIG_HOSTCMD_FLASH_SPI_INFO
static int flash_command_spi_info(struct host_cmd_handler_args *args)
{
	struct ec_response_flash_spi_info *r = args->response;

	spi_flash_get_jedec_id(r->jedec);
	r->reserved0 = 0;
	spi_flash_get_mfr_dev_id(r->mfr_dev_id);
	r->sr1 = spi_flash_get_status1();
	r->sr2 = spi_flash_get_status2();

	args->response_size = sizeof(*r);
	return EC_RES_SUCCESS;
}
DECLARE_HOST_COMMAND(EC_CMD_FLASH_SPI_INFO,
		     flash_command_spi_info,
		     EC_VER_MASK(0));
#endif  /* CONFIG_HOSTCMD_FLASH_SPI_INFO */

#ifdef CONFIG_CMD_SPI_FLASH
static int command_spi_flasherase(int argc, char **argv)
{
	int offset = -1;
	int bytes = 4096;
	int rv = parse_offset_size(argc, argv, 1, &offset, &bytes);

	if (rv)
		return rv;

	spi_enable(CONFIG_SPI_FLASH_PORT, 1);

	/* Chip has protection */
	if (spi_flash_check_protect(offset, bytes))
		return EC_ERROR_ACCESS_DENIED;

	ccprintf("Erasing %d bytes at 0x%x...\n", bytes, offset);
	return spi_flash_erase(offset, bytes);
}
DECLARE_CONSOLE_COMMAND(spi_flasherase, command_spi_flasherase,
	"offset [bytes]",
	"Erase flash");

static int command_spi_flashwrite(int argc, char **argv)
{
	int offset = -1;
	int bytes = SPI_FLASH_MAX_WRITE_SIZE;
	int write_len;
	int rv = EC_SUCCESS;
	int i;

	rv = parse_offset_size(argc, argv, 1, &offset, &bytes);
	if (rv)
		return rv;

	spi_enable(CONFIG_SPI_FLASH_PORT, 1);

	/* Chip has protection */
	if (spi_flash_check_protect(offset, bytes))
		return EC_ERROR_ACCESS_DENIED;

	/* Fill the data buffer with a pattern */
	for (i = 0; i < SPI_FLASH_MAX_WRITE_SIZE; i++)
		buf[i] = i;

	ccprintf("Writing %d bytes to 0x%x...\n", bytes, offset);
	while (bytes > 0) {
		/* First write multiples of 256, then (bytes % 256) last */
		write_len = ((bytes % SPI_FLASH_MAX_WRITE_SIZE) == bytes) ?
					bytes : SPI_FLASH_MAX_WRITE_SIZE;

		/* Perform write */
		rv = spi_flash_write(offset, write_len, buf);
		if (rv)
			return rv;

		offset += write_len;
		bytes -= write_len;
	}

	ASSERT(bytes == 0);

	return rv;
}
DECLARE_CONSOLE_COMMAND(spi_flashwrite, command_spi_flashwrite,
	"offset [bytes]",
	"Write pattern to flash");

static int command_spi_flashread(int argc, char **argv)
{
	int i;
	int offset = -1;
	int bytes = -1;
	int read_len;
	int rv;

	rv = parse_offset_size(argc, argv, 1, &offset, &bytes);
	if (rv)
		return rv;

	spi_enable(CONFIG_SPI_FLASH_PORT, 1);

	/* Can't read past size of memory */
	if (offset + bytes > CONFIG_FLASH_SIZE)
		return EC_ERROR_INVAL;

	/* Wait for previous operation to complete */
	rv = spi_flash_wait();
	if (rv)
		return rv;

	ccprintf("Reading %d bytes from 0x%x...\n", bytes, offset);
	/* Read <= 256 bytes to avoid allocating another buffer */
	while (bytes > 0) {
		watchdog_reload();

		/* First read (bytes % 256), then in multiples of 256 */
		read_len = (bytes % SPI_FLASH_MAX_READ_SIZE) ?
					(bytes % SPI_FLASH_MAX_READ_SIZE) :
					SPI_FLASH_MAX_READ_SIZE;

		rv = spi_flash_read(buf, offset, read_len);
		if (rv)
			return rv;

		for (i = 0; i < read_len; i++) {
			if (i % 16 == 0)
				ccprintf("%02x:", offset + i);

			ccprintf(" %02x", buf[i]);

			if (i % 16 == 15 || i == read_len - 1)
				ccputs("\n");
		}

		offset += read_len;
		bytes -= read_len;
	}

	ASSERT(bytes == 0);
	return EC_SUCCESS;
}
DECLARE_CONSOLE_COMMAND(spi_flashread, command_spi_flashread,
	"offset bytes",
	"Read flash");

static int command_spi_flashread_sr(int argc, char **argv)
{
	spi_enable(CONFIG_SPI_FLASH_PORT, 1);

	ccprintf("Status Register 1: 0x%02x\n", spi_flash_get_status1());
	ccprintf("Status Register 2: 0x%02x\n", spi_flash_get_status2());

	return EC_SUCCESS;
}
DECLARE_CONSOLE_COMMAND(spi_flash_rsr, command_spi_flashread_sr,
	NULL,
	"Read status registers");

static int command_spi_flashwrite_sr(int argc, char **argv)
{
	int val1 = 0;
	int val2 = 0;
	int rv = parse_offset_size(argc, argv, 1, &val1, &val2);

	if (rv)
		return rv;

	spi_enable(CONFIG_SPI_FLASH_PORT, 1);

	ccprintf("Writing 0x%02x to status register 1, ", val1);
	ccprintf("0x%02x to status register 2...\n", val2);
	return spi_flash_set_status(val1, val2);
}
DECLARE_CONSOLE_COMMAND(spi_flash_wsr, command_spi_flashwrite_sr,
	"value1 value2",
	"Write to status registers");

static int command_spi_flashprotect(int argc, char **argv)
{
	int val1 = 0;
	int val2 = 0;
	int rv = parse_offset_size(argc, argv, 1, &val1, &val2);

	if (rv)
		return rv;

	spi_enable(CONFIG_SPI_FLASH_PORT, 1);

	ccprintf("Setting protection for 0x%06x to 0x%06x\n", val1, val1+val2);
	return spi_flash_set_protect(val1, val2);
}
DECLARE_CONSOLE_COMMAND(spi_flash_prot, command_spi_flashprotect,
	"offset len",
	"Set block protection");
#endif
