/* Copyright 2015 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.
 */

#include "common.h"
#include "console.h"
#include "flash.h"
#include "host_command.h"
#include "system.h"
#include "util.h"
#include "watchdog.h"
#include "registers.h"
#include "task.h"
#include "shared_mem.h"
#include "uart.h"

const char __flash_dma_start;
#define FLASH_DMA_START ((uint32_t) &__flash_dma_start)
#define FLASH_DMA_CODE __attribute__((section(".flash_direct_map")))

#define FLASH_SECTOR_ERASE_SIZE       0x00000400

/* Flash sector erase (1K bytes) command */
#define FLASH_CMD_SECTOR_ERASE 0xD7
/* Write status register */
#define FLASH_CMD_WRSR         0x01
/* Write disable */
#define FLASH_CMD_WRDI         0x04
/* Write enable */
#define FLASH_CMD_WREN         0x06
/* Read status register */
#define FLASH_CMD_RS           0x05
/* Auto address increment programming */
#define FLASH_CMD_AAI_WORD     0xAD

static int stuck_locked;
static int inconsistent_locked;
static int all_protected;
static int flash_dma_code_enabled;

#define FWP_REG(bank) (bank / 8)
#define FWP_MASK(bank) (1 << (bank % 8))

enum flash_wp_interface {
	FLASH_WP_HOST = 0x01,
	FLASH_WP_DBGR = 0x02,
	FLASH_WP_EC = 0x04,
};

enum flash_wp_status {
	FLASH_WP_STATUS_PROTECT_RO = EC_FLASH_PROTECT_RO_NOW,
	FLASH_WP_STATUS_PROTECT_ALL = EC_FLASH_PROTECT_ALL_NOW,
};

enum flash_status_mask {
	FLASH_SR_NO_BUSY = 0,
	/* Internal write operation is in progress */
	FLASH_SR_BUSY = 0x01,
	/* Device is memory Write enabled */
	FLASH_SR_WEL = 0x02,

	FLASH_SR_ALL = (FLASH_SR_BUSY | FLASH_SR_WEL),
};

enum dlm_address_view {
	SCAR0_ILM0_DLM13   = 0x8D000, /* DLM ~ 0x8DFFF H2RAM map LPC I/O */
	SCAR1_ILM1_DLM11   = 0x8B000, /* DLM ~ 0x8BFFF ram 44K ~ 48K */
	SCAR2_ILM2_DLM14   = 0x8E000, /* DLM ~ 0x8EFFF RO/RW flash code DMA */
	SCAR3_ILM3_DLM6    = 0x86000, /* DLM ~ 0x86FFF ram 24K ~ 28K */
	SCAR4_ILM4_DLM7    = 0x87000, /* DLM ~ 0x87FFF ram 28K ~ 32K */
	SCAR5_ILM5_DLM8    = 0x88000, /* DLM ~ 0x88FFF ram 32K ~ 36K */
	SCAR6_ILM6_DLM9    = 0x89000, /* DLM ~ 0x89FFF ram 36K ~ 40K */
	SCAR7_ILM7_DLM10   = 0x8A000, /* DLM ~ 0x8AFFF ram 40K ~ 44K */
	SCAR8_ILM8_DLM4    = 0x84000, /* DLM ~ 0x84FFF ram 16K ~ 20K */
	SCAR9_ILM9_DLM5    = 0x85000, /* DLM ~ 0x85FFF ram 20K ~ 24K */
	SCAR10_ILM10_DLM2  = 0x82000, /* DLM ~ 0x82FFF ram 8K ~ 12K */
	SCAR11_ILM11_DLM3  = 0x83000, /* DLM ~ 0x83FFF ram 12K ~ 16K */
	SCAR12_ILM12_DLM12 = 0x8C000, /* DLM ~ 0x8CFFF immu cache */
};

void FLASH_DMA_CODE dma_reset_immu(void)
{
	/* Immu tag sram reset */
	IT83XX_GCTRL_MCCR |= 0x10;
	/* Make sure the immu(dynamic cache) is reset */
	asm volatile ("dsb");
	IT83XX_GCTRL_MCCR &= ~0x10;
	asm volatile ("dsb");
}

void FLASH_DMA_CODE dma_flash_follow_mode(void)
{
	/* Enter follow mode and FSCE# high level */
	IT83XX_SMFI_ECINDAR3 = 0x4F;
	IT83XX_SMFI_ECINDAR2 = 0xFF;
	IT83XX_SMFI_ECINDAR1 = 0xFE;
	IT83XX_SMFI_ECINDAR0 = 0x00;
	IT83XX_SMFI_ECINDDR = 0x00;
}

void FLASH_DMA_CODE dma_flash_follow_mode_exit(void)
{
	/* Exit follow mode */
	IT83XX_SMFI_ECINDAR3 = 0x00;
	IT83XX_SMFI_ECINDAR2 = 0x00;
}

void FLASH_DMA_CODE dma_flash_fsce_high(void)
{
	/* FSCE# high level */
	IT83XX_SMFI_ECINDAR1 = 0xFE;
	IT83XX_SMFI_ECINDDR = 0x00;
}

uint8_t FLASH_DMA_CODE dma_flash_read_dat(void)
{
	/* Read data from FMISO */
	return IT83XX_SMFI_ECINDDR;
}

void FLASH_DMA_CODE dma_flash_write_dat(uint8_t wdata)
{
	/* Write data to FMOSI */
	IT83XX_SMFI_ECINDDR = wdata;
}

void FLASH_DMA_CODE dma_flash_transaction(int wlen, uint8_t *wbuf,
					int rlen, uint8_t *rbuf, int cmd_end)
{
	int i;

	/*  FSCE# with low level */
	IT83XX_SMFI_ECINDAR1 = 0xFD;
	/* Write data to FMOSI */
	for (i = 0; i < wlen; i++)
		IT83XX_SMFI_ECINDDR = wbuf[i];
	/* Read data from FMISO */
	for (i = 0; i < rlen; i++)
		rbuf[i] = IT83XX_SMFI_ECINDDR;

	/* FSCE# high level if transaction done */
	if (cmd_end)
		dma_flash_fsce_high();
}

void FLASH_DMA_CODE dma_flash_cmd_read_status(enum flash_status_mask mask,
						enum flash_status_mask target)
{
	uint8_t status[1];
	uint8_t cmd_rs[] = {FLASH_CMD_RS};

	/*
	 * We prefer no timeout here. We can always get the status
	 * we want, or wait for watchdog triggered to check
	 * e-flash's status instead of breaking loop.
	 * This will avoid fetching unknown instruction from e-flash
	 * and causing exception.
	 */
	while (1) {
		/* read status */
		dma_flash_transaction(sizeof(cmd_rs), cmd_rs, 1, status, 1);
		/* only bit[1:0] valid */
		if ((status[0] & mask) == target)
			break;
	}
}

void FLASH_DMA_CODE dma_flash_cmd_write_enable(void)
{
	uint8_t cmd_we[] = {FLASH_CMD_WREN};

	/* enter EC-indirect follow mode */
	dma_flash_follow_mode();
	/* send write enable command */
	dma_flash_transaction(sizeof(cmd_we), cmd_we, 0, NULL, 1);
	/* read status and make sure busy bit cleared and write enabled. */
	dma_flash_cmd_read_status(FLASH_SR_ALL, FLASH_SR_WEL);
	/* exit EC-indirect follow mode */
	dma_flash_follow_mode_exit();
}

void FLASH_DMA_CODE dma_flash_cmd_write_disable(void)
{
	uint8_t cmd_wd[] = {FLASH_CMD_WRDI};

	/* enter EC-indirect follow mode */
	dma_flash_follow_mode();
	/* send write disable command */
	dma_flash_transaction(sizeof(cmd_wd), cmd_wd, 0, NULL, 1);
	/* make sure busy bit cleared. */
	dma_flash_cmd_read_status(FLASH_SR_ALL, FLASH_SR_NO_BUSY);
	/* exit EC-indirect follow mode */
	dma_flash_follow_mode_exit();
}

void FLASH_DMA_CODE dma_flash_cmd_erase(int addr, int cmd)
{
	uint8_t cmd_erase[] = {cmd, ((addr >> 16) & 0xFF),
				((addr >> 8) & 0xFF), (addr & 0xFF)};

	/* enter EC-indirect follow mode */
	dma_flash_follow_mode();
	/* send erase command */
	dma_flash_transaction(sizeof(cmd_erase), cmd_erase, 0, NULL, 1);
	/* make sure busy bit cleared. */
	dma_flash_cmd_read_status(FLASH_SR_BUSY, FLASH_SR_NO_BUSY);
	/* exit EC-indirect follow mode */
	dma_flash_follow_mode_exit();
}

void FLASH_DMA_CODE dma_flash_cmd_aai_write(int addr, int wlen, uint8_t *wbuf)
{
	int i;
	uint8_t aai_write[] = {FLASH_CMD_AAI_WORD, ((addr >> 16) & 0xFF),
				((addr >> 8) & 0xFF), (addr & 0xFF)};

	/* enter EC-indirect follow mode */
	dma_flash_follow_mode();
	/* send aai word command */
	dma_flash_transaction(sizeof(aai_write), aai_write, 0, NULL, 0);
	for (i = 0; i < wlen; i += 2) {
		dma_flash_write_dat(wbuf[i]);
		dma_flash_write_dat(wbuf[i + 1]);
		dma_flash_fsce_high();
		/* make sure busy bit cleared. */
		dma_flash_cmd_read_status(FLASH_SR_BUSY, FLASH_SR_NO_BUSY);
		/* resend aai word command without address field */
		if ((i + 2) < wlen)
			dma_flash_transaction(1, aai_write, 0, NULL, 0);
	}
	/* exit EC-indirect follow mode */
	dma_flash_follow_mode_exit();
}

uint8_t FLASH_DMA_CODE dma_flash_indirect_fast_read(int addr)
{
	IT83XX_SMFI_ECINDAR3 = 0x40;
	IT83XX_SMFI_ECINDAR2 = (addr >> 16) & 0xFF;
	IT83XX_SMFI_ECINDAR1 = (addr >> 8) & 0xFF;
	IT83XX_SMFI_ECINDAR0 = (addr & 0xFF);

	return IT83XX_SMFI_ECINDDR;
}

int FLASH_DMA_CODE dma_flash_verify(int addr, int size, const char *data)
{
	int i;
	uint8_t *wbuf = (uint8_t *)data;
	uint8_t *flash = (uint8_t *)addr;

	/* verify for erase */
	if (data == NULL) {
		for (i = 0; i < size; i++) {
			if (flash[i] != 0xFF)
				return EC_ERROR_UNKNOWN;
		}
	/* verify for write */
	} else {
		for (i = 0; i < size; i++) {
			if (flash[i] != wbuf[i])
				return EC_ERROR_UNKNOWN;
		}
	}

	return EC_SUCCESS;
}

void FLASH_DMA_CODE dma_flash_aai_write(int addr, int wlen, const char *wbuf)
{
	dma_flash_cmd_write_enable();
	dma_flash_cmd_aai_write(addr, wlen, (uint8_t *)wbuf);
	dma_flash_cmd_write_disable();
}

void FLASH_DMA_CODE dma_flash_erase(int addr, int cmd)
{
	dma_flash_cmd_write_enable();
	dma_flash_cmd_erase(addr, cmd);
	dma_flash_cmd_write_disable();
}

static enum flash_wp_status flash_check_wp(void)
{
	enum flash_wp_status wp_status;
	int all_bank_count, bank;

	all_bank_count = CONFIG_FLASH_SIZE / CONFIG_FLASH_BANK_SIZE;

	for (bank = 0; bank < all_bank_count; bank++) {
		if (!(IT83XX_GCTRL_EWPR0PFEC(FWP_REG(bank)) & FWP_MASK(bank)))
			break;
	}

	if (bank == WP_BANK_COUNT)
		wp_status = FLASH_WP_STATUS_PROTECT_RO;
	else if (bank == (WP_BANK_COUNT + PSTATE_BANK_COUNT))
		wp_status = FLASH_WP_STATUS_PROTECT_RO;
	else if (bank == all_bank_count)
		wp_status = FLASH_WP_STATUS_PROTECT_ALL;
	else
		wp_status = 0;

	return wp_status;
}

/**
 * Protect flash banks until reboot.
 *
 * @param start_bank    Start bank to protect
 * @param bank_count    Number of banks to protect
 */
static void flash_protect_banks(int start_bank,
				int bank_count,
				enum flash_wp_interface wp_if)
{
	int bank;

	for (bank = start_bank; bank < start_bank + bank_count; bank++) {
		if (wp_if & FLASH_WP_EC)
			IT83XX_GCTRL_EWPR0PFEC(FWP_REG(bank)) |= FWP_MASK(bank);
		if (wp_if & FLASH_WP_HOST)
			IT83XX_GCTRL_EWPR0PFH(FWP_REG(bank)) |= FWP_MASK(bank);
		if (wp_if & FLASH_WP_DBGR)
			IT83XX_GCTRL_EWPR0PFD(FWP_REG(bank)) |= FWP_MASK(bank);
	}
}

int FLASH_DMA_CODE flash_physical_read(int offset, int size, char *data)
{
	int i;

	for (i = 0; i < size; i++) {
		data[i] = dma_flash_indirect_fast_read(offset);
		offset++;
	}

	return EC_SUCCESS;
}

/**
 * Write to physical flash.
 *
 * Offset and size must be a multiple of CONFIG_FLASH_WRITE_SIZE.
 *
 * @param offset        Flash offset to write.
 * @param size          Number of bytes to write.
 * @param data          Data to write to flash.  Must be 32-bit aligned.
 */
int FLASH_DMA_CODE flash_physical_write(int offset, int size, const char *data)
{
	int ret = EC_ERROR_UNKNOWN;

	if (flash_dma_code_enabled == 0)
		return EC_ERROR_ACCESS_DENIED;

	if (all_protected)
		return EC_ERROR_ACCESS_DENIED;

	watchdog_reload();

	/*
	 * CPU can't fetch instruction from flash while use
	 * EC-indirect follow mode to access flash, interrupts need to be
	 * disabled.
	 */
	interrupt_disable();

	dma_flash_aai_write(offset, size, data);
	dma_reset_immu();
	ret = dma_flash_verify(offset, size, data);

	interrupt_enable();

	return ret;
}

/**
 * Erase physical flash.
 *
 * Offset and size must be a multiple of CONFIG_FLASH_ERASE_SIZE.
 *
 * @param offset        Flash offset to erase.
 * @param size          Number of bytes to erase.
 */
int FLASH_DMA_CODE flash_physical_erase(int offset, int size)
{
	int v_size = size, v_addr = offset, ret = EC_ERROR_UNKNOWN;

	if (flash_dma_code_enabled == 0)
		return EC_ERROR_ACCESS_DENIED;

	if (all_protected)
		return EC_ERROR_ACCESS_DENIED;

	/*
	 * CPU can't fetch instruction from flash while use
	 * EC-indirect follow mode to access flash, interrupts need to be
	 * disabled.
	 */
	interrupt_disable();

	/* Always use sector erase command (1K bytes) */
	for (; size > 0; size -= FLASH_SECTOR_ERASE_SIZE) {
		dma_flash_erase(offset, FLASH_CMD_SECTOR_ERASE);
		offset += FLASH_SECTOR_ERASE_SIZE;
	}
	dma_reset_immu();
	ret = dma_flash_verify(v_addr, v_size, NULL);

	interrupt_enable();

	return ret;
}

/**
 * Read physical write protect setting for a flash bank.
 *
 * @param bank    Bank index to check.
 * @return        non-zero if bank is protected until reboot.
 */
int flash_physical_get_protect(int bank)
{
	return IT83XX_GCTRL_EWPR0PFEC(FWP_REG(bank)) & FWP_MASK(bank);
}

/**
 * Protect flash now.
 *
 * @param all      Protect all (=1) or just read-only and pstate (=0).
 * @return         non-zero if error.
 */
int flash_physical_protect_now(int all)
{
	if (all) {
		/* Protect the entire flash */
		flash_protect_banks(0,
			CONFIG_FLASH_SIZE / CONFIG_FLASH_BANK_SIZE,
			FLASH_WP_EC);
		all_protected = 1;
	} else {
		/* Protect the read-only section and persistent state */
		flash_protect_banks(WP_BANK_OFFSET,
			WP_BANK_COUNT, FLASH_WP_EC);
#ifdef PSTATE_BANK
		flash_protect_banks(PSTATE_BANK,
			PSTATE_BANK_COUNT, FLASH_WP_EC);
#endif
	}

	/*
	 * bit[0], eflash protect lock register which can only be write 1 and
	 * only be cleared by power-on reset.
	 */
	IT83XX_GCTRL_EPLR |= 0x01;

	return EC_SUCCESS;
}

/**
 * Return flash protect state flags from the physical layer.
 *
 * This should only be called by flash_get_protect().
 *
 * Uses the EC_FLASH_PROTECT_* flags from ec_commands.h
 */
uint32_t flash_physical_get_protect_flags(void)
{
	uint32_t flags = 0;

	flags |= flash_check_wp();

	if (all_protected)
		flags |= EC_FLASH_PROTECT_ALL_NOW;

	/* Check if blocks were stuck locked at pre-init */
	if (stuck_locked)
		flags |= EC_FLASH_PROTECT_ERROR_STUCK;

	/* Check if flash protection is in inconsistent state at pre-init */
	if (inconsistent_locked)
		flags |= EC_FLASH_PROTECT_ERROR_INCONSISTENT;

	return flags;
}

/**
 * Return the valid flash protect flags.
 *
 * @return   A combination of EC_FLASH_PROTECT_* flags from ec_commands.h
 */
uint32_t flash_physical_get_valid_flags(void)
{
	return EC_FLASH_PROTECT_RO_AT_BOOT |
	       EC_FLASH_PROTECT_RO_NOW |
	       EC_FLASH_PROTECT_ALL_NOW;
}

/**
 * Return the writable flash protect flags.
 *
 * @param    cur_flags The current flash protect flags.
 * @return   A combination of EC_FLASH_PROTECT_* flags from ec_commands.h
 */
uint32_t flash_physical_get_writable_flags(uint32_t cur_flags)
{
	uint32_t ret = 0;

	/* If RO protection isn't enabled, its at-boot state can be changed. */
	if (!(cur_flags & EC_FLASH_PROTECT_RO_NOW))
		ret |= EC_FLASH_PROTECT_RO_AT_BOOT;

	/*
	 * If entire flash isn't protected at this boot, it can be enabled if
	 * the WP GPIO is asserted.
	 */
	if (!(cur_flags & EC_FLASH_PROTECT_ALL_NOW) &&
	    (cur_flags & EC_FLASH_PROTECT_GPIO_ASSERTED))
		ret |= EC_FLASH_PROTECT_ALL_NOW;

	return ret;
}

static void flash_code_static_dma(void)
{

	/* Make sure no interrupt while enable static DMA */
	interrupt_disable();

	/* invalid static DMA first */
	IT83XX_SMFI_SCAR2H = 0x08;

	/* Copy to DLM */
	IT83XX_GCTRL_MCCR2 |= 0x20;
	memcpy((void *)SCAR2_ILM2_DLM14, (const void *)FLASH_DMA_START,
		IT83XX_ILM_BLOCK_SIZE);
	IT83XX_GCTRL_MCCR2 &= ~0x20;

	/*
	 * Enable ILM
	 * Set the logic memory address(flash code of RO/RW) in eflash
	 * by programming the register SCARx bit19-bit0.
	 */
	IT83XX_SMFI_SCAR2L = FLASH_DMA_START & 0xFF;
	IT83XX_SMFI_SCAR2M = (FLASH_DMA_START >> 8) & 0xFF;
	IT83XX_SMFI_SCAR2H = (FLASH_DMA_START >> 16) & 0x0F;
	/*
	 * Validate Direct-map SRAM function by programming
	 * register SCARx bit20=0
	 */
	IT83XX_SMFI_SCAR2H &= ~0x10;

	flash_dma_code_enabled = 0x01;

	interrupt_enable();
}

/**
 * Initialize the module.
 *
 * Applies at-boot protection settings if necessary.
 */
int flash_pre_init(void)
{
	int32_t reset_flags, prot_flags, unwanted_prot_flags;

	flash_code_static_dma();

	reset_flags = system_get_reset_flags();
	prot_flags = flash_get_protect();
	unwanted_prot_flags = EC_FLASH_PROTECT_ALL_NOW |
		EC_FLASH_PROTECT_ERROR_INCONSISTENT;

	/*
	 * If we have already jumped between images, an earlier image could
	 * have applied write protection.  Nothing additional needs to be done.
	 */
	if (reset_flags & RESET_FLAG_SYSJUMP)
		return EC_SUCCESS;

	if (prot_flags & EC_FLASH_PROTECT_GPIO_ASSERTED) {
		/* Protect the entire flash of host interface */
		flash_protect_banks(0,
			CONFIG_FLASH_SIZE / CONFIG_FLASH_BANK_SIZE,
			FLASH_WP_HOST);
		/* Protect the entire flash of DBGR interface */
		flash_protect_banks(0,
			CONFIG_FLASH_SIZE / CONFIG_FLASH_BANK_SIZE,
			FLASH_WP_DBGR);
		/*
		 * Write protect is asserted.  If we want RO flash protected,
		 * protect it now.
		 */
		if ((prot_flags & EC_FLASH_PROTECT_RO_AT_BOOT) &&
		    !(prot_flags & EC_FLASH_PROTECT_RO_NOW)) {
			int rv = flash_set_protect(EC_FLASH_PROTECT_RO_NOW,
						   EC_FLASH_PROTECT_RO_NOW);
			if (rv)
				return rv;

			/* Re-read flags */
			prot_flags = flash_get_protect();
		}
	} else {
		/* Don't want RO flash protected */
		unwanted_prot_flags |= EC_FLASH_PROTECT_RO_NOW;
	}

	/* If there are no unwanted flags, done */
	if (!(prot_flags & unwanted_prot_flags))
		return EC_SUCCESS;

	/*
	 * If the last reboot was a power-on reset, it should have cleared
	 * write-protect.  If it didn't, then the flash write protect registers
	 * have been permanently committed and we can't fix that.
	 */
	if (reset_flags & RESET_FLAG_POWER_ON) {
		stuck_locked = 1;
		return EC_ERROR_ACCESS_DENIED;
	} else {
		/*
		 * Set inconsistent flag, because there is no software
		 * reset can clear write-protect.
		 */
		inconsistent_locked = 1;
		return EC_ERROR_ACCESS_DENIED;
	}

	/* That doesn't return, so if we're still here that's an error */
	return EC_ERROR_UNKNOWN;
}
