Files
android_kernel_motorola_sm6225/drivers/power/mmi-smbcharger-iio/mmi-smbcharger-iio.c
T
Lei Chen ed5a5e73c0 sbmcharger:optimize the pdo select
bangkk select the max current is beyond the sourcecap pdo.
Optimize the pdo max current when the pd power is lower than
18W.

Change-Id: Iaded43336951bcfbd9620d45a89c3517ccd058a9
Signed-off-by: Lei Chen <chenlei18@lenovo.com>
Reviewed-on: https://gerrit.mot.com/2638964
SME-Granted: SME Approvals Granted
SLTApproved: Slta Waiver
Tested-by: Jira Key
Reviewed-by: Xiangpo Zhao <zhaoxp3@motorola.com>
Reviewed-by: Lei Chen <chenl18@motorola.com>
Submit-Approved: Jira Key
2023-06-09 01:02:03 -05:00

5208 lines
136 KiB
C
Executable File

/* Copyright (c) 2017, 2018 The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/alarmtimer.h>
#include <linux/device.h>
#include <linux/module.h>
#include <linux/notifier.h>
#include <linux/reboot.h>
#include <linux/platform_device.h>
#include <linux/power_supply.h>
#include <linux/version.h>
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 61))
#include <linux/mmi-pmic-voter.h>
#else
#include <linux/pmic-voter.h>
#endif
#include <linux/of_device.h>
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
#include <linux/slab.h>
#include <linux/of.h>
#include <linux/workqueue.h>
#include <linux/ipc_logging.h>
#include <linux/debugfs.h>
#include <linux/string.h>
#include <linux/version.h>
#include <linux/mmi_wake_lock.h>
#include <linux/iio/consumer.h>
#include <linux/qti_power_supply.h>
#include <linux/usb/usbpd.h>
#include <linux/iio/iio.h>
#include <dt-bindings/iio/qti_power_supply_iio.h>
#define MODULE_LOG "SMBMMI"
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 61))
#define get_effective_result(votable) \
mmi_get_effective_result(votable)
#define vote(votable, client_str, enabled, val) \
mmi_vote(votable, client_str, enabled, val)
#define find_votable(name) mmi_find_votable(name)
#define pmic_vote_force_val_set(votable, val) \
mmi_pmic_vote_force_val_set(votable, val)
#define pmic_vote_force_active_set(votable, val) \
mmi_pmic_vote_force_active_set(votable, val)
#endif
#define mmi_err(chg, fmt, ...) \
do { \
pr_err("%s: %s: %s: " fmt, chg->name, \
MODULE_LOG, __func__, ##__VA_ARGS__); \
ipc_log_string(chg->ipc_log, \
"ERR:%s: " fmt, __func__, ##__VA_ARGS__); \
} while (0)
#define mmi_warn(chg, fmt, ...) \
do { \
pr_warn("%s: %s: %s: " fmt, chg->name, \
MODULE_LOG, __func__, ##__VA_ARGS__); \
ipc_log_string(chg->ipc_log, \
"WARN:%s: " fmt, __func__, ##__VA_ARGS__); \
} while (0)
#define mmi_info(chg, fmt, ...) \
do { \
pr_info("%s: %s: %s: " fmt, chg->name, \
MODULE_LOG, __func__, ##__VA_ARGS__); \
ipc_log_string(chg->ipc_log, \
"INFO:%s: " fmt, __func__, ##__VA_ARGS__); \
} while (0)
#define mmi_dbg(chg, fmt, ...) \
do { \
if (*chg->debug_enabled) \
pr_info("%s: %s: %s: " fmt, chg->name, \
MODULE_LOG, __func__, ##__VA_ARGS__); \
else \
pr_debug("%s: %s: %s: " fmt, chg->name, \
MODULE_LOG, __func__, ##__VA_ARGS__); \
ipc_log_string(chg->ipc_log, \
"DBG:%s: " fmt, __func__, ##__VA_ARGS__); \
} while (0)
#define MMI_LOG_PAGES (50)
static bool debug_enabled;
module_param(debug_enabled, bool, 0600);
MODULE_PARM_DESC(debug_enabled, "Enable debug for SMBMMI driver");
struct mmi_smbcharger_iio_channels {
const char *datasheet_name;
int channel_num;
enum iio_chan_type type;
long info_mask;
};
#define MMI_SMBCHARGER_IIO_CHAN(_name, _num, _type, _mask) \
{ \
.datasheet_name = _name, \
.channel_num = _num, \
.type = _type, \
.info_mask = _mask, \
},
#define MMI_SMBCHARGER_CHAN_INDEX(_name, _num) \
MMI_SMBCHARGER_IIO_CHAN(_name, _num, IIO_INDEX, \
BIT(IIO_CHAN_INFO_PROCESSED))
static const struct mmi_smbcharger_iio_channels mmi_smbcharger_iio_psy_channels[] = {
MMI_SMBCHARGER_CHAN_INDEX("mmi_cp_enabled_status", PSY_IIO_CP_ENABLE)
};
static struct smb_mmi_charger *this_chip = NULL;
enum pmic_type {
PM8150B,
PM7250B,
PM6150,
PMI632,
};
#define CHGR_BASE 0x1000
#define DCDC_BASE 0x1100
#define BATIF_BASE 0x1200
#define USBIN_BASE 0x1300
#define DCIN_BASE 0x1400
#define TYPEC_BASE 0X1500
#define MISC_BASE 0x1600
#define BATTERY_CHARGER_STATUS_1_REG (CHGR_BASE + 0x06)
#define BATTERY_CHARGER_STATUS_MASK (GENMASK(2, 0))
#define PM8150B_JEITA_EN_CFG_REG (CHGR_BASE + 0x90)
#define POWER_PATH_STATUS_REG (DCDC_BASE + 0x0B)
#define MISC_POWER_PATH_STATUS_REG (MISC_BASE + 0x0B)
#define USBIN_SUSPEND_STS_BIT BIT(6)
#define DCDC_CFG_REF_MAX_PSNS_REG (DCDC_BASE + 0x8C)
#define APSD_STATUS_REG (USBIN_BASE + 0x07)
#define APSD_STATUS_7_BIT BIT(7)
#define HVDCP_CHECK_TIMEOUT_BIT BIT(6)
#define SLOW_PLUGIN_TIMEOUT_BIT BIT(5)
#define ENUMERATION_DONE_BIT BIT(4)
#define VADP_CHANGE_DONE_AFTER_AUTH_BIT BIT(3)
#define QC_AUTH_DONE_STATUS_BIT BIT(2)
#define QC_CHARGER_BIT BIT(1)
#define APSD_DTC_STATUS_DONE_BIT BIT(0)
#define APSD_RESULT_STATUS_REG (USBIN_BASE + 0x08)
#define APSD_RESULT_STATUS_7_BIT BIT(7)
#define APSD_RESULT_STATUS_MASK GENMASK(6, 0)
#define QC_3P0_BIT BIT(6)
#define QC_2P0_BIT BIT(5)
#define FLOAT_CHARGER_BIT BIT(4)
#define DCP_CHARGER_BIT BIT(3)
#define CDP_CHARGER_BIT BIT(2)
#define OCP_CHARGER_BIT BIT(1)
#define SDP_CHARGER_BIT BIT(0)
#define USBIN_INT_RT_STS (USBIN_BASE + 0x10)
#define USBIN_PLUGIN_RT_STS_BIT BIT(4)
#define USBIN_INT_EN_CLR (USBIN_BASE + 0x16)
#define USBIN_OV_EN_CLR BIT(3)
#define CMD_APSD_REG (USBIN_BASE + 0x41)
#define APSD_RERUN_BIT BIT(0)
#define ICL_OVERRIDE_BIT BIT(1)
#define USBIN_CMD_ICL_OVERRIDE_REG (USBIN_BASE + 0x42)
#define USBIN_ICL_OVERRIDE_BIT BIT(0)
#define HVDCP_PULSE_COUNT_MAX_REG (USBIN_BASE + 0x5B)
#define HVDCP_PULSE_COUNT_MAX_QC2_MASK GENMASK(7, 6)
#define HVDCP_PULSE_COUNT_MAX_QC3_MASK GENMASK(5, 0)
#define USBIN_ICL_OPTIONS_REG (USBIN_BASE + 0x66)
#define USBIN_MODE_CHG_BIT BIT(0)
#define USBIN_LOAD_CFG_REG (USBIN_BASE + 0x65)
#define ICL_OVERRIDE_AFTER_APSD_BIT BIT(4)
#define USBIN_AICL_OPTIONS_CFG_REG (USBIN_BASE + 0x80)
#define LEGACY_CABLE_CFG_REG (TYPEC_BASE + 0x5A)
#define USBIN_ADAPTER_ALLOW_CFG_REG (USBIN_BASE + 0x60)
#define USBIN_ADAPTER_ALLOW_MASK GENMASK(3, 0)
/* DCIN Interrupt Bits */
#define DCIN_PLUGIN_RT_STS_BIT BIT(4)
#define DCIN_INT_RT_STS (DCIN_BASE + 0x10)
enum {
USBIN_ADAPTER_ALLOW_5V = 0,
USBIN_ADAPTER_ALLOW_9V = 2,
USBIN_ADAPTER_ALLOW_5V_OR_9V = 3,
USBIN_ADAPTER_ALLOW_12V = 4,
USBIN_ADAPTER_ALLOW_5V_OR_12V = 5,
USBIN_ADAPTER_ALLOW_9V_TO_12V = 6,
USBIN_ADAPTER_ALLOW_5V_OR_9V_TO_12V = 7,
USBIN_ADAPTER_ALLOW_5V_TO_9V = 8,
USBIN_ADAPTER_ALLOW_5V_TO_12V = 12,
};
#define LOW_BATT_MV 3300
#define SHUT_BATT_MV 3100
#define REV_BST_THRESH 4700
#define REV_BST_DROP 150
#define REV_BST_BULK_DROP 100
#define REV_BST_MA -10
#define BOOST_BACK_VOTER "BOOST_BACK_VOTER"
#define MMI_HB_VOTER "MMI_HB_VOTER"
#define BATT_PROFILE_VOTER "BATT_PROFILE_VOTER"
#define DEMO_VOTER "DEMO_VOTER"
#define MMI_VOTER "MMI_VOTER"
#define HYST_STEP_MV 50
#define HYST_STEP_FLIP_MV (HYST_STEP_MV*2)
#define DEMO_MODE_HYS_SOC 5
#define DEMO_MODE_VOLTAGE 4000
#define WARM_TEMP 45
#define COOL_TEMP 0
#define WEAK_CHARGER_WAIT 30000
#define EVENT_WEAK_CHARGER_WAIT 0
#define HEARTBEAT_DELAY_MS 5000
#define HEARTBEAT_DUAL_DELAY_MS 10000
#define HEARTBEAT_DUAL_DELAY_OCP_MS 1000
#define HEARTBEAT_OCP_SETTLE_CNT 7
#define HEARTBEAT_FACTORY_MS 1000
#define HEARTBEAT_DISCHARGE_MS 100000
#define EMPTY_CYCLES 101
enum {
TRICKLE_CHARGE = 0,
PRE_CHARGE,
FAST_CHARGE,
FULLON_CHARGE,
TAPER_CHARGE,
TERMINATE_CHARGE,
INHIBIT_CHARGE,
DISABLE_CHARGE,
};
enum {
INHIBIT_CHARGE_V5 = 0,
TRICKLE_CHARGE_V5,
PRE_CHARGE_V5,
FULLON_CHARGE_V5,
TAPER_CHARGE_V5,
TERMINATE_CHARGE_V5,
PAUSE_CHARGE_V5,
DISABLE_CHARGE_V5,
};
enum {
POWER_SUPPLY_CHARGE_RATE_NONE = 0,
POWER_SUPPLY_CHARGE_RATE_NORMAL,
POWER_SUPPLY_CHARGE_RATE_WEAK,
POWER_SUPPLY_CHARGE_RATE_TURBO,
};
enum {
CHG_BC1P2_SDP = 0,
CHG_BC1P2_OCP,
CHG_BC1P2_CDP,
CHG_BC1P2_DCP,
CHG_BC1P2_FLOAT,
CHG_BC1P2_QC_2P0,
CHG_BC1P2_QC_3P0,
CHG_BC1P2_UNKNOWN,
};
static char *charge_rate[] = {
"None", "Normal", "Weak", "Turbo"
};
struct mmi_temp_zone {
int temp_c;
int norm_mv;
int fcc_max_ma;
int fcc_norm_ma;
};
#define ZONE_35C_TO_45C 5
#define MAX_NUM_STEPS 10
enum mmi_temp_zones {
ZONE_FIRST = 0,
/* states 0-9 are reserved for zones */
ZONE_LAST = MAX_NUM_STEPS + ZONE_FIRST - 1,
ZONE_HOT,
ZONE_COLD,
ZONE_NONE = 0xFF,
};
enum {
BASE_BATT = 0,
MAIN_BATT,
FLIP_BATT,
};
enum mmi_chrg_step {
STEP_MAX,
STEP_NORM,
STEP_FULL,
STEP_FLOAT,
STEP_DEMO,
STEP_STOP,
STEP_NONE = 0xFF,
};
static char *stepchg_str[] = {
[STEP_MAX] = "MAX",
[STEP_NORM] = "NORMAL",
[STEP_FULL] = "FULL",
[STEP_FLOAT] = "FLOAT",
[STEP_DEMO] = "DEMO",
[STEP_STOP] = "STOP",
[STEP_NONE] = "NONE",
};
struct smb_mmi_chg_status {
int batt_mv;
int batt_ma;
int batt_soc;
int batt_temp;
int usb_mv;
int dc_mv;
int charger_present;
int vbus_present;
int dc_present;
};
struct smb_mmi_chg_param {
const char *name;
u16 reg;
int min_u;
int max_u;
int step_u;
int (*get_proc)(struct smb_mmi_chg_param *param,
u8 val_raw);
int (*set_proc)(struct smb_mmi_chg_param *param,
int val_u,
u8 *val_raw);
};
struct smb_mmi_chg_freq {
unsigned int freq_5V;
unsigned int freq_6V_8V;
unsigned int freq_9V;
unsigned int freq_12V;
};
struct smb_mmi_params {
struct smb_mmi_chg_param fcc;
struct smb_mmi_chg_param fv;
struct smb_mmi_chg_param usb_icl;
struct smb_mmi_chg_param dc_icl;
struct smb_mmi_chg_param aicl_cont_threshold;
struct smb_mmi_chg_param freq_switcher;
};
struct mmi_ffc_zone {
int ffc_max_mv;
int ffc_chg_iterm;
int ffc_qg_iterm;
};
struct mmi_sm_params {
int num_temp_zones;
struct mmi_ffc_zone *ffc_zones;
struct mmi_temp_zone *temp_zones;
enum mmi_temp_zones pres_temp_zone;
enum mmi_chrg_step pres_chrg_step;
int chrg_taper_cnt;
int batt_health;
int chrg_iterm;
int target_fcc;
int target_fv;
int ocp[MAX_NUM_STEPS];
int demo_mode_prev_soc;
};
enum charging_limit_modes {
CHARGING_LIMIT_OFF,
CHARGING_LIMIT_RUN,
CHARGING_LIMIT_UNKNOWN,
};
#define IS_SUSPENDED BIT(0)
#define WAS_SUSPENDED BIT(1)
struct smb_mmi_charger {
struct device *dev;
struct regmap *regmap;
struct smb_mmi_params param;
struct smb_mmi_chg_freq chg_freq;
char *name;
int smb_version;
struct iio_channel **ext_iio_chans;
struct iio_dev *indio_dev;
struct iio_chan_spec *iio_chan;
struct iio_channel *int_iio_chans;
bool cp_active;
bool enable_factory_mode_aicl;
bool factory_mode;
int demo_mode;
struct notifier_block smb_reboot;
struct power_supply *mmi_psy;
struct power_supply *batt_psy;
struct power_supply *qcom_psy;
struct power_supply *usb_psy;
struct power_supply *bms_psy;
struct power_supply *pc_port_psy;
struct power_supply *dc_psy;
struct power_supply *wls_psy;
struct power_supply *max_main_psy;
struct power_supply *max_flip_psy;
struct notifier_block mmi_psy_notifier;
struct delayed_work heartbeat_work;
struct delayed_work weakcharger_work;
struct votable *chg_dis_votable;
struct votable *fcc_votable;
struct votable *fv_votable;
struct votable *usb_icl_votable;
struct votable *dc_suspend_votable;
bool enable_charging_limit;
bool enable_factory_poweroff;
bool is_factory_image;
enum charging_limit_modes charging_limit_modes;
int upper_limit_capacity;
int lower_limit_capacity;
int max_chrg_temp;
int last_iusb_ua;
bool factory_kill_armed;
bool demo_mode_usb_suspend;
unsigned long flags;
/* Charge Profile */
struct mmi_sm_params sm_param[3];
int base_fv_mv;
int vfloat_comp_mv;
int suspended;
bool awake;
int last_reported_soc;
int last_reported_status;
struct regulator *vbus;
bool vbus_enabled;
int prev_chg_rate;
int charger_rate;
int age;
int cycles;
int soc_cycles_start;
bool shut_batt;
int dc_cl_ma;
bool *debug_enabled;
void *ipc_log;
struct usbpd *pd_handle;
struct usbpd_pdo_info mmi_pdo_info[PD_MAX_PDO_NUM];
int pd_power_max;
int hvdcp_power_max;
int inc_hvdcp_cnt;
int hb_startup_cnt;
bool ocp_flag;
/* none ffc paramter */
int noffc_chg_iterm;
int noffc_chg_iterm_45c;
int noffc_qg_iterm;
int noffc_max_fv;
int pd_pps_active;
int real_charger_type;
};
#define CHGR_FAST_CHARGE_CURRENT_CFG_REG (CHGR_BASE + 0x61)
#define CHGR_FLOAT_VOLTAGE_CFG_REG (CHGR_BASE + 0x70)
#define USBIN_CURRENT_LIMIT_CFG_REG (USBIN_BASE + 0x70)
#define DCIN_CURRENT_LIMIT_CFG_REG (DCIN_BASE + 0x70)
#define USBIN_CONT_AICL_THRESHOLD_REG (USBIN_BASE + 0x84)
#define DCDC_FSW_SEL_REG (DCDC_BASE + 0x50)
#define AICL_RANGE2_MIN_MV 5600
#define AICL_RANGE2_STEP_DELTA_MV 200
#define AICL_RANGE2_OFFSET 16
int smblib_get_aicl_cont_threshold(struct smb_mmi_chg_param *param, u8 val_raw)
{
int base = param->min_u;
u8 reg = val_raw;
int step = param->step_u;
if (val_raw >= AICL_RANGE2_OFFSET) {
reg = val_raw - AICL_RANGE2_OFFSET;
base = AICL_RANGE2_MIN_MV;
step = AICL_RANGE2_STEP_DELTA_MV;
}
return base + (reg * step);
}
int smblib_set_aicl_cont_threshold(struct smb_mmi_chg_param *param,
int val_u, u8 *val_raw)
{
int base = param->min_u;
int offset = 0;
int step = param->step_u;
if (val_u > param->max_u)
val_u = param->max_u;
if (val_u < param->min_u)
val_u = param->min_u;
if (val_u >= AICL_RANGE2_MIN_MV) {
base = AICL_RANGE2_MIN_MV;
step = AICL_RANGE2_STEP_DELTA_MV;
offset = AICL_RANGE2_OFFSET;
};
*val_raw = ((val_u - base) / step) + offset;
return 0;
}
/********************
* REGISTER SETTERS *
********************/
struct smb_buck_boost_freq {
int freq_khz;
u8 val;
};
static const struct smb_buck_boost_freq chg_freq_list[] = {
[0] = {
.freq_khz = 2400,
.val = 7,
},
[1] = {
.freq_khz = 2100,
.val = 8,
},
[2] = {
.freq_khz = 1600,
.val = 11,
},
[3] = {
.freq_khz = 1200,
.val = 15,
},
};
int smblib_set_chg_freq(struct smb_mmi_chg_param *param,
int val_u, u8 *val_raw)
{
u8 i;
if (val_u > param->max_u || val_u < param->min_u)
return -EINVAL;
/* Charger FSW is the configured freqency / 2 */
val_u *= 2;
for (i = 0; i < ARRAY_SIZE(chg_freq_list); i++) {
if (chg_freq_list[i].freq_khz == val_u)
break;
}
if (i == ARRAY_SIZE(chg_freq_list)) {
pr_err("Invalid frequency %d Hz\n", val_u / 2);
return -EINVAL;
}
*val_raw = chg_freq_list[i].val;
return 0;
}
static struct smb_mmi_params smb5_pm8150b_params = {
.fcc = {
.name = "fast charge current",
.reg = CHGR_FAST_CHARGE_CURRENT_CFG_REG,
.min_u = 0,
.max_u = 8000000,
.step_u = 50000,
},
.fv = {
.name = "float voltage",
.reg = CHGR_FLOAT_VOLTAGE_CFG_REG,
.min_u = 3600000,
.max_u = 4790000,
.step_u = 10000,
},
.usb_icl = {
.name = "usb input current limit",
.reg = USBIN_CURRENT_LIMIT_CFG_REG,
.min_u = 0,
.max_u = 5000000,
.step_u = 50000,
},
.dc_icl = {
.name = "DC input current limit",
.reg = DCDC_CFG_REF_MAX_PSNS_REG,
.min_u = 0,
.max_u = 1500000,
.step_u = 50000,
},
.aicl_cont_threshold = {
.name = "AICL CONT threshold",
.reg = USBIN_CONT_AICL_THRESHOLD_REG,
.min_u = 4000,
.max_u = 11800,
.step_u = 100,
.get_proc = smblib_get_aicl_cont_threshold,
.set_proc = smblib_set_aicl_cont_threshold,
},
.freq_switcher = {
.name = "switching frequency",
.reg = DCDC_FSW_SEL_REG,
.min_u = 600,
.max_u = 1200,
.step_u = 400,
.set_proc = smblib_set_chg_freq,
},
};
static struct smb_mmi_params smb5_pmi632_params = {
.fcc = {
.name = "fast charge current",
.reg = CHGR_FAST_CHARGE_CURRENT_CFG_REG,
.min_u = 0,
.max_u = 3000000,
.step_u = 50000,
},
.fv = {
.name = "float voltage",
.reg = CHGR_FLOAT_VOLTAGE_CFG_REG,
.min_u = 3600000,
.max_u = 4800000,
.step_u = 10000,
},
.usb_icl = {
.name = "usb input current limit",
.reg = USBIN_CURRENT_LIMIT_CFG_REG,
.min_u = 0,
.max_u = 3000000,
.step_u = 50000,
},
.dc_icl = {
.name = "dc input current limit",
/* TODO: For now USBIN seems to be the way to set this */
.reg = USBIN_CURRENT_LIMIT_CFG_REG,
.min_u = 0,
.max_u = 3000000,
.step_u = 50000,
},
.freq_switcher = {
.name = "switching frequency",
.reg = DCDC_FSW_SEL_REG,
.min_u = 600,
.max_u = 1200,
.step_u = 400,
.set_proc = smblib_set_chg_freq,
},
};
enum smb_mmi_ext_iio_channels {
//smb5
SMB5_CTM_CURRENT_MAX = 0,
SMB5_CURRENT_MAX,
SMB5_TYPEC_MODE,
SMB5_DP_DM,
SMB5_PD_ACTIVE,
SMB5_USB_REAL_TYPE,
SMB5_HW_CURRENT_MAX,
SMB5_USB_INPUT_CURRENT_SETTLED,
SMB5_RERUN_AICL,
SMB5_SW_JEITA_ENABLED,
SMB5_PD_CURRENT_MAX,
//bms
SMB5_QG_VOLTAGE_NOW,
SMB5_QG_CURRENT_NOW,
SMB5_QG_TEMP,
SMB5_QG_CAPACITY,
SMB5_QG_CHARGE_FULL,
SMB5_QG_CHARGE_FULL_DESIGN,
SMB5_QG_BATT_FULL_CURRENT,
};
static const char * const smb_mmi_ext_iio_chan_name[] = {
//smb5
[SMB5_CTM_CURRENT_MAX] = "usb_ctm_current_max",
[SMB5_CURRENT_MAX] = "main_current_max",
[SMB5_TYPEC_MODE] = "usb_typec_mode",
[SMB5_DP_DM] = "battery_dp_dm",
[SMB5_PD_ACTIVE] = "usb_pd_active",
[SMB5_USB_REAL_TYPE] = "usb_real_type",
[SMB5_HW_CURRENT_MAX] = "usb_hw_current_max",
[SMB5_USB_INPUT_CURRENT_SETTLED] = "usb_input_current_settled",
[SMB5_RERUN_AICL] = "battery_rerun_aicl",
[SMB5_SW_JEITA_ENABLED] = "battery_sw_jeita_enabled",
[SMB5_PD_CURRENT_MAX] = "usb_pd_current_max",
//bms
[SMB5_QG_VOLTAGE_NOW] = "voltage_now",
[SMB5_QG_CURRENT_NOW] = "current_now",
[SMB5_QG_TEMP] = "temp",
[SMB5_QG_CAPACITY] = "capacity",
[SMB5_QG_CHARGE_FULL] = "charge_full",
[SMB5_QG_CHARGE_FULL_DESIGN] = "charge_full_design",
[SMB5_QG_BATT_FULL_CURRENT] = "batt_full_current",
};
bool is_chan_valid(struct smb_mmi_charger *chip,
enum smb_mmi_ext_iio_channels chan)
{
int rc;
if (IS_ERR(chip->ext_iio_chans[chan]))
return false;
if (!chip->ext_iio_chans[chan]) {
chip->ext_iio_chans[chan] = iio_channel_get(chip->dev,
smb_mmi_ext_iio_chan_name[chan]);
if (IS_ERR(chip->ext_iio_chans[chan])) {
rc = PTR_ERR(chip->ext_iio_chans[chan]);
if (rc == -EPROBE_DEFER)
chip->ext_iio_chans[chan] = NULL;
pr_err("Failed to get IIO channel %s, rc=%d\n",
smb_mmi_ext_iio_chan_name[chan], rc);
return false;
}
}
return true;
}
int smb_mmi_read_iio_chan(struct smb_mmi_charger *chip,
enum smb_mmi_ext_iio_channels chan, int *val)
{
int rc;
if (is_chan_valid(chip, chan)) {
rc = iio_read_channel_processed(
chip->ext_iio_chans[chan], val);
return (rc < 0) ? rc : 0;
}
return -EINVAL;
}
int smb_mmi_write_iio_chan(struct smb_mmi_charger *chip,
enum smb_mmi_ext_iio_channels chan, int val)
{
if (is_chan_valid(chip, chan))
return iio_write_channel_raw(chip->ext_iio_chans[chan],
val);
return -EINVAL;
}
int smblib_read_mmi(struct smb_mmi_charger *chg, u16 addr, u8 *val)
{
unsigned int value;
int rc = 0;
rc = regmap_read(chg->regmap, addr, &value);
if (rc >= 0)
*val = (u8)value;
return rc;
}
int smblib_write_mmi(struct smb_mmi_charger *chg, u16 addr, u8 val)
{
return regmap_write(chg->regmap, addr, val);
}
int smblib_masked_write_mmi(struct smb_mmi_charger *chg, u16 addr, u8 mask, u8 val)
{
return regmap_update_bits(chg->regmap, addr, mask, val);
}
int smblib_get_apsd_result(struct smb_mmi_charger *chg, int *type)
{
int rc = 0;
u8 stat;
int apsd;
rc = smblib_read_mmi(chg, APSD_STATUS_REG, &stat);
if (rc < 0) {
mmi_err(chg, "Couldn't read APSD_STATUS_REG rc=%d\n", rc);
return rc;
}
if (!(stat & APSD_DTC_STATUS_DONE_BIT))
return -EBUSY;
rc = smblib_read_mmi(chg, APSD_RESULT_STATUS_REG, &stat);
if (rc < 0) {
mmi_err(chg, "Couldn't read APSD_RESULT_STATUS_REG rc=%d\n", rc);
return rc;
}
if (stat & QC_3P0_BIT)
apsd = CHG_BC1P2_QC_3P0;
else if (stat & QC_2P0_BIT)
apsd = CHG_BC1P2_QC_2P0;
else if (stat & FLOAT_CHARGER_BIT)
apsd = CHG_BC1P2_FLOAT;
else if (stat & DCP_CHARGER_BIT)
apsd = CHG_BC1P2_DCP;
else if (stat & CDP_CHARGER_BIT)
apsd = CHG_BC1P2_CDP;
else if (stat & OCP_CHARGER_BIT)
apsd = CHG_BC1P2_OCP;
else if (stat & SDP_CHARGER_BIT)
apsd = CHG_BC1P2_SDP;
else
apsd = CHG_BC1P2_UNKNOWN;
*type = apsd;
return rc;
}
#define CHG_SHOW_MAX_SIZE 50
static u16 smblib_mmi_address = 0;
static ssize_t smblib_mmi_address_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
unsigned long rc;
unsigned long mode;
struct platform_device *pdev = to_platform_device(dev);
struct smb_mmi_charger *mmi_chip = platform_get_drvdata(pdev);
rc = kstrtoul(buf, 0, &mode);
if (rc) {
pr_err("SMBMMI: Invalid smblib mmi address value = %lu\n", mode);
return -EINVAL;
}
smblib_mmi_address = (u16)mode;
mmi_info(mmi_chip, "smblib mmi address value = 0x%04x\n", \
smblib_mmi_address);
return rc ? rc : count;
}
static DEVICE_ATTR(smblib_mmi_address, 0644,
NULL,
smblib_mmi_address_store);
static ssize_t smblib_mmi_data_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
u8 data = 0;
int rc = 0;
struct platform_device *pdev = to_platform_device(dev);
struct smb_mmi_charger *mmi_chip = platform_get_drvdata(pdev);
rc = smblib_read_mmi(mmi_chip, smblib_mmi_address, &data);
if (rc) {
mmi_err(mmi_chip, "Couldn't read address 0x%x rc=%d\n", \
smblib_mmi_address, rc);
return rc;
}
mmi_info(mmi_chip, "smblib mmi read data value = 0x%02x\n", data);
return scnprintf(buf, CHG_SHOW_MAX_SIZE, "%04x: %02x\n", \
smblib_mmi_address, data);
}
static ssize_t smblib_mmi_data_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
unsigned long data = 0;
int rc = 0;
struct platform_device *pdev = to_platform_device(dev);
struct smb_mmi_charger *mmi_chip = platform_get_drvdata(pdev);
rc = kstrtoul(buf, 0, &data);
if (rc) {
pr_err("SMBMMI: Invalid smblib mmi data value = %lu\n", data);
return -EINVAL;
}
rc = smblib_write_mmi(mmi_chip, smblib_mmi_address, (u8)data);
if (rc) {
mmi_err(mmi_chip, "Couldn't write address 0x%x rc=%d\n", \
smblib_mmi_address, rc);
return rc;
}
mmi_info(mmi_chip, "smblib mmi write data value = 0x%02x\n", (u8)data);
return rc ? rc : count;
}
static DEVICE_ATTR(smblib_mmi_data, 0644,
smblib_mmi_data_show,
smblib_mmi_data_store);
static ssize_t factory_image_mode_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
unsigned long r;
unsigned long mode;
struct platform_device *pdev = to_platform_device(dev);
struct smb_mmi_charger *mmi_chip = platform_get_drvdata(pdev);
r = kstrtoul(buf, 0, &mode);
if (r) {
pr_err("SMBMMI: Invalid factory image mode value = %lu\n", mode);
return -EINVAL;
}
if (!mmi_chip) {
pr_err("SMBMMI: chip not valid\n");
return -ENODEV;
}
mmi_chip->is_factory_image = (mode) ? true : false;
return r ? r : count;
}
static ssize_t factory_image_mode_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
int state;
struct platform_device *pdev = to_platform_device(dev);
struct smb_mmi_charger *mmi_chip = platform_get_drvdata(pdev);
if (!mmi_chip) {
pr_err("SMBMMI: chip not valid\n");
return -ENODEV;
}
state = (mmi_chip->is_factory_image) ? 1 : 0;
return scnprintf(buf, CHG_SHOW_MAX_SIZE, "%d\n", state);
}
static DEVICE_ATTR(factory_image_mode, 0644,
factory_image_mode_show,
factory_image_mode_store);
static ssize_t factory_charge_upper_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
int state;
struct platform_device *pdev = to_platform_device(dev);
struct smb_mmi_charger *mmi_chip = platform_get_drvdata(pdev);
if (!mmi_chip) {
pr_err("SMBMMI: chip not valid\n");
return -ENODEV;
}
state = mmi_chip->upper_limit_capacity;
return scnprintf(buf, CHG_SHOW_MAX_SIZE, "%d\n", state);
}
static DEVICE_ATTR(factory_charge_upper, 0444,
factory_charge_upper_show,
NULL);
#define MMI_CHIP_MODE_LOWER_LIMIT 35
#define MMI_CHIP_MODE_UPPER_LIMIT 80
static ssize_t force_demo_mode_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
unsigned long r;
unsigned long mode;
struct platform_device *pdev = to_platform_device(dev);
struct smb_mmi_charger *mmi_chip = platform_get_drvdata(pdev);
r = kstrtoul(buf, 0, &mode);
if (r) {
pr_err("SMBMMI: Invalid demo mode value = %lu\n", mode);
return -EINVAL;
}
if (!mmi_chip) {
pr_err("SMBMMI: chip not valid\n");
return -ENODEV;
}
if ((mode >= MMI_CHIP_MODE_LOWER_LIMIT) &&
(mode <= MMI_CHIP_MODE_UPPER_LIMIT))
mmi_chip->demo_mode = mode;
else
mmi_chip->demo_mode = MMI_CHIP_MODE_LOWER_LIMIT;
return r ? r : count;
}
static ssize_t force_demo_mode_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
int state;
struct platform_device *pdev = to_platform_device(dev);
struct smb_mmi_charger *mmi_chip = platform_get_drvdata(pdev);
if (!mmi_chip) {
pr_err("SMBMMI: chip not valid\n");
return -ENODEV;
}
state = mmi_chip->demo_mode;
return scnprintf(buf, CHG_SHOW_MAX_SIZE, "%d\n", state);
}
static DEVICE_ATTR(force_demo_mode, 0644,
force_demo_mode_show,
force_demo_mode_store);
#define MIN_TEMP_C -20
#define MAX_TEMP_C 60
#define MIN_MAX_TEMP_C 47
#define HYSTERESIS_DEGC 2
static ssize_t force_max_chrg_temp_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
unsigned long r;
unsigned long mode;
struct platform_device *pdev = to_platform_device(dev);
struct smb_mmi_charger *mmi_chip = platform_get_drvdata(pdev);
r = kstrtoul(buf, 0, &mode);
if (r) {
pr_err("SMBMMI: Invalid max temp value = %lu\n", mode);
return -EINVAL;
}
if (!mmi_chip) {
pr_err("SMBMMI: chip not valid\n");
return -ENODEV;
}
if ((mode >= MIN_MAX_TEMP_C) && (mode <= MAX_TEMP_C))
mmi_chip->max_chrg_temp = mode;
else
mmi_chip->max_chrg_temp = MAX_TEMP_C;
return r ? r : count;
}
static ssize_t force_max_chrg_temp_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
int state;
struct platform_device *pdev = to_platform_device(dev);
struct smb_mmi_charger *mmi_chip = platform_get_drvdata(pdev);
if (!mmi_chip) {
pr_err("SMBMMI: chip not valid\n");
return -ENODEV;
}
state = mmi_chip->max_chrg_temp;
return scnprintf(buf, CHG_SHOW_MAX_SIZE, "%d\n", state);
}
static DEVICE_ATTR(force_max_chrg_temp, 0644,
force_max_chrg_temp_show,
force_max_chrg_temp_store);
#define USBIN_CMD_IL_REG (USBIN_BASE + 0x40)
#define USBIN_SUSPEND_BIT BIT(0)
int smblib_set_usb_suspend(struct smb_mmi_charger *chg, bool suspend)
{
int rc = 0;
rc = smblib_masked_write_mmi(chg, USBIN_CMD_IL_REG, USBIN_SUSPEND_BIT,
suspend ? USBIN_SUSPEND_BIT : 0);
if (rc < 0)
mmi_err(chg, "Couldn't write %s to USBIN suspend rc=%d\n",
suspend ? "suspend" : "resume", rc);
return rc;
}
int smblib_get_usb_suspend(struct smb_mmi_charger *chg, int *suspend)
{
int rc = 0;
u8 temp = 0;
rc = smblib_read_mmi(chg, POWER_PATH_STATUS_REG, &temp);
if (rc < 0) {
mmi_err(chg, "Couldn't read POWER_PATH_STATUS_REG rc=%d\n", rc);
return rc;
}
*suspend = temp & USBIN_SUSPEND_STS_BIT;
return rc;
}
static ssize_t force_chg_usb_suspend_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
unsigned long r;
unsigned long mode;
struct platform_device *pdev = to_platform_device(dev);
struct smb_mmi_charger *mmi_chip = platform_get_drvdata(pdev);
r = kstrtoul(buf, 0, &mode);
if (r) {
pr_err("SMBMMI: Invalid usb suspend mode value = %lu\n", mode);
return -EINVAL;
}
if (!mmi_chip) {
pr_err("SMBMMI: chip not valid\n");
return -ENODEV;
}
r = smblib_set_usb_suspend(mmi_chip, (bool)mode);
return r ? r : count;
}
static ssize_t force_chg_usb_suspend_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
int state;
int ret;
struct platform_device *pdev = to_platform_device(dev);
struct smb_mmi_charger *mmi_chip = platform_get_drvdata(pdev);
if (!mmi_chip) {
pr_err("SMBMMI: chip not valid\n");
return -ENODEV;
}
ret = smblib_get_usb_suspend(mmi_chip, &state);
if (ret) {
mmi_err(mmi_chip, "USBIN_SUSPEND_BIT failed ret = %d\n", ret);
state = -EFAULT;
goto end;
}
end:
return scnprintf(buf, CHG_SHOW_MAX_SIZE, "%d\n", state);
}
static DEVICE_ATTR(force_chg_usb_suspend, 0664,
force_chg_usb_suspend_show,
force_chg_usb_suspend_store);
static ssize_t force_chg_fail_clear_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
unsigned long r;
unsigned long mode;
r = kstrtoul(buf, 0, &mode);
if (r) {
pr_err("SMBMMI: Invalid chg fail mode value = %lu\n", mode);
return -EINVAL;
}
/* do nothing for SMBCHG */
r = 0;
return r ? r : count;
}
static ssize_t force_chg_fail_clear_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
/* do nothing for SMBCHG */
return scnprintf(buf, CHG_SHOW_MAX_SIZE, "0\n");
}
static DEVICE_ATTR(force_chg_fail_clear, 0664,
force_chg_fail_clear_show,
force_chg_fail_clear_store);
#define CHARGING_ENABLE_CMD_REG (CHGR_BASE + 0x42)
#define CHARGING_ENABLE_CMD_BIT BIT(0)
static ssize_t force_chg_auto_enable_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
unsigned long r;
unsigned long mode;
struct platform_device *pdev = to_platform_device(dev);
struct smb_mmi_charger *mmi_chip = platform_get_drvdata(pdev);
r = kstrtoul(buf, 0, &mode);
if (r) {
pr_err("SMBMMI: Invalid chrg enable value = %lu\n", mode);
return -EINVAL;
}
if (!mmi_chip) {
pr_err("SMBMMI: chip not valid\n");
return -ENODEV;
}
r = smblib_masked_write_mmi(mmi_chip, CHARGING_ENABLE_CMD_REG,
CHARGING_ENABLE_CMD_BIT,
mode ? CHARGING_ENABLE_CMD_BIT : 0);
if (r < 0) {
mmi_err(mmi_chip, "Factory Couldn't %s charging rc=%d\n",
mode ? "enable" : "disable", (int)r);
return r;
}
return r ? r : count;
}
static ssize_t force_chg_auto_enable_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
int state;
int ret;
u8 value;
struct platform_device *pdev = to_platform_device(dev);
struct smb_mmi_charger *mmi_chip = platform_get_drvdata(pdev);
if (!mmi_chip) {
pr_err("SMBMMI: chip not valid\n");
state = -ENODEV;
goto end;
}
ret = smblib_read_mmi(mmi_chip, CHARGING_ENABLE_CMD_REG, &value);
if (ret) {
mmi_err(mmi_chip, "CHG_EN_BIT failed ret = %d\n", ret);
state = -EFAULT;
goto end;
}
state = (CHARGING_ENABLE_CMD_BIT & value) ? 1 : 0;
end:
return scnprintf(buf, CHG_SHOW_MAX_SIZE, "%d\n", state);
}
static DEVICE_ATTR(force_chg_auto_enable, 0664,
force_chg_auto_enable_show,
force_chg_auto_enable_store);
int smblib_set_charge_param(struct smb_mmi_charger *chg,
struct smb_mmi_chg_param *param, int val_u)
{
int rc = 0;
u8 val_raw;
if (param->set_proc) {
rc = param->set_proc(param, val_u, &val_raw);
if (rc < 0)
return -EINVAL;
} else {
if (val_u > param->max_u || val_u < param->min_u)
mmi_dbg(chg, "%s: %d is out of range [%d, %d]\n",
param->name, val_u,
param->min_u, param->max_u);
if (val_u > param->max_u)
val_u = param->max_u;
if (val_u < param->min_u)
val_u = param->min_u;
val_raw = (val_u - param->min_u) / param->step_u;
}
rc = smblib_write_mmi(chg, param->reg, val_raw);
if (rc < 0) {
mmi_err(chg, "%s: Couldn't write 0x%02x to 0x%04x rc=%d\n",
param->name, val_raw, param->reg, rc);
return rc;
}
mmi_dbg(chg, "%s = %d (0x%02x)\n", param->name, val_u, val_raw);
return rc;
}
int smblib_get_charge_param(struct smb_mmi_charger *chg,
struct smb_mmi_chg_param *param, int *val_u)
{
int rc = 0;
u8 val_raw;
rc = smblib_read_mmi(chg, param->reg, &val_raw);
if (rc < 0) {
mmi_err(chg, "%s: Couldn't read from 0x%04x rc=%d\n",
param->name, param->reg, rc);
return rc;
}
if (param->get_proc)
*val_u = param->get_proc(param, val_raw);
else
*val_u = val_raw * param->step_u + param->min_u;
mmi_dbg(chg, "%s = %d (0x%02x)\n", param->name, *val_u, val_raw);
return rc;
}
static ssize_t force_chg_ibatt_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
unsigned long r;
unsigned long chg_current;
struct platform_device *pdev = to_platform_device(dev);
struct smb_mmi_charger *mmi_chip = platform_get_drvdata(pdev);
r = kstrtoul(buf, 0, &chg_current);
if (r) {
pr_err("SMBMMI: Invalid ibatt value = %lu\n", chg_current);
return -EINVAL;
}
if (!mmi_chip) {
pr_err("SMBMMI: chip not valid\n");
return -ENODEV;
}
chg_current *= 1000; /* Convert to uA */
if(mmi_chip->fcc_votable) {
pmic_vote_force_val_set(mmi_chip->fcc_votable, chg_current);
pmic_vote_force_active_set(mmi_chip->fcc_votable, 1);
}
return r ? r : count;
}
static ssize_t force_chg_ibatt_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
int state;
int ret;
struct platform_device *pdev = to_platform_device(dev);
struct smb_mmi_charger *mmi_chip = platform_get_drvdata(pdev);
if (!mmi_chip) {
pr_err("SMBMMI: chip not valid\n");
state = -ENODEV;
goto end;
}
ret = smblib_get_charge_param(mmi_chip, &mmi_chip->param.fcc, &state);
if (ret < 0) {
mmi_err(mmi_chip, "Factory Couldn't get master fcc rc=%d\n", (int)ret);
return ret;
}
state /= 1000; /* Convert to mA */
end:
return scnprintf(buf, CHG_SHOW_MAX_SIZE, "%d\n", state);
}
static DEVICE_ATTR(force_chg_ibatt, 0664,
force_chg_ibatt_show,
force_chg_ibatt_store);
static ssize_t force_chg_iusb_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
unsigned long r;
unsigned long usb_curr;
struct platform_device *pdev = to_platform_device(dev);
struct smb_mmi_charger *mmi_chip = platform_get_drvdata(pdev);
r = kstrtoul(buf, 0, &usb_curr);
if (r) {
pr_err("SMBMMI: Invalid iusb value = %lu\n", usb_curr);
return -EINVAL;
}
if (!mmi_chip) {
pr_err("SMBMMI: chip not valid\n");
return -ENODEV;
}
usb_curr *= 1000; /* Convert to uA */
if (mmi_chip->usb_icl_votable) {
pmic_vote_force_val_set(mmi_chip->usb_icl_votable, usb_curr);
pmic_vote_force_active_set(mmi_chip->usb_icl_votable, 1);
}
r = smblib_masked_write_mmi(mmi_chip, USBIN_ICL_OPTIONS_REG,
USBIN_MODE_CHG_BIT, USBIN_MODE_CHG_BIT);
if (r < 0)
mmi_err(mmi_chip, "Couldn't set USBIN_ICL_OPTIONS r=%d\n", (int)r);
r = smblib_masked_write_mmi(mmi_chip, USBIN_LOAD_CFG_REG,
ICL_OVERRIDE_AFTER_APSD_BIT,
ICL_OVERRIDE_AFTER_APSD_BIT);
if (r < 0)
mmi_err(mmi_chip, "Couldn't set USBIN_LOAD_CFG rc=%d\n", (int)r);
r = smblib_masked_write_mmi(mmi_chip, USBIN_AICL_OPTIONS_CFG_REG,
0xFF, 0x00);
if (r < 0)
mmi_err(mmi_chip, "Couldn't set USBIN_AICL_OPTIONS rc=%d\n", (int)r);
return r ? r : count;
}
static ssize_t force_chg_iusb_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
int state;
int r;
struct platform_device *pdev = to_platform_device(dev);
struct smb_mmi_charger *mmi_chip = platform_get_drvdata(pdev);
if (!mmi_chip) {
pr_err("chip not valid\n");
state = -ENODEV;
goto end;
}
r = smblib_get_charge_param(mmi_chip, &mmi_chip->param.usb_icl, &state);
if (r < 0) {
mmi_err(mmi_chip, "Factory Couldn't get usb_icl rc=%d\n", (int)r);
return r;
}
state /= 1000; /* Convert to mA */
end:
return scnprintf(buf, CHG_SHOW_MAX_SIZE, "%d\n", state);
}
static DEVICE_ATTR(force_chg_iusb, 0664,
force_chg_iusb_show,
force_chg_iusb_store);
static ssize_t force_chg_idc_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
unsigned long r;
unsigned long dc_curr;
struct platform_device *pdev = to_platform_device(dev);
struct smb_mmi_charger *mmi_chip = platform_get_drvdata(pdev);
r = kstrtoul(buf, 0, &dc_curr);
if (r) {
pr_err("SMBMMI: Invalid idc value = %lu\n", dc_curr);
return -EINVAL;
}
if (!mmi_chip) {
pr_err("SMBMMI: chip not valid\n");
return -ENODEV;
}
dc_curr *= 1000; /* Convert to uA */
r = smblib_set_charge_param(mmi_chip, &mmi_chip->param.dc_icl, dc_curr);
if (r < 0) {
mmi_err(mmi_chip, "Factory Couldn't set dc icl = %d rc=%d\n",
(int)dc_curr, (int)r);
return r;
}
return r ? r : count;
}
static ssize_t force_chg_idc_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
int state;
int r;
struct platform_device *pdev = to_platform_device(dev);
struct smb_mmi_charger *mmi_chip = platform_get_drvdata(pdev);
if (!mmi_chip) {
pr_err("SMBMMI: chip not valid\n");
state = -ENODEV;
goto end;
}
r = smblib_get_charge_param(mmi_chip, &mmi_chip->param.dc_icl, &state);
if (r < 0) {
mmi_err(mmi_chip, "Factory Couldn't get dc_icl rc=%d\n", (int)r);
return r;
}
state /= 1000; /* Convert to mA */
end:
return scnprintf(buf, CHG_SHOW_MAX_SIZE, "%d\n", state);
}
static DEVICE_ATTR(force_chg_idc, 0664,
force_chg_idc_show,
force_chg_idc_store);
#define PRE_CHARGE_CURRENT_CFG_REG (CHGR_BASE + 0x60)
#define PRE_CHARGE_CURRENT_SETTING_MASK 0x07
#define PRE_CHARGE_CONV_MV 50
#define PRE_CHARGE_MAX 7
#define PRE_CHARGE_MIN 100
#define PRE_CHARGE_SMB2_CONV_MV 25
#define PRE_CHARGE_SMB2_MAX 0x3F
static ssize_t force_chg_itrick_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
unsigned long r;
unsigned long chg_current;
u8 value, mask;
struct platform_device *pdev = to_platform_device(dev);
struct smb_mmi_charger *mmi_chip = platform_get_drvdata(pdev);
r = kstrtoul(buf, 0, &chg_current);
if (r) {
pr_err("SMBMMI: Invalid pre-charge value = %lu\n", chg_current);
return -EINVAL;
}
if (!mmi_chip) {
pr_err("SMBMMI: chip not valid\n");
return -ENODEV;
}
switch (mmi_chip->smb_version) {
case PM8150B:
case PM7250B:
case PMI632:
mask = PRE_CHARGE_CURRENT_SETTING_MASK;
if (chg_current < PRE_CHARGE_MIN)
chg_current = 0;
else
chg_current -= PRE_CHARGE_MIN;
chg_current /= PRE_CHARGE_CONV_MV;
if (chg_current > PRE_CHARGE_MAX)
value = PRE_CHARGE_MAX;
else
value = (u8)chg_current;
break;
default:
mmi_err(mmi_chip, "Set ITRICK PMIC subtype %d not supported\n",
mmi_chip->smb_version);
return -EINVAL;
}
r = smblib_masked_write_mmi(mmi_chip, PRE_CHARGE_CURRENT_CFG_REG,
mask,
value);
if (r < 0) {
mmi_err(mmi_chip, "Factory Couldn't set ITRICK %d mV rc=%d\n",
(int)value, (int)r);
return r;
}
return r ? r : count;
}
static ssize_t force_chg_itrick_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
int state;
int ret;
u8 value;
struct platform_device *pdev = to_platform_device(dev);
struct smb_mmi_charger *mmi_chip = platform_get_drvdata(pdev);
if (!mmi_chip) {
pr_err("SMBMMI: chip not valid\n");
state = -ENODEV;
goto end;
}
ret = smblib_read_mmi(mmi_chip, PRE_CHARGE_CURRENT_CFG_REG, &value);
if (ret) {
mmi_err(mmi_chip, "Pre Chg ITrick failed ret = %d\n", ret);
state = -EFAULT;
goto end;
}
switch (mmi_chip->smb_version) {
case PM8150B:
case PM7250B:
case PMI632:
value &= PRE_CHARGE_CURRENT_SETTING_MASK;
state = (value * PRE_CHARGE_CONV_MV) + PRE_CHARGE_MIN;
break;
default:
mmi_err(mmi_chip, "Get ITRICK PMIC subtype %d not supported\n",
mmi_chip->smb_version);
return -EINVAL;
}
end:
return scnprintf(buf, CHG_SHOW_MAX_SIZE, "%d\n", state);
}
static DEVICE_ATTR(force_chg_itrick, 0664,
force_chg_itrick_show,
force_chg_itrick_store);
enum {
MMI_FACTORY_MODE,
MMI_FACTORY_BUILD,
};
static bool mmi_factory_check(int type)
{
struct device_node *np = of_find_node_by_path("/chosen");
bool factory = false;
const char *bootargs = NULL;
char *bl_version = NULL;
char *end = NULL;
if (!np)
return factory;
switch (type) {
case MMI_FACTORY_MODE:
factory = of_property_read_bool(np,
"mmi,factory-cable");
break;
case MMI_FACTORY_BUILD:
if (!of_property_read_string(np, "bootargs", &bootargs)) {
bl_version = strstr(bootargs, "androidboot.bootloader=");
if (bl_version) {
end = strpbrk(bl_version, " ");
bl_version = strpbrk(bl_version, "=");
}
if (bl_version && end > bl_version &&
strnstr(bl_version, "factory", end - bl_version)) {
factory = true;
}
}
break;
default:
factory = false;
}
of_node_put(np);
return factory;
}
#define CHARGER_POWER_15W 15000
#define CHARGER_POWER_18W 18000
#define CHARGER_POWER_20W 20000
static void mmi_charger_power_support(struct smb_mmi_charger *chg)
{
struct device_node *np = chg->dev->of_node;
const char *charger_ability = NULL;
int retval;
retval = of_property_read_u32(np, "qcom,pd-power-max",
&chg->pd_power_max);
if (retval) {
chg->pd_power_max = CHARGER_POWER_18W;
}
chg->pd_handle =
devm_usbpd_get_by_phandle(chg->dev, "qcom,usbpd-phandle");
if (IS_ERR_OR_NULL(chg->pd_handle)) {
dev_err(chg->dev, "Error getting the pd phandle %ld\n",
PTR_ERR(chg->pd_handle));
chg->pd_handle = NULL;
}
if (of_property_read_bool(np, "qcom,force-hvdcp-5v")) {
chg->hvdcp_power_max = CHARGER_POWER_15W;
return;
}
retval = of_property_read_u32(np, "qcom,hvdcp-power-max",
&chg->hvdcp_power_max);
if (retval) {
chg->hvdcp_power_max = 0;
return;
}
np = of_find_node_by_path("/chosen");
if (!np)
return;
retval = of_property_read_string(np, "mmi,charger",
&charger_ability);
if ((retval == -EINVAL) || !charger_ability) {
mmi_err(chg, "mmi,charger unused\n");
of_node_put(np);
return;
} else
mmi_info(chg, "charger = %s\n", charger_ability);
if (strstr(charger_ability, "15W"))
chg->hvdcp_power_max = CHARGER_POWER_15W;
else if (strstr(charger_ability, "18W"))
chg->hvdcp_power_max = CHARGER_POWER_18W;
else if (strstr(charger_ability, "20W"))
chg->hvdcp_power_max = CHARGER_POWER_20W;
of_node_put(np);
return;
}
static enum power_supply_property smb_mmi_battery_props[] = {
POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_CURRENT_MAX,
};
static int smb_mmi_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
switch (psp) {
case POWER_SUPPLY_PROP_PRESENT:
val->intval = 1;
break;
case POWER_SUPPLY_PROP_CURRENT_MAX:
val->intval = -EINVAL;
break;
default:
return -EINVAL;
}
return 0;
}
static int smb_mmi_set_property(struct power_supply *psy,
enum power_supply_property psp,
const union power_supply_propval *val)
{
struct smb_mmi_charger *chip = power_supply_get_drvdata(psy);
int rc = 0;
int override = 0;
switch (psp) {
case POWER_SUPPLY_PROP_CURRENT_MAX:
if (val->intval) {
rc = smb_mmi_write_iio_chan(chip,
SMB5_CTM_CURRENT_MAX, val->intval);
rc = smb_mmi_write_iio_chan(chip,
SMB5_CURRENT_MAX, val->intval);
override = USBIN_ICL_OVERRIDE_BIT;
}
mmi_warn(chip, "Request for ICL to %d uA\n", val->intval);
rc = smblib_masked_write_mmi(chip,
USBIN_CMD_ICL_OVERRIDE_REG,
USBIN_ICL_OVERRIDE_BIT,
override);
if (rc < 0)
mmi_err(chip,
"Couldn't set USBIN_CMD_ICL_OVERRIDE rc=%d\n", rc);
break;
default:
rc = -EINVAL;
}
return rc;
}
static bool mmi_find_temp_zone(struct smb_mmi_charger *chg,
struct mmi_sm_params *chip,
int temp_c)
{
int prev_zone, num_zones;
struct mmi_temp_zone *zones;
int hotter_t, hotter_fcc;
int colder_t, colder_fcc;
int i;
int max_temp;
if (!chg) {
pr_debug("SMBMMI: called before chg valid!\n");
return false;
}
zones = chip->temp_zones;
num_zones = chip->num_temp_zones;
prev_zone = chip->pres_temp_zone;
if (chg->max_chrg_temp >= MIN_MAX_TEMP_C)
max_temp = chg->max_chrg_temp;
else
max_temp = zones[num_zones - 1].temp_c;
if (prev_zone == ZONE_NONE) {
for (i = num_zones - 1; i >= 0; i--) {
if (temp_c >= zones[i].temp_c) {
if (i == num_zones - 1)
chip->pres_temp_zone = ZONE_HOT;
else
chip->pres_temp_zone = i + 1;
return true;
}
}
chip->pres_temp_zone = ZONE_COLD;
return true;
}
if (prev_zone == ZONE_COLD) {
if (temp_c >= MIN_TEMP_C + HYSTERESIS_DEGC)
chip->pres_temp_zone = ZONE_FIRST;
} else if (prev_zone == ZONE_HOT) {
if (temp_c <= max_temp - HYSTERESIS_DEGC)
chip->pres_temp_zone = num_zones - 1;
} else {
if (prev_zone == ZONE_FIRST) {
hotter_fcc = zones[prev_zone + 1].fcc_max_ma;
colder_fcc = 0;
hotter_t = zones[prev_zone].temp_c;
colder_t = MIN_TEMP_C;
} else if (prev_zone == num_zones - 1) {
hotter_fcc = 0;
colder_fcc = zones[prev_zone - 1].fcc_max_ma;
hotter_t = zones[prev_zone].temp_c;
colder_t = zones[prev_zone - 1].temp_c;
} else {
hotter_fcc = zones[prev_zone + 1].fcc_max_ma;
colder_fcc = zones[prev_zone - 1].fcc_max_ma;
hotter_t = zones[prev_zone].temp_c;
colder_t = zones[prev_zone - 1].temp_c;
}
if (zones[prev_zone].fcc_max_ma < hotter_fcc)
hotter_t += HYSTERESIS_DEGC;
if (zones[prev_zone].fcc_max_ma < colder_fcc)
colder_t -= HYSTERESIS_DEGC;
if (temp_c < MIN_TEMP_C)
chip->pres_temp_zone = ZONE_COLD;
else if (temp_c >= max_temp)
chip->pres_temp_zone = ZONE_HOT;
else if (temp_c >= hotter_t)
chip->pres_temp_zone++;
else if (temp_c < colder_t)
chip->pres_temp_zone--;
}
if (prev_zone != chip->pres_temp_zone) {
mmi_dbg(chg, "Entered Temp Zone %d!\n",
chip->pres_temp_zone);
return true;
}
return false;
}
static int get_prop_batt_voltage_now(struct smb_mmi_charger *chg,
struct power_supply *psy,
union power_supply_propval *val)
{
int rc;
if (!psy)
return -EINVAL;
if (!strcmp(psy->desc->name, "bms")) {
rc = smb_mmi_read_iio_chan(chg, SMB5_QG_VOLTAGE_NOW, &val->intval);
} else
rc = power_supply_get_property(psy,
POWER_SUPPLY_PROP_VOLTAGE_NOW, val);
return rc;
}
static int get_prop_batt_current_now(struct smb_mmi_charger *chg,
struct power_supply *psy,
union power_supply_propval *val)
{
int rc;
if (!psy)
return -EINVAL;
if (!strcmp(psy->desc->name, "bms")) {
rc = smb_mmi_read_iio_chan(chg, SMB5_QG_CURRENT_NOW, &val->intval);
} else
rc = power_supply_get_property(psy,
POWER_SUPPLY_PROP_CURRENT_NOW, val);
return rc;
}
static int get_prop_usb_voltage_now(struct smb_mmi_charger *chg,
union power_supply_propval *val)
{
int rc;
if (!chg->usb_psy)
return -EINVAL;
rc = power_supply_get_property(chg->usb_psy,
POWER_SUPPLY_PROP_VOLTAGE_NOW, val);
return rc;
}
static int get_prop_dc_voltage_now(struct smb_mmi_charger *chg,
union power_supply_propval *val)
{
int rc;
if (!chg->dc_psy)
return -EINVAL;
rc = power_supply_get_property(chg->dc_psy,
POWER_SUPPLY_PROP_VOLTAGE_NOW, val);
return rc;
}
static int get_prop_batt_temp(struct smb_mmi_charger *chg,
struct power_supply *psy,
union power_supply_propval *val)
{
int rc;
if (!psy)
return -EINVAL;
if (!strcmp(psy->desc->name, "bms")) {
rc = smb_mmi_read_iio_chan(chg, SMB5_QG_TEMP, &val->intval);
} else
rc = power_supply_get_property(psy,
POWER_SUPPLY_PROP_TEMP, val);
return rc;
}
static int get_prop_batt_capacity(struct smb_mmi_charger *chg,
struct power_supply *psy,
union power_supply_propval *val)
{
int rc = -EINVAL;
if (!psy)
return -EINVAL;
if (!strcmp(psy->desc->name, "bms")) {
rc = smb_mmi_read_iio_chan(chg, SMB5_QG_CAPACITY, &val->intval);
} else
rc = power_supply_get_property(psy,
POWER_SUPPLY_PROP_CAPACITY, val);
return rc;
}
static int get_prop_usb_present(struct smb_mmi_charger *chg,
union power_supply_propval *val)
{
int rc;
u8 stat;
rc = smblib_read_mmi(chg, USBIN_INT_RT_STS, &stat);
if (rc < 0) {
mmi_err(chg, "Couldn't read USBIN_RT_STS rc=%d\n", rc);
return rc;
}
val->intval = (bool)(stat & USBIN_PLUGIN_RT_STS_BIT);
return 0;
}
static int get_prop_dc_present(struct smb_mmi_charger *chg,
union power_supply_propval *val)
{
int rc;
u8 stat;
/*
* PMI632 has no DCIN support and address at 0x14XX is undefined,
* so just return 0 to avoid too much err log and heatbeat_work
* break
*/
if (chg->smb_version == PMI632) {
val->intval = 0;
return 0;
}
rc = smblib_read_mmi(chg, DCIN_INT_RT_STS, &stat);
if (rc < 0) {
mmi_err(chg, "Couldn't read DCIN_RT_STS rc=%d\n", rc);
return rc;
}
val->intval = (bool)(stat & DCIN_PLUGIN_RT_STS_BIT);
return 0;
}
static int get_prop_charger_present(struct smb_mmi_charger *chg,
union power_supply_propval *val)
{
int rc = -EINVAL;
val->intval = 0;
if (chg->usb_psy) {
rc = power_supply_get_property(chg->usb_psy,
POWER_SUPPLY_PROP_ONLINE, val);
if (rc < 0)
mmi_err(chg, "Couldn't read USB online rc=%d\n", rc);
else if (val->intval)
return rc;
}
if (chg->pc_port_psy) {
rc = power_supply_get_property(chg->pc_port_psy,
POWER_SUPPLY_PROP_ONLINE, val);
if (rc < 0)
mmi_err(chg, "Couldn't read PC online rc=%d\n", rc);
else if (val->intval)
return rc;
}
rc = smb_mmi_read_iio_chan(chg,
SMB5_TYPEC_MODE, &val->intval);
if (rc < 0)
mmi_err(chg, "Couldn't read typec mode rc=%d\n", rc);
else if (val->intval == QTI_POWER_SUPPLY_TYPEC_SOURCE_DEFAULT ||
val->intval == QTI_POWER_SUPPLY_TYPEC_SOURCE_MEDIUM ||
val->intval == QTI_POWER_SUPPLY_TYPEC_SOURCE_HIGH) {
val->intval = 1;
return rc;
}
if (chg->wls_psy) {
rc = power_supply_get_property(chg->wls_psy,
POWER_SUPPLY_PROP_ONLINE, val);
if (rc < 0)
mmi_err(chg, "Couldn't read wls online rc=%d\n", rc);
else if (val->intval)
return rc;
}
//intval can be changed by get_property call we should reset to 0
//this is true for a OTG cable
val->intval = 0;
return rc;
}
#define USBIN_500MA 500000
#define USBIN_900MA 900000
int mmi_usb_icl_override(struct smb_mmi_charger *chg, int icl)
{
int rc = 0;
int apsd;
u8 usb51_mode, icl_override, apsd_override;
union power_supply_propval val;
if (chg->factory_mode)
return rc;
val.intval = QTI_POWER_SUPPLY_TYPEC_NONE;
rc = smb_mmi_read_iio_chan(chg,
SMB5_TYPEC_MODE, &val.intval);
if (rc < 0 || val.intval != QTI_POWER_SUPPLY_TYPEC_NONE)
return rc;
rc = smblib_get_apsd_result(chg, &apsd);
if (rc < 0 || apsd == CHG_BC1P2_UNKNOWN)
return rc;
if (apsd == CHG_BC1P2_SDP &&
(icl == USBIN_900MA || icl <= USBIN_500MA)) {
usb51_mode = 0;
apsd_override = 0;
icl_override = USBIN_ICL_OVERRIDE_BIT;
} else {
usb51_mode = USBIN_MODE_CHG_BIT;
apsd_override = ICL_OVERRIDE_AFTER_APSD_BIT;
icl_override = USBIN_ICL_OVERRIDE_BIT;
}
rc = smb_mmi_write_iio_chan(chg,
SMB5_CURRENT_MAX, icl);
if (rc < 0) {
mmi_err(chg, "Couldn't set usb icl, rc=%d\n", rc);
}
rc = smblib_masked_write_mmi(chg, USBIN_ICL_OPTIONS_REG,
USBIN_MODE_CHG_BIT,
usb51_mode);
if (rc < 0)
mmi_err(chg,
"Couldn't set USBIN_ICL_OPTIONS rc=%d\n", rc);
rc = smblib_masked_write_mmi(chg, USBIN_CMD_ICL_OVERRIDE_REG,
USBIN_ICL_OVERRIDE_BIT,
icl_override);
if (rc < 0)
mmi_err(chg,
"Couldn't set USBIN_CMD_ICL_OVERRIDE rc=%d\n", rc);
rc = smblib_masked_write_mmi(chg, USBIN_LOAD_CFG_REG,
ICL_OVERRIDE_AFTER_APSD_BIT,
apsd_override);
if (rc < 0)
mmi_err(chg,
"Couldn't set USBIN_LOAD_CFG rc=%d\n", rc);
return rc;
}
#define HVDCP_VOLTAGE_BASIC (this_chip->hvdcp_power_max / 3)
#define HVDCP_VOLTAGE_NOM (HVDCP_VOLTAGE_BASIC - 200)
#define HVDCP_VOLTAGE_MAX (HVDCP_VOLTAGE_BASIC + 200)
#define HVDCP_VOLTAGE_MIN 4000
#define HVDCP_PULSE_COUNT_MAX ((HVDCP_VOLTAGE_BASIC - 5000) / 200 + 2)
static int mmi_increase_vbus_power(struct smb_mmi_charger *chg, int cur_mv)
{
int rc = -EINVAL;
union power_supply_propval val = {0, };
int pulse_cnt;
rc = smb_mmi_read_iio_chan(chg, SMB5_DP_DM, &val.intval);
if (rc < 0) {
mmi_err(chg, "Couldn't read dpdm pulse count rc=%d\n", rc);
return cur_mv;
} else {
mmi_info(chg, "DP DM pulse count = %d\n", val.intval);
pulse_cnt = val.intval;
}
/* Maintain vbus voltage for QC3.0 power/3A charging */
if (pulse_cnt == 0 || chg->inc_hvdcp_cnt != HVDCP_PULSE_COUNT_MAX) {
chg->inc_hvdcp_cnt = HVDCP_PULSE_COUNT_MAX;
vote(chg->chg_dis_votable, MMI_HB_VOTER, true, 0);
while (cur_mv < HVDCP_VOLTAGE_NOM && pulse_cnt < chg->inc_hvdcp_cnt) {
val.intval = QTI_POWER_SUPPLY_DP_DM_DP_PULSE;
rc = smb_mmi_write_iio_chan(chg, SMB5_DP_DM, val.intval);
if (rc < 0) {
mmi_err(chg, "Couldn't set dpdm pulse rc=%d\n", rc);
break;
}
msleep(100);
rc = get_prop_usb_voltage_now(chg, &val);
if (rc < 0) {
mmi_err(chg, "Error getting USB Voltage rc = %d\n", rc);
break;
} else
cur_mv = val.intval / 1000;
rc = smb_mmi_read_iio_chan(chg, SMB5_DP_DM, &val.intval);
if (rc < 0) {
mmi_err(chg, "Couldn't read dpdm pulse count rc=%d\n", rc);
break;
} else {
pulse_cnt = val.intval;
}
mmi_info(chg, "pulse count = %d, cur_mv = %d\n", pulse_cnt, cur_mv);
}
rc = smblib_set_charge_param(chg, &chg->param.aicl_cont_threshold,
HVDCP_VOLTAGE_NOM - 1000);
if (rc < 0) {
mmi_err(chg, "Couldn't set aicl cont threshold to 9V rc=%d\n", rc);
}
vote(chg->chg_dis_votable, MMI_HB_VOTER, false, 0);
}
return cur_mv;
}
static ssize_t force_hvdcp_power_max_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
unsigned long r;
unsigned long power;
struct platform_device *pdev = to_platform_device(dev);
struct smb_mmi_charger *mmi_chip = platform_get_drvdata(pdev);
r = kstrtoul(buf, 0, &power);
if (r) {
pr_err("SMBMMI: Invalid hvdcp_power_max value = %lu\n", power);
return -EINVAL;
}
if (!mmi_chip) {
pr_err("SMBMMI: chip not valid\n");
return -ENODEV;
}
if ((power >= CHARGER_POWER_15W) &&
(power <= CHARGER_POWER_20W) &&
(mmi_chip->hvdcp_power_max != power)) {
mmi_chip->hvdcp_power_max = power;
r = smblib_masked_write_mmi(mmi_chip, HVDCP_PULSE_COUNT_MAX_REG,
HVDCP_PULSE_COUNT_MAX_QC2_MASK |
HVDCP_PULSE_COUNT_MAX_QC3_MASK,
HVDCP_PULSE_COUNT_MAX);
if (r < 0) {
mmi_err(mmi_chip, "Could not set HVDCP pulse count max\n");
return r;
}
cancel_delayed_work(&mmi_chip->heartbeat_work);
schedule_delayed_work(&mmi_chip->heartbeat_work,
msecs_to_jiffies(100));
mmi_info(mmi_chip, "Reset hvdcp power max as %d, "
"Reschedule heartbeat\n",
mmi_chip->hvdcp_power_max);
}
return r ? r : count;
}
static ssize_t force_hvdcp_power_max_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
int power;
struct platform_device *pdev = to_platform_device(dev);
struct smb_mmi_charger *mmi_chip = platform_get_drvdata(pdev);
if (!mmi_chip) {
pr_err("SMBMMI: chip not valid\n");
return -ENODEV;
}
power = mmi_chip->hvdcp_power_max;
return scnprintf(buf, CHG_SHOW_MAX_SIZE, "%d\n", power);
}
static DEVICE_ATTR(force_hvdcp_power_max, 0644,
force_hvdcp_power_max_show,
force_hvdcp_power_max_store);
static ssize_t force_pd_power_max_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
unsigned long r;
unsigned long power;
struct platform_device *pdev = to_platform_device(dev);
struct smb_mmi_charger *mmi_chip = platform_get_drvdata(pdev);
r = kstrtoul(buf, 0, &power);
if (r) {
pr_err("SMBMMI: Invalid pd_power_max value = %lu\n", power);
return -EINVAL;
}
if (!mmi_chip) {
pr_err("SMBMMI: chip not valid\n");
return -ENODEV;
}
if ((power >= CHARGER_POWER_15W) &&
(power <= CHARGER_POWER_20W) &&
(mmi_chip->pd_power_max != power)) {
mmi_chip->pd_power_max = power;
cancel_delayed_work(&mmi_chip->heartbeat_work);
schedule_delayed_work(&mmi_chip->heartbeat_work,
msecs_to_jiffies(100));
mmi_info(mmi_chip, "Reset pd power max as %d, "
"Reschedule heartbeat\n",
mmi_chip->pd_power_max);
}
return r ? r : count;
}
static ssize_t force_pd_power_max_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
int power;
struct platform_device *pdev = to_platform_device(dev);
struct smb_mmi_charger *mmi_chip = platform_get_drvdata(pdev);
if (!mmi_chip) {
pr_err("SMBMMI: chip not valid\n");
return -ENODEV;
}
power = mmi_chip->pd_power_max;
return scnprintf(buf, CHG_SHOW_MAX_SIZE, "%d\n", power);
}
static DEVICE_ATTR(force_pd_power_max, 0644,
force_pd_power_max_show,
force_pd_power_max_store);
static int smblib_set_opt_switcher_freq(struct smb_mmi_charger *chg, int fsw_khz)
{
int rc = 0;
rc = smblib_set_charge_param(chg, &chg->param.freq_switcher, fsw_khz);
if (rc < 0)
mmi_err(chg, "Error in setting freq_buck rc=%d\n", rc);
return rc;
}
#define MICRO_1V 1000000
#define MICRO_5V 5000000
#define MICRO_9V 9000000
#define MICRO_12V 12000000
static int smblib_set_usb_pd_fsw(struct smb_mmi_charger *chg, int voltage)
{
int rc = 0;
if (voltage == MICRO_5V)
rc = smblib_set_opt_switcher_freq(chg, chg->chg_freq.freq_5V);
else if (voltage > MICRO_5V && voltage < MICRO_9V)
rc = smblib_set_opt_switcher_freq(chg,
chg->chg_freq.freq_6V_8V);
else if (voltage >= MICRO_9V && voltage < MICRO_12V)
rc = smblib_set_opt_switcher_freq(chg, chg->chg_freq.freq_9V);
else if (voltage == MICRO_12V)
rc = smblib_set_opt_switcher_freq(chg, chg->chg_freq.freq_12V);
else {
mmi_err(chg, "Couldn't set Fsw: invalid voltage %d\n",
voltage);
return -EINVAL;
}
return rc;
}
#define PD_SRC_PDO_TYPE_FIXED 0
#define PD_SRC_PDO_TYPE_BATTERY 1
#define PD_SRC_PDO_TYPE_VARIABLE 2
#define PD_SRC_PDO_TYPE_AUGMENTED 3
#define PPS_VOLT_MIN 5000000
#define PPS_CURR_MIN 2000000
#define PPS_CURR_MAX 3000000
static void mmi_chrg_usb_vin_pd_config(struct smb_mmi_charger *chg, int vbus_mv)
{
int rc = -EINVAL, i =0, req_pdo = -1, req_pd_volt = 0, req_pd_curr = 0;
int fixed_pd_volt = 0;
union power_supply_propval val = {0, };
bool pd_active = false, vbus_present = false, pps_active = false, fixed_active = false;
static bool fsw_setted = false, fixed_power_done = false;
if (!chg->usb_psy || !chg->pd_handle || chg->cp_active)
return;
rc = get_prop_usb_present(chg, &val);
if (rc) {
mmi_err(chg, "Unable to read USB PRESENT: %d\n", rc);
return;
}
vbus_present = val.intval;
rc = smb_mmi_read_iio_chan(chg, SMB5_PD_ACTIVE, &val.intval);
if (rc) {
mmi_err(chg, "Unable to read PD ACTIVE: %d\n", rc);
return;
}
pd_active = val.intval;
if (!pd_active || !vbus_present) {
fsw_setted = false;
fixed_power_done = false;
return;
}
memset(chg->mmi_pdo_info, 0,
sizeof(struct usbpd_pdo_info) * PD_MAX_PDO_NUM);
rc = usbpd_get_pdo_info(chg->pd_handle, chg->mmi_pdo_info, PD_MAX_PDO_NUM);
if (rc ) {
mmi_err(chg, "PD not ready, %d\n", rc);
return;
}
for (i = 0; i < PD_MAX_PDO_NUM; i++) {
if ((chg->mmi_pdo_info[i].type == PD_SRC_PDO_TYPE_AUGMENTED)
&& chg->mmi_pdo_info[i].uv_max >= PPS_VOLT_MIN
&& chg->mmi_pdo_info[i].ua >= PPS_CURR_MIN
&& (chg->mmi_pdo_info[i].uv_max / 1000) * (chg->mmi_pdo_info[i].ua / 1000) / 1000
>= chg->pd_power_max) {
req_pdo = chg->mmi_pdo_info[i].pdo_pos;
req_pd_curr = chg->mmi_pdo_info[i].ua;
if (req_pd_curr > PPS_CURR_MAX)
req_pd_curr = PPS_CURR_MAX;
req_pd_volt = (chg->pd_power_max * 1000 / (req_pd_curr / 1000)) * 1000 % 20000;
req_pd_volt = (chg->pd_power_max * 1000 / (req_pd_curr / 1000)) * 1000 - req_pd_volt;
req_pd_volt = max(req_pd_volt, PPS_VOLT_MIN);
req_pd_volt = min(req_pd_volt, chg->mmi_pdo_info[i].uv_max);
pps_active = true;
break;
}
}
if (!pps_active) {
if (chg->pd_power_max < CHARGER_POWER_18W)
fixed_pd_volt = MICRO_5V;
else
fixed_pd_volt = MICRO_9V;
for (i = 0; i < PD_MAX_PDO_NUM; i++) {
if ((chg->mmi_pdo_info[i].type ==
PD_SRC_PDO_TYPE_FIXED)
&& chg->mmi_pdo_info[i].uv_max <= fixed_pd_volt
&& chg->mmi_pdo_info[i].uv_max >= MICRO_5V) {
req_pdo = chg->mmi_pdo_info[i].pdo_pos;
req_pd_volt = chg->mmi_pdo_info[i].uv_max;
req_pd_curr = min((chg->pd_power_max / (req_pd_volt / 1000)) * 1000,chg->mmi_pdo_info[i].ua);
fixed_active = true;
}
}
if (fixed_active == true && vbus_mv * 1000 < req_pd_volt - MICRO_1V)
fixed_power_done = false;
}
if (!pps_active && !fixed_active)
return;
else if (!fsw_setted){
smblib_set_usb_pd_fsw(chg, req_pd_volt);
fsw_setted = true;
}
if (!fixed_power_done && fixed_active) {
rc = usbpd_select_pdo(chg->pd_handle, req_pdo, req_pd_volt, req_pd_curr);
if (rc < 0) {
mmi_err(chg, "select pdo failed\n");
return;
}
val.intval = req_pd_curr * 1000; /* mA->uA */
rc = smb_mmi_write_iio_chan(chg, SMB5_PD_CURRENT_MAX, val.intval);
if (rc) {
mmi_err(chg, "set PD current MAX failed\n");
return;
}
fixed_power_done = true;
mmi_err(chg, "Request fixed power , re-vote USB_ICL %duA\n", val.intval);
} else if (pps_active){
rc = usbpd_select_pdo(chg->pd_handle, req_pdo, req_pd_volt, req_pd_curr);
if (rc < 0) {
mmi_err(chg, "select pdo failed\n");
return;
}
}
mmi_err(chg,
"Request PD power, fixed %d, pps %d, pdo %d, "
"req volt %dmV, req curr %dmA, vbus %dmV\n",
fixed_active,
pps_active,
req_pdo,
req_pd_volt,
req_pd_curr,
vbus_mv);
return;
}
static void mmi_chrg_usb_vin_config(struct smb_mmi_charger *chg, int cur_mv)
{
int rc = -EINVAL;
int val = 0;
if (!chg->usb_psy || !chg->qcom_psy)
return;
chg->pd_pps_active = 0;
chg->real_charger_type = 0;
if (cur_mv < HVDCP_VOLTAGE_MIN)
return;
rc = smb_mmi_read_iio_chan(chg, SMB5_PD_ACTIVE, &val);
if (rc < 0) {
mmi_err(chg, "Couldn't read PD active rc=%d\n", rc);
} else if (val) {
mmi_dbg(chg, "Skip usb vbus voltage config for PD charger\n");
chg->pd_pps_active = val;
return;
}
rc = smb_mmi_read_iio_chan(chg, SMB5_USB_REAL_TYPE, &val);
if (rc < 0) {
mmi_err(chg, "Couldn't read charger type rc=%d\n", rc);
return;
}
chg->real_charger_type = val;
if (val != QTI_POWER_SUPPLY_TYPE_USB_HVDCP_3)
return;
if (chg->hvdcp_power_max > CHARGER_POWER_15W) {
mmi_increase_vbus_power(chg, cur_mv);
return;
}
/* Maintain vbus voltage for QC3.0 5V/3A 15W charging */
rc = smb_mmi_read_iio_chan(chg, SMB5_DP_DM, &val);
if (rc < 0) {
mmi_err(chg, "Couldn't read dpdm pulse count rc=%d\n", rc);
return;
} else
mmi_info(chg, "DP DM pulse count = %d\n", val);
if (cur_mv < HVDCP_VOLTAGE_NOM && val < chg->inc_hvdcp_cnt)
val = QTI_POWER_SUPPLY_DP_DM_DP_PULSE;
else if (cur_mv > HVDCP_VOLTAGE_MAX && val > 0)
val = QTI_POWER_SUPPLY_DP_DM_DM_PULSE;
else {
mmi_dbg(chg, "No need to change hvdcp voltage\n");
return;
}
rc = smb_mmi_write_iio_chan(chg, SMB5_DP_DM, val);
if (rc < 0) {
mmi_err(chg, "Couldn't set dpdm pulse rc=%d\n", rc);
return;
}
power_supply_changed(chg->usb_psy);
}
static void mmi_chrg_input_config(struct smb_mmi_charger *chg,
struct smb_mmi_chg_status *stat)
{
if (chg->hvdcp_power_max)
mmi_chrg_usb_vin_config(chg, stat->usb_mv);
if (chg->pd_power_max)
mmi_chrg_usb_vin_pd_config(chg, stat->usb_mv);
}
static void mmi_weakcharger_work(struct work_struct *work)
{
struct smb_mmi_charger *chip = container_of(work,
struct smb_mmi_charger,
weakcharger_work.work);
set_bit(EVENT_WEAK_CHARGER_WAIT, &chip->flags);
cancel_delayed_work(&chip->heartbeat_work);
schedule_delayed_work(&chip->heartbeat_work,
msecs_to_jiffies(100));
mmi_dbg(chip, "Weak timer expired\n");
}
int is_wls_online(struct smb_mmi_charger *chg)
{
int rc;
union power_supply_propval val;
if (!chg->wls_psy)
return 0;
rc = power_supply_get_property(chg->wls_psy,
POWER_SUPPLY_PROP_ONLINE, &val);
if (rc < 0) {
mmi_err(chg, "Error wls online rc = %d\n", rc);
return 0;
}
return val.intval;
}
#define WEAK_CHRG_THRSH 450
#define TURBO_CHRG_THRSH 2500
#define PD_CHRG_THRSH 8500
void mmi_chrg_rate_check(struct smb_mmi_charger *chg)
{
union power_supply_propval val;
union power_supply_propval wls_val;
int chrg_v_mv = 0;
int chrg_cm_ma = 0;
int chrg_cs_ma = 0;
int rc = -EINVAL;
if (!chg->usb_psy) {
mmi_err(chg, "No usb PSY\n");
return;
}
val.intval = 0;
chg->prev_chg_rate = chg->charger_rate;
rc = get_prop_charger_present(chg, &val);
if (rc < 0) {
mmi_err(chg, "Error getting Charger Present rc = %d\n", rc);
return;
}
if (val.intval && is_wls_online(chg)) {
rc = power_supply_get_property(chg->wls_psy,
POWER_SUPPLY_PROP_CHARGE_TYPE, &wls_val);
if (rc >= 0 && wls_val.intval == POWER_SUPPLY_CHARGE_TYPE_FAST)
chg->charger_rate = POWER_SUPPLY_CHARGE_RATE_TURBO;
else
chg->charger_rate = POWER_SUPPLY_CHARGE_RATE_NORMAL;
goto end_rate_check;
}
if (val.intval) {
val.intval = 0;
rc = smb_mmi_read_iio_chan(chg, SMB5_HW_CURRENT_MAX, &val.intval);
if (rc < 0) {
mmi_err(chg, "Error getting HW Current Max rc = %d\n", rc);
return;
}
chrg_cm_ma = val.intval / 1000;
val.intval = 0;
rc = smb_mmi_read_iio_chan(chg,
SMB5_USB_INPUT_CURRENT_SETTLED, &val.intval);
if (rc < 0) {
mmi_err(chg, "Error getting ICL Settled rc = %d\n", rc);
return;
}
chrg_cs_ma = val.intval / 1000;
rc = get_prop_usb_voltage_now(chg, &val);
if (rc != -ENOMEM && rc < 0) {
mmi_err(chg, "Error getting USB Input Voltage rc = %d\n", rc);
return;
}
chrg_v_mv = val.intval / 1000;
} else {
chg->charger_rate = POWER_SUPPLY_CHARGE_RATE_NONE;
goto end_rate_check;
}
mmi_dbg(chg, "cm %d, cs %d, v %d\n", chrg_cm_ma, chrg_cs_ma, chrg_v_mv);
if (chrg_cm_ma >= TURBO_CHRG_THRSH || chrg_v_mv >= PD_CHRG_THRSH)
chg->charger_rate = POWER_SUPPLY_CHARGE_RATE_TURBO;
else if ((chrg_cm_ma > WEAK_CHRG_THRSH) &&
(chrg_cs_ma < WEAK_CHRG_THRSH) &&
test_bit(EVENT_WEAK_CHARGER_WAIT, &chg->flags))
chg->charger_rate = POWER_SUPPLY_CHARGE_RATE_WEAK;
else if (chg->prev_chg_rate == POWER_SUPPLY_CHARGE_RATE_NONE)
chg->charger_rate = POWER_SUPPLY_CHARGE_RATE_NORMAL;
else if ((chg->prev_chg_rate == POWER_SUPPLY_CHARGE_RATE_WEAK) &&
(chrg_cm_ma > WEAK_CHRG_THRSH) &&
(chrg_cs_ma >= WEAK_CHRG_THRSH))
chg->charger_rate = POWER_SUPPLY_CHARGE_RATE_NORMAL;
end_rate_check:
if (chg->prev_chg_rate != chg->charger_rate) {
clear_bit(EVENT_WEAK_CHARGER_WAIT, &chg->flags);
cancel_delayed_work(&chg->weakcharger_work);
if(chg->charger_rate == POWER_SUPPLY_CHARGE_RATE_NORMAL)
schedule_delayed_work(&chg->weakcharger_work,
msecs_to_jiffies(WEAK_CHARGER_WAIT));
mmi_info(chg, "%s Charger Detected\n",
charge_rate[chg->charger_rate]);
}
}
#define TAPER_COUNT 2
#define TAPER_DROP_MA 100
static bool mmi_has_current_tapered(int batt, struct smb_mmi_charger *chg,
struct mmi_sm_params *chip,
int batt_ma, int taper_ma)
{
bool change_state = false;
int allowed_fcc, target_ma;
if (!chg) {
mmi_dbg(chg, "called before chip valid!\n");
return false;
}
allowed_fcc = get_effective_result(chg->fcc_votable) / 1000;
if (allowed_fcc >= taper_ma)
target_ma = taper_ma;
else
target_ma = allowed_fcc - TAPER_DROP_MA;
if (batt == BASE_BATT)
batt_ma *= -1;
if (batt_ma < 0) {
if (chip->chrg_taper_cnt >= TAPER_COUNT) {
change_state = true;
chip->chrg_taper_cnt = 0;
} else
chip->chrg_taper_cnt++;
} else {
if (batt_ma <= target_ma)
if (chip->chrg_taper_cnt >= TAPER_COUNT) {
change_state = true;
chip->chrg_taper_cnt = 0;
} else
chip->chrg_taper_cnt++;
else
chip->chrg_taper_cnt = 0;
}
return change_state;
}
bool mmi_charge_halted(struct smb_mmi_charger *chg)
{
u8 stat;
int rc;
bool flag = false;
rc = smblib_read_mmi(chg, BATTERY_CHARGER_STATUS_1_REG, &stat);
if (rc < 0) {
mmi_err(chg, "Couldn't read BATTERY_CHARGER_STATUS_1 rc=%d\n",
rc);
return flag;
}
stat = stat & BATTERY_CHARGER_STATUS_MASK;
if (chg->smb_version == PM8150B ||
chg->smb_version == PM7250B ||
chg->smb_version == PMI632) {
switch (stat) {
case TERMINATE_CHARGE_V5:
case INHIBIT_CHARGE_V5:
flag = true;
break;
default:
break;
}
} else {
switch (stat) {
case TERMINATE_CHARGE:
case INHIBIT_CHARGE:
flag = true;
break;
default:
break;
}
}
return flag;
}
static int mmi_dual_charge_sm(struct smb_mmi_charger *chg,
struct smb_mmi_chg_status *stat,
int batt, int fv_offset)
{
int max_fv_mv;
int i;
struct mmi_temp_zone *zone;
struct mmi_sm_params *chip = &chg->sm_param[batt];
int start_tz = chip->pres_temp_zone;
int start_step = chip->pres_chrg_step;
if (!chip->temp_zones) {
mmi_err(chg, "No Temp Zone Defined for batt %d!\n", batt);
return -ENODEV;
}
if (chg->base_fv_mv == 0) {
chg->base_fv_mv = get_effective_result(chg->fv_votable);
chg->base_fv_mv /= 1000;
vote(chg->fv_votable,
BATT_PROFILE_VOTER, false, 0);
}
max_fv_mv = chg->base_fv_mv;
mmi_find_temp_zone(chg, chip, stat->batt_temp);
zone = &chip->temp_zones[chip->pres_temp_zone];
if (!stat->charger_present) {
chip->pres_chrg_step = STEP_NONE;
for (i = 0; i < MAX_NUM_STEPS; i++)
chip->ocp[i] = 0;
} else if ((chip->pres_temp_zone == ZONE_HOT) ||
(chip->pres_temp_zone == ZONE_COLD) ||
(chg->charging_limit_modes == CHARGING_LIMIT_RUN)) {
chip->pres_chrg_step = STEP_STOP;
} else if (chg->demo_mode) { /* Demo Mode */
chip->pres_chrg_step = STEP_DEMO;
} else if ((chip->pres_chrg_step == STEP_NONE) ||
(chip->pres_chrg_step == STEP_STOP)) {
if (zone->norm_mv && (stat->batt_mv >= zone->norm_mv)) {
if (zone->fcc_norm_ma)
chip->pres_chrg_step = STEP_NORM;
else
chip->pres_chrg_step = STEP_STOP;
} else
chip->pres_chrg_step = STEP_MAX;
} else if (chip->pres_chrg_step == STEP_MAX) {
if (!zone->norm_mv) {
/* No Step in this Zone */
chip->chrg_taper_cnt = 0;
if ((stat->batt_mv + fv_offset) >= max_fv_mv)
chip->pres_chrg_step = STEP_NORM;
else
chip->pres_chrg_step = STEP_MAX;
} else if ((stat->batt_mv + fv_offset) < zone->norm_mv) {
chip->chrg_taper_cnt = 0;
chip->pres_chrg_step = STEP_MAX;
} else if (!zone->fcc_norm_ma)
chip->pres_chrg_step = STEP_FLOAT;
else if (mmi_has_current_tapered(batt, chg, chip, stat->batt_ma,
zone->fcc_norm_ma)) {
chip->chrg_taper_cnt = 0;
for (i = 0; i < MAX_NUM_STEPS; i++)
chip->ocp[i] = 0;
chip->pres_chrg_step = STEP_NORM;
}
} else if (chip->pres_chrg_step == STEP_NORM) {
if (!zone->fcc_norm_ma)
chip->pres_chrg_step = STEP_STOP;
else if ((stat->batt_soc < 100) ||
(stat->batt_mv + fv_offset) < max_fv_mv) {
chip->chrg_taper_cnt = 0;
chip->pres_chrg_step = STEP_NORM;
} else if (mmi_has_current_tapered(batt, chg, chip, stat->batt_ma,
chip->chrg_iterm)) {
chip->pres_chrg_step = STEP_FULL;
}
} else if (chip->pres_chrg_step == STEP_FULL) {
if (stat->batt_soc <= 99) {
chip->chrg_taper_cnt = 0;
chip->pres_chrg_step = STEP_NORM;
}
} else if (chip->pres_chrg_step == STEP_FLOAT) {
if ((zone->fcc_norm_ma) ||
((stat->batt_mv + fv_offset) < zone->norm_mv))
chip->pres_chrg_step = STEP_MAX;
}
/* Take State actions */
switch (chip->pres_chrg_step) {
case STEP_FLOAT:
case STEP_MAX:
if (!zone->norm_mv)
chip->target_fv = max_fv_mv;
else
chip->target_fv = zone->norm_mv;
chip->target_fcc = zone->fcc_max_ma;
break;
case STEP_FULL:
chip->target_fv = max_fv_mv;
chip->target_fcc = -EINVAL;
break;
case STEP_NORM:
chip->target_fv = max_fv_mv + chg->vfloat_comp_mv;
chip->target_fcc = zone->fcc_norm_ma;
break;
case STEP_NONE:
chip->target_fv = max_fv_mv;
chip->target_fcc = zone->fcc_norm_ma;
break;
case STEP_STOP:
chip->target_fv = max_fv_mv;
chip->target_fcc = -EINVAL;
break;
case STEP_DEMO:
chip->target_fv = DEMO_MODE_VOLTAGE;
chip->target_fcc = zone->fcc_max_ma;
break;
default:
chip->target_fv = max_fv_mv;
chip->target_fcc = zone->fcc_norm_ma;
break;
}
if (chip->pres_temp_zone == ZONE_HOT) {
chip->batt_health = POWER_SUPPLY_HEALTH_OVERHEAT;
} else if (chip->pres_temp_zone == ZONE_COLD) {
chip->batt_health = POWER_SUPPLY_HEALTH_COLD;
} else if (stat->batt_temp >= WARM_TEMP) {
if (chip->pres_chrg_step == STEP_STOP)
chip->batt_health = POWER_SUPPLY_HEALTH_OVERHEAT;
else
chip->batt_health = POWER_SUPPLY_HEALTH_GOOD;
} else if (stat->batt_temp <= COOL_TEMP) {
if (chip->pres_chrg_step == STEP_STOP)
chip->batt_health = POWER_SUPPLY_HEALTH_COLD;
else
chip->batt_health = POWER_SUPPLY_HEALTH_GOOD;
} else
chip->batt_health = POWER_SUPPLY_HEALTH_GOOD;
if ((start_tz != chip->pres_temp_zone) ||
(start_step != chip->pres_chrg_step)) {
mmi_info(chg, "Batt %d: batt_mv = %d, batt_ma %d, batt_soc %d,"
" batt_temp %d, usb_mv %d, cp %d, vp %d\n",
batt,
stat->batt_mv,
stat->batt_ma,
stat->batt_soc,
stat->batt_temp,
stat->usb_mv,
stat->charger_present,
stat->vbus_present);
mmi_info(chg, "Batt %d Step State = %s, Temp Zone %d, Health %d\n",
batt,
stepchg_str[(int)chip->pres_chrg_step],
chip->pres_temp_zone,
chip->batt_health);
chg->hb_startup_cnt = HEARTBEAT_OCP_SETTLE_CNT;
chg->ocp_flag = true; // 1x settle per state chng
return 1;
} else {
mmi_dbg(chg, "Batt %d: batt_mv = %d, batt_ma %d, batt_soc %d,"
" batt_temp %d, usb_mv %d, cp %d, vp %d\n",
batt,
stat->batt_mv,
stat->batt_ma,
stat->batt_soc,
stat->batt_temp,
stat->usb_mv,
stat->charger_present,
stat->vbus_present);
mmi_dbg(chg, "Batt %d Step State = %s, Temp Zone %d, Health %d\n",
batt,
stepchg_str[(int)chip->pres_chrg_step],
chip->pres_temp_zone,
chip->batt_health);
}
return 0;
}
static int mmi_dual_charge_control(struct smb_mmi_charger *chg,
struct smb_mmi_chg_status *stat)
{
int rc;
int target_fcc;
int target_fv;
int effective_fv;
int effective_fcc;
int scaled_fcc =0;
int ocp;
int sm_update;
bool voltage_full;
int i;
int sched_time = HEARTBEAT_DUAL_DELAY_MS;
struct smb_mmi_chg_status chg_stat_main, chg_stat_flip;
union power_supply_propval pval;
struct mmi_sm_params *main_p = &chg->sm_param[MAIN_BATT];
struct mmi_sm_params *flip_p = &chg->sm_param[FLIP_BATT];
static int demo_full_soc = 100;
bool is_chg_dis = get_effective_result(chg->chg_dis_votable);
chg_stat_main.charger_present = stat->charger_present;
chg_stat_flip.charger_present = stat->charger_present;
chg_stat_main.vbus_present = stat->vbus_present;
chg_stat_flip.vbus_present = stat->vbus_present;
chg_stat_main.usb_mv = stat->usb_mv;
chg_stat_flip.usb_mv = stat->usb_mv;
rc = get_prop_batt_voltage_now(chg, chg->max_main_psy, &pval);
if (rc < 0) {
mmi_err(chg, "Error getting Main Batt Voltage rc = %d\n", rc);
return sched_time;
} else
chg_stat_main.batt_mv = pval.intval / 1000;
rc = get_prop_batt_voltage_now(chg, chg->max_flip_psy, &pval);
if (rc < 0) {
mmi_err(chg, "Error getting Flip Batt Voltage rc = %d\n", rc);
return sched_time;
} else
chg_stat_flip.batt_mv = pval.intval / 1000;
rc = get_prop_batt_current_now(chg, chg->max_main_psy, &pval);
if (rc < 0) {
mmi_err(chg, "Error getting Main Batt Current rc = %d\n", rc);
return sched_time;
} else
chg_stat_main.batt_ma = (pval.intval / 1000) * -1;
rc = get_prop_batt_current_now(chg, chg->max_flip_psy, &pval);
if (rc < 0) {
mmi_err(chg, "Error getting Flip Batt Current rc = %d\n", rc);
return sched_time;
} else
chg_stat_flip.batt_ma = (pval.intval / 1000) * -1;
rc = get_prop_batt_capacity(chg, chg->max_main_psy, &pval);
if (rc < 0) {
mmi_err(chg, "Error getting Main Batt Capacity rc = %d\n", rc);
return sched_time;
} else
chg_stat_main.batt_soc = pval.intval;
rc = get_prop_batt_capacity(chg, chg->max_flip_psy, &pval);
if (rc < 0) {
mmi_err(chg, "Error getting Flip Batt Capacity rc = %d\n", rc);
return sched_time;
} else
chg_stat_flip.batt_soc = pval.intval;
rc = get_prop_batt_temp(chg, chg->max_main_psy, &pval);
if (rc < 0) {
mmi_err(chg, "Error getting Main Batt Temperature rc = %d\n", rc);
return sched_time;
} else
chg_stat_main.batt_temp = pval.intval / 10;
rc = get_prop_batt_temp(chg, chg->max_flip_psy, &pval);
if (rc < 0) {
mmi_err(chg, "Error getting Flip Batt Temperature rc = %d\n", rc);
return sched_time;
} else
chg_stat_flip.batt_temp = pval.intval / 10;
sm_update = mmi_dual_charge_sm(chg, &chg_stat_main,
MAIN_BATT, HYST_STEP_MV);
if (sm_update < 0)
return sched_time;
sm_update = mmi_dual_charge_sm(chg, &chg_stat_flip,
FLIP_BATT, HYST_STEP_FLIP_MV);
if (sm_update < 0)
return sched_time;
effective_fv = get_effective_result(chg->fv_votable) / 1000;
effective_fcc = get_effective_result(chg->fcc_votable);
/* OCP Check - Scale down fcc if too high for initial set */
if (chg->hb_startup_cnt > 1) {
chg->hb_startup_cnt--;
sched_time = HEARTBEAT_DUAL_DELAY_OCP_MS;
} else if ( (effective_fcc >= (main_p->target_fcc + flip_p->target_fcc) *1000) &&
(chg->ocp_flag) ) {
chg->ocp_flag = false;
sched_time = HEARTBEAT_DUAL_DELAY_OCP_MS;
chg->hb_startup_cnt = HEARTBEAT_OCP_SETTLE_CNT;
scaled_fcc = effective_fcc /10;
effective_fcc -= scaled_fcc;
mmi_dbg(chg, "fccScaled: %d, AdjFCC: %d\n", scaled_fcc, effective_fcc);
main_p->ocp[main_p->pres_temp_zone] =0;
flip_p->ocp[flip_p->pres_temp_zone] =0;
}
/* Check for Charge None */
if ((main_p->pres_chrg_step == STEP_NONE) ||
(flip_p->pres_chrg_step == STEP_NONE)) {
chg->sm_param[BASE_BATT].pres_chrg_step = STEP_NONE;
target_fcc = main_p->target_fcc;
target_fv = chg->base_fv_mv;
sched_time = HEARTBEAT_DELAY_MS;
goto vote_now;
/* Check for Charge Demo */
} else if ((main_p->pres_chrg_step == STEP_DEMO) ||
(flip_p->pres_chrg_step == STEP_DEMO)) {
mmi_info(chg, "Battery in Demo Mode charging limited "
"%d%%\n", chg->demo_mode);
voltage_full = ((chg->demo_mode_usb_suspend == false) &&
((stat->batt_mv + HYST_STEP_MV) >= DEMO_MODE_VOLTAGE)
&& mmi_has_current_tapered(MAIN_BATT, chg, main_p, stat->batt_ma,
(main_p->chrg_iterm * 2)));
if ((chg->demo_mode_usb_suspend == false) &&
((chg->last_reported_soc >= chg->demo_mode) ||
voltage_full)) {
demo_full_soc = chg->last_reported_soc;
vote(chg->usb_icl_votable, DEMO_VOTER, true, 0);
vote(chg->dc_suspend_votable, DEMO_VOTER, true, 1);
chg->demo_mode_usb_suspend = true;
main_p->chrg_taper_cnt = 0;
flip_p->chrg_taper_cnt = 0;
for (i = 0; i < MAX_NUM_STEPS; i++) {
main_p->ocp[i] = 0;
flip_p->ocp[i] = 0;
}
} else if (chg->demo_mode_usb_suspend == true &&
(chg->last_reported_soc <=
(demo_full_soc - DEMO_MODE_HYS_SOC))) {
vote(chg->usb_icl_votable, DEMO_VOTER, false, 0);
vote(chg->dc_suspend_votable, DEMO_VOTER, false, 0);
chg->demo_mode_usb_suspend = false;
main_p->chrg_taper_cnt = 0;
flip_p->chrg_taper_cnt = 0;
for (i = 0; i < MAX_NUM_STEPS; i++) {
main_p->ocp[i] = 0;
flip_p->ocp[i] = 0;
}
}
target_fv = DEMO_MODE_VOLTAGE;
target_fcc = main_p->target_fcc;
goto vote_now;
/* Check for Charge FULL from each */
} else if ((main_p->pres_chrg_step == STEP_FULL) &&
(flip_p->pres_chrg_step == STEP_FULL)) {
chg->sm_param[BASE_BATT].pres_chrg_step = STEP_FULL;
target_fcc = -EINVAL;
target_fv = chg->base_fv_mv;
sched_time = HEARTBEAT_DELAY_MS;
chg->last_reported_status = POWER_SUPPLY_STATUS_FULL;
goto vote_now;
/* Align FULL between batteries */
} else if ((main_p->pres_chrg_step == STEP_FULL) &&
((flip_p->pres_chrg_step == STEP_MAX) ||
(flip_p->pres_chrg_step == STEP_NORM)) &&
(chg_stat_flip.batt_soc >= 95) &&
((chg_stat_flip.batt_mv + HYST_STEP_FLIP_MV) >=
chg->base_fv_mv)) {
chg->sm_param[BASE_BATT].pres_chrg_step =
flip_p->pres_chrg_step;
target_fcc = flip_p->target_fcc;
target_fv = flip_p->target_fv;
mmi_info(chg, "Align Flip to Main FULL\n");
sched_time = HEARTBEAT_DUAL_DELAY_MS;
goto vote_now;
} else if ((flip_p->pres_chrg_step == STEP_FULL) &&
((main_p->pres_chrg_step == STEP_MAX) ||
(main_p->pres_chrg_step == STEP_NORM)) &&
(chg_stat_main.batt_soc >= 95) &&
((chg_stat_main.batt_mv + HYST_STEP_MV) >=
chg->base_fv_mv)) {
chg->sm_param[BASE_BATT].pres_chrg_step =
main_p->pres_chrg_step;
target_fcc = main_p->target_fcc;
target_fv = main_p->target_fv;
mmi_info(chg, "Align Main to Flip FULL\n");
sched_time = HEARTBEAT_DUAL_DELAY_MS;
goto vote_now;
/* Check for Charge Disable from each */
} else if ((main_p->target_fcc < 0) ||
(flip_p->target_fcc < 0)) {
chg->sm_param[BASE_BATT].pres_chrg_step = STEP_STOP;
target_fcc = -EINVAL;
target_fv = chg->base_fv_mv;
goto vote_now;
}
chg->sm_param[BASE_BATT].pres_chrg_step = main_p->pres_chrg_step;
chg->last_reported_status = -1;
if (main_p->target_fv < flip_p->target_fv)
target_fv = main_p->target_fv;
else
target_fv = flip_p->target_fv;
if ((chg_stat_main.batt_ma > main_p->target_fcc) &&
chg->hb_startup_cnt %2) { //Allow settle time
sched_time = HEARTBEAT_DUAL_DELAY_OCP_MS;
ocp = chg_stat_main.batt_ma - main_p->target_fcc;
main_p->ocp[main_p->pres_temp_zone] += ocp;
mmi_info(chg, "Main Exceed by %d mA\n",
main_p->ocp[main_p->pres_temp_zone]);
}
if ((chg_stat_flip.batt_ma > flip_p->target_fcc) &&
chg->hb_startup_cnt %2) { //Allow settle time
sched_time = HEARTBEAT_DUAL_DELAY_OCP_MS;
ocp = chg_stat_flip.batt_ma - flip_p->target_fcc;
flip_p->ocp[flip_p->pres_temp_zone] += ocp;
mmi_info(chg, "Flip Exceed by %d mA\n",
flip_p->ocp[flip_p->pres_temp_zone]);
}
target_fcc = main_p->target_fcc + flip_p->target_fcc;
target_fcc -= scaled_fcc/1000;
target_fcc -= main_p->ocp[main_p->pres_temp_zone];
target_fcc -= flip_p->ocp[flip_p->pres_temp_zone];
if ( (target_fcc < main_p->target_fcc) &&
(chg_stat_flip.batt_ma < flip_p->target_fcc *7/10) ) {
mmi_info(chg, "Target FCC adjust too much\n");
chg->hb_startup_cnt = HEARTBEAT_OCP_SETTLE_CNT;
if (main_p->target_fcc < flip_p->target_fcc)
target_fcc = (main_p->target_fcc)*5/2;
else
target_fcc = (flip_p->target_fcc)*5/2;
main_p->ocp[main_p->pres_temp_zone] =0;
flip_p->ocp[flip_p->pres_temp_zone] =0;
}
if (((main_p->pres_chrg_step == STEP_MAX) ||
(flip_p->pres_chrg_step == STEP_MAX) ||
(main_p->pres_chrg_step == STEP_NORM) ||
(flip_p->pres_chrg_step == STEP_NORM)) &&
mmi_charge_halted(chg)) {
vote(chg->chg_dis_votable,
MMI_HB_VOTER, true, 0);
mmi_err(chg, "Charge Halt..Toggle\n");
msleep(50);
}
vote_now:
/* Votes for State */
vote(chg->fv_votable, MMI_HB_VOTER, true, target_fv * 1000);
vote(chg->chg_dis_votable, MMI_HB_VOTER,
(target_fcc < 0), 0);
vote(chg->fcc_votable, MMI_HB_VOTER,
true, (target_fcc >= 0) ? (target_fcc * 1000) : 0);
/* Rerun AICL to recover USB ICL from recharge */
if (is_chg_dis != get_effective_result(chg->chg_dis_votable)) {
smb_mmi_write_iio_chan(chg,
SMB5_RERUN_AICL, 0);
}
if (!stat->charger_present &&
(chg_stat_main.batt_mv <= LOW_BATT_MV) &&
(chg_stat_flip.batt_mv <= LOW_BATT_MV)) {
if ((chg_stat_main.batt_mv <= SHUT_BATT_MV) ||
(chg_stat_flip.batt_mv <= SHUT_BATT_MV))
chg->shut_batt = true;
} else
chg->shut_batt = false;
if (sm_update)
mmi_info(chg, "IMPOSED: FV = %d, CDIS = %d, FCC = %d, USBICL = %d\n",
effective_fv,
get_effective_result(chg->chg_dis_votable),
target_fcc,
get_effective_result(chg->usb_icl_votable));
else
mmi_dbg(chg, "IMPOSED: FV = %d, CDIS = %d, FCC = %d, USBICL = %d\n",
effective_fv,
get_effective_result(chg->chg_dis_votable),
target_fcc,
get_effective_result(chg->usb_icl_votable));
return sched_time;
}
static int mmi_get_ffc_fv(struct smb_mmi_charger *chip, int zone)
{
int rc;
int ffc_max_fv;
struct mmi_sm_params *prm = &chip->sm_param[BASE_BATT];
if (prm->ffc_zones == NULL || zone >= prm->num_temp_zones)
return 0;
mmi_info(chip,"real_charger_type=%d, pd_pps_active=%d\n",chip->real_charger_type,
chip->pd_pps_active);
if ((chip->real_charger_type != QTI_POWER_SUPPLY_TYPE_USB_HVDCP_3P5) &&
(chip->pd_pps_active != QTI_POWER_SUPPLY_PD_PPS_ACTIVE) &&
(chip->noffc_chg_iterm != -EINVAL) &&
(chip->noffc_qg_iterm != -EINVAL) &&
(chip->noffc_max_fv != -EINVAL)) {
rc = smb_mmi_write_iio_chan(chip,
SMB5_QG_BATT_FULL_CURRENT, chip->noffc_qg_iterm);
if (rc < 0) {
mmi_err(chip, "Couldn't set batt full current, rc=%d\n", rc);
}
if ((zone == ZONE_35C_TO_45C) && (chip->noffc_chg_iterm_45c != -EINVAL)) {
prm->chrg_iterm = chip->noffc_chg_iterm_45c;
} else {
prm->chrg_iterm = chip->noffc_chg_iterm;
}
ffc_max_fv = chip->noffc_max_fv;
mmi_info(chip,"NONEFFC temp zone %d, fv %d mV, chg iterm %d mA, qg iterm %d mA\n",
zone, ffc_max_fv, prm->chrg_iterm, chip->noffc_qg_iterm);
return ffc_max_fv;
}
rc = smb_mmi_write_iio_chan(chip,
SMB5_QG_BATT_FULL_CURRENT, prm->ffc_zones[zone].ffc_qg_iterm);
if (rc < 0) {
mmi_err(chip, "Couldn't set batt full current, rc=%d\n", rc);
}
prm->chrg_iterm = prm->ffc_zones[zone].ffc_chg_iterm;
ffc_max_fv = prm->ffc_zones[zone].ffc_max_mv;
mmi_info(chip,
"FFC temp zone %d, fv %d mV, chg iterm %d mA, qg iterm %d mA\n",
zone, ffc_max_fv, prm->chrg_iterm, prm->ffc_zones[zone].ffc_qg_iterm);
return ffc_max_fv;
}
static void mmi_basic_charge_sm(struct smb_mmi_charger *chip,
struct smb_mmi_chg_status *stat)
{
int target_fcc;
int target_fv;
int max_fv_mv;
struct mmi_temp_zone *zone;
struct mmi_sm_params *prm = &chip->sm_param[BASE_BATT];
bool voltage_full;
bool is_chg_dis = get_effective_result(chip->chg_dis_votable);
static int demo_full_soc = 100;
mmi_info(chip, "batt_mv = %d, batt_ma %d, batt_soc %d,"
" batt_temp %d, usb_mv %d, dc_mv %d, cp %d, vp %d dp %d\n",
stat->batt_mv,
stat->batt_ma,
stat->batt_soc,
stat->batt_temp,
stat->usb_mv,
stat->dc_mv,
stat->charger_present,
stat->vbus_present,
stat->dc_present);
if (!prm->temp_zones) {
mmi_dbg(chip, "Skipping SM since No Temp Zone Defined!\n");
return;
}
if (chip->base_fv_mv == 0) {
chip->base_fv_mv = get_effective_result(chip->fv_votable);
chip->base_fv_mv /= 1000;
vote(chip->fv_votable,
BATT_PROFILE_VOTER, false, 0);
}
max_fv_mv = mmi_get_ffc_fv(chip, prm->pres_temp_zone);
if (max_fv_mv == 0)
max_fv_mv = chip->base_fv_mv;
mmi_find_temp_zone(chip, prm, stat->batt_temp);
if (prm->pres_temp_zone >= prm->num_temp_zones)
zone = &prm->temp_zones[0];
else
zone = &prm->temp_zones[prm->pres_temp_zone];
if (!stat->charger_present && !is_wls_online(chip)) {
prm->pres_chrg_step = STEP_NONE;
} else if ((prm->pres_temp_zone == ZONE_HOT) ||
(prm->pres_temp_zone == ZONE_COLD) ||
(chip->charging_limit_modes == CHARGING_LIMIT_RUN)) {
prm->pres_chrg_step = STEP_STOP;
} else if (chip->demo_mode) { /* Demo Mode */
prm->pres_chrg_step = STEP_DEMO;
mmi_info(chip, "Battery in Demo Mode charging limited "
"%d%%\n", chip->demo_mode);
voltage_full = ((chip->demo_mode_usb_suspend == false) &&
((stat->batt_mv + HYST_STEP_MV) >= DEMO_MODE_VOLTAGE)
&& mmi_has_current_tapered(BASE_BATT, chip, prm, stat->batt_ma,
prm->chrg_iterm));
if ((chip->demo_mode_usb_suspend == false) &&
((stat->batt_soc >= chip->demo_mode) || voltage_full)) {
demo_full_soc = stat->batt_soc;
vote(chip->usb_icl_votable, DEMO_VOTER, true, 0);
vote(chip->dc_suspend_votable, DEMO_VOTER, true, 0);
chip->demo_mode_usb_suspend = true;
} else if (chip->demo_mode_usb_suspend == true &&
(stat->batt_soc <= (demo_full_soc - DEMO_MODE_HYS_SOC))) {
vote(chip->usb_icl_votable, DEMO_VOTER, false, 0);
vote(chip->dc_suspend_votable, DEMO_VOTER, false, 0);
chip->demo_mode_usb_suspend = false;
prm->chrg_taper_cnt = 0;
}
} else if ((prm->pres_chrg_step == STEP_NONE) ||
(prm->pres_chrg_step == STEP_STOP)) {
if (zone->norm_mv && ((stat->batt_mv + HYST_STEP_MV) >= zone->norm_mv)) {
if (zone->fcc_norm_ma)
prm->pres_chrg_step = STEP_NORM;
else
prm->pres_chrg_step = STEP_STOP;
} else
prm->pres_chrg_step = STEP_MAX;
} else if (prm->pres_chrg_step == STEP_MAX) {
if (!zone->norm_mv) {
/* No Step in this Zone */
prm->chrg_taper_cnt = 0;
if ((stat->batt_mv + HYST_STEP_MV) >= max_fv_mv)
prm->pres_chrg_step = STEP_NORM;
else
prm->pres_chrg_step = STEP_MAX;
} else if ((stat->batt_mv + HYST_STEP_MV) < zone->norm_mv) {
prm->chrg_taper_cnt = 0;
prm->pres_chrg_step = STEP_MAX;
} else if (!zone->fcc_norm_ma)
prm->pres_chrg_step = STEP_FLOAT;
else if (mmi_has_current_tapered(BASE_BATT, chip, prm, stat->batt_ma,
zone->fcc_norm_ma)) {
prm->chrg_taper_cnt = 0;
if (mmi_charge_halted(chip)) {
vote(chip->chg_dis_votable,
MMI_HB_VOTER, true, 0);
mmi_warn(chip, "Charge Halt..Toggle\n");
msleep(50);
}
prm->pres_chrg_step = STEP_NORM;
}
} else if (prm->pres_chrg_step == STEP_NORM) {
if (!zone->fcc_norm_ma)
prm->pres_chrg_step = STEP_FLOAT;
else if ((stat->batt_soc < 100) ||
(stat->batt_mv + HYST_STEP_MV) < max_fv_mv) {
prm->chrg_taper_cnt = 0;
prm->pres_chrg_step = STEP_NORM;
} else if (mmi_has_current_tapered(BASE_BATT, chip, prm, stat->batt_ma,
prm->chrg_iterm)) {
prm->pres_chrg_step = STEP_FULL;
}
} else if (prm->pres_chrg_step == STEP_FULL) {
if ((stat->batt_soc <= 99) ||
stat->batt_mv < (max_fv_mv - HYST_STEP_MV * 2)) {
prm->chrg_taper_cnt = 0;
prm->pres_chrg_step = STEP_NORM;
}
} else if (prm->pres_chrg_step == STEP_FLOAT) {
if ((zone->fcc_norm_ma) ||
((stat->batt_mv + HYST_STEP_MV) < zone->norm_mv))
prm->pres_chrg_step = STEP_MAX;
else if (mmi_has_current_tapered(BASE_BATT, chip, prm, stat->batt_ma,
prm->chrg_iterm))
prm->pres_chrg_step = STEP_STOP;
}
/* Take State actions */
switch (prm->pres_chrg_step) {
case STEP_FLOAT:
case STEP_MAX:
if (!zone->norm_mv)
target_fv = max_fv_mv + chip->vfloat_comp_mv;
else
target_fv = zone->norm_mv + chip->vfloat_comp_mv;
target_fcc = zone->fcc_max_ma;
chip->last_reported_status = -1;
break;
case STEP_FULL:
target_fv = max_fv_mv;
target_fcc = -EINVAL;
chip->last_reported_status = POWER_SUPPLY_STATUS_FULL;
break;
case STEP_NORM:
target_fv = max_fv_mv + chip->vfloat_comp_mv;
target_fcc = zone->fcc_norm_ma;
chip->last_reported_status = -1;
break;
case STEP_NONE:
target_fv = max_fv_mv;
if (is_wls_online(chip))
target_fcc = zone->fcc_norm_ma;
else if (stat->dc_present)
target_fcc = 500;
else
target_fcc = zone->fcc_norm_ma;
chip->last_reported_status = -1;
break;
case STEP_STOP:
target_fv = max_fv_mv;
target_fcc = -EINVAL;
chip->last_reported_status = -1;
break;
case STEP_DEMO:
target_fv = DEMO_MODE_VOLTAGE;
target_fcc = zone->fcc_max_ma;
chip->last_reported_status = -1;
break;
default:
target_fv = max_fv_mv;
target_fcc = zone->fcc_norm_ma;
chip->last_reported_status = -1;
break;
}
/* Votes for State */
if (stat->dc_present)
vote(chip->dc_suspend_votable, MMI_HB_VOTER, false, 0);
else
vote(chip->dc_suspend_votable, MMI_HB_VOTER, true, 1);
vote(chip->fv_votable, MMI_HB_VOTER, true, target_fv * 1000);
vote(chip->chg_dis_votable, MMI_HB_VOTER,
(target_fcc < 0), 0);
vote(chip->fcc_votable, MMI_HB_VOTER,
true, (target_fcc >= 0) ? (target_fcc * 1000) : 0);
/* Override USB ICL using the target value */
if (stat->charger_present) {
mmi_usb_icl_override(chip,
get_effective_result(chip->usb_icl_votable));
}
/* Rerun AICL to recover USB ICL from recharge */
if (is_chg_dis != get_effective_result(chip->chg_dis_votable)) {
smb_mmi_write_iio_chan(chip,
SMB5_RERUN_AICL, 0);
}
if (prm->pres_temp_zone == ZONE_HOT) {
prm->batt_health = POWER_SUPPLY_HEALTH_OVERHEAT;
} else if (prm->pres_temp_zone == ZONE_COLD) {
prm->batt_health = POWER_SUPPLY_HEALTH_COLD;
} else if (stat->batt_temp >= WARM_TEMP) {
if (prm->pres_chrg_step == STEP_STOP)
prm->batt_health = POWER_SUPPLY_HEALTH_OVERHEAT;
else
prm->batt_health = POWER_SUPPLY_HEALTH_GOOD;
} else if (stat->batt_temp <= COOL_TEMP) {
if (prm->pres_chrg_step == STEP_STOP)
prm->batt_health = POWER_SUPPLY_HEALTH_COLD;
else
prm->batt_health = POWER_SUPPLY_HEALTH_GOOD;
} else
prm->batt_health = POWER_SUPPLY_HEALTH_GOOD;
mmi_info(chip, "Step State = %s, Temp Zone %d, Health %d\n",
stepchg_str[(int)prm->pres_chrg_step],
prm->pres_temp_zone,
prm->batt_health);
mmi_info(chip, "IMPOSED: FV = %d, CDIS = %d, FCC = %d, USBICL = %d\n",
get_effective_result(chip->fv_votable),
get_effective_result(chip->chg_dis_votable),
get_effective_result(chip->fcc_votable),
get_effective_result(chip->usb_icl_votable));
}
static void smb_mmi_awake_vote(struct smb_mmi_charger *chip, bool awake)
{
if (awake == chip->awake)
return;
chip->awake = awake;
if (awake)
pm_stay_awake(chip->dev);
else
pm_relax(chip->dev);
}
void update_charging_limit_modes(struct smb_mmi_charger *chip, int batt_soc)
{
enum charging_limit_modes charging_limit_modes;
charging_limit_modes = chip->charging_limit_modes;
if ((charging_limit_modes != CHARGING_LIMIT_RUN)
&& (batt_soc >= chip->upper_limit_capacity))
charging_limit_modes = CHARGING_LIMIT_RUN;
else if ((charging_limit_modes != CHARGING_LIMIT_OFF)
&& (batt_soc <= chip->lower_limit_capacity))
charging_limit_modes = CHARGING_LIMIT_OFF;
if (charging_limit_modes != chip->charging_limit_modes)
chip->charging_limit_modes = charging_limit_modes;
}
static void smb_mmi_power_supply_changed(struct power_supply *psy,
char *envp_ext[])
{
if (this_chip && this_chip->prev_chg_rate != this_chip->charger_rate) {
dev_err(&psy->dev, "SMBMMI: %s: %s\n", __func__, envp_ext[0]);
}
kobject_uevent_env(&psy->dev.kobj, KOBJ_CHANGE, envp_ext);
power_supply_changed(psy);
}
static int factory_kill_disable;
module_param(factory_kill_disable, int, 0644);
#define TWO_VOLT 2000000
#define MONOTONIC_SOC 2 /* 2 percent */
static void mmi_heartbeat_work(struct work_struct *work)
{
struct smb_mmi_charger *chip = container_of(work,
struct smb_mmi_charger,
heartbeat_work.work);
int hb_resch_time = HEARTBEAT_DELAY_MS;
union power_supply_propval pval;
int rc, usb_suspend;
int batt_cap = 0;
int main_cap = 0;
int main_cap_full = 0;
int main_age = 0;
int flip_cap = 0;
int flip_cap_full = 0;
int flip_age = 0;
int cap_err;
int report_cap;
int pc_online;
int pres_chrg_step, prev_chrg_step;
static int prev_vbus_mv = -1;
char *chrg_rate_string = NULL;
char *envp[2];
struct smb_mmi_chg_status chg_stat;
/* Have not been resumed so wait another 100 ms */
if (chip->suspended & IS_SUSPENDED) {
mmi_err(chip, "HB running before Resume\n");
schedule_delayed_work(&chip->heartbeat_work,
msecs_to_jiffies(100));
return;
}
smb_mmi_awake_vote(chip, true);
mmi_dbg(chip, "Heartbeat!\n");
chg_stat.charger_present = false;
rc = get_prop_batt_voltage_now(chip, chip->bms_psy, &pval);
if (rc < 0) {
mmi_err(chip, "Error getting Batt Voltage rc = %d\n", rc);
goto sch_hb;
} else
chg_stat.batt_mv = pval.intval / 1000;
rc = get_prop_batt_current_now(chip, chip->bms_psy, &pval);
if (rc < 0) {
mmi_err(chip, "Error getting Batt Current rc = %d\n", rc);
goto sch_hb;
} else
chg_stat.batt_ma = pval.intval / 1000;
rc = get_prop_usb_voltage_now(chip, &pval);
if (rc < 0) {
chg_stat.usb_mv = 0;
if (rc != -ENODATA)
mmi_err(chip, "Error getting USB Voltage rc = %d\n", rc);
} else
chg_stat.usb_mv = pval.intval / 1000;
if (prev_vbus_mv == -1)
prev_vbus_mv = chg_stat.usb_mv;
rc = get_prop_dc_voltage_now(chip, &pval);
if (rc < 0) {
mmi_dbg(chip, "Error getting DC Voltage rc = %d\n", rc);
chg_stat.dc_mv = 0;
} else
chg_stat.dc_mv = pval.intval / 1000;
rc = get_prop_batt_capacity(chip, chip->bms_psy, &pval);
if (rc < 0) {
mmi_err(chip, "Error getting Batt Capacity rc = %d\n", rc);
return;
} else
chg_stat.batt_soc = pval.intval;
if (chip->last_reported_soc != -1)
chg_stat.batt_soc = chip->last_reported_soc;
rc = get_prop_batt_temp(chip, chip->bms_psy, &pval);
if (rc < 0) {
mmi_err(chip, "Error getting Batt Temperature rc = %d\n", rc);
return;
} else
chg_stat.batt_temp = pval.intval / 10;
rc = get_prop_usb_present(chip, &pval);
if (rc < 0) {
mmi_err(chip, "Error getting USB Present rc = %d\n", rc);
return;
} else
chg_stat.vbus_present = pval.intval;
rc = get_prop_dc_present(chip, &pval);
if (rc < 0) {
mmi_err(chip, "Error getting DC Present rc = %d\n", rc);
return;
} else
chg_stat.dc_present = pval.intval;
rc = get_prop_charger_present(chip, &pval);
if (rc < 0) {
mmi_err(chip, "Error getting Charger Present rc = %d\n", rc);
return;
} else
chg_stat.charger_present = pval.intval & chg_stat.vbus_present;
if (chip->factory_mode)
hb_resch_time = HEARTBEAT_FACTORY_MS;
else if (chg_stat.charger_present || chg_stat.dc_present)
hb_resch_time = HEARTBEAT_DELAY_MS;
else
hb_resch_time = HEARTBEAT_DISCHARGE_MS;
mmi_chrg_rate_check(chip);
if (chip->vbus_enabled && chip->vbus && chg_stat.charger_present) {
rc = regulator_disable(chip->vbus);
if (rc)
mmi_err(chip, "Unable to disable vbus (%d)\n", rc);
else {
chip->vbus_enabled = false;
mmi_info(chip, "VBUS Disable due to Charger\n");
}
}
if (chip->enable_charging_limit && chip->is_factory_image)
update_charging_limit_modes(chip, chg_stat.batt_soc);
if (chip->charging_limit_modes == CHARGING_LIMIT_RUN)
mmi_warn(chip, "Factory Mode/Image so Limiting Charging!!!\n");
mmi_chrg_input_config(chip, &chg_stat);
if (chip->max_main_psy && chip->max_flip_psy) {
cap_err = 0;
rc = power_supply_get_property(chip->max_main_psy,
POWER_SUPPLY_PROP_CAPACITY,
&pval);
if (rc < 0) {
mmi_err(chip, "Couldn't get maxim main capacity\n");
cap_err = rc;
} else if (pval.intval <0) {
cap_err = -EAGAIN; // TI FG RESET may return neg cap prior to init, so delay
} else
main_cap = pval.intval;
rc = power_supply_get_property(chip->max_main_psy,
POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
&pval);
if (rc < 0) {
mmi_err(chip, "Couldn't get maxim main chrg full\n");
cap_err = rc;
} else
main_cap_full = pval.intval;
rc = power_supply_get_property(chip->max_flip_psy,
POWER_SUPPLY_PROP_CAPACITY,
&pval);
if (rc < 0) {
mmi_err(chip, "Couldn't get maxim flip capacity\n");
cap_err = rc;
} else if (pval.intval <0) {
cap_err = -EAGAIN; // TI FG RESET may return neg cap prior to init, so delay
} else
flip_cap = pval.intval;
rc = power_supply_get_property(chip->max_flip_psy,
POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
&pval);
if (rc < 0) {
mmi_err(chip, "Couldn't get maxim flip chrg full\n");
cap_err = rc;
} else
flip_cap_full = pval.intval;
if (cap_err == -EAGAIN) {
mmi_err(chip, "The FGs are not ready yet\n");
hb_resch_time = HEARTBEAT_DUAL_DELAY_MS;
goto sch_hb;
}
report_cap = main_cap * main_cap_full;
report_cap += flip_cap * flip_cap_full;
report_cap /= main_cap_full + flip_cap_full;
if (chip->shut_batt || (report_cap < 0))
report_cap = 0;
else if (report_cap > 100)
report_cap = 100;
if (chip->suspended & WAS_SUSPENDED)
batt_cap = -1;
else
batt_cap = chip->last_reported_soc;
if ((batt_cap == -1) ||
((report_cap != batt_cap) &&
(report_cap <= (batt_cap + MONOTONIC_SOC)) &&
(report_cap >= (batt_cap - MONOTONIC_SOC)))) {
mmi_info(chip, "Updating Reported Capacity to %d, main_cap= %d, flip_cap= %d, main_cap_full= %d, flip_cap_full= %d\n",
report_cap, main_cap, flip_cap, main_cap_full, flip_cap_full);
chip->last_reported_soc = report_cap;
}else if ((batt_cap < 100) && (report_cap > (batt_cap + MONOTONIC_SOC))) {
chip->last_reported_soc++;
mmi_info(chip, "Alter Up Reported Capacity to %d target %d\n",
chip->last_reported_soc, report_cap);
}else if ((batt_cap > 0) && (report_cap < (batt_cap - MONOTONIC_SOC))) {
chip->last_reported_soc--;
mmi_info(chip, "Alter Down Reported Capacity to %d target %d\n",
chip->last_reported_soc, report_cap);
}
/* Age calculation */
rc = power_supply_get_property(chip->max_main_psy,
POWER_SUPPLY_PROP_CHARGE_FULL,
&pval);
if (rc < 0) {
mmi_err(chip, "Couldn't get maxim main charge full\n");
cap_err = rc;
} else
main_cap = pval.intval;
rc = power_supply_get_property(chip->max_flip_psy,
POWER_SUPPLY_PROP_CHARGE_FULL,
&pval);
if (rc < 0) {
mmi_err(chip, "Couldn't get maxim flip charge full\n");
cap_err = rc;
} else
flip_cap = pval.intval;
main_age = ((main_cap / 10) / (main_cap_full / 1000));
flip_age = ((flip_cap / 10) / (flip_cap_full / 1000));
/* Block age output for now until FG can be vetted */
if (cap_err == 0)
chip->age = 100;
mmi_dbg(chip, "Age %d, Main Age %d, Flip Age %d\n",
chip->age, main_age, flip_age);
prev_chrg_step = chip->sm_param[BASE_BATT].pres_chrg_step;
/* Dual Step and Thermal Charging */
hb_resch_time = mmi_dual_charge_control(chip, &chg_stat);
/* Check to see if Voltage is shutdown */
if (chip->shut_batt)
chip->last_reported_soc = 0;
pres_chrg_step = chip->sm_param[BASE_BATT].pres_chrg_step;
if ((prev_chrg_step == STEP_NONE) &&
((pres_chrg_step == STEP_MAX) ||
(pres_chrg_step == STEP_NORM)))
chip->soc_cycles_start = chip->last_reported_soc;
else if ((pres_chrg_step == STEP_NONE) &&
((prev_chrg_step == STEP_MAX) ||
(prev_chrg_step == STEP_NORM)) &&
(chip->last_reported_soc > chip->soc_cycles_start)) {
chip->cycles += (chip->last_reported_soc -
chip->soc_cycles_start);
chip->soc_cycles_start = EMPTY_CYCLES;
} else if ((pres_chrg_step == STEP_FULL) &&
((prev_chrg_step == STEP_MAX) ||
(prev_chrg_step == STEP_NORM)) &&
(100 > chip->soc_cycles_start)) {
chip->cycles += (100 - chip->soc_cycles_start);
chip->soc_cycles_start = EMPTY_CYCLES;
}
} else if (!chip->factory_mode) {
cap_err = 0;
rc = smb_mmi_read_iio_chan(chip,
SMB5_QG_CHARGE_FULL,
&pval.intval);
if (rc < 0) {
mmi_err(chip, "Couldn't get charge full\n");
cap_err = rc;
} else
main_cap = pval.intval;
rc = smb_mmi_read_iio_chan(chip,
SMB5_QG_CHARGE_FULL_DESIGN,
&pval.intval);
if (rc < 0) {
mmi_err(chip, "Couldn't get charge full design\n");
cap_err = rc;
} else
main_cap_full = pval.intval;
if (cap_err == 0)
chip->age = ((main_cap / 10) / (main_cap_full / 1000));
mmi_dbg(chip, "Age %d\n", chip->age);
/* Fall here for Basic Step and Thermal Charging */
mmi_basic_charge_sm(chip, &chg_stat);
}
mmi_dbg(chip, "SMBMMI: batt_mv %d, usb_mv %d, prev_usb_mv %d batt_ma %d\n",
chg_stat.batt_mv, chg_stat.usb_mv,
prev_vbus_mv, chg_stat.batt_ma);
rc = smblib_get_usb_suspend(chip, &usb_suspend);
if (rc < 0) {
usb_suspend = 0;
mmi_err(chip, "Couldn't get USB suspend rc = %d\n", rc);
}
if (!usb_suspend &&
(abs(chg_stat.usb_mv - chg_stat.batt_mv) < REV_BST_BULK_DROP) &&
((chg_stat.usb_mv*1000) > TWO_VOLT)) {
if (((chg_stat.usb_mv < REV_BST_THRESH) &&
((prev_vbus_mv - REV_BST_DROP) > chg_stat.usb_mv)) ||
(chg_stat.batt_ma > REV_BST_MA)) {
mmi_err(chip, "Reverse Boosted: Clear, USB Suspend. usb_mv: %d prev_vbus_mv: %d batt_ma: %d \n", chg_stat.usb_mv, prev_vbus_mv, chg_stat.batt_ma);
if (chip->factory_mode)
smblib_set_usb_suspend(chip, true);
else
vote(chip->usb_icl_votable, BOOST_BACK_VOTER,
true, 0);
msleep(50);
if (chip->factory_mode)
smblib_set_usb_suspend(chip, false);
else
vote(chip->usb_icl_votable, BOOST_BACK_VOTER,
false, 0);
} else {
mmi_err(chip, "Reverse Boosted: USB %d mV PUSB %d mV\n",
chg_stat.usb_mv, prev_vbus_mv);
}
}
prev_vbus_mv = chg_stat.usb_mv;
chip->suspended = 0;
if (chip->factory_mode ||
(chip->is_factory_image && chip->enable_factory_poweroff)) {
rc = power_supply_get_property(chip->pc_port_psy,
POWER_SUPPLY_PROP_ONLINE,
&pval);
if (rc < 0)
goto sch_hb;
pc_online = pval.intval;
rc = power_supply_get_property(chip->usb_psy,
POWER_SUPPLY_PROP_ONLINE,
&pval);
if (rc < 0)
goto sch_hb;
mmi_dbg(chip, "Factory Kill check pc %d, usb %d, susp %d\n",
pc_online, pval.intval, usb_suspend);
if (pc_online ||
pval.intval ||
(usb_suspend && ((chg_stat.usb_mv*1000) > TWO_VOLT))) {
mmi_dbg(chip, "Factory Kill Armed\n");
chip->factory_kill_armed = true;
} else if (chip->factory_kill_armed && !factory_kill_disable) {
mmi_warn(chip, "Factory kill power off\n");
orderly_poweroff(true);
} else
chip->factory_kill_armed = false;
}
sch_hb:
schedule_delayed_work(&chip->heartbeat_work,
msecs_to_jiffies(hb_resch_time));
if (!chg_stat.charger_present)
smb_mmi_awake_vote(chip, false);
chrg_rate_string = kmalloc(CHG_SHOW_MAX_SIZE, GFP_KERNEL);
if (!chrg_rate_string) {
mmi_err(chip, "Failed to Get Uevent Mem\n");
envp[0] = NULL;
} else {
scnprintf(chrg_rate_string, CHG_SHOW_MAX_SIZE,
"POWER_SUPPLY_CHARGE_RATE=%s",
charge_rate[chip->charger_rate]);
envp[0] = chrg_rate_string;
envp[1] = NULL;
}
if (chip->batt_psy) {
smb_mmi_power_supply_changed(chip->batt_psy, envp);
} else if (chip->qcom_psy) {
smb_mmi_power_supply_changed(chip->qcom_psy, envp);
}
kfree(chrg_rate_string);
}
static int mmi_psy_notifier_call(struct notifier_block *nb, unsigned long val,
void *v)
{
struct smb_mmi_charger *chip = container_of(nb,
struct smb_mmi_charger, mmi_psy_notifier);
struct power_supply *psy = v;
static bool first_boot = true;
if (!chip) {
pr_err("SMBMMI: called before chip valid!\n");
return NOTIFY_DONE;
}
if (val != PSY_EVENT_PROP_CHANGED)
return NOTIFY_OK;
if (psy &&
((strcmp(psy->desc->name, "usb") == 0) ||
(strcmp(psy->desc->name, "wireless") == 0) ||
(strcmp(psy->desc->name, "dc") == 0))) {
mmi_info(chip, "Psy notifier call %s\n", psy->desc->name);
if(first_boot && chip->factory_mode)
{
//In factory mode for the first heartbeat make sure to wait 10 second until boot is complete
cancel_delayed_work(&chip->heartbeat_work);
schedule_delayed_work(&chip->heartbeat_work,
msecs_to_jiffies(10000));
first_boot = false;
}
else
{
cancel_delayed_work(&chip->heartbeat_work);
schedule_delayed_work(&chip->heartbeat_work,
msecs_to_jiffies(0));
first_boot = false;
}
}
return NOTIFY_OK;
}
static int smbchg_reboot(struct notifier_block *nb,
unsigned long event, void *unused)
{
struct smb_mmi_charger *chg = container_of(nb, struct smb_mmi_charger,
smb_reboot);
union power_supply_propval val;
int rc;
pr_err("SMBMMI: Reboot/POFF\n");
if (!chg) {
pr_err("SMBMMI: called before chip valid!\n");
return NOTIFY_DONE;
}
if (chg->factory_mode) {
switch (event) {
case SYS_POWER_OFF:
/* Disable Factory Kill */
factory_kill_disable = true;
/* Disable Charging */
smblib_masked_write_mmi(chg, CHARGING_ENABLE_CMD_REG,
CHARGING_ENABLE_CMD_BIT,
0);
/* Suspend USB and DC */
smblib_set_usb_suspend(chg, true);
rc = power_supply_get_property(chg->usb_psy,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
&val);
while ((rc >= 0) && (val.intval > TWO_VOLT)) {
msleep(100);
rc = power_supply_get_property(
chg->usb_psy,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
&val);
mmi_info(chg, "Wait for VBUS to decay\n");
}
mmi_info(chg, "VBUS UV wait 1 sec!\n");
/* Delay 1 sec to allow more VBUS decay */
msleep(1000);
break;
default:
break;
}
}
return NOTIFY_DONE;
}
static const struct power_supply_desc mmi_psy_desc = {
.name = "mmi_battery",
.type = POWER_SUPPLY_TYPE_MAINS,
.get_property = smb_mmi_get_property,
.set_property = smb_mmi_set_property,
.properties = smb_mmi_battery_props,
.num_properties = ARRAY_SIZE(smb_mmi_battery_props),
};
static enum power_supply_property batt_props[] = {
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_HEALTH,
POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_CHARGE_TYPE,
POWER_SUPPLY_PROP_CAPACITY,
POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
POWER_SUPPLY_PROP_VOLTAGE_MAX,
POWER_SUPPLY_PROP_CURRENT_NOW,
POWER_SUPPLY_PROP_CURRENT_MAX,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT,
POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT,
POWER_SUPPLY_PROP_TEMP,
POWER_SUPPLY_PROP_TECHNOLOGY,
POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX,
POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT,
POWER_SUPPLY_PROP_CHARGE_COUNTER,
POWER_SUPPLY_PROP_CYCLE_COUNT,
POWER_SUPPLY_PROP_CHARGE_FULL,
POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
POWER_SUPPLY_PROP_TIME_TO_FULL_NOW,
};
static int batt_get_prop(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct smb_mmi_charger *chip = power_supply_get_drvdata(psy);
int rc = 0;
switch (psp) {
case POWER_SUPPLY_PROP_STATUS:
if (chip->last_reported_status == -1)
rc = power_supply_get_property(chip->qcom_psy,
psp, val);
else
val->intval = chip->last_reported_status;
break;
case POWER_SUPPLY_PROP_CAPACITY:
if (chip->last_reported_soc == -1)
rc = power_supply_get_property(chip->qcom_psy,
psp, val);
else
val->intval = chip->last_reported_soc;
break;
case POWER_SUPPLY_PROP_CYCLE_COUNT:
val->intval = chip->cycles / 100;
break;
case POWER_SUPPLY_PROP_HEALTH:
if (chip->max_main_psy && chip->max_flip_psy)
val->intval = chip->sm_param[MAIN_BATT].batt_health;
else
val->intval = chip->sm_param[BASE_BATT].batt_health;
break;
case POWER_SUPPLY_PROP_CURRENT_MAX:
val->intval = get_effective_result(chip->fcc_votable);
break;
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
if (chip->smb_version == PMI632) {
val->intval = get_effective_result(chip->fcc_votable);
break;
}
rc = power_supply_get_property(chip->qcom_psy, psp, val);
if (rc < 0) {
/* soft fail so uevents are not blocked */
rc = 0;
val->intval = -EINVAL;
}
break;
case POWER_SUPPLY_PROP_TEMP:
if (chip->max_main_psy && chip->max_flip_psy) {
union power_supply_propval main_psy_val;
union power_supply_propval flip_psy_val;
/* Get the highest temp between main psy and flip psy */
rc = power_supply_get_property(chip->max_main_psy, psp, &main_psy_val);
if (rc >= 0) {
rc = power_supply_get_property(chip->max_flip_psy, psp, &flip_psy_val);
if (rc >= 0) {
if (main_psy_val.intval > flip_psy_val.intval)
val->intval = main_psy_val.intval;
else
val->intval = flip_psy_val.intval;
break;
}
}
}
/* Fall through */
default:
rc = power_supply_get_property(chip->qcom_psy, psp, val);
if (rc < 0) {
mmi_dbg(chip, "Get Unknown prop %d rc = %d\n", psp, rc);
/* soft fail so uevents are not blocked */
rc = 0;
val->intval = -EINVAL;
}
break;
}
return rc;
}
static int batt_set_prop(struct power_supply *psy,
enum power_supply_property prop,
const union power_supply_propval *val)
{
int rc = 0;
struct smb_mmi_charger *chip = power_supply_get_drvdata(psy);
switch (prop) {
case POWER_SUPPLY_PROP_CYCLE_COUNT:
chip->cycles += val->intval * 100;
break;
case POWER_SUPPLY_PROP_CURRENT_MAX:
if (val->intval < 0) {
vote(chip->fcc_votable, MMI_VOTER, false, 0);
} else
vote(chip->fcc_votable, MMI_VOTER, true, val->intval);
break;
default:
rc = power_supply_set_property(chip->qcom_psy, prop, val);
if (rc < 0) {
mmi_dbg(chip, "Get Unknown prop %d rc = %d\n", prop, rc);
/* soft fail so uevents are not blocked */
rc = 0;
}
break;
}
return rc;
}
static int batt_prop_is_writeable(struct power_supply *psy,
enum power_supply_property psp)
{
switch (psp) {
case POWER_SUPPLY_PROP_STATUS:
case POWER_SUPPLY_PROP_CAPACITY:
case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
case POWER_SUPPLY_PROP_CYCLE_COUNT:
case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT:
case POWER_SUPPLY_PROP_CURRENT_MAX:
return 1;
default:
break;
}
return 0;
}
static const struct power_supply_desc batt_psy_desc = {
.name = "battery",
.type = POWER_SUPPLY_TYPE_BATTERY,
.get_property = batt_get_prop,
.set_property = batt_set_prop,
.property_is_writeable = batt_prop_is_writeable,
.properties = batt_props,
.num_properties = ARRAY_SIZE(batt_props),
};
static int parse_mmi_dt(struct smb_mmi_charger *chg)
{
struct device_node *node = chg->dev->of_node;
int rc = 0;
int byte_len;
int i;
struct mmi_sm_params *chip;
if (!node) {
mmi_err(chg, "mmi dtree info. missing\n");
return -ENODEV;
}
chip = &chg->sm_param[BASE_BATT];
if (of_find_property(node, "qcom,mmi-temp-zones", &byte_len)) {
if ((byte_len / sizeof(u32)) % 4) {
mmi_err(chg, "DT error wrong mmi temp zones\n");
return -ENODEV;
}
chip->temp_zones = (struct mmi_temp_zone *)
devm_kzalloc(chg->dev, byte_len, GFP_KERNEL);
if (chip->temp_zones == NULL)
return -ENOMEM;
chip->num_temp_zones =
byte_len / sizeof(struct mmi_temp_zone);
rc = of_property_read_u32_array(node,
"qcom,mmi-temp-zones",
(u32 *)chip->temp_zones,
byte_len / sizeof(u32));
if (rc < 0) {
mmi_err(chg, "Couldn't read mmi temp zones rc = %d\n", rc);
return rc;
}
mmi_info(chg, "mmi temp zones: Num: %d\n", chip->num_temp_zones);
for (i = 0; i < chip->num_temp_zones; i++) {
mmi_info(chg, "mmi temp zones: Zone %d, Temp %d C, " \
"Step Volt %d mV, Full Rate %d mA, " \
"Taper Rate %d mA\n", i,
chip->temp_zones[i].temp_c,
chip->temp_zones[i].norm_mv,
chip->temp_zones[i].fcc_max_ma,
chip->temp_zones[i].fcc_norm_ma);
}
chip->pres_temp_zone = ZONE_NONE;
chip->pres_chrg_step = STEP_NONE;
}
if (of_find_property(node, "qcom,mmi-ffc-zones", &byte_len)) {
if ((byte_len / sizeof(struct mmi_ffc_zone) != chip->num_temp_zones)
|| ((byte_len / sizeof(u32)) % 3)) {
mmi_err(chg, "DT error wrong mmi ffc zones\n");
return -ENODEV;
}
chip->ffc_zones = (struct mmi_ffc_zone *)
devm_kzalloc(chg->dev, byte_len, GFP_KERNEL);
if (chip->ffc_zones == NULL)
return -ENOMEM;
rc = of_property_read_u32_array(node,
"qcom,mmi-ffc-zones",
(u32 *)chip->ffc_zones,
byte_len / sizeof(u32));
if (rc < 0) {
mmi_err(chg, "Couldn't read mmi ffc zones rc = %d\n", rc);
return rc;
}
for (i = 0; i < chip->num_temp_zones; i++) {
mmi_info(chg, "FFC:Zone %d,Volt %d,Ich %d,Iqg %d", i,
chip->ffc_zones[i].ffc_max_mv,
chip->ffc_zones[i].ffc_chg_iterm,
chip->ffc_zones[i].ffc_qg_iterm);
}
} else
chip->ffc_zones = NULL;
rc = of_property_read_u32(node, "qcom,iterm-ma",
&chip->chrg_iterm);
if (rc)
chip->chrg_iterm = 150;
rc = of_property_read_u32(node, "qcom,vfloat-comp-uv",
&chg->vfloat_comp_mv);
if (rc)
chg->vfloat_comp_mv = 0;
chg->vfloat_comp_mv /= 1000;
// none ffc parameter
rc = of_property_read_u32(node, "qcom,noffc-chg-iterm",
&chg->noffc_chg_iterm);
if (rc)
chg->noffc_chg_iterm = -EINVAL;
rc = of_property_read_u32(node, "qcom,noffc-chg-iterm-45c",
&chg->noffc_chg_iterm_45c);
if (rc)
chg->noffc_chg_iterm_45c = -EINVAL;
rc = of_property_read_u32(node, "qcom,noffc-qg-iterm",
&chg->noffc_qg_iterm);
if (rc)
chg->noffc_qg_iterm = -EINVAL;
rc = of_property_read_u32(node, "qcom,noffc-max-fv",
&chg->noffc_max_fv);
if (rc)
chg->noffc_max_fv = -EINVAL;
chg->enable_charging_limit =
of_property_read_bool(node, "qcom,enable-charging-limit");
chg->enable_factory_poweroff =
of_property_read_bool(node, "qcom,enable-factory-poweroff");
rc = of_property_read_u32(node, "qcom,upper-limit-capacity",
&chg->upper_limit_capacity);
if (rc)
chg->upper_limit_capacity = 100;
rc = of_property_read_u32(node, "qcom,lower-limit-capacity",
&chg->lower_limit_capacity);
if (rc)
chg->lower_limit_capacity = 0;
rc = of_property_read_u32(node, "qcom,dc-icl-ma",
&chg->dc_cl_ma);
if (rc)
chg->dc_cl_ma = -EINVAL;
chg->enable_factory_mode_aicl =
of_property_read_bool(node, "mmi,enable-factory-mode-aicl");
mmi_charger_power_support(chg);
rc = of_property_read_u32(node, "qcom,inc-hvdcp-cnt",
&chg->inc_hvdcp_cnt);
if (rc)
chg->inc_hvdcp_cnt = HVDCP_PULSE_COUNT_MAX;
return rc;
}
static int parse_mmi_dual_dt(struct smb_mmi_charger *chg)
{
struct device_node *node = chg->dev->of_node;
int rc = 0;
int byte_len;
int i;
struct mmi_sm_params *chip;
if (!node) {
mmi_err(chg, "mmi dtree info. missing\n");
return -ENODEV;
}
chip = &chg->sm_param[MAIN_BATT];
if (of_find_property(node, "qcom,mmi-temp-zones-main", &byte_len)) {
if ((byte_len / sizeof(u32)) % 4) {
mmi_err(chg, "DT error wrong mmi temp zones\n");
return -ENODEV;
}
chip->temp_zones = (struct mmi_temp_zone *)
devm_kzalloc(chg->dev, byte_len, GFP_KERNEL);
if (chip->temp_zones == NULL)
return -ENOMEM;
chip->num_temp_zones =
byte_len / sizeof(struct mmi_temp_zone);
rc = of_property_read_u32_array(node,
"qcom,mmi-temp-zones-main",
(u32 *)chip->temp_zones,
byte_len / sizeof(u32));
if (rc < 0) {
mmi_err(chg, "Couldn't read mmi temp zones rc = %d\n", rc);
return rc;
}
mmi_info(chg, "mmi temp zones main: Num: %d\n", chip->num_temp_zones);
for (i = 0; i < chip->num_temp_zones; i++) {
mmi_info(chg, "mmi temp zones: Zone %d, Temp %d C, " \
"Step Volt %d mV, Full Rate %d mA, " \
"Taper Rate %d mA\n", i,
chip->temp_zones[i].temp_c,
chip->temp_zones[i].norm_mv,
chip->temp_zones[i].fcc_max_ma,
chip->temp_zones[i].fcc_norm_ma);
}
chip->pres_temp_zone = ZONE_NONE;
}
rc = of_property_read_u32(node, "qcom,iterm-ma-main",
&chip->chrg_iterm);
if (rc)
chip->chrg_iterm = 150;
chip = &chg->sm_param[FLIP_BATT];
if (of_find_property(node, "qcom,mmi-temp-zones-flip", &byte_len)) {
if ((byte_len / sizeof(u32)) % 4) {
mmi_err(chg, "DT error wrong mmi temp zones\n");
return -ENODEV;
}
chip->temp_zones = (struct mmi_temp_zone *)
devm_kzalloc(chg->dev, byte_len, GFP_KERNEL);
if (chip->temp_zones == NULL)
return -ENOMEM;
chip->num_temp_zones =
byte_len / sizeof(struct mmi_temp_zone);
rc = of_property_read_u32_array(node,
"qcom,mmi-temp-zones-flip",
(u32 *)chip->temp_zones,
byte_len / sizeof(u32));
if (rc < 0) {
mmi_err(chg, "Couldn't read mmi temp zones rc = %d\n", rc);
return rc;
}
mmi_info(chg, "mmi temp zones main: Num: %d\n", chip->num_temp_zones);
for (i = 0; i < chip->num_temp_zones; i++) {
mmi_info(chg, "mmi temp zones: Zone %d, Temp %d C, " \
"Step Volt %d mV, Full Rate %d mA, " \
"Taper Rate %d mA\n", i,
chip->temp_zones[i].temp_c,
chip->temp_zones[i].norm_mv,
chip->temp_zones[i].fcc_max_ma,
chip->temp_zones[i].fcc_norm_ma);
}
chip->pres_temp_zone = ZONE_NONE;
}
rc = of_property_read_u32(node, "qcom,iterm-ma-flip",
&chip->chrg_iterm);
if (rc)
chip->chrg_iterm = 150;
return rc;
}
static int smb_mmi_chg_config_init(struct smb_mmi_charger *chip)
{
int subtype = (u8)of_device_get_match_data(chip->dev);
switch (subtype) {
case PM8150B:
chip->smb_version = PM8150B;
chip->name = "pm8150b_charger";
chip->param = smb5_pm8150b_params;
break;
case PM7250B:
chip->smb_version = PM7250B;
chip->name = "pm7250b_charger";
chip->param = smb5_pm8150b_params;
break;
case PMI632:
chip->smb_version = PMI632;
chip->name = "pmi632_charger";
chip->param = smb5_pmi632_params;
break;
default:
pr_err("SMBMMI: PMIC subtype %d not supported\n",
subtype);
return -EINVAL;
}
chip->chg_freq.freq_5V = 600;
chip->chg_freq.freq_6V_8V = 800;
chip->chg_freq.freq_9V = 1050;
chip->chg_freq.freq_12V = 1200;
pr_err("SMBMMI: PMIC %d is %s\n", chip->smb_version, chip->name);
return 0;
}
static ssize_t charge_rate_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
if (!this_chip) {
pr_err("SMBMMI: mmi_chip is not initialized\n");
return 0;
}
mmi_chrg_rate_check(this_chip);
return scnprintf(buf, CHG_SHOW_MAX_SIZE, "%s\n",
charge_rate[this_chip->charger_rate]);
}
static DEVICE_ATTR(charge_rate, S_IRUGO, charge_rate_show, NULL);
static ssize_t age_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
if (!this_chip) {
pr_err("SMBMMI: mmi_chip is not initialized\n");
return 0;
}
return scnprintf(buf, CHG_SHOW_MAX_SIZE, "%d\n", this_chip->age);
}
static DEVICE_ATTR(age, S_IRUGO, age_show, NULL);
static struct attribute * mmi_g[] = {
&dev_attr_charge_rate.attr,
&dev_attr_age.attr,
NULL,
};
static const struct attribute_group power_supply_mmi_attr_group = {
.attrs = mmi_g,
};
#if defined(CONFIG_DEBUG_FS)
static int register_dump_read(struct seq_file *m, void *data)
{
int rc;
u8 stat;
int i;
struct smb_mmi_charger *chip = m->private;
for (i = CHGR_BASE; i < MISC_BASE + 0x100; i++) {
rc = smblib_read_mmi(chip, i, &stat);
if (rc < 0)
continue;
seq_printf(m, "REG:0x%x: 0x%x\n", i, stat);
}
return 0;
}
static int register_dump_debugfs_open(struct inode *inode, struct file *file)
{
struct smb_mmi_charger *chip = inode->i_private;
return single_open(file, register_dump_read, chip);
}
static const struct file_operations register_dump_debugfs_ops = {
.owner = THIS_MODULE,
.open = register_dump_debugfs_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static void smb_mmi_create_debugfs(struct smb_mmi_charger *chip)
{
struct dentry *dfs_root, *file;
dfs_root = debugfs_create_dir("charger_mmi", NULL);
if (IS_ERR_OR_NULL(dfs_root)) {
pr_err("SMBMMI: Couldn't create charger debugfs rc=%ld\n",
(long)dfs_root);
return;
}
file = debugfs_create_file("register_dump",
S_IRUSR | S_IRGRP | S_IROTH,
dfs_root, chip, &register_dump_debugfs_ops);
if (IS_ERR_OR_NULL(file))
pr_err("SMBMMI: Couldn't create register_dump file rc=%ld\n",
(long)file);
}
#else
static void smb_mmi_create_debugfs(struct smb_mmi_charger *chip)
{}
#endif
static int mmi_smbcharger_iio_write_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan, int val1,
int val2, long mask)
{
struct smb_mmi_charger *chip = iio_priv(indio_dev);
int rc = 0;
switch (chan->channel) {
case PSY_IIO_CP_ENABLE:
chip->cp_active = !!val1;
break;
default:
pr_err("Unsupported mmi_charger IIO chan %d\n", chan->channel);
rc = -EINVAL;
break;
}
if (rc < 0)
pr_err("Couldn't write IIO channel %d, rc = %d\n",
chan->channel, rc);
return rc;
}
static int mmi_smbcharger_iio_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan, int *val1,
int *val2, long mask)
{
struct smb_mmi_charger *chip = iio_priv(indio_dev);
int rc = 0;
*val1 = 0;
switch (chan->channel) {
case PSY_IIO_CP_ENABLE:
*val1 = chip->cp_active;
break;
default:
pr_err("Unsupported mmi_charger IIO chan %d\n", chan->channel);
rc = -EINVAL;
break;
}
if (rc < 0) {
pr_err("Couldn't read IIO channel %d, rc = %d\n",
chan->channel, rc);
return rc;
}
return IIO_VAL_INT;
}
static int mmi_smbcharger_iio_of_xlate(struct iio_dev *indio_dev,
const struct of_phandle_args *iiospec)
{
struct smb_mmi_charger *chip = iio_priv(indio_dev);
struct iio_chan_spec *iio_chan = chip->iio_chan;
int i;
for (i = 0; i < ARRAY_SIZE(mmi_smbcharger_iio_psy_channels);
i++, iio_chan++)
if (iio_chan->channel == iiospec->args[0])
return i;
return -EINVAL;
}
static const struct iio_info mmi_smbcharger_iio_info = {
.read_raw = mmi_smbcharger_iio_read_raw,
.write_raw = mmi_smbcharger_iio_write_raw,
.of_xlate = mmi_smbcharger_iio_of_xlate,
};
static int smb_mmi_init_iio_psy(struct smb_mmi_charger *chip,
struct platform_device *pdev)
{
struct iio_dev *indio_dev = chip->indio_dev;
struct iio_chan_spec *chan;
int mmi_smbcharger_num_iio_channels = ARRAY_SIZE(mmi_smbcharger_iio_psy_channels);
int rc, i;
chip->iio_chan = devm_kcalloc(chip->dev, mmi_smbcharger_num_iio_channels,
sizeof(*chip->iio_chan), GFP_KERNEL);
if (!chip->iio_chan)
return -ENOMEM;
chip->int_iio_chans = devm_kcalloc(chip->dev,
mmi_smbcharger_num_iio_channels,
sizeof(*chip->int_iio_chans),
GFP_KERNEL);
if (!chip->int_iio_chans)
return -ENOMEM;
indio_dev->info = &mmi_smbcharger_iio_info;
indio_dev->dev.parent = chip->dev;
indio_dev->dev.of_node = chip->dev->of_node;
indio_dev->name = "mmi-smbcharger-iio";
indio_dev->modes = INDIO_DIRECT_MODE;
indio_dev->channels = chip->iio_chan;
indio_dev->num_channels = mmi_smbcharger_num_iio_channels;
for (i = 0; i < mmi_smbcharger_num_iio_channels; i++) {
chip->int_iio_chans[i].indio_dev = indio_dev;
chan = &chip->iio_chan[i];
chip->int_iio_chans[i].channel = chan;
chan->address = i;
chan->channel = mmi_smbcharger_iio_psy_channels[i].channel_num;
chan->type = mmi_smbcharger_iio_psy_channels[i].type;
chan->datasheet_name =
mmi_smbcharger_iio_psy_channels[i].datasheet_name;
chan->extend_name =
mmi_smbcharger_iio_psy_channels[i].datasheet_name;
chan->info_mask_separate =
mmi_smbcharger_iio_psy_channels[i].info_mask;
}
rc = devm_iio_device_register(chip->dev, indio_dev);
if (rc) {
pr_err("Failed to register mmi-smbcharger-iio IIO device, rc=%d\n", rc);
return rc;
}
chip->ext_iio_chans = devm_kcalloc(chip->dev,
ARRAY_SIZE(smb_mmi_ext_iio_chan_name),
sizeof(*chip->ext_iio_chans),
GFP_KERNEL);
if (!chip->ext_iio_chans)
return -ENOMEM;
return rc;
}
static int smb_mmi_probe(struct platform_device *pdev)
{
struct smb_mmi_charger *chip;
struct iio_dev *indio_dev;
int rc = 0;
union power_supply_propval val;
struct power_supply_config psy_cfg = {};
const char *max_main_name, *max_flip_name;
union power_supply_propval pval;
indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*chip));
if (!indio_dev)
return -ENOMEM;
chip = iio_priv(indio_dev);
if (!chip) {
dev_err(&pdev->dev,
"Unable to alloc memory for mmi_smbcharger_iio\n");
return -ENOMEM;
}
chip->indio_dev = indio_dev;
chip->dev = &pdev->dev;
psy_cfg.drv_data = chip;
platform_set_drvdata(pdev, chip);
chip->suspended = 0;
chip->awake = false;
this_chip = chip;
device_init_wakeup(chip->dev, true);
chip->hb_startup_cnt = HEARTBEAT_OCP_SETTLE_CNT;
chip->ocp_flag = true;
smb_mmi_chg_config_init(chip);
chip->debug_enabled = &debug_enabled;
chip->ipc_log = ipc_log_context_create(MMI_LOG_PAGES, "charger", 0);
if (!chip->ipc_log)
mmi_err(chip, "Failed to create SMBMMI IPC log\n");
else
mmi_info(chip, "IPC logging is enabled for SMBMMI\n");
chip->regmap = dev_get_regmap(chip->dev->parent, NULL);
if (!chip->regmap) {
mmi_err(chip, "Parent regmap is missing\n");
return -EINVAL;
}
smb_mmi_init_iio_psy(chip, pdev);
parse_mmi_dt(chip);
INIT_DELAYED_WORK(&chip->heartbeat_work, mmi_heartbeat_work);
INIT_DELAYED_WORK(&chip->weakcharger_work, mmi_weakcharger_work);
chip->mmi_psy = devm_power_supply_register(chip->dev, &mmi_psy_desc,
&psy_cfg);
if (IS_ERR(chip->mmi_psy)) {
mmi_err(chip, "failed: mmi power supply register\n");
return PTR_ERR(chip->mmi_psy);
}
chip->age = 100;
chip->cycles = 0;
chip->shut_batt = false;
chip->soc_cycles_start = EMPTY_CYCLES;
chip->last_reported_soc = -1;
chip->last_reported_status = -1;
chip->qcom_psy = power_supply_get_by_name("qcom_battery");
if (chip->qcom_psy) {
chip->batt_psy = devm_power_supply_register(chip->dev,
&batt_psy_desc,
&psy_cfg);
if (IS_ERR(chip->batt_psy)) {
mmi_err(chip,
"Failed: batt power supply register\n");
return PTR_ERR(chip->batt_psy);
}
rc = sysfs_create_group(&chip->batt_psy->dev.kobj,
&power_supply_mmi_attr_group);
if (rc)
mmi_err(chip, "Failed: attr create\n");
} else {
chip->qcom_psy = power_supply_get_by_name("battery");
chip->batt_psy = NULL;
if (chip->qcom_psy) {
rc = sysfs_create_group(&chip->qcom_psy->dev.kobj,
&power_supply_mmi_attr_group);
if (rc)
mmi_err(chip, "Failed: attr create\n");
} else {
mmi_err(chip, "Failed: SMB MMI probed failed, wait SMB5 prob\n");
return -EPROBE_DEFER;
}
}
chip->bms_psy = power_supply_get_by_name("bms");
chip->usb_psy = power_supply_get_by_name("usb");
chip->pc_port_psy = power_supply_get_by_name("pc_port");
chip->dc_psy = power_supply_get_by_name("dc");
chip->wls_psy = power_supply_get_by_name("wireless");
/* parse the dc power supply configuration */
rc = of_property_read_string(pdev->dev.of_node,
"mmi,max-main-psy", &max_main_name);
rc |= of_property_read_string(pdev->dev.of_node,
"mmi,max-flip-psy", &max_flip_name);
if (!rc && max_main_name && max_flip_name) {
chip->max_main_psy = power_supply_get_by_name(max_main_name);
chip->max_flip_psy = power_supply_get_by_name(max_flip_name);
parse_mmi_dual_dt(chip);
}
chip->chg_dis_votable = find_votable("CHG_DISABLE");
if (IS_ERR(chip->chg_dis_votable))
chip->chg_dis_votable = NULL;
chip->fcc_votable = find_votable("FCC");
if (IS_ERR(chip->fcc_votable))
chip->fcc_votable = NULL;
chip->fv_votable = find_votable("FV");
if (IS_ERR(chip->fv_votable))
chip->fv_votable = NULL;
chip->usb_icl_votable = find_votable("USB_ICL");
if (IS_ERR(chip->usb_icl_votable))
chip->usb_icl_votable = NULL;
chip->dc_suspend_votable = find_votable("DC_SUSPEND");
if (IS_ERR(chip->dc_suspend_votable))
chip->dc_suspend_votable = NULL;
if (chip->smb_version == PM8150B) {
if (smblib_masked_write_mmi(chip, LEGACY_CABLE_CFG_REG,
0xFF, 0))
mmi_err(chip, "Could not set Legacy Cable CFG\n");
}
if (chip->smb_version == PM8150B ||
chip->smb_version == PM7250B ||
chip->smb_version == PMI632) {
mmi_err(chip, "DISABLE all QCOM JEITA\n");
/* Ensure SW JEITA is DISABLED */
pval.intval = 0;
smb_mmi_write_iio_chan(chip,
SMB5_SW_JEITA_ENABLED, pval.intval);
/* Ensure HW JEITA is DISABLED */
if (smblib_masked_write_mmi(chip, PM8150B_JEITA_EN_CFG_REG,
0xFF, 0x00))
mmi_err(chip, "Could not disable JEITA CFG\n");
}
if ((chip->smb_version == PM8150B) ||
(chip->smb_version == PM7250B) ||
(chip->smb_version == PMI632))
if (smblib_masked_write_mmi(chip, USBIN_ADAPTER_ALLOW_CFG_REG,
USBIN_ADAPTER_ALLOW_MASK,
USBIN_ADAPTER_ALLOW_5V_TO_9V))
mmi_err(chip, "Could not set USB Adapter CFG\n");
/*
* Only 5V is allowed for QC2.0 and QC3.0, so set pulse count max
* accordingly to limit voltage increment.
*/
if (chip->hvdcp_power_max) {
rc = smblib_masked_write_mmi(chip, HVDCP_PULSE_COUNT_MAX_REG,
HVDCP_PULSE_COUNT_MAX_QC2_MASK |
HVDCP_PULSE_COUNT_MAX_QC3_MASK,
chip->inc_hvdcp_cnt);
if (rc < 0)
mmi_err(chip, "Could not set HVDCP pulse count max\n");
}
chip->factory_mode = mmi_factory_check(MMI_FACTORY_MODE);
chip->is_factory_image = mmi_factory_check(MMI_FACTORY_BUILD);
chip->charging_limit_modes = CHARGING_LIMIT_UNKNOWN;
if (chip->dc_cl_ma >= 0) {
rc = smblib_set_charge_param(chip, &chip->param.dc_icl,
chip->dc_cl_ma * 1000);
if (rc)
mmi_err(chip, "Failed to set DC ICL %d\n",
chip->dc_cl_ma);
}
/* Workaround for some cables that collapse on boot */
if (!chip->factory_mode) {
dev_err(chip->dev, "SMBMMI: Suspending USB for 50 ms to clear\n");
smblib_set_usb_suspend(chip, true);
msleep(50);
smblib_set_usb_suspend(chip, false);
}
if (chip->factory_mode &&
(chip->smb_version == PMI632)) {
rc = smblib_masked_write_mmi(chip, USBIN_INT_EN_CLR,
0xFF, USBIN_OV_EN_CLR);
if (rc) {
mmi_err(chip, "Could Not disable usbin ov irq\n");
}
}
rc = device_create_file(chip->dev,
&dev_attr_force_demo_mode);
if (rc) {
mmi_err(chip, "Couldn't create force_demo_mode\n");
}
rc = device_create_file(chip->dev,
&dev_attr_force_max_chrg_temp);
if (rc) {
mmi_err(chip, "Couldn't create force_max_chrg_temp\n");
}
rc = device_create_file(chip->dev,
&dev_attr_factory_image_mode);
if (rc)
mmi_err(chip, "Couldn't create factory_image_mode\n");
rc = device_create_file(chip->dev,
&dev_attr_factory_charge_upper);
if (rc)
mmi_err(chip, "Couldn't create factory_charge_upper\n");
rc = device_create_file(chip->dev,
&dev_attr_force_hvdcp_power_max);
if (rc)
mmi_err(chip, "Couldn't create force_hvdcp_power_max\n");
rc = device_create_file(chip->dev,
&dev_attr_force_pd_power_max);
if (rc)
mmi_err(chip, "Couldn't create force_pd_power_max\n");
/* Register the notifier for the psy updates*/
chip->mmi_psy_notifier.notifier_call = mmi_psy_notifier_call;
rc = power_supply_reg_notifier(&chip->mmi_psy_notifier);
if (rc)
mmi_err(chip, "Failed to reg notifier: %d\n", rc);
if (chip->factory_mode) {
mmi_info(chip, "Entering Factory Mode SMB!\n");
rc = smblib_masked_write_mmi(chip, USBIN_ICL_OPTIONS_REG,
USBIN_MODE_CHG_BIT,
USBIN_MODE_CHG_BIT);
if (rc < 0)
mmi_err(chip,
"Couldn't set USBIN_ICL_OPTIONS rc=%d\n", rc);
rc = smblib_masked_write_mmi(chip, USBIN_LOAD_CFG_REG,
ICL_OVERRIDE_AFTER_APSD_BIT,
ICL_OVERRIDE_AFTER_APSD_BIT);
if (rc < 0)
mmi_err(chip,
"Couldn't set USBIN_LOAD_CFG rc=%d\n", rc);
if (!chip->enable_factory_mode_aicl) {
rc = smblib_masked_write_mmi(chip, USBIN_AICL_OPTIONS_CFG_REG,
0xFF, 0x00);
if (rc < 0)
mmi_err(chip,
"Couldn't set USBIN_AICL_OPTIONS rc=%d\n", rc);
}
chip->smb_reboot.notifier_call = smbchg_reboot;
chip->smb_reboot.next = NULL;
chip->smb_reboot.priority = 1;
rc = register_reboot_notifier(&chip->smb_reboot);
if (rc)
mmi_err(chip, "Register for reboot failed\n");
rc = power_supply_get_property(chip->pc_port_psy,
POWER_SUPPLY_PROP_ONLINE,
&val);
if (rc >= 0 && val.intval) {
mmi_info(chip, "Factory Kill Armed\n");
chip->factory_kill_armed = true;
}
if(chip->chg_dis_votable) {
pmic_vote_force_val_set(chip->chg_dis_votable, 1);
pmic_vote_force_active_set(chip->chg_dis_votable, 1);
}
if(chip->fcc_votable) {
pmic_vote_force_val_set(chip->fcc_votable, 3000000);
pmic_vote_force_active_set(chip->fcc_votable, 1);
}
if(chip->fv_votable) {
pmic_vote_force_val_set(chip->fv_votable, 4400000);
pmic_vote_force_active_set(chip->fv_votable, 1);
}
/* Some Cables need a more forced approach */
rc = smblib_set_charge_param(chip, &chip->param.usb_icl,
3000000);
if (rc < 0)
mmi_err(chip,
"Factory Couldn't set usb icl = 3000 rc=%d\n",
(int)rc);
rc = device_create_file(chip->dev,
&dev_attr_smblib_mmi_address);
if (rc) {
mmi_err(chip,
"Couldn't create smblib_mmi_address\n");
}
rc = device_create_file(chip->dev,
&dev_attr_smblib_mmi_data);
if (rc) {
mmi_err(chip,
"Couldn't create smblib_mmi_data\n");
}
rc = device_create_file(chip->dev,
&dev_attr_force_chg_usb_suspend);
if (rc) {
mmi_err(chip,
"Couldn't create force_chg_usb_suspend\n");
}
rc = device_create_file(chip->dev,
&dev_attr_force_chg_fail_clear);
if (rc) {
mmi_err(chip,
"Couldn't create force_chg_fail_clear\n");
}
rc = device_create_file(chip->dev,
&dev_attr_force_chg_auto_enable);
if (rc) {
mmi_err(chip,
"Couldn't create force_chg_auto_enable\n");
}
rc = device_create_file(chip->dev,
&dev_attr_force_chg_ibatt);
if (rc) {
mmi_err(chip,
"Couldn't create force_chg_ibatt\n");
}
rc = device_create_file(chip->dev,
&dev_attr_force_chg_iusb);
if (rc) {
mmi_err(chip,
"Couldn't create force_chg_iusb\n");
}
rc = device_create_file(chip->dev,
&dev_attr_force_chg_idc);
if (rc) {
mmi_err(chip, "Couldn't create force_chg_idc\n");
}
rc = device_create_file(chip->dev,
&dev_attr_force_chg_itrick);
if (rc) {
mmi_err(chip, "Couldn't create force_chg_itrick\n");
}
}
smb_mmi_create_debugfs(chip);
cancel_delayed_work(&chip->heartbeat_work);
if(chip->factory_mode)
{
//delay heartbeat in factory mode until after boot
schedule_delayed_work(&chip->heartbeat_work,
msecs_to_jiffies(10000));
}
else
{
schedule_delayed_work(&chip->heartbeat_work,
msecs_to_jiffies(0));
}
mmi_info(chip, "QPNP SMB MMI probed successfully!\n");
return rc;
}
static void smb_mmi_shutdown(struct platform_device *pdev)
{
struct smb_mmi_charger *chip = platform_get_drvdata(pdev);
if (chip->qcom_psy)
power_supply_put(chip->qcom_psy);
if (chip->bms_psy)
power_supply_put(chip->bms_psy);
if (chip->usb_psy)
power_supply_put(chip->usb_psy);
if (chip->pc_port_psy)
power_supply_put(chip->pc_port_psy);
if (chip->dc_psy)
power_supply_put(chip->dc_psy);
if (chip->max_main_psy)
power_supply_put(chip->max_main_psy);
if (chip->max_flip_psy)
power_supply_put(chip->max_flip_psy);
return;
}
#ifdef CONFIG_PM_SLEEP
static int smb_mmi_suspend(struct device *device)
{
struct platform_device *pdev = to_platform_device(device);
struct smb_mmi_charger *chip = platform_get_drvdata(pdev);
chip->suspended &= ~WAS_SUSPENDED;
chip->suspended |= IS_SUSPENDED;
return 0;
}
static int smb_mmi_resume(struct device *device)
{
struct platform_device *pdev = to_platform_device(device);
struct smb_mmi_charger *chip = platform_get_drvdata(pdev);
chip->suspended &= ~IS_SUSPENDED;
chip->suspended |= WAS_SUSPENDED;
return 0;
}
#else
#define smb_mmi_suspend NULL
#define smb_mmi_resume NULL
#endif
static const struct dev_pm_ops smb_mmi_dev_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(smb_mmi_suspend, smb_mmi_resume)
};
static const struct of_device_id match_table[] = {
{
.compatible = "qcom,mmi-pm8150-smb5",
.data = (void *)PM8150B,
},
{
.compatible = "qcom,mmi-pm7250b-smb5",
.data = (void *)PM7250B,
},
{
.compatible = "qcom,mmi-pm6150-smb5",
.data = (void *)PM6150,
},
{
.compatible = "qcom,mmi-pmi632-smb5",
.data = (void *)PMI632,
},
{ },
};
static struct platform_driver smb_mmi_driver = {
.driver = {
.name = "qcom,mmi-smbcharger-iio",
.owner = THIS_MODULE,
.pm = &smb_mmi_dev_pm_ops,
.of_match_table = match_table,
},
.probe = smb_mmi_probe,
.shutdown = smb_mmi_shutdown,
};
module_platform_driver(smb_mmi_driver);
MODULE_DESCRIPTION("QPNP SMB MMI Charger Driver");
MODULE_LICENSE("GPL v2");