blob: 2be591c0aabe9429675249d1c6ea638a739d9736 [file] [log] [blame]
Xiaozhe Shi5ee95192012-09-12 15:16:34 -07001/* Copyright (c) 2012, The Linux Foundation. All rights reserved.
2 *
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License version 2 and
5 * only version 2 as published by the Free Software Foundation.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 */
12
13#define pr_fmt(fmt) "%s: " fmt, __func__
14
15#include <linux/module.h>
16#include <linux/mfd/pm8xxx/batterydata-lib.h>
17
18int linear_interpolate(int y0, int x0, int y1, int x1, int x)
19{
20 if (y0 == y1 || x == x0)
21 return y0;
22 if (x1 == x0 || x == x1)
23 return y1;
24
25 return y0 + ((y1 - y0) * (x - x0) / (x1 - x0));
26}
27
28int is_between(int left, int right, int value)
29{
30 if (left >= right && left >= value && value >= right)
31 return 1;
32 if (left <= right && left <= value && value <= right)
33 return 1;
34 return 0;
35}
36
37static int interpolate_single_lut(struct single_row_lut *lut, int x)
38{
39 int i, result;
40
41 if (x < lut->x[0]) {
42 pr_debug("x %d less than known range return y = %d lut = %pS\n",
43 x, lut->y[0], lut);
44 return lut->y[0];
45 }
46 if (x > lut->x[lut->cols - 1]) {
47 pr_debug("x %d more than known range return y = %d lut = %pS\n",
48 x, lut->y[lut->cols - 1], lut);
49 return lut->y[lut->cols - 1];
50 }
51
52 for (i = 0; i < lut->cols; i++)
53 if (x <= lut->x[i])
54 break;
55 if (x == lut->x[i]) {
56 result = lut->y[i];
57 } else {
58 result = linear_interpolate(
59 lut->y[i - 1],
60 lut->x[i - 1],
61 lut->y[i],
62 lut->x[i],
63 x);
64 }
65 return result;
66}
67
68int interpolate_fcc(struct single_row_lut *fcc_temp_lut, int batt_temp)
69{
70 /* batt_temp is in tenths of degC - convert it to degC for lookups */
71 batt_temp = batt_temp/10;
72 return interpolate_single_lut(fcc_temp_lut, batt_temp);
73}
74
75int interpolate_scalingfactor_fcc(struct single_row_lut *fcc_sf_lut,
76 int cycles)
77{
78 /*
79 * sf table could be null when no battery aging data is available, in
80 * that case return 100%
81 */
82 if (fcc_sf_lut)
83 return interpolate_single_lut(fcc_sf_lut, cycles);
84 else
85 return 100;
86}
87
88int interpolate_scalingfactor(struct sf_lut *sf_lut, int row_entry, int pc)
89{
90 int i, scalefactorrow1, scalefactorrow2, scalefactor, rows, cols;
91 int row1 = 0;
92 int row2 = 0;
93
94 /*
95 * sf table could be null when no battery aging data is available, in
96 * that case return 100%
97 */
98 if (!sf_lut)
99 return 100;
100
101 rows = sf_lut->rows;
102 cols = sf_lut->cols;
103 if (pc > sf_lut->percent[0]) {
104 pr_debug("pc %d greater than known pc ranges for sfd\n", pc);
105 row1 = 0;
106 row2 = 0;
107 }
108 if (pc < sf_lut->percent[rows - 1]) {
109 pr_debug("pc %d less than known pc ranges for sf\n", pc);
110 row1 = rows - 1;
111 row2 = rows - 1;
112 }
113 for (i = 0; i < rows; i++) {
114 if (pc == sf_lut->percent[i]) {
115 row1 = i;
116 row2 = i;
117 break;
118 }
119 if (pc > sf_lut->percent[i]) {
120 row1 = i - 1;
121 row2 = i;
122 break;
123 }
124 }
125
126 if (row_entry < sf_lut->row_entries[0])
127 row_entry = sf_lut->row_entries[0];
128 if (row_entry > sf_lut->row_entries[cols - 1])
129 row_entry = sf_lut->row_entries[cols - 1];
130
131 for (i = 0; i < cols; i++)
132 if (row_entry <= sf_lut->row_entries[i])
133 break;
134 if (row_entry == sf_lut->row_entries[i]) {
135 scalefactor = linear_interpolate(
136 sf_lut->sf[row1][i],
137 sf_lut->percent[row1],
138 sf_lut->sf[row2][i],
139 sf_lut->percent[row2],
140 pc);
141 return scalefactor;
142 }
143
144 scalefactorrow1 = linear_interpolate(
145 sf_lut->sf[row1][i - 1],
146 sf_lut->row_entries[i - 1],
147 sf_lut->sf[row1][i],
148 sf_lut->row_entries[i],
149 row_entry);
150
151 scalefactorrow2 = linear_interpolate(
152 sf_lut->sf[row2][i - 1],
153 sf_lut->row_entries[i - 1],
154 sf_lut->sf[row2][i],
155 sf_lut->row_entries[i],
156 row_entry);
157
158 scalefactor = linear_interpolate(
159 scalefactorrow1,
160 sf_lut->percent[row1],
161 scalefactorrow2,
162 sf_lut->percent[row2],
163 pc);
164
165 return scalefactor;
166}
167
168/* get ocv given a soc -- reverse lookup */
169int interpolate_ocv(struct pc_temp_ocv_lut *pc_temp_ocv,
170 int batt_temp_degc, int pc)
171{
172 int i, ocvrow1, ocvrow2, ocv, rows, cols;
173 int row1 = 0;
174 int row2 = 0;
175
176 rows = pc_temp_ocv->rows;
177 cols = pc_temp_ocv->cols;
178 if (pc > pc_temp_ocv->percent[0]) {
179 pr_debug("pc %d greater than known pc ranges for sfd\n", pc);
180 row1 = 0;
181 row2 = 0;
182 }
183 if (pc < pc_temp_ocv->percent[rows - 1]) {
184 pr_debug("pc %d less than known pc ranges for sf\n", pc);
185 row1 = rows - 1;
186 row2 = rows - 1;
187 }
188 for (i = 0; i < rows; i++) {
189 if (pc == pc_temp_ocv->percent[i]) {
190 row1 = i;
191 row2 = i;
192 break;
193 }
194 if (pc > pc_temp_ocv->percent[i]) {
195 row1 = i - 1;
196 row2 = i;
197 break;
198 }
199 }
200
201 if (batt_temp_degc < pc_temp_ocv->temp[0])
202 batt_temp_degc = pc_temp_ocv->temp[0];
203 if (batt_temp_degc > pc_temp_ocv->temp[cols - 1])
204 batt_temp_degc = pc_temp_ocv->temp[cols - 1];
205
206 for (i = 0; i < cols; i++)
207 if (batt_temp_degc <= pc_temp_ocv->temp[i])
208 break;
209 if (batt_temp_degc == pc_temp_ocv->temp[i]) {
210 ocv = linear_interpolate(
211 pc_temp_ocv->ocv[row1][i],
212 pc_temp_ocv->percent[row1],
213 pc_temp_ocv->ocv[row2][i],
214 pc_temp_ocv->percent[row2],
215 pc);
216 return ocv;
217 }
218
219 ocvrow1 = linear_interpolate(
220 pc_temp_ocv->ocv[row1][i - 1],
221 pc_temp_ocv->temp[i - 1],
222 pc_temp_ocv->ocv[row1][i],
223 pc_temp_ocv->temp[i],
224 batt_temp_degc);
225
226 ocvrow2 = linear_interpolate(
227 pc_temp_ocv->ocv[row2][i - 1],
228 pc_temp_ocv->temp[i - 1],
229 pc_temp_ocv->ocv[row2][i],
230 pc_temp_ocv->temp[i],
231 batt_temp_degc);
232
233 ocv = linear_interpolate(
234 ocvrow1,
235 pc_temp_ocv->percent[row1],
236 ocvrow2,
237 pc_temp_ocv->percent[row2],
238 pc);
239
240 return ocv;
241}
242
243int interpolate_pc(struct pc_temp_ocv_lut *pc_temp_ocv,
244 int batt_temp_degc, int ocv)
245{
246 int i, j, pcj, pcj_minus_one, pc;
247 int rows = pc_temp_ocv->rows;
248 int cols = pc_temp_ocv->cols;
249
250 if (batt_temp_degc < pc_temp_ocv->temp[0]) {
251 pr_debug("batt_temp %d < known temp range\n", batt_temp_degc);
252 batt_temp_degc = pc_temp_ocv->temp[0];
253 }
254
255 if (batt_temp_degc > pc_temp_ocv->temp[cols - 1]) {
256 pr_debug("batt_temp %d > known temp range\n", batt_temp_degc);
257 batt_temp_degc = pc_temp_ocv->temp[cols - 1];
258 }
259
260 for (j = 0; j < cols; j++)
261 if (batt_temp_degc <= pc_temp_ocv->temp[j])
262 break;
263 if (batt_temp_degc == pc_temp_ocv->temp[j]) {
264 /* found an exact match for temp in the table */
265 if (ocv >= pc_temp_ocv->ocv[0][j])
266 return pc_temp_ocv->percent[0];
267 if (ocv <= pc_temp_ocv->ocv[rows - 1][j])
268 return pc_temp_ocv->percent[rows - 1];
269 for (i = 0; i < rows; i++) {
270 if (ocv >= pc_temp_ocv->ocv[i][j]) {
271 if (ocv == pc_temp_ocv->ocv[i][j])
272 return pc_temp_ocv->percent[i];
273 pc = linear_interpolate(
274 pc_temp_ocv->percent[i],
275 pc_temp_ocv->ocv[i][j],
276 pc_temp_ocv->percent[i - 1],
277 pc_temp_ocv->ocv[i - 1][j],
278 ocv);
279 return pc;
280 }
281 }
282 }
283
284 /*
285 * batt_temp_degc is within temperature for
286 * column j-1 and j
287 */
288 if (ocv >= pc_temp_ocv->ocv[0][j])
289 return pc_temp_ocv->percent[0];
290 if (ocv <= pc_temp_ocv->ocv[rows - 1][j - 1])
291 return pc_temp_ocv->percent[rows - 1];
292
293 pcj_minus_one = 0;
294 pcj = 0;
295 for (i = 0; i < rows-1; i++) {
296 if (pcj == 0
297 && is_between(pc_temp_ocv->ocv[i][j],
298 pc_temp_ocv->ocv[i+1][j], ocv)) {
299 pcj = linear_interpolate(
300 pc_temp_ocv->percent[i],
301 pc_temp_ocv->ocv[i][j],
302 pc_temp_ocv->percent[i + 1],
303 pc_temp_ocv->ocv[i+1][j],
304 ocv);
305 }
306
307 if (pcj_minus_one == 0
308 && is_between(pc_temp_ocv->ocv[i][j-1],
309 pc_temp_ocv->ocv[i+1][j-1], ocv)) {
310 pcj_minus_one = linear_interpolate(
311 pc_temp_ocv->percent[i],
312 pc_temp_ocv->ocv[i][j-1],
313 pc_temp_ocv->percent[i + 1],
314 pc_temp_ocv->ocv[i+1][j-1],
315 ocv);
316 }
317
318 if (pcj && pcj_minus_one) {
319 pc = linear_interpolate(
320 pcj_minus_one,
321 pc_temp_ocv->temp[j-1],
322 pcj,
323 pc_temp_ocv->temp[j],
324 batt_temp_degc);
325 return pc;
326 }
327 }
328
329 if (pcj)
330 return pcj;
331
332 if (pcj_minus_one)
333 return pcj_minus_one;
334
335 pr_debug("%d ocv wasn't found for temp %d in the LUT returning 100%%\n",
336 ocv, batt_temp_degc);
337 return 100;
338}