/* 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 "common.h"
#include "dma.h"
#include "hooks.h"
#include "i2c.h"
#include "link_defs.h"
#include "registers.h"
#include "timer.h"
#include "usb_descriptor.h"
#include "usb_power.h"
#include "util.h"

#define CPRINTS(format, args...) cprints(CC_I2C, format, ## args)



static int usb_power_init_inas(struct usb_power_config const *config);
static int usb_power_read(struct usb_power_config const *config);
static int usb_power_write_line(struct usb_power_config const *config);

void usb_power_deferred_rx(struct usb_power_config const *config)
{
	int rx_count = rx_ep_pending(config->endpoint);

	/* Handle an incoming command if available */
	if (rx_count)
		usb_power_read(config);
}

void usb_power_deferred_tx(struct usb_power_config const *config)
{
	struct usb_power_state *state = config->state;

	if (!tx_ep_is_ready(config->endpoint))
		return;

	/* We've replied, set up the next read. */
	if (!rx_ep_is_active(config->endpoint)) {
		/* Remove any active dma region from output buffer */
		state->reports_xmit_active = state->reports_tail;

		/* Wait for the next command */
		usb_read_ep(config->endpoint,
			config->ep->out_databuffer_max,
			config->ep->out_databuffer);
		return;
	}
}

/* Reset stream */
void usb_power_reset(struct usb_power_config const *config)
{
	config->ep->out_databuffer = config->state->rx_buf;
	config->ep->out_databuffer_max = sizeof(config->state->rx_buf);
	config->ep->in_databuffer = config->state->tx_buf;
	config->ep->in_databuffer_max = sizeof(config->state->tx_buf);

	epN_reset(config->endpoint);

	/* Flush any queued data */
	hook_call_deferred(config->ep->rx_deferred, 0);
	hook_call_deferred(config->ep->tx_deferred, 0);
}


/* Write one or more power records to USB */
static int usb_power_write_line(struct usb_power_config const *config)
{
	struct usb_power_state *state = config->state;
	struct usb_power_report *r = (struct usb_power_report *)(
		state->reports_data_area +
		(USB_POWER_RECORD_SIZE(state->ina_count)
		* state->reports_tail));
	/* status + size + timestamps + power list */
	size_t bytes = USB_POWER_RECORD_SIZE(state->ina_count);

	/* Check if queue has active data. */
	if (config->state->reports_head != config->state->reports_tail) {
		int recordcount = 1;

		/* We'll concatenate all the upcoming recrds. */
		if (config->state->reports_tail < config->state->reports_head)
			recordcount = config->state->reports_head -
				      config->state->reports_tail;
		else
			recordcount = state->max_cached -
				      config->state->reports_tail;

		state->reports_xmit_active = state->reports_tail;
		state->reports_tail = (state->reports_tail + recordcount) %
			state->max_cached;

		usb_write_ep(config->endpoint, bytes * recordcount, r);
		return bytes;
	}

	CPRINTS("usb_power_write_line: no data rs: %d, rc: %d",
		USB_POWER_RECORD_SIZE(state->ina_count),
		USB_POWER_MAX_CACHED(state->ina_count));
	return 0;
}


static int usb_power_state_reset(struct usb_power_config const *config)
{
	struct usb_power_state *state = config->state;

	state->state = USB_POWER_STATE_OFF;
	state->reports_head = 0;
	state->reports_tail = 0;
	state->reports_xmit_active = 0;

	CPRINTS("[RESET] STATE -> OFF");
	return USB_POWER_SUCCESS;
}


static int usb_power_state_stop(struct usb_power_config const *config)
{
	struct usb_power_state *state = config->state;

	/* Only a valid transition from CAPTURING */
	if (state->state != USB_POWER_STATE_CAPTURING) {
		CPRINTS("[STOP] Error not capturing.");
		return USB_POWER_ERROR_NOT_CAPTURING;
	}

	state->state = USB_POWER_STATE_SETUP;
	state->reports_head = 0;
	state->reports_tail = 0;
	state->reports_xmit_active = 0;
	state->stride_bytes = 0;
	CPRINTS("[STOP] STATE: CAPTURING -> SETUP");
	return USB_POWER_SUCCESS;
}



static int usb_power_state_start(struct usb_power_config const *config,
				 union usb_power_command_data *cmd, int count)
{
	struct usb_power_state *state = config->state;
	int integration_us = cmd->start.integration_us;

	if (state->state != USB_POWER_STATE_SETUP) {
		CPRINTS("[START] Error not setup.");
		return USB_POWER_ERROR_NOT_SETUP;
	}

	if (count != 6) {
		CPRINTS("[START] Error count %d is not 6", (int)count);
		return USB_POWER_ERROR_READ_SIZE;
	}

	if (integration_us == 0) {
		CPRINTS("[START] integration_us cannot be 0");
		return USB_POWER_ERROR_UNKNOWN;
	}

	/* Calculate the reports array */
	state->stride_bytes = USB_POWER_RECORD_SIZE(state->ina_count);
	state->max_cached = USB_POWER_MAX_CACHED(state->ina_count);

	state->integration_us = integration_us;
	usb_power_init_inas(config);

	state->state = USB_POWER_STATE_CAPTURING;
	CPRINTS("[START] STATE: SETUP -> CAPTURING %dus", integration_us);

	/* Find our starting time. */
	config->state->base_time = get_time().val;

	hook_call_deferred(config->deferred_cap, state->integration_us);
	return USB_POWER_SUCCESS;
}


static int usb_power_state_settime(struct usb_power_config const *config,
				 union usb_power_command_data *cmd, int count)
{
	if (count != sizeof(struct usb_power_command_settime)) {
		CPRINTS("[SETTIME] Error: count %d is not %d",
			(int)count, sizeof(struct usb_power_command_settime));
		return USB_POWER_ERROR_READ_SIZE;
	}

	/* Find the offset between microcontroller clock and host clock. */
	if (cmd->settime.time)
		config->state->wall_offset = cmd->settime.time - get_time().val;
	else
		config->state->wall_offset = 0;

	return EC_SUCCESS;
}


static int usb_power_state_addina(struct usb_power_config const *config,
				 union usb_power_command_data *cmd, int count)
{
	struct usb_power_state *state = config->state;
	struct usb_power_ina_cfg *ina;

	/* Only valid from OFF or SETUP */
	if ((state->state != USB_POWER_STATE_OFF) &&
	    (state->state != USB_POWER_STATE_SETUP)) {
		CPRINTS("[ADDINA] Error incorrect state.");
		return USB_POWER_ERROR_NOT_SETUP;
	}

	if (count != sizeof(struct usb_power_command_addina)) {
		CPRINTS("[ADDINA] Error count %d is not %d",
			(int)count, sizeof(struct usb_power_command_addina));
		return USB_POWER_ERROR_READ_SIZE;
	}

	if (state->ina_count >= USB_POWER_MAX_READ_COUNT) {
		CPRINTS("[ADDINA] Error INA list full");
		return USB_POWER_ERROR_FULL;
	}

	/* Transition to SETUP state if necessary and clear INA data */
	if (state->state == USB_POWER_STATE_OFF) {
		state->state = USB_POWER_STATE_SETUP;
		state->ina_count = 0;
	}

	/* Select INA to configure */
	ina = state->ina_cfg + state->ina_count;

	ina->port = cmd->addina.port;
	ina->addr = (cmd->addina.addr) << 1;  /* 7 to 8 bit addr. */
	ina->rs = cmd->addina.rs;

	state->ina_count += 1;
	return USB_POWER_SUCCESS;
}

static int usb_power_read(struct usb_power_config const *config)
{
	/*
	 * If there is a USB packet waiting we process it and generate a
	 * response.
	 */
	uint8_t count		= rx_ep_pending(config->endpoint);
	uint8_t result		= USB_POWER_SUCCESS;
	union usb_power_command_data *cmd =
		(union usb_power_command_data *)config->ep->out_databuffer;

	struct usb_power_state *state = config->state;
	struct dwc_usb_ep *ep = config->ep;

	/* Bytes to return */
	int in_msgsize = 1;

	if (count < 2)
		return EC_ERROR_INVAL;

	/* State machine. */
	switch (cmd->command) {
	case USB_POWER_CMD_RESET:
		result = usb_power_state_reset(config);
		break;

	case USB_POWER_CMD_STOP:
		result = usb_power_state_stop(config);
		break;

	case USB_POWER_CMD_START:
		result = usb_power_state_start(config, cmd, count);
		if (result == USB_POWER_SUCCESS) {
			/* Send back actual integration time. */
			ep->in_databuffer[1] =
				(state->integration_us >> 0) & 0xff;
			ep->in_databuffer[2] =
				(state->integration_us >> 8) & 0xff;
			ep->in_databuffer[3] =
				(state->integration_us >> 16) & 0xff;
			ep->in_databuffer[4] =
				(state->integration_us >> 24) & 0xff;
			in_msgsize += 4;
		}
		break;

	case USB_POWER_CMD_ADDINA:
		result = usb_power_state_addina(config, cmd, count);
		break;

	case USB_POWER_CMD_SETTIME:
		result = usb_power_state_settime(config, cmd, count);
		break;

	case USB_POWER_CMD_NEXT:
		if (state->state == USB_POWER_STATE_CAPTURING) {
			int ret;

			ret = usb_power_write_line(config);
			if (ret)
				return EC_SUCCESS;

			CPRINTS("[CAP] busy");
			result = USB_POWER_ERROR_BUSY;
		} else {
			CPRINTS("[STOP] Error not capturing.");
			result = USB_POWER_ERROR_NOT_CAPTURING;
		}
		break;

	default:
		CPRINTS("[ERROR] Unknown command 0x%04x", (int)cmd->command);
		result = USB_POWER_ERROR_UNKNOWN;
		break;
	}

	/* Return result code if applicable. */
	ep->in_databuffer[0] = result;

	usb_write_ep(config->endpoint, in_msgsize, ep->in_databuffer);

	return EC_SUCCESS;
}



/******************************************************************************
 * INA231 interface.
 * List the registers and fields here.
 * TODO(nsanders): combine with the currently incompatible common INA drivers.
 */

#define INA231_REG_CONF 0
#define INA231_REG_RSHV 1
#define INA231_REG_BUSV 2
#define INA231_REG_PWR  3
#define INA231_REG_CURR 4
#define INA231_REG_CAL  5
#define INA231_REG_EN   6


#define INA231_CONF_AVG(val)		(((int)(val & 0x7)) << 9)
#define INA231_CONF_BUS_TIME(val)	(((int)(val & 0x7)) << 6)
#define INA231_CONF_SHUNT_TIME(val)	(((int)(val & 0x7)) << 3)
#define INA231_CONF_MODE(val)		(((int)(val & 0x7)) << 0)
#define INA231_MODE_OFF			0x0
#define INA231_MODE_SHUNT		0x5
#define INA231_MODE_BUS			0x6
#define INA231_MODE_BOTH		0x7



uint16_t ina2xx_readagain(uint8_t port, uint8_t addr)
{
	int res;
	uint16_t val;

	res = i2c_xfer(port, addr, NULL, 0, (uint8_t *)&val, sizeof(uint16_t),
		      I2C_XFER_SINGLE);
	if (res) {
		CPRINTS("INA2XX I2C readagain failed p:%d a:%02x",
			(int)port, (int)addr);
		return 0x0bad;
	}
	return (val >> 8) | ((val & 0xff) << 8);
}


uint16_t ina2xx_read(uint8_t port, uint8_t addr, uint8_t reg)
{
	int res;
	int val;

	res = i2c_read16(port, addr, reg, &val);
	if (res) {
		CPRINTS("INA2XX I2C read failed p:%d a:%02x, r:%02x",
			(int)port, (int)addr, (int)reg);
		return 0x0bad;
	}
	return (val >> 8) | ((val & 0xff) << 8);
}

int ina2xx_write(uint8_t port, uint8_t addr, uint8_t reg, uint16_t val)
{
	int res;
	uint16_t be_val = (val >> 8) | ((val & 0xff) << 8);

	res = i2c_write16(port, addr, reg, be_val);
	if (res)
		CPRINTS("INA2XX I2C write failed");
	return res;
}



/******************************************************************************
 * Background tasks
 *
 * Here we setup the INAs and read them at the specified interval.
 * INA samples are stored in a ringbuffer that can be fetched using the
 * USB commands.
 */

/* INA231 integration and averaging time presets, indexed by register value */
static const int average_settings[] = {
	1, 4, 16, 64, 128, 256, 512, 1024};
static const int conversion_time_us[] =	{
	140, 204, 332, 588, 1100, 2116, 4156, 8244};

static int usb_power_init_inas(struct usb_power_config const *config)
{
	struct usb_power_state *state = config->state;
	int i;
	int shunt_time = 0;
	int avg = 0;
	int target_us = state->integration_us;

	if (state->state != USB_POWER_STATE_SETUP) {
		CPRINTS("[ERROR] usb_power_init_inas while not SETUP");
		return -1;
	}

	/* Find an INA preset integration time less than specified */
	while (shunt_time < 7) {
		if (conversion_time_us[shunt_time + 1] > target_us)
			break;
		shunt_time++;
	}

	/* Find an averaging setting from the INA presets that fits. */
	while (avg < 7) {
		if ((conversion_time_us[shunt_time] *
		     average_settings[avg + 1])
		    > target_us)
			break;
		avg++;
	}

	state->integration_us =
		conversion_time_us[shunt_time] * average_settings[avg];

	for (i = 0; i < state->ina_count; i++) {
		int value;
		int ret;
		struct usb_power_ina_cfg *ina = state->ina_cfg + i;

#ifdef USB_POWER_VERBOSE
		{
		int conf, cal;

		conf = ina2xx_read(ina->port, ina->addr, INA231_REG_CONF);
		cal = ina2xx_read(ina->port, ina->addr, INA231_REG_CAL);
		CPRINTS("[CAP] %d (%d,0x%02x): conf:%x, cal:%x",
			i, ina->port, ina->addr, conf, cal);
		}
#endif
		/*
		 * Calculate INA231 Calibration register
		 * CurrentLSB = uA per div = 80mV / (Rsh * 2^15)
		 * CurrentLSB uA = 80000000nV / (Rsh mOhm * 0x8000)
		 */
		ina->scale = 80000000 / (ina->rs * 0x8000);

		/*
		 * CAL = .00512 / (CurrentLSB * Rsh)
		 * CAL = 5120000 / (uA * mOhm)
		 */
		value = 5120000 / (ina->scale * ina->rs);
		ret = ina2xx_write(ina->port, ina->addr, INA231_REG_CAL, value);
		if (ret != EC_SUCCESS) {
			CPRINTS("[CAP] usb_power_init_inas CAL FAIL: %d", ret);
			return ret;
		}
#ifdef USB_POWER_VERBOSE
		{
		int actual;

		actual = ina2xx_read(ina->port, ina->addr, INA231_REG_CAL);
		CPRINTS("[CAP] scale: %d uA/div, %d uW/div, cal:%x act:%x",
			ina->scale, ina->scale*25, value, actual);
		}
#endif
		/* Conversion time, shunt + bus, set average. */
		value = INA231_CONF_MODE(INA231_MODE_BOTH) |
			INA231_CONF_SHUNT_TIME(shunt_time) |
			INA231_CONF_BUS_TIME(shunt_time) |
			INA231_CONF_AVG(avg);
		ret = ina2xx_write(
			ina->port, ina->addr, INA231_REG_CONF, value);
		if (ret != EC_SUCCESS) {
			CPRINTS("[CAP] usb_power_init_inas CONF FAIL: %d", ret);
			return ret;
		}
#ifdef USB_POWER_VERBOSE
		{
		int actual;

		actual = ina2xx_read(ina->port, ina->addr, INA231_REG_CONF);
		CPRINTS("[CAP] %d (%d,0x%02x): conf:%x, act:%x",
			i, ina->port, ina->addr, value, actual);
		}
#endif
#ifdef USB_POWER_VERBOSE
		{
		int busv_mv =
			(ina2xx_read(ina->port, ina->addr, INA231_REG_BUSV)
			 * 125) / 100;

		CPRINTS("[CAP] %d (%d,0x%02x): busv:%dmv",
			i, ina->port, ina->addr, busv_mv);
		}
#endif
		/* Initialize read from power register. This register address
		 * will be cached and all ina2xx_readagain() calls will read
		 * from the same address.
		 */
		ina2xx_read(ina->port, ina->addr, INA231_REG_PWR);
	}

	return EC_SUCCESS;
}


/*
 * Read each INA's power integration measurement.
 *
 * INAs recall the most recent address, so no register access write is
 * necessary, simply read 16 bits from each INA and fill the result into
 * the power record.
 *
 * If the power record ringbuffer is full, fail with USB_POWER_ERROR_OVERFLOW.
 */
static int usb_power_get_samples(struct usb_power_config const *config)
{
	uint64_t time = get_time().val;
	struct usb_power_state *state = config->state;
	struct usb_power_report *r = (struct usb_power_report *)(
		state->reports_data_area +
		(USB_POWER_RECORD_SIZE(state->ina_count)
		* state->reports_head));
	struct usb_power_ina_cfg *inas = state->ina_cfg;
	int i;

	/* TODO(nsanders): Would we prefer to evict oldest? */
	if (((state->reports_head + 1) % USB_POWER_MAX_CACHED(state->ina_count))
	    == state->reports_xmit_active) {
		CPRINTS("Overflow! h:%d a:%d t:%d (%d)",
			state->reports_head, state->reports_xmit_active,
			state->reports_tail,
			USB_POWER_MAX_CACHED(state->ina_count));
		return USB_POWER_ERROR_OVERFLOW;
	}

	r->status = USB_POWER_SUCCESS;
	r->size = state->ina_count;
	if (config->state->wall_offset)
		time = time + config->state->wall_offset;
	else
		time -= config->state->base_time;
	r->timestamp = time;

	for (i = 0; i < state->ina_count; i++) {
		int power;
		struct usb_power_ina_cfg *ina = inas + i;

		/* Read INA231.
		 * ina2xx_read(ina->port, ina->addr, INA231_REG_PWR);
		 * Readagain cached this address so we'll save an I2C
		 * transaction.
		 */
		power = ina2xx_readagain(ina->port, ina->addr);
		r->power[i] = power;
#ifdef USB_POWER_VERBOSE
		{
		int current;
		int voltage;
		int bvoltage;

		voltage = ina2xx_read(ina->port, ina->addr, INA231_REG_RSHV);
		bvoltage = ina2xx_read(ina->port, ina->addr, INA231_REG_BUSV);
		current = ina2xx_read(ina->port, ina->addr, INA231_REG_CURR);
		power = ina2xx_read(ina->port, ina->addr, INA231_REG_PWR);
		}
		{
		int uV = ((int)voltage * 25) / 10;
		int mV = ((int)bvoltage * 125) / 100;
		int uA = (uV * 1000) / ina->rs;
		int CuA = ((int)current * ina->scale);
		int uW = ((int)power * ina->scale*25);

		CPRINTS("[CAP] %d (%d,0x%02x): %dmV / %dmO = %dmA",
			i, ina->port, ina->addr, uV/1000, ina->rs, uA/1000);
		CPRINTS("[CAP] %duV %dmV %duA %dCuA "
			"%duW v:%04x, b:%04x, p:%04x",
			uV, mV, uA, CuA, uW, voltage, bvoltage, power);
		}
#endif
	}

	/* Mark this slot as used. */
	state->reports_head = (state->reports_head + 1) %
		USB_POWER_MAX_CACHED(state->ina_count);

	return EC_SUCCESS;
}

/*
 * This function is called every [interval] uS, and reads the accumulated
 * values of the INAs, and reschedules itself for the next interval.
 *
 * It will stop collecting frames if a ringbuffer overflow is
 * detected, or a stop request is seen..
 */
void usb_power_deferred_cap(struct usb_power_config const *config)
{
	int ret;
	uint64_t timeout = get_time().val + config->state->integration_us;
	uint64_t timein;

	/* Exit if we have stopped capturing in the meantime. */
	if (config->state->state != USB_POWER_STATE_CAPTURING)
		return;

	/* Get samples for this timeslice */
	ret = usb_power_get_samples(config);
	if (ret == USB_POWER_ERROR_OVERFLOW) {
		CPRINTS("[CAP] usb_power_deferred_cap: OVERFLOW");
		return;
	}

	/* Calculate time remaining until next slice. */
	timein = get_time().val;
	if (timeout > timein)
		timeout = timeout - timein;
	else
		timeout = 0;

	/* Double check if we are still capturing. */
	if (config->state->state == USB_POWER_STATE_CAPTURING)
		hook_call_deferred(config->deferred_cap, timeout);
}

