summaryrefslogtreecommitdiff
path: root/max1720x_outliers.c
blob: e42b9036d75ec8c6ec52d0182a8a21aa9d3becd7 (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
/*
 * Fix for capacity drift for max1720x and for max77759
 *
 * Copyright (C) 2021 Google LLC
 */


#include <linux/err.h>
#include <linux/i2c.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <linux/time.h>
#include "gbms_power_supply.h"
#include "google_bms.h"
#include "max1720x_battery.h"

/* delay between attempts after a write failure */
#define MAX17201_FIXUP_UPDATE_DELAY_MS	10

/* max 110% of design cap, max 60% of design cap for age model */
#define MAX1720x_CC_UPPER_BOUND	110
#define MAX1720x_CC_LOWER_BOUND	60

#define ALGO_VER_CHECK(algo_ver) ((algo_ver) == MAX1720X_DA_VER_MWA2 || \
				  (algo_ver) == MAX1720X_DA_VER_NONE)


/* registers accessed from the workaround */
enum {
	MAX17X0X_REPCAP		= 0x05,
	MAX17X0X_REPSOC		= 0x06,
	MAX17X0X_MIXCAP 	= 0x0F,
	MAX17X0X_FULLCAPNOM	= 0x23,
	MAX17X0X_FULLCAPREP	= 0x35,
	MAX17X0X_RCOMP0		= 0x38,	/* 16 bits in MW A1+ */
	MAX17X0X_TEMPCO		= 0x39,
	MAX17X0X_DQACC		= 0x45,
	MAX17X0X_DPACC		= 0x46,
	MAX17X0X_VFSOC		= 0xFF,
};

/* 1 = success, 0 compare error, < 0 error */
static int max1720x_update_compare(struct max17x0x_regmap *map, int reg,
				   u16 data0, u16 data1)
{
	u16 data[2] = {data0, data1};
	int ret;

	ret = regmap_raw_write(map->regmap, reg, data, sizeof(data));
	if (ret < 0)
		return -EIO;

	msleep(2);

	ret = regmap_raw_read(map->regmap, reg, data, sizeof(data));
	if (ret < 0)
		return -EIO;

	return (data[0] == data0) && (data[1] == data1);
}

/* 0 not updated, 1 updated, doesn't return IO errors */
static int max1720x_update_capacity(struct max17x0x_regmap *map,
				    u16 mixcap, u16 repcap,
				    u16 fullcaprep)
{
	u16 temp;
	int err;

	err = REGMAP_WRITE(map, MAX17X0X_MIXCAP, mixcap);
	if (err == 0)
		err = REGMAP_READ(map, MAX17X0X_MIXCAP, &temp);
	if (err < 0 || temp != mixcap)
		return 0;

	/* RepCap and FullCapRep must be updated together */
	err = REGMAP_WRITE(map, MAX17X0X_REPCAP, repcap);
	if (err == 0)
		err = REGMAP_READ(map, MAX17X0X_REPCAP, &temp);
	if (err < 0 || temp != repcap)
		return 0;

	err = REGMAP_WRITE(map, MAX17X0X_FULLCAPREP, fullcaprep);
	if (err == 0)
		err = REGMAP_READ(map, MAX17X0X_FULLCAPREP, &temp);
	if (err < 0 || temp != fullcaprep)
		return 0;

	return 1;
}


/* return fullcapnom if no changes */
static int max1720x_capacity_check(int fullcapnom, int cycle_count,
				   const struct max1720x_drift_data *ddata)
{
	const int fcn = fullcapnom;
	int refcap, upper_bound, lower_bound;

	if (!ddata->design_capacity || cycle_count < ddata->cycle_stable)
		return fullcapnom;

	/* apply fade model to design capacity, bound to absolute min/max */
	refcap = ddata->design_capacity;
	if (ddata->cycle_fade) {
		const int base_capacity = ddata->design_capacity;

		lower_bound = (base_capacity * MAX1720x_CC_LOWER_BOUND) / 100;
		upper_bound = (base_capacity * MAX1720x_CC_UPPER_BOUND) / 100;

		refcap -= (base_capacity * cycle_count) / ddata->cycle_fade;
		if (refcap < lower_bound)
			refcap = lower_bound;
		else if (refcap > upper_bound)
			refcap = upper_bound;

		pr_debug("refcap@%d=%d abs_min=%d abs_max=%d\n",
			cycle_count, refcap, lower_bound, upper_bound);
	}

	/* bound FCN max to the target age. Will not operate on devices
	 * that underestimate capacity.
	 * NOTE: range decrease with cycle count
	 */
	upper_bound = (refcap * (100 + ddata->cycle_band)) / 100;
	if (fullcapnom > upper_bound)
		fullcapnom = upper_bound;

	pr_debug("fullcapnom=%d->%d upper_bound=%d\n",
		 fcn, fullcapnom, upper_bound);

	return fullcapnom;
}

/*
 * dQACC @0x45 battery charge between relaxation points.
 * dPACC @0x46 change in battery state of charge between relaxation points.
 */
/* 1 changed, 0 no changes, < 0 error*/

int max1720x_fixup_dxacc(struct max1720x_drift_data *ddata,
			 struct max17x0x_regmap *map,
			 int cycle_count,
			 int plugged,
			 int lsb)
{
	u16 temp, vfsoc = 0, repsoc = 0, fullcapnom, mixcap, repcap, fcrep;
	int capacity, new_capacity;
	int dpacc, dqacc;
	int err, loops;

	if (ddata->design_capacity <= 0 || ALGO_VER_CHECK(ddata->algo_ver))
		return 0;

	err = REGMAP_READ(map, MAX17X0X_FULLCAPNOM, &fullcapnom);
	if (err < 0)
		return err;

	capacity = reg_to_micro_amp_h(fullcapnom, ddata->rsense, lsb) / 1000;

	/* return the expected FCN, done if the same of th eold one */
	new_capacity = max1720x_capacity_check(capacity, cycle_count, ddata);
	if (new_capacity == capacity)
		return 0;

	/* You can use a ratio of dPAcc = 0x190 ( = 25%) with dQACC with 64 mAh
	 * LSB. Can make dPACC larger (ex 0xC80, 200%) and give dQAcc a smaller
	 * LSB (FullCapNom >> 4, LSB = 8 mAh). The equation can be written a
	 * (DesignCap * scale) >> 4 when writing the age-compensated value.
	 */

	/* TODO: fix for TaskPeriod == 351ms b/177099997 */
	fcrep = micro_amp_h_to_reg(new_capacity * 1000, ddata->rsense, lsb);
	dqacc = fcrep >> 4;
	dpacc = 0xc80;

	/* will not update if dqacc/dpacc is already in line */
	err = REGMAP_READ(map, MAX17X0X_DQACC, &temp);
	if (err == 0 && temp == dqacc) {
		err = REGMAP_READ(map, MAX17X0X_DPACC, &temp);
		if (err == 0 && temp == dpacc) {
			pr_debug("Fix capacity: same dqacc=0x%x dpacc=0x%x\n",
				 dqacc, dpacc);
			return 0;
		}
	}

	/* fast convergence, avoid ghost drain */
	err = REGMAP_READ(map, MAX17X0X_VFSOC, &vfsoc);
	if (err == 0)
		err = REGMAP_READ(map, MAX17X0X_REPSOC, &repsoc);
	if (err < 0) {
		pr_warn("Fix capacity: fcn=%d new=%d vfsoc=0x%x repsoc=0x%x (%d)\n",
			fullcapnom, new_capacity, vfsoc, repsoc, err);
		return err;
	}

	/* vfsoc/repsoc perc, lsb = 1/256 */
	mixcap = (((u32)vfsoc) * fcrep) / 25600;
	repcap = (((u32)repsoc) * fcrep) / 25600;

	for (loops = 0; loops < 3; loops++) {
		err = max1720x_update_capacity(map, mixcap, repcap, fcrep);
		if (err == -EIO || err > 0)
			break;

		/* arbitrary delay between attempts */
		msleep(MAX17201_FIXUP_UPDATE_DELAY_MS);
	}

	pr_info("Fix capacity: fixing caps retries=%d (%d)\n", loops, err);

	/* 3 loops suggested from vendor */
	for (loops = 0; loops < 3; loops++) {
		err = max1720x_update_compare(map, MAX17X0X_DQACC, dqacc, dpacc);
		if (err == -EIO || err > 0)
			break;

		/* arbitrary delay between attempts */
		msleep(MAX17201_FIXUP_UPDATE_DELAY_MS);
	}

	pr_info("Fix capacity: %d->%d, vfsoc=0x%x repsoc=0x%x fcrep=0x%x mixcap=0x%x repcap=0x%x ddqacc=0x%x dpacc=0x%x retries=%d (%d)\n",
		fullcapnom, new_capacity, vfsoc, repsoc, fcrep, mixcap, repcap,
		dqacc, dpacc, loops, err);

	/* TODO:  b/144630261 fix Google Capacity */

	return (loops == 3) ? -ETIMEDOUT : err;
}

/* Tempco and rcomp0 must remain within the following limits to avoid capacity
 * drift. Note that tempco has a hi and low limit (one byte).
 * (RCOMP0 > INI_RCOMP0 * 1.4) or (RCOMP0 < 0.7 * INI_RCOMP0)
 * (TempCoHot >INI_TempCoHot * 1.4) or (TempCoHot < 0.7 * INI_TempCoHot)
 * (TempCoCold >INI_TempCoCold * 1.4) or (TempCoCold < 0.7 * INI_TempCoCold)
 */
#define MAXIM_RCOMP0_LIM_HI	140
#define MAXIM_RCOMP0_LIM_LO	70
#define MAXIM_TEMPCO_LIM_HI	140
#define MAXIM_TEMPCO_LIM_LO	70

static u8 comp_check(int value, int scale, int lim_low, int lim_high)
{
	if ((value * scale) < lim_low) {
		value = lim_low / scale;
	} else if ((value * scale) > lim_high) {
		value = lim_high / scale;
		if (value > 0xff)
			value = 0xff;
	}

	return value;
}

/* NOT OK For MW A1+ */
static u16 max1720x_check_rcomp0(const struct max1720x_drift_data *ddata,
				      u16 rcomp0)
{
	const int ini_rcomp0_lob = ddata->ini_rcomp0 & 0xff;
	int rcomp0_lob = rcomp0 & 0xff;

	rcomp0_lob = comp_check(rcomp0_lob, 100,
				ini_rcomp0_lob * MAXIM_RCOMP0_LIM_LO,
				ini_rcomp0_lob * MAXIM_RCOMP0_LIM_HI);

	pr_debug("rcomp0=%x rcomp0_lob=%x->%x min=%x max=%x\n",
		 rcomp0, (rcomp0 & 0xff), rcomp0_lob,
		 (ini_rcomp0_lob * MAXIM_RCOMP0_LIM_LO) / 100,
		 (ini_rcomp0_lob * MAXIM_RCOMP0_LIM_HI) / 100);

	/* always write 0 to the high byte */
	return rcomp0_lob & 0xff;
}

/* MW A1+ */
static u16 max1720x_check_mw_rcomp0(const struct max1720x_drift_data *ddata,
				    u16 rcomp0)
{
	const int lim_low = ddata->ini_rcomp0 * MAXIM_RCOMP0_LIM_LO;
	const int lim_high = ddata->ini_rcomp0 * MAXIM_RCOMP0_LIM_HI;
	const int scale = 100;
	int value = rcomp0;

	if ((value * scale) < lim_low) {
		value = lim_low / scale;
	} else if ((value * scale) > lim_high) {
		value = lim_high / scale;
		if (value > 0xffff)
			value = 0xffff;
	}

	return value;
}

/* 0 no changes, >0 changes */
static bool max1720x_comp_check(u16 *new_rcomp0, u16 *new_tempco,
				const struct max1720x_drift_data *ddata)
{
	const u16 rcomp0 = *new_rcomp0;
	const u16 tempco = *new_tempco;
	const int ini_tc_lob = ddata->ini_tempco & 0xff;
	const int ini_tc_hib = (ddata->ini_tempco >> 8) & 0xff;
	int tc_hib = (tempco >> 8) & 0xff;
	int tc_lob = tempco & 0xff;
	bool fix_rcomp0 = false;

	if (ddata->algo_ver == MAX1720X_DA_VER_ORIG) {
		*new_rcomp0 = max1720x_check_rcomp0(ddata, rcomp0);

		fix_rcomp0 = (rcomp0 & 0xff) != *new_rcomp0;
	} else if (ddata->algo_ver == MAX1720X_DA_VER_MWA1) {
		if (*new_rcomp0 < 0x100)
			*new_rcomp0 = *new_rcomp0 << 4;
		*new_rcomp0 = max1720x_check_mw_rcomp0(ddata, *new_rcomp0);

		fix_rcomp0 = rcomp0 != *new_rcomp0;
	}

	tc_lob = comp_check(tc_lob, 100, ini_tc_lob * MAXIM_TEMPCO_LIM_LO,
			    ini_tc_lob * MAXIM_TEMPCO_LIM_HI);
	tc_hib = comp_check(tc_hib, 100, ini_tc_hib * MAXIM_TEMPCO_LIM_LO,
			    ini_tc_hib * MAXIM_TEMPCO_LIM_HI);

	pr_debug("tempco=%x tempco_lob=%x->%x min=%x max=%x, tempco_hib=%x->%x min=%x max=%x\n",
		 tempco, tempco & 0xff, tc_lob,
		 (ini_tc_lob * MAXIM_TEMPCO_LIM_LO) / 100,
		 (ini_tc_lob * MAXIM_TEMPCO_LIM_HI) / 100,
		 (tempco >> 8) & 0xff, tc_hib,
		 (ini_tc_hib * MAXIM_TEMPCO_LIM_LO) / 100,
		 (ini_tc_hib * MAXIM_TEMPCO_LIM_HI) / 100);

	*new_tempco = (tc_hib << 8) | (tc_lob);

	/* fix for MW A1+ */
	return fix_rcomp0 || (tempco != *new_tempco);
}


/* fix rcomp0 and tempco */
int max1720x_fixup_comp(struct max1720x_drift_data *ddata,
			struct max17x0x_regmap *map,
			int plugged)
{
	u16 new_rcomp0, new_tempco, data[2] = { 0 };
	int err, loops;

	if (ddata->ini_rcomp0 == -1 || ddata->ini_tempco == -1 ||
	    ALGO_VER_CHECK(ddata->algo_ver))
		return 0;

	err = regmap_raw_read(map->regmap, MAX17X0X_RCOMP0, data,
			      sizeof(data));
	if (err < 0)
		return -EIO;

	new_rcomp0 = data[0];
	new_tempco = data[1];

	err = max1720x_comp_check(&new_rcomp0, &new_tempco, ddata);
	pr_debug("rcomp0=0x%x tempco=0x%x (%d)\n", data[0], data[1], err);
	if (err <= 0)
		return err;

	/* 3 loops suggested from vendor */
	for (loops = 0; loops < 3; loops++) {

		err = max1720x_update_compare(map, MAX17X0X_RCOMP0,
					      new_rcomp0, new_tempco);
		if (err == -EIO || err > 0)
			break;

		/* arbitrary delay between attempts */
		msleep(MAX17201_FIXUP_UPDATE_DELAY_MS);
	}

	pr_info("Fix rcomp0=0x%x->0x%x tempco:0x%x->0x%x, retries=%d, (%d)\n",
		data[0], new_rcomp0, data[1], new_tempco, loops, err);

	return (loops == 3) ? -ETIMEDOUT : err;
}