view src/cs/drivers/drv_app/fchg/fchg_process.c @ 281:a75eefbf8be4

Phone boot with PWON: weed out short button presses Every standard end user phone has a design provision, most naturally implemented in firmware, whereby the PWON button effects a boot only if it is held down long enough - short presses of this PWON button are detected, assumed to be spurious and cause the fw to power back off instead of proceeding with boot. The present change introduces this standard function in FreeCalypso.
author Mychaela Falconia <falcon@freecalypso.org>
date Fri, 24 Sep 2021 02:03:08 +0000
parents 7420959e02ec
children
line wrap: on
line source

/*
 * In this module we are going to implement the main process functions
 * for FCHG.
 */

#include "fchg/fchg_env.h"
#include "fchg/fchg_func_i.h"
#include "rv/rv_general.h"
#include "rvf/rvf_api.h"
#include "rvm/rvm_use_id_list.h"
#include "abb/abb.h"
#include "fc-target.h"
#include <string.h>
#include <stdio.h>

extern UINT16 madc_vbat_2_physical(UINT16 adc_val);
extern UINT16 madc_vbat_inverse(UINT16 mv);

#if defined(CONFIG_TARGET_C155) || defined(CONFIG_TARGET_J100)
#define	LEDC	0x20
#else
#define	LEDC	0
#endif

#define	CHARGER_PLUG_DEBOUNCE_MS	200

/*
 * The following global variable captures the initial battery voltage
 * at the time of system boot - it is intended to be used for various
 * system start-up decisions.
 */
UINT16 fchg_first_vbat;

void pwr_init_discharge(void)
{
	pwr_ctrl->curr_disch_thresh = 0;
}

static void handle_discharge(void)
{
	UINT16 i;
	char trace[64];

	/* first we need to find the current threshold we are at */
	i = pwr_ctrl->curr_disch_thresh;
	/* are we at the bottom? */
	if ((i + 1) == pwr_ctrl->nb_percent_thresh)
		return;
	/* are we crossing our current threshold? */
	if (pwr_ctrl->batt_mv >= pwr_ctrl->batt.percent_thresh[i].bat_voltage)
		return;
	/* yes, we crossed it - see if we fell even further down */
	while (i < pwr_ctrl->nb_percent_thresh-1 &&
	       pwr_ctrl->batt_mv < pwr_ctrl->batt.percent_thresh[i].bat_voltage)
		i++;
	/* the last one was it */
	pwr_ctrl->curr_disch_thresh = i;
	sprintf(trace, "Battery fell through %u%% mark",
		pwr_ctrl->batt.percent_thresh[i-1].remain_capa);
	rvf_send_trace(trace, strlen(trace), NULL_PARAM,
			RV_TRACE_LEVEL_WARNING, FCHG_USE_ID);
	if (pwr_ctrl->event_handler)
		pwr_ctrl->event_handler(FCHG_EVENT_DISCHARGE);
}

static void capture_first_vbat(void)
{
	char trace[64];

	fchg_first_vbat = pwr_ctrl->batt_mv;
	sprintf(trace, "System boot: Vbat=%u", pwr_ctrl->batt_mv);
	rvf_send_trace(trace, strlen(trace), NULL_PARAM,
			RV_TRACE_LEVEL_DEBUG_HIGH, FCHG_USE_ID);
}

static void start_i2v_cal(void)
{
	UINT16 bciconf;

	rvf_send_trace("Calibrating i2v offset", 22, NULL_PARAM,
			RV_TRACE_LEVEL_DEBUG_HIGH, FCHG_USE_ID);
	pwr_ctrl->state = FCHG_STATE_I2V_CAL_2;
	bciconf = ABB_Read_Register_on_page(PAGE1, BCICONF);
	bciconf &= 0x3E0;
	bciconf |= pwr_ctrl->config.bciconf;
	ABB_Write_Register_on_page(PAGE1, BCICONF, bciconf);
	/*
	 * Set the CHDISPA bit and start the zero calibration routine
	 * of the I to V converter
	 */
	ABB_Write_Register_on_page(PAGE0, BCICTL2, 0x0010);
	ABB_Write_Register_on_page(PAGE0, BCICTL2, 0x0019 | LEDC);
}

static void start_ci_charging(void)
{
	rvf_send_trace("Start CI charging", 17, NULL_PARAM,
			RV_TRACE_LEVEL_DEBUG_HIGH, FCHG_USE_ID);
	pwr_ctrl->state = FCHG_STATE_CI_CHARGING;
	/* Select constant current charging. The charger is disabled */
	ABB_Write_Register_on_page(PAGE0, BCICTL2, 0x0002);
	/* Program the DAC with the constant current value */
	ABB_Write_Register_on_page(PAGE0, CHGREG,
			pwr_ctrl->config.ci_current + pwr_ctrl->i2v_offset);
	/* Enable the charger */
	ABB_Write_Register_on_page(PAGE0, BCICTL2, 0x0003 | LEDC);
	/* The total charging time starts now */
	pwr_ctrl->start_time = rvf_get_tick_count();
	if (pwr_ctrl->event_handler)
		pwr_ctrl->event_handler(FCHG_EVENT_CHARGING_START);
}

static void start_cv_charging(void)
{
	UINT16 code;

	rvf_send_trace("Start CV charging", 17, NULL_PARAM,
			RV_TRACE_LEVEL_DEBUG_HIGH, FCHG_USE_ID);
	pwr_ctrl->state = FCHG_STATE_CV_CHARGING;
	/* Select constant voltage charging. The charger is disabled */
	ABB_Write_Register_on_page(PAGE0, BCICTL2, 0);
	/* figure out the DAC code */
	code = madc_vbat_inverse(pwr_ctrl->config.cv_init_set);
	rvf_send_trace("Voltage (DAC code) ", 19, code,
			RV_TRACE_LEVEL_DEBUG_LOW, FCHG_USE_ID);
	/* Program the DAC with the constant voltage value */
	ABB_Write_Register_on_page(PAGE0, CHGREG, code);
	/* Enable the charger */
	ABB_Write_Register_on_page(PAGE0, BCICTL2, 0x0001 | LEDC);
	/* CV control loop state init */
	pwr_ctrl->cv_dac_init = code;
	pwr_ctrl->cv_dac_curr = code;
	pwr_ctrl->cv_high_vbat_count = 0;
	pwr_ctrl->cv_low_vbat_count = 0;
	/* Ichg averaging state init */
	pwr_ctrl->ichg_fill_level = 0;
	pwr_ctrl->ichg_ring_ptr = 0;
	pwr_ctrl->ichg_low_count = 0;
}

static void start_charge_condition_met(void)
{
	rvf_send_trace("Charge start condition met", 26, NULL_PARAM,
			RV_TRACE_LEVEL_DEBUG_HIGH, FCHG_USE_ID);
	if (pwr_ctrl->config.bciconf)
		start_i2v_cal();
	else {
		pwr_ctrl->i2v_offset = 0;
		start_ci_charging();
	}
}

static void ci_progress_trace(UINT16 ichg)
{
	char trace[64];

	pwr_ctrl->ci_ichg = ichg;
	sprintf(trace, "CI charging: Vbat=%u Ichg=%u i2v=%u",
		pwr_ctrl->batt_mv, ichg, pwr_ctrl->i2v_offset);
	rvf_send_trace(trace, strlen(trace), NULL_PARAM,
			RV_TRACE_LEVEL_DEBUG_LOW, FCHG_USE_ID);
}

static int cv_ichg_process(UINT16 ichg_new)
{
	UINT16 ichg_clip, ichg_entry;
	UINT32 ichg_accum;
	UINT16 i;
	char trace[64];

	if (pwr_ctrl->ichg_fill_level < ICHG_AVG_WINDOW)
		pwr_ctrl->ichg_avg_buf[pwr_ctrl->ichg_fill_level++] = ichg_new;
	else {
		ichg_clip = pwr_ctrl->ichg_average +
				pwr_ctrl->config.ichg_max_spike;
		if (ichg_new > ichg_clip)
			ichg_entry = ichg_clip;
		else
			ichg_entry = ichg_new;
		pwr_ctrl->ichg_avg_buf[pwr_ctrl->ichg_ring_ptr++] = ichg_entry;
		if (pwr_ctrl->ichg_ring_ptr >= ICHG_AVG_WINDOW)
			pwr_ctrl->ichg_ring_ptr = 0;
	}
	ichg_accum = 0;
	for (i = 0; i < pwr_ctrl->ichg_fill_level; i++)
		ichg_accum += pwr_ctrl->ichg_avg_buf[i];
	pwr_ctrl->ichg_average = ichg_accum / pwr_ctrl->ichg_fill_level;
	sprintf(trace, "CV charging: Vbat=%u Ichg=%u Ichg_avg=%u i2v=%u",
		pwr_ctrl->batt_mv, ichg_new, pwr_ctrl->ichg_average,
		pwr_ctrl->i2v_offset);
	rvf_send_trace(trace, strlen(trace), NULL_PARAM,
			RV_TRACE_LEVEL_DEBUG_LOW, FCHG_USE_ID);
	if (pwr_ctrl->ichg_average >
	    (pwr_ctrl->config.end_current + pwr_ctrl->i2v_offset)) {
		pwr_ctrl->ichg_low_count = 0;
		return 0;
	}
	pwr_ctrl->ichg_low_count++;
	if (pwr_ctrl->ichg_low_count < pwr_ctrl->config.ichg_samples_needed)
		return 0;
	rvf_send_trace("Stopping charge by low current condition", 40,
			NULL_PARAM, RV_TRACE_LEVEL_DEBUG_HIGH, FCHG_USE_ID);
	ABB_Write_Register_on_page(PAGE0, BCICTL2, 0);
	pwr_init_discharge();
	pwr_ctrl->state = FCHG_STATE_READY_TO_RECHARGE;
	if (pwr_ctrl->event_handler)
		pwr_ctrl->event_handler(FCHG_EVENT_CHARGING_COMPLETE);
	return 1;
}

static int overvoltage_end_charge_check(void)
{
	if (pwr_ctrl->batt_mv < pwr_ctrl->config.overvoltage)
		return 0;
	if (pwr_ctrl->cv_dac_curr !=
	    (pwr_ctrl->cv_dac_init - pwr_ctrl->config.cv_dac_max_decr))
		return 0;
	rvf_send_trace("Stopping charge by overvoltage condition", 40,
			NULL_PARAM, RV_TRACE_LEVEL_WARNING, FCHG_USE_ID);
	ABB_Write_Register_on_page(PAGE0, BCICTL2, 0);
	pwr_init_discharge();
	pwr_ctrl->state = FCHG_STATE_READY_TO_RECHARGE;
	if (pwr_ctrl->event_handler)
		pwr_ctrl->event_handler(FCHG_EVENT_CHARGING_COMPLETE);
	return 1;
}

static void cv_ctrl_loop_high_check(void)
{
	if (pwr_ctrl->batt_mv < pwr_ctrl->config.cv_ctrl_loop_high) {
		pwr_ctrl->cv_high_vbat_count = 0;
		return;
	}
	pwr_ctrl->cv_high_vbat_count++;
	if (pwr_ctrl->cv_high_vbat_count < pwr_ctrl->config.cv_samples_needed)
		return;
	if (pwr_ctrl->cv_dac_curr ==
	    (pwr_ctrl->cv_dac_init - pwr_ctrl->config.cv_dac_max_decr))
		return;
	pwr_ctrl->cv_dac_curr--;
	ABB_Write_Register_on_page(PAGE0, CHGREG, pwr_ctrl->cv_dac_curr);
	rvf_send_trace("Sub CV DAC", 10, pwr_ctrl->cv_dac_curr,
			RV_TRACE_LEVEL_DEBUG_MEDIUM, FCHG_USE_ID);
	pwr_ctrl->cv_high_vbat_count = 0;
}

static void cv_ctrl_loop_low_check(void)
{
	if (pwr_ctrl->batt_mv >= pwr_ctrl->config.cv_ctrl_loop_low) {
		pwr_ctrl->cv_low_vbat_count = 0;
		return;
	}
	pwr_ctrl->cv_low_vbat_count++;
	if (pwr_ctrl->cv_low_vbat_count < pwr_ctrl->config.cv_samples_needed)
		return;
	if (pwr_ctrl->cv_dac_curr ==
	    (pwr_ctrl->cv_dac_init + pwr_ctrl->config.cv_dac_max_incr))
		return;
	pwr_ctrl->cv_dac_curr++;
	ABB_Write_Register_on_page(PAGE0, CHGREG, pwr_ctrl->cv_dac_curr);
	rvf_send_trace("Add CV DAC", 10, pwr_ctrl->cv_dac_curr,
			RV_TRACE_LEVEL_DEBUG_MEDIUM, FCHG_USE_ID);
	pwr_ctrl->cv_low_vbat_count = 0;
}

static int charging_time_limit_check(void)
{
	if ((rvf_get_tick_count() - pwr_ctrl->start_time) <
	    RVF_SECS_TO_TICKS(pwr_ctrl->config.charge_time_limit))
		return 0;
	rvf_send_trace("Stopping charge by time exceeded condition", 42,
			NULL_PARAM, RV_TRACE_LEVEL_WARNING, FCHG_USE_ID);
	ABB_Write_Register_on_page(PAGE0, BCICTL2, 0);
	pwr_init_discharge();
	pwr_ctrl->state = FCHG_STATE_RECHARGE_TIMER;
	pwr_ctrl->start_time = rvf_get_tick_count();
	if (pwr_ctrl->event_handler)
		pwr_ctrl->event_handler(FCHG_EVENT_CHARGING_TIMEOUT);
	return 1;
}

static void process_adc_regular(struct pwr_adc_ind_s *msg)
{
	switch (pwr_ctrl->state) {
	case FCHG_STATE_NO_EXT_PWR:
	case FCHG_STATE_PWR_PLUG_TIMER:
	case FCHG_STATE_NO_CHARGING:
		handle_discharge();
		return;
	case FCHG_STATE_READY_TO_CHARGE:
		handle_discharge();
		if (!(msg->data[9] & CHGPRES)) {
			pwr_ctrl->state = FCHG_STATE_NO_CHARGING;
			return;
		}
		if (pwr_ctrl->batt_mv < pwr_ctrl->config.start_thresh)
			start_charge_condition_met();
		return;
	case FCHG_STATE_READY_TO_RECHARGE:
		handle_discharge();
		if (!(msg->data[9] & CHGPRES)) {
			pwr_ctrl->state = FCHG_STATE_NO_CHARGING;
			return;
		}
		if (pwr_ctrl->batt_mv < pwr_ctrl->config.restart_thresh)
			start_charge_condition_met();
		return;
	case FCHG_STATE_I2V_CAL_1:
		if (!(msg->data[9] & CHGPRES)) {
			pwr_ctrl->state = FCHG_STATE_NO_CHARGING;
			return;
		}
		if (pwr_ctrl->config.bciconf)
			start_i2v_cal();
		else {
			pwr_ctrl->i2v_offset = 0;
			start_ci_charging();
		}
		return;
	case FCHG_STATE_I2V_CAL_2:
		pwr_ctrl->i2v_offset = msg->data[2];
		ABB_Write_Register_on_page(PAGE0, BCICTL2, 0);
		rvf_send_trace("i2v offset (MADC code) ", 23,
				pwr_ctrl->i2v_offset,
				RV_TRACE_LEVEL_DEBUG_LOW, FCHG_USE_ID);
		if (!(msg->data[9] & CHGPRES)) {
			pwr_ctrl->state = FCHG_STATE_NO_CHARGING;
			pwr_init_discharge();
			return;
		}
		start_ci_charging();
		return;
	case FCHG_STATE_CI_CHARGING:
		ci_progress_trace(msg->data[2]);
		if (!(msg->data[9] & CHGPRES)) {
			ABB_Write_Register_on_page(PAGE0, BCICTL2, 0);
			pwr_ctrl->state = FCHG_STATE_NO_CHARGING;
			pwr_init_discharge();
			return;
		}
		if (charging_time_limit_check())
			return;
		if (pwr_ctrl->batt_mv >= pwr_ctrl->config.ci2cv_thresh)
			start_cv_charging();
		return;
	case FCHG_STATE_CV_CHARGING:
		if (!(msg->data[9] & CHGPRES)) {
			ABB_Write_Register_on_page(PAGE0, BCICTL2, 0);
			pwr_ctrl->state = FCHG_STATE_NO_CHARGING;
			pwr_init_discharge();
			return;
		}
		if (cv_ichg_process(msg->data[2]))
			return;
		if (overvoltage_end_charge_check())
			return;
		if (charging_time_limit_check())
			return;
		cv_ctrl_loop_high_check();
		cv_ctrl_loop_low_check();
		return;
	case FCHG_STATE_RECHARGE_TIMER:
		handle_discharge();
		if ((rvf_get_tick_count() - pwr_ctrl->start_time) <
		    RVF_SECS_TO_TICKS(pwr_ctrl->config.recharge_delay))
			return;
		rvf_send_trace("Restart time met, allowing new charging", 39,
				NULL_PARAM, RV_TRACE_LEVEL_DEBUG_HIGH,
				FCHG_USE_ID);
		pwr_ctrl->state = FCHG_STATE_READY_TO_RECHARGE;
		return;
	default:
		rvf_send_trace("Invalid state in pwr_process_adc()", 34,
				pwr_ctrl->state, RV_TRACE_LEVEL_ERROR,
				FCHG_USE_ID);
	}
}

static void process_adc_bsim(struct pwr_adc_ind_s *msg)
{
	switch (pwr_ctrl->state) {
	case FCHG_STATE_READY_TO_CHARGE:
		if (!pwr_ctrl->bsim.start_enable)
			return;
		rvf_send_trace("BSIM: simulated charging auto-start", 35,
				NULL_PARAM, RV_TRACE_LEVEL_DEBUG_MEDIUM,
				FCHG_USE_ID);
		pwr_ctrl->state = FCHG_STATE_I2V_CAL_1;
		return;
	case FCHG_STATE_I2V_CAL_1:
		rvf_send_trace("BSIM: advancing from I2V_CAL_1 to I2V_CAL_2",
				43, NULL_PARAM, RV_TRACE_LEVEL_DEBUG_LOW,
				FCHG_USE_ID);
		pwr_ctrl->state = FCHG_STATE_I2V_CAL_2;
		return;
	case FCHG_STATE_I2V_CAL_2:
		rvf_send_trace("BSIM: advancing from I2V_CAL_2 to CI charging",
				45, NULL_PARAM, RV_TRACE_LEVEL_DEBUG_LOW,
				FCHG_USE_ID);
		pwr_ctrl->state = FCHG_STATE_CI_CHARGING;
		if (pwr_ctrl->event_handler)
			pwr_ctrl->event_handler(FCHG_EVENT_CHARGING_START);
		return;
	default:
		/* remaining states require no action in BSIM */
		return;
	}
}

void pwr_process_adc(struct pwr_adc_ind_s *msg)
{
	pwr_ctrl->batt_mv = madc_vbat_2_physical(msg->data[0]);
	if (!fchg_first_vbat)
		capture_first_vbat();
	if (pwr_ctrl->bsim_mode)
		process_adc_bsim(msg);
	else
		process_adc_regular(msg);
}

void pwr_charger_plug_unplug_ind(void)
{
	switch (pwr_ctrl->state) {
	case FCHG_STATE_NO_EXT_PWR:
		rvf_send_trace("Charger plug interrupt, beginning debounce",
				42, NULL_PARAM, RV_TRACE_LEVEL_DEBUG_LOW,
				FCHG_USE_ID);
		pwr_ctrl->state = FCHG_STATE_PWR_PLUG_TIMER;
		break;
	case FCHG_STATE_PWR_PLUG_TIMER:
		/* debouncing in action - nothing to do here */
		break;
	case FCHG_STATE_READY_TO_CHARGE:
	case FCHG_STATE_READY_TO_RECHARGE:
	case FCHG_STATE_I2V_CAL_1:
	case FCHG_STATE_RECHARGE_TIMER:
	case FCHG_STATE_NO_CHARGING:
		rvf_send_trace("Charger unplug", 14, NULL_PARAM,
				RV_TRACE_LEVEL_DEBUG_LOW, FCHG_USE_ID);
		pwr_ctrl->state = FCHG_STATE_PWR_PLUG_TIMER;
		if (pwr_ctrl->event_handler)
			pwr_ctrl->event_handler(FCHG_EVENT_CHARGER_UNPLUG);
		break;
	case FCHG_STATE_I2V_CAL_2:
	case FCHG_STATE_CI_CHARGING:
	case FCHG_STATE_CV_CHARGING:
		ABB_Write_Register_on_page(PAGE0, BCICTL2, 0);
		rvf_send_trace("Charger unplug, charging stopped", 32,
				NULL_PARAM, RV_TRACE_LEVEL_DEBUG_HIGH,
				FCHG_USE_ID);
		pwr_ctrl->state = FCHG_STATE_PWR_PLUG_TIMER;
		pwr_init_discharge();
		if (pwr_ctrl->event_handler)
			pwr_ctrl->event_handler(FCHG_EVENT_CHARGER_UNPLUG);
		break;
	default:
		rvf_send_trace("Invalid state in pwr_charger_plug_unplug_ind()",
				46, pwr_ctrl->state, RV_TRACE_LEVEL_ERROR,
				FCHG_USE_ID);
	}
	rvf_start_timer(FCHG_TIMER, RVF_MS_TO_TICKS(CHARGER_PLUG_DEBOUNCE_MS),
			FALSE);
	/* unmask further charger interrupts */
	ABB_Write_Register_on_page(PAGE0, ITMASK, 0);
}

void pwr_handle_timer(void)
{
	SYS_UWORD16 abb_status;

	if (pwr_ctrl->state != FCHG_STATE_PWR_PLUG_TIMER) {
		rvf_send_trace(
			"Charger debounce timer expired in unexpected state",
				50, pwr_ctrl->state, RV_TRACE_LEVEL_ERROR,
				FCHG_USE_ID);
		return;
	}
	/* so was it a plug or an unplug after all? */
	abb_status = ABB_Read_Status();
	if (abb_status & CHGPRES) {
		/* charger plug */
		if (pwr_ctrl->config_present || pwr_ctrl->bsim_mode) {
			rvf_send_trace("Charger plug, ready to charge", 29,
					NULL_PARAM, RV_TRACE_LEVEL_DEBUG_HIGH,
					FCHG_USE_ID);
			pwr_ctrl->state = FCHG_STATE_READY_TO_CHARGE;
			if (pwr_ctrl->event_handler)
			    pwr_ctrl->event_handler(FCHG_EVENT_CHARGER_PLUG);
		} else {
			rvf_send_trace(
			"Charger plugged in, but no config: won't charge", 47,
				NULL_PARAM, RV_TRACE_LEVEL_ERROR, FCHG_USE_ID);
			pwr_ctrl->state = FCHG_STATE_NO_CHARGING;
		}
	} else {
		/* charger unplug, already handled */
		pwr_ctrl->state = FCHG_STATE_NO_EXT_PWR;
	}
}

void pwr_charge_start_req(void)
{
	switch (pwr_ctrl->state) {
	case FCHG_STATE_NO_EXT_PWR:
	case FCHG_STATE_PWR_PLUG_TIMER:
		rvf_send_trace("Cannot charge without a power source", 36,
				NULL_PARAM, RV_TRACE_LEVEL_ERROR, FCHG_USE_ID);
		return;
	case FCHG_STATE_NO_CHARGING:
		if (!pwr_ctrl->config_present && !pwr_ctrl->bsim_mode) {
			rvf_send_trace("No config set, cannot charge", 28,
					NULL_PARAM, RV_TRACE_LEVEL_ERROR,
					FCHG_USE_ID);
			return;
		}
		/* FALL THRU */
	case FCHG_STATE_READY_TO_CHARGE:
	case FCHG_STATE_READY_TO_RECHARGE:
	case FCHG_STATE_RECHARGE_TIMER:
		rvf_send_trace("Starting charge on user request", 31,
				NULL_PARAM, RV_TRACE_LEVEL_DEBUG_HIGH,
				FCHG_USE_ID);
		pwr_ctrl->state = FCHG_STATE_I2V_CAL_1;
		return;
	case FCHG_STATE_I2V_CAL_1:
	case FCHG_STATE_I2V_CAL_2:
	case FCHG_STATE_CI_CHARGING:
	case FCHG_STATE_CV_CHARGING:
		rvf_send_trace(
			"Charging already in progress, start request ignored",
			51, NULL_PARAM, RV_TRACE_LEVEL_WARNING, FCHG_USE_ID);
		return;
	default:
		rvf_send_trace("Invalid state in pwr_charge_start_req()", 39,
				pwr_ctrl->state, RV_TRACE_LEVEL_ERROR,
				FCHG_USE_ID);
	}
}

void pwr_charge_stop_req(void)
{
	switch (pwr_ctrl->state) {
	case FCHG_STATE_NO_EXT_PWR:
	case FCHG_STATE_PWR_PLUG_TIMER:
	case FCHG_STATE_NO_CHARGING:
		/* nothing to do */
		return;
	case FCHG_STATE_READY_TO_CHARGE:
	case FCHG_STATE_READY_TO_RECHARGE:
	case FCHG_STATE_I2V_CAL_1:
	case FCHG_STATE_RECHARGE_TIMER:
		rvf_send_trace("Charging disabled by user request", 33,
				NULL_PARAM, RV_TRACE_LEVEL_DEBUG_HIGH,
				FCHG_USE_ID);
		pwr_ctrl->state = FCHG_STATE_NO_CHARGING;
		return;
	case FCHG_STATE_I2V_CAL_2:
	case FCHG_STATE_CI_CHARGING:
	case FCHG_STATE_CV_CHARGING:
		ABB_Write_Register_on_page(PAGE0, BCICTL2, 0);
		rvf_send_trace("Charging stopped by user request", 32,
				NULL_PARAM, RV_TRACE_LEVEL_DEBUG_HIGH,
				FCHG_USE_ID);
		pwr_ctrl->state = FCHG_STATE_NO_CHARGING;
		pwr_init_discharge();
		if (pwr_ctrl->event_handler)
			pwr_ctrl->event_handler(FCHG_EVENT_CHARGING_STOPPED);
		return;
	default:
		rvf_send_trace("Invalid state in pwr_charge_stop_req()", 38,
				pwr_ctrl->state, RV_TRACE_LEVEL_ERROR,
				FCHG_USE_ID);
	}
}