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

/* Chipset common code for Chrome EC */

#include "chipset.h"
#include "common.h"
#include "console.h"
#include "ec_commands.h"
#include "host_command.h"
#include "link_defs.h"
#include "system.h"
#include "task.h"
#include "timer.h"
#include "util.h"

/* Console output macros */
#define CPUTS(outstr) cputs(CC_CHIPSET, outstr)
#define CPRINTS(format, args...) cprints(CC_CHIPSET, format, ## args)

/*****************************************************************************/
/* Console commands */

#ifdef CONFIG_CMD_POWER_AP
static int command_apreset(int argc, char **argv)
{
	/* Force the chipset to reset */
	ccprintf("Issuing AP reset...\n");
	chipset_reset(CHIPSET_RESET_CONSOLE_CMD);
	return EC_SUCCESS;
}
DECLARE_CONSOLE_COMMAND(apreset, command_apreset,
			NULL,
			"Issue AP reset");

static int command_apshutdown(int argc, char **argv)
{
	chipset_force_shutdown(CHIPSET_SHUTDOWN_CONSOLE_CMD);
	return EC_SUCCESS;
}
DECLARE_CONSOLE_COMMAND(apshutdown, command_apshutdown,
			NULL,
			"Force AP shutdown");

#endif

#ifdef CONFIG_HOSTCMD_AP_RESET
static enum ec_status host_command_apreset(struct host_cmd_handler_args *args)
{
	/* Force the chipset to reset */
	chipset_reset(CHIPSET_RESET_HOST_CMD);
	return EC_RES_SUCCESS;
}
DECLARE_HOST_COMMAND(EC_CMD_AP_RESET,
		     host_command_apreset,
		     EC_VER_MASK(0));

#endif

#ifdef CONFIG_CMD_AP_RESET_LOG
static struct mutex reset_log_mutex;
static int next_reset_log __preserved_logs(next_reset_log);
static uint32_t ap_resets_since_ec_boot;
/* keep reset_logs size a power of 2 */
static struct ap_reset_log_entry
		reset_logs[4] __preserved_logs(reset_logs);
static int reset_log_checksum __preserved_logs(reset_log_checksum);

/* Calculate reset log checksum */
static int calc_reset_log_checksum(void)
{
	return next_reset_log ^ reset_logs[next_reset_log].reset_cause;
}

/* Initialize reset logs and next reset log */
void init_reset_log(void)
{
	if (next_reset_log < 0 || next_reset_log >= ARRAY_SIZE(reset_logs) ||
	    reset_log_checksum != calc_reset_log_checksum()) {
		reset_log_checksum = 0;
		next_reset_log = 0;
		memset(&reset_logs, 0, sizeof(reset_logs));
	}
}

void report_ap_reset(enum chipset_shutdown_reason reason)
{
	timestamp_t now = get_time();
	uint32_t now_ms = (uint32_t)(now.val / MSEC);

	mutex_lock(&reset_log_mutex);
	reset_logs[next_reset_log].reset_cause = reason;
	reset_logs[next_reset_log++].reset_time_ms = now_ms;
	next_reset_log &= ARRAY_SIZE(reset_logs) - 1;
	ap_resets_since_ec_boot++;
	mutex_unlock(&reset_log_mutex);

	/* Update checksum */
	reset_log_checksum = calc_reset_log_checksum();
}

test_mockable enum ec_error_list
get_ap_reset_stats(struct ap_reset_log_entry *reset_log_entries,
		   size_t num_reset_log_entries, uint32_t *resets_since_ec_boot)
{
	size_t log_address;
	size_t i;

	if (reset_log_entries == NULL || resets_since_ec_boot == NULL)
		return EC_ERROR_INVAL;

	mutex_lock(&reset_log_mutex);
	*resets_since_ec_boot = ap_resets_since_ec_boot;
	for (i = 0;
	     i != ARRAY_SIZE(reset_logs) && i != num_reset_log_entries;
	     ++i) {
		log_address = (next_reset_log + i) &
			(ARRAY_SIZE(reset_logs) - 1);
		reset_log_entries[i] = reset_logs[log_address];
	}
	mutex_unlock(&reset_log_mutex);

	return EC_SUCCESS;
}

#endif  /* !CONFIG_AP_RESET_LOG */

