power: pm8921-bms: Update the FCC learning algorithm
1. Add configurable parameters to
- enable/disable fcc learning
- minimum soc to start FCC learning
- minimum ocv (pc) to start FCC learning
- minimum cycles to update fcc vs temp. table
2. New FCC is calculated using the cc count-
FCC_NEW = (cc_end - cc_start) / (soc2 - soc1)
cc_end = CC count when charing ends
cc_start = CC count when charging starts
soc1, soc2 = starting and ending SOC_rbatt value
3. Add a new sysfs entries to update the learnt fcc values
to userspace. These values are restored back on
reboot.
CRs-Fixed: 417288
Change-Id: I0dc713d59ef9c45f96b9f0b5844252758bd09320
Signed-off-by: Anirudh Ghayal <aghayal@codeaurora.org>
Signed-off-by: Sridhar Gujje <sgujje@codeaurora.org>
diff --git a/arch/arm/mach-msm/board-8064-pmic.c b/arch/arm/mach-msm/board-8064-pmic.c
index 2d06031..90c612e 100644
--- a/arch/arm/mach-msm/board-8064-pmic.c
+++ b/arch/arm/mach-msm/board-8064-pmic.c
@@ -451,6 +451,10 @@
.high_ocv_correction_limit_uv = 50,
.low_ocv_correction_limit_uv = 100,
.hold_soc_est = 3,
+ .enable_fcc_learning = 1,
+ .min_fcc_learning_soc = 20,
+ .min_fcc_ocv_pc = 30,
+ .max_fcc_learning_samples = 5,
};
static struct pm8921_platform_data
diff --git a/arch/arm/mach-msm/board-8930-pmic.c b/arch/arm/mach-msm/board-8930-pmic.c
index 4f09a51..2f3b6a9 100644
--- a/arch/arm/mach-msm/board-8930-pmic.c
+++ b/arch/arm/mach-msm/board-8930-pmic.c
@@ -487,6 +487,10 @@
.high_ocv_correction_limit_uv = 50,
.low_ocv_correction_limit_uv = 100,
.hold_soc_est = 3,
+ .enable_fcc_learning = 1,
+ .min_fcc_learning_soc = 20,
+ .min_fcc_ocv_pc = 30,
+ .max_fcc_learning_samples = 5,
};
static struct pm8038_platform_data pm8038_platform_data __devinitdata = {
diff --git a/arch/arm/mach-msm/board-8960-pmic.c b/arch/arm/mach-msm/board-8960-pmic.c
index c87d966..8e758bf 100644
--- a/arch/arm/mach-msm/board-8960-pmic.c
+++ b/arch/arm/mach-msm/board-8960-pmic.c
@@ -438,6 +438,10 @@
.high_ocv_correction_limit_uv = 50,
.low_ocv_correction_limit_uv = 100,
.hold_soc_est = 3,
+ .enable_fcc_learning = 1,
+ .min_fcc_learning_soc = 20,
+ .min_fcc_ocv_pc = 30,
+ .max_fcc_learning_samples = 5,
};
#define PM8921_LC_LED_MAX_CURRENT 4 /* I = 4mA */
diff --git a/drivers/power/pm8921-bms.c b/drivers/power/pm8921-bms.c
index 0032ea8..66da98b 100644
--- a/drivers/power/pm8921-bms.c
+++ b/drivers/power/pm8921-bms.c
@@ -79,6 +79,14 @@
int last_good_ocv_uv;
};
+struct fcc_data {
+ int fcc_new;
+ int chargecycles;
+ int batt_temp;
+ int fcc_real;
+ int temp_real;
+};
+
/**
* struct pm8921_bms_chip -
* @bms_output_lock: lock to prevent concurrent bms reads
@@ -144,7 +152,16 @@
int pon_ocv_uv;
int last_cc_uah;
unsigned long tm_sec;
+
int enable_fcc_learning;
+ int min_fcc_learning_soc;
+ int min_fcc_ocv_pc;
+ int max_fcc_learning_samples;
+ struct fcc_data *fcc_table;
+ int fcc_new;
+ int start_real_soc;
+ int pc_at_start_charge;
+
int shutdown_soc;
int shutdown_iavg_ua;
struct delayed_work calculate_soc_delayed_work;
@@ -194,18 +211,28 @@
#define DEFAULT_OCV_MICROVOLTS 3900000
#define DEFAULT_CHARGE_CYCLES 0
+#define DELTA_FCC_PERCENT 5
+#define MIN_START_PERCENT_FOR_LEARNING 20
+#define MIN_START_OCV_PERCENT_FOR_LEARNING 30
+#define MAX_FCC_LEARNING_COUNT 5
+#define VALID_FCC_CHGCYL_RANGE 50
+
static int last_usb_cal_delta_uv = 1800;
module_param(last_usb_cal_delta_uv, int, 0644);
static int last_chargecycles = DEFAULT_CHARGE_CYCLES;
static int last_charge_increase;
+static int last_fcc_update_count;
+static int max_fcc_cycles = -EINVAL;
module_param(last_chargecycles, int, 0644);
module_param(last_charge_increase, int, 0644);
+module_param(last_fcc_update_count, int, 0644);
static int calculated_soc = -EINVAL;
static int last_soc = -EINVAL;
static int last_real_fcc_mah = -EINVAL;
static int last_real_fcc_batt_temp = -EINVAL;
+static int battery_removed;
static int pm8921_battery_gauge_alarm_notify(struct notifier_block *nb,
unsigned long status, void *unused);
@@ -214,20 +241,20 @@
.notifier_call = pm8921_battery_gauge_alarm_notify,
};
-static int bms_ops_set(const char *val, const struct kernel_param *kp)
+static int bms_ro_ops_set(const char *val, const struct kernel_param *kp)
{
- if (*(int *)kp->arg == -EINVAL)
- return param_set_int(val, kp);
- else
- return 0;
+ return -EINVAL;
}
static struct kernel_param_ops bms_param_ops = {
- .set = bms_ops_set,
+ .set = bms_ro_ops_set,
.get = param_get_int,
};
-
+/* Make last_soc as read only as it is already calculated from shutdown_soc */
module_param_cb(last_soc, &bms_param_ops, &last_soc, 0644);
+module_param_cb(battery_removed, &bms_param_ops, &battery_removed, 0644);
+module_param_cb(max_fcc_cycles, &bms_param_ops,
+ &max_fcc_cycles, 0644);
/*
* bms_fake_battery is set in setups where a battery emulator is used instead
@@ -245,11 +272,6 @@
static int bms_end_ocv_uv;
static int bms_end_cc_uah;
-static int bms_ro_ops_set(const char *val, const struct kernel_param *kp)
-{
- return -EINVAL;
-}
-
static struct kernel_param_ops bms_ro_param_ops = {
.set = bms_ro_ops_set,
.get = param_get_int,
@@ -267,6 +289,9 @@
struct single_row_lut *temp, *old;
int i, fcc, ratio;
+ if (!the_chip->enable_fcc_learning || battery_removed)
+ return;
+
if (!the_chip->fcc_temp_lut) {
pr_err("The static fcc lut table is NULL\n");
return;
@@ -301,6 +326,9 @@
{
int rc = 0;
+ if (battery_removed)
+ return rc;
+
if (last_real_fcc_mah == -EINVAL)
rc = param_set_int(val, kp);
if (rc) {
@@ -323,6 +351,9 @@
{
int rc = 0;
+ if (battery_removed)
+ return rc;
+
if (last_real_fcc_batt_temp == -EINVAL)
rc = param_set_int(val, kp);
if (rc) {
@@ -1552,33 +1583,6 @@
pr_debug("UUC = %uuAh\n", *unusable_charge_uah);
}
-static int calculate_real_fcc_uah(struct pm8921_bms_chip *chip,
- struct pm8921_soc_params *raw,
- int batt_temp, int chargecycles,
- int *ret_fcc_uah)
-{
- int fcc_uah, unusable_charge_uah;
- int remaining_charge_uah;
- int cc_uah;
- int real_fcc_uah;
- int rbatt;
- int iavg_ua;
-
- calculate_soc_params(chip, raw, batt_temp, chargecycles,
- &fcc_uah,
- &unusable_charge_uah,
- &remaining_charge_uah,
- &cc_uah,
- &rbatt,
- &iavg_ua);
-
- real_fcc_uah = remaining_charge_uah - cc_uah;
- *ret_fcc_uah = fcc_uah;
- pr_debug("real_fcc = %d, RC = %d CC = %d fcc = %d\n",
- real_fcc_uah, remaining_charge_uah, cc_uah, fcc_uah);
- return real_fcc_uah;
-}
-
int pm8921_bms_get_simultaneous_battery_voltage_and_current(int *ibat_ua,
int *vbat_uv)
{
@@ -2575,6 +2579,12 @@
/* store invalid soc */
pm8xxx_writeb(the_chip->dev->parent, TEMP_SOC_STORAGE, 0);
+ /* fcc learning cleanup */
+ if (the_chip->enable_fcc_learning) {
+ battery_removed = 1;
+ sysfs_notify(&the_chip->dev->kobj, NULL, "fcc_data");
+ }
+
/* UUC related data is left as is - use the same historical load avg */
update_power_supply(the_chip);
}
@@ -2598,6 +2608,14 @@
int calculate_soc = 0;
struct pm8921_bms_chip *chip = the_chip;
+ /* clean up the fcc learning table */
+ if (!the_chip)
+ the_chip->adjusted_fcc_temp_lut = NULL;
+ last_fcc_update_count = 0;
+ last_real_fcc_mah = -EINVAL;
+ last_real_fcc_batt_temp = -EINVAL;
+ battery_removed = 1;
+
pr_debug("Invalidating shutdown soc - the battery was removed\n");
if (shutdown_soc_invalid)
return;
@@ -2719,6 +2737,20 @@
return calculate_fcc_uah(the_chip, batt_temp, last_chargecycles);
}
EXPORT_SYMBOL_GPL(pm8921_bms_get_fcc);
+
+static void calculate_real_soc(struct pm8921_bms_chip *chip, int *soc,
+ int batt_temp, struct pm8921_soc_params *raw, int cc_uah)
+{
+ int fcc_uah = 0, rc_uah = 0;
+
+ fcc_uah = calculate_fcc_uah(chip, batt_temp, last_chargecycles);
+ rc_uah = calculate_remaining_charge_uah(chip, raw,
+ fcc_uah, batt_temp, last_chargecycles);
+ *soc = ((rc_uah - cc_uah) * 100) / fcc_uah;
+ pr_debug("fcc = %d, rc = %d, cc = %d Real SOC = %d\n",
+ fcc_uah, rc_uah, cc_uah, *soc);
+}
+
void pm8921_bms_charging_began(void)
{
struct pm8921_soc_params raw;
@@ -2742,12 +2774,123 @@
the_chip->soc_at_cv = -EINVAL;
the_chip->prev_chg_soc = -EINVAL;
+ if (the_chip->enable_fcc_learning) {
+ calculate_real_soc(the_chip, &the_chip->start_real_soc,
+ batt_temp, &raw, bms_start_cc_uah);
+ the_chip->pc_at_start_charge =
+ interpolate_pc(the_chip->pc_temp_ocv_lut, batt_temp,
+ bms_start_ocv_uv / 1000);
+ pr_debug("Start real soc = %d, start pc = %d\n",
+ the_chip->start_real_soc, the_chip->pc_at_start_charge);
+ }
+
pr_debug("start_percent = %u%%\n", the_chip->start_percent);
}
EXPORT_SYMBOL_GPL(pm8921_bms_charging_began);
-#define DELTA_FCC_PERCENT 3
-#define MIN_START_PERCENT_FOR_LEARNING 30
+static void invalidate_fcc(struct pm8921_bms_chip *chip)
+{
+ memset(chip->fcc_table, 0, chip->max_fcc_learning_samples *
+ sizeof(*(chip->fcc_table)));
+ last_fcc_update_count = 0;
+ chip->adjusted_fcc_temp_lut = NULL;
+ last_real_fcc_mah = -EINVAL;
+ last_real_fcc_batt_temp = -EINVAL;
+ last_chargecycles = 0;
+ last_charge_increase = 0;
+}
+
+static void update_fcc_table_for_temp(struct pm8921_bms_chip *chip,
+ int batt_temp_final)
+{
+ int i, fcc_t1, fcc_t2, fcc_final;
+ struct fcc_data *ft;
+
+ /* Interpolate all the FCC entries to the same temperature */
+ for (i = 0; i < chip->max_fcc_learning_samples; i++) {
+ ft = &chip->fcc_table[i];
+ if (ft->batt_temp == batt_temp_final)
+ continue;
+ fcc_t1 = interpolate_fcc(chip->fcc_temp_lut, ft->batt_temp);
+ fcc_t2 = interpolate_fcc(chip->fcc_temp_lut, batt_temp_final);
+ fcc_final = (ft->fcc_new / fcc_t1) * fcc_t2;
+ ft->fcc_new = fcc_final;
+ ft->batt_temp = batt_temp_final;
+ }
+}
+
+static void update_fcc_learning_table(struct pm8921_bms_chip *chip,
+ int fcc_uah, int new_fcc_uah, int chargecycles, int batt_temp)
+{
+ int i, temp_fcc_avg = 0, new_fcc_avg = 0, temp_fcc_delta = 0, count;
+ struct fcc_data *ft;
+
+ count = last_fcc_update_count % chip->max_fcc_learning_samples;
+ ft = &chip->fcc_table[count];
+ ft->fcc_new = ft->fcc_real = new_fcc_uah;
+ ft->batt_temp = ft->temp_real = batt_temp;
+ ft->chargecycles = chargecycles;
+ chip->fcc_new = new_fcc_uah;
+ last_fcc_update_count++;
+ /* update userspace with the new data */
+ sysfs_notify(&chip->dev->kobj, NULL, "fcc_data");
+
+ pr_debug("Updated fcc table. new_fcc=%d, chargecycle=%d, temp=%d fcc_update_count=%d\n",
+ new_fcc_uah, chargecycles, batt_temp, last_fcc_update_count);
+
+ if (last_fcc_update_count < chip->max_fcc_learning_samples) {
+ pr_debug("Not enough FCC samples. Current count = %d\n",
+ last_fcc_update_count);
+ return; /* Not enough samples to update fcc */
+ }
+
+ /* reject entries if they are > 50 chargecycles apart */
+ for (i = 0; i < chip->max_fcc_learning_samples; i++) {
+ if ((chip->fcc_table[i].chargecycles + VALID_FCC_CHGCYL_RANGE)
+ < chargecycles) {
+ pr_debug("Charge cycle too old (> %d cycles apart)\n",
+ VALID_FCC_CHGCYL_RANGE);
+ return; /* Samples old, > 50 cycles apart*/
+ }
+ }
+ /* update the fcc table for temperature difference*/
+ update_fcc_table_for_temp(chip, batt_temp);
+
+ /* Calculate the avg. and SD for all the fcc entries */
+ for (i = 0; i < chip->max_fcc_learning_samples; i++)
+ temp_fcc_avg += chip->fcc_table[i].fcc_new;
+
+ temp_fcc_avg /= chip->max_fcc_learning_samples;
+ temp_fcc_delta = div_u64(temp_fcc_avg * DELTA_FCC_PERCENT, 100);
+
+ /* fix the fcc if its an outlier i.e. > 5% of the average */
+ for (i = 0; i < chip->max_fcc_learning_samples; i++) {
+ ft = &chip->fcc_table[i];
+ if (abs(ft->fcc_new - temp_fcc_avg) > temp_fcc_delta)
+ ft->fcc_new = temp_fcc_avg;
+ new_fcc_avg += ft->fcc_new;
+ }
+ new_fcc_avg /= chip->max_fcc_learning_samples;
+
+ last_real_fcc_mah = new_fcc_avg/1000;
+ last_real_fcc_batt_temp = batt_temp;
+
+ pr_debug("FCC update: last_real_fcc_mah=%d, last_real_fcc_batt_temp=%d\n",
+ new_fcc_avg, batt_temp);
+ readjust_fcc_table();
+}
+
+static bool is_new_fcc_valid(int new_fcc_uah, int fcc_uah)
+{
+ /* reject the new fcc if < 50% and > 105% of nominal fcc */
+ if ((new_fcc_uah >= (fcc_uah / 2)) &&
+ ((new_fcc_uah * 100) <= (fcc_uah * 105)))
+ return true;
+
+ pr_debug("FCC rejected - not within valid limit\n");
+ return false;
+}
+
void pm8921_bms_charging_end(int is_battery_full)
{
int batt_temp;
@@ -2766,37 +2909,27 @@
bms_end_ocv_uv = raw.last_good_ocv_uv;
- if (is_battery_full && the_chip->enable_fcc_learning
- && the_chip->start_percent <= MIN_START_PERCENT_FOR_LEARNING) {
- int fcc_uah, new_fcc_uah, delta_fcc_uah;
+ pr_debug("battery_full = %d, fcc_learning = %d, pc_start_chg = %d\n",
+ is_battery_full, the_chip->enable_fcc_learning,
+ the_chip->pc_at_start_charge);
+ if (is_battery_full && the_chip->enable_fcc_learning &&
+ (the_chip->start_percent <= the_chip->min_fcc_learning_soc) &&
+ (the_chip->pc_at_start_charge <= the_chip->min_fcc_ocv_pc)) {
- new_fcc_uah = calculate_real_fcc_uah(the_chip, &raw,
- batt_temp, last_chargecycles,
- &fcc_uah);
- delta_fcc_uah = new_fcc_uah - fcc_uah;
- if (delta_fcc_uah < 0)
- delta_fcc_uah = -delta_fcc_uah;
+ int fcc_uah, new_fcc_uah, delta_cc_uah, delta_soc;
+ /* new_fcc = (cc_end - cc_start) / (end_soc - start_soc) */
+ delta_soc = 100 - the_chip->start_real_soc;
+ delta_cc_uah = abs(bms_end_cc_uah - bms_start_cc_uah);
+ new_fcc_uah = div_u64(delta_cc_uah * 100, delta_soc);
- if (delta_fcc_uah * 100 > (DELTA_FCC_PERCENT * fcc_uah)) {
- /* new_fcc_uah is outside the scope limit it */
- if (new_fcc_uah > fcc_uah)
- new_fcc_uah
- = (fcc_uah +
- (DELTA_FCC_PERCENT * fcc_uah) / 100);
- else
- new_fcc_uah
- = (fcc_uah -
- (DELTA_FCC_PERCENT * fcc_uah) / 100);
-
- pr_debug("delta_fcc=%d > %d percent of fcc=%d"
- "restring it to %d\n",
- delta_fcc_uah, DELTA_FCC_PERCENT,
- fcc_uah, new_fcc_uah);
- }
-
- last_real_fcc_mah = new_fcc_uah/1000;
- last_real_fcc_batt_temp = batt_temp;
- readjust_fcc_table();
+ fcc_uah = calculate_fcc_uah(the_chip, batt_temp,
+ last_chargecycles);
+ pr_info("start_real_soc = %d, end_real_soc = 100, start_cc = %d, end_cc = %d, nominal_fcc = %d, new_fcc = %d\n",
+ the_chip->start_real_soc, bms_start_cc_uah,
+ bms_end_cc_uah, fcc_uah, new_fcc_uah);
+ if (is_new_fcc_valid(new_fcc_uah, fcc_uah))
+ update_fcc_learning_table(the_chip, fcc_uah,
+ new_fcc_uah, last_chargecycles, batt_temp);
}
if (is_battery_full) {
@@ -3350,6 +3483,127 @@
}
}
+static ssize_t fcc_data_set(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct pm8921_bms_chip *chip = dev_get_drvdata(dev);
+ static int i;
+ int fcc_new = 0, rc;
+
+ if (battery_removed) {
+ pr_debug("Invalid FCC table. Possible battery removal\n");
+ last_fcc_update_count = 0;
+ return count;
+ }
+
+ i %= chip->max_fcc_learning_samples;
+ rc = sscanf(buf, "%d", &fcc_new);
+ if (rc != 1)
+ return -EINVAL;
+ chip->fcc_table[i].fcc_new = fcc_new;
+ chip->fcc_table[i].fcc_real = fcc_new;
+ pr_debug("Rcvd: [%d] fcc_new=%d\n", i, fcc_new);
+ i++;
+
+ return count;
+}
+
+static ssize_t fcc_data_get(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ int count = 0;
+ struct pm8921_bms_chip *chip = dev_get_drvdata(dev);
+
+ if (battery_removed) {
+ pr_debug("Invalidate the fcc table\n");
+ invalidate_fcc(chip);
+ battery_removed = 0;
+ return count;
+ }
+
+ count = snprintf(buf, PAGE_SIZE, "%d", chip->fcc_new);
+
+ pr_debug("Sent: fcc_new=%d\n", chip->fcc_new);
+
+ return count;
+}
+
+static ssize_t fcc_temp_set(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ static int i;
+ int batt_temp = 0, rc;
+ struct pm8921_bms_chip *chip = dev_get_drvdata(dev);
+
+ i %= chip->max_fcc_learning_samples;
+ rc = sscanf(buf, "%d", &batt_temp);
+ if (rc != 1)
+ return -EINVAL;
+ chip->fcc_table[i].batt_temp = batt_temp;
+ chip->fcc_table[i].temp_real = batt_temp;
+ pr_debug("Rcvd: [%d] batt_temp=%d\n", i, batt_temp);
+ i++;
+
+ return count;
+}
+
+static ssize_t fcc_chgcyl_set(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ static int i;
+ int chargecycle = 0, rc;
+ struct pm8921_bms_chip *chip = dev_get_drvdata(dev);
+
+ i %= chip->max_fcc_learning_samples;
+ rc = sscanf(buf, "%d", &chargecycle);
+ if (rc != 1)
+ return -EINVAL;
+ chip->fcc_table[i].chargecycles = chargecycle;
+ pr_debug("Rcvd: [%d] chargecycle=%d\n", i, chargecycle);
+ i++;
+
+ return count;
+}
+
+static ssize_t fcc_list_get(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct pm8921_bms_chip *chip = dev_get_drvdata(dev);
+ struct fcc_data *ft;
+ int i = 0, j, count = 0;
+
+ if (last_fcc_update_count < chip->max_fcc_learning_samples)
+ i = last_fcc_update_count;
+ else
+ i = chip->max_fcc_learning_samples;
+
+ for (j = 0; j < i; j++) {
+ ft = &chip->fcc_table[j];
+ count += snprintf(buf + count, PAGE_SIZE - count,
+ "%d %d %d %d %d\n", ft->fcc_new, ft->chargecycles,
+ ft->batt_temp, ft->fcc_real, ft->temp_real);
+ }
+
+ return count;
+}
+
+static DEVICE_ATTR(fcc_data, 0664, fcc_data_get, fcc_data_set);
+static DEVICE_ATTR(fcc_temp, 0664, NULL, fcc_temp_set);
+static DEVICE_ATTR(fcc_chgcyl, 0664, NULL, fcc_chgcyl_set);
+static DEVICE_ATTR(fcc_list, 0664, fcc_list_get, NULL);
+
+static struct attribute *fcc_attrs[] = {
+ &dev_attr_fcc_data.attr,
+ &dev_attr_fcc_temp.attr,
+ &dev_attr_fcc_chgcyl.attr,
+ &dev_attr_fcc_list.attr,
+ NULL
+};
+
+static const struct attribute_group fcc_attr_group = {
+ .attrs = fcc_attrs,
+};
+
#define REG_SBI_CONFIG 0x04F
#define PAGE3_ENABLE_MASK 0x6
#define PROGRAM_REV_MASK 0x0F
@@ -3477,6 +3731,34 @@
chip->batt_id_channel = pdata->bms_cdata.batt_id_channel;
chip->revision = pm8xxx_get_revision(chip->dev->parent);
chip->enable_fcc_learning = pdata->enable_fcc_learning;
+ chip->min_fcc_learning_soc = pdata->min_fcc_learning_soc;
+ chip->min_fcc_ocv_pc = pdata->min_fcc_ocv_pc;
+ chip->max_fcc_learning_samples = pdata->max_fcc_learning_samples;
+ if (chip->enable_fcc_learning) {
+ if (!chip->min_fcc_learning_soc)
+ chip->min_fcc_learning_soc =
+ MIN_START_PERCENT_FOR_LEARNING;
+ if (!chip->min_fcc_ocv_pc)
+ chip->min_fcc_ocv_pc =
+ MIN_START_OCV_PERCENT_FOR_LEARNING;
+ if (!chip->max_fcc_learning_samples ||
+ chip->max_fcc_learning_samples > MAX_FCC_LEARNING_COUNT)
+ chip->max_fcc_learning_samples = MAX_FCC_LEARNING_COUNT;
+
+ max_fcc_cycles = chip->max_fcc_learning_samples;
+ chip->fcc_table = kzalloc(sizeof(struct fcc_data) *
+ chip->max_fcc_learning_samples, GFP_KERNEL);
+ if (!chip->fcc_table) {
+ pr_err("Unable to allocate table for fcc learning\n");
+ rc = -ENOMEM;
+ goto free_chip;
+ }
+ rc = sysfs_create_group(&pdev->dev.kobj, &fcc_attr_group);
+ if (rc) {
+ pr_err("Unable to create sysfs entries\n");
+ goto free_chip;
+ }
+ }
chip->disable_flat_portion_ocv = pdata->disable_flat_portion_ocv;
chip->ocv_dis_high_soc = pdata->ocv_dis_high_soc;
diff --git a/include/linux/mfd/pm8xxx/pm8921-bms.h b/include/linux/mfd/pm8xxx/pm8921-bms.h
index a19c0b6..7562166 100644
--- a/include/linux/mfd/pm8xxx/pm8921-bms.h
+++ b/include/linux/mfd/pm8xxx/pm8921-bms.h
@@ -37,6 +37,12 @@
* is considered empty(mV)
* @enable_fcc_learning: if set the driver will learn full charge
* capacity of the battery upon end of charge
+ * @min_fcc_learning_soc: minimum SOC as which CC counting for FCC
+ * learning can start
+ * @min_fcc_ocv_pc: minimum PC (lookup(OCV)) at which CC counting
+ * for FCC learning can start
+ * @max_fcc_learning_samples: Maximum number of FCC measurement cycles to be
+ * used for FCC update
* @normal_voltage_calc_ms: The period of soc calculation in ms when battery
* voltage higher than cutoff voltage
* @low_voltage_calc_ms: The period of soc calculation in ms when battery
@@ -66,6 +72,9 @@
unsigned int alarm_low_mv;
unsigned int alarm_high_mv;
int enable_fcc_learning;
+ int min_fcc_learning_soc;
+ int min_fcc_ocv_pc;
+ int max_fcc_learning_samples;
int shutdown_soc_valid_limit;
int ignore_shutdown_soc;
int adjust_soc_low_threshold;