In P, run_cmd(poweroff_cmd) can work to trigger /sbin/poweroff. Now in R, it can not work but still return success. We need to use kernel_power_off() to trigger power off for factory kill. The change is referring to (CR). Change-Id: Ia18faee3138259c7ee35946313e9b0dac75dd244 Signed-off-by: xieqiong2 <xieqiong2@lenovo.com> Reviewed-on: https://gerrit.mot.com/1763914 SLTApproved: Slta Waiver SME-Granted: SME Approvals Granted Tested-by: Jira Key Reviewed-by: Xiangpo Zhao <zhaoxp3@motorola.com> Submit-Approved: Jira Key
12146 lines
306 KiB
C
12146 lines
306 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright (c) 2018-2021 The Linux Foundation. All rights reserved.
|
|
*/
|
|
|
|
#include <linux/device.h>
|
|
#include <linux/regmap.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/of_gpio.h>
|
|
#include <linux/module.h>
|
|
#include <linux/power_supply.h>
|
|
#include <linux/regulator/driver.h>
|
|
#include <linux/qpnp/qpnp-revid.h>
|
|
#include <linux/ipc_logging.h>
|
|
#include <linux/irq.h>
|
|
#include <linux/iio/consumer.h>
|
|
#include <linux/pmic-voter.h>
|
|
#include <linux/of_batterydata.h>
|
|
#include <linux/ktime.h>
|
|
#include <linux/usb/usbpd.h>
|
|
#include "smb5-lib.h"
|
|
#include "smb5-reg.h"
|
|
#include "schgm-flash.h"
|
|
#include "step-chg-jeita.h"
|
|
#include "storm-watch.h"
|
|
#include "schgm-flash.h"
|
|
|
|
#ifdef QCOM_BASE
|
|
#define smblib_err(chg, fmt, ...) \
|
|
pr_err("%s: %s: " fmt, chg->name, \
|
|
__func__, ##__VA_ARGS__) \
|
|
|
|
#define smblib_dbg(chg, reason, fmt, ...) \
|
|
do { \
|
|
if (*chg->debug_mask & (reason)) \
|
|
pr_info("%s: %s: " fmt, chg->name, \
|
|
__func__, ##__VA_ARGS__); \
|
|
else \
|
|
pr_debug("%s: %s: " fmt, chg->name, \
|
|
__func__, ##__VA_ARGS__); \
|
|
} while (0)
|
|
#else
|
|
#define smblib_err(chg, fmt, ...) \
|
|
do { \
|
|
pr_err("%s: %s: " fmt, chg->name, \
|
|
__func__, ##__VA_ARGS__); \
|
|
ipc_log_string(chg->ipc_log, \
|
|
"ERR:%s: " fmt, __func__, ##__VA_ARGS__); \
|
|
} while (0)
|
|
|
|
#define smblib_dbg(chg, reason, fmt, ...) \
|
|
do { \
|
|
if (*chg->debug_mask & (reason)) \
|
|
pr_info("%s: %s: " fmt, chg->name, \
|
|
__func__, ##__VA_ARGS__); \
|
|
else \
|
|
pr_debug("%s: %s: " fmt, chg->name, \
|
|
__func__, ##__VA_ARGS__); \
|
|
if ((PR_REGISTER) & (reason)) \
|
|
ipc_log_string(chg->ipc_log_reg, \
|
|
"REG:%s: " fmt, __func__, ##__VA_ARGS__); \
|
|
else \
|
|
ipc_log_string(chg->ipc_log, \
|
|
"INFO:%s: " fmt, __func__, ##__VA_ARGS__); \
|
|
} while (0)
|
|
#endif
|
|
|
|
#define QPNP_LOG_PAGES (50)
|
|
|
|
#define EB_RCV_NEVER BIT(7)
|
|
#define EB_RCV_PARALLEL BIT(4)
|
|
#define EB_SND_EXT BIT(2)
|
|
#define EB_SND_LOW BIT(1)
|
|
#define EB_SND_NEVER BIT(0)
|
|
static void mmi_check_extbat_ability(struct smb_charger *chip, char *able);
|
|
|
|
#define typec_rp_med_high(chg, typec_mode) \
|
|
((typec_mode == POWER_SUPPLY_TYPEC_SOURCE_MEDIUM \
|
|
|| typec_mode == POWER_SUPPLY_TYPEC_SOURCE_HIGH) \
|
|
&& (!chg->typec_legacy || chg->typec_legacy_use_rp_icl))
|
|
|
|
#ifdef QCOM_BASE
|
|
static void update_sw_icl_max(struct smb_charger *chg, int pst);
|
|
#endif
|
|
static int smblib_get_prop_typec_mode(struct smb_charger *chg);
|
|
|
|
int smblib_read(struct smb_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_batch_read(struct smb_charger *chg, u16 addr, u8 *val,
|
|
int count)
|
|
{
|
|
return regmap_bulk_read(chg->regmap, addr, val, count);
|
|
}
|
|
|
|
int smblib_write(struct smb_charger *chg, u16 addr, u8 val)
|
|
{
|
|
return regmap_write(chg->regmap, addr, val);
|
|
}
|
|
|
|
int smblib_batch_write(struct smb_charger *chg, u16 addr, u8 *val,
|
|
int count)
|
|
{
|
|
return regmap_bulk_write(chg->regmap, addr, val, count);
|
|
}
|
|
|
|
int smblib_masked_write(struct smb_charger *chg, u16 addr, u8 mask, u8 val)
|
|
{
|
|
return regmap_update_bits(chg->regmap, addr, mask, val);
|
|
}
|
|
|
|
int smblib_get_iio_channel(struct smb_charger *chg, const char *propname,
|
|
struct iio_channel **chan)
|
|
{
|
|
int rc = 0;
|
|
|
|
rc = of_property_match_string(chg->dev->of_node,
|
|
"io-channel-names", propname);
|
|
if (rc < 0)
|
|
return 0;
|
|
|
|
*chan = iio_channel_get(chg->dev, propname);
|
|
if (IS_ERR(*chan)) {
|
|
rc = PTR_ERR(*chan);
|
|
if (rc != -EPROBE_DEFER)
|
|
smblib_err(chg, "%s channel unavailable, %d\n",
|
|
propname, rc);
|
|
*chan = NULL;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
#define DIV_FACTOR_MICRO_V_I 1
|
|
#define DIV_FACTOR_MILI_V_I 1000
|
|
#define DIV_FACTOR_DECIDEGC 100
|
|
int smblib_read_iio_channel(struct smb_charger *chg, struct iio_channel *chan,
|
|
int div, int *data)
|
|
{
|
|
int rc = 0;
|
|
*data = -ENODATA;
|
|
|
|
if (chan) {
|
|
rc = iio_read_channel_processed(chan, data);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Error in reading IIO channel data, rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
if (div != 0)
|
|
*data /= div;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
int smblib_get_jeita_cc_delta(struct smb_charger *chg, int *cc_delta_ua)
|
|
{
|
|
int rc, cc_minus_ua;
|
|
u8 stat;
|
|
|
|
rc = smblib_read(chg, BATTERY_CHARGER_STATUS_7_REG, &stat);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't read BATTERY_CHARGER_STATUS_2 rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
if (stat & BAT_TEMP_STATUS_HOT_SOFT_BIT) {
|
|
rc = smblib_get_charge_param(chg, &chg->param.jeita_cc_comp_hot,
|
|
&cc_minus_ua);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't get jeita cc minus rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
} else if (stat & BAT_TEMP_STATUS_COLD_SOFT_BIT) {
|
|
rc = smblib_get_charge_param(chg,
|
|
&chg->param.jeita_cc_comp_cold,
|
|
&cc_minus_ua);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't get jeita cc minus rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
} else {
|
|
cc_minus_ua = 0;
|
|
}
|
|
|
|
*cc_delta_ua = -cc_minus_ua;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int smblib_icl_override(struct smb_charger *chg, enum icl_override_mode mode)
|
|
{
|
|
int rc;
|
|
u8 usb51_mode, icl_override, apsd_override;
|
|
|
|
switch (mode) {
|
|
case SW_OVERRIDE_USB51_MODE:
|
|
usb51_mode = 0;
|
|
icl_override = ICL_OVERRIDE_BIT;
|
|
apsd_override = 0;
|
|
break;
|
|
case SW_OVERRIDE_HC_MODE:
|
|
usb51_mode = USBIN_MODE_CHG_BIT;
|
|
icl_override = 0;
|
|
apsd_override = ICL_OVERRIDE_AFTER_APSD_BIT;
|
|
break;
|
|
case HW_AUTO_MODE:
|
|
default:
|
|
usb51_mode = USBIN_MODE_CHG_BIT;
|
|
icl_override = 0;
|
|
apsd_override = 0;
|
|
break;
|
|
}
|
|
|
|
if (chg->mmi.factory_mode)
|
|
apsd_override = ICL_OVERRIDE_AFTER_APSD_BIT;
|
|
|
|
rc = smblib_masked_write(chg, USBIN_ICL_OPTIONS_REG,
|
|
USBIN_MODE_CHG_BIT, usb51_mode);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't set USBIN_ICL_OPTIONS rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
rc = smblib_masked_write(chg, CMD_ICL_OVERRIDE_REG,
|
|
ICL_OVERRIDE_BIT, icl_override);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't override ICL rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
rc = smblib_masked_write(chg, USBIN_LOAD_CFG_REG,
|
|
ICL_OVERRIDE_AFTER_APSD_BIT, apsd_override);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't override ICL_AFTER_APSD rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
* This function does smb_en pin access, which is lock protected.
|
|
* It should be called with smb_lock held.
|
|
*/
|
|
static int smblib_select_sec_charger_locked(struct smb_charger *chg,
|
|
int sec_chg)
|
|
{
|
|
int rc = 0;
|
|
|
|
switch (sec_chg) {
|
|
case POWER_SUPPLY_CHARGER_SEC_CP:
|
|
vote(chg->pl_disable_votable, PL_SMB_EN_VOTER, true, 0);
|
|
|
|
/* select Charge Pump instead of slave charger */
|
|
rc = smblib_masked_write(chg, MISC_SMB_CFG_REG,
|
|
SMB_EN_SEL_BIT, SMB_EN_SEL_BIT);
|
|
if (rc < 0) {
|
|
dev_err(chg->dev, "Couldn't select SMB charger rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
/* Enable Charge Pump, under HW control */
|
|
rc = smblib_masked_write(chg, MISC_SMB_EN_CMD_REG,
|
|
EN_CP_CMD_BIT, EN_CP_CMD_BIT);
|
|
if (rc < 0) {
|
|
dev_err(chg->dev, "Couldn't enable SMB charger rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
vote(chg->smb_override_votable, PL_SMB_EN_VOTER, false, 0);
|
|
break;
|
|
case POWER_SUPPLY_CHARGER_SEC_PL:
|
|
/* select slave charger instead of Charge Pump */
|
|
rc = smblib_masked_write(chg, MISC_SMB_CFG_REG,
|
|
SMB_EN_SEL_BIT, 0);
|
|
if (rc < 0) {
|
|
dev_err(chg->dev, "Couldn't select SMB charger rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
/* Enable slave charger, under HW control */
|
|
rc = smblib_masked_write(chg, MISC_SMB_EN_CMD_REG,
|
|
EN_STAT_CMD_BIT, EN_STAT_CMD_BIT);
|
|
if (rc < 0) {
|
|
dev_err(chg->dev, "Couldn't enable SMB charger rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
vote(chg->smb_override_votable, PL_SMB_EN_VOTER, false, 0);
|
|
|
|
vote(chg->pl_disable_votable, PL_SMB_EN_VOTER, false, 0);
|
|
|
|
break;
|
|
case POWER_SUPPLY_CHARGER_SEC_NONE:
|
|
default:
|
|
vote(chg->pl_disable_votable, PL_SMB_EN_VOTER, true, 0);
|
|
|
|
/* SW override, disabling secondary charger(s) */
|
|
vote(chg->smb_override_votable, PL_SMB_EN_VOTER, true, 0);
|
|
break;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int smblib_select_sec_charger(struct smb_charger *chg, int sec_chg,
|
|
int reason, bool toggle)
|
|
{
|
|
int rc;
|
|
|
|
mutex_lock(&chg->smb_lock);
|
|
|
|
if (toggle && sec_chg == POWER_SUPPLY_CHARGER_SEC_CP) {
|
|
rc = smblib_select_sec_charger_locked(chg,
|
|
POWER_SUPPLY_CHARGER_SEC_NONE);
|
|
if (rc < 0) {
|
|
dev_err(chg->dev, "Couldn't disable secondary charger rc=%d\n",
|
|
rc);
|
|
goto unlock_out;
|
|
}
|
|
|
|
/*
|
|
* A minimum of 20us delay is expected before switching on STAT
|
|
* pin.
|
|
*/
|
|
usleep_range(20, 30);
|
|
}
|
|
|
|
rc = smblib_select_sec_charger_locked(chg, sec_chg);
|
|
if (rc < 0) {
|
|
dev_err(chg->dev, "Couldn't switch secondary charger rc=%d\n",
|
|
rc);
|
|
goto unlock_out;
|
|
}
|
|
|
|
chg->sec_chg_selected = sec_chg;
|
|
chg->cp_reason = reason;
|
|
|
|
unlock_out:
|
|
mutex_unlock(&chg->smb_lock);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static void smblib_notify_extcon_props(struct smb_charger *chg, int id)
|
|
{
|
|
union extcon_property_value val;
|
|
union power_supply_propval prop_val;
|
|
|
|
if (chg->connector_type == POWER_SUPPLY_CONNECTOR_TYPEC) {
|
|
smblib_get_prop_typec_cc_orientation(chg, &prop_val);
|
|
val.intval = ((prop_val.intval == 2) ? 1 : 0);
|
|
extcon_set_property(chg->extcon, id,
|
|
EXTCON_PROP_USB_TYPEC_POLARITY, val);
|
|
val.intval = true;
|
|
extcon_set_property(chg->extcon, id,
|
|
EXTCON_PROP_USB_SS, val);
|
|
} else if (chg->connector_type == POWER_SUPPLY_CONNECTOR_MICRO_USB) {
|
|
val.intval = false;
|
|
extcon_set_property(chg->extcon, id,
|
|
EXTCON_PROP_USB_SS, val);
|
|
}
|
|
|
|
#ifdef CONFIG_MODS_NEW_SW_ARCH
|
|
extcon_set_cable_state_(chg->extcon, EXTCON_USB_CC,
|
|
(val.intval == 2) ? 1 : 0);
|
|
extcon_set_cable_state_(chg->extcon, EXTCON_USB_SPEED, true);
|
|
#endif
|
|
}
|
|
|
|
static void smblib_notify_device_mode(struct smb_charger *chg, bool enable)
|
|
{
|
|
if (enable)
|
|
smblib_notify_extcon_props(chg, EXTCON_USB);
|
|
|
|
extcon_set_state_sync(chg->extcon, EXTCON_USB, enable);
|
|
|
|
if (chg->dr_inst)
|
|
dual_role_instance_changed(chg->dr_inst);
|
|
}
|
|
|
|
static void smblib_notify_usb_host(struct smb_charger *chg, bool enable)
|
|
{
|
|
int rc = 0;
|
|
|
|
if (enable) {
|
|
smblib_dbg(chg, PR_OTG, "enabling VBUS in OTG mode\n");
|
|
rc = smblib_masked_write(chg, DCDC_CMD_OTG_REG,
|
|
OTG_EN_BIT, OTG_EN_BIT);
|
|
if (rc < 0) {
|
|
smblib_err(chg,
|
|
"Couldn't enable VBUS in OTG mode rc=%d\n", rc);
|
|
return;
|
|
}
|
|
|
|
smblib_notify_extcon_props(chg, EXTCON_USB_HOST);
|
|
} else {
|
|
smblib_dbg(chg, PR_OTG, "disabling VBUS in OTG mode\n");
|
|
rc = smblib_masked_write(chg, DCDC_CMD_OTG_REG,
|
|
OTG_EN_BIT, 0);
|
|
if (rc < 0) {
|
|
smblib_err(chg,
|
|
"Couldn't disable VBUS in OTG mode rc=%d\n",
|
|
rc);
|
|
return;
|
|
}
|
|
}
|
|
|
|
extcon_set_state_sync(chg->extcon, EXTCON_USB_HOST, enable);
|
|
|
|
if (chg->dr_inst)
|
|
dual_role_instance_changed(chg->dr_inst);
|
|
}
|
|
|
|
/********************
|
|
* REGISTER GETTERS *
|
|
********************/
|
|
|
|
int smblib_get_charge_param(struct smb_charger *chg,
|
|
struct smb_chg_param *param, int *val_u)
|
|
{
|
|
int rc = 0;
|
|
u8 val_raw;
|
|
|
|
rc = smblib_read(chg, param->reg, &val_raw);
|
|
if (rc < 0) {
|
|
smblib_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;
|
|
smblib_dbg(chg, PR_REGISTER, "%s = %d (0x%02x)\n",
|
|
param->name, *val_u, val_raw);
|
|
|
|
return rc;
|
|
}
|
|
|
|
int smblib_get_usb_suspend(struct smb_charger *chg, int *suspend)
|
|
{
|
|
int rc = 0;
|
|
u8 temp;
|
|
|
|
rc = smblib_read(chg, USBIN_CMD_IL_REG, &temp);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't read USBIN_CMD_IL rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
*suspend = temp & USBIN_SUSPEND_BIT;
|
|
|
|
return rc;
|
|
}
|
|
|
|
|
|
static const s16 therm_lookup_table[] = {
|
|
/* Index -30C~85C, ADC raw code */
|
|
0x6C92, 0x6C43, 0x6BF0, 0x6B98, 0x6B3A, 0x6AD8, 0x6A70, 0x6A03,
|
|
0x6990, 0x6916, 0x6897, 0x6811, 0x6785, 0x66F2, 0x6658, 0x65B7,
|
|
0x650F, 0x6460, 0x63AA, 0x62EC, 0x6226, 0x6159, 0x6084, 0x5FA8,
|
|
0x5EC3, 0x5DD8, 0x5CE4, 0x5BE9, 0x5AE7, 0x59DD, 0x58CD, 0x57B5,
|
|
0x5696, 0x5571, 0x5446, 0x5314, 0x51DD, 0x50A0, 0x4F5E, 0x4E17,
|
|
0x4CCC, 0x4B7D, 0x4A2A, 0x48D4, 0x477C, 0x4621, 0x44C4, 0x4365,
|
|
0x4206, 0x40A6, 0x3F45, 0x3DE6, 0x3C86, 0x3B28, 0x39CC, 0x3872,
|
|
0x3719, 0x35C4, 0x3471, 0x3322, 0x31D7, 0x308F, 0x2F4C, 0x2E0D,
|
|
0x2CD3, 0x2B9E, 0x2A6E, 0x2943, 0x281D, 0x26FE, 0x25E3, 0x24CF,
|
|
0x23C0, 0x22B8, 0x21B5, 0x20B8, 0x1FC2, 0x1ED1, 0x1DE6, 0x1D01,
|
|
0x1C22, 0x1B49, 0x1A75, 0x19A8, 0x18E0, 0x181D, 0x1761, 0x16A9,
|
|
0x15F7, 0x154A, 0x14A2, 0x13FF, 0x1361, 0x12C8, 0x1234, 0x11A4,
|
|
0x1119, 0x1091, 0x100F, 0x0F90, 0x0F15, 0x0E9E, 0x0E2B, 0x0DBC,
|
|
0x0D50, 0x0CE8, 0x0C83, 0x0C21, 0x0BC3, 0x0B67, 0x0B0F, 0x0AB9,
|
|
0x0A66, 0x0A16, 0x09C9, 0x097E,
|
|
};
|
|
|
|
int smblib_get_thermal_threshold(struct smb_charger *chg, u16 addr, int *val)
|
|
{
|
|
u8 buff[2];
|
|
s16 temp;
|
|
int rc = 0;
|
|
int i, lower, upper;
|
|
|
|
rc = smblib_batch_read(chg, addr, buff, 2);
|
|
if (rc < 0) {
|
|
pr_err("failed to write to 0x%04X, rc=%d\n", addr, rc);
|
|
return rc;
|
|
}
|
|
|
|
temp = buff[1] | buff[0] << 8;
|
|
|
|
lower = 0;
|
|
upper = ARRAY_SIZE(therm_lookup_table) - 1;
|
|
while (lower <= upper) {
|
|
i = (upper + lower) / 2;
|
|
if (therm_lookup_table[i] < temp)
|
|
upper = i - 1;
|
|
else if (therm_lookup_table[i] > temp)
|
|
lower = i + 1;
|
|
else
|
|
break;
|
|
}
|
|
|
|
/* index 0 corresonds to -30C */
|
|
*val = (i - 30) * 10;
|
|
|
|
return rc;
|
|
}
|
|
|
|
struct apsd_result {
|
|
const char * const name;
|
|
const u8 bit;
|
|
const enum power_supply_type pst;
|
|
};
|
|
|
|
enum {
|
|
UNKNOWN,
|
|
SDP,
|
|
CDP,
|
|
DCP,
|
|
OCP,
|
|
FLOAT,
|
|
HVDCP2,
|
|
HVDCP3,
|
|
MAX_TYPES
|
|
};
|
|
|
|
static const struct apsd_result smblib_apsd_results[] = {
|
|
[UNKNOWN] = {
|
|
.name = "UNKNOWN",
|
|
.bit = 0,
|
|
.pst = POWER_SUPPLY_TYPE_UNKNOWN
|
|
},
|
|
[SDP] = {
|
|
.name = "SDP",
|
|
.bit = SDP_CHARGER_BIT,
|
|
.pst = POWER_SUPPLY_TYPE_USB
|
|
},
|
|
[CDP] = {
|
|
.name = "CDP",
|
|
.bit = CDP_CHARGER_BIT,
|
|
.pst = POWER_SUPPLY_TYPE_USB_CDP
|
|
},
|
|
[DCP] = {
|
|
.name = "DCP",
|
|
.bit = DCP_CHARGER_BIT,
|
|
.pst = POWER_SUPPLY_TYPE_USB_DCP
|
|
},
|
|
[OCP] = {
|
|
.name = "OCP",
|
|
.bit = OCP_CHARGER_BIT,
|
|
.pst = POWER_SUPPLY_TYPE_USB_DCP
|
|
},
|
|
[FLOAT] = {
|
|
.name = "FLOAT",
|
|
.bit = FLOAT_CHARGER_BIT,
|
|
.pst = POWER_SUPPLY_TYPE_USB_FLOAT
|
|
},
|
|
[HVDCP2] = {
|
|
.name = "HVDCP2",
|
|
.bit = DCP_CHARGER_BIT | QC_2P0_BIT,
|
|
.pst = POWER_SUPPLY_TYPE_USB_DCP
|
|
},
|
|
[HVDCP3] = {
|
|
.name = "HVDCP3",
|
|
.bit = DCP_CHARGER_BIT | QC_3P0_BIT,
|
|
.pst = POWER_SUPPLY_TYPE_USB_DCP,
|
|
},
|
|
};
|
|
|
|
static const struct apsd_result *smblib_get_apsd_result(struct smb_charger *chg)
|
|
{
|
|
int rc, i;
|
|
u8 apsd_stat, stat;
|
|
const struct apsd_result *result = &smblib_apsd_results[UNKNOWN];
|
|
|
|
rc = smblib_read(chg, APSD_STATUS_REG, &apsd_stat);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't read APSD_STATUS rc=%d\n", rc);
|
|
return result;
|
|
}
|
|
smblib_dbg(chg, PR_REGISTER, "APSD_STATUS = 0x%02x\n", apsd_stat);
|
|
|
|
if (!(apsd_stat & APSD_DTC_STATUS_DONE_BIT))
|
|
return result;
|
|
|
|
rc = smblib_read(chg, APSD_RESULT_STATUS_REG, &stat);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't read APSD_RESULT_STATUS rc=%d\n",
|
|
rc);
|
|
return result;
|
|
}
|
|
stat &= APSD_RESULT_STATUS_MASK;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(smblib_apsd_results); i++) {
|
|
if (smblib_apsd_results[i].bit == stat)
|
|
result = &smblib_apsd_results[i];
|
|
}
|
|
|
|
if (apsd_stat & QC_CHARGER_BIT) {
|
|
/* since its a qc_charger, either return HVDCP3 or HVDCP2 */
|
|
if (result != &smblib_apsd_results[HVDCP3])
|
|
result = &smblib_apsd_results[HVDCP2];
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
#define INPUT_NOT_PRESENT 0
|
|
#define INPUT_PRESENT_USB BIT(1)
|
|
#define INPUT_PRESENT_DC BIT(2)
|
|
static int smblib_is_input_present(struct smb_charger *chg,
|
|
int *present)
|
|
{
|
|
int rc;
|
|
union power_supply_propval pval = {0, };
|
|
|
|
*present = INPUT_NOT_PRESENT;
|
|
|
|
rc = smblib_get_prop_usb_present(chg, &pval);
|
|
if (rc < 0) {
|
|
pr_err("Couldn't get usb presence status rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
*present |= pval.intval ? INPUT_PRESENT_USB : INPUT_NOT_PRESENT;
|
|
|
|
rc = smblib_get_prop_dc_present(chg, &pval);
|
|
if (rc < 0) {
|
|
pr_err("Couldn't get dc presence status rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
*present |= pval.intval ? INPUT_PRESENT_DC : INPUT_NOT_PRESENT;
|
|
|
|
return 0;
|
|
}
|
|
|
|
#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_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);
|
|
}
|
|
|
|
/********************
|
|
* REGISTER SETTERS *
|
|
********************/
|
|
static const struct 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_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;
|
|
}
|
|
|
|
int smblib_set_opt_switcher_freq(struct smb_charger *chg, int fsw_khz)
|
|
{
|
|
union power_supply_propval pval = {0, };
|
|
int rc = 0;
|
|
|
|
rc = smblib_set_charge_param(chg, &chg->param.freq_switcher, fsw_khz);
|
|
if (rc < 0)
|
|
dev_err(chg->dev, "Error in setting freq_buck rc=%d\n", rc);
|
|
|
|
if (chg->mode == PARALLEL_MASTER && chg->pl.psy) {
|
|
pval.intval = fsw_khz;
|
|
/*
|
|
* Some parallel charging implementations may not have
|
|
* PROP_BUCK_FREQ property - they could be running
|
|
* with a fixed frequency
|
|
*/
|
|
power_supply_set_property(chg->pl.psy,
|
|
POWER_SUPPLY_PROP_BUCK_FREQ, &pval);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
int smblib_set_charge_param(struct smb_charger *chg,
|
|
struct smb_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)
|
|
smblib_dbg(chg, PR_MISC,
|
|
"%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(chg, param->reg, val_raw);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "%s: Couldn't write 0x%02x to 0x%04x rc=%d\n",
|
|
param->name, val_raw, param->reg, rc);
|
|
return rc;
|
|
}
|
|
|
|
smblib_dbg(chg, PR_REGISTER, "%s = %d (0x%02x)\n",
|
|
param->name, val_u, val_raw);
|
|
|
|
return rc;
|
|
}
|
|
|
|
int smblib_set_usb_suspend(struct smb_charger *chg, bool suspend)
|
|
{
|
|
int rc = 0;
|
|
if (suspend)
|
|
vote(chg->icl_irq_disable_votable, USB_SUSPEND_VOTER,
|
|
true, 0);
|
|
|
|
smblib_dbg(chg, PR_MISC, "%s\n", suspend ? "suspend" : "resume");
|
|
rc = smblib_masked_write(chg, USBIN_CMD_IL_REG, USBIN_SUSPEND_BIT,
|
|
suspend ? USBIN_SUSPEND_BIT : 0);
|
|
if (rc < 0)
|
|
smblib_err(chg, "Couldn't write %s to USBIN_SUSPEND_BIT rc=%d\n",
|
|
suspend ? "suspend" : "resume", rc);
|
|
|
|
if (!suspend)
|
|
vote(chg->icl_irq_disable_votable, USB_SUSPEND_VOTER,
|
|
false, 0);
|
|
|
|
return rc;
|
|
}
|
|
|
|
int smblib_set_dc_suspend(struct smb_charger *chg, bool suspend)
|
|
{
|
|
int rc = 0;
|
|
|
|
rc = smblib_masked_write(chg, DCIN_CMD_IL_REG, DCIN_SUSPEND_BIT,
|
|
suspend ? DCIN_SUSPEND_BIT : 0);
|
|
if (rc < 0)
|
|
smblib_err(chg, "Couldn't write %s to DCIN_SUSPEND_BIT rc=%d\n",
|
|
suspend ? "suspend" : "resume", rc);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int smblib_set_adapter_allowance(struct smb_charger *chg,
|
|
u8 allowed_voltage)
|
|
{
|
|
int rc = 0;
|
|
|
|
/* PMI632 only support max. 9V */
|
|
if (chg->chg_param.smb_version == PMI632_SUBTYPE) {
|
|
switch (allowed_voltage) {
|
|
case USBIN_ADAPTER_ALLOW_12V:
|
|
case USBIN_ADAPTER_ALLOW_9V_TO_12V:
|
|
allowed_voltage = USBIN_ADAPTER_ALLOW_9V;
|
|
break;
|
|
case USBIN_ADAPTER_ALLOW_5V_OR_12V:
|
|
case USBIN_ADAPTER_ALLOW_5V_OR_9V_TO_12V:
|
|
allowed_voltage = USBIN_ADAPTER_ALLOW_5V_OR_9V;
|
|
break;
|
|
case USBIN_ADAPTER_ALLOW_5V_TO_12V:
|
|
allowed_voltage = USBIN_ADAPTER_ALLOW_5V_TO_9V;
|
|
break;
|
|
}
|
|
}
|
|
|
|
rc = smblib_write(chg, USBIN_ADAPTER_ALLOW_CFG_REG, allowed_voltage);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't write 0x%02x to USBIN_ADAPTER_ALLOW_CFG rc=%d\n",
|
|
allowed_voltage, rc);
|
|
return rc;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int smblib_usb_pd_adapter_allowance_override(struct smb_charger *chg,
|
|
u8 allowed_voltage)
|
|
{
|
|
int rc = 0;
|
|
|
|
if (chg->chg_param.smb_version == PMI632_SUBTYPE)
|
|
return 0;
|
|
|
|
rc = smblib_write(chg, USBIN_ADAPTER_ALLOW_OVERRIDE_REG,
|
|
allowed_voltage);
|
|
if (rc < 0)
|
|
smblib_err(chg, "Couldn't write 0x%02x to USBIN_ADAPTER_ALLOW_OVERRIDE_REG rc=%d\n",
|
|
allowed_voltage, rc);
|
|
|
|
smblib_dbg(chg, PR_MISC, "set USBIN_ALLOW_OVERRIDE: %d\n",
|
|
allowed_voltage);
|
|
return rc;
|
|
}
|
|
|
|
#define MICRO_5V 5000000
|
|
#define MICRO_9V 9000000
|
|
#define MICRO_12V 12000000
|
|
static int smblib_set_usb_pd_fsw(struct smb_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 {
|
|
smblib_err(chg, "Couldn't set Fsw: invalid voltage %d\n",
|
|
voltage);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
#define CONT_AICL_HEADROOM_MV 1000
|
|
#define AICL_THRESHOLD_MV_IN_CC 5000
|
|
static int smblib_set_usb_pd_allowed_voltage(struct smb_charger *chg,
|
|
int min_allowed_uv, int max_allowed_uv)
|
|
{
|
|
int rc;
|
|
#ifdef QCOM_BASE
|
|
int rc, aicl_threshold;
|
|
u8 vbus_allowance;
|
|
|
|
if (chg->chg_param.smb_version == PMI632_SUBTYPE)
|
|
return 0;
|
|
|
|
if (chg->pd_active == POWER_SUPPLY_PD_PPS_ACTIVE) {
|
|
vbus_allowance = CONTINUOUS;
|
|
} else if (min_allowed_uv == MICRO_5V && max_allowed_uv == MICRO_5V) {
|
|
vbus_allowance = FORCE_5V;
|
|
} else if (min_allowed_uv == MICRO_9V && max_allowed_uv == MICRO_9V) {
|
|
vbus_allowance = FORCE_9V;
|
|
} else if (min_allowed_uv == MICRO_12V && max_allowed_uv == MICRO_12V) {
|
|
vbus_allowance = FORCE_12V;
|
|
} else if (min_allowed_uv < MICRO_12V && max_allowed_uv <= MICRO_12V) {
|
|
vbus_allowance = CONTINUOUS;
|
|
} else {
|
|
smblib_err(chg, "invalid allowed voltage [%d, %d]\n",
|
|
min_allowed_uv, max_allowed_uv);
|
|
return -EINVAL;
|
|
}
|
|
|
|
rc = smblib_usb_pd_adapter_allowance_override(chg, vbus_allowance);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "set CONTINUOUS allowance failed, rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
if (vbus_allowance != CONTINUOUS)
|
|
return 0;
|
|
|
|
aicl_threshold = min_allowed_uv / 1000 - CONT_AICL_HEADROOM_MV;
|
|
if (chg->adapter_cc_mode)
|
|
aicl_threshold = min(aicl_threshold, AICL_THRESHOLD_MV_IN_CC);
|
|
|
|
rc = smblib_set_charge_param(chg, &chg->param.aicl_cont_threshold,
|
|
aicl_threshold);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "set CONT_AICL_THRESHOLD failed, rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
#else
|
|
if (chg->pd_active && (chg->pd_contract_uv <= 0)) {
|
|
cancel_delayed_work(&chg->pd_contract_work);
|
|
schedule_delayed_work(&chg->pd_contract_work,
|
|
msecs_to_jiffies(0));
|
|
}
|
|
#endif
|
|
return rc;
|
|
}
|
|
|
|
int smblib_set_aicl_cont_threshold(struct smb_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;
|
|
}
|
|
|
|
/********************
|
|
* HELPER FUNCTIONS *
|
|
********************/
|
|
static int is_extrn_fg_present(struct smb_charger *chip)
|
|
{
|
|
int ret;
|
|
struct power_supply *extrn_fg_psy;
|
|
|
|
if (!chip->mmi.extrn_fg_psy_name)
|
|
return -ENODEV;
|
|
|
|
extrn_fg_psy =
|
|
power_supply_get_by_name((char *)chip->mmi.extrn_fg_psy_name);
|
|
if (!extrn_fg_psy || !extrn_fg_psy->desc ||
|
|
!extrn_fg_psy->desc->get_property) {
|
|
ret = false;
|
|
} else {
|
|
ret = true;
|
|
power_supply_put(extrn_fg_psy);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int smblib_get_prop_from_extrn_bms(struct smb_charger *chg,
|
|
enum power_supply_property psp,
|
|
union power_supply_propval *val)
|
|
{
|
|
int rc;
|
|
struct power_supply *extrn_fg_psy;
|
|
|
|
if (!chg->mmi.extrn_fg_psy_name)
|
|
return -ENODEV;
|
|
|
|
extrn_fg_psy =
|
|
power_supply_get_by_name((char *)chg->mmi.extrn_fg_psy_name);
|
|
if (!extrn_fg_psy || !extrn_fg_psy->desc ||
|
|
!extrn_fg_psy->desc->get_property)
|
|
return -ENODEV;
|
|
|
|
switch(psp) {
|
|
case POWER_SUPPLY_PROP_CAPACITY:
|
|
case POWER_SUPPLY_PROP_CURRENT_NOW:
|
|
case POWER_SUPPLY_PROP_TEMP:
|
|
rc = power_supply_get_property(extrn_fg_psy, psp, val);
|
|
break;
|
|
default:
|
|
rc = -EINVAL;
|
|
break;
|
|
}
|
|
|
|
power_supply_put(extrn_fg_psy);
|
|
return rc;
|
|
}
|
|
static bool is_cp_available(struct smb_charger *chg)
|
|
{
|
|
if (!chg->cp_psy)
|
|
chg->cp_psy = power_supply_get_by_name("charge_pump_master");
|
|
|
|
return !!chg->cp_psy;
|
|
}
|
|
|
|
static bool is_cp_topo_vbatt(struct smb_charger *chg)
|
|
{
|
|
int rc;
|
|
bool is_vbatt;
|
|
union power_supply_propval pval;
|
|
|
|
if (!is_cp_available(chg))
|
|
return false;
|
|
|
|
rc = power_supply_get_property(chg->cp_psy,
|
|
POWER_SUPPLY_PROP_PARALLEL_OUTPUT_MODE, &pval);
|
|
if (rc < 0)
|
|
return false;
|
|
|
|
is_vbatt = (pval.intval == POWER_SUPPLY_PL_OUTPUT_VBAT);
|
|
|
|
smblib_dbg(chg, PR_WLS, "%s\n", is_vbatt ? "true" : "false");
|
|
|
|
return is_vbatt;
|
|
}
|
|
|
|
#define CP_TO_MAIN_ICL_OFFSET_PC 10
|
|
int smblib_get_qc3_main_icl_offset(struct smb_charger *chg, int *offset_ua)
|
|
{
|
|
union power_supply_propval pval = {0, };
|
|
int rc;
|
|
|
|
/*
|
|
* Apply ILIM offset to main charger's FCC if all of the following
|
|
* conditions are met:
|
|
* - HVDCP3 adapter with CP as parallel charger
|
|
* - Output connection topology is VBAT
|
|
*/
|
|
if (!is_cp_topo_vbatt(chg) || chg->hvdcp3_standalone_config
|
|
|| ((chg->real_charger_type != POWER_SUPPLY_TYPE_USB_HVDCP_3)
|
|
&& chg->real_charger_type != POWER_SUPPLY_TYPE_USB_HVDCP_3P5))
|
|
return -EINVAL;
|
|
|
|
rc = power_supply_get_property(chg->cp_psy, POWER_SUPPLY_PROP_CP_ENABLE,
|
|
&pval);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't get CP ENABLE rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
if (!pval.intval)
|
|
return -EINVAL;
|
|
|
|
rc = power_supply_get_property(chg->cp_psy, POWER_SUPPLY_PROP_CP_ILIM,
|
|
&pval);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't get CP ILIM rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
*offset_ua = (pval.intval * CP_TO_MAIN_ICL_OFFSET_PC * 2) / 100;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int smblib_get_prop_from_bms(struct smb_charger *chg,
|
|
enum power_supply_property psp,
|
|
union power_supply_propval *val)
|
|
{
|
|
int rc;
|
|
|
|
if (!chg->bms_psy)
|
|
return -EINVAL;
|
|
|
|
if (chg->extrn_fg && is_extrn_fg_present(chg)) {
|
|
|
|
rc = smblib_get_prop_from_extrn_bms(chg,
|
|
psp, val);
|
|
if (!rc)
|
|
return rc;
|
|
}
|
|
|
|
rc = power_supply_get_property(chg->bms_psy, psp, val);
|
|
|
|
return rc;
|
|
}
|
|
|
|
void smblib_apsd_enable(struct smb_charger *chg, bool enable)
|
|
{
|
|
int rc;
|
|
|
|
rc = smblib_masked_write(chg, USBIN_OPTIONS_1_CFG_REG,
|
|
BC1P2_SRC_DETECT_BIT,
|
|
enable ? BC1P2_SRC_DETECT_BIT : 0);
|
|
if (rc < 0)
|
|
smblib_err(chg, "failed to write USBIN_OPTIONS_1_CFG rc=%d\n",
|
|
rc);
|
|
}
|
|
|
|
void smblib_hvdcp_detect_enable(struct smb_charger *chg, bool enable)
|
|
{
|
|
int rc;
|
|
u8 mask;
|
|
|
|
mask = HVDCP_AUTH_ALG_EN_CFG_BIT | HVDCP_EN_BIT;
|
|
rc = smblib_masked_write(chg, USBIN_OPTIONS_1_CFG_REG, mask,
|
|
enable ? mask : 0);
|
|
if (rc < 0)
|
|
smblib_err(chg, "failed to write USBIN_OPTIONS_1_CFG rc=%d\n",
|
|
rc);
|
|
}
|
|
|
|
static void smblib_hvdcp_detect_try_enable(struct smb_charger *chg, bool enable)
|
|
{
|
|
if (chg->hvdcp_disable || chg->pd_not_supported)
|
|
return;
|
|
smblib_hvdcp_detect_enable(chg, enable);
|
|
}
|
|
|
|
void smblib_hvdcp_hw_inov_enable(struct smb_charger *chg, bool enable)
|
|
{
|
|
int rc;
|
|
|
|
rc = smblib_masked_write(chg, USBIN_OPTIONS_1_CFG_REG,
|
|
HVDCP_AUTONOMOUS_MODE_EN_CFG_BIT,
|
|
enable ? HVDCP_AUTONOMOUS_MODE_EN_CFG_BIT : 0);
|
|
if (rc < 0)
|
|
smblib_err(chg, "failed to write USBIN_OPTIONS_1_CFG rc=%d\n",
|
|
rc);
|
|
}
|
|
|
|
void smblib_hvdcp_enable_mmi(struct smb_charger *chg, bool enable)
|
|
{
|
|
int rc;
|
|
u8 mask;
|
|
|
|
if (chg->hvdcp_disable)
|
|
return;
|
|
|
|
mask = HVDCP_AUTH_ALG_EN_CFG_BIT | HVDCP_EN_BIT;
|
|
rc = smblib_masked_write(chg, USBIN_OPTIONS_1_CFG_REG, mask,
|
|
enable ? mask : 0);
|
|
if (rc < 0)
|
|
smblib_err(chg, "MMI failed to write USBIN_OPTIONS_1_CFG rc=%d\n",
|
|
rc);
|
|
else
|
|
chg->hvdcp_is_disable = enable ? false : true;
|
|
|
|
return;
|
|
}
|
|
|
|
void smblib_hvdcp_exit_config(struct smb_charger *chg)
|
|
{
|
|
u8 stat;
|
|
int rc;
|
|
|
|
rc = smblib_read(chg, APSD_RESULT_STATUS_REG, &stat);
|
|
if (rc < 0)
|
|
return;
|
|
|
|
if (stat & (QC_3P0_BIT | QC_2P0_BIT)) {
|
|
/* force HVDCP to 5V */
|
|
smblib_masked_write(chg, USBIN_OPTIONS_1_CFG_REG,
|
|
HVDCP_AUTONOMOUS_MODE_EN_CFG_BIT, 0);
|
|
smblib_write(chg, CMD_HVDCP_2_REG, FORCE_5V_BIT);
|
|
|
|
/* rerun APSD */
|
|
smblib_masked_write(chg, CMD_APSD_REG, APSD_RERUN_BIT,
|
|
APSD_RERUN_BIT);
|
|
}
|
|
}
|
|
|
|
static int smblib_request_dpdm(struct smb_charger *chg, bool enable)
|
|
{
|
|
int rc = 0;
|
|
|
|
if (chg->pr_swap_in_progress)
|
|
return 0;
|
|
|
|
/* fetch the DPDM regulator */
|
|
if (!chg->dpdm_reg && of_get_property(chg->dev->of_node,
|
|
"dpdm-supply", NULL)) {
|
|
chg->dpdm_reg = devm_regulator_get(chg->dev, "dpdm");
|
|
if (IS_ERR(chg->dpdm_reg)) {
|
|
rc = PTR_ERR(chg->dpdm_reg);
|
|
smblib_err(chg, "Couldn't get dpdm regulator rc=%d\n",
|
|
rc);
|
|
chg->dpdm_reg = NULL;
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
mutex_lock(&chg->dpdm_lock);
|
|
if (enable) {
|
|
if (chg->dpdm_reg && !chg->dpdm_enabled) {
|
|
smblib_dbg(chg, PR_MISC, "enabling DPDM regulator\n");
|
|
rc = regulator_enable(chg->dpdm_reg);
|
|
if (rc < 0)
|
|
smblib_err(chg,
|
|
"Couldn't enable dpdm regulator rc=%d\n",
|
|
rc);
|
|
else
|
|
chg->dpdm_enabled = true;
|
|
}
|
|
} else {
|
|
if (chg->dpdm_reg && chg->dpdm_enabled) {
|
|
smblib_dbg(chg, PR_MISC, "disabling DPDM regulator\n");
|
|
rc = regulator_disable(chg->dpdm_reg);
|
|
if (rc < 0)
|
|
smblib_err(chg,
|
|
"Couldn't disable dpdm regulator rc=%d\n",
|
|
rc);
|
|
else
|
|
chg->dpdm_enabled = false;
|
|
}
|
|
}
|
|
mutex_unlock(&chg->dpdm_lock);
|
|
|
|
return rc;
|
|
}
|
|
|
|
void smblib_rerun_apsd(struct smb_charger *chg)
|
|
{
|
|
int rc;
|
|
|
|
smblib_dbg(chg, PR_MISC, "re-running APSD\n");
|
|
|
|
rc = smblib_masked_write(chg, CMD_APSD_REG,
|
|
APSD_RERUN_BIT, APSD_RERUN_BIT);
|
|
if (rc < 0)
|
|
smblib_err(chg, "Couldn't re-run APSD rc=%d\n", rc);
|
|
}
|
|
|
|
static const struct apsd_result *smblib_update_usb_type(struct smb_charger *chg)
|
|
{
|
|
const struct apsd_result *apsd_result = smblib_get_apsd_result(chg);
|
|
|
|
if (!strcmp(apsd_result->name, "HVDCP3"))
|
|
chg->mmi.hvdcp3_con = true;
|
|
|
|
if (!strcmp(apsd_result->name, "HVDCP2")) {
|
|
char ability = 0;
|
|
mmi_check_extbat_ability(chg, &ability);
|
|
if (ability & EB_RCV_PARALLEL) {
|
|
chg->mmi.hvdcp3_con = true;
|
|
smblib_err(chg, "Force HVDCP3 from HVDCP2\n");
|
|
}
|
|
}
|
|
|
|
/* if PD is active, APSD is disabled so won't have a valid result */
|
|
if (chg->pd_active) {
|
|
chg->real_charger_type = POWER_SUPPLY_TYPE_USB_PD;
|
|
} else if (chg->qc3p5_detected) {
|
|
chg->real_charger_type = POWER_SUPPLY_TYPE_USB_HVDCP_3P5;
|
|
} else {
|
|
/*
|
|
* Update real charger type only if its not FLOAT
|
|
* detected as as SDP
|
|
*/
|
|
if (!(apsd_result->pst == POWER_SUPPLY_TYPE_USB_FLOAT &&
|
|
chg->real_charger_type == POWER_SUPPLY_TYPE_USB))
|
|
chg->real_charger_type = apsd_result->pst;
|
|
if (chg->real_charger_type == POWER_SUPPLY_TYPE_USB_FLOAT)
|
|
chg->real_charger_type = POWER_SUPPLY_TYPE_USB;
|
|
}
|
|
|
|
smblib_dbg(chg, PR_MOTO, "APSD=%s PD=%d\n",
|
|
apsd_result->name, chg->pd_active);
|
|
return apsd_result;
|
|
}
|
|
|
|
static int smblib_notifier_call(struct notifier_block *nb,
|
|
unsigned long ev, void *v)
|
|
{
|
|
struct power_supply *psy = v;
|
|
struct smb_charger *chg = container_of(nb, struct smb_charger, nb);
|
|
|
|
if (!strcmp(psy->desc->name, "bms")) {
|
|
if (!chg->bms_psy)
|
|
chg->bms_psy = psy;
|
|
if (ev == PSY_EVENT_PROP_CHANGED)
|
|
schedule_work(&chg->bms_update_work);
|
|
}
|
|
|
|
if (chg->jeita_configured == JEITA_CFG_NONE)
|
|
schedule_work(&chg->jeita_update_work);
|
|
|
|
if (chg->sec_pl_present && !chg->pl.psy
|
|
&& !strcmp(psy->desc->name, "parallel")) {
|
|
chg->pl.psy = psy;
|
|
schedule_work(&chg->pl_update_work);
|
|
}
|
|
|
|
if (!strcmp(psy->desc->name, "charge_pump_master")) {
|
|
pm_stay_awake(chg->dev);
|
|
schedule_work(&chg->cp_status_change_work);
|
|
}
|
|
|
|
return NOTIFY_OK;
|
|
}
|
|
|
|
static int smblib_register_notifier(struct smb_charger *chg)
|
|
{
|
|
int rc;
|
|
|
|
chg->nb.notifier_call = smblib_notifier_call;
|
|
rc = power_supply_reg_notifier(&chg->nb);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't register psy notifier rc = %d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int smblib_mapping_soc_from_field_value(struct smb_chg_param *param,
|
|
int val_u, u8 *val_raw)
|
|
{
|
|
if (val_u > param->max_u || val_u < param->min_u)
|
|
return -EINVAL;
|
|
|
|
*val_raw = val_u << 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int smblib_mapping_cc_delta_to_field_value(struct smb_chg_param *param,
|
|
u8 val_raw)
|
|
{
|
|
int val_u = val_raw * param->step_u + param->min_u;
|
|
|
|
if (val_u > param->max_u)
|
|
val_u -= param->max_u * 2;
|
|
|
|
return val_u;
|
|
}
|
|
|
|
int smblib_mapping_cc_delta_from_field_value(struct smb_chg_param *param,
|
|
int val_u, u8 *val_raw)
|
|
{
|
|
if (val_u > param->max_u || val_u < param->min_u - param->max_u)
|
|
return -EINVAL;
|
|
|
|
val_u += param->max_u * 2 - param->min_u;
|
|
val_u %= param->max_u * 2;
|
|
*val_raw = val_u / param->step_u;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void smblib_uusb_removal(struct smb_charger *chg)
|
|
{
|
|
int rc;
|
|
struct smb_irq_data *data;
|
|
struct storm_watch *wdata;
|
|
int sec_charger;
|
|
|
|
sec_charger = chg->sec_pl_present ? POWER_SUPPLY_CHARGER_SEC_PL :
|
|
POWER_SUPPLY_CHARGER_SEC_NONE;
|
|
smblib_select_sec_charger(chg, sec_charger, POWER_SUPPLY_CP_NONE,
|
|
false);
|
|
|
|
cancel_delayed_work_sync(&chg->pl_enable_work);
|
|
|
|
if (chg->wa_flags & BOOST_BACK_WA) {
|
|
data = chg->irq_info[SWITCHER_POWER_OK_IRQ].irq_data;
|
|
if (data) {
|
|
wdata = &data->storm_data;
|
|
update_storm_count(wdata, WEAK_CHG_STORM_COUNT);
|
|
vote(chg->usb_icl_votable, BOOST_BACK_VOTER, false, 0);
|
|
vote(chg->usb_icl_votable, WEAK_CHARGER_VOTER,
|
|
false, 0);
|
|
}
|
|
}
|
|
vote(chg->pl_disable_votable, PL_DELAY_VOTER, true, 0);
|
|
vote(chg->awake_votable, PL_DELAY_VOTER, false, 0);
|
|
|
|
/* reset both usbin current and voltage votes */
|
|
vote(chg->pl_enable_votable_indirect, USBIN_I_VOTER, false, 0);
|
|
vote(chg->pl_enable_votable_indirect, USBIN_V_VOTER, false, 0);
|
|
#ifdef QCOM_BASE
|
|
vote(chg->usb_icl_votable, SW_ICL_MAX_VOTER, true,
|
|
is_flash_active(chg) ? SDP_CURRENT_UA : SDP_100_MA);
|
|
#else
|
|
vote(chg->usb_icl_votable, SW_ICL_MAX_VOTER, false, 0);
|
|
#endif
|
|
vote(chg->usb_icl_votable, SW_QC3_VOTER, false, 0);
|
|
vote(chg->usb_icl_votable, HVDCP2_ICL_VOTER, false, 0);
|
|
vote(chg->usb_icl_votable, HVDCP2_12V_ICL_VOTER, false, 0);
|
|
vote(chg->usb_icl_votable, CHG_TERMINATION_VOTER, false, 0);
|
|
vote(chg->usb_icl_votable, THERMAL_THROTTLE_VOTER, false, 0);
|
|
vote(chg->limited_irq_disable_votable, CHARGER_TYPE_VOTER,
|
|
true, 0);
|
|
vote(chg->hdc_irq_disable_votable, CHARGER_TYPE_VOTER, true, 0);
|
|
vote(chg->hdc_irq_disable_votable, HDC_IRQ_VOTER, false, 0);
|
|
|
|
/* Remove SW thermal regulation WA votes */
|
|
vote(chg->usb_icl_votable, SW_THERM_REGULATION_VOTER, false, 0);
|
|
vote(chg->pl_disable_votable, SW_THERM_REGULATION_VOTER, false, 0);
|
|
vote(chg->dc_suspend_votable, SW_THERM_REGULATION_VOTER, false, 0);
|
|
if (chg->cp_disable_votable)
|
|
vote(chg->cp_disable_votable, SW_THERM_REGULATION_VOTER,
|
|
false, 0);
|
|
|
|
/* reconfigure allowed voltage for HVDCP */
|
|
rc = smblib_set_adapter_allowance(chg,
|
|
USBIN_ADAPTER_ALLOW_5V_TO_9V);
|
|
if (rc < 0)
|
|
smblib_err(chg, "Couldn't set USBIN_ADAPTER_ALLOW_5V_TO_9V rc=%d\n",
|
|
rc);
|
|
|
|
/* reset USBOV votes and cancel work */
|
|
cancel_delayed_work_sync(&chg->usbov_dbc_work);
|
|
vote(chg->awake_votable, USBOV_DBC_VOTER, false, 0);
|
|
chg->dbc_usbov = false;
|
|
|
|
#ifdef QCOM_BASE
|
|
chg->voltage_min_uv = MICRO_5V;
|
|
chg->voltage_max_uv = MICRO_5V;
|
|
#endif
|
|
chg->usbin_forced_max_uv = 0;
|
|
chg->pd_contract_uv = 0;
|
|
chg->usb_icl_delta_ua = 0;
|
|
chg->pulse_cnt = 0;
|
|
chg->uusb_apsd_rerun_done = false;
|
|
chg->chg_param.forced_main_fcc = 0;
|
|
|
|
del_timer_sync(&chg->apsd_timer);
|
|
chg->apsd_ext_timeout = false;
|
|
|
|
/* write back the default FLOAT charger configuration */
|
|
rc = smblib_masked_write(chg, USBIN_OPTIONS_2_CFG_REG,
|
|
(u8)FLOAT_OPTIONS_MASK, chg->float_cfg);
|
|
if (rc < 0)
|
|
smblib_err(chg, "Couldn't write float charger options rc=%d\n",
|
|
rc);
|
|
|
|
/* clear USB ICL vote for USB_PSY_VOTER */
|
|
rc = vote(chg->usb_icl_votable, USB_PSY_VOTER, false, 0);
|
|
if (rc < 0)
|
|
smblib_err(chg, "Couldn't un-vote for USB ICL rc=%d\n", rc);
|
|
|
|
/* clear USB ICL vote for DCP_VOTER */
|
|
rc = vote(chg->usb_icl_votable, DCP_VOTER, false, 0);
|
|
if (rc < 0)
|
|
smblib_err(chg,
|
|
"Couldn't un-vote DCP from USB ICL rc=%d\n", rc);
|
|
|
|
/*
|
|
* if non-compliant charger caused UV, restore original max pulses
|
|
* and turn SUSPEND_ON_COLLAPSE_USBIN_BIT back on.
|
|
*/
|
|
if (chg->qc2_unsupported_voltage) {
|
|
rc = smblib_masked_write(chg, HVDCP_PULSE_COUNT_MAX_REG,
|
|
HVDCP_PULSE_COUNT_MAX_QC2_MASK,
|
|
chg->qc2_max_pulses);
|
|
if (rc < 0)
|
|
smblib_err(chg, "Couldn't restore max pulses rc=%d\n",
|
|
rc);
|
|
|
|
rc = smblib_masked_write(chg, USBIN_AICL_OPTIONS_CFG_REG,
|
|
SUSPEND_ON_COLLAPSE_USBIN_BIT,
|
|
SUSPEND_ON_COLLAPSE_USBIN_BIT);
|
|
if (rc < 0)
|
|
smblib_err(chg, "Couldn't turn on SUSPEND_ON_COLLAPSE_USBIN_BIT rc=%d\n",
|
|
rc);
|
|
|
|
chg->qc2_unsupported_voltage = QC2_COMPLIANT;
|
|
}
|
|
|
|
chg->qc3p5_detected = false;
|
|
smblib_update_usb_type(chg);
|
|
}
|
|
|
|
void smblib_suspend_on_debug_battery(struct smb_charger *chg)
|
|
{
|
|
int rc;
|
|
union power_supply_propval val;
|
|
|
|
rc = smblib_get_prop_from_bms(chg,
|
|
POWER_SUPPLY_PROP_DEBUG_BATTERY, &val);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't get debug battery prop rc=%d\n", rc);
|
|
return;
|
|
}
|
|
if (chg->suspend_input_on_debug_batt) {
|
|
vote(chg->usb_icl_votable, DEBUG_BOARD_VOTER, val.intval, 0);
|
|
vote(chg->dc_suspend_votable, DEBUG_BOARD_VOTER, val.intval, 0);
|
|
if (val.intval)
|
|
pr_info("Input suspended: Fake battery\n");
|
|
} else {
|
|
vote(chg->chg_disable_votable, DEBUG_BOARD_VOTER,
|
|
val.intval, 0);
|
|
}
|
|
}
|
|
|
|
int smblib_rerun_apsd_if_required(struct smb_charger *chg)
|
|
{
|
|
union power_supply_propval val;
|
|
int rc;
|
|
|
|
rc = smblib_get_prop_usb_present(chg, &val);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't get usb present rc = %d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
if (!val.intval)
|
|
return 0;
|
|
|
|
rc = smblib_request_dpdm(chg, true);
|
|
if (rc < 0)
|
|
smblib_err(chg, "Couldn't to enable DPDM rc=%d\n", rc);
|
|
|
|
chg->uusb_apsd_rerun_done = true;
|
|
smblib_rerun_apsd(chg);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int smblib_get_pulse_cnt(struct smb_charger *chg, int *count)
|
|
{
|
|
*count = chg->pulse_cnt;
|
|
return 0;
|
|
}
|
|
|
|
#define USBIN_25MA 25000
|
|
#define USBIN_100MA 100000
|
|
#define USBIN_150MA 150000
|
|
#define USBIN_500MA 500000
|
|
#define USBIN_900MA 900000
|
|
#define USBIN_1000MA 1000000
|
|
static int set_sdp_current(struct smb_charger *chg, int icl_ua)
|
|
{
|
|
int rc;
|
|
u8 icl_options;
|
|
const struct apsd_result *apsd_result = smblib_get_apsd_result(chg);
|
|
|
|
/* power source is SDP */
|
|
switch (icl_ua) {
|
|
case USBIN_100MA:
|
|
/* USB 2.0 100mA */
|
|
icl_options = 0;
|
|
break;
|
|
case USBIN_150MA:
|
|
/* USB 3.0 150mA */
|
|
icl_options = CFG_USB3P0_SEL_BIT;
|
|
break;
|
|
case USBIN_500MA:
|
|
/* USB 2.0 500mA */
|
|
icl_options = USB51_MODE_BIT;
|
|
break;
|
|
case USBIN_900MA:
|
|
/* USB 3.0 900mA */
|
|
icl_options = CFG_USB3P0_SEL_BIT | USB51_MODE_BIT;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (chg->real_charger_type == POWER_SUPPLY_TYPE_USB &&
|
|
apsd_result->pst == POWER_SUPPLY_TYPE_USB_FLOAT) {
|
|
/*
|
|
* change the float charger configuration to SDP, if this
|
|
* is the case of SDP being detected as FLOAT
|
|
*/
|
|
rc = smblib_masked_write(chg, USBIN_OPTIONS_2_CFG_REG,
|
|
FORCE_FLOAT_SDP_CFG_BIT, FORCE_FLOAT_SDP_CFG_BIT);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't set float ICL options rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
rc = smblib_masked_write(chg, USBIN_ICL_OPTIONS_REG,
|
|
CFG_USB3P0_SEL_BIT | USB51_MODE_BIT, icl_options);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't set ICL options rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
rc = smblib_icl_override(chg, SW_OVERRIDE_USB51_MODE);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't set ICL override rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
int smblib_set_icl_current(struct smb_charger *chg, int icl_ua)
|
|
{
|
|
int rc = 0;
|
|
enum icl_override_mode icl_override = HW_AUTO_MODE;
|
|
/* suspend if 25mA or less is requested */
|
|
bool suspend = (icl_ua <= USBIN_25MA);
|
|
|
|
if (chg->mmi.factory_mode) {
|
|
pr_err("USB ICL callback in Facory Mode! %d\n", icl_ua);
|
|
return rc;
|
|
}
|
|
|
|
smblib_dbg(chg, PR_MISC, "%d uA\n", icl_ua);
|
|
|
|
if (chg->chg_param.smb_version == PMI632_SUBTYPE)
|
|
schgm_flash_torch_priority(chg, suspend ? TORCH_BOOST_MODE :
|
|
TORCH_BUCK_MODE);
|
|
|
|
/* Do not configure ICL from SW for DAM cables */
|
|
if (smblib_get_prop_typec_mode(chg) ==
|
|
POWER_SUPPLY_TYPEC_SINK_DEBUG_ACCESSORY)
|
|
return 0;
|
|
|
|
if (suspend)
|
|
return smblib_set_usb_suspend(chg, true);
|
|
|
|
if (icl_ua == INT_MAX)
|
|
goto set_mode;
|
|
|
|
/* configure current */
|
|
if (chg->real_charger_type == POWER_SUPPLY_TYPE_USB
|
|
&& (chg->typec_mode == POWER_SUPPLY_TYPEC_SOURCE_DEFAULT
|
|
|| chg->connector_type == POWER_SUPPLY_CONNECTOR_MICRO_USB)) {
|
|
rc = set_sdp_current(chg, icl_ua);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't set SDP ICL rc=%d\n", rc);
|
|
goto out;
|
|
}
|
|
} else {
|
|
/*
|
|
* Try USB 2.0/3,0 option first on USB path when maximum input
|
|
* current limit is 500mA or below for better accuracy; in case
|
|
* of error, proceed to use USB high-current mode.
|
|
*/
|
|
if (icl_ua <= USBIN_500MA) {
|
|
rc = set_sdp_current(chg, icl_ua);
|
|
if (rc >= 0)
|
|
goto unsuspend;
|
|
}
|
|
|
|
rc = smblib_set_charge_param(chg, &chg->param.usb_icl, icl_ua);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't set HC ICL rc=%d\n", rc);
|
|
goto out;
|
|
}
|
|
icl_override = SW_OVERRIDE_HC_MODE;
|
|
}
|
|
|
|
set_mode:
|
|
rc = smblib_icl_override(chg, icl_override);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't set ICL override rc=%d\n", rc);
|
|
goto out;
|
|
}
|
|
|
|
unsuspend:
|
|
/* unsuspend after configuring current and override */
|
|
rc = smblib_set_usb_suspend(chg, false);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't resume input rc=%d\n", rc);
|
|
goto out;
|
|
}
|
|
|
|
/* Re-run AICL */
|
|
if (icl_override != SW_OVERRIDE_HC_MODE)
|
|
rc = smblib_run_aicl(chg, RERUN_AICL);
|
|
out:
|
|
return rc;
|
|
}
|
|
|
|
int smblib_get_icl_current(struct smb_charger *chg, int *icl_ua)
|
|
{
|
|
int rc;
|
|
|
|
rc = smblib_get_charge_param(chg, &chg->param.icl_max_stat, icl_ua);
|
|
if (rc < 0)
|
|
smblib_err(chg, "Couldn't get HC ICL rc=%d\n", rc);
|
|
|
|
return rc;
|
|
}
|
|
|
|
int smblib_toggle_smb_en(struct smb_charger *chg, int toggle)
|
|
{
|
|
int rc = 0;
|
|
|
|
if (!toggle)
|
|
return rc;
|
|
|
|
rc = smblib_select_sec_charger(chg, chg->sec_chg_selected,
|
|
chg->cp_reason, true);
|
|
|
|
return rc;
|
|
}
|
|
|
|
int smblib_get_irq_status(struct smb_charger *chg,
|
|
union power_supply_propval *val)
|
|
{
|
|
int rc;
|
|
u8 reg;
|
|
|
|
if (chg->wa_flags & SKIP_MISC_PBS_IRQ_WA) {
|
|
val->intval = 0;
|
|
return 0;
|
|
}
|
|
|
|
mutex_lock(&chg->irq_status_lock);
|
|
/* Report and clear cached status */
|
|
val->intval = chg->irq_status;
|
|
chg->irq_status = 0;
|
|
|
|
/* get real time status of pulse skip irq */
|
|
rc = smblib_read(chg, MISC_PBS_RT_STS_REG, ®);
|
|
if (rc < 0)
|
|
smblib_err(chg, "Couldn't read MISC_PBS_RT_STS_REG rc=%d\n",
|
|
rc);
|
|
else
|
|
val->intval |= (reg & PULSE_SKIP_IRQ_BIT);
|
|
mutex_unlock(&chg->irq_status_lock);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/****************************
|
|
* uUSB Moisture Protection *
|
|
****************************/
|
|
#define MICRO_USB_DETECTION_ON_TIME_20_MS 0x08
|
|
#define MICRO_USB_DETECTION_PERIOD_X_100 0x03
|
|
#define U_USB_STATUS_WATER_PRESENT 0x00
|
|
static int smblib_set_moisture_protection(struct smb_charger *chg,
|
|
bool enable)
|
|
{
|
|
int rc = 0;
|
|
|
|
if (chg->moisture_present == enable) {
|
|
smblib_dbg(chg, PR_MISC, "No change in moisture protection status\n");
|
|
return rc;
|
|
}
|
|
|
|
if (enable) {
|
|
chg->moisture_present = true;
|
|
|
|
/* Disable uUSB factory mode detection */
|
|
rc = smblib_masked_write(chg, TYPEC_U_USB_CFG_REG,
|
|
EN_MICRO_USB_FACTORY_MODE_BIT, 0);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't disable uUSB factory mode detection rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
/* Disable moisture detection and uUSB state change interrupt */
|
|
rc = smblib_masked_write(chg, TYPE_C_INTERRUPT_EN_CFG_2_REG,
|
|
TYPEC_WATER_DETECTION_INT_EN_BIT |
|
|
MICRO_USB_STATE_CHANGE_INT_EN_BIT, 0);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't disable moisture detection interrupt rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
/* Set 1% duty cycle on ID detection */
|
|
rc = smblib_masked_write(chg,
|
|
((chg->chg_param.smb_version == PMI632_SUBTYPE)
|
|
? PMI632_TYPEC_U_USB_WATER_PROTECTION_CFG_REG :
|
|
TYPEC_U_USB_WATER_PROTECTION_CFG_REG),
|
|
EN_MICRO_USB_WATER_PROTECTION_BIT |
|
|
MICRO_USB_DETECTION_ON_TIME_CFG_MASK |
|
|
MICRO_USB_DETECTION_PERIOD_CFG_MASK,
|
|
EN_MICRO_USB_WATER_PROTECTION_BIT |
|
|
MICRO_USB_DETECTION_ON_TIME_20_MS |
|
|
MICRO_USB_DETECTION_PERIOD_X_100);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't set 1 percent CC_ID duty cycle rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
vote(chg->usb_icl_votable, MOISTURE_VOTER, true, 0);
|
|
} else {
|
|
chg->moisture_present = false;
|
|
vote(chg->usb_icl_votable, MOISTURE_VOTER, false, 0);
|
|
|
|
/* Enable moisture detection and uUSB state change interrupt */
|
|
rc = smblib_masked_write(chg, TYPE_C_INTERRUPT_EN_CFG_2_REG,
|
|
TYPEC_WATER_DETECTION_INT_EN_BIT |
|
|
MICRO_USB_STATE_CHANGE_INT_EN_BIT,
|
|
TYPEC_WATER_DETECTION_INT_EN_BIT |
|
|
MICRO_USB_STATE_CHANGE_INT_EN_BIT);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't enable moisture detection and uUSB state change interrupt rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
/* Disable periodic monitoring of CC_ID pin */
|
|
rc = smblib_write(chg,
|
|
((chg->chg_param.smb_version == PMI632_SUBTYPE)
|
|
? PMI632_TYPEC_U_USB_WATER_PROTECTION_CFG_REG :
|
|
TYPEC_U_USB_WATER_PROTECTION_CFG_REG), 0);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't disable 1 percent CC_ID duty cycle rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
/* Enable uUSB factory mode detection */
|
|
rc = smblib_masked_write(chg, TYPEC_U_USB_CFG_REG,
|
|
EN_MICRO_USB_FACTORY_MODE_BIT,
|
|
EN_MICRO_USB_FACTORY_MODE_BIT);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't disable uUSB factory mode detection rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
smblib_dbg(chg, PR_MISC, "Moisture protection %s\n",
|
|
chg->moisture_present ? "enabled" : "disabled");
|
|
return rc;
|
|
}
|
|
|
|
/*********************
|
|
* VOTABLE CALLBACKS *
|
|
*********************/
|
|
static int smblib_smb_disable_override_vote_callback(struct votable *votable,
|
|
void *data, int disable_smb, const char *client)
|
|
{
|
|
struct smb_charger *chg = data;
|
|
int rc = 0;
|
|
|
|
/* Enable/disable SMB_EN pin */
|
|
rc = smblib_masked_write(chg, MISC_SMB_EN_CMD_REG,
|
|
SMB_EN_OVERRIDE_BIT | SMB_EN_OVERRIDE_VALUE_BIT,
|
|
disable_smb ? SMB_EN_OVERRIDE_BIT : 0);
|
|
if (rc < 0)
|
|
smblib_err(chg, "Couldn't configure SMB_EN, rc=%d\n", rc);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int smblib_dc_suspend_vote_callback(struct votable *votable, void *data,
|
|
int suspend, const char *client)
|
|
{
|
|
struct smb_charger *chg = data;
|
|
|
|
if (chg->chg_param.smb_version == PMI632_SUBTYPE)
|
|
return 0;
|
|
|
|
if (chg->mmi.factory_mode)
|
|
return 0;
|
|
|
|
/* resume input if suspend is invalid */
|
|
if (suspend < 0)
|
|
suspend = 0;
|
|
|
|
return smblib_set_dc_suspend(chg, (bool)suspend);
|
|
}
|
|
|
|
static int smblib_dc_icl_vote_callback(struct votable *votable, void *data,
|
|
int icl_ua, const char *client)
|
|
{
|
|
struct smb_charger *chg = data;
|
|
int rc = 0;
|
|
bool suspend;
|
|
|
|
if (icl_ua < 0) {
|
|
smblib_dbg(chg, PR_MISC, "No Voter hence suspending\n");
|
|
icl_ua = 0;
|
|
}
|
|
|
|
suspend = (icl_ua <= USBIN_25MA);
|
|
if (suspend)
|
|
goto suspend;
|
|
|
|
rc = smblib_set_charge_param(chg, &chg->param.dc_icl, icl_ua);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't set DC input current limit rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
suspend:
|
|
rc = vote(chg->dc_suspend_votable, USER_VOTER, suspend, 0);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't vote to %s DC rc=%d\n",
|
|
suspend ? "suspend" : "resume", rc);
|
|
return rc;
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
static int smblib_awake_vote_callback(struct votable *votable, void *data,
|
|
int awake, const char *client)
|
|
{
|
|
struct smb_charger *chg = data;
|
|
|
|
if (awake)
|
|
pm_stay_awake(chg->dev);
|
|
else
|
|
pm_relax(chg->dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int smblib_chg_disable_vote_callback(struct votable *votable, void *data,
|
|
int chg_disable, const char *client)
|
|
{
|
|
struct smb_charger *chg = data;
|
|
int rc;
|
|
|
|
if (chg->mmi.factory_mode)
|
|
return 0;
|
|
|
|
rc = smblib_masked_write(chg, CHARGING_ENABLE_CMD_REG,
|
|
CHARGING_ENABLE_CMD_BIT,
|
|
chg_disable ? 0 : CHARGING_ENABLE_CMD_BIT);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't %s charging rc=%d\n",
|
|
chg_disable ? "disable" : "enable", rc);
|
|
return rc;
|
|
}
|
|
smblib_run_aicl(chg, RESTART_AICL);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int smblib_hdc_irq_disable_vote_callback(struct votable *votable,
|
|
void *data, int disable, const char *client)
|
|
{
|
|
struct smb_charger *chg = data;
|
|
|
|
if (!chg->irq_info[HIGH_DUTY_CYCLE_IRQ].irq)
|
|
return 0;
|
|
|
|
if (chg->irq_info[HIGH_DUTY_CYCLE_IRQ].enabled) {
|
|
if (disable)
|
|
disable_irq_nosync(
|
|
chg->irq_info[HIGH_DUTY_CYCLE_IRQ].irq);
|
|
} else {
|
|
if (!disable)
|
|
enable_irq(chg->irq_info[HIGH_DUTY_CYCLE_IRQ].irq);
|
|
}
|
|
|
|
chg->irq_info[HIGH_DUTY_CYCLE_IRQ].enabled = !disable;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int smblib_limited_irq_disable_vote_callback(struct votable *votable,
|
|
void *data, int disable, const char *client)
|
|
{
|
|
struct smb_charger *chg = data;
|
|
|
|
if (!chg->irq_info[INPUT_CURRENT_LIMITING_IRQ].irq)
|
|
return 0;
|
|
|
|
if (chg->irq_info[INPUT_CURRENT_LIMITING_IRQ].enabled) {
|
|
if (disable)
|
|
disable_irq_nosync(
|
|
chg->irq_info[INPUT_CURRENT_LIMITING_IRQ].irq);
|
|
} else {
|
|
if (!disable)
|
|
enable_irq(
|
|
chg->irq_info[INPUT_CURRENT_LIMITING_IRQ].irq);
|
|
}
|
|
|
|
chg->irq_info[INPUT_CURRENT_LIMITING_IRQ].enabled = !disable;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int smblib_icl_irq_disable_vote_callback(struct votable *votable,
|
|
void *data, int disable, const char *client)
|
|
{
|
|
struct smb_charger *chg = data;
|
|
|
|
if (!chg->irq_info[USBIN_ICL_CHANGE_IRQ].irq)
|
|
return 0;
|
|
|
|
if (chg->irq_info[USBIN_ICL_CHANGE_IRQ].enabled) {
|
|
if (disable)
|
|
disable_irq_nosync(
|
|
chg->irq_info[USBIN_ICL_CHANGE_IRQ].irq);
|
|
} else {
|
|
if (!disable)
|
|
enable_irq(chg->irq_info[USBIN_ICL_CHANGE_IRQ].irq);
|
|
}
|
|
|
|
chg->irq_info[USBIN_ICL_CHANGE_IRQ].enabled = !disable;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int smblib_temp_change_irq_disable_vote_callback(struct votable *votable,
|
|
void *data, int disable, const char *client)
|
|
{
|
|
struct smb_charger *chg = data;
|
|
|
|
if (!chg->irq_info[TEMP_CHANGE_IRQ].irq)
|
|
return 0;
|
|
|
|
if (chg->irq_info[TEMP_CHANGE_IRQ].enabled && disable) {
|
|
if (chg->irq_info[TEMP_CHANGE_IRQ].wake)
|
|
disable_irq_wake(chg->irq_info[TEMP_CHANGE_IRQ].irq);
|
|
disable_irq_nosync(chg->irq_info[TEMP_CHANGE_IRQ].irq);
|
|
} else if (!chg->irq_info[TEMP_CHANGE_IRQ].enabled && !disable) {
|
|
enable_irq(chg->irq_info[TEMP_CHANGE_IRQ].irq);
|
|
if (chg->irq_info[TEMP_CHANGE_IRQ].wake)
|
|
enable_irq_wake(chg->irq_info[TEMP_CHANGE_IRQ].irq);
|
|
}
|
|
|
|
chg->irq_info[TEMP_CHANGE_IRQ].enabled = !disable;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*******************
|
|
* VCONN REGULATOR *
|
|
* *****************/
|
|
|
|
int smblib_vconn_regulator_enable(struct regulator_dev *rdev)
|
|
{
|
|
struct smb_charger *chg = rdev_get_drvdata(rdev);
|
|
int rc = 0;
|
|
u8 stat, orientation;
|
|
|
|
smblib_dbg(chg, PR_OTG, "enabling VCONN\n");
|
|
|
|
rc = smblib_read(chg, TYPE_C_MISC_STATUS_REG, &stat);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't read TYPE_C_STATUS_4 rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
/* VCONN orientation is opposite to that of CC */
|
|
orientation =
|
|
stat & TYPEC_CCOUT_VALUE_BIT ? 0 : VCONN_EN_ORIENTATION_BIT;
|
|
rc = smblib_masked_write(chg, TYPE_C_VCONN_CONTROL_REG,
|
|
VCONN_EN_VALUE_BIT | VCONN_EN_ORIENTATION_BIT,
|
|
VCONN_EN_VALUE_BIT | orientation);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't read TYPE_C_CCOUT_CONTROL_REG rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int smblib_vconn_regulator_disable(struct regulator_dev *rdev)
|
|
{
|
|
struct smb_charger *chg = rdev_get_drvdata(rdev);
|
|
int rc = 0;
|
|
|
|
smblib_dbg(chg, PR_OTG, "disabling VCONN\n");
|
|
rc = smblib_masked_write(chg, TYPE_C_VCONN_CONTROL_REG,
|
|
VCONN_EN_VALUE_BIT, 0);
|
|
if (rc < 0)
|
|
smblib_err(chg, "Couldn't disable vconn regulator rc=%d\n", rc);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int smblib_vconn_regulator_is_enabled(struct regulator_dev *rdev)
|
|
{
|
|
struct smb_charger *chg = rdev_get_drvdata(rdev);
|
|
int rc;
|
|
u8 cmd;
|
|
|
|
rc = smblib_read(chg, TYPE_C_VCONN_CONTROL_REG, &cmd);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't read TYPE_C_INTRPT_ENB_SOFTWARE_CTRL rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
return (cmd & VCONN_EN_VALUE_BIT) ? 1 : 0;
|
|
}
|
|
|
|
/*****************
|
|
* OTG REGULATOR *
|
|
*****************/
|
|
|
|
static void mmi_set_extbat_state(struct smb_charger *chip,
|
|
enum ebchg_state state,
|
|
bool force);
|
|
int smblib_vbus_regulator_enable(struct regulator_dev *rdev)
|
|
{
|
|
struct smb_charger *chg = rdev_get_drvdata(rdev);
|
|
int rc;
|
|
|
|
smblib_dbg(chg, PR_OTG, "enabling OTG\n");
|
|
|
|
mutex_lock(&chg->otg_lock);
|
|
chg->mmi.is_otg_enable = 1;
|
|
mutex_unlock(&chg->otg_lock);
|
|
|
|
if (chg->mmi.ebchg_state == EB_SRC)
|
|
mmi_set_extbat_state(chg, EB_OFF, false);
|
|
|
|
rc = smblib_masked_write(chg, DCDC_CMD_OTG_REG, OTG_EN_BIT, OTG_EN_BIT);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't enable OTG rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
if (chg->usb_psy)
|
|
power_supply_changed(chg->usb_psy);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int smblib_vbus_regulator_disable(struct regulator_dev *rdev)
|
|
{
|
|
struct smb_charger *chg = rdev_get_drvdata(rdev);
|
|
int rc;
|
|
|
|
smblib_dbg(chg, PR_OTG, "disabling OTG\n");
|
|
|
|
rc = smblib_masked_write(chg, DCDC_CMD_OTG_REG, OTG_EN_BIT, 0);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't disable OTG regulator rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
mutex_lock(&chg->otg_lock);
|
|
chg->mmi.is_otg_enable = 0;
|
|
mutex_unlock(&chg->otg_lock);
|
|
|
|
if (chg->mmi.ebchg_state != EB_DISCONN) {
|
|
cancel_delayed_work(&chg->mmi.heartbeat_work);
|
|
schedule_delayed_work(&chg->mmi.heartbeat_work,
|
|
msecs_to_jiffies(0));
|
|
}
|
|
|
|
if (chg->usb_psy)
|
|
power_supply_changed(chg->usb_psy);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int smblib_vbus_regulator_is_enabled(struct regulator_dev *rdev)
|
|
{
|
|
struct smb_charger *chg = rdev_get_drvdata(rdev);
|
|
int rc = 0;
|
|
u8 cmd;
|
|
|
|
rc = smblib_read(chg, DCDC_CMD_OTG_REG, &cmd);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't read CMD_OTG rc=%d", rc);
|
|
return rc;
|
|
}
|
|
|
|
return (cmd & OTG_EN_BIT) ? 1 : 0;
|
|
}
|
|
|
|
/********************
|
|
* BATT PSY GETTERS *
|
|
********************/
|
|
|
|
int smblib_get_prop_input_suspend(struct smb_charger *chg,
|
|
union power_supply_propval *val)
|
|
{
|
|
val->intval
|
|
= (get_client_vote(chg->usb_icl_votable, USER_VOTER) == 0)
|
|
&& get_client_vote(chg->dc_suspend_votable, USER_VOTER);
|
|
return 0;
|
|
}
|
|
|
|
int smblib_get_prop_batt_present(struct smb_charger *chg,
|
|
union power_supply_propval *val)
|
|
{
|
|
int rc;
|
|
u8 stat;
|
|
|
|
rc = smblib_read(chg, BATIF_BASE + INT_RT_STS_OFFSET, &stat);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't read BATIF_INT_RT_STS rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
val->intval = !(stat & (BAT_THERM_OR_ID_MISSING_RT_STS_BIT
|
|
| BAT_TERMINAL_MISSING_RT_STS_BIT));
|
|
|
|
return rc;
|
|
}
|
|
|
|
int smblib_get_prop_batt_capacity(struct smb_charger *chg,
|
|
union power_supply_propval *val)
|
|
{
|
|
int rc = -EINVAL;
|
|
|
|
if (chg->mmi.test_mode && !(chg->mmi.test_mode_soc < 0)
|
|
&& !(chg->mmi.test_mode_soc > 100)) {
|
|
val->intval = chg->mmi.test_mode_soc;
|
|
return 0;
|
|
}
|
|
|
|
if (chg->fake_capacity >= 0) {
|
|
val->intval = chg->fake_capacity;
|
|
return 0;
|
|
}
|
|
|
|
rc = smblib_get_prop_from_bms(chg, POWER_SUPPLY_PROP_CAPACITY, val);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static bool is_charging_paused(struct smb_charger *chg)
|
|
{
|
|
int rc;
|
|
u8 val;
|
|
|
|
rc = smblib_read(chg, CHARGING_PAUSE_CMD_REG, &val);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't read CHARGING_PAUSE_CMD rc=%d\n", rc);
|
|
return false;
|
|
}
|
|
|
|
return val & CHARGING_PAUSE_CMD_BIT;
|
|
}
|
|
|
|
int smblib_get_prop_batt_age(struct smb_charger *chg,
|
|
union power_supply_propval *val)
|
|
{
|
|
int age;
|
|
int rc = -EINVAL;
|
|
union power_supply_propval pval = {0, };
|
|
|
|
if (chg->bms_psy) {
|
|
rc = power_supply_get_property(chg->bms_psy,
|
|
POWER_SUPPLY_PROP_CHARGE_FULL, &pval);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Fail: get charge full rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
age = pval.intval * 100;
|
|
|
|
rc = power_supply_get_property(chg->bms_psy,
|
|
POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, &pval);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Fail: get chrg full design rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
val->intval = age / pval.intval;
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
#define CUTOFF_COUNT 3
|
|
int smblib_get_prop_batt_status(struct smb_charger *chg,
|
|
union power_supply_propval *val)
|
|
{
|
|
union power_supply_propval pval = {0, };
|
|
bool usb_online, dc_online;
|
|
u8 stat;
|
|
int rc, suspend = 0, input_present = 0;
|
|
|
|
if (chg->fake_chg_status_on_debug_batt) {
|
|
rc = smblib_get_prop_from_bms(chg,
|
|
POWER_SUPPLY_PROP_DEBUG_BATTERY, &pval);
|
|
if (rc < 0) {
|
|
pr_err_ratelimited("Couldn't get debug battery prop rc=%d\n",
|
|
rc);
|
|
} else if (pval.intval == 1) {
|
|
val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
rc = smblib_get_prop_batt_health(chg, &pval);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't get batt health rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
/*
|
|
* The charger status register shows charging even though the battery
|
|
* is discharging when the over voltage condition is hit. Report power
|
|
* supply state as NOT_CHARGING when the battery health reports
|
|
* over voltage.
|
|
*/
|
|
if (pval.intval == POWER_SUPPLY_HEALTH_OVERVOLTAGE) {
|
|
val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* If SOC = 0 and we are discharging with input connected, report
|
|
* the battery status as DISCHARGING.
|
|
*/
|
|
smblib_is_input_present(chg, &input_present);
|
|
rc = smblib_get_prop_from_bms(chg, POWER_SUPPLY_PROP_CAPACITY, &pval);
|
|
if (!rc && pval.intval == 0 && input_present) {
|
|
rc = smblib_get_prop_from_bms(chg,
|
|
POWER_SUPPLY_PROP_CURRENT_NOW, &pval);
|
|
if (!rc && pval.intval > 0) {
|
|
if (chg->cutoff_count > CUTOFF_COUNT) {
|
|
val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
|
|
return 0;
|
|
}
|
|
chg->cutoff_count++;
|
|
} else {
|
|
chg->cutoff_count = 0;
|
|
}
|
|
} else {
|
|
chg->cutoff_count = 0;
|
|
}
|
|
|
|
if (chg->dbc_usbov) {
|
|
rc = smblib_get_prop_usb_present(chg, &pval);
|
|
if (rc < 0) {
|
|
smblib_err(chg,
|
|
"Couldn't get usb present prop rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
rc = smblib_get_usb_suspend(chg, &suspend);
|
|
if (rc < 0) {
|
|
smblib_err(chg,
|
|
"Couldn't get usb suspend rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
* Report charging as long as USBOV is not debounced and
|
|
* charging path is un-suspended.
|
|
*/
|
|
if (pval.intval && !suspend) {
|
|
val->intval = POWER_SUPPLY_STATUS_CHARGING;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
rc = smblib_get_prop_usb_online(chg, &pval);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't get usb online property rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
usb_online = (bool)pval.intval;
|
|
|
|
rc = smblib_get_prop_dc_online(chg, &pval);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't get dc online property rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
dc_online = (bool)pval.intval;
|
|
|
|
rc = smblib_read(chg, BATTERY_CHARGER_STATUS_1_REG, &stat);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't read BATTERY_CHARGER_STATUS_1 rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
stat = stat & BATTERY_CHARGER_STATUS_MASK;
|
|
|
|
if (!usb_online && !dc_online) {
|
|
switch (stat) {
|
|
case TERMINATE_CHARGE:
|
|
case INHIBIT_CHARGE:
|
|
#ifdef QCOM_BASE
|
|
val->intval = POWER_SUPPLY_STATUS_FULL;
|
|
break;
|
|
#endif
|
|
default:
|
|
val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
|
|
break;
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
switch (stat) {
|
|
case TRICKLE_CHARGE:
|
|
case PRE_CHARGE:
|
|
case FULLON_CHARGE:
|
|
case TAPER_CHARGE:
|
|
val->intval = POWER_SUPPLY_STATUS_CHARGING;
|
|
break;
|
|
case TERMINATE_CHARGE:
|
|
case INHIBIT_CHARGE:
|
|
#ifdef QCOM_BASE
|
|
val->intval = POWER_SUPPLY_STATUS_FULL;
|
|
break;
|
|
#endif
|
|
case DISABLE_CHARGE:
|
|
case PAUSE_CHARGE:
|
|
val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
|
|
break;
|
|
default:
|
|
val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
|
|
break;
|
|
}
|
|
|
|
if (is_charging_paused(chg)) {
|
|
val->intval = POWER_SUPPLY_STATUS_CHARGING;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* If charge termination WA is active and has suspended charging, then
|
|
* continue reporting charging status as FULL.
|
|
*/
|
|
if (is_client_vote_enabled_locked(chg->usb_icl_votable,
|
|
CHG_TERMINATION_VOTER)) {
|
|
val->intval = POWER_SUPPLY_STATUS_FULL;
|
|
return 0;
|
|
}
|
|
|
|
if (chg->mmi.pres_chrg_step == STEP_FULL)
|
|
val->intval = POWER_SUPPLY_STATUS_FULL;
|
|
|
|
if (val->intval != POWER_SUPPLY_STATUS_CHARGING)
|
|
return 0;
|
|
|
|
if (!usb_online && dc_online
|
|
&& chg->fake_batt_status == POWER_SUPPLY_STATUS_FULL) {
|
|
val->intval = POWER_SUPPLY_STATUS_FULL;
|
|
return 0;
|
|
}
|
|
|
|
rc = smblib_read(chg, BATTERY_CHARGER_STATUS_5_REG, &stat);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't read BATTERY_CHARGER_STATUS_2 rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
stat &= ENABLE_TRICKLE_BIT | ENABLE_PRE_CHARGING_BIT |
|
|
ENABLE_FULLON_MODE_BIT;
|
|
|
|
if (!stat)
|
|
val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
|
|
|
|
return 0;
|
|
}
|
|
|
|
bool smblib_charge_halted(struct smb_charger *chg)
|
|
{
|
|
u8 stat;
|
|
int rc;
|
|
bool flag = false;
|
|
|
|
rc = smblib_read(chg, BATTERY_CHARGER_STATUS_1_REG, &stat);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't read BATTERY_CHARGER_STATUS_1 rc=%d\n",
|
|
rc);
|
|
return flag;
|
|
}
|
|
stat = stat & BATTERY_CHARGER_STATUS_MASK;
|
|
|
|
switch (stat) {
|
|
case TERMINATE_CHARGE:
|
|
case INHIBIT_CHARGE:
|
|
flag = true;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return flag;
|
|
}
|
|
|
|
int smblib_get_prop_batt_charge_type(struct smb_charger *chg,
|
|
union power_supply_propval *val)
|
|
{
|
|
int rc;
|
|
u8 stat;
|
|
|
|
rc = smblib_read(chg, BATTERY_CHARGER_STATUS_1_REG, &stat);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't read BATTERY_CHARGER_STATUS_1 rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
switch (stat & BATTERY_CHARGER_STATUS_MASK) {
|
|
case TRICKLE_CHARGE:
|
|
case PRE_CHARGE:
|
|
val->intval = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
|
|
break;
|
|
case FULLON_CHARGE:
|
|
val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST;
|
|
break;
|
|
case TAPER_CHARGE:
|
|
val->intval = POWER_SUPPLY_CHARGE_TYPE_TAPER;
|
|
break;
|
|
default:
|
|
val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
int smblib_get_prop_batt_health(struct smb_charger *chg,
|
|
union power_supply_propval *val)
|
|
{
|
|
union power_supply_propval pval;
|
|
int rc;
|
|
int effective_fv_uv;
|
|
u8 stat;
|
|
|
|
rc = smblib_read(chg, BATTERY_CHARGER_STATUS_2_REG, &stat);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't read BATTERY_CHARGER_STATUS_2 rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
smblib_dbg(chg, PR_REGISTER, "BATTERY_CHARGER_STATUS_2 = 0x%02x\n",
|
|
stat);
|
|
|
|
if (stat & CHARGER_ERROR_STATUS_BAT_OV_BIT) {
|
|
rc = smblib_get_prop_from_bms(chg,
|
|
POWER_SUPPLY_PROP_VOLTAGE_NOW, &pval);
|
|
if (!rc) {
|
|
/*
|
|
* If Vbatt is within 40mV above Vfloat, then don't
|
|
* treat it as overvoltage.
|
|
*/
|
|
effective_fv_uv = get_effective_result_locked(
|
|
chg->fv_votable);
|
|
if (pval.intval >= effective_fv_uv + 40000) {
|
|
val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
|
|
smblib_err(chg, "battery over-voltage vbat_fg = %duV, fv = %duV\n",
|
|
pval.intval, effective_fv_uv);
|
|
goto done;
|
|
}
|
|
}
|
|
}
|
|
|
|
rc = smblib_read(chg, BATTERY_CHARGER_STATUS_7_REG, &stat);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't read BATTERY_CHARGER_STATUS_2 rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
if (stat & BAT_TEMP_STATUS_TOO_COLD_BIT)
|
|
val->intval = POWER_SUPPLY_HEALTH_COLD;
|
|
else if (stat & BAT_TEMP_STATUS_TOO_HOT_BIT)
|
|
val->intval = POWER_SUPPLY_HEALTH_OVERHEAT;
|
|
else if (stat & BAT_TEMP_STATUS_COLD_SOFT_BIT)
|
|
val->intval = POWER_SUPPLY_HEALTH_COOL;
|
|
else if (stat & BAT_TEMP_STATUS_HOT_SOFT_BIT)
|
|
val->intval = POWER_SUPPLY_HEALTH_WARM;
|
|
else
|
|
val->intval = POWER_SUPPLY_HEALTH_GOOD;
|
|
|
|
val->intval = chg->mmi.batt_health;
|
|
done:
|
|
return rc;
|
|
}
|
|
|
|
int smblib_get_prop_system_temp_level(struct smb_charger *chg,
|
|
union power_supply_propval *val)
|
|
{
|
|
val->intval = chg->system_temp_level;
|
|
return 0;
|
|
}
|
|
|
|
int smblib_get_prop_system_temp_level_max(struct smb_charger *chg,
|
|
union power_supply_propval *val)
|
|
{
|
|
val->intval = chg->thermal_levels;
|
|
return 0;
|
|
}
|
|
|
|
int smblib_get_prop_input_current_limited(struct smb_charger *chg,
|
|
union power_supply_propval *val)
|
|
{
|
|
u8 stat;
|
|
int rc;
|
|
|
|
if (chg->fake_input_current_limited >= 0) {
|
|
val->intval = chg->fake_input_current_limited;
|
|
return 0;
|
|
}
|
|
|
|
rc = smblib_read(chg, AICL_STATUS_REG, &stat);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't read AICL_STATUS rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
val->intval = (stat & SOFT_ILIMIT_BIT) || chg->is_hdc;
|
|
return 0;
|
|
}
|
|
|
|
int smblib_get_prop_batt_iterm(struct smb_charger *chg,
|
|
union power_supply_propval *val)
|
|
{
|
|
int rc, temp;
|
|
u8 stat, buf[2];
|
|
|
|
/*
|
|
* Currently, only ADC comparator-based termination is supported,
|
|
* hence read only the threshold corresponding to ADC source.
|
|
* Proceed only if CHGR_ITERM_USE_ANALOG_BIT is 0.
|
|
*/
|
|
rc = smblib_read(chg, CHGR_ENG_CHARGING_CFG_REG, &stat);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't read CHGR_ENG_CHARGING_CFG_REG rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
if (stat & CHGR_ITERM_USE_ANALOG_BIT) {
|
|
val->intval = -EINVAL;
|
|
return 0;
|
|
}
|
|
|
|
rc = smblib_batch_read(chg, CHGR_ADC_ITERM_UP_THD_MSB_REG, buf, 2);
|
|
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't read CHGR_ADC_ITERM_UP_THD_MSB_REG rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
temp = buf[1] | (buf[0] << 8);
|
|
temp = sign_extend32(temp, 15);
|
|
|
|
if (chg->chg_param.smb_version == PMI632_SUBTYPE)
|
|
temp = DIV_ROUND_CLOSEST(temp * ITERM_LIMITS_PMI632_MA,
|
|
ADC_CHG_ITERM_MASK);
|
|
else
|
|
temp = DIV_ROUND_CLOSEST(temp * ITERM_LIMITS_PM8150B_MA,
|
|
ADC_CHG_ITERM_MASK);
|
|
|
|
val->intval = temp;
|
|
|
|
return rc;
|
|
}
|
|
|
|
int smblib_get_prop_batt_charge_done(struct smb_charger *chg,
|
|
union power_supply_propval *val)
|
|
{
|
|
int rc;
|
|
u8 stat;
|
|
|
|
rc = smblib_read(chg, BATTERY_CHARGER_STATUS_1_REG, &stat);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't read BATTERY_CHARGER_STATUS_1 rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
stat = stat & BATTERY_CHARGER_STATUS_MASK;
|
|
val->intval = (stat == TERMINATE_CHARGE);
|
|
|
|
if ((chg->mmi.pres_chrg_step == STEP_FULL) ||
|
|
(chg->mmi.pres_chrg_step == STEP_EB))
|
|
val->intval = 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int smblib_get_batt_current_now(struct smb_charger *chg,
|
|
union power_supply_propval *val)
|
|
{
|
|
int rc;
|
|
|
|
rc = smblib_get_prop_from_bms(chg,
|
|
POWER_SUPPLY_PROP_CURRENT_NOW, val);
|
|
if (!rc)
|
|
val->intval *= (-1);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/***********************
|
|
* BATTERY PSY SETTERS *
|
|
***********************/
|
|
|
|
int smblib_set_prop_input_suspend(struct smb_charger *chg,
|
|
const union power_supply_propval *val)
|
|
{
|
|
int rc;
|
|
|
|
/* vote 0mA when suspended */
|
|
rc = vote(chg->usb_icl_votable, USER_VOTER, (bool)val->intval, 0);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't vote to %s USB rc=%d\n",
|
|
(bool)val->intval ? "suspend" : "resume", rc);
|
|
return rc;
|
|
}
|
|
|
|
rc = vote(chg->dc_suspend_votable, USER_VOTER, (bool)val->intval, 0);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't vote to %s DC rc=%d\n",
|
|
(bool)val->intval ? "suspend" : "resume", rc);
|
|
return rc;
|
|
}
|
|
|
|
power_supply_changed(chg->batt_psy);
|
|
return rc;
|
|
}
|
|
|
|
int smblib_set_prop_batt_capacity(struct smb_charger *chg,
|
|
const union power_supply_propval *val)
|
|
{
|
|
if (chg->mmi.test_mode)
|
|
chg->mmi.test_mode_soc = val->intval;
|
|
|
|
chg->fake_capacity = val->intval;
|
|
|
|
power_supply_changed(chg->batt_psy);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int smblib_set_prop_batt_status(struct smb_charger *chg,
|
|
const union power_supply_propval *val)
|
|
{
|
|
/* Faking battery full */
|
|
if (val->intval == POWER_SUPPLY_STATUS_FULL)
|
|
chg->fake_batt_status = val->intval;
|
|
else
|
|
chg->fake_batt_status = -EINVAL;
|
|
|
|
power_supply_changed(chg->batt_psy);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int smblib_set_prop_system_temp_level(struct smb_charger *chg,
|
|
const union power_supply_propval *val)
|
|
{
|
|
if (val->intval < 0)
|
|
return -EINVAL;
|
|
|
|
if (chg->thermal_levels <= 0)
|
|
return -EINVAL;
|
|
|
|
if (val->intval >= chg->thermal_levels)
|
|
chg->system_temp_level = chg->thermal_levels - 1;
|
|
else
|
|
chg->system_temp_level = val->intval;
|
|
|
|
vote(chg->chg_disable_votable, THERMAL_DAEMON_VOTER, false, 0);
|
|
if (chg->system_temp_level == 0)
|
|
return vote(chg->fcc_votable, THERMAL_DAEMON_VOTER, false, 0);
|
|
|
|
vote(chg->fcc_votable, THERMAL_DAEMON_VOTER, true,
|
|
chg->thermal_mitigation[chg->system_temp_level] * 1000);
|
|
return 0;
|
|
}
|
|
|
|
int smblib_set_prop_input_current_limited(struct smb_charger *chg,
|
|
const union power_supply_propval *val)
|
|
{
|
|
chg->fake_input_current_limited = val->intval;
|
|
return 0;
|
|
}
|
|
|
|
int smblib_set_prop_rechg_soc_thresh(struct smb_charger *chg,
|
|
const union power_supply_propval *val)
|
|
{
|
|
int rc;
|
|
u8 new_thr = DIV_ROUND_CLOSEST(val->intval * 255, 100);
|
|
|
|
rc = smblib_write(chg, CHARGE_RCHG_SOC_THRESHOLD_CFG_REG,
|
|
new_thr);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't write to RCHG_SOC_THRESHOLD_CFG_REG rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
chg->auto_recharge_soc = val->intval;
|
|
|
|
return rc;
|
|
}
|
|
|
|
int smblib_run_aicl(struct smb_charger *chg, int type)
|
|
{
|
|
int rc;
|
|
u8 stat;
|
|
|
|
rc = smblib_read(chg, POWER_PATH_STATUS_REG, &stat);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't read POWER_PATH_STATUS rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
/* USB is suspended so skip re-running AICL */
|
|
if (stat & USBIN_SUSPEND_STS_BIT)
|
|
return rc;
|
|
|
|
smblib_dbg(chg, PR_MISC, "re-running AICL\n");
|
|
|
|
stat = (type == RERUN_AICL) ? RERUN_AICL_BIT : RESTART_AICL_BIT;
|
|
rc = smblib_masked_write(chg, AICL_CMD_REG, stat, stat);
|
|
if (rc < 0)
|
|
smblib_err(chg, "Couldn't write to AICL_CMD_REG rc=%d\n",
|
|
rc);
|
|
return 0;
|
|
}
|
|
|
|
static int smblib_dp_pulse(struct smb_charger *chg)
|
|
{
|
|
int rc;
|
|
|
|
/* QC 3.0 increment */
|
|
rc = smblib_masked_write(chg, CMD_HVDCP_2_REG, SINGLE_INCREMENT_BIT,
|
|
SINGLE_INCREMENT_BIT);
|
|
if (rc < 0)
|
|
smblib_err(chg, "Couldn't write to CMD_HVDCP_2_REG rc=%d\n",
|
|
rc);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int smblib_dm_pulse(struct smb_charger *chg)
|
|
{
|
|
int rc;
|
|
|
|
/* QC 3.0 decrement */
|
|
rc = smblib_masked_write(chg, CMD_HVDCP_2_REG, SINGLE_DECREMENT_BIT,
|
|
SINGLE_DECREMENT_BIT);
|
|
if (rc < 0)
|
|
smblib_err(chg, "Couldn't write to CMD_HVDCP_2_REG rc=%d\n",
|
|
rc);
|
|
|
|
return rc;
|
|
}
|
|
|
|
int smblib_force_vbus_voltage(struct smb_charger *chg, u8 val)
|
|
{
|
|
int rc;
|
|
|
|
rc = smblib_masked_write(chg, CMD_HVDCP_2_REG, val, val);
|
|
if (rc < 0)
|
|
smblib_err(chg, "Couldn't write to CMD_HVDCP_2_REG rc=%d\n",
|
|
rc);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static void smblib_hvdcp_set_fsw(struct smb_charger *chg, int bit)
|
|
{
|
|
switch (bit) {
|
|
case QC_5V_BIT:
|
|
smblib_set_opt_switcher_freq(chg,
|
|
chg->chg_freq.freq_5V);
|
|
break;
|
|
case QC_9V_BIT:
|
|
smblib_set_opt_switcher_freq(chg,
|
|
chg->chg_freq.freq_9V);
|
|
break;
|
|
case QC_12V_BIT:
|
|
smblib_set_opt_switcher_freq(chg,
|
|
chg->chg_freq.freq_12V);
|
|
break;
|
|
default:
|
|
smblib_set_opt_switcher_freq(chg,
|
|
chg->chg_freq.freq_removal);
|
|
break;
|
|
}
|
|
}
|
|
|
|
#define QC3_PULSES_FOR_6V 5
|
|
#define QC3_PULSES_FOR_9V 20
|
|
#define QC3_PULSES_FOR_12V 35
|
|
static int smblib_hvdcp3_set_fsw(struct smb_charger *chg)
|
|
{
|
|
int pulse_count, rc;
|
|
|
|
rc = smblib_get_pulse_cnt(chg, &pulse_count);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't read QC_PULSE_COUNT rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
if (pulse_count < QC3_PULSES_FOR_6V)
|
|
smblib_set_opt_switcher_freq(chg,
|
|
chg->chg_freq.freq_5V);
|
|
else if (pulse_count < QC3_PULSES_FOR_9V)
|
|
smblib_set_opt_switcher_freq(chg,
|
|
chg->chg_freq.freq_6V_8V);
|
|
else if (pulse_count < QC3_PULSES_FOR_12V)
|
|
smblib_set_opt_switcher_freq(chg,
|
|
chg->chg_freq.freq_9V);
|
|
else
|
|
smblib_set_opt_switcher_freq(chg,
|
|
chg->chg_freq.freq_12V);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void smblib_hvdcp_adaptive_voltage_change(struct smb_charger *chg)
|
|
{
|
|
int rc;
|
|
u8 stat;
|
|
|
|
if (chg->real_charger_type == POWER_SUPPLY_TYPE_USB_HVDCP) {
|
|
rc = smblib_read(chg, QC_CHANGE_STATUS_REG, &stat);
|
|
if (rc < 0) {
|
|
smblib_err(chg,
|
|
"Couldn't read QC_CHANGE_STATUS rc=%d\n", rc);
|
|
return;
|
|
}
|
|
|
|
smblib_hvdcp_set_fsw(chg, stat & QC_2P0_STATUS_MASK);
|
|
vote(chg->usb_icl_votable, HVDCP2_ICL_VOTER, false, 0);
|
|
}
|
|
|
|
if (chg->real_charger_type == POWER_SUPPLY_TYPE_USB_HVDCP_3
|
|
|| chg->real_charger_type == POWER_SUPPLY_TYPE_USB_HVDCP_3P5) {
|
|
rc = smblib_hvdcp3_set_fsw(chg);
|
|
if (rc < 0)
|
|
smblib_err(chg, "Couldn't set QC3.0 Fsw rc=%d\n", rc);
|
|
}
|
|
|
|
power_supply_changed(chg->usb_main_psy);
|
|
}
|
|
|
|
int smblib_dp_dm(struct smb_charger *chg, int val)
|
|
{
|
|
#ifdef QCOM_BASE
|
|
int target_icl_ua, rc = 0;
|
|
union power_supply_propval pval;
|
|
u8 stat;
|
|
|
|
switch (val) {
|
|
case POWER_SUPPLY_DP_DM_DP_PULSE:
|
|
/*
|
|
* Pre-emptively increment pulse count to enable the setting
|
|
* of FSW prior to increasing voltage.
|
|
*/
|
|
chg->pulse_cnt++;
|
|
|
|
rc = smblib_hvdcp3_set_fsw(chg);
|
|
if (rc < 0)
|
|
smblib_err(chg, "Couldn't set QC3.0 Fsw rc=%d\n", rc);
|
|
|
|
rc = smblib_dp_pulse(chg);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't increase pulse count rc=%d\n",
|
|
rc);
|
|
/*
|
|
* Increment pulse count failed;
|
|
* reset to former value.
|
|
*/
|
|
chg->pulse_cnt--;
|
|
}
|
|
|
|
smblib_dbg(chg, PR_PARALLEL, "DP_DM_DP_PULSE rc=%d cnt=%d\n",
|
|
rc, chg->pulse_cnt);
|
|
break;
|
|
case POWER_SUPPLY_DP_DM_DM_PULSE:
|
|
rc = smblib_dm_pulse(chg);
|
|
if (!rc && chg->pulse_cnt)
|
|
chg->pulse_cnt--;
|
|
smblib_dbg(chg, PR_PARALLEL, "DP_DM_DM_PULSE rc=%d cnt=%d\n",
|
|
rc, chg->pulse_cnt);
|
|
break;
|
|
case POWER_SUPPLY_DP_DM_ICL_DOWN:
|
|
target_icl_ua = get_effective_result(chg->usb_icl_votable);
|
|
if (target_icl_ua < 0) {
|
|
/* no client vote, get the ICL from charger */
|
|
rc = power_supply_get_property(chg->usb_psy,
|
|
POWER_SUPPLY_PROP_HW_CURRENT_MAX,
|
|
&pval);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't get max curr rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
target_icl_ua = pval.intval;
|
|
}
|
|
|
|
/*
|
|
* Check if any other voter voted on USB_ICL in case of
|
|
* voter other than SW_QC3_VOTER reset and restart reduction
|
|
* again.
|
|
*/
|
|
if (target_icl_ua != get_client_vote(chg->usb_icl_votable,
|
|
SW_QC3_VOTER))
|
|
chg->usb_icl_delta_ua = 0;
|
|
|
|
chg->usb_icl_delta_ua += 100000;
|
|
vote(chg->usb_icl_votable, SW_QC3_VOTER, true,
|
|
target_icl_ua - 100000);
|
|
smblib_dbg(chg, PR_PARALLEL, "ICL DOWN ICL=%d reduction=%d\n",
|
|
target_icl_ua, chg->usb_icl_delta_ua);
|
|
break;
|
|
case POWER_SUPPLY_DP_DM_FORCE_5V:
|
|
rc = smblib_force_vbus_voltage(chg, FORCE_5V_BIT);
|
|
if (rc < 0)
|
|
pr_err("Failed to force 5V\n");
|
|
|
|
vote(chg->usb_icl_votable, HVDCP2_12V_ICL_VOTER, false, 0);
|
|
break;
|
|
case POWER_SUPPLY_DP_DM_FORCE_9V:
|
|
if (chg->qc2_unsupported_voltage == QC2_NON_COMPLIANT_9V) {
|
|
smblib_err(chg, "Couldn't set 9V: unsupported\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* If we are increasing voltage to get to 9V, set FSW first */
|
|
rc = smblib_read(chg, QC_CHANGE_STATUS_REG, &stat);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't read QC_CHANGE_STATUS_REG rc=%d\n",
|
|
rc);
|
|
break;
|
|
}
|
|
|
|
if (stat & QC_5V_BIT) {
|
|
/* Force 1A ICL before requesting higher voltage */
|
|
vote(chg->usb_icl_votable, HVDCP2_ICL_VOTER,
|
|
true, 1000000);
|
|
smblib_hvdcp_set_fsw(chg, QC_9V_BIT);
|
|
}
|
|
|
|
rc = smblib_force_vbus_voltage(chg, FORCE_9V_BIT);
|
|
if (rc < 0)
|
|
pr_err("Failed to force 9V\n");
|
|
|
|
vote(chg->usb_icl_votable, HVDCP2_12V_ICL_VOTER, false, 0);
|
|
break;
|
|
case POWER_SUPPLY_DP_DM_FORCE_12V:
|
|
if (chg->qc2_unsupported_voltage == QC2_NON_COMPLIANT_12V) {
|
|
smblib_err(chg, "Couldn't set 12V: unsupported\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* If we are increasing voltage to get to 12V, set FSW first */
|
|
rc = smblib_read(chg, QC_CHANGE_STATUS_REG, &stat);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't read QC_CHANGE_STATUS_REG rc=%d\n",
|
|
rc);
|
|
break;
|
|
}
|
|
|
|
if ((stat & QC_9V_BIT) || (stat & QC_5V_BIT)) {
|
|
vote(chg->usb_icl_votable, HVDCP2_12V_ICL_VOTER, true,
|
|
chg->chg_param.hvdcp2_12v_max_icl_ua);
|
|
/* Force 1A ICL before requesting higher voltage */
|
|
vote(chg->usb_icl_votable, HVDCP2_ICL_VOTER,
|
|
true, 1000000);
|
|
smblib_hvdcp_set_fsw(chg, QC_12V_BIT);
|
|
}
|
|
|
|
rc = smblib_force_vbus_voltage(chg, FORCE_12V_BIT);
|
|
if (rc < 0)
|
|
pr_err("Failed to force 12V\n");
|
|
break;
|
|
case POWER_SUPPLY_DP_DM_CONFIRMED_HVDCP3P5:
|
|
chg->qc3p5_detected = true;
|
|
smblib_update_usb_type(chg);
|
|
break;
|
|
case POWER_SUPPLY_DP_DM_ICL_UP:
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return rc;
|
|
#else
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
int smblib_disable_hw_jeita(struct smb_charger *chg, bool disable)
|
|
{
|
|
int rc;
|
|
u8 mask;
|
|
|
|
/*
|
|
* Disable h/w base JEITA compensation if s/w JEITA is enabled
|
|
*/
|
|
mask = JEITA_EN_COLD_SL_FCV_BIT
|
|
| JEITA_EN_HOT_SL_FCV_BIT
|
|
| JEITA_EN_HOT_SL_CCC_BIT
|
|
| JEITA_EN_COLD_SL_CCC_BIT,
|
|
rc = smblib_masked_write(chg, JEITA_EN_CFG_REG, mask,
|
|
disable ? 0 : mask);
|
|
if (rc < 0) {
|
|
dev_err(chg->dev, "Couldn't configure s/w jeita rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int smblib_set_sw_thermal_regulation(struct smb_charger *chg,
|
|
bool enable)
|
|
{
|
|
int rc = 0;
|
|
|
|
if (!(chg->wa_flags & SW_THERM_REGULATION_WA))
|
|
return rc;
|
|
|
|
if (enable) {
|
|
/*
|
|
* Configure min time to quickly address thermal
|
|
* condition.
|
|
*/
|
|
rc = smblib_masked_write(chg, SNARL_BARK_BITE_WD_CFG_REG,
|
|
SNARL_WDOG_TIMEOUT_MASK, SNARL_WDOG_TMOUT_62P5MS);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't configure snarl wdog tmout, rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
* Schedule SW_THERM_REGULATION_WORK directly if USB input
|
|
* is suspended due to SW thermal regulation WA since WDOG
|
|
* IRQ won't trigger with input suspended.
|
|
*/
|
|
if (is_client_vote_enabled(chg->usb_icl_votable,
|
|
SW_THERM_REGULATION_VOTER)) {
|
|
vote(chg->awake_votable, SW_THERM_REGULATION_VOTER,
|
|
true, 0);
|
|
schedule_delayed_work(&chg->thermal_regulation_work, 0);
|
|
}
|
|
} else {
|
|
cancel_delayed_work_sync(&chg->thermal_regulation_work);
|
|
vote(chg->awake_votable, SW_THERM_REGULATION_VOTER, false, 0);
|
|
}
|
|
|
|
smblib_dbg(chg, PR_MISC, "WDOG SNARL INT %s\n",
|
|
enable ? "Enabled" : "Disabled");
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int smblib_update_thermal_readings(struct smb_charger *chg)
|
|
{
|
|
union power_supply_propval pval = {0, };
|
|
int rc = 0;
|
|
|
|
if (!chg->pl.psy)
|
|
chg->pl.psy = power_supply_get_by_name("parallel");
|
|
|
|
rc = smblib_read_iio_channel(chg, chg->iio.die_temp_chan,
|
|
DIV_FACTOR_DECIDEGC, &chg->die_temp);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't read DIE TEMP channel, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
rc = smblib_read_iio_channel(chg, chg->iio.connector_temp_chan,
|
|
DIV_FACTOR_DECIDEGC, &chg->connector_temp);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't read CONN TEMP channel, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
rc = smblib_read_iio_channel(chg, chg->iio.skin_temp_chan,
|
|
DIV_FACTOR_DECIDEGC, &chg->skin_temp);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't read SKIN TEMP channel, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
if (chg->sec_chg_selected == POWER_SUPPLY_CHARGER_SEC_CP) {
|
|
if (is_cp_available(chg)) {
|
|
rc = power_supply_get_property(chg->cp_psy,
|
|
POWER_SUPPLY_PROP_CP_DIE_TEMP, &pval);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't get smb1390 charger temp, rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
chg->smb_temp = pval.intval;
|
|
} else {
|
|
smblib_dbg(chg, PR_MISC, "Coudln't find cp_psy\n");
|
|
chg->smb_temp = -ENODATA;
|
|
}
|
|
} else if (chg->pl.psy && chg->sec_chg_selected ==
|
|
POWER_SUPPLY_CHARGER_SEC_PL) {
|
|
rc = power_supply_get_property(chg->pl.psy,
|
|
POWER_SUPPLY_PROP_CHARGER_TEMP, &pval);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't get smb1355 charger temp, rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
chg->smb_temp = pval.intval;
|
|
} else {
|
|
chg->smb_temp = -ENODATA;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
/* SW thermal regulation thresholds in deciDegC */
|
|
#define DIE_TEMP_RST_THRESH 1000
|
|
#define DIE_TEMP_REG_H_THRESH 800
|
|
#define DIE_TEMP_REG_L_THRESH 600
|
|
|
|
#define CONNECTOR_TEMP_SHDN_THRESH 700
|
|
#define CONNECTOR_TEMP_RST_THRESH 600
|
|
#define CONNECTOR_TEMP_REG_H_THRESH 550
|
|
#define CONNECTOR_TEMP_REG_L_THRESH 500
|
|
|
|
#define SMB_TEMP_SHDN_THRESH 1400
|
|
#define SMB_TEMP_RST_THRESH 900
|
|
#define SMB_TEMP_REG_H_THRESH 800
|
|
#define SMB_TEMP_REG_L_THRESH 600
|
|
|
|
#define SKIN_TEMP_SHDN_THRESH 700
|
|
#define SKIN_TEMP_RST_THRESH 600
|
|
#define SKIN_TEMP_REG_H_THRESH 550
|
|
#define SKIN_TEMP_REG_L_THRESH 500
|
|
|
|
#define THERM_REG_RECHECK_DELAY_1S 1000 /* 1 sec */
|
|
#define THERM_REG_RECHECK_DELAY_8S 8000 /* 8 sec */
|
|
static int smblib_process_thermal_readings(struct smb_charger *chg)
|
|
{
|
|
int rc = 0, wdog_timeout = SNARL_WDOG_TMOUT_8S;
|
|
u32 thermal_status = TEMP_BELOW_RANGE;
|
|
bool suspend_input = false, disable_smb = false;
|
|
|
|
/*
|
|
* Following is the SW thermal regulation flow:
|
|
*
|
|
* TEMP_SHUT_DOWN_LEVEL: If either connector temp or skin temp
|
|
* exceeds their respective SHDN threshold. Need to suspend input
|
|
* and secondary charger.
|
|
*
|
|
* TEMP_SHUT_DOWN_SMB_LEVEL: If smb temp exceed its SHDN threshold
|
|
* but connector and skin temp are below it. Need to suspend SMB.
|
|
*
|
|
* TEMP_ALERT_LEVEL: If die, connector, smb or skin temp exceeds it's
|
|
* respective RST threshold. Stay put and monitor temperature closely.
|
|
*
|
|
* TEMP_ABOVE_RANGE or TEMP_WITHIN_RANGE or TEMP_BELOW_RANGE: If die,
|
|
* connector, smb or skin temp exceeds it's respective REG_H or REG_L
|
|
* threshold. Unsuspend input and SMB.
|
|
*/
|
|
if (chg->connector_temp > CONNECTOR_TEMP_SHDN_THRESH ||
|
|
chg->skin_temp > SKIN_TEMP_SHDN_THRESH) {
|
|
thermal_status = TEMP_SHUT_DOWN;
|
|
wdog_timeout = SNARL_WDOG_TMOUT_1S;
|
|
suspend_input = true;
|
|
disable_smb = true;
|
|
goto out;
|
|
}
|
|
|
|
if (chg->smb_temp > SMB_TEMP_SHDN_THRESH) {
|
|
thermal_status = TEMP_SHUT_DOWN_SMB;
|
|
wdog_timeout = SNARL_WDOG_TMOUT_1S;
|
|
disable_smb = true;
|
|
goto out;
|
|
}
|
|
|
|
if (chg->connector_temp > CONNECTOR_TEMP_RST_THRESH ||
|
|
chg->skin_temp > SKIN_TEMP_RST_THRESH ||
|
|
chg->smb_temp > SMB_TEMP_RST_THRESH ||
|
|
chg->die_temp > DIE_TEMP_RST_THRESH) {
|
|
thermal_status = TEMP_ALERT_LEVEL;
|
|
wdog_timeout = SNARL_WDOG_TMOUT_1S;
|
|
goto out;
|
|
}
|
|
|
|
if (chg->connector_temp > CONNECTOR_TEMP_REG_H_THRESH ||
|
|
chg->skin_temp > SKIN_TEMP_REG_H_THRESH ||
|
|
chg->smb_temp > SMB_TEMP_REG_H_THRESH ||
|
|
chg->die_temp > DIE_TEMP_REG_H_THRESH) {
|
|
thermal_status = TEMP_ABOVE_RANGE;
|
|
wdog_timeout = SNARL_WDOG_TMOUT_1S;
|
|
goto out;
|
|
}
|
|
|
|
if (chg->connector_temp > CONNECTOR_TEMP_REG_L_THRESH ||
|
|
chg->skin_temp > SKIN_TEMP_REG_L_THRESH ||
|
|
chg->smb_temp > SMB_TEMP_REG_L_THRESH ||
|
|
chg->die_temp > DIE_TEMP_REG_L_THRESH) {
|
|
thermal_status = TEMP_WITHIN_RANGE;
|
|
wdog_timeout = SNARL_WDOG_TMOUT_8S;
|
|
}
|
|
out:
|
|
smblib_dbg(chg, PR_MISC, "Current temperatures: \tDIE_TEMP: %d,\tCONN_TEMP: %d,\tSMB_TEMP: %d,\tSKIN_TEMP: %d\nTHERMAL_STATUS: %d\n",
|
|
chg->die_temp, chg->connector_temp, chg->smb_temp,
|
|
chg->skin_temp, thermal_status);
|
|
|
|
if (thermal_status != chg->thermal_status) {
|
|
chg->thermal_status = thermal_status;
|
|
/*
|
|
* If thermal level changes to TEMP ALERT LEVEL, don't
|
|
* enable/disable main/parallel charging.
|
|
*/
|
|
if (chg->thermal_status == TEMP_ALERT_LEVEL)
|
|
goto exit;
|
|
|
|
vote(chg->smb_override_votable, SW_THERM_REGULATION_VOTER,
|
|
disable_smb, 0);
|
|
|
|
/*
|
|
* Enable/disable secondary charger through votables to ensure
|
|
* that if SMB_EN pin get's toggled somehow, secondary charger
|
|
* remains enabled/disabled according to SW thermal regulation.
|
|
*/
|
|
if (!chg->cp_disable_votable)
|
|
chg->cp_disable_votable = find_votable("CP_DISABLE");
|
|
if (chg->cp_disable_votable)
|
|
vote(chg->cp_disable_votable, SW_THERM_REGULATION_VOTER,
|
|
disable_smb, 0);
|
|
|
|
vote(chg->pl_disable_votable, SW_THERM_REGULATION_VOTER,
|
|
disable_smb, 0);
|
|
smblib_dbg(chg, PR_MISC, "Parallel %s as per SW thermal regulation\n",
|
|
disable_smb ? "disabled" : "enabled");
|
|
|
|
/*
|
|
* If thermal level changes to TEMP_SHUT_DOWN_SMB, don't
|
|
* enable/disable main charger.
|
|
*/
|
|
if (chg->thermal_status == TEMP_SHUT_DOWN_SMB)
|
|
goto exit;
|
|
|
|
/* Suspend input if SHDN threshold reached */
|
|
vote(chg->dc_suspend_votable, SW_THERM_REGULATION_VOTER,
|
|
suspend_input, 0);
|
|
vote(chg->usb_icl_votable, SW_THERM_REGULATION_VOTER,
|
|
suspend_input, 0);
|
|
smblib_dbg(chg, PR_MISC, "USB/DC %s as per SW thermal regulation\n",
|
|
suspend_input ? "suspended" : "unsuspended");
|
|
}
|
|
exit:
|
|
/*
|
|
* On USB suspend, WDOG IRQ stops triggering. To continue thermal
|
|
* monitoring and regulation until USB is plugged out, reschedule
|
|
* the SW thermal regulation work without releasing the wake lock.
|
|
*/
|
|
if (is_client_vote_enabled(chg->usb_icl_votable,
|
|
SW_THERM_REGULATION_VOTER)) {
|
|
schedule_delayed_work(&chg->thermal_regulation_work,
|
|
msecs_to_jiffies(THERM_REG_RECHECK_DELAY_1S));
|
|
return 0;
|
|
}
|
|
|
|
rc = smblib_masked_write(chg, SNARL_BARK_BITE_WD_CFG_REG,
|
|
SNARL_WDOG_TIMEOUT_MASK, wdog_timeout);
|
|
if (rc < 0)
|
|
smblib_err(chg, "Couldn't set WD SNARL timer, rc=%d\n", rc);
|
|
|
|
vote(chg->awake_votable, SW_THERM_REGULATION_VOTER, false, 0);
|
|
return rc;
|
|
}
|
|
|
|
/*******************
|
|
* DC PSY GETTERS *
|
|
*******************/
|
|
|
|
int smblib_get_prop_voltage_wls_output(struct smb_charger *chg,
|
|
union power_supply_propval *val)
|
|
{
|
|
int rc;
|
|
|
|
if (!chg->wls_psy) {
|
|
chg->wls_psy = power_supply_get_by_name("wireless");
|
|
if (!chg->wls_psy)
|
|
return -ENODEV;
|
|
}
|
|
|
|
rc = power_supply_get_property(chg->wls_psy,
|
|
POWER_SUPPLY_PROP_INPUT_VOLTAGE_REGULATION,
|
|
val);
|
|
if (rc < 0)
|
|
dev_err(chg->dev, "Couldn't get POWER_SUPPLY_PROP_VOLTAGE_REGULATION, rc=%d\n",
|
|
rc);
|
|
|
|
return rc;
|
|
}
|
|
|
|
int smblib_get_prop_dc_present(struct smb_charger *chg,
|
|
union power_supply_propval *val)
|
|
{
|
|
int rc;
|
|
u8 stat;
|
|
|
|
if (chg->chg_param.smb_version == PMI632_SUBTYPE) {
|
|
val->intval = 0;
|
|
return 0;
|
|
}
|
|
|
|
rc = smblib_read(chg, DCIN_BASE + INT_RT_STS_OFFSET, &stat);
|
|
if (rc < 0) {
|
|
smblib_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;
|
|
}
|
|
|
|
int smblib_get_prop_dc_online(struct smb_charger *chg,
|
|
union power_supply_propval *val)
|
|
{
|
|
int rc = 0;
|
|
u8 stat;
|
|
|
|
if (chg->chg_param.smb_version == PMI632_SUBTYPE) {
|
|
val->intval = 0;
|
|
return 0;
|
|
}
|
|
|
|
if (get_client_vote(chg->dc_suspend_votable, USER_VOTER)) {
|
|
val->intval = false;
|
|
return rc;
|
|
}
|
|
|
|
if (is_client_vote_enabled(chg->dc_suspend_votable,
|
|
CHG_TERMINATION_VOTER)) {
|
|
rc = smblib_get_prop_dc_present(chg, val);
|
|
return rc;
|
|
}
|
|
|
|
rc = smblib_read(chg, POWER_PATH_STATUS_REG, &stat);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't read POWER_PATH_STATUS rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
smblib_dbg(chg, PR_REGISTER, "POWER_PATH_STATUS = 0x%02x\n",
|
|
stat);
|
|
|
|
val->intval = (stat & USE_DCIN_BIT) &&
|
|
(stat & VALID_INPUT_POWER_SOURCE_STS_BIT);
|
|
|
|
return rc;
|
|
}
|
|
|
|
int smblib_get_prop_dc_current_max(struct smb_charger *chg,
|
|
union power_supply_propval *val)
|
|
{
|
|
#ifdef QCOM_BASE
|
|
return smblib_get_charge_param(chg, &chg->param.dc_icl, &val->intval);
|
|
#else
|
|
val->intval = get_effective_result_locked(chg->dc_icl_votable);
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
int smblib_get_prop_dc_voltage_max(struct smb_charger *chg,
|
|
union power_supply_propval *val)
|
|
{
|
|
int rc;
|
|
val->intval = MICRO_12V;
|
|
|
|
if (!chg->wls_psy)
|
|
chg->wls_psy = power_supply_get_by_name("wireless");
|
|
|
|
if (chg->wls_psy) {
|
|
rc = power_supply_get_property(chg->wls_psy,
|
|
POWER_SUPPLY_PROP_VOLTAGE_MAX,
|
|
val);
|
|
if (rc < 0) {
|
|
dev_err(chg->dev, "Couldn't get VOLTAGE_MAX, rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef QCOM_BASE
|
|
int smblib_get_prop_dc_voltage_now(struct smb_charger *chg,
|
|
union power_supply_propval *val)
|
|
{
|
|
int rc;
|
|
|
|
if (!chg->wls_psy) {
|
|
chg->wls_psy = power_supply_get_by_name("wireless");
|
|
if (!chg->wls_psy)
|
|
return -ENODEV;
|
|
}
|
|
|
|
rc = power_supply_get_property(chg->wls_psy,
|
|
POWER_SUPPLY_PROP_INPUT_VOLTAGE_REGULATION,
|
|
val);
|
|
if (rc < 0) {
|
|
dev_err(chg->dev, "Couldn't get POWER_SUPPLY_PROP_VOLTAGE_REGULATION, rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
#endif
|
|
/*******************
|
|
* DC PSY SETTERS *
|
|
*******************/
|
|
|
|
int smblib_set_prop_dc_current_max(struct smb_charger *chg,
|
|
const union power_supply_propval *val)
|
|
{
|
|
#ifdef QCOM_BASE
|
|
chg->dcin_icl_user_set = true;
|
|
return smblib_set_charge_param(chg, &chg->param.dc_icl, val->intval);
|
|
#else
|
|
int rc = 0;
|
|
|
|
rc = vote(chg->dc_icl_votable, USER_VOTER, true, val->intval);
|
|
return rc;
|
|
#endif
|
|
}
|
|
|
|
#define DCIN_AICL_RERUN_DELAY_MS 5000
|
|
int smblib_set_prop_voltage_wls_output(struct smb_charger *chg,
|
|
const union power_supply_propval *val)
|
|
{
|
|
int rc;
|
|
|
|
if (!chg->wls_psy) {
|
|
chg->wls_psy = power_supply_get_by_name("wireless");
|
|
if (!chg->wls_psy)
|
|
return -ENODEV;
|
|
}
|
|
|
|
rc = power_supply_set_property(chg->wls_psy,
|
|
POWER_SUPPLY_PROP_INPUT_VOLTAGE_REGULATION,
|
|
val);
|
|
if (rc < 0)
|
|
dev_err(chg->dev, "Couldn't set POWER_SUPPLY_PROP_VOLTAGE_REGULATION, rc=%d\n",
|
|
rc);
|
|
|
|
smblib_dbg(chg, PR_WLS, "%d\n", val->intval);
|
|
|
|
/*
|
|
* When WLS VOUT goes down, the power-constrained adaptor may be able
|
|
* to supply more current, so allow it to do so - unless userspace has
|
|
* changed DCIN ICL value already due to thermal considerations.
|
|
*/
|
|
if (!chg->dcin_icl_user_set && (val->intval > 0) &&
|
|
(val->intval < chg->last_wls_vout)) {
|
|
alarm_start_relative(&chg->dcin_aicl_alarm,
|
|
ms_to_ktime(DCIN_AICL_RERUN_DELAY_MS));
|
|
}
|
|
|
|
chg->last_wls_vout = val->intval;
|
|
|
|
return rc;
|
|
}
|
|
|
|
int smblib_set_prop_dc_reset(struct smb_charger *chg)
|
|
{
|
|
int rc;
|
|
|
|
rc = vote(chg->dc_suspend_votable, VOUT_VOTER, true, 0);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't suspend DC rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
rc = smblib_masked_write(chg, DCIN_CMD_IL_REG, DCIN_EN_MASK,
|
|
DCIN_EN_OVERRIDE_BIT);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't set DCIN_EN_OVERRIDE_BIT rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
rc = smblib_write(chg, DCIN_CMD_PON_REG, DCIN_PON_BIT | MID_CHG_BIT);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't write %d to DCIN_CMD_PON_REG rc=%d\n",
|
|
DCIN_PON_BIT | MID_CHG_BIT, rc);
|
|
return rc;
|
|
}
|
|
|
|
/* Wait for 10ms to allow the charge to get drained */
|
|
usleep_range(10000, 10010);
|
|
|
|
rc = smblib_write(chg, DCIN_CMD_PON_REG, 0);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't clear DCIN_CMD_PON_REG rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
rc = smblib_masked_write(chg, DCIN_CMD_IL_REG, DCIN_EN_MASK, 0);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't clear DCIN_EN_OVERRIDE_BIT rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
rc = vote(chg->dc_suspend_votable, VOUT_VOTER, false, 0);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't unsuspend DC rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
smblib_dbg(chg, PR_MISC, "Wireless charger removal detection successful\n");
|
|
return rc;
|
|
}
|
|
|
|
/*******************
|
|
* USB PSY GETTERS *
|
|
*******************/
|
|
|
|
int smblib_get_prop_usb_present(struct smb_charger *chg,
|
|
union power_supply_propval *val)
|
|
{
|
|
int rc;
|
|
u8 stat;
|
|
|
|
rc = smblib_read(chg, USBIN_BASE + INT_RT_STS_OFFSET, &stat);
|
|
if (rc < 0) {
|
|
smblib_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;
|
|
}
|
|
|
|
int smblib_get_prop_usb_online(struct smb_charger *chg,
|
|
union power_supply_propval *val)
|
|
{
|
|
int rc = 0;
|
|
#ifdef QCOM_BASE
|
|
u8 stat;
|
|
|
|
if (get_client_vote_locked(chg->usb_icl_votable, USER_VOTER) == 0) {
|
|
val->intval = false;
|
|
return rc;
|
|
}
|
|
|
|
if (is_client_vote_enabled_locked(chg->usb_icl_votable,
|
|
CHG_TERMINATION_VOTER)) {
|
|
rc = smblib_get_prop_usb_present(chg, val);
|
|
return rc;
|
|
}
|
|
|
|
rc = smblib_read(chg, POWER_PATH_STATUS_REG, &stat);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't read POWER_PATH_STATUS rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
smblib_dbg(chg, PR_REGISTER, "POWER_PATH_STATUS = 0x%02x\n",
|
|
stat);
|
|
|
|
val->intval = (stat & USE_USBIN_BIT) &&
|
|
(stat & VALID_INPUT_POWER_SOURCE_STS_BIT);
|
|
#else
|
|
rc = smblib_get_prop_usb_present(chg, val);
|
|
#endif
|
|
return rc;
|
|
}
|
|
|
|
int smblib_get_usb_online(struct smb_charger *chg,
|
|
union power_supply_propval *val)
|
|
{
|
|
int rc;
|
|
|
|
rc = smblib_get_prop_usb_online(chg, val);
|
|
if (!val->intval)
|
|
goto exit;
|
|
|
|
if ( (chg->real_charger_type == POWER_SUPPLY_TYPE_USB) ||
|
|
(chg->pd_active && chg->pd &&
|
|
(usbpd_get_current_dr(chg->pd) == 1)))
|
|
val->intval = 0;
|
|
else
|
|
val->intval = 1;
|
|
if (chg->real_charger_type == POWER_SUPPLY_TYPE_UNKNOWN)
|
|
val->intval = 0;
|
|
|
|
exit:
|
|
return rc;
|
|
}
|
|
|
|
int smblib_get_prop_usb_voltage_max_design(struct smb_charger *chg,
|
|
union power_supply_propval *val)
|
|
{
|
|
switch (chg->real_charger_type) {
|
|
case POWER_SUPPLY_TYPE_USB_HVDCP:
|
|
if (chg->qc2_unsupported_voltage == QC2_NON_COMPLIANT_9V) {
|
|
val->intval = MICRO_5V;
|
|
break;
|
|
} else if (chg->qc2_unsupported_voltage ==
|
|
QC2_NON_COMPLIANT_12V) {
|
|
val->intval = MICRO_9V;
|
|
break;
|
|
}
|
|
/* else, fallthrough */
|
|
case POWER_SUPPLY_TYPE_USB_HVDCP_3P5:
|
|
case POWER_SUPPLY_TYPE_USB_HVDCP_3:
|
|
case POWER_SUPPLY_TYPE_USB_PD:
|
|
if ((chg->chg_param.smb_version == PMI632_SUBTYPE) ||
|
|
(chg->chg_param.smb_version == PM6150_SUBTYPE))
|
|
val->intval = MICRO_9V;
|
|
else
|
|
val->intval = MICRO_12V;
|
|
break;
|
|
default:
|
|
val->intval = MICRO_5V;
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int smblib_get_prop_usb_voltage_max(struct smb_charger *chg,
|
|
union power_supply_propval *val)
|
|
{
|
|
switch (chg->real_charger_type) {
|
|
case POWER_SUPPLY_TYPE_USB_HVDCP:
|
|
if (chg->qc2_unsupported_voltage == QC2_NON_COMPLIANT_9V) {
|
|
val->intval = MICRO_5V;
|
|
break;
|
|
} else if (chg->qc2_unsupported_voltage ==
|
|
QC2_NON_COMPLIANT_12V) {
|
|
val->intval = MICRO_9V;
|
|
break;
|
|
}
|
|
/* else, fallthrough */
|
|
case POWER_SUPPLY_TYPE_USB_HVDCP_3P5:
|
|
case POWER_SUPPLY_TYPE_USB_HVDCP_3:
|
|
if (chg->chg_param.smb_version == PMI632_SUBTYPE)
|
|
val->intval = MICRO_9V;
|
|
else
|
|
val->intval = MICRO_12V;
|
|
break;
|
|
case POWER_SUPPLY_TYPE_USB_PD:
|
|
val->intval = chg->voltage_max_uv;
|
|
break;
|
|
default:
|
|
val->intval = MICRO_5V;
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#define HVDCP3_STEP_UV 200000
|
|
#define HVDCP3P5_STEP_UV 20000
|
|
static int smblib_estimate_adaptor_voltage(struct smb_charger *chg,
|
|
union power_supply_propval *val)
|
|
{
|
|
int step_uv = HVDCP3_STEP_UV;
|
|
|
|
switch (chg->real_charger_type) {
|
|
case POWER_SUPPLY_TYPE_USB_HVDCP:
|
|
val->intval = MICRO_12V;
|
|
break;
|
|
case POWER_SUPPLY_TYPE_USB_HVDCP_3P5:
|
|
step_uv = HVDCP3P5_STEP_UV;
|
|
case POWER_SUPPLY_TYPE_USB_HVDCP_3:
|
|
val->intval = MICRO_5V + (step_uv * chg->pulse_cnt);
|
|
break;
|
|
case POWER_SUPPLY_TYPE_USB_PD:
|
|
/* Take the average of min and max values */
|
|
val->intval = chg->voltage_min_uv +
|
|
((chg->voltage_max_uv - chg->voltage_min_uv) / 2);
|
|
break;
|
|
default:
|
|
val->intval = MICRO_5V;
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int smblib_read_mid_voltage_chan(struct smb_charger *chg,
|
|
union power_supply_propval *val)
|
|
{
|
|
int rc;
|
|
|
|
if (!chg->iio.mid_chan)
|
|
return -ENODATA;
|
|
|
|
rc = iio_read_channel_processed(chg->iio.mid_chan, &val->intval);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't read MID channel rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
* If MID voltage < 1V, it is unreliable.
|
|
* Figure out voltage from registers and calculations.
|
|
*/
|
|
if (val->intval < 1000000)
|
|
return smblib_estimate_adaptor_voltage(chg, val);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int smblib_read_usbin_voltage_chan(struct smb_charger *chg,
|
|
union power_supply_propval *val)
|
|
{
|
|
int rc;
|
|
|
|
if (!chg->iio.usbin_v_chan)
|
|
return -ENODATA;
|
|
|
|
rc = iio_read_channel_processed(chg->iio.usbin_v_chan, &val->intval);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't read USBIN channel rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int smblib_get_prop_usb_voltage_now(struct smb_charger *chg,
|
|
union power_supply_propval *val)
|
|
{
|
|
union power_supply_propval pval = {0, };
|
|
int rc, ret = 0;
|
|
u8 reg, adc_ch_reg;
|
|
|
|
mutex_lock(&chg->adc_lock);
|
|
|
|
if (chg->wa_flags & USBIN_ADC_WA) {
|
|
/* Store ADC channel config in order to restore later */
|
|
rc = smblib_read(chg, BATIF_ADC_CHANNEL_EN_REG, &adc_ch_reg);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't read ADC config rc=%d\n", rc);
|
|
ret = rc;
|
|
goto unlock;
|
|
}
|
|
|
|
/* Disable all ADC channels except IBAT channel */
|
|
rc = smblib_write(chg, BATIF_ADC_CHANNEL_EN_REG,
|
|
IBATT_CHANNEL_EN_BIT);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't disable ADC channels rc=%d\n",
|
|
rc);
|
|
ret = rc;
|
|
goto unlock;
|
|
}
|
|
}
|
|
|
|
rc = smblib_get_prop_usb_present(chg, &pval);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't get usb presence status rc=%d\n", rc);
|
|
ret = -ENODATA;
|
|
goto restore_adc_config;
|
|
}
|
|
|
|
/*
|
|
* Skip reading voltage only if USB is not present and we are not in
|
|
* OTG mode.
|
|
*/
|
|
if (!pval.intval) {
|
|
rc = smblib_read(chg, DCDC_CMD_OTG_REG, ®);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't read CMD_OTG rc=%d", rc);
|
|
goto restore_adc_config;
|
|
}
|
|
|
|
if (!(reg & OTG_EN_BIT))
|
|
goto restore_adc_config;
|
|
}
|
|
|
|
/*
|
|
* For PM8150B, use MID_CHG ADC channel because overvoltage is observed
|
|
* to occur randomly in the USBIN channel, particularly at high
|
|
* voltages.
|
|
*/
|
|
if (chg->chg_param.smb_version == PM8150B_SUBTYPE)
|
|
rc = smblib_read_mid_voltage_chan(chg, val);
|
|
else
|
|
rc = smblib_read_usbin_voltage_chan(chg, val);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Failed to read USBIN over vadc, rc=%d\n", rc);
|
|
ret = rc;
|
|
}
|
|
|
|
restore_adc_config:
|
|
/* Restore ADC channel config */
|
|
if (chg->wa_flags & USBIN_ADC_WA) {
|
|
rc = smblib_write(chg, BATIF_ADC_CHANNEL_EN_REG, adc_ch_reg);
|
|
if (rc < 0)
|
|
smblib_err(chg, "Couldn't write ADC config rc=%d\n",
|
|
rc);
|
|
}
|
|
|
|
unlock:
|
|
mutex_unlock(&chg->adc_lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int smblib_get_prop_vph_voltage_now(struct smb_charger *chg,
|
|
union power_supply_propval *val)
|
|
{
|
|
int rc;
|
|
|
|
if (!chg->iio.vph_v_chan)
|
|
return -ENODATA;
|
|
|
|
rc = iio_read_channel_processed(chg->iio.vph_v_chan, &val->intval);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't read vph channel rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
bool smblib_rsbux_low(struct smb_charger *chg, int r_thr)
|
|
{
|
|
int r_sbu1, r_sbu2;
|
|
bool ret = false;
|
|
int rc;
|
|
|
|
if (!chg->iio.sbux_chan)
|
|
return false;
|
|
|
|
/* disable crude sensors */
|
|
rc = smblib_masked_write(chg, TYPE_C_CRUDE_SENSOR_CFG_REG,
|
|
EN_SRC_CRUDE_SENSOR_BIT | EN_SNK_CRUDE_SENSOR_BIT,
|
|
0);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't disable crude sensor rc=%d\n", rc);
|
|
return false;
|
|
}
|
|
|
|
/* select SBU1 as current source */
|
|
rc = smblib_write(chg, TYPE_C_SBU_CFG_REG, SEL_SBU1_ISRC_VAL);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't select SBU1 rc=%d\n", rc);
|
|
goto cleanup;
|
|
}
|
|
|
|
rc = iio_read_channel_processed(chg->iio.sbux_chan, &r_sbu1);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't read SBU1 rc=%d\n", rc);
|
|
goto cleanup;
|
|
}
|
|
|
|
if (r_sbu1 < r_thr) {
|
|
ret = true;
|
|
goto cleanup;
|
|
}
|
|
|
|
/* select SBU2 as current source */
|
|
rc = smblib_write(chg, TYPE_C_SBU_CFG_REG, SEL_SBU2_ISRC_VAL);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't select SBU1 rc=%d\n", rc);
|
|
goto cleanup;
|
|
}
|
|
|
|
rc = iio_read_channel_processed(chg->iio.sbux_chan, &r_sbu2);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't read SBU1 rc=%d\n", rc);
|
|
goto cleanup;
|
|
}
|
|
|
|
if (r_sbu2 < r_thr)
|
|
ret = true;
|
|
cleanup:
|
|
/* enable crude sensors */
|
|
rc = smblib_masked_write(chg, TYPE_C_CRUDE_SENSOR_CFG_REG,
|
|
EN_SRC_CRUDE_SENSOR_BIT | EN_SNK_CRUDE_SENSOR_BIT,
|
|
EN_SRC_CRUDE_SENSOR_BIT | EN_SNK_CRUDE_SENSOR_BIT);
|
|
if (rc < 0)
|
|
smblib_err(chg, "Couldn't enable crude sensor rc=%d\n", rc);
|
|
|
|
/* disable current source */
|
|
rc = smblib_write(chg, TYPE_C_SBU_CFG_REG, 0);
|
|
if (rc < 0)
|
|
smblib_err(chg, "Couldn't select SBU1 rc=%d\n", rc);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int smblib_get_prop_charger_temp(struct smb_charger *chg,
|
|
union power_supply_propval *val)
|
|
{
|
|
int temp, rc;
|
|
int input_present;
|
|
|
|
rc = smblib_is_input_present(chg, &input_present);
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
if (input_present == INPUT_NOT_PRESENT)
|
|
return -ENODATA;
|
|
|
|
if (chg->iio.temp_chan) {
|
|
rc = iio_read_channel_processed(chg->iio.temp_chan,
|
|
&temp);
|
|
if (rc < 0) {
|
|
pr_err("Error in reading temp channel, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
val->intval = temp / 100;
|
|
} else {
|
|
return -ENODATA;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
int smblib_get_prop_typec_cc_orientation(struct smb_charger *chg,
|
|
union power_supply_propval *val)
|
|
{
|
|
int rc = 0;
|
|
u8 stat;
|
|
|
|
val->intval = 0;
|
|
|
|
if (chg->connector_type == POWER_SUPPLY_CONNECTOR_MICRO_USB)
|
|
return 0;
|
|
|
|
rc = smblib_read(chg, TYPE_C_MISC_STATUS_REG, &stat);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't read TYPE_C_STATUS_4 rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
smblib_dbg(chg, PR_REGISTER, "TYPE_C_STATUS_4 = 0x%02x\n", stat);
|
|
|
|
if (stat & CC_ATTACHED_BIT)
|
|
val->intval = (bool)(stat & CC_ORIENTATION_BIT) + 1;
|
|
|
|
return rc;
|
|
}
|
|
|
|
static const char * const smblib_typec_mode_name[] = {
|
|
[POWER_SUPPLY_TYPEC_NONE] = "NONE",
|
|
[POWER_SUPPLY_TYPEC_SOURCE_DEFAULT] = "SOURCE_DEFAULT",
|
|
[POWER_SUPPLY_TYPEC_SOURCE_MEDIUM] = "SOURCE_MEDIUM",
|
|
[POWER_SUPPLY_TYPEC_SOURCE_HIGH] = "SOURCE_HIGH",
|
|
[POWER_SUPPLY_TYPEC_NON_COMPLIANT] = "NON_COMPLIANT",
|
|
[POWER_SUPPLY_TYPEC_SINK] = "SINK",
|
|
[POWER_SUPPLY_TYPEC_SINK_POWERED_CABLE] = "SINK_POWERED_CABLE",
|
|
[POWER_SUPPLY_TYPEC_SINK_DEBUG_ACCESSORY] = "SINK_DEBUG_ACCESSORY",
|
|
[POWER_SUPPLY_TYPEC_SINK_AUDIO_ADAPTER] = "SINK_AUDIO_ADAPTER",
|
|
[POWER_SUPPLY_TYPEC_POWERED_CABLE_ONLY] = "POWERED_CABLE_ONLY",
|
|
};
|
|
|
|
static int smblib_get_prop_ufp_mode(struct smb_charger *chg)
|
|
{
|
|
int rc;
|
|
u8 stat;
|
|
|
|
rc = smblib_read(chg, TYPE_C_SNK_STATUS_REG, &stat);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't read TYPE_C_STATUS_1 rc=%d\n", rc);
|
|
return POWER_SUPPLY_TYPEC_NONE;
|
|
}
|
|
smblib_dbg(chg, PR_REGISTER, "TYPE_C_STATUS_1 = 0x%02x\n", stat);
|
|
|
|
switch (stat & DETECTED_SRC_TYPE_MASK) {
|
|
case SNK_DAM_500MA_BIT:
|
|
case SNK_RP_STD_BIT:
|
|
return POWER_SUPPLY_TYPEC_SOURCE_DEFAULT;
|
|
case SNK_DAM_1500MA_BIT:
|
|
case SNK_RP_1P5_BIT:
|
|
return POWER_SUPPLY_TYPEC_SOURCE_MEDIUM;
|
|
case SNK_DAM_3000MA_BIT:
|
|
case SNK_RP_3P0_BIT:
|
|
return POWER_SUPPLY_TYPEC_SOURCE_HIGH;
|
|
case SNK_RP_SHORT_BIT:
|
|
return POWER_SUPPLY_TYPEC_NON_COMPLIANT;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return POWER_SUPPLY_TYPEC_NONE;
|
|
}
|
|
|
|
static int smblib_get_prop_dfp_mode(struct smb_charger *chg)
|
|
{
|
|
int rc;
|
|
u8 stat;
|
|
|
|
if (chg->lpd_stage == LPD_STAGE_COMMIT)
|
|
return POWER_SUPPLY_TYPEC_NONE;
|
|
|
|
rc = smblib_read(chg, TYPE_C_SRC_STATUS_REG, &stat);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't read TYPE_C_SRC_STATUS_REG rc=%d\n",
|
|
rc);
|
|
return POWER_SUPPLY_TYPEC_NONE;
|
|
}
|
|
smblib_dbg(chg, PR_REGISTER, "TYPE_C_SRC_STATUS_REG = 0x%02x\n", stat);
|
|
|
|
switch (stat & DETECTED_SNK_TYPE_MASK) {
|
|
case AUDIO_ACCESS_RA_RA_BIT:
|
|
return POWER_SUPPLY_TYPEC_SINK_AUDIO_ADAPTER;
|
|
case SRC_DEBUG_ACCESS_BIT:
|
|
return POWER_SUPPLY_TYPEC_SINK_DEBUG_ACCESSORY;
|
|
case SRC_RD_RA_VCONN_BIT:
|
|
return POWER_SUPPLY_TYPEC_SINK_POWERED_CABLE;
|
|
case SRC_RD_OPEN_BIT:
|
|
return POWER_SUPPLY_TYPEC_SINK;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return POWER_SUPPLY_TYPEC_NONE;
|
|
}
|
|
|
|
static int smblib_get_prop_typec_mode(struct smb_charger *chg)
|
|
{
|
|
int rc;
|
|
u8 stat;
|
|
|
|
rc = smblib_read(chg, TYPE_C_MISC_STATUS_REG, &stat);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't read TYPE_C_MISC_STATUS_REG rc=%d\n",
|
|
rc);
|
|
return 0;
|
|
}
|
|
smblib_dbg(chg, PR_REGISTER, "TYPE_C_MISC_STATUS_REG = 0x%02x\n", stat);
|
|
|
|
if (stat & SNK_SRC_MODE_BIT)
|
|
return smblib_get_prop_dfp_mode(chg);
|
|
else
|
|
return smblib_get_prop_ufp_mode(chg);
|
|
}
|
|
|
|
inline int smblib_get_usb_prop_typec_mode(struct smb_charger *chg,
|
|
union power_supply_propval *val)
|
|
{
|
|
if (chg->connector_type == POWER_SUPPLY_CONNECTOR_MICRO_USB)
|
|
val->intval = POWER_SUPPLY_TYPEC_NONE;
|
|
else
|
|
val->intval = chg->typec_mode;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int smblib_get_prop_typec_power_role(struct smb_charger *chg,
|
|
union power_supply_propval *val)
|
|
{
|
|
int rc = 0;
|
|
u8 ctrl;
|
|
|
|
if (chg->connector_type == POWER_SUPPLY_CONNECTOR_MICRO_USB) {
|
|
val->intval = POWER_SUPPLY_TYPEC_PR_NONE;
|
|
return 0;
|
|
}
|
|
|
|
spin_lock(&chg->typec_pr_lock);
|
|
rc = smblib_read(chg, TYPE_C_MODE_CFG_REG, &ctrl);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't read TYPE_C_MODE_CFG_REG rc=%d\n",
|
|
rc);
|
|
goto unlock;
|
|
}
|
|
smblib_dbg(chg, PR_REGISTER, "TYPE_C_MODE_CFG_REG = 0x%02x\n",
|
|
ctrl);
|
|
|
|
if (ctrl & TYPEC_DISABLE_CMD_BIT) {
|
|
val->intval = POWER_SUPPLY_TYPEC_PR_NONE;
|
|
goto unlock;
|
|
}
|
|
|
|
switch (ctrl & (EN_SRC_ONLY_BIT | EN_SNK_ONLY_BIT)) {
|
|
case 0:
|
|
val->intval = POWER_SUPPLY_TYPEC_PR_DUAL;
|
|
break;
|
|
case EN_SRC_ONLY_BIT:
|
|
val->intval = POWER_SUPPLY_TYPEC_PR_SOURCE;
|
|
break;
|
|
case EN_SNK_ONLY_BIT:
|
|
val->intval = POWER_SUPPLY_TYPEC_PR_SINK;
|
|
break;
|
|
default:
|
|
val->intval = POWER_SUPPLY_TYPEC_PR_NONE;
|
|
smblib_err(chg, "unsupported power role 0x%02lx\n",
|
|
ctrl & (EN_SRC_ONLY_BIT | EN_SNK_ONLY_BIT));
|
|
rc = -EINVAL;
|
|
goto unlock;
|
|
}
|
|
|
|
chg->power_role = val->intval;
|
|
unlock:
|
|
spin_unlock(&chg->typec_pr_lock);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static inline bool typec_in_src_mode(struct smb_charger *chg)
|
|
{
|
|
return (chg->typec_mode > POWER_SUPPLY_TYPEC_NONE &&
|
|
chg->typec_mode < POWER_SUPPLY_TYPEC_SOURCE_DEFAULT);
|
|
}
|
|
|
|
int smblib_get_prop_typec_select_rp(struct smb_charger *chg,
|
|
union power_supply_propval *val)
|
|
{
|
|
int rc, rp;
|
|
u8 stat;
|
|
|
|
if (!typec_in_src_mode(chg))
|
|
return -ENODATA;
|
|
|
|
rc = smblib_read(chg, TYPE_C_CURRSRC_CFG_REG, &stat);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't read TYPE_C_CURRSRC_CFG_REG rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
switch (stat & TYPEC_SRC_RP_SEL_MASK) {
|
|
case TYPEC_SRC_RP_STD:
|
|
rp = POWER_SUPPLY_TYPEC_SRC_RP_STD;
|
|
break;
|
|
case TYPEC_SRC_RP_1P5A:
|
|
rp = POWER_SUPPLY_TYPEC_SRC_RP_1P5A;
|
|
break;
|
|
case TYPEC_SRC_RP_3A:
|
|
case TYPEC_SRC_RP_3A_DUPLICATE:
|
|
rp = POWER_SUPPLY_TYPEC_SRC_RP_3A;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
val->intval = rp;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int smblib_get_prop_usb_current_now(struct smb_charger *chg,
|
|
union power_supply_propval *val)
|
|
{
|
|
union power_supply_propval pval = {0, };
|
|
int rc = 0, buck_scale = 1, boost_scale = 1;
|
|
|
|
if (chg->iio.usbin_i_chan) {
|
|
rc = iio_read_channel_processed(chg->iio.usbin_i_chan,
|
|
&val->intval);
|
|
if (rc < 0) {
|
|
pr_err("Error in reading USBIN_I channel, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
* For PM8150B, scaling factor = reciprocal of
|
|
* 0.2V/A in Buck mode, 0.4V/A in Boost mode.
|
|
* For PMI632, scaling factor = reciprocal of
|
|
* 0.4V/A in Buck mode, 0.8V/A in Boost mode.
|
|
*/
|
|
switch (chg->chg_param.smb_version) {
|
|
case PMI632_SUBTYPE:
|
|
buck_scale = 40;
|
|
boost_scale = 80;
|
|
break;
|
|
default:
|
|
buck_scale = 20;
|
|
boost_scale = 40;
|
|
break;
|
|
}
|
|
|
|
if (chg->otg_present || smblib_get_prop_dfp_mode(chg) !=
|
|
POWER_SUPPLY_TYPEC_NONE) {
|
|
val->intval = DIV_ROUND_CLOSEST(val->intval * 100,
|
|
boost_scale);
|
|
return rc;
|
|
}
|
|
|
|
rc = smblib_get_prop_usb_present(chg, &pval);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't get usb present status,rc=%d\n",
|
|
rc);
|
|
return -ENODATA;
|
|
}
|
|
|
|
/* If USB is not present, return 0 */
|
|
if (!pval.intval)
|
|
val->intval = 0;
|
|
else
|
|
val->intval = DIV_ROUND_CLOSEST(val->intval * 100,
|
|
buck_scale);
|
|
} else {
|
|
val->intval = 0;
|
|
rc = -ENODATA;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
int smblib_get_prop_low_power(struct smb_charger *chg,
|
|
union power_supply_propval *val)
|
|
{
|
|
int rc;
|
|
u8 stat;
|
|
|
|
if (chg->sink_src_mode != SRC_MODE)
|
|
return -ENODATA;
|
|
|
|
rc = smblib_read(chg, TYPE_C_SRC_STATUS_REG, &stat);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't read TYPE_C_SRC_STATUS_REG rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
val->intval = !(stat & SRC_HIGH_BATT_BIT);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int smblib_get_prop_input_current_settled(struct smb_charger *chg,
|
|
union power_supply_propval *val)
|
|
{
|
|
return smblib_get_charge_param(chg, &chg->param.icl_stat, &val->intval);
|
|
}
|
|
|
|
int smblib_get_prop_input_current_max(struct smb_charger *chg,
|
|
union power_supply_propval *val)
|
|
{
|
|
int icl_ua = 0, rc;
|
|
|
|
rc = smblib_get_charge_param(chg, &chg->param.usb_icl, &icl_ua);
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
if (is_override_vote_enabled_locked(chg->usb_icl_votable) &&
|
|
icl_ua < USBIN_1000MA) {
|
|
val->intval = USBIN_1000MA;
|
|
return 0;
|
|
}
|
|
|
|
return smblib_get_charge_param(chg, &chg->param.icl_stat, &val->intval);
|
|
}
|
|
|
|
int smblib_get_prop_input_voltage_settled(struct smb_charger *chg,
|
|
union power_supply_propval *val)
|
|
{
|
|
int rc, pulses;
|
|
int step_uv = HVDCP3_STEP_UV;
|
|
|
|
switch (chg->real_charger_type) {
|
|
case POWER_SUPPLY_TYPE_USB_HVDCP_3P5:
|
|
step_uv = HVDCP3P5_STEP_UV;
|
|
case POWER_SUPPLY_TYPE_USB_HVDCP_3:
|
|
rc = smblib_get_pulse_cnt(chg, &pulses);
|
|
if (rc < 0) {
|
|
smblib_err(chg,
|
|
"Couldn't read QC_PULSE_COUNT rc=%d\n", rc);
|
|
return 0;
|
|
}
|
|
val->intval = MICRO_5V + step_uv * pulses;
|
|
break;
|
|
case POWER_SUPPLY_TYPE_USB_PD:
|
|
val->intval = chg->voltage_min_uv;
|
|
break;
|
|
default:
|
|
val->intval = MICRO_5V;
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int smblib_get_prop_pd_in_hard_reset(struct smb_charger *chg,
|
|
union power_supply_propval *val)
|
|
{
|
|
val->intval = chg->pd_hard_reset;
|
|
return 0;
|
|
}
|
|
|
|
int smblib_get_pe_start(struct smb_charger *chg,
|
|
union power_supply_propval *val)
|
|
{
|
|
val->intval = chg->ok_to_pd;
|
|
return 0;
|
|
}
|
|
|
|
int smblib_get_prop_smb_health(struct smb_charger *chg)
|
|
{
|
|
int rc;
|
|
int input_present;
|
|
union power_supply_propval prop = {0, };
|
|
|
|
rc = smblib_is_input_present(chg, &input_present);
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
if ((input_present == INPUT_NOT_PRESENT) || (!is_cp_available(chg)))
|
|
return POWER_SUPPLY_HEALTH_UNKNOWN;
|
|
|
|
rc = power_supply_get_property(chg->cp_psy,
|
|
POWER_SUPPLY_PROP_CP_DIE_TEMP, &prop);
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
if (prop.intval > SMB_TEMP_RST_THRESH)
|
|
return POWER_SUPPLY_HEALTH_OVERHEAT;
|
|
|
|
if (prop.intval > SMB_TEMP_REG_H_THRESH)
|
|
return POWER_SUPPLY_HEALTH_HOT;
|
|
|
|
if (prop.intval > SMB_TEMP_REG_L_THRESH)
|
|
return POWER_SUPPLY_HEALTH_WARM;
|
|
|
|
return POWER_SUPPLY_HEALTH_COOL;
|
|
}
|
|
|
|
int smblib_get_prop_die_health(struct smb_charger *chg)
|
|
{
|
|
int rc;
|
|
u8 stat;
|
|
int input_present;
|
|
|
|
rc = smblib_is_input_present(chg, &input_present);
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
if (input_present == INPUT_NOT_PRESENT)
|
|
return POWER_SUPPLY_HEALTH_UNKNOWN;
|
|
|
|
if (chg->wa_flags & SW_THERM_REGULATION_WA) {
|
|
if (chg->die_temp == -ENODATA)
|
|
return POWER_SUPPLY_HEALTH_UNKNOWN;
|
|
|
|
if (chg->die_temp > DIE_TEMP_RST_THRESH)
|
|
return POWER_SUPPLY_HEALTH_OVERHEAT;
|
|
|
|
if (chg->die_temp > DIE_TEMP_REG_H_THRESH)
|
|
return POWER_SUPPLY_HEALTH_HOT;
|
|
|
|
if (chg->die_temp > DIE_TEMP_REG_L_THRESH)
|
|
return POWER_SUPPLY_HEALTH_WARM;
|
|
|
|
return POWER_SUPPLY_HEALTH_COOL;
|
|
}
|
|
|
|
rc = smblib_read(chg, DIE_TEMP_STATUS_REG, &stat);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't read DIE_TEMP_STATUS_REG, rc=%d\n",
|
|
rc);
|
|
return POWER_SUPPLY_HEALTH_UNKNOWN;
|
|
}
|
|
|
|
if (stat & DIE_TEMP_RST_BIT)
|
|
return POWER_SUPPLY_HEALTH_OVERHEAT;
|
|
|
|
if (stat & DIE_TEMP_UB_BIT)
|
|
return POWER_SUPPLY_HEALTH_HOT;
|
|
|
|
if (stat & DIE_TEMP_LB_BIT)
|
|
return POWER_SUPPLY_HEALTH_WARM;
|
|
|
|
return POWER_SUPPLY_HEALTH_COOL;
|
|
}
|
|
|
|
int smblib_get_die_health(struct smb_charger *chg,
|
|
union power_supply_propval *val)
|
|
{
|
|
if (chg->die_health == -EINVAL)
|
|
val->intval = smblib_get_prop_die_health(chg);
|
|
else
|
|
val->intval = chg->die_health;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int smblib_get_prop_scope(struct smb_charger *chg,
|
|
union power_supply_propval *val)
|
|
{
|
|
int rc;
|
|
union power_supply_propval pval;
|
|
|
|
val->intval = POWER_SUPPLY_SCOPE_UNKNOWN;
|
|
rc = smblib_get_prop_usb_present(chg, &pval);
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
val->intval = pval.intval ? POWER_SUPPLY_SCOPE_DEVICE
|
|
: chg->otg_present ? POWER_SUPPLY_SCOPE_SYSTEM
|
|
: POWER_SUPPLY_SCOPE_UNKNOWN;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int smblib_get_typec_connector_temp_status(struct smb_charger *chg)
|
|
{
|
|
int rc;
|
|
u8 stat;
|
|
|
|
if (chg->connector_health != -EINVAL)
|
|
return chg->connector_health;
|
|
|
|
if (chg->wa_flags & SW_THERM_REGULATION_WA) {
|
|
if (chg->connector_temp == -ENODATA)
|
|
return POWER_SUPPLY_HEALTH_UNKNOWN;
|
|
|
|
if (chg->connector_temp > CONNECTOR_TEMP_RST_THRESH)
|
|
return POWER_SUPPLY_HEALTH_OVERHEAT;
|
|
|
|
if (chg->connector_temp > CONNECTOR_TEMP_REG_H_THRESH)
|
|
return POWER_SUPPLY_HEALTH_HOT;
|
|
|
|
if (chg->connector_temp > CONNECTOR_TEMP_REG_L_THRESH)
|
|
return POWER_SUPPLY_HEALTH_WARM;
|
|
|
|
return POWER_SUPPLY_HEALTH_COOL;
|
|
}
|
|
|
|
rc = smblib_read(chg, CONNECTOR_TEMP_STATUS_REG, &stat);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't read CONNECTOR_TEMP_STATUS_REG, rc=%d\n",
|
|
rc);
|
|
return POWER_SUPPLY_HEALTH_UNKNOWN;
|
|
}
|
|
|
|
if (stat & CONNECTOR_TEMP_RST_BIT)
|
|
return POWER_SUPPLY_HEALTH_OVERHEAT;
|
|
|
|
if (stat & CONNECTOR_TEMP_UB_BIT)
|
|
return POWER_SUPPLY_HEALTH_HOT;
|
|
|
|
if (stat & CONNECTOR_TEMP_LB_BIT)
|
|
return POWER_SUPPLY_HEALTH_WARM;
|
|
|
|
return POWER_SUPPLY_HEALTH_COOL;
|
|
}
|
|
|
|
int smblib_get_skin_temp_status(struct smb_charger *chg)
|
|
{
|
|
int rc;
|
|
u8 stat;
|
|
|
|
if (!chg->en_skin_therm_mitigation)
|
|
return POWER_SUPPLY_HEALTH_UNKNOWN;
|
|
|
|
rc = smblib_read(chg, SKIN_TEMP_STATUS_REG, &stat);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't read SKIN_TEMP_STATUS_REG, rc=%d\n",
|
|
rc);
|
|
return POWER_SUPPLY_HEALTH_UNKNOWN;
|
|
}
|
|
|
|
if (stat & SKIN_TEMP_RST_BIT)
|
|
return POWER_SUPPLY_HEALTH_OVERHEAT;
|
|
|
|
if (stat & SKIN_TEMP_UB_BIT)
|
|
return POWER_SUPPLY_HEALTH_HOT;
|
|
|
|
if (stat & SKIN_TEMP_LB_BIT)
|
|
return POWER_SUPPLY_HEALTH_WARM;
|
|
|
|
return POWER_SUPPLY_HEALTH_COOL;
|
|
}
|
|
|
|
int smblib_get_prop_connector_health(struct smb_charger *chg)
|
|
{
|
|
bool dc_present, usb_present;
|
|
int input_present;
|
|
int rc;
|
|
|
|
rc = smblib_is_input_present(chg, &input_present);
|
|
if (rc < 0)
|
|
return POWER_SUPPLY_HEALTH_UNKNOWN;
|
|
|
|
dc_present = input_present & INPUT_PRESENT_DC;
|
|
usb_present = input_present & INPUT_PRESENT_USB;
|
|
|
|
if (usb_present)
|
|
return smblib_get_typec_connector_temp_status(chg);
|
|
|
|
/*
|
|
* In PM8150B, SKIN channel measures Wireless charger receiver
|
|
* temp, used to regulate DC ICL.
|
|
*/
|
|
if (chg->chg_param.smb_version == PM8150B_SUBTYPE && dc_present)
|
|
return smblib_get_skin_temp_status(chg);
|
|
|
|
return POWER_SUPPLY_HEALTH_COOL;
|
|
}
|
|
|
|
#ifdef QCOM_BASE
|
|
static int get_rp_based_dcp_current(struct smb_charger *chg, int typec_mode)
|
|
{
|
|
int rp_ua;
|
|
|
|
switch (typec_mode) {
|
|
case POWER_SUPPLY_TYPEC_SOURCE_HIGH:
|
|
rp_ua = TYPEC_HIGH_CURRENT_UA;
|
|
break;
|
|
case POWER_SUPPLY_TYPEC_SOURCE_MEDIUM:
|
|
case POWER_SUPPLY_TYPEC_SOURCE_DEFAULT:
|
|
/* fall through */
|
|
default:
|
|
rp_ua = DCP_CURRENT_UA;
|
|
}
|
|
|
|
return rp_ua;
|
|
}
|
|
#endif
|
|
|
|
/*******************
|
|
* USB PSY SETTERS *
|
|
* *****************/
|
|
|
|
int smblib_set_prop_pd_current_max(struct smb_charger *chg,
|
|
const union power_supply_propval *val)
|
|
{
|
|
int rc, icl;
|
|
|
|
#ifdef QCOM_BASE
|
|
if (chg->pd_active) {
|
|
icl = get_client_vote(chg->usb_icl_votable, PD_VOTER);
|
|
rc = vote(chg->usb_icl_votable, PD_VOTER, true, val->intval);
|
|
if (val->intval != icl)
|
|
power_supply_changed(chg->usb_psy);
|
|
} else {
|
|
rc = -EPERM;
|
|
}
|
|
#else
|
|
bool enable = true;
|
|
|
|
if (0 == val->intval)
|
|
enable = false;
|
|
|
|
if (chg->pd_active) {
|
|
icl = get_client_vote(chg->usb_icl_votable, PD_VOTER);
|
|
rc = vote(chg->usb_icl_votable, PD_VOTER, enable, val->intval);
|
|
if (val->intval != icl)
|
|
power_supply_changed(chg->usb_psy);
|
|
} else {
|
|
rc = -EPERM;
|
|
}
|
|
#endif
|
|
return rc;
|
|
}
|
|
|
|
#ifdef QCOM_BASE
|
|
static int smblib_handle_usb_current(struct smb_charger *chg,
|
|
int usb_current)
|
|
{
|
|
int rc = 0, rp_ua, typec_mode;
|
|
union power_supply_propval val = {0, };
|
|
|
|
if (chg->real_charger_type == POWER_SUPPLY_TYPE_USB_FLOAT) {
|
|
if (usb_current == -ETIMEDOUT) {
|
|
if ((chg->float_cfg & FLOAT_OPTIONS_MASK)
|
|
== FORCE_FLOAT_SDP_CFG_BIT) {
|
|
/*
|
|
* Confiugure USB500 mode if Float charger is
|
|
* configured for SDP mode.
|
|
*/
|
|
rc = vote(chg->usb_icl_votable,
|
|
SW_ICL_MAX_VOTER, true, USBIN_500MA);
|
|
if (rc < 0)
|
|
smblib_err(chg,
|
|
"Couldn't set SDP ICL rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
if (chg->connector_type ==
|
|
POWER_SUPPLY_CONNECTOR_TYPEC) {
|
|
/*
|
|
* Valid FLOAT charger, report the current
|
|
* based of Rp.
|
|
*/
|
|
typec_mode = smblib_get_prop_typec_mode(chg);
|
|
rp_ua = get_rp_based_dcp_current(chg,
|
|
typec_mode);
|
|
rc = vote(chg->usb_icl_votable,
|
|
SW_ICL_MAX_VOTER, true, rp_ua);
|
|
if (rc < 0)
|
|
return rc;
|
|
} else {
|
|
rc = vote(chg->usb_icl_votable,
|
|
SW_ICL_MAX_VOTER, true, DCP_CURRENT_UA);
|
|
if (rc < 0)
|
|
return rc;
|
|
}
|
|
} else {
|
|
/*
|
|
* FLOAT charger detected as SDP by USB driver,
|
|
* charge with the requested current and update the
|
|
* real_charger_type
|
|
*/
|
|
chg->real_charger_type = POWER_SUPPLY_TYPE_USB;
|
|
rc = vote(chg->usb_icl_votable, USB_PSY_VOTER,
|
|
true, usb_current);
|
|
if (rc < 0)
|
|
return rc;
|
|
rc = vote(chg->usb_icl_votable, SW_ICL_MAX_VOTER,
|
|
false, 0);
|
|
if (rc < 0)
|
|
return rc;
|
|
}
|
|
} else {
|
|
rc = smblib_get_prop_usb_present(chg, &val);
|
|
if (!rc && !val.intval)
|
|
return 0;
|
|
|
|
/* if flash is active force 500mA */
|
|
if ((usb_current < SDP_CURRENT_UA) && is_flash_active(chg))
|
|
usb_current = SDP_CURRENT_UA;
|
|
|
|
rc = vote(chg->usb_icl_votable, USB_PSY_VOTER, true,
|
|
usb_current);
|
|
if (rc < 0) {
|
|
pr_err("Couldn't vote ICL USB_PSY_VOTER rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
rc = vote(chg->usb_icl_votable, SW_ICL_MAX_VOTER, false, 0);
|
|
if (rc < 0) {
|
|
pr_err("Couldn't remove SW_ICL_MAX vote rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
int smblib_set_prop_sdp_current_max(struct smb_charger *chg,
|
|
const union power_supply_propval *val)
|
|
{
|
|
#ifdef QCOM_BASE
|
|
union power_supply_propval pval;
|
|
int rc = 0;
|
|
|
|
if (!chg->pd_active) {
|
|
rc = smblib_get_prop_usb_present(chg, &pval);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't get usb present rc = %d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
/* handle the request only when USB is present */
|
|
if (pval.intval)
|
|
rc = smblib_handle_usb_current(chg, val->intval);
|
|
} else if (chg->system_suspend_supported) {
|
|
if (val->intval <= USBIN_25MA)
|
|
rc = vote(chg->usb_icl_votable,
|
|
PD_SUSPEND_SUPPORTED_VOTER, true, val->intval);
|
|
else
|
|
rc = vote(chg->usb_icl_votable,
|
|
PD_SUSPEND_SUPPORTED_VOTER, false, 0);
|
|
}
|
|
#else
|
|
int rc = 0;
|
|
bool enable = true;
|
|
const struct apsd_result *apsd_result = smblib_get_apsd_result(chg);
|
|
|
|
if (0 >= val->intval)
|
|
enable = false;
|
|
|
|
if (!chg->pd_active &&
|
|
(smblib_get_prop_typec_mode(chg) ==
|
|
POWER_SUPPLY_TYPEC_SOURCE_DEFAULT) &&
|
|
(apsd_result->pst != POWER_SUPPLY_TYPE_USB_FLOAT)) {
|
|
rc = vote(chg->usb_icl_votable, USB_PSY_VOTER,
|
|
enable, val->intval);
|
|
} else if (chg->system_suspend_supported) {
|
|
if (val->intval <= USBIN_25MA)
|
|
rc = vote(chg->usb_icl_votable, USB_PSY_VOTER,
|
|
enable, val->intval);
|
|
else
|
|
rc = vote(chg->usb_icl_votable, USB_PSY_VOTER,
|
|
false, 0);
|
|
}
|
|
#endif
|
|
return rc;
|
|
}
|
|
|
|
int smblib_set_prop_boost_current(struct smb_charger *chg,
|
|
const union power_supply_propval *val)
|
|
{
|
|
int rc = 0;
|
|
|
|
rc = smblib_set_charge_param(chg, &chg->param.freq_switcher,
|
|
val->intval <= chg->boost_threshold_ua ?
|
|
chg->chg_freq.freq_below_otg_threshold :
|
|
chg->chg_freq.freq_above_otg_threshold);
|
|
if (rc < 0) {
|
|
dev_err(chg->dev, "Error in setting freq_boost rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
chg->boost_current_ua = val->intval;
|
|
return rc;
|
|
}
|
|
|
|
int smblib_set_prop_usb_voltage_max_limit(struct smb_charger *chg,
|
|
const union power_supply_propval *val)
|
|
{
|
|
union power_supply_propval pval = {0, };
|
|
|
|
/* Exit if same value is re-configured */
|
|
if (val->intval == chg->usbin_forced_max_uv)
|
|
return 0;
|
|
|
|
smblib_get_prop_usb_voltage_max_design(chg, &pval);
|
|
|
|
if (val->intval >= MICRO_5V && val->intval <= pval.intval) {
|
|
chg->usbin_forced_max_uv = val->intval;
|
|
smblib_dbg(chg, PR_MISC, "Max VBUS limit changed to: %d\n",
|
|
val->intval);
|
|
} else if (chg->usbin_forced_max_uv) {
|
|
chg->usbin_forced_max_uv = 0;
|
|
} else {
|
|
return 0;
|
|
}
|
|
|
|
power_supply_changed(chg->usb_psy);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void smblib_typec_irq_config(struct smb_charger *chg, bool en)
|
|
{
|
|
if (en == chg->typec_irq_en)
|
|
return;
|
|
|
|
if (en) {
|
|
enable_irq(
|
|
chg->irq_info[TYPEC_ATTACH_DETACH_IRQ].irq);
|
|
enable_irq(
|
|
chg->irq_info[TYPEC_CC_STATE_CHANGE_IRQ].irq);
|
|
enable_irq(
|
|
chg->irq_info[TYPEC_OR_RID_DETECTION_CHANGE_IRQ].irq);
|
|
} else {
|
|
disable_irq_nosync(
|
|
chg->irq_info[TYPEC_ATTACH_DETACH_IRQ].irq);
|
|
disable_irq_nosync(
|
|
chg->irq_info[TYPEC_CC_STATE_CHANGE_IRQ].irq);
|
|
disable_irq_nosync(
|
|
chg->irq_info[TYPEC_OR_RID_DETECTION_CHANGE_IRQ].irq);
|
|
}
|
|
|
|
chg->typec_irq_en = en;
|
|
}
|
|
|
|
#define PR_LOCK_TIMEOUT_MS 1000
|
|
int smblib_set_prop_typec_power_role(struct smb_charger *chg,
|
|
const union power_supply_propval *val)
|
|
{
|
|
int rc = 0;
|
|
u8 power_role;
|
|
enum power_supply_typec_mode typec_mode;
|
|
bool snk_attached = false, src_attached = false, is_pr_lock = false;
|
|
|
|
if (chg->connector_type == POWER_SUPPLY_CONNECTOR_MICRO_USB)
|
|
return 0;
|
|
|
|
spin_lock(&chg->typec_pr_lock);
|
|
smblib_dbg(chg, PR_MISC, "power role change: %d --> %d!",
|
|
chg->power_role, val->intval);
|
|
|
|
/*
|
|
* Force the power-role if the initial value is NONE, for the
|
|
* legacy cable detection WA.
|
|
*/
|
|
if (chg->power_role == val->intval &&
|
|
chg->power_role != POWER_SUPPLY_TYPEC_PR_NONE) {
|
|
smblib_dbg(chg, PR_MISC, "power role already in %d, ignore!",
|
|
chg->power_role);
|
|
goto unlock;
|
|
}
|
|
|
|
typec_mode = smblib_get_prop_typec_mode(chg);
|
|
if (typec_mode >= POWER_SUPPLY_TYPEC_SINK &&
|
|
typec_mode <= POWER_SUPPLY_TYPEC_SINK_AUDIO_ADAPTER)
|
|
snk_attached = true;
|
|
else if (typec_mode >= POWER_SUPPLY_TYPEC_SOURCE_DEFAULT &&
|
|
typec_mode <= POWER_SUPPLY_TYPEC_SOURCE_HIGH)
|
|
src_attached = true;
|
|
|
|
/*
|
|
* If current power role is in DRP, and type-c is already in the
|
|
* mode (source or sink) that's being requested, it means this is
|
|
* a power role locking request from USBPD driver. Disable type-c
|
|
* related interrupts for locking power role to avoid the redundant
|
|
* notifications.
|
|
*/
|
|
if ((chg->power_role == POWER_SUPPLY_TYPEC_PR_DUAL) &&
|
|
((src_attached && val->intval == POWER_SUPPLY_TYPEC_PR_SINK) ||
|
|
(snk_attached && val->intval == POWER_SUPPLY_TYPEC_PR_SOURCE)))
|
|
is_pr_lock = true;
|
|
|
|
smblib_dbg(chg, PR_MISC, "snk_attached = %d, src_attached = %d, is_pr_lock = %d\n",
|
|
snk_attached, src_attached, is_pr_lock);
|
|
cancel_delayed_work(&chg->pr_lock_clear_work);
|
|
if (!chg->pr_lock_in_progress && is_pr_lock) {
|
|
smblib_dbg(chg, PR_MISC, "disable type-c interrupts for power role locking\n");
|
|
smblib_typec_irq_config(chg, false);
|
|
schedule_delayed_work(&chg->pr_lock_clear_work,
|
|
msecs_to_jiffies(PR_LOCK_TIMEOUT_MS));
|
|
} else if (chg->pr_lock_in_progress && !is_pr_lock) {
|
|
smblib_dbg(chg, PR_MISC, "restore type-c interrupts after exit power role locking\n");
|
|
smblib_typec_irq_config(chg, true);
|
|
}
|
|
|
|
chg->pr_lock_in_progress = is_pr_lock;
|
|
|
|
switch (val->intval) {
|
|
case POWER_SUPPLY_TYPEC_PR_NONE:
|
|
power_role = TYPEC_DISABLE_CMD_BIT;
|
|
break;
|
|
case POWER_SUPPLY_TYPEC_PR_DUAL:
|
|
power_role = chg->typec_try_mode;
|
|
break;
|
|
case POWER_SUPPLY_TYPEC_PR_SINK:
|
|
power_role = EN_SNK_ONLY_BIT;
|
|
break;
|
|
case POWER_SUPPLY_TYPEC_PR_SOURCE:
|
|
power_role = EN_SRC_ONLY_BIT;
|
|
break;
|
|
default:
|
|
smblib_err(chg, "power role %d not supported\n", val->intval);
|
|
rc = -EINVAL;
|
|
goto unlock;
|
|
}
|
|
|
|
if ((power_role != EN_SNK_ONLY_BIT)
|
|
&& (chg->source_current_ma < DEFAULT_SOURCE_CURRENT_MA)) {
|
|
/*advertise default current*/
|
|
rc = smblib_masked_write(chg, TYPE_C_CURRSRC_CFG_REG,
|
|
TYPEC_SRC_RP_SEL_MASK, TYPEC_SRC_RP_STD);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't write to TYPE_C_CURRSRC_CFG_REG rc=%d\n",
|
|
rc);
|
|
}
|
|
}
|
|
|
|
rc = smblib_masked_write(chg, TYPE_C_MODE_CFG_REG,
|
|
TYPEC_POWER_ROLE_CMD_MASK | TYPEC_TRY_MODE_MASK,
|
|
power_role);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't write 0x%02x to TYPE_C_INTRPT_ENB_SOFTWARE_CTRL rc=%d\n",
|
|
power_role, rc);
|
|
goto unlock;
|
|
}
|
|
|
|
chg->power_role = val->intval;
|
|
unlock:
|
|
spin_unlock(&chg->typec_pr_lock);
|
|
|
|
return rc;
|
|
}
|
|
|
|
int smblib_set_prop_typec_select_rp(struct smb_charger *chg,
|
|
const union power_supply_propval *val)
|
|
{
|
|
int rc;
|
|
|
|
if (!typec_in_src_mode(chg)) {
|
|
smblib_err(chg, "Couldn't set curr src: not in SRC mode\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (val->intval < TYPEC_SRC_RP_MAX_ELEMENTS) {
|
|
rc = smblib_masked_write(chg, TYPE_C_CURRSRC_CFG_REG,
|
|
TYPEC_SRC_RP_SEL_MASK,
|
|
val->intval);
|
|
if (rc < 0)
|
|
smblib_err(chg, "Couldn't write to TYPE_C_CURRSRC_CFG rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
int smblib_set_prop_pd_voltage_min(struct smb_charger *chg,
|
|
const union power_supply_propval *val)
|
|
{
|
|
int rc, min_uv;
|
|
|
|
min_uv = min(val->intval, chg->voltage_max_uv);
|
|
if (chg->voltage_min_uv == min_uv)
|
|
return 0;
|
|
|
|
rc = smblib_set_usb_pd_allowed_voltage(chg, min_uv,
|
|
chg->voltage_max_uv);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "invalid min voltage %duV rc=%d\n",
|
|
val->intval, rc);
|
|
return rc;
|
|
}
|
|
|
|
chg->voltage_min_uv = min_uv;
|
|
power_supply_changed(chg->usb_main_psy);
|
|
|
|
return rc;
|
|
}
|
|
|
|
int smblib_set_prop_pd_voltage_max(struct smb_charger *chg,
|
|
const union power_supply_propval *val)
|
|
{
|
|
int rc, max_uv;
|
|
|
|
max_uv = max(val->intval, chg->voltage_min_uv);
|
|
if (chg->voltage_max_uv == max_uv)
|
|
return 0;
|
|
if ((chg->pd_active) && (max_uv != chg->voltage_max_uv))
|
|
chg->pd_contract_uv = 0;
|
|
|
|
rc = smblib_set_usb_pd_fsw(chg, max_uv);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't set FSW for voltage %duV rc=%d\n",
|
|
val->intval, rc);
|
|
return rc;
|
|
}
|
|
|
|
rc = smblib_set_usb_pd_allowed_voltage(chg, chg->voltage_min_uv,
|
|
max_uv);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "invalid max voltage %duV rc=%d\n",
|
|
val->intval, rc);
|
|
return rc;
|
|
}
|
|
|
|
chg->voltage_max_uv = max_uv;
|
|
power_supply_changed(chg->usb_main_psy);
|
|
|
|
return rc;
|
|
}
|
|
|
|
int smblib_set_prop_pd_active(struct smb_charger *chg,
|
|
const union power_supply_propval *val)
|
|
{
|
|
#ifdef QCOM_BASE
|
|
const struct apsd_result *apsd = smblib_get_apsd_result(chg);
|
|
#endif
|
|
int rc = 0;
|
|
int sec_charger, typec_mode;
|
|
|
|
/*
|
|
* Ignore repetitive notification while PD is active, which
|
|
* is caused by hard reset.
|
|
*/
|
|
if (chg->pd_active && chg->pd_active == val->intval)
|
|
return 0;
|
|
|
|
chg->pd_active = val->intval;
|
|
|
|
smblib_apsd_enable(chg, !chg->pd_active);
|
|
|
|
#ifdef QCOM_BASE
|
|
update_sw_icl_max(chg, apsd->pst);
|
|
#endif
|
|
|
|
if (chg->pd_active) {
|
|
#ifndef QCOM_BASE
|
|
/*
|
|
* advertise 1.5A current to comply with the Type-C Specification.
|
|
* While an explicit USB PD contract is in place, the provider shall
|
|
* advertisea USB Type-C Current of either 1.5 A or 3.0 A.*/
|
|
rc = smblib_masked_write(chg, TYPE_C_CURRSRC_CFG_REG,
|
|
TYPEC_SRC_RP_SEL_MASK, TYPEC_SRC_RP_1P5A);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't write to TYPE_C_CURRSRC_CFG_REG rc=%d\n",
|
|
rc);
|
|
}
|
|
#endif
|
|
vote(chg->limited_irq_disable_votable, CHARGER_TYPE_VOTER,
|
|
false, 0);
|
|
vote(chg->hdc_irq_disable_votable, CHARGER_TYPE_VOTER,
|
|
false, 0);
|
|
|
|
/*
|
|
* Enforce 100mA for PD until the real vote comes in later.
|
|
* It is guaranteed that pd_active is set prior to
|
|
* pd_current_max
|
|
*/
|
|
vote(chg->usb_icl_votable, PD_VOTER, true, USBIN_100MA);
|
|
vote(chg->usb_icl_votable, USB_PSY_VOTER, false, 0);
|
|
vote(chg->usb_icl_votable, SW_ICL_MAX_VOTER, false, 0);
|
|
|
|
if (chg->pd_contract_uv <= 0) {
|
|
cancel_delayed_work(&chg->pd_contract_work);
|
|
schedule_delayed_work(&chg->pd_contract_work,
|
|
msecs_to_jiffies(2000));
|
|
}
|
|
|
|
/*
|
|
* For PPS, Charge Pump is preferred over parallel charger if
|
|
* present.
|
|
*/
|
|
if (chg->pd_active == POWER_SUPPLY_PD_PPS_ACTIVE
|
|
&& chg->sec_cp_present) {
|
|
rc = smblib_select_sec_charger(chg,
|
|
POWER_SUPPLY_CHARGER_SEC_CP,
|
|
POWER_SUPPLY_CP_PPS, false);
|
|
if (rc < 0)
|
|
dev_err(chg->dev, "Couldn't enable secondary charger rc=%d\n",
|
|
rc);
|
|
}
|
|
} else {
|
|
#ifndef QCOM_BASE
|
|
if (chg->source_current_ma < DEFAULT_SOURCE_CURRENT_MA) {
|
|
/*advertise default current*/
|
|
rc = smblib_masked_write(chg, TYPE_C_CURRSRC_CFG_REG,
|
|
TYPEC_SRC_RP_SEL_MASK, TYPEC_SRC_RP_STD);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't write to TYPE_C_CURRSRC_CFG_REG rc=%d\n",
|
|
rc);
|
|
}
|
|
}
|
|
#endif
|
|
vote(chg->usb_icl_votable, PD_VOTER, false, 0);
|
|
vote(chg->limited_irq_disable_votable, CHARGER_TYPE_VOTER,
|
|
true, 0);
|
|
vote(chg->hdc_irq_disable_votable, CHARGER_TYPE_VOTER,
|
|
true, 0);
|
|
|
|
sec_charger = chg->sec_pl_present ?
|
|
POWER_SUPPLY_CHARGER_SEC_PL :
|
|
POWER_SUPPLY_CHARGER_SEC_NONE;
|
|
rc = smblib_select_sec_charger(chg, sec_charger,
|
|
POWER_SUPPLY_CP_NONE, false);
|
|
if (rc < 0)
|
|
dev_err(chg->dev,
|
|
"Couldn't enable secondary charger rc=%d\n",
|
|
rc);
|
|
|
|
/* PD hard resets failed, proceed to detect QC2/3 */
|
|
if (chg->ok_to_pd) {
|
|
chg->ok_to_pd = false;
|
|
smblib_hvdcp_detect_try_enable(chg, true);
|
|
}
|
|
}
|
|
|
|
#ifdef QCOM_BASE
|
|
smblib_usb_pd_adapter_allowance_override(chg,
|
|
!!chg->pd_active ? FORCE_5V : FORCE_NULL);
|
|
#endif
|
|
smblib_update_usb_type(chg);
|
|
|
|
if (chg->real_charger_type == POWER_SUPPLY_TYPE_USB &&
|
|
!chg->ok_to_pd) {
|
|
typec_mode = smblib_get_prop_typec_mode(chg);
|
|
if (typec_rp_med_high(chg, typec_mode))
|
|
vote(chg->usb_icl_votable, USB_PSY_VOTER, false, 0);
|
|
}
|
|
|
|
power_supply_changed(chg->usb_psy);
|
|
return rc;
|
|
}
|
|
|
|
int smblib_set_prop_ship_mode(struct smb_charger *chg,
|
|
const union power_supply_propval *val)
|
|
{
|
|
int rc;
|
|
|
|
smblib_dbg(chg, PR_MISC, "Set ship mode: %d!!\n", !!val->intval);
|
|
|
|
rc = smblib_masked_write(chg, SHIP_MODE_REG, SHIP_MODE_EN_BIT,
|
|
!!val->intval ? SHIP_MODE_EN_BIT : 0);
|
|
if (rc < 0)
|
|
dev_err(chg->dev, "Couldn't %s ship mode, rc=%d\n",
|
|
!!val->intval ? "enable" : "disable", rc);
|
|
|
|
return rc;
|
|
}
|
|
|
|
int smblib_set_prop_pd_in_hard_reset(struct smb_charger *chg,
|
|
const union power_supply_propval *val)
|
|
{
|
|
int rc = 0;
|
|
|
|
if (chg->pd_hard_reset == val->intval)
|
|
return rc;
|
|
|
|
chg->pd_hard_reset = val->intval;
|
|
rc = smblib_masked_write(chg, TYPE_C_EXIT_STATE_CFG_REG,
|
|
EXIT_SNK_BASED_ON_CC_BIT,
|
|
(chg->pd_hard_reset) ? EXIT_SNK_BASED_ON_CC_BIT : 0);
|
|
if (rc < 0)
|
|
smblib_err(chg, "Couldn't set EXIT_SNK_BASED_ON_CC rc=%d\n",
|
|
rc);
|
|
|
|
return rc;
|
|
}
|
|
|
|
#define JEITA_SOFT 0
|
|
#define JEITA_HARD 1
|
|
static int smblib_update_jeita(struct smb_charger *chg, u32 *thresholds,
|
|
int type)
|
|
{
|
|
int rc;
|
|
u16 temp, base;
|
|
|
|
base = CHGR_JEITA_THRESHOLD_BASE_REG(type);
|
|
|
|
temp = thresholds[1] & 0xFFFF;
|
|
temp = ((temp & 0xFF00) >> 8) | ((temp & 0xFF) << 8);
|
|
rc = smblib_batch_write(chg, base, (u8 *)&temp, 2);
|
|
if (rc < 0) {
|
|
smblib_err(chg,
|
|
"Couldn't configure Jeita %s hot threshold rc=%d\n",
|
|
(type == JEITA_SOFT) ? "Soft" : "Hard", rc);
|
|
return rc;
|
|
}
|
|
|
|
temp = thresholds[0] & 0xFFFF;
|
|
temp = ((temp & 0xFF00) >> 8) | ((temp & 0xFF) << 8);
|
|
rc = smblib_batch_write(chg, base + 2, (u8 *)&temp, 2);
|
|
if (rc < 0) {
|
|
smblib_err(chg,
|
|
"Couldn't configure Jeita %s cold threshold rc=%d\n",
|
|
(type == JEITA_SOFT) ? "Soft" : "Hard", rc);
|
|
return rc;
|
|
}
|
|
|
|
smblib_dbg(chg, PR_MISC, "%s Jeita threshold configured\n",
|
|
(type == JEITA_SOFT) ? "Soft" : "Hard");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int smblib_charge_inhibit_en(struct smb_charger *chg, bool enable)
|
|
{
|
|
int rc;
|
|
|
|
rc = smblib_masked_write(chg, CHGR_CFG2_REG,
|
|
CHARGER_INHIBIT_BIT,
|
|
enable ? CHARGER_INHIBIT_BIT : 0);
|
|
return rc;
|
|
}
|
|
|
|
static int smblib_soft_jeita_arb_wa(struct smb_charger *chg)
|
|
{
|
|
union power_supply_propval pval;
|
|
int rc = 0;
|
|
bool soft_jeita;
|
|
|
|
rc = smblib_get_prop_batt_health(chg, &pval);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't get battery health rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
/* Do nothing on entering hard JEITA condition */
|
|
if (pval.intval == POWER_SUPPLY_HEALTH_COLD ||
|
|
pval.intval == POWER_SUPPLY_HEALTH_HOT)
|
|
return 0;
|
|
|
|
if (chg->jeita_soft_fcc[0] < 0 || chg->jeita_soft_fcc[1] < 0 ||
|
|
chg->jeita_soft_fv[0] < 0 || chg->jeita_soft_fv[1] < 0)
|
|
return 0;
|
|
|
|
soft_jeita = (pval.intval == POWER_SUPPLY_HEALTH_COOL) ||
|
|
(pval.intval == POWER_SUPPLY_HEALTH_WARM);
|
|
|
|
/* Do nothing on entering soft JEITA from hard JEITA */
|
|
if (chg->jeita_arb_flag && soft_jeita)
|
|
return 0;
|
|
|
|
/* Do nothing, initial to health condition */
|
|
if (!chg->jeita_arb_flag && !soft_jeita)
|
|
return 0;
|
|
|
|
/* Entering soft JEITA from normal state */
|
|
if (!chg->jeita_arb_flag && soft_jeita) {
|
|
vote(chg->chg_disable_votable, JEITA_ARB_VOTER, true, 0);
|
|
|
|
rc = smblib_charge_inhibit_en(chg, true);
|
|
if (rc < 0)
|
|
smblib_err(chg, "Couldn't enable charge inhibit rc=%d\n",
|
|
rc);
|
|
|
|
rc = smblib_update_jeita(chg, chg->jeita_soft_hys_thlds,
|
|
JEITA_SOFT);
|
|
if (rc < 0)
|
|
smblib_err(chg,
|
|
"Couldn't configure Jeita soft threshold rc=%d\n",
|
|
rc);
|
|
|
|
if (pval.intval == POWER_SUPPLY_HEALTH_COOL) {
|
|
vote(chg->fcc_votable, JEITA_ARB_VOTER, true,
|
|
chg->jeita_soft_fcc[0]);
|
|
vote(chg->fv_votable, JEITA_ARB_VOTER, true,
|
|
chg->jeita_soft_fv[0]);
|
|
} else {
|
|
vote(chg->fcc_votable, JEITA_ARB_VOTER, true,
|
|
chg->jeita_soft_fcc[1]);
|
|
vote(chg->fv_votable, JEITA_ARB_VOTER, true,
|
|
chg->jeita_soft_fv[1]);
|
|
}
|
|
|
|
vote(chg->chg_disable_votable, JEITA_ARB_VOTER, false, 0);
|
|
chg->jeita_arb_flag = true;
|
|
} else if (chg->jeita_arb_flag && !soft_jeita) {
|
|
/* Exit to health state from soft JEITA */
|
|
|
|
vote(chg->chg_disable_votable, JEITA_ARB_VOTER, true, 0);
|
|
|
|
rc = smblib_charge_inhibit_en(chg, false);
|
|
if (rc < 0)
|
|
smblib_err(chg, "Couldn't disable charge inhibit rc=%d\n",
|
|
rc);
|
|
|
|
rc = smblib_update_jeita(chg, chg->jeita_soft_thlds,
|
|
JEITA_SOFT);
|
|
if (rc < 0)
|
|
smblib_err(chg, "Couldn't configure Jeita soft threshold rc=%d\n",
|
|
rc);
|
|
|
|
vote(chg->fcc_votable, JEITA_ARB_VOTER, false, 0);
|
|
vote(chg->fv_votable, JEITA_ARB_VOTER, false, 0);
|
|
vote(chg->chg_disable_votable, JEITA_ARB_VOTER, false, 0);
|
|
chg->jeita_arb_flag = false;
|
|
}
|
|
|
|
smblib_dbg(chg, PR_MISC, "JEITA ARB status %d, soft JEITA status %d\n",
|
|
chg->jeita_arb_flag, soft_jeita);
|
|
return rc;
|
|
}
|
|
|
|
/************************
|
|
* USB MAIN PSY GETTERS *
|
|
************************/
|
|
int smblib_get_prop_fcc_delta(struct smb_charger *chg,
|
|
union power_supply_propval *val)
|
|
{
|
|
int rc, jeita_cc_delta_ua = 0;
|
|
|
|
if (chg->sw_jeita_enabled) {
|
|
val->intval = 0;
|
|
return 0;
|
|
}
|
|
|
|
rc = smblib_get_jeita_cc_delta(chg, &jeita_cc_delta_ua);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't get jeita cc delta rc=%d\n", rc);
|
|
jeita_cc_delta_ua = 0;
|
|
}
|
|
|
|
val->intval = jeita_cc_delta_ua;
|
|
return 0;
|
|
}
|
|
|
|
/************************
|
|
* USB MAIN PSY SETTERS *
|
|
************************/
|
|
int smblib_get_charge_current(struct smb_charger *chg,
|
|
int *total_current_ua)
|
|
{
|
|
const struct apsd_result *apsd_result = smblib_get_apsd_result(chg);
|
|
union power_supply_propval val = {0, };
|
|
int rc = 0, typec_source_rd, current_ua;
|
|
bool non_compliant;
|
|
u8 stat;
|
|
|
|
if (chg->pd_active) {
|
|
*total_current_ua =
|
|
get_client_vote_locked(chg->usb_icl_votable, PD_VOTER);
|
|
return rc;
|
|
}
|
|
|
|
rc = smblib_read(chg, LEGACY_CABLE_STATUS_REG, &stat);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't read TYPE_C_STATUS_5 rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
non_compliant = stat & TYPEC_NONCOMP_LEGACY_CABLE_STATUS_BIT;
|
|
|
|
/* get settled ICL */
|
|
rc = smblib_get_prop_input_current_settled(chg, &val);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't get settled ICL rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
typec_source_rd = smblib_get_prop_ufp_mode(chg);
|
|
|
|
/* QC 2.0/3.0 adapter */
|
|
if (apsd_result->bit & (QC_3P0_BIT | QC_2P0_BIT)) {
|
|
*total_current_ua = HVDCP_CURRENT_UA;
|
|
return 0;
|
|
}
|
|
|
|
if (non_compliant && !chg->typec_legacy_use_rp_icl) {
|
|
switch (apsd_result->bit) {
|
|
case CDP_CHARGER_BIT:
|
|
current_ua = CDP_CURRENT_UA;
|
|
break;
|
|
case DCP_CHARGER_BIT:
|
|
case OCP_CHARGER_BIT:
|
|
case FLOAT_CHARGER_BIT:
|
|
current_ua = DCP_CURRENT_UA;
|
|
break;
|
|
default:
|
|
current_ua = 0;
|
|
break;
|
|
}
|
|
|
|
*total_current_ua = max(current_ua, val.intval);
|
|
return 0;
|
|
}
|
|
|
|
switch (typec_source_rd) {
|
|
case POWER_SUPPLY_TYPEC_SOURCE_DEFAULT:
|
|
switch (apsd_result->bit) {
|
|
case CDP_CHARGER_BIT:
|
|
current_ua = CDP_CURRENT_UA;
|
|
break;
|
|
case DCP_CHARGER_BIT:
|
|
case OCP_CHARGER_BIT:
|
|
case FLOAT_CHARGER_BIT:
|
|
current_ua = chg->default_icl_ua;
|
|
break;
|
|
default:
|
|
current_ua = 0;
|
|
break;
|
|
}
|
|
break;
|
|
case POWER_SUPPLY_TYPEC_SOURCE_MEDIUM:
|
|
current_ua = TYPEC_MEDIUM_CURRENT_UA;
|
|
break;
|
|
case POWER_SUPPLY_TYPEC_SOURCE_HIGH:
|
|
if(chg->hvdcp_disable)
|
|
current_ua = TYPEC_HIGH_CURRENT_UA_NON_HVDCP;
|
|
else
|
|
current_ua = TYPEC_HIGH_CURRENT_UA;
|
|
break;
|
|
case POWER_SUPPLY_TYPEC_NON_COMPLIANT:
|
|
case POWER_SUPPLY_TYPEC_NONE:
|
|
default:
|
|
current_ua = 0;
|
|
break;
|
|
}
|
|
|
|
*total_current_ua = max(current_ua, val.intval);
|
|
return 0;
|
|
}
|
|
|
|
#define IADP_OVERHEAT_UA 500000
|
|
int smblib_set_prop_thermal_overheat(struct smb_charger *chg,
|
|
int therm_overheat)
|
|
{
|
|
int icl_ua = 0;
|
|
|
|
if (chg->thermal_overheat == !!therm_overheat)
|
|
return 0;
|
|
|
|
/* Configure ICL to 500mA in case system health is Overheat */
|
|
if (therm_overheat)
|
|
icl_ua = IADP_OVERHEAT_UA;
|
|
|
|
if (!chg->cp_disable_votable)
|
|
chg->cp_disable_votable = find_votable("CP_DISABLE");
|
|
|
|
if (chg->cp_disable_votable) {
|
|
vote(chg->cp_disable_votable, OVERHEAT_LIMIT_VOTER,
|
|
therm_overheat, 0);
|
|
vote(chg->usb_icl_votable, OVERHEAT_LIMIT_VOTER,
|
|
therm_overheat, icl_ua);
|
|
}
|
|
|
|
chg->thermal_overheat = !!therm_overheat;
|
|
return 0;
|
|
}
|
|
|
|
/**********************
|
|
* INTERRUPT HANDLERS *
|
|
**********************/
|
|
|
|
irqreturn_t default_irq_handler(int irq, void *data)
|
|
{
|
|
struct smb_irq_data *irq_data = data;
|
|
struct smb_charger *chg = irq_data->parent_data;
|
|
|
|
smblib_dbg(chg, PR_INTERRUPT, "IRQ: %s\n", irq_data->name);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
irqreturn_t smb_en_irq_handler(int irq, void *data)
|
|
{
|
|
struct smb_irq_data *irq_data = data;
|
|
struct smb_charger *chg = irq_data->parent_data;
|
|
int rc, input_present;
|
|
|
|
if (!chg->cp_disable_votable) {
|
|
chg->cp_disable_votable = find_votable("CP_DISABLE");
|
|
if (!chg->cp_disable_votable)
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
if (chg->pd_hard_reset) {
|
|
vote(chg->cp_disable_votable, BOOST_BACK_VOTER, true, 0);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
rc = smblib_is_input_present(chg, &input_present);
|
|
if (rc < 0) {
|
|
pr_err("Couldn't get usb presence status rc=%d\n", rc);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
if (input_present) {
|
|
/*
|
|
* Add some delay to enable SMB1390 switcher after SMB_EN
|
|
* pin goes high
|
|
*/
|
|
usleep_range(1000, 1100);
|
|
vote(chg->cp_disable_votable, BOOST_BACK_VOTER, false, 0);
|
|
}
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
irqreturn_t sdam_sts_change_irq_handler(int irq, void *data)
|
|
{
|
|
struct smb_irq_data *irq_data = data;
|
|
struct smb_charger *chg = irq_data->parent_data;
|
|
|
|
smblib_dbg(chg, PR_INTERRUPT, "IRQ: %s\n", irq_data->name);
|
|
|
|
mutex_lock(&chg->irq_status_lock);
|
|
chg->irq_status |= PULSE_SKIP_IRQ_BIT;
|
|
mutex_unlock(&chg->irq_status_lock);
|
|
|
|
power_supply_changed(chg->usb_main_psy);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
#define CHG_TERM_WA_ENTRY_DELAY_MS 300000 /* 5 min */
|
|
#define CHG_TERM_WA_EXIT_DELAY_MS 60000 /* 1 min */
|
|
static void smblib_eval_chg_termination(struct smb_charger *chg, u8 batt_status)
|
|
{
|
|
union power_supply_propval pval = {0, };
|
|
int rc = 0;
|
|
|
|
rc = smblib_get_prop_from_bms(chg,
|
|
POWER_SUPPLY_PROP_REAL_CAPACITY, &pval);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't read SOC value, rc=%d\n", rc);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Post charge termination, switch to BSM mode triggers the risk of
|
|
* over charging as BATFET opening may take some time post the necessity
|
|
* of staying in supplemental mode, leading to unintended charging of
|
|
* battery. Trigger the charge termination WA once charging is completed
|
|
* to prevent overcharing.
|
|
*/
|
|
if ((batt_status == TERMINATE_CHARGE) && (pval.intval == 100) &&
|
|
(ktime_to_ms(alarm_expires_remaining(/* alarm not pending */
|
|
&chg->chg_termination_alarm)) <= 0)) {
|
|
chg->cc_soc_ref = 0;
|
|
chg->last_cc_soc = 0;
|
|
chg->term_vbat_uv = 0;
|
|
alarm_start_relative(&chg->chg_termination_alarm,
|
|
ms_to_ktime(CHG_TERM_WA_ENTRY_DELAY_MS));
|
|
} else if (pval.intval < 100) {
|
|
/*
|
|
* Reset CC_SOC reference value for charge termination WA once
|
|
* we exit the TERMINATE_CHARGE state and soc drops below 100%
|
|
*/
|
|
chg->cc_soc_ref = 0;
|
|
chg->last_cc_soc = 0;
|
|
chg->term_vbat_uv = 0;
|
|
}
|
|
}
|
|
|
|
irqreturn_t smblib_handle_usbin_collapse(int irq, void *data)
|
|
{
|
|
struct smb_irq_data *irq_data = data;
|
|
struct smb_charger *chg = irq_data->parent_data;
|
|
int rc;
|
|
union power_supply_propval val = {0, };
|
|
|
|
rc = smblib_get_prop_usb_present(chg, &val);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't get usb present rc=%d\n", rc);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
if (val.intval && !chg->reverse_boost) {
|
|
cancel_delayed_work(&chg->mmi.heartbeat_work);
|
|
schedule_delayed_work(&chg->mmi.heartbeat_work,
|
|
msecs_to_jiffies(100));
|
|
}
|
|
smblib_dbg(chg, PR_INTERRUPT, "IRQ: %s\n", irq_data->name);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
irqreturn_t chg_state_change_irq_handler(int irq, void *data)
|
|
{
|
|
struct smb_irq_data *irq_data = data;
|
|
struct smb_charger *chg = irq_data->parent_data;
|
|
u8 stat;
|
|
int rc;
|
|
|
|
smblib_dbg(chg, PR_INTERRUPT, "IRQ: %s\n", irq_data->name);
|
|
|
|
rc = smblib_read(chg, BATTERY_CHARGER_STATUS_1_REG, &stat);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't read BATTERY_CHARGER_STATUS_1 rc=%d\n",
|
|
rc);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
stat = stat & BATTERY_CHARGER_STATUS_MASK;
|
|
|
|
if (chg->wa_flags & CHG_TERMINATION_WA)
|
|
smblib_eval_chg_termination(chg, stat);
|
|
|
|
power_supply_changed(chg->batt_psy);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
irqreturn_t batt_temp_changed_irq_handler(int irq, void *data)
|
|
{
|
|
struct smb_irq_data *irq_data = data;
|
|
struct smb_charger *chg = irq_data->parent_data;
|
|
int rc;
|
|
|
|
smblib_dbg(chg, PR_INTERRUPT, "IRQ: %s\n", irq_data->name);
|
|
|
|
if (chg->jeita_configured != JEITA_CFG_COMPLETE)
|
|
return IRQ_HANDLED;
|
|
|
|
rc = smblib_soft_jeita_arb_wa(chg);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't fix soft jeita arb rc=%d\n",
|
|
rc);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
irqreturn_t batt_psy_changed_irq_handler(int irq, void *data)
|
|
{
|
|
struct smb_irq_data *irq_data = data;
|
|
struct smb_charger *chg = irq_data->parent_data;
|
|
|
|
smblib_dbg(chg, PR_INTERRUPT, "IRQ: %s\n", irq_data->name);
|
|
power_supply_changed(chg->batt_psy);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
#define AICL_STEP_MV 200
|
|
#define MAX_AICL_THRESHOLD_MV 4800
|
|
irqreturn_t usbin_uv_irq_handler(int irq, void *data)
|
|
{
|
|
struct smb_irq_data *irq_data = data;
|
|
struct smb_charger *chg = irq_data->parent_data;
|
|
struct storm_watch *wdata;
|
|
const struct apsd_result *apsd = smblib_get_apsd_result(chg);
|
|
int rc;
|
|
u8 stat = 0, max_pulses = 0;
|
|
|
|
smblib_dbg(chg, PR_INTERRUPT, "IRQ: %s\n", irq_data->name);
|
|
|
|
if ((chg->wa_flags & WEAK_ADAPTER_WA)
|
|
&& is_storming(&irq_data->storm_data)) {
|
|
|
|
if (chg->aicl_max_reached) {
|
|
smblib_dbg(chg, PR_MISC,
|
|
"USBIN_UV storm at max AICL threshold\n");
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
smblib_dbg(chg, PR_MISC, "USBIN_UV storm at threshold %d\n",
|
|
chg->aicl_5v_threshold_mv);
|
|
|
|
/* suspend USBIN before updating AICL threshold */
|
|
vote(chg->usb_icl_votable, AICL_THRESHOLD_VOTER, true, 0);
|
|
|
|
/* delay for VASHDN deglitch */
|
|
msleep(20);
|
|
|
|
if (chg->aicl_5v_threshold_mv > MAX_AICL_THRESHOLD_MV) {
|
|
/* reached max AICL threshold */
|
|
chg->aicl_max_reached = true;
|
|
goto unsuspend_input;
|
|
}
|
|
|
|
/* Increase AICL threshold by 200mV */
|
|
rc = smblib_set_charge_param(chg, &chg->param.aicl_5v_threshold,
|
|
chg->aicl_5v_threshold_mv + AICL_STEP_MV);
|
|
if (rc < 0)
|
|
dev_err(chg->dev,
|
|
"Error in setting AICL threshold rc=%d\n", rc);
|
|
else
|
|
chg->aicl_5v_threshold_mv += AICL_STEP_MV;
|
|
|
|
rc = smblib_set_charge_param(chg,
|
|
&chg->param.aicl_cont_threshold,
|
|
chg->aicl_cont_threshold_mv + AICL_STEP_MV);
|
|
if (rc < 0)
|
|
dev_err(chg->dev,
|
|
"Error in setting AICL threshold rc=%d\n", rc);
|
|
else
|
|
chg->aicl_cont_threshold_mv += AICL_STEP_MV;
|
|
|
|
unsuspend_input:
|
|
/* Force torch in boost mode to ensure it works with low ICL */
|
|
if (chg->chg_param.smb_version == PMI632_SUBTYPE)
|
|
schgm_flash_torch_priority(chg, TORCH_BOOST_MODE);
|
|
|
|
if (chg->aicl_max_reached) {
|
|
smblib_dbg(chg, PR_MISC,
|
|
"Reached max AICL threshold resctricting ICL to 100mA\n");
|
|
vote(chg->usb_icl_votable, AICL_THRESHOLD_VOTER,
|
|
true, USBIN_100MA);
|
|
smblib_run_aicl(chg, RESTART_AICL);
|
|
} else {
|
|
smblib_run_aicl(chg, RESTART_AICL);
|
|
vote(chg->usb_icl_votable, AICL_THRESHOLD_VOTER,
|
|
false, 0);
|
|
}
|
|
|
|
wdata = &chg->irq_info[USBIN_UV_IRQ].irq_data->storm_data;
|
|
reset_storm_count(wdata);
|
|
}
|
|
|
|
if (!chg->irq_info[SWITCHER_POWER_OK_IRQ].irq_data)
|
|
return IRQ_HANDLED;
|
|
|
|
wdata = &chg->irq_info[SWITCHER_POWER_OK_IRQ].irq_data->storm_data;
|
|
reset_storm_count(wdata);
|
|
|
|
/* Workaround for non-QC2.0-compliant chargers follows */
|
|
if (!chg->qc2_unsupported_voltage &&
|
|
apsd->pst == POWER_SUPPLY_TYPE_USB_HVDCP) {
|
|
rc = smblib_read(chg, QC_CHANGE_STATUS_REG, &stat);
|
|
if (rc < 0)
|
|
smblib_err(chg,
|
|
"Couldn't read CHANGE_STATUS_REG rc=%d\n", rc);
|
|
|
|
if (stat & QC_5V_BIT)
|
|
return IRQ_HANDLED;
|
|
|
|
rc = smblib_read(chg, HVDCP_PULSE_COUNT_MAX_REG, &max_pulses);
|
|
if (rc < 0)
|
|
smblib_err(chg,
|
|
"Couldn't read QC2 max pulses rc=%d\n", rc);
|
|
|
|
chg->qc2_max_pulses = (max_pulses &
|
|
HVDCP_PULSE_COUNT_MAX_QC2_MASK);
|
|
|
|
if (stat & QC_12V_BIT) {
|
|
chg->qc2_unsupported_voltage = QC2_NON_COMPLIANT_12V;
|
|
rc = smblib_masked_write(chg, HVDCP_PULSE_COUNT_MAX_REG,
|
|
HVDCP_PULSE_COUNT_MAX_QC2_MASK,
|
|
HVDCP_PULSE_COUNT_MAX_QC2_9V);
|
|
if (rc < 0)
|
|
smblib_err(chg, "Couldn't force max pulses to 9V rc=%d\n",
|
|
rc);
|
|
|
|
} else if (stat & QC_9V_BIT) {
|
|
chg->qc2_unsupported_voltage = QC2_NON_COMPLIANT_9V;
|
|
rc = smblib_masked_write(chg, HVDCP_PULSE_COUNT_MAX_REG,
|
|
HVDCP_PULSE_COUNT_MAX_QC2_MASK,
|
|
HVDCP_PULSE_COUNT_MAX_QC2_5V);
|
|
if (rc < 0)
|
|
smblib_err(chg, "Couldn't force max pulses to 5V rc=%d\n",
|
|
rc);
|
|
|
|
}
|
|
|
|
rc = smblib_masked_write(chg, USBIN_AICL_OPTIONS_CFG_REG,
|
|
SUSPEND_ON_COLLAPSE_USBIN_BIT,
|
|
0);
|
|
if (rc < 0)
|
|
smblib_err(chg, "Couldn't turn off SUSPEND_ON_COLLAPSE_USBIN_BIT rc=%d\n",
|
|
rc);
|
|
|
|
smblib_rerun_apsd(chg);
|
|
}
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
#define USB_WEAK_INPUT_UA 1400000
|
|
#define ICL_CHANGE_DELAY_MS 1000
|
|
irqreturn_t icl_change_irq_handler(int irq, void *data)
|
|
{
|
|
u8 stat;
|
|
int rc, settled_ua, delay = ICL_CHANGE_DELAY_MS;
|
|
struct smb_irq_data *irq_data = data;
|
|
struct smb_charger *chg = irq_data->parent_data;
|
|
|
|
if (chg->mode == PARALLEL_MASTER) {
|
|
/*
|
|
* Ignore if change in ICL is due to DIE temp mitigation.
|
|
* This is to prevent any further ICL split.
|
|
*/
|
|
if (chg->hw_die_temp_mitigation) {
|
|
rc = smblib_read(chg, DIE_TEMP_STATUS_REG, &stat);
|
|
if (rc < 0) {
|
|
smblib_err(chg,
|
|
"Couldn't read DIE_TEMP rc=%d\n", rc);
|
|
return IRQ_HANDLED;
|
|
}
|
|
if (stat & (DIE_TEMP_UB_BIT | DIE_TEMP_LB_BIT)) {
|
|
smblib_dbg(chg, PR_PARALLEL,
|
|
"skip ICL change DIE_TEMP %x\n", stat);
|
|
return IRQ_HANDLED;
|
|
}
|
|
}
|
|
|
|
rc = smblib_read(chg, AICL_STATUS_REG, &stat);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't read AICL_STATUS rc=%d\n",
|
|
rc);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
rc = smblib_get_charge_param(chg, &chg->param.icl_stat,
|
|
&settled_ua);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't get ICL status rc=%d\n", rc);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
/* If AICL settled then schedule work now */
|
|
if (settled_ua == get_effective_result(chg->usb_icl_votable))
|
|
delay = 0;
|
|
|
|
cancel_delayed_work_sync(&chg->icl_change_work);
|
|
schedule_delayed_work(&chg->icl_change_work,
|
|
msecs_to_jiffies(delay));
|
|
}
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static int smblib_role_switch_failure(struct smb_charger *chg)
|
|
{
|
|
int rc = 0;
|
|
union power_supply_propval pval = {0, };
|
|
|
|
if (!chg->use_extcon)
|
|
return 0;
|
|
|
|
rc = smblib_get_prop_usb_present(chg, &pval);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't get usb presence status rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
* When role switch fails notify the
|
|
* current charger state to usb driver.
|
|
*/
|
|
if (pval.intval) {
|
|
smblib_dbg(chg, PR_MISC, " Role reversal failed, notifying device mode to usb driver.\n");
|
|
smblib_notify_device_mode(chg, true);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int typec_partner_register(struct smb_charger *chg)
|
|
{
|
|
int typec_mode, rc = 0;
|
|
|
|
mutex_lock(&chg->typec_lock);
|
|
|
|
if (!chg->typec_port || chg->pr_swap_in_progress)
|
|
goto unlock;
|
|
|
|
if (!chg->typec_partner) {
|
|
if (chg->sink_src_mode == AUDIO_ACCESS_MODE)
|
|
chg->typec_partner_desc.accessory =
|
|
TYPEC_ACCESSORY_AUDIO;
|
|
else
|
|
chg->typec_partner_desc.accessory =
|
|
TYPEC_ACCESSORY_NONE;
|
|
|
|
chg->typec_partner = typec_register_partner(chg->typec_port,
|
|
&chg->typec_partner_desc);
|
|
if (IS_ERR(chg->typec_partner)) {
|
|
rc = PTR_ERR(chg->typec_partner);
|
|
pr_err("failed to register typec_partner rc=%d\n", rc);
|
|
goto unlock;
|
|
}
|
|
}
|
|
|
|
typec_mode = smblib_get_prop_typec_mode(chg);
|
|
|
|
if (typec_mode >= POWER_SUPPLY_TYPEC_SOURCE_DEFAULT
|
|
|| typec_mode == POWER_SUPPLY_TYPEC_NONE) {
|
|
|
|
if (chg->typec_role_swap_failed) {
|
|
rc = smblib_role_switch_failure(chg);
|
|
if (rc < 0)
|
|
smblib_err(chg, "Failed to role switch rc=%d\n",
|
|
rc);
|
|
chg->typec_role_swap_failed = false;
|
|
}
|
|
|
|
typec_set_data_role(chg->typec_port, TYPEC_DEVICE);
|
|
typec_set_pwr_role(chg->typec_port, TYPEC_SINK);
|
|
} else {
|
|
typec_set_data_role(chg->typec_port, TYPEC_HOST);
|
|
typec_set_pwr_role(chg->typec_port, TYPEC_SOURCE);
|
|
}
|
|
|
|
unlock:
|
|
mutex_unlock(&chg->typec_lock);
|
|
return rc;
|
|
}
|
|
|
|
static void typec_partner_unregister(struct smb_charger *chg)
|
|
{
|
|
mutex_lock(&chg->typec_lock);
|
|
|
|
if (!chg->typec_port)
|
|
goto unlock;
|
|
|
|
if (chg->typec_partner && !chg->pr_swap_in_progress) {
|
|
smblib_dbg(chg, PR_MISC, "Un-registering typeC partner\n");
|
|
typec_unregister_partner(chg->typec_partner);
|
|
chg->typec_partner = NULL;
|
|
}
|
|
|
|
unlock:
|
|
mutex_unlock(&chg->typec_lock);
|
|
}
|
|
|
|
static void smblib_micro_usb_plugin(struct smb_charger *chg, bool vbus_rising)
|
|
{
|
|
int rc = 0;
|
|
|
|
if (!vbus_rising) {
|
|
smblib_update_usb_type(chg);
|
|
smblib_notify_device_mode(chg, false);
|
|
smblib_uusb_removal(chg);
|
|
typec_partner_unregister(chg);
|
|
} else {
|
|
rc = typec_partner_register(chg);
|
|
if (rc < 0)
|
|
smblib_err(chg, "Couldn't register partner rc =%d\n",
|
|
rc);
|
|
}
|
|
}
|
|
|
|
void smblib_usb_plugin_hard_reset_locked(struct smb_charger *chg)
|
|
{
|
|
int rc;
|
|
u8 stat;
|
|
bool vbus_rising;
|
|
struct smb_irq_data *data;
|
|
struct storm_watch *wdata;
|
|
|
|
rc = smblib_read(chg, USBIN_BASE + INT_RT_STS_OFFSET, &stat);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't read USB_INT_RT_STS rc=%d\n", rc);
|
|
return;
|
|
}
|
|
|
|
vbus_rising = (bool)(stat & USBIN_PLUGIN_RT_STS_BIT);
|
|
|
|
if (vbus_rising) {
|
|
/* Remove FCC_STEPPER 1.5A init vote to allow FCC ramp up */
|
|
if (chg->fcc_stepper_enable)
|
|
vote(chg->fcc_votable, FCC_STEPPER_VOTER, false, 0);
|
|
} else {
|
|
if (chg->wa_flags & BOOST_BACK_WA) {
|
|
data = chg->irq_info[SWITCHER_POWER_OK_IRQ].irq_data;
|
|
if (data) {
|
|
wdata = &data->storm_data;
|
|
update_storm_count(wdata,
|
|
WEAK_CHG_STORM_COUNT);
|
|
chg->reverse_boost = false;
|
|
vote(chg->usb_icl_votable, BOOST_BACK_VOTER,
|
|
false, 0);
|
|
vote(chg->usb_icl_votable, WEAK_CHARGER_VOTER,
|
|
false, 0);
|
|
}
|
|
}
|
|
|
|
/* Force 1500mA FCC on USB removal if fcc stepper is enabled */
|
|
if (chg->fcc_stepper_enable)
|
|
vote(chg->fcc_votable, FCC_STEPPER_VOTER,
|
|
true, 1500000);
|
|
}
|
|
|
|
power_supply_changed(chg->usb_psy);
|
|
smblib_dbg(chg, PR_INTERRUPT, "IRQ: usbin-plugin %s\n",
|
|
vbus_rising ? "attached" : "detached");
|
|
}
|
|
|
|
#define PL_DELAY_MS 30000
|
|
static int factory_kill_disable;
|
|
module_param(factory_kill_disable, int, 0644);
|
|
void smblib_usb_plugin_locked(struct smb_charger *chg)
|
|
{
|
|
int rc;
|
|
u8 stat;
|
|
bool vbus_rising;
|
|
struct smb_irq_data *data;
|
|
struct storm_watch *wdata;
|
|
|
|
rc = smblib_read(chg, USBIN_BASE + INT_RT_STS_OFFSET, &stat);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't read USB_INT_RT_STS rc=%d\n", rc);
|
|
return;
|
|
}
|
|
|
|
vbus_rising = (bool)(stat & USBIN_PLUGIN_RT_STS_BIT);
|
|
smblib_set_opt_switcher_freq(chg, vbus_rising ? chg->chg_freq.freq_5V :
|
|
chg->chg_freq.freq_removal);
|
|
|
|
if (vbus_rising) {
|
|
cancel_delayed_work_sync(&chg->pr_swap_detach_work);
|
|
vote(chg->awake_votable, DETACH_DETECT_VOTER, false, 0);
|
|
rc = smblib_request_dpdm(chg, true);
|
|
if (rc < 0)
|
|
smblib_err(chg, "Couldn't to enable DPDM rc=%d\n", rc);
|
|
|
|
/* Enable SW Thermal regulation */
|
|
rc = smblib_set_sw_thermal_regulation(chg, true);
|
|
if (rc < 0)
|
|
smblib_err(chg, "Couldn't start SW thermal regulation WA, rc=%d\n",
|
|
rc);
|
|
|
|
/* Remove FCC_STEPPER 1.5A init vote to allow FCC ramp up */
|
|
if (chg->fcc_stepper_enable)
|
|
vote(chg->fcc_votable, FCC_STEPPER_VOTER, false, 0);
|
|
|
|
/* Schedule work to enable parallel charger */
|
|
vote(chg->awake_votable, PL_DELAY_VOTER, true, 0);
|
|
schedule_delayed_work(&chg->pl_enable_work,
|
|
msecs_to_jiffies(PL_DELAY_MS));
|
|
|
|
if (chg->mmi.factory_mode)
|
|
chg->mmi.factory_kill_armed = true;
|
|
} else {
|
|
/* Disable SW Thermal Regulation */
|
|
rc = smblib_set_sw_thermal_regulation(chg, false);
|
|
if (rc < 0)
|
|
smblib_err(chg, "Couldn't stop SW thermal regulation WA, rc=%d\n",
|
|
rc);
|
|
|
|
if (chg->wa_flags & BOOST_BACK_WA) {
|
|
data = chg->irq_info[SWITCHER_POWER_OK_IRQ].irq_data;
|
|
if (data) {
|
|
wdata = &data->storm_data;
|
|
update_storm_count(wdata,
|
|
WEAK_CHG_STORM_COUNT);
|
|
vote(chg->usb_icl_votable, BOOST_BACK_VOTER,
|
|
false, 0);
|
|
vote(chg->usb_icl_votable, WEAK_CHARGER_VOTER,
|
|
false, 0);
|
|
}
|
|
}
|
|
|
|
/* Force 1500mA FCC on removal if fcc stepper is enabled */
|
|
if (chg->fcc_stepper_enable)
|
|
vote(chg->fcc_votable, FCC_STEPPER_VOTER,
|
|
true, 1500000);
|
|
|
|
if (chg->wa_flags & WEAK_ADAPTER_WA) {
|
|
chg->aicl_5v_threshold_mv =
|
|
chg->default_aicl_5v_threshold_mv;
|
|
chg->aicl_cont_threshold_mv =
|
|
chg->default_aicl_cont_threshold_mv;
|
|
|
|
smblib_set_charge_param(chg,
|
|
&chg->param.aicl_5v_threshold,
|
|
chg->aicl_5v_threshold_mv);
|
|
smblib_set_charge_param(chg,
|
|
&chg->param.aicl_cont_threshold,
|
|
chg->aicl_cont_threshold_mv);
|
|
chg->aicl_max_reached = false;
|
|
|
|
if (chg->chg_param.smb_version == PMI632_SUBTYPE)
|
|
schgm_flash_torch_priority(chg,
|
|
TORCH_BUCK_MODE);
|
|
|
|
data = chg->irq_info[USBIN_UV_IRQ].irq_data;
|
|
if (data) {
|
|
wdata = &data->storm_data;
|
|
reset_storm_count(wdata);
|
|
}
|
|
vote(chg->usb_icl_votable, AICL_THRESHOLD_VOTER,
|
|
false, 0);
|
|
}
|
|
|
|
rc = smblib_request_dpdm(chg, false);
|
|
if (rc < 0)
|
|
smblib_err(chg, "Couldn't disable DPDM rc=%d\n", rc);
|
|
|
|
smblib_update_usb_type(chg);
|
|
|
|
if (chg->mmi.factory_kill_armed && !factory_kill_disable) {
|
|
smblib_err(chg, "Factory kill power off\n");
|
|
kernel_power_off();
|
|
} else
|
|
chg->mmi.factory_kill_armed = false;
|
|
}
|
|
|
|
if (chg->connector_type == POWER_SUPPLY_CONNECTOR_MICRO_USB)
|
|
smblib_micro_usb_plugin(chg, vbus_rising);
|
|
|
|
vote(chg->temp_change_irq_disable_votable, DEFAULT_VOTER,
|
|
!vbus_rising, 0);
|
|
|
|
power_supply_changed(chg->usb_psy);
|
|
__pm_stay_awake(chg->mmi.smblib_mmi_hb_wake_source);
|
|
cancel_delayed_work(&chg->mmi.heartbeat_work);
|
|
schedule_delayed_work(&chg->mmi.heartbeat_work,
|
|
msecs_to_jiffies(0));
|
|
smblib_dbg(chg, PR_INTERRUPT, "IRQ: usbin-plugin %s\n",
|
|
vbus_rising ? "attached" : "detached");
|
|
}
|
|
|
|
irqreturn_t usb_plugin_irq_handler(int irq, void *data)
|
|
{
|
|
struct smb_irq_data *irq_data = data;
|
|
struct smb_charger *chg = irq_data->parent_data;
|
|
|
|
if (chg->pd_hard_reset)
|
|
smblib_usb_plugin_hard_reset_locked(chg);
|
|
else
|
|
smblib_usb_plugin_locked(chg);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static void smblib_handle_slow_plugin_timeout(struct smb_charger *chg,
|
|
bool rising)
|
|
{
|
|
smblib_dbg(chg, PR_INTERRUPT, "IRQ: slow-plugin-timeout %s\n",
|
|
rising ? "rising" : "falling");
|
|
}
|
|
|
|
static void smblib_handle_sdp_enumeration_done(struct smb_charger *chg,
|
|
bool rising)
|
|
{
|
|
smblib_dbg(chg, PR_INTERRUPT, "IRQ: sdp-enumeration-done %s\n",
|
|
rising ? "rising" : "falling");
|
|
}
|
|
|
|
#define APSD_EXTENDED_TIMEOUT_MS 400
|
|
/* triggers when HVDCP 3.0 authentication has finished */
|
|
static void smblib_handle_hvdcp_3p0_auth_done(struct smb_charger *chg,
|
|
bool rising)
|
|
{
|
|
const struct apsd_result *apsd_result;
|
|
int rc;
|
|
|
|
if (!rising)
|
|
return;
|
|
|
|
if (chg->mode == PARALLEL_MASTER)
|
|
vote(chg->pl_enable_votable_indirect, USBIN_V_VOTER, true, 0);
|
|
|
|
/* the APSD done handler will set the USB supply type */
|
|
apsd_result = smblib_get_apsd_result(chg);
|
|
|
|
if (apsd_result->bit & QC_3P0_BIT) {
|
|
/* for QC3, switch to CP if present */
|
|
if (chg->sec_cp_present) {
|
|
rc = smblib_select_sec_charger(chg,
|
|
POWER_SUPPLY_CHARGER_SEC_CP,
|
|
POWER_SUPPLY_CP_HVDCP3, false);
|
|
if (rc < 0)
|
|
dev_err(chg->dev,
|
|
"Couldn't enable secondary chargers rc=%d\n",
|
|
rc);
|
|
}
|
|
|
|
/* QC3.5 detection timeout */
|
|
if (!chg->apsd_ext_timeout &&
|
|
!timer_pending(&chg->apsd_timer)) {
|
|
smblib_dbg(chg, PR_MISC,
|
|
"APSD Extented timer started at %lld\n",
|
|
jiffies_to_msecs(jiffies));
|
|
|
|
mod_timer(&chg->apsd_timer,
|
|
msecs_to_jiffies(APSD_EXTENDED_TIMEOUT_MS)
|
|
+ jiffies);
|
|
}
|
|
}
|
|
|
|
smblib_dbg(chg, PR_INTERRUPT, "IRQ: hvdcp-3p0-auth-done rising; %s detected\n",
|
|
apsd_result->name);
|
|
}
|
|
|
|
static void smblib_handle_hvdcp_check_timeout(struct smb_charger *chg,
|
|
bool rising, bool qc_charger)
|
|
{
|
|
u32 hvdcp_ua = 0;
|
|
|
|
if (rising) {
|
|
if (qc_charger) {
|
|
hvdcp_ua = (chg->real_charger_type ==
|
|
POWER_SUPPLY_TYPE_USB_HVDCP) ?
|
|
chg->chg_param.hvdcp2_max_icl_ua :
|
|
HVDCP_CURRENT_UA;
|
|
|
|
/* enable HDC and ICL irq for QC2/3 charger */
|
|
vote(chg->limited_irq_disable_votable,
|
|
CHARGER_TYPE_VOTER, false, 0);
|
|
vote(chg->hdc_irq_disable_votable,
|
|
CHARGER_TYPE_VOTER, false, 0);
|
|
#ifdef QCOM_BASE
|
|
vote(chg->usb_icl_votable, SW_ICL_MAX_VOTER, true,
|
|
hvdcp_ua);
|
|
#endif
|
|
} else {
|
|
/* A plain DCP, enforce DCP ICL if specified */
|
|
vote(chg->usb_icl_votable, DCP_VOTER,
|
|
chg->dcp_icl_ua != -EINVAL, chg->dcp_icl_ua);
|
|
}
|
|
}
|
|
|
|
smblib_dbg(chg, PR_INTERRUPT, "IRQ: %s %s\n", __func__,
|
|
rising ? "rising" : "falling");
|
|
}
|
|
|
|
/* triggers when HVDCP is detected */
|
|
static void smblib_handle_hvdcp_detect_done(struct smb_charger *chg,
|
|
bool rising)
|
|
{
|
|
smblib_dbg(chg, PR_INTERRUPT, "IRQ: hvdcp-detect-done %s\n",
|
|
rising ? "rising" : "falling");
|
|
}
|
|
|
|
#ifdef QCOM_BASE
|
|
static void update_sw_icl_max(struct smb_charger *chg, int pst)
|
|
{
|
|
int typec_mode;
|
|
int rp_ua;
|
|
|
|
/* while PD is active it should have complete ICL control */
|
|
if (chg->pd_active)
|
|
return;
|
|
|
|
if (chg->typec_mode == POWER_SUPPLY_TYPEC_SINK_AUDIO_ADAPTER) {
|
|
vote(chg->usb_icl_votable, SW_ICL_MAX_VOTER, true, 500000);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* HVDCP 2/3, handled separately
|
|
*/
|
|
if (pst == POWER_SUPPLY_TYPE_USB_HVDCP
|
|
|| pst == POWER_SUPPLY_TYPE_USB_HVDCP_3)
|
|
return;
|
|
|
|
/* TypeC rp med or high, use rp value */
|
|
typec_mode = smblib_get_prop_typec_mode(chg);
|
|
if (typec_rp_med_high(chg, typec_mode)) {
|
|
rp_ua = get_rp_based_dcp_current(chg, typec_mode);
|
|
vote(chg->usb_icl_votable, SW_ICL_MAX_VOTER, true, rp_ua);
|
|
return;
|
|
}
|
|
|
|
/* rp-std or legacy, USB BC 1.2 */
|
|
switch (pst) {
|
|
case POWER_SUPPLY_TYPE_USB:
|
|
/*
|
|
* USB_PSY will vote to increase the current to 500/900mA once
|
|
* enumeration is done.
|
|
*/
|
|
if (!is_client_vote_enabled(chg->usb_icl_votable,
|
|
USB_PSY_VOTER)) {
|
|
/* if flash is active force 500mA */
|
|
vote(chg->usb_icl_votable, USB_PSY_VOTER, true,
|
|
is_flash_active(chg) ?
|
|
SDP_CURRENT_UA : SDP_100_MA);
|
|
}
|
|
vote(chg->usb_icl_votable, SW_ICL_MAX_VOTER, false, 0);
|
|
break;
|
|
case POWER_SUPPLY_TYPE_USB_CDP:
|
|
vote(chg->usb_icl_votable, SW_ICL_MAX_VOTER, true,
|
|
CDP_CURRENT_UA);
|
|
break;
|
|
case POWER_SUPPLY_TYPE_USB_DCP:
|
|
rp_ua = get_rp_based_dcp_current(chg, typec_mode);
|
|
vote(chg->usb_icl_votable, SW_ICL_MAX_VOTER, true, rp_ua);
|
|
break;
|
|
case POWER_SUPPLY_TYPE_USB_FLOAT:
|
|
/*
|
|
* limit ICL to 100mA, the USB driver will enumerate to check
|
|
* if this is a SDP and appropriately set the current
|
|
*/
|
|
vote(chg->usb_icl_votable, SW_ICL_MAX_VOTER, true,
|
|
SDP_100_MA);
|
|
break;
|
|
case POWER_SUPPLY_TYPE_UNKNOWN:
|
|
default:
|
|
vote(chg->usb_icl_votable, SW_ICL_MAX_VOTER, true,
|
|
SDP_100_MA);
|
|
break;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static void smblib_handle_apsd_done(struct smb_charger *chg, bool rising)
|
|
{
|
|
const struct apsd_result *apsd_result;
|
|
|
|
if (!rising)
|
|
return;
|
|
|
|
apsd_result = smblib_update_usb_type(chg);
|
|
|
|
#ifdef QCOM_BASE
|
|
update_sw_icl_max(chg, apsd_result->pst);
|
|
#endif
|
|
|
|
switch (apsd_result->bit) {
|
|
case SDP_CHARGER_BIT:
|
|
case CDP_CHARGER_BIT:
|
|
case FLOAT_CHARGER_BIT:
|
|
if (chg->use_extcon)
|
|
smblib_notify_device_mode(chg, true);
|
|
break;
|
|
case OCP_CHARGER_BIT:
|
|
case DCP_CHARGER_BIT:
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
chg->mmi.apsd_done = true;
|
|
|
|
smblib_dbg(chg, PR_INTERRUPT, "IRQ: apsd-done rising; %s detected\n",
|
|
apsd_result->name);
|
|
}
|
|
|
|
irqreturn_t usb_source_change_irq_handler(int irq, void *data)
|
|
{
|
|
struct smb_irq_data *irq_data = data;
|
|
struct smb_charger *chg = irq_data->parent_data;
|
|
int rc = 0;
|
|
u8 stat;
|
|
|
|
/* PD session is ongoing, ignore BC1.2 and QC detection */
|
|
if (chg->pd_active)
|
|
return IRQ_HANDLED;
|
|
|
|
rc = smblib_read(chg, APSD_STATUS_REG, &stat);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't read APSD_STATUS rc=%d\n", rc);
|
|
return IRQ_HANDLED;
|
|
}
|
|
smblib_dbg(chg, PR_INTERRUPT, "APSD_STATUS = 0x%02x\n", stat);
|
|
|
|
if ((chg->connector_type == POWER_SUPPLY_CONNECTOR_MICRO_USB)
|
|
&& (stat & APSD_DTC_STATUS_DONE_BIT)
|
|
&& !chg->uusb_apsd_rerun_done) {
|
|
/*
|
|
* Force re-run APSD to handle slow insertion related
|
|
* charger-mis-detection.
|
|
*/
|
|
chg->uusb_apsd_rerun_done = true;
|
|
smblib_rerun_apsd_if_required(chg);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
smblib_handle_apsd_done(chg,
|
|
(bool)(stat & APSD_DTC_STATUS_DONE_BIT));
|
|
|
|
smblib_handle_hvdcp_detect_done(chg,
|
|
(bool)(stat & QC_CHARGER_BIT));
|
|
|
|
smblib_handle_hvdcp_check_timeout(chg,
|
|
(bool)(stat & HVDCP_CHECK_TIMEOUT_BIT),
|
|
(bool)(stat & QC_CHARGER_BIT));
|
|
|
|
smblib_handle_hvdcp_3p0_auth_done(chg,
|
|
(bool)(stat & QC_AUTH_DONE_STATUS_BIT));
|
|
|
|
smblib_handle_sdp_enumeration_done(chg,
|
|
(bool)(stat & ENUMERATION_DONE_BIT));
|
|
|
|
smblib_handle_slow_plugin_timeout(chg,
|
|
(bool)(stat & SLOW_PLUGIN_TIMEOUT_BIT));
|
|
|
|
smblib_hvdcp_adaptive_voltage_change(chg);
|
|
|
|
power_supply_changed(chg->usb_psy);
|
|
|
|
rc = smblib_read(chg, APSD_STATUS_REG, &stat);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't read APSD_STATUS rc=%d\n", rc);
|
|
return IRQ_HANDLED;
|
|
}
|
|
smblib_dbg(chg, PR_INTERRUPT, "APSD_STATUS = 0x%02x\n", stat);
|
|
|
|
vote(chg->awake_votable, HEARTBEAT_VOTER, true, true);
|
|
cancel_delayed_work(&chg->mmi.heartbeat_work);
|
|
schedule_delayed_work(&chg->mmi.heartbeat_work,
|
|
msecs_to_jiffies(0));
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
enum alarmtimer_restart smblib_lpd_recheck_timer(struct alarm *alarm,
|
|
ktime_t time)
|
|
{
|
|
union power_supply_propval pval;
|
|
struct smb_charger *chg = container_of(alarm, struct smb_charger,
|
|
lpd_recheck_timer);
|
|
int rc;
|
|
|
|
if (chg->lpd_reason == LPD_MOISTURE_DETECTED) {
|
|
pval.intval = POWER_SUPPLY_TYPEC_PR_DUAL;
|
|
rc = smblib_set_prop_typec_power_role(chg, &pval);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't write 0x%02x to TYPE_C_INTRPT_ENB_SOFTWARE_CTRL rc=%d\n",
|
|
pval.intval, rc);
|
|
return ALARMTIMER_NORESTART;
|
|
}
|
|
chg->moisture_present = false;
|
|
power_supply_changed(chg->usb_psy);
|
|
} else {
|
|
rc = smblib_masked_write(chg, TYPE_C_INTERRUPT_EN_CFG_2_REG,
|
|
TYPEC_WATER_DETECTION_INT_EN_BIT,
|
|
TYPEC_WATER_DETECTION_INT_EN_BIT);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't set TYPE_C_INTERRUPT_EN_CFG_2_REG rc=%d\n",
|
|
rc);
|
|
return ALARMTIMER_NORESTART;
|
|
}
|
|
}
|
|
|
|
chg->lpd_stage = LPD_STAGE_NONE;
|
|
chg->lpd_reason = LPD_NONE;
|
|
|
|
return ALARMTIMER_NORESTART;
|
|
}
|
|
|
|
#define RSBU_K_300K_UV 3000000
|
|
static bool smblib_src_lpd(struct smb_charger *chg)
|
|
{
|
|
union power_supply_propval pval;
|
|
bool lpd_flag = false;
|
|
u8 stat;
|
|
int rc;
|
|
|
|
if (chg->lpd_disabled)
|
|
return false;
|
|
|
|
rc = smblib_read(chg, TYPE_C_SRC_STATUS_REG, &stat);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't read TYPE_C_SRC_STATUS_REG rc=%d\n",
|
|
rc);
|
|
return false;
|
|
}
|
|
|
|
switch (stat & DETECTED_SNK_TYPE_MASK) {
|
|
case SRC_DEBUG_ACCESS_BIT:
|
|
if (smblib_rsbux_low(chg, RSBU_K_300K_UV))
|
|
lpd_flag = true;
|
|
break;
|
|
case SRC_RD_RA_VCONN_BIT:
|
|
case SRC_RD_OPEN_BIT:
|
|
case AUDIO_ACCESS_RA_RA_BIT:
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (lpd_flag) {
|
|
chg->lpd_stage = LPD_STAGE_COMMIT;
|
|
pval.intval = POWER_SUPPLY_TYPEC_PR_SINK;
|
|
rc = smblib_set_prop_typec_power_role(chg, &pval);
|
|
if (rc < 0)
|
|
smblib_err(chg, "Couldn't write 0x%02x to TYPE_C_INTRPT_ENB_SOFTWARE_CTRL rc=%d\n",
|
|
pval.intval, rc);
|
|
chg->lpd_reason = LPD_MOISTURE_DETECTED;
|
|
chg->moisture_present = true;
|
|
vote(chg->usb_icl_votable, LPD_VOTER, true, 0);
|
|
alarm_start_relative(&chg->lpd_recheck_timer,
|
|
ms_to_ktime(60000));
|
|
power_supply_changed(chg->usb_psy);
|
|
} else {
|
|
chg->lpd_reason = LPD_NONE;
|
|
chg->typec_mode = smblib_get_prop_typec_mode(chg);
|
|
}
|
|
|
|
return lpd_flag;
|
|
}
|
|
|
|
static void typec_src_fault_condition_cfg(struct smb_charger *chg, bool src)
|
|
{
|
|
int rc;
|
|
u8 mask = USBIN_MID_COMP_FAULT_EN_BIT | USBIN_COLLAPSE_FAULT_EN_BIT;
|
|
|
|
rc = smblib_masked_write(chg, OTG_FAULT_CONDITION_CFG_REG, mask,
|
|
src ? 0 : mask);
|
|
if (rc < 0)
|
|
smblib_err(chg, "Couldn't write OTG_FAULT_CONDITION_CFG_REG rc=%d\n",
|
|
rc);
|
|
}
|
|
|
|
static void typec_sink_insertion(struct smb_charger *chg)
|
|
{
|
|
int rc;
|
|
|
|
typec_src_fault_condition_cfg(chg, true);
|
|
rc = smblib_set_charge_param(chg, &chg->param.freq_switcher,
|
|
chg->chg_freq.freq_above_otg_threshold);
|
|
if (rc < 0)
|
|
dev_err(chg->dev, "Error in setting freq_boost rc=%d\n", rc);
|
|
|
|
if (chg->use_extcon) {
|
|
smblib_notify_usb_host(chg, true);
|
|
chg->otg_present = true;
|
|
}
|
|
|
|
if (!chg->pr_swap_in_progress)
|
|
chg->ok_to_pd = (!(chg->pd_disabled) || chg->early_usb_attach)
|
|
&& !chg->pd_not_supported;
|
|
}
|
|
|
|
static void typec_src_insertion(struct smb_charger *chg)
|
|
{
|
|
int rc = 0;
|
|
u8 stat;
|
|
|
|
if (chg->pr_swap_in_progress) {
|
|
vote(chg->usb_icl_votable, SW_ICL_MAX_VOTER, false, 0);
|
|
return;
|
|
}
|
|
|
|
rc = smblib_read(chg, LEGACY_CABLE_STATUS_REG, &stat);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't read TYPE_C_STATE_MACHINE_STATUS_REG rc=%d\n",
|
|
rc);
|
|
return;
|
|
}
|
|
|
|
chg->typec_legacy = stat & TYPEC_LEGACY_CABLE_STATUS_BIT;
|
|
chg->ok_to_pd = (!(chg->typec_legacy || chg->pd_disabled)
|
|
|| chg->early_usb_attach) && !chg->pd_not_supported;
|
|
|
|
/* allow apsd proceed to detect QC2/3 */
|
|
if (!chg->ok_to_pd)
|
|
smblib_hvdcp_detect_try_enable(chg, true);
|
|
}
|
|
|
|
static void typec_ra_ra_insertion(struct smb_charger *chg)
|
|
{
|
|
vote(chg->usb_icl_votable, SW_ICL_MAX_VOTER, true, 500000);
|
|
vote(chg->usb_icl_votable, USB_PSY_VOTER, false, 0);
|
|
chg->ok_to_pd = false;
|
|
smblib_hvdcp_detect_enable(chg, true);
|
|
}
|
|
|
|
static const char * const dr_mode_text[] = {
|
|
"ufp", "dfp", "none"
|
|
};
|
|
|
|
static int smblib_force_dr_mode(struct smb_charger *chg, int mode)
|
|
{
|
|
int rc = 0;
|
|
|
|
switch (mode) {
|
|
case TYPEC_PORT_SNK:
|
|
rc = smblib_masked_write(chg, TYPE_C_MODE_CFG_REG,
|
|
TYPEC_POWER_ROLE_CMD_MASK, EN_SNK_ONLY_BIT);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't enable snk, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
break;
|
|
case TYPEC_PORT_SRC:
|
|
rc = smblib_masked_write(chg, TYPE_C_MODE_CFG_REG,
|
|
TYPEC_POWER_ROLE_CMD_MASK, EN_SRC_ONLY_BIT);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't enable src, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
break;
|
|
case TYPEC_PORT_DRP:
|
|
rc = smblib_masked_write(chg, TYPE_C_MODE_CFG_REG,
|
|
TYPEC_POWER_ROLE_CMD_MASK, 0);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't enable DRP, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
break;
|
|
default:
|
|
smblib_err(chg, "Power role %d not supported\n", mode);
|
|
return -EINVAL;
|
|
}
|
|
|
|
chg->dr_mode = mode;
|
|
|
|
return rc;
|
|
}
|
|
|
|
int smblib_typec_port_type_set(const struct typec_capability *cap,
|
|
enum typec_port_type type)
|
|
{
|
|
struct smb_charger *chg = container_of(cap,
|
|
struct smb_charger, typec_caps);
|
|
int rc = 0;
|
|
|
|
mutex_lock(&chg->typec_lock);
|
|
|
|
if ((chg->pr_swap_in_progress) || (type == TYPEC_PORT_DRP)) {
|
|
smblib_dbg(chg, PR_MISC, "Ignoring port type request type = %d swap_in_progress = %d\n",
|
|
type, chg->pr_swap_in_progress);
|
|
goto unlock;
|
|
}
|
|
|
|
chg->pr_swap_in_progress = true;
|
|
|
|
rc = smblib_force_dr_mode(chg, type);
|
|
if (rc < 0) {
|
|
chg->pr_swap_in_progress = false;
|
|
smblib_err(chg, "Failed to force mode, rc=%d\n", rc);
|
|
goto unlock;
|
|
}
|
|
|
|
smblib_dbg(chg, PR_MISC, "Requested role %s\n",
|
|
type ? "SINK" : "SOURCE");
|
|
|
|
/*
|
|
* As per the hardware requirements,
|
|
* schedule the work with required delay.
|
|
*/
|
|
if (!(delayed_work_pending(&chg->role_reversal_check))) {
|
|
cancel_delayed_work_sync(&chg->role_reversal_check);
|
|
schedule_delayed_work(&chg->role_reversal_check,
|
|
msecs_to_jiffies(ROLE_REVERSAL_DELAY_MS));
|
|
vote(chg->awake_votable, TYPEC_SWAP_VOTER, true, 0);
|
|
}
|
|
|
|
unlock:
|
|
mutex_unlock(&chg->typec_lock);
|
|
return rc;
|
|
}
|
|
|
|
static void smblib_typec_role_check_work(struct work_struct *work)
|
|
{
|
|
struct smb_charger *chg = container_of(work, struct smb_charger,
|
|
role_reversal_check.work);
|
|
int rc = 0;
|
|
|
|
mutex_lock(&chg->typec_lock);
|
|
|
|
switch (chg->dr_mode) {
|
|
case TYPEC_PORT_SNK:
|
|
if (chg->typec_mode < POWER_SUPPLY_TYPEC_SOURCE_DEFAULT) {
|
|
smblib_dbg(chg, PR_MISC, "Role reversal not latched to UFP in %d msecs. Resetting to DRP mode\n",
|
|
ROLE_REVERSAL_DELAY_MS);
|
|
rc = smblib_force_dr_mode(chg, TYPEC_PORT_DRP);
|
|
if (rc < 0)
|
|
smblib_err(chg, "Failed to set DRP mode, rc=%d\n",
|
|
rc);
|
|
} else {
|
|
chg->power_role = POWER_SUPPLY_TYPEC_PR_SINK;
|
|
typec_set_pwr_role(chg->typec_port, TYPEC_SINK);
|
|
typec_set_data_role(chg->typec_port, TYPEC_DEVICE);
|
|
smblib_dbg(chg, PR_MISC, "Role changed successfully to SINK");
|
|
}
|
|
break;
|
|
case TYPEC_PORT_SRC:
|
|
if (chg->typec_mode >= POWER_SUPPLY_TYPEC_SOURCE_DEFAULT
|
|
|| chg->typec_mode == POWER_SUPPLY_TYPEC_NONE) {
|
|
smblib_dbg(chg, PR_MISC, "Role reversal not latched to DFP in %d msecs. Resetting to DRP mode\n",
|
|
ROLE_REVERSAL_DELAY_MS);
|
|
chg->pr_swap_in_progress = false;
|
|
chg->typec_role_swap_failed = true;
|
|
rc = smblib_force_dr_mode(chg, TYPEC_PORT_DRP);
|
|
if (rc < 0)
|
|
smblib_err(chg, "Failed to set DRP mode, rc=%d\n",
|
|
rc);
|
|
} else {
|
|
chg->power_role = POWER_SUPPLY_TYPEC_PR_SOURCE;
|
|
typec_set_pwr_role(chg->typec_port, TYPEC_SOURCE);
|
|
typec_set_data_role(chg->typec_port, TYPEC_HOST);
|
|
smblib_dbg(chg, PR_MISC, "Role changed successfully to SOURCE");
|
|
}
|
|
break;
|
|
default:
|
|
pr_debug("Already in DRP mode\n");
|
|
break;
|
|
}
|
|
|
|
chg->pr_swap_in_progress = false;
|
|
vote(chg->awake_votable, TYPEC_SWAP_VOTER, false, 0);
|
|
mutex_unlock(&chg->typec_lock);
|
|
}
|
|
|
|
static void typec_sink_removal(struct smb_charger *chg)
|
|
{
|
|
int rc;
|
|
|
|
typec_src_fault_condition_cfg(chg, false);
|
|
rc = smblib_set_charge_param(chg, &chg->param.freq_switcher,
|
|
chg->chg_freq.freq_removal);
|
|
if (rc < 0)
|
|
dev_err(chg->dev, "Error in setting freq_removal rc=%d\n", rc);
|
|
|
|
if (chg->use_extcon) {
|
|
if (chg->otg_present)
|
|
smblib_notify_usb_host(chg, false);
|
|
chg->otg_present = false;
|
|
}
|
|
}
|
|
|
|
static void typec_src_removal(struct smb_charger *chg)
|
|
{
|
|
int rc;
|
|
struct smb_irq_data *data;
|
|
struct storm_watch *wdata;
|
|
int sec_charger;
|
|
u8 val[2] = {0};
|
|
union power_supply_propval propval;
|
|
|
|
sec_charger = chg->sec_pl_present ? POWER_SUPPLY_CHARGER_SEC_PL :
|
|
POWER_SUPPLY_CHARGER_SEC_NONE;
|
|
|
|
rc = smblib_select_sec_charger(chg, sec_charger, POWER_SUPPLY_CP_NONE,
|
|
false);
|
|
if (rc < 0)
|
|
dev_err(chg->dev,
|
|
"Couldn't disable secondary charger rc=%d\n", rc);
|
|
|
|
chg->qc3p5_detected = false;
|
|
typec_src_fault_condition_cfg(chg, false);
|
|
smblib_hvdcp_detect_try_enable(chg, false);
|
|
smblib_update_usb_type(chg);
|
|
|
|
if (chg->wa_flags & BOOST_BACK_WA) {
|
|
data = chg->irq_info[SWITCHER_POWER_OK_IRQ].irq_data;
|
|
if (data) {
|
|
wdata = &data->storm_data;
|
|
update_storm_count(wdata, WEAK_CHG_STORM_COUNT);
|
|
vote(chg->usb_icl_votable, BOOST_BACK_VOTER, false, 0);
|
|
vote(chg->usb_icl_votable, WEAK_CHARGER_VOTER,
|
|
false, 0);
|
|
}
|
|
}
|
|
|
|
cancel_delayed_work_sync(&chg->pl_enable_work);
|
|
|
|
/* reset input current limit voters */
|
|
#ifdef QCOM_BASE
|
|
vote(chg->usb_icl_votable, SW_ICL_MAX_VOTER, true,
|
|
is_flash_active(chg) ? SDP_CURRENT_UA : SDP_100_MA);
|
|
#else
|
|
vote(chg->usb_icl_votable, SW_ICL_MAX_VOTER, false, 0);
|
|
#endif
|
|
vote(chg->usb_icl_votable, PD_VOTER, false, 0);
|
|
vote(chg->usb_icl_votable, USB_PSY_VOTER, false, 0);
|
|
vote(chg->usb_icl_votable, DCP_VOTER, false, 0);
|
|
vote(chg->usb_icl_votable, SW_QC3_VOTER, false, 0);
|
|
vote(chg->usb_icl_votable, CTM_VOTER, false, 0);
|
|
vote(chg->usb_icl_votable, HVDCP2_ICL_VOTER, false, 0);
|
|
vote(chg->usb_icl_votable, HVDCP2_12V_ICL_VOTER, false, 0);
|
|
vote(chg->usb_icl_votable, CHG_TERMINATION_VOTER, false, 0);
|
|
vote(chg->usb_icl_votable, THERMAL_THROTTLE_VOTER, false, 0);
|
|
vote(chg->usb_icl_votable, LPD_VOTER, false, 0);
|
|
vote(chg->usb_icl_votable, ICL_LIMIT_VOTER, false, 0);
|
|
rc = smblib_get_prop_usb_present(chg, &propval);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Error getting USB Present rc = %d\n", rc);
|
|
chg->mmi.apsd_done = false;
|
|
chg->mmi.charger_rate = POWER_SUPPLY_CHARGE_RATE_NONE;
|
|
} else if (!propval.intval) {
|
|
chg->mmi.apsd_done = false;
|
|
chg->mmi.charger_rate = POWER_SUPPLY_CHARGE_RATE_NONE;
|
|
}
|
|
|
|
/* reset usb irq voters */
|
|
vote(chg->limited_irq_disable_votable, CHARGER_TYPE_VOTER,
|
|
true, 0);
|
|
vote(chg->hdc_irq_disable_votable, CHARGER_TYPE_VOTER, true, 0);
|
|
vote(chg->hdc_irq_disable_votable, HDC_IRQ_VOTER, false, 0);
|
|
|
|
/* reset parallel voters */
|
|
vote(chg->pl_disable_votable, PL_DELAY_VOTER, true, 0);
|
|
vote(chg->pl_disable_votable, PL_FCC_LOW_VOTER, false, 0);
|
|
vote(chg->pl_enable_votable_indirect, USBIN_I_VOTER, false, 0);
|
|
vote(chg->pl_enable_votable_indirect, USBIN_V_VOTER, false, 0);
|
|
vote(chg->awake_votable, PL_DELAY_VOTER, false, 0);
|
|
|
|
/* Remove SW thermal regulation WA votes */
|
|
vote(chg->usb_icl_votable, SW_THERM_REGULATION_VOTER, false, 0);
|
|
vote(chg->pl_disable_votable, SW_THERM_REGULATION_VOTER, false, 0);
|
|
vote(chg->dc_suspend_votable, SW_THERM_REGULATION_VOTER, false, 0);
|
|
if (chg->cp_disable_votable)
|
|
vote(chg->cp_disable_votable, SW_THERM_REGULATION_VOTER,
|
|
false, 0);
|
|
|
|
/* reset USBOV votes and cancel work */
|
|
cancel_delayed_work_sync(&chg->usbov_dbc_work);
|
|
vote(chg->awake_votable, USBOV_DBC_VOTER, false, 0);
|
|
chg->dbc_usbov = false;
|
|
|
|
chg->pulse_cnt = 0;
|
|
chg->usb_icl_delta_ua = 0;
|
|
#ifdef QCOM_BASE
|
|
chg->voltage_min_uv = MICRO_5V;
|
|
chg->voltage_max_uv = MICRO_5V;
|
|
#endif
|
|
chg->usbin_forced_max_uv = 0;
|
|
chg->chg_param.forced_main_fcc = 0;
|
|
|
|
/* Reset all CC mode votes */
|
|
vote(chg->fcc_main_votable, MAIN_FCC_VOTER, false, 0);
|
|
chg->adapter_cc_mode = 0;
|
|
chg->thermal_overheat = 0;
|
|
vote_override(chg->fcc_votable, CC_MODE_VOTER, false, 0);
|
|
vote_override(chg->usb_icl_votable, CC_MODE_VOTER, false, 0);
|
|
vote(chg->cp_disable_votable, OVERHEAT_LIMIT_VOTER, false, 0);
|
|
vote(chg->usb_icl_votable, OVERHEAT_LIMIT_VOTER, false, 0);
|
|
chg->pd_contract_uv = 0;
|
|
/* write back the default FLOAT charger configuration */
|
|
rc = smblib_masked_write(chg, USBIN_OPTIONS_2_CFG_REG,
|
|
(u8)FLOAT_OPTIONS_MASK, chg->float_cfg);
|
|
if (rc < 0)
|
|
smblib_err(chg, "Couldn't write float charger options rc=%d\n",
|
|
rc);
|
|
|
|
if (chg->sdam_base) {
|
|
rc = smblib_write(chg,
|
|
chg->sdam_base + SDAM_QC_DET_STATUS_REG, 0);
|
|
if (rc < 0)
|
|
pr_err("Couldn't clear SDAM QC status rc=%d\n", rc);
|
|
|
|
rc = smblib_batch_write(chg,
|
|
chg->sdam_base + SDAM_QC_ADC_LSB_REG, val, 2);
|
|
if (rc < 0)
|
|
pr_err("Couldn't clear SDAM ADC status rc=%d\n", rc);
|
|
}
|
|
|
|
/* reconfigure allowed voltage for HVDCP */
|
|
rc = smblib_set_adapter_allowance(chg,
|
|
USBIN_ADAPTER_ALLOW_5V_TO_9V);
|
|
if (rc < 0)
|
|
smblib_err(chg, "Couldn't set USBIN_ADAPTER_ALLOW_5V_TO_9V rc=%d\n",
|
|
rc);
|
|
|
|
if (!chg->pr_swap_in_progress) {
|
|
rc = smblib_usb_pd_adapter_allowance_override(chg, FORCE_NULL);
|
|
if (rc < 0)
|
|
smblib_err(chg, "Couldn't set FORCE_NULL rc=%d\n", rc);
|
|
|
|
rc = smblib_set_charge_param(chg,
|
|
&chg->param.aicl_cont_threshold,
|
|
chg->default_aicl_cont_threshold_mv);
|
|
if (rc < 0)
|
|
smblib_err(chg, "Couldn't restore aicl_cont_threshold, rc=%d",
|
|
rc);
|
|
}
|
|
/*
|
|
* if non-compliant charger caused UV, restore original max pulses
|
|
* and turn SUSPEND_ON_COLLAPSE_USBIN_BIT back on.
|
|
*/
|
|
if (chg->qc2_unsupported_voltage) {
|
|
rc = smblib_masked_write(chg, HVDCP_PULSE_COUNT_MAX_REG,
|
|
HVDCP_PULSE_COUNT_MAX_QC2_MASK,
|
|
chg->qc2_max_pulses);
|
|
if (rc < 0)
|
|
smblib_err(chg, "Couldn't restore max pulses rc=%d\n",
|
|
rc);
|
|
|
|
rc = smblib_masked_write(chg, USBIN_AICL_OPTIONS_CFG_REG,
|
|
SUSPEND_ON_COLLAPSE_USBIN_BIT,
|
|
SUSPEND_ON_COLLAPSE_USBIN_BIT);
|
|
if (rc < 0)
|
|
smblib_err(chg, "Couldn't turn on SUSPEND_ON_COLLAPSE_USBIN_BIT rc=%d\n",
|
|
rc);
|
|
|
|
chg->qc2_unsupported_voltage = QC2_COMPLIANT;
|
|
}
|
|
|
|
if (chg->hvdcp_is_disable &&
|
|
((!chg->ext_ovp_greater_12v && !chg->qc_usbov) ||
|
|
(chg->ext_ovp_greater_12v && chg->qc_usbov))) {
|
|
smblib_err(chg, "Re-enable HVDCP as type-c removal\n");
|
|
smblib_hvdcp_enable_mmi(chg, true);
|
|
}
|
|
chg->qc_usbov = false;
|
|
|
|
if (chg->use_extcon)
|
|
smblib_notify_device_mode(chg, false);
|
|
|
|
chg->typec_legacy = false;
|
|
|
|
chg->mmi.charging_limit_modes = CHARGING_LIMIT_OFF;
|
|
chg->mmi.hvdcp3_con = false;
|
|
chg->mmi.vbus_inc_cnt = 0;
|
|
vote(chg->awake_votable, HEARTBEAT_VOTER, true, true);
|
|
cancel_delayed_work(&chg->mmi.heartbeat_work);
|
|
schedule_delayed_work(&chg->mmi.heartbeat_work,
|
|
msecs_to_jiffies(0));
|
|
del_timer_sync(&chg->apsd_timer);
|
|
chg->apsd_ext_timeout = false;
|
|
}
|
|
|
|
static void typec_mode_unattached(struct smb_charger *chg)
|
|
{
|
|
#ifdef QCOM_BASE
|
|
vote(chg->usb_icl_votable, SW_ICL_MAX_VOTER, true, USBIN_100MA);
|
|
#endif
|
|
}
|
|
#ifdef QCOM_BASE
|
|
static void smblib_handle_rp_change(struct smb_charger *chg, int typec_mode)
|
|
{
|
|
const struct apsd_result *apsd = smblib_get_apsd_result(chg);
|
|
|
|
/*
|
|
* We want the ICL vote @ 100mA for a FLOAT charger
|
|
* until the detection by the USB stack is complete.
|
|
* Ignore the Rp changes unless there is a
|
|
* pre-existing valid vote or FLOAT is configured for
|
|
* SDP current.
|
|
*/
|
|
if (apsd->pst == POWER_SUPPLY_TYPE_USB_FLOAT) {
|
|
if (get_client_vote(chg->usb_icl_votable, SW_ICL_MAX_VOTER)
|
|
<= USBIN_100MA
|
|
|| (chg->float_cfg & FLOAT_OPTIONS_MASK)
|
|
== FORCE_FLOAT_SDP_CFG_BIT)
|
|
return;
|
|
}
|
|
|
|
update_sw_icl_max(chg, apsd->pst);
|
|
|
|
smblib_dbg(chg, PR_MISC, "CC change old_mode=%d new_mode=%d\n",
|
|
chg->typec_mode, typec_mode);
|
|
}
|
|
#endif
|
|
|
|
static void smblib_lpd_launch_ra_open_work(struct smb_charger *chg)
|
|
{
|
|
u8 stat;
|
|
int rc;
|
|
|
|
if (chg->lpd_disabled)
|
|
return;
|
|
|
|
rc = smblib_read(chg, TYPE_C_MISC_STATUS_REG, &stat);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't read TYPE_C_MISC_STATUS_REG rc=%d\n",
|
|
rc);
|
|
return;
|
|
}
|
|
|
|
if (!(stat & TYPEC_TCCDEBOUNCE_DONE_STATUS_BIT)
|
|
&& chg->lpd_stage == LPD_STAGE_NONE) {
|
|
chg->lpd_stage = LPD_STAGE_FLOAT;
|
|
cancel_delayed_work_sync(&chg->lpd_ra_open_work);
|
|
vote(chg->awake_votable, LPD_VOTER, true, 0);
|
|
schedule_delayed_work(&chg->lpd_ra_open_work,
|
|
msecs_to_jiffies(300));
|
|
}
|
|
}
|
|
|
|
irqreturn_t typec_or_rid_detection_change_irq_handler(int irq, void *data)
|
|
{
|
|
struct smb_irq_data *irq_data = data;
|
|
struct smb_charger *chg = irq_data->parent_data;
|
|
|
|
smblib_dbg(chg, PR_INTERRUPT, "IRQ: %s\n", irq_data->name);
|
|
|
|
if (chg->connector_type == POWER_SUPPLY_CONNECTOR_MICRO_USB) {
|
|
if (chg->uusb_moisture_protection_enabled) {
|
|
/*
|
|
* Adding pm_stay_awake as because pm_relax is called
|
|
* on exit path from the work routine.
|
|
*/
|
|
pm_stay_awake(chg->dev);
|
|
schedule_work(&chg->moisture_protection_work);
|
|
}
|
|
|
|
cancel_delayed_work_sync(&chg->uusb_otg_work);
|
|
/*
|
|
* Skip OTG enablement if RID interrupt triggers with moisture
|
|
* protection still enabled.
|
|
*/
|
|
if (!chg->moisture_present) {
|
|
vote(chg->awake_votable, OTG_DELAY_VOTER, true, 0);
|
|
smblib_dbg(chg, PR_INTERRUPT, "Scheduling OTG work\n");
|
|
schedule_delayed_work(&chg->uusb_otg_work,
|
|
msecs_to_jiffies(chg->otg_delay_ms));
|
|
}
|
|
|
|
goto out;
|
|
}
|
|
|
|
if (chg->pr_swap_in_progress || chg->pd_hard_reset)
|
|
goto out;
|
|
|
|
smblib_lpd_launch_ra_open_work(chg);
|
|
|
|
if (chg->usb_psy)
|
|
power_supply_changed(chg->usb_psy);
|
|
|
|
out:
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
irqreturn_t typec_state_change_irq_handler(int irq, void *data)
|
|
{
|
|
struct smb_irq_data *irq_data = data;
|
|
struct smb_charger *chg = irq_data->parent_data;
|
|
int typec_mode, icl_vote_ma;
|
|
bool typec_chg = false;
|
|
|
|
if (chg->connector_type == POWER_SUPPLY_CONNECTOR_MICRO_USB) {
|
|
smblib_dbg(chg, PR_INTERRUPT,
|
|
"Ignoring for micro USB\n");
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
typec_mode = smblib_get_prop_typec_mode(chg);
|
|
if (chg->sink_src_mode != UNATTACHED_MODE
|
|
&& (typec_mode != chg->typec_mode))
|
|
#ifdef QCOM_BASE
|
|
smblib_handle_rp_change(chg, typec_mode);
|
|
#else
|
|
typec_chg = true;
|
|
#endif
|
|
|
|
chg->typec_mode = typec_mode;
|
|
|
|
if (typec_chg) {
|
|
icl_vote_ma = get_client_vote(chg->usb_icl_votable,
|
|
HEARTBEAT_VOTER) / 1000;
|
|
if ((typec_mode == POWER_SUPPLY_TYPEC_SOURCE_MEDIUM) &&
|
|
(icl_vote_ma > 1500))
|
|
vote(chg->usb_icl_votable, HEARTBEAT_VOTER,
|
|
true, 1500000);
|
|
else if ((typec_mode == POWER_SUPPLY_TYPEC_SOURCE_DEFAULT) &&
|
|
(icl_vote_ma > 500))
|
|
vote(chg->usb_icl_votable, HEARTBEAT_VOTER,
|
|
true, 500000);
|
|
vote(chg->awake_votable, HEARTBEAT_VOTER, true, true);
|
|
cancel_delayed_work(&chg->mmi.heartbeat_work);
|
|
schedule_delayed_work(&chg->mmi.heartbeat_work,
|
|
msecs_to_jiffies(0));
|
|
}
|
|
|
|
smblib_dbg(chg, PR_INTERRUPT, "IRQ: cc-state-change; Type-C %s detected\n",
|
|
smblib_typec_mode_name[chg->typec_mode]);
|
|
|
|
power_supply_changed(chg->usb_psy);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static void smblib_lpd_clear_ra_open_work(struct smb_charger *chg)
|
|
{
|
|
if (chg->lpd_disabled)
|
|
return;
|
|
|
|
cancel_delayed_work_sync(&chg->lpd_detach_work);
|
|
chg->lpd_stage = LPD_STAGE_FLOAT_CANCEL;
|
|
cancel_delayed_work_sync(&chg->lpd_ra_open_work);
|
|
vote(chg->awake_votable, LPD_VOTER, false, 0);
|
|
}
|
|
|
|
#define TYPEC_DETACH_DETECT_DELAY_MS 2000
|
|
irqreturn_t typec_attach_detach_irq_handler(int irq, void *data)
|
|
{
|
|
struct smb_irq_data *irq_data = data;
|
|
struct smb_charger *chg = irq_data->parent_data;
|
|
u8 stat;
|
|
bool attached = false;
|
|
int rc;
|
|
|
|
/* IRQ not expected to be executed for uUSB, return */
|
|
if (chg->connector_type == POWER_SUPPLY_CONNECTOR_MICRO_USB)
|
|
return IRQ_HANDLED;
|
|
|
|
smblib_dbg(chg, PR_INTERRUPT, "IRQ: %s\n", irq_data->name);
|
|
|
|
rc = smblib_read(chg, TYPE_C_STATE_MACHINE_STATUS_REG, &stat);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't read TYPE_C_STATE_MACHINE_STATUS_REG rc=%d\n",
|
|
rc);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
attached = !!(stat & TYPEC_ATTACH_DETACH_STATE_BIT);
|
|
|
|
if (attached) {
|
|
smblib_lpd_clear_ra_open_work(chg);
|
|
|
|
rc = smblib_read(chg, TYPE_C_MISC_STATUS_REG, &stat);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't read TYPE_C_MISC_STATUS_REG rc=%d\n",
|
|
rc);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
if (smblib_get_prop_dfp_mode(chg) ==
|
|
POWER_SUPPLY_TYPEC_SINK_AUDIO_ADAPTER) {
|
|
chg->sink_src_mode = AUDIO_ACCESS_MODE;
|
|
typec_ra_ra_insertion(chg);
|
|
} else if (stat & SNK_SRC_MODE_BIT) {
|
|
if (smblib_src_lpd(chg))
|
|
return IRQ_HANDLED;
|
|
chg->sink_src_mode = SRC_MODE;
|
|
typec_sink_insertion(chg);
|
|
} else {
|
|
chg->sink_src_mode = SINK_MODE;
|
|
typec_src_insertion(chg);
|
|
}
|
|
|
|
rc = typec_partner_register(chg);
|
|
if (rc < 0)
|
|
smblib_err(chg, "failed to register partner rc =%d\n",
|
|
rc);
|
|
} else {
|
|
switch (chg->sink_src_mode) {
|
|
case SRC_MODE:
|
|
typec_sink_removal(chg);
|
|
break;
|
|
case SINK_MODE:
|
|
case AUDIO_ACCESS_MODE:
|
|
typec_src_removal(chg);
|
|
break;
|
|
case UNATTACHED_MODE:
|
|
default:
|
|
typec_mode_unattached(chg);
|
|
break;
|
|
}
|
|
|
|
if (!chg->pr_swap_in_progress) {
|
|
chg->ok_to_pd = false;
|
|
chg->sink_src_mode = UNATTACHED_MODE;
|
|
chg->early_usb_attach = false;
|
|
smblib_apsd_enable(chg, true);
|
|
}
|
|
|
|
/*
|
|
* Restore DRP mode on type-C cable disconnect if role
|
|
* swap is not in progress, to ensure forced sink or src
|
|
* mode configuration is reset properly.
|
|
*/
|
|
mutex_lock(&chg->typec_lock);
|
|
|
|
if (chg->typec_port && !chg->pr_swap_in_progress) {
|
|
|
|
/*
|
|
* Schedule the work to differentiate actual removal
|
|
* of cable and detach interrupt during role swap,
|
|
* unregister the partner only during actual cable
|
|
* removal.
|
|
*/
|
|
cancel_delayed_work(&chg->pr_swap_detach_work);
|
|
vote(chg->awake_votable, DETACH_DETECT_VOTER, true, 0);
|
|
schedule_delayed_work(&chg->pr_swap_detach_work,
|
|
msecs_to_jiffies(TYPEC_DETACH_DETECT_DELAY_MS));
|
|
smblib_force_dr_mode(chg, TYPEC_PORT_DRP);
|
|
/*
|
|
* To handle cable removal during role
|
|
* swap failure.
|
|
*/
|
|
chg->typec_role_swap_failed = false;
|
|
}
|
|
|
|
mutex_unlock(&chg->typec_lock);
|
|
|
|
if (chg->lpd_stage == LPD_STAGE_FLOAT_CANCEL)
|
|
schedule_delayed_work(&chg->lpd_detach_work,
|
|
msecs_to_jiffies(1000));
|
|
}
|
|
|
|
rc = smblib_masked_write(chg, USB_CMD_PULLDOWN_REG,
|
|
EN_PULLDOWN_USB_IN_BIT,
|
|
attached ? 0 : EN_PULLDOWN_USB_IN_BIT);
|
|
if (rc < 0)
|
|
smblib_err(chg, "Couldn't configure pulldown on USB_IN rc=%d\n",
|
|
rc);
|
|
if (chg->dr_inst)
|
|
dual_role_instance_changed(chg->dr_inst);
|
|
|
|
power_supply_changed(chg->usb_psy);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static void dcin_aicl(struct smb_charger *chg)
|
|
{
|
|
int rc, icl, icl_save;
|
|
int input_present;
|
|
bool aicl_done = true;
|
|
|
|
/*
|
|
* Hold awake votable to prevent pm_relax being called prior to
|
|
* completion of this work.
|
|
*/
|
|
vote(chg->awake_votable, DCIN_AICL_VOTER, true, 0);
|
|
|
|
increment:
|
|
mutex_lock(&chg->dcin_aicl_lock);
|
|
|
|
rc = smblib_get_charge_param(chg, &chg->param.dc_icl, &icl);
|
|
if (rc < 0)
|
|
goto err;
|
|
|
|
if (icl == chg->wls_icl_ua) {
|
|
/* Upper limit reached; do nothing */
|
|
smblib_dbg(chg, PR_WLS, "hit max ICL: stop\n");
|
|
|
|
rc = smblib_is_input_present(chg, &input_present);
|
|
if (rc < 0 || !(input_present & INPUT_PRESENT_DC))
|
|
aicl_done = false;
|
|
|
|
goto unlock;
|
|
}
|
|
|
|
icl = min(chg->wls_icl_ua, icl + DCIN_ICL_STEP_UA);
|
|
icl_save = icl;
|
|
|
|
rc = smblib_set_charge_param(chg, &chg->param.dc_icl, icl);
|
|
if (rc < 0)
|
|
goto err;
|
|
|
|
mutex_unlock(&chg->dcin_aicl_lock);
|
|
|
|
smblib_dbg(chg, PR_WLS, "icl: %d mA\n", (icl / 1000));
|
|
|
|
/* Check to see if DC is still present before and after sleep */
|
|
rc = smblib_is_input_present(chg, &input_present);
|
|
if (rc < 0 || !(input_present & INPUT_PRESENT_DC)) {
|
|
aicl_done = false;
|
|
goto unvote;
|
|
}
|
|
|
|
/*
|
|
* Wait awhile to check for any DCIN_UVs (the UV handler reduces the
|
|
* ICL). If the adaptor collapses, the ICL read after waking up will be
|
|
* lesser, indicating that the AICL process is complete.
|
|
*/
|
|
msleep(500);
|
|
|
|
rc = smblib_is_input_present(chg, &input_present);
|
|
if (rc < 0 || !(input_present & INPUT_PRESENT_DC)) {
|
|
aicl_done = false;
|
|
goto unvote;
|
|
}
|
|
|
|
mutex_lock(&chg->dcin_aicl_lock);
|
|
|
|
rc = smblib_get_charge_param(chg, &chg->param.dc_icl, &icl);
|
|
if (rc < 0)
|
|
goto err;
|
|
|
|
if (icl < icl_save) {
|
|
smblib_dbg(chg, PR_WLS, "done: icl: %d mA\n", (icl / 1000));
|
|
goto unlock;
|
|
}
|
|
|
|
mutex_unlock(&chg->dcin_aicl_lock);
|
|
|
|
goto increment;
|
|
|
|
err:
|
|
aicl_done = false;
|
|
unlock:
|
|
mutex_unlock(&chg->dcin_aicl_lock);
|
|
unvote:
|
|
vote(chg->awake_votable, DCIN_AICL_VOTER, false, 0);
|
|
chg->dcin_aicl_done = aicl_done;
|
|
}
|
|
|
|
static void dcin_aicl_work(struct work_struct *work)
|
|
{
|
|
struct smb_charger *chg = container_of(work, struct smb_charger,
|
|
dcin_aicl_work);
|
|
dcin_aicl(chg);
|
|
}
|
|
|
|
static enum alarmtimer_restart dcin_aicl_alarm_cb(struct alarm *alarm,
|
|
ktime_t now)
|
|
{
|
|
struct smb_charger *chg = container_of(alarm, struct smb_charger,
|
|
dcin_aicl_alarm);
|
|
|
|
smblib_dbg(chg, PR_WLS, "rerunning DCIN AICL\n");
|
|
|
|
pm_stay_awake(chg->dev);
|
|
schedule_work(&chg->dcin_aicl_work);
|
|
|
|
return ALARMTIMER_NORESTART;
|
|
}
|
|
#ifdef QCOM_BASE
|
|
static void dcin_icl_decrement(struct smb_charger *chg)
|
|
{
|
|
int rc, icl;
|
|
ktime_t now = ktime_get();
|
|
|
|
rc = smblib_get_charge_param(chg, &chg->param.dc_icl, &icl);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "reading DCIN ICL failed: %d\n", rc);
|
|
return;
|
|
}
|
|
|
|
if (icl == DCIN_ICL_MIN_UA) {
|
|
/* Cannot possibly decrease ICL any further - do nothing */
|
|
smblib_dbg(chg, PR_WLS, "hit min ICL: stop\n");
|
|
return;
|
|
}
|
|
|
|
/* Reduce ICL by 100 mA if 3 UVs happen in a row */
|
|
if (ktime_us_delta(now, chg->dcin_uv_last_time) > (200 * 1000)) {
|
|
chg->dcin_uv_count = 0;
|
|
} else if (chg->dcin_uv_count >= 3) {
|
|
icl -= DCIN_ICL_STEP_UA;
|
|
|
|
smblib_dbg(chg, PR_WLS, "icl: %d mA\n", (icl / 1000));
|
|
rc = smblib_set_charge_param(chg, &chg->param.dc_icl, icl);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "setting DCIN ICL failed: %d\n", rc);
|
|
return;
|
|
}
|
|
|
|
chg->dcin_uv_count = 0;
|
|
}
|
|
|
|
chg->dcin_uv_last_time = now;
|
|
}
|
|
|
|
irqreturn_t dcin_uv_irq_handler(int irq, void *data)
|
|
{
|
|
struct smb_irq_data *irq_data = data;
|
|
struct smb_charger *chg = irq_data->parent_data;
|
|
|
|
mutex_lock(&chg->dcin_aicl_lock);
|
|
|
|
chg->dcin_uv_count++;
|
|
smblib_dbg(chg, (PR_WLS | PR_INTERRUPT), "DCIN UV count: %d\n",
|
|
chg->dcin_uv_count);
|
|
dcin_icl_decrement(chg);
|
|
|
|
mutex_unlock(&chg->dcin_aicl_lock);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
#endif
|
|
|
|
irqreturn_t dc_plugin_irq_handler(int irq, void *data)
|
|
{
|
|
struct smb_irq_data *irq_data = data;
|
|
struct smb_charger *chg = irq_data->parent_data;
|
|
union power_supply_propval pval;
|
|
int input_present;
|
|
bool dcin_present, vbus_present;
|
|
int rc, wireless_vout = 0, wls_set = 0;
|
|
int sec_charger;
|
|
|
|
rc = smblib_get_prop_vph_voltage_now(chg, &pval);
|
|
if (rc < 0)
|
|
return IRQ_HANDLED;
|
|
|
|
/* 2*VPH, with a granularity of 100mV */
|
|
wireless_vout = ((pval.intval * 2) / 100000) * 100000;
|
|
|
|
rc = smblib_is_input_present(chg, &input_present);
|
|
if (rc < 0)
|
|
return IRQ_HANDLED;
|
|
|
|
dcin_present = input_present & INPUT_PRESENT_DC;
|
|
vbus_present = input_present & INPUT_PRESENT_USB;
|
|
|
|
if (!chg->cp_ilim_votable)
|
|
chg->cp_ilim_votable = find_votable("CP_ILIM");
|
|
|
|
if (dcin_present && !vbus_present) {
|
|
cancel_work_sync(&chg->dcin_aicl_work);
|
|
|
|
/* Reset DCIN ICL to 100 mA */
|
|
mutex_lock(&chg->dcin_aicl_lock);
|
|
rc = smblib_set_charge_param(chg, &chg->param.dc_icl,
|
|
DCIN_ICL_MIN_UA);
|
|
mutex_unlock(&chg->dcin_aicl_lock);
|
|
if (rc < 0)
|
|
return IRQ_HANDLED;
|
|
|
|
smblib_dbg(chg, (PR_WLS | PR_INTERRUPT), "reset: icl: 100 mA\n");
|
|
|
|
/*
|
|
* Remove USB's CP ILIM vote - inapplicable for wireless
|
|
* parallel charging.
|
|
*/
|
|
if (chg->cp_ilim_votable)
|
|
vote(chg->cp_ilim_votable, ICL_CHANGE_VOTER, false, 0);
|
|
|
|
if (chg->sec_cp_present) {
|
|
/*
|
|
* If CP output topology is VBATT, limit main charger's
|
|
* FCC share and let the CPs handle the rest.
|
|
*/
|
|
if (is_cp_topo_vbatt(chg))
|
|
vote(chg->fcc_main_votable,
|
|
WLS_PL_CHARGING_VOTER, true, 800000);
|
|
|
|
rc = smblib_get_prop_batt_status(chg, &pval);
|
|
if (rc < 0)
|
|
smblib_err(chg, "Couldn't read batt status rc=%d\n",
|
|
rc);
|
|
|
|
wls_set = (pval.intval == POWER_SUPPLY_STATUS_FULL) ?
|
|
MICRO_5V : wireless_vout;
|
|
|
|
pval.intval = wls_set;
|
|
rc = smblib_set_prop_voltage_wls_output(chg, &pval);
|
|
if (rc < 0)
|
|
dev_err(chg->dev, "Couldn't set dc voltage to 2*vph rc=%d\n",
|
|
rc);
|
|
|
|
rc = smblib_select_sec_charger(chg,
|
|
POWER_SUPPLY_CHARGER_SEC_CP,
|
|
POWER_SUPPLY_CP_WIRELESS, false);
|
|
if (rc < 0)
|
|
dev_err(chg->dev, "Couldn't enable secondary chargers rc=%d\n",
|
|
rc);
|
|
} else {
|
|
/*
|
|
* If no secondary charger is present, commence
|
|
* wireless charging at 5 V by default.
|
|
*/
|
|
pval.intval = 5000000;
|
|
rc = smblib_set_prop_voltage_wls_output(chg, &pval);
|
|
if (rc < 0)
|
|
dev_err(chg->dev, "Couldn't set dc voltage to 5 V rc=%d\n",
|
|
rc);
|
|
}
|
|
|
|
schedule_work(&chg->dcin_aicl_work);
|
|
} else {
|
|
if (chg->cp_reason == POWER_SUPPLY_CP_WIRELESS) {
|
|
sec_charger = chg->sec_pl_present ?
|
|
POWER_SUPPLY_CHARGER_SEC_PL :
|
|
POWER_SUPPLY_CHARGER_SEC_NONE;
|
|
rc = smblib_select_sec_charger(chg, sec_charger,
|
|
POWER_SUPPLY_CP_NONE, false);
|
|
if (rc < 0)
|
|
dev_err(chg->dev, "Couldn't disable secondary charger rc=%d\n",
|
|
rc);
|
|
}
|
|
|
|
vote(chg->dc_suspend_votable, CHG_TERMINATION_VOTER, false, 0);
|
|
vote(chg->fcc_main_votable, WLS_PL_CHARGING_VOTER, false, 0);
|
|
|
|
chg->last_wls_vout = 0;
|
|
chg->dcin_aicl_done = false;
|
|
chg->dcin_icl_user_set = false;
|
|
}
|
|
|
|
/*
|
|
* Vote for 1500mA FCC upon WLS detach and remove vote upon attach if
|
|
* FCC stepper is enabled.
|
|
*/
|
|
if (chg->fcc_stepper_enable && !vbus_present)
|
|
vote(chg->fcc_votable, FCC_STEPPER_VOTER, !dcin_present,
|
|
dcin_present ? 0 : 1500000);
|
|
|
|
chg->mmi.dc_ebmax_current_ma = chg->mmi.dc_ebmax_current_ma_default;
|
|
chg->mmi.cl_ebsrc = chg->mmi.dc_ebmax_current_ma_default;
|
|
|
|
if (chg->dc_psy)
|
|
power_supply_changed(chg->dc_psy);
|
|
|
|
smblib_dbg(chg, (PR_WLS | PR_INTERRUPT), "dcin_present= %d, usbin_present= %d, cp_reason = %d\n",
|
|
dcin_present, vbus_present, chg->cp_reason);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
irqreturn_t high_duty_cycle_irq_handler(int irq, void *data)
|
|
{
|
|
struct smb_irq_data *irq_data = data;
|
|
struct smb_charger *chg = irq_data->parent_data;
|
|
|
|
chg->is_hdc = true;
|
|
/*
|
|
* Disable usb IRQs after the flag set and re-enable IRQs after
|
|
* the flag cleared in the delayed work queue, to avoid any IRQ
|
|
* storming during the delays
|
|
*/
|
|
vote(chg->hdc_irq_disable_votable, HDC_IRQ_VOTER, true, 0);
|
|
|
|
schedule_delayed_work(&chg->clear_hdc_work, msecs_to_jiffies(60));
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static void smblib_bb_removal_work(struct work_struct *work)
|
|
{
|
|
struct smb_charger *chg = container_of(work, struct smb_charger,
|
|
bb_removal_work.work);
|
|
|
|
vote(chg->usb_icl_votable, BOOST_BACK_VOTER, false, 0);
|
|
vote(chg->awake_votable, BOOST_BACK_VOTER, false, 0);
|
|
}
|
|
|
|
#define BOOST_BACK_UNVOTE_DELAY_MS 750
|
|
#define BOOST_BACK_STORM_COUNT 3
|
|
#define WEAK_CHG_STORM_COUNT 8
|
|
irqreturn_t switcher_power_ok_irq_handler(int irq, void *data)
|
|
{
|
|
struct smb_irq_data *irq_data = data;
|
|
struct smb_charger *chg = irq_data->parent_data;
|
|
struct storm_watch *wdata = &irq_data->storm_data;
|
|
int pok_irq = chg->irq_info[SWITCHER_POWER_OK_IRQ].irq;
|
|
int rc;
|
|
u8 stat, susp;
|
|
|
|
if (!(chg->wa_flags & BOOST_BACK_WA))
|
|
return IRQ_HANDLED;
|
|
|
|
rc = smblib_read(chg, POWER_PATH_STATUS_REG, &stat);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't read POWER_PATH_STATUS rc=%d\n", rc);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
rc = smblib_read(chg, USBIN_CMD_IL_REG, &susp);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't read USBIN_CMD_IL rc=%d\n", rc);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
/* skip suspending input if its already suspended by some other voter */
|
|
if ((stat & USE_USBIN_BIT) && (susp & USBIN_SUSPEND_BIT))
|
|
return IRQ_HANDLED;
|
|
|
|
if (stat & USE_DCIN_BIT)
|
|
return IRQ_HANDLED;
|
|
|
|
if (is_storming(&irq_data->storm_data)) {
|
|
chg->reverse_boost = true;
|
|
stat = 0;
|
|
rc = smblib_read(chg, TYPE_C_SNK_STATUS_REG, &stat);
|
|
stat = stat & DETECTED_SRC_TYPE_MASK;
|
|
if (rc < 0) {
|
|
smblib_err(chg,
|
|
"Error getting USB Pres rc = %d\n", rc);
|
|
} else if (stat) {
|
|
smblib_err(chg, "USB Present, Disable Power OK IRQ\n");
|
|
disable_irq_nosync(pok_irq);
|
|
cancel_delayed_work(&chg->mmi.heartbeat_work);
|
|
schedule_delayed_work(&chg->mmi.heartbeat_work,
|
|
msecs_to_jiffies(100));
|
|
return IRQ_HANDLED;
|
|
}
|
|
/* This could be a weak charger reduce ICL */
|
|
if (!is_client_vote_enabled(chg->usb_icl_votable,
|
|
WEAK_CHARGER_VOTER)) {
|
|
smblib_err(chg,
|
|
"Weak charger detected: voting %dmA ICL\n",
|
|
chg->weak_chg_icl_ua / 1000);
|
|
vote(chg->usb_icl_votable, WEAK_CHARGER_VOTER,
|
|
true, chg->weak_chg_icl_ua);
|
|
/*
|
|
* reset storm data and set the storm threshold
|
|
* to 3 for reverse boost detection.
|
|
*/
|
|
update_storm_count(wdata, BOOST_BACK_STORM_COUNT);
|
|
} else {
|
|
smblib_err(chg,
|
|
"Reverse boost detected: voting 0mA to suspend input\n");
|
|
vote(chg->usb_icl_votable, BOOST_BACK_VOTER, true, 0);
|
|
vote(chg->awake_votable, BOOST_BACK_VOTER, true, 0);
|
|
/*
|
|
* Remove the boost-back vote after a delay, to avoid
|
|
* permanently suspending the input if the boost-back
|
|
* condition is unintentionally hit.
|
|
*/
|
|
schedule_delayed_work(&chg->bb_removal_work,
|
|
msecs_to_jiffies(BOOST_BACK_UNVOTE_DELAY_MS));
|
|
}
|
|
}
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
irqreturn_t wdog_snarl_irq_handler(int irq, void *data)
|
|
{
|
|
struct smb_irq_data *irq_data = data;
|
|
struct smb_charger *chg = irq_data->parent_data;
|
|
|
|
smblib_dbg(chg, PR_INTERRUPT, "IRQ: %s\n", irq_data->name);
|
|
|
|
if (chg->wa_flags & SW_THERM_REGULATION_WA) {
|
|
cancel_delayed_work_sync(&chg->thermal_regulation_work);
|
|
vote(chg->awake_votable, SW_THERM_REGULATION_VOTER, true, 0);
|
|
schedule_delayed_work(&chg->thermal_regulation_work, 0);
|
|
}
|
|
|
|
power_supply_changed(chg->batt_psy);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
irqreturn_t wdog_bark_irq_handler(int irq, void *data)
|
|
{
|
|
struct smb_irq_data *irq_data = data;
|
|
struct smb_charger *chg = irq_data->parent_data;
|
|
int rc;
|
|
|
|
smblib_dbg(chg, PR_INTERRUPT, "IRQ: %s\n", irq_data->name);
|
|
|
|
rc = smblib_write(chg, BARK_BITE_WDOG_PET_REG, BARK_BITE_WDOG_PET_BIT);
|
|
if (rc < 0)
|
|
smblib_err(chg, "Couldn't pet the dog rc=%d\n", rc);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static void smblib_die_rst_icl_regulate(struct smb_charger *chg)
|
|
{
|
|
int rc;
|
|
u8 temp;
|
|
|
|
rc = smblib_read(chg, DIE_TEMP_STATUS_REG, &temp);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't read DIE_TEMP_STATUS_REG rc=%d\n",
|
|
rc);
|
|
return;
|
|
}
|
|
|
|
/* Regulate ICL on die temp crossing DIE_RST threshold */
|
|
vote(chg->usb_icl_votable, DIE_TEMP_VOTER,
|
|
temp & DIE_TEMP_RST_BIT, 500000);
|
|
}
|
|
|
|
/*
|
|
* triggered when DIE or SKIN or CONNECTOR temperature across
|
|
* either of the _REG_L, _REG_H, _RST, or _SHDN thresholds
|
|
*/
|
|
irqreturn_t temp_change_irq_handler(int irq, void *data)
|
|
{
|
|
struct smb_irq_data *irq_data = data;
|
|
struct smb_charger *chg = irq_data->parent_data;
|
|
|
|
smblib_die_rst_icl_regulate(chg);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static void smblib_usbov_dbc_work(struct work_struct *work)
|
|
{
|
|
struct smb_charger *chg = container_of(work, struct smb_charger,
|
|
usbov_dbc_work.work);
|
|
|
|
smblib_dbg(chg, PR_MISC, "Resetting USBOV debounce\n");
|
|
chg->dbc_usbov = false;
|
|
vote(chg->awake_votable, USBOV_DBC_VOTER, false, 0);
|
|
}
|
|
|
|
#define USB_OV_DBC_PERIOD_MS 1000
|
|
irqreturn_t usbin_ov_irq_handler(int irq, void *data)
|
|
{
|
|
struct smb_irq_data *irq_data = data;
|
|
struct smb_charger *chg = irq_data->parent_data;
|
|
const struct apsd_result *apsd_result = smblib_get_apsd_result(chg);
|
|
u8 stat;
|
|
int rc;
|
|
|
|
smblib_dbg(chg, PR_INTERRUPT, "IRQ: %s\n", irq_data->name);
|
|
|
|
if (!(chg->wa_flags & USBIN_OV_WA))
|
|
return IRQ_HANDLED;
|
|
|
|
rc = smblib_read(chg, USBIN_BASE + INT_RT_STS_OFFSET, &stat);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't read USB_INT_RT_STS rc=%d\n", rc);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
/*
|
|
* On specific PMICs, OV IRQ triggers for very small duration in
|
|
* interim periods affecting charging status reflection. In order to
|
|
* differentiate between OV IRQ glitch and real OV_IRQ, add a debounce
|
|
* period for evaluation.
|
|
*/
|
|
if (stat & USBIN_OV_RT_STS_BIT) {
|
|
chg->dbc_usbov = true;
|
|
vote(chg->awake_votable, USBOV_DBC_VOTER, true, 0);
|
|
schedule_delayed_work(&chg->usbov_dbc_work,
|
|
msecs_to_jiffies(USB_OV_DBC_PERIOD_MS));
|
|
} else {
|
|
cancel_delayed_work_sync(&chg->usbov_dbc_work);
|
|
chg->dbc_usbov = false;
|
|
vote(chg->awake_votable, USBOV_DBC_VOTER, false, 0);
|
|
}
|
|
|
|
smblib_dbg(chg, PR_MISC, "USBOV debounce status %d\n",
|
|
chg->dbc_usbov);
|
|
|
|
/* QC 2.0/3.0 adapter */
|
|
if ((!chg->qc_usbov) &&
|
|
(apsd_result->bit & (QC_3P0_BIT | QC_2P0_BIT))) {
|
|
smblib_err(chg, "QC vbus OV disable HVDCP\n");
|
|
chg->qc_usbov = true;
|
|
smblib_hvdcp_enable_mmi(chg, false);
|
|
smblib_rerun_apsd(chg);
|
|
}
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
/**************
|
|
* Additional USB PSY getters/setters
|
|
* that call interrupt functions
|
|
***************/
|
|
|
|
int smblib_get_prop_pr_swap_in_progress(struct smb_charger *chg,
|
|
union power_supply_propval *val)
|
|
{
|
|
val->intval = chg->pr_swap_in_progress;
|
|
return 0;
|
|
}
|
|
|
|
#define DETACH_DETECT_DELAY_MS 20
|
|
int smblib_set_prop_pr_swap_in_progress(struct smb_charger *chg,
|
|
const union power_supply_propval *val)
|
|
{
|
|
int rc;
|
|
u8 stat = 0, orientation;
|
|
|
|
smblib_dbg(chg, PR_MISC, "Requested PR_SWAP %d\n", val->intval);
|
|
chg->pr_swap_in_progress = val->intval;
|
|
|
|
/* check for cable removal during pr_swap */
|
|
if (!chg->pr_swap_in_progress) {
|
|
cancel_delayed_work_sync(&chg->pr_swap_detach_work);
|
|
vote(chg->awake_votable, DETACH_DETECT_VOTER, true, 0);
|
|
schedule_delayed_work(&chg->pr_swap_detach_work,
|
|
msecs_to_jiffies(DETACH_DETECT_DELAY_MS));
|
|
}
|
|
|
|
rc = smblib_masked_write(chg, TYPE_C_DEBOUNCE_OPTION_REG,
|
|
REDUCE_TCCDEBOUNCE_TO_2MS_BIT,
|
|
val->intval ? REDUCE_TCCDEBOUNCE_TO_2MS_BIT : 0);
|
|
if (rc < 0)
|
|
smblib_err(chg, "Couldn't set tCC debounce rc=%d\n", rc);
|
|
|
|
rc = smblib_masked_write(chg, TYPE_C_EXIT_STATE_CFG_REG,
|
|
BYPASS_VSAFE0V_DURING_ROLE_SWAP_BIT,
|
|
val->intval ? BYPASS_VSAFE0V_DURING_ROLE_SWAP_BIT : 0);
|
|
if (rc < 0)
|
|
smblib_err(chg, "Couldn't set exit state cfg rc=%d\n", rc);
|
|
|
|
if (chg->pr_swap_in_progress) {
|
|
rc = smblib_read(chg, TYPE_C_MISC_STATUS_REG, &stat);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't read TYPE_C_STATUS_4 rc=%d\n",
|
|
rc);
|
|
}
|
|
|
|
orientation =
|
|
stat & CC_ORIENTATION_BIT ? TYPEC_CCOUT_VALUE_BIT : 0;
|
|
rc = smblib_masked_write(chg, TYPE_C_CCOUT_CONTROL_REG,
|
|
TYPEC_CCOUT_SRC_BIT | TYPEC_CCOUT_BUFFER_EN_BIT
|
|
| TYPEC_CCOUT_VALUE_BIT,
|
|
TYPEC_CCOUT_SRC_BIT | TYPEC_CCOUT_BUFFER_EN_BIT
|
|
| orientation);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't read TYPE_C_CCOUT_CONTROL_REG rc=%d\n",
|
|
rc);
|
|
}
|
|
} else {
|
|
rc = smblib_masked_write(chg, TYPE_C_CCOUT_CONTROL_REG,
|
|
TYPEC_CCOUT_SRC_BIT, 0);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't read TYPE_C_CCOUT_CONTROL_REG rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
/* enable DRP */
|
|
rc = smblib_masked_write(chg, TYPE_C_MODE_CFG_REG,
|
|
TYPEC_POWER_ROLE_CMD_MASK, 0);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't enable DRP rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
chg->power_role = POWER_SUPPLY_TYPEC_PR_DUAL;
|
|
smblib_dbg(chg, PR_MISC, "restore power role: %d\n",
|
|
chg->power_role);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/***************
|
|
* Work Queues *
|
|
***************/
|
|
static void smblib_pr_lock_clear_work(struct work_struct *work)
|
|
{
|
|
struct smb_charger *chg = container_of(work, struct smb_charger,
|
|
pr_lock_clear_work.work);
|
|
|
|
spin_lock(&chg->typec_pr_lock);
|
|
if (chg->pr_lock_in_progress) {
|
|
smblib_dbg(chg, PR_MISC, "restore type-c interrupts\n");
|
|
smblib_typec_irq_config(chg, true);
|
|
chg->pr_lock_in_progress = false;
|
|
}
|
|
spin_unlock(&chg->typec_pr_lock);
|
|
}
|
|
|
|
static void smblib_pr_swap_detach_work(struct work_struct *work)
|
|
{
|
|
struct smb_charger *chg = container_of(work, struct smb_charger,
|
|
pr_swap_detach_work.work);
|
|
int rc;
|
|
u8 stat;
|
|
|
|
rc = smblib_read(chg, TYPE_C_STATE_MACHINE_STATUS_REG, &stat);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't read STATE_MACHINE_STS rc=%d\n", rc);
|
|
goto out;
|
|
}
|
|
smblib_dbg(chg, PR_REGISTER, "STATE_MACHINE_STS %x\n", stat);
|
|
if (!(stat & TYPEC_ATTACH_DETACH_STATE_BIT)) {
|
|
rc = smblib_request_dpdm(chg, false);
|
|
if (rc < 0)
|
|
smblib_err(chg, "Couldn't disable DPDM rc=%d\n", rc);
|
|
|
|
if (chg->typec_port)
|
|
typec_partner_unregister(chg);
|
|
}
|
|
out:
|
|
vote(chg->awake_votable, DETACH_DETECT_VOTER, false, 0);
|
|
}
|
|
|
|
#define MAX_INPUT_PWR_UW 18000000
|
|
static void smblib_pd_contract_work(struct work_struct *work)
|
|
{
|
|
struct smb_charger *chg = container_of(work, struct smb_charger,
|
|
pd_contract_work.work);
|
|
int rc, max_ua;
|
|
|
|
if (!chg->pd) {
|
|
chg->pd = devm_usbpd_get_by_phandle(chg->dev,
|
|
"qcom,usbpd-phandle");
|
|
if (IS_ERR_OR_NULL(chg->pd)) {
|
|
pr_err("Error getting the pd phandle %ld\n",
|
|
PTR_ERR(chg->pd));
|
|
chg->pd = NULL;
|
|
}
|
|
}
|
|
if (!chg->pd || !chg->pd_active)
|
|
return;
|
|
|
|
chg->pd_contract_uv = usbpd_select_pdo_match(chg->pd);
|
|
|
|
if (chg->pd_contract_uv == -ENOTSUPP)
|
|
return;
|
|
|
|
if (chg->pd_contract_uv <= 0) {
|
|
schedule_delayed_work(&chg->pd_contract_work,
|
|
msecs_to_jiffies(500));
|
|
return;
|
|
} else
|
|
power_supply_changed(chg->usb_psy);
|
|
|
|
if (chg->pd_contract_uv >= MICRO_9V)
|
|
smblib_set_opt_switcher_freq(chg, chg->chg_freq.freq_9V);
|
|
else
|
|
smblib_set_opt_switcher_freq(chg, chg->chg_freq.freq_5V);
|
|
|
|
max_ua = (MAX_INPUT_PWR_UW / (chg->pd_contract_uv / 1000)) * 1000;
|
|
smblib_dbg(chg, PR_MISC, "smblib_pd_contract_work: %d uV, %d uA\n",
|
|
chg->pd_contract_uv, max_ua);
|
|
|
|
rc = vote(chg->usb_icl_votable, ICL_LIMIT_VOTER, true, max_ua);
|
|
if (rc < 0)
|
|
smblib_err(chg, "Error setting %d uA rc=%d\n", max_ua, rc);
|
|
|
|
cancel_delayed_work(&chg->mmi.heartbeat_work);
|
|
schedule_delayed_work(&chg->mmi.heartbeat_work, 0);
|
|
|
|
}
|
|
|
|
static void smblib_uusb_otg_work(struct work_struct *work)
|
|
{
|
|
struct smb_charger *chg = container_of(work, struct smb_charger,
|
|
uusb_otg_work.work);
|
|
int rc;
|
|
u8 stat;
|
|
bool otg;
|
|
|
|
rc = smblib_read(chg, TYPEC_U_USB_STATUS_REG, &stat);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't read TYPE_C_STATUS_3 rc=%d\n", rc);
|
|
goto out;
|
|
}
|
|
otg = !!(stat & U_USB_GROUND_NOVBUS_BIT);
|
|
if (chg->otg_present != otg) {
|
|
if (otg) {
|
|
/* otg cable inserted */
|
|
if (chg->typec_port) {
|
|
typec_partner_register(chg);
|
|
typec_set_data_role(chg->typec_port,
|
|
TYPEC_HOST);
|
|
typec_set_pwr_role(chg->typec_port,
|
|
TYPEC_SOURCE);
|
|
}
|
|
} else if (chg->typec_port) {
|
|
/* otg cable removed */
|
|
typec_set_data_role(chg->typec_port, TYPEC_DEVICE);
|
|
typec_set_pwr_role(chg->typec_port, TYPEC_SINK);
|
|
typec_partner_unregister(chg);
|
|
}
|
|
|
|
smblib_notify_usb_host(chg, otg);
|
|
} else {
|
|
goto out;
|
|
}
|
|
|
|
chg->otg_present = otg;
|
|
if (!otg)
|
|
chg->boost_current_ua = 0;
|
|
|
|
rc = smblib_set_charge_param(chg, &chg->param.freq_switcher,
|
|
otg ? chg->chg_freq.freq_below_otg_threshold
|
|
: chg->chg_freq.freq_removal);
|
|
if (rc < 0)
|
|
dev_err(chg->dev, "Error in setting freq_boost rc=%d\n", rc);
|
|
|
|
smblib_dbg(chg, PR_REGISTER, "TYPE_C_U_USB_STATUS = 0x%02x OTG=%d\n",
|
|
stat, otg);
|
|
power_supply_changed(chg->usb_psy);
|
|
|
|
out:
|
|
vote(chg->awake_votable, OTG_DELAY_VOTER, false, 0);
|
|
}
|
|
|
|
static void bms_update_work(struct work_struct *work)
|
|
{
|
|
struct smb_charger *chg = container_of(work, struct smb_charger,
|
|
bms_update_work);
|
|
|
|
smblib_suspend_on_debug_battery(chg);
|
|
|
|
if (chg->batt_psy)
|
|
power_supply_changed(chg->batt_psy);
|
|
}
|
|
|
|
static void pl_update_work(struct work_struct *work)
|
|
{
|
|
union power_supply_propval prop_val;
|
|
struct smb_charger *chg = container_of(work, struct smb_charger,
|
|
pl_update_work);
|
|
int rc;
|
|
|
|
if (chg->smb_temp_max == -EINVAL) {
|
|
rc = smblib_get_thermal_threshold(chg,
|
|
SMB_REG_H_THRESHOLD_MSB_REG,
|
|
&chg->smb_temp_max);
|
|
if (rc < 0) {
|
|
dev_err(chg->dev, "Couldn't get charger_temp_max rc=%d\n",
|
|
rc);
|
|
return;
|
|
}
|
|
}
|
|
|
|
prop_val.intval = chg->smb_temp_max;
|
|
rc = power_supply_set_property(chg->pl.psy,
|
|
POWER_SUPPLY_PROP_CHARGER_TEMP_MAX,
|
|
&prop_val);
|
|
if (rc < 0) {
|
|
dev_err(chg->dev, "Couldn't set POWER_SUPPLY_PROP_CHARGER_TEMP_MAX rc=%d\n",
|
|
rc);
|
|
return;
|
|
}
|
|
|
|
if (chg->sec_chg_selected == POWER_SUPPLY_CHARGER_SEC_CP)
|
|
return;
|
|
|
|
smblib_select_sec_charger(chg, POWER_SUPPLY_CHARGER_SEC_PL,
|
|
POWER_SUPPLY_CP_NONE, false);
|
|
}
|
|
|
|
static void clear_hdc_work(struct work_struct *work)
|
|
{
|
|
struct smb_charger *chg = container_of(work, struct smb_charger,
|
|
clear_hdc_work.work);
|
|
|
|
chg->is_hdc = false;
|
|
vote(chg->hdc_irq_disable_votable, HDC_IRQ_VOTER, false, 0);
|
|
}
|
|
|
|
static void smblib_icl_change_work(struct work_struct *work)
|
|
{
|
|
struct smb_charger *chg = container_of(work, struct smb_charger,
|
|
icl_change_work.work);
|
|
int rc, settled_ua;
|
|
|
|
rc = smblib_get_charge_param(chg, &chg->param.icl_stat, &settled_ua);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't get ICL status rc=%d\n", rc);
|
|
return;
|
|
}
|
|
|
|
power_supply_changed(chg->usb_main_psy);
|
|
|
|
smblib_dbg(chg, PR_INTERRUPT, "icl_settled=%d\n", settled_ua);
|
|
}
|
|
|
|
static void smblib_pl_enable_work(struct work_struct *work)
|
|
{
|
|
struct smb_charger *chg = container_of(work, struct smb_charger,
|
|
pl_enable_work.work);
|
|
|
|
smblib_dbg(chg, PR_PARALLEL, "timer expired, enabling parallel\n");
|
|
vote(chg->pl_disable_votable, PL_DELAY_VOTER, false, 0);
|
|
vote(chg->awake_votable, PL_DELAY_VOTER, false, 0);
|
|
}
|
|
|
|
static void smblib_thermal_regulation_work(struct work_struct *work)
|
|
{
|
|
struct smb_charger *chg = container_of(work, struct smb_charger,
|
|
thermal_regulation_work.work);
|
|
int rc;
|
|
|
|
rc = smblib_update_thermal_readings(chg);
|
|
if (rc < 0)
|
|
smblib_err(chg, "Couldn't read current thermal values %d\n",
|
|
rc);
|
|
|
|
rc = smblib_process_thermal_readings(chg);
|
|
if (rc < 0)
|
|
smblib_err(chg, "Couldn't run sw thermal regulation %d\n",
|
|
rc);
|
|
}
|
|
|
|
#define MOISTURE_PROTECTION_CHECK_DELAY_MS 300000 /* 5 mins */
|
|
static void smblib_moisture_protection_work(struct work_struct *work)
|
|
{
|
|
struct smb_charger *chg = container_of(work, struct smb_charger,
|
|
moisture_protection_work);
|
|
int rc;
|
|
bool usb_plugged_in;
|
|
u8 stat;
|
|
|
|
/*
|
|
* Hold awake votable to prevent pm_relax being called prior to
|
|
* completion of this work.
|
|
*/
|
|
vote(chg->awake_votable, MOISTURE_VOTER, true, 0);
|
|
|
|
/*
|
|
* Disable 1% duty cycle on CC_ID pin and enable uUSB factory mode
|
|
* detection to track any change on RID, as interrupts are disable.
|
|
*/
|
|
rc = smblib_write(chg, ((chg->chg_param.smb_version == PMI632_SUBTYPE) ?
|
|
PMI632_TYPEC_U_USB_WATER_PROTECTION_CFG_REG :
|
|
TYPEC_U_USB_WATER_PROTECTION_CFG_REG), 0);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't disable periodic monitoring of CC_ID rc=%d\n",
|
|
rc);
|
|
goto out;
|
|
}
|
|
|
|
rc = smblib_masked_write(chg, TYPEC_U_USB_CFG_REG,
|
|
EN_MICRO_USB_FACTORY_MODE_BIT,
|
|
EN_MICRO_USB_FACTORY_MODE_BIT);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't enable uUSB factory mode detection rc=%d\n",
|
|
rc);
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* Add a delay of 100ms to allow change in rid to reflect on
|
|
* status registers.
|
|
*/
|
|
msleep(100);
|
|
|
|
rc = smblib_read(chg, USBIN_BASE + INT_RT_STS_OFFSET, &stat);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't read USB_INT_RT_STS rc=%d\n", rc);
|
|
goto out;
|
|
}
|
|
usb_plugged_in = (bool)(stat & USBIN_PLUGIN_RT_STS_BIT);
|
|
|
|
/* Check uUSB status for moisture presence */
|
|
rc = smblib_read(chg, TYPEC_U_USB_STATUS_REG, &stat);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't read TYPE_C_U_USB_STATUS_REG rc=%d\n",
|
|
rc);
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* Factory mode detection happens in case of USB plugged-in by using
|
|
* a different current source of 2uA which can hamper moisture
|
|
* detection. Since factory mode is not supported in kernel, factory
|
|
* mode detection can be considered as equivalent to presence of
|
|
* moisture.
|
|
*/
|
|
if (stat == U_USB_STATUS_WATER_PRESENT || stat == U_USB_FMB1_BIT ||
|
|
stat == U_USB_FMB2_BIT || (usb_plugged_in &&
|
|
stat == U_USB_FLOAT1_BIT)) {
|
|
smblib_set_moisture_protection(chg, true);
|
|
alarm_start_relative(&chg->moisture_protection_alarm,
|
|
ms_to_ktime(MOISTURE_PROTECTION_CHECK_DELAY_MS));
|
|
} else {
|
|
smblib_set_moisture_protection(chg, false);
|
|
rc = alarm_cancel(&chg->moisture_protection_alarm);
|
|
if (rc < 0)
|
|
smblib_err(chg, "Couldn't cancel moisture protection alarm\n");
|
|
}
|
|
|
|
out:
|
|
vote(chg->awake_votable, MOISTURE_VOTER, false, 0);
|
|
}
|
|
|
|
static enum alarmtimer_restart moisture_protection_alarm_cb(struct alarm *alarm,
|
|
ktime_t now)
|
|
{
|
|
struct smb_charger *chg = container_of(alarm, struct smb_charger,
|
|
moisture_protection_alarm);
|
|
|
|
smblib_dbg(chg, PR_MISC, "moisture Protection Alarm Triggered %lld\n",
|
|
ktime_to_ms(now));
|
|
|
|
/* Atomic context, cannot use voter */
|
|
pm_stay_awake(chg->dev);
|
|
schedule_work(&chg->moisture_protection_work);
|
|
|
|
return ALARMTIMER_NORESTART;
|
|
}
|
|
|
|
static void smblib_chg_termination_work(struct work_struct *work)
|
|
{
|
|
union power_supply_propval pval;
|
|
struct smb_charger *chg = container_of(work, struct smb_charger,
|
|
chg_termination_work);
|
|
int rc, input_present, delay = CHG_TERM_WA_ENTRY_DELAY_MS;
|
|
int vbat_now_uv, max_fv_uv;
|
|
u8 stat = 0;
|
|
|
|
/*
|
|
* Hold awake votable to prevent pm_relax being called prior to
|
|
* completion of this work.
|
|
*/
|
|
vote(chg->awake_votable, CHG_TERMINATION_VOTER, true, 0);
|
|
|
|
rc = smblib_is_input_present(chg, &input_present);
|
|
if ((rc < 0) || !input_present)
|
|
goto out;
|
|
|
|
rc = smblib_get_prop_from_bms(chg,
|
|
POWER_SUPPLY_PROP_REAL_CAPACITY, &pval);
|
|
if ((rc < 0) || (pval.intval < 100)) {
|
|
rc = smblib_read(chg, BATTERY_CHARGER_STATUS_1_REG, &stat);
|
|
if (rc < 0)
|
|
goto out;
|
|
|
|
/* check we are not in termination to exit the WA */
|
|
if ((stat & BATTERY_CHARGER_STATUS_MASK) != TERMINATE_CHARGE) {
|
|
vote(chg->usb_icl_votable,
|
|
CHG_TERMINATION_VOTER, false, 0);
|
|
vote(chg->dc_suspend_votable,
|
|
CHG_TERMINATION_VOTER, false, 0);
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
/* Get the battery float voltage */
|
|
rc = smblib_get_prop_from_bms(chg, POWER_SUPPLY_PROP_VOLTAGE_MAX,
|
|
&pval);
|
|
if (rc < 0) {
|
|
/* FG based targets supports only MAX_DESIGN property */
|
|
rc = smblib_get_prop_from_bms(chg,
|
|
POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
|
|
&pval);
|
|
if (rc < 0)
|
|
goto out;
|
|
}
|
|
|
|
max_fv_uv = pval.intval;
|
|
|
|
rc = smblib_get_prop_from_bms(chg, POWER_SUPPLY_PROP_CHARGE_FULL,
|
|
&pval);
|
|
if (rc < 0)
|
|
goto out;
|
|
|
|
/*
|
|
* On change in the value of learned capacity, re-initialize the
|
|
* reference cc_soc value due to change in cc_soc characteristic value
|
|
* at full capacity. Also, in case cc_soc_ref value is reset,
|
|
* re-initialize it.
|
|
*/
|
|
if (pval.intval != chg->charge_full_cc || !chg->cc_soc_ref) {
|
|
chg->charge_full_cc = pval.intval;
|
|
|
|
rc = smblib_get_prop_from_bms(chg,
|
|
POWER_SUPPLY_PROP_VOLTAGE_NOW, &pval);
|
|
if (rc < 0)
|
|
goto out;
|
|
|
|
/*
|
|
* Store the Vbat at the charge termination to compare with
|
|
* the current voltage to see if the Vbat is increasing after
|
|
* charge termination in BSM.
|
|
*/
|
|
chg->term_vbat_uv = pval.intval;
|
|
vbat_now_uv = pval.intval;
|
|
|
|
rc = smblib_get_prop_from_bms(chg, POWER_SUPPLY_PROP_CC_SOC,
|
|
&pval);
|
|
if (rc < 0)
|
|
goto out;
|
|
|
|
chg->cc_soc_ref = pval.intval;
|
|
} else {
|
|
rc = smblib_get_prop_from_bms(chg,
|
|
POWER_SUPPLY_PROP_VOLTAGE_NOW, &pval);
|
|
if (rc < 0)
|
|
goto out;
|
|
|
|
vbat_now_uv = pval.intval;
|
|
|
|
rc = smblib_get_prop_from_bms(chg, POWER_SUPPLY_PROP_CC_SOC,
|
|
&pval);
|
|
if (rc < 0)
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* In BSM a sudden jump in CC_SOC is not expected. If seen, its a
|
|
* good_ocv or updated capacity, reject it.
|
|
*/
|
|
if (chg->last_cc_soc && pval.intval > (chg->last_cc_soc + 100)) {
|
|
/* CC_SOC has increased by 1% from last time */
|
|
chg->cc_soc_ref = pval.intval;
|
|
smblib_dbg(chg, PR_MISC, "cc_soc jumped(%d->%d), reset cc_soc_ref\n",
|
|
chg->last_cc_soc, pval.intval);
|
|
}
|
|
chg->last_cc_soc = pval.intval;
|
|
|
|
/*
|
|
* Suspend/Unsuspend USB input to keep cc_soc within the 0.5% to 0.75%
|
|
* overshoot range of the cc_soc value at termination and make sure that
|
|
* vbat is indeed rising above vfloat.
|
|
*/
|
|
if (pval.intval < DIV_ROUND_CLOSEST(chg->cc_soc_ref * 10050, 10000)) {
|
|
vote(chg->usb_icl_votable, CHG_TERMINATION_VOTER, false, 0);
|
|
vote(chg->dc_suspend_votable, CHG_TERMINATION_VOTER, false, 0);
|
|
delay = CHG_TERM_WA_ENTRY_DELAY_MS;
|
|
} else if ((pval.intval > DIV_ROUND_CLOSEST(chg->cc_soc_ref * 10075,
|
|
10000))
|
|
&& ((vbat_now_uv > chg->term_vbat_uv) &&
|
|
(vbat_now_uv > max_fv_uv))) {
|
|
|
|
if (input_present & INPUT_PRESENT_USB)
|
|
vote(chg->usb_icl_votable, CHG_TERMINATION_VOTER,
|
|
true, 0);
|
|
if (input_present & INPUT_PRESENT_DC)
|
|
vote(chg->dc_suspend_votable, CHG_TERMINATION_VOTER,
|
|
true, 0);
|
|
delay = CHG_TERM_WA_EXIT_DELAY_MS;
|
|
}
|
|
|
|
smblib_dbg(chg, PR_MISC, "Chg Term WA readings: cc_soc: %d, cc_soc_ref: %d, delay: %d vbat_now %d term_vbat %d\n",
|
|
pval.intval, chg->cc_soc_ref, delay, vbat_now_uv,
|
|
chg->term_vbat_uv);
|
|
alarm_start_relative(&chg->chg_termination_alarm, ms_to_ktime(delay));
|
|
out:
|
|
vote(chg->awake_votable, CHG_TERMINATION_VOTER, false, 0);
|
|
}
|
|
|
|
static enum alarmtimer_restart chg_termination_alarm_cb(struct alarm *alarm,
|
|
ktime_t now)
|
|
{
|
|
struct smb_charger *chg = container_of(alarm, struct smb_charger,
|
|
chg_termination_alarm);
|
|
|
|
smblib_dbg(chg, PR_MISC, "Charge termination WA alarm triggered %lld\n",
|
|
ktime_to_ms(now));
|
|
|
|
/* Atomic context, cannot use voter */
|
|
pm_stay_awake(chg->dev);
|
|
schedule_work(&chg->chg_termination_work);
|
|
|
|
return ALARMTIMER_NORESTART;
|
|
}
|
|
|
|
static void apsd_timer_cb(struct timer_list *tm)
|
|
{
|
|
struct smb_charger *chg = container_of(tm, struct smb_charger,
|
|
apsd_timer);
|
|
|
|
smblib_dbg(chg, PR_MISC, "APSD Extented timer timeout at %lld\n",
|
|
jiffies_to_msecs(jiffies));
|
|
|
|
chg->apsd_ext_timeout = true;
|
|
}
|
|
|
|
#define SOFT_JEITA_HYSTERESIS_OFFSET 0x200
|
|
static void jeita_update_work(struct work_struct *work)
|
|
{
|
|
struct smb_charger *chg = container_of(work, struct smb_charger,
|
|
jeita_update_work);
|
|
struct device_node *node = chg->dev->of_node;
|
|
struct device_node *batt_node, *pnode;
|
|
union power_supply_propval val;
|
|
int rc, tmp[2], max_fcc_ma, max_fv_uv;
|
|
u32 jeita_hard_thresholds[2];
|
|
u16 addr;
|
|
u8 buff[2];
|
|
|
|
batt_node = of_find_node_by_name(node, "qcom,battery-data");
|
|
if (!batt_node) {
|
|
smblib_err(chg, "Batterydata not available\n");
|
|
goto out;
|
|
}
|
|
|
|
/* if BMS is not ready, defer the work */
|
|
if (!chg->bms_psy)
|
|
return;
|
|
|
|
rc = smblib_get_prop_from_bms(chg,
|
|
POWER_SUPPLY_PROP_RESISTANCE_ID, &val);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Failed to get batt-id rc=%d\n", rc);
|
|
goto out;
|
|
}
|
|
|
|
/* if BMS hasn't read out the batt_id yet, defer the work */
|
|
if (val.intval <= 0)
|
|
return;
|
|
|
|
pnode = of_batterydata_get_best_profile(batt_node,
|
|
val.intval / 1000, NULL);
|
|
if (IS_ERR(pnode)) {
|
|
rc = PTR_ERR(pnode);
|
|
smblib_err(chg, "Failed to detect valid battery profile %d\n",
|
|
rc);
|
|
goto out;
|
|
}
|
|
|
|
rc = of_property_read_u32_array(pnode, "qcom,jeita-hard-thresholds",
|
|
jeita_hard_thresholds, 2);
|
|
if (!rc) {
|
|
rc = smblib_update_jeita(chg, jeita_hard_thresholds,
|
|
JEITA_HARD);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't configure Hard Jeita rc=%d\n",
|
|
rc);
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
rc = of_property_read_u32_array(pnode, "qcom,jeita-soft-thresholds",
|
|
chg->jeita_soft_thlds, 2);
|
|
if (!rc) {
|
|
rc = smblib_update_jeita(chg, chg->jeita_soft_thlds,
|
|
JEITA_SOFT);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't configure Soft Jeita rc=%d\n",
|
|
rc);
|
|
goto out;
|
|
}
|
|
|
|
rc = of_property_read_u32_array(pnode,
|
|
"qcom,jeita-soft-hys-thresholds",
|
|
chg->jeita_soft_hys_thlds, 2);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't get Soft Jeita hysteresis thresholds rc=%d\n",
|
|
rc);
|
|
goto out;
|
|
}
|
|
} else {
|
|
/* Populate the jeita-soft-thresholds */
|
|
addr = CHGR_JEITA_THRESHOLD_BASE_REG(JEITA_SOFT);
|
|
rc = smblib_batch_read(chg, addr, buff, 2);
|
|
if (rc < 0) {
|
|
pr_err("failed to read 0x%4X, rc=%d\n", addr, rc);
|
|
goto out;
|
|
}
|
|
|
|
chg->jeita_soft_thlds[1] = buff[1] | buff[0] << 8;
|
|
|
|
rc = smblib_batch_read(chg, addr + 2, buff, 2);
|
|
if (rc < 0) {
|
|
pr_err("failed to read 0x%4X, rc=%d\n", addr + 2, rc);
|
|
goto out;
|
|
}
|
|
|
|
chg->jeita_soft_thlds[0] = buff[1] | buff[0] << 8;
|
|
|
|
/*
|
|
* Update the soft jeita hysteresis 2 DegC less for warm and
|
|
* 2 DegC more for cool than the soft jeita thresholds to avoid
|
|
* overwriting the registers with invalid values.
|
|
*/
|
|
chg->jeita_soft_hys_thlds[0] =
|
|
chg->jeita_soft_thlds[0] - SOFT_JEITA_HYSTERESIS_OFFSET;
|
|
chg->jeita_soft_hys_thlds[1] =
|
|
chg->jeita_soft_thlds[1] + SOFT_JEITA_HYSTERESIS_OFFSET;
|
|
}
|
|
|
|
chg->jeita_soft_fcc[0] = chg->jeita_soft_fcc[1] = -EINVAL;
|
|
chg->jeita_soft_fv[0] = chg->jeita_soft_fv[1] = -EINVAL;
|
|
max_fcc_ma = max_fv_uv = -EINVAL;
|
|
|
|
of_property_read_u32(pnode, "qcom,fastchg-current-ma", &max_fcc_ma);
|
|
of_property_read_u32(pnode, "qcom,max-voltage-uv", &max_fv_uv);
|
|
|
|
if (max_fcc_ma <= 0 || max_fv_uv <= 0) {
|
|
smblib_err(chg, "Incorrect fastchg-current-ma or max-voltage-uv\n");
|
|
goto out;
|
|
}
|
|
|
|
rc = of_property_read_u32_array(pnode, "qcom,jeita-soft-fcc-ua",
|
|
tmp, 2);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't get fcc values for soft JEITA rc=%d\n",
|
|
rc);
|
|
goto out;
|
|
}
|
|
|
|
max_fcc_ma *= 1000;
|
|
if (tmp[0] > max_fcc_ma || tmp[1] > max_fcc_ma) {
|
|
smblib_err(chg, "Incorrect FCC value [%d %d] max: %d\n", tmp[0],
|
|
tmp[1], max_fcc_ma);
|
|
goto out;
|
|
}
|
|
chg->jeita_soft_fcc[0] = tmp[0];
|
|
chg->jeita_soft_fcc[1] = tmp[1];
|
|
|
|
rc = of_property_read_u32_array(pnode, "qcom,jeita-soft-fv-uv", tmp,
|
|
2);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't get fv values for soft JEITA rc=%d\n",
|
|
rc);
|
|
goto out;
|
|
}
|
|
|
|
if (tmp[0] > max_fv_uv || tmp[1] > max_fv_uv) {
|
|
smblib_err(chg, "Incorrect FV value [%d %d] max: %d\n", tmp[0],
|
|
tmp[1], max_fv_uv);
|
|
goto out;
|
|
}
|
|
chg->jeita_soft_fv[0] = tmp[0];
|
|
chg->jeita_soft_fv[1] = tmp[1];
|
|
|
|
rc = smblib_soft_jeita_arb_wa(chg);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't fix soft jeita arb rc=%d\n",
|
|
rc);
|
|
goto out;
|
|
}
|
|
|
|
chg->jeita_configured = JEITA_CFG_COMPLETE;
|
|
return;
|
|
|
|
out:
|
|
chg->jeita_configured = JEITA_CFG_FAILURE;
|
|
}
|
|
|
|
static void smblib_lpd_ra_open_work(struct work_struct *work)
|
|
{
|
|
struct smb_charger *chg = container_of(work, struct smb_charger,
|
|
lpd_ra_open_work.work);
|
|
union power_supply_propval pval;
|
|
u8 stat;
|
|
int rc;
|
|
|
|
if (chg->pr_swap_in_progress || chg->pd_hard_reset) {
|
|
chg->lpd_stage = LPD_STAGE_NONE;
|
|
goto out;
|
|
}
|
|
|
|
if (chg->lpd_stage != LPD_STAGE_FLOAT)
|
|
goto out;
|
|
|
|
rc = smblib_read(chg, TYPE_C_MISC_STATUS_REG, &stat);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't read TYPE_C_MISC_STATUS_REG rc=%d\n",
|
|
rc);
|
|
goto out;
|
|
}
|
|
|
|
/* quit if moisture status is gone or in attached state */
|
|
if (!(stat & TYPEC_WATER_DETECTION_STATUS_BIT)
|
|
|| (stat & TYPEC_TCCDEBOUNCE_DONE_STATUS_BIT)) {
|
|
chg->lpd_stage = LPD_STAGE_NONE;
|
|
goto out;
|
|
}
|
|
|
|
chg->lpd_stage = LPD_STAGE_COMMIT;
|
|
|
|
/* Enable source only mode */
|
|
pval.intval = POWER_SUPPLY_TYPEC_PR_SOURCE;
|
|
rc = smblib_set_prop_typec_power_role(chg, &pval);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't set typec source only mode rc=%d\n",
|
|
rc);
|
|
goto out;
|
|
}
|
|
|
|
/* Wait 1.5ms to get SBUx ready */
|
|
usleep_range(1500, 1510);
|
|
|
|
if (smblib_rsbux_low(chg, RSBU_K_300K_UV)) {
|
|
/* Moisture detected, enable sink only mode */
|
|
pval.intval = POWER_SUPPLY_TYPEC_PR_SINK;
|
|
rc = smblib_set_prop_typec_power_role(chg, &pval);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't set typec sink only rc=%d\n",
|
|
rc);
|
|
goto out;
|
|
}
|
|
|
|
chg->lpd_reason = LPD_MOISTURE_DETECTED;
|
|
chg->moisture_present = true;
|
|
vote(chg->usb_icl_votable, LPD_VOTER, true, 0);
|
|
|
|
} else {
|
|
/* Floating cable, disable water detection irq temporarily */
|
|
rc = smblib_masked_write(chg, TYPE_C_INTERRUPT_EN_CFG_2_REG,
|
|
TYPEC_WATER_DETECTION_INT_EN_BIT, 0);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't set TYPE_C_INTERRUPT_EN_CFG_2_REG rc=%d\n",
|
|
rc);
|
|
goto out;
|
|
}
|
|
|
|
/* restore DRP mode */
|
|
pval.intval = POWER_SUPPLY_TYPEC_PR_DUAL;
|
|
rc = smblib_set_prop_typec_power_role(chg, &pval);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't write 0x%02x to TYPE_C_INTRPT_ENB_SOFTWARE_CTRL rc=%d\n",
|
|
pval.intval, rc);
|
|
goto out;
|
|
}
|
|
|
|
chg->lpd_reason = LPD_FLOATING_CABLE;
|
|
}
|
|
|
|
/* recheck in 60 seconds */
|
|
alarm_start_relative(&chg->lpd_recheck_timer, ms_to_ktime(60000));
|
|
out:
|
|
vote(chg->awake_votable, LPD_VOTER, false, 0);
|
|
}
|
|
|
|
static void smblib_lpd_detach_work(struct work_struct *work)
|
|
{
|
|
struct smb_charger *chg = container_of(work, struct smb_charger,
|
|
lpd_detach_work.work);
|
|
|
|
if (chg->lpd_stage == LPD_STAGE_FLOAT_CANCEL)
|
|
chg->lpd_stage = LPD_STAGE_NONE;
|
|
}
|
|
|
|
static void smblib_cp_status_change_work(struct work_struct *work)
|
|
{
|
|
int rc;
|
|
union power_supply_propval pval;
|
|
struct smb_charger *chg = container_of(work, struct smb_charger,
|
|
cp_status_change_work);
|
|
|
|
if (!chg->cp_psy)
|
|
chg->cp_psy = power_supply_get_by_name("charge_pump_master");
|
|
|
|
if (!chg->cp_psy)
|
|
goto relax;
|
|
|
|
if (chg->cp_topo == -EINVAL) {
|
|
rc = power_supply_get_property(chg->cp_psy,
|
|
POWER_SUPPLY_PROP_PARALLEL_OUTPUT_MODE, &pval);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't read cp topo rc=%d\n", rc);
|
|
goto relax;
|
|
}
|
|
|
|
chg->cp_topo = pval.intval;
|
|
|
|
if (chg->cp_topo == POWER_SUPPLY_PL_OUTPUT_VBAT &&
|
|
chg->cp_reason == POWER_SUPPLY_CP_WIRELESS)
|
|
vote(chg->fcc_main_votable, WLS_PL_CHARGING_VOTER, true,
|
|
800000);
|
|
}
|
|
relax:
|
|
pm_relax(chg->dev);
|
|
}
|
|
|
|
static int smblib_create_votables(struct smb_charger *chg)
|
|
{
|
|
int rc = 0;
|
|
|
|
chg->fcc_votable = find_votable("FCC");
|
|
if (chg->fcc_votable == NULL) {
|
|
rc = -EINVAL;
|
|
smblib_err(chg, "Couldn't find FCC votable rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
chg->fcc_main_votable = find_votable("FCC_MAIN");
|
|
if (chg->fcc_main_votable == NULL) {
|
|
rc = -EINVAL;
|
|
smblib_err(chg, "Couldn't find FCC Main votable rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
chg->fv_votable = find_votable("FV");
|
|
if (chg->fv_votable == NULL) {
|
|
rc = -EINVAL;
|
|
smblib_err(chg, "Couldn't find FV votable rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
chg->usb_icl_votable = find_votable("USB_ICL");
|
|
if (chg->usb_icl_votable == NULL) {
|
|
rc = -EINVAL;
|
|
smblib_err(chg, "Couldn't find USB_ICL votable rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
chg->pl_disable_votable = find_votable("PL_DISABLE");
|
|
if (chg->pl_disable_votable == NULL) {
|
|
rc = -EINVAL;
|
|
smblib_err(chg, "Couldn't find votable PL_DISABLE rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
chg->pl_enable_votable_indirect = find_votable("PL_ENABLE_INDIRECT");
|
|
if (chg->pl_enable_votable_indirect == NULL) {
|
|
rc = -EINVAL;
|
|
smblib_err(chg,
|
|
"Couldn't find votable PL_ENABLE_INDIRECT rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
vote(chg->pl_disable_votable, PL_DELAY_VOTER, true, 0);
|
|
|
|
chg->smb_override_votable = create_votable("SMB_EN_OVERRIDE",
|
|
VOTE_SET_ANY,
|
|
smblib_smb_disable_override_vote_callback, chg);
|
|
if (IS_ERR(chg->smb_override_votable)) {
|
|
rc = PTR_ERR(chg->smb_override_votable);
|
|
chg->smb_override_votable = NULL;
|
|
return rc;
|
|
}
|
|
|
|
chg->dc_suspend_votable = create_votable("DC_SUSPEND", VOTE_SET_ANY,
|
|
smblib_dc_suspend_vote_callback,
|
|
chg);
|
|
if (IS_ERR(chg->dc_suspend_votable)) {
|
|
rc = PTR_ERR(chg->dc_suspend_votable);
|
|
chg->dc_suspend_votable = NULL;
|
|
return rc;
|
|
}
|
|
|
|
chg->dc_icl_votable = create_votable("DC_ICL", VOTE_MIN,
|
|
smblib_dc_icl_vote_callback,
|
|
chg);
|
|
if (IS_ERR(chg->dc_icl_votable)) {
|
|
rc = PTR_ERR(chg->dc_icl_votable);
|
|
return rc;
|
|
}
|
|
|
|
chg->awake_votable = create_votable("AWAKE", VOTE_SET_ANY,
|
|
smblib_awake_vote_callback,
|
|
chg);
|
|
if (IS_ERR(chg->awake_votable)) {
|
|
rc = PTR_ERR(chg->awake_votable);
|
|
chg->awake_votable = NULL;
|
|
return rc;
|
|
}
|
|
|
|
chg->chg_disable_votable = create_votable("CHG_DISABLE", VOTE_SET_ANY,
|
|
smblib_chg_disable_vote_callback,
|
|
chg);
|
|
if (IS_ERR(chg->chg_disable_votable)) {
|
|
rc = PTR_ERR(chg->chg_disable_votable);
|
|
chg->chg_disable_votable = NULL;
|
|
return rc;
|
|
}
|
|
|
|
chg->limited_irq_disable_votable = create_votable(
|
|
"USB_LIMITED_IRQ_DISABLE",
|
|
VOTE_SET_ANY,
|
|
smblib_limited_irq_disable_vote_callback,
|
|
chg);
|
|
if (IS_ERR(chg->limited_irq_disable_votable)) {
|
|
rc = PTR_ERR(chg->limited_irq_disable_votable);
|
|
chg->limited_irq_disable_votable = NULL;
|
|
return rc;
|
|
}
|
|
|
|
chg->hdc_irq_disable_votable = create_votable("USB_HDC_IRQ_DISABLE",
|
|
VOTE_SET_ANY,
|
|
smblib_hdc_irq_disable_vote_callback,
|
|
chg);
|
|
if (IS_ERR(chg->hdc_irq_disable_votable)) {
|
|
rc = PTR_ERR(chg->hdc_irq_disable_votable);
|
|
chg->hdc_irq_disable_votable = NULL;
|
|
return rc;
|
|
}
|
|
|
|
chg->icl_irq_disable_votable = create_votable("USB_ICL_IRQ_DISABLE",
|
|
VOTE_SET_ANY,
|
|
smblib_icl_irq_disable_vote_callback,
|
|
chg);
|
|
if (IS_ERR(chg->icl_irq_disable_votable)) {
|
|
rc = PTR_ERR(chg->icl_irq_disable_votable);
|
|
chg->icl_irq_disable_votable = NULL;
|
|
return rc;
|
|
}
|
|
|
|
chg->temp_change_irq_disable_votable = create_votable(
|
|
"TEMP_CHANGE_IRQ_DISABLE", VOTE_SET_ANY,
|
|
smblib_temp_change_irq_disable_vote_callback, chg);
|
|
if (IS_ERR(chg->temp_change_irq_disable_votable)) {
|
|
rc = PTR_ERR(chg->temp_change_irq_disable_votable);
|
|
chg->temp_change_irq_disable_votable = NULL;
|
|
return rc;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static void smblib_destroy_votables(struct smb_charger *chg)
|
|
{
|
|
if (chg->dc_suspend_votable)
|
|
destroy_votable(chg->dc_suspend_votable);
|
|
if (chg->dc_icl_votable)
|
|
destroy_votable(chg->dc_icl_votable);
|
|
if (chg->usb_icl_votable)
|
|
destroy_votable(chg->usb_icl_votable);
|
|
if (chg->awake_votable)
|
|
destroy_votable(chg->awake_votable);
|
|
if (chg->chg_disable_votable)
|
|
destroy_votable(chg->chg_disable_votable);
|
|
}
|
|
|
|
static void smblib_iio_deinit(struct smb_charger *chg)
|
|
{
|
|
if (!IS_ERR_OR_NULL(chg->iio.usbin_v_chan))
|
|
iio_channel_release(chg->iio.usbin_v_chan);
|
|
if (!IS_ERR_OR_NULL(chg->iio.usbin_i_chan))
|
|
iio_channel_release(chg->iio.usbin_i_chan);
|
|
if (!IS_ERR_OR_NULL(chg->iio.temp_chan))
|
|
iio_channel_release(chg->iio.temp_chan);
|
|
if (!IS_ERR_OR_NULL(chg->iio.sbux_chan))
|
|
iio_channel_release(chg->iio.sbux_chan);
|
|
if (!IS_ERR_OR_NULL(chg->iio.vph_v_chan))
|
|
iio_channel_release(chg->iio.vph_v_chan);
|
|
if (!IS_ERR_OR_NULL(chg->iio.die_temp_chan))
|
|
iio_channel_release(chg->iio.die_temp_chan);
|
|
if (!IS_ERR_OR_NULL(chg->iio.connector_temp_chan))
|
|
iio_channel_release(chg->iio.connector_temp_chan);
|
|
if (!IS_ERR_OR_NULL(chg->iio.skin_temp_chan))
|
|
iio_channel_release(chg->iio.skin_temp_chan);
|
|
if (!IS_ERR_OR_NULL(chg->iio.smb_temp_chan))
|
|
iio_channel_release(chg->iio.smb_temp_chan);
|
|
if (!IS_ERR_OR_NULL(chg->iio.dcin_v_chan))
|
|
iio_channel_release(chg->iio.dcin_v_chan);
|
|
}
|
|
|
|
static bool smblib_test_mode(struct smb_charger *chg)
|
|
{
|
|
struct device_node *np = of_find_node_by_path("/chosen");
|
|
const char *mode;
|
|
int rc;
|
|
bool test = false;
|
|
|
|
if (!np)
|
|
return test;
|
|
|
|
rc = of_property_read_string(np, "mmi,battery", &mode);
|
|
if ((rc >= 0) && mode) {
|
|
if (strcmp(mode, "test") == 0)
|
|
test = true;
|
|
}
|
|
of_node_put(np);
|
|
|
|
return test;
|
|
}
|
|
|
|
int smblib_init(struct smb_charger *chg)
|
|
{
|
|
union power_supply_propval prop_val;
|
|
int rc = 0;
|
|
|
|
device_init_wakeup(chg->dev, true);
|
|
mutex_init(&chg->smb_lock);
|
|
mutex_init(&chg->irq_status_lock);
|
|
mutex_init(&chg->dcin_aicl_lock);
|
|
mutex_init(&chg->dpdm_lock);
|
|
spin_lock_init(&chg->typec_pr_lock);
|
|
mutex_init(&chg->otg_lock);
|
|
INIT_WORK(&chg->bms_update_work, bms_update_work);
|
|
INIT_WORK(&chg->pl_update_work, pl_update_work);
|
|
INIT_WORK(&chg->jeita_update_work, jeita_update_work);
|
|
INIT_WORK(&chg->dcin_aicl_work, dcin_aicl_work);
|
|
INIT_WORK(&chg->cp_status_change_work, smblib_cp_status_change_work);
|
|
INIT_DELAYED_WORK(&chg->clear_hdc_work, clear_hdc_work);
|
|
INIT_DELAYED_WORK(&chg->icl_change_work, smblib_icl_change_work);
|
|
INIT_DELAYED_WORK(&chg->pl_enable_work, smblib_pl_enable_work);
|
|
INIT_DELAYED_WORK(&chg->pd_contract_work, smblib_pd_contract_work);
|
|
INIT_DELAYED_WORK(&chg->uusb_otg_work, smblib_uusb_otg_work);
|
|
INIT_DELAYED_WORK(&chg->bb_removal_work, smblib_bb_removal_work);
|
|
INIT_DELAYED_WORK(&chg->lpd_ra_open_work, smblib_lpd_ra_open_work);
|
|
INIT_DELAYED_WORK(&chg->lpd_detach_work, smblib_lpd_detach_work);
|
|
INIT_DELAYED_WORK(&chg->thermal_regulation_work,
|
|
smblib_thermal_regulation_work);
|
|
INIT_DELAYED_WORK(&chg->usbov_dbc_work, smblib_usbov_dbc_work);
|
|
INIT_DELAYED_WORK(&chg->pr_swap_detach_work,
|
|
smblib_pr_swap_detach_work);
|
|
INIT_DELAYED_WORK(&chg->pr_lock_clear_work,
|
|
smblib_pr_lock_clear_work);
|
|
timer_setup(&chg->apsd_timer, apsd_timer_cb, 0);
|
|
|
|
INIT_DELAYED_WORK(&chg->role_reversal_check,
|
|
smblib_typec_role_check_work);
|
|
|
|
if (chg->wa_flags & CHG_TERMINATION_WA) {
|
|
INIT_WORK(&chg->chg_termination_work,
|
|
smblib_chg_termination_work);
|
|
|
|
if (alarmtimer_get_rtcdev()) {
|
|
alarm_init(&chg->chg_termination_alarm, ALARM_BOOTTIME,
|
|
chg_termination_alarm_cb);
|
|
} else {
|
|
smblib_err(chg, "Couldn't get rtc device\n");
|
|
return -ENODEV;
|
|
}
|
|
}
|
|
|
|
if (chg->uusb_moisture_protection_enabled) {
|
|
INIT_WORK(&chg->moisture_protection_work,
|
|
smblib_moisture_protection_work);
|
|
|
|
if (alarmtimer_get_rtcdev()) {
|
|
alarm_init(&chg->moisture_protection_alarm,
|
|
ALARM_BOOTTIME, moisture_protection_alarm_cb);
|
|
} else {
|
|
smblib_err(chg, "Failed to initialize moisture protection alarm\n");
|
|
return -ENODEV;
|
|
}
|
|
}
|
|
|
|
if (alarmtimer_get_rtcdev()) {
|
|
alarm_init(&chg->dcin_aicl_alarm, ALARM_REALTIME,
|
|
dcin_aicl_alarm_cb);
|
|
} else {
|
|
smblib_err(chg, "Failed to initialize dcin aicl alarm\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
chg->fake_capacity = -EINVAL;
|
|
chg->fake_input_current_limited = -EINVAL;
|
|
chg->fake_batt_status = -EINVAL;
|
|
chg->sink_src_mode = UNATTACHED_MODE;
|
|
chg->jeita_configured = false;
|
|
chg->sec_chg_selected = POWER_SUPPLY_CHARGER_SEC_NONE;
|
|
chg->cp_reason = POWER_SUPPLY_CP_NONE;
|
|
chg->thermal_status = TEMP_BELOW_RANGE;
|
|
chg->typec_irq_en = true;
|
|
chg->cp_topo = -EINVAL;
|
|
chg->dr_mode = TYPEC_PORT_DRP;
|
|
|
|
chg->mmi.test_mode_soc = DEFAULT_TEST_MODE_SOC;
|
|
chg->mmi.test_mode_temp = DEFAULT_TEST_MODE_TEMP;
|
|
chg->mmi.test_mode = smblib_test_mode(chg);
|
|
if (chg->mmi.test_mode)
|
|
smblib_dbg(chg, PR_MISC, "Test Mode Enabled\n");
|
|
|
|
switch (chg->mode) {
|
|
case PARALLEL_MASTER:
|
|
rc = qcom_batt_init(&chg->chg_param);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't init qcom_batt_init rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
rc = qcom_step_chg_init(chg->dev, chg->step_chg_enabled,
|
|
chg->sw_jeita_enabled, chg->jeita_arb_enable);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't init qcom_step_chg_init rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
rc = smblib_create_votables(chg);
|
|
if (rc < 0) {
|
|
smblib_err(chg, "Couldn't create votables rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
chg->bms_psy = power_supply_get_by_name("bms");
|
|
|
|
if (chg->sec_pl_present) {
|
|
chg->pl.psy = power_supply_get_by_name("parallel");
|
|
if (chg->pl.psy) {
|
|
if (chg->sec_chg_selected
|
|
!= POWER_SUPPLY_CHARGER_SEC_CP) {
|
|
rc = smblib_select_sec_charger(chg,
|
|
POWER_SUPPLY_CHARGER_SEC_PL,
|
|
POWER_SUPPLY_CP_NONE, false);
|
|
if (rc < 0)
|
|
smblib_err(chg, "Couldn't config pl charger rc=%d\n",
|
|
rc);
|
|
}
|
|
|
|
if (chg->smb_temp_max == -EINVAL) {
|
|
rc = smblib_get_thermal_threshold(chg,
|
|
SMB_REG_H_THRESHOLD_MSB_REG,
|
|
&chg->smb_temp_max);
|
|
if (rc < 0) {
|
|
dev_err(chg->dev, "Couldn't get charger_temp_max rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
prop_val.intval = chg->smb_temp_max;
|
|
rc = power_supply_set_property(chg->pl.psy,
|
|
POWER_SUPPLY_PROP_CHARGER_TEMP_MAX,
|
|
&prop_val);
|
|
if (rc < 0) {
|
|
dev_err(chg->dev, "Couldn't set POWER_SUPPLY_PROP_CHARGER_TEMP_MAX rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
}
|
|
}
|
|
|
|
rc = smblib_register_notifier(chg);
|
|
if (rc < 0) {
|
|
smblib_err(chg,
|
|
"Couldn't register notifier rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
break;
|
|
case PARALLEL_SLAVE:
|
|
break;
|
|
default:
|
|
smblib_err(chg, "Unsupported mode %d\n", chg->mode);
|
|
return -EINVAL;
|
|
}
|
|
|
|
mmi_init(chg);
|
|
|
|
return rc;
|
|
}
|
|
|
|
int smblib_deinit(struct smb_charger *chg)
|
|
{
|
|
mmi_deinit(chg);
|
|
|
|
switch (chg->mode) {
|
|
case PARALLEL_MASTER:
|
|
if (chg->uusb_moisture_protection_enabled) {
|
|
alarm_cancel(&chg->moisture_protection_alarm);
|
|
cancel_work_sync(&chg->moisture_protection_work);
|
|
}
|
|
if (chg->wa_flags & CHG_TERMINATION_WA) {
|
|
alarm_cancel(&chg->chg_termination_alarm);
|
|
cancel_work_sync(&chg->chg_termination_work);
|
|
}
|
|
del_timer_sync(&chg->apsd_timer);
|
|
cancel_work_sync(&chg->bms_update_work);
|
|
cancel_work_sync(&chg->jeita_update_work);
|
|
cancel_work_sync(&chg->pl_update_work);
|
|
cancel_work_sync(&chg->dcin_aicl_work);
|
|
cancel_work_sync(&chg->cp_status_change_work);
|
|
cancel_delayed_work_sync(&chg->clear_hdc_work);
|
|
cancel_delayed_work_sync(&chg->icl_change_work);
|
|
cancel_delayed_work_sync(&chg->pl_enable_work);
|
|
cancel_delayed_work_sync(&chg->uusb_otg_work);
|
|
cancel_delayed_work_sync(&chg->bb_removal_work);
|
|
cancel_delayed_work_sync(&chg->lpd_ra_open_work);
|
|
cancel_delayed_work_sync(&chg->lpd_detach_work);
|
|
cancel_delayed_work_sync(&chg->thermal_regulation_work);
|
|
cancel_delayed_work_sync(&chg->usbov_dbc_work);
|
|
cancel_delayed_work_sync(&chg->role_reversal_check);
|
|
cancel_delayed_work_sync(&chg->pr_swap_detach_work);
|
|
power_supply_unreg_notifier(&chg->nb);
|
|
smblib_destroy_votables(chg);
|
|
qcom_step_chg_deinit();
|
|
qcom_batt_deinit();
|
|
break;
|
|
case PARALLEL_SLAVE:
|
|
break;
|
|
default:
|
|
smblib_err(chg, "Unsupported mode %d\n", chg->mode);
|
|
return -EINVAL;
|
|
}
|
|
|
|
smblib_iio_deinit(chg);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*********************
|
|
* MMI Functionality *
|
|
*********************/
|
|
|
|
struct smb_charger *mmi_chip;
|
|
|
|
static char *stepchg_str[] = {
|
|
[STEP_MAX] = "MAX",
|
|
[STEP_NORM] = "NORMAL",
|
|
[STEP_EB] = "EXTERNAL BATT",
|
|
[STEP_FULL] = "FULL",
|
|
[STEP_FLOAT] = "FLOAT",
|
|
[STEP_DEMO] = "DEMO",
|
|
[STEP_STOP] = "STOP",
|
|
[STEP_NONE] = "NONE",
|
|
};
|
|
|
|
static char *ebchg_str[] = {
|
|
[EB_DISCONN] = "DISCONN",
|
|
[EB_SINK] = "SINK",
|
|
[EB_SRC] = "SOURCE",
|
|
[EB_OFF] = "OFF",
|
|
};
|
|
|
|
int smblib_get_prop_dc_voltage_now(struct smb_charger *chg,
|
|
union power_supply_propval *val)
|
|
{
|
|
int rc = 0;
|
|
|
|
rc = smblib_get_prop_dc_present(chg, val);
|
|
if (rc < 0 || !val->intval)
|
|
return rc;
|
|
|
|
if (!chg->iio.dcin_v_chan ||
|
|
PTR_ERR(chg->iio.dcin_v_chan) == -EPROBE_DEFER)
|
|
chg->iio.dcin_v_chan = iio_channel_get(chg->dev, "dcin_v");
|
|
|
|
if (IS_ERR(chg->iio.dcin_v_chan))
|
|
return PTR_ERR(chg->iio.dcin_v_chan);
|
|
|
|
return iio_read_channel_processed(chg->iio.dcin_v_chan, &val->intval);
|
|
}
|
|
|
|
#define MIN_TEMP_C -20
|
|
#define MAX_TEMP_C 60
|
|
#define MIN_MAX_TEMP_C 47
|
|
#define HYSTERISIS_DEGC 2
|
|
static bool mmi_find_temp_zone(struct smb_charger *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 (!chip) {
|
|
smblib_dbg(chip, PR_MOTO, "called before chip valid!\n");
|
|
return false;
|
|
}
|
|
|
|
zones = chip->mmi.temp_zones;
|
|
num_zones = chip->mmi.num_temp_zones;
|
|
prev_zone = chip->mmi.pres_temp_zone;
|
|
|
|
if (chip->mmi.max_chrg_temp >= MIN_MAX_TEMP_C)
|
|
max_temp = chip->mmi.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->mmi.pres_temp_zone = ZONE_HOT;
|
|
else
|
|
chip->mmi.pres_temp_zone = i + 1;
|
|
return true;
|
|
}
|
|
}
|
|
chip->mmi.pres_temp_zone = ZONE_COLD;
|
|
return true;
|
|
}
|
|
|
|
if (prev_zone == ZONE_COLD) {
|
|
if (temp_c >= MIN_TEMP_C + HYSTERISIS_DEGC)
|
|
chip->mmi.pres_temp_zone = ZONE_FIRST;
|
|
} else if (prev_zone == ZONE_HOT) {
|
|
if (temp_c <= max_temp - HYSTERISIS_DEGC)
|
|
chip->mmi.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 += HYSTERISIS_DEGC;
|
|
|
|
if (zones[prev_zone].fcc_max_ma < colder_fcc)
|
|
colder_t -= HYSTERISIS_DEGC;
|
|
|
|
if (temp_c < MIN_TEMP_C)
|
|
chip->mmi.pres_temp_zone = ZONE_COLD;
|
|
else if (temp_c >= max_temp)
|
|
chip->mmi.pres_temp_zone = ZONE_HOT;
|
|
else if (temp_c >= hotter_t)
|
|
chip->mmi.pres_temp_zone++;
|
|
else if (temp_c < colder_t)
|
|
chip->mmi.pres_temp_zone--;
|
|
}
|
|
|
|
if (prev_zone != chip->mmi.pres_temp_zone) {
|
|
smblib_dbg(chip, PR_MOTO,
|
|
"Entered Temp Zone %d!\n",
|
|
chip->mmi.pres_temp_zone);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
#define TAPER_COUNT 2
|
|
#define TAPER_DROP_MA 100
|
|
static bool mmi_has_current_tapered(struct smb_charger *chip,
|
|
int batt_ma, int taper_ma)
|
|
{
|
|
bool change_state = false;
|
|
int allowed_fcc, target_ma;
|
|
|
|
if (!chip) {
|
|
smblib_dbg(chip, PR_MOTO, "called before chip valid!\n");
|
|
return false;
|
|
}
|
|
|
|
allowed_fcc = get_effective_result(chip->fcc_votable) / 1000;
|
|
|
|
if (allowed_fcc >= taper_ma)
|
|
target_ma = taper_ma;
|
|
else
|
|
target_ma = allowed_fcc - TAPER_DROP_MA;
|
|
|
|
if (batt_ma < 0) {
|
|
batt_ma *= -1;
|
|
if (batt_ma <= target_ma)
|
|
if (chip->mmi.chrg_taper_cnt >= TAPER_COUNT) {
|
|
change_state = true;
|
|
chip->mmi.chrg_taper_cnt = 0;
|
|
} else
|
|
chip->mmi.chrg_taper_cnt++;
|
|
else
|
|
chip->mmi.chrg_taper_cnt = 0;
|
|
} else {
|
|
if (chip->mmi.chrg_taper_cnt >= TAPER_COUNT) {
|
|
change_state = true;
|
|
chip->mmi.chrg_taper_cnt = 0;
|
|
} else
|
|
chip->mmi.chrg_taper_cnt++;
|
|
}
|
|
|
|
return change_state;
|
|
}
|
|
|
|
static bool is_wls_present(struct smb_charger *chip)
|
|
{
|
|
int rc;
|
|
union power_supply_propval ret = {0, };
|
|
const struct power_supply_desc *wls;
|
|
|
|
if (chip->mmi.wls_psy && chip->mmi.wls_psy->desc)
|
|
wls = chip->mmi.wls_psy->desc;
|
|
else
|
|
return false;
|
|
|
|
if (!wls->get_property)
|
|
return false;
|
|
|
|
rc = wls->get_property(chip->mmi.wls_psy,
|
|
POWER_SUPPLY_PROP_PRESENT,
|
|
&ret);
|
|
if (rc < 0) {
|
|
smblib_err(chip, "Couldn't get wls status\n");
|
|
return false;
|
|
}
|
|
|
|
return ret.intval ? true : false;
|
|
}
|
|
|
|
static bool is_usbeb_present(struct smb_charger *chip)
|
|
{
|
|
int rc;
|
|
union power_supply_propval ret = {0, };
|
|
const struct power_supply_desc *usbeb;
|
|
|
|
if (chip->mmi.usbeb_psy && chip->mmi.usbeb_psy->desc)
|
|
usbeb = chip->mmi.usbeb_psy->desc;
|
|
else
|
|
return false;
|
|
|
|
if (!usbeb->get_property)
|
|
return false;
|
|
|
|
rc = power_supply_get_property(chip->mmi.usbeb_psy,
|
|
POWER_SUPPLY_PROP_PRESENT,
|
|
&ret);
|
|
if (rc < 0) {
|
|
smblib_err(chip, "Couldn't get usbeb status\n");
|
|
return false;
|
|
}
|
|
|
|
return ret.intval ? true : false;
|
|
}
|
|
|
|
static int get_eb_pwr_prop(struct smb_charger *chip,
|
|
enum power_supply_property prop)
|
|
{
|
|
union power_supply_propval ret = {0, };
|
|
int eb_prop;
|
|
int rc;
|
|
struct power_supply *eb_pwr_psy;
|
|
|
|
eb_pwr_psy =
|
|
power_supply_get_by_name((char *)chip->mmi.eb_pwr_psy_name);
|
|
if (!eb_pwr_psy || !eb_pwr_psy->desc ||
|
|
!eb_pwr_psy->desc->get_property)
|
|
return -ENODEV;
|
|
|
|
rc = power_supply_get_property(eb_pwr_psy, prop, &ret);
|
|
if (rc) {
|
|
smblib_dbg(chip, PR_MISC,
|
|
"eb pwr error reading Prop %d rc = %d\n", prop, rc);
|
|
ret.intval = -EINVAL;
|
|
}
|
|
eb_prop = ret.intval;
|
|
|
|
power_supply_put(eb_pwr_psy);
|
|
|
|
return eb_prop;
|
|
}
|
|
|
|
static int is_eb_present(struct smb_charger *chip)
|
|
{
|
|
int ret;
|
|
struct power_supply *eb_batt_psy;
|
|
|
|
eb_batt_psy =
|
|
power_supply_get_by_name((char *)chip->mmi.eb_batt_psy_name);
|
|
if (!eb_batt_psy || !eb_batt_psy->desc ||
|
|
!eb_batt_psy->desc->get_property) {
|
|
ret = false;
|
|
} else {
|
|
ret = true;
|
|
power_supply_put(eb_batt_psy);
|
|
}
|
|
|
|
pr_info("%s eb is present? %d\n", __func__, ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int get_eb_prop(struct smb_charger *chip,
|
|
enum power_supply_property prop)
|
|
{
|
|
union power_supply_propval ret = {0, };
|
|
int eb_prop;
|
|
int rc;
|
|
struct power_supply *eb_batt_psy;
|
|
|
|
eb_batt_psy =
|
|
power_supply_get_by_name((char *)chip->mmi.eb_batt_psy_name);
|
|
if (!eb_batt_psy || !eb_batt_psy->desc ||
|
|
!eb_batt_psy->desc->get_property)
|
|
return -ENODEV;
|
|
|
|
rc = power_supply_get_property(eb_batt_psy, prop, &ret);
|
|
if (rc) {
|
|
smblib_dbg(chip, PR_MISC,
|
|
"eb batt error reading Prop %d rc = %d\n",
|
|
prop, rc);
|
|
ret.intval = -EINVAL;
|
|
}
|
|
eb_prop = ret.intval;
|
|
|
|
power_supply_put(eb_batt_psy);
|
|
|
|
return eb_prop;
|
|
}
|
|
|
|
irqreturn_t dcin_uv_irq_handler(int irq, void *data)
|
|
{
|
|
struct smb_irq_data *irq_data = data;
|
|
struct smb_charger *chg = irq_data->parent_data;
|
|
int ret, dcin_chg_cur = 0;
|
|
union power_supply_propval vol = {0, };
|
|
|
|
smblib_dbg(chg, PR_INTERRUPT, "IRQ: %s\n", irq_data->name);
|
|
|
|
smblib_get_prop_dc_voltage_now(chg,
|
|
&vol);
|
|
smblib_dbg(chg, PR_INTERRUPT, "dcin: %d\n", vol);
|
|
if ((vol.intval > 3800000) && (vol.intval < 4600000))
|
|
ret = 1;
|
|
else
|
|
ret = 0;
|
|
|
|
if (ret <= 0) {
|
|
goto end;
|
|
}
|
|
|
|
dcin_chg_cur = get_effective_result(chg->dc_icl_votable);
|
|
|
|
//divide dcin current by uv abnormal count nubmer
|
|
dcin_chg_cur = dcin_chg_cur - (dcin_chg_cur / 4);
|
|
if (dcin_chg_cur <= USBIN_25MA)
|
|
dcin_chg_cur = USBIN_100MA;
|
|
|
|
chg->mmi.dc_ebmax_current_ma = dcin_chg_cur / 1000;
|
|
chg->mmi.cl_ebsrc = chg->mmi.dc_ebmax_current_ma;
|
|
vote(chg->dc_icl_votable,
|
|
HEARTBEAT_VOTER, true, dcin_chg_cur);
|
|
|
|
pr_info("DCIN uv set dcin %d\n", dcin_chg_cur);
|
|
|
|
end:
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static void mmi_check_extbat_ability(struct smb_charger *chip, char *able)
|
|
{
|
|
int rc;
|
|
const struct power_supply_desc *eb_pwr_d;
|
|
union power_supply_propval ret = {0, };
|
|
struct power_supply *eb_pwr_psy;
|
|
|
|
if (!able)
|
|
return;
|
|
|
|
eb_pwr_psy =
|
|
power_supply_get_by_name((char *)chip->mmi.eb_pwr_psy_name);
|
|
|
|
*able = 0;
|
|
|
|
if (!eb_pwr_psy || !eb_pwr_psy->desc) {
|
|
*able |= (EB_RCV_NEVER | EB_SND_NEVER);
|
|
return;
|
|
}
|
|
|
|
eb_pwr_d = eb_pwr_psy->desc;
|
|
rc = eb_pwr_d->get_property(eb_pwr_psy,
|
|
POWER_SUPPLY_PROP_PTP_INTERNAL_RECEIVE,
|
|
&ret);
|
|
if (rc) {
|
|
smblib_err(chip,
|
|
"Could not read Receive Params rc = %d\n", rc);
|
|
*able |= EB_RCV_NEVER;
|
|
} else if (ret.intval == POWER_SUPPLY_PTP_INT_RCV_PARALLEL)
|
|
*able |= EB_RCV_PARALLEL;
|
|
else if ((ret.intval == POWER_SUPPLY_PTP_INT_RCV_NEVER) ||
|
|
(ret.intval == POWER_SUPPLY_PTP_INT_RCV_UNKNOWN))
|
|
*able |= EB_RCV_NEVER;
|
|
|
|
rc = eb_pwr_d->get_property(eb_pwr_psy,
|
|
POWER_SUPPLY_PROP_PTP_POWER_REQUIRED,
|
|
&ret);
|
|
if (rc) {
|
|
smblib_err(chip,
|
|
"Could not read Power Required rc = %d\n", rc);
|
|
*able |= EB_RCV_NEVER;
|
|
} else if ((ret.intval == POWER_SUPPLY_PTP_POWER_NOT_REQUIRED) ||
|
|
(ret.intval == POWER_SUPPLY_PTP_POWER_REQUIREMENTS_UNKNOWN))
|
|
*able |= EB_RCV_NEVER;
|
|
|
|
rc = eb_pwr_d->get_property(eb_pwr_psy,
|
|
POWER_SUPPLY_PROP_PTP_POWER_AVAILABLE,
|
|
&ret);
|
|
if (rc) {
|
|
smblib_err(chip,
|
|
"Could not read Power Available rc = %d\n", rc);
|
|
*able |= EB_SND_NEVER;
|
|
} else if ((ret.intval == POWER_SUPPLY_PTP_POWER_NOT_AVAILABLE) ||
|
|
(ret.intval == POWER_SUPPLY_PTP_POWER_AVAILABILITY_UNKNOWN))
|
|
*able |= EB_SND_NEVER;
|
|
else if (ret.intval == POWER_SUPPLY_PTP_POWER_AVAILABLE_INTERNAL) {
|
|
rc = eb_pwr_d->get_property(eb_pwr_psy,
|
|
POWER_SUPPLY_PROP_PTP_INTERNAL_SEND,
|
|
&ret);
|
|
if (rc) {
|
|
smblib_err(chip,
|
|
"Could not read Send Params rc = %d\n", rc);
|
|
*able |= EB_SND_NEVER;
|
|
} else if ((ret.intval == POWER_SUPPLY_PTP_INT_SND_NEVER) ||
|
|
(ret.intval == POWER_SUPPLY_PTP_INT_SND_UNKNOWN))
|
|
*able |= EB_SND_NEVER;
|
|
else if (ret.intval == POWER_SUPPLY_PTP_INT_SND_LOW_BATT_SAVER)
|
|
*able |= EB_SND_LOW;
|
|
} else if (ret.intval == POWER_SUPPLY_PTP_POWER_AVAILABLE_EXTERNAL)
|
|
*able |= EB_SND_EXT;
|
|
|
|
power_supply_put(eb_pwr_psy);
|
|
}
|
|
|
|
static void mmi_set_extbat_in_cl(struct smb_charger *chip)
|
|
{
|
|
int rc;
|
|
union power_supply_propval ret = {0, };
|
|
struct power_supply *eb_pwr_psy =
|
|
power_supply_get_by_name((char *)chip->mmi.eb_pwr_psy_name);
|
|
|
|
if (!eb_pwr_psy || !eb_pwr_psy->desc ||
|
|
!eb_pwr_psy->desc->set_property)
|
|
return;
|
|
|
|
ret.intval = chip->mmi.cl_ebchg * 1000;
|
|
|
|
smblib_dbg(chip, PR_MISC, "Set EB In Current %d uA\n", ret.intval);
|
|
rc = power_supply_set_property(eb_pwr_psy,
|
|
POWER_SUPPLY_PROP_PTP_MAX_INPUT_CURRENT,
|
|
&ret);
|
|
if (rc)
|
|
smblib_err(chip, "Failed to set EB Input Current %d mA",
|
|
ret.intval / 1000);
|
|
|
|
power_supply_put(eb_pwr_psy);
|
|
}
|
|
|
|
static void mmi_get_extbat_out_cl(struct smb_charger *chip)
|
|
{
|
|
int rc;
|
|
union power_supply_propval ret = {0, };
|
|
struct power_supply *eb_pwr_psy =
|
|
power_supply_get_by_name((char *)chip->mmi.eb_pwr_psy_name);
|
|
int prev_cl_ebsrc = chip->mmi.cl_ebsrc;
|
|
bool ebsrc_max, wls_src_max;
|
|
|
|
if (!eb_pwr_psy || !eb_pwr_psy->desc ||
|
|
!eb_pwr_psy->desc->get_property) {
|
|
chip->mmi.cl_ebsrc = 0;
|
|
return;
|
|
}
|
|
|
|
if (chip->mmi.turbo_pwr_ebsrc == TURBO_EBSRC_UNKNOWN) {
|
|
rc = power_supply_get_property(eb_pwr_psy,
|
|
POWER_SUPPLY_PROP_PTP_POWER_SOURCE,
|
|
&ret);
|
|
|
|
switch (ret.intval) {
|
|
case POWER_SUPPLY_PTP_POWER_SOURCE_NONE_TURBO:
|
|
case POWER_SUPPLY_PTP_POWER_SOURCE_BATTERY_TURBO:
|
|
case POWER_SUPPLY_PTP_POWER_SOURCE_WIRED_TURBO:
|
|
case POWER_SUPPLY_PTP_POWER_SOURCE_WIRELESS_TURBO:
|
|
chip->mmi.turbo_pwr_ebsrc = TURBO_EBSRC_VALID;
|
|
break;
|
|
default:
|
|
chip->mmi.turbo_pwr_ebsrc = TURBO_EBSRC_NOT_SUPPORTED;
|
|
break;
|
|
}
|
|
}
|
|
|
|
rc = power_supply_get_property(eb_pwr_psy,
|
|
POWER_SUPPLY_PROP_PTP_MAX_OUTPUT_CURRENT,
|
|
&ret);
|
|
if (rc)
|
|
smblib_err(chip, "Failed to get EB Output Current");
|
|
else {
|
|
smblib_dbg(chip, PR_MISC,
|
|
"Get EB Out Current %d uA\n", ret.intval);
|
|
ret.intval /= 1000;
|
|
|
|
wls_src_max = (is_wls_present(chip) &&
|
|
(chip->mmi.vo_ebsrc > (MICRO_5V / 1000)));
|
|
ebsrc_max =
|
|
((chip->mmi.turbo_pwr_ebsrc == TURBO_EBSRC_VALID) &&
|
|
((eb_attach_stop_soc == 100) ||
|
|
is_wls_present(chip)));
|
|
|
|
if (ret.intval == 0)
|
|
chip->mmi.cl_ebsrc = 0;
|
|
else if ((ret.intval < chip->mmi.dc_ebmax_current_ma) ||
|
|
ebsrc_max ||
|
|
wls_src_max ||
|
|
is_usbeb_present(chip))
|
|
chip->mmi.cl_ebsrc = ret.intval;
|
|
else
|
|
chip->mmi.cl_ebsrc = chip->mmi.dc_ebmax_current_ma;
|
|
}
|
|
|
|
if (prev_cl_ebsrc != chip->mmi.cl_ebsrc)
|
|
smblib_dbg(chip, PR_MISC, "cl_ebsrc %d mA, retval %d mA\n",
|
|
chip->mmi.cl_ebsrc, ret.intval);
|
|
|
|
power_supply_put(eb_pwr_psy);
|
|
}
|
|
|
|
static void mmi_get_extbat_in_vl(struct smb_charger *chip)
|
|
{
|
|
int rc;
|
|
union power_supply_propval ret = {0, };
|
|
struct power_supply *eb_pwr_psy =
|
|
power_supply_get_by_name((char *)chip->mmi.eb_pwr_psy_name);
|
|
int prev_vi_ebsrc = chip->mmi.vi_ebsrc;
|
|
|
|
if (!eb_pwr_psy || !eb_pwr_psy->desc ||
|
|
!eb_pwr_psy->desc->get_property) {
|
|
chip->mmi.vi_ebsrc = 0;
|
|
return;
|
|
}
|
|
|
|
rc = power_supply_get_property(eb_pwr_psy,
|
|
POWER_SUPPLY_PROP_PTP_MAX_INPUT_VOLTAGE,
|
|
&ret);
|
|
if (rc) {
|
|
smblib_err(chip, "Failed to get EB Input Voltage");
|
|
chip->mmi.vi_ebsrc = MICRO_5V;
|
|
} else {
|
|
smblib_dbg(chip, PR_MISC,
|
|
"Get EB In Voltage %d uV\n", ret.intval);
|
|
chip->mmi.vi_ebsrc = ret.intval;
|
|
}
|
|
|
|
if (prev_vi_ebsrc != chip->mmi.vi_ebsrc)
|
|
smblib_dbg(chip, PR_MISC, "vi_ebsrc %d uV, retval %d uV\n",
|
|
chip->mmi.vi_ebsrc, ret.intval);
|
|
|
|
power_supply_put(eb_pwr_psy);
|
|
}
|
|
|
|
#define USBIN_TOLER 500000
|
|
static void mmi_set_extbat_in_volt(struct smb_charger *chip)
|
|
{
|
|
int rc;
|
|
union power_supply_propval ret = {0, };
|
|
struct power_supply *eb_pwr_psy =
|
|
power_supply_get_by_name((char *)chip->mmi.eb_pwr_psy_name);
|
|
|
|
if (!eb_pwr_psy || !eb_pwr_psy->desc ||
|
|
!eb_pwr_psy->desc->set_property)
|
|
return;
|
|
|
|
rc = smblib_get_prop_usb_voltage_now(chip, &ret);
|
|
if (rc < 0)
|
|
ret.intval = MICRO_5V;
|
|
else if (ret.intval >= (MICRO_9V - USBIN_TOLER))
|
|
ret.intval = MICRO_9V;
|
|
else
|
|
ret.intval = MICRO_5V;
|
|
|
|
smblib_dbg(chip, PR_MISC, "Set EB In Voltage %d uV\n", ret.intval);
|
|
rc = power_supply_set_property(eb_pwr_psy,
|
|
POWER_SUPPLY_PROP_PTP_INPUT_VOLTAGE,
|
|
&ret);
|
|
if (rc)
|
|
smblib_err(chip, "Failed to set EB Input Voltage %d mV",
|
|
ret.intval / 1000);
|
|
|
|
power_supply_put(eb_pwr_psy);
|
|
}
|
|
|
|
static void mmi_get_extbat_out_volt(struct smb_charger *chip)
|
|
{
|
|
int rc;
|
|
union power_supply_propval ret = {0, };
|
|
struct power_supply *eb_pwr_psy =
|
|
power_supply_get_by_name((char *)chip->mmi.eb_pwr_psy_name);
|
|
int prev_vo_ebsrc = chip->mmi.vo_ebsrc;
|
|
|
|
if (!eb_pwr_psy || !eb_pwr_psy->desc ||
|
|
!eb_pwr_psy->desc->get_property) {
|
|
chip->mmi.vo_ebsrc = 0;
|
|
return;
|
|
}
|
|
|
|
rc = power_supply_get_property(eb_pwr_psy,
|
|
POWER_SUPPLY_PROP_PTP_OUTPUT_VOLTAGE,
|
|
&ret);
|
|
if (rc)
|
|
smblib_err(chip, "Failed to get EB Output Voltage");
|
|
else {
|
|
smblib_dbg(chip, PR_MISC, "Get EB Out Voltage %d uV\n",
|
|
ret.intval);
|
|
ret.intval /= 1000;
|
|
chip->mmi.vo_ebsrc = ret.intval;
|
|
}
|
|
|
|
if (prev_vo_ebsrc != chip->mmi.vo_ebsrc)
|
|
smblib_dbg(chip, PR_MISC, "vo_ebsrc %d mV, retval %d mV\n",
|
|
chip->mmi.vo_ebsrc, ret.intval);
|
|
|
|
power_supply_put(eb_pwr_psy);
|
|
}
|
|
|
|
static void mmi_set_extbat_out_vl(struct smb_charger *chip)
|
|
{
|
|
int rc;
|
|
union power_supply_propval ret = {0, };
|
|
struct power_supply *eb_pwr_psy =
|
|
power_supply_get_by_name((char *)chip->mmi.eb_pwr_psy_name);
|
|
|
|
if (!eb_pwr_psy || !eb_pwr_psy->desc ||
|
|
!eb_pwr_psy->desc->set_property)
|
|
return;
|
|
|
|
ret.intval = chip->mmi.vl_ebsrc;
|
|
|
|
smblib_dbg(chip, PR_MISC, "Set EB Out Voltage %d uV\n", ret.intval);
|
|
rc = power_supply_set_property(eb_pwr_psy,
|
|
POWER_SUPPLY_PROP_PTP_MAX_OUTPUT_VOLTAGE,
|
|
&ret);
|
|
if (rc)
|
|
smblib_err(chip, "Failed to set EB Output Voltage %d mV",
|
|
ret.intval / 1000);
|
|
|
|
power_supply_put(eb_pwr_psy);
|
|
}
|
|
|
|
static int mmi_get_extbat_state(struct smb_charger *chip, int *state)
|
|
{
|
|
int rc;
|
|
union power_supply_propval ret = {0, };
|
|
struct power_supply *eb_pwr_psy =
|
|
power_supply_get_by_name((char *)chip->mmi.eb_pwr_psy_name);
|
|
|
|
if (!eb_pwr_psy || !eb_pwr_psy->desc ||
|
|
!eb_pwr_psy->desc->get_property) {
|
|
*state = EB_DISCONN;
|
|
chip->mmi.turbo_pwr_ebsrc = TURBO_EBSRC_UNKNOWN;
|
|
return 0;
|
|
}
|
|
|
|
rc = power_supply_get_property(eb_pwr_psy,
|
|
POWER_SUPPLY_PROP_PTP_CURRENT_FLOW,
|
|
&ret);
|
|
power_supply_put(eb_pwr_psy);
|
|
|
|
if (rc) {
|
|
smblib_dbg(chip, PR_MISC, "Failed to get EB State");
|
|
return -EINVAL;
|
|
}
|
|
|
|
smblib_dbg(chip, PR_MISC, "Get EB State %d\n", ret.intval);
|
|
switch (ret.intval) {
|
|
case POWER_SUPPLY_PTP_CURRENT_FROM_PHONE:
|
|
*state = EB_SINK;
|
|
break;
|
|
case POWER_SUPPLY_PTP_CURRENT_TO_PHONE:
|
|
*state = EB_SRC;
|
|
break;
|
|
case POWER_SUPPLY_PTP_CURRENT_OFF:
|
|
default:
|
|
*state = EB_OFF;
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#define MAX_PINCTRL_TRIES 5
|
|
static void mmi_set_extbat_state(struct smb_charger *chip,
|
|
enum ebchg_state state,
|
|
bool force)
|
|
{
|
|
int rc = 0;
|
|
char ability = 0;
|
|
int i = 0;
|
|
int dcin_mv = 0;
|
|
int dcin_high_cnt = 0;
|
|
static unsigned char mmi_pinctrl_tries;
|
|
union power_supply_propval ret = {0, };
|
|
struct power_supply *eb_pwr_psy =
|
|
power_supply_get_by_name((char *)chip->mmi.eb_pwr_psy_name);
|
|
|
|
if (mmi_pinctrl_tries < MAX_PINCTRL_TRIES) {
|
|
chip->mmi.smb_pinctrl =
|
|
pinctrl_get_select(chip->dev, "eb_active");
|
|
if (!chip->mmi.smb_pinctrl)
|
|
smblib_err(chip,
|
|
"Could not get/set pinctrl state active\n");
|
|
chip->mmi.smb_pinctrl = pinctrl_get_select_default(chip->dev);
|
|
if (!chip->mmi.smb_pinctrl)
|
|
smblib_err(chip, "Could not get/set pinctrl state\n");
|
|
|
|
mmi_pinctrl_tries++;
|
|
}
|
|
|
|
if ((state == chip->mmi.ebchg_state) && !force) {
|
|
if (eb_pwr_psy)
|
|
goto set_eb_done;
|
|
else
|
|
return;
|
|
}
|
|
|
|
if (!eb_pwr_psy || !eb_pwr_psy->desc ||
|
|
!eb_pwr_psy->desc->get_property) {
|
|
vote(chip->usb_icl_votable, EB_VOTER,
|
|
false, 0);
|
|
vote(chip->dc_suspend_votable, EB_VOTER,
|
|
true, 1);
|
|
if (gpio_is_valid(chip->mmi.ebchg_gpio.gpio))
|
|
gpio_set_value(chip->mmi.ebchg_gpio.gpio, 0);
|
|
chip->mmi.cl_ebsrc = 0;
|
|
|
|
ret.intval = MICRO_9V;
|
|
rc = smblib_set_prop_pd_voltage_max(chip, &ret);
|
|
if (rc < 0)
|
|
smblib_err(chip,
|
|
"Couldn't set 9V USBC Voltage rc=%d\n", rc);
|
|
chip->mmi.turbo_pwr_ebsrc = TURBO_EBSRC_UNKNOWN;
|
|
chip->mmi.ebchg_state = EB_DISCONN;
|
|
return;
|
|
}
|
|
|
|
mmi_check_extbat_ability(chip, &ability);
|
|
|
|
if ((state == EB_SINK) && (ability & EB_RCV_NEVER) && !force &&
|
|
!(ability & EB_RCV_PARALLEL)) {
|
|
smblib_err(chip,
|
|
"Setting Sink State not Allowed on RVC Never EB\n");
|
|
goto set_eb_done;
|
|
}
|
|
|
|
if ((state == EB_SRC) && (ability & EB_SND_NEVER) &&
|
|
!chip->mmi.usbeb_present && !force) {
|
|
smblib_err(chip,
|
|
"Setting SRC State not Allowed on SND Never EB\n");
|
|
goto set_eb_done;
|
|
}
|
|
|
|
mutex_lock(&chip->otg_lock);
|
|
if (chip->mmi.is_otg_enable == true && state == EB_SRC) {
|
|
smblib_err(chip,
|
|
"OTG is enable, so couldn't set SRC state\n");
|
|
mutex_unlock(&chip->otg_lock);
|
|
goto set_eb_done;
|
|
}
|
|
mutex_unlock(&chip->otg_lock);
|
|
|
|
smblib_dbg(chip, PR_MOTO,
|
|
"EB State is %d setting %d\n",
|
|
chip->mmi.ebchg_state, state);
|
|
|
|
switch (state) {
|
|
case EB_SINK:
|
|
mmi_get_extbat_in_vl(chip);
|
|
|
|
if (chip->mmi.vi_ebsrc < MICRO_9V) {
|
|
ret.intval = MICRO_5V;
|
|
rc = smblib_set_prop_pd_voltage_max(chip, &ret);
|
|
if (rc < 0)
|
|
smblib_err(chip,
|
|
"Couldn't set 5V USBC Voltage rc=%d\n",
|
|
rc);
|
|
msleep(500);
|
|
rc = smblib_get_prop_usb_voltage_now(chip, &ret);
|
|
if (rc < 0) {
|
|
smblib_err(chip,
|
|
"Couldn't get 5V USBC Voltage rc=%d\n",
|
|
rc);
|
|
goto set_eb_done;
|
|
}
|
|
if (ret.intval > (MICRO_5V + USBIN_TOLER)) {
|
|
smblib_err(chip,
|
|
"Voltage too high for EB_SINK %d uV\n",
|
|
ret.intval);
|
|
goto set_eb_done;
|
|
}
|
|
}
|
|
|
|
mmi_set_extbat_in_volt(chip);
|
|
|
|
ret.intval = POWER_SUPPLY_PTP_CURRENT_FROM_PHONE;
|
|
rc = power_supply_set_property(eb_pwr_psy,
|
|
POWER_SUPPLY_PROP_PTP_CURRENT_FLOW,
|
|
&ret);
|
|
if (!rc) {
|
|
chip->mmi.ebchg_state = state;
|
|
|
|
vote(chip->usb_icl_votable, EB_VOTER,
|
|
false, 0);
|
|
vote(chip->dc_suspend_votable, EB_VOTER,
|
|
true, 1);
|
|
if (gpio_is_valid(chip->mmi.ebchg_gpio.gpio))
|
|
gpio_set_value(chip->mmi.ebchg_gpio.gpio, 1);
|
|
}
|
|
break;
|
|
case EB_SRC:
|
|
if (!(ability & EB_SND_EXT) && (eb_attach_stop_soc != 100))
|
|
chip->mmi.vl_ebsrc = MICRO_5V;
|
|
else
|
|
chip->mmi.vl_ebsrc = MICRO_9V;
|
|
|
|
mmi_set_extbat_out_vl(chip);
|
|
ret.intval = POWER_SUPPLY_PTP_CURRENT_TO_PHONE;
|
|
rc = power_supply_set_property(eb_pwr_psy,
|
|
POWER_SUPPLY_PROP_PTP_CURRENT_FLOW,
|
|
&ret);
|
|
if (!rc) {
|
|
for (i = 0; i < 100; i++) {
|
|
rc = smblib_get_prop_dc_voltage_now(chip,
|
|
&ret);
|
|
if (rc == -EINVAL)
|
|
break;
|
|
dcin_mv = ret.intval / 1000;
|
|
pr_err("DCIN mv = %d\n", dcin_mv);
|
|
if (dcin_mv > 4500)
|
|
dcin_high_cnt++;
|
|
else
|
|
dcin_high_cnt = 0;
|
|
|
|
if (dcin_high_cnt > 10) {
|
|
/* Total required delay is 200 msec
|
|
* for power mux transition timing.
|
|
*/
|
|
msleep(100);
|
|
break;
|
|
}
|
|
msleep(20);
|
|
}
|
|
|
|
chip->mmi.ebchg_state = state;
|
|
vote(chip->usb_icl_votable, EB_VOTER,
|
|
true, 0);
|
|
vote(chip->dc_suspend_votable, EB_VOTER,
|
|
false, 1);
|
|
if (gpio_is_valid(chip->mmi.ebchg_gpio.gpio))
|
|
gpio_set_value(chip->mmi.ebchg_gpio.gpio, 0);
|
|
|
|
ret.intval = MICRO_9V;
|
|
rc = smblib_set_prop_pd_voltage_max(chip, &ret);
|
|
if (rc < 0)
|
|
smblib_err(chip,
|
|
"Couldn't set 9V USBC Voltage rc=%d\n",
|
|
rc);
|
|
}
|
|
break;
|
|
case EB_OFF:
|
|
ret.intval = POWER_SUPPLY_PTP_CURRENT_OFF;
|
|
rc = power_supply_set_property(eb_pwr_psy,
|
|
POWER_SUPPLY_PROP_PTP_CURRENT_FLOW,
|
|
&ret);
|
|
if (!rc) {
|
|
chip->mmi.ebchg_state = state;
|
|
if (gpio_is_valid(chip->mmi.ebchg_gpio.gpio))
|
|
gpio_set_value(chip->mmi.ebchg_gpio.gpio, 0);
|
|
vote(chip->usb_icl_votable, EB_VOTER,
|
|
false, 0);
|
|
vote(chip->dc_suspend_votable, EB_VOTER,
|
|
true, 1);
|
|
|
|
ret.intval = MICRO_9V;
|
|
rc = smblib_set_prop_pd_voltage_max(chip, &ret);
|
|
if (rc < 0)
|
|
smblib_err(chip,
|
|
"Couldn't set 9V USBC Voltage rc=%d\n",
|
|
rc);
|
|
}
|
|
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
set_eb_done:
|
|
power_supply_put(eb_pwr_psy);
|
|
}
|
|
|
|
#define WEAK_CHRG_THRSH 450
|
|
#define TURBO_CHRG_THRSH 2500
|
|
void mmi_chrg_rate_check(struct smb_charger *chip)
|
|
{
|
|
int rc;
|
|
union power_supply_propval val;
|
|
struct mmi_params *mmi = &chip->mmi;
|
|
int prev_chg_rate = mmi->charger_rate;
|
|
char *charge_rate[] = {
|
|
"None", "Normal", "Weak", "Turbo"
|
|
};
|
|
|
|
rc = smblib_get_prop_usb_present(chip, &val);
|
|
if (rc < 0) {
|
|
smblib_err(chip, "Error getting USB Present rc = %d\n", rc);
|
|
return;
|
|
} else if (!val.intval) {
|
|
if ((mmi->ebchg_state != EB_DISCONN) &&
|
|
(mmi->turbo_pwr_ebsrc == TURBO_EBSRC_VALID))
|
|
mmi->charger_rate = POWER_SUPPLY_CHARGE_RATE_TURBO;
|
|
else if (is_usbeb_present(chip)) {
|
|
if (mmi->cl_ebsrc >= TURBO_CHRG_THRSH)
|
|
mmi->charger_rate =
|
|
POWER_SUPPLY_CHARGE_RATE_TURBO;
|
|
else
|
|
mmi->charger_rate =
|
|
POWER_SUPPLY_CHARGE_RATE_NORMAL;
|
|
} else if (is_wls_present(chip))
|
|
mmi->charger_rate = POWER_SUPPLY_CHARGE_RATE_NORMAL;
|
|
else
|
|
mmi->charger_rate = POWER_SUPPLY_CHARGE_RATE_NONE;
|
|
|
|
goto end_rate_check;
|
|
}
|
|
|
|
if (mmi->charger_rate == POWER_SUPPLY_CHARGE_RATE_TURBO)
|
|
goto end_rate_check;
|
|
|
|
val.intval = get_client_vote(chip->usb_icl_votable, PD_VOTER);
|
|
if ((val.intval / 1000) >= TURBO_CHRG_THRSH) {
|
|
mmi->charger_rate = POWER_SUPPLY_CHARGE_RATE_TURBO;
|
|
goto end_rate_check;
|
|
}
|
|
|
|
if (chip->typec_mode == POWER_SUPPLY_TYPEC_SOURCE_HIGH ||
|
|
mmi->hvdcp3_con) {
|
|
if(chip->hvdcp_disable)
|
|
mmi->charger_rate = POWER_SUPPLY_CHARGE_RATE_NORMAL;
|
|
else
|
|
mmi->charger_rate = POWER_SUPPLY_CHARGE_RATE_TURBO;
|
|
goto end_rate_check;
|
|
}
|
|
|
|
rc = smblib_get_prop_input_current_limited(chip, &val);
|
|
if (rc < 0) {
|
|
smblib_err(chip, "Error getting AICL Limited rc = %d\n", rc);
|
|
} else if (val.intval) {
|
|
int target_icl = get_effective_result(chip->usb_icl_votable);
|
|
rc = smblib_get_prop_input_current_settled(chip, &val);
|
|
if (rc < 0) {
|
|
smblib_err(chip,
|
|
"Error getting AICL Settled rc = %d\n", rc);
|
|
} else if (target_icl > 0 && val.intval < WEAK_CHRG_THRSH) {
|
|
mmi->charger_rate = POWER_SUPPLY_CHARGE_RATE_WEAK;
|
|
goto end_rate_check;
|
|
}
|
|
}
|
|
|
|
mmi->charger_rate = POWER_SUPPLY_CHARGE_RATE_NORMAL;
|
|
|
|
end_rate_check:
|
|
if (prev_chg_rate != mmi->charger_rate)
|
|
smblib_err(chip, "%s Charger Detected\n",
|
|
charge_rate[mmi->charger_rate]);
|
|
|
|
}
|
|
|
|
static int mmi_psy_notifier_call(struct notifier_block *nb, unsigned long val,
|
|
void *v)
|
|
{
|
|
struct smb_charger *chip = container_of(nb,
|
|
struct smb_charger, mmi.mmi_psy_notifier);
|
|
struct power_supply *psy = v;
|
|
|
|
if (!chip) {
|
|
smblib_dbg(chip, PR_MOTO, "called before chip valid!\n");
|
|
return NOTIFY_DONE;
|
|
}
|
|
|
|
if ((val == PSY_EVENT_PROP_ADDED) ||
|
|
(val == PSY_EVENT_PROP_REMOVED)) {
|
|
smblib_dbg(chip, PR_MISC, "PSY Added/Removed run HB!\n");
|
|
cancel_delayed_work(&chip->mmi.heartbeat_work);
|
|
schedule_delayed_work(&chip->mmi.heartbeat_work,
|
|
msecs_to_jiffies(0));
|
|
return NOTIFY_OK;
|
|
}
|
|
|
|
if (val != PSY_EVENT_PROP_CHANGED)
|
|
return NOTIFY_OK;
|
|
|
|
if (psy &&
|
|
(strcmp(psy->desc->name,
|
|
(char *)chip->mmi.eb_pwr_psy_name) == 0)) {
|
|
smblib_dbg(chip, PR_MISC, "PSY changed on PTP\n");
|
|
cancel_delayed_work(&chip->mmi.heartbeat_work);
|
|
schedule_delayed_work(&chip->mmi.heartbeat_work,
|
|
msecs_to_jiffies(100));
|
|
return NOTIFY_OK;
|
|
}
|
|
|
|
return NOTIFY_OK;
|
|
}
|
|
|
|
void update_charging_limit_modes(struct smb_charger *chip, int batt_soc)
|
|
{
|
|
enum charging_limit_modes charging_limit_modes;
|
|
|
|
charging_limit_modes = chip->mmi.charging_limit_modes;
|
|
if ((charging_limit_modes != CHARGING_LIMIT_RUN)
|
|
&& (batt_soc >= chip->mmi.upper_limit_capacity))
|
|
charging_limit_modes = CHARGING_LIMIT_RUN;
|
|
else if ((charging_limit_modes != CHARGING_LIMIT_OFF)
|
|
&& (batt_soc <= chip->mmi.lower_limit_capacity))
|
|
charging_limit_modes = CHARGING_LIMIT_OFF;
|
|
|
|
if (charging_limit_modes != chip->mmi.charging_limit_modes)
|
|
chip->mmi.charging_limit_modes = charging_limit_modes;
|
|
}
|
|
|
|
/* For Testing 50/50 Turbo Charge Split with EB */
|
|
static int force_eb_fifty;
|
|
module_param(force_eb_fifty, int, 0644);
|
|
|
|
#define CHARGER_DETECTION_DONE 7
|
|
#define SMBCHG_HEARTBEAT_INTERVAL_NS 70000000000
|
|
#define HEARTBEAT_DELAY_MS 60000
|
|
#define HEARTBEAT_HOLDOFF_MS 10000
|
|
#define HEARTBEAT_EB_WAIT_MS 1000
|
|
#define HYST_STEP_MV 50
|
|
#define EB_SPLIT_MA 500
|
|
#define DEMO_MODE_HYS_SOC 5
|
|
#define DEMO_MODE_VOLTAGE 4000
|
|
#define VBUS_INPUT_VOLTAGE_TARGET 5200
|
|
#define VBUS_INPUT_VOLTAGE_NOM ((VBUS_INPUT_VOLTAGE_TARGET) - 200)
|
|
#define VBUS_INPUT_VOLTAGE_MAX ((VBUS_INPUT_VOLTAGE_TARGET) + 200)
|
|
#define VBUS_INPUT_VOLTAGE_MIN 4000
|
|
#define VBUS_INPUT_MAX_COUNT 4
|
|
#define WARM_TEMP 45
|
|
#define COOL_TEMP 0
|
|
#define REV_BST_THRESH 4650
|
|
#define REV_BST_DROP 150
|
|
#define REV_BST_MA -10
|
|
static void mmi_heartbeat_work(struct work_struct *work)
|
|
{
|
|
struct smb_charger *chip = container_of(work,
|
|
struct smb_charger,
|
|
mmi.heartbeat_work.work);
|
|
int rc;
|
|
int batt_mv;
|
|
int batt_ma;
|
|
int batt_soc;
|
|
int eb_soc;
|
|
int batt_temp;
|
|
int usb_mv;
|
|
int dcin_mv;
|
|
u8 apsd_reg;
|
|
bool icl_override;
|
|
static int vbus_inc_mv = VBUS_INPUT_VOLTAGE_TARGET;
|
|
bool vbus_inc_now = false;
|
|
int vbus_present = 0;
|
|
int charger_present = 0;
|
|
int cl_usb = -EINVAL;
|
|
int cl_pd = -EINVAL;
|
|
int cl_cc = -EINVAL;
|
|
|
|
int prev_step;
|
|
char eb_able = 0;
|
|
int hb_resch_time;
|
|
int ebparams_cnt = chip->mmi.update_eb_params;
|
|
bool eb_chrg_allowed;
|
|
|
|
int eb_state = 0;
|
|
|
|
int pwr_ext;
|
|
bool eb_sink_to_off = false;
|
|
|
|
union power_supply_propval val;
|
|
struct mmi_params *mmi = &chip->mmi;
|
|
struct mmi_temp_zone *zone;
|
|
int max_fv_mv;
|
|
int target_usb;
|
|
int target_dc;
|
|
int target_fcc;
|
|
int target_fv;
|
|
int prev_vl_ebsrc = chip->mmi.vl_ebsrc;
|
|
int pok_irq;
|
|
static int prev_vbus_mv = -1;
|
|
|
|
if (!atomic_read(&chip->mmi.hb_ready))
|
|
return;
|
|
|
|
/* Have not been resumed so wait another 100 ms */
|
|
if (chip->suspended) {
|
|
smblib_err(chip, "SMB HB running before Resume\n");
|
|
schedule_delayed_work(&mmi->heartbeat_work,
|
|
msecs_to_jiffies(300));
|
|
return;
|
|
}
|
|
|
|
vote(chip->awake_votable, HEARTBEAT_VOTER, true, true);
|
|
|
|
alarm_try_to_cancel(&chip->mmi.heartbeat_alarm);
|
|
|
|
/* Collect External Battery Information */
|
|
eb_soc = get_eb_prop(chip, POWER_SUPPLY_PROP_CAPACITY);
|
|
pwr_ext = get_eb_pwr_prop(chip, POWER_SUPPLY_PROP_PTP_EXTERNAL);
|
|
mmi_check_extbat_ability(chip, &eb_able);
|
|
|
|
if (eb_soc == -EINVAL)
|
|
eb_soc = 0;
|
|
|
|
if ((eb_soc == -ENODEV) && (pwr_ext == -ENODEV))
|
|
mmi_set_extbat_state(chip, EB_DISCONN, false);
|
|
else if (mmi->ebchg_state == EB_DISCONN)
|
|
mmi_set_extbat_state(chip, EB_OFF, false);
|
|
else {
|
|
rc = mmi_get_extbat_state(chip, &eb_state);
|
|
if ((rc >= 0) && (mmi->ebchg_state != eb_state))
|
|
mmi_set_extbat_state(chip, EB_OFF, true);
|
|
}
|
|
|
|
if (pwr_ext == POWER_SUPPLY_PTP_EXT_SUPPORTED) {
|
|
mmi->usbeb_present = is_usbeb_present(chip);
|
|
mmi->wls_present = is_wls_present(chip);
|
|
} else {
|
|
mmi->usbeb_present = false;
|
|
mmi->wls_present = false;
|
|
}
|
|
/* Hold Wakelock if External Battery has External Power */
|
|
if ((mmi->wls_present || mmi->usbeb_present) &&
|
|
(mmi->ebchg_state == EB_SRC))
|
|
vote(chip->awake_votable, WIRELESS_VOTER, true, 0);
|
|
else
|
|
vote(chip->awake_votable, WIRELESS_VOTER, false, 0);
|
|
|
|
if ((chip->mmi.ebchg_state == EB_SRC) && chip->mmi.check_ebsrc_vl) {
|
|
if (!(eb_able & EB_SND_EXT) && (eb_attach_stop_soc != 100))
|
|
chip->mmi.vl_ebsrc = MICRO_5V;
|
|
else
|
|
chip->mmi.vl_ebsrc = MICRO_9V;
|
|
|
|
if (prev_vl_ebsrc != chip->mmi.vl_ebsrc)
|
|
mmi_set_extbat_out_vl(chip);
|
|
}
|
|
chip->mmi.check_ebsrc_vl = false;
|
|
|
|
/* Collect Current Information */
|
|
rc = smblib_get_prop_usb_present(chip, &val);
|
|
if (rc < 0) {
|
|
pr_err("Error getting USB Present rc = %d\n", rc);
|
|
goto end_hb;
|
|
} else
|
|
vbus_present = val.intval;
|
|
|
|
if (!vbus_present) {
|
|
vbus_inc_mv = VBUS_INPUT_VOLTAGE_TARGET;
|
|
charger_present = 0;
|
|
mmi->charger_debounce_cnt = 0;
|
|
if (mmi->apsd_done &&
|
|
chip->typec_mode == POWER_SUPPLY_TYPEC_NONE)
|
|
typec_src_removal(chip);
|
|
} else if (mmi->charger_debounce_cnt < CHARGER_DETECTION_DONE) {
|
|
mmi->charger_debounce_cnt++;
|
|
/* Set the USB CL to 500 only if pd is not active to avoid
|
|
* PD Compliance issues */
|
|
if (!chip->pd_active)
|
|
cl_usb = 500;
|
|
if ((chip->typec_mode == POWER_SUPPLY_TYPEC_NONE) ||
|
|
(chip->typec_mode ==
|
|
POWER_SUPPLY_TYPEC_SINK_AUDIO_ADAPTER) ||
|
|
(chip->pd_active && (chip->pd_contract_uv > 0))) {
|
|
mmi->charger_debounce_cnt = CHARGER_DETECTION_DONE;
|
|
charger_present = 1;
|
|
mmi->apsd_done = true;
|
|
}
|
|
} else if (mmi->charger_debounce_cnt == CHARGER_DETECTION_DONE)
|
|
charger_present = 1;
|
|
|
|
rc = smblib_get_prop_from_bms(chip,
|
|
POWER_SUPPLY_PROP_VOLTAGE_NOW, &val);
|
|
if (rc < 0) {
|
|
smblib_err(chip, "Error getting Batt Voltage rc = %d\n", rc);
|
|
goto end_hb;
|
|
} else
|
|
batt_mv = val.intval / 1000;
|
|
|
|
rc = smblib_get_prop_from_bms(chip,
|
|
POWER_SUPPLY_PROP_CURRENT_NOW, &val);
|
|
if (rc < 0) {
|
|
smblib_err(chip, "Error getting Batt Current rc = %d\n", rc);
|
|
goto end_hb;
|
|
} else
|
|
batt_ma = val.intval / 1000;
|
|
|
|
rc = smblib_get_prop_batt_capacity(chip, &val);
|
|
if (rc < 0) {
|
|
smblib_err(chip, "Error getting Batt Capacity rc = %d\n", rc);
|
|
goto end_hb;
|
|
} else
|
|
batt_soc = val.intval;
|
|
|
|
if (chip->mmi.test_mode && !(chip->mmi.test_mode_temp < -350)
|
|
&& !(chip->mmi.test_mode_temp > 1250))
|
|
val.intval = chip->mmi.test_mode_temp;
|
|
else
|
|
rc = smblib_get_prop_from_bms(chip,
|
|
POWER_SUPPLY_PROP_TEMP, &val);
|
|
if (rc < 0) {
|
|
smblib_err(chip,
|
|
"Error getting Batt Temperature rc = %d\n", rc);
|
|
goto end_hb;
|
|
} else
|
|
batt_temp = val.intval / 10;
|
|
|
|
rc = smblib_get_prop_usb_voltage_now(chip, &val);
|
|
if (rc < 0) {
|
|
smblib_err(chip, "Error getting USB Voltage rc = %d\n", rc);
|
|
goto end_hb;
|
|
} else
|
|
usb_mv = val.intval / 1000;
|
|
|
|
rc = smblib_read(chip, APSD_RESULT_STATUS_REG, &apsd_reg);
|
|
if (rc < 0) {
|
|
smblib_err(chip, "Couldn't read APSD_RESULT_STATUS rc=%d\n",
|
|
rc);
|
|
icl_override = false;
|
|
apsd_reg = 0;
|
|
} else {
|
|
icl_override = !!(apsd_reg & APSD_RESULT_STATUS_7_BIT);
|
|
apsd_reg &= APSD_RESULT_STATUS_MASK;
|
|
}
|
|
|
|
if (prev_vbus_mv == -1)
|
|
prev_vbus_mv = usb_mv;
|
|
|
|
smblib_dbg(chip, PR_MOTO, "batt=%d mV, %d mA, %d C, USB= %d mV\n",
|
|
batt_mv, batt_ma, batt_temp, usb_mv);
|
|
|
|
pok_irq = chip->irq_info[SWITCHER_POWER_OK_IRQ].irq;
|
|
if (chip->reverse_boost || (batt_mv >= usb_mv && vbus_present)) {
|
|
if (((usb_mv < REV_BST_THRESH) &&
|
|
((prev_vbus_mv - REV_BST_DROP) > usb_mv)) ||
|
|
(batt_ma > REV_BST_MA)) {
|
|
smblib_err(chip,
|
|
"Reverse Boosted: Clear, USB Suspend\n");
|
|
if (chip->mmi.factory_mode)
|
|
smblib_set_usb_suspend(chip, true);
|
|
else
|
|
vote(chip->usb_icl_votable, BOOST_BACK_VOTER,
|
|
true, 0);
|
|
msleep(50);
|
|
if (chip->mmi.factory_mode)
|
|
smblib_set_usb_suspend(chip,
|
|
chip->mmi.force_chg_suspend);
|
|
else
|
|
vote(chip->usb_icl_votable, BOOST_BACK_VOTER,
|
|
false, 0);
|
|
if (chip->reverse_boost) {
|
|
chip->reverse_boost = false;
|
|
enable_irq(pok_irq);
|
|
}
|
|
} else {
|
|
smblib_err(chip,
|
|
"Reverse Boosted: USB %d mV PUSB %d mV\n",
|
|
usb_mv, prev_vbus_mv);
|
|
}
|
|
}
|
|
|
|
prev_vbus_mv = usb_mv;
|
|
|
|
if (charger_present) {
|
|
val.intval = get_client_vote(chip->usb_icl_votable,
|
|
USB_PSY_VOTER);
|
|
cl_usb = val.intval / 1000;
|
|
|
|
val.intval = get_client_vote(chip->usb_icl_votable,
|
|
PD_VOTER);
|
|
cl_pd = val.intval / 1000;
|
|
|
|
switch (chip->typec_mode) {
|
|
case POWER_SUPPLY_TYPEC_SOURCE_DEFAULT:
|
|
if (mmi->hvdcp3_con)
|
|
cl_cc = 3000;
|
|
else
|
|
cl_cc = 500;
|
|
break;
|
|
case POWER_SUPPLY_TYPEC_SOURCE_MEDIUM:
|
|
cl_cc = 1500;
|
|
break;
|
|
case POWER_SUPPLY_TYPEC_SOURCE_HIGH:
|
|
if(chip->hvdcp_disable)
|
|
cl_cc = 2000;
|
|
else
|
|
cl_cc = 3000;
|
|
break;
|
|
case POWER_SUPPLY_TYPEC_SINK_AUDIO_ADAPTER:
|
|
cl_cc = 500;
|
|
break;
|
|
case POWER_SUPPLY_TYPEC_NONE:
|
|
if (vbus_present) {
|
|
if (mmi->hvdcp3_con)
|
|
cl_cc = 3000;
|
|
else
|
|
cl_cc = 500;
|
|
} else
|
|
cl_cc = 0;
|
|
|
|
if (vbus_present &&
|
|
!icl_override &&
|
|
(apsd_reg == 0)) {
|
|
rc = smblib_masked_write(chip, CMD_ICL_OVERRIDE_REG,
|
|
ICL_OVERRIDE_BIT,
|
|
ICL_OVERRIDE_BIT);
|
|
if (rc < 0)
|
|
smblib_err(chip,
|
|
"Fail ICL Over rc%d\n", rc);
|
|
}
|
|
break;
|
|
default:
|
|
cl_cc = 0;
|
|
break;
|
|
}
|
|
|
|
if (cl_pd > 0)
|
|
cl_usb = cl_pd;
|
|
else if ((cl_cc > 500) ||
|
|
(chip->typec_mode ==
|
|
POWER_SUPPLY_TYPEC_SINK_AUDIO_ADAPTER))
|
|
cl_usb = cl_cc;
|
|
else if (chip->real_charger_type ==
|
|
POWER_SUPPLY_TYPE_USB_DCP)
|
|
if (apsd_reg & OCP_CHARGER_BIT)
|
|
cl_usb = 1000;
|
|
else if(chip->mmi.support_2a_dcp)
|
|
cl_usb = 2000;
|
|
else if(chip->mmi.support_mb_dcp)
|
|
cl_usb = 1900;
|
|
else
|
|
cl_usb = 1500;
|
|
else if (chip->real_charger_type ==
|
|
POWER_SUPPLY_TYPE_USB_CDP)
|
|
cl_usb = 1500;
|
|
else if ((cl_cc == 500) &&
|
|
(cl_usb <= 500))
|
|
cl_usb = cl_cc;
|
|
else if (cl_usb <= 0)
|
|
cl_usb = 500;
|
|
|
|
if (cl_usb >= TURBO_CHRG_THRSH)
|
|
mmi->charger_rate = POWER_SUPPLY_CHARGE_RATE_TURBO;
|
|
}
|
|
|
|
|
|
/* Determine External Battery Next State */
|
|
eb_chrg_allowed = (!(eb_able & EB_RCV_NEVER) &&
|
|
!mmi->usbeb_present &&
|
|
!mmi->wls_present);
|
|
|
|
if (force_eb_fifty)
|
|
eb_able |= EB_RCV_PARALLEL;
|
|
|
|
mmi_get_extbat_out_volt(chip);
|
|
mmi_get_extbat_out_cl(chip);
|
|
|
|
if (mmi->enable_charging_limit && mmi->is_factory_image)
|
|
update_charging_limit_modes(chip, batt_soc);
|
|
|
|
if (!vbus_present) {
|
|
switch (mmi->ebchg_state) {
|
|
case EB_SRC:
|
|
if (mmi->wls_present || mmi->usbeb_present) {
|
|
mmi->eb_rechrg = false;
|
|
if (batt_soc == 100)
|
|
mmi_set_extbat_state(chip, EB_OFF,
|
|
false);
|
|
} else if ((eb_able & EB_SND_NEVER) ||
|
|
(eb_able & EB_SND_EXT) ||
|
|
(eb_on_sw == 0)) {
|
|
mmi_set_extbat_state(chip, EB_OFF, false);
|
|
mmi->eb_rechrg = true;
|
|
}
|
|
|
|
break;
|
|
case EB_SINK:
|
|
mmi_set_extbat_state(chip, EB_OFF, false);
|
|
eb_sink_to_off = true;
|
|
break;
|
|
case EB_OFF:
|
|
if (mmi->wls_present || mmi->usbeb_present) {
|
|
mmi->eb_rechrg = false;
|
|
if (batt_soc < 100)
|
|
mmi_set_extbat_state(chip, EB_SRC,
|
|
false);
|
|
} else if ((eb_able & EB_SND_NEVER) ||
|
|
(eb_on_sw == 0))
|
|
mmi_set_extbat_state(chip, EB_OFF, false);
|
|
else if (eb_able & EB_SND_LOW) {
|
|
if (batt_soc <= eb_low_start_soc)
|
|
mmi_set_extbat_state(chip, EB_SRC,
|
|
false);
|
|
else
|
|
mmi_set_extbat_state(chip, EB_OFF,
|
|
false);
|
|
} else
|
|
mmi_set_extbat_state(chip, EB_SRC, false);
|
|
|
|
if ((mmi->ebchg_state == EB_SRC) && (batt_soc < 75))
|
|
mmi->eb_rechrg = false;
|
|
|
|
break;
|
|
case EB_DISCONN:
|
|
default:
|
|
break;
|
|
}
|
|
} else if (vbus_present ||
|
|
mmi->wls_present ||
|
|
mmi->usbeb_present) {
|
|
mmi->eb_rechrg = false;
|
|
}
|
|
|
|
if (mmi->eb_rechrg)
|
|
target_dc = mmi->dc_eff_current_ma;
|
|
else if (mmi->cl_ebsrc)
|
|
target_dc = mmi->cl_ebsrc;
|
|
else
|
|
target_dc = mmi->dc_ebmax_current_ma;
|
|
|
|
mmi_find_temp_zone(chip, batt_temp);
|
|
if (mmi->pres_temp_zone >= mmi->num_temp_zones)
|
|
zone = &mmi->temp_zones[0];
|
|
else
|
|
zone = &mmi->temp_zones[mmi->pres_temp_zone];
|
|
|
|
if (mmi->base_fv_mv == 0) {
|
|
mmi->base_fv_mv = get_client_vote(chip->fv_votable,
|
|
BATT_PROFILE_VOTER) / 1000;
|
|
vote(chip->fv_votable,
|
|
BATT_PROFILE_VOTER, false, 0);
|
|
}
|
|
max_fv_mv = mmi->base_fv_mv;
|
|
|
|
rc = smblib_get_prop_dc_voltage_now(chip, &val);
|
|
if (rc == -EINVAL) {
|
|
dcin_mv = 0;
|
|
smblib_err(chip, "Failed to get DCIN\n");
|
|
} else
|
|
dcin_mv = val.intval / 1000;
|
|
|
|
/* Determine Next State */
|
|
prev_step = chip->mmi.pres_chrg_step;
|
|
|
|
if (mmi->charging_limit_modes == CHARGING_LIMIT_RUN)
|
|
pr_warn("Factory Mode/Image so Limiting Charging!!!\n");
|
|
|
|
if (!charger_present && (mmi->ebchg_state != EB_SRC)) {
|
|
mmi->pres_chrg_step = STEP_NONE;
|
|
} else if ((mmi->pres_temp_zone == ZONE_HOT) ||
|
|
(mmi->pres_temp_zone == ZONE_COLD) ||
|
|
(mmi->charging_limit_modes == CHARGING_LIMIT_RUN)) {
|
|
chip->mmi.pres_chrg_step = STEP_STOP;
|
|
} else if (mmi->force_eb_chrg && !(eb_able & EB_RCV_NEVER) &&
|
|
(mmi->ebchg_state != EB_DISCONN)) {
|
|
mmi->pres_chrg_step = STEP_EB;
|
|
} else if (mmi->demo_mode) {
|
|
bool voltage_full;
|
|
static int demo_full_soc = 100;
|
|
int usb_suspend = get_client_vote(chip->usb_icl_votable,
|
|
DEMO_VOTER);
|
|
if (usb_suspend == -EINVAL)
|
|
usb_suspend = 0;
|
|
else if(usb_suspend == 0)
|
|
usb_suspend = 1;
|
|
|
|
mmi->pres_chrg_step = STEP_DEMO;
|
|
smblib_dbg(chip, PR_MOTO,
|
|
"Battery in Demo Mode charging Limited %dper\n",
|
|
mmi->demo_mode);
|
|
|
|
voltage_full = ((usb_suspend == 0) &&
|
|
((batt_mv + HYST_STEP_MV) >= DEMO_MODE_VOLTAGE) &&
|
|
mmi_has_current_tapered(chip, batt_ma,
|
|
mmi->chrg_iterm));
|
|
|
|
if ((usb_suspend == 0) &&
|
|
((batt_soc >= mmi->demo_mode) ||
|
|
voltage_full)) {
|
|
demo_full_soc = batt_soc;
|
|
vote(chip->usb_icl_votable, DEMO_VOTER, true, 0);
|
|
vote(chip->dc_suspend_votable, DEMO_VOTER, true, 0);
|
|
usb_suspend = 1;
|
|
} else if (usb_suspend &&
|
|
(batt_soc <=
|
|
(demo_full_soc - DEMO_MODE_HYS_SOC))) {
|
|
if (chip->mmi.ebchg_state == EB_SINK) {
|
|
mmi_set_extbat_state(chip, EB_OFF, false);
|
|
mmi->cl_ebchg = 0;
|
|
mmi_set_extbat_in_cl(chip);
|
|
}
|
|
vote(chip->usb_icl_votable, DEMO_VOTER, false, 0);
|
|
vote(chip->dc_suspend_votable, DEMO_VOTER, false, 0);
|
|
usb_suspend = 0;
|
|
mmi->chrg_taper_cnt = 0;
|
|
}
|
|
|
|
if (vbus_present &&
|
|
((eb_soc >= mmi->demo_mode) ||
|
|
(eb_able & EB_RCV_NEVER))) {
|
|
mmi_set_extbat_state(chip, EB_OFF, false);
|
|
mmi->cl_ebchg = 0;
|
|
mmi_set_extbat_in_cl(chip);
|
|
} else if (vbus_present && usb_suspend &&
|
|
(eb_soc <=
|
|
(mmi->demo_mode - DEMO_MODE_HYS_SOC))) {
|
|
mmi->cl_ebchg = cl_usb;
|
|
mmi_set_extbat_in_cl(chip);
|
|
mmi_set_extbat_state(chip, EB_SINK, false);
|
|
}
|
|
} else if ((mmi->pres_chrg_step == STEP_NONE) ||
|
|
(mmi->pres_chrg_step == STEP_STOP)) {
|
|
if (zone->norm_mv && (batt_mv >= zone->norm_mv)) {
|
|
if (zone->fcc_norm_ma)
|
|
mmi->pres_chrg_step = STEP_NORM;
|
|
else
|
|
mmi->pres_chrg_step = STEP_STOP;
|
|
} else
|
|
mmi->pres_chrg_step = STEP_MAX;
|
|
} else if (mmi->pres_chrg_step == STEP_MAX) {
|
|
if (!zone->norm_mv) {
|
|
/* No Step in this Zone */
|
|
mmi->chrg_taper_cnt = 0;
|
|
if ((batt_mv + HYST_STEP_MV) >= max_fv_mv)
|
|
mmi->pres_chrg_step = STEP_NORM;
|
|
else
|
|
mmi->pres_chrg_step = STEP_MAX;
|
|
} else if ((batt_mv + HYST_STEP_MV) < zone->norm_mv) {
|
|
mmi->chrg_taper_cnt = 0;
|
|
mmi->pres_chrg_step = STEP_MAX;
|
|
} else if (!zone->fcc_norm_ma)
|
|
mmi->pres_chrg_step = STEP_FLOAT;
|
|
else if (mmi_has_current_tapered(chip, batt_ma,
|
|
zone->fcc_norm_ma)) {
|
|
mmi->chrg_taper_cnt = 0;
|
|
if (smblib_charge_halted(chip)) {
|
|
vote(chip->chg_disable_votable,
|
|
HEARTBEAT_VOTER, true, 0);
|
|
smblib_err(chip, "Charge Halt..Toggle\n");
|
|
msleep(50);
|
|
}
|
|
mmi->pres_chrg_step = STEP_NORM;
|
|
}
|
|
} else if (mmi->pres_chrg_step == STEP_NORM) {
|
|
if (!zone->fcc_norm_ma)
|
|
mmi->pres_chrg_step = STEP_STOP;
|
|
else if ((batt_soc < 100) ||
|
|
(batt_mv + HYST_STEP_MV) < max_fv_mv) {
|
|
mmi->chrg_taper_cnt = 0;
|
|
mmi->pres_chrg_step = STEP_NORM;
|
|
} else if (mmi_has_current_tapered(chip, batt_ma,
|
|
mmi->chrg_iterm)) {
|
|
if (eb_chrg_allowed)
|
|
mmi->pres_chrg_step = STEP_EB;
|
|
else
|
|
mmi->pres_chrg_step = STEP_FULL;
|
|
}
|
|
} else if (mmi->pres_chrg_step == STEP_EB) {
|
|
if ((batt_soc < 95) || !eb_chrg_allowed) {
|
|
mmi->chrg_taper_cnt = 0;
|
|
mmi->pres_chrg_step = STEP_NORM;
|
|
}
|
|
} else if (mmi->pres_chrg_step == STEP_FULL) {
|
|
if (batt_soc <= 99) {
|
|
mmi->chrg_taper_cnt = 0;
|
|
mmi->pres_chrg_step = STEP_NORM;
|
|
} else if (eb_chrg_allowed) {
|
|
mmi->pres_chrg_step = STEP_EB;
|
|
}
|
|
} else if (mmi->pres_chrg_step == STEP_FLOAT) {
|
|
if ((zone->fcc_norm_ma) ||
|
|
((batt_mv + HYST_STEP_MV) < zone->norm_mv))
|
|
mmi->pres_chrg_step = STEP_MAX;
|
|
|
|
}
|
|
|
|
/* Take State actions */
|
|
switch (mmi->pres_chrg_step) {
|
|
case STEP_EB:
|
|
target_fv = max_fv_mv;
|
|
|
|
if (cl_usb > 1500) {
|
|
mmi->cl_ebchg = cl_usb - EB_SPLIT_MA;
|
|
target_usb = cl_usb;
|
|
if (batt_soc < 100)
|
|
target_fcc = min(zone->fcc_norm_ma, EB_SPLIT_MA);
|
|
else
|
|
target_fcc = -EINVAL;
|
|
} else {
|
|
mmi->cl_ebchg = cl_usb;
|
|
target_usb = cl_usb;
|
|
target_fcc = -EINVAL;
|
|
}
|
|
|
|
break;
|
|
case STEP_FLOAT:
|
|
case STEP_MAX:
|
|
if (!zone->norm_mv)
|
|
target_fv = max_fv_mv;
|
|
else
|
|
target_fv = zone->norm_mv;
|
|
target_fcc = zone->fcc_max_ma;
|
|
mmi->cl_ebchg = 0;
|
|
|
|
if (((cl_usb - EB_SPLIT_MA) >= target_fcc) && eb_chrg_allowed)
|
|
mmi->cl_ebchg = EB_SPLIT_MA;
|
|
else if (eb_chrg_allowed && (eb_able & EB_RCV_PARALLEL) &&
|
|
(mmi->charger_rate == POWER_SUPPLY_CHARGE_RATE_TURBO))
|
|
mmi->cl_ebchg = cl_usb / 2; /* 50/50 Split */
|
|
else if ((eb_able & EB_RCV_PARALLEL) &&
|
|
(mmi->charger_rate ==
|
|
POWER_SUPPLY_CHARGE_RATE_TURBO) &&
|
|
!mmi->usbeb_present)
|
|
mmi->cl_ebchg = EB_SPLIT_MA;
|
|
|
|
target_usb = cl_usb;
|
|
break;
|
|
case STEP_FULL:
|
|
target_fv = max_fv_mv;
|
|
target_fcc = -EINVAL;
|
|
mmi->cl_ebchg = 0;
|
|
|
|
if ((eb_able & EB_RCV_PARALLEL) &&
|
|
(mmi->charger_rate ==
|
|
POWER_SUPPLY_CHARGE_RATE_TURBO) &&
|
|
!mmi->usbeb_present)
|
|
mmi->cl_ebchg = EB_SPLIT_MA;
|
|
|
|
target_usb = cl_usb;
|
|
break;
|
|
case STEP_NORM:
|
|
target_fv = max_fv_mv + mmi->vfloat_comp_mv;
|
|
target_fcc = zone->fcc_norm_ma;
|
|
mmi->cl_ebchg = 0;
|
|
|
|
if (((cl_usb - EB_SPLIT_MA) >= target_fcc) && eb_chrg_allowed)
|
|
mmi->cl_ebchg = EB_SPLIT_MA;
|
|
else if (eb_chrg_allowed && (eb_able & EB_RCV_PARALLEL) &&
|
|
(mmi->charger_rate == POWER_SUPPLY_CHARGE_RATE_TURBO))
|
|
mmi->cl_ebchg = cl_usb / 2; /* 50/50 Split */
|
|
else if ((eb_able & EB_RCV_PARALLEL) &&
|
|
(mmi->charger_rate ==
|
|
POWER_SUPPLY_CHARGE_RATE_TURBO) &&
|
|
!mmi->usbeb_present)
|
|
mmi->cl_ebchg = EB_SPLIT_MA;
|
|
|
|
target_usb = cl_usb;
|
|
break;
|
|
case STEP_NONE:
|
|
target_fv = max_fv_mv;
|
|
target_fcc = zone->fcc_norm_ma;
|
|
target_usb = cl_usb;
|
|
break;
|
|
case STEP_STOP:
|
|
target_fv = max_fv_mv;
|
|
target_fcc = -EINVAL;
|
|
target_usb = cl_usb;
|
|
break;
|
|
case STEP_DEMO:
|
|
target_fv = DEMO_MODE_VOLTAGE;
|
|
target_fcc = zone->fcc_max_ma;
|
|
target_usb = cl_usb;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
/* Votes for State */
|
|
vote(chip->fv_votable, HEARTBEAT_VOTER, true, target_fv * 1000);
|
|
|
|
vote(chip->chg_disable_votable, HEARTBEAT_VOTER,
|
|
(target_fcc < 0), 0);
|
|
|
|
vote(chip->fcc_votable, HEARTBEAT_VOTER,
|
|
true, (target_fcc >= 0) ? (target_fcc * 1000) : 0);
|
|
|
|
rc = vote(chip->usb_icl_votable, HEARTBEAT_VOTER,
|
|
true, (target_usb >= 0) ? (target_usb * 1000) : 0);
|
|
if (rc < 0) {
|
|
smblib_err(chip, "Problem setting USB ICL %d\n", target_usb);
|
|
goto end_hb;
|
|
}
|
|
|
|
if (vbus_present) {
|
|
if (mmi->cl_ebchg > 0) {
|
|
mmi_set_extbat_in_cl(chip);
|
|
mmi_set_extbat_state(chip, EB_SINK, false);
|
|
} else if (mmi->ebchg_state != EB_DISCONN)
|
|
mmi_set_extbat_state(chip, EB_OFF, false);
|
|
}
|
|
|
|
if ((target_dc > 1600) &&
|
|
(chip->mmi.vl_ebsrc >= MICRO_9V) &&
|
|
(dcin_mv > ((MICRO_5V / 1000) + 500)))
|
|
target_dc = 1600;
|
|
|
|
vote(chip->dc_icl_votable, HEARTBEAT_VOTER,
|
|
true, (target_dc >= 0) ? (target_dc * 1000) : 0);
|
|
|
|
if (charger_present && mmi->hvdcp3_con && !chip->pd_active &&
|
|
(vbus_inc_mv > VBUS_INPUT_VOLTAGE_NOM)) {
|
|
if ((usb_mv < vbus_inc_mv) &&
|
|
(usb_mv >= VBUS_INPUT_VOLTAGE_MIN) &&
|
|
(mmi->vbus_inc_cnt < VBUS_INPUT_MAX_COUNT)) {
|
|
smblib_dbg(chip, PR_MOTO,
|
|
"HVDCP Input %d mV Low, Increase\n",
|
|
usb_mv);
|
|
smblib_dp_pulse(chip);
|
|
vbus_inc_now = true;
|
|
mmi->vbus_inc_cnt++;
|
|
} else if ((usb_mv > VBUS_INPUT_VOLTAGE_MAX)
|
|
&& (mmi->vbus_inc_cnt > 0)) {
|
|
smblib_dbg(chip, PR_MOTO,
|
|
"HVDCP Input %d mV High, Decrease\n",
|
|
usb_mv);
|
|
vbus_inc_mv -= 50;
|
|
smblib_dm_pulse(chip);
|
|
vbus_inc_now = true;
|
|
mmi->vbus_inc_cnt--;
|
|
}
|
|
}
|
|
|
|
if (chip->mmi.pres_temp_zone == ZONE_HOT) {
|
|
chip->mmi.batt_health = POWER_SUPPLY_HEALTH_OVERHEAT;
|
|
} else if (chip->mmi.pres_temp_zone == ZONE_COLD) {
|
|
chip->mmi.batt_health = POWER_SUPPLY_HEALTH_COLD;
|
|
} else if (batt_temp >= WARM_TEMP) {
|
|
if (chip->mmi.pres_chrg_step == STEP_STOP)
|
|
chip->mmi.batt_health = POWER_SUPPLY_HEALTH_OVERHEAT;
|
|
else
|
|
chip->mmi.batt_health = POWER_SUPPLY_HEALTH_GOOD;
|
|
} else if (batt_temp <= COOL_TEMP) {
|
|
if (chip->mmi.pres_chrg_step == STEP_STOP)
|
|
chip->mmi.batt_health = POWER_SUPPLY_HEALTH_COLD;
|
|
else
|
|
chip->mmi.batt_health = POWER_SUPPLY_HEALTH_GOOD;
|
|
} else
|
|
chip->mmi.batt_health = POWER_SUPPLY_HEALTH_GOOD;
|
|
|
|
smblib_dbg(chip, PR_MOTO,
|
|
"EB Input %d mA, PMI Input %d mA, USBC CL %d mA, DC %d mA\n",
|
|
mmi->cl_ebchg, target_usb, cl_cc,
|
|
target_dc);
|
|
smblib_dbg(chip, PR_MOTO, "Step State = %s, EB State %s\n",
|
|
stepchg_str[(int)mmi->pres_chrg_step],
|
|
ebchg_str[(int)mmi->ebchg_state]);
|
|
smblib_dbg(chip, PR_MOTO,
|
|
"EFFECTIVE: FV = %d, CDIS = %d, FCC = %d,"
|
|
"USBICL = %d, DCICL = %d\n",
|
|
get_effective_result(chip->fv_votable),
|
|
get_effective_result(chip->chg_disable_votable),
|
|
get_effective_result(chip->fcc_votable),
|
|
get_effective_result(chip->usb_icl_votable),
|
|
get_effective_result(chip->dc_icl_votable));
|
|
end_hb:
|
|
if (chip->batt_psy)
|
|
power_supply_changed(chip->batt_psy);
|
|
|
|
hb_resch_time = HEARTBEAT_HOLDOFF_MS;
|
|
|
|
if (!chip->mmi.chrg_taper_cnt && (rc >= 0))
|
|
hb_resch_time = HEARTBEAT_DELAY_MS;
|
|
|
|
if (ebparams_cnt != mmi->update_eb_params)
|
|
hb_resch_time = HEARTBEAT_EB_MS;
|
|
else
|
|
mmi->update_eb_params = 0;
|
|
|
|
if (eb_sink_to_off ||
|
|
(!charger_present && mmi->charger_debounce_cnt) ||
|
|
vbus_inc_now)
|
|
hb_resch_time = HEARTBEAT_EB_WAIT_MS;
|
|
|
|
schedule_delayed_work(&mmi->heartbeat_work,
|
|
msecs_to_jiffies(hb_resch_time));
|
|
|
|
if (mmi->ebchg_state == EB_SRC) {
|
|
alarm_start_relative(&mmi->heartbeat_alarm,
|
|
ns_to_ktime(SMBCHG_HEARTBEAT_INTERVAL_NS));
|
|
}
|
|
|
|
__pm_relax(mmi->smblib_mmi_hb_wake_source);
|
|
if (!vbus_present)
|
|
vote(chip->awake_votable, HEARTBEAT_VOTER, false, 0);
|
|
}
|
|
static int smbchg_reboot(struct notifier_block *nb,
|
|
unsigned long event, void *unused)
|
|
{
|
|
struct smb_charger *chg = container_of(nb, struct smb_charger,
|
|
mmi.smb_reboot);
|
|
union power_supply_propval val;
|
|
int rc;
|
|
char eb_able;
|
|
int usb_present = -EINVAL, batt_soc = -EINVAL;
|
|
int soc_max = 99;
|
|
|
|
smblib_dbg(chg, PR_MISC, "SMB Reboot\n");
|
|
if (!chg) {
|
|
smblib_dbg(chg, PR_MOTO, "called before chip valid!\n");
|
|
return NOTIFY_DONE;
|
|
}
|
|
|
|
mmi_check_extbat_ability(chg, &eb_able);
|
|
if (eb_able & EB_SND_NEVER)
|
|
soc_max = 0;
|
|
else if (eb_able & EB_SND_LOW)
|
|
soc_max = eb_low_start_soc;
|
|
|
|
atomic_set(&chg->mmi.hb_ready, 0);
|
|
cancel_delayed_work_sync(&chg->mmi.heartbeat_work);
|
|
|
|
rc = smblib_get_prop_usb_present(chg, &val);
|
|
if (rc >= 0)
|
|
usb_present = val.intval;
|
|
|
|
val.intval = 0;
|
|
rc = smblib_get_prop_batt_capacity(chg, &val);
|
|
if (rc >= 0)
|
|
batt_soc = val.intval;
|
|
|
|
if (chg->mmi.factory_mode) {
|
|
switch (event) {
|
|
case SYS_POWER_OFF:
|
|
/* Disable Factory Kill */
|
|
factory_kill_disable = true;
|
|
/* Disable Charging */
|
|
smblib_masked_write(chg, CHARGING_ENABLE_CMD_REG,
|
|
CHARGING_ENABLE_CMD_BIT,
|
|
0);
|
|
|
|
/* Suspend USB and DC */
|
|
smblib_set_usb_suspend(chg, true);
|
|
smblib_set_dc_suspend(chg, true);
|
|
|
|
rc = smblib_get_prop_usb_present(chg, &val);
|
|
while (rc >= 0 && val.intval) {
|
|
msleep(100);
|
|
rc = smblib_get_prop_usb_present(chg, &val);
|
|
smblib_dbg(chg, PR_MOTO,
|
|
"Wait for VBUS to decay\n");
|
|
}
|
|
smblib_dbg(chg, PR_MOTO, "VBUS UV wait 1 sec!\n");
|
|
/* Delay 1 sec to allow more VBUS decay */
|
|
msleep(1000);
|
|
/* configure power role for UFP */
|
|
smblib_masked_write(chg,
|
|
TYPE_C_MODE_CFG_REG,
|
|
TYPEC_POWER_ROLE_CMD_MASK,
|
|
EN_SNK_ONLY_BIT);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
} else if ((batt_soc >= soc_max) ||
|
|
((get_eb_prop(chg, POWER_SUPPLY_PROP_CAPACITY) <= 0) &&
|
|
(is_eb_present(chg) == true))) {
|
|
/* Turn off any Ext batt charging */
|
|
smblib_err(chg, "Attempt to Shutdown EB!\n");
|
|
mmi_set_extbat_state(chg, EB_OFF, false);
|
|
if (gpio_is_valid(chg->mmi.ebchg_gpio.gpio)) {
|
|
gpio_set_value(chg->mmi.ebchg_gpio.gpio, 0);
|
|
gpio_free(chg->mmi.ebchg_gpio.gpio);
|
|
}
|
|
} else if ((chg->mmi.ebchg_state == EB_OFF) && (usb_present == 0)) {
|
|
smblib_err(chg, "Attempt to Turn EB ON!\n");
|
|
mmi_set_extbat_state(chg, EB_SRC, false);
|
|
}
|
|
|
|
return NOTIFY_DONE;
|
|
}
|
|
|
|
#define CHG_SHOW_MAX_SIZE 50
|
|
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;
|
|
union power_supply_propval val;
|
|
int rc;
|
|
|
|
r = kstrtoul(buf, 0, &mode);
|
|
if (r) {
|
|
pr_err("Invalid factory image mode value = %lu\n", mode);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!mmi_chip) {
|
|
pr_err("chip not valid\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
mmi_chip->mmi.is_factory_image = (mode) ? true : false;
|
|
rc = smblib_get_prop_usb_present(mmi_chip,&val);
|
|
if(rc<0)
|
|
pr_err("Error getting USB present rc = %d\n", rc);
|
|
|
|
if((mmi_chip->mmi.is_factory_image == true)&&(val.intval)){
|
|
cancel_delayed_work(&mmi_chip->mmi.heartbeat_work);
|
|
schedule_delayed_work(&mmi_chip->mmi.heartbeat_work,msecs_to_jiffies(0));
|
|
}
|
|
|
|
return r ? r : count;
|
|
}
|
|
|
|
static ssize_t factory_image_mode_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
int state;
|
|
|
|
if (!mmi_chip) {
|
|
pr_err("chip not valid\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
state = (mmi_chip->mmi.is_factory_image) ? 1 : 0;
|
|
|
|
return scnprintf(buf, CHG_SHOW_MAX_SIZE, "%d\n", state);
|
|
}
|
|
|
|
ssize_t mmi_get_factory_image_mode(void)
|
|
{
|
|
if (!mmi_chip) {
|
|
pr_err("chip not valid\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
return mmi_chip->mmi.is_factory_image;
|
|
}
|
|
|
|
ssize_t mmi_set_factory_image_mode(int mode)
|
|
{
|
|
if (!mmi_chip) {
|
|
pr_err("chip not valid\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
mmi_chip->mmi.is_factory_image = (mode) ? true : false;
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(mmi_get_factory_image_mode);
|
|
EXPORT_SYMBOL(mmi_set_factory_image_mode);
|
|
|
|
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;
|
|
|
|
if (!mmi_chip) {
|
|
pr_err("chip not valid\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
state = mmi_chip->mmi.upper_limit_capacity;
|
|
|
|
return scnprintf(buf, CHG_SHOW_MAX_SIZE, "%d\n", state);
|
|
}
|
|
|
|
ssize_t mmi_get_factory_charge_upper(void)
|
|
{
|
|
if (!mmi_chip) {
|
|
pr_err("chip not valid\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
return mmi_chip->mmi.upper_limit_capacity;
|
|
}
|
|
EXPORT_SYMBOL(mmi_get_factory_charge_upper);
|
|
|
|
static DEVICE_ATTR(factory_charge_upper, 0444,
|
|
factory_charge_upper_show,
|
|
NULL);
|
|
|
|
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;
|
|
|
|
r = kstrtoul(buf, 0, &mode);
|
|
if (r) {
|
|
smblib_err(mmi_chip, "Invalid demo mode value = %lu\n", mode);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!mmi_chip) {
|
|
smblib_err(mmi_chip, "chip not valid\n");
|
|
return -ENODEV;
|
|
}
|
|
mmi_chip->mmi.chrg_taper_cnt = 0;
|
|
|
|
if ((mode >= 35) && (mode <= 80))
|
|
mmi_chip->mmi.demo_mode = mode;
|
|
else
|
|
mmi_chip->mmi.demo_mode = 35;
|
|
|
|
return r ? r : count;
|
|
}
|
|
|
|
static ssize_t force_demo_mode_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
int state;
|
|
|
|
if (!mmi_chip) {
|
|
smblib_err(mmi_chip, "chip not valid\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
state = mmi_chip->mmi.demo_mode;
|
|
|
|
return scnprintf(buf, CHG_SHOW_MAX_SIZE, "%d\n", state);
|
|
}
|
|
|
|
ssize_t mmi_get_demo_mode(void)
|
|
{
|
|
if (!mmi_chip) {
|
|
pr_err("chip not valid\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
return mmi_chip->mmi.demo_mode;
|
|
}
|
|
|
|
ssize_t mmi_set_demo_mode(int mode)
|
|
{
|
|
if (!mmi_chip) {
|
|
pr_err("chip not valid\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
mmi_chip->mmi.chrg_taper_cnt = 0;
|
|
if ((mode >= 35) && (mode <= 80))
|
|
mmi_chip->mmi.demo_mode = mode;
|
|
else
|
|
mmi_chip->mmi.demo_mode = 35;
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(mmi_get_demo_mode);
|
|
EXPORT_SYMBOL(mmi_set_demo_mode);
|
|
|
|
static DEVICE_ATTR(force_demo_mode, 0644,
|
|
force_demo_mode_show,
|
|
force_demo_mode_store);
|
|
|
|
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;
|
|
|
|
r = kstrtoul(buf, 0, &mode);
|
|
if (r) {
|
|
smblib_err(mmi_chip, "Invalid max temp value = %lu\n", mode);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!mmi_chip) {
|
|
smblib_err(mmi_chip, "chip not valid\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
if ((mode >= MIN_MAX_TEMP_C) && (mode <= MAX_TEMP_C))
|
|
mmi_chip->mmi.max_chrg_temp = mode;
|
|
else
|
|
mmi_chip->mmi.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;
|
|
|
|
if (!mmi_chip) {
|
|
smblib_err(mmi_chip, "chip not valid\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
state = mmi_chip->mmi.max_chrg_temp;
|
|
|
|
return scnprintf(buf, CHG_SHOW_MAX_SIZE, "%d\n", state);
|
|
}
|
|
|
|
ssize_t mmi_get_max_chrg_temp(void)
|
|
{
|
|
if (!mmi_chip) {
|
|
pr_err("chip not valid\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
return mmi_chip->mmi.max_chrg_temp;
|
|
}
|
|
|
|
ssize_t mmi_set_max_chrg_temp(int value)
|
|
{
|
|
if (!mmi_chip) {
|
|
pr_err("chip not valid\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
if ((value >= MIN_MAX_TEMP_C) && (value <= MAX_TEMP_C))
|
|
mmi_chip->mmi.max_chrg_temp = value;
|
|
else
|
|
mmi_chip->mmi.max_chrg_temp = MAX_TEMP_C;
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(mmi_set_max_chrg_temp);
|
|
EXPORT_SYMBOL(mmi_get_max_chrg_temp);
|
|
|
|
static DEVICE_ATTR(force_max_chrg_temp, 0644,
|
|
force_max_chrg_temp_show,
|
|
force_max_chrg_temp_store);
|
|
|
|
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;
|
|
|
|
r = kstrtoul(buf, 0, &mode);
|
|
if (r) {
|
|
smblib_err(mmi_chip,
|
|
"Invalid usb suspend mode value = %lu\n", mode);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!mmi_chip) {
|
|
smblib_err(mmi_chip, "chip not valid\n");
|
|
return -ENODEV;
|
|
}
|
|
mmi_chip->mmi.force_chg_suspend = (bool)mode;
|
|
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;
|
|
|
|
if (!mmi_chip) {
|
|
smblib_err(mmi_chip, "chip not valid\n");
|
|
return -ENODEV;
|
|
}
|
|
ret = smblib_get_usb_suspend(mmi_chip, &state);
|
|
if (ret) {
|
|
smblib_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) {
|
|
smblib_err(mmi_chip,
|
|
"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);
|
|
|
|
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;
|
|
|
|
r = kstrtoul(buf, 0, &mode);
|
|
if (r) {
|
|
smblib_err(mmi_chip,
|
|
"Invalid chrg enable value = %lu\n", mode);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!mmi_chip) {
|
|
smblib_err(mmi_chip, "chip not valid\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
r = smblib_masked_write(mmi_chip, CHARGING_ENABLE_CMD_REG,
|
|
CHARGING_ENABLE_CMD_BIT,
|
|
mode ? CHARGING_ENABLE_CMD_BIT : 0);
|
|
if (r < 0) {
|
|
smblib_err(mmi_chip, "Factory Couldn't %s charging rc=%d\n",
|
|
mode ? "disable" : "enable", (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;
|
|
|
|
if (!mmi_chip) {
|
|
smblib_err(mmi_chip, "chip not valid\n");
|
|
state = -ENODEV;
|
|
goto end;
|
|
}
|
|
|
|
ret = smblib_read(mmi_chip, CHARGING_ENABLE_CMD_REG, &value);
|
|
if (ret) {
|
|
smblib_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);
|
|
|
|
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;
|
|
|
|
r = kstrtoul(buf, 0, &chg_current);
|
|
if (r) {
|
|
smblib_err(mmi_chip,
|
|
"Invalid ibatt value = %lu\n", chg_current);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!mmi_chip) {
|
|
smblib_err(mmi_chip, "chip not valid\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
chg_current *= 1000; /* Convert to uA */
|
|
r = smblib_set_charge_param(mmi_chip, &mmi_chip->param.fcc, chg_current);
|
|
if (r < 0) {
|
|
smblib_err(mmi_chip,
|
|
"Factory Couldn't set masterfcc = %d rc=%d\n",
|
|
(int)chg_current, (int)r);
|
|
return r;
|
|
}
|
|
|
|
return r ? r : count;
|
|
}
|
|
|
|
static ssize_t force_chg_ibatt_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
int state;
|
|
int ret;
|
|
|
|
if (!mmi_chip) {
|
|
smblib_err(mmi_chip, "chip not valid\n");
|
|
state = -ENODEV;
|
|
goto end;
|
|
}
|
|
|
|
ret = smblib_get_charge_param(mmi_chip, &mmi_chip->param.fcc, &state);
|
|
if (ret < 0) {
|
|
smblib_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;
|
|
|
|
r = kstrtoul(buf, 0, &usb_curr);
|
|
if (r) {
|
|
smblib_err(mmi_chip, "Invalid iusb value = %lu\n", usb_curr);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!mmi_chip) {
|
|
smblib_err(mmi_chip, "chip not valid\n");
|
|
return -ENODEV;
|
|
}
|
|
usb_curr *= 1000; /* Convert to uA */
|
|
r = smblib_set_charge_param(mmi_chip, &mmi_chip->param.usb_icl, usb_curr);
|
|
if (r < 0) {
|
|
smblib_err(mmi_chip,
|
|
"Factory Couldn't set usb icl = %d rc=%d\n",
|
|
(int)usb_curr, (int)r);
|
|
return 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;
|
|
|
|
if (!mmi_chip) {
|
|
smblib_err(mmi_chip, "chip not valid\n");
|
|
r = -ENODEV;
|
|
goto end;
|
|
}
|
|
|
|
r = smblib_get_charge_param(mmi_chip, &mmi_chip->param.usb_icl, &state);
|
|
if (r < 0) {
|
|
smblib_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);
|
|
|
|
#define PRE_CHARGE_CONV_MV 50
|
|
#define PRE_CHARGE_MAX 0x7
|
|
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;
|
|
|
|
r = kstrtoul(buf, 0, &chg_current);
|
|
if (r) {
|
|
smblib_err(mmi_chip,
|
|
"Invalid pre-charge value = %lu\n", chg_current);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!mmi_chip) {
|
|
smblib_err(mmi_chip, "chip not valid\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
chg_current /= PRE_CHARGE_CONV_MV;
|
|
|
|
if (chg_current > PRE_CHARGE_MAX)
|
|
value = PRE_CHARGE_MAX;
|
|
else
|
|
value = (u8)chg_current;
|
|
|
|
r = smblib_masked_write(mmi_chip, CHGR_PRE_CHARGE_CURRENT_CFG_REG,
|
|
CHGR_PRE_CHARGE_CURRENT_SETTING_MASK,
|
|
value);
|
|
if (r < 0) {
|
|
smblib_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;
|
|
|
|
if (!mmi_chip) {
|
|
smblib_err(mmi_chip, "chip not valid\n");
|
|
state = -ENODEV;
|
|
goto end;
|
|
}
|
|
|
|
ret = smblib_read(mmi_chip, CHGR_PRE_CHARGE_CURRENT_CFG_REG, &value);
|
|
if (ret) {
|
|
smblib_err(mmi_chip, "Pre Chg ITrick failed ret = %d\n", ret);
|
|
state = -EFAULT;
|
|
goto end;
|
|
}
|
|
|
|
value &= CHGR_PRE_CHARGE_CURRENT_SETTING_MASK;
|
|
|
|
state = value * PRE_CHARGE_CONV_MV;
|
|
|
|
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);
|
|
|
|
int smblib_get_prop_dc_system_temp_level(struct smb_charger *chg,
|
|
union power_supply_propval *val)
|
|
{
|
|
val->intval = chg->mmi.dc_system_temp_level;
|
|
return 0;
|
|
}
|
|
|
|
int smblib_set_prop_dc_system_temp_level(struct smb_charger *chg,
|
|
const union power_supply_propval *val)
|
|
{
|
|
if (val->intval < 0)
|
|
return -EINVAL;
|
|
|
|
if (chg->mmi.dc_thermal_levels <= 0)
|
|
return -EINVAL;
|
|
|
|
if (val->intval >= chg->mmi.dc_thermal_levels)
|
|
chg->mmi.dc_system_temp_level = chg->mmi.dc_thermal_levels - 1;
|
|
else
|
|
chg->mmi.dc_system_temp_level = val->intval;
|
|
|
|
if (chg->mmi.dc_system_temp_level == 0)
|
|
return vote(chg->dc_icl_votable, THERMAL_DAEMON_VOTER, false, 0);
|
|
|
|
vote(chg->dc_icl_votable, THERMAL_DAEMON_VOTER, true,
|
|
chg->mmi.dc_thermal_mitigation[chg->mmi.dc_system_temp_level] * 1000);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int smblib_get_prop_usb_system_temp_level(struct smb_charger *chg,
|
|
union power_supply_propval *val)
|
|
{
|
|
val->intval = chg->mmi.usb_system_temp_level;
|
|
return 0;
|
|
}
|
|
|
|
int smblib_set_prop_usb_system_temp_level(struct smb_charger *chg,
|
|
const union power_supply_propval *val)
|
|
{
|
|
if (val->intval < 0)
|
|
return -EINVAL;
|
|
|
|
if (chg->mmi.usb_thermal_levels <= 0)
|
|
return -EINVAL;
|
|
|
|
if (chg->mmi.usb_system_temp_level >= chg->mmi.usb_thermal_levels)
|
|
chg->mmi.usb_system_temp_level = chg->mmi.usb_thermal_levels - 1;
|
|
else
|
|
chg->mmi.usb_system_temp_level = val->intval;
|
|
|
|
if (chg->mmi.usb_system_temp_level == 0)
|
|
return vote(chg->usb_icl_votable, THERMAL_DAEMON_VOTER, false, 0);
|
|
|
|
vote(chg->usb_icl_votable, THERMAL_DAEMON_VOTER, true,
|
|
chg->mmi.usb_thermal_mitigation[chg->mmi.usb_system_temp_level] * 1000);
|
|
|
|
return 0;
|
|
}
|
|
|
|
enum dual_role_property smblib_typec_dr_properties[] = {
|
|
DUAL_ROLE_PROP_SUPPORTED_MODES,
|
|
DUAL_ROLE_PROP_MODE,
|
|
DUAL_ROLE_PROP_PR,
|
|
DUAL_ROLE_PROP_DR,
|
|
};
|
|
|
|
static int smblib_typec_dr_is_writeable(
|
|
struct dual_role_phy_instance *dual_role,
|
|
enum dual_role_property prop)
|
|
{
|
|
int rc = 0;
|
|
struct smb_charger *chip = dual_role_get_drvdata(dual_role);
|
|
|
|
if (!chip || (chip->typec_mode == POWER_SUPPLY_TYPEC_NONE))
|
|
return 0;
|
|
|
|
if (chip->connector_type == POWER_SUPPLY_CONNECTOR_MICRO_USB)
|
|
return 0;
|
|
|
|
/* FIXME:
|
|
* How to decide the connected devices support the role swap ?
|
|
*/
|
|
switch (prop) {
|
|
case DUAL_ROLE_PROP_MODE:
|
|
case DUAL_ROLE_PROP_PR:
|
|
rc = 0;
|
|
break;
|
|
default:
|
|
rc = 0;
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
static int smblib_typec_dr_set_property(
|
|
struct dual_role_phy_instance *dual_role,
|
|
enum dual_role_property prop,
|
|
const unsigned int *val)
|
|
{
|
|
int rc = 0;
|
|
union power_supply_propval propval = {0,};
|
|
struct smb_charger *chip = dual_role_get_drvdata(dual_role);
|
|
|
|
if (!chip || (chip->typec_mode == POWER_SUPPLY_TYPEC_NONE))
|
|
return -EINVAL;
|
|
|
|
/* FIXME:
|
|
* Need dual role state sync during swap
|
|
*/
|
|
switch (prop) {
|
|
case DUAL_ROLE_PROP_PR:
|
|
switch (*val) {
|
|
case DUAL_ROLE_PROP_PR_SRC:
|
|
propval.intval = POWER_SUPPLY_TYPEC_PR_SOURCE;
|
|
break;
|
|
case DUAL_ROLE_PROP_PR_SNK:
|
|
propval.intval = POWER_SUPPLY_TYPEC_PR_SINK;
|
|
break;
|
|
default:
|
|
propval.intval = POWER_SUPPLY_TYPEC_PR_DUAL;
|
|
}
|
|
rc = smblib_set_prop_typec_power_role(chip, &propval);
|
|
break;
|
|
default:
|
|
pr_debug("Invalid DUAL ROLE request %d\n", prop);
|
|
rc = -EINVAL;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int smblib_typec_dr_get_property(
|
|
struct dual_role_phy_instance *dual_role,
|
|
enum dual_role_property prop,
|
|
unsigned int *val)
|
|
{
|
|
int rc = 0;
|
|
struct smb_charger *chip = dual_role_get_drvdata(dual_role);
|
|
unsigned int mode, power_role, data_role;
|
|
|
|
if (!chip)
|
|
return -EINVAL;
|
|
|
|
if (chip->connector_type == POWER_SUPPLY_CONNECTOR_MICRO_USB) {
|
|
union power_supply_propval val;
|
|
|
|
rc = smblib_get_prop_usb_present(chip, &val);
|
|
if (rc < 0) {
|
|
smblib_err(chip, "Error getting USB Present rc = %d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
if (chip->otg_present) {
|
|
mode = DUAL_ROLE_PROP_MODE_DFP;
|
|
power_role = DUAL_ROLE_PROP_PR_SRC;
|
|
data_role = DUAL_ROLE_PROP_DR_HOST;
|
|
} else if (val.intval > 0) {
|
|
mode = DUAL_ROLE_PROP_MODE_UFP;
|
|
power_role = DUAL_ROLE_PROP_PR_SNK;
|
|
data_role = DUAL_ROLE_PROP_DR_DEVICE;
|
|
} else {
|
|
mode = DUAL_ROLE_PROP_MODE_NONE;
|
|
power_role = DUAL_ROLE_PROP_PR_NONE;
|
|
data_role = DUAL_ROLE_PROP_DR_NONE;
|
|
}
|
|
} else {
|
|
switch (chip->typec_mode) {
|
|
case POWER_SUPPLY_TYPEC_SINK:
|
|
case POWER_SUPPLY_TYPEC_SINK_POWERED_CABLE:
|
|
case POWER_SUPPLY_TYPEC_SINK_DEBUG_ACCESSORY:
|
|
case POWER_SUPPLY_TYPEC_SINK_AUDIO_ADAPTER:
|
|
case POWER_SUPPLY_TYPEC_POWERED_CABLE_ONLY:
|
|
mode = DUAL_ROLE_PROP_MODE_DFP;
|
|
power_role = DUAL_ROLE_PROP_PR_SRC;
|
|
data_role = DUAL_ROLE_PROP_DR_HOST;
|
|
break;
|
|
case POWER_SUPPLY_TYPEC_SOURCE_DEFAULT:
|
|
case POWER_SUPPLY_TYPEC_SOURCE_MEDIUM:
|
|
case POWER_SUPPLY_TYPEC_SOURCE_HIGH:
|
|
case POWER_SUPPLY_TYPEC_NON_COMPLIANT:
|
|
mode = DUAL_ROLE_PROP_MODE_UFP;
|
|
power_role = DUAL_ROLE_PROP_PR_SNK;
|
|
data_role = DUAL_ROLE_PROP_DR_DEVICE;
|
|
break;
|
|
default:
|
|
mode = DUAL_ROLE_PROP_MODE_NONE;
|
|
power_role = DUAL_ROLE_PROP_PR_NONE;
|
|
data_role = DUAL_ROLE_PROP_DR_NONE;
|
|
}
|
|
}
|
|
|
|
switch (prop) {
|
|
case DUAL_ROLE_PROP_SUPPORTED_MODES:
|
|
*val = chip->dr_desc.supported_modes;
|
|
break;
|
|
case DUAL_ROLE_PROP_MODE:
|
|
*val = mode;
|
|
break;
|
|
case DUAL_ROLE_PROP_PR:
|
|
*val = power_role;
|
|
break;
|
|
case DUAL_ROLE_PROP_DR:
|
|
*val = data_role;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int smblib_typec_dual_role_init(struct smb_charger *chip)
|
|
{
|
|
int rc = 0;
|
|
|
|
/* Register for android TypeC dual role framework */
|
|
chip->dr_desc.name = "otg_default";
|
|
chip->dr_desc.properties = smblib_typec_dr_properties;
|
|
chip->dr_desc.get_property = smblib_typec_dr_get_property;
|
|
chip->dr_desc.set_property = smblib_typec_dr_set_property;
|
|
chip->dr_desc.property_is_writeable =
|
|
smblib_typec_dr_is_writeable;
|
|
chip->dr_desc.supported_modes =
|
|
DUAL_ROLE_SUPPORTED_MODES_DFP_AND_UFP;
|
|
chip->dr_desc.num_properties =
|
|
ARRAY_SIZE(smblib_typec_dr_properties);
|
|
|
|
chip->dr_inst = devm_dual_role_instance_register(chip->dev,
|
|
&chip->dr_desc);
|
|
if (IS_ERR(chip->dr_inst)) {
|
|
pr_err("Failed to initialize dual role\n");
|
|
rc = PTR_ERR(chip->dr_inst);
|
|
} else {
|
|
chip->dr_inst->drv_data = chip;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static bool mmi_factory_check(void)
|
|
{
|
|
struct device_node *np = of_find_node_by_path("/chosen");
|
|
bool factory = false;
|
|
|
|
if (np)
|
|
factory = of_property_read_bool(np, "mmi,factory-cable");
|
|
|
|
of_node_put(np);
|
|
|
|
return factory;
|
|
}
|
|
|
|
static void parse_mmi_dt_gpio(struct smb_charger *chg)
|
|
{
|
|
struct device_node *node = chg->dev->of_node;
|
|
enum of_gpio_flags flags;
|
|
int rc;
|
|
|
|
chg->mmi.ebchg_gpio.gpio = -EINVAL;
|
|
|
|
if (!node) {
|
|
smblib_err(chg, "gpio dtree info. missing\n");
|
|
return;
|
|
}
|
|
|
|
if (of_gpio_count(node) < 0) {
|
|
smblib_err(chg, "No GPIOS defined.\n");
|
|
return;
|
|
}
|
|
|
|
chg->mmi.ebchg_gpio.gpio = of_get_gpio_flags(node, 0, &flags);
|
|
chg->mmi.ebchg_gpio.flags = flags;
|
|
of_property_read_string_index(node, "gpio-names", 0,
|
|
&chg->mmi.ebchg_gpio.label);
|
|
|
|
if (!gpio_is_valid(chg->mmi.ebchg_gpio.gpio)) {
|
|
dev_err(chg->dev, "get gpio ebchg error rc=%d\n", chg->mmi.ebchg_gpio.gpio);
|
|
return;
|
|
}
|
|
|
|
rc = gpio_request_one(chg->mmi.ebchg_gpio.gpio,
|
|
chg->mmi.ebchg_gpio.flags,
|
|
chg->mmi.ebchg_gpio.label);
|
|
if (rc) {
|
|
smblib_err(chg, "failed to request eb GPIO\n");
|
|
return;
|
|
}
|
|
|
|
rc = gpio_export(chg->mmi.ebchg_gpio.gpio, 1);
|
|
if (rc) {
|
|
smblib_err(chg, "Failed to export eb GPIO %s: %d\n",
|
|
chg->mmi.ebchg_gpio.label,
|
|
chg->mmi.ebchg_gpio.gpio);
|
|
return;
|
|
}
|
|
|
|
rc = gpio_export_link(chg->dev, chg->mmi.ebchg_gpio.label,
|
|
chg->mmi.ebchg_gpio.gpio);
|
|
if (rc)
|
|
smblib_err(chg, "Failed to eb link GPIO %s: %d\n",
|
|
chg->mmi.ebchg_gpio.label,
|
|
chg->mmi.ebchg_gpio.gpio);
|
|
|
|
}
|
|
|
|
static int parse_mmi_dt(struct smb_charger *chg)
|
|
{
|
|
struct device_node *node = chg->dev->of_node;
|
|
int rc = 0;
|
|
int byte_len;
|
|
int i;
|
|
|
|
if (!node) {
|
|
smblib_err(chg, "mmi dtree info. missing\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
if (of_find_property(node, "qcom,mmi-temp-zones", &byte_len)) {
|
|
if ((byte_len / sizeof(u32)) % 4) {
|
|
smblib_err(chg,
|
|
"DT error wrong mmi temp zones\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
chg->mmi.temp_zones = (struct mmi_temp_zone *)
|
|
devm_kzalloc(chg->dev, byte_len, GFP_KERNEL);
|
|
|
|
if (chg->mmi.temp_zones == NULL)
|
|
return -ENOMEM;
|
|
|
|
chg->mmi.num_temp_zones =
|
|
byte_len / sizeof(struct mmi_temp_zone);
|
|
|
|
rc = of_property_read_u32_array(node,
|
|
"qcom,mmi-temp-zones",
|
|
(u32 *)chg->mmi.temp_zones,
|
|
byte_len / sizeof(u32));
|
|
if (rc < 0) {
|
|
smblib_err(chg,
|
|
"Couldn't read mmi temp zones rc = %d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
smblib_err(chg, "mmi temp zones: Num: %d\n",
|
|
chg->mmi.num_temp_zones);
|
|
for (i = 0; i < chg->mmi.num_temp_zones; i++) {
|
|
smblib_err(chg,
|
|
"mmi temp zones: Zone %d, Temp %d C, " \
|
|
"Step Volt %d mV, Full Rate %d mA, " \
|
|
"Taper Rate %d mA\n", i,
|
|
chg->mmi.temp_zones[i].temp_c,
|
|
chg->mmi.temp_zones[i].norm_mv,
|
|
chg->mmi.temp_zones[i].fcc_max_ma,
|
|
chg->mmi.temp_zones[i].fcc_norm_ma);
|
|
}
|
|
chg->mmi.pres_temp_zone = ZONE_NONE;
|
|
}
|
|
|
|
if (of_find_property(node, "qcom,usb-thermal-mitigation", &byte_len)) {
|
|
chg->mmi.usb_thermal_mitigation =
|
|
devm_kzalloc(chg->dev, byte_len, GFP_KERNEL);
|
|
|
|
if (chg->mmi.usb_thermal_mitigation == NULL)
|
|
return -ENOMEM;
|
|
|
|
chg->mmi.usb_thermal_levels = byte_len / sizeof(u32);
|
|
rc = of_property_read_u32_array(node,
|
|
"qcom,usb-thermal-mitigation",
|
|
chg->mmi.usb_thermal_mitigation,
|
|
chg->mmi.usb_thermal_levels);
|
|
if (rc < 0) {
|
|
smblib_err(chg,
|
|
"Couldn't read usb therm limits rc = %d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
} else {
|
|
chg->mmi.usb_thermal_mitigation = NULL;
|
|
chg->mmi.usb_thermal_levels = 0;
|
|
dev_err(chg->dev, "usb-thermal-mitigation is not set\n");
|
|
}
|
|
|
|
if (of_find_property(node, "qcom,dc-thermal-mitigation", &byte_len)) {
|
|
chg->mmi.dc_thermal_mitigation =
|
|
devm_kzalloc(chg->dev, byte_len, GFP_KERNEL);
|
|
|
|
if (chg->mmi.dc_thermal_mitigation == NULL)
|
|
return -ENOMEM;
|
|
|
|
chg->mmi.dc_thermal_levels = byte_len / sizeof(u32);
|
|
rc = of_property_read_u32_array(node,
|
|
"qcom,dc-thermal-mitigation",
|
|
chg->mmi.dc_thermal_mitigation,
|
|
chg->mmi.dc_thermal_levels);
|
|
if (rc < 0) {
|
|
smblib_err(chg,
|
|
"Couldn't read dc therm limits rc = %d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
} else {
|
|
chg->mmi.dc_thermal_mitigation = NULL;
|
|
chg->mmi.dc_thermal_levels = 0;
|
|
dev_err(chg->dev, "dc-thermal-mitigation is not set\n");
|
|
}
|
|
|
|
rc = of_property_read_u32(node, "qcom,iterm-ma",
|
|
&chg->mmi.chrg_iterm);
|
|
if (rc)
|
|
chg->mmi.chrg_iterm = 150;
|
|
|
|
rc = of_property_read_u32(node, "qcom,dc-eb-icl-ma",
|
|
&chg->mmi.dc_ebmax_current_ma);
|
|
if (rc)
|
|
chg->mmi.dc_ebmax_current_ma = 900;
|
|
|
|
chg->mmi.dc_ebmax_current_ma_default = chg->mmi.dc_ebmax_current_ma;
|
|
|
|
rc = of_property_read_u32(node, "qcom,dc-eb-icl-eff-ma",
|
|
&chg->mmi.dc_eff_current_ma);
|
|
if (rc)
|
|
chg->mmi.dc_eff_current_ma = 500;
|
|
|
|
/* read the external battery power supply name */
|
|
rc = of_property_read_string(node, "qcom,eb-batt-psy-name",
|
|
&chg->mmi.eb_batt_psy_name);
|
|
if (rc)
|
|
chg->mmi.eb_batt_psy_name = "gb_battery";
|
|
|
|
/* read the external power control power supply name */
|
|
rc = of_property_read_string(node, "qcom,eb-pwr-psy-name",
|
|
&chg->mmi.eb_pwr_psy_name);
|
|
if (rc)
|
|
chg->mmi.eb_pwr_psy_name = "gb_ptp";
|
|
|
|
/* read the external fuel gauge power supply name */
|
|
rc = of_property_read_string(node, "qcom,extrn-fg-psy-name",
|
|
&chg->mmi.extrn_fg_psy_name);
|
|
if (rc)
|
|
chg->mmi.extrn_fg_psy_name = NULL;
|
|
|
|
chg->mmi.vl_ebsrc = MICRO_9V;
|
|
chg->mmi.check_ebsrc_vl = false;
|
|
|
|
chg->mmi.enable_charging_limit =
|
|
of_property_read_bool(node, "qcom,enable-charging-limit");
|
|
|
|
rc = of_property_read_u32(node, "qcom,upper-limit-capacity",
|
|
&chg->mmi.upper_limit_capacity);
|
|
if (rc)
|
|
chg->mmi.upper_limit_capacity = 100;
|
|
|
|
rc = of_property_read_u32(node, "qcom,lower-limit-capacity",
|
|
&chg->mmi.lower_limit_capacity);
|
|
if (rc)
|
|
chg->mmi.lower_limit_capacity = 0;
|
|
|
|
rc = of_property_read_u32(node, "qcom,vfloat-comp-uv",
|
|
&chg->mmi.vfloat_comp_mv);
|
|
if (rc)
|
|
chg->mmi.vfloat_comp_mv = 0;
|
|
chg->mmi.vfloat_comp_mv /= 1000;
|
|
|
|
chg->mmi.support_2a_dcp = of_property_read_bool(node, "qcom,support-2a-dcp");
|
|
|
|
chg->mmi.support_mb_dcp = of_property_read_bool(node, "qcom,support-microb-dcp");
|
|
|
|
return rc;
|
|
}
|
|
|
|
static enum alarmtimer_restart mmi_heartbeat_alarm_cb(struct alarm *alarm,
|
|
ktime_t now)
|
|
{
|
|
struct smb_charger *chip = container_of(alarm, struct smb_charger,
|
|
mmi.heartbeat_alarm);
|
|
|
|
smblib_dbg(chip, PR_MOTO, "SMB: HB alarm fired\n");
|
|
|
|
__pm_stay_awake(chip->mmi.smblib_mmi_hb_wake_source);
|
|
cancel_delayed_work(&chip->mmi.heartbeat_work);
|
|
/* Delay by 500 ms to allow devices to resume. */
|
|
schedule_delayed_work(&chip->mmi.heartbeat_work,
|
|
msecs_to_jiffies(500));
|
|
|
|
return ALARMTIMER_NORESTART;
|
|
}
|
|
|
|
void mmi_init(struct smb_charger *chg)
|
|
{
|
|
int rc;
|
|
|
|
if (!chg)
|
|
return;
|
|
|
|
chg->ipc_log = ipc_log_context_create(QPNP_LOG_PAGES,
|
|
"charger", 0);
|
|
if (chg->ipc_log == NULL)
|
|
pr_err("%s: failed to create charger IPC log\n",
|
|
__func__);
|
|
else
|
|
smblib_dbg(chg, PR_MISC,
|
|
"IPC logging is enabled for charger\n");
|
|
|
|
chg->ipc_log_reg = ipc_log_context_create(QPNP_LOG_PAGES,
|
|
"charger_reg", 0);
|
|
if (chg->ipc_log_reg == NULL)
|
|
pr_err("%s: failed to create register IPC log\n",
|
|
__func__);
|
|
else
|
|
smblib_dbg(chg, PR_MISC,
|
|
"IPC logging is enabled for charger\n");
|
|
|
|
mmi_chip = chg;
|
|
chg->voltage_min_uv = MICRO_5V;
|
|
chg->voltage_max_uv = MICRO_9V;
|
|
chg->mmi.factory_mode = mmi_factory_check();
|
|
|
|
chg->mmi.is_factory_image = false;
|
|
chg->mmi.charging_limit_modes = CHARGING_LIMIT_UNKNOWN;
|
|
chg->mmi.charger_rate = POWER_SUPPLY_CHARGE_RATE_NONE;
|
|
|
|
INIT_DELAYED_WORK(&chg->mmi.heartbeat_work, mmi_heartbeat_work);
|
|
/*wakeup_source_init(&chg->mmi.smblib_mmi_hb_wake_source,
|
|
"smblib_mmi_hb_wake");*/
|
|
chg->mmi.smblib_mmi_hb_wake_source = wakeup_source_register(chg->dev,
|
|
"smblib_mmi_hb_wake");
|
|
if(!chg->mmi.smblib_mmi_hb_wake_source) {
|
|
smblib_err(chg, "Failed to allocate wakeup source\n");
|
|
return;
|
|
}
|
|
alarm_init(&chg->mmi.heartbeat_alarm, ALARM_BOOTTIME,
|
|
mmi_heartbeat_alarm_cb);
|
|
|
|
rc = parse_mmi_dt(chg);
|
|
if (rc < 0)
|
|
smblib_err(chg, "Error getting mmi dt items\n");
|
|
|
|
parse_mmi_dt_gpio(chg);
|
|
|
|
chg->mmi.smb_reboot.notifier_call = smbchg_reboot;
|
|
chg->mmi.smb_reboot.next = NULL;
|
|
chg->mmi.smb_reboot.priority = 1;
|
|
rc = register_reboot_notifier(&chg->mmi.smb_reboot);
|
|
if (rc)
|
|
smblib_err(chg, "SMB register for reboot failed\n");
|
|
|
|
rc = device_create_file(chg->dev,
|
|
&dev_attr_force_demo_mode);
|
|
if (rc) {
|
|
smblib_err(chg, "couldn't create force_demo_mode\n");
|
|
}
|
|
|
|
rc = device_create_file(chg->dev,
|
|
&dev_attr_force_max_chrg_temp);
|
|
if (rc) {
|
|
smblib_err(chg, "couldn't create force_max_chrg_temp\n");
|
|
}
|
|
|
|
rc = device_create_file(chg->dev,
|
|
&dev_attr_factory_image_mode);
|
|
if (rc)
|
|
smblib_err(chg, "couldn't create factory_image_mode\n");
|
|
|
|
rc = device_create_file(chg->dev,
|
|
&dev_attr_factory_charge_upper);
|
|
if (rc)
|
|
smblib_err(chg, "couldn't create factory_charge_upper\n");
|
|
|
|
if (chg->mmi.factory_mode) {
|
|
mmi_chip = chg;
|
|
smblib_err(chg, "Entering Factory Mode SMB!\n");
|
|
|
|
rc = device_create_file(chg->dev,
|
|
&dev_attr_force_chg_usb_suspend);
|
|
if (rc) {
|
|
smblib_err(chg,
|
|
"couldn't create force_chg_usb_suspend\n");
|
|
}
|
|
|
|
rc = device_create_file(chg->dev,
|
|
&dev_attr_force_chg_fail_clear);
|
|
if (rc) {
|
|
smblib_err(chg,
|
|
"couldn't create force_chg_fail_clear\n");
|
|
}
|
|
|
|
rc = device_create_file(chg->dev,
|
|
&dev_attr_force_chg_auto_enable);
|
|
if (rc) {
|
|
smblib_err(chg,
|
|
"couldn't create force_chg_auto_enable\n");
|
|
}
|
|
|
|
rc = device_create_file(chg->dev,
|
|
&dev_attr_force_chg_ibatt);
|
|
if (rc) {
|
|
smblib_err(chg, "couldn't create force_chg_ibatt\n");
|
|
}
|
|
|
|
rc = device_create_file(chg->dev,
|
|
&dev_attr_force_chg_iusb);
|
|
if (rc) {
|
|
smblib_err(chg, "couldn't create force_chg_iusb\n");
|
|
}
|
|
|
|
rc = device_create_file(chg->dev,
|
|
&dev_attr_force_chg_itrick);
|
|
if (rc) {
|
|
smblib_err(chg, "couldn't create force_chg_itrick\n");
|
|
}
|
|
}
|
|
|
|
/* reconfigure allowed voltage for HVDCP */
|
|
rc = smblib_set_adapter_allowance(chg,
|
|
USBIN_ADAPTER_ALLOW_5V_TO_9V);
|
|
|
|
/* Set DCIN adapter allowance */
|
|
rc = smblib_masked_write(chg, DCIN_ADAPTER_ALLOW_CFG_REG,
|
|
DCIN_ADAPTER_ALLOW_MASK,
|
|
DCIN_ADAPTER_ALLOW_5V_TO_9V);
|
|
if (rc)
|
|
smblib_err(chg, "couldn't set DCIN adapter allowance\n");
|
|
|
|
/* Turn Jeita OFF */
|
|
rc = smblib_masked_write(chg, JEITA_EN_CFG_REG,
|
|
0x3F,
|
|
0x00);
|
|
if (rc)
|
|
smblib_err(chg, "couldn't set JEITA CFG\n");
|
|
|
|
/* Register the notifier for the psy updates*/
|
|
chg->mmi.mmi_psy_notifier.notifier_call = mmi_psy_notifier_call;
|
|
rc = power_supply_reg_notifier(&chg->mmi.mmi_psy_notifier);
|
|
if (rc)
|
|
smblib_err(chg, "failed to reg notifier: %d\n", rc);
|
|
|
|
chg->mmi.init_done = true;
|
|
}
|
|
|
|
void mmi_deinit(struct smb_charger *chg)
|
|
{
|
|
if (!chg || !chg->mmi.init_done)
|
|
return;
|
|
|
|
device_remove_file(chg->dev,
|
|
&dev_attr_force_demo_mode);
|
|
device_remove_file(chg->dev,
|
|
&dev_attr_force_max_chrg_temp);
|
|
|
|
if (chg->mmi.factory_mode) {
|
|
device_remove_file(chg->dev,
|
|
&dev_attr_force_chg_usb_suspend);
|
|
device_remove_file(chg->dev,
|
|
&dev_attr_force_chg_fail_clear);
|
|
device_remove_file(chg->dev,
|
|
&dev_attr_force_chg_auto_enable);
|
|
device_remove_file(chg->dev,
|
|
&dev_attr_force_chg_ibatt);
|
|
device_remove_file(chg->dev,
|
|
&dev_attr_force_chg_iusb);
|
|
device_remove_file(chg->dev,
|
|
&dev_attr_force_chg_itrick);
|
|
}
|
|
wakeup_source_unregister(chg->mmi.smblib_mmi_hb_wake_source);
|
|
power_supply_unreg_notifier(&chg->mmi.mmi_psy_notifier);
|
|
}
|