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

/* System module for Chrome EC : hardware specific implementation */

#include "console.h"
#include "cpu.h"
#include "ec2i_chip.h"
#include "flash.h"
#include "hooks.h"
#include "host_command.h"
#include "intc.h"
#include "registers.h"
#include "system.h"
#include "task.h"
#include "util.h"
#include "version.h"
#include "watchdog.h"

void system_hibernate(uint32_t seconds, uint32_t microseconds)
{
#ifdef CONFIG_HOSTCMD_PD
	/* Inform the PD MCU that we are going to hibernate. */
	host_command_pd_request_hibernate();
	/* Wait to ensure exchange with PD before hibernating. */
	msleep(100);
#endif

	/* Flush console before hibernating */
	cflush();

	if (board_hibernate)
		board_hibernate();

	/* chip specific standby mode */
	__enter_hibernate(seconds, microseconds);
}

/* Clear reset flags if it's not cleared in check_reset_cause() */
static int delayed_clear_reset_flags;
static void clear_reset_flags(void)
{
	if (IS_ENABLED(CONFIG_BOARD_RESET_AFTER_POWER_ON) &&
			delayed_clear_reset_flags) {
		chip_save_reset_flags(0);
	}
}
DECLARE_HOOK(HOOK_INIT, clear_reset_flags, HOOK_PRIO_LAST);

static void check_reset_cause(void)
{
	uint32_t flags;
	uint8_t raw_reset_cause = IT83XX_GCTRL_RSTS & 0x03;
	uint8_t raw_reset_cause2 = IT83XX_GCTRL_SPCTRL4 & 0x07;

	/* Restore saved reset flags. */
	flags = chip_read_reset_flags();

	/* Clear reset cause. */
	IT83XX_GCTRL_RSTS |= 0x03;
	IT83XX_GCTRL_SPCTRL4 |= 0x07;

	/* Determine if watchdog reset or power on reset. */
	if (raw_reset_cause & 0x02) {
		flags |= EC_RESET_FLAG_WATCHDOG;
	} else if (raw_reset_cause & 0x01) {
		flags |= EC_RESET_FLAG_POWER_ON;
	} else {
		if ((IT83XX_GCTRL_RSTS & 0xC0) == 0x80)
			flags |= EC_RESET_FLAG_POWER_ON;
	}

	if (raw_reset_cause2 & 0x04)
		flags |= EC_RESET_FLAG_RESET_PIN;

	/* watchdog module triggers these reset */
	if (flags & (EC_RESET_FLAG_HARD | EC_RESET_FLAG_SOFT))
		flags &= ~EC_RESET_FLAG_WATCHDOG;

	/*
	 * On power-on of some boards, H1 releases the EC from reset but then
	 * quickly asserts and releases the reset a second time. This means the
	 * EC sees 2 resets. In order to carry over some important flags (e.g.
	 * HIBERNATE) to the second resets, the reset flag will not be wiped if
	 * we know this is the first reset.
	 */
	if (IS_ENABLED(CONFIG_BOARD_RESET_AFTER_POWER_ON) &&
			(flags & EC_RESET_FLAG_POWER_ON)) {
		if (flags & EC_RESET_FLAG_INITIAL_PWR) {
			/* Second boot, clear the flag immediately */
			chip_save_reset_flags(0);
		} else {
			/*
			 * First boot, Keep current flags and set INITIAL_PWR
			 * flag. EC reset should happen soon.
			 *
			 * It's possible that H1 never trigger EC reset, or
			 * reset happens before this line. Both cases should be
			 * fine because we will have the correct flag anyway.
			 */
			chip_save_reset_flags(chip_read_reset_flags() |
						EC_RESET_FLAG_INITIAL_PWR);

			/*
			 * Schedule chip_save_reset_flags(0) later.
			 * Wait until end of HOOK_INIT should be long enough.
			 */
			delayed_clear_reset_flags = 1;
		}
	} else {
		/* Clear saved reset flags. */
		chip_save_reset_flags(0);
	}

	system_set_reset_flags(flags);

	/* Clear PD contract recorded in bram if this is a power-on reset. */
	if (IS_ENABLED(CONFIG_IT83XX_RESET_PD_CONTRACT_IN_BRAM) &&
	   (flags == (EC_RESET_FLAG_POWER_ON | EC_RESET_FLAG_RESET_PIN))) {
		for (int i = 0; i < MAX_SYSTEM_BBRAM_IDX_PD_PORTS; i++)
			system_set_bbram((SYSTEM_BBRAM_IDX_PD0 + i), 0);
	}
}

static void system_reset_cause_is_unknown(void)
{
	/* No reset cause and not sysjump. */
	if (!system_get_reset_flags() && !system_jumped_to_this_image())
		/*
		 * We decrease 4 or 2 for "ec_reset_lp" here, that depend on
		 * which jump and link instruction has executed.
		 * eg: Andes core (jral5: LP=PC+2, jal: LP=PC+4)
		 */
		ccprintf("===Unknown reset! jump from %x or %x===\n",
				ec_reset_lp - 4, ec_reset_lp - 2);
}
DECLARE_HOOK(HOOK_INIT, system_reset_cause_is_unknown, HOOK_PRIO_FIRST);

int system_is_reboot_warm(void)
{
	uint32_t reset_flags;
	/*
	 * Check reset cause here,
	 * gpio_pre_init is executed faster than system_pre_init
	 */
	check_reset_cause();
	reset_flags = system_get_reset_flags();

	if ((reset_flags & EC_RESET_FLAG_RESET_PIN) ||
	    (reset_flags & EC_RESET_FLAG_POWER_ON) ||
	    (reset_flags & EC_RESET_FLAG_WATCHDOG) ||
	    (reset_flags & EC_RESET_FLAG_HARD) ||
	    (reset_flags & EC_RESET_FLAG_SOFT) ||
	    (reset_flags & EC_RESET_FLAG_HIBERNATE))
		return 0;
	else
		return 1;
}

void chip_pre_init(void)
{
	/* bit0, EC received the special waveform from iteflash */
	if (IT83XX_GCTRL_DBGROS & IT83XX_SMB_DBGR) {
		/*
		 * Wait ~200ms, so iteflash will have enough time to let
		 * EC enter follow mode. And once EC goes into follow mode, EC
		 * will be stayed here (no following sequences, eg:
		 * enable watchdog/write protect/power-on sequence...) until
		 * we reset it.
		 */
		for (int i = 0; i < (200 * MSEC / 15); i++)
			/* delay ~15.25us */
			IT83XX_GCTRL_WNCKR = 0;
	}

	if (IS_ENABLED(IT83XX_ETWD_HW_RESET_SUPPORT))
		/* System triggers a soft reset by default (command: reboot). */
		IT83XX_GCTRL_ETWDUARTCR &= ~ETWD_HW_RST_EN;

	if (IS_ENABLED(IT83XX_RISCV_WAKEUP_CPU_WITHOUT_INT_ENABLED))
		/*
		 * bit7: wake up CPU if it is in low power mode and
		 * an interrupt is pending.
		 */
		IT83XX_GCTRL_WMCR |= BIT(7);
}

#define BRAM_VALID_MAGIC        0x4252414D  /* "BRAM" */
#define BRAM_VALID_MAGIC_FIELD0 (BRAM_VALID_MAGIC & 0xff)
#define BRAM_VALID_MAGIC_FIELD1 ((BRAM_VALID_MAGIC >> 8) & 0xff)
#define BRAM_VALID_MAGIC_FIELD2 ((BRAM_VALID_MAGIC >> 16) & 0xff)
#define BRAM_VALID_MAGIC_FIELD3 ((BRAM_VALID_MAGIC >> 24) & 0xff)
void chip_bram_valid(void)
{
	int i;

	if ((BRAM_VALID_FLAGS0 != BRAM_VALID_MAGIC_FIELD0) ||
	    (BRAM_VALID_FLAGS1 != BRAM_VALID_MAGIC_FIELD1) ||
	    (BRAM_VALID_FLAGS2 != BRAM_VALID_MAGIC_FIELD2) ||
	    (BRAM_VALID_FLAGS3 != BRAM_VALID_MAGIC_FIELD3)) {
		/*
		 * Magic does not match, so BRAM must be uninitialized. Clear
		 * entire Bank0 BRAM, and set magic value.
		 */
		for (i = 0; i < BRAM_IDX_VALID_FLAGS0; i++)
			IT83XX_BRAM_BANK0(i) = 0;

		BRAM_VALID_FLAGS0 = BRAM_VALID_MAGIC_FIELD0;
		BRAM_VALID_FLAGS1 = BRAM_VALID_MAGIC_FIELD1;
		BRAM_VALID_FLAGS2 = BRAM_VALID_MAGIC_FIELD2;
		BRAM_VALID_FLAGS3 = BRAM_VALID_MAGIC_FIELD3;
	}
}

void system_pre_init(void)
{
	/* No initialization required */

}

uint32_t chip_read_reset_flags(void)
{
	uint32_t flags = 0;
	flags |= BRAM_RESET_FLAGS0 << 24;
	flags |= BRAM_RESET_FLAGS1 << 16;
	flags |= BRAM_RESET_FLAGS2 << 8;
	flags |= BRAM_RESET_FLAGS3;
	return flags;
}

void chip_save_reset_flags(uint32_t save_flags)
{
	BRAM_RESET_FLAGS0 = save_flags >> 24;
	BRAM_RESET_FLAGS1 = (save_flags >> 16) & 0xff;
	BRAM_RESET_FLAGS2 = (save_flags >> 8) & 0xff;
	BRAM_RESET_FLAGS3 = save_flags & 0xff;
}

void system_reset(int flags)
{
	uint32_t save_flags = 0;

	/* We never get this warning message in normal case. */
	if (IT83XX_GCTRL_DBGROS & IT83XX_SMB_DBGR) {
		ccprintf("!Reset will be failed due to EC is in debug mode!\n");
		cflush();
	}

	/* Disable interrupts to avoid task swaps during reboot. */
	interrupt_disable();

	/* Handle saving common reset flags. */
	system_encode_save_flags(flags, &save_flags);

	if (clock_ec_wake_from_sleep())
		save_flags |= EC_RESET_FLAG_HIBERNATE;

	/* Store flags to battery backed RAM. */
	chip_save_reset_flags(save_flags);

	/* If WAIT_EXT is set, then allow 10 seconds for external reset */
	if (flags & SYSTEM_RESET_WAIT_EXT) {
		int i;

		/* Wait 10 seconds for external reset */
		for (i = 0; i < 1000; i++) {
			watchdog_reload();
			udelay(10000);
		}
	}

	/* bit0: enable watchdog hardware reset. */
#ifdef IT83XX_ETWD_HW_RESET_SUPPORT
	if (flags & SYSTEM_RESET_HARD)
		IT83XX_GCTRL_ETWDUARTCR |= ETWD_HW_RST_EN;
#endif
	/*
	 * Writing invalid key to watchdog module triggers a soft or hardware
	 * reset. It depends on the setting of bit0 at ETWDUARTCR register.
	 */
	IT83XX_ETWD_ETWCFG |= 0x20;
	IT83XX_ETWD_EWDKEYR = 0x00;

	/* Spin and wait for reboot; should never return */
	while (1)
		;
}

int system_set_scratchpad(uint32_t value)
{
	BRAM_SCRATCHPAD3 = (value >> 24) & 0xff;
	BRAM_SCRATCHPAD2 = (value >> 16) & 0xff;
	BRAM_SCRATCHPAD1 = (value >> 8) & 0xff;
	BRAM_SCRATCHPAD0 = value & 0xff;

	return EC_SUCCESS;
}

uint32_t system_get_scratchpad(void)
{
	uint32_t value = 0;

	value |= BRAM_SCRATCHPAD3 << 24;
	value |= BRAM_SCRATCHPAD2 << 16;
	value |= BRAM_SCRATCHPAD1 << 8;
	value |= BRAM_SCRATCHPAD0;

	return value;
}

static uint32_t system_get_chip_id(void)
{
#ifdef IT83XX_CHIP_ID_3BYTES
	return (IT83XX_GCTRL_CHIPID1 << 16) | (IT83XX_GCTRL_CHIPID2 << 8) |
		IT83XX_GCTRL_CHIPID3;
#else
	return (IT83XX_GCTRL_CHIPID1 << 8) | IT83XX_GCTRL_CHIPID2;
#endif
}

static uint8_t system_get_chip_version(void)
{
	/* bit[3-0], chip version */
	return IT83XX_GCTRL_CHIPVER & 0x0F;
}

static char to_hex(int x)
{
	if (x >= 0 && x <= 9)
		return '0' + x;
	return 'a' + x - 10;
}

const char *system_get_chip_vendor(void)
{
	return "ite";
}

const char *system_get_chip_name(void)
{
	static char buf[8] = {'i', 't'};
	int num = (IS_ENABLED(IT83XX_CHIP_ID_3BYTES) ? 4 : 3);
	uint32_t chip_id = system_get_chip_id();

	for (int n = 2; num >= 0; n++, num--)
		buf[n] = to_hex(chip_id >> (num * 4) & 0xF);

	return buf;
}

const char *system_get_chip_revision(void)
{
	static char buf[3];
	uint8_t rev = system_get_chip_version();

	buf[0] = to_hex(rev + 0xa);
	buf[1] = 'x';
	buf[2] = '\0';
	return buf;
}

static int bram_idx_lookup(enum system_bbram_idx idx)
{
	if (idx >= SYSTEM_BBRAM_IDX_VBNVBLOCK0 &&
	    idx <= SYSTEM_BBRAM_IDX_VBNVBLOCK15)
		return BRAM_IDX_NVCONTEXT +
		       idx - SYSTEM_BBRAM_IDX_VBNVBLOCK0;
	if (idx == SYSTEM_BBRAM_IDX_PD0)
		return BRAM_IDX_PD0;
	if (idx == SYSTEM_BBRAM_IDX_PD1)
		return BRAM_IDX_PD1;
	if (idx == SYSTEM_BBRAM_IDX_PD2)
		return BRAM_IDX_PD2;
	return -1;
}

int system_get_bbram(enum system_bbram_idx idx, uint8_t *value)
{
	int bram_idx = bram_idx_lookup(idx);

	if (bram_idx < 0)
		return EC_ERROR_INVAL;

	*value = IT83XX_BRAM_BANK0(bram_idx);
	return EC_SUCCESS;
}

int system_set_bbram(enum system_bbram_idx idx, uint8_t value)
{
	int bram_idx = bram_idx_lookup(idx);

	if (bram_idx < 0)
		return EC_ERROR_INVAL;

	IT83XX_BRAM_BANK0(bram_idx) = value;
	return EC_SUCCESS;
}

#define BRAM_NVCONTEXT_SIZE (BRAM_IDX_NVCONTEXT_END - BRAM_IDX_NVCONTEXT + 1)
BUILD_ASSERT(EC_VBNV_BLOCK_SIZE <= BRAM_NVCONTEXT_SIZE);

uintptr_t system_get_fw_reset_vector(uintptr_t base)
{
	/*
	 * Because our reset vector is at the beginning of image copy
	 * (see init.S). So I just need to return 'base' here and EC will jump
	 * to the reset vector.
	 */
	return base;
}
