// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2021, The Linux Foundation. All rights reserved. * Copyright (c) 2022-2024, Qualcomm Innovation Center, Inc. All rights reserved. */ #define pr_fmt(fmt) "QBG_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 #include #include #include #include #include #include #include "qbg-iio.h" #include "qbg-battery-profile.h" #include "qbg-core.h" #include "qbg-sdam.h" #include "battery-profile-loader.h" /* QBG register definitions */ #define QBG_MAIN_STATUS1 0x08 #define QBG_OK_BIT BIT(7) #define QBG_MAIN_STATUS2 0x09 #define QBG_BATTERY_MISSING_BIT BIT(2) #define QBG_ADC_CONV_FAULT_OCCURRED_BIT BIT(1) #define QBG_DATA_RESET_BIT BIT(0) #define QBG_MAIN_STATUS3 0x0a #define QBG_FSM_STATE_MASK GENMASK(3, 0) #define QBG_MAIN_QBG_STATE_FORCE_CMD 0x41 #define FORCE_FAST_CHAR_MODE_BIT BIT(3) #define FORCE_FAST_CHAR_SHIFT 3 #define QBG_MAIN_EN_CTL 0x46 #define QBG_EN_BIT BIT(7) #define QBG_MAIN_MPM_TO_LPM_IBAT_THRESH 0x50 #define QBG_MAIN_LPM_TO_MPM_IBAT_THRESH 0x51 #define QBG_MAIN_HPM_TO_MPM_IBAT_THRESH 0x52 #define QBG_MAIN_MPM_TO_HPM_IBAT_THRESH 0x53 #define QBG_MAIN_VBAT_EMPTY_THRESH 0x56 #define QBG_MAIN_HPM_MEAS_CTL2 0x5d #define QBG_MAIN_MPM_MEAS_CTL2 0x61 #define QBG_MAIN_LPM_MEAS_CTL2 0x65 #define QBG_NUM_OF_ACCUM_MASK GENMASK(7, 5) #define QBG_NUM_OF_ACCUM_SHIFT 5 #define QBG_ACCUM_INTERVAL_MASK GENMASK(2, 0) #define QBG_MAIN_FAST_CHAR_MEAS_CTL2 0x69 #define QBG_MAIN_PON_OCV_ACC0_DATA0 0x70 #define QBG_MAIN_LAST_ADC_ACC0_DATA0 0x9A #define QBG_MAIN_LAST_BURST_AVG_ACC0_DATA0 0xA0 #define QBG_MAIN_LAST_BURST_AVG_ACC2_DATA0 0xA4 /* PM5100 REVID */ #define REVID_REVISION4 0x103 #define REVISION_V1 0x1 #define QBG_FAST_CHAR_DELTA_MS 100000 #define VBATT_1S_LSB 19463 #define IBATT_10A_LSB 6103 #define VBAT_0PCT_OCV 300000000ULL #define ICHG_FS_10A 1 #define TBAT_LSB 7500 #define TBAT_DENOMINATOR 115600 #define BOOST_COMPENSATION_UV 400000 #define OCV_BOOST_MIN_UV 4100000 #define OCV_BOOST_MAX_UV 5600000 #define OCV_BOOST_STEP_UV 100000 #define BATT_HOT_DECIDEGREE_MAX 600 #define MILLI_TO_10NANO 100000 #define MICRO_TO_10NANO 100 #define MILLI_TO_MICRO 1000 /* PM5100 ADC_CMN and ADC_CMN_WB register definitions */ #define BAT_THERM_IBAT_CTL_2 0x62 #define VADC_ANA_PARAM_4 0x46 static int qbg_debug_mask; enum qbg_esr_fcc_reduction { ESR_FCC_150MA = 0x6, }; #define ESR_PULSE_10NA(x) (-(x * 25 * MILLI_TO_10NANO)) #define ESR_IBAT_QUALIFY_OFFSET_10NA (-5000000) static const unsigned int qbg_accum_interval[8] = { 10000, 20000, 50000, 100000, 150000, 200000, 500000, 1000000}; static const unsigned int qbg_lpm_accum_interval[8] = { 100000, 200000, 500000, 1000000, 2000000, 5000000, 10000000, 100000000}; static const unsigned int qbg_fast_char_avg_interval[8] = { 6250, 12500, 25000, 50000, 100000, 200000, 400000, 800000}; static const struct qbg_iio_channels qbg_iio_psy_channels[] = { QBG_CHAN_INDEX("debug_battery", PSY_IIO_DEBUG_BATTERY) QBG_CHAN_ENERGY("capacity", PSY_IIO_CAPACITY) QBG_CHAN_ENERGY("real_capacity", PSY_IIO_REAL_CAPACITY) QBG_CHAN_TEMP("temp", PSY_IIO_TEMP) QBG_CHAN_VOLT("voltage_max", PSY_IIO_VOLTAGE_MAX) QBG_CHAN_VOLT("voltage_now", PSY_IIO_VOLTAGE_NOW) QBG_CHAN_VOLT("voltage_ocv", PSY_IIO_VOLTAGE_OCV) QBG_CHAN_VOLT("voltage_avg", PSY_IIO_VOLTAGE_AVG) QBG_CHAN_CUR("current_now", PSY_IIO_CURRENT_NOW) QBG_CHAN_RES("resistance_id", PSY_IIO_RESISTANCE_ID) QBG_CHAN_TSTAMP("time_to_full_avg", PSY_IIO_TIME_TO_FULL_AVG) QBG_CHAN_TSTAMP("time_to_full_now", PSY_IIO_TIME_TO_FULL_NOW) QBG_CHAN_TSTAMP("time_to_empty_avg", PSY_IIO_TIME_TO_EMPTY_AVG) QBG_CHAN_RES("esr", PSY_IIO_ESR_ACTUAL) QBG_CHAN_INDEX("soh", PSY_IIO_SOH) QBG_CHAN_COUNT("cycle_count", PSY_IIO_CYCLE_COUNT) QBG_CHAN_ENERGY("charge_full", PSY_IIO_CHARGE_FULL) QBG_CHAN_ENERGY("charge_full_design", PSY_IIO_CHARGE_FULL_DESIGN) QBG_CHAN_ENERGY("charge_counter", PSY_IIO_CHARGE_COUNTER) }; int qbg_read(struct qti_qbg *chip, u32 addr, u8 *val, int len) { int rc; rc = regmap_bulk_read(chip->regmap, chip->base + addr, val, len); if (rc < 0) { pr_err("Failed to read from address %04x rc=%d\n", chip->base + addr, rc); return rc; } if (*chip->debug_mask & QBG_DEBUG_BUS_READ) pr_info("length %d addr=%#x data:%*ph\n", len, chip->base + addr, len, val); return 0; } int qbg_write(struct qti_qbg *chip, u32 addr, u8 *val, int len) { int rc; rc = regmap_bulk_write(chip->regmap, chip->base + addr, val, len); if (rc < 0) { pr_err("Failed to write to address %04x rc=%d\n", chip->base + addr, rc); return rc; } if (*chip->debug_mask & QBG_DEBUG_BUS_WRITE) pr_info("length %d addr=%#x data:%*ph\n", len, chip->base + addr, len, val); return 0; } static bool is_chan_valid(struct qti_qbg *chip, enum qbg_ext_iio_channels chan) { int rc; if (IS_ERR(chip->ext_iio_chans[chan])) return false; if (!chip->ext_iio_chans[chan]) { chip->ext_iio_chans[chan] = iio_channel_get(chip->dev, qbg_ext_iio_chan_name[chan]); if (IS_ERR(chip->ext_iio_chans[chan])) { rc = PTR_ERR(chip->ext_iio_chans[chan]); if (rc == -EPROBE_DEFER) chip->ext_iio_chans[chan] = NULL; pr_err("Failed to get IIO channel %s, rc=%d\n", qbg_ext_iio_chan_name[chan], rc); return false; } } return true; } static int qbg_read_iio_chan(struct qti_qbg *chip, enum qbg_ext_iio_channels chan, int *val) { int rc; if (is_chan_valid(chip, chan)) { rc = iio_read_channel_processed(chip->ext_iio_chans[chan], val); return (rc < 0) ? rc : 0; } return -EINVAL; } static int qbg_write_iio_chan(struct qti_qbg *chip, enum qbg_ext_iio_channels chan, int val) { if (is_chan_valid(chip, chan)) return iio_write_channel_raw(chip->ext_iio_chans[chan], val); return -EINVAL; } static void qbg_notify_charger(struct qti_qbg *chip) { union power_supply_propval prop = {0, }; int rc; if (!chip->batt_psy || !chip->profile_loaded) return; prop.intval = chip->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->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; } if (chip->default_iterm_ma != -EINVAL) { prop.intval = chip->default_iterm_ma; rc = power_supply_set_property(chip->batt_psy, POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT, &prop); if (rc < 0) { pr_err("Failed to set charge_current_max property on batt_psy, rc=%d\n", rc); return; } } pr_debug("Notified charger on float voltage:%d uV and FCC:%d mA iterm:%d mA\n", chip->float_volt_uv, chip->fastchg_curr_ma, chip->default_iterm_ma); } static bool is_batt_available(struct qti_qbg *chip) { int rc; union power_supply_propval prop = {0, }; 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 */ qbg_notify_charger(chip); /* * Read termination current from charger, * Use it to configure iterm after recharge upon USB removal. */ rc = power_supply_get_property(chip->batt_psy, POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT, &prop); if (!rc) chip->default_iterm_ma = prop.intval; return true; } static bool is_usb_available(struct qti_qbg *chip) { if (chip->usb_psy) return true; chip->usb_psy = power_supply_get_by_name("usb"); if (!chip->usb_psy) return false; return true; } #define DEFAULT_RECHARGE_SOC 95 static int qbg_charge_full_update(struct qti_qbg *chip) { union power_supply_propval prop = {0, }; int rc, recharge_soc, health, val; /* Nothing to process */ if (!chip->charger_present || !chip->charge_done) return 0; 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); return rc; } health = prop.intval; rc = qbg_read_iio_chan(chip, RECHARGE_SOC, &val); if (rc < 0 || val < 0) { pr_debug("Failed to get recharge-soc, rc=%d\n", rc); recharge_soc = DEFAULT_RECHARGE_SOC; } else { recharge_soc = val; } chip->recharge_soc = recharge_soc; qbg_dbg(chip, QBG_DEBUG_STATUS, "msoc=%d sys_soc:%d charge_done:%d charge_status=%d recharge_soc=%d in_recharge:%u\n", chip->soc, chip->sys_soc, chip->charge_done, chip->charge_status, chip->recharge_soc, chip->in_recharge); if (chip->charge_status == POWER_SUPPLY_STATUS_CHARGING) { /* Charger is charging in recharge */ return 0; } /* System SOC has not dropped below recharge SOC to trigger recharge */ if (chip->sys_soc > recharge_soc) return 0; if (!chip->in_recharge) { if (chip->recharge_vflt_delta_mv && chip->recharge_vflt_delta_mv != -EINVAL) { prop.intval = (chip->float_volt_uv) - (chip->recharge_vflt_delta_mv * 1000); rc = power_supply_set_property(chip->batt_psy, POWER_SUPPLY_PROP_VOLTAGE_MAX, &prop); if (rc < 0) { pr_err("Failed to set recharge voltage_max property on batt_psy, rc=%d\n", rc); return rc; } } if (chip->recharge_iterm_ma && chip->recharge_iterm_ma != -EINVAL) { prop.intval = chip->recharge_iterm_ma; prop.intval = (-1 * prop.intval); rc = power_supply_set_property(chip->batt_psy, POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT, &prop); if (rc < 0) { pr_err("Failed to set recharge charge_term_current property on batt_psy, rc=%d\n", rc); return rc; } } chip->in_recharge = true; } return rc; } static void status_change_work(struct work_struct *work) { struct qti_qbg *chip = container_of(work, struct qti_qbg, status_change_work); union power_supply_propval prop = {0, }; int rc, val, charger_present = 0; if (!is_batt_available(chip)) return; if (!is_usb_available(chip)) return; 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 = qbg_read_iio_chan(chip, CHARGE_DONE, &val); if (rc < 0) pr_err("Failed to get charge done status, rc=%d\n", rc); else chip->charge_done = val; rc = power_supply_get_property(chip->usb_psy, POWER_SUPPLY_PROP_PRESENT, &prop); if (rc < 0) pr_err("Failed to get charger present, rc=%d\n", rc); else charger_present = prop.intval; qbg_dbg(chip, QBG_DEBUG_STATUS, "charge_status=%d charge_done=%d charger_present:%d\n", chip->charge_status, chip->charge_done, chip->charger_present); /* * Set FCC and iterm back to default value on charger removal * as they could have been updated during recharge. */ if (chip->charger_present && !charger_present) { qbg_notify_charger(chip); chip->in_recharge = false; } chip->charger_present = charger_present; rc = qbg_charge_full_update(chip); if (rc < 0) { pr_err("Failed in charge full update, rc=%d\n", rc); return; } } static int qbg_get_max_fifo_count(struct qti_qbg *chip) { int rc = 0; u8 val[2]; rc = qbg_sdam_read(chip, QBG_SDAM_BASE(chip, SDAM_CTRL0) + QBG_SDAM_MAX_FIFO_COUNT_OFFSET, val, 2); if (rc < 0) { pr_err("Failed to read QBG SDAM_MAX_FIFO_COUNT_OFFSET, rc=%d\n", rc); return rc; } chip->max_fifo_count = (val[1] << 8) | val[0]; qbg_dbg(chip, QBG_DEBUG_SDAM, "max FIFO count=%d\n", chip->max_fifo_count); return rc; } static int qbg_get_fifo_count(struct qti_qbg *chip, u32 *fifo_count) { int rc = 0; u8 val[2]; rc = qbg_sdam_read(chip, QBG_SDAM_BASE(chip, SDAM_CTRL0) + QBG_SDAM_FIFO_COUNT_OFFSET, val, 2); if (rc < 0) { pr_err("Failed to read QBG SDAM, rc=%d\n", rc); return rc; } *fifo_count = (val[1] << 8) | val[0]; qbg_dbg(chip, QBG_DEBUG_SDAM, "FIFO count=%d\n", *fifo_count); return rc; } static int qbg_hpm_fifo_depth_half(struct qti_qbg *chip, int current_fifo_count, bool enable) { int rc = 0; u8 val = 0; if (enable) val = current_fifo_count / 2; else val = current_fifo_count * 2; if (val > chip->max_fifo_count) val = chip->max_fifo_count; rc = qbg_sdam_write(chip, QBG_SDAM_BASE(chip, SDAM_CTRL0) + QBG_SDAM_HPM_FIFO_COUNT_OFFSET, &val, 1); if (rc < 0) pr_err("Failed to write QBG SDAM, rc=%d\n", rc); return rc; } static void update_ocv_for_boost(struct qti_qbg *chip) { int ocv_for_boost = 0x00; /* * Every time when OCV gets updated, add 0.4V headroom compensation * and round it to a value between 4.1V to 5.6V, then map it to a * setting between [0x00 - 0x0F] and set it in SDAM to notify charger-boost. */ ocv_for_boost = chip->ocv_uv + BOOST_COMPENSATION_UV; if (ocv_for_boost < OCV_BOOST_MIN_UV) ocv_for_boost = OCV_BOOST_MIN_UV; else if (ocv_for_boost > OCV_BOOST_MAX_UV) ocv_for_boost = OCV_BOOST_MAX_UV; ocv_for_boost -= OCV_BOOST_MIN_UV; ocv_for_boost /= OCV_BOOST_STEP_UV; qbg_dbg(chip, QBG_DEBUG_STATUS, "Boost OCV value written =%d\n", ocv_for_boost); qbg_sdam_write(chip, QBG_SDAM_BASE(chip, SDAM_CTRL0) + QBG_SDAM_BHARGER_OCV_HDRM_OFFSET, (u8 *)&ocv_for_boost, 1); } static void process_udata_work(struct work_struct *work) { struct qti_qbg *chip = container_of(work, struct qti_qbg, udata_work); int rc; if (chip->udata.param[QBG_PARAM_BATT_SOC].valid) chip->batt_soc = chip->udata.param[QBG_PARAM_BATT_SOC].data; if (chip->udata.param[QBG_PARAM_SOC].valid || chip->udata.param[QBG_PARAM_SYS_SOC].valid) { qbg_dbg(chip, QBG_DEBUG_SOC, "udata update: QBG_SOC=%d QBG_SYS_SOC=%d\n", chip->udata.param[QBG_PARAM_SOC].valid ? chip->udata.param[QBG_PARAM_SOC].data : -EINVAL, chip->udata.param[QBG_PARAM_SYS_SOC].valid ? chip->udata.param[QBG_PARAM_SYS_SOC].data : -EINVAL); if (chip->udata.param[QBG_PARAM_SYS_SOC].valid) { chip->sys_soc = chip->udata.param[QBG_PARAM_SYS_SOC].data; rc = qbg_write_iio_chan(chip, SYS_SOC, chip->sys_soc); if (rc < 0) pr_err("Failed to write battery sys_soc, rc=%d\n", rc); } chip->soc = chip->udata.param[QBG_PARAM_SOC].data; } if (chip->udata.param[QBG_PARAM_ESR].valid) { chip->esr = chip->udata.param[QBG_PARAM_ESR].data; if (chip->qbg_psy) power_supply_changed(chip->qbg_psy); } if (chip->udata.param[QBG_PARAM_OCV_UV].valid) { chip->ocv_uv = chip->udata.param[QBG_PARAM_OCV_UV].data; update_ocv_for_boost(chip); } if (chip->udata.param[QBG_PARAM_CHARGE_CYCLE_COUNT].valid) chip->charge_cycle_count = chip->udata.param[QBG_PARAM_CHARGE_CYCLE_COUNT].data; if (chip->udata.param[QBG_PARAM_LEARNED_CAPACITY].valid) chip->learned_capacity = chip->udata.param[QBG_PARAM_LEARNED_CAPACITY].data; if (chip->udata.param[QBG_PARAM_TTF_100MS].valid) chip->ttf = chip->udata.param[QBG_PARAM_TTF_100MS].data; if (chip->udata.param[QBG_PARAM_TTE_100MS].valid) chip->tte = chip->udata.param[QBG_PARAM_TTE_100MS].data; if (chip->udata.param[QBG_PARAM_SOH].valid) chip->soh = chip->udata.param[QBG_PARAM_SOH].data; if (chip->udata.param[QBG_PARAM_TBAT].valid) chip->tbat = chip->udata.param[QBG_PARAM_TBAT].data; if (chip->udata.param[QBG_PARAM_ESSENTIAL_PARAM_REVID].valid) { chip->essential_param_revid = chip->udata.param[QBG_PARAM_ESSENTIAL_PARAM_REVID].data; rc = qbg_sdam_set_essential_param_revid(chip, chip->essential_param_revid); if (rc < 0) pr_err("Failed to set essential param revid in sdam, rc=%d\n", rc); } rc = qbg_sdam_set_battery_id(chip, chip->batt_id_ohm / 1000); if (rc < 0) pr_err("Failed to set battid in sdam, rc=%d\n", rc); __pm_relax(chip->qbg_ws); qbg_dbg(chip, QBG_DEBUG_STATUS, "udata update: batt_soc=%d sys_soc=%d soc=%d qbg_esr=%d\n", (chip->batt_soc != INT_MIN) ? chip->batt_soc : -EINVAL, (chip->sys_soc != INT_MIN) ? chip->sys_soc : -EINVAL, chip->soc, chip->esr); } static int qbg_decode_fifo_data(struct fifo_data fifo, unsigned int *vbat1, unsigned int *vbat2, int *ibat, unsigned int *tbat, unsigned int *ibat_t) { unsigned short int acc0_data = 0, acc1_data = 0, acc2_data = 0, tbat_data = 0; unsigned int ibat_temp; if (!vbat1 || !vbat2 || !ibat || !tbat || !ibat_t) return -EINVAL; acc0_data = fifo.v1; acc1_data = fifo.v2; acc2_data = fifo.i; tbat_data = fifo.tbat; ibat_temp = acc2_data; *vbat1 = acc0_data * VBATT_1S_LSB; *ibat = ((int16_t)ibat_temp) * IBATT_10A_LSB * ICHG_FS_10A; *tbat = tbat_data; *ibat_t = fifo.ibat; return 0; } static int get_rtc_time(struct qti_qbg *chip, time64_t *rtc_time) { struct rtc_time tm; int rc; if (!chip->rtc) { pr_err("Invalid RTC handle\n"); return -EINVAL; } rc = rtc_read_time(chip->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_time = rtc_tm_to_time64(&tm); return rc; close_time: rtc_class_close(chip->rtc); chip->rtc = NULL; return rc; } #define BAT_THERM_CAL_DENOM 2080 #define BAT_THERM_CAL_NUM 10000 #define BAT_THERM_SCALE 100 static int qbg_set_therm_trace_resistance(struct qti_qbg *chip, int vref_adc, int tbat_adc, int ibat_10na, int ibat_esr_10na, int tbat_esr_adc) { int rc = 0; int64_t r_pack_nom = 0; int64_t r_pack = 0; int64_t r_pack_denom = 0; u8 reg_val = 0; int tbat_decidegree = div64_s64(((int64_t)tbat_adc * TBAT_LSB * BAT_THERM_SCALE), TBAT_DENOMINATOR); int tbat_esr_decidegree = div64_s64(((int64_t)tbat_esr_adc * TBAT_LSB * BAT_THERM_SCALE), TBAT_DENOMINATOR); int vref_tbat_decidegree = div64_s64(((int64_t)vref_adc * TBAT_LSB * BAT_THERM_SCALE), TBAT_DENOMINATOR); r_pack_nom = tbat_decidegree * (vref_tbat_decidegree - tbat_esr_decidegree) - tbat_esr_decidegree * (vref_tbat_decidegree - tbat_decidegree); r_pack_denom = DIV_ROUND_CLOSEST(ibat_esr_10na, MILLI_TO_10NANO) * (vref_tbat_decidegree - tbat_decidegree) - DIV_ROUND_CLOSEST(ibat_10na, MILLI_TO_10NANO) * (vref_tbat_decidegree - tbat_esr_decidegree); if (r_pack_denom != 0) { r_pack = div64_s64(r_pack_nom * BAT_THERM_CAL_NUM, r_pack_denom); r_pack = div64_s64(r_pack, BAT_THERM_CAL_DENOM); reg_val = (u8)r_pack; qbg_dbg(chip, QBG_DEBUG_SDAM, "tbat=%d, tbat_esr=%d vref=%d 10uv, therm trace resistance is %#x\n", tbat_decidegree, tbat_esr_decidegree, vref_tbat_decidegree, reg_val); rc = regmap_write(chip->regmap, chip->adc_cmn_wb_base + BAT_THERM_IBAT_CTL_2, reg_val); if (rc < 0) return rc; } return 0; } static int qbg_process_fifo(struct qti_qbg *chip, u32 fifo_count) { struct fifo_data *fifo; int rc = 0, i = 0, ibat = 0, ibat_esr = 0; unsigned char data_tag; unsigned int vbat1 = 0, vbat2 = 0, tbat = 0, ibat_t = 0, esr = 0; unsigned int vbat1_esr = 0, vbat2_esr = 0, tbat_esr = 0, ibat_t_esr = 0; time64_t timestamp = 0; if (!fifo_count) { qbg_dbg(chip, QBG_DEBUG_SDAM, "No FIFO data\n"); return -EINVAL; } fifo = kcalloc(fifo_count, sizeof(*fifo), GFP_KERNEL); if (!fifo) return -ENOMEM; rc = get_rtc_time(chip, ×tamp); if (rc < 0) { pr_err("Failed to read rtc time, rc=%d\n", rc); return rc; } chip->kdata.param[QBG_PARAM_FIFO_TIMESTAMP].valid = true; chip->kdata.param[QBG_PARAM_FIFO_TIMESTAMP].data = timestamp; /* Read raw FIFO data from SDAM */ rc = qbg_sdam_get_fifo_data(chip, fifo, fifo_count); if (rc < 0) { pr_err("Failed to get QBG FIFO data, rc=%d\n", rc); goto ret; } data_tag = fifo[0].data_tag; if (fifo_count > QBG_SDAM_ESR_PULSE_FIFO_INDEX) data_tag = fifo[QBG_SDAM_ESR_PULSE_FIFO_INDEX].data_tag; data_tag = (data_tag & 0xC0) >> 6; if (data_tag == QBG_FAST_CHAR) { if (fifo_count <= QBG_SDAM_ESR_PULSE_FIFO_INDEX) { pr_err("Not enough FIFO samples in fast char mode\n"); goto qbg_cal; } rc = qbg_decode_fifo_data(fifo[QBG_SDAM_ESR_PULSE_FIFO_INDEX - 1], &vbat1, &vbat2, &ibat, &tbat, &ibat_t); if (rc < 0) { pr_err("Couldn't decode FIFO before ESR pulse in fast char mode, rc=%d\n", rc); goto qbg_cal; } rc = qbg_decode_fifo_data(fifo[QBG_SDAM_ESR_PULSE_FIFO_INDEX], &vbat1_esr, &vbat2_esr, &ibat_esr, &tbat_esr, &ibat_t_esr); if (rc < 0) { pr_err("Couldn't decode FIFO before ESR pulse in fast char mode, rc=%d\n", rc); goto qbg_cal; } if ((ibat < ibat_esr) && (ibat < (ESR_PULSE_10NA(ESR_FCC_150MA) + ESR_IBAT_QUALIFY_OFFSET_10NA)) && (vbat1 > vbat1_esr) && (tbat > tbat_esr)) { esr = (vbat1 - vbat1_esr) / ((ibat_esr - ibat) / 10000); esr *= 10000; chip->esr = esr; qbg_dbg(chip, QBG_DEBUG_SDAM, "ESR:%u, ibat=%d ibat_esr=%d vbat1:%u vbat1_esr:%u\n", esr, ibat, ibat_esr, vbat1, vbat1_esr); rc = qbg_set_therm_trace_resistance(chip, fifo[QBG_SDAM_ESR_PULSE_FIFO_INDEX - 1].vref, tbat, ibat, ibat_esr, tbat_esr); if (rc < 0) { pr_err("Failed to set therm trace resistance, rc=%d\n", rc); goto qbg_cal; } } else { pr_err("Couldn't calculate ESR, ibat=%d ibat_esr=%d vbat1:%u vbat1_esr:%u\n", ibat, ibat_esr, vbat1, vbat1_esr); } } qbg_cal: for (i = 0; i < fifo_count; i++) { rc = qbg_decode_fifo_data(fifo[i], &vbat1, &vbat2, &ibat, &tbat, &ibat_t); if (rc < 0) { pr_err("Failed to decode fifo %d, rc=%d\n", i, rc); goto ret; } qbg_dbg(chip, QBG_DEBUG_SDAM, "vbat1:%u ibat:%d tbat:%u ibat_t:%u\n", vbat1, ibat, tbat, ibat_t); chip->kdata.fifo[i].v1 = vbat1; chip->kdata.fifo[i].v2 = vbat2; chip->kdata.fifo[i].i = ibat; chip->kdata.fifo[i].tbat = tbat; chip->kdata.fifo[i].ibat = ibat_t; chip->kdata.fifo[i].data_tag = fifo[i].data_tag; chip->kdata.fifo[i].qg_sts = fifo[i].qg_sts; } if (!chip->enable_fifo_depth_half && ((-1 * ibat) > chip->rated_capacity)) { chip->enable_fifo_depth_half = true; rc = qbg_hpm_fifo_depth_half(chip, fifo_count, chip->enable_fifo_depth_half); qbg_dbg(chip, QBG_DEBUG_SDAM, "HPM FIFO count reduced by half ibat = %d rated_capacity=%d\n", ibat, chip->rated_capacity); } else if (chip->enable_fifo_depth_half && ((-1 * ibat) < chip->rated_capacity)) { chip->enable_fifo_depth_half = false; rc = qbg_hpm_fifo_depth_half(chip, fifo_count, chip->enable_fifo_depth_half); qbg_dbg(chip, QBG_DEBUG_SDAM, "HPM FIFO count restored ibat = %d rated_capacity=%d\n", ibat, chip->rated_capacity); } if (rc < 0) pr_err("Failed to update fifo depth, rc=%d\n", rc); ret: kfree(fifo); return rc; } static int qbg_clear_fifo_data(struct qti_qbg *chip) { int rc = 0, i; u8 val[2] = {0, 0}; rc = qbg_sdam_write(chip, QBG_SDAM_BASE(chip, SDAM_CTRL0) + QBG_SDAM_FIFO_COUNT_OFFSET, val, 2); if (rc < 0) { pr_err("Failed to clear QBG FIFO Count, rc=%d\n", rc); return rc; } for (i = 0; i < 10; i++) { val[0] = 0; rc = qbg_sdam_write(chip, QBG_SDAM_BASE(chip, SDAM_CTRL0) + QBG_SDAM_INT_TEST_VAL, val, 1); if (rc < 0) { pr_err("Failed to SDAM0 test val to 0, rc=%d\n", rc); return rc; } /* Handshake with PBS to access FIFO data */ rc = qbg_sdam_read(chip, QBG_SDAM_BASE(chip, SDAM_CTRL0) + QBG_SDAM_INT_TEST_VAL, val, 1); if (rc < 0) { pr_err("Failed to read QBG SDAM, rc=%d\n", rc); return rc; } if (val[0] == 0) break; } if (i == 10) return -EINVAL; val[0] = QBG_SDAM_START_OFFSET; val[1] = 0x0; rc = qbg_sdam_write(chip, QBG_SDAM_BASE(chip, SDAM_DATA0) + QBG_SDAM_DATA_PUSH_COUNTER_OFFSET, val, 2); if (rc < 0) { pr_err("Failed to configure QBG data push counter, rc=%d\n", rc); return rc; } val[0] = 0x0; rc = qbg_sdam_write(chip, QBG_SDAM_BASE(chip, SDAM_CTRL0) + QBG_SDAM_PBS_STATUS_OFFSET, val, 1); if (rc < 0) { pr_err("Failed to set QBG PBS status, rc=%d\n", rc); return rc; } return 0; } static int qbg_enable_data_full_int(struct qti_qbg *chip, bool enable) { /* Writing 0x80 while FIFO is full will raise the interrupt */ u8 val = (enable ? 0x80 : 0x00); return qbg_sdam_write(chip, QBG_SDAM_BASE(chip, SDAM_CTRL0) + QBG_SDAM_INT_TEST1, &val, 1); } static int qbg_init_sdam(struct qti_qbg *chip) { int rc = 0; u8 val[2] = {0}; val[0] = 0x80; rc = qbg_sdam_write(chip, QBG_SDAM_BASE(chip, SDAM_CTRL0) + QBG_SDAM_INT_TEST1, val, 1); if (rc < 0) { pr_err("Failed to write QBG SDAM, rc=%d\n", rc); return rc; } val[0] = 0; rc = qbg_sdam_read(chip, QBG_SDAM_BASE(chip, SDAM_CTRL0) + QBG_SDAM_INT_TEST_VAL, val, 1); if (rc < 0) { pr_err("Faiiled to read QBG SDAM, rc=%d\n", rc); return rc; } if (val[0] == QBG_SDAM_TEST_VAL_1_SET) { rc = qbg_clear_fifo_data(chip); if (rc < 0) { pr_err("Failed to clear QBG FIFO data, rc=%d\n", rc); return rc; } } return rc; } static int qbg_init_esr(struct qti_qbg *chip) { int rc = 0; u8 val = ESR_FCC_150MA; rc = qbg_sdam_write(chip, QBG_SDAM_BASE(chip, SDAM_CTRL0) + QBG_ESR_PULSE_FCC_REDUCE_OFFSET, &val, 1); if (rc < 0) pr_err("Failed to write QBG ESR pulse FCC reduction offset, rc=%d\n", rc); return rc; } #define QBG_VBAT_EMPTY_STEP_THRES_UV 49920 static void qbg_init_vbatt_empty_threshold(struct qti_qbg *chip) { int rc = 0; u8 val = 0; u32 vbatt_in_uv = 0; /* Check for Vbatt empty threshold limits -- Should be b/w 0 to 6V */ if (chip->vbatt_empty_threshold_mv == 0 || chip->vbatt_empty_threshold_mv > 6000) { pr_info("Invalid Vbatt_empty threshold, value: %d\n", chip->vbatt_empty_threshold_mv); return; } vbatt_in_uv = chip->vbatt_empty_threshold_mv * 1000; val = (u8) (vbatt_in_uv / QBG_VBAT_EMPTY_STEP_THRES_UV); rc = qbg_write(chip, QBG_MAIN_VBAT_EMPTY_THRESH, &val, 1); if (rc < 0) pr_err("Failed to write QBG VBATT EMPTY THRESHOLD offset, rc=%d\n", rc); } static int qbg_force_fast_char(struct qti_qbg *chip, bool force) { int rc = 0; /* The default ground used for BAT_THERM measurement is GND_XOADC*/ u8 batt_therm_gnd_sel = 0x80; u8 val = force ? (1 << FORCE_FAST_CHAR_SHIFT) : 0; rc = qbg_write(chip, QBG_MAIN_QBG_STATE_FORCE_CMD, &val, 1); if (rc < 0) { pr_err("Failed to %s QBG fast char mode, rc=%d\n", force ? "enable" : "disable", rc); return rc; } /* Set the ground of BAT_THERM measurement to PACK_SNS_M */ if (force) batt_therm_gnd_sel = 0x40; rc = regmap_write(chip->regmap, chip->adc_cmn_base + VADC_ANA_PARAM_4, batt_therm_gnd_sel); if (rc < 0) pr_err("Failed to set batt therm gnd sel rc=%d\n", rc); return rc; } static int qbg_handle_fast_char(struct qti_qbg *chip) { int rc = 0; ktime_t now; u8 *data; ssize_t len = 0; if (chip->skip_esr_state) { data = nvmem_cell_read(chip->skip_esr_state, &len); if (IS_ERR(data)) { pr_err("Failed to read skip_esr_state from SDAM\n"); return PTR_ERR(data); } if (*data) { qbg_dbg(chip, QBG_DEBUG_STATUS, "skip_esr=1 in_fast_char=%d\n", chip->in_fast_char); if (chip->in_fast_char) { rc = qbg_force_fast_char(chip, false); if (rc < 0) { pr_err("Failed to get out of fast char mode, rc=%d\n", rc); return rc; } chip->in_fast_char = false; } return 0; } } if (chip->in_fast_char) { qbg_dbg(chip, QBG_DEBUG_STATUS, "QBG ESR pulse disabled\n"); rc = qbg_force_fast_char(chip, false); if (rc < 0) { pr_err("Failed to get out of fast char mode, rc=%d\n", rc); return rc; } chip->in_fast_char = false; } else if (chip->charge_type == POWER_SUPPLY_CHARGE_TYPE_ADAPTIVE) { now = ktime_get(); if ((ktime_ms_delta(now, chip->last_fast_char_time) > QBG_FAST_CHAR_DELTA_MS) && !chip->in_fast_char) { qbg_dbg(chip, QBG_DEBUG_STATUS, "QBG ESR pulse enabled\n"); rc = qbg_force_fast_char(chip, true); if (rc < 0) { pr_err("Failed to put QBG to fast char mode, rc=%d\n", rc); return rc; } chip->in_fast_char = true; chip->last_fast_char_time = now; } } return rc; } static irqreturn_t qbg_data_full_irq_handler(int irq, void *_chip) { struct qti_qbg *chip = _chip; int rc; u32 fifo_count = 0; qbg_dbg(chip, QBG_DEBUG_IRQ, "DATA FULL IRQ triggered\n"); /* Disable fast char for PM5100 V1 */ if (chip->rev4 != REVISION_V1) { rc = qbg_handle_fast_char(chip); if (rc < 0) { pr_err("Failed to handle QBG fast char, rc=%d\n", rc); return IRQ_HANDLED; } } mutex_lock(&chip->fifo_lock); memset(&chip->kdata.fifo, 0, sizeof(struct k_fifo_data)); rc = qbg_get_fifo_count(chip, &fifo_count); if (rc < 0) { pr_err("Failed to get QBG FIFO length, rc=%d\n", rc); goto done; } rc = qbg_process_fifo(chip, fifo_count); if (rc < 0) { pr_err("Failed to process QBG FIFO, rc=%d\n", rc); goto done; } chip->kdata.fifo_count = fifo_count; rc = qbg_init_sdam(chip); if (rc < 0) { pr_err("Failed to initialize QBG sdam, rc=%d\n", rc); goto done; } chip->data_ready = true; /* Wakeup the read thread */ wake_up_interruptible(&chip->qbg_wait_q); done: mutex_unlock(&chip->fifo_lock); return IRQ_HANDLED; } static irqreturn_t qbg_vbatt_empty_irq_handler(int irq, void *_chip) { struct qti_qbg *chip = _chip; qbg_dbg(chip, QBG_DEBUG_IRQ, "Vbatt empty IRQ Triggered\n"); return IRQ_HANDLED; } static int qbg_notifier_cb(struct notifier_block *nb, unsigned long event, void *data) { struct power_supply *psy = data; struct qti_qbg *chip = container_of(nb, struct qti_qbg, nb); if (event != PSY_EVENT_PROP_CHANGED) return NOTIFY_OK; if (work_pending(&chip->status_change_work)) return NOTIFY_OK; if ((strcmp(psy->desc->name, "battery") == 0) || (strcmp(psy->desc->name, "usb") == 0) || (strcmp(psy->desc->name, "dc") == 0)) { schedule_work(&chip->status_change_work); } return NOTIFY_OK; } static int qbg_read_range_data_from_node(struct device_node *node, const char *prop_str, struct ranges *ranges, int max_threshold, u32 max_value) { int rc = 0, i, length, per_tuple_length, tuples; if (!node || !prop_str || !ranges) { pr_err("Invalid parameters passed\n"); return -EINVAL; } rc = of_property_count_elems_of_size(node, prop_str, sizeof(u32)); if (rc < 0) { pr_err("Count %s failed, rc=%d\n", prop_str, rc); return rc; } length = rc; per_tuple_length = sizeof(struct range_data) / sizeof(u32); if (length % per_tuple_length) { pr_err("%s length (%d) should be multiple of %d\n", prop_str, length, per_tuple_length); return -EINVAL; } tuples = length / per_tuple_length; if (tuples > QBG_MAX_STEP_CHG_ENTRIES) { pr_err("too many entries(%d), only %d allowed\n", tuples, QBG_MAX_STEP_CHG_ENTRIES); return -EINVAL; } rc = of_property_read_u32_array(node, prop_str, (u32 *)ranges->data, length); if (rc < 0) { pr_err("Read %s failed, rc=%d\n", prop_str, rc); return rc; } for (i = 0; i < tuples; i++) { if (ranges->data[i].low_threshold > ranges->data[i].high_threshold) { pr_err("%s thresholds should be in ascendant ranges data\n", prop_str); rc = -EINVAL; goto clean; } if (i != 0) { if (ranges->data[i - 1].high_threshold > ranges->data[i].low_threshold) { pr_err("%s thresholds should be in ascendant ranges data\n", prop_str); rc = -EINVAL; goto clean; } } if (ranges->data[i].low_threshold > max_threshold) ranges->data[i].low_threshold = max_threshold; if (ranges->data[i].high_threshold > max_threshold) ranges->data[i].high_threshold = max_threshold; if (ranges->data[i].value > max_value) ranges->data[i].value = max_value; } ranges->range_count = tuples; ranges->valid = true; return rc; clean: memset(ranges, 0, tuples * sizeof(struct range_data)); return rc; } #define QBG_DEFAULT_JEITA_FULL_FV_10NV 420000000 #define QBG_DEFAULT_BATTERY_BETA 4250 #define QBG_DEFAULT_BATTERY_THERM_KOHM 100 #define QBG_DEFAULT_JEITA_WARM_ADC_DATA 0x25e3 /* WARM = 40 DegC */ #define QBG_DEFAULT_JEITA_COOL_ADC_DATA 0x5314 /* COOL = 5 DegC */ static int parse_step_chg_jeita_params(struct qti_qbg *chip, struct device_node *profile_node) { struct qbg_step_chg_jeita_params *step_chg_jeita; bool soc_based_step_chg = false; bool ocv_based_step_chg = false; int rc; int i = 0; u32 temp[2]; bool jeita_thresh_valid = true; step_chg_jeita = devm_kzalloc(chip->dev, sizeof(*step_chg_jeita), GFP_KERNEL); if (!step_chg_jeita) return -ENOMEM; /* choose the lower of the two soft threaholds as the jeita-full-fv */ step_chg_jeita->jeita_full_fv_10nv = QBG_DEFAULT_JEITA_FULL_FV_10NV; rc = of_property_read_u32_array(profile_node, "qcom,jeita-soft-fv-uv", temp, 2); if (!rc) step_chg_jeita->jeita_full_fv_10nv = min(temp[0], temp[1]) * MICRO_TO_10NANO; else pr_debug("Failed to read jeita full fv from battery profile, rc=%d\n", rc); step_chg_jeita->jeita_full_iterm_10na = chip->iterm_ma * MILLI_TO_10NANO; soc_based_step_chg = of_property_read_bool(profile_node, "qcom,soc-based-step-chg"); ocv_based_step_chg = of_property_read_bool(profile_node, "qcom,ocv-based-step-chg"); if (soc_based_step_chg) step_chg_jeita->ttf_calc_mode = TTF_MODE_SOC_STEP_CHG; else if (ocv_based_step_chg) step_chg_jeita->ttf_calc_mode = TTF_MODE_OCV_STEP_CHG; step_chg_jeita->battery_beta = QBG_DEFAULT_BATTERY_BETA; rc = of_property_read_u32(profile_node, "qcom,battery-beta", &step_chg_jeita->battery_beta); if (rc < 0) pr_debug("Failed to read battery beta form battery profile, rc=%d\n", rc); step_chg_jeita->battery_therm_kohm = QBG_DEFAULT_BATTERY_THERM_KOHM; rc = of_property_read_u32(profile_node, "qcom,battery-therm-kohm", &step_chg_jeita->battery_therm_kohm); if (rc < 0) pr_debug("Failed to read battery therm from battery profile, rc=%d\n", rc); step_chg_jeita->jeita_cool_adc_value = QBG_DEFAULT_JEITA_COOL_ADC_DATA; step_chg_jeita->jeita_warm_adc_value = QBG_DEFAULT_JEITA_WARM_ADC_DATA; rc = of_property_read_u32_array(profile_node, "qcom,jeita-soft-thresholds", temp, 2); if (!rc) { step_chg_jeita->jeita_cool_adc_value = temp[0]; step_chg_jeita->jeita_warm_adc_value = temp[1]; } else { jeita_thresh_valid = false; pr_debug("Failed to read jeita cool and warm value from battery profile, rc=%d\n", rc); } rc = qbg_read_range_data_from_node(profile_node, "qcom,jeita-fcc-ranges", &step_chg_jeita->jeita_fcc_cfg, BATT_HOT_DECIDEGREE_MAX, chip->fastchg_curr_ma * MILLI_TO_MICRO); if (rc < 0) pr_debug("Failed to read qcom,jeita-fcc-ranges from battery profile, rc=%d\n", rc); rc = qbg_read_range_data_from_node(profile_node, "qcom,jeita-fv-ranges", &step_chg_jeita->jeita_fv_cfg, BATT_HOT_DECIDEGREE_MAX, chip->float_volt_uv); if (rc < 0) pr_debug("Failed to read qcom,jeita-fv-ranges from battery profile, rc=%d\n", rc); if ((step_chg_jeita->jeita_fcc_cfg.valid != step_chg_jeita->jeita_fv_cfg.valid) || (jeita_thresh_valid != step_chg_jeita->jeita_fcc_cfg.valid)) { pr_err("battery JEITA configuration is not valid\n"); return -EINVAL; } rc = qbg_read_range_data_from_node(profile_node, "qcom,step-chg-ranges", &step_chg_jeita->step_fcc_cfg, soc_based_step_chg ? 100 : chip->float_volt_uv, chip->fastchg_curr_ma * MILLI_TO_MICRO); if (rc < 0) pr_debug("Failed to read qcom,step-chg-ranges from battery profile, rc=%d\n", rc); qbg_dbg(chip, QBG_DEBUG_PROFILE, "jeita_full_fv = %dnv, jeita_full_iterm = %dna, jeita_warm_adc = 0x%x, jeita_cool_adc = 0x%x, battery_beta = %d, battery_therm = %dkohm\n", step_chg_jeita->jeita_full_fv_10nv, step_chg_jeita->jeita_full_iterm_10na, step_chg_jeita->jeita_warm_adc_value, step_chg_jeita->jeita_cool_adc_value, step_chg_jeita->battery_beta, step_chg_jeita->battery_therm_kohm); pr_debug("step-chg-ranges:\n"); for (i = 0; i < step_chg_jeita->step_fcc_cfg.range_count; i++) { pr_debug("%d %d %d\n", step_chg_jeita->step_fcc_cfg.data[i].low_threshold, step_chg_jeita->step_fcc_cfg.data[i].high_threshold, step_chg_jeita->step_fcc_cfg.data[i].value); } pr_debug("jeita-fcc-ranges:\n"); for (i = 0; i < step_chg_jeita->jeita_fcc_cfg.range_count; i++) { pr_debug("%d %d %d\n", step_chg_jeita->jeita_fcc_cfg.data[i].low_threshold, step_chg_jeita->jeita_fcc_cfg.data[i].high_threshold, step_chg_jeita->jeita_fcc_cfg.data[i].value); } pr_debug("jeita-fv-ranges:\n"); for (i = 0; i < step_chg_jeita->jeita_fv_cfg.range_count; i++) { pr_debug("%d %d %d\n", step_chg_jeita->jeita_fv_cfg.data[i].low_threshold, step_chg_jeita->jeita_fv_cfg.data[i].high_threshold, step_chg_jeita->jeita_fv_cfg.data[i].value); } chip->step_chg_jeita_params = step_chg_jeita; return 0; } #define QBG_DEFAULT_RECHARGE_ITERM_MA 150 #define QBG_DEFAULT_RECHARGE_SOC_DELTA 5 #define QBG_DEFAULT_RECHARGE_VFLT_DELTA 100 static int qbg_load_battery_profile(struct qti_qbg *chip) { struct device_node *node = chip->dev->of_node; struct device_node *profile_node; int rc; u32 temp[2]; chip->batt_node = of_find_node_by_name(node, "qcom,battery-data"); if (!chip->batt_node) { pr_err("Batterydata not available\n"); return -ENXIO; } profile_node = of_batterydata_get_best_profile(chip->batt_node, chip->batt_id_ohm / 1000, NULL); if (IS_ERR_OR_NULL(profile_node)) { rc = profile_node ? PTR_ERR(profile_node) : -EINVAL; pr_err("Failed to detect valid QBG battery profile, rc=%d\n", rc); goto out; } chip->battery = devm_kzalloc(chip->dev, sizeof(*chip->battery), GFP_KERNEL); if (!chip->battery) { rc = -ENOMEM; goto out; } rc = qbg_batterydata_init(profile_node, chip->battery); if (rc < 0) { pr_err("Failed to load batterydata, rc=%d\n", rc); chip->profile_loaded = false; goto out; } rc = of_property_read_string(profile_node, "qcom,battery-type", &chip->batt_type_str); if (rc < 0) { pr_err("Failed to get battery name, rc=%d\n", rc); goto out; } rc = of_property_read_u32(profile_node, "qcom,max-voltage-uv", &chip->float_volt_uv); if (rc < 0) { pr_err("Failed to read battery float-voltage, rc:%d\n", rc); chip->float_volt_uv = -EINVAL; goto out; } rc = of_property_read_u32(profile_node, "qcom,fastchg-current-ma", &chip->fastchg_curr_ma); if (rc < 0) { pr_err("Failed to read battery fastcharge current, rc:%d\n", rc); chip->fastchg_curr_ma = -EINVAL; goto out; } rc = of_property_read_u32(profile_node, "qcom,capacity", &chip->rated_capacity); if (rc < 0) { pr_err("Failed to read battery rated capacity, rc:%d\n", rc); chip->rated_capacity = -EINVAL; goto out; } chip->rated_capacity *= MILLI_TO_10NANO; rc = of_property_read_u32_array(profile_node, "qcom,battery-capacity", temp, 2); if (rc) { pr_err("Failed to read battery nominal capacity, rc=%d\n", rc); goto out; } chip->nominal_capacity = temp[0]; chip->recharge_iterm_ma = QBG_DEFAULT_RECHARGE_ITERM_MA; rc = of_property_read_u32(profile_node, "qcom,recharge-iterm-ma", &temp[0]); if (!rc) chip->recharge_iterm_ma = temp[0]; chip->recharge_soc = 100 - QBG_DEFAULT_RECHARGE_SOC_DELTA; rc = of_property_read_u32(profile_node, "qcom,recharge-soc-delta", &temp[0]); if (!rc) chip->recharge_soc = 100 - temp[0]; chip->recharge_vflt_delta_mv = QBG_DEFAULT_RECHARGE_VFLT_DELTA; rc = of_property_read_u32(profile_node, "qcom,recharge-vflt-delta", &temp[0]); if (!rc) chip->recharge_vflt_delta_mv = temp[0]; qbg_dbg(chip, QBG_DEBUG_SDAM, "Recharge SOC=% Recharge-Vflt=%duV Recharge-Ibat=%dmA\n", chip->recharge_soc, chip->recharge_vflt_delta_mv, chip->recharge_iterm_ma); rc = parse_step_chg_jeita_params(chip, profile_node); out: of_node_put(chip->batt_node); return rc; } #define DEBUG_BATT_ID_LOW 6000 #define DEBUG_BATT_ID_HIGH 8500 static bool is_debug_batt_id(struct qti_qbg *chip) { if (is_between(DEBUG_BATT_ID_LOW, DEBUG_BATT_ID_HIGH, chip->batt_id_ohm)) return true; return false; } #define DEFAULT_BATT_TYPE "Unknown Battery" #define MISSING_BATT_TYPE "Missing Battery" #define DEBUG_BATT_TYPE "Debug Board" static const char *qbg_get_battery_type(struct qti_qbg *chip) { if (chip->battery_missing) return MISSING_BATT_TYPE; if (is_debug_batt_id(chip)) return DEBUG_BATT_TYPE; if (chip->batt_type_str && chip->profile_loaded) return chip->batt_type_str; return DEFAULT_BATT_TYPE; } #define DEBUG_BATT_SOC 67 #define BATT_MISSING_SOC 50 static int qbg_get_battery_capacity(struct qti_qbg *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->soc; return 0; } #define TEN_NANO_TO_MICRO 100 static int qbg_get_battery_voltage(struct qti_qbg *chip, int *vbat_uv) { int rc; unsigned short acc0_data; u8 buf[2]; if (chip->battery_missing) { *vbat_uv = 3700000; return 0; } rc = qbg_read(chip, QBG_MAIN_LAST_BURST_AVG_ACC0_DATA0, buf, 2); if (rc < 0) { pr_err("Failed to read average vbatt, rc=%d\n", rc); return rc; } acc0_data = buf[0] | (buf[1] << 8); *vbat_uv = acc0_data * VBATT_1S_LSB; *vbat_uv = *vbat_uv / TEN_NANO_TO_MICRO; return 0; } static int qbg_get_battery_current(struct qti_qbg *chip, int *ibat_ua) { int rc; unsigned short acc2_data; u8 buf[2]; if (chip->battery_missing) { *ibat_ua = 0; return 0; } rc = qbg_read(chip, QBG_MAIN_LAST_BURST_AVG_ACC2_DATA0, buf, 2); if (rc < 0) { pr_err("Failed to read average ibatt, rc=%d\n", rc); return rc; } acc2_data = buf[0] | (buf[1] << 8); *ibat_ua = ((int16_t)acc2_data) * IBATT_10A_LSB * ICHG_FS_10A; *ibat_ua = *ibat_ua / TEN_NANO_TO_MICRO; return 0; } static int qbg_get_battery_temp(struct qti_qbg *chip, int *temp) { int rc; if (!chip->batt_temp_chan) return -EINVAL; rc = iio_read_channel_processed(chip->batt_temp_chan, temp); if (rc < 0) { pr_err("Failed to read BATT_TEMP over ADC, rc=%d\n", rc); return rc; } chip->tbat = *temp; return 0; } #define UAH_FACTOR 10 static int qbg_get_charge_counter(struct qti_qbg *chip, int *charge_count) { if (is_debug_batt_id(chip) || chip->battery_missing || chip->battery_unknown) { *charge_count = -EINVAL; return 0; } *charge_count = chip->learned_capacity * chip->soc * UAH_FACTOR; return 0; } static bool is_battery_present(struct qti_qbg *chip) { bool present = false; u8 reg; int rc; rc = qbg_read(chip, QBG_MAIN_STATUS2, ®, 1); if (rc < 0) pr_err("Failed to read battery presence, rc=%d\n", rc); else present = !(reg & QBG_BATTERY_MISSING_BIT); return present; } static int get_batt_id_ohm(struct qti_qbg *chip, u32 *batt_id_ohm) { int rc, batt_id; if (!chip->batt_id_chan) return -EINVAL; /* Read battery-id */ rc = iio_read_channel_processed(chip->batt_id_chan, &batt_id); if (rc < 0) { pr_err("Failed to read BATT_ID over ADC, rc=%d\n", rc); return rc; } *batt_id_ohm = (u32)batt_id; qbg_dbg(chip, QBG_DEBUG_PROFILE, "batt_id_ohm=%u\n", *batt_id_ohm); return 0; } static int qbg_setup_battery(struct qti_qbg *chip) { int rc = 0; u8 ocv_boost_val = 0x02; chip->profile_loaded = false; chip->battery_unknown = false; if (!is_battery_present(chip)) { qbg_dbg(chip, QBG_DEBUG_PROFILE, "Battery Missing!\n"); chip->battery_missing = 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); } else { rc = qbg_load_battery_profile(chip); if (rc < 0) pr_err("Failed to load battery-profile, rc=%d\n", rc); else chip->profile_loaded = true; } /* Update OCV boost headroom compensation value to 4.3V in debug battery case */ if (is_debug_batt_id(chip)) { rc = qbg_sdam_write(chip, QBG_SDAM_BASE(chip, SDAM_CTRL0) + QBG_SDAM_BHARGER_OCV_HDRM_OFFSET, &ocv_boost_val, 1); } if (!strcmp(qbg_get_battery_type(chip), DEFAULT_BATT_TYPE)) { chip->batt_type_str = DEFAULT_BATT_TYPE; chip->battery_unknown = true; chip->soc = BATT_MISSING_SOC; rc = 0; } } qbg_dbg(chip, QBG_DEBUG_PROFILE, "battery_missing=%s batt_id=%d Ohm profile_loaded=%d battery_type=%s\n", chip->battery_missing ? "true" : "false", chip->batt_id_ohm, chip->profile_loaded, chip->batt_type_str); return rc; } static int qbg_get_pon_reading(struct qti_qbg *chip, unsigned int *ocv, unsigned int *ibat) { int rc; unsigned short int ocv0_data, ibat_data; u8 acc_data[6]; rc = qbg_read(chip, QBG_MAIN_PON_OCV_ACC0_DATA0, (u8 *)acc_data, 6); if (rc < 0) { pr_err("Failed to read PON accumulator data, rc=%d\n", rc); return rc; } ocv0_data = (acc_data[1] << 8) | acc_data[0]; ibat_data = (acc_data[5] << 8) | acc_data[4]; *ocv = ocv0_data * VBATT_1S_LSB; if (*ocv < VBAT_0PCT_OCV) *ocv = VBAT_0PCT_OCV; *ibat = ibat_data * IBATT_10A_LSB * ICHG_FS_10A; qbg_dbg(chip, QBG_DEBUG_PON, "ocv:%u ibat:%u\n", ocv, ibat); return 0; } static int qbg_determine_pon_soc(struct qti_qbg *chip) { int rc, batt_temp; union power_supply_propval prop = {0, }; unsigned long time_diff = 0; unsigned int pon_ocv; time64_t rtc_sec; /* Get RTC time here */ rc = get_rtc_time(chip, &rtc_sec); if (rc < 0) { pr_err("Failed to read rtc time, rc=%d\n", rc); return rc; } if (!chip->profile_loaded) { qbg_dbg(chip, QBG_DEBUG_SOC, "No Profile, skipping PON soc\n"); return 0; } rc = qbg_sdam_get_essential_params(chip, (u8 *)&chip->essential_params); if (rc < 0) { pr_err("Failed to read essential params, rc=%d\n", rc); return rc; } rc = qbg_get_pon_reading(chip, &chip->pon_ocv, &chip->pon_ibat); if (rc < 0) { pr_err("Failed to get QBG PON reading, rc=%d\n", rc); return rc; } /* Read battery-temp */ rc = iio_read_channel_processed(chip->batt_temp_chan, &batt_temp); if (rc < 0) { pr_err("Failed to read BATT_TEMP over ADC, rc=%d\n", rc); return rc; } chip->pon_tbat = (unsigned int)batt_temp; if (is_batt_available(chip)) { 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); } pon_ocv = chip->pon_ocv / 10000; rc = qbg_lookup_soc_ocv(chip->battery, &chip->pon_soc, pon_ocv, prop.intval == POWER_SUPPLY_STATUS_CHARGING); if (rc < 0) { pr_err("Failed to lookup PON SOC, rc=%d\n", rc); return rc; } chip->pon_soc = DIV_ROUND_CLOSEST(chip->pon_soc, 100); chip->soc = chip->pon_soc; qbg_dbg(chip, QBG_DEBUG_PON, "Shutdown: SOC=%d OCV=%duV Ibat:%duA timediff=%lusecs, time_now=%lusecs\n", chip->pon_soc, chip->pon_ocv, chip->pon_ibat, time_diff, rtc_sec); return 0; } static int qbg_psy_get_prop(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *pval) { if (psp == POWER_SUPPLY_PROP_TYPE) pval->intval = POWER_SUPPLY_TYPE_MAINS; return 0; } static enum power_supply_property qbg_psy_props[] = { POWER_SUPPLY_PROP_TYPE, }; static struct power_supply_desc qbg_psy_desc = { .name = "bms", .type = POWER_SUPPLY_TYPE_MAINS, .properties = qbg_psy_props, .num_properties = ARRAY_SIZE(qbg_psy_props), .get_property = qbg_psy_get_prop, }; static int qbg_init_psy(struct qti_qbg *chip) { struct power_supply_config qbg_psy_cfg = {}; int rc; qbg_psy_cfg.drv_data = chip; chip->qbg_psy = devm_power_supply_register(chip->dev, &qbg_psy_desc, &qbg_psy_cfg); if (IS_ERR_OR_NULL(chip->qbg_psy)) { pr_err("Failed to register qbg_psy, rc = %d\n", PTR_ERR(chip->qbg_psy)); return PTR_ERR(chip->qbg_psy); } chip->nb.notifier_call = qbg_notifier_cb; rc = power_supply_reg_notifier(&chip->nb); if (rc < 0) pr_err("Failed to register psy notifier rc = %d\n", rc); return rc; } static int qbg_iio_write_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, int val1, int val2, long mask) { return 0; } static int qbg_iio_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, int *val1, int *val2, long mask) { struct qti_qbg *chip = iio_priv(indio_dev); int rc = 0; *val1 = 0; switch (chan->channel) { case PSY_IIO_DEBUG_BATTERY: *val1 = is_debug_batt_id(chip); break; case PSY_IIO_CAPACITY: rc = qbg_get_battery_capacity(chip, val1); break; case PSY_IIO_REAL_CAPACITY: rc = qbg_get_battery_capacity(chip, val1); break; case PSY_IIO_TEMP: rc = qbg_get_battery_temp(chip, val1); break; case PSY_IIO_VOLTAGE_MAX: *val1 = chip->float_volt_uv; break; case PSY_IIO_VOLTAGE_OCV: *val1 = chip->ocv_uv; break; case PSY_IIO_VOLTAGE_AVG: rc = qbg_get_battery_voltage(chip, val1); break; case PSY_IIO_VOLTAGE_NOW: rc = qbg_get_battery_voltage(chip, val1); break; case PSY_IIO_CURRENT_NOW: rc = qbg_get_battery_current(chip, val1); break; case PSY_IIO_RESISTANCE_ID: *val1 = chip->batt_id_ohm; break; case PSY_IIO_TIME_TO_FULL_AVG: *val1 = chip->ttf; break; case PSY_IIO_TIME_TO_FULL_NOW: *val1 = chip->ttf; break; case PSY_IIO_TIME_TO_EMPTY_AVG: *val1 = chip->tte; break; case PSY_IIO_ESR_ACTUAL: *val1 = chip->esr; break; case PSY_IIO_SOH: *val1 = chip->soh; break; case PSY_IIO_CHARGE_COUNTER: rc = qbg_get_charge_counter(chip, val1); break; case PSY_IIO_CYCLE_COUNT: *val1 = chip->charge_cycle_count; break; case PSY_IIO_CHARGE_FULL: *val1 = chip->learned_capacity * MILLI_TO_MICRO; break; case PSY_IIO_CHARGE_FULL_DESIGN: *val1 = chip->nominal_capacity * MILLI_TO_MICRO; break; default: pr_err_ratelimited("Unsupported property %d\n", chan->channel); rc = -EINVAL; break; } return (rc < 0) ? rc : IIO_VAL_INT; } static int qbg_iio_of_xlate(struct iio_dev *indio_dev, const struct of_phandle_args *iiospec) { struct qti_qbg *chip = iio_priv(indio_dev); struct iio_chan_spec *iio_chan = chip->iio_chan; int i; for (i = 0; i < ARRAY_SIZE(qbg_iio_psy_channels); i++, iio_chan++) { if (iio_chan->channel == iiospec->args[0]) return i; } return -EINVAL; } static const struct iio_info qbg_iio_info = { .read_raw = qbg_iio_read_raw, .write_raw = qbg_iio_write_raw, .of_xlate = qbg_iio_of_xlate, }; static int qbg_init_iio(struct qti_qbg *chip, struct platform_device *pdev) { struct iio_dev *indio_dev = chip->indio_dev; struct iio_chan_spec *chan; int qbg_num_iio_channels = ARRAY_SIZE(qbg_iio_psy_channels); int rc, i; chip->iio_chan = devm_kcalloc(chip->dev, qbg_num_iio_channels, sizeof(*chip->iio_chan), GFP_KERNEL); if (!chip->iio_chan) return -ENOMEM; indio_dev->info = &qbg_iio_info; indio_dev->dev.parent = chip->dev; indio_dev->dev.of_node = chip->dev->of_node; indio_dev->name = pdev->name; indio_dev->modes = INDIO_DIRECT_MODE; indio_dev->channels = chip->iio_chan; indio_dev->num_channels = qbg_num_iio_channels; for (i = 0; i < qbg_num_iio_channels; i++) { chan = &chip->iio_chan[i]; chan->address = i; chan->channel = qbg_iio_psy_channels[i].channel_num; chan->type = qbg_iio_psy_channels[i].type; chan->datasheet_name = qbg_iio_psy_channels[i].datasheet_name; chan->extend_name = qbg_iio_psy_channels[i].datasheet_name; chan->info_mask_separate = qbg_iio_psy_channels[i].info_mask; } rc = devm_iio_device_register(chip->dev, indio_dev); if (rc < 0) pr_err("Failed to register QBG IIO device, rc=%d\n", rc); return rc; } static int qbg_get_accumulator_properties(struct qti_qbg *chip, enum QBG_STATE state, enum QBG_SAMPLE_NUM_TYPE *num_of_accum, enum QBG_ACCUM_INTERVAL_TYPE *accum_interval) { int rc = 0; unsigned int reg = QBG_MAIN_LPM_MEAS_CTL2; unsigned char data = 0; if (state >= QBG_STATE_MAX || !num_of_accum || !accum_interval) return -EINVAL; switch (state) { case QBG_LPM: reg = QBG_MAIN_LPM_MEAS_CTL2; break; case QBG_MPM: reg = QBG_MAIN_MPM_MEAS_CTL2; break; case QBG_HPM: reg = QBG_MAIN_HPM_MEAS_CTL2; break; case QBG_FAST_CHAR: reg = QBG_MAIN_FAST_CHAR_MEAS_CTL2; break; default: pr_err("Invalid QBG state requested for accumulator properties %u\n", state); return rc; } rc = qbg_read(chip, reg, &data, 1); if (rc < 0) { pr_err("Failed to MEAS_CTL2 %u, rc=%d\n", reg, rc); return rc; } if (state == QBG_FAST_CHAR) *num_of_accum = 0; else *num_of_accum = (data & QBG_NUM_OF_ACCUM_MASK) >> QBG_NUM_OF_ACCUM_SHIFT; *accum_interval = data & QBG_ACCUM_INTERVAL_MASK; qbg_dbg(chip, QBG_DEBUG_DEVICE, "state:%u num_of_accum:%u accum_interval:%u\n", state, *num_of_accum, *accum_interval); return rc; } static int qbg_get_sample_time_us(struct qti_qbg *chip) { int rc = 0, index; unsigned int interval; enum QBG_ACCUM_INTERVAL_TYPE accum_interval = ACCUM_INTERVAL_100MS; enum QBG_SAMPLE_NUM_TYPE num_accum = SAMPLE_NUM_1; for (index = 0; index < QBG_STATE_MAX; index++) { if (index == QBG_PON_OCV) continue; rc = qbg_get_accumulator_properties(chip, index, &num_accum, &accum_interval); if (rc < 0) return rc; if (index == QBG_FAST_CHAR) interval = qbg_fast_char_avg_interval[accum_interval]; else if (index == QBG_LPM) interval = qbg_lpm_accum_interval[accum_interval]; else interval = qbg_accum_interval[accum_interval]; chip->sample_time_us[index] = interval; } return rc; } static ssize_t qbg_context_show(struct class *c, struct class_attribute *attr, char *buf) { struct qti_qbg *chip = container_of(c, struct qti_qbg, qbg_class); int count; if (!chip) return -ENODEV; if (!chip->context_count) { qbg_dbg(chip, QBG_DEBUG_DEVICE, "Empty context buffer, nothing to show\n"); return 0; } mutex_lock(&chip->context_lock); memcpy(buf, chip->context, chip->context_count); count = chip->context_count; chip->context_count = 0; memset(chip->context, 0, QBG_CONTEXT_LOCAL_BUF_SIZE); mutex_unlock(&chip->context_lock); return count; } static ssize_t qbg_context_store(struct class *c, struct class_attribute *attr, const char *buf, size_t count) { struct qti_qbg *chip = container_of(c, struct qti_qbg, qbg_class); if (!chip) return -ENODEV; if (count > QBG_CONTEXT_LOCAL_BUF_SIZE) { qbg_dbg(chip, QBG_DEBUG_DEVICE, "Context dump is greater than %d bytes\n", QBG_CONTEXT_LOCAL_BUF_SIZE); return -EINVAL; } if (!chip->context) { chip->context = devm_kcalloc(chip->dev, 1, QBG_CONTEXT_LOCAL_BUF_SIZE, GFP_KERNEL); if (!chip->context) return -ENOMEM; } mutex_lock(&chip->context_lock); memcpy(chip->context, buf, count); chip->context_count = count; mutex_unlock(&chip->context_lock); return count; } static CLASS_ATTR_RW(qbg_context); static struct attribute *qbg_class_attrs[] = { &class_attr_qbg_context.attr, NULL, }; ATTRIBUTE_GROUPS(qbg_class); static ssize_t qbg_device_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) { int rc; struct qti_qbg *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->qbg_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 = -ENODATA; goto fail_read; } if (copy_to_user(buf, &chip->kdata, data_size)) { pr_err("Failed to copy_to_user\n"); rc = -EFAULT; goto fail_read; } chip->data_ready = false; qbg_dbg(chip, QBG_DEBUG_DEVICE, "QBG device read complete Size=%ld\n", data_size); /* clear data */ memset(&chip->kdata, 0, sizeof(chip->kdata)); rc = data_size; fail_read: mutex_unlock(&chip->data_lock); return rc; } static ssize_t qbg_device_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { int rc = -EINVAL; struct qti_qbg *chip = file->private_data; unsigned long data_size = sizeof(chip->udata); mutex_lock(&chip->data_lock); if (count == 0) { pr_err("No data!\n"); rc = -ENODATA; goto fail; } if (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 to copy_from_user\n"); rc = -EFAULT; goto fail; } __pm_stay_awake(chip->qbg_ws); rc = data_size; schedule_work(&chip->udata_work); qbg_dbg(chip, QBG_DEBUG_DEVICE, "QBG write complete size=%d\n", rc); fail: mutex_unlock(&chip->data_lock); return rc; } static unsigned int qbg_device_poll(struct file *file, poll_table *wait) { struct qti_qbg *chip = file->private_data; unsigned int mask = 0; poll_wait(file, &chip->qbg_wait_q, wait); if (chip->data_ready) mask = POLLIN | POLLRDNORM; return mask; } static int qbg_device_open(struct inode *inode, struct file *file) { struct qti_qbg *chip = container_of(inode->i_cdev, struct qti_qbg, qbg_cdev); file->private_data = chip; qbg_dbg(chip, QBG_DEBUG_DEVICE, "QBG device opened!\n"); return 0; } static int qbg_device_release(struct inode *inode, struct file *file) { struct qti_qbg *chip = container_of(inode->i_cdev, struct qti_qbg, qbg_cdev); file->private_data = NULL; qbg_dbg(chip, QBG_DEBUG_DEVICE, "QBG device closed!\n"); return 0; } static long qbg_device_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { struct qti_qbg *chip = file->private_data; struct qbg_config __user *config_user; struct qbg_config config; struct qbg_essential_params __user *params_user; struct qbg_step_chg_jeita_params __user *step_chg_params_user; time64_t rtc_sec; int rc = 0, i; if (!chip) { pr_err("Device private data not set!\n"); return -EINVAL; } if (!arg) { pr_err("Invalid user pointer\n"); return -EINVAL; } /* Get RTC time here */ rc = get_rtc_time(chip, &rtc_sec); if (rc < 0) { pr_err("Failed to read rtc time, rc=%d\n", rc); return rc; } switch (cmd) { case QBGIOCXCFG: case QBG_QBGIOCXCFG: config_user = (struct qbg_config __user *)arg; rc = qbg_get_sample_time_us(chip); if (rc < 0) { pr_err("Failed to calculate sample time us, rc=%d\n", rc); return rc; } rc = qbg_sdam_get_battery_id(chip, &chip->sdam_batt_id); if (rc < 0) { pr_err("Failed to get battid from sdam, rc=%d\n", rc); return rc; } rc = qbg_sdam_get_essential_param_revid(chip, (u8 *)&chip->essential_param_revid); if (rc < 0) { pr_err("Failed to get essential param revid, rc=%d\n", rc); return rc; } config.current_time = rtc_sec; config.batt_id = chip->batt_id_ohm / 1000; config.pon_ocv = chip->pon_ocv; config.pon_ibat = chip->pon_ibat; config.pon_tbat = chip->pon_tbat; config.pon_soc = chip->pon_soc; config.float_volt_uv = chip->float_volt_uv; config.fastchg_curr_ma = chip->fastchg_curr_ma; config.vbat_cutoff_mv = chip->vbat_cutoff_mv; config.ibat_cutoff_ma = chip->ibat_cutoff_ma; config.vph_min_mv = chip->vph_min_mv; config.iterm_ma = chip->iterm_ma; config.rconn_mohm = chip->rconn_mohm; config.sdam_batt_id = chip->sdam_batt_id; config.essential_param_revid = chip->essential_param_revid; for (i = 0; i < QBG_STATE_MAX; i++) { config.sample_time_us[i] = chip->sample_time_us[i]; qbg_dbg(chip, QBG_DEBUG_DEVICE, "QBGIOCXCFG: sample_time_us[%d]:%u\n", i, config.sample_time_us[i]); } if (copy_to_user(config_user, (void *)&config, sizeof(config))) { pr_err("Failed to copy QBG config to user\n"); return -EFAULT; } qbg_dbg(chip, QBG_DEBUG_DEVICE, "QBGIOCXCFG: sdam_battid:%u essential param revid:%u battid:%u pon_ocv:%u pon_ibat:%u pon_soc:%u vbatt_cutoff_mv:%u iterm_ma:%u\n", config.sdam_batt_id, config.essential_param_revid, config.batt_id, config.pon_ocv, config.pon_ibat, config.pon_soc, config.vbat_cutoff_mv, config.iterm_ma); break; case QBGIOCXEPR: case QBG_QBGIOCXEPR: params_user = (struct qbg_essential_params __user *)arg; rc = qbg_sdam_get_essential_params(chip, (u8 *)&chip->essential_params); if (rc < 0) { pr_err("Failed to read essential params, rc=%d\n", rc); return -EFAULT; } if (copy_to_user(params_user, (void *)&chip->essential_params, sizeof(chip->essential_params))) { pr_err("Failed to copy QBG essential params to user\n"); return -EFAULT; } break; case QBGIOCXEPW: case QBG_QBGIOCXEPW: params_user = (struct qbg_essential_params __user *)arg; if (copy_from_user((void *)&chip->essential_params, params_user, sizeof(chip->essential_params))) { pr_err("Failed to copy QBG essential params from user\n"); return -EFAULT; } chip->previous_ep_time = rtc_sec; chip->essential_params.rtc_time = rtc_sec; rc = qbg_sdam_set_essential_params(chip, (u8 *)&chip->essential_params); if (rc < 0) { pr_err("Failed to write essential params, rc=%d\n", rc); return -EFAULT; } qbg_dbg(chip, QBG_DEBUG_SDAM, "Essential params written, time:%lu secs\n", rtc_sec); break; case QBGIOCXSTEPCHGCFG: case QBG_QBGIOCXSTEPCHGCFG: step_chg_params_user = (struct qbg_step_chg_jeita_params __user *)arg; if (copy_to_user(step_chg_params_user, (void *)chip->step_chg_jeita_params, sizeof(struct qbg_step_chg_jeita_params))) { pr_err("Failed to copy QBG step and jeita charge params to user\n"); return -EFAULT; } qbg_dbg(chip, QBG_DEBUG_DEVICE, "QBGIOCXSTEPCHGCFG: jeita_full_fv_10nv:%d jeita_warm_adc_value:0x%x jeita_cool_adc_value:0x%x ttf_calc_mode:%u\n", chip->step_chg_jeita_params->jeita_full_fv_10nv, chip->step_chg_jeita_params->jeita_warm_adc_value, chip->step_chg_jeita_params->jeita_cool_adc_value, chip->step_chg_jeita_params->ttf_calc_mode); break; default: pr_err("IOCTL %d not supported\n", cmd); rc = -EINVAL; } return rc; } static long qbg_device_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { return qbg_device_ioctl(file, _IOC_NR(cmd), arg); } static const struct file_operations qbg_fops = { .owner = THIS_MODULE, .open = qbg_device_open, .release = qbg_device_release, .read = qbg_device_read, .write = qbg_device_write, .poll = qbg_device_poll, .unlocked_ioctl = qbg_device_ioctl, .compat_ioctl = qbg_device_compat_ioctl, }; static int qbg_register_device(struct qti_qbg *chip) { int rc; rc = alloc_chrdev_region(&chip->dev_no, 0, 1, "qbg"); if (rc < 0) { pr_err("Failed to allocate chardev rc=%d\n", rc); return rc; } cdev_init(&chip->qbg_cdev, &qbg_fops); rc = cdev_add(&chip->qbg_cdev, chip->dev_no, 1); if (rc < 0) { pr_err("Failed to cdev_add rc=%d\n", rc); goto unregister_chrdev; } chip->qbg_class.name = "qbg"; chip->qbg_class.class_groups = qbg_class_groups; rc = class_register(&chip->qbg_class); if (rc < 0) { pr_err("Failed to create qbg_class rc=%d\n", rc); goto delete_cdev; } chip->qbg_device = device_create(&chip->qbg_class, NULL, chip->dev_no, NULL, "qbg"); if (IS_ERR(chip->qbg_device)) { pr_err("Failed to create qbg_device\n"); rc = -EINVAL; goto destroy_class; } qbg_dbg(chip, QBG_DEBUG_DEVICE, "'/dev/qbg' successfully created\n"); return 0; destroy_class: class_unregister(&chip->qbg_class); delete_cdev: cdev_del(&chip->qbg_cdev); unregister_chrdev: unregister_chrdev_region(chip->dev_no, 1); return rc; } static int qbg_register_interrupts(struct qti_qbg *chip) { int rc = 0; /* * Do not register for data-full to skip processing QBG * data if a valid battery or debug battery is not detected */ if (chip->battery_unknown || is_debug_batt_id(chip)) return rc; /* * Turn off data full interrupt from PMIC side. After IRQ handler * registration, we re-enable the interrupt which guarantees the IRQ * handler will fire if the FIFO is already full. */ rc = qbg_enable_data_full_int(chip, false); if (rc < 0) { dev_err(chip->dev, "Failed to disable interrupt from PMIC side, rc=%d\n", rc); } rc = devm_request_threaded_irq(chip->dev, chip->irq, NULL, qbg_data_full_irq_handler, IRQF_ONESHOT, "qbg-sdam", chip); if (rc < 0) { dev_err(chip->dev, "Failed to request IRQ(qbg-sdam), rc=%d\n", rc); return rc; } /* Enable the interrupt, it will get raised if FIFO is already full */ rc = qbg_enable_data_full_int(chip, true); if (rc < 0) { dev_err(chip->dev, "Failed to enable interrupt from PMIC side, rc=%d\n", rc); } rc = enable_irq_wake(chip->irq); if (rc < 0) dev_err(chip->dev, "Failed to set IRQ(qbg-sdam) wake-able, rc=%d\n", rc); /* Register for Vbatt_empty INT only if valid value is defined in DT */ if (chip->vbatt_empty_threshold_mv != 0) { rc = devm_request_threaded_irq(chip->dev, chip->vbatt_empty_irq, NULL, qbg_vbatt_empty_irq_handler, IRQF_ONESHOT, "qbg-vbatt-empty", chip); if (rc < 0) { dev_err(chip->dev, "Failed to request IRQ(qbg-vbatt-empty), rc=%d\n", rc); return rc; } rc = enable_irq_wake(chip->vbatt_empty_irq); if (rc < 0) dev_err(chip->dev, "Failed to set IRQ(qbg-vbatt-empty) wake-able, rc=%d\n", rc); } return rc; } #ifdef CONFIG_DEBUG_FS static ssize_t qbg_debug_mask_read(struct file *filp, char __user *buffer, size_t count, loff_t *ppos) { char *buf; ssize_t len = 0; if (*ppos != 0) return 0; buf = kasprintf(GFP_KERNEL, "%d\n", qbg_debug_mask); if (!buf) return -ENOMEM; if (count < strlen(buf)) { kfree(buf); return -ENOSPC; } len = simple_read_from_buffer(buffer, count, ppos, buf, strlen(buf)); kfree(buf); return len; } static ssize_t qbg_debug_mask_write(struct file *filp, const char __user *buffer, size_t count, loff_t *ppos) { struct qti_qbg *chip = filp->private_data; int rc = 0; char data[2]; rc = kstrtou32_from_user(buffer, count, 10, &qbg_debug_mask); if (rc < 0) return rc; if (!chip->debug_mask_nvmem_low || !chip->debug_mask_nvmem_high) return count; data[0] = qbg_debug_mask & 0xff; data[1] = (qbg_debug_mask >> 8) & 0xff; rc = nvmem_cell_write(chip->debug_mask_nvmem_low, &data[0], sizeof(data[0])); if (rc < 0) { pr_err("Failed to write qbg debug mask low byte, rc = %d\n", rc); return rc; } rc = nvmem_cell_write(chip->debug_mask_nvmem_high, &data[1], sizeof(data[1])); if (rc < 0) { pr_err("Failed to write qbg debug mask high byte, rc = %d\n", rc); return rc; } return count; } static const struct file_operations qbg_debug_mask_fops = { .owner = THIS_MODULE, .open = simple_open, .read = qbg_debug_mask_read, .write = qbg_debug_mask_write, }; static void qbg_create_debugfs(struct qti_qbg *chip) { struct dentry *entry; pr_err("%s:%u\n", __func__, __LINE__); chip->dfs_root = debugfs_create_dir("qbg", 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_file("debug_mask", 0600, chip->dfs_root, chip, &qbg_debug_mask_fops); 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 qbg_create_debugfs(struct qti_qbg *chip) { } #endif static void get_qbg_debug_mask(struct qti_qbg *chip) { ssize_t len; char *data[2]; if (!chip->debug_mask_nvmem_low || !chip->debug_mask_nvmem_high) return; data[0] = nvmem_cell_read(chip->debug_mask_nvmem_low, &len); if (IS_ERR(data[0])) { pr_err("Failed to read qbg debug mask low byte from SDAM\n"); return; } data[1] = nvmem_cell_read(chip->debug_mask_nvmem_high, &len); if (IS_ERR(data[1])) { pr_err("Failed to read qbg debug mask high byte from SDAM\n"); return; } qbg_debug_mask = *data[1] & 0xff; qbg_debug_mask = (qbg_debug_mask << 8) | (*data[0]); kfree(data[0]); kfree(data[1]); } #define SOC_UPDATE_FREQUENCY_MS 60000 static void soc_update_work(struct work_struct *work) { struct qti_qbg *chip = container_of(work, struct qti_qbg, soc_update_work.work); int rc = 0; if (chip->sys_soc != INT_MIN) { qbg_dbg(chip, QBG_DEBUG_STATUS, "write soc = %d to register\n", chip->sys_soc); rc = qbg_write_iio_chan(chip, SYS_SOC, chip->sys_soc); if (rc < 0) { pr_err("Failed to write battery sys_soc, rc=%d\n", rc); return; } } schedule_delayed_work(&chip->soc_update_work, msecs_to_jiffies(SOC_UPDATE_FREQUENCY_MS)); } static int qbg_parse_sdam_dt(struct qti_qbg *chip, struct device_node *node) { int rc; chip->irq = of_irq_get_byname(node, "qbg-sdam"); if (chip->irq < 0) { pr_err("Failed to get irq for QBG, rc=%d\n", chip->irq); return -EINVAL; } rc = of_property_read_u32(node, "qcom,num-data-sdams", &chip->num_data_sdams); if (rc < 0) { pr_err("Failed to read number of DATA SDAMs for QBG, rc=%d\n", rc); return rc; } rc = of_property_read_u32(node, "qcom,sdam-base", &chip->sdam_base); if (rc < 0) { pr_err("Failed to read SDAM base address, rc=%d\n", rc); return rc; } return rc; } #define QBG_DEFAULT_VBAT_CUTOFF_MV 3100 #define QBG_DEFAULT_IBAT_CUTOFF_MA 150 #define QBG_DEFAULT_VPH_MIN_MV 2700 #define QBG_DEFAULT_ITERM_MA 100 #define QBG_DEFAULT_RCONN_MOHM 0 static int qbg_parse_dt(struct qti_qbg *chip) { struct device_node *node = chip->dev->of_node; int rc = 0; u32 val; rc = qbg_parse_sdam_dt(chip, node); if (rc < 0) { pr_err("Failed to QBG SDAM DT, rc=%d\n", rc); return rc; } rc = of_property_read_u32(node, "qcom,adc-cmn-wb-base", &chip->adc_cmn_wb_base); if (rc) { pr_err("Failed to get adc cmn wb addr, rc=%d\n", rc); return rc; } rc = of_property_read_u32(node, "qcom,adc-cmn-base", &chip->adc_cmn_base); if (rc) { pr_err("Failed to get adc cmn addr, rc=%d\n", rc); return rc; } chip->vbat_cutoff_mv = QBG_DEFAULT_VBAT_CUTOFF_MV; rc = of_property_read_u32(node, "qcom,vbat-cutoff-mv", &val); if (!rc) chip->vbat_cutoff_mv = val; chip->ibat_cutoff_ma = QBG_DEFAULT_IBAT_CUTOFF_MA; rc = of_property_read_u32(node, "qcom,ibat-cutoff-ma", &val); if (!rc) chip->ibat_cutoff_ma = val; chip->vph_min_mv = QBG_DEFAULT_VPH_MIN_MV; rc = of_property_read_u32(node, "qcom,vph-min-mv", &val); if (!rc) chip->vph_min_mv = val; chip->iterm_ma = QBG_DEFAULT_ITERM_MA; rc = of_property_read_u32(node, "qcom,iterm-ma", &val); if (!rc) chip->iterm_ma = val; chip->rconn_mohm = QBG_DEFAULT_RCONN_MOHM; rc = of_property_read_u32(node, "qcom,rconn-mohm", &val); if (!rc) chip->rconn_mohm = val; chip->vbatt_empty_threshold_mv = 0; rc = of_property_read_u32(node, "qcom,vbatt-empty-threshold-mv", &val); if (!rc) chip->vbatt_empty_threshold_mv = val; chip->vbatt_empty_irq = of_irq_get_byname(node, "qbg-vbatt-empty"); if (chip->vbatt_empty_irq < 0) { pr_err("Failed to get Vbatt_empty IRQ, rc=%d\n", chip->vbatt_empty_irq); return -EINVAL; } if (of_find_property(node, "nvmem-cells", NULL)) { chip->debug_mask_nvmem_low = devm_nvmem_cell_get(chip->dev, "qbg_debug_mask_low"); if (IS_ERR(chip->debug_mask_nvmem_low)) { rc = PTR_ERR(chip->debug_mask_nvmem_low); if (rc != -EPROBE_DEFER) dev_err(chip->dev, "Failed to get nvmem-cells, rc=%d\n", rc); return rc; } chip->debug_mask_nvmem_high = devm_nvmem_cell_get(chip->dev, "qbg_debug_mask_high"); if (IS_ERR(chip->debug_mask_nvmem_high)) { rc = PTR_ERR(chip->debug_mask_nvmem_high); if (rc != -EPROBE_DEFER) dev_err(chip->dev, "Failed to get nvmem-cells, rc=%d\n", rc); return rc; } chip->skip_esr_state = devm_nvmem_cell_get(chip->dev, "skip_esr_state"); if (IS_ERR(chip->skip_esr_state)) { rc = PTR_ERR(chip->skip_esr_state); if (rc != -EPROBE_DEFER) { dev_dbg(chip->dev, "Failed to get skip_esr_state, rc=%d\n", rc); chip->skip_esr_state = NULL; goto exit; } return rc; } } exit: return 0; } static int qti_qbg_probe(struct platform_device *pdev) { struct qti_qbg *chip; struct iio_dev *indio_dev; int rc; u32 val; indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*chip)); if (!indio_dev) return -ENOMEM; chip = iio_priv(indio_dev); chip->dev = &pdev->dev; chip->indio_dev = indio_dev; chip->ext_iio_chans = devm_kcalloc(chip->dev, ARRAY_SIZE(qbg_ext_iio_chan_name), sizeof(*chip->ext_iio_chans), GFP_KERNEL); if (!chip->ext_iio_chans) return -ENOMEM; chip->regmap = dev_get_regmap(pdev->dev.parent, NULL); if (!chip->regmap) { dev_err(&pdev->dev, "Failed to get regmap\n"); return -EINVAL; } rc = of_property_read_u32(pdev->dev.of_node, "reg", &val); if (rc < 0) { pr_err("Failed to get base address for QBG, rc = %d\n", rc); return rc; } chip->base = val; INIT_WORK(&chip->status_change_work, status_change_work); INIT_WORK(&chip->udata_work, process_udata_work); INIT_DELAYED_WORK(&chip->soc_update_work, soc_update_work); mutex_init(&chip->fifo_lock); mutex_init(&chip->data_lock); mutex_init(&chip->context_lock); dev_set_drvdata(chip->dev, chip); init_waitqueue_head(&chip->qbg_wait_q); chip->debug_mask = &qbg_debug_mask; chip->qbg_ws = wakeup_source_register(chip->dev, "qcom-qbg"); if (!chip->qbg_ws) return -EINVAL; chip->default_iterm_ma = -EINVAL; chip->soc = INT_MIN; chip->batt_soc = INT_MIN; chip->sys_soc = INT_MIN; chip->esr = INT_MIN; chip->ocv_uv = INT_MIN; chip->pon_ocv = INT_MIN; chip->charge_cycle_count = INT_MIN; chip->nominal_capacity = INT_MIN; chip->learned_capacity = INT_MIN; chip->ttf = INT_MIN; chip->tte = INT_MIN; chip->soh = INT_MIN; chip->tbat = INT_MIN; rc = qbg_parse_dt(chip); if (rc < 0) { dev_err(&pdev->dev, "Failed to parse QBG devicetree, rc=%d\n", rc); return rc; } get_qbg_debug_mask(chip); /* ADC for Battery-ID */ chip->batt_id_chan = devm_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) dev_err(&pdev->dev, "batt-id channel unavailable, rc=%d\n", rc); chip->batt_id_chan = NULL; return rc; } /* ADC for Battery-Temp */ chip->batt_temp_chan = devm_iio_channel_get(&pdev->dev, "batt-temp"); if (IS_ERR(chip->batt_temp_chan)) { rc = PTR_ERR(chip->batt_temp_chan); if (rc != -EPROBE_DEFER) dev_err(&pdev->dev, "batt-temp channel unavailable, rc=%d\n", rc); chip->batt_temp_chan = NULL; return rc; } qbg_create_debugfs(chip); chip->rtc = rtc_class_open(CONFIG_RTC_HCTOSYS_DEVICE); if (chip->rtc == NULL) return -EPROBE_DEFER; rc = regmap_read(chip->regmap, REVID_REVISION4, &chip->rev4); if (rc < 0) { pr_err("Failed to read REVID_REVISION4, rc=%d\n", rc); return rc; } rc = qbg_init_sdam(chip); if (rc < 0) { dev_err(&pdev->dev, "Failed to initialize QBG sdam, rc=%d\n", rc); return rc; } rc = qbg_get_max_fifo_count(chip); if (rc < 0) { dev_err(&pdev->dev, "Failed to get fifo count, rc=%d\n", rc); return rc; } rc = qbg_init_esr(chip); if (rc < 0) { dev_err(&pdev->dev, "Failed to initialize QBG ESR, rc=%d\n", rc); return rc; } rc = qbg_setup_battery(chip); if (rc < 0) { dev_err(&pdev->dev, "Failed to setup battery for QBG, rc=%d\n", rc); return rc; } rc = qbg_determine_pon_soc(chip); if (rc < 0) { dev_err(&pdev->dev, "Failed to determine initial state, rc=%d\n", rc); return rc; } rc = qbg_register_device(chip); if (rc < 0) { dev_err(&pdev->dev, "Failed to register QBG device, rc=%d\n", rc); return rc; } rc = qbg_init_iio(chip, pdev); if (rc < 0) { dev_err(&pdev->dev, "Failed to initialize QBG IIO device, rc=%d\n", rc); return rc; } rc = qbg_init_psy(chip); if (rc < 0) { dev_err(&pdev->dev, "Failed to initialize QBG PSY, rc=%d\n", rc); return rc; } qbg_init_vbatt_empty_threshold(chip); rc = qbg_register_interrupts(chip); if (rc < 0) { dev_err(&pdev->dev, "Failed to register QBG interrupts, rc=%d\n", rc); return rc; } schedule_delayed_work(&chip->soc_update_work, msecs_to_jiffies(SOC_UPDATE_FREQUENCY_MS)); dev_info(&pdev->dev, "QBG initialized! battery_profile=%s SOC=%d\n", qbg_get_battery_type(chip), chip->soc); return rc; } static int qti_qbg_remove(struct platform_device *pdev) { struct qti_qbg *chip = platform_get_drvdata(pdev); if (chip->rtc) rtc_class_close(chip->rtc); cancel_work_sync(&chip->status_change_work); cancel_work_sync(&chip->udata_work); cancel_delayed_work_sync(&chip->soc_update_work); mutex_destroy(&chip->fifo_lock); mutex_destroy(&chip->data_lock); mutex_destroy(&chip->context_lock); cdev_del(&chip->qbg_cdev); unregister_chrdev_region(chip->dev_no, 1); class_unregister(&chip->qbg_class); return 0; } static int qbg_freeze(struct device *dev) { struct qti_qbg *chip = dev_get_drvdata(dev); /*free irq*/ if (chip->irq > 0) devm_free_irq(dev, chip->irq, chip); return 0; } static int qbg_restore(struct device *dev) { int ret = 0; struct qti_qbg *chip = dev_get_drvdata(dev); /* Init & clear SDAM to kick-start QBG sampling */ ret = qbg_init_sdam(chip); if (ret < 0) { dev_err(dev, "Failed to init qbg sdam rc = %d\n"); return ret; } ret = qbg_register_interrupts(chip); if (ret < 0) dev_err(dev, "Failed to register qbg interrupt rc = %d\n"); return ret; } static int qbg_suspend(struct device *dev) { #ifdef CONFIG_DEEPSLEEP if (pm_suspend_via_firmware()) return qbg_freeze(dev); #endif return 0; } static int qbg_resume(struct device *dev) { #ifdef CONFIG_DEEPSLEEP if (pm_suspend_via_firmware()) return qbg_restore(dev); #endif return 0; } static const struct dev_pm_ops qbg_pm_ops = { .freeze = qbg_freeze, .restore = qbg_restore, .suspend = qbg_suspend, .resume = qbg_resume, }; static const struct of_device_id qbg_match_table[] = { { .compatible = "qcom,qbg", }, { }, }; static struct platform_driver qti_qbg_driver = { .driver = { .name = "qti_qbg", .of_match_table = qbg_match_table, .pm = &qbg_pm_ops, }, .probe = qti_qbg_probe, .remove = qti_qbg_remove, }; module_platform_driver(qti_qbg_driver); MODULE_DESCRIPTION("QTI QBG (Qualcomm Battery Gauging) driver"); MODULE_LICENSE("GPL v2");