/* ----------------------------------------------------------------------------
 *         SAM Software Package License
 * ----------------------------------------------------------------------------
 * Copyright (c) 2015, Atmel Corporation
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * - Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the disclaimer below.
 *
 * Atmel's name may not be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * DISCLAIMER: THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE
 * DISCLAIMED. IN NO EVENT SHALL ATMEL BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * ----------------------------------------------------------------------------
 */

/*----------------------------------------------------------------------------
 *        Headers
 *----------------------------------------------------------------------------*/

#include "board.h"
#include "chip.h"

#include "peripherals/pio.h"
#include "peripherals/pmc.h"
#include "peripherals/flexcom.h"
#include "peripherals/twi.h"
#include "peripherals/twid.h"

#include "power/act8945a.h"

#include "trace.h"

#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <assert.h>

/*----------------------------------------------------------------------------
 *        Types
 *----------------------------------------------------------------------------*/

#define STATE_VSEL 1 // HW config DDR3L

// SYS @0x00
union _sys0 {
	struct {
		uint8_t
			trst:       1,  // Reset time out 0->260 1->65ms
			nsysmode:   1,  // response of SYSLEV voltage detector, 1->int 0>shutdown
			nsyslevmsk: 1,  // 1->unmask int
			nsysstat:   1,  // 1 if vsys < syslev voltage threshold
			syslev:     4;  // defines SYSLEV voltage threshold
	} bits;
	uint8_t u8;
};

// SYS @0x01
union _sys1 {
	struct {
		uint8_t
			scratch: 4, // user area to store system status information
			ruf4:    1,
			mstroff: 1, // Set bit to 1 to turn off all regulators
			ruf67:   2;
	} bits;
	uint8_t u8;
};

// REG1 @0x20, REG2 @0x30, REG3 @0x40
union _vset1 {
	struct {
		uint8_t
			vset1:  6,
			ruf_67: 2;
	} bits;
	uint8_t u8;
};

// REG1 @0x21, REG2 @0x31, REG3 @0x41
union _vset2 {
	struct {
		uint8_t
			vset2:  6,
			ruf_67: 2;
	} bits;
	uint8_t u8;
};

// REG1 @0x22, REG2 @0x32, REG3 @0x42
union _ctrl1 {
	struct {
		uint8_t
			ok:      1,
			nfltmsk: 1,
			delay:   3,
			mode:    1,
			phase:   1,
			on:      1;
	} bits;
	uint8_t u8;
};

// REG4 @0x51, REG5 @0x55, REG6 @0x61, REG7 @0x65
union _ctrl2 {
	struct {
		uint8_t
			ok:      1,
			nfltmsk: 1,
			delay:   3,
			lowiq:   1,
			dis:     1,
			on:      1;
	} bits;
	uint8_t u8;
};

union _apch_70 {
	struct {
		uint8_t
			ruf_0:  1,
			ruf_1:  1,
			ruf_2:  1,
			ruf_3:  1,
			ruf_45: 2,
			ruf_67: 2;
	} bits;
	uint8_t u8;
};

union _apch_71 {
	struct {
		uint8_t
			ovpset:  2,
			pretimo: 2,
			tottimo: 2,
			ruf6:    1,
			suschg:  1;
	} bits;
	uint8_t u8;
};

union _apch_78 {
	struct {
		uint8_t
			chgdat:   1,
			indat:    1,
			tempdat:  1,
			timrdat:  1,
			chgstat:  1,
			instat:   1,
			tempstat: 1,
			timrstat: 1;
	} bits;
	uint8_t u8;
};

union _apch_79 {
	struct {
		uint8_t
			chgeocout: 1,
			indis:     1,
			tempout:   1,
			timrpre:   1,
			chgeocin:  1,
			incon:     1,
			tempin:    1,
			timrtot:   1;
	} bits;
	uint8_t u8;
};

union _apch_7a {
	struct {
		uint8_t
			ruf0:     1,
			acinstat: 1,
			ruf32:    2,
			cstate:   2,
			ruf76:    2;
	} bits;
	uint8_t u8;
};

/*----------------------------------------------------------------------------
 *        Constants
 *----------------------------------------------------------------------------*/

/// Slave address
#define ACT8945A_TWI_ADDRESS 0x5B

#define NUM_REGULATORS 7

#define IADDR_SYS0   0x00
#define IADDR_SYS1   0x01
#define IADDR_REG1      0x20
#define IADDR_REG2      0x30
#define IADDR_REG3      0x40
#define IADDR_REG4      0x50
#define IADDR_REG5      0x54
#define IADDR_REG6      0x60
#define IADDR_REG7      0x64
#define IADDR_APCH_70   0x70
#define IADDR_APCH_71   0x71
#define IADDR_APCH_78   0x78
#define IADDR_APCH_79   0x79
#define IADDR_APCH_7A   0x7a

static const uint8_t _iaddr_reg[] = {
	IADDR_REG1, IADDR_REG2, IADDR_REG3, IADDR_REG4,
	IADDR_REG5, IADDR_REG6, IADDR_REG7,
};

static const char* _charging_states[4] = {
	"Suspend/Disable/Fault",
	"End of charge",
	"Fast charge/Top-off",
	"Precondition",
};

struct _reg
{
	const char* name;
	uint8_t iaddr;
};

static const struct _reg _regs[] = {
	{ "SYS0   ", IADDR_SYS0 },
	{ "SYS1   ", IADDR_SYS1 },
	{ "REG1_20", IADDR_REG1 },
	{ "REG1_21", IADDR_REG1 + 1 },
	{ "REG1_22", IADDR_REG1 + 2 },
	{ "REG2_30", IADDR_REG2 },
	{ "REG2_31", IADDR_REG2 + 1 },
	{ "REG2_32", IADDR_REG2 + 2 },
	{ "REG3_40", IADDR_REG3 },
	{ "REG3_41", IADDR_REG3 + 1 },
	{ "REG3_42", IADDR_REG3 + 2 },
	{ "REG4_50", IADDR_REG4 },
	{ "REG4_51", IADDR_REG4 + 1 },
	{ "REG5_54", IADDR_REG5 },
	{ "REG5_55", IADDR_REG5 + 1 },
	{ "REG6_60", IADDR_REG6 },
	{ "REG6_61", IADDR_REG6 + 1 },
	{ "REG7_64", IADDR_REG7 },
	{ "REG7_65", IADDR_REG7 + 1 },
	{ "APCH_70", IADDR_APCH_70 },
	{ "APCH_71", IADDR_APCH_71 },
	{ "APCH_78", IADDR_APCH_78 },
	{ "APCH_79", IADDR_APCH_79 },
	{ "APCH_7A", IADDR_APCH_7A },
};

static const char *_ovp_setting[4] = {
	"6.6V", "7.0V", "7.5V", "8.0V",
} ;

/*------------------------------------------------------------------------------
 *         Local functions
 *----------------------------------------------------------------------------*/

static bool _act8945a_read_reg(struct _act8945a* act8945a, uint32_t iaddr,
		uint8_t* value)
{
	uint32_t status;
	struct _buffer in = {
		.data = value,
		.size = 1
	};
	act8945a->twid->slave_addr = ACT8945A_TWI_ADDRESS;
	act8945a->twid->iaddr = iaddr;
	act8945a->twid->isize = 1;
	status = twid_transfert(act8945a->twid, &in, 0,
			twid_finish_transfert_callback, 0);
	if (status != TWID_SUCCESS)
		return false;
	twid_wait_transfert(act8945a->twid);
	return true;
}

static bool _act8945a_write_reg(struct _act8945a* act8945a, uint32_t iaddr,
		uint8_t value)
{
	uint32_t status;
	struct _buffer out = {
		.data = (uint8_t*)&value,
		.size = 1
	};
	act8945a->twid->slave_addr = ACT8945A_TWI_ADDRESS;
	act8945a->twid->iaddr = iaddr;
	act8945a->twid->isize = 1;
	status = twid_transfert(act8945a->twid, 0, &out,
			twid_finish_transfert_callback, 0);
	if (status != TWID_SUCCESS)
		return false;
	twid_wait_transfert(act8945a->twid);
	return true;
}

static bool _act8945a_update_cached_registers(struct _act8945a *act8945a)
{
	return _act8945a_read_reg(act8945a, IADDR_SYS0, &act8945a->sys0) &&
		_act8945a_read_reg(act8945a, IADDR_APCH_78, &act8945a->apch78) &&
		_act8945a_read_reg(act8945a, IADDR_APCH_79, &act8945a->apch79) &&
		_act8945a_read_reg(act8945a, IADDR_APCH_7A, &act8945a->apch7a);
}

static void _act8945a_irq_handler(uint32_t group, uint32_t status, void* user_arg)
{
	struct _act8945a *act8945a = (struct _act8945a*)user_arg;

	if (status & act8945a->desc.pin_irq.mask) {
		union _sys0 sys0;
		union _apch_78 apch78;
		union _apch_79 apch79;
		union _apch_7a apch7a;

		// save previous values
		sys0.u8 = act8945a->sys0;
		apch78.u8 = act8945a->apch78;
		apch79.u8 = act8945a->apch79;
		apch7a.u8 = act8945a->apch7a;

		// update values
		_act8945a_update_cached_registers(act8945a);

		// show changes
		if (sys0.u8 != act8945a->sys0) {
			trace_debug("PMIC IRQ: SYST0 changed\r\n");
		}
		if (apch78.u8 != act8945a->apch78) {
			if (apch78.bits.chgdat == 0x01)
				trace_debug("PMIC IRQ: charger state machine, END-OF-CHARGE state\r\n");
		}
		if (apch79.u8 != act8945a->apch79) {
			printf("PMIC IRQ: APCH79 changed\r\n");
		}
		if (apch7a.u8 != act8945a->apch7a) {
			trace_debug("PMIC IRQ: %s\r\n", _charging_states[apch7a.bits.cstate]);
		}
	}
}

static void _act8945a_lbo_handler(uint32_t group, uint32_t status, void* user_arg)
{
	struct _act8945a *act8945a = (struct _act8945a*)user_arg;

	if (status & act8945a->desc.pin_lbo.mask) {
		trace_debug("PMIC LBO: Low Battery Output\r\n");
		if(	act8945a->lbo_count++ >= 10)
			pio_disable_it(&act8945a->desc.pin_lbo);
	}
}

// Enable interrupt on nIRQ pin to MPU
static void _act8945a_enable_interrupt_handlers(struct _act8945a *act8945a)
{
	/* Configure PMIC line interrupts. */
	pio_configure_it(&act8945a->desc.pin_irq);
	pio_add_handler_to_group(act8945a->desc.pin_irq.group,
				 act8945a->desc.pin_irq.mask,
				 &_act8945a_irq_handler,
				 act8945a);
	pio_enable_it(&act8945a->desc.pin_irq);

	/* Configure LBO line interrupts. */
	act8945a->lbo_count = 0;
	pio_configure_it(&act8945a->desc.pin_lbo);
	pio_add_handler_to_group(act8945a->desc.pin_lbo.group,
				 act8945a->desc.pin_lbo.mask,
				 &_act8945a_lbo_handler,
				 act8945a);
	pio_enable_it(&act8945a->desc.pin_lbo);
}

static uint16_t _act8945a_convert_voltage_setting(uint8_t setting)
{
	uint8_t mul20, mul53;

	mul20 = (setting & 0x07) >> 0;
	mul53 = (setting & 0x38) >> 3;

	if (setting <= 0x17)
		return (uint16_t)(1000 * (0.6 + (0.2 * mul53) + (0.025 * mul20)));
	else if (setting <= 0x2F)
		return (uint16_t)(1000 * (1.2 + (0.4 * (mul53 - 3)) + (0.050 * mul20)));
	else
		return (uint16_t)(1000 * (2.4 + (0.8 * (mul53 - 6)) + (0.1 * mul20)));
}

/*------------------------------------------------------------------------------
 *         Exported functions
 *----------------------------------------------------------------------------*/

bool act8945a_configure(struct _act8945a *act8945a, struct _twi_desc *twid)
{
	uint8_t data = 0;

	act8945a->twid = twid;
	twid_configure(twid);

	pio_configure(&act8945a->desc.pin_chglev, 1);
	pio_configure(&act8945a->desc.pin_irq, 1);
	pio_configure(&act8945a->desc.pin_lbo, 1);

	if (!_act8945a_read_reg(act8945a, IADDR_SYS0, &data))
		return false;

	/* Set Charge Level */
	act8945a_set_charge_level(act8945a, ACT8945A_CHARGE_LEVEL_450MA);

	/* Set level interrupt */
	act8945a_disable_all_apch_interrupts(act8945a);
	act8945a_configure_apch_interrupt(act8945a, CHARGE_STATE_INTO_EOC_STATE, true);
	act8945a_configure_apch_interrupt(act8945a, CHARGE_STATE_OUT_EOC_STATE, true);
	act8945a_configure_apch_interrupt(act8945a, PRECHARGE_TIME_OUT, true);
	act8945a_configure_apch_interrupt(act8945a, TOTAL_CHARGE_TIME_OUT, true);
	act8945a_enable_system_voltage_level_interrupt(act8945a, true);

	/* Update cached register values */
	if (!_act8945a_update_cached_registers(act8945a))
		return false;

	act8945a_enable_regulator_fault_interrupt(act8945a, 1, true);
	act8945a_enable_regulator_fault_interrupt(act8945a, 5, true);

	/* Enable interrupts */
	_act8945a_enable_interrupt_handlers(act8945a);

	return true;
}

// Charge Current Selection Input
// In USB-Mode: CHGLEV = 1 -> I charge 450mA
//              CHGLEV = 0 -> I charge 100mA
void act8945a_set_charge_level(struct _act8945a *act8945a,
		enum _act8945a_charge_level level)
{
	switch (level) {
	case ACT8945A_CHARGE_LEVEL_100MA:
		pio_clear(&act8945a->desc.pin_chglev);
		trace_debug("Charge Level: 100mA\r\n");
		break;
	case ACT8945A_CHARGE_LEVEL_450MA:
		pio_set(&act8945a->desc.pin_chglev);
		trace_debug("Charge Level: 450mA\r\n");
		break;
	default:
		trace_warning("Invalid charge level requested: %d\r\n",
				(int)level)
		break;
	}
}

// Set or Clear an APCH interrupt
// Set bit to 1 enable interrupt,
// Clear bit to 0 to disable interrupt
bool act8945a_configure_apch_interrupt(struct _act8945a *act8945a,
		enum _act8945a_interrupt interrupt, bool enable)
{
	bool status;
	union _apch_78 apch78;
	union _apch_79 apch79;

	if (!_act8945a_read_reg(act8945a, IADDR_APCH_78, &apch78.u8) ||
		!_act8945a_read_reg(act8945a, IADDR_APCH_79, &apch79.u8))
		return false;

	switch (interrupt)
	{
	// Interrupt generated any time the input supply is disconnected when
	// INSTAT[] bit is set to 1 and the INDIS[] bit is set to 1.
	case INPUT_VOLTAGE_OUT_VALID_RANGE: // Interrupt
		apch78.bits.instat = enable ? 1 : 0;
		apch79.bits.indis = enable ? 1 : 0;
		break;

	// Interrupt generated any time the input supply is connected when
	// INSTAT[] bit is set to 1 and the INCON[] bit is set to 1.
	case INPUT_VOLTAGE_INTO_VALID_RANGE:
		apch78.bits.instat = enable ? 1 : 0;
		apch79.bits.incon = enable ? 1 : 0;
		break;

	// Interrupts based upon the status of the battery temperature.
	// Set the TEMPOUT[] bit to 1 and TEMPSTAT[] bit to 1 to generate
	// an interrupt when battery temperature goes out of the valid
	// temperature range.
	case BATTERY_TEMPERATURE_OUT_RANGE:
		apch78.bits.tempstat = enable ? 1 : 0;
		apch79.bits.tempout = enable ? 1 : 0;
		break;

	// Interrupts based upon the status of the battery temperature.
	// Set the TEMPIN[] bit to 1 and TEMPSTAT[] bit to 1 to generate
	// an interrupt when battery temperature returns to the valid range.
	case BATTERY_TEMPERATURE_INTO_RANGE:
		apch78.bits.tempstat = enable ? 1 : 0;
		apch79.bits.tempin = enable ? 1 : 0;
		break;

	// Interrupt when the charger state machine goes into the
	// END-OF-CHARGE (EOC). Set CHGEOCIN[] bit to 1 and CHGSTAT[] bit
	// to 1 to generate an interrupt when the charger state machine goes
	// into the END-OF-CHARGE (EOC)state.
	case CHARGE_STATE_INTO_EOC_STATE:
		apch78.bits.chgstat = enable ? 1 : 0;
		apch79.bits.chgeocin = enable ? 1 : 0;
		break;

	// Interrupt when the charger state machine exit the
	// END-OF-CHARGE (EOC). Set CHGEOCOUT[] bit to 1 and CHGSTAT[] bit
	// to 1 to generate an interrupt when the charger state machine exits
	// the EOC state.
	case CHARGE_STATE_OUT_EOC_STATE:
		apch78.bits.chgstat = enable ? 1 : 0;
		apch79.bits.chgeocout = enable ? 1 : 0;
		break;

	// Interrupts based upon the status of the charge timers.
	// Set the TIMRPRE[] bit to 1 and TIMRSTAT[] bit to 1 to generate an
	// interrupt when the Precondition Timer expires.
	case PRECHARGE_TIME_OUT:
		apch78.bits.timrstat = enable ? 1 : 0;
		apch79.bits.timrpre = enable ? 1 : 0;
		break;

	// Set the TIMRTOT[] bit to 1 and TIMRSTAT[] bit to 1 to generate an
	// interrupt when the Total-Charge Timer expires.
	case TOTAL_CHARGE_TIME_OUT:
		apch78.bits.timrstat = enable ? 1 : 0;
		apch79.bits.timrtot = enable ? 1 : 0;
		break;

	default:
		trace_warning("Unknown interrupt %d\r\n", interrupt);
		return false;
	}

	// Write configuration to registers
	status = _act8945a_write_reg(act8945a, IADDR_APCH_78, apch78.u8);
	status |= _act8945a_write_reg(act8945a, IADDR_APCH_79, apch79.u8);
	return status;

}

// Disable all interrupt from APCH
bool act8945a_disable_all_apch_interrupts(struct _act8945a *act8945a)
{
	return act8945a_configure_apch_interrupt(act8945a, CHARGE_STATE_OUT_EOC_STATE, false) &&
		act8945a_configure_apch_interrupt(act8945a, INPUT_VOLTAGE_OUT_VALID_RANGE, false) &&
		act8945a_configure_apch_interrupt(act8945a, BATTERY_TEMPERATURE_OUT_RANGE, false) &&
		act8945a_configure_apch_interrupt(act8945a, PRECHARGE_TIME_OUT, false) &&
		act8945a_configure_apch_interrupt(act8945a, CHARGE_STATE_INTO_EOC_STATE, false) &&
		act8945a_configure_apch_interrupt(act8945a, INPUT_VOLTAGE_INTO_VALID_RANGE, false) &&
		act8945a_configure_apch_interrupt(act8945a, BATTERY_TEMPERATURE_INTO_RANGE, false) &&
		act8945a_configure_apch_interrupt(act8945a, TOTAL_CHARGE_TIME_OUT, false);
}

extern bool act8945a_set_system_voltage_detect_threshold(struct _act8945a *act8945a,
		uint16_t threshold)
{
	union _sys0 sys0;

	if (threshold < 2300 || threshold > 3800)
		return false;
	if (!_act8945a_read_reg(act8945a, IADDR_SYS0, &sys0.u8))
		return false;
	sys0.bits.syslev = (threshold - 2300) / 100;
	return _act8945a_write_reg(act8945a, IADDR_SYS0, sys0.u8);
}

bool act8945a_enable_system_voltage_level_interrupt(struct _act8945a *act8945a,
		bool enable)
{
	union _sys0 sys0;

	if (!_act8945a_read_reg(act8945a, IADDR_SYS0, &sys0.u8))
		return false;
	sys0.bits.nsyslevmsk = enable ? 1 : 0;

	sys0.bits.nsysmode = 1; //*************

	return _act8945a_write_reg(act8945a, IADDR_SYS0, sys0.u8);
}

bool act8945a_set_regulator_voltage(struct _act8945a *act8945a,
		uint8_t reg, uint16_t vout)
{
	// minimum is 600mV
	if (vout < 600) {
		trace_warning("Cannot set regulator %d voltage to %dmV, using 600mV instead\r\n", reg, vout);
		vout = 600;
	}
	// maximum is 3900mV
	if (vout > 3900) {
		trace_warning("Cannot set regulator %d voltage to %dmV, using 3900mV instead\r\n", reg, vout);
		vout = 3900;
	}

	// can only set voltage for regulators 4 to 7
	if (reg < 4 || reg > 7) {
		trace_error("Cannot change voltage of regulator %d\r\n", reg);
		return false;
	};

	uint8_t value = 0;
	if (vout < 1200) {
		value = (vout - 600) / 25;
	} else if (vout < 2400) {
		value = 0x18 + (vout - 1200) / 50;
	} else if (vout <= 3900) {
		value = 0x30 + (vout - 2400) / 100;
	}

	uint32_t iaddr = _iaddr_reg[reg - 1];
	return _act8945a_write_reg(act8945a, iaddr, value & 0x3f);
}

bool act8945a_enable_regulator(struct _act8945a *act8945a,
		uint8_t reg, bool enable)
{
	if (reg >= 1 && reg <= 3) {
		union _ctrl1 ctrl1;
		uint32_t iaddr = _iaddr_reg[reg - 1] + 1;

		if (!_act8945a_read_reg(act8945a, iaddr, &ctrl1.u8))
			return false;

		ctrl1.bits.on = enable ? 1 : 0;

		if (!_act8945a_write_reg(act8945a, iaddr, ctrl1.u8))
			return false;
	} else if (reg >= 4 && reg <= 7) {
		union _ctrl2 ctrl2;
		uint32_t iaddr = _iaddr_reg[reg - 1] + 1;

		if (!_act8945a_read_reg(act8945a, iaddr, &ctrl2.u8))
			return false;

		ctrl2.bits.on = enable ? 1 : 0;

		if (!_act8945a_write_reg(act8945a, iaddr, ctrl2.u8))
			return false;
	} else {
		trace_error("Invalid regulator number %d\r\n", reg);
		return false;
	}

	return true;
}

bool act8945a_enable_regulator_fault_interrupt(struct _act8945a *act8945a,
		uint8_t reg, bool enable)
{
	if (reg >= 1 && reg <= 3) {
		union _ctrl1 ctrl1;
		uint8_t iaddr = (_iaddr_reg[reg-1]) + 2;


		if (!_act8945a_read_reg(act8945a, iaddr, &ctrl1.u8))
			return false;

		ctrl1.bits.nfltmsk = enable ? 1 : 0;

		if (!_act8945a_write_reg(act8945a, iaddr, ctrl1.u8))
			return false;
	} else if (reg >= 4 && reg <= 7) {
		union _ctrl2 ctrl2;
		uint8_t iaddr = (_iaddr_reg[reg-1]) + 1;

		if (!_act8945a_read_reg(act8945a, iaddr, &ctrl2.u8))
			return false;

		ctrl2.bits.nfltmsk = enable ? 1 : 0;

		if (!_act8945a_write_reg(act8945a, iaddr, ctrl2.u8))
			return false;
	} else {
		trace_error("Invalid regulator number %d\r\n", reg);
		return false;
	}

	return true;
}

extern bool act8945a_get_lbo_pin_state(struct _act8945a *act8945a)
{
	return pio_get(&act8945a->desc.pin_lbo) ? true : false;
}



extern void act8945a_display_voltage_settings(struct _act8945a *act8945a)
{
	int reg;

	trace_info_wp("\r\n-- ACT8945A - Voltage Settings & State --\r\n");

	for (reg = 0; reg < NUM_REGULATORS; reg++)
	{
		uint8_t iadd_reg, setting, ctrl;
		uint16_t u;

		/* Warning VSEL state */
		iadd_reg = _iaddr_reg[reg];
		if( (iadd_reg < IADDR_REG4) && (STATE_VSEL == 1) )
			iadd_reg ++;

		if (!_act8945a_read_reg(act8945a, iadd_reg, &setting))
			return;

		if (!_act8945a_read_reg(act8945a, iadd_reg + 1, &ctrl))
			return;

		u = _act8945a_convert_voltage_setting(setting);
		trace_info_wp(" - VOUT_%d (0x%02x) = %dmV", reg + 1, ctrl, u);
		if (reg <= 3) {
			union _ctrl1 *ctrl1 = (union _ctrl1*)&ctrl;
			trace_info_wp(" %s", ctrl1->bits.on ? "on" : "off");
			trace_info_wp(" %s", ctrl1->bits.phase ? "180" : "osc");
			trace_info_wp(" %s", ctrl1->bits.mode ? "pwm" : "pow-saving");
			trace_info_wp(" delay:0x%02x", ctrl1->bits.delay);
			trace_info_wp(" %s", ctrl1->bits.nfltmsk ? "en" : "dis");
			trace_info_wp(" %s", ctrl1->bits.ok ? "OK" : "<tresh");
		} else {
			union _ctrl2 *ctrl2 = (union _ctrl2*)&ctrl;
			trace_info_wp(" %s", ctrl2->bits.on ? "on": "off");
			trace_info_wp(" %s", ctrl2->bits.dis ? "off" : "on");
			trace_info_wp(" %s", ctrl2->bits.lowiq ? "normal" : "low-power");
			trace_info_wp(" delay:0x%02x", ctrl2->bits.delay);
			trace_info_wp(" %s", ctrl2->bits.nfltmsk ? "en" : "dis");
			trace_info_wp(" %s", ctrl2->bits.ok ? "OK" : "<tresh");
		}
		trace_info_wp("\r\n");
	}

	union _sys0 sys0;
	if (!_act8945a_read_reg(act8945a, IADDR_SYS0, &sys0.u8))
		return;
	trace_info_wp(" - SYSLEV Failing Treshold (0x%02x) = %dmV\r\n", sys0.u8,
			2300 + sys0.bits.syslev * 100);
}

void act8945a_dump_registers(struct _act8945a *act8945a)
{
	uint8_t reg, data, mask, i;


	trace_info_wp("\r\n-- ACT8945A - Registers Dump --\r\n");
	for (reg = 0; reg < ARRAY_SIZE(_regs); reg++) {
		if (!_act8945a_read_reg(act8945a, _regs[reg].iaddr, &data))
			return;
		trace_info_wp(" - %s: 0x%02X  b:", _regs[reg].name, data);
		mask = 0x80;
		for (i=0; i<8; i++, mask>>=1) {
			printf ("%x", (data&mask) ? 1 : 0);
		}
		trace_info_wp("\r\n");
	}
	trace_info_wp("\r\n");
}

void act8945a_display_apch_registers(struct _act8945a *act8945a)
{
	union _apch_71 apch71;
	union _apch_78 apch78;
	union _apch_79 apch79;
	union _apch_7a apch7a;

	trace_info_wp("\r\n-- ACT8945A - APCH Registers --\r\n");

//	if (!_act8945a_read_reg(act8945a, IADDR_APCH_70, &data))
//		return;
//	trace_info_wp(" - APCH @0x70: 0x%02x (reserved)\r\n", data);

	if (!_act8945a_read_reg(act8945a, IADDR_APCH_71, &apch71.u8))
		return;
	trace_info_wp(" - APCH @0x71: 0x%02x\r\n", apch71.u8);
	trace_info_wp("     Charge Suspend Control Input:          %x\r\n",
			apch71.bits.suschg);
	trace_info_wp("     Total Charge Time-out Selection:       %x\r\n",
			apch71.bits.tottimo);
	trace_info_wp("     Precondition Charge Time-out Sel:      %x\r\n",
			apch71.bits.pretimo);
	trace_info_wp("     Input Over-Volt Prot.Threshold Sel:    %x (%s)\r\n",
			apch71.bits.ovpset, _ovp_setting[apch71.bits.ovpset]);

	if (!_act8945a_read_reg(act8945a, IADDR_APCH_78, &apch78.u8))
		return;
	trace_info_wp(" - APCH @0x78: 0x%02x\r\n", apch78.u8);
	trace_info_wp("     Charge Time-out Interrupt Status:      %x\r\n",
			apch78.bits.timrstat);
	trace_info_wp("     Battery Temperature Interrupt Status:  %x\r\n",
			apch78.bits.tempstat);
	trace_info_wp("     Input Voltage Interrupt Status:        %x\r\n",
			apch78.bits.instat);
	trace_info_wp("     Charge State Interrupt Status:         %x\r\n",
			apch78.bits.chgstat);
	trace_info_wp("     Charge Timer Status                    %x\r\n",
			apch78.bits.timrdat);
	trace_info_wp("     Temperature Status                     %x\r\n",
			apch78.bits.tempdat);
	trace_info_wp("     Input Voltage Status                   %x\r\n",
			apch78.bits.indat);
	trace_info_wp("     Charge State Machine Status            %x\r\n",
			apch78.bits.chgdat);

	if (!_act8945a_read_reg(act8945a, IADDR_APCH_79, &apch79.u8))
		return;
	trace_info_wp(" - APCH @0x79: 0x%02x\r\n", apch79.u8);
	trace_info_wp("     Total Charge Time-out Int Control:     %x\r\n",
			apch79.bits.timrtot);
	trace_info_wp("     Batt.Temp.Int.Ctrl into valid range:   %x\r\n",
			apch79.bits.tempin);
	trace_info_wp("     Inp.Voltage Int.Ctrl into valid range: %x\r\n",
			apch79.bits.incon);
	trace_info_wp("     Charge State Int Ctrl into EOC state:  %x\r\n",
			apch79.bits.chgeocin);
	trace_info_wp("     Precharge Time-out Int Ctrl:           %x\r\n",
			apch79.bits.timrpre);
	trace_info_wp("     Batt.Temp.Int.Ctrl. out valid range:   %x\r\n",
			apch79.bits.tempout);
	trace_info_wp("     Inp.Voltage Int.Ctrl. out valid range: %x\r\n",
			apch79.bits.indis);
	trace_info_wp("     Charge State Int.Ctrl. out EOC state:  %x\r\n",
			apch79.bits.chgeocout);

	if (!_act8945a_read_reg(act8945a, IADDR_APCH_7A, &apch7a.u8))
		return;
	trace_info_wp(" - APCH @0x7a: 0x%02x\r\n", apch7a.u8);
	trace_info_wp("     Charge State:                          %x (%s)\r\n",
			apch7a.bits.cstate, _charging_states[apch7a.bits.cstate]);
	trace_info_wp("     ACIN Status:                           %x\r\n",
			apch7a.bits.acinstat);
}

void act8945a_display_system_registers(struct _act8945a *act8945a)
{
	union _sys0 sys0;
	union _sys1 sys1;

	trace_info_wp("\r\n-- ACT8945A - System Registers --\r\n");

	if (!_act8945a_read_reg(act8945a, IADDR_SYS0, &sys0.u8))
		return;
	trace_info_wp(" - SYS0 @0x00: 0x%02x\r\n", sys0.u8);
	trace_info_wp("     Reset Timer Setting:                   %s\r\n",
			sys0.bits.trst ? "64ms" : "260ms");
	trace_info_wp("     SYSLEV Mode Select:                    %s\r\n",
			sys0.bits.nsysmode ?"int" : "shutdown");
	trace_info_wp("     System Voltage Level Int.Mask:         %s\r\n",
			sys0.bits.nsyslevmsk ?"int" : "noint");
	trace_info_wp("     System Voltage Status:                 %s\r\n",
			sys0.bits.nsysstat ? "vsys<syslev" : "vsys>syslev");
	trace_info_wp("     SYSLEV Failing Treshold value:         %dmV\r\n",
			2300 + sys0.bits.syslev * 100);

	if (!_act8945a_read_reg(act8945a, IADDR_SYS1, &sys1.u8))
		return;
	trace_info_wp(" - SYS1 @0x01: 0x%02x\r\n", sys1.u8);
	trace_info_wp("     Master Off Ctrl, All regul:            %s\r\n",
			sys1.bits.mstroff ? "off" : "on");
	trace_info_wp("     Scratchpad Bits, free user:            %x\r\n",
			sys1.bits.scratch);
}

void act8945a_display_charge_state(struct _act8945a *act8945a)
{
	union _apch_7a apch7a;
	if (!_act8945a_read_reg(act8945a, IADDR_APCH_7A, &apch7a.u8)) return;

	if (act8945a->apch7a != apch7a.u8) {
		trace_info_wp(" Charge State: %x (%s)\r\n", apch7a.bits.cstate, _charging_states[apch7a.bits.cstate]);
		act8945a->apch7a = apch7a.u8;
	}
}