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

/* Virtual battery cross-platform code for Chrome EC */

#include "battery.h"
#include "charge_state.h"
#include "i2c.h"
#include "system.h"
#include "util.h"
#include "virtual_battery.h"

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

#define BATT_MODE_UNINITIALIZED -1

/*
 * The state machine used to parse smart battery command
 * to support virtual battery.
 */
enum batt_cmd_parse_state {
	IDLE = 0, /* initial state */
	START = 1, /* received the register address (command code) */
	WRITE_VB, /* writing data bytes to the slave */
	READ_VB, /* reading data bytes to the slave */
};

static enum batt_cmd_parse_state sb_cmd_state;
static uint8_t cache_hit;
static const uint8_t *batt_cmd_head;
static int acc_write_len;

int virtual_battery_handler(struct ec_response_i2c_passthru *resp,
				   int in_len, int *err_code, int xferflags,
				   int read_len, int write_len,
				   const uint8_t *out)
{

#if defined(CONFIG_BATTERY_PRESENT_GPIO) || \
	defined(CONFIG_BATTERY_PRESENT_CUSTOM)
	/*
	 * If the battery isn't present, return a NAK (which we
	 * would have gotten anyways had we attempted to talk to
	 * the battery.)
	 */
	if (battery_is_present() != BP_YES) {
		resp->i2c_status = EC_I2C_STATUS_NAK;
		return EC_ERROR_INVAL;
	}
#endif
	switch (sb_cmd_state) {
	case IDLE:
		/*
		 * A legal battery command must start
		 * with a i2c write for reg index.
		 */
		if (write_len == 0) {
			resp->i2c_status = EC_I2C_STATUS_NAK;
			return EC_ERROR_INVAL;
		}
		/* Record the head of battery command. */
		batt_cmd_head = out;
		sb_cmd_state = START;
		*err_code = 0;
		break;
	case START:
		if (write_len > 0) {
			sb_cmd_state = WRITE_VB;
			*err_code = 0;
		} else {
			sb_cmd_state = READ_VB;
			*err_code = virtual_battery_operation(batt_cmd_head,
						NULL, 0, 0);
			/*
			 * If the reg is not handled by virtual battery, we
			 * do not support it.
			 */
			if (*err_code)
				return EC_ERROR_INVAL;
			cache_hit = 1;
		}
		break;
	case WRITE_VB:
		if (write_len == 0) {
			resp->i2c_status = EC_I2C_STATUS_NAK;
			reset_parse_state();
			return EC_ERROR_INVAL;
		}
		*err_code = 0;
		break;
	case READ_VB:
		if (read_len == 0) {
			resp->i2c_status = EC_I2C_STATUS_NAK;
			reset_parse_state();
			return EC_ERROR_INVAL;
		}
		/*
		 * Do not send the command to battery
		 * if the reg is cached.
		 */
		if (cache_hit)
			*err_code = 0;
		break;
	default:
		reset_parse_state();
		return EC_ERROR_INVAL;
	}

	acc_write_len += write_len;

	/* the last message */
	if (xferflags & I2C_XFER_STOP) {
		switch (sb_cmd_state) {
		/* write to virtual battery */
		case START:
		case WRITE_VB:
			virtual_battery_operation(batt_cmd_head,
						NULL,
						0,
						acc_write_len);
			break;
		/* read from virtual battery */
		case READ_VB:
			if (cache_hit) {
				read_len += in_len;
				memset(&resp->data[0], 0, read_len);
				virtual_battery_operation(batt_cmd_head,
							&resp->data[0],
							read_len,
							0);
			}
			break;
		default:
			reset_parse_state();
			return EC_ERROR_INVAL;

		}
		/* Reset the state in the end of messages */
		reset_parse_state();
	}
	return EC_RES_SUCCESS;
}

void reset_parse_state(void)
{
	sb_cmd_state = IDLE;
	cache_hit = 0;
	acc_write_len = 0;
}

/*
 * Copy memmap string data from offset to dest, up to size len, in the format
 * expected by SBS (first byte of dest contains strlen).
 */
void copy_memmap_string(uint8_t *dest, int offset, int len)
{
	uint8_t *memmap_str;
	uint8_t memmap_strlen;

	if (len == 0)
		return;
	memmap_str = host_get_memmap(offset);
	/* memmap_str might not be NULL terminated */
	memmap_strlen = *(memmap_str + EC_MEMMAP_TEXT_MAX - 1) == '\0' ?
			strlen(memmap_str) : EC_MEMMAP_TEXT_MAX;
	dest[0] = memmap_strlen;
	memcpy(dest + 1, memmap_str, MIN(memmap_strlen, len - 1));
}

int virtual_battery_operation(const uint8_t *batt_cmd_head,
			      uint8_t *dest,
			      int read_len,
			      int write_len)
{
	int val;
	int year, month, day;
	/*
	 * We cache battery operational mode locally for both read and write
	 * commands. If MODE_CAPACITY bit is set, battery capacity will be
	 * reported in 10mW/10mWh, instead of the default unit, mA/mAh.
	 * Note that we don't update the cached capacity: We do a real-time
	 * conversion and return the converted values.
	 */
	static int batt_mode_cache = BATT_MODE_UNINITIALIZED;
	const struct batt_params *curr_batt;
	/*
	 * Don't allow host reads into arbitrary memory space, most params
	 * are two bytes.
	 */
	int bounded_read_len = MIN(read_len, 2);

	curr_batt = charger_current_battery_params();
	switch (*batt_cmd_head) {
	case SB_BATTERY_MODE:
		if (write_len == 3) {
			batt_mode_cache = batt_cmd_head[1] |
					  (batt_cmd_head[2] << 8);
		} else if (read_len > 0) {
			if (batt_mode_cache == BATT_MODE_UNINITIALIZED)
				/*
				 * Read the battery operational mode from
				 * the battery to initialize batt_mode_cache.
				 * This may cause an i2c transaction.
				 */
				if (battery_get_mode(&batt_mode_cache) ==
				    EC_ERROR_UNIMPLEMENTED)
					/*
					 * Register not supported, choose
					 * typical SB defaults.
					 */
					batt_mode_cache =
					   MODE_INTERNAL_CHARGE_CONTROLLER |
					   MODE_ALARM |
					   MODE_CHARGER;

			memcpy(dest, &batt_mode_cache, bounded_read_len);
		}
		break;
	case SB_SERIAL_NUMBER:
		val = strtoi(host_get_memmap(EC_MEMMAP_BATT_SERIAL), NULL, 16);
		memcpy(dest, &val, bounded_read_len);
		break;
	case SB_VOLTAGE:
		if (curr_batt->flags & BATT_FLAG_BAD_VOLTAGE)
			return EC_ERROR_BUSY;
		memcpy(dest, &(curr_batt->voltage), bounded_read_len);
		break;
	case SB_RELATIVE_STATE_OF_CHARGE:
		if (curr_batt->flags & BATT_FLAG_BAD_STATE_OF_CHARGE)
			return EC_ERROR_BUSY;
		memcpy(dest, &(curr_batt->state_of_charge), bounded_read_len);
		break;
	case SB_TEMPERATURE:
		if (curr_batt->flags & BATT_FLAG_BAD_TEMPERATURE)
			return EC_ERROR_BUSY;
		memcpy(dest, &(curr_batt->temperature), bounded_read_len);
		break;
	case SB_CURRENT:
		if (curr_batt->flags & BATT_FLAG_BAD_CURRENT)
			return EC_ERROR_BUSY;
		memcpy(dest, &(curr_batt->current), bounded_read_len);
		break;
	case SB_FULL_CHARGE_CAPACITY:
		if (curr_batt->flags & BATT_FLAG_BAD_FULL_CAPACITY ||
				curr_batt->flags & BATT_FLAG_BAD_VOLTAGE)
			return EC_ERROR_BUSY;
		val = curr_batt->full_capacity;
		if (batt_mode_cache & MODE_CAPACITY)
			val = val * curr_batt->voltage / 10000;
		memcpy(dest, &val, bounded_read_len);
		break;
	case SB_BATTERY_STATUS:
		if (curr_batt->flags & BATT_FLAG_BAD_STATUS)
			return EC_ERROR_BUSY;
		memcpy(dest, &(curr_batt->status), bounded_read_len);
		break;
	case SB_CYCLE_COUNT:
		memcpy(dest, (int *)host_get_memmap(EC_MEMMAP_BATT_CCNT),
		       bounded_read_len);
		break;
	case SB_DESIGN_CAPACITY:
		if (curr_batt->flags & BATT_FLAG_BAD_VOLTAGE)
			return EC_ERROR_BUSY;
		val = *(int *)host_get_memmap(EC_MEMMAP_BATT_DCAP);
		if (batt_mode_cache & MODE_CAPACITY)
			val = val * curr_batt->voltage / 10000;
		memcpy(dest, &val, bounded_read_len);
		break;
	case SB_DESIGN_VOLTAGE:
		memcpy(dest, (int *)host_get_memmap(EC_MEMMAP_BATT_DVLT),
		       bounded_read_len);
		break;
	case SB_REMAINING_CAPACITY:
		if (curr_batt->flags & BATT_FLAG_BAD_REMAINING_CAPACITY ||
				curr_batt->flags & BATT_FLAG_BAD_VOLTAGE)
			return EC_ERROR_BUSY;
		val = curr_batt->remaining_capacity;
		if (batt_mode_cache & MODE_CAPACITY)
			val = val * curr_batt->voltage / 10000;
		memcpy(dest, &val, bounded_read_len);
		break;
	case SB_MANUFACTURER_NAME:
		copy_memmap_string(dest, EC_MEMMAP_BATT_MFGR, read_len);
		break;
	case SB_DEVICE_NAME:
		copy_memmap_string(dest, EC_MEMMAP_BATT_MODEL, read_len);
		break;
	case SB_DEVICE_CHEMISTRY:
		copy_memmap_string(dest, EC_MEMMAP_BATT_TYPE, read_len);
		break;
	case SB_AVERAGE_TIME_TO_FULL:
		/* This may cause an i2c transaction */
		if (battery_time_to_full(&val))
			return EC_ERROR_INVAL;
		memcpy(dest, &val, bounded_read_len);
		break;
	case SB_AVERAGE_TIME_TO_EMPTY:
		/* This may cause an i2c transaction */
		if (battery_time_to_empty(&val))
			return EC_ERROR_INVAL;
		memcpy(dest, &val, bounded_read_len);
		break;
	case SB_MANUFACTURE_DATE:
		/* This may cause an i2c transaction */
		if (!battery_manufacture_date(&year, &month, &day)) {
			/* Encode in Smart Battery Spec format */
			val = ((year - 1980) << 9) + (month << 5) + day;
		} else {
			/*
			 * Return 0 on error. The kernel is unhappy with
			 * returning an error code.
			 */
			val = 0;
		}
		memcpy(dest, &val, bounded_read_len);
		break;
	case SB_MANUFACTURER_ACCESS:
		/* No manuf. access reg access allowed over VB interface */
		return EC_ERROR_INVAL;
	case SB_SPECIFICATION_INFO:
		/* v1.1 without PEC, no scale factor to voltage and current */
		val = 0x0011;
		memcpy(dest, &val, bounded_read_len);
		break;
	default:
		CPRINTS("Unhandled VB reg %x", *batt_cmd_head);
		return EC_ERROR_INVAL;
	}
	return EC_SUCCESS;
}

