2016-03-21 22:55:19 +00:00
|
|
|
/* Copyright (c) 2014-2016 The Linux Foundation. All rights reserved.
|
2014-02-20 01:27:12 +00:00
|
|
|
*
|
|
|
|
* 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>
|
2014-08-25 22:43:10 +00:00
|
|
|
#include <linux/ratelimit.h>
|
2014-11-05 01:04:29 +00:00
|
|
|
#include <linux/debugfs.h>
|
2015-03-10 08:42:22 +00:00
|
|
|
#include <linux/leds.h>
|
2015-02-22 00:14:03 +00:00
|
|
|
#include <linux/rtc.h>
|
2014-10-30 21:59:48 +00:00
|
|
|
#include <linux/qpnp/qpnp-adc.h>
|
|
|
|
#include <linux/batterydata-lib.h>
|
2015-03-27 02:23:59 +00:00
|
|
|
#include <linux/of_batterydata.h>
|
2015-02-07 23:40:12 +00:00
|
|
|
#include <linux/msm_bcl.h>
|
2015-03-30 21:20:44 +00:00
|
|
|
#include <linux/ktime.h>
|
2015-11-26 07:54:21 +00:00
|
|
|
#include "pmic-voter.h"
|
2014-02-20 01:27:12 +00:00
|
|
|
|
|
|
|
/* 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))
|
|
|
|
/* Config registers */
|
|
|
|
struct smbchg_regulator {
|
|
|
|
struct regulator_desc rdesc;
|
|
|
|
struct regulator_dev *rdev;
|
|
|
|
};
|
|
|
|
|
2014-05-30 21:07:14 +00:00
|
|
|
struct parallel_usb_cfg {
|
|
|
|
struct power_supply *psy;
|
|
|
|
int min_current_thr_ma;
|
2014-08-06 18:27:04 +00:00
|
|
|
int min_9v_current_thr_ma;
|
2014-10-09 01:36:33 +00:00
|
|
|
int allowed_lowering_ma;
|
2014-05-30 21:07:14 +00:00
|
|
|
int current_max_ma;
|
|
|
|
bool avail;
|
2014-08-06 18:27:04 +00:00
|
|
|
struct mutex lock;
|
2014-10-09 01:36:33 +00:00
|
|
|
int initial_aicl_ma;
|
2015-03-30 21:20:44 +00:00
|
|
|
ktime_t last_disabled;
|
|
|
|
bool enabled_once;
|
2014-05-30 21:07:14 +00:00
|
|
|
};
|
|
|
|
|
2014-10-30 21:59:48 +00:00
|
|
|
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;
|
|
|
|
};
|
|
|
|
|
2015-11-26 07:39:46 +00:00
|
|
|
struct smbchg_version_tables {
|
|
|
|
const int *dc_ilim_ma_table;
|
|
|
|
int dc_ilim_ma_len;
|
|
|
|
const int *usb_ilim_ma_table;
|
|
|
|
int usb_ilim_ma_len;
|
|
|
|
const int *iterm_ma_table;
|
|
|
|
int iterm_ma_len;
|
|
|
|
const int *fcc_comp_table;
|
|
|
|
int fcc_comp_len;
|
2015-11-26 09:51:36 +00:00
|
|
|
const int *aicl_rerun_period_table;
|
|
|
|
int aicl_rerun_period_len;
|
2015-11-26 07:39:46 +00:00
|
|
|
int rchg_thr_mv;
|
|
|
|
};
|
|
|
|
|
2014-02-20 01:27:12 +00:00
|
|
|
struct smbchg_chip {
|
|
|
|
struct device *dev;
|
|
|
|
struct spmi_device *spmi;
|
2015-02-11 14:23:46 +00:00
|
|
|
int schg_version;
|
2014-02-20 01:27:12 +00:00
|
|
|
|
|
|
|
/* 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;
|
2014-09-09 21:03:24 +00:00
|
|
|
u8 revision[4];
|
2014-02-20 01:27:12 +00:00
|
|
|
|
|
|
|
/* configuration parameters */
|
|
|
|
int iterm_ma;
|
|
|
|
int usb_max_current_ma;
|
|
|
|
int dc_max_current_ma;
|
|
|
|
int dc_target_current_ma;
|
2015-03-10 02:58:05 +00:00
|
|
|
int cfg_fastchg_current_ma;
|
2014-08-06 18:27:04 +00:00
|
|
|
int fastchg_current_ma;
|
2014-02-20 01:27:12 +00:00
|
|
|
int vfloat_mv;
|
2014-12-26 20:25:54 +00:00
|
|
|
int fastchg_current_comp;
|
|
|
|
int float_voltage_comp;
|
2014-02-20 01:27:12 +00:00
|
|
|
int resume_delta_mv;
|
|
|
|
int safety_time;
|
|
|
|
int prechg_safety_time;
|
|
|
|
int bmd_pin_src;
|
2015-01-08 04:39:21 +00:00
|
|
|
int jeita_temp_hard_limit;
|
2015-09-17 22:07:37 +00:00
|
|
|
int sw_esr_pulse_current_ma;
|
2015-11-26 09:51:36 +00:00
|
|
|
int aicl_rerun_period_s;
|
2014-10-07 23:30:02 +00:00
|
|
|
bool use_vfloat_adjustments;
|
2014-02-20 01:27:12 +00:00
|
|
|
bool iterm_disabled;
|
|
|
|
bool bmd_algo_disabled;
|
|
|
|
bool soft_vfloat_comp_disabled;
|
|
|
|
bool chg_enabled;
|
2014-07-09 21:51:58 +00:00
|
|
|
bool charge_unknown_battery;
|
2014-11-18 21:44:25 +00:00
|
|
|
bool chg_inhibit_en;
|
2014-09-26 22:23:56 +00:00
|
|
|
bool chg_inhibit_source_fg;
|
2015-02-26 00:01:41 +00:00
|
|
|
bool low_volt_dcin;
|
2015-03-10 08:42:22 +00:00
|
|
|
bool cfg_chg_led_support;
|
|
|
|
bool cfg_chg_led_sw_ctrl;
|
2015-02-22 05:47:02 +00:00
|
|
|
bool vbat_above_headroom;
|
2015-04-21 23:02:50 +00:00
|
|
|
bool force_aicl_rerun;
|
2015-08-18 03:14:26 +00:00
|
|
|
bool hvdcp3_supported;
|
2016-03-21 22:55:19 +00:00
|
|
|
bool allow_hvdcp3_detection;
|
2015-09-14 22:40:02 +00:00
|
|
|
bool restricted_charging;
|
2016-03-25 17:56:39 +00:00
|
|
|
bool cfg_override_usb_current;
|
2014-10-07 23:30:02 +00:00
|
|
|
u8 original_usbin_allowance;
|
2014-05-30 21:07:14 +00:00
|
|
|
struct parallel_usb_cfg parallel;
|
2014-10-15 17:41:50 +00:00
|
|
|
struct delayed_work parallel_en_work;
|
2014-11-05 01:04:29 +00:00
|
|
|
struct dentry *debug_root;
|
2015-11-26 07:39:46 +00:00
|
|
|
struct smbchg_version_tables tables;
|
2014-02-20 01:27:12 +00:00
|
|
|
|
2014-10-30 21:59:48 +00:00
|
|
|
/* 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;
|
|
|
|
|
2014-07-31 22:10:07 +00:00
|
|
|
/* flash current prediction */
|
|
|
|
int rpara_uohm;
|
|
|
|
int rslow_uohm;
|
2015-02-07 23:40:12 +00:00
|
|
|
int vled_max_uv;
|
2014-07-31 22:10:07 +00:00
|
|
|
|
2014-10-29 18:01:40 +00:00
|
|
|
/* vfloat adjustment */
|
|
|
|
int max_vbat_sample;
|
|
|
|
int n_vbat_samples;
|
|
|
|
|
2014-02-20 01:27:12 +00:00
|
|
|
/* status variables */
|
2014-10-15 17:41:50 +00:00
|
|
|
int wake_reasons;
|
2014-11-04 15:09:26 +00:00
|
|
|
int previous_soc;
|
2014-12-15 22:39:08 +00:00
|
|
|
int usb_online;
|
2014-02-20 01:27:12 +00:00
|
|
|
bool dc_present;
|
|
|
|
bool usb_present;
|
|
|
|
bool batt_present;
|
2014-12-03 19:30:00 +00:00
|
|
|
int otg_retries;
|
|
|
|
ktime_t otg_enable_time;
|
2015-02-22 05:47:02 +00:00
|
|
|
bool aicl_deglitch_short;
|
2015-01-08 03:57:35 +00:00
|
|
|
bool safety_timer_en;
|
2015-01-22 01:34:30 +00:00
|
|
|
bool aicl_complete;
|
2015-02-04 03:07:09 +00:00
|
|
|
bool usb_ov_det;
|
2015-06-26 21:10:50 +00:00
|
|
|
bool otg_pulse_skip_dis;
|
2015-03-27 02:23:59 +00:00
|
|
|
const char *battery_type;
|
2015-10-19 23:01:13 +00:00
|
|
|
enum power_supply_type usb_supply_type;
|
power: qpnp-smbcharger: handle very weak chargers
The PMi charger peripheral can detect weak chargers through the automatic
input current limit (AICL) feature by observing how much current it
takes to collapse an external charger. Additionally, under very bad
conditions, such as having a very weak charger with a high impedance
cable, the AICL algorithm will actually stop altogether when the USB
voltage drops below the autoshutdown threshold.
Normally, this will trigger AICL suspend and shut off the charger buck.
However, if hardware AICL reruns are enabled, AICL cannot be suspended
in hardware. This can cause a problem where the USBIN_UV interrupt is
continuously received when the USBIN voltage hovers around the lower
limit of the valid range.
Fix this by detecting very weak chargers if the USBIN_UV is high at the
same time USBIN_SRC_DET is high. If both interrupts lines are high,
it means that the USB voltage is below the low threshold for UV but
higher than the coarse detect threshold (1V).
If a weak charger is detected, disable AICL rerun to allow the charger
to enter into AICL suspend, and set the USB health to UNSPEC_FAILURE,
and do not report USB online.
Also, do not allow USBIN_UV to reset the aicl_irq_count in order to
allow the battery OV interrupt to run correctly.
CRs-Fixed: 823770
Change-Id: If853659fa7aaf899f500d8933a773ab80d0464a1
Signed-off-by: Xiaozhe Shi <xiaozhes@codeaurora.org>
2015-04-15 21:39:43 +00:00
|
|
|
bool very_weak_charger;
|
2015-05-26 07:03:45 +00:00
|
|
|
bool parallel_charger_detected;
|
2015-11-26 07:46:51 +00:00
|
|
|
bool chg_otg_enabled;
|
|
|
|
bool flash_triggered;
|
|
|
|
bool icl_disabled;
|
2015-06-08 11:14:19 +00:00
|
|
|
u32 wa_flags;
|
2015-10-29 23:45:51 +00:00
|
|
|
int usb_icl_delta;
|
2014-02-20 01:27:12 +00:00
|
|
|
|
|
|
|
/* jeita and temperature */
|
|
|
|
bool batt_hot;
|
|
|
|
bool batt_cold;
|
|
|
|
bool batt_warm;
|
|
|
|
bool batt_cool;
|
|
|
|
unsigned int thermal_levels;
|
|
|
|
unsigned int therm_lvl_sel;
|
|
|
|
unsigned int *thermal_mitigation;
|
|
|
|
|
|
|
|
/* 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;
|
2014-08-06 18:27:04 +00:00
|
|
|
bool taper_irq_enabled;
|
|
|
|
struct mutex taper_irq_lock;
|
2014-02-20 01:27:12 +00:00
|
|
|
int recharge_irq;
|
2014-05-28 02:07:48 +00:00
|
|
|
int fastchg_irq;
|
2015-07-31 09:29:44 +00:00
|
|
|
int wdog_timeout_irq;
|
2014-02-20 01:27:12 +00:00
|
|
|
int power_ok_irq;
|
|
|
|
int dcin_uv_irq;
|
|
|
|
int usbin_uv_irq;
|
2015-02-04 03:07:09 +00:00
|
|
|
int usbin_ov_irq;
|
2014-02-20 01:27:12 +00:00
|
|
|
int src_detect_irq;
|
2014-06-17 23:08:46 +00:00
|
|
|
int otg_fail_irq;
|
|
|
|
int otg_oc_irq;
|
2014-05-30 21:07:14 +00:00
|
|
|
int aicl_done_irq;
|
2014-06-17 23:08:46 +00:00
|
|
|
int usbid_change_irq;
|
power: qpnp-smbcharger: add workaround for UVLO resets
When using the PMI8994 smbcharger, usb removal or suspend can cause
an under voltage lock out (UVLO) due to a hardware bug.
The condition for the UVLO to happen is when the whole system is running
off the charger buck when the USB is either removed or suspended. This
case can easily happen when the system is running at medium loads
(300-400 mA) and the battery is considered missing, overtemp, or
overvoltage by the charger hardware.
When this happens, the charger stops trying to pump current into the
battery, and instead just draws power from the active charge path
(USB in ths case). However, when the input charge path is removed, either
by suspending it through software or by physically removing the USB
cable, the battery FET does not turn on fast enough in hardware to
supplement the system load. This causes a droop on VPH power, manifesting
itself as an UVLO reset.
The work around for this issue is to ensure the battery is supplementing
the current before a charge path removal event happens. Implement this by
forcing USB input current limit to be 100mA whenever the device is not
charging. More specifically, whenever a charger p2f-thresh interrupt
(pre-to-fastcharge event, charging starts) or a battery interface
chg-error interrupt (battery missing/overtemp/overvolt) happens, check
the charging status and if the device is not charging, force the charger
to only draw 100mA.
If the input current limit is at 100mA, the system will be drawing
current from the battery as long as the system load is higher than
100mA. Thus, when the USB is removed, the battery will already be
supplementing the system load, and there will not be an UVLO reset.
There are a few limitations to this work around.
1. If the system load is less than 100mA when the input charge
path is removed, there may still be a droop on VPH power due
to the battery FET still being open. However, this should not
cause an UVLO reset because the system load will not cause a
large enough droop to cause a UVLO reset.
2. If the input is removed at the exact instant a charger error
occurs (battery goes missing, battery becomes overtemp,
battery becomes overvoltage), and the interrupt does not get
serviced before the input is removed, an UVLO reset can still
happen. However, this is a hardware limitation and there is
no solution for this at this time.
3. Currently, the workaround for the DC input charge path is not
supported. Until this is implemented, DC charging and wipower
will be impacted.
CRs-Fixed: 667489
Change-Id: I1a185d126e1e6bb851e5981f8b49072e8f8c6f68
Signed-off-by: Xiaozhe Shi <xiaozhes@codeaurora.org>
2014-06-12 00:35:01 +00:00
|
|
|
int chg_error_irq;
|
2014-10-15 22:14:18 +00:00
|
|
|
bool enable_aicl_wake;
|
2014-02-20 01:27:12 +00:00
|
|
|
|
|
|
|
/* 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;
|
|
|
|
|
2014-05-30 21:07:14 +00:00
|
|
|
struct smbchg_regulator otg_vreg;
|
2014-10-07 23:30:02 +00:00
|
|
|
struct smbchg_regulator ext_otg_vreg;
|
2014-02-20 01:27:12 +00:00
|
|
|
struct work_struct usb_set_online_work;
|
2014-10-29 18:01:40 +00:00
|
|
|
struct delayed_work vfloat_adjust_work;
|
2015-01-07 03:07:07 +00:00
|
|
|
struct delayed_work hvdcp_det_work;
|
2014-02-20 01:27:12 +00:00
|
|
|
spinlock_t sec_access_lock;
|
2015-11-26 09:07:24 +00:00
|
|
|
struct mutex therm_lvl_lock;
|
2014-02-20 01:27:12 +00:00
|
|
|
struct mutex usb_set_online_lock;
|
2014-10-15 17:41:50 +00:00
|
|
|
struct mutex pm_lock;
|
2015-02-22 00:14:03 +00:00
|
|
|
/* aicl deglitch workaround */
|
|
|
|
unsigned long first_aicl_seconds;
|
|
|
|
int aicl_irq_count;
|
2015-03-19 17:10:26 +00:00
|
|
|
struct mutex usb_status_lock;
|
2015-07-27 05:46:49 +00:00
|
|
|
bool hvdcp_3_det_ignore_uv;
|
|
|
|
struct completion src_det_lowered;
|
|
|
|
struct completion src_det_raised;
|
|
|
|
struct completion usbin_uv_lowered;
|
|
|
|
struct completion usbin_uv_raised;
|
|
|
|
int pulse_cnt;
|
2015-03-10 08:42:22 +00:00
|
|
|
struct led_classdev led_cdev;
|
2015-11-26 07:20:42 +00:00
|
|
|
bool skip_usb_notification;
|
2015-11-26 09:38:11 +00:00
|
|
|
|
|
|
|
/* voters */
|
2015-11-26 07:54:21 +00:00
|
|
|
struct votable *fcc_votable;
|
2015-11-26 09:07:24 +00:00
|
|
|
struct votable *usb_icl_votable;
|
|
|
|
struct votable *dc_icl_votable;
|
2015-11-26 09:38:11 +00:00
|
|
|
struct votable *usb_suspend_votable;
|
|
|
|
struct votable *dc_suspend_votable;
|
|
|
|
struct votable *battchg_suspend_votable;
|
2015-11-04 06:45:11 +00:00
|
|
|
|
|
|
|
u32 vchg_adc_channel;
|
|
|
|
struct qpnp_vadc_chip *vchg_vadc_dev;
|
2014-02-20 01:27:12 +00:00
|
|
|
};
|
|
|
|
|
2015-02-11 14:23:46 +00:00
|
|
|
enum qpnp_schg {
|
|
|
|
QPNP_SCHG,
|
|
|
|
QPNP_SCHG_LITE,
|
|
|
|
};
|
|
|
|
|
|
|
|
static char *version_str[] = {
|
|
|
|
[QPNP_SCHG] = "SCHG",
|
|
|
|
[QPNP_SCHG_LITE] = "SCHG_LITE",
|
|
|
|
};
|
|
|
|
|
2015-06-08 11:14:19 +00:00
|
|
|
enum pmic_subtype {
|
|
|
|
PMI8994 = 10,
|
|
|
|
PMI8950 = 17,
|
2015-08-05 16:12:25 +00:00
|
|
|
PMI8996 = 19,
|
2015-06-08 11:14:19 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
enum smbchg_wa {
|
|
|
|
SMBCHG_AICL_DEGLITCH_WA = BIT(0),
|
|
|
|
SMBCHG_HVDCP_9V_EN_WA = BIT(1),
|
2015-07-05 04:26:35 +00:00
|
|
|
SMBCHG_USB100_WA = BIT(2),
|
2015-08-13 06:11:46 +00:00
|
|
|
SMBCHG_BATT_OV_WA = BIT(3),
|
2015-09-17 22:07:37 +00:00
|
|
|
SMBCHG_CC_ESR_WA = BIT(4),
|
2015-11-26 07:46:51 +00:00
|
|
|
SMBCHG_FLASH_ICL_DISABLE_WA = BIT(5),
|
2015-06-08 11:14:19 +00:00
|
|
|
};
|
|
|
|
|
2014-06-30 22:02:34 +00:00
|
|
|
enum print_reason {
|
2014-10-30 21:59:48 +00:00
|
|
|
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),
|
2014-10-15 17:41:50 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
enum wake_reason {
|
|
|
|
PM_PARALLEL_CHECK = BIT(0),
|
2014-10-29 18:01:40 +00:00
|
|
|
PM_REASON_VFLOAT_ADJUST = BIT(1),
|
2014-11-04 15:09:26 +00:00
|
|
|
PM_ESR_PULSE = BIT(2),
|
2015-07-24 01:30:38 +00:00
|
|
|
PM_PARALLEL_TAPER = BIT(3),
|
2014-06-30 22:02:34 +00:00
|
|
|
};
|
|
|
|
|
2015-11-26 07:54:21 +00:00
|
|
|
enum fcc_voters {
|
|
|
|
ESR_PULSE_FCC_VOTER,
|
|
|
|
BATT_TYPE_FCC_VOTER,
|
2015-09-14 22:40:02 +00:00
|
|
|
RESTRICTED_CHG_FCC_VOTER,
|
2015-11-26 07:54:21 +00:00
|
|
|
NUM_FCC_VOTER,
|
|
|
|
};
|
|
|
|
|
2015-11-26 09:07:24 +00:00
|
|
|
enum icl_voters {
|
|
|
|
PSY_ICL_VOTER,
|
|
|
|
THERMAL_ICL_VOTER,
|
|
|
|
HVDCP_ICL_VOTER,
|
|
|
|
USER_ICL_VOTER,
|
2015-09-14 20:51:25 +00:00
|
|
|
WEAK_CHARGER_ICL_VOTER,
|
2015-10-29 23:45:51 +00:00
|
|
|
SW_AICL_ICL_VOTER,
|
2015-11-26 09:07:24 +00:00
|
|
|
NUM_ICL_VOTER,
|
|
|
|
};
|
|
|
|
|
2015-11-26 09:38:11 +00:00
|
|
|
enum enable_voters {
|
|
|
|
/* userspace has suspended charging altogether */
|
|
|
|
USER_EN_VOTER,
|
|
|
|
/*
|
|
|
|
* this specific path has been suspended through the power supply
|
|
|
|
* framework
|
|
|
|
*/
|
|
|
|
POWER_SUPPLY_EN_VOTER,
|
|
|
|
/*
|
|
|
|
* the usb driver has suspended this path by setting a current limit
|
|
|
|
* of < 2MA
|
|
|
|
*/
|
|
|
|
USB_EN_VOTER,
|
|
|
|
/*
|
|
|
|
* when a wireless charger comes online,
|
|
|
|
* the dc path is suspended for a second
|
|
|
|
*/
|
|
|
|
WIRELESS_EN_VOTER,
|
|
|
|
/*
|
|
|
|
* the thermal daemon can suspend a charge path when the system
|
|
|
|
* temperature levels rise
|
|
|
|
*/
|
|
|
|
THERMAL_EN_VOTER,
|
|
|
|
/*
|
|
|
|
* an external OTG supply is being used, suspend charge path so the
|
|
|
|
* charger does not accidentally try to charge from the external supply.
|
|
|
|
*/
|
|
|
|
OTG_EN_VOTER,
|
|
|
|
/*
|
|
|
|
* the charger is very weak, do not draw any current from it
|
|
|
|
*/
|
|
|
|
WEAK_CHARGER_EN_VOTER,
|
|
|
|
NUM_EN_VOTERS,
|
|
|
|
};
|
|
|
|
|
|
|
|
enum battchg_enable_voters {
|
|
|
|
/* userspace has disabled battery charging */
|
|
|
|
BATTCHG_USER_EN_VOTER,
|
|
|
|
/* battery charging disabled while loading battery profiles */
|
|
|
|
BATTCHG_UNKNOWN_BATTERY_EN_VOTER,
|
|
|
|
NUM_BATTCHG_EN_VOTERS,
|
|
|
|
};
|
|
|
|
|
2014-06-30 22:02:34 +00:00
|
|
|
static int smbchg_debug_mask;
|
|
|
|
module_param_named(
|
|
|
|
debug_mask, smbchg_debug_mask, int, S_IRUSR | S_IWUSR
|
|
|
|
);
|
|
|
|
|
2015-03-18 23:30:55 +00:00
|
|
|
static int smbchg_parallel_en = 1;
|
2014-09-24 00:21:25 +00:00
|
|
|
module_param_named(
|
|
|
|
parallel_en, smbchg_parallel_en, int, S_IRUSR | S_IWUSR
|
|
|
|
);
|
|
|
|
|
2015-10-23 00:03:13 +00:00
|
|
|
static int smbchg_main_chg_fcc_percent = 50;
|
|
|
|
module_param_named(
|
|
|
|
main_chg_fcc_percent, smbchg_main_chg_fcc_percent,
|
|
|
|
int, S_IRUSR | S_IWUSR
|
|
|
|
);
|
|
|
|
|
|
|
|
static int smbchg_main_chg_icl_percent = 60;
|
|
|
|
module_param_named(
|
|
|
|
main_chg_icl_percent, smbchg_main_chg_icl_percent,
|
|
|
|
int, S_IRUSR | S_IWUSR
|
|
|
|
);
|
|
|
|
|
2015-10-29 19:46:21 +00:00
|
|
|
static int smbchg_default_hvdcp_icl_ma = 3000;
|
|
|
|
module_param_named(
|
|
|
|
default_hvdcp_icl_ma, smbchg_default_hvdcp_icl_ma,
|
|
|
|
int, S_IRUSR | S_IWUSR
|
|
|
|
);
|
|
|
|
|
|
|
|
static int smbchg_default_dcp_icl_ma = 1800;
|
|
|
|
module_param_named(
|
|
|
|
default_dcp_icl_ma, smbchg_default_dcp_icl_ma,
|
|
|
|
int, S_IRUSR | S_IWUSR
|
|
|
|
);
|
|
|
|
|
2014-10-30 21:59:48 +00:00
|
|
|
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
|
|
|
|
);
|
|
|
|
|
2014-06-30 22:02:34 +00:00
|
|
|
#define pr_smb(reason, fmt, ...) \
|
|
|
|
do { \
|
|
|
|
if (smbchg_debug_mask & (reason)) \
|
|
|
|
pr_info(fmt, ##__VA_ARGS__); \
|
|
|
|
else \
|
|
|
|
pr_debug(fmt, ##__VA_ARGS__); \
|
|
|
|
} while (0)
|
|
|
|
|
2014-08-25 22:43:10 +00:00
|
|
|
#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)
|
|
|
|
|
2014-02-20 01:27:12 +00:00
|
|
|
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;
|
|
|
|
|
2014-06-30 22:02:34 +00:00
|
|
|
pr_smb(PR_REGISTER, "addr = 0x%x writing 0x%x\n", base, reg);
|
2014-02-20 01:27:12 +00:00
|
|
|
|
|
|
|
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
|
2014-05-28 02:11:10 +00:00
|
|
|
#define PERIPHERAL_MASK 0xFF
|
2014-02-20 01:27:12 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2014-10-15 17:41:50 +00:00
|
|
|
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);
|
2014-10-17 02:07:38 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
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;
|
2014-10-15 17:41:50 +00:00
|
|
|
}
|
|
|
|
|
2014-06-17 23:08:46 +00:00
|
|
|
#define RID_STS 0xB
|
|
|
|
#define RID_MASK 0xF
|
2014-10-03 00:42:41 +00:00
|
|
|
#define IDEV_STS 0x8
|
2014-09-24 20:41:10 +00:00
|
|
|
#define RT_STS 0x10
|
2014-10-03 00:42:41 +00:00
|
|
|
#define USBID_MSB 0xE
|
2014-12-23 02:48:44 +00:00
|
|
|
#define USBIN_UV_BIT BIT(0)
|
|
|
|
#define USBIN_OV_BIT BIT(1)
|
2015-02-21 05:34:44 +00:00
|
|
|
#define USBIN_SRC_DET_BIT BIT(2)
|
2014-10-03 00:42:41 +00:00
|
|
|
#define FMB_STS_MASK SMB_MASK(3, 0)
|
|
|
|
#define USBID_GND_THRESHOLD 0x495
|
2015-02-11 14:23:46 +00:00
|
|
|
static bool is_otg_present_schg(struct smbchg_chip *chip)
|
2014-06-17 23:08:46 +00:00
|
|
|
{
|
|
|
|
int rc;
|
|
|
|
u8 reg;
|
2014-10-10 22:46:51 +00:00
|
|
|
u8 usbid_reg[2];
|
|
|
|
u16 usbid_val;
|
2015-02-11 14:23:46 +00:00
|
|
|
/*
|
|
|
|
* After the falling edge of the usbid change interrupt occurs,
|
|
|
|
* there may still be some time before the ADC conversion for USB RID
|
|
|
|
* finishes in the fuel gauge. In the worst case, this could be up to
|
|
|
|
* 15 ms.
|
|
|
|
*
|
|
|
|
* Sleep for 20 ms (minimum msleep time) to wait for the conversion to
|
|
|
|
* finish and the USB RID status register to be updated before trying
|
|
|
|
* to detect OTG insertions.
|
|
|
|
*/
|
|
|
|
|
|
|
|
msleep(20);
|
2014-06-17 23:08:46 +00:00
|
|
|
|
2014-09-24 20:41:10 +00:00
|
|
|
/*
|
|
|
|
* There is a problem with USBID conversions on PMI8994 revisions
|
2014-10-03 00:42:41 +00:00
|
|
|
* 2.0.0. As a workaround, check that the cable is not
|
|
|
|
* detected as factory test before enabling OTG.
|
2014-09-24 20:41:10 +00:00
|
|
|
*/
|
2014-10-03 00:42:41 +00:00
|
|
|
rc = smbchg_read(chip, ®, chip->misc_base + IDEV_STS, 1);
|
2014-09-24 20:41:10 +00:00
|
|
|
if (rc < 0) {
|
2014-10-03 00:42:41 +00:00
|
|
|
dev_err(chip->dev, "Couldn't read IDEV_STS rc = %d\n", rc);
|
2014-09-24 20:41:10 +00:00
|
|
|
return false;
|
|
|
|
}
|
2014-10-03 00:42:41 +00:00
|
|
|
|
|
|
|
if ((reg & FMB_STS_MASK) != 0) {
|
|
|
|
pr_smb(PR_STATUS, "IDEV_STS = %02x, not ground\n", reg);
|
2014-09-24 20:41:10 +00:00
|
|
|
return false;
|
2014-10-03 00:42:41 +00:00
|
|
|
}
|
|
|
|
|
2014-10-10 22:46:51 +00:00
|
|
|
rc = smbchg_read(chip, usbid_reg, chip->usb_chgpth_base + USBID_MSB, 2);
|
2014-10-03 00:42:41 +00:00
|
|
|
if (rc < 0) {
|
|
|
|
dev_err(chip->dev, "Couldn't read USBID rc = %d\n", rc);
|
|
|
|
return false;
|
|
|
|
}
|
2014-10-10 22:46:51 +00:00
|
|
|
usbid_val = (usbid_reg[0] << 8) | usbid_reg[1];
|
2014-10-03 00:42:41 +00:00
|
|
|
|
2014-10-10 22:46:51 +00:00
|
|
|
if (usbid_val > USBID_GND_THRESHOLD) {
|
2014-10-03 00:42:41 +00:00
|
|
|
pr_smb(PR_STATUS, "USBID = 0x%04x, too high to be ground\n",
|
2014-10-10 22:46:51 +00:00
|
|
|
usbid_val);
|
2014-10-03 00:42:41 +00:00
|
|
|
return false;
|
|
|
|
}
|
2014-09-24 20:41:10 +00:00
|
|
|
|
2014-06-17 23:08:46 +00:00
|
|
|
rc = smbchg_read(chip, ®, chip->usb_chgpth_base + RID_STS, 1);
|
|
|
|
if (rc < 0) {
|
2014-06-30 22:02:34 +00:00
|
|
|
dev_err(chip->dev,
|
|
|
|
"Couldn't read usb rid status rc = %d\n", rc);
|
2014-06-17 23:08:46 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2014-06-30 22:02:34 +00:00
|
|
|
pr_smb(PR_STATUS, "RID_STS = %02x\n", reg);
|
2014-06-17 23:08:46 +00:00
|
|
|
|
|
|
|
return (reg & RID_MASK) == 0;
|
|
|
|
}
|
|
|
|
|
2015-04-13 13:50:42 +00:00
|
|
|
#define RID_GND_DET_STS BIT(2)
|
2015-02-11 14:23:46 +00:00
|
|
|
static bool is_otg_present_schg_lite(struct smbchg_chip *chip)
|
|
|
|
{
|
|
|
|
int rc;
|
|
|
|
u8 reg;
|
|
|
|
|
|
|
|
rc = smbchg_read(chip, ®, chip->otg_base + RT_STS, 1);
|
|
|
|
if (rc < 0) {
|
|
|
|
dev_err(chip->dev,
|
|
|
|
"Couldn't read otg RT status rc = %d\n", rc);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2015-04-13 13:50:42 +00:00
|
|
|
return !!(reg & RID_GND_DET_STS);
|
2015-02-11 14:23:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static bool is_otg_present(struct smbchg_chip *chip)
|
|
|
|
{
|
|
|
|
if (chip->schg_version == QPNP_SCHG_LITE)
|
|
|
|
return is_otg_present_schg_lite(chip);
|
|
|
|
|
|
|
|
return is_otg_present_schg(chip);
|
|
|
|
}
|
|
|
|
|
2014-02-20 01:27:12 +00:00
|
|
|
#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
|
2014-12-23 02:48:44 +00:00
|
|
|
#define DCIN_UV_BIT BIT(0)
|
|
|
|
#define DCIN_OV_BIT BIT(1)
|
2014-02-20 01:27:12 +00:00
|
|
|
static bool is_dc_present(struct smbchg_chip *chip)
|
|
|
|
{
|
|
|
|
int rc;
|
|
|
|
u8 reg;
|
|
|
|
|
2014-10-17 02:07:38 +00:00
|
|
|
rc = smbchg_read(chip, ®, chip->dc_chgpth_base + RT_STS, 1);
|
2014-02-20 01:27:12 +00:00
|
|
|
if (rc < 0) {
|
2014-10-17 02:07:38 +00:00
|
|
|
dev_err(chip->dev, "Couldn't read dc status rc = %d\n", rc);
|
2014-02-20 01:27:12 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2014-10-17 02:07:38 +00:00
|
|
|
if ((reg & DCIN_UV_BIT) || (reg & DCIN_OV_BIT))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
return true;
|
2014-02-20 01:27:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
2015-04-30 01:52:26 +00:00
|
|
|
if (!(reg & USBIN_SRC_DET_BIT) || (reg & USBIN_OV_BIT))
|
2014-02-20 01:27:12 +00:00
|
|
|
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[] = {
|
2014-09-25 18:44:16 +00:00
|
|
|
"SDP", /* bit 0 */
|
|
|
|
"OTHER", /* bit 1 */
|
|
|
|
"DCP", /* bit 2 */
|
|
|
|
"CDP", /* bit 3 */
|
|
|
|
"NONE", /* bit 4 error case */
|
2014-02-20 01:27:12 +00:00
|
|
|
};
|
|
|
|
|
2014-09-25 18:44:16 +00:00
|
|
|
#define N_TYPE_BITS 4
|
|
|
|
#define TYPE_BITS_OFFSET 4
|
2014-09-30 20:52:53 +00:00
|
|
|
|
|
|
|
static int get_type(u8 type_reg)
|
2014-02-20 01:27:12 +00:00
|
|
|
{
|
|
|
|
unsigned long type = type_reg;
|
2014-09-25 18:44:16 +00:00
|
|
|
type >>= TYPE_BITS_OFFSET;
|
2014-09-30 20:52:53 +00:00
|
|
|
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];
|
2014-02-20 01:27:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static enum power_supply_type usb_type_enum[] = {
|
2014-09-25 18:44:16 +00:00
|
|
|
POWER_SUPPLY_TYPE_USB, /* bit 0 */
|
2015-05-11 22:01:02 +00:00
|
|
|
POWER_SUPPLY_TYPE_USB_DCP, /* bit 1 */
|
2014-09-25 18:44:16 +00:00
|
|
|
POWER_SUPPLY_TYPE_USB_DCP, /* bit 2 */
|
|
|
|
POWER_SUPPLY_TYPE_USB_CDP, /* bit 3 */
|
2015-06-17 04:16:05 +00:00
|
|
|
POWER_SUPPLY_TYPE_USB_DCP, /* bit 4 error case, report DCP */
|
2014-02-20 01:27:12 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/* helper to return enum power_supply_type of USB type */
|
2014-09-30 20:52:53 +00:00
|
|
|
static inline enum power_supply_type get_usb_supply_type(int type)
|
2014-02-20 01:27:12 +00:00
|
|
|
{
|
2014-09-30 20:52:53 +00:00
|
|
|
return usb_type_enum[type];
|
2014-02-20 01:27:12 +00:00
|
|
|
}
|
|
|
|
|
2015-06-03 22:21:25 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2014-02-20 01:27:12 +00:00
|
|
|
#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
|
2015-08-05 22:05:21 +00:00
|
|
|
#define CHG_INHIBIT_BIT BIT(1)
|
2014-10-15 23:10:50 +00:00
|
|
|
#define BAT_TCC_REACHED_BIT BIT(7)
|
2014-02-20 01:27:12 +00:00
|
|
|
static int get_prop_batt_status(struct smbchg_chip *chip)
|
|
|
|
{
|
|
|
|
int rc, status = POWER_SUPPLY_STATUS_DISCHARGING;
|
|
|
|
u8 reg = 0, chg_type;
|
2014-10-15 23:10:50 +00:00
|
|
|
bool charger_present, chg_inhibit;
|
|
|
|
|
2015-09-11 00:20:29 +00:00
|
|
|
charger_present = is_usb_present(chip) | is_dc_present(chip) |
|
|
|
|
chip->hvdcp_3_det_ignore_uv;
|
2015-06-17 05:12:00 +00:00
|
|
|
if (!charger_present)
|
|
|
|
return POWER_SUPPLY_STATUS_DISCHARGING;
|
|
|
|
|
2014-10-15 23:10:50 +00:00
|
|
|
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;
|
|
|
|
}
|
2014-02-20 01:27:12 +00:00
|
|
|
|
2014-10-15 23:10:50 +00:00
|
|
|
if (reg & BAT_TCC_REACHED_BIT)
|
|
|
|
return POWER_SUPPLY_STATUS_FULL;
|
|
|
|
|
|
|
|
chg_inhibit = reg & CHG_INHIBIT_BIT;
|
|
|
|
if (chg_inhibit)
|
2014-02-20 01:27:12 +00:00
|
|
|
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;
|
|
|
|
|
2015-09-11 00:20:29 +00:00
|
|
|
if (chg_type == BATT_NOT_CHG_VAL && !chip->hvdcp_3_det_ignore_uv)
|
2014-02-20 01:27:12 +00:00
|
|
|
status = POWER_SUPPLY_STATUS_DISCHARGING;
|
|
|
|
else
|
|
|
|
status = POWER_SUPPLY_STATUS_CHARGING;
|
|
|
|
out:
|
2014-10-14 22:39:42 +00:00
|
|
|
pr_smb_rt(PR_MISC, "CHGR_STS = 0x%02x\n", reg);
|
2014-02-20 01:27:12 +00:00
|
|
|
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;
|
2014-08-06 18:27:04 +00:00
|
|
|
else if (chg_type == BATT_TAPER_CHG_VAL)
|
|
|
|
return POWER_SUPPLY_CHARGE_TYPE_TAPER;
|
2014-02-20 01:27:12 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2014-10-29 18:01:40 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2014-10-22 22:57:20 +00:00
|
|
|
static int get_property_from_fg(struct smbchg_chip *chip,
|
|
|
|
enum power_supply_property prop, int *val)
|
2014-02-20 01:27:12 +00:00
|
|
|
{
|
2014-10-22 22:57:20 +00:00
|
|
|
int rc;
|
2014-02-20 01:27:12 +00:00
|
|
|
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);
|
2014-10-22 22:57:20 +00:00
|
|
|
if (!chip->bms_psy) {
|
|
|
|
pr_smb(PR_STATUS, "no bms psy found\n");
|
|
|
|
return -EINVAL;
|
2014-02-20 01:27:12 +00:00
|
|
|
}
|
|
|
|
|
2014-10-22 22:57:20 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
#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;
|
|
|
|
}
|
|
|
|
return capacity;
|
2014-02-20 01:27:12 +00:00
|
|
|
}
|
|
|
|
|
2014-09-03 21:25:06 +00:00
|
|
|
#define DEFAULT_BATT_TEMP 200
|
|
|
|
static int get_prop_batt_temp(struct smbchg_chip *chip)
|
|
|
|
{
|
2014-10-22 22:57:20 +00:00
|
|
|
int temp, rc;
|
2014-09-03 21:25:06 +00:00
|
|
|
|
2014-10-22 22:57:20 +00:00
|
|
|
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;
|
2014-09-03 21:25:06 +00:00
|
|
|
}
|
2014-10-22 22:57:20 +00:00
|
|
|
return temp;
|
2014-09-03 21:25:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#define DEFAULT_BATT_CURRENT_NOW 0
|
|
|
|
static int get_prop_batt_current_now(struct smbchg_chip *chip)
|
|
|
|
{
|
2014-10-22 22:57:20 +00:00
|
|
|
int ua, rc;
|
2014-09-03 21:25:06 +00:00
|
|
|
|
2014-10-22 22:57:20 +00:00
|
|
|
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;
|
2014-09-03 21:25:06 +00:00
|
|
|
}
|
2014-10-22 22:57:20 +00:00
|
|
|
return ua;
|
2014-09-03 21:25:06 +00:00
|
|
|
}
|
|
|
|
|
2014-09-20 01:19:27 +00:00
|
|
|
#define DEFAULT_BATT_VOLTAGE_NOW 0
|
|
|
|
static int get_prop_batt_voltage_now(struct smbchg_chip *chip)
|
|
|
|
{
|
2014-10-22 22:57:20 +00:00
|
|
|
int uv, rc;
|
2014-09-20 01:19:27 +00:00
|
|
|
|
2014-10-22 22:57:20 +00:00
|
|
|
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;
|
2014-09-20 01:19:27 +00:00
|
|
|
}
|
2014-10-22 22:57:20 +00:00
|
|
|
return uv;
|
2014-09-20 01:19:27 +00:00
|
|
|
}
|
|
|
|
|
2014-10-22 22:58:22 +00:00
|
|
|
#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;
|
|
|
|
}
|
|
|
|
|
2014-02-20 01:27:12 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2015-11-26 07:39:46 +00:00
|
|
|
/*
|
|
|
|
* finds the index of the closest value in the array. If there are two that
|
|
|
|
* are equally close, the lower index will be returned
|
|
|
|
*/
|
|
|
|
static int find_closest_in_array(const int *arr, int len, int val)
|
|
|
|
{
|
|
|
|
int i, closest = 0;
|
|
|
|
|
|
|
|
if (len == 0)
|
|
|
|
return closest;
|
|
|
|
for (i = 0; i < len; i++)
|
2015-10-22 08:51:17 +00:00
|
|
|
if (abs(val - arr[i]) < abs(val - arr[closest]))
|
2015-11-26 07:39:46 +00:00
|
|
|
closest = i;
|
|
|
|
|
|
|
|
return closest;
|
|
|
|
}
|
|
|
|
|
2015-08-25 18:17:24 +00:00
|
|
|
/* finds the index of the closest smaller value in the array. */
|
|
|
|
static int find_smaller_in_array(const int *table, int val, int len)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = len - 1; i >= 0; i--) {
|
|
|
|
if (val >= table[i])
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return i;
|
|
|
|
}
|
|
|
|
|
2015-11-26 07:39:46 +00:00
|
|
|
static const int iterm_ma_table_8994[] = {
|
|
|
|
300,
|
|
|
|
50,
|
|
|
|
100,
|
|
|
|
150,
|
|
|
|
200,
|
|
|
|
250,
|
|
|
|
500,
|
|
|
|
600
|
|
|
|
};
|
|
|
|
|
|
|
|
static const int iterm_ma_table_8996[] = {
|
|
|
|
300,
|
|
|
|
50,
|
|
|
|
100,
|
|
|
|
150,
|
|
|
|
200,
|
|
|
|
250,
|
|
|
|
400,
|
|
|
|
500
|
|
|
|
};
|
|
|
|
|
|
|
|
static const int usb_ilim_ma_table_8994[] = {
|
2014-02-20 01:27:12 +00:00
|
|
|
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
|
|
|
|
};
|
|
|
|
|
2015-11-26 07:39:46 +00:00
|
|
|
static const int usb_ilim_ma_table_8996[] = {
|
|
|
|
300,
|
|
|
|
400,
|
|
|
|
500,
|
|
|
|
600,
|
|
|
|
700,
|
|
|
|
800,
|
|
|
|
900,
|
|
|
|
1000,
|
|
|
|
1100,
|
|
|
|
1200,
|
|
|
|
1300,
|
|
|
|
1400,
|
|
|
|
1450,
|
|
|
|
1500,
|
|
|
|
1550,
|
|
|
|
1600,
|
|
|
|
1700,
|
|
|
|
1800,
|
|
|
|
1900,
|
|
|
|
1950,
|
|
|
|
2000,
|
|
|
|
2050,
|
|
|
|
2100,
|
|
|
|
2200,
|
|
|
|
2300,
|
|
|
|
2400,
|
|
|
|
2500,
|
|
|
|
2600,
|
|
|
|
2700,
|
|
|
|
2800,
|
|
|
|
2900,
|
|
|
|
3000
|
|
|
|
};
|
|
|
|
|
|
|
|
static int dc_ilim_ma_table_8994[] = {
|
2014-02-20 01:27:12 +00:00
|
|
|
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,
|
|
|
|
};
|
|
|
|
|
2015-11-26 07:39:46 +00:00
|
|
|
static int dc_ilim_ma_table_8996[] = {
|
|
|
|
300,
|
|
|
|
400,
|
|
|
|
500,
|
|
|
|
600,
|
|
|
|
700,
|
|
|
|
800,
|
|
|
|
900,
|
|
|
|
1000,
|
|
|
|
1100,
|
|
|
|
1200,
|
|
|
|
1300,
|
|
|
|
1400,
|
|
|
|
1450,
|
|
|
|
1500,
|
|
|
|
1550,
|
|
|
|
1600,
|
|
|
|
1700,
|
|
|
|
1800,
|
|
|
|
1900,
|
|
|
|
1950,
|
|
|
|
2000,
|
|
|
|
2050,
|
|
|
|
2100,
|
|
|
|
2200,
|
|
|
|
2300,
|
|
|
|
2400,
|
|
|
|
};
|
|
|
|
|
|
|
|
static const int fcc_comp_table_8994[] = {
|
2014-12-26 20:25:54 +00:00
|
|
|
250,
|
|
|
|
700,
|
|
|
|
900,
|
|
|
|
1200,
|
|
|
|
};
|
|
|
|
|
2015-11-26 07:39:46 +00:00
|
|
|
static const int fcc_comp_table_8996[] = {
|
|
|
|
250,
|
|
|
|
1100,
|
|
|
|
1200,
|
|
|
|
1500,
|
|
|
|
};
|
|
|
|
|
2015-11-26 09:51:36 +00:00
|
|
|
static const int aicl_rerun_period[] = {
|
|
|
|
45,
|
|
|
|
90,
|
|
|
|
180,
|
|
|
|
360,
|
|
|
|
};
|
|
|
|
|
2015-12-23 02:08:15 +00:00
|
|
|
static const int aicl_rerun_period_schg_lite[] = {
|
|
|
|
3, /* 2.8s */
|
|
|
|
6, /* 5.6s */
|
|
|
|
11, /* 11.3s */
|
|
|
|
23, /* 22.5s */
|
|
|
|
45,
|
|
|
|
90,
|
|
|
|
180,
|
|
|
|
360,
|
|
|
|
};
|
|
|
|
|
2015-11-26 07:39:46 +00:00
|
|
|
static void use_pmi8994_tables(struct smbchg_chip *chip)
|
|
|
|
{
|
|
|
|
chip->tables.usb_ilim_ma_table = usb_ilim_ma_table_8994;
|
|
|
|
chip->tables.usb_ilim_ma_len = ARRAY_SIZE(usb_ilim_ma_table_8994);
|
|
|
|
chip->tables.dc_ilim_ma_table = dc_ilim_ma_table_8994;
|
|
|
|
chip->tables.dc_ilim_ma_len = ARRAY_SIZE(dc_ilim_ma_table_8994);
|
|
|
|
chip->tables.iterm_ma_table = iterm_ma_table_8994;
|
|
|
|
chip->tables.iterm_ma_len = ARRAY_SIZE(iterm_ma_table_8994);
|
|
|
|
chip->tables.fcc_comp_table = fcc_comp_table_8994;
|
|
|
|
chip->tables.fcc_comp_len = ARRAY_SIZE(fcc_comp_table_8994);
|
|
|
|
chip->tables.rchg_thr_mv = 200;
|
2015-11-26 09:51:36 +00:00
|
|
|
chip->tables.aicl_rerun_period_table = aicl_rerun_period;
|
|
|
|
chip->tables.aicl_rerun_period_len = ARRAY_SIZE(aicl_rerun_period);
|
2015-11-26 07:39:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void use_pmi8996_tables(struct smbchg_chip *chip)
|
|
|
|
{
|
|
|
|
chip->tables.usb_ilim_ma_table = usb_ilim_ma_table_8996;
|
|
|
|
chip->tables.usb_ilim_ma_len = ARRAY_SIZE(usb_ilim_ma_table_8996);
|
|
|
|
chip->tables.dc_ilim_ma_table = dc_ilim_ma_table_8996;
|
|
|
|
chip->tables.dc_ilim_ma_len = ARRAY_SIZE(dc_ilim_ma_table_8996);
|
|
|
|
chip->tables.iterm_ma_table = iterm_ma_table_8996;
|
|
|
|
chip->tables.iterm_ma_len = ARRAY_SIZE(iterm_ma_table_8996);
|
|
|
|
chip->tables.fcc_comp_table = fcc_comp_table_8996;
|
|
|
|
chip->tables.fcc_comp_len = ARRAY_SIZE(fcc_comp_table_8996);
|
|
|
|
chip->tables.rchg_thr_mv = 150;
|
2015-11-26 09:51:36 +00:00
|
|
|
chip->tables.aicl_rerun_period_table = aicl_rerun_period;
|
|
|
|
chip->tables.aicl_rerun_period_len = ARRAY_SIZE(aicl_rerun_period);
|
2015-11-26 07:39:46 +00:00
|
|
|
}
|
|
|
|
|
2015-01-13 18:33:28 +00:00
|
|
|
#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);
|
|
|
|
}
|
|
|
|
|
2014-02-20 01:27:12 +00:00
|
|
|
#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
|
2015-06-03 22:21:25 +00:00
|
|
|
#define CURRENT_1500_MA 1500
|
2014-02-20 01:27:12 +00:00
|
|
|
#define SUSPEND_CURRENT_MA 2
|
2015-06-03 22:21:25 +00:00
|
|
|
#define ICL_OVERRIDE_BIT BIT(2)
|
2014-02-20 01:27:12 +00:00
|
|
|
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;
|
|
|
|
|
2015-08-25 18:17:24 +00:00
|
|
|
i = find_smaller_in_array(chip->tables.dc_ilim_ma_table,
|
|
|
|
current_ma, chip->tables.dc_ilim_ma_len);
|
2014-02-20 01:27:12 +00:00
|
|
|
|
|
|
|
if (i < 0) {
|
|
|
|
dev_err(chip->dev, "Cannot find %dma current_table\n",
|
|
|
|
current_ma);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
2014-06-14 02:12:25 +00:00
|
|
|
|
2015-11-26 07:39:46 +00:00
|
|
|
chip->dc_max_current_ma = chip->tables.dc_ilim_ma_table[i];
|
2014-06-14 02:12:25 +00:00
|
|
|
dc_cur_val = i & DCIN_INPUT_MASK;
|
|
|
|
|
2014-06-30 22:02:34 +00:00
|
|
|
pr_smb(PR_STATUS, "dc current set to %d mA\n",
|
2014-02-20 01:27:12 +00:00
|
|
|
chip->dc_max_current_ma);
|
|
|
|
return smbchg_sec_masked_write(chip, chip->dc_chgpth_base + IL_CFG,
|
|
|
|
DCIN_INPUT_MASK, dc_cur_val);
|
|
|
|
}
|
|
|
|
|
2015-11-26 09:51:36 +00:00
|
|
|
#define AICL_WL_SEL_CFG 0xF5
|
|
|
|
#define AICL_WL_SEL_MASK SMB_MASK(1, 0)
|
2015-12-23 02:08:15 +00:00
|
|
|
#define AICL_WL_SEL_SCHG_LITE_MASK SMB_MASK(2, 0)
|
2015-11-26 09:51:36 +00:00
|
|
|
static int smbchg_set_aicl_rerun_period_s(struct smbchg_chip *chip,
|
|
|
|
int period_s)
|
|
|
|
{
|
|
|
|
int i;
|
2015-12-23 02:08:15 +00:00
|
|
|
u8 reg, mask;
|
2015-11-26 09:51:36 +00:00
|
|
|
|
|
|
|
i = find_smaller_in_array(chip->tables.aicl_rerun_period_table,
|
|
|
|
period_s, chip->tables.aicl_rerun_period_len);
|
|
|
|
|
|
|
|
if (i < 0) {
|
|
|
|
dev_err(chip->dev, "Cannot find %ds in aicl rerun period\n",
|
|
|
|
period_s);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
2015-12-23 02:08:15 +00:00
|
|
|
if (chip->schg_version == QPNP_SCHG_LITE)
|
|
|
|
mask = AICL_WL_SEL_SCHG_LITE_MASK;
|
|
|
|
else
|
|
|
|
mask = AICL_WL_SEL_MASK;
|
|
|
|
|
|
|
|
reg = i & mask;
|
2015-11-26 09:51:36 +00:00
|
|
|
|
|
|
|
pr_smb(PR_STATUS, "aicl rerun period set to %ds\n",
|
|
|
|
chip->tables.aicl_rerun_period_table[i]);
|
|
|
|
return smbchg_sec_masked_write(chip,
|
|
|
|
chip->dc_chgpth_base + AICL_WL_SEL_CFG,
|
2015-12-23 02:08:15 +00:00
|
|
|
mask, reg);
|
2015-11-26 09:51:36 +00:00
|
|
|
}
|
|
|
|
|
2014-05-30 21:07:14 +00:00
|
|
|
static struct power_supply *get_parallel_psy(struct smbchg_chip *chip)
|
|
|
|
{
|
2014-08-06 18:27:04 +00:00
|
|
|
if (!chip->parallel.avail)
|
|
|
|
return NULL;
|
2014-05-30 21:07:14 +00:00
|
|
|
if (chip->parallel.psy)
|
|
|
|
return chip->parallel.psy;
|
|
|
|
chip->parallel.psy = power_supply_get_by_name("usb-parallel");
|
|
|
|
if (!chip->parallel.psy)
|
2014-06-30 22:02:34 +00:00
|
|
|
pr_smb(PR_STATUS, "parallel charger not found\n");
|
2014-05-30 21:07:14 +00:00
|
|
|
return chip->parallel.psy;
|
|
|
|
}
|
|
|
|
|
2014-02-20 01:27:12 +00:00
|
|
|
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);
|
2015-11-26 09:38:11 +00:00
|
|
|
bool user_enabled = !get_client_vote(chip->usb_suspend_votable,
|
|
|
|
USER_EN_VOTER);
|
power: qpnp-smbcharger: handle very weak chargers
The PMi charger peripheral can detect weak chargers through the automatic
input current limit (AICL) feature by observing how much current it
takes to collapse an external charger. Additionally, under very bad
conditions, such as having a very weak charger with a high impedance
cable, the AICL algorithm will actually stop altogether when the USB
voltage drops below the autoshutdown threshold.
Normally, this will trigger AICL suspend and shut off the charger buck.
However, if hardware AICL reruns are enabled, AICL cannot be suspended
in hardware. This can cause a problem where the USBIN_UV interrupt is
continuously received when the USBIN voltage hovers around the lower
limit of the valid range.
Fix this by detecting very weak chargers if the USBIN_UV is high at the
same time USBIN_SRC_DET is high. If both interrupts lines are high,
it means that the USB voltage is below the low threshold for UV but
higher than the coarse detect threshold (1V).
If a weak charger is detected, disable AICL rerun to allow the charger
to enter into AICL suspend, and set the USB health to UNSPEC_FAILURE,
and do not report USB online.
Also, do not allow USBIN_UV to reset the aicl_irq_count in order to
allow the battery OV interrupt to run correctly.
CRs-Fixed: 823770
Change-Id: If853659fa7aaf899f500d8933a773ab80d0464a1
Signed-off-by: Xiaozhe Shi <xiaozhes@codeaurora.org>
2015-04-15 21:39:43 +00:00
|
|
|
int online;
|
|
|
|
|
|
|
|
online = user_enabled && chip->usb_present && !chip->very_weak_charger;
|
2014-02-20 01:27:12 +00:00
|
|
|
|
|
|
|
mutex_lock(&chip->usb_set_online_lock);
|
|
|
|
if (chip->usb_online != online) {
|
2015-06-11 17:27:14 +00:00
|
|
|
pr_smb(PR_MISC, "setting usb psy online = %d\n", online);
|
2014-02-20 01:27:12 +00:00
|
|
|
power_supply_set_online(chip->usb_psy, online);
|
|
|
|
chip->usb_online = online;
|
|
|
|
}
|
|
|
|
mutex_unlock(&chip->usb_set_online_lock);
|
|
|
|
}
|
|
|
|
|
|
|
|
#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;
|
|
|
|
|
2015-09-14 20:51:25 +00:00
|
|
|
if (current_ma == CURRENT_100_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 CFG_USB_2 rc=%d\n", rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
rc = smbchg_masked_write(chip, chip->usb_chgpth_base + CMD_IL,
|
|
|
|
USBIN_MODE_CHG_BIT | USB51_MODE_BIT | ICL_OVERRIDE_BIT,
|
|
|
|
USBIN_LIMITED_MODE | USB51_100MA | ICL_OVERRIDE_BIT);
|
|
|
|
if (rc < 0) {
|
|
|
|
pr_err("Couldn't set ICL_OVERRIDE rc=%d\n", rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
pr_smb(PR_STATUS,
|
|
|
|
"Forcing 100mA current limit\n");
|
|
|
|
chip->usb_max_current_ma = CURRENT_100_MA;
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2015-08-25 18:17:24 +00:00
|
|
|
i = find_smaller_in_array(chip->tables.usb_ilim_ma_table,
|
|
|
|
current_ma, chip->tables.usb_ilim_ma_len);
|
2014-02-20 01:27:12 +00:00
|
|
|
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,
|
2014-12-17 22:30:21 +00:00
|
|
|
CFG_USB_2_3_SEL_BIT, CFG_USB_3);
|
2014-02-20 01:27:12 +00:00
|
|
|
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);
|
2015-11-26 07:39:46 +00:00
|
|
|
chip->usb_max_current_ma = chip->tables.usb_ilim_ma_table[i];
|
2014-02-20 01:27:12 +00:00
|
|
|
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)
|
|
|
|
{
|
2015-05-14 03:16:06 +00:00
|
|
|
int rc = 0;
|
2014-02-20 01:27:12 +00:00
|
|
|
|
2015-09-30 22:45:07 +00:00
|
|
|
/*
|
|
|
|
* if the battery is not present, do not allow the usb ICL to lower in
|
|
|
|
* order to avoid browning out the device during a hotswap.
|
|
|
|
*/
|
|
|
|
if (!chip->batt_present && current_ma < chip->usb_max_current_ma) {
|
2014-02-20 01:27:12 +00:00
|
|
|
pr_info_ratelimited("Ignoring usb current->%d, battery is absent\n",
|
|
|
|
current_ma);
|
|
|
|
return 0;
|
|
|
|
}
|
2014-06-30 22:02:34 +00:00
|
|
|
pr_smb(PR_STATUS, "USB current_ma = %d\n", current_ma);
|
2014-02-20 01:27:12 +00:00
|
|
|
|
2015-09-24 15:43:30 +00:00
|
|
|
if (current_ma <= SUSPEND_CURRENT_MA) {
|
|
|
|
/* suspend the usb if current <= 2mA */
|
2015-11-26 09:38:11 +00:00
|
|
|
rc = vote(chip->usb_suspend_votable, USB_EN_VOTER, true, 0);
|
2014-02-20 01:27:12 +00:00
|
|
|
chip->usb_max_current_ma = 0;
|
|
|
|
goto out;
|
|
|
|
} else {
|
2015-11-26 09:38:11 +00:00
|
|
|
rc = vote(chip->usb_suspend_votable, USB_EN_VOTER, false, 0);
|
2014-02-20 01:27:12 +00:00
|
|
|
}
|
power: qpnp-smbcharger: add workaround for UVLO resets
When using the PMI8994 smbcharger, usb removal or suspend can cause
an under voltage lock out (UVLO) due to a hardware bug.
The condition for the UVLO to happen is when the whole system is running
off the charger buck when the USB is either removed or suspended. This
case can easily happen when the system is running at medium loads
(300-400 mA) and the battery is considered missing, overtemp, or
overvoltage by the charger hardware.
When this happens, the charger stops trying to pump current into the
battery, and instead just draws power from the active charge path
(USB in ths case). However, when the input charge path is removed, either
by suspending it through software or by physically removing the USB
cable, the battery FET does not turn on fast enough in hardware to
supplement the system load. This causes a droop on VPH power, manifesting
itself as an UVLO reset.
The work around for this issue is to ensure the battery is supplementing
the current before a charge path removal event happens. Implement this by
forcing USB input current limit to be 100mA whenever the device is not
charging. More specifically, whenever a charger p2f-thresh interrupt
(pre-to-fastcharge event, charging starts) or a battery interface
chg-error interrupt (battery missing/overtemp/overvolt) happens, check
the charging status and if the device is not charging, force the charger
to only draw 100mA.
If the input current limit is at 100mA, the system will be drawing
current from the battery as long as the system load is higher than
100mA. Thus, when the USB is removed, the battery will already be
supplementing the system load, and there will not be an UVLO reset.
There are a few limitations to this work around.
1. If the system load is less than 100mA when the input charge
path is removed, there may still be a droop on VPH power due
to the battery FET still being open. However, this should not
cause an UVLO reset because the system load will not cause a
large enough droop to cause a UVLO reset.
2. If the input is removed at the exact instant a charger error
occurs (battery goes missing, battery becomes overtemp,
battery becomes overvoltage), and the interrupt does not get
serviced before the input is removed, an UVLO reset can still
happen. However, this is a hardware limitation and there is
no solution for this at this time.
3. Currently, the workaround for the DC input charge path is not
supported. Until this is implemented, DC charging and wipower
will be impacted.
CRs-Fixed: 667489
Change-Id: I1a185d126e1e6bb851e5981f8b49072e8f8c6f68
Signed-off-by: Xiaozhe Shi <xiaozhes@codeaurora.org>
2014-06-12 00:35:01 +00:00
|
|
|
|
2015-10-19 23:01:13 +00:00
|
|
|
switch (chip->usb_supply_type) {
|
2015-06-03 22:21:25 +00:00
|
|
|
case POWER_SUPPLY_TYPE_USB:
|
2015-07-05 04:26:35 +00:00
|
|
|
if ((current_ma < CURRENT_150_MA) &&
|
|
|
|
(chip->wa_flags & SMBCHG_USB100_WA))
|
|
|
|
current_ma = CURRENT_150_MA;
|
|
|
|
|
2016-03-25 17:56:39 +00:00
|
|
|
/* handle special SDP case when USB reports high current */
|
|
|
|
if (current_ma > CURRENT_900_MA) {
|
|
|
|
if (chip->cfg_override_usb_current) {
|
|
|
|
/*
|
|
|
|
* allow setting the current value as reported
|
|
|
|
* by USB driver.
|
|
|
|
*/
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
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 ICL override rc = %d\n",
|
|
|
|
rc);
|
|
|
|
} else {
|
|
|
|
/* default to 500mA */
|
|
|
|
current_ma = CURRENT_500_MA;
|
|
|
|
}
|
|
|
|
pr_smb(PR_STATUS,
|
|
|
|
"override_usb_current=%d current_ma set to %d\n",
|
|
|
|
chip->cfg_override_usb_current, current_ma);
|
|
|
|
}
|
|
|
|
|
2015-06-03 22:21:25 +00:00
|
|
|
if (current_ma < CURRENT_150_MA) {
|
|
|
|
/* force 100mA */
|
|
|
|
rc = smbchg_sec_masked_write(chip,
|
2014-02-20 01:27:12 +00:00
|
|
|
chip->usb_chgpth_base + CHGPTH_CFG,
|
|
|
|
CFG_USB_2_3_SEL_BIT, CFG_USB_2);
|
2015-06-03 22:21:25 +00:00
|
|
|
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,
|
2014-02-20 01:27:12 +00:00
|
|
|
USBIN_MODE_CHG_BIT | USB51_MODE_BIT,
|
|
|
|
USBIN_LIMITED_MODE | USB51_100MA);
|
2015-06-03 22:21:25 +00:00
|
|
|
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,
|
2014-02-20 01:27:12 +00:00
|
|
|
chip->usb_chgpth_base + CHGPTH_CFG,
|
|
|
|
CFG_USB_2_3_SEL_BIT, CFG_USB_3);
|
2015-06-03 22:21:25 +00:00
|
|
|
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,
|
2014-02-20 01:27:12 +00:00
|
|
|
USBIN_MODE_CHG_BIT | USB51_MODE_BIT,
|
|
|
|
USBIN_LIMITED_MODE | USB51_100MA);
|
2015-06-03 22:21:25 +00:00
|
|
|
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,
|
2014-02-20 01:27:12 +00:00
|
|
|
chip->usb_chgpth_base + CHGPTH_CFG,
|
|
|
|
CFG_USB_2_3_SEL_BIT, CFG_USB_2);
|
2015-06-03 22:21:25 +00:00
|
|
|
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,
|
2014-02-20 01:27:12 +00:00
|
|
|
USBIN_MODE_CHG_BIT | USB51_MODE_BIT,
|
|
|
|
USBIN_LIMITED_MODE | USB51_500MA);
|
2015-06-03 22:21:25 +00:00
|
|
|
if (rc < 0) {
|
|
|
|
pr_err("Couldn't set CMD_IL rc = %d\n", rc);
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
chip->usb_max_current_ma = 500;
|
|
|
|
}
|
|
|
|
if (current_ma == CURRENT_900_MA) {
|
|
|
|
rc = smbchg_sec_masked_write(chip,
|
2014-02-20 01:27:12 +00:00
|
|
|
chip->usb_chgpth_base + CHGPTH_CFG,
|
|
|
|
CFG_USB_2_3_SEL_BIT, CFG_USB_3);
|
2015-06-03 22:21:25 +00:00
|
|
|
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,
|
2014-02-20 01:27:12 +00:00
|
|
|
USBIN_MODE_CHG_BIT | USB51_MODE_BIT,
|
|
|
|
USBIN_LIMITED_MODE | USB51_500MA);
|
2015-06-03 22:21:25 +00:00
|
|
|
if (rc < 0) {
|
|
|
|
pr_err("Couldn't set CMD_IL rc = %d\n", rc);
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
chip->usb_max_current_ma = 900;
|
|
|
|
}
|
|
|
|
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;
|
2014-02-20 01:27:12 +00:00
|
|
|
}
|
|
|
|
|
2015-05-14 03:16:06 +00:00
|
|
|
out:
|
2015-10-19 23:01:13 +00:00
|
|
|
pr_smb(PR_STATUS, "usb type = %d current set to %d mA\n",
|
|
|
|
chip->usb_supply_type, chip->usb_max_current_ma);
|
2014-02-20 01:27:12 +00:00
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2015-02-11 14:23:46 +00:00
|
|
|
#define USBIN_HVDCP_STS 0x0C
|
|
|
|
#define USBIN_HVDCP_SEL_BIT BIT(4)
|
|
|
|
#define USBIN_HVDCP_SEL_9V_BIT BIT(1)
|
|
|
|
#define SCHG_LITE_USBIN_HVDCP_SEL_9V_BIT BIT(2)
|
|
|
|
#define SCHG_LITE_USBIN_HVDCP_SEL_BIT BIT(0)
|
2014-08-06 18:27:04 +00:00
|
|
|
static int smbchg_get_min_parallel_current_ma(struct smbchg_chip *chip)
|
|
|
|
{
|
|
|
|
int rc;
|
2015-02-11 14:23:46 +00:00
|
|
|
u8 reg, hvdcp_sel, hvdcp_sel_9v;
|
2014-08-06 18:27:04 +00:00
|
|
|
|
2014-09-27 01:35:53 +00:00
|
|
|
rc = smbchg_read(chip, ®,
|
|
|
|
chip->usb_chgpth_base + USBIN_HVDCP_STS, 1);
|
2014-08-06 18:27:04 +00:00
|
|
|
if (rc < 0) {
|
|
|
|
dev_err(chip->dev, "Couldn't read usb status rc = %d\n", rc);
|
|
|
|
return 0;
|
|
|
|
}
|
2015-02-11 14:23:46 +00:00
|
|
|
if (chip->schg_version == QPNP_SCHG_LITE) {
|
|
|
|
hvdcp_sel = SCHG_LITE_USBIN_HVDCP_SEL_BIT;
|
|
|
|
hvdcp_sel_9v = SCHG_LITE_USBIN_HVDCP_SEL_9V_BIT;
|
|
|
|
} else {
|
|
|
|
hvdcp_sel = USBIN_HVDCP_SEL_BIT;
|
|
|
|
hvdcp_sel_9v = USBIN_HVDCP_SEL_9V_BIT;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((reg & hvdcp_sel) && (reg & hvdcp_sel_9v))
|
2014-08-06 18:27:04 +00:00
|
|
|
return chip->parallel.min_9v_current_thr_ma;
|
2014-09-27 01:35:53 +00:00
|
|
|
return chip->parallel.min_current_thr_ma;
|
2014-08-06 18:27:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#define FCC_CFG 0xF2
|
|
|
|
#define FCC_500MA_VAL 0x4
|
|
|
|
#define FCC_MASK SMB_MASK(4, 0)
|
2014-11-04 15:09:26 +00:00
|
|
|
static int smbchg_set_fastchg_current_raw(struct smbchg_chip *chip,
|
2014-08-06 18:27:04 +00:00
|
|
|
int current_ma)
|
|
|
|
{
|
|
|
|
int i, rc;
|
|
|
|
u8 cur_val;
|
|
|
|
|
|
|
|
/* the fcc enumerations are the same as the usb currents */
|
2015-08-25 18:17:24 +00:00
|
|
|
i = find_smaller_in_array(chip->tables.usb_ilim_ma_table,
|
|
|
|
current_ma, chip->tables.usb_ilim_ma_len);
|
2014-08-06 18:27:04 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2015-09-17 22:07:37 +00:00
|
|
|
if (chip->tables.usb_ilim_ma_table[i] == chip->fastchg_current_ma) {
|
|
|
|
pr_smb(PR_STATUS, "skipping fastchg current request: %d\n",
|
|
|
|
chip->fastchg_current_ma);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-08-06 18:27:04 +00:00
|
|
|
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;
|
|
|
|
}
|
2015-03-05 20:30:30 +00:00
|
|
|
pr_smb(PR_STATUS, "fastcharge current requested %d, set to %d\n",
|
2015-11-26 07:39:46 +00:00
|
|
|
current_ma, chip->tables.usb_ilim_ma_table[cur_val]);
|
2014-08-06 18:27:04 +00:00
|
|
|
|
2015-11-26 07:39:46 +00:00
|
|
|
chip->fastchg_current_ma = chip->tables.usb_ilim_ma_table[cur_val];
|
2014-11-04 15:09:26 +00:00
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2015-09-29 21:53:51 +00:00
|
|
|
#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)
|
2015-10-07 01:05:56 +00:00
|
|
|
#define PARALLEL_REENABLE_TIMER_MS 1000
|
2015-09-29 21:53:51 +00:00
|
|
|
#define PARALLEL_CHG_THRESHOLD_CURRENT 1800
|
2015-10-21 21:03:40 +00:00
|
|
|
static bool smbchg_is_usbin_active_pwr_src(struct smbchg_chip *chip)
|
|
|
|
{
|
|
|
|
int rc;
|
|
|
|
u8 reg;
|
|
|
|
|
|
|
|
rc = smbchg_read(chip, ®,
|
|
|
|
chip->usb_chgpth_base + ICL_STS_2_REG, 1);
|
|
|
|
if (rc < 0) {
|
|
|
|
dev_err(chip->dev, "Could not read usb icl sts 2: %d\n", rc);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return !(reg & USBIN_SUSPEND_STS_BIT)
|
|
|
|
&& (reg & USBIN_ACTIVE_PWR_SRC_BIT);
|
|
|
|
}
|
2015-09-29 21:53:51 +00:00
|
|
|
|
2016-03-25 17:59:39 +00:00
|
|
|
static void smbchg_detect_parallel_charger(struct smbchg_chip *chip)
|
|
|
|
{
|
|
|
|
struct power_supply *parallel_psy = get_parallel_psy(chip);
|
|
|
|
|
|
|
|
if (parallel_psy)
|
|
|
|
chip->parallel_charger_detected =
|
|
|
|
power_supply_set_present(parallel_psy, true) ?
|
|
|
|
false : true;
|
|
|
|
}
|
|
|
|
|
2014-11-04 15:09:26 +00:00
|
|
|
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, };
|
|
|
|
|
2015-05-26 07:03:45 +00:00
|
|
|
if (!parallel_psy || !chip->parallel_charger_detected)
|
2014-11-04 15:09:26 +00:00
|
|
|
return 0;
|
|
|
|
|
|
|
|
pval.intval = en;
|
|
|
|
return parallel_psy->set_property(parallel_psy,
|
|
|
|
POWER_SUPPLY_PROP_CHARGING_ENABLED, &pval);
|
|
|
|
}
|
|
|
|
|
2015-11-26 07:54:21 +00:00
|
|
|
#define ESR_PULSE_CURRENT_DELTA_MA 200
|
2014-11-04 15:09:26 +00:00
|
|
|
static int smbchg_sw_esr_pulse_en(struct smbchg_chip *chip, bool en)
|
|
|
|
{
|
2015-11-26 07:54:21 +00:00
|
|
|
int rc, fg_current_now, icl_ma;
|
2014-11-04 15:09:26 +00:00
|
|
|
|
2015-11-26 07:54:21 +00:00
|
|
|
rc = get_property_from_fg(chip, POWER_SUPPLY_PROP_CURRENT_NOW,
|
|
|
|
&fg_current_now);
|
2015-09-17 22:07:37 +00:00
|
|
|
if (rc) {
|
2015-11-26 07:54:21 +00:00
|
|
|
pr_smb(PR_STATUS, "bms psy does not support OCV\n");
|
|
|
|
return 0;
|
2015-09-17 22:07:37 +00:00
|
|
|
}
|
2015-11-26 07:54:21 +00:00
|
|
|
|
|
|
|
icl_ma = max(chip->iterm_ma + ESR_PULSE_CURRENT_DELTA_MA,
|
|
|
|
fg_current_now - ESR_PULSE_CURRENT_DELTA_MA);
|
|
|
|
rc = vote(chip->fcc_votable, ESR_PULSE_FCC_VOTER, en, icl_ma);
|
|
|
|
if (rc < 0) {
|
|
|
|
pr_err("Couldn't Vote FCC en = %d rc = %d\n", en, rc);
|
2014-11-04 15:09:26 +00:00
|
|
|
return rc;
|
2015-11-26 07:54:21 +00:00
|
|
|
}
|
2014-11-04 15:09:26 +00:00
|
|
|
rc = smbchg_parallel_usb_charging_en(chip, !en);
|
2014-08-06 18:27:04 +00:00
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
#define USB_AICL_CFG 0xF3
|
|
|
|
#define AICL_EN_BIT BIT(2)
|
|
|
|
static void smbchg_rerun_aicl(struct smbchg_chip *chip)
|
|
|
|
{
|
2015-03-03 01:38:22 +00:00
|
|
|
pr_smb(PR_STATUS, "Rerunning AICL...\n");
|
2014-08-06 18:27:04 +00:00
|
|
|
smbchg_sec_masked_write(chip, chip->usb_chgpth_base + USB_AICL_CFG,
|
|
|
|
AICL_EN_BIT, 0);
|
2014-10-15 17:41:50 +00:00
|
|
|
/* Add a delay so that AICL successfully clears */
|
|
|
|
msleep(50);
|
2014-08-06 18:27:04 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2015-09-29 21:53:51 +00:00
|
|
|
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 < 0) {
|
|
|
|
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 >= chip->tables.usb_ilim_ma_len) {
|
|
|
|
pr_warn("invalid AICL value: %02x\n", reg);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
return chip->tables.usb_ilim_ma_table[reg];
|
|
|
|
}
|
|
|
|
|
2014-08-06 18:27:04 +00:00
|
|
|
static void smbchg_parallel_usb_disable(struct smbchg_chip *chip)
|
|
|
|
{
|
|
|
|
struct power_supply *parallel_psy = get_parallel_psy(chip);
|
|
|
|
|
2015-05-26 07:03:45 +00:00
|
|
|
if (!parallel_psy || !chip->parallel_charger_detected)
|
2014-08-06 18:27:04 +00:00
|
|
|
return;
|
|
|
|
pr_smb(PR_STATUS, "disabling parallel charger\n");
|
2015-03-30 21:20:44 +00:00
|
|
|
chip->parallel.last_disabled = ktime_get_boottime();
|
2014-08-06 18:27:04 +00:00
|
|
|
taper_irq_en(chip, false);
|
2014-10-09 01:36:33 +00:00
|
|
|
chip->parallel.initial_aicl_ma = 0;
|
2014-08-06 18:27:04 +00:00
|
|
|
chip->parallel.current_max_ma = 0;
|
|
|
|
power_supply_set_current_limit(parallel_psy,
|
|
|
|
SUSPEND_CURRENT_MA * 1000);
|
|
|
|
power_supply_set_present(parallel_psy, false);
|
2015-11-26 07:54:21 +00:00
|
|
|
smbchg_set_fastchg_current_raw(chip,
|
|
|
|
get_effective_result_locked(chip->fcc_votable));
|
2015-11-26 09:07:24 +00:00
|
|
|
smbchg_set_usb_current_max(chip,
|
|
|
|
get_effective_result_locked(chip->usb_icl_votable));
|
2014-08-06 18:27:04 +00:00
|
|
|
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;
|
|
|
|
|
2016-03-25 17:59:39 +00:00
|
|
|
smbchg_detect_parallel_charger(chip);
|
|
|
|
if (!chip->parallel_charger_detected)
|
2014-08-06 18:27:04 +00:00
|
|
|
return;
|
|
|
|
|
2015-07-24 01:30:38 +00:00
|
|
|
smbchg_stay_awake(chip, PM_PARALLEL_TAPER);
|
2014-08-06 18:27:04 +00:00
|
|
|
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;
|
|
|
|
}
|
2015-03-10 02:58:05 +00:00
|
|
|
pval.intval = ((parallel_fcc_ma
|
2014-08-06 18:27:04 +00:00
|
|
|
* PARALLEL_FCC_PERCENT_REDUCTION) / 100);
|
2015-03-10 02:58:05 +00:00
|
|
|
pr_smb(PR_STATUS, "reducing FCC of parallel charger to %d\n",
|
|
|
|
pval.intval);
|
|
|
|
/* Change it to uA */
|
|
|
|
pval.intval *= 1000;
|
2014-08-06 18:27:04 +00:00
|
|
|
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);
|
2014-10-15 17:41:50 +00:00
|
|
|
if (reg & BAT_TAPER_MODE_BIT) {
|
|
|
|
mutex_unlock(&chip->parallel.lock);
|
2014-08-06 18:27:04 +00:00
|
|
|
goto try_again;
|
2014-10-15 17:41:50 +00:00
|
|
|
}
|
2014-08-06 18:27:04 +00:00
|
|
|
taper_irq_en(chip, true);
|
|
|
|
done:
|
|
|
|
mutex_unlock(&chip->parallel.lock);
|
2015-07-24 01:30:38 +00:00
|
|
|
smbchg_relax(chip, PM_PARALLEL_TAPER);
|
2014-08-06 18:27:04 +00:00
|
|
|
}
|
|
|
|
|
2015-09-29 21:53:51 +00:00
|
|
|
static void smbchg_parallel_usb_enable(struct smbchg_chip *chip,
|
|
|
|
int total_current_ma)
|
2015-03-03 01:38:22 +00:00
|
|
|
{
|
2015-09-29 21:53:51 +00:00
|
|
|
struct power_supply *parallel_psy = get_parallel_psy(chip);
|
|
|
|
union power_supply_propval pval = {0, };
|
power: qpnp-smbcharger: allow icl to recover during parallel charging
Currently, if the charger gets a lower AICL setting due to a temporary
glitch or dip in the amount of current provided by the charging source,
the parallel charger can never recover the original AICL setting. This
is because the primary charger caps the input current limit to half of
the original AICL result for both the parallel charger and the primary
charger. Subsequent runs of AICL during parallel charging can never
exceed the original ICL setpoint because AICL can never be higher than
the configured ICL.
Fix this by setting the primary charger to (targeted ICL - parallel ICL).
This way, if the AICL is reran, either through the hardware feature or
through software, any extra available current can be recovered by the
algorithm to be rebalanced again.
This change can cause the AICL to collapse multiple times after enabling
parallel charging, so also change the algorithm to make sure the AICL is
settled before changing parallel charging. More specifically, when the
algorithm needs to change parallel charging settings, it will take two
AICL samples with 500ms between each sample. It will wait until
these two samples match before making any changes.
Furthermore, since the AICL is now more stable, we can remove the 100mA
decrements to the parallel charging current, and allow it to go to the
requested amount immediately.
Finally, remove the AICL rerun at the end of smbchg_parallel_usb_enable()
since AICL results can no longer be higher than the input current limit
anyways.
CRs-Fixed: 920791
Change-Id: I93e9f926b6f5ed520e4d6c2495087df25debf29d
Signed-off-by: Xiaozhe Shi <xiaozhes@codeaurora.org>
2015-10-08 23:35:32 +00:00
|
|
|
int new_parallel_cl_ma, set_parallel_cl_ma, new_pmi_cl_ma, rc;
|
2015-10-23 00:03:13 +00:00
|
|
|
int current_table_index, target_icl_ma;
|
|
|
|
int fcc_ma, main_fastchg_current_ma;
|
|
|
|
int target_parallel_fcc_ma, supplied_parallel_fcc_ma;
|
|
|
|
int parallel_chg_fcc_percent;
|
2015-03-03 01:38:22 +00:00
|
|
|
|
2015-09-29 21:53:51 +00:00
|
|
|
if (!parallel_psy || !chip->parallel_charger_detected)
|
|
|
|
return;
|
|
|
|
|
|
|
|
pr_smb(PR_STATUS, "Attempting to enable parallel charger\n");
|
|
|
|
|
|
|
|
rc = power_supply_set_voltage_limit(parallel_psy, chip->vfloat_mv + 50);
|
|
|
|
if (rc < 0) {
|
|
|
|
dev_err(chip->dev,
|
|
|
|
"Couldn't set Vflt on parallel psy rc: %d\n", rc);
|
|
|
|
return;
|
2015-03-03 01:38:22 +00:00
|
|
|
}
|
2015-09-29 21:53:51 +00:00
|
|
|
/* Set USB ICL */
|
power: qpnp-smbcharger: allow icl to recover during parallel charging
Currently, if the charger gets a lower AICL setting due to a temporary
glitch or dip in the amount of current provided by the charging source,
the parallel charger can never recover the original AICL setting. This
is because the primary charger caps the input current limit to half of
the original AICL result for both the parallel charger and the primary
charger. Subsequent runs of AICL during parallel charging can never
exceed the original ICL setpoint because AICL can never be higher than
the configured ICL.
Fix this by setting the primary charger to (targeted ICL - parallel ICL).
This way, if the AICL is reran, either through the hardware feature or
through software, any extra available current can be recovered by the
algorithm to be rebalanced again.
This change can cause the AICL to collapse multiple times after enabling
parallel charging, so also change the algorithm to make sure the AICL is
settled before changing parallel charging. More specifically, when the
algorithm needs to change parallel charging settings, it will take two
AICL samples with 500ms between each sample. It will wait until
these two samples match before making any changes.
Furthermore, since the AICL is now more stable, we can remove the 100mA
decrements to the parallel charging current, and allow it to go to the
requested amount immediately.
Finally, remove the AICL rerun at the end of smbchg_parallel_usb_enable()
since AICL results can no longer be higher than the input current limit
anyways.
CRs-Fixed: 920791
Change-Id: I93e9f926b6f5ed520e4d6c2495087df25debf29d
Signed-off-by: Xiaozhe Shi <xiaozhes@codeaurora.org>
2015-10-08 23:35:32 +00:00
|
|
|
target_icl_ma = get_effective_result_locked(chip->usb_icl_votable);
|
2015-10-23 00:03:13 +00:00
|
|
|
new_parallel_cl_ma = total_current_ma
|
|
|
|
* (100 - smbchg_main_chg_icl_percent) / 100;
|
2015-09-29 21:53:51 +00:00
|
|
|
taper_irq_en(chip, true);
|
|
|
|
power_supply_set_present(parallel_psy, true);
|
|
|
|
power_supply_set_current_limit(parallel_psy,
|
power: qpnp-smbcharger: allow icl to recover during parallel charging
Currently, if the charger gets a lower AICL setting due to a temporary
glitch or dip in the amount of current provided by the charging source,
the parallel charger can never recover the original AICL setting. This
is because the primary charger caps the input current limit to half of
the original AICL result for both the parallel charger and the primary
charger. Subsequent runs of AICL during parallel charging can never
exceed the original ICL setpoint because AICL can never be higher than
the configured ICL.
Fix this by setting the primary charger to (targeted ICL - parallel ICL).
This way, if the AICL is reran, either through the hardware feature or
through software, any extra available current can be recovered by the
algorithm to be rebalanced again.
This change can cause the AICL to collapse multiple times after enabling
parallel charging, so also change the algorithm to make sure the AICL is
settled before changing parallel charging. More specifically, when the
algorithm needs to change parallel charging settings, it will take two
AICL samples with 500ms between each sample. It will wait until
these two samples match before making any changes.
Furthermore, since the AICL is now more stable, we can remove the 100mA
decrements to the parallel charging current, and allow it to go to the
requested amount immediately.
Finally, remove the AICL rerun at the end of smbchg_parallel_usb_enable()
since AICL results can no longer be higher than the input current limit
anyways.
CRs-Fixed: 920791
Change-Id: I93e9f926b6f5ed520e4d6c2495087df25debf29d
Signed-off-by: Xiaozhe Shi <xiaozhes@codeaurora.org>
2015-10-08 23:35:32 +00:00
|
|
|
new_parallel_cl_ma * 1000);
|
|
|
|
/* read back the real amount of current we are getting */
|
|
|
|
parallel_psy->get_property(parallel_psy,
|
|
|
|
POWER_SUPPLY_PROP_CURRENT_MAX, &pval);
|
|
|
|
set_parallel_cl_ma = pval.intval / 1000;
|
|
|
|
chip->parallel.current_max_ma = new_parallel_cl_ma;
|
2015-10-23 00:03:13 +00:00
|
|
|
pr_smb(PR_MISC, "Requested ICL = %d from parallel, got %d\n",
|
power: qpnp-smbcharger: allow icl to recover during parallel charging
Currently, if the charger gets a lower AICL setting due to a temporary
glitch or dip in the amount of current provided by the charging source,
the parallel charger can never recover the original AICL setting. This
is because the primary charger caps the input current limit to half of
the original AICL result for both the parallel charger and the primary
charger. Subsequent runs of AICL during parallel charging can never
exceed the original ICL setpoint because AICL can never be higher than
the configured ICL.
Fix this by setting the primary charger to (targeted ICL - parallel ICL).
This way, if the AICL is reran, either through the hardware feature or
through software, any extra available current can be recovered by the
algorithm to be rebalanced again.
This change can cause the AICL to collapse multiple times after enabling
parallel charging, so also change the algorithm to make sure the AICL is
settled before changing parallel charging. More specifically, when the
algorithm needs to change parallel charging settings, it will take two
AICL samples with 500ms between each sample. It will wait until
these two samples match before making any changes.
Furthermore, since the AICL is now more stable, we can remove the 100mA
decrements to the parallel charging current, and allow it to go to the
requested amount immediately.
Finally, remove the AICL rerun at the end of smbchg_parallel_usb_enable()
since AICL results can no longer be higher than the input current limit
anyways.
CRs-Fixed: 920791
Change-Id: I93e9f926b6f5ed520e4d6c2495087df25debf29d
Signed-off-by: Xiaozhe Shi <xiaozhes@codeaurora.org>
2015-10-08 23:35:32 +00:00
|
|
|
new_parallel_cl_ma, set_parallel_cl_ma);
|
|
|
|
new_pmi_cl_ma = max(0, target_icl_ma - set_parallel_cl_ma);
|
|
|
|
pr_smb(PR_STATUS, "New Total USB current = %d[%d, %d]\n",
|
|
|
|
total_current_ma, new_pmi_cl_ma,
|
|
|
|
set_parallel_cl_ma);
|
|
|
|
smbchg_set_usb_current_max(chip, new_pmi_cl_ma);
|
2015-09-30 23:53:26 +00:00
|
|
|
|
2015-10-23 00:03:13 +00:00
|
|
|
/* begin splitting the fast charge current */
|
|
|
|
fcc_ma = get_effective_result_locked(chip->fcc_votable);
|
|
|
|
parallel_chg_fcc_percent =
|
|
|
|
100 - smbchg_main_chg_fcc_percent;
|
|
|
|
target_parallel_fcc_ma =
|
|
|
|
(fcc_ma * parallel_chg_fcc_percent) / 100;
|
|
|
|
pval.intval = target_parallel_fcc_ma * 1000;
|
|
|
|
parallel_psy->set_property(parallel_psy,
|
|
|
|
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, &pval);
|
|
|
|
/* check how much actual current is supplied by the parallel charger */
|
|
|
|
parallel_psy->get_property(parallel_psy,
|
|
|
|
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, &pval);
|
|
|
|
supplied_parallel_fcc_ma = pval.intval / 1000;
|
|
|
|
pr_smb(PR_MISC, "Requested FCC = %d from parallel, got %d\n",
|
|
|
|
target_parallel_fcc_ma, supplied_parallel_fcc_ma);
|
|
|
|
|
|
|
|
/* then for the main charger, use the left over FCC */
|
|
|
|
current_table_index = find_smaller_in_array(
|
|
|
|
chip->tables.usb_ilim_ma_table,
|
|
|
|
fcc_ma - supplied_parallel_fcc_ma,
|
|
|
|
chip->tables.usb_ilim_ma_len);
|
|
|
|
main_fastchg_current_ma =
|
|
|
|
chip->tables.usb_ilim_ma_table[current_table_index];
|
|
|
|
smbchg_set_fastchg_current_raw(chip, main_fastchg_current_ma);
|
|
|
|
pr_smb(PR_STATUS, "FCC = %d[%d, %d]\n", fcc_ma, main_fastchg_current_ma,
|
|
|
|
supplied_parallel_fcc_ma);
|
|
|
|
|
|
|
|
chip->parallel.enabled_once = true;
|
|
|
|
|
2015-09-29 21:53:51 +00:00
|
|
|
return;
|
2015-03-03 01:38:22 +00:00
|
|
|
}
|
|
|
|
|
2015-09-29 21:53:51 +00:00
|
|
|
static bool smbchg_is_parallel_usb_ok(struct smbchg_chip *chip,
|
|
|
|
int *ret_total_current_ma)
|
2014-08-06 18:27:04 +00:00
|
|
|
{
|
2015-09-29 21:53:51 +00:00
|
|
|
struct power_supply *parallel_psy = get_parallel_psy(chip);
|
power: qpnp-smbcharger: allow icl to recover during parallel charging
Currently, if the charger gets a lower AICL setting due to a temporary
glitch or dip in the amount of current provided by the charging source,
the parallel charger can never recover the original AICL setting. This
is because the primary charger caps the input current limit to half of
the original AICL result for both the parallel charger and the primary
charger. Subsequent runs of AICL during parallel charging can never
exceed the original ICL setpoint because AICL can never be higher than
the configured ICL.
Fix this by setting the primary charger to (targeted ICL - parallel ICL).
This way, if the AICL is reran, either through the hardware feature or
through software, any extra available current can be recovered by the
algorithm to be rebalanced again.
This change can cause the AICL to collapse multiple times after enabling
parallel charging, so also change the algorithm to make sure the AICL is
settled before changing parallel charging. More specifically, when the
algorithm needs to change parallel charging settings, it will take two
AICL samples with 500ms between each sample. It will wait until
these two samples match before making any changes.
Furthermore, since the AICL is now more stable, we can remove the 100mA
decrements to the parallel charging current, and allow it to go to the
requested amount immediately.
Finally, remove the AICL rerun at the end of smbchg_parallel_usb_enable()
since AICL results can no longer be higher than the input current limit
anyways.
CRs-Fixed: 920791
Change-Id: I93e9f926b6f5ed520e4d6c2495087df25debf29d
Signed-off-by: Xiaozhe Shi <xiaozhes@codeaurora.org>
2015-10-08 23:35:32 +00:00
|
|
|
union power_supply_propval pval = {0, };
|
2015-09-29 21:53:51 +00:00
|
|
|
int min_current_thr_ma, rc, type;
|
|
|
|
int total_current_ma, current_limit_ma, parallel_cl_ma;
|
|
|
|
ktime_t kt_since_last_disable;
|
2014-08-06 18:27:04 +00:00
|
|
|
u8 reg;
|
2015-11-26 07:54:21 +00:00
|
|
|
int fcc_ma = get_effective_result_locked(chip->fcc_votable);
|
power: qpnp-smbcharger: allow icl to recover during parallel charging
Currently, if the charger gets a lower AICL setting due to a temporary
glitch or dip in the amount of current provided by the charging source,
the parallel charger can never recover the original AICL setting. This
is because the primary charger caps the input current limit to half of
the original AICL result for both the parallel charger and the primary
charger. Subsequent runs of AICL during parallel charging can never
exceed the original ICL setpoint because AICL can never be higher than
the configured ICL.
Fix this by setting the primary charger to (targeted ICL - parallel ICL).
This way, if the AICL is reran, either through the hardware feature or
through software, any extra available current can be recovered by the
algorithm to be rebalanced again.
This change can cause the AICL to collapse multiple times after enabling
parallel charging, so also change the algorithm to make sure the AICL is
settled before changing parallel charging. More specifically, when the
algorithm needs to change parallel charging settings, it will take two
AICL samples with 500ms between each sample. It will wait until
these two samples match before making any changes.
Furthermore, since the AICL is now more stable, we can remove the 100mA
decrements to the parallel charging current, and allow it to go to the
requested amount immediately.
Finally, remove the AICL rerun at the end of smbchg_parallel_usb_enable()
since AICL results can no longer be higher than the input current limit
anyways.
CRs-Fixed: 920791
Change-Id: I93e9f926b6f5ed520e4d6c2495087df25debf29d
Signed-off-by: Xiaozhe Shi <xiaozhes@codeaurora.org>
2015-10-08 23:35:32 +00:00
|
|
|
int fcc_voter_id = get_effective_client_id_locked(chip->fcc_votable);
|
2015-11-26 09:07:24 +00:00
|
|
|
int usb_icl_ma = get_effective_result_locked(chip->usb_icl_votable);
|
2014-08-06 18:27:04 +00:00
|
|
|
|
2015-09-29 21:53:51 +00:00
|
|
|
if (!parallel_psy || !smbchg_parallel_en
|
|
|
|
|| !chip->parallel_charger_detected) {
|
|
|
|
pr_smb(PR_STATUS, "Parallel charging not enabled\n");
|
|
|
|
return false;
|
2014-08-06 18:27:04 +00:00
|
|
|
}
|
2015-09-29 21:53:51 +00:00
|
|
|
|
|
|
|
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;
|
2015-03-10 18:03:32 +00:00
|
|
|
}
|
2015-09-29 21:53:51 +00:00
|
|
|
|
2015-09-30 22:45:07 +00:00
|
|
|
/*
|
|
|
|
* If the battery is not present, try not to change parallel charging
|
|
|
|
* from OFF to ON or from ON to OFF, as it could cause the device to
|
|
|
|
* brown out in the instant that the USB settings are changed.
|
|
|
|
*
|
|
|
|
* Only allow parallel charging check to report false (thereby turnin
|
|
|
|
* off parallel charging) if the battery is still there, or if parallel
|
|
|
|
* charging is disabled in the first place.
|
|
|
|
*/
|
|
|
|
if (get_prop_charge_type(chip) != POWER_SUPPLY_CHARGE_TYPE_FAST
|
|
|
|
&& (get_prop_batt_present(chip)
|
|
|
|
|| chip->parallel.current_max_ma == 0)) {
|
2015-09-29 21:53:51 +00:00
|
|
|
pr_smb(PR_STATUS, "Not in fast charge, skipping\n");
|
|
|
|
return false;
|
2014-08-06 18:27:04 +00:00
|
|
|
}
|
|
|
|
|
2015-09-29 21:53:51 +00:00
|
|
|
if (get_prop_batt_health(chip) != POWER_SUPPLY_HEALTH_GOOD) {
|
|
|
|
pr_smb(PR_STATUS, "JEITA active, skipping\n");
|
|
|
|
return false;
|
|
|
|
}
|
2014-08-06 18:27:04 +00:00
|
|
|
|
2015-09-29 21:53:51 +00:00
|
|
|
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;
|
|
|
|
}
|
2014-08-06 18:27:04 +00:00
|
|
|
|
2015-09-29 21:53:51 +00:00
|
|
|
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;
|
2015-03-05 20:30:30 +00:00
|
|
|
}
|
2015-09-29 21:53:51 +00:00
|
|
|
|
|
|
|
if (get_usb_supply_type(type) == POWER_SUPPLY_TYPE_USB) {
|
|
|
|
pr_smb(PR_STATUS, "SDP adapter, skipping\n");
|
|
|
|
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.
|
|
|
|
*/
|
2015-10-21 21:03:40 +00:00
|
|
|
if (!smbchg_is_usbin_active_pwr_src(chip)) {
|
2015-09-29 21:53:51 +00:00
|
|
|
pr_smb(PR_STATUS, "USB not active power source: %02x\n", reg);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2014-08-06 18:27:04 +00:00
|
|
|
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);
|
2015-09-29 21:53:51 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2015-11-26 09:07:24 +00:00
|
|
|
if (usb_icl_ma < min_current_thr_ma) {
|
2015-09-29 21:53:51 +00:00
|
|
|
pr_smb(PR_STATUS, "Weak USB chg skip enable: %d < %d\n",
|
2015-11-26 09:07:24 +00:00
|
|
|
usb_icl_ma, min_current_thr_ma);
|
2015-09-29 21:53:51 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
power: qpnp-smbcharger: allow icl to recover during parallel charging
Currently, if the charger gets a lower AICL setting due to a temporary
glitch or dip in the amount of current provided by the charging source,
the parallel charger can never recover the original AICL setting. This
is because the primary charger caps the input current limit to half of
the original AICL result for both the parallel charger and the primary
charger. Subsequent runs of AICL during parallel charging can never
exceed the original ICL setpoint because AICL can never be higher than
the configured ICL.
Fix this by setting the primary charger to (targeted ICL - parallel ICL).
This way, if the AICL is reran, either through the hardware feature or
through software, any extra available current can be recovered by the
algorithm to be rebalanced again.
This change can cause the AICL to collapse multiple times after enabling
parallel charging, so also change the algorithm to make sure the AICL is
settled before changing parallel charging. More specifically, when the
algorithm needs to change parallel charging settings, it will take two
AICL samples with 500ms between each sample. It will wait until
these two samples match before making any changes.
Furthermore, since the AICL is now more stable, we can remove the 100mA
decrements to the parallel charging current, and allow it to go to the
requested amount immediately.
Finally, remove the AICL rerun at the end of smbchg_parallel_usb_enable()
since AICL results can no longer be higher than the input current limit
anyways.
CRs-Fixed: 920791
Change-Id: I93e9f926b6f5ed520e4d6c2495087df25debf29d
Signed-off-by: Xiaozhe Shi <xiaozhes@codeaurora.org>
2015-10-08 23:35:32 +00:00
|
|
|
/*
|
|
|
|
* Suspend the parallel charger if the charging current is < 1800 mA
|
|
|
|
* and is not because of an ESR pulse.
|
|
|
|
*/
|
|
|
|
if (fcc_voter_id != ESR_PULSE_FCC_VOTER
|
|
|
|
&& fcc_ma < PARALLEL_CHG_THRESHOLD_CURRENT) {
|
2015-09-29 21:53:51 +00:00
|
|
|
pr_smb(PR_STATUS, "FCC %d lower than %d\n",
|
2015-11-26 07:54:21 +00:00
|
|
|
fcc_ma,
|
2015-09-29 21:53:51 +00:00
|
|
|
PARALLEL_CHG_THRESHOLD_CURRENT);
|
|
|
|
return false;
|
2014-08-06 18:27:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
current_limit_ma = smbchg_get_aicl_level_ma(chip);
|
|
|
|
if (current_limit_ma <= 0)
|
2015-09-29 21:53:51 +00:00
|
|
|
return false;
|
2014-08-06 18:27:04 +00:00
|
|
|
|
2014-10-09 01:36:33 +00:00
|
|
|
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);
|
2015-09-29 21:53:51 +00:00
|
|
|
return false;
|
2014-10-09 01:36:33 +00:00
|
|
|
}
|
|
|
|
chip->parallel.initial_aicl_ma = current_limit_ma;
|
|
|
|
}
|
|
|
|
|
power: qpnp-smbcharger: allow icl to recover during parallel charging
Currently, if the charger gets a lower AICL setting due to a temporary
glitch or dip in the amount of current provided by the charging source,
the parallel charger can never recover the original AICL setting. This
is because the primary charger caps the input current limit to half of
the original AICL result for both the parallel charger and the primary
charger. Subsequent runs of AICL during parallel charging can never
exceed the original ICL setpoint because AICL can never be higher than
the configured ICL.
Fix this by setting the primary charger to (targeted ICL - parallel ICL).
This way, if the AICL is reran, either through the hardware feature or
through software, any extra available current can be recovered by the
algorithm to be rebalanced again.
This change can cause the AICL to collapse multiple times after enabling
parallel charging, so also change the algorithm to make sure the AICL is
settled before changing parallel charging. More specifically, when the
algorithm needs to change parallel charging settings, it will take two
AICL samples with 500ms between each sample. It will wait until
these two samples match before making any changes.
Furthermore, since the AICL is now more stable, we can remove the 100mA
decrements to the parallel charging current, and allow it to go to the
requested amount immediately.
Finally, remove the AICL rerun at the end of smbchg_parallel_usb_enable()
since AICL results can no longer be higher than the input current limit
anyways.
CRs-Fixed: 920791
Change-Id: I93e9f926b6f5ed520e4d6c2495087df25debf29d
Signed-off-by: Xiaozhe Shi <xiaozhes@codeaurora.org>
2015-10-08 23:35:32 +00:00
|
|
|
parallel_psy->get_property(parallel_psy,
|
|
|
|
POWER_SUPPLY_PROP_CURRENT_MAX, &pval);
|
|
|
|
parallel_cl_ma = pval.intval / 1000;
|
2014-08-06 18:27:04 +00:00
|
|
|
/*
|
power: qpnp-smbcharger: allow icl to recover during parallel charging
Currently, if the charger gets a lower AICL setting due to a temporary
glitch or dip in the amount of current provided by the charging source,
the parallel charger can never recover the original AICL setting. This
is because the primary charger caps the input current limit to half of
the original AICL result for both the parallel charger and the primary
charger. Subsequent runs of AICL during parallel charging can never
exceed the original ICL setpoint because AICL can never be higher than
the configured ICL.
Fix this by setting the primary charger to (targeted ICL - parallel ICL).
This way, if the AICL is reran, either through the hardware feature or
through software, any extra available current can be recovered by the
algorithm to be rebalanced again.
This change can cause the AICL to collapse multiple times after enabling
parallel charging, so also change the algorithm to make sure the AICL is
settled before changing parallel charging. More specifically, when the
algorithm needs to change parallel charging settings, it will take two
AICL samples with 500ms between each sample. It will wait until
these two samples match before making any changes.
Furthermore, since the AICL is now more stable, we can remove the 100mA
decrements to the parallel charging current, and allow it to go to the
requested amount immediately.
Finally, remove the AICL rerun at the end of smbchg_parallel_usb_enable()
since AICL results can no longer be higher than the input current limit
anyways.
CRs-Fixed: 920791
Change-Id: I93e9f926b6f5ed520e4d6c2495087df25debf29d
Signed-off-by: Xiaozhe Shi <xiaozhes@codeaurora.org>
2015-10-08 23:35:32 +00:00
|
|
|
* Read back the real amount of current we are getting
|
2014-08-06 18:27:04 +00:00
|
|
|
* Treat 2mA as 0 because that is the suspend current setting
|
|
|
|
*/
|
|
|
|
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
|
|
|
|
*/
|
2015-10-23 00:03:13 +00:00
|
|
|
total_current_ma = min(current_limit_ma + parallel_cl_ma, usb_icl_ma);
|
2014-08-06 18:27:04 +00:00
|
|
|
|
2014-10-09 01:36:33 +00:00
|
|
|
if (total_current_ma < chip->parallel.initial_aicl_ma
|
|
|
|
- chip->parallel.allowed_lowering_ma) {
|
|
|
|
pr_smb(PR_STATUS,
|
2015-09-29 21:53:51 +00:00
|
|
|
"Total current reduced a lot: %d (%d + %d) < %d - %d\n",
|
2014-10-09 01:36:33 +00:00
|
|
|
total_current_ma,
|
|
|
|
current_limit_ma, parallel_cl_ma,
|
|
|
|
chip->parallel.initial_aicl_ma,
|
|
|
|
chip->parallel.allowed_lowering_ma);
|
2015-09-29 21:53:51 +00:00
|
|
|
return false;
|
2014-08-06 18:27:04 +00:00
|
|
|
}
|
|
|
|
|
2015-09-29 21:53:51 +00:00
|
|
|
*ret_total_current_ma = total_current_ma;
|
|
|
|
return true;
|
2014-08-06 18:27:04 +00:00
|
|
|
}
|
|
|
|
|
power: qpnp-smbcharger: allow icl to recover during parallel charging
Currently, if the charger gets a lower AICL setting due to a temporary
glitch or dip in the amount of current provided by the charging source,
the parallel charger can never recover the original AICL setting. This
is because the primary charger caps the input current limit to half of
the original AICL result for both the parallel charger and the primary
charger. Subsequent runs of AICL during parallel charging can never
exceed the original ICL setpoint because AICL can never be higher than
the configured ICL.
Fix this by setting the primary charger to (targeted ICL - parallel ICL).
This way, if the AICL is reran, either through the hardware feature or
through software, any extra available current can be recovered by the
algorithm to be rebalanced again.
This change can cause the AICL to collapse multiple times after enabling
parallel charging, so also change the algorithm to make sure the AICL is
settled before changing parallel charging. More specifically, when the
algorithm needs to change parallel charging settings, it will take two
AICL samples with 500ms between each sample. It will wait until
these two samples match before making any changes.
Furthermore, since the AICL is now more stable, we can remove the 100mA
decrements to the parallel charging current, and allow it to go to the
requested amount immediately.
Finally, remove the AICL rerun at the end of smbchg_parallel_usb_enable()
since AICL results can no longer be higher than the input current limit
anyways.
CRs-Fixed: 920791
Change-Id: I93e9f926b6f5ed520e4d6c2495087df25debf29d
Signed-off-by: Xiaozhe Shi <xiaozhes@codeaurora.org>
2015-10-08 23:35:32 +00:00
|
|
|
#define PARALLEL_CHARGER_EN_DELAY_MS 500
|
2014-10-15 17:41:50 +00:00
|
|
|
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);
|
power: qpnp-smbcharger: allow icl to recover during parallel charging
Currently, if the charger gets a lower AICL setting due to a temporary
glitch or dip in the amount of current provided by the charging source,
the parallel charger can never recover the original AICL setting. This
is because the primary charger caps the input current limit to half of
the original AICL result for both the parallel charger and the primary
charger. Subsequent runs of AICL during parallel charging can never
exceed the original ICL setpoint because AICL can never be higher than
the configured ICL.
Fix this by setting the primary charger to (targeted ICL - parallel ICL).
This way, if the AICL is reran, either through the hardware feature or
through software, any extra available current can be recovered by the
algorithm to be rebalanced again.
This change can cause the AICL to collapse multiple times after enabling
parallel charging, so also change the algorithm to make sure the AICL is
settled before changing parallel charging. More specifically, when the
algorithm needs to change parallel charging settings, it will take two
AICL samples with 500ms between each sample. It will wait until
these two samples match before making any changes.
Furthermore, since the AICL is now more stable, we can remove the 100mA
decrements to the parallel charging current, and allow it to go to the
requested amount immediately.
Finally, remove the AICL rerun at the end of smbchg_parallel_usb_enable()
since AICL results can no longer be higher than the input current limit
anyways.
CRs-Fixed: 920791
Change-Id: I93e9f926b6f5ed520e4d6c2495087df25debf29d
Signed-off-by: Xiaozhe Shi <xiaozhes@codeaurora.org>
2015-10-08 23:35:32 +00:00
|
|
|
int previous_aicl_ma, total_current_ma, aicl_ma;
|
|
|
|
bool in_progress;
|
|
|
|
|
|
|
|
/* do a check to see if the aicl is stable */
|
|
|
|
previous_aicl_ma = smbchg_get_aicl_level_ma(chip);
|
|
|
|
msleep(PARALLEL_CHARGER_EN_DELAY_MS);
|
|
|
|
aicl_ma = smbchg_get_aicl_level_ma(chip);
|
|
|
|
if (previous_aicl_ma == aicl_ma) {
|
|
|
|
pr_smb(PR_STATUS, "AICL at %d\n", aicl_ma);
|
|
|
|
} else {
|
|
|
|
pr_smb(PR_STATUS,
|
|
|
|
"AICL changed [%d -> %d], recheck %d ms\n",
|
|
|
|
previous_aicl_ma, aicl_ma,
|
|
|
|
PARALLEL_CHARGER_EN_DELAY_MS);
|
|
|
|
goto recheck;
|
|
|
|
}
|
2014-10-15 17:41:50 +00:00
|
|
|
|
|
|
|
mutex_lock(&chip->parallel.lock);
|
power: qpnp-smbcharger: allow icl to recover during parallel charging
Currently, if the charger gets a lower AICL setting due to a temporary
glitch or dip in the amount of current provided by the charging source,
the parallel charger can never recover the original AICL setting. This
is because the primary charger caps the input current limit to half of
the original AICL result for both the parallel charger and the primary
charger. Subsequent runs of AICL during parallel charging can never
exceed the original ICL setpoint because AICL can never be higher than
the configured ICL.
Fix this by setting the primary charger to (targeted ICL - parallel ICL).
This way, if the AICL is reran, either through the hardware feature or
through software, any extra available current can be recovered by the
algorithm to be rebalanced again.
This change can cause the AICL to collapse multiple times after enabling
parallel charging, so also change the algorithm to make sure the AICL is
settled before changing parallel charging. More specifically, when the
algorithm needs to change parallel charging settings, it will take two
AICL samples with 500ms between each sample. It will wait until
these two samples match before making any changes.
Furthermore, since the AICL is now more stable, we can remove the 100mA
decrements to the parallel charging current, and allow it to go to the
requested amount immediately.
Finally, remove the AICL rerun at the end of smbchg_parallel_usb_enable()
since AICL results can no longer be higher than the input current limit
anyways.
CRs-Fixed: 920791
Change-Id: I93e9f926b6f5ed520e4d6c2495087df25debf29d
Signed-off-by: Xiaozhe Shi <xiaozhes@codeaurora.org>
2015-10-08 23:35:32 +00:00
|
|
|
in_progress = (chip->parallel.current_max_ma != 0);
|
2015-09-29 21:53:51 +00:00
|
|
|
if (smbchg_is_parallel_usb_ok(chip, &total_current_ma)) {
|
|
|
|
smbchg_parallel_usb_enable(chip, total_current_ma);
|
power: qpnp-smbcharger: allow icl to recover during parallel charging
Currently, if the charger gets a lower AICL setting due to a temporary
glitch or dip in the amount of current provided by the charging source,
the parallel charger can never recover the original AICL setting. This
is because the primary charger caps the input current limit to half of
the original AICL result for both the parallel charger and the primary
charger. Subsequent runs of AICL during parallel charging can never
exceed the original ICL setpoint because AICL can never be higher than
the configured ICL.
Fix this by setting the primary charger to (targeted ICL - parallel ICL).
This way, if the AICL is reran, either through the hardware feature or
through software, any extra available current can be recovered by the
algorithm to be rebalanced again.
This change can cause the AICL to collapse multiple times after enabling
parallel charging, so also change the algorithm to make sure the AICL is
settled before changing parallel charging. More specifically, when the
algorithm needs to change parallel charging settings, it will take two
AICL samples with 500ms between each sample. It will wait until
these two samples match before making any changes.
Furthermore, since the AICL is now more stable, we can remove the 100mA
decrements to the parallel charging current, and allow it to go to the
requested amount immediately.
Finally, remove the AICL rerun at the end of smbchg_parallel_usb_enable()
since AICL results can no longer be higher than the input current limit
anyways.
CRs-Fixed: 920791
Change-Id: I93e9f926b6f5ed520e4d6c2495087df25debf29d
Signed-off-by: Xiaozhe Shi <xiaozhes@codeaurora.org>
2015-10-08 23:35:32 +00:00
|
|
|
} else {
|
|
|
|
if (in_progress) {
|
|
|
|
pr_smb(PR_STATUS, "parallel charging unavailable\n");
|
|
|
|
smbchg_parallel_usb_disable(chip);
|
|
|
|
}
|
2015-03-10 18:03:32 +00:00
|
|
|
}
|
2014-10-15 17:41:50 +00:00
|
|
|
mutex_unlock(&chip->parallel.lock);
|
power: qpnp-smbcharger: allow icl to recover during parallel charging
Currently, if the charger gets a lower AICL setting due to a temporary
glitch or dip in the amount of current provided by the charging source,
the parallel charger can never recover the original AICL setting. This
is because the primary charger caps the input current limit to half of
the original AICL result for both the parallel charger and the primary
charger. Subsequent runs of AICL during parallel charging can never
exceed the original ICL setpoint because AICL can never be higher than
the configured ICL.
Fix this by setting the primary charger to (targeted ICL - parallel ICL).
This way, if the AICL is reran, either through the hardware feature or
through software, any extra available current can be recovered by the
algorithm to be rebalanced again.
This change can cause the AICL to collapse multiple times after enabling
parallel charging, so also change the algorithm to make sure the AICL is
settled before changing parallel charging. More specifically, when the
algorithm needs to change parallel charging settings, it will take two
AICL samples with 500ms between each sample. It will wait until
these two samples match before making any changes.
Furthermore, since the AICL is now more stable, we can remove the 100mA
decrements to the parallel charging current, and allow it to go to the
requested amount immediately.
Finally, remove the AICL rerun at the end of smbchg_parallel_usb_enable()
since AICL results can no longer be higher than the input current limit
anyways.
CRs-Fixed: 920791
Change-Id: I93e9f926b6f5ed520e4d6c2495087df25debf29d
Signed-off-by: Xiaozhe Shi <xiaozhes@codeaurora.org>
2015-10-08 23:35:32 +00:00
|
|
|
smbchg_relax(chip, PM_PARALLEL_CHECK);
|
|
|
|
return;
|
|
|
|
|
|
|
|
recheck:
|
|
|
|
schedule_delayed_work(&chip->parallel_en_work, 0);
|
2014-10-15 17:41:50 +00:00
|
|
|
}
|
|
|
|
|
2014-08-06 18:27:04 +00:00
|
|
|
static void smbchg_parallel_usb_check_ok(struct smbchg_chip *chip)
|
|
|
|
{
|
|
|
|
struct power_supply *parallel_psy = get_parallel_psy(chip);
|
|
|
|
|
2015-05-26 07:03:45 +00:00
|
|
|
if (!parallel_psy || !chip->parallel_charger_detected)
|
2014-08-06 18:27:04 +00:00
|
|
|
return;
|
power: qpnp-smbcharger: allow icl to recover during parallel charging
Currently, if the charger gets a lower AICL setting due to a temporary
glitch or dip in the amount of current provided by the charging source,
the parallel charger can never recover the original AICL setting. This
is because the primary charger caps the input current limit to half of
the original AICL result for both the parallel charger and the primary
charger. Subsequent runs of AICL during parallel charging can never
exceed the original ICL setpoint because AICL can never be higher than
the configured ICL.
Fix this by setting the primary charger to (targeted ICL - parallel ICL).
This way, if the AICL is reran, either through the hardware feature or
through software, any extra available current can be recovered by the
algorithm to be rebalanced again.
This change can cause the AICL to collapse multiple times after enabling
parallel charging, so also change the algorithm to make sure the AICL is
settled before changing parallel charging. More specifically, when the
algorithm needs to change parallel charging settings, it will take two
AICL samples with 500ms between each sample. It will wait until
these two samples match before making any changes.
Furthermore, since the AICL is now more stable, we can remove the 100mA
decrements to the parallel charging current, and allow it to go to the
requested amount immediately.
Finally, remove the AICL rerun at the end of smbchg_parallel_usb_enable()
since AICL results can no longer be higher than the input current limit
anyways.
CRs-Fixed: 920791
Change-Id: I93e9f926b6f5ed520e4d6c2495087df25debf29d
Signed-off-by: Xiaozhe Shi <xiaozhes@codeaurora.org>
2015-10-08 23:35:32 +00:00
|
|
|
|
|
|
|
smbchg_stay_awake(chip, PM_PARALLEL_CHECK);
|
|
|
|
schedule_delayed_work(&chip->parallel_en_work, 0);
|
2014-08-06 18:27:04 +00:00
|
|
|
}
|
|
|
|
|
2015-11-26 09:38:11 +00:00
|
|
|
static int charging_suspend_vote_cb(struct device *dev, int suspend,
|
|
|
|
int client, int last_suspend,
|
|
|
|
int last_client)
|
|
|
|
{
|
|
|
|
int rc;
|
|
|
|
struct smbchg_chip *chip = dev_get_drvdata(dev);
|
|
|
|
|
|
|
|
rc = smbchg_charging_en(chip, !suspend);
|
|
|
|
if (rc < 0) {
|
|
|
|
dev_err(chip->dev,
|
|
|
|
"Couldn't configure batt chg: 0x%x rc = %d\n",
|
|
|
|
!suspend, rc);
|
|
|
|
}
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int usb_suspend_vote_cb(struct device *dev, int suspend,
|
|
|
|
int client, int last_suspend,
|
|
|
|
int last_client)
|
|
|
|
{
|
|
|
|
int rc;
|
|
|
|
struct smbchg_chip *chip = dev_get_drvdata(dev);
|
|
|
|
|
|
|
|
rc = smbchg_usb_suspend(chip, suspend);
|
|
|
|
if (rc < 0)
|
|
|
|
return rc;
|
|
|
|
|
|
|
|
if (client == THERMAL_EN_VOTER || client == POWER_SUPPLY_EN_VOTER ||
|
|
|
|
client == USER_EN_VOTER)
|
|
|
|
smbchg_parallel_usb_check_ok(chip);
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int dc_suspend_vote_cb(struct device *dev, int suspend,
|
|
|
|
int client, int last_suspend,
|
|
|
|
int last_client)
|
|
|
|
{
|
|
|
|
int rc;
|
|
|
|
struct smbchg_chip *chip = dev_get_drvdata(dev);
|
|
|
|
|
|
|
|
rc = smbchg_dc_suspend(chip, suspend);
|
|
|
|
if (rc < 0)
|
|
|
|
return rc;
|
|
|
|
|
|
|
|
if (chip->dc_psy_type != -EINVAL && chip->psy_registered)
|
|
|
|
power_supply_changed(&chip->dc_psy);
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2015-11-26 07:54:21 +00:00
|
|
|
static int set_fastchg_current_vote_cb(struct device *dev,
|
|
|
|
int fcc_ma,
|
|
|
|
int client,
|
|
|
|
int last_fcc_ma,
|
|
|
|
int last_client)
|
|
|
|
{
|
|
|
|
struct smbchg_chip *chip = dev_get_drvdata(dev);
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
if (chip->parallel.current_max_ma == 0) {
|
|
|
|
rc = smbchg_set_fastchg_current_raw(chip, fcc_ma);
|
|
|
|
if (rc < 0) {
|
|
|
|
pr_err("Can't set FCC fcc_ma=%d rc=%d\n", fcc_ma, rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/*
|
|
|
|
* check if parallel charging can be enabled, and if enabled,
|
|
|
|
* distribute the fcc
|
|
|
|
*/
|
|
|
|
smbchg_parallel_usb_check_ok(chip);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-03-05 20:30:30 +00:00
|
|
|
static int smbchg_set_fastchg_current_user(struct smbchg_chip *chip,
|
|
|
|
int current_ma)
|
|
|
|
{
|
|
|
|
int rc = 0;
|
|
|
|
|
|
|
|
pr_smb(PR_STATUS, "User setting FCC to %d\n", current_ma);
|
2015-09-29 22:48:01 +00:00
|
|
|
|
2015-10-29 22:27:28 +00:00
|
|
|
rc = vote(chip->fcc_votable, BATT_TYPE_FCC_VOTER, true, current_ma);
|
2015-11-26 07:54:21 +00:00
|
|
|
if (rc < 0)
|
|
|
|
pr_err("Couldn't vote en rc %d\n", rc);
|
2015-03-05 20:30:30 +00:00
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2014-10-30 21:59:48 +00:00
|
|
|
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++) {
|
2015-01-29 23:20:59 +00:00
|
|
|
if (is_between(map->entries[i].vmin_uv, map->entries[i].vmax_uv,
|
|
|
|
uv))
|
2014-10-30 21:59:48 +00:00
|
|
|
ret = &map->entries[i];
|
|
|
|
}
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
#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;
|
|
|
|
|
2015-08-25 18:17:24 +00:00
|
|
|
i = find_smaller_in_array(chip->tables.dc_ilim_ma_table,
|
|
|
|
ma, chip->tables.dc_ilim_ma_len);
|
2014-10-30 21:59:48 +00:00
|
|
|
|
|
|
|
if (i < 0)
|
|
|
|
i = 0;
|
|
|
|
|
2015-02-07 00:11:10 +00:00
|
|
|
rc = smbchg_sec_masked_write(chip, chip->bat_if_base + offset,
|
2014-10-30 21:59:48 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2014-11-05 01:04:29 +00:00
|
|
|
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");
|
|
|
|
|
2014-02-20 01:27:12 +00:00
|
|
|
/*
|
|
|
|
* set the dc charge path's maximum allowed current draw
|
|
|
|
* that may be limited by the system's thermal level
|
|
|
|
*/
|
2015-11-26 09:07:24 +00:00
|
|
|
static int set_dc_current_limit_vote_cb(struct device *dev,
|
|
|
|
int icl_ma,
|
|
|
|
int client,
|
|
|
|
int last_icl_ma,
|
|
|
|
int last_client)
|
2014-02-20 01:27:12 +00:00
|
|
|
{
|
2015-11-26 09:07:24 +00:00
|
|
|
struct smbchg_chip *chip = dev_get_drvdata(dev);
|
|
|
|
|
|
|
|
return smbchg_set_dc_current_max(chip, icl_ma);
|
2014-02-20 01:27:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* set the usb charge path's maximum allowed current draw
|
|
|
|
* that may be limited by the system's thermal level
|
|
|
|
*/
|
2015-11-26 09:07:24 +00:00
|
|
|
static int set_usb_current_limit_vote_cb(struct device *dev,
|
|
|
|
int icl_ma,
|
|
|
|
int client,
|
|
|
|
int last_icl_ma,
|
|
|
|
int last_client)
|
2014-02-20 01:27:12 +00:00
|
|
|
{
|
2015-11-26 09:07:24 +00:00
|
|
|
struct smbchg_chip *chip = dev_get_drvdata(dev);
|
2015-10-23 00:03:13 +00:00
|
|
|
int rc, aicl_ma, effective_id;
|
|
|
|
|
|
|
|
effective_id = get_effective_client_id_locked(chip->usb_icl_votable);
|
|
|
|
|
|
|
|
/* disable parallel charging if HVDCP is voting for 300mA */
|
|
|
|
if (effective_id == HVDCP_ICL_VOTER)
|
|
|
|
smbchg_parallel_usb_disable(chip);
|
2014-08-06 18:27:04 +00:00
|
|
|
|
2015-11-26 09:07:24 +00:00
|
|
|
if (chip->parallel.current_max_ma == 0) {
|
|
|
|
rc = smbchg_set_usb_current_max(chip, icl_ma);
|
|
|
|
if (rc) {
|
|
|
|
pr_err("Failed to set usb current max: %d\n", rc);
|
|
|
|
return rc;
|
|
|
|
}
|
2015-03-03 01:38:22 +00:00
|
|
|
}
|
2014-08-06 18:27:04 +00:00
|
|
|
|
2015-10-23 00:03:13 +00:00
|
|
|
/* skip the aicl rerun if hvdcp icl voter is active */
|
|
|
|
if (effective_id == HVDCP_ICL_VOTER)
|
|
|
|
return 0;
|
|
|
|
|
2015-11-26 09:07:24 +00:00
|
|
|
aicl_ma = smbchg_get_aicl_level_ma(chip);
|
2015-12-01 08:53:41 +00:00
|
|
|
if (icl_ma > aicl_ma)
|
2015-03-03 01:38:22 +00:00
|
|
|
smbchg_rerun_aicl(chip);
|
2014-08-06 18:27:04 +00:00
|
|
|
smbchg_parallel_usb_check_ok(chip);
|
2015-11-26 09:07:24 +00:00
|
|
|
return 0;
|
2014-02-20 01:27:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static int smbchg_system_temp_level_set(struct smbchg_chip *chip,
|
|
|
|
int lvl_sel)
|
|
|
|
{
|
|
|
|
int rc = 0;
|
|
|
|
int prev_therm_lvl;
|
2015-11-26 09:07:24 +00:00
|
|
|
int thermal_icl_ma;
|
2014-02-20 01:27:12 +00:00
|
|
|
|
|
|
|
if (!chip->thermal_mitigation) {
|
2014-06-30 22:02:34 +00:00
|
|
|
dev_err(chip->dev, "Thermal mitigation not supported\n");
|
2014-02-20 01:27:12 +00:00
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (lvl_sel < 0) {
|
2014-06-30 22:02:34 +00:00
|
|
|
dev_err(chip->dev, "Unsupported level selected %d\n", lvl_sel);
|
2014-02-20 01:27:12 +00:00
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (lvl_sel >= chip->thermal_levels) {
|
2014-06-30 22:02:34 +00:00
|
|
|
dev_err(chip->dev, "Unsupported level selected %d forcing %d\n",
|
|
|
|
lvl_sel, chip->thermal_levels - 1);
|
2014-02-20 01:27:12 +00:00
|
|
|
lvl_sel = chip->thermal_levels - 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (lvl_sel == chip->therm_lvl_sel)
|
|
|
|
return 0;
|
|
|
|
|
2015-11-26 09:07:24 +00:00
|
|
|
mutex_lock(&chip->therm_lvl_lock);
|
2014-02-20 01:27:12 +00:00
|
|
|
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
|
|
|
|
*/
|
2015-11-26 09:38:11 +00:00
|
|
|
rc = vote(chip->dc_suspend_votable, THERMAL_EN_VOTER, true, 0);
|
2014-02-20 01:27:12 +00:00
|
|
|
if (rc < 0) {
|
|
|
|
dev_err(chip->dev,
|
|
|
|
"Couldn't set dc suspend rc %d\n", rc);
|
|
|
|
goto out;
|
|
|
|
}
|
2015-11-26 09:38:11 +00:00
|
|
|
rc = vote(chip->usb_suspend_votable, THERMAL_EN_VOTER, true, 0);
|
2014-02-20 01:27:12 +00:00
|
|
|
if (rc < 0) {
|
|
|
|
dev_err(chip->dev,
|
|
|
|
"Couldn't set usb suspend rc %d\n", rc);
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
2015-11-26 09:07:24 +00:00
|
|
|
if (chip->therm_lvl_sel == 0) {
|
|
|
|
rc = vote(chip->usb_icl_votable, THERMAL_ICL_VOTER, false, 0);
|
|
|
|
if (rc < 0)
|
|
|
|
pr_err("Couldn't disable USB thermal ICL vote rc=%d\n",
|
|
|
|
rc);
|
|
|
|
|
|
|
|
rc = vote(chip->dc_icl_votable, THERMAL_ICL_VOTER, false, 0);
|
|
|
|
if (rc < 0)
|
|
|
|
pr_err("Couldn't disable DC thermal ICL vote rc=%d\n",
|
|
|
|
rc);
|
|
|
|
} else {
|
|
|
|
thermal_icl_ma =
|
|
|
|
(int)chip->thermal_mitigation[chip->therm_lvl_sel];
|
|
|
|
rc = vote(chip->usb_icl_votable, THERMAL_ICL_VOTER, true,
|
|
|
|
thermal_icl_ma);
|
|
|
|
if (rc < 0)
|
|
|
|
pr_err("Couldn't vote for USB thermal ICL rc=%d\n", rc);
|
|
|
|
|
|
|
|
rc = vote(chip->dc_icl_votable, THERMAL_ICL_VOTER, true,
|
|
|
|
thermal_icl_ma);
|
|
|
|
if (rc < 0)
|
|
|
|
pr_err("Couldn't vote for DC thermal ICL rc=%d\n", rc);
|
|
|
|
}
|
2014-02-20 01:27:12 +00:00
|
|
|
|
|
|
|
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.
|
|
|
|
*/
|
2015-11-26 09:38:11 +00:00
|
|
|
rc = vote(chip->dc_suspend_votable, THERMAL_EN_VOTER, false, 0);
|
2014-02-20 01:27:12 +00:00
|
|
|
if (rc < 0) {
|
|
|
|
dev_err(chip->dev,
|
|
|
|
"Couldn't set dc suspend rc %d\n", rc);
|
|
|
|
goto out;
|
|
|
|
}
|
2015-11-26 09:38:11 +00:00
|
|
|
rc = vote(chip->usb_suspend_votable, THERMAL_EN_VOTER,
|
|
|
|
false, 0);
|
2014-02-20 01:27:12 +00:00
|
|
|
if (rc < 0) {
|
|
|
|
dev_err(chip->dev,
|
|
|
|
"Couldn't set usb suspend rc %d\n", rc);
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
out:
|
2015-11-26 09:07:24 +00:00
|
|
|
mutex_unlock(&chip->therm_lvl_lock);
|
2014-02-20 01:27:12 +00:00
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2015-02-07 23:40:12 +00:00
|
|
|
static int smbchg_ibat_ocp_threshold_ua = 4500000;
|
|
|
|
module_param(smbchg_ibat_ocp_threshold_ua, int, 0644);
|
|
|
|
|
2014-07-31 22:10:07 +00:00
|
|
|
#define UCONV 1000000LL
|
2015-02-07 23:40:12 +00:00
|
|
|
#define MCONV 1000LL
|
|
|
|
#define FLASH_V_THRESHOLD 3000000
|
|
|
|
#define FLASH_VDIP_MARGIN 100000
|
|
|
|
#define VPH_FLASH_VDIP (FLASH_V_THRESHOLD + FLASH_VDIP_MARGIN)
|
2014-07-31 22:10:07 +00:00
|
|
|
#define BUCK_EFFICIENCY 800LL
|
|
|
|
static int smbchg_calc_max_flash_current(struct smbchg_chip *chip)
|
|
|
|
{
|
2015-02-07 23:40:12 +00:00
|
|
|
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;
|
2014-07-31 22:10:07 +00:00
|
|
|
|
2014-10-22 22:57:20 +00:00
|
|
|
rc = get_property_from_fg(chip, POWER_SUPPLY_PROP_VOLTAGE_OCV, &ocv_uv);
|
2014-07-31 22:10:07 +00:00
|
|
|
if (rc) {
|
|
|
|
pr_smb(PR_STATUS, "bms psy does not support OCV\n");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-02-07 23:40:12 +00:00
|
|
|
rc = get_property_from_fg(chip, POWER_SUPPLY_PROP_RESISTANCE,
|
|
|
|
&esr_uohm);
|
2014-07-31 22:10:07 +00:00
|
|
|
if (rc) {
|
2015-02-07 23:40:12 +00:00
|
|
|
pr_smb(PR_STATUS, "bms psy does not support resistance\n");
|
2014-07-31 22:10:07 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-02-07 23:40:12 +00:00
|
|
|
rc = msm_bcl_read(BCL_PARAM_CURRENT, &ibat_now);
|
2014-07-31 22:10:07 +00:00
|
|
|
if (rc) {
|
2015-02-07 23:40:12 +00:00
|
|
|
pr_smb(PR_STATUS, "BCL current read failed: %d\n", rc);
|
2014-07-31 22:10:07 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
rbatt_uohm = esr_uohm + chip->rpara_uohm + chip->rslow_uohm;
|
2015-02-07 23:40:12 +00:00
|
|
|
/*
|
|
|
|
* 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),
|
2015-04-11 22:30:39 +00:00
|
|
|
div64_s64((vph_flash_uv * 1200), 1000));
|
2015-02-07 23:40:12 +00:00
|
|
|
/* 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);
|
2014-10-14 22:39:42 +00:00
|
|
|
pr_smb(PR_MISC,
|
2015-02-07 23:40:12 +00:00
|
|
|
"avail_iflash=%lld, ocv=%d, ibat=%d, rbatt=%d\n",
|
|
|
|
avail_flash_ua, ocv_uv, ibat_now, rbatt_uohm);
|
|
|
|
return (int)avail_flash_ua;
|
2014-07-31 22:10:07 +00:00
|
|
|
}
|
|
|
|
|
2014-12-26 20:25:54 +00:00
|
|
|
#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;
|
|
|
|
|
2015-11-26 07:39:46 +00:00
|
|
|
for (i = 0; i < chip->tables.fcc_comp_len; i++)
|
|
|
|
if (comp_current == chip->tables.fcc_comp_table[i])
|
2014-12-26 20:25:54 +00:00
|
|
|
break;
|
|
|
|
|
2015-11-26 07:39:46 +00:00
|
|
|
if (i >= chip->tables.fcc_comp_len)
|
2014-12-26 20:25:54 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2015-09-17 22:07:37 +00:00
|
|
|
#define CFG_TCC_REG 0xF9
|
|
|
|
#define CHG_ITERM_MASK SMB_MASK(2, 0)
|
|
|
|
static int smbchg_iterm_set(struct smbchg_chip *chip, int iterm_ma)
|
|
|
|
{
|
|
|
|
int rc;
|
|
|
|
u8 reg;
|
|
|
|
|
|
|
|
reg = find_closest_in_array(
|
|
|
|
chip->tables.iterm_ma_table,
|
|
|
|
chip->tables.iterm_ma_len,
|
|
|
|
iterm_ma);
|
|
|
|
|
|
|
|
rc = smbchg_sec_masked_write(chip,
|
|
|
|
chip->chgr_base + CFG_TCC_REG,
|
|
|
|
CHG_ITERM_MASK, reg);
|
|
|
|
if (rc) {
|
|
|
|
dev_err(chip->dev,
|
|
|
|
"Couldn't set iterm rc = %d\n", rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
pr_smb(PR_STATUS, "set tcc (%d) to 0x%02x\n",
|
|
|
|
iterm_ma, reg);
|
|
|
|
chip->iterm_ma = iterm_ma;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-12-26 20:25:54 +00:00
|
|
|
#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;
|
|
|
|
}
|
|
|
|
|
2014-10-30 21:31:00 +00:00
|
|
|
#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)
|
|
|
|
{
|
2015-03-04 21:41:07 +00:00
|
|
|
struct power_supply *parallel_psy = get_parallel_psy(chip);
|
2014-10-30 21:31:00 +00:00
|
|
|
int rc, delta;
|
|
|
|
u8 temp;
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2015-03-04 21:41:07 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2014-10-30 21:31:00 +00:00
|
|
|
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;
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int smbchg_float_voltage_get(struct smbchg_chip *chip)
|
|
|
|
{
|
|
|
|
return chip->vfloat_mv;
|
|
|
|
}
|
|
|
|
|
2015-01-08 03:57:35 +00:00
|
|
|
#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;
|
|
|
|
}
|
|
|
|
|
2015-06-26 21:10:50 +00:00
|
|
|
enum skip_reason {
|
|
|
|
REASON_OTG_ENABLED = BIT(0),
|
|
|
|
REASON_FLASH_ENABLED = BIT(1)
|
|
|
|
};
|
|
|
|
|
2015-03-18 22:56:31 +00:00
|
|
|
#define OTG_TRIM6 0xF6
|
|
|
|
#define TR_ENB_SKIP_BIT BIT(2)
|
|
|
|
#define OTG_EN_BIT BIT(0)
|
2015-06-26 21:10:50 +00:00
|
|
|
static int smbchg_otg_pulse_skip_disable(struct smbchg_chip *chip,
|
|
|
|
enum skip_reason reason, bool disable)
|
2015-03-18 22:56:31 +00:00
|
|
|
{
|
|
|
|
int rc;
|
2015-06-26 21:10:50 +00:00
|
|
|
bool disabled;
|
2015-03-18 22:56:31 +00:00
|
|
|
|
2015-06-26 21:10:50 +00:00
|
|
|
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)
|
2015-03-18 22:56:31 +00:00
|
|
|
return 0;
|
2015-06-26 21:10:50 +00:00
|
|
|
disabled = !!chip->otg_pulse_skip_dis;
|
2015-03-18 22:56:31 +00:00
|
|
|
|
|
|
|
rc = smbchg_sec_masked_write(chip, chip->otg_base + OTG_TRIM6,
|
2015-06-26 21:10:50 +00:00
|
|
|
TR_ENB_SKIP_BIT, disabled ? TR_ENB_SKIP_BIT : 0);
|
2015-03-18 22:56:31 +00:00
|
|
|
if (rc < 0) {
|
|
|
|
dev_err(chip->dev,
|
|
|
|
"Couldn't %s otg pulse skip rc = %d\n",
|
2015-06-26 21:10:50 +00:00
|
|
|
disabled ? "disable" : "enable", rc);
|
2015-03-18 22:56:31 +00:00
|
|
|
return rc;
|
|
|
|
}
|
2015-06-26 21:10:50 +00:00
|
|
|
pr_smb(PR_STATUS, "%s pulse skip\n", disabled ? "disabled" : "enabled");
|
2015-03-18 22:56:31 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-05-06 23:09:49 +00:00
|
|
|
#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;
|
|
|
|
}
|
|
|
|
|
2014-10-29 18:01:40 +00:00
|
|
|
static void smbchg_vfloat_adjust_check(struct smbchg_chip *chip)
|
|
|
|
{
|
|
|
|
if (!chip->use_vfloat_adjustments)
|
|
|
|
return;
|
|
|
|
|
|
|
|
smbchg_stay_awake(chip, PM_REASON_VFLOAT_ADJUST);
|
|
|
|
pr_smb(PR_STATUS, "Starting vfloat adjustments\n");
|
|
|
|
schedule_delayed_work(&chip->vfloat_adjust_work, 0);
|
|
|
|
}
|
|
|
|
|
2014-11-04 15:09:26 +00:00
|
|
|
#define FV_STS_REG 0xC
|
|
|
|
#define AICL_INPUT_STS_BIT BIT(6)
|
|
|
|
static bool smbchg_is_input_current_limited(struct smbchg_chip *chip)
|
|
|
|
{
|
|
|
|
int rc;
|
|
|
|
u8 reg;
|
|
|
|
|
|
|
|
rc = smbchg_read(chip, ®, chip->chgr_base + FV_STS_REG, 1);
|
|
|
|
if (rc < 0) {
|
|
|
|
dev_err(chip->dev, "Couldn't read FV_STS rc=%d\n", rc);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return !!(reg & AICL_INPUT_STS_BIT);
|
|
|
|
}
|
|
|
|
|
|
|
|
#define SW_ESR_PULSE_MS 1500
|
|
|
|
static void smbchg_cc_esr_wa_check(struct smbchg_chip *chip)
|
|
|
|
{
|
|
|
|
int rc, esr_count;
|
|
|
|
|
2015-09-17 22:07:37 +00:00
|
|
|
if (!(chip->wa_flags & SMBCHG_CC_ESR_WA))
|
2015-02-11 14:23:46 +00:00
|
|
|
return;
|
|
|
|
|
2014-11-04 15:09:26 +00:00
|
|
|
if (!is_usb_present(chip) && !is_dc_present(chip)) {
|
|
|
|
pr_smb(PR_STATUS, "No inputs present, skipping\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (get_prop_charge_type(chip) != POWER_SUPPLY_CHARGE_TYPE_FAST) {
|
|
|
|
pr_smb(PR_STATUS, "Not in fast charge, skipping\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!smbchg_is_input_current_limited(chip)) {
|
|
|
|
pr_smb(PR_STATUS, "Not input current limited, skipping\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
set_property_on_fg(chip, POWER_SUPPLY_PROP_UPDATE_NOW, 1);
|
|
|
|
rc = get_property_from_fg(chip,
|
|
|
|
POWER_SUPPLY_PROP_ESR_COUNT, &esr_count);
|
|
|
|
if (rc) {
|
|
|
|
pr_smb(PR_STATUS,
|
|
|
|
"could not read ESR counter rc = %d\n", rc);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* The esr_count is counting down the number of fuel gauge cycles
|
|
|
|
* before a ESR pulse is needed.
|
|
|
|
*
|
|
|
|
* After a successful ESR pulse, this count is reset to some
|
|
|
|
* high number like 28. If this reaches 0, then the fuel gauge
|
|
|
|
* hardware should force a ESR pulse.
|
|
|
|
*
|
|
|
|
* However, if the device is in constant current charge mode while
|
|
|
|
* being input current limited, the ESR pulse will not affect the
|
|
|
|
* battery current, so the measurement will fail.
|
|
|
|
*
|
|
|
|
* As a failsafe, force a manual ESR pulse if this value is read as
|
|
|
|
* 0.
|
|
|
|
*/
|
|
|
|
if (esr_count != 0) {
|
|
|
|
pr_smb(PR_STATUS, "ESR count is not zero, skipping\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
pr_smb(PR_STATUS, "Lowering charge current for ESR pulse\n");
|
|
|
|
smbchg_stay_awake(chip, PM_ESR_PULSE);
|
|
|
|
smbchg_sw_esr_pulse_en(chip, true);
|
|
|
|
msleep(SW_ESR_PULSE_MS);
|
|
|
|
pr_smb(PR_STATUS, "Raising charge current for ESR pulse\n");
|
|
|
|
smbchg_relax(chip, PM_ESR_PULSE);
|
|
|
|
smbchg_sw_esr_pulse_en(chip, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void smbchg_soc_changed(struct smbchg_chip *chip)
|
|
|
|
{
|
|
|
|
smbchg_cc_esr_wa_check(chip);
|
|
|
|
}
|
|
|
|
|
2015-02-22 05:47:02 +00:00
|
|
|
#define DC_AICL_CFG 0xF3
|
2015-02-22 00:14:03 +00:00
|
|
|
#define MISC_TRIM_OPT_15_8 0xF5
|
2015-02-22 05:47:02 +00:00
|
|
|
#define USB_AICL_DEGLITCH_MASK (BIT(5) | BIT(4) | BIT(3))
|
|
|
|
#define USB_AICL_DEGLITCH_SHORT (BIT(5) | BIT(4) | BIT(3))
|
|
|
|
#define USB_AICL_DEGLITCH_LONG 0
|
|
|
|
#define DC_AICL_DEGLITCH_MASK (BIT(5) | BIT(4) | BIT(3))
|
|
|
|
#define DC_AICL_DEGLITCH_SHORT (BIT(5) | BIT(4) | BIT(3))
|
|
|
|
#define DC_AICL_DEGLITCH_LONG 0
|
|
|
|
#define AICL_RERUN_MASK (BIT(5) | BIT(4))
|
|
|
|
#define AICL_RERUN_ON (BIT(5) | BIT(4))
|
|
|
|
#define AICL_RERUN_OFF 0
|
2015-04-21 23:02:50 +00:00
|
|
|
|
|
|
|
static int smbchg_hw_aicl_rerun_en(struct smbchg_chip *chip, bool en)
|
|
|
|
{
|
|
|
|
int rc = 0;
|
|
|
|
|
|
|
|
rc = smbchg_sec_masked_write(chip,
|
|
|
|
chip->misc_base + MISC_TRIM_OPT_15_8,
|
|
|
|
AICL_RERUN_MASK, en ? AICL_RERUN_ON : AICL_RERUN_OFF);
|
|
|
|
if (rc)
|
|
|
|
pr_err("Couldn't write to MISC_TRIM_OPTIONS_15_8 rc=%d\n",
|
|
|
|
rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int smbchg_aicl_config(struct smbchg_chip *chip)
|
|
|
|
{
|
|
|
|
int rc = 0;
|
|
|
|
|
|
|
|
rc = smbchg_sec_masked_write(chip,
|
|
|
|
chip->usb_chgpth_base + USB_AICL_CFG,
|
|
|
|
USB_AICL_DEGLITCH_MASK, USB_AICL_DEGLITCH_LONG);
|
|
|
|
if (rc) {
|
|
|
|
pr_err("Couldn't write to USB_AICL_CFG rc=%d\n", rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
rc = smbchg_sec_masked_write(chip,
|
|
|
|
chip->dc_chgpth_base + DC_AICL_CFG,
|
|
|
|
DC_AICL_DEGLITCH_MASK, DC_AICL_DEGLITCH_LONG);
|
|
|
|
if (rc) {
|
|
|
|
pr_err("Couldn't write to DC_AICL_CFG rc=%d\n", rc);
|
|
|
|
return rc;
|
|
|
|
}
|
power: qpnp-smbcharger: handle very weak chargers
The PMi charger peripheral can detect weak chargers through the automatic
input current limit (AICL) feature by observing how much current it
takes to collapse an external charger. Additionally, under very bad
conditions, such as having a very weak charger with a high impedance
cable, the AICL algorithm will actually stop altogether when the USB
voltage drops below the autoshutdown threshold.
Normally, this will trigger AICL suspend and shut off the charger buck.
However, if hardware AICL reruns are enabled, AICL cannot be suspended
in hardware. This can cause a problem where the USBIN_UV interrupt is
continuously received when the USBIN voltage hovers around the lower
limit of the valid range.
Fix this by detecting very weak chargers if the USBIN_UV is high at the
same time USBIN_SRC_DET is high. If both interrupts lines are high,
it means that the USB voltage is below the low threshold for UV but
higher than the coarse detect threshold (1V).
If a weak charger is detected, disable AICL rerun to allow the charger
to enter into AICL suspend, and set the USB health to UNSPEC_FAILURE,
and do not report USB online.
Also, do not allow USBIN_UV to reset the aicl_irq_count in order to
allow the battery OV interrupt to run correctly.
CRs-Fixed: 823770
Change-Id: If853659fa7aaf899f500d8933a773ab80d0464a1
Signed-off-by: Xiaozhe Shi <xiaozhes@codeaurora.org>
2015-04-15 21:39:43 +00:00
|
|
|
if (!chip->very_weak_charger) {
|
|
|
|
rc = smbchg_hw_aicl_rerun_en(chip, true);
|
|
|
|
if (rc)
|
|
|
|
pr_err("Couldn't enable AICL rerun rc= %d\n", rc);
|
|
|
|
}
|
2015-04-21 23:02:50 +00:00
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2015-02-22 05:47:02 +00:00
|
|
|
static void smbchg_aicl_deglitch_wa_en(struct smbchg_chip *chip, bool en)
|
|
|
|
{
|
|
|
|
int rc;
|
|
|
|
|
2015-04-21 23:02:50 +00:00
|
|
|
if (chip->force_aicl_rerun)
|
|
|
|
return;
|
2015-02-22 05:47:02 +00:00
|
|
|
if (en && !chip->aicl_deglitch_short) {
|
|
|
|
rc = smbchg_sec_masked_write(chip,
|
|
|
|
chip->usb_chgpth_base + USB_AICL_CFG,
|
|
|
|
USB_AICL_DEGLITCH_MASK, USB_AICL_DEGLITCH_SHORT);
|
|
|
|
if (rc) {
|
|
|
|
pr_err("Couldn't write to USB_AICL_CFG rc=%d\n", rc);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
rc = smbchg_sec_masked_write(chip,
|
|
|
|
chip->dc_chgpth_base + DC_AICL_CFG,
|
|
|
|
DC_AICL_DEGLITCH_MASK, DC_AICL_DEGLITCH_SHORT);
|
|
|
|
if (rc) {
|
|
|
|
pr_err("Couldn't write to DC_AICL_CFG rc=%d\n", rc);
|
|
|
|
return;
|
|
|
|
}
|
power: qpnp-smbcharger: handle very weak chargers
The PMi charger peripheral can detect weak chargers through the automatic
input current limit (AICL) feature by observing how much current it
takes to collapse an external charger. Additionally, under very bad
conditions, such as having a very weak charger with a high impedance
cable, the AICL algorithm will actually stop altogether when the USB
voltage drops below the autoshutdown threshold.
Normally, this will trigger AICL suspend and shut off the charger buck.
However, if hardware AICL reruns are enabled, AICL cannot be suspended
in hardware. This can cause a problem where the USBIN_UV interrupt is
continuously received when the USBIN voltage hovers around the lower
limit of the valid range.
Fix this by detecting very weak chargers if the USBIN_UV is high at the
same time USBIN_SRC_DET is high. If both interrupts lines are high,
it means that the USB voltage is below the low threshold for UV but
higher than the coarse detect threshold (1V).
If a weak charger is detected, disable AICL rerun to allow the charger
to enter into AICL suspend, and set the USB health to UNSPEC_FAILURE,
and do not report USB online.
Also, do not allow USBIN_UV to reset the aicl_irq_count in order to
allow the battery OV interrupt to run correctly.
CRs-Fixed: 823770
Change-Id: If853659fa7aaf899f500d8933a773ab80d0464a1
Signed-off-by: Xiaozhe Shi <xiaozhes@codeaurora.org>
2015-04-15 21:39:43 +00:00
|
|
|
if (!chip->very_weak_charger) {
|
|
|
|
rc = smbchg_hw_aicl_rerun_en(chip, true);
|
|
|
|
if (rc) {
|
|
|
|
pr_err("Couldn't enable AICL rerun rc= %d\n",
|
|
|
|
rc);
|
|
|
|
return;
|
|
|
|
}
|
2015-02-22 05:47:02 +00:00
|
|
|
}
|
|
|
|
pr_smb(PR_STATUS, "AICL deglitch set to short\n");
|
|
|
|
} else if (!en && chip->aicl_deglitch_short) {
|
|
|
|
rc = smbchg_sec_masked_write(chip,
|
|
|
|
chip->usb_chgpth_base + USB_AICL_CFG,
|
|
|
|
USB_AICL_DEGLITCH_MASK, USB_AICL_DEGLITCH_LONG);
|
|
|
|
if (rc) {
|
|
|
|
pr_err("Couldn't write to USB_AICL_CFG rc=%d\n", rc);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
rc = smbchg_sec_masked_write(chip,
|
|
|
|
chip->dc_chgpth_base + DC_AICL_CFG,
|
|
|
|
DC_AICL_DEGLITCH_MASK, DC_AICL_DEGLITCH_LONG);
|
|
|
|
if (rc) {
|
|
|
|
pr_err("Couldn't write to DC_AICL_CFG rc=%d\n", rc);
|
|
|
|
return;
|
|
|
|
}
|
2015-04-21 23:02:50 +00:00
|
|
|
rc = smbchg_hw_aicl_rerun_en(chip, false);
|
2015-02-22 05:47:02 +00:00
|
|
|
if (rc) {
|
2015-04-21 23:02:50 +00:00
|
|
|
pr_err("Couldn't disable AICL rerun rc= %d\n", rc);
|
2015-02-22 05:47:02 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
pr_smb(PR_STATUS, "AICL deglitch set to normal\n");
|
|
|
|
}
|
|
|
|
chip->aicl_deglitch_short = en;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void smbchg_aicl_deglitch_wa_check(struct smbchg_chip *chip)
|
|
|
|
{
|
|
|
|
union power_supply_propval prop = {0,};
|
|
|
|
int rc;
|
|
|
|
u8 reg;
|
|
|
|
bool low_volt_chgr = true;
|
|
|
|
|
2015-06-08 11:14:19 +00:00
|
|
|
if (!(chip->wa_flags & SMBCHG_AICL_DEGLITCH_WA))
|
|
|
|
return;
|
|
|
|
|
2015-02-22 05:47:02 +00:00
|
|
|
if (!is_usb_present(chip) && !is_dc_present(chip)) {
|
|
|
|
pr_smb(PR_STATUS, "Charger removed\n");
|
|
|
|
smbchg_aicl_deglitch_wa_en(chip, false);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!chip->bms_psy)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (is_usb_present(chip)) {
|
|
|
|
rc = smbchg_read(chip, ®,
|
|
|
|
chip->usb_chgpth_base + USBIN_HVDCP_STS, 1);
|
|
|
|
if (rc < 0) {
|
|
|
|
pr_err("Couldn't read hvdcp status rc = %d\n", rc);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (reg & USBIN_HVDCP_SEL_BIT)
|
|
|
|
low_volt_chgr = false;
|
|
|
|
} else if (is_dc_present(chip)) {
|
|
|
|
if (chip->dc_psy_type == POWER_SUPPLY_TYPE_WIPOWER)
|
|
|
|
low_volt_chgr = false;
|
|
|
|
else
|
|
|
|
low_volt_chgr = chip->low_volt_dcin;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!low_volt_chgr) {
|
|
|
|
pr_smb(PR_STATUS, "High volt charger! Don't set deglitch\n");
|
|
|
|
smbchg_aicl_deglitch_wa_en(chip, false);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* It is possible that battery voltage went high above threshold
|
|
|
|
* when the charger is inserted and can go low because of system
|
|
|
|
* load. We shouldn't be reconfiguring AICL deglitch when this
|
|
|
|
* happens as it will lead to oscillation again which is being
|
|
|
|
* fixed here. Do it once when the battery voltage crosses the
|
|
|
|
* threshold (e.g. 4.2 V) and clear it only when the charger
|
|
|
|
* is removed.
|
|
|
|
*/
|
|
|
|
if (!chip->vbat_above_headroom) {
|
|
|
|
rc = chip->bms_psy->get_property(chip->bms_psy,
|
|
|
|
POWER_SUPPLY_PROP_VOLTAGE_MIN, &prop);
|
|
|
|
if (rc < 0) {
|
|
|
|
pr_err("could not read voltage_min, rc=%d\n", rc);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
chip->vbat_above_headroom = !prop.intval;
|
|
|
|
}
|
|
|
|
smbchg_aicl_deglitch_wa_en(chip, chip->vbat_above_headroom);
|
|
|
|
}
|
|
|
|
|
2015-11-26 07:46:51 +00:00
|
|
|
#define MISC_TEST_REG 0xE2
|
|
|
|
#define BB_LOOP_DISABLE_ICL BIT(2)
|
|
|
|
static int smbchg_icl_loop_disable_check(struct smbchg_chip *chip)
|
|
|
|
{
|
|
|
|
bool icl_disabled = !chip->chg_otg_enabled && chip->flash_triggered;
|
|
|
|
int rc = 0;
|
|
|
|
|
|
|
|
if ((chip->wa_flags & SMBCHG_FLASH_ICL_DISABLE_WA)
|
|
|
|
&& icl_disabled != chip->icl_disabled) {
|
|
|
|
rc = smbchg_sec_masked_write(chip,
|
|
|
|
chip->misc_base + MISC_TEST_REG,
|
|
|
|
BB_LOOP_DISABLE_ICL,
|
|
|
|
icl_disabled ? BB_LOOP_DISABLE_ICL : 0);
|
|
|
|
chip->icl_disabled = icl_disabled;
|
|
|
|
}
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2014-08-16 00:05:21 +00:00
|
|
|
#define UNKNOWN_BATT_TYPE "Unknown Battery"
|
2014-10-15 18:55:44 +00:00
|
|
|
#define LOADING_BATT_TYPE "Loading Battery Data"
|
2015-03-27 02:23:59 +00:00
|
|
|
static int smbchg_config_chg_battery_type(struct smbchg_chip *chip)
|
|
|
|
{
|
2015-09-17 22:07:37 +00:00
|
|
|
int rc = 0, max_voltage_uv = 0, fastchg_ma = 0, ret = 0, iterm_ua = 0;
|
2015-03-27 02:23:59 +00:00
|
|
|
struct device_node *batt_node, *profile_node;
|
|
|
|
struct device_node *node = chip->spmi->dev.of_node;
|
|
|
|
union power_supply_propval prop = {0,};
|
|
|
|
|
|
|
|
rc = chip->bms_psy->get_property(chip->bms_psy,
|
|
|
|
POWER_SUPPLY_PROP_BATTERY_TYPE, &prop);
|
|
|
|
if (rc) {
|
|
|
|
pr_smb(PR_STATUS, "Unable to read battery-type rc=%d\n", rc);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
if (!strcmp(prop.strval, UNKNOWN_BATT_TYPE) ||
|
|
|
|
!strcmp(prop.strval, LOADING_BATT_TYPE)) {
|
|
|
|
pr_smb(PR_MISC, "Battery-type not identified\n");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
/* quit if there is no change in the battery-type from previous */
|
|
|
|
if (chip->battery_type && !strcmp(prop.strval, chip->battery_type))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
batt_node = of_parse_phandle(node, "qcom,battery-data", 0);
|
|
|
|
if (!batt_node) {
|
|
|
|
pr_smb(PR_MISC, "No batterydata available\n");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
profile_node = of_batterydata_get_best_profile(batt_node,
|
|
|
|
"bms", NULL);
|
|
|
|
if (!profile_node) {
|
|
|
|
pr_err("couldn't find profile handle\n");
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
chip->battery_type = prop.strval;
|
|
|
|
|
|
|
|
/* change vfloat */
|
|
|
|
rc = of_property_read_u32(profile_node, "qcom,max-voltage-uv",
|
|
|
|
&max_voltage_uv);
|
|
|
|
if (rc) {
|
|
|
|
pr_warn("couldn't find battery max voltage rc=%d\n", rc);
|
2015-06-02 18:09:39 +00:00
|
|
|
ret = rc;
|
|
|
|
} else {
|
|
|
|
if (chip->vfloat_mv != (max_voltage_uv / 1000)) {
|
|
|
|
pr_info("Vfloat changed from %dmV to %dmV for battery-type %s\n",
|
2015-03-27 02:23:59 +00:00
|
|
|
chip->vfloat_mv, (max_voltage_uv / 1000),
|
|
|
|
chip->battery_type);
|
2015-06-02 18:09:39 +00:00
|
|
|
rc = smbchg_float_voltage_set(chip,
|
|
|
|
(max_voltage_uv / 1000));
|
|
|
|
if (rc < 0) {
|
|
|
|
dev_err(chip->dev,
|
2015-03-27 02:23:59 +00:00
|
|
|
"Couldn't set float voltage rc = %d\n", rc);
|
2015-06-02 18:09:39 +00:00
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
}
|
2015-03-27 02:23:59 +00:00
|
|
|
}
|
|
|
|
|
2015-09-17 22:07:37 +00:00
|
|
|
/* change chg term */
|
|
|
|
rc = of_property_read_u32(profile_node, "qcom,chg-term-ua",
|
|
|
|
&iterm_ua);
|
|
|
|
if (rc && rc != -EINVAL) {
|
|
|
|
pr_warn("couldn't read battery term current=%d\n", rc);
|
|
|
|
ret = rc;
|
|
|
|
} else if (!rc) {
|
|
|
|
if (chip->iterm_ma != (iterm_ua / 1000)
|
|
|
|
&& !chip->iterm_disabled) {
|
|
|
|
pr_info("Term current changed from %dmA to %dmA for battery-type %s\n",
|
|
|
|
chip->iterm_ma, (iterm_ua / 1000),
|
|
|
|
chip->battery_type);
|
|
|
|
rc = smbchg_iterm_set(chip,
|
|
|
|
(iterm_ua / 1000));
|
|
|
|
if (rc < 0) {
|
|
|
|
dev_err(chip->dev,
|
|
|
|
"Couldn't set iterm rc = %d\n", rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
chip->iterm_ma = iterm_ua / 1000;
|
|
|
|
}
|
|
|
|
|
2015-06-02 18:09:39 +00:00
|
|
|
/*
|
|
|
|
* Only configure from profile if fastchg-ma is not defined in the
|
|
|
|
* charger device node.
|
|
|
|
*/
|
|
|
|
if (!of_find_property(chip->spmi->dev.of_node,
|
|
|
|
"qcom,fastchg-current-ma", NULL)) {
|
|
|
|
rc = of_property_read_u32(profile_node,
|
|
|
|
"qcom,fastchg-current-ma", &fastchg_ma);
|
|
|
|
if (rc) {
|
|
|
|
ret = rc;
|
|
|
|
} else {
|
|
|
|
pr_smb(PR_MISC,
|
2015-11-26 07:54:21 +00:00
|
|
|
"fastchg-ma changed from to %dma for battery-type %s\n",
|
|
|
|
fastchg_ma, chip->battery_type);
|
|
|
|
rc = vote(chip->fcc_votable, BATT_TYPE_FCC_VOTER, true,
|
|
|
|
fastchg_ma);
|
2015-06-02 18:09:39 +00:00
|
|
|
if (rc < 0) {
|
|
|
|
dev_err(chip->dev,
|
2015-11-26 07:54:21 +00:00
|
|
|
"Couldn't vote for fastchg current rc=%d\n",
|
2015-06-02 18:09:39 +00:00
|
|
|
rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
2015-03-27 02:23:59 +00:00
|
|
|
}
|
|
|
|
|
2015-04-01 22:03:35 +00:00
|
|
|
static void check_battery_type(struct smbchg_chip *chip)
|
|
|
|
{
|
|
|
|
union power_supply_propval prop = {0,};
|
|
|
|
bool en;
|
|
|
|
|
|
|
|
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) {
|
|
|
|
chip->bms_psy->get_property(chip->bms_psy,
|
|
|
|
POWER_SUPPLY_PROP_BATTERY_TYPE, &prop);
|
|
|
|
en = (strcmp(prop.strval, UNKNOWN_BATT_TYPE) != 0
|
2015-06-24 21:09:23 +00:00
|
|
|
|| chip->charge_unknown_battery)
|
|
|
|
&& (strcmp(prop.strval, LOADING_BATT_TYPE) != 0);
|
2015-11-26 09:38:11 +00:00
|
|
|
vote(chip->battchg_suspend_votable,
|
|
|
|
BATTCHG_UNKNOWN_BATTERY_EN_VOTER, !en, 0);
|
2015-04-01 22:03:35 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-02-20 01:27:12 +00:00
|
|
|
static void smbchg_external_power_changed(struct power_supply *psy)
|
|
|
|
{
|
|
|
|
struct smbchg_chip *chip = container_of(psy,
|
|
|
|
struct smbchg_chip, batt_psy);
|
|
|
|
union power_supply_propval prop = {0,};
|
2014-11-04 15:09:26 +00:00
|
|
|
int rc, current_limit = 0, soc;
|
2015-06-17 03:50:32 +00:00
|
|
|
enum power_supply_type usb_supply_type;
|
|
|
|
char *usb_type_name = "null";
|
2014-02-20 01:27:12 +00:00
|
|
|
|
|
|
|
if (chip->bms_psy_name)
|
|
|
|
chip->bms_psy =
|
|
|
|
power_supply_get_by_name((char *)chip->bms_psy_name);
|
|
|
|
|
2015-02-22 05:47:02 +00:00
|
|
|
smbchg_aicl_deglitch_wa_check(chip);
|
2014-08-16 00:05:21 +00:00
|
|
|
if (chip->bms_psy) {
|
2015-04-01 22:03:35 +00:00
|
|
|
check_battery_type(chip);
|
2014-11-04 15:09:26 +00:00
|
|
|
soc = get_prop_batt_capacity(chip);
|
|
|
|
if (chip->previous_soc != soc) {
|
|
|
|
chip->previous_soc = soc;
|
|
|
|
smbchg_soc_changed(chip);
|
|
|
|
}
|
2015-03-27 02:23:59 +00:00
|
|
|
|
|
|
|
rc = smbchg_config_chg_battery_type(chip);
|
|
|
|
if (rc)
|
|
|
|
pr_smb(PR_MISC,
|
|
|
|
"Couldn't update charger configuration rc=%d\n",
|
|
|
|
rc);
|
2014-08-16 00:05:21 +00:00
|
|
|
}
|
|
|
|
|
2014-02-20 01:27:12 +00:00
|
|
|
rc = chip->usb_psy->get_property(chip->usb_psy,
|
|
|
|
POWER_SUPPLY_PROP_CHARGING_ENABLED, &prop);
|
2015-06-11 17:27:14 +00:00
|
|
|
if (rc == 0)
|
2015-11-26 09:38:11 +00:00
|
|
|
vote(chip->usb_suspend_votable, POWER_SUPPLY_EN_VOTER,
|
|
|
|
!prop.intval, 0);
|
2014-02-20 01:27:12 +00:00
|
|
|
|
|
|
|
rc = chip->usb_psy->get_property(chip->usb_psy,
|
|
|
|
POWER_SUPPLY_PROP_CURRENT_MAX, &prop);
|
2015-07-08 02:22:35 +00:00
|
|
|
if (rc == 0)
|
2014-02-20 01:27:12 +00:00
|
|
|
current_limit = prop.intval / 1000;
|
|
|
|
|
2015-06-17 03:50:32 +00:00
|
|
|
read_usb_type(chip, &usb_type_name, &usb_supply_type);
|
|
|
|
if (usb_supply_type != POWER_SUPPLY_TYPE_USB)
|
|
|
|
goto skip_current_for_non_sdp;
|
|
|
|
|
|
|
|
pr_smb(PR_MISC, "usb type = %s current_limit = %d\n",
|
|
|
|
usb_type_name, current_limit);
|
2015-11-26 09:07:24 +00:00
|
|
|
|
|
|
|
rc = vote(chip->usb_icl_votable, PSY_ICL_VOTER, true,
|
2015-10-19 23:01:13 +00:00
|
|
|
current_limit);
|
2015-11-26 09:07:24 +00:00
|
|
|
if (rc < 0)
|
|
|
|
pr_err("Couldn't update USB PSY ICL vote rc=%d\n", rc);
|
2014-02-20 01:27:12 +00:00
|
|
|
|
2015-06-17 03:50:32 +00:00
|
|
|
skip_current_for_non_sdp:
|
2014-10-29 18:01:40 +00:00
|
|
|
smbchg_vfloat_adjust_check(chip);
|
|
|
|
|
2014-02-20 01:27:12 +00:00
|
|
|
power_supply_changed(&chip->batt_psy);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int smbchg_otg_regulator_enable(struct regulator_dev *rdev)
|
|
|
|
{
|
|
|
|
int rc = 0;
|
|
|
|
struct smbchg_chip *chip = rdev_get_drvdata(rdev);
|
|
|
|
|
2014-06-18 21:13:50 +00:00
|
|
|
chip->otg_retries = 0;
|
2015-11-26 07:46:51 +00:00
|
|
|
chip->chg_otg_enabled = true;
|
|
|
|
smbchg_icl_loop_disable_check(chip);
|
2015-06-26 21:10:50 +00:00
|
|
|
smbchg_otg_pulse_skip_disable(chip, REASON_OTG_ENABLED, true);
|
|
|
|
/* sleep to make sure the pulse skip is actually disabled */
|
|
|
|
msleep(20);
|
2014-02-20 01:27:12 +00:00
|
|
|
rc = smbchg_masked_write(chip, chip->bat_if_base + CMD_CHG_REG,
|
2015-03-18 22:56:31 +00:00
|
|
|
OTG_EN_BIT, OTG_EN_BIT);
|
2014-02-20 01:27:12 +00:00
|
|
|
if (rc < 0)
|
|
|
|
dev_err(chip->dev, "Couldn't enable OTG mode rc=%d\n", rc);
|
2014-12-03 19:30:00 +00:00
|
|
|
else
|
|
|
|
chip->otg_enable_time = ktime_get();
|
2014-06-30 22:02:34 +00:00
|
|
|
pr_smb(PR_STATUS, "Enabling OTG Boost\n");
|
2014-02-20 01:27:12 +00:00
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int smbchg_otg_regulator_disable(struct regulator_dev *rdev)
|
|
|
|
{
|
|
|
|
int rc = 0;
|
|
|
|
struct smbchg_chip *chip = rdev_get_drvdata(rdev);
|
|
|
|
|
|
|
|
rc = smbchg_masked_write(chip, chip->bat_if_base + CMD_CHG_REG,
|
2015-03-18 22:56:31 +00:00
|
|
|
OTG_EN_BIT, 0);
|
2014-02-20 01:27:12 +00:00
|
|
|
if (rc < 0)
|
|
|
|
dev_err(chip->dev, "Couldn't disable OTG mode rc=%d\n", rc);
|
2015-11-26 07:46:51 +00:00
|
|
|
chip->chg_otg_enabled = false;
|
2015-06-26 21:10:50 +00:00
|
|
|
smbchg_otg_pulse_skip_disable(chip, REASON_OTG_ENABLED, false);
|
2015-11-26 07:46:51 +00:00
|
|
|
smbchg_icl_loop_disable_check(chip);
|
2014-06-30 22:02:34 +00:00
|
|
|
pr_smb(PR_STATUS, "Disabling OTG Boost\n");
|
2014-02-20 01:27:12 +00:00
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int smbchg_otg_regulator_is_enable(struct regulator_dev *rdev)
|
|
|
|
{
|
|
|
|
int rc = 0;
|
|
|
|
u8 reg = 0;
|
|
|
|
struct smbchg_chip *chip = rdev_get_drvdata(rdev);
|
|
|
|
|
|
|
|
rc = smbchg_read(chip, ®, chip->bat_if_base + CMD_CHG_REG, 1);
|
|
|
|
if (rc < 0) {
|
|
|
|
dev_err(chip->dev,
|
|
|
|
"Couldn't read OTG enable bit rc=%d\n", rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2015-03-18 22:56:31 +00:00
|
|
|
return (reg & OTG_EN_BIT) ? 1 : 0;
|
2014-02-20 01:27:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
struct regulator_ops smbchg_otg_reg_ops = {
|
|
|
|
.enable = smbchg_otg_regulator_enable,
|
|
|
|
.disable = smbchg_otg_regulator_disable,
|
|
|
|
.is_enabled = smbchg_otg_regulator_is_enable,
|
|
|
|
};
|
|
|
|
|
2014-10-07 23:30:02 +00:00
|
|
|
#define USBIN_CHGR_CFG 0xF1
|
2015-03-18 20:43:47 +00:00
|
|
|
#define ADAPTER_ALLOWANCE_MASK 0x7
|
2014-10-07 23:30:02 +00:00
|
|
|
#define USBIN_ADAPTER_9V 0x3
|
2015-07-27 05:46:49 +00:00
|
|
|
#define USBIN_ADAPTER_5V_9V_CONT 0x2
|
2014-10-07 23:30:02 +00:00
|
|
|
#define HVDCP_EN_BIT BIT(3)
|
|
|
|
static int smbchg_external_otg_regulator_enable(struct regulator_dev *rdev)
|
|
|
|
{
|
|
|
|
int rc = 0;
|
|
|
|
struct smbchg_chip *chip = rdev_get_drvdata(rdev);
|
|
|
|
|
2015-11-26 09:38:11 +00:00
|
|
|
rc = vote(chip->usb_suspend_votable, OTG_EN_VOTER, true, 0);
|
2014-10-07 23:30:02 +00:00
|
|
|
if (rc < 0) {
|
|
|
|
dev_err(chip->dev, "Couldn't suspend charger rc=%d\n", rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
rc = smbchg_read(chip, &chip->original_usbin_allowance,
|
|
|
|
chip->usb_chgpth_base + USBIN_CHGR_CFG, 1);
|
|
|
|
if (rc < 0) {
|
|
|
|
dev_err(chip->dev, "Couldn't read usb allowance rc=%d\n", rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* To disallow source detect and usbin_uv interrupts, set the adapter
|
|
|
|
* allowance to 9V, so that the audio boost operating in reverse never
|
|
|
|
* gets detected as a valid input
|
|
|
|
*/
|
|
|
|
rc = smbchg_sec_masked_write(chip,
|
|
|
|
chip->usb_chgpth_base + CHGPTH_CFG,
|
|
|
|
HVDCP_EN_BIT, 0);
|
|
|
|
if (rc < 0) {
|
|
|
|
dev_err(chip->dev, "Couldn't disable HVDCP rc=%d\n", rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
rc = smbchg_sec_masked_write(chip,
|
|
|
|
chip->usb_chgpth_base + USBIN_CHGR_CFG,
|
|
|
|
0xFF, USBIN_ADAPTER_9V);
|
|
|
|
if (rc < 0) {
|
|
|
|
dev_err(chip->dev, "Couldn't write usb allowance rc=%d\n", rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
pr_smb(PR_STATUS, "Enabling OTG Boost\n");
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int smbchg_external_otg_regulator_disable(struct regulator_dev *rdev)
|
|
|
|
{
|
|
|
|
int rc = 0;
|
|
|
|
struct smbchg_chip *chip = rdev_get_drvdata(rdev);
|
|
|
|
|
2015-11-26 09:38:11 +00:00
|
|
|
rc = vote(chip->usb_suspend_votable, OTG_EN_VOTER, false, 0);
|
2014-10-07 23:30:02 +00:00
|
|
|
if (rc < 0) {
|
|
|
|
dev_err(chip->dev, "Couldn't unsuspend charger rc=%d\n", rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Reenable HVDCP and set the adapter allowance back to the original
|
|
|
|
* value in order to allow normal USBs to be recognized as a valid
|
|
|
|
* input.
|
|
|
|
*/
|
|
|
|
rc = smbchg_sec_masked_write(chip,
|
|
|
|
chip->usb_chgpth_base + CHGPTH_CFG,
|
|
|
|
HVDCP_EN_BIT, HVDCP_EN_BIT);
|
|
|
|
if (rc < 0) {
|
|
|
|
dev_err(chip->dev, "Couldn't enable HVDCP rc=%d\n", rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
rc = smbchg_sec_masked_write(chip,
|
|
|
|
chip->usb_chgpth_base + USBIN_CHGR_CFG,
|
|
|
|
0xFF, chip->original_usbin_allowance);
|
|
|
|
if (rc < 0) {
|
|
|
|
dev_err(chip->dev, "Couldn't write usb allowance rc=%d\n", rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
pr_smb(PR_STATUS, "Disabling OTG Boost\n");
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int smbchg_external_otg_regulator_is_enable(struct regulator_dev *rdev)
|
|
|
|
{
|
|
|
|
struct smbchg_chip *chip = rdev_get_drvdata(rdev);
|
|
|
|
|
2015-11-26 09:38:11 +00:00
|
|
|
return get_client_vote(chip->usb_suspend_votable, OTG_EN_VOTER);
|
2014-10-07 23:30:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
struct regulator_ops smbchg_external_otg_reg_ops = {
|
|
|
|
.enable = smbchg_external_otg_regulator_enable,
|
|
|
|
.disable = smbchg_external_otg_regulator_disable,
|
|
|
|
.is_enabled = smbchg_external_otg_regulator_is_enable,
|
|
|
|
};
|
|
|
|
|
2014-02-20 01:27:12 +00:00
|
|
|
static int smbchg_regulator_init(struct smbchg_chip *chip)
|
|
|
|
{
|
|
|
|
int rc = 0;
|
|
|
|
struct regulator_init_data *init_data;
|
|
|
|
struct regulator_config cfg = {};
|
2014-10-07 23:30:02 +00:00
|
|
|
struct device_node *regulator_node;
|
|
|
|
|
|
|
|
regulator_node = of_get_child_by_name(chip->dev->of_node,
|
|
|
|
"qcom,smbcharger-boost-otg");
|
2014-02-20 01:27:12 +00:00
|
|
|
|
2014-10-07 23:30:02 +00:00
|
|
|
init_data = of_get_regulator_init_data(chip->dev, regulator_node);
|
2014-02-20 01:27:12 +00:00
|
|
|
if (!init_data) {
|
|
|
|
dev_err(chip->dev, "Unable to allocate memory\n");
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (init_data->constraints.name) {
|
|
|
|
chip->otg_vreg.rdesc.owner = THIS_MODULE;
|
|
|
|
chip->otg_vreg.rdesc.type = REGULATOR_VOLTAGE;
|
|
|
|
chip->otg_vreg.rdesc.ops = &smbchg_otg_reg_ops;
|
|
|
|
chip->otg_vreg.rdesc.name = init_data->constraints.name;
|
|
|
|
|
|
|
|
cfg.dev = chip->dev;
|
|
|
|
cfg.init_data = init_data;
|
|
|
|
cfg.driver_data = chip;
|
2014-10-07 23:30:02 +00:00
|
|
|
cfg.of_node = regulator_node;
|
2014-02-20 01:27:12 +00:00
|
|
|
|
|
|
|
init_data->constraints.valid_ops_mask
|
|
|
|
|= REGULATOR_CHANGE_STATUS;
|
|
|
|
|
|
|
|
chip->otg_vreg.rdev = regulator_register(
|
|
|
|
&chip->otg_vreg.rdesc, &cfg);
|
|
|
|
if (IS_ERR(chip->otg_vreg.rdev)) {
|
|
|
|
rc = PTR_ERR(chip->otg_vreg.rdev);
|
|
|
|
chip->otg_vreg.rdev = NULL;
|
|
|
|
if (rc != -EPROBE_DEFER)
|
|
|
|
dev_err(chip->dev,
|
|
|
|
"OTG reg failed, rc=%d\n", rc);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-10-07 23:30:02 +00:00
|
|
|
if (rc)
|
|
|
|
return rc;
|
|
|
|
|
|
|
|
regulator_node = of_get_child_by_name(chip->dev->of_node,
|
|
|
|
"qcom,smbcharger-external-otg");
|
2015-02-11 14:23:46 +00:00
|
|
|
if (!regulator_node) {
|
|
|
|
dev_dbg(chip->dev, "external-otg node absent\n");
|
|
|
|
return 0;
|
|
|
|
}
|
2014-10-07 23:30:02 +00:00
|
|
|
init_data = of_get_regulator_init_data(chip->dev, regulator_node);
|
|
|
|
if (!init_data) {
|
|
|
|
dev_err(chip->dev, "Unable to allocate memory\n");
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (init_data->constraints.name) {
|
|
|
|
if (of_get_property(chip->dev->of_node,
|
|
|
|
"otg-parent-supply", NULL))
|
|
|
|
init_data->supply_regulator = "otg-parent";
|
|
|
|
chip->ext_otg_vreg.rdesc.owner = THIS_MODULE;
|
|
|
|
chip->ext_otg_vreg.rdesc.type = REGULATOR_VOLTAGE;
|
|
|
|
chip->ext_otg_vreg.rdesc.ops = &smbchg_external_otg_reg_ops;
|
|
|
|
chip->ext_otg_vreg.rdesc.name = init_data->constraints.name;
|
|
|
|
|
|
|
|
cfg.dev = chip->dev;
|
|
|
|
cfg.init_data = init_data;
|
|
|
|
cfg.driver_data = chip;
|
|
|
|
cfg.of_node = regulator_node;
|
|
|
|
|
|
|
|
init_data->constraints.valid_ops_mask
|
|
|
|
|= REGULATOR_CHANGE_STATUS;
|
|
|
|
|
|
|
|
chip->ext_otg_vreg.rdev = regulator_register(
|
|
|
|
&chip->ext_otg_vreg.rdesc, &cfg);
|
|
|
|
if (IS_ERR(chip->ext_otg_vreg.rdev)) {
|
|
|
|
rc = PTR_ERR(chip->ext_otg_vreg.rdev);
|
|
|
|
chip->ext_otg_vreg.rdev = NULL;
|
|
|
|
if (rc != -EPROBE_DEFER)
|
|
|
|
dev_err(chip->dev,
|
|
|
|
"external OTG reg failed, rc=%d\n", rc);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-02-20 01:27:12 +00:00
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void smbchg_regulator_deinit(struct smbchg_chip *chip)
|
|
|
|
{
|
|
|
|
if (chip->otg_vreg.rdev)
|
|
|
|
regulator_unregister(chip->otg_vreg.rdev);
|
2014-10-07 23:30:02 +00:00
|
|
|
if (chip->ext_otg_vreg.rdev)
|
|
|
|
regulator_unregister(chip->ext_otg_vreg.rdev);
|
2014-02-20 01:27:12 +00:00
|
|
|
}
|
|
|
|
|
2015-03-10 08:42:22 +00:00
|
|
|
#define CMD_CHG_LED_REG 0x43
|
|
|
|
#define CHG_LED_CTRL_BIT BIT(0)
|
|
|
|
#define LED_SW_CTRL_BIT 0x1
|
|
|
|
#define LED_CHG_CTRL_BIT 0x0
|
|
|
|
#define CHG_LED_ON 0x03
|
|
|
|
#define CHG_LED_OFF 0x00
|
|
|
|
#define LED_BLINKING_PATTERN1 0x01
|
|
|
|
#define LED_BLINKING_PATTERN2 0x02
|
|
|
|
#define LED_BLINKING_CFG_MASK SMB_MASK(2, 1)
|
|
|
|
#define CHG_LED_SHIFT 1
|
|
|
|
static int smbchg_chg_led_controls(struct smbchg_chip *chip)
|
|
|
|
{
|
2015-05-25 05:45:35 +00:00
|
|
|
u8 reg, mask;
|
2015-03-10 08:42:22 +00:00
|
|
|
int rc;
|
|
|
|
|
2015-05-25 05:45:35 +00:00
|
|
|
if (chip->cfg_chg_led_sw_ctrl) {
|
|
|
|
/* turn-off LED by default for software control */
|
|
|
|
mask = CHG_LED_CTRL_BIT | LED_BLINKING_CFG_MASK;
|
2015-03-10 08:42:22 +00:00
|
|
|
reg = LED_SW_CTRL_BIT;
|
2015-05-25 05:45:35 +00:00
|
|
|
} else {
|
|
|
|
mask = CHG_LED_CTRL_BIT;
|
2015-03-10 08:42:22 +00:00
|
|
|
reg = LED_CHG_CTRL_BIT;
|
2015-05-25 05:45:35 +00:00
|
|
|
}
|
2015-03-10 08:42:22 +00:00
|
|
|
|
|
|
|
rc = smbchg_masked_write(chip, chip->bat_if_base + CMD_CHG_LED_REG,
|
2015-05-25 05:45:35 +00:00
|
|
|
mask, reg);
|
2015-03-10 08:42:22 +00:00
|
|
|
if (rc < 0)
|
|
|
|
dev_err(chip->dev,
|
|
|
|
"Couldn't write LED_CTRL_BIT rc=%d\n", rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void smbchg_chg_led_brightness_set(struct led_classdev *cdev,
|
|
|
|
enum led_brightness value)
|
|
|
|
{
|
|
|
|
struct smbchg_chip *chip = container_of(cdev,
|
|
|
|
struct smbchg_chip, led_cdev);
|
|
|
|
u8 reg;
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
reg = (value > LED_OFF) ? CHG_LED_ON << CHG_LED_SHIFT :
|
|
|
|
CHG_LED_OFF << CHG_LED_SHIFT;
|
|
|
|
|
|
|
|
pr_smb(PR_STATUS,
|
|
|
|
"set the charger led brightness to value=%d\n",
|
|
|
|
value);
|
|
|
|
rc = smbchg_sec_masked_write(chip,
|
|
|
|
chip->bat_if_base + CMD_CHG_LED_REG,
|
|
|
|
LED_BLINKING_CFG_MASK, reg);
|
|
|
|
if (rc)
|
|
|
|
dev_err(chip->dev, "Couldn't write CHG_LED rc=%d\n",
|
|
|
|
rc);
|
|
|
|
}
|
|
|
|
|
|
|
|
static enum
|
|
|
|
led_brightness smbchg_chg_led_brightness_get(struct led_classdev *cdev)
|
|
|
|
{
|
|
|
|
struct smbchg_chip *chip = container_of(cdev,
|
|
|
|
struct smbchg_chip, led_cdev);
|
|
|
|
u8 reg_val, chg_led_sts;
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
rc = smbchg_read(chip, ®_val, chip->bat_if_base + CMD_CHG_LED_REG,
|
|
|
|
1);
|
|
|
|
if (rc < 0) {
|
|
|
|
dev_err(chip->dev,
|
|
|
|
"Couldn't read CHG_LED_REG sts rc=%d\n",
|
|
|
|
rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
chg_led_sts = (reg_val & LED_BLINKING_CFG_MASK) >> CHG_LED_SHIFT;
|
|
|
|
|
|
|
|
pr_smb(PR_STATUS, "chg_led_sts = %02x\n", chg_led_sts);
|
|
|
|
|
|
|
|
return (chg_led_sts == CHG_LED_OFF) ? LED_OFF : LED_FULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void smbchg_chg_led_blink_set(struct smbchg_chip *chip,
|
|
|
|
unsigned long blinking)
|
|
|
|
{
|
|
|
|
u8 reg;
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
if (blinking == 0)
|
|
|
|
reg = CHG_LED_OFF << CHG_LED_SHIFT;
|
|
|
|
else if (blinking == 1)
|
|
|
|
reg = LED_BLINKING_PATTERN1 << CHG_LED_SHIFT;
|
|
|
|
else if (blinking == 2)
|
|
|
|
reg = LED_BLINKING_PATTERN2 << CHG_LED_SHIFT;
|
|
|
|
else
|
|
|
|
reg = LED_BLINKING_PATTERN1 << CHG_LED_SHIFT;
|
|
|
|
|
|
|
|
rc = smbchg_sec_masked_write(chip,
|
|
|
|
chip->bat_if_base + CMD_CHG_LED_REG,
|
|
|
|
LED_BLINKING_CFG_MASK, reg);
|
|
|
|
if (rc)
|
|
|
|
dev_err(chip->dev, "Couldn't write CHG_LED rc=%d\n",
|
|
|
|
rc);
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t smbchg_chg_led_blink_store(struct device *dev,
|
|
|
|
struct device_attribute *attr, const char *buf, size_t len)
|
|
|
|
{
|
|
|
|
struct led_classdev *cdev = dev_get_drvdata(dev);
|
|
|
|
struct smbchg_chip *chip = container_of(cdev, struct smbchg_chip,
|
|
|
|
led_cdev);
|
|
|
|
unsigned long blinking;
|
|
|
|
ssize_t rc = -EINVAL;
|
|
|
|
|
|
|
|
rc = kstrtoul(buf, 10, &blinking);
|
|
|
|
if (rc)
|
|
|
|
return rc;
|
|
|
|
|
|
|
|
smbchg_chg_led_blink_set(chip, blinking);
|
|
|
|
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
|
|
|
static DEVICE_ATTR(blink, 0664, NULL, smbchg_chg_led_blink_store);
|
|
|
|
|
|
|
|
static struct attribute *led_blink_attributes[] = {
|
|
|
|
&dev_attr_blink.attr,
|
|
|
|
NULL,
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct attribute_group smbchg_led_attr_group = {
|
|
|
|
.attrs = led_blink_attributes
|
|
|
|
};
|
|
|
|
|
|
|
|
static int smbchg_register_chg_led(struct smbchg_chip *chip)
|
|
|
|
{
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
chip->led_cdev.name = "red";
|
|
|
|
chip->led_cdev.brightness_set = smbchg_chg_led_brightness_set;
|
|
|
|
chip->led_cdev.brightness_get = smbchg_chg_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;
|
|
|
|
}
|
|
|
|
|
|
|
|
rc = sysfs_create_group(&chip->led_cdev.dev->kobj,
|
|
|
|
&smbchg_led_attr_group);
|
|
|
|
if (rc) {
|
|
|
|
dev_err(chip->dev, "led sysfs rc: %d\n", rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2014-10-29 18:01:40 +00:00
|
|
|
static int vf_adjust_low_threshold = 5;
|
|
|
|
module_param(vf_adjust_low_threshold, int, 0644);
|
|
|
|
|
|
|
|
static int vf_adjust_high_threshold = 7;
|
|
|
|
module_param(vf_adjust_high_threshold, int, 0644);
|
|
|
|
|
|
|
|
static int vf_adjust_n_samples = 10;
|
|
|
|
module_param(vf_adjust_n_samples, int, 0644);
|
|
|
|
|
|
|
|
static int vf_adjust_max_delta_mv = 40;
|
|
|
|
module_param(vf_adjust_max_delta_mv, int, 0644);
|
|
|
|
|
|
|
|
static int vf_adjust_trim_steps_per_adjust = 1;
|
|
|
|
module_param(vf_adjust_trim_steps_per_adjust, int, 0644);
|
|
|
|
|
|
|
|
#define CENTER_TRIM_CODE 7
|
|
|
|
#define MAX_LIN_CODE 14
|
|
|
|
#define MAX_TRIM_CODE 15
|
|
|
|
#define SCALE_SHIFT 4
|
|
|
|
#define VF_TRIM_OFFSET_MASK SMB_MASK(3, 0)
|
|
|
|
#define VF_STEP_SIZE_MV 10
|
|
|
|
#define SCALE_LSB_MV 17
|
|
|
|
static int smbchg_trim_add_steps(int prev_trim, int delta_steps)
|
|
|
|
{
|
|
|
|
int scale_steps;
|
|
|
|
int linear_offset, linear_scale;
|
|
|
|
int offset_code = prev_trim & VF_TRIM_OFFSET_MASK;
|
|
|
|
int scale_code = (prev_trim & ~VF_TRIM_OFFSET_MASK) >> SCALE_SHIFT;
|
|
|
|
|
|
|
|
if (abs(delta_steps) > 1) {
|
|
|
|
pr_smb(PR_STATUS,
|
|
|
|
"Cant trim multiple steps delta_steps = %d\n",
|
|
|
|
delta_steps);
|
|
|
|
return prev_trim;
|
|
|
|
}
|
|
|
|
if (offset_code <= CENTER_TRIM_CODE)
|
|
|
|
linear_offset = offset_code + CENTER_TRIM_CODE;
|
|
|
|
else if (offset_code > CENTER_TRIM_CODE)
|
|
|
|
linear_offset = MAX_TRIM_CODE - offset_code;
|
|
|
|
|
|
|
|
if (scale_code <= CENTER_TRIM_CODE)
|
|
|
|
linear_scale = scale_code + CENTER_TRIM_CODE;
|
|
|
|
else if (scale_code > CENTER_TRIM_CODE)
|
|
|
|
linear_scale = scale_code - (CENTER_TRIM_CODE + 1);
|
|
|
|
|
|
|
|
/* check if we can accomodate delta steps with just the offset */
|
|
|
|
if (linear_offset + delta_steps >= 0
|
|
|
|
&& linear_offset + delta_steps <= MAX_LIN_CODE) {
|
|
|
|
linear_offset += delta_steps;
|
|
|
|
|
|
|
|
if (linear_offset > CENTER_TRIM_CODE)
|
|
|
|
offset_code = linear_offset - CENTER_TRIM_CODE;
|
|
|
|
else
|
|
|
|
offset_code = MAX_TRIM_CODE - linear_offset;
|
|
|
|
|
|
|
|
return (prev_trim & ~VF_TRIM_OFFSET_MASK) | offset_code;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* changing offset cannot satisfy delta steps, change the scale bits */
|
|
|
|
scale_steps = delta_steps > 0 ? 1 : -1;
|
|
|
|
|
|
|
|
if (linear_scale + scale_steps < 0
|
|
|
|
|| linear_scale + scale_steps > MAX_LIN_CODE) {
|
|
|
|
pr_smb(PR_STATUS,
|
|
|
|
"Cant trim scale_steps = %d delta_steps = %d\n",
|
|
|
|
scale_steps, delta_steps);
|
|
|
|
return prev_trim;
|
|
|
|
}
|
|
|
|
|
|
|
|
linear_scale += scale_steps;
|
|
|
|
|
|
|
|
if (linear_scale > CENTER_TRIM_CODE)
|
|
|
|
scale_code = linear_scale - CENTER_TRIM_CODE;
|
|
|
|
else
|
|
|
|
scale_code = linear_scale + (CENTER_TRIM_CODE + 1);
|
|
|
|
prev_trim = (prev_trim & VF_TRIM_OFFSET_MASK)
|
|
|
|
| scale_code << SCALE_SHIFT;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* now that we have changed scale which is a 17mV jump, change the
|
|
|
|
* offset bits (10mV) too so the effective change is just 7mV
|
|
|
|
*/
|
|
|
|
delta_steps = -1 * delta_steps;
|
|
|
|
|
|
|
|
linear_offset = clamp(linear_offset + delta_steps, 0, MAX_LIN_CODE);
|
|
|
|
if (linear_offset > CENTER_TRIM_CODE)
|
|
|
|
offset_code = linear_offset - CENTER_TRIM_CODE;
|
|
|
|
else
|
|
|
|
offset_code = MAX_TRIM_CODE - linear_offset;
|
|
|
|
|
|
|
|
return (prev_trim & ~VF_TRIM_OFFSET_MASK) | offset_code;
|
|
|
|
}
|
|
|
|
|
|
|
|
#define TRIM_14 0xFE
|
|
|
|
#define VF_TRIM_MASK 0xFF
|
|
|
|
static int smbchg_adjust_vfloat_mv_trim(struct smbchg_chip *chip,
|
|
|
|
int delta_mv)
|
|
|
|
{
|
|
|
|
int sign, delta_steps, rc = 0;
|
|
|
|
u8 prev_trim, new_trim;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
sign = delta_mv > 0 ? 1 : -1;
|
|
|
|
delta_steps = (delta_mv + sign * VF_STEP_SIZE_MV / 2)
|
|
|
|
/ VF_STEP_SIZE_MV;
|
|
|
|
|
|
|
|
rc = smbchg_read(chip, &prev_trim, chip->misc_base + TRIM_14, 1);
|
|
|
|
if (rc) {
|
|
|
|
dev_err(chip->dev, "Unable to read trim 14: %d\n", rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 1; i <= abs(delta_steps)
|
|
|
|
&& i <= vf_adjust_trim_steps_per_adjust; i++) {
|
|
|
|
new_trim = (u8)smbchg_trim_add_steps(prev_trim,
|
|
|
|
delta_steps > 0 ? 1 : -1);
|
|
|
|
if (new_trim == prev_trim) {
|
|
|
|
pr_smb(PR_STATUS,
|
|
|
|
"VFloat trim unchanged from %02x\n", prev_trim);
|
|
|
|
/* treat no trim change as an error */
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
rc = smbchg_sec_masked_write(chip, chip->misc_base + TRIM_14,
|
|
|
|
VF_TRIM_MASK, new_trim);
|
|
|
|
if (rc < 0) {
|
|
|
|
dev_err(chip->dev,
|
|
|
|
"Couldn't change vfloat trim rc=%d\n", rc);
|
|
|
|
}
|
|
|
|
pr_smb(PR_STATUS,
|
|
|
|
"VFlt trim %02x to %02x, delta steps: %d\n",
|
|
|
|
prev_trim, new_trim, delta_steps);
|
|
|
|
prev_trim = new_trim;
|
|
|
|
}
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2015-03-26 20:32:38 +00:00
|
|
|
#define VFLOAT_RESAMPLE_DELAY_MS 10000
|
2014-10-29 18:01:40 +00:00
|
|
|
static void smbchg_vfloat_adjust_work(struct work_struct *work)
|
|
|
|
{
|
|
|
|
struct smbchg_chip *chip = container_of(work,
|
|
|
|
struct smbchg_chip,
|
|
|
|
vfloat_adjust_work.work);
|
|
|
|
int vbat_uv, vbat_mv, ibat_ua, rc, delta_vfloat_mv;
|
|
|
|
bool taper, enable;
|
|
|
|
|
2015-07-22 23:38:25 +00:00
|
|
|
smbchg_stay_awake(chip, PM_REASON_VFLOAT_ADJUST);
|
2014-10-29 18:01:40 +00:00
|
|
|
taper = (get_prop_charge_type(chip)
|
|
|
|
== POWER_SUPPLY_CHARGE_TYPE_TAPER);
|
|
|
|
enable = taper && (chip->parallel.current_max_ma == 0);
|
|
|
|
|
|
|
|
if (!enable) {
|
|
|
|
pr_smb(PR_MISC,
|
|
|
|
"Stopping vfloat adj taper=%d parallel_ma = %d\n",
|
|
|
|
taper, chip->parallel.current_max_ma);
|
|
|
|
goto stop;
|
|
|
|
}
|
|
|
|
|
2015-06-30 16:33:13 +00:00
|
|
|
if (get_prop_batt_health(chip) != POWER_SUPPLY_HEALTH_GOOD) {
|
|
|
|
pr_smb(PR_STATUS, "JEITA active, skipping\n");
|
2015-07-22 23:38:25 +00:00
|
|
|
goto stop;
|
2015-06-30 16:33:13 +00:00
|
|
|
}
|
|
|
|
|
2014-10-29 18:01:40 +00:00
|
|
|
set_property_on_fg(chip, POWER_SUPPLY_PROP_UPDATE_NOW, 1);
|
|
|
|
rc = get_property_from_fg(chip,
|
|
|
|
POWER_SUPPLY_PROP_VOLTAGE_NOW, &vbat_uv);
|
|
|
|
if (rc) {
|
|
|
|
pr_smb(PR_STATUS,
|
|
|
|
"bms psy does not support voltage rc = %d\n", rc);
|
|
|
|
goto stop;
|
|
|
|
}
|
|
|
|
vbat_mv = vbat_uv / 1000;
|
|
|
|
|
|
|
|
if ((vbat_mv - chip->vfloat_mv) < -1 * vf_adjust_max_delta_mv) {
|
|
|
|
pr_smb(PR_STATUS, "Skip vbat out of range: %d\n", vbat_mv);
|
2015-03-26 20:32:38 +00:00
|
|
|
goto reschedule;
|
2014-10-29 18:01:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
rc = get_property_from_fg(chip,
|
|
|
|
POWER_SUPPLY_PROP_CURRENT_NOW, &ibat_ua);
|
|
|
|
if (rc) {
|
|
|
|
pr_smb(PR_STATUS,
|
|
|
|
"bms psy does not support current_now rc = %d\n", rc);
|
|
|
|
goto stop;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ibat_ua / 1000 > -chip->iterm_ma) {
|
|
|
|
pr_smb(PR_STATUS, "Skip ibat too high: %d\n", ibat_ua);
|
2015-03-26 20:32:38 +00:00
|
|
|
goto reschedule;
|
2014-10-29 18:01:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pr_smb(PR_STATUS, "sample number = %d vbat_mv = %d ibat_ua = %d\n",
|
|
|
|
chip->n_vbat_samples,
|
|
|
|
vbat_mv,
|
|
|
|
ibat_ua);
|
|
|
|
|
|
|
|
chip->max_vbat_sample = max(chip->max_vbat_sample, vbat_mv);
|
|
|
|
chip->n_vbat_samples += 1;
|
|
|
|
if (chip->n_vbat_samples < vf_adjust_n_samples) {
|
|
|
|
pr_smb(PR_STATUS, "Skip %d samples; max = %d\n",
|
|
|
|
chip->n_vbat_samples, chip->max_vbat_sample);
|
2015-03-26 20:32:38 +00:00
|
|
|
goto reschedule;
|
2014-10-29 18:01:40 +00:00
|
|
|
}
|
|
|
|
/* if max vbat > target vfloat, delta_vfloat_mv could be negative */
|
|
|
|
delta_vfloat_mv = chip->vfloat_mv - chip->max_vbat_sample;
|
|
|
|
pr_smb(PR_STATUS, "delta_vfloat_mv = %d, samples = %d, mvbat = %d\n",
|
|
|
|
delta_vfloat_mv, chip->n_vbat_samples, chip->max_vbat_sample);
|
|
|
|
/*
|
|
|
|
* enough valid samples has been collected, adjust trim codes
|
|
|
|
* based on maximum of collected vbat samples if necessary
|
|
|
|
*/
|
|
|
|
if (delta_vfloat_mv > vf_adjust_high_threshold
|
|
|
|
|| delta_vfloat_mv < -1 * vf_adjust_low_threshold) {
|
|
|
|
rc = smbchg_adjust_vfloat_mv_trim(chip, delta_vfloat_mv);
|
|
|
|
if (rc) {
|
|
|
|
pr_smb(PR_STATUS,
|
|
|
|
"Stopping vfloat adj after trim adj rc = %d\n",
|
|
|
|
rc);
|
|
|
|
goto stop;
|
|
|
|
}
|
|
|
|
chip->max_vbat_sample = 0;
|
|
|
|
chip->n_vbat_samples = 0;
|
2015-03-26 20:32:38 +00:00
|
|
|
goto reschedule;
|
2014-10-29 18:01:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
stop:
|
|
|
|
chip->max_vbat_sample = 0;
|
|
|
|
chip->n_vbat_samples = 0;
|
|
|
|
smbchg_relax(chip, PM_REASON_VFLOAT_ADJUST);
|
|
|
|
return;
|
2015-03-26 20:32:38 +00:00
|
|
|
|
|
|
|
reschedule:
|
|
|
|
schedule_delayed_work(&chip->vfloat_adjust_work,
|
|
|
|
msecs_to_jiffies(VFLOAT_RESAMPLE_DELAY_MS));
|
|
|
|
return;
|
2014-10-29 18:01:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static int smbchg_charging_status_change(struct smbchg_chip *chip)
|
|
|
|
{
|
|
|
|
smbchg_vfloat_adjust_check(chip);
|
2015-02-12 19:56:11 +00:00
|
|
|
set_property_on_fg(chip, POWER_SUPPLY_PROP_STATUS,
|
|
|
|
get_prop_batt_status(chip));
|
2014-10-29 18:01:40 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-10-21 21:03:40 +00:00
|
|
|
#define BAT_IF_TRIM7_REG 0xF7
|
|
|
|
#define CFG_750KHZ_BIT BIT(1)
|
|
|
|
#define BB_CLMP_SEL 0xF8
|
|
|
|
#define BB_CLMP_MASK SMB_MASK(1, 0)
|
|
|
|
#define BB_CLMP_VFIX_3338MV 0x1
|
|
|
|
#define BB_CLMP_VFIX_3512MV 0x2
|
|
|
|
static int smbchg_set_optimal_charging_mode(struct smbchg_chip *chip, int type)
|
|
|
|
{
|
|
|
|
int rc;
|
|
|
|
bool hvdcp2 = (type == POWER_SUPPLY_TYPE_USB_HVDCP
|
|
|
|
&& smbchg_is_usbin_active_pwr_src(chip));
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Set the charger switching freq to 1MHZ if HVDCP 2.0,
|
|
|
|
* or 750KHZ otherwise
|
|
|
|
*/
|
|
|
|
rc = smbchg_sec_masked_write(chip,
|
|
|
|
chip->bat_if_base + BAT_IF_TRIM7_REG,
|
|
|
|
CFG_750KHZ_BIT, hvdcp2 ? 0 : CFG_750KHZ_BIT);
|
|
|
|
if (rc) {
|
|
|
|
dev_err(chip->dev, "Cannot set switching freq: %d\n", rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Set the charger switch frequency clamp voltage threshold to 3.338V
|
|
|
|
* if HVDCP 2.0, or 3.512V otherwise.
|
|
|
|
*/
|
|
|
|
rc = smbchg_sec_masked_write(chip, chip->bat_if_base + BB_CLMP_SEL,
|
|
|
|
BB_CLMP_MASK,
|
|
|
|
hvdcp2 ? BB_CLMP_VFIX_3338MV : BB_CLMP_VFIX_3512MV);
|
|
|
|
if (rc) {
|
|
|
|
dev_err(chip->dev, "Cannot set switching freq: %d\n", rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-10-19 23:01:13 +00:00
|
|
|
#define DEFAULT_SDP_MA 100
|
|
|
|
#define DEFAULT_CDP_MA 1500
|
|
|
|
static int smbchg_change_usb_supply_type(struct smbchg_chip *chip,
|
|
|
|
enum power_supply_type type)
|
|
|
|
{
|
|
|
|
int rc, current_limit_ma;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* if the type is not unknown, set the type before changing ICL vote
|
|
|
|
* in order to ensure that the correct current limit registers are
|
|
|
|
* used
|
|
|
|
*/
|
|
|
|
if (type != POWER_SUPPLY_TYPE_UNKNOWN)
|
|
|
|
chip->usb_supply_type = type;
|
|
|
|
|
|
|
|
if (type == POWER_SUPPLY_TYPE_USB)
|
|
|
|
current_limit_ma = DEFAULT_SDP_MA;
|
|
|
|
else if (type == POWER_SUPPLY_TYPE_USB)
|
|
|
|
current_limit_ma = DEFAULT_SDP_MA;
|
|
|
|
else if (type == POWER_SUPPLY_TYPE_USB_CDP)
|
|
|
|
current_limit_ma = DEFAULT_CDP_MA;
|
|
|
|
else if (type == POWER_SUPPLY_TYPE_USB_HVDCP
|
|
|
|
|| type == POWER_SUPPLY_TYPE_USB_HVDCP_3)
|
2015-10-29 19:46:21 +00:00
|
|
|
current_limit_ma = smbchg_default_hvdcp_icl_ma;
|
2015-10-19 23:01:13 +00:00
|
|
|
else
|
2015-10-29 19:46:21 +00:00
|
|
|
current_limit_ma = smbchg_default_dcp_icl_ma;
|
2015-10-19 23:01:13 +00:00
|
|
|
|
|
|
|
pr_smb(PR_STATUS, "Type %d: setting mA = %d\n",
|
|
|
|
type, current_limit_ma);
|
|
|
|
rc = vote(chip->usb_icl_votable, PSY_ICL_VOTER, true,
|
|
|
|
current_limit_ma);
|
|
|
|
if (rc < 0) {
|
|
|
|
pr_err("Couldn't vote for new USB ICL rc=%d\n", rc);
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!chip->skip_usb_notification)
|
|
|
|
power_supply_set_supply_type(chip->usb_psy, type);
|
|
|
|
|
2016-03-23 10:16:19 +00:00
|
|
|
/*
|
|
|
|
* otherwise if it is unknown, remove vote
|
|
|
|
* and set type after the vote
|
|
|
|
*/
|
|
|
|
if (type == POWER_SUPPLY_TYPE_UNKNOWN) {
|
|
|
|
rc = vote(chip->usb_icl_votable, PSY_ICL_VOTER, false,
|
|
|
|
current_limit_ma);
|
|
|
|
if (rc < 0)
|
|
|
|
pr_err("Couldn't remove ICL vote rc=%d\n", rc);
|
|
|
|
|
2015-10-19 23:01:13 +00:00
|
|
|
chip->usb_supply_type = type;
|
2016-03-23 10:16:19 +00:00
|
|
|
}
|
2015-10-19 23:01:13 +00:00
|
|
|
|
2015-10-21 21:03:40 +00:00
|
|
|
/* set the correct buck switching frequency */
|
|
|
|
rc = smbchg_set_optimal_charging_mode(chip, type);
|
|
|
|
if (rc < 0)
|
|
|
|
pr_err("Couldn't set charger optimal mode rc=%d\n", rc);
|
|
|
|
|
2015-10-19 23:01:13 +00:00
|
|
|
out:
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2015-07-27 05:46:49 +00:00
|
|
|
static bool is_hvdcp_present(struct smbchg_chip *chip)
|
2015-06-23 03:59:35 +00:00
|
|
|
{
|
|
|
|
int rc;
|
|
|
|
u8 reg, hvdcp_sel;
|
|
|
|
|
|
|
|
rc = smbchg_read(chip, ®,
|
|
|
|
chip->usb_chgpth_base + USBIN_HVDCP_STS, 1);
|
|
|
|
if (rc < 0) {
|
2015-07-27 05:46:49 +00:00
|
|
|
pr_err("Couldn't read hvdcp status rc = %d\n", rc);
|
|
|
|
return false;
|
2015-06-23 03:59:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pr_smb(PR_STATUS, "HVDCP_STS = 0x%02x\n", reg);
|
|
|
|
/*
|
|
|
|
* If a valid HVDCP is detected, notify it to the usb_psy only
|
|
|
|
* if USB is still present.
|
|
|
|
*/
|
|
|
|
if (chip->schg_version == QPNP_SCHG_LITE)
|
|
|
|
hvdcp_sel = SCHG_LITE_USBIN_HVDCP_SEL_BIT;
|
|
|
|
else
|
|
|
|
hvdcp_sel = USBIN_HVDCP_SEL_BIT;
|
|
|
|
|
2015-07-27 05:46:49 +00:00
|
|
|
if ((reg & hvdcp_sel) && is_usb_present(chip))
|
|
|
|
return true;
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2015-07-27 06:44:03 +00:00
|
|
|
#define HVDCP_ADAPTER_SEL_MASK SMB_MASK(5, 4)
|
|
|
|
#define HVDCP_5V 0x00
|
|
|
|
#define HVDCP_9V 0x10
|
|
|
|
#define USB_CMD_HVDCP_1 0x42
|
|
|
|
#define FORCE_HVDCP_2p0 BIT(3)
|
|
|
|
static int force_9v_hvdcp(struct smbchg_chip *chip)
|
|
|
|
{
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
/* Force 5V HVDCP */
|
|
|
|
rc = smbchg_sec_masked_write(chip,
|
|
|
|
chip->usb_chgpth_base + CHGPTH_CFG,
|
|
|
|
HVDCP_ADAPTER_SEL_MASK, HVDCP_5V);
|
|
|
|
if (rc) {
|
|
|
|
pr_err("Couldn't set hvdcp config in chgpath_chg rc=%d\n", rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Force QC2.0 */
|
|
|
|
rc = smbchg_masked_write(chip,
|
|
|
|
chip->usb_chgpth_base + USB_CMD_HVDCP_1,
|
|
|
|
FORCE_HVDCP_2p0, FORCE_HVDCP_2p0);
|
|
|
|
rc |= smbchg_masked_write(chip,
|
|
|
|
chip->usb_chgpth_base + USB_CMD_HVDCP_1,
|
|
|
|
FORCE_HVDCP_2p0, 0);
|
|
|
|
if (rc < 0) {
|
|
|
|
pr_err("Couldn't force QC2.0 rc=%d\n", rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* wait for QC2.0 */
|
|
|
|
msleep(500);
|
|
|
|
|
|
|
|
/* Force 9V HVDCP */
|
|
|
|
rc = smbchg_sec_masked_write(chip,
|
|
|
|
chip->usb_chgpth_base + CHGPTH_CFG,
|
|
|
|
HVDCP_ADAPTER_SEL_MASK, HVDCP_9V);
|
|
|
|
if (rc)
|
|
|
|
pr_err("Couldn't set hvdcp config in chgpath_chg rc=%d\n", rc);
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2015-07-27 05:46:49 +00:00
|
|
|
static void smbchg_hvdcp_det_work(struct work_struct *work)
|
|
|
|
{
|
|
|
|
struct smbchg_chip *chip = container_of(work,
|
|
|
|
struct smbchg_chip,
|
|
|
|
hvdcp_det_work.work);
|
2015-07-27 06:44:03 +00:00
|
|
|
int rc;
|
|
|
|
|
2015-07-27 05:46:49 +00:00
|
|
|
if (is_hvdcp_present(chip)) {
|
2015-08-18 03:14:26 +00:00
|
|
|
if (!chip->hvdcp3_supported &&
|
|
|
|
(chip->wa_flags & SMBCHG_HVDCP_9V_EN_WA)) {
|
2015-07-27 06:44:03 +00:00
|
|
|
/* force HVDCP 2.0 */
|
|
|
|
rc = force_9v_hvdcp(chip);
|
|
|
|
if (rc)
|
|
|
|
pr_err("could not force 9V HVDCP continuing rc=%d\n",
|
|
|
|
rc);
|
|
|
|
}
|
2015-10-19 23:01:13 +00:00
|
|
|
smbchg_change_usb_supply_type(chip,
|
|
|
|
POWER_SUPPLY_TYPE_USB_HVDCP);
|
2015-06-23 03:59:35 +00:00
|
|
|
if (chip->psy_registered)
|
|
|
|
power_supply_changed(&chip->batt_psy);
|
|
|
|
smbchg_aicl_deglitch_wa_check(chip);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-06-30 01:11:04 +00:00
|
|
|
static int set_usb_psy_dp_dm(struct smbchg_chip *chip, int state)
|
|
|
|
{
|
|
|
|
int rc;
|
|
|
|
u8 reg;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* ensure that we are not in the middle of an insertion where usbin_uv
|
|
|
|
* is low and src_detect hasnt gone high. If so force dp=F dm=F
|
|
|
|
* which guarantees proper type detection
|
|
|
|
*/
|
|
|
|
rc = smbchg_read(chip, ®, chip->usb_chgpth_base + RT_STS, 1);
|
|
|
|
if (!rc && !(reg & USBIN_UV_BIT) && !(reg & USBIN_SRC_DET_BIT)) {
|
|
|
|
pr_smb(PR_MISC, "overwriting state = %d with %d\n",
|
|
|
|
state, POWER_SUPPLY_DP_DM_DPF_DMF);
|
|
|
|
state = POWER_SUPPLY_DP_DM_DPF_DMF;
|
|
|
|
}
|
|
|
|
pr_smb(PR_MISC, "setting usb psy dp dm = %d\n", state);
|
|
|
|
return power_supply_set_dp_dm(chip->usb_psy, state);
|
|
|
|
}
|
2015-07-27 05:46:49 +00:00
|
|
|
|
|
|
|
#define APSD_CFG 0xF5
|
|
|
|
#define AUTO_SRC_DETECT_EN_BIT BIT(0)
|
|
|
|
#define APSD_TIMEOUT_MS 1500
|
|
|
|
static void restore_from_hvdcp_detection(struct smbchg_chip *chip)
|
|
|
|
{
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
/* switch to 9V HVDCP */
|
|
|
|
rc = smbchg_sec_masked_write(chip, chip->usb_chgpth_base + CHGPTH_CFG,
|
|
|
|
HVDCP_ADAPTER_SEL_MASK, HVDCP_9V);
|
|
|
|
if (rc < 0)
|
|
|
|
pr_err("Couldn't configure HVDCP 9V rc=%d\n", rc);
|
|
|
|
|
|
|
|
/* enable HVDCP */
|
|
|
|
rc = smbchg_sec_masked_write(chip,
|
|
|
|
chip->usb_chgpth_base + CHGPTH_CFG,
|
|
|
|
HVDCP_EN_BIT, HVDCP_EN_BIT);
|
|
|
|
if (rc < 0)
|
|
|
|
pr_err("Couldn't enable HVDCP rc=%d\n", rc);
|
|
|
|
|
|
|
|
/* enable APSD */
|
|
|
|
rc = smbchg_sec_masked_write(chip,
|
|
|
|
chip->usb_chgpth_base + APSD_CFG,
|
|
|
|
AUTO_SRC_DETECT_EN_BIT, AUTO_SRC_DETECT_EN_BIT);
|
|
|
|
if (rc < 0)
|
|
|
|
pr_err("Couldn't enable APSD rc=%d\n", rc);
|
|
|
|
|
|
|
|
/* allow 5 to 9V chargers */
|
|
|
|
rc = smbchg_sec_masked_write(chip,
|
|
|
|
chip->usb_chgpth_base + USBIN_CHGR_CFG,
|
|
|
|
ADAPTER_ALLOWANCE_MASK, USBIN_ADAPTER_5V_9V_CONT);
|
|
|
|
if (rc < 0)
|
|
|
|
pr_err("Couldn't write usb allowance rc=%d\n", rc);
|
|
|
|
|
2015-07-22 00:44:43 +00:00
|
|
|
rc = smbchg_sec_masked_write(chip, chip->usb_chgpth_base + USB_AICL_CFG,
|
|
|
|
AICL_EN_BIT, AICL_EN_BIT);
|
|
|
|
if (rc < 0)
|
|
|
|
pr_err("Couldn't enable AICL rc=%d\n", rc);
|
|
|
|
|
2015-07-27 05:46:49 +00:00
|
|
|
chip->hvdcp_3_det_ignore_uv = false;
|
|
|
|
chip->pulse_cnt = 0;
|
2016-02-01 15:14:16 +00:00
|
|
|
|
|
|
|
if ((chip->schg_version == QPNP_SCHG_LITE)
|
|
|
|
&& is_hvdcp_present(chip)) {
|
|
|
|
pr_smb(PR_MISC, "Forcing 9V HVDCP 2.0\n");
|
|
|
|
rc = force_9v_hvdcp(chip);
|
|
|
|
if (rc)
|
|
|
|
pr_err("Failed to force 9V HVDCP=%d\n", rc);
|
|
|
|
}
|
|
|
|
|
|
|
|
pr_smb(PR_MISC, "Retracting HVDCP vote for ICL\n");
|
|
|
|
rc = vote(chip->usb_icl_votable, HVDCP_ICL_VOTER, false, 0);
|
|
|
|
if (rc < 0)
|
|
|
|
pr_err("Couldn't retract HVDCP ICL vote rc=%d\n", rc);
|
2015-07-27 05:46:49 +00:00
|
|
|
}
|
|
|
|
|
2015-09-14 22:40:02 +00:00
|
|
|
#define RESTRICTED_CHG_FCC_PERCENT 50
|
|
|
|
static int smbchg_restricted_charging(struct smbchg_chip *chip, bool enable)
|
|
|
|
{
|
|
|
|
int current_table_index, fastchg_current;
|
|
|
|
int rc = 0;
|
|
|
|
|
|
|
|
/* If enable, set the fcc to the set point closest
|
|
|
|
* to 50% of the configured fcc while remaining below it
|
|
|
|
*/
|
|
|
|
current_table_index = find_smaller_in_array(
|
|
|
|
chip->tables.usb_ilim_ma_table,
|
|
|
|
chip->cfg_fastchg_current_ma
|
|
|
|
* RESTRICTED_CHG_FCC_PERCENT / 100,
|
|
|
|
chip->tables.usb_ilim_ma_len);
|
|
|
|
fastchg_current =
|
|
|
|
chip->tables.usb_ilim_ma_table[current_table_index];
|
|
|
|
rc = vote(chip->fcc_votable, RESTRICTED_CHG_FCC_VOTER, enable,
|
|
|
|
fastchg_current);
|
|
|
|
|
|
|
|
pr_smb(PR_STATUS, "restricted_charging set to %d\n", enable);
|
|
|
|
chip->restricted_charging = enable;
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2015-06-23 03:59:35 +00:00
|
|
|
static void handle_usb_removal(struct smbchg_chip *chip)
|
|
|
|
{
|
|
|
|
struct power_supply *parallel_psy = get_parallel_psy(chip);
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
pr_smb(PR_STATUS, "triggered\n");
|
|
|
|
smbchg_aicl_deglitch_wa_check(chip);
|
|
|
|
if (chip->force_aicl_rerun && !chip->very_weak_charger) {
|
|
|
|
rc = smbchg_hw_aicl_rerun_en(chip, true);
|
|
|
|
if (rc)
|
|
|
|
pr_err("Error enabling AICL rerun rc= %d\n",
|
|
|
|
rc);
|
|
|
|
}
|
|
|
|
/* Clear the OV detected status set before */
|
|
|
|
if (chip->usb_ov_det)
|
|
|
|
chip->usb_ov_det = false;
|
2016-08-10 09:58:50 +00:00
|
|
|
/* cancel/wait for hvdcp pending work if any */
|
|
|
|
cancel_delayed_work_sync(&chip->hvdcp_det_work);
|
2015-10-19 23:01:13 +00:00
|
|
|
smbchg_change_usb_supply_type(chip, POWER_SUPPLY_TYPE_UNKNOWN);
|
2015-11-26 07:20:42 +00:00
|
|
|
if (!chip->skip_usb_notification) {
|
2015-06-23 03:59:35 +00:00
|
|
|
pr_smb(PR_MISC, "setting usb psy present = %d\n",
|
|
|
|
chip->usb_present);
|
|
|
|
power_supply_set_present(chip->usb_psy, chip->usb_present);
|
|
|
|
}
|
2015-11-26 07:20:42 +00:00
|
|
|
set_usb_psy_dp_dm(chip, POWER_SUPPLY_DP_DM_DPR_DMR);
|
|
|
|
schedule_work(&chip->usb_set_online_work);
|
|
|
|
pr_smb(PR_MISC, "setting usb psy health UNKNOWN\n");
|
|
|
|
rc = power_supply_set_health_state(chip->usb_psy,
|
|
|
|
POWER_SUPPLY_HEALTH_UNKNOWN);
|
|
|
|
if (rc < 0)
|
|
|
|
pr_smb(PR_STATUS,
|
|
|
|
"usb psy does not allow updating prop %d rc = %d\n",
|
|
|
|
POWER_SUPPLY_HEALTH_UNKNOWN, rc);
|
|
|
|
|
2015-06-23 03:59:35 +00:00
|
|
|
if (parallel_psy && chip->parallel_charger_detected)
|
|
|
|
power_supply_set_present(parallel_psy, false);
|
|
|
|
if (chip->parallel.avail && chip->aicl_done_irq
|
|
|
|
&& chip->enable_aicl_wake) {
|
|
|
|
disable_irq_wake(chip->aicl_done_irq);
|
|
|
|
chip->enable_aicl_wake = false;
|
|
|
|
}
|
|
|
|
chip->parallel.enabled_once = false;
|
|
|
|
chip->vbat_above_headroom = false;
|
|
|
|
rc = smbchg_masked_write(chip, chip->usb_chgpth_base + CMD_IL,
|
|
|
|
ICL_OVERRIDE_BIT, 0);
|
|
|
|
if (rc < 0)
|
|
|
|
pr_err("Couldn't set override rc = %d\n", rc);
|
2015-07-27 05:46:49 +00:00
|
|
|
|
2015-09-14 20:51:25 +00:00
|
|
|
vote(chip->usb_icl_votable, WEAK_CHARGER_ICL_VOTER, false, 0);
|
2015-10-29 23:45:51 +00:00
|
|
|
chip->usb_icl_delta = 0;
|
|
|
|
vote(chip->usb_icl_votable, SW_AICL_ICL_VOTER, false, 0);
|
2015-07-27 05:46:49 +00:00
|
|
|
restore_from_hvdcp_detection(chip);
|
2015-06-23 03:59:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static bool is_src_detect_high(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;
|
|
|
|
}
|
|
|
|
return reg &= USBIN_SRC_DET_BIT;
|
|
|
|
}
|
|
|
|
|
2015-07-27 05:46:49 +00:00
|
|
|
static bool is_usbin_uv_high(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;
|
|
|
|
}
|
|
|
|
return reg &= USBIN_UV_BIT;
|
|
|
|
}
|
|
|
|
|
2015-06-23 03:59:35 +00:00
|
|
|
#define HVDCP_NOTIFY_MS 2500
|
|
|
|
static void handle_usb_insertion(struct smbchg_chip *chip)
|
|
|
|
{
|
|
|
|
enum power_supply_type usb_supply_type;
|
|
|
|
int rc;
|
|
|
|
char *usb_type_name = "null";
|
|
|
|
|
|
|
|
pr_smb(PR_STATUS, "triggered\n");
|
|
|
|
/* usb inserted */
|
|
|
|
read_usb_type(chip, &usb_type_name, &usb_supply_type);
|
|
|
|
pr_smb(PR_STATUS,
|
|
|
|
"inserted type = %d (%s)", usb_supply_type, usb_type_name);
|
|
|
|
|
|
|
|
smbchg_aicl_deglitch_wa_check(chip);
|
2015-10-19 23:01:13 +00:00
|
|
|
smbchg_change_usb_supply_type(chip, usb_supply_type);
|
2015-11-26 07:20:42 +00:00
|
|
|
if (!chip->skip_usb_notification) {
|
2015-06-23 03:59:35 +00:00
|
|
|
pr_smb(PR_MISC, "setting usb psy present = %d\n",
|
|
|
|
chip->usb_present);
|
|
|
|
power_supply_set_present(chip->usb_psy, chip->usb_present);
|
|
|
|
}
|
|
|
|
|
2015-11-26 07:20:42 +00:00
|
|
|
/* Notify the USB psy if OV condition is not present */
|
|
|
|
if (!chip->usb_ov_det) {
|
|
|
|
/*
|
|
|
|
* Note that this could still be a very weak charger
|
|
|
|
* if the handle_usb_insertion was triggered from
|
|
|
|
* the falling edge of an USBIN_OV interrupt
|
|
|
|
*/
|
|
|
|
pr_smb(PR_MISC, "setting usb psy health %s\n",
|
|
|
|
chip->very_weak_charger
|
|
|
|
? "UNSPEC_FAILURE" : "GOOD");
|
|
|
|
rc = power_supply_set_health_state(chip->usb_psy,
|
|
|
|
chip->very_weak_charger
|
|
|
|
? POWER_SUPPLY_HEALTH_UNSPEC_FAILURE
|
|
|
|
: POWER_SUPPLY_HEALTH_GOOD);
|
|
|
|
if (rc < 0)
|
|
|
|
pr_smb(PR_STATUS,
|
|
|
|
"usb psy does not allow updating prop %d rc = %d\n",
|
|
|
|
POWER_SUPPLY_HEALTH_GOOD, rc);
|
|
|
|
}
|
|
|
|
schedule_work(&chip->usb_set_online_work);
|
|
|
|
|
2015-06-23 03:59:35 +00:00
|
|
|
if (usb_supply_type == POWER_SUPPLY_TYPE_USB_DCP)
|
|
|
|
schedule_delayed_work(&chip->hvdcp_det_work,
|
|
|
|
msecs_to_jiffies(HVDCP_NOTIFY_MS));
|
2015-06-17 03:50:32 +00:00
|
|
|
|
2016-03-25 17:59:39 +00:00
|
|
|
smbchg_detect_parallel_charger(chip);
|
2015-06-23 03:59:35 +00:00
|
|
|
|
|
|
|
if (chip->parallel.avail && chip->aicl_done_irq
|
|
|
|
&& !chip->enable_aicl_wake) {
|
|
|
|
rc = enable_irq_wake(chip->aicl_done_irq);
|
|
|
|
chip->enable_aicl_wake = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void update_usb_status(struct smbchg_chip *chip, bool usb_present, bool force)
|
|
|
|
{
|
|
|
|
mutex_lock(&chip->usb_status_lock);
|
|
|
|
if (force) {
|
|
|
|
chip->usb_present = usb_present;
|
|
|
|
chip->usb_present ? handle_usb_insertion(chip)
|
|
|
|
: handle_usb_removal(chip);
|
|
|
|
goto unlock;
|
|
|
|
}
|
|
|
|
if (!chip->usb_present && usb_present) {
|
|
|
|
chip->usb_present = usb_present;
|
|
|
|
handle_usb_insertion(chip);
|
|
|
|
} else if (chip->usb_present && !usb_present) {
|
|
|
|
chip->usb_present = usb_present;
|
|
|
|
handle_usb_removal(chip);
|
|
|
|
}
|
2015-07-06 05:43:40 +00:00
|
|
|
|
|
|
|
/* update FG */
|
|
|
|
set_property_on_fg(chip, POWER_SUPPLY_PROP_STATUS,
|
|
|
|
get_prop_batt_status(chip));
|
2015-06-23 03:59:35 +00:00
|
|
|
unlock:
|
|
|
|
mutex_unlock(&chip->usb_status_lock);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int otg_oc_reset(struct smbchg_chip *chip)
|
|
|
|
{
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
rc = smbchg_masked_write(chip, chip->bat_if_base + CMD_CHG_REG,
|
|
|
|
OTG_EN_BIT, 0);
|
|
|
|
if (rc)
|
|
|
|
pr_err("Failed to disable OTG rc=%d\n", rc);
|
|
|
|
|
|
|
|
msleep(20);
|
2015-08-11 16:48:06 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* There is a possibility that an USBID interrupt might have
|
|
|
|
* occurred notifying USB power supply to disable OTG. We
|
|
|
|
* should not enable OTG in such cases.
|
|
|
|
*/
|
|
|
|
if (!is_otg_present(chip)) {
|
|
|
|
pr_smb(PR_STATUS,
|
|
|
|
"OTG is not present, not enabling OTG_EN_BIT\n");
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
2015-06-23 03:59:35 +00:00
|
|
|
rc = smbchg_masked_write(chip, chip->bat_if_base + CMD_CHG_REG,
|
|
|
|
OTG_EN_BIT, OTG_EN_BIT);
|
|
|
|
if (rc)
|
|
|
|
pr_err("Failed to re-enable OTG rc=%d\n", rc);
|
|
|
|
|
2015-08-11 16:48:06 +00:00
|
|
|
out:
|
2015-06-23 03:59:35 +00:00
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int get_current_time(unsigned long *now_tm_sec)
|
|
|
|
{
|
|
|
|
struct rtc_time tm;
|
|
|
|
struct rtc_device *rtc;
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
rtc = rtc_class_open(CONFIG_RTC_HCTOSYS_DEVICE);
|
|
|
|
if (rtc == NULL) {
|
|
|
|
pr_err("%s: unable to open rtc device (%s)\n",
|
|
|
|
__FILE__, CONFIG_RTC_HCTOSYS_DEVICE);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
rc = rtc_read_time(rtc, &tm);
|
|
|
|
if (rc) {
|
|
|
|
pr_err("Error reading rtc device (%s) : %d\n",
|
|
|
|
CONFIG_RTC_HCTOSYS_DEVICE, rc);
|
|
|
|
goto close_time;
|
|
|
|
}
|
|
|
|
|
|
|
|
rc = rtc_valid_tm(&tm);
|
|
|
|
if (rc) {
|
|
|
|
pr_err("Invalid RTC time (%s): %d\n",
|
|
|
|
CONFIG_RTC_HCTOSYS_DEVICE, rc);
|
|
|
|
goto close_time;
|
|
|
|
}
|
|
|
|
rtc_tm_to_time(&tm, now_tm_sec);
|
|
|
|
|
|
|
|
close_time:
|
|
|
|
rtc_class_close(rtc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
#define AICL_IRQ_LIMIT_SECONDS 60
|
|
|
|
#define AICL_IRQ_LIMIT_COUNT 25
|
|
|
|
static void increment_aicl_count(struct smbchg_chip *chip)
|
|
|
|
{
|
|
|
|
bool bad_charger = false;
|
2015-10-12 21:32:31 +00:00
|
|
|
int max_aicl_count, rc;
|
2015-06-23 03:59:35 +00:00
|
|
|
u8 reg;
|
|
|
|
long elapsed_seconds;
|
|
|
|
unsigned long now_seconds;
|
|
|
|
|
|
|
|
pr_smb(PR_INTERRUPT, "aicl count c:%d dgltch:%d first:%ld\n",
|
|
|
|
chip->aicl_irq_count, chip->aicl_deglitch_short,
|
|
|
|
chip->first_aicl_seconds);
|
|
|
|
|
|
|
|
rc = smbchg_read(chip, ®,
|
|
|
|
chip->usb_chgpth_base + ICL_STS_1_REG, 1);
|
|
|
|
if (!rc)
|
|
|
|
chip->aicl_complete = reg & AICL_STS_BIT;
|
|
|
|
else
|
|
|
|
chip->aicl_complete = false;
|
|
|
|
|
|
|
|
if (chip->aicl_deglitch_short || chip->force_aicl_rerun) {
|
|
|
|
if (!chip->aicl_irq_count)
|
|
|
|
get_current_time(&chip->first_aicl_seconds);
|
|
|
|
get_current_time(&now_seconds);
|
|
|
|
elapsed_seconds = now_seconds
|
|
|
|
- chip->first_aicl_seconds;
|
|
|
|
|
|
|
|
if (elapsed_seconds > AICL_IRQ_LIMIT_SECONDS) {
|
|
|
|
pr_smb(PR_INTERRUPT,
|
|
|
|
"resetting: elp:%ld first:%ld now:%ld c=%d\n",
|
|
|
|
elapsed_seconds, chip->first_aicl_seconds,
|
|
|
|
now_seconds, chip->aicl_irq_count);
|
|
|
|
chip->aicl_irq_count = 1;
|
|
|
|
get_current_time(&chip->first_aicl_seconds);
|
|
|
|
return;
|
|
|
|
}
|
2015-10-12 21:32:31 +00:00
|
|
|
/*
|
|
|
|
* Double the amount of AICLs allowed if parallel charging is
|
|
|
|
* enabled.
|
|
|
|
*/
|
|
|
|
max_aicl_count = AICL_IRQ_LIMIT_COUNT
|
|
|
|
* (chip->parallel.avail ? 2 : 1);
|
2015-06-23 03:59:35 +00:00
|
|
|
chip->aicl_irq_count++;
|
|
|
|
|
2015-10-12 21:32:31 +00:00
|
|
|
if (chip->aicl_irq_count > max_aicl_count) {
|
2015-06-23 03:59:35 +00:00
|
|
|
pr_smb(PR_INTERRUPT, "elp:%ld first:%ld now:%ld c=%d\n",
|
|
|
|
elapsed_seconds, chip->first_aicl_seconds,
|
|
|
|
now_seconds, chip->aicl_irq_count);
|
|
|
|
pr_smb(PR_INTERRUPT, "Disable AICL rerun\n");
|
2015-09-14 20:51:25 +00:00
|
|
|
chip->very_weak_charger = true;
|
|
|
|
bad_charger = true;
|
|
|
|
|
2015-06-23 03:59:35 +00:00
|
|
|
/*
|
|
|
|
* Disable AICL rerun since many interrupts were
|
|
|
|
* triggered in a short time
|
|
|
|
*/
|
|
|
|
rc = smbchg_hw_aicl_rerun_en(chip, false);
|
|
|
|
if (rc)
|
2015-09-14 20:51:25 +00:00
|
|
|
pr_err("Couldn't disable AICL reruns rc=%d\n",
|
|
|
|
rc);
|
|
|
|
|
|
|
|
/* Vote 100mA current limit */
|
|
|
|
rc = vote(chip->usb_icl_votable, WEAK_CHARGER_ICL_VOTER,
|
|
|
|
true, CURRENT_100_MA);
|
|
|
|
if (rc < 0) {
|
|
|
|
pr_err("Can't vote %d current limit rc=%d\n",
|
|
|
|
CURRENT_100_MA, rc);
|
|
|
|
}
|
|
|
|
|
2015-06-23 03:59:35 +00:00
|
|
|
chip->aicl_irq_count = 0;
|
|
|
|
} else if ((get_prop_charge_type(chip) ==
|
|
|
|
POWER_SUPPLY_CHARGE_TYPE_FAST) &&
|
|
|
|
(reg & AICL_SUSP_BIT)) {
|
|
|
|
/*
|
|
|
|
* If the AICL_SUSP_BIT is on, then AICL reruns have
|
|
|
|
* already been disabled. Set the very weak charger
|
|
|
|
* flag so that the driver reports a bad charger
|
|
|
|
* and does not reenable AICL reruns.
|
|
|
|
*/
|
|
|
|
chip->very_weak_charger = true;
|
|
|
|
bad_charger = true;
|
|
|
|
}
|
|
|
|
if (bad_charger) {
|
2015-06-11 17:27:14 +00:00
|
|
|
pr_smb(PR_MISC,
|
|
|
|
"setting usb psy health UNSPEC_FAILURE\n");
|
2015-06-23 03:59:35 +00:00
|
|
|
rc = power_supply_set_health_state(chip->usb_psy,
|
|
|
|
POWER_SUPPLY_HEALTH_UNSPEC_FAILURE);
|
|
|
|
if (rc)
|
|
|
|
pr_err("Couldn't set health on usb psy rc:%d\n",
|
|
|
|
rc);
|
|
|
|
schedule_work(&chip->usb_set_online_work);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-07-27 05:46:49 +00:00
|
|
|
static int wait_for_usbin_uv(struct smbchg_chip *chip, bool high)
|
|
|
|
{
|
|
|
|
int rc;
|
|
|
|
int tries = 3;
|
|
|
|
struct completion *completion = &chip->usbin_uv_lowered;
|
|
|
|
bool usbin_uv;
|
|
|
|
|
|
|
|
if (high)
|
|
|
|
completion = &chip->usbin_uv_raised;
|
|
|
|
|
|
|
|
while (tries--) {
|
|
|
|
rc = wait_for_completion_interruptible_timeout(
|
|
|
|
completion,
|
|
|
|
msecs_to_jiffies(APSD_TIMEOUT_MS));
|
|
|
|
if (rc >= 0)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
usbin_uv = is_usbin_uv_high(chip);
|
|
|
|
|
|
|
|
if (high == usbin_uv)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
pr_err("usbin uv didnt go to a %s state, still at %s, tries = %d, rc = %d\n",
|
|
|
|
high ? "risen" : "lowered",
|
|
|
|
usbin_uv ? "high" : "low",
|
|
|
|
tries, rc);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int wait_for_src_detect(struct smbchg_chip *chip, bool high)
|
|
|
|
{
|
|
|
|
int rc;
|
|
|
|
int tries = 3;
|
|
|
|
struct completion *completion = &chip->src_det_lowered;
|
|
|
|
bool src_detect;
|
|
|
|
|
|
|
|
if (high)
|
|
|
|
completion = &chip->src_det_raised;
|
|
|
|
|
|
|
|
while (tries--) {
|
|
|
|
rc = wait_for_completion_interruptible_timeout(
|
|
|
|
completion,
|
|
|
|
msecs_to_jiffies(APSD_TIMEOUT_MS));
|
|
|
|
if (rc >= 0)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
src_detect = is_src_detect_high(chip);
|
|
|
|
|
|
|
|
if (high == src_detect)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
pr_err("src detect didnt go to a %s state, still at %s, tries = %d, rc = %d\n",
|
|
|
|
high ? "risen" : "lowered",
|
|
|
|
src_detect ? "high" : "low",
|
|
|
|
tries, rc);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int fake_insertion_removal(struct smbchg_chip *chip, bool insertion)
|
|
|
|
{
|
|
|
|
int rc;
|
|
|
|
bool src_detect;
|
|
|
|
bool usbin_uv;
|
|
|
|
|
|
|
|
if (insertion) {
|
|
|
|
INIT_COMPLETION(chip->src_det_raised);
|
|
|
|
INIT_COMPLETION(chip->usbin_uv_lowered);
|
|
|
|
} else {
|
|
|
|
INIT_COMPLETION(chip->src_det_lowered);
|
|
|
|
INIT_COMPLETION(chip->usbin_uv_raised);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* ensure that usbin uv real time status is in the right state */
|
|
|
|
usbin_uv = is_usbin_uv_high(chip);
|
|
|
|
if (usbin_uv != insertion) {
|
|
|
|
pr_err("Skip faking, usbin uv is already %d\n", usbin_uv);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* ensure that src_detect real time status is in the right state */
|
|
|
|
src_detect = is_src_detect_high(chip);
|
|
|
|
if (src_detect == insertion) {
|
|
|
|
pr_err("Skip faking, src detect is already %d\n", src_detect);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
pr_smb(PR_MISC, "Allow only %s charger\n",
|
|
|
|
insertion ? "5-9V" : "9V only");
|
|
|
|
rc = smbchg_sec_masked_write(chip,
|
|
|
|
chip->usb_chgpth_base + USBIN_CHGR_CFG,
|
|
|
|
ADAPTER_ALLOWANCE_MASK,
|
|
|
|
insertion ?
|
|
|
|
USBIN_ADAPTER_5V_9V_CONT : USBIN_ADAPTER_9V);
|
|
|
|
if (rc < 0) {
|
|
|
|
pr_err("Couldn't write usb allowance rc=%d\n", rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
pr_smb(PR_MISC, "Waiting on %s usbin uv\n",
|
|
|
|
insertion ? "falling" : "rising");
|
|
|
|
rc = wait_for_usbin_uv(chip, !insertion);
|
|
|
|
if (rc < 0) {
|
|
|
|
pr_err("wait for usbin uv failed rc = %d\n", rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
pr_smb(PR_MISC, "Waiting on %s src det\n",
|
|
|
|
insertion ? "rising" : "falling");
|
|
|
|
rc = wait_for_src_detect(chip, insertion);
|
|
|
|
if (rc < 0) {
|
|
|
|
pr_err("wait for src detect failed rc = %d\n", rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2016-03-21 22:55:19 +00:00
|
|
|
static void smbchg_handle_hvdcp3_disable(struct smbchg_chip *chip)
|
|
|
|
{
|
|
|
|
enum power_supply_type usb_supply_type;
|
|
|
|
char *usb_type_name = "NULL";
|
|
|
|
|
|
|
|
chip->pulse_cnt = 0;
|
|
|
|
|
|
|
|
if (is_hvdcp_present(chip)) {
|
|
|
|
smbchg_change_usb_supply_type(chip,
|
|
|
|
POWER_SUPPLY_TYPE_USB_HVDCP);
|
|
|
|
} else {
|
|
|
|
read_usb_type(chip, &usb_type_name, &usb_supply_type);
|
|
|
|
smbchg_change_usb_supply_type(chip, usb_supply_type);
|
|
|
|
if (usb_supply_type == POWER_SUPPLY_TYPE_USB_DCP)
|
|
|
|
schedule_delayed_work(&chip->hvdcp_det_work,
|
|
|
|
msecs_to_jiffies(HVDCP_NOTIFY_MS));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-07-27 05:46:49 +00:00
|
|
|
static int smbchg_prepare_for_pulsing(struct smbchg_chip *chip)
|
|
|
|
{
|
|
|
|
int rc = 0;
|
|
|
|
u8 reg;
|
|
|
|
|
|
|
|
/* switch to 5V HVDCP */
|
|
|
|
pr_smb(PR_MISC, "Switch to 5V HVDCP\n");
|
|
|
|
rc = smbchg_sec_masked_write(chip, chip->usb_chgpth_base + CHGPTH_CFG,
|
|
|
|
HVDCP_ADAPTER_SEL_MASK, HVDCP_5V);
|
|
|
|
if (rc < 0) {
|
|
|
|
pr_err("Couldn't configure HVDCP 5V rc=%d\n", rc);
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* wait for HVDCP to lower to 5V */
|
|
|
|
msleep(500);
|
|
|
|
/*
|
|
|
|
* Check if the same hvdcp session is in progress. src_det should be
|
|
|
|
* high and that we are still in 5V hvdcp
|
|
|
|
*/
|
|
|
|
if (!is_src_detect_high(chip)) {
|
|
|
|
pr_smb(PR_MISC, "src det low after 500mS sleep\n");
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* disable HVDCP */
|
|
|
|
pr_smb(PR_MISC, "Disable HVDCP\n");
|
|
|
|
rc = smbchg_sec_masked_write(chip, chip->usb_chgpth_base + CHGPTH_CFG,
|
|
|
|
HVDCP_EN_BIT, 0);
|
|
|
|
if (rc < 0) {
|
|
|
|
pr_err("Couldn't disable HVDCP rc=%d\n", rc);
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
2015-11-26 09:07:24 +00:00
|
|
|
pr_smb(PR_MISC, "HVDCP voting for 300mA ICL\n");
|
|
|
|
rc = vote(chip->usb_icl_votable, HVDCP_ICL_VOTER, true, 300);
|
2015-07-27 05:46:49 +00:00
|
|
|
if (rc < 0) {
|
2015-11-26 09:07:24 +00:00
|
|
|
pr_err("Couldn't vote for 300mA HVDCP ICL rc=%d\n", rc);
|
2015-07-27 05:46:49 +00:00
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
pr_smb(PR_MISC, "Disable AICL\n");
|
|
|
|
smbchg_sec_masked_write(chip, chip->usb_chgpth_base + USB_AICL_CFG,
|
|
|
|
AICL_EN_BIT, 0);
|
|
|
|
|
|
|
|
chip->hvdcp_3_det_ignore_uv = true;
|
|
|
|
/* fake a removal */
|
|
|
|
pr_smb(PR_MISC, "Faking Removal\n");
|
|
|
|
rc = fake_insertion_removal(chip, false);
|
|
|
|
if (rc < 0) {
|
|
|
|
pr_err("Couldn't fake removal HVDCP Removed rc=%d\n", rc);
|
|
|
|
goto handle_removal;
|
|
|
|
}
|
|
|
|
|
2016-04-04 23:13:50 +00:00
|
|
|
set_usb_psy_dp_dm(chip, POWER_SUPPLY_DP_DM_DP0P6_DMF);
|
|
|
|
|
2015-07-27 05:46:49 +00:00
|
|
|
/* disable APSD */
|
|
|
|
pr_smb(PR_MISC, "Disabling APSD\n");
|
|
|
|
rc = smbchg_sec_masked_write(chip,
|
|
|
|
chip->usb_chgpth_base + APSD_CFG,
|
|
|
|
AUTO_SRC_DETECT_EN_BIT, 0);
|
|
|
|
if (rc < 0) {
|
|
|
|
pr_err("Couldn't disable APSD rc=%d\n", rc);
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* fake an insertion */
|
|
|
|
pr_smb(PR_MISC, "Faking Insertion\n");
|
|
|
|
rc = fake_insertion_removal(chip, true);
|
|
|
|
if (rc < 0) {
|
|
|
|
pr_err("Couldn't fake insertion rc=%d\n", rc);
|
|
|
|
goto handle_removal;
|
|
|
|
}
|
|
|
|
chip->hvdcp_3_det_ignore_uv = false;
|
|
|
|
|
|
|
|
pr_smb(PR_MISC, "Enable AICL\n");
|
|
|
|
smbchg_sec_masked_write(chip, chip->usb_chgpth_base + USB_AICL_CFG,
|
|
|
|
AICL_EN_BIT, AICL_EN_BIT);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* DCP will switch to HVDCP in this time by removing the short
|
|
|
|
* between DP DM
|
|
|
|
*/
|
|
|
|
msleep(HVDCP_NOTIFY_MS);
|
|
|
|
/*
|
|
|
|
* Check if the same hvdcp session is in progress. src_det should be
|
|
|
|
* high and the usb type should be none since APSD was disabled
|
|
|
|
*/
|
|
|
|
if (!is_src_detect_high(chip)) {
|
|
|
|
pr_smb(PR_MISC, "src det low after 2s sleep\n");
|
|
|
|
rc = -EINVAL;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
smbchg_read(chip, ®, chip->misc_base + IDEV_STS, 1);
|
|
|
|
if ((reg >> TYPE_BITS_OFFSET) != 0) {
|
|
|
|
pr_smb(PR_MISC, "type bits set after 2s sleep - abort\n");
|
|
|
|
rc = -EINVAL;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
set_usb_psy_dp_dm(chip, POWER_SUPPLY_DP_DM_DP0P6_DM3P3);
|
|
|
|
/* Wait 60mS after entering continuous mode */
|
|
|
|
msleep(60);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
out:
|
|
|
|
chip->hvdcp_3_det_ignore_uv = false;
|
|
|
|
restore_from_hvdcp_detection(chip);
|
|
|
|
return rc;
|
|
|
|
handle_removal:
|
|
|
|
chip->hvdcp_3_det_ignore_uv = false;
|
|
|
|
update_usb_status(chip, 0, 0);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int smbchg_unprepare_for_pulsing(struct smbchg_chip *chip)
|
|
|
|
{
|
|
|
|
int rc = 0;
|
|
|
|
|
|
|
|
set_usb_psy_dp_dm(chip, POWER_SUPPLY_DP_DM_DPF_DMF);
|
|
|
|
|
|
|
|
/* switch to 9V HVDCP */
|
|
|
|
pr_smb(PR_MISC, "Switch to 9V HVDCP\n");
|
|
|
|
rc = smbchg_sec_masked_write(chip, chip->usb_chgpth_base + CHGPTH_CFG,
|
|
|
|
HVDCP_ADAPTER_SEL_MASK, HVDCP_9V);
|
|
|
|
if (rc < 0) {
|
|
|
|
pr_err("Couldn't configure HVDCP 9V rc=%d\n", rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* enable HVDCP */
|
|
|
|
pr_smb(PR_MISC, "Enable HVDCP\n");
|
|
|
|
rc = smbchg_sec_masked_write(chip,
|
|
|
|
chip->usb_chgpth_base + CHGPTH_CFG,
|
|
|
|
HVDCP_EN_BIT, HVDCP_EN_BIT);
|
|
|
|
if (rc < 0) {
|
|
|
|
pr_err("Couldn't enable HVDCP rc=%d\n", rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* enable APSD */
|
|
|
|
pr_smb(PR_MISC, "Enabling APSD\n");
|
|
|
|
rc = smbchg_sec_masked_write(chip,
|
|
|
|
chip->usb_chgpth_base + APSD_CFG,
|
|
|
|
AUTO_SRC_DETECT_EN_BIT, AUTO_SRC_DETECT_EN_BIT);
|
|
|
|
if (rc < 0) {
|
|
|
|
pr_err("Couldn't enable APSD rc=%d\n", rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Disable AICL */
|
|
|
|
pr_smb(PR_MISC, "Disable AICL\n");
|
|
|
|
rc = smbchg_sec_masked_write(chip, chip->usb_chgpth_base + USB_AICL_CFG,
|
|
|
|
AICL_EN_BIT, 0);
|
|
|
|
if (rc < 0) {
|
|
|
|
pr_err("Couldn't disable AICL rc=%d\n", rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* fake a removal */
|
|
|
|
chip->hvdcp_3_det_ignore_uv = true;
|
|
|
|
pr_smb(PR_MISC, "Faking Removal\n");
|
|
|
|
rc = fake_insertion_removal(chip, false);
|
|
|
|
if (rc < 0) {
|
|
|
|
pr_err("Couldn't fake removal rc=%d\n", rc);
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
2015-10-07 01:05:56 +00:00
|
|
|
/*
|
|
|
|
* reset the enabled once flag for parallel charging so
|
|
|
|
* parallel charging can immediately restart after the HVDCP pulsing
|
|
|
|
* is complete
|
|
|
|
*/
|
|
|
|
chip->parallel.enabled_once = false;
|
|
|
|
|
2015-07-27 05:46:49 +00:00
|
|
|
/* fake an insertion */
|
|
|
|
pr_smb(PR_MISC, "Faking Insertion\n");
|
|
|
|
rc = fake_insertion_removal(chip, true);
|
|
|
|
if (rc < 0) {
|
|
|
|
pr_err("Couldn't fake insertion rc=%d\n", rc);
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
chip->hvdcp_3_det_ignore_uv = false;
|
|
|
|
|
|
|
|
/* Enable AICL */
|
|
|
|
pr_smb(PR_MISC, "Enable AICL\n");
|
|
|
|
rc = smbchg_sec_masked_write(chip, chip->usb_chgpth_base + USB_AICL_CFG,
|
|
|
|
AICL_EN_BIT, 0);
|
|
|
|
if (rc < 0) {
|
|
|
|
pr_err("Couldn't enable AICL rc=%d\n", rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2015-11-26 09:07:24 +00:00
|
|
|
out:
|
|
|
|
pr_smb(PR_MISC, "Retracting HVDCP vote for ICL\n");
|
|
|
|
rc = vote(chip->usb_icl_votable, HVDCP_ICL_VOTER, false, 0);
|
2015-07-27 05:46:49 +00:00
|
|
|
if (rc < 0)
|
2015-11-26 09:07:24 +00:00
|
|
|
pr_err("Couldn't retract HVDCP ICL vote rc=%d\n", rc);
|
2015-07-27 05:46:49 +00:00
|
|
|
|
|
|
|
chip->hvdcp_3_det_ignore_uv = false;
|
|
|
|
if (!is_src_detect_high(chip)) {
|
|
|
|
pr_smb(PR_MISC, "HVDCP removed\n");
|
|
|
|
update_usb_status(chip, 0, 0);
|
|
|
|
}
|
2016-03-21 22:55:19 +00:00
|
|
|
|
|
|
|
/* This could be because allow_hvdcp3 set to false runtime */
|
|
|
|
if (is_usb_present(chip) && !chip->allow_hvdcp3_detection)
|
|
|
|
smbchg_handle_hvdcp3_disable(chip);
|
|
|
|
|
2015-07-27 05:46:49 +00:00
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2015-08-12 12:28:44 +00:00
|
|
|
#define USB_CMD_APSD 0x41
|
|
|
|
#define APSD_RERUN BIT(0)
|
|
|
|
static int rerun_apsd(struct smbchg_chip *chip)
|
|
|
|
{
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
INIT_COMPLETION(chip->src_det_raised);
|
|
|
|
INIT_COMPLETION(chip->usbin_uv_lowered);
|
|
|
|
INIT_COMPLETION(chip->src_det_lowered);
|
|
|
|
INIT_COMPLETION(chip->usbin_uv_raised);
|
|
|
|
|
|
|
|
/* re-run APSD */
|
|
|
|
rc = smbchg_masked_write(chip, chip->usb_chgpth_base + USB_CMD_APSD,
|
|
|
|
APSD_RERUN, APSD_RERUN);
|
|
|
|
if (rc) {
|
|
|
|
pr_err("Couldn't re-run APSD rc=%d\n", rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
pr_smb(PR_MISC, "Waiting on rising usbin uv\n");
|
|
|
|
rc = wait_for_usbin_uv(chip, true);
|
|
|
|
if (rc < 0) {
|
|
|
|
pr_err("wait for usbin uv failed rc = %d\n", rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
pr_smb(PR_MISC, "Waiting on falling src det\n");
|
|
|
|
rc = wait_for_src_detect(chip, false);
|
|
|
|
if (rc < 0) {
|
|
|
|
pr_err("wait for src detect failed rc = %d\n", rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
pr_smb(PR_MISC, "Waiting on falling usbin uv\n");
|
|
|
|
rc = wait_for_usbin_uv(chip, false);
|
|
|
|
if (rc < 0) {
|
|
|
|
pr_err("wait for usbin uv failed rc = %d\n", rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
pr_smb(PR_MISC, "Waiting on rising src det\n");
|
|
|
|
rc = wait_for_src_detect(chip, true);
|
|
|
|
if (rc < 0) {
|
|
|
|
pr_err("wait for src detect failed rc = %d\n", rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
#define SCHG_LITE_USBIN_HVDCP_5_9V 0x8
|
|
|
|
#define SCHG_LITE_USBIN_HVDCP_5_9V_SEL_MASK 0x38
|
|
|
|
#define SCHG_LITE_USBIN_HVDCP_SEL_IDLE BIT(3)
|
|
|
|
static bool is_hvdcp_5v_cont_mode(struct smbchg_chip *chip)
|
|
|
|
{
|
|
|
|
int rc;
|
|
|
|
u8 reg = 0;
|
|
|
|
|
|
|
|
rc = smbchg_read(chip, ®,
|
|
|
|
chip->usb_chgpth_base + USBIN_HVDCP_STS, 1);
|
|
|
|
if (rc) {
|
|
|
|
pr_err("Unable to read HVDCP status rc=%d\n", rc);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
pr_smb(PR_STATUS, "HVDCP status = %x\n", reg);
|
|
|
|
|
|
|
|
if (reg & SCHG_LITE_USBIN_HVDCP_SEL_IDLE) {
|
|
|
|
rc = smbchg_read(chip, ®,
|
|
|
|
chip->usb_chgpth_base + INPUT_STS, 1);
|
|
|
|
if (rc) {
|
|
|
|
pr_err("Unable to read INPUT status rc=%d\n", rc);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
pr_smb(PR_STATUS, "INPUT status = %x\n", reg);
|
|
|
|
if ((reg & SCHG_LITE_USBIN_HVDCP_5_9V_SEL_MASK) ==
|
|
|
|
SCHG_LITE_USBIN_HVDCP_5_9V)
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2015-07-27 05:46:49 +00:00
|
|
|
static int smbchg_prepare_for_pulsing_lite(struct smbchg_chip *chip)
|
|
|
|
{
|
|
|
|
int rc = 0;
|
|
|
|
|
2016-02-01 15:14:16 +00:00
|
|
|
pr_smb(PR_MISC, "HVDCP voting for 300mA ICL\n");
|
|
|
|
rc = vote(chip->usb_icl_votable, HVDCP_ICL_VOTER, true, 300);
|
|
|
|
if (rc < 0) {
|
|
|
|
pr_err("Couldn't vote for 300mA HVDCP ICL rc=%d\n", rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2015-08-12 12:28:44 +00:00
|
|
|
/* check if HVDCP is already in 5V continuous mode */
|
|
|
|
if (is_hvdcp_5v_cont_mode(chip)) {
|
|
|
|
pr_smb(PR_MISC, "HVDCP by default is in 5V continuous mode\n");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* switch to 5V HVDCP */
|
|
|
|
pr_smb(PR_MISC, "Switch to 5V HVDCP\n");
|
|
|
|
rc = smbchg_sec_masked_write(chip, chip->usb_chgpth_base + CHGPTH_CFG,
|
|
|
|
HVDCP_ADAPTER_SEL_MASK, HVDCP_5V);
|
|
|
|
if (rc < 0) {
|
|
|
|
pr_err("Couldn't configure HVDCP 5V rc=%d\n", rc);
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* wait for HVDCP to lower to 5V */
|
|
|
|
msleep(500);
|
|
|
|
/*
|
|
|
|
* Check if the same hvdcp session is in progress. src_det should be
|
|
|
|
* high and that we are still in 5V hvdcp
|
|
|
|
*/
|
|
|
|
if (!is_src_detect_high(chip)) {
|
|
|
|
pr_smb(PR_MISC, "src det low after 500mS sleep\n");
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
pr_smb(PR_MISC, "Disable AICL\n");
|
|
|
|
smbchg_sec_masked_write(chip, chip->usb_chgpth_base + USB_AICL_CFG,
|
|
|
|
AICL_EN_BIT, 0);
|
|
|
|
|
|
|
|
chip->hvdcp_3_det_ignore_uv = true;
|
|
|
|
|
|
|
|
/* re-run APSD */
|
|
|
|
rc = rerun_apsd(chip);
|
|
|
|
if (rc) {
|
|
|
|
pr_err("APSD rerun failed\n");
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
chip->hvdcp_3_det_ignore_uv = false;
|
|
|
|
|
|
|
|
pr_smb(PR_MISC, "Enable AICL\n");
|
|
|
|
smbchg_sec_masked_write(chip, chip->usb_chgpth_base + USB_AICL_CFG,
|
|
|
|
AICL_EN_BIT, AICL_EN_BIT);
|
|
|
|
/*
|
|
|
|
* DCP will switch to HVDCP in this time by removing the short
|
|
|
|
* between DP DM
|
|
|
|
*/
|
|
|
|
msleep(HVDCP_NOTIFY_MS);
|
|
|
|
/*
|
|
|
|
* Check if the same hvdcp session is in progress. src_det should be
|
|
|
|
* high and the usb type should be none since APSD was disabled
|
|
|
|
*/
|
|
|
|
if (!is_src_detect_high(chip)) {
|
|
|
|
pr_smb(PR_MISC, "src det low after 2s sleep\n");
|
|
|
|
rc = -EINVAL;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* We are set if HVDCP in 5V continuous mode */
|
|
|
|
if (!is_hvdcp_5v_cont_mode(chip)) {
|
|
|
|
pr_err("HVDCP could not be set in 5V continuous mode\n");
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
out:
|
|
|
|
chip->hvdcp_3_det_ignore_uv = false;
|
|
|
|
restore_from_hvdcp_detection(chip);
|
2016-08-10 09:58:50 +00:00
|
|
|
if (!is_src_detect_high(chip)) {
|
|
|
|
pr_smb(PR_MISC, "HVDCP removed - force removal\n");
|
|
|
|
update_usb_status(chip, 0, true);
|
|
|
|
}
|
2015-07-27 05:46:49 +00:00
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int smbchg_unprepare_for_pulsing_lite(struct smbchg_chip *chip)
|
|
|
|
{
|
|
|
|
int rc = 0;
|
|
|
|
|
2015-08-12 12:28:44 +00:00
|
|
|
pr_smb(PR_MISC, "Forcing 9V HVDCP 2.0\n");
|
|
|
|
rc = force_9v_hvdcp(chip);
|
|
|
|
if (rc) {
|
|
|
|
pr_err("Failed to force 9V HVDCP=%d\n", rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2015-11-26 09:07:24 +00:00
|
|
|
pr_smb(PR_MISC, "Retracting HVDCP vote for ICL\n");
|
|
|
|
rc = vote(chip->usb_icl_votable, HVDCP_ICL_VOTER, false, 0);
|
2015-08-12 12:28:44 +00:00
|
|
|
if (rc < 0)
|
2015-11-26 09:07:24 +00:00
|
|
|
pr_err("Couldn't retract HVDCP ICL vote rc=%d\n", rc);
|
2015-08-12 12:28:44 +00:00
|
|
|
|
2016-08-10 09:58:50 +00:00
|
|
|
if (!is_src_detect_high(chip)) {
|
|
|
|
pr_smb(PR_MISC, "HVDCP removed\n");
|
|
|
|
update_usb_status(chip, 0, 0);
|
|
|
|
}
|
2016-03-21 22:55:19 +00:00
|
|
|
/* This could be because allow_hvdcp3 set to false runtime */
|
|
|
|
if (is_usb_present(chip) && !chip->allow_hvdcp3_detection)
|
|
|
|
smbchg_handle_hvdcp3_disable(chip);
|
|
|
|
|
2015-07-27 05:46:49 +00:00
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2015-08-12 12:28:44 +00:00
|
|
|
#define CMD_HVDCP_2 0x43
|
|
|
|
#define SINGLE_INCREMENT BIT(0)
|
|
|
|
#define SINGLE_DECREMENT BIT(1)
|
2015-07-27 05:46:49 +00:00
|
|
|
static int smbchg_dp_pulse_lite(struct smbchg_chip *chip)
|
|
|
|
{
|
|
|
|
int rc = 0;
|
|
|
|
|
2015-08-12 12:28:44 +00:00
|
|
|
pr_smb(PR_MISC, "Increment DP\n");
|
|
|
|
rc = smbchg_masked_write(chip, chip->usb_chgpth_base + CMD_HVDCP_2,
|
|
|
|
SINGLE_INCREMENT, SINGLE_INCREMENT);
|
|
|
|
if (rc)
|
|
|
|
pr_err("Single-increment failed rc=%d\n", rc);
|
|
|
|
|
2015-07-27 05:46:49 +00:00
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int smbchg_dm_pulse_lite(struct smbchg_chip *chip)
|
|
|
|
{
|
|
|
|
int rc = 0;
|
|
|
|
|
2015-08-12 12:28:44 +00:00
|
|
|
pr_smb(PR_MISC, "Decrement DM\n");
|
|
|
|
rc = smbchg_masked_write(chip, chip->usb_chgpth_base + CMD_HVDCP_2,
|
|
|
|
SINGLE_DECREMENT, SINGLE_DECREMENT);
|
|
|
|
if (rc)
|
|
|
|
pr_err("Single-decrement failed rc=%d\n", rc);
|
|
|
|
|
2015-07-27 05:46:49 +00:00
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int smbchg_hvdcp3_confirmed(struct smbchg_chip *chip)
|
|
|
|
{
|
2015-11-26 09:07:24 +00:00
|
|
|
int rc = 0;
|
2015-07-27 05:46:49 +00:00
|
|
|
|
2015-10-07 01:05:56 +00:00
|
|
|
/*
|
|
|
|
* reset the enabled once flag for parallel charging because this is
|
|
|
|
* effectively a new insertion.
|
|
|
|
*/
|
|
|
|
chip->parallel.enabled_once = false;
|
|
|
|
|
2015-11-26 09:07:24 +00:00
|
|
|
pr_smb(PR_MISC, "Retracting HVDCP vote for ICL\n");
|
|
|
|
rc = vote(chip->usb_icl_votable, HVDCP_ICL_VOTER, false, 0);
|
2015-07-27 05:46:49 +00:00
|
|
|
if (rc < 0)
|
2015-11-26 09:07:24 +00:00
|
|
|
pr_err("Couldn't retract HVDCP ICL vote rc=%d\n", rc);
|
2015-07-27 05:46:49 +00:00
|
|
|
|
2015-10-19 23:01:13 +00:00
|
|
|
smbchg_change_usb_supply_type(chip, POWER_SUPPLY_TYPE_USB_HVDCP_3);
|
|
|
|
|
2015-11-26 09:07:24 +00:00
|
|
|
return rc;
|
2015-07-27 05:46:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static int smbchg_dp_dm(struct smbchg_chip *chip, int val)
|
|
|
|
{
|
|
|
|
int rc = 0;
|
2015-10-29 23:45:51 +00:00
|
|
|
int target_icl_vote_ma;
|
2015-07-27 05:46:49 +00:00
|
|
|
|
|
|
|
switch (val) {
|
|
|
|
case POWER_SUPPLY_DP_DM_PREPARE:
|
|
|
|
if (!is_hvdcp_present(chip)) {
|
|
|
|
pr_err("No pulsing unless HVDCP\n");
|
|
|
|
return -ENODEV;
|
|
|
|
}
|
|
|
|
if (chip->schg_version == QPNP_SCHG_LITE)
|
|
|
|
rc = smbchg_prepare_for_pulsing_lite(chip);
|
|
|
|
else
|
|
|
|
rc = smbchg_prepare_for_pulsing(chip);
|
|
|
|
break;
|
|
|
|
case POWER_SUPPLY_DP_DM_UNPREPARE:
|
|
|
|
if (chip->schg_version == QPNP_SCHG_LITE)
|
|
|
|
rc = smbchg_unprepare_for_pulsing_lite(chip);
|
|
|
|
else
|
|
|
|
rc = smbchg_unprepare_for_pulsing(chip);
|
|
|
|
break;
|
|
|
|
case POWER_SUPPLY_DP_DM_CONFIRMED_HVDCP3:
|
|
|
|
rc = smbchg_hvdcp3_confirmed(chip);
|
|
|
|
break;
|
|
|
|
case POWER_SUPPLY_DP_DM_DP_PULSE:
|
|
|
|
if (chip->schg_version == QPNP_SCHG)
|
|
|
|
rc = set_usb_psy_dp_dm(chip,
|
|
|
|
POWER_SUPPLY_DP_DM_DP_PULSE);
|
|
|
|
else
|
|
|
|
rc = smbchg_dp_pulse_lite(chip);
|
|
|
|
if (!rc)
|
|
|
|
chip->pulse_cnt++;
|
|
|
|
pr_smb(PR_MISC, "pulse_cnt = %d\n", chip->pulse_cnt);
|
|
|
|
break;
|
|
|
|
case POWER_SUPPLY_DP_DM_DM_PULSE:
|
|
|
|
if (chip->schg_version == QPNP_SCHG)
|
|
|
|
rc = set_usb_psy_dp_dm(chip,
|
|
|
|
POWER_SUPPLY_DP_DM_DM_PULSE);
|
|
|
|
else
|
|
|
|
rc = smbchg_dm_pulse_lite(chip);
|
|
|
|
if (!rc && chip->pulse_cnt)
|
|
|
|
chip->pulse_cnt--;
|
|
|
|
pr_smb(PR_MISC, "pulse_cnt = %d\n", chip->pulse_cnt);
|
|
|
|
break;
|
2015-08-18 03:14:26 +00:00
|
|
|
case POWER_SUPPLY_DP_DM_HVDCP3_SUPPORTED:
|
|
|
|
chip->hvdcp3_supported = true;
|
|
|
|
pr_smb(PR_MISC, "HVDCP3 supported\n");
|
|
|
|
break;
|
2015-10-29 23:45:51 +00:00
|
|
|
case POWER_SUPPLY_DP_DM_ICL_DOWN:
|
|
|
|
chip->usb_icl_delta -= 100;
|
|
|
|
target_icl_vote_ma = get_client_vote(chip->usb_icl_votable,
|
|
|
|
PSY_ICL_VOTER);
|
|
|
|
vote(chip->usb_icl_votable, SW_AICL_ICL_VOTER, true,
|
|
|
|
target_icl_vote_ma + chip->usb_icl_delta);
|
|
|
|
break;
|
|
|
|
case POWER_SUPPLY_DP_DM_ICL_UP:
|
|
|
|
chip->usb_icl_delta += 100;
|
|
|
|
target_icl_vote_ma = get_client_vote(chip->usb_icl_votable,
|
|
|
|
PSY_ICL_VOTER);
|
|
|
|
vote(chip->usb_icl_votable, SW_AICL_ICL_VOTER, true,
|
|
|
|
target_icl_vote_ma + chip->usb_icl_delta);
|
|
|
|
break;
|
2015-07-27 05:46:49 +00:00
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2015-11-04 06:45:11 +00:00
|
|
|
#define CHARGE_OUTPUT_VTG_RATIO 840
|
|
|
|
static int smbchg_get_iusb(struct smbchg_chip *chip)
|
|
|
|
{
|
|
|
|
int rc, iusb_ua = -EINVAL;
|
|
|
|
struct qpnp_vadc_result adc_result;
|
|
|
|
|
|
|
|
if (!is_usb_present(chip) && !is_dc_present(chip))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if (chip->vchg_vadc_dev && chip->vchg_adc_channel != -EINVAL) {
|
|
|
|
rc = qpnp_vadc_read(chip->vchg_vadc_dev,
|
|
|
|
chip->vchg_adc_channel, &adc_result);
|
|
|
|
if (rc) {
|
|
|
|
pr_smb(PR_STATUS,
|
|
|
|
"error in VCHG (channel-%d) read rc = %d\n",
|
|
|
|
chip->vchg_adc_channel, rc);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
iusb_ua = div_s64(adc_result.physical * 1000,
|
|
|
|
CHARGE_OUTPUT_VTG_RATIO);
|
|
|
|
}
|
|
|
|
|
|
|
|
return iusb_ua;
|
|
|
|
}
|
|
|
|
|
2015-06-10 02:32:58 +00:00
|
|
|
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,
|
2015-11-04 06:45:11 +00:00
|
|
|
POWER_SUPPLY_PROP_INPUT_CURRENT_NOW,
|
2015-06-10 02:32:58 +00:00
|
|
|
POWER_SUPPLY_PROP_FLASH_ACTIVE,
|
2015-11-26 07:46:51 +00:00
|
|
|
POWER_SUPPLY_PROP_FLASH_TRIGGER,
|
2015-07-27 05:46:49 +00:00
|
|
|
POWER_SUPPLY_PROP_DP_DM,
|
|
|
|
POWER_SUPPLY_PROP_INPUT_CURRENT_LIMITED,
|
|
|
|
POWER_SUPPLY_PROP_RERUN_AICL,
|
2015-09-14 22:40:02 +00:00
|
|
|
POWER_SUPPLY_PROP_RESTRICTED_CHARGING,
|
2016-03-21 22:55:19 +00:00
|
|
|
POWER_SUPPLY_PROP_ALLOW_HVDCP3,
|
2015-06-10 02:32:58 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
static int smbchg_battery_set_property(struct power_supply *psy,
|
|
|
|
enum power_supply_property prop,
|
|
|
|
const union power_supply_propval *val)
|
|
|
|
{
|
|
|
|
int rc = 0;
|
|
|
|
struct smbchg_chip *chip = container_of(psy,
|
|
|
|
struct smbchg_chip, batt_psy);
|
|
|
|
|
|
|
|
switch (prop) {
|
|
|
|
case POWER_SUPPLY_PROP_BATTERY_CHARGING_ENABLED:
|
2015-11-26 09:38:11 +00:00
|
|
|
vote(chip->battchg_suspend_votable, BATTCHG_USER_EN_VOTER,
|
|
|
|
!val->intval, 0);
|
2015-06-10 02:32:58 +00:00
|
|
|
break;
|
|
|
|
case POWER_SUPPLY_PROP_CHARGING_ENABLED:
|
2015-11-26 09:38:11 +00:00
|
|
|
rc = vote(chip->usb_suspend_votable, USER_EN_VOTER,
|
|
|
|
!val->intval, 0);
|
|
|
|
rc = vote(chip->dc_suspend_votable, USER_EN_VOTER,
|
|
|
|
!val->intval, 0);
|
2015-06-10 02:32:58 +00:00
|
|
|
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:
|
2015-06-26 21:10:50 +00:00
|
|
|
rc = smbchg_otg_pulse_skip_disable(chip,
|
|
|
|
REASON_FLASH_ENABLED, val->intval);
|
2015-06-10 02:32:58 +00:00
|
|
|
break;
|
2015-11-26 07:46:51 +00:00
|
|
|
case POWER_SUPPLY_PROP_FLASH_TRIGGER:
|
|
|
|
chip->flash_triggered = !!val->intval;
|
|
|
|
smbchg_icl_loop_disable_check(chip);
|
|
|
|
break;
|
2015-06-10 02:32:58 +00:00
|
|
|
case POWER_SUPPLY_PROP_FORCE_TLIM:
|
|
|
|
rc = smbchg_force_tlim_en(chip, val->intval);
|
|
|
|
break;
|
2015-07-27 05:46:49 +00:00
|
|
|
case POWER_SUPPLY_PROP_DP_DM:
|
|
|
|
rc = smbchg_dp_dm(chip, val->intval);
|
|
|
|
break;
|
|
|
|
case POWER_SUPPLY_PROP_RERUN_AICL:
|
|
|
|
smbchg_rerun_aicl(chip);
|
|
|
|
break;
|
2015-09-14 22:40:02 +00:00
|
|
|
case POWER_SUPPLY_PROP_RESTRICTED_CHARGING:
|
|
|
|
rc = smbchg_restricted_charging(chip, val->intval);
|
|
|
|
break;
|
2016-03-21 22:55:19 +00:00
|
|
|
case POWER_SUPPLY_PROP_ALLOW_HVDCP3:
|
|
|
|
if (chip->allow_hvdcp3_detection != val->intval) {
|
|
|
|
chip->allow_hvdcp3_detection = !!val->intval;
|
|
|
|
power_supply_changed(&chip->batt_psy);
|
|
|
|
}
|
|
|
|
break;
|
2015-06-10 02:32:58 +00:00
|
|
|
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:
|
2015-07-27 05:46:49 +00:00
|
|
|
case POWER_SUPPLY_PROP_DP_DM:
|
|
|
|
case POWER_SUPPLY_PROP_RERUN_AICL:
|
2015-09-14 22:40:02 +00:00
|
|
|
case POWER_SUPPLY_PROP_RESTRICTED_CHARGING:
|
2016-03-21 22:55:19 +00:00
|
|
|
case POWER_SUPPLY_PROP_ALLOW_HVDCP3:
|
2015-06-10 02:32:58 +00:00
|
|
|
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);
|
|
|
|
break;
|
|
|
|
case POWER_SUPPLY_PROP_BATTERY_CHARGING_ENABLED:
|
2015-11-26 09:38:11 +00:00
|
|
|
val->intval =
|
|
|
|
!get_effective_result(chip->battchg_suspend_votable);
|
2015-06-10 02:32:58 +00:00
|
|
|
break;
|
|
|
|
case POWER_SUPPLY_PROP_CHARGING_ENABLED:
|
|
|
|
val->intval = chip->chg_enabled;
|
|
|
|
break;
|
|
|
|
case POWER_SUPPLY_PROP_CHARGE_TYPE:
|
|
|
|
val->intval = get_prop_charge_type(chip);
|
|
|
|
break;
|
|
|
|
case POWER_SUPPLY_PROP_VOLTAGE_MAX:
|
|
|
|
val->intval = smbchg_float_voltage_get(chip);
|
|
|
|
break;
|
|
|
|
case POWER_SUPPLY_PROP_HEALTH:
|
|
|
|
val->intval = get_prop_batt_health(chip);
|
|
|
|
break;
|
|
|
|
case POWER_SUPPLY_PROP_TECHNOLOGY:
|
|
|
|
val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
|
|
|
|
break;
|
|
|
|
case POWER_SUPPLY_PROP_FLASH_CURRENT_MAX:
|
|
|
|
val->intval = smbchg_calc_max_flash_current(chip);
|
|
|
|
break;
|
|
|
|
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
|
|
|
|
val->intval = chip->fastchg_current_ma * 1000;
|
|
|
|
break;
|
|
|
|
case POWER_SUPPLY_PROP_SYSTEM_TEMP_LEVEL:
|
|
|
|
val->intval = chip->therm_lvl_sel;
|
|
|
|
break;
|
|
|
|
case POWER_SUPPLY_PROP_INPUT_CURRENT_MAX:
|
|
|
|
val->intval = smbchg_get_aicl_level_ma(chip) * 1000;
|
|
|
|
break;
|
|
|
|
case POWER_SUPPLY_PROP_INPUT_CURRENT_SETTLED:
|
|
|
|
val->intval = (int)chip->aicl_complete;
|
|
|
|
break;
|
2015-09-14 22:40:02 +00:00
|
|
|
case POWER_SUPPLY_PROP_RESTRICTED_CHARGING:
|
|
|
|
val->intval = (int)chip->restricted_charging;
|
|
|
|
break;
|
2015-06-10 02:32:58 +00:00
|
|
|
/* properties from fg */
|
|
|
|
case POWER_SUPPLY_PROP_CAPACITY:
|
|
|
|
val->intval = get_prop_batt_capacity(chip);
|
|
|
|
break;
|
|
|
|
case POWER_SUPPLY_PROP_CURRENT_NOW:
|
|
|
|
val->intval = get_prop_batt_current_now(chip);
|
|
|
|
break;
|
|
|
|
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
|
|
|
|
val->intval = get_prop_batt_voltage_now(chip);
|
|
|
|
break;
|
|
|
|
case POWER_SUPPLY_PROP_TEMP:
|
|
|
|
val->intval = get_prop_batt_temp(chip);
|
|
|
|
break;
|
|
|
|
case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
|
|
|
|
val->intval = get_prop_batt_voltage_max_design(chip);
|
|
|
|
break;
|
|
|
|
case POWER_SUPPLY_PROP_SAFETY_TIMER_ENABLE:
|
|
|
|
val->intval = chip->safety_timer_en;
|
|
|
|
break;
|
|
|
|
case POWER_SUPPLY_PROP_FLASH_ACTIVE:
|
2015-06-26 21:10:50 +00:00
|
|
|
val->intval = chip->otg_pulse_skip_dis;
|
2015-06-10 02:32:58 +00:00
|
|
|
break;
|
2015-11-26 07:46:51 +00:00
|
|
|
case POWER_SUPPLY_PROP_FLASH_TRIGGER:
|
|
|
|
val->intval = chip->flash_triggered;
|
|
|
|
break;
|
2015-07-27 05:46:49 +00:00
|
|
|
case POWER_SUPPLY_PROP_DP_DM:
|
|
|
|
val->intval = chip->pulse_cnt;
|
|
|
|
break;
|
|
|
|
case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMITED:
|
|
|
|
val->intval = smbchg_is_input_current_limited(chip);
|
|
|
|
break;
|
|
|
|
case POWER_SUPPLY_PROP_RERUN_AICL:
|
|
|
|
val->intval = 0;
|
|
|
|
break;
|
2015-11-04 06:45:11 +00:00
|
|
|
case POWER_SUPPLY_PROP_INPUT_CURRENT_NOW:
|
|
|
|
val->intval = smbchg_get_iusb(chip);
|
|
|
|
break;
|
2016-03-21 22:55:19 +00:00
|
|
|
case POWER_SUPPLY_PROP_ALLOW_HVDCP3:
|
|
|
|
val->intval = chip->allow_hvdcp3_detection;
|
|
|
|
break;
|
2015-06-10 02:32:58 +00:00
|
|
|
default:
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static char *smbchg_dc_supplicants[] = {
|
|
|
|
"bms",
|
|
|
|
};
|
|
|
|
|
|
|
|
static enum power_supply_property smbchg_dc_properties[] = {
|
|
|
|
POWER_SUPPLY_PROP_PRESENT,
|
|
|
|
POWER_SUPPLY_PROP_ONLINE,
|
|
|
|
POWER_SUPPLY_PROP_CHARGING_ENABLED,
|
|
|
|
POWER_SUPPLY_PROP_CURRENT_MAX,
|
|
|
|
};
|
|
|
|
|
|
|
|
static int smbchg_dc_set_property(struct power_supply *psy,
|
|
|
|
enum power_supply_property prop,
|
|
|
|
const union power_supply_propval *val)
|
|
|
|
{
|
|
|
|
int rc = 0;
|
|
|
|
struct smbchg_chip *chip = container_of(psy,
|
|
|
|
struct smbchg_chip, dc_psy);
|
|
|
|
|
|
|
|
switch (prop) {
|
|
|
|
case POWER_SUPPLY_PROP_CHARGING_ENABLED:
|
2015-11-26 09:38:11 +00:00
|
|
|
rc = vote(chip->dc_suspend_votable, POWER_SUPPLY_EN_VOTER,
|
|
|
|
!val->intval, 0);
|
2015-06-10 02:32:58 +00:00
|
|
|
break;
|
|
|
|
case POWER_SUPPLY_PROP_CURRENT_MAX:
|
2015-11-26 09:07:24 +00:00
|
|
|
rc = vote(chip->dc_icl_votable, USER_ICL_VOTER, true,
|
|
|
|
val->intval / 1000);
|
2015-06-10 02:32:58 +00:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int smbchg_dc_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, dc_psy);
|
|
|
|
|
|
|
|
switch (prop) {
|
|
|
|
case POWER_SUPPLY_PROP_PRESENT:
|
|
|
|
val->intval = is_dc_present(chip);
|
|
|
|
break;
|
|
|
|
case POWER_SUPPLY_PROP_CHARGING_ENABLED:
|
2015-11-26 09:38:11 +00:00
|
|
|
val->intval = !get_effective_result(chip->dc_suspend_votable);
|
2015-06-10 02:32:58 +00:00
|
|
|
break;
|
|
|
|
case POWER_SUPPLY_PROP_ONLINE:
|
|
|
|
/* return if dc is charging the battery */
|
|
|
|
val->intval = (smbchg_get_pwr_path(chip) == PWR_PATH_DC)
|
|
|
|
&& (get_prop_batt_status(chip)
|
|
|
|
== POWER_SUPPLY_STATUS_CHARGING);
|
|
|
|
break;
|
|
|
|
case POWER_SUPPLY_PROP_CURRENT_MAX:
|
|
|
|
val->intval = chip->dc_max_current_ma * 1000;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int smbchg_dc_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_CURRENT_MAX:
|
|
|
|
rc = 1;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
rc = 0;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return rc;
|
|
|
|
}
|
2015-06-23 03:59:35 +00:00
|
|
|
|
2014-02-20 01:27:12 +00:00
|
|
|
#define HOT_BAT_HARD_BIT BIT(0)
|
|
|
|
#define HOT_BAT_SOFT_BIT BIT(1)
|
|
|
|
#define COLD_BAT_HARD_BIT BIT(2)
|
|
|
|
#define COLD_BAT_SOFT_BIT BIT(3)
|
2015-10-12 23:43:12 +00:00
|
|
|
#define BAT_OV_BIT BIT(4)
|
2014-02-20 01:27:12 +00:00
|
|
|
#define BAT_LOW_BIT BIT(5)
|
|
|
|
#define BAT_MISSING_BIT BIT(6)
|
|
|
|
#define BAT_TERM_MISSING_BIT BIT(7)
|
|
|
|
static irqreturn_t batt_hot_handler(int irq, void *_chip)
|
|
|
|
{
|
|
|
|
struct smbchg_chip *chip = _chip;
|
|
|
|
u8 reg = 0;
|
|
|
|
|
|
|
|
smbchg_read(chip, ®, chip->bat_if_base + RT_STS, 1);
|
|
|
|
chip->batt_hot = !!(reg & HOT_BAT_HARD_BIT);
|
2014-06-30 22:02:34 +00:00
|
|
|
pr_smb(PR_INTERRUPT, "triggered: 0x%02x\n", reg);
|
2014-08-06 18:27:04 +00:00
|
|
|
smbchg_parallel_usb_check_ok(chip);
|
2014-02-20 01:27:12 +00:00
|
|
|
if (chip->psy_registered)
|
|
|
|
power_supply_changed(&chip->batt_psy);
|
2015-02-12 19:56:11 +00:00
|
|
|
smbchg_charging_status_change(chip);
|
2014-10-30 21:59:48 +00:00
|
|
|
smbchg_wipower_check(chip);
|
2015-03-05 23:24:25 +00:00
|
|
|
set_property_on_fg(chip, POWER_SUPPLY_PROP_HEALTH,
|
|
|
|
get_prop_batt_health(chip));
|
2014-02-20 01:27:12 +00:00
|
|
|
return IRQ_HANDLED;
|
|
|
|
}
|
|
|
|
|
|
|
|
static irqreturn_t batt_cold_handler(int irq, void *_chip)
|
|
|
|
{
|
|
|
|
struct smbchg_chip *chip = _chip;
|
|
|
|
u8 reg = 0;
|
|
|
|
|
|
|
|
smbchg_read(chip, ®, chip->bat_if_base + RT_STS, 1);
|
|
|
|
chip->batt_cold = !!(reg & COLD_BAT_HARD_BIT);
|
2014-06-30 22:02:34 +00:00
|
|
|
pr_smb(PR_INTERRUPT, "triggered: 0x%02x\n", reg);
|
2014-08-06 18:27:04 +00:00
|
|
|
smbchg_parallel_usb_check_ok(chip);
|
2014-02-20 01:27:12 +00:00
|
|
|
if (chip->psy_registered)
|
|
|
|
power_supply_changed(&chip->batt_psy);
|
2015-02-12 19:56:11 +00:00
|
|
|
smbchg_charging_status_change(chip);
|
2014-10-30 21:59:48 +00:00
|
|
|
smbchg_wipower_check(chip);
|
2015-07-15 05:12:32 +00:00
|
|
|
set_property_on_fg(chip, POWER_SUPPLY_PROP_HEALTH,
|
|
|
|
get_prop_batt_health(chip));
|
2014-02-20 01:27:12 +00:00
|
|
|
return IRQ_HANDLED;
|
|
|
|
}
|
|
|
|
|
|
|
|
static irqreturn_t batt_warm_handler(int irq, void *_chip)
|
|
|
|
{
|
|
|
|
struct smbchg_chip *chip = _chip;
|
|
|
|
u8 reg = 0;
|
|
|
|
|
|
|
|
smbchg_read(chip, ®, chip->bat_if_base + RT_STS, 1);
|
|
|
|
chip->batt_warm = !!(reg & HOT_BAT_SOFT_BIT);
|
2014-06-30 22:02:34 +00:00
|
|
|
pr_smb(PR_INTERRUPT, "triggered: 0x%02x\n", reg);
|
2014-08-06 18:27:04 +00:00
|
|
|
smbchg_parallel_usb_check_ok(chip);
|
2014-02-20 01:27:12 +00:00
|
|
|
if (chip->psy_registered)
|
|
|
|
power_supply_changed(&chip->batt_psy);
|
2015-03-05 23:24:25 +00:00
|
|
|
set_property_on_fg(chip, POWER_SUPPLY_PROP_HEALTH,
|
|
|
|
get_prop_batt_health(chip));
|
2014-02-20 01:27:12 +00:00
|
|
|
return IRQ_HANDLED;
|
|
|
|
}
|
|
|
|
|
|
|
|
static irqreturn_t batt_cool_handler(int irq, void *_chip)
|
|
|
|
{
|
|
|
|
struct smbchg_chip *chip = _chip;
|
|
|
|
u8 reg = 0;
|
|
|
|
|
|
|
|
smbchg_read(chip, ®, chip->bat_if_base + RT_STS, 1);
|
|
|
|
chip->batt_cool = !!(reg & COLD_BAT_SOFT_BIT);
|
2014-06-30 22:02:34 +00:00
|
|
|
pr_smb(PR_INTERRUPT, "triggered: 0x%02x\n", reg);
|
2014-08-06 18:27:04 +00:00
|
|
|
smbchg_parallel_usb_check_ok(chip);
|
2014-02-20 01:27:12 +00:00
|
|
|
if (chip->psy_registered)
|
|
|
|
power_supply_changed(&chip->batt_psy);
|
2015-03-05 23:24:25 +00:00
|
|
|
set_property_on_fg(chip, POWER_SUPPLY_PROP_HEALTH,
|
|
|
|
get_prop_batt_health(chip));
|
2014-02-20 01:27:12 +00:00
|
|
|
return IRQ_HANDLED;
|
|
|
|
}
|
|
|
|
|
|
|
|
static irqreturn_t batt_pres_handler(int irq, void *_chip)
|
|
|
|
{
|
|
|
|
struct smbchg_chip *chip = _chip;
|
|
|
|
u8 reg = 0;
|
|
|
|
|
|
|
|
smbchg_read(chip, ®, chip->bat_if_base + RT_STS, 1);
|
2014-05-22 17:09:08 +00:00
|
|
|
chip->batt_present = !(reg & BAT_MISSING_BIT);
|
2014-06-30 22:02:34 +00:00
|
|
|
pr_smb(PR_INTERRUPT, "triggered: 0x%02x\n", reg);
|
2014-02-20 01:27:12 +00:00
|
|
|
if (chip->psy_registered)
|
|
|
|
power_supply_changed(&chip->batt_psy);
|
2015-02-12 19:56:11 +00:00
|
|
|
smbchg_charging_status_change(chip);
|
2015-03-05 23:24:25 +00:00
|
|
|
set_property_on_fg(chip, POWER_SUPPLY_PROP_HEALTH,
|
|
|
|
get_prop_batt_health(chip));
|
2014-02-20 01:27:12 +00:00
|
|
|
return IRQ_HANDLED;
|
|
|
|
}
|
|
|
|
|
|
|
|
static irqreturn_t vbat_low_handler(int irq, void *_chip)
|
|
|
|
{
|
|
|
|
pr_warn_ratelimited("vbat low\n");
|
|
|
|
return IRQ_HANDLED;
|
|
|
|
}
|
|
|
|
|
2015-07-31 09:29:44 +00:00
|
|
|
#define CHG_COMP_SFT_BIT BIT(3)
|
power: qpnp-smbcharger: add workaround for UVLO resets
When using the PMI8994 smbcharger, usb removal or suspend can cause
an under voltage lock out (UVLO) due to a hardware bug.
The condition for the UVLO to happen is when the whole system is running
off the charger buck when the USB is either removed or suspended. This
case can easily happen when the system is running at medium loads
(300-400 mA) and the battery is considered missing, overtemp, or
overvoltage by the charger hardware.
When this happens, the charger stops trying to pump current into the
battery, and instead just draws power from the active charge path
(USB in ths case). However, when the input charge path is removed, either
by suspending it through software or by physically removing the USB
cable, the battery FET does not turn on fast enough in hardware to
supplement the system load. This causes a droop on VPH power, manifesting
itself as an UVLO reset.
The work around for this issue is to ensure the battery is supplementing
the current before a charge path removal event happens. Implement this by
forcing USB input current limit to be 100mA whenever the device is not
charging. More specifically, whenever a charger p2f-thresh interrupt
(pre-to-fastcharge event, charging starts) or a battery interface
chg-error interrupt (battery missing/overtemp/overvolt) happens, check
the charging status and if the device is not charging, force the charger
to only draw 100mA.
If the input current limit is at 100mA, the system will be drawing
current from the battery as long as the system load is higher than
100mA. Thus, when the USB is removed, the battery will already be
supplementing the system load, and there will not be an UVLO reset.
There are a few limitations to this work around.
1. If the system load is less than 100mA when the input charge
path is removed, there may still be a droop on VPH power due
to the battery FET still being open. However, this should not
cause an UVLO reset because the system load will not cause a
large enough droop to cause a UVLO reset.
2. If the input is removed at the exact instant a charger error
occurs (battery goes missing, battery becomes overtemp,
battery becomes overvoltage), and the interrupt does not get
serviced before the input is removed, an UVLO reset can still
happen. However, this is a hardware limitation and there is
no solution for this at this time.
3. Currently, the workaround for the DC input charge path is not
supported. Until this is implemented, DC charging and wipower
will be impacted.
CRs-Fixed: 667489
Change-Id: I1a185d126e1e6bb851e5981f8b49072e8f8c6f68
Signed-off-by: Xiaozhe Shi <xiaozhes@codeaurora.org>
2014-06-12 00:35:01 +00:00
|
|
|
static irqreturn_t chg_error_handler(int irq, void *_chip)
|
|
|
|
{
|
|
|
|
struct smbchg_chip *chip = _chip;
|
2015-07-31 09:29:44 +00:00
|
|
|
int rc = 0;
|
|
|
|
u8 reg;
|
power: qpnp-smbcharger: add workaround for UVLO resets
When using the PMI8994 smbcharger, usb removal or suspend can cause
an under voltage lock out (UVLO) due to a hardware bug.
The condition for the UVLO to happen is when the whole system is running
off the charger buck when the USB is either removed or suspended. This
case can easily happen when the system is running at medium loads
(300-400 mA) and the battery is considered missing, overtemp, or
overvoltage by the charger hardware.
When this happens, the charger stops trying to pump current into the
battery, and instead just draws power from the active charge path
(USB in ths case). However, when the input charge path is removed, either
by suspending it through software or by physically removing the USB
cable, the battery FET does not turn on fast enough in hardware to
supplement the system load. This causes a droop on VPH power, manifesting
itself as an UVLO reset.
The work around for this issue is to ensure the battery is supplementing
the current before a charge path removal event happens. Implement this by
forcing USB input current limit to be 100mA whenever the device is not
charging. More specifically, whenever a charger p2f-thresh interrupt
(pre-to-fastcharge event, charging starts) or a battery interface
chg-error interrupt (battery missing/overtemp/overvolt) happens, check
the charging status and if the device is not charging, force the charger
to only draw 100mA.
If the input current limit is at 100mA, the system will be drawing
current from the battery as long as the system load is higher than
100mA. Thus, when the USB is removed, the battery will already be
supplementing the system load, and there will not be an UVLO reset.
There are a few limitations to this work around.
1. If the system load is less than 100mA when the input charge
path is removed, there may still be a droop on VPH power due
to the battery FET still being open. However, this should not
cause an UVLO reset because the system load will not cause a
large enough droop to cause a UVLO reset.
2. If the input is removed at the exact instant a charger error
occurs (battery goes missing, battery becomes overtemp,
battery becomes overvoltage), and the interrupt does not get
serviced before the input is removed, an UVLO reset can still
happen. However, this is a hardware limitation and there is
no solution for this at this time.
3. Currently, the workaround for the DC input charge path is not
supported. Until this is implemented, DC charging and wipower
will be impacted.
CRs-Fixed: 667489
Change-Id: I1a185d126e1e6bb851e5981f8b49072e8f8c6f68
Signed-off-by: Xiaozhe Shi <xiaozhes@codeaurora.org>
2014-06-12 00:35:01 +00:00
|
|
|
|
2014-06-30 22:02:34 +00:00
|
|
|
pr_smb(PR_INTERRUPT, "chg-error triggered\n");
|
2015-07-31 09:29:44 +00:00
|
|
|
|
|
|
|
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);
|
|
|
|
} else {
|
|
|
|
pr_smb(PR_INTERRUPT, "triggered: 0x%02x\n", reg);
|
|
|
|
if (reg & CHG_COMP_SFT_BIT)
|
|
|
|
set_property_on_fg(chip,
|
|
|
|
POWER_SUPPLY_PROP_SAFETY_TIMER_EXPIRED,
|
|
|
|
1);
|
|
|
|
}
|
|
|
|
|
2014-08-06 18:27:04 +00:00
|
|
|
smbchg_parallel_usb_check_ok(chip);
|
power: qpnp-smbcharger: add workaround for UVLO resets
When using the PMI8994 smbcharger, usb removal or suspend can cause
an under voltage lock out (UVLO) due to a hardware bug.
The condition for the UVLO to happen is when the whole system is running
off the charger buck when the USB is either removed or suspended. This
case can easily happen when the system is running at medium loads
(300-400 mA) and the battery is considered missing, overtemp, or
overvoltage by the charger hardware.
When this happens, the charger stops trying to pump current into the
battery, and instead just draws power from the active charge path
(USB in ths case). However, when the input charge path is removed, either
by suspending it through software or by physically removing the USB
cable, the battery FET does not turn on fast enough in hardware to
supplement the system load. This causes a droop on VPH power, manifesting
itself as an UVLO reset.
The work around for this issue is to ensure the battery is supplementing
the current before a charge path removal event happens. Implement this by
forcing USB input current limit to be 100mA whenever the device is not
charging. More specifically, whenever a charger p2f-thresh interrupt
(pre-to-fastcharge event, charging starts) or a battery interface
chg-error interrupt (battery missing/overtemp/overvolt) happens, check
the charging status and if the device is not charging, force the charger
to only draw 100mA.
If the input current limit is at 100mA, the system will be drawing
current from the battery as long as the system load is higher than
100mA. Thus, when the USB is removed, the battery will already be
supplementing the system load, and there will not be an UVLO reset.
There are a few limitations to this work around.
1. If the system load is less than 100mA when the input charge
path is removed, there may still be a droop on VPH power due
to the battery FET still being open. However, this should not
cause an UVLO reset because the system load will not cause a
large enough droop to cause a UVLO reset.
2. If the input is removed at the exact instant a charger error
occurs (battery goes missing, battery becomes overtemp,
battery becomes overvoltage), and the interrupt does not get
serviced before the input is removed, an UVLO reset can still
happen. However, this is a hardware limitation and there is
no solution for this at this time.
3. Currently, the workaround for the DC input charge path is not
supported. Until this is implemented, DC charging and wipower
will be impacted.
CRs-Fixed: 667489
Change-Id: I1a185d126e1e6bb851e5981f8b49072e8f8c6f68
Signed-off-by: Xiaozhe Shi <xiaozhes@codeaurora.org>
2014-06-12 00:35:01 +00:00
|
|
|
if (chip->psy_registered)
|
|
|
|
power_supply_changed(&chip->batt_psy);
|
2015-02-12 19:56:11 +00:00
|
|
|
smbchg_charging_status_change(chip);
|
2014-10-30 21:59:48 +00:00
|
|
|
smbchg_wipower_check(chip);
|
power: qpnp-smbcharger: add workaround for UVLO resets
When using the PMI8994 smbcharger, usb removal or suspend can cause
an under voltage lock out (UVLO) due to a hardware bug.
The condition for the UVLO to happen is when the whole system is running
off the charger buck when the USB is either removed or suspended. This
case can easily happen when the system is running at medium loads
(300-400 mA) and the battery is considered missing, overtemp, or
overvoltage by the charger hardware.
When this happens, the charger stops trying to pump current into the
battery, and instead just draws power from the active charge path
(USB in ths case). However, when the input charge path is removed, either
by suspending it through software or by physically removing the USB
cable, the battery FET does not turn on fast enough in hardware to
supplement the system load. This causes a droop on VPH power, manifesting
itself as an UVLO reset.
The work around for this issue is to ensure the battery is supplementing
the current before a charge path removal event happens. Implement this by
forcing USB input current limit to be 100mA whenever the device is not
charging. More specifically, whenever a charger p2f-thresh interrupt
(pre-to-fastcharge event, charging starts) or a battery interface
chg-error interrupt (battery missing/overtemp/overvolt) happens, check
the charging status and if the device is not charging, force the charger
to only draw 100mA.
If the input current limit is at 100mA, the system will be drawing
current from the battery as long as the system load is higher than
100mA. Thus, when the USB is removed, the battery will already be
supplementing the system load, and there will not be an UVLO reset.
There are a few limitations to this work around.
1. If the system load is less than 100mA when the input charge
path is removed, there may still be a droop on VPH power due
to the battery FET still being open. However, this should not
cause an UVLO reset because the system load will not cause a
large enough droop to cause a UVLO reset.
2. If the input is removed at the exact instant a charger error
occurs (battery goes missing, battery becomes overtemp,
battery becomes overvoltage), and the interrupt does not get
serviced before the input is removed, an UVLO reset can still
happen. However, this is a hardware limitation and there is
no solution for this at this time.
3. Currently, the workaround for the DC input charge path is not
supported. Until this is implemented, DC charging and wipower
will be impacted.
CRs-Fixed: 667489
Change-Id: I1a185d126e1e6bb851e5981f8b49072e8f8c6f68
Signed-off-by: Xiaozhe Shi <xiaozhes@codeaurora.org>
2014-06-12 00:35:01 +00:00
|
|
|
return IRQ_HANDLED;
|
|
|
|
}
|
|
|
|
|
2014-05-28 02:07:48 +00:00
|
|
|
static irqreturn_t fastchg_handler(int irq, void *_chip)
|
|
|
|
{
|
|
|
|
struct smbchg_chip *chip = _chip;
|
|
|
|
|
2014-06-30 22:02:34 +00:00
|
|
|
pr_smb(PR_INTERRUPT, "p2f triggered\n");
|
2016-03-25 17:59:39 +00:00
|
|
|
smbchg_detect_parallel_charger(chip);
|
2014-08-06 18:27:04 +00:00
|
|
|
smbchg_parallel_usb_check_ok(chip);
|
2014-05-28 02:07:48 +00:00
|
|
|
if (chip->psy_registered)
|
|
|
|
power_supply_changed(&chip->batt_psy);
|
2015-02-12 19:56:11 +00:00
|
|
|
smbchg_charging_status_change(chip);
|
2014-10-30 21:59:48 +00:00
|
|
|
smbchg_wipower_check(chip);
|
2014-05-28 02:07:48 +00:00
|
|
|
return IRQ_HANDLED;
|
|
|
|
}
|
|
|
|
|
2014-02-20 01:27:12 +00:00
|
|
|
static irqreturn_t chg_hot_handler(int irq, void *_chip)
|
|
|
|
{
|
|
|
|
pr_warn_ratelimited("chg hot\n");
|
2014-10-30 21:59:48 +00:00
|
|
|
smbchg_wipower_check(_chip);
|
2014-02-20 01:27:12 +00:00
|
|
|
return IRQ_HANDLED;
|
|
|
|
}
|
|
|
|
|
|
|
|
static irqreturn_t chg_term_handler(int irq, void *_chip)
|
|
|
|
{
|
|
|
|
struct smbchg_chip *chip = _chip;
|
2015-08-31 23:47:13 +00:00
|
|
|
int rc;
|
2014-02-20 01:27:12 +00:00
|
|
|
u8 reg = 0;
|
2015-08-31 23:47:13 +00:00
|
|
|
bool terminated = false;
|
2014-02-20 01:27:12 +00:00
|
|
|
|
2015-08-31 23:47:13 +00:00
|
|
|
rc = smbchg_read(chip, ®, chip->chgr_base + RT_STS, 1);
|
|
|
|
if (rc) {
|
|
|
|
dev_err(chip->dev, "Error reading RT_STS rc= %d\n", rc);
|
|
|
|
} else {
|
|
|
|
terminated = !!(reg & BAT_TCC_REACHED_BIT);
|
|
|
|
pr_smb(PR_INTERRUPT, "triggered: 0x%02x\n", reg);
|
|
|
|
}
|
|
|
|
/*
|
|
|
|
* If charging has not actually terminated, then this means that
|
|
|
|
* either this is a manual call to chg_term_handler during
|
|
|
|
* determine_initial_status(), or the charger has instantly restarted
|
|
|
|
* charging.
|
|
|
|
*
|
|
|
|
* In either case, do not do the usual status updates here. If there
|
|
|
|
* is something that needs to be updated, the recharge handler will
|
|
|
|
* handle it.
|
|
|
|
*/
|
|
|
|
if (terminated) {
|
|
|
|
smbchg_parallel_usb_check_ok(chip);
|
|
|
|
if (chip->psy_registered)
|
|
|
|
power_supply_changed(&chip->batt_psy);
|
|
|
|
smbchg_charging_status_change(chip);
|
|
|
|
set_property_on_fg(chip, POWER_SUPPLY_PROP_CHARGE_DONE, 1);
|
|
|
|
}
|
2014-02-20 01:27:12 +00:00
|
|
|
return IRQ_HANDLED;
|
|
|
|
}
|
|
|
|
|
|
|
|
static irqreturn_t taper_handler(int irq, void *_chip)
|
|
|
|
{
|
|
|
|
struct smbchg_chip *chip = _chip;
|
|
|
|
u8 reg = 0;
|
|
|
|
|
2014-08-06 18:27:04 +00:00
|
|
|
taper_irq_en(chip, false);
|
2014-02-20 01:27:12 +00:00
|
|
|
smbchg_read(chip, ®, chip->chgr_base + RT_STS, 1);
|
2014-06-30 22:02:34 +00:00
|
|
|
pr_smb(PR_INTERRUPT, "triggered: 0x%02x\n", reg);
|
2014-08-06 18:27:04 +00:00
|
|
|
smbchg_parallel_usb_taper(chip);
|
2015-02-12 19:56:11 +00:00
|
|
|
if (chip->psy_registered)
|
|
|
|
power_supply_changed(&chip->batt_psy);
|
|
|
|
smbchg_charging_status_change(chip);
|
2014-10-30 21:59:48 +00:00
|
|
|
smbchg_wipower_check(chip);
|
2014-02-20 01:27:12 +00:00
|
|
|
return IRQ_HANDLED;
|
|
|
|
}
|
|
|
|
|
|
|
|
static irqreturn_t recharge_handler(int irq, void *_chip)
|
|
|
|
{
|
|
|
|
struct smbchg_chip *chip = _chip;
|
|
|
|
u8 reg = 0;
|
|
|
|
|
|
|
|
smbchg_read(chip, ®, chip->chgr_base + RT_STS, 1);
|
2014-06-30 22:02:34 +00:00
|
|
|
pr_smb(PR_INTERRUPT, "triggered: 0x%02x\n", reg);
|
2014-08-06 18:27:04 +00:00
|
|
|
smbchg_parallel_usb_check_ok(chip);
|
2014-02-20 01:27:12 +00:00
|
|
|
if (chip->psy_registered)
|
|
|
|
power_supply_changed(&chip->batt_psy);
|
2015-06-23 03:59:35 +00:00
|
|
|
smbchg_charging_status_change(chip);
|
|
|
|
return IRQ_HANDLED;
|
2014-02-20 01:27:12 +00:00
|
|
|
}
|
|
|
|
|
2015-07-31 09:29:44 +00:00
|
|
|
static irqreturn_t wdog_timeout_handler(int irq, void *_chip)
|
2015-03-18 20:43:47 +00:00
|
|
|
{
|
2015-06-23 03:59:35 +00:00
|
|
|
struct smbchg_chip *chip = _chip;
|
|
|
|
u8 reg = 0;
|
2015-03-18 20:43:47 +00:00
|
|
|
|
2015-06-23 03:59:35 +00:00
|
|
|
smbchg_read(chip, ®, chip->misc_base + RT_STS, 1);
|
2015-07-31 09:29:44 +00:00
|
|
|
pr_warn_ratelimited("wdog timeout rt_stat = 0x%02x\n", reg);
|
2015-06-23 03:59:35 +00:00
|
|
|
if (chip->psy_registered)
|
|
|
|
power_supply_changed(&chip->batt_psy);
|
|
|
|
smbchg_charging_status_change(chip);
|
|
|
|
return IRQ_HANDLED;
|
2015-03-18 20:43:47 +00:00
|
|
|
}
|
|
|
|
|
2015-06-23 03:59:35 +00:00
|
|
|
/**
|
|
|
|
* power_ok_handler() - called when the switcher turns on or turns off
|
|
|
|
* @chip: pointer to smbchg_chip
|
|
|
|
* @rt_stat: the status bit indicating switcher turning on or off
|
|
|
|
*/
|
|
|
|
static irqreturn_t power_ok_handler(int irq, void *_chip)
|
2014-02-20 01:27:12 +00:00
|
|
|
{
|
2015-06-23 03:59:35 +00:00
|
|
|
struct smbchg_chip *chip = _chip;
|
|
|
|
u8 reg = 0;
|
|
|
|
|
|
|
|
smbchg_read(chip, ®, chip->misc_base + RT_STS, 1);
|
|
|
|
pr_smb(PR_INTERRUPT, "triggered: 0x%02x\n", reg);
|
|
|
|
return IRQ_HANDLED;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* dcin_uv_handler() - called when the dc voltage crosses the uv threshold
|
|
|
|
* @chip: pointer to smbchg_chip
|
|
|
|
* @rt_stat: the status bit indicating whether dc voltage is uv
|
|
|
|
*/
|
|
|
|
#define DCIN_UNSUSPEND_DELAY_MS 1000
|
|
|
|
static irqreturn_t dcin_uv_handler(int irq, void *_chip)
|
|
|
|
{
|
|
|
|
struct smbchg_chip *chip = _chip;
|
|
|
|
bool dc_present = is_dc_present(chip);
|
2015-03-18 20:43:47 +00:00
|
|
|
int rc;
|
2014-02-20 01:27:12 +00:00
|
|
|
|
2015-06-23 03:59:35 +00:00
|
|
|
pr_smb(PR_STATUS, "chip->dc_present = %d dc_present = %d\n",
|
|
|
|
chip->dc_present, dc_present);
|
2015-03-18 20:43:47 +00:00
|
|
|
|
2015-06-23 03:59:35 +00:00
|
|
|
if (chip->dc_present != dc_present) {
|
|
|
|
/* dc changed */
|
|
|
|
chip->dc_present = dc_present;
|
|
|
|
if (chip->dc_psy_type != -EINVAL && chip->psy_registered)
|
|
|
|
power_supply_changed(&chip->dc_psy);
|
|
|
|
smbchg_charging_status_change(chip);
|
|
|
|
smbchg_aicl_deglitch_wa_check(chip);
|
|
|
|
if (chip->force_aicl_rerun && !dc_present) {
|
|
|
|
rc = smbchg_hw_aicl_rerun_en(chip, true);
|
2015-02-04 03:07:09 +00:00
|
|
|
if (rc)
|
2015-06-23 03:59:35 +00:00
|
|
|
pr_err("Error enabling AICL rerun rc= %d\n",
|
|
|
|
rc);
|
2015-02-04 03:07:09 +00:00
|
|
|
}
|
2015-06-23 03:59:35 +00:00
|
|
|
chip->vbat_above_headroom = false;
|
2014-05-30 21:07:14 +00:00
|
|
|
}
|
2014-02-20 01:27:12 +00:00
|
|
|
|
2015-06-23 03:59:35 +00:00
|
|
|
smbchg_wipower_check(chip);
|
|
|
|
return IRQ_HANDLED;
|
2015-03-19 17:10:26 +00:00
|
|
|
}
|
|
|
|
|
2015-02-04 03:07:09 +00:00
|
|
|
/**
|
|
|
|
* usbin_ov_handler() - this is called when an overvoltage condition occurs
|
|
|
|
* @chip: pointer to smbchg_chip chip
|
|
|
|
*/
|
|
|
|
static irqreturn_t usbin_ov_handler(int irq, void *_chip)
|
|
|
|
{
|
|
|
|
struct smbchg_chip *chip = _chip;
|
|
|
|
int rc;
|
|
|
|
u8 reg;
|
2015-03-19 17:10:26 +00:00
|
|
|
bool usb_present;
|
2015-02-04 03:07:09 +00:00
|
|
|
|
|
|
|
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);
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* OV condition is detected. Notify it to USB psy */
|
|
|
|
if (reg & USBIN_OV_BIT) {
|
|
|
|
chip->usb_ov_det = true;
|
|
|
|
if (chip->usb_psy) {
|
2015-06-11 17:27:14 +00:00
|
|
|
pr_smb(PR_MISC, "setting usb psy health OV\n");
|
2015-02-04 03:07:09 +00:00
|
|
|
rc = power_supply_set_health_state(chip->usb_psy,
|
|
|
|
POWER_SUPPLY_HEALTH_OVERVOLTAGE);
|
|
|
|
if (rc)
|
|
|
|
pr_smb(PR_STATUS,
|
|
|
|
"usb psy does not allow updating prop %d rc = %d\n",
|
|
|
|
POWER_SUPPLY_HEALTH_OVERVOLTAGE, rc);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
chip->usb_ov_det = false;
|
|
|
|
/* If USB is present, then handle the USB insertion */
|
2015-03-19 17:10:26 +00:00
|
|
|
usb_present = is_usb_present(chip);
|
|
|
|
if (usb_present)
|
|
|
|
update_usb_status(chip, usb_present, false);
|
2015-02-04 03:07:09 +00:00
|
|
|
}
|
|
|
|
out:
|
|
|
|
return IRQ_HANDLED;
|
|
|
|
}
|
|
|
|
|
2014-02-20 01:27:12 +00:00
|
|
|
/**
|
|
|
|
* usbin_uv_handler() - this is called when USB charger is removed
|
|
|
|
* @chip: pointer to smbchg_chip chip
|
|
|
|
* @rt_stat: the status bit indicating chg insertion/removal
|
|
|
|
*/
|
power: qpnp-smbcharger: handle very weak chargers
The PMi charger peripheral can detect weak chargers through the automatic
input current limit (AICL) feature by observing how much current it
takes to collapse an external charger. Additionally, under very bad
conditions, such as having a very weak charger with a high impedance
cable, the AICL algorithm will actually stop altogether when the USB
voltage drops below the autoshutdown threshold.
Normally, this will trigger AICL suspend and shut off the charger buck.
However, if hardware AICL reruns are enabled, AICL cannot be suspended
in hardware. This can cause a problem where the USBIN_UV interrupt is
continuously received when the USBIN voltage hovers around the lower
limit of the valid range.
Fix this by detecting very weak chargers if the USBIN_UV is high at the
same time USBIN_SRC_DET is high. If both interrupts lines are high,
it means that the USB voltage is below the low threshold for UV but
higher than the coarse detect threshold (1V).
If a weak charger is detected, disable AICL rerun to allow the charger
to enter into AICL suspend, and set the USB health to UNSPEC_FAILURE,
and do not report USB online.
Also, do not allow USBIN_UV to reset the aicl_irq_count in order to
allow the battery OV interrupt to run correctly.
CRs-Fixed: 823770
Change-Id: If853659fa7aaf899f500d8933a773ab80d0464a1
Signed-off-by: Xiaozhe Shi <xiaozhes@codeaurora.org>
2015-04-15 21:39:43 +00:00
|
|
|
#define ICL_MODE_MASK SMB_MASK(5, 4)
|
|
|
|
#define ICL_MODE_HIGH_CURRENT 0
|
2014-02-20 01:27:12 +00:00
|
|
|
static irqreturn_t usbin_uv_handler(int irq, void *_chip)
|
|
|
|
{
|
2015-04-30 01:52:26 +00:00
|
|
|
struct smbchg_chip *chip = _chip;
|
|
|
|
int aicl_level = smbchg_get_aicl_level_ma(chip);
|
2015-02-21 05:34:44 +00:00
|
|
|
int rc;
|
|
|
|
u8 reg;
|
2014-02-20 01:27:12 +00:00
|
|
|
|
2015-04-30 01:52:26 +00:00
|
|
|
rc = smbchg_read(chip, ®, chip->usb_chgpth_base + RT_STS, 1);
|
|
|
|
if (rc) {
|
|
|
|
pr_err("could not read rt sts: %d", rc);
|
|
|
|
goto out;
|
|
|
|
}
|
2015-03-18 20:43:47 +00:00
|
|
|
|
|
|
|
pr_smb(PR_STATUS,
|
2015-07-27 05:46:49 +00:00
|
|
|
"%s chip->usb_present = %d rt_sts = 0x%02x hvdcp_3_det_ignore_uv = %d aicl = %d\n",
|
|
|
|
chip->hvdcp_3_det_ignore_uv ? "Ignoring":"",
|
|
|
|
chip->usb_present, reg, chip->hvdcp_3_det_ignore_uv,
|
|
|
|
aicl_level);
|
2015-02-21 05:34:44 +00:00
|
|
|
|
2015-05-26 19:07:10 +00:00
|
|
|
/*
|
2015-07-27 05:41:12 +00:00
|
|
|
* set usb_psy's dp=f dm=f if this is a new insertion, i.e. it is
|
2015-05-26 19:07:10 +00:00
|
|
|
* not already src_detected and usbin_uv is seen falling
|
|
|
|
*/
|
2016-04-04 23:13:50 +00:00
|
|
|
if (!(reg & USBIN_UV_BIT) && !(reg & USBIN_SRC_DET_BIT) &&
|
|
|
|
!chip->hvdcp_3_det_ignore_uv) {
|
2015-07-27 05:41:12 +00:00
|
|
|
pr_smb(PR_MISC, "setting usb psy dp=f dm=f\n");
|
|
|
|
power_supply_set_dp_dm(chip->usb_psy,
|
|
|
|
POWER_SUPPLY_DP_DM_DPF_DMF);
|
2015-05-26 19:07:10 +00:00
|
|
|
}
|
|
|
|
|
2015-07-27 05:46:49 +00:00
|
|
|
if (reg & USBIN_UV_BIT)
|
|
|
|
complete_all(&chip->usbin_uv_raised);
|
|
|
|
else
|
|
|
|
complete_all(&chip->usbin_uv_lowered);
|
|
|
|
|
|
|
|
if (chip->hvdcp_3_det_ignore_uv)
|
|
|
|
goto out;
|
|
|
|
|
power: qpnp-smbcharger: handle very weak chargers
The PMi charger peripheral can detect weak chargers through the automatic
input current limit (AICL) feature by observing how much current it
takes to collapse an external charger. Additionally, under very bad
conditions, such as having a very weak charger with a high impedance
cable, the AICL algorithm will actually stop altogether when the USB
voltage drops below the autoshutdown threshold.
Normally, this will trigger AICL suspend and shut off the charger buck.
However, if hardware AICL reruns are enabled, AICL cannot be suspended
in hardware. This can cause a problem where the USBIN_UV interrupt is
continuously received when the USBIN voltage hovers around the lower
limit of the valid range.
Fix this by detecting very weak chargers if the USBIN_UV is high at the
same time USBIN_SRC_DET is high. If both interrupts lines are high,
it means that the USB voltage is below the low threshold for UV but
higher than the coarse detect threshold (1V).
If a weak charger is detected, disable AICL rerun to allow the charger
to enter into AICL suspend, and set the USB health to UNSPEC_FAILURE,
and do not report USB online.
Also, do not allow USBIN_UV to reset the aicl_irq_count in order to
allow the battery OV interrupt to run correctly.
CRs-Fixed: 823770
Change-Id: If853659fa7aaf899f500d8933a773ab80d0464a1
Signed-off-by: Xiaozhe Shi <xiaozhes@codeaurora.org>
2015-04-15 21:39:43 +00:00
|
|
|
if ((reg & USBIN_UV_BIT) && (reg & USBIN_SRC_DET_BIT)) {
|
|
|
|
pr_smb(PR_STATUS, "Very weak charger detected\n");
|
|
|
|
chip->very_weak_charger = true;
|
|
|
|
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);
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
if ((reg & ICL_MODE_MASK) != ICL_MODE_HIGH_CURRENT) {
|
|
|
|
/*
|
|
|
|
* If AICL is not even enabled, this is either an
|
|
|
|
* SDP or a grossly out of spec charger. Do not
|
|
|
|
* draw any current from it.
|
|
|
|
*/
|
2015-11-26 09:38:11 +00:00
|
|
|
rc = vote(chip->usb_suspend_votable,
|
|
|
|
WEAK_CHARGER_EN_VOTER, true, 0);
|
power: qpnp-smbcharger: handle very weak chargers
The PMi charger peripheral can detect weak chargers through the automatic
input current limit (AICL) feature by observing how much current it
takes to collapse an external charger. Additionally, under very bad
conditions, such as having a very weak charger with a high impedance
cable, the AICL algorithm will actually stop altogether when the USB
voltage drops below the autoshutdown threshold.
Normally, this will trigger AICL suspend and shut off the charger buck.
However, if hardware AICL reruns are enabled, AICL cannot be suspended
in hardware. This can cause a problem where the USBIN_UV interrupt is
continuously received when the USBIN voltage hovers around the lower
limit of the valid range.
Fix this by detecting very weak chargers if the USBIN_UV is high at the
same time USBIN_SRC_DET is high. If both interrupts lines are high,
it means that the USB voltage is below the low threshold for UV but
higher than the coarse detect threshold (1V).
If a weak charger is detected, disable AICL rerun to allow the charger
to enter into AICL suspend, and set the USB health to UNSPEC_FAILURE,
and do not report USB online.
Also, do not allow USBIN_UV to reset the aicl_irq_count in order to
allow the battery OV interrupt to run correctly.
CRs-Fixed: 823770
Change-Id: If853659fa7aaf899f500d8933a773ab80d0464a1
Signed-off-by: Xiaozhe Shi <xiaozhes@codeaurora.org>
2015-04-15 21:39:43 +00:00
|
|
|
if (rc)
|
|
|
|
pr_err("could not disable charger: %d", rc);
|
|
|
|
} else if ((chip->aicl_deglitch_short || chip->force_aicl_rerun)
|
2015-11-26 07:39:46 +00:00
|
|
|
&& aicl_level == chip->tables.usb_ilim_ma_table[0]) {
|
power: qpnp-smbcharger: handle very weak chargers
The PMi charger peripheral can detect weak chargers through the automatic
input current limit (AICL) feature by observing how much current it
takes to collapse an external charger. Additionally, under very bad
conditions, such as having a very weak charger with a high impedance
cable, the AICL algorithm will actually stop altogether when the USB
voltage drops below the autoshutdown threshold.
Normally, this will trigger AICL suspend and shut off the charger buck.
However, if hardware AICL reruns are enabled, AICL cannot be suspended
in hardware. This can cause a problem where the USBIN_UV interrupt is
continuously received when the USBIN voltage hovers around the lower
limit of the valid range.
Fix this by detecting very weak chargers if the USBIN_UV is high at the
same time USBIN_SRC_DET is high. If both interrupts lines are high,
it means that the USB voltage is below the low threshold for UV but
higher than the coarse detect threshold (1V).
If a weak charger is detected, disable AICL rerun to allow the charger
to enter into AICL suspend, and set the USB health to UNSPEC_FAILURE,
and do not report USB online.
Also, do not allow USBIN_UV to reset the aicl_irq_count in order to
allow the battery OV interrupt to run correctly.
CRs-Fixed: 823770
Change-Id: If853659fa7aaf899f500d8933a773ab80d0464a1
Signed-off-by: Xiaozhe Shi <xiaozhes@codeaurora.org>
2015-04-15 21:39:43 +00:00
|
|
|
rc = smbchg_hw_aicl_rerun_en(chip, false);
|
|
|
|
if (rc)
|
|
|
|
pr_err("could not enable aicl reruns: %d", rc);
|
|
|
|
}
|
2015-06-11 17:27:14 +00:00
|
|
|
pr_smb(PR_MISC, "setting usb psy health UNSPEC_FAILURE\n");
|
power: qpnp-smbcharger: handle very weak chargers
The PMi charger peripheral can detect weak chargers through the automatic
input current limit (AICL) feature by observing how much current it
takes to collapse an external charger. Additionally, under very bad
conditions, such as having a very weak charger with a high impedance
cable, the AICL algorithm will actually stop altogether when the USB
voltage drops below the autoshutdown threshold.
Normally, this will trigger AICL suspend and shut off the charger buck.
However, if hardware AICL reruns are enabled, AICL cannot be suspended
in hardware. This can cause a problem where the USBIN_UV interrupt is
continuously received when the USBIN voltage hovers around the lower
limit of the valid range.
Fix this by detecting very weak chargers if the USBIN_UV is high at the
same time USBIN_SRC_DET is high. If both interrupts lines are high,
it means that the USB voltage is below the low threshold for UV but
higher than the coarse detect threshold (1V).
If a weak charger is detected, disable AICL rerun to allow the charger
to enter into AICL suspend, and set the USB health to UNSPEC_FAILURE,
and do not report USB online.
Also, do not allow USBIN_UV to reset the aicl_irq_count in order to
allow the battery OV interrupt to run correctly.
CRs-Fixed: 823770
Change-Id: If853659fa7aaf899f500d8933a773ab80d0464a1
Signed-off-by: Xiaozhe Shi <xiaozhes@codeaurora.org>
2015-04-15 21:39:43 +00:00
|
|
|
rc = power_supply_set_health_state(chip->usb_psy,
|
|
|
|
POWER_SUPPLY_HEALTH_UNSPEC_FAILURE);
|
|
|
|
if (rc)
|
|
|
|
pr_err("Couldn't set health on usb psy rc:%d\n", rc);
|
|
|
|
schedule_work(&chip->usb_set_online_work);
|
|
|
|
}
|
|
|
|
|
2014-10-30 21:59:48 +00:00
|
|
|
smbchg_wipower_check(chip);
|
2015-02-21 05:34:44 +00:00
|
|
|
out:
|
2014-02-20 01:27:12 +00:00
|
|
|
return IRQ_HANDLED;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2015-02-21 05:34:44 +00:00
|
|
|
* src_detect_handler() - this is called on rising edge when USB charger type
|
|
|
|
* is detected and on falling edge when USB voltage falls
|
|
|
|
* below the coarse detect voltage(1V), use it for
|
2015-04-24 02:41:17 +00:00
|
|
|
* handling USB charger insertion and removal.
|
2014-02-20 01:27:12 +00:00
|
|
|
* @chip: pointer to smbchg_chip
|
|
|
|
* @rt_stat: the status bit indicating chg insertion/removal
|
|
|
|
*/
|
|
|
|
static irqreturn_t src_detect_handler(int irq, void *_chip)
|
|
|
|
{
|
|
|
|
struct smbchg_chip *chip = _chip;
|
2015-11-26 09:38:11 +00:00
|
|
|
bool usb_present = is_usb_present(chip);
|
2015-05-01 23:53:00 +00:00
|
|
|
bool src_detect = is_src_detect_high(chip);
|
power: qpnp-smbcharger: handle very weak chargers
The PMi charger peripheral can detect weak chargers through the automatic
input current limit (AICL) feature by observing how much current it
takes to collapse an external charger. Additionally, under very bad
conditions, such as having a very weak charger with a high impedance
cable, the AICL algorithm will actually stop altogether when the USB
voltage drops below the autoshutdown threshold.
Normally, this will trigger AICL suspend and shut off the charger buck.
However, if hardware AICL reruns are enabled, AICL cannot be suspended
in hardware. This can cause a problem where the USBIN_UV interrupt is
continuously received when the USBIN voltage hovers around the lower
limit of the valid range.
Fix this by detecting very weak chargers if the USBIN_UV is high at the
same time USBIN_SRC_DET is high. If both interrupts lines are high,
it means that the USB voltage is below the low threshold for UV but
higher than the coarse detect threshold (1V).
If a weak charger is detected, disable AICL rerun to allow the charger
to enter into AICL suspend, and set the USB health to UNSPEC_FAILURE,
and do not report USB online.
Also, do not allow USBIN_UV to reset the aicl_irq_count in order to
allow the battery OV interrupt to run correctly.
CRs-Fixed: 823770
Change-Id: If853659fa7aaf899f500d8933a773ab80d0464a1
Signed-off-by: Xiaozhe Shi <xiaozhes@codeaurora.org>
2015-04-15 21:39:43 +00:00
|
|
|
int rc;
|
2014-02-20 01:27:12 +00:00
|
|
|
|
2015-03-18 20:43:47 +00:00
|
|
|
pr_smb(PR_STATUS,
|
2015-07-27 05:46:49 +00:00
|
|
|
"%s chip->usb_present = %d usb_present = %d src_detect = %d hvdcp_3_det_ignore_uv=%d\n",
|
|
|
|
chip->hvdcp_3_det_ignore_uv ? "Ignoring":"",
|
|
|
|
chip->usb_present, usb_present, src_detect,
|
|
|
|
chip->hvdcp_3_det_ignore_uv);
|
|
|
|
|
|
|
|
if (src_detect)
|
|
|
|
complete_all(&chip->src_det_raised);
|
|
|
|
else
|
|
|
|
complete_all(&chip->src_det_lowered);
|
|
|
|
|
|
|
|
if (chip->hvdcp_3_det_ignore_uv)
|
|
|
|
goto out;
|
2014-02-20 01:27:12 +00:00
|
|
|
|
2015-04-30 01:52:26 +00:00
|
|
|
/*
|
|
|
|
* When VBAT is above the AICL threshold (4.25V) - 180mV (4.07V),
|
|
|
|
* an input collapse due to AICL will actually cause an USBIN_UV
|
|
|
|
* interrupt to fire as well.
|
|
|
|
*
|
|
|
|
* Handle USB insertions and removals in the source detect handler
|
|
|
|
* instead of the USBIN_UV handler since the latter is untrustworthy
|
|
|
|
* when the battery voltage is high.
|
|
|
|
*/
|
2015-07-21 18:15:21 +00:00
|
|
|
chip->very_weak_charger = false;
|
2015-11-26 09:38:11 +00:00
|
|
|
rc = vote(chip->usb_suspend_votable, WEAK_CHARGER_EN_VOTER, false, 0);
|
2015-07-21 18:15:21 +00:00
|
|
|
if (rc < 0)
|
|
|
|
pr_err("could not enable charger: %d\n", rc);
|
|
|
|
|
2015-05-01 23:53:00 +00:00
|
|
|
if (src_detect) {
|
2015-05-26 19:07:10 +00:00
|
|
|
update_usb_status(chip, usb_present, 0);
|
2015-04-30 01:52:26 +00:00
|
|
|
} else {
|
2015-04-24 02:41:17 +00:00
|
|
|
update_usb_status(chip, 0, false);
|
|
|
|
chip->aicl_irq_count = 0;
|
2015-02-21 05:34:44 +00:00
|
|
|
}
|
2015-07-27 05:46:49 +00:00
|
|
|
out:
|
2014-02-20 01:27:12 +00:00
|
|
|
return IRQ_HANDLED;
|
|
|
|
}
|
|
|
|
|
2014-06-17 23:08:46 +00:00
|
|
|
/**
|
|
|
|
* otg_oc_handler() - called when the usb otg goes over current
|
|
|
|
*/
|
2014-12-03 19:30:00 +00:00
|
|
|
#define NUM_OTG_RETRIES 5
|
|
|
|
#define OTG_OC_RETRY_DELAY_US 50000
|
2014-06-17 23:08:46 +00:00
|
|
|
static irqreturn_t otg_oc_handler(int irq, void *_chip)
|
|
|
|
{
|
2015-05-15 03:37:42 +00:00
|
|
|
int rc;
|
2014-06-18 21:13:50 +00:00
|
|
|
struct smbchg_chip *chip = _chip;
|
2014-12-03 19:30:00 +00:00
|
|
|
s64 elapsed_us = ktime_us_delta(ktime_get(), chip->otg_enable_time);
|
|
|
|
|
2015-02-11 14:23:46 +00:00
|
|
|
pr_smb(PR_INTERRUPT, "triggered\n");
|
|
|
|
|
2015-05-15 03:37:42 +00:00
|
|
|
if (chip->schg_version == QPNP_SCHG_LITE) {
|
2015-06-30 15:36:37 +00:00
|
|
|
pr_warn("OTG OC triggered - OTG disabled\n");
|
2015-02-11 14:23:46 +00:00
|
|
|
return IRQ_HANDLED;
|
2015-05-15 03:37:42 +00:00
|
|
|
}
|
2015-02-11 14:23:46 +00:00
|
|
|
|
2014-12-03 19:30:00 +00:00
|
|
|
if (elapsed_us > OTG_OC_RETRY_DELAY_US)
|
|
|
|
chip->otg_retries = 0;
|
2014-06-18 21:13:50 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Due to a HW bug in the PMI8994 charger, the current inrush that
|
|
|
|
* occurs when connecting certain OTG devices can cause the OTG
|
|
|
|
* overcurrent protection to trip.
|
|
|
|
*
|
|
|
|
* The work around is to try reenabling the OTG when getting an
|
|
|
|
* overcurrent interrupt once.
|
|
|
|
*/
|
|
|
|
if (chip->otg_retries < NUM_OTG_RETRIES) {
|
|
|
|
chip->otg_retries += 1;
|
2014-12-03 19:30:00 +00:00
|
|
|
pr_smb(PR_STATUS,
|
|
|
|
"Retrying OTG enable. Try #%d, elapsed_us %lld\n",
|
|
|
|
chip->otg_retries, elapsed_us);
|
2015-05-15 03:37:42 +00:00
|
|
|
rc = otg_oc_reset(chip);
|
|
|
|
if (rc)
|
|
|
|
pr_err("Failed to reset OTG OC state rc=%d\n", rc);
|
2014-12-03 19:30:00 +00:00
|
|
|
chip->otg_enable_time = ktime_get();
|
2014-06-18 21:13:50 +00:00
|
|
|
}
|
2014-06-17 23:08:46 +00:00
|
|
|
return IRQ_HANDLED;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* otg_fail_handler() - called when the usb otg fails
|
|
|
|
* (when vbat < OTG UVLO threshold)
|
|
|
|
*/
|
|
|
|
static irqreturn_t otg_fail_handler(int irq, void *_chip)
|
|
|
|
{
|
2014-06-30 22:02:34 +00:00
|
|
|
pr_smb(PR_INTERRUPT, "triggered\n");
|
2014-06-17 23:08:46 +00:00
|
|
|
return IRQ_HANDLED;
|
|
|
|
}
|
|
|
|
|
power: qpnp-smbcharger: handle very weak chargers
The PMi charger peripheral can detect weak chargers through the automatic
input current limit (AICL) feature by observing how much current it
takes to collapse an external charger. Additionally, under very bad
conditions, such as having a very weak charger with a high impedance
cable, the AICL algorithm will actually stop altogether when the USB
voltage drops below the autoshutdown threshold.
Normally, this will trigger AICL suspend and shut off the charger buck.
However, if hardware AICL reruns are enabled, AICL cannot be suspended
in hardware. This can cause a problem where the USBIN_UV interrupt is
continuously received when the USBIN voltage hovers around the lower
limit of the valid range.
Fix this by detecting very weak chargers if the USBIN_UV is high at the
same time USBIN_SRC_DET is high. If both interrupts lines are high,
it means that the USB voltage is below the low threshold for UV but
higher than the coarse detect threshold (1V).
If a weak charger is detected, disable AICL rerun to allow the charger
to enter into AICL suspend, and set the USB health to UNSPEC_FAILURE,
and do not report USB online.
Also, do not allow USBIN_UV to reset the aicl_irq_count in order to
allow the battery OV interrupt to run correctly.
CRs-Fixed: 823770
Change-Id: If853659fa7aaf899f500d8933a773ab80d0464a1
Signed-off-by: Xiaozhe Shi <xiaozhes@codeaurora.org>
2015-04-15 21:39:43 +00:00
|
|
|
/**
|
|
|
|
* aicl_done_handler() - called when the usb AICL algorithm is finished
|
|
|
|
* and a current is set.
|
|
|
|
*/
|
|
|
|
static irqreturn_t aicl_done_handler(int irq, void *_chip)
|
|
|
|
{
|
|
|
|
struct smbchg_chip *chip = _chip;
|
|
|
|
bool usb_present = is_usb_present(chip);
|
|
|
|
int aicl_level = smbchg_get_aicl_level_ma(chip);
|
|
|
|
|
|
|
|
pr_smb(PR_INTERRUPT, "triggered, aicl: %d\n", aicl_level);
|
|
|
|
|
|
|
|
increment_aicl_count(chip);
|
2015-02-22 00:14:03 +00:00
|
|
|
|
|
|
|
if (usb_present)
|
|
|
|
smbchg_parallel_usb_check_ok(chip);
|
|
|
|
|
2015-01-22 01:34:30 +00:00
|
|
|
if (chip->aicl_complete)
|
|
|
|
power_supply_changed(&chip->batt_psy);
|
|
|
|
|
2014-05-30 21:07:14 +00:00
|
|
|
return IRQ_HANDLED;
|
|
|
|
}
|
|
|
|
|
2014-06-17 23:08:46 +00:00
|
|
|
/**
|
|
|
|
* usbid_change_handler() - called when the usb RID changes.
|
|
|
|
* This is used mostly for detecting OTG
|
|
|
|
*/
|
|
|
|
static irqreturn_t usbid_change_handler(int irq, void *_chip)
|
|
|
|
{
|
|
|
|
struct smbchg_chip *chip = _chip;
|
|
|
|
bool otg_present;
|
|
|
|
|
2014-06-30 22:02:34 +00:00
|
|
|
pr_smb(PR_INTERRUPT, "triggered\n");
|
2014-06-17 23:08:46 +00:00
|
|
|
|
|
|
|
otg_present = is_otg_present(chip);
|
2015-06-11 17:27:14 +00:00
|
|
|
if (chip->usb_psy) {
|
|
|
|
pr_smb(PR_MISC, "setting usb psy OTG = %d\n",
|
|
|
|
otg_present ? 1 : 0);
|
2014-06-17 23:08:46 +00:00
|
|
|
power_supply_set_usb_otg(chip->usb_psy, otg_present ? 1 : 0);
|
2015-06-11 17:27:14 +00:00
|
|
|
}
|
2014-06-18 21:13:50 +00:00
|
|
|
if (otg_present)
|
2014-06-30 22:02:34 +00:00
|
|
|
pr_smb(PR_STATUS, "OTG detected\n");
|
2014-06-17 23:08:46 +00:00
|
|
|
|
2015-07-06 05:43:40 +00:00
|
|
|
/* update FG */
|
|
|
|
set_property_on_fg(chip, POWER_SUPPLY_PROP_STATUS,
|
|
|
|
get_prop_batt_status(chip));
|
|
|
|
|
2014-06-17 23:08:46 +00:00
|
|
|
return IRQ_HANDLED;
|
|
|
|
}
|
|
|
|
|
2014-02-20 01:27:12 +00:00
|
|
|
static int determine_initial_status(struct smbchg_chip *chip)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* It is okay to read the interrupt status here since
|
|
|
|
* interrupts aren't requested. reading interrupt status
|
|
|
|
* clears the interrupt so be careful to read interrupt
|
|
|
|
* status only in interrupt handling code
|
|
|
|
*/
|
|
|
|
|
|
|
|
batt_pres_handler(0, chip);
|
|
|
|
batt_hot_handler(0, chip);
|
|
|
|
batt_warm_handler(0, chip);
|
|
|
|
batt_cool_handler(0, chip);
|
|
|
|
batt_cold_handler(0, chip);
|
|
|
|
chg_term_handler(0, chip);
|
2014-06-17 23:08:46 +00:00
|
|
|
usbid_change_handler(0, chip);
|
2014-08-06 18:27:04 +00:00
|
|
|
src_detect_handler(0, chip);
|
2014-02-20 01:27:12 +00:00
|
|
|
|
|
|
|
chip->usb_present = is_usb_present(chip);
|
|
|
|
chip->dc_present = is_dc_present(chip);
|
|
|
|
|
2015-07-21 02:00:59 +00:00
|
|
|
if (chip->usb_present) {
|
|
|
|
pr_smb(PR_MISC, "setting usb psy dp=f dm=f\n");
|
|
|
|
power_supply_set_dp_dm(chip->usb_psy,
|
|
|
|
POWER_SUPPLY_DP_DM_DPF_DMF);
|
2014-02-20 01:27:12 +00:00
|
|
|
handle_usb_insertion(chip);
|
2015-07-21 02:00:59 +00:00
|
|
|
} else {
|
2014-02-20 01:27:12 +00:00
|
|
|
handle_usb_removal(chip);
|
2015-07-21 02:00:59 +00:00
|
|
|
}
|
2014-02-20 01:27:12 +00:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int prechg_time[] = {
|
|
|
|
24,
|
|
|
|
48,
|
|
|
|
96,
|
|
|
|
192,
|
|
|
|
};
|
|
|
|
static int chg_time[] = {
|
|
|
|
192,
|
|
|
|
384,
|
|
|
|
768,
|
|
|
|
1536,
|
|
|
|
};
|
|
|
|
|
2014-07-29 23:39:57 +00:00
|
|
|
enum bpd_type {
|
|
|
|
BPD_TYPE_BAT_NONE,
|
|
|
|
BPD_TYPE_BAT_ID,
|
|
|
|
BPD_TYPE_BAT_THM,
|
|
|
|
BPD_TYPE_BAT_THM_BAT_ID,
|
|
|
|
BPD_TYPE_DEFAULT,
|
|
|
|
};
|
|
|
|
|
|
|
|
static const char * const bpd_label[] = {
|
|
|
|
[BPD_TYPE_BAT_NONE] = "bpd_none",
|
|
|
|
[BPD_TYPE_BAT_ID] = "bpd_id",
|
|
|
|
[BPD_TYPE_BAT_THM] = "bpd_thm",
|
|
|
|
[BPD_TYPE_BAT_THM_BAT_ID] = "bpd_thm_id",
|
|
|
|
};
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2015-02-11 14:23:46 +00:00
|
|
|
#define REVISION1_REG 0x0
|
|
|
|
#define DIG_MINOR 0
|
|
|
|
#define DIG_MAJOR 1
|
|
|
|
#define ANA_MINOR 2
|
|
|
|
#define ANA_MAJOR 3
|
2014-05-28 02:11:10 +00:00
|
|
|
#define CHGR_CFG1 0xFB
|
|
|
|
#define RECHG_THRESHOLD_SRC_BIT BIT(1)
|
|
|
|
#define TERM_I_SRC_BIT BIT(2)
|
2014-11-18 03:56:08 +00:00
|
|
|
#define TERM_SRC_FG BIT(2)
|
2014-02-20 01:27:12 +00:00
|
|
|
#define CHG_INHIB_CFG_REG 0xF7
|
|
|
|
#define CHG_INHIBIT_50MV_VAL 0x00
|
|
|
|
#define CHG_INHIBIT_100MV_VAL 0x01
|
|
|
|
#define CHG_INHIBIT_200MV_VAL 0x02
|
|
|
|
#define CHG_INHIBIT_300MV_VAL 0x03
|
|
|
|
#define CHG_INHIBIT_MASK 0x03
|
|
|
|
#define USE_REGISTER_FOR_CURRENT BIT(2)
|
2015-08-13 06:11:46 +00:00
|
|
|
#define CHGR_CFG2 0xFC
|
2014-02-20 01:27:12 +00:00
|
|
|
#define CHG_EN_SRC_BIT BIT(7)
|
2015-08-05 22:05:21 +00:00
|
|
|
#define CHG_EN_POLARITY_BIT BIT(6)
|
2014-02-20 01:27:12 +00:00
|
|
|
#define P2F_CHG_TRAN BIT(5)
|
2015-08-13 06:11:46 +00:00
|
|
|
#define CHG_BAT_OV_ECC BIT(4)
|
2014-02-20 01:27:12 +00:00
|
|
|
#define I_TERM_BIT BIT(3)
|
|
|
|
#define AUTO_RECHG_BIT BIT(2)
|
2014-10-08 01:56:02 +00:00
|
|
|
#define CHARGER_INHIBIT_BIT BIT(0)
|
2014-02-20 01:27:12 +00:00
|
|
|
#define USB51_COMMAND_POL BIT(2)
|
|
|
|
#define USB51AC_CTRL BIT(1)
|
2014-08-18 21:02:15 +00:00
|
|
|
#define TR_8OR32B 0xFE
|
|
|
|
#define BUCK_8_16_FREQ_BIT BIT(0)
|
2014-02-20 01:27:12 +00:00
|
|
|
#define BM_CFG 0xF3
|
|
|
|
#define BATT_MISSING_ALGO_BIT BIT(2)
|
|
|
|
#define BMD_PIN_SRC_MASK SMB_MASK(1, 0)
|
|
|
|
#define PIN_SRC_SHIFT 0
|
|
|
|
#define CHGR_CFG 0xFF
|
|
|
|
#define RCHG_LVL_BIT BIT(0)
|
2015-11-04 06:45:11 +00:00
|
|
|
#define VCHG_EN_BIT BIT(1)
|
|
|
|
#define VCHG_INPUT_CURRENT_BIT BIT(3)
|
2014-12-26 20:25:54 +00:00
|
|
|
#define CFG_AFVC 0xF6
|
2014-02-20 01:27:12 +00:00
|
|
|
#define VFLOAT_COMP_ENABLE_MASK SMB_MASK(2, 0)
|
power: qpnp-smbcharger: add workaround for UVLO resets
When using the PMI8994 smbcharger, usb removal or suspend can cause
an under voltage lock out (UVLO) due to a hardware bug.
The condition for the UVLO to happen is when the whole system is running
off the charger buck when the USB is either removed or suspended. This
case can easily happen when the system is running at medium loads
(300-400 mA) and the battery is considered missing, overtemp, or
overvoltage by the charger hardware.
When this happens, the charger stops trying to pump current into the
battery, and instead just draws power from the active charge path
(USB in ths case). However, when the input charge path is removed, either
by suspending it through software or by physically removing the USB
cable, the battery FET does not turn on fast enough in hardware to
supplement the system load. This causes a droop on VPH power, manifesting
itself as an UVLO reset.
The work around for this issue is to ensure the battery is supplementing
the current before a charge path removal event happens. Implement this by
forcing USB input current limit to be 100mA whenever the device is not
charging. More specifically, whenever a charger p2f-thresh interrupt
(pre-to-fastcharge event, charging starts) or a battery interface
chg-error interrupt (battery missing/overtemp/overvolt) happens, check
the charging status and if the device is not charging, force the charger
to only draw 100mA.
If the input current limit is at 100mA, the system will be drawing
current from the battery as long as the system load is higher than
100mA. Thus, when the USB is removed, the battery will already be
supplementing the system load, and there will not be an UVLO reset.
There are a few limitations to this work around.
1. If the system load is less than 100mA when the input charge
path is removed, there may still be a droop on VPH power due
to the battery FET still being open. However, this should not
cause an UVLO reset because the system load will not cause a
large enough droop to cause a UVLO reset.
2. If the input is removed at the exact instant a charger error
occurs (battery goes missing, battery becomes overtemp,
battery becomes overvoltage), and the interrupt does not get
serviced before the input is removed, an UVLO reset can still
happen. However, this is a hardware limitation and there is
no solution for this at this time.
3. Currently, the workaround for the DC input charge path is not
supported. Until this is implemented, DC charging and wipower
will be impacted.
CRs-Fixed: 667489
Change-Id: I1a185d126e1e6bb851e5981f8b49072e8f8c6f68
Signed-off-by: Xiaozhe Shi <xiaozhes@codeaurora.org>
2014-06-12 00:35:01 +00:00
|
|
|
#define TR_RID_REG 0xFA
|
|
|
|
#define FG_INPUT_FET_DELAY_BIT BIT(3)
|
|
|
|
#define TRIM_OPTIONS_7_0 0xF6
|
|
|
|
#define INPUT_MISSING_POLLER_EN_BIT BIT(3)
|
2015-01-08 04:39:21 +00:00
|
|
|
#define CHGR_CCMP_CFG 0xFA
|
|
|
|
#define JEITA_TEMP_HARD_LIMIT_BIT BIT(5)
|
2015-06-08 11:14:19 +00:00
|
|
|
#define HVDCP_ADAPTER_SEL_MASK SMB_MASK(5, 4)
|
|
|
|
#define HVDCP_ADAPTER_SEL_9V_BIT BIT(4)
|
|
|
|
#define HVDCP_AUTH_ALG_EN_BIT BIT(6)
|
|
|
|
#define CMD_APSD 0x41
|
|
|
|
#define APSD_RERUN_BIT BIT(0)
|
2015-06-30 15:36:37 +00:00
|
|
|
#define OTG_OC_CFG 0xF1
|
|
|
|
#define HICCUP_ENABLED_BIT BIT(6)
|
2015-11-26 09:42:32 +00:00
|
|
|
#define AICL_ADC_BIT BIT(6)
|
2015-08-13 06:11:46 +00:00
|
|
|
static void batt_ov_wa_check(struct smbchg_chip *chip)
|
|
|
|
{
|
|
|
|
int rc;
|
|
|
|
u8 reg;
|
|
|
|
|
|
|
|
/* disable-'battery OV disables charging' feature */
|
|
|
|
rc = smbchg_sec_masked_write(chip, chip->chgr_base + CHGR_CFG2,
|
|
|
|
CHG_BAT_OV_ECC, 0);
|
|
|
|
if (rc < 0) {
|
|
|
|
dev_err(chip->dev, "Couldn't set chgr_cfg2 rc=%d\n", rc);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* if battery OV is set:
|
|
|
|
* restart charging by disable/enable charging
|
|
|
|
*/
|
|
|
|
rc = smbchg_read(chip, ®, chip->bat_if_base + RT_STS, 1);
|
|
|
|
if (rc < 0) {
|
|
|
|
dev_err(chip->dev,
|
|
|
|
"Couldn't read Battery RT status rc = %d\n", rc);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (reg & BAT_OV_BIT) {
|
|
|
|
rc = smbchg_charging_en(chip, false);
|
|
|
|
if (rc < 0) {
|
|
|
|
dev_err(chip->dev,
|
|
|
|
"Couldn't disable charging: rc = %d\n", rc);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* delay for charging-disable to take affect */
|
|
|
|
msleep(200);
|
|
|
|
|
|
|
|
rc = smbchg_charging_en(chip, true);
|
|
|
|
if (rc < 0) {
|
|
|
|
dev_err(chip->dev,
|
|
|
|
"Couldn't enable charging: rc = %d\n", rc);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-02-20 01:27:12 +00:00
|
|
|
static int smbchg_hw_init(struct smbchg_chip *chip)
|
|
|
|
{
|
|
|
|
int rc, i;
|
2014-07-29 23:39:57 +00:00
|
|
|
u8 reg, mask;
|
2014-02-20 01:27:12 +00:00
|
|
|
|
2014-09-09 21:03:24 +00:00
|
|
|
rc = smbchg_read(chip, chip->revision,
|
|
|
|
chip->misc_base + REVISION1_REG, 4);
|
|
|
|
if (rc < 0) {
|
|
|
|
dev_err(chip->dev, "Couldn't read revision rc=%d\n",
|
|
|
|
rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
pr_smb(PR_STATUS, "Charger Revision DIG: %d.%d; ANA: %d.%d\n",
|
|
|
|
chip->revision[DIG_MAJOR], chip->revision[DIG_MINOR],
|
|
|
|
chip->revision[ANA_MAJOR], chip->revision[ANA_MINOR]);
|
|
|
|
|
2015-11-26 09:51:36 +00:00
|
|
|
if (chip->aicl_rerun_period_s > 0) {
|
|
|
|
rc = smbchg_set_aicl_rerun_period_s(chip,
|
|
|
|
chip->aicl_rerun_period_s);
|
|
|
|
if (rc < 0) {
|
|
|
|
dev_err(chip->dev, "Couldn't set AICL rerun timer rc=%d\n",
|
|
|
|
rc);
|
|
|
|
return rc;
|
|
|
|
}
|
2014-11-04 18:38:29 +00:00
|
|
|
}
|
|
|
|
|
power: qpnp-smbcharger: add workaround for UVLO resets
When using the PMI8994 smbcharger, usb removal or suspend can cause
an under voltage lock out (UVLO) due to a hardware bug.
The condition for the UVLO to happen is when the whole system is running
off the charger buck when the USB is either removed or suspended. This
case can easily happen when the system is running at medium loads
(300-400 mA) and the battery is considered missing, overtemp, or
overvoltage by the charger hardware.
When this happens, the charger stops trying to pump current into the
battery, and instead just draws power from the active charge path
(USB in ths case). However, when the input charge path is removed, either
by suspending it through software or by physically removing the USB
cable, the battery FET does not turn on fast enough in hardware to
supplement the system load. This causes a droop on VPH power, manifesting
itself as an UVLO reset.
The work around for this issue is to ensure the battery is supplementing
the current before a charge path removal event happens. Implement this by
forcing USB input current limit to be 100mA whenever the device is not
charging. More specifically, whenever a charger p2f-thresh interrupt
(pre-to-fastcharge event, charging starts) or a battery interface
chg-error interrupt (battery missing/overtemp/overvolt) happens, check
the charging status and if the device is not charging, force the charger
to only draw 100mA.
If the input current limit is at 100mA, the system will be drawing
current from the battery as long as the system load is higher than
100mA. Thus, when the USB is removed, the battery will already be
supplementing the system load, and there will not be an UVLO reset.
There are a few limitations to this work around.
1. If the system load is less than 100mA when the input charge
path is removed, there may still be a droop on VPH power due
to the battery FET still being open. However, this should not
cause an UVLO reset because the system load will not cause a
large enough droop to cause a UVLO reset.
2. If the input is removed at the exact instant a charger error
occurs (battery goes missing, battery becomes overtemp,
battery becomes overvoltage), and the interrupt does not get
serviced before the input is removed, an UVLO reset can still
happen. However, this is a hardware limitation and there is
no solution for this at this time.
3. Currently, the workaround for the DC input charge path is not
supported. Until this is implemented, DC charging and wipower
will be impacted.
CRs-Fixed: 667489
Change-Id: I1a185d126e1e6bb851e5981f8b49072e8f8c6f68
Signed-off-by: Xiaozhe Shi <xiaozhes@codeaurora.org>
2014-06-12 00:35:01 +00:00
|
|
|
rc = smbchg_sec_masked_write(chip, chip->usb_chgpth_base + TR_RID_REG,
|
|
|
|
FG_INPUT_FET_DELAY_BIT, FG_INPUT_FET_DELAY_BIT);
|
|
|
|
if (rc < 0) {
|
2014-06-30 22:02:34 +00:00
|
|
|
dev_err(chip->dev, "Couldn't disable fg input fet delay rc=%d\n",
|
|
|
|
rc);
|
power: qpnp-smbcharger: add workaround for UVLO resets
When using the PMI8994 smbcharger, usb removal or suspend can cause
an under voltage lock out (UVLO) due to a hardware bug.
The condition for the UVLO to happen is when the whole system is running
off the charger buck when the USB is either removed or suspended. This
case can easily happen when the system is running at medium loads
(300-400 mA) and the battery is considered missing, overtemp, or
overvoltage by the charger hardware.
When this happens, the charger stops trying to pump current into the
battery, and instead just draws power from the active charge path
(USB in ths case). However, when the input charge path is removed, either
by suspending it through software or by physically removing the USB
cable, the battery FET does not turn on fast enough in hardware to
supplement the system load. This causes a droop on VPH power, manifesting
itself as an UVLO reset.
The work around for this issue is to ensure the battery is supplementing
the current before a charge path removal event happens. Implement this by
forcing USB input current limit to be 100mA whenever the device is not
charging. More specifically, whenever a charger p2f-thresh interrupt
(pre-to-fastcharge event, charging starts) or a battery interface
chg-error interrupt (battery missing/overtemp/overvolt) happens, check
the charging status and if the device is not charging, force the charger
to only draw 100mA.
If the input current limit is at 100mA, the system will be drawing
current from the battery as long as the system load is higher than
100mA. Thus, when the USB is removed, the battery will already be
supplementing the system load, and there will not be an UVLO reset.
There are a few limitations to this work around.
1. If the system load is less than 100mA when the input charge
path is removed, there may still be a droop on VPH power due
to the battery FET still being open. However, this should not
cause an UVLO reset because the system load will not cause a
large enough droop to cause a UVLO reset.
2. If the input is removed at the exact instant a charger error
occurs (battery goes missing, battery becomes overtemp,
battery becomes overvoltage), and the interrupt does not get
serviced before the input is removed, an UVLO reset can still
happen. However, this is a hardware limitation and there is
no solution for this at this time.
3. Currently, the workaround for the DC input charge path is not
supported. Until this is implemented, DC charging and wipower
will be impacted.
CRs-Fixed: 667489
Change-Id: I1a185d126e1e6bb851e5981f8b49072e8f8c6f68
Signed-off-by: Xiaozhe Shi <xiaozhes@codeaurora.org>
2014-06-12 00:35:01 +00:00
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
rc = smbchg_sec_masked_write(chip, chip->misc_base + TRIM_OPTIONS_7_0,
|
|
|
|
INPUT_MISSING_POLLER_EN_BIT, 0);
|
|
|
|
if (rc < 0) {
|
2014-06-30 22:02:34 +00:00
|
|
|
dev_err(chip->dev, "Couldn't disable input missing poller rc=%d\n",
|
|
|
|
rc);
|
power: qpnp-smbcharger: add workaround for UVLO resets
When using the PMI8994 smbcharger, usb removal or suspend can cause
an under voltage lock out (UVLO) due to a hardware bug.
The condition for the UVLO to happen is when the whole system is running
off the charger buck when the USB is either removed or suspended. This
case can easily happen when the system is running at medium loads
(300-400 mA) and the battery is considered missing, overtemp, or
overvoltage by the charger hardware.
When this happens, the charger stops trying to pump current into the
battery, and instead just draws power from the active charge path
(USB in ths case). However, when the input charge path is removed, either
by suspending it through software or by physically removing the USB
cable, the battery FET does not turn on fast enough in hardware to
supplement the system load. This causes a droop on VPH power, manifesting
itself as an UVLO reset.
The work around for this issue is to ensure the battery is supplementing
the current before a charge path removal event happens. Implement this by
forcing USB input current limit to be 100mA whenever the device is not
charging. More specifically, whenever a charger p2f-thresh interrupt
(pre-to-fastcharge event, charging starts) or a battery interface
chg-error interrupt (battery missing/overtemp/overvolt) happens, check
the charging status and if the device is not charging, force the charger
to only draw 100mA.
If the input current limit is at 100mA, the system will be drawing
current from the battery as long as the system load is higher than
100mA. Thus, when the USB is removed, the battery will already be
supplementing the system load, and there will not be an UVLO reset.
There are a few limitations to this work around.
1. If the system load is less than 100mA when the input charge
path is removed, there may still be a droop on VPH power due
to the battery FET still being open. However, this should not
cause an UVLO reset because the system load will not cause a
large enough droop to cause a UVLO reset.
2. If the input is removed at the exact instant a charger error
occurs (battery goes missing, battery becomes overtemp,
battery becomes overvoltage), and the interrupt does not get
serviced before the input is removed, an UVLO reset can still
happen. However, this is a hardware limitation and there is
no solution for this at this time.
3. Currently, the workaround for the DC input charge path is not
supported. Until this is implemented, DC charging and wipower
will be impacted.
CRs-Fixed: 667489
Change-Id: I1a185d126e1e6bb851e5981f8b49072e8f8c6f68
Signed-off-by: Xiaozhe Shi <xiaozhes@codeaurora.org>
2014-06-12 00:35:01 +00:00
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2014-02-20 01:27:12 +00:00
|
|
|
/*
|
2014-12-16 01:16:24 +00:00
|
|
|
* Do not force using current from the register i.e. use auto
|
|
|
|
* power source detect (APSD) mA ratings for the initial current values.
|
|
|
|
*
|
|
|
|
* If this is set, AICL will not rerun at 9V for HVDCPs
|
2014-02-20 01:27:12 +00:00
|
|
|
*/
|
|
|
|
rc = smbchg_masked_write(chip, chip->usb_chgpth_base + CMD_IL,
|
2014-12-16 01:16:24 +00:00
|
|
|
USE_REGISTER_FOR_CURRENT, 0);
|
|
|
|
|
2014-02-20 01:27:12 +00:00
|
|
|
if (rc < 0) {
|
|
|
|
dev_err(chip->dev, "Couldn't set input limit cmd rc=%d\n", rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* set chg en by cmd register, set chg en by writing bit 1,
|
2014-11-18 21:44:25 +00:00
|
|
|
* enable auto pre to fast, enable auto recharge by default.
|
|
|
|
* enable current termination and charge inhibition based on
|
|
|
|
* the device tree configuration.
|
2014-02-20 01:27:12 +00:00
|
|
|
*/
|
|
|
|
rc = smbchg_sec_masked_write(chip, chip->chgr_base + CHGR_CFG2,
|
2015-08-05 22:05:21 +00:00
|
|
|
CHG_EN_SRC_BIT | CHG_EN_POLARITY_BIT | P2F_CHG_TRAN
|
2014-02-20 01:27:12 +00:00
|
|
|
| I_TERM_BIT | AUTO_RECHG_BIT | CHARGER_INHIBIT_BIT,
|
2015-08-05 22:05:21 +00:00
|
|
|
CHG_EN_POLARITY_BIT
|
2014-11-18 21:44:25 +00:00
|
|
|
| (chip->chg_inhibit_en ? CHARGER_INHIBIT_BIT : 0)
|
2014-02-20 01:27:12 +00:00
|
|
|
| (chip->iterm_disabled ? I_TERM_BIT : 0));
|
|
|
|
if (rc < 0) {
|
|
|
|
dev_err(chip->dev, "Couldn't set chgr_cfg2 rc=%d\n", rc);
|
|
|
|
return rc;
|
|
|
|
}
|
2015-08-05 22:05:21 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* enable battery charging to make sure it hasn't been changed before
|
|
|
|
* boot.
|
|
|
|
*/
|
|
|
|
rc = smbchg_charging_en(chip, true);
|
|
|
|
if (rc < 0) {
|
|
|
|
dev_err(chip->dev, "Couldn't enable battery charging=%d\n", rc);
|
|
|
|
return rc;
|
|
|
|
}
|
2014-02-20 01:27:12 +00:00
|
|
|
|
2014-05-28 02:19:21 +00:00
|
|
|
/*
|
2014-09-26 22:23:56 +00:00
|
|
|
* Based on the configuration, use the analog sensors or the fuelgauge
|
|
|
|
* adc for recharge threshold source.
|
2014-05-28 02:19:21 +00:00
|
|
|
*/
|
2014-09-26 22:23:56 +00:00
|
|
|
|
|
|
|
if (chip->chg_inhibit_source_fg)
|
|
|
|
rc = smbchg_sec_masked_write(chip, chip->chgr_base + CHGR_CFG1,
|
|
|
|
TERM_I_SRC_BIT | RECHG_THRESHOLD_SRC_BIT,
|
2014-11-18 03:56:08 +00:00
|
|
|
TERM_SRC_FG | RECHG_THRESHOLD_SRC_BIT);
|
2014-09-26 22:23:56 +00:00
|
|
|
else
|
|
|
|
rc = smbchg_sec_masked_write(chip, chip->chgr_base + CHGR_CFG1,
|
2014-05-28 02:19:21 +00:00
|
|
|
TERM_I_SRC_BIT | RECHG_THRESHOLD_SRC_BIT, 0);
|
2014-09-26 22:23:56 +00:00
|
|
|
|
2014-05-28 02:19:21 +00:00
|
|
|
if (rc < 0) {
|
|
|
|
dev_err(chip->dev, "Couldn't set chgr_cfg2 rc=%d\n", rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2014-02-20 01:27:12 +00:00
|
|
|
/*
|
|
|
|
* control USB suspend via command bits and set correct 100/500mA
|
|
|
|
* polarity on the usb current
|
|
|
|
*/
|
|
|
|
rc = smbchg_sec_masked_write(chip, chip->usb_chgpth_base + CHGPTH_CFG,
|
2015-04-01 22:03:35 +00:00
|
|
|
USB51_COMMAND_POL | USB51AC_CTRL, 0);
|
2014-02-20 01:27:12 +00:00
|
|
|
if (rc < 0) {
|
|
|
|
dev_err(chip->dev, "Couldn't set usb_chgpth cfg rc=%d\n", rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2015-04-01 22:03:35 +00:00
|
|
|
check_battery_type(chip);
|
|
|
|
|
2014-02-20 01:27:12 +00:00
|
|
|
/* set the float voltage */
|
|
|
|
if (chip->vfloat_mv != -EINVAL) {
|
|
|
|
rc = smbchg_float_voltage_set(chip, chip->vfloat_mv);
|
|
|
|
if (rc < 0) {
|
|
|
|
dev_err(chip->dev,
|
|
|
|
"Couldn't set float voltage rc = %d\n", rc);
|
|
|
|
return rc;
|
|
|
|
}
|
2014-06-30 22:02:34 +00:00
|
|
|
pr_smb(PR_STATUS, "set vfloat to %d\n", chip->vfloat_mv);
|
2014-02-20 01:27:12 +00:00
|
|
|
}
|
|
|
|
|
2014-12-26 20:25:54 +00:00
|
|
|
/* set the fast charge current compensation */
|
|
|
|
if (chip->fastchg_current_comp != -EINVAL) {
|
|
|
|
rc = smbchg_fastchg_current_comp_set(chip,
|
|
|
|
chip->fastchg_current_comp);
|
|
|
|
if (rc < 0) {
|
|
|
|
dev_err(chip->dev, "Couldn't set fastchg current comp rc = %d\n",
|
|
|
|
rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
pr_smb(PR_STATUS, "set fastchg current comp to %d\n",
|
|
|
|
chip->fastchg_current_comp);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* set the float voltage compensation */
|
|
|
|
if (chip->float_voltage_comp != -EINVAL) {
|
|
|
|
rc = smbchg_float_voltage_comp_set(chip,
|
|
|
|
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",
|
|
|
|
chip->float_voltage_comp);
|
|
|
|
}
|
|
|
|
|
2014-02-20 01:27:12 +00:00
|
|
|
/* set iterm */
|
|
|
|
if (chip->iterm_ma != -EINVAL) {
|
|
|
|
if (chip->iterm_disabled) {
|
|
|
|
dev_err(chip->dev, "Error: Both iterm_disabled and iterm_ma set\n");
|
|
|
|
return -EINVAL;
|
|
|
|
} else {
|
2015-09-17 22:07:37 +00:00
|
|
|
smbchg_iterm_set(chip, chip->iterm_ma);
|
2014-02-20 01:27:12 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* set the safety time voltage */
|
|
|
|
if (chip->safety_time != -EINVAL) {
|
|
|
|
reg = (chip->safety_time > 0 ? 0 : SFT_TIMER_DISABLE_BIT) |
|
|
|
|
(chip->prechg_safety_time > 0
|
|
|
|
? 0 : PRECHG_SFT_TIMER_DISABLE_BIT);
|
|
|
|
|
|
|
|
for (i = 0; i < ARRAY_SIZE(chg_time); i++) {
|
|
|
|
if (chip->safety_time <= chg_time[i]) {
|
|
|
|
reg |= i << SAFETY_TIME_MINUTES_SHIFT;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (i = 0; i < ARRAY_SIZE(prechg_time); i++) {
|
|
|
|
if (chip->prechg_safety_time <= prechg_time[i]) {
|
|
|
|
reg |= i;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
rc = smbchg_sec_masked_write(chip,
|
|
|
|
chip->chgr_base + SFT_CFG,
|
|
|
|
SFT_EN_MASK | SFT_TO_MASK |
|
|
|
|
(chip->prechg_safety_time > 0
|
|
|
|
? PRECHG_SFT_TO_MASK : 0), reg);
|
|
|
|
if (rc < 0) {
|
|
|
|
dev_err(chip->dev,
|
|
|
|
"Couldn't set safety timer rc = %d\n",
|
|
|
|
rc);
|
|
|
|
return rc;
|
|
|
|
}
|
2015-01-08 03:57:35 +00:00
|
|
|
chip->safety_timer_en = true;
|
|
|
|
} else {
|
|
|
|
rc = smbchg_read(chip, ®, chip->chgr_base + SFT_CFG, 1);
|
|
|
|
if (rc < 0)
|
|
|
|
dev_err(chip->dev, "Unable to read SFT_CFG rc = %d\n",
|
|
|
|
rc);
|
|
|
|
else if (!(reg & SFT_EN_MASK))
|
|
|
|
chip->safety_timer_en = true;
|
2014-02-20 01:27:12 +00:00
|
|
|
}
|
|
|
|
|
2015-01-08 04:39:21 +00:00
|
|
|
/* configure jeita temperature hard limit */
|
|
|
|
if (chip->jeita_temp_hard_limit >= 0) {
|
|
|
|
rc = smbchg_sec_masked_write(chip,
|
|
|
|
chip->chgr_base + CHGR_CCMP_CFG,
|
|
|
|
JEITA_TEMP_HARD_LIMIT_BIT,
|
|
|
|
chip->jeita_temp_hard_limit
|
|
|
|
? 0 : JEITA_TEMP_HARD_LIMIT_BIT);
|
|
|
|
if (rc < 0) {
|
|
|
|
dev_err(chip->dev,
|
|
|
|
"Couldn't set jeita temp hard limit rc = %d\n",
|
|
|
|
rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-08-18 21:02:15 +00:00
|
|
|
/* make the buck switch faster to prevent some vbus oscillation */
|
|
|
|
rc = smbchg_sec_masked_write(chip,
|
|
|
|
chip->usb_chgpth_base + TR_8OR32B,
|
|
|
|
BUCK_8_16_FREQ_BIT, 0);
|
|
|
|
if (rc < 0) {
|
|
|
|
dev_err(chip->dev, "Couldn't set buck frequency rc = %d\n", rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2014-02-20 01:27:12 +00:00
|
|
|
/* battery missing detection */
|
2014-07-29 23:39:57 +00:00
|
|
|
mask = BATT_MISSING_ALGO_BIT;
|
|
|
|
reg = chip->bmd_algo_disabled ? BATT_MISSING_ALGO_BIT : 0;
|
|
|
|
if (chip->bmd_pin_src < BPD_TYPE_DEFAULT) {
|
|
|
|
mask |= BMD_PIN_SRC_MASK;
|
|
|
|
reg |= chip->bmd_pin_src << PIN_SRC_SHIFT;
|
|
|
|
}
|
|
|
|
rc = smbchg_sec_masked_write(chip,
|
|
|
|
chip->bat_if_base + BM_CFG, mask, reg);
|
2014-02-20 01:27:12 +00:00
|
|
|
if (rc < 0) {
|
|
|
|
dev_err(chip->dev, "Couldn't set batt_missing config = %d\n",
|
|
|
|
rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2015-11-04 06:45:11 +00:00
|
|
|
if (chip->vchg_adc_channel != -EINVAL) {
|
|
|
|
/* configure and enable VCHG */
|
|
|
|
rc = smbchg_sec_masked_write(chip, chip->chgr_base + CHGR_CFG,
|
|
|
|
VCHG_INPUT_CURRENT_BIT | VCHG_EN_BIT,
|
|
|
|
VCHG_INPUT_CURRENT_BIT | VCHG_EN_BIT);
|
|
|
|
if (rc < 0) {
|
|
|
|
dev_err(chip->dev, "Couldn't set recharge rc = %d\n",
|
|
|
|
rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-10-29 18:01:40 +00:00
|
|
|
smbchg_charging_status_change(chip);
|
2015-11-26 09:38:11 +00:00
|
|
|
vote(chip->usb_suspend_votable, USER_EN_VOTER, !chip->chg_enabled, 0);
|
|
|
|
vote(chip->dc_suspend_votable, USER_EN_VOTER, !chip->chg_enabled, 0);
|
2015-08-05 22:05:21 +00:00
|
|
|
|
2014-02-20 01:27:12 +00:00
|
|
|
/* resume threshold */
|
|
|
|
if (chip->resume_delta_mv != -EINVAL) {
|
|
|
|
|
2014-09-26 22:23:56 +00:00
|
|
|
/*
|
|
|
|
* Configure only if the recharge threshold source is not
|
|
|
|
* fuel gauge ADC.
|
|
|
|
*/
|
|
|
|
if (!chip->chg_inhibit_source_fg) {
|
|
|
|
if (chip->resume_delta_mv < 100)
|
|
|
|
reg = CHG_INHIBIT_50MV_VAL;
|
|
|
|
else if (chip->resume_delta_mv < 200)
|
|
|
|
reg = CHG_INHIBIT_100MV_VAL;
|
|
|
|
else if (chip->resume_delta_mv < 300)
|
|
|
|
reg = CHG_INHIBIT_200MV_VAL;
|
|
|
|
else
|
|
|
|
reg = CHG_INHIBIT_300MV_VAL;
|
|
|
|
|
|
|
|
rc = smbchg_sec_masked_write(chip,
|
|
|
|
chip->chgr_base + CHG_INHIB_CFG_REG,
|
|
|
|
CHG_INHIBIT_MASK, reg);
|
|
|
|
if (rc < 0) {
|
|
|
|
dev_err(chip->dev, "Couldn't set inhibit val rc = %d\n",
|
|
|
|
rc);
|
|
|
|
return rc;
|
|
|
|
}
|
2014-02-20 01:27:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
rc = smbchg_sec_masked_write(chip,
|
|
|
|
chip->chgr_base + CHGR_CFG,
|
2015-11-26 07:39:46 +00:00
|
|
|
RCHG_LVL_BIT,
|
|
|
|
(chip->resume_delta_mv
|
|
|
|
< chip->tables.rchg_thr_mv)
|
2014-02-20 01:27:12 +00:00
|
|
|
? 0 : RCHG_LVL_BIT);
|
|
|
|
if (rc < 0) {
|
|
|
|
dev_err(chip->dev, "Couldn't set recharge rc = %d\n",
|
|
|
|
rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* DC path current settings */
|
2014-08-19 23:42:16 +00:00
|
|
|
if (chip->dc_psy_type != -EINVAL) {
|
2015-11-26 09:07:24 +00:00
|
|
|
rc = vote(chip->dc_icl_votable, PSY_ICL_VOTER, true,
|
|
|
|
chip->dc_target_current_ma);
|
2014-08-19 23:42:16 +00:00
|
|
|
if (rc < 0) {
|
2015-11-26 09:07:24 +00:00
|
|
|
dev_err(chip->dev,
|
|
|
|
"Couldn't vote for initial DC ICL rc=%d\n", rc);
|
2014-08-19 23:42:16 +00:00
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-02-20 01:27:12 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* on some devices the battery is powered via external sources which
|
|
|
|
* could raise its voltage above the float voltage. smbchargers go
|
|
|
|
* in to reverse boost in such a situation and the workaround is to
|
|
|
|
* disable float voltage compensation (note that the battery will appear
|
|
|
|
* hot/cold when powered via external source).
|
|
|
|
*/
|
|
|
|
if (chip->soft_vfloat_comp_disabled) {
|
2014-08-19 23:42:16 +00:00
|
|
|
rc = smbchg_sec_masked_write(chip, chip->chgr_base + CFG_AFVC,
|
2014-02-20 01:27:12 +00:00
|
|
|
VFLOAT_COMP_ENABLE_MASK, 0);
|
|
|
|
if (rc < 0) {
|
|
|
|
dev_err(chip->dev, "Couldn't disable soft vfloat rc = %d\n",
|
|
|
|
rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-11-26 07:54:21 +00:00
|
|
|
rc = vote(chip->fcc_votable, BATT_TYPE_FCC_VOTER, true,
|
|
|
|
chip->cfg_fastchg_current_ma);
|
2014-08-06 18:27:04 +00:00
|
|
|
if (rc < 0) {
|
2015-11-26 07:54:21 +00:00
|
|
|
dev_err(chip->dev, "Couldn't vote fastchg ma rc = %d\n", rc);
|
2014-08-06 18:27:04 +00:00
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2014-10-07 23:30:02 +00:00
|
|
|
rc = smbchg_read(chip, &chip->original_usbin_allowance,
|
|
|
|
chip->usb_chgpth_base + USBIN_CHGR_CFG, 1);
|
|
|
|
if (rc < 0)
|
|
|
|
dev_err(chip->dev, "Couldn't read usb allowance rc=%d\n", rc);
|
|
|
|
|
2014-10-30 21:59:48 +00:00
|
|
|
if (chip->wipower_dyn_icl_avail) {
|
|
|
|
rc = smbchg_wipower_ilim_config(chip,
|
|
|
|
&(chip->wipower_default.entries[0]));
|
|
|
|
if (rc < 0) {
|
|
|
|
dev_err(chip->dev, "Couldn't set default wipower ilim = %d\n",
|
|
|
|
rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
}
|
2015-08-03 19:11:45 +00:00
|
|
|
/* unsuspend dc path, it could be suspended by the bootloader */
|
|
|
|
rc = smbchg_dc_suspend(chip, 0);
|
|
|
|
if (rc < 0) {
|
2015-11-26 09:38:11 +00:00
|
|
|
dev_err(chip->dev, "Couldn't unsuspend dc path= %d\n", rc);
|
2015-08-03 19:11:45 +00:00
|
|
|
return rc;
|
|
|
|
}
|
2014-10-30 21:59:48 +00:00
|
|
|
|
2015-04-21 23:02:50 +00:00
|
|
|
if (chip->force_aicl_rerun)
|
|
|
|
rc = smbchg_aicl_config(chip);
|
|
|
|
|
2015-06-30 15:36:37 +00:00
|
|
|
if (chip->schg_version == QPNP_SCHG_LITE) {
|
|
|
|
/* enable OTG hiccup mode */
|
|
|
|
rc = smbchg_sec_masked_write(chip, chip->otg_base + OTG_OC_CFG,
|
|
|
|
HICCUP_ENABLED_BIT, HICCUP_ENABLED_BIT);
|
|
|
|
if (rc < 0)
|
|
|
|
dev_err(chip->dev, "Couldn't set OTG OC config rc = %d\n",
|
|
|
|
rc);
|
|
|
|
}
|
|
|
|
|
2015-08-13 06:11:46 +00:00
|
|
|
if (chip->wa_flags & SMBCHG_BATT_OV_WA)
|
|
|
|
batt_ov_wa_check(chip);
|
|
|
|
|
2015-11-26 09:42:32 +00:00
|
|
|
/* turn off AICL adc for improved accuracy */
|
|
|
|
rc = smbchg_sec_masked_write(chip,
|
|
|
|
chip->misc_base + MISC_TRIM_OPT_15_8, AICL_ADC_BIT, 0);
|
|
|
|
if (rc)
|
|
|
|
pr_err("Couldn't write to MISC_TRIM_OPTIONS_15_8 rc=%d\n",
|
|
|
|
rc);
|
|
|
|
|
2014-02-20 01:27:12 +00:00
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct of_device_id smbchg_match_table[] = {
|
|
|
|
{
|
2015-02-11 14:23:46 +00:00
|
|
|
.compatible = "qcom,qpnp-smbcharger",
|
2014-02-20 01:27:12 +00:00
|
|
|
},
|
|
|
|
{ },
|
|
|
|
};
|
|
|
|
|
|
|
|
#define DC_MA_MIN 300
|
|
|
|
#define DC_MA_MAX 2000
|
|
|
|
#define OF_PROP_READ(chip, prop, dt_property, retval, optional) \
|
|
|
|
do { \
|
|
|
|
if (retval) \
|
|
|
|
break; \
|
|
|
|
if (optional) \
|
2014-05-30 21:07:14 +00:00
|
|
|
prop = -EINVAL; \
|
2014-02-20 01:27:12 +00:00
|
|
|
\
|
|
|
|
retval = of_property_read_u32(chip->spmi->dev.of_node, \
|
|
|
|
"qcom," dt_property , \
|
2014-05-30 21:07:14 +00:00
|
|
|
&prop); \
|
2014-02-20 01:27:12 +00:00
|
|
|
\
|
|
|
|
if ((retval == -EINVAL) && optional) \
|
|
|
|
retval = 0; \
|
|
|
|
else if (retval) \
|
2014-06-30 22:02:34 +00:00
|
|
|
dev_err(chip->dev, "Error reading " #dt_property \
|
2014-02-20 01:27:12 +00:00
|
|
|
" property rc = %d\n", rc); \
|
|
|
|
} while (0)
|
|
|
|
|
2014-10-30 21:59:48 +00:00
|
|
|
#define ILIM_ENTRIES 3
|
|
|
|
#define VOLTAGE_RANGE_ENTRIES 2
|
|
|
|
#define RANGE_ENTRY (ILIM_ENTRIES + VOLTAGE_RANGE_ENTRIES)
|
|
|
|
static int smb_parse_wipower_map_dt(struct smbchg_chip *chip,
|
|
|
|
struct ilim_map *map, char *property)
|
|
|
|
{
|
|
|
|
struct device_node *node = chip->dev->of_node;
|
|
|
|
int total_elements, size;
|
|
|
|
struct property *prop;
|
|
|
|
const __be32 *data;
|
|
|
|
int num, i;
|
|
|
|
|
|
|
|
prop = of_find_property(node, property, &size);
|
|
|
|
if (!prop) {
|
|
|
|
dev_err(chip->dev, "%s missing\n", property);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
total_elements = size / sizeof(int);
|
|
|
|
if (total_elements % RANGE_ENTRY) {
|
|
|
|
dev_err(chip->dev, "%s table not in multiple of %d, total elements = %d\n",
|
|
|
|
property, RANGE_ENTRY, total_elements);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
data = prop->value;
|
|
|
|
num = total_elements / RANGE_ENTRY;
|
|
|
|
map->entries = devm_kzalloc(chip->dev,
|
|
|
|
num * sizeof(struct ilim_entry), GFP_KERNEL);
|
|
|
|
if (!map->entries) {
|
|
|
|
dev_err(chip->dev, "kzalloc failed for default ilim\n");
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
for (i = 0; i < num; i++) {
|
|
|
|
map->entries[i].vmin_uv = be32_to_cpup(data++);
|
|
|
|
map->entries[i].vmax_uv = be32_to_cpup(data++);
|
|
|
|
map->entries[i].icl_pt_ma = be32_to_cpup(data++);
|
|
|
|
map->entries[i].icl_lv_ma = be32_to_cpup(data++);
|
|
|
|
map->entries[i].icl_hv_ma = be32_to_cpup(data++);
|
|
|
|
}
|
|
|
|
map->num = num;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int smb_parse_wipower_dt(struct smbchg_chip *chip)
|
|
|
|
{
|
|
|
|
int rc = 0;
|
|
|
|
|
|
|
|
chip->wipower_dyn_icl_avail = false;
|
|
|
|
|
|
|
|
if (!chip->vadc_dev)
|
|
|
|
goto err;
|
|
|
|
|
|
|
|
rc = smb_parse_wipower_map_dt(chip, &chip->wipower_default,
|
|
|
|
"qcom,wipower-default-ilim-map");
|
|
|
|
if (rc) {
|
|
|
|
dev_err(chip->dev, "failed to parse wipower-pt-ilim-map rc = %d\n",
|
|
|
|
rc);
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
|
|
|
|
rc = smb_parse_wipower_map_dt(chip, &chip->wipower_pt,
|
|
|
|
"qcom,wipower-pt-ilim-map");
|
|
|
|
if (rc) {
|
|
|
|
dev_err(chip->dev, "failed to parse wipower-pt-ilim-map rc = %d\n",
|
|
|
|
rc);
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
|
|
|
|
rc = smb_parse_wipower_map_dt(chip, &chip->wipower_div2,
|
|
|
|
"qcom,wipower-div2-ilim-map");
|
|
|
|
if (rc) {
|
|
|
|
dev_err(chip->dev, "failed to parse wipower-div2-ilim-map rc = %d\n",
|
|
|
|
rc);
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
chip->wipower_dyn_icl_avail = true;
|
|
|
|
return 0;
|
|
|
|
err:
|
|
|
|
chip->wipower_default.num = 0;
|
|
|
|
chip->wipower_pt.num = 0;
|
|
|
|
chip->wipower_default.num = 0;
|
|
|
|
if (chip->wipower_default.entries)
|
|
|
|
devm_kfree(chip->dev, chip->wipower_default.entries);
|
|
|
|
if (chip->wipower_pt.entries)
|
|
|
|
devm_kfree(chip->dev, chip->wipower_pt.entries);
|
|
|
|
if (chip->wipower_div2.entries)
|
|
|
|
devm_kfree(chip->dev, chip->wipower_div2.entries);
|
|
|
|
chip->wipower_default.entries = NULL;
|
|
|
|
chip->wipower_pt.entries = NULL;
|
|
|
|
chip->wipower_div2.entries = NULL;
|
|
|
|
chip->vadc_dev = NULL;
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2015-02-07 23:40:12 +00:00
|
|
|
#define DEFAULT_VLED_MAX_UV 3500000
|
2015-06-02 18:09:39 +00:00
|
|
|
#define DEFAULT_FCC_MA 2000
|
2014-02-20 01:27:12 +00:00
|
|
|
static int smb_parse_dt(struct smbchg_chip *chip)
|
|
|
|
{
|
2015-02-07 23:40:12 +00:00
|
|
|
int rc = 0, ocp_thresh = -EINVAL;
|
2014-02-20 01:27:12 +00:00
|
|
|
struct device_node *node = chip->dev->of_node;
|
|
|
|
const char *dc_psy_type, *bpd;
|
|
|
|
|
|
|
|
if (!node) {
|
|
|
|
dev_err(chip->dev, "device tree info. missing\n");
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* read optional u32 properties */
|
2015-02-07 23:40:12 +00:00
|
|
|
OF_PROP_READ(chip, ocp_thresh,
|
|
|
|
"ibat-ocp-threshold-ua", rc, 1);
|
|
|
|
if (ocp_thresh >= 0)
|
|
|
|
smbchg_ibat_ocp_threshold_ua = ocp_thresh;
|
2014-05-30 21:07:14 +00:00
|
|
|
OF_PROP_READ(chip, chip->iterm_ma, "iterm-ma", rc, 1);
|
2015-11-26 07:54:21 +00:00
|
|
|
OF_PROP_READ(chip, chip->cfg_fastchg_current_ma,
|
2014-08-06 18:27:04 +00:00
|
|
|
"fastchg-current-ma", rc, 1);
|
2015-11-26 07:54:21 +00:00
|
|
|
if (chip->cfg_fastchg_current_ma == -EINVAL)
|
|
|
|
chip->cfg_fastchg_current_ma = DEFAULT_FCC_MA;
|
2014-05-30 21:07:14 +00:00
|
|
|
OF_PROP_READ(chip, chip->vfloat_mv, "float-voltage-mv", rc, 1);
|
|
|
|
OF_PROP_READ(chip, chip->safety_time, "charging-timeout-mins", rc, 1);
|
2015-02-07 23:40:12 +00:00
|
|
|
OF_PROP_READ(chip, chip->vled_max_uv, "vled-max-uv", rc, 1);
|
|
|
|
if (chip->vled_max_uv < 0)
|
|
|
|
chip->vled_max_uv = DEFAULT_VLED_MAX_UV;
|
2014-07-31 22:10:07 +00:00
|
|
|
OF_PROP_READ(chip, chip->rpara_uohm, "rparasitic-uohm", rc, 1);
|
2015-02-07 23:40:12 +00:00
|
|
|
if (chip->rpara_uohm < 0)
|
|
|
|
chip->rpara_uohm = 0;
|
2014-05-30 21:07:14 +00:00
|
|
|
OF_PROP_READ(chip, chip->prechg_safety_time, "precharging-timeout-mins",
|
2014-02-20 01:27:12 +00:00
|
|
|
rc, 1);
|
2014-12-26 20:25:54 +00:00
|
|
|
OF_PROP_READ(chip, chip->fastchg_current_comp, "fastchg-current-comp",
|
|
|
|
rc, 1);
|
|
|
|
OF_PROP_READ(chip, chip->float_voltage_comp, "float-voltage-comp",
|
|
|
|
rc, 1);
|
2014-02-20 01:27:12 +00:00
|
|
|
if (chip->safety_time != -EINVAL &&
|
|
|
|
(chip->safety_time > chg_time[ARRAY_SIZE(chg_time) - 1])) {
|
|
|
|
dev_err(chip->dev, "Bad charging-timeout-mins %d\n",
|
|
|
|
chip->safety_time);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
if (chip->prechg_safety_time != -EINVAL &&
|
|
|
|
(chip->prechg_safety_time >
|
|
|
|
prechg_time[ARRAY_SIZE(prechg_time) - 1])) {
|
|
|
|
dev_err(chip->dev, "Bad precharging-timeout-mins %d\n",
|
|
|
|
chip->prechg_safety_time);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
2014-05-30 21:07:14 +00:00
|
|
|
OF_PROP_READ(chip, chip->resume_delta_mv, "resume-delta-mv", rc, 1);
|
|
|
|
OF_PROP_READ(chip, chip->parallel.min_current_thr_ma,
|
|
|
|
"parallel-usb-min-current-ma", rc, 1);
|
2014-08-06 18:27:04 +00:00
|
|
|
OF_PROP_READ(chip, chip->parallel.min_9v_current_thr_ma,
|
|
|
|
"parallel-usb-9v-min-current-ma", rc, 1);
|
2014-10-09 01:36:33 +00:00
|
|
|
OF_PROP_READ(chip, chip->parallel.allowed_lowering_ma,
|
|
|
|
"parallel-allowed-lowering-ma", rc, 1);
|
2014-10-15 17:41:50 +00:00
|
|
|
if (chip->parallel.min_current_thr_ma != -EINVAL
|
|
|
|
&& chip->parallel.min_9v_current_thr_ma != -EINVAL)
|
2014-05-30 21:07:14 +00:00
|
|
|
chip->parallel.avail = true;
|
2015-10-23 00:03:13 +00:00
|
|
|
/*
|
|
|
|
* use the dt values if they exist, otherwise do not touch the params
|
|
|
|
*/
|
|
|
|
of_property_read_u32(chip->spmi->dev.of_node,
|
|
|
|
"qcom,parallel-main-chg-fcc-percent",
|
|
|
|
&smbchg_main_chg_fcc_percent);
|
|
|
|
of_property_read_u32(chip->spmi->dev.of_node,
|
|
|
|
"qcom,parallel-main-chg-icl-percent",
|
|
|
|
&smbchg_main_chg_icl_percent);
|
2014-08-06 18:27:04 +00:00
|
|
|
pr_smb(PR_STATUS, "parallel usb thr: %d, 9v thr: %d\n",
|
|
|
|
chip->parallel.min_current_thr_ma,
|
|
|
|
chip->parallel.min_9v_current_thr_ma);
|
2015-01-08 04:39:21 +00:00
|
|
|
OF_PROP_READ(chip, chip->jeita_temp_hard_limit,
|
|
|
|
"jeita-temp-hard-limit", rc, 1);
|
2015-11-26 09:51:36 +00:00
|
|
|
OF_PROP_READ(chip, chip->aicl_rerun_period_s,
|
|
|
|
"aicl-rerun-period-s", rc, 1);
|
2015-11-04 06:45:11 +00:00
|
|
|
OF_PROP_READ(chip, chip->vchg_adc_channel,
|
|
|
|
"vchg-adc-channel-id", rc, 1);
|
2014-02-20 01:27:12 +00:00
|
|
|
|
|
|
|
/* read boolean configuration properties */
|
2014-10-29 18:01:40 +00:00
|
|
|
chip->use_vfloat_adjustments = of_property_read_bool(node,
|
|
|
|
"qcom,autoadjust-vfloat");
|
2014-02-20 01:27:12 +00:00
|
|
|
chip->bmd_algo_disabled = of_property_read_bool(node,
|
|
|
|
"qcom,bmd-algo-disabled");
|
|
|
|
chip->iterm_disabled = of_property_read_bool(node,
|
|
|
|
"qcom,iterm-disabled");
|
|
|
|
chip->soft_vfloat_comp_disabled = of_property_read_bool(node,
|
|
|
|
"qcom,soft-vfloat-comp-disabled");
|
|
|
|
chip->chg_enabled = !(of_property_read_bool(node,
|
|
|
|
"qcom,charging-disabled"));
|
2014-07-09 21:51:58 +00:00
|
|
|
chip->charge_unknown_battery = of_property_read_bool(node,
|
|
|
|
"qcom,charge-unknown-battery");
|
2014-11-18 21:44:25 +00:00
|
|
|
chip->chg_inhibit_en = of_property_read_bool(node,
|
|
|
|
"qcom,chg-inhibit-en");
|
2014-09-26 22:23:56 +00:00
|
|
|
chip->chg_inhibit_source_fg = of_property_read_bool(node,
|
|
|
|
"qcom,chg-inhibit-fg");
|
2015-02-26 00:01:41 +00:00
|
|
|
chip->low_volt_dcin = of_property_read_bool(node,
|
|
|
|
"qcom,low-volt-dcin");
|
2015-04-21 23:02:50 +00:00
|
|
|
chip->force_aicl_rerun = of_property_read_bool(node,
|
|
|
|
"qcom,force-aicl-rerun");
|
2014-02-20 01:27:12 +00:00
|
|
|
|
|
|
|
/* parse the battery missing detection pin source */
|
|
|
|
rc = of_property_read_string(chip->spmi->dev.of_node,
|
|
|
|
"qcom,bmd-pin-src", &bpd);
|
|
|
|
if (rc) {
|
|
|
|
/* Select BAT_THM as default BPD scheme */
|
2014-07-29 23:39:57 +00:00
|
|
|
chip->bmd_pin_src = BPD_TYPE_DEFAULT;
|
2014-02-20 01:27:12 +00:00
|
|
|
rc = 0;
|
|
|
|
} else {
|
|
|
|
chip->bmd_pin_src = get_bpd(bpd);
|
|
|
|
if (chip->bmd_pin_src < 0) {
|
2014-06-30 22:02:34 +00:00
|
|
|
dev_err(chip->dev,
|
|
|
|
"failed to determine bpd schema %d\n", rc);
|
2014-02-20 01:27:12 +00:00
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* parse the dc power supply configuration */
|
|
|
|
rc = of_property_read_string(node, "qcom,dc-psy-type", &dc_psy_type);
|
|
|
|
if (rc) {
|
|
|
|
chip->dc_psy_type = -EINVAL;
|
|
|
|
rc = 0;
|
|
|
|
} else {
|
|
|
|
if (strcmp(dc_psy_type, "Mains") == 0)
|
|
|
|
chip->dc_psy_type = POWER_SUPPLY_TYPE_MAINS;
|
|
|
|
else if (strcmp(dc_psy_type, "Wireless") == 0)
|
|
|
|
chip->dc_psy_type = POWER_SUPPLY_TYPE_WIRELESS;
|
2014-09-29 20:27:23 +00:00
|
|
|
else if (strcmp(dc_psy_type, "Wipower") == 0)
|
|
|
|
chip->dc_psy_type = POWER_SUPPLY_TYPE_WIPOWER;
|
2014-02-20 01:27:12 +00:00
|
|
|
}
|
|
|
|
if (chip->dc_psy_type != -EINVAL) {
|
2014-05-30 21:07:14 +00:00
|
|
|
OF_PROP_READ(chip, chip->dc_target_current_ma,
|
|
|
|
"dc-psy-ma", rc, 0);
|
2014-02-20 01:27:12 +00:00
|
|
|
if (rc)
|
|
|
|
return rc;
|
|
|
|
if (chip->dc_target_current_ma < DC_MA_MIN
|
|
|
|
|| chip->dc_target_current_ma > DC_MA_MAX) {
|
|
|
|
dev_err(chip->dev, "Bad dc mA %d\n",
|
|
|
|
chip->dc_target_current_ma);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-10-30 21:59:48 +00:00
|
|
|
if (chip->dc_psy_type == POWER_SUPPLY_TYPE_WIPOWER)
|
|
|
|
smb_parse_wipower_dt(chip);
|
|
|
|
|
2014-02-20 01:27:12 +00:00
|
|
|
/* read the bms power supply name */
|
|
|
|
rc = of_property_read_string(node, "qcom,bms-psy-name",
|
|
|
|
&chip->bms_psy_name);
|
|
|
|
if (rc)
|
|
|
|
chip->bms_psy_name = NULL;
|
|
|
|
|
2015-02-07 23:40:12 +00:00
|
|
|
/* read the battery power supply name */
|
2014-02-20 01:27:12 +00:00
|
|
|
rc = of_property_read_string(node, "qcom,battery-psy-name",
|
|
|
|
&chip->battery_psy_name);
|
|
|
|
if (rc)
|
|
|
|
chip->battery_psy_name = "battery";
|
|
|
|
|
2015-03-10 08:42:22 +00:00
|
|
|
/* Get the charger led support property */
|
|
|
|
chip->cfg_chg_led_sw_ctrl =
|
|
|
|
of_property_read_bool(node, "qcom,chg-led-sw-controls");
|
|
|
|
chip->cfg_chg_led_support =
|
|
|
|
of_property_read_bool(node, "qcom,chg-led-support");
|
|
|
|
|
2014-02-20 01:27:12 +00:00
|
|
|
if (of_find_property(node, "qcom,thermal-mitigation",
|
|
|
|
&chip->thermal_levels)) {
|
|
|
|
chip->thermal_mitigation = devm_kzalloc(chip->dev,
|
|
|
|
chip->thermal_levels,
|
|
|
|
GFP_KERNEL);
|
|
|
|
|
|
|
|
if (chip->thermal_mitigation == NULL) {
|
2014-06-30 22:02:34 +00:00
|
|
|
dev_err(chip->dev, "thermal mitigation kzalloc() failed.\n");
|
2014-02-20 01:27:12 +00:00
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
|
|
|
chip->thermal_levels /= sizeof(int);
|
|
|
|
rc = of_property_read_u32_array(node,
|
|
|
|
"qcom,thermal-mitigation",
|
|
|
|
chip->thermal_mitigation, chip->thermal_levels);
|
|
|
|
if (rc) {
|
2014-06-30 22:02:34 +00:00
|
|
|
dev_err(chip->dev,
|
|
|
|
"Couldn't read threm limits rc = %d\n", rc);
|
2014-02-20 01:27:12 +00:00
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-11-26 07:20:42 +00:00
|
|
|
chip->skip_usb_notification
|
|
|
|
= of_property_read_bool(node,
|
|
|
|
"qcom,skip-usb-notification");
|
2016-03-25 17:56:39 +00:00
|
|
|
|
|
|
|
chip->cfg_override_usb_current = of_property_read_bool(node,
|
|
|
|
"qcom,override-usb-current");
|
|
|
|
|
2014-02-20 01:27:12 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
#define SUBTYPE_REG 0x5
|
|
|
|
#define SMBCHG_CHGR_SUBTYPE 0x1
|
|
|
|
#define SMBCHG_OTG_SUBTYPE 0x8
|
|
|
|
#define SMBCHG_BAT_IF_SUBTYPE 0x3
|
|
|
|
#define SMBCHG_USB_CHGPTH_SUBTYPE 0x4
|
|
|
|
#define SMBCHG_DC_CHGPTH_SUBTYPE 0x5
|
|
|
|
#define SMBCHG_MISC_SUBTYPE 0x7
|
2015-02-11 14:23:46 +00:00
|
|
|
#define SMBCHG_LITE_CHGR_SUBTYPE 0x51
|
|
|
|
#define SMBCHG_LITE_OTG_SUBTYPE 0x58
|
|
|
|
#define SMBCHG_LITE_BAT_IF_SUBTYPE 0x53
|
|
|
|
#define SMBCHG_LITE_USB_CHGPTH_SUBTYPE 0x54
|
|
|
|
#define SMBCHG_LITE_DC_CHGPTH_SUBTYPE 0x55
|
|
|
|
#define SMBCHG_LITE_MISC_SUBTYPE 0x57
|
2014-06-17 23:08:46 +00:00
|
|
|
#define REQUEST_IRQ(chip, resource, irq_num, irq_name, irq_handler, flags, rc)\
|
2014-02-20 01:27:12 +00:00
|
|
|
do { \
|
|
|
|
irq_num = spmi_get_irq_byname(chip->spmi, \
|
|
|
|
resource, irq_name); \
|
|
|
|
if (irq_num < 0) { \
|
|
|
|
dev_err(chip->dev, "Unable to get " irq_name " irq\n"); \
|
|
|
|
return -ENXIO; \
|
|
|
|
} \
|
2014-05-30 21:07:14 +00:00
|
|
|
rc = devm_request_threaded_irq(chip->dev, \
|
2014-06-17 23:08:46 +00:00
|
|
|
irq_num, NULL, irq_handler, flags, irq_name, \
|
|
|
|
chip); \
|
2014-02-20 01:27:12 +00:00
|
|
|
if (rc < 0) { \
|
|
|
|
dev_err(chip->dev, "Unable to request " irq_name " irq: %d\n",\
|
|
|
|
rc); \
|
|
|
|
return -ENXIO; \
|
|
|
|
} \
|
|
|
|
} while (0)
|
|
|
|
|
|
|
|
static int smbchg_request_irqs(struct smbchg_chip *chip)
|
|
|
|
{
|
|
|
|
int rc = 0;
|
|
|
|
struct resource *resource;
|
|
|
|
struct spmi_resource *spmi_resource;
|
|
|
|
u8 subtype;
|
|
|
|
struct spmi_device *spmi = chip->spmi;
|
2014-06-17 23:08:46 +00:00
|
|
|
unsigned long flags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING
|
|
|
|
| IRQF_ONESHOT;
|
2014-02-20 01:27:12 +00:00
|
|
|
|
|
|
|
spmi_for_each_container_dev(spmi_resource, chip->spmi) {
|
|
|
|
if (!spmi_resource) {
|
|
|
|
dev_err(chip->dev, "spmi resource absent\n");
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
resource = spmi_get_resource(spmi, spmi_resource,
|
|
|
|
IORESOURCE_MEM, 0);
|
|
|
|
if (!(resource && resource->start)) {
|
|
|
|
dev_err(chip->dev, "node %s IO resource absent!\n",
|
|
|
|
spmi->dev.of_node->full_name);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
rc = smbchg_read(chip, &subtype,
|
|
|
|
resource->start + SUBTYPE_REG, 1);
|
|
|
|
if (rc) {
|
|
|
|
dev_err(chip->dev, "Peripheral subtype read failed rc=%d\n",
|
|
|
|
rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (subtype) {
|
|
|
|
case SMBCHG_CHGR_SUBTYPE:
|
2015-02-11 14:23:46 +00:00
|
|
|
case SMBCHG_LITE_CHGR_SUBTYPE:
|
power: qpnp-smbcharger: add workaround for UVLO resets
When using the PMI8994 smbcharger, usb removal or suspend can cause
an under voltage lock out (UVLO) due to a hardware bug.
The condition for the UVLO to happen is when the whole system is running
off the charger buck when the USB is either removed or suspended. This
case can easily happen when the system is running at medium loads
(300-400 mA) and the battery is considered missing, overtemp, or
overvoltage by the charger hardware.
When this happens, the charger stops trying to pump current into the
battery, and instead just draws power from the active charge path
(USB in ths case). However, when the input charge path is removed, either
by suspending it through software or by physically removing the USB
cable, the battery FET does not turn on fast enough in hardware to
supplement the system load. This causes a droop on VPH power, manifesting
itself as an UVLO reset.
The work around for this issue is to ensure the battery is supplementing
the current before a charge path removal event happens. Implement this by
forcing USB input current limit to be 100mA whenever the device is not
charging. More specifically, whenever a charger p2f-thresh interrupt
(pre-to-fastcharge event, charging starts) or a battery interface
chg-error interrupt (battery missing/overtemp/overvolt) happens, check
the charging status and if the device is not charging, force the charger
to only draw 100mA.
If the input current limit is at 100mA, the system will be drawing
current from the battery as long as the system load is higher than
100mA. Thus, when the USB is removed, the battery will already be
supplementing the system load, and there will not be an UVLO reset.
There are a few limitations to this work around.
1. If the system load is less than 100mA when the input charge
path is removed, there may still be a droop on VPH power due
to the battery FET still being open. However, this should not
cause an UVLO reset because the system load will not cause a
large enough droop to cause a UVLO reset.
2. If the input is removed at the exact instant a charger error
occurs (battery goes missing, battery becomes overtemp,
battery becomes overvoltage), and the interrupt does not get
serviced before the input is removed, an UVLO reset can still
happen. However, this is a hardware limitation and there is
no solution for this at this time.
3. Currently, the workaround for the DC input charge path is not
supported. Until this is implemented, DC charging and wipower
will be impacted.
CRs-Fixed: 667489
Change-Id: I1a185d126e1e6bb851e5981f8b49072e8f8c6f68
Signed-off-by: Xiaozhe Shi <xiaozhes@codeaurora.org>
2014-06-12 00:35:01 +00:00
|
|
|
REQUEST_IRQ(chip, spmi_resource, chip->chg_error_irq,
|
2014-06-17 23:08:46 +00:00
|
|
|
"chg-error", chg_error_handler, flags, rc);
|
2014-02-20 01:27:12 +00:00
|
|
|
REQUEST_IRQ(chip, spmi_resource, chip->taper_irq,
|
2014-08-06 18:27:04 +00:00
|
|
|
"chg-taper-thr", taper_handler,
|
|
|
|
(IRQF_TRIGGER_RISING | IRQF_ONESHOT), rc);
|
|
|
|
disable_irq_nosync(chip->taper_irq);
|
2014-02-20 01:27:12 +00:00
|
|
|
REQUEST_IRQ(chip, spmi_resource, chip->chg_term_irq,
|
2015-03-05 23:24:25 +00:00
|
|
|
"chg-tcc-thr", chg_term_handler,
|
|
|
|
(IRQF_TRIGGER_RISING | IRQF_ONESHOT), rc);
|
2014-02-20 01:27:12 +00:00
|
|
|
REQUEST_IRQ(chip, spmi_resource, chip->recharge_irq,
|
2014-06-17 23:08:46 +00:00
|
|
|
"chg-rechg-thr", recharge_handler, flags, rc);
|
2014-05-28 02:07:48 +00:00
|
|
|
REQUEST_IRQ(chip, spmi_resource, chip->fastchg_irq,
|
2014-06-17 23:08:46 +00:00
|
|
|
"chg-p2f-thr", fastchg_handler, flags, rc);
|
2014-05-28 23:21:55 +00:00
|
|
|
enable_irq_wake(chip->chg_term_irq);
|
power: qpnp-smbcharger: add workaround for UVLO resets
When using the PMI8994 smbcharger, usb removal or suspend can cause
an under voltage lock out (UVLO) due to a hardware bug.
The condition for the UVLO to happen is when the whole system is running
off the charger buck when the USB is either removed or suspended. This
case can easily happen when the system is running at medium loads
(300-400 mA) and the battery is considered missing, overtemp, or
overvoltage by the charger hardware.
When this happens, the charger stops trying to pump current into the
battery, and instead just draws power from the active charge path
(USB in ths case). However, when the input charge path is removed, either
by suspending it through software or by physically removing the USB
cable, the battery FET does not turn on fast enough in hardware to
supplement the system load. This causes a droop on VPH power, manifesting
itself as an UVLO reset.
The work around for this issue is to ensure the battery is supplementing
the current before a charge path removal event happens. Implement this by
forcing USB input current limit to be 100mA whenever the device is not
charging. More specifically, whenever a charger p2f-thresh interrupt
(pre-to-fastcharge event, charging starts) or a battery interface
chg-error interrupt (battery missing/overtemp/overvolt) happens, check
the charging status and if the device is not charging, force the charger
to only draw 100mA.
If the input current limit is at 100mA, the system will be drawing
current from the battery as long as the system load is higher than
100mA. Thus, when the USB is removed, the battery will already be
supplementing the system load, and there will not be an UVLO reset.
There are a few limitations to this work around.
1. If the system load is less than 100mA when the input charge
path is removed, there may still be a droop on VPH power due
to the battery FET still being open. However, this should not
cause an UVLO reset because the system load will not cause a
large enough droop to cause a UVLO reset.
2. If the input is removed at the exact instant a charger error
occurs (battery goes missing, battery becomes overtemp,
battery becomes overvoltage), and the interrupt does not get
serviced before the input is removed, an UVLO reset can still
happen. However, this is a hardware limitation and there is
no solution for this at this time.
3. Currently, the workaround for the DC input charge path is not
supported. Until this is implemented, DC charging and wipower
will be impacted.
CRs-Fixed: 667489
Change-Id: I1a185d126e1e6bb851e5981f8b49072e8f8c6f68
Signed-off-by: Xiaozhe Shi <xiaozhes@codeaurora.org>
2014-06-12 00:35:01 +00:00
|
|
|
enable_irq_wake(chip->chg_error_irq);
|
2014-05-28 02:07:48 +00:00
|
|
|
enable_irq_wake(chip->fastchg_irq);
|
2014-02-20 01:27:12 +00:00
|
|
|
break;
|
|
|
|
case SMBCHG_BAT_IF_SUBTYPE:
|
2015-02-11 14:23:46 +00:00
|
|
|
case SMBCHG_LITE_BAT_IF_SUBTYPE:
|
2014-02-20 01:27:12 +00:00
|
|
|
REQUEST_IRQ(chip, spmi_resource, chip->batt_hot_irq,
|
2014-06-17 23:08:46 +00:00
|
|
|
"batt-hot", batt_hot_handler, flags, rc);
|
2014-02-20 01:27:12 +00:00
|
|
|
REQUEST_IRQ(chip, spmi_resource, chip->batt_warm_irq,
|
2014-06-17 23:08:46 +00:00
|
|
|
"batt-warm", batt_warm_handler, flags, rc);
|
2014-02-20 01:27:12 +00:00
|
|
|
REQUEST_IRQ(chip, spmi_resource, chip->batt_cool_irq,
|
2014-06-17 23:08:46 +00:00
|
|
|
"batt-cool", batt_cool_handler, flags, rc);
|
2014-02-20 01:27:12 +00:00
|
|
|
REQUEST_IRQ(chip, spmi_resource, chip->batt_cold_irq,
|
2014-06-17 23:08:46 +00:00
|
|
|
"batt-cold", batt_cold_handler, flags, rc);
|
2014-02-20 01:27:12 +00:00
|
|
|
REQUEST_IRQ(chip, spmi_resource, chip->batt_missing_irq,
|
2014-06-17 23:08:46 +00:00
|
|
|
"batt-missing", batt_pres_handler, flags, rc);
|
2014-02-20 01:27:12 +00:00
|
|
|
REQUEST_IRQ(chip, spmi_resource, chip->vbat_low_irq,
|
2014-06-17 23:08:46 +00:00
|
|
|
"batt-low", vbat_low_handler, flags, rc);
|
2014-05-28 23:21:55 +00:00
|
|
|
enable_irq_wake(chip->batt_hot_irq);
|
|
|
|
enable_irq_wake(chip->batt_warm_irq);
|
|
|
|
enable_irq_wake(chip->batt_cool_irq);
|
|
|
|
enable_irq_wake(chip->batt_cold_irq);
|
|
|
|
enable_irq_wake(chip->batt_missing_irq);
|
|
|
|
enable_irq_wake(chip->vbat_low_irq);
|
2014-02-20 01:27:12 +00:00
|
|
|
break;
|
|
|
|
case SMBCHG_USB_CHGPTH_SUBTYPE:
|
2015-02-11 14:23:46 +00:00
|
|
|
case SMBCHG_LITE_USB_CHGPTH_SUBTYPE:
|
2014-02-20 01:27:12 +00:00
|
|
|
REQUEST_IRQ(chip, spmi_resource, chip->usbin_uv_irq,
|
2015-05-26 19:07:10 +00:00
|
|
|
"usbin-uv", usbin_uv_handler,
|
|
|
|
flags | IRQF_EARLY_RESUME, rc);
|
2015-02-04 03:07:09 +00:00
|
|
|
REQUEST_IRQ(chip, spmi_resource, chip->usbin_ov_irq,
|
|
|
|
"usbin-ov", usbin_ov_handler, flags, rc);
|
2014-02-20 01:27:12 +00:00
|
|
|
REQUEST_IRQ(chip, spmi_resource, chip->src_detect_irq,
|
2014-06-17 23:08:46 +00:00
|
|
|
"usbin-src-det",
|
|
|
|
src_detect_handler, flags, rc);
|
2014-05-30 21:07:14 +00:00
|
|
|
REQUEST_IRQ(chip, spmi_resource, chip->aicl_done_irq,
|
2014-06-17 23:08:46 +00:00
|
|
|
"aicl-done",
|
2015-10-13 20:56:19 +00:00
|
|
|
aicl_done_handler,
|
|
|
|
(IRQF_TRIGGER_RISING | IRQF_ONESHOT),
|
|
|
|
rc);
|
2015-02-11 14:23:46 +00:00
|
|
|
if (chip->schg_version != QPNP_SCHG_LITE) {
|
|
|
|
REQUEST_IRQ(chip, spmi_resource,
|
|
|
|
chip->otg_fail_irq, "otg-fail",
|
|
|
|
otg_fail_handler, flags, rc);
|
|
|
|
REQUEST_IRQ(chip, spmi_resource,
|
|
|
|
chip->otg_oc_irq, "otg-oc",
|
|
|
|
otg_oc_handler,
|
|
|
|
(IRQF_TRIGGER_RISING | IRQF_ONESHOT),
|
|
|
|
rc);
|
|
|
|
REQUEST_IRQ(chip, spmi_resource,
|
|
|
|
chip->usbid_change_irq, "usbid-change",
|
|
|
|
usbid_change_handler,
|
|
|
|
(IRQF_TRIGGER_FALLING | IRQF_ONESHOT),
|
|
|
|
rc);
|
|
|
|
enable_irq_wake(chip->otg_oc_irq);
|
|
|
|
enable_irq_wake(chip->usbid_change_irq);
|
|
|
|
enable_irq_wake(chip->otg_fail_irq);
|
|
|
|
}
|
2014-05-28 23:21:55 +00:00
|
|
|
enable_irq_wake(chip->usbin_uv_irq);
|
2015-02-04 03:07:09 +00:00
|
|
|
enable_irq_wake(chip->usbin_ov_irq);
|
2014-05-28 23:21:55 +00:00
|
|
|
enable_irq_wake(chip->src_detect_irq);
|
2014-12-16 21:30:42 +00:00
|
|
|
if (chip->parallel.avail && chip->usb_present) {
|
|
|
|
rc = enable_irq_wake(chip->aicl_done_irq);
|
|
|
|
chip->enable_aicl_wake = true;
|
|
|
|
}
|
2014-02-20 01:27:12 +00:00
|
|
|
break;
|
|
|
|
case SMBCHG_DC_CHGPTH_SUBTYPE:
|
2015-02-11 14:23:46 +00:00
|
|
|
case SMBCHG_LITE_DC_CHGPTH_SUBTYPE:
|
2014-02-20 01:27:12 +00:00
|
|
|
REQUEST_IRQ(chip, spmi_resource, chip->dcin_uv_irq,
|
2014-06-17 23:08:46 +00:00
|
|
|
"dcin-uv", dcin_uv_handler, flags, rc);
|
2014-05-28 23:21:55 +00:00
|
|
|
enable_irq_wake(chip->dcin_uv_irq);
|
2014-02-20 01:27:12 +00:00
|
|
|
break;
|
|
|
|
case SMBCHG_MISC_SUBTYPE:
|
2015-02-11 14:23:46 +00:00
|
|
|
case SMBCHG_LITE_MISC_SUBTYPE:
|
2014-02-20 01:27:12 +00:00
|
|
|
REQUEST_IRQ(chip, spmi_resource, chip->power_ok_irq,
|
2014-06-17 23:08:46 +00:00
|
|
|
"power-ok", power_ok_handler, flags, rc);
|
2014-02-20 01:27:12 +00:00
|
|
|
REQUEST_IRQ(chip, spmi_resource, chip->chg_hot_irq,
|
2014-06-17 23:08:46 +00:00
|
|
|
"temp-shutdown", chg_hot_handler, flags, rc);
|
2014-02-20 01:27:12 +00:00
|
|
|
REQUEST_IRQ(chip, spmi_resource,
|
2015-07-31 09:29:44 +00:00
|
|
|
chip->wdog_timeout_irq,
|
|
|
|
"wdog-timeout",
|
|
|
|
wdog_timeout_handler, flags, rc);
|
2014-05-28 23:21:55 +00:00
|
|
|
enable_irq_wake(chip->chg_hot_irq);
|
2015-07-31 09:29:44 +00:00
|
|
|
enable_irq_wake(chip->wdog_timeout_irq);
|
2014-02-20 01:27:12 +00:00
|
|
|
break;
|
|
|
|
case SMBCHG_OTG_SUBTYPE:
|
|
|
|
break;
|
2015-02-11 14:23:46 +00:00
|
|
|
case SMBCHG_LITE_OTG_SUBTYPE:
|
|
|
|
REQUEST_IRQ(chip, spmi_resource,
|
|
|
|
chip->usbid_change_irq, "usbid-change",
|
|
|
|
usbid_change_handler,
|
|
|
|
(IRQF_TRIGGER_FALLING | IRQF_ONESHOT),
|
|
|
|
rc);
|
|
|
|
REQUEST_IRQ(chip, spmi_resource,
|
|
|
|
chip->otg_oc_irq, "otg-oc",
|
|
|
|
otg_oc_handler,
|
|
|
|
(IRQF_TRIGGER_RISING | IRQF_ONESHOT), rc);
|
|
|
|
REQUEST_IRQ(chip, spmi_resource,
|
|
|
|
chip->otg_fail_irq, "otg-fail",
|
|
|
|
otg_fail_handler, flags, rc);
|
|
|
|
enable_irq_wake(chip->usbid_change_irq);
|
|
|
|
enable_irq_wake(chip->otg_oc_irq);
|
|
|
|
enable_irq_wake(chip->otg_fail_irq);
|
|
|
|
break;
|
2014-02-20 01:27:12 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
#define REQUIRE_BASE(chip, base, rc) \
|
|
|
|
do { \
|
|
|
|
if (!rc && !chip->base) { \
|
|
|
|
dev_err(chip->dev, "Missing " #base "\n"); \
|
|
|
|
rc = -EINVAL; \
|
|
|
|
} \
|
|
|
|
} while (0)
|
|
|
|
|
|
|
|
static int smbchg_parse_peripherals(struct smbchg_chip *chip)
|
|
|
|
{
|
|
|
|
int rc = 0;
|
|
|
|
struct resource *resource;
|
|
|
|
struct spmi_resource *spmi_resource;
|
|
|
|
u8 subtype;
|
|
|
|
struct spmi_device *spmi = chip->spmi;
|
|
|
|
|
|
|
|
spmi_for_each_container_dev(spmi_resource, chip->spmi) {
|
|
|
|
if (!spmi_resource) {
|
|
|
|
dev_err(chip->dev, "spmi resource absent\n");
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
resource = spmi_get_resource(spmi, spmi_resource,
|
|
|
|
IORESOURCE_MEM, 0);
|
|
|
|
if (!(resource && resource->start)) {
|
|
|
|
dev_err(chip->dev, "node %s IO resource absent!\n",
|
|
|
|
spmi->dev.of_node->full_name);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
rc = smbchg_read(chip, &subtype,
|
|
|
|
resource->start + SUBTYPE_REG, 1);
|
|
|
|
if (rc) {
|
|
|
|
dev_err(chip->dev, "Peripheral subtype read failed rc=%d\n",
|
|
|
|
rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (subtype) {
|
|
|
|
case SMBCHG_CHGR_SUBTYPE:
|
2015-02-11 14:23:46 +00:00
|
|
|
case SMBCHG_LITE_CHGR_SUBTYPE:
|
2014-02-20 01:27:12 +00:00
|
|
|
chip->chgr_base = resource->start;
|
|
|
|
break;
|
|
|
|
case SMBCHG_BAT_IF_SUBTYPE:
|
2015-02-11 14:23:46 +00:00
|
|
|
case SMBCHG_LITE_BAT_IF_SUBTYPE:
|
2014-02-20 01:27:12 +00:00
|
|
|
chip->bat_if_base = resource->start;
|
|
|
|
break;
|
|
|
|
case SMBCHG_USB_CHGPTH_SUBTYPE:
|
2015-02-11 14:23:46 +00:00
|
|
|
case SMBCHG_LITE_USB_CHGPTH_SUBTYPE:
|
2014-02-20 01:27:12 +00:00
|
|
|
chip->usb_chgpth_base = resource->start;
|
|
|
|
break;
|
|
|
|
case SMBCHG_DC_CHGPTH_SUBTYPE:
|
2015-02-11 14:23:46 +00:00
|
|
|
case SMBCHG_LITE_DC_CHGPTH_SUBTYPE:
|
2014-02-20 01:27:12 +00:00
|
|
|
chip->dc_chgpth_base = resource->start;
|
|
|
|
break;
|
|
|
|
case SMBCHG_MISC_SUBTYPE:
|
2015-02-11 14:23:46 +00:00
|
|
|
case SMBCHG_LITE_MISC_SUBTYPE:
|
2014-02-20 01:27:12 +00:00
|
|
|
chip->misc_base = resource->start;
|
|
|
|
break;
|
|
|
|
case SMBCHG_OTG_SUBTYPE:
|
2015-02-11 14:23:46 +00:00
|
|
|
case SMBCHG_LITE_OTG_SUBTYPE:
|
2014-02-20 01:27:12 +00:00
|
|
|
chip->otg_base = resource->start;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
REQUIRE_BASE(chip, chgr_base, rc);
|
|
|
|
REQUIRE_BASE(chip, bat_if_base, rc);
|
|
|
|
REQUIRE_BASE(chip, usb_chgpth_base, rc);
|
|
|
|
REQUIRE_BASE(chip, dc_chgpth_base, rc);
|
|
|
|
REQUIRE_BASE(chip, misc_base, rc);
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline void dump_reg(struct smbchg_chip *chip, u16 addr,
|
|
|
|
const char *name)
|
|
|
|
{
|
|
|
|
u8 reg;
|
|
|
|
|
|
|
|
smbchg_read(chip, ®, addr, 1);
|
2014-06-30 22:02:34 +00:00
|
|
|
pr_smb(PR_DUMP, "%s - %04X = %02X\n", name, addr, reg);
|
2014-02-20 01:27:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* dumps useful registers for debug */
|
|
|
|
static void dump_regs(struct smbchg_chip *chip)
|
|
|
|
{
|
|
|
|
u16 addr;
|
|
|
|
|
|
|
|
/* charger peripheral */
|
|
|
|
for (addr = 0xB; addr <= 0x10; addr++)
|
|
|
|
dump_reg(chip, chip->chgr_base + addr, "CHGR Status");
|
|
|
|
for (addr = 0xF0; addr <= 0xFF; addr++)
|
|
|
|
dump_reg(chip, chip->chgr_base + addr, "CHGR Config");
|
|
|
|
/* battery interface peripheral */
|
|
|
|
dump_reg(chip, chip->bat_if_base + RT_STS, "BAT_IF Status");
|
2014-05-28 02:11:10 +00:00
|
|
|
dump_reg(chip, chip->bat_if_base + CMD_CHG_REG, "BAT_IF Command");
|
2014-02-20 01:27:12 +00:00
|
|
|
for (addr = 0xF0; addr <= 0xFB; addr++)
|
|
|
|
dump_reg(chip, chip->bat_if_base + addr, "BAT_IF Config");
|
|
|
|
/* usb charge path peripheral */
|
|
|
|
for (addr = 0x7; addr <= 0x10; addr++)
|
|
|
|
dump_reg(chip, chip->usb_chgpth_base + addr, "USB Status");
|
|
|
|
dump_reg(chip, chip->usb_chgpth_base + CMD_IL, "USB Command");
|
|
|
|
for (addr = 0xF0; addr <= 0xF5; addr++)
|
|
|
|
dump_reg(chip, chip->usb_chgpth_base + addr, "USB Config");
|
|
|
|
/* dc charge path peripheral */
|
|
|
|
dump_reg(chip, chip->dc_chgpth_base + RT_STS, "DC Status");
|
|
|
|
for (addr = 0xF0; addr <= 0xF6; addr++)
|
|
|
|
dump_reg(chip, chip->dc_chgpth_base + addr, "DC Config");
|
|
|
|
/* misc peripheral */
|
|
|
|
dump_reg(chip, chip->misc_base + IDEV_STS, "MISC Status");
|
|
|
|
dump_reg(chip, chip->misc_base + RT_STS, "MISC Status");
|
|
|
|
for (addr = 0xF0; addr <= 0xF3; addr++)
|
|
|
|
dump_reg(chip, chip->misc_base + addr, "MISC CFG");
|
|
|
|
}
|
|
|
|
|
2014-11-05 01:04:29 +00:00
|
|
|
static int create_debugfs_entries(struct smbchg_chip *chip)
|
|
|
|
{
|
|
|
|
struct dentry *ent;
|
|
|
|
|
|
|
|
chip->debug_root = debugfs_create_dir("qpnp-smbcharger", NULL);
|
|
|
|
if (!chip->debug_root) {
|
|
|
|
dev_err(chip->dev, "Couldn't create debug dir\n");
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
ent = debugfs_create_file("force_dcin_icl_check",
|
|
|
|
S_IFREG | S_IWUSR | S_IRUGO,
|
|
|
|
chip->debug_root, chip,
|
|
|
|
&force_dcin_icl_ops);
|
|
|
|
if (!ent) {
|
|
|
|
dev_err(chip->dev,
|
|
|
|
"Couldn't create force dcin icl check file\n");
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-11-26 07:39:46 +00:00
|
|
|
static int smbchg_check_chg_version(struct smbchg_chip *chip)
|
2015-06-08 11:14:19 +00:00
|
|
|
{
|
|
|
|
struct pmic_revid_data *pmic_rev_id;
|
|
|
|
struct device_node *revid_dev_node;
|
|
|
|
|
|
|
|
revid_dev_node = of_parse_phandle(chip->spmi->dev.of_node,
|
|
|
|
"qcom,pmic-revid", 0);
|
|
|
|
if (!revid_dev_node) {
|
|
|
|
pr_err("Missing qcom,pmic-revid property - driver failed\n");
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
pmic_rev_id = get_revid_data(revid_dev_node);
|
|
|
|
if (IS_ERR(pmic_rev_id)) {
|
|
|
|
pr_err("Unable to get pmic_revid rc=%ld\n",
|
|
|
|
PTR_ERR(pmic_rev_id));
|
|
|
|
return -EPROBE_DEFER;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (pmic_rev_id->pmic_subtype) {
|
|
|
|
case PMI8994:
|
2015-08-13 06:11:46 +00:00
|
|
|
chip->wa_flags |= SMBCHG_AICL_DEGLITCH_WA
|
2015-09-17 22:07:37 +00:00
|
|
|
| SMBCHG_BATT_OV_WA
|
|
|
|
| SMBCHG_CC_ESR_WA;
|
2015-11-26 07:39:46 +00:00
|
|
|
use_pmi8994_tables(chip);
|
|
|
|
chip->schg_version = QPNP_SCHG;
|
2015-11-26 07:30:57 +00:00
|
|
|
break;
|
2015-06-08 11:14:19 +00:00
|
|
|
case PMI8950:
|
2015-08-13 06:11:46 +00:00
|
|
|
chip->wa_flags |= SMBCHG_BATT_OV_WA;
|
2015-07-05 04:26:35 +00:00
|
|
|
if (pmic_rev_id->rev4 < 2) /* PMI8950 1.0 */ {
|
2015-06-08 11:14:19 +00:00
|
|
|
chip->wa_flags |= SMBCHG_AICL_DEGLITCH_WA;
|
2015-07-05 04:26:35 +00:00
|
|
|
} else { /* rev > PMI8950 v1.0 */
|
|
|
|
chip->wa_flags |= SMBCHG_HVDCP_9V_EN_WA
|
|
|
|
| SMBCHG_USB100_WA;
|
|
|
|
}
|
2015-11-26 07:39:46 +00:00
|
|
|
use_pmi8994_tables(chip);
|
2015-12-23 02:08:15 +00:00
|
|
|
chip->tables.aicl_rerun_period_table =
|
|
|
|
aicl_rerun_period_schg_lite;
|
|
|
|
chip->tables.aicl_rerun_period_len =
|
|
|
|
ARRAY_SIZE(aicl_rerun_period_schg_lite);
|
|
|
|
|
2015-11-26 07:39:46 +00:00
|
|
|
chip->schg_version = QPNP_SCHG_LITE;
|
2015-06-08 11:14:19 +00:00
|
|
|
break;
|
2015-08-05 16:12:25 +00:00
|
|
|
case PMI8996:
|
2015-11-26 07:46:51 +00:00
|
|
|
chip->wa_flags |= SMBCHG_CC_ESR_WA
|
|
|
|
| SMBCHG_FLASH_ICL_DISABLE_WA;
|
2015-11-26 07:39:46 +00:00
|
|
|
use_pmi8996_tables(chip);
|
|
|
|
chip->schg_version = QPNP_SCHG;
|
2015-08-05 16:12:25 +00:00
|
|
|
break;
|
2015-06-08 11:14:19 +00:00
|
|
|
default:
|
|
|
|
pr_err("PMIC subtype %d not supported, WA flags not set\n",
|
|
|
|
pmic_rev_id->pmic_subtype);
|
|
|
|
}
|
2016-03-21 22:55:19 +00:00
|
|
|
chip->allow_hvdcp3_detection = true;
|
2015-06-08 11:14:19 +00:00
|
|
|
|
2015-11-26 07:39:46 +00:00
|
|
|
pr_smb(PR_STATUS, "pmic=%s, wa_flags=0x%x\n",
|
|
|
|
pmic_rev_id->pmic_name, chip->wa_flags);
|
2015-06-08 11:14:19 +00:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-02-20 01:27:12 +00:00
|
|
|
static int smbchg_probe(struct spmi_device *spmi)
|
|
|
|
{
|
|
|
|
int rc;
|
|
|
|
struct smbchg_chip *chip;
|
|
|
|
struct power_supply *usb_psy;
|
2015-11-04 06:45:11 +00:00
|
|
|
struct qpnp_vadc_chip *vadc_dev, *vchg_vadc_dev;
|
2014-02-20 01:27:12 +00:00
|
|
|
|
|
|
|
usb_psy = power_supply_get_by_name("usb");
|
|
|
|
if (!usb_psy) {
|
2014-06-30 22:02:34 +00:00
|
|
|
pr_smb(PR_STATUS, "USB supply not found, deferring probe\n");
|
2014-02-20 01:27:12 +00:00
|
|
|
return -EPROBE_DEFER;
|
|
|
|
}
|
|
|
|
|
2014-10-30 21:59:48 +00:00
|
|
|
if (of_find_property(spmi->dev.of_node, "qcom,dcin-vadc", NULL)) {
|
|
|
|
vadc_dev = qpnp_get_vadc(&spmi->dev, "dcin");
|
|
|
|
if (IS_ERR(vadc_dev)) {
|
|
|
|
rc = PTR_ERR(vadc_dev);
|
|
|
|
if (rc != -EPROBE_DEFER)
|
|
|
|
dev_err(&spmi->dev, "Couldn't get vadc rc=%d\n",
|
|
|
|
rc);
|
|
|
|
return rc;
|
|
|
|
}
|
2016-12-12 23:31:49 +00:00
|
|
|
} else {
|
|
|
|
vadc_dev = NULL;
|
2014-10-30 21:59:48 +00:00
|
|
|
}
|
|
|
|
|
2015-11-04 06:45:11 +00:00
|
|
|
if (of_find_property(spmi->dev.of_node, "qcom,vchg_sns-vadc", NULL)) {
|
|
|
|
vchg_vadc_dev = qpnp_get_vadc(&spmi->dev, "vchg_sns");
|
|
|
|
if (IS_ERR(vchg_vadc_dev)) {
|
|
|
|
rc = PTR_ERR(vchg_vadc_dev);
|
|
|
|
if (rc != -EPROBE_DEFER)
|
|
|
|
dev_err(&spmi->dev, "Couldn't get vadc 'vchg' rc=%d\n",
|
|
|
|
rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-02-20 01:27:12 +00:00
|
|
|
chip = devm_kzalloc(&spmi->dev, sizeof(*chip), GFP_KERNEL);
|
|
|
|
if (!chip) {
|
|
|
|
dev_err(&spmi->dev, "Unable to allocate memory\n");
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
2015-10-06 23:11:53 +00:00
|
|
|
chip->fcc_votable = create_votable(&spmi->dev,
|
|
|
|
"SMBCHG: fcc",
|
2015-10-20 00:13:42 +00:00
|
|
|
VOTE_MIN, NUM_FCC_VOTER, 2000,
|
|
|
|
set_fastchg_current_vote_cb);
|
2015-11-26 07:54:21 +00:00
|
|
|
if (IS_ERR(chip->fcc_votable))
|
|
|
|
return PTR_ERR(chip->fcc_votable);
|
|
|
|
|
2015-10-06 23:11:53 +00:00
|
|
|
chip->usb_icl_votable = create_votable(&spmi->dev,
|
|
|
|
"SMBCHG: usb_icl",
|
2015-10-20 00:13:42 +00:00
|
|
|
VOTE_MIN, NUM_ICL_VOTER, 3000,
|
|
|
|
set_usb_current_limit_vote_cb);
|
2015-11-26 09:07:24 +00:00
|
|
|
if (IS_ERR(chip->usb_icl_votable))
|
|
|
|
return PTR_ERR(chip->usb_icl_votable);
|
|
|
|
|
2015-10-06 23:11:53 +00:00
|
|
|
chip->dc_icl_votable = create_votable(&spmi->dev,
|
|
|
|
"SMBCHG: dcl_icl",
|
2015-10-20 00:13:42 +00:00
|
|
|
VOTE_MIN, NUM_ICL_VOTER, 3000,
|
|
|
|
set_dc_current_limit_vote_cb);
|
2015-11-26 09:07:24 +00:00
|
|
|
if (IS_ERR(chip->dc_icl_votable))
|
|
|
|
return PTR_ERR(chip->dc_icl_votable);
|
|
|
|
|
2015-10-06 23:11:53 +00:00
|
|
|
chip->usb_suspend_votable = create_votable(&spmi->dev,
|
2015-10-20 00:13:42 +00:00
|
|
|
"SMBCHG: usb_suspend",
|
|
|
|
VOTE_SET_ANY, NUM_EN_VOTERS, 0,
|
|
|
|
usb_suspend_vote_cb);
|
2015-11-26 09:38:11 +00:00
|
|
|
if (IS_ERR(chip->usb_suspend_votable))
|
|
|
|
return PTR_ERR(chip->usb_suspend_votable);
|
|
|
|
|
2015-10-06 23:11:53 +00:00
|
|
|
chip->dc_suspend_votable = create_votable(&spmi->dev,
|
2015-10-20 00:13:42 +00:00
|
|
|
"SMBCHG: dc_suspend",
|
|
|
|
VOTE_SET_ANY, NUM_EN_VOTERS, 0,
|
|
|
|
dc_suspend_vote_cb);
|
2015-11-26 09:38:11 +00:00
|
|
|
if (IS_ERR(chip->dc_suspend_votable))
|
|
|
|
return PTR_ERR(chip->dc_suspend_votable);
|
|
|
|
|
2015-10-06 23:11:53 +00:00
|
|
|
chip->battchg_suspend_votable = create_votable(&spmi->dev,
|
2015-10-20 00:13:42 +00:00
|
|
|
"SMBCHG: battchg_suspend",
|
|
|
|
VOTE_SET_ANY, NUM_BATTCHG_EN_VOTERS, 0,
|
|
|
|
charging_suspend_vote_cb);
|
|
|
|
if (IS_ERR(chip->battchg_suspend_votable))
|
2015-11-26 09:38:11 +00:00
|
|
|
return PTR_ERR(chip->battchg_suspend_votable);
|
|
|
|
|
2014-02-20 01:27:12 +00:00
|
|
|
INIT_WORK(&chip->usb_set_online_work, smbchg_usb_update_online_work);
|
2014-10-15 17:41:50 +00:00
|
|
|
INIT_DELAYED_WORK(&chip->parallel_en_work,
|
|
|
|
smbchg_parallel_usb_en_work);
|
2014-10-29 18:01:40 +00:00
|
|
|
INIT_DELAYED_WORK(&chip->vfloat_adjust_work, smbchg_vfloat_adjust_work);
|
2015-01-07 03:07:07 +00:00
|
|
|
INIT_DELAYED_WORK(&chip->hvdcp_det_work, smbchg_hvdcp_det_work);
|
2015-07-27 05:46:49 +00:00
|
|
|
init_completion(&chip->src_det_lowered);
|
|
|
|
init_completion(&chip->src_det_raised);
|
|
|
|
init_completion(&chip->usbin_uv_lowered);
|
|
|
|
init_completion(&chip->usbin_uv_raised);
|
2014-10-30 21:59:48 +00:00
|
|
|
chip->vadc_dev = vadc_dev;
|
2015-11-04 06:45:11 +00:00
|
|
|
chip->vchg_vadc_dev = vchg_vadc_dev;
|
2014-02-20 01:27:12 +00:00
|
|
|
chip->spmi = spmi;
|
|
|
|
chip->dev = &spmi->dev;
|
|
|
|
chip->usb_psy = usb_psy;
|
|
|
|
chip->fake_battery_soc = -EINVAL;
|
|
|
|
chip->usb_online = -EINVAL;
|
|
|
|
dev_set_drvdata(&spmi->dev, chip);
|
|
|
|
|
|
|
|
spin_lock_init(&chip->sec_access_lock);
|
2015-11-26 09:07:24 +00:00
|
|
|
mutex_init(&chip->therm_lvl_lock);
|
2014-02-20 01:27:12 +00:00
|
|
|
mutex_init(&chip->usb_set_online_lock);
|
2014-08-06 18:27:04 +00:00
|
|
|
mutex_init(&chip->parallel.lock);
|
|
|
|
mutex_init(&chip->taper_irq_lock);
|
2014-10-15 17:41:50 +00:00
|
|
|
mutex_init(&chip->pm_lock);
|
2014-10-30 21:59:48 +00:00
|
|
|
mutex_init(&chip->wipower_config);
|
2015-03-19 17:10:26 +00:00
|
|
|
mutex_init(&chip->usb_status_lock);
|
2015-07-24 01:30:38 +00:00
|
|
|
device_init_wakeup(chip->dev, true);
|
2014-02-20 01:27:12 +00:00
|
|
|
|
|
|
|
rc = smbchg_parse_peripherals(chip);
|
|
|
|
if (rc) {
|
|
|
|
dev_err(chip->dev, "Error parsing DT peripherals: %d\n", rc);
|
|
|
|
return rc;
|
|
|
|
}
|
2015-02-11 14:23:46 +00:00
|
|
|
|
|
|
|
rc = smbchg_check_chg_version(chip);
|
|
|
|
if (rc) {
|
|
|
|
pr_err("Unable to check schg version rc=%d\n", rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2014-02-20 01:27:12 +00:00
|
|
|
rc = smb_parse_dt(chip);
|
|
|
|
if (rc < 0) {
|
|
|
|
dev_err(&spmi->dev, "Unable to parse DT nodes: %d\n", rc);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
rc = smbchg_regulator_init(chip);
|
|
|
|
if (rc) {
|
|
|
|
dev_err(&spmi->dev,
|
|
|
|
"Couldn't initialize regulator rc=%d\n", rc);
|
2015-08-25 18:43:32 +00:00
|
|
|
goto free_regulator;
|
2014-02-20 01:27:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
rc = smbchg_hw_init(chip);
|
|
|
|
if (rc < 0) {
|
|
|
|
dev_err(&spmi->dev,
|
|
|
|
"Unable to intialize hardware rc = %d\n", rc);
|
|
|
|
goto free_regulator;
|
|
|
|
}
|
|
|
|
|
|
|
|
rc = determine_initial_status(chip);
|
|
|
|
if (rc < 0) {
|
|
|
|
dev_err(&spmi->dev,
|
|
|
|
"Unable to determine init status rc = %d\n", rc);
|
|
|
|
goto free_regulator;
|
|
|
|
}
|
|
|
|
|
2014-11-04 15:09:26 +00:00
|
|
|
chip->previous_soc = -EINVAL;
|
2014-02-20 01:27:12 +00:00
|
|
|
chip->batt_psy.name = chip->battery_psy_name;
|
|
|
|
chip->batt_psy.type = POWER_SUPPLY_TYPE_BATTERY;
|
|
|
|
chip->batt_psy.get_property = smbchg_battery_get_property;
|
|
|
|
chip->batt_psy.set_property = smbchg_battery_set_property;
|
|
|
|
chip->batt_psy.properties = smbchg_battery_properties;
|
|
|
|
chip->batt_psy.num_properties = ARRAY_SIZE(smbchg_battery_properties);
|
|
|
|
chip->batt_psy.external_power_changed = smbchg_external_power_changed;
|
|
|
|
chip->batt_psy.property_is_writeable = smbchg_battery_is_writeable;
|
|
|
|
|
|
|
|
rc = power_supply_register(chip->dev, &chip->batt_psy);
|
|
|
|
if (rc < 0) {
|
|
|
|
dev_err(&spmi->dev,
|
|
|
|
"Unable to register batt_psy rc = %d\n", rc);
|
|
|
|
goto free_regulator;
|
|
|
|
}
|
|
|
|
if (chip->dc_psy_type != -EINVAL) {
|
|
|
|
chip->dc_psy.name = "dc";
|
|
|
|
chip->dc_psy.type = chip->dc_psy_type;
|
|
|
|
chip->dc_psy.get_property = smbchg_dc_get_property;
|
|
|
|
chip->dc_psy.set_property = smbchg_dc_set_property;
|
|
|
|
chip->dc_psy.property_is_writeable = smbchg_dc_is_writeable;
|
|
|
|
chip->dc_psy.properties = smbchg_dc_properties;
|
|
|
|
chip->dc_psy.num_properties = ARRAY_SIZE(smbchg_dc_properties);
|
2015-03-05 23:24:25 +00:00
|
|
|
chip->dc_psy.supplied_to = smbchg_dc_supplicants;
|
|
|
|
chip->dc_psy.num_supplicants
|
|
|
|
= ARRAY_SIZE(smbchg_dc_supplicants);
|
2014-02-20 01:27:12 +00:00
|
|
|
rc = power_supply_register(chip->dev, &chip->dc_psy);
|
|
|
|
if (rc < 0) {
|
|
|
|
dev_err(&spmi->dev,
|
|
|
|
"Unable to register dc_psy rc = %d\n", rc);
|
|
|
|
goto unregister_batt_psy;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
chip->psy_registered = true;
|
|
|
|
|
2015-03-10 08:42:22 +00:00
|
|
|
if (chip->cfg_chg_led_support &&
|
|
|
|
chip->schg_version == QPNP_SCHG_LITE) {
|
|
|
|
rc = smbchg_register_chg_led(chip);
|
|
|
|
if (rc) {
|
|
|
|
dev_err(chip->dev,
|
|
|
|
"Unable to register charger led: %d\n",
|
|
|
|
rc);
|
|
|
|
goto unregister_dc_psy;
|
|
|
|
}
|
|
|
|
|
|
|
|
rc = smbchg_chg_led_controls(chip);
|
|
|
|
if (rc) {
|
|
|
|
dev_err(chip->dev,
|
|
|
|
"Failed to set charger led controld bit: %d\n",
|
|
|
|
rc);
|
|
|
|
goto unregister_led_class;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-02-20 01:27:12 +00:00
|
|
|
rc = smbchg_request_irqs(chip);
|
|
|
|
if (rc < 0) {
|
|
|
|
dev_err(&spmi->dev, "Unable to request irqs rc = %d\n", rc);
|
2015-03-10 08:42:22 +00:00
|
|
|
goto unregister_led_class;
|
2014-02-20 01:27:12 +00:00
|
|
|
}
|
|
|
|
|
2015-11-26 07:20:42 +00:00
|
|
|
if (!chip->skip_usb_notification) {
|
|
|
|
pr_smb(PR_MISC, "setting usb psy present = %d\n",
|
|
|
|
chip->usb_present);
|
|
|
|
power_supply_set_present(chip->usb_psy, chip->usb_present);
|
|
|
|
}
|
2014-02-20 01:27:12 +00:00
|
|
|
|
|
|
|
dump_regs(chip);
|
2014-11-05 01:04:29 +00:00
|
|
|
create_debugfs_entries(chip);
|
2015-02-11 14:23:46 +00:00
|
|
|
dev_info(chip->dev,
|
|
|
|
"SMBCHG successfully probe Charger version=%s Revision DIG:%d.%d ANA:%d.%d batt=%d dc=%d usb=%d\n",
|
|
|
|
version_str[chip->schg_version],
|
|
|
|
chip->revision[DIG_MAJOR], chip->revision[DIG_MINOR],
|
|
|
|
chip->revision[ANA_MAJOR], chip->revision[ANA_MINOR],
|
2014-02-20 01:27:12 +00:00
|
|
|
get_prop_batt_present(chip),
|
|
|
|
chip->dc_present, chip->usb_present);
|
|
|
|
return 0;
|
|
|
|
|
2015-03-10 08:42:22 +00:00
|
|
|
unregister_led_class:
|
|
|
|
if (chip->cfg_chg_led_support && chip->schg_version == QPNP_SCHG_LITE)
|
|
|
|
led_classdev_unregister(&chip->led_cdev);
|
2014-02-20 01:27:12 +00:00
|
|
|
unregister_dc_psy:
|
|
|
|
power_supply_unregister(&chip->dc_psy);
|
|
|
|
unregister_batt_psy:
|
|
|
|
power_supply_unregister(&chip->batt_psy);
|
|
|
|
free_regulator:
|
|
|
|
smbchg_regulator_deinit(chip);
|
|
|
|
handle_usb_removal(chip);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int smbchg_remove(struct spmi_device *spmi)
|
|
|
|
{
|
|
|
|
struct smbchg_chip *chip = dev_get_drvdata(&spmi->dev);
|
|
|
|
|
2014-11-05 01:04:29 +00:00
|
|
|
debugfs_remove_recursive(chip->debug_root);
|
|
|
|
|
2014-02-20 01:27:12 +00:00
|
|
|
if (chip->dc_psy_type != -EINVAL)
|
|
|
|
power_supply_unregister(&chip->dc_psy);
|
|
|
|
|
|
|
|
power_supply_unregister(&chip->batt_psy);
|
|
|
|
smbchg_regulator_deinit(chip);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct dev_pm_ops smbchg_pm_ops = {
|
|
|
|
};
|
|
|
|
|
|
|
|
MODULE_DEVICE_TABLE(spmi, smbchg_id);
|
|
|
|
|
|
|
|
static struct spmi_driver smbchg_driver = {
|
|
|
|
.driver = {
|
|
|
|
.name = "qpnp-smbcharger",
|
|
|
|
.owner = THIS_MODULE,
|
|
|
|
.of_match_table = smbchg_match_table,
|
|
|
|
.pm = &smbchg_pm_ops,
|
|
|
|
},
|
|
|
|
.probe = smbchg_probe,
|
|
|
|
.remove = smbchg_remove,
|
|
|
|
};
|
|
|
|
|
|
|
|
static int __init smbchg_init(void)
|
|
|
|
{
|
|
|
|
return spmi_driver_register(&smbchg_driver);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void __exit smbchg_exit(void)
|
|
|
|
{
|
|
|
|
return spmi_driver_unregister(&smbchg_driver);
|
|
|
|
}
|
|
|
|
|
|
|
|
module_init(smbchg_init);
|
|
|
|
module_exit(smbchg_exit);
|
|
|
|
|
|
|
|
MODULE_DESCRIPTION("QPNP SMB Charger");
|
|
|
|
MODULE_LICENSE("GPL v2");
|
|
|
|
MODULE_ALIAS("platform:qpnp-smbcharger");
|