power: qpnp-bms: implement workarounds for coupling issue
On certain PMICs there is a coupling issue which causes bad calibration
values that reports a high current value even when the BATFET is open.
This coupling issue happens regardless of an internal/external rsense
configuration.
The suggested workaround is to repeatedly calibrate until we get values
that result in less than 3mA of current.
Expose the BATFET open/closed as battery's ONLINE property. It is
reasonable use of this property since PRESENT suggests a battery is
actually present in the device and ONLINE suggests if it is connected to
the system via BATFET.
The BATFET can open when a charger is present and
* the battery is fully charged
* the battery is hot/cold
(Note that if charger is disabled the buck will not operate
and we don't have coupling issue)
The fully charged notifications trickle down to BMS driver,
since battery psy supplies to "bms", use it to check if the
BATFET has opened.
The hot/cold interrupt is not yet registered to, add it so we can notify
the BMS driver if the battery goes hot or cold. Since the battery can
go hot or cold while the device is sleeping, we need to wakeup for
calibration workaround.
The BATFET can close when
* the charger is removed
* the battery recovers form hot/cold conditions when charger is present
These notifications too trickle down to the BMS driver and can be used to
check if the BATFET has closed.
Also once good calibration values are detected above use them until BATFET
closes i.e. do NOT calibrate until the BATFET closes. Update the code
that periodically calculates SOC to skip calibrations as long as BATFET
is open. On the same lines ask the iadc driver to skip auto calibrations
until BATFET closes.
CRs-Fixed: 509620
Change-Id: I89b521368304d7171d75e462cf15f2f1daa55cdc
Signed-off-by: Abhijeet Dharmapurikar <adharmap@codeaurora.org>
diff --git a/drivers/hwmon/qpnp-adc-current.c b/drivers/hwmon/qpnp-adc-current.c
index f0793b2..a453159 100644
--- a/drivers/hwmon/qpnp-adc-current.c
+++ b/drivers/hwmon/qpnp-adc-current.c
@@ -146,9 +146,10 @@
bool iadc_mode_sel;
struct qpnp_iadc_comp iadc_comp;
struct sensor_device_attribute sens_attr[0];
+ bool skip_auto_calibrations;
};
-struct qpnp_iadc_drv *qpnp_iadc;
+static struct qpnp_iadc_drv *qpnp_iadc;
static int32_t qpnp_iadc_read_reg(uint32_t reg, u8 *data)
{
@@ -635,13 +636,15 @@
struct qpnp_iadc_drv *iadc = qpnp_iadc;
int rc = 0;
- rc = qpnp_iadc_calibrate_for_trim(true);
- if (rc)
- pr_debug("periodic IADC calibration failed\n");
- else
- schedule_delayed_work(&iadc->iadc_work,
- round_jiffies_relative(msecs_to_jiffies
- (QPNP_IADC_CALIB_SECONDS)));
+ if (!iadc->skip_auto_calibrations) {
+ rc = qpnp_iadc_calibrate_for_trim(true);
+ if (rc)
+ pr_debug("periodic IADC calibration failed\n");
+ }
+
+ schedule_delayed_work(&iadc->iadc_work,
+ round_jiffies_relative(msecs_to_jiffies
+ (QPNP_IADC_CALIB_SECONDS)));
return;
}
@@ -731,9 +734,11 @@
if (die_temp_offset > QPNP_IADC_DIE_TEMP_CALIB_OFFSET) {
iadc->die_temp = result_pmic_therm.physical;
- rc = qpnp_iadc_calibrate_for_trim(true);
- if (rc)
- pr_err("periodic IADC calibration failed\n");
+ if (!iadc->skip_auto_calibrations) {
+ rc = qpnp_iadc_calibrate_for_trim(true);
+ if (rc)
+ pr_err("IADC calibration failed rc = %d\n", rc);
+ }
}
return rc;
@@ -833,6 +838,30 @@
}
EXPORT_SYMBOL(qpnp_iadc_get_gain_and_offset);
+int qpnp_iadc_skip_calibration(void)
+{
+ struct qpnp_iadc_drv *iadc = qpnp_iadc;
+
+ if (!iadc || !iadc->iadc_initialized)
+ return -EPROBE_DEFER;
+
+ iadc->skip_auto_calibrations = true;
+ return 0;
+}
+EXPORT_SYMBOL(qpnp_iadc_skip_calibration);
+
+int qpnp_iadc_resume_calibration(void)
+{
+ struct qpnp_iadc_drv *iadc = qpnp_iadc;
+
+ if (!iadc || !iadc->iadc_initialized)
+ return -EPROBE_DEFER;
+
+ iadc->skip_auto_calibrations = false;
+ return 0;
+}
+EXPORT_SYMBOL(qpnp_iadc_resume_calibration);
+
int32_t qpnp_iadc_vadc_sync_read(
enum qpnp_iadc_channels i_channel, struct qpnp_iadc_result *i_result,
enum qpnp_vadc_channels v_channel, struct qpnp_vadc_result *v_result)
diff --git a/drivers/power/qpnp-bms.c b/drivers/power/qpnp-bms.c
index aac0fa2..18da5a6 100644
--- a/drivers/power/qpnp-bms.c
+++ b/drivers/power/qpnp-bms.c
@@ -47,6 +47,7 @@
#define BMS1_S1_DELAY_CTL 0x5A
/* OCV interrupt threshold */
#define BMS1_OCV_THR0 0x50
+#define BMS1_S2_SAMP_AVG_CTL 0x61
/* SW CC interrupt threshold */
#define BMS1_SW_CC_THR0 0xA0
/* OCV for r registers */
@@ -71,6 +72,7 @@
#define CHARGE_CYCLE_STORAGE_LSB 0xBE /* LSB=0xBE, MSB=0xBF */
/* IADC Channel Select */
+#define IADC1_BMS_REVISION2 0x01
#define IADC1_BMS_ADC_CH_SEL_CTL 0x48
#define IADC1_BMS_ADC_INT_RSNSN_CTL 0x49
#define IADC1_BMS_FAST_AVG_EN 0x5B
@@ -152,6 +154,7 @@
int battery_present;
int battery_status;
+ bool batfet_closed;
bool new_battery;
bool done_charging;
bool last_soc_invalid;
@@ -176,6 +179,7 @@
struct delayed_work calculate_soc_delayed_work;
struct work_struct recalc_work;
+ struct work_struct batfet_open_work;
struct mutex bms_output_lock;
struct mutex last_ocv_uv_mutex;
@@ -745,6 +749,11 @@
return get_battery_status(chip) == POWER_SUPPLY_STATUS_CHARGING;
}
+static bool is_battery_full(struct qpnp_bms_chip *chip)
+{
+ return get_battery_status(chip) == POWER_SUPPLY_STATUS_FULL;
+}
+
static bool is_battery_present(struct qpnp_bms_chip *chip)
{
union power_supply_propval ret = {0,};
@@ -763,9 +772,22 @@
return false;
}
-static bool is_battery_full(struct qpnp_bms_chip *chip)
+static bool is_batfet_closed(struct qpnp_bms_chip *chip)
{
- return get_battery_status(chip) == POWER_SUPPLY_STATUS_FULL;
+ union power_supply_propval ret = {0,};
+
+ if (chip->batt_psy == NULL)
+ chip->batt_psy = power_supply_get_by_name("battery");
+ if (chip->batt_psy) {
+ /* if battery has been registered, use the online property */
+ chip->batt_psy->get_property(chip->batt_psy,
+ POWER_SUPPLY_PROP_ONLINE, &ret);
+ return !!ret.intval;
+ }
+
+ /* Default to true if the battery power supply is not registered. */
+ pr_debug("battery power supply is not registered\n");
+ return true;
}
static int get_simultaneous_batt_v_and_i(struct qpnp_bms_chip *chip,
@@ -2333,7 +2355,8 @@
if (chip->use_voltage_soc) {
soc = calculate_soc_from_voltage(chip);
} else {
- qpnp_iadc_calibrate_for_trim(true);
+ if (!chip->batfet_closed)
+ qpnp_iadc_calibrate_for_trim(true);
rc = qpnp_vadc_read(LR_MUX1_BATT_THERM, &result);
if (rc) {
pr_err("error reading vadc LR_MUX1_BATT_THERM = %d, rc = %d\n",
@@ -2904,9 +2927,55 @@
}
}
+#define MAX_CAL_TRIES 200
+#define MIN_CAL_UA 3000
+static void batfet_open_work(struct work_struct *work)
+{
+ int i;
+ int rc;
+ int result_ua;
+ u8 orig_delay, sample_delay;
+ struct qpnp_bms_chip *chip = container_of(work,
+ struct qpnp_bms_chip,
+ batfet_open_work);
+
+ rc = qpnp_read_wrapper(chip, &orig_delay,
+ chip->base + BMS1_S1_DELAY_CTL, 1);
+
+ sample_delay = 0x0;
+ rc = qpnp_write_wrapper(chip, &sample_delay,
+ chip->base + BMS1_S1_DELAY_CTL, 1);
+
+ /*
+ * In certain PMICs there is a coupling issue which causes
+ * bad calibration value that result in a huge battery current
+ * even when the BATFET is open. Do continious calibrations until
+ * we hit reasonable cal values which result in low battery current
+ */
+
+ for (i = 0; (!chip->batfet_closed) && i < MAX_CAL_TRIES; i++) {
+ rc = qpnp_iadc_calibrate_for_trim(false);
+ /*
+ * Wait 20mS after calibration and before reading battery
+ * current. The BMS h/w uses calibration values in the
+ * next sampling of vsense.
+ */
+ msleep(20);
+ rc |= get_battery_current(chip, &result_ua);
+ if (rc == 0 && abs(result_ua) <= MIN_CAL_UA) {
+ pr_debug("good cal at %d attempt\n", i);
+ break;
+ }
+ }
+ pr_debug("batfet_closed = %d i = %d result_ua = %d\n",
+ chip->batfet_closed, i, result_ua);
+
+ rc = qpnp_write_wrapper(chip, &orig_delay,
+ chip->base + BMS1_S1_DELAY_CTL, 1);
+}
+
static void charging_began(struct qpnp_bms_chip *chip)
{
-
mutex_lock(&chip->last_soc_mutex);
chip->charge_start_tm_sec = 0;
chip->catch_up_time_sec = 0;
@@ -2997,6 +3066,27 @@
}
#define CALIB_WRKARND_DIG_MAJOR_MAX 0x03
+static void batfet_status_check(struct qpnp_bms_chip *chip)
+{
+ bool batfet_closed;
+
+ if (chip->iadc_bms_revision2 > CALIB_WRKARND_DIG_MAJOR_MAX)
+ return;
+
+ batfet_closed = is_batfet_closed(chip);
+ if (chip->batfet_closed != batfet_closed) {
+ chip->batfet_closed = batfet_closed;
+ if (batfet_closed == false) {
+ /* batfet opened */
+ schedule_work(&chip->batfet_open_work);
+ qpnp_iadc_skip_calibration();
+ } else {
+ /* batfet closed */
+ qpnp_iadc_calibrate_for_trim(true);
+ qpnp_iadc_resume_calibration();
+ }
+ }
+}
static void battery_insertion_check(struct qpnp_bms_chip *chip)
{
@@ -3032,6 +3122,7 @@
bms_psy);
battery_insertion_check(chip);
+ batfet_status_check(chip);
battery_status_check(chip);
}
@@ -3791,6 +3882,7 @@
INIT_DELAYED_WORK(&chip->calculate_soc_delayed_work,
calculate_soc_work);
INIT_WORK(&chip->recalc_work, recalculate_work);
+ INIT_WORK(&chip->batfet_open_work, batfet_open_work);
read_shutdown_soc_and_iavg(chip);
@@ -3828,6 +3920,7 @@
}
battery_insertion_check(chip);
+ batfet_status_check(chip);
battery_status_check(chip);
calculate_soc_work(&(chip->calculate_soc_delayed_work.work));
diff --git a/drivers/power/qpnp-charger.c b/drivers/power/qpnp-charger.c
index 88e00ba..e93d085 100644
--- a/drivers/power/qpnp-charger.c
+++ b/drivers/power/qpnp-charger.c
@@ -275,6 +275,7 @@
struct qpnp_chg_irq chg_vbatdet_lo;
struct qpnp_chg_irq batt_pres;
struct qpnp_chg_irq vchg_loop;
+ struct qpnp_chg_irq batt_temp_ok;
bool bat_is_cool;
bool bat_is_warm;
bool chg_done;
@@ -503,6 +504,23 @@
}
static int
+qpnp_chg_is_batt_temp_ok(struct qpnp_chg_chip *chip)
+{
+ u8 batt_rt_sts;
+ int rc;
+
+ rc = qpnp_chg_read(chip, &batt_rt_sts,
+ INT_RT_STS(chip->bat_if_base), 1);
+ if (rc) {
+ pr_err("spmi read failed: addr=%03X, rc=%d\n",
+ INT_RT_STS(chip->bat_if_base), rc);
+ return rc;
+ }
+
+ return (batt_rt_sts & BAT_TEMP_OK_IRQ) ? 1 : 0;
+}
+
+static int
qpnp_chg_is_batt_present(struct qpnp_chg_chip *chip)
{
u8 batt_pres_rt_sts;
@@ -519,6 +537,23 @@
return (batt_pres_rt_sts & BATT_PRES_IRQ) ? 1 : 0;
}
+static int
+qpnp_chg_is_batfet_closed(struct qpnp_chg_chip *chip)
+{
+ u8 batfet_closed_rt_sts;
+ int rc;
+
+ rc = qpnp_chg_read(chip, &batfet_closed_rt_sts,
+ INT_RT_STS(chip->bat_if_base), 1);
+ if (rc) {
+ pr_err("spmi read failed: addr=%03X, rc=%d\n",
+ INT_RT_STS(chip->bat_if_base), rc);
+ return rc;
+ }
+
+ return (batfet_closed_rt_sts & BAT_FET_ON_IRQ) ? 1 : 0;
+}
+
#define USB_VALID_BIT BIT(7)
static int
qpnp_chg_is_usb_chg_plugged_in(struct qpnp_chg_chip *chip)
@@ -980,6 +1015,19 @@
}
static irqreturn_t
+qpnp_chg_bat_if_batt_temp_irq_handler(int irq, void *_chip)
+{
+ struct qpnp_chg_chip *chip = _chip;
+ int batt_temp_good;
+
+ batt_temp_good = qpnp_chg_is_batt_temp_ok(chip);
+ pr_debug("batt-temp triggered: %d\n", batt_temp_good);
+
+ power_supply_changed(&chip->batt_psy);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t
qpnp_chg_bat_if_batt_pres_irq_handler(int irq, void *_chip)
{
struct qpnp_chg_chip *chip = _chip;
@@ -1217,6 +1265,7 @@
POWER_SUPPLY_PROP_CHARGE_TYPE,
POWER_SUPPLY_PROP_HEALTH,
POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_ONLINE,
POWER_SUPPLY_PROP_TECHNOLOGY,
POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
@@ -1512,6 +1561,11 @@
return (buck_sts & VCHG_LOOP_IRQ) ? 1 : 0;
}
+static int get_prop_online(struct qpnp_chg_chip *chip)
+{
+ return qpnp_chg_is_batfet_closed(chip);
+}
+
static void
qpnp_batt_external_power_changed(struct power_supply *psy)
{
@@ -1621,6 +1675,9 @@
case POWER_SUPPLY_PROP_VOLTAGE_MIN:
val->intval = qpnp_chg_vinmin_get(chip) * 1000;
break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = get_prop_online(chip);
+ break;
default:
return -EINVAL;
}
@@ -2291,6 +2348,8 @@
if (qpnp_adc_tm_channel_measure(&chip->adc_param))
pr_err("request ADC error\n");
+
+ power_supply_changed(&chip->batt_psy);
}
static int
@@ -2494,6 +2553,25 @@
}
enable_irq_wake(chip->batt_pres.irq);
+
+ chip->batt_temp_ok.irq = spmi_get_irq_byname(spmi,
+ spmi_resource, "bat-temp-ok");
+ if (chip->batt_temp_ok.irq < 0) {
+ pr_err("Unable to get bat-temp-ok irq\n");
+ return rc;
+ }
+ rc = devm_request_irq(chip->dev, chip->batt_temp_ok.irq,
+ qpnp_chg_bat_if_batt_temp_irq_handler,
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+ "bat-temp-ok", chip);
+ if (rc < 0) {
+ pr_err("Can't request %d bat-temp-ok irq: %d\n",
+ chip->batt_temp_ok.irq, rc);
+ return rc;
+ }
+
+ enable_irq_wake(chip->batt_temp_ok.irq);
+
break;
case SMBB_BUCK_SUBTYPE:
case SMBBP_BUCK_SUBTYPE: