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

#include "atomic.h"
#include "common.h"
#include "console.h"
#include "dptf.h"
#include "hooks.h"
#include "host_command.h"
#include "temp_sensor.h"
#include "util.h"

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

/*****************************************************************************/
/* DPTF temperature thresholds */

static struct {
	int temp;     /* degrees K, negative for disabled */
	cond_t over;      /* watch for crossings */
} dptf_threshold[TEMP_SENSOR_COUNT][DPTF_THRESHOLDS_PER_SENSOR];

static void dptf_init(void)
{
	int id, t;

	for (id = 0; id < TEMP_SENSOR_COUNT; id++)
		for (t = 0; t < DPTF_THRESHOLDS_PER_SENSOR; t++) {
			dptf_threshold[id][t].temp = -1;
			cond_init(&dptf_threshold[id][t].over, 0);
		}

}
DECLARE_HOOK(HOOK_INIT, dptf_init, HOOK_PRIO_DEFAULT);

/* Keep track of which triggered sensor thresholds the AP has seen */
static uint32_t dptf_seen;

int dptf_query_next_sensor_event(void)
{
	int id;

	for (id = 0; id < TEMP_SENSOR_COUNT; id++)
		if (dptf_seen & (1 << id)) {  /* atomic? */
			atomic_clear(&dptf_seen, (1 << id));
			return id;
		}

	return -1;
}

/* Return true if any threshold transition occurs. */
static int dpft_check_temp_threshold(int sensor_id, int temp)
{
	int tripped = 0;
	int max, i;

	for (i = 0; i < DPTF_THRESHOLDS_PER_SENSOR; i++) {

		max = dptf_threshold[sensor_id][i].temp;
		if (max < 0)      /* disabled? */
			continue;

		if (temp >= max)
			cond_set_true(&dptf_threshold[sensor_id][i].over);
		else if (temp <= max - DPTF_THRESHOLD_HYSTERESIS)
			cond_set_false(&dptf_threshold[sensor_id][i].over);

		if (cond_went_true(&dptf_threshold[sensor_id][i].over)) {
			CPRINTS("DPTF over threshold [%d][%d",
				sensor_id, i);
			atomic_or(&dptf_seen, (1 << sensor_id));
			tripped = 1;
		}
		if (cond_went_false(&dptf_threshold[sensor_id][i].over)) {
			CPRINTS("DPTF under threshold [%d][%d",
				sensor_id, i);
			atomic_or(&dptf_seen, (1 << sensor_id));
			tripped = 1;
		}
	}

	return tripped;
}

void dptf_set_temp_threshold(int sensor_id, int temp, int idx, int enable)
{
	CPRINTS("DPTF sensor %d, threshold %d C, index %d, %sabled",
		sensor_id, K_TO_C(temp), idx, enable ? "en" : "dis");

	if (enable) {
		/* Don't update threshold condition if already enabled */
		if (dptf_threshold[sensor_id][idx].temp == -1)
			cond_init(&dptf_threshold[sensor_id][idx].over, 0);
		dptf_threshold[sensor_id][idx].temp = temp;
		atomic_clear(&dptf_seen, (1 << sensor_id));
	} else {
		dptf_threshold[sensor_id][idx].temp = -1;
	}
}

#ifdef CONFIG_DPTF_DEVICE_ORIENTATION
/*
 * When tablet mode changes, send an event to ACPI to retrieve
 * tablet mode value and send an event to the kernel.
 */
static void dptf_tablet_mode_changed(void)
{
	host_set_single_event(EC_HOST_EVENT_MODE_CHANGE);
}
DECLARE_HOOK(HOOK_TABLET_MODE_CHANGE, dptf_tablet_mode_changed,
	     HOOK_PRIO_DEFAULT);
#endif

/*****************************************************************************/
/* EC-specific thermal controls */

test_mockable_static void smi_sensor_failure_warning(void)
{
	CPRINTS("can't read any temp sensors!");
	host_set_single_event(EC_HOST_EVENT_THERMAL);
}

static void thermal_control_dptf(void)
{
	int i, t, rv;
	int dptf_tripped;
	int num_sensors_read;

	dptf_tripped = 0;
	num_sensors_read = 0;

	/* go through all the sensors */
	for (i = 0; i < TEMP_SENSOR_COUNT; ++i) {
		rv = temp_sensor_read(i, &t);
		if (rv != EC_SUCCESS)
			continue;
		else
			num_sensors_read++;
		/* and check the dptf thresholds */
		dptf_tripped |= dpft_check_temp_threshold(i, t);
	}

	if (!num_sensors_read) {
		/*
		 * Trigger a SMI event if we can't read any sensors.
		 *
		 * In theory we could do something more elaborate like forcing
		 * the system to shut down if no sensors are available after
		 * several retries.  This is a very unlikely scenario -
		 * particularly on LM4-based boards, since the LM4 has its own
		 * internal temp sensor.  It's most likely to occur during
		 * bringup of a new board, where we haven't debugged the I2C
		 * bus to the sensors; forcing a shutdown in that case would
		 * merely hamper board bringup.
		 */
		smi_sensor_failure_warning();
	}

	/* Don't forget to signal any DPTF thresholds */
	if (dptf_tripped)
		host_set_single_event(EC_HOST_EVENT_THERMAL_THRESHOLD);
}

/* Wait until after the sensors have been read */
DECLARE_HOOK(HOOK_SECOND, thermal_control_dptf, HOOK_PRIO_TEMP_SENSOR_DONE);

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

static int command_dptftemp(int argc, char **argv)
{
	int id, t;
	int temp, trig;

	ccprintf("sensor   thresh0   thresh1\n");
	for (id = 0; id < TEMP_SENSOR_COUNT; id++) {
		ccprintf(" %2d", id);
		for (t = 0; t < DPTF_THRESHOLDS_PER_SENSOR; t++) {
			temp = dptf_threshold[id][t].temp;
			trig = cond_is_true(&dptf_threshold[id][t].over);
			if (temp < 0)
				ccprintf("       --- ");
			else
				ccprintf("       %3d%c", temp,
					 trig ? '*' : ' ');
		}
		ccprintf("    %s\n", temp_sensors[id].name);
	}

	ccprintf("AP seen mask: 0x%08x\n", dptf_seen);
	return EC_SUCCESS;
}
DECLARE_CONSOLE_COMMAND(dptftemp, command_dptftemp,
			NULL,
			"Print DPTF thermal parameters (degrees Kelvin)");
