3421 lines
91 KiB
C
3421 lines
91 KiB
C
/* Copyright (c) 2013-2015, 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) "CHG: %s: " fmt, __func__
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/err.h>
|
|
#include <linux/spmi.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_device.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/power_supply.h>
|
|
#include <linux/qpnp/qpnp-adc.h>
|
|
#include <linux/alarmtimer.h>
|
|
#include <linux/bitops.h>
|
|
#include <linux/leds.h>
|
|
#include <linux/debugfs.h>
|
|
|
|
#define CREATE_MASK(NUM_BITS, POS) \
|
|
((unsigned char) (((1 << (NUM_BITS)) - 1) << (POS)))
|
|
#define LBC_MASK(MSB_BIT, LSB_BIT) \
|
|
CREATE_MASK(MSB_BIT - LSB_BIT + 1, LSB_BIT)
|
|
|
|
/* Interrupt offsets */
|
|
#define INT_RT_STS_REG 0x10
|
|
#define FAST_CHG_ON_IRQ BIT(5)
|
|
#define OVERTEMP_ON_IRQ BIT(4)
|
|
#define BAT_TEMP_OK_IRQ BIT(1)
|
|
#define BATT_PRES_IRQ BIT(0)
|
|
|
|
/* USB CHARGER PATH peripheral register offsets */
|
|
#define USB_IN_VALID_MASK BIT(1)
|
|
#define CHG_GONE_BIT BIT(2)
|
|
#define USB_SUSP_REG 0x47
|
|
#define USB_SUSPEND_BIT BIT(0)
|
|
#define USB_COMP_OVR1_REG 0xEA
|
|
#define USBIN_LLIMIT_OK_MASK LBC_MASK(1, 0)
|
|
#define USBIN_LLIMIT_OK_NO_OVERRIDE 0x00
|
|
#define USBIN_LLIMIT_OK_OVERRIDE_1 0x03
|
|
#define USB_OVP_TST5_REG 0xE7
|
|
#define CHG_GONE_OK_EN_BIT BIT(2)
|
|
|
|
/* CHARGER peripheral register offset */
|
|
#define CHG_OPTION_REG 0x08
|
|
#define CHG_OPTION_MASK BIT(7)
|
|
#define CHG_STATUS_REG 0x09
|
|
#define CHG_VDD_LOOP_BIT BIT(1)
|
|
#define VINMIN_LOOP_BIT BIT(3)
|
|
#define CHG_VDD_MAX_REG 0x40
|
|
#define CHG_VDD_SAFE_REG 0x41
|
|
#define CHG_IBAT_MAX_REG 0x44
|
|
#define CHG_IBAT_SAFE_REG 0x45
|
|
#define CHG_VIN_MIN_REG 0x47
|
|
#define CHG_CTRL_REG 0x49
|
|
#define CHG_ENABLE BIT(7)
|
|
#define CHG_FORCE_BATT_ON BIT(0)
|
|
#define CHG_EN_MASK (BIT(7) | BIT(0))
|
|
#define CHG_FAILED_REG 0x4A
|
|
#define CHG_FAILED_BIT BIT(7)
|
|
#define CHG_VBAT_WEAK_REG 0x52
|
|
#define CHG_IBATTERM_EN_REG 0x5B
|
|
#define CHG_USB_ENUM_T_STOP_REG 0x4E
|
|
#define CHG_TCHG_MAX_EN_REG 0x60
|
|
#define CHG_TCHG_MAX_EN_BIT BIT(7)
|
|
#define CHG_TCHG_MAX_MASK LBC_MASK(6, 0)
|
|
#define CHG_TCHG_MAX_REG 0x61
|
|
#define CHG_WDOG_EN_REG 0x65
|
|
#define CHG_PERPH_RESET_CTRL3_REG 0xDA
|
|
#define CHG_COMP_OVR1 0xEE
|
|
#define CHG_VBAT_DET_OVR_MASK LBC_MASK(1, 0)
|
|
#define CHG_TEST_LOOP_REG 0xE5
|
|
#define VIN_MIN_LOOP_DISABLE_BIT BIT(0)
|
|
#define OVERRIDE_0 0x2
|
|
#define OVERRIDE_NONE 0x0
|
|
|
|
/* BATTIF peripheral register offset */
|
|
#define BAT_IF_PRES_STATUS_REG 0x08
|
|
#define BATT_PRES_MASK BIT(7)
|
|
#define BAT_IF_TEMP_STATUS_REG 0x09
|
|
#define BATT_TEMP_HOT_MASK BIT(6)
|
|
#define BATT_TEMP_COLD_MASK LBC_MASK(7, 6)
|
|
#define BATT_TEMP_OK_MASK BIT(7)
|
|
#define BAT_IF_VREF_BAT_THM_CTRL_REG 0x4A
|
|
#define VREF_BATT_THERM_FORCE_ON LBC_MASK(7, 6)
|
|
#define VREF_BAT_THM_ENABLED_FSM BIT(7)
|
|
#define BAT_IF_BPD_CTRL_REG 0x48
|
|
#define BATT_BPD_CTRL_SEL_MASK LBC_MASK(1, 0)
|
|
#define BATT_BPD_OFFMODE_EN BIT(3)
|
|
#define BATT_THM_EN BIT(1)
|
|
#define BATT_ID_EN BIT(0)
|
|
#define BAT_IF_BTC_CTRL 0x49
|
|
#define BTC_COMP_EN_MASK BIT(7)
|
|
#define BTC_COLD_MASK BIT(1)
|
|
#define BTC_HOT_MASK BIT(0)
|
|
#define BTC_COMP_OVERRIDE_REG 0xE5
|
|
|
|
/* MISC peripheral register offset */
|
|
#define MISC_REV2_REG 0x01
|
|
#define MISC_BOOT_DONE_REG 0x42
|
|
#define MISC_BOOT_DONE BIT(7)
|
|
#define MISC_TRIM3_REG 0xF3
|
|
#define MISC_TRIM3_VDD_MASK LBC_MASK(5, 4)
|
|
#define MISC_TRIM4_REG 0xF4
|
|
#define MISC_TRIM4_VDD_MASK BIT(4)
|
|
|
|
#define PERP_SUBTYPE_REG 0x05
|
|
#define SEC_ACCESS 0xD0
|
|
|
|
/* Linear peripheral subtype values */
|
|
#define LBC_CHGR_SUBTYPE 0x15
|
|
#define LBC_BAT_IF_SUBTYPE 0x16
|
|
#define LBC_USB_PTH_SUBTYPE 0x17
|
|
#define LBC_MISC_SUBTYPE 0x18
|
|
|
|
#define QPNP_CHG_I_MAX_MIN_90 90
|
|
|
|
/* Feature flags */
|
|
#define VDD_TRIM_SUPPORTED BIT(0)
|
|
|
|
#define QPNP_CHARGER_DEV_NAME "qcom,qpnp-linear-charger"
|
|
|
|
/* usb_interrupts */
|
|
|
|
struct qpnp_lbc_irq {
|
|
int irq;
|
|
unsigned long disabled;
|
|
bool is_wake;
|
|
};
|
|
|
|
enum {
|
|
USBIN_VALID = 0,
|
|
USB_OVER_TEMP,
|
|
USB_CHG_GONE,
|
|
BATT_PRES,
|
|
BATT_TEMPOK,
|
|
CHG_DONE,
|
|
CHG_FAILED,
|
|
CHG_FAST_CHG,
|
|
CHG_VBAT_DET_LO,
|
|
MAX_IRQS,
|
|
};
|
|
|
|
enum {
|
|
USER = BIT(0),
|
|
THERMAL = BIT(1),
|
|
CURRENT = BIT(2),
|
|
SOC = BIT(3),
|
|
PARALLEL = BIT(4),
|
|
COLLAPSE = BIT(5),
|
|
};
|
|
|
|
enum bpd_type {
|
|
BPD_TYPE_BAT_ID,
|
|
BPD_TYPE_BAT_THM,
|
|
BPD_TYPE_BAT_THM_BAT_ID,
|
|
};
|
|
|
|
static const char * const bpd_label[] = {
|
|
[BPD_TYPE_BAT_ID] = "bpd_id",
|
|
[BPD_TYPE_BAT_THM] = "bpd_thm",
|
|
[BPD_TYPE_BAT_THM_BAT_ID] = "bpd_thm_id",
|
|
};
|
|
|
|
enum btc_type {
|
|
HOT_THD_25_PCT = 25,
|
|
HOT_THD_35_PCT = 35,
|
|
COLD_THD_70_PCT = 70,
|
|
COLD_THD_80_PCT = 80,
|
|
};
|
|
|
|
static u8 btc_value[] = {
|
|
[HOT_THD_25_PCT] = 0x0,
|
|
[HOT_THD_35_PCT] = BIT(0),
|
|
[COLD_THD_70_PCT] = 0x0,
|
|
[COLD_THD_80_PCT] = BIT(1),
|
|
};
|
|
|
|
static inline int get_bpd(const char *name)
|
|
{
|
|
int i = 0;
|
|
for (i = 0; i < ARRAY_SIZE(bpd_label); i++) {
|
|
if (strcmp(bpd_label[i], name) == 0)
|
|
return i;
|
|
}
|
|
return -EINVAL;
|
|
}
|
|
|
|
static enum power_supply_property msm_batt_power_props[] = {
|
|
POWER_SUPPLY_PROP_CHARGING_ENABLED,
|
|
POWER_SUPPLY_PROP_STATUS,
|
|
POWER_SUPPLY_PROP_CHARGE_TYPE,
|
|
POWER_SUPPLY_PROP_HEALTH,
|
|
POWER_SUPPLY_PROP_PRESENT,
|
|
POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
|
|
POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
|
|
POWER_SUPPLY_PROP_VOLTAGE_NOW,
|
|
POWER_SUPPLY_PROP_CAPACITY,
|
|
POWER_SUPPLY_PROP_CURRENT_NOW,
|
|
POWER_SUPPLY_PROP_TEMP,
|
|
POWER_SUPPLY_PROP_COOL_TEMP,
|
|
POWER_SUPPLY_PROP_WARM_TEMP,
|
|
POWER_SUPPLY_PROP_SYSTEM_TEMP_LEVEL,
|
|
};
|
|
|
|
static char *pm_batt_supplied_to[] = {
|
|
"bms",
|
|
};
|
|
|
|
struct vddtrim_map {
|
|
int trim_uv;
|
|
int trim_val;
|
|
};
|
|
|
|
/*
|
|
* VDDTRIM is a 3 bit value which is split across two
|
|
* register TRIM3(bit 5:4) -> VDDTRIM bit(2:1)
|
|
* register TRIM4(bit 4) -> VDDTRIM bit(0)
|
|
*/
|
|
#define TRIM_CENTER 4
|
|
#define MAX_VDD_EA_TRIM_CFG 8
|
|
#define VDD_TRIM3_MASK LBC_MASK(2, 1)
|
|
#define VDD_TRIM3_SHIFT 3
|
|
#define VDD_TRIM4_MASK BIT(0)
|
|
#define VDD_TRIM4_SHIFT 4
|
|
#define AVG(VAL1, VAL2) ((VAL1 + VAL2) / 2)
|
|
|
|
/*
|
|
* VDDTRIM table containing map of trim voltage and
|
|
* corresponding trim value.
|
|
*/
|
|
struct vddtrim_map vddtrim_map[] = {
|
|
{36700, 0x00},
|
|
{28000, 0x01},
|
|
{19800, 0x02},
|
|
{10760, 0x03},
|
|
{0, 0x04},
|
|
{-8500, 0x05},
|
|
{-16800, 0x06},
|
|
{-25440, 0x07},
|
|
};
|
|
|
|
/*
|
|
* struct qpnp_lbc_chip - device information
|
|
* @dev: device pointer to access the parent
|
|
* @spmi: spmi pointer to access spmi information
|
|
* @chgr_base: charger peripheral base address
|
|
* @bat_if_base: battery interface peripheral base address
|
|
* @usb_chgpth_base: USB charge path peripheral base address
|
|
* @misc_base: misc peripheral base address
|
|
* @bat_is_cool: indicates that battery is cool
|
|
* @bat_is_warm: indicates that battery is warm
|
|
* @chg_done: indicates that charging is completed
|
|
* @usb_present: present status of USB
|
|
* @batt_present: present status of battery
|
|
* @cfg_charging_disabled: disable drawing current from USB.
|
|
* @cfg_use_fake_battery: flag to report default battery properties
|
|
* @fastchg_on: indicate charger in fast charge mode
|
|
* @cfg_btc_disabled: flag to disable btc (disables hot and cold
|
|
* irqs)
|
|
* @cfg_max_voltage_mv: the max volts the batt should be charged up to
|
|
* @cfg_min_voltage_mv: VIN_MIN configuration
|
|
* @cfg_batt_weak_voltage_uv: weak battery voltage threshold
|
|
* @cfg_warm_bat_chg_ma: warm battery maximum charge current in mA
|
|
* @cfg_cool_bat_chg_ma: cool battery maximum charge current in mA
|
|
* @cfg_safe_voltage_mv: safe voltage to which battery can charge
|
|
* @cfg_warm_bat_mv: warm temperature battery target voltage
|
|
* @cfg_warm_bat_mv: warm temperature battery target voltage
|
|
* @cfg_cool_bat_mv: cool temperature battery target voltage
|
|
* @cfg_soc_resume_limit: SOC at which battery resumes charging
|
|
* @cfg_float_charge: enable float charging
|
|
* @charger_disabled: maintain USB path state.
|
|
* @cfg_charger_detect_eoc: charger can detect end of charging
|
|
* @cfg_disable_vbatdet_based_recharge: keep VBATDET comparator overriden to
|
|
* low and VBATDET irq disabled.
|
|
* @cfg_collapsible_chgr_support: support collapsible charger
|
|
* @cfg_chgr_led_support: support charger led work.
|
|
* @cfg_safe_current: battery safety current setting
|
|
* @cfg_hot_batt_p: hot battery threshold setting
|
|
* @cfg_cold_batt_p: eold battery threshold setting
|
|
* @cfg_warm_bat_decidegc: warm battery temperature in degree Celsius
|
|
* @cfg_cool_bat_decidegc: cool battery temperature in degree Celsius
|
|
* @fake_battery_soc: SOC value to be reported to userspace
|
|
* @cfg_tchg_mins: maximum allowed software initiated charge time
|
|
* @chg_failed_count: counter to maintained number of times charging
|
|
* failed
|
|
* @cfg_bpd_detection: battery present detection mechanism selection
|
|
* @cfg_thermal_levels: amount of thermal mitigation levels
|
|
* @cfg_thermal_mitigation: thermal mitigation level values
|
|
* @therm_lvl_sel: thermal mitigation level selection
|
|
* @jeita_configure_lock: lock to serialize jeita configuration request
|
|
* @hw_access_lock: lock to serialize access to charger registers
|
|
* @ibat_change_lock: lock to serialize ibat change requests from
|
|
* USB and thermal.
|
|
* @irq_lock lock to serialize enabling/disabling of irq
|
|
* @supported_feature_flag bitmask for all supported features
|
|
* @vddtrim_alarm alarm to schedule trim work at regular
|
|
* interval
|
|
* @vddtrim_work work to perform actual vddmax trimming
|
|
* @init_trim_uv initial trim voltage at bootup
|
|
* @delta_vddmax_uv current vddmax trim voltage
|
|
* @chg_enable_lock: lock to serialize charging enable/disable for
|
|
* SOC based resume charging
|
|
* @usb_psy: power supply to export information to
|
|
* userspace
|
|
* @bms_psy: power supply to export information to
|
|
* userspace
|
|
* @batt_psy: power supply to export information to
|
|
* userspace
|
|
*/
|
|
struct qpnp_lbc_chip {
|
|
struct device *dev;
|
|
struct spmi_device *spmi;
|
|
u16 chgr_base;
|
|
u16 bat_if_base;
|
|
u16 usb_chgpth_base;
|
|
u16 misc_base;
|
|
bool bat_is_cool;
|
|
bool bat_is_warm;
|
|
bool chg_done;
|
|
bool usb_present;
|
|
bool batt_present;
|
|
bool cfg_charging_disabled;
|
|
bool cfg_btc_disabled;
|
|
bool cfg_use_fake_battery;
|
|
bool fastchg_on;
|
|
bool cfg_use_external_charger;
|
|
bool cfg_chgr_led_support;
|
|
bool non_collapsible_chgr_detected;
|
|
unsigned int cfg_warm_bat_chg_ma;
|
|
unsigned int cfg_cool_bat_chg_ma;
|
|
unsigned int cfg_safe_voltage_mv;
|
|
unsigned int cfg_max_voltage_mv;
|
|
unsigned int cfg_min_voltage_mv;
|
|
unsigned int cfg_charger_detect_eoc;
|
|
unsigned int cfg_disable_vbatdet_based_recharge;
|
|
unsigned int cfg_batt_weak_voltage_uv;
|
|
unsigned int cfg_collapsible_chgr_support;
|
|
unsigned int cfg_warm_bat_mv;
|
|
unsigned int cfg_cool_bat_mv;
|
|
unsigned int cfg_hot_batt_p;
|
|
unsigned int cfg_cold_batt_p;
|
|
unsigned int cfg_thermal_levels;
|
|
unsigned int therm_lvl_sel;
|
|
unsigned int *thermal_mitigation;
|
|
unsigned int cfg_safe_current;
|
|
unsigned int cfg_tchg_mins;
|
|
unsigned int chg_failed_count;
|
|
unsigned int supported_feature_flag;
|
|
int cfg_bpd_detection;
|
|
int cfg_warm_bat_decidegc;
|
|
int cfg_cool_bat_decidegc;
|
|
int fake_battery_soc;
|
|
int cfg_soc_resume_limit;
|
|
int cfg_float_charge;
|
|
int charger_disabled;
|
|
int prev_max_ma;
|
|
int usb_psy_ma;
|
|
int delta_vddmax_uv;
|
|
int init_trim_uv;
|
|
struct delayed_work collapsible_detection_work;
|
|
|
|
/* parallel-chg params */
|
|
int parallel_charging_enabled;
|
|
int lbc_max_chg_current;
|
|
int ichg_now;
|
|
|
|
struct alarm vddtrim_alarm;
|
|
struct work_struct vddtrim_work;
|
|
struct qpnp_lbc_irq irqs[MAX_IRQS];
|
|
struct mutex jeita_configure_lock;
|
|
struct mutex chg_enable_lock;
|
|
spinlock_t ibat_change_lock;
|
|
spinlock_t hw_access_lock;
|
|
spinlock_t irq_lock;
|
|
struct power_supply *usb_psy;
|
|
struct power_supply *bms_psy;
|
|
struct power_supply batt_psy;
|
|
struct qpnp_adc_tm_btm_param adc_param;
|
|
struct qpnp_vadc_chip *vadc_dev;
|
|
struct qpnp_adc_tm_chip *adc_tm_dev;
|
|
struct led_classdev led_cdev;
|
|
struct dentry *debug_root;
|
|
|
|
/* parallel-chg params */
|
|
struct power_supply parallel_psy;
|
|
struct delayed_work parallel_work;
|
|
};
|
|
|
|
static void qpnp_lbc_enable_irq(struct qpnp_lbc_chip *chip,
|
|
struct qpnp_lbc_irq *irq)
|
|
{
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&chip->irq_lock, flags);
|
|
if (__test_and_clear_bit(0, &irq->disabled)) {
|
|
pr_debug("number = %d\n", irq->irq);
|
|
enable_irq(irq->irq);
|
|
if (irq->is_wake)
|
|
enable_irq_wake(irq->irq);
|
|
}
|
|
spin_unlock_irqrestore(&chip->irq_lock, flags);
|
|
}
|
|
|
|
static void qpnp_lbc_disable_irq(struct qpnp_lbc_chip *chip,
|
|
struct qpnp_lbc_irq *irq)
|
|
{
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&chip->irq_lock, flags);
|
|
if (!__test_and_set_bit(0, &irq->disabled)) {
|
|
pr_debug("number = %d\n", irq->irq);
|
|
disable_irq_nosync(irq->irq);
|
|
if (irq->is_wake)
|
|
disable_irq_wake(irq->irq);
|
|
}
|
|
spin_unlock_irqrestore(&chip->irq_lock, flags);
|
|
}
|
|
|
|
static int __qpnp_lbc_read(struct spmi_device *spmi, u16 base,
|
|
u8 *val, int count)
|
|
{
|
|
int rc = 0;
|
|
|
|
rc = spmi_ext_register_readl(spmi->ctrl, spmi->sid, base, val, count);
|
|
if (rc)
|
|
pr_err("SPMI read failed base=0x%02x sid=0x%02x rc=%d\n",
|
|
base, spmi->sid, rc);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int __qpnp_lbc_write(struct spmi_device *spmi, u16 base,
|
|
u8 *val, int count)
|
|
{
|
|
int rc;
|
|
|
|
rc = spmi_ext_register_writel(spmi->ctrl, spmi->sid, base, val,
|
|
count);
|
|
if (rc)
|
|
pr_err("SPMI write failed base=0x%02x sid=0x%02x rc=%d\n",
|
|
base, spmi->sid, rc);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int __qpnp_lbc_secure_write(struct spmi_device *spmi, u16 base,
|
|
u16 offset, u8 *val, int count)
|
|
{
|
|
int rc;
|
|
u8 reg_val;
|
|
|
|
reg_val = 0xA5;
|
|
rc = __qpnp_lbc_write(spmi, base + SEC_ACCESS, ®_val, 1);
|
|
if (rc) {
|
|
pr_err("SPMI read failed base=0x%02x sid=0x%02x rc=%d\n",
|
|
base + SEC_ACCESS, spmi->sid, rc);
|
|
return rc;
|
|
}
|
|
|
|
rc = __qpnp_lbc_write(spmi, base + offset, val, 1);
|
|
if (rc)
|
|
pr_err("SPMI write failed base=0x%02x sid=0x%02x rc=%d\n",
|
|
base + SEC_ACCESS, spmi->sid, rc);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int qpnp_lbc_read(struct qpnp_lbc_chip *chip, u16 base,
|
|
u8 *val, int count)
|
|
{
|
|
int rc = 0;
|
|
struct spmi_device *spmi = chip->spmi;
|
|
unsigned long flags;
|
|
|
|
if (base == 0) {
|
|
pr_err("base cannot be zero base=0x%02x sid=0x%02x rc=%d\n",
|
|
base, spmi->sid, rc);
|
|
return -EINVAL;
|
|
}
|
|
|
|
spin_lock_irqsave(&chip->hw_access_lock, flags);
|
|
rc = __qpnp_lbc_read(spmi, base, val, count);
|
|
spin_unlock_irqrestore(&chip->hw_access_lock, flags);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int qpnp_lbc_write(struct qpnp_lbc_chip *chip, u16 base,
|
|
u8 *val, int count)
|
|
{
|
|
int rc = 0;
|
|
struct spmi_device *spmi = chip->spmi;
|
|
unsigned long flags;
|
|
|
|
if (base == 0) {
|
|
pr_err("base cannot be zero base=0x%02x sid=0x%02x rc=%d\n",
|
|
base, spmi->sid, rc);
|
|
return -EINVAL;
|
|
}
|
|
|
|
spin_lock_irqsave(&chip->hw_access_lock, flags);
|
|
rc = __qpnp_lbc_write(spmi, base, val, count);
|
|
spin_unlock_irqrestore(&chip->hw_access_lock, flags);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int qpnp_lbc_masked_write(struct qpnp_lbc_chip *chip, u16 base,
|
|
u8 mask, u8 val)
|
|
{
|
|
int rc;
|
|
u8 reg_val;
|
|
struct spmi_device *spmi = chip->spmi;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&chip->hw_access_lock, flags);
|
|
rc = __qpnp_lbc_read(spmi, base, ®_val, 1);
|
|
if (rc) {
|
|
pr_err("spmi read failed: addr=%03X, rc=%d\n", base, rc);
|
|
goto out;
|
|
}
|
|
pr_debug("addr = 0x%x read 0x%x\n", base, reg_val);
|
|
|
|
reg_val &= ~mask;
|
|
reg_val |= val & mask;
|
|
|
|
pr_debug("writing to base=%x val=%x\n", base, reg_val);
|
|
|
|
rc = __qpnp_lbc_write(spmi, base, ®_val, 1);
|
|
if (rc)
|
|
pr_err("spmi write failed: addr=%03X, rc=%d\n", base, rc);
|
|
|
|
out:
|
|
spin_unlock_irqrestore(&chip->hw_access_lock, flags);
|
|
return rc;
|
|
}
|
|
|
|
static int __qpnp_lbc_secure_masked_write(struct spmi_device *spmi, u16 base,
|
|
u16 offset, u8 mask, u8 val)
|
|
{
|
|
int rc;
|
|
u8 reg_val, reg_val1;
|
|
|
|
rc = __qpnp_lbc_read(spmi, base + offset, ®_val, 1);
|
|
if (rc) {
|
|
pr_err("spmi read failed: addr=%03X, rc=%d\n", base, rc);
|
|
return rc;
|
|
}
|
|
pr_debug("addr = 0x%x read 0x%x\n", base, reg_val);
|
|
|
|
reg_val &= ~mask;
|
|
reg_val |= val & mask;
|
|
pr_debug("writing to base=%x val=%x\n", base, reg_val);
|
|
|
|
reg_val1 = 0xA5;
|
|
rc = __qpnp_lbc_write(spmi, base + SEC_ACCESS, ®_val1, 1);
|
|
if (rc) {
|
|
pr_err("SPMI read failed base=0x%02x sid=0x%02x rc=%d\n",
|
|
base + SEC_ACCESS, spmi->sid, rc);
|
|
return rc;
|
|
}
|
|
|
|
rc = __qpnp_lbc_write(spmi, base + offset, ®_val, 1);
|
|
if (rc) {
|
|
pr_err("SPMI write failed base=0x%02x sid=0x%02x rc=%d\n",
|
|
base + offset, spmi->sid, rc);
|
|
return rc;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int qpnp_lbc_get_trim_voltage(u8 trim_reg)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < MAX_VDD_EA_TRIM_CFG; i++)
|
|
if (trim_reg == vddtrim_map[i].trim_val)
|
|
return vddtrim_map[i].trim_uv;
|
|
|
|
pr_err("Invalid trim reg reg_val=%x\n", trim_reg);
|
|
return -EINVAL;
|
|
}
|
|
|
|
static u8 qpnp_lbc_get_trim_val(struct qpnp_lbc_chip *chip)
|
|
{
|
|
int i, sign;
|
|
int delta_uv;
|
|
|
|
sign = (chip->delta_vddmax_uv >= 0) ? -1 : 1;
|
|
|
|
switch (sign) {
|
|
case -1:
|
|
for (i = TRIM_CENTER; i >= 0; i--) {
|
|
if (vddtrim_map[i].trim_uv > chip->delta_vddmax_uv) {
|
|
delta_uv = AVG(vddtrim_map[i].trim_uv,
|
|
vddtrim_map[i + 1].trim_uv);
|
|
if (chip->delta_vddmax_uv >= delta_uv)
|
|
return vddtrim_map[i].trim_val;
|
|
else
|
|
return vddtrim_map[i + 1].trim_val;
|
|
}
|
|
}
|
|
i = 0;
|
|
break;
|
|
case 1:
|
|
for (i = TRIM_CENTER; i < ARRAY_SIZE(vddtrim_map); i++) {
|
|
if (vddtrim_map[i].trim_uv < chip->delta_vddmax_uv) {
|
|
delta_uv = AVG(vddtrim_map[i].trim_uv,
|
|
vddtrim_map[i - 1].trim_uv);
|
|
if (chip->delta_vddmax_uv >= delta_uv)
|
|
return vddtrim_map[i - 1].trim_val;
|
|
else
|
|
return vddtrim_map[i].trim_val;
|
|
}
|
|
}
|
|
i = ARRAY_SIZE(vddtrim_map) - 1;
|
|
break;
|
|
}
|
|
|
|
return vddtrim_map[i].trim_val;
|
|
}
|
|
|
|
static int qpnp_lbc_is_usb_chg_plugged_in(struct qpnp_lbc_chip *chip)
|
|
{
|
|
u8 usbin_valid_rt_sts;
|
|
int rc;
|
|
|
|
rc = qpnp_lbc_read(chip, chip->usb_chgpth_base + INT_RT_STS_REG,
|
|
&usbin_valid_rt_sts, 1);
|
|
if (rc) {
|
|
pr_err("spmi read failed: addr=%03X, rc=%d\n",
|
|
chip->usb_chgpth_base + INT_RT_STS_REG, rc);
|
|
return rc;
|
|
}
|
|
|
|
pr_debug("rt_sts 0x%x\n", usbin_valid_rt_sts);
|
|
|
|
return (usbin_valid_rt_sts & USB_IN_VALID_MASK) ? 1 : 0;
|
|
}
|
|
|
|
static int qpnp_lbc_is_chg_gone(struct qpnp_lbc_chip *chip)
|
|
{
|
|
u8 rt_sts;
|
|
int rc;
|
|
|
|
rc = qpnp_lbc_read(chip, chip->usb_chgpth_base + INT_RT_STS_REG,
|
|
&rt_sts, 1);
|
|
if (rc) {
|
|
pr_err("spmi read failed: addr=0x%04x, rc=%d\n",
|
|
chip->usb_chgpth_base + INT_RT_STS_REG, rc);
|
|
return rc;
|
|
}
|
|
|
|
pr_debug("rt_sts 0x%x\n", rt_sts);
|
|
|
|
return (rt_sts & CHG_GONE_BIT) ? 1 : 0;
|
|
}
|
|
|
|
static int qpnp_lbc_charger_enable(struct qpnp_lbc_chip *chip, int reason,
|
|
int enable)
|
|
{
|
|
int disabled = chip->charger_disabled;
|
|
u8 reg_val;
|
|
int rc = 0;
|
|
|
|
pr_debug("reason=%d requested_enable=%d disabled_status=%d\n",
|
|
reason, enable, disabled);
|
|
if (enable)
|
|
disabled &= ~reason;
|
|
else
|
|
disabled |= reason;
|
|
|
|
if (!!chip->charger_disabled == !!disabled)
|
|
goto skip;
|
|
|
|
reg_val = !!disabled ? CHG_FORCE_BATT_ON : CHG_ENABLE;
|
|
rc = qpnp_lbc_masked_write(chip, chip->chgr_base + CHG_CTRL_REG,
|
|
CHG_EN_MASK, reg_val);
|
|
if (rc) {
|
|
pr_err("Failed to %s charger rc=%d\n",
|
|
reg_val ? "enable" : "disable", rc);
|
|
return rc;
|
|
}
|
|
skip:
|
|
chip->charger_disabled = disabled;
|
|
return rc;
|
|
}
|
|
|
|
static int qpnp_lbc_is_batt_present(struct qpnp_lbc_chip *chip)
|
|
{
|
|
u8 batt_pres_rt_sts;
|
|
int rc;
|
|
|
|
rc = qpnp_lbc_read(chip, chip->bat_if_base + INT_RT_STS_REG,
|
|
&batt_pres_rt_sts, 1);
|
|
if (rc) {
|
|
pr_err("spmi read failed: addr=%03X, rc=%d\n",
|
|
chip->bat_if_base + INT_RT_STS_REG, rc);
|
|
return rc;
|
|
}
|
|
|
|
return (batt_pres_rt_sts & BATT_PRES_IRQ) ? 1 : 0;
|
|
}
|
|
|
|
static int qpnp_lbc_bat_if_configure_btc(struct qpnp_lbc_chip *chip)
|
|
{
|
|
u8 btc_cfg = 0, mask = 0, rc;
|
|
|
|
/* Do nothing if battery peripheral not present */
|
|
if (!chip->bat_if_base)
|
|
return 0;
|
|
|
|
if ((chip->cfg_hot_batt_p == HOT_THD_25_PCT)
|
|
|| (chip->cfg_hot_batt_p == HOT_THD_35_PCT)) {
|
|
btc_cfg |= btc_value[chip->cfg_hot_batt_p];
|
|
mask |= BTC_HOT_MASK;
|
|
}
|
|
|
|
if ((chip->cfg_cold_batt_p == COLD_THD_70_PCT) ||
|
|
(chip->cfg_cold_batt_p == COLD_THD_80_PCT)) {
|
|
btc_cfg |= btc_value[chip->cfg_cold_batt_p];
|
|
mask |= BTC_COLD_MASK;
|
|
}
|
|
|
|
mask |= BTC_COMP_EN_MASK;
|
|
if (!chip->cfg_btc_disabled)
|
|
btc_cfg |= BTC_COMP_EN_MASK;
|
|
|
|
pr_debug("BTC configuration mask=%x\n", btc_cfg);
|
|
|
|
rc = qpnp_lbc_masked_write(chip,
|
|
chip->bat_if_base + BAT_IF_BTC_CTRL,
|
|
mask, btc_cfg);
|
|
if (rc)
|
|
pr_err("Failed to configure BTC rc=%d\n", rc);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int qpnp_chg_collapsible_chgr_config(struct qpnp_lbc_chip *chip,
|
|
bool enable)
|
|
{
|
|
u8 reg_val;
|
|
int rc;
|
|
|
|
pr_debug("Configure for %scollapsible charger\n",
|
|
enable ? "" : "non-");
|
|
/*
|
|
* The flow to enable/disable the collapsible charger configuration:
|
|
* Enable: Override USBIN_LLIMIT_OK -->
|
|
* Disable VIN_MIN comparator -->
|
|
* Enable CHG_GONE comparator
|
|
* Disable: Enable VIN_MIN comparator -->
|
|
* Enable USBIN_LLIMIT_OK -->
|
|
* Disable CHG_GONE comparator
|
|
*/
|
|
if (enable) {
|
|
/* Override USBIN_LLIMIT_OK */
|
|
reg_val = USBIN_LLIMIT_OK_OVERRIDE_1;
|
|
rc = __qpnp_lbc_secure_masked_write(chip->spmi,
|
|
chip->usb_chgpth_base,
|
|
USB_COMP_OVR1_REG,
|
|
USBIN_LLIMIT_OK_MASK, reg_val);
|
|
if (rc) {
|
|
pr_err("Failed to override USB_LLIMIT_OK rc = %d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
/* Configure VIN_MIN comparator */
|
|
rc = __qpnp_lbc_secure_masked_write(chip->spmi,
|
|
chip->chgr_base, CHG_TEST_LOOP_REG,
|
|
VIN_MIN_LOOP_DISABLE_BIT,
|
|
enable ? VIN_MIN_LOOP_DISABLE_BIT : 0);
|
|
if (rc) {
|
|
pr_err("Failed to %s VIN_MIN comparator rc = %d\n",
|
|
enable ? "disable" : "enable", rc);
|
|
return rc;
|
|
}
|
|
|
|
if (!enable) {
|
|
/* Enable USBIN_LLIMIT_OK */
|
|
reg_val = USBIN_LLIMIT_OK_NO_OVERRIDE;
|
|
rc = __qpnp_lbc_secure_masked_write(chip->spmi,
|
|
chip->usb_chgpth_base,
|
|
USB_COMP_OVR1_REG,
|
|
USBIN_LLIMIT_OK_MASK, reg_val);
|
|
if (rc) {
|
|
pr_err("Failed to override USB_LLIMIT_OK rc = %d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
/* Configure CHG_GONE comparator */
|
|
reg_val = enable ? CHG_GONE_OK_EN_BIT : 0;
|
|
rc = __qpnp_lbc_secure_masked_write(chip->spmi,
|
|
chip->usb_chgpth_base, USB_OVP_TST5_REG,
|
|
CHG_GONE_OK_EN_BIT, reg_val);
|
|
if (rc) {
|
|
pr_err("Failed to write CHG_GONE comparator rc = %d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#define QPNP_LBC_VBATWEAK_MIN_UV 3000000
|
|
#define QPNP_LBC_VBATWEAK_MAX_UV 3581250
|
|
#define QPNP_LBC_VBATWEAK_STEP_UV 18750
|
|
static int qpnp_lbc_vbatweak_set(struct qpnp_lbc_chip *chip, int voltage)
|
|
{
|
|
u8 reg_val;
|
|
int rc;
|
|
|
|
if (voltage < QPNP_LBC_VBATWEAK_MIN_UV ||
|
|
voltage > QPNP_LBC_VBATWEAK_MAX_UV) {
|
|
rc = -EINVAL;
|
|
} else {
|
|
reg_val = (voltage - QPNP_LBC_VBATWEAK_MIN_UV) /
|
|
QPNP_LBC_VBATWEAK_STEP_UV;
|
|
pr_debug("VBAT_WEAK=%d setting %02x\n",
|
|
chip->cfg_batt_weak_voltage_uv, reg_val);
|
|
rc = qpnp_lbc_write(chip, chip->chgr_base + CHG_VBAT_WEAK_REG,
|
|
®_val, 1);
|
|
if (rc)
|
|
pr_err("Failed to set VBAT_WEAK rc=%d\n", rc);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
#define QPNP_LBC_VBAT_MIN_MV 4000
|
|
#define QPNP_LBC_VBAT_MAX_MV 4775
|
|
#define QPNP_LBC_VBAT_STEP_MV 25
|
|
static int qpnp_lbc_vddsafe_set(struct qpnp_lbc_chip *chip, int voltage)
|
|
{
|
|
u8 reg_val;
|
|
int rc;
|
|
|
|
if (voltage < QPNP_LBC_VBAT_MIN_MV
|
|
|| voltage > QPNP_LBC_VBAT_MAX_MV) {
|
|
pr_err("bad mV=%d asked to set\n", voltage);
|
|
return -EINVAL;
|
|
}
|
|
reg_val = (voltage - QPNP_LBC_VBAT_MIN_MV) / QPNP_LBC_VBAT_STEP_MV;
|
|
pr_debug("voltage=%d setting %02x\n", voltage, reg_val);
|
|
rc = qpnp_lbc_write(chip, chip->chgr_base + CHG_VDD_SAFE_REG,
|
|
®_val, 1);
|
|
if (rc)
|
|
pr_err("Failed to set VDD_SAFE rc=%d\n", rc);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int qpnp_lbc_vddmax_set(struct qpnp_lbc_chip *chip, int voltage)
|
|
{
|
|
u8 reg_val;
|
|
int rc, trim_val;
|
|
unsigned long flags;
|
|
|
|
if (voltage < QPNP_LBC_VBAT_MIN_MV
|
|
|| voltage > QPNP_LBC_VBAT_MAX_MV) {
|
|
pr_err("bad mV=%d asked to set\n", voltage);
|
|
return -EINVAL;
|
|
}
|
|
|
|
spin_lock_irqsave(&chip->hw_access_lock, flags);
|
|
reg_val = (voltage - QPNP_LBC_VBAT_MIN_MV) / QPNP_LBC_VBAT_STEP_MV;
|
|
pr_debug("voltage=%d setting %02x\n", voltage, reg_val);
|
|
rc = __qpnp_lbc_write(chip->spmi, chip->chgr_base + CHG_VDD_MAX_REG,
|
|
®_val, 1);
|
|
if (rc) {
|
|
pr_err("Failed to set VDD_MAX rc=%d\n", rc);
|
|
goto out;
|
|
}
|
|
|
|
/* Update trim value */
|
|
if (chip->supported_feature_flag & VDD_TRIM_SUPPORTED) {
|
|
trim_val = qpnp_lbc_get_trim_val(chip);
|
|
reg_val = (trim_val & VDD_TRIM3_MASK) << VDD_TRIM3_SHIFT;
|
|
rc = __qpnp_lbc_secure_masked_write(chip->spmi,
|
|
chip->misc_base, MISC_TRIM3_REG,
|
|
MISC_TRIM3_VDD_MASK, reg_val);
|
|
if (rc) {
|
|
pr_err("Failed to set MISC_TRIM3_REG rc=%d\n", rc);
|
|
goto out;
|
|
}
|
|
|
|
reg_val = (trim_val & VDD_TRIM4_MASK) << VDD_TRIM4_SHIFT;
|
|
rc = __qpnp_lbc_secure_masked_write(chip->spmi,
|
|
chip->misc_base, MISC_TRIM4_REG,
|
|
MISC_TRIM4_VDD_MASK, reg_val);
|
|
if (rc) {
|
|
pr_err("Failed to set MISC_TRIM4_REG rc=%d\n", rc);
|
|
goto out;
|
|
}
|
|
|
|
chip->delta_vddmax_uv = qpnp_lbc_get_trim_voltage(trim_val);
|
|
if (chip->delta_vddmax_uv == -EINVAL) {
|
|
pr_err("Invalid trim voltage=%d\n",
|
|
chip->delta_vddmax_uv);
|
|
rc = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
pr_debug("VDD_MAX delta=%d trim value=%x\n",
|
|
chip->delta_vddmax_uv, trim_val);
|
|
}
|
|
|
|
out:
|
|
spin_unlock_irqrestore(&chip->hw_access_lock, flags);
|
|
return rc;
|
|
}
|
|
|
|
static int qpnp_lbc_set_appropriate_vddmax(struct qpnp_lbc_chip *chip)
|
|
{
|
|
int rc;
|
|
|
|
if (chip->bat_is_cool)
|
|
rc = qpnp_lbc_vddmax_set(chip, chip->cfg_cool_bat_mv);
|
|
else if (chip->bat_is_warm)
|
|
rc = qpnp_lbc_vddmax_set(chip, chip->cfg_warm_bat_mv);
|
|
else
|
|
rc = qpnp_lbc_vddmax_set(chip, chip->cfg_max_voltage_mv);
|
|
if (rc)
|
|
pr_err("Failed to set appropriate vddmax rc=%d\n", rc);
|
|
|
|
return rc;
|
|
}
|
|
|
|
#define QPNP_LBC_MIN_DELTA_UV 13000
|
|
static void qpnp_lbc_adjust_vddmax(struct qpnp_lbc_chip *chip, int vbat_uv)
|
|
{
|
|
int delta_uv, prev_delta_uv, rc;
|
|
|
|
prev_delta_uv = chip->delta_vddmax_uv;
|
|
delta_uv = (int)(chip->cfg_max_voltage_mv * 1000) - vbat_uv;
|
|
|
|
/*
|
|
* If delta_uv is positive, apply trim if delta_uv > 13mv
|
|
* If delta_uv is negative always apply trim.
|
|
*/
|
|
if (delta_uv > 0 && delta_uv < QPNP_LBC_MIN_DELTA_UV) {
|
|
pr_debug("vbat is not low enough to increase vdd\n");
|
|
return;
|
|
}
|
|
|
|
pr_debug("vbat=%d current delta_uv=%d prev delta_vddmax_uv=%d\n",
|
|
vbat_uv, delta_uv, chip->delta_vddmax_uv);
|
|
chip->delta_vddmax_uv = delta_uv + chip->delta_vddmax_uv;
|
|
pr_debug("new delta_vddmax_uv %d\n", chip->delta_vddmax_uv);
|
|
rc = qpnp_lbc_set_appropriate_vddmax(chip);
|
|
if (rc) {
|
|
pr_err("Failed to set appropriate vddmax rc=%d\n", rc);
|
|
chip->delta_vddmax_uv = prev_delta_uv;
|
|
}
|
|
}
|
|
|
|
#define QPNP_LBC_VINMIN_MIN_MV 4200
|
|
#define QPNP_LBC_VINMIN_MAX_MV 5037
|
|
#define QPNP_LBC_VINMIN_STEP_MV 27
|
|
static int qpnp_lbc_vinmin_set(struct qpnp_lbc_chip *chip, int voltage)
|
|
{
|
|
u8 reg_val;
|
|
int rc;
|
|
|
|
if ((voltage < QPNP_LBC_VINMIN_MIN_MV)
|
|
|| (voltage > QPNP_LBC_VINMIN_MAX_MV)) {
|
|
pr_err("bad mV=%d asked to set\n", voltage);
|
|
return -EINVAL;
|
|
}
|
|
|
|
reg_val = (voltage - QPNP_LBC_VINMIN_MIN_MV) / QPNP_LBC_VINMIN_STEP_MV;
|
|
pr_debug("VIN_MIN=%d setting %02x\n", voltage, reg_val);
|
|
rc = qpnp_lbc_write(chip, chip->chgr_base + CHG_VIN_MIN_REG,
|
|
®_val, 1);
|
|
if (rc)
|
|
pr_err("Failed to set VIN_MIN rc=%d\n", rc);
|
|
|
|
return rc;
|
|
}
|
|
|
|
#define QPNP_LBC_IBATSAFE_MIN_MA 90
|
|
#define QPNP_LBC_IBATSAFE_MAX_MA 1440
|
|
#define QPNP_LBC_I_STEP_MA 90
|
|
static int qpnp_lbc_ibatsafe_set(struct qpnp_lbc_chip *chip, int safe_current)
|
|
{
|
|
u8 reg_val;
|
|
int rc;
|
|
|
|
if (safe_current < QPNP_LBC_IBATSAFE_MIN_MA
|
|
|| safe_current > QPNP_LBC_IBATSAFE_MAX_MA) {
|
|
pr_err("bad mA=%d asked to set\n", safe_current);
|
|
return -EINVAL;
|
|
}
|
|
|
|
reg_val = (safe_current - QPNP_LBC_IBATSAFE_MIN_MA)
|
|
/ QPNP_LBC_I_STEP_MA;
|
|
pr_debug("Ibate_safe=%d setting %02x\n", safe_current, reg_val);
|
|
|
|
rc = qpnp_lbc_write(chip, chip->chgr_base + CHG_IBAT_SAFE_REG,
|
|
®_val, 1);
|
|
if (rc)
|
|
pr_err("Failed to set IBAT_SAFE rc=%d\n", rc);
|
|
|
|
return rc;
|
|
}
|
|
|
|
#define QPNP_LBC_IBATMAX_MIN 90
|
|
#define QPNP_LBC_IBATMAX_MAX 1440
|
|
/*
|
|
* Set maximum current limit from charger
|
|
* ibat = System current + charging current
|
|
*/
|
|
static int qpnp_lbc_ibatmax_set(struct qpnp_lbc_chip *chip, int chg_current)
|
|
{
|
|
u8 reg_val;
|
|
int rc;
|
|
|
|
if (chg_current > QPNP_LBC_IBATMAX_MAX)
|
|
pr_debug("bad mA=%d clamping current\n", chg_current);
|
|
|
|
chg_current = clamp(chg_current, QPNP_LBC_IBATMAX_MIN,
|
|
QPNP_LBC_IBATMAX_MAX);
|
|
reg_val = (chg_current - QPNP_LBC_IBATMAX_MIN) / QPNP_LBC_I_STEP_MA;
|
|
|
|
rc = qpnp_lbc_write(chip, chip->chgr_base + CHG_IBAT_MAX_REG,
|
|
®_val, 1);
|
|
if (rc)
|
|
pr_err("Failed to set IBAT_MAX rc=%d\n", rc);
|
|
else
|
|
chip->prev_max_ma = chg_current;
|
|
|
|
return rc;
|
|
}
|
|
|
|
#define QPNP_LBC_TCHG_MIN 4
|
|
#define QPNP_LBC_TCHG_MAX 512
|
|
#define QPNP_LBC_TCHG_STEP 4
|
|
static int qpnp_lbc_tchg_max_set(struct qpnp_lbc_chip *chip, int minutes)
|
|
{
|
|
u8 reg_val = 0;
|
|
int rc;
|
|
|
|
/* Disable timer */
|
|
rc = qpnp_lbc_masked_write(chip, chip->chgr_base + CHG_TCHG_MAX_EN_REG,
|
|
CHG_TCHG_MAX_EN_BIT, 0);
|
|
if (rc) {
|
|
pr_err("Failed to write tchg_max_en rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
/* If minutes is 0, just disable timer */
|
|
if (!minutes) {
|
|
pr_debug("Charger safety timer disabled\n");
|
|
return rc;
|
|
}
|
|
|
|
minutes = clamp(minutes, QPNP_LBC_TCHG_MIN, QPNP_LBC_TCHG_MAX);
|
|
|
|
reg_val = (minutes / QPNP_LBC_TCHG_STEP) - 1;
|
|
|
|
pr_debug("TCHG_MAX=%d mins setting %x\n", minutes, reg_val);
|
|
rc = qpnp_lbc_masked_write(chip, chip->chgr_base + CHG_TCHG_MAX_REG,
|
|
CHG_TCHG_MAX_MASK, reg_val);
|
|
if (rc) {
|
|
pr_err("Failed to write tchg_max_reg rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
/* Enable timer */
|
|
rc = qpnp_lbc_masked_write(chip, chip->chgr_base + CHG_TCHG_MAX_EN_REG,
|
|
CHG_TCHG_MAX_EN_BIT, CHG_TCHG_MAX_EN_BIT);
|
|
if (rc) {
|
|
pr_err("Failed to write tchg_max_en rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
#define LBC_CHGR_LED 0x4D
|
|
#define CHGR_LED_ON BIT(0)
|
|
#define CHGR_LED_OFF 0x0
|
|
#define CHGR_LED_STAT_MASK LBC_MASK(1, 0)
|
|
static void qpnp_lbc_chgr_led_brightness_set(struct led_classdev *cdev,
|
|
enum led_brightness value)
|
|
{
|
|
struct qpnp_lbc_chip *chip = container_of(cdev, struct qpnp_lbc_chip,
|
|
led_cdev);
|
|
u8 reg;
|
|
int rc;
|
|
|
|
if (value > LED_FULL)
|
|
value = LED_FULL;
|
|
|
|
pr_debug("set the charger led brightness to value=%d\n", value);
|
|
reg = (value > LED_OFF) ? CHGR_LED_ON : CHGR_LED_OFF;
|
|
|
|
rc = qpnp_lbc_masked_write(chip, chip->chgr_base + LBC_CHGR_LED,
|
|
CHGR_LED_STAT_MASK, reg);
|
|
if (rc)
|
|
pr_err("Failed to write charger led rc=%d\n", rc);
|
|
}
|
|
|
|
static enum
|
|
led_brightness qpnp_lbc_chgr_led_brightness_get(struct led_classdev *cdev)
|
|
{
|
|
|
|
struct qpnp_lbc_chip *chip = container_of(cdev, struct qpnp_lbc_chip,
|
|
led_cdev);
|
|
u8 reg_val, chgr_led_sts;
|
|
int rc;
|
|
|
|
rc = qpnp_lbc_read(chip, chip->chgr_base + LBC_CHGR_LED,
|
|
®_val, 1);
|
|
if (rc) {
|
|
pr_err("Failed to read charger led rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
chgr_led_sts = reg_val & CHGR_LED_STAT_MASK;
|
|
pr_debug("charger led brightness chgr_led_sts=%d\n", chgr_led_sts);
|
|
|
|
return (chgr_led_sts == CHGR_LED_ON) ? LED_FULL : LED_OFF;
|
|
}
|
|
|
|
static int qpnp_lbc_register_chgr_led(struct qpnp_lbc_chip *chip)
|
|
{
|
|
int rc;
|
|
|
|
chip->led_cdev.name = "red";
|
|
chip->led_cdev.brightness_set = qpnp_lbc_chgr_led_brightness_set;
|
|
chip->led_cdev.brightness_get = qpnp_lbc_chgr_led_brightness_get;
|
|
|
|
rc = led_classdev_register(chip->dev, &chip->led_cdev);
|
|
if (rc)
|
|
dev_err(chip->dev, "unable to register charger led, rc=%d\n",
|
|
rc);
|
|
|
|
return rc;
|
|
};
|
|
|
|
static int is_vinmin_set(struct qpnp_lbc_chip *chip)
|
|
{
|
|
u8 reg;
|
|
int rc;
|
|
|
|
rc = qpnp_lbc_read(chip, chip->chgr_base + CHG_STATUS_REG, ®, 1);
|
|
if (rc) {
|
|
pr_err("Unable to read charger status rc=%d\n", rc);
|
|
return false;
|
|
}
|
|
pr_debug("chg_status=0x%x\n", reg);
|
|
|
|
return !!(reg & VINMIN_LOOP_BIT);
|
|
|
|
}
|
|
|
|
static int qpnp_lbc_vbatdet_override(struct qpnp_lbc_chip *chip, int ovr_val)
|
|
{
|
|
int rc;
|
|
u8 reg_val;
|
|
struct spmi_device *spmi = chip->spmi;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&chip->hw_access_lock, flags);
|
|
|
|
rc = __qpnp_lbc_read(spmi, chip->chgr_base + CHG_COMP_OVR1,
|
|
®_val, 1);
|
|
if (rc) {
|
|
pr_err("spmi read failed: addr=%03X, rc=%d\n",
|
|
chip->chgr_base, rc);
|
|
goto out;
|
|
}
|
|
pr_debug("addr = 0x%x read 0x%x\n", chip->chgr_base, reg_val);
|
|
|
|
reg_val &= ~CHG_VBAT_DET_OVR_MASK;
|
|
reg_val |= ovr_val & CHG_VBAT_DET_OVR_MASK;
|
|
|
|
pr_debug("writing to base=%x val=%x\n", chip->chgr_base, reg_val);
|
|
|
|
rc = __qpnp_lbc_secure_write(spmi, chip->chgr_base, CHG_COMP_OVR1,
|
|
®_val, 1);
|
|
if (rc)
|
|
pr_err("spmi write failed: addr=%03X, rc=%d\n",
|
|
chip->chgr_base, rc);
|
|
|
|
out:
|
|
spin_unlock_irqrestore(&chip->hw_access_lock, flags);
|
|
return rc;
|
|
}
|
|
|
|
static int get_prop_battery_voltage_now(struct qpnp_lbc_chip *chip)
|
|
{
|
|
int rc = 0;
|
|
struct qpnp_vadc_result results;
|
|
|
|
rc = qpnp_vadc_read(chip->vadc_dev, VBAT_SNS, &results);
|
|
if (rc) {
|
|
pr_err("Unable to read vbat rc=%d\n", rc);
|
|
return 0;
|
|
}
|
|
|
|
return results.physical;
|
|
}
|
|
|
|
static int get_prop_batt_present(struct qpnp_lbc_chip *chip)
|
|
{
|
|
u8 reg_val;
|
|
int rc;
|
|
|
|
rc = qpnp_lbc_read(chip, chip->bat_if_base + BAT_IF_PRES_STATUS_REG,
|
|
®_val, 1);
|
|
if (rc) {
|
|
pr_err("Failed to read battery status read failed rc=%d\n",
|
|
rc);
|
|
return 0;
|
|
}
|
|
|
|
return (reg_val & BATT_PRES_MASK) ? 1 : 0;
|
|
}
|
|
|
|
static int get_prop_batt_health(struct qpnp_lbc_chip *chip)
|
|
{
|
|
u8 reg_val;
|
|
int rc;
|
|
|
|
rc = qpnp_lbc_read(chip, chip->bat_if_base + BAT_IF_TEMP_STATUS_REG,
|
|
®_val, 1);
|
|
if (rc) {
|
|
pr_err("Failed to read battery health rc=%d\n", rc);
|
|
return POWER_SUPPLY_HEALTH_UNKNOWN;
|
|
}
|
|
|
|
if (BATT_TEMP_HOT_MASK & reg_val)
|
|
return POWER_SUPPLY_HEALTH_OVERHEAT;
|
|
if (!(BATT_TEMP_COLD_MASK & reg_val))
|
|
return POWER_SUPPLY_HEALTH_COLD;
|
|
if (chip->bat_is_cool)
|
|
return POWER_SUPPLY_HEALTH_COOL;
|
|
if (chip->bat_is_warm)
|
|
return POWER_SUPPLY_HEALTH_WARM;
|
|
|
|
return POWER_SUPPLY_HEALTH_GOOD;
|
|
}
|
|
|
|
static int get_prop_charge_type(struct qpnp_lbc_chip *chip)
|
|
{
|
|
int rc;
|
|
u8 reg_val;
|
|
|
|
if (!get_prop_batt_present(chip))
|
|
return POWER_SUPPLY_CHARGE_TYPE_NONE;
|
|
|
|
rc = qpnp_lbc_read(chip, chip->chgr_base + INT_RT_STS_REG,
|
|
®_val, 1);
|
|
if (rc) {
|
|
pr_err("Failed to read interrupt sts %d\n", rc);
|
|
return POWER_SUPPLY_CHARGE_TYPE_NONE;
|
|
}
|
|
|
|
if (reg_val & FAST_CHG_ON_IRQ)
|
|
return POWER_SUPPLY_CHARGE_TYPE_FAST;
|
|
|
|
return POWER_SUPPLY_CHARGE_TYPE_NONE;
|
|
}
|
|
|
|
static int get_prop_batt_status(struct qpnp_lbc_chip *chip)
|
|
{
|
|
int rc;
|
|
u8 reg_val;
|
|
|
|
if (qpnp_lbc_is_usb_chg_plugged_in(chip) && chip->chg_done)
|
|
return POWER_SUPPLY_STATUS_FULL;
|
|
|
|
rc = qpnp_lbc_read(chip, chip->chgr_base + INT_RT_STS_REG,
|
|
®_val, 1);
|
|
if (rc) {
|
|
pr_err("Failed to read interrupt sts rc= %d\n", rc);
|
|
return POWER_SUPPLY_CHARGE_TYPE_NONE;
|
|
}
|
|
|
|
if (reg_val & FAST_CHG_ON_IRQ)
|
|
return POWER_SUPPLY_STATUS_CHARGING;
|
|
|
|
return POWER_SUPPLY_STATUS_DISCHARGING;
|
|
}
|
|
|
|
static int get_prop_current_now(struct qpnp_lbc_chip *chip)
|
|
{
|
|
union power_supply_propval ret = {0,};
|
|
|
|
if (chip->bms_psy) {
|
|
chip->bms_psy->get_property(chip->bms_psy,
|
|
POWER_SUPPLY_PROP_CURRENT_NOW, &ret);
|
|
return ret.intval;
|
|
} else {
|
|
pr_debug("No BMS supply registered return 0\n");
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#define DEFAULT_CAPACITY 50
|
|
static int get_prop_capacity(struct qpnp_lbc_chip *chip)
|
|
{
|
|
union power_supply_propval ret = {0,};
|
|
int soc;
|
|
|
|
if (chip->fake_battery_soc >= 0)
|
|
return chip->fake_battery_soc;
|
|
|
|
if (chip->cfg_use_fake_battery || !get_prop_batt_present(chip))
|
|
return DEFAULT_CAPACITY;
|
|
|
|
if (chip->bms_psy) {
|
|
chip->bms_psy->get_property(chip->bms_psy,
|
|
POWER_SUPPLY_PROP_CAPACITY, &ret);
|
|
soc = ret.intval;
|
|
if (soc == 0) {
|
|
if (!qpnp_lbc_is_usb_chg_plugged_in(chip))
|
|
pr_warn_ratelimited("Batt 0, CHG absent\n");
|
|
}
|
|
return soc;
|
|
} else {
|
|
pr_debug("No BMS supply registered return %d\n",
|
|
DEFAULT_CAPACITY);
|
|
}
|
|
|
|
/*
|
|
* Return default capacity to avoid userspace
|
|
* from shutting down unecessarily
|
|
*/
|
|
return DEFAULT_CAPACITY;
|
|
}
|
|
|
|
#define DEFAULT_TEMP 250
|
|
static int get_prop_batt_temp(struct qpnp_lbc_chip *chip)
|
|
{
|
|
int rc = 0;
|
|
struct qpnp_vadc_result results;
|
|
|
|
if (chip->cfg_use_fake_battery || !get_prop_batt_present(chip))
|
|
return DEFAULT_TEMP;
|
|
|
|
rc = qpnp_vadc_read(chip->vadc_dev, LR_MUX1_BATT_THERM, &results);
|
|
if (rc) {
|
|
pr_debug("Unable to read batt temperature rc=%d\n", rc);
|
|
return DEFAULT_TEMP;
|
|
}
|
|
pr_debug("get_bat_temp %d, %lld\n", results.adc_code,
|
|
results.physical);
|
|
|
|
return (int)results.physical;
|
|
}
|
|
|
|
static void qpnp_lbc_set_appropriate_current(struct qpnp_lbc_chip *chip)
|
|
{
|
|
unsigned int chg_current = chip->usb_psy_ma;
|
|
|
|
if (chip->bat_is_cool && chip->cfg_cool_bat_chg_ma)
|
|
chg_current = min(chg_current, chip->cfg_cool_bat_chg_ma);
|
|
if (chip->bat_is_warm && chip->cfg_warm_bat_chg_ma)
|
|
chg_current = min(chg_current, chip->cfg_warm_bat_chg_ma);
|
|
if (chip->therm_lvl_sel != 0 && chip->thermal_mitigation)
|
|
chg_current = min(chg_current,
|
|
chip->thermal_mitigation[chip->therm_lvl_sel]);
|
|
|
|
pr_debug("setting charger current %d mA\n", chg_current);
|
|
qpnp_lbc_ibatmax_set(chip, chg_current);
|
|
}
|
|
|
|
static void qpnp_batt_external_power_changed(struct power_supply *psy)
|
|
{
|
|
struct qpnp_lbc_chip *chip = container_of(psy, struct qpnp_lbc_chip,
|
|
batt_psy);
|
|
union power_supply_propval ret = {0,};
|
|
int current_ma;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&chip->ibat_change_lock, flags);
|
|
if (!chip->bms_psy)
|
|
chip->bms_psy = power_supply_get_by_name("bms");
|
|
|
|
if (qpnp_lbc_is_usb_chg_plugged_in(chip)) {
|
|
chip->usb_psy->get_property(chip->usb_psy,
|
|
POWER_SUPPLY_PROP_CURRENT_MAX, &ret);
|
|
current_ma = ret.intval / 1000;
|
|
|
|
if (current_ma == chip->prev_max_ma)
|
|
goto skip_current_config;
|
|
|
|
/* Disable charger in case of reset or suspend event */
|
|
if (current_ma <= 2 && !chip->cfg_use_fake_battery
|
|
&& get_prop_batt_present(chip)) {
|
|
qpnp_lbc_charger_enable(chip, CURRENT, 0);
|
|
chip->usb_psy_ma = QPNP_CHG_I_MAX_MIN_90;
|
|
qpnp_lbc_set_appropriate_current(chip);
|
|
} else {
|
|
chip->usb_psy_ma = current_ma;
|
|
qpnp_lbc_set_appropriate_current(chip);
|
|
qpnp_lbc_charger_enable(chip, CURRENT, 1);
|
|
}
|
|
}
|
|
|
|
skip_current_config:
|
|
spin_unlock_irqrestore(&chip->ibat_change_lock, flags);
|
|
pr_debug("power supply changed batt_psy\n");
|
|
power_supply_changed(&chip->batt_psy);
|
|
}
|
|
|
|
static int qpnp_lbc_system_temp_level_set(struct qpnp_lbc_chip *chip,
|
|
int lvl_sel)
|
|
{
|
|
int rc = 0;
|
|
int prev_therm_lvl;
|
|
unsigned long flags;
|
|
|
|
if (!chip->thermal_mitigation) {
|
|
pr_err("Thermal mitigation not supported\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (lvl_sel < 0) {
|
|
pr_err("Unsupported level selected %d\n", lvl_sel);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (lvl_sel >= chip->cfg_thermal_levels) {
|
|
pr_err("Unsupported level selected %d forcing %d\n", lvl_sel,
|
|
chip->cfg_thermal_levels - 1);
|
|
lvl_sel = chip->cfg_thermal_levels - 1;
|
|
}
|
|
|
|
if (lvl_sel == chip->therm_lvl_sel)
|
|
return 0;
|
|
|
|
spin_lock_irqsave(&chip->ibat_change_lock, flags);
|
|
prev_therm_lvl = chip->therm_lvl_sel;
|
|
chip->therm_lvl_sel = lvl_sel;
|
|
if (chip->therm_lvl_sel == (chip->cfg_thermal_levels - 1)) {
|
|
/* Disable charging if highest value selected by */
|
|
rc = qpnp_lbc_charger_enable(chip, THERMAL, 0);
|
|
if (rc < 0)
|
|
dev_err(chip->dev,
|
|
"Failed to set disable charging rc %d\n", rc);
|
|
goto out;
|
|
}
|
|
|
|
qpnp_lbc_set_appropriate_current(chip);
|
|
|
|
if (prev_therm_lvl == chip->cfg_thermal_levels - 1) {
|
|
/*
|
|
* If previously highest value was selected charging must have
|
|
* been disabed. Enable charging.
|
|
*/
|
|
rc = qpnp_lbc_charger_enable(chip, THERMAL, 1);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev,
|
|
"Failed to enable charging rc %d\n", rc);
|
|
}
|
|
}
|
|
out:
|
|
spin_unlock_irqrestore(&chip->ibat_change_lock, flags);
|
|
return rc;
|
|
}
|
|
|
|
#define MIN_COOL_TEMP -300
|
|
#define MAX_WARM_TEMP 1000
|
|
#define HYSTERISIS_DECIDEGC 20
|
|
|
|
static int qpnp_lbc_configure_jeita(struct qpnp_lbc_chip *chip,
|
|
enum power_supply_property psp, int temp_degc)
|
|
{
|
|
int rc = 0;
|
|
|
|
if ((temp_degc < MIN_COOL_TEMP) || (temp_degc > MAX_WARM_TEMP)) {
|
|
pr_err("Bad temperature request %d\n", temp_degc);
|
|
return -EINVAL;
|
|
}
|
|
|
|
mutex_lock(&chip->jeita_configure_lock);
|
|
switch (psp) {
|
|
case POWER_SUPPLY_PROP_COOL_TEMP:
|
|
if (temp_degc >=
|
|
(chip->cfg_warm_bat_decidegc - HYSTERISIS_DECIDEGC)) {
|
|
pr_err("Can't set cool %d higher than warm %d - hysterisis %d\n",
|
|
temp_degc,
|
|
chip->cfg_warm_bat_decidegc,
|
|
HYSTERISIS_DECIDEGC);
|
|
rc = -EINVAL;
|
|
goto mutex_unlock;
|
|
}
|
|
if (chip->bat_is_cool)
|
|
chip->adc_param.high_temp =
|
|
temp_degc + HYSTERISIS_DECIDEGC;
|
|
else if (!chip->bat_is_warm)
|
|
chip->adc_param.low_temp = temp_degc;
|
|
|
|
chip->cfg_cool_bat_decidegc = temp_degc;
|
|
break;
|
|
case POWER_SUPPLY_PROP_WARM_TEMP:
|
|
if (temp_degc <=
|
|
(chip->cfg_cool_bat_decidegc + HYSTERISIS_DECIDEGC)) {
|
|
pr_err("Can't set warm %d higher than cool %d + hysterisis %d\n",
|
|
temp_degc,
|
|
chip->cfg_warm_bat_decidegc,
|
|
HYSTERISIS_DECIDEGC);
|
|
rc = -EINVAL;
|
|
goto mutex_unlock;
|
|
}
|
|
if (chip->bat_is_warm)
|
|
chip->adc_param.low_temp =
|
|
temp_degc - HYSTERISIS_DECIDEGC;
|
|
else if (!chip->bat_is_cool)
|
|
chip->adc_param.high_temp = temp_degc;
|
|
|
|
chip->cfg_warm_bat_decidegc = temp_degc;
|
|
break;
|
|
default:
|
|
rc = -EINVAL;
|
|
goto mutex_unlock;
|
|
}
|
|
|
|
if (qpnp_adc_tm_channel_measure(chip->adc_tm_dev, &chip->adc_param))
|
|
pr_err("request ADC error\n");
|
|
|
|
mutex_unlock:
|
|
mutex_unlock(&chip->jeita_configure_lock);
|
|
return rc;
|
|
}
|
|
|
|
static int qpnp_batt_property_is_writeable(struct power_supply *psy,
|
|
enum power_supply_property psp)
|
|
{
|
|
switch (psp) {
|
|
case POWER_SUPPLY_PROP_STATUS:
|
|
case POWER_SUPPLY_PROP_CAPACITY:
|
|
case POWER_SUPPLY_PROP_CHARGING_ENABLED:
|
|
case POWER_SUPPLY_PROP_COOL_TEMP:
|
|
case POWER_SUPPLY_PROP_VOLTAGE_MIN:
|
|
case POWER_SUPPLY_PROP_WARM_TEMP:
|
|
case POWER_SUPPLY_PROP_SYSTEM_TEMP_LEVEL:
|
|
return 1;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* End of charge happens only when BMS reports the battery status as full. For
|
|
* charging to end the s/w must put the usb path in suspend. Note that there
|
|
* is no battery fet and usb path suspend is the only control to prevent any
|
|
* current going in to the battery (and the system)
|
|
* Charging can begin only when VBATDET comparator outputs 0. This indicates
|
|
* that the battery is a at a lower voltage than 4% of the vddmax value.
|
|
* S/W can override this comparator to output a favourable value - this is
|
|
* used while resuming charging when the battery hasnt fallen below 4% but
|
|
* the SOC has fallen below the resume threshold.
|
|
*
|
|
* In short, when SOC resume happens:
|
|
* a. overide the comparator to output 0
|
|
* b. enable charging
|
|
*
|
|
* When vbatdet based resume happens:
|
|
* a. enable charging
|
|
*
|
|
* When end of charge happens:
|
|
* a. disable the overrides in the comparator
|
|
* (may be from a previous soc resume)
|
|
* b. disable charging
|
|
*/
|
|
static int qpnp_batt_power_set_property(struct power_supply *psy,
|
|
enum power_supply_property psp,
|
|
const union power_supply_propval *val)
|
|
{
|
|
struct qpnp_lbc_chip *chip = container_of(psy, struct qpnp_lbc_chip,
|
|
batt_psy);
|
|
int rc = 0;
|
|
|
|
switch (psp) {
|
|
case POWER_SUPPLY_PROP_STATUS:
|
|
mutex_lock(&chip->chg_enable_lock);
|
|
switch (val->intval) {
|
|
case POWER_SUPPLY_STATUS_FULL:
|
|
if (chip->cfg_float_charge)
|
|
break;
|
|
/* Disable charging */
|
|
rc = qpnp_lbc_charger_enable(chip, SOC, 0);
|
|
if (rc)
|
|
pr_err("Failed to disable charging rc=%d\n",
|
|
rc);
|
|
else
|
|
chip->chg_done = true;
|
|
|
|
/*
|
|
* Enable VBAT_DET based charging:
|
|
* To enable charging when VBAT falls below VBAT_DET
|
|
* and device stays suspended after EOC.
|
|
*/
|
|
if (!chip->cfg_disable_vbatdet_based_recharge) {
|
|
/* No override for VBAT_DET_LO comp */
|
|
rc = qpnp_lbc_vbatdet_override(chip,
|
|
OVERRIDE_NONE);
|
|
if (rc)
|
|
pr_err("Failed to override VBAT_DET rc=%d\n",
|
|
rc);
|
|
else
|
|
qpnp_lbc_enable_irq(chip,
|
|
&chip->irqs[CHG_VBAT_DET_LO]);
|
|
}
|
|
break;
|
|
case POWER_SUPPLY_STATUS_CHARGING:
|
|
chip->chg_done = false;
|
|
pr_debug("resuming charging by bms\n");
|
|
if (!chip->cfg_disable_vbatdet_based_recharge)
|
|
qpnp_lbc_vbatdet_override(chip, OVERRIDE_0);
|
|
|
|
qpnp_lbc_charger_enable(chip, SOC, 1);
|
|
break;
|
|
case POWER_SUPPLY_STATUS_DISCHARGING:
|
|
chip->chg_done = false;
|
|
pr_debug("status = DISCHARGING chg_done = %d\n",
|
|
chip->chg_done);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
mutex_unlock(&chip->chg_enable_lock);
|
|
break;
|
|
case POWER_SUPPLY_PROP_COOL_TEMP:
|
|
rc = qpnp_lbc_configure_jeita(chip, psp, val->intval);
|
|
break;
|
|
case POWER_SUPPLY_PROP_WARM_TEMP:
|
|
rc = qpnp_lbc_configure_jeita(chip, psp, val->intval);
|
|
break;
|
|
case POWER_SUPPLY_PROP_CAPACITY:
|
|
chip->fake_battery_soc = val->intval;
|
|
pr_debug("power supply changed batt_psy\n");
|
|
break;
|
|
case POWER_SUPPLY_PROP_CHARGING_ENABLED:
|
|
chip->cfg_charging_disabled = !(val->intval);
|
|
rc = qpnp_lbc_charger_enable(chip, USER,
|
|
!chip->cfg_charging_disabled);
|
|
if (rc)
|
|
pr_err("Failed to disable charging rc=%d\n", rc);
|
|
break;
|
|
case POWER_SUPPLY_PROP_VOLTAGE_MIN:
|
|
qpnp_lbc_vinmin_set(chip, val->intval / 1000);
|
|
break;
|
|
case POWER_SUPPLY_PROP_SYSTEM_TEMP_LEVEL:
|
|
qpnp_lbc_system_temp_level_set(chip, val->intval);
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
power_supply_changed(&chip->batt_psy);
|
|
return rc;
|
|
}
|
|
|
|
static int qpnp_batt_power_get_property(struct power_supply *psy,
|
|
enum power_supply_property psp,
|
|
union power_supply_propval *val)
|
|
{
|
|
struct qpnp_lbc_chip *chip =
|
|
container_of(psy, struct qpnp_lbc_chip, batt_psy);
|
|
|
|
switch (psp) {
|
|
case POWER_SUPPLY_PROP_STATUS:
|
|
val->intval = get_prop_batt_status(chip);
|
|
break;
|
|
case POWER_SUPPLY_PROP_CHARGE_TYPE:
|
|
val->intval = get_prop_charge_type(chip);
|
|
break;
|
|
case POWER_SUPPLY_PROP_HEALTH:
|
|
val->intval = get_prop_batt_health(chip);
|
|
break;
|
|
case POWER_SUPPLY_PROP_PRESENT:
|
|
val->intval = get_prop_batt_present(chip);
|
|
break;
|
|
case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
|
|
val->intval = chip->cfg_max_voltage_mv * 1000;
|
|
break;
|
|
case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
|
|
val->intval = chip->cfg_min_voltage_mv * 1000;
|
|
break;
|
|
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
|
|
val->intval = get_prop_battery_voltage_now(chip);
|
|
break;
|
|
case POWER_SUPPLY_PROP_TEMP:
|
|
val->intval = get_prop_batt_temp(chip);
|
|
break;
|
|
case POWER_SUPPLY_PROP_COOL_TEMP:
|
|
val->intval = chip->cfg_cool_bat_decidegc;
|
|
break;
|
|
case POWER_SUPPLY_PROP_WARM_TEMP:
|
|
val->intval = chip->cfg_warm_bat_decidegc;
|
|
break;
|
|
case POWER_SUPPLY_PROP_CAPACITY:
|
|
val->intval = get_prop_capacity(chip);
|
|
break;
|
|
case POWER_SUPPLY_PROP_CURRENT_NOW:
|
|
val->intval = get_prop_current_now(chip);
|
|
break;
|
|
case POWER_SUPPLY_PROP_CHARGING_ENABLED:
|
|
val->intval = !(chip->cfg_charging_disabled);
|
|
break;
|
|
case POWER_SUPPLY_PROP_SYSTEM_TEMP_LEVEL:
|
|
val->intval = chip->therm_lvl_sel;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#define VINMIN_DELAY msecs_to_jiffies(500)
|
|
static void qpnp_lbc_parallel_work(struct work_struct *work)
|
|
{
|
|
struct delayed_work *dwork = to_delayed_work(work);
|
|
struct qpnp_lbc_chip *chip = container_of(dwork,
|
|
struct qpnp_lbc_chip, parallel_work);
|
|
|
|
if (is_vinmin_set(chip)) {
|
|
/* vinmin-loop triggered - stop ibat increase */
|
|
pr_debug("vinmin_loop triggered ichg_now=%d\n", chip->ichg_now);
|
|
goto exit_work;
|
|
} else {
|
|
int temp = chip->ichg_now + QPNP_LBC_I_STEP_MA;
|
|
if (temp > chip->lbc_max_chg_current) {
|
|
pr_debug("ichg_now=%d beyond max_chg_limit=%d - stopping\n",
|
|
temp, chip->lbc_max_chg_current);
|
|
goto exit_work;
|
|
}
|
|
chip->ichg_now = temp;
|
|
qpnp_lbc_ibatmax_set(chip, chip->ichg_now);
|
|
pr_debug("ichg_now increased to %d\n", chip->ichg_now);
|
|
}
|
|
|
|
schedule_delayed_work(&chip->parallel_work, VINMIN_DELAY);
|
|
|
|
return;
|
|
|
|
exit_work:
|
|
pm_relax(chip->dev);
|
|
}
|
|
|
|
static int qpnp_lbc_parallel_charging_config(struct qpnp_lbc_chip *chip,
|
|
int enable)
|
|
{
|
|
chip->parallel_charging_enabled = !!enable;
|
|
|
|
if (enable) {
|
|
/* Prevent sleep until charger is configured */
|
|
chip->ichg_now = QPNP_LBC_IBATMAX_MIN;
|
|
qpnp_lbc_ibatmax_set(chip, chip->ichg_now);
|
|
qpnp_lbc_charger_enable(chip, PARALLEL, 1);
|
|
pm_stay_awake(chip->dev);
|
|
schedule_delayed_work(&chip->parallel_work, VINMIN_DELAY);
|
|
} else {
|
|
cancel_delayed_work_sync(&chip->parallel_work);
|
|
pm_relax(chip->dev);
|
|
/* set minimum charging current and disable charging */
|
|
chip->ichg_now = 0;
|
|
chip->lbc_max_chg_current = 0;
|
|
qpnp_lbc_ibatmax_set(chip, 0);
|
|
qpnp_lbc_charger_enable(chip, PARALLEL, 0);
|
|
}
|
|
|
|
pr_debug("charging=%d ichg_now=%d max_chg_current=%d\n",
|
|
enable, chip->ichg_now, chip->lbc_max_chg_current);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static enum power_supply_property qpnp_lbc_parallel_properties[] = {
|
|
POWER_SUPPLY_PROP_CHARGING_ENABLED,
|
|
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
|
|
POWER_SUPPLY_PROP_CURRENT_NOW,
|
|
POWER_SUPPLY_PROP_CHARGE_TYPE,
|
|
POWER_SUPPLY_PROP_STATUS,
|
|
POWER_SUPPLY_PROP_INPUT_VOLTAGE_REGULATION,
|
|
};
|
|
|
|
static int qpnp_lbc_parallel_set_property(struct power_supply *psy,
|
|
enum power_supply_property prop,
|
|
const union power_supply_propval *val)
|
|
{
|
|
int rc = 0;
|
|
struct qpnp_lbc_chip *chip = container_of(psy,
|
|
struct qpnp_lbc_chip, parallel_psy);
|
|
|
|
switch (prop) {
|
|
case POWER_SUPPLY_PROP_CHARGING_ENABLED:
|
|
qpnp_lbc_parallel_charging_config(chip, !!val->intval);
|
|
break;
|
|
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
|
|
chip->lbc_max_chg_current = val->intval / 1000;
|
|
pr_debug("lbc_max_current=%d\n", chip->lbc_max_chg_current);
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int qpnp_lbc_parallel_is_writeable(struct power_supply *psy,
|
|
enum power_supply_property prop)
|
|
{
|
|
int rc;
|
|
|
|
switch (prop) {
|
|
case POWER_SUPPLY_PROP_CHARGING_ENABLED:
|
|
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
|
|
rc = 1;
|
|
break;
|
|
default:
|
|
rc = 0;
|
|
break;
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
static int qpnp_lbc_parallel_get_property(struct power_supply *psy,
|
|
enum power_supply_property prop,
|
|
union power_supply_propval *val)
|
|
{
|
|
struct qpnp_lbc_chip *chip = container_of(psy,
|
|
struct qpnp_lbc_chip, parallel_psy);
|
|
|
|
switch (prop) {
|
|
case POWER_SUPPLY_PROP_CHARGING_ENABLED:
|
|
val->intval = chip->parallel_charging_enabled;
|
|
break;
|
|
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
|
|
val->intval = chip->lbc_max_chg_current * 1000;
|
|
break;
|
|
case POWER_SUPPLY_PROP_CURRENT_NOW:
|
|
val->intval = chip->ichg_now * 1000;
|
|
break;
|
|
case POWER_SUPPLY_PROP_CHARGE_TYPE:
|
|
val->intval = get_prop_charge_type(chip);
|
|
break;
|
|
case POWER_SUPPLY_PROP_STATUS:
|
|
val->intval = get_prop_batt_status(chip);
|
|
break;
|
|
case POWER_SUPPLY_PROP_INPUT_VOLTAGE_REGULATION:
|
|
val->intval = is_vinmin_set(chip);
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
static void qpnp_lbc_jeita_adc_notification(enum qpnp_tm_state state, void *ctx)
|
|
{
|
|
struct qpnp_lbc_chip *chip = ctx;
|
|
bool bat_warm = 0, bat_cool = 0;
|
|
int temp;
|
|
unsigned long flags;
|
|
|
|
if (state >= ADC_TM_STATE_NUM) {
|
|
pr_err("invalid notification %d\n", state);
|
|
return;
|
|
}
|
|
|
|
temp = get_prop_batt_temp(chip);
|
|
|
|
pr_debug("temp = %d state = %s\n", temp,
|
|
state == ADC_TM_WARM_STATE ? "warm" : "cool");
|
|
|
|
if (state == ADC_TM_WARM_STATE) {
|
|
if (temp >= chip->cfg_warm_bat_decidegc) {
|
|
/* Normal to warm */
|
|
bat_warm = true;
|
|
bat_cool = false;
|
|
chip->adc_param.low_temp =
|
|
chip->cfg_warm_bat_decidegc
|
|
- HYSTERISIS_DECIDEGC;
|
|
chip->adc_param.state_request =
|
|
ADC_TM_COOL_THR_ENABLE;
|
|
} else if (temp >=
|
|
chip->cfg_cool_bat_decidegc + HYSTERISIS_DECIDEGC) {
|
|
/* Cool to normal */
|
|
bat_warm = false;
|
|
bat_cool = false;
|
|
|
|
chip->adc_param.low_temp =
|
|
chip->cfg_cool_bat_decidegc;
|
|
chip->adc_param.high_temp =
|
|
chip->cfg_warm_bat_decidegc;
|
|
chip->adc_param.state_request =
|
|
ADC_TM_HIGH_LOW_THR_ENABLE;
|
|
}
|
|
} else {
|
|
if (temp <= chip->cfg_cool_bat_decidegc) {
|
|
/* Normal to cool */
|
|
bat_warm = false;
|
|
bat_cool = true;
|
|
chip->adc_param.high_temp =
|
|
chip->cfg_cool_bat_decidegc
|
|
+ HYSTERISIS_DECIDEGC;
|
|
chip->adc_param.state_request =
|
|
ADC_TM_WARM_THR_ENABLE;
|
|
} else if (temp <= (chip->cfg_warm_bat_decidegc -
|
|
HYSTERISIS_DECIDEGC)){
|
|
/* Warm to normal */
|
|
bat_warm = false;
|
|
bat_cool = false;
|
|
|
|
chip->adc_param.low_temp =
|
|
chip->cfg_cool_bat_decidegc;
|
|
chip->adc_param.high_temp =
|
|
chip->cfg_warm_bat_decidegc;
|
|
chip->adc_param.state_request =
|
|
ADC_TM_HIGH_LOW_THR_ENABLE;
|
|
}
|
|
}
|
|
|
|
if (chip->bat_is_cool ^ bat_cool || chip->bat_is_warm ^ bat_warm) {
|
|
spin_lock_irqsave(&chip->ibat_change_lock, flags);
|
|
chip->bat_is_cool = bat_cool;
|
|
chip->bat_is_warm = bat_warm;
|
|
qpnp_lbc_set_appropriate_vddmax(chip);
|
|
qpnp_lbc_set_appropriate_current(chip);
|
|
spin_unlock_irqrestore(&chip->ibat_change_lock, flags);
|
|
}
|
|
|
|
pr_debug("warm %d, cool %d, low = %d deciDegC, high = %d deciDegC\n",
|
|
chip->bat_is_warm, chip->bat_is_cool,
|
|
chip->adc_param.low_temp, chip->adc_param.high_temp);
|
|
|
|
if (qpnp_adc_tm_channel_measure(chip->adc_tm_dev, &chip->adc_param))
|
|
pr_err("request ADC error\n");
|
|
}
|
|
|
|
#define IBAT_TERM_EN_MASK BIT(3)
|
|
static int qpnp_lbc_chg_init(struct qpnp_lbc_chip *chip)
|
|
{
|
|
int rc;
|
|
u8 reg_val;
|
|
|
|
qpnp_lbc_vbatweak_set(chip, chip->cfg_batt_weak_voltage_uv);
|
|
rc = qpnp_lbc_vinmin_set(chip, chip->cfg_min_voltage_mv);
|
|
if (rc) {
|
|
pr_err("Failed to set vin_min rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
rc = qpnp_lbc_vddsafe_set(chip, chip->cfg_safe_voltage_mv);
|
|
if (rc) {
|
|
pr_err("Failed to set vdd_safe rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
rc = qpnp_lbc_vddmax_set(chip, chip->cfg_max_voltage_mv);
|
|
if (rc) {
|
|
pr_err("Failed to set vdd_safe rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
rc = qpnp_lbc_ibatsafe_set(chip, chip->cfg_safe_current);
|
|
if (rc) {
|
|
pr_err("Failed to set ibat_safe rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
if (of_find_property(chip->spmi->dev.of_node, "qcom,tchg-mins", NULL)) {
|
|
rc = qpnp_lbc_tchg_max_set(chip, chip->cfg_tchg_mins);
|
|
if (rc) {
|
|
pr_err("Failed to set tchg_mins rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Override VBAT_DET comparator to enable charging
|
|
* irrespective of VBAT above VBAT_DET.
|
|
*/
|
|
rc = qpnp_lbc_vbatdet_override(chip, OVERRIDE_0);
|
|
if (rc) {
|
|
pr_err("Failed to override comp rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
* Disable iterm comparator of linear charger to disable charger
|
|
* detecting end of charge condition based on DT configuration
|
|
* and float charge configuration.
|
|
*/
|
|
if (!chip->cfg_charger_detect_eoc || chip->cfg_float_charge) {
|
|
rc = qpnp_lbc_masked_write(chip,
|
|
chip->chgr_base + CHG_IBATTERM_EN_REG,
|
|
IBAT_TERM_EN_MASK, 0);
|
|
if (rc) {
|
|
pr_err("Failed to disable EOC comp rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
/* Disable charger watchdog */
|
|
reg_val = 0;
|
|
rc = qpnp_lbc_write(chip, chip->chgr_base + CHG_WDOG_EN_REG,
|
|
®_val, 1);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int qpnp_lbc_bat_if_init(struct qpnp_lbc_chip *chip)
|
|
{
|
|
u8 reg_val;
|
|
int rc;
|
|
|
|
/* Select battery presence detection */
|
|
switch (chip->cfg_bpd_detection) {
|
|
case BPD_TYPE_BAT_THM:
|
|
reg_val = BATT_THM_EN;
|
|
break;
|
|
case BPD_TYPE_BAT_ID:
|
|
reg_val = BATT_ID_EN;
|
|
break;
|
|
case BPD_TYPE_BAT_THM_BAT_ID:
|
|
reg_val = BATT_THM_EN | BATT_ID_EN;
|
|
break;
|
|
default:
|
|
reg_val = BATT_THM_EN;
|
|
break;
|
|
}
|
|
|
|
rc = qpnp_lbc_masked_write(chip,
|
|
chip->bat_if_base + BAT_IF_BPD_CTRL_REG,
|
|
BATT_BPD_CTRL_SEL_MASK, reg_val);
|
|
if (rc) {
|
|
pr_err("Failed to choose BPD rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
/* Force on VREF_BAT_THM */
|
|
reg_val = VREF_BATT_THERM_FORCE_ON;
|
|
rc = qpnp_lbc_write(chip,
|
|
chip->bat_if_base + BAT_IF_VREF_BAT_THM_CTRL_REG,
|
|
®_val, 1);
|
|
if (rc) {
|
|
pr_err("Failed to force on VREF_BAT_THM rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int qpnp_lbc_usb_path_init(struct qpnp_lbc_chip *chip)
|
|
{
|
|
int rc;
|
|
u8 reg_val;
|
|
|
|
if (qpnp_lbc_is_usb_chg_plugged_in(chip)) {
|
|
reg_val = 0;
|
|
rc = qpnp_lbc_write(chip,
|
|
chip->usb_chgpth_base + CHG_USB_ENUM_T_STOP_REG,
|
|
®_val, 1);
|
|
if (rc) {
|
|
pr_err("Failed to write enum stop rc=%d\n", rc);
|
|
return -ENXIO;
|
|
}
|
|
}
|
|
|
|
if (chip->cfg_charging_disabled) {
|
|
rc = qpnp_lbc_charger_enable(chip, USER, 0);
|
|
if (rc)
|
|
pr_err("Failed to disable charging rc=%d\n", rc);
|
|
|
|
/*
|
|
* Disable follow-on-reset if charging is explictly disabled,
|
|
* this forces the charging to be disabled across reset.
|
|
* Note: Explicitly disabling charging is only a debug/test
|
|
* configuration
|
|
*/
|
|
reg_val = 0x0;
|
|
rc = __qpnp_lbc_secure_write(chip->spmi, chip->chgr_base,
|
|
CHG_PERPH_RESET_CTRL3_REG, ®_val, 1);
|
|
if (rc)
|
|
pr_err("Failed to configure PERPH_CTRL3 rc=%d\n", rc);
|
|
else
|
|
pr_debug("Charger is not following PMIC reset\n");
|
|
} else {
|
|
/*
|
|
* Enable charging explictly,
|
|
* because not sure the default behavior.
|
|
*/
|
|
reg_val = CHG_ENABLE;
|
|
rc = qpnp_lbc_masked_write(chip, chip->chgr_base + CHG_CTRL_REG,
|
|
CHG_EN_MASK, reg_val);
|
|
if (rc)
|
|
pr_err("Failed to enable charger rc=%d\n", rc);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
#define LBC_MISC_DIG_VERSION_1 0x01
|
|
static int qpnp_lbc_misc_init(struct qpnp_lbc_chip *chip)
|
|
{
|
|
int rc;
|
|
u8 reg_val, reg_val1, trim_center;
|
|
|
|
/* Check if this LBC MISC version supports VDD trimming */
|
|
rc = qpnp_lbc_read(chip, chip->misc_base + MISC_REV2_REG,
|
|
®_val, 1);
|
|
if (rc) {
|
|
pr_err("Failed to read VDD_EA TRIM3 reg rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
if (reg_val >= LBC_MISC_DIG_VERSION_1) {
|
|
chip->supported_feature_flag |= VDD_TRIM_SUPPORTED;
|
|
/* Read initial VDD trim value */
|
|
rc = qpnp_lbc_read(chip, chip->misc_base + MISC_TRIM3_REG,
|
|
®_val, 1);
|
|
if (rc) {
|
|
pr_err("Failed to read VDD_EA TRIM3 reg rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
rc = qpnp_lbc_read(chip, chip->misc_base + MISC_TRIM4_REG,
|
|
®_val1, 1);
|
|
if (rc) {
|
|
pr_err("Failed to read VDD_EA TRIM3 reg rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
trim_center = ((reg_val & MISC_TRIM3_VDD_MASK)
|
|
>> VDD_TRIM3_SHIFT)
|
|
| ((reg_val1 & MISC_TRIM4_VDD_MASK)
|
|
>> VDD_TRIM4_SHIFT);
|
|
chip->init_trim_uv = qpnp_lbc_get_trim_voltage(trim_center);
|
|
chip->delta_vddmax_uv = chip->init_trim_uv;
|
|
pr_debug("Initial trim center %x trim_uv %d\n",
|
|
trim_center, chip->init_trim_uv);
|
|
}
|
|
|
|
pr_debug("Setting BOOT_DONE\n");
|
|
reg_val = MISC_BOOT_DONE;
|
|
rc = qpnp_lbc_write(chip, chip->misc_base + MISC_BOOT_DONE_REG,
|
|
®_val, 1);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int show_lbc_config(struct seq_file *m, void *data)
|
|
{
|
|
struct qpnp_lbc_chip *chip = m->private;
|
|
|
|
seq_printf(m, "cfg_charging_disabled\t=\t%d\n"
|
|
"cfg_btc_disabled\t=\t%d\n"
|
|
"cfg_use_fake_battery\t=\t%d\n"
|
|
"cfg_use_external_charger\t=\t%d\n"
|
|
"cfg_chgr_led_support\t=\t%d\n"
|
|
"cfg_warm_bat_chg_ma\t=\t%d\n"
|
|
"cfg_cool_bat_chg_ma\t=\t%d\n"
|
|
"cfg_safe_voltage_mv\t=\t%d\n"
|
|
"cfg_max_voltage_mv\t=\t%d\n"
|
|
"cfg_min_voltage_mv\t=\t%d\n"
|
|
"cfg_charger_detect_eoc\t=\t%d\n"
|
|
"cfg_disable_vbatdet_based_recharge\t=\t%d\n"
|
|
"cfg_collapsible_chgr_support\t=\t%d\n"
|
|
"cfg_batt_weak_voltage_uv\t=\t%d\n"
|
|
"cfg_warm_bat_mv\t=\t%d\n"
|
|
"cfg_cool_bat_mv\t=\t%d\n"
|
|
"cfg_hot_batt_p\t=\t%d\n"
|
|
"cfg_cold_batt_p\t=\t%d\n"
|
|
"cfg_thermal_levels\t=\t%d\n"
|
|
"cfg_safe_current\t=\t%d\n"
|
|
"cfg_tchg_mins\t=\t%d\n"
|
|
"cfg_bpd_detection\t=\t%d\n"
|
|
"cfg_warm_bat_decidegc\t=\t%d\n"
|
|
"cfg_cool_bat_decidegc\t=\t%d\n"
|
|
"cfg_soc_resume_limit\t=\t%d\n"
|
|
"cfg_float_charge\t=\t%d\n",
|
|
chip->cfg_charging_disabled,
|
|
chip->cfg_btc_disabled,
|
|
chip->cfg_use_fake_battery,
|
|
chip->cfg_use_external_charger,
|
|
chip->cfg_chgr_led_support,
|
|
chip->cfg_warm_bat_chg_ma,
|
|
chip->cfg_cool_bat_chg_ma,
|
|
chip->cfg_safe_voltage_mv,
|
|
chip->cfg_max_voltage_mv,
|
|
chip->cfg_min_voltage_mv,
|
|
chip->cfg_charger_detect_eoc,
|
|
chip->cfg_disable_vbatdet_based_recharge,
|
|
chip->cfg_collapsible_chgr_support,
|
|
chip->cfg_batt_weak_voltage_uv,
|
|
chip->cfg_warm_bat_mv,
|
|
chip->cfg_cool_bat_mv,
|
|
chip->cfg_hot_batt_p,
|
|
chip->cfg_cold_batt_p,
|
|
chip->cfg_thermal_levels,
|
|
chip->cfg_safe_current,
|
|
chip->cfg_tchg_mins,
|
|
chip->cfg_bpd_detection,
|
|
chip->cfg_warm_bat_decidegc,
|
|
chip->cfg_cool_bat_decidegc,
|
|
chip->cfg_soc_resume_limit,
|
|
chip->cfg_float_charge);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int qpnp_lbc_config_open(struct inode *inode, struct file *file)
|
|
{
|
|
struct qpnp_lbc_chip *chip = inode->i_private;
|
|
|
|
return single_open(file, show_lbc_config, chip);
|
|
}
|
|
|
|
static const struct file_operations qpnp_lbc_config_debugfs_ops = {
|
|
.owner = THIS_MODULE,
|
|
.open = qpnp_lbc_config_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
|
|
#define OF_PROP_READ(chip, prop, qpnp_dt_property, retval, optional) \
|
|
do { \
|
|
if (retval) \
|
|
break; \
|
|
\
|
|
retval = of_property_read_u32(chip->spmi->dev.of_node, \
|
|
"qcom," qpnp_dt_property, \
|
|
&chip->prop); \
|
|
\
|
|
if ((retval == -EINVAL) && optional) \
|
|
retval = 0; \
|
|
else if (retval) \
|
|
pr_err("Error reading " #qpnp_dt_property \
|
|
" property rc = %d\n", rc); \
|
|
} while (0)
|
|
|
|
static int qpnp_charger_read_dt_props(struct qpnp_lbc_chip *chip)
|
|
{
|
|
int rc = 0;
|
|
const char *bpd;
|
|
|
|
OF_PROP_READ(chip, cfg_max_voltage_mv, "vddmax-mv", rc, 0);
|
|
OF_PROP_READ(chip, cfg_safe_voltage_mv, "vddsafe-mv", rc, 0);
|
|
OF_PROP_READ(chip, cfg_min_voltage_mv, "vinmin-mv", rc, 0);
|
|
OF_PROP_READ(chip, cfg_safe_current, "ibatsafe-ma", rc, 0);
|
|
if (rc)
|
|
pr_err("Error reading required property rc=%d\n", rc);
|
|
|
|
OF_PROP_READ(chip, cfg_tchg_mins, "tchg-mins", rc, 1);
|
|
OF_PROP_READ(chip, cfg_warm_bat_decidegc, "warm-bat-decidegc", rc, 1);
|
|
OF_PROP_READ(chip, cfg_cool_bat_decidegc, "cool-bat-decidegc", rc, 1);
|
|
OF_PROP_READ(chip, cfg_hot_batt_p, "batt-hot-percentage", rc, 1);
|
|
OF_PROP_READ(chip, cfg_cold_batt_p, "batt-cold-percentage", rc, 1);
|
|
OF_PROP_READ(chip, cfg_batt_weak_voltage_uv, "vbatweak-uv", rc, 1);
|
|
OF_PROP_READ(chip, cfg_soc_resume_limit, "resume-soc", rc, 1);
|
|
if (rc) {
|
|
pr_err("Error reading optional property rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
rc = of_property_read_string(chip->spmi->dev.of_node,
|
|
"qcom,bpd-detection", &bpd);
|
|
if (rc) {
|
|
|
|
chip->cfg_bpd_detection = BPD_TYPE_BAT_THM;
|
|
rc = 0;
|
|
} else {
|
|
chip->cfg_bpd_detection = get_bpd(bpd);
|
|
if (chip->cfg_bpd_detection < 0) {
|
|
pr_err("Failed to determine bpd schema rc=%d\n", rc);
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Look up JEITA compliance parameters if cool and warm temp
|
|
* provided
|
|
*/
|
|
if (chip->cfg_cool_bat_decidegc || chip->cfg_warm_bat_decidegc) {
|
|
chip->adc_tm_dev = qpnp_get_adc_tm(chip->dev, "chg");
|
|
if (IS_ERR(chip->adc_tm_dev)) {
|
|
rc = PTR_ERR(chip->adc_tm_dev);
|
|
if (rc != -EPROBE_DEFER)
|
|
pr_err("Failed to get adc-tm rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
OF_PROP_READ(chip, cfg_warm_bat_chg_ma, "ibatmax-warm-ma",
|
|
rc, 1);
|
|
OF_PROP_READ(chip, cfg_cool_bat_chg_ma, "ibatmax-cool-ma",
|
|
rc, 1);
|
|
OF_PROP_READ(chip, cfg_warm_bat_mv, "warm-bat-mv", rc, 1);
|
|
OF_PROP_READ(chip, cfg_cool_bat_mv, "cool-bat-mv", rc, 1);
|
|
if (rc) {
|
|
pr_err("Error reading battery temp prop rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
/* Get the btc-disabled property */
|
|
chip->cfg_btc_disabled = of_property_read_bool(
|
|
chip->spmi->dev.of_node, "qcom,btc-disabled");
|
|
|
|
/* Get the charging-disabled property */
|
|
chip->cfg_charging_disabled =
|
|
of_property_read_bool(chip->spmi->dev.of_node,
|
|
"qcom,charging-disabled");
|
|
|
|
/* Get the fake-batt-values property */
|
|
chip->cfg_use_fake_battery =
|
|
of_property_read_bool(chip->spmi->dev.of_node,
|
|
"qcom,use-default-batt-values");
|
|
|
|
/* Get the float charging property */
|
|
chip->cfg_float_charge =
|
|
of_property_read_bool(chip->spmi->dev.of_node,
|
|
"qcom,float-charge");
|
|
|
|
/* Get the charger EOC detect property */
|
|
chip->cfg_charger_detect_eoc =
|
|
of_property_read_bool(chip->spmi->dev.of_node,
|
|
"qcom,charger-detect-eoc");
|
|
|
|
/* Get the vbatdet disable property */
|
|
chip->cfg_disable_vbatdet_based_recharge =
|
|
of_property_read_bool(chip->spmi->dev.of_node,
|
|
"qcom,disable-vbatdet-based-recharge");
|
|
|
|
/* Get the charger led support property */
|
|
chip->cfg_chgr_led_support =
|
|
of_property_read_bool(chip->spmi->dev.of_node,
|
|
"qcom,chgr-led-support");
|
|
|
|
/* Get the collapsible charger support property */
|
|
chip->cfg_collapsible_chgr_support =
|
|
of_property_read_bool(chip->spmi->dev.of_node,
|
|
"qcom,collapsible-chgr-support");
|
|
|
|
/* Disable charging when faking battery values */
|
|
if (chip->cfg_use_fake_battery)
|
|
chip->cfg_charging_disabled = true;
|
|
|
|
chip->cfg_use_external_charger = of_property_read_bool(
|
|
chip->spmi->dev.of_node, "qcom,use-external-charger");
|
|
|
|
if (of_find_property(chip->spmi->dev.of_node,
|
|
"qcom,thermal-mitigation",
|
|
&chip->cfg_thermal_levels)) {
|
|
chip->thermal_mitigation = devm_kzalloc(chip->dev,
|
|
chip->cfg_thermal_levels,
|
|
GFP_KERNEL);
|
|
|
|
if (chip->thermal_mitigation == NULL) {
|
|
pr_err("thermal mitigation kzalloc() failed.\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
chip->cfg_thermal_levels /= sizeof(int);
|
|
rc = of_property_read_u32_array(chip->spmi->dev.of_node,
|
|
"qcom,thermal-mitigation",
|
|
chip->thermal_mitigation,
|
|
chip->cfg_thermal_levels);
|
|
if (rc) {
|
|
pr_err("Failed to read threm limits rc = %d\n", rc);
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
pr_debug("vddmax-mv=%d, vddsafe-mv=%d, vinmin-mv=%d, ibatsafe-ma=$=%d\n",
|
|
chip->cfg_max_voltage_mv,
|
|
chip->cfg_safe_voltage_mv,
|
|
chip->cfg_min_voltage_mv,
|
|
chip->cfg_safe_current);
|
|
pr_debug("warm-bat-decidegc=%d, cool-bat-decidegc=%d, batt-hot-percentage=%d, batt-cold-percentage=%d\n",
|
|
chip->cfg_warm_bat_decidegc,
|
|
chip->cfg_cool_bat_decidegc,
|
|
chip->cfg_hot_batt_p,
|
|
chip->cfg_cold_batt_p);
|
|
pr_debug("tchg-mins=%d, vbatweak-uv=%d, resume-soc=%d\n",
|
|
chip->cfg_tchg_mins,
|
|
chip->cfg_batt_weak_voltage_uv,
|
|
chip->cfg_soc_resume_limit);
|
|
pr_debug("bpd-detection=%d, ibatmax-warm-ma=%d, ibatmax-cool-ma=%d, warm-bat-mv=%d, cool-bat-mv=%d\n",
|
|
chip->cfg_bpd_detection,
|
|
chip->cfg_warm_bat_chg_ma,
|
|
chip->cfg_cool_bat_chg_ma,
|
|
chip->cfg_warm_bat_mv,
|
|
chip->cfg_cool_bat_mv);
|
|
pr_debug("btc-disabled=%d, charging-disabled=%d, use-default-batt-values=%d, float-charge=%d\n",
|
|
chip->cfg_btc_disabled,
|
|
chip->cfg_charging_disabled,
|
|
chip->cfg_use_fake_battery,
|
|
chip->cfg_float_charge);
|
|
pr_debug("charger-detect-eoc=%d, disable-vbatdet-based-recharge=%d, chgr-led-support=%d\n",
|
|
chip->cfg_charger_detect_eoc,
|
|
chip->cfg_disable_vbatdet_based_recharge,
|
|
chip->cfg_chgr_led_support);
|
|
pr_debug("collapsible-chg-support=%d, use-external-charger=%d, thermal_levels=%d\n",
|
|
chip->cfg_collapsible_chgr_support,
|
|
chip->cfg_use_external_charger,
|
|
chip->cfg_thermal_levels);
|
|
return rc;
|
|
}
|
|
|
|
#define CHG_REMOVAL_DETECT_DLY_MS 300
|
|
static irqreturn_t qpnp_lbc_chg_gone_irq_handler(int irq, void *_chip)
|
|
{
|
|
struct qpnp_lbc_chip *chip = _chip;
|
|
int chg_gone;
|
|
|
|
if (chip->cfg_collapsible_chgr_support) {
|
|
chg_gone = qpnp_lbc_is_chg_gone(chip);
|
|
pr_debug("chg-gone triggered, rt_sts: %d\n", chg_gone);
|
|
if (chg_gone) {
|
|
/*
|
|
* Disable charger to prevent fastchg irq storming
|
|
* if a non-collapsible charger is being used.
|
|
*/
|
|
pr_debug("disable charging for non-collapsbile charger\n");
|
|
qpnp_lbc_charger_enable(chip, COLLAPSE, 0);
|
|
qpnp_lbc_disable_irq(chip, &chip->irqs[USBIN_VALID]);
|
|
qpnp_lbc_disable_irq(chip, &chip->irqs[USB_CHG_GONE]);
|
|
qpnp_chg_collapsible_chgr_config(chip, 0);
|
|
/*
|
|
* Check after a delay if the charger is still
|
|
* inserted. It decides if a non-collapsible
|
|
* charger is being used, or charger has been
|
|
* removed.
|
|
*/
|
|
schedule_delayed_work(&chip->collapsible_detection_work,
|
|
msecs_to_jiffies(CHG_REMOVAL_DETECT_DLY_MS));
|
|
}
|
|
}
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static irqreturn_t qpnp_lbc_usbin_valid_irq_handler(int irq, void *_chip)
|
|
{
|
|
struct qpnp_lbc_chip *chip = _chip;
|
|
int usb_present;
|
|
unsigned long flags;
|
|
|
|
usb_present = qpnp_lbc_is_usb_chg_plugged_in(chip);
|
|
pr_debug("usbin-valid triggered: %d\n", usb_present);
|
|
|
|
if (chip->usb_present ^ usb_present) {
|
|
chip->usb_present = usb_present;
|
|
if (!usb_present) {
|
|
qpnp_lbc_charger_enable(chip, CURRENT, 0);
|
|
spin_lock_irqsave(&chip->ibat_change_lock, flags);
|
|
chip->usb_psy_ma = QPNP_CHG_I_MAX_MIN_90;
|
|
qpnp_lbc_set_appropriate_current(chip);
|
|
spin_unlock_irqrestore(&chip->ibat_change_lock,
|
|
flags);
|
|
if (chip->cfg_collapsible_chgr_support)
|
|
chip->non_collapsible_chgr_detected = false;
|
|
|
|
if (chip->supported_feature_flag & VDD_TRIM_SUPPORTED)
|
|
alarm_try_to_cancel(&chip->vddtrim_alarm);
|
|
} else {
|
|
/*
|
|
* Override VBAT_DET comparator to start charging
|
|
* even if VBAT > VBAT_DET.
|
|
*/
|
|
if (!chip->cfg_disable_vbatdet_based_recharge)
|
|
qpnp_lbc_vbatdet_override(chip, OVERRIDE_0);
|
|
|
|
/*
|
|
* If collapsible charger supported, enable chgr_gone
|
|
* irq, and configure for collapsible charger.
|
|
*/
|
|
if (chip->cfg_collapsible_chgr_support &&
|
|
!chip->non_collapsible_chgr_detected) {
|
|
qpnp_lbc_enable_irq(chip,
|
|
&chip->irqs[USB_CHG_GONE]);
|
|
qpnp_chg_collapsible_chgr_config(chip, 1);
|
|
}
|
|
/*
|
|
* Enable SOC based charging to make sure
|
|
* charging gets enabled on USB insertion
|
|
* irrespective of battery SOC above resume_soc.
|
|
*/
|
|
qpnp_lbc_charger_enable(chip, SOC, 1);
|
|
}
|
|
|
|
pr_debug("Updating usb_psy PRESENT property\n");
|
|
power_supply_set_present(chip->usb_psy, chip->usb_present);
|
|
}
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static int qpnp_lbc_is_batt_temp_ok(struct qpnp_lbc_chip *chip)
|
|
{
|
|
u8 reg_val;
|
|
int rc;
|
|
|
|
rc = qpnp_lbc_read(chip, chip->bat_if_base + INT_RT_STS_REG,
|
|
®_val, 1);
|
|
if (rc) {
|
|
pr_err("spmi read failed: addr=%03X, rc=%d\n",
|
|
chip->bat_if_base + INT_RT_STS_REG, rc);
|
|
return rc;
|
|
}
|
|
|
|
return (reg_val & BAT_TEMP_OK_IRQ) ? 1 : 0;
|
|
}
|
|
|
|
static irqreturn_t qpnp_lbc_batt_temp_irq_handler(int irq, void *_chip)
|
|
{
|
|
struct qpnp_lbc_chip *chip = _chip;
|
|
int batt_temp_good;
|
|
|
|
batt_temp_good = qpnp_lbc_is_batt_temp_ok(chip);
|
|
pr_debug("batt-temp triggered: %d\n", batt_temp_good);
|
|
|
|
pr_debug("power supply changed batt_psy\n");
|
|
power_supply_changed(&chip->batt_psy);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static irqreturn_t qpnp_lbc_batt_pres_irq_handler(int irq, void *_chip)
|
|
{
|
|
struct qpnp_lbc_chip *chip = _chip;
|
|
int batt_present;
|
|
|
|
batt_present = qpnp_lbc_is_batt_present(chip);
|
|
pr_debug("batt-pres triggered: %d\n", batt_present);
|
|
|
|
if (chip->batt_present ^ batt_present) {
|
|
chip->batt_present = batt_present;
|
|
pr_debug("power supply changed batt_psy\n");
|
|
power_supply_changed(&chip->batt_psy);
|
|
|
|
if ((chip->cfg_cool_bat_decidegc
|
|
|| chip->cfg_warm_bat_decidegc)
|
|
&& batt_present) {
|
|
pr_debug("enabling vadc notifications\n");
|
|
if (qpnp_adc_tm_channel_measure(chip->adc_tm_dev,
|
|
&chip->adc_param))
|
|
pr_err("request ADC error\n");
|
|
} else if ((chip->cfg_cool_bat_decidegc
|
|
|| chip->cfg_warm_bat_decidegc)
|
|
&& !batt_present) {
|
|
qpnp_adc_tm_disable_chan_meas(chip->adc_tm_dev,
|
|
&chip->adc_param);
|
|
pr_debug("disabling vadc notifications\n");
|
|
}
|
|
}
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static irqreturn_t qpnp_lbc_chg_failed_irq_handler(int irq, void *_chip)
|
|
{
|
|
struct qpnp_lbc_chip *chip = _chip;
|
|
int rc;
|
|
u8 reg_val = CHG_FAILED_BIT;
|
|
|
|
pr_debug("chg_failed triggered count=%u\n", ++chip->chg_failed_count);
|
|
rc = qpnp_lbc_write(chip, chip->chgr_base + CHG_FAILED_REG,
|
|
®_val, 1);
|
|
if (rc)
|
|
pr_err("Failed to write chg_fail clear bit rc=%d\n", rc);
|
|
|
|
if (chip->bat_if_base) {
|
|
pr_debug("power supply changed batt_psy\n");
|
|
power_supply_changed(&chip->batt_psy);
|
|
}
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static int qpnp_lbc_is_fastchg_on(struct qpnp_lbc_chip *chip)
|
|
{
|
|
u8 reg_val;
|
|
int rc;
|
|
|
|
rc = qpnp_lbc_read(chip, chip->chgr_base + INT_RT_STS_REG,
|
|
®_val, 1);
|
|
if (rc) {
|
|
pr_err("Failed to read interrupt status rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
pr_debug("charger status %x\n", reg_val);
|
|
return (reg_val & FAST_CHG_ON_IRQ) ? 1 : 0;
|
|
}
|
|
|
|
#define TRIM_PERIOD_NS (50LL * NSEC_PER_SEC)
|
|
static irqreturn_t qpnp_lbc_fastchg_irq_handler(int irq, void *_chip)
|
|
{
|
|
ktime_t kt;
|
|
struct qpnp_lbc_chip *chip = _chip;
|
|
bool fastchg_on = false;
|
|
|
|
fastchg_on = qpnp_lbc_is_fastchg_on(chip);
|
|
|
|
pr_debug("FAST_CHG IRQ triggered, fastchg_on: %d\n", fastchg_on);
|
|
|
|
if (chip->fastchg_on ^ fastchg_on) {
|
|
chip->fastchg_on = fastchg_on;
|
|
if (fastchg_on) {
|
|
mutex_lock(&chip->chg_enable_lock);
|
|
chip->chg_done = false;
|
|
mutex_unlock(&chip->chg_enable_lock);
|
|
/*
|
|
* Start alarm timer to periodically calculate
|
|
* and update VDD_MAX trim value.
|
|
*/
|
|
if (chip->supported_feature_flag &
|
|
VDD_TRIM_SUPPORTED) {
|
|
kt = ns_to_ktime(TRIM_PERIOD_NS);
|
|
alarm_start_relative(&chip->vddtrim_alarm,
|
|
kt);
|
|
}
|
|
}
|
|
|
|
if (chip->bat_if_base) {
|
|
pr_debug("power supply changed batt_psy\n");
|
|
power_supply_changed(&chip->batt_psy);
|
|
}
|
|
}
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static irqreturn_t qpnp_lbc_chg_done_irq_handler(int irq, void *_chip)
|
|
{
|
|
struct qpnp_lbc_chip *chip = _chip;
|
|
|
|
pr_debug("charging done triggered\n");
|
|
|
|
chip->chg_done = true;
|
|
pr_debug("power supply changed batt_psy\n");
|
|
power_supply_changed(&chip->batt_psy);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static irqreturn_t qpnp_lbc_vbatdet_lo_irq_handler(int irq, void *_chip)
|
|
{
|
|
struct qpnp_lbc_chip *chip = _chip;
|
|
int rc;
|
|
|
|
pr_debug("vbatdet-lo triggered\n");
|
|
|
|
/*
|
|
* Disable vbatdet irq to prevent interrupt storm when VBAT is
|
|
* close to VBAT_DET.
|
|
*/
|
|
qpnp_lbc_disable_irq(chip, &chip->irqs[CHG_VBAT_DET_LO]);
|
|
|
|
/*
|
|
* Override VBAT_DET comparator to 0 to fix comparator toggling
|
|
* near VBAT_DET threshold.
|
|
*/
|
|
qpnp_lbc_vbatdet_override(chip, OVERRIDE_0);
|
|
|
|
/*
|
|
* Battery has fallen below the vbatdet threshold and it is
|
|
* time to resume charging.
|
|
*/
|
|
rc = qpnp_lbc_charger_enable(chip, SOC, 1);
|
|
if (rc)
|
|
pr_err("Failed to enable charging\n");
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static int qpnp_lbc_is_overtemp(struct qpnp_lbc_chip *chip)
|
|
{
|
|
u8 reg_val;
|
|
int rc;
|
|
|
|
rc = qpnp_lbc_read(chip, chip->usb_chgpth_base + INT_RT_STS_REG,
|
|
®_val, 1);
|
|
if (rc) {
|
|
pr_err("Failed to read interrupt status rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
pr_debug("OVERTEMP rt status %x\n", reg_val);
|
|
return (reg_val & OVERTEMP_ON_IRQ) ? 1 : 0;
|
|
}
|
|
|
|
static irqreturn_t qpnp_lbc_usb_overtemp_irq_handler(int irq, void *_chip)
|
|
{
|
|
struct qpnp_lbc_chip *chip = _chip;
|
|
int overtemp = qpnp_lbc_is_overtemp(chip);
|
|
|
|
pr_warn_ratelimited("charger %s temperature limit !!!\n",
|
|
overtemp ? "exceeds" : "within");
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static int qpnp_disable_lbc_charger(struct qpnp_lbc_chip *chip)
|
|
{
|
|
int rc;
|
|
u8 reg;
|
|
|
|
reg = CHG_FORCE_BATT_ON;
|
|
rc = qpnp_lbc_masked_write(chip, chip->chgr_base + CHG_CTRL_REG,
|
|
CHG_EN_MASK, reg);
|
|
/* disable BTC */
|
|
rc |= qpnp_lbc_masked_write(chip, chip->bat_if_base + BAT_IF_BTC_CTRL,
|
|
BTC_COMP_EN_MASK, 0);
|
|
/* Enable BID and disable THM based BPD */
|
|
reg = BATT_ID_EN | BATT_BPD_OFFMODE_EN;
|
|
rc |= qpnp_lbc_write(chip, chip->bat_if_base + BAT_IF_BPD_CTRL_REG,
|
|
®, 1);
|
|
return rc;
|
|
}
|
|
|
|
#define SPMI_REQUEST_IRQ(chip, idx, rc, irq_name, threaded, flags, wake)\
|
|
do { \
|
|
if (rc) \
|
|
break; \
|
|
if (chip->irqs[idx].irq) { \
|
|
if (threaded) \
|
|
rc = devm_request_threaded_irq(chip->dev, \
|
|
chip->irqs[idx].irq, NULL, \
|
|
qpnp_lbc_##irq_name##_irq_handler, \
|
|
flags, #irq_name, chip); \
|
|
else \
|
|
rc = devm_request_irq(chip->dev, \
|
|
chip->irqs[idx].irq, \
|
|
qpnp_lbc_##irq_name##_irq_handler, \
|
|
flags, #irq_name, chip); \
|
|
if (rc < 0) { \
|
|
pr_err("Unable to request " #irq_name " %d\n", \
|
|
rc); \
|
|
} else { \
|
|
rc = 0; \
|
|
if (wake) { \
|
|
enable_irq_wake(chip->irqs[idx].irq); \
|
|
chip->irqs[idx].is_wake = true; \
|
|
} \
|
|
} \
|
|
} \
|
|
} while (0)
|
|
|
|
#define SPMI_GET_IRQ_RESOURCE(chip, rc, resource, idx, name) \
|
|
do { \
|
|
if (rc) \
|
|
break; \
|
|
\
|
|
rc = spmi_get_irq_byname(chip->spmi, resource, #name); \
|
|
if (rc < 0) { \
|
|
pr_err("Unable to get irq resource " #name "%d\n", rc); \
|
|
} else { \
|
|
chip->irqs[idx].irq = rc; \
|
|
rc = 0; \
|
|
} \
|
|
} while (0)
|
|
|
|
static int qpnp_lbc_request_irqs(struct qpnp_lbc_chip *chip)
|
|
{
|
|
int rc = 0;
|
|
|
|
SPMI_REQUEST_IRQ(chip, CHG_FAILED, rc, chg_failed, 0,
|
|
IRQF_TRIGGER_RISING, 1);
|
|
|
|
SPMI_REQUEST_IRQ(chip, CHG_FAST_CHG, rc, fastchg, 1,
|
|
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING
|
|
| IRQF_ONESHOT, 1);
|
|
|
|
SPMI_REQUEST_IRQ(chip, CHG_DONE, rc, chg_done, 0,
|
|
IRQF_TRIGGER_RISING, 0);
|
|
|
|
SPMI_REQUEST_IRQ(chip, CHG_VBAT_DET_LO, rc, vbatdet_lo, 0,
|
|
IRQF_TRIGGER_FALLING, 1);
|
|
|
|
SPMI_REQUEST_IRQ(chip, BATT_PRES, rc, batt_pres, 1,
|
|
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING
|
|
| IRQF_ONESHOT, 1);
|
|
|
|
SPMI_REQUEST_IRQ(chip, BATT_TEMPOK, rc, batt_temp, 0,
|
|
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, 1);
|
|
|
|
SPMI_REQUEST_IRQ(chip, USBIN_VALID, rc, usbin_valid, 0,
|
|
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, 1);
|
|
|
|
SPMI_REQUEST_IRQ(chip, USB_CHG_GONE, rc, chg_gone, 0,
|
|
IRQF_TRIGGER_RISING, 1);
|
|
|
|
SPMI_REQUEST_IRQ(chip, USB_OVER_TEMP, rc, usb_overtemp, 0,
|
|
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, 0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int qpnp_lbc_get_irqs(struct qpnp_lbc_chip *chip, u8 subtype,
|
|
struct spmi_resource *spmi_resource)
|
|
{
|
|
int rc = 0;
|
|
|
|
switch (subtype) {
|
|
case LBC_CHGR_SUBTYPE:
|
|
SPMI_GET_IRQ_RESOURCE(chip, rc, spmi_resource,
|
|
CHG_FAST_CHG, fast-chg-on);
|
|
SPMI_GET_IRQ_RESOURCE(chip, rc, spmi_resource,
|
|
CHG_FAILED, chg-failed);
|
|
if (!chip->cfg_disable_vbatdet_based_recharge)
|
|
SPMI_GET_IRQ_RESOURCE(chip, rc, spmi_resource,
|
|
CHG_VBAT_DET_LO, vbat-det-lo);
|
|
if (chip->cfg_charger_detect_eoc)
|
|
SPMI_GET_IRQ_RESOURCE(chip, rc, spmi_resource,
|
|
CHG_DONE, chg-done);
|
|
break;
|
|
case LBC_BAT_IF_SUBTYPE:
|
|
SPMI_GET_IRQ_RESOURCE(chip, rc, spmi_resource,
|
|
BATT_PRES, batt-pres);
|
|
SPMI_GET_IRQ_RESOURCE(chip, rc, spmi_resource,
|
|
BATT_TEMPOK, bat-temp-ok);
|
|
break;
|
|
case LBC_USB_PTH_SUBTYPE:
|
|
SPMI_GET_IRQ_RESOURCE(chip, rc, spmi_resource,
|
|
USBIN_VALID, usbin-valid);
|
|
SPMI_GET_IRQ_RESOURCE(chip, rc, spmi_resource,
|
|
USB_OVER_TEMP, usb-over-temp);
|
|
SPMI_GET_IRQ_RESOURCE(chip, rc, spmi_resource,
|
|
USB_CHG_GONE, chg-gone);
|
|
break;
|
|
};
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Get/Set initial state of charger */
|
|
static void determine_initial_status(struct qpnp_lbc_chip *chip)
|
|
{
|
|
chip->usb_present = qpnp_lbc_is_usb_chg_plugged_in(chip);
|
|
power_supply_set_present(chip->usb_psy, chip->usb_present);
|
|
/*
|
|
* Set USB psy online to avoid userspace from shutting down if battery
|
|
* capacity is at zero and no chargers online.
|
|
*/
|
|
if (chip->usb_present) {
|
|
if (chip->cfg_collapsible_chgr_support &&
|
|
!chip->non_collapsible_chgr_detected) {
|
|
qpnp_lbc_enable_irq(chip,
|
|
&chip->irqs[USB_CHG_GONE]);
|
|
qpnp_chg_collapsible_chgr_config(chip, 1);
|
|
}
|
|
power_supply_set_online(chip->usb_psy, 1);
|
|
}
|
|
}
|
|
|
|
static void qpnp_lbc_collapsible_detection_work(struct work_struct *work)
|
|
{
|
|
struct delayed_work *dwork = to_delayed_work(work);
|
|
struct qpnp_lbc_chip *chip = container_of(dwork,
|
|
struct qpnp_lbc_chip,
|
|
collapsible_detection_work);
|
|
|
|
if (qpnp_lbc_is_usb_chg_plugged_in(chip)) {
|
|
chip->non_collapsible_chgr_detected = true;
|
|
pr_debug("Non-collapsible charger detected\n");
|
|
} else {
|
|
chip->non_collapsible_chgr_detected = false;
|
|
pr_debug("Charger removal detected\n");
|
|
}
|
|
qpnp_lbc_charger_enable(chip, COLLAPSE, 1);
|
|
qpnp_lbc_enable_irq(chip, &chip->irqs[USBIN_VALID]);
|
|
}
|
|
|
|
#define IBAT_TRIM -300
|
|
static void qpnp_lbc_vddtrim_work_fn(struct work_struct *work)
|
|
{
|
|
int rc, vbat_now_uv, ibat_now;
|
|
u8 reg_val;
|
|
ktime_t kt;
|
|
struct qpnp_lbc_chip *chip = container_of(work, struct qpnp_lbc_chip,
|
|
vddtrim_work);
|
|
|
|
vbat_now_uv = get_prop_battery_voltage_now(chip);
|
|
ibat_now = get_prop_current_now(chip) / 1000;
|
|
pr_debug("vbat %d ibat %d capacity %d\n",
|
|
vbat_now_uv, ibat_now, get_prop_capacity(chip));
|
|
|
|
/*
|
|
* Stop trimming under following condition:
|
|
* USB removed
|
|
* Charging Stopped
|
|
*/
|
|
if (!qpnp_lbc_is_fastchg_on(chip) ||
|
|
!qpnp_lbc_is_usb_chg_plugged_in(chip)) {
|
|
pr_debug("stop trim charging stopped\n");
|
|
goto exit;
|
|
} else {
|
|
rc = qpnp_lbc_read(chip, chip->chgr_base + CHG_STATUS_REG,
|
|
®_val, 1);
|
|
if (rc) {
|
|
pr_err("Failed to read chg status rc=%d\n", rc);
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* Update VDD trim voltage only if following conditions are
|
|
* met:
|
|
* If charger is in VDD loop AND
|
|
* If ibat is between 0 ma and -300 ma
|
|
*/
|
|
if ((reg_val & CHG_VDD_LOOP_BIT) &&
|
|
((ibat_now < 0) && (ibat_now > IBAT_TRIM)))
|
|
qpnp_lbc_adjust_vddmax(chip, vbat_now_uv);
|
|
}
|
|
|
|
out:
|
|
kt = ns_to_ktime(TRIM_PERIOD_NS);
|
|
alarm_start_relative(&chip->vddtrim_alarm, kt);
|
|
exit:
|
|
pm_relax(chip->dev);
|
|
}
|
|
|
|
static enum alarmtimer_restart vddtrim_callback(struct alarm *alarm,
|
|
ktime_t now)
|
|
{
|
|
struct qpnp_lbc_chip *chip = container_of(alarm, struct qpnp_lbc_chip,
|
|
vddtrim_alarm);
|
|
|
|
pm_stay_awake(chip->dev);
|
|
schedule_work(&chip->vddtrim_work);
|
|
|
|
return ALARMTIMER_NORESTART;
|
|
}
|
|
|
|
static int qpnp_lbc_parallel_charger_init(struct qpnp_lbc_chip *chip)
|
|
{
|
|
u8 reg_val;
|
|
int rc;
|
|
|
|
rc = qpnp_lbc_vinmin_set(chip, chip->cfg_min_voltage_mv);
|
|
if (rc) {
|
|
pr_err("Failed to set vin_min rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
rc = qpnp_lbc_vddsafe_set(chip, chip->cfg_max_voltage_mv);
|
|
if (rc) {
|
|
pr_err("Failed to set vdd_safe rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
rc = qpnp_lbc_vddmax_set(chip, chip->cfg_max_voltage_mv);
|
|
if (rc) {
|
|
pr_err("Failed to set vdd_max rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
/* set the minimum charging current */
|
|
rc = qpnp_lbc_ibatmax_set(chip, 0);
|
|
if (rc) {
|
|
pr_err("Failed to set IBAT_MAX to 0 rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
/* disable charging */
|
|
rc = qpnp_lbc_charger_enable(chip, PARALLEL, 0);
|
|
if (rc) {
|
|
pr_err("Unable to disable charging rc=%d\n", rc);
|
|
return 0;
|
|
}
|
|
|
|
/* Enable BID and disable THM based BPD */
|
|
reg_val = BATT_ID_EN | BATT_BPD_OFFMODE_EN;
|
|
rc = qpnp_lbc_write(chip, chip->bat_if_base + BAT_IF_BPD_CTRL_REG,
|
|
®_val, 1);
|
|
if (rc)
|
|
pr_err("Failed to override BPD configuration rc=%d\n", rc);
|
|
|
|
/* Disable and override BTC */
|
|
reg_val = 0x2A;
|
|
rc = __qpnp_lbc_secure_write(chip->spmi, chip->bat_if_base,
|
|
BTC_COMP_OVERRIDE_REG, ®_val, 1);
|
|
if (rc)
|
|
pr_err("Failed to disable BTC override rc=%d\n", rc);
|
|
|
|
reg_val = 0;
|
|
rc = qpnp_lbc_write(chip,
|
|
chip->bat_if_base + BAT_IF_BTC_CTRL, ®_val, 1);
|
|
if (rc)
|
|
pr_err("Failed to disable BTC rc=%d\n", rc);
|
|
|
|
/* override VBAT_DET */
|
|
rc = qpnp_lbc_vbatdet_override(chip, OVERRIDE_0);
|
|
if (rc)
|
|
pr_err("Failed to override VBAT_DET rc=%d\n", rc);
|
|
|
|
/* Set BOOT_DONE and ENUM complete */
|
|
reg_val = 0;
|
|
rc = qpnp_lbc_write(chip,
|
|
chip->usb_chgpth_base + CHG_USB_ENUM_T_STOP_REG,
|
|
®_val, 1);
|
|
if (rc)
|
|
pr_err("Failed to stop enum-timer rc=%d\n", rc);
|
|
|
|
reg_val = MISC_BOOT_DONE;
|
|
rc = qpnp_lbc_write(chip, chip->misc_base + MISC_BOOT_DONE_REG,
|
|
®_val, 1);
|
|
if (rc)
|
|
pr_err("Failed to set boot-done rc=%d\n", rc);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int qpnp_lbc_parse_resources(struct qpnp_lbc_chip *chip)
|
|
{
|
|
u8 subtype;
|
|
int rc = 0;
|
|
struct resource *resource;
|
|
struct spmi_resource *spmi_resource;
|
|
struct spmi_device *spmi = chip->spmi;
|
|
|
|
spmi_for_each_container_dev(spmi_resource, spmi) {
|
|
if (!spmi_resource) {
|
|
pr_err("spmi resource absent\n");
|
|
return -ENXIO;
|
|
}
|
|
|
|
resource = spmi_get_resource(spmi, spmi_resource,
|
|
IORESOURCE_MEM, 0);
|
|
if (!(resource && resource->start)) {
|
|
pr_err("node %s IO resource absent!\n",
|
|
spmi->dev.of_node->full_name);
|
|
return -ENXIO;
|
|
}
|
|
|
|
rc = qpnp_lbc_read(chip, resource->start + PERP_SUBTYPE_REG,
|
|
&subtype, 1);
|
|
if (rc) {
|
|
pr_err("Peripheral subtype read failed rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
switch (subtype) {
|
|
case LBC_CHGR_SUBTYPE:
|
|
chip->chgr_base = resource->start;
|
|
rc = qpnp_lbc_get_irqs(chip, subtype, spmi_resource);
|
|
if (rc) {
|
|
pr_err("Failed to get CHGR irqs rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
break;
|
|
case LBC_USB_PTH_SUBTYPE:
|
|
chip->usb_chgpth_base = resource->start;
|
|
rc = qpnp_lbc_get_irqs(chip, subtype, spmi_resource);
|
|
if (rc) {
|
|
pr_err("Failed to get USB_PTH irqs rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
break;
|
|
case LBC_BAT_IF_SUBTYPE:
|
|
chip->bat_if_base = resource->start;
|
|
rc = qpnp_lbc_get_irqs(chip, subtype, spmi_resource);
|
|
if (rc) {
|
|
pr_err("Failed to get BAT_IF irqs rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
break;
|
|
case LBC_MISC_SUBTYPE:
|
|
chip->misc_base = resource->start;
|
|
break;
|
|
default:
|
|
pr_err("Invalid peripheral subtype=0x%x\n", subtype);
|
|
rc = -EINVAL;
|
|
}
|
|
}
|
|
|
|
pr_debug("chgr_base=%x usb_chgpth_base=%x bat_if_base=%x misc_base=%x\n",
|
|
chip->chgr_base, chip->usb_chgpth_base,
|
|
chip->bat_if_base, chip->misc_base);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int qpnp_lbc_parallel_probe(struct spmi_device *spmi)
|
|
{
|
|
int rc = 0;
|
|
struct qpnp_lbc_chip *chip;
|
|
|
|
chip = devm_kzalloc(&spmi->dev, sizeof(struct qpnp_lbc_chip),
|
|
GFP_KERNEL);
|
|
if (!chip) {
|
|
pr_err("memory allocation failed.\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
chip->dev = &spmi->dev;
|
|
chip->spmi = spmi;
|
|
dev_set_drvdata(&spmi->dev, chip);
|
|
device_init_wakeup(&spmi->dev, 1);
|
|
spin_lock_init(&chip->hw_access_lock);
|
|
spin_lock_init(&chip->ibat_change_lock);
|
|
INIT_DELAYED_WORK(&chip->parallel_work, qpnp_lbc_parallel_work);
|
|
|
|
OF_PROP_READ(chip, cfg_max_voltage_mv, "vddmax-mv", rc, 0);
|
|
if (rc)
|
|
return rc;
|
|
OF_PROP_READ(chip, cfg_min_voltage_mv, "vinmin-mv", rc, 0);
|
|
if (rc)
|
|
return rc;
|
|
|
|
rc = qpnp_lbc_parse_resources(chip);
|
|
if (rc) {
|
|
pr_err("Unable to parse LBC(parallel) resources rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
rc = qpnp_lbc_parallel_charger_init(chip);
|
|
if (rc) {
|
|
pr_err("Unable to initialize LBC(parallel) rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
chip->parallel_psy.name = "usb-parallel";
|
|
chip->parallel_psy.type = POWER_SUPPLY_TYPE_USB_PARALLEL;
|
|
chip->parallel_psy.get_property = qpnp_lbc_parallel_get_property;
|
|
chip->parallel_psy.set_property = qpnp_lbc_parallel_set_property;
|
|
chip->parallel_psy.properties = qpnp_lbc_parallel_properties;
|
|
chip->parallel_psy.property_is_writeable
|
|
= qpnp_lbc_parallel_is_writeable;
|
|
chip->parallel_psy.num_properties
|
|
= ARRAY_SIZE(qpnp_lbc_parallel_properties);
|
|
|
|
rc = power_supply_register(chip->dev, &chip->parallel_psy);
|
|
if (rc < 0) {
|
|
pr_err("Unable to register LBC parallel_psy rc = %d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
pr_info("LBC (parallel) registered successfully!\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int qpnp_lbc_main_probe(struct spmi_device *spmi)
|
|
{
|
|
ktime_t kt;
|
|
struct qpnp_lbc_chip *chip;
|
|
struct power_supply *usb_psy;
|
|
int rc = 0;
|
|
|
|
usb_psy = power_supply_get_by_name("usb");
|
|
if (!usb_psy) {
|
|
pr_err("usb supply not found deferring probe\n");
|
|
return -EPROBE_DEFER;
|
|
}
|
|
|
|
chip = devm_kzalloc(&spmi->dev, sizeof(struct qpnp_lbc_chip),
|
|
GFP_KERNEL);
|
|
if (!chip) {
|
|
pr_err("memory allocation failed.\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
chip->usb_psy = usb_psy;
|
|
chip->dev = &spmi->dev;
|
|
chip->spmi = spmi;
|
|
chip->fake_battery_soc = -EINVAL;
|
|
dev_set_drvdata(&spmi->dev, chip);
|
|
device_init_wakeup(&spmi->dev, 1);
|
|
mutex_init(&chip->jeita_configure_lock);
|
|
mutex_init(&chip->chg_enable_lock);
|
|
spin_lock_init(&chip->hw_access_lock);
|
|
spin_lock_init(&chip->ibat_change_lock);
|
|
spin_lock_init(&chip->irq_lock);
|
|
INIT_WORK(&chip->vddtrim_work, qpnp_lbc_vddtrim_work_fn);
|
|
alarm_init(&chip->vddtrim_alarm, ALARM_REALTIME, vddtrim_callback);
|
|
INIT_DELAYED_WORK(&chip->collapsible_detection_work,
|
|
qpnp_lbc_collapsible_detection_work);
|
|
|
|
/* Get all device-tree properties */
|
|
rc = qpnp_charger_read_dt_props(chip);
|
|
if (rc) {
|
|
pr_err("Failed to read DT properties rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
rc = qpnp_lbc_parse_resources(chip);
|
|
if (rc) {
|
|
pr_err("Unable to parse LBC resources rc=%d\n", rc);
|
|
goto fail_chg_enable;
|
|
}
|
|
|
|
if (chip->cfg_use_external_charger) {
|
|
pr_warn("Disabling Linear Charger (e-external-charger = 1)\n");
|
|
rc = qpnp_disable_lbc_charger(chip);
|
|
if (rc)
|
|
pr_err("Unable to disable charger rc=%d\n", rc);
|
|
return -ENODEV;
|
|
}
|
|
|
|
chip->vadc_dev = qpnp_get_vadc(chip->dev, "chg");
|
|
if (IS_ERR(chip->vadc_dev)) {
|
|
rc = PTR_ERR(chip->vadc_dev);
|
|
if (rc != -EPROBE_DEFER)
|
|
pr_err("vadc prop missing rc=%d\n",
|
|
rc);
|
|
goto fail_chg_enable;
|
|
}
|
|
|
|
/* Initialize h/w */
|
|
rc = qpnp_lbc_misc_init(chip);
|
|
if (rc) {
|
|
pr_err("unable to initialize LBC MISC rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
rc = qpnp_lbc_chg_init(chip);
|
|
if (rc) {
|
|
pr_err("unable to initialize LBC charger rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
rc = qpnp_lbc_bat_if_init(chip);
|
|
if (rc) {
|
|
pr_err("unable to initialize LBC BAT_IF rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
rc = qpnp_lbc_usb_path_init(chip);
|
|
if (rc) {
|
|
pr_err("unable to initialize LBC USB path rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
if (chip->cfg_chgr_led_support) {
|
|
rc = qpnp_lbc_register_chgr_led(chip);
|
|
if (rc) {
|
|
pr_err("unable to register charger led rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
if (chip->bat_if_base) {
|
|
chip->batt_present = qpnp_lbc_is_batt_present(chip);
|
|
chip->batt_psy.name = "battery";
|
|
chip->batt_psy.type = POWER_SUPPLY_TYPE_BATTERY;
|
|
chip->batt_psy.properties = msm_batt_power_props;
|
|
chip->batt_psy.num_properties =
|
|
ARRAY_SIZE(msm_batt_power_props);
|
|
chip->batt_psy.get_property = qpnp_batt_power_get_property;
|
|
chip->batt_psy.set_property = qpnp_batt_power_set_property;
|
|
chip->batt_psy.property_is_writeable =
|
|
qpnp_batt_property_is_writeable;
|
|
chip->batt_psy.external_power_changed =
|
|
qpnp_batt_external_power_changed;
|
|
chip->batt_psy.supplied_to = pm_batt_supplied_to;
|
|
chip->batt_psy.num_supplicants =
|
|
ARRAY_SIZE(pm_batt_supplied_to);
|
|
rc = power_supply_register(chip->dev, &chip->batt_psy);
|
|
if (rc < 0) {
|
|
pr_err("batt failed to register rc=%d\n", rc);
|
|
goto fail_chg_enable;
|
|
}
|
|
}
|
|
|
|
if ((chip->cfg_cool_bat_decidegc || chip->cfg_warm_bat_decidegc)
|
|
&& chip->bat_if_base) {
|
|
chip->adc_param.low_temp = chip->cfg_cool_bat_decidegc;
|
|
chip->adc_param.high_temp = chip->cfg_warm_bat_decidegc;
|
|
chip->adc_param.timer_interval = ADC_MEAS1_INTERVAL_1S;
|
|
chip->adc_param.state_request = ADC_TM_HIGH_LOW_THR_ENABLE;
|
|
chip->adc_param.btm_ctx = chip;
|
|
chip->adc_param.threshold_notification =
|
|
qpnp_lbc_jeita_adc_notification;
|
|
chip->adc_param.channel = LR_MUX1_BATT_THERM;
|
|
|
|
if (get_prop_batt_present(chip)) {
|
|
rc = qpnp_adc_tm_channel_measure(chip->adc_tm_dev,
|
|
&chip->adc_param);
|
|
if (rc) {
|
|
pr_err("request ADC error rc=%d\n", rc);
|
|
goto unregister_batt;
|
|
}
|
|
}
|
|
}
|
|
|
|
rc = qpnp_lbc_bat_if_configure_btc(chip);
|
|
if (rc) {
|
|
pr_err("Failed to configure btc rc=%d\n", rc);
|
|
goto unregister_batt;
|
|
}
|
|
|
|
/* Get/Set charger's initial status */
|
|
determine_initial_status(chip);
|
|
|
|
rc = qpnp_lbc_request_irqs(chip);
|
|
if (rc) {
|
|
pr_err("unable to initialize LBC MISC rc=%d\n", rc);
|
|
goto unregister_batt;
|
|
}
|
|
|
|
if (chip->cfg_charging_disabled && !get_prop_batt_present(chip))
|
|
pr_info("Battery absent and charging disabled !!!\n");
|
|
|
|
/* Configure initial alarm for VDD trim */
|
|
if ((chip->supported_feature_flag & VDD_TRIM_SUPPORTED) &&
|
|
qpnp_lbc_is_fastchg_on(chip)) {
|
|
kt = ns_to_ktime(TRIM_PERIOD_NS);
|
|
alarm_start_relative(&chip->vddtrim_alarm, kt);
|
|
}
|
|
|
|
chip->debug_root = debugfs_create_dir("qpnp_lbc", NULL);
|
|
if (!chip->debug_root)
|
|
pr_err("Couldn't create debug dir\n");
|
|
|
|
if (chip->debug_root) {
|
|
struct dentry *ent;
|
|
|
|
ent = debugfs_create_file("lbc_config", S_IFREG | S_IRUGO,
|
|
chip->debug_root, chip,
|
|
&qpnp_lbc_config_debugfs_ops);
|
|
if (!ent)
|
|
pr_err("Couldn't create lbc_config debug file\n");
|
|
}
|
|
|
|
pr_info("Probe chg_dis=%d bpd=%d usb=%d batt_pres=%d batt_volt=%d soc=%d\n",
|
|
chip->cfg_charging_disabled,
|
|
chip->cfg_bpd_detection,
|
|
qpnp_lbc_is_usb_chg_plugged_in(chip),
|
|
get_prop_batt_present(chip),
|
|
get_prop_battery_voltage_now(chip),
|
|
get_prop_capacity(chip));
|
|
|
|
return 0;
|
|
|
|
unregister_batt:
|
|
if (chip->bat_if_base)
|
|
power_supply_unregister(&chip->batt_psy);
|
|
fail_chg_enable:
|
|
dev_set_drvdata(&spmi->dev, NULL);
|
|
return rc;
|
|
}
|
|
|
|
static int is_parallel_charger(struct spmi_device *spmi)
|
|
{
|
|
return of_property_read_bool(spmi->dev.of_node,
|
|
"qcom,parallel-charger");
|
|
}
|
|
|
|
static int qpnp_lbc_probe(struct spmi_device *spmi)
|
|
{
|
|
if (is_parallel_charger(spmi))
|
|
return qpnp_lbc_parallel_probe(spmi);
|
|
else
|
|
return qpnp_lbc_main_probe(spmi);
|
|
}
|
|
|
|
|
|
static int qpnp_lbc_remove(struct spmi_device *spmi)
|
|
{
|
|
struct qpnp_lbc_chip *chip = dev_get_drvdata(&spmi->dev);
|
|
|
|
if (chip->supported_feature_flag & VDD_TRIM_SUPPORTED) {
|
|
alarm_cancel(&chip->vddtrim_alarm);
|
|
cancel_work_sync(&chip->vddtrim_work);
|
|
}
|
|
cancel_delayed_work_sync(&chip->collapsible_detection_work);
|
|
debugfs_remove_recursive(chip->debug_root);
|
|
if (chip->bat_if_base)
|
|
power_supply_unregister(&chip->batt_psy);
|
|
mutex_destroy(&chip->jeita_configure_lock);
|
|
mutex_destroy(&chip->chg_enable_lock);
|
|
dev_set_drvdata(&spmi->dev, NULL);
|
|
return 0;
|
|
}
|
|
|
|
static struct of_device_id qpnp_lbc_match_table[] = {
|
|
{ .compatible = QPNP_CHARGER_DEV_NAME, },
|
|
{}
|
|
};
|
|
|
|
static struct spmi_driver qpnp_lbc_driver = {
|
|
.probe = qpnp_lbc_probe,
|
|
.remove = qpnp_lbc_remove,
|
|
.driver = {
|
|
.name = QPNP_CHARGER_DEV_NAME,
|
|
.owner = THIS_MODULE,
|
|
.of_match_table = qpnp_lbc_match_table,
|
|
},
|
|
};
|
|
|
|
/*
|
|
* qpnp_lbc_init() - register spmi driver for qpnp-chg
|
|
*/
|
|
static int __init qpnp_lbc_init(void)
|
|
{
|
|
return spmi_driver_register(&qpnp_lbc_driver);
|
|
}
|
|
module_init(qpnp_lbc_init);
|
|
|
|
static void __exit qpnp_lbc_exit(void)
|
|
{
|
|
spmi_driver_unregister(&qpnp_lbc_driver);
|
|
}
|
|
module_exit(qpnp_lbc_exit);
|
|
|
|
MODULE_DESCRIPTION("QPNP Linear charger driver");
|
|
MODULE_LICENSE("GPL v2");
|
|
MODULE_ALIAS("platform:" QPNP_CHARGER_DEV_NAME);
|