diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile index 5adbd079f2fa..b5874cd35e3a 100644 --- a/drivers/power/supply/Makefile +++ b/drivers/power/supply/Makefile @@ -93,3 +93,4 @@ obj-$(CONFIG_CHARGER_UCS1002) += ucs1002_power.o obj-$(CONFIG_CHARGER_BD70528) += bd70528-charger.o obj-$(CONFIG_CHARGER_WILCO) += wilco-charger.o obj-$(CONFIG_QTI_BATTERY_CHARGER) += qti_battery_charger.o +obj-$(CONFIG_QCOM_POWER_SUPPLY) += qcom/ diff --git a/drivers/power/supply/qcom/Kconfig b/drivers/power/supply/qcom/Kconfig new file mode 100644 index 000000000000..b7c457fcd750 --- /dev/null +++ b/drivers/power/supply/qcom/Kconfig @@ -0,0 +1,18 @@ +# SPDX-License-Identifier: GPL-2.0-only + +menuconfig QCOM_POWER_SUPPLY + tristate "Support for Qualcomm Technologies, Inc. power supply" + depends on ARCH_QCOM + +if QCOM_POWER_SUPPLY + +config QPNP_QG + bool "QPNP Qgauge driver" + depends on MFD_SPMI_PMIC + help + Say Y here to enable the Qualcomm Technologies, Inc. QGauge driver + which uses the periodic sampling of the battery voltage and current + to determine the battery state-of-charge (SOC) and supports other + battery management features. + +endif diff --git a/drivers/power/supply/qcom/Makefile b/drivers/power/supply/qcom/Makefile new file mode 100644 index 000000000000..0e23d0efed03 --- /dev/null +++ b/drivers/power/supply/qcom/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0-only + +obj-$(CONFIG_QPNP_QG) += qpnp-qg.o battery-profile-loader.o pmic-voter.o qg-util.o qg-soc.o qg-sdam.o qg-battery-profile.o qg-profile-lib.o fg-alg.o diff --git a/drivers/power/supply/qcom/battery-profile-loader.c b/drivers/power/supply/qcom/battery-profile-loader.c new file mode 100644 index 000000000000..287ddb9e5403 --- /dev/null +++ b/drivers/power/supply/qcom/battery-profile-loader.c @@ -0,0 +1,336 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2013-2020, The Linux Foundation. All rights reserved. + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include "battery-profile-loader.h" + +static int of_batterydata_read_batt_id_kohm(const struct device_node *np, + const char *propname, struct batt_ids *batt_ids) +{ + struct property *prop; + const __be32 *data; + int num, i, *id_kohm = batt_ids->kohm; + + prop = of_find_property(np, "qcom,batt-id-kohm", NULL); + if (!prop) { + pr_err("%s: No battery id resistor found\n", np->name); + return -EINVAL; + } else if (!prop->value) { + pr_err("%s: No battery id resistor value found, np->name\n", + np->name); + return -ENODATA; + } else if (prop->length > MAX_BATT_ID_NUM * sizeof(__be32)) { + pr_err("%s: Too many battery id resistors\n", np->name); + return -EINVAL; + } + + num = prop->length/sizeof(__be32); + batt_ids->num = num; + data = prop->value; + for (i = 0; i < num; i++) + *id_kohm++ = be32_to_cpup(data++); + + return 0; +} + +struct device_node *of_batterydata_get_best_profile( + const struct device_node *batterydata_container_node, + int batt_id_kohm, const char *batt_type) +{ + struct batt_ids batt_ids; + struct device_node *node, *best_node = NULL; + const char *battery_type = NULL; + int delta = 0, best_delta = 0, best_id_kohm = 0, id_range_pct, + i = 0, rc = 0, limit = 0; + bool in_range = false; + + /* read battery id range percentage for best profile */ + rc = of_property_read_u32(batterydata_container_node, + "qcom,batt-id-range-pct", &id_range_pct); + + if (rc) { + if (rc == -EINVAL) { + id_range_pct = 0; + } else { + pr_err("failed to read battery id range\n"); + return ERR_PTR(-ENXIO); + } + } + + /* + * Find the battery data with a battery id resistor closest to this one + */ + for_each_child_of_node(batterydata_container_node, node) { + if (batt_type != NULL) { + rc = of_property_read_string(node, "qcom,battery-type", + &battery_type); + if (!rc && strcmp(battery_type, batt_type) == 0) { + best_node = node; + best_id_kohm = batt_id_kohm; + break; + } + } else { + rc = of_batterydata_read_batt_id_kohm(node, + "qcom,batt-id-kohm", + &batt_ids); + if (rc) + continue; + for (i = 0; i < batt_ids.num; i++) { + delta = abs(batt_ids.kohm[i] - batt_id_kohm); + limit = (batt_ids.kohm[i] * id_range_pct) / 100; + in_range = (delta <= limit); + /* + * Check if the delta is the lowest one + * and also if the limits are in range + * before selecting the best node. + */ + if ((delta < best_delta || !best_node) + && in_range) { + best_node = node; + best_delta = delta; + best_id_kohm = batt_ids.kohm[i]; + } + } + } + } + + if (best_node == NULL) { + pr_err("No battery data found\n"); + return best_node; + } + + /* check that profile id is in range of the measured batt_id */ + if (abs(best_id_kohm - batt_id_kohm) > + ((best_id_kohm * id_range_pct) / 100)) { + pr_err("out of range: profile id %d batt id %d pct %d\n", + best_id_kohm, batt_id_kohm, id_range_pct); + return NULL; + } + + rc = of_property_read_string(best_node, "qcom,battery-type", + &battery_type); + if (!rc) + pr_info("%s found\n", battery_type); + else + pr_info("%s found\n", best_node->name); + + return best_node; +} + +struct device_node *of_batterydata_get_best_aged_profile( + const struct device_node *batterydata_container_node, + int batt_id_kohm, int batt_age_level, int *avail_age_level) +{ + struct batt_ids batt_ids; + struct device_node *node, *best_node = NULL; + const char *battery_type = NULL; + int delta = 0, best_id_kohm = 0, id_range_pct, i = 0, rc = 0, limit = 0; + u32 val; + bool in_range = false; + + /* read battery id range percentage for best profile */ + rc = of_property_read_u32(batterydata_container_node, + "qcom,batt-id-range-pct", &id_range_pct); + + if (rc) { + if (rc == -EINVAL) { + id_range_pct = 0; + } else { + pr_err("failed to read battery id range\n"); + return ERR_PTR(-ENXIO); + } + } + + /* + * Find the battery data with a battery id resistor closest to this one + */ + for_each_available_child_of_node(batterydata_container_node, node) { + val = 0; + of_property_read_u32(node, "qcom,batt-age-level", &val); + rc = of_batterydata_read_batt_id_kohm(node, + "qcom,batt-id-kohm", &batt_ids); + if (rc) + continue; + for (i = 0; i < batt_ids.num; i++) { + delta = abs(batt_ids.kohm[i] - batt_id_kohm); + limit = (batt_ids.kohm[i] * id_range_pct) / 100; + in_range = (delta <= limit); + + /* + * Check if the battery aging level matches and the + * limits are in range before selecting the best node. + */ + if ((batt_age_level == val || !best_node) && in_range) { + best_node = node; + best_id_kohm = batt_ids.kohm[i]; + *avail_age_level = val; + break; + } + } + } + + if (best_node == NULL) { + pr_err("No battery data found\n"); + return best_node; + } + + /* check that profile id is in range of the measured batt_id */ + if (abs(best_id_kohm - batt_id_kohm) > + ((best_id_kohm * id_range_pct) / 100)) { + pr_err("out of range: profile id %d batt id %d pct %d\n", + best_id_kohm, batt_id_kohm, id_range_pct); + return NULL; + } + + rc = of_property_read_string(best_node, "qcom,battery-type", + &battery_type); + if (!rc) + pr_info("%s age level %d found\n", battery_type, + *avail_age_level); + else + pr_info("%s age level %d found\n", best_node->name, + *avail_age_level); + + return best_node; +} + +int of_batterydata_get_aged_profile_count( + const struct device_node *batterydata_node, + int batt_id_kohm, int *count) +{ + struct device_node *node; + int id_range_pct, i = 0, rc = 0, limit = 0, delta = 0; + bool in_range = false; + u32 batt_id; + + /* read battery id range percentage for best profile */ + rc = of_property_read_u32(batterydata_node, + "qcom,batt-id-range-pct", &id_range_pct); + if (rc) { + if (rc == -EINVAL) { + id_range_pct = 0; + } else { + pr_err("failed to read battery id range\n"); + return -ENXIO; + } + } + + for_each_available_child_of_node(batterydata_node, node) { + if (!of_find_property(node, "qcom,batt-age-level", NULL)) + continue; + + if (!of_find_property(node, "qcom,soh-range", NULL)) + continue; + + rc = of_property_read_u32(node, "qcom,batt-id-kohm", &batt_id); + if (rc) + continue; + + delta = abs(batt_id_kohm - batt_id); + limit = (batt_id_kohm * id_range_pct) / 100; + in_range = (delta <= limit); + + if (!in_range) { + pr_debug("not in range batt_id: %d\n", batt_id); + continue; + } + + i++; + } + + if (i <= 1) { + pr_err("Less number of profiles to support SOH\n"); + return -EINVAL; + } + + *count = i; + return 0; +} + +int of_batterydata_read_soh_aged_profiles( + const struct device_node *batterydata_node, + int batt_id_kohm, struct soh_range *soh_data) +{ + struct device_node *node; + u32 val, temp[2], i = 0; + int rc, batt_id, id_range_pct, limit = 0, delta = 0; + bool in_range = false; + + if (!batterydata_node || !soh_data) + return -ENODEV; + + /* read battery id range percentage for best profile */ + rc = of_property_read_u32(batterydata_node, + "qcom,batt-id-range-pct", &id_range_pct); + if (rc) { + if (rc == -EINVAL) { + id_range_pct = 0; + } else { + pr_err("failed to read battery id range\n"); + return -ENXIO; + } + } + + for_each_available_child_of_node(batterydata_node, node) { + rc = of_property_read_u32(node, "qcom,batt-age-level", &val); + if (rc) + continue; + + rc = of_property_read_u32(node, "qcom,batt-id-kohm", &batt_id); + if (rc) + continue; + + delta = abs(batt_id_kohm - batt_id); + limit = (batt_id_kohm * id_range_pct) / 100; + in_range = (delta <= limit); + + if (!in_range) { + pr_debug("not in range batt_id: %d\n", batt_id); + continue; + } + + if (!of_find_property(node, "qcom,soh-range", NULL)) + continue; + + rc = of_property_count_elems_of_size(node, "qcom,soh-range", + sizeof(u32)); + if (rc != 2) { + pr_err("Incorrect element size for qcom,soh-range, rc=%d\n", + rc); + return -EINVAL; + } + + rc = of_property_read_u32_array(node, "qcom,soh-range", temp, + 2); + if (rc < 0) { + pr_err("Error in reading qcom,soh-range, rc=%d\n", rc); + return rc; + } + + if (temp[0] > 100 || temp[1] > 100 || (temp[0] > temp[1])) { + pr_err("Incorrect SOH range [%d %d]\n", temp[0], + temp[1]); + return -ERANGE; + } + + pr_debug("batt_age_level: %d soh: [%d %d]\n", val, temp[0], + temp[1]); + soh_data[i].batt_age_level = val; + soh_data[i].soh_min = temp[0]; + soh_data[i].soh_max = temp[1]; + i++; + } + + return 0; +} + +MODULE_LICENSE("GPL v2"); diff --git a/drivers/power/supply/qcom/battery-profile-loader.h b/drivers/power/supply/qcom/battery-profile-loader.h new file mode 100644 index 000000000000..6069a1d50bae --- /dev/null +++ b/drivers/power/supply/qcom/battery-profile-loader.h @@ -0,0 +1,89 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2013-2014, 2016-2020 The Linux Foundation. All rights reserved. + */ + +#ifndef __BATTERY_PROFILE_LOADER_H +#define __BATTERY_PROFILE_LOADER_H + +#include + +#define MAX_BATT_ID_NUM 4 +#define DEGC_SCALE 10 + +struct batt_ids { + int kohm[MAX_BATT_ID_NUM]; + int num; +}; + +/** + * struct soh_range - + * @batt_age_level: Battery age level (e.g. 0, 1 etc.,) + * @soh_min: Minimum SOH (state of health) level that this battery + * profile can support. + * @soh_max: Maximum SOH (state of health) level that this battery + * profile can support. + */ +struct soh_range { + int batt_age_level; + int soh_min; + int soh_max; +}; + +/** + * of_batterydata_get_best_profile() - Find matching battery data device node + * @batterydata_container_node: pointer to the battery-data container device + * node containing the profile nodes. + * @batt_id_kohm: Battery ID in KOhms for which we want to find the profile. + * @batt_type: Battery type which we want to force load the profile. + * + * This routine returns a device_node pointer to the closest match battery data + * from device tree based on the battery id reading. + */ +struct device_node *of_batterydata_get_best_profile( + const struct device_node *batterydata_container_node, + int batt_id_kohm, const char *batt_type); + +/** + * of_batterydata_get_best_aged_profile() - Find best aged battery profile + * @batterydata_container_node: pointer to the battery-data container device + * node containing the profile nodes. + * @batt_id_kohm: Battery ID in KOhms for which we want to find the profile. + * @batt_age_level: Battery age level. + * @avail_age_level: Available battery age level. + * + * This routine returns a device_node pointer to the closest match battery data + * from device tree based on the battery id reading and age level. + */ +struct device_node *of_batterydata_get_best_aged_profile( + const struct device_node *batterydata_container_node, + int batt_id_kohm, int batt_age_level, int *avail_age_level); + +/** + * of_batterydata_get_aged_profile_count() - Gets the number of aged profiles + * @batterydata_node: pointer to the battery-data container device + * node containing the profile nodes. + * @batt_id_kohm: Battery ID in KOhms for which we want to find the profile. + * @count: Number of aged profiles available to support SOH based profile + * loading. + * + * This routine returns zero if valid number of aged profiles are available. + */ +int of_batterydata_get_aged_profile_count( + const struct device_node *batterydata_node, + int batt_id_kohm, int *count); + +/** + * of_batterydata_read_soh_aged_profiles() - Reads the data from aged profiles + * @batterydata_node: pointer to the battery-data container device + * node containing the profile nodes. + * @batt_id_kohm: Battery ID in KOhms for which we want to find the profile. + * @soh_data: SOH data from the profile if it is found to be valid. + * + * This routine returns zero if SOH data of aged profiles is valid. + */ +int of_batterydata_read_soh_aged_profiles( + const struct device_node *batterydata_node, + int batt_id_kohm, struct soh_range *soh_data); + +#endif /* __BATTERY_PROFILE_LOADER_H */ diff --git a/drivers/power/supply/qcom/fg-alg.c b/drivers/power/supply/qcom/fg-alg.c new file mode 100644 index 000000000000..24a009d4796f --- /dev/null +++ b/drivers/power/supply/qcom/fg-alg.c @@ -0,0 +1,1646 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2018-2020 The Linux Foundation. All rights reserved. + */ + +#define pr_fmt(fmt) "ALG: %s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include "fg-alg.h" + +#define FULL_SOC_RAW 255 +#define CAPACITY_DELTA_DECIPCT 500 +#define CENTI_FULL_SOC 10000 + +#define CENTI_ICORRECT_C0 105 +#define CENTI_ICORRECT_C1 20 + +#define HOURS_TO_SECONDS 3600 +#define OCV_SLOPE_UV 10869 +#define MILLI_UNIT 1000 +#define MICRO_UNIT 1000000 +#define NANO_UNIT 1000000000 + +#define DEFAULT_TTF_RUN_PERIOD_MS 10000 +#define DEFAULT_TTF_ITERM_DELTA_MA 200 + +static const struct ttf_pt ttf_ln_table[] = { + { 1000, 0 }, + { 2000, 693 }, + { 4000, 1386 }, + { 6000, 1792 }, + { 8000, 2079 }, + { 16000, 2773 }, + { 32000, 3466 }, + { 64000, 4159 }, + { 128000, 4852 }, +}; + +/* Cycle counter APIs */ + +/** + * restore_cycle_count - + * @counter: Cycle counter object + * + * Restores all the counters back from FG/QG during boot + * + */ +int restore_cycle_count(struct cycle_counter *counter) +{ + int rc = 0; + + if (!counter) + return -ENODEV; + + mutex_lock(&counter->lock); + rc = counter->restore_count(counter->data, counter->count, + BUCKET_COUNT); + if (rc < 0) + pr_err("failed to restore cycle counter rc=%d\n", rc); + mutex_unlock(&counter->lock); + + return rc; +} + +/** + * clear_cycle_count - + * @counter: Cycle counter object + * + * Clears all the counters stored by FG/QG when a battery is inserted + * or the profile is re-loaded. + * + */ +void clear_cycle_count(struct cycle_counter *counter) +{ + int rc = 0, i; + + if (!counter) + return; + + mutex_lock(&counter->lock); + memset(counter->count, 0, sizeof(counter->count)); + for (i = 0; i < BUCKET_COUNT; i++) { + counter->started[i] = false; + counter->last_soc[i] = 0; + } + + rc = counter->store_count(counter->data, counter->count, 0, + BUCKET_COUNT * 2); + if (rc < 0) + pr_err("failed to clear cycle counter rc=%d\n", rc); + + mutex_unlock(&counter->lock); +} + +/** + * store_cycle_count - + * @counter: Cycle counter object + * @id: Cycle counter bucket id + * + * Stores the cycle counter for a bucket in FG/QG. + * + */ +static int store_cycle_count(struct cycle_counter *counter, int id) +{ + int rc = 0; + u16 cyc_count; + + if (!counter) + return -ENODEV; + + if (id < 0 || (id > BUCKET_COUNT - 1)) { + pr_err("Invalid id %d\n", id); + return -EINVAL; + } + + cyc_count = counter->count[id]; + cyc_count++; + + rc = counter->store_count(counter->data, &cyc_count, id, 2); + if (rc < 0) { + pr_err("failed to write cycle_count[%d] rc=%d\n", + id, rc); + return rc; + } + + counter->count[id] = cyc_count; + pr_debug("Stored count %d in id %d\n", cyc_count, id); + + return rc; +} + +/** + * cycle_count_update - + * @counter: Cycle counter object + * @batt_soc: Battery State of Charge (SOC) + * @charge_status: Charging status from power supply + * @charge_done: Indicator for charge termination + * @input_present: Indicator for input presence + * + * Called by FG/QG whenever there is a state change (Charging status, SOC) + * + */ +void cycle_count_update(struct cycle_counter *counter, int batt_soc, + int charge_status, bool charge_done, bool input_present) +{ + int rc = 0, id, i, soc_thresh; + + if (!counter) + return; + + mutex_lock(&counter->lock); + + /* Find out which id the SOC falls in */ + id = batt_soc / BUCKET_SOC_PCT; + + if (charge_status == POWER_SUPPLY_STATUS_CHARGING) { + if (!counter->started[id] && id != counter->last_bucket) { + counter->started[id] = true; + counter->last_soc[id] = batt_soc; + } + } else if (charge_done || !input_present) { + for (i = 0; i < BUCKET_COUNT; i++) { + soc_thresh = counter->last_soc[i] + BUCKET_SOC_PCT / 2; + if (counter->started[i] && batt_soc > soc_thresh) { + rc = store_cycle_count(counter, i); + if (rc < 0) + pr_err("Error in storing cycle_ctr rc: %d\n", + rc); + counter->last_soc[i] = 0; + counter->started[i] = false; + counter->last_bucket = i; + } + } + } + + pr_debug("batt_soc: %d id: %d chg_status: %d\n", batt_soc, id, + charge_status); + mutex_unlock(&counter->lock); +} + +/** + * get_bucket_cycle_count - + * @counter: Cycle counter object + * + * Returns the cycle counter for a SOC bucket. + * + */ +static int get_bucket_cycle_count(struct cycle_counter *counter) +{ + int count; + + if (!counter) + return 0; + + if ((counter->id <= 0) || (counter->id > BUCKET_COUNT)) + return -EINVAL; + + mutex_lock(&counter->lock); + count = counter->count[counter->id - 1]; + mutex_unlock(&counter->lock); + return count; +} + +/** + * get_cycle_count - + * @counter: Cycle counter object + * @count: Average cycle count returned to the caller + * + * Get average cycle count for all buckets + * + */ +int get_cycle_count(struct cycle_counter *counter, int *count) +{ + int i, rc, temp = 0; + + for (i = 1; i <= BUCKET_COUNT; i++) { + counter->id = i; + rc = get_bucket_cycle_count(counter); + if (rc < 0) { + pr_err("Couldn't get cycle count rc=%d\n", rc); + return rc; + } + temp += rc; + } + + /* + * Normalize the counter across each bucket so that we can get + * the overall charge cycle count. + */ + + *count = temp / BUCKET_COUNT; + return 0; +} + +/** + * get_cycle_counts - + * @counter: Cycle counter object + * @buf: Bucket cycle counts formatted in a string returned to the caller + * + * Get cycle count for all buckets in a string format + * + */ +int get_cycle_counts(struct cycle_counter *counter, const char **buf) +{ + int i, rc, len = 0; + + for (i = 1; i <= BUCKET_COUNT; i++) { + counter->id = i; + rc = get_bucket_cycle_count(counter); + if (rc < 0) { + pr_err("Couldn't get cycle count rc=%d\n", rc); + return rc; + } + + if (sizeof(counter->str_buf) - len < 8) { + pr_err("Invalid length %d\n", len); + return -EINVAL; + } + + len += scnprintf(counter->str_buf + len, 8, "%d ", rc); + } + + counter->str_buf[len] = '\0'; + *buf = counter->str_buf; + return 0; +} + +/** + * cycle_count_init - + * @counter: Cycle counter object + * + * FG/QG have to call this during driver probe to validate the required + * parameters after allocating cycle_counter object. + * + */ +int cycle_count_init(struct cycle_counter *counter) +{ + if (!counter) + return -ENODEV; + + if (!counter->data || !counter->restore_count || + !counter->store_count) { + pr_err("Invalid parameters for using cycle counter\n"); + return -EINVAL; + } + + mutex_init(&counter->lock); + counter->last_bucket = -1; + return 0; +} + +/* Capacity learning algorithm APIs */ + +/** + * cap_learning_post_process - + * @cl: Capacity learning object + * + * Does post processing on the learnt capacity based on the user specified + * or default parameters for the capacity learning algorithm. + * + */ +static void cap_learning_post_process(struct cap_learning *cl) +{ + int64_t max_inc_val, min_dec_val, old_cap; + int rc; + + if (cl->dt.skew_decipct) { + pr_debug("applying skew %d on current learnt capacity %lld\n", + cl->dt.skew_decipct, cl->final_cap_uah); + cl->final_cap_uah = cl->final_cap_uah * + (1000 + cl->dt.skew_decipct); + cl->final_cap_uah = div64_u64(cl->final_cap_uah, 1000); + } + + max_inc_val = cl->learned_cap_uah * (1000 + cl->dt.max_cap_inc); + max_inc_val = div64_u64(max_inc_val, 1000); + + min_dec_val = cl->learned_cap_uah * (1000 - cl->dt.max_cap_dec); + min_dec_val = div64_u64(min_dec_val, 1000); + + old_cap = cl->learned_cap_uah; + if (cl->final_cap_uah > max_inc_val) + cl->learned_cap_uah = max_inc_val; + else if (cl->final_cap_uah < min_dec_val) + cl->learned_cap_uah = min_dec_val; + else + cl->learned_cap_uah = cl->final_cap_uah; + + if (cl->dt.max_cap_limit) { + max_inc_val = (int64_t)cl->nom_cap_uah * (1000 + + cl->dt.max_cap_limit); + max_inc_val = div64_u64(max_inc_val, 1000); + if (cl->final_cap_uah > max_inc_val) { + pr_debug("learning capacity %lld goes above max limit %lld\n", + cl->final_cap_uah, max_inc_val); + cl->learned_cap_uah = max_inc_val; + } + } + + if (cl->dt.min_cap_limit) { + min_dec_val = (int64_t)cl->nom_cap_uah * (1000 - + cl->dt.min_cap_limit); + min_dec_val = div64_u64(min_dec_val, 1000); + if (cl->final_cap_uah < min_dec_val) { + pr_debug("learning capacity %lld goes below min limit %lld\n", + cl->final_cap_uah, min_dec_val); + cl->learned_cap_uah = min_dec_val; + } + } + + if (cl->store_learned_capacity) { + rc = cl->store_learned_capacity(cl->data, cl->learned_cap_uah); + if (rc < 0) + pr_err("Error in storing learned_cap_uah, rc=%d\n", rc); + } + + pr_debug("final cap_uah = %lld, learned capacity %lld -> %lld uah\n", + cl->final_cap_uah, old_cap, cl->learned_cap_uah); +} + +/** + * cap_wt_learning_process_full_data - + * @cl: Capacity learning object + * @delta_batt_soc_pct: percentage change in battery State of Charge + * @batt_soc_cp: Battery State of Charge in centi-percentage + * + * Calculates the final learnt capacity when + * weighted capacity learning is enabled. + * + */ +static int cap_wt_learning_process_full_data(struct cap_learning *cl, + int delta_batt_soc_pct, + int batt_soc_cp) +{ + int64_t del_cap_uah, total_cap_uah, + res_cap_uah, wt_learnt_cap_uah; + int delta_batt_soc_cp, res_batt_soc_cp; + + /* If the delta is < 10%, then skip processing full data */ + if (delta_batt_soc_pct < cl->dt.min_delta_batt_soc) { + pr_debug("batt_soc_delta_pct: %d\n", delta_batt_soc_pct); + return -ERANGE; + } + + delta_batt_soc_cp = batt_soc_cp - cl->init_batt_soc_cp; + res_batt_soc_cp = CENTI_FULL_SOC - batt_soc_cp; + /* Learnt Capacity from end Battery SOC to CENTI_FULL_SOC */ + res_cap_uah = div64_s64(cl->learned_cap_uah * + res_batt_soc_cp, CENTI_FULL_SOC); + total_cap_uah = cl->init_cap_uah + cl->delta_cap_uah + res_cap_uah; + /* + * difference in capacity learnt in this + * charge cycle and previous learnt capacity + */ + del_cap_uah = total_cap_uah - cl->learned_cap_uah; + /* Applying weight based on change in battery SOC MSB */ + wt_learnt_cap_uah = div64_s64(del_cap_uah * delta_batt_soc_cp, + CENTI_FULL_SOC); + cl->final_cap_uah = cl->learned_cap_uah + wt_learnt_cap_uah; + + pr_debug("wt_learnt_cap_uah=%lld, del_cap_uah=%lld\n", + wt_learnt_cap_uah, del_cap_uah); + pr_debug("init_cap_uah=%lld, total_cap_uah=%lld, res_cap_uah=%lld, delta_cap_uah=%lld\n", + cl->init_cap_uah, cl->final_cap_uah, + res_cap_uah, cl->delta_cap_uah); + return 0; +} + +/** + * cap_learning_process_full_data - + * @cl: Capacity learning object + * @batt_soc_cp: Battery State of Charge in centi-percentage + * + * Processes the coulomb counter during charge termination and calculates the + * delta w.r.to the coulomb counter obtained earlier when the learning begun. + * + */ +static int cap_learning_process_full_data(struct cap_learning *cl, + int batt_soc_cp) +{ + int rc, cc_soc_sw, cc_soc_delta_pct, delta_batt_soc_pct, batt_soc_pct, + cc_soc_fraction; + int64_t cc_soc_cap_uah, cc_soc_fraction_uah; + + rc = cl->get_cc_soc(cl->data, &cc_soc_sw); + if (rc < 0) { + pr_err("Error in getting CC_SOC_SW, rc=%d\n", rc); + return rc; + } + + batt_soc_pct = DIV_ROUND_CLOSEST(batt_soc_cp, 100); + delta_batt_soc_pct = batt_soc_pct - cl->init_batt_soc; + cc_soc_delta_pct = + div_s64_rem((int64_t)(cc_soc_sw - cl->init_cc_soc_sw) * 100, + cl->cc_soc_max, &cc_soc_fraction); + cc_soc_fraction_uah = div64_s64(cl->learned_cap_uah * + cc_soc_fraction, (int64_t)cl->cc_soc_max * 100); + cc_soc_cap_uah = div64_s64(cl->learned_cap_uah * cc_soc_delta_pct, 100); + cl->delta_cap_uah = cc_soc_cap_uah + cc_soc_fraction_uah; + pr_debug("cc_soc_delta_pct=%d, cc_soc_cap_uah=%lld, cc_soc_fraction_uah=%lld\n", + cc_soc_delta_pct, cc_soc_cap_uah, cc_soc_fraction_uah); + + if (cl->dt.cl_wt_enable) { + rc = cap_wt_learning_process_full_data(cl, delta_batt_soc_pct, + batt_soc_cp); + return rc; + } + + /* If the delta is < 50%, then skip processing full data */ + if (cc_soc_delta_pct < 50) { + pr_err("cc_soc_delta_pct: %d\n", cc_soc_delta_pct); + return -ERANGE; + } + + cl->final_cap_uah = cl->init_cap_uah + cl->delta_cap_uah; + pr_debug("Current cc_soc=%d cc_soc_delta_pct=%d total_cap_uah=%lld\n", + cc_soc_sw, cc_soc_delta_pct, cl->final_cap_uah); + return 0; +} + +/** + * cap_learning_begin - + * @cl: Capacity learning object + * @batt_soc_cp: Battery State of Charge in centi-percentage + * + * Gets the coulomb counter from FG/QG when the conditions are suitable for + * beginning capacity learning. Also, primes the coulomb counter based on + * battery SOC if required. + * + */ +#define BATT_SOC_32BIT GENMASK(31, 0) +static int cap_learning_begin(struct cap_learning *cl, u32 batt_soc_cp) +{ + int rc, cc_soc_sw, batt_soc_pct; + u32 batt_soc_prime; + + if (cl->ok_to_begin && !cl->ok_to_begin(cl->data)) { + pr_debug("Not OK to begin\n"); + return -EINVAL; + } + + batt_soc_pct = DIV_ROUND_CLOSEST(batt_soc_cp, 100); + + if ((cl->dt.max_start_soc != -EINVAL && + batt_soc_pct > cl->dt.max_start_soc) || + (cl->dt.min_start_soc != -EINVAL && + batt_soc_pct < cl->dt.min_start_soc)) { + pr_debug("Battery SOC %d is high/low, not starting\n", + batt_soc_pct); + return -EINVAL; + } + + cl->init_cap_uah = div64_s64(cl->learned_cap_uah * batt_soc_cp, + CENTI_FULL_SOC); + + if (cl->prime_cc_soc) { + /* + * Prime cc_soc_sw with battery SOC when capacity learning + * begins. + */ + batt_soc_prime = div64_u64( + (uint64_t)batt_soc_cp * BATT_SOC_32BIT, + CENTI_FULL_SOC); + rc = cl->prime_cc_soc(cl->data, batt_soc_prime); + if (rc < 0) { + pr_err("Error in writing cc_soc_sw, rc=%d\n", rc); + goto out; + } + } + + rc = cl->get_cc_soc(cl->data, &cc_soc_sw); + if (rc < 0) { + pr_err("Error in getting CC_SOC_SW, rc=%d\n", rc); + goto out; + } + + cl->init_cc_soc_sw = cc_soc_sw; + cl->init_batt_soc = batt_soc_pct; + cl->init_batt_soc_cp = batt_soc_cp; + pr_debug("Capacity learning started @ battery SOC %d init_cc_soc_sw:%d\n", + batt_soc_cp, cl->init_cc_soc_sw); +out: + return rc; +} + +/** + * cap_learning_done - + * @cl: Capacity learning object + * @batt_soc_cp: Battery State of Charge in centi-percentage + * + * Top level function for getting coulomb counter and post processing the + * data once the capacity learning is complete after charge termination. + * + */ +static int cap_learning_done(struct cap_learning *cl, int batt_soc_cp) +{ + int rc; + + rc = cap_learning_process_full_data(cl, batt_soc_cp); + if (rc < 0) { + pr_debug("Error in processing cap learning full data, rc=%d\n", + rc); + goto out; + } + + if (cl->prime_cc_soc) { + /* Write a FULL value to cc_soc_sw */ + rc = cl->prime_cc_soc(cl->data, cl->cc_soc_max); + if (rc < 0) { + pr_err("Error in writing cc_soc_sw, rc=%d\n", rc); + goto out; + } + } + + cap_learning_post_process(cl); +out: + return rc; +} + +/** + * cap_wt_learning_update - + * @cl: Capacity learning object + * @batt_soc_cp: Battery State of Charge in centi-percentage + * @input_present: Indicator for input presence + * + * Called by cap_learning_update when weighted learning is enabled + * + */ +static void cap_wt_learning_update(struct cap_learning *cl, int batt_soc_cp, + bool input_present) +{ + int rc; + + if (!input_present) { + rc = cap_learning_done(cl, batt_soc_cp); + if (rc < 0) + pr_debug("Error in completing capacity learning, rc=%d\n", + rc); + cl->active = false; + cl->init_cap_uah = 0; + } +} + +/** + * cap_learning_update - + * @cl: Capacity learning object + * @batt_temp - Battery temperature + * @batt_soc: Battery State of Charge (SOC) + * @charge_status: Charging status from power supply + * @charge_done: Indicator for charge termination + * @input_present: Indicator for input presence + * @qnovo_en: Indicator for Qnovo enable status + * + * Called by FG/QG driver when there is a state change (Charging status, SOC) + * + */ +void cap_learning_update(struct cap_learning *cl, int batt_temp, + int batt_soc_cp, int charge_status, bool charge_done, + bool input_present, bool qnovo_en) +{ + int rc; + u32 batt_soc_prime; + bool prime_cc = false; + + if (!cl) + return; + + mutex_lock(&cl->lock); + + if (batt_temp > cl->dt.max_temp || batt_temp < cl->dt.min_temp || + !cl->learned_cap_uah) { + cl->active = false; + cl->init_cap_uah = 0; + goto out; + } + + pr_debug("Charge_status: %d active: %d batt_soc: %d\n", + charge_status, cl->active, batt_soc_cp); + + if (cl->active && cl->dt.cl_wt_enable) + cap_wt_learning_update(cl, batt_soc_cp, input_present); + + /* Initialize the starting point of learning capacity */ + if (!cl->active) { + if (charge_status == POWER_SUPPLY_STATUS_CHARGING) { + rc = cap_learning_begin(cl, batt_soc_cp); + cl->active = (rc == 0); + } else { + if (charge_status == POWER_SUPPLY_STATUS_DISCHARGING || + charge_done) + prime_cc = true; + } + } else { + if (charge_done) { + rc = cap_learning_done(cl, batt_soc_cp); + if (rc < 0) + pr_err("Error in completing capacity learning, rc=%d\n", + rc); + + cl->active = false; + cl->init_cap_uah = 0; + } + + if (charge_status == POWER_SUPPLY_STATUS_DISCHARGING && + !input_present) { + pr_debug("Capacity learning aborted @ battery SOC %d\n", + batt_soc_cp); + cl->active = false; + cl->init_cap_uah = 0; + prime_cc = true; + } + + if (charge_status == POWER_SUPPLY_STATUS_NOT_CHARGING && + !cl->dt.cl_wt_enable) { + if (qnovo_en && input_present) { + /* + * Don't abort the capacity learning when qnovo + * is enabled and input is present where the + * charging status can go to "not charging" + * intermittently. + */ + } else { + pr_debug("Capacity learning aborted @ battery SOC %d\n", + batt_soc_cp); + cl->active = false; + cl->init_cap_uah = 0; + prime_cc = true; + } + } + } + + /* + * Prime CC_SOC_SW when the device is not charging or during charge + * termination when the capacity learning is not active. + */ + + if (prime_cc && cl->prime_cc_soc) { + /* pass 32-bit batt_soc to the priming logic */ + if (charge_done) + batt_soc_prime = cl->cc_soc_max; + else + batt_soc_prime = div64_u64( + (uint64_t)batt_soc_cp * BATT_SOC_32BIT, + CENTI_FULL_SOC); + + rc = cl->prime_cc_soc(cl->data, batt_soc_prime); + if (rc < 0) + pr_err("Error in writing cc_soc_sw, rc=%d\n", + rc); + } + +out: + mutex_unlock(&cl->lock); +} + +/** + * cap_learning_abort - + * @cl: Capacity learning object + * + * Aborts the capacity learning and initializes variables + * + */ +void cap_learning_abort(struct cap_learning *cl) +{ + if (!cl) + return; + + mutex_lock(&cl->lock); + pr_debug("Aborting cap_learning\n"); + cl->active = false; + cl->init_cap_uah = 0; + mutex_unlock(&cl->lock); +} + +/** + * cap_learning_post_profile_init - + * @cl: Capacity learning object + * @nom_cap_uah: Nominal capacity of battery in uAh + * + * Called by FG/QG once the profile load is complete and nominal capacity + * of battery is known. This also gets the last learned capacity back from + * FG/QG to feed back to the algorithm. + * + */ +int cap_learning_post_profile_init(struct cap_learning *cl, int64_t nom_cap_uah) +{ + int64_t delta_cap_uah, pct_nom_cap_uah; + int rc; + + if (!cl || !cl->data) + return -EINVAL; + + mutex_lock(&cl->lock); + cl->nom_cap_uah = nom_cap_uah; + rc = cl->get_learned_capacity(cl->data, &cl->learned_cap_uah); + if (rc < 0) { + pr_err("Couldn't get learned capacity, rc=%d\n", rc); + goto out; + } + + if (cl->learned_cap_uah != cl->nom_cap_uah) { + if (cl->learned_cap_uah == 0) + cl->learned_cap_uah = cl->nom_cap_uah; + + delta_cap_uah = abs(cl->learned_cap_uah - cl->nom_cap_uah); + pct_nom_cap_uah = div64_s64((int64_t)cl->nom_cap_uah * + CAPACITY_DELTA_DECIPCT, 1000); + /* + * If the learned capacity is out of range by 50% from the + * nominal capacity, then overwrite the learned capacity with + * the nominal capacity. + */ + if (cl->nom_cap_uah && delta_cap_uah > pct_nom_cap_uah) { + pr_debug("learned_cap_uah: %lld is higher than expected, capping it to nominal: %lld\n", + cl->learned_cap_uah, cl->nom_cap_uah); + cl->learned_cap_uah = cl->nom_cap_uah; + } + + rc = cl->store_learned_capacity(cl->data, cl->learned_cap_uah); + if (rc < 0) + pr_err("Error in storing learned_cap_uah, rc=%d\n", rc); + } + +out: + mutex_unlock(&cl->lock); + return rc; +} + +/** + * cap_learning_init - + * @cl: Capacity learning object + * + * FG/QG have to call this during driver probe to validate the required + * parameters after allocating cap_learning object. + * + */ +int cap_learning_init(struct cap_learning *cl) +{ + if (!cl) + return -ENODEV; + + if (!cl->get_learned_capacity || !cl->store_learned_capacity || + !cl->get_cc_soc) { + pr_err("Insufficient functions for supporting capacity learning\n"); + return -EINVAL; + } + + if (!cl->cc_soc_max) { + pr_err("Insufficient parameters for supporting capacity learning\n"); + return -EINVAL; + } + + mutex_init(&cl->lock); + return 0; +} + +/* SOH based profile loading */ + +/** + * soh_get_batt_age_level - + * @sp: SOH profile object + * @soh: SOH level + * @batt_age_level: Battery age level if exists for the SOH passed + * + */ +static int soh_get_batt_age_level(struct soh_profile *sp, int soh, + int *batt_age_level) +{ + struct soh_range *range = sp->soh_data; + int i; + + for (i = 0; i < sp->profile_count; i++) { + if (is_between(range[i].soh_min, range[i].soh_max, soh)) { + *batt_age_level = range[i].batt_age_level; + return 0; + } + } + + return -ENOENT; +} + +/** + * soh_profile_update - + * @sp: SOH profile object + * @new_soh: SOH level that is updated and notified to FG/QG driver + * + * FG/QG have to call this whenever SOH is notified by the userspace. + * + */ +int soh_profile_update(struct soh_profile *sp, int new_soh) +{ + union power_supply_propval pval = {0, }; + int rc, batt_age_level = 0; + + if (!sp || !sp->bms_psy) + return -ENODEV; + + if (new_soh <= 0) + return 0; + + if (sp->last_soh <= 0) + pr_debug("SOH initialized to %d\n", new_soh); + else if (new_soh != sp->last_soh) + pr_debug("SOH changed from %d to %d\n", sp->last_soh, new_soh); + + sp->last_soh = new_soh; + + rc = soh_get_batt_age_level(sp, new_soh, &batt_age_level); + if (rc < 0) + return rc; + + if (batt_age_level != sp->last_batt_age_level) { + pval.intval = batt_age_level; + rc = power_supply_set_property(sp->bms_psy, + POWER_SUPPLY_PROP_BATT_AGE_LEVEL, &pval); + if (rc < 0) { + pr_err("Couldn't set batt_age_level rc=%d\n", rc); + return rc; + } + + sp->last_batt_age_level = batt_age_level; + pr_info("Batt_age_level set to %d for SOH %d\n", + batt_age_level, new_soh); + } + + return 0; +} + +/** + * soh_profile_init - + * @dev: Device node of FG/QG + * @sp: SOH profile object + * + * FG/QG have to call this after parsing battery profile node and multiple + * profile load feature is enabled. SOH profile object should have atleast + * the power supply of FG/QG and battery profile node. SOH specific range + * data is allocated by this function. + * + */ +int soh_profile_init(struct device *dev, struct soh_profile *sp) +{ + int rc, profile_count = 0; + + if (!dev || !sp || !sp->bp_node || !sp->bms_psy) + return -ENODEV; + + rc = of_batterydata_get_aged_profile_count(sp->bp_node, + sp->batt_id_kohms, &profile_count); + if (rc < 0) { + pr_err("Couldn't get profile count rc=%d\n", rc); + return rc; + } + + sp->soh_data = devm_kcalloc(dev, profile_count, sizeof(*sp->soh_data), + GFP_KERNEL); + if (!sp->soh_data) + return -ENOMEM; + + rc = of_batterydata_read_soh_aged_profiles(sp->bp_node, + sp->batt_id_kohms, sp->soh_data); + if (rc < 0) { + pr_err("Couldn't read SOH data for profile loading, rc=%d\n", + rc); + return rc; + } + + sp->profile_count = profile_count; + sp->last_soh = -EINVAL; + sp->initialized = true; + return 0; +} + +/* Time to full/empty algorithm helper functions */ + +static void ttf_circ_buf_add(struct ttf_circ_buf *buf, int val) +{ + buf->arr[buf->head] = val; + buf->head = (buf->head + 1) % ARRAY_SIZE(buf->arr); + buf->size = min(++buf->size, (int)ARRAY_SIZE(buf->arr)); +} + +static void ttf_circ_buf_clr(struct ttf_circ_buf *buf) +{ + buf->size = 0; + buf->head = 0; + memset(buf->arr, 0, sizeof(buf->arr)); +} + +static int cmp_int(const void *a, const void *b) +{ + return *(int *)a - *(int *)b; +} + +static int ttf_circ_buf_median(struct ttf_circ_buf *buf, int *median) +{ + int *temp; + + if (buf->size == 0) + return -ENODATA; + + if (buf->size == 1) { + *median = buf->arr[0]; + return 0; + } + + temp = kmalloc_array(buf->size, sizeof(*temp), GFP_KERNEL); + if (!temp) + return -ENOMEM; + + memcpy(temp, buf->arr, buf->size * sizeof(*temp)); + sort(temp, buf->size, sizeof(*temp), cmp_int, NULL); + + if (buf->size % 2) + *median = temp[buf->size / 2]; + else + *median = (temp[buf->size / 2 - 1] + temp[buf->size / 2]) / 2; + + kfree(temp); + return 0; +} + +static int ttf_lerp(const struct ttf_pt *pts, size_t tablesize, + s32 input, s32 *output) +{ + int i; + s64 temp; + + if (pts == NULL) { + pr_err("Table is NULL\n"); + return -EINVAL; + } + + if (tablesize < 1) { + pr_err("Table has no entries\n"); + return -ENOENT; + } + + if (tablesize == 1) { + *output = pts[0].y; + return 0; + } + + if (pts[0].x > pts[1].x) { + pr_err("Table is not in acending order\n"); + return -EINVAL; + } + + if (input <= pts[0].x) { + *output = pts[0].y; + return 0; + } + + if (input >= pts[tablesize - 1].x) { + *output = pts[tablesize - 1].y; + return 0; + } + + for (i = 1; i < tablesize; i++) { + if (input >= pts[i].x) + continue; + + temp = ((s64)pts[i].y - pts[i - 1].y) * + ((s64)input - pts[i - 1].x); + temp = div_s64(temp, pts[i].x - pts[i - 1].x); + *output = temp + pts[i - 1].y; + return 0; + } + + return -EINVAL; +} + +static int get_step_chg_current_window(struct ttf *ttf) +{ + struct range_data *step_chg_cfg = ttf->step_chg_cfg; + int i, rc, curr_window, vbatt; + + if (ttf->mode == TTF_MODE_VBAT_STEP_CHG) { + rc = ttf->get_ttf_param(ttf->data, TTF_VBAT, &vbatt); + if (rc < 0) { + pr_err("failed to get battery voltage, rc=%d\n", rc); + return rc; + } + } else { + rc = ttf->get_ttf_param(ttf->data, TTF_OCV, &vbatt); + if (rc < 0) { + pr_err("failed to get battery OCV, rc=%d\n", rc); + return rc; + } + } + + curr_window = ttf->step_chg_num_params - 1; + for (i = 0; i < ttf->step_chg_num_params; i++) { + if (is_between(step_chg_cfg[i].low_threshold, + step_chg_cfg[i].high_threshold, + vbatt)) + curr_window = i; + } + + return curr_window; +} + +static int get_cc2cv_current(struct ttf *ttf, int ibatt_avg, int vbatt_avg, + int float_volt_uv) +{ + int i_cc2cv = 0; + + switch (ttf->mode) { + case TTF_MODE_NORMAL: + case TTF_MODE_VBAT_STEP_CHG: + case TTF_MODE_OCV_STEP_CHG: + i_cc2cv = ibatt_avg * vbatt_avg / + max(MILLI_UNIT, float_volt_uv / MILLI_UNIT); + break; + case TTF_MODE_QNOVO: + i_cc2cv = min( + ttf->cc_step.arr[MAX_CC_STEPS - 1] / MILLI_UNIT, + ibatt_avg * vbatt_avg / + max(MILLI_UNIT, float_volt_uv / MILLI_UNIT)); + break; + default: + pr_err("TTF mode %d is not supported\n", ttf->mode); + break; + } + + return i_cc2cv; +} + +static int get_time_to_full_locked(struct ttf *ttf, int *val) +{ + struct step_chg_data *step_chg_data = ttf->step_chg_data; + struct range_data *step_chg_cfg = ttf->step_chg_cfg; + int rc, ibatt_avg, vbatt_avg, rbatt = 0, msoc = 0, act_cap_mah = 0, + i_cc2cv = 0, soc_cc2cv, tau, divisor, iterm = 0, ttf_mode = 0, + i, soc_per_step, msoc_this_step, msoc_next_step, + ibatt_this_step, t_predicted_this_step, ttf_slope, + t_predicted_cv, t_predicted = 0, charge_type = 0, i_step, + float_volt_uv = 0, valid = 0, charge_status = 0; + int multiplier, curr_window = 0, pbatt_avg; + bool power_approx = false; + s64 delta_ms; + + rc = ttf->get_ttf_param(ttf->data, TTF_TTE_VALID, &valid); + if (rc < 0) { + pr_err("failed to get ttf_tte_valid rc=%d\n", rc); + return rc; + } + + if (!valid) { + *val = -1; + return 0; + } + + rc = ttf->get_ttf_param(ttf->data, TTF_CHG_STATUS, &charge_status); + if (rc < 0) { + pr_err("failed to get charge-status rc=%d\n", rc); + return rc; + } + + if (charge_status != POWER_SUPPLY_STATUS_CHARGING) { + *val = -1; + return 0; + } + + rc = ttf->get_ttf_param(ttf->data, TTF_MSOC, &msoc); + if (rc < 0) { + pr_err("failed to get msoc rc=%d\n", rc); + return rc; + } + pr_debug("TTF: msoc=%d\n", msoc); + + /* the battery is considered full if the SOC is 100% */ + if (msoc >= 100) { + *val = 0; + return 0; + } + + rc = ttf->get_ttf_param(ttf->data, TTF_MODE, &ttf_mode); + + /* when switching TTF algorithms the TTF needs to be reset */ + if (ttf->mode != ttf_mode) { + ttf_circ_buf_clr(&ttf->ibatt); + ttf_circ_buf_clr(&ttf->vbatt); + ttf->last_ttf = 0; + ttf->last_ms = 0; + ttf->mode = ttf_mode; + } + + /* at least 10 samples are required to produce a stable IBATT */ + if (ttf->ibatt.size < MAX_TTF_SAMPLES) { + if (ttf->clear_ibatt) + *val = ttf->last_ttf; + else + *val = -1; + return 0; + } + + rc = ttf_circ_buf_median(&ttf->ibatt, &ibatt_avg); + if (rc < 0) { + pr_err("failed to get IBATT AVG rc=%d\n", rc); + return rc; + } + + rc = ttf_circ_buf_median(&ttf->vbatt, &vbatt_avg); + if (rc < 0) { + pr_err("failed to get VBATT AVG rc=%d\n", rc); + return rc; + } + + ttf->clear_ibatt = false; + ibatt_avg = -ibatt_avg / MILLI_UNIT; + vbatt_avg /= MILLI_UNIT; + + rc = ttf->get_ttf_param(ttf->data, TTF_ITERM, &iterm); + if (rc < 0) { + pr_err("failed to get iterm rc=%d\n", rc); + return rc; + } + /* clamp ibatt_avg to iterm */ + if (ibatt_avg < abs(iterm)) + ibatt_avg = abs(iterm); + + rc = ttf->get_ttf_param(ttf->data, TTF_RBATT, &rbatt); + if (rc < 0) { + pr_err("failed to get battery resistance rc=%d\n", rc); + return rc; + } + rbatt /= MILLI_UNIT; + + rc = ttf->get_ttf_param(ttf->data, TTF_FCC, &act_cap_mah); + if (rc < 0) { + pr_err("failed to get ACT_BATT_CAP rc=%d\n", rc); + return rc; + } + + pr_debug("TTF: ibatt_avg=%d vbatt_avg=%d rbatt=%d act_cap_mah=%d\n", + ibatt_avg, vbatt_avg, rbatt, act_cap_mah); + + rc = ttf->get_ttf_param(ttf->data, TTF_VFLOAT, &float_volt_uv); + if (rc < 0) { + pr_err("failed to get float_volt_uv rc=%d\n", rc); + return rc; + } + + rc = ttf->get_ttf_param(ttf->data, TTF_CHG_TYPE, &charge_type); + if (rc < 0) { + pr_err("failed to get charge_type rc=%d\n", rc); + return rc; + } + + pr_debug("TTF: mode: %d\n", ttf->mode); + + /* estimated battery current at the CC to CV transition */ + i_cc2cv = get_cc2cv_current(ttf, ibatt_avg, vbatt_avg, float_volt_uv); + pr_debug("TTF: i_cc2cv=%d\n", i_cc2cv); + + /* if we are already in CV state then we can skip estimating CC */ + if (charge_type == POWER_SUPPLY_CHARGE_TYPE_TAPER) + goto cv_estimate; + + /* estimated SOC at the CC to CV transition */ + soc_cc2cv = DIV_ROUND_CLOSEST(rbatt * i_cc2cv, OCV_SLOPE_UV); + soc_cc2cv = 100 - soc_cc2cv; + pr_debug("TTF: soc_cc2cv=%d\n", soc_cc2cv); + + switch (ttf->mode) { + case TTF_MODE_NORMAL: + if (soc_cc2cv - msoc <= 0) + goto cv_estimate; + + divisor = max(100, (ibatt_avg + i_cc2cv) / 2 * 100); + t_predicted = div_s64((s64)act_cap_mah * (soc_cc2cv - msoc) * + HOURS_TO_SECONDS, divisor); + break; + case TTF_MODE_QNOVO: + soc_per_step = 100 / MAX_CC_STEPS; + for (i = msoc / soc_per_step; i < MAX_CC_STEPS - 1; ++i) { + msoc_next_step = (i + 1) * soc_per_step; + if (i == msoc / soc_per_step) + msoc_this_step = msoc; + else + msoc_this_step = i * soc_per_step; + + /* scale ibatt by 85% to account for discharge pulses */ + ibatt_this_step = min( + ttf->cc_step.arr[i] / MILLI_UNIT, + ibatt_avg) * 85 / 100; + divisor = max(100, ibatt_this_step * 100); + t_predicted_this_step = div_s64((s64)act_cap_mah * + (msoc_next_step - msoc_this_step) * + HOURS_TO_SECONDS, divisor); + t_predicted += t_predicted_this_step; + pr_debug("TTF: [%d, %d] ma=%d t=%d\n", + msoc_this_step, msoc_next_step, + ibatt_this_step, t_predicted_this_step); + } + break; + case TTF_MODE_VBAT_STEP_CHG: + case TTF_MODE_OCV_STEP_CHG: + if (!step_chg_data || !step_chg_cfg) + break; + + pbatt_avg = vbatt_avg * ibatt_avg; + curr_window = get_step_chg_current_window(ttf); + if (curr_window < 0) { + pr_err("Failed to get step charging window\n"); + return curr_window; + } + + pr_debug("TTF: curr_window: %d pbatt_avg: %d\n", curr_window, + pbatt_avg); + + t_predicted_this_step = 0; + for (i = 0; i < ttf->step_chg_num_params; i++) { + /* + * If Ibatt_avg differs by step charging threshold by + * more than 100 mA, then use power approximation to + * get charging current step. + */ + + if (step_chg_cfg[i].value - ibatt_avg > 100) + power_approx = true; + + /* Calculate OCV for each window */ + if (power_approx) { + i_step = pbatt_avg / max(MILLI_UNIT, + (step_chg_cfg[i].high_threshold / + MILLI_UNIT)); + } else { + if (i == curr_window) + i_step = ((step_chg_cfg[i].value / + MILLI_UNIT) + + ibatt_avg) / 2; + else + i_step = (step_chg_cfg[i].value / + MILLI_UNIT); + } + + if (ttf->mode == TTF_MODE_VBAT_STEP_CHG) + step_chg_data[i].ocv = + step_chg_cfg[i].high_threshold - + (rbatt * i_step); + else + step_chg_data[i].ocv = + step_chg_cfg[i].high_threshold; + + /* Calculate SOC for each window */ + step_chg_data[i].soc = (float_volt_uv - + step_chg_data[i].ocv) / OCV_SLOPE_UV; + step_chg_data[i].soc = 100 - step_chg_data[i].soc; + + /* Calculate CC time for each window */ + multiplier = act_cap_mah * HOURS_TO_SECONDS; + if (curr_window > 0 && i < curr_window) + t_predicted_this_step = 0; + else if (i == curr_window) + t_predicted_this_step = + div_s64((s64)multiplier * + (step_chg_data[i].soc - msoc), + i_step); + else if (i > 0) + t_predicted_this_step = + div_s64((s64)multiplier * + (step_chg_data[i].soc - + step_chg_data[i - 1].soc), + i_step); + + if (t_predicted_this_step < 0) + t_predicted_this_step = 0; + + t_predicted_this_step = + DIV_ROUND_CLOSEST(t_predicted_this_step, 100); + pr_debug("TTF: step: %d i_step: %d OCV: %d SOC: %d t_pred: %d\n", + i, i_step, step_chg_data[i].ocv, + step_chg_data[i].soc, t_predicted_this_step); + t_predicted += t_predicted_this_step; + } + + break; + default: + pr_err("TTF mode %d is not supported\n", ttf->mode); + break; + } + +cv_estimate: + pr_debug("TTF: t_predicted_cc=%d\n", t_predicted); + + if (charge_type == POWER_SUPPLY_CHARGE_TYPE_TAPER) + iterm = max(100, abs(iterm)); + else + iterm = max(100, abs(iterm) + ttf->iterm_delta); + + pr_debug("TTF: iterm=%d\n", iterm); + + if (charge_type == POWER_SUPPLY_CHARGE_TYPE_TAPER) + tau = max(MILLI_UNIT, ibatt_avg * MILLI_UNIT / iterm); + else + tau = max(MILLI_UNIT, i_cc2cv * MILLI_UNIT / iterm); + + rc = ttf_lerp(ttf_ln_table, ARRAY_SIZE(ttf_ln_table), tau, &tau); + if (rc < 0) { + pr_err("failed to interpolate tau rc=%d\n", rc); + return rc; + } + + /* tau is scaled linearly from 95% to 100% SOC */ + if (msoc >= 95) + tau = tau * 2 * (100 - msoc) / 10; + + pr_debug("TTF: tau=%d\n", tau); + t_predicted_cv = div_s64((s64)act_cap_mah * rbatt * tau * + HOURS_TO_SECONDS, NANO_UNIT); + pr_debug("TTF: t_predicted_cv=%d\n", t_predicted_cv); + t_predicted += t_predicted_cv; + + pr_debug("TTF: t_predicted_prefilter=%d\n", t_predicted); + if (ttf->last_ms != 0) { + delta_ms = ktime_ms_delta(ktime_get_boottime(), + ms_to_ktime(ttf->last_ms)); + if (delta_ms > 10000) { + ttf_slope = div64_s64( + ((s64)t_predicted - ttf->last_ttf) * + MICRO_UNIT, delta_ms); + if (ttf_slope > -100) + ttf_slope = -100; + else if (ttf_slope < -2000) + ttf_slope = -2000; + + t_predicted = div_s64( + (s64)ttf_slope * delta_ms, MICRO_UNIT) + + ttf->last_ttf; + pr_debug("TTF: ttf_slope=%d\n", ttf_slope); + } else { + t_predicted = ttf->last_ttf; + } + } + + /* clamp the ttf to 0 */ + if (t_predicted < 0) + t_predicted = 0; + + pr_debug("TTF: t_predicted_postfilter=%d\n", t_predicted); + *val = t_predicted; + return 0; +} + +/** + * ttf_get_time_to_full - + * @ttf: ttf object + * @val: Average time to full returned to the caller + * + * Get Average time to full the battery based on current soc, rbatt + * battery voltage and charge current etc. + */ +int ttf_get_time_to_full(struct ttf *ttf, int *val) +{ + int rc; + + mutex_lock(&ttf->lock); + rc = get_time_to_full_locked(ttf, val); + mutex_unlock(&ttf->lock); + + return rc; +} + +#define DELTA_TTF_IBATT_UA 500000 +static void ttf_work(struct work_struct *work) +{ + struct ttf *ttf = container_of(work, + struct ttf, ttf_work.work); + int rc, ibatt_now, vbatt_now, ttf_now, charge_status, ibatt_avg, + msoc = 0, charge_done; + ktime_t ktime_now; + + mutex_lock(&ttf->lock); + rc = ttf->get_ttf_param(ttf->data, TTF_CHG_STATUS, &charge_status); + if (rc < 0) { + pr_err("failed to get charge_status rc=%d\n", rc); + goto end_work; + } + + rc = ttf->get_ttf_param(ttf->data, TTF_CHG_DONE, &charge_done); + if (rc < 0) { + pr_err("failed to get charge_done rc=%d\n", rc); + goto end_work; + } + + rc = ttf->get_ttf_param(ttf->data, TTF_MSOC, &msoc); + if (rc < 0) { + pr_err("failed to get msoc, rc=%d\n", rc); + goto end_work; + } + pr_debug("TTF: charge_status:%d charge_done:%d msoc:%d\n", + charge_status, charge_done, msoc); + /* Do not schedule ttf work if SOC is 100% or charge teminated. */ + if (charge_done || + ((msoc == 100) && + (charge_status == POWER_SUPPLY_STATUS_CHARGING))) + goto end_work; + + rc = ttf->get_ttf_param(ttf->data, TTF_IBAT, &ibatt_now); + if (rc < 0) { + pr_err("failed to get battery current, rc=%d\n", rc); + goto end_work; + } + + rc = ttf->get_ttf_param(ttf->data, TTF_VBAT, &vbatt_now); + if (rc < 0) { + pr_err("failed to get battery voltage, rc=%d\n", rc); + goto end_work; + } + + ttf_circ_buf_add(&ttf->ibatt, ibatt_now); + ttf_circ_buf_add(&ttf->vbatt, vbatt_now); + + if (charge_status == POWER_SUPPLY_STATUS_CHARGING) { + rc = ttf_circ_buf_median(&ttf->ibatt, &ibatt_avg); + if (rc < 0) { + pr_err("failed to get IBATT AVG rc=%d\n", rc); + goto end_work; + } + + /* + * While Charging, if Ibatt_now differ from Ibatt_avg by 500mA, + * clear Ibatt buffer and refill with settled Ibatt values, to + * calculate accurate TTF + */ + if (ibatt_now < 0 && (abs(ibatt_now - + ibatt_avg) >= DELTA_TTF_IBATT_UA)) { + pr_debug("Clear Ibatt buffer, Ibatt_avg=%d Ibatt_now=%d\n", + ibatt_avg, ibatt_now); + ttf_circ_buf_clr(&ttf->ibatt); + ttf->clear_ibatt = true; + } + + rc = get_time_to_full_locked(ttf, &ttf_now); + if (rc < 0) { + pr_err("failed to get ttf, rc=%d\n", rc); + goto end_work; + } + + /* keep the wake lock and prime the IBATT and VBATT buffers */ + if (ttf_now < 0 || ttf->clear_ibatt) { + /* delay for one FG cycle */ + schedule_delayed_work(&ttf->ttf_work, + msecs_to_jiffies(1000)); + mutex_unlock(&ttf->lock); + return; + } + + /* update the TTF reference point every minute */ + ktime_now = ktime_get_boottime(); + if (ktime_ms_delta(ktime_now, + ms_to_ktime(ttf->last_ms)) > 60000 || + ttf->last_ms == 0) { + ttf->last_ttf = ttf_now; + ttf->last_ms = ktime_to_ms(ktime_now); + } + } + + /* recurse every 10 seconds */ + schedule_delayed_work(&ttf->ttf_work, msecs_to_jiffies(ttf->period_ms)); +end_work: + ttf->awake_voter(ttf->data, false); + mutex_unlock(&ttf->lock); +} + +/** + * ttf_get_time_to_empty - + * @ttf: ttf object + * @val: Average time to empty returned to the caller + * + * Get Average time to empty the battery based on current soc + * and average battery current. + */ +int ttf_get_time_to_empty(struct ttf *ttf, int *val) +{ + int rc, ibatt_avg, msoc, act_cap_mah, divisor, valid = 0, + charge_status = 0; + + rc = ttf->get_ttf_param(ttf->data, TTF_TTE_VALID, &valid); + if (rc < 0) { + pr_err("failed to get ttf_tte_valid rc=%d\n", rc); + return rc; + } + + if (!valid) { + *val = -1; + return 0; + } + + rc = ttf->get_ttf_param(ttf->data, TTF_CHG_STATUS, &charge_status); + if (rc < 0) { + pr_err("failed to get charge-status rc=%d\n", rc); + return rc; + } + + if (charge_status == POWER_SUPPLY_STATUS_CHARGING) { + *val = -1; + return 0; + } + + rc = ttf_circ_buf_median(&ttf->ibatt, &ibatt_avg); + if (rc < 0) { + /* try to get instantaneous current */ + rc = ttf->get_ttf_param(ttf->data, TTF_IBAT, &ibatt_avg); + if (rc < 0) { + pr_err("failed to get battery current, rc=%d\n", rc); + return rc; + } + } + + ibatt_avg /= MILLI_UNIT; + /* clamp ibatt_avg to 100mA */ + if (ibatt_avg < 100) + ibatt_avg = 100; + + rc = ttf->get_ttf_param(ttf->data, TTF_MSOC, &msoc); + if (rc < 0) { + pr_err("Error in getting capacity, rc=%d\n", rc); + return rc; + } + + rc = ttf->get_ttf_param(ttf->data, TTF_FCC, &act_cap_mah); + if (rc < 0) { + pr_err("Error in getting ACT_BATT_CAP, rc=%d\n", rc); + return rc; + } + + divisor = CENTI_ICORRECT_C0 * 100 + CENTI_ICORRECT_C1 * msoc; + divisor = ibatt_avg * divisor / 100; + divisor = max(100, divisor); + *val = act_cap_mah * msoc * HOURS_TO_SECONDS / divisor; + + pr_debug("TTF: ibatt_avg=%d msoc=%d act_cap_mah=%d TTE=%d\n", + ibatt_avg, msoc, act_cap_mah, *val); + + return 0; +} + +/** + * ttf_update - + * @ttf: ttf object + * @input_present: Indicator for input presence + * + * Called by FG/QG driver when there is a state change (Charging status, SOC) + * + */ +void ttf_update(struct ttf *ttf, bool input_present) +{ + int delay_ms; + + if (ttf->input_present == input_present) + return; + + ttf->input_present = input_present; + if (input_present) + /* wait 35 seconds for the input to settle */ + delay_ms = 35000; + else + /* wait 5 seconds for current to settle during discharge */ + delay_ms = 5000; + + ttf->awake_voter(ttf->data, true); + cancel_delayed_work_sync(&ttf->ttf_work); + mutex_lock(&ttf->lock); + ttf_circ_buf_clr(&ttf->ibatt); + ttf_circ_buf_clr(&ttf->vbatt); + ttf->last_ttf = 0; + ttf->last_ms = 0; + mutex_unlock(&ttf->lock); + schedule_delayed_work(&ttf->ttf_work, msecs_to_jiffies(delay_ms)); +} + +/** + * ttf_tte_init - + * @ttf: Time to full object + * + * FG/QG have to call this during driver probe to validate the required + * parameters after allocating ttf object. + * + */ +int ttf_tte_init(struct ttf *ttf) +{ + if (!ttf) + return -ENODEV; + + if (!ttf->awake_voter || !ttf->get_ttf_param) { + pr_err("Insufficient functions for supporting ttf\n"); + return -EINVAL; + } + + if (!ttf->iterm_delta) + ttf->iterm_delta = DEFAULT_TTF_ITERM_DELTA_MA; + if (!ttf->period_ms) + ttf->period_ms = DEFAULT_TTF_RUN_PERIOD_MS; + + mutex_init(&ttf->lock); + INIT_DELAYED_WORK(&ttf->ttf_work, ttf_work); + + return 0; +} diff --git a/drivers/power/supply/qcom/fg-alg.h b/drivers/power/supply/qcom/fg-alg.h new file mode 100644 index 000000000000..99626f671152 --- /dev/null +++ b/drivers/power/supply/qcom/fg-alg.h @@ -0,0 +1,170 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2016-2020, The Linux Foundation. All rights reserved. + */ + +#ifndef __FG_ALG_H__ +#define __FG_ALG_H__ + +#include "battery-profile-loader.h" +#include "step-chg-jeita.h" + +#define BUCKET_COUNT 8 +#define BUCKET_SOC_PCT (256 / BUCKET_COUNT) +#define MAX_CC_STEPS 20 +#define MAX_TTF_SAMPLES 10 + +#define is_between(left, right, value) \ + (((left) >= (right) && (left) >= (value) \ + && (value) >= (right)) \ + || ((left) <= (right) && (left) <= (value) \ + && (value) <= (right))) +struct cycle_counter { + void *data; + char str_buf[BUCKET_COUNT * 8]; + bool started[BUCKET_COUNT]; + u16 count[BUCKET_COUNT]; + u8 last_soc[BUCKET_COUNT]; + int id; + int last_bucket; + struct mutex lock; + int (*restore_count)(void *data, u16 *buf, int num_bytes); + int (*store_count)(void *data, u16 *buf, int id, int num_bytes); +}; + +struct cl_params { + int min_start_soc; + int max_start_soc; + int max_temp; + int min_temp; + int max_cap_inc; + int max_cap_dec; + int max_cap_limit; + int min_cap_limit; + int skew_decipct; + int min_delta_batt_soc; + int ibat_flt_thr_ma; + bool cl_wt_enable; +}; + +struct cap_learning { + void *data; + int init_cc_soc_sw; + int cc_soc_max; + int init_batt_soc; + int init_batt_soc_cp; + int64_t nom_cap_uah; + int64_t init_cap_uah; + int64_t final_cap_uah; + int64_t learned_cap_uah; + int64_t delta_cap_uah; + bool active; + struct mutex lock; + struct cl_params dt; + bool (*ok_to_begin)(void *data); + int (*get_learned_capacity)(void *data, int64_t *learned_cap_uah); + int (*store_learned_capacity)(void *data, int64_t learned_cap_uah); + int (*get_cc_soc)(void *data, int *cc_soc_sw); + int (*prime_cc_soc)(void *data, u32 cc_soc_sw); +}; + +enum ttf_mode { + TTF_MODE_NORMAL = 0, + TTF_MODE_QNOVO, + TTF_MODE_VBAT_STEP_CHG, + TTF_MODE_OCV_STEP_CHG, +}; + +enum ttf_param { + TTF_MSOC = 0, + TTF_VBAT, + TTF_OCV, + TTF_IBAT, + TTF_FCC, + TTF_MODE, + TTF_ITERM, + TTF_RBATT, + TTF_VFLOAT, + TTF_CHG_TYPE, + TTF_CHG_STATUS, + TTF_TTE_VALID, + TTF_CHG_DONE, +}; + +struct ttf_circ_buf { + int arr[MAX_TTF_SAMPLES]; + int size; + int head; +}; + +struct ttf_cc_step_data { + int arr[MAX_CC_STEPS]; + int sel; +}; + +struct ttf_pt { + s32 x; + s32 y; +}; + +struct step_chg_data { + int ocv; + int soc; +}; + +struct ttf { + void *data; + struct ttf_circ_buf ibatt; + struct ttf_circ_buf vbatt; + struct ttf_cc_step_data cc_step; + struct mutex lock; + struct step_chg_data *step_chg_data; + struct range_data *step_chg_cfg; + bool step_chg_cfg_valid; + bool ocv_step_chg_cfg_valid; + bool clear_ibatt; + int step_chg_num_params; + int mode; + int last_ttf; + int input_present; + int iterm_delta; + int period_ms; + s64 last_ms; + struct delayed_work ttf_work; + int (*get_ttf_param)(void *data, enum ttf_param, int *val); + int (*awake_voter)(void *data, bool vote); +}; + +struct soh_profile { + struct device_node *bp_node; + struct power_supply *bms_psy; + struct soh_range *soh_data; + int batt_id_kohms; + int profile_count; + int last_soh; + int last_batt_age_level; + bool initialized; +}; + +int restore_cycle_count(struct cycle_counter *counter); +void clear_cycle_count(struct cycle_counter *counter); +void cycle_count_update(struct cycle_counter *counter, int batt_soc, + int charge_status, bool charge_done, bool input_present); +int get_cycle_count(struct cycle_counter *counter, int *count); +int get_cycle_counts(struct cycle_counter *counter, const char **buf); +int cycle_count_init(struct cycle_counter *counter); +void cap_learning_abort(struct cap_learning *cl); +void cap_learning_update(struct cap_learning *cl, int batt_temp, + int batt_soc, int charge_status, bool charge_done, + bool input_present, bool qnovo_en); +int cap_learning_init(struct cap_learning *cl); +int cap_learning_post_profile_init(struct cap_learning *cl, + int64_t nom_cap_uah); +void ttf_update(struct ttf *ttf, bool input_present); +int ttf_get_time_to_empty(struct ttf *ttf, int *val); +int ttf_get_time_to_full(struct ttf *ttf, int *val); +int ttf_tte_init(struct ttf *ttf); +int soh_profile_init(struct device *dev, struct soh_profile *sp); +int soh_profile_update(struct soh_profile *sp, int soh); + +#endif diff --git a/drivers/power/supply/qcom/pmic-voter.c b/drivers/power/supply/qcom/pmic-voter.c new file mode 100644 index 000000000000..b10c2a060b10 --- /dev/null +++ b/drivers/power/supply/qcom/pmic-voter.c @@ -0,0 +1,832 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2015-2017, 2019-2020, The Linux Foundation. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#define NUM_MAX_CLIENTS 32 +#define DEBUG_FORCE_CLIENT "DEBUG_FORCE_CLIENT" + +static DEFINE_SPINLOCK(votable_list_slock); +static LIST_HEAD(votable_list); + +static struct dentry *debug_root; + +struct client_vote { + bool enabled; + int value; +}; + +struct votable { + const char *name; + const char *override_client; + struct list_head list; + struct client_vote votes[NUM_MAX_CLIENTS]; + int num_clients; + int type; + int effective_client_id; + int effective_result; + int override_result; + struct mutex vote_lock; + void *data; + int (*callback)(struct votable *votable, + void *data, + int effective_result, + const char *effective_client); + char *client_strs[NUM_MAX_CLIENTS]; + bool voted_on; + struct dentry *root; + struct dentry *status_ent; + u32 force_val; + struct dentry *force_val_ent; + bool force_active; + struct dentry *force_active_ent; +}; + +/** + * vote_set_any() + * @votable: votable object + * @client_id: client number of the latest voter + * @eff_res: sets 0 or 1 based on the voting + * @eff_id: Always returns the client_id argument + * + * Note that for SET_ANY voter, the value is always same as enabled. There is + * no idea of a voter abstaining from the election. Hence there is never a + * situation when the effective_id will be invalid, during election. + * + * Context: + * Must be called with the votable->lock held + */ +static void vote_set_any(struct votable *votable, int client_id, + int *eff_res, int *eff_id) +{ + int i; + + *eff_res = 0; + + for (i = 0; i < votable->num_clients && votable->client_strs[i]; i++) + *eff_res |= votable->votes[i].enabled; + + *eff_id = client_id; +} + +/** + * vote_min() - + * @votable: votable object + * @client_id: client number of the latest voter + * @eff_res: sets this to the min. of all the values amongst enabled voters. + * If there is no enabled client, this is set to INT_MAX + * @eff_id: sets this to the client id that has the min value amongst all + * the enabled clients. If there is no enabled client, sets this + * to -EINVAL + * + * Context: + * Must be called with the votable->lock held + */ +static void vote_min(struct votable *votable, int client_id, + int *eff_res, int *eff_id) +{ + int i; + + *eff_res = INT_MAX; + *eff_id = -EINVAL; + for (i = 0; i < votable->num_clients && votable->client_strs[i]; i++) { + if (votable->votes[i].enabled + && *eff_res > votable->votes[i].value) { + *eff_res = votable->votes[i].value; + *eff_id = i; + } + } + if (*eff_id == -EINVAL) + *eff_res = -EINVAL; +} + +/** + * vote_max() - + * @votable: votable object + * @client_id: client number of the latest voter + * @eff_res: sets this to the max. of all the values amongst enabled voters. + * If there is no enabled client, this is set to -EINVAL + * @eff_id: sets this to the client id that has the max value amongst all + * the enabled clients. If there is no enabled client, sets this to + * -EINVAL + * + * Context: + * Must be called with the votable->lock held + */ +static void vote_max(struct votable *votable, int client_id, + int *eff_res, int *eff_id) +{ + int i; + + *eff_res = INT_MIN; + *eff_id = -EINVAL; + for (i = 0; i < votable->num_clients && votable->client_strs[i]; i++) { + if (votable->votes[i].enabled && + *eff_res < votable->votes[i].value) { + *eff_res = votable->votes[i].value; + *eff_id = i; + } + } + if (*eff_id == -EINVAL) + *eff_res = -EINVAL; +} + +static int get_client_id(struct votable *votable, const char *client_str) +{ + int i; + + for (i = 0; i < votable->num_clients; i++) { + if (votable->client_strs[i] + && (strcmp(votable->client_strs[i], client_str) == 0)) + return i; + } + + /* new client */ + for (i = 0; i < votable->num_clients; i++) { + if (!votable->client_strs[i]) { + votable->client_strs[i] + = kstrdup(client_str, GFP_KERNEL); + if (!votable->client_strs[i]) + return -ENOMEM; + return i; + } + } + return -EINVAL; +} + +static char *get_client_str(struct votable *votable, int client_id) +{ + if (!votable || (client_id == -EINVAL)) + return NULL; + + return votable->client_strs[client_id]; +} + +void lock_votable(struct votable *votable) +{ + mutex_lock(&votable->vote_lock); +} + +void unlock_votable(struct votable *votable) +{ + mutex_unlock(&votable->vote_lock); +} + +/** + * is_override_vote_enabled() - + * is_override_vote_enabled_locked() - + * The unlocked and locked variants of getting whether override + vote is enabled. + * @votable: the votable object + * + * Returns: + * True if the client's vote is enabled; false otherwise. + */ +bool is_override_vote_enabled_locked(struct votable *votable) +{ + if (!votable) + return false; + + return votable->override_result != -EINVAL; +} + +bool is_override_vote_enabled(struct votable *votable) +{ + bool enable; + + if (!votable) + return false; + + lock_votable(votable); + enable = is_override_vote_enabled_locked(votable); + unlock_votable(votable); + + return enable; +} + +/** + * is_client_vote_enabled() - + * is_client_vote_enabled_locked() - + * The unlocked and locked variants of getting whether a client's + vote is enabled. + * @votable: the votable object + * @client_str: client of interest + * + * Returns: + * True if the client's vote is enabled; false otherwise. + */ +bool is_client_vote_enabled_locked(struct votable *votable, + const char *client_str) +{ + + int client_id; + + if (!votable || !client_str) + return false; + + client_id = get_client_id(votable, client_str); + if (client_id < 0) + return false; + + return votable->votes[client_id].enabled; +} + +bool is_client_vote_enabled(struct votable *votable, const char *client_str) +{ + bool enabled; + + if (!votable || !client_str) + return false; + + lock_votable(votable); + enabled = is_client_vote_enabled_locked(votable, client_str); + unlock_votable(votable); + return enabled; +} + +/** + * get_client_vote() - + * get_client_vote_locked() - + * The unlocked and locked variants of getting a client's voted + * value. + * @votable: the votable object + * @client_str: client of interest + * + * Returns: + * The value the client voted for. -EINVAL is returned if the client + * is not enabled or the client is not found. + */ +int get_client_vote_locked(struct votable *votable, const char *client_str) +{ + int client_id; + + if (!votable || !client_str) + return -EINVAL; + + client_id = get_client_id(votable, client_str); + if (client_id < 0) + return -EINVAL; + + if ((votable->type != VOTE_SET_ANY) + && !votable->votes[client_id].enabled) + return -EINVAL; + + return votable->votes[client_id].value; +} + +int get_client_vote(struct votable *votable, const char *client_str) +{ + int value; + + if (!votable || !client_str) + return -EINVAL; + + lock_votable(votable); + value = get_client_vote_locked(votable, client_str); + unlock_votable(votable); + return value; +} + +/** + * get_effective_result() - + * get_effective_result_locked() - + * The unlocked and locked variants of getting the effective value + * amongst all the enabled voters. + * + * @votable: the votable object + * + * Returns: + * The effective result. + * For MIN and MAX votable, returns -EINVAL when the votable + * object has been created but no clients have casted their votes or + * the last enabled client disables its vote. + * For SET_ANY votable it returns 0 when no clients have casted their votes + * because for SET_ANY there is no concept of abstaining from election. The + * votes for all the clients of SET_ANY votable is defaulted to false. + */ +int get_effective_result_locked(struct votable *votable) +{ + if (!votable) + return -EINVAL; + + if (votable->force_active) + return votable->force_val; + + if (votable->override_result != -EINVAL) + return votable->override_result; + + return votable->effective_result; +} + +int get_effective_result(struct votable *votable) +{ + int value; + + if (!votable) + return -EINVAL; + + lock_votable(votable); + value = get_effective_result_locked(votable); + unlock_votable(votable); + return value; +} + +/** + * get_effective_client() - + * get_effective_client_locked() - + * The unlocked and locked variants of getting the effective client + * amongst all the enabled voters. + * + * @votable: the votable object + * + * Returns: + * The effective client. + * For MIN and MAX votable, returns NULL when the votable + * object has been created but no clients have casted their votes or + * the last enabled client disables its vote. + * For SET_ANY votable it returns NULL too when no clients have casted + * their votes. But for SET_ANY since there is no concept of abstaining + * from election, the only client that casts a vote or the client that + * caused the result to change is returned. + */ +const char *get_effective_client_locked(struct votable *votable) +{ + if (!votable) + return NULL; + + if (votable->force_active) + return DEBUG_FORCE_CLIENT; + + if (votable->override_result != -EINVAL) + return votable->override_client; + + return get_client_str(votable, votable->effective_client_id); +} + +const char *get_effective_client(struct votable *votable) +{ + const char *client_str; + + if (!votable) + return NULL; + + lock_votable(votable); + client_str = get_effective_client_locked(votable); + unlock_votable(votable); + return client_str; +} + +/** + * vote() - + * + * @votable: the votable object + * @client_str: the voting client + * @enabled: This provides a means for the client to exclude himself from + * election. This clients val (the next argument) will be + * considered only when he has enabled his participation. + * Note that this takes a differnt meaning for SET_ANY type, as + * there is no concept of abstaining from participation. + * Enabled is treated as the boolean value the client is voting. + * @val: The vote value. This is ignored for SET_ANY votable types. + * For MIN, MAX votable types this value is used as the + * clients vote value when the enabled is true, this value is + * ignored if enabled is false. + * + * The callback is called only when there is a change in the election results or + * if it is the first time someone is voting. + * + * Returns: + * The return from the callback when present and needs to be called + * or zero. + */ +int vote(struct votable *votable, const char *client_str, bool enabled, int val) +{ + int effective_id = -EINVAL; + int effective_result; + int client_id; + int rc = 0; + bool similar_vote = false; + + if (!votable || !client_str) + return -EINVAL; + + lock_votable(votable); + + client_id = get_client_id(votable, client_str); + if (client_id < 0) { + rc = client_id; + goto out; + } + + /* + * for SET_ANY the val is to be ignored, set it + * to enabled so that the election still works based on + * value regardless of the type + */ + if (votable->type == VOTE_SET_ANY) + val = enabled; + + if ((votable->votes[client_id].enabled == enabled) && + (votable->votes[client_id].value == val)) { + pr_debug("%s: %s,%d same vote %s of val=%d\n", + votable->name, + client_str, client_id, + enabled ? "on" : "off", + val); + similar_vote = true; + } + + votable->votes[client_id].enabled = enabled; + votable->votes[client_id].value = val; + + if (similar_vote && votable->voted_on) { + pr_debug("%s: %s,%d Ignoring similar vote %s of val=%d\n", + votable->name, + client_str, client_id, enabled ? "on" : "off", val); + goto out; + } + + pr_debug("%s: %s,%d voting %s of val=%d\n", + votable->name, + client_str, client_id, enabled ? "on" : "off", val); + switch (votable->type) { + case VOTE_MIN: + vote_min(votable, client_id, &effective_result, &effective_id); + break; + case VOTE_MAX: + vote_max(votable, client_id, &effective_result, &effective_id); + break; + case VOTE_SET_ANY: + vote_set_any(votable, client_id, + &effective_result, &effective_id); + break; + default: + return -EINVAL; + } + + /* + * Note that the callback is called with a NULL string and -EINVAL + * result when there are no enabled votes + */ + if (!votable->voted_on + || (effective_result != votable->effective_result)) { + votable->effective_client_id = effective_id; + votable->effective_result = effective_result; + pr_debug("%s: effective vote is now %d voted by %s,%d\n", + votable->name, effective_result, + get_client_str(votable, effective_id), + effective_id); + if (votable->callback && !votable->force_active + && (votable->override_result == -EINVAL)) + rc = votable->callback(votable, votable->data, + effective_result, + get_client_str(votable, effective_id)); + } + + votable->voted_on = true; +out: + unlock_votable(votable); + return rc; +} + +/** + * vote_override() - + * + * @votable: The votable object + * @override_client: The voting client that will override other client's + * votes, that are already present. When force_active + * and override votes are set on a votable, force_active's + * client will have the higher priority and it's vote will + * be the effective one. + * @enabled: This provides a means for the override client to exclude + * itself from election. This client's vote + * (the next argument) will be considered only when + * it has enabled its participation. When this is + * set true, this will force a value on a MIN/MAX votable + * irrespective of its current value. + * @val: The vote value. This will be effective only if enabled + * is set true. + * Returns: + * The result of vote. 0 is returned if the vote + * is successfully set by the overriding client, when enabled is set. + */ +int vote_override(struct votable *votable, const char *override_client, + bool enabled, int val) +{ + int rc = 0; + + if (!votable || !override_client) + return -EINVAL; + + lock_votable(votable); + if (votable->force_active) { + votable->override_result = enabled ? val : -EINVAL; + goto out; + } + + if (enabled) { + rc = votable->callback(votable, votable->data, + val, override_client); + if (!rc) { + votable->override_client = override_client; + votable->override_result = val; + } + } else { + rc = votable->callback(votable, votable->data, + votable->effective_result, + get_client_str(votable, votable->effective_client_id)); + votable->override_result = -EINVAL; + } + +out: + unlock_votable(votable); + return rc; +} + +int rerun_election(struct votable *votable) +{ + int rc = 0; + int effective_result; + + if (!votable) + return -EINVAL; + + lock_votable(votable); + effective_result = get_effective_result_locked(votable); + if (votable->callback) + rc = votable->callback(votable, + votable->data, + effective_result, + get_client_str(votable, votable->effective_client_id)); + unlock_votable(votable); + return rc; +} + +struct votable *find_votable(const char *name) +{ + unsigned long flags; + struct votable *v; + bool found = false; + + if (!name) + return NULL; + + spin_lock_irqsave(&votable_list_slock, flags); + if (list_empty(&votable_list)) + goto out; + + list_for_each_entry(v, &votable_list, list) { + if (strcmp(v->name, name) == 0) { + found = true; + break; + } + } +out: + spin_unlock_irqrestore(&votable_list_slock, flags); + + if (found) + return v; + else + return NULL; +} + +static int force_active_get(void *data, u64 *val) +{ + struct votable *votable = data; + + *val = votable->force_active; + + return 0; +} + +static int force_active_set(void *data, u64 val) +{ + struct votable *votable = data; + int rc = 0; + int effective_result; + const char *client; + + lock_votable(votable); + votable->force_active = !!val; + + if (!votable->callback) + goto out; + + if (votable->force_active) { + rc = votable->callback(votable, votable->data, + votable->force_val, + DEBUG_FORCE_CLIENT); + } else { + if (votable->override_result != -EINVAL) { + effective_result = votable->override_result; + client = votable->override_client; + } else { + effective_result = votable->effective_result; + client = get_client_str(votable, + votable->effective_client_id); + } + rc = votable->callback(votable, votable->data, effective_result, + client); + } +out: + unlock_votable(votable); + return rc; +} +DEFINE_DEBUGFS_ATTRIBUTE(votable_force_ops, force_active_get, force_active_set, + "%lld\n"); + +static int show_votable_clients(struct seq_file *m, void *data) +{ + struct votable *votable = m->private; + int i; + char *type_str = "Unkonwn"; + const char *effective_client_str; + + lock_votable(votable); + + for (i = 0; i < votable->num_clients; i++) { + if (votable->client_strs[i]) { + seq_printf(m, "%s: %s:\t\t\ten=%d v=%d\n", + votable->name, + votable->client_strs[i], + votable->votes[i].enabled, + votable->votes[i].value); + } + } + + switch (votable->type) { + case VOTE_MIN: + type_str = "Min"; + break; + case VOTE_MAX: + type_str = "Max"; + break; + case VOTE_SET_ANY: + type_str = "Set_any"; + break; + } + + effective_client_str = get_effective_client_locked(votable); + seq_printf(m, "%s: effective=%s type=%s v=%d\n", + votable->name, + effective_client_str ? effective_client_str : "none", + type_str, + get_effective_result_locked(votable)); + unlock_votable(votable); + + return 0; +} + +static int votable_status_open(struct inode *inode, struct file *file) +{ + struct votable *votable = inode->i_private; + + return single_open(file, show_votable_clients, votable); +} + +static const struct file_operations votable_status_ops = { + .owner = THIS_MODULE, + .open = votable_status_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +struct votable *create_votable(const char *name, + int votable_type, + int (*callback)(struct votable *votable, + void *data, + int effective_result, + const char *effective_client), + void *data) +{ + struct votable *votable; + unsigned long flags; + + if (!name) + return ERR_PTR(-EINVAL); + + votable = find_votable(name); + if (votable) + return ERR_PTR(-EEXIST); + + if (debug_root == NULL) { + debug_root = debugfs_create_dir("pmic-votable", NULL); + if (!debug_root) { + pr_err("Couldn't create debug dir\n"); + return ERR_PTR(-ENOMEM); + } + } + + if (votable_type >= NUM_VOTABLE_TYPES) { + pr_err("Invalid votable_type specified for voter\n"); + return ERR_PTR(-EINVAL); + } + + votable = kzalloc(sizeof(struct votable), GFP_KERNEL); + if (!votable) + return ERR_PTR(-ENOMEM); + + votable->name = kstrdup(name, GFP_KERNEL); + if (!votable->name) { + kfree(votable); + return ERR_PTR(-ENOMEM); + } + + votable->num_clients = NUM_MAX_CLIENTS; + votable->callback = callback; + votable->type = votable_type; + votable->data = data; + votable->override_result = -EINVAL; + mutex_init(&votable->vote_lock); + + /* + * Because effective_result and client states are invalid + * before the first vote, initialize them to -EINVAL + */ + votable->effective_result = -EINVAL; + if (votable->type == VOTE_SET_ANY) + votable->effective_result = 0; + votable->effective_client_id = -EINVAL; + + spin_lock_irqsave(&votable_list_slock, flags); + list_add(&votable->list, &votable_list); + spin_unlock_irqrestore(&votable_list_slock, flags); + + votable->root = debugfs_create_dir(name, debug_root); + if (!votable->root) { + pr_err("Couldn't create debug dir %s\n", name); + kfree(votable->name); + kfree(votable); + return ERR_PTR(-ENOMEM); + } + + votable->status_ent = debugfs_create_file("status", S_IFREG | 0444, + votable->root, votable, + &votable_status_ops); + if (!votable->status_ent) { + pr_err("Couldn't create status dbg file for %s\n", name); + debugfs_remove_recursive(votable->root); + kfree(votable->name); + kfree(votable); + return ERR_PTR(-EEXIST); + } + + votable->force_val_ent = debugfs_create_u32("force_val", + S_IFREG | 0644, + votable->root, + &(votable->force_val)); + + if (!votable->force_val_ent) { + pr_err("Couldn't create force_val dbg file for %s\n", name); + debugfs_remove_recursive(votable->root); + kfree(votable->name); + kfree(votable); + return ERR_PTR(-EEXIST); + } + + votable->force_active_ent = debugfs_create_file("force_active", + S_IFREG | 0444, + votable->root, votable, + &votable_force_ops); + if (!votable->force_active_ent) { + pr_err("Couldn't create force_active dbg file for %s\n", name); + debugfs_remove_recursive(votable->root); + kfree(votable->name); + kfree(votable); + return ERR_PTR(-EEXIST); + } + + return votable; +} + +void destroy_votable(struct votable *votable) +{ + unsigned long flags; + int i; + + if (!votable) + return; + + spin_lock_irqsave(&votable_list_slock, flags); + list_del(&votable->list); + spin_unlock_irqrestore(&votable_list_slock, flags); + + debugfs_remove_recursive(votable->root); + + for (i = 0; i < votable->num_clients && votable->client_strs[i]; i++) + kfree(votable->client_strs[i]); + + kfree(votable->name); + kfree(votable); +} diff --git a/drivers/power/supply/qcom/qg-battery-profile.c b/drivers/power/supply/qcom/qg-battery-profile.c new file mode 100644 index 000000000000..216937e466cf --- /dev/null +++ b/drivers/power/supply/qcom/qg-battery-profile.c @@ -0,0 +1,536 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2018-2020 The Linux Foundation. All rights reserved. + */ + +#define pr_fmt(fmt) "QG-K: %s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "qg-battery-profile.h" +#include "qg-profile-lib.h" +#include "qg-defs.h" + +struct qg_battery_data { + /* battery-data class node */ + dev_t dev_no; + struct class *battery_class; + struct device *battery_device; + struct cdev battery_cdev; + + /* profile */ + struct device_node *profile_node; + struct profile_table_data profile[TABLE_MAX]; +}; + +struct tables { + int table_index; + char *table_name; +}; + +static struct tables table[] = { + {TABLE_SOC_OCV1, "qcom,pc-temp-v1-lut"}, + {TABLE_SOC_OCV2, "qcom,pc-temp-v2-lut"}, + {TABLE_FCC1, "qcom,fcc1-temp-lut"}, + {TABLE_FCC2, "qcom,fcc2-temp-lut"}, + {TABLE_Z1, "qcom,pc-temp-z1-lut"}, + {TABLE_Z2, "qcom,pc-temp-z2-lut"}, + {TABLE_Z3, "qcom,pc-temp-z3-lut"}, + {TABLE_Z4, "qcom,pc-temp-z4-lut"}, + {TABLE_Z5, "qcom,pc-temp-z5-lut"}, + {TABLE_Z6, "qcom,pc-temp-z6-lut"}, + {TABLE_Y1, "qcom,pc-temp-y1-lut"}, + {TABLE_Y2, "qcom,pc-temp-y2-lut"}, + {TABLE_Y3, "qcom,pc-temp-y3-lut"}, + {TABLE_Y4, "qcom,pc-temp-y4-lut"}, + {TABLE_Y5, "qcom,pc-temp-y5-lut"}, + {TABLE_Y6, "qcom,pc-temp-y6-lut"}, +}; + +static struct qg_battery_data *the_battery; + +static void qg_battery_profile_free(void); + +static int qg_battery_data_open(struct inode *inode, struct file *file) +{ + struct qg_battery_data *battery = container_of(inode->i_cdev, + struct qg_battery_data, battery_cdev); + + file->private_data = battery; + + return 0; +} + +static long qg_battery_data_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct qg_battery_data *battery = file->private_data; + struct battery_params __user *bp_user = + (struct battery_params __user *)arg; + struct battery_params bp; + int rc = 0, soc, ocv_uv, fcc_mah, var, slope; + + if (!battery->profile_node) { + pr_err("Battery data not set!\n"); + return -EINVAL; + } + + if (!bp_user) { + pr_err("Invalid battery-params user pointer\n"); + return -EINVAL; + } + + if (copy_from_user(&bp, bp_user, sizeof(bp))) { + pr_err("Failed in copy_from_user\n"); + return -EFAULT; + } + + switch (cmd) { + case BPIOCXSOC: + if (bp.table_index != TABLE_SOC_OCV1 && + bp.table_index != TABLE_SOC_OCV2) { + pr_err("Invalid table index %d for SOC-OCV lookup\n", + bp.table_index); + rc = -EINVAL; + } else { + /* OCV is passed as deci-uV - 10^-4 V */ + soc = qg_interpolate_soc( + &battery->profile[bp.table_index], + bp.batt_temp, UV_TO_DECIUV(bp.ocv_uv)); + soc = CAP(QG_MIN_SOC, QG_MAX_SOC, soc); + rc = put_user(soc, &bp_user->soc); + if (rc < 0) { + pr_err("BPIOCXSOC: Failed rc=%d\n", rc); + goto ret_err; + } + pr_debug("BPIOCXSOC: lut=%s ocv=%d batt_temp=%d soc=%d\n", + battery->profile[bp.table_index].name, + bp.ocv_uv, bp.batt_temp, soc); + } + break; + case BPIOCXOCV: + if (bp.table_index != TABLE_SOC_OCV1 && + bp.table_index != TABLE_SOC_OCV2) { + pr_err("Invalid table index %d for SOC-OCV lookup\n", + bp.table_index); + rc = -EINVAL; + } else { + ocv_uv = qg_interpolate_var( + &battery->profile[bp.table_index], + bp.batt_temp, bp.soc); + ocv_uv = DECIUV_TO_UV(ocv_uv); + ocv_uv = CAP(QG_MIN_OCV_UV, QG_MAX_OCV_UV, ocv_uv); + rc = put_user(ocv_uv, &bp_user->ocv_uv); + if (rc < 0) { + pr_err("BPIOCXOCV: Failed rc=%d\n", rc); + goto ret_err; + } + pr_debug("BPIOCXOCV: lut=%s ocv=%d batt_temp=%d soc=%d\n", + battery->profile[bp.table_index].name, + ocv_uv, bp.batt_temp, bp.soc); + } + break; + case BPIOCXFCC: + if (bp.table_index != TABLE_FCC1 && + bp.table_index != TABLE_FCC2) { + pr_err("Invalid table index %d for FCC lookup\n", + bp.table_index); + rc = -EINVAL; + } else { + fcc_mah = qg_interpolate_single_row_lut( + &battery->profile[bp.table_index], + bp.batt_temp, DEGC_SCALE); + fcc_mah = CAP(QG_MIN_FCC_MAH, QG_MAX_FCC_MAH, fcc_mah); + rc = put_user(fcc_mah, &bp_user->fcc_mah); + if (rc) { + pr_err("BPIOCXFCC: Failed rc=%d\n", rc); + goto ret_err; + } + pr_debug("BPIOCXFCC: lut=%s batt_temp=%d fcc_mah=%d\n", + battery->profile[bp.table_index].name, + bp.batt_temp, fcc_mah); + } + break; + case BPIOCXVAR: + if (bp.table_index < TABLE_Z1 || bp.table_index >= TABLE_MAX) { + pr_err("Invalid table index %d for VAR lookup\n", + bp.table_index); + rc = -EINVAL; + } else { + var = qg_interpolate_var( + &battery->profile[bp.table_index], + bp.batt_temp, bp.soc); + var = CAP(QG_MIN_VAR, QG_MAX_VAR, var); + rc = put_user(var, &bp_user->var); + if (rc < 0) { + pr_err("BPIOCXVAR: Failed rc=%d\n", rc); + goto ret_err; + } + pr_debug("BPIOCXVAR: lut=%s var=%d batt_temp=%d soc=%d\n", + battery->profile[bp.table_index].name, + var, bp.batt_temp, bp.soc); + } + break; + case BPIOCXSLOPE: + if (bp.table_index != TABLE_SOC_OCV1 && + bp.table_index != TABLE_SOC_OCV2) { + pr_err("Invalid table index %d for Slope lookup\n", + bp.table_index); + rc = -EINVAL; + } else { + slope = qg_interpolate_slope( + &battery->profile[bp.table_index], + bp.batt_temp, bp.soc); + slope = CAP(QG_MIN_SLOPE, QG_MAX_SLOPE, slope); + rc = put_user(slope, &bp_user->slope); + if (rc) { + pr_err("BPIOCXSLOPE: Failed rc=%d\n", rc); + goto ret_err; + } + pr_debug("BPIOCXSLOPE: lut=%s soc=%d batt_temp=%d slope=%d\n", + battery->profile[bp.table_index].name, + bp.soc, bp.batt_temp, slope); + } + break; + default: + pr_err("IOCTL %d not supported\n", cmd); + rc = -EINVAL; + } +ret_err: + return rc; +} + +static int qg_battery_data_release(struct inode *inode, struct file *file) +{ + pr_debug("battery_data device closed\n"); + + return 0; +} + +static const struct file_operations qg_battery_data_fops = { + .owner = THIS_MODULE, + .open = qg_battery_data_open, + .unlocked_ioctl = qg_battery_data_ioctl, + .compat_ioctl = qg_battery_data_ioctl, + .release = qg_battery_data_release, +}; + +static int get_length(struct device_node *node, + int *length, char *prop_name, bool ignore_null) +{ + struct property *prop; + + prop = of_find_property(node, prop_name, NULL); + if (!prop) { + if (ignore_null) { + *length = 1; + return 0; + } + pr_err("Failed to find %s property\n", prop_name); + return -ENODATA; + } else if (!prop->value) { + pr_err("Failed to find value for %s property\n", prop_name); + return -ENODATA; + } + + *length = prop->length / sizeof(u32); + + return 0; +} + +static int qg_parse_battery_profile(struct qg_battery_data *battery) +{ + int i, j, k, rows = 0, cols = 0, lut_length = 0, rc = 0; + struct device_node *node; + struct property *prop; + const __be32 *data; + + for (i = 0; i < TABLE_MAX; i++) { + node = of_find_node_by_name(battery->profile_node, + table[i].table_name); + if (!node) { + pr_err("%s table not found\n", table[i].table_name); + rc = -ENODEV; + goto cleanup; + } + + rc = get_length(node, &cols, "qcom,lut-col-legend", false); + if (rc < 0) { + pr_err("Failed to get col-length for %s table rc=%d\n", + table[i].table_name, rc); + goto cleanup; + } + + rc = get_length(node, &rows, "qcom,lut-row-legend", true); + if (rc < 0) { + pr_err("Failed to get row-length for %s table rc=%d\n", + table[i].table_name, rc); + goto cleanup; + } + + rc = get_length(node, &lut_length, "qcom,lut-data", false); + if (rc < 0) { + pr_err("Failed to get lut-length for %s table rc=%d\n", + table[i].table_name, rc); + goto cleanup; + } + + if (lut_length != cols * rows) { + pr_err("Invalid lut-length for %s table\n", + table[i].table_name); + rc = -EINVAL; + goto cleanup; + } + + battery->profile[i].name = kzalloc(strlen(table[i].table_name) + + 1, GFP_KERNEL); + if (!battery->profile[i].name) { + rc = -ENOMEM; + goto cleanup; + } + + strlcpy(battery->profile[i].name, table[i].table_name, + strlen(table[i].table_name)); + battery->profile[i].rows = rows; + battery->profile[i].cols = cols; + + if (rows != 1) { + battery->profile[i].row_entries = kcalloc(rows, + sizeof(*battery->profile[i].row_entries), + GFP_KERNEL); + if (!battery->profile[i].row_entries) { + rc = -ENOMEM; + goto cleanup; + } + } + + battery->profile[i].col_entries = kcalloc(cols, + sizeof(*battery->profile[i].col_entries), + GFP_KERNEL); + if (!battery->profile[i].col_entries) { + rc = -ENOMEM; + goto cleanup; + } + + battery->profile[i].data = kcalloc(rows, + sizeof(*battery->profile[i].data), GFP_KERNEL); + if (!battery->profile[i].data) { + rc = -ENOMEM; + goto cleanup; + } + + for (j = 0; j < rows; j++) { + battery->profile[i].data[j] = kcalloc(cols, + sizeof(**battery->profile[i].data), + GFP_KERNEL); + if (!battery->profile[i].data[j]) { + rc = -ENOMEM; + goto cleanup; + } + } + + /* read profile data */ + rc = of_property_read_u32_array(node, "qcom,lut-col-legend", + battery->profile[i].col_entries, cols); + if (rc < 0) { + pr_err("Failed to read cols values for table %s rc=%d\n", + table[i].table_name, rc); + goto cleanup; + } + + if (rows != 1) { + rc = of_property_read_u32_array(node, + "qcom,lut-row-legend", + battery->profile[i].row_entries, rows); + if (rc < 0) { + pr_err("Failed to read row values for table %s rc=%d\n", + table[i].table_name, rc); + goto cleanup; + } + } + + prop = of_find_property(node, "qcom,lut-data", NULL); + if (!prop) { + pr_err("Failed to find lut-data\n"); + rc = -EINVAL; + goto cleanup; + } + data = prop->value; + for (j = 0; j < rows; j++) { + for (k = 0; k < cols; k++) + battery->profile[i].data[j][k] = + be32_to_cpup(data++); + } + + pr_debug("Profile table %s parsed rows=%d cols=%d\n", + battery->profile[i].name, battery->profile[i].rows, + battery->profile[i].cols); + } + + return 0; + +cleanup: + for (; i >= 0; i++) { + kfree(battery->profile[i].name); + kfree(battery->profile[i].row_entries); + kfree(battery->profile[i].col_entries); + for (j = 0; j < battery->profile[i].rows; j++) { + if (battery->profile[i].data) + kfree(battery->profile[i].data[j]); + } + kfree(battery->profile[i].data); + } + return rc; +} + +int lookup_soc_ocv(u32 *soc, u32 ocv_uv, int batt_temp, bool charging) +{ + u8 table_index = charging ? TABLE_SOC_OCV1 : TABLE_SOC_OCV2; + + if (!the_battery || !the_battery->profile_node) + return -ENODEV; + + *soc = qg_interpolate_soc(&the_battery->profile[table_index], + batt_temp, UV_TO_DECIUV(ocv_uv)); + + *soc = CAP(0, 100, DIV_ROUND_CLOSEST(*soc, 100)); + + return 0; +} + +int qg_get_nominal_capacity(u32 *nom_cap_uah, int batt_temp, bool charging) +{ + u8 table_index = charging ? TABLE_FCC1 : TABLE_FCC2; + u32 fcc_mah; + + if (!the_battery || !the_battery->profile_node) + return -ENODEV; + + fcc_mah = qg_interpolate_single_row_lut( + &the_battery->profile[table_index], + batt_temp, DEGC_SCALE); + fcc_mah = CAP(QG_MIN_FCC_MAH, QG_MAX_FCC_MAH, fcc_mah); + + *nom_cap_uah = fcc_mah * 1000; + + return 0; +} + +int qg_batterydata_init(struct device_node *profile_node) +{ + int rc = 0; + struct qg_battery_data *battery; + + /* + * If a battery profile is already initialized, free the existing + * profile data and re-allocate and load the new profile. This is + * required for multi-profile load support. + */ + if (the_battery) { + battery = the_battery; + battery->profile_node = NULL; + qg_battery_profile_free(); + } else { + battery = kzalloc(sizeof(*battery), GFP_KERNEL); + if (!battery) + return -ENOMEM; + /* char device to access battery-profile data */ + rc = alloc_chrdev_region(&battery->dev_no, 0, 1, + "qg_battery"); + if (rc < 0) { + pr_err("Failed to allocate chrdev rc=%d\n", rc); + goto free_battery; + } + + cdev_init(&battery->battery_cdev, &qg_battery_data_fops); + rc = cdev_add(&battery->battery_cdev, + battery->dev_no, 1); + if (rc) { + pr_err("Failed to add battery_cdev rc=%d\n", rc); + goto unregister_chrdev; + } + + battery->battery_class = class_create(THIS_MODULE, + "qg_battery"); + if (IS_ERR_OR_NULL(battery->battery_class)) { + pr_err("Failed to create qg-battery class\n"); + rc = -ENODEV; + goto delete_cdev; + } + + battery->battery_device = device_create( + battery->battery_class, + NULL, battery->dev_no, + NULL, "qg_battery"); + if (IS_ERR_OR_NULL(battery->battery_device)) { + pr_err("Failed to create battery_device device\n"); + rc = -ENODEV; + goto destroy_class; + } + the_battery = battery; + } + + battery->profile_node = profile_node; + /* parse the battery profile */ + rc = qg_parse_battery_profile(battery); + if (rc < 0) { + pr_err("Failed to parse battery profile rc=%d\n", rc); + goto destroy_device; + } + + pr_info("QG Battery-profile loaded\n"); + + return 0; + +destroy_device: + device_destroy(battery->battery_class, battery->dev_no); +destroy_class: + class_destroy(battery->battery_class); +delete_cdev: + cdev_del(&battery->battery_cdev); +unregister_chrdev: + unregister_chrdev_region(battery->dev_no, 1); +free_battery: + kfree(battery); + return rc; +} + +static void qg_battery_profile_free(void) +{ + int i, j; + + /* delete all the battery profile memory */ + for (i = 0; i < TABLE_MAX; i++) { + kfree(the_battery->profile[i].name); + kfree(the_battery->profile[i].row_entries); + kfree(the_battery->profile[i].col_entries); + for (j = 0; j < the_battery->profile[i].rows; j++) { + if (the_battery->profile[i].data) + kfree(the_battery->profile[i].data[j]); + } + kfree(the_battery->profile[i].data); + } +} + +void qg_batterydata_exit(void) +{ + if (the_battery) { + /* unregister the device node */ + device_destroy(the_battery->battery_class, the_battery->dev_no); + class_destroy(the_battery->battery_class); + cdev_del(&the_battery->battery_cdev); + unregister_chrdev_region(the_battery->dev_no, 1); + qg_battery_profile_free(); + } + + kfree(the_battery); + the_battery = NULL; +} diff --git a/drivers/power/supply/qcom/qg-battery-profile.h b/drivers/power/supply/qcom/qg-battery-profile.h new file mode 100644 index 000000000000..cdf5b8bd4201 --- /dev/null +++ b/drivers/power/supply/qcom/qg-battery-profile.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2018, 2020, The Linux Foundation. All rights reserved. + */ + +#ifndef __QG_BATTERY_PROFILE_H__ +#define __QG_BATTERY_PROFILE_H__ + +int qg_batterydata_init(struct device_node *node); +void qg_batterydata_exit(void); +int lookup_soc_ocv(u32 *soc, u32 ocv_uv, int batt_temp, bool charging); +int qg_get_nominal_capacity(u32 *nom_cap_uah, int batt_temp, bool charging); + +#endif /* __QG_BATTERY_PROFILE_H__ */ diff --git a/drivers/power/supply/qcom/qg-core.h b/drivers/power/supply/qcom/qg-core.h new file mode 100644 index 000000000000..858bd196b1fe --- /dev/null +++ b/drivers/power/supply/qcom/qg-core.h @@ -0,0 +1,271 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2018-2020 The Linux Foundation. All rights reserved. + */ + +#ifndef __QG_CORE_H__ +#define __QG_CORE_H__ + +#include +#include "fg-alg.h" +#include "qg-defs.h" + +struct qg_batt_props { + const char *batt_type_str; + int float_volt_uv; + int vbatt_full_mv; + int fastchg_curr_ma; + int qg_profile_version; +}; + +struct qg_irq_info { + const char *name; + const irq_handler_t handler; + const bool wake; + int irq; +}; + +struct qg_dt { + int vbatt_empty_mv; + int vbatt_empty_cold_mv; + int vbatt_low_mv; + int vbatt_low_cold_mv; + int vbatt_cutoff_mv; + int iterm_ma; + int s2_fifo_length; + int s2_vbat_low_fifo_length; + int s2_acc_length; + int s2_acc_intvl_ms; + int sleep_s2_fifo_length; + int sleep_s2_acc_length; + int sleep_s2_acc_intvl_ms; + int fast_chg_s2_fifo_length; + int ocv_timer_expiry_min; + int ocv_tol_threshold_uv; + int s3_entry_fifo_length; + int s3_entry_ibat_ua; + int s3_exit_ibat_ua; + int delta_soc; + int rbat_conn_mohm; + int ignore_shutdown_soc_secs; + int shutdown_temp_diff; + int cold_temp_threshold; + int esr_qual_i_ua; + int esr_qual_v_uv; + int esr_disable_soc; + int esr_min_ibat_ua; + int shutdown_soc_threshold; + int min_sleep_time_secs; + int sys_min_volt_mv; + int fvss_vbat_mv; + int tcss_entry_soc; + bool hold_soc_while_full; + bool linearize_soc; + bool cl_disable; + bool cl_feedback_on; + bool esr_disable; + bool esr_discharge_enable; + bool qg_ext_sense; + bool use_s7_ocv; + bool qg_sleep_config; + bool qg_fast_chg_cfg; + bool fvss_enable; + bool multi_profile_load; + bool tcss_enable; + bool bass_enable; +}; + +struct qg_esr_data { + u32 pre_esr_v; + u32 pre_esr_i; + u32 post_esr_v; + u32 post_esr_i; + u32 esr; + bool valid; +}; + +struct qpnp_qg { + struct device *dev; + struct pmic_revid_data *pmic_rev_id; + struct regmap *regmap; + struct qpnp_vadc_chip *vadc_dev; + struct soh_profile *sp; + struct power_supply *qg_psy; + struct class *qg_class; + struct device *qg_device; + struct cdev qg_cdev; + struct device_node *batt_node; + struct dentry *dfs_root; + dev_t dev_no; + struct work_struct udata_work; + struct work_struct scale_soc_work; + struct work_struct qg_status_change_work; + struct delayed_work qg_sleep_exit_work; + struct notifier_block nb; + struct mutex bus_lock; + struct mutex data_lock; + struct mutex soc_lock; + wait_queue_head_t qg_wait_q; + struct votable *awake_votable; + struct votable *vbatt_irq_disable_votable; + struct votable *fifo_irq_disable_votable; + struct votable *good_ocv_irq_disable_votable; + u32 qg_base; + u8 qg_subtype; + u8 qg_mode; + + /* local data variables */ + u32 batt_id_ohm; + struct qg_kernel_data kdata; + struct qg_user_data udata; + struct power_supply *batt_psy; + struct power_supply *usb_psy; + struct power_supply *dc_psy; + struct power_supply *parallel_psy; + struct qg_esr_data esr_data[QG_MAX_ESR_COUNT]; + + /* status variable */ + u32 *debug_mask; + u32 qg_version; + bool qg_device_open; + bool profile_loaded; + bool battery_missing; + bool data_ready; + bool suspend_data; + bool vbat_low; + bool charge_done; + bool parallel_enabled; + bool usb_present; + bool dc_present; + bool charge_full; + bool force_soc; + bool fvss_active; + bool tcss_active; + bool bass_active; + int charge_status; + int charge_type; + int chg_iterm_ma; + int next_wakeup_ms; + int esr_actual; + int esr_nominal; + int soh; + int soc_reporting_ready; + int last_fifo_v_uv; + int last_fifo_i_ua; + int prev_fifo_i_ua; + int soc_tcss_entry; + int ibat_tcss_entry; + int soc_tcss; + int tcss_entry_count; + int max_fcc_limit_ma; + int bsoc_bass_entry; + int qg_v_ibat; + u32 fifo_done_count; + u32 wa_flags; + u32 seq_no; + u32 charge_counter_uah; + u32 esr_avg; + u32 esr_last; + u32 s2_state; + u32 s2_state_mask; + u32 soc_fvss_entry; + u32 vbat_fvss_entry; + u32 max_fifo_length; + ktime_t last_user_update_time; + ktime_t last_fifo_update_time; + unsigned long last_maint_soc_update_time; + unsigned long suspend_time; + struct iio_channel *batt_therm_chan; + struct iio_channel *batt_id_chan; + + /* soc params */ + int catch_up_soc; + int maint_soc; + int msoc; + int pon_soc; + int batt_soc; + int cc_soc; + int full_soc; + int sys_soc; + int last_adj_ssoc; + int recharge_soc; + int batt_age_level; + struct alarm alarm_timer; + u32 sdam_data[SDAM_MAX]; + + /* DT */ + struct qg_dt dt; + struct qg_batt_props bp; + /* capacity learning */ + struct cap_learning *cl; + /* charge counter */ + struct cycle_counter *counter; + /* ttf */ + struct ttf *ttf; +}; + +struct ocv_all { + u32 ocv_uv; + u32 ocv_raw; + char ocv_type[20]; +}; + +enum ocv_type { + S7_PON_OCV, + S3_GOOD_OCV, + S3_LAST_OCV, + SDAM_PON_OCV, + PON_OCV_MAX, +}; + +enum s2_state { + S2_FAST_CHARGING = BIT(0), + S2_LOW_VBAT = BIT(1), + S2_SLEEP = BIT(2), + S2_DEFAULT = BIT(3), +}; + +enum debug_mask { + QG_DEBUG_PON = BIT(0), + QG_DEBUG_PROFILE = BIT(1), + QG_DEBUG_DEVICE = BIT(2), + QG_DEBUG_STATUS = BIT(3), + QG_DEBUG_FIFO = BIT(4), + QG_DEBUG_IRQ = BIT(5), + QG_DEBUG_SOC = BIT(6), + QG_DEBUG_PM = BIT(7), + QG_DEBUG_BUS_READ = BIT(8), + QG_DEBUG_BUS_WRITE = BIT(9), + QG_DEBUG_ALG_CL = BIT(10), + QG_DEBUG_ESR = BIT(11), +}; + +enum qg_irq { + QG_BATT_MISSING_IRQ, + QG_VBATT_LOW_IRQ, + QG_VBATT_EMPTY_IRQ, + QG_FIFO_UPDATE_DONE_IRQ, + QG_GOOD_OCV_IRQ, + QG_FSM_STAT_CHG_IRQ, + QG_EVENT_IRQ, + QG_MAX_IRQ, +}; + +enum qg_wa_flags { + QG_VBAT_LOW_WA = BIT(0), + QG_RECHARGE_SOC_WA = BIT(1), + QG_CLK_ADJUST_WA = BIT(2), + QG_PON_OCV_WA = BIT(3), +}; + +enum qg_version { + QG_PMIC5, + QG_LITE, +}; + +enum qg_mode { + QG_V_I_MODE, + QG_V_MODE, +}; + +#endif /* __QG_CORE_H__ */ diff --git a/drivers/power/supply/qcom/qg-defs.h b/drivers/power/supply/qcom/qg-defs.h new file mode 100644 index 000000000000..43f679e8e525 --- /dev/null +++ b/drivers/power/supply/qcom/qg-defs.h @@ -0,0 +1,51 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2018-2020 The Linux Foundation. All rights reserved. + */ + +#ifndef __QG_DEFS_H__ +#define __QG_DEFS_H__ + +#define qg_dbg(chip, reason, fmt, ...) \ + do { \ + if (*chip->debug_mask & (reason)) \ + pr_info(fmt, ##__VA_ARGS__); \ + else \ + pr_debug(fmt, ##__VA_ARGS__); \ + } while (0) + +#define is_between(left, right, value) \ + (((left) >= (right) && (left) >= (value) \ + && (value) >= (right)) \ + || ((left) <= (right) && (left) <= (value) \ + && (value) <= (right))) + +#define UDATA_READY_VOTER "UDATA_READY_VOTER" +#define FIFO_DONE_VOTER "FIFO_DONE_VOTER" +#define FIFO_RT_DONE_VOTER "FIFO_RT_DONE_VOTER" +#define SUSPEND_DATA_VOTER "SUSPEND_DATA_VOTER" +#define GOOD_OCV_VOTER "GOOD_OCV_VOTER" +#define PROFILE_IRQ_DISABLE "NO_PROFILE_IRQ_DISABLE" +#define QG_INIT_STATE_IRQ_DISABLE "QG_INIT_STATE_IRQ_DISABLE" +#define TTF_AWAKE_VOTER "TTF_AWAKE_VOTER" +#define SLEEP_EXIT_DATA_VOTER "SLEEP_EXIT_DATA_VOTER" +#define SLEEP_EXIT_VOTER "SLEEP_EXIT_VOTER" + +#define V_RAW_TO_UV(V_RAW) div_u64(194637ULL * (u64)V_RAW, 1000) +#define FIFO_V_RESET_VAL 0x8000 +#define FIFO_I_RESET_VAL 0x8000 + +#define DEGC_SCALE 10 +#define UV_TO_DECIUV(a) (a / 100) +#define DECIUV_TO_UV(a) (a * 100) + +#define QG_MAX_ESR_COUNT 10 +#define QG_MIN_ESR_COUNT 2 + +#define CAP(min, max, value) \ + ((min > value) ? min : ((value > max) ? max : value)) + +#define QG_SOC_FULL 10000 +#define BATT_SOC_32BIT GENMASK(31, 0) + +#endif /* __QG_DEFS_H__ */ diff --git a/drivers/power/supply/qcom/qg-profile-lib.c b/drivers/power/supply/qcom/qg-profile-lib.c new file mode 100644 index 000000000000..2ccbab0afd95 --- /dev/null +++ b/drivers/power/supply/qcom/qg-profile-lib.c @@ -0,0 +1,304 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2018-2020 The Linux Foundation. All rights reserved. + */ + +#include +#include +#include +#include "qg-profile-lib.h" +#include "qg-defs.h" + +int qg_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)); +} + +int qg_interpolate_single_row_lut(struct profile_table_data *lut, + int x, int scale) +{ + int i, result; + int cols = lut->cols; + + if (x < lut->col_entries[0] * scale) { + pr_debug("x %d less than known range return y = %d lut = %s\n", + x, lut->data[0][0], lut->name); + return lut->data[0][0]; + } + + if (x > lut->col_entries[cols-1] * scale) { + pr_debug("x %d more than known range return y = %d lut = %s\n", + x, lut->data[0][cols-1], lut->name); + return lut->data[0][cols-1]; + } + + for (i = 0; i < cols; i++) { + if (x <= lut->col_entries[i] * scale) + break; + } + + if (x == lut->col_entries[i] * scale) { + result = lut->data[0][i]; + } else { + result = qg_linear_interpolate( + lut->data[0][i-1], + lut->col_entries[i-1] * scale, + lut->data[0][i], + lut->col_entries[i] * scale, + x); + } + + return result; +} + +int qg_interpolate_soc(struct profile_table_data *lut, + int batt_temp, int ocv) +{ + int i, j, soc_high, soc_low, soc; + int rows = lut->rows; + int cols = lut->cols; + + if (batt_temp < lut->col_entries[0] * DEGC_SCALE) { + pr_debug("batt_temp %d < known temp range\n", batt_temp); + batt_temp = lut->col_entries[0] * DEGC_SCALE; + } + + if (batt_temp > lut->col_entries[cols - 1] * DEGC_SCALE) { + pr_debug("batt_temp %d > known temp range\n", batt_temp); + batt_temp = lut->col_entries[cols - 1] * DEGC_SCALE; + } + + for (j = 0; j < cols; j++) + if (batt_temp <= lut->col_entries[j] * DEGC_SCALE) + break; + + if (batt_temp == lut->col_entries[j] * DEGC_SCALE) { + /* found an exact match for temp in the table */ + if (ocv >= lut->data[0][j]) + return lut->row_entries[0]; + if (ocv <= lut->data[rows - 1][j]) + return lut->row_entries[rows - 1]; + for (i = 0; i < rows; i++) { + if (ocv >= lut->data[i][j]) { + if (ocv == lut->data[i][j]) + return lut->row_entries[i]; + soc = qg_linear_interpolate( + lut->row_entries[i], + lut->data[i][j], + lut->row_entries[i - 1], + lut->data[i - 1][j], + ocv); + return soc; + } + } + } + + /* batt_temp is within temperature for column j-1 and j */ + if (ocv >= lut->data[0][j]) + return lut->row_entries[0]; + if (ocv <= lut->data[rows - 1][j - 1]) + return lut->row_entries[rows - 1]; + + soc_low = soc_high = 0; + for (i = 0; i < rows-1; i++) { + if (soc_high == 0 && is_between(lut->data[i][j], + lut->data[i+1][j], ocv)) { + soc_high = qg_linear_interpolate( + lut->row_entries[i], + lut->data[i][j], + lut->row_entries[i + 1], + lut->data[i+1][j], + ocv); + } + + if (soc_low == 0 && is_between(lut->data[i][j-1], + lut->data[i+1][j-1], ocv)) { + soc_low = qg_linear_interpolate( + lut->row_entries[i], + lut->data[i][j-1], + lut->row_entries[i + 1], + lut->data[i+1][j-1], + ocv); + } + + if (soc_high && soc_low) { + soc = qg_linear_interpolate( + soc_low, + lut->col_entries[j-1] * DEGC_SCALE, + soc_high, + lut->col_entries[j] * DEGC_SCALE, + batt_temp); + return soc; + } + } + + if (soc_high) + return soc_high; + + if (soc_low) + return soc_low; + + pr_debug("%d ocv wasn't found for temp %d in the LUT %s returning 100%%\n", + ocv, batt_temp, lut->name); + return 10000; +} + +int qg_interpolate_var(struct profile_table_data *lut, + int batt_temp, int soc) +{ + int i, var1, var2, var, rows, cols; + int row1 = 0; + int row2 = 0; + + rows = lut->rows; + cols = lut->cols; + if (soc > lut->row_entries[0]) { + pr_debug("soc %d greater than known soc ranges for %s lut\n", + soc, lut->name); + row1 = 0; + row2 = 0; + } else if (soc < lut->row_entries[rows - 1]) { + pr_debug("soc %d less than known soc ranges for %s lut\n", + soc, lut->name); + row1 = rows - 1; + row2 = rows - 1; + } else { + for (i = 0; i < rows; i++) { + if (soc == lut->row_entries[i]) { + row1 = i; + row2 = i; + break; + } + if (soc > lut->row_entries[i]) { + row1 = i - 1; + row2 = i; + break; + } + } + } + + if (batt_temp < lut->col_entries[0] * DEGC_SCALE) + batt_temp = lut->col_entries[0] * DEGC_SCALE; + if (batt_temp > lut->col_entries[cols - 1] * DEGC_SCALE) + batt_temp = lut->col_entries[cols - 1] * DEGC_SCALE; + + for (i = 0; i < cols; i++) + if (batt_temp <= lut->col_entries[i] * DEGC_SCALE) + break; + + if (batt_temp == lut->col_entries[i] * DEGC_SCALE) { + var = qg_linear_interpolate( + lut->data[row1][i], + lut->row_entries[row1], + lut->data[row2][i], + lut->row_entries[row2], + soc); + return var; + } + + var1 = qg_linear_interpolate( + lut->data[row1][i - 1], + lut->col_entries[i - 1] * DEGC_SCALE, + lut->data[row1][i], + lut->col_entries[i] * DEGC_SCALE, + batt_temp); + + var2 = qg_linear_interpolate( + lut->data[row2][i - 1], + lut->col_entries[i - 1] * DEGC_SCALE, + lut->data[row2][i], + lut->col_entries[i] * DEGC_SCALE, + batt_temp); + + var = qg_linear_interpolate( + var1, + lut->row_entries[row1], + var2, + lut->row_entries[row2], + soc); + + return var; +} + +int qg_interpolate_slope(struct profile_table_data *lut, + int batt_temp, int soc) +{ + int i, ocvrow1, ocvrow2, rows, cols; + int row1 = 0; + int row2 = 0; + int slope; + + rows = lut->rows; + cols = lut->cols; + if (soc >= lut->row_entries[0]) { + pr_debug("soc %d >= max soc range - use the slope at soc=%d for lut %s\n", + soc, lut->row_entries[0], lut->name); + row1 = 0; + row2 = 1; + } else if (soc <= lut->row_entries[rows - 1]) { + pr_debug("soc %d is <= min soc range - use the slope at soc=%d for lut %s\n", + soc, lut->row_entries[rows - 1], lut->name); + row1 = rows - 2; + row2 = rows - 1; + } else { + for (i = 0; i < rows; i++) { + if (soc >= lut->row_entries[i]) { + row1 = i - 1; + row2 = i; + break; + } + } + } + + if (batt_temp < lut->col_entries[0] * DEGC_SCALE) + batt_temp = lut->col_entries[0] * DEGC_SCALE; + if (batt_temp > lut->col_entries[cols - 1] * DEGC_SCALE) + batt_temp = lut->col_entries[cols - 1] * DEGC_SCALE; + + for (i = 0; i < cols; i++) { + if (batt_temp <= lut->col_entries[i] * DEGC_SCALE) + break; + } + + if (batt_temp == lut->col_entries[i] * DEGC_SCALE) { + slope = (lut->data[row1][i] - lut->data[row2][i]); + if (slope <= 0) { + pr_warn_ratelimited("Slope=%d for soc=%d, using 1\n", + slope, soc); + slope = 1; + } + slope *= 10000; + slope /= (lut->row_entries[row1] - + lut->row_entries[row2]); + return slope; + } + ocvrow1 = qg_linear_interpolate( + lut->data[row1][i - 1], + lut->col_entries[i - 1] * DEGC_SCALE, + lut->data[row1][i], + lut->col_entries[i] * DEGC_SCALE, + batt_temp); + + ocvrow2 = qg_linear_interpolate( + lut->data[row2][i - 1], + lut->col_entries[i - 1] * DEGC_SCALE, + lut->data[row2][i], + lut->col_entries[i] * DEGC_SCALE, + batt_temp); + + slope = (ocvrow1 - ocvrow2); + if (slope <= 0) { + pr_warn_ratelimited("Slope=%d for soc=%d, using 1\n", + slope, soc); + slope = 1; + } + slope *= 10000; + slope /= (lut->row_entries[row1] - lut->row_entries[row2]); + + return slope; +} diff --git a/drivers/power/supply/qcom/qg-profile-lib.h b/drivers/power/supply/qcom/qg-profile-lib.h new file mode 100644 index 000000000000..f57799ec159b --- /dev/null +++ b/drivers/power/supply/qcom/qg-profile-lib.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2018-2020 The Linux Foundation. All rights reserved. + */ + +#ifndef __QG_PROFILE_LIB_H__ +#define __QG_PROFILE_LIB_H__ + +struct profile_table_data { + char *name; + int rows; + int cols; + int *row_entries; + int *col_entries; + int **data; +}; + +int qg_linear_interpolate(int y0, int x0, int y1, int x1, int x); +int qg_interpolate_single_row_lut(struct profile_table_data *lut, + int x, int scale); +int qg_interpolate_soc(struct profile_table_data *lut, + int batt_temp, int ocv); +int qg_interpolate_var(struct profile_table_data *lut, + int batt_temp, int soc); +int qg_interpolate_slope(struct profile_table_data *lut, + int batt_temp, int soc); + +#endif /*__QG_PROFILE_LIB_H__ */ diff --git a/drivers/power/supply/qcom/qg-reg.h b/drivers/power/supply/qcom/qg-reg.h new file mode 100644 index 000000000000..647ee60ee29e --- /dev/null +++ b/drivers/power/supply/qcom/qg-reg.h @@ -0,0 +1,136 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2018-2020 The Linux Foundation. All rights reserved. + */ + +#ifndef __QG_REG_H__ +#define __QG_REG_H__ + +#define PERPH_TYPE_REG 0x04 + +#define PERPH_SUBTYPE_REG 0x05 +#define QG_ADC_IBAT_5A 0x3 +#define QG_ADC_IBAT_10A 0x4 + +#define QG_TYPE 0x0D + +#define QG_STATUS1_REG 0x08 +#define QG_OK_BIT BIT(7) +#define BATTERY_PRESENT_BIT BIT(0) +#define ESR_MEAS_DONE_BIT BIT(4) + +#define QG_STATUS2_REG 0x09 +#define BATTERY_MISSING_BIT BIT(3) +#define GOOD_OCV_BIT BIT(1) + +#define QG_STATUS3_REG 0x0A +#define COUNT_FIFO_RT_MASK GENMASK(3, 0) + +#define QG_STATUS4_REG 0x0B +#define ESR_MEAS_IN_PROGRESS_BIT BIT(4) + +#define QG_INT_RT_STS_REG 0x10 +#define FIFO_UPDATE_DONE_RT_STS_BIT BIT(3) +#define VBAT_LOW_INT_RT_STS_BIT BIT(1) +#define BATTERY_MISSING_INT_RT_STS_BIT BIT(0) + +#define QG_INT_LATCHED_STS_REG 0x18 +#define FIFO_UPDATE_DONE_INT_LAT_STS_BIT BIT(3) + +#define QG_STATE_TRIG_CMD_REG 0x40 +#define S7_PON_OCV_START BIT(3) + +#define QG_DATA_CTL1_REG 0x41 +#define MASTER_HOLD_OR_CLR_BIT BIT(0) + +#define QG_DATA_CTL2_REG 0x42 +#define BURST_AVG_HOLD_FOR_READ_BIT BIT(0) + +#define QG_MODE_CTL1_REG 0x43 +#define PARALLEL_IBAT_SENSE_EN_BIT BIT(7) + +#define QG_MODE_CTL2_REG 0x44 +#define VI_MODE_BIT BIT(0) + +#define QG_VBAT_EMPTY_THRESHOLD_REG 0x4B +#define QG_VBAT_LOW_THRESHOLD_REG 0x4C + +#define QG_S2_NORMAL_MEAS_CTL2_REG 0x51 +#define FIFO_LENGTH_MASK GENMASK(5, 3) +#define FIFO_LENGTH_SHIFT 3 +#define NUM_OF_ACCUM_MASK GENMASK(2, 0) + +#define QG_S2_NORMAL_MEAS_CTL3_REG 0x52 + +#define QG_S3_SLEEP_OCV_MEAS_CTL4_REG 0x59 +#define S3_SLEEP_OCV_TIMER_MASK GENMASK(2, 0) + +#define QG_S3_SLEEP_OCV_TREND_CTL2_REG 0x5C +#define TREND_TOL_MASK GENMASK(5, 0) + +#define QG_S3_SLEEP_OCV_IBAT_CTL1_REG 0x5D +#define SLEEP_IBAT_QUALIFIED_LENGTH_MASK GENMASK(2, 0) + +#define QG_S3_ENTRY_IBAT_THRESHOLD_REG 0x5E +#define QG_S3_EXIT_IBAT_THRESHOLD_REG 0x5F + +#define QG_S5_OCV_VALIDATE_MEAS_CTL1_REG 0x60 +#define ALLOW_S5_BIT BIT(7) + +#define QG_S7_PON_OCV_MEAS_CTL1_REG 0x64 +#define ADC_CONV_DLY_MASK GENMASK(3, 0) + +#define QG_ESR_MEAS_TRIG_REG 0x68 +#define HW_ESR_MEAS_START_BIT BIT(0) + +#define QG_S7_PON_OCV_V_DATA0_REG 0x70 +#define QG_S7_PON_OCV_I_DATA0_REG 0x72 +#define QG_S3_GOOD_OCV_V_DATA0_REG 0x74 +#define QG_S3_GOOD_OCV_I_DATA0_REG 0x76 + +#define QG_PRE_ESR_V_DATA0_REG 0x78 +#define QG_PRE_ESR_I_DATA0_REG 0x7A +#define QG_POST_ESR_V_DATA0_REG 0x7C +#define QG_POST_ESR_I_DATA0_REG 0x7E + +#define QG_S2_NORMAL_AVG_V_DATA0_REG 0x80 +#define QG_S2_NORMAL_AVG_I_DATA0_REG 0x82 + +#define QG_V_ACCUM_DATA0_RT_REG 0x88 +#define QG_I_ACCUM_DATA0_RT_REG 0x8B +#define QG_ACCUM_CNT_RT_REG 0x8E + +#define QG_V_FIFO0_DATA0_REG 0x90 +#define QG_I_FIFO0_DATA0_REG 0xA0 + +#define QG_SOC_MONOTONIC_REG 0xBF + +#define QG_LAST_ADC_V_DATA0_REG 0xC0 +#define QG_LAST_ADC_I_DATA0_REG 0xC2 + +#define QG_LAST_BURST_AVG_I_DATA0_REG 0xC6 + +#define QG_LAST_S3_SLEEP_V_DATA0_REG 0xCC + +/* SDAM offsets */ +#define QG_SDAM_VALID_OFFSET 0x46 /* 1-byte 0x46 */ +#define QG_SDAM_SOC_OFFSET 0x47 /* 1-byte 0x47 */ +#define QG_SDAM_TEMP_OFFSET 0x48 /* 2-byte 0x48-0x49 */ +#define QG_SDAM_RBAT_OFFSET 0x4A /* 2-byte 0x4A-0x4B */ +#define QG_SDAM_OCV_OFFSET 0x4C /* 4-byte 0x4C-0x4F */ +#define QG_SDAM_IBAT_OFFSET 0x50 /* 4-byte 0x50-0x53 */ +#define QG_SDAM_TIME_OFFSET 0x54 /* 4-byte 0x54-0x57 */ +#define QG_SDAM_CYCLE_COUNT_OFFSET 0x58 /* 16-byte 0x58-0x67 */ +#define QG_SDAM_LEARNED_CAPACITY_OFFSET 0x68 /* 2-byte 0x68-0x69 */ +#define QG_SDAM_ESR_CHARGE_DELTA_OFFSET 0x6A /* 4-byte 0x6A-0x6D */ +#define QG_SDAM_ESR_DISCHARGE_DELTA_OFFSET 0x6E /* 4-byte 0x6E-0x71 */ +#define QG_SDAM_ESR_CHARGE_SF_OFFSET 0x72 /* 2-byte 0x72-0x73 */ +#define QG_SDAM_ESR_DISCHARGE_SF_OFFSET 0x74 /* 2-byte 0x74-0x75 */ +#define QG_SDAM_BATT_AGE_LEVEL_OFFSET 0x76 /* 1-byte 0x76 */ +#define QG_SDAM_MAGIC_OFFSET 0x80 /* 4-byte 0x80-0x83 */ +#define QG_SDAM_MAX_OFFSET 0xA4 + +/* Below offset is used by PBS */ +#define QG_SDAM_PON_OCV_OFFSET 0xBC /* 2-byte 0xBC-0xBD */ + +#endif diff --git a/drivers/power/supply/qcom/qg-sdam.c b/drivers/power/supply/qcom/qg-sdam.c new file mode 100644 index 000000000000..95b5aea539ed --- /dev/null +++ b/drivers/power/supply/qcom/qg-sdam.c @@ -0,0 +1,312 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2018-2020 The Linux Foundation. All rights reserved. + */ + +#define pr_fmt(fmt) "QG-K: %s: " fmt, __func__ + +#include +#include +#include +#include +#include "qg-sdam.h" +#include "qg-reg.h" + +static struct qg_sdam *the_chip; + +struct qg_sdam_info { + char *name; + u32 offset; + u32 length; +}; + +static struct qg_sdam_info sdam_info[] = { + [SDAM_VALID] = { + .name = "VALID", + .offset = QG_SDAM_VALID_OFFSET, + .length = 1, + }, + [SDAM_SOC] = { + .name = "SOC", + .offset = QG_SDAM_SOC_OFFSET, + .length = 1, + }, + [SDAM_TEMP] = { + .name = "BATT_TEMP", + .offset = QG_SDAM_TEMP_OFFSET, + .length = 2, + }, + [SDAM_RBAT_MOHM] = { + .name = "RBAT_MOHM", + .offset = QG_SDAM_RBAT_OFFSET, + .length = 2, + }, + [SDAM_OCV_UV] = { + .name = "OCV_UV", + .offset = QG_SDAM_OCV_OFFSET, + .length = 4, + }, + [SDAM_IBAT_UA] = { + .name = "IBAT_UA", + .offset = QG_SDAM_IBAT_OFFSET, + .length = 4, + }, + [SDAM_TIME_SEC] = { + .name = "TIME_SEC", + .offset = QG_SDAM_TIME_OFFSET, + .length = 4, + }, + [SDAM_PON_OCV_UV] = { + .name = "SDAM_PON_OCV", + .offset = QG_SDAM_PON_OCV_OFFSET, + .length = 2, + }, + [SDAM_ESR_CHARGE_DELTA] = { + .name = "SDAM_ESR_CHARGE_DELTA", + .offset = QG_SDAM_ESR_CHARGE_DELTA_OFFSET, + .length = 4, + }, + [SDAM_ESR_DISCHARGE_DELTA] = { + .name = "SDAM_ESR_DISCHARGE_DELTA", + .offset = QG_SDAM_ESR_DISCHARGE_DELTA_OFFSET, + .length = 4, + }, + [SDAM_ESR_CHARGE_SF] = { + .name = "SDAM_ESR_CHARGE_SF_OFFSET", + .offset = QG_SDAM_ESR_CHARGE_SF_OFFSET, + .length = 2, + }, + [SDAM_ESR_DISCHARGE_SF] = { + .name = "SDAM_ESR_DISCHARGE_SF_OFFSET", + .offset = QG_SDAM_ESR_DISCHARGE_SF_OFFSET, + .length = 2, + }, + [SDAM_BATT_AGE_LEVEL] = { + .name = "SDAM_BATT_AGE_LEVEL_OFFSET", + .offset = QG_SDAM_BATT_AGE_LEVEL_OFFSET, + .length = 1, + }, + [SDAM_MAGIC] = { + .name = "SDAM_MAGIC_OFFSET", + .offset = QG_SDAM_MAGIC_OFFSET, + .length = 4, + }, +}; + +int qg_sdam_write(u8 param, u32 data) +{ + int rc; + struct qg_sdam *chip = the_chip; + u32 offset; + size_t length; + + if (!chip) { + pr_err("Invalid sdam-chip pointer\n"); + return -EINVAL; + } + + if (param >= SDAM_MAX) { + pr_err("Invalid SDAM param %d\n", param); + return -EINVAL; + } + + offset = chip->sdam_base + sdam_info[param].offset; + length = sdam_info[param].length; + rc = regmap_bulk_write(chip->regmap, offset, (u8 *)&data, length); + if (rc < 0) + pr_err("Failed to write offset=%0x4 param=%d value=%d\n", + offset, param, data); + else + pr_debug("QG SDAM write param=%s value=%d\n", + sdam_info[param].name, data); + + return rc; +} + +int qg_sdam_read(u8 param, u32 *data) +{ + int rc; + struct qg_sdam *chip = the_chip; + u32 offset; + size_t length; + + if (!chip) { + pr_err("Invalid sdam-chip pointer\n"); + return -EINVAL; + } + + if (param >= SDAM_MAX) { + pr_err("Invalid SDAM param %d\n", param); + return -EINVAL; + } + + *data = 0; + offset = chip->sdam_base + sdam_info[param].offset; + length = sdam_info[param].length; + rc = regmap_raw_read(chip->regmap, offset, (u8 *)data, length); + if (rc < 0) + pr_err("Failed to read offset=%0x4 param=%d\n", + offset, param); + else + pr_debug("QG SDAM read param=%s value=%d\n", + sdam_info[param].name, *data); + + return rc; +} + +int qg_sdam_multibyte_write(u32 offset, u8 *data, u32 length) +{ + int rc, i; + struct qg_sdam *chip = the_chip; + + if (!chip) { + pr_err("Invalid sdam-chip pointer\n"); + return -EINVAL; + } + + offset = chip->sdam_base + offset; + rc = regmap_bulk_write(chip->regmap, offset, data, (size_t)length); + if (rc < 0) { + pr_err("Failed to write offset=%0x4 value=%d\n", + offset, *data); + } else { + for (i = 0; i < length; i++) + pr_debug("QG SDAM write offset=%0x4 value=%d\n", + offset++, data[i]); + } + + return rc; +} + +int qg_sdam_multibyte_read(u32 offset, u8 *data, u32 length) +{ + int rc, i; + struct qg_sdam *chip = the_chip; + + if (!chip) { + pr_err("Invalid sdam-chip pointer\n"); + return -EINVAL; + } + + offset = chip->sdam_base + offset; + rc = regmap_raw_read(chip->regmap, offset, (u8 *)data, (size_t)length); + if (rc < 0) { + pr_err("Failed to read offset=%0x4\n", offset); + } else { + for (i = 0; i < length; i++) + pr_debug("QG SDAM read offset=%0x4 value=%d\n", + offset++, data[i]); + } + + return rc; +} + +int qg_sdam_read_all(u32 *sdam_data) +{ + int i, rc; + struct qg_sdam *chip = the_chip; + + if (!chip) { + pr_err("Invalid sdam-chip pointer\n"); + return -EINVAL; + } + + for (i = 0; i < SDAM_MAX; i++) { + rc = qg_sdam_read(i, &sdam_data[i]); + if (rc < 0) { + pr_err("Failed to read SDAM param=%s rc=%d\n", + sdam_info[i].name, rc); + return rc; + } + } + + return 0; +} + +int qg_sdam_write_all(u32 *sdam_data) +{ + int i, rc; + struct qg_sdam *chip = the_chip; + + if (!chip) { + pr_err("Invalid sdam-chip pointer\n"); + return -EINVAL; + } + + for (i = 0; i < SDAM_MAX; i++) { + rc = qg_sdam_write(i, sdam_data[i]); + if (rc < 0) { + pr_err("Failed to write SDAM param=%s rc=%d\n", + sdam_info[i].name, rc); + return rc; + } + } + + return 0; +} + +int qg_sdam_clear(void) +{ + int i, rc = 0; + struct qg_sdam *chip = the_chip; + u8 data = 0; + + if (!chip) { + pr_err("Invalid sdam-chip pointer\n"); + return -EINVAL; + } + + for (i = SDAM_MIN_OFFSET; i <= SDAM_MAX_OFFSET; i++) + rc |= qg_sdam_multibyte_write(i, &data, 1); + + return rc; +} + +int qg_sdam_init(struct device *dev) +{ + int rc; + u32 base = 0, type = 0; + struct qg_sdam *chip; + struct device_node *child, *node = dev->of_node; + + chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return 0; + + chip->regmap = dev_get_regmap(dev->parent, NULL); + if (!chip->regmap) { + pr_err("Parent regmap is unavailable\n"); + return -ENXIO; + } + + /* get the SDAM base address */ + for_each_available_child_of_node(node, child) { + rc = of_property_read_u32(child, "reg", &base); + if (rc < 0) { + pr_err("Failed to read base address rc=%d\n", rc); + return rc; + } + + rc = regmap_read(chip->regmap, base + PERPH_TYPE_REG, &type); + if (rc < 0) { + pr_err("Failed to read type rc=%d\n", rc); + return rc; + } + + switch (type) { + case SDAM_TYPE: + chip->sdam_base = base; + break; + default: + break; + } + } + if (!chip->sdam_base) { + pr_err("QG SDAM node not defined\n"); + return -EINVAL; + } + + the_chip = chip; + + return 0; +} diff --git a/drivers/power/supply/qcom/qg-sdam.h b/drivers/power/supply/qcom/qg-sdam.h new file mode 100644 index 000000000000..dea1fc5e96cd --- /dev/null +++ b/drivers/power/supply/qcom/qg-sdam.h @@ -0,0 +1,45 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2018-2020 The Linux Foundation. All rights reserved. + */ + +#ifndef __QG_SDAM_H__ +#define __QG_SDAM_H__ + +#define SDAM_TYPE 0x2E +#define SDAM_MIN_OFFSET 0x45 +#define SDAM_MAX_OFFSET 0xB3 + +enum qg_sdam_param { + SDAM_VALID, + SDAM_SOC, + SDAM_TEMP, + SDAM_RBAT_MOHM, + SDAM_OCV_UV, + SDAM_IBAT_UA, + SDAM_TIME_SEC, + SDAM_PON_OCV_UV, + SDAM_ESR_CHARGE_DELTA, + SDAM_ESR_DISCHARGE_DELTA, + SDAM_ESR_CHARGE_SF, + SDAM_ESR_DISCHARGE_SF, + SDAM_MAGIC, + SDAM_BATT_AGE_LEVEL, + SDAM_MAX, +}; + +struct qg_sdam { + struct regmap *regmap; + u16 sdam_base; +}; + +int qg_sdam_init(struct device *dev); +int qg_sdam_write(u8 param, u32 data); +int qg_sdam_read(u8 param, u32 *data); +int qg_sdam_write_all(u32 *sdam_data); +int qg_sdam_read_all(u32 *sdam_data); +int qg_sdam_multibyte_write(u32 offset, u8 *sdam_data, u32 length); +int qg_sdam_multibyte_read(u32 offset, u8 *sdam_data, u32 length); +int qg_sdam_clear(void); + +#endif diff --git a/drivers/power/supply/qcom/qg-soc.c b/drivers/power/supply/qcom/qg-soc.c new file mode 100644 index 000000000000..ad57f2dfb694 --- /dev/null +++ b/drivers/power/supply/qcom/qg-soc.c @@ -0,0 +1,703 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2018-2020 The Linux Foundation. All rights reserved. + */ + +#define pr_fmt(fmt) "QG-K: %s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include "fg-alg.h" +#include "qg-sdam.h" +#include "qg-core.h" +#include "qg-reg.h" +#include "qg-util.h" +#include "qg-defs.h" +#include "qg-profile-lib.h" +#include "qg-soc.h" + +enum soc_scaling_feature { + QG_FVSS = BIT(0), + QG_TCSS = BIT(1), + QG_BASS = BIT(2), +}; + +#define DEFAULT_UPDATE_TIME_MS 64000 +#define SOC_SCALE_HYST_MS 2000 +#define VBAT_LOW_HYST_UV 50000 +#define FULL_SOC 100 + +static int qg_ss_feature; +static ssize_t qg_ss_feature_show(struct device *dev, struct device_attribute + *attr, char *buf) +{ + return scnprintf(buf, PAGE_SIZE, "0x%4x\n", qg_ss_feature); +} + +static ssize_t qg_ss_feature_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int val; + + if (kstrtos32(buf, 0, &val)) + return -EINVAL; + + qg_ss_feature = val; + + return count; +} +DEVICE_ATTR_RW(qg_ss_feature); + +static int qg_delta_soc_interval_ms = 20000; +static ssize_t soc_interval_ms_show(struct device *dev, struct device_attribute + *attr, char *buf) +{ + return scnprintf(buf, PAGE_SIZE, "%d\n", qg_delta_soc_interval_ms); +} + +static ssize_t soc_interval_ms_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int val; + + if (kstrtos32(buf, 0, &val)) + return -EINVAL; + + qg_delta_soc_interval_ms = val; + + return count; +} +DEVICE_ATTR_RW(soc_interval_ms); + +static int qg_fvss_delta_soc_interval_ms = 10000; +static ssize_t fvss_delta_soc_interval_ms_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return scnprintf(buf, PAGE_SIZE, "%d\n", qg_fvss_delta_soc_interval_ms); +} + +static ssize_t fvss_delta_soc_interval_ms_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int val; + + if (kstrtos32(buf, 0, &val)) + return -EINVAL; + + qg_fvss_delta_soc_interval_ms = val; + + return count; +} +DEVICE_ATTR_RW(fvss_delta_soc_interval_ms); + +static int qg_delta_soc_cold_interval_ms = 4000; +static ssize_t soc_cold_interval_ms_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return scnprintf(buf, PAGE_SIZE, "%d\n", qg_delta_soc_cold_interval_ms); +} + +static ssize_t soc_cold_interval_ms_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int val; + + if (kstrtos32(buf, 0, &val)) + return -EINVAL; + + qg_delta_soc_cold_interval_ms = val; + + return count; +} +DEVICE_ATTR_RW(soc_cold_interval_ms); + +static int qg_maint_soc_update_ms = 120000; +static ssize_t maint_soc_update_ms_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return scnprintf(buf, PAGE_SIZE, "%d\n", qg_maint_soc_update_ms); +} + +static ssize_t maint_soc_update_ms_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int val; + + if (kstrtos32(buf, 0, &val)) + return -EINVAL; + + qg_maint_soc_update_ms = val; + + return count; +} +DEVICE_ATTR_RW(maint_soc_update_ms); + +/* FVSS scaling only based on VBAT */ +static int qg_fvss_vbat_scaling = 1; +static ssize_t fvss_vbat_scaling_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return scnprintf(buf, PAGE_SIZE, "%d\n", qg_fvss_vbat_scaling); +} + +static ssize_t fvss_vbat_scaling_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int val; + + if (kstrtos32(buf, 0, &val)) + return -EINVAL; + + qg_fvss_vbat_scaling = val; + + return count; +} +DEVICE_ATTR_RW(fvss_vbat_scaling); + +static int qg_process_fvss_soc(struct qpnp_qg *chip, int sys_soc) +{ + int rc, vbat_uv = 0, vbat_cutoff_uv = chip->dt.vbatt_cutoff_mv * 1000; + int soc_vbat = 0, wt_vbat = 0, wt_sys = 0, soc_fvss = 0; + + if (!chip->dt.fvss_enable && !(qg_ss_feature & QG_FVSS)) + goto exit_soc_scale; + + if (chip->charge_status == POWER_SUPPLY_STATUS_CHARGING) + goto exit_soc_scale; + + rc = qg_get_battery_voltage(chip, &vbat_uv); + if (rc < 0) + goto exit_soc_scale; + + if (!chip->last_fifo_v_uv) + chip->last_fifo_v_uv = vbat_uv; + + if (chip->last_fifo_v_uv > (chip->dt.fvss_vbat_mv * 1000)) { + qg_dbg(chip, QG_DEBUG_SOC, "FVSS: last_fifo_v=%d fvss_entry_uv=%d - exit\n", + chip->last_fifo_v_uv, chip->dt.fvss_vbat_mv * 1000); + goto exit_soc_scale; + } + + /* Enter FVSS */ + if (!chip->fvss_active) { + chip->vbat_fvss_entry = CAP(vbat_cutoff_uv, + chip->dt.fvss_vbat_mv * 1000, + chip->last_fifo_v_uv); + chip->soc_fvss_entry = sys_soc; + chip->fvss_active = true; + } else if (chip->last_fifo_v_uv > chip->vbat_fvss_entry) { + /* VBAT has gone beyond the entry voltage */ + chip->vbat_fvss_entry = chip->last_fifo_v_uv; + chip->soc_fvss_entry = sys_soc; + } + + soc_vbat = qg_linear_interpolate(chip->soc_fvss_entry, + chip->vbat_fvss_entry, + 0, + vbat_cutoff_uv, + chip->last_fifo_v_uv); + soc_vbat = CAP(0, 100, soc_vbat); + + if (qg_fvss_vbat_scaling) { + wt_vbat = 100; + wt_sys = 0; + } else { + wt_sys = qg_linear_interpolate(100, + chip->soc_fvss_entry, + 0, + 0, + sys_soc); + wt_sys = CAP(0, 100, wt_sys); + wt_vbat = 100 - wt_sys; + } + + soc_fvss = ((soc_vbat * wt_vbat) + (sys_soc * wt_sys)) / 100; + soc_fvss = CAP(0, 100, soc_fvss); + + qg_dbg(chip, QG_DEBUG_SOC, "FVSS: vbat_fvss_entry=%d soc_fvss_entry=%d cutoff_uv=%d vbat_uv=%d fifo_avg_v=%d soc_vbat=%d sys_soc=%d wt_vbat=%d wt_sys=%d soc_fvss=%d\n", + chip->vbat_fvss_entry, chip->soc_fvss_entry, + vbat_cutoff_uv, vbat_uv, chip->last_fifo_v_uv, + soc_vbat, sys_soc, wt_vbat, wt_sys, soc_fvss); + + return soc_fvss; + +exit_soc_scale: + chip->fvss_active = false; + return sys_soc; +} + +#define IBAT_HYST_PC 10 +#define TCSS_ENTRY_COUNT 2 +static int qg_process_tcss_soc(struct qpnp_qg *chip, int sys_soc) +{ + int rc, ibatt_diff = 0, ibat_inc_hyst = 0; + int qg_iterm_ua = (-1 * chip->dt.iterm_ma * 1000); + int soc_ibat, wt_ibat, wt_sys; + union power_supply_propval prop = {0, }; + + if (!chip->dt.tcss_enable && !(qg_ss_feature & QG_TCSS)) + goto exit_soc_scale; + + if (chip->sys_soc < (chip->dt.tcss_entry_soc * 100)) + goto exit_soc_scale; + + if (chip->sys_soc >= QG_MAX_SOC && chip->soc_tcss >= QG_MAX_SOC) + goto exit_soc_scale; + + rc = power_supply_get_property(chip->batt_psy, + POWER_SUPPLY_PROP_HEALTH, &prop); + if (!rc && (prop.intval == POWER_SUPPLY_HEALTH_COOL || + prop.intval == POWER_SUPPLY_HEALTH_WARM)) + goto exit_soc_scale; + + if (chip->last_fifo_i_ua >= 0) + goto exit_soc_scale; + else if (++chip->tcss_entry_count < TCSS_ENTRY_COUNT) + goto skip_entry_count; + + if (!chip->tcss_active) { + chip->soc_tcss = sys_soc; + chip->soc_tcss_entry = sys_soc; + chip->ibat_tcss_entry = min(chip->last_fifo_i_ua, qg_iterm_ua); + chip->prev_fifo_i_ua = chip->last_fifo_i_ua; + chip->tcss_active = true; + } + + rc = power_supply_get_property(chip->batt_psy, + POWER_SUPPLY_PROP_INPUT_CURRENT_LIMITED, &prop); + if (!rc && prop.intval) { + qg_dbg(chip, QG_DEBUG_SOC, + "Input limited sys_soc=%d soc_tcss=%d\n", + sys_soc, chip->soc_tcss); + if (chip->soc_tcss > sys_soc) + sys_soc = chip->soc_tcss; + goto exit_soc_scale; + } + + ibatt_diff = chip->last_fifo_i_ua - chip->prev_fifo_i_ua; + if (ibatt_diff > 0) { + /* + * if the battery charge current has suddendly dropped, allow it + * to decrease only by a small fraction to avoid a SOC jump. + */ + ibat_inc_hyst = (chip->prev_fifo_i_ua * IBAT_HYST_PC) / 100; + if (ibatt_diff > abs(ibat_inc_hyst)) + chip->prev_fifo_i_ua -= ibat_inc_hyst; + else + chip->prev_fifo_i_ua = chip->last_fifo_i_ua; + } + + chip->prev_fifo_i_ua = min(chip->prev_fifo_i_ua, qg_iterm_ua); + soc_ibat = qg_linear_interpolate(chip->soc_tcss_entry, + chip->ibat_tcss_entry, + QG_MAX_SOC, + qg_iterm_ua, + chip->prev_fifo_i_ua); + soc_ibat = CAP(QG_MIN_SOC, QG_MAX_SOC, soc_ibat); + + wt_ibat = qg_linear_interpolate(1, chip->soc_tcss_entry, + 10000, 10000, soc_ibat); + wt_ibat = CAP(QG_MIN_SOC, QG_MAX_SOC, wt_ibat); + wt_sys = 10000 - wt_ibat; + + chip->soc_tcss = DIV_ROUND_CLOSEST((soc_ibat * wt_ibat) + + (wt_sys * sys_soc), 10000); + chip->soc_tcss = CAP(QG_MIN_SOC, QG_MAX_SOC, chip->soc_tcss); + + qg_dbg(chip, QG_DEBUG_SOC, + "TCSS: fifo_i=%d prev_fifo_i=%d ibatt_tcss_entry=%d qg_term=%d soc_tcss_entry=%d sys_soc=%d soc_ibat=%d wt_ibat=%d wt_sys=%d soc_tcss=%d\n", + chip->last_fifo_i_ua, chip->prev_fifo_i_ua, + chip->ibat_tcss_entry, qg_iterm_ua, + chip->soc_tcss_entry, sys_soc, soc_ibat, + wt_ibat, wt_sys, chip->soc_tcss); + + return chip->soc_tcss; + +exit_soc_scale: + chip->tcss_entry_count = 0; +skip_entry_count: + chip->tcss_active = false; + if (chip->dt.tcss_enable || (qg_ss_feature & QG_TCSS)) + qg_dbg(chip, QG_DEBUG_SOC, "TCSS: Quit - enabled=%d sys_soc=%d tcss_entry_count=%d fifo_i_ua=%d\n", + chip->dt.tcss_enable, sys_soc, chip->tcss_entry_count, + chip->last_fifo_i_ua); + return sys_soc; +} + +#define BASS_SYS_MSOC_DELTA 2 +static int qg_process_bass_soc(struct qpnp_qg *chip, int sys_soc) +{ + int bass_soc = sys_soc, msoc = chip->msoc; + int batt_soc = CAP(0, 100, DIV_ROUND_CLOSEST(chip->batt_soc, 100)); + + if (!chip->dt.bass_enable && !(qg_ss_feature & QG_BASS)) + goto exit_soc_scale; + + qg_dbg(chip, QG_DEBUG_SOC, "BASS Entry: fifo_i=%d sys_soc=%d msoc=%d batt_soc=%d fvss_active=%d\n", + chip->last_fifo_i_ua, sys_soc, msoc, + batt_soc, chip->fvss_active); + + /* Skip BASS if FVSS is active */ + if (chip->fvss_active) + goto exit_soc_scale; + + if (((sys_soc - msoc) < BASS_SYS_MSOC_DELTA) || + chip->last_fifo_i_ua <= 0) + goto exit_soc_scale; + + if (!chip->bass_active) { + chip->bass_active = true; + chip->bsoc_bass_entry = batt_soc; + } + + /* Drop the sys_soc by 1% if batt_soc has dropped */ + if ((chip->bsoc_bass_entry - batt_soc) >= 1) { + bass_soc = (msoc > 0) ? msoc - 1 : 0; + chip->bass_active = false; + } + + qg_dbg(chip, QG_DEBUG_SOC, "BASS Exit: fifo_i_ua=%d sys_soc=%d msoc=%d bsoc_bass_entry=%d batt_soc=%d bass_soc=%d\n", + chip->last_fifo_i_ua, sys_soc, msoc, + chip->bsoc_bass_entry, chip->batt_soc, bass_soc); + + return bass_soc; + +exit_soc_scale: + chip->bass_active = false; + if (chip->dt.bass_enable || (qg_ss_feature & QG_BASS)) + qg_dbg(chip, QG_DEBUG_SOC, "BASS Quit: enabled=%d fifo_i_ua=%d sys_soc=%d msoc=%d batt_soc=%d\n", + chip->dt.bass_enable, chip->last_fifo_i_ua, + sys_soc, msoc, chip->batt_soc); + return sys_soc; +} + +int qg_adjust_sys_soc(struct qpnp_qg *chip) +{ + int soc, vbat_uv, rc; + int vcutoff_uv = chip->dt.vbatt_cutoff_mv * 1000; + + chip->sys_soc = CAP(QG_MIN_SOC, QG_MAX_SOC, chip->sys_soc); + + /* TCSS */ + chip->sys_soc = qg_process_tcss_soc(chip, chip->sys_soc); + + if (chip->sys_soc == QG_MAX_SOC) { + soc = FULL_SOC; + } else if (chip->sys_soc >= (QG_MAX_SOC - 100)) { + /* Hold SOC to 100% if we are dropping from 100 to 99 */ + if (chip->last_adj_ssoc == FULL_SOC) + soc = FULL_SOC; + else /* Hold SOC at 99% until we hit 100% */ + soc = FULL_SOC - 1; + } else { + soc = DIV_ROUND_CLOSEST(chip->sys_soc, 100); + } + + /* FVSS */ + soc = qg_process_fvss_soc(chip, soc); + + /* BASS */ + soc = qg_process_bass_soc(chip, soc); + + if (soc == 0) { + /* Hold SOC to 1% if we have not dropped below cutoff */ + rc = qg_get_vbat_avg(chip, &vbat_uv); + if (!rc && (vbat_uv >= (vcutoff_uv + VBAT_LOW_HYST_UV))) { + soc = 1; + qg_dbg(chip, QG_DEBUG_SOC, "vbat_uv=%duV holding SOC to 1%\n", + vbat_uv); + } + } + + qg_dbg(chip, QG_DEBUG_SOC, "sys_soc=%d adjusted sys_soc=%d\n", + chip->sys_soc, soc); + + chip->last_adj_ssoc = soc; + + return soc; +} + +static void get_next_update_time(struct qpnp_qg *chip) +{ + int soc_points = 0, batt_temp = 0; + int min_delta_soc_interval_ms = qg_delta_soc_interval_ms; + int rc = 0, rt_time_ms = 0, full_time_ms = DEFAULT_UPDATE_TIME_MS; + + get_fifo_done_time(chip, false, &full_time_ms); + get_fifo_done_time(chip, true, &rt_time_ms); + + full_time_ms = CAP(0, DEFAULT_UPDATE_TIME_MS, + full_time_ms - rt_time_ms); + + soc_points = abs(chip->msoc - chip->catch_up_soc); + if (chip->maint_soc > 0) + soc_points = max(abs(chip->msoc - chip->maint_soc), soc_points); + soc_points /= chip->dt.delta_soc; + + /* Lower the delta soc interval by half at cold */ + rc = qg_get_battery_temp(chip, &batt_temp); + if (!rc && batt_temp < chip->dt.cold_temp_threshold) + min_delta_soc_interval_ms = qg_delta_soc_cold_interval_ms; + else if (chip->maint_soc > 0 && chip->maint_soc >= chip->recharge_soc) + /* if in maintenance mode scale slower */ + min_delta_soc_interval_ms = qg_maint_soc_update_ms; + else if (chip->fvss_active) + min_delta_soc_interval_ms = qg_fvss_delta_soc_interval_ms; + + if (!min_delta_soc_interval_ms) + min_delta_soc_interval_ms = 1000; /* 1 second */ + + chip->next_wakeup_ms = (full_time_ms / (soc_points + 1)) + - SOC_SCALE_HYST_MS; + chip->next_wakeup_ms = max(chip->next_wakeup_ms, + min_delta_soc_interval_ms); + + qg_dbg(chip, QG_DEBUG_SOC, "fifo_full_time=%d secs fifo_real_time=%d secs soc_scale_points=%d\n", + full_time_ms / 1000, rt_time_ms / 1000, soc_points); +} + +static bool is_scaling_required(struct qpnp_qg *chip) +{ + bool input_present = is_input_present(chip); + + if (!chip->profile_loaded) + return false; + + if (chip->maint_soc > 0 && + (abs(chip->maint_soc - chip->msoc) >= chip->dt.delta_soc)) + return true; + + if ((abs(chip->catch_up_soc - chip->msoc) < chip->dt.delta_soc) && + chip->catch_up_soc != 0 && chip->catch_up_soc != 100) + return false; + + if (chip->catch_up_soc == chip->msoc) + /* SOC has not changed */ + return false; + + + if (chip->catch_up_soc > chip->msoc && !input_present) + /* input is not present and SOC has increased */ + return false; + + if (chip->catch_up_soc > chip->msoc && input_present && + (chip->charge_status != POWER_SUPPLY_STATUS_CHARGING && + chip->charge_status != POWER_SUPPLY_STATUS_FULL)) + /* USB is present, but not charging */ + return false; + + return true; +} + +static bool maint_soc_timeout(struct qpnp_qg *chip) +{ + unsigned long now; + int rc; + + if (chip->maint_soc < 0) + return false; + + rc = get_rtc_time(&now); + if (rc < 0) + return true; + + /* Do not scale if we have dropped below recharge-soc */ + if (chip->maint_soc < chip->recharge_soc) + return true; + + if ((now - chip->last_maint_soc_update_time) >= + (qg_maint_soc_update_ms / 1000)) { + chip->last_maint_soc_update_time = now; + return true; + } + + return false; +} + +static void update_msoc(struct qpnp_qg *chip) +{ + int rc = 0, sdam_soc, batt_temp = 0; + bool input_present = is_input_present(chip); + + if (chip->catch_up_soc > chip->msoc) { + /* SOC increased */ + if (input_present) /* Increment if input is present */ + chip->msoc += chip->dt.delta_soc; + } else if (chip->catch_up_soc < chip->msoc) { + /* SOC dropped */ + chip->msoc -= chip->dt.delta_soc; + } + chip->msoc = CAP(0, 100, chip->msoc); + + if (chip->maint_soc > 0 && chip->msoc < chip->maint_soc + && maint_soc_timeout(chip)) { + chip->maint_soc -= chip->dt.delta_soc; + chip->maint_soc = CAP(0, 100, chip->maint_soc); + } + + /* maint_soc dropped below msoc, skip using it */ + if (chip->maint_soc <= chip->msoc) + chip->maint_soc = -EINVAL; + + /* update the SOC register */ + rc = qg_write_monotonic_soc(chip, chip->msoc); + if (rc < 0) + pr_err("Failed to update MSOC register rc=%d\n", rc); + + /* update SDAM with the new MSOC */ + sdam_soc = (chip->maint_soc > 0) ? chip->maint_soc : chip->msoc; + chip->sdam_data[SDAM_SOC] = sdam_soc; + rc = qg_sdam_write(SDAM_SOC, sdam_soc); + if (rc < 0) + pr_err("Failed to update SDAM with MSOC rc=%d\n", rc); + + if (!chip->dt.cl_disable && chip->cl->active) { + rc = qg_get_battery_temp(chip, &batt_temp); + if (rc < 0) { + pr_err("Failed to read BATT_TEMP rc=%d\n", rc); + } else if (chip->batt_soc >= 0) { + cap_learning_update(chip->cl, batt_temp, chip->batt_soc, + chip->charge_status, chip->charge_done, + input_present, false); + } + } + + cycle_count_update(chip->counter, + DIV_ROUND_CLOSEST(chip->msoc * 255, 100), + chip->charge_status, chip->charge_done, + input_present); + + qg_dbg(chip, QG_DEBUG_SOC, + "SOC scale: Update maint_soc=%d msoc=%d catch_up_soc=%d delta_soc=%d\n", + chip->maint_soc, chip->msoc, + chip->catch_up_soc, chip->dt.delta_soc); +} + +static void scale_soc_stop(struct qpnp_qg *chip) +{ + chip->next_wakeup_ms = 0; + alarm_cancel(&chip->alarm_timer); + + qg_dbg(chip, QG_DEBUG_SOC, + "SOC scale stopped: msoc=%d catch_up_soc=%d\n", + chip->msoc, chip->catch_up_soc); +} + +static void scale_soc_work(struct work_struct *work) +{ + struct qpnp_qg *chip = container_of(work, + struct qpnp_qg, scale_soc_work); + + mutex_lock(&chip->soc_lock); + + if (!is_scaling_required(chip)) { + scale_soc_stop(chip); + goto done; + } + + update_msoc(chip); + + if (is_scaling_required(chip)) { + alarm_start_relative(&chip->alarm_timer, + ms_to_ktime(chip->next_wakeup_ms)); + } else { + scale_soc_stop(chip); + goto done_psy; + } + + qg_dbg(chip, QG_DEBUG_SOC, + "SOC scale: Work msoc=%d catch_up_soc=%d delta_soc=%d next_wakeup=%d sec\n", + chip->msoc, chip->catch_up_soc, chip->dt.delta_soc, + chip->next_wakeup_ms / 1000); + +done_psy: + power_supply_changed(chip->qg_psy); +done: + pm_relax(chip->dev); + mutex_unlock(&chip->soc_lock); +} + +static enum alarmtimer_restart + qpnp_msoc_timer(struct alarm *alarm, ktime_t now) +{ + struct qpnp_qg *chip = container_of(alarm, + struct qpnp_qg, alarm_timer); + + /* timer callback runs in atomic context, cannot use voter */ + pm_stay_awake(chip->dev); + schedule_work(&chip->scale_soc_work); + + return ALARMTIMER_NORESTART; +} + +int qg_scale_soc(struct qpnp_qg *chip, bool force_soc) +{ + int rc = 0; + + mutex_lock(&chip->soc_lock); + + qg_dbg(chip, QG_DEBUG_SOC, + "SOC scale: Start msoc=%d catch_up_soc=%d delta_soc=%d\n", + chip->msoc, chip->catch_up_soc, chip->dt.delta_soc); + + if (force_soc) { + chip->msoc = chip->catch_up_soc; + rc = qg_write_monotonic_soc(chip, chip->msoc); + if (rc < 0) + pr_err("Failed to update MSOC register rc=%d\n", rc); + + qg_dbg(chip, QG_DEBUG_SOC, + "SOC scale: Forced msoc=%d\n", chip->msoc); + goto done_psy; + } + + if (!is_scaling_required(chip)) { + scale_soc_stop(chip); + goto done; + } + + update_msoc(chip); + + if (is_scaling_required(chip)) { + get_next_update_time(chip); + alarm_start_relative(&chip->alarm_timer, + ms_to_ktime(chip->next_wakeup_ms)); + } else { + scale_soc_stop(chip); + goto done_psy; + } + + qg_dbg(chip, QG_DEBUG_SOC, + "SOC scale: msoc=%d catch_up_soc=%d delta_soc=%d next_wakeup=%d sec\n", + chip->msoc, chip->catch_up_soc, chip->dt.delta_soc, + chip->next_wakeup_ms / 1000); + +done_psy: + power_supply_changed(chip->qg_psy); +done: + mutex_unlock(&chip->soc_lock); + return rc; +} + +int qg_soc_init(struct qpnp_qg *chip) +{ + if (alarmtimer_get_rtcdev()) { + alarm_init(&chip->alarm_timer, ALARM_BOOTTIME, + qpnp_msoc_timer); + } else { + pr_err("Failed to get soc alarm-timer\n"); + return -EINVAL; + } + INIT_WORK(&chip->scale_soc_work, scale_soc_work); + + return 0; +} + +void qg_soc_exit(struct qpnp_qg *chip) +{ + alarm_cancel(&chip->alarm_timer); +} diff --git a/drivers/power/supply/qcom/qg-soc.h b/drivers/power/supply/qcom/qg-soc.h new file mode 100644 index 000000000000..064ab48a4b6e --- /dev/null +++ b/drivers/power/supply/qcom/qg-soc.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2018, 2020 The Linux Foundation. All rights reserved. + */ + +#ifndef __QG_SOC_H__ +#define __QG_SOC_H__ + +int qg_scale_soc(struct qpnp_qg *chip, bool force_soc); +int qg_soc_init(struct qpnp_qg *chip); +void qg_soc_exit(struct qpnp_qg *chip); +int qg_adjust_sys_soc(struct qpnp_qg *chip); + +extern struct device_attribute dev_attr_soc_interval_ms; +extern struct device_attribute dev_attr_soc_cold_interval_ms; +extern struct device_attribute dev_attr_maint_soc_update_ms; +extern struct device_attribute dev_attr_fvss_delta_soc_interval_ms; +extern struct device_attribute dev_attr_fvss_vbat_scaling; +extern struct device_attribute dev_attr_qg_ss_feature; + +#endif /* __QG_SOC_H__ */ diff --git a/drivers/power/supply/qcom/qg-util.c b/drivers/power/supply/qcom/qg-util.c new file mode 100644 index 000000000000..170ca876d9b7 --- /dev/null +++ b/drivers/power/supply/qcom/qg-util.c @@ -0,0 +1,471 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2018-2020 The Linux Foundation. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "qg-sdam.h" +#include "qg-core.h" +#include "qg-reg.h" +#include "qg-defs.h" +#include "qg-util.h" + +static inline bool is_sticky_register(u32 addr) +{ + if ((addr & 0xFF) == QG_STATUS2_REG) + return true; + + return false; +} + +int qg_read(struct qpnp_qg *chip, u32 addr, u8 *val, int len) +{ + int rc, i; + u32 dummy = 0; + + rc = regmap_bulk_read(chip->regmap, addr, val, len); + if (rc < 0) { + pr_err("Failed regmap_read for address %04x rc=%d\n", addr, rc); + return rc; + } + + if (is_sticky_register(addr)) { + /* write to the sticky register to clear it */ + rc = regmap_write(chip->regmap, addr, dummy); + if (rc < 0) { + pr_err("Failed regmap_write for %04x rc=%d\n", + addr, rc); + return rc; + } + } + + if (*chip->debug_mask & QG_DEBUG_BUS_READ) { + pr_info("length %d addr=%04x\n", len, addr); + for (i = 0; i < len; i++) + pr_info("val[%d]: %02x\n", i, val[i]); + } + + return 0; +} + +int qg_write(struct qpnp_qg *chip, u32 addr, u8 *val, int len) +{ + int rc, i; + + mutex_lock(&chip->bus_lock); + + if (len > 1) + rc = regmap_bulk_write(chip->regmap, addr, val, len); + else + rc = regmap_write(chip->regmap, addr, *val); + + if (rc < 0) { + pr_err("Failed regmap_write for address %04x rc=%d\n", + addr, rc); + goto out; + } + + if (*chip->debug_mask & QG_DEBUG_BUS_WRITE) { + pr_info("length %d addr=%04x\n", len, addr); + for (i = 0; i < len; i++) + pr_info("val[%d]: %02x\n", i, val[i]); + } +out: + mutex_unlock(&chip->bus_lock); + return rc; +} + +int qg_masked_write(struct qpnp_qg *chip, int addr, u32 mask, u32 val) +{ + int rc; + + mutex_lock(&chip->bus_lock); + + rc = regmap_update_bits(chip->regmap, addr, mask, val); + if (rc < 0) { + pr_err("Failed regmap_update_bits for address %04x rc=%d\n", + addr, rc); + goto out; + } + + if (*chip->debug_mask & QG_DEBUG_BUS_WRITE) + pr_info("addr=%04x mask: %02x val: %02x\n", addr, mask, val); + +out: + mutex_unlock(&chip->bus_lock); + return rc; +} + +int qg_read_raw_data(struct qpnp_qg *chip, int addr, u32 *data) +{ + int rc; + u8 reg[2] = {0}; + + rc = qg_read(chip, chip->qg_base + addr, ®[0], 2); + if (rc < 0) { + pr_err("Failed to read QG addr %d rc=%d\n", addr, rc); + return rc; + } + + *data = reg[0] | (reg[1] << 8); + + return rc; +} + +s64 qg_iraw_to_ua(struct qpnp_qg *chip, int iraw) +{ + if (chip->qg_subtype == QG_ADC_IBAT_5A) + return div_s64(152588LL * (s64)iraw, 1000); + else + return div_s64(305176LL * (s64)iraw, 1000); +} + +int get_fifo_length(struct qpnp_qg *chip, u32 *fifo_length, bool rt) +{ + int rc; + u8 reg = 0; + u32 addr; + + addr = rt ? QG_STATUS3_REG : QG_S2_NORMAL_MEAS_CTL2_REG; + rc = qg_read(chip, chip->qg_base + addr, ®, 1); + if (rc < 0) { + pr_err("Failed to read FIFO length rc=%d\n", rc); + return rc; + } + + if (rt) { + *fifo_length = reg & COUNT_FIFO_RT_MASK; + } else { + *fifo_length = (reg & FIFO_LENGTH_MASK) >> FIFO_LENGTH_SHIFT; + *fifo_length += 1; + } + + return rc; +} + +int get_sample_count(struct qpnp_qg *chip, u32 *sample_count) +{ + int rc; + u8 reg = 0; + + rc = qg_read(chip, chip->qg_base + QG_S2_NORMAL_MEAS_CTL2_REG, + ®, 1); + if (rc < 0) { + pr_err("Failed to read FIFO sample count rc=%d\n", rc); + return rc; + } + + *sample_count = 1 << ((reg & NUM_OF_ACCUM_MASK) + 1); + + return rc; +} + +#define QG_CLK_RATE 32000 +#define QG_ACTUAL_CLK_RATE 32764 +int get_sample_interval(struct qpnp_qg *chip, u32 *sample_interval) +{ + int rc; + u8 reg = 0; + + rc = qg_read(chip, chip->qg_base + QG_S2_NORMAL_MEAS_CTL3_REG, + ®, 1); + if (rc < 0) { + pr_err("Failed to read FIFO sample interval rc=%d\n", rc); + return rc; + } + + *sample_interval = reg * 10; + + if (chip->wa_flags & QG_CLK_ADJUST_WA) { + *sample_interval = DIV_ROUND_CLOSEST( + *sample_interval * QG_CLK_RATE, QG_ACTUAL_CLK_RATE); + } + + return rc; +} + +int get_rtc_time(unsigned long *rtc_time) +{ + struct rtc_time tm; + struct rtc_device *rtc; + int rc; + + rtc = rtc_class_open(CONFIG_RTC_HCTOSYS_DEVICE); + if (rtc == NULL) { + pr_err("Failed to open rtc device (%s)\n", + CONFIG_RTC_HCTOSYS_DEVICE); + return -EINVAL; + } + + rc = rtc_read_time(rtc, &tm); + if (rc) { + pr_err("Failed to read rtc time (%s) : %d\n", + CONFIG_RTC_HCTOSYS_DEVICE, rc); + goto close_time; + } + + rc = rtc_valid_tm(&tm); + if (rc) { + pr_err("Invalid RTC time (%s): %d\n", + CONFIG_RTC_HCTOSYS_DEVICE, rc); + goto close_time; + } + rtc_tm_to_time(&tm, rtc_time); + +close_time: + rtc_class_close(rtc); + return rc; +} + +int get_fifo_done_time(struct qpnp_qg *chip, bool rt, int *time_ms) +{ + int rc, length = 0; + u32 sample_count = 0, sample_interval = 0, acc_count = 0; + + rc = get_fifo_length(chip, &length, rt ? true : false); + if (rc < 0) + return rc; + + rc = get_sample_count(chip, &sample_count); + if (rc < 0) + return rc; + + rc = get_sample_interval(chip, &sample_interval); + if (rc < 0) + return rc; + + *time_ms = length * sample_count * sample_interval; + + if (rt) { + rc = qg_read(chip, chip->qg_base + QG_ACCUM_CNT_RT_REG, + (u8 *)&acc_count, 1); + if (rc < 0) + return rc; + + *time_ms += ((sample_count - acc_count) * sample_interval); + } + + return 0; +} + +static bool is_usb_available(struct qpnp_qg *chip) +{ + if (chip->usb_psy) + return true; + + chip->usb_psy = power_supply_get_by_name("usb"); + if (!chip->usb_psy) + return false; + + return true; +} + +static bool is_dc_available(struct qpnp_qg *chip) +{ + if (chip->dc_psy) + return true; + + chip->dc_psy = power_supply_get_by_name("dc"); + if (!chip->dc_psy) + return false; + + return true; +} + +bool is_usb_present(struct qpnp_qg *chip) +{ + union power_supply_propval pval = {0, }; + + if (is_usb_available(chip)) + power_supply_get_property(chip->usb_psy, + POWER_SUPPLY_PROP_PRESENT, &pval); + + return pval.intval ? true : false; +} + +bool is_dc_present(struct qpnp_qg *chip) +{ + union power_supply_propval pval = {0, }; + + if (is_dc_available(chip)) + power_supply_get_property(chip->dc_psy, + POWER_SUPPLY_PROP_PRESENT, &pval); + + return pval.intval ? true : false; +} + +bool is_input_present(struct qpnp_qg *chip) +{ + return is_usb_present(chip) || is_dc_present(chip); +} + +static bool is_parallel_available(struct qpnp_qg *chip) +{ + if (chip->parallel_psy) + return true; + + chip->parallel_psy = power_supply_get_by_name("parallel"); + if (!chip->parallel_psy) + return false; + + return true; +} + +bool is_parallel_enabled(struct qpnp_qg *chip) +{ + union power_supply_propval pval = {0, }; + + if (is_parallel_available(chip)) { + power_supply_get_property(chip->parallel_psy, + POWER_SUPPLY_PROP_CHARGING_ENABLED, &pval); + } + + return pval.intval ? true : false; +} + +int qg_write_monotonic_soc(struct qpnp_qg *chip, int msoc) +{ + u8 reg = 0; + int rc; + + reg = (msoc * 255) / 100; + rc = qg_write(chip, chip->qg_base + QG_SOC_MONOTONIC_REG, + ®, 1); + if (rc < 0) + pr_err("Failed to update QG_SOC_MONOTINIC reg rc=%d\n", rc); + + return rc; +} + +int qg_get_battery_temp(struct qpnp_qg *chip, int *temp) +{ + int rc = 0; + + if (chip->battery_missing) { + *temp = 250; + return 0; + } + + rc = iio_read_channel_processed(chip->batt_therm_chan, temp); + if (rc < 0) { + pr_err("Failed reading BAT_TEMP over ADC rc=%d\n", rc); + return rc; + } + pr_debug("batt_temp = %d\n", *temp); + + return 0; +} + +int qg_get_battery_current(struct qpnp_qg *chip, int *ibat_ua) +{ + int rc = 0, last_ibat = 0; + + if (chip->battery_missing) { + *ibat_ua = 0; + return 0; + } + + if (chip->qg_mode == QG_V_MODE) { + *ibat_ua = chip->qg_v_ibat; + return 0; + } + + /* hold data */ + rc = qg_masked_write(chip, chip->qg_base + QG_DATA_CTL2_REG, + BURST_AVG_HOLD_FOR_READ_BIT, + BURST_AVG_HOLD_FOR_READ_BIT); + if (rc < 0) { + pr_err("Failed to hold burst-avg data rc=%d\n", rc); + goto release; + } + + rc = qg_read(chip, chip->qg_base + QG_LAST_BURST_AVG_I_DATA0_REG, + (u8 *)&last_ibat, 2); + if (rc < 0) { + pr_err("Failed to read LAST_BURST_AVG_I reg, rc=%d\n", rc); + goto release; + } + + last_ibat = sign_extend32(last_ibat, 15); + *ibat_ua = qg_iraw_to_ua(chip, last_ibat); + +release: + /* release */ + qg_masked_write(chip, chip->qg_base + QG_DATA_CTL2_REG, + BURST_AVG_HOLD_FOR_READ_BIT, 0); + return rc; +} + +int qg_get_battery_voltage(struct qpnp_qg *chip, int *vbat_uv) +{ + int rc = 0; + u64 last_vbat = 0; + + if (chip->battery_missing) { + *vbat_uv = 3700000; + return 0; + } + + rc = qg_read(chip, chip->qg_base + QG_LAST_ADC_V_DATA0_REG, + (u8 *)&last_vbat, 2); + if (rc < 0) { + pr_err("Failed to read LAST_ADV_V reg, rc=%d\n", rc); + return rc; + } + + *vbat_uv = V_RAW_TO_UV(last_vbat); + + return rc; +} + +int qg_get_vbat_avg(struct qpnp_qg *chip, int *vbat_uv) +{ + int rc = 0; + u64 last_vbat = 0; + + rc = qg_read(chip, chip->qg_base + QG_S2_NORMAL_AVG_V_DATA0_REG, + (u8 *)&last_vbat, 2); + if (rc < 0) { + pr_err("Failed to read S2_NORMAL_AVG_V reg, rc=%d\n", rc); + return rc; + } + + *vbat_uv = V_RAW_TO_UV(last_vbat); + + return 0; +} + +int qg_get_ibat_avg(struct qpnp_qg *chip, int *ibat_ua) +{ + int rc = 0; + int last_ibat = 0; + + rc = qg_read(chip, chip->qg_base + QG_S2_NORMAL_AVG_I_DATA0_REG, + (u8 *)&last_ibat, 2); + if (rc < 0) { + pr_err("Failed to read S2_NORMAL_AVG_I reg, rc=%d\n", rc); + return rc; + } + + if (last_ibat == FIFO_I_RESET_VAL) { + /* First FIFO is not complete, read instantaneous IBAT */ + rc = qg_get_battery_current(chip, ibat_ua); + if (rc < 0) + pr_err("Failed to read inst. IBAT rc=%d\n", rc); + + return rc; + } + + last_ibat = sign_extend32(last_ibat, 15); + *ibat_ua = qg_iraw_to_ua(chip, last_ibat); + + return 0; +} diff --git a/drivers/power/supply/qcom/qg-util.h b/drivers/power/supply/qcom/qg-util.h new file mode 100644 index 000000000000..c72f0e5c503b --- /dev/null +++ b/drivers/power/supply/qcom/qg-util.h @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2018-2020 The Linux Foundation. All rights reserved. + */ + +#ifndef __QG_UTIL_H__ +#define __QG_UTIL_H__ + +int qg_read(struct qpnp_qg *chip, u32 addr, u8 *val, int len); +int qg_write(struct qpnp_qg *chip, u32 addr, u8 *val, int len); +int qg_masked_write(struct qpnp_qg *chip, int addr, u32 mask, u32 val); +int qg_read_raw_data(struct qpnp_qg *chip, int addr, u32 *data); +int get_fifo_length(struct qpnp_qg *chip, u32 *fifo_length, bool rt); +int get_sample_count(struct qpnp_qg *chip, u32 *sample_count); +int get_sample_interval(struct qpnp_qg *chip, u32 *sample_interval); +int get_fifo_done_time(struct qpnp_qg *chip, bool rt, int *time_ms); +int get_rtc_time(unsigned long *rtc_time); +bool is_usb_present(struct qpnp_qg *chip); +bool is_dc_present(struct qpnp_qg *chip); +bool is_input_present(struct qpnp_qg *chip); +bool is_parallel_enabled(struct qpnp_qg *chip); +int qg_write_monotonic_soc(struct qpnp_qg *chip, int msoc); +int qg_get_battery_temp(struct qpnp_qg *chip, int *batt_temp); +int qg_get_battery_current(struct qpnp_qg *chip, int *ibat_ua); +int qg_get_battery_voltage(struct qpnp_qg *chip, int *vbat_uv); +int qg_get_vbat_avg(struct qpnp_qg *chip, int *vbat_uv); +s64 qg_iraw_to_ua(struct qpnp_qg *chip, int iraw); +int qg_get_ibat_avg(struct qpnp_qg *chip, int *ibat_ua); + +#endif diff --git a/drivers/power/supply/qcom/qpnp-qg.c b/drivers/power/supply/qcom/qpnp-qg.c new file mode 100644 index 000000000000..e4aa5f0c7763 --- /dev/null +++ b/drivers/power/supply/qcom/qpnp-qg.c @@ -0,0 +1,4905 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2018-2020 The Linux Foundation. All rights reserved. + */ + +#define pr_fmt(fmt) "QG-K: %s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "fg-alg.h" +#include "qg-sdam.h" +#include "qg-core.h" +#include "qg-reg.h" +#include "qg-util.h" +#include "qg-soc.h" +#include "qg-battery-profile.h" +#include "qg-defs.h" +#include "battery-profile-loader.h" + +static int qg_debug_mask; + +static int qg_esr_mod_count = 30; +static ssize_t esr_mod_count_show(struct device *dev, struct device_attribute + *attr, char *buf) +{ + return scnprintf(buf, PAGE_SIZE, "%d\n", qg_esr_mod_count); +} + +static ssize_t esr_mod_count_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int val; + + if (kstrtos32(buf, 0, &val)) + return -EINVAL; + + qg_esr_mod_count = val; + + return count; +} +static DEVICE_ATTR_RW(esr_mod_count); + +static int qg_esr_count = 3; +static ssize_t esr_count_show(struct device *dev, struct device_attribute + *attr, char *buf) +{ + return scnprintf(buf, PAGE_SIZE, "%d\n", qg_esr_count); +} + +static ssize_t esr_count_store(struct device *dev, struct device_attribute + *attr, const char *buf, size_t count) +{ + int val; + + if (kstrtos32(buf, 0, &val)) + return -EINVAL; + + qg_esr_count = val; + + return count; +} +static DEVICE_ATTR_RW(esr_count); + +static struct attribute *qg_attrs[] = { + &dev_attr_esr_mod_count.attr, + &dev_attr_esr_count.attr, + &dev_attr_soc_interval_ms.attr, + &dev_attr_soc_cold_interval_ms.attr, + &dev_attr_maint_soc_update_ms.attr, + &dev_attr_fvss_delta_soc_interval_ms.attr, + &dev_attr_fvss_vbat_scaling.attr, + &dev_attr_qg_ss_feature.attr, + NULL, +}; +ATTRIBUTE_GROUPS(qg); + +static int qg_process_rt_fifo(struct qpnp_qg *chip); +static int qg_load_battery_profile(struct qpnp_qg *chip); + +static bool is_battery_present(struct qpnp_qg *chip) +{ + bool present = true; + u8 reg = 0; + int rc; + + if (chip->qg_version == QG_LITE) { + rc = qg_read(chip, chip->qg_base + QG_STATUS2_REG, ®, 1); + if (rc < 0) + pr_err("Failed to read battery presence, rc=%d\n", rc); + else + present = !(reg & BATTERY_MISSING_BIT); + } else { + rc = qg_read(chip, chip->qg_base + QG_STATUS1_REG, ®, 1); + if (rc < 0) + pr_err("Failed to read battery presence, rc=%d\n", rc); + else + present = !!(reg & BATTERY_PRESENT_BIT); + } + + return present; +} + +#define DEBUG_BATT_ID_LOW 6000 +#define DEBUG_BATT_ID_HIGH 8500 +static bool is_debug_batt_id(struct qpnp_qg *chip) +{ + if (is_between(DEBUG_BATT_ID_LOW, DEBUG_BATT_ID_HIGH, + chip->batt_id_ohm)) + return true; + + return false; +} + +static int qg_read_ocv(struct qpnp_qg *chip, u32 *ocv_uv, u32 *ocv_raw, u8 type) +{ + int rc, addr; + u64 temp = 0; + char ocv_name[20]; + + switch (type) { + case S3_GOOD_OCV: + addr = QG_S3_GOOD_OCV_V_DATA0_REG; + strlcpy(ocv_name, "S3_GOOD_OCV", 20); + break; + case S7_PON_OCV: + addr = QG_S7_PON_OCV_V_DATA0_REG; + strlcpy(ocv_name, "S7_PON_OCV", 20); + break; + case S3_LAST_OCV: + addr = QG_LAST_S3_SLEEP_V_DATA0_REG; + strlcpy(ocv_name, "S3_LAST_OCV", 20); + break; + case SDAM_PON_OCV: + addr = QG_SDAM_PON_OCV_OFFSET; + strlcpy(ocv_name, "SDAM_PON_OCV", 20); + break; + default: + pr_err("Invalid OCV type %d\n", type); + return -EINVAL; + } + + if (type == SDAM_PON_OCV) { + rc = qg_sdam_read(SDAM_PON_OCV_UV, ocv_raw); + if (rc < 0) { + pr_err("Failed to read SDAM PON OCV rc=%d\n", rc); + return rc; + } + } else { + rc = qg_read(chip, chip->qg_base + addr, (u8 *)ocv_raw, 2); + if (rc < 0) { + pr_err("Failed to read ocv, rc=%d\n", rc); + return rc; + } + } + + temp = *ocv_raw; + *ocv_uv = V_RAW_TO_UV(temp); + + pr_debug("%s: OCV_RAW=%x OCV=%duV\n", ocv_name, *ocv_raw, *ocv_uv); + + return rc; +} + +#define DEFAULT_S3_FIFO_LENGTH 3 +static int qg_update_fifo_length(struct qpnp_qg *chip, u8 length) +{ + int rc; + u8 s3_entry_fifo_length = 0; + + if (!length || length > chip->max_fifo_length) { + pr_err("Invalid FIFO length %d\n", length); + return -EINVAL; + } + + rc = qg_masked_write(chip, chip->qg_base + QG_S2_NORMAL_MEAS_CTL2_REG, + FIFO_LENGTH_MASK, (length - 1) << FIFO_LENGTH_SHIFT); + if (rc < 0) + pr_err("Failed to write S2 FIFO length, rc=%d\n", rc); + + /* update the S3 FIFO length, when S2 length is updated */ + if (length > 3 && !chip->dt.qg_sleep_config) + s3_entry_fifo_length = (chip->dt.s3_entry_fifo_length > 0) ? + chip->dt.s3_entry_fifo_length : DEFAULT_S3_FIFO_LENGTH; + else /* Use S3 length as 1 for any S2 length <= 3 */ + s3_entry_fifo_length = 1; + + rc = qg_masked_write(chip, + chip->qg_base + QG_S3_SLEEP_OCV_IBAT_CTL1_REG, + SLEEP_IBAT_QUALIFIED_LENGTH_MASK, + s3_entry_fifo_length - 1); + if (rc < 0) + pr_err("Failed to write S3-entry fifo-length, rc=%d\n", + rc); + + return rc; +} + +static int qg_master_hold(struct qpnp_qg *chip, bool hold) +{ + int rc; + + /* clear the master */ + rc = qg_masked_write(chip, chip->qg_base + QG_DATA_CTL1_REG, + MASTER_HOLD_OR_CLR_BIT, 0); + if (rc < 0) + return rc; + + if (hold) { + /* 0 -> 1, hold the master */ + rc = qg_masked_write(chip, chip->qg_base + QG_DATA_CTL1_REG, + MASTER_HOLD_OR_CLR_BIT, + MASTER_HOLD_OR_CLR_BIT); + if (rc < 0) + return rc; + } + + qg_dbg(chip, QG_DEBUG_STATUS, "Master hold = %d\n", hold); + + return rc; +} + +static void qg_notify_charger(struct qpnp_qg *chip) +{ + union power_supply_propval prop = {0, }; + int rc; + + if (!chip->batt_psy) + return; + + if (is_debug_batt_id(chip)) { + prop.intval = 1; + power_supply_set_property(chip->batt_psy, + POWER_SUPPLY_PROP_DEBUG_BATTERY, &prop); + return; + } + + if (!chip->profile_loaded) + return; + + prop.intval = chip->bp.float_volt_uv; + rc = power_supply_set_property(chip->batt_psy, + POWER_SUPPLY_PROP_VOLTAGE_MAX, &prop); + if (rc < 0) { + pr_err("Failed to set voltage_max property on batt_psy, rc=%d\n", + rc); + return; + } + + prop.intval = chip->bp.fastchg_curr_ma * 1000; + rc = power_supply_set_property(chip->batt_psy, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, &prop); + if (rc < 0) { + pr_err("Failed to set constant_charge_current_max property on batt_psy, rc=%d\n", + rc); + return; + } + + pr_debug("Notified charger on float voltage and FCC\n"); + + rc = power_supply_get_property(chip->batt_psy, + POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT, &prop); + if (rc < 0) { + pr_err("Failed to get charge term current, rc=%d\n", rc); + return; + } + chip->chg_iterm_ma = prop.intval; +} + +static bool is_batt_available(struct qpnp_qg *chip) +{ + if (chip->batt_psy) + return true; + + chip->batt_psy = power_supply_get_by_name("battery"); + if (!chip->batt_psy) + return false; + + /* batt_psy is initialized, set the fcc and fv */ + qg_notify_charger(chip); + + return true; +} + +static int qg_store_soc_params(struct qpnp_qg *chip) +{ + int rc, batt_temp = 0, i; + unsigned long rtc_sec = 0; + + rc = get_rtc_time(&rtc_sec); + if (rc < 0) + pr_err("Failed to get RTC time, rc=%d\n", rc); + else + chip->sdam_data[SDAM_TIME_SEC] = rtc_sec; + + rc = qg_get_battery_temp(chip, &batt_temp); + if (rc < 0) + pr_err("Failed to get battery-temp, rc = %d\n", rc); + else + chip->sdam_data[SDAM_TEMP] = (u32)batt_temp; + + for (i = 0; i <= SDAM_TIME_SEC; i++) { + rc |= qg_sdam_write(i, chip->sdam_data[i]); + qg_dbg(chip, QG_DEBUG_STATUS, "SDAM write param %d value=%d\n", + i, chip->sdam_data[i]); + } + + return rc; +} + +#define MAX_FIFO_CNT_FOR_ESR 50 +static int qg_config_s2_state(struct qpnp_qg *chip, + enum s2_state requested_state, bool state_enable, + bool process_fifo) +{ + int rc, acc_interval, acc_length; + u8 fifo_length, reg = 0, state = S2_DEFAULT; + + if ((chip->s2_state_mask & requested_state) && state_enable) + return 0; /* No change in state */ + + if (!(chip->s2_state_mask & requested_state) && !state_enable) + return 0; /* No change in state */ + + if (state_enable) + chip->s2_state_mask |= requested_state; + else + chip->s2_state_mask &= ~requested_state; + + /* define the priority of the states */ + if (chip->s2_state_mask & S2_FAST_CHARGING) + state = S2_FAST_CHARGING; + else if (chip->s2_state_mask & S2_LOW_VBAT) + state = S2_LOW_VBAT; + else if (chip->s2_state_mask & S2_SLEEP) + state = S2_SLEEP; + else + state = S2_DEFAULT; + + if (state == chip->s2_state) + return 0; + + switch (state) { + case S2_FAST_CHARGING: + fifo_length = chip->dt.fast_chg_s2_fifo_length; + acc_interval = chip->dt.s2_acc_intvl_ms; + acc_length = chip->dt.s2_acc_length; + break; + case S2_LOW_VBAT: + fifo_length = chip->dt.s2_vbat_low_fifo_length; + acc_interval = chip->dt.s2_acc_intvl_ms; + acc_length = chip->dt.s2_acc_length; + break; + case S2_SLEEP: + fifo_length = chip->dt.sleep_s2_fifo_length; + acc_interval = chip->dt.sleep_s2_acc_intvl_ms; + acc_length = chip->dt.sleep_s2_acc_length; + break; + case S2_DEFAULT: + fifo_length = chip->dt.s2_fifo_length; + acc_interval = chip->dt.s2_acc_intvl_ms; + acc_length = chip->dt.s2_acc_length; + break; + default: + pr_err("Invalid S2 state %d\n", state); + return -EINVAL; + } + + if (fifo_length) + qg_esr_mod_count = MAX_FIFO_CNT_FOR_ESR / fifo_length; + + rc = qg_master_hold(chip, true); + if (rc < 0) { + pr_err("Failed to hold master, rc=%d\n", rc); + return rc; + } + + if (process_fifo) { + rc = qg_process_rt_fifo(chip); + if (rc < 0) { + pr_err("Failed to process FIFO real-time, rc=%d\n", rc); + goto done; + } + } + + rc = qg_update_fifo_length(chip, fifo_length); + if (rc < 0) { + pr_err("Failed to update S2 fifo-length, rc=%d\n", rc); + goto done; + } + + reg = acc_interval / 10; + rc = qg_write(chip, chip->qg_base + QG_S2_NORMAL_MEAS_CTL3_REG, + ®, 1); + if (rc < 0) { + pr_err("Failed to update S2 acc intrvl, rc=%d\n", rc); + goto done; + } + + reg = ilog2(acc_length) - 1; + rc = qg_masked_write(chip, chip->qg_base + QG_S2_NORMAL_MEAS_CTL2_REG, + NUM_OF_ACCUM_MASK, reg); + if (rc < 0) { + pr_err("Failed to update S2 ACC length, rc=%d\n", rc); + goto done; + } + + chip->s2_state = state; + + qg_dbg(chip, QG_DEBUG_STATUS, "S2 New state=%x fifo_length=%d interval=%d acc_length=%d\n", + state, fifo_length, acc_interval, acc_length); + +done: + qg_master_hold(chip, false); + /* FIFO restarted */ + chip->last_fifo_update_time = ktime_get_boottime(); + return rc; +} + +static int qg_process_fifo(struct qpnp_qg *chip, u32 fifo_length) +{ + int rc = 0, i, j = 0, temp; + u8 v_fifo[MAX_FIFO_LENGTH * 2], i_fifo[MAX_FIFO_LENGTH * 2]; + u32 sample_interval = 0, sample_count = 0, fifo_v = 0, fifo_i = 0; + unsigned long rtc_sec = 0; + bool qg_v_mode = (chip->qg_mode == QG_V_MODE); + + rc = get_rtc_time(&rtc_sec); + if (rc < 0) + pr_err("Failed to get RTC time, rc=%d\n", rc); + + chip->kdata.fifo_time = (u32)rtc_sec; + + if (!fifo_length) { + pr_debug("No FIFO data\n"); + return 0; + } + + qg_dbg(chip, QG_DEBUG_FIFO, "FIFO length=%d\n", fifo_length); + + rc = get_sample_interval(chip, &sample_interval); + if (rc < 0) { + pr_err("Failed to get FIFO sample interval, rc=%d\n", rc); + return rc; + } + + rc = get_sample_count(chip, &sample_count); + if (rc < 0) { + pr_err("Failed to get FIFO sample count, rc=%d\n", rc); + return rc; + } + + /* + * If there is pending data from suspend, append the new FIFO + * data to it. Only do this if we can accomadate 8 FIFOs + */ + if (chip->suspend_data && + (chip->kdata.fifo_length < (MAX_FIFO_LENGTH / 2))) { + j = chip->kdata.fifo_length; /* append the data */ + chip->suspend_data = false; + qg_dbg(chip, QG_DEBUG_FIFO, + "Pending suspend-data FIFO length=%d\n", j); + } else { + /* clear any old pending data */ + chip->kdata.fifo_length = 0; + } + + for (i = 0; i < fifo_length * 2; i = i + 2, j++) { + rc = qg_read(chip, chip->qg_base + QG_V_FIFO0_DATA0_REG + i, + &v_fifo[i], 2); + if (rc < 0) { + pr_err("Failed to read QG_V_FIFO, rc=%d\n", rc); + return rc; + } + rc = qg_read(chip, chip->qg_base + QG_I_FIFO0_DATA0_REG + i, + &i_fifo[i], 2); + if (rc < 0) { + pr_err("Failed to read QG_I_FIFO, rc=%d\n", rc); + return rc; + } + + fifo_v = v_fifo[i] | (v_fifo[i + 1] << 8); + fifo_i = i_fifo[i] | (i_fifo[i + 1] << 8); + + if (fifo_v == FIFO_V_RESET_VAL || + (fifo_i == FIFO_I_RESET_VAL && !qg_v_mode)) { + pr_err("Invalid FIFO data V_RAW=%x I_RAW=%x - FIFO rejected\n", + fifo_v, fifo_i); + return -EINVAL; + } + + temp = sign_extend32(fifo_i, 15); + + chip->kdata.fifo[j].v = V_RAW_TO_UV(fifo_v); + chip->kdata.fifo[j].i = + qg_v_mode ? 0 : qg_iraw_to_ua(chip, temp); + chip->kdata.fifo[j].interval = sample_interval; + chip->kdata.fifo[j].count = sample_count; + + chip->last_fifo_v_uv = chip->kdata.fifo[j].v; + chip->last_fifo_i_ua = chip->kdata.fifo[j].i; + + qg_dbg(chip, QG_DEBUG_FIFO, "FIFO %d raw_v=%d uV=%d raw_i=%d uA=%d interval=%d count=%d\n", + j, fifo_v, + chip->kdata.fifo[j].v, + qg_v_mode ? 0 : fifo_i, + (int)chip->kdata.fifo[j].i, + chip->kdata.fifo[j].interval, + chip->kdata.fifo[j].count); + } + + chip->kdata.fifo_length += fifo_length; + chip->kdata.seq_no = chip->seq_no++ % U32_MAX; + + return rc; +} + +static int qg_process_accumulator(struct qpnp_qg *chip) +{ + int rc, sample_interval = 0; + u8 count, index = chip->kdata.fifo_length; + u64 acc_v = 0, acc_i = 0; + s64 temp = 0; + bool qg_v_mode = (chip->qg_mode == QG_V_MODE); + + rc = qg_read(chip, chip->qg_base + QG_ACCUM_CNT_RT_REG, + &count, 1); + if (rc < 0) { + pr_err("Failed to read ACC count, rc=%d\n", rc); + return rc; + } + + if (!count || count < 10) { /* Ignore small accumulator data */ + pr_debug("No ACCUMULATOR data!\n"); + return 0; + } + + rc = get_sample_interval(chip, &sample_interval); + if (rc < 0) { + pr_err("Failed to get ACC sample interval, rc=%d\n", rc); + return 0; + } + + rc = qg_read(chip, chip->qg_base + QG_V_ACCUM_DATA0_RT_REG, + (u8 *)&acc_v, 3); + if (rc < 0) { + pr_err("Failed to read ACC RT V data, rc=%d\n", rc); + return rc; + } + + rc = qg_read(chip, chip->qg_base + QG_I_ACCUM_DATA0_RT_REG, + (u8 *)&acc_i, 3); + if (rc < 0) { + pr_err("Failed to read ACC RT I data, rc=%d\n", rc); + return rc; + } + + temp = sign_extend64(acc_i, 23); + + chip->kdata.fifo[index].v = V_RAW_TO_UV(div_u64(acc_v, count)); + chip->kdata.fifo[index].i = qg_v_mode ? + 0 : qg_iraw_to_ua(chip, div_s64(temp, count)); + chip->kdata.fifo[index].interval = sample_interval; + chip->kdata.fifo[index].count = count; + chip->kdata.fifo_length++; + if (chip->kdata.fifo_length == MAX_FIFO_LENGTH) + chip->kdata.fifo_length = MAX_FIFO_LENGTH - 1; + + chip->last_fifo_v_uv = chip->kdata.fifo[index].v; + chip->last_fifo_i_ua = chip->kdata.fifo[index].i; + + if (chip->kdata.fifo_length == 1) /* Only accumulator data */ + chip->kdata.seq_no = chip->seq_no++ % U32_MAX; + + qg_dbg(chip, QG_DEBUG_FIFO, "ACC v_avg=%duV i_avg=%duA interval=%d count=%d\n", + chip->kdata.fifo[index].v, + (int)chip->kdata.fifo[index].i, + chip->kdata.fifo[index].interval, + chip->kdata.fifo[index].count); + + return rc; +} + +static int qg_process_rt_fifo(struct qpnp_qg *chip) +{ + int rc; + u32 fifo_length = 0; + + /* Get the real-time FIFO length */ + rc = get_fifo_length(chip, &fifo_length, true); + if (rc < 0) { + pr_err("Failed to read RT FIFO length, rc=%d\n", rc); + return rc; + } + + rc = qg_process_fifo(chip, fifo_length); + if (rc < 0) { + pr_err("Failed to process FIFO data, rc=%d\n", rc); + return rc; + } + + rc = qg_process_accumulator(chip); + if (rc < 0) { + pr_err("Failed to process ACC data, rc=%d\n", rc); + return rc; + } + + return rc; +} + +#define MIN_FIFO_FULL_TIME_MS 12000 +static int process_rt_fifo_data(struct qpnp_qg *chip, bool update_smb) +{ + int rc = 0; + ktime_t now = ktime_get_boottime(); + s64 time_delta; + + /* + * Reject the FIFO read event if there are back-to-back requests + * This is done to gaurantee that there is always a minimum FIFO + * data to be processed, ignore this if vbat_low is set. + */ + time_delta = ktime_ms_delta(now, chip->last_user_update_time); + + qg_dbg(chip, QG_DEBUG_FIFO, "time_delta=%lld ms update_smb=%d\n", + time_delta, update_smb); + + if (time_delta > MIN_FIFO_FULL_TIME_MS || update_smb) { + rc = qg_master_hold(chip, true); + if (rc < 0) { + pr_err("Failed to hold master, rc=%d\n", rc); + goto done; + } + + rc = qg_process_rt_fifo(chip); + if (rc < 0) { + pr_err("Failed to process FIFO real-time, rc=%d\n", rc); + goto done; + } + + if (update_smb) { + rc = qg_masked_write(chip, chip->qg_base + + QG_MODE_CTL1_REG, PARALLEL_IBAT_SENSE_EN_BIT, + chip->parallel_enabled ? + PARALLEL_IBAT_SENSE_EN_BIT : 0); + if (rc < 0) { + pr_err("Failed to update SMB_EN, rc=%d\n", rc); + goto done; + } + qg_dbg(chip, QG_DEBUG_STATUS, "Parallel SENSE %d\n", + chip->parallel_enabled); + } + + rc = qg_master_hold(chip, false); + if (rc < 0) { + pr_err("Failed to release master, rc=%d\n", rc); + goto done; + } + /* FIFOs restarted */ + chip->last_fifo_update_time = ktime_get_boottime(); + + /* signal the read thread */ + chip->data_ready = true; + wake_up_interruptible(&chip->qg_wait_q); + chip->last_user_update_time = now; + + /* vote to stay awake until userspace reads data */ + vote(chip->awake_votable, FIFO_RT_DONE_VOTER, true, 0); + } else { + qg_dbg(chip, QG_DEBUG_FIFO, "FIFO processing too early time_delta=%lld\n", + time_delta); + } +done: + qg_master_hold(chip, false); + return rc; +} + +#define VBAT_LOW_HYST_UV 50000 /* 50mV */ +static int qg_vbat_low_wa(struct qpnp_qg *chip) +{ + int rc, i, temp = 0; + u32 vbat_low_uv = 0; + + if (chip->wa_flags & QG_VBAT_LOW_WA) { + rc = qg_get_battery_temp(chip, &temp); + if (rc < 0) { + pr_err("Failed to read batt_temp rc=%d\n", rc); + temp = 250; + } + + vbat_low_uv = 1000 * ((temp < chip->dt.cold_temp_threshold) ? + chip->dt.vbatt_low_cold_mv : + chip->dt.vbatt_low_mv); + + for (i = 0; i < chip->kdata.fifo_length; i++) { + if ((chip->kdata.fifo[i].v > (vbat_low_uv + + VBAT_LOW_HYST_UV)) && chip->vbat_low) { + chip->vbat_low = false; + pr_info("Exit VBAT_LOW vbat_avg=%duV vbat_low=%duV\n", + chip->kdata.fifo[i].v, vbat_low_uv); + break; + } else if ((chip->kdata.fifo[i].v < vbat_low_uv) && + !chip->vbat_low) { + chip->vbat_low = true; + pr_info("Enter VBAT_LOW vbat_avg=%duV vbat_low=%duV\n", + chip->kdata.fifo[i].v, vbat_low_uv); + break; + } + } + } + + rc = qg_config_s2_state(chip, S2_LOW_VBAT, + chip->vbat_low ? true : false, false); + if (rc < 0) + pr_err("Failed to configure for VBAT_LOW rc=%d\n", rc); + + return rc; +} + +static int qg_vbat_thresholds_config(struct qpnp_qg *chip) +{ + int rc, temp = 0, vbat_mv; + u8 reg; + + rc = qg_get_battery_temp(chip, &temp); + if (rc < 0) { + pr_err("Failed to read batt_temp rc=%d\n", rc); + return rc; + } + + vbat_mv = (temp < chip->dt.cold_temp_threshold) ? + chip->dt.vbatt_empty_cold_mv : + chip->dt.vbatt_empty_mv; + + rc = qg_read(chip, chip->qg_base + QG_VBAT_EMPTY_THRESHOLD_REG, + ®, 1); + if (rc < 0) { + pr_err("Failed to read vbat-empty, rc=%d\n", rc); + return rc; + } + + if (vbat_mv == (reg * 50)) /* No change */ + goto config_vbat_low; + + reg = vbat_mv / 50; + rc = qg_write(chip, chip->qg_base + QG_VBAT_EMPTY_THRESHOLD_REG, + ®, 1); + if (rc < 0) { + pr_err("Failed to write vbat-empty, rc=%d\n", rc); + return rc; + } + + qg_dbg(chip, QG_DEBUG_STATUS, + "VBAT EMPTY threshold updated to %dmV temp=%d\n", + vbat_mv, temp); + +config_vbat_low: + if (chip->qg_version == QG_LITE) + return 0; + + vbat_mv = (temp < chip->dt.cold_temp_threshold) ? + chip->dt.vbatt_low_cold_mv : + chip->dt.vbatt_low_mv; + + rc = qg_read(chip, chip->qg_base + QG_VBAT_LOW_THRESHOLD_REG, + ®, 1); + if (rc < 0) { + pr_err("Failed to read vbat-low, rc=%d\n", rc); + return rc; + } + + if (vbat_mv == (reg * 50)) /* No change */ + return 0; + + reg = vbat_mv / 50; + rc = qg_write(chip, chip->qg_base + QG_VBAT_LOW_THRESHOLD_REG, + ®, 1); + if (rc < 0) { + pr_err("Failed to write vbat-low, rc=%d\n", rc); + return rc; + } + + qg_dbg(chip, QG_DEBUG_STATUS, + "VBAT LOW threshold updated to %dmV temp=%d\n", + vbat_mv, temp); + + return rc; +} + +static int qg_fast_charge_config(struct qpnp_qg *chip) +{ + int rc = 0; + + if (!chip->dt.qg_fast_chg_cfg) + return 0; + + rc = qg_config_s2_state(chip, S2_FAST_CHARGING, + (chip->charge_status == POWER_SUPPLY_STATUS_CHARGING) + ? true : false, false); + if (rc < 0) + pr_err("Failed to exit S2_SLEEP rc=%d\n", rc); + + return rc; +} + +static void qg_retrieve_esr_params(struct qpnp_qg *chip) +{ + u32 data = 0; + int rc; + + rc = qg_sdam_read(SDAM_ESR_CHARGE_DELTA, &data); + if (!rc && data) { + chip->kdata.param[QG_ESR_CHARGE_DELTA].data = data; + chip->kdata.param[QG_ESR_CHARGE_DELTA].valid = true; + qg_dbg(chip, QG_DEBUG_ESR, + "ESR_CHARGE_DELTA SDAM=%d\n", data); + } else if (rc < 0) { + pr_err("Failed to read ESR_CHARGE_DELTA rc=%d\n", rc); + } + + rc = qg_sdam_read(SDAM_ESR_DISCHARGE_DELTA, &data); + if (!rc && data) { + chip->kdata.param[QG_ESR_DISCHARGE_DELTA].data = data; + chip->kdata.param[QG_ESR_DISCHARGE_DELTA].valid = true; + qg_dbg(chip, QG_DEBUG_ESR, + "ESR_DISCHARGE_DELTA SDAM=%d\n", data); + } else if (rc < 0) { + pr_err("Failed to read ESR_DISCHARGE_DELTA rc=%d\n", rc); + } + + rc = qg_sdam_read(SDAM_ESR_CHARGE_SF, &data); + if (!rc && data) { + data = CAP(QG_ESR_SF_MIN, QG_ESR_SF_MAX, data); + chip->kdata.param[QG_ESR_CHARGE_SF].data = data; + chip->kdata.param[QG_ESR_CHARGE_SF].valid = true; + qg_dbg(chip, QG_DEBUG_ESR, + "ESR_CHARGE_SF SDAM=%d\n", data); + } else if (rc < 0) { + pr_err("Failed to read ESR_CHARGE_SF rc=%d\n", rc); + } + + rc = qg_sdam_read(SDAM_ESR_DISCHARGE_SF, &data); + if (!rc && data) { + data = CAP(QG_ESR_SF_MIN, QG_ESR_SF_MAX, data); + chip->kdata.param[QG_ESR_DISCHARGE_SF].data = data; + chip->kdata.param[QG_ESR_DISCHARGE_SF].valid = true; + qg_dbg(chip, QG_DEBUG_ESR, + "ESR_DISCHARGE_SF SDAM=%d\n", data); + } else if (rc < 0) { + pr_err("Failed to read ESR_DISCHARGE_SF rc=%d\n", rc); + } +} + +static void qg_store_esr_params(struct qpnp_qg *chip) +{ + unsigned int esr; + + if (chip->udata.param[QG_ESR_CHARGE_DELTA].valid) { + esr = chip->udata.param[QG_ESR_CHARGE_DELTA].data; + qg_sdam_write(SDAM_ESR_CHARGE_DELTA, esr); + qg_dbg(chip, QG_DEBUG_ESR, + "SDAM store ESR_CHARGE_DELTA=%d\n", esr); + } + + if (chip->udata.param[QG_ESR_DISCHARGE_DELTA].valid) { + esr = chip->udata.param[QG_ESR_DISCHARGE_DELTA].data; + qg_sdam_write(SDAM_ESR_DISCHARGE_DELTA, esr); + qg_dbg(chip, QG_DEBUG_ESR, + "SDAM store ESR_DISCHARGE_DELTA=%d\n", esr); + } + + if (chip->udata.param[QG_ESR_CHARGE_SF].valid) { + esr = chip->udata.param[QG_ESR_CHARGE_SF].data; + qg_sdam_write(SDAM_ESR_CHARGE_SF, esr); + qg_dbg(chip, QG_DEBUG_ESR, + "SDAM store ESR_CHARGE_SF=%d\n", esr); + } + + if (chip->udata.param[QG_ESR_DISCHARGE_SF].valid) { + esr = chip->udata.param[QG_ESR_DISCHARGE_SF].data; + qg_sdam_write(SDAM_ESR_DISCHARGE_SF, esr); + qg_dbg(chip, QG_DEBUG_ESR, + "SDAM store ESR_DISCHARGE_SF=%d\n", esr); + } +} + +#define MAX_ESR_RETRY_COUNT 10 +#define ESR_SD_PERCENT 10 +static int qg_process_esr_data(struct qpnp_qg *chip) +{ + int i; + int pre_i, post_i, pre_v, post_v, first_pre_i = 0; + int diff_v, diff_i, esr_avg = 0, count = 0; + + for (i = 0; i < qg_esr_count; i++) { + if (!chip->esr_data[i].valid) + continue; + + pre_i = chip->esr_data[i].pre_esr_i; + pre_v = chip->esr_data[i].pre_esr_v; + post_i = chip->esr_data[i].post_esr_i; + post_v = chip->esr_data[i].post_esr_v; + + /* + * Check if any of the pre/post readings have changed + * signs by comparing it with the first valid + * pre_i value. + */ + if (!first_pre_i) + first_pre_i = pre_i; + + if ((first_pre_i < 0 && pre_i > 0) || + (first_pre_i > 0 && post_i < 0) || + (first_pre_i < 0 && post_i > 0)) { + qg_dbg(chip, QG_DEBUG_ESR, + "ESR-sign mismatch %d reject all data\n", i); + esr_avg = count = 0; + break; + } + + /* calculate ESR */ + diff_v = abs(post_v - pre_v); + diff_i = abs(post_i - pre_i); + + if (!diff_v || !diff_i || + (diff_i < chip->dt.esr_qual_i_ua) || + (diff_v < chip->dt.esr_qual_v_uv)) { + qg_dbg(chip, QG_DEBUG_ESR, + "ESR (%d) V/I %duA %duV fails qualification\n", + i, diff_i, diff_v); + chip->esr_data[i].valid = false; + continue; + } + + chip->esr_data[i].esr = + DIV_ROUND_CLOSEST(diff_v * 1000, diff_i); + qg_dbg(chip, QG_DEBUG_ESR, + "ESR qualified: i=%d pre_i=%d pre_v=%d post_i=%d post_v=%d esr_diff_v=%d esr_diff_i=%d esr=%d\n", + i, pre_i, pre_v, post_i, post_v, + diff_v, diff_i, chip->esr_data[i].esr); + + esr_avg += chip->esr_data[i].esr; + count++; + } + + if (!count) { + qg_dbg(chip, QG_DEBUG_ESR, + "No ESR samples qualified, ESR not found\n"); + chip->esr_avg = 0; + return 0; + } + + esr_avg /= count; + qg_dbg(chip, QG_DEBUG_ESR, + "ESR all sample average=%d count=%d apply_SD=%d\n", + esr_avg, count, (esr_avg * ESR_SD_PERCENT) / 100); + + /* + * Reject ESR samples which do not fall in + * 10% the standard-deviation + */ + count = 0; + for (i = 0; i < qg_esr_count; i++) { + if (!chip->esr_data[i].valid) + continue; + + if ((abs(chip->esr_data[i].esr - esr_avg) <= + (esr_avg * ESR_SD_PERCENT) / 100)) { + /* valid ESR */ + chip->esr_avg += chip->esr_data[i].esr; + count++; + qg_dbg(chip, QG_DEBUG_ESR, + "Valid ESR after SD (%d) %d mOhm\n", + i, chip->esr_data[i].esr); + } else { + qg_dbg(chip, QG_DEBUG_ESR, + "ESR (%d) %d falls-out of SD(%d)\n", + i, chip->esr_data[i].esr, ESR_SD_PERCENT); + } + } + + if (count >= QG_MIN_ESR_COUNT) { + chip->esr_avg /= count; + qg_dbg(chip, QG_DEBUG_ESR, "Average estimated ESR %d mOhm\n", + chip->esr_avg); + } else { + qg_dbg(chip, QG_DEBUG_ESR, + "Not enough ESR samples, ESR not found\n"); + chip->esr_avg = 0; + } + + return 0; +} + +static int qg_esr_estimate(struct qpnp_qg *chip) +{ + int rc, i, ibat = 0; + u8 esr_done_count, reg0 = 0, reg1 = 0; + bool is_charging = false; + + if (chip->dt.esr_disable) + return 0; + + /* + * Charge - enable ESR estimation if IBAT > MIN_IBAT. + * Discharge - enable ESR estimation only if enabled via DT. + */ + rc = qg_get_battery_current(chip, &ibat); + if (rc < 0) + return rc; + if (chip->charge_status == POWER_SUPPLY_STATUS_CHARGING && + ibat > chip->dt.esr_min_ibat_ua) { + qg_dbg(chip, QG_DEBUG_ESR, + "Skip CHG ESR, Fails IBAT ibat(%d) min_ibat(%d)\n", + ibat, chip->dt.esr_min_ibat_ua); + return 0; + } + + if (chip->charge_status != POWER_SUPPLY_STATUS_CHARGING && + !chip->dt.esr_discharge_enable) + return 0; + + if (chip->batt_soc != INT_MIN && (chip->batt_soc < + chip->dt.esr_disable_soc)) { + qg_dbg(chip, QG_DEBUG_ESR, + "Skip ESR, batt-soc below %d\n", + chip->dt.esr_disable_soc); + return 0; + } + + qg_dbg(chip, QG_DEBUG_ESR, "FIFO done count=%d ESR mod count=%d\n", + chip->fifo_done_count, qg_esr_mod_count); + + if ((chip->fifo_done_count % qg_esr_mod_count) != 0) + return 0; + + if (qg_esr_count > QG_MAX_ESR_COUNT) + qg_esr_count = QG_MAX_ESR_COUNT; + + if (qg_esr_count < QG_MIN_ESR_COUNT) + qg_esr_count = QG_MIN_ESR_COUNT; + + /* clear all data */ + chip->esr_avg = 0; + memset(&chip->esr_data, 0, sizeof(chip->esr_data)); + + rc = qg_master_hold(chip, true); + if (rc < 0) { + pr_err("Failed to hold master, rc=%d\n", rc); + goto done; + } + + for (i = 0; i < qg_esr_count; i++) { + /* Fire ESR measurement */ + rc = qg_masked_write(chip, + chip->qg_base + QG_ESR_MEAS_TRIG_REG, + HW_ESR_MEAS_START_BIT, HW_ESR_MEAS_START_BIT); + if (rc < 0) { + pr_err("Failed to start ESR rc=%d\n", rc); + continue; + } + + esr_done_count = reg0 = reg1 = 0; + do { + /* delay for ESR processing to complete */ + msleep(50); + + esr_done_count++; + + rc = qg_read(chip, + chip->qg_base + QG_STATUS1_REG, ®0, 1); + if (rc < 0) + continue; + + rc = qg_read(chip, + chip->qg_base + QG_STATUS4_REG, ®1, 1); + if (rc < 0) + continue; + + /* check ESR-done status */ + if (!(reg1 & ESR_MEAS_IN_PROGRESS_BIT) && + (reg0 & ESR_MEAS_DONE_BIT)) { + qg_dbg(chip, QG_DEBUG_ESR, + "ESR measurement done %d count %d\n", + i, esr_done_count); + break; + } + } while (esr_done_count < MAX_ESR_RETRY_COUNT); + + if (esr_done_count == MAX_ESR_RETRY_COUNT) { + pr_err("Failed to get ESR done for %d iteration\n", i); + continue; + } else { + /* found a valid ESR, read pre-post data */ + rc = qg_read_raw_data(chip, QG_PRE_ESR_V_DATA0_REG, + &chip->esr_data[i].pre_esr_v); + if (rc < 0) + goto done; + + rc = qg_read_raw_data(chip, QG_PRE_ESR_I_DATA0_REG, + &chip->esr_data[i].pre_esr_i); + if (rc < 0) + goto done; + + rc = qg_read_raw_data(chip, QG_POST_ESR_V_DATA0_REG, + &chip->esr_data[i].post_esr_v); + if (rc < 0) + goto done; + + rc = qg_read_raw_data(chip, QG_POST_ESR_I_DATA0_REG, + &chip->esr_data[i].post_esr_i); + if (rc < 0) + goto done; + + chip->esr_data[i].pre_esr_v = + V_RAW_TO_UV(chip->esr_data[i].pre_esr_v); + ibat = sign_extend32(chip->esr_data[i].pre_esr_i, 15); + chip->esr_data[i].pre_esr_i = qg_iraw_to_ua(chip, ibat); + chip->esr_data[i].post_esr_v = + V_RAW_TO_UV(chip->esr_data[i].post_esr_v); + ibat = sign_extend32(chip->esr_data[i].post_esr_i, 15); + chip->esr_data[i].post_esr_i = + qg_iraw_to_ua(chip, ibat); + + chip->esr_data[i].valid = true; + + if ((int)chip->esr_data[i].pre_esr_i < 0) + is_charging = true; + + qg_dbg(chip, QG_DEBUG_ESR, + "ESR values for %d iteration pre_v=%d pre_i=%d post_v=%d post_i=%d\n", + i, chip->esr_data[i].pre_esr_v, + (int)chip->esr_data[i].pre_esr_i, + chip->esr_data[i].post_esr_v, + (int)chip->esr_data[i].post_esr_i); + } + /* delay before the next ESR measurement */ + msleep(200); + } + + rc = qg_process_esr_data(chip); + if (rc < 0) + pr_err("Failed to process ESR data rc=%d\n", rc); + + rc = qg_master_hold(chip, false); + if (rc < 0) { + pr_err("Failed to release master, rc=%d\n", rc); + goto done; + } + /* FIFOs restarted */ + chip->last_fifo_update_time = ktime_get_boottime(); + + if (chip->esr_avg) { + chip->kdata.param[QG_ESR].data = chip->esr_avg; + chip->kdata.param[QG_ESR].valid = true; + qg_dbg(chip, QG_DEBUG_ESR, "ESR_SW=%d during %s\n", + chip->esr_avg, is_charging ? "CHARGE" : "DISCHARGE"); + qg_retrieve_esr_params(chip); + chip->esr_actual = chip->esr_avg; + } + + return 0; +done: + qg_master_hold(chip, false); + return rc; +} + +static void process_udata_work(struct work_struct *work) +{ + struct qpnp_qg *chip = container_of(work, + struct qpnp_qg, udata_work); + int rc; + + if (chip->udata.param[QG_CC_SOC].valid) + chip->cc_soc = chip->udata.param[QG_CC_SOC].data; + + if (chip->udata.param[QG_BATT_SOC].valid) + chip->batt_soc = chip->udata.param[QG_BATT_SOC].data; + + if (chip->udata.param[QG_FULL_SOC].valid) + chip->full_soc = chip->udata.param[QG_FULL_SOC].data; + + if (chip->udata.param[QG_V_IBAT].valid) + chip->qg_v_ibat = chip->udata.param[QG_V_IBAT].data; + + if (chip->udata.param[QG_SOC].valid || + chip->udata.param[QG_SYS_SOC].valid) { + + qg_dbg(chip, QG_DEBUG_SOC, "udata update: QG_SOC=%d QG_SYS_SOC=%d last_catchup_soc=%d\n", + chip->udata.param[QG_SOC].valid ? + chip->udata.param[QG_SOC].data : -EINVAL, + chip->udata.param[QG_SYS_SOC].valid ? + chip->udata.param[QG_SYS_SOC].data : -EINVAL, + chip->catch_up_soc); + + if (chip->udata.param[QG_SYS_SOC].valid) { + chip->sys_soc = chip->udata.param[QG_SYS_SOC].data; + chip->catch_up_soc = qg_adjust_sys_soc(chip); + } else { + chip->catch_up_soc = chip->udata.param[QG_SOC].data; + } + + qg_scale_soc(chip, chip->force_soc); + chip->force_soc = false; + + /* update parameters to SDAM */ + chip->sdam_data[SDAM_SOC] = chip->msoc; + chip->sdam_data[SDAM_OCV_UV] = + chip->udata.param[QG_OCV_UV].data; + chip->sdam_data[SDAM_RBAT_MOHM] = + chip->udata.param[QG_RBAT_MOHM].data; + chip->sdam_data[SDAM_VALID] = 1; + + rc = qg_store_soc_params(chip); + if (rc < 0) + pr_err("Failed to update SDAM params, rc=%d\n", rc); + } + + if (chip->udata.param[QG_ESR].valid) + chip->esr_last = chip->udata.param[QG_ESR].data; + + if (chip->esr_actual != -EINVAL && chip->udata.param[QG_ESR].valid) { + chip->esr_nominal = chip->udata.param[QG_ESR].data; + if (chip->qg_psy) + power_supply_changed(chip->qg_psy); + } + + if (!chip->dt.esr_disable) + qg_store_esr_params(chip); + + qg_dbg(chip, QG_DEBUG_STATUS, "udata update: batt_soc=%d cc_soc=%d full_soc=%d qg_esr=%d\n", + (chip->batt_soc != INT_MIN) ? chip->batt_soc : -EINVAL, + (chip->cc_soc != INT_MIN) ? chip->cc_soc : -EINVAL, + chip->full_soc, chip->esr_last); + vote(chip->awake_votable, UDATA_READY_VOTER, false, 0); +} + +#define MAX_FIFO_DELTA_PERCENT 10 +static irqreturn_t qg_fifo_update_done_handler(int irq, void *data) +{ + ktime_t now = ktime_get_boottime(); + int rc, hw_delta_ms = 0, margin_ms = 0; + u32 fifo_length = 0; + s64 time_delta_ms = 0; + struct qpnp_qg *chip = data; + + time_delta_ms = ktime_ms_delta(now, chip->last_fifo_update_time); + chip->last_fifo_update_time = now; + + qg_dbg(chip, QG_DEBUG_IRQ, "IRQ triggered\n"); + mutex_lock(&chip->data_lock); + + rc = get_fifo_length(chip, &fifo_length, false); + if (rc < 0) { + pr_err("Failed to get FIFO length, rc=%d\n", rc); + goto done; + } + + rc = qg_process_fifo(chip, fifo_length); + if (rc < 0) { + pr_err("Failed to process QG FIFO, rc=%d\n", rc); + goto done; + } + + if (++chip->fifo_done_count == U32_MAX) + chip->fifo_done_count = 0; + + rc = qg_vbat_thresholds_config(chip); + if (rc < 0) + pr_err("Failed to apply VBAT EMPTY config rc=%d\n", rc); + + rc = qg_fast_charge_config(chip); + if (rc < 0) + pr_err("Failed to apply fast-charge config rc=%d\n", rc); + + rc = qg_vbat_low_wa(chip); + if (rc < 0) { + pr_err("Failed to apply VBAT LOW WA, rc=%d\n", rc); + goto done; + } + + rc = qg_esr_estimate(chip); + if (rc < 0) { + pr_err("Failed to estimate ESR, rc=%d\n", rc); + goto done; + } + + rc = get_fifo_done_time(chip, false, &hw_delta_ms); + if (rc < 0) + hw_delta_ms = 0; + else + margin_ms = (hw_delta_ms * MAX_FIFO_DELTA_PERCENT) / 100; + + if (abs(hw_delta_ms - time_delta_ms) < margin_ms) { + chip->kdata.param[QG_FIFO_TIME_DELTA].data = time_delta_ms; + chip->kdata.param[QG_FIFO_TIME_DELTA].valid = true; + qg_dbg(chip, QG_DEBUG_FIFO, "FIFO_done time_delta_ms=%lld\n", + time_delta_ms); + } + + /* signal the read thread */ + chip->data_ready = true; + wake_up_interruptible(&chip->qg_wait_q); + + /* vote to stay awake until userspace reads data */ + vote(chip->awake_votable, FIFO_DONE_VOTER, true, 0); + +done: + mutex_unlock(&chip->data_lock); + return IRQ_HANDLED; +} + +static irqreturn_t qg_vbat_low_handler(int irq, void *data) +{ + int rc; + struct qpnp_qg *chip = data; + u8 status = 0; + + qg_dbg(chip, QG_DEBUG_IRQ, "IRQ triggered\n"); + mutex_lock(&chip->data_lock); + + rc = qg_read(chip, chip->qg_base + QG_INT_RT_STS_REG, &status, 1); + if (rc < 0) { + pr_err("Failed to read RT status, rc=%d\n", rc); + goto done; + } + /* ignore VBAT low if battery is missing */ + if ((status & BATTERY_MISSING_INT_RT_STS_BIT) || + chip->battery_missing) + goto done; + + chip->vbat_low = !!(status & VBAT_LOW_INT_RT_STS_BIT); + + qg_dbg(chip, QG_DEBUG_IRQ, "VBAT_LOW = %d\n", chip->vbat_low); +done: + mutex_unlock(&chip->data_lock); + return IRQ_HANDLED; +} + +static irqreturn_t qg_vbat_empty_handler(int irq, void *data) +{ + struct qpnp_qg *chip = data; + u32 ocv_uv = 0; + int rc; + u8 status = 0; + + qg_dbg(chip, QG_DEBUG_IRQ, "IRQ triggered\n"); + + rc = qg_read(chip, chip->qg_base + QG_INT_RT_STS_REG, &status, 1); + if (rc < 0) + pr_err("Failed to read RT status rc=%d\n", rc); + + /* ignore VBAT empty if battery is missing */ + if ((status & BATTERY_MISSING_INT_RT_STS_BIT) || + chip->battery_missing) + return IRQ_HANDLED; + + pr_warn("VBATT EMPTY SOC = 0\n"); + + chip->catch_up_soc = 0; + qg_scale_soc(chip, true); + + qg_sdam_read(SDAM_OCV_UV, &ocv_uv); + chip->sdam_data[SDAM_SOC] = 0; + chip->sdam_data[SDAM_OCV_UV] = ocv_uv; + chip->sdam_data[SDAM_VALID] = 1; + + qg_store_soc_params(chip); + + if (chip->qg_psy) + power_supply_changed(chip->qg_psy); + + return IRQ_HANDLED; +} + +static irqreturn_t qg_good_ocv_handler(int irq, void *data) +{ + int rc; + u8 status = 0; + u32 ocv_uv = 0, ocv_raw = 0; + struct qpnp_qg *chip = data; + unsigned long rtc_sec = 0; + + qg_dbg(chip, QG_DEBUG_IRQ, "IRQ triggered\n"); + + mutex_lock(&chip->data_lock); + + rc = qg_read(chip, chip->qg_base + QG_STATUS2_REG, &status, 1); + if (rc < 0) { + pr_err("Failed to read status2 register rc=%d\n", rc); + goto done; + } + + if (!(status & GOOD_OCV_BIT)) + goto done; + + rc = qg_read_ocv(chip, &ocv_uv, &ocv_raw, S3_GOOD_OCV); + if (rc < 0) { + pr_err("Failed to read good_ocv, rc=%d\n", rc); + goto done; + } + + get_rtc_time(&rtc_sec); + chip->kdata.fifo_time = (u32)rtc_sec; + chip->kdata.param[QG_GOOD_OCV_UV].data = ocv_uv; + chip->kdata.param[QG_GOOD_OCV_UV].valid = true; + + vote(chip->awake_votable, GOOD_OCV_VOTER, true, 0); + + /* signal the readd thread */ + chip->data_ready = true; + wake_up_interruptible(&chip->qg_wait_q); +done: + mutex_unlock(&chip->data_lock); + return IRQ_HANDLED; +} + +static struct qg_irq_info qg_irqs[] = { + [QG_BATT_MISSING_IRQ] = { + .name = "qg-batt-missing", + }, + [QG_VBATT_LOW_IRQ] = { + .name = "qg-vbat-low", + .handler = qg_vbat_low_handler, + .wake = true, + }, + [QG_VBATT_EMPTY_IRQ] = { + .name = "qg-vbat-empty", + .handler = qg_vbat_empty_handler, + .wake = true, + }, + [QG_FIFO_UPDATE_DONE_IRQ] = { + .name = "qg-fifo-done", + .handler = qg_fifo_update_done_handler, + .wake = true, + }, + [QG_GOOD_OCV_IRQ] = { + .name = "qg-good-ocv", + .handler = qg_good_ocv_handler, + .wake = true, + }, + [QG_FSM_STAT_CHG_IRQ] = { + .name = "qg-fsm-state-chg", + }, + [QG_EVENT_IRQ] = { + .name = "qg-event", + }, +}; + +static int qg_awake_cb(struct votable *votable, void *data, int awake, + const char *client) +{ + struct qpnp_qg *chip = data; + + /* ignore if the QG device is not open */ + if (!chip->qg_device_open) + return 0; + + if (awake) + pm_stay_awake(chip->dev); + else + pm_relax(chip->dev); + + pr_debug("client: %s awake: %d\n", client, awake); + return 0; +} + +static int qg_fifo_irq_disable_cb(struct votable *votable, void *data, + int disable, const char *client) +{ + if (disable) { + if (qg_irqs[QG_FIFO_UPDATE_DONE_IRQ].wake) + disable_irq_wake( + qg_irqs[QG_FIFO_UPDATE_DONE_IRQ].irq); + if (qg_irqs[QG_FIFO_UPDATE_DONE_IRQ].irq) + disable_irq_nosync( + qg_irqs[QG_FIFO_UPDATE_DONE_IRQ].irq); + } else { + if (qg_irqs[QG_FIFO_UPDATE_DONE_IRQ].irq) + enable_irq(qg_irqs[QG_FIFO_UPDATE_DONE_IRQ].irq); + if (qg_irqs[QG_FIFO_UPDATE_DONE_IRQ].wake) + enable_irq_wake( + qg_irqs[QG_FIFO_UPDATE_DONE_IRQ].irq); + } + + return 0; +} + +static int qg_vbatt_irq_disable_cb(struct votable *votable, void *data, + int disable, const char *client) +{ + if (disable) { + if (qg_irqs[QG_VBATT_LOW_IRQ].wake) + disable_irq_wake(qg_irqs[QG_VBATT_LOW_IRQ].irq); + if (qg_irqs[QG_VBATT_EMPTY_IRQ].wake) + disable_irq_wake(qg_irqs[QG_VBATT_EMPTY_IRQ].irq); + if (qg_irqs[QG_VBATT_LOW_IRQ].irq) + disable_irq_nosync(qg_irqs[QG_VBATT_LOW_IRQ].irq); + if (qg_irqs[QG_VBATT_EMPTY_IRQ].irq) + disable_irq_nosync(qg_irqs[QG_VBATT_EMPTY_IRQ].irq); + } else { + if (qg_irqs[QG_VBATT_LOW_IRQ].irq) + enable_irq(qg_irqs[QG_VBATT_LOW_IRQ].irq); + if (qg_irqs[QG_VBATT_EMPTY_IRQ].irq) + enable_irq(qg_irqs[QG_VBATT_EMPTY_IRQ].irq); + if (qg_irqs[QG_VBATT_LOW_IRQ].wake) + enable_irq_wake(qg_irqs[QG_VBATT_LOW_IRQ].irq); + if (qg_irqs[QG_VBATT_EMPTY_IRQ].wake) + enable_irq_wake(qg_irqs[QG_VBATT_EMPTY_IRQ].irq); + } + + return 0; +} + +static int qg_good_ocv_irq_disable_cb(struct votable *votable, void *data, + int disable, const char *client) +{ + if (disable) { + if (qg_irqs[QG_GOOD_OCV_IRQ].wake) + disable_irq_wake(qg_irqs[QG_GOOD_OCV_IRQ].irq); + if (qg_irqs[QG_GOOD_OCV_IRQ].irq) + disable_irq_nosync(qg_irqs[QG_GOOD_OCV_IRQ].irq); + } else { + if (qg_irqs[QG_GOOD_OCV_IRQ].irq) + enable_irq(qg_irqs[QG_GOOD_OCV_IRQ].irq); + if (qg_irqs[QG_GOOD_OCV_IRQ].wake) + enable_irq_wake(qg_irqs[QG_GOOD_OCV_IRQ].irq); + } + + return 0; +} + +/* ALG callback functions below */ + +static int qg_get_learned_capacity(void *data, int64_t *learned_cap_uah) +{ + struct qpnp_qg *chip = data; + int16_t cc_mah; + int rc; + + if (!chip) + return -ENODEV; + + if (chip->battery_missing || !chip->profile_loaded) + return -ENODEV; + + rc = qg_sdam_multibyte_read(QG_SDAM_LEARNED_CAPACITY_OFFSET, + (u8 *)&cc_mah, 2); + if (rc < 0) { + pr_err("Error in reading learned_capacity, rc=%d\n", rc); + return rc; + } + *learned_cap_uah = cc_mah * 1000; + + return 0; +} + +static int qg_store_learned_capacity(void *data, int64_t learned_cap_uah) +{ + struct qpnp_qg *chip = data; + int16_t cc_mah; + int rc; + + if (!chip) + return -ENODEV; + + if (chip->battery_missing || !learned_cap_uah) + return -ENODEV; + + cc_mah = div64_s64(learned_cap_uah, 1000); + rc = qg_sdam_multibyte_write(QG_SDAM_LEARNED_CAPACITY_OFFSET, + (u8 *)&cc_mah, 2); + if (rc < 0) { + pr_err("Error in writing learned_capacity, rc=%d\n", rc); + return rc; + } + + qg_dbg(chip, QG_DEBUG_ALG_CL, "Stored learned capacity %llduah\n", + learned_cap_uah); + return 0; +} + +static int qg_get_batt_age_level(void *data, u32 *batt_age_level) +{ + struct qpnp_qg *chip = data; + int rc; + + if (!chip) + return -ENODEV; + + if (chip->battery_missing || is_debug_batt_id(chip)) + return -ENODEV; + + *batt_age_level = 0; + rc = qg_sdam_read(SDAM_BATT_AGE_LEVEL, batt_age_level); + if (rc < 0) { + pr_err("Error in reading batt_age_level, rc=%d\n", rc); + return rc; + } + + return 0; +} + +static int qg_store_batt_age_level(void *data, u32 batt_age_level) +{ + struct qpnp_qg *chip = data; + int rc; + + if (!chip) + return -ENODEV; + + if (chip->battery_missing) + return -ENODEV; + + rc = qg_sdam_write(SDAM_BATT_AGE_LEVEL, batt_age_level); + if (rc < 0) { + pr_err("Error in writing batt_age_level, rc=%d\n", rc); + return rc; + } + + return 0; +} + +static int qg_get_cc_soc(void *data, int *cc_soc) +{ + struct qpnp_qg *chip = data; + + if (!chip) + return -ENODEV; + + if (is_debug_batt_id(chip) || chip->battery_missing) { + *cc_soc = -EINVAL; + return 0; + } + + if (chip->cc_soc == INT_MIN) + *cc_soc = -EINVAL; + else + *cc_soc = chip->cc_soc; + + return 0; +} + +static int qg_restore_cycle_count(void *data, u16 *buf, int length) +{ + struct qpnp_qg *chip = data; + int id, rc = 0; + u8 tmp[2]; + + if (!chip) + return -ENODEV; + + if (chip->battery_missing || !chip->profile_loaded) + return -ENODEV; + + if (!buf || length > BUCKET_COUNT) + return -EINVAL; + + for (id = 0; id < length; id++) { + rc = qg_sdam_multibyte_read( + QG_SDAM_CYCLE_COUNT_OFFSET + (id * 2), + (u8 *)tmp, 2); + if (rc < 0) { + pr_err("failed to read bucket %d rc=%d\n", id, rc); + return rc; + } + *buf++ = tmp[0] | tmp[1] << 8; + } + + return rc; +} + +static int qg_store_cycle_count(void *data, u16 *buf, int id, int length) +{ + struct qpnp_qg *chip = data; + int rc = 0; + + if (!chip) + return -ENODEV; + + if (chip->battery_missing || !chip->profile_loaded) + return -ENODEV; + + if (!buf || length > BUCKET_COUNT * 2 || id < 0 || + id > BUCKET_COUNT - 1 || + (((id * 2) + length) > BUCKET_COUNT * 2)) + return -EINVAL; + + rc = qg_sdam_multibyte_write( + QG_SDAM_CYCLE_COUNT_OFFSET + (id * 2), + (u8 *)buf, length); + if (rc < 0) + pr_err("failed to write bucket %d rc=%d\n", id, rc); + + return rc; +} + +#define DEFAULT_BATT_TYPE "Unknown Battery" +#define MISSING_BATT_TYPE "Missing Battery" +#define DEBUG_BATT_TYPE "Debug Board" +static const char *qg_get_battery_type(struct qpnp_qg *chip) +{ + if (chip->battery_missing) + return MISSING_BATT_TYPE; + + if (is_debug_batt_id(chip)) + return DEBUG_BATT_TYPE; + + if (chip->bp.batt_type_str) { + if (chip->profile_loaded) + return chip->bp.batt_type_str; + } + + return DEFAULT_BATT_TYPE; +} + +#define DEBUG_BATT_SOC 67 +#define BATT_MISSING_SOC 50 +#define EMPTY_SOC 0 +#define FULL_SOC 100 +static int qg_get_battery_capacity(struct qpnp_qg *chip, int *soc) +{ + if (is_debug_batt_id(chip)) { + *soc = DEBUG_BATT_SOC; + return 0; + } + + if (chip->battery_missing || !chip->profile_loaded) { + *soc = BATT_MISSING_SOC; + return 0; + } + + if (chip->charge_full) { + *soc = FULL_SOC; + return 0; + } + + mutex_lock(&chip->soc_lock); + + if (chip->dt.linearize_soc && chip->maint_soc > 0) + *soc = chip->maint_soc; + else + *soc = chip->msoc; + + mutex_unlock(&chip->soc_lock); + + return 0; +} + +static int qg_get_battery_capacity_real(struct qpnp_qg *chip, int *soc) +{ + mutex_lock(&chip->soc_lock); + *soc = chip->msoc; + mutex_unlock(&chip->soc_lock); + + return 0; +} + +static int qg_get_charge_counter(struct qpnp_qg *chip, int *charge_counter) +{ + int rc, cc_soc = 0; + int64_t temp = 0; + + if (is_debug_batt_id(chip) || chip->battery_missing) { + *charge_counter = -EINVAL; + return 0; + } + + rc = qg_get_learned_capacity(chip, &temp); + if (rc < 0 || !temp) + rc = qg_get_nominal_capacity((int *)&temp, 250, true); + + if (rc < 0) { + pr_err("Failed to get FCC for charge-counter rc=%d\n", rc); + return rc; + } + + cc_soc = CAP(0, 100, DIV_ROUND_CLOSEST(chip->cc_soc, 100)); + *charge_counter = div_s64(temp * cc_soc, 100); + + return 0; +} + +static int qg_get_power(struct qpnp_qg *chip, int *val, bool average) +{ + int rc, v_min, v_ocv, rbatt = 0, esr = 0; + s64 power; + + if (is_debug_batt_id(chip)) { + *val = -EINVAL; + return 0; + } + + v_min = chip->dt.sys_min_volt_mv * 1000; + + rc = qg_sdam_read(SDAM_OCV_UV, &v_ocv); + if (rc < 0) { + pr_err("Failed to read OCV rc=%d\n", rc); + return rc; + } + + rc = qg_sdam_read(SDAM_RBAT_MOHM, &rbatt); + if (rc < 0) { + pr_err("Failed to read T_RBAT rc=%d\n", rc); + return rc; + } + + rbatt *= 1000; /* uohms */ + esr = chip->esr_last * 1000; + + if (rbatt <= 0 || esr <= 0) { + pr_debug("Invalid rbatt/esr rbatt=%d esr=%d\n", rbatt, esr); + *val = -EINVAL; + return 0; + } + + power = (s64)v_min * (v_ocv - v_min); + + if (average) + power = div_s64(power, rbatt); + else + power = div_s64(power, esr); + + *val = power; + + qg_dbg(chip, QG_DEBUG_STATUS, "v_min=%d v_ocv=%d rbatt=%d esr=%d power=%lld\n", + v_min, v_ocv, rbatt, esr, power); + + return 0; +} + +static int qg_get_ttf_param(void *data, enum ttf_param param, int *val) +{ + union power_supply_propval prop = {0, }; + struct qpnp_qg *chip = data; + int rc = 0; + int64_t temp = 0; + + if (!chip) + return -ENODEV; + + switch (param) { + case TTF_TTE_VALID: + *val = 1; + if (chip->battery_missing || is_debug_batt_id(chip)) + *val = 0; + break; + case TTF_MSOC: + rc = qg_get_battery_capacity(chip, val); + break; + case TTF_VBAT: + rc = qg_get_battery_voltage(chip, val); + break; + case TTF_IBAT: + rc = qg_get_battery_current(chip, val); + break; + case TTF_FCC: + if (chip->qg_psy) { + rc = power_supply_get_property(chip->qg_psy, + POWER_SUPPLY_PROP_CHARGE_FULL, &prop); + if (rc >= 0) { + temp = div64_u64(prop.intval, 1000); + *val = div64_u64(chip->full_soc * temp, + QG_SOC_FULL); + } + } + break; + case TTF_MODE: + if (chip->ttf->step_chg_cfg_valid) + *val = TTF_MODE_VBAT_STEP_CHG; + else + *val = TTF_MODE_NORMAL; + break; + case TTF_ITERM: + if (chip->chg_iterm_ma == INT_MIN) + *val = 0; + else + *val = chip->chg_iterm_ma; + break; + case TTF_RBATT: + rc = qg_sdam_read(SDAM_RBAT_MOHM, val); + if (!rc) + *val *= 1000; + break; + case TTF_VFLOAT: + *val = chip->bp.float_volt_uv; + break; + case TTF_CHG_TYPE: + *val = chip->charge_type; + break; + case TTF_CHG_STATUS: + *val = chip->charge_status; + break; + case TTF_CHG_DONE: + *val = chip->charge_done; + break; + default: + pr_err("Unsupported property %d\n", param); + rc = -EINVAL; + break; + } + + return rc; +} + +static int qg_ttf_awake_voter(void *data, bool val) +{ + struct qpnp_qg *chip = data; + + if (!chip) + return -ENODEV; + + if (chip->battery_missing || !chip->profile_loaded) + return -ENODEV; + + vote(chip->awake_votable, TTF_AWAKE_VOTER, val, 0); + + return 0; +} + +#define MAX_QG_OK_RETRIES 20 +static int qg_reset(struct qpnp_qg *chip) +{ + int rc = 0, count = 0, soc = 0; + u32 ocv_uv = 0, ocv_raw = 0; + u8 reg = 0; + + qg_dbg(chip, QG_DEBUG_STATUS, "QG RESET triggered\n"); + + mutex_lock(&chip->data_lock); + + /* hold and release master to clear FIFO's */ + rc = qg_master_hold(chip, true); + if (rc < 0) { + pr_err("Failed to hold master, rc=%d\n", rc); + goto done; + } + + /* delay for the master-hold */ + msleep(20); + + rc = qg_master_hold(chip, false); + if (rc < 0) { + pr_err("Failed to release master, rc=%d\n", rc); + goto done; + } + + /* delay for master to settle */ + msleep(20); + + qg_get_battery_voltage(chip, &rc); + qg_get_battery_capacity(chip, &soc); + qg_dbg(chip, QG_DEBUG_STATUS, "VBAT=%duV SOC=%d\n", rc, soc); + + /* Trigger S7 */ + rc = qg_masked_write(chip, chip->qg_base + QG_STATE_TRIG_CMD_REG, + S7_PON_OCV_START, S7_PON_OCV_START); + if (rc < 0) { + pr_err("Failed to trigger S7, rc=%d\n", rc); + goto done; + } + + /* poll for QG OK */ + do { + rc = qg_read(chip, chip->qg_base + QG_STATUS1_REG, ®, 1); + if (rc < 0) { + pr_err("Failed to read STATUS1_REG rc=%d\n", rc); + goto done; + } + + if (reg & QG_OK_BIT) + break; + + msleep(200); + count++; + } while (count < MAX_QG_OK_RETRIES); + + if (count == MAX_QG_OK_RETRIES) { + qg_dbg(chip, QG_DEBUG_STATUS, "QG_OK not set\n"); + goto done; + } + + /* read S7 PON OCV */ + rc = qg_read_ocv(chip, &ocv_uv, &ocv_raw, S7_PON_OCV); + if (rc < 0) { + pr_err("Failed to read PON OCV rc=%d\n", rc); + goto done; + } + + qg_dbg(chip, QG_DEBUG_STATUS, "S7_OCV = %duV\n", ocv_uv); + + chip->kdata.param[QG_GOOD_OCV_UV].data = ocv_uv; + chip->kdata.param[QG_GOOD_OCV_UV].valid = true; + /* clear all the userspace data */ + chip->kdata.param[QG_CLEAR_LEARNT_DATA].data = 1; + chip->kdata.param[QG_CLEAR_LEARNT_DATA].valid = true; + + vote(chip->awake_votable, GOOD_OCV_VOTER, true, 0); + /* signal the read thread */ + chip->data_ready = true; + chip->force_soc = true; + wake_up_interruptible(&chip->qg_wait_q); + +done: + mutex_unlock(&chip->data_lock); + return rc; +} + +static int qg_setprop_batt_age_level(struct qpnp_qg *chip, int batt_age_level) +{ + int rc = 0; + u16 data = 0; + + if (!chip->dt.multi_profile_load) + return 0; + + if (batt_age_level < 0) { + pr_err("Invalid age-level %d\n", batt_age_level); + return -EINVAL; + } + + if (chip->batt_age_level == batt_age_level) { + qg_dbg(chip, QG_DEBUG_PROFILE, "Same age-level %d\n", + chip->batt_age_level); + return 0; + } + + chip->batt_age_level = batt_age_level; + rc = qg_load_battery_profile(chip); + if (rc < 0) { + pr_err("failed to load profile\n"); + } else { + rc = qg_store_batt_age_level(chip, batt_age_level); + if (rc < 0) + pr_err("error in storing batt_age_level rc =%d\n", rc); + } + + /* Clear the learned capacity on loading a new profile */ + rc = qg_sdam_multibyte_write(QG_SDAM_LEARNED_CAPACITY_OFFSET, + (u8 *)&data, 2); + + if (rc < 0) + pr_err("Failed to clear SDAM learnt capacity rc=%d\n", rc); + + qg_dbg(chip, QG_DEBUG_PROFILE, "Profile with batt_age_level = %d loaded\n", + chip->batt_age_level); + + return rc; +} + +static int qg_psy_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *pval) +{ + struct qpnp_qg *chip = power_supply_get_drvdata(psy); + int rc = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_CHARGE_FULL: + if (chip->dt.cl_disable) { + pr_warn("Capacity learning disabled!\n"); + return 0; + } + if (chip->cl->active) { + pr_warn("Capacity learning active!\n"); + return 0; + } + if (pval->intval <= 0 || pval->intval > chip->cl->nom_cap_uah) { + pr_err("charge_full is out of bounds\n"); + return -EINVAL; + } + mutex_lock(&chip->cl->lock); + rc = qg_store_learned_capacity(chip, pval->intval); + if (!rc) + chip->cl->learned_cap_uah = pval->intval; + mutex_unlock(&chip->cl->lock); + break; + case POWER_SUPPLY_PROP_SOH: + chip->soh = pval->intval; + qg_dbg(chip, QG_DEBUG_STATUS, "SOH update: SOH=%d esr_actual=%d esr_nominal=%d\n", + chip->soh, chip->esr_actual, chip->esr_nominal); + if (chip->sp) + soh_profile_update(chip->sp, chip->soh); + break; + case POWER_SUPPLY_PROP_ESR_ACTUAL: + chip->esr_actual = pval->intval; + break; + case POWER_SUPPLY_PROP_ESR_NOMINAL: + chip->esr_nominal = pval->intval; + break; + case POWER_SUPPLY_PROP_FG_RESET: + qg_reset(chip); + break; + case POWER_SUPPLY_PROP_BATT_AGE_LEVEL: + rc = qg_setprop_batt_age_level(chip, pval->intval); + break; + default: + break; + } + return 0; +} + +static int qg_psy_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *pval) +{ + struct qpnp_qg *chip = power_supply_get_drvdata(psy); + int rc = 0; + int64_t temp = 0; + + pval->intval = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_CAPACITY: + rc = qg_get_battery_capacity(chip, &pval->intval); + break; + case POWER_SUPPLY_PROP_CAPACITY_RAW: + pval->intval = chip->sys_soc; + break; + case POWER_SUPPLY_PROP_REAL_CAPACITY: + rc = qg_get_battery_capacity_real(chip, &pval->intval); + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + rc = qg_get_battery_voltage(chip, &pval->intval); + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + rc = qg_get_battery_current(chip, &pval->intval); + break; + case POWER_SUPPLY_PROP_VOLTAGE_OCV: + rc = qg_sdam_read(SDAM_OCV_UV, &pval->intval); + break; + case POWER_SUPPLY_PROP_TEMP: + rc = qg_get_battery_temp(chip, &pval->intval); + break; + case POWER_SUPPLY_PROP_RESISTANCE_ID: + pval->intval = chip->batt_id_ohm; + break; + case POWER_SUPPLY_PROP_DEBUG_BATTERY: + pval->intval = is_debug_batt_id(chip); + break; + case POWER_SUPPLY_PROP_RESISTANCE: + rc = qg_sdam_read(SDAM_RBAT_MOHM, &pval->intval); + if (!rc) + pval->intval *= 1000; + break; + case POWER_SUPPLY_PROP_RESISTANCE_NOW: + pval->intval = chip->esr_last; + break; + case POWER_SUPPLY_PROP_SOC_REPORTING_READY: + pval->intval = chip->soc_reporting_ready; + break; + case POWER_SUPPLY_PROP_RESISTANCE_CAPACITIVE: + pval->intval = chip->dt.rbat_conn_mohm; + break; + case POWER_SUPPLY_PROP_BATTERY_TYPE: + pval->strval = qg_get_battery_type(chip); + break; + case POWER_SUPPLY_PROP_VOLTAGE_MIN: + pval->intval = chip->dt.vbatt_cutoff_mv * 1000; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + pval->intval = chip->bp.float_volt_uv; + break; + case POWER_SUPPLY_PROP_BATT_FULL_CURRENT: + pval->intval = chip->dt.iterm_ma * 1000; + break; + case POWER_SUPPLY_PROP_BATT_PROFILE_VERSION: + pval->intval = chip->bp.qg_profile_version; + break; + case POWER_SUPPLY_PROP_CHARGE_COUNTER: + rc = qg_get_charge_counter(chip, &pval->intval); + break; + case POWER_SUPPLY_PROP_CHARGE_FULL: + if (!chip->dt.cl_disable && chip->dt.cl_feedback_on) + rc = qg_get_learned_capacity(chip, &temp); + else + rc = qg_get_nominal_capacity((int *)&temp, 250, true); + if (!rc) + pval->intval = (int)temp; + break; + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: + rc = qg_get_nominal_capacity((int *)&temp, 250, true); + if (!rc) + pval->intval = (int)temp; + break; + case POWER_SUPPLY_PROP_CYCLE_COUNTS: + rc = get_cycle_counts(chip->counter, &pval->strval); + if (rc < 0) + pval->strval = NULL; + break; + case POWER_SUPPLY_PROP_CYCLE_COUNT: + rc = get_cycle_count(chip->counter, &pval->intval); + break; + case POWER_SUPPLY_PROP_TIME_TO_FULL_AVG: + rc = ttf_get_time_to_full(chip->ttf, &pval->intval); + break; + case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG: + rc = ttf_get_time_to_empty(chip->ttf, &pval->intval); + break; + case POWER_SUPPLY_PROP_ESR_ACTUAL: + pval->intval = (chip->esr_actual == -EINVAL) ? -EINVAL : + (chip->esr_actual * 1000); + break; + case POWER_SUPPLY_PROP_ESR_NOMINAL: + pval->intval = (chip->esr_nominal == -EINVAL) ? -EINVAL : + (chip->esr_nominal * 1000); + break; + case POWER_SUPPLY_PROP_SOH: + pval->intval = chip->soh; + break; + case POWER_SUPPLY_PROP_CC_SOC: + rc = qg_get_cc_soc(chip, &pval->intval); + break; + case POWER_SUPPLY_PROP_VOLTAGE_AVG: + rc = qg_get_vbat_avg(chip, &pval->intval); + break; + case POWER_SUPPLY_PROP_CURRENT_AVG: + rc = qg_get_ibat_avg(chip, &pval->intval); + break; + case POWER_SUPPLY_PROP_POWER_NOW: + rc = qg_get_power(chip, &pval->intval, false); + break; + case POWER_SUPPLY_PROP_POWER_AVG: + rc = qg_get_power(chip, &pval->intval, true); + break; + case POWER_SUPPLY_PROP_SCALE_MODE_EN: + pval->intval = chip->fvss_active; + break; + case POWER_SUPPLY_PROP_BATT_AGE_LEVEL: + pval->intval = chip->batt_age_level; + break; + case POWER_SUPPLY_PROP_FG_TYPE: + pval->intval = chip->qg_mode; + break; + default: + pr_debug("Unsupported property %d\n", psp); + break; + } + + return rc; +} + +static int qg_property_is_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + switch (psp) { + case POWER_SUPPLY_PROP_CHARGE_FULL: + case POWER_SUPPLY_PROP_ESR_ACTUAL: + case POWER_SUPPLY_PROP_ESR_NOMINAL: + case POWER_SUPPLY_PROP_SOH: + case POWER_SUPPLY_PROP_FG_RESET: + case POWER_SUPPLY_PROP_BATT_AGE_LEVEL: + return 1; + default: + break; + } + return 0; +} + +static enum power_supply_property qg_psy_props[] = { + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_CAPACITY_RAW, + POWER_SUPPLY_PROP_REAL_CAPACITY, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_VOLTAGE_OCV, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CHARGE_COUNTER, + POWER_SUPPLY_PROP_RESISTANCE, + POWER_SUPPLY_PROP_RESISTANCE_ID, + POWER_SUPPLY_PROP_RESISTANCE_NOW, + POWER_SUPPLY_PROP_SOC_REPORTING_READY, + POWER_SUPPLY_PROP_RESISTANCE_CAPACITIVE, + POWER_SUPPLY_PROP_DEBUG_BATTERY, + POWER_SUPPLY_PROP_BATTERY_TYPE, + POWER_SUPPLY_PROP_VOLTAGE_MIN, + POWER_SUPPLY_PROP_VOLTAGE_MAX, + POWER_SUPPLY_PROP_BATT_FULL_CURRENT, + POWER_SUPPLY_PROP_BATT_PROFILE_VERSION, + POWER_SUPPLY_PROP_CYCLE_COUNT, + POWER_SUPPLY_PROP_CYCLE_COUNTS, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, + POWER_SUPPLY_PROP_TIME_TO_FULL_AVG, + POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG, + POWER_SUPPLY_PROP_ESR_ACTUAL, + POWER_SUPPLY_PROP_ESR_NOMINAL, + POWER_SUPPLY_PROP_SOH, + POWER_SUPPLY_PROP_CC_SOC, + POWER_SUPPLY_PROP_FG_RESET, + POWER_SUPPLY_PROP_VOLTAGE_AVG, + POWER_SUPPLY_PROP_CURRENT_AVG, + POWER_SUPPLY_PROP_POWER_AVG, + POWER_SUPPLY_PROP_POWER_NOW, + POWER_SUPPLY_PROP_SCALE_MODE_EN, + POWER_SUPPLY_PROP_BATT_AGE_LEVEL, + POWER_SUPPLY_PROP_FG_TYPE, +}; + +static const struct power_supply_desc qg_psy_desc = { + .name = "bms", + .type = POWER_SUPPLY_TYPE_BMS, + .properties = qg_psy_props, + .num_properties = ARRAY_SIZE(qg_psy_props), + .get_property = qg_psy_get_property, + .set_property = qg_psy_set_property, + .property_is_writeable = qg_property_is_writeable, +}; + +#define DEFAULT_CL_BEGIN_IBAT_UA (-100000) +static bool qg_cl_ok_to_begin(void *data) +{ + struct qpnp_qg *chip = data; + + if (chip->last_fifo_i_ua < DEFAULT_CL_BEGIN_IBAT_UA) + return true; + + return false; +} + +#define DEFAULT_RECHARGE_SOC 95 +static int qg_charge_full_update(struct qpnp_qg *chip) +{ + union power_supply_propval prop = {0, }; + int rc, recharge_soc, health; + + if (!chip->dt.hold_soc_while_full) + goto out; + + rc = power_supply_get_property(chip->batt_psy, + POWER_SUPPLY_PROP_HEALTH, &prop); + if (rc < 0) { + pr_err("Failed to get battery health, rc=%d\n", rc); + goto out; + } + health = prop.intval; + + rc = power_supply_get_property(chip->batt_psy, + POWER_SUPPLY_PROP_RECHARGE_SOC, &prop); + if (rc < 0 || prop.intval < 0) { + pr_debug("Failed to get recharge-soc\n"); + recharge_soc = DEFAULT_RECHARGE_SOC; + } else { + recharge_soc = prop.intval; + } + chip->recharge_soc = recharge_soc; + + qg_dbg(chip, QG_DEBUG_STATUS, "msoc=%d health=%d charge_full=%d charge_done=%d\n", + chip->msoc, health, chip->charge_full, + chip->charge_done); + if (chip->charge_done && !chip->charge_full) { + if (chip->msoc >= 99 && health == POWER_SUPPLY_HEALTH_GOOD) { + chip->charge_full = true; + qg_dbg(chip, QG_DEBUG_STATUS, "Setting charge_full (0->1) @ msoc=%d\n", + chip->msoc); + } else if (health != POWER_SUPPLY_HEALTH_GOOD) { + /* terminated in JEITA */ + qg_dbg(chip, QG_DEBUG_STATUS, "Terminated charging @ msoc=%d\n", + chip->msoc); + } + } else if ((!chip->charge_done || chip->msoc <= recharge_soc) + && chip->charge_full) { + + bool input_present = is_input_present(chip); + + /* + * force a recharge only if SOC <= recharge SOC and + * we have not started charging. + */ + if ((chip->wa_flags & QG_RECHARGE_SOC_WA) && + input_present && chip->msoc <= recharge_soc && + chip->charge_status != POWER_SUPPLY_STATUS_CHARGING) { + /* Force recharge */ + prop.intval = 0; + rc = power_supply_set_property(chip->batt_psy, + POWER_SUPPLY_PROP_FORCE_RECHARGE, &prop); + if (rc < 0) + pr_err("Failed to force recharge rc=%d\n", rc); + else + qg_dbg(chip, QG_DEBUG_STATUS, "Forced recharge\n"); + } + + + if (chip->charge_done) + return 0; /* wait for recharge */ + + /* + * If SOC has indeed dropped below recharge-SOC or + * the input is removed, if linearize-soc is set scale + * msoc from 100% for better UX. + */ + if (chip->msoc < recharge_soc || !input_present) { + if (chip->dt.linearize_soc) { + get_rtc_time(&chip->last_maint_soc_update_time); + chip->maint_soc = FULL_SOC; + qg_scale_soc(chip, false); + } + chip->charge_full = false; + qg_dbg(chip, QG_DEBUG_STATUS, "msoc=%d recharge_soc=%d charge_full (1->0)\n", + chip->msoc, recharge_soc); + } else { + /* continue with charge_full state */ + qg_dbg(chip, QG_DEBUG_STATUS, "msoc=%d recharge_soc=%d charge_full=%d input_present=%d\n", + chip->msoc, recharge_soc, + chip->charge_full, input_present); + } + } +out: + return 0; +} + +static int qg_parallel_status_update(struct qpnp_qg *chip) +{ + int rc; + bool parallel_enabled = is_parallel_enabled(chip); + bool update_smb = false; + + if (parallel_enabled == chip->parallel_enabled) + return 0; + + chip->parallel_enabled = parallel_enabled; + qg_dbg(chip, QG_DEBUG_STATUS, + "Parallel status changed Enabled=%d\n", parallel_enabled); + + mutex_lock(&chip->data_lock); + + /* + * Parallel charger uses the same external sense, hence do not + * enable SMB sensing if PMI632 is configured for external sense. + */ + if (!chip->dt.qg_ext_sense) + update_smb = true; + + rc = process_rt_fifo_data(chip, update_smb); + if (rc < 0) + pr_err("Failed to process RT FIFO data, rc=%d\n", rc); + + mutex_unlock(&chip->data_lock); + + return 0; +} + +static int qg_input_status_update(struct qpnp_qg *chip) +{ + bool usb_present = is_usb_present(chip); + bool dc_present = is_dc_present(chip); + + if ((chip->usb_present != usb_present) || + (chip->dc_present != dc_present)) { + qg_dbg(chip, QG_DEBUG_STATUS, + "Input status changed usb_present=%d dc_present=%d\n", + usb_present, dc_present); + qg_scale_soc(chip, false); + } + + chip->usb_present = usb_present; + chip->dc_present = dc_present; + + return 0; +} + +static int qg_handle_battery_removal(struct qpnp_qg *chip) +{ + int rc, length = QG_SDAM_MAX_OFFSET - QG_SDAM_VALID_OFFSET; + u8 *data; + + /* clear SDAM */ + data = kcalloc(length, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + rc = qg_sdam_multibyte_write(QG_SDAM_VALID_OFFSET, data, length); + if (rc < 0) + pr_err("Failed to clear SDAM rc=%d\n", rc); + + return rc; +} + +static int qg_handle_battery_insertion(struct qpnp_qg *chip) +{ + int rc, count = 0; + u32 ocv_uv = 0, ocv_raw = 0; + u8 reg = 0; + + do { + rc = qg_read(chip, chip->qg_base + QG_STATUS1_REG, ®, 1); + if (rc < 0) { + pr_err("Failed to read STATUS1_REG rc=%d\n", rc); + return rc; + } + + if (reg & QG_OK_BIT) + break; + + msleep(200); + count++; + } while (count < MAX_QG_OK_RETRIES); + + if (count == MAX_QG_OK_RETRIES) { + qg_dbg(chip, QG_DEBUG_STATUS, "QG_OK not set!\n"); + return 0; + } + + /* read S7 PON OCV */ + rc = qg_read_ocv(chip, &ocv_uv, &ocv_raw, S7_PON_OCV); + if (rc < 0) { + pr_err("Failed to read PON OCV rc=%d\n", rc); + return rc; + } + + qg_dbg(chip, QG_DEBUG_STATUS, + "S7_OCV on battery insertion = %duV\n", ocv_uv); + + chip->kdata.param[QG_GOOD_OCV_UV].data = ocv_uv; + chip->kdata.param[QG_GOOD_OCV_UV].valid = true; + /* clear all the userspace data */ + chip->kdata.param[QG_CLEAR_LEARNT_DATA].data = 1; + chip->kdata.param[QG_CLEAR_LEARNT_DATA].valid = true; + + vote(chip->awake_votable, GOOD_OCV_VOTER, true, 0); + /* signal the read thread */ + chip->data_ready = true; + wake_up_interruptible(&chip->qg_wait_q); + + return 0; +} + +static int qg_battery_status_update(struct qpnp_qg *chip) +{ + int rc; + union power_supply_propval prop = {0, }; + + if (!is_batt_available(chip)) + return 0; + + mutex_lock(&chip->data_lock); + + rc = power_supply_get_property(chip->batt_psy, + POWER_SUPPLY_PROP_PRESENT, &prop); + if (rc < 0) { + pr_err("Failed to get battery-present, rc=%d\n", rc); + goto done; + } + + if (chip->battery_missing && prop.intval) { + pr_warn("Battery inserted!\n"); + rc = qg_handle_battery_insertion(chip); + if (rc < 0) + pr_err("Failed in battery-insertion rc=%d\n", rc); + } else if (!chip->battery_missing && !prop.intval) { + pr_warn("Battery removed!\n"); + rc = qg_handle_battery_removal(chip); + if (rc < 0) + pr_err("Failed in battery-removal rc=%d\n", rc); + } + + chip->battery_missing = !prop.intval; + +done: + mutex_unlock(&chip->data_lock); + return rc; +} + +static void qg_sleep_exit_work(struct work_struct *work) +{ + int rc; + struct qpnp_qg *chip = container_of(work, + struct qpnp_qg, qg_sleep_exit_work.work); + + vote(chip->awake_votable, SLEEP_EXIT_VOTER, true, 0); + + mutex_lock(&chip->data_lock); + /* + * if this work is executing, the system has been active + * for a while. So, force back the S2 active configuration + */ + qg_dbg(chip, QG_DEBUG_STATUS, "sleep_exit_work: exit S2_SLEEP\n"); + rc = qg_config_s2_state(chip, S2_SLEEP, false, true); + if (rc < 0) + pr_err("Failed to exit S2_SLEEP rc=%d\n", rc); + + vote(chip->awake_votable, SLEEP_EXIT_DATA_VOTER, true, 0); + /* signal the read thread */ + chip->data_ready = true; + wake_up_interruptible(&chip->qg_wait_q); + + mutex_unlock(&chip->data_lock); + + vote(chip->awake_votable, SLEEP_EXIT_VOTER, false, 0); +} + +static void qg_status_change_work(struct work_struct *work) +{ + struct qpnp_qg *chip = container_of(work, + struct qpnp_qg, qg_status_change_work); + union power_supply_propval prop = {0, }; + int rc = 0, batt_temp = 0; + bool input_present = false; + + if (!is_batt_available(chip)) { + pr_debug("batt-psy not available\n"); + goto out; + } + + rc = qg_battery_status_update(chip); + if (rc < 0) + pr_err("Failed to process battery status update rc=%d\n", rc); + + rc = power_supply_get_property(chip->batt_psy, + POWER_SUPPLY_PROP_CHARGE_TYPE, &prop); + if (rc < 0) + pr_err("Failed to get charge-type, rc=%d\n", rc); + else + chip->charge_type = prop.intval; + + rc = power_supply_get_property(chip->batt_psy, + POWER_SUPPLY_PROP_STATUS, &prop); + if (rc < 0) + pr_err("Failed to get charger status, rc=%d\n", rc); + else + chip->charge_status = prop.intval; + + rc = power_supply_get_property(chip->batt_psy, + POWER_SUPPLY_PROP_CHARGE_DONE, &prop); + if (rc < 0) + pr_err("Failed to get charge done status, rc=%d\n", rc); + else + chip->charge_done = prop.intval; + + qg_dbg(chip, QG_DEBUG_STATUS, "charge_status=%d charge_done=%d\n", + chip->charge_status, chip->charge_done); + + rc = qg_parallel_status_update(chip); + if (rc < 0) + pr_err("Failed to update parallel-status, rc=%d\n", rc); + + rc = qg_input_status_update(chip); + if (rc < 0) + pr_err("Failed to update input status, rc=%d\n", rc); + + /* get input status */ + input_present = is_input_present(chip); + + cycle_count_update(chip->counter, + DIV_ROUND_CLOSEST(chip->msoc * 255, 100), + chip->charge_status, chip->charge_done, + input_present); + + if (!chip->dt.cl_disable) { + rc = qg_get_battery_temp(chip, &batt_temp); + if (rc < 0) { + pr_err("Failed to read BATT_TEMP at PON rc=%d\n", rc); + } else if (chip->batt_soc >= 0) { + cap_learning_update(chip->cl, batt_temp, chip->batt_soc, + chip->charge_status, chip->charge_done, + input_present, false); + } + } + rc = qg_charge_full_update(chip); + if (rc < 0) + pr_err("Failed in charge_full_update, rc=%d\n", rc); + + ttf_update(chip->ttf, input_present); +out: + pm_relax(chip->dev); +} + +static int qg_notifier_cb(struct notifier_block *nb, + unsigned long event, void *data) +{ + struct power_supply *psy = data; + struct qpnp_qg *chip = container_of(nb, struct qpnp_qg, nb); + + if (event != PSY_EVENT_PROP_CHANGED) + return NOTIFY_OK; + + if (work_pending(&chip->qg_status_change_work)) + return NOTIFY_OK; + + if ((strcmp(psy->desc->name, "battery") == 0) + || (strcmp(psy->desc->name, "parallel") == 0) + || (strcmp(psy->desc->name, "usb") == 0) + || (strcmp(psy->desc->name, "dc") == 0)) { + /* + * We cannot vote for awake votable here as that takes + * a mutex lock and this is executed in an atomic context. + */ + pm_stay_awake(chip->dev); + schedule_work(&chip->qg_status_change_work); + } + + return NOTIFY_OK; +} + +static int qg_init_psy(struct qpnp_qg *chip) +{ + struct power_supply_config qg_psy_cfg = {}; + int rc; + + qg_psy_cfg.drv_data = chip; + qg_psy_cfg.of_node = chip->dev->of_node; + chip->qg_psy = devm_power_supply_register(chip->dev, + &qg_psy_desc, &qg_psy_cfg); + if (IS_ERR_OR_NULL(chip->qg_psy)) { + pr_err("Failed to register qg_psy rc = %ld\n", + PTR_ERR(chip->qg_psy)); + return -ENODEV; + } + + chip->nb.notifier_call = qg_notifier_cb; + rc = power_supply_reg_notifier(&chip->nb); + if (rc < 0) + pr_err("Failed register psy notifier rc = %d\n", rc); + + return rc; +} + +static ssize_t qg_device_read(struct file *file, char __user *buf, size_t count, + loff_t *ppos) +{ + int rc; + struct qpnp_qg *chip = file->private_data; + unsigned long data_size = sizeof(chip->kdata); + + if (count < data_size) { + pr_err("Invalid datasize %lu, expected lesser then %zu\n", + data_size, count); + return -EINVAL; + } + + /* non-blocking access, return */ + if (!chip->data_ready && (file->f_flags & O_NONBLOCK)) + return 0; + + /* blocking access wait on data_ready */ + if (!(file->f_flags & O_NONBLOCK)) { + rc = wait_event_interruptible(chip->qg_wait_q, + chip->data_ready); + if (rc < 0) { + pr_debug("Failed wait! rc=%d\n", rc); + return rc; + } + } + + mutex_lock(&chip->data_lock); + + if (!chip->data_ready) { + pr_debug("No Data, false wakeup\n"); + rc = -EFAULT; + goto fail_read; + } + + + if (copy_to_user(buf, &chip->kdata, data_size)) { + pr_err("Failed in copy_to_user\n"); + rc = -EFAULT; + goto fail_read; + } + chip->data_ready = false; + + /* release all wake sources */ + vote(chip->awake_votable, GOOD_OCV_VOTER, false, 0); + vote(chip->awake_votable, FIFO_DONE_VOTER, false, 0); + vote(chip->awake_votable, FIFO_RT_DONE_VOTER, false, 0); + vote(chip->awake_votable, SUSPEND_DATA_VOTER, false, 0); + vote(chip->awake_votable, SLEEP_EXIT_DATA_VOTER, false, 0); + + qg_dbg(chip, QG_DEBUG_DEVICE, + "QG device read complete Seq_no=%u Size=%ld\n", + chip->kdata.seq_no, data_size); + + /* clear data */ + memset(&chip->kdata, 0, sizeof(chip->kdata)); + + mutex_unlock(&chip->data_lock); + + return data_size; + +fail_read: + mutex_unlock(&chip->data_lock); + return rc; +} + +static ssize_t qg_device_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + int rc = -EINVAL; + struct qpnp_qg *chip = file->private_data; + unsigned long data_size = sizeof(chip->udata); + + mutex_lock(&chip->data_lock); + if (count == 0) { + pr_err("No data!\n"); + goto fail; + } + + if (count != 0 && count < data_size) { + pr_err("Invalid datasize %zu expected %lu\n", count, data_size); + goto fail; + } + + if (copy_from_user(&chip->udata, buf, data_size)) { + pr_err("Failed in copy_from_user\n"); + rc = -EFAULT; + goto fail; + } + + rc = data_size; + vote(chip->awake_votable, UDATA_READY_VOTER, true, 0); + schedule_work(&chip->udata_work); + qg_dbg(chip, QG_DEBUG_DEVICE, "QG write complete size=%d\n", rc); +fail: + mutex_unlock(&chip->data_lock); + return rc; +} + +static unsigned int qg_device_poll(struct file *file, poll_table *wait) +{ + struct qpnp_qg *chip = file->private_data; + unsigned int mask = 0; + + poll_wait(file, &chip->qg_wait_q, wait); + + if (chip->data_ready) + mask = POLLIN | POLLRDNORM; + + return mask; +} + +static int qg_device_open(struct inode *inode, struct file *file) +{ + struct qpnp_qg *chip = container_of(inode->i_cdev, + struct qpnp_qg, qg_cdev); + + file->private_data = chip; + chip->qg_device_open = true; + qg_dbg(chip, QG_DEBUG_DEVICE, "QG device opened!\n"); + + return 0; +} + +static int qg_device_release(struct inode *inode, struct file *file) +{ + struct qpnp_qg *chip = container_of(inode->i_cdev, + struct qpnp_qg, qg_cdev); + + file->private_data = chip; + chip->qg_device_open = false; + qg_dbg(chip, QG_DEBUG_DEVICE, "QG device closed!\n"); + + return 0; +} + +static const struct file_operations qg_fops = { + .owner = THIS_MODULE, + .open = qg_device_open, + .release = qg_device_release, + .read = qg_device_read, + .write = qg_device_write, + .poll = qg_device_poll, +}; + +static int qg_register_device(struct qpnp_qg *chip) +{ + int rc; + + rc = alloc_chrdev_region(&chip->dev_no, 0, 1, "qg"); + if (rc < 0) { + pr_err("Failed to allocate chardev rc=%d\n", rc); + return rc; + } + + cdev_init(&chip->qg_cdev, &qg_fops); + rc = cdev_add(&chip->qg_cdev, chip->dev_no, 1); + if (rc < 0) { + pr_err("Failed to cdev_add rc=%d\n", rc); + goto unregister_chrdev; + } + + chip->qg_class = class_create(THIS_MODULE, "qg"); + if (IS_ERR_OR_NULL(chip->qg_class)) { + pr_err("Failed to create qg class\n"); + rc = -EINVAL; + goto delete_cdev; + } + chip->qg_device = device_create(chip->qg_class, NULL, chip->dev_no, + NULL, "qg"); + if (IS_ERR(chip->qg_device)) { + pr_err("Failed to create qg_device\n"); + rc = -EINVAL; + goto destroy_class; + } + + qg_dbg(chip, QG_DEBUG_DEVICE, "'/dev/qg' successfully created\n"); + + return 0; + +destroy_class: + class_destroy(chip->qg_class); +delete_cdev: + cdev_del(&chip->qg_cdev); +unregister_chrdev: + unregister_chrdev_region(chip->dev_no, 1); + return rc; +} + +#define BID_RPULL_OHM 100000 +#define BID_VREF_MV 1875 +static int get_batt_id_ohm(struct qpnp_qg *chip, u32 *batt_id_ohm) +{ + int rc, batt_id_mv; + int64_t denom; + + /* Read battery-id */ + rc = iio_read_channel_processed(chip->batt_id_chan, &batt_id_mv); + if (rc < 0) { + pr_err("Failed to read BATT_ID over ADC, rc=%d\n", rc); + return rc; + } + + batt_id_mv = div_s64(batt_id_mv, 1000); + if (batt_id_mv == 0) { + pr_debug("batt_id_mv = 0 from ADC\n"); + return 0; + } + + denom = div64_s64(BID_VREF_MV * 1000, batt_id_mv) - 1000; + if (denom <= 0) { + /* batt id connector might be open, return 0 kohms */ + return 0; + } + + *batt_id_ohm = div64_u64(BID_RPULL_OHM * 1000 + denom / 2, denom); + + qg_dbg(chip, QG_DEBUG_PROFILE, "batt_id_mv=%d, batt_id_ohm=%d\n", + batt_id_mv, *batt_id_ohm); + + return 0; +} + +static int qg_load_battery_profile(struct qpnp_qg *chip) +{ + struct device_node *node = chip->dev->of_node; + struct device_node *profile_node; + int rc, tuple_len, len, i, avail_age_level = 0; + + chip->batt_node = of_find_node_by_name(node, "qcom,battery-data"); + if (!chip->batt_node) { + pr_err("Batterydata not available\n"); + return -ENXIO; + } + + if (chip->dt.multi_profile_load) { + if (chip->batt_age_level == -EINVAL) { + rc = qg_get_batt_age_level(chip, &chip->batt_age_level); + if (rc < 0) { + pr_err("error in retrieving batt age level rc=%d\n", + rc); + return rc; + } + } + profile_node = of_batterydata_get_best_aged_profile( + chip->batt_node, + chip->batt_id_ohm / 1000, + chip->batt_age_level, + &avail_age_level); + if (chip->batt_age_level != avail_age_level) { + qg_dbg(chip, QG_DEBUG_PROFILE, "Batt_age_level %d doesn't exist, using %d\n", + chip->batt_age_level, avail_age_level); + chip->batt_age_level = avail_age_level; + } + } else { + profile_node = of_batterydata_get_best_profile(chip->batt_node, + chip->batt_id_ohm / 1000, NULL); + } + + if (IS_ERR(profile_node)) { + rc = PTR_ERR(profile_node); + pr_err("Failed to detect valid QG battery profile %d\n", rc); + return rc; + } + + rc = of_property_read_string(profile_node, "qcom,battery-type", + &chip->bp.batt_type_str); + if (rc < 0) { + pr_err("Failed to detect battery type rc:%d\n", rc); + return rc; + } + + rc = qg_batterydata_init(profile_node); + if (rc < 0) { + pr_err("Failed to initialize battery-profile rc=%d\n", rc); + return rc; + } + + rc = of_property_read_u32(profile_node, "qcom,max-voltage-uv", + &chip->bp.float_volt_uv); + if (rc < 0) { + pr_err("Failed to read battery float-voltage rc:%d\n", rc); + chip->bp.float_volt_uv = -EINVAL; + } + + rc = of_property_read_u32(profile_node, "qcom,fastchg-current-ma", + &chip->bp.fastchg_curr_ma); + if (rc < 0) { + pr_err("Failed to read battery fastcharge current rc:%d\n", rc); + chip->bp.fastchg_curr_ma = -EINVAL; + } + + /* + * Update the max fcc values based on QG subtype including + * error margins. + */ + chip->bp.fastchg_curr_ma = min(chip->max_fcc_limit_ma, + chip->bp.fastchg_curr_ma); + + rc = of_property_read_u32(profile_node, "qcom,qg-batt-profile-ver", + &chip->bp.qg_profile_version); + if (rc < 0) { + pr_err("Failed to read QG profile version rc:%d\n", rc); + chip->bp.qg_profile_version = -EINVAL; + } + + /* + * Currently step charging thresholds should be read only for Vbatt + * based and not for SOC based. + */ + if (!of_property_read_bool(profile_node, "qcom,soc-based-step-chg") && + of_find_property(profile_node, "qcom,step-chg-ranges", &len) && + chip->bp.float_volt_uv > 0 && chip->bp.fastchg_curr_ma > 0) { + len /= sizeof(u32); + tuple_len = len / (sizeof(struct range_data) / sizeof(u32)); + if (tuple_len <= 0 || tuple_len > MAX_STEP_CHG_ENTRIES) + return -EINVAL; + + mutex_lock(&chip->ttf->lock); + chip->ttf->step_chg_cfg = + kcalloc(len, sizeof(*chip->ttf->step_chg_cfg), + GFP_KERNEL); + if (!chip->ttf->step_chg_cfg) { + mutex_unlock(&chip->ttf->lock); + return -ENOMEM; + } + + chip->ttf->step_chg_data = + kcalloc(tuple_len, sizeof(*chip->ttf->step_chg_data), + GFP_KERNEL); + if (!chip->ttf->step_chg_data) { + kfree(chip->ttf->step_chg_cfg); + mutex_unlock(&chip->ttf->lock); + return -ENOMEM; + } + + rc = read_range_data_from_node(profile_node, + "qcom,step-chg-ranges", + chip->ttf->step_chg_cfg, + chip->bp.float_volt_uv, + chip->bp.fastchg_curr_ma * 1000); + if (rc < 0) { + pr_err("Error in reading qcom,step-chg-ranges from battery profile, rc=%d\n", + rc); + kfree(chip->ttf->step_chg_data); + kfree(chip->ttf->step_chg_cfg); + chip->ttf->step_chg_cfg = NULL; + mutex_unlock(&chip->ttf->lock); + return rc; + } + + chip->ttf->step_chg_num_params = tuple_len; + chip->ttf->step_chg_cfg_valid = true; + mutex_unlock(&chip->ttf->lock); + + if (chip->ttf->step_chg_cfg_valid) { + for (i = 0; i < tuple_len; i++) + pr_debug("Vbatt_low: %d Vbatt_high: %d FCC: %d\n", + chip->ttf->step_chg_cfg[i].low_threshold, + chip->ttf->step_chg_cfg[i].high_threshold, + chip->ttf->step_chg_cfg[i].value); + } + } + + qg_dbg(chip, QG_DEBUG_PROFILE, "profile=%s FV=%duV FCC=%dma\n", + chip->bp.batt_type_str, chip->bp.float_volt_uv, + chip->bp.fastchg_curr_ma); + + return 0; +} + +static int qg_setup_battery(struct qpnp_qg *chip) +{ + int rc; + + if (!is_battery_present(chip)) { + qg_dbg(chip, QG_DEBUG_PROFILE, "Battery Missing!\n"); + chip->battery_missing = true; + chip->profile_loaded = false; + chip->soc_reporting_ready = true; + } else { + /* battery present */ + rc = get_batt_id_ohm(chip, &chip->batt_id_ohm); + if (rc < 0) { + pr_err("Failed to detect batt_id rc=%d\n", rc); + chip->profile_loaded = false; + } else { + rc = qg_load_battery_profile(chip); + if (rc < 0) { + pr_err("Failed to load battery-profile rc=%d\n", + rc); + chip->profile_loaded = false; + chip->soc_reporting_ready = true; + } else { + chip->profile_loaded = true; + } + } + } + + qg_dbg(chip, QG_DEBUG_PROFILE, "battery_missing=%d batt_id_ohm=%d Ohm profile_loaded=%d profile=%s\n", + chip->battery_missing, chip->batt_id_ohm, + chip->profile_loaded, chip->bp.batt_type_str); + + return 0; +} + +static struct ocv_all ocv[] = { + [S7_PON_OCV] = { 0, 0, "S7_PON_OCV"}, + [S3_GOOD_OCV] = { 0, 0, "S3_GOOD_OCV"}, + [S3_LAST_OCV] = { 0, 0, "S3_LAST_OCV"}, + [SDAM_PON_OCV] = { 0, 0, "SDAM_PON_OCV"}, +}; + +#define S7_ERROR_MARGIN_UV 20000 +static int qg_determine_pon_soc(struct qpnp_qg *chip) +{ + int rc = 0, batt_temp = 0, i, shutdown_temp = 0; + bool use_pon_ocv = true; + unsigned long rtc_sec = 0; + u32 ocv_uv = 0, soc = 0, pon_soc = 0, full_soc = 0, cutoff_soc = 0; + u32 shutdown[SDAM_MAX] = {0}, soc_raw = 0; + char ocv_type[20] = "NONE"; + + if (!chip->profile_loaded) { + qg_dbg(chip, QG_DEBUG_PON, "No Profile, skipping PON soc\n"); + return 0; + } + + /* read all OCVs */ + for (i = S7_PON_OCV; i < PON_OCV_MAX; i++) { + rc = qg_read_ocv(chip, &ocv[i].ocv_uv, + &ocv[i].ocv_raw, i); + if (rc < 0) + pr_err("Failed to read %s OCV rc=%d\n", + ocv[i].ocv_type, rc); + else + qg_dbg(chip, QG_DEBUG_PON, "%s OCV=%d\n", + ocv[i].ocv_type, ocv[i].ocv_uv); + } + + rc = qg_get_battery_temp(chip, &batt_temp); + if (rc < 0) { + pr_err("Failed to read BATT_TEMP at PON rc=%d\n", rc); + goto done; + } + + rc = get_rtc_time(&rtc_sec); + if (rc < 0) { + pr_err("Failed to read RTC time rc=%d\n", rc); + goto use_pon_ocv; + } + + rc = qg_sdam_read_all(shutdown); + if (rc < 0) { + pr_err("Failed to read shutdown params rc=%d\n", rc); + goto use_pon_ocv; + } + shutdown_temp = sign_extend32(shutdown[SDAM_TEMP], 15); + + rc = lookup_soc_ocv(&pon_soc, ocv[S7_PON_OCV].ocv_uv, batt_temp, false); + if (rc < 0) { + pr_err("Failed to lookup S7_PON SOC rc=%d\n", rc); + goto done; + } + + qg_dbg(chip, QG_DEBUG_PON, "Shutdown: Valid=%d SOC=%d OCV=%duV time=%dsecs temp=%d, time_now=%ldsecs temp_now=%d S7_soc=%d\n", + shutdown[SDAM_VALID], + shutdown[SDAM_SOC], + shutdown[SDAM_OCV_UV], + shutdown[SDAM_TIME_SEC], + shutdown_temp, + rtc_sec, batt_temp, + pon_soc); + /* + * Use the shutdown SOC if + * 1. SDAM read is a success & SDAM data is valid + * 2. The device was powered off for < ignore_shutdown_time + * 2. Batt temp has not changed more than shutdown_temp_diff + */ + if (!shutdown[SDAM_VALID]) + goto use_pon_ocv; + + if (!is_between(0, chip->dt.ignore_shutdown_soc_secs, + (rtc_sec - shutdown[SDAM_TIME_SEC]))) + goto use_pon_ocv; + + if (!is_between(0, chip->dt.shutdown_temp_diff, + abs(shutdown_temp - batt_temp)) && + (shutdown_temp < 0 || batt_temp < 0)) + goto use_pon_ocv; + + if ((chip->dt.shutdown_soc_threshold != -EINVAL) && + !is_between(0, chip->dt.shutdown_soc_threshold, + abs(pon_soc - shutdown[SDAM_SOC]))) + goto use_pon_ocv; + + use_pon_ocv = false; + ocv_uv = shutdown[SDAM_OCV_UV]; + soc = shutdown[SDAM_SOC]; + soc_raw = shutdown[SDAM_SOC] * 100; + strlcpy(ocv_type, "SHUTDOWN_SOC", 20); + qg_dbg(chip, QG_DEBUG_PON, "Using SHUTDOWN_SOC @ PON\n"); + +use_pon_ocv: + if (use_pon_ocv) { + if (chip->wa_flags & QG_PON_OCV_WA) { + if (ocv[S3_LAST_OCV].ocv_raw == FIFO_V_RESET_VAL) { + if (!ocv[SDAM_PON_OCV].ocv_uv) { + strlcpy(ocv_type, "S7_PON_SOC", 20); + ocv_uv = ocv[S7_PON_OCV].ocv_uv; + } else if (ocv[SDAM_PON_OCV].ocv_uv <= + ocv[S7_PON_OCV].ocv_uv) { + strlcpy(ocv_type, "S7_PON_SOC", 20); + ocv_uv = ocv[S7_PON_OCV].ocv_uv; + } else if (!shutdown[SDAM_VALID] && + ((ocv[SDAM_PON_OCV].ocv_uv - + ocv[S7_PON_OCV].ocv_uv) > + S7_ERROR_MARGIN_UV)) { + strlcpy(ocv_type, "S7_PON_SOC", 20); + ocv_uv = ocv[S7_PON_OCV].ocv_uv; + } else { + strlcpy(ocv_type, "SDAM_PON_SOC", 20); + ocv_uv = ocv[SDAM_PON_OCV].ocv_uv; + } + } else { + if (ocv[S3_LAST_OCV].ocv_uv >= + ocv[S7_PON_OCV].ocv_uv) { + strlcpy(ocv_type, "S3_LAST_SOC", 20); + ocv_uv = ocv[S3_LAST_OCV].ocv_uv; + } else { + strlcpy(ocv_type, "S7_PON_SOC", 20); + ocv_uv = ocv[S7_PON_OCV].ocv_uv; + } + } + } else { + /* Use S7 PON OCV */ + strlcpy(ocv_type, "S7_PON_SOC", 20); + ocv_uv = ocv[S7_PON_OCV].ocv_uv; + } + + ocv_uv = CAP(QG_MIN_OCV_UV, QG_MAX_OCV_UV, ocv_uv); + rc = lookup_soc_ocv(&pon_soc, ocv_uv, batt_temp, false); + if (rc < 0) { + pr_err("Failed to lookup SOC@PON rc=%d\n", rc); + goto done; + } + + rc = lookup_soc_ocv(&full_soc, chip->bp.float_volt_uv, + batt_temp, true); + if (rc < 0) { + pr_err("Failed to lookup FULL_SOC@PON rc=%d\n", rc); + goto done; + } + full_soc = CAP(0, 99, full_soc); + + rc = lookup_soc_ocv(&cutoff_soc, + chip->dt.vbatt_cutoff_mv * 1000, + batt_temp, false); + if (rc < 0) { + pr_err("Failed to lookup CUTOFF_SOC@PON rc=%d\n", rc); + goto done; + } + + if ((full_soc > cutoff_soc) && (pon_soc > cutoff_soc)) { + soc = DIV_ROUND_UP(((pon_soc - cutoff_soc) * 100), + (full_soc - cutoff_soc)); + soc = CAP(0, 100, soc); + soc_raw = soc * 100; + } else { + soc_raw = pon_soc * 100; + soc = pon_soc; + } + + qg_dbg(chip, QG_DEBUG_PON, "v_float=%d v_cutoff=%d FULL_SOC=%d CUTOFF_SOC=%d PON_SYS_SOC=%d pon_soc=%d\n", + chip->bp.float_volt_uv, chip->dt.vbatt_cutoff_mv * 1000, + full_soc, cutoff_soc, soc, pon_soc); + } +done: + if (rc < 0) { + pr_err("Failed to get %s @ PON, rc=%d\n", ocv_type, rc); + return rc; + } + + if (chip->qg_mode == QG_V_I_MODE) + chip->cc_soc = soc_raw; + chip->sys_soc = soc_raw; + chip->last_adj_ssoc = chip->catch_up_soc = chip->msoc = soc; + chip->kdata.param[QG_PON_OCV_UV].data = ocv_uv; + chip->kdata.param[QG_PON_OCV_UV].valid = true; + + /* write back to SDAM */ + chip->sdam_data[SDAM_SOC] = soc; + chip->sdam_data[SDAM_OCV_UV] = ocv_uv; + chip->sdam_data[SDAM_VALID] = 1; + + rc = qg_write_monotonic_soc(chip, chip->msoc); + if (rc < 0) + pr_err("Failed to update MSOC register rc=%d\n", rc); + + rc = qg_store_soc_params(chip); + if (rc < 0) + pr_err("Failed to update sdam params rc=%d\n", rc); + + pr_info("using %s @ PON ocv_uv=%duV soc=%d\n", + ocv_type, ocv_uv, chip->msoc); + + /* SOC reporting is now ready */ + chip->soc_reporting_ready = 1; + + return 0; +} + +static int qg_set_wa_flags(struct qpnp_qg *chip) +{ + switch (chip->pmic_rev_id->pmic_subtype) { + case PMI632_SUBTYPE: + chip->wa_flags |= QG_RECHARGE_SOC_WA; + if (!chip->dt.use_s7_ocv) + chip->wa_flags |= QG_PON_OCV_WA; + if (chip->pmic_rev_id->rev4 == PMI632_V1P0_REV4) + chip->wa_flags |= QG_VBAT_LOW_WA; + break; + case PM6150_SUBTYPE: + chip->wa_flags |= QG_CLK_ADJUST_WA | + QG_RECHARGE_SOC_WA; + qg_esr_mod_count = 10; + break; + case PM7250B_SUBTYPE: + qg_esr_mod_count = 10; + break; + case PM2250_SUBTYPE: + chip->wa_flags |= QG_CLK_ADJUST_WA | + QG_RECHARGE_SOC_WA | + QG_VBAT_LOW_WA; + break; + default: + pr_err("Unsupported PMIC subtype %d\n", + chip->pmic_rev_id->pmic_subtype); + return -EINVAL; + } + + qg_dbg(chip, QG_DEBUG_PON, "wa_flags = %x\n", chip->wa_flags); + + return 0; +} + +#define SDAM_MAGIC_NUMBER 0x12345678 +static int qg_sanitize_sdam(struct qpnp_qg *chip) +{ + int rc = 0; + u32 data = 0; + + rc = qg_sdam_read(SDAM_MAGIC, &data); + if (rc < 0) { + pr_err("Failed to read SDAM rc=%d\n", rc); + return rc; + } + + if (data == SDAM_MAGIC_NUMBER) { + qg_dbg(chip, QG_DEBUG_PON, "SDAM valid\n"); + } else if (data == 0) { + rc = qg_sdam_write(SDAM_MAGIC, SDAM_MAGIC_NUMBER); + if (!rc) + qg_dbg(chip, QG_DEBUG_PON, "First boot. SDAM initilized\n"); + } else { + /* SDAM has invalid value */ + rc = qg_sdam_clear(); + if (!rc) { + pr_err("SDAM uninitialized, SDAM reset\n"); + rc = qg_sdam_write(SDAM_MAGIC, SDAM_MAGIC_NUMBER); + } + } + + if (rc < 0) + pr_err("Failed in SDAM operation, rc=%d\n", rc); + + return rc; +} + +#define ADC_CONV_DLY_512MS 0xA +#define IBAT_5A_FCC_MA 4800 +#define IBAT_10A_FCC_MA 9600 +static int qg_hw_init(struct qpnp_qg *chip) +{ + int rc, temp; + u8 reg; + + /* read STATUS2 register to clear its last state */ + qg_read(chip, chip->qg_base + QG_STATUS2_REG, ®, 1); + + /* read the QG perph_subtype */ + rc = qg_read(chip, chip->qg_base + PERPH_SUBTYPE_REG, + &chip->qg_subtype, 1); + if (rc < 0) { + pr_err("Failed to read QG subtype rc=%d\n", rc); + return rc; + } + + if (chip->qg_subtype == QG_ADC_IBAT_5A) + chip->max_fcc_limit_ma = IBAT_5A_FCC_MA; + else + chip->max_fcc_limit_ma = IBAT_10A_FCC_MA; + + if (chip->qg_version == QG_LITE) { + rc = qg_read(chip, chip->qg_base + QG_MODE_CTL2_REG, ®, 1); + if (rc < 0) { + pr_err("Failed to read QG mode rc=%d\n", rc); + return rc; + } + chip->qg_mode = (reg & VI_MODE_BIT) ? QG_V_I_MODE : QG_V_MODE; + } else { + chip->qg_mode = QG_V_I_MODE; + } + + if (chip->qg_mode == QG_V_MODE) { + chip->dt.esr_disable = true; + chip->dt.cl_disable = true; + chip->dt.tcss_enable = false; + chip->dt.bass_enable = false; + } + + rc = qg_set_wa_flags(chip); + if (rc < 0) { + pr_err("Failed to update PMIC type flags, rc=%d\n", rc); + return rc; + } + + rc = qg_master_hold(chip, true); + if (rc < 0) { + pr_err("Failed to hold master, rc=%d\n", rc); + goto done_fifo; + } + + rc = qg_process_rt_fifo(chip); + if (rc < 0) { + pr_err("Failed to process FIFO real-time, rc=%d\n", rc); + goto done_fifo; + } + + /* update the changed S2 fifo DT parameters */ + if (chip->dt.s2_fifo_length > 0) { + rc = qg_update_fifo_length(chip, chip->dt.s2_fifo_length); + if (rc < 0) + goto done_fifo; + } + + if (chip->dt.s2_acc_length > 0) { + reg = ilog2(chip->dt.s2_acc_length) - 1; + rc = qg_masked_write(chip, chip->qg_base + + QG_S2_NORMAL_MEAS_CTL2_REG, + NUM_OF_ACCUM_MASK, reg); + if (rc < 0) { + pr_err("Failed to write S2 ACC length, rc=%d\n", rc); + goto done_fifo; + } + } + + if (chip->dt.s2_acc_intvl_ms > 0) { + reg = chip->dt.s2_acc_intvl_ms / 10; + rc = qg_write(chip, chip->qg_base + + QG_S2_NORMAL_MEAS_CTL3_REG, + ®, 1); + if (rc < 0) { + pr_err("Failed to write S2 ACC intrvl, rc=%d\n", rc); + goto done_fifo; + } + } + + chip->s2_state = S2_DEFAULT; + chip->s2_state_mask |= S2_DEFAULT; + /* signal the read thread */ + chip->data_ready = true; + wake_up_interruptible(&chip->qg_wait_q); + +done_fifo: + rc = qg_master_hold(chip, false); + if (rc < 0) { + pr_err("Failed to release master, rc=%d\n", rc); + return rc; + } + chip->last_fifo_update_time = ktime_get_boottime(); + + if (chip->dt.ocv_timer_expiry_min != -EINVAL && + chip->qg_version != QG_LITE) { + if (chip->dt.ocv_timer_expiry_min < 2) + chip->dt.ocv_timer_expiry_min = 2; + else if (chip->dt.ocv_timer_expiry_min > 30) + chip->dt.ocv_timer_expiry_min = 30; + + reg = (chip->dt.ocv_timer_expiry_min - 2) / 4; + rc = qg_masked_write(chip, + chip->qg_base + QG_S3_SLEEP_OCV_MEAS_CTL4_REG, + SLEEP_IBAT_QUALIFIED_LENGTH_MASK, reg); + if (rc < 0) { + pr_err("Failed to write OCV timer, rc=%d\n", rc); + return rc; + } + } + + if (chip->dt.ocv_tol_threshold_uv != -EINVAL) { + if (chip->dt.ocv_tol_threshold_uv < 0) + chip->dt.ocv_tol_threshold_uv = 0; + else if (chip->dt.ocv_tol_threshold_uv > 12262) + chip->dt.ocv_tol_threshold_uv = 12262; + + reg = chip->dt.ocv_tol_threshold_uv / 195; + rc = qg_masked_write(chip, + chip->qg_base + QG_S3_SLEEP_OCV_TREND_CTL2_REG, + TREND_TOL_MASK, reg); + if (rc < 0) { + pr_err("Failed to write OCV tol-thresh, rc=%d\n", rc); + return rc; + } + } + + if (chip->dt.s3_entry_fifo_length != -EINVAL) { + if (chip->dt.s3_entry_fifo_length < 1) + chip->dt.s3_entry_fifo_length = 1; + else if (chip->dt.s3_entry_fifo_length > + chip->max_fifo_length) + chip->dt.s3_entry_fifo_length = + chip->max_fifo_length; + + reg = chip->dt.s3_entry_fifo_length - 1; + rc = qg_masked_write(chip, + chip->qg_base + QG_S3_SLEEP_OCV_IBAT_CTL1_REG, + SLEEP_IBAT_QUALIFIED_LENGTH_MASK, reg); + if (rc < 0) { + pr_err("Failed to write S3-entry fifo-length, rc=%d\n", + rc); + return rc; + } + } + + if (chip->dt.s3_entry_ibat_ua != -EINVAL) { + if (chip->dt.s3_entry_ibat_ua < 0) + chip->dt.s3_entry_ibat_ua = 0; + else if (chip->dt.s3_entry_ibat_ua > 155550) + chip->dt.s3_entry_ibat_ua = 155550; + + reg = chip->dt.s3_entry_ibat_ua / 610; + rc = qg_write(chip, chip->qg_base + + QG_S3_ENTRY_IBAT_THRESHOLD_REG, + ®, 1); + if (rc < 0) { + pr_err("Failed to write S3-entry ibat-uA, rc=%d\n", rc); + return rc; + } + } + + if (chip->dt.s3_exit_ibat_ua != -EINVAL) { + if (chip->dt.s3_exit_ibat_ua < 0) + chip->dt.s3_exit_ibat_ua = 0; + else if (chip->dt.s3_exit_ibat_ua > 155550) + chip->dt.s3_exit_ibat_ua = 155550; + + rc = qg_read(chip, chip->qg_base + + QG_S3_ENTRY_IBAT_THRESHOLD_REG, + ®, 1); + if (rc < 0) { + pr_err("Failed to read S3-entry ibat-uA, rc=%d\n", rc); + return rc; + } + temp = reg * 610; + if (chip->dt.s3_exit_ibat_ua < temp) + chip->dt.s3_exit_ibat_ua = temp; + else + chip->dt.s3_exit_ibat_ua -= temp; + + reg = chip->dt.s3_exit_ibat_ua / 610; + rc = qg_write(chip, + chip->qg_base + QG_S3_EXIT_IBAT_THRESHOLD_REG, + ®, 1); + if (rc < 0) { + pr_err("Failed to write S3-entry ibat-uA, rc=%d\n", rc); + return rc; + } + } + + /* vbat based configs */ + if (chip->dt.vbatt_low_mv < 0) + chip->dt.vbatt_low_mv = 0; + else if (chip->dt.vbatt_low_mv > 12750) + chip->dt.vbatt_low_mv = 12750; + + if (chip->dt.vbatt_empty_mv < 0) + chip->dt.vbatt_empty_mv = 0; + else if (chip->dt.vbatt_empty_mv > 12750) + chip->dt.vbatt_empty_mv = 12750; + + if (chip->dt.vbatt_empty_cold_mv < 0) + chip->dt.vbatt_empty_cold_mv = 0; + else if (chip->dt.vbatt_empty_cold_mv > 12750) + chip->dt.vbatt_empty_cold_mv = 12750; + + rc = qg_vbat_thresholds_config(chip); + if (rc < 0) { + pr_err("Failed to configure VBAT empty/low rc=%d\n", rc); + return rc; + } + + if (chip->qg_version != QG_LITE) { + /* disable S5 */ + rc = qg_masked_write(chip, chip->qg_base + + QG_S5_OCV_VALIDATE_MEAS_CTL1_REG, + ALLOW_S5_BIT, 0); + if (rc < 0) + pr_err("Failed to disable S5 rc=%d\n", rc); + } + + /* change PON OCV time to 512ms */ + rc = qg_masked_write(chip, chip->qg_base + + QG_S7_PON_OCV_MEAS_CTL1_REG, + ADC_CONV_DLY_MASK, + ADC_CONV_DLY_512MS); + if (rc < 0) + pr_err("Failed to reconfigure S7-delay rc=%d\n", rc); + + + return 0; +} + +static int qg_soh_batt_profile_init(struct qpnp_qg *chip) +{ + int rc = 0; + + if (!chip->dt.multi_profile_load) + return 0; + + if (is_debug_batt_id(chip) || chip->battery_missing) + return 0; + + if (!chip->sp) + chip->sp = devm_kzalloc(chip->dev, sizeof(*chip->sp), + GFP_KERNEL); + if (!chip->sp) + return -ENOMEM; + + if (!chip->sp->initialized) { + chip->sp->batt_id_kohms = chip->batt_id_ohm / 1000; + chip->sp->bp_node = chip->batt_node; + chip->sp->last_batt_age_level = chip->batt_age_level; + chip->sp->bms_psy = chip->qg_psy; + rc = soh_profile_init(chip->dev, chip->sp); + if (rc < 0) + chip->sp = NULL; + else + qg_dbg(chip, QG_DEBUG_PROFILE, "SOH profile count: %d\n", + chip->sp->profile_count); + } + + return rc; +} + +static int qg_post_init(struct qpnp_qg *chip) +{ + int rc = 0; + + /* disable all IRQs if profile is not loaded */ + if (!chip->profile_loaded) { + vote(chip->vbatt_irq_disable_votable, + PROFILE_IRQ_DISABLE, true, 0); + vote(chip->fifo_irq_disable_votable, + PROFILE_IRQ_DISABLE, true, 0); + vote(chip->good_ocv_irq_disable_votable, + PROFILE_IRQ_DISABLE, true, 0); + } + + /* restore ESR data */ + if (!chip->dt.esr_disable) + qg_retrieve_esr_params(chip); + + /*soh based multi profile init */ + rc = qg_soh_batt_profile_init(chip); + if (rc < 0) { + pr_err("Failed to initialize battery based on soh rc=%d\n", + rc); + return rc; + } + + return 0; +} + +static int qg_get_irq_index_byname(const char *irq_name) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(qg_irqs); i++) { + if (strcmp(qg_irqs[i].name, irq_name) == 0) + return i; + } + + return -ENOENT; +} + +static int qg_request_interrupt(struct qpnp_qg *chip, + struct device_node *node, const char *irq_name) +{ + int rc, irq, irq_index; + + irq = of_irq_get_byname(node, irq_name); + if (irq < 0) { + pr_err("Failed to get irq %s byname\n", irq_name); + return irq; + } + + irq_index = qg_get_irq_index_byname(irq_name); + if (irq_index < 0) { + pr_err("%s is not a defined irq\n", irq_name); + return irq_index; + } + + if (!qg_irqs[irq_index].handler) + return 0; + + rc = devm_request_threaded_irq(chip->dev, irq, NULL, + qg_irqs[irq_index].handler, + IRQF_ONESHOT, irq_name, chip); + if (rc < 0) { + pr_err("Failed to request irq %d\n", irq); + return rc; + } + + qg_irqs[irq_index].irq = irq; + if (qg_irqs[irq_index].wake) + enable_irq_wake(irq); + + qg_dbg(chip, QG_DEBUG_PON, "IRQ %s registered wakeable=%d\n", + qg_irqs[irq_index].name, qg_irqs[irq_index].wake); + + return 0; +} + +static int qg_request_irqs(struct qpnp_qg *chip) +{ + struct device_node *node = chip->dev->of_node; + struct device_node *child; + const char *name; + struct property *prop; + int rc = 0; + + for_each_available_child_of_node(node, child) { + of_property_for_each_string(child, "interrupt-names", + prop, name) { + rc = qg_request_interrupt(chip, child, name); + if (rc < 0) + return rc; + } + } + + + return 0; +} + +#define QG_TTF_ITERM_DELTA_MA 1 +static int qg_alg_init(struct qpnp_qg *chip) +{ + struct cycle_counter *counter; + struct cap_learning *cl; + struct ttf *ttf; + struct device_node *node = chip->dev->of_node; + int rc; + + counter = devm_kzalloc(chip->dev, sizeof(*counter), GFP_KERNEL); + if (!counter) + return -ENOMEM; + + counter->restore_count = qg_restore_cycle_count; + counter->store_count = qg_store_cycle_count; + counter->data = chip; + + rc = cycle_count_init(counter); + if (rc < 0) { + dev_err(chip->dev, "Error in initializing cycle counter, rc:%d\n", + rc); + counter->data = NULL; + return rc; + } + + chip->counter = counter; + + ttf = devm_kzalloc(chip->dev, sizeof(*ttf), GFP_KERNEL); + if (!ttf) + return -ENOMEM; + + ttf->get_ttf_param = qg_get_ttf_param; + ttf->awake_voter = qg_ttf_awake_voter; + ttf->iterm_delta = QG_TTF_ITERM_DELTA_MA; + ttf->data = chip; + + rc = ttf_tte_init(ttf); + if (rc < 0) { + dev_err(chip->dev, "Error in initializing ttf, rc:%d\n", + rc); + ttf->data = NULL; + counter->data = NULL; + return rc; + } + + chip->ttf = ttf; + + chip->dt.cl_disable = of_property_read_bool(node, + "qcom,cl-disable"); + + /*Return if capacity learning is disabled*/ + if (chip->dt.cl_disable) + return 0; + + cl = devm_kzalloc(chip->dev, sizeof(*cl), GFP_KERNEL); + if (!cl) + return -ENOMEM; + + cl->cc_soc_max = QG_SOC_FULL; + cl->get_cc_soc = qg_get_cc_soc; + cl->get_learned_capacity = qg_get_learned_capacity; + cl->store_learned_capacity = qg_store_learned_capacity; + cl->ok_to_begin = qg_cl_ok_to_begin; + cl->data = chip; + + rc = cap_learning_init(cl); + if (rc < 0) { + dev_err(chip->dev, "Error in initializing capacity learning, rc:%d\n", + rc); + counter->data = NULL; + cl->data = NULL; + return rc; + } + + chip->cl = cl; + return 0; +} + +#ifdef CONFIG_DEBUG_FS +static void qg_create_debugfs(struct qpnp_qg *chip) +{ + struct dentry *entry; + + chip->dfs_root = debugfs_create_dir("qgauge", NULL); + if (IS_ERR_OR_NULL(chip->dfs_root)) { + pr_err("Failed to create debugfs directory rc=%ld\n", + (long)chip->dfs_root); + return; + } + + entry = debugfs_create_u32("debug_mask", 0600, chip->dfs_root, + &qg_debug_mask); + if (IS_ERR_OR_NULL(entry)) { + pr_err("Failed to create debug_mask rc=%ld\n", (long)entry); + debugfs_remove_recursive(chip->dfs_root); + } +} +#else +static void qg_create_debugfs(struct qpnp_qg *chip) +{ +} +#endif + +#define DEFAULT_S2_FIFO_LENGTH 5 +#define DEFAULT_S2_VBAT_LOW_LENGTH 2 +#define DEFAULT_S2_ACC_LENGTH 128 +#define DEFAULT_S2_ACC_INTVL_MS 100 +#define DEFAULT_SLEEP_S2_FIFO_LENGTH 8 +#define DEFAULT_SLEEP_S2_ACC_LENGTH 256 +#define DEFAULT_SLEEP_S2_ACC_INTVL_MS 200 +#define DEFAULT_FAST_CHG_S2_FIFO_LENGTH 1 +static int qg_parse_s2_dt(struct qpnp_qg *chip) +{ + int rc; + struct device_node *node = chip->dev->of_node; + u32 temp; + + /* S2 state params */ + rc = of_property_read_u32(node, "qcom,s2-fifo-length", &temp); + if (rc < 0) + chip->dt.s2_fifo_length = DEFAULT_S2_FIFO_LENGTH; + else + chip->dt.s2_fifo_length = temp; + + if (chip->dt.s2_fifo_length > chip->max_fifo_length) { + pr_err("Invalid S2 fifo-length=%d max_length=%d\n", + chip->dt.s2_fifo_length, + chip->max_fifo_length); + return -EINVAL; + } + + rc = of_property_read_u32(node, "qcom,s2-vbat-low-fifo-length", &temp); + if (rc < 0) + chip->dt.s2_vbat_low_fifo_length = DEFAULT_S2_VBAT_LOW_LENGTH; + else + chip->dt.s2_vbat_low_fifo_length = temp; + + rc = of_property_read_u32(node, "qcom,s2-acc-length", &temp); + if (rc < 0) + chip->dt.s2_acc_length = DEFAULT_S2_ACC_LENGTH; + else + chip->dt.s2_acc_length = temp; + + rc = of_property_read_u32(node, "qcom,s2-acc-interval-ms", &temp); + if (rc < 0) + chip->dt.s2_acc_intvl_ms = DEFAULT_S2_ACC_INTVL_MS; + else + chip->dt.s2_acc_intvl_ms = temp; + + qg_dbg(chip, QG_DEBUG_PON, "DT: S2 FIFO length=%d low_vbat_length=%d acc_length=%d acc_interval=%d\n", + chip->dt.s2_fifo_length, chip->dt.s2_vbat_low_fifo_length, + chip->dt.s2_acc_length, chip->dt.s2_acc_intvl_ms); + + if (of_property_read_bool(node, "qcom,qg-sleep-config")) { + + chip->dt.qg_sleep_config = true; + + rc = of_property_read_u32(node, + "qcom,sleep-s2-fifo-length", &temp); + if (rc < 0) + chip->dt.sleep_s2_fifo_length = + DEFAULT_SLEEP_S2_FIFO_LENGTH; + else + chip->dt.sleep_s2_fifo_length = temp; + + if (chip->dt.s2_fifo_length > chip->max_fifo_length) { + pr_err("Invalid S2 sleep-fifo-length=%d max_length=%d\n", + chip->dt.sleep_s2_fifo_length, + chip->max_fifo_length); + return -EINVAL; + } + + rc = of_property_read_u32(node, + "qcom,sleep-s2-acc-length", &temp); + if (rc < 0) + chip->dt.sleep_s2_acc_length = + DEFAULT_SLEEP_S2_ACC_LENGTH; + else + chip->dt.sleep_s2_acc_length = temp; + + rc = of_property_read_u32(node, + "qcom,sleep-s2-acc-intvl-ms", &temp); + if (rc < 0) + chip->dt.sleep_s2_acc_intvl_ms = + DEFAULT_SLEEP_S2_ACC_INTVL_MS; + else + chip->dt.sleep_s2_acc_intvl_ms = temp; + } + + if (of_property_read_bool(node, "qcom,qg-fast-chg-config")) { + + chip->dt.qg_fast_chg_cfg = true; + + rc = of_property_read_u32(node, + "qcom,fast-chg-s2-fifo-length", &temp); + if (rc < 0) + chip->dt.fast_chg_s2_fifo_length = + DEFAULT_FAST_CHG_S2_FIFO_LENGTH; + else + chip->dt.fast_chg_s2_fifo_length = temp; + + if (chip->dt.fast_chg_s2_fifo_length > chip->max_fifo_length) { + pr_err("Invalid S2 fast-fifo-length=%d max_length=%d\n", + chip->dt.fast_chg_s2_fifo_length, + chip->max_fifo_length); + return -EINVAL; + } + } + + return 0; +} + +#define DEFAULT_CL_MIN_START_SOC 10 +#define DEFAULT_CL_MAX_START_SOC 15 +#define DEFAULT_CL_MIN_TEMP_DECIDEGC 150 +#define DEFAULT_CL_MAX_TEMP_DECIDEGC 500 +#define DEFAULT_CL_MAX_INC_DECIPERC 10 +#define DEFAULT_CL_MAX_DEC_DECIPERC 20 +#define DEFAULT_CL_MIN_LIM_DECIPERC 500 +#define DEFAULT_CL_MAX_LIM_DECIPERC 100 +#define DEFAULT_CL_DELTA_BATT_SOC 10 +#define DEFAULT_CL_WT_START_SOC 15 +static int qg_parse_cl_dt(struct qpnp_qg *chip) +{ + int rc; + struct device_node *node = chip->dev->of_node; + u32 temp; + + if (chip->dt.cl_disable) + return 0; + + chip->dt.cl_feedback_on = of_property_read_bool(node, + "qcom,cl-feedback-on"); + + rc = of_property_read_u32(node, "qcom,cl-min-start-soc", &temp); + if (rc < 0) + chip->cl->dt.min_start_soc = DEFAULT_CL_MIN_START_SOC; + else + chip->cl->dt.min_start_soc = temp; + + rc = of_property_read_u32(node, "qcom,cl-max-start-soc", &temp); + if (rc < 0) + chip->cl->dt.max_start_soc = DEFAULT_CL_MAX_START_SOC; + else + chip->cl->dt.max_start_soc = temp; + + rc = of_property_read_u32(node, "qcom,cl-min-temp", &temp); + if (rc < 0) + chip->cl->dt.min_temp = DEFAULT_CL_MIN_TEMP_DECIDEGC; + else + chip->cl->dt.min_temp = temp; + + rc = of_property_read_u32(node, "qcom,cl-max-temp", &temp); + if (rc < 0) + chip->cl->dt.max_temp = DEFAULT_CL_MAX_TEMP_DECIDEGC; + else + chip->cl->dt.max_temp = temp; + + rc = of_property_read_u32(node, "qcom,cl-max-increment", &temp); + if (rc < 0) + chip->cl->dt.max_cap_inc = DEFAULT_CL_MAX_INC_DECIPERC; + else + chip->cl->dt.max_cap_inc = temp; + + rc = of_property_read_u32(node, "qcom,cl-max-decrement", &temp); + if (rc < 0) + chip->cl->dt.max_cap_dec = DEFAULT_CL_MAX_DEC_DECIPERC; + else + chip->cl->dt.max_cap_dec = temp; + + rc = of_property_read_u32(node, "qcom,cl-min-limit", &temp); + if (rc < 0) + chip->cl->dt.min_cap_limit = + DEFAULT_CL_MIN_LIM_DECIPERC; + else + chip->cl->dt.min_cap_limit = temp; + + rc = of_property_read_u32(node, "qcom,cl-max-limit", &temp); + if (rc < 0) + chip->cl->dt.max_cap_limit = + DEFAULT_CL_MAX_LIM_DECIPERC; + else + chip->cl->dt.max_cap_limit = temp; + + chip->cl->dt.min_delta_batt_soc = DEFAULT_CL_DELTA_BATT_SOC; + /* read from DT property and update, if value exists */ + of_property_read_u32(node, "qcom,cl-min-delta-batt-soc", + &chip->cl->dt.min_delta_batt_soc); + + if (of_property_read_bool(node, "qcom,cl-wt-enable")) { + chip->cl->dt.cl_wt_enable = true; + chip->cl->dt.min_start_soc = DEFAULT_CL_WT_START_SOC; + chip->cl->dt.max_start_soc = -EINVAL; + } + + qg_dbg(chip, QG_DEBUG_PON, "DT: cl_min_start_soc=%d cl_max_start_soc=%d cl_min_temp=%d cl_max_temp=%d chip->cl->dt.cl_wt_enable=%d\n", + chip->cl->dt.min_start_soc, chip->cl->dt.max_start_soc, + chip->cl->dt.min_temp, chip->cl->dt.max_temp, + chip->cl->dt.cl_wt_enable); + + return 0; +} + +#define DEFAULT_VBATT_EMPTY_MV 3200 +#define DEFAULT_VBATT_EMPTY_COLD_MV 3000 +#define DEFAULT_VBATT_CUTOFF_MV 3400 +#define DEFAULT_VBATT_LOW_MV 3500 +#define DEFAULT_VBATT_LOW_COLD_MV 3800 +#define DEFAULT_ITERM_MA 100 +#define DEFAULT_DELTA_SOC 1 +#define DEFAULT_SHUTDOWN_SOC_SECS 360 +#define DEFAULT_COLD_TEMP_THRESHOLD 0 +#define DEFAULT_SHUTDOWN_TEMP_DIFF 60 /* 6 degC */ +#define DEFAULT_ESR_QUAL_CURRENT_UA 130000 +#define DEFAULT_ESR_QUAL_VBAT_UV 7000 +#define DEFAULT_ESR_DISABLE_SOC 1000 +#define ESR_CHG_MIN_IBAT_UA (-450000) +#define DEFAULT_SLEEP_TIME_SECS 1800 /* 30 mins */ +#define DEFAULT_SYS_MIN_VOLT_MV 2800 +#define DEFAULT_FVSS_VBAT_MV 3500 +#define DEFAULT_TCSS_ENTRY_SOC 90 +static int qg_parse_dt(struct qpnp_qg *chip) +{ + int rc = 0; + struct device_node *revid_node, *child, *node = chip->dev->of_node; + u32 base, temp; + u8 type; + + if (!node) { + pr_err("Failed to find device-tree node\n"); + return -ENXIO; + } + + revid_node = of_parse_phandle(node, "qcom,pmic-revid", 0); + if (!revid_node) { + pr_err("Missing qcom,pmic-revid property - driver failed\n"); + return -EINVAL; + } + + chip->pmic_rev_id = get_revid_data(revid_node); + of_node_put(revid_node); + if (IS_ERR_OR_NULL(chip->pmic_rev_id)) { + pr_err("Failed to get pmic_revid, rc=%ld\n", + PTR_ERR(chip->pmic_rev_id)); + /* + * the revid peripheral must be registered, any failure + * here only indicates that the rev-id module has not + * probed yet. + */ + return -EPROBE_DEFER; + } + + qg_dbg(chip, QG_DEBUG_PON, "PMIC subtype %d Digital major %d\n", + chip->pmic_rev_id->pmic_subtype, chip->pmic_rev_id->rev4); + + for_each_available_child_of_node(node, child) { + rc = of_property_read_u32(child, "reg", &base); + if (rc < 0) { + pr_err("Failed to read base address, rc=%d\n", rc); + return rc; + } + + rc = qg_read(chip, base + PERPH_TYPE_REG, &type, 1); + if (rc < 0) { + pr_err("Failed to read type, rc=%d\n", rc); + return rc; + } + + switch (type) { + case QG_TYPE: + chip->qg_base = base; + break; + default: + break; + } + } + + if (!chip->qg_base) { + pr_err("QG device node missing\n"); + return -EINVAL; + } + + rc = qg_parse_s2_dt(chip); + if (rc < 0) + pr_err("Failed to parse S2 DT params rc=%d\n", rc); + + rc = qg_parse_cl_dt(chip); + if (rc < 0) + pr_err("Failed to parse CL parameters rc=%d\n", rc); + + /* OCV params */ + rc = of_property_read_u32(node, "qcom,ocv-timer-expiry-min", &temp); + if (rc < 0) + chip->dt.ocv_timer_expiry_min = -EINVAL; + else + chip->dt.ocv_timer_expiry_min = temp; + + rc = of_property_read_u32(node, "qcom,ocv-tol-threshold-uv", &temp); + if (rc < 0) + chip->dt.ocv_tol_threshold_uv = -EINVAL; + else + chip->dt.ocv_tol_threshold_uv = temp; + + qg_dbg(chip, QG_DEBUG_PON, "DT: OCV timer_expiry =%dmin ocv_tol_threshold=%duV\n", + chip->dt.ocv_timer_expiry_min, chip->dt.ocv_tol_threshold_uv); + + /* S3 sleep configuration */ + rc = of_property_read_u32(node, "qcom,s3-entry-fifo-length", &temp); + if (rc < 0) + chip->dt.s3_entry_fifo_length = -EINVAL; + else + chip->dt.s3_entry_fifo_length = temp; + + rc = of_property_read_u32(node, "qcom,s3-entry-ibat-ua", &temp); + if (rc < 0) + chip->dt.s3_entry_ibat_ua = -EINVAL; + else + chip->dt.s3_entry_ibat_ua = temp; + + rc = of_property_read_u32(node, "qcom,s3-exit-ibat-ua", &temp); + if (rc < 0) + chip->dt.s3_exit_ibat_ua = -EINVAL; + else + chip->dt.s3_exit_ibat_ua = temp; + + /* VBAT thresholds */ + rc = of_property_read_u32(node, "qcom,vbatt-empty-mv", &temp); + if (rc < 0) + chip->dt.vbatt_empty_mv = DEFAULT_VBATT_EMPTY_MV; + else + chip->dt.vbatt_empty_mv = temp; + + rc = of_property_read_u32(node, "qcom,vbatt-empty-cold-mv", &temp); + if (rc < 0) + chip->dt.vbatt_empty_cold_mv = DEFAULT_VBATT_EMPTY_COLD_MV; + else + chip->dt.vbatt_empty_cold_mv = temp; + + rc = of_property_read_u32(node, "qcom,cold-temp-threshold", &temp); + if (rc < 0) + chip->dt.cold_temp_threshold = DEFAULT_COLD_TEMP_THRESHOLD; + else + chip->dt.cold_temp_threshold = temp; + + rc = of_property_read_u32(node, "qcom,vbatt-low-mv", &temp); + if (rc < 0) + chip->dt.vbatt_low_mv = DEFAULT_VBATT_LOW_MV; + else + chip->dt.vbatt_low_mv = temp; + + rc = of_property_read_u32(node, "qcom,vbatt-low-cold-mv", &temp); + if (rc < 0) + chip->dt.vbatt_low_cold_mv = DEFAULT_VBATT_LOW_COLD_MV; + else + chip->dt.vbatt_low_cold_mv = temp; + + rc = of_property_read_u32(node, "qcom,vbatt-cutoff-mv", &temp); + if (rc < 0) + chip->dt.vbatt_cutoff_mv = DEFAULT_VBATT_CUTOFF_MV; + else + chip->dt.vbatt_cutoff_mv = temp; + + /* IBAT thresholds */ + rc = of_property_read_u32(node, "qcom,qg-iterm-ma", &temp); + if (rc < 0) + chip->dt.iterm_ma = DEFAULT_ITERM_MA; + else + chip->dt.iterm_ma = temp; + + rc = of_property_read_u32(node, "qcom,delta-soc", &temp); + if (rc < 0) + chip->dt.delta_soc = DEFAULT_DELTA_SOC; + else + chip->dt.delta_soc = temp; + + rc = of_property_read_u32(node, "qcom,ignore-shutdown-soc-secs", &temp); + if (rc < 0) + chip->dt.ignore_shutdown_soc_secs = DEFAULT_SHUTDOWN_SOC_SECS; + else + chip->dt.ignore_shutdown_soc_secs = temp; + + rc = of_property_read_u32(node, "qcom,shutdown-temp-diff", &temp); + if (rc < 0) + chip->dt.shutdown_temp_diff = DEFAULT_SHUTDOWN_TEMP_DIFF; + else + chip->dt.shutdown_temp_diff = temp; + + chip->dt.hold_soc_while_full = of_property_read_bool(node, + "qcom,hold-soc-while-full"); + + chip->dt.linearize_soc = of_property_read_bool(node, + "qcom,linearize-soc"); + + rc = of_property_read_u32(node, "qcom,rbat-conn-mohm", &temp); + if (rc < 0) + chip->dt.rbat_conn_mohm = 0; + else + chip->dt.rbat_conn_mohm = temp; + + /* esr */ + chip->dt.esr_disable = of_property_read_bool(node, + "qcom,esr-disable"); + + chip->dt.esr_discharge_enable = of_property_read_bool(node, + "qcom,esr-discharge-enable"); + + rc = of_property_read_u32(node, "qcom,esr-qual-current-ua", &temp); + if (rc < 0) + chip->dt.esr_qual_i_ua = DEFAULT_ESR_QUAL_CURRENT_UA; + else + chip->dt.esr_qual_i_ua = temp; + + rc = of_property_read_u32(node, "qcom,esr-qual-vbatt-uv", &temp); + if (rc < 0) + chip->dt.esr_qual_v_uv = DEFAULT_ESR_QUAL_VBAT_UV; + else + chip->dt.esr_qual_v_uv = temp; + + rc = of_property_read_u32(node, "qcom,esr-disable-soc", &temp); + if (rc < 0) + chip->dt.esr_disable_soc = DEFAULT_ESR_DISABLE_SOC; + else + chip->dt.esr_disable_soc = temp * 100; + + rc = of_property_read_u32(node, "qcom,esr-chg-min-ibat-ua", &temp); + if (rc < 0) + chip->dt.esr_min_ibat_ua = ESR_CHG_MIN_IBAT_UA; + else + chip->dt.esr_min_ibat_ua = (int)temp; + + rc = of_property_read_u32(node, "qcom,shutdown_soc_threshold", &temp); + if (rc < 0) + chip->dt.shutdown_soc_threshold = -EINVAL; + else + chip->dt.shutdown_soc_threshold = temp; + + rc = of_property_read_u32(node, "qcom,qg-sys-min-voltage", &temp); + if (rc < 0) + chip->dt.sys_min_volt_mv = DEFAULT_SYS_MIN_VOLT_MV; + else + chip->dt.sys_min_volt_mv = temp; + + chip->dt.qg_ext_sense = of_property_read_bool(node, "qcom,qg-ext-sns"); + + chip->dt.use_s7_ocv = of_property_read_bool(node, "qcom,qg-use-s7-ocv"); + + rc = of_property_read_u32(node, "qcom,min-sleep-time-secs", &temp); + if (rc < 0) + chip->dt.min_sleep_time_secs = DEFAULT_SLEEP_TIME_SECS; + else + chip->dt.min_sleep_time_secs = temp; + + if (of_property_read_bool(node, "qcom,fvss-enable")) { + + chip->dt.fvss_enable = true; + + rc = of_property_read_u32(node, + "qcom,fvss-vbatt-mv", &temp); + if (rc < 0) + chip->dt.fvss_vbat_mv = DEFAULT_FVSS_VBAT_MV; + else + chip->dt.fvss_vbat_mv = temp; + } + + if (of_property_read_bool(node, "qcom,tcss-enable")) { + + chip->dt.tcss_enable = true; + + rc = of_property_read_u32(node, + "qcom,tcss-entry-soc", &temp); + if (rc < 0) + chip->dt.tcss_entry_soc = DEFAULT_TCSS_ENTRY_SOC; + else + chip->dt.tcss_entry_soc = temp; + } + + chip->dt.bass_enable = of_property_read_bool(node, "qcom,bass-enable"); + + chip->dt.multi_profile_load = of_property_read_bool(node, + "qcom,multi-profile-load"); + + qg_dbg(chip, QG_DEBUG_PON, "DT: vbatt_empty_mv=%dmV vbatt_low_mv=%dmV delta_soc=%d ext-sns=%d\n", + chip->dt.vbatt_empty_mv, chip->dt.vbatt_low_mv, + chip->dt.delta_soc, chip->dt.qg_ext_sense); + + return 0; +} + +static int process_suspend(struct qpnp_qg *chip) +{ + u8 status = 0; + int rc; + u32 fifo_rt_length = 0, sleep_fifo_length = 0; + + /* skip if profile is not loaded */ + if (!chip->profile_loaded) + return 0; + + cancel_delayed_work_sync(&chip->ttf->ttf_work); + + chip->suspend_data = false; + + /* read STATUS2 register to clear its last state */ + qg_read(chip, chip->qg_base + QG_STATUS2_REG, &status, 1); + + /* ignore any suspend processing if we are charging */ + if (chip->charge_status == POWER_SUPPLY_STATUS_CHARGING) { + /* Reset the sleep config if we are charging */ + if (chip->dt.qg_sleep_config) { + qg_dbg(chip, QG_DEBUG_STATUS, "Suspend: Charging - Exit S2_SLEEP\n"); + rc = qg_config_s2_state(chip, S2_SLEEP, false, true); + if (rc < 0) + pr_err("Failed to exit S2-sleep rc=%d\n", rc); + } + qg_dbg(chip, QG_DEBUG_PM, "Charging @ suspend - ignore processing\n"); + return 0; + } + + rc = get_fifo_length(chip, &fifo_rt_length, true); + if (rc < 0) { + pr_err("Failed to read FIFO RT count, rc=%d\n", rc); + return rc; + } + + rc = qg_read(chip, chip->qg_base + QG_S3_SLEEP_OCV_IBAT_CTL1_REG, + (u8 *)&sleep_fifo_length, 1); + if (rc < 0) { + pr_err("Failed to read sleep FIFO count, rc=%d\n", rc); + return rc; + } + sleep_fifo_length &= SLEEP_IBAT_QUALIFIED_LENGTH_MASK; + + if (chip->dt.qg_sleep_config) { + qg_dbg(chip, QG_DEBUG_STATUS, "Suspend: Forcing S2_SLEEP\n"); + rc = qg_config_s2_state(chip, S2_SLEEP, true, true); + if (rc < 0) + pr_err("Failed to config S2_SLEEP rc=%d\n", rc); + if (chip->kdata.fifo_length > 0) + chip->suspend_data = true; + } else if (fifo_rt_length >= + (chip->dt.s2_fifo_length - sleep_fifo_length)) { + /* + * If the real-time FIFO count is greater than + * the the #fifo to enter sleep, save the FIFO data + * and reset the fifo count. This is avoid a gauranteed wakeup + * due to fifo_done event as the curent FIFO length is already + * beyond the sleep length. + */ + rc = qg_master_hold(chip, true); + if (rc < 0) { + pr_err("Failed to hold master, rc=%d\n", rc); + return rc; + } + + rc = qg_process_rt_fifo(chip); + if (rc < 0) { + pr_err("Failed to process FIFO real-time, rc=%d\n", rc); + qg_master_hold(chip, false); + return rc; + } + + rc = qg_master_hold(chip, false); + if (rc < 0) { + pr_err("Failed to release master, rc=%d\n", rc); + return rc; + } + /* FIFOs restarted */ + chip->last_fifo_update_time = ktime_get_boottime(); + + chip->suspend_data = true; + } + + get_rtc_time(&chip->suspend_time); + + qg_dbg(chip, QG_DEBUG_PM, "FIFO rt_length=%d sleep_fifo_length=%d default_s2_count=%d suspend_data=%d time=%d\n", + fifo_rt_length, sleep_fifo_length, + chip->dt.s2_fifo_length, chip->suspend_data, + chip->suspend_time); + + return rc; +} + +#define QG_SLEEP_EXIT_TIME_MS 15000 /* 15 secs */ +static int process_resume(struct qpnp_qg *chip) +{ + u8 status2 = 0, rt_status = 0; + u32 ocv_uv = 0, ocv_raw = 0; + int rc; + unsigned long rtc_sec = 0, sleep_time_secs = 0; + + /* skip if profile is not loaded */ + if (!chip->profile_loaded) + return 0; + + get_rtc_time(&rtc_sec); + sleep_time_secs = rtc_sec - chip->suspend_time; + + if (chip->dt.qg_sleep_config) + schedule_delayed_work(&chip->qg_sleep_exit_work, + msecs_to_jiffies(QG_SLEEP_EXIT_TIME_MS)); + + rc = qg_read(chip, chip->qg_base + QG_STATUS2_REG, &status2, 1); + if (rc < 0) { + pr_err("Failed to read status2 register, rc=%d\n", rc); + return rc; + } + + if (status2 & GOOD_OCV_BIT) { + rc = qg_read_ocv(chip, &ocv_uv, &ocv_raw, S3_GOOD_OCV); + if (rc < 0) { + pr_err("Failed to read good_ocv, rc=%d\n", rc); + return rc; + } + + /* Clear suspend data as there has been a GOOD OCV */ + memset(&chip->kdata, 0, sizeof(chip->kdata)); + chip->kdata.fifo_time = (u32)rtc_sec; + chip->kdata.param[QG_GOOD_OCV_UV].data = ocv_uv; + chip->kdata.param[QG_GOOD_OCV_UV].valid = true; + chip->suspend_data = false; + + /* allow SOC jump if we have slept longer */ + if (sleep_time_secs >= chip->dt.min_sleep_time_secs) + chip->force_soc = true; + + qg_dbg(chip, QG_DEBUG_PM, "GOOD OCV @ resume good_ocv=%d uV\n", + ocv_uv); + } + + rc = qg_read(chip, chip->qg_base + QG_INT_LATCHED_STS_REG, + &rt_status, 1); + if (rc < 0) { + pr_err("Failed to read latched status register, rc=%d\n", rc); + return rc; + } + rt_status &= FIFO_UPDATE_DONE_INT_LAT_STS_BIT; + + qg_dbg(chip, QG_DEBUG_PM, "FIFO_DONE_STS=%d suspend_data=%d good_ocv=%d sleep_time=%d secs\n", + !!rt_status, chip->suspend_data, + chip->kdata.param[QG_GOOD_OCV_UV].valid, + sleep_time_secs); + /* + * If this is not a wakeup from FIFO-done, + * process the data immediately if - we have data from + * suspend or there is a good OCV. + */ + if (!rt_status && (chip->suspend_data || + chip->kdata.param[QG_GOOD_OCV_UV].valid)) { + vote(chip->awake_votable, SUSPEND_DATA_VOTER, true, 0); + /* signal the read thread */ + chip->data_ready = true; + wake_up_interruptible(&chip->qg_wait_q); + chip->suspend_data = false; + } + + schedule_delayed_work(&chip->ttf->ttf_work, 0); + + return rc; +} + +static int qpnp_qg_suspend_noirq(struct device *dev) +{ + int rc; + struct qpnp_qg *chip = dev_get_drvdata(dev); + + /* cancel any pending sleep_exit work */ + cancel_delayed_work_sync(&chip->qg_sleep_exit_work); + + mutex_lock(&chip->data_lock); + + rc = process_suspend(chip); + if (rc < 0) + pr_err("Failed to process QG suspend, rc=%d\n", rc); + + mutex_unlock(&chip->data_lock); + + return 0; +} + +static int qpnp_qg_resume_noirq(struct device *dev) +{ + int rc; + struct qpnp_qg *chip = dev_get_drvdata(dev); + + mutex_lock(&chip->data_lock); + + rc = process_resume(chip); + if (rc < 0) + pr_err("Failed to process QG resume, rc=%d\n", rc); + + mutex_unlock(&chip->data_lock); + + return 0; +} + +static int qpnp_qg_suspend(struct device *dev) +{ + struct qpnp_qg *chip = dev_get_drvdata(dev); + + /* skip if profile is not loaded */ + if (!chip->profile_loaded) + return 0; + + /* disable GOOD_OCV IRQ in sleep */ + vote(chip->good_ocv_irq_disable_votable, + QG_INIT_STATE_IRQ_DISABLE, true, 0); + + return 0; +} + +static int qpnp_qg_resume(struct device *dev) +{ + struct qpnp_qg *chip = dev_get_drvdata(dev); + + /* skip if profile is not loaded */ + if (!chip->profile_loaded) + return 0; + + /* enable GOOD_OCV IRQ when active */ + vote(chip->good_ocv_irq_disable_votable, + QG_INIT_STATE_IRQ_DISABLE, false, 0); + + return 0; +} + +static const struct dev_pm_ops qpnp_qg_pm_ops = { + .suspend_noirq = qpnp_qg_suspend_noirq, + .resume_noirq = qpnp_qg_resume_noirq, + .suspend = qpnp_qg_suspend, + .resume = qpnp_qg_resume, +}; + +static int qpnp_qg_probe(struct platform_device *pdev) +{ + int rc = 0, soc = 0, nom_cap_uah; + struct qpnp_qg *chip; + + chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->regmap = dev_get_regmap(pdev->dev.parent, NULL); + if (!chip->regmap) { + pr_err("Parent regmap is unavailable\n"); + return -ENXIO; + } + + /* ADC for BID & THERM */ + chip->batt_id_chan = iio_channel_get(&pdev->dev, "batt-id"); + if (IS_ERR(chip->batt_id_chan)) { + rc = PTR_ERR(chip->batt_id_chan); + if (rc != -EPROBE_DEFER) + pr_err("batt-id channel unavailable, rc=%d\n", rc); + chip->batt_id_chan = NULL; + return rc; + } + + chip->batt_therm_chan = iio_channel_get(&pdev->dev, "batt-therm"); + if (IS_ERR(chip->batt_therm_chan)) { + rc = PTR_ERR(chip->batt_therm_chan); + if (rc != -EPROBE_DEFER) + pr_err("batt-therm channel unavailable, rc=%d\n", rc); + chip->batt_therm_chan = NULL; + return rc; + } + + chip->dev = &pdev->dev; + chip->debug_mask = &qg_debug_mask; + platform_set_drvdata(pdev, chip); + INIT_WORK(&chip->udata_work, process_udata_work); + INIT_WORK(&chip->qg_status_change_work, qg_status_change_work); + INIT_DELAYED_WORK(&chip->qg_sleep_exit_work, qg_sleep_exit_work); + mutex_init(&chip->bus_lock); + mutex_init(&chip->soc_lock); + mutex_init(&chip->data_lock); + init_waitqueue_head(&chip->qg_wait_q); + chip->maint_soc = -EINVAL; + chip->batt_soc = INT_MIN; + chip->cc_soc = INT_MIN; + chip->sys_soc = INT_MIN; + chip->full_soc = QG_SOC_FULL; + chip->chg_iterm_ma = INT_MIN; + chip->soh = -EINVAL; + chip->esr_actual = -EINVAL; + chip->esr_nominal = -EINVAL; + chip->batt_age_level = -EINVAL; + + chip->qg_version = (u8)of_device_get_match_data(&pdev->dev); + + switch (chip->qg_version) { + case QG_LITE: + chip->max_fifo_length = 5; + break; + default: + chip->max_fifo_length = 8; + break; + } + + qg_create_debugfs(chip); + + rc = qg_alg_init(chip); + if (rc < 0) { + pr_err("Error in alg_init, rc:%d\n", rc); + return rc; + } + + rc = qg_parse_dt(chip); + if (rc < 0) { + pr_err("Failed to parse DT, rc=%d\n", rc); + return rc; + } + + rc = qg_hw_init(chip); + if (rc < 0) { + pr_err("Failed to hw_init, rc=%d\n", rc); + return rc; + } + + rc = qg_sdam_init(chip->dev); + if (rc < 0) { + pr_err("Failed to initialize QG SDAM, rc=%d\n", rc); + return rc; + } + + rc = qg_setup_battery(chip); + if (rc < 0) { + pr_err("Failed to setup battery, rc=%d\n", rc); + return rc; + } + + rc = qg_register_device(chip); + if (rc < 0) { + pr_err("Failed to register QG char device, rc=%d\n", rc); + return rc; + } + + rc = qg_sanitize_sdam(chip); + if (rc < 0) { + pr_err("Failed to sanitize SDAM, rc=%d\n", rc); + return rc; + } + + rc = qg_soc_init(chip); + if (rc < 0) { + pr_err("Failed to initialize SOC scaling init rc=%d\n", rc); + return rc; + } + + if (chip->profile_loaded) { + if (!chip->dt.cl_disable) { + /* + * Use FCC @ 25 C and charge-profile for + * Nominal Capacity + */ + rc = qg_get_nominal_capacity(&nom_cap_uah, 250, true); + if (!rc) { + rc = cap_learning_post_profile_init(chip->cl, + nom_cap_uah); + if (rc < 0) { + pr_err("Error in cap_learning_post_profile_init rc=%d\n", + rc); + return rc; + } + } + } + rc = restore_cycle_count(chip->counter); + if (rc < 0) { + pr_err("Error in restoring cycle_count, rc=%d\n", rc); + return rc; + } + schedule_delayed_work(&chip->ttf->ttf_work, 10000); + } + + rc = qg_determine_pon_soc(chip); + if (rc < 0) { + pr_err("Failed to determine initial state, rc=%d\n", rc); + goto fail_device; + } + + chip->awake_votable = create_votable("QG_WS", VOTE_SET_ANY, + qg_awake_cb, chip); + if (IS_ERR(chip->awake_votable)) { + rc = PTR_ERR(chip->awake_votable); + chip->awake_votable = NULL; + goto fail_device; + } + + chip->vbatt_irq_disable_votable = create_votable("QG_VBATT_IRQ_DISABLE", + VOTE_SET_ANY, qg_vbatt_irq_disable_cb, chip); + if (IS_ERR(chip->vbatt_irq_disable_votable)) { + rc = PTR_ERR(chip->vbatt_irq_disable_votable); + chip->vbatt_irq_disable_votable = NULL; + goto fail_device; + } + + chip->fifo_irq_disable_votable = create_votable("QG_FIFO_IRQ_DISABLE", + VOTE_SET_ANY, qg_fifo_irq_disable_cb, chip); + if (IS_ERR(chip->fifo_irq_disable_votable)) { + rc = PTR_ERR(chip->fifo_irq_disable_votable); + chip->fifo_irq_disable_votable = NULL; + goto fail_device; + } + + chip->good_ocv_irq_disable_votable = + create_votable("QG_GOOD_IRQ_DISABLE", + VOTE_SET_ANY, qg_good_ocv_irq_disable_cb, chip); + if (IS_ERR(chip->good_ocv_irq_disable_votable)) { + rc = PTR_ERR(chip->good_ocv_irq_disable_votable); + chip->good_ocv_irq_disable_votable = NULL; + goto fail_device; + } + + rc = qg_init_psy(chip); + if (rc < 0) { + pr_err("Failed to initialize QG psy, rc=%d\n", rc); + goto fail_votable; + } + + rc = qg_request_irqs(chip); + if (rc < 0) { + pr_err("Failed to register QG interrupts, rc=%d\n", rc); + goto fail_votable; + } + + rc = qg_post_init(chip); + if (rc < 0) { + pr_err("Failed in qg_post_init rc=%d\n", rc); + goto fail_votable; + } + + rc = sysfs_create_groups(&chip->dev->kobj, qg_groups); + if (rc < 0) { + pr_err("Failed to create sysfs files rc=%d\n", rc); + goto fail_votable; + } + + qg_get_battery_capacity(chip, &soc); + + pr_info("QG initialized! battery_profile=%s SOC=%d QG_subtype=%d QG_version=%s QG_mode=%s\n", + qg_get_battery_type(chip), soc, chip->qg_subtype, + (chip->qg_version == QG_LITE) ? "QG_LITE" : "QG_PMIC5", + (chip->qg_mode == QG_V_I_MODE) ? "QG_V_I" : "QG_V"); + + return rc; + +fail_votable: + destroy_votable(chip->awake_votable); +fail_device: + device_destroy(chip->qg_class, chip->dev_no); + cdev_del(&chip->qg_cdev); + unregister_chrdev_region(chip->dev_no, 1); + return rc; +} + +static int qpnp_qg_remove(struct platform_device *pdev) +{ + struct qpnp_qg *chip = platform_get_drvdata(pdev); + + qg_batterydata_exit(); + qg_soc_exit(chip); + + cancel_delayed_work_sync(&chip->qg_sleep_exit_work); + cancel_work_sync(&chip->udata_work); + cancel_work_sync(&chip->qg_status_change_work); + sysfs_remove_groups(&chip->dev->kobj, qg_groups); + debugfs_remove_recursive(chip->dfs_root); + device_destroy(chip->qg_class, chip->dev_no); + cdev_del(&chip->qg_cdev); + unregister_chrdev_region(chip->dev_no, 1); + mutex_destroy(&chip->bus_lock); + mutex_destroy(&chip->data_lock); + mutex_destroy(&chip->soc_lock); + if (chip->awake_votable) + destroy_votable(chip->awake_votable); + + return 0; +} + +static void qpnp_qg_shutdown(struct platform_device *pdev) +{ + struct qpnp_qg *chip = platform_get_drvdata(pdev); + bool input_present = is_input_present(chip); + + if (!input_present || !chip->profile_loaded) + return; + /* + * Charging status doesn't matter when the device shuts down and we + * have to treat this as charge done. Hence pass charge_done as true. + */ + cycle_count_update(chip->counter, + DIV_ROUND_CLOSEST(chip->msoc * 255, 100), + POWER_SUPPLY_STATUS_NOT_CHARGING, + true, input_present); +} + +static const struct of_device_id match_table[] = { + { .compatible = "qcom,qpnp-qg", .data = (void *)QG_PMIC5, }, + { .compatible = "qcom,qpnp-qg-lite", .data = (void *)QG_LITE, }, + { }, +}; + +static struct platform_driver qpnp_qg_driver = { + .driver = { + .name = "qcom,qpnp-qg", + .of_match_table = match_table, + .pm = &qpnp_qg_pm_ops, + }, + .probe = qpnp_qg_probe, + .remove = qpnp_qg_remove, + .shutdown = qpnp_qg_shutdown, +}; +module_platform_driver(qpnp_qg_driver); + +MODULE_DESCRIPTION("QPNP QG Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/gen_headers_arm.bp b/gen_headers_arm.bp index 9a44b6c393db..12e3b33055e2 100644 --- a/gen_headers_arm.bp +++ b/gen_headers_arm.bp @@ -470,6 +470,8 @@ gen_headers_out_arm = [ "linux/qcedev.h", "linux/qcota.h", "linux/qemu_fw_cfg.h", + "linux/qg.h", + "linux/qg-profile.h", "linux/qnx4_fs.h", "linux/qnxtypes.h", "linux/qrng.h", diff --git a/gen_headers_arm64.bp b/gen_headers_arm64.bp index 9e5af918a728..d985b4f885dd 100644 --- a/gen_headers_arm64.bp +++ b/gen_headers_arm64.bp @@ -465,6 +465,8 @@ gen_headers_out_arm64 = [ "linux/qcedev.h", "linux/qcota.h", "linux/qemu_fw_cfg.h", + "linux/qg.h", + "linux/qg-profile.h", "linux/qnx4_fs.h", "linux/qnxtypes.h", "linux/qrng.h", diff --git a/include/linux/pmic-voter.h b/include/linux/pmic-voter.h new file mode 100644 index 000000000000..d1202928ddd8 --- /dev/null +++ b/include/linux/pmic-voter.h @@ -0,0 +1,47 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2016-2020 The Linux Foundation. All rights reserved. + */ + +#ifndef __PMIC_VOTER_H +#define __PMIC_VOTER_H + +#include + +struct votable; + +enum votable_type { + VOTE_MIN, + VOTE_MAX, + VOTE_SET_ANY, + NUM_VOTABLE_TYPES, +}; + +bool is_client_vote_enabled(struct votable *votable, const char *client_str); +bool is_client_vote_enabled_locked(struct votable *votable, + const char *client_str); +bool is_override_vote_enabled(struct votable *votable); +bool is_override_vote_enabled_locked(struct votable *votable); +int get_client_vote(struct votable *votable, const char *client_str); +int get_client_vote_locked(struct votable *votable, const char *client_str); +int get_effective_result(struct votable *votable); +int get_effective_result_locked(struct votable *votable); +const char *get_effective_client(struct votable *votable); +const char *get_effective_client_locked(struct votable *votable); +int vote(struct votable *votable, const char *client_str, bool state, int val); +int vote_override(struct votable *votable, const char *override_client, + bool state, int val); +int rerun_election(struct votable *votable); +struct votable *find_votable(const char *name); +struct votable *create_votable(const char *name, + int votable_type, + int (*callback)(struct votable *votable, + void *data, + int effective_result, + const char *effective_client), + void *data); +void destroy_votable(struct votable *votable); +void lock_votable(struct votable *votable); +void unlock_votable(struct votable *votable); + +#endif /* __PMIC_VOTER_H */ diff --git a/include/uapi/linux/qg-profile.h b/include/uapi/linux/qg-profile.h new file mode 100644 index 000000000000..e8c652bb5c57 --- /dev/null +++ b/include/uapi/linux/qg-profile.h @@ -0,0 +1,73 @@ +/* SPDX-License-Identifier: GPL-2.0-only WITH Linux-syscall-note */ +/* + * Copyright (c) 2018-2020, The Linux Foundation. All rights reserved. + */ + +#ifndef __QG_PROFILE_H__ +#define __QG_PROFILE_H__ + +#include + +/** + * enum profile_table - Table index for battery profile data + */ +enum profile_table { + TABLE_SOC_OCV1, + TABLE_SOC_OCV2, + TABLE_FCC1, + TABLE_FCC2, + TABLE_Z1, + TABLE_Z2, + TABLE_Z3, + TABLE_Z4, + TABLE_Z5, + TABLE_Z6, + TABLE_Y1, + TABLE_Y2, + TABLE_Y3, + TABLE_Y4, + TABLE_Y5, + TABLE_Y6, + TABLE_MAX, +}; + +/** + * struct battery_params - Battery profile data to be exchanged + * @soc: SOC (state of charge) of the battery + * @ocv_uv: OCV (open circuit voltage) of the battery + * @batt_temp: Battery temperature in deci-degree + * @var: 'X' axis param for interpolation + * @table_index:Table index to be used for interpolation + */ +struct battery_params { + int soc; + int ocv_uv; + int fcc_mah; + int slope; + int var; + int batt_temp; + int table_index; +}; + +/* Profile MIN / MAX values */ +#define QG_MIN_SOC 0 +#define QG_MAX_SOC 10000 +#define QG_MIN_OCV_UV 2000000 +#define QG_MAX_OCV_UV 5000000 +#define QG_MIN_VAR 0 +#define QG_MAX_VAR 65535 +#define QG_MIN_FCC_MAH 100 +#define QG_MAX_FCC_MAH 16000 +#define QG_MIN_SLOPE 1 +#define QG_MAX_SLOPE 50000 +#define QG_ESR_SF_MIN 5000 +#define QG_ESR_SF_MAX 20000 + +/* IOCTLs to query battery profile data */ +#define BPIOCXSOC _IOWR('B', 0x01, struct battery_params) /* SOC */ +#define BPIOCXOCV _IOWR('B', 0x02, struct battery_params) /* OCV */ +#define BPIOCXFCC _IOWR('B', 0x03, struct battery_params) /* FCC */ +#define BPIOCXSLOPE _IOWR('B', 0x04, struct battery_params) /* Slope */ +#define BPIOCXVAR _IOWR('B', 0x05, struct battery_params) /* All-other */ + +#endif /* __QG_PROFILE_H__ */ diff --git a/include/uapi/linux/qg.h b/include/uapi/linux/qg.h new file mode 100644 index 000000000000..dfb1f2e71111 --- /dev/null +++ b/include/uapi/linux/qg.h @@ -0,0 +1,68 @@ +/* SPDX-License-Identifier: GPL-2.0-only WITH Linux-syscall-note */ +/* + * Copyright (c) 2018, 2020, The Linux Foundation. All rights reserved. + */ + +#ifndef __QG_H__ +#define __QG_H__ + +#define MAX_FIFO_LENGTH 16 + +enum qg { + QG_SOC, + QG_OCV_UV, + QG_RBAT_MOHM, + QG_PON_OCV_UV, + QG_GOOD_OCV_UV, + QG_ESR, + QG_CHARGE_COUNTER, + QG_FIFO_TIME_DELTA, + QG_BATT_SOC, + QG_CC_SOC, + QG_ESR_CHARGE_DELTA, + QG_ESR_DISCHARGE_DELTA, + QG_ESR_CHARGE_SF, + QG_ESR_DISCHARGE_SF, + QG_FULL_SOC, + QG_CLEAR_LEARNT_DATA, + QG_SYS_SOC, + QG_V_IBAT, + QG_MAX, +}; + +#define QG_BATT_SOC QG_BATT_SOC +#define QG_CC_SOC QG_CC_SOC +#define QG_ESR_CHARGE_DELTA QG_ESR_CHARGE_DELTA +#define QG_ESR_DISCHARGE_DELTA QG_ESR_DISCHARGE_DELTA +#define QG_ESR_CHARGE_SF QG_ESR_CHARGE_SF +#define QG_ESR_DISCHARGE_SF QG_ESR_DISCHARGE_SF +#define QG_FULL_SOC QG_FULL_SOC +#define QG_CLEAR_LEARNT_DATA QG_CLEAR_LEARNT_DATA +#define QG_SYS_SOC QG_SYS_SOC +#define QG_V_IBAT QG_V_IBAT + +struct fifo_data { + unsigned int v; + unsigned int i; + unsigned int count; + unsigned int interval; +}; + +struct qg_param { + unsigned int data; + bool valid; +}; + +struct qg_kernel_data { + unsigned int seq_no; + unsigned int fifo_time; + unsigned int fifo_length; + struct fifo_data fifo[MAX_FIFO_LENGTH]; + struct qg_param param[QG_MAX]; +}; + +struct qg_user_data { + struct qg_param param[QG_MAX]; +}; + +#endif