summaryrefslogtreecommitdiff
path: root/common/battery_precharge.c
blob: 10cae9726932ff8f4da542c2118256bd15c96519 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
/* Copyright (c) 2012 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 charging task and state machine.
 */

#include "battery.h"
#include "battery_pack.h"
#include "charge_state.h"
#include "charger.h"
#include "smart_battery.h"
#include "timer.h"
#include "uart.h"
#include "util.h"

/* Buffer size for charging resistance calculation */
#define LOG_BUFFER_SIZE  16

static int log_index;
static short log_volt[LOG_BUFFER_SIZE];
static short log_curr[LOG_BUFFER_SIZE];
static int baseline_voltage;
static int kicking_count;

static inline int time_after(timestamp_t now, timestamp_t orig, uint64_t usec)
{
	return (now.val > (orig.val + usec));
}

static inline void reset_data_log(void)
{
	log_index = 0;
}

static inline void trickle_charging_init(void)
{
	baseline_voltage = 0;
	kicking_count    = 0;
	reset_data_log();
}

/**
 * Adjust charging voltage with voltage value range checking.
 * Reset data log and charger watchdog timer.
 */
static int set_voltage(struct power_state_context *ctx, int voltage)
{
	if (voltage <= ctx->curr.batt.desired_voltage && voltage > 0) {
		charger_set_voltage(voltage);
		charger_get_voltage(&ctx->curr.charging_voltage);
		ctx->charger_update_time = get_time();
		reset_data_log();
		return 0;
	}
	return -1;
}

/**
 * Increase the charging voltage one step.
 */
static int inc_voltage(struct power_state_context *ctx)
{
	return set_voltage(ctx, ctx->curr.charging_voltage +
			   ctx->charger->voltage_step);
}

/**
 * Decrease the charging voltage one step.
 */
static int dec_voltage(struct power_state_context *ctx)
{
	return set_voltage(ctx, ctx->curr.charging_voltage -
			   ctx->charger->voltage_step);
}

/**
 * Increase the charging voltage baseline one step.
 */
static enum power_state go_next_level(struct power_state_context *ctx)
{
	if (inc_voltage(ctx))
		return PWR_STATE_ERROR;

	/*
	 * Battery chemical reaction lags behind the charging voltage
	 * change. Delay the charging state machine 2 seconds.
	 */
	sleep(2);
	charger_get_voltage(&baseline_voltage);

	return PWR_STATE_UNCHANGE;
}

/**
 * Trickle charging handler
 *
 *     - check trickle charging timeout
 *     - new state: INIT
 *     - exit condition: when desired_current reaches current_min
 *     - try to charge larger current when battery voltage reaches
 *       105% of voltage_min
 */
enum power_state trickle_charge(struct power_state_context *ctx)
{
	int sum_volt, sum_curr;
	int desired_volt, desired_curr;

	struct power_state_data *curr = &ctx->curr;
	struct batt_params *batt = &curr->batt;
	const struct charger_info *cinfo = ctx->charger;
	const struct battery_info *binfo = ctx->battery;

	/* Clear trickle charging duration on AC change */
	if (curr->ac != ctx->prev.ac) {
		ctx->trickle_charging_time.val = 0;
		if (!curr->ac)
			return PWR_STATE_INIT;
	}

	/* Start timer */
	if (ctx->trickle_charging_time.val == 0) {
		trickle_charging_init();
		ctx->trickle_charging_time = get_time();
	}

	/* Check charger reset */
	if (curr->charging_voltage == 0 || curr->charging_current == 0) {
		ctx->trickle_charging_time.val = 0;
		return PWR_STATE_INIT;
	}

	/*
	 * 4 hours is long enough to pre-charge a large battery (8000mAh)
	 * using minimal current (5mAh).
	 */
	if (time_after(curr->ts, ctx->trickle_charging_time, HOUR * 4))
		return PWR_STATE_ERROR;

	if (curr->error & F_BATTERY_MASK)
		return PWR_STATE_UNCHANGE;

	/*
	 * End of pre-charge condition; battery desired a current higher than
	 * the minimal charging cap.
	 */
	if (batt->desired_current > cinfo->current_min) {
		trickle_charging_init();
		ctx->trickle_charging_time.val = 0;
		return PWR_STATE_INIT;
	}

	/*
	 * If the trickle charging current drops to zero, raise charging
	 * voltage baseline to next level.
	 */
	if (batt->current == 0)
		return go_next_level(ctx);

	/*
	 * When the battery voltage reaches normal charging value (105% min),
	 * try kicking the current up and see if it starts normal charging.
	 */
	if (kicking_count < 5 &&
	    batt->voltage > (binfo->voltage_min * 105 / 100)) {
		kicking_count++;
		charger_set_voltage(batt->desired_voltage);
		sleep(5);
		desired_curr = 0;
		battery_desired_current(&desired_curr);
		if (desired_curr >= cinfo->current_min) {
			/* Exit trickle charging state */
			trickle_charging_init();
			ctx->trickle_charging_time.val = 0;
			return PWR_STATE_INIT;
		}
		charger_set_voltage(curr->charging_voltage);
		ctx->charger_update_time = get_time();

		reset_data_log();
		return PWR_STATE_UNCHANGE;
	}

	/*
	 * Over current protection.  Decrease charging voltage and baseline
	 * voltage.
	 */
	if (batt->current > binfo->precharge_current) {
		dec_voltage(ctx);
		if (baseline_voltage > ctx->curr.charging_voltage)
			baseline_voltage = ctx->curr.charging_voltage;
		sleep(1);
		reset_data_log();
		return PWR_STATE_UNCHANGE;
	}

	/* Voltage and current data acquisition. */
	if (log_index < LOG_BUFFER_SIZE) {
		log_volt[log_index] = batt->voltage;
		log_curr[log_index] = batt->current;
		log_index++;
		return PWR_STATE_UNCHANGE;
	}

	sum_volt = 0;
	sum_curr = 0;
	for (log_index = 0; log_index < LOG_BUFFER_SIZE; log_index++) {
		sum_volt += log_volt[log_index];
		sum_curr += log_curr[log_index];
	}

	reset_data_log();

	/*
	 * Estimate desired_voltage.  The target current to desired voltage
	 * function is a monotonic function.  To simplify the calculation, use
	 * linear estimation when the current delta is small.
	 *
	 * V_desired = I_target * ( avg(dV_batt) / avg(I_batt) ) + V_batt
	 */
	desired_volt = (1 + batt->desired_current) *
		(curr->charging_voltage * LOG_BUFFER_SIZE - sum_volt) /
		sum_curr + batt->voltage;

	if (desired_volt > baseline_voltage) {
		if (desired_volt > curr->charging_voltage) {
			inc_voltage(ctx);
			sleep(1);
			return PWR_STATE_UNCHANGE;
		}

		if (desired_volt < (curr->charging_voltage -
				cinfo->voltage_step)) {
			dec_voltage(ctx);
			sleep(1);
			return PWR_STATE_UNCHANGE;
		}
	}

	/* Update charger watchdog periodically */
	if (time_after(curr->ts, ctx->charger_update_time,
			CHARGER_UPDATE_PERIOD)) {
		charger_set_current(curr->charging_current);
		ctx->charger_update_time = get_time();
	}

	return PWR_STATE_UNCHANGE;
}