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

/* Flash memory module for stm32f4 */

#include "clock.h"
#include "compile_time_macros.h"
#include "console.h"
#include "common.h"
#include "flash.h"
#include "hooks.h"
#include "registers.h"
#include "system.h"
#include "panic.h"
#include "watchdog.h"


#define CPRINTS(format, args...) cprints(CC_CLOCK, format, ## args)

/*
 * Approximate number of CPU cycles per iteration of the loop when polling
 * the flash status
 */
#define CYCLE_PER_FLASH_LOOP 10

/* Flash page programming timeout.  This is 2x the datasheet max. */
#define FLASH_TIMEOUT_US 16000

static inline int calculate_flash_timeout(void)
{
	return (FLASH_TIMEOUT_US *
		(clock_get_freq() / SECOND) / CYCLE_PER_FLASH_LOOP);
}


/* Flag indicating whether we have locked down entire flash */
static int entire_flash_locked;

#define FLASH_SYSJUMP_TAG 0x5750 /* "WP" - Write Protect */
#define FLASH_HOOK_VERSION 1
/* The previous write protect state before sys jump */
struct flash_wp_state {
	int entire_flash_locked;
};

/*****************************************************************************/
/* Physical layer APIs */

/* Flash unlocking keys */
#define PRG_LOCK 0
#define KEY1    0x45670123
#define KEY2    0xCDEF89AB

static int unlock(void)
{
	/*
	 * We may have already locked the flash module and get a bus fault
	 * in the attempt to unlock. Need to disable bus fault handler now.
	 */
	ignore_bus_fault(1);

	/* unlock CR if needed */
	if (STM32_FLASH_CR & FLASH_CR_LOCK) {
		STM32_FLASH_KEYR = KEY1;
		STM32_FLASH_KEYR = KEY2;
	}

	/* Re-enable bus fault handler */
	ignore_bus_fault(0);

	return (STM32_FLASH_CR & FLASH_CR_LOCK) ?
			EC_ERROR_UNKNOWN : EC_SUCCESS;
}

static void lock(void)
{
	STM32_FLASH_CR = FLASH_CR_LOCK;
}


int flash_physical_get_protect(int block)
{
	/* TODO: not sure if write protect can be implemented like this. */
	return 0;
}

uint32_t flash_physical_get_protect_flags(void)
{
	return entire_flash_locked ? EC_FLASH_PROTECT_ALL_NOW : 0;
}

int flash_physical_protect_now(int all)
{
	if (all) {
		/*
		 * Lock by writing a wrong key to FLASH_KEYR. This triggers a
		 * bus fault, so we need to disable bus fault handler while
		 * doing this.
		 *
		 * This incorrect key fault causes the flash to become
		 * permanenlty locked until reset, a correct keyring write
		 * will not unlock it. In this way we can implement system
		 * write protect.
		 */
		ignore_bus_fault(1);
		STM32_FLASH_KEYR = 0xffffffff;
		ignore_bus_fault(0);

		entire_flash_locked = 1;

		/* Check if lock happened */
		if (STM32_FLASH_CR & FLASH_CR_LOCK)
			return EC_SUCCESS;
	}

	/* No way to protect just the RO flash until next boot */
	return EC_ERROR_INVAL;
}

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;
}

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;
}

int flash_physical_restore_state(void)
{
	uint32_t reset_flags = system_get_reset_flags();
	int version, size;
	const struct flash_wp_state *prev;

	/*
	 * 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) {
		prev = (const struct flash_wp_state *)system_get_jump_tag(
				FLASH_SYSJUMP_TAG, &version, &size);
		if (prev && version == FLASH_HOOK_VERSION &&
		    size == sizeof(*prev))
			entire_flash_locked = prev->entire_flash_locked;
		return 1;
	}

	return 0;
}

static int flash_idle(void)
{
	timestamp_t deadline;

	deadline.val = get_time().val + FLASH_TIMEOUT_US;
	/* Wait for flash op to complete.
	 * This function is used for both reads and writes, so
	 * we need a long timeout, but a relatively short poll interval.
	 */
	while ((STM32_FLASH_SR & FLASH_SR_BUSY) &&
		(get_time().val < deadline.val)) {
		usleep(1);
	}

	if (STM32_FLASH_SR & FLASH_SR_BUSY)
		return EC_ERROR_TIMEOUT;

	return EC_SUCCESS;
}

static void clear_flash_errors(void)
{
	/* Clear previous error status */
	STM32_FLASH_SR = FLASH_SR_ERR_MASK;
}

/*****************************************************************************/
/* Physical layer APIs */

int flash_physical_protect_at_boot(uint32_t new_flags)
{
	return EC_SUCCESS;
}

int flash_physical_write(int offset, int size, const char *data)
{
	uint32_t *address = (uint32_t *)(CONFIG_MAPPED_STORAGE_BASE + offset);
	int res = EC_SUCCESS;

	if (unlock() != EC_SUCCESS) {
		res = EC_ERROR_UNKNOWN;
		goto exit_wr;
	}

	/* Wait for busy to clear */
	res = flash_idle();
	if (res)
		goto exit_wr;
	clear_flash_errors();

	/* set PG bit */
	STM32_FLASH_CR &= ~FLASH_CR_PSIZE_MASK;
	STM32_FLASH_CR |= FLASH_CR_PSIZE(FLASH_CR_PSIZE_32);
	STM32_FLASH_CR |= FLASH_CR_PG;

	for (; size > 0; size -= sizeof(uint32_t)) {
		/*
		 * Reload the watchdog timer to avoid watchdog reset when doing
		 * long writing with interrupt disabled.
		 */
		watchdog_reload();

		res = flash_idle();
		if (res)
			goto exit_wr;

		/* write the word */
		*address = data[0] + (data[1] << 8) +
			   (data[2] << 16) + (data[3] << 24);

		address++;
		data += sizeof(uint32_t);

		res = flash_idle();
		if (res)
			goto exit_wr;

		if (STM32_FLASH_SR & FLASH_SR_BUSY) {
			res = EC_ERROR_TIMEOUT;
			goto exit_wr;
		}

		/* Check for error conditions - erase failed, voltage error,
		 * protection error.
		 */
		if (STM32_FLASH_SR & FLASH_SR_ERR_MASK) {
			res = EC_ERROR_UNKNOWN;
			goto exit_wr;
		}
	}

exit_wr:
	/* Disable PG bit */
	STM32_FLASH_CR &= ~FLASH_CR_PG;

	lock();

	return res;
}



/* "@Internal Flash  /0x08000000/04*016Kg,01*064Kg,03*128Kg" */
struct flash_sector {
	int base;
	int size;
};
static const struct flash_sector sectors[] = {
	{(0 * 1024), (16 * 1024)},
	{(16 * 1024), (16 * 1024)},
	{(32 * 1024), (16 * 1024)},
	{(48 * 1024), (16 * 1024)},
	{(64 * 1024), (64 * 1024)},
	{(128 * 1024), (128 * 1024)},
	{(256 * 1024), (128 * 1024)},
	{(384 * 1024), (128 * 1024)}
};
static const int num_sectors = ARRAY_SIZE(sectors);

int flash_physical_erase(int offset, int size)
{
	int res = EC_SUCCESS;
	int start_sector;
	int end_sector;

	/* Check that offset/size align with sectors. */
	for (start_sector = 0; start_sector < num_sectors; start_sector++)
		if (offset == sectors[start_sector].base)
			break;
	for (end_sector = start_sector; end_sector < num_sectors; end_sector++)
		if ((offset + size) ==
		    (sectors[end_sector].base + sectors[end_sector].size))
			break;

	/* We can only erase on sector boundaries. */
	if ((start_sector >= num_sectors) || (end_sector >= num_sectors))
		return EC_ERROR_PARAM1;

	if (unlock() != EC_SUCCESS)
		return EC_ERROR_UNKNOWN;

	res = flash_idle();
	if (res)
		goto exit_er;

	clear_flash_errors();

	for (; start_sector <= end_sector; start_sector++) {
		/* Do nothing if already erased */
		if (flash_is_erased(sectors[start_sector].base,
				    sectors[start_sector].size))
			continue;

		res = flash_idle();
		if (res)
			goto exit_er;

		/* set Sector Erase bit and select sector */
		STM32_FLASH_CR = (STM32_FLASH_CR & ~FLASH_CR_SNB_MASK) |
				FLASH_CR_SER | FLASH_CR_SNB(start_sector);

		/* set STRT bit : start erase */
		STM32_FLASH_CR |= FLASH_CR_STRT;

		/*
		 * Reload the watchdog timer to avoid watchdog reset during a
		 * long erase operation.
		 */
		watchdog_reload();

		/* Wait for erase to complete, this will be awhile */
		res = flash_idle();
		if (res)
			goto exit_er;
		/*
		 * Check for error conditions - erase failed, voltage error,
		 * protection error
		 */
		if (STM32_FLASH_SR & FLASH_SR_ERR_MASK) {
			res = EC_ERROR_UNKNOWN;
			goto exit_er;
		}
	}

exit_er:
	/* reset PER bit */
	STM32_FLASH_CR &= ~FLASH_CR_SER;

	lock();

	return res;
}

/*****************************************************************************/
/* High-level APIs */

int flash_pre_init(void)
{
	return EC_SUCCESS;
}

/*****************************************************************************/
/* Hooks */

static void flash_preserve_state(void)
{
	struct flash_wp_state state;

	state.entire_flash_locked = entire_flash_locked;

	system_add_jump_tag(FLASH_SYSJUMP_TAG, FLASH_HOOK_VERSION,
			    sizeof(state), &state);
}
DECLARE_HOOK(HOOK_SYSJUMP, flash_preserve_state, HOOK_PRIO_DEFAULT);

