| /* Copyright (c) 2014-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) "SMBCHG: %s: " fmt, __func__ |
| |
| #include <linux/spmi.h> |
| #include <linux/spinlock.h> |
| #include <linux/gpio.h> |
| #include <linux/errno.h> |
| #include <linux/delay.h> |
| #include <linux/module.h> |
| #include <linux/interrupt.h> |
| #include <linux/slab.h> |
| #include <linux/sched.h> |
| #include <linux/power_supply.h> |
| #include <linux/of.h> |
| #include <linux/of_gpio.h> |
| #include <linux/bitops.h> |
| #include <linux/regulator/driver.h> |
| #include <linux/regulator/of_regulator.h> |
| #include <linux/regulator/machine.h> |
| #include <linux/spmi.h> |
| #include <linux/printk.h> |
| #include <linux/ratelimit.h> |
| #include <linux/debugfs.h> |
| #include <linux/rtc.h> |
| #include <linux/qpnp/qpnp-adc.h> |
| #include <linux/batterydata-lib.h> |
| #include <linux/of_batterydata.h> |
| #include <linux/msm_bcl.h> |
| #include <linux/ktime.h> |
| #include <linux/usb/typec.h> |
| |
| /* Mask/Bit helpers */ |
| #define _SMB_MASK(BITS, POS) \ |
| ((unsigned char)(((1 << (BITS)) - 1) << (POS))) |
| #define SMB_MASK(LEFT_BIT_POS, RIGHT_BIT_POS) \ |
| _SMB_MASK((LEFT_BIT_POS) - (RIGHT_BIT_POS) + 1, \ |
| (RIGHT_BIT_POS)) |
| #define STAGE_WORK_DELAY_MS 30000 |
| #define STAGE_COUNT_MAX 2 |
| #define CHG_MONITOR_WORK_DELAY_MS 30000 |
| #define HIGH_CURRENT_COUNT_MAX 2 |
| #define DELTA_MV 100 |
| #define DEFAULT_USB_MA 100 |
| #define HVDCP_MA 1800 |
| /* Parameters for ibus compass compensation */ |
| #define POWER_CONSUMPTION 500 |
| #define COMPENSATION_RATIO 96 |
| #define CHARGE_STAGE_DELTA_MV 50 |
| #define COMPENSATION_LEN 16 |
| #define COMPENSATION_DELAY_MS 5000 |
| #define TAPER_IRQ_LIMIT_SECONDS 5 |
| /* Config registers */ |
| struct smbchg_regulator { |
| struct regulator_desc rdesc; |
| struct regulator_dev *rdev; |
| }; |
| |
| struct parallel_usb_cfg { |
| struct power_supply *psy; |
| int min_current_thr_ma; |
| int min_9v_current_thr_ma; |
| int allowed_lowering_ma; |
| int current_max_ma; |
| bool avail; |
| struct mutex lock; |
| int initial_aicl_ma; |
| ktime_t last_disabled; |
| bool enabled_once; |
| }; |
| |
| struct ilim_entry { |
| int vmin_uv; |
| int vmax_uv; |
| int icl_pt_ma; |
| int icl_lv_ma; |
| int icl_hv_ma; |
| }; |
| |
| struct ilim_map { |
| int num; |
| struct ilim_entry *entries; |
| }; |
| |
| struct smbchg_chip { |
| struct device *dev; |
| struct spmi_device *spmi; |
| |
| /* peripheral register address bases */ |
| u16 chgr_base; |
| u16 bat_if_base; |
| u16 usb_chgpth_base; |
| u16 dc_chgpth_base; |
| u16 otg_base; |
| u16 misc_base; |
| |
| int fake_battery_soc; |
| u8 revision[4]; |
| |
| /* configuration parameters */ |
| int iterm_ma; |
| int usb_max_current_ma; |
| int dc_max_current_ma; |
| int usb_target_current_ma; |
| int usb_tl_current_ma; |
| int dc_target_current_ma; |
| int target_fastchg_current_ma; |
| int cfg_fastchg_current_ma; |
| int fastchg_current_ma; |
| int vfloat_mv; |
| int fastchg_current_comp; |
| int float_voltage_comp; |
| int resume_delta_mv; |
| int safety_time; |
| int prechg_safety_time; |
| int bmd_pin_src; |
| int jeita_temp_hard_limit; |
| int cfg_vfloat_mv; |
| int current_stage_thr_mv; |
| int current_stage_delta_mv; |
| int current_stage_ma; |
| int current_stage_count; |
| int parallel_current_limited; |
| int parallel_voltage_checked; |
| unsigned long charge_stage_convert_time; |
| int warm_current_ma; |
| int cool_current_ma; |
| int soft_temp_comp; |
| int warm_bat_decidegc; |
| int high_current_thr_ma; |
| int high_current_count; |
| int max_input_current_ma; |
| int float_voltage_comp_stage; |
| bool use_vfloat_adjustments; |
| bool iterm_disabled; |
| bool bmd_algo_disabled; |
| bool soft_vfloat_comp_disabled; |
| bool chg_enabled; |
| bool low_icl_wa_on; |
| bool charge_unknown_battery; |
| bool chg_inhibit_en; |
| bool chg_inhibit_source_fg; |
| bool low_volt_dcin; |
| bool vbat_above_headroom; |
| bool force_aicl_rerun; |
| bool temp_comp_done; |
| u8 original_usbin_allowance; |
| struct parallel_usb_cfg parallel; |
| struct delayed_work parallel_en_work; |
| struct dentry *debug_root; |
| |
| /* wipower params */ |
| struct ilim_map wipower_default; |
| struct ilim_map wipower_pt; |
| struct ilim_map wipower_div2; |
| struct qpnp_vadc_chip *vadc_dev; |
| bool wipower_dyn_icl_avail; |
| struct ilim_entry current_ilim; |
| struct mutex wipower_config; |
| bool wipower_configured; |
| struct qpnp_adc_tm_btm_param param; |
| |
| /* flash current prediction */ |
| int rpara_uohm; |
| int rslow_uohm; |
| int vled_max_uv; |
| |
| /* vfloat adjustment */ |
| int max_vbat_sample; |
| int n_vbat_samples; |
| |
| /* status variables */ |
| int battchg_disabled; |
| int usb_suspended; |
| int dc_suspended; |
| int wake_reasons; |
| int previous_soc; |
| int usb_online; |
| bool dc_present; |
| bool usb_present; |
| bool batt_present; |
| int otg_retries; |
| ktime_t otg_enable_time; |
| bool aicl_deglitch_short; |
| bool sw_esr_pulse_en; |
| bool safety_timer_en; |
| bool aicl_complete; |
| bool usb_ov_det; |
| bool otg_pulse_skip_dis; |
| const char *battery_type; |
| bool very_weak_charger; |
| bool parallel_charger_detected; |
| |
| /* jeita and temperature */ |
| bool batt_hot; |
| bool batt_cold; |
| bool batt_warm; |
| bool batt_cool; |
| bool batt_ov; |
| unsigned int thermal_levels; |
| unsigned int therm_lvl_sel; |
| unsigned int *thermal_mitigation; |
| unsigned int *thermal_mitigation_hvdcp; |
| |
| /* irqs */ |
| int batt_hot_irq; |
| int batt_warm_irq; |
| int batt_cool_irq; |
| int batt_cold_irq; |
| int batt_missing_irq; |
| int vbat_low_irq; |
| int chg_hot_irq; |
| int chg_term_irq; |
| int taper_irq; |
| bool taper_irq_enabled; |
| struct mutex taper_irq_lock; |
| int recharge_irq; |
| int fastchg_irq; |
| int safety_timeout_irq; |
| int power_ok_irq; |
| int dcin_uv_irq; |
| int usbin_uv_irq; |
| int usbin_ov_irq; |
| int src_detect_irq; |
| int otg_fail_irq; |
| int otg_oc_irq; |
| int aicl_done_irq; |
| int usbid_change_irq; |
| int chg_error_irq; |
| bool enable_aicl_wake; |
| |
| /* psy */ |
| struct power_supply *usb_psy; |
| struct power_supply batt_psy; |
| struct power_supply dc_psy; |
| struct power_supply *bms_psy; |
| int dc_psy_type; |
| const char *bms_psy_name; |
| const char *battery_psy_name; |
| bool psy_registered; |
| |
| struct smbchg_regulator otg_vreg; |
| struct smbchg_regulator ext_otg_vreg; |
| struct work_struct usb_set_online_work; |
| struct work_struct resume_to_normal_work; |
| struct delayed_work vfloat_adjust_work; |
| struct delayed_work hvdcp_det_work; |
| struct delayed_work current_stage_work; |
| struct delayed_work monitor_charging_work; |
| spinlock_t sec_access_lock; |
| struct mutex current_change_lock; |
| struct mutex usb_set_online_lock; |
| struct mutex battchg_disabled_lock; |
| struct mutex usb_en_lock; |
| struct mutex dc_en_lock; |
| struct mutex fcc_lock; |
| struct mutex pm_lock; |
| struct mutex otg_lock; |
| /* aicl deglitch workaround */ |
| unsigned long first_aicl_seconds; |
| int aicl_irq_count; |
| struct mutex usb_status_lock; |
| char compass_compensation[COMPENSATION_LEN]; |
| }; |
| |
| enum print_reason { |
| PR_REGISTER = BIT(0), |
| PR_INTERRUPT = BIT(1), |
| PR_STATUS = BIT(2), |
| PR_DUMP = BIT(3), |
| PR_PM = BIT(4), |
| PR_MISC = BIT(5), |
| PR_WIPOWER = BIT(6), |
| }; |
| |
| enum wake_reason { |
| PM_PARALLEL_CHECK = BIT(0), |
| PM_REASON_VFLOAT_ADJUST = BIT(1), |
| PM_ESR_PULSE = BIT(2), |
| PM_PARALLEL_TAPER = BIT(3), |
| PM_CHARGING_CHECK = BIT(7), |
| }; |
| |
| static int smbchg_float_voltage_set(struct smbchg_chip *chip, int vfloat_mv); |
| static void update_compass_compensation(struct power_supply *psy); |
| static int get_current_time(unsigned long *now_tm_sec); |
| static int smbchg_is_hvdcp(struct smbchg_chip *chip); |
| static int smbchg_debug_mask; |
| module_param_named( |
| debug_mask, smbchg_debug_mask, int, S_IRUSR | S_IWUSR |
| ); |
| |
| static int smbchg_parallel_en = 1; |
| module_param_named( |
| parallel_en, smbchg_parallel_en, int, S_IRUSR | S_IWUSR |
| ); |
| |
| static int wipower_dyn_icl_en; |
| module_param_named( |
| dynamic_icl_wipower_en, wipower_dyn_icl_en, |
| int, S_IRUSR | S_IWUSR |
| ); |
| |
| static int wipower_dcin_interval = ADC_MEAS1_INTERVAL_2P0MS; |
| module_param_named( |
| wipower_dcin_interval, wipower_dcin_interval, |
| int, S_IRUSR | S_IWUSR |
| ); |
| |
| #define WIPOWER_DEFAULT_HYSTERISIS_UV 250000 |
| static int wipower_dcin_hyst_uv = WIPOWER_DEFAULT_HYSTERISIS_UV; |
| module_param_named( |
| wipower_dcin_hyst_uv, wipower_dcin_hyst_uv, |
| int, S_IRUSR | S_IWUSR |
| ); |
| |
| static bool off_charge_flag; |
| |
| #define pr_smb(reason, fmt, ...) \ |
| do { \ |
| if (smbchg_debug_mask & (reason)) \ |
| pr_info(fmt, ##__VA_ARGS__); \ |
| else \ |
| pr_debug(fmt, ##__VA_ARGS__); \ |
| } while (0) |
| |
| #define pr_smb_rt(reason, fmt, ...) \ |
| do { \ |
| if (smbchg_debug_mask & (reason)) \ |
| pr_info_ratelimited(fmt, ##__VA_ARGS__); \ |
| else \ |
| pr_debug_ratelimited(fmt, ##__VA_ARGS__); \ |
| } while (0) |
| |
| static int smbchg_read(struct smbchg_chip *chip, u8 *val, |
| u16 addr, int count) |
| { |
| int rc = 0; |
| struct spmi_device *spmi = chip->spmi; |
| |
| if (addr == 0) { |
| dev_err(chip->dev, "addr cannot be zero addr=0x%02x sid=0x%02x rc=%d\n", |
| addr, spmi->sid, rc); |
| return -EINVAL; |
| } |
| |
| rc = spmi_ext_register_readl(spmi->ctrl, spmi->sid, addr, val, count); |
| if (rc) { |
| dev_err(chip->dev, "spmi read failed addr=0x%02x sid=0x%02x rc=%d\n", |
| addr, spmi->sid, rc); |
| return rc; |
| } |
| return 0; |
| } |
| |
| /* |
| * Writes an arbitrary number of bytes to a specified register |
| * |
| * Do not use this function for register writes if possible. Instead use the |
| * smbchg_masked_write function. |
| * |
| * The sec_access_lock must be held for all register writes and this function |
| * does not do that. If this function is used, please hold the spinlock or |
| * random secure access writes may fail. |
| */ |
| static int smbchg_write(struct smbchg_chip *chip, u8 *val, |
| u16 addr, int count) |
| { |
| int rc = 0; |
| struct spmi_device *spmi = chip->spmi; |
| |
| if (addr == 0) { |
| dev_err(chip->dev, "addr cannot be zero addr=0x%02x sid=0x%02x rc=%d\n", |
| addr, spmi->sid, rc); |
| return -EINVAL; |
| } |
| |
| rc = spmi_ext_register_writel(spmi->ctrl, spmi->sid, addr, val, count); |
| if (rc) { |
| dev_err(chip->dev, "write failed addr=0x%02x sid=0x%02x rc=%d\n", |
| addr, spmi->sid, rc); |
| return rc; |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * Writes a register to the specified by the base and limited by the bit mask |
| * |
| * Do not use this function for register writes if possible. Instead use the |
| * smbchg_masked_write function. |
| * |
| * The sec_access_lock must be held for all register writes and this function |
| * does not do that. If this function is used, please hold the spinlock or |
| * random secure access writes may fail. |
| */ |
| static int smbchg_masked_write_raw(struct smbchg_chip *chip, u16 base, u8 mask, |
| u8 val) |
| { |
| int rc; |
| u8 reg; |
| |
| rc = smbchg_read(chip, ®, base, 1); |
| if (rc) { |
| dev_err(chip->dev, "spmi read failed: addr=%03X, rc=%d\n", |
| base, rc); |
| return rc; |
| } |
| |
| reg &= ~mask; |
| reg |= val & mask; |
| |
| pr_smb(PR_REGISTER, "addr = 0x%x writing 0x%x\n", base, reg); |
| |
| rc = smbchg_write(chip, ®, base, 1); |
| if (rc) { |
| dev_err(chip->dev, "spmi write failed: addr=%03X, rc=%d\n", |
| base, rc); |
| return rc; |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * Writes a register to the specified by the base and limited by the bit mask |
| * |
| * This function holds a spin lock to ensure secure access register writes goes |
| * through. If the secure access unlock register is armed, any old register |
| * write can unarm the secure access unlock, causing the next write to fail. |
| * |
| * Note: do not use this for sec_access registers. Instead use the function |
| * below: smbchg_sec_masked_write |
| */ |
| static int smbchg_masked_write(struct smbchg_chip *chip, u16 base, u8 mask, |
| u8 val) |
| { |
| unsigned long flags; |
| int rc; |
| |
| spin_lock_irqsave(&chip->sec_access_lock, flags); |
| rc = smbchg_masked_write_raw(chip, base, mask, val); |
| spin_unlock_irqrestore(&chip->sec_access_lock, flags); |
| |
| return rc; |
| } |
| |
| /* |
| * Unlocks sec access and writes to the register specified. |
| * |
| * This function holds a spin lock to exclude other register writes while |
| * the two writes are taking place. |
| */ |
| #define SEC_ACCESS_OFFSET 0xD0 |
| #define SEC_ACCESS_VALUE 0xA5 |
| #define PERIPHERAL_MASK 0xFF |
| static int smbchg_sec_masked_write(struct smbchg_chip *chip, u16 base, u8 mask, |
| u8 val) |
| { |
| unsigned long flags; |
| int rc; |
| u16 peripheral_base = base & (~PERIPHERAL_MASK); |
| |
| spin_lock_irqsave(&chip->sec_access_lock, flags); |
| |
| rc = smbchg_masked_write_raw(chip, peripheral_base + SEC_ACCESS_OFFSET, |
| SEC_ACCESS_VALUE, SEC_ACCESS_VALUE); |
| if (rc) { |
| dev_err(chip->dev, "Unable to unlock sec_access: %d", rc); |
| goto out; |
| } |
| |
| rc = smbchg_masked_write_raw(chip, base, mask, val); |
| |
| out: |
| spin_unlock_irqrestore(&chip->sec_access_lock, flags); |
| return rc; |
| } |
| |
| static void smbchg_stay_awake(struct smbchg_chip *chip, int reason) |
| { |
| int reasons; |
| |
| mutex_lock(&chip->pm_lock); |
| reasons = chip->wake_reasons | reason; |
| if (reasons != 0 && chip->wake_reasons == 0) { |
| pr_smb(PR_PM, "staying awake: 0x%02x (bit %d)\n", |
| reasons, reason); |
| pm_stay_awake(chip->dev); |
| } |
| chip->wake_reasons = reasons; |
| mutex_unlock(&chip->pm_lock); |
| } |
| |
| static void smbchg_relax(struct smbchg_chip *chip, int reason) |
| { |
| int reasons; |
| |
| mutex_lock(&chip->pm_lock); |
| reasons = chip->wake_reasons & (~reason); |
| if (reasons == 0 && chip->wake_reasons != 0) { |
| pr_smb(PR_PM, "relaxing: 0x%02x (bit %d)\n", |
| reasons, reason); |
| pm_relax(chip->dev); |
| } |
| chip->wake_reasons = reasons; |
| mutex_unlock(&chip->pm_lock); |
| }; |
| |
| enum pwr_path_type { |
| UNKNOWN = 0, |
| PWR_PATH_BATTERY = 1, |
| PWR_PATH_USB = 2, |
| PWR_PATH_DC = 3, |
| }; |
| |
| #define PWR_PATH 0x08 |
| #define PWR_PATH_MASK 0x03 |
| static enum pwr_path_type smbchg_get_pwr_path(struct smbchg_chip *chip) |
| { |
| int rc; |
| u8 reg; |
| |
| rc = smbchg_read(chip, ®, chip->usb_chgpth_base + PWR_PATH, 1); |
| if (rc < 0) { |
| dev_err(chip->dev, "Couldn't read PWR_PATH rc = %d\n", rc); |
| return PWR_PATH_BATTERY; |
| } |
| |
| return reg & PWR_PATH_MASK; |
| } |
| |
| #define RID_STS 0xB |
| #define RID_MASK 0xF |
| #define IDEV_STS 0x8 |
| #define RT_STS 0x10 |
| #define USBID_MSB 0xE |
| #define USBIN_UV_BIT BIT(0) |
| #define USBIN_OV_BIT BIT(1) |
| #define USBIN_SRC_DET_BIT BIT(2) |
| #define FMB_STS_MASK SMB_MASK(3, 0) |
| #define USBID_GND_THRESHOLD 0x495 |
| |
| #ifndef CONFIG_TYPEC |
| static bool is_otg_present(struct smbchg_chip *chip) |
| { |
| int rc; |
| u8 reg; |
| u8 usbid_reg[2]; |
| u16 usbid_val; |
| |
| /* |
| * There is a problem with USBID conversions on PMI8994 revisions |
| * 2.0.0. As a workaround, check that the cable is not |
| * detected as factory test before enabling OTG. |
| */ |
| rc = smbchg_read(chip, ®, chip->misc_base + IDEV_STS, 1); |
| if (rc < 0) { |
| dev_err(chip->dev, "Couldn't read IDEV_STS rc = %d\n", rc); |
| return false; |
| } |
| |
| if ((reg & FMB_STS_MASK) != 0) { |
| pr_smb(PR_STATUS, "IDEV_STS = %02x, not ground\n", reg); |
| return false; |
| } |
| |
| rc = smbchg_read(chip, usbid_reg, chip->usb_chgpth_base + USBID_MSB, 2); |
| if (rc < 0) { |
| dev_err(chip->dev, "Couldn't read USBID rc = %d\n", rc); |
| return false; |
| } |
| usbid_val = (usbid_reg[0] << 8) | usbid_reg[1]; |
| |
| if (usbid_val > USBID_GND_THRESHOLD) { |
| pr_smb(PR_STATUS, "USBID = 0x%04x, too high to be ground\n", |
| usbid_val); |
| return false; |
| } |
| |
| rc = smbchg_read(chip, ®, chip->usb_chgpth_base + RID_STS, 1); |
| if (rc < 0) { |
| dev_err(chip->dev, |
| "Couldn't read usb rid status rc = %d\n", rc); |
| return false; |
| } |
| |
| pr_smb(PR_STATUS, "RID_STS = %02x\n", reg); |
| |
| return (reg & RID_MASK) == 0; |
| } |
| #endif |
| |
| #define USBIN_9V BIT(5) |
| #define USBIN_UNREG BIT(4) |
| #define USBIN_LV BIT(3) |
| #define DCIN_9V BIT(2) |
| #define DCIN_UNREG BIT(1) |
| #define DCIN_LV BIT(0) |
| #define INPUT_STS 0x0D |
| #define DCIN_UV_BIT BIT(0) |
| #define DCIN_OV_BIT BIT(1) |
| static bool is_dc_present(struct smbchg_chip *chip) |
| { |
| int rc; |
| u8 reg; |
| |
| rc = smbchg_read(chip, ®, chip->dc_chgpth_base + RT_STS, 1); |
| if (rc < 0) { |
| dev_err(chip->dev, "Couldn't read dc status rc = %d\n", rc); |
| return false; |
| } |
| |
| if ((reg & DCIN_UV_BIT) || (reg & DCIN_OV_BIT)) |
| return false; |
| |
| return true; |
| } |
| |
| static bool is_usb_present(struct smbchg_chip *chip) |
| { |
| int rc; |
| u8 reg; |
| |
| rc = smbchg_read(chip, ®, chip->usb_chgpth_base + RT_STS, 1); |
| if (rc < 0) { |
| dev_err(chip->dev, "Couldn't read usb rt status rc = %d\n", rc); |
| return false; |
| } |
| if (!(reg & USBIN_SRC_DET_BIT) || (reg & USBIN_OV_BIT)) |
| return false; |
| |
| rc = smbchg_read(chip, ®, chip->usb_chgpth_base + INPUT_STS, 1); |
| if (rc < 0) { |
| dev_err(chip->dev, "Couldn't read usb status rc = %d\n", rc); |
| return false; |
| } |
| |
| return !!(reg & (USBIN_9V | USBIN_UNREG | USBIN_LV)); |
| } |
| |
| static char *usb_type_str[] = { |
| "SDP", /* bit 0 */ |
| "OTHER", /* bit 1 */ |
| "DCP", /* bit 2 */ |
| "CDP", /* bit 3 */ |
| "NONE", /* bit 4 error case */ |
| }; |
| |
| #define N_TYPE_BITS 4 |
| #define TYPE_BITS_OFFSET 4 |
| |
| static int get_type(u8 type_reg) |
| { |
| unsigned long type = type_reg; |
| type >>= TYPE_BITS_OFFSET; |
| return find_first_bit(&type, N_TYPE_BITS); |
| } |
| |
| /* helper to return the string of USB type */ |
| static inline char *get_usb_type_name(int type) |
| { |
| return usb_type_str[type]; |
| } |
| |
| static enum power_supply_type usb_type_enum[] = { |
| POWER_SUPPLY_TYPE_USB, /* bit 0 */ |
| POWER_SUPPLY_TYPE_USB_DCP, /* bit 1 */ |
| POWER_SUPPLY_TYPE_USB_DCP, /* bit 2 */ |
| POWER_SUPPLY_TYPE_USB_CDP, /* bit 3 */ |
| POWER_SUPPLY_TYPE_USB_DCP, /* bit 4 error case, report DCP */ |
| }; |
| |
| /* helper to return enum power_supply_type of USB type */ |
| static inline enum power_supply_type get_usb_supply_type(int type) |
| { |
| return usb_type_enum[type]; |
| } |
| |
| static void read_usb_type(struct smbchg_chip *chip, char **usb_type_name, |
| enum power_supply_type *usb_supply_type) |
| { |
| int rc, type; |
| u8 reg; |
| |
| rc = smbchg_read(chip, ®, chip->misc_base + IDEV_STS, 1); |
| if (rc < 0) { |
| dev_err(chip->dev, "Couldn't read status 5 rc = %d\n", rc); |
| *usb_type_name = "Other"; |
| *usb_supply_type = POWER_SUPPLY_TYPE_UNKNOWN; |
| } |
| type = get_type(reg); |
| *usb_type_name = get_usb_type_name(type); |
| *usb_supply_type = get_usb_supply_type(type); |
| } |
| |
| static enum power_supply_property smbchg_battery_properties[] = { |
| POWER_SUPPLY_PROP_STATUS, |
| POWER_SUPPLY_PROP_PRESENT, |
| POWER_SUPPLY_PROP_BATTERY_CHARGING_ENABLED, |
| POWER_SUPPLY_PROP_CHARGING_ENABLED, |
| POWER_SUPPLY_PROP_CHARGE_TYPE, |
| POWER_SUPPLY_PROP_CAPACITY, |
| POWER_SUPPLY_PROP_HEALTH, |
| POWER_SUPPLY_PROP_TECHNOLOGY, |
| POWER_SUPPLY_PROP_SYSTEM_TEMP_LEVEL, |
| POWER_SUPPLY_PROP_FLASH_CURRENT_MAX, |
| POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, |
| POWER_SUPPLY_PROP_VOLTAGE_MAX, |
| POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, |
| POWER_SUPPLY_PROP_CURRENT_NOW, |
| POWER_SUPPLY_PROP_TEMP, |
| POWER_SUPPLY_PROP_VOLTAGE_NOW, |
| POWER_SUPPLY_PROP_SAFETY_TIMER_ENABLE, |
| POWER_SUPPLY_PROP_INPUT_CURRENT_MAX, |
| POWER_SUPPLY_PROP_INPUT_CURRENT_SETTLED, |
| POWER_SUPPLY_PROP_FLASH_ACTIVE, |
| POWER_SUPPLY_PROP_PROFILE_STATUS, |
| POWER_SUPPLY_PROP_COMPASS_COMPENSATION, |
| }; |
| |
| #define CHGR_STS 0x0E |
| #define BATT_LESS_THAN_2V BIT(4) |
| #define CHG_HOLD_OFF_BIT BIT(3) |
| #define CHG_TYPE_MASK SMB_MASK(2, 1) |
| #define CHG_TYPE_SHIFT 1 |
| #define BATT_NOT_CHG_VAL 0x0 |
| #define BATT_PRE_CHG_VAL 0x1 |
| #define BATT_FAST_CHG_VAL 0x2 |
| #define BATT_TAPER_CHG_VAL 0x3 |
| #define CHG_EN_BIT BIT(0) |
| #define CHG_INHIBIT_BIT BIT(1) |
| #define BAT_TCC_REACHED_BIT BIT(7) |
| static int get_prop_batt_status(struct smbchg_chip *chip) |
| { |
| int rc, status = POWER_SUPPLY_STATUS_DISCHARGING; |
| u8 reg = 0, chg_type; |
| bool charger_present, chg_inhibit; |
| |
| charger_present = is_usb_present(chip) | is_dc_present(chip); |
| if (!charger_present) |
| return POWER_SUPPLY_STATUS_DISCHARGING; |
| |
| rc = smbchg_read(chip, ®, chip->chgr_base + RT_STS, 1); |
| if (rc < 0) { |
| dev_err(chip->dev, "Unable to read RT_STS rc = %d\n", rc); |
| return POWER_SUPPLY_STATUS_UNKNOWN; |
| } |
| |
| if (reg & BAT_TCC_REACHED_BIT) |
| return POWER_SUPPLY_STATUS_FULL; |
| |
| chg_inhibit = reg & CHG_INHIBIT_BIT; |
| if (chg_inhibit) |
| return POWER_SUPPLY_STATUS_FULL; |
| |
| rc = smbchg_read(chip, ®, chip->chgr_base + CHGR_STS, 1); |
| if (rc < 0) { |
| dev_err(chip->dev, "Unable to read CHGR_STS rc = %d\n", rc); |
| return POWER_SUPPLY_STATUS_UNKNOWN; |
| } |
| |
| if (reg & CHG_HOLD_OFF_BIT) { |
| /* |
| * when chg hold off happens the battery is |
| * not charging |
| */ |
| status = POWER_SUPPLY_STATUS_NOT_CHARGING; |
| goto out; |
| } |
| |
| chg_type = (reg & CHG_TYPE_MASK) >> CHG_TYPE_SHIFT; |
| |
| if (chg_type == BATT_NOT_CHG_VAL) |
| status = POWER_SUPPLY_STATUS_DISCHARGING; |
| else |
| status = POWER_SUPPLY_STATUS_CHARGING; |
| out: |
| pr_smb_rt(PR_MISC, "CHGR_STS = 0x%02x\n", reg); |
| return status; |
| } |
| |
| #define BAT_PRES_STATUS 0x08 |
| #define BAT_PRES_BIT BIT(7) |
| static int get_prop_batt_present(struct smbchg_chip *chip) |
| { |
| int rc; |
| u8 reg; |
| |
| rc = smbchg_read(chip, ®, chip->bat_if_base + BAT_PRES_STATUS, 1); |
| if (rc < 0) { |
| dev_err(chip->dev, "Unable to read CHGR_STS rc = %d\n", rc); |
| return 0; |
| } |
| |
| return !!(reg & BAT_PRES_BIT); |
| } |
| |
| static int get_prop_charge_type(struct smbchg_chip *chip) |
| { |
| int rc; |
| u8 reg, chg_type; |
| |
| rc = smbchg_read(chip, ®, chip->chgr_base + CHGR_STS, 1); |
| if (rc < 0) { |
| dev_err(chip->dev, "Unable to read CHGR_STS rc = %d\n", rc); |
| return 0; |
| } |
| |
| chg_type = (reg & CHG_TYPE_MASK) >> CHG_TYPE_SHIFT; |
| if (chg_type == BATT_NOT_CHG_VAL) |
| return POWER_SUPPLY_CHARGE_TYPE_NONE; |
| else if (chg_type == BATT_TAPER_CHG_VAL) |
| return POWER_SUPPLY_CHARGE_TYPE_TAPER; |
| else if (chg_type == BATT_FAST_CHG_VAL) |
| return POWER_SUPPLY_CHARGE_TYPE_FAST; |
| else if (chg_type == BATT_PRE_CHG_VAL) |
| return POWER_SUPPLY_CHARGE_TYPE_TRICKLE; |
| |
| return POWER_SUPPLY_CHARGE_TYPE_NONE; |
| } |
| |
| static int set_property_on_fg(struct smbchg_chip *chip, |
| enum power_supply_property prop, int val) |
| { |
| int rc; |
| union power_supply_propval ret = {0, }; |
| |
| if (!chip->bms_psy && chip->bms_psy_name) |
| chip->bms_psy = |
| power_supply_get_by_name((char *)chip->bms_psy_name); |
| if (!chip->bms_psy) { |
| pr_smb(PR_STATUS, "no bms psy found\n"); |
| return -EINVAL; |
| } |
| |
| ret.intval = val; |
| rc = chip->bms_psy->set_property(chip->bms_psy, prop, &ret); |
| if (rc) |
| pr_smb(PR_STATUS, |
| "bms psy does not allow updating prop %d rc = %d\n", |
| prop, rc); |
| |
| return rc; |
| } |
| |
| static int get_property_from_fg(struct smbchg_chip *chip, |
| enum power_supply_property prop, int *val) |
| { |
| int rc; |
| union power_supply_propval ret = {0, }; |
| |
| if (!chip->bms_psy && chip->bms_psy_name) |
| chip->bms_psy = |
| power_supply_get_by_name((char *)chip->bms_psy_name); |
| if (!chip->bms_psy) { |
| pr_smb(PR_STATUS, "no bms psy found\n"); |
| return -EINVAL; |
| } |
| |
| rc = chip->bms_psy->get_property(chip->bms_psy, prop, &ret); |
| if (rc) { |
| pr_smb(PR_STATUS, |
| "bms psy doesn't support reading prop %d rc = %d\n", |
| prop, rc); |
| return rc; |
| } |
| |
| *val = ret.intval; |
| return rc; |
| } |
| |
| static void check_usb_status(struct smbchg_chip *chip) |
| { |
| union power_supply_propval prop = {0,}; |
| |
| if (chip->usb_psy) { |
| chip->usb_psy->get_property(chip->usb_psy, |
| POWER_SUPPLY_PROP_ONLINE, &prop); |
| /* |
| * if battery soc is 0% and usb_psy property online is true in |
| * normal mode(not power-off charging mode), set online to |
| * false to notify system to power off. |
| */ |
| if ((prop.intval == 1) && (!off_charge_flag) && chip->usb_present) { |
| power_supply_set_present(chip->usb_psy, false); |
| power_supply_set_online(chip->usb_psy, false); |
| chip->usb_present = false; |
| } |
| } |
| } |
| |
| #define DEFAULT_BATT_CAPACITY 50 |
| static int get_prop_batt_capacity(struct smbchg_chip *chip) |
| { |
| int capacity, rc; |
| |
| if (chip->fake_battery_soc >= 0) |
| return chip->fake_battery_soc; |
| |
| rc = get_property_from_fg(chip, POWER_SUPPLY_PROP_CAPACITY, &capacity); |
| if (rc) { |
| pr_smb(PR_STATUS, "Couldn't get capacity rc = %d\n", rc); |
| capacity = DEFAULT_BATT_CAPACITY; |
| } |
| |
| if (capacity == 0) { |
| check_usb_status(chip); |
| } |
| |
| return capacity; |
| } |
| |
| /* parse the androidboot.mode, check whether it is power-off charging */ |
| static int __init early_parse_off_charge_flag(char *p) |
| { |
| if (p) { |
| if (!strcmp(p, "charger")) |
| off_charge_flag = true; |
| } |
| return 0; |
| } |
| early_param("androidboot.mode", early_parse_off_charge_flag); |
| |
| #define DEFAULT_BATT_TEMP 200 |
| static int get_prop_batt_temp(struct smbchg_chip *chip) |
| { |
| int temp, rc; |
| |
| rc = get_property_from_fg(chip, POWER_SUPPLY_PROP_TEMP, &temp); |
| if (rc) { |
| pr_smb(PR_STATUS, "Couldn't get temperature rc = %d\n", rc); |
| temp = DEFAULT_BATT_TEMP; |
| } |
| return temp; |
| } |
| |
| #define DEFAULT_BATT_CURRENT_NOW 0 |
| static int get_prop_batt_current_now(struct smbchg_chip *chip) |
| { |
| int ua, rc; |
| |
| rc = get_property_from_fg(chip, POWER_SUPPLY_PROP_CURRENT_NOW, &ua); |
| if (rc) { |
| pr_smb(PR_STATUS, "Couldn't get current rc = %d\n", rc); |
| ua = DEFAULT_BATT_CURRENT_NOW; |
| } |
| return ua; |
| } |
| |
| #define DEFAULT_BATT_VOLTAGE_NOW 0 |
| static int get_prop_batt_voltage_now(struct smbchg_chip *chip) |
| { |
| int uv, rc; |
| |
| rc = get_property_from_fg(chip, POWER_SUPPLY_PROP_VOLTAGE_NOW, &uv); |
| if (rc) { |
| pr_smb(PR_STATUS, "Couldn't get voltage rc = %d\n", rc); |
| uv = DEFAULT_BATT_VOLTAGE_NOW; |
| } |
| return uv; |
| } |
| |
| #define DEFAULT_BATT_VOLTAGE_MAX_DESIGN 4200000 |
| static int get_prop_batt_voltage_max_design(struct smbchg_chip *chip) |
| { |
| int uv, rc; |
| |
| rc = get_property_from_fg(chip, |
| POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, &uv); |
| if (rc) { |
| pr_smb(PR_STATUS, "Couldn't get voltage rc = %d\n", rc); |
| uv = DEFAULT_BATT_VOLTAGE_MAX_DESIGN; |
| } |
| return uv; |
| } |
| |
| static int get_prop_batt_health(struct smbchg_chip *chip) |
| { |
| if (chip->batt_hot) |
| return POWER_SUPPLY_HEALTH_OVERHEAT; |
| else if (chip->batt_cold) |
| return POWER_SUPPLY_HEALTH_COLD; |
| else if (chip->batt_warm) |
| return POWER_SUPPLY_HEALTH_WARM; |
| else if (chip->batt_cool) |
| return POWER_SUPPLY_HEALTH_COOL; |
| else |
| return POWER_SUPPLY_HEALTH_GOOD; |
| } |
| |
| /* add for healthd, as healthd do not have warm/cool */ |
| static int get_batt_health(struct smbchg_chip *chip) |
| { |
| if (chip->batt_hot) |
| return POWER_SUPPLY_HEALTH_OVERHEAT; |
| else if (chip->batt_cold) |
| return POWER_SUPPLY_HEALTH_COLD; |
| else |
| return POWER_SUPPLY_HEALTH_GOOD; |
| } |
| |
| static int get_prop_batt_profile(struct smbchg_chip *chip) |
| { |
| int profile_status, rc; |
| |
| rc = get_property_from_fg(chip, |
| POWER_SUPPLY_PROP_PROFILE_STATUS, &profile_status); |
| if (rc) { |
| pr_smb(PR_STATUS, "Couldn't get profile status rc = %d\n", rc); |
| profile_status = 0; |
| } |
| return profile_status; |
| } |
| |
| static const int usb_current_table[] = { |
| 300, |
| 400, |
| 450, |
| 475, |
| 500, |
| 550, |
| 600, |
| 650, |
| 700, |
| 900, |
| 950, |
| 1000, |
| 1100, |
| 1200, |
| 1400, |
| 1450, |
| 1500, |
| 1600, |
| 1800, |
| 1850, |
| 1880, |
| 1910, |
| 1930, |
| 1950, |
| 1970, |
| 2000, |
| 2050, |
| 2100, |
| 2300, |
| 2400, |
| 2500, |
| 3000 |
| }; |
| |
| static const int dc_current_table[] = { |
| 300, |
| 400, |
| 450, |
| 475, |
| 500, |
| 550, |
| 600, |
| 650, |
| 700, |
| 900, |
| 950, |
| 1000, |
| 1100, |
| 1200, |
| 1400, |
| 1450, |
| 1500, |
| 1600, |
| 1800, |
| 1850, |
| 1880, |
| 1910, |
| 1930, |
| 1950, |
| 1970, |
| 2000, |
| }; |
| |
| static const int fcc_comp_table[] = { |
| 250, |
| 700, |
| 900, |
| 1200, |
| }; |
| |
| static int calc_thermal_limited_current(struct smbchg_chip *chip, |
| int current_ma) |
| { |
| int therm_ma; |
| |
| if (chip->therm_lvl_sel > 0 |
| && chip->therm_lvl_sel < (chip->thermal_levels - 1)) { |
| /* |
| * consider thermal limit only when it is active and not at |
| * the highest level |
| */ |
| if (smbchg_is_hvdcp(chip)) |
| therm_ma = (int)chip->thermal_mitigation_hvdcp[chip->therm_lvl_sel]; |
| else |
| therm_ma = (int)chip->thermal_mitigation[chip->therm_lvl_sel]; |
| if (therm_ma < current_ma) { |
| pr_smb(PR_STATUS, |
| "Limiting current due to thermal: %d mA", |
| therm_ma); |
| return therm_ma; |
| } |
| } |
| |
| return current_ma; |
| } |
| |
| #define CMD_CHG_REG 0x42 |
| #define EN_BAT_CHG_BIT BIT(1) |
| static int smbchg_charging_en(struct smbchg_chip *chip, bool en) |
| { |
| /* The en bit is configured active low */ |
| return smbchg_masked_write(chip, chip->bat_if_base + CMD_CHG_REG, |
| EN_BAT_CHG_BIT, en ? 0 : EN_BAT_CHG_BIT); |
| } |
| |
| #define CMD_IL 0x40 |
| #define USBIN_SUSPEND_BIT BIT(4) |
| #define CURRENT_100_MA 100 |
| #define CURRENT_150_MA 150 |
| #define CURRENT_500_MA 500 |
| #define CURRENT_900_MA 900 |
| #define CURRENT_1500_MA 1500 |
| #define SUSPEND_CURRENT_MA 2 |
| #define ICL_OVERRIDE_BIT BIT(2) |
| static int smbchg_usb_suspend(struct smbchg_chip *chip, bool suspend) |
| { |
| int rc; |
| |
| rc = smbchg_masked_write(chip, chip->usb_chgpth_base + CMD_IL, |
| USBIN_SUSPEND_BIT, suspend ? USBIN_SUSPEND_BIT : 0); |
| if (rc < 0) |
| dev_err(chip->dev, "Couldn't set usb suspend rc = %d\n", rc); |
| return rc; |
| } |
| |
| #define DCIN_SUSPEND_BIT BIT(3) |
| static int smbchg_dc_suspend(struct smbchg_chip *chip, bool suspend) |
| { |
| int rc = 0; |
| |
| rc = smbchg_masked_write(chip, chip->usb_chgpth_base + CMD_IL, |
| DCIN_SUSPEND_BIT, suspend ? DCIN_SUSPEND_BIT : 0); |
| if (rc < 0) |
| dev_err(chip->dev, "Couldn't set dc suspend rc = %d\n", rc); |
| return rc; |
| } |
| |
| #define IL_CFG 0xF2 |
| #define DCIN_INPUT_MASK SMB_MASK(4, 0) |
| static int smbchg_set_dc_current_max(struct smbchg_chip *chip, int current_ma) |
| { |
| int i; |
| u8 dc_cur_val; |
| |
| for (i = ARRAY_SIZE(dc_current_table) - 1; i >= 0; i--) { |
| if (current_ma >= dc_current_table[i]) |
| break; |
| } |
| |
| if (i < 0) { |
| dev_err(chip->dev, "Cannot find %dma current_table\n", |
| current_ma); |
| return -EINVAL; |
| } |
| |
| chip->dc_max_current_ma = dc_current_table[i]; |
| dc_cur_val = i & DCIN_INPUT_MASK; |
| |
| pr_smb(PR_STATUS, "dc current set to %d mA\n", |
| chip->dc_max_current_ma); |
| return smbchg_sec_masked_write(chip, chip->dc_chgpth_base + IL_CFG, |
| DCIN_INPUT_MASK, dc_cur_val); |
| } |
| |
| enum enable_reason { |
| /* userspace has suspended charging altogether */ |
| REASON_USER = BIT(0), |
| /* |
| * this specific path has been suspended through the power supply |
| * framework |
| */ |
| REASON_POWER_SUPPLY = BIT(1), |
| /* |
| * the usb driver has suspended this path by setting a current limit |
| * of < 2MA |
| */ |
| REASON_USB = BIT(2), |
| /* |
| * when a wireless charger comes online, |
| * the dc path is suspended for a second |
| */ |
| REASON_WIRELESS = BIT(3), |
| /* |
| * the thermal daemon can suspend a charge path when the system |
| * temperature levels rise |
| */ |
| REASON_THERMAL = BIT(4), |
| /* |
| * an external OTG supply is being used, suspend charge path so the |
| * charger does not accidentally try to charge from the external supply. |
| */ |
| REASON_OTG = BIT(5), |
| /* |
| * the charger is very weak, do not draw any current from it |
| */ |
| REASON_WEAK_CHARGER = BIT(6), |
| }; |
| |
| enum battchg_enable_reason { |
| /* userspace has disabled battery charging */ |
| REASON_BATTCHG_USER = BIT(0), |
| /* battery charging disabled while loading battery profiles */ |
| REASON_BATTCHG_UNKNOWN_BATTERY = BIT(1), |
| }; |
| |
| static struct power_supply *get_parallel_psy(struct smbchg_chip *chip) |
| { |
| if (!chip->parallel.avail) |
| return NULL; |
| if (chip->parallel.psy) |
| return chip->parallel.psy; |
| chip->parallel.psy = power_supply_get_by_name("usb-parallel"); |
| if (!chip->parallel.psy) |
| pr_smb(PR_STATUS, "parallel charger not found\n"); |
| return chip->parallel.psy; |
| } |
| |
| static void smbchg_usb_update_online_work(struct work_struct *work) |
| { |
| struct smbchg_chip *chip = container_of(work, |
| struct smbchg_chip, |
| usb_set_online_work); |
| bool user_enabled = (chip->usb_suspended & REASON_USER) == 0; |
| int online; |
| |
| online = user_enabled && chip->usb_present && !chip->very_weak_charger; |
| |
| mutex_lock(&chip->usb_set_online_lock); |
| if (chip->usb_online != online) { |
| pr_smb(PR_MISC, "setting usb psy online = %d\n", online); |
| power_supply_set_online(chip->usb_psy, online); |
| chip->usb_online = online; |
| } |
| mutex_unlock(&chip->usb_set_online_lock); |
| } |
| |
| static bool smbchg_primary_usb_is_en(struct smbchg_chip *chip, |
| enum enable_reason reason) |
| { |
| bool enabled; |
| |
| mutex_lock(&chip->usb_en_lock); |
| enabled = (chip->usb_suspended & reason) == 0; |
| mutex_unlock(&chip->usb_en_lock); |
| |
| return enabled; |
| } |
| |
| static bool smcghg_is_battchg_en(struct smbchg_chip *chip, |
| enum battchg_enable_reason reason) |
| { |
| bool enabled; |
| |
| mutex_lock(&chip->battchg_disabled_lock); |
| enabled = !(chip->battchg_disabled & reason); |
| mutex_unlock(&chip->battchg_disabled_lock); |
| |
| return enabled; |
| } |
| |
| static int smbchg_battchg_en(struct smbchg_chip *chip, bool enable, |
| enum battchg_enable_reason reason, bool *changed) |
| { |
| int rc = 0, battchg_disabled; |
| |
| pr_smb(PR_STATUS, "battchg %s, susp = %02x, en? = %d, reason = %02x\n", |
| chip->battchg_disabled == 0 ? "enabled" : "disabled", |
| chip->battchg_disabled, enable, reason); |
| |
| mutex_lock(&chip->battchg_disabled_lock); |
| if (!enable) |
| battchg_disabled = chip->battchg_disabled | reason; |
| else |
| battchg_disabled = chip->battchg_disabled & (~reason); |
| |
| /* avoid unnecessary spmi interactions if nothing changed */ |
| /* avoid goto skip when enable charge but chip->battchg_disabled is 0 */ |
| if ((!!battchg_disabled == !!chip->battchg_disabled) |
| && chip->battchg_disabled) { |
| *changed = false; |
| goto out; |
| } |
| |
| rc = smbchg_charging_en(chip, !battchg_disabled); |
| if (rc < 0) { |
| dev_err(chip->dev, |
| "Couldn't configure batt chg: 0x%x rc = %d\n", |
| battchg_disabled, rc); |
| goto out; |
| } |
| *changed = true; |
| |
| pr_smb(PR_STATUS, "batt charging %s, battchg_disabled = %02x\n", |
| battchg_disabled == 0 ? "enabled" : "disabled", |
| battchg_disabled); |
| out: |
| chip->battchg_disabled = battchg_disabled; |
| mutex_unlock(&chip->battchg_disabled_lock); |
| return rc; |
| } |
| |
| static int smbchg_primary_usb_en(struct smbchg_chip *chip, bool enable, |
| enum enable_reason reason, bool *changed) |
| { |
| int rc = 0, suspended; |
| |
| pr_smb(PR_STATUS, "usb %s, susp = %02x, en? = %d, reason = %02x\n", |
| chip->usb_suspended == 0 ? "enabled" |
| : "suspended", chip->usb_suspended, enable, reason); |
| mutex_lock(&chip->usb_en_lock); |
| if (!enable) |
| suspended = chip->usb_suspended | reason; |
| else |
| suspended = chip->usb_suspended & (~reason); |
| |
| /* avoid unnecessary spmi interactions if nothing changed */ |
| /* avoid goto skip when usb enable but chip->usb_suspended is 0 */ |
| if ((!!suspended == !!chip->usb_suspended) |
| && chip->usb_suspended) { |
| *changed = false; |
| goto out; |
| } |
| |
| *changed = true; |
| rc = smbchg_usb_suspend(chip, suspended != 0); |
| if (rc < 0) { |
| dev_err(chip->dev, |
| "Couldn't set usb suspend: %d rc = %d\n", |
| suspended, rc); |
| goto out; |
| } |
| |
| pr_smb(PR_STATUS, "usb charging %s, suspended = %02x\n", |
| suspended == 0 ? "enabled" |
| : "suspended", suspended); |
| out: |
| chip->usb_suspended = suspended; |
| mutex_unlock(&chip->usb_en_lock); |
| return rc; |
| } |
| |
| static int smbchg_dc_en(struct smbchg_chip *chip, bool enable, |
| enum enable_reason reason) |
| { |
| int rc = 0, suspended; |
| |
| pr_smb(PR_STATUS, "dc %s, susp = %02x, en? = %d, reason = %02x\n", |
| chip->dc_suspended == 0 ? "enabled" |
| : "suspended", chip->dc_suspended, enable, reason); |
| mutex_lock(&chip->dc_en_lock); |
| if (!enable) |
| suspended = chip->dc_suspended | reason; |
| else |
| suspended = chip->dc_suspended & ~reason; |
| |
| /* avoid unnecessary spmi interactions if nothing changed */ |
| if (!!suspended == !!chip->dc_suspended) |
| goto out; |
| |
| rc = smbchg_dc_suspend(chip, suspended != 0); |
| if (rc < 0) { |
| dev_err(chip->dev, |
| "Couldn't set dc suspend: %d rc = %d\n", |
| suspended, rc); |
| goto out; |
| } |
| |
| if (chip->dc_psy_type != -EINVAL && chip->psy_registered) |
| power_supply_changed(&chip->dc_psy); |
| pr_smb(PR_STATUS, "dc charging %s, suspended = %02x\n", |
| suspended == 0 ? "enabled" |
| : "suspended", suspended); |
| out: |
| chip->dc_suspended = suspended; |
| mutex_unlock(&chip->dc_en_lock); |
| return rc; |
| } |
| |
| #define CHGPTH_CFG 0xF4 |
| #define CFG_USB_2_3_SEL_BIT BIT(7) |
| #define CFG_USB_2 0 |
| #define CFG_USB_3 BIT(7) |
| #define USBIN_INPUT_MASK SMB_MASK(4, 0) |
| #define USBIN_MODE_CHG_BIT BIT(0) |
| #define USBIN_LIMITED_MODE 0 |
| #define USBIN_HC_MODE BIT(0) |
| #define USB51_MODE_BIT BIT(1) |
| #define USB51_100MA 0 |
| #define USB51_500MA BIT(1) |
| static int smbchg_set_high_usb_chg_current(struct smbchg_chip *chip, |
| int current_ma) |
| { |
| int i, rc; |
| u8 usb_cur_val; |
| |
| for (i = ARRAY_SIZE(usb_current_table) - 1; i >= 0; i--) { |
| if (current_ma >= usb_current_table[i]) |
| break; |
| } |
| if (i < 0) { |
| dev_err(chip->dev, |
| "Cannot find %dma current_table using %d\n", |
| current_ma, CURRENT_150_MA); |
| |
| rc = smbchg_sec_masked_write(chip, |
| chip->usb_chgpth_base + CHGPTH_CFG, |
| CFG_USB_2_3_SEL_BIT, CFG_USB_3); |
| rc |= smbchg_masked_write(chip, chip->usb_chgpth_base + CMD_IL, |
| USBIN_MODE_CHG_BIT | USB51_MODE_BIT, |
| USBIN_LIMITED_MODE | USB51_100MA); |
| if (rc < 0) |
| dev_err(chip->dev, "Couldn't set %dmA rc=%d\n", |
| CURRENT_150_MA, rc); |
| else |
| chip->usb_max_current_ma = 150; |
| return rc; |
| } |
| |
| usb_cur_val = i & USBIN_INPUT_MASK; |
| rc = smbchg_sec_masked_write(chip, chip->usb_chgpth_base + IL_CFG, |
| USBIN_INPUT_MASK, usb_cur_val); |
| if (rc < 0) { |
| dev_err(chip->dev, "cannot write to config c rc = %d\n", rc); |
| return rc; |
| } |
| |
| rc = smbchg_masked_write(chip, chip->usb_chgpth_base + CMD_IL, |
| USBIN_MODE_CHG_BIT, USBIN_HC_MODE); |
| if (rc < 0) |
| dev_err(chip->dev, "Couldn't write cfg 5 rc = %d\n", rc); |
| chip->usb_max_current_ma = usb_current_table[i]; |
| return rc; |
| } |
| |
| /* if APSD results are used |
| * if SDP is detected it will look at 500mA setting |
| * if set it will draw 500mA |
| * if unset it will draw 100mA |
| * if CDP/DCP it will look at 0x0C setting |
| * i.e. values in 0x41[1, 0] does not matter |
| */ |
| static int smbchg_set_usb_current_max(struct smbchg_chip *chip, |
| int current_ma) |
| { |
| int rc = 0; |
| bool changed; |
| enum power_supply_type usb_supply_type; |
| char *usb_type_name = "null"; |
| |
| if (!chip->batt_present) { |
| pr_info_ratelimited("Ignoring usb current->%d, battery is absent\n", |
| current_ma); |
| return 0; |
| } |
| pr_smb(PR_STATUS, "USB current_ma = %d\n", current_ma); |
| |
| if (current_ma == SUSPEND_CURRENT_MA) { |
| /* suspend the usb if current set to 2mA */ |
| rc = smbchg_primary_usb_en(chip, false, REASON_USB, &changed); |
| chip->usb_max_current_ma = 0; |
| goto out; |
| } else { |
| rc = smbchg_primary_usb_en(chip, true, REASON_USB, &changed); |
| } |
| |
| if (chip->low_icl_wa_on) { |
| chip->usb_max_current_ma = current_ma; |
| pr_smb(PR_STATUS, |
| "low_icl_wa on, ignoring the usb current setting\n"); |
| goto out; |
| } |
| |
| read_usb_type(chip, &usb_type_name, &usb_supply_type); |
| |
| switch (usb_supply_type) { |
| case POWER_SUPPLY_TYPE_USB: |
| if (current_ma < CURRENT_150_MA) { |
| /* force 100mA */ |
| rc = smbchg_sec_masked_write(chip, |
| chip->usb_chgpth_base + CHGPTH_CFG, |
| CFG_USB_2_3_SEL_BIT, CFG_USB_2); |
| if (rc < 0) { |
| pr_err("Couldn't set CHGPTH_CFG rc = %d\n", rc); |
| goto out; |
| } |
| rc = smbchg_masked_write(chip, |
| chip->usb_chgpth_base + CMD_IL, |
| USBIN_MODE_CHG_BIT | USB51_MODE_BIT, |
| USBIN_LIMITED_MODE | USB51_100MA); |
| if (rc < 0) { |
| pr_err("Couldn't set CMD_IL rc = %d\n", rc); |
| goto out; |
| } |
| chip->usb_max_current_ma = 100; |
| } |
| /* specific current values */ |
| if (current_ma == CURRENT_150_MA) { |
| rc = smbchg_sec_masked_write(chip, |
| chip->usb_chgpth_base + CHGPTH_CFG, |
| CFG_USB_2_3_SEL_BIT, CFG_USB_3); |
| if (rc < 0) { |
| pr_err("Couldn't set CHGPTH_CFG rc = %d\n", rc); |
| goto out; |
| } |
| rc = smbchg_masked_write(chip, |
| chip->usb_chgpth_base + CMD_IL, |
| USBIN_MODE_CHG_BIT | USB51_MODE_BIT, |
| USBIN_LIMITED_MODE | USB51_100MA); |
| if (rc < 0) { |
| pr_err("Couldn't set CMD_IL rc = %d\n", rc); |
| goto out; |
| } |
| chip->usb_max_current_ma = 150; |
| } |
| if (current_ma == CURRENT_500_MA) { |
| rc = smbchg_sec_masked_write(chip, |
| chip->usb_chgpth_base + CHGPTH_CFG, |
| CFG_USB_2_3_SEL_BIT, CFG_USB_2); |
| if (rc < 0) { |
| pr_err("Couldn't set CHGPTH_CFG rc = %d\n", rc); |
| goto out; |
| } |
| rc = smbchg_masked_write(chip, |
| chip->usb_chgpth_base + CMD_IL, |
| USBIN_MODE_CHG_BIT | USB51_MODE_BIT, |
| USBIN_LIMITED_MODE | USB51_500MA); |
| if (rc < 0) { |
| pr_err("Couldn't set CMD_IL rc = %d\n", rc); |
| goto out; |
| } |
| chip->usb_max_current_ma = 500; |
| } |
| /* |
| * Remove the code of 900ma, as angler do not support USB3.0. |
| * MacBook which has typec port is detected as SDP, and it is |
| * also a typec port using medium current mode (22k pull up resistance) |
| * so set 1.5A input current according to typec protocol, 1.5A is |
| * above parallel charging threshold, enable parallel charging, set |
| * half of 1.5A (0.7A) for pmi8994 and smb1351, so if current_ma |
| * is above 500ma, set HC(high current) mode for pmi8994. |
| * Set HC mode also for SDP 500mA, if not set, AICL will not run, so |
| * compass compensation is not accurate with SDP 500mA, it is because |
| * we use AICL to calculate compass compensation, and AICL is 1.5A |
| * in default. |
| */ |
| rc = smbchg_set_high_usb_chg_current(chip, current_ma); |
| if (rc < 0) { |
| pr_err("Couldn't set %dmA rc = %d\n", current_ma, rc); |
| goto out; |
| } |
| break; |
| case POWER_SUPPLY_TYPE_USB_CDP: |
| if (current_ma < CURRENT_1500_MA) { |
| /* use override for CDP */ |
| rc = smbchg_masked_write(chip, |
| chip->usb_chgpth_base + CMD_IL, |
| ICL_OVERRIDE_BIT, ICL_OVERRIDE_BIT); |
| if (rc < 0) |
| pr_err("Couldn't set override rc = %d\n", rc); |
| } |
| /* fall through */ |
| default: |
| rc = smbchg_set_high_usb_chg_current(chip, current_ma); |
| if (rc < 0) |
| pr_err("Couldn't set %dmA rc = %d\n", current_ma, rc); |
| break; |
| } |
| |
| out: |
| pr_smb(PR_STATUS, "usb type = %s current set to %d mA\n", |
| usb_type_name, chip->usb_max_current_ma); |
| return rc; |
| } |
| |
| #define USBIN_HVDCP_STS 0x0C |
| #define USBIN_HVDCP_SEL_BIT BIT(4) |
| #define USBIN_HVDCP_SEL_9V_BIT BIT(1) |
| static int smbchg_get_min_parallel_current_ma(struct smbchg_chip *chip) |
| { |
| int rc; |
| u8 reg; |
| |
| rc = smbchg_read(chip, ®, |
| chip->usb_chgpth_base + USBIN_HVDCP_STS, 1); |
| if (rc < 0) { |
| dev_err(chip->dev, "Couldn't read usb status rc = %d\n", rc); |
| return 0; |
| } |
| if ((reg & USBIN_HVDCP_SEL_BIT) && (reg & USBIN_HVDCP_SEL_9V_BIT)) |
| return chip->parallel.min_9v_current_thr_ma; |
| return chip->parallel.min_current_thr_ma; |
| } |
| |
| #define ICL_STS_1_REG 0x7 |
| #define ICL_STS_2_REG 0x9 |
| #define ICL_STS_MASK 0x1F |
| #define AICL_SUSP_BIT BIT(6) |
| #define AICL_STS_BIT BIT(5) |
| #define USBIN_SUSPEND_STS_BIT BIT(3) |
| #define USBIN_ACTIVE_PWR_SRC_BIT BIT(1) |
| #define DCIN_ACTIVE_PWR_SRC_BIT BIT(0) |
| /* change parallel reenable timer to 2.5 seconds */ |
| #define PARALLEL_REENABLE_TIMER_MS 2500 |
| static bool smbchg_is_parallel_usb_ok(struct smbchg_chip *chip) |
| { |
| int min_current_thr_ma, rc, type; |
| ktime_t kt_since_last_disable; |
| u8 reg; |
| enum typec_current_mode current_mode = TYPEC_CURRENT_MODE_DEFAULT; |
| |
| if (!smbchg_parallel_en || !chip->parallel_charger_detected) { |
| pr_smb(PR_STATUS, "Parallel charging not enabled\n"); |
| return false; |
| } |
| |
| kt_since_last_disable = ktime_sub(ktime_get_boottime(), |
| chip->parallel.last_disabled); |
| if (chip->parallel.current_max_ma == 0 |
| && chip->parallel.enabled_once |
| && ktime_to_ms(kt_since_last_disable) |
| < PARALLEL_REENABLE_TIMER_MS) { |
| pr_smb(PR_STATUS, "Only been %lld since disable, skipping\n", |
| ktime_to_ms(kt_since_last_disable)); |
| return false; |
| } |
| |
| /* Skip charge type check when vfloat_mv isn't set as cfg_vfloat_mv */ |
| if ((get_prop_charge_type(chip) != POWER_SUPPLY_CHARGE_TYPE_FAST) |
| && (chip->vfloat_mv == chip->cfg_vfloat_mv)) { |
| pr_smb(PR_STATUS, "Not in fast charge, skipping\n"); |
| return false; |
| } |
| |
| if (get_prop_batt_health(chip) != POWER_SUPPLY_HEALTH_GOOD) { |
| pr_smb(PR_STATUS, "JEITA active, skipping\n"); |
| return false; |
| } |
| |
| rc = smbchg_read(chip, ®, chip->misc_base + IDEV_STS, 1); |
| if (rc < 0) { |
| dev_err(chip->dev, "Couldn't read status 5 rc = %d\n", rc); |
| return false; |
| } |
| |
| type = get_type(reg); |
| if (get_usb_supply_type(type) == POWER_SUPPLY_TYPE_USB_CDP) { |
| pr_smb(PR_STATUS, "CDP adapter, skipping\n"); |
| return false; |
| } |
| |
| current_mode = typec_current_mode_detect(); |
| if ((get_usb_supply_type(type) == POWER_SUPPLY_TYPE_USB) |
| && ((current_mode == TYPEC_CURRENT_MODE_DEFAULT) |
| || (current_mode == TYPEC_CURRENT_MODE_UNSPPORTED))) { |
| pr_smb(PR_STATUS, "SDP adapter, skipping\n"); |
| return false; |
| } |
| |
| rc = smbchg_read(chip, ®, |
| chip->usb_chgpth_base + ICL_STS_2_REG, 1); |
| if (rc) { |
| dev_err(chip->dev, "Could not read usb icl sts 2: %d\n", rc); |
| return false; |
| } |
| |
| /* |
| * If USBIN is suspended or not the active power source, do not enable |
| * parallel charging. The device may be charging off of DCIN. |
| */ |
| if (!!(reg & USBIN_SUSPEND_STS_BIT) || |
| !(reg & USBIN_ACTIVE_PWR_SRC_BIT)) { |
| pr_smb(PR_STATUS, "USB not active power source: %02x\n", reg); |
| return false; |
| } |
| |
| min_current_thr_ma = smbchg_get_min_parallel_current_ma(chip); |
| if (min_current_thr_ma <= 0) { |
| pr_smb(PR_STATUS, "parallel charger unavailable for thr: %d\n", |
| min_current_thr_ma); |
| return false; |
| } |
| if (chip->usb_tl_current_ma < min_current_thr_ma) { |
| pr_smb(PR_STATUS, "Weak USB chg skip enable: %d < %d\n", |
| chip->usb_tl_current_ma, min_current_thr_ma); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| #define FCC_CFG 0xF2 |
| #define FCC_500MA_VAL 0x4 |
| #define FCC_MASK SMB_MASK(4, 0) |
| static int smbchg_set_fastchg_current_raw(struct smbchg_chip *chip, |
| int current_ma) |
| { |
| int i, rc; |
| u8 cur_val; |
| |
| /* the fcc enumerations are the same as the usb currents */ |
| for (i = ARRAY_SIZE(usb_current_table) - 1; i >= 0; i--) { |
| if (current_ma >= usb_current_table[i]) |
| break; |
| } |
| if (i < 0) { |
| dev_err(chip->dev, |
| "Cannot find %dma current_table using %d\n", |
| current_ma, CURRENT_500_MA); |
| |
| rc = smbchg_sec_masked_write(chip, chip->chgr_base + FCC_CFG, |
| FCC_MASK, |
| FCC_500MA_VAL); |
| if (rc < 0) |
| dev_err(chip->dev, "Couldn't set %dmA rc=%d\n", |
| CURRENT_500_MA, rc); |
| else |
| chip->fastchg_current_ma = 500; |
| return rc; |
| } |
| |
| cur_val = i & FCC_MASK; |
| rc = smbchg_sec_masked_write(chip, chip->chgr_base + FCC_CFG, |
| FCC_MASK, cur_val); |
| if (rc < 0) { |
| dev_err(chip->dev, "cannot write to fcc cfg rc = %d\n", rc); |
| return rc; |
| } |
| pr_smb(PR_STATUS, "fastcharge current requested %d, set to %d\n", |
| current_ma, usb_current_table[cur_val]); |
| |
| chip->fastchg_current_ma = usb_current_table[cur_val]; |
| return rc; |
| } |
| |
| static int smbchg_set_fastchg_current(struct smbchg_chip *chip, |
| int current_ma) |
| { |
| int rc = 0; |
| |
| mutex_lock(&chip->fcc_lock); |
| if (chip->sw_esr_pulse_en) |
| current_ma = 300; |
| /* If the requested FCC is same, do not configure it again */ |
| if (current_ma == chip->fastchg_current_ma) { |
| pr_smb(PR_STATUS, "not configuring FCC current: %d FCC: %d\n", |
| current_ma, chip->fastchg_current_ma); |
| goto out; |
| } |
| rc = smbchg_set_fastchg_current_raw(chip, current_ma); |
| out: |
| mutex_unlock(&chip->fcc_lock); |
| return rc; |
| } |
| |
| static int smbchg_parallel_usb_charging_en(struct smbchg_chip *chip, bool en) |
| { |
| struct power_supply *parallel_psy = get_parallel_psy(chip); |
| union power_supply_propval pval = {0, }; |
| |
| if (!parallel_psy || !chip->parallel_charger_detected) |
| return 0; |
| |
| pval.intval = en; |
| return parallel_psy->set_property(parallel_psy, |
| POWER_SUPPLY_PROP_CHARGING_ENABLED, &pval); |
| } |
| |
| static int smbchg_sw_esr_pulse_en(struct smbchg_chip *chip, bool en) |
| { |
| int rc; |
| |
| chip->sw_esr_pulse_en = en; |
| rc = smbchg_set_fastchg_current(chip, chip->target_fastchg_current_ma); |
| if (rc) |
| return rc; |
| rc = smbchg_parallel_usb_charging_en(chip, !en); |
| return rc; |
| } |
| |
| #define USB_AICL_CFG 0xF3 |
| #define AICL_EN_BIT BIT(2) |
| static void smbchg_rerun_aicl(struct smbchg_chip *chip) |
| { |
| pr_smb(PR_STATUS, "Rerunning AICL...\n"); |
| smbchg_sec_masked_write(chip, chip->usb_chgpth_base + USB_AICL_CFG, |
| AICL_EN_BIT, 0); |
| /* Add a delay so that AICL successfully clears */ |
| msleep(50); |
| smbchg_sec_masked_write(chip, chip->usb_chgpth_base + USB_AICL_CFG, |
| AICL_EN_BIT, AICL_EN_BIT); |
| } |
| |
| static void taper_irq_en(struct smbchg_chip *chip, bool en) |
| { |
| mutex_lock(&chip->taper_irq_lock); |
| if (en != chip->taper_irq_enabled) { |
| if (en) { |
| enable_irq(chip->taper_irq); |
| enable_irq_wake(chip->taper_irq); |
| } else { |
| disable_irq_wake(chip->taper_irq); |
| disable_irq_nosync(chip->taper_irq); |
| } |
| chip->taper_irq_enabled = en; |
| } |
| mutex_unlock(&chip->taper_irq_lock); |
| } |
| |
| static void smbchg_parallel_usb_disable(struct smbchg_chip *chip) |
| { |
| struct power_supply *parallel_psy = get_parallel_psy(chip); |
| |
| if (!parallel_psy || !chip->parallel_charger_detected) |
| return; |
| pr_smb(PR_STATUS, "disabling parallel charger\n"); |
| chip->parallel.last_disabled = ktime_get_boottime(); |
| taper_irq_en(chip, false); |
| chip->parallel.initial_aicl_ma = 0; |
| chip->parallel.current_max_ma = 0; |
| power_supply_set_current_limit(parallel_psy, |
| SUSPEND_CURRENT_MA * 1000); |
| power_supply_set_present(parallel_psy, false); |
| if (chip->batt_warm) |
| chip->target_fastchg_current_ma = chip->warm_current_ma; |
| else if (chip->batt_cool) |
| chip->target_fastchg_current_ma = chip->cool_current_ma; |
| else |
| chip->target_fastchg_current_ma = chip->cfg_fastchg_current_ma; |
| smbchg_set_fastchg_current(chip, chip->target_fastchg_current_ma); |
| chip->usb_tl_current_ma = |
| calc_thermal_limited_current(chip, chip->usb_target_current_ma); |
| smbchg_set_usb_current_max(chip, chip->usb_tl_current_ma); |
| smbchg_rerun_aicl(chip); |
| } |
| |
| #define PARALLEL_TAPER_MAX_TRIES 3 |
| #define PARALLEL_FCC_PERCENT_REDUCTION 75 |
| #define MINIMUM_PARALLEL_FCC_MA 500 |
| #define CHG_ERROR_BIT BIT(0) |
| #define BAT_TAPER_MODE_BIT BIT(6) |
| static void smbchg_parallel_usb_taper(struct smbchg_chip *chip) |
| { |
| struct power_supply *parallel_psy = get_parallel_psy(chip); |
| union power_supply_propval pval = {0, }; |
| int parallel_fcc_ma, tries = 0; |
| u8 reg = 0; |
| |
| if (!parallel_psy || !chip->parallel_charger_detected) |
| return; |
| |
| smbchg_stay_awake(chip, PM_PARALLEL_TAPER); |
| try_again: |
| mutex_lock(&chip->parallel.lock); |
| if (chip->parallel.current_max_ma == 0) { |
| pr_smb(PR_STATUS, "Not parallel charging, skipping\n"); |
| goto done; |
| } |
| parallel_psy->get_property(parallel_psy, |
| POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, &pval); |
| tries += 1; |
| parallel_fcc_ma = pval.intval / 1000; |
| pr_smb(PR_STATUS, "try #%d parallel charger fcc = %d\n", |
| tries, parallel_fcc_ma); |
| if (parallel_fcc_ma < MINIMUM_PARALLEL_FCC_MA |
| || tries > PARALLEL_TAPER_MAX_TRIES) { |
| smbchg_parallel_usb_disable(chip); |
| goto done; |
| } |
| pval.intval = ((parallel_fcc_ma |
| * PARALLEL_FCC_PERCENT_REDUCTION) / 100); |
| pr_smb(PR_STATUS, "reducing FCC of parallel charger to %d\n", |
| pval.intval); |
| /* Change it to uA */ |
| pval.intval *= 1000; |
| parallel_psy->set_property(parallel_psy, |
| POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, &pval); |
| /* |
| * sleep here for 100 ms in order to make sure the charger has a chance |
| * to go back into constant current charging |
| */ |
| mutex_unlock(&chip->parallel.lock); |
| msleep(100); |
| |
| mutex_lock(&chip->parallel.lock); |
| if (chip->parallel.current_max_ma == 0) { |
| pr_smb(PR_STATUS, "Not parallel charging, skipping\n"); |
| goto done; |
| } |
| smbchg_read(chip, ®, chip->chgr_base + RT_STS, 1); |
| if (reg & BAT_TAPER_MODE_BIT) { |
| mutex_unlock(&chip->parallel.lock); |
| goto try_again; |
| } |
| taper_irq_en(chip, true); |
| done: |
| mutex_unlock(&chip->parallel.lock); |
| smbchg_relax(chip, PM_PARALLEL_TAPER); |
| } |
| |
| static bool smbchg_is_aicl_complete(struct smbchg_chip *chip) |
| { |
| int rc; |
| u8 reg; |
| |
| rc = smbchg_read(chip, ®, |
| chip->usb_chgpth_base + ICL_STS_1_REG, 1); |
| if (rc) { |
| dev_err(chip->dev, "Could not read usb icl sts 1: %d\n", rc); |
| return true; |
| } |
| return (reg & AICL_STS_BIT) != 0; |
| } |
| |
| static int smbchg_get_aicl_level_ma(struct smbchg_chip *chip) |
| { |
| int rc; |
| u8 reg; |
| |
| rc = smbchg_read(chip, ®, |
| chip->usb_chgpth_base + ICL_STS_1_REG, 1); |
| if (rc) { |
| dev_err(chip->dev, "Could not read usb icl sts 1: %d\n", rc); |
| return 0; |
| } |
| if (reg & AICL_SUSP_BIT) { |
| pr_warn("AICL suspended: %02x\n", reg); |
| return 0; |
| } |
| reg &= ICL_STS_MASK; |
| if (reg >= ARRAY_SIZE(usb_current_table)) { |
| pr_warn("invalid AICL value: %02x\n", reg); |
| return 0; |
| } |
| return usb_current_table[reg]; |
| } |
| |
| #define PARALLEL_CHG_THRESHOLD_CURRENT 1800 |
| static void smbchg_parallel_usb_enable(struct smbchg_chip *chip) |
| { |
| struct power_supply *parallel_psy = get_parallel_psy(chip); |
| union power_supply_propval pval = {0, }; |
| int current_limit_ma, parallel_cl_ma, total_current_ma; |
| int new_parallel_cl_ma, min_current_thr_ma, rc; |
| int batt_voltage_mv; |
| u8 reg; |
| |
| if (!parallel_psy || !chip->parallel_charger_detected) |
| return; |
| |
| pr_smb(PR_STATUS, "Attempting to enable parallel charger\n"); |
| /* Suspend the parallel charger if the charging current is < 1800 mA */ |
| if (chip->cfg_fastchg_current_ma < PARALLEL_CHG_THRESHOLD_CURRENT) { |
| pr_smb(PR_STATUS, "suspend parallel charger as FCC is %d\n", |
| chip->cfg_fastchg_current_ma); |
| goto disable_parallel; |
| } |
| min_current_thr_ma = smbchg_get_min_parallel_current_ma(chip); |
| if (min_current_thr_ma <= 0) { |
| pr_smb(PR_STATUS, "parallel charger unavailable for thr: %d\n", |
| min_current_thr_ma); |
| goto disable_parallel; |
| } |
| |
| current_limit_ma = smbchg_get_aicl_level_ma(chip); |
| if (current_limit_ma <= 0) |
| goto disable_parallel; |
| |
| if (chip->parallel.initial_aicl_ma == 0) { |
| if (current_limit_ma < min_current_thr_ma) { |
| pr_smb(PR_STATUS, "Initial AICL very low: %d < %d\n", |
| current_limit_ma, min_current_thr_ma); |
| goto disable_parallel; |
| } |
| chip->parallel.initial_aicl_ma = current_limit_ma; |
| } |
| |
| /* |
| * Use the previous set current from the parallel charger. |
| * Treat 2mA as 0 because that is the suspend current setting |
| */ |
| parallel_cl_ma = chip->parallel.current_max_ma; |
| if (parallel_cl_ma <= SUSPEND_CURRENT_MA) |
| parallel_cl_ma = 0; |
| |
| /* |
| * Set the parallel charge path's input current limit (ICL) |
| * to the total current / 2 |
| */ |
| total_current_ma = current_limit_ma + parallel_cl_ma; |
| |
| if (total_current_ma < chip->parallel.initial_aicl_ma |
| - chip->parallel.allowed_lowering_ma) { |
| pr_smb(PR_STATUS, |
| "Too little total current : %d (%d + %d) < %d - %d\n", |
| total_current_ma, |
| current_limit_ma, parallel_cl_ma, |
| chip->parallel.initial_aicl_ma, |
| chip->parallel.allowed_lowering_ma); |
| goto disable_parallel; |
| } |
| |
| rc = power_supply_set_voltage_limit(parallel_psy, chip->vfloat_mv + 50); |
| if (rc) { |
| dev_err(chip->dev, "Couldn't set float voltage on parallel psy rc: %d\n", |
| rc); |
| goto disable_parallel; |
| } |
| chip->target_fastchg_current_ma = chip->cfg_fastchg_current_ma / 2; |
| /* |
| * When the batt voltage is above the current_stage_thr_mv, |
| * limit parallel charge current to 0.7C, or set a lower |
| * vfloat_mv and call the work to check charge current. It |
| * will adjust the vfloat_mv back to the normal value with |
| * the conditions satisfied. |
| */ |
| if (!chip->parallel_voltage_checked && chip->current_stage_thr_mv > 0) { |
| rc = smbchg_read(chip, ®, |
| chip->usb_chgpth_base + USBIN_HVDCP_STS, 1); |
| if (rc < 0) { |
| dev_err(chip->dev, "Couldn't read hvdcp status rc = %d\n", rc); |
| goto disable_parallel; |
| } |
| if ((typec_current_mode_detect() == TYPEC_CURRENT_MODE_HIGH) |
| || (reg & USBIN_HVDCP_SEL_BIT)) { |
| set_property_on_fg(chip, |
| POWER_SUPPLY_PROP_UPDATE_NOW, 1); |
| batt_voltage_mv = |
| get_prop_batt_voltage_now(chip) / 1000; |
| |
| if (batt_voltage_mv > chip->current_stage_thr_mv |
| - chip->current_stage_delta_mv) { |
| chip->parallel_current_limited = 1; |
| } else { |
| smbchg_float_voltage_set(chip, |
| chip->current_stage_thr_mv); |
| schedule_delayed_work(&chip->current_stage_work, |
| msecs_to_jiffies(STAGE_WORK_DELAY_MS)); |
| } |
| } |
| chip->parallel_voltage_checked = 1; |
| } |
| if (chip->parallel_current_limited) |
| chip->target_fastchg_current_ma = chip->current_stage_ma / 2; |
| |
| smbchg_set_fastchg_current(chip, chip->target_fastchg_current_ma); |
| pval.intval = chip->target_fastchg_current_ma * 1000; |
| parallel_psy->set_property(parallel_psy, |
| POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, &pval); |
| |
| chip->parallel.enabled_once = true; |
| new_parallel_cl_ma = total_current_ma / 2; |
| |
| if (new_parallel_cl_ma == parallel_cl_ma) { |
| pr_smb(PR_STATUS, |
| "AICL at %d, old ICL: %d new ICL: %d, skipping\n", |
| current_limit_ma, parallel_cl_ma, new_parallel_cl_ma); |
| return; |
| } else { |
| pr_smb(PR_STATUS, "AICL at %d, old ICL: %d new ICL: %d\n", |
| current_limit_ma, parallel_cl_ma, new_parallel_cl_ma); |
| } |
| |
| taper_irq_en(chip, true); |
| chip->parallel.current_max_ma = new_parallel_cl_ma; |
| power_supply_set_present(parallel_psy, true); |
| smbchg_set_usb_current_max(chip, chip->parallel.current_max_ma); |
| power_supply_set_current_limit(parallel_psy, |
| chip->parallel.current_max_ma * 1000); |
| return; |
| |
| disable_parallel: |
| if (chip->parallel.current_max_ma != 0) { |
| pr_smb(PR_STATUS, "disabling parallel charger\n"); |
| smbchg_parallel_usb_disable(chip); |
| } else if (chip->cfg_fastchg_current_ma != |
| chip->target_fastchg_current_ma) { |
| /* There is a possibility that parallel charging is enabled |
| * and a weak charger is connected, AICL result will be |
| * lower than the min_current_thr_ma. In those cases, we |
| * should fall back to configure the FCC of main charger. |
| */ |
| rc = smbchg_set_fastchg_current(chip, |
| chip->cfg_fastchg_current_ma); |
| if (rc) |
| pr_err("Couldn't set fastchg current rc: %d\n", |
| rc); |
| else |
| chip->target_fastchg_current_ma = |
| chip->cfg_fastchg_current_ma; |
| } |
| } |
| |
| static void smbchg_parallel_usb_en_work(struct work_struct *work) |
| { |
| struct smbchg_chip *chip = container_of(work, |
| struct smbchg_chip, |
| parallel_en_work.work); |
| |
| smbchg_relax(chip, PM_PARALLEL_CHECK); |
| mutex_lock(&chip->parallel.lock); |
| if (smbchg_is_parallel_usb_ok(chip)) { |
| smbchg_parallel_usb_enable(chip); |
| } else if (chip->parallel.current_max_ma != 0) { |
| pr_smb(PR_STATUS, "parallel charging unavailable\n"); |
| smbchg_parallel_usb_disable(chip); |
| } |
| mutex_unlock(&chip->parallel.lock); |
| } |
| |
| #define PARALLEL_CHARGER_EN_DELAY_MS 3500 |
| static void smbchg_parallel_usb_check_ok(struct smbchg_chip *chip) |
| { |
| struct power_supply *parallel_psy = get_parallel_psy(chip); |
| |
| if (!parallel_psy || !chip->parallel_charger_detected) |
| return; |
| mutex_lock(&chip->parallel.lock); |
| if (smbchg_is_parallel_usb_ok(chip)) { |
| smbchg_stay_awake(chip, PM_PARALLEL_CHECK); |
| schedule_delayed_work( |
| &chip->parallel_en_work, |
| msecs_to_jiffies(PARALLEL_CHARGER_EN_DELAY_MS)); |
| } else if (chip->parallel.current_max_ma != 0) { |
| pr_smb(PR_STATUS, "parallel charging unavailable\n"); |
| smbchg_parallel_usb_disable(chip); |
| } |
| mutex_unlock(&chip->parallel.lock); |
| } |
| |
| static int smbchg_usb_en(struct smbchg_chip *chip, bool enable, |
| enum enable_reason reason) |
| { |
| bool changed = false; |
| int rc = smbchg_primary_usb_en(chip, enable, reason, &changed); |
| |
| if (changed) |
| smbchg_parallel_usb_check_ok(chip); |
| return rc; |
| } |
| |
| static int smbchg_set_fastchg_current_user(struct smbchg_chip *chip, |
| int current_ma) |
| { |
| int rc = 0; |
| |
| mutex_lock(&chip->parallel.lock); |
| pr_smb(PR_STATUS, "User setting FCC to %d\n", current_ma); |
| chip->cfg_fastchg_current_ma = current_ma; |
| if (smbchg_is_parallel_usb_ok(chip)) { |
| smbchg_parallel_usb_enable(chip); |
| } else { |
| if (chip->parallel.current_max_ma != 0) { |
| /* |
| * If parallel charging is not available, disable it. |
| * FCC for main charger will be configured in that. |
| */ |
| pr_smb(PR_STATUS, "parallel charging unavailable\n"); |
| smbchg_parallel_usb_disable(chip); |
| goto out; |
| } |
| rc = smbchg_set_fastchg_current(chip, |
| chip->cfg_fastchg_current_ma); |
| if (rc) |
| pr_err("Couldn't set fastchg current rc: %d\n", |
| rc); |
| } |
| out: |
| mutex_unlock(&chip->parallel.lock); |
| return rc; |
| } |
| |
| static struct ilim_entry *smbchg_wipower_find_entry(struct smbchg_chip *chip, |
| struct ilim_map *map, int uv) |
| { |
| int i; |
| struct ilim_entry *ret = &(chip->wipower_default.entries[0]); |
| |
| for (i = 0; i < map->num; i++) { |
| if (is_between(map->entries[i].vmin_uv, map->entries[i].vmax_uv, |
| uv)) |
| ret = &map->entries[i]; |
| } |
| return ret; |
| } |
| |
| static int ilim_ma_table[] = { |
| 300, |
| 400, |
| 450, |
| 475, |
| 500, |
| 550, |
| 600, |
| 650, |
| 700, |
| 900, |
| 950, |
| 1000, |
| 1100, |
| 1200, |
| 1400, |
| 1450, |
| 1500, |
| 1600, |
| 1800, |
| 1850, |
| 1880, |
| 1910, |
| 1930, |
| 1950, |
| 1970, |
| 2000, |
| }; |
| |
| #define ZIN_ICL_PT 0xFC |
| #define ZIN_ICL_LV 0xFD |
| #define ZIN_ICL_HV 0xFE |
| #define ZIN_ICL_MASK SMB_MASK(4, 0) |
| static int smbchg_dcin_ilim_config(struct smbchg_chip *chip, int offset, int ma) |
| { |
| int i, rc; |
| |
| for (i = ARRAY_SIZE(ilim_ma_table) - 1; i >= 0; i--) { |
| if (ma >= ilim_ma_table[i]) |
| break; |
| } |
| |
| if (i < 0) |
| i = 0; |
| |
| rc = smbchg_sec_masked_write(chip, chip->bat_if_base + offset, |
| ZIN_ICL_MASK, i); |
| if (rc) |
| dev_err(chip->dev, "Couldn't write bat if offset %d value = %d rc = %d\n", |
| offset, i, rc); |
| return rc; |
| } |
| |
| static int smbchg_wipower_ilim_config(struct smbchg_chip *chip, |
| struct ilim_entry *ilim) |
| { |
| int rc = 0; |
| |
| if (chip->current_ilim.icl_pt_ma != ilim->icl_pt_ma) { |
| rc = smbchg_dcin_ilim_config(chip, ZIN_ICL_PT, ilim->icl_pt_ma); |
| if (rc) |
| dev_err(chip->dev, "failed to write batif offset %d %dma rc = %d\n", |
| ZIN_ICL_PT, ilim->icl_pt_ma, rc); |
| else |
| chip->current_ilim.icl_pt_ma = ilim->icl_pt_ma; |
| } |
| |
| if (chip->current_ilim.icl_lv_ma != ilim->icl_lv_ma) { |
| rc = smbchg_dcin_ilim_config(chip, ZIN_ICL_LV, ilim->icl_lv_ma); |
| if (rc) |
| dev_err(chip->dev, "failed to write batif offset %d %dma rc = %d\n", |
| ZIN_ICL_LV, ilim->icl_lv_ma, rc); |
| else |
| chip->current_ilim.icl_lv_ma = ilim->icl_lv_ma; |
| } |
| |
| if (chip->current_ilim.icl_hv_ma != ilim->icl_hv_ma) { |
| rc = smbchg_dcin_ilim_config(chip, ZIN_ICL_HV, ilim->icl_hv_ma); |
| if (rc) |
| dev_err(chip->dev, "failed to write batif offset %d %dma rc = %d\n", |
| ZIN_ICL_HV, ilim->icl_hv_ma, rc); |
| else |
| chip->current_ilim.icl_hv_ma = ilim->icl_hv_ma; |
| } |
| return rc; |
| } |
| |
| static void btm_notify_dcin(enum qpnp_tm_state state, void *ctx); |
| static int smbchg_wipower_dcin_btm_configure(struct smbchg_chip *chip, |
| struct ilim_entry *ilim) |
| { |
| int rc; |
| |
| if (ilim->vmin_uv == chip->current_ilim.vmin_uv |
| && ilim->vmax_uv == chip->current_ilim.vmax_uv) |
| return 0; |
| |
| chip->param.channel = DCIN; |
| chip->param.btm_ctx = chip; |
| if (wipower_dcin_interval < ADC_MEAS1_INTERVAL_0MS) |
| wipower_dcin_interval = ADC_MEAS1_INTERVAL_0MS; |
| |
| if (wipower_dcin_interval > ADC_MEAS1_INTERVAL_16S) |
| wipower_dcin_interval = ADC_MEAS1_INTERVAL_16S; |
| |
| chip->param.timer_interval = wipower_dcin_interval; |
| chip->param.threshold_notification = &btm_notify_dcin; |
| chip->param.high_thr = ilim->vmax_uv + wipower_dcin_hyst_uv; |
| chip->param.low_thr = ilim->vmin_uv - wipower_dcin_hyst_uv; |
| chip->param.state_request = ADC_TM_HIGH_LOW_THR_ENABLE; |
| rc = qpnp_vadc_channel_monitor(chip->vadc_dev, &chip->param); |
| if (rc) { |
| dev_err(chip->dev, "Couldn't configure btm for dcin rc = %d\n", |
| rc); |
| } else { |
| chip->current_ilim.vmin_uv = ilim->vmin_uv; |
| chip->current_ilim.vmax_uv = ilim->vmax_uv; |
| pr_smb(PR_STATUS, "btm ilim = (%duV %duV %dmA %dmA %dmA)\n", |
| ilim->vmin_uv, ilim->vmax_uv, |
| ilim->icl_pt_ma, ilim->icl_lv_ma, ilim->icl_hv_ma); |
| } |
| return rc; |
| } |
| |
| static int smbchg_wipower_icl_configure(struct smbchg_chip *chip, |
| int dcin_uv, bool div2) |
| { |
| int rc = 0; |
| struct ilim_map *map = div2 ? &chip->wipower_div2 : &chip->wipower_pt; |
| struct ilim_entry *ilim = smbchg_wipower_find_entry(chip, map, dcin_uv); |
| |
| rc = smbchg_wipower_ilim_config(chip, ilim); |
| if (rc) { |
| dev_err(chip->dev, "failed to config ilim rc = %d, dcin_uv = %d , div2 = %d, ilim = (%duV %duV %dmA %dmA %dmA)\n", |
| rc, dcin_uv, div2, |
| ilim->vmin_uv, ilim->vmax_uv, |
| ilim->icl_pt_ma, ilim->icl_lv_ma, ilim->icl_hv_ma); |
| return rc; |
| } |
| |
| rc = smbchg_wipower_dcin_btm_configure(chip, ilim); |
| if (rc) { |
| dev_err(chip->dev, "failed to config btm rc = %d, dcin_uv = %d , div2 = %d, ilim = (%duV %duV %dmA %dmA %dmA)\n", |
| rc, dcin_uv, div2, |
| ilim->vmin_uv, ilim->vmax_uv, |
| ilim->icl_pt_ma, ilim->icl_lv_ma, ilim->icl_hv_ma); |
| return rc; |
| } |
| chip->wipower_configured = true; |
| return 0; |
| } |
| |
| static void smbchg_wipower_icl_deconfigure(struct smbchg_chip *chip) |
| { |
| int rc; |
| struct ilim_entry *ilim = &(chip->wipower_default.entries[0]); |
| |
| if (!chip->wipower_configured) |
| return; |
| |
| rc = smbchg_wipower_ilim_config(chip, ilim); |
| if (rc) |
| dev_err(chip->dev, "Couldn't config default ilim rc = %d\n", |
| rc); |
| |
| rc = qpnp_vadc_end_channel_monitor(chip->vadc_dev); |
| if (rc) |
| dev_err(chip->dev, "Couldn't de configure btm for dcin rc = %d\n", |
| rc); |
| |
| chip->wipower_configured = false; |
| chip->current_ilim.vmin_uv = 0; |
| chip->current_ilim.vmax_uv = 0; |
| chip->current_ilim.icl_pt_ma = ilim->icl_pt_ma; |
| chip->current_ilim.icl_lv_ma = ilim->icl_lv_ma; |
| chip->current_ilim.icl_hv_ma = ilim->icl_hv_ma; |
| pr_smb(PR_WIPOWER, "De config btm\n"); |
| } |
| |
| #define FV_STS 0x0C |
| #define DIV2_ACTIVE BIT(7) |
| static void __smbchg_wipower_check(struct smbchg_chip *chip) |
| { |
| int chg_type; |
| bool usb_present, dc_present; |
| int rc; |
| int dcin_uv; |
| bool div2; |
| struct qpnp_vadc_result adc_result; |
| u8 reg; |
| |
| if (!wipower_dyn_icl_en) { |
| smbchg_wipower_icl_deconfigure(chip); |
| return; |
| } |
| |
| chg_type = get_prop_charge_type(chip); |
| usb_present = is_usb_present(chip); |
| dc_present = is_dc_present(chip); |
| if (chg_type != POWER_SUPPLY_CHARGE_TYPE_NONE |
| && !usb_present |
| && dc_present |
| && chip->dc_psy_type == POWER_SUPPLY_TYPE_WIPOWER) { |
| rc = qpnp_vadc_read(chip->vadc_dev, DCIN, &adc_result); |
| if (rc) { |
| pr_smb(PR_STATUS, "error DCIN read rc = %d\n", rc); |
| return; |
| } |
| dcin_uv = adc_result.physical; |
| |
| /* check div_by_2 */ |
| rc = smbchg_read(chip, ®, chip->chgr_base + FV_STS, 1); |
| if (rc) { |
| pr_smb(PR_STATUS, "error DCIN read rc = %d\n", rc); |
| return; |
| } |
| div2 = !!(reg & DIV2_ACTIVE); |
| |
| pr_smb(PR_WIPOWER, |
| "config ICL chg_type = %d usb = %d dc = %d dcin_uv(adc_code) = %d (0x%x) div2 = %d\n", |
| chg_type, usb_present, dc_present, dcin_uv, |
| adc_result.adc_code, div2); |
| smbchg_wipower_icl_configure(chip, dcin_uv, div2); |
| } else { |
| pr_smb(PR_WIPOWER, |
| "deconfig ICL chg_type = %d usb = %d dc = %d\n", |
| chg_type, usb_present, dc_present); |
| smbchg_wipower_icl_deconfigure(chip); |
| } |
| } |
| |
| static void smbchg_wipower_check(struct smbchg_chip *chip) |
| { |
| if (!chip->wipower_dyn_icl_avail) |
| return; |
| |
| mutex_lock(&chip->wipower_config); |
| __smbchg_wipower_check(chip); |
| mutex_unlock(&chip->wipower_config); |
| } |
| |
| static void btm_notify_dcin(enum qpnp_tm_state state, void *ctx) |
| { |
| struct smbchg_chip *chip = ctx; |
| |
| mutex_lock(&chip->wipower_config); |
| pr_smb(PR_WIPOWER, "%s state\n", |
| state == ADC_TM_LOW_STATE ? "low" : "high"); |
| chip->current_ilim.vmin_uv = 0; |
| chip->current_ilim.vmax_uv = 0; |
| __smbchg_wipower_check(chip); |
| mutex_unlock(&chip->wipower_config); |
| } |
| |
| static int force_dcin_icl_write(void *data, u64 val) |
| { |
| struct smbchg_chip *chip = data; |
| |
| smbchg_wipower_check(chip); |
| return 0; |
| } |
| DEFINE_SIMPLE_ATTRIBUTE(force_dcin_icl_ops, NULL, |
| force_dcin_icl_write, "0x%02llx\n"); |
| |
| /* |
| * set the dc charge path's maximum allowed current draw |
| * that may be limited by the system's thermal level |
| */ |
| static int smbchg_set_thermal_limited_dc_current_max(struct smbchg_chip *chip, |
| int current_ma) |
| { |
| current_ma = calc_thermal_limited_current(chip, current_ma); |
| return smbchg_set_dc_current_max(chip, current_ma); |
| } |
| |
| /* |
| * get input current according to type-c protocol |
| */ |
| #define FLOAT_CURRENT 1000 |
| #define MEDIUM_CURRENT 1500 |
| #define TYPEC_HIGH_CURRENT 3000 |
| static int get_typec_input_current(void) |
| { |
| int typec_current = 0; |
| enum typec_current_mode current_mode = TYPEC_CURRENT_MODE_DEFAULT; |
| |
| current_mode = typec_current_mode_detect(); |
| switch (current_mode) { |
| case TYPEC_CURRENT_MODE_HIGH: |
| typec_current = TYPEC_HIGH_CURRENT; |
| break; |
| case TYPEC_CURRENT_MODE_MID: |
| typec_current = MEDIUM_CURRENT; |
| break; |
| case TYPEC_CURRENT_MODE_DEFAULT: |
| default: |
| typec_current = CURRENT_500_MA; |
| break; |
| } |
| |
| return typec_current; |
| } |
| |
| /* |
| * determine the input current according to BC1.2 and type-c protocol |
| * typeC charger/usb with 10k ohm pull-up resistance is 3A; hvdcp is 1.8A; |
| * BC1.2 DCP with D+/- shorted is 1.5A; SDP 0.5A; floated charger is 1A |
| */ |
| static int determine_target_input_current(int current_ma) |
| { |
| int target_current = 0, typec_current = 0; |
| |
| typec_current = get_typec_input_current(); |
| |
| if (DEFAULT_USB_MA >= current_ma) { |
| /* for usb suspend or 100ma */ |
| target_current = current_ma; |
| } else { |
| /* typec high and medium current mode has high priority*/ |
| target_current = max(typec_current, current_ma); |
| } |
| |
| pr_info("target_current = %d\n", target_current); |
| |
| return target_current; |
| } |
| |
| static void resume_to_normal_charge_work(struct work_struct *work) |
| { |
| int rc, aicl_ma; |
| struct smbchg_chip *chip = container_of(work, |
| struct smbchg_chip, |
| resume_to_normal_work); |
| |
| if (chip->max_input_current_ma <= CURRENT_500_MA) |
| return; |
| |
| aicl_ma = smbchg_get_aicl_level_ma(chip); |
| |
| rc = smbchg_set_usb_current_max(chip, chip->max_input_current_ma); |
| if (rc) { |
| pr_err("Failed to set usb current max: %d\n", rc); |
| return; |
| } |
| |
| if (chip->usb_max_current_ma > aicl_ma && smbchg_is_aicl_complete(chip)) |
| smbchg_rerun_aicl(chip); |
| smbchg_parallel_usb_check_ok(chip); |
| } |
| |
| /* |
| * set the usb charge path's maximum allowed current draw |
| * that may be limited by the system's thermal level |
| */ |
| static int smbchg_set_thermal_limited_usb_current_max(struct smbchg_chip *chip, |
| int current_ma) |
| { |
| int rc, aicl_ma; |
| int target_ma = 0; |
| enum power_supply_type usb_supply_type; |
| char *usb_type_name = "null"; |
| |
| aicl_ma = smbchg_get_aicl_level_ma(chip); |
| /* determine the input current by typec protocol and BC1.2 */ |
| target_ma = determine_target_input_current(current_ma); |
| |
| read_usb_type(chip, &usb_type_name, &usb_supply_type); |
| if (usb_supply_type == POWER_SUPPLY_TYPE_USB) { |
| /* |
| * set ICL_OVERRIDE_BIT when use typec charger (D+/- are floated) |
| * or floated charger (floated charger is detected as SDP by pmi8994) |
| * or SDP USB. |
| */ |
| rc = smbchg_masked_write(chip, chip->usb_chgpth_base + CMD_IL, |
| ICL_OVERRIDE_BIT, ICL_OVERRIDE_BIT); |
| if (rc < 0) { |
| dev_err(chip->dev, "Couldn't set input limit cmd rc=%d\n", rc); |
| return rc; |
| } |
| } |
| |
| chip->usb_tl_current_ma = |
| calc_thermal_limited_current(chip, target_ma); |
| |
| /* save target input current to chip->max_input_current_ma, |
| * when charging recover from warm/cool to normal, |
| * use this input current to resume high current charging |
| */ |
| chip->max_input_current_ma = chip->usb_tl_current_ma; |
| |
| rc = smbchg_set_usb_current_max(chip, chip->usb_tl_current_ma); |
| if (rc) { |
| pr_err("Failed to set usb current max: %d\n", rc); |
| return rc; |
| } |
| |
| pr_smb(PR_STATUS, "AICL = %d, ICL = %d\n", |
| aicl_ma, chip->usb_max_current_ma); |
| if (chip->usb_max_current_ma > aicl_ma && smbchg_is_aicl_complete(chip)) |
| smbchg_rerun_aicl(chip); |
| smbchg_parallel_usb_check_ok(chip); |
| return rc; |
| } |
| |
| static int smbchg_system_temp_level_set(struct smbchg_chip *chip, |
| int lvl_sel) |
| { |
| int rc = 0; |
| int prev_therm_lvl; |
| int therm_ma, min_current_thr_ma; |
| struct power_supply *parallel_psy = get_parallel_psy(chip); |
| |
| if (!chip->thermal_mitigation) { |
| dev_err(chip->dev, "Thermal mitigation not supported\n"); |
| return -EINVAL; |
| } |
| |
| if (!chip->thermal_mitigation_hvdcp) { |
| dev_err(chip->dev, "HVDCP Thermal mitigation not supported\n"); |
| return -EINVAL; |
| } |
| |
| if (lvl_sel < 0) { |
| dev_err(chip->dev, "Unsupported level selected %d\n", lvl_sel); |
| return -EINVAL; |
| } |
| |
| if (lvl_sel >= chip->thermal_levels) { |
| dev_err(chip->dev, "Unsupported level selected %d forcing %d\n", |
| lvl_sel, chip->thermal_levels - 1); |
| lvl_sel = chip->thermal_levels - 1; |
| } |
| |
| if (lvl_sel == chip->therm_lvl_sel) |
| return 0; |
| |
| mutex_lock(&chip->current_change_lock); |
| prev_therm_lvl = chip->therm_lvl_sel; |
| chip->therm_lvl_sel = lvl_sel; |
| if (chip->therm_lvl_sel == (chip->thermal_levels - 1)) { |
| /* |
| * Disable charging if highest value selected by |
| * setting the DC and USB path in suspend |
| */ |
| rc = smbchg_dc_en(chip, false, REASON_THERMAL); |
| if (rc < 0) { |
| dev_err(chip->dev, |
| "Couldn't set dc suspend rc %d\n", rc); |
| goto out; |
| } |
| rc = smbchg_usb_en(chip, false, REASON_THERMAL); |
| if (rc < 0) { |
| dev_err(chip->dev, |
| "Couldn't set usb suspend rc %d\n", rc); |
| goto out; |
| } |
| goto out; |
| } |
| |
| /* |
| * If thermal-engine set therm_ma which is above min_current_thr_ma, |
| * reset chip->parallel.initial_aicl_ma and chip->parallel.current_max_ma |
| * to 0. if parallel charging is running, use new input |
| * current to re-run aicl to enable new parallel charging. |
| * if therm_ma is below min_current_thr_ma, disable parallel charging. |
| */ |
| if (chip->parallel.current_max_ma !=0) { |
| min_current_thr_ma = smbchg_get_min_parallel_current_ma(chip); |
| if (min_current_thr_ma <= 0) { |
| pr_smb(PR_STATUS, "parallel charger unavailable for thr: %d\n", |
| min_current_thr_ma); |
| goto out; |
| } |
| if (smbchg_is_hvdcp(chip)) |
| therm_ma = (int)chip->thermal_mitigation_hvdcp[chip->therm_lvl_sel]; |
| else |
| therm_ma = (int)chip->thermal_mitigation[chip->therm_lvl_sel]; |
| if (therm_ma < min_current_thr_ma) |
| smbchg_parallel_usb_disable(chip); |
| else { |
| chip->parallel.initial_aicl_ma = 0; |
| chip->parallel.current_max_ma = 0; |
| if (!parallel_psy || !chip->parallel_charger_detected) |
| goto out; |
| power_supply_set_present(parallel_psy, false); |
| } |
| } |
| |
| rc = smbchg_set_thermal_limited_usb_current_max(chip, |
| chip->usb_target_current_ma); |
| rc = smbchg_set_thermal_limited_dc_current_max(chip, |
| chip->dc_target_current_ma); |
| |
| if (prev_therm_lvl == chip->thermal_levels - 1) { |
| /* |
| * If previously highest value was selected charging must have |
| * been disabed. Enable charging by taking the DC and USB path |
| * out of suspend. |
| */ |
| rc = smbchg_dc_en(chip, true, REASON_THERMAL); |
| if (rc < 0) { |
| dev_err(chip->dev, |
| "Couldn't set dc suspend rc %d\n", rc); |
| goto out; |
| } |
| rc = smbchg_usb_en(chip, true, REASON_THERMAL); |
| if (rc < 0) { |
| dev_err(chip->dev, |
| "Couldn't set usb suspend rc %d\n", rc); |
| goto out; |
| } |
| } |
| out: |
| mutex_unlock(&chip->current_change_lock); |
| return rc; |
| } |
| |
| static int smbchg_ibat_ocp_threshold_ua = 4500000; |
| module_param(smbchg_ibat_ocp_threshold_ua, int, 0644); |
| |
| #define UCONV 1000000LL |
| #define MCONV 1000LL |
| #define FLASH_V_THRESHOLD 3000000 |
| #define FLASH_VDIP_MARGIN 100000 |
| #define VPH_FLASH_VDIP (FLASH_V_THRESHOLD + FLASH_VDIP_MARGIN) |
| #define BUCK_EFFICIENCY 800LL |
| static int smbchg_calc_max_flash_current(struct smbchg_chip *chip) |
| { |
| int ocv_uv, esr_uohm, rbatt_uohm, ibat_now, rc; |
| int64_t ibat_flash_ua, avail_flash_ua, avail_flash_power_fw; |
| int64_t ibat_safe_ua, vin_flash_uv, vph_flash_uv; |
| |
| rc = get_property_from_fg(chip, POWER_SUPPLY_PROP_VOLTAGE_OCV, &ocv_uv); |
| if (rc) { |
| pr_smb(PR_STATUS, "bms psy does not support OCV\n"); |
| return 0; |
| } |
| |
| rc = get_property_from_fg(chip, POWER_SUPPLY_PROP_RESISTANCE, |
| &esr_uohm); |
| if (rc) { |
| pr_smb(PR_STATUS, "bms psy does not support resistance\n"); |
| return 0; |
| } |
| |
| rc = msm_bcl_read(BCL_PARAM_CURRENT, &ibat_now); |
| if (rc) { |
| pr_smb(PR_STATUS, "BCL current read failed: %d\n", rc); |
| return 0; |
| } |
| |
| rbatt_uohm = esr_uohm + chip->rpara_uohm + chip->rslow_uohm; |
| /* |
| * Calculate the maximum current that can pulled out of the battery |
| * before the battery voltage dips below a safe threshold. |
| */ |
| ibat_safe_ua = div_s64((ocv_uv - VPH_FLASH_VDIP) * UCONV, |
| rbatt_uohm); |
| |
| if (ibat_safe_ua <= smbchg_ibat_ocp_threshold_ua) { |
| /* |
| * If the calculated current is below the OCP threshold, then |
| * use it as the possible flash current. |
| */ |
| ibat_flash_ua = ibat_safe_ua - ibat_now; |
| vph_flash_uv = VPH_FLASH_VDIP; |
| } else { |
| /* |
| * If the calculated current is above the OCP threshold, then |
| * use the ocp threshold instead. |
| * |
| * Any higher current will be tripping the battery OCP. |
| */ |
| ibat_flash_ua = smbchg_ibat_ocp_threshold_ua - ibat_now; |
| vph_flash_uv = ocv_uv - div64_s64((int64_t)rbatt_uohm |
| * smbchg_ibat_ocp_threshold_ua, UCONV); |
| } |
| /* Calculate the input voltage of the flash module. */ |
| vin_flash_uv = max((chip->vled_max_uv + 500000LL), |
| div64_s64((vph_flash_uv * 1200), 1000)); |
| /* Calculate the available power for the flash module. */ |
| avail_flash_power_fw = BUCK_EFFICIENCY * vph_flash_uv * ibat_flash_ua; |
| /* |
| * Calculate the available amount of current the flash module can draw |
| * before collapsing the battery. (available power/ flash input voltage) |
| */ |
| avail_flash_ua = div64_s64(avail_flash_power_fw, vin_flash_uv * MCONV); |
| pr_smb(PR_MISC, |
| "avail_iflash=%lld, ocv=%d, ibat=%d, rbatt=%d\n", |
| avail_flash_ua, ocv_uv, ibat_now, rbatt_uohm); |
| return (int)avail_flash_ua; |
| } |
| |
| #define FCC_CMP_CFG 0xF3 |
| #define FCC_COMP_MASK SMB_MASK(1, 0) |
| static int smbchg_fastchg_current_comp_set(struct smbchg_chip *chip, |
| int comp_current) |
| { |
| int rc; |
| u8 i; |
| |
| for (i = 0; i < ARRAY_SIZE(fcc_comp_table); i++) |
| if (comp_current == fcc_comp_table[i]) |
| break; |
| |
| if (i >= ARRAY_SIZE(fcc_comp_table)) |
| return -EINVAL; |
| |
| rc = smbchg_sec_masked_write(chip, chip->chgr_base + FCC_CMP_CFG, |
| FCC_COMP_MASK, i); |
| |
| if (rc) |
| dev_err(chip->dev, "Couldn't set fastchg current comp rc = %d\n", |
| rc); |
| |
| return rc; |
| } |
| |
| #define FV_CMP_CFG 0xF5 |
| #define FV_COMP_MASK SMB_MASK(5, 0) |
| static int smbchg_float_voltage_comp_set(struct smbchg_chip *chip, int code) |
| { |
| int rc; |
| u8 val; |
| |
| val = code & FV_COMP_MASK; |
| rc = smbchg_sec_masked_write(chip, chip->chgr_base + FV_CMP_CFG, |
| FV_COMP_MASK, val); |
| |
| if (rc) |
| dev_err(chip->dev, "Couldn't set float voltage comp rc = %d\n", |
| rc); |
| |
| return rc; |
| } |
| |
| #define VFLOAT_CFG_REG 0xF4 |
| #define MIN_FLOAT_MV 3600 |
| #define MAX_FLOAT_MV 4500 |
| #define VFLOAT_MASK SMB_MASK(5, 0) |
| |
| #define MID_RANGE_FLOAT_MV_MIN 3600 |
| #define MID_RANGE_FLOAT_MIN_VAL 0x05 |
| #define MID_RANGE_FLOAT_STEP_MV 20 |
| |
| #define HIGH_RANGE_FLOAT_MIN_MV 4340 |
| #define HIGH_RANGE_FLOAT_MIN_VAL 0x2A |
| #define HIGH_RANGE_FLOAT_STEP_MV 10 |
| |
| #define VHIGH_RANGE_FLOAT_MIN_MV 4360 |
| #define VHIGH_RANGE_FLOAT_MIN_VAL 0x2C |
| #define VHIGH_RANGE_FLOAT_STEP_MV 20 |
| static int smbchg_float_voltage_set(struct smbchg_chip *chip, int vfloat_mv) |
| { |
| struct power_supply *parallel_psy = get_parallel_psy(chip); |
| int rc, delta; |
| u8 temp; |
| int float_voltage_comp; |
| |
| if ((vfloat_mv < MIN_FLOAT_MV) || (vfloat_mv > MAX_FLOAT_MV)) { |
| dev_err(chip->dev, "bad float voltage mv =%d asked to set\n", |
| vfloat_mv); |
| return -EINVAL; |
| } |
| |
| if (vfloat_mv <= HIGH_RANGE_FLOAT_MIN_MV) { |
| /* mid range */ |
| delta = vfloat_mv - MID_RANGE_FLOAT_MV_MIN; |
| temp = MID_RANGE_FLOAT_MIN_VAL + delta |
| / MID_RANGE_FLOAT_STEP_MV; |
| vfloat_mv -= delta % MID_RANGE_FLOAT_STEP_MV; |
| } else if (vfloat_mv <= VHIGH_RANGE_FLOAT_MIN_MV) { |
| /* high range */ |
| delta = vfloat_mv - HIGH_RANGE_FLOAT_MIN_MV; |
| temp = HIGH_RANGE_FLOAT_MIN_VAL + delta |
| / HIGH_RANGE_FLOAT_STEP_MV; |
| vfloat_mv -= delta % HIGH_RANGE_FLOAT_STEP_MV; |
| } else { |
| /* very high range */ |
| delta = vfloat_mv - VHIGH_RANGE_FLOAT_MIN_MV; |
| temp = VHIGH_RANGE_FLOAT_MIN_VAL + delta |
| / VHIGH_RANGE_FLOAT_STEP_MV; |
| vfloat_mv -= delta % VHIGH_RANGE_FLOAT_STEP_MV; |
| } |
| |
| if (parallel_psy) { |
| rc = power_supply_set_voltage_limit(parallel_psy, |
| vfloat_mv + 50); |
| if (rc) |
| dev_err(chip->dev, "Couldn't set float voltage on parallel psy rc: %d\n", |
| rc); |
| } |
| |
| rc = smbchg_sec_masked_write(chip, chip->chgr_base + VFLOAT_CFG_REG, |
| VFLOAT_MASK, temp); |
| |
| if (rc) |
| dev_err(chip->dev, "Couldn't set float voltage rc = %d\n", rc); |
| else |
| chip->vfloat_mv = vfloat_mv; |
| |
| /* as stage charge vfloat have two value: 4.2V and 4.4V, |
| * stage charge will limit vfloat to 4.2V for first stage, |
| * then reset to 4.4V in second stage. when battery is warm, |
| * we should compensate the vfloat to protect the battery, |
| * and warm vfloat is 4.1V (according to battery datasheet), |
| * so if vfloat is 4.4V, use chip->float_voltage_comp = 16, |
| * if vfloat is 4.2V, use chip->float_voltage_comp_stage = 6. |
| */ |
| if (chip->vfloat_mv == chip->cfg_vfloat_mv) { |
| float_voltage_comp = chip->float_voltage_comp; |
| } else { |
| float_voltage_comp = chip->float_voltage_comp_stage; |
| } |
| |
| /* set the float voltage compensation */ |
| if (float_voltage_comp != -EINVAL) { |
| rc = smbchg_float_voltage_comp_set(chip, |
| float_voltage_comp); |
| if (rc < 0) { |
| dev_err(chip->dev, "Couldn't set float voltage comp rc = %d\n", |
| rc); |
| return rc; |
| } |
| pr_smb(PR_STATUS, "set float voltage comp to %d\n", |
| float_voltage_comp); |
| } |
| |
| return rc; |
| } |
| |
| static int smbchg_float_voltage_get(struct smbchg_chip *chip) |
| { |
| return chip->vfloat_mv; |
| } |
| |
| #define SFT_CFG 0xFD |
| #define SFT_EN_MASK SMB_MASK(5, 4) |
| #define SFT_TO_MASK SMB_MASK(3, 2) |
| #define PRECHG_SFT_TO_MASK SMB_MASK(1, 0) |
| #define SFT_TIMER_DISABLE_BIT BIT(5) |
| #define PRECHG_SFT_TIMER_DISABLE_BIT BIT(4) |
| #define SAFETY_TIME_MINUTES_SHIFT 2 |
| static int smbchg_safety_timer_enable(struct smbchg_chip *chip, bool enable) |
| { |
| int rc; |
| u8 reg; |
| |
| if (enable == chip->safety_timer_en) |
| return 0; |
| |
| if (enable) |
| reg = 0; |
| else |
| reg = SFT_TIMER_DISABLE_BIT | PRECHG_SFT_TIMER_DISABLE_BIT; |
| |
| rc = smbchg_sec_masked_write(chip, chip->chgr_base + SFT_CFG, |
| SFT_EN_MASK, reg); |
| if (rc < 0) { |
| dev_err(chip->dev, |
| "Couldn't %s safety timer rc = %d\n", |
| enable ? "enable" : "disable", rc); |
| return rc; |
| } |
| chip->safety_timer_en = enable; |
| return 0; |
| } |
| |
| enum skip_reason { |
| REASON_OTG_ENABLED = BIT(0), |
| REASON_FLASH_ENABLED = BIT(1) |
| }; |
| |
| #define OTG_TRIM6 0xF6 |
| #define TR_ENB_SKIP_BIT BIT(2) |
| #define OTG_EN_BIT BIT(0) |
| static int smbchg_otg_pulse_skip_disable(struct smbchg_chip *chip, |
| enum skip_reason reason, bool disable) |
| { |
| int rc; |
| bool disabled; |
| |
| disabled = !!chip->otg_pulse_skip_dis; |
| pr_smb(PR_STATUS, "%s pulse skip, reason %d\n", |
| disable ? "disabling" : "enabling", reason); |
| if (disable) |
| chip->otg_pulse_skip_dis |= reason; |
| else |
| chip->otg_pulse_skip_dis &= ~reason; |
| if (disabled == !!chip->otg_pulse_skip_dis) |
| return 0; |
| disabled = !!chip->otg_pulse_skip_dis; |
| |
| rc = smbchg_sec_masked_write(chip, chip->otg_base + OTG_TRIM6, |
| TR_ENB_SKIP_BIT, disabled ? TR_ENB_SKIP_BIT : 0); |
| if (rc < 0) { |
| dev_err(chip->dev, |
| "Couldn't %s otg pulse skip rc = %d\n", |
| disabled ? "disable" : "enable", rc); |
| return rc; |
| } |
| pr_smb(PR_STATUS, "%s pulse skip\n", disabled ? "disabled" : "enabled"); |
| return 0; |
| } |
| |
| #define LOW_PWR_OPTIONS_REG 0xFF |
| #define FORCE_TLIM_BIT BIT(4) |
| static int smbchg_force_tlim_en(struct smbchg_chip *chip, bool enable) |
| { |
| int rc; |
| |
| rc = smbchg_sec_masked_write(chip, chip->otg_base + LOW_PWR_OPTIONS_REG, |
| FORCE_TLIM_BIT, enable ? FORCE_TLIM_BIT : 0); |
| if (rc < 0) { |
| dev_err(chip->dev, |
| "Couldn't %s otg force tlim rc = %d\n", |
| enable ? "enable" : "disable", rc); |
| return rc; |
| } |
| return rc; |
| } |
| |
| static int smbchg_battery_set_property(struct power_supply *psy, |
| enum power_supply_property prop, |
| const union power_supply_propval *val) |
| { |
| int rc = 0; |
| bool unused; |
| struct smbchg_chip *chip = container_of(psy, |
| struct smbchg_chip, batt_psy); |
| |
| switch (prop) { |
| case POWER_SUPPLY_PROP_BATTERY_CHARGING_ENABLED: |
| smbchg_battchg_en(chip, val->intval, |
| REASON_BATTCHG_USER, &unused); |
| break; |
| case POWER_SUPPLY_PROP_CHARGING_ENABLED: |
| smbchg_usb_en(chip, val->intval, REASON_USER); |
| smbchg_dc_en(chip, val->intval, REASON_USER); |
| chip->chg_enabled = val->intval; |
| schedule_work(&chip->usb_set_online_work); |
| break; |
| case POWER_SUPPLY_PROP_CAPACITY: |
| chip->fake_battery_soc = val->intval; |
| power_supply_changed(&chip->batt_psy); |
| break; |
| case POWER_SUPPLY_PROP_SYSTEM_TEMP_LEVEL: |
| smbchg_system_temp_level_set(chip, val->intval); |
| break; |
| case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: |
| rc = smbchg_set_fastchg_current_user(chip, val->intval / 1000); |
| break; |
| case POWER_SUPPLY_PROP_VOLTAGE_MAX: |
| rc = smbchg_float_voltage_set(chip, val->intval); |
| break; |
| case POWER_SUPPLY_PROP_SAFETY_TIMER_ENABLE: |
| rc = smbchg_safety_timer_enable(chip, val->intval); |
| break; |
| case POWER_SUPPLY_PROP_FLASH_ACTIVE: |
| rc = smbchg_otg_pulse_skip_disable(chip, |
| REASON_FLASH_ENABLED, val->intval); |
| break; |
| case POWER_SUPPLY_PROP_FORCE_TLIM: |
| rc = smbchg_force_tlim_en(chip, val->intval); |
| break; |
| case POWER_SUPPLY_PROP_COMPASS_COMPENSATION: |
| update_compass_compensation(&chip->batt_psy); |
| break; |
| default: |
| return -EINVAL; |
| } |
| |
| return rc; |
| } |
| |
| static int smbchg_battery_is_writeable(struct power_supply *psy, |
| enum power_supply_property prop) |
| { |
| int rc; |
| |
| switch (prop) { |
| case POWER_SUPPLY_PROP_BATTERY_CHARGING_ENABLED: |
| case POWER_SUPPLY_PROP_CHARGING_ENABLED: |
| case POWER_SUPPLY_PROP_CAPACITY: |
| case POWER_SUPPLY_PROP_SYSTEM_TEMP_LEVEL: |
| case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: |
| case POWER_SUPPLY_PROP_VOLTAGE_MAX: |
| case POWER_SUPPLY_PROP_SAFETY_TIMER_ENABLE: |
| rc = 1; |
| break; |
| default: |
| rc = 0; |
| break; |
| } |
| return rc; |
| } |
| |
| static int smbchg_battery_get_property(struct power_supply *psy, |
| enum power_supply_property prop, |
| union power_supply_propval *val) |
| { |
| struct smbchg_chip *chip = container_of(psy, |
| struct smbchg_chip, batt_psy); |
| |
| switch (prop) { |
| case POWER_SUPPLY_PROP_STATUS: |
| val->intval = get_prop_batt_status(chip); |
| break; |
| case POWER_SUPPLY_PROP_PRESENT: |
| val->intval = get_prop_batt_present(chip); |
| |