Rtwo/kernel/motorola/sm8550/drivers/power/supply/qcom/smb23x-charger.c
2025-09-30 19:22:48 -05:00

2632 lines
62 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2015-2016 The Linux Foundation. All rights reserved.
* Copyright (c) 2022-2023, Qualcomm Innovation Center, Inc. All rights reserved.
*/
#define pr_fmt(fmt) "SMB:%s: " fmt, __func__
#include <linux/i2c.h>
#include <linux/delay.h>
#include <linux/errno.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/iio/iio.h>
#include <linux/iio/consumer.h>
#include <linux/power_supply.h>
#include <linux/of.h>
#include <linux/bitops.h>
#include <linux/mutex.h>
#include <linux/debugfs.h>
#include <linux/pm_wakeup.h>
#include <linux/spinlock.h>
#include <linux/suspend.h>
#define is_between(left, right, value) \
(((left) >= (right) && (left) >= (value) \
&& (value) >= (right)) \
|| ((left) <= (right) && (left) <= (value) \
&& (value) <= (right)))
struct smb23x_chip {
struct i2c_client *client;
struct device *dev;
/* charger configurations -- via devicetree */
bool cfg_charging_disabled;
bool cfg_recharge_disabled;
bool cfg_chg_inhibit_disabled;
bool cfg_iterm_disabled;
bool cfg_aicl_disabled;
bool cfg_apsd_disabled;
bool cfg_bms_controlled_charging;
int cfg_vfloat_mv;
int cfg_resume_delta_mv;
int cfg_chg_inhibit_delta_mv;
int cfg_iterm_ma;
int cfg_fastchg_ma;
int cfg_cold_bat_decidegc;
int cfg_cool_bat_decidegc;
int cfg_warm_bat_decidegc;
int cfg_hot_bat_decidegc;
int cfg_temp_comp_mv;
int cfg_temp_comp_ma;
int cfg_safety_time;
int *cfg_thermal_mitigation;
/* status */
bool batt_present;
bool batt_full;
bool batt_hot;
bool batt_cold;
bool batt_warm;
bool batt_cool;
bool usb_present;
bool apsd_enabled;
bool debug_batt;
int chg_disabled_status;
int usb_suspended_status;
int usb_psy_ma;
enum power_supply_type charger_type;
/* others */
bool irq_waiting;
bool resume_completed;
u8 irq_cfg_mask;
u32 peek_poke_address;
int fake_battery_soc;
int therm_lvl_sel;
int thermal_levels;
u32 workaround_flags;
int batt_id_ohm;
bool usb_poll;
const char *bms_psy_name;
struct power_supply *usb_psy;
struct power_supply *bms_psy;
struct power_supply *batt_psy;
struct power_supply_desc usb_psy_desc;
struct iio_channel *batt_id_chan;
struct mutex read_write_lock;
struct mutex irq_complete;
struct mutex chg_disable_lock;
struct mutex usb_suspend_lock;
struct mutex icl_set_lock;
struct dentry *debug_root;
struct delayed_work irq_polling_work;
};
static int usbin_current_ma_table[] = {
100,
300,
500,
650,
900,
1000,
1500,
};
static int fastchg_current_ma_table[] = {
100,
250,
300,
370,
500,
600,
700,
1000,
};
static int iterm_ma_table[] = {
20,
30,
50,
75,
};
static int recharge_mv_table[] = {
150,
80,
};
static int safety_time_min_table[] = {
180,
270,
360,
0,
};
static int vfloat_compensation_mv_table[] = {
80,
120,
160,
200,
};
static int inhibit_mv_table[] = {
0,
265,
385,
512,
};
static int cold_bat_decidegc_table[] = {
100,
50,
0,
-50,
};
static int cool_bat_decidegc_table[] = {
150,
100,
50,
0,
};
static int warm_bat_decidegc_table[] = {
400,
450,
500,
550,
};
static int hot_bat_decidegc_table[] = {
500,
550,
600,
650,
};
#define MIN_FLOAT_MV 3480
#define MAX_FLOAT_MV 4720
#define FLOAT_STEP_MV 40
#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))
/* register mapping definitions */
#define CFG_REG_0 0x00
#define USBIN_ICL_MASK SMB_MASK(4, 2)
#define USBIN_ICL_OFFSET 2
#define ITERM_MASK SMB_MASK(1, 0)
#define CFG_REG_2 0x02
#define RECHARGE_DIS_BIT BIT(7)
#define RECHARGE_THRESH_MASK BIT(6)
#define RECHARGE_THRESH_OFFSET 6
#define ITERM_DIS_BIT BIT(5)
#define FASTCHG_CURR_MASK SMB_MASK(2, 0)
#define CFG_REG_3 0x03
#define FASTCHG_CURR_SOFT_COMP SMB_MASK(7, 5)
#define FASTCHG_CURR_SOFT_COMP_OFFSET 5
#define FLOAT_VOLTAGE_MASK SMB_MASK(4, 0)
#define CFG_REG_4 0x04
#define CHG_EN_ACTIVE_LOW_BIT BIT(5)
#define SAFETY_TIMER_MASK SMB_MASK(4, 3)
#define SAFETY_TIMER_OFFSET 3
#define SAFETY_TIMER_DISABLE SMB_MASK(4, 3)
#define SYSTEM_VOLATGE_MASK SMB_MASK(1, 0)
#define TEMP_MASK SMB_MASK(7, 0)
#define CFG_REG_5 0x05
#define BAT_THERM_DIS_BIT BIT(7)
#define HARD_THERM_NOT_SUSPEND BIT(6)
#define SOFT_THERM_BEHAVIOR_MASK SMB_MASK(5, 4)
#define SOFT_THERM_VFLT_CHG_COMP SMB_MASK(5, 4)
#define VFLOAT_COMP_MASK SMB_MASK(3, 2)
#define VFLOAT_COMP_OFFSET 2
#define APSD_EN_BIT BIT(1)
#define AICL_EN_BIT BIT(0)
#define CFG_REG_6 0x06
#define CHG_INHIBIT_THRESH_MASK SMB_MASK(7, 6)
#define SYS_THRESHOLD_MASK SMB_MASK(5, 4)
#define INHIBIT_THRESH_OFFSET 6
#define BMD_ALGO_MASK SMB_MASK(1, 0)
#define BMD_ALGO_THERM_IO SMB_MASK(1, 0)
#define CFG_REG_7 0x07
#define USB1_5_PIN_CNTRL_BIT BIT(7)
#define USB_AC_PIN_CNTRL_BIT BIT(6)
#define CHG_EN_PIN_CNTRL_BIT BIT(5)
#define SUSPEND_SW_CNTRL_BIT BIT(3)
#define CFG_REG_8 0x08
#define HARD_COLD_TEMP_MASK SMB_MASK(7, 6)
#define HARD_COLD_TEMP_OFFSET 6
#define HARD_HOT_TEMP_MASK SMB_MASK(5, 4)
#define HARD_HOT_TEMP_OFFSET 4
#define SOFT_COLD_TEMP_MASK SMB_MASK(3, 2)
#define SOFT_COLD_TEMP_OFFSET 2
#define SOFT_HOT_TEMP_MASK SMB_MASK(1, 0)
#define SOFT_HOT_TEMP_OFFSET 0
#define IRQ_CFG_REG_9 0x09
#define SAFETY_TIMER_IRQ_EN_BIT BIT(7)
#define BATT_OV_IRQ_EN_BIT BIT(6)
#define BATT_MISSING_IRQ_EN_BIT BIT(5)
#define ITERM_IRQ_EN_BIT BIT(4)
#define HARD_TEMP_IRQ_EN_BIT BIT(3)
#define SOFT_TEMP_IRQ_EN_BIT BIT(2)
#define AICL_DONE_IRQ_EN_BIT BIT(1)
#define INOK_IRQ_EN_BIT BIT(0)
#define I2C_COMM_CFG_REG 0x0a
#define VOLATILE_WRITE_EN_BIT BIT(0)
#define NV_CFG_REG 0x14
#define UNPLUG_RELOAD_DIS_BIT BIT(2)
#define CMD_REG_0 0x30
#define VOLATILE_WRITE_ALLOW BIT(7)
#define USB_SUSPEND_BIT BIT(2)
#define CHARGE_EN_BIT BIT(1)
#define STATE_PIN_OUT_DIS_BIT BIT(0)
#define CMD_REG_1 0x31
#define USB500_MODE_BIT BIT(1)
#define USBAC_MODE_BIT BIT(0)
#define IRQ_A_STATUS_REG 0x38
#define HOT_HARD_BIT BIT(6)
#define COLD_HARD_BIT BIT(4)
#define HOT_SOFT_BIT BIT(2)
#define COLD_SOFT_BIT BIT(0)
#define IRQ_B_STATUS_REG 0x39
#define BATT_OV_BIT BIT(6)
#define BATT_ABSENT_BIT BIT(4)
#define BATT_UV_BIT BIT(2)
#define BATT_P2F_BIT BIT(0)
#define IRQ_C_STATUS_REG 0x3A
#define CHG_ERROR_BIT BIT(6)
#define RECHG_THRESH_BIT BIT(4)
#define TAPER_CHG_BIT BIT(2)
#define ITERM_BIT BIT(0)
#define IRQ_D_STATUS_REG 0x3B
#define APSD_DONE_BIT BIT(6)
#define AICL_DONE_BIT BIT(4)
#define SAFETYTIMER_EXPIRED_BIT BIT(2)
#define CHG_STATUS_A_REG 0x3C
#define USBIN_OV_BIT BIT(6)
#define USBIN_UV_BIT BIT(4)
#define POWER_OK_BIT BIT(2)
#define DIE_TEMP_LIMIT_BIT BIT(0)
#define CHG_STATUS_B_REG 0x3D
#define CHARGE_TYPE_MASK SMB_MASK(2, 1)
#define CHARGE_TYPE_OFFSET 1
#define NO_CHARGE_VAL 0x00
#define PRE_CHARGE_VAL BIT(1)
#define FAST_CHARGE_VAL BIT(2)
#define TAPER_CHARGE_VAL SMB_MASK(2, 1)
#define CHARGE_EN_STS_BIT BIT(0)
#define CHG_STATUS_C_REG 0x3E
#define APSD_STATUS_BIT BIT(4)
#define APSD_RESULT_MASK SMB_MASK(3, 0)
#define CDP_TYPE_VAL BIT(3)
#define DCP_TYPE_VAL BIT(2)
#define SDP_TYPE_VAL BIT(0)
#define OTHERS_TYPE_VAL BIT(1)
#define AICL_STATUS_REG 0x3F
#define AICL_DONE_STS_BIT BIT(6)
#define AICL_RESULT_MASK SMB_MASK(5, 0)
#define AICL_75_MA BIT(3)
#define AICL_100_MA BIT(4)
#define AICL_300_MA BIT(0)
#define AICL_500_MA BIT(1)
#define AICL_650_MA SMB_MASK(1, 0)
#define AICL_900_MA BIT(2)
#define AICL_1000_MA (BIT(2) | BIT(0))
#define AICL_1500_MA (BIT(2) | BIT(1))
#define USB5_LIMIT BIT(4)
#define USB1_LIMIT BIT(5)
struct smb_irq_info {
const char *name;
int (*smb_irq)(struct smb23x_chip *chip, u8 rt_stat);
int high;
int low;
};
struct irq_handler_info {
u8 stat_reg;
u8 val;
u8 prev_val;
struct smb_irq_info irq_info[4];
};
enum {
USER = BIT(0),
CURRENT = BIT(1),
BMS = BIT(2),
THERMAL = BIT(3),
};
enum {
WRKRND_IRQ_POLLING = BIT(0),
WRKRND_USB_SUSPEND = BIT(1),
};
static irqreturn_t smb23x_stat_handler(int irq, void *dev_id);
#define MAX_RW_RETRIES 3
static int __smb23x_read(struct smb23x_chip *chip, u8 reg, u8 *val)
{
int rc, i;
for (i = 0; i < MAX_RW_RETRIES; i++) {
rc = i2c_smbus_read_byte_data(chip->client, reg);
if (rc >= 0)
break;
/* delay between i2c retries */
msleep(20);
}
if (rc < 0) {
pr_err("Reading 0x%02x failed, rc = %d\n", reg, rc);
return rc;
}
*val = rc;
pr_debug("Reading 0x%02x = 0x%02x\n", reg, *val);
return 0;
}
static int __smb23x_write(struct smb23x_chip *chip, u8 reg, u8 val)
{
int rc, i;
for (i = 0; i < MAX_RW_RETRIES; i++) {
rc = i2c_smbus_write_byte_data(chip->client, reg, val);
if (!rc)
break;
/* delay between i2c retries */
msleep(20);
}
if (rc < 0) {
pr_err("Writing val 0x%02x to reg 0x%02x failed, rc = %d\n",
val, reg, rc);
return rc;
}
pr_debug("Writing 0x%02x = 0x%02x\n", reg, val);
return 0;
}
static int smb23x_read(struct smb23x_chip *chip, u8 reg, u8 *val)
{
int rc;
mutex_lock(&chip->read_write_lock);
rc = __smb23x_read(chip, reg, val);
mutex_unlock(&chip->read_write_lock);
return rc;
}
static int smb23x_write(struct smb23x_chip *chip, int reg, int val)
{
int rc;
mutex_lock(&chip->read_write_lock);
rc = __smb23x_write(chip, reg, val);
mutex_unlock(&chip->read_write_lock);
return rc;
}
static int smb23x_masked_write(struct smb23x_chip *chip,
u8 reg, u8 mask, u8 val)
{
int rc;
u8 tmp;
mutex_lock(&chip->read_write_lock);
rc = __smb23x_read(chip, reg, &tmp);
if (rc < 0) {
pr_err("Read failed\n");
goto i2c_error;
}
tmp &= ~mask;
tmp |= val & mask;
rc = __smb23x_write(chip, reg, tmp);
if (rc < 0)
pr_err("Write failed\n");
i2c_error:
mutex_unlock(&chip->read_write_lock);
return rc;
}
static int smb23x_parse_dt(struct smb23x_chip *chip)
{
int rc = 0;
struct device_node *node = chip->dev->of_node;
if (!node) {
pr_err("device node invalid\n");
return (-EINVAL);
}
chip->cfg_charging_disabled =
of_property_read_bool(node, "qcom,charging-disabled");
chip->cfg_recharge_disabled =
of_property_read_bool(node, "qcom,recharge-disabled");
chip->cfg_chg_inhibit_disabled =
of_property_read_bool(node, "qcom,chg-inhibit-disabled");
chip->cfg_iterm_disabled =
of_property_read_bool(node, "qcom,iterm-disabled");
chip->cfg_aicl_disabled =
of_property_read_bool(node, "qcom,aicl-disabled");
chip->cfg_apsd_disabled =
of_property_read_bool(node, "qcom,apsd-disabled");
chip->cfg_bms_controlled_charging =
of_property_read_bool(node, "qcom,bms-controlled-charging");
rc = of_property_read_string(node, "qcom,bms-psy-name",
&chip->bms_psy_name);
if (rc) {
chip->bms_psy_name = NULL;
chip->cfg_bms_controlled_charging = false;
}
/*
* If bms-controlled-charging is defined, then the charging
* termination and recharge behavior will be controlled by
* BMS power supply instead of the SMB chip itself, so we
* need to keep iterm and recharge on the chip disabled.
*/
if (chip->cfg_bms_controlled_charging) {
chip->cfg_iterm_disabled = true;
chip->cfg_recharge_disabled = true;
}
rc = of_property_read_u32(node, "qcom,float-voltage-mv",
&chip->cfg_vfloat_mv);
if (rc < 0) {
chip->cfg_vfloat_mv = -EINVAL;
} else {
if (chip->cfg_vfloat_mv > MAX_FLOAT_MV
|| chip->cfg_vfloat_mv < MIN_FLOAT_MV) {
pr_err("Float voltage out of range\n");
return (-EINVAL);
}
}
rc = of_property_read_u32(node, "qcom,recharge-thresh-mv",
&chip->cfg_resume_delta_mv);
if (rc < 0)
chip->cfg_resume_delta_mv = -EINVAL;
rc = of_property_read_u32(node, "qcom,chg-inhibit-thresh-mv",
&chip->cfg_chg_inhibit_delta_mv);
if (rc < 0)
chip->cfg_chg_inhibit_delta_mv = -EINVAL;
rc = of_property_read_u32(node, "qcom,iterm-ma",
&chip->cfg_iterm_ma);
if (rc < 0)
chip->cfg_iterm_ma = -EINVAL;
rc = of_property_read_u32(node, "qcom,fastchg-ma",
&chip->cfg_fastchg_ma);
if (rc < 0)
chip->cfg_fastchg_ma = -EINVAL;
rc = of_property_read_u32(node, "qcom,cold-bat-decidegc",
&chip->cfg_cold_bat_decidegc);
if (rc < 0)
chip->cfg_cold_bat_decidegc = -EINVAL;
rc = of_property_read_u32(node, "qcom,cool-bat-decidegc",
&chip->cfg_cool_bat_decidegc);
if (rc < 0)
chip->cfg_cool_bat_decidegc = -EINVAL;
rc = of_property_read_u32(node, "qcom,warm-bat-decidegc",
&chip->cfg_warm_bat_decidegc);
if (rc < 0)
chip->cfg_warm_bat_decidegc = -EINVAL;
rc = of_property_read_u32(node, "qcom,hot-bat-decidegc",
&chip->cfg_hot_bat_decidegc);
if (rc < 0)
chip->cfg_hot_bat_decidegc = -EINVAL;
rc = of_property_read_u32(node, "qcom,soft-temp-vfloat-comp-mv",
&chip->cfg_temp_comp_mv);
if (rc < 0)
chip->cfg_temp_comp_mv = -EINVAL;
rc = of_property_read_u32(node, "qcom,soft-temp-current-comp-ma",
&chip->cfg_temp_comp_ma);
if (rc < 0)
chip->cfg_temp_comp_ma = -EINVAL;
rc = of_property_read_u32(node, "qcom,charging-timeout",
&chip->cfg_safety_time);
if (rc < 0)
chip->cfg_safety_time = -EINVAL;
if (of_find_property(node, "qcom,thermal-mitigation",
&chip->thermal_levels)) {
chip->cfg_thermal_mitigation = devm_kzalloc(chip->dev,
chip->thermal_levels,
GFP_KERNEL);
if (chip->cfg_thermal_mitigation == NULL) {
pr_err("thermal mitigation kzalloc() failed.\n");
return (-ENOMEM);
}
chip->thermal_levels /= sizeof(int);
rc = of_property_read_u32_array(node,
"qcom,thermal-mitigation",
chip->cfg_thermal_mitigation,
chip->thermal_levels);
if (rc) {
pr_err("Read therm limits failed, rc=%d\n", rc);
return rc;
}
}
return 0;
}
static int smb23x_enable_volatile_writes(struct smb23x_chip *chip)
{
int rc;
u8 reg;
rc = smb23x_read(chip, I2C_COMM_CFG_REG, &reg);
if (rc < 0) {
pr_err("Read I2C_COMM_CFG_REG, rc=%d\n", rc);
return rc;
}
if (!(reg & VOLATILE_WRITE_EN_BIT)) {
pr_err("Volatile write is not allowed!\n");
return (-EACCES);
}
rc = smb23x_masked_write(chip, CMD_REG_0,
VOLATILE_WRITE_ALLOW, VOLATILE_WRITE_ALLOW);
if (rc < 0) {
pr_err("Set VOLATILE_WRITE_ALLOW bit failed\n");
return rc;
}
return 0;
}
static inline int find_closest_in_ascendant_list(
int val, int lst[], int length)
{
int i;
for (i = 0; i < length; i++) {
if (val <= lst[i])
break;
}
if (i == length)
i--;
return i;
}
static inline int find_closest_in_descendant_list(
int val, int lst[], int length)
{
int i;
for (i = 0; i < length; i++) {
if (val >= lst[i])
break;
}
if (i == length)
i--;
return i;
}
static int __smb23x_charging_disable(struct smb23x_chip *chip, bool disable)
{
int rc;
rc = smb23x_masked_write(chip, CMD_REG_0,
CHARGE_EN_BIT, disable ? 0 : CHARGE_EN_BIT);
if (rc < 0)
pr_err("%s charging failed, rc=%d\n",
disable ? "Disable" : "Enable", rc);
return rc;
}
static int smb23x_charging_disable(struct smb23x_chip *chip,
int reason, int disable)
{
int rc = 0;
int disabled;
mutex_lock(&chip->chg_disable_lock);
disabled = chip->chg_disabled_status;
if (disable)
disabled |= reason;
else
disabled &= ~reason;
rc = __smb23x_charging_disable(chip, disabled ? true : false);
if (rc < 0)
pr_err("%s charging for reason 0x%x failed\n",
disable ? "Disable" : "Enable", reason);
else
chip->chg_disabled_status = disabled;
mutex_unlock(&chip->chg_disable_lock);
return rc;
}
static int smb23x_suspend_usb(struct smb23x_chip *chip,
int reason, bool suspend)
{
int rc = 0;
int suspended;
mutex_lock(&chip->usb_suspend_lock);
suspended = chip->usb_suspended_status;
if (suspend)
suspended |= reason;
else
suspended &= ~reason;
rc = smb23x_masked_write(chip, CMD_REG_0, USB_SUSPEND_BIT,
suspended ? USB_SUSPEND_BIT : 0);
if (rc < 0) {
pr_err("Write USB_SUSPEND failed, rc=%d\n", rc);
} else {
chip->usb_suspended_status = suspended;
pr_debug("%suspend USB!\n", suspend ? "S" : "Un-s");
}
mutex_unlock(&chip->usb_suspend_lock);
return rc;
}
#define CURRENT_SUSPEND 2
#define CURRENT_100_MA 100
#define CURRENT_500_MA 500
#define CURRENT_1500_MA 1500
static int smb23x_set_appropriate_usb_current(struct smb23x_chip *chip)
{
int rc = 0, therm_ma, current_ma;
int usb_current;
u8 tmp;
if ((chip->charger_type == POWER_SUPPLY_TYPE_USB_CDP) ||
(chip->charger_type == POWER_SUPPLY_TYPE_USB_DCP))
chip->usb_psy_ma = CURRENT_1500_MA;
usb_current = chip->usb_psy_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 = chip->cfg_thermal_mitigation[chip->therm_lvl_sel];
else
therm_ma = usb_current;
current_ma = min(therm_ma, usb_current);
if (current_ma <= CURRENT_SUSPEND) {
if (chip->workaround_flags & WRKRND_USB_SUSPEND) {
current_ma = CURRENT_100_MA;
} else {
/* suspend USB input */
rc = smb23x_suspend_usb(chip, CURRENT, true);
if (rc)
pr_err("Suspend USB failed, rc=%d\n", rc);
return rc;
}
}
if (current_ma <= CURRENT_100_MA) {
/* USB 100 */
rc = smb23x_masked_write(chip, CMD_REG_1,
USB500_MODE_BIT | USBAC_MODE_BIT, 0);
if (rc)
pr_err("Set USB100 failed, rc=%d\n", rc);
pr_debug("Setting USB 100\n");
} else if (current_ma <= CURRENT_500_MA) {
/* USB 500 */
rc = smb23x_masked_write(chip, CMD_REG_1,
USB500_MODE_BIT | USBAC_MODE_BIT,
USB500_MODE_BIT);
if (rc)
pr_err("Set USB500 failed, rc=%d\n", rc);
pr_debug("Setting USB 500\n");
} else {
/* USB AC */
rc = smb23x_masked_write(chip, CMD_REG_1,
USBAC_MODE_BIT, USBAC_MODE_BIT);
if (rc)
pr_err("Set USBAC failed, rc=%d\n", rc);
pr_debug("Setting USB AC\n");
}
/* set ICL */
tmp = find_closest_in_ascendant_list(current_ma, usbin_current_ma_table,
ARRAY_SIZE(usbin_current_ma_table));
tmp = tmp << USBIN_ICL_OFFSET;
rc = smb23x_masked_write(chip, CFG_REG_0, USBIN_ICL_MASK, tmp);
if (rc < 0) {
pr_err("Set ICL failed\n, rc=%d\n", rc);
return rc;
}
pr_debug("ICL set to = %d\n",
usbin_current_ma_table[tmp >> USBIN_ICL_OFFSET]);
if (!(chip->workaround_flags & WRKRND_USB_SUSPEND)) {
/* un-suspend USB input */
rc = smb23x_suspend_usb(chip, CURRENT, false);
if (rc < 0)
pr_err("Un-suspend USB failed, rc=%d\n", rc);
}
return rc;
}
static int smb23x_hw_init(struct smb23x_chip *chip)
{
int rc, i = 0;
u8 tmp = 0;
rc = smb23x_enable_volatile_writes(chip);
if (rc < 0) {
pr_err("Enable volatile writes failed, rc=%d\n", rc);
return rc;
}
/* iterm setting */
if (chip->cfg_iterm_disabled) {
rc = smb23x_masked_write(chip, CFG_REG_2,
ITERM_DIS_BIT, ITERM_DIS_BIT);
if (rc < 0) {
pr_err("Disable ITERM failed, rc=%d\n", rc);
return rc;
}
} else if (chip->cfg_iterm_ma != -EINVAL) {
i = find_closest_in_ascendant_list(chip->cfg_iterm_ma,
iterm_ma_table, ARRAY_SIZE(iterm_ma_table));
tmp = i;
rc = smb23x_masked_write(chip, CFG_REG_0, ITERM_MASK, tmp);
if (rc < 0) {
pr_err("Set ITERM failed, rc=%d\n", rc);
return rc;
}
}
/* recharging setting */
if (chip->cfg_recharge_disabled) {
rc = smb23x_masked_write(chip, CFG_REG_2,
RECHARGE_DIS_BIT, RECHARGE_DIS_BIT);
if (rc < 0) {
pr_err("Disable recharging failed, rc=%d\n", rc);
return rc;
}
} else if (chip->cfg_resume_delta_mv != -EINVAL) {
i = find_closest_in_descendant_list(
chip->cfg_resume_delta_mv, recharge_mv_table,
ARRAY_SIZE(recharge_mv_table));
tmp = i << RECHARGE_THRESH_OFFSET;
rc = smb23x_masked_write(chip, CFG_REG_2,
RECHARGE_THRESH_MASK, tmp);
if (rc < 0) {
pr_err("Set recharge thresh failed, rc=%d\n", rc);
return rc;
}
}
/* charging inhibit setting */
if (chip->cfg_chg_inhibit_disabled) {
rc = smb23x_masked_write(chip, CFG_REG_6,
CHG_INHIBIT_THRESH_MASK, 0);
if (rc < 0) {
pr_err("Disable charge inhibit failed, rc=%d\n", rc);
return rc;
}
} else if (chip->cfg_chg_inhibit_delta_mv != -EINVAL) {
i = find_closest_in_ascendant_list(
chip->cfg_chg_inhibit_delta_mv, inhibit_mv_table,
ARRAY_SIZE(inhibit_mv_table));
tmp = i << INHIBIT_THRESH_OFFSET;
rc = smb23x_masked_write(chip, CFG_REG_6,
CHG_INHIBIT_THRESH_MASK, tmp);
if (rc < 0) {
pr_err("Set inhibit threshold failed, rc=%d\n", rc);
return rc;
}
}
/* disable AICL */
if (chip->cfg_aicl_disabled) {
rc = smb23x_masked_write(chip, CFG_REG_5, AICL_EN_BIT, 0);
if (rc < 0) {
pr_err("Disable AICL failed, rc=%d\n", rc);
return rc;
}
}
/* disable APSD */
if (chip->cfg_apsd_disabled) {
rc = smb23x_masked_write(chip, CFG_REG_5, APSD_EN_BIT, 0);
if (rc < 0) {
pr_err("Disable APSD failed, rc=%d\n", rc);
return rc;
}
chip->apsd_enabled = false;
rc = smb23x_masked_write(chip, CFG_REG_5, AICL_EN_BIT, 0);
if (rc < 0) {
pr_err("Disable CFG_5 failed, rc=%d\n", rc);
return rc;
}
} else {
rc = smb23x_read(chip, CFG_REG_5, &tmp);
if (rc < 0) {
pr_err("read CFG_REG_5 failed, rc=%d\n", rc);
return rc;
}
chip->apsd_enabled = !!(tmp & APSD_EN_BIT);
}
/* float voltage setting */
if (chip->cfg_vfloat_mv != -EINVAL) {
tmp = (chip->cfg_vfloat_mv - MIN_FLOAT_MV) / FLOAT_STEP_MV;
rc = smb23x_masked_write(chip, CFG_REG_3,
FLOAT_VOLTAGE_MASK, tmp);
if (rc < 0) {
pr_err("Set float voltage failed, rc=%d\n", rc);
return rc;
}
}
/* fastchg current setting */
if (chip->cfg_fastchg_ma != -EINVAL) {
i = find_closest_in_ascendant_list(
chip->cfg_fastchg_ma, fastchg_current_ma_table,
ARRAY_SIZE(fastchg_current_ma_table));
tmp = i;
rc = smb23x_masked_write(chip, CFG_REG_2,
FASTCHG_CURR_MASK, tmp);
if (rc < 0) {
pr_err("Set fastchg current failed, rc=%d\n", rc);
return rc;
}
}
/* hard JEITA settings */
if (chip->cfg_cold_bat_decidegc != -EINVAL ||
chip->cfg_hot_bat_decidegc != -EINVAL) {
u8 mask = 0;
rc = smb23x_masked_write(chip, CFG_REG_5,
BAT_THERM_DIS_BIT | HARD_THERM_NOT_SUSPEND, 0);
if (rc < 0) {
pr_err("Enable thermal monitor failed, rc=%d\n", rc);
return rc;
}
if (chip->cfg_cold_bat_decidegc != -EINVAL) {
i = find_closest_in_descendant_list(
chip->cfg_cold_bat_decidegc,
cold_bat_decidegc_table,
ARRAY_SIZE(cold_bat_decidegc_table));
mask |= HARD_COLD_TEMP_MASK;
tmp = i << HARD_COLD_TEMP_OFFSET;
}
if (chip->cfg_hot_bat_decidegc != -EINVAL) {
i = find_closest_in_ascendant_list(
chip->cfg_hot_bat_decidegc,
hot_bat_decidegc_table,
ARRAY_SIZE(hot_bat_decidegc_table));
mask |= HARD_HOT_TEMP_MASK;
tmp |= i << HARD_HOT_TEMP_OFFSET;
}
rc = smb23x_masked_write(chip, CFG_REG_8, mask, tmp);
if (rc < 0) {
pr_err("Set hard cold/hot temperature failed, rc=%d\n",
rc);
return rc;
}
}
/* soft JEITA settings */
if (chip->cfg_cool_bat_decidegc != -EINVAL ||
chip->cfg_warm_bat_decidegc != -EINVAL) {
u8 mask = 0;
rc = smb23x_masked_write(chip, CFG_REG_5,
SOFT_THERM_BEHAVIOR_MASK, SOFT_THERM_VFLT_CHG_COMP);
if (rc < 0) {
pr_err("Set soft JEITA behavior failed, rc=%d\n", rc);
return rc;
}
if (chip->cfg_cool_bat_decidegc != -EINVAL) {
i = find_closest_in_descendant_list(
chip->cfg_cool_bat_decidegc,
cool_bat_decidegc_table,
ARRAY_SIZE(cool_bat_decidegc_table));
mask |= SOFT_COLD_TEMP_MASK;
tmp = i << SOFT_COLD_TEMP_OFFSET;
}
if (chip->cfg_warm_bat_decidegc != -EINVAL) {
i = find_closest_in_ascendant_list(
chip->cfg_warm_bat_decidegc,
warm_bat_decidegc_table,
ARRAY_SIZE(warm_bat_decidegc_table));
mask |= SOFT_HOT_TEMP_MASK;
tmp |= i << SOFT_HOT_TEMP_OFFSET;
}
rc = smb23x_masked_write(chip, CFG_REG_8, mask, tmp);
if (rc < 0) {
pr_err("Set soft cold/hot temperature failed, rc=%d\n",
rc);
return rc;
}
}
/* float voltage and fastchg current compensation for soft JEITA */
if (chip->cfg_temp_comp_mv != -EINVAL) {
i = find_closest_in_ascendant_list(
chip->cfg_temp_comp_mv, vfloat_compensation_mv_table,
ARRAY_SIZE(vfloat_compensation_mv_table));
tmp = i << VFLOAT_COMP_OFFSET;
rc = smb23x_masked_write(chip, CFG_REG_5,
VFLOAT_COMP_MASK, tmp);
if (rc < 0) {
pr_err("Set VFLOAT_COMP failed, rc=%d\n", rc);
return rc;
}
}
if (chip->cfg_temp_comp_ma != -EINVAL) {
int compensated_ma;
compensated_ma = chip->cfg_fastchg_ma - chip->cfg_temp_comp_ma;
i = find_closest_in_ascendant_list(
compensated_ma, fastchg_current_ma_table,
ARRAY_SIZE(fastchg_current_ma_table));
tmp = i << FASTCHG_CURR_SOFT_COMP_OFFSET;
rc = smb23x_masked_write(chip, CFG_REG_3,
FASTCHG_CURR_SOFT_COMP, tmp);
if (rc < 0) {
pr_err("Set FASTCHG_COMP failed, rc=%d\n", rc);
return rc;
}
}
/* safety timer setting */
if (chip->cfg_safety_time != -EINVAL) {
i = find_closest_in_ascendant_list(
chip->cfg_safety_time, safety_time_min_table,
ARRAY_SIZE(safety_time_min_table));
tmp = i << SAFETY_TIMER_OFFSET;
/* disable safety timer if the value equals 0 */
if (chip->cfg_safety_time == 0)
tmp = SAFETY_TIMER_DISABLE;
rc = smb23x_masked_write(chip, CFG_REG_4,
SAFETY_TIMER_MASK, tmp);
if (rc < 0) {
pr_err("Set safety timer failed, rc=%d\n", rc);
return rc;
}
}
/*
* Disable the STAT pin output, to make the pin keep at open drain
* state and detect the IRQ on the falling edge
*/
rc = smb23x_masked_write(chip, CMD_REG_0,
STATE_PIN_OUT_DIS_BIT,
STATE_PIN_OUT_DIS_BIT);
if (rc < 0) {
pr_err("Disable state pin output failed, rc=%d\n", rc);
return rc;
}
rc = smb23x_masked_write(chip, CFG_REG_4,
CHG_EN_ACTIVE_LOW_BIT, 0); /* EN active high */
rc |= smb23x_masked_write(chip, CFG_REG_7,
USB1_5_PIN_CNTRL_BIT |
USB_AC_PIN_CNTRL_BIT |
CHG_EN_PIN_CNTRL_BIT |
SUSPEND_SW_CNTRL_BIT,
SUSPEND_SW_CNTRL_BIT); /* suspend ctrl by sw */
if (rc < 0) {
pr_err("Configure board setting failed, rc=%d\n", rc);
return rc;
}
/* Enable necessary IRQs */
rc = smb23x_masked_write(chip, IRQ_CFG_REG_9, 0xff,
SAFETY_TIMER_IRQ_EN_BIT |
BATT_MISSING_IRQ_EN_BIT |
ITERM_IRQ_EN_BIT |
HARD_TEMP_IRQ_EN_BIT |
SOFT_TEMP_IRQ_EN_BIT |
AICL_DONE_IRQ_EN_BIT |
INOK_IRQ_EN_BIT);
if (rc < 0) {
pr_err("Configure IRQ failed, rc=%d\n", rc);
return rc;
}
return rc;
}
static int hot_hard_irq_handler(struct smb23x_chip *chip, u8 rt_sts)
{
pr_warn("rt_sts = 0x02%x\n", rt_sts);
chip->batt_hot = !!rt_sts;
return 0;
}
static int cold_hard_irq_handler(struct smb23x_chip *chip, u8 rt_sts)
{
pr_debug("rt_sts = 0x02%x\n", rt_sts);
chip->batt_cold = !!rt_sts;
return 0;
}
static int hot_soft_irq_handler(struct smb23x_chip *chip, u8 rt_sts)
{
pr_debug("rt_sts = 0x02%x\n", rt_sts);
chip->batt_warm = !!rt_sts;
return 0;
}
static int cold_soft_irq_handler(struct smb23x_chip *chip, u8 rt_sts)
{
pr_debug("rt_sts = 0x02%x\n", rt_sts);
chip->batt_cool = !!rt_sts;
return 0;
}
static int batt_ov_irq_handler(struct smb23x_chip *chip, u8 rt_sts)
{
pr_debug("rt_sts = 0x02%x\n", rt_sts);
return 0;
}
static int batt_missing_irq_handler(struct smb23x_chip *chip, u8 rt_sts)
{
pr_debug("rt_sts = 0x02%x\n", rt_sts);
chip->batt_present = !rt_sts;
return 0;
}
static int batt_low_irq_handler(struct smb23x_chip *chip, u8 rt_sts)
{
pr_warn("rt_sts = 0x02%x\n", rt_sts);
return 0;
}
static int pre_to_fast_irq_handler(struct smb23x_chip *chip, u8 rt_sts)
{
pr_debug("rt_sts = 0x02%x\n", rt_sts);
chip->batt_full = false;
return 0;
}
static int chg_error_irq_handler(struct smb23x_chip *chip, u8 rt_sts)
{
pr_warn("rt_sts = 0x02%x\n", rt_sts);
return 0;
}
static int recharge_irq_handler(struct smb23x_chip *chip, u8 rt_sts)
{
pr_debug("rt_sts = 0x02%x\n", rt_sts);
chip->batt_full = !rt_sts;
return 0;
}
static int taper_irq_handler(struct smb23x_chip *chip, u8 rt_sts)
{
pr_debug("rt_sts = 0x02%x\n", rt_sts);
return 0;
}
static int iterm_irq_handler(struct smb23x_chip *chip, u8 rt_sts)
{
pr_debug("rt_sts = 0x02%x\n", rt_sts);
chip->batt_full = !!rt_sts;
return 0;
}
static const char * const usb_type_str[] = {
"SDP",
"UNKNOWN",
"DCP",
"CDP",
};
static int src_detect_irq_handler(struct smb23x_chip *chip, u8 rt_sts)
{
bool usb_present = !!rt_sts;
if (!chip->apsd_enabled)
return 0;
pr_debug("chip->usb_present = %d, usb_present = %d\n",
chip->usb_present, usb_present);
return 0;
}
static int aicl_done_irq_handler(struct smb23x_chip *chip, u8 rt_sts)
{
pr_debug("rt_sts = 0x02%x\n", rt_sts);
return 0;
}
static int chg_timeout_irq_handler(struct smb23x_chip *chip, u8 rt_sts)
{
pr_debug("rt_sts = 0x02%x\n", rt_sts);
return 0;
}
static int pre_chg_timeout_irq_handler(struct smb23x_chip *chip, u8 rt_sts)
{
pr_debug("rt_sts = 0x02%x\n", rt_sts);
return 0;
}
static int usbin_ov_irq_handler(struct smb23x_chip *chip, u8 rt_sts)
{
bool usb_present = !rt_sts;
int health = !!rt_sts ? POWER_SUPPLY_HEALTH_OVERVOLTAGE :
POWER_SUPPLY_HEALTH_GOOD;
union power_supply_propval pval = {0, };
pr_debug("chip->usb_present = %d, usb_present = %d\n",
chip->usb_present, usb_present);
if (chip->usb_present != usb_present) {
pval.intval = usb_present;
power_supply_set_property(chip->usb_psy,
POWER_SUPPLY_PROP_PRESENT,
&pval);
pval.intval = health;
power_supply_set_property(chip->usb_psy,
POWER_SUPPLY_PROP_HEALTH,
&pval);
}
return 0;
}
#define IRQ_POLLING_MS 500
static void smb23x_irq_polling_work_fn(struct work_struct *work)
{
struct smb23x_chip *chip = container_of(work, struct smb23x_chip,
irq_polling_work.work);
smb23x_stat_handler(chip->client->irq, (void *)chip);
if (chip->usb_present)
schedule_delayed_work(&chip->irq_polling_work,
msecs_to_jiffies(IRQ_POLLING_MS));
}
#define DEBUG_BATT_ID_LOW 6000
#define DEBUG_BATT_ID_HIGH 9000
static bool is_debug_batt_id(struct smb23x_chip *chip)
{
pr_info("DEBUG_BATT: DEBUG BATT ID value is %d\n", chip->batt_id_ohm);
if (is_between(DEBUG_BATT_ID_LOW, DEBUG_BATT_ID_HIGH,
chip->batt_id_ohm))
return true;
return false;
}
/*
* On some of the parts, the Non-volatile register values will
* be reloaded upon unplug event. Even the unplug event won't be
* detected because the IRQ isn't enabled by default in chip
* internally.
* Use polling to detect the unplug event, and after that, redo
* hw_init() to repropgram the software configurations.
*/
static void reconfig_upon_unplug(struct smb23x_chip *chip)
{
int rc;
int reason;
if (is_debug_batt_id(chip))
return;
if (chip->workaround_flags & WRKRND_IRQ_POLLING) {
if (chip->usb_present && chip->usb_poll) {
pm_stay_awake(chip->dev);
schedule_delayed_work(&chip->irq_polling_work,
msecs_to_jiffies(IRQ_POLLING_MS));
chip->usb_poll = false;
} else {
pr_debug("restore software settings after unplug\n");
pm_relax(chip->dev);
rc = smb23x_hw_init(chip);
if (rc)
pr_err("smb23x init upon unplug failed, rc=%d\n",
rc);
/*
* Retore the CHARGE_EN && USB_SUSPEND bit
* according to the status maintained in sw.
*/
mutex_lock(&chip->chg_disable_lock);
reason = chip->chg_disabled_status;
mutex_unlock(&chip->chg_disable_lock);
rc = smb23x_charging_disable(chip, reason,
!!reason ? true : false);
if (rc < 0)
pr_err("%s charging failed\n",
!!reason ? "Disable" : "Enable");
mutex_lock(&chip->usb_suspend_lock);
reason = chip->usb_suspended_status;
mutex_unlock(&chip->usb_suspend_lock);
if (!(chip->workaround_flags & WRKRND_USB_SUSPEND)) {
rc = smb23x_suspend_usb(chip, reason,
!!reason ? true : false);
if (rc < 0)
pr_err("%suspend USB failed\n",
!!reason ? "S" : "Un-s");
}
}
}
}
static int usbin_uv_irq_handler(struct smb23x_chip *chip, u8 rt_sts)
{
bool usb_present = !rt_sts;
union power_supply_propval pval = {0, };
pr_debug("chip->usb_present = %d, usb_present = %d\n",
chip->usb_present, usb_present);
if (chip->usb_present == usb_present)
return 0;
chip->usb_present = usb_present;
if (!chip->cfg_apsd_disabled)
reconfig_upon_unplug(chip);
pval.intval = usb_present;
power_supply_set_property(chip->usb_psy,
POWER_SUPPLY_PROP_PRESENT, &pval);
return 0;
}
static int power_ok_irq_handler(struct smb23x_chip *chip, u8 rt_sts)
{
pr_debug("rt_sts = 0x02%x\n", rt_sts);
return 0;
}
static int die_temp_irq_handler(struct smb23x_chip *chip, u8 rt_sts)
{
pr_warn("rt_sts = 0x02%x\n", rt_sts);
return 0;
}
static struct irq_handler_info handlers[] = {
{ IRQ_A_STATUS_REG, 0, 0,
{
{
.name = "cold_soft",
.smb_irq = cold_soft_irq_handler,
},
{
.name = "hot_soft",
.smb_irq = hot_soft_irq_handler,
},
{
.name = "cold_hard",
.smb_irq = cold_hard_irq_handler,
},
{
.name = "hot_hard",
.smb_irq = hot_hard_irq_handler,
},
},
},
{ IRQ_B_STATUS_REG, 0, 0,
{
{
.name = "p2f",
.smb_irq = pre_to_fast_irq_handler,
},
{
.name = "batt_low",
.smb_irq = batt_low_irq_handler,
},
{
.name = "batt_missing",
.smb_irq = batt_missing_irq_handler,
},
{
.name = "batt_ov",
.smb_irq = batt_ov_irq_handler,
},
},
},
{ IRQ_C_STATUS_REG, 0, 0,
{
{
.name = "iterm",
.smb_irq = iterm_irq_handler,
},
{
.name = "taper",
.smb_irq = taper_irq_handler,
},
{
.name = "recharge",
.smb_irq = recharge_irq_handler,
},
{
.name = "chg_error",
.smb_irq = chg_error_irq_handler,
},
},
},
{ IRQ_D_STATUS_REG, 0, 0,
{
{
.name = "pre_chg_timeout",
.smb_irq = pre_chg_timeout_irq_handler,
},
{
.name = "chg_timeout",
.smb_irq = chg_timeout_irq_handler,
},
{
.name = "aicl_done",
.smb_irq = aicl_done_irq_handler,
},
{
.name = "src_detect",
.smb_irq = src_detect_irq_handler,
},
},
},
{ CHG_STATUS_A_REG, 0, 0,
{
{
.name = "die_temp",
.smb_irq = die_temp_irq_handler,
},
{
.name = "power_ok",
.smb_irq = power_ok_irq_handler,
},
{
.name = "usbin_uv",
.smb_irq = usbin_uv_irq_handler,
},
{
.name = "usbin_ov",
.smb_irq = usbin_ov_irq_handler,
},
},
},
};
#define UPDATE_IRQ_STAT(irq_reg, value) \
handlers[irq_reg - IRQ_A_STATUS_REG].prev_val = value
static int smb23x_determine_initial_status(struct smb23x_chip *chip)
{
int rc = 0;
u8 reg;
union power_supply_propval pval = {0, };
rc = smb23x_read(chip, IRQ_A_STATUS_REG, &reg);
if (rc < 0) {
pr_err("Read IRQ_A failed, rc=%d\n", rc);
return rc;
}
UPDATE_IRQ_STAT(IRQ_A_STATUS_REG, reg);
if (reg & HOT_HARD_BIT)
chip->batt_hot = true;
else if (reg & COLD_HARD_BIT)
chip->batt_cold = true;
else if (reg & HOT_SOFT_BIT)
chip->batt_warm = true;
else if (reg & COLD_SOFT_BIT)
chip->batt_cool = true;
rc = iio_read_channel_processed(chip->batt_id_chan, &chip->batt_id_ohm);
if (rc < 0) {
pr_err("Failed to read BATT_ID over ADC, rc=%d\n", rc);
return rc;
}
if (!is_debug_batt_id(chip))
chip->batt_present = true;
else
chip->batt_present = false;
rc = smb23x_read(chip, IRQ_B_STATUS_REG, &reg);
if (rc < 0) {
pr_err("Read IRQ_B failed, rc=%d\n", rc);
return rc;
}
UPDATE_IRQ_STAT(IRQ_B_STATUS_REG, reg);
if (reg & BATT_ABSENT_BIT)
chip->batt_present = false;
rc = smb23x_read(chip, IRQ_C_STATUS_REG, &reg);
if (rc < 0) {
pr_err("Read IRQ_C failed, rc=%d\n", rc);
return rc;
}
UPDATE_IRQ_STAT(IRQ_C_STATUS_REG, reg);
if (reg & ITERM_BIT)
chip->batt_full = true;
rc = smb23x_read(chip, IRQ_D_STATUS_REG, &reg);
if (rc < 0) {
pr_err("Read IRQ_D failed, rc=%d\n", rc);
return rc;
}
UPDATE_IRQ_STAT(IRQ_D_STATUS_REG, reg);
if (chip->apsd_enabled && (reg & APSD_DONE_BIT))
chip->usb_present = true;
rc = smb23x_read(chip, CHG_STATUS_A_REG, &reg);
if (rc < 0) {
pr_err("Read CHG_STATUS_A failed, rc=%d\n", rc);
return rc;
}
UPDATE_IRQ_STAT(CHG_STATUS_A_REG, reg);
if (!(reg & USBIN_OV_BIT) && !(reg & USBIN_UV_BIT)) {
chip->usb_present = true;
} else if (reg & USBIN_OV_BIT) {
pval.intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
power_supply_set_property(chip->usb_psy,
POWER_SUPPLY_PROP_HEALTH,
&pval);
}
if (chip->usb_present) {
pval.intval = chip->usb_present;
power_supply_set_property(chip->usb_psy,
POWER_SUPPLY_PROP_PRESENT,
&pval);
chip->usb_poll = true;
reconfig_upon_unplug(chip);
}
return rc;
}
static int smb23x_irq_read(struct smb23x_chip *chip)
{
int rc, i;
for (i = 0; i < ARRAY_SIZE(handlers); i++) {
rc = smb23x_read(chip, handlers[i].stat_reg,
&handlers[i].val);
if (rc < 0) {
pr_err("Couldn't read %d rc = %d\n",
handlers[i].stat_reg, rc);
handlers[i].val = 0;
continue;
}
}
return rc;
}
#define IRQ_LATCHED_MASK 0x02
#define IRQ_STATUS_MASK 0x01
#define BITS_PER_IRQ 2
static irqreturn_t smb23x_stat_handler(int irq, void *dev_id)
{
struct smb23x_chip *chip = dev_id;
int i, j;
u8 triggered;
u8 changed;
u8 rt_stat, prev_rt_stat;
int rc;
int handler_count = 0;
pr_debug("entering\n");
mutex_lock(&chip->irq_complete);
chip->irq_waiting = true;
if (!chip->resume_completed) {
pr_debug("IRQ triggered before device-resume\n");
disable_irq_nosync(irq);
mutex_unlock(&chip->irq_complete);
return IRQ_HANDLED;
}
chip->irq_waiting = false;
smb23x_irq_read(chip);
for (i = 0; i < ARRAY_SIZE(handlers); i++) {
for (j = 0; j < ARRAY_SIZE(handlers[i].irq_info); j++) {
triggered = handlers[i].val
& (IRQ_LATCHED_MASK << (j * BITS_PER_IRQ));
rt_stat = handlers[i].val
& (IRQ_STATUS_MASK << (j * BITS_PER_IRQ));
prev_rt_stat = handlers[i].prev_val
& (IRQ_STATUS_MASK << (j * BITS_PER_IRQ));
changed = prev_rt_stat ^ rt_stat;
if (triggered || changed)
rt_stat ? handlers[i].irq_info[j].high++ :
handlers[i].irq_info[j].low++;
if ((triggered || changed)
&& handlers[i].irq_info[j].smb_irq != NULL) {
handler_count++;
rc = handlers[i].irq_info[j].smb_irq(chip,
rt_stat);
if (rc < 0)
pr_err("Couldn't handle %d irq for reg 0x%02x rc = %d\n",
j, handlers[i].stat_reg, rc);
}
}
handlers[i].prev_val = handlers[i].val;
}
pr_debug("handler count = %d\n", handler_count);
if (handler_count) {
pr_debug("batt psy changed\n");
power_supply_changed(chip->batt_psy);
if (chip->usb_psy) {
pr_debug("usb psy changed\n");
power_supply_changed(chip->usb_psy);
}
}
mutex_unlock(&chip->irq_complete);
return IRQ_HANDLED;
}
static enum power_supply_property smb23x_battery_properties[] = {
POWER_SUPPLY_PROP_HEALTH,
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT,
POWER_SUPPLY_PROP_CHARGE_TYPE,
POWER_SUPPLY_PROP_CAPACITY,
POWER_SUPPLY_PROP_TEMP,
POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT,
};
static int smb23x_get_prop_batt_health(struct smb23x_chip *chip)
{
int health;
if (chip->batt_hot)
health = POWER_SUPPLY_HEALTH_OVERHEAT;
else if (chip->batt_cold)
health = POWER_SUPPLY_HEALTH_COLD;
else if (chip->batt_warm)
health = POWER_SUPPLY_HEALTH_WARM;
else if (chip->batt_cool)
health = POWER_SUPPLY_HEALTH_COOL;
else
health = POWER_SUPPLY_HEALTH_GOOD;
return health;
}
static int smb23x_get_prop_batt_status(struct smb23x_chip *chip)
{
int rc, status;
u8 tmp;
if (is_debug_batt_id(chip))
return POWER_SUPPLY_STATUS_NOT_CHARGING;
if (chip->batt_full)
return POWER_SUPPLY_STATUS_FULL;
rc = smb23x_read(chip, CHG_STATUS_B_REG, &tmp);
if (rc < 0) {
pr_err("Read STATUS_B failed, rc=%d\n", rc);
return POWER_SUPPLY_STATUS_UNKNOWN;
}
status = tmp & CHARGE_TYPE_MASK;
return (status == NO_CHARGE_VAL) ? POWER_SUPPLY_STATUS_DISCHARGING :
POWER_SUPPLY_STATUS_CHARGING;
}
static int smb23x_get_prop_charging_enabled(struct smb23x_chip *chip)
{
return !chip->usb_suspended_status;
}
static int smb23x_get_prop_batt_present(struct smb23x_chip *chip)
{
return chip->batt_present ? 1 : 0;
}
static int smb23x_get_prop_charge_type(struct smb23x_chip *chip)
{
int rc, status;
u8 tmp;
rc = smb23x_read(chip, CHG_STATUS_B_REG, &tmp);
if (rc < 0) {
pr_err("Read STATUS_B failed, rc=%d\n", rc);
goto exit;
}
status = tmp & CHARGE_TYPE_MASK;
if (status == NO_CHARGE_VAL)
return POWER_SUPPLY_CHARGE_TYPE_NONE;
else if (status == PRE_CHARGE_VAL)
return POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
else if (status == FAST_CHARGE_VAL)
return POWER_SUPPLY_CHARGE_TYPE_FAST;
else if (status == TAPER_CHARGE_VAL)
return POWER_SUPPLY_CHARGE_TYPE_ADAPTIVE;
exit:
return POWER_SUPPLY_CHARGE_TYPE_UNKNOWN;
}
#define DEFAULT_BATT_CAPACITY 50
#define DEBUG_BATT_CAPACITY 67
static int smb23x_get_prop_batt_capacity(struct smb23x_chip *chip)
{
union power_supply_propval ret = {0, };
if (is_debug_batt_id(chip))
return DEBUG_BATT_CAPACITY;
if (chip->fake_battery_soc != -EINVAL)
return chip->fake_battery_soc;
if (chip->bms_psy) {
power_supply_get_property(chip->bms_psy,
POWER_SUPPLY_PROP_CAPACITY, &ret);
return ret.intval;
}
return DEFAULT_BATT_CAPACITY;
}
#define DEFAULT_BATT_TEMP 280
static int smb23x_get_prop_batt_temp(struct smb23x_chip *chip)
{
union power_supply_propval ret = {0, };
if (chip->bms_psy) {
power_supply_get_property(chip->bms_psy,
POWER_SUPPLY_PROP_TEMP, &ret);
return ret.intval;
}
return DEFAULT_BATT_TEMP;
}
static int smb23x_system_temp_level_set(struct smb23x_chip *chip, int lvl_sel)
{
int rc = 0;
if (!chip->cfg_thermal_mitigation) {
pr_err("Thermal mitigation not supported\n");
return (-EINVAL);
}
if (lvl_sel < 0) {
pr_err("Unsupported level selected %d\n", lvl_sel);
return (-EINVAL);
}
if (lvl_sel >= chip->thermal_levels) {
pr_err("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->icl_set_lock);
chip->therm_lvl_sel = lvl_sel;
rc = smb23x_set_appropriate_usb_current(chip);
if (rc)
pr_err("Couldn't set USB current rc = %d\n", rc);
mutex_unlock(&chip->icl_set_lock);
return rc;
}
static enum power_supply_usb_type smb23x_usb_psy_supported_types[] = {
POWER_SUPPLY_USB_TYPE_UNKNOWN,
POWER_SUPPLY_USB_TYPE_SDP,
POWER_SUPPLY_USB_TYPE_DCP,
POWER_SUPPLY_USB_TYPE_CDP,
POWER_SUPPLY_USB_TYPE_ACA,
};
static enum power_supply_property smb23x_usb_properties[] = {
POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_ONLINE,
POWER_SUPPLY_PROP_CURRENT_MAX,
POWER_SUPPLY_PROP_USB_TYPE,
POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT,
};
static void smb23x_update_desc_type(struct smb23x_chip *chip)
{
switch (chip->charger_type) {
case POWER_SUPPLY_TYPE_USB_CDP:
case POWER_SUPPLY_TYPE_USB_DCP:
case POWER_SUPPLY_TYPE_USB_ACA:
chip->usb_psy_desc.type = chip->charger_type;
reconfig_upon_unplug(chip);
break;
case POWER_SUPPLY_TYPE_USB:
chip->usb_psy_desc.type = chip->charger_type;
break;
default:
chip->usb_psy_desc.type = POWER_SUPPLY_TYPE_USB;
break;
}
}
static void smb23x_get_usb_type(struct smb23x_chip *chip,
union power_supply_propval *val)
{
switch (chip->charger_type) {
case POWER_SUPPLY_TYPE_USB_CDP:
val->intval = POWER_SUPPLY_USB_TYPE_CDP;
break;
case POWER_SUPPLY_TYPE_USB_DCP:
val->intval = POWER_SUPPLY_USB_TYPE_DCP;
break;
case POWER_SUPPLY_TYPE_USB:
val->intval = POWER_SUPPLY_USB_TYPE_SDP;
break;
case POWER_SUPPLY_TYPE_USB_ACA:
val->intval = POWER_SUPPLY_USB_TYPE_ACA;
break;
default:
val->intval = POWER_SUPPLY_USB_TYPE_UNKNOWN;
break;
}
}
static int smb23x_usb_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct smb23x_chip *chip = power_supply_get_drvdata(psy);
switch (psp) {
case POWER_SUPPLY_PROP_CURRENT_MAX:
case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
val->intval = chip->usb_psy_ma;
break;
case POWER_SUPPLY_PROP_PRESENT:
val->intval = chip->usb_present;
break;
case POWER_SUPPLY_PROP_ONLINE:
val->intval = chip->usb_present && !chip->usb_suspended_status;
break;
case POWER_SUPPLY_PROP_USB_TYPE:
smb23x_get_usb_type(chip, val);
break;
default:
return -EINVAL;
}
return 0;
}
static int smb23x_usb_set_property(struct power_supply *psy,
enum power_supply_property psp,
const union power_supply_propval *val)
{
int rc = 0;
struct smb23x_chip *chip = power_supply_get_drvdata(psy);
switch (psp) {
case POWER_SUPPLY_PROP_USB_TYPE:
chip->charger_type = val->intval;
smb23x_update_desc_type(chip);
case POWER_SUPPLY_PROP_CURRENT_MAX:
case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
if (!is_debug_batt_id(chip)) {
chip->usb_psy_ma = val->intval / 1000;
smb23x_enable_volatile_writes(chip);
rc = smb23x_set_appropriate_usb_current(chip);
if (rc)
pr_err("Couldn't set USB current rc = %d\n", rc);
break;
}
case POWER_SUPPLY_PROP_PRESENT:
chip->usb_present = val->intval;
break;
default:
return -EINVAL;
}
return 0;
}
static int smb23x_usb_is_writeable(struct power_supply *psy,
enum power_supply_property psp)
{
switch (psp) {
case POWER_SUPPLY_PROP_CURRENT_MAX:
return 1;
default:
break;
}
return 0;
}
static int smb23x_init_usb_psy(struct smb23x_chip *chip)
{
struct power_supply_config usb_cfg = {};
int rc = 0;
chip->usb_psy_desc.name = "usb",
chip->usb_psy_desc.type = POWER_SUPPLY_TYPE_USB,
chip->usb_psy_desc.properties = smb23x_usb_properties,
chip->usb_psy_desc.num_properties = ARRAY_SIZE(smb23x_usb_properties),
chip->usb_psy_desc.get_property = smb23x_usb_get_property,
chip->usb_psy_desc.set_property = smb23x_usb_set_property,
chip->usb_psy_desc.property_is_writeable = smb23x_usb_is_writeable,
chip->usb_psy_desc.usb_types = smb23x_usb_psy_supported_types,
chip->usb_psy_desc.num_usb_types = ARRAY_SIZE(smb23x_usb_psy_supported_types);
chip->usb_psy_desc.num_properties = ARRAY_SIZE(smb23x_usb_properties);
usb_cfg.drv_data = chip;
usb_cfg.of_node = chip->dev->of_node;
chip->usb_psy = devm_power_supply_register(chip->dev,
&chip->usb_psy_desc,
&usb_cfg);
if (IS_ERR(chip->usb_psy)) {
pr_err("Couldn't register usb power supply\n");
return PTR_ERR(chip->usb_psy);
}
return rc;
}
static int smb23x_battery_get_property(struct power_supply *psy,
enum power_supply_property prop,
union power_supply_propval *val)
{
struct smb23x_chip *chip = power_supply_get_drvdata(psy);
switch (prop) {
case POWER_SUPPLY_PROP_HEALTH:
val->intval = smb23x_get_prop_batt_health(chip);
break;
case POWER_SUPPLY_PROP_STATUS:
val->intval = smb23x_get_prop_batt_status(chip);
break;
case POWER_SUPPLY_PROP_PRESENT:
val->intval = smb23x_get_prop_batt_present(chip);
break;
case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
val->intval = smb23x_get_prop_charging_enabled(chip);
break;
case POWER_SUPPLY_PROP_CHARGE_TYPE:
val->intval = smb23x_get_prop_charge_type(chip);
break;
case POWER_SUPPLY_PROP_CAPACITY:
val->intval = smb23x_get_prop_batt_capacity(chip);
break;
case POWER_SUPPLY_PROP_TEMP:
val->intval = smb23x_get_prop_batt_temp(chip);
break;
case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT:
val->intval = chip->therm_lvl_sel;
break;
default:
return (-EINVAL);
}
pr_debug("get_property: prop(%d) = %d\n", (int)prop, (int)val->intval);
return 0;
}
static int smb23x_battery_set_property(struct power_supply *psy,
enum power_supply_property prop,
const union power_supply_propval *val)
{
struct smb23x_chip *chip = power_supply_get_drvdata(psy);
int rc;
pr_debug("set_property: prop(%d) = %d\n", (int)prop, (int)val->intval);
switch (prop) {
case POWER_SUPPLY_PROP_STATUS:
if (!chip->cfg_bms_controlled_charging)
return (-EINVAL);
switch (val->intval) {
case POWER_SUPPLY_STATUS_FULL:
pr_debug("BMS notify: battery FULL!\n");
chip->batt_full = true;
rc = smb23x_charging_disable(chip, BMS, true);
if (rc < 0) {
pr_err("Disable charging for BMS failed, rc=%d\n",
rc);
return rc;
}
break;
case POWER_SUPPLY_STATUS_CHARGING:
pr_debug("BMS notify: battery CHARGING!\n");
chip->batt_full = false;
rc = smb23x_charging_disable(chip, BMS, false);
if (rc < 0) {
pr_err("Enable charging for BMS failed, rc=%d\n",
rc);
return rc;
}
break;
case POWER_SUPPLY_STATUS_DISCHARGING:
pr_debug("BMS notify: battery DISCHARGING!\n");
chip->batt_full = false;
break;
default:
break;
}
case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
smb23x_suspend_usb(chip, USER, !val->intval);
power_supply_changed(chip->batt_psy);
power_supply_changed(chip->usb_psy);
break;
case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT:
smb23x_system_temp_level_set(chip, val->intval);
break;
case POWER_SUPPLY_PROP_CAPACITY:
chip->fake_battery_soc = val->intval;
power_supply_changed(chip->batt_psy);
break;
default:
return (-EINVAL);
}
return 0;
}
static int smb23x_battery_is_writeable(struct power_supply *psy,
enum power_supply_property prop)
{
int rc;
switch (prop) {
case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT:
case POWER_SUPPLY_PROP_CAPACITY:
rc = 1;
break;
default:
rc = 0;
break;
}
return rc;
}
#define LAST_CNFG_REG 0x1F
static int show_cnfg_regs(struct seq_file *m, void *data)
{
struct smb23x_chip *chip = m->private;
int rc;
u8 reg;
u8 addr;
for (addr = CFG_REG_0; addr <= I2C_COMM_CFG_REG; addr++) {
rc = smb23x_read(chip, addr, &reg);
if (!rc)
seq_printf(m, "0x%02x = 0x%02x\n", addr, reg);
}
return 0;
}
static int cnfg_debugfs_open(struct inode *inode, struct file *file)
{
struct smb23x_chip *chip = inode->i_private;
return single_open(file, show_cnfg_regs, chip);
}
static const struct file_operations cnfg_debugfs_ops = {
.open = cnfg_debugfs_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static int show_cmd_regs(struct seq_file *m, void *data)
{
struct smb23x_chip *chip = m->private;
int rc;
u8 reg;
u8 addr;
for (addr = CMD_REG_0; addr <= CMD_REG_1; addr++) {
rc = smb23x_read(chip, addr, &reg);
if (!rc)
seq_printf(m, "0x%02x = 0x%02x\n", addr, reg);
}
return 0;
}
static int cmd_debugfs_open(struct inode *inode, struct file *file)
{
struct smb23x_chip *chip = inode->i_private;
return single_open(file, show_cmd_regs, chip);
}
static const struct file_operations cmd_debugfs_ops = {
.owner = THIS_MODULE,
.open = cmd_debugfs_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static int show_status_regs(struct seq_file *m, void *data)
{
struct smb23x_chip *chip = m->private;
int rc;
u8 reg;
u8 addr;
for (addr = IRQ_A_STATUS_REG; addr <= AICL_STATUS_REG; addr++) {
rc = smb23x_read(chip, addr, &reg);
if (!rc)
seq_printf(m, "0x%02x = 0x%02x\n", addr, reg);
}
return 0;
}
static int status_debugfs_open(struct inode *inode, struct file *file)
{
struct smb23x_chip *chip = inode->i_private;
return single_open(file, show_status_regs, chip);
}
static const struct file_operations status_debugfs_ops = {
.owner = THIS_MODULE,
.open = status_debugfs_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static int show_irq_count(struct seq_file *m, void *data)
{
int i, j, total = 0;
for (i = 0; i < ARRAY_SIZE(handlers); i++)
for (j = 0; j < 4; j++) {
seq_printf(m, "%s=%d\t(high=%d low=%d)\n",
handlers[i].irq_info[j].name,
handlers[i].irq_info[j].high
+ handlers[i].irq_info[j].low,
handlers[i].irq_info[j].high,
handlers[i].irq_info[j].low);
total += (handlers[i].irq_info[j].high
+ handlers[i].irq_info[j].low);
}
seq_printf(m, "\n\tTotal = %d\n", total);
return 0;
}
static int irq_count_debugfs_open(struct inode *inode, struct file *file)
{
struct smb23x_chip *chip = inode->i_private;
return single_open(file, show_irq_count, chip);
}
static const struct file_operations irq_count_debugfs_ops = {
.owner = THIS_MODULE,
.open = irq_count_debugfs_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static int get_reg(void *data, u64 *val)
{
struct smb23x_chip *chip = data;
int rc;
u8 temp;
rc = smb23x_read(chip, chip->peek_poke_address, &temp);
if (rc < 0) {
pr_err("Couldn't read reg %x rc = %d\n",
chip->peek_poke_address, rc);
return -EAGAIN;
}
*val = temp;
return 0;
}
static int set_reg(void *data, u64 val)
{
struct smb23x_chip *chip = data;
int rc;
u8 temp;
temp = (u8) val;
rc = smb23x_write(chip, chip->peek_poke_address, temp);
if (rc < 0) {
pr_err("Couldn't write 0x%02x to 0x%02x rc= %d\n",
chip->peek_poke_address, temp, rc);
return -EAGAIN;
}
return 0;
}
DEFINE_DEBUGFS_ATTRIBUTE(poke_poke_debug_ops, get_reg, set_reg, "0x%02llx\n");
static int force_irq_set(void *data, u64 val)
{
struct smb23x_chip *chip = data;
smb23x_stat_handler(chip->client->irq, data);
return 0;
}
DEFINE_DEBUGFS_ATTRIBUTE(force_irq_ops, NULL, force_irq_set, "0x%02llx\n");
static int create_debugfs_entries(struct smb23x_chip *chip)
{
chip->debug_root = debugfs_create_dir("smb23x", NULL);
if (!chip->debug_root)
pr_err("Couldn't create debug dir\n");
if (chip->debug_root) {
struct dentry *ent;
ent = debugfs_create_file("config_registers", 0444,
chip->debug_root, chip,
&cnfg_debugfs_ops);
if (!ent)
pr_err("Couldn't create cnfg debug file\n");
ent = debugfs_create_file("status_registers", 0444,
chip->debug_root, chip,
&status_debugfs_ops);
if (!ent)
pr_err("Couldn't create status debug file\n");
ent = debugfs_create_file("cmd_registers", 0444,
chip->debug_root, chip,
&cmd_debugfs_ops);
if (!ent)
pr_err("Couldn't create cmd debug file\n");
debugfs_create_x32("address", 0644,
chip->debug_root,
&(chip->peek_poke_address));
ent = debugfs_create_file("data", 0644,
chip->debug_root, chip,
&poke_poke_debug_ops);
if (!ent)
pr_err("Couldn't create data debug file\n");
ent = debugfs_create_file("force_irq", 0644,
chip->debug_root, chip,
&force_irq_ops);
if (!ent)
pr_err("Couldn't create force_irq debug file\n");
ent = debugfs_create_file("irq_count", 0444,
chip->debug_root, chip,
&irq_count_debugfs_ops);
if (!ent)
pr_err("Couldn't create irq_count debug file\n");
}
return 0;
}
static void smb23x_irq_polling_wa_check(struct smb23x_chip *chip)
{
int rc;
u8 reg;
rc = smb23x_read(chip, NV_CFG_REG, &reg);
if (rc) {
pr_err("Read NV_CFG_REG failed, rc=%d\n", rc);
return;
}
if (!(reg & UNPLUG_RELOAD_DIS_BIT))
chip->workaround_flags |= WRKRND_IRQ_POLLING;
pr_debug("use polling: %d\n", !(reg & UNPLUG_RELOAD_DIS_BIT));
}
static const struct power_supply_desc batt_psy_desc = {
.name = "battery",
.type = POWER_SUPPLY_TYPE_BATTERY,
.properties = smb23x_battery_properties,
.num_properties = ARRAY_SIZE(smb23x_battery_properties),
.get_property = smb23x_battery_get_property,
.set_property = smb23x_battery_set_property,
.property_is_writeable = smb23x_battery_is_writeable,
};
static int smb23x_init_batt_psy(struct smb23x_chip *chip)
{
struct power_supply_config batt_cfg = {};
int rc = 0;
batt_cfg.drv_data = chip;
batt_cfg.of_node = chip->dev->of_node;
chip->batt_psy = devm_power_supply_register(chip->dev,
&batt_psy_desc,
&batt_cfg);
if (IS_ERR(chip->batt_psy)) {
pr_err("Couldn't register battery power supply\n");
return PTR_ERR(chip->batt_psy);
}
return rc;
}
static int smb23x_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
int rc;
struct smb23x_chip *chip;
chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
if (chip == NULL)
return (-ENOMEM);
chip->client = client;
chip->dev = &client->dev;
chip->fake_battery_soc = -EINVAL;
i2c_set_clientdata(client, chip);
mutex_init(&chip->read_write_lock);
mutex_init(&chip->irq_complete);
mutex_init(&chip->chg_disable_lock);
mutex_init(&chip->usb_suspend_lock);
mutex_init(&chip->icl_set_lock);
rc = device_init_wakeup(chip->dev, true);
if (rc < 0) {
dev_err(chip->dev, "init wakeup failed for smb23x chip, rc=%d\n",
rc);
return rc;
}
INIT_DELAYED_WORK(&chip->irq_polling_work, smb23x_irq_polling_work_fn);
/* enable the USB_SUSPEND always */
chip->workaround_flags |= WRKRND_USB_SUSPEND;
rc = smb23x_parse_dt(chip);
if (rc < 0) {
pr_err("Parse DT nodes failed!\n");
goto destroy_mutex;
}
chip->batt_id_chan = devm_iio_channel_get(chip->dev, "batt-id");
if (IS_ERR(chip->batt_id_chan)) {
rc = PTR_ERR(chip->batt_id_chan);
if (rc != -EPROBE_DEFER)
dev_err(chip->dev, "batt-id channel unavailable, rc=%d\n", rc);
chip->batt_id_chan = NULL;
return rc;
}
/*
* Enable register based battery charging as the hw_init moves CHG_EN
* control from pin-based to register based.
*/
rc = smb23x_charging_disable(chip, USER, false);
if (rc < 0) {
pr_err("Register control based charging enable failed\n");
goto destroy_mutex;
}
rc = smb23x_hw_init(chip);
if (rc < 0) {
pr_err("Initialize hardware failed!\n");
goto destroy_mutex;
}
smb23x_irq_polling_wa_check(chip);
smb23x_init_batt_psy(chip);
smb23x_init_usb_psy(chip);
rc = smb23x_determine_initial_status(chip);
if (rc < 0) {
pr_err("Update initial status failed\n");
goto destroy_mutex;
}
if (is_debug_batt_id(chip))
chip->debug_batt = true;
/*
* Disable charging if device tree (USER) requested:
* set USB_SUSPEND to cutoff USB power completely
*/
rc = smb23x_suspend_usb(chip, USER,
(chip->cfg_charging_disabled | chip->debug_batt) ? true : false);
if (rc < 0) {
pr_err("%suspend USB failed\n",
chip->cfg_charging_disabled ? "S" : "Un-s");
goto destroy_mutex;
}
chip->resume_completed = true;
/* Register IRQ */
if (client->irq) {
rc = devm_request_threaded_irq(&client->dev, client->irq, NULL,
smb23x_stat_handler, IRQF_ONESHOT,
"smb23x_stat_irq", chip);
if (rc < 0) {
pr_err("Request IRQ(%d) failed, rc = %d\n",
client->irq, rc);
goto unregister_batt_psy;
}
enable_irq_wake(client->irq);
}
create_debugfs_entries(chip);
pr_info("SMB23x successfully probed batt=%d usb = %d\n",
smb23x_get_prop_batt_present(chip), chip->usb_present);
return 0;
unregister_batt_psy:
power_supply_unregister(chip->batt_psy);
destroy_mutex:
mutex_destroy(&chip->read_write_lock);
mutex_destroy(&chip->irq_complete);
mutex_destroy(&chip->chg_disable_lock);
mutex_destroy(&chip->usb_suspend_lock);
mutex_destroy(&chip->icl_set_lock);
return rc;
}
static int smb23x_freeze(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
struct smb23x_chip *chip = i2c_get_clientdata(client);
pr_debug("Entering hibernation via smb23x freeze\n");
disable_irq(client->irq);
cancel_delayed_work_sync(&chip->irq_polling_work);
return 0;
}
static int smb23x_restore(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
struct smb23x_chip *chip = i2c_get_clientdata(client);
int rc;
pr_debug("Resuming from hibernation via smb23x restore\n");
devm_free_irq(&client->dev, client->irq, chip);
/*
* Enable register based battery charging as the hw_init moves CHG_EN
* control from pin-based to register based.
*/
rc = smb23x_charging_disable(chip, USER, false);
if (rc < 0) {
pr_err("Register control based charging enable failed\n");
return rc;
}
rc = smb23x_hw_init(chip);
if (rc < 0) {
pr_err("smb23x hw init failed rc=%d\n", rc);
return rc;
}
/*
* Disable charging if device tree (USER) requested:
* set USB_SUSPEND to cutoff USB power completely
*/
rc = smb23x_suspend_usb(chip, USER,
chip->cfg_charging_disabled ? true : false);
if (rc < 0) {
pr_err("%suspend USB failed\n",
chip->cfg_charging_disabled ? "S" : "Un-s");
return rc;
}
rc = smb23x_determine_initial_status(chip);
if (rc < 0) {
pr_err("Update initial status failed\n");
return rc;
}
smb23x_irq_polling_wa_check(chip);
/* Register IRQ */
if (client->irq) {
rc = devm_request_threaded_irq(&client->dev, client->irq, NULL,
smb23x_stat_handler, IRQF_ONESHOT,
"smb23x_stat_irq", chip);
if (rc < 0) {
pr_err("Request IRQ(%d) failed, rc = %d\n",
client->irq, rc);
return rc;
}
enable_irq_wake(client->irq);
}
pr_debug("Restoring all hardware context in smb23x restore\n");
return 0;
}
static int smb23x_suspend(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
struct smb23x_chip *chip = i2c_get_clientdata(client);
int rc;
#ifdef CONFIG_DEEPSLEEP
if (pm_suspend_via_firmware()) {
pr_debug("Entering deepsleep via smb23x suspend\n");
smb23x_freeze(dev);
}
#endif
/* Save the current IRQ config */
rc = smb23x_read(chip, IRQ_CFG_REG_9, &chip->irq_cfg_mask);
if (rc < 0)
pr_err("Save irq config failed, rc=%d\n", rc);
/* enable only important IRQs */
rc = smb23x_write(chip, IRQ_CFG_REG_9,
BATT_MISSING_IRQ_EN_BIT | INOK_IRQ_EN_BIT);
if (rc < 0)
pr_err("Set irq_cfg failed, rc = %d\n", rc);
mutex_lock(&chip->irq_complete);
chip->resume_completed = false;
mutex_unlock(&chip->irq_complete);
return 0;
}
static int smb23x_suspend_noirq(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
struct smb23x_chip *chip = i2c_get_clientdata(client);
if (chip->irq_waiting) {
pr_err_ratelimited("Aborting suspend, an interrupt was detected while suspending\n");
return (-EBUSY);
}
return 0;
}
static int smb23x_resume(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
struct smb23x_chip *chip = i2c_get_clientdata(client);
int rc;
rc = smb23x_write(chip, IRQ_CFG_REG_9, chip->irq_cfg_mask);
if (rc)
pr_err("Restore irq cfg reg failed, rc=%d\n", rc);
#ifdef CONFIG_DEEPSLEEP
if (pm_suspend_via_firmware()) {
pr_debug("Resuming from Deepsleep via smb23x resume\n");
smb23x_restore(dev);
}
#endif
mutex_lock(&chip->irq_complete);
chip->resume_completed = true;
if (chip->irq_waiting) {
mutex_unlock(&chip->irq_complete);
smb23x_stat_handler(client->irq, chip);
enable_irq(client->irq);
} else {
mutex_unlock(&chip->irq_complete);
}
return 0;
}
static int smb23x_remove(struct i2c_client *client)
{
struct smb23x_chip *chip = i2c_get_clientdata(client);
power_supply_unregister(chip->batt_psy);
mutex_destroy(&chip->read_write_lock);
mutex_destroy(&chip->irq_complete);
mutex_destroy(&chip->chg_disable_lock);
mutex_destroy(&chip->usb_suspend_lock);
mutex_destroy(&chip->icl_set_lock);
return 0;
}
static const struct dev_pm_ops smb23x_pm_ops = {
.freeze = smb23x_freeze,
.restore = smb23x_restore,
.resume = smb23x_resume,
.suspend_noirq = smb23x_suspend_noirq,
.suspend = smb23x_suspend,
};
static const struct of_device_id smb23x_match_table[] = {
{ .compatible = "qcom,smb231-lbc",},
{ .compatible = "qcom,smb232-lbc",},
{ .compatible = "qcom,smb233-lbc",},
{ },
};
static const struct i2c_device_id smb23x_id[] = {
{"smb23x-lbc", 0},
{},
};
MODULE_DEVICE_TABLE(i2c, smb23x_id);
static struct i2c_driver smb23x_driver = {
.driver = {
.name = "smb23x-lbc",
.of_match_table = smb23x_match_table,
.pm = &smb23x_pm_ops,
},
.probe = smb23x_probe,
.remove = smb23x_remove,
.id_table = smb23x_id,
};
module_i2c_driver(smb23x_driver);
MODULE_DESCRIPTION("SMB23x Linear Battery Charger");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("i2c:smb23x-lbc");