/* 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 #include #include #include #include #include #include #include #include #include #include #include #include #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);