/* Copyright 2017 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 MAX17055.
 */
#include "SRI_Global.h"
#include "Parse.h"
#include "sdi_task.h"
#include <stdio.h>
#include <string.h>
#include "Board.h"
#include "math.h"
#include "LP5521.h"
#include "i2c.h"
#include "max17055.h"
#include "Battery.h"


/*
 * Convert the register values to the units that match
 * smart battery protocol.
 */
/* Voltage reg value to mV */
#define VOLTAGE_CONV(REG)       ((REG * 5) >> 6)
/* Current reg value to mA */
#define CURRENT_CONV(REG)       (((REG * 25) >> 4) / BATTERY_MAX17055_RSENSE)
/* Capacity reg value to mAh */
#define CAPACITY_CONV(REG)      (REG * 5 / BATTERY_MAX17055_RSENSE)
/* Time reg value to minute */
#define TIME_CONV(REG)          ((REG * 3) >> 5)
/* Temperature reg value to 0.1K */
#define TEMPERATURE_CONV(REG)   (((REG * 10) >> 8) + 2731)
/* Percentage reg value to 1% */
#define PERCENTAGE_CONV(REG)    (REG >> 8)

/* Useful macros */
#define MAX17055_READ_DEBUG(offset, ptr_reg) \
	do { \
		if (max17055_read(offset, ptr_reg)) { \
			SDITask_Printf("%s: failed to read reg %02x", \
				__func__, offset); \
			return; \
		} \
	} while (0)


#define MAX17055_WRITE_DEBUG(offset, reg) \
	do { \
		if (max17055_write(offset, reg)) { \
			SDITask_Printf("%s: failed to read reg %02x", \
				__func__, offset); \
			return; \
		} \
	} while (0)


static int fake_state_of_charge = -1;

int max17055_read(int offset, int *data)
{
	  bool i2c_return;

	  i2c_txBuffer[0] = offset;

	  i2cTransaction.slaveAddress = MAX17055_ADDR;
	  i2cTransaction.writeBuf = i2c_txBuffer;
	  i2cTransaction.writeCount = 1;
	  i2cTransaction.readBuf = i2c_rxBuffer;
	  i2cTransaction.readCount = 2;
	  i2c_return = I2C_transfer(i2c, &i2cTransaction);
	  *data = (i2c_rxBuffer[1]*256 + i2c_rxBuffer[0]);
	  return(i2c_return);
}


int max17055_write(int offset, int data)
{
    bool i2c_return;

    i2c_txBuffer[0] = offset;
    i2c_txBuffer[1] = (data & 0x00FF);
    i2c_txBuffer[2] = (data >> 8);

    i2cTransaction.slaveAddress = MAX17055_ADDR;
    i2cTransaction.writeBuf = i2c_txBuffer;
    i2cTransaction.writeCount = 3;
    i2cTransaction.readBuf = i2c_rxBuffer;
    i2cTransaction.readCount = 0;
    i2c_return = I2C_transfer(i2c, &i2cTransaction);
    return(i2c_return);
}


/* Return 1 if the device id is correct. */
int max17055_probe(void)
{
	int dev_id;
	if (max17055_read(REG_DEVICE_NAME, &dev_id))
		return 0;
	if (dev_id == MAX17055_DEVICE_ID)
		return 1;
	return 0;
}


int battery_remaining_capacity(int *capacity)
{
	int rv;
	int reg;

	rv = max17055_read(REG_REMAINING_CAPACITY, &reg);
	if (!rv)
		*capacity = CAPACITY_CONV(reg);
	return rv;
}


int battery_full_charge_capacity(int *capacity)
{
	int rv;
	int reg;

	rv = max17055_read(REG_FULL_CHARGE_CAPACITY, &reg);
	if (!rv)
		*capacity = CAPACITY_CONV(reg);
	return rv;
}


int battery_time_to_empty(int *minutes)
{
	int rv;
	int reg;

	rv = max17055_read(REG_TIME_TO_EMPTY, &reg);
	if (!rv)
		*minutes = TIME_CONV(reg);
	return rv;
}


int battery_time_to_full(int *minutes)
{
	int rv;
	int reg;

	rv = max17055_read(REG_TIME_TO_FULL, &reg);
	if (!rv)
		*minutes = TIME_CONV(reg);
	return rv;
}


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


int battery_design_capacity(int *capacity)
{
	int rv;
	int reg;

	rv = max17055_read(REG_DESIGN_CAPACITY, &reg);
	if (!rv)
		*capacity = CAPACITY_CONV(reg);
	return rv;
}


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


enum battery_present battery_is_present(void)
{
	int status = 0;
	if (max17055_read(REG_STATUS, &status))
		return BP_NOT_SURE;
	if (status & STATUS_BST)
		return BP_NO;
	return BP_YES;
}


void battery_get_params(struct batt_params *batt)
{
	int reg = 0;
	const uint32_t flags_to_check = BATT_FLAG_BAD_TEMPERATURE |
					BATT_FLAG_BAD_STATE_OF_CHARGE |
					BATT_FLAG_BAD_VOLTAGE |
					BATT_FLAG_BAD_CURRENT;

	/* Reset flags */
	batt->flags = 0;
	if (max17055_read(REG_TEMPERATURE, &reg))
		batt->flags |= BATT_FLAG_BAD_TEMPERATURE;
	batt->temperature = TEMPERATURE_CONV((int16_t)reg);
	if (max17055_read(REG_STATE_OF_CHARGE, &reg) &&
	    fake_state_of_charge < 0)
		batt->flags |= BATT_FLAG_BAD_STATE_OF_CHARGE;
	batt->state_of_charge = fake_state_of_charge >= 0 ?
				fake_state_of_charge : PERCENTAGE_CONV(reg);
	if (max17055_read(REG_VOLTAGE, &reg))
		batt->flags |= BATT_FLAG_BAD_VOLTAGE;
	batt->voltage = VOLTAGE_CONV(reg);
	if (max17055_read(REG_AVERAGE_CURRENT, &reg))
		batt->flags |= BATT_FLAG_BAD_CURRENT;
	batt->current = CURRENT_CONV((int16_t)reg);
	batt->desired_voltage = battery_get_info()->voltage_max;
	batt->desired_current = BATTERY_DESIRED_CHARGING_CURRENT;
	if (battery_remaining_capacity(&batt->remaining_capacity))
		batt->flags |= BATT_FLAG_BAD_REMAINING_CAPACITY;
	if (battery_full_charge_capacity(&batt->full_capacity))
		batt->flags |= BATT_FLAG_BAD_FULL_CAPACITY;
	/* If any of those reads worked, the battery is responsive */
	if ((batt->flags & flags_to_check) != flags_to_check) {
		batt->flags |= BATT_FLAG_RESPONSIVE;
		batt->is_present = BP_YES;
	} else
		batt->is_present = BP_NOT_SURE;
	/*
	 * Charging allowed if both desired voltage and current are nonzero
	 * and battery isn't full (and we read them all correctly).
	 */
	if (!(batt->flags & BATT_FLAG_BAD_STATE_OF_CHARGE) &&
	    batt->desired_voltage &&
	    batt->desired_current &&
	    batt->state_of_charge < BATTERY_LEVEL_FULL)
		batt->flags |= BATT_FLAG_WANT_CHARGE;
}


/* Configured MAX17055 with the battery parameters for optimal performance. */
int max17055_init_config(void)
{
	int reg;
	int hib_cfg;
	int dqacc;
	int dpacc;
	int retries = 50;
	const struct max17055_batt_profile *config;

	config = max17055_get_batt_profile();
	if (config->is_ez_config)
	{
		dqacc = config->design_cap / 32;
		/* Choose the model for charge voltage > 4.275V. */
		dpacc = dqacc * 51200 / config->design_cap;
	} else
	{
		dqacc = config->design_cap / 16;
		dpacc = config->dpacc;
	}
	if (max17055_write(REG_DESIGN_CAPACITY, config->design_cap) ||
	    max17055_write(REG_DQACC, dqacc) ||
	    max17055_write(REG_CHARGE_TERM_CURRENT, config->ichg_term) ||
	    max17055_write(REG_EMPTY_VOLTAGE, config->v_empty_detect))
		return -1;
	if (!config->is_ez_config)
	{
		if (max17055_write(REG_LEARNCFG, config->learn_cfg))
			return -1;
	}
	/* Store the original HibCFG value. */
	if (max17055_read(REG_HIBCFG, &hib_cfg))
		return -1;
	/* Special sequence to exit hibernate mode. */
	if (max17055_write(0x60, 0x90) ||
	    max17055_write(REG_HIBCFG, 0) ||
	    max17055_write(0x60, 0))
		return -1;
	if (max17055_write(REG_DPACC, dpacc) ||
	    max17055_write(REG_MODELCFG, (MODELCFG_REFRESH | MODELCFG_VCHG)))
		return -1;
	/* Delay up to 500 ms until MODELCFG.REFRESH bit == 0. */
	while (--retries)
	{
		if (max17055_read(REG_MODELCFG, &reg))
			return -1;
		if (!(MODELCFG_REFRESH & reg))
			break;
		usleep(10000);
	}
	if (!retries)
		return -2;
	if (!config->is_ez_config)
	{
		if (max17055_write(REG_RCOMP0, config->rcomp0) ||
		    max17055_write(REG_TEMPCO, config->tempco) ||
		    max17055_write(REG_QR_TABLE00, config->qr_table00) ||
		    max17055_write(REG_QR_TABLE10, config->qr_table10) ||
		    max17055_write(REG_QR_TABLE20, config->qr_table20) ||
		    max17055_write(REG_QR_TABLE30, config->qr_table30))
			return -1;
	}
	/* Restore the original HibCFG value. */
	if (max17055_write(REG_HIBCFG, hib_cfg))
		return -1;
	return 0;
}


void max17055_init(void)
{
	int reg;
	int retries = 80;
	if (!max17055_probe())
	{
		SDITask_Printf("Wrong max17055 id!");
		return;
	}
	MAX17055_READ_DEBUG(REG_STATUS, &reg);
	/* Check for POR */
	if (STATUS_POR & reg)
	{
		/* Delay up to 800 ms until FSTAT.DNR bit == 0. */
		while (--retries)
		{
			MAX17055_READ_DEBUG(REG_FSTAT, &reg);
			if (!(FSTAT_DNR & reg))
				break;
			usleep(10000);
		}
		if (!retries)
		{
			SDITask_Printf("%s: timeout waiting for FSTAT.DNR cleared",
				__func__);
			return;
		}
		if (max17055_init_config())
		{
			SDITask_Printf("max17055 configuration failed!");
			return;
		}
	}
	/* Clear POR bit */
	MAX17055_READ_DEBUG(REG_STATUS, &reg);
	MAX17055_WRITE_DEBUG(REG_STATUS, (reg & ~STATUS_POR));
	/* Set CONFIG.TSEL to measure temperature using external thermistor */
	MAX17055_READ_DEBUG(REG_CONFIG, &reg);
	MAX17055_WRITE_DEBUG(REG_CONFIG, (reg | CONF_TSEL));
	SDITask_Printf("max17055 configuration succeeded!");
}


/************************************ Done ***********************************************/
