power: qcom: Add the QPNP QGAUGE (QG) driver
The QG driver adds support for battery gauging and reporting
the battery specific parameters to userspace. The driver comprises
of the below files:
qpnp-qg.c: Core QG driver
qg-soc.c: SOC (state of charge) scaling operations
qg-util.c: Common functions
qg-battery-profile.c: Parsing battery profile from DT
qg-profile-lib.c: Operations on battery profiles
qg-sdam.c: SDAM interface for QG
This commit also adds the below UAPI files,
qg.h: QG data shared between kernel and userspace QG modules
qg-profile.c: Profile data exchange between kernel and userspace.
CRs-Fixed: 2190528
Change-Id: I97f9d051692ed659030136a622e62009da55b462
Signed-off-by: Anirudh Ghayal <aghayal@codeaurora.org>
diff --git a/drivers/power/supply/qcom/qpnp-qg.c b/drivers/power/supply/qcom/qpnp-qg.c
new file mode 100644
index 0000000..55cb5ef
--- /dev/null
+++ b/drivers/power/supply/qcom/qpnp-qg.c
@@ -0,0 +1,2376 @@
+/* Copyright (c) 2018 The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#define pr_fmt(fmt) "QG-K: %s: " fmt, __func__
+
+#include <linux/alarmtimer.h>
+#include <linux/cdev.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/ktime.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+#include <linux/of_batterydata.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/regmap.h>
+#include <linux/uaccess.h>
+#include <linux/pmic-voter.h>
+#include <linux/qpnp/qpnp-adc.h>
+#include <uapi/linux/qg.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"
+
+static int qg_debug_mask;
+module_param_named(
+ debug_mask, qg_debug_mask, int, 0600
+);
+
+static int qg_get_battery_temp(struct qpnp_qg *chip, int *batt_temp);
+
+static bool is_battery_present(struct qpnp_qg *chip)
+{
+ u8 reg = 0;
+ int rc;
+
+ 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);
+
+ return !!(reg & BATTERY_PRESENT_BIT);
+}
+
+#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, u8 type)
+{
+ int rc, addr;
+ u64 temp = 0;
+
+ switch (type) {
+ case GOOD_OCV:
+ addr = QG_S3_GOOD_OCV_V_DATA0_REG;
+ break;
+ case PON_OCV:
+ addr = QG_S7_PON_OCV_V_DATA0_REG;
+ break;
+ default:
+ pr_err("Invalid OCV type %d\n", type);
+ return -EINVAL;
+ }
+
+ rc = qg_read(chip, chip->qg_base + addr, (u8 *)&temp, 2);
+ if (rc < 0) {
+ pr_err("Failed to read ocv, rc=%d\n", rc);
+ return rc;
+ }
+
+ *ocv_uv = V_RAW_TO_UV(temp);
+
+ pr_debug("%s: OCV=%duV\n",
+ type == GOOD_OCV ? "GOOD_OCV" : "PON_OCV", *ocv_uv);
+
+ return rc;
+}
+
+static int qg_update_fifo_length(struct qpnp_qg *chip, u8 length)
+{
+ int rc;
+
+ if (!length || length > 8) {
+ 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);
+
+ 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");
+}
+
+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_update_sdam_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;
+
+ rc = qg_sdam_write_all(chip->sdam_data);
+ if (rc < 0)
+ pr_err("Failed to write to SDAM rc=%d\n", rc);
+
+ for (i = 0; i < SDAM_MAX; i++)
+ qg_dbg(chip, QG_DEBUG_STATUS, "SDAM write param %d value=%d\n",
+ i, chip->sdam_data[i]);
+
+ 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;
+
+ 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;
+ }
+
+ 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);
+
+ temp = sign_extend32(fifo_i, 15);
+
+ chip->kdata.fifo[j].v = V_RAW_TO_UV(fifo_v);
+ chip->kdata.fifo[j].i = I_RAW_TO_UA(temp);
+ chip->kdata.fifo[j].interval = sample_interval;
+ chip->kdata.fifo[j].count = sample_count;
+
+ 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,
+ fifo_i,
+ (int)chip->kdata.fifo[j].i,
+ chip->kdata.fifo[j].interval,
+ chip->kdata.fifo[j].count);
+ }
+
+ chip->kdata.fifo_length = fifo_length;
+
+ 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;
+
+ 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) {
+ 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 = I_RAW_TO_UA(div_s64(temp, count));
+ chip->kdata.fifo[index].interval = sample_interval;
+ chip->kdata.fifo[index].count = count;
+ chip->kdata.fifo_length++;
+
+ 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 VBAT_LOW_HYST_UV 50000 /* 50mV */
+static int qg_vbat_low_wa(struct qpnp_qg *chip)
+{
+ int rc, i;
+ u32 vbat_low_uv = chip->dt.vbatt_low_mv * 1000 + VBAT_LOW_HYST_UV;
+
+ if (!(chip->wa_flags & QG_VBAT_LOW_WA) || !chip->vbat_low)
+ return 0;
+
+ /*
+ * PMI632 1.0 does not generate a falling VBAT_LOW IRQ.
+ * To exit from VBAT_LOW config, check if any of the FIFO
+ * averages is > vbat_low threshold and reconfigure the
+ * FIFO length to normal.
+ */
+ for (i = 0; i < chip->kdata.fifo_length; i++) {
+ if (chip->kdata.fifo[i].v > vbat_low_uv) {
+ rc = qg_master_hold(chip, true);
+ if (rc < 0) {
+ pr_err("Failed to hold master, rc=%d\n", rc);
+ goto done;
+ }
+ rc = qg_update_fifo_length(chip,
+ chip->dt.s2_fifo_length);
+ if (rc < 0)
+ goto done;
+
+ 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();
+
+ chip->vbat_low = false;
+ pr_info("Exit VBAT_LOW vbat_avg=%duV vbat_low=%duV updated fifo_length=%d\n",
+ chip->kdata.fifo[i].v, vbat_low_uv,
+ chip->dt.s2_fifo_length);
+ break;
+ }
+ }
+
+ return 0;
+
+done:
+ qg_master_hold(chip, false);
+ return rc;
+}
+
+#define MIN_FIFO_FULL_TIME_MS 12000
+static int process_rt_fifo_data(struct qpnp_qg *chip,
+ bool vbat_low, bool update_smb)
+{
+ int rc = 0;
+ ktime_t now = ktime_get();
+ 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 vbat_low=%d\n",
+ time_delta, vbat_low);
+
+ if (time_delta > MIN_FIFO_FULL_TIME_MS || vbat_low || 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 (vbat_low) {
+ /* change FIFO length */
+ rc = qg_update_fifo_length(chip,
+ chip->dt.s2_vbat_low_fifo_length);
+ if (rc < 0)
+ goto done;
+
+ qg_dbg(chip, QG_DEBUG_STATUS,
+ "FIFO length updated to %d vbat_low=%d\n",
+ chip->dt.s2_vbat_low_fifo_length,
+ vbat_low);
+ }
+
+ 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();
+
+ /* 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;
+}
+
+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_SOC].valid) {
+ qg_dbg(chip, QG_DEBUG_SOC, "udata SOC=%d last SOC=%d\n",
+ chip->udata.param[QG_SOC].data, chip->catch_up_soc);
+
+ /* Only scale if SOC has changed */
+ if (chip->catch_up_soc != chip->udata.param[QG_SOC].data) {
+ chip->catch_up_soc = chip->udata.param[QG_SOC].data;
+ qg_scale_soc(chip, false);
+ }
+
+ /* update parameters to SDAM */
+ chip->sdam_data[SDAM_SOC] =
+ chip->udata.param[QG_SOC].data;
+ 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_update_sdam_params(chip);
+ if (rc < 0)
+ pr_err("Failed to update SDAM params, rc=%d\n", rc);
+ }
+
+ vote(chip->awake_votable, UDATA_READY_VOTER, false, 0);
+}
+
+static irqreturn_t qg_default_irq_handler(int irq, void *data)
+{
+ struct qpnp_qg *chip = data;
+
+ qg_dbg(chip, QG_DEBUG_IRQ, "IRQ triggered\n");
+
+ return IRQ_HANDLED;
+}
+
+#define MAX_FIFO_DELTA_PERCENT 10
+static irqreturn_t qg_fifo_update_done_handler(int irq, void *data)
+{
+ ktime_t now = ktime_get();
+ 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;
+ }
+
+ rc = qg_vbat_low_wa(chip);
+ if (rc < 0) {
+ pr_err("Failed to apply VBAT LOW WA, 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;
+ }
+ chip->vbat_low = !!(status & VBAT_LOW_INT_RT_STS_BIT);
+
+ rc = process_rt_fifo_data(chip, chip->vbat_low, false);
+ if (rc < 0)
+ pr_err("Failed to process RT FIFO data, rc=%d\n", rc);
+
+ 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;
+
+ qg_dbg(chip, QG_DEBUG_IRQ, "IRQ triggered\n");
+ 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_update_sdam_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;
+ u32 ocv_uv;
+ struct qpnp_qg *chip = data;
+
+ qg_dbg(chip, QG_DEBUG_IRQ, "IRQ triggered\n");
+
+ mutex_lock(&chip->data_lock);
+
+ rc = qg_read_ocv(chip, &ocv_uv, GOOD_OCV);
+ if (rc < 0) {
+ pr_err("Failed to read good_ocv, rc=%d\n", rc);
+ goto done;
+ }
+
+ 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",
+ .handler = qg_default_irq_handler,
+ },
+ [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",
+ .handler = qg_default_irq_handler,
+ },
+ [QG_EVENT_IRQ] = {
+ .name = "qg-event",
+ .handler = qg_default_irq_handler,
+ },
+};
+
+static int qg_awake_cb(struct votable *votable, void *data, int awake,
+ const char *client)
+{
+ struct qpnp_qg *chip = data;
+
+ 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;
+}
+
+#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;
+}
+
+static 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;
+ }
+
+ rc = qg_read(chip, chip->qg_base + QG_LAST_ADC_I_DATA0_REG,
+ (u8 *)&last_ibat, 2);
+ if (rc < 0) {
+ pr_err("Failed to read LAST_ADV_I reg, rc=%d\n", rc);
+ return rc;
+ }
+
+ last_ibat = sign_extend32(last_ibat, 15);
+ *ibat_ua = I_RAW_TO_UA(last_ibat);
+
+ return rc;
+}
+
+static 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;
+}
+
+#define DEBUG_BATT_SOC 67
+#define BATT_MISSING_SOC 50
+#define EMPTY_SOC 0
+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;
+ }
+
+ *soc = chip->msoc;
+
+ return 0;
+}
+
+static int qg_get_battery_temp(struct qpnp_qg *chip, int *temp)
+{
+ int rc = 0;
+ struct qpnp_vadc_result result;
+
+ if (chip->battery_missing) {
+ *temp = 250;
+ return 0;
+ }
+
+ rc = qpnp_vadc_read(chip->vadc_dev, VADC_BAT_THERM_PU2, &result);
+ if (rc) {
+ pr_err("Failed reading adc channel=%d, rc=%d\n",
+ VADC_BAT_THERM_PU2, rc);
+ return rc;
+ }
+ pr_debug("batt_temp = %lld meas = 0x%llx\n",
+ result.physical, result.measurement);
+
+ *temp = (int)result.physical;
+
+ return rc;
+}
+
+static int qg_psy_set_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ const union power_supply_propval *pval)
+{
+ 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;
+
+ pval->intval = 0;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CAPACITY:
+ rc = qg_get_battery_capacity(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_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;
+ 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)
+{
+ return 0;
+}
+
+static enum power_supply_property qg_psy_props[] = {
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_TEMP,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_VOLTAGE_OCV,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ POWER_SUPPLY_PROP_RESISTANCE,
+ POWER_SUPPLY_PROP_RESISTANCE_ID,
+ 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,
+};
+
+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,
+};
+
+static int qg_charge_full_update(struct qpnp_qg *chip)
+{
+
+ vote(chip->good_ocv_irq_disable_votable,
+ QG_INIT_STATE_IRQ_DISABLE, !chip->charge_done, 0);
+
+ /* TODO: add hold-soc-at-full logic */
+ return 0;
+}
+
+static int qg_parallel_status_update(struct qpnp_qg *chip)
+{
+ int rc;
+ bool parallel_enabled = is_parallel_enabled(chip);
+
+ 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);
+
+ rc = process_rt_fifo_data(chip, false, true);
+ 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_usb_status_update(struct qpnp_qg *chip)
+{
+ bool usb_present = is_usb_present(chip);
+
+ if (chip->usb_present != usb_present) {
+ qg_dbg(chip, QG_DEBUG_STATUS,
+ "USB status changed Present=%d\n",
+ usb_present);
+ qg_scale_soc(chip, false);
+ }
+
+ chip->usb_present = usb_present;
+
+ return 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;
+
+ if (!is_batt_available(chip)) {
+ pr_debug("batt-psy not available\n");
+ goto out;
+ }
+
+ 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;
+
+ rc = qg_parallel_status_update(chip);
+ if (rc < 0)
+ pr_err("Failed to update parallel-status, rc=%d\n", rc);
+
+ rc = qg_usb_status_update(chip);
+ if (rc < 0)
+ pr_err("Failed to update usb status, rc=%d\n", rc);
+
+ rc = qg_charge_full_update(chip);
+ if (rc < 0)
+ pr_err("Failed in charge_full_update, rc=%d\n", rc);
+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)) {
+ /*
+ * 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 = NULL;
+ qg_psy_cfg.supplied_to = NULL;
+ qg_psy_cfg.num_supplicants = 0;
+ 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);
+
+ /* non-blocking access, return */
+ if (!chip->data_ready && (file->f_flags & O_NONBLOCK))
+ return -EAGAIN;
+
+ /* 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;
+
+ /* clear data */
+ memset(&chip->kdata, 0, sizeof(chip->kdata));
+
+ /* 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);
+
+ qg_dbg(chip, QG_DEBUG_DEVICE,
+ "QG device read complete Size=%ld\n", data_size);
+
+ 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 %zu\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;
+
+ poll_wait(file, &chip->qg_wait_q, wait);
+
+ if (chip->data_ready)
+ mask = POLLIN | POLLRDNORM;
+ else
+ mask = POLLERR;
+
+ 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;
+ qg_dbg(chip, QG_DEBUG_DEVICE, "QG device opened!\n");
+
+ return 0;
+}
+
+static const struct file_operations qg_fops = {
+ .owner = THIS_MODULE,
+ .open = qg_device_open,
+ .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;
+ struct qpnp_vadc_result result;
+
+ /* Read battery-id */
+ rc = qpnp_vadc_read(chip->vadc_dev, VADC_BAT_ID_PU2, &result);
+ if (rc) {
+ pr_err("Failed to read BATT_ID over vadc, rc=%d\n", rc);
+ return rc;
+ }
+
+ batt_id_mv = result.physical / 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 *batt_node, *profile_node;
+ int rc;
+
+ batt_node = of_find_node_by_name(node, "qcom,battery-data");
+ if (!batt_node) {
+ pr_err("Batterydata not available\n");
+ return -ENXIO;
+ }
+
+ profile_node = of_batterydata_get_best_profile(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;
+ }
+
+ 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;
+ }
+
+ 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;
+ } 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);
+ 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 int qg_determine_pon_soc(struct qpnp_qg *chip)
+{
+ u8 status;
+ int rc, batt_temp = 0;
+ bool use_pon_ocv = false;
+ unsigned long rtc_sec = 0;
+ u32 ocv_uv = 0, soc = 0, shutdown[SDAM_MAX] = {0};
+
+ if (!chip->profile_loaded) {
+ qg_dbg(chip, QG_DEBUG_PON, "No Profile, skipping PON soc\n");
+ return 0;
+ }
+
+ rc = qg_get_battery_temp(chip, &batt_temp);
+ if (rc) {
+ pr_err("Failed to read BATT_TEMP at PON rc=%d\n", rc);
+ return rc;
+ }
+
+ 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);
+ return rc;
+ }
+
+ if (status & GOOD_OCV_BIT) {
+ qg_dbg(chip, QG_DEBUG_PON, "Using GOOD_OCV @ PON\n");
+ rc = qg_read_ocv(chip, &ocv_uv, GOOD_OCV);
+ if (rc < 0) {
+ pr_err("Failed to read good_ocv rc=%d\n", rc);
+ use_pon_ocv = true;
+ } else {
+ rc = lookup_soc_ocv(&soc, ocv_uv, batt_temp, false);
+ if (rc < 0) {
+ pr_err("Failed to lookup SOC (GOOD_OCV) @ PON rc=%d\n",
+ rc);
+ use_pon_ocv = true;
+ }
+ }
+ } else {
+ rc = get_rtc_time(&rtc_sec);
+ if (rc < 0) {
+ pr_err("Failed to read RTC time rc=%d\n", rc);
+ use_pon_ocv = true;
+ goto done;
+ }
+
+ rc = qg_sdam_read_all(shutdown);
+ if (rc < 0) {
+ pr_err("Failed to read shutdown params rc=%d\n", rc);
+ use_pon_ocv = true;
+ goto done;
+ }
+ qg_dbg(chip, QG_DEBUG_PON, "Shutdown: SOC=%d OCV=%duV time=%dsecs, time_now=%ldsecs\n",
+ shutdown[SDAM_SOC],
+ shutdown[SDAM_OCV_UV],
+ shutdown[SDAM_TIME_SEC],
+ rtc_sec);
+ /*
+ * Use the shutdown SOC if
+ * 1. The device was powered off for < 180 seconds
+ * 2. SDAM read is a success & SDAM data is valid
+ */
+ use_pon_ocv = true;
+ if (!rc && shutdown[SDAM_VALID] &&
+ ((rtc_sec - shutdown[SDAM_TIME_SEC]) < 180)) {
+ use_pon_ocv = false;
+ ocv_uv = shutdown[SDAM_OCV_UV];
+ soc = shutdown[SDAM_SOC];
+ qg_dbg(chip, QG_DEBUG_PON, "Using SHUTDOWN_SOC @ PON\n");
+ }
+ }
+done:
+ /*
+ * Use PON OCV if
+ * OCV_UV is not set or shutdown SOC is invalid.
+ */
+ if (use_pon_ocv || !ocv_uv || !rtc_sec) {
+ qg_dbg(chip, QG_DEBUG_PON, "Using PON_OCV @ PON\n");
+ rc = qg_read_ocv(chip, &ocv_uv, PON_OCV);
+ if (rc < 0) {
+ pr_err("Failed to read HW PON ocv rc=%d\n", rc);
+ return rc;
+ }
+ rc = lookup_soc_ocv(&soc, ocv_uv, batt_temp, false);
+ if (rc < 0) {
+ pr_err("Failed to lookup SOC @ PON rc=%d\n", rc);
+ soc = 50;
+ }
+ }
+
+ chip->pon_soc = 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_update_sdam_params(chip);
+ if (rc < 0)
+ pr_err("Failed to update sdam params rc=%d\n", rc);
+
+ pr_info("use_pon_ocv=%d good_ocv=%d ocv_uv=%duV temp=%d soc=%d\n",
+ use_pon_ocv, !!(status & GOOD_OCV_BIT),
+ ocv_uv, batt_temp, chip->msoc);
+
+ return 0;
+}
+
+static int qg_set_wa_flags(struct qpnp_qg *chip)
+{
+ switch (chip->pmic_rev_id->pmic_subtype) {
+ case PMI632_SUBTYPE:
+ if (chip->pmic_rev_id->rev4 == PMI632_V1P0_REV4)
+ chip->wa_flags |= 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;
+}
+
+static int qg_hw_init(struct qpnp_qg *chip)
+{
+ int rc, temp;
+ u8 reg;
+
+ 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;
+ }
+ }
+
+ /* 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();
+
+ if (chip->dt.ocv_timer_expiry_min != -EINVAL) {
+ 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 > 8)
+ chip->dt.s3_entry_fifo_length = 8;
+
+ 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", 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 low */
+ 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;
+
+ reg = chip->dt.vbatt_low_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;
+ }
+
+ /* vbat empty */
+ 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;
+
+ reg = chip->dt.vbatt_empty_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;
+ }
+
+ return 0;
+}
+
+static int qg_post_init(struct qpnp_qg *chip)
+{
+ /* 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);
+ } else {
+ /* disable GOOD_OCV IRQ at init */
+ vote(chip->good_ocv_irq_disable_votable,
+ QG_INIT_STATE_IRQ_DISABLE, true, 0);
+ }
+
+ 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 DEFAULT_VBATT_EMPTY_MV 3200
+#define DEFAULT_VBATT_CUTOFF_MV 3400
+#define DEFAULT_VBATT_LOW_MV 3500
+#define DEFAULT_ITERM_MA 100
+#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_DELTA_SOC 1
+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;
+ }
+
+ /* 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;
+
+ 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);
+
+ /* 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-entry-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-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-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,rbat-conn-mohm", &temp);
+ if (rc < 0)
+ chip->dt.rbat_conn_mohm = 0;
+ else
+ chip->dt.rbat_conn_mohm = temp;
+
+ qg_dbg(chip, QG_DEBUG_PON, "DT: vbatt_empty_mv=%dmV vbatt_low_mv=%dmV delta_soc=%d\n",
+ chip->dt.vbatt_empty_mv, chip->dt.vbatt_low_mv,
+ chip->dt.delta_soc);
+
+ return 0;
+}
+
+static int process_suspend(struct qpnp_qg *chip)
+{
+ int rc;
+ u32 fifo_rt_length = 0, sleep_fifo_length = 0;
+
+ /* ignore any suspend processing if we are charging */
+ if (chip->charge_status == POWER_SUPPLY_STATUS_CHARGING) {
+ 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 the real-time FIFO count is greater than
+ * the the #fifo to enter sleep, save the FIFO data
+ * and reset the fifo count.
+ */
+ if (fifo_rt_length >= (chip->dt.s2_fifo_length - sleep_fifo_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();
+
+ chip->suspend_data = true;
+ }
+
+ qg_dbg(chip, QG_DEBUG_PM, "FIFO rt_length=%d sleep_fifo_length=%d default_s2_count=%d suspend_data=%d\n",
+ fifo_rt_length, sleep_fifo_length,
+ chip->dt.s2_fifo_length, chip->suspend_data);
+
+ return rc;
+}
+
+static int process_resume(struct qpnp_qg *chip)
+{
+ int rc, batt_temp = 0;
+ u8 status2 = 0, rt_status = 0;
+ u32 ocv_uv = 0, soc = 0;
+
+ 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, GOOD_OCV);
+ if (rc < 0) {
+ pr_err("Failed to read good_ocv, rc=%d\n", rc);
+ return rc;
+ }
+ rc = qg_get_battery_temp(chip, &batt_temp);
+ if (rc < 0) {
+ pr_err("Failed to read BATT_TEMP, rc=%d\n", rc);
+ return rc;
+ }
+
+ chip->kdata.param[QG_GOOD_OCV_UV].data = ocv_uv;
+ chip->kdata.param[QG_GOOD_OCV_UV].valid = true;
+ chip->suspend_data = false;
+ rc = lookup_soc_ocv(&soc, ocv_uv, batt_temp, false);
+ if (rc < 0) {
+ pr_err("Failed to lookup OCV, rc=%d\n", rc);
+ return rc;
+ }
+ chip->catch_up_soc = soc;
+ /* update the SOC immediately */
+ qg_scale_soc(chip, true);
+
+ qg_dbg(chip, QG_DEBUG_PM, "GOOD OCV @ resume good_ocv=%d uV soc=%d\n",
+ ocv_uv, soc);
+ }
+ /*
+ * If the wakeup was not because of FIFO_DONE
+ * send the pending data collected during suspend.
+ */
+ 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;
+
+ if (!rt_status && chip->suspend_data) {
+ vote(chip->awake_votable, SUSPEND_DATA_VOTER, true, 0);
+ /* signal the read thread */
+ chip->data_ready = true;
+ wake_up_interruptible(&chip->qg_wait_q);
+ }
+
+ qg_dbg(chip, QG_DEBUG_PM, "fifo_done rt_status=%d suspend_data=%d data_ready=%d\n",
+ !!rt_status, chip->suspend_data, chip->data_ready);
+
+ chip->suspend_data = false;
+
+ return rc;
+}
+
+static int qpnp_qg_suspend_noirq(struct device *dev)
+{
+ int rc;
+ struct qpnp_qg *chip = dev_get_drvdata(dev);
+
+ 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 const struct dev_pm_ops qpnp_qg_pm_ops = {
+ .suspend_noirq = qpnp_qg_suspend_noirq,
+ .resume_noirq = qpnp_qg_resume_noirq,
+};
+
+static int qpnp_qg_probe(struct platform_device *pdev)
+{
+ int rc = 0, soc = 0;
+ 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;
+ }
+
+ /* VADC for BID */
+ chip->vadc_dev = qpnp_get_vadc(&pdev->dev, "qg");
+ if (IS_ERR(chip->vadc_dev)) {
+ rc = PTR_ERR(chip->vadc_dev);
+ if (rc != -EPROBE_DEFER)
+ pr_err("Failed to find VADC node, rc=%d\n", rc);
+
+ 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);
+ mutex_init(&chip->bus_lock);
+ mutex_init(&chip->soc_lock);
+ mutex_init(&chip->data_lock);
+ init_waitqueue_head(&chip->qg_wait_q);
+
+ 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_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_sdam_init(chip->dev);
+ if (rc < 0) {
+ pr_err("Failed to initialize QG 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;
+ }
+
+ 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;
+ }
+
+ qg_get_battery_capacity(chip, &soc);
+ pr_info("QG initialized! battery_profile=%s SOC=%d\n",
+ qg_get_battery_type(chip), soc);
+
+ 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_work_sync(&chip->udata_work);
+ cancel_work_sync(&chip->qg_status_change_work);
+ 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 const struct of_device_id match_table[] = {
+ { .compatible = "qcom,qpnp-qg", },
+ { },
+};
+
+static struct platform_driver qpnp_qg_driver = {
+ .driver = {
+ .name = "qcom,qpnp-qg",
+ .owner = THIS_MODULE,
+ .of_match_table = match_table,
+ .pm = &qpnp_qg_pm_ops,
+ },
+ .probe = qpnp_qg_probe,
+ .remove = qpnp_qg_remove,
+};
+module_platform_driver(qpnp_qg_driver);
+
+MODULE_DESCRIPTION("QPNP QG Driver");
+MODULE_LICENSE("GPL v2");