/* USRP E310 Firmware Texas Instruments BQ2419x driver
 * Copyright (C) 2014 Ettus Research
 * This file is part of the USRP E310 Firmware
 * The USRP E310 Firmware is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 * The USRP E310 Firmware is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with the USRP E310 Firmware. If not, see <http://www.gnu.org/licenses/>.
 */

#include "bq2419x.h"
#include "io.h"
#include "i2c_twi.h"
#include "interrupt.h"
#include "pmu.h"
#include "mcu_settings.h"
#include "utils.h"

#include <stdbool.h>
#include <string.h>

#include <util/atomic.h>
#include <util/delay.h>

static const uint8_t BQ2419X_I2C_ADDR = 0x6b;

#define bq2419x_read(reg, val) \
	(i2c_twi_read(BQ2419X_I2C_ADDR, reg, val))

#define bq2419x_write(reg, val) \
	(i2c_twi_write(BQ2419X_I2C_ADDR, reg, val))

/* register addresses */
static const uint8_t BQ2419X_REG_INPUT_SRC_CTL		=	0x00;
static const uint8_t BQ2419X_REG_PWR_ON_CONFIG		=	0x01;
static const uint8_t BQ2419X_REG_CHARGE_CURRENT		=	0x02;
static const uint8_t BQ2419X_REG_PRE_TERM_CURRENT	=	0x03;
static const uint8_t BQ2419X_REG_CHARGE_VOLTAGE		=	0x04;
static const uint8_t BQ2419X_REG_TIMER_CONTROL		=	0x05;
static const uint8_t BQ2419X_REG_THERMAL_REG_CTRL	=	0x06;
static const uint8_t BQ2419X_REG_MISC_OPERATION		=	0x07;
static const uint8_t BQ2419X_REG_SYSTEM_STATUS		=	0x08;
static const uint8_t BQ2419X_REG_FAULT				=	0x09;
static const uint8_t BQ2419X_REG_VENDOR_PART_REV	=	0x0a;

/* input source control register (REG00) */
static const uint8_t BQ2419X_EN_HIZ_MASK = BIT(7);
static const uint8_t BQ2419X_EN_HIZ_SHIFT = 7;

/* power on configuration register (REG01) */
static const uint8_t BQ2419X_REGISTER_RESET_MASK	=	BIT(7);
static const uint8_t BQ2419X_REGISTER_RESET_SHIFT	=	7;
static const uint8_t BQ2419X_I2C_TIMER_RESET	=	BIT(6);
static const uint8_t BQ2419X_I2C_TIMER_SHIFT	=	BIT(6);

static const uint8_t BQ2419X_CHARGE_CFG_MASK	=	BIT(5) | BIT(4);
static const uint8_t BQ2419X_CHARGE_CFG_SHIFT	=	4;
static const uint8_t BQ2419X_SYS_MIN_MASK	=	BIT(3) | BIT(2) | BIT(1);
static const uint8_t BQ2419X_SYS_MIN_SHIFT	=	1;

/* charge current control register (REG02) */
static const uint8_t BQ2419X_ICHG_MASK	=	BIT(7) | BIT(6) \
    | BIT(5) | BIT(4) | BIT(3) | BIT(2);
static const uint8_t BQ2419X_ICHG_SHIFT	=	2;
/* reserved */
static const uint8_t BQ2419X_FORCE_20_PCT_MASK	=	BIT(0);
static const uint8_t BQ2419X_FORCE_20_PCT_SHIFT	=	0;

static const uint8_t BQ2419X_CHARGE_CFG_DISABLED	=	0x00;
static const uint8_t BQ2419X_CHARGE_CFG_CHARGE	=	0x01;
static const uint8_t BQ2419X_CHARGE_CFG_OTG	=	0x03;

/* pre charge / termination current control register (REG03) */

/* charge voltage control register (REG04) */

/* charge / termination register (REG05) */
static const uint8_t BQ2419X_EN_TERM_MASK = BIT(7);
static const uint8_t BQ2419X_EN_TERM_SHIFT = BIT(7);
static const uint8_t BQ2419X_TERM_STAT_MASK = BIT(6);
static const uint8_t BQ2419X_TERM_STAT_SHIFT = 6;
static const uint8_t BQ2419X_WDT_MASK	= BIT(5) | BIT(4);
static const uint8_t BQ2419X_WDT_SHIFT	= 4;
static const uint8_t BQ2419X_EN_TIMER_MASK	= BIT(3);
static const uint8_t BQ2419X_EN_TIMER_SHIFT	= 3;

/* ir compensation / thermal regulation control register (REG06) */
static const uint8_t BQ2419X_BAT_COMP_MASK = BIT(7) | BIT(6) | BIT(5);
static const uint8_t BQ2419X_BAT_COMP_SHIFT = 5;
static const uint8_t BQ2419X_TREG_MASK = BIT(1) | BIT(0);
static const uint8_t BQ2419X_TREG_SHIFT = 0;

/* misc operation register (REG07) */
static const uint8_t BQ2419X_DPDM_EN_MASK	=	BIT(7);
static const uint8_t BQ2419X_DPDM_EN_SHIFT	=	7;
static const uint8_t BQ2419X_TMR2X_EN_MASK	=	BIT(6);
static const uint8_t BQ2419X_TMR2X_EN_SHIFT	=	6;
static const uint8_t BQ2419X_BATFET_DISABLE_MASK	= BIT(5);
static const uint8_t BQ2419X_BATFET_DISABLE_SHIFT	= 5;

static const uint8_t BQ2419X_JEITA_VSET_MASK	= BIT(4);
static const uint8_t BQ2419X_JEITA_VSET_SHIFT	= BIT(4);
/* reserved bits */
/* reserved bits */
static const uint8_t BQ2419X_INT_MASK_MASK	= BIT(1) | BIT(0);
static const uint8_t BQ2419X_INT_MASK_SHIFT	= 0;

/* system status register (REG08) */
static const uint8_t BQ2419X_VBUS_STAT_MASK	=	BIT(7) | BIT(6);
static const uint8_t BQ2419X_VBUS_STAT_SHIFT	=	6;
static const uint8_t BQ2419X_CHG_STAT_MASK	=	BIT(5) | BIT(4);
static const uint8_t BQ2419X_CHG_STAT_SHIFT	=	4;
static const uint8_t BQ2419X_DPM_STAT_MASK	=	BIT(3);
static const uint8_t BQ2419X_DPM_STAT_SHIFT	=	3;
static const uint8_t BQ2419X_PG_STAT_MASK	=	BIT(2);
static const uint8_t BQ2419X_PG_STAT_SHIFT	=	2;
static const uint8_t BQ2419X_THERM_STAT_MASK	=	BIT(1);
static const uint8_t BQ2419X_THERM_STAT_SHIFT	=	1;
static const uint8_t BQ2419X_VSYS_STAT_MASK	=	BIT(0);
static const uint8_t BQ2419X_VSYS_STAT_SHIFT	=	0;

static const uint8_t BQ2419X_CHARGE_STATUS_NOT_CHARGING	= 0x00;
static const uint8_t BQ2419X_CHARGE_STATUS_PRE_CHARGE	= 0x01;
static const uint8_t BQ2419X_CHARGE_STATUS_FAST_CHARGE	= 0x02;
static const uint8_t BQ2419X_CHARGE_STATUS_DONE	= 0x03;

/* fault register (REG09) */
static const uint8_t BQ2419X_WATCHDOG_FAULT_MASK	=	BIT(7);
static const uint8_t BQ2419X_WATCHDOG_FAULT_SHIFT	=	7;
static const uint8_t BQ2419X_BOOST_FAULT_MASK	=	BIT(6);
static const uint8_t BQ2419X_BOOST_FAULT_SHIFT	=	6;
static const uint8_t BQ2419X_CHG_FAULT_MASK	=	BIT(5) | BIT(4);
static const uint8_t BQ2419X_CHG_FAULT_SHIFT	=	4;
static const uint8_t BQ2419X_BAT_FAULT_MASK	=	BIT(3);
static const uint8_t BQ2419X_BAT_FAULT_SHIFT	=	3;
static const uint8_t BQ2419X_NTC_FAULT_MASK	=	BIT(2) | BIT(1) | BIT(0);
static const uint8_t BQ2419X_NTC_FAULT_SHIFT = 0;

static io_pin_t CHG_IRQ	= IO_PB(1);

typedef struct bq2419x_pmu_charger
{
	pmu_charger_t pmu_charger;
	uint8_t fault;
	uint8_t status;
	bool first_time;

	bool battery_status_valid;
	bool battery_health_valid;
	bool charger_health_valid;

	volatile bool event;
} bq2419x_pmu_charger_t;

static bq2419x_pmu_charger_t charger;

static volatile bool bq2419x_event = false;

int8_t bq2419x_set_charger(bool on)
{
	uint8_t config;
	int8_t ret;

	ret = bq2419x_read(BQ2419X_REG_PWR_ON_CONFIG, &config);
	if (ret)
		return ret;

	config &= ~BQ2419X_CHARGE_CFG_MASK;
	if (on)
		config |= 1 << BQ2419X_CHARGE_CFG_SHIFT;

	ret = bq2419x_write(BQ2419X_REG_PWR_ON_CONFIG, config);
	if (ret)
		return ret;

	return 0;
}

static int8_t bq2419x_reset(void)
{
	uint8_t config;
	int8_t ret;
	uint8_t retry = 100;

	ret = bq2419x_read(BQ2419X_REG_PWR_ON_CONFIG, &config);
	if (ret)
		return ret;

	config |= BQ2419X_REGISTER_RESET_MASK;
	ret = bq2419x_write(BQ2419X_REG_PWR_ON_CONFIG, config);

	do {
		ret = bq2419x_read(BQ2419X_REG_PWR_ON_CONFIG, &config);
		if (!(config & BQ2419X_REGISTER_RESET_MASK))
			return 0;
		_delay_ms(10);
	} while (retry--);

	return ret;
}

static void bq2419x_set_host_mode(void)
{
	uint8_t timer_ctrl;

	/* to disable watchdog, we need to clear the WDT bits */
	bq2419x_read(BQ2419X_REG_TIMER_CONTROL, &timer_ctrl);
	timer_ctrl &= ~BQ2419X_WDT_MASK;
	bq2419x_write(BQ2419X_REG_TIMER_CONTROL, timer_ctrl);
}


static enum pmu_charge_type bq2419x_charger_get_charge_type(pmu_charger_t *pmu_charger)
{
	uint8_t val;

	(void) pmu_charger;

	bq2419x_read(BQ2419X_REG_PWR_ON_CONFIG, &val);

	val &= BQ2419X_CHARGE_CFG_MASK;
	val >>= BQ2419X_CHARGE_CFG_SHIFT;

	/* if check if charging is disabled */
	if (!val)
		return PMU_CHARGE_TYPE_NONE;

	bq2419x_read(BQ2419X_REG_CHARGE_CURRENT, &val);

	val &= BQ2419X_FORCE_20_PCT_MASK;
	val >>= BQ2419X_FORCE_20_PCT_SHIFT;

	if (val)
		return PMU_CHARGE_TYPE_TRICKLE;
	else
		return PMU_CHARGE_TYPE_FAST;
}

static enum pmu_health bq2419x_charger_get_health(pmu_charger_t *pmu_charger)
{
	uint8_t fault;
	int8_t ret;
	struct bq2419x_pmu_charger *bq2419x_charger;

	bq2419x_charger = container_of(
		pmu_charger, struct bq2419x_pmu_charger, pmu_charger);

	if (bq2419x_charger->charger_health_valid) {
		fault = bq2419x_charger->fault;
		bq2419x_charger->charger_health_valid = false;
	} else {
		ret = bq2419x_read(BQ2419X_REG_FAULT, &fault);
		if (ret)
			return PMU_HEALTH_UNKNOWN;
	}

	/* if BOOST_FAULT then report overvoltage */
	if (fault & BQ2419X_BOOST_FAULT_MASK) {
		return PMU_HEALTH_OVERVOLTAGE;
	} else {
		fault &= BQ2419X_CHG_FAULT_MASK;
		fault >>= BQ2419X_CHG_FAULT_SHIFT;
		switch (fault) {
		case 0x0:
			/* all is well */
			return PMU_HEALTH_GOOD;
		case 0x1:
			/* input fault, could be over- or under-voltage
			 * and we can't tell which, so we report unspec */
			return PMU_HEALTH_UNSPEC_FAIL;
		case 0x2:
			/* thermal shutdown */
			return PMU_HEALTH_OVERHEAT;
		case 0x3:
			/* the charge safety timer expired */
			return PMU_HEALTH_SAFETY_TIMER_EXPIRE;
		default:
			return PMU_HEALTH_UNKNOWN;
		}
	}
}

static enum pmu_status bq2419x_battery_get_status(pmu_charger_t *pmu_charger)
{
	uint8_t fault, ss_reg;
	int8_t ret;
	struct bq2419x_pmu_charger *bq2419x_charger;

	bq2419x_charger = container_of(
		pmu_charger, struct bq2419x_pmu_charger, pmu_charger);

	if (bq2419x_charger->battery_status_valid) {
		fault = bq2419x_charger->fault;
		ss_reg = bq2419x_charger->status;
		bq2419x_charger->battery_status_valid = false;
	} else {
		ret = bq2419x_read(BQ2419X_REG_FAULT, &fault);
		if (ret)
			return ret;

		ret = bq2419x_read(BQ2419X_REG_SYSTEM_STATUS, &ss_reg);
		if (ret)
			return ret;
	}

	fault &= BQ2419X_CHG_FAULT_MASK;
	fault >>= BQ2419X_CHG_FAULT_SHIFT;

	/* the battery is discharging if either
	 * - we don't have a good power source
	 * - we have a charge fault */
	if (!(ss_reg & BQ2419X_PG_STAT_MASK) || fault) {
		return PMU_STATUS_DISCHARGING;
	} else {
		ss_reg &= BQ2419X_CHG_STAT_MASK;
		ss_reg >>= BQ2419X_CHG_STAT_SHIFT;

		switch(ss_reg) {
		case 0x0: /* not charging */
			return PMU_STATUS_NOT_CHARGING;
		case 0x1: /* pre charging */
		case 0x2: /* fast charging */
			return PMU_STATUS_CHARGING;
		case 0x3: /* charge termination done */
			return PMU_STATUS_FULL;
		}
	}
	return PMU_STATUS_NOT_CHARGING;
}

static bool bq2419x_battery_get_online(pmu_charger_t *pmu_charger)
{
	uint8_t batfet_disable;
	int8_t ret;

	(void) pmu_charger;

	ret = bq2419x_read(BQ2419X_REG_MISC_OPERATION, &batfet_disable);
	if (ret)
		return false;

	batfet_disable &= BQ2419X_BATFET_DISABLE_MASK;
	batfet_disable >>= BQ2419X_BATFET_DISABLE_SHIFT;

	return !batfet_disable;
}

static enum pmu_health bq2419x_battery_get_health(pmu_charger_t *pmu_charger)
{
	uint8_t fault;
	int8_t ret;
	struct bq2419x_pmu_charger *bq2419x_charger;

	bq2419x_charger = container_of(
		pmu_charger, struct bq2419x_pmu_charger, pmu_charger);

	if (bq2419x_charger->battery_health_valid) {
		fault = bq2419x_charger->fault;
		bq2419x_charger->battery_health_valid = false;
	} else {
		ret = bq2419x_read(BQ2419X_REG_FAULT, &fault);
		if (ret)
			return ret;
	}

	if (fault & BQ2419X_BAT_FAULT_MASK)
		return PMU_HEALTH_OVERVOLTAGE;
	else {
		fault &= BQ2419X_NTC_FAULT_MASK;
		fault >>= BQ2419X_NTC_FAULT_SHIFT;
		switch (fault) {
		case 0x0:
			/* all is well */
			return PMU_HEALTH_GOOD;
		case 0x1:
		case 0x3:
		case 0x5:
			/* either TS1 cold, TS2 cold, or both cold */
			return PMU_HEALTH_COLD;
		case 0x2:
		case 0x4:
		case 0x6:
			/* either TS1 hot, TS2 hot, or both hot */
			return PMU_HEALTH_OVERHEAT;
		default:
			return PMU_HEALTH_UNKNOWN;
		}
	}
}

static bool bq2419x_charger_get_online(pmu_charger_t *pmu_charger)
{
	uint8_t val;
	int8_t ret;

	(void) pmu_charger;

	ret = bq2419x_read(BQ2419X_REG_SYSTEM_STATUS, &val);
	if (ret)
		return ret;

	/* check the power good bit */
	return !!(BQ2419X_PG_STAT_MASK & val);
}

static inline bool bq2419x_get_irq(void)
{
	/* the CHG_IRQ line is low active */
	return !io_test_pin(CHG_IRQ);
}

uint8_t bq2419x_pmu_charger_check_events(pmu_charger_t *pmu_charger)
{
	uint8_t flags;
	int8_t ret;
	uint8_t status;
	uint8_t fault;
	uint8_t isc;
	volatile bool event;
	bq2419x_pmu_charger_t *bq2419x_charger;

	bq2419x_charger = container_of(
		pmu_charger, struct bq2419x_pmu_charger, pmu_charger);

	event = false;
	flags = PMU_CHARGER_EVENT_NONE;

	ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
		if (bq2419x_charger->event) {
			bq2419x_charger->event = false;
			event = true;
		}
	}

	if (event) {

		ret = bq2419x_read(BQ2419X_REG_SYSTEM_STATUS, &status);
		if (ret)
			return ret;

		if (status != bq2419x_charger->status) {
			if ((bq2419x_charger->status & BQ2419X_PG_STAT_MASK) &&
					!(status & BQ2419X_PG_STAT_MASK)) {
				/* we're in host mode and need to turn off HIZ
				 * when PG_STAT goes 1->0, in order to have the
				 * battery supply the juice */
				ret = bq2419x_read(BQ2419X_REG_INPUT_SRC_CTL,
						&isc);
				if (ret)
					return 0;

				isc &= ~BQ2419X_EN_HIZ_MASK;
				ret = bq2419x_write(BQ2419X_REG_INPUT_SRC_CTL,
						isc);
				if (ret)
					return 0;
			}

			if ((bq2419x_charger->status & BQ2419X_CHG_STAT_MASK)
				!= (status & BQ2419X_CHG_STAT_MASK)) {
				if ((status & BQ2419X_CHG_STAT_MASK) >> BQ2419X_CHG_STAT_SHIFT == 0x3)
					flags |= PMU_CHARGER_EVENT_CHARGE_DONE;
			}
			bq2419x_charger->status = status;
			flags |= PMU_CHARGER_EVENT_STATUS_CHANGE;
		}

		ret = bq2419x_read(BQ2419X_REG_FAULT, &fault);
		if (ret)
			return ret;

		if (fault != bq2419x_charger->fault) {
			bq2419x_charger->fault = fault;
			bq2419x_charger->battery_status_valid = true;
			bq2419x_charger->battery_health_valid = true;
			bq2419x_charger->charger_health_valid = true;
			flags |= PMU_CHARGER_EVENT_FAULT_CHANGE;
		}

		if (!bq2419x_charger->first_time)
			bq2419x_charger->first_time = true;
	}
	return flags;
}

irqreturn_t bq2419x_irq_handler(void)
{
	/* we check if the device indicates an event
	 * if so we are the source of the IRQ,
	 * so set the flag to deal with it later.
	 * Otherwise we indicate to check the other devices,
	 * by returning IRQ_NONE
	 */
	if (bq2419x_get_irq()) {
		charger.event = true;
		return IRQ_HANDLED;
	}
	(void) charger.event;
	return IRQ_NONE;
}

static const pmu_charger_ops_t bq2419x_pmu_charger_ops = {
	.set_charger_voltage	=	NULL,
	.set_charger_current	=	NULL,

	.get_charge_type	=	bq2419x_charger_get_charge_type,
	.set_charge_type	=	NULL,
	.get_charger_health	=	bq2419x_charger_get_health,
	.get_charger_online	=	bq2419x_charger_get_online,

	.get_battery_health	=	bq2419x_battery_get_health,
	.get_battery_status	=	bq2419x_battery_get_status,
	.get_battery_online	=	bq2419x_battery_get_online,

	.check_events	=	bq2419x_pmu_charger_check_events,
};

int8_t bq2419x_init(void)
{
	uint8_t id, input_src_ctrl, ir_comp;
	int8_t ret;

	/* initialize the state struct */
	memset(&charger, 0, sizeof(charger));
	charger.pmu_charger.ops = &bq2419x_pmu_charger_ops;
	charger.first_time = true;

	/* check vendor register to verify we're looking at
	 * a TI BQ2419x chip */
	ret = bq2419x_read(BQ2419X_REG_VENDOR_PART_REV, &id);
	if (ret)
		goto fail_i2c_read;

	/* set charge IRQ pin as input */
	io_input_pin(CHG_IRQ);
	io_set_pin(CHG_IRQ);

	bq2419x_reset();
	bq2419x_set_host_mode();

	/* we leave the other registers at default values
	 * BQ2419X_REG_PWR_ON_CONFIG:
	 * - minimum system voltage limit (default) 101 3.5V
	 * BQ2419X_REG_CHARGE_VOLTAGE:
	 * - fast charge current limit (default) 011000 2048mA
	 * BQ2419X_REG_PRE_TERM_CURRENT:
	 * - pre-charge current limit (default) 0001 256mA
	 * - termination current limit (default) 0001 256mA
	 * BQ2419X_REG_CHARGE_VOLTAGE:
	 * - charge voltage limit (default) 101100 4.208V
	 */

	bq2419x_read(BQ2419X_REG_INPUT_SRC_CTL, &input_src_ctrl);

	/* set a 3A limit */
	input_src_ctrl |= 0x7;

	ret = bq2419x_write(BQ2419X_REG_INPUT_SRC_CTL, input_src_ctrl);
	if (ret)
		return ret;

	/* compensate for 20m r_sense */
	ret = bq2419x_read(BQ2419X_REG_THERMAL_REG_CTRL, &ir_comp);
	if (ret)
		return ret;
	ir_comp &= ~BQ2419X_BAT_COMP_MASK;
	ir_comp |= (0x02 << BQ2419X_BAT_COMP_SHIFT);

	/* set thermal regulation to 120 C */
	ir_comp &= ~BQ2419X_TREG_MASK;
	ir_comp |= (0x03 << BQ2419X_TREG_SHIFT);

	ret = bq2419x_write(BQ2419X_REG_THERMAL_REG_CTRL, ir_comp);
	if (ret)
		return ret;

	pmu_register_charger(&charger.pmu_charger);

	return 0;

fail_i2c_read:
	return 1;
}