blob: b6378c04029ad01d90d2503dedd9afb0b2544ce3 [file] [log] [blame]
/* Copyright (c) 2015-2016 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) "SMB:%s: " fmt, __func__
#define FEATURE_DOCK_CHARGING
#include <linux/i2c.h>
#include <linux/delay.h>
#include <linux/errno.h>
#include <linux/module.h>
#include <linux/interrupt.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>
#ifdef FEATURE_DOCK_CHARGING
#include <linux/switch.h>
#endif /* FEATURE_DOCK_CHARGING */
#define FEATURE_CHARGING_BY_DUTY_CYCLE
struct smb23x_wakeup_source {
struct wakeup_source source;
unsigned long enabled_bitmap;
spinlock_t ws_lock;
};
enum wakeup_src {
WAKEUP_SRC_IRQ_POLLING = 0,
WAKEUP_SRC_MAX,
};
#define WAKEUP_SRC_MASK (~(~0 << WAKEUP_SRC_MAX))
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_vsystem;
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;
int chg_disabled_status;
int usb_suspended_status;
int usb_psy_ma;
/* 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;
const char *bms_psy_name;
struct power_supply *usb_psy;
struct power_supply *bms_psy;
struct power_supply batt_psy;
#ifdef FEATURE_DOCK_CHARGING
/* switch */
struct switch_dev dock_sdev;
#endif /* FEATURE_DOCK_CHARGING */
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;
struct smb23x_wakeup_source smb23x_ws;
};
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_VOLTAGE_MASK SMB_MASK(1, 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 INHIBIT_THRESH_OFFSET 6
#define SYSTEM_VOLTAGE_THRESH_MASK SMB_MASK(5, 4)
#define VOLTAGE_THRESH_MASK_OFFSET 4
#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),
};
enum system_voltage_type {
SYSTEM_VOLTAGE_TO_4300 = 0,
SYSTEM_VOLTAGE_TO_5500,
SYSTEM_VOLTAGE_TO_VBATT_TRACKING,
SYSTEM_VOLTAGE_TO_VIN,
SYSTEM_VOLTAGE_UNKNOWN
};
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 void smb23x_wakeup_src_init(struct smb23x_chip *chip)
{
spin_lock_init(&chip->smb23x_ws.ws_lock);
wakeup_source_init(&chip->smb23x_ws.source, "smb23x");
}
static void smb23x_stay_awake(struct smb23x_wakeup_source *source,
enum wakeup_src wk_src)
{
unsigned long flags;
spin_lock_irqsave(&source->ws_lock, flags);
if (!__test_and_set_bit(wk_src, &source->enabled_bitmap)) {
__pm_stay_awake(&source->source);
pr_debug("enabled source %s, wakeup_src %d\n",
source->source.name, wk_src);
}
spin_unlock_irqrestore(&source->ws_lock, flags);
}
static void smb23x_relax(struct smb23x_wakeup_source *source,
enum wakeup_src wk_src)
{
unsigned long flags;
spin_lock_irqsave(&source->ws_lock, flags);
if (__test_and_clear_bit(wk_src, &source->enabled_bitmap) &&
!(source->enabled_bitmap & WAKEUP_SRC_MASK)) {
__pm_relax(&source->source);
pr_debug("disabled source %s\n", source->source.name);
}
spin_unlock_irqrestore(&source->ws_lock, flags);
pr_debug("relax source %s, wakeup_src %d\n",
source->source.name, wk_src);
}
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,system-voltage",
&chip->cfg_vsystem);
if (rc < 0) {
chip->cfg_vsystem = -EINVAL;
} else {
if (chip->cfg_vsystem >= SYSTEM_VOLTAGE_UNKNOWN) {
pr_err("System 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!");
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
static int smb23x_set_appropriate_usb_current(struct smb23x_chip *chip)
{
int rc = 0, therm_ma, current_ma;
int usb_current = chip->usb_psy_ma;
u8 tmp;
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;
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;
} 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;
}
}
/* system voltage setting */
if (chip->cfg_vsystem != -EINVAL) {
rc = smb23x_masked_write(chip, CFG_REG_4,
SYSTEM_VOLTAGE_MASK, chip->cfg_vsystem);
if (rc < 0) {
pr_err("Set system voltage failed, rc=%d\n", rc);
return rc;
}
if (chip->cfg_vsystem == SYSTEM_VOLTAGE_TO_VBATT_TRACKING) {
tmp = chip->cfg_vsystem << VOLTAGE_THRESH_MASK_OFFSET;
rc = smb23x_masked_write(chip, CFG_REG_6,
SYSTEM_VOLTAGE_THRESH_MASK, tmp);
if (rc < 0) {
pr_err("Set system voltage threshold 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;
#ifdef FEATURE_CHARGING_BY_DUTY_CYCLE
pr_err ("[CycleCharging] write current = %d\n", fastchg_current_ma_table[i]);
#endif
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 get_usb_supply_type(struct smb23x_chip *chip)
{
int rc;
u8 reg, tmp;
enum power_supply_type type;
rc = smb23x_read(chip, CHG_STATUS_C_REG, &reg);
if (rc < 0) {
pr_err("Read STATUS_C failed, rc=%d\n", rc);
return rc;
}
tmp = reg & APSD_STATUS_BIT;
if (!tmp) {
pr_debug("APSD not completed\n");
return POWER_SUPPLY_TYPE_UNKNOWN;
}
tmp = reg & APSD_RESULT_MASK;
if (tmp == CDP_TYPE_VAL) {
type = POWER_SUPPLY_TYPE_USB_CDP;
tmp = 3;
} else if (tmp == DCP_TYPE_VAL) {
type = POWER_SUPPLY_TYPE_USB_DCP;
tmp = 2;
} else if (tmp == SDP_TYPE_VAL) {
type = POWER_SUPPLY_TYPE_USB;
tmp = 0;
} else {
type = POWER_SUPPLY_TYPE_UNKNOWN;
tmp = 1;
}
pr_debug("Charger type %s detected\n", usb_type_str[tmp]);
return type;
}
#ifdef FEATURE_CHARGING_BY_DUTY_CYCLE
static int duty_cycle = 0;
static bool charging_enable = true;
#endif
static int handle_usb_insertion(struct smb23x_chip *chip)
{
enum power_supply_type usb_type;
usb_type = get_usb_supply_type(chip);
power_supply_set_supply_type(chip->usb_psy, usb_type);
power_supply_set_present(chip->usb_psy, chip->usb_present);
power_supply_set_online(chip->usb_psy, true);
#ifdef FEATURE_CHARGING_BY_DUTY_CYCLE
pr_err("\n[[CycleCharging] set duty_cycle = 0\n");
duty_cycle = 0;
#endif
return 0;
}
static int handle_usb_removal(struct smb23x_chip *chip)
{
power_supply_set_supply_type(chip->usb_psy,
POWER_SUPPLY_TYPE_UNKNOWN);
power_supply_set_present(chip->usb_psy, chip->usb_present);
power_supply_set_online(chip->usb_psy, false);
return 0;
}
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);
if (usb_present && !chip->usb_present) {
chip->usb_present = usb_present;
handle_usb_insertion(chip);
} else if (!usb_present && chip->usb_present) {
chip->usb_present = usb_present;
handle_usb_removal(chip);
}
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;
pr_debug("chip->usb_present = %d, usb_present = %d\n",
chip->usb_present, usb_present);
if (chip->usb_present != usb_present) {
chip->usb_present = usb_present;
power_supply_set_present(chip->usb_psy, usb_present);
power_supply_set_health_state(chip->usb_psy, health);
}
return 0;
}
#ifdef FEATURE_CHARGING_BY_DUTY_CYCLE
unsigned int charging_cycle = 43;
unsigned int discharging_cycle = 17;
#endif
#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));
#ifdef FEATURE_CHARGING_BY_DUTY_CYCLE
if (chip->usb_present && charging_enable)
{
if (duty_cycle == charging_cycle*2)
{
pr_err("discharging cycle start (%d) sec\n", discharging_cycle);
__smb23x_charging_disable(chip, true);
}
if (duty_cycle == (charging_cycle+discharging_cycle)*2)
{
//read_cycle_parameter();
pr_err("charging cycle start (%d) sec\n", charging_cycle);
__smb23x_charging_disable(chip, false);
duty_cycle = 0;
}
duty_cycle++;
}
#endif
}
/*
* 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 (chip->workaround_flags & WRKRND_IRQ_POLLING) {
if (chip->usb_present) {
smb23x_stay_awake(&chip->smb23x_ws,
WAKEUP_SRC_IRQ_POLLING);
schedule_delayed_work(&chip->irq_polling_work,
msecs_to_jiffies(IRQ_POLLING_MS));
} else {
pr_debug("restore software settings after unplug\n");
pr_debug("waiting 300ms for smb231 chip reset\n");
msleep(300);
smb23x_relax(&chip->smb23x_ws, WAKEUP_SRC_IRQ_POLLING);
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;
#ifdef FEATURE_DOCK_CHARGING
int state = 0;
#endif /* FEATURE_DOCK_CHARGING */
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;
reconfig_upon_unplug(chip);
power_supply_set_present(chip->usb_psy, usb_present);
#ifdef FEATURE_DOCK_CHARGING
state = chip->dock_sdev.state;
pr_err("[Alan] chip->chg_present = %d, dock state = %d\n", chip->usb_present, state);
if (chip->usb_present != state) {
switch_set_state(&chip->dock_sdev, chip->usb_present);
pr_err("[Alan] Dock state changed, dock state %d to %d\n", state, chip->dock_sdev.state);
}
#endif /* FEATURE_DOCK_CHARGING */
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;
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;
chip->batt_present = true;
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)
power_supply_set_health_state(chip->usb_psy,
POWER_SUPPLY_HEALTH_OVERVOLTAGE);
if (chip->usb_present && chip->apsd_enabled) {
handle_usb_insertion(chip);
} else if (chip->usb_present) {
power_supply_set_present(chip->usb_psy, chip->usb_present);
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_CHARGING_ENABLED,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
POWER_SUPPLY_PROP_CURRENT_NOW,
POWER_SUPPLY_PROP_BATTERY_CHARGING_ENABLED,
POWER_SUPPLY_PROP_CHARGE_TYPE,
POWER_SUPPLY_PROP_CAPACITY,
POWER_SUPPLY_PROP_TEMP,
POWER_SUPPLY_PROP_TECHNOLOGY,
POWER_SUPPLY_PROP_SYSTEM_TEMP_LEVEL,
};
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 (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_battery_charging_enabled(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);
return !chip->chg_disabled_status;
}
status = tmp & CHARGE_EN_STS_BIT;
if (status && !chip->chg_disabled_status)
return 1;
else
return 0;
}
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_TAPER;
exit:
return POWER_SUPPLY_CHARGE_TYPE_UNKNOWN;
}
#define DEFAULT_BATT_CAPACITY 50
static int smb23x_get_prop_batt_capacity(struct smb23x_chip *chip)
{
union power_supply_propval ret = {0, };
if (chip->fake_battery_soc != -EINVAL)
return chip->fake_battery_soc;
if (chip->bms_psy) {
chip->bms_psy->get_property(chip->bms_psy,
POWER_SUPPLY_PROP_CAPACITY, &ret);
return ret.intval;
}
return DEFAULT_BATT_CAPACITY;
}
#define DEFAULT_BATT_HOT_TEMP 650
#define DEFAULT_BATT_WARM_TEMP 450
#define DEFAULT_BATT_NORM_TEMP 250
static int smb23x_get_prop_batt_temp(struct smb23x_chip *chip)
{
if (chip->batt_hot)
return DEFAULT_BATT_HOT_TEMP;
if (chip->batt_warm)
return DEFAULT_BATT_WARM_TEMP;
return DEFAULT_BATT_NORM_TEMP;
}
#define DEFAULT_BATT_VOLTAGE 3760000
static int smb23x_get_prop_batt_voltage(struct smb23x_chip *chip)
{
union power_supply_propval ret = {0, };
if (chip->bms_psy) {
chip->bms_psy->get_property(chip->bms_psy,
POWER_SUPPLY_PROP_VOLTAGE_NOW, &ret);
return ret.intval;
}
return DEFAULT_BATT_VOLTAGE;
}
#define DEFAULT_BATT_CURRENT 99000
static int smb23x_get_prop_batt_current(struct smb23x_chip *chip)
{
union power_supply_propval ret = {0, };
if (chip->bms_psy) {
chip->bms_psy->get_property(chip->bms_psy,
POWER_SUPPLY_PROP_CURRENT_NOW, &ret);
return ret.intval;
}
return DEFAULT_BATT_CURRENT;
}
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 int smb23x_battery_get_property(struct power_supply *psy,
enum power_supply_property prop,
union power_supply_propval *val)
{
struct smb23x_chip *chip = container_of(psy,
struct smb23x_chip, batt_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_CHARGING_ENABLED:
val->intval = smb23x_get_prop_charging_enabled(chip);
break;
case POWER_SUPPLY_PROP_BATTERY_CHARGING_ENABLED:
val->intval = smb23x_get_prop_battery_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_VOLTAGE_NOW:
val->intval = smb23x_get_prop_batt_voltage(chip);
break;
case POWER_SUPPLY_PROP_CURRENT_NOW:
val->intval = smb23x_get_prop_batt_current(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);
}
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 = container_of(psy,
struct smb23x_chip, batt_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_CHARGING_ENABLED:
smb23x_suspend_usb(chip, USER, !val->intval);
power_supply_changed(&chip->batt_psy);
power_supply_changed(chip->usb_psy);
#ifdef FEATURE_CHARGING_BY_DUTY_CYCLE
charging_enable = val->intval;
pr_err("%s USB charging\n", val->intval ? "Resume" : "Suspend");
#endif
break;
case POWER_SUPPLY_PROP_BATTERY_CHARGING_ENABLED:
smb23x_charging_disable(chip, USER, !val->intval);
#ifdef FEATURE_CHARGING_BY_DUTY_CYCLE
charging_enable = val->intval;
pr_err("%s Battery charging\n", val->intval ? "Enable" : "Disable");
#endif
power_supply_changed(&chip->batt_psy);
break;
case POWER_SUPPLY_PROP_SYSTEM_TEMP_LEVEL:
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_CHARGING_ENABLED:
case POWER_SUPPLY_PROP_BATTERY_CHARGING_ENABLED:
case POWER_SUPPLY_PROP_SYSTEM_TEMP_LEVEL:
case POWER_SUPPLY_PROP_CAPACITY:
rc = 1;
break;
default:
rc = 0;
break;
}
return rc;
}
static void smb23x_external_power_changed(struct power_supply *psy)
{
struct smb23x_chip *chip = container_of(psy,
struct smb23x_chip, batt_psy);
union power_supply_propval prop = {0,};
int icl = 0, rc;
bool online;
if (chip->bms_psy_name && chip->bms_psy == NULL)
chip->bms_psy =
power_supply_get_by_name((char *)chip->bms_psy_name);
#if 0
rc = chip->usb_psy->get_property(chip->usb_psy,
POWER_SUPPLY_PROP_CURRENT_MAX, &prop);
if (rc < 0)
pr_err("Get CURRENT_MAX from usb failed, rc=%d\n", rc);
else
icl = prop.intval / 1000;
pr_debug("current_limit = %d\n", icl);
if (chip->usb_psy_ma != icl) {
mutex_lock(&chip->icl_set_lock);
chip->usb_psy_ma = icl;
rc = smb23x_set_appropriate_usb_current(chip);
mutex_unlock(&chip->icl_set_lock);
if (rc < 0)
pr_err("Set appropriate current failed, rc=%d\n", rc);
}
#endif
icl = 500;
rc = chip->usb_psy->get_property(chip->usb_psy,
POWER_SUPPLY_PROP_PRESENT, &prop);
if (rc < 0)
pr_err("Get present from usb failed, rc=%d\n", rc);
else {
pr_debug("[Alan] usb present value is %d\n", prop.intval);
mutex_lock(&chip->icl_set_lock);
if ( prop.intval == 1 )
chip->usb_psy_ma = icl;
else
chip->usb_psy_ma = 0;
rc = smb23x_set_appropriate_usb_current(chip);
mutex_unlock(&chip->icl_set_lock);
if (rc < 0)
pr_err("Set appropriate current failed, rc=%d\n", rc);
}
rc = chip->usb_psy->get_property(chip->usb_psy,
POWER_SUPPLY_PROP_ONLINE, &prop);
if (rc < 0)
pr_err("Get ONLINE from usb failed, rc=%d\n", rc);
else
online = !!prop.intval;
if (chip->usb_present && chip->usb_psy_ma != 0) {
if (!online && !chip->apsd_enabled)
power_supply_set_online(chip->usb_psy, true);
} else if (online && !chip->apsd_enabled) {
power_supply_set_online(chip->usb_psy, false);
}
}
#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 = {
.owner = THIS_MODULE,
.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_SIMPLE_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_SIMPLE_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", S_IFREG | S_IRUGO,
chip->debug_root, chip,
&cnfg_debugfs_ops);
if (!ent)
pr_err("Couldn't create cnfg debug file\n");
ent = debugfs_create_file("status_registers", S_IFREG | S_IRUGO,
chip->debug_root, chip,
&status_debugfs_ops);
if (!ent)
pr_err("Couldn't create status debug file\n");
ent = debugfs_create_file("cmd_registers", S_IFREG | S_IRUGO,
chip->debug_root, chip,
&cmd_debugfs_ops);
if (!ent)
pr_err("Couldn't create cmd debug file\n");
ent = debugfs_create_x32("address", S_IFREG | S_IWUSR | S_IRUGO,
chip->debug_root,
&(chip->peek_poke_address));
if (!ent)
pr_err("Couldn't create address debug file\n");
ent = debugfs_create_file("data", S_IFREG | S_IWUSR | S_IRUGO,
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",
S_IFREG | S_IWUSR | S_IRUGO,
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", S_IFREG | S_IRUGO,
chip->debug_root, chip,
&irq_count_debugfs_ops);
if (!ent)
pr_err("Couldn't create irq_count debug file\n");
}
return 0;
}
static char *batt_supplied_to[] = {
"bms",
};
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 int smb23x_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
int rc;
struct power_supply *usb_psy;
struct smb23x_chip *chip;
usb_psy = power_supply_get_by_name("usb");
if (!usb_psy) {
dev_dbg(&client->dev, "USB supply not found; defer probe\n");
return (-EPROBE_DEFER);
}
chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
if (chip == NULL)
return (-ENOMEM);
chip->client = client;
chip->dev = &client->dev;
chip->usb_psy = usb_psy;
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);
smb23x_wakeup_src_init(chip);
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;
}
/*
* 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);
/*
* 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");
goto destroy_mutex;
}
rc = smb23x_determine_initial_status(chip);
if (rc < 0) {
pr_err("Update initial status failed\n");
goto destroy_mutex;
}
/* register battery power_supply */
chip->batt_psy.name = "battery";
chip->batt_psy.type = POWER_SUPPLY_TYPE_BATTERY;
chip->batt_psy.get_property = smb23x_battery_get_property;
chip->batt_psy.set_property = smb23x_battery_set_property;
chip->batt_psy.properties = smb23x_battery_properties;
chip->batt_psy.num_properties = ARRAY_SIZE(smb23x_battery_properties);
chip->batt_psy.external_power_changed = smb23x_external_power_changed;
chip->batt_psy.property_is_writeable = smb23x_battery_is_writeable;
if (chip->cfg_bms_controlled_charging) {
chip->batt_psy.supplied_to = batt_supplied_to;
chip->batt_psy.num_supplicants =
ARRAY_SIZE(batt_supplied_to);
}
rc = power_supply_register(chip->dev, &chip->batt_psy);
if (rc < 0) {
pr_err("Register power_supply failed, rc = %d\n", rc);
goto destroy_mutex;
}
#ifdef FEATURE_DOCK_CHARGING
chip->dock_sdev.name = "dock";
rc = switch_dev_register(&chip->dock_sdev);
if (rc < 0) {
pr_err("Couldn't register dock switch rc = %d\n", rc);
goto unregister_batt_psy;
}
#endif /* FEATURE_DOCK_CHARGING */
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);
#ifdef FEATURE_DOCK_CHARGING
goto unregister_switch_dev;
#else /* FEATURE_DOCK_CHARGING */
goto unregister_batt_psy;
#endif /* FEATURE_DOCK_CHARGING */
}
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;
#ifdef FEATURE_DOCK_CHARGING
unregister_switch_dev:
switch_dev_unregister(&chip->dock_sdev);
#endif /* FEATURE_DOCK_CHARGING */
unregister_batt_psy:
power_supply_unregister(&chip->batt_psy);
destroy_mutex:
wakeup_source_trash(&chip->smb23x_ws.source);
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_suspend(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
struct smb23x_chip *chip = i2c_get_clientdata(client);
int rc;
/* Save the current IRQ config */
rc = smb23x_read(chip, IRQ_CFG_REG_9, &chip->irq_cfg_mask);
if (rc)
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);
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);
#ifdef FEATURE_DOCK_CHARGING
switch_dev_unregister(&chip->dock_sdev);
#endif /* FEATURE_DOCK_CHARGING */
power_supply_unregister(&chip->batt_psy);
wakeup_source_trash(&chip->smb23x_ws.source);
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 = {
.resume = smb23x_resume,
.suspend_noirq = smb23x_suspend_noirq,
.suspend = smb23x_suspend,
};
static 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",
.owner = THIS_MODULE,
.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");