/* Copyright 2019 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.
 *
 * Battery driver for MM8013.
 */

#include "battery.h"
#include "battery_smart.h"
#include "console.h"
#include "i2c.h"
#include "mm8013.h"
#include "timer.h"
#include "util.h"

#define BATTERY_PACK_INFO_LENGTH 8

/* MM8013 requires a 100us wait time after a read operation. */
#define I2C_WAIT_TIME 100

static int mm8013_read16(int offset, int *data)
{
	int rv;

	*data = 0;
	rv = i2c_read16(I2C_PORT_BATTERY, MM8013_ADDR_FLAGS, offset, data);
	usleep(I2C_WAIT_TIME);
	if (rv)
		return rv;
	return EC_SUCCESS;
}

static int mm8013_read_block(int offset, uint8_t *data, int len)
{
	int rv;

	rv = i2c_read_block(I2C_PORT_BATTERY, MM8013_ADDR_FLAGS,
			    offset, data, len);
	usleep(I2C_WAIT_TIME);
	if (rv)
		return rv;
	return EC_SUCCESS;
}

static int battery_flag(int *flag)
{
	return mm8013_read16(REG_FLAGS, flag);
}

static int battery_current(int *current)
{
	int16_t tmp;
	int rv;

	rv = mm8013_read_block(REG_AVERAGE_CURRENT,
			(uint8_t *)&tmp, sizeof(int16_t));
	if (rv)
		return rv;
	*current = tmp;

	return EC_SUCCESS;
}

int battery_device_name(char *device_name, int buf_size)
{
	int rv;
	char out_buf[BATTERY_PACK_INFO_LENGTH + 1];

	rv = mm8013_read_block(REG_PRODUCT_INFORMATION,
			(uint8_t *)out_buf, BATTERY_PACK_INFO_LENGTH);
	if (rv)
		return rv;

	out_buf[BATTERY_PACK_INFO_LENGTH] = '\0';
	strzcpy(device_name, out_buf, buf_size);

	return EC_SUCCESS;
}

int battery_state_of_charge_abs(int *percent)
{
	return mm8013_read16(REG_STATE_OF_CHARGE, percent);
}

int battery_remaining_capacity(int *capacity)
{
	return mm8013_read16(REG_REMAINING_CAPACITY, capacity);
}

int battery_full_charge_capacity(int *capacity)
{
	return mm8013_read16(REG_FULL_CHARGE_CAPACITY, capacity);
}

int battery_time_to_empty(int *minutes)
{
	return mm8013_read16(REG_AVERAGE_TIME_TO_EMPTY, minutes);
}

int battery_time_to_full(int *minutes)
{
	return mm8013_read16(REG_AVERAGE_TIME_TO_FULL, minutes);
}

int battery_cycle_count(int *count)
{
	return mm8013_read16(REG_CYCLE_COUNT, count);
}

int battery_design_capacity(int *capacity)
{
	return mm8013_read16(REG_DESIGN_CAPACITY, capacity);
}

int battery_time_at_rate(int rate, int *minutes)
{
	return EC_ERROR_UNIMPLEMENTED;
}

int battery_device_chemistry(char *dest, int size)
{
	strzcpy(dest, "<unkn>", size);

	return EC_SUCCESS;
}

int battery_serial_number(int *serial)
{
	*serial = 0xFFFFFFFF;
	return EC_SUCCESS;
}

int battery_design_voltage(int *voltage)
{
	*voltage = battery_get_info()->voltage_normal;

	return EC_SUCCESS;
}

int battery_get_mode(int *mode)
{
	return EC_ERROR_UNIMPLEMENTED;
}

int battery_status(int *status)
{
	int rv;
	int flag = 0;

	*status = 0;

	rv = battery_flag(&flag);
	if (rv)
		return rv;

	if (flag & (MM8013_FLAG_OTC | MM8013_FLAG_OTD))
		*status |= STATUS_OVERTEMP_ALARM;
	if (flag & MM8013_FLAG_FC)
		*status |= STATUS_FULLY_CHARGED;
	if (flag & MM8013_FLAG_DSG)
		*status |= STATUS_DISCHARGING;
	if (flag & MM8013_FLAG_BATHI)
		*status |= STATUS_OVERCHARGED_ALARM;

	return EC_SUCCESS;
}

enum battery_present battery_is_present(void)
{
	int temp;

	if (mm8013_read16(REG_TEMPERATURE, &temp))
		return BP_NO;
	return BP_YES;
}

void battery_get_params(struct batt_params *batt)
{
	struct batt_params batt_new = {0};
	int flag = 0;

	/*
	 * Assuming the battery is responsive as long as
	 * mm8013 finds battery is present.
	 */
	batt_new.is_present = battery_is_present();

	if (batt_new.is_present == BP_YES)
		batt_new.flags |= BATT_FLAG_RESPONSIVE;
	else if (batt_new.is_present == BP_NO)
		/* Battery is not present, gauge won't report useful info. */
		goto batt_out;

	if (mm8013_read16(REG_TEMPERATURE, &batt_new.temperature))
		batt_new.flags |= BATT_FLAG_BAD_TEMPERATURE;

	if (mm8013_read16(REG_STATE_OF_CHARGE, &batt_new.state_of_charge))
		batt_new.flags |= BATT_FLAG_BAD_STATE_OF_CHARGE;

	if (mm8013_read16(REG_VOLTAGE, &batt_new.voltage))
		batt_new.flags |= BATT_FLAG_BAD_VOLTAGE;

	if (battery_current(&batt_new.current))
		batt_new.flags |= BATT_FLAG_BAD_CURRENT;

	batt_new.desired_voltage = battery_get_info()->voltage_max;
	batt_new.desired_current = BATTERY_DESIRED_CHARGING_CURRENT;

	if (battery_remaining_capacity(&batt_new.remaining_capacity))
		batt_new.flags |= BATT_FLAG_BAD_REMAINING_CAPACITY;

	if (battery_full_charge_capacity(&batt_new.full_capacity))
		batt_new.flags |= BATT_FLAG_BAD_FULL_CAPACITY;

	if (battery_flag(&flag) && (flag & MM8013_FLAG_CHG))
		batt_new.flags |= BATT_FLAG_WANT_CHARGE;

	if (battery_status(&batt_new.status))
		batt_new.flags |= BATT_FLAG_BAD_STATUS;

batt_out:
	/* Update visible battery parameters */
	memcpy(batt, &batt_new, sizeof(*batt));
}

#ifdef CONFIG_CMD_PWR_AVG
int battery_get_avg_current(void)
{
	/* TODO(crbug.com/752320) implement this */
	return -EC_ERROR_UNIMPLEMENTED;
}

int battery_get_avg_voltage(void)
{
	/* TODO(crbug.com/752320) implement this */
	return -EC_ERROR_UNIMPLEMENTED;
}
#endif /* CONFIG_CMD_PWR_AVG */

/* Wait until battery is totally stable. */
int battery_wait_for_stable(void)
{
	/* TODO(phoenixshen): Implement this function. */
	return EC_SUCCESS;
}
