494 lines
12 KiB
C
494 lines
12 KiB
C
/* Copyright (c) 2012-2014, The Linux Foundation. All rights reserved.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 and
|
|
* only version 2 as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*/
|
|
|
|
#define pr_fmt(fmt) "%s: " fmt, __func__
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/batterydata-lib.h>
|
|
|
|
int linear_interpolate(int y0, int x0, int y1, int x1, int x)
|
|
{
|
|
if (y0 == y1 || x == x0)
|
|
return y0;
|
|
if (x1 == x0 || x == x1)
|
|
return y1;
|
|
|
|
return y0 + ((y1 - y0) * (x - x0) / (x1 - x0));
|
|
}
|
|
|
|
static int interpolate_single_lut_scaled(struct single_row_lut *lut,
|
|
int x, int scale)
|
|
{
|
|
int i, result;
|
|
|
|
if (x < lut->x[0] * scale) {
|
|
pr_debug("x %d less than known range return y = %d lut = %pS\n",
|
|
x, lut->y[0], lut);
|
|
return lut->y[0];
|
|
}
|
|
if (x > lut->x[lut->cols - 1] * scale) {
|
|
pr_debug("x %d more than known range return y = %d lut = %pS\n",
|
|
x, lut->y[lut->cols - 1], lut);
|
|
return lut->y[lut->cols - 1];
|
|
}
|
|
|
|
for (i = 0; i < lut->cols; i++)
|
|
if (x <= lut->x[i] * scale)
|
|
break;
|
|
if (x == lut->x[i] * scale) {
|
|
result = lut->y[i];
|
|
} else {
|
|
result = linear_interpolate(
|
|
lut->y[i - 1],
|
|
lut->x[i - 1] * scale,
|
|
lut->y[i],
|
|
lut->x[i] * scale,
|
|
x);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
int interpolate_fcc(struct single_row_lut *fcc_temp_lut, int batt_temp)
|
|
{
|
|
return interpolate_single_lut_scaled(fcc_temp_lut,
|
|
batt_temp,
|
|
DEGC_SCALE);
|
|
}
|
|
|
|
int interpolate_scalingfactor_fcc(struct single_row_lut *fcc_sf_lut,
|
|
int cycles)
|
|
{
|
|
/*
|
|
* sf table could be null when no battery aging data is available, in
|
|
* that case return 100%
|
|
*/
|
|
if (fcc_sf_lut)
|
|
return interpolate_single_lut_scaled(fcc_sf_lut, cycles, 1);
|
|
else
|
|
return 100;
|
|
}
|
|
|
|
int interpolate_scalingfactor(struct sf_lut *sf_lut, int row_entry, int pc)
|
|
{
|
|
int i, scalefactorrow1, scalefactorrow2, scalefactor, rows, cols;
|
|
int row1 = 0;
|
|
int row2 = 0;
|
|
|
|
/*
|
|
* sf table could be null when no battery aging data is available, in
|
|
* that case return 100%
|
|
*/
|
|
if (!sf_lut)
|
|
return 100;
|
|
|
|
rows = sf_lut->rows;
|
|
cols = sf_lut->cols;
|
|
if (pc > sf_lut->percent[0]) {
|
|
pr_debug("pc %d greater than known pc ranges for sfd\n", pc);
|
|
row1 = 0;
|
|
row2 = 0;
|
|
} else if (pc < sf_lut->percent[rows - 1]) {
|
|
pr_debug("pc %d less than known pc ranges for sf\n", pc);
|
|
row1 = rows - 1;
|
|
row2 = rows - 1;
|
|
} else {
|
|
for (i = 0; i < rows; i++) {
|
|
if (pc == sf_lut->percent[i]) {
|
|
row1 = i;
|
|
row2 = i;
|
|
break;
|
|
}
|
|
if (pc > sf_lut->percent[i]) {
|
|
row1 = i - 1;
|
|
row2 = i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (row_entry < sf_lut->row_entries[0] * DEGC_SCALE)
|
|
row_entry = sf_lut->row_entries[0] * DEGC_SCALE;
|
|
if (row_entry > sf_lut->row_entries[cols - 1] * DEGC_SCALE)
|
|
row_entry = sf_lut->row_entries[cols - 1] * DEGC_SCALE;
|
|
|
|
for (i = 0; i < cols; i++)
|
|
if (row_entry <= sf_lut->row_entries[i] * DEGC_SCALE)
|
|
break;
|
|
if (row_entry == sf_lut->row_entries[i] * DEGC_SCALE) {
|
|
scalefactor = linear_interpolate(
|
|
sf_lut->sf[row1][i],
|
|
sf_lut->percent[row1],
|
|
sf_lut->sf[row2][i],
|
|
sf_lut->percent[row2],
|
|
pc);
|
|
return scalefactor;
|
|
}
|
|
|
|
scalefactorrow1 = linear_interpolate(
|
|
sf_lut->sf[row1][i - 1],
|
|
sf_lut->row_entries[i - 1] * DEGC_SCALE,
|
|
sf_lut->sf[row1][i],
|
|
sf_lut->row_entries[i] * DEGC_SCALE,
|
|
row_entry);
|
|
|
|
scalefactorrow2 = linear_interpolate(
|
|
sf_lut->sf[row2][i - 1],
|
|
sf_lut->row_entries[i - 1] * DEGC_SCALE,
|
|
sf_lut->sf[row2][i],
|
|
sf_lut->row_entries[i] * DEGC_SCALE,
|
|
row_entry);
|
|
|
|
scalefactor = linear_interpolate(
|
|
scalefactorrow1,
|
|
sf_lut->percent[row1],
|
|
scalefactorrow2,
|
|
sf_lut->percent[row2],
|
|
pc);
|
|
|
|
return scalefactor;
|
|
}
|
|
|
|
/* get ocv given a soc -- reverse lookup */
|
|
int interpolate_ocv(struct pc_temp_ocv_lut *pc_temp_ocv,
|
|
int batt_temp, int pc)
|
|
{
|
|
int i, ocvrow1, ocvrow2, ocv, rows, cols;
|
|
int row1 = 0;
|
|
int row2 = 0;
|
|
|
|
rows = pc_temp_ocv->rows;
|
|
cols = pc_temp_ocv->cols;
|
|
if (pc > pc_temp_ocv->percent[0]) {
|
|
pr_debug("pc %d greater than known pc ranges for sfd\n", pc);
|
|
row1 = 0;
|
|
row2 = 0;
|
|
} else if (pc < pc_temp_ocv->percent[rows - 1]) {
|
|
pr_debug("pc %d less than known pc ranges for sf\n", pc);
|
|
row1 = rows - 1;
|
|
row2 = rows - 1;
|
|
} else {
|
|
for (i = 0; i < rows; i++) {
|
|
if (pc == pc_temp_ocv->percent[i]) {
|
|
row1 = i;
|
|
row2 = i;
|
|
break;
|
|
}
|
|
if (pc > pc_temp_ocv->percent[i]) {
|
|
row1 = i - 1;
|
|
row2 = i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (batt_temp < pc_temp_ocv->temp[0] * DEGC_SCALE)
|
|
batt_temp = pc_temp_ocv->temp[0] * DEGC_SCALE;
|
|
if (batt_temp > pc_temp_ocv->temp[cols - 1] * DEGC_SCALE)
|
|
batt_temp = pc_temp_ocv->temp[cols - 1] * DEGC_SCALE;
|
|
|
|
for (i = 0; i < cols; i++)
|
|
if (batt_temp <= pc_temp_ocv->temp[i] * DEGC_SCALE)
|
|
break;
|
|
if (batt_temp == pc_temp_ocv->temp[i] * DEGC_SCALE) {
|
|
ocv = linear_interpolate(
|
|
pc_temp_ocv->ocv[row1][i],
|
|
pc_temp_ocv->percent[row1],
|
|
pc_temp_ocv->ocv[row2][i],
|
|
pc_temp_ocv->percent[row2],
|
|
pc);
|
|
return ocv;
|
|
}
|
|
|
|
ocvrow1 = linear_interpolate(
|
|
pc_temp_ocv->ocv[row1][i - 1],
|
|
pc_temp_ocv->temp[i - 1] * DEGC_SCALE,
|
|
pc_temp_ocv->ocv[row1][i],
|
|
pc_temp_ocv->temp[i] * DEGC_SCALE,
|
|
batt_temp);
|
|
|
|
ocvrow2 = linear_interpolate(
|
|
pc_temp_ocv->ocv[row2][i - 1],
|
|
pc_temp_ocv->temp[i - 1] * DEGC_SCALE,
|
|
pc_temp_ocv->ocv[row2][i],
|
|
pc_temp_ocv->temp[i] * DEGC_SCALE,
|
|
batt_temp);
|
|
|
|
ocv = linear_interpolate(
|
|
ocvrow1,
|
|
pc_temp_ocv->percent[row1],
|
|
ocvrow2,
|
|
pc_temp_ocv->percent[row2],
|
|
pc);
|
|
|
|
return ocv;
|
|
}
|
|
|
|
int interpolate_pc(struct pc_temp_ocv_lut *pc_temp_ocv,
|
|
int batt_temp, int ocv)
|
|
{
|
|
int i, j, pcj, pcj_minus_one, pc;
|
|
int rows = pc_temp_ocv->rows;
|
|
int cols = pc_temp_ocv->cols;
|
|
|
|
if (batt_temp < pc_temp_ocv->temp[0] * DEGC_SCALE) {
|
|
pr_debug("batt_temp %d < known temp range\n", batt_temp);
|
|
batt_temp = pc_temp_ocv->temp[0] * DEGC_SCALE;
|
|
}
|
|
|
|
if (batt_temp > pc_temp_ocv->temp[cols - 1] * DEGC_SCALE) {
|
|
pr_debug("batt_temp %d > known temp range\n", batt_temp);
|
|
batt_temp = pc_temp_ocv->temp[cols - 1] * DEGC_SCALE;
|
|
}
|
|
|
|
for (j = 0; j < cols; j++)
|
|
if (batt_temp <= pc_temp_ocv->temp[j] * DEGC_SCALE)
|
|
break;
|
|
if (batt_temp == pc_temp_ocv->temp[j] * DEGC_SCALE) {
|
|
/* found an exact match for temp in the table */
|
|
if (ocv >= pc_temp_ocv->ocv[0][j])
|
|
return pc_temp_ocv->percent[0];
|
|
if (ocv <= pc_temp_ocv->ocv[rows - 1][j])
|
|
return pc_temp_ocv->percent[rows - 1];
|
|
for (i = 0; i < rows; i++) {
|
|
if (ocv >= pc_temp_ocv->ocv[i][j]) {
|
|
if (ocv == pc_temp_ocv->ocv[i][j])
|
|
return pc_temp_ocv->percent[i];
|
|
pc = linear_interpolate(
|
|
pc_temp_ocv->percent[i],
|
|
pc_temp_ocv->ocv[i][j],
|
|
pc_temp_ocv->percent[i - 1],
|
|
pc_temp_ocv->ocv[i - 1][j],
|
|
ocv);
|
|
return pc;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* batt_temp is within temperature for
|
|
* column j-1 and j
|
|
*/
|
|
if (ocv >= pc_temp_ocv->ocv[0][j])
|
|
return pc_temp_ocv->percent[0];
|
|
if (ocv <= pc_temp_ocv->ocv[rows - 1][j - 1])
|
|
return pc_temp_ocv->percent[rows - 1];
|
|
|
|
pcj_minus_one = 0;
|
|
pcj = 0;
|
|
for (i = 0; i < rows-1; i++) {
|
|
if (pcj == 0
|
|
&& is_between(pc_temp_ocv->ocv[i][j],
|
|
pc_temp_ocv->ocv[i+1][j], ocv)) {
|
|
pcj = linear_interpolate(
|
|
pc_temp_ocv->percent[i],
|
|
pc_temp_ocv->ocv[i][j],
|
|
pc_temp_ocv->percent[i + 1],
|
|
pc_temp_ocv->ocv[i+1][j],
|
|
ocv);
|
|
}
|
|
|
|
if (pcj_minus_one == 0
|
|
&& is_between(pc_temp_ocv->ocv[i][j-1],
|
|
pc_temp_ocv->ocv[i+1][j-1], ocv)) {
|
|
pcj_minus_one = linear_interpolate(
|
|
pc_temp_ocv->percent[i],
|
|
pc_temp_ocv->ocv[i][j-1],
|
|
pc_temp_ocv->percent[i + 1],
|
|
pc_temp_ocv->ocv[i+1][j-1],
|
|
ocv);
|
|
}
|
|
|
|
if (pcj && pcj_minus_one) {
|
|
pc = linear_interpolate(
|
|
pcj_minus_one,
|
|
pc_temp_ocv->temp[j-1] * DEGC_SCALE,
|
|
pcj,
|
|
pc_temp_ocv->temp[j] * DEGC_SCALE,
|
|
batt_temp);
|
|
return pc;
|
|
}
|
|
}
|
|
|
|
if (pcj)
|
|
return pcj;
|
|
|
|
if (pcj_minus_one)
|
|
return pcj_minus_one;
|
|
|
|
pr_debug("%d ocv wasn't found for temp %d in the LUT returning 100%%\n",
|
|
ocv, batt_temp);
|
|
return 100;
|
|
}
|
|
|
|
int interpolate_slope(struct pc_temp_ocv_lut *pc_temp_ocv,
|
|
int batt_temp, int pc)
|
|
{
|
|
int i, ocvrow1, ocvrow2, rows, cols;
|
|
int row1 = 0;
|
|
int row2 = 0;
|
|
int slope;
|
|
|
|
rows = pc_temp_ocv->rows;
|
|
cols = pc_temp_ocv->cols;
|
|
if (pc >= pc_temp_ocv->percent[0]) {
|
|
pr_debug("pc %d >= max pc range - use the slope at pc=%d\n",
|
|
pc, pc_temp_ocv->percent[0]);
|
|
row1 = 0;
|
|
row2 = 1;
|
|
} else if (pc <= pc_temp_ocv->percent[rows - 1]) {
|
|
pr_debug("pc %d is <= min pc range - use the slope at pc=%d\n",
|
|
pc, pc_temp_ocv->percent[rows - 1]);
|
|
row1 = rows - 2;
|
|
row2 = rows - 1;
|
|
} else {
|
|
for (i = 0; i < rows; i++) {
|
|
if (pc == pc_temp_ocv->percent[i]) {
|
|
row1 = i - 1;
|
|
row2 = i;
|
|
break;
|
|
}
|
|
if (pc > pc_temp_ocv->percent[i]) {
|
|
row1 = i - 1;
|
|
row2 = i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (batt_temp < pc_temp_ocv->temp[0] * DEGC_SCALE)
|
|
batt_temp = pc_temp_ocv->temp[0] * DEGC_SCALE;
|
|
if (batt_temp > pc_temp_ocv->temp[cols - 1] * DEGC_SCALE)
|
|
batt_temp = pc_temp_ocv->temp[cols - 1] * DEGC_SCALE;
|
|
|
|
for (i = 0; i < cols; i++)
|
|
if (batt_temp <= pc_temp_ocv->temp[i] * DEGC_SCALE)
|
|
break;
|
|
|
|
if (batt_temp == pc_temp_ocv->temp[i] * DEGC_SCALE) {
|
|
slope = (pc_temp_ocv->ocv[row1][i] -
|
|
pc_temp_ocv->ocv[row2][i]);
|
|
if (slope <= 0) {
|
|
pr_warn("Slope=%d for pc=%d, using 1\n", slope, pc);
|
|
slope = 1;
|
|
}
|
|
slope *= 1000;
|
|
slope /= (pc_temp_ocv->percent[row1] -
|
|
pc_temp_ocv->percent[row2]);
|
|
return slope;
|
|
}
|
|
ocvrow1 = linear_interpolate(
|
|
pc_temp_ocv->ocv[row1][i - 1],
|
|
pc_temp_ocv->temp[i - 1] * DEGC_SCALE,
|
|
pc_temp_ocv->ocv[row1][i],
|
|
pc_temp_ocv->temp[i] * DEGC_SCALE,
|
|
batt_temp);
|
|
|
|
ocvrow2 = linear_interpolate(
|
|
pc_temp_ocv->ocv[row2][i - 1],
|
|
pc_temp_ocv->temp[i - 1] * DEGC_SCALE,
|
|
pc_temp_ocv->ocv[row2][i],
|
|
pc_temp_ocv->temp[i] * DEGC_SCALE,
|
|
batt_temp);
|
|
|
|
slope = (ocvrow1 - ocvrow2);
|
|
if (slope <= 0) {
|
|
pr_warn("Slope=%d for pc=%d, using 1\n", slope, pc);
|
|
slope = 1;
|
|
}
|
|
slope *= 1000;
|
|
slope /= (pc_temp_ocv->percent[row1] - pc_temp_ocv->percent[row2]);
|
|
|
|
return slope;
|
|
}
|
|
|
|
|
|
int interpolate_acc(struct ibat_temp_acc_lut *ibat_acc_lut,
|
|
int batt_temp, int ibat)
|
|
{
|
|
int i, accrow1, accrow2, rows, cols;
|
|
int row1 = 0;
|
|
int row2 = 0;
|
|
int acc;
|
|
|
|
rows = ibat_acc_lut->rows;
|
|
cols = ibat_acc_lut->cols;
|
|
|
|
if (ibat > ibat_acc_lut->ibat[rows - 1]) {
|
|
pr_debug("ibatt(%d) > max range(%d)\n", ibat,
|
|
ibat_acc_lut->ibat[rows - 1]);
|
|
row1 = rows - 1;
|
|
row2 = rows - 2;
|
|
} else if (ibat < ibat_acc_lut->ibat[0]) {
|
|
pr_debug("ibatt(%d) < max range(%d)\n", ibat,
|
|
ibat_acc_lut->ibat[0]);
|
|
row1 = 0;
|
|
row2 = 0;
|
|
} else {
|
|
for (i = 0; i < rows; i++) {
|
|
if (ibat == ibat_acc_lut->ibat[i]) {
|
|
row1 = i;
|
|
row2 = i;
|
|
break;
|
|
}
|
|
if (ibat < ibat_acc_lut->ibat[i]) {
|
|
row1 = i;
|
|
row2 = i - 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (batt_temp < ibat_acc_lut->temp[0] * DEGC_SCALE)
|
|
batt_temp = ibat_acc_lut->temp[0] * DEGC_SCALE;
|
|
if (batt_temp > ibat_acc_lut->temp[cols - 1] * DEGC_SCALE)
|
|
batt_temp = ibat_acc_lut->temp[cols - 1] * DEGC_SCALE;
|
|
|
|
for (i = 0; i < cols; i++)
|
|
if (batt_temp <= ibat_acc_lut->temp[i] * DEGC_SCALE)
|
|
break;
|
|
|
|
if (batt_temp == (ibat_acc_lut->temp[i] * DEGC_SCALE)) {
|
|
acc = linear_interpolate(
|
|
ibat_acc_lut->acc[row1][i],
|
|
ibat_acc_lut->ibat[row1],
|
|
ibat_acc_lut->acc[row2][i],
|
|
ibat_acc_lut->ibat[row2],
|
|
ibat);
|
|
return acc;
|
|
}
|
|
|
|
accrow1 = linear_interpolate(
|
|
ibat_acc_lut->acc[row1][i - 1],
|
|
ibat_acc_lut->temp[i - 1] * DEGC_SCALE,
|
|
ibat_acc_lut->acc[row1][i],
|
|
ibat_acc_lut->temp[i] * DEGC_SCALE,
|
|
batt_temp);
|
|
|
|
accrow2 = linear_interpolate(
|
|
ibat_acc_lut->acc[row2][i - 1],
|
|
ibat_acc_lut->temp[i - 1] * DEGC_SCALE,
|
|
ibat_acc_lut->acc[row2][i],
|
|
ibat_acc_lut->temp[i] * DEGC_SCALE,
|
|
batt_temp);
|
|
|
|
acc = linear_interpolate(accrow1,
|
|
ibat_acc_lut->ibat[row1],
|
|
accrow2,
|
|
ibat_acc_lut->ibat[row2],
|
|
ibat);
|
|
|
|
if (acc < 0)
|
|
acc = 0;
|
|
|
|
return acc;
|
|
}
|