blob: 7d5a8bdf7c803b6f4ea86cfc0088403652780f89 [file] [log] [blame]
/* Copyright (c) 2016-2017 The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/device.h>
#include <linux/regmap.h>
#include <linux/delay.h>
#include <linux/iio/consumer.h>
#include <linux/power_supply.h>
#include <linux/regulator/driver.h>
#include <linux/qpnp/qpnp-revid.h>
#include <linux/irq.h>
#include "smb-lib.h"
#include "smb-reg.h"
#include "storm-watch.h"
#include <linux/pmic-voter.h>
#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)
static bool is_secure(struct smb_charger *chg, int addr)
{
if (addr == SHIP_MODE_REG || addr == FREQ_CLK_DIV_REG)
return true;
/* assume everything above 0xA0 is secure */
return (bool)((addr & 0xFF) >= 0xA0);
}
int smblib_read(struct smb_charger *chg, u16 addr, u8 *val)
{
unsigned int temp;
int rc = 0;
rc = regmap_read(chg->regmap, addr, &temp);
if (rc >= 0)
*val = (u8)temp;
return rc;
}
int smblib_multibyte_read(struct smb_charger *chg, u16 addr, u8 *val,
int count)
{
return regmap_bulk_read(chg->regmap, addr, val, count);
}
int smblib_masked_write(struct smb_charger *chg, u16 addr, u8 mask, u8 val)
{
int rc = 0;
mutex_lock(&chg->write_lock);
if (is_secure(chg, addr)) {
rc = regmap_write(chg->regmap, (addr & 0xFF00) | 0xD0, 0xA5);
if (rc < 0)
goto unlock;
}
rc = regmap_update_bits(chg->regmap, addr, mask, val);
unlock:
mutex_unlock(&chg->write_lock);
return rc;
}
int smblib_write(struct smb_charger *chg, u16 addr, u8 val)
{
int rc = 0;
mutex_lock(&chg->write_lock);
if (is_secure(chg, addr)) {
rc = regmap_write(chg->regmap, (addr & ~(0xFF)) | 0xD0, 0xA5);
if (rc < 0)
goto unlock;
}
rc = regmap_write(chg->regmap, addr, val);
unlock:
mutex_unlock(&chg->write_lock);
return rc;
}
static int smblib_get_step_cc_delta(struct smb_charger *chg, int *cc_delta_ua)
{
int rc, step_state;
u8 stat;
if (!chg->step_chg_enabled) {
*cc_delta_ua = 0;
return 0;
}
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;
}
step_state = (stat & STEP_CHARGING_STATUS_MASK) >>
STEP_CHARGING_STATUS_SHIFT;
rc = smblib_get_charge_param(chg, &chg->param.step_cc_delta[step_state],
cc_delta_ua);
if (rc < 0) {
smblib_err(chg, "Couldn't get step cc delta rc=%d\n", rc);
return rc;
}
return 0;
}
static 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_2_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_SOFT_LIMIT_MASK)) {
*cc_delta_ua = 0;
return 0;
}
rc = smblib_get_charge_param(chg, &chg->param.jeita_cc_comp,
&cc_minus_ua);
if (rc < 0) {
smblib_err(chg, "Couldn't get jeita cc minus rc=%d\n", rc);
return rc;
}
*cc_delta_ua = -cc_minus_ua;
return 0;
}
int smblib_icl_override(struct smb_charger *chg, bool override)
{
int rc;
rc = smblib_masked_write(chg, USBIN_LOAD_CFG_REG,
ICL_OVERRIDE_AFTER_APSD_BIT,
override ? ICL_OVERRIDE_AFTER_APSD_BIT : 0);
if (rc < 0)
smblib_err(chg, "Couldn't override ICL rc=%d\n", rc);
return rc;
}
/********************
* 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;
}
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 const 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_DCP
},
[HVDCP2] = {
.name = "HVDCP2",
.bit = DCP_CHARGER_BIT | QC_2P0_BIT,
.pst = POWER_SUPPLY_TYPE_USB_HVDCP
},
[HVDCP3] = {
.name = "HVDCP3",
.bit = DCP_CHARGER_BIT | QC_3P0_BIT,
.pst = POWER_SUPPLY_TYPE_USB_HVDCP_3,
},
};
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;
}
/********************
* REGISTER SETTERS *
********************/
static int chg_freq_list[] = {
9600, 9600, 6400, 4800, 3800, 3200, 2700, 2400, 2100, 1900, 1700,
1600, 1500, 1400, 1300, 1200,
};
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] == val_u)
break;
}
if (i == ARRAY_SIZE(chg_freq_list)) {
pr_err("Invalid frequency %d Hz\n", val_u / 2);
return -EINVAL;
}
*val_raw = i;
return 0;
}
static int smblib_set_opt_freq_buck(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_buck, 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_err(chg, "%s: %d is out of range [%d, %d]\n",
param->name, val_u, param->min_u, param->max_u);
return -EINVAL;
}
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;
}
static int step_charge_soc_update(struct smb_charger *chg, int capacity)
{
int rc = 0;
rc = smblib_set_charge_param(chg, &chg->param.step_soc, capacity);
if (rc < 0) {
smblib_err(chg, "Error in updating soc, rc=%d\n", rc);
return rc;
}
rc = smblib_write(chg, STEP_CHG_SOC_VBATT_V_UPDATE_REG,
STEP_CHG_SOC_VBATT_V_UPDATE_BIT);
if (rc < 0) {
smblib_err(chg,
"Couldn't set STEP_CHG_SOC_VBATT_V_UPDATE_REG rc=%d\n",
rc);
return rc;
}
return rc;
}
int smblib_set_usb_suspend(struct smb_charger *chg, bool suspend)
{
int rc = 0;
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);
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;
switch (allowed_voltage) {
case USBIN_ADAPTER_ALLOW_12V:
case USBIN_ADAPTER_ALLOW_5V_OR_12V:
case USBIN_ADAPTER_ALLOW_9V_TO_12V:
case USBIN_ADAPTER_ALLOW_5V_OR_9V_TO_12V:
case USBIN_ADAPTER_ALLOW_5V_TO_12V:
/* PM660 only support max. 9V */
if (chg->smb_version == PM660_SUBTYPE) {
smblib_dbg(chg, PR_MISC, "voltage not supported=%d\n",
allowed_voltage);
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;
}
#define MICRO_5V 5000000
#define MICRO_9V 9000000
#define MICRO_12V 12000000
static int smblib_set_usb_pd_allowed_voltage(struct smb_charger *chg,
int min_allowed_uv, int max_allowed_uv)
{
int rc;
u8 allowed_voltage;
if (min_allowed_uv == MICRO_5V && max_allowed_uv == MICRO_5V) {
allowed_voltage = USBIN_ADAPTER_ALLOW_5V;
smblib_set_opt_freq_buck(chg, chg->chg_freq.freq_5V);
} else if (min_allowed_uv == MICRO_9V && max_allowed_uv == MICRO_9V) {
allowed_voltage = USBIN_ADAPTER_ALLOW_9V;
smblib_set_opt_freq_buck(chg, chg->chg_freq.freq_9V);
} else if (min_allowed_uv == MICRO_12V && max_allowed_uv == MICRO_12V) {
allowed_voltage = USBIN_ADAPTER_ALLOW_12V;
smblib_set_opt_freq_buck(chg, chg->chg_freq.freq_12V);
} else if (min_allowed_uv < MICRO_9V && max_allowed_uv <= MICRO_9V) {
allowed_voltage = USBIN_ADAPTER_ALLOW_5V_TO_9V;
} else if (min_allowed_uv < MICRO_9V && max_allowed_uv <= MICRO_12V) {
allowed_voltage = USBIN_ADAPTER_ALLOW_5V_TO_12V;
} else if (min_allowed_uv < MICRO_12V && max_allowed_uv <= MICRO_12V) {
allowed_voltage = USBIN_ADAPTER_ALLOW_9V_TO_12V;
} else {
smblib_err(chg, "invalid allowed voltage [%d, %d]\n",
min_allowed_uv, max_allowed_uv);
return -EINVAL;
}
rc = smblib_set_adapter_allowance(chg, allowed_voltage);
if (rc < 0) {
smblib_err(chg, "Couldn't configure adapter allowance rc=%d\n",
rc);
return rc;
}
return rc;
}
/********************
* HELPER FUNCTIONS *
********************/
static void smblib_rerun_apsd(struct smb_charger *chg)
{
int rc;
smblib_dbg(chg, PR_MISC, "re-running APSD\n");
if (chg->wa_flags & QC_AUTH_INTERRUPT_WA_BIT) {
rc = smblib_masked_write(chg,
USBIN_SOURCE_CHANGE_INTRPT_ENB_REG,
AUTH_IRQ_EN_CFG_BIT, AUTH_IRQ_EN_CFG_BIT);
if (rc < 0)
smblib_err(chg, "Couldn't enable HVDCP auth IRQ rc=%d\n",
rc);
}
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 PD is active, APSD is disabled so won't have a valid result */
if (chg->pd_active)
chg->usb_psy_desc.type = POWER_SUPPLY_TYPE_USB_PD;
else
chg->usb_psy_desc.type = apsd_result->pst;
smblib_dbg(chg, PR_MISC, "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->pl.psy && !strcmp(psy->desc->name, "parallel"))
chg->pl.psy = psy;
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;
cancel_delayed_work_sync(&chg->pl_enable_work);
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);
cancel_delayed_work_sync(&chg->hvdcp_detect_work);
if (chg->wa_flags & QC_AUTH_INTERRUPT_WA_BIT) {
/* re-enable AUTH_IRQ_EN_CFG_BIT */
rc = smblib_masked_write(chg,
USBIN_SOURCE_CHANGE_INTRPT_ENB_REG,
AUTH_IRQ_EN_CFG_BIT, AUTH_IRQ_EN_CFG_BIT);
if (rc < 0)
smblib_err(chg,
"Couldn't enable QC auth setting rc=%d\n", rc);
}
/* reconfigure allowed voltage for HVDCP */
rc = smblib_set_adapter_allowance(chg,
USBIN_ADAPTER_ALLOW_5V_OR_9V_TO_12V);
if (rc < 0)
smblib_err(chg, "Couldn't set USBIN_ADAPTER_ALLOW_5V_OR_9V_TO_12V rc=%d\n",
rc);
chg->voltage_min_uv = MICRO_5V;
chg->voltage_max_uv = MICRO_5V;
chg->usb_icl_delta_ua = 0;
chg->pulse_cnt = 0;
chg->uusb_apsd_rerun_done = false;
/* 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);
}
void smblib_suspend_on_debug_battery(struct smb_charger *chg)
{
int rc;
union power_supply_propval val;
if (!chg->suspend_input_on_debug_batt)
return;
rc = power_supply_get_property(chg->bms_psy,
POWER_SUPPLY_PROP_DEBUG_BATTERY, &val);
if (rc < 0) {
smblib_err(chg, "Couldn't get debug battery prop rc=%d\n", rc);
return;
}
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");
}
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;
/* 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)) {
smblib_err(chg, "Couldn't get dpdm regulator rc=%ld\n",
PTR_ERR(chg->dpdm_reg));
chg->dpdm_reg = NULL;
}
}
if (chg->dpdm_reg && !regulator_is_enabled(chg->dpdm_reg)) {
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);
}
chg->uusb_apsd_rerun_done = true;
smblib_rerun_apsd(chg);
return 0;
}
static int smblib_get_pulse_cnt(struct smb_charger *chg, int *count)
{
int rc;
u8 val[2];
switch (chg->smb_version) {
case PMI8998_SUBTYPE:
rc = smblib_read(chg, QC_PULSE_COUNT_STATUS_REG, val);
if (rc) {
pr_err("failed to read QC_PULSE_COUNT_STATUS_REG rc=%d\n",
rc);
return rc;
}
*count = val[0] & QC_PULSE_COUNT_MASK;
break;
case PM660_SUBTYPE:
rc = smblib_multibyte_read(chg,
QC_PULSE_COUNT_STATUS_1_REG, val, 2);
if (rc) {
pr_err("failed to read QC_PULSE_COUNT_STATUS_1_REG rc=%d\n",
rc);
return rc;
}
*count = (val[1] << 8) | val[0];
break;
default:
smblib_dbg(chg, PR_PARALLEL, "unknown SMB chip %d\n",
chg->smb_version);
return -EINVAL;
}
return 0;
}
#define USBIN_25MA 25000
#define USBIN_100MA 100000
#define USBIN_150MA 150000
#define USBIN_500MA 500000
#define USBIN_900MA 900000
static int set_sdp_current(struct smb_charger *chg, int icl_ua)
{
int rc;
u8 icl_options;
/* 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:
smblib_err(chg, "ICL %duA isn't supported for SDP\n", icl_ua);
return -EINVAL;
}
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;
}
return rc;
}
int smblib_set_icl_current(struct smb_charger *chg, int icl_ua)
{
int rc = 0;
bool override;
union power_supply_propval pval;
/* suspend and return if 25mA or less is requested */
if (icl_ua < USBIN_25MA)
return smblib_set_usb_suspend(chg, true);
disable_irq_nosync(chg->irq_info[USBIN_ICL_CHANGE_IRQ].irq);
if (icl_ua == INT_MAX)
goto override_suspend_config;
rc = smblib_get_prop_typec_mode(chg, &pval);
if (rc < 0) {
smblib_err(chg, "Couldn't get typeC mode rc = %d\n", rc);
goto enable_icl_changed_interrupt;
}
/* configure current */
if (pval.intval == POWER_SUPPLY_TYPEC_SOURCE_DEFAULT
&& (chg->usb_psy_desc.type == POWER_SUPPLY_TYPE_USB)) {
rc = set_sdp_current(chg, icl_ua);
if (rc < 0) {
smblib_err(chg, "Couldn't set SDP ICL rc=%d\n", rc);
goto enable_icl_changed_interrupt;
}
} else {
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 enable_icl_changed_interrupt;
}
}
override_suspend_config:
/* determine if override needs to be enforced */
override = true;
if (icl_ua == INT_MAX) {
/* remove override if no voters - hw defaults is desired */
override = false;
} else if (pval.intval == POWER_SUPPLY_TYPEC_SOURCE_DEFAULT) {
if (chg->usb_psy_desc.type == POWER_SUPPLY_TYPE_USB)
/* For std cable with type = SDP never override */
override = false;
else if (chg->usb_psy_desc.type == POWER_SUPPLY_TYPE_USB_CDP
&& icl_ua == 1500000)
/*
* For std cable with type = CDP override only if
* current is not 1500mA
*/
override = false;
}
/* enforce override */
rc = smblib_masked_write(chg, USBIN_ICL_OPTIONS_REG,
USBIN_MODE_CHG_BIT, override ? USBIN_MODE_CHG_BIT : 0);
rc = smblib_icl_override(chg, override);
if (rc < 0) {
smblib_err(chg, "Couldn't set ICL override rc=%d\n", rc);
goto enable_icl_changed_interrupt;
}
/* 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 enable_icl_changed_interrupt;
}
enable_icl_changed_interrupt:
enable_irq(chg->irq_info[USBIN_ICL_CHANGE_IRQ].irq);
return rc;
}
/*********************
* VOTABLE CALLBACKS *
*********************/
static int smblib_dc_suspend_vote_callback(struct votable *votable, void *data,
int suspend, const char *client)
{
struct smb_charger *chg = data;
/* 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_pd_disallowed_votable_indirect_callback(
struct votable *votable, void *data, int disallowed, const char *client)
{
struct smb_charger *chg = data;
int rc;
rc = vote(chg->pd_allowed_votable, PD_DISALLOWED_INDIRECT_VOTER,
!disallowed, 0);
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;
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;
}
return 0;
}
static int smblib_pl_enable_indirect_vote_callback(struct votable *votable,
void *data, int chg_enable, const char *client)
{
struct smb_charger *chg = data;
vote(chg->pl_disable_votable, PL_INDIRECT_VOTER, !chg_enable, 0);
return 0;
}
static int smblib_hvdcp_enable_vote_callback(struct votable *votable,
void *data,
int hvdcp_enable, const char *client)
{
struct smb_charger *chg = data;
int rc;
u8 val = HVDCP_AUTH_ALG_EN_CFG_BIT | HVDCP_EN_BIT;
u8 stat;
/* vote to enable/disable HW autonomous INOV */
vote(chg->hvdcp_hw_inov_dis_votable, client, !hvdcp_enable, 0);
/*
* Disable the autonomous bit and auth bit for disabling hvdcp.
* This ensures only qc 2.0 detection runs but no vbus
* negotiation happens.
*/
if (!hvdcp_enable)
val = HVDCP_EN_BIT;
rc = smblib_masked_write(chg, USBIN_OPTIONS_1_CFG_REG,
HVDCP_EN_BIT | HVDCP_AUTH_ALG_EN_CFG_BIT,
val);
if (rc < 0) {
smblib_err(chg, "Couldn't %s hvdcp rc=%d\n",
hvdcp_enable ? "enable" : "disable", rc);
return rc;
}
rc = smblib_read(chg, APSD_STATUS_REG, &stat);
if (rc < 0) {
smblib_err(chg, "Couldn't read APSD status rc=%d\n", rc);
return rc;
}
/* re-run APSD if HVDCP was detected */
if (stat & QC_CHARGER_BIT)
smblib_rerun_apsd(chg);
return 0;
}
static int smblib_hvdcp_disable_indirect_vote_callback(struct votable *votable,
void *data, int hvdcp_disable, const char *client)
{
struct smb_charger *chg = data;
vote(chg->hvdcp_enable_votable, HVDCP_INDIRECT_VOTER,
!hvdcp_disable, 0);
return 0;
}
static int smblib_apsd_disable_vote_callback(struct votable *votable,
void *data,
int apsd_disable, const char *client)
{
struct smb_charger *chg = data;
int rc;
if (apsd_disable) {
rc = smblib_masked_write(chg, USBIN_OPTIONS_1_CFG_REG,
AUTO_SRC_DETECT_BIT,
0);
if (rc < 0) {
smblib_err(chg, "Couldn't disable APSD rc=%d\n", rc);
return rc;
}
} else {
rc = smblib_masked_write(chg, USBIN_OPTIONS_1_CFG_REG,
AUTO_SRC_DETECT_BIT,
AUTO_SRC_DETECT_BIT);
if (rc < 0) {
smblib_err(chg, "Couldn't enable APSD rc=%d\n", rc);
return rc;
}
}
return 0;
}
static int smblib_hvdcp_hw_inov_dis_vote_callback(struct votable *votable,
void *data, int disable, const char *client)
{
struct smb_charger *chg = data;
int rc;
if (disable) {
/*
* the pulse count register get zeroed when autonomous mode is
* disabled. Track that in variables before disabling
*/
rc = smblib_get_pulse_cnt(chg, &chg->pulse_cnt);
if (rc < 0) {
pr_err("failed to read QC_PULSE_COUNT_STATUS_REG rc=%d\n",
rc);
return rc;
}
}
rc = smblib_masked_write(chg, USBIN_OPTIONS_1_CFG_REG,
HVDCP_AUTONOMOUS_MODE_EN_CFG_BIT,
disable ? 0 : HVDCP_AUTONOMOUS_MODE_EN_CFG_BIT);
if (rc < 0) {
smblib_err(chg, "Couldn't %s hvdcp rc=%d\n",
disable ? "disable" : "enable", rc);
return rc;
}
return rc;
}
static int smblib_usb_irq_enable_vote_callback(struct votable *votable,
void *data, int enable, const char *client)
{
struct smb_charger *chg = data;
if (!chg->irq_info[INPUT_CURRENT_LIMIT_IRQ].irq ||
!chg->irq_info[HIGH_DUTY_CYCLE_IRQ].irq)
return 0;
if (enable) {
enable_irq(chg->irq_info[INPUT_CURRENT_LIMIT_IRQ].irq);
enable_irq(chg->irq_info[HIGH_DUTY_CYCLE_IRQ].irq);
} else {
disable_irq(chg->irq_info[INPUT_CURRENT_LIMIT_IRQ].irq);
disable_irq(chg->irq_info[HIGH_DUTY_CYCLE_IRQ].irq);
}
return 0;
}
static int smblib_typec_irq_disable_vote_callback(struct votable *votable,
void *data, int disable, const char *client)
{
struct smb_charger *chg = data;
if (!chg->irq_info[TYPE_C_CHANGE_IRQ].irq)
return 0;
if (disable)
disable_irq_nosync(chg->irq_info[TYPE_C_CHANGE_IRQ].irq);
else
enable_irq(chg->irq_info[TYPE_C_CHANGE_IRQ].irq);
return 0;
}
/*******************
* VCONN REGULATOR *
* *****************/
#define MAX_OTG_SS_TRIES 2
static int _smblib_vconn_regulator_enable(struct regulator_dev *rdev)
{
struct smb_charger *chg = rdev_get_drvdata(rdev);
u8 otg_stat, val;
int rc = 0, i;
if (!chg->external_vconn) {
/*
* Hardware based OTG soft start should complete within 1ms, so
* wait for 2ms in the worst case.
*/
for (i = 0; i < MAX_OTG_SS_TRIES; ++i) {
usleep_range(1000, 1100);
rc = smblib_read(chg, OTG_STATUS_REG, &otg_stat);
if (rc < 0) {
smblib_err(chg, "Couldn't read OTG status rc=%d\n",
rc);
return rc;
}
if (otg_stat & BOOST_SOFTSTART_DONE_BIT)
break;
}
if (!(otg_stat & BOOST_SOFTSTART_DONE_BIT)) {
smblib_err(chg, "Couldn't enable VCONN; OTG soft start failed\n");
return -EAGAIN;
}
}
/*
* VCONN_EN_ORIENTATION is overloaded with overriding the CC pin used
* for Vconn, and it should be set with reverse polarity of CC_OUT.
*/
smblib_dbg(chg, PR_OTG, "enabling VCONN\n");
val = chg->typec_status[3] &
CC_ORIENTATION_BIT ? 0 : VCONN_EN_ORIENTATION_BIT;
rc = smblib_masked_write(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG,
VCONN_EN_VALUE_BIT | VCONN_EN_ORIENTATION_BIT,
VCONN_EN_VALUE_BIT | val);
if (rc < 0) {
smblib_err(chg, "Couldn't enable vconn setting rc=%d\n", rc);
return rc;
}
return rc;
}
int smblib_vconn_regulator_enable(struct regulator_dev *rdev)
{
struct smb_charger *chg = rdev_get_drvdata(rdev);
int rc = 0;
mutex_lock(&chg->otg_oc_lock);
if (chg->vconn_en)
goto unlock;
rc = _smblib_vconn_regulator_enable(rdev);
if (rc >= 0)
chg->vconn_en = true;
unlock:
mutex_unlock(&chg->otg_oc_lock);
return rc;
}
static 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_INTRPT_ENB_SOFTWARE_CTRL_REG,
VCONN_EN_VALUE_BIT, 0);
if (rc < 0)
smblib_err(chg, "Couldn't disable vconn regulator rc=%d\n", rc);
return rc;
}
int smblib_vconn_regulator_disable(struct regulator_dev *rdev)
{
struct smb_charger *chg = rdev_get_drvdata(rdev);
int rc = 0;
mutex_lock(&chg->otg_oc_lock);
if (!chg->vconn_en)
goto unlock;
rc = _smblib_vconn_regulator_disable(rdev);
if (rc >= 0)
chg->vconn_en = false;
unlock:
mutex_unlock(&chg->otg_oc_lock);
return rc;
}
int smblib_vconn_regulator_is_enabled(struct regulator_dev *rdev)
{
struct smb_charger *chg = rdev_get_drvdata(rdev);
int ret;
mutex_lock(&chg->otg_oc_lock);
ret = chg->vconn_en;
mutex_unlock(&chg->otg_oc_lock);
return ret;
}
/*****************
* OTG REGULATOR *
*****************/
#define MAX_RETRY 15
#define MIN_DELAY_US 2000
#define MAX_DELAY_US 9000
static int _smblib_vbus_regulator_enable(struct regulator_dev *rdev)
{
struct smb_charger *chg = rdev_get_drvdata(rdev);
int rc, retry_count = 0, min_delay = MIN_DELAY_US;
u8 stat;
smblib_dbg(chg, PR_OTG, "halt 1 in 8 mode\n");
rc = smblib_masked_write(chg, OTG_ENG_OTG_CFG_REG,
ENG_BUCKBOOST_HALT1_8_MODE_BIT,
ENG_BUCKBOOST_HALT1_8_MODE_BIT);
if (rc < 0) {
smblib_err(chg, "Couldn't set OTG_ENG_OTG_CFG_REG rc=%d\n",
rc);
return rc;
}
smblib_dbg(chg, PR_OTG, "enabling OTG\n");
rc = smblib_write(chg, CMD_OTG_REG, OTG_EN_BIT);
if (rc < 0) {
smblib_err(chg, "Couldn't enable OTG regulator rc=%d\n", rc);
return rc;
}
if (chg->wa_flags & OTG_WA) {
/* check for softstart */
do {
usleep_range(min_delay, min_delay + 100);
rc = smblib_read(chg, OTG_STATUS_REG, &stat);
if (rc < 0) {
smblib_err(chg,
"Couldn't read OTG status rc=%d\n",
rc);
goto out;
}
if (stat & BOOST_SOFTSTART_DONE_BIT) {
rc = smblib_set_charge_param(chg,
&chg->param.otg_cl, chg->otg_cl_ua);
if (rc < 0)
smblib_err(chg,
"Couldn't set otg limit\n");
break;
}
/* increase the delay for following iterations */
if (retry_count > 5)
min_delay = MAX_DELAY_US;
} while (retry_count++ < MAX_RETRY);
if (retry_count >= MAX_RETRY) {
smblib_dbg(chg, PR_OTG, "Boost Softstart not done\n");
goto out;
}
}
return 0;
out:
/* disable OTG if softstart failed */
smblib_write(chg, CMD_OTG_REG, 0);
return rc;
}
int smblib_vbus_regulator_enable(struct regulator_dev *rdev)
{
struct smb_charger *chg = rdev_get_drvdata(rdev);
int rc = 0;
mutex_lock(&chg->otg_oc_lock);
if (chg->otg_en)
goto unlock;
if (!chg->usb_icl_votable) {
chg->usb_icl_votable = find_votable("USB_ICL");
if (!chg->usb_icl_votable)
return -EINVAL;
}
vote(chg->usb_icl_votable, USBIN_USBIN_BOOST_VOTER, true, 0);
rc = _smblib_vbus_regulator_enable(rdev);
if (rc >= 0)
chg->otg_en = true;
unlock:
mutex_unlock(&chg->otg_oc_lock);
return rc;
}
static int _smblib_vbus_regulator_disable(struct regulator_dev *rdev)
{
struct smb_charger *chg = rdev_get_drvdata(rdev);
int rc;
if (!chg->external_vconn && chg->vconn_en) {
smblib_dbg(chg, PR_OTG, "Killing VCONN before disabling OTG\n");
rc = _smblib_vconn_regulator_disable(rdev);
if (rc < 0)
smblib_err(chg, "Couldn't disable VCONN rc=%d\n", rc);
}
if (chg->wa_flags & OTG_WA) {
/* set OTG current limit to minimum value */
rc = smblib_set_charge_param(chg, &chg->param.otg_cl,
chg->param.otg_cl.min_u);
if (rc < 0) {
smblib_err(chg,
"Couldn't set otg current limit rc=%d\n", rc);
return rc;
}
}
smblib_dbg(chg, PR_OTG, "disabling OTG\n");
rc = smblib_write(chg, CMD_OTG_REG, 0);
if (rc < 0) {
smblib_err(chg, "Couldn't disable OTG regulator rc=%d\n", rc);
return rc;
}
smblib_dbg(chg, PR_OTG, "start 1 in 8 mode\n");
rc = smblib_masked_write(chg, OTG_ENG_OTG_CFG_REG,
ENG_BUCKBOOST_HALT1_8_MODE_BIT, 0);
if (rc < 0) {
smblib_err(chg, "Couldn't set OTG_ENG_OTG_CFG_REG rc=%d\n", rc);
return rc;
}
return 0;
}
int smblib_vbus_regulator_disable(struct regulator_dev *rdev)
{
struct smb_charger *chg = rdev_get_drvdata(rdev);
int rc = 0;
mutex_lock(&chg->otg_oc_lock);
if (!chg->otg_en)
goto unlock;
rc = _smblib_vbus_regulator_disable(rdev);
if (rc >= 0)
chg->otg_en = false;
if (chg->usb_icl_votable)
vote(chg->usb_icl_votable, USBIN_USBIN_BOOST_VOTER, false, 0);
unlock:
mutex_unlock(&chg->otg_oc_lock);
return rc;
}
int smblib_vbus_regulator_is_enabled(struct regulator_dev *rdev)
{
struct smb_charger *chg = rdev_get_drvdata(rdev);
int ret;
mutex_lock(&chg->otg_oc_lock);
ret = chg->otg_en;
mutex_unlock(&chg->otg_oc_lock);
return ret;
}
/********************
* 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->fake_capacity >= 0) {
val->intval = chg->fake_capacity;
return 0;
}
if (chg->bms_psy)
rc = power_supply_get_property(chg->bms_psy,
POWER_SUPPLY_PROP_CAPACITY, val);
return rc;
}
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;
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:
val->intval = POWER_SUPPLY_STATUS_FULL;
break;
default:
val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
break;
}
return rc;
}
switch (stat) {
case TRICKLE_CHARGE:
case PRE_CHARGE:
case FAST_CHARGE:
case FULLON_CHARGE:
case TAPER_CHARGE:
val->intval = POWER_SUPPLY_STATUS_CHARGING;
break;
case TERMINATE_CHARGE:
case INHIBIT_CHARGE:
val->intval = POWER_SUPPLY_STATUS_FULL;
break;
case DISABLE_CHARGE:
val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
break;
default:
val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
break;
}
if (val->intval != POWER_SUPPLY_STATUS_CHARGING)
return 0;
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;
}
stat &= ENABLE_TRICKLE_BIT | ENABLE_PRE_CHARGING_BIT |
ENABLE_FAST_CHARGING_BIT | ENABLE_FULLON_MODE_BIT;
if (!stat)
val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
return 0;
}
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 FAST_CHARGE:
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;
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_batt_voltage_now(chg, &pval);
if (!rc) {
/*
* If Vbatt is within 40mV above Vfloat, then don't
* treat it as overvoltage.
*/
if (pval.intval >=
get_effective_result(chg->fv_votable) + 40000) {
val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
smblib_err(chg, "battery over-voltage\n");
goto done;
}
}
}
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_LIMIT_BIT)
val->intval = POWER_SUPPLY_HEALTH_COOL;
else if (stat & BAT_TEMP_STATUS_HOT_SOFT_LIMIT_BIT)
val->intval = POWER_SUPPLY_HEALTH_WARM;
else
val->intval = POWER_SUPPLY_HEALTH_GOOD;
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_input_current_limited(struct smb_charger *chg,
union power_supply_propval *val)
{
u8 stat;
int rc;
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_voltage_now(struct smb_charger *chg,
union power_supply_propval *val)
{
int rc;
if (!chg->bms_psy)
return -EINVAL;
rc = power_supply_get_property(chg->bms_psy,
POWER_SUPPLY_PROP_VOLTAGE_NOW, val);
return rc;
}
int smblib_get_prop_batt_current_now(struct smb_charger *chg,
union power_supply_propval *val)
{
int rc;
if (!chg->bms_psy)
return -EINVAL;
rc = power_supply_get_property(chg->bms_psy,
POWER_SUPPLY_PROP_CURRENT_NOW, val);
return rc;
}
int smblib_get_prop_batt_temp(struct smb_charger *chg,
union power_supply_propval *val)
{
int rc;
if (!chg->bms_psy)
return -EINVAL;
rc = power_supply_get_property(chg->bms_psy,
POWER_SUPPLY_PROP_TEMP, val);
return rc;
}
int smblib_get_prop_step_chg_step(struct smb_charger *chg,
union power_supply_propval *val)
{
int rc;
u8 stat;
if (!chg->step_chg_enabled) {
val->intval = -1;
return 0;
}
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;
}
val->intval = (stat & STEP_CHARGING_STATUS_MASK) >>
STEP_CHARGING_STATUS_SHIFT;
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);
return 0;
}
int smblib_get_prop_charge_qnovo_enable(struct smb_charger *chg,
union power_supply_propval *val)
{
int rc;
u8 stat;
rc = smblib_read(chg, QNOVO_PT_ENABLE_CMD_REG, &stat);
if (rc < 0) {
smblib_err(chg, "Couldn't read QNOVO_PT_ENABLE_CMD rc=%d\n",
rc);
return rc;
}
val->intval = (bool)(stat & QNOVO_PT_ENABLE_CMD_BIT);
return 0;
}
/***********************
* 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)
{
chg->fake_capacity = val->intval;
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)
return -EINVAL;
chg->system_temp_level = val->intval;
/* disable parallel charge in case of system temp level */
vote(chg->pl_disable_votable, THERMAL_DAEMON_VOTER,
chg->system_temp_level ? true : false, 0);
if (chg->system_temp_level == chg->thermal_levels)
return vote(chg->chg_disable_votable,
THERMAL_DAEMON_VOTER, true, 0);
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]);
return 0;
}
int smblib_set_prop_charge_qnovo_enable(struct smb_charger *chg,
const union power_supply_propval *val)
{
int rc = 0;
rc = smblib_masked_write(chg, QNOVO_PT_ENABLE_CMD_REG,
QNOVO_PT_ENABLE_CMD_BIT,
val->intval ? QNOVO_PT_ENABLE_CMD_BIT : 0);
if (rc < 0) {
dev_err(chg->dev, "Couldn't enable qnovo rc=%d\n", rc);
return rc;
}
return rc;
}
int smblib_rerun_aicl(struct smb_charger *chg)
{
int rc, settled_icl_ua;
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");
switch (chg->smb_version) {
case PMI8998_SUBTYPE:
rc = smblib_get_charge_param(chg, &chg->param.icl_stat,
&settled_icl_ua);
if (rc < 0) {
smblib_err(chg, "Couldn't get settled ICL rc=%d\n", rc);
return rc;
}
vote(chg->usb_icl_votable, AICL_RERUN_VOTER, true,
max(settled_icl_ua - chg->param.usb_icl.step_u,
chg->param.usb_icl.step_u));
vote(chg->usb_icl_votable, AICL_RERUN_VOTER, false, 0);
break;
case PM660_SUBTYPE:
/*
* Use restart_AICL instead of trigger_AICL as it runs the
* complete AICL instead of starting from the last settled
* value.
*/
rc = smblib_masked_write(chg, CMD_HVDCP_2_REG,
RESTART_AICL_BIT, RESTART_AICL_BIT);
if (rc < 0)
smblib_err(chg, "Couldn't write to CMD_HVDCP_2_REG rc=%d\n",
rc);
break;
default:
smblib_dbg(chg, PR_PARALLEL, "unknown SMB chip %d\n",
chg->smb_version);
return -EINVAL;
}
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_dp_dm(struct smb_charger *chg, int val)
{
int target_icl_ua, rc = 0;
switch (val) {
case POWER_SUPPLY_DP_DM_DP_PULSE:
rc = smblib_dp_pulse(chg);
if (!rc)
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:
chg->usb_icl_delta_ua -= 100000;
target_icl_ua = get_effective_result(chg->usb_icl_votable);
vote(chg->usb_icl_votable, SW_QC3_VOTER, true,
target_icl_ua + chg->usb_icl_delta_ua);
break;
case POWER_SUPPLY_DP_DM_ICL_UP:
default:
break;
}
return rc;
}
/*******************
* DC PSY GETTERS *
*******************/
int smblib_get_prop_dc_present(struct smb_charger *chg,
union power_supply_propval *val)
{
int rc;
u8 stat;
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 (get_client_vote(chg->dc_suspend_votable, USER_VOTER)) {
val->intval = false;
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)
{
val->intval = get_effective_result_locked(chg->dc_icl_votable);
return 0;
}
/*******************
* DC PSY SETTERS *
* *****************/
int smblib_set_prop_dc_current_max(struct smb_charger *chg,
const union power_supply_propval *val)
{
int rc;
rc = vote(chg->dc_icl_votable, USER_VOTER, true, val->intval);
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;
u8 stat;
if (get_client_vote_locked(chg->usb_icl_votable, USER_VOTER) == 0) {
val->intval = false;
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);
return rc;
}
int smblib_get_prop_usb_voltage_now(struct smb_charger *chg,
union power_supply_propval *val)
{
int rc = 0;
rc = smblib_get_prop_usb_present(chg, val);
if (rc < 0 || !val->intval)
return rc;
if (!chg->iio.usbin_v_chan ||
PTR_ERR(chg->iio.usbin_v_chan) == -EPROBE_DEFER)
chg->iio.usbin_v_chan = iio_channel_get(chg->dev, "usbin_v");
if (IS_ERR(chg->iio.usbin_v_chan))
return PTR_ERR(chg->iio.usbin_v_chan);
return iio_read_channel_processed(chg->iio.usbin_v_chan, &val->intval);
}
int smblib_get_prop_pd_current_max(struct smb_charger *chg,
union power_supply_propval *val)
{
val->intval = get_client_vote_locked(chg->usb_icl_votable, PD_VOTER);
return 0;
}
int smblib_get_prop_usb_current_max(struct smb_charger *chg,
union power_supply_propval *val)
{
val->intval = get_client_vote_locked(chg->usb_icl_votable,
USB_PSY_VOTER);
return 0;
}
int smblib_get_prop_usb_current_now(struct smb_charger *chg,
union power_supply_propval *val)
{
int rc = 0;
rc = smblib_get_prop_usb_present(chg, val);
if (rc < 0 || !val->intval)
return rc;
if (!chg->iio.usbin_i_chan ||
PTR_ERR(chg->iio.usbin_i_chan) == -EPROBE_DEFER)
chg->iio.usbin_i_chan = iio_channel_get(chg->dev, "usbin_i");
if (IS_ERR(chg->iio.usbin_i_chan))
return PTR_ERR(chg->iio.usbin_i_chan);
return iio_read_channel_processed(chg->iio.usbin_i_chan, &val->intval);
}
int smblib_get_prop_charger_temp(struct smb_charger *chg,
union power_supply_propval *val)
{
int rc;
if (!chg->iio.temp_chan ||
PTR_ERR(chg->iio.temp_chan) == -EPROBE_DEFER)
chg->iio.temp_chan = iio_channel_get(chg->dev, "charger_temp");
if (IS_ERR(chg->iio.temp_chan))
return PTR_ERR(chg->iio.temp_chan);
rc = iio_read_channel_processed(chg->iio.temp_chan, &val->intval);
val->intval /= 100;
return rc;
}
int smblib_get_prop_charger_temp_max(struct smb_charger *chg,
union power_supply_propval *val)
{
int rc;
if (!chg->iio.temp_max_chan ||
PTR_ERR(chg->iio.temp_max_chan) == -EPROBE_DEFER)
chg->iio.temp_max_chan = iio_channel_get(chg->dev,
"charger_temp_max");
if (IS_ERR(chg->iio.temp_max_chan))
return PTR_ERR(chg->iio.temp_max_chan);
rc = iio_read_channel_processed(chg->iio.temp_max_chan, &val->intval);
val->intval /= 100;
return rc;
}
int smblib_get_prop_typec_cc_orientation(struct smb_charger *chg,
union power_supply_propval *val)
{
if (chg->typec_status[3] & CC_ATTACHED_BIT)
val->intval =
(bool)(chg->typec_status[3] & CC_ORIENTATION_BIT) + 1;
else
val->intval = 0;
return 0;
}
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)
{
switch (chg->typec_status[0]) {
case 0:
return POWER_SUPPLY_TYPEC_NONE;
case UFP_TYPEC_RDSTD_BIT:
return POWER_SUPPLY_TYPEC_SOURCE_DEFAULT;
case UFP_TYPEC_RD1P5_BIT:
return POWER_SUPPLY_TYPEC_SOURCE_MEDIUM;
case UFP_TYPEC_RD3P0_BIT:
return POWER_SUPPLY_TYPEC_SOURCE_HIGH;
default:
break;
}
return POWER_SUPPLY_TYPEC_NON_COMPLIANT;
}
static int smblib_get_prop_dfp_mode(struct smb_charger *chg)
{
switch (chg->typec_status[1] & DFP_TYPEC_MASK) {
case DFP_RA_RA_BIT:
return POWER_SUPPLY_TYPEC_SINK_AUDIO_ADAPTER;
case DFP_RD_RD_BIT:
return POWER_SUPPLY_TYPEC_SINK_DEBUG_ACCESSORY;
case DFP_RD_RA_VCONN_BIT:
return POWER_SUPPLY_TYPEC_SINK_POWERED_CABLE;
case DFP_RD_OPEN_BIT:
return POWER_SUPPLY_TYPEC_SINK;
case DFP_RA_OPEN_BIT:
return POWER_SUPPLY_TYPEC_POWERED_CABLE_ONLY;
default:
break;
}
return POWER_SUPPLY_TYPEC_NONE;
}
int smblib_get_prop_typec_mode(struct smb_charger *chg,
union power_supply_propval *val)
{
if (!(chg->typec_status[3] & TYPEC_DEBOUNCE_DONE_STATUS_BIT)) {
val->intval = POWER_SUPPLY_TYPEC_NONE;
return 0;
}
if (chg->typec_status[3] & UFP_DFP_MODE_STATUS_BIT)
val->intval = smblib_get_prop_dfp_mode(chg);
else
val->intval = smblib_get_prop_ufp_mode(chg);
return 0;
}
int smblib_get_prop_typec_power_role(struct smb_charger *chg,
union power_supply_propval *val)
{
int rc = 0;
u8 ctrl;
rc = smblib_read(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG, &ctrl);
if (rc < 0) {
smblib_err(chg, "Couldn't read TYPE_C_INTRPT_ENB_SOFTWARE_CTRL rc=%d\n",
rc);
return rc;
}
smblib_dbg(chg, PR_REGISTER, "TYPE_C_INTRPT_ENB_SOFTWARE_CTRL = 0x%02x\n",
ctrl);
if (ctrl & TYPEC_DISABLE_CMD_BIT) {
val->intval = POWER_SUPPLY_TYPEC_PR_NONE;
return rc;
}
switch (ctrl & (DFP_EN_CMD_BIT | UFP_EN_CMD_BIT)) {
case 0:
val->intval = POWER_SUPPLY_TYPEC_PR_DUAL;
break;
case DFP_EN_CMD_BIT:
val->intval = POWER_SUPPLY_TYPEC_PR_SOURCE;
break;
case UFP_EN_CMD_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 & (DFP_EN_CMD_BIT | UFP_EN_CMD_BIT));
return -EINVAL;
}
return rc;
}
int smblib_get_prop_pd_allowed(struct smb_charger *chg,
union power_supply_propval *val)
{
val->intval = get_effective_result(chg->pd_allowed_votable);
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);
}
#define HVDCP3_STEP_UV 200000
int smblib_get_prop_input_voltage_settled(struct smb_charger *chg,
union power_supply_propval *val)
{
const struct apsd_result *apsd_result = smblib_get_apsd_result(chg);
int rc, pulses;
u8 stat;
val->intval = MICRO_5V;
if (apsd_result == NULL) {
smblib_err(chg, "APSD result is NULL\n");
return 0;
}
switch (apsd_result->pst) {
case POWER_SUPPLY_TYPE_USB_HVDCP_3:
rc = smblib_read(chg, QC_PULSE_COUNT_STATUS_REG, &stat);
if (rc < 0) {
smblib_err(chg,
"Couldn't read QC_PULSE_COUNT rc=%d\n", rc);
return 0;
}
pulses = (stat & QC_PULSE_COUNT_MASK);
val->intval = MICRO_5V + HVDCP3_STEP_UV * pulses;
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)
{
/*
* hvdcp timeout voter is the last one to allow pd. Use its vote
* to indicate start of pe engine
*/
val->intval
= !get_client_vote_locked(chg->pd_disallowed_votable_indirect,
HVDCP_TIMEOUT_VOTER);
return 0;
}
int smblib_get_prop_die_health(struct smb_charger *chg,
union power_supply_propval *val)
{
int rc;
u8 stat;
rc = smblib_read(chg, TEMP_RANGE_STATUS_REG, &stat);
if (rc < 0) {
smblib_err(chg, "Couldn't read TEMP_RANGE_STATUS_REG rc=%d\n",
rc);
return rc;
}
/* TEMP_RANGE bits are mutually exclusive */
switch (stat & TEMP_RANGE_MASK) {
case TEMP_BELOW_RANGE_BIT:
val->intval = POWER_SUPPLY_HEALTH_COOL;
break;
case TEMP_WITHIN_RANGE_BIT:
val->intval = POWER_SUPPLY_HEALTH_WARM;
break;
case TEMP_ABOVE_RANGE_BIT:
val->intval = POWER_SUPPLY_HEALTH_HOT;
break;
case ALERT_LEVEL_BIT:
val->intval = POWER_SUPPLY_HEALTH_OVERHEAT;
break;
default:
val->intval = POWER_SUPPLY_HEALTH_UNKNOWN;
}
return 0;
}
/*******************
* USB PSY SETTERS *
* *****************/
int smblib_set_prop_pd_current_max(struct smb_charger *chg,
const union power_supply_propval *val)
{
int rc;
if (chg->pd_active)
rc = vote(chg->usb_icl_votable, PD_VOTER, true, val->intval);
else
rc = -EPERM;
return rc;
}
int smblib_set_prop_usb_current_max(struct smb_charger *chg,
const union power_supply_propval *val)
{
int rc = 0;
if (!chg->pd_active) {
rc = vote(chg->usb_icl_votable, USB_PSY_VOTER,
true, 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);
}
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_boost,
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_typec_power_role(struct smb_charger *chg,
const union power_supply_propval *val)
{
int rc = 0;
u8 power_role;
switch (val->intval) {
case POWER_SUPPLY_TYPEC_PR_NONE:
power_role = TYPEC_DISABLE_CMD_BIT;
break;
case POWER_SUPPLY_TYPEC_PR_DUAL:
power_role = 0;
break;
case POWER_SUPPLY_TYPEC_PR_SINK:
power_role = UFP_EN_CMD_BIT;
break;
case POWER_SUPPLY_TYPEC_PR_SOURCE:
power_role = DFP_EN_CMD_BIT;
break;
default:
smblib_err(chg, "power role %d not supported\n", val->intval);
return -EINVAL;
}
if (power_role == UFP_EN_CMD_BIT) {
/* disable PBS workaround when forcing sink mode */
rc = smblib_write(chg, TM_IO_DTEST4_SEL, 0x0);
if (rc < 0) {
smblib_err(chg, "Couldn't write to TM_IO_DTEST4_SEL rc=%d\n",
rc);
}
} else {
/* restore it back to 0xA5 */
rc = smblib_write(chg, TM_IO_DTEST4_SEL, 0xA5);
if (rc < 0) {
smblib_err(chg, "Couldn't write to TM_IO_DTEST4_SEL rc=%d\n",
rc);
}
}
rc = smblib_masked_write(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG,
TYPEC_POWER_ROLE_CMD_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);
return rc;
}
return rc;
}
int smblib_set_prop_usb_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);
rc = smblib_set_usb_pd_allowed_voltage(chg, min_uv,
chg->voltage_max_uv);
if (rc < 0) {
smblib_err(chg, "invalid max voltage %duV rc=%d\n",
val->intval, rc);
return rc;
}
chg->voltage_min_uv = min_uv;
return rc;
}
int smblib_set_prop_usb_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);
rc = smblib_set_usb_pd_allowed_voltage(chg, chg->voltage_min_uv,
max_uv);
if (rc < 0) {
smblib_err(chg, "invalid min voltage %duV rc=%d\n",
val->intval, rc);
return rc;
}
chg->voltage_max_uv = max_uv;
return rc;
}
int smblib_set_prop_pd_active(struct smb_charger *chg,
const union power_supply_propval *val)
{
int rc;
bool orientation, cc_debounced, sink_attached, hvdcp;
u8 stat;
if (!get_effective_result(chg->pd_allowed_votable))
return -EINVAL;
rc = smblib_read(chg, APSD_STATUS_REG, &stat);
if (rc < 0) {
smblib_err(chg, "Couldn't read APSD status rc=%d\n", rc);
return rc;
}
cc_debounced = (bool)
(chg->typec_status[3] & TYPEC_DEBOUNCE_DONE_STATUS_BIT);
sink_attached = (bool)
(chg->typec_status[3] & UFP_DFP_MODE_STATUS_BIT);
hvdcp = stat & QC_CHARGER_BIT;
chg->pd_active = val->intval;
if (chg->pd_active) {
vote(chg->apsd_disable_votable, PD_VOTER, true, 0);
vote(chg->pd_allowed_votable, PD_VOTER, true, 0);
vote(chg->usb_irq_enable_votable, PD_VOTER, true, 0);
/*
* VCONN_EN_ORIENTATION_BIT controls whether to use CC1 or CC2
* line when TYPEC_SPARE_CFG_BIT (CC pin selection s/w override)
* is set or when VCONN_EN_VALUE_BIT is set.
*/
orientation = chg->typec_status[3] & CC_ORIENTATION_BIT;
rc = smblib_masked_write(chg,
TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG,
VCONN_EN_ORIENTATION_BIT,
orientation ? 0 : VCONN_EN_ORIENTATION_BIT);
if (rc < 0)
smblib_err(chg,
"Couldn't enable vconn on CC line rc=%d\n", rc);
/* SW controlled CC_OUT */
rc = smblib_masked_write(chg, TAPER_TIMER_SEL_CFG_REG,
TYPEC_SPARE_CFG_BIT, TYPEC_SPARE_CFG_BIT);
if (rc < 0)
smblib_err(chg, "Couldn't enable SW cc_out rc=%d\n",
rc);
/*
* Enforce 500mA for PD until the real vote comes in later.
* It is guaranteed that pd_active is set prior to
* pd_current_max
*/
rc = vote(chg->usb_icl_votable, PD_VOTER, true, USBIN_500MA);
if (rc < 0)
smblib_err(chg, "Couldn't vote for USB ICL rc=%d\n",
rc);
/* since PD was found the cable must be non-legacy */
vote(chg->usb_icl_votable, LEGACY_UNKNOWN_VOTER, false, 0);
/* 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);
/* remove USB_PSY_VOTER */
rc = vote(chg->usb_icl_votable, USB_PSY_VOTER, false, 0);
if (rc < 0)
smblib_err(chg, "Couldn't unvote USB_PSY rc=%d\n", rc);
} else {
vote(chg->apsd_disable_votable, PD_VOTER, false, 0);
vote(chg->pd_allowed_votable, PD_VOTER, true, 0);
vote(chg->usb_irq_enable_votable, PD_VOTER, true, 0);
vote(chg->hvdcp_disable_votable_indirect, PD_INACTIVE_VOTER,
false, 0);
/* HW controlled CC_OUT */
rc = smblib_masked_write(chg, TAPER_TIMER_SEL_CFG_REG,
TYPEC_SPARE_CFG_BIT, 0);
if (rc < 0)
smblib_err(chg, "Couldn't enable HW cc_out rc=%d\n",
rc);
/*
* This WA should only run for HVDCP. Non-legacy SDP/CDP could
* draw more, but this WA will remove Rd causing VBUS to drop,
* and data could be interrupted. Non-legacy DCP could also draw
* more, but it may impact compliance.
*/
if (!chg->typec_legacy_valid && cc_debounced &&
!sink_attached && hvdcp)
schedule_work(&chg->legacy_detection_work);
}
smblib_update_usb_type(chg);
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_reg_block_update(struct smb_charger *chg,
struct reg_info *entry)
{
int rc = 0;
while (entry && entry->reg) {
rc = smblib_read(chg, entry->reg, &entry->bak);
if (rc < 0) {
dev_err(chg->dev, "Error in reading %s rc=%d\n",
entry->desc, rc);
break;
}
entry->bak &= entry->mask;
rc = smblib_masked_write(chg, entry->reg,
entry->mask, entry->val);
if (rc < 0) {
dev_err(chg->dev, "Error in writing %s rc=%d\n",
entry->desc, rc);
break;
}
entry++;
}
return rc;
}
int smblib_reg_block_restore(struct smb_charger *chg,
struct reg_info *entry)
{
int rc = 0;
while (entry && entry->reg) {
rc = smblib_masked_write(chg, entry->reg,
entry->mask, entry->bak);
if (rc < 0) {
dev_err(chg->dev, "Error in writing %s rc=%d\n",
entry->desc, rc);
break;
}
entry++;
}
return rc;
}
static struct reg_info cc2_detach_settings[] = {
{
.reg = TYPE_C_CFG_2_REG,
.mask = TYPE_C_UFP_MODE_BIT | EN_TRY_SOURCE_MODE_BIT,
.val = TYPE_C_UFP_MODE_BIT,
.desc = "TYPE_C_CFG_2_REG",
},
{
.reg = TYPE_C_CFG_3_REG,
.mask = EN_TRYSINK_MODE_BIT,
.val = 0,
.desc = "TYPE_C_CFG_3_REG",
},
{
.reg = TAPER_TIMER_SEL_CFG_REG,
.mask = TYPEC_SPARE_CFG_BIT,
.val = TYPEC_SPARE_CFG_BIT,
.desc = "TAPER_TIMER_SEL_CFG_REG",
},
{
.reg = TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG,
.mask = VCONN_EN_ORIENTATION_BIT,
.val = 0,
.desc = "TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG",
},
{
.reg = MISC_CFG_REG,
.mask = TCC_DEBOUNCE_20MS_BIT,
.val = TCC_DEBOUNCE_20MS_BIT,
.desc = "Tccdebounce time"
},
{
},
};
static int smblib_cc2_sink_removal_enter(struct smb_charger *chg)
{
int rc, ccout, ufp_mode;
u8 stat;
if ((chg->wa_flags & TYPEC_CC2_REMOVAL_WA_BIT) == 0)
return 0;
if (chg->cc2_detach_wa_active)
return 0;
rc = smblib_read(chg, TYPE_C_STATUS_4_REG, &stat);
if (rc < 0) {
smblib_err(chg, "Couldn't read TYPE_C_STATUS_4 rc=%d\n", rc);
return rc;
}
ccout = (stat & CC_ATTACHED_BIT) ?
(!!(stat & CC_ORIENTATION_BIT) + 1) : 0;
ufp_mode = (stat & TYPEC_DEBOUNCE_DONE_STATUS_BIT) ?
!(stat & UFP_DFP_MODE_STATUS_BIT) : 0;
if (ccout != 2)
return 0;
if (!ufp_mode)
return 0;
chg->cc2_detach_wa_active = true;
/* The CC2 removal WA will cause a type-c-change IRQ storm */
smblib_reg_block_update(chg, cc2_detach_settings);
schedule_work(&chg->rdstd_cc2_detach_work);
return rc;
}
static int smblib_cc2_sink_removal_exit(struct smb_charger *chg)
{
if ((chg->wa_flags & TYPEC_CC2_REMOVAL_WA_BIT) == 0)
return 0;
if (!chg->cc2_detach_wa_active)
return 0;
chg->cc2_detach_wa_active = false;
cancel_work_sync(&chg->rdstd_cc2_detach_work);
smblib_reg_block_restore(chg, cc2_detach_settings);
return 0;
}
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_INTRPT_ENB_SOFTWARE_CTRL_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);
vote(chg->apsd_disable_votable, PD_HARD_RESET_VOTER,
chg->pd_hard_reset, 0);
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, step_cc_delta_ua, hw_cc_delta_ua = 0;
rc = smblib_get_step_cc_delta(chg, &step_cc_delta_ua);
if (rc < 0) {
smblib_err(chg, "Couldn't get step cc delta rc=%d\n", rc);
step_cc_delta_ua = 0;
} else {
hw_cc_delta_ua = step_cc_delta_ua;
}
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;
} else if (jeita_cc_delta_ua < 0) {
/* HW will take the min between JEITA and step charge */
hw_cc_delta_ua = min(hw_cc_delta_ua, jeita_cc_delta_ua);
}
val->intval = hw_cc_delta_ua;
return 0;
}
/************************
* USB MAIN PSY SETTERS *
************************/
#define SDP_CURRENT_MA 500000
#define CDP_CURRENT_MA 1500000
#define DCP_CURRENT_MA 1500000
#define HVDCP_CURRENT_MA 3000000
#define TYPEC_DEFAULT_CURRENT_MA 900000
#define TYPEC_MEDIUM_CURRENT_MA 1500000
#define TYPEC_HIGH_CURRENT_MA 3000000
int smblib_get_charge_current(struct smb_charger *chg,
int *total_current_ua)
{
const struct apsd_result *apsd_result = smblib_update_usb_type(chg);
union power_supply_propval val = {0, };
int rc = 0, typec_source_rd, current_ua;
bool non_compliant;
u8 stat5;
if (chg->pd_active) {
*total_current_ua =
get_client_vote_locked(chg->usb_icl_votable, PD_VOTER);
return rc;
}
rc = smblib_read(chg, TYPE_C_STATUS_5_REG, &stat5);
if (rc < 0) {
smblib_err(chg, "Couldn't read TYPE_C_STATUS_5 rc=%d\n", rc);
return rc;
}
non_compliant = stat5 & 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_MA;
return 0;
}
if (non_compliant) {
switch (apsd_result->bit) {
case CDP_CHARGER_BIT:
current_ua = CDP_CURRENT_MA;
break;
case DCP_CHARGER_BIT:
case OCP_CHARGER_BIT:
case FLOAT_CHARGER_BIT:
current_ua = DCP_CURRENT_MA;
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_MA;
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_MA;
break;
case POWER_SUPPLY_TYPEC_SOURCE_HIGH:
current_ua = TYPEC_HIGH_CURRENT_MA;
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;
}
/************************
* PARALLEL PSY GETTERS *
************************/
int smblib_get_prop_slave_current_now(struct smb_charger *chg,
union power_supply_propval *pval)
{
if (IS_ERR_OR_NULL(chg->iio.batt_i_chan))
chg->iio.batt_i_chan = iio_channel_get(chg->dev, "batt_i");
if (IS_ERR(chg->iio.batt_i_chan))
return PTR_ERR(chg->iio.batt_i_chan);
return iio_read_channel_processed(chg->iio.batt_i_chan, &pval->intval);
}
/**********************
* INTERRUPT HANDLERS *
**********************/
irqreturn_t smblib_handle_debug(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 smblib_handle_otg_overcurrent(int irq, void *data)
{
struct smb_irq_data *irq_data = data;
struct smb_charger *chg = irq_data->parent_data;
int rc;
u8 stat;
rc = smblib_read(chg, OTG_BASE + INT_RT_STS_OFFSET, &stat);
if (rc < 0) {
dev_err(chg->dev, "Couldn't read OTG_INT_RT_STS rc=%d\n", rc);
return IRQ_HANDLED;
}
if (chg->wa_flags & OTG_WA) {
if (stat & OTG_OC_DIS_SW_STS_RT_STS_BIT)
smblib_err(chg, "OTG disabled by hw\n");
/* not handling software based hiccups for PM660 */
return IRQ_HANDLED;
}
if (stat & OTG_OVERCURRENT_RT_STS_BIT)
schedule_work(&chg->otg_oc_work);
return IRQ_HANDLED;
}
irqreturn_t smblib_handle_chg_state_change(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;
power_supply_changed(chg->batt_psy);
return IRQ_HANDLED;
}
irqreturn_t smblib_handle_step_chg_state_change(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->step_chg_enabled)
rerun_election(chg->fcc_votable);
return IRQ_HANDLED;
}
irqreturn_t smblib_handle_step_chg_soc_update_fail(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->step_chg_enabled)
rerun_election(chg->fcc_votable);
return IRQ_HANDLED;
}
#define STEP_SOC_REQ_MS 3000
irqreturn_t smblib_handle_step_chg_soc_update_request(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 pval = {0, };
smblib_dbg(chg, PR_INTERRUPT, "IRQ: %s\n", irq_data->name);
if (!chg->bms_psy) {
schedule_delayed_work(&chg->step_soc_req_work,
msecs_to_jiffies(STEP_SOC_REQ_MS));
return IRQ_HANDLED;
}
rc = smblib_get_prop_batt_capacity(chg, &pval);
if (rc < 0)
smblib_err(chg, "Couldn't get batt capacity rc=%d\n", rc);
else
step_charge_soc_update(chg, pval.intval);
return IRQ_HANDLED;
}
irqreturn_t smblib_handle_batt_temp_changed(int irq, void *data)
{
struct smb_irq_data *irq_data = data;
struct smb_charger *chg = irq_data->parent_data;
rerun_election(chg->fcc_votable);
power_supply_changed(chg->batt_psy);
return IRQ_HANDLED;
}
irqreturn_t smblib_handle_batt_psy_changed(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;
}
irqreturn_t smblib_handle_usb_psy_changed(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->usb_psy);
return IRQ_HANDLED;
}
irqreturn_t smblib_handle_usbin_uv(int irq, void *data)
{
struct smb_irq_data *irq_data = data;
struct smb_charger *chg = irq_data->parent_data;
struct storm_watch *wdata;
smblib_dbg(chg, PR_INTERRUPT, "IRQ: %s\n", irq_data->name);
if (!chg->irq_info[SWITCH_POWER_OK_IRQ].irq_data)
return IRQ_HANDLED;
wdata = &chg->irq_info[SWITCH_POWER_OK_IRQ].irq_data->storm_data;
reset_storm_count(wdata);
return IRQ_HANDLED;
}
static void smblib_micro_usb_plugin(struct smb_charger *chg, bool vbus_rising)
{
if (vbus_rising) {
/* use the typec flag even though its not typec */
chg->typec_present = 1;
} else {
chg->typec_present = 0;
smblib_update_usb_type(chg);
extcon_set_cable_state_(chg->extcon, EXTCON_USB, false);
smblib_uusb_removal(chg);
}
}
static void smblib_typec_usb_plugin(struct smb_charger *chg, bool vbus_rising)
{
if (vbus_rising)
smblib_cc2_sink_removal_exit(chg);
else
smblib_cc2_sink_removal_enter(chg);
}
#define PL_DELAY_MS 30000
void smblib_usb_plugin_locked(struct smb_charger *chg)
{
int rc;
u8 stat;
bool vbus_rising;
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_freq_buck(chg, vbus_rising ? chg->chg_freq.freq_5V :
chg->chg_freq.freq_removal);
/* 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)) {
smblib_err(chg, "Couldn't get dpdm regulator rc=%ld\n",
PTR_ERR(chg->dpdm_reg));
chg->dpdm_reg = NULL;
}
}
if (vbus_rising) {
if (chg->dpdm_reg && !regulator_is_enabled(chg->dpdm_reg)) {
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);
}
/* 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));
} else {
if (chg->wa_flags & BOOST_BACK_WA)
vote(chg->usb_icl_votable, BOOST_BACK_VOTER, false, 0);
if (chg->dpdm_reg && regulator_is_enabled(chg->dpdm_reg)) {
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);
}
}
if (chg->micro_usb_mode)
smblib_micro_usb_plugin(chg, vbus_rising);
else
smblib_typec_usb_plugin(chg, vbus_rising);
power_supply_changed(chg->usb_psy);
smblib_dbg(chg, PR_INTERRUPT, "IRQ: usbin-plugin %s\n",
vbus_rising ? "attached" : "detached");
}
irqreturn_t smblib_handle_usb_plugin(int irq, void *data)
{
struct smb_irq_data *irq_data = data;
struct smb_charger *chg = irq_data->parent_data;
mutex_lock(&chg->lock);
smblib_usb_plugin_locked(chg);
mutex_unlock(&chg->lock);
return IRQ_HANDLED;
}
#define USB_WEAK_INPUT_UA 1400000
#define ICL_CHANGE_DELAY_MS 1000
irqreturn_t smblib_handle_icl_change(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) {
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))
|| (stat & AICL_DONE_BIT))
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 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 QC3_PULSES_FOR_6V 5
#define QC3_PULSES_FOR_9V 20
#define QC3_PULSES_FOR_12V 35
static void smblib_hvdcp_adaptive_voltage_change(struct smb_charger *chg)
{
int rc;
u8 stat;
int pulses;
power_supply_changed(chg->usb_main_psy);
if (chg->usb_psy_desc.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;
}
switch (stat & QC_2P0_STATUS_MASK) {
case QC_5V_BIT:
smblib_set_opt_freq_buck(chg,
chg->chg_freq.freq_5V);
break;
case QC_9V_BIT:
smblib_set_opt_freq_buck(chg,
chg->chg_freq.freq_9V);
break;
case QC_12V_BIT:
smblib_set_opt_freq_buck(chg,
chg->chg_freq.freq_12V);
break;
default:
smblib_set_opt_freq_buck(chg,
chg->chg_freq.freq_removal);
break;
}
}
if (chg->usb_psy_desc.type == POWER_SUPPLY_TYPE_USB_HVDCP_3) {
rc = smblib_read(chg, QC_PULSE_COUNT_STATUS_REG, &stat);
if (rc < 0) {
smblib_err(chg,
"Couldn't read QC_PULSE_COUNT rc=%d\n", rc);
return;
}
pulses = (stat & QC_PULSE_COUNT_MASK);
if (pulses < QC3_PULSES_FOR_6V)
smblib_set_opt_freq_buck(chg,
chg->chg_freq.freq_5V);
else if (pulses < QC3_PULSES_FOR_9V)
smblib_set_opt_freq_buck(chg,
chg->chg_freq.freq_6V_8V);
else if (pulses < QC3_PULSES_FOR_12V)
smblib_set_opt_freq_buck(chg,
chg->chg_freq.freq_9V);
else
smblib_set_opt_freq_buck(chg,
chg->chg_freq.freq_12V);
}
}
/* 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->wa_flags & QC_AUTH_INTERRUPT_WA_BIT) {
/*
* Disable AUTH_IRQ_EN_CFG_BIT to receive adapter voltage
* change interrupt.
*/
rc = smblib_masked_write(chg,
USBIN_SOURCE_CHANGE_INTRPT_ENB_REG,
AUTH_IRQ_EN_CFG_BIT, 0);
if (rc < 0)
smblib_err(chg,
"Couldn't enable QC auth setting rc=%d\n", rc);
}
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 (get_effective_result(chg->hvdcp_hw_inov_dis_votable)) {
if (apsd_result->pst == POWER_SUPPLY_TYPE_USB_HVDCP) {
/* force HVDCP2 to 9V if INOV is disabled */
rc = smblib_masked_write(chg, CMD_HVDCP_2_REG,
FORCE_9V_BIT, FORCE_9V_BIT);
if (rc < 0)
smblib_err(chg,
"Couldn't force 9V HVDCP rc=%d\n", rc);
}
}
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)
{
const struct apsd_result *apsd_result = smblib_update_usb_type(chg);
/* Hold off PD only until hvdcp 2.0 detection timeout */
if (rising) {
vote(chg->pd_disallowed_votable_indirect, HVDCP_TIMEOUT_VOTER,
false, 0);
/* enable HDC and ICL irq for QC2/3 charger */
if (qc_charger)
vote(chg->usb_irq_enable_votable, QC_VOTER, true, 0);
/*
* HVDCP detection timeout done
* If adapter is not QC2.0/QC3.0 - it is a plain old DCP.
*/
if (!qc_charger && (apsd_result->bit & DCP_CHARGER_BIT))
/* 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: smblib_handle_hvdcp_check_timeout %s\n",
rising ? "rising" : "falling");
}
/* triggers when HVDCP is detected */
static void smblib_handle_hvdcp_detect_done(struct smb_charger *chg,
bool rising)
{
if (!rising)
return;
/* the APSD done handler will set the USB supply type */
cancel_delayed_work_sync(&chg->hvdcp_detect_work);
smblib_dbg(chg, PR_INTERRUPT, "IRQ: hvdcp-detect-done %s\n",
rising ? "rising" : "falling");
}
static void smblib_force_legacy_icl(struct smb_charger *chg, int pst)
{
/* while PD is active it should have complete ICL control */
if (chg->pd_active)
return;
switch (pst) {
case POWER_SUPPLY_TYPE_USB:
/*
* USB_PSY will vote to increase the current to 500/900mA once
* enumeration is done. Ensure that USB_PSY has at least voted
* for 100mA before releasing the LEGACY_UNKNOWN vote
*/
if (!is_client_vote_enabled(chg->usb_icl_votable,
USB_PSY_VOTER))
vote(chg->usb_icl_votable, USB_PSY_VOTER, true, 100000);
vote(chg->usb_icl_votable, LEGACY_UNKNOWN_VOTER, false, 0);
break;
case POWER_SUPPLY_TYPE_USB_CDP:
vote(chg->usb_icl_votable, LEGACY_UNKNOWN_VOTER, true, 1500000);
break;
case POWER_SUPPLY_TYPE_USB_DCP:
vote(chg->usb_icl_votable, LEGACY_UNKNOWN_VOTER, true, 1500000);
break;
case POWER_SUPPLY_TYPE_USB_HVDCP:
case POWER_SUPPLY_TYPE_USB_HVDCP_3:
vote(chg->usb_icl_votable, LEGACY_UNKNOWN_VOTER, true, 3000000);
break;
default:
smblib_err(chg, "Unknown APSD %d; forcing 500mA\n", pst);
vote(chg->usb_icl_votable, LEGACY_UNKNOWN_VOTER, true, 500000);
break;
}
}
#define HVDCP_DET_MS 2500
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);
if (!chg->typec_legacy_valid)
smblib_force_legacy_icl(chg, apsd_result->pst);
switch (apsd_result->bit) {
case SDP_CHARGER_BIT:
case CDP_CHARGER_BIT:
if (chg->micro_usb_mode)
extcon_set_cable_state_(chg->extcon, EXTCON_USB,
true);
/* if not DCP then no hvdcp timeout happens. Enable pd here */
vote(chg->pd_disallowed_votable_indirect, HVDCP_TIMEOUT_VOTER,
false, 0);
break;
case OCP_CHARGER_BIT:
case FLOAT_CHARGER_BIT:
/* if not DCP then no hvdcp timeout happens, Enable pd here. */
vote(chg->pd_disallowed_votable_indirect, HVDCP_TIMEOUT_VOTER,
false, 0);
break;
case DCP_CHARGER_BIT:
if (chg->wa_flags & QC_CHARGER_DETECTION_WA_BIT)
schedule_delayed_work(&chg->hvdcp_detect_work,
msecs_to_jiffies(HVDCP_DET_MS));
break;
default:
break;
}
smblib_dbg(chg, PR_INTERRUPT, "IRQ: apsd-done rising; %s detected\n",
apsd_result->name);
}
irqreturn_t smblib_handle_usb_source_change(int irq, void *data)
{
struct smb_irq_data *irq_data = data;
struct smb_charger *chg = irq_data->parent_data;
int rc = 0;
u8 stat;
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_REGISTER, "APSD_STATUS = 0x%02x\n", stat);
if (chg->micro_usb_mode && (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(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_REGISTER, "APSD_STATUS = 0x%02x\n", stat);
return IRQ_HANDLED;
}
static void typec_sink_insertion(struct smb_charger *chg)
{
/* when a sink is inserted we should not wait on hvdcp timeout to
* enable pd
*/
vote(chg->pd_disallowed_votable_indirect, HVDCP_TIMEOUT_VOTER,
false, 0);
}
static void typec_sink_removal(struct smb_charger *chg)
{
smblib_set_charge_param(chg, &chg->param.freq_boost,
chg->chg_freq.freq_above_otg_threshold);
chg->boost_current_ua = 0;
}
static void smblib_handle_typec_removal(struct smb_charger *chg)
{
int rc;
chg->cc2_detach_wa_active = false;
/* reset APSD voters */
vote(chg->apsd_disable_votable, PD_HARD_RESET_VOTER, false, 0);
vote(chg->apsd_disable_votable, PD_VOTER, false, 0);
cancel_delayed_work_sync(&chg->pl_enable_work);
cancel_delayed_work_sync(&chg->hvdcp_detect_work);
/* reset input current limit voters */
vote(chg->usb_icl_votable, LEGACY_UNKNOWN_VOTER, true, 100000);
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, PL_USBIN_USBIN_VOTER, false, 0);
/* reset hvdcp voters */
vote(chg->hvdcp_disable_votable_indirect, VBUS_CC_SHORT_VOTER, true, 0);
vote(chg->hvdcp_disable_votable_indirect, PD_INACTIVE_VOTER, true, 0);
/* reset power delivery voters */
vote(chg->pd_allowed_votable, PD_VOTER, false, 0);
vote(chg->pd_disallowed_votable_indirect, CC_DETACHED_VOTER, true, 0);
vote(chg->pd_disallowed_votable_indirect, HVDCP_TIMEOUT_VOTER, true, 0);
/* reset usb irq voters */
vote(chg->usb_irq_enable_votable, PD_VOTER, false, 0);
vote(chg->usb_irq_enable_votable, QC_VOTER, false, 0);
/* reset parallel voters */
vote(chg->pl_disable_votable, PL_DELAY_VOTER, true, 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);
chg->vconn_attempts = 0;
chg->otg_attempts = 0;
chg->pulse_cnt = 0;
chg->usb_icl_delta_ua = 0;
chg->voltage_min_uv = MICRO_5V;
chg->voltage_max_uv = MICRO_5V;
chg->pd_active = 0;
chg->pd_hard_reset = 0;
chg->typec_legacy_valid = false;
/* enable APSD CC trigger for next insertion */
rc = smblib_masked_write(chg, TYPE_C_CFG_REG,
APSD_START_ON_CC_BIT, APSD_START_ON_CC_BIT);
if (rc < 0)
smblib_err(chg, "Couldn't enable APSD_START_ON_CC rc=%d\n", rc);
if (chg->wa_flags & QC_AUTH_INTERRUPT_WA_BIT) {
/* re-enable AUTH_IRQ_EN_CFG_BIT */
rc = smblib_masked_write(chg,
USBIN_SOURCE_CHANGE_INTRPT_ENB_REG,
AUTH_IRQ_EN_CFG_BIT, AUTH_IRQ_EN_CFG_BIT);
if (rc < 0)
smblib_err(chg,
"Couldn't enable QC auth setting rc=%d\n", rc);
}
/* reconfigure allowed voltage for HVDCP */
rc = smblib_set_adapter_allowance(chg,
USBIN_ADAPTER_ALLOW_5V_OR_9V_TO_12V);
if (rc < 0)
smblib_err(chg, "Couldn't set USBIN_ADAPTER_ALLOW_5V_OR_9V_TO_12V rc=%d\n",
rc);
/* enable DRP */
rc = smblib_masked_write(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG,
TYPEC_POWER_ROLE_CMD_MASK, 0);
if (rc < 0)
smblib_err(chg, "Couldn't enable DRP rc=%d\n", rc);
/* HW controlled CC_OUT */
rc = smblib_masked_write(chg, TAPER_TIMER_SEL_CFG_REG,
TYPEC_SPARE_CFG_BIT, 0);
if (rc < 0)
smblib_err(chg, "Couldn't enable HW cc_out rc=%d\n", rc);
/* restore crude sensor */
rc = smblib_write(chg, TM_IO_DTEST4_SEL, 0xA5);
if (rc < 0)
smblib_err(chg, "Couldn't restore crude sensor rc=%d\n", rc);
typec_sink_removal(chg);
smblib_update_usb_type(chg);
}
static void smblib_handle_typec_insertion(struct smb_charger *chg,
bool sink_attached)
{
int rc;
vote(chg->pd_disallowed_votable_indirect, CC_DETACHED_VOTER, false, 0);
/* disable APSD CC trigger since CC is attached */
rc = smblib_masked_write(chg, TYPE_C_CFG_REG, APSD_START_ON_CC_BIT, 0);
if (rc < 0)
smblib_err(chg, "Couldn't disable APSD_START_ON_CC rc=%d\n",
rc);
if (sink_attached)
typec_sink_insertion(chg);
else
typec_sink_removal(chg);
}
static void smblib_handle_typec_debounce_done(struct smb_charger *chg,
bool rising, bool sink_attached)
{
int rc;
union power_supply_propval pval = {0, };
if (rising) {
if (!chg->typec_present) {
chg->typec_present = true;
smblib_dbg(chg, PR_MISC, "TypeC insertion\n");
smblib_handle_typec_insertion(chg, sink_attached);
}
} else {
if (chg->typec_present) {
chg->typec_present = false;
smblib_dbg(chg, PR_MISC, "TypeC removal\n");
smblib_handle_typec_removal(chg);
}
}
rc = smblib_get_prop_typec_mode(chg, &pval);
if (rc < 0)
smblib_err(chg, "Couldn't get prop typec mode rc=%d\n", rc);
smblib_dbg(chg, PR_INTERRUPT, "IRQ: debounce-done %s; Type-C %s detected\n",
rising ? "rising" : "falling",
smblib_typec_mode_name[pval.intval]);
}
irqreturn_t smblib_handle_usb_typec_change_for_uusb(struct smb_charger *chg)
{
int rc;
u8 stat;
rc = smblib_read(chg, TYPE_C_STATUS_3_REG, &stat);
if (rc < 0) {
smblib_err(chg, "Couldn't read TYPE_C_STATUS_3 rc=%d\n", rc);
return IRQ_HANDLED;
}
smblib_dbg(chg, PR_REGISTER, "TYPE_C_STATUS_3 = 0x%02x OTG=%d\n",
stat, !!(stat & (U_USB_GND_NOVBUS_BIT | U_USB_GND_BIT)));
extcon_set_cable_state_(chg->extcon, EXTCON_USB_HOST,
!!(stat & (U_USB_GND_NOVBUS_BIT | U_USB_GND_BIT)));
power_supply_changed(chg->usb_psy);
return IRQ_HANDLED;
}
static void smblib_usb_typec_change(struct smb_charger *chg)
{
int rc;
bool debounce_done, sink_attached;
rc = smblib_multibyte_read(chg, TYPE_C_STATUS_1_REG,
chg->typec_status, 5);
if (rc < 0) {
smblib_err(chg, "Couldn't cache USB Type-C status rc=%d\n", rc);
return;
}
debounce_done =
(bool)(chg->typec_status[3] & TYPEC_DEBOUNCE_DONE_STATUS_BIT);
sink_attached =
(bool)(chg->typec_status[3] & UFP_DFP_MODE_STATUS_BIT);
smblib_handle_typec_debounce_done(chg, debounce_done, sink_attached);
if (chg->typec_status[3] & TYPEC_VBUS_ERROR_STATUS_BIT)
smblib_dbg(chg, PR_INTERRUPT, "IRQ: vbus-error\n");
if (chg->typec_status[3] & TYPEC_VCONN_OVERCURR_STATUS_BIT)
schedule_work(&chg->vconn_oc_work);
power_supply_changed(chg->usb_psy);
}
irqreturn_t smblib_handle_usb_typec_change(int irq, void *data)
{
struct smb_irq_data *irq_data = data;
struct smb_charger *chg = irq_data->parent_data;
if (chg->micro_usb_mode) {
smblib_handle_usb_typec_change_for_uusb(chg);
return IRQ_HANDLED;
}
if (chg->cc2_detach_wa_active || chg->typec_en_dis_active) {
smblib_dbg(chg, PR_INTERRUPT, "Ignoring since %s active\n",
chg->cc2_detach_wa_active ?
"cc2_detach_wa" : "typec_en_dis");
return IRQ_HANDLED;
}
mutex_lock(&chg->lock);
smblib_usb_typec_change(chg);
mutex_unlock(&chg->lock);
return IRQ_HANDLED;
}
irqreturn_t smblib_handle_dc_plugin(int irq, void *data)
{
struct smb_irq_data *irq_data = data;
struct smb_charger *chg = irq_data->parent_data;
power_supply_changed(chg->dc_psy);
return IRQ_HANDLED;
}
irqreturn_t smblib_handle_high_duty_cycle(int irq, void *data)
{
struct smb_irq_data *irq_data = data;
struct smb_charger *chg = irq_data->parent_data;
chg->is_hdc = true;
schedule_delayed_work(&chg->clear_hdc_work, msecs_to_jiffies(60));
return IRQ_HANDLED;
}
irqreturn_t smblib_handle_switcher_power_ok(int irq, void *data)
{
struct smb_irq_data *irq_data = data;
struct smb_charger *chg = irq_data->parent_data;
int rc, usb_icl;
u8 stat;
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;
}
/* skip suspending input if its already suspended by some other voter */
usb_icl = get_effective_result(chg->usb_icl_votable);
if ((stat & USE_USBIN_BIT) && usb_icl >= 0 && usb_icl < USBIN_25MA)
return IRQ_HANDLED;
if (stat & USE_DCIN_BIT)
return IRQ_HANDLED;
if (is_storming(&irq_data->storm_data)) {
smblib_err(chg, "Reverse boost detected: voting 0mA to suspend input\n");
vote(chg->usb_icl_votable, BOOST_BACK_VOTER, true, 0);
}
return IRQ_HANDLED;
}
irqreturn_t smblib_handle_wdog_bark(int irq, void *data)
{
struct smb_irq_data *irq_data = data;
struct smb_charger *chg = irq_data->parent_data;
int rc;
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;
}
/***************
* Work Queues *
***************/
static void smblib_hvdcp_detect_work(struct work_struct *work)
{
struct smb_charger *chg = container_of(work, struct smb_charger,
hvdcp_detect_work.work);
vote(chg->pd_disallowed_votable_indirect, HVDCP_TIMEOUT_VOTER,
false, 0);
power_supply_changed(chg->usb_psy);
}
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 step_soc_req_work(struct work_struct *work)
{
struct smb_charger *chg = container_of(work, struct smb_charger,
step_soc_req_work.work);
union power_supply_propval pval = {0, };
int rc;
rc = smblib_get_prop_batt_capacity(chg, &pval);
if (rc < 0) {
smblib_err(chg, "Couldn't get batt capacity rc=%d\n", rc);
return;
}
step_charge_soc_update(chg, pval.intval);
}
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 = 0;
}
static void rdstd_cc2_detach_work(struct work_struct *work)
{
int rc;
u8 stat4, stat5;
struct smb_charger *chg = container_of(work, struct smb_charger,
rdstd_cc2_detach_work);
if (!chg->cc2_detach_wa_active)
return;
/*
* WA steps -
* 1. Enable both UFP and DFP, wait for 10ms.
* 2. Disable DFP, wait for 30ms.
* 3. Removal detected if both TYPEC_DEBOUNCE_DONE_STATUS
* and TIMER_STAGE bits are gone, otherwise repeat all by
* work rescheduling.
* Note, work will be cancelled when USB_PLUGIN rises.
*/
rc = smblib_masked_write(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG,
UFP_EN_CMD_BIT | DFP_EN_CMD_BIT,
UFP_EN_CMD_BIT | DFP_EN_CMD_BIT);
if (rc < 0) {
smblib_err(chg, "Couldn't write TYPE_C_CTRL_REG rc=%d\n", rc);
return;
}
usleep_range(10000, 11000);
rc = smblib_masked_write(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG,
UFP_EN_CMD_BIT | DFP_EN_CMD_BIT,
UFP_EN_CMD_BIT);
if (rc < 0) {
smblib_err(chg, "Couldn't write TYPE_C_CTRL_REG rc=%d\n", rc);
return;
}
usleep_range(30000, 31000);
rc = smblib_read(chg, TYPE_C_STATUS_4_REG, &stat4);
if (rc < 0) {
smblib_err(chg, "Couldn't read TYPE_C_STATUS_4 rc=%d\n", rc);
return;
}
rc = smblib_read(chg, TYPE_C_STATUS_5_REG, &stat5);
if (rc < 0) {
smblib_err(chg,
"Couldn't read TYPE_C_STATUS_5_REG rc=%d\n", rc);
return;
}
if ((stat4 & TYPEC_DEBOUNCE_DONE_STATUS_BIT)
|| (stat5 & TIMER_STAGE_2_BIT)) {
smblib_dbg(chg, PR_MISC, "rerunning DD=%d TS2BIT=%d\n",
(int)(stat4 & TYPEC_DEBOUNCE_DONE_STATUS_BIT),
(int)(stat5 & TIMER_STAGE_2_BIT));
goto rerun;
}
smblib_dbg(chg, PR_MISC, "Bingo CC2 Removal detected\n");
chg->cc2_detach_wa_active = false;
rc = smblib_masked_write(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG,
EXIT_SNK_BASED_ON_CC_BIT, 0);
smblib_reg_block_restore(chg, cc2_detach_settings);
mutex_lock(&chg->lock);
smblib_usb_typec_change(chg);
mutex_unlock(&chg->lock);
return;
rerun:
schedule_work(&chg->rdstd_cc2_detach_work);
}
static void smblib_otg_oc_exit(struct smb_charger *chg, bool success)
{
int rc;
chg->otg_attempts = 0;
if (!success) {
smblib_err(chg, "OTG soft start failed\n");
chg->otg_en = false;
}
smblib_dbg(chg, PR_OTG, "enabling VBUS < 1V check\n");
rc = smblib_masked_write(chg, OTG_CFG_REG,
QUICKSTART_OTG_FASTROLESWAP_BIT, 0);
if (rc < 0)
smblib_err(chg, "Couldn't enable VBUS < 1V check rc=%d\n", rc);
if (!chg->external_vconn && chg->vconn_en) {
chg->vconn_attempts = 0;
if (success) {
rc = _smblib_vconn_regulator_enable(
chg->vconn_vreg->rdev);
if (rc < 0)
smblib_err(chg, "Couldn't enable VCONN rc=%d\n",
rc);
} else {
chg->vconn_en = false;
}
}
}
#define MAX_OC_FALLING_TRIES 10
static void smblib_otg_oc_work(struct work_struct *work)
{
struct smb_charger *chg = container_of(work, struct smb_charger,
otg_oc_work);
int rc, i;
u8 stat;
if (!chg->vbus_vreg || !chg->vbus_vreg->rdev)
return;
smblib_err(chg, "over-current detected on VBUS\n");
mutex_lock(&chg->otg_oc_lock);
if (!chg->otg_en)
goto unlock;
smblib_dbg(chg, PR_OTG, "disabling VBUS < 1V check\n");
smblib_masked_write(chg, OTG_CFG_REG,
QUICKSTART_OTG_FASTROLESWAP_BIT,
QUICKSTART_OTG_FASTROLESWAP_BIT);
/*
* If 500ms has passed and another over-current interrupt has not
* triggered then it is likely that the software based soft start was
* successful and the VBUS < 1V restriction should be re-enabled.
*/
schedule_delayed_work(&chg->otg_ss_done_work, msecs_to_jiffies(500));
rc = _smblib_vbus_regulator_disable(chg->vbus_vreg->rdev);
if (rc < 0) {
smblib_err(chg, "Couldn't disable VBUS rc=%d\n", rc);
goto unlock;
}
if (++chg->otg_attempts > OTG_MAX_ATTEMPTS) {
cancel_delayed_work_sync(&chg->otg_ss_done_work);
smblib_err(chg, "OTG failed to enable after %d attempts\n",
chg->otg_attempts - 1);
smblib_otg_oc_exit(chg, false);
goto unlock;
}
/*
* The real time status should go low within 10ms. Poll every 1-2ms to
* minimize the delay when re-enabling OTG.
*/
for (i = 0; i < MAX_OC_FALLING_TRIES; ++i) {
usleep_range(1000, 2000);
rc = smblib_read(chg, OTG_BASE + INT_RT_STS_OFFSET, &stat);
if (rc >= 0 && !(stat & OTG_OVERCURRENT_RT_STS_BIT))
break;
}
if (i >= MAX_OC_FALLING_TRIES) {
cancel_delayed_work_sync(&chg->otg_ss_done_work);
smblib_err(chg, "OTG OC did not fall after %dms\n",
2 * MAX_OC_FALLING_TRIES);
smblib_otg_oc_exit(chg, false);
goto unlock;
}
smblib_dbg(chg, PR_OTG, "OTG OC fell after %dms\n", 2 * i + 1);
rc = _smblib_vbus_regulator_enable(chg->vbus_vreg->rdev);
if (rc < 0) {
smblib_err(chg, "Couldn't enable VBUS rc=%d\n", rc);
goto unlock;
}
unlock:
mutex_unlock(&chg->otg_oc_lock);
}
static void smblib_vconn_oc_work(struct work_struct *work)
{
struct smb_charger *chg = container_of(work, struct smb_charger,
vconn_oc_work);
int rc, i;
u8 stat;
if (chg->micro_usb_mode)
return;
smblib_err(chg, "over-current detected on VCONN\n");
if (!chg->vconn_vreg || !chg->vconn_vreg->rdev)
return;
mutex_lock(&chg->otg_oc_lock);
rc = _smblib_vconn_regulator_disable(chg->vconn_vreg->rdev);
if (rc < 0) {
smblib_err(chg, "Couldn't disable VCONN rc=%d\n", rc);
goto unlock;
}
if (++chg->vconn_attempts > VCONN_MAX_ATTEMPTS) {
smblib_err(chg, "VCONN failed to enable after %d attempts\n",
chg->otg_attempts - 1);
chg->vconn_en = false;
chg->vconn_attempts = 0;
goto unlock;
}
/*
* The real time status should go low within 10ms. Poll every 1-2ms to
* minimize the delay when re-enabling OTG.
*/
for (i = 0; i < MAX_OC_FALLING_TRIES; ++i) {
usleep_range(1000, 2000);
rc = smblib_read(chg, TYPE_C_STATUS_4_REG, &stat);
if (rc >= 0 && !(stat & TYPEC_VCONN_OVERCURR_STATUS_BIT))
break;
}
if (i >= MAX_OC_FALLING_TRIES) {
smblib_err(chg, "VCONN OC did not fall after %dms\n",
2 * MAX_OC_FALLING_TRIES);
chg->vconn_en = false;
chg->vconn_attempts = 0;
goto unlock;
}
smblib_dbg(chg, PR_OTG, "VCONN OC fell after %dms\n", 2 * i + 1);
if (++chg->vconn_attempts > VCONN_MAX_ATTEMPTS) {
smblib_err(chg, "VCONN failed to enable after %d attempts\n",
chg->vconn_attempts - 1);
chg->vconn_en = false;
goto unlock;
}
rc = _smblib_vconn_regulator_enable(chg->vconn_vreg->rdev);
if (rc < 0) {
smblib_err(chg, "Couldn't enable VCONN rc=%d\n", rc);
goto unlock;
}
unlock:
mutex_unlock(&chg->otg_oc_lock);
}
static void smblib_otg_ss_done_work(struct work_struct *work)
{
struct smb_charger *chg = container_of(work, struct smb_charger,
otg_ss_done_work.work);
int rc;
bool success = false;
u8 stat;
mutex_lock(&chg->otg_oc_lock);
rc = smblib_read(chg, OTG_STATUS_REG, &stat);
if (rc < 0)
smblib_err(chg, "Couldn't read OTG status rc=%d\n", rc);
else if (stat & BOOST_SOFTSTART_DONE_BIT)
success = true;
smblib_otg_oc_exit(chg, success);
mutex_unlock(&chg->otg_oc_lock);
}
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);
vote(chg->pl_enable_votable_indirect, USBIN_I_VOTER,
settled_ua >= USB_WEAK_INPUT_UA, 0);
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_legacy_detection_work(struct work_struct *work)
{
struct smb_charger *chg = container_of(work, struct smb_charger,
legacy_detection_work);
int rc;
u8 stat;
bool legacy, rp_high;
mutex_lock(&chg->lock);
chg->typec_en_dis_active = 1;
smblib_dbg(chg, PR_MISC, "running legacy unknown workaround\n");
rc = smblib_masked_write(chg,
TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG,
TYPEC_DISABLE_CMD_BIT,
TYPEC_DISABLE_CMD_BIT);
if (rc < 0)
smblib_err(chg, "Couldn't disable type-c rc=%d\n", rc);
/* wait for the adapter to turn off VBUS */
msleep(500);
rc = smblib_masked_write(chg,
TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG,
TYPEC_DISABLE_CMD_BIT, 0);
if (rc < 0)
smblib_err(chg, "Couldn't enable type-c rc=%d\n", rc);
/* wait for type-c detection to complete */
msleep(100);
rc = smblib_read(chg, TYPE_C_STATUS_5_REG, &stat);
if (rc < 0) {
smblib_err(chg, "Couldn't read typec stat5 rc = %d\n", rc);
goto unlock;
}
chg->typec_legacy_valid = true;
vote(chg->usb_icl_votable, LEGACY_UNKNOWN_VOTER, false, 0);
legacy = stat & TYPEC_LEGACY_CABLE_STATUS_BIT;
rp_high = smblib_get_prop_ufp_mode(chg) ==
POWER_SUPPLY_TYPEC_SOURCE_HIGH;
if (!legacy || !rp_high)
vote(chg->hvdcp_disable_votable_indirect, VBUS_CC_SHORT_VOTER,
false, 0);
unlock:
chg->typec_en_dis_active = 0;
mutex_unlock(&chg->lock);
}
static int smblib_create_votables(struct smb_charger *chg)
{
int rc = 0;
chg->fcc_votable = find_votable("FCC");
if (!chg->fcc_votable) {
rc = -EPROBE_DEFER;
return rc;
}
chg->fv_votable = find_votable("FV");
if (!chg->fv_votable) {
rc = -EPROBE_DEFER;
return rc;
}
chg->usb_icl_votable = find_votable("USB_ICL");
if (!chg->usb_icl_votable) {
rc = -EPROBE_DEFER;
return rc;
}
chg->pl_disable_votable = find_votable("PL_DISABLE");
if (!chg->pl_disable_votable) {
rc = -EPROBE_DEFER;
return rc;
}
vote(chg->pl_disable_votable, PL_INDIRECT_VOTER, true, 0);
vote(chg->pl_disable_votable, PL_DELAY_VOTER, true, 0);
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);
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->pd_disallowed_votable_indirect
= create_votable("PD_DISALLOWED_INDIRECT", VOTE_SET_ANY,
smblib_pd_disallowed_votable_indirect_callback, chg);
if (IS_ERR(chg->pd_disallowed_votable_indirect)) {
rc = PTR_ERR(chg->pd_disallowed_votable_indirect);
return rc;
}
chg->pd_allowed_votable = create_votable("PD_ALLOWED",
VOTE_SET_ANY, NULL, NULL);
if (IS_ERR(chg->pd_allowed_votable)) {
rc = PTR_ERR(chg->pd_allowed_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);
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);
return rc;
}
chg->pl_enable_votable_indirect = create_votable("PL_ENABLE_INDIRECT",
VOTE_SET_ANY,
smblib_pl_enable_indirect_vote_callback,
chg);
if (IS_ERR(chg->pl_enable_votable_indirect)) {
rc = PTR_ERR(chg->pl_enable_votable_indirect);
return rc;
}
chg->hvdcp_disable_votable_indirect = create_votable(
"HVDCP_DISABLE_INDIRECT",
VOTE_SET_ANY,
smblib_hvdcp_disable_indirect_vote_callback,
chg);
if (IS_ERR(chg->hvdcp_disable_votable_indirect)) {
rc = PTR_ERR(chg->hvdcp_disable_votable_indirect);
return rc;
}
chg->hvdcp_enable_votable = create_votable("HVDCP_ENABLE",
VOTE_SET_ANY,
smblib_hvdcp_enable_vote_callback,
chg);
if (IS_ERR(chg->hvdcp_enable_votable)) {
rc = PTR_ERR(chg->hvdcp_enable_votable);
return rc;
}
chg->apsd_disable_votable = create_votable("APSD_DISABLE",
VOTE_SET_ANY,
smblib_apsd_disable_vote_callback,
chg);
if (IS_ERR(chg->apsd_disable_votable)) {
rc = PTR_ERR(chg->apsd_disable_votable);
return rc;
}
chg->hvdcp_hw_inov_dis_votable = create_votable("HVDCP_HW_INOV_DIS",
VOTE_SET_ANY,
smblib_hvdcp_hw_inov_dis_vote_callback,
chg);
if (IS_ERR(chg->hvdcp_hw_inov_dis_votable)) {
rc = PTR_ERR(chg->hvdcp_hw_inov_dis_votable);
return rc;
}
chg->usb_irq_enable_votable = create_votable("USB_IRQ_DISABLE",
VOTE_SET_ANY,
smblib_usb_irq_enable_vote_callback,
chg);
if (IS_ERR(chg->usb_irq_enable_votable)) {
rc = PTR_ERR(chg->usb_irq_enable_votable);
return rc;
}
chg->typec_irq_disable_votable = create_votable("TYPEC_IRQ_DISABLE",
VOTE_SET_ANY,
smblib_typec_irq_disable_vote_callback,
chg);
if (IS_ERR(chg->typec_irq_disable_votable)) {
rc = PTR_ERR(chg->typec_irq_disable_votable);
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->usb_icl_votable)
destroy_votable(chg->usb_icl_votable);
if (chg->dc_icl_votable)
destroy_votable(chg->dc_icl_votable);
if (chg->pd_disallowed_votable_indirect)
destroy_votable(chg->pd_disallowed_votable_indirect);
if (chg->pd_allowed_votable)
destroy_votable(chg->pd_allowed_votable);
if (chg->awake_votable)
destroy_votable(chg->awake_votable);
if (chg->chg_disable_votable)
destroy_votable(chg->chg_disable_votable);
if (chg->pl_enable_votable_indirect)
destroy_votable(chg->pl_enable_votable_indirect);
if (chg->apsd_disable_votable)
destroy_votable(chg->apsd_disable_votable);
if (chg->hvdcp_hw_inov_dis_votable)
destroy_votable(chg->hvdcp_hw_inov_dis_votable);
if (chg->typec_irq_disable_votable)
destroy_votable(chg->typec_irq_disable_votable);
}
static void smblib_iio_deinit(struct smb_charger *chg)
{
if (!IS_ERR_OR_NULL(chg->iio.temp_chan))
iio_channel_release(chg->iio.temp_chan);
if (!IS_ERR_OR_NULL(chg->iio.temp_max_chan))
iio_channel_release(chg->iio.temp_max_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.usbin_v_chan))
iio_channel_release(chg->iio.usbin_v_chan);
if (!IS_ERR_OR_NULL(chg->iio.batt_i_chan))
iio_channel_release(chg->iio.batt_i_chan);
}
int smblib_init(struct smb_charger *chg)
{
int rc = 0;
mutex_init(&chg->lock);
mutex_init(&chg->write_lock);
mutex_init(&chg->otg_oc_lock);
INIT_WORK(&chg->bms_update_work, bms_update_work);
INIT_WORK(&chg->rdstd_cc2_detach_work, rdstd_cc2_detach_work);
INIT_DELAYED_WORK(&chg->hvdcp_detect_work, smblib_hvdcp_detect_work);
INIT_DELAYED_WORK(&chg->step_soc_req_work, step_soc_req_work);
INIT_DELAYED_WORK(&chg->clear_hdc_work, clear_hdc_work);
INIT_WORK(&chg->otg_oc_work, smblib_otg_oc_work);
INIT_WORK(&chg->vconn_oc_work, smblib_vconn_oc_work);
INIT_DELAYED_WORK(&chg->otg_ss_done_work, smblib_otg_ss_done_work);
INIT_DELAYED_WORK(&chg->icl_change_work, smblib_icl_change_work);
INIT_DELAYED_WORK(&chg->pl_enable_work, smblib_pl_enable_work);
INIT_WORK(&chg->legacy_detection_work, smblib_legacy_detection_work);
chg->fake_capacity = -EINVAL;
switch (chg->mode) {
case PARALLEL_MASTER:
chg->qnovo_fcc_ua = -EINVAL;
chg->qnovo_fv_uv = -EINVAL;
rc = smblib_create_votables(chg);
if (rc < 0) {
smblib_err(chg, "Couldn't create votables 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;
}
chg->bms_psy = power_supply_get_by_name("bms");
chg->pl.psy = power_supply_get_by_name("parallel");
break;
case PARALLEL_SLAVE:
break;
default:
smblib_err(chg, "Unsupported mode %d\n", chg->mode);
return -EINVAL;
}
return rc;
}
int smblib_deinit(struct smb_charger *chg)
{
switch (chg->mode) {
case PARALLEL_MASTER:
power_supply_unreg_notifier(&chg->nb);
smblib_destroy_votables(chg);
break;
case PARALLEL_SLAVE:
break;
default:
smblib_err(chg, "Unsupported mode %d\n", chg->mode);
return -EINVAL;
}
smblib_iio_deinit(chg);
return 0;
}