summaryrefslogtreecommitdiff
path: root/driver/battery/bq27541.c
blob: b39147092f6fdcdbe61941b3f1bee92f79edbca0 (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
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
/* Copyright 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 driver for BQ27541/BQ27542/BQ27741/BQ27742.
 */

#include "battery.h"
#include "battery_smart.h"
#include "console.h"
#include "extpower.h"
#include "hooks.h"
#include "i2c.h"
#include "util.h"

#define BQ27541_ADDR_FLAGS          0x55
#define BQ27541_TYPE_ID             0x0541
#define BQ27542_TYPE_ID             0x0542
#define BQ27741_TYPE_ID             0x0741
#define BQ27742_TYPE_ID             0x0742

#define REG_CTRL                    0x00
#define REG_AT_RATE                 0x02
#define REG_AT_RATE_TIME_TO_EMPTY   0x04
#define REG_TEMPERATURE             0x06
#define REG_VOLTAGE                 0x08
#define REG_FLAGS                   0x0a
#define REG_NOMINAL_CAPACITY        0x0c
#define REG_FULL_AVAILABLE_CAPACITY 0x0e
#define REG_REMAINING_CAPACITY      0x10
#define REG_FULL_CHARGE_CAPACITY    0x12
#define REG_AVERAGE_CURRENT         0x14
#define REG_TIME_TO_EMPTY           0x16
#define REG_TIME_TO_FULL            0x18
#define REG_STANDBY_CURRENT         0x1a
#define REG_STANDBY_TIME_TO_EMPTY   0x1c
#define REG_MAX_LOAD_CURRENT        0x1e
#define REG_MAX_LOAD_TIME_TO_EMPTY  0x20
#define REG_AVAILABLE_ENERGY        0x22
#define REG_AVERAGE_POEWR           0x24
#define REG_TT_EAT_CONSTANT_POWER   0x26
#define REG_CYCLE_COUNT             0x2a
#define REG_STATE_OF_CHARGE         0x2c
#define	REG_DATA_FLASH_BLOCK	    0x3f
#define REG_DESIGN_CAPACITY         0x3c
#define REG_MANUFACTURER_INFO	    0x52
#define REG_DEVICE_NAME_LENGTH      0x62
#define MAX_DEVICE_NAME_LENGTH      7
#define REG_DEVICE_NAME             0x63
#define REG_PROTECTOR               0x6d

/* Over-charge */
#define BQ27542_FLAG_BATHI           BIT(13)
/* Over Temperature in discharge */
#define BQ27542_FLAG_OTD             BIT(11)
/* Over Temperature in charge */
#define BQ27542_FLAG_OTC             BIT(7)
/* Charge allowed */
#define BQ27542_FLAG_CHG             BIT(3)
/* Discharge */
#define BQ27542_FLAG_DSG             BIT(0)

static int battery_type_id;
static int fake_state_of_charge = -1;

static int bq27541_read(int offset, int *data)
{
	return i2c_read16(I2C_PORT_BATTERY, BQ27541_ADDR, offset, data);
}

static int bq27541_read8(int offset, int *data)
{
	return i2c_read8(I2C_PORT_BATTERY, BQ27541_ADDR, offset, data);
}

static int bq27541_write(int offset, int data)
{
	return i2c_write16(I2C_PORT_BATTERY, BQ27541_ADDR, offset, data);
}

int bq27541_probe(void)
{
	int rv;

	rv = bq27541_write(REG_CTRL, 0x1);
	rv |= bq27541_read(REG_CTRL, &battery_type_id);
	/* Read twice to get the right value */
	rv |= bq27541_read(REG_CTRL, &battery_type_id);
	if (rv)
		return rv;
	if (battery_type_id == BQ27541_TYPE_ID ||
	    battery_type_id == BQ27542_TYPE_ID ||
	    battery_type_id == BQ27741_TYPE_ID ||
	    battery_type_id == BQ27742_TYPE_ID)
		return EC_SUCCESS;
	return EC_ERROR_UNKNOWN;
}

static void probe_type_id(void)
{
	bq27541_probe();
}
DECLARE_HOOK(HOOK_INIT, probe_type_id, HOOK_PRIO_DEFAULT);

int battery_device_name(char *device_name, int buf_size)
{
	int rv, i, val;
	int len = MIN(7, buf_size - 1);

	if (battery_type_id == BQ27742_TYPE_ID) {
		/* No device name register available */
		strzcpy(device_name, "<BATT>", len);
		return 0;
	}
	/* Battery pack vendor specific */
	if (battery_type_id == BQ27542_TYPE_ID) {
		rv = bq27541_write(REG_DATA_FLASH_BLOCK, 0x1);
		for (i = 0; i < len; ++i) {
			rv |= bq27541_read8(REG_MANUFACTURER_INFO + i, &val);
			device_name[i] = val;
		}
		device_name[i] = '\0';
		return rv;
	}

	rv = bq27541_read8(REG_DEVICE_NAME_LENGTH, &val);
	if (rv)
		return rv;
	len = MIN(len, val);

	for (i = 0; i < len; ++i) {
		rv |= bq27541_read8(REG_DEVICE_NAME + i, &val);
		device_name[i] = val;
	}
	device_name[i] = '\0';

	return rv;
}

int battery_state_of_charge_abs(int *percent)
{
	return bq27541_read(REG_STATE_OF_CHARGE, percent);
}

int battery_remaining_capacity(int *capacity)
{
	return bq27541_read(REG_REMAINING_CAPACITY, capacity);
}

int battery_full_charge_capacity(int *capacity)
{
	return bq27541_read(REG_FULL_CHARGE_CAPACITY, capacity);
}

int battery_time_to_empty(int *minutes)
{
	return bq27541_read(REG_TIME_TO_EMPTY, minutes);
}

int battery_time_to_full(int *minutes)
{
	return bq27541_read(REG_TIME_TO_FULL, minutes);
}

int battery_cycle_count(int *count)
{
	return bq27541_read(REG_CYCLE_COUNT, count);
}

int battery_design_capacity(int *capacity)
{
	return bq27541_read(REG_DESIGN_CAPACITY, capacity);
}

int battery_time_at_rate(int rate, int *minutes)
{
	int rv;

	if (battery_type_id == BQ27542_TYPE_ID)
		return EC_ERROR_UNIMPLEMENTED;

	rv = bq27541_write(REG_AT_RATE, rate);
	if (rv)
		return rv;
	return bq27541_read(REG_AT_RATE_TIME_TO_EMPTY, minutes);
}

int battery_device_chemistry(char *dest, int size)
{
	strzcpy(dest, "<unkn>", size);

	return EC_SUCCESS;
}

int battery_serial_number(int *serial)
{
	*serial = 0x0BAD0BAD;

	return EC_SUCCESS;
}

int battery_manufacture_date(int *year, int *month, int *day)
{
	return EC_ERROR_UNIMPLEMENTED;
}

int battery_design_voltage(int *voltage)
{
	*voltage = battery_get_info()->voltage_normal;

	return EC_SUCCESS;
}

/**
 * Check if battery allows charging.
 *
 * @param allowed	Non-zero if charging allowed; zero if not allowed.
 * @return non-zero if error.
 */
static int battery_charging_allowed(int *allowed)
{
	int rv, val;

	rv = bq27541_read(REG_FLAGS, &val);
	if (rv)
		return rv;
	if (battery_type_id == BQ27541_TYPE_ID ||
	    battery_type_id == BQ27741_TYPE_ID)
		*allowed = (val & 0x100);
	else /* BQ27742_TYPE_ID, BQ27542_TYPE_ID */
		*allowed = (val & 0x8);

	return EC_SUCCESS;
}

int battery_get_mode(int *mode)
{
	return EC_ERROR_UNIMPLEMENTED;
}

int battery_status(int *status)
{
	int rv;
	int flag = 0;

	*status = 0;
	if (battery_type_id == BQ27542_TYPE_ID) {
		rv = bq27541_read(REG_FLAGS, &flag);
		if (rv)
			return rv;

		if (flag & (BQ27542_FLAG_OTC | BQ27542_FLAG_OTD))
			*status |= STATUS_OVERTEMP_ALARM;
		if (flag & BQ27542_FLAG_DSG)
			*status |= STATUS_DISCHARGING;
		if (flag & BQ27542_FLAG_BATHI)
			*status |= STATUS_OVERCHARGED_ALARM;

		return EC_SUCCESS;
	}

	return EC_ERROR_UNIMPLEMENTED;
}

enum battery_present battery_is_present(void)
{
	int v;
	if (bq27541_read(REG_TEMPERATURE, &v))
		return BP_NO;
	return BP_YES;
}

void battery_get_params(struct batt_params *batt)
{
	int v;
	const uint32_t flags_to_check = BATT_FLAG_BAD_TEMPERATURE |
					BATT_FLAG_BAD_STATE_OF_CHARGE |
					BATT_FLAG_BAD_VOLTAGE |
					BATT_FLAG_BAD_CURRENT;

	/* Reset flags */
	batt->flags = 0;

	if (bq27541_read(REG_TEMPERATURE, &batt->temperature))
		batt->flags |= BATT_FLAG_BAD_TEMPERATURE;

	if (bq27541_read8(REG_STATE_OF_CHARGE, &v) && fake_state_of_charge < 0)
		batt->flags |= BATT_FLAG_BAD_STATE_OF_CHARGE;

	batt->state_of_charge = fake_state_of_charge >= 0 ?
					fake_state_of_charge : v;

	if (bq27541_read(REG_VOLTAGE, &batt->voltage))
		batt->flags |= BATT_FLAG_BAD_VOLTAGE;

	v = 0;
	if (bq27541_read(REG_AVERAGE_CURRENT, &v))
		batt->flags |= BATT_FLAG_BAD_CURRENT;
	batt->current = (int16_t)v;

	if (battery_remaining_capacity(&batt->remaining_capacity))
		batt->flags |= BATT_FLAG_BAD_REMAINING_CAPACITY;

	if (battery_full_charge_capacity(&batt->full_capacity))
		batt->flags |= BATT_FLAG_BAD_FULL_CAPACITY;

	/* Default to not desiring voltage and current */
	batt->desired_voltage = batt->desired_current = 0;

	/* If any of those reads worked, the battery is responsive */
	if ((batt->flags & flags_to_check) != flags_to_check) {
		batt->flags |= BATT_FLAG_RESPONSIVE;
		batt->is_present = BP_YES;
	} else {

	/* If all of those reads error, the battery is not present */
		batt->is_present = BP_NO;
	}

	/* update the battery status */
	if (battery_status(&batt->status))
		batt->flags |= BATT_FLAG_BAD_STATUS;

	v = 0;
	if (battery_charging_allowed(&v)) {
		batt->flags |= BATT_FLAG_BAD_ANY;
	} else if (v) {
		batt->flags |= BATT_FLAG_WANT_CHARGE;

		/*
		 * Desired voltage and current are not provided by the battery.
		 * So ask for battery's max voltage and an arbitrarily large
		 * current.
		 */
		batt->desired_voltage = battery_get_info()->voltage_max;
		batt->desired_current = 4096;
	}
}

/* Wait until battery is totally stable */
int battery_wait_for_stable(void)
{
	/* TODO(crosbug.com/p/30426): implement me */
	return EC_SUCCESS;
}

#ifdef CONFIG_BATTERY_REVIVE_DISCONNECT
/*
 * Check if battery is in disconnect state, a state entered by pulling
 * BATT_DISCONN_N low, and clear that state if we have external power plugged
 * and no battery faults are detected. Disconnect state resembles battery
 * shutdown mode, but extra steps must be taken to get the battery out of this
 * mode.
 */
enum battery_disconnect_state battery_get_disconnect_state(void)
{
	int val, rv;
	/*
	 * Take note if we find that the battery isn't in disconnect state,
	 * and always return NOT_DISCONNECTED without probing the battery.
	 * This assumes the battery will not go to disconnect state during
	 * runtime.
	 */
	static int not_disconnected;

	if (not_disconnected)
		return BATTERY_NOT_DISCONNECTED;

	if (extpower_is_present()) {
		/* Check DSG_OFF bit */
		rv = bq27541_read(REG_PROTECTOR, &val);
		if (rv)
			return BATTERY_DISCONNECT_ERROR;
		if (!(val & BIT(6))) {
			not_disconnected = 1;
			return BATTERY_NOT_DISCONNECTED;
		}

		/* DSG_OFF is set. Verify this is not due to a safety fault */
		if (val & 0x3f)
			return BATTERY_DISCONNECT_ERROR;
		rv = bq27541_read(REG_FLAGS, &val);
		if (rv)
			return BATTERY_DISCONNECT_ERROR;
		if (val & 0xfc60)
			return BATTERY_DISCONNECT_ERROR;
		return BATTERY_DISCONNECTED;
	}
	not_disconnected = 1;
	return BATTERY_NOT_DISCONNECTED;
}
#endif /* CONFIG_BATTERY_REVIVE_DISCONNECT */

static int command_battfake(int argc, char **argv)
{
	char *e;
	int v;

	if (argc == 2) {
		v = strtoi(argv[1], &e, 0);
		if (*e || v < -1 || v > 100)
			return EC_ERROR_PARAM1;

		fake_state_of_charge = v;
	}

	if (fake_state_of_charge >= 0)
		ccprintf("Fake batt %d%%\n",
			 fake_state_of_charge);

	return EC_SUCCESS;
}
DECLARE_CONSOLE_COMMAND(battfake, command_battfake,
			"percent (-1 = use real level)",
			"Set fake battery level");

#ifdef CONFIG_CMD_PWR_AVG
int battery_get_avg_current(void)
{
	int current = -EC_ERROR_UNKNOWN;

	bq27541_read(REG_AVERAGE_CURRENT, &current);
	return current;
}

int battery_get_avg_voltage(void)
{
	/* BQ27541 does not have this parameter */
	return -EC_ERROR_UNIMPLEMENTED;
}
#endif /* CONFIG_CMD_PWR_AVG */