/* Copyright 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.
 *
 * NPCX SoC spi flash update tool - monitor firmware
 */

#include <stdint.h>
#include "config.h"
#include "npcx_monitor.h"
#include "registers.h"
#include "util.h"

/*****************************************************************************/
/* spi flash internal functions */
void sspi_flash_pinmux(int enable)
{
	if (enable)
		CLEAR_BIT(NPCX_DEVALT(0), NPCX_DEVALT0_NO_F_SPI);
	else
		SET_BIT(NPCX_DEVALT(0), NPCX_DEVALT0_NO_F_SPI);

	/* CS0/1 pinmux */
	if (enable) {
#if (FIU_CHIP_SELECT == 1)
		SET_BIT(NPCX_DEVALT(0), NPCX_DEVALT0_F_SPI_CS1_1);
#elif (FIU_CHIP_SELECT == 2)
		SET_BIT(NPCX_DEVALT(0), NPCX_DEVALT0_F_SPI_CS1_2);
#endif
	} else {
		CLEAR_BIT(NPCX_DEVALT(0), NPCX_DEVALT0_F_SPI_CS1_1);
		CLEAR_BIT(NPCX_DEVALT(0), NPCX_DEVALT0_F_SPI_CS1_2);
	}
}

void sspi_flash_tristate(int enable)
{
	if (enable) {
		/* Enable FIU pins to tri-state */
		SET_BIT(NPCX_DEVCNT, NPCX_DEVCNT_F_SPI_TRIS);
	} else {
		/* Disable FIU pins to tri-state */
		CLEAR_BIT(NPCX_DEVCNT, NPCX_DEVCNT_F_SPI_TRIS);
	}
}

void sspi_flash_execute_cmd(uint8_t code, uint8_t cts)
{
	/* set UMA_CODE */
	NPCX_UMA_CODE = code;
	/* execute UMA flash transaction */
	NPCX_UMA_CTS  = cts;
	while (IS_BIT_SET(NPCX_UMA_CTS, NPCX_UMA_CTS_EXEC_DONE))
		;
}

void sspi_flash_cs_level(int level)
{
	/* level is high */
	if (level) {
		/* Set chip select to high */
		SET_BIT(NPCX_UMA_ECTS, NPCX_UMA_ECTS_SW_CS1);
	} else { /* level is low */
		/* Set chip select to low */
		CLEAR_BIT(NPCX_UMA_ECTS, NPCX_UMA_ECTS_SW_CS1);
	}
}

void sspi_flash_wait_ready(void)
{
	uint8_t mask = SPI_FLASH_SR1_BUSY;

	/* Chip Select down. */
	sspi_flash_cs_level(0);
	/* Command for Read status register */
	sspi_flash_execute_cmd(CMD_READ_STATUS_REG, MASK_CMD_ONLY);
	do {
		/* Read status register */
		NPCX_UMA_CTS  = MASK_RD_1BYTE;
		while (IS_BIT_SET(NPCX_UMA_CTS, NPCX_UMA_CTS_EXEC_DONE))
			;
	} while (NPCX_UMA_DB0 & mask); /* Wait for Busy clear */
	/* Chip Select high. */
	sspi_flash_cs_level(1);
}

int sspi_flash_write_enable(void)
{
	uint8_t mask = SPI_FLASH_SR1_WEL;
	/* Write enable command */
	sspi_flash_execute_cmd(CMD_WRITE_EN, MASK_CMD_ONLY);
	/* Wait for flash is not busy */
	sspi_flash_wait_ready();

	if (NPCX_UMA_DB0 & mask)
		return 1;
	else
		return 0;
}

void sspi_flash_set_address(uint32_t dest_addr)
{
	uint8_t *addr = (uint8_t *)&dest_addr;
	/* Write address */
	NPCX_UMA_AB2 = addr[2];
	NPCX_UMA_AB1 = addr[1];
	NPCX_UMA_AB0 = addr[0];
}

void sspi_flash_burst_write(unsigned int dest_addr, unsigned int bytes,
		const char *data)
{
	unsigned int i;
	/* Chip Select down. */
	sspi_flash_cs_level(0);
	/* Set erase address */
	sspi_flash_set_address(dest_addr);
	/* Start write */
	sspi_flash_execute_cmd(CMD_FLASH_PROGRAM, MASK_CMD_WR_ADR);
	for (i = 0; i < bytes; i++) {
		sspi_flash_execute_cmd(*data, MASK_CMD_WR_ONLY);
		data++;
	}
	/* Chip Select up */
	sspi_flash_cs_level(1);
}

int sspi_flash_physical_clear_stsreg(void)
{
	/* Disable tri-state */
	sspi_flash_tristate(0);
	/* Enable write */
	sspi_flash_write_enable();

	NPCX_UMA_DB0 = 0x0;
	NPCX_UMA_DB1 = 0x0;

	/* Write status register 1/2 */
	sspi_flash_execute_cmd(CMD_WRITE_STATUS_REG, MASK_CMD_WR_2BYTE);

	/* Wait writing completed */
	sspi_flash_wait_ready();

	/* Read status register 1/2 for checking */
	sspi_flash_execute_cmd(CMD_READ_STATUS_REG, MASK_CMD_RD_1BYTE);
	if (NPCX_UMA_DB0 != 0x00)
		return 0;
	sspi_flash_execute_cmd(CMD_READ_STATUS_REG2, MASK_CMD_RD_1BYTE);
	if (NPCX_UMA_DB0 != 0x00)
		return 0;
	/* Enable tri-state */
	sspi_flash_tristate(1);

	return 1;
}

void sspi_flash_physical_write(int offset, int size, const char *data)
{
	int dest_addr = offset;
	const int sz_page = CONFIG_FLASH_WRITE_IDEAL_SIZE;

	/* Disable tri-state */
	sspi_flash_tristate(0);

	/* Write the data per CONFIG_FLASH_WRITE_IDEAL_SIZE bytes */
	for (; size >= sz_page; size -= sz_page) {
		/* Enable write */
		sspi_flash_write_enable();
		/* Burst UMA transaction */
		sspi_flash_burst_write(dest_addr, sz_page, data);
		/* Wait write completed */
		sspi_flash_wait_ready();

		data += sz_page;
		dest_addr += sz_page;
	}

	/* Handle final partial page, if any */
	if (size != 0) {
		/* Enable write */
		sspi_flash_write_enable();
		/* Burst UMA transaction */
		sspi_flash_burst_write(dest_addr, size, data);

		/* Wait write completed */
		sspi_flash_wait_ready();
	}

	/* Enable tri-state */
	sspi_flash_tristate(1);
}

void sspi_flash_physical_erase(int offset, int size)
{
	/* Disable tri-state */
	sspi_flash_tristate(0);

	/* Alignment has been checked in upper layer */
	for (; size > 0; size -= NPCX_MONITOR_FLASH_ERASE_SIZE,
		offset += NPCX_MONITOR_FLASH_ERASE_SIZE) {
		/* Enable write */
		sspi_flash_write_enable();
		/* Set erase address */
		sspi_flash_set_address(offset);
		/* Start erase */
		sspi_flash_execute_cmd(CMD_SECTOR_ERASE, MASK_CMD_ADR);

		/* Wait erase completed */
		sspi_flash_wait_ready();
	}

	/* Enable tri-state */
	sspi_flash_tristate(1);
}

int sspi_flash_verify(int offset, int size, const char *data)
{
	int i, result;
	uint8_t *ptr_flash;
	uint8_t *ptr_mram;
	uint8_t cmp_data;

	ptr_flash = (uint8_t *)(CONFIG_MAPPED_STORAGE_BASE + offset);
	ptr_mram  = (uint8_t *)data;
	result = 1;

	/* Disable tri-state */
	sspi_flash_tristate(0);

	/* Start to verify */
	for (i = 0; i < size; i++) {
		cmp_data = ptr_mram ? ptr_mram[i] : 0xFF;
		if (ptr_flash[i] != cmp_data) {
			result = 0;
			break;
		}
	}

	/* Enable tri-state */
	sspi_flash_tristate(1);
	return result;
}

int sspi_flash_get_image_used(const char *fw_base)
{
	const uint8_t *image;
	int size = MAX(CONFIG_RO_SIZE, CONFIG_RW_SIZE); /* max size is 128KB */

	image = (const uint8_t *)fw_base;
	/*
	 * Scan backwards looking for 0xea byte, which is by definition the
	 * last byte of the image.  See ec.lds.S for how this is inserted at
	 * the end of the image.
	 */
	for (size--; size > 0 && image[size] != 0xea; size--)
		;

	return size ? size + 1 : 0;  /* 0xea byte IS part of the image */

}

/* Entry function of spi upload function */
uint32_t  __attribute__ ((section(".startup_text")))
sspi_flash_upload(int spi_offset, int spi_size)
{
	/*
	 * Flash image has been uploaded to Code RAM
	 */
	uint32_t sz_image;
	uint32_t uut_tag;
	const char *image_base;
	uint32_t *flag_upload = (uint32_t *)SPI_PROGRAMMING_FLAG;
	struct monitor_header_tag *monitor_header =
		(struct monitor_header_tag *)NPCX_MONITOR_HEADER_ADDR;

	*flag_upload = 0;

	uut_tag = monitor_header->tag;
	/* If it is UUT tag, read required parameters from header */
	if (uut_tag == NPCX_MONITOR_UUT_TAG) {
		sz_image = monitor_header->size;
		spi_offset = monitor_header->dest_addr;
		image_base = (const char *)(monitor_header->src_addr);
	} else {
		sz_image = spi_size;
		image_base = (const char *)CONFIG_PROGRAM_MEMORY_BASE;
	}

	/* Unlock & stop watchdog */
	NPCX_WDSDM = 0x87;
	NPCX_WDSDM = 0x61;
	NPCX_WDSDM = 0x63;

	/* UMA Unlock */
	CLEAR_BIT(NPCX_UMA_ECTS, NPCX_UMA_ECTS_UMA_LOCK);

	/*
	 * If UUT is used, assuming the target is the internal flash.
	 * Don't switch the pinmux and make sure bit 7 of DEVALT0 is set.
	 */
	if (uut_tag == NPCX_MONITOR_UUT_TAG)
		SET_BIT(NPCX_DEVALT(0), NPCX_DEVALT0_NO_F_SPI);
	else
		/* Set pinmux first */
		sspi_flash_pinmux(1);

	/* Get size of image automatically */
	if (sz_image == 0)
		sz_image = sspi_flash_get_image_used(image_base);

	/* Clear status reg of spi flash for protection */
	if (sspi_flash_physical_clear_stsreg()) {
		/* Start to erase */
		sspi_flash_physical_erase(spi_offset, sz_image);
		/* Start to write */
		if (image_base != NULL)
			sspi_flash_physical_write(spi_offset, sz_image,
						image_base);
		/* Verify data */
		if (sspi_flash_verify(spi_offset, sz_image, image_base))
			*flag_upload |= 0x02;
	}
	if (uut_tag != NPCX_MONITOR_UUT_TAG)
		/* Disable pinmux */
		sspi_flash_pinmux(0);

	/* Mark we have finished upload work */
	*flag_upload |= 0x01;

	/* Return the status back to ROM code is required for UUT */
	if (uut_tag == NPCX_MONITOR_UUT_TAG)
		return *flag_upload;

	/* Infinite loop */
	for (;;)
		;
}

