Xiaozhe Shi | 5ee9519 | 2012-09-12 15:16:34 -0700 | [diff] [blame] | 1 | /* 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 | |
| 18 | int 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 | |
| 28 | int 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 | |
| 37 | static 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 | |
| 68 | int 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 | |
| 75 | int 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 | |
| 88 | int 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 */ |
| 169 | int 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 | |
| 243 | int 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 | } |