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

/* PECI interface for Chrome EC */

#include "chipset.h"
#include "clock.h"
#include "clock_chip.h"
#include "common.h"
#include "console.h"
#include "gpio.h"
#include "hooks.h"
#include "peci.h"
#include "registers.h"
#include "task.h"
#include "timer.h"
#include "temp_sensor.h"
#include "util.h"


/* Initial PECI baud rate */
#define PECI_BAUD_RATE        750000

#define TEMP_AVG_LENGTH       4  /* Should be power of 2 */


/* PECI Time-out */
#define PECI_DONE_TIMEOUT_US  (10*MSEC)

#define NULL_PENDING_TASK_ID  0xFFFFFFFF
#define PECI_MAX_FIFO_SIZE    16
#define PROC_SOCKET           0x30
/* PECI Command Code */
enum peci_command_t {
	PECI_COMMAND_PING               = 0x00,
	PECI_COMMAND_GET_DIB            = 0xF7,
	PECI_COMMAND_GET_TEMP           = 0x01,
	PECI_COMMAND_RD_PKG_CFG         = 0xA1,
	PECI_COMMAND_WR_PKG_CFG         = 0xA5,
	PECI_COMMAND_RD_IAMSR           = 0xB1,
	PECI_COMMAND_RD_PCI_CFG         = 0x61,
	PECI_COMMAND_RD_PCI_CFG_LOCAL   = 0xE1,
	PECI_COMMAND_WR_PCI_CFG_LOCAL   = 0xE5,
	PECI_COMMAND_NONE               = 0xFF
};

#define PECI_COMMAND_GET_TEMP_WR_LENS 0x00
#define PECI_COMMAND_GET_TEMP_RD_LENS 0x02

/* PECI Domain Number */
static int temp_vals[TEMP_AVG_LENGTH];
static int temp_idx;
static uint8_t peci_sts;
/* For PECI Done interrupt usage */
static int peci_pending_task_id;

/*****************************************************************************/
/* Internal functions */

/**
 * This routine initiates the parameters of a PECI transaction
 *
 * @param   wr_length How many byte of *wr_data went to be send
 * @param   rd_length How many byte went to received (not include FCS)
 * @param   cmd_code  Command code
 * @param   *wr_data  Buffer pointer of write data
 * @return  TASK_EVENT_PECI_DONE that mean slave had a response
 */
static uint32_t peci_trans(
		uint8_t             wr_length,
		uint8_t             rd_length,
		enum peci_command_t cmd_code,
		uint8_t            *wr_data
)
{
	uint32_t events;
	/* Ensure no PECI transaction is in progress */
	if (IS_BIT_SET(NPCX_PECI_CTL_STS, NPCX_PECI_CTL_STS_START_BUSY)) {
		/*
		 * PECI transaction is in progress -
		 * can not initiate a new one
		 */
		return 0;
	}
	/* Set basic transaction parameters */
	NPCX_PECI_ADDR = PROC_SOCKET;
	NPCX_PECI_CMD = cmd_code;
	/* Aviod over space */
	if (rd_length > PECI_MAX_FIFO_SIZE)
		rd_length = PECI_MAX_FIFO_SIZE;
	/* Read-Length */
	NPCX_PECI_RD_LENGTH = rd_length;
	if (wr_length > PECI_MAX_FIFO_SIZE)
		wr_length = PECI_MAX_FIFO_SIZE;
	/* copy of data */
	for (events = 0; events < wr_length; events++)
		NPCX_PECI_DATA_OUT(events) = wr_data[events];
	/* Write-Length */
	if (cmd_code != PECI_COMMAND_PING) {
		if ((cmd_code == PECI_COMMAND_WR_PKG_CFG) ||
				(cmd_code == PECI_COMMAND_WR_PCI_CFG_LOCAL)) {
			/*CMD+AWFCS*/
			NPCX_PECI_WR_LENGTH = wr_length + 2;
			/* Enable AWFCS */
			SET_BIT(NPCX_PECI_CTL_STS, NPCX_PECI_CTL_STS_AWFCS_EN);
		} else {
			/*CMD*/
			NPCX_PECI_WR_LENGTH = wr_length + 1;
			/* Enable AWFCS */
			CLEAR_BIT(NPCX_PECI_CTL_STS,
					NPCX_PECI_CTL_STS_AWFCS_EN);
		}
	}

	/* Start the PECI transaction */
	SET_BIT(NPCX_PECI_CTL_STS, NPCX_PECI_CTL_STS_START_BUSY);

	/* It should be using a interrupt , don't waste cpu computing power */
	peci_pending_task_id = task_get_current();
	return task_wait_event_mask(TASK_EVENT_PECI_DONE,
					PECI_DONE_TIMEOUT_US);

}

/**
 * PECI transaction error status.
 *
 * @return  Bit3 - CRC error Bit4 - ABRT error
 */
static uint8_t peci_check_error_state(void)
{
	return peci_sts;
}

/*****************************************************************************/
/* PECI drivers */
int peci_get_cpu_temp(void)
{
	uint32_t events;
	int16_t cpu_temp = -1;

	/* Start PECI trans */
	events = peci_trans(PECI_COMMAND_GET_TEMP_WR_LENS,
				PECI_COMMAND_GET_TEMP_RD_LENS,
				PECI_COMMAND_GET_TEMP, NULL);
	/* if return DONE , that mean slave had a PECI response */
	if ((events & TASK_EVENT_PECI_DONE) == TASK_EVENT_PECI_DONE) {
		/* check CRC & ABRT */
		events = peci_check_error_state();
		if (events) {
			;
		} else {
			uint16_t *ptr;
			ptr = (uint16_t *)&cpu_temp;
			ptr[0] = (NPCX_PECI_DATA_IN(1) << 8) |
				 (NPCX_PECI_DATA_IN(0) << 0);
		}
	}
	return (int)cpu_temp;
}

int peci_temp_sensor_get_val(int idx, int *temp_ptr)
{
	int sum = 0;
	int success_cnt = 0;
	int i;

	if (!chipset_in_state(CHIPSET_STATE_ON))
		return EC_ERROR_NOT_POWERED;

	for (i = 0; i < TEMP_AVG_LENGTH; ++i) {
		if (temp_vals[i] >= 0) {
			success_cnt++;
			sum += temp_vals[i];
		}
	}

	/*
	 * Require at least two valid samples. When the AP transitions into S0,
	 * it is possible, depending on the timing of the PECI sample, to read
	 * an invalid temperature. This is very rare, but when it does happen
	 * the temperature returned is CONFIG_PECI_TJMAX. Requiring two valid
	 * samples here assures us that one bad maximum temperature reading
	 * when entering S0 won't cause us to trigger an over temperature.
	 */
	if (success_cnt < 2)
		return EC_ERROR_UNKNOWN;

	*temp_ptr = sum / success_cnt;
	return EC_SUCCESS;
}

static void peci_temp_sensor_poll(void)
{
	int val;

	val = peci_get_cpu_temp();
	if (val != -1) {
		temp_vals[temp_idx] = val;
		temp_idx = (temp_idx + 1) & (TEMP_AVG_LENGTH - 1);
	}
}
DECLARE_HOOK(HOOK_TICK, peci_temp_sensor_poll, HOOK_PRIO_TEMP_SENSOR);

static void peci_freq_changed(void)
{
	/* PECI's clock source is FMCLK */
	int freq = clock_get_fm_freq();
	int baud = 0xF;

	/* Disable polling while reconfiguring */
	NPCX_PECI_CTL_STS = 0;

	/*
	 * Set the maximum bit rate used by the PECI module during both
	 * Address Timing Negotiation and Data Timing Negotiation.
	 * The resulting maximum bit rate MAX_BIT_RATE in decimal is
	 * according to the following formula:
	 *
	 * MAX_BIT_RATE [d] = (freq / (4 * baudrate)) - 1
	 * Maximum bit rate should not extend the field's boundaries.
	 */
	if (freq != 0) {
		baud = (uint8_t)(freq  / (4 * PECI_BAUD_RATE)) - 1;
		/* Set maximum PECI baud rate (bit0 - bit4) */
		if (baud > 0x1F)
			baud = 0x1F;
	}
	/* Enhanced High-Speed */
	if (baud >= 7) {
		CLEAR_BIT(NPCX_PECI_RATE, 6);
		CLEAR_BIT(NPCX_PECI_CFG, 3);
	} else {
		SET_BIT(NPCX_PECI_RATE, 6);
		SET_BIT(NPCX_PECI_CFG, 3);
	}
	/* Setting Rate */
	NPCX_PECI_RATE = baud;
}
DECLARE_HOOK(HOOK_FREQ_CHANGE, peci_freq_changed, HOOK_PRIO_DEFAULT);

static void peci_init(void)
{
	int i;

	/* Enable clock for PECI peripheral */
	clock_enable_peripheral(CGC_OFFSET_PECI, CGC_PECI_MASK,
			CGC_MODE_RUN | CGC_MODE_SLEEP);
	/* Set PECI freq */
	peci_freq_changed();

	/* make sure PECI_DATA function pin enable */
	CLEAR_BIT(NPCX_DEVALT(0x0A), 6);
	/* Set initial clock frequency */
	peci_freq_changed();
	/* Initialize temperature reading buffer to a valid value. */
	for (i = 0; i < TEMP_AVG_LENGTH; ++i)
		temp_vals[i] = 300; /* 27 C */

	/* init Pending task id */
	peci_pending_task_id = NULL_PENDING_TASK_ID;
	/* Enable PECI Done interrupt */
	SET_BIT(NPCX_PECI_CTL_STS, NPCX_PECI_CTL_STS_DONE_EN);

	task_enable_irq(NPCX_IRQ_PECI);
}
DECLARE_HOOK(HOOK_INIT, peci_init, HOOK_PRIO_DEFAULT);

/* If received a PECI DONE interrupt, post the event to PECI task */
void peci_done_interrupt(void){
	if (peci_pending_task_id != NULL_PENDING_TASK_ID)
		task_set_event(peci_pending_task_id, TASK_EVENT_PECI_DONE);
	peci_sts = NPCX_PECI_CTL_STS & 0x18;
	/* no matter what, clear status bit again */
	SET_BIT(NPCX_PECI_CTL_STS, NPCX_PECI_CTL_STS_DONE);
	SET_BIT(NPCX_PECI_CTL_STS, NPCX_PECI_CTL_STS_CRC_ERR);
	SET_BIT(NPCX_PECI_CTL_STS, NPCX_PECI_CTL_STS_ABRT_ERR);
}
DECLARE_IRQ(NPCX_IRQ_PECI, peci_done_interrupt, 4);

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

static int command_peci_temp(int argc, char **argv)
{
	int t = peci_get_cpu_temp();
	if (t == -1) {
		ccprintf("PECI response timeout\n");
		return EC_ERROR_UNKNOWN;
	}
	ccprintf("CPU temp = %d K = %d\n", t, K_TO_C(t));
	return EC_SUCCESS;
}
DECLARE_CONSOLE_COMMAND(pecitemp, command_peci_temp,
		NULL,
		"Print CPU temperature");
