/* Copyright 2018 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 : hardware specific implementation */

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

/*
 * SCP_GPR[0] b15-b0  - scratchpad
 * SCP_GPR[0] b31-b16 - saved_flags
 */

int system_set_scratchpad(uint32_t value)
{
	/* Check if value fits in 16 bits */
	if (value & 0xffff0000)
		return EC_ERROR_INVAL;

	SCP_GPR[0] = (SCP_GPR[0] & 0xffff0000) | value;

	return EC_SUCCESS;
}

uint32_t system_get_scratchpad(void)
{
	return SCP_GPR[0] & 0xffff;
}

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

const char *system_get_chip_name(void)
{
	/* Support only SCP_A for now */
	return "scp_a";
}

const char *system_get_chip_revision(void)
{
	return "";
}

void chip_pre_init(void)
{
}

static void scp_cm4_mod(void)
{
	CM4_MODIFICATION = 3;
	CM4_DCM_FEATURE = 3;
}

static void scp_enable_pirq(void)
{
	/* Enable all peripheral to SCP IRQ, except IPC0. */
	SCP_INTC_IRQ_ENABLE = 0xFFFFFFFE;
	SCP_INTC_IRQ_ENABLE_MSB = 0xFFFFFFFF;
}

void system_pre_init(void)
{
	/* CM4 Modification */
	scp_cm4_mod();
	/* Clock */
	scp_enable_clock();
	/* Peripheral IRQ */
	scp_enable_pirq();
	/* Init dram mapping (and cache) */
	scp_memmap_init();
	/* Disable jump (mt_scp has only RW) and enable MPU. */
	system_disable_jump();
}

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

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

	/* Save current reset reasons if necessary */
	if (flags & SYSTEM_RESET_PRESERVE_FLAGS)
		save_flags = system_get_reset_flags() | EC_RESET_FLAG_PRESERVED;

	if (flags & SYSTEM_RESET_LEAVE_AP_OFF)
		save_flags |= EC_RESET_FLAG_AP_OFF;

	/* Remember that the software asked us to hard reboot */
	if (flags & SYSTEM_RESET_HARD)
		save_flags |= EC_RESET_FLAG_HARD;

	/* Reset flags are 32-bits, but save only 16 bits. */
	ASSERT(!(save_flags >> 16));
	SCP_GPR[0] = (save_flags << 16) | (SCP_GPR[0] & 0xffff);

	/* SCP can not hard reset itself */
	ASSERT(!(flags & SYSTEM_RESET_HARD));

	if (flags & SYSTEM_RESET_WAIT_EXT) {
		int i;

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

	/* Set watchdog timer to small value, and spin wait for watchdog reset */
	SCP_WDT_CFG = 0;
	SCP_WDT_CFG = SCP_WDT_ENABLE | SCP_WDT_PERIOD(1);
	watchdog_reload();
	while (1)
		;
}

static void check_reset_cause(void)
{
	uint32_t flags = 0;
	uint32_t raw_reset_cause = SCP_GPR[1];

	/* Set state to power-on */
	SCP_PWRON_STATE = PWRON_DEFAULT;

	if ((raw_reset_cause & 0xffff0000) == PWRON_DEFAULT) {
		/* Reboot */
		if (raw_reset_cause & PWRON_WATCHDOG)
			flags |= EC_RESET_FLAG_WATCHDOG;
		else if (raw_reset_cause & PWRON_RESET)
			flags |= EC_RESET_FLAG_POWER_ON;
		else
			flags |= EC_RESET_FLAG_OTHER;
	} else {
		/* Power lost restart */
		flags |= EC_RESET_FLAG_POWER_ON;
	}
	system_set_reset_flags(SCP_GPR[0] >> 16);
	SCP_GPR[0] &= 0xffff;
}

int system_is_reboot_warm(void)
{
	const uint32_t cold_flags =
		EC_RESET_FLAG_RESET_PIN |
		EC_RESET_FLAG_POWER_ON  |
		EC_RESET_FLAG_WATCHDOG  |
		EC_RESET_FLAG_HARD      |
		EC_RESET_FLAG_SOFT      |
		EC_RESET_FLAG_HIBERNATE;

	check_reset_cause();

	return !(system_get_reset_flags() & cold_flags);
}

int system_get_bbram(enum system_bbram_idx idx, uint8_t *value)
{
	return EC_ERROR_INVAL;
}
