/* Copyright (c) 2013 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 pack vendor provided charging profile
 */

#include "battery.h"
#include "battery_smart.h"
#include "gpio.h"
#include "host_command.h"
#include "util.h"
#include "console.h"

/* Console output macros */
#define CPRINTS(format, args...) cprints(CC_CHARGER, format, ## args)

/* These 2 defines are for cut_off command for 3S battery */
#define SB_SHIP_MODE_ADDR	0x3a
#define SB_SHIP_MODE_DATA	0xc574

static struct battery_info *battery_info;
static int support_cut_off;

struct battery_device {
	char			manuf[9];
	char			device[9];
	int			design_mv;
	struct battery_info	*battery_info;
	int			support_cut_off;
};

/*
 * Used for the case that battery cannot be detected, such as the pre-charge
 * case. In this case, we need to provide the battery with the enough voltage
 * (usually the highest voltage among batteries, but the smallest precharge
 * current). This should be as conservative as possible.
 */
static struct battery_info info_precharge = {

	.voltage_max    = 12900,  /* the max voltage among batteries */
	.voltage_normal = 11400,
	.voltage_min    =  9000,

	/* Pre-charge values. */
	.precharge_current  = 256,  /* mA, the min current among batteries */

	.start_charging_min_c = 0,
	.start_charging_max_c = 50,
	.charging_min_c       = 0,
	.charging_max_c       = 60,
	.discharging_min_c    = 0,
	.discharging_max_c    = 75,
};

static struct battery_info info_2s = {
	/*
	 * Design voltage
	 *   max    = 8.4V
	 *   normal = 7.4V
	 *   min    = 6.0V
	 */
	.voltage_max    = 8400,
	.voltage_normal = 7400,
	.voltage_min    = 6000,

	/* Pre-charge current: I <= 0.01C */
	.precharge_current  = 64, /* mA */

	/*
	 * Operational temperature range
	 *   0 <= T_charge    <= 50 deg C
	 * -20 <= T_discharge <= 60 deg C
	 */
	.start_charging_min_c = 0,
	.start_charging_max_c = 50,
	.charging_min_c       = 0,
	.charging_max_c       = 50,
	.discharging_min_c    = -20,
	.discharging_max_c    = 60,
};

static struct battery_info info_3s = {

	.voltage_max    = 12600,
	.voltage_normal = 11100, /* Average of max & min */
	.voltage_min    =  9000,

	/* Pre-charge values. */
	.precharge_current  = 392,	/* mA */

	.start_charging_min_c = 0,
	.start_charging_max_c = 60,
	.charging_min_c       = 0,
	.charging_max_c       = 60,
	.discharging_min_c    = 0,
	.discharging_max_c    = 50,
};

static struct battery_info info_3s_LGC = {

        .voltage_max    = 12900,
        .voltage_normal = 11400, /* Average of max & min */
        .voltage_min    =  9000,

        /* Pre-charge values. */
        .precharge_current  = 256,      /* mA */

        .start_charging_min_c = 0,
        .start_charging_max_c = 50,
        .charging_min_c       = 0,
        .charging_max_c       = 60,
        .discharging_min_c    = 0,
        .discharging_max_c    = 75,
};

static struct battery_info info_4s_LGC = {

	.voltage_max    = 17200,
	.voltage_normal = 15200, /* Average of max & min */
	.voltage_min    = 12000,

	/* Pre-charge values. */
	.precharge_current  = 256,	/* mA */

	.start_charging_min_c = 0,
	.start_charging_max_c = 50,
	.charging_min_c       = 0,
	.charging_max_c       = 60,
	.discharging_min_c    = 0,
	.discharging_max_c    = 75,
};

static struct battery_device support_batteries[] = {
	{
		.manuf			= "NVT",
		.device			= "ARROW",
		.design_mv		= 7400,
		.battery_info		= &info_2s,
		.support_cut_off	= 0,
	},
	{
		.manuf			= "SANYO",
		.device			= "AP13J3K",
		.design_mv		= 11250,
		.battery_info		= &info_3s,
		.support_cut_off	= 1,
	},
	{
		.manuf			= "SONYCorp",
		.device			= "AP13J4K",
		.design_mv		= 11400,
		.battery_info		= &info_3s,
		.support_cut_off	= 1,
	},
	{
		.manuf			= "LGC",
		.device			= "AC14B8K",
		.design_mv		= 15200,
		.battery_info		= &info_4s_LGC,
		.support_cut_off	= 1,
	},
	{
		.manuf                  = "LGC",
		.device                 = "AC14B18J",
		.design_mv              = 11400,
		.battery_info           = &info_3s_LGC,
		.support_cut_off        = 1,
	},
};

#ifdef CONFIG_BATTERY_OVERRIDE_PARAMS
/*
 * The following parameters are for 2S battery.
 * There is no corresponding params for 3S battery.
 */
enum {
	TEMP_RANGE_10,
	TEMP_RANGE_23,
	TEMP_RANGE_35,
	TEMP_RANGE_45,
	TEMP_RANGE_50,
	TEMP_RANGE_MAX
};

enum {
	VOLT_RANGE_7200,
	VOLT_RANGE_8000,
	VOLT_RANGE_8400,
	VOLT_RANGE_MAX
};

/*
 * Vendor provided charging method
 *      temp  : < 7.2V, 7.2V ~ 8.0V, 8.0V ~ 8.4V
 *  -  0 ~ 10 :  0.8A       1.6A         0.8A
 *  - 10 ~ 23 :  1.6A       4.0A         1.6A
 *  - 23 ~ 35 :  4.0A       4.0A         4.0A
 *  - 35 ~ 45 :  1.6A       4.0A         1.6A
 *  - 45 ~ 50 :  0.8A       1.6A         0.8A
 */
static const int const current_limit[TEMP_RANGE_MAX][VOLT_RANGE_MAX] = {
	{ 800, 1600,  800},
	{1600, 4000, 1600},
	{4000, 4000, 4000},
	{1600, 4000, 1600},
	{ 800, 1600,  800},
};

static inline void limit_value(int *val, int limit)
{
	if (*val > limit)
		*val = limit;
}

void battery_override_params(struct batt_params *batt)
{
	int *desired_current = &batt->desired_current;
	int temp_range, volt_range;
	int bat_temp_c = DECI_KELVIN_TO_CELSIUS(batt->temperature);

	if (battery_info == NULL)
		return;

	/* Return if the battery is not a 2S battery */
	if (battery_info->voltage_max != info_2s.voltage_max)
		return;

	/* Limit charging voltage */
	if (batt->desired_voltage > battery_info->voltage_max)
		batt->desired_voltage = battery_info->voltage_max;

	/* Don't charge if outside of allowable temperature range */
	if (bat_temp_c >= battery_info->charging_max_c ||
	    bat_temp_c < battery_info->charging_min_c) {
		batt->desired_voltage = 0;
		batt->desired_current = 0;
		batt->flags &= ~BATT_FLAG_WANT_CHARGE;
		return;
	}

	if (bat_temp_c <= 10)
		temp_range = TEMP_RANGE_10;
	else if (bat_temp_c <= 23)
		temp_range = TEMP_RANGE_23;
	else if (bat_temp_c <= 35)
		temp_range = TEMP_RANGE_35;
	else if (bat_temp_c <= 45)
		temp_range = TEMP_RANGE_45;
	else
		temp_range = TEMP_RANGE_50;

	if (batt->voltage < 7200)
		volt_range = VOLT_RANGE_7200;
	else if (batt->voltage < 8000)
		volt_range = VOLT_RANGE_8000;
	else
		volt_range = VOLT_RANGE_8400;

	limit_value(desired_current, current_limit[temp_range][volt_range]);

	/* If battery wants current, give it at least the precharge current */
	if (*desired_current > 0 &&
	    *desired_current < battery_info->precharge_current)
		*desired_current = battery_info->precharge_current;
}
#endif	/* CONFIG_BATTERY_OVERRIDE_PARAMS */

const struct battery_info *battery_get_info(void)
{
	int i;
	char manuf[9];
	char device[9];
	int design_mv;

	if (battery_manufacturer_name(manuf, sizeof(manuf))) {
		CPRINTS("Failed to get MANUF name");
		return &info_precharge;
	}

	if (battery_device_name(device, sizeof(device))) {
		CPRINTS("Failed to get DEVICE name");
		return &info_precharge;
	}
	if (battery_design_voltage((int *)&design_mv)) {
		CPRINTS("Failed to get DESIGN_VOLTAGE");
		return &info_precharge;
	}

	for (i = 0; i < ARRAY_SIZE(support_batteries); ++i) {
		if ((strcasecmp(support_batteries[i].manuf, manuf) == 0) &&
		    (strcasecmp(support_batteries[i].device, device) == 0) &&
		    (support_batteries[i].design_mv == design_mv)) {
			CPRINTS("battery Manuf:%s, Device=%s, design=%u",
				manuf, device, design_mv);
			support_cut_off = support_batteries[i].support_cut_off;
			battery_info = support_batteries[i].battery_info;
			return battery_info;
		}
	}

	CPRINTS("un-recognized battery Manuf:%s, Device:%s",
		manuf, device);
	return &info_precharge;
}

int board_cut_off_battery(void)
{
	if (support_cut_off)
		return sb_write(SB_SHIP_MODE_ADDR, SB_SHIP_MODE_DATA);
	else
		return EC_RES_INVALID_COMMAND;
}
