blob: 353d7b1b6b378b491e34940b8e933ea6f93607e2 [file] [log] [blame]
/* Copyright (c) 2014 The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#define pr_fmt(fmt) "SMBCHG: %s: " fmt, __func__
#include <linux/spmi.h>
#include <linux/spinlock.h>
#include <linux/gpio.h>
#include <linux/errno.h>
#include <linux/delay.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/sched.h>
#include <linux/power_supply.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/bitops.h>
#include <linux/regulator/driver.h>
#include <linux/regulator/of_regulator.h>
#include <linux/regulator/machine.h>
#include <linux/spmi.h>
#include <linux/printk.h>
/* Mask/Bit helpers */
#define _SMB_MASK(BITS, POS) \
((unsigned char)(((1 << (BITS)) - 1) << (POS)))
#define SMB_MASK(LEFT_BIT_POS, RIGHT_BIT_POS) \
_SMB_MASK((LEFT_BIT_POS) - (RIGHT_BIT_POS) + 1, \
(RIGHT_BIT_POS))
/* Config registers */
struct smbchg_regulator {
struct regulator_desc rdesc;
struct regulator_dev *rdev;
};
struct parallel_usb_cfg {
struct power_supply *psy;
int min_current_thr_ma;
int current_max_ma;
bool avail;
};
struct smbchg_chip {
struct device *dev;
struct spmi_device *spmi;
/* peripheral register address bases */
u16 chgr_base;
u16 bat_if_base;
u16 usb_chgpth_base;
u16 dc_chgpth_base;
u16 otg_base;
u16 misc_base;
int fake_battery_soc;
/* configuration parameters */
int iterm_ma;
int usb_max_current_ma;
int dc_max_current_ma;
int usb_target_current_ma;
int dc_target_current_ma;
int vfloat_mv;
int resume_delta_mv;
int safety_time;
int prechg_safety_time;
int bmd_pin_src;
bool iterm_disabled;
bool bmd_algo_disabled;
bool soft_vfloat_comp_disabled;
bool chg_enabled;
bool low_icl_wa_on;
struct parallel_usb_cfg parallel;
/* status variables */
int usb_suspended;
int dc_suspended;
bool usb_online;
bool dc_present;
bool usb_present;
bool batt_present;
bool chg_done_batt_full;
bool otg_retries;
/* jeita and temperature */
bool batt_hot;
bool batt_cold;
bool batt_warm;
bool batt_cool;
unsigned int thermal_levels;
unsigned int therm_lvl_sel;
unsigned int *thermal_mitigation;
/* irqs */
int batt_hot_irq;
int batt_warm_irq;
int batt_cool_irq;
int batt_cold_irq;
int batt_missing_irq;
int vbat_low_irq;
int chg_hot_irq;
int chg_term_irq;
int taper_irq;
int recharge_irq;
int fastchg_irq;
int safety_timeout_irq;
int power_ok_irq;
int dcin_uv_irq;
int usbin_uv_irq;
int src_detect_irq;
int otg_fail_irq;
int otg_oc_irq;
int aicl_done_irq;
int usbid_change_irq;
int chg_inhibit_irq;
int chg_error_irq;
/* psy */
struct power_supply *usb_psy;
struct power_supply batt_psy;
struct power_supply dc_psy;
struct power_supply *bms_psy;
int dc_psy_type;
const char *bms_psy_name;
const char *battery_psy_name;
bool psy_registered;
struct smbchg_regulator otg_vreg;
struct work_struct usb_set_online_work;
spinlock_t sec_access_lock;
struct mutex current_change_lock;
struct mutex usb_set_online_lock;
struct mutex usb_en_lock;
struct mutex dc_en_lock;
};
enum print_reason {
PR_REGISTER = BIT(0),
PR_INTERRUPT = BIT(1),
PR_STATUS = BIT(2),
PR_DUMP = BIT(3),
};
static int smbchg_debug_mask;
module_param_named(
debug_mask, smbchg_debug_mask, int, S_IRUSR | S_IWUSR
);
#define pr_smb(reason, fmt, ...) \
do { \
if (smbchg_debug_mask & (reason)) \
pr_info(fmt, ##__VA_ARGS__); \
else \
pr_debug(fmt, ##__VA_ARGS__); \
} while (0)
static int smbchg_read(struct smbchg_chip *chip, u8 *val,
u16 addr, int count)
{
int rc = 0;
struct spmi_device *spmi = chip->spmi;
if (addr == 0) {
dev_err(chip->dev, "addr cannot be zero addr=0x%02x sid=0x%02x rc=%d\n",
addr, spmi->sid, rc);
return -EINVAL;
}
rc = spmi_ext_register_readl(spmi->ctrl, spmi->sid, addr, val, count);
if (rc) {
dev_err(chip->dev, "spmi read failed addr=0x%02x sid=0x%02x rc=%d\n",
addr, spmi->sid, rc);
return rc;
}
return 0;
}
/*
* Writes an arbitrary number of bytes to a specified register
*
* Do not use this function for register writes if possible. Instead use the
* smbchg_masked_write function.
*
* The sec_access_lock must be held for all register writes and this function
* does not do that. If this function is used, please hold the spinlock or
* random secure access writes may fail.
*/
static int smbchg_write(struct smbchg_chip *chip, u8 *val,
u16 addr, int count)
{
int rc = 0;
struct spmi_device *spmi = chip->spmi;
if (addr == 0) {
dev_err(chip->dev, "addr cannot be zero addr=0x%02x sid=0x%02x rc=%d\n",
addr, spmi->sid, rc);
return -EINVAL;
}
rc = spmi_ext_register_writel(spmi->ctrl, spmi->sid, addr, val, count);
if (rc) {
dev_err(chip->dev, "write failed addr=0x%02x sid=0x%02x rc=%d\n",
addr, spmi->sid, rc);
return rc;
}
return 0;
}
/*
* Writes a register to the specified by the base and limited by the bit mask
*
* Do not use this function for register writes if possible. Instead use the
* smbchg_masked_write function.
*
* The sec_access_lock must be held for all register writes and this function
* does not do that. If this function is used, please hold the spinlock or
* random secure access writes may fail.
*/
static int smbchg_masked_write_raw(struct smbchg_chip *chip, u16 base, u8 mask,
u8 val)
{
int rc;
u8 reg;
rc = smbchg_read(chip, &reg, base, 1);
if (rc) {
dev_err(chip->dev, "spmi read failed: addr=%03X, rc=%d\n",
base, rc);
return rc;
}
reg &= ~mask;
reg |= val & mask;
pr_smb(PR_REGISTER, "addr = 0x%x writing 0x%x\n", base, reg);
rc = smbchg_write(chip, &reg, base, 1);
if (rc) {
dev_err(chip->dev, "spmi write failed: addr=%03X, rc=%d\n",
base, rc);
return rc;
}
return 0;
}
/*
* Writes a register to the specified by the base and limited by the bit mask
*
* This function holds a spin lock to ensure secure access register writes goes
* through. If the secure access unlock register is armed, any old register
* write can unarm the secure access unlock, causing the next write to fail.
*
* Note: do not use this for sec_access registers. Instead use the function
* below: smbchg_sec_masked_write
*/
static int smbchg_masked_write(struct smbchg_chip *chip, u16 base, u8 mask,
u8 val)
{
unsigned long flags;
int rc;
spin_lock_irqsave(&chip->sec_access_lock, flags);
rc = smbchg_masked_write_raw(chip, base, mask, val);
spin_unlock_irqrestore(&chip->sec_access_lock, flags);
return rc;
}
/*
* Unlocks sec access and writes to the register specified.
*
* This function holds a spin lock to exclude other register writes while
* the two writes are taking place.
*/
#define SEC_ACCESS_OFFSET 0xD0
#define SEC_ACCESS_VALUE 0xA5
#define PERIPHERAL_MASK 0xFF
static int smbchg_sec_masked_write(struct smbchg_chip *chip, u16 base, u8 mask,
u8 val)
{
unsigned long flags;
int rc;
u16 peripheral_base = base & (~PERIPHERAL_MASK);
spin_lock_irqsave(&chip->sec_access_lock, flags);
rc = smbchg_masked_write_raw(chip, peripheral_base + SEC_ACCESS_OFFSET,
SEC_ACCESS_VALUE, SEC_ACCESS_VALUE);
if (rc) {
dev_err(chip->dev, "Unable to unlock sec_access: %d", rc);
goto out;
}
rc = smbchg_masked_write_raw(chip, base, mask, val);
out:
spin_unlock_irqrestore(&chip->sec_access_lock, flags);
return rc;
}
#define RID_STS 0xB
#define RID_MASK 0xF
static bool is_otg_present(struct smbchg_chip *chip)
{
int rc;
u8 reg;
rc = smbchg_read(chip, &reg, chip->usb_chgpth_base + RID_STS, 1);
if (rc < 0) {
dev_err(chip->dev,
"Couldn't read usb rid status rc = %d\n", rc);
return false;
}
pr_smb(PR_STATUS, "RID_STS = %02x\n", reg);
return (reg & RID_MASK) == 0;
}
#define USBIN_9V BIT(5)
#define USBIN_UNREG BIT(4)
#define USBIN_LV BIT(3)
#define DCIN_9V BIT(2)
#define DCIN_UNREG BIT(1)
#define DCIN_LV BIT(0)
#define INPUT_STS 0x0D
static bool is_dc_present(struct smbchg_chip *chip)
{
int rc;
u8 reg;
rc = smbchg_read(chip, &reg, chip->usb_chgpth_base + INPUT_STS, 1);
if (rc < 0) {
dev_err(chip->dev, "Couldn't read usb status rc = %d\n", rc);
return false;
}
return !!(reg & (DCIN_9V | DCIN_UNREG | DCIN_LV));
}
#define RT_STS 0x10
#define USBIN_UV_BIT 0x0
#define USBIN_OV_BIT 0x1
static bool is_usb_present(struct smbchg_chip *chip)
{
int rc;
u8 reg;
rc = smbchg_read(chip, &reg, chip->usb_chgpth_base + RT_STS, 1);
if (rc < 0) {
dev_err(chip->dev, "Couldn't read usb rt status rc = %d\n", rc);
return false;
}
if ((reg & USBIN_UV_BIT) || (reg & USBIN_OV_BIT))
return false;
rc = smbchg_read(chip, &reg, chip->usb_chgpth_base + INPUT_STS, 1);
if (rc < 0) {
dev_err(chip->dev, "Couldn't read usb status rc = %d\n", rc);
return false;
}
return !!(reg & (USBIN_9V | USBIN_UNREG | USBIN_LV));
}
static char *usb_type_str[] = {
"ACA_DOCK", /* bit 0 */
"ACA_C", /* bit 1 */
"ACA_B", /* bit 2 */
"ACA_A", /* bit 3 */
"SDP", /* bit 4 */
"OTHER", /* bit 5 */
"DCP", /* bit 6 */
"CDP", /* bit 7 */
"NONE", /* bit 8 error case */
};
#define BITS_PER_REG 8
/* helper to return the string of USB type */
static char *get_usb_type_name(u8 type_reg)
{
unsigned long type = type_reg;
return usb_type_str[find_first_bit(&type, BITS_PER_REG)];
}
static enum power_supply_type usb_type_enum[] = {
POWER_SUPPLY_TYPE_USB_ACA, /* bit 0 */
POWER_SUPPLY_TYPE_USB_ACA, /* bit 1 */
POWER_SUPPLY_TYPE_USB_ACA, /* bit 2 */
POWER_SUPPLY_TYPE_USB_ACA, /* bit 3 */
POWER_SUPPLY_TYPE_USB, /* bit 4 */
POWER_SUPPLY_TYPE_UNKNOWN, /* bit 5 */
POWER_SUPPLY_TYPE_USB_DCP, /* bit 6 */
POWER_SUPPLY_TYPE_USB_CDP, /* bit 7 */
POWER_SUPPLY_TYPE_USB, /* bit 8 error case, report SDP */
};
/* helper to return enum power_supply_type of USB type */
static enum power_supply_type get_usb_supply_type(u8 type_reg)
{
unsigned long type = type_reg;
return usb_type_enum[find_first_bit(&type, BITS_PER_REG)];
}
static enum power_supply_property smbchg_battery_properties[] = {
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_CHARGING_ENABLED,
POWER_SUPPLY_PROP_CHARGE_TYPE,
POWER_SUPPLY_PROP_CAPACITY,
POWER_SUPPLY_PROP_HEALTH,
POWER_SUPPLY_PROP_TECHNOLOGY,
POWER_SUPPLY_PROP_SYSTEM_TEMP_LEVEL,
};
#define CHGR_STS 0x0E
#define BATT_LESS_THAN_2V BIT(4)
#define CHG_HOLD_OFF_BIT BIT(3)
#define CHG_TYPE_MASK SMB_MASK(2, 1)
#define CHG_TYPE_SHIFT 1
#define BATT_NOT_CHG_VAL 0x0
#define BATT_PRE_CHG_VAL 0x1
#define BATT_FAST_CHG_VAL 0x2
#define BATT_TAPER_CHG_VAL 0x3
#define CHG_EN_BIT BIT(0)
static int get_prop_batt_status(struct smbchg_chip *chip)
{
int rc, status = POWER_SUPPLY_STATUS_DISCHARGING;
u8 reg = 0, chg_type;
if (chip->chg_done_batt_full)
return POWER_SUPPLY_STATUS_FULL;
rc = smbchg_read(chip, &reg, chip->chgr_base + CHGR_STS, 1);
if (rc < 0) {
dev_err(chip->dev, "Unable to read CHGR_STS rc = %d\n", rc);
return POWER_SUPPLY_STATUS_UNKNOWN;
}
if (reg & CHG_HOLD_OFF_BIT) {
/*
* when chg hold off happens the battery is
* not charging
*/
status = POWER_SUPPLY_STATUS_NOT_CHARGING;
goto out;
}
chg_type = (reg & CHG_TYPE_MASK) >> CHG_TYPE_SHIFT;
if (chg_type == BATT_NOT_CHG_VAL)
status = POWER_SUPPLY_STATUS_DISCHARGING;
else
status = POWER_SUPPLY_STATUS_CHARGING;
out:
pr_smb(PR_STATUS, "CHGR_STS = 0x%02x\n", reg);
return status;
}
#define BAT_PRES_STATUS 0x08
#define BAT_PRES_BIT BIT(7)
static int get_prop_batt_present(struct smbchg_chip *chip)
{
int rc;
u8 reg;
rc = smbchg_read(chip, &reg, chip->bat_if_base + BAT_PRES_STATUS, 1);
if (rc < 0) {
dev_err(chip->dev, "Unable to read CHGR_STS rc = %d\n", rc);
return 0;
}
return !!(reg & BAT_PRES_BIT);
}
static int get_prop_charge_type(struct smbchg_chip *chip)
{
int rc;
u8 reg, chg_type;
rc = smbchg_read(chip, &reg, chip->chgr_base + CHGR_STS, 1);
if (rc < 0) {
dev_err(chip->dev, "Unable to read CHGR_STS rc = %d\n", rc);
return 0;
}
chg_type = (reg & CHG_TYPE_MASK) >> CHG_TYPE_SHIFT;
if (chg_type == BATT_NOT_CHG_VAL)
return POWER_SUPPLY_CHARGE_TYPE_NONE;
else if (chg_type == BATT_FAST_CHG_VAL)
return POWER_SUPPLY_CHARGE_TYPE_FAST;
else if (chg_type == BATT_PRE_CHG_VAL)
return POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
return POWER_SUPPLY_CHARGE_TYPE_NONE;
}
#define DEFAULT_BATT_CAPACITY 50
static int get_prop_batt_capacity(struct smbchg_chip *chip)
{
union power_supply_propval ret = {0, };
if (chip->fake_battery_soc >= 0)
return chip->fake_battery_soc;
if (!chip->bms_psy && chip->bms_psy_name)
chip->bms_psy =
power_supply_get_by_name((char *)chip->bms_psy_name);
if (chip->bms_psy) {
chip->bms_psy->get_property(chip->bms_psy,
POWER_SUPPLY_PROP_CAPACITY, &ret);
return ret.intval;
}
return DEFAULT_BATT_CAPACITY;
}
static int get_prop_batt_health(struct smbchg_chip *chip)
{
if (chip->batt_hot)
return POWER_SUPPLY_HEALTH_OVERHEAT;
else if (chip->batt_cold)
return POWER_SUPPLY_HEALTH_COLD;
else if (chip->batt_warm)
return POWER_SUPPLY_HEALTH_WARM;
else if (chip->batt_cool)
return POWER_SUPPLY_HEALTH_COOL;
else
return POWER_SUPPLY_HEALTH_GOOD;
}
int usb_current_table[] = {
300,
400,
450,
475,
500,
550,
600,
650,
700,
900,
950,
1000,
1100,
1200,
1400,
1450,
1500,
1600,
1800,
1850,
1880,
1910,
1930,
1950,
1970,
2000,
2050,
2100,
2300,
2400,
2500,
3000
};
int dc_current_table[] = {
300,
400,
450,
475,
500,
550,
600,
650,
700,
900,
950,
1000,
1100,
1200,
1400,
1450,
1500,
1600,
1800,
1850,
1880,
1910,
1930,
1950,
1970,
2000,
};
static int calc_thermal_limited_current(struct smbchg_chip *chip,
int current_ma)
{
int therm_ma;
if (chip->therm_lvl_sel > 0
&& chip->therm_lvl_sel < (chip->thermal_levels - 1)) {
/*
* consider thermal limit only when it is active and not at
* the highest level
*/
therm_ma = (int)chip->thermal_mitigation[chip->therm_lvl_sel];
if (therm_ma < current_ma) {
pr_smb(PR_STATUS,
"Limiting current due to thermal: %d mA",
therm_ma);
return therm_ma;
}
}
return current_ma;
}
#define CMD_IL 0x40
#define USBIN_SUSPEND_BIT BIT(4)
#define CURRENT_100_MA 100
#define CURRENT_150_MA 150
#define CURRENT_500_MA 500
#define CURRENT_900_MA 900
#define SUSPEND_CURRENT_MA 2
static int smbchg_usb_suspend(struct smbchg_chip *chip, bool suspend)
{
int rc;
rc = smbchg_masked_write(chip, chip->usb_chgpth_base + CMD_IL,
USBIN_SUSPEND_BIT, suspend ? USBIN_SUSPEND_BIT : 0);
if (rc < 0)
dev_err(chip->dev, "Couldn't set usb suspend rc = %d\n", rc);
return rc;
}
#define DCIN_SUSPEND_BIT BIT(3)
static int smbchg_dc_suspend(struct smbchg_chip *chip, bool suspend)
{
int rc = 0;
rc = smbchg_masked_write(chip, chip->usb_chgpth_base + CMD_IL,
DCIN_SUSPEND_BIT, suspend ? DCIN_SUSPEND_BIT : 0);
if (rc < 0)
dev_err(chip->dev, "Couldn't set dc suspend rc = %d\n", rc);
return rc;
}
#define IL_CFG 0xF2
#define DCIN_INPUT_MASK SMB_MASK(4, 0)
static int smbchg_set_dc_current_max(struct smbchg_chip *chip, int current_ma)
{
int i;
u8 dc_cur_val;
for (i = ARRAY_SIZE(dc_current_table) - 1; i >= 0; i--) {
if (current_ma >= dc_current_table[i])
break;
}
if (i < 0) {
dev_err(chip->dev, "Cannot find %dma current_table\n",
current_ma);
return -EINVAL;
}
chip->dc_max_current_ma = dc_current_table[i];
dc_cur_val = i & DCIN_INPUT_MASK;
pr_smb(PR_STATUS, "dc current set to %d mA\n",
chip->dc_max_current_ma);
return smbchg_sec_masked_write(chip, chip->dc_chgpth_base + IL_CFG,
DCIN_INPUT_MASK, dc_cur_val);
}
enum enable_reason {
/* userspace has suspended charging altogether */
REASON_USER = BIT(0),
/*
* this specific path has been suspended through the power supply
* framework
*/
REASON_POWER_SUPPLY = BIT(1),
/*
* the usb driver has suspended this path by setting a current limit
* of < 2MA
*/
REASON_USB = BIT(2),
/*
* when a wireless charger comes online,
* the dc path is suspended for a second
*/
REASON_WIRELESS = BIT(3),
/*
* the thermal daemon can suspend a charge path when the system
* temperature levels rise
*/
REASON_THERMAL = BIT(4),
};
static struct power_supply *get_parallel_psy(struct smbchg_chip *chip)
{
if (chip->parallel.psy)
return chip->parallel.psy;
chip->parallel.psy = power_supply_get_by_name("usb-parallel");
if (!chip->parallel.psy)
pr_smb(PR_STATUS, "parallel charger not found\n");
return chip->parallel.psy;
}
#define ICL_STS_1_REG 0x7
#define ICL_STS_2_REG 0x9
#define ICL_STS_MASK 0x1F
#define AICL_STS_BIT BIT(5)
#define USBIN_SUSPEND_STS_BIT BIT(3)
#define USBIN_ACTIVE_PWR_SRC_BIT BIT(1)
static void smbchg_parallel_usb_determine_current(struct smbchg_chip *chip)
{
struct power_supply *parallel_psy;
int current_limit_ma, parallel_cl_ma, total_current_ma;
int new_parallel_cl_ma, rc;
u8 reg;
parallel_psy = get_parallel_psy(chip);
if (!parallel_psy)
return;
rc = smbchg_read(chip, &reg,
chip->usb_chgpth_base + ICL_STS_2_REG, 1);
if (rc) {
dev_err(chip->dev, "Could not read usb icl sts 2: %d\n", rc);
return;
}
/*
* If the usbin is suspended or not the active power src, then this
* was triggered from DCIN AICL. Retrun silently if this is the case.
*/
if (!!(reg & USBIN_SUSPEND_STS_BIT) ||
!(reg & USBIN_ACTIVE_PWR_SRC_BIT)) {
pr_smb(PR_STATUS, "USB not active power source: %02x\n", reg);
return;
}
rc = smbchg_read(chip, &reg,
chip->usb_chgpth_base + ICL_STS_1_REG, 1);
if (rc) {
dev_err(chip->dev, "Could not read usb icl sts 1: %d\n", rc);
return;
}
reg &= ICL_STS_MASK;
if (reg >= ARRAY_SIZE(usb_current_table)) {
pr_warn("invalid AICL value: %02x\n", reg);
return;
}
parallel_cl_ma = chip->parallel.current_max_ma;
if (parallel_cl_ma <= SUSPEND_CURRENT_MA)
parallel_cl_ma = 0;
current_limit_ma = usb_current_table[reg];
total_current_ma = current_limit_ma + parallel_cl_ma;
/*
* if the total available current is less than the minimum threshold
* to enable parallel charging, set the current limit to 0 to disable
* the parallel charge path.
*
* otherwise, set the parallel charge path's input current limit (ICL)
* to the total current / 2
*/
if (total_current_ma <= chip->parallel.min_current_thr_ma)
new_parallel_cl_ma = 0;
else
new_parallel_cl_ma = total_current_ma / 2;
if (new_parallel_cl_ma == 0)
new_parallel_cl_ma = SUSPEND_CURRENT_MA;
if (new_parallel_cl_ma < chip->parallel.current_max_ma
|| chip->parallel.current_max_ma <= SUSPEND_CURRENT_MA)
chip->parallel.current_max_ma = new_parallel_cl_ma;
pr_smb(PR_STATUS, "ICL at %d. Setting Parallel ICL at %d\n",
current_limit_ma, chip->parallel.current_max_ma);
mutex_lock(&chip->usb_en_lock);
if (!chip->usb_suspended)
power_supply_set_current_limit(parallel_psy,
chip->parallel.current_max_ma * 1000);
mutex_unlock(&chip->usb_en_lock);
}
static void smbchg_parallel_usb_en(struct smbchg_chip *chip, bool enable)
{
struct power_supply *parallel_psy;
parallel_psy = get_parallel_psy(chip);
if (!parallel_psy)
return;
power_supply_set_current_limit(parallel_psy,
enable ? chip->parallel.current_max_ma * 1000
: (SUSPEND_CURRENT_MA * 1000));
pr_smb(PR_STATUS, "parallel charger %s\n",
enable ? "unsuspended" : "suspended");
}
static void smbchg_usb_update_online_work(struct work_struct *work)
{
struct smbchg_chip *chip = container_of(work,
struct smbchg_chip,
usb_set_online_work);
bool user_enabled = (chip->usb_suspended & REASON_USER) == 0;
int online = user_enabled && chip->usb_present;
mutex_lock(&chip->usb_set_online_lock);
if (chip->usb_online != online) {
power_supply_set_online(chip->usb_psy, online);
chip->usb_online = online;
}
mutex_unlock(&chip->usb_set_online_lock);
}
static int smbchg_usb_en(struct smbchg_chip *chip, bool enable,
enum enable_reason reason)
{
int rc = 0, suspended;
pr_smb(PR_STATUS, "usb %s, susp = %02x, en? = %d, reason = %02x\n",
chip->usb_suspended == 0 ? "enabled"
: "suspended", chip->usb_suspended, enable, reason);
mutex_lock(&chip->usb_en_lock);
if (!enable)
suspended = chip->usb_suspended | reason;
else
suspended = chip->usb_suspended & (~reason);
/* avoid unnecessary spmi interactions if nothing changed */
if (!!suspended == !!chip->usb_suspended)
goto out;
if (chip->parallel.avail)
smbchg_parallel_usb_en(chip, suspended == 0);
rc = smbchg_usb_suspend(chip, suspended != 0);
if (rc < 0) {
dev_err(chip->dev,
"Couldn't set usb suspend: %d rc = %d\n",
suspended, rc);
goto out;
}
pr_smb(PR_STATUS, "usb charging %s, suspended = %02x\n",
suspended == 0 ? "enabled"
: "suspended", suspended);
out:
chip->usb_suspended = suspended;
mutex_unlock(&chip->usb_en_lock);
return rc;
}
static int smbchg_dc_en(struct smbchg_chip *chip, bool enable,
enum enable_reason reason)
{
int rc = 0, suspended;
pr_smb(PR_STATUS, "dc %s, susp = %02x, en? = %d, reason = %02x\n",
chip->dc_suspended == 0 ? "enabled"
: "suspended", chip->dc_suspended, enable, reason);
mutex_lock(&chip->dc_en_lock);
if (!enable)
suspended = chip->dc_suspended | reason;
else
suspended = chip->dc_suspended & ~reason;
/* avoid unnecessary spmi interactions if nothing changed */
if (!!suspended == !!chip->dc_suspended)
goto out;
rc = smbchg_dc_suspend(chip, suspended != 0);
if (rc < 0) {
dev_err(chip->dev,
"Couldn't set dc suspend: %d rc = %d\n",
suspended, rc);
goto out;
}
if (chip->psy_registered)
power_supply_changed(&chip->dc_psy);
pr_smb(PR_STATUS, "dc charging %s, suspended = %02x\n",
suspended == 0 ? "enabled"
: "suspended", suspended);
out:
chip->dc_suspended = suspended;
mutex_unlock(&chip->dc_en_lock);
return rc;
}
#define CHGPTH_CFG 0xF4
#define CFG_USB_2_3_SEL_BIT BIT(7)
#define CFG_USB_2 0
#define CFG_USB_3 BIT(7)
#define USBIN_INPUT_MASK SMB_MASK(4, 0)
#define USBIN_MODE_CHG_BIT BIT(0)
#define USBIN_LIMITED_MODE 0
#define USBIN_HC_MODE BIT(0)
#define USB51_MODE_BIT BIT(1)
#define USB51_100MA 0
#define USB51_500MA BIT(1)
static int smbchg_set_high_usb_chg_current(struct smbchg_chip *chip,
int current_ma)
{
int i, rc;
u8 usb_cur_val;
for (i = ARRAY_SIZE(usb_current_table) - 1; i >= 0; i--) {
if (current_ma >= usb_current_table[i])
break;
}
if (i < 0) {
dev_err(chip->dev,
"Cannot find %dma current_table using %d\n",
current_ma, CURRENT_150_MA);
rc = smbchg_sec_masked_write(chip,
chip->usb_chgpth_base + CHGPTH_CFG,
CFG_USB_2_3_SEL_BIT, CFG_USB_2);
rc |= smbchg_masked_write(chip, chip->usb_chgpth_base + CMD_IL,
USBIN_MODE_CHG_BIT | USB51_MODE_BIT,
USBIN_LIMITED_MODE | USB51_100MA);
if (rc < 0)
dev_err(chip->dev, "Couldn't set %dmA rc=%d\n",
CURRENT_150_MA, rc);
else
chip->usb_max_current_ma = 150;
return rc;
}
usb_cur_val = i & USBIN_INPUT_MASK;
rc = smbchg_sec_masked_write(chip, chip->usb_chgpth_base + IL_CFG,
USBIN_INPUT_MASK, usb_cur_val);
if (rc < 0) {
dev_err(chip->dev, "cannot write to config c rc = %d\n", rc);
return rc;
}
rc = smbchg_masked_write(chip, chip->usb_chgpth_base + CMD_IL,
USBIN_MODE_CHG_BIT, USBIN_HC_MODE);
if (rc < 0)
dev_err(chip->dev, "Couldn't write cfg 5 rc = %d\n", rc);
chip->usb_max_current_ma = usb_current_table[i];
return rc;
}
/* if APSD results are used
* if SDP is detected it will look at 500mA setting
* if set it will draw 500mA
* if unset it will draw 100mA
* if CDP/DCP it will look at 0x0C setting
* i.e. values in 0x41[1, 0] does not matter
*/
static int smbchg_set_usb_current_max(struct smbchg_chip *chip,
int current_ma)
{
int rc;
if (!chip->batt_present) {
pr_info_ratelimited("Ignoring usb current->%d, battery is absent\n",
current_ma);
return 0;
}
pr_smb(PR_STATUS, "USB current_ma = %d\n", current_ma);
if (current_ma == SUSPEND_CURRENT_MA) {
/* suspend the usb if current set to 2mA */
rc = smbchg_usb_en(chip, false, REASON_USB);
chip->usb_max_current_ma = 0;
goto out;
} else {
rc = smbchg_usb_en(chip, true, REASON_USB);
}
if (chip->low_icl_wa_on) {
chip->usb_max_current_ma = current_ma;
pr_smb(PR_STATUS,
"low_icl_wa on, ignoring the usb current setting\n");
goto out;
}
if (current_ma < CURRENT_150_MA) {
/* force 100mA */
rc = smbchg_sec_masked_write(chip,
chip->usb_chgpth_base + CHGPTH_CFG,
CFG_USB_2_3_SEL_BIT, CFG_USB_2);
rc |= smbchg_masked_write(chip, chip->usb_chgpth_base + CMD_IL,
USBIN_MODE_CHG_BIT | USB51_MODE_BIT,
USBIN_LIMITED_MODE | USB51_100MA);
chip->usb_max_current_ma = 100;
goto out;
}
/* specific current values */
if (current_ma == CURRENT_150_MA) {
rc = smbchg_sec_masked_write(chip,
chip->usb_chgpth_base + CHGPTH_CFG,
CFG_USB_2_3_SEL_BIT, CFG_USB_3);
rc |= smbchg_masked_write(chip, chip->usb_chgpth_base + CMD_IL,
USBIN_MODE_CHG_BIT | USB51_MODE_BIT,
USBIN_LIMITED_MODE | USB51_100MA);
chip->usb_max_current_ma = 150;
goto out;
}
if (current_ma == CURRENT_500_MA) {
rc = smbchg_sec_masked_write(chip,
chip->usb_chgpth_base + CHGPTH_CFG,
CFG_USB_2_3_SEL_BIT, CFG_USB_2);
rc |= smbchg_masked_write(chip, chip->usb_chgpth_base + CMD_IL,
USBIN_MODE_CHG_BIT | USB51_MODE_BIT,
USBIN_LIMITED_MODE | USB51_500MA);
chip->usb_max_current_ma = 500;
goto out;
}
if (current_ma == CURRENT_900_MA) {
rc = smbchg_sec_masked_write(chip,
chip->usb_chgpth_base + CHGPTH_CFG,
CFG_USB_2_3_SEL_BIT, CFG_USB_3);
rc |= smbchg_masked_write(chip, chip->usb_chgpth_base + CMD_IL,
USBIN_MODE_CHG_BIT | USB51_MODE_BIT,
USBIN_LIMITED_MODE | USB51_500MA);
chip->usb_max_current_ma = 900;
goto out;
}
rc = smbchg_set_high_usb_chg_current(chip, current_ma);
out:
pr_smb(PR_STATUS, "usb current set to %d mA\n",
chip->usb_max_current_ma);
if (rc < 0)
dev_err(chip->dev,
"Couldn't set %dmA rc = %d\n", current_ma, rc);
return rc;
}
static int smbchg_low_icl_wa_check(struct smbchg_chip *chip)
{
int rc = 0;
bool enable = (get_prop_batt_status(chip)
!= POWER_SUPPLY_STATUS_CHARGING);
mutex_lock(&chip->current_change_lock);
pr_smb(PR_STATUS, "low icl %s -> %s\n",
chip->low_icl_wa_on ? "on" : "off",
enable ? "on" : "off");
if (enable == chip->low_icl_wa_on)
goto out;
chip->low_icl_wa_on = enable;
if (enable) {
rc = smbchg_sec_masked_write(chip,
chip->usb_chgpth_base + CHGPTH_CFG,
CFG_USB_2_3_SEL_BIT, CFG_USB_2);
rc |= smbchg_masked_write(chip, chip->usb_chgpth_base + CMD_IL,
USBIN_MODE_CHG_BIT | USB51_MODE_BIT,
USBIN_LIMITED_MODE | USB51_100MA);
if (rc)
dev_err(chip->dev,
"could not set low current limit: %d\n", rc);
} else {
rc = smbchg_set_usb_current_max(chip, chip->usb_max_current_ma);
}
out:
mutex_unlock(&chip->current_change_lock);
return rc;
}
/*
* set the dc charge path's maximum allowed current draw
* that may be limited by the system's thermal level
*/
static int smbchg_set_thermal_limited_dc_current_max(struct smbchg_chip *chip,
int current_ma)
{
current_ma = calc_thermal_limited_current(chip, current_ma);
return smbchg_set_dc_current_max(chip, current_ma);
}
/*
* set the usb charge path's maximum allowed current draw
* that may be limited by the system's thermal level
*/
static int smbchg_set_thermal_limited_usb_current_max(struct smbchg_chip *chip,
int current_ma)
{
current_ma = calc_thermal_limited_current(chip, current_ma);
return smbchg_set_usb_current_max(chip, current_ma);
}
static int smbchg_system_temp_level_set(struct smbchg_chip *chip,
int lvl_sel)
{
int rc = 0;
int prev_therm_lvl;
if (!chip->thermal_mitigation) {
dev_err(chip->dev, "Thermal mitigation not supported\n");
return -EINVAL;
}
if (lvl_sel < 0) {
dev_err(chip->dev, "Unsupported level selected %d\n", lvl_sel);
return -EINVAL;
}
if (lvl_sel >= chip->thermal_levels) {
dev_err(chip->dev, "Unsupported level selected %d forcing %d\n",
lvl_sel, chip->thermal_levels - 1);
lvl_sel = chip->thermal_levels - 1;
}
if (lvl_sel == chip->therm_lvl_sel)
return 0;
mutex_lock(&chip->current_change_lock);
prev_therm_lvl = chip->therm_lvl_sel;
chip->therm_lvl_sel = lvl_sel;
if (chip->therm_lvl_sel == (chip->thermal_levels - 1)) {
/*
* Disable charging if highest value selected by
* setting the DC and USB path in suspend
*/
rc = smbchg_dc_en(chip, false, REASON_THERMAL);
if (rc < 0) {
dev_err(chip->dev,
"Couldn't set dc suspend rc %d\n", rc);
goto out;
}
rc = smbchg_usb_en(chip, false, REASON_THERMAL);
if (rc < 0) {
dev_err(chip->dev,
"Couldn't set usb suspend rc %d\n", rc);
goto out;
}
goto out;
}
rc = smbchg_set_thermal_limited_usb_current_max(chip,
chip->usb_target_current_ma);
rc = smbchg_set_thermal_limited_dc_current_max(chip,
chip->dc_target_current_ma);
if (prev_therm_lvl == chip->thermal_levels - 1) {
/*
* If previously highest value was selected charging must have
* been disabed. Enable charging by taking the DC and USB path
* out of suspend.
*/
rc = smbchg_dc_en(chip, true, REASON_THERMAL);
if (rc < 0) {
dev_err(chip->dev,
"Couldn't set dc suspend rc %d\n", rc);
goto out;
}
rc = smbchg_usb_en(chip, true, REASON_THERMAL);
if (rc < 0) {
dev_err(chip->dev,
"Couldn't set usb suspend rc %d\n", rc);
goto out;
}
}
out:
mutex_unlock(&chip->current_change_lock);
return rc;
}
static int smbchg_battery_set_property(struct power_supply *psy,
enum power_supply_property prop,
const union power_supply_propval *val)
{
struct smbchg_chip *chip = container_of(psy,
struct smbchg_chip, batt_psy);
switch (prop) {
case POWER_SUPPLY_PROP_CHARGING_ENABLED:
smbchg_usb_en(chip, val->intval, REASON_USER);
smbchg_dc_en(chip, val->intval, REASON_USER);
chip->chg_enabled = val->intval;
schedule_work(&chip->usb_set_online_work);
break;
case POWER_SUPPLY_PROP_CAPACITY:
chip->fake_battery_soc = val->intval;
power_supply_changed(&chip->batt_psy);
break;
case POWER_SUPPLY_PROP_SYSTEM_TEMP_LEVEL:
smbchg_system_temp_level_set(chip, val->intval);
break;
default:
return -EINVAL;
}
return 0;
}
static int smbchg_battery_is_writeable(struct power_supply *psy,
enum power_supply_property prop)
{
int rc;
switch (prop) {
case POWER_SUPPLY_PROP_CHARGING_ENABLED:
case POWER_SUPPLY_PROP_CAPACITY:
case POWER_SUPPLY_PROP_SYSTEM_TEMP_LEVEL:
rc = 1;
break;
default:
rc = 0;
break;
}
return rc;
}
static int smbchg_battery_get_property(struct power_supply *psy,
enum power_supply_property prop,
union power_supply_propval *val)
{
struct smbchg_chip *chip = container_of(psy,
struct smbchg_chip, batt_psy);
switch (prop) {
case POWER_SUPPLY_PROP_STATUS:
val->intval = get_prop_batt_status(chip);
break;
case POWER_SUPPLY_PROP_PRESENT:
val->intval = get_prop_batt_present(chip);
break;
case POWER_SUPPLY_PROP_CHARGING_ENABLED:
val->intval = chip->chg_enabled;
break;
case POWER_SUPPLY_PROP_CHARGE_TYPE:
val->intval = get_prop_charge_type(chip);
break;
case POWER_SUPPLY_PROP_CAPACITY:
val->intval = get_prop_batt_capacity(chip);
break;
case POWER_SUPPLY_PROP_HEALTH:
val->intval = get_prop_batt_health(chip);
break;
case POWER_SUPPLY_PROP_TECHNOLOGY:
val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
break;
case POWER_SUPPLY_PROP_SYSTEM_TEMP_LEVEL:
val->intval = chip->therm_lvl_sel;
break;
default:
return -EINVAL;
}
return 0;
}
static enum power_supply_property smbchg_dc_properties[] = {
POWER_SUPPLY_PROP_ONLINE,
POWER_SUPPLY_PROP_CHARGING_ENABLED,
};
static int smbchg_dc_set_property(struct power_supply *psy,
enum power_supply_property prop,
const union power_supply_propval *val)
{
struct smbchg_chip *chip = container_of(psy,
struct smbchg_chip, dc_psy);
switch (prop) {
case POWER_SUPPLY_PROP_CHARGING_ENABLED:
return smbchg_dc_en(chip, val->intval, REASON_POWER_SUPPLY);
break;
default:
return -EINVAL;
}
return 0;
}
static int smbchg_dc_get_property(struct power_supply *psy,
enum power_supply_property prop,
union power_supply_propval *val)
{
struct smbchg_chip *chip = container_of(psy,
struct smbchg_chip, dc_psy);
bool user_enabled = (chip->dc_suspended & REASON_USER) == 0;
switch (prop) {
case POWER_SUPPLY_PROP_PRESENT:
val->intval = is_dc_present(chip);
break;
case POWER_SUPPLY_PROP_CHARGING_ENABLED:
val->intval = chip->dc_suspended == 0;
break;
case POWER_SUPPLY_PROP_ONLINE:
/* return if dc is charging the battery */
val->intval = user_enabled && chip->dc_present;
break;
default:
return -EINVAL;
}
return 0;
}
static int smbchg_dc_is_writeable(struct power_supply *psy,
enum power_supply_property prop)
{
int rc;
switch (prop) {
case POWER_SUPPLY_PROP_CHARGING_ENABLED:
rc = 1;
break;
default:
rc = 0;
break;
}
return rc;
}
static void smbchg_external_power_changed(struct power_supply *psy)
{
struct smbchg_chip *chip = container_of(psy,
struct smbchg_chip, batt_psy);
union power_supply_propval prop = {0,};
int rc, current_limit = 0;
if (chip->bms_psy_name)
chip->bms_psy =
power_supply_get_by_name((char *)chip->bms_psy_name);
rc = chip->usb_psy->get_property(chip->usb_psy,
POWER_SUPPLY_PROP_CHARGING_ENABLED, &prop);
if (rc < 0)
pr_smb(PR_STATUS, "could not read USB charge_en, rc=%d\n",
rc);
else
smbchg_usb_en(chip, prop.intval, REASON_POWER_SUPPLY);
rc = chip->usb_psy->get_property(chip->usb_psy,
POWER_SUPPLY_PROP_CURRENT_MAX, &prop);
if (rc < 0)
dev_err(chip->dev,
"could not read USB current_max property, rc=%d\n", rc);
else
current_limit = prop.intval / 1000;
pr_smb(PR_STATUS, "current_limit = %d\n", current_limit);
mutex_lock(&chip->current_change_lock);
chip->usb_target_current_ma = current_limit;
rc = smbchg_set_thermal_limited_usb_current_max(chip, current_limit);
if (rc < 0)
dev_err(chip->dev, "Couldn't set usb current rc = %d\n", rc);
mutex_unlock(&chip->current_change_lock);
power_supply_changed(&chip->batt_psy);
}
#define VFLOAT_CFG_REG 0xF4
#define MIN_FLOAT_MV 3600
#define MAX_FLOAT_MV 4500
#define VFLOAT_MASK SMB_MASK(5, 0)
#define MID_RANGE_FLOAT_MV_MIN 3600
#define MID_RANGE_FLOAT_MIN_VAL 0x05
#define MID_RANGE_FLOAT_STEP_MV 20
#define HIGH_RANGE_FLOAT_MIN_MV 4340
#define HIGH_RANGE_FLOAT_MIN_VAL 0x2A
#define HIGH_RANGE_FLOAT_STEP_MV 10
#define VHIGH_RANGE_FLOAT_MIN_MV 4400
#define VHIGH_RANGE_FLOAT_MIN_VAL 0x2E
#define VHIGH_RANGE_FLOAT_STEP_MV 20
static int smbchg_float_voltage_set(struct smbchg_chip *chip, int vfloat_mv)
{
u8 temp;
if ((vfloat_mv < MIN_FLOAT_MV) || (vfloat_mv > MAX_FLOAT_MV)) {
dev_err(chip->dev, "bad float voltage mv =%d asked to set\n",
vfloat_mv);
return -EINVAL;
}
if (vfloat_mv <= HIGH_RANGE_FLOAT_MIN_MV) {
/* mid range */
temp = MID_RANGE_FLOAT_MIN_VAL
+ (vfloat_mv - MID_RANGE_FLOAT_MV_MIN)
/ MID_RANGE_FLOAT_STEP_MV;
} else if (vfloat_mv <= VHIGH_RANGE_FLOAT_MIN_MV) {
/* high range */
temp = HIGH_RANGE_FLOAT_MIN_VAL
+ (vfloat_mv - HIGH_RANGE_FLOAT_MIN_MV)
/ HIGH_RANGE_FLOAT_STEP_MV;
} else {
/* very high range */
temp = VHIGH_RANGE_FLOAT_MIN_VAL
+ (vfloat_mv - VHIGH_RANGE_FLOAT_MIN_MV)
/ VHIGH_RANGE_FLOAT_STEP_MV;
}
return smbchg_sec_masked_write(chip, chip->chgr_base + VFLOAT_CFG_REG,
VFLOAT_MASK, temp);
}
#define CMD_CHG_REG 0x42
#define OTG_EN BIT(0)
static int smbchg_otg_regulator_enable(struct regulator_dev *rdev)
{
int rc = 0;
struct smbchg_chip *chip = rdev_get_drvdata(rdev);
chip->otg_retries = 0;
rc = smbchg_masked_write(chip, chip->bat_if_base + CMD_CHG_REG,
OTG_EN, OTG_EN);
if (rc < 0)
dev_err(chip->dev, "Couldn't enable OTG mode rc=%d\n", rc);
pr_smb(PR_STATUS, "Enabling OTG Boost\n");
return rc;
}
static int smbchg_otg_regulator_disable(struct regulator_dev *rdev)
{
int rc = 0;
struct smbchg_chip *chip = rdev_get_drvdata(rdev);
rc = smbchg_masked_write(chip, chip->bat_if_base + CMD_CHG_REG,
OTG_EN, 0);
if (rc < 0)
dev_err(chip->dev, "Couldn't disable OTG mode rc=%d\n", rc);
pr_smb(PR_STATUS, "Disabling OTG Boost\n");
return rc;
}
static int smbchg_otg_regulator_is_enable(struct regulator_dev *rdev)
{
int rc = 0;
u8 reg = 0;
struct smbchg_chip *chip = rdev_get_drvdata(rdev);
rc = smbchg_read(chip, &reg, chip->bat_if_base + CMD_CHG_REG, 1);
if (rc < 0) {
dev_err(chip->dev,
"Couldn't read OTG enable bit rc=%d\n", rc);
return rc;
}
return (reg & OTG_EN) ? 1 : 0;
}
struct regulator_ops smbchg_otg_reg_ops = {
.enable = smbchg_otg_regulator_enable,
.disable = smbchg_otg_regulator_disable,
.is_enabled = smbchg_otg_regulator_is_enable,
};
static int smbchg_regulator_init(struct smbchg_chip *chip)
{
int rc = 0;
struct regulator_init_data *init_data;
struct regulator_config cfg = {};
init_data = of_get_regulator_init_data(chip->dev, chip->dev->of_node);
if (!init_data) {
dev_err(chip->dev, "Unable to allocate memory\n");
return -ENOMEM;
}
if (init_data->constraints.name) {
chip->otg_vreg.rdesc.owner = THIS_MODULE;
chip->otg_vreg.rdesc.type = REGULATOR_VOLTAGE;
chip->otg_vreg.rdesc.ops = &smbchg_otg_reg_ops;
chip->otg_vreg.rdesc.name = init_data->constraints.name;
cfg.dev = chip->dev;
cfg.init_data = init_data;
cfg.driver_data = chip;
cfg.of_node = chip->dev->of_node;
init_data->constraints.valid_ops_mask
|= REGULATOR_CHANGE_STATUS;
chip->otg_vreg.rdev = regulator_register(
&chip->otg_vreg.rdesc, &cfg);
if (IS_ERR(chip->otg_vreg.rdev)) {
rc = PTR_ERR(chip->otg_vreg.rdev);
chip->otg_vreg.rdev = NULL;
if (rc != -EPROBE_DEFER)
dev_err(chip->dev,
"OTG reg failed, rc=%d\n", rc);
}
}
return rc;
}
static void smbchg_regulator_deinit(struct smbchg_chip *chip)
{
if (chip->otg_vreg.rdev)
regulator_unregister(chip->otg_vreg.rdev);
}
#define HOT_BAT_HARD_BIT BIT(0)
#define HOT_BAT_SOFT_BIT BIT(1)
#define COLD_BAT_HARD_BIT BIT(2)
#define COLD_BAT_SOFT_BIT BIT(3)
#define BAT_OV_BIT BIT(4)
#define BAT_LOW_BIT BIT(5)
#define BAT_MISSING_BIT BIT(6)
#define BAT_TERM_MISSING_BIT BIT(7)
static irqreturn_t batt_hot_handler(int irq, void *_chip)
{
struct smbchg_chip *chip = _chip;
u8 reg = 0;
smbchg_read(chip, &reg, chip->bat_if_base + RT_STS, 1);
chip->batt_hot = !!(reg & HOT_BAT_HARD_BIT);
pr_smb(PR_INTERRUPT, "triggered: 0x%02x\n", reg);
smbchg_low_icl_wa_check(chip);
if (chip->psy_registered)
power_supply_changed(&chip->batt_psy);
return IRQ_HANDLED;
}
static irqreturn_t batt_cold_handler(int irq, void *_chip)
{
struct smbchg_chip *chip = _chip;
u8 reg = 0;
smbchg_read(chip, &reg, chip->bat_if_base + RT_STS, 1);
chip->batt_cold = !!(reg & COLD_BAT_HARD_BIT);
pr_smb(PR_INTERRUPT, "triggered: 0x%02x\n", reg);
smbchg_low_icl_wa_check(chip);
if (chip->psy_registered)
power_supply_changed(&chip->batt_psy);
return IRQ_HANDLED;
}
static irqreturn_t batt_warm_handler(int irq, void *_chip)
{
struct smbchg_chip *chip = _chip;
u8 reg = 0;
smbchg_read(chip, &reg, chip->bat_if_base + RT_STS, 1);
chip->batt_warm = !!(reg & HOT_BAT_SOFT_BIT);
pr_smb(PR_INTERRUPT, "triggered: 0x%02x\n", reg);
if (chip->psy_registered)
power_supply_changed(&chip->batt_psy);
return IRQ_HANDLED;
}
static irqreturn_t batt_cool_handler(int irq, void *_chip)
{
struct smbchg_chip *chip = _chip;
u8 reg = 0;
smbchg_read(chip, &reg, chip->bat_if_base + RT_STS, 1);
chip->batt_cool = !!(reg & COLD_BAT_SOFT_BIT);
pr_smb(PR_INTERRUPT, "triggered: 0x%02x\n", reg);
if (chip->psy_registered)
power_supply_changed(&chip->batt_psy);
return IRQ_HANDLED;
}
static irqreturn_t batt_pres_handler(int irq, void *_chip)
{
struct smbchg_chip *chip = _chip;
u8 reg = 0;
smbchg_read(chip, &reg, chip->bat_if_base + RT_STS, 1);
chip->batt_present = !(reg & BAT_MISSING_BIT);
pr_smb(PR_INTERRUPT, "triggered: 0x%02x\n", reg);
if (chip->psy_registered)
power_supply_changed(&chip->batt_psy);
return IRQ_HANDLED;
}
static irqreturn_t vbat_low_handler(int irq, void *_chip)
{
pr_warn_ratelimited("vbat low\n");
return IRQ_HANDLED;
}
static irqreturn_t chg_error_handler(int irq, void *_chip)
{
struct smbchg_chip *chip = _chip;
pr_smb(PR_INTERRUPT, "chg-error triggered\n");
smbchg_low_icl_wa_check(chip);
if (chip->psy_registered)
power_supply_changed(&chip->batt_psy);
return IRQ_HANDLED;
}
static irqreturn_t fastchg_handler(int irq, void *_chip)
{
struct smbchg_chip *chip = _chip;
pr_smb(PR_INTERRUPT, "p2f triggered\n");
smbchg_low_icl_wa_check(chip);
if (chip->psy_registered)
power_supply_changed(&chip->batt_psy);
return IRQ_HANDLED;
}
static irqreturn_t chg_hot_handler(int irq, void *_chip)
{
pr_warn_ratelimited("chg hot\n");
return IRQ_HANDLED;
}
#define CHG_INHIBIT_BIT BIT(1)
#define BAT_TAPER_MODE_BIT BIT(6)
#define BAT_TCC_REACHED_BIT BIT(7)
static irqreturn_t chg_term_handler(int irq, void *_chip)
{
struct smbchg_chip *chip = _chip;
u8 reg = 0;
smbchg_read(chip, &reg, chip->chgr_base + RT_STS, 1);
chip->chg_done_batt_full = !!(reg & BAT_TCC_REACHED_BIT);
pr_smb(PR_INTERRUPT, "triggered: 0x%02x\n", reg);
if (chip->psy_registered)
power_supply_changed(&chip->batt_psy);
return IRQ_HANDLED;
}
static irqreturn_t taper_handler(int irq, void *_chip)
{
struct smbchg_chip *chip = _chip;
u8 reg = 0;
smbchg_read(chip, &reg, chip->chgr_base + RT_STS, 1);
pr_smb(PR_INTERRUPT, "triggered: 0x%02x\n", reg);
return IRQ_HANDLED;
}
static irqreturn_t recharge_handler(int irq, void *_chip)
{
struct smbchg_chip *chip = _chip;
u8 reg = 0;
smbchg_read(chip, &reg, chip->chgr_base + RT_STS, 1);
pr_smb(PR_INTERRUPT, "triggered: 0x%02x\n", reg);
if (chip->psy_registered)
power_supply_changed(&chip->batt_psy);
return IRQ_HANDLED;
}
static irqreturn_t safety_timeout_handler(int irq, void *_chip)
{
struct smbchg_chip *chip = _chip;
u8 reg = 0;
smbchg_read(chip, &reg, chip->misc_base + RT_STS, 1);
pr_warn_ratelimited("safety timeout rt_stat = 0x%02x\n", reg);
if (chip->psy_registered)
power_supply_changed(&chip->batt_psy);
return IRQ_HANDLED;
}
/**
* power_ok_handler() - called when the switcher turns on or turns off
* @chip: pointer to smbchg_chip
* @rt_stat: the status bit indicating switcher turning on or off
*/
static irqreturn_t power_ok_handler(int irq, void *_chip)
{
struct smbchg_chip *chip = _chip;
u8 reg = 0;
smbchg_read(chip, &reg, chip->misc_base + RT_STS, 1);
pr_smb(PR_INTERRUPT, "triggered: 0x%02x\n", reg);
return IRQ_HANDLED;
}
/**
* dcin_uv_handler() - called when the dc voltage crosses the uv threshold
* @chip: pointer to smbchg_chip
* @rt_stat: the status bit indicating whether dc voltage is uv
*/
#define DCIN_UNSUSPEND_DELAY_MS 1000
static irqreturn_t dcin_uv_handler(int irq, void *_chip)
{
struct smbchg_chip *chip = _chip;
bool dc_present = is_dc_present(chip);
pr_smb(PR_STATUS, "chip->dc_present = %d dc_present = %d\n",
chip->dc_present, dc_present);
if (chip->dc_present != dc_present) {
/* dc changed */
chip->dc_present = dc_present;
if (chip->psy_registered)
power_supply_changed(&chip->dc_psy);
}
return IRQ_HANDLED;
}
static void handle_usb_removal(struct smbchg_chip *chip)
{
struct power_supply *parallel_psy;
if (chip->usb_psy) {
pr_smb(PR_STATUS, "setting usb psy type = %d\n",
POWER_SUPPLY_TYPE_UNKNOWN);
pr_smb(PR_STATUS, "setting usb psy present = %d\n",
chip->usb_present);
power_supply_set_supply_type(chip->usb_psy,
POWER_SUPPLY_TYPE_UNKNOWN);
power_supply_set_present(chip->usb_psy, chip->usb_present);
schedule_work(&chip->usb_set_online_work);
}
if (chip->parallel.avail) {
parallel_psy = get_parallel_psy(chip);
if (parallel_psy) {
power_supply_set_present(parallel_psy, false);
chip->parallel.current_max_ma = SUSPEND_CURRENT_MA;
disable_irq_wake(chip->aicl_done_irq);
}
}
}
#define IDEV_STS 0x8
static void handle_usb_insertion(struct smbchg_chip *chip)
{
struct power_supply *parallel_psy;
u8 reg = 0;
int rc;
char *usb_type_name = "null";
enum power_supply_type usb_supply_type;
/* usb inserted */
rc = smbchg_read(chip, &reg, chip->misc_base + IDEV_STS, 1);
if (rc < 0)
dev_err(chip->dev, "Couldn't read status 5 rc = %d\n", rc);
usb_type_name = get_usb_type_name(reg);
usb_supply_type = get_usb_supply_type(reg);
pr_smb(PR_STATUS, "inserted %s, usb psy type = %d stat_5 = 0x%02x\n",
usb_type_name, usb_supply_type, reg);
if (chip->usb_psy) {
pr_smb(PR_STATUS, "setting usb psy type = %d\n",
usb_supply_type);
power_supply_set_supply_type(chip->usb_psy, usb_supply_type);
pr_smb(PR_STATUS, "setting usb psy present = %d\n",
chip->usb_present);
power_supply_set_present(chip->usb_psy, chip->usb_present);
schedule_work(&chip->usb_set_online_work);
}
if (chip->parallel.avail) {
chip->parallel.current_max_ma = SUSPEND_CURRENT_MA;
parallel_psy = get_parallel_psy(chip);
if (parallel_psy) {
power_supply_set_present(parallel_psy, true);
enable_irq_wake(chip->aicl_done_irq);
}
}
}
/**
* usbin_uv_handler() - this is called when USB charger is removed
* @chip: pointer to smbchg_chip chip
* @rt_stat: the status bit indicating chg insertion/removal
*/
static irqreturn_t usbin_uv_handler(int irq, void *_chip)
{
struct smbchg_chip *chip = _chip;
bool usb_present = is_usb_present(chip);
pr_smb(PR_STATUS, "chip->usb_present = %d usb_present = %d\n",
chip->usb_present, usb_present);
if (chip->usb_present && !usb_present) {
/* USB removed */
chip->usb_present = usb_present;
handle_usb_removal(chip);
}
return IRQ_HANDLED;
}
/**
* src_detect_handler() - this is called when USB charger type is detected, use
* it for handling USB charger insertion
* @chip: pointer to smbchg_chip
* @rt_stat: the status bit indicating chg insertion/removal
*/
static irqreturn_t src_detect_handler(int irq, void *_chip)
{
struct smbchg_chip *chip = _chip;
bool usb_present = is_usb_present(chip);
pr_smb(PR_STATUS, "chip->usb_present = %d usb_present = %d\n",
chip->usb_present, usb_present);
if (!chip->usb_present && usb_present) {
/* USB inserted */
chip->usb_present = usb_present;
handle_usb_insertion(chip);
}
return IRQ_HANDLED;
}
/**
* otg_oc_handler() - called when the usb otg goes over current
*/
#define NUM_OTG_RETRIES 1
static irqreturn_t otg_oc_handler(int irq, void *_chip)
{
struct smbchg_chip *chip = _chip;
pr_smb(PR_INTERRUPT, "triggered\n");
/*
* Due to a HW bug in the PMI8994 charger, the current inrush that
* occurs when connecting certain OTG devices can cause the OTG
* overcurrent protection to trip.
*
* The work around is to try reenabling the OTG when getting an
* overcurrent interrupt once.
*/
if (chip->otg_retries < NUM_OTG_RETRIES) {
chip->otg_retries += 1;
pr_smb(PR_STATUS, "Retrying OTG enable. Try #%d\n",
chip->otg_retries);
smbchg_masked_write(chip, chip->bat_if_base + CMD_CHG_REG,
OTG_EN, 0);
msleep(20);
smbchg_masked_write(chip, chip->bat_if_base + CMD_CHG_REG,
OTG_EN, OTG_EN);
}
return IRQ_HANDLED;
}
/**
* otg_fail_handler() - called when the usb otg fails
* (when vbat < OTG UVLO threshold)
*/
static irqreturn_t otg_fail_handler(int irq, void *_chip)
{
pr_smb(PR_INTERRUPT, "triggered\n");
return IRQ_HANDLED;
}
/**
* aicl_done_handler() - called when the usb AICL algorithm is finished
* and a current is set.
*/
static irqreturn_t aicl_done_handler(int irq, void *_chip)
{
struct smbchg_chip *chip = _chip;
bool usb_present = is_usb_present(chip);
pr_smb(PR_INTERRUPT, "aicl_done triggered\n");
if (chip->parallel.avail && usb_present)
smbchg_parallel_usb_determine_current(chip);
return IRQ_HANDLED;
}
/**
* usbid_change_handler() - called when the usb RID changes.
* This is used mostly for detecting OTG
*/
static irqreturn_t usbid_change_handler(int irq, void *_chip)
{
struct smbchg_chip *chip = _chip;
bool otg_present;
pr_smb(PR_INTERRUPT, "triggered\n");
/*
* After the falling edge of the usbid change interrupt occurs,
* there may still be some time before the ADC conversion for USB RID
* finishes in the fuel gauge.
*
* Sleep for a bit to wait for the conversion to finish and the USB RID
* status register to be updated before trying to detect OTG insertions.
*/
usleep_range(5000, 20000);
otg_present = is_otg_present(chip);
if (chip->usb_psy)
power_supply_set_usb_otg(chip->usb_psy, otg_present ? 1 : 0);
if (otg_present)
pr_smb(PR_STATUS, "OTG detected\n");
return IRQ_HANDLED;
}
static irqreturn_t chg_inhibit_handler(int irq, void *_chip)
{
/*
* charger is inserted when the battery voltage is high
* so h/w won't start charging just yet. Treat this as
* battery full
*/
struct smbchg_chip *chip = _chip;
u8 reg = 0;
smbchg_read(chip, &reg, chip->chgr_base + RT_STS, 1);
chip->chg_done_batt_full = !!(reg & CHG_INHIBIT_BIT);
pr_smb(PR_INTERRUPT, "triggered: 0x%02x\n", reg);
if (chip->psy_registered)
power_supply_changed(&chip->batt_psy);
return IRQ_HANDLED;
}
static int determine_initial_status(struct smbchg_chip *chip)
{
/*
* It is okay to read the interrupt status here since
* interrupts aren't requested. reading interrupt status
* clears the interrupt so be careful to read interrupt
* status only in interrupt handling code
*/
batt_pres_handler(0, chip);
batt_hot_handler(0, chip);
batt_warm_handler(0, chip);
batt_cool_handler(0, chip);
batt_cold_handler(0, chip);
chg_term_handler(0, chip);
usbid_change_handler(0, chip);
chip->usb_present = is_usb_present(chip);
chip->dc_present = is_dc_present(chip);
if (chip->usb_present)
handle_usb_insertion(chip);
else
handle_usb_removal(chip);
return 0;
}
static int prechg_time[] = {
24,
48,
96,
192,
};
static int chg_time[] = {
192,
384,
768,
1536,
};
#define CHGR_CFG1 0xFB
#define RECHG_THRESHOLD_SRC_BIT BIT(1)
#define TERM_I_SRC_BIT BIT(2)
#define CHGR_CFG2 0xFC
#define CHG_INHIB_CFG_REG 0xF7
#define CHG_INHIBIT_50MV_VAL 0x00
#define CHG_INHIBIT_100MV_VAL 0x01
#define CHG_INHIBIT_200MV_VAL 0x02
#define CHG_INHIBIT_300MV_VAL 0x03
#define CHG_INHIBIT_MASK 0x03
#define USE_REGISTER_FOR_CURRENT BIT(2)
#define CHG_EN_SRC_BIT BIT(7)
#define CHG_EN_COMMAND_BIT BIT(6)
#define P2F_CHG_TRAN BIT(5)
#define I_TERM_BIT BIT(3)
#define AUTO_RECHG_BIT BIT(2)
#define CHARGER_INHIBIT_BIT BIT(0)
#define CFG_TCC_REG 0xF9
#define CHG_ITERM_50MA 0x1
#define CHG_ITERM_100MA 0x2
#define CHG_ITERM_150MA 0x3
#define CHG_ITERM_200MA 0x4
#define CHG_ITERM_250MA 0x5
#define CHG_ITERM_300MA 0x0
#define CHG_ITERM_500MA 0x6
#define CHG_ITERM_600MA 0x7
#define CHG_ITERM_MASK SMB_MASK(2, 0)
#define USBIN_SUSPEND_SRC_BIT BIT(6)
#define USB51_COMMAND_POL BIT(2)
#define USB51AC_CTRL BIT(1)
#define SFT_CFG 0xFD
#define SFT_EN_MASK SMB_MASK(5, 4)
#define SFT_TO_MASK SMB_MASK(3, 2)
#define PRECHG_SFT_TO_MASK SMB_MASK(1, 0)
#define SFT_TIMER_DISABLE_BIT BIT(5)
#define PRECHG_SFT_TIMER_DISABLE_BIT BIT(4)
#define SAFETY_TIME_MINUTES_SHIFT 2
#define BM_CFG 0xF3
#define BATT_MISSING_ALGO_BIT BIT(2)
#define BMD_PIN_SRC_MASK SMB_MASK(1, 0)
#define PIN_SRC_SHIFT 0
#define CHGR_CFG 0xFF
#define RCHG_LVL_BIT BIT(0)
#define CFG_AFVC 0xF5
#define VFLOAT_COMP_ENABLE_MASK SMB_MASK(2, 0)
#define TR_RID_REG 0xFA
#define FG_INPUT_FET_DELAY_BIT BIT(3)
#define TRIM_OPTIONS_7_0 0xF6
#define INPUT_MISSING_POLLER_EN_BIT BIT(3)
static int smbchg_hw_init(struct smbchg_chip *chip)
{
int rc, i;
u8 reg;
rc = smbchg_sec_masked_write(chip, chip->usb_chgpth_base + TR_RID_REG,
FG_INPUT_FET_DELAY_BIT, FG_INPUT_FET_DELAY_BIT);
if (rc < 0) {
dev_err(chip->dev, "Couldn't disable fg input fet delay rc=%d\n",
rc);
return rc;
}
rc = smbchg_sec_masked_write(chip, chip->misc_base + TRIM_OPTIONS_7_0,
INPUT_MISSING_POLLER_EN_BIT, 0);
if (rc < 0) {
dev_err(chip->dev, "Couldn't disable input missing poller rc=%d\n",
rc);
return rc;
}
/*
* force using current from the register i.e. ignore auto
* power source detect (APSD) mA ratings
*/
reg = USE_REGISTER_FOR_CURRENT;
rc = smbchg_masked_write(chip, chip->usb_chgpth_base + CMD_IL,
USE_REGISTER_FOR_CURRENT, USE_REGISTER_FOR_CURRENT);
if (rc < 0) {
dev_err(chip->dev, "Couldn't set input limit cmd rc=%d\n", rc);
return rc;
}
/*
* set chg en by cmd register, set chg en by writing bit 1,
* enable auto pre to fast, enable current termination, enable
* auto recharge, enable chg inhibition
*/
rc = smbchg_sec_masked_write(chip, chip->chgr_base + CHGR_CFG2,
CHG_EN_SRC_BIT | CHG_EN_COMMAND_BIT | P2F_CHG_TRAN
| I_TERM_BIT | AUTO_RECHG_BIT | CHARGER_INHIBIT_BIT,
CHARGER_INHIBIT_BIT | CHG_EN_COMMAND_BIT
| (chip->iterm_disabled ? I_TERM_BIT : 0));
if (rc < 0) {
dev_err(chip->dev, "Couldn't set chgr_cfg2 rc=%d\n", rc);
return rc;
}
/*
* use the analog sensors instead of the fuelgauge adcs so that
* tcc detection works without trimmed parts
*/
rc = smbchg_sec_masked_write(chip, chip->chgr_base + CHGR_CFG1,
TERM_I_SRC_BIT | RECHG_THRESHOLD_SRC_BIT, 0);
if (rc < 0) {
dev_err(chip->dev, "Couldn't set chgr_cfg2 rc=%d\n", rc);
return rc;
}
/*
* control USB suspend via command bits and set correct 100/500mA
* polarity on the usb current
*/
rc = smbchg_sec_masked_write(chip, chip->usb_chgpth_base + CHGPTH_CFG,
USBIN_SUSPEND_SRC_BIT | USB51_COMMAND_POL | USB51AC_CTRL,
USBIN_SUSPEND_SRC_BIT);
if (rc < 0) {
dev_err(chip->dev, "Couldn't set usb_chgpth cfg rc=%d\n", rc);
return rc;
}
/* set the float voltage */
if (chip->vfloat_mv != -EINVAL) {
rc = smbchg_float_voltage_set(chip, chip->vfloat_mv);
if (rc < 0) {
dev_err(chip->dev,
"Couldn't set float voltage rc = %d\n", rc);
return rc;
}
pr_smb(PR_STATUS, "set vfloat to %d\n", chip->vfloat_mv);
}
/* set iterm */
if (chip->iterm_ma != -EINVAL) {
if (chip->iterm_disabled) {
dev_err(chip->dev, "Error: Both iterm_disabled and iterm_ma set\n");
return -EINVAL;
} else {
if (chip->iterm_ma <= 50)
reg = CHG_ITERM_50MA;
else if (chip->iterm_ma <= 100)
reg = CHG_ITERM_100MA;
else if (chip->iterm_ma <= 150)
reg = CHG_ITERM_150MA;
else if (chip->iterm_ma <= 200)
reg = CHG_ITERM_200MA;
else if (chip->iterm_ma <= 250)
reg = CHG_ITERM_250MA;
else if (chip->iterm_ma <= 300)
reg = CHG_ITERM_300MA;
else if (chip->iterm_ma <= 500)
reg = CHG_ITERM_500MA;
else
reg = CHG_ITERM_600MA;
rc = smbchg_sec_masked_write(chip,
chip->chgr_base + CFG_TCC_REG,
CHG_ITERM_MASK, reg);
if (rc) {
dev_err(chip->dev,
"Couldn't set iterm rc = %d\n", rc);
return rc;
}
pr_smb(PR_STATUS, "set tcc (%d) to 0x%02x\n",
chip->iterm_ma, reg);
}
}
/* set the safety time voltage */
if (chip->safety_time != -EINVAL) {
reg = (chip->safety_time > 0 ? 0 : SFT_TIMER_DISABLE_BIT) |
(chip->prechg_safety_time > 0
? 0 : PRECHG_SFT_TIMER_DISABLE_BIT);
for (i = 0; i < ARRAY_SIZE(chg_time); i++) {
if (chip->safety_time <= chg_time[i]) {
reg |= i << SAFETY_TIME_MINUTES_SHIFT;
break;
}
}
for (i = 0; i < ARRAY_SIZE(prechg_time); i++) {
if (chip->prechg_safety_time <= prechg_time[i]) {
reg |= i;
break;
}
}
rc = smbchg_sec_masked_write(chip,
chip->chgr_base + SFT_CFG,
SFT_EN_MASK | SFT_TO_MASK |
(chip->prechg_safety_time > 0
? PRECHG_SFT_TO_MASK : 0), reg);
if (rc < 0) {
dev_err(chip->dev,
"Couldn't set safety timer rc = %d\n",
rc);
return rc;
}
}
/* battery missing detection */
rc = smbchg_sec_masked_write(chip, chip->bat_if_base + BM_CFG,
BATT_MISSING_ALGO_BIT
| (chip->bmd_pin_src > 0 ? BMD_PIN_SRC_MASK : 0),
(chip->bmd_algo_disabled ? BATT_MISSING_ALGO_BIT : 0)
| (chip->bmd_pin_src << PIN_SRC_SHIFT));
if (rc < 0) {
dev_err(chip->dev, "Couldn't set batt_missing config = %d\n",
rc);
return rc;
}
smbchg_low_icl_wa_check(chip);
/*
* The charger needs 20 milliseconds to go into battery supplementary
* mode. Sleep here until we are sure it takes into effect.
*/
msleep(20);
smbchg_usb_en(chip, chip->chg_enabled, REASON_USER);
smbchg_dc_en(chip, chip->chg_enabled, REASON_USER);
/* resume threshold */
if (chip->resume_delta_mv != -EINVAL) {
if (chip->resume_delta_mv < 100)
reg = CHG_INHIBIT_50MV_VAL;
else if (chip->resume_delta_mv < 200)
reg = CHG_INHIBIT_100MV_VAL;
else if (chip->resume_delta_mv < 300)
reg = CHG_INHIBIT_200MV_VAL;
else
reg = CHG_INHIBIT_300MV_VAL;
rc = smbchg_sec_masked_write(chip,
chip->chgr_base + CHG_INHIB_CFG_REG,
CHG_INHIBIT_MASK, reg);
if (rc < 0) {
dev_err(chip->dev, "Couldn't set inhibit val rc = %d\n",
rc);
return rc;
}
rc = smbchg_sec_masked_write(chip,
chip->chgr_base + CHGR_CFG,
RCHG_LVL_BIT, (chip->resume_delta_mv < 200)
? 0 : RCHG_LVL_BIT);
if (rc < 0) {
dev_err(chip->dev, "Couldn't set recharge rc = %d\n",
rc);
return rc;
}
}
/* DC path current settings */
if (chip->dc_psy_type != -EINVAL)
return smbchg_set_thermal_limited_dc_current_max(chip,
chip->dc_target_current_ma);
/*
* on some devices the battery is powered via external sources which
* could raise its voltage above the float voltage. smbchargers go
* in to reverse boost in such a situation and the workaround is to
* disable float voltage compensation (note that the battery will appear
* hot/cold when powered via external source).
*/
if (chip->soft_vfloat_comp_disabled) {
rc |= smbchg_sec_masked_write(chip, chip->chgr_base + CFG_AFVC,
VFLOAT_COMP_ENABLE_MASK, 0);
if (rc < 0) {
dev_err(chip->dev, "Couldn't disable soft vfloat rc = %d\n",
rc);
return rc;
}
}
/*
* Allow the smb charger to enable charging based on the command
* register rather than the enable pin.
*/
rc = smbchg_sec_masked_write(chip, chip->chgr_base + CHGR_CFG2,
CHG_EN_SRC_BIT,
CHG_EN_SRC_BIT);
if (rc < 0) {
dev_err(chip->dev, "Couldn't switch to cmd register for enable = %d\n",
rc);
return rc;
}
return rc;
}
static struct of_device_id smbchg_match_table[] = {
{
.compatible = "qcom,qpnp-smbcharger",
.data = (void *)ARRAY_SIZE(usb_current_table),
},
{ },
};
enum bpd_type {
BPD_TYPE_BAT_NONE,
BPD_TYPE_BAT_ID,
BPD_TYPE_BAT_THM,
BPD_TYPE_BAT_THM_BAT_ID,
};
static const char * const bpd_label[] = {
[BPD_TYPE_BAT_NONE] = "bpd_none",
[BPD_TYPE_BAT_ID] = "bpd_id",
[BPD_TYPE_BAT_THM] = "bpd_thm",
[BPD_TYPE_BAT_THM_BAT_ID] = "bpd_thm_id",
};
static inline int get_bpd(const char *name)
{
int i = 0;
for (i = 0; i < ARRAY_SIZE(bpd_label); i++) {
if (strcmp(bpd_label[i], name) == 0)
return i;
}
return -EINVAL;
}
#define DC_MA_MIN 300
#define DC_MA_MAX 2000
#define OF_PROP_READ(chip, prop, dt_property, retval, optional) \
do { \
if (retval) \
break; \
if (optional) \
prop = -EINVAL; \
\
retval = of_property_read_u32(chip->spmi->dev.of_node, \
"qcom," dt_property , \
&prop); \
\
if ((retval == -EINVAL) && optional) \
retval = 0; \
else if (retval) \
dev_err(chip->dev, "Error reading " #dt_property \
" property rc = %d\n", rc); \
} while (0)
static int smb_parse_dt(struct smbchg_chip *chip)
{
int rc = 0;
struct device_node *node = chip->dev->of_node;
const char *dc_psy_type, *bpd;
if (!node) {
dev_err(chip->dev, "device tree info. missing\n");
return -EINVAL;
}
/* read optional u32 properties */
OF_PROP_READ(chip, chip->iterm_ma, "iterm-ma", rc, 1);
OF_PROP_READ(chip, chip->vfloat_mv, "float-voltage-mv", rc, 1);
OF_PROP_READ(chip, chip->safety_time, "charging-timeout-mins", rc, 1);
OF_PROP_READ(chip, chip->prechg_safety_time, "precharging-timeout-mins",
rc, 1);
if (chip->safety_time != -EINVAL &&
(chip->safety_time > chg_time[ARRAY_SIZE(chg_time) - 1])) {
dev_err(chip->dev, "Bad charging-timeout-mins %d\n",
chip->safety_time);
return -EINVAL;
}
if (chip->prechg_safety_time != -EINVAL &&
(chip->prechg_safety_time >
prechg_time[ARRAY_SIZE(prechg_time) - 1])) {
dev_err(chip->dev, "Bad precharging-timeout-mins %d\n",
chip->prechg_safety_time);
return -EINVAL;
}
OF_PROP_READ(chip, chip->resume_delta_mv, "resume-delta-mv", rc, 1);
OF_PROP_READ(chip, chip->parallel.min_current_thr_ma,
"parallel-usb-min-current-ma", rc, 1);
if (chip->parallel.min_current_thr_ma != -EINVAL)
chip->parallel.avail = true;
/* read boolean configuration properties */
chip->bmd_algo_disabled = of_property_read_bool(node,
"qcom,bmd-algo-disabled");
chip->iterm_disabled = of_property_read_bool(node,
"qcom,iterm-disabled");
chip->soft_vfloat_comp_disabled = of_property_read_bool(node,
"qcom,soft-vfloat-comp-disabled");
chip->chg_enabled = !(of_property_read_bool(node,
"qcom,charging-disabled"));
/* parse the battery missing detection pin source */
rc = of_property_read_string(chip->spmi->dev.of_node,
"qcom,bmd-pin-src", &bpd);
if (rc) {
/* Select BAT_THM as default BPD scheme */
chip->bmd_pin_src = BPD_TYPE_BAT_THM;
rc = 0;
} else {
chip->bmd_pin_src = get_bpd(bpd);
if (chip->bmd_pin_src < 0) {
dev_err(chip->dev,
"failed to determine bpd schema %d\n", rc);
return rc;
}
}
/* parse the dc power supply configuration */
rc = of_property_read_string(node, "qcom,dc-psy-type", &dc_psy_type);
if (rc) {
chip->dc_psy_type = -EINVAL;
rc = 0;
} else {
if (strcmp(dc_psy_type, "Mains") == 0)
chip->dc_psy_type = POWER_SUPPLY_TYPE_MAINS;
else if (strcmp(dc_psy_type, "Wireless") == 0)
chip->dc_psy_type = POWER_SUPPLY_TYPE_WIRELESS;
}
if (chip->dc_psy_type != -EINVAL) {
OF_PROP_READ(chip, chip->dc_target_current_ma,
"dc-psy-ma", rc, 0);
if (rc)
return rc;
if (chip->dc_target_current_ma < DC_MA_MIN
|| chip->dc_target_current_ma > DC_MA_MAX) {
dev_err(chip->dev, "Bad dc mA %d\n",
chip->dc_target_current_ma);
return -EINVAL;
}
}
/* read the bms power supply name */
rc = of_property_read_string(node, "qcom,bms-psy-name",
&chip->bms_psy_name);
if (rc)
chip->bms_psy_name = NULL;
/* read the bms power supply name */
rc = of_property_read_string(node, "qcom,battery-psy-name",
&chip->battery_psy_name);
if (rc)
chip->battery_psy_name = "battery";
if (of_find_property(node, "qcom,thermal-mitigation",
&chip->thermal_levels)) {
chip->thermal_mitigation = devm_kzalloc(chip->dev,
chip->thermal_levels,
GFP_KERNEL);
if (chip->thermal_mitigation == NULL) {
dev_err(chip->dev, "thermal mitigation kzalloc() failed.\n");
return -ENOMEM;
}
chip->thermal_levels /= sizeof(int);
rc = of_property_read_u32_array(node,
"qcom,thermal-mitigation",
chip->thermal_mitigation, chip->thermal_levels);
if (rc) {
dev_err(chip->dev,
"Couldn't read threm limits rc = %d\n", rc);
return rc;
}
}
return 0;
}
#define SUBTYPE_REG 0x5
#define SMBCHG_CHGR_SUBTYPE 0x1
#define SMBCHG_OTG_SUBTYPE 0x8
#define SMBCHG_BAT_IF_SUBTYPE 0x3
#define SMBCHG_USB_CHGPTH_SUBTYPE 0x4
#define SMBCHG_DC_CHGPTH_SUBTYPE 0x5
#define SMBCHG_MISC_SUBTYPE 0x7
#define REQUEST_IRQ(chip, resource, irq_num, irq_name, irq_handler, flags, rc)\
do { \
irq_num = spmi_get_irq_byname(chip->spmi, \
resource, irq_name); \
if (irq_num < 0) { \
dev_err(chip->dev, "Unable to get " irq_name " irq\n"); \
return -ENXIO; \
} \
rc = devm_request_threaded_irq(chip->dev, \
irq_num, NULL, irq_handler, flags, irq_name, \
chip); \
if (rc < 0) { \
dev_err(chip->dev, "Unable to request " irq_name " irq: %d\n",\
rc); \
return -ENXIO; \
} \
} while (0)
static int smbchg_request_irqs(struct smbchg_chip *chip)
{
int rc = 0;
struct resource *resource;
struct spmi_resource *spmi_resource;
u8 subtype;
struct spmi_device *spmi = chip->spmi;
unsigned long flags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING
| IRQF_ONESHOT;
spmi_for_each_container_dev(spmi_resource, chip->spmi) {
if (!spmi_resource) {
dev_err(chip->dev, "spmi resource absent\n");
return rc;
}
resource = spmi_get_resource(spmi, spmi_resource,
IORESOURCE_MEM, 0);
if (!(resource && resource->start)) {
dev_err(chip->dev, "node %s IO resource absent!\n",
spmi->dev.of_node->full_name);
return rc;
}
rc = smbchg_read(chip, &subtype,
resource->start + SUBTYPE_REG, 1);
if (rc) {
dev_err(chip->dev, "Peripheral subtype read failed rc=%d\n",
rc);
return rc;
}
switch (subtype) {
case SMBCHG_CHGR_SUBTYPE:
REQUEST_IRQ(chip, spmi_resource, chip->chg_error_irq,
"chg-error", chg_error_handler, flags, rc);
REQUEST_IRQ(chip, spmi_resource, chip->taper_irq,
"chg-taper-thr", taper_handler, flags, rc);
REQUEST_IRQ(chip, spmi_resource, chip->chg_term_irq,
"chg-tcc-thr", chg_term_handler, flags, rc);
REQUEST_IRQ(chip, spmi_resource, chip->chg_inhibit_irq,
"chg-inhibit", chg_inhibit_handler, flags, rc);
REQUEST_IRQ(chip, spmi_resource, chip->recharge_irq,
"chg-rechg-thr", recharge_handler, flags, rc);
REQUEST_IRQ(chip, spmi_resource, chip->fastchg_irq,
"chg-p2f-thr", fastchg_handler, flags, rc);
enable_irq_wake(chip->chg_term_irq);
enable_irq_wake(chip->chg_error_irq);
enable_irq_wake(chip->fastchg_irq);
break;
case SMBCHG_BAT_IF_SUBTYPE:
REQUEST_IRQ(chip, spmi_resource, chip->batt_hot_irq,
"batt-hot", batt_hot_handler, flags, rc);
REQUEST_IRQ(chip, spmi_resource, chip->batt_warm_irq,
"batt-warm", batt_warm_handler, flags, rc);
REQUEST_IRQ(chip, spmi_resource, chip->batt_cool_irq,
"batt-cool", batt_cool_handler, flags, rc);
REQUEST_IRQ(chip, spmi_resource, chip->batt_cold_irq,
"batt-cold", batt_cold_handler, flags, rc);
REQUEST_IRQ(chip, spmi_resource, chip->batt_missing_irq,
"batt-missing", batt_pres_handler, flags, rc);
REQUEST_IRQ(chip, spmi_resource, chip->vbat_low_irq,
"batt-low", vbat_low_handler, flags, rc);
enable_irq_wake(chip->batt_hot_irq);
enable_irq_wake(chip->batt_warm_irq);
enable_irq_wake(chip->batt_cool_irq);
enable_irq_wake(chip->batt_cold_irq);
enable_irq_wake(chip->batt_missing_irq);
enable_irq_wake(chip->vbat_low_irq);
break;
case SMBCHG_USB_CHGPTH_SUBTYPE:
REQUEST_IRQ(chip, spmi_resource, chip->usbin_uv_irq,
"usbin-uv", usbin_uv_handler, flags, rc);
REQUEST_IRQ(chip, spmi_resource, chip->src_detect_irq,
"usbin-src-det",
src_detect_handler, flags, rc);
REQUEST_IRQ(chip, spmi_resource, chip->otg_fail_irq,
"otg-fail", otg_fail_handler, flags, rc);
REQUEST_IRQ(chip, spmi_resource, chip->otg_oc_irq,
"otg-oc", otg_oc_handler,
(IRQF_TRIGGER_RISING | IRQF_ONESHOT), rc);
REQUEST_IRQ(chip, spmi_resource, chip->aicl_done_irq,
"aicl-done",
aicl_done_handler, flags, rc);
REQUEST_IRQ(chip, spmi_resource,
chip->usbid_change_irq, "usbid-change",
usbid_change_handler,
(IRQF_TRIGGER_FALLING | IRQF_ONESHOT), rc);
enable_irq_wake(chip->usbin_uv_irq);
enable_irq_wake(chip->src_detect_irq);
enable_irq_wake(chip->otg_fail_irq);
enable_irq_wake(chip->otg_oc_irq);
enable_irq_wake(chip->usbid_change_irq);
break;
case SMBCHG_DC_CHGPTH_SUBTYPE:
REQUEST_IRQ(chip, spmi_resource, chip->dcin_uv_irq,
"dcin-uv", dcin_uv_handler, flags, rc);
enable_irq_wake(chip->dcin_uv_irq);
break;
case SMBCHG_MISC_SUBTYPE:
REQUEST_IRQ(chip, spmi_resource, chip->power_ok_irq,
"power-ok", power_ok_handler, flags, rc);
REQUEST_IRQ(chip, spmi_resource, chip->chg_hot_irq,
"temp-shutdown", chg_hot_handler, flags, rc);
REQUEST_IRQ(chip, spmi_resource,
chip->safety_timeout_irq,
"safety-timeout",
safety_timeout_handler, flags, rc);
enable_irq_wake(chip->chg_hot_irq);
enable_irq_wake(chip->safety_timeout_irq);
break;
case SMBCHG_OTG_SUBTYPE:
break;
}
}
return rc;
}
#define REQUIRE_BASE(chip, base, rc) \
do { \
if (!rc && !chip->base) { \
dev_err(chip->dev, "Missing " #base "\n"); \
rc = -EINVAL; \
} \
} while (0)
static int smbchg_parse_peripherals(struct smbchg_chip *chip)
{
int rc = 0;
struct resource *resource;
struct spmi_resource *spmi_resource;
u8 subtype;
struct spmi_device *spmi = chip->spmi;
spmi_for_each_container_dev(spmi_resource, chip->spmi) {
if (!spmi_resource) {
dev_err(chip->dev, "spmi resource absent\n");
return rc;
}
resource = spmi_get_resource(spmi, spmi_resource,
IORESOURCE_MEM, 0);
if (!(resource && resource->start)) {
dev_err(chip->dev, "node %s IO resource absent!\n",
spmi->dev.of_node->full_name);
return rc;
}
rc = smbchg_read(chip, &subtype,
resource->start + SUBTYPE_REG, 1);
if (rc) {
dev_err(chip->dev, "Peripheral subtype read failed rc=%d\n",
rc);
return rc;
}
switch (subtype) {
case SMBCHG_CHGR_SUBTYPE:
chip->chgr_base = resource->start;
break;
case SMBCHG_BAT_IF_SUBTYPE:
chip->bat_if_base = resource->start;
break;
case SMBCHG_USB_CHGPTH_SUBTYPE:
chip->usb_chgpth_base = resource->start;
break;
case SMBCHG_DC_CHGPTH_SUBTYPE:
chip->dc_chgpth_base = resource->start;
break;
case SMBCHG_MISC_SUBTYPE:
chip->misc_base = resource->start;
break;
case SMBCHG_OTG_SUBTYPE:
chip->otg_base = resource->start;
break;
}
}
REQUIRE_BASE(chip, chgr_base, rc);
REQUIRE_BASE(chip, bat_if_base, rc);
REQUIRE_BASE(chip, usb_chgpth_base, rc);
REQUIRE_BASE(chip, dc_chgpth_base, rc);
REQUIRE_BASE(chip, misc_base, rc);
return rc;
}
static inline void dump_reg(struct smbchg_chip *chip, u16 addr,
const char *name)
{
u8 reg;
smbchg_read(chip, &reg, addr, 1);
pr_smb(PR_DUMP, "%s - %04X = %02X\n", name, addr, reg);
}
/* dumps useful registers for debug */
static void dump_regs(struct smbchg_chip *chip)
{
u16 addr;
/* charger peripheral */
for (addr = 0xB; addr <= 0x10; addr++)
dump_reg(chip, chip->chgr_base + addr, "CHGR Status");
for (addr = 0xF0; addr <= 0xFF; addr++)
dump_reg(chip, chip->chgr_base + addr, "CHGR Config");
/* battery interface peripheral */
dump_reg(chip, chip->bat_if_base + RT_STS, "BAT_IF Status");
dump_reg(chip, chip->bat_if_base + CMD_CHG_REG, "BAT_IF Command");
for (addr = 0xF0; addr <= 0xFB; addr++)
dump_reg(chip, chip->bat_if_base + addr, "BAT_IF Config");
/* usb charge path peripheral */
for (addr = 0x7; addr <= 0x10; addr++)
dump_reg(chip, chip->usb_chgpth_base + addr, "USB Status");
dump_reg(chip, chip->usb_chgpth_base + CMD_IL, "USB Command");
for (addr = 0xF0; addr <= 0xF5; addr++)
dump_reg(chip, chip->usb_chgpth_base + addr, "USB Config");
/* dc charge path peripheral */
dump_reg(chip, chip->dc_chgpth_base + RT_STS, "DC Status");
for (addr = 0xF0; addr <= 0xF6; addr++)
dump_reg(chip, chip->dc_chgpth_base + addr, "DC Config");
/* misc peripheral */
dump_reg(chip, chip->misc_base + IDEV_STS, "MISC Status");
dump_reg(chip, chip->misc_base + RT_STS, "MISC Status");
for (addr = 0xF0; addr <= 0xF3; addr++)
dump_reg(chip, chip->misc_base + addr, "MISC CFG");
}
static int smbchg_probe(struct spmi_device *spmi)
{
int rc;
struct smbchg_chip *chip;
struct power_supply *usb_psy;
usb_psy = power_supply_get_by_name("usb");
if (!usb_psy) {
pr_smb(PR_STATUS, "USB supply not found, deferring probe\n");
return -EPROBE_DEFER;
}
chip = devm_kzalloc(&spmi->dev, sizeof(*chip), GFP_KERNEL);
if (!chip) {
dev_err(&spmi->dev, "Unable to allocate memory\n");
return -ENOMEM;
}
INIT_WORK(&chip->usb_set_online_work, smbchg_usb_update_online_work);
chip->spmi = spmi;
chip->dev = &spmi->dev;
chip->usb_psy = usb_psy;
chip->fake_battery_soc = -EINVAL;
chip->usb_online = -EINVAL;
dev_set_drvdata(&spmi->dev, chip);
spin_lock_init(&chip->sec_access_lock);
mutex_init(&chip->current_change_lock);
mutex_init(&chip->usb_set_online_lock);
mutex_init(&chip->usb_en_lock);
mutex_init(&chip->dc_en_lock);
rc = smbchg_parse_peripherals(chip);
if (rc) {
dev_err(chip->dev, "Error parsing DT peripherals: %d\n", rc);
return rc;
}
rc = smb_parse_dt(chip);
if (rc < 0) {
dev_err(&spmi->dev, "Unable to parse DT nodes: %d\n", rc);
return rc;
}
rc = smbchg_regulator_init(chip);
if (rc) {
dev_err(&spmi->dev,
"Couldn't initialize regulator rc=%d\n", rc);
return rc;
}
rc = smbchg_hw_init(chip);
if (rc < 0) {
dev_err(&spmi->dev,
"Unable to intialize hardware rc = %d\n", rc);
goto free_regulator;
}
rc = determine_initial_status(chip);
if (rc < 0) {
dev_err(&spmi->dev,
"Unable to determine init status rc = %d\n", rc);
goto free_regulator;
}
chip->batt_psy.name = chip->battery_psy_name;
chip->batt_psy.type = POWER_SUPPLY_TYPE_BATTERY;
chip->batt_psy.get_property = smbchg_battery_get_property;
chip->batt_psy.set_property = smbchg_battery_set_property;
chip->batt_psy.properties = smbchg_battery_properties;
chip->batt_psy.num_properties = ARRAY_SIZE(smbchg_battery_properties);
chip->batt_psy.external_power_changed = smbchg_external_power_changed;
chip->batt_psy.property_is_writeable = smbchg_battery_is_writeable;
rc = power_supply_register(chip->dev, &chip->batt_psy);
if (rc < 0) {
dev_err(&spmi->dev,
"Unable to register batt_psy rc = %d\n", rc);
goto free_regulator;
}
if (chip->dc_psy_type != -EINVAL) {
chip->dc_psy.name = "dc";
chip->dc_psy.type = chip->dc_psy_type;
chip->dc_psy.get_property = smbchg_dc_get_property;
chip->dc_psy.set_property = smbchg_dc_set_property;
chip->dc_psy.property_is_writeable = smbchg_dc_is_writeable;
chip->dc_psy.properties = smbchg_dc_properties;
chip->dc_psy.num_properties = ARRAY_SIZE(smbchg_dc_properties);
rc = power_supply_register(chip->dev, &chip->dc_psy);
if (rc < 0) {
dev_err(&spmi->dev,
"Unable to register dc_psy rc = %d\n", rc);
goto unregister_batt_psy;
}
}
chip->psy_registered = true;
rc = smbchg_request_irqs(chip);
if (rc < 0) {
dev_err(&spmi->dev, "Unable to request irqs rc = %d\n", rc);
goto unregister_dc_psy;
}
power_supply_set_present(chip->usb_psy, chip->usb_present);
dump_regs(chip);
dev_info(chip->dev, "SMBCHG successfully probed batt=%d dc = %d usb = %d\n",
get_prop_batt_present(chip),
chip->dc_present, chip->usb_present);
return 0;
unregister_dc_psy:
power_supply_unregister(&chip->dc_psy);
unregister_batt_psy:
power_supply_unregister(&chip->batt_psy);
free_regulator:
smbchg_regulator_deinit(chip);
handle_usb_removal(chip);
return rc;
}
static int smbchg_remove(struct spmi_device *spmi)
{
struct smbchg_chip *chip = dev_get_drvdata(&spmi->dev);
if (chip->dc_psy_type != -EINVAL)
power_supply_unregister(&chip->dc_psy);
power_supply_unregister(&chip->batt_psy);
smbchg_regulator_deinit(chip);
return 0;
}
static const struct dev_pm_ops smbchg_pm_ops = {
};
MODULE_DEVICE_TABLE(spmi, smbchg_id);
static struct spmi_driver smbchg_driver = {
.driver = {
.name = "qpnp-smbcharger",
.owner = THIS_MODULE,
.of_match_table = smbchg_match_table,
.pm = &smbchg_pm_ops,
},
.probe = smbchg_probe,
.remove = smbchg_remove,
};
static int __init smbchg_init(void)
{
return spmi_driver_register(&smbchg_driver);
}
static void __exit smbchg_exit(void)
{
return spmi_driver_unregister(&smbchg_driver);
}
module_init(smbchg_init);
module_exit(smbchg_exit);
MODULE_DESCRIPTION("QPNP SMB Charger");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:qpnp-smbcharger");