4499 lines
108 KiB
C
4499 lines
108 KiB
C
/* Copyright (c) 2013-2015 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) "%s: " fmt, __func__
|
|
|
|
#include <linux/i2c.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/gpio.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/module.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/power_supply.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_gpio.h>
|
|
#include <linux/bitops.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/regulator/driver.h>
|
|
#include <linux/regulator/of_regulator.h>
|
|
#include <linux/regulator/machine.h>
|
|
#include <linux/pinctrl/consumer.h>
|
|
|
|
#define SMB135X_BITS_PER_REG 8
|
|
|
|
/* Mask/Bit helpers */
|
|
#define _SMB135X_MASK(BITS, POS) \
|
|
((unsigned char)(((1 << (BITS)) - 1) << (POS)))
|
|
#define SMB135X_MASK(LEFT_BIT_POS, RIGHT_BIT_POS) \
|
|
_SMB135X_MASK((LEFT_BIT_POS) - (RIGHT_BIT_POS) + 1, \
|
|
(RIGHT_BIT_POS))
|
|
|
|
/* Config registers */
|
|
#define CFG_3_REG 0x03
|
|
#define CHG_ITERM_50MA 0x08
|
|
#define CHG_ITERM_100MA 0x10
|
|
#define CHG_ITERM_150MA 0x18
|
|
#define CHG_ITERM_200MA 0x20
|
|
#define CHG_ITERM_250MA 0x28
|
|
#define CHG_ITERM_300MA 0x00
|
|
#define CHG_ITERM_500MA 0x30
|
|
#define CHG_ITERM_600MA 0x38
|
|
#define CHG_ITERM_MASK SMB135X_MASK(5, 3)
|
|
|
|
#define CFG_4_REG 0x04
|
|
#define CHG_INHIBIT_MASK SMB135X_MASK(7, 6)
|
|
#define CHG_INHIBIT_50MV_VAL 0x00
|
|
#define CHG_INHIBIT_100MV_VAL 0x40
|
|
#define CHG_INHIBIT_200MV_VAL 0x80
|
|
#define CHG_INHIBIT_300MV_VAL 0xC0
|
|
|
|
#define CFG_5_REG 0x05
|
|
#define RECHARGE_200MV_BIT BIT(2)
|
|
#define USB_2_3_BIT BIT(5)
|
|
|
|
#define CFG_A_REG 0x0A
|
|
#define DCIN_INPUT_MASK SMB135X_MASK(4, 0)
|
|
|
|
#define CFG_C_REG 0x0C
|
|
#define USBIN_INPUT_MASK SMB135X_MASK(4, 0)
|
|
#define USBIN_ADAPTER_ALLOWANCE_MASK SMB135X_MASK(7, 5)
|
|
#define ALLOW_5V_ONLY 0x00
|
|
#define ALLOW_5V_OR_9V 0x20
|
|
#define ALLOW_5V_TO_9V 0x40
|
|
#define ALLOW_9V_ONLY 0x60
|
|
|
|
#define CFG_D_REG 0x0D
|
|
|
|
#define CFG_E_REG 0x0E
|
|
#define POLARITY_100_500_BIT BIT(2)
|
|
#define USB_CTRL_BY_PIN_BIT BIT(1)
|
|
#define HVDCP_5_9_BIT BIT(4)
|
|
|
|
#define CFG_11_REG 0x11
|
|
#define PRIORITY_BIT BIT(7)
|
|
#define AUTO_SRC_DET_EN_BIT BIT(0)
|
|
|
|
#define USBIN_DCIN_CFG_REG 0x12
|
|
#define USBIN_SUSPEND_VIA_COMMAND_BIT BIT(6)
|
|
|
|
#define CFG_14_REG 0x14
|
|
#define CHG_EN_BY_PIN_BIT BIT(7)
|
|
#define CHG_EN_ACTIVE_LOW_BIT BIT(6)
|
|
#define PRE_TO_FAST_REQ_CMD_BIT BIT(5)
|
|
#define DISABLE_CURRENT_TERM_BIT BIT(3)
|
|
#define DISABLE_AUTO_RECHARGE_BIT BIT(2)
|
|
#define EN_CHG_INHIBIT_BIT BIT(0)
|
|
|
|
#define CFG_16_REG 0x16
|
|
#define SAFETY_TIME_EN_BIT BIT(5)
|
|
#define SAFETY_TIME_EN_SHIFT 5
|
|
#define SAFETY_TIME_MINUTES_MASK SMB135X_MASK(3, 2)
|
|
#define SAFETY_TIME_MINUTES_SHIFT 2
|
|
|
|
#define CFG_17_REG 0x17
|
|
#define CHG_STAT_DISABLE_BIT BIT(0)
|
|
#define CHG_STAT_ACTIVE_HIGH_BIT BIT(1)
|
|
#define CHG_STAT_IRQ_ONLY_BIT BIT(4)
|
|
|
|
#define CFG_19_REG 0x19
|
|
#define BATT_MISSING_ALGO_BIT BIT(2)
|
|
#define BATT_MISSING_THERM_BIT BIT(1)
|
|
|
|
#define CFG_1A_REG 0x1A
|
|
#define HOT_SOFT_VFLOAT_COMP_EN_BIT BIT(3)
|
|
#define COLD_SOFT_VFLOAT_COMP_EN_BIT BIT(2)
|
|
#define HOT_SOFT_CURRENT_COMP_EN_BIT BIT(1)
|
|
#define COLD_SOFT_CURRENT_COMP_EN_BIT BIT(0)
|
|
|
|
#define CFG_1B_REG 0x1B
|
|
#define COLD_HARD_MASK SMB135X_MASK(7, 6)
|
|
#define COLD_HARD_SHIFT 6
|
|
#define HOT_HARD_MASK SMB135X_MASK(5, 4)
|
|
#define HOT_HARD_SHIFT 4
|
|
#define COLD_SOFT_MASK SMB135X_MASK(3, 2)
|
|
#define COLD_SOFT_SHIFT 2
|
|
#define HOT_SOFT_MASK SMB135X_MASK(1, 0)
|
|
#define HOT_SOFT_SHIFT 0
|
|
|
|
#define VFLOAT_REG 0x1E
|
|
|
|
#define VERSION1_REG 0x2A
|
|
#define VERSION1_MASK SMB135X_MASK(7, 6)
|
|
#define VERSION1_SHIFT 6
|
|
#define VERSION2_REG 0x32
|
|
#define VERSION2_MASK SMB135X_MASK(1, 0)
|
|
#define VERSION3_REG 0x34
|
|
|
|
/* Irq Config registers */
|
|
#define IRQ_CFG_REG 0x07
|
|
#define IRQ_BAT_HOT_COLD_HARD_BIT BIT(7)
|
|
#define IRQ_BAT_HOT_COLD_SOFT_BIT BIT(6)
|
|
#define IRQ_OTG_OVER_CURRENT_BIT BIT(4)
|
|
#define IRQ_USBIN_UV_BIT BIT(2)
|
|
#define IRQ_INTERNAL_TEMPERATURE_BIT BIT(0)
|
|
|
|
#define IRQ2_CFG_REG 0x08
|
|
#define IRQ2_SAFETY_TIMER_BIT BIT(7)
|
|
#define IRQ2_CHG_ERR_BIT BIT(6)
|
|
#define IRQ2_CHG_PHASE_CHANGE_BIT BIT(4)
|
|
#define IRQ2_CHG_INHIBIT_BIT BIT(3)
|
|
#define IRQ2_POWER_OK_BIT BIT(2)
|
|
#define IRQ2_BATT_MISSING_BIT BIT(1)
|
|
#define IRQ2_VBAT_LOW_BIT BIT(0)
|
|
|
|
#define IRQ3_CFG_REG 0x09
|
|
#define IRQ3_RID_DETECT_BIT BIT(4)
|
|
#define IRQ3_SRC_DETECT_BIT BIT(2)
|
|
#define IRQ3_DCIN_UV_BIT BIT(0)
|
|
|
|
#define USBIN_OTG_REG 0x0F
|
|
#define OTG_CNFG_MASK SMB135X_MASK(3, 2)
|
|
#define OTG_CNFG_PIN_CTRL 0x04
|
|
#define OTG_CNFG_COMMAND_CTRL 0x08
|
|
#define OTG_CNFG_AUTO_CTRL 0x0C
|
|
|
|
/* Command Registers */
|
|
#define CMD_I2C_REG 0x40
|
|
#define ALLOW_VOLATILE_BIT BIT(6)
|
|
|
|
#define CMD_INPUT_LIMIT 0x41
|
|
#define USB_SHUTDOWN_BIT BIT(6)
|
|
#define DC_SHUTDOWN_BIT BIT(5)
|
|
#define USE_REGISTER_FOR_CURRENT BIT(2)
|
|
#define USB_100_500_AC_MASK SMB135X_MASK(1, 0)
|
|
#define USB_100_VAL 0x02
|
|
#define USB_500_VAL 0x00
|
|
#define USB_AC_VAL 0x01
|
|
|
|
#define CMD_CHG_REG 0x42
|
|
#define CMD_CHG_EN BIT(1)
|
|
#define OTG_EN BIT(0)
|
|
|
|
/* Status registers */
|
|
#define STATUS_1_REG 0x47
|
|
#define USING_USB_BIT BIT(1)
|
|
#define USING_DC_BIT BIT(0)
|
|
|
|
#define STATUS_4_REG 0x4A
|
|
#define BATT_NET_CHG_CURRENT_BIT BIT(7)
|
|
#define BATT_LESS_THAN_2V BIT(4)
|
|
#define CHG_HOLD_OFF_BIT BIT(3)
|
|
#define CHG_TYPE_MASK SMB135X_MASK(2, 1)
|
|
#define CHG_TYPE_SHIFT 1
|
|
#define BATT_NOT_CHG_VAL 0x0
|
|
#define BATT_PRE_CHG_VAL 0x1
|
|
#define BATT_FAST_CHG_VAL 0x2
|
|
#define BATT_TAPER_CHG_VAL 0x3
|
|
#define CHG_EN_BIT BIT(0)
|
|
|
|
#define STATUS_5_REG 0x4B
|
|
#define CDP_BIT BIT(7)
|
|
#define DCP_BIT BIT(6)
|
|
#define OTHER_BIT BIT(5)
|
|
#define SDP_BIT BIT(4)
|
|
#define ACA_A_BIT BIT(3)
|
|
#define ACA_B_BIT BIT(2)
|
|
#define ACA_C_BIT BIT(1)
|
|
#define ACA_DOCK_BIT BIT(0)
|
|
|
|
#define STATUS_6_REG 0x4C
|
|
#define RID_FLOAT_BIT BIT(3)
|
|
#define RID_A_BIT BIT(2)
|
|
#define RID_B_BIT BIT(1)
|
|
#define RID_C_BIT BIT(0)
|
|
|
|
#define STATUS_8_REG 0x4E
|
|
#define USBIN_9V BIT(5)
|
|
#define USBIN_UNREG BIT(4)
|
|
#define USBIN_LV BIT(3)
|
|
#define DCIN_9V BIT(2)
|
|
#define DCIN_UNREG BIT(1)
|
|
#define DCIN_LV BIT(0)
|
|
|
|
#define STATUS_9_REG 0x4F
|
|
#define REV_MASK SMB135X_MASK(3, 0)
|
|
|
|
/* Irq Status registers */
|
|
#define IRQ_A_REG 0x50
|
|
#define IRQ_A_HOT_HARD_BIT BIT(6)
|
|
#define IRQ_A_COLD_HARD_BIT BIT(4)
|
|
#define IRQ_A_HOT_SOFT_BIT BIT(2)
|
|
#define IRQ_A_COLD_SOFT_BIT BIT(0)
|
|
|
|
#define IRQ_B_REG 0x51
|
|
#define IRQ_B_BATT_TERMINAL_BIT BIT(6)
|
|
#define IRQ_B_BATT_MISSING_BIT BIT(4)
|
|
#define IRQ_B_VBAT_LOW_BIT BIT(2)
|
|
#define IRQ_B_TEMPERATURE_BIT BIT(0)
|
|
|
|
#define IRQ_C_REG 0x52
|
|
#define IRQ_C_TERM_BIT BIT(0)
|
|
#define IRQ_C_FASTCHG_BIT BIT(6)
|
|
|
|
#define IRQ_D_REG 0x53
|
|
#define IRQ_D_TIMEOUT_BIT BIT(2)
|
|
|
|
#define IRQ_E_REG 0x54
|
|
#define IRQ_E_DC_OV_BIT BIT(6)
|
|
#define IRQ_E_DC_UV_BIT BIT(4)
|
|
#define IRQ_E_USB_OV_BIT BIT(2)
|
|
#define IRQ_E_USB_UV_BIT BIT(0)
|
|
|
|
#define IRQ_F_REG 0x55
|
|
#define IRQ_F_POWER_OK_BIT BIT(0)
|
|
|
|
#define IRQ_G_REG 0x56
|
|
#define IRQ_G_SRC_DETECT_BIT BIT(6)
|
|
|
|
enum {
|
|
WRKARND_USB100_BIT = BIT(0),
|
|
WRKARND_APSD_FAIL = BIT(1),
|
|
};
|
|
|
|
enum {
|
|
REV_1 = 1, /* Rev 1.0 */
|
|
REV_1_1 = 2, /* Rev 1.1 */
|
|
REV_2 = 3, /* Rev 2 */
|
|
REV_2_1 = 5, /* Rev 2.1 */
|
|
REV_MAX,
|
|
};
|
|
|
|
static char *revision_str[] = {
|
|
[REV_1] = "rev1",
|
|
[REV_1_1] = "rev1.1",
|
|
[REV_2] = "rev2",
|
|
[REV_2_1] = "rev2.1",
|
|
};
|
|
|
|
enum {
|
|
V_SMB1356,
|
|
V_SMB1357,
|
|
V_SMB1358,
|
|
V_SMB1359,
|
|
V_MAX,
|
|
};
|
|
|
|
static int version_data[] = {
|
|
[V_SMB1356] = V_SMB1356,
|
|
[V_SMB1357] = V_SMB1357,
|
|
[V_SMB1358] = V_SMB1358,
|
|
[V_SMB1359] = V_SMB1359,
|
|
};
|
|
|
|
static char *version_str[] = {
|
|
[V_SMB1356] = "smb1356",
|
|
[V_SMB1357] = "smb1357",
|
|
[V_SMB1358] = "smb1358",
|
|
[V_SMB1359] = "smb1359",
|
|
};
|
|
|
|
enum {
|
|
USER = BIT(0),
|
|
THERMAL = BIT(1),
|
|
CURRENT = BIT(2),
|
|
};
|
|
|
|
enum path_type {
|
|
USB,
|
|
DC,
|
|
};
|
|
|
|
static int chg_time[] = {
|
|
192,
|
|
384,
|
|
768,
|
|
1536,
|
|
};
|
|
|
|
static char *pm_batt_supplied_to[] = {
|
|
"bms",
|
|
};
|
|
|
|
struct smb135x_regulator {
|
|
struct regulator_desc rdesc;
|
|
struct regulator_dev *rdev;
|
|
};
|
|
|
|
struct smb135x_chg {
|
|
struct i2c_client *client;
|
|
struct device *dev;
|
|
struct mutex read_write_lock;
|
|
|
|
u8 revision;
|
|
int version;
|
|
|
|
bool chg_enabled;
|
|
bool chg_disabled_permanently;
|
|
|
|
bool usb_present;
|
|
bool dc_present;
|
|
bool usb_slave_present;
|
|
bool dc_ov;
|
|
|
|
bool bmd_algo_disabled;
|
|
bool iterm_disabled;
|
|
int iterm_ma;
|
|
int vfloat_mv;
|
|
int safety_time;
|
|
int resume_delta_mv;
|
|
int fake_battery_soc;
|
|
struct dentry *debug_root;
|
|
int usb_current_arr_size;
|
|
int *usb_current_table;
|
|
int dc_current_arr_size;
|
|
int *dc_current_table;
|
|
bool inhibit_disabled;
|
|
int fastchg_current_arr_size;
|
|
int *fastchg_current_table;
|
|
int fastchg_ma;
|
|
u8 irq_cfg_mask[3];
|
|
int otg_oc_count;
|
|
struct delayed_work reset_otg_oc_count_work;
|
|
struct mutex otg_oc_count_lock;
|
|
|
|
bool parallel_charger;
|
|
bool parallel_charger_present;
|
|
bool bms_controlled_charging;
|
|
|
|
/* psy */
|
|
struct power_supply *usb_psy;
|
|
int usb_psy_ma;
|
|
int real_usb_psy_ma;
|
|
struct power_supply batt_psy;
|
|
struct power_supply dc_psy;
|
|
struct power_supply parallel_psy;
|
|
struct power_supply *bms_psy;
|
|
int dc_psy_type;
|
|
int dc_psy_ma;
|
|
const char *bms_psy_name;
|
|
|
|
/* status tracking */
|
|
bool chg_done_batt_full;
|
|
bool batt_present;
|
|
bool batt_hot;
|
|
bool batt_cold;
|
|
bool batt_warm;
|
|
bool batt_cool;
|
|
|
|
bool resume_completed;
|
|
bool irq_waiting;
|
|
u32 usb_suspended;
|
|
u32 dc_suspended;
|
|
struct mutex path_suspend_lock;
|
|
|
|
u32 peek_poke_address;
|
|
struct smb135x_regulator otg_vreg;
|
|
int skip_writes;
|
|
int skip_reads;
|
|
u32 workaround_flags;
|
|
bool soft_vfloat_comp_disabled;
|
|
bool soft_current_comp_disabled;
|
|
struct mutex irq_complete;
|
|
struct regulator *therm_bias_vreg;
|
|
struct regulator *usb_pullup_vreg;
|
|
struct delayed_work wireless_insertion_work;
|
|
|
|
unsigned int thermal_levels;
|
|
unsigned int therm_lvl_sel;
|
|
unsigned int *thermal_mitigation;
|
|
unsigned int gamma_setting_num;
|
|
unsigned int *gamma_setting;
|
|
struct mutex current_change_lock;
|
|
|
|
const char *pinctrl_state_name;
|
|
struct pinctrl *smb_pinctrl;
|
|
|
|
bool apsd_rerun;
|
|
bool id_line_not_connected;
|
|
};
|
|
|
|
#define RETRY_COUNT 5
|
|
int retry_sleep_ms[RETRY_COUNT] = {
|
|
10, 20, 30, 40, 50
|
|
};
|
|
|
|
static int __smb135x_read(struct smb135x_chg *chip, int reg,
|
|
u8 *val)
|
|
{
|
|
s32 ret;
|
|
int retry_count = 0;
|
|
|
|
retry:
|
|
ret = i2c_smbus_read_byte_data(chip->client, reg);
|
|
if (ret < 0 && retry_count < RETRY_COUNT) {
|
|
/* sleep for few ms before retrying */
|
|
msleep(retry_sleep_ms[retry_count++]);
|
|
goto retry;
|
|
}
|
|
if (ret < 0) {
|
|
dev_err(chip->dev,
|
|
"i2c read fail: can't read from %02x: %d\n", reg, ret);
|
|
return ret;
|
|
} else {
|
|
*val = ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int __smb135x_write(struct smb135x_chg *chip, int reg,
|
|
u8 val)
|
|
{
|
|
s32 ret;
|
|
int retry_count = 0;
|
|
|
|
retry:
|
|
ret = i2c_smbus_write_byte_data(chip->client, reg, val);
|
|
if (ret < 0 && retry_count < RETRY_COUNT) {
|
|
/* sleep for few ms before retrying */
|
|
msleep(retry_sleep_ms[retry_count++]);
|
|
goto retry;
|
|
}
|
|
if (ret < 0) {
|
|
dev_err(chip->dev,
|
|
"i2c write fail: can't write %02x to %02x: %d\n",
|
|
val, reg, ret);
|
|
return ret;
|
|
}
|
|
pr_debug("Writing 0x%02x=0x%02x\n", reg, val);
|
|
return 0;
|
|
}
|
|
|
|
static int smb135x_read(struct smb135x_chg *chip, int reg,
|
|
u8 *val)
|
|
{
|
|
int rc;
|
|
|
|
if (chip->skip_reads) {
|
|
*val = 0;
|
|
return 0;
|
|
}
|
|
mutex_lock(&chip->read_write_lock);
|
|
pm_stay_awake(chip->dev);
|
|
rc = __smb135x_read(chip, reg, val);
|
|
pm_relax(chip->dev);
|
|
mutex_unlock(&chip->read_write_lock);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int smb135x_write(struct smb135x_chg *chip, int reg,
|
|
u8 val)
|
|
{
|
|
int rc;
|
|
|
|
if (chip->skip_writes)
|
|
return 0;
|
|
|
|
mutex_lock(&chip->read_write_lock);
|
|
pm_stay_awake(chip->dev);
|
|
rc = __smb135x_write(chip, reg, val);
|
|
pm_relax(chip->dev);
|
|
mutex_unlock(&chip->read_write_lock);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int smb135x_masked_write(struct smb135x_chg *chip, int reg,
|
|
u8 mask, u8 val)
|
|
{
|
|
s32 rc;
|
|
u8 temp;
|
|
|
|
if (chip->skip_writes || chip->skip_reads)
|
|
return 0;
|
|
|
|
mutex_lock(&chip->read_write_lock);
|
|
rc = __smb135x_read(chip, reg, &temp);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "read failed: reg=%03X, rc=%d\n", reg, rc);
|
|
goto out;
|
|
}
|
|
temp &= ~mask;
|
|
temp |= val & mask;
|
|
rc = __smb135x_write(chip, reg, temp);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev,
|
|
"write failed: reg=%03X, rc=%d\n", reg, rc);
|
|
}
|
|
out:
|
|
mutex_unlock(&chip->read_write_lock);
|
|
return rc;
|
|
}
|
|
|
|
static int read_revision(struct smb135x_chg *chip, u8 *revision)
|
|
{
|
|
int rc;
|
|
u8 reg;
|
|
|
|
rc = smb135x_read(chip, STATUS_9_REG, ®);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Couldn't read status 9 rc = %d\n", rc);
|
|
return rc;
|
|
}
|
|
*revision = (reg & REV_MASK);
|
|
return 0;
|
|
}
|
|
|
|
static int read_version1(struct smb135x_chg *chip, u8 *version)
|
|
{
|
|
int rc;
|
|
u8 reg;
|
|
|
|
rc = smb135x_read(chip, VERSION1_REG, ®);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Couldn't read version 1 rc = %d\n", rc);
|
|
return rc;
|
|
}
|
|
*version = (reg & VERSION1_MASK) >> VERSION1_SHIFT;
|
|
return 0;
|
|
}
|
|
|
|
static int read_version2(struct smb135x_chg *chip, u8 *version)
|
|
{
|
|
int rc;
|
|
u8 reg;
|
|
|
|
rc = smb135x_read(chip, VERSION2_REG, ®);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Couldn't read version 2 rc = %d\n", rc);
|
|
return rc;
|
|
}
|
|
*version = (reg & VERSION2_MASK);
|
|
return 0;
|
|
}
|
|
|
|
static int read_version3(struct smb135x_chg *chip, u8 *version)
|
|
{
|
|
int rc;
|
|
u8 reg;
|
|
|
|
rc = smb135x_read(chip, VERSION3_REG, ®);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Couldn't read version 3 rc = %d\n", rc);
|
|
return rc;
|
|
}
|
|
*version = reg;
|
|
return 0;
|
|
}
|
|
|
|
#define TRIM_23_REG 0x23
|
|
#define CHECK_USB100_GOOD_BIT BIT(1)
|
|
static bool is_usb100_broken(struct smb135x_chg *chip)
|
|
{
|
|
int rc;
|
|
u8 reg;
|
|
|
|
rc = smb135x_read(chip, TRIM_23_REG, ®);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Couldn't read status 9 rc = %d\n", rc);
|
|
return rc;
|
|
}
|
|
return !!(reg & CHECK_USB100_GOOD_BIT);
|
|
}
|
|
|
|
static bool is_usb_slave_present(struct smb135x_chg *chip)
|
|
{
|
|
bool usb_slave_present;
|
|
u8 reg;
|
|
int rc;
|
|
|
|
if (chip->id_line_not_connected)
|
|
return false;
|
|
|
|
rc = smb135x_read(chip, STATUS_6_REG, ®);
|
|
if (rc < 0) {
|
|
pr_err("Couldn't read stat 6 rc = %d\n", rc);
|
|
return false;
|
|
}
|
|
|
|
if ((reg & (RID_FLOAT_BIT | RID_A_BIT | RID_B_BIT | RID_C_BIT)) == 0)
|
|
usb_slave_present = 1;
|
|
else
|
|
usb_slave_present = 0;
|
|
|
|
pr_debug("stat6= 0x%02x slave_present = %d\n", reg, usb_slave_present);
|
|
return usb_slave_present;
|
|
}
|
|
|
|
static char *usb_type_str[] = {
|
|
"ACA_DOCK", /* bit 0 */
|
|
"ACA_C", /* bit 1 */
|
|
"ACA_B", /* bit 2 */
|
|
"ACA_A", /* bit 3 */
|
|
"SDP", /* bit 4 */
|
|
"OTHER", /* bit 5 */
|
|
"DCP", /* bit 6 */
|
|
"CDP", /* bit 7 */
|
|
"NONE", /* bit 8 error case */
|
|
};
|
|
|
|
/* helper to return the string of USB type */
|
|
static char *get_usb_type_name(u8 stat_5)
|
|
{
|
|
unsigned long stat = stat_5;
|
|
|
|
return usb_type_str[find_first_bit(&stat, SMB135X_BITS_PER_REG)];
|
|
}
|
|
|
|
static enum power_supply_type usb_type_enum[] = {
|
|
POWER_SUPPLY_TYPE_USB_ACA, /* bit 0 */
|
|
POWER_SUPPLY_TYPE_USB_ACA, /* bit 1 */
|
|
POWER_SUPPLY_TYPE_USB_ACA, /* bit 2 */
|
|
POWER_SUPPLY_TYPE_USB_ACA, /* bit 3 */
|
|
POWER_SUPPLY_TYPE_USB, /* bit 4 */
|
|
POWER_SUPPLY_TYPE_UNKNOWN, /* bit 5 */
|
|
POWER_SUPPLY_TYPE_USB_DCP, /* bit 6 */
|
|
POWER_SUPPLY_TYPE_USB_CDP, /* bit 7 */
|
|
POWER_SUPPLY_TYPE_UNKNOWN, /* bit 8 error case, report UNKNWON */
|
|
};
|
|
|
|
/* helper to return enum power_supply_type of USB type */
|
|
static enum power_supply_type get_usb_supply_type(u8 stat_5)
|
|
{
|
|
unsigned long stat = stat_5;
|
|
|
|
return usb_type_enum[find_first_bit(&stat, SMB135X_BITS_PER_REG)];
|
|
}
|
|
|
|
static enum power_supply_property smb135x_battery_properties[] = {
|
|
POWER_SUPPLY_PROP_STATUS,
|
|
POWER_SUPPLY_PROP_PRESENT,
|
|
POWER_SUPPLY_PROP_CHARGING_ENABLED,
|
|
POWER_SUPPLY_PROP_CHARGE_TYPE,
|
|
POWER_SUPPLY_PROP_CAPACITY,
|
|
POWER_SUPPLY_PROP_HEALTH,
|
|
POWER_SUPPLY_PROP_TECHNOLOGY,
|
|
POWER_SUPPLY_PROP_SYSTEM_TEMP_LEVEL,
|
|
};
|
|
|
|
static int smb135x_get_prop_batt_status(struct smb135x_chg *chip)
|
|
{
|
|
int rc;
|
|
int status = POWER_SUPPLY_STATUS_DISCHARGING;
|
|
u8 reg = 0;
|
|
u8 chg_type;
|
|
|
|
if (chip->chg_done_batt_full)
|
|
return POWER_SUPPLY_STATUS_FULL;
|
|
|
|
rc = smb135x_read(chip, STATUS_4_REG, ®);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Unable to read STATUS_4_REG rc = %d\n", rc);
|
|
return POWER_SUPPLY_STATUS_UNKNOWN;
|
|
}
|
|
|
|
if (reg & CHG_HOLD_OFF_BIT) {
|
|
/*
|
|
* when chg hold off happens the battery is
|
|
* not charging
|
|
*/
|
|
status = POWER_SUPPLY_STATUS_NOT_CHARGING;
|
|
goto out;
|
|
}
|
|
|
|
chg_type = (reg & CHG_TYPE_MASK) >> CHG_TYPE_SHIFT;
|
|
|
|
if (chg_type == BATT_NOT_CHG_VAL)
|
|
status = POWER_SUPPLY_STATUS_DISCHARGING;
|
|
else
|
|
status = POWER_SUPPLY_STATUS_CHARGING;
|
|
out:
|
|
pr_debug("STATUS_4_REG=%x\n", reg);
|
|
return status;
|
|
}
|
|
|
|
static int smb135x_get_prop_batt_present(struct smb135x_chg *chip)
|
|
{
|
|
int rc;
|
|
u8 reg;
|
|
|
|
rc = smb135x_read(chip, STATUS_4_REG, ®);
|
|
if (rc < 0)
|
|
return 0;
|
|
|
|
/* treat battery gone if less than 2V */
|
|
if (reg & BATT_LESS_THAN_2V)
|
|
return 0;
|
|
|
|
return chip->batt_present;
|
|
}
|
|
|
|
static int smb135x_get_prop_charge_type(struct smb135x_chg *chip)
|
|
{
|
|
int rc;
|
|
u8 reg;
|
|
u8 chg_type;
|
|
|
|
rc = smb135x_read(chip, STATUS_4_REG, ®);
|
|
if (rc < 0)
|
|
return POWER_SUPPLY_CHARGE_TYPE_UNKNOWN;
|
|
|
|
chg_type = (reg & CHG_TYPE_MASK) >> CHG_TYPE_SHIFT;
|
|
if (chg_type == BATT_NOT_CHG_VAL)
|
|
return POWER_SUPPLY_CHARGE_TYPE_NONE;
|
|
else if (chg_type == BATT_FAST_CHG_VAL)
|
|
return POWER_SUPPLY_CHARGE_TYPE_FAST;
|
|
else if (chg_type == BATT_PRE_CHG_VAL)
|
|
return POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
|
|
else if (chg_type == BATT_TAPER_CHG_VAL)
|
|
return POWER_SUPPLY_CHARGE_TYPE_TAPER;
|
|
|
|
return POWER_SUPPLY_CHARGE_TYPE_NONE;
|
|
}
|
|
|
|
#define DEFAULT_BATT_CAPACITY 50
|
|
static int smb135x_get_prop_batt_capacity(struct smb135x_chg *chip)
|
|
{
|
|
union power_supply_propval ret = {0, };
|
|
|
|
if (chip->fake_battery_soc >= 0)
|
|
return chip->fake_battery_soc;
|
|
if (chip->bms_psy) {
|
|
chip->bms_psy->get_property(chip->bms_psy,
|
|
POWER_SUPPLY_PROP_CAPACITY, &ret);
|
|
return ret.intval;
|
|
}
|
|
|
|
return DEFAULT_BATT_CAPACITY;
|
|
}
|
|
|
|
static int smb135x_get_prop_batt_health(struct smb135x_chg *chip)
|
|
{
|
|
union power_supply_propval ret = {0, };
|
|
|
|
if (chip->batt_hot)
|
|
ret.intval = POWER_SUPPLY_HEALTH_OVERHEAT;
|
|
else if (chip->batt_cold)
|
|
ret.intval = POWER_SUPPLY_HEALTH_COLD;
|
|
else if (chip->batt_warm)
|
|
ret.intval = POWER_SUPPLY_HEALTH_WARM;
|
|
else if (chip->batt_cool)
|
|
ret.intval = POWER_SUPPLY_HEALTH_COOL;
|
|
else
|
|
ret.intval = POWER_SUPPLY_HEALTH_GOOD;
|
|
|
|
return ret.intval;
|
|
}
|
|
|
|
static int smb135x_enable_volatile_writes(struct smb135x_chg *chip)
|
|
{
|
|
int rc;
|
|
|
|
rc = smb135x_masked_write(chip, CMD_I2C_REG,
|
|
ALLOW_VOLATILE_BIT, ALLOW_VOLATILE_BIT);
|
|
if (rc < 0)
|
|
dev_err(chip->dev,
|
|
"Couldn't set VOLATILE_W_PERM_BIT rc=%d\n", rc);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int usb_current_table_smb1356[] = {
|
|
180,
|
|
240,
|
|
270,
|
|
285,
|
|
300,
|
|
330,
|
|
360,
|
|
390,
|
|
420,
|
|
540,
|
|
570,
|
|
600,
|
|
660,
|
|
720,
|
|
840,
|
|
900,
|
|
960,
|
|
1080,
|
|
1110,
|
|
1128,
|
|
1146,
|
|
1170,
|
|
1182,
|
|
1200,
|
|
1230,
|
|
1260,
|
|
1380,
|
|
1440,
|
|
1560,
|
|
1620,
|
|
1680,
|
|
1800
|
|
};
|
|
|
|
static int fastchg_current_table[] = {
|
|
300,
|
|
400,
|
|
450,
|
|
475,
|
|
500,
|
|
550,
|
|
600,
|
|
650,
|
|
700,
|
|
900,
|
|
950,
|
|
1000,
|
|
1100,
|
|
1200,
|
|
1400,
|
|
2700,
|
|
1500,
|
|
1600,
|
|
1800,
|
|
1850,
|
|
1880,
|
|
1910,
|
|
2800,
|
|
1950,
|
|
1970,
|
|
2000,
|
|
2050,
|
|
2100,
|
|
2300,
|
|
2400,
|
|
2500,
|
|
3000
|
|
};
|
|
|
|
static int usb_current_table_smb1357_smb1358[] = {
|
|
300,
|
|
400,
|
|
450,
|
|
475,
|
|
500,
|
|
550,
|
|
600,
|
|
650,
|
|
700,
|
|
900,
|
|
950,
|
|
1000,
|
|
1100,
|
|
1200,
|
|
1400,
|
|
1450,
|
|
1500,
|
|
1600,
|
|
1800,
|
|
1850,
|
|
1880,
|
|
1910,
|
|
1930,
|
|
1950,
|
|
1970,
|
|
2000,
|
|
2050,
|
|
2100,
|
|
2300,
|
|
2400,
|
|
2500,
|
|
3000
|
|
};
|
|
|
|
static int usb_current_table_smb1359[] = {
|
|
300,
|
|
400,
|
|
450,
|
|
475,
|
|
500,
|
|
550,
|
|
600,
|
|
650,
|
|
700,
|
|
900,
|
|
950,
|
|
1000,
|
|
1100,
|
|
1200,
|
|
1400,
|
|
1450,
|
|
1500,
|
|
1600,
|
|
1800,
|
|
1850,
|
|
1880,
|
|
1910,
|
|
1930,
|
|
1950,
|
|
1970,
|
|
2000,
|
|
2050,
|
|
2100,
|
|
2300,
|
|
2400,
|
|
2500
|
|
};
|
|
|
|
static int dc_current_table_smb1356[] = {
|
|
180,
|
|
240,
|
|
270,
|
|
285,
|
|
300,
|
|
330,
|
|
360,
|
|
390,
|
|
420,
|
|
540,
|
|
570,
|
|
600,
|
|
660,
|
|
720,
|
|
840,
|
|
870,
|
|
900,
|
|
960,
|
|
1080,
|
|
1110,
|
|
1128,
|
|
1146,
|
|
1158,
|
|
1170,
|
|
1182,
|
|
1200,
|
|
};
|
|
|
|
static int dc_current_table[] = {
|
|
300,
|
|
400,
|
|
450,
|
|
475,
|
|
500,
|
|
550,
|
|
600,
|
|
650,
|
|
700,
|
|
900,
|
|
950,
|
|
1000,
|
|
1100,
|
|
1200,
|
|
1400,
|
|
1450,
|
|
1500,
|
|
1600,
|
|
1800,
|
|
1850,
|
|
1880,
|
|
1910,
|
|
1930,
|
|
1950,
|
|
1970,
|
|
2000,
|
|
};
|
|
|
|
#define CURRENT_100_MA 100
|
|
#define CURRENT_150_MA 150
|
|
#define CURRENT_500_MA 500
|
|
#define CURRENT_900_MA 900
|
|
#define SUSPEND_CURRENT_MA 2
|
|
|
|
static int __smb135x_usb_suspend(struct smb135x_chg *chip, bool suspend)
|
|
{
|
|
int rc;
|
|
|
|
rc = smb135x_masked_write(chip, CMD_INPUT_LIMIT,
|
|
USB_SHUTDOWN_BIT, suspend ? USB_SHUTDOWN_BIT : 0);
|
|
if (rc < 0)
|
|
dev_err(chip->dev, "Couldn't set cfg 11 rc = %d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
static int __smb135x_dc_suspend(struct smb135x_chg *chip, bool suspend)
|
|
{
|
|
int rc = 0;
|
|
|
|
rc = smb135x_masked_write(chip, CMD_INPUT_LIMIT,
|
|
DC_SHUTDOWN_BIT, suspend ? DC_SHUTDOWN_BIT : 0);
|
|
if (rc < 0)
|
|
dev_err(chip->dev, "Couldn't set cfg 11 rc = %d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
static int smb135x_path_suspend(struct smb135x_chg *chip, enum path_type path,
|
|
int reason, bool suspend)
|
|
{
|
|
int rc = 0;
|
|
int suspended;
|
|
int *path_suspended;
|
|
int (*func)(struct smb135x_chg *chip, bool suspend);
|
|
|
|
mutex_lock(&chip->path_suspend_lock);
|
|
if (path == USB) {
|
|
suspended = chip->usb_suspended;
|
|
path_suspended = &chip->usb_suspended;
|
|
func = __smb135x_usb_suspend;
|
|
} else {
|
|
suspended = chip->dc_suspended;
|
|
path_suspended = &chip->dc_suspended;
|
|
func = __smb135x_dc_suspend;
|
|
}
|
|
|
|
if (suspend == false)
|
|
suspended &= ~reason;
|
|
else
|
|
suspended |= reason;
|
|
|
|
if (*path_suspended && !suspended)
|
|
rc = func(chip, 0);
|
|
if (!(*path_suspended) && suspended)
|
|
rc = func(chip, 1);
|
|
|
|
if (rc)
|
|
dev_err(chip->dev, "Couldn't set/unset suspend for %s path rc = %d\n",
|
|
path == USB ? "usb" : "dc",
|
|
rc);
|
|
else
|
|
*path_suspended = suspended;
|
|
|
|
mutex_unlock(&chip->path_suspend_lock);
|
|
return rc;
|
|
}
|
|
|
|
static int smb135x_get_usb_chg_current(struct smb135x_chg *chip)
|
|
{
|
|
if (chip->usb_suspended)
|
|
return SUSPEND_CURRENT_MA;
|
|
else
|
|
return chip->real_usb_psy_ma;
|
|
}
|
|
#define FCC_MASK SMB135X_MASK(4, 0)
|
|
#define CFG_1C_REG 0x1C
|
|
static int smb135x_get_fastchg_current(struct smb135x_chg *chip)
|
|
{
|
|
u8 reg;
|
|
int rc;
|
|
|
|
rc = smb135x_read(chip, CFG_1C_REG, ®);
|
|
if (rc < 0) {
|
|
pr_debug("cannot read 1c rc = %d\n", rc);
|
|
return 0;
|
|
}
|
|
reg &= FCC_MASK;
|
|
if (reg < 0 || chip->fastchg_current_arr_size == 0
|
|
|| reg > chip->fastchg_current_table[
|
|
chip->fastchg_current_arr_size - 1]) {
|
|
dev_err(chip->dev, "Current table out of range\n");
|
|
return -EINVAL;
|
|
}
|
|
return chip->fastchg_current_table[reg];
|
|
}
|
|
|
|
static int smb135x_set_fastchg_current(struct smb135x_chg *chip,
|
|
int current_ma)
|
|
{
|
|
int i, rc, diff, best, best_diff;
|
|
u8 reg;
|
|
|
|
/*
|
|
* if there is no array loaded or if the smallest current limit is
|
|
* above the requested current, then do nothing
|
|
*/
|
|
if (chip->fastchg_current_arr_size == 0) {
|
|
dev_err(chip->dev, "no table loaded\n");
|
|
return -EINVAL;
|
|
} else if ((current_ma - chip->fastchg_current_table[0]) < 0) {
|
|
dev_err(chip->dev, "invalid current requested\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* use the closest setting under the requested current */
|
|
best = 0;
|
|
best_diff = current_ma - chip->fastchg_current_table[best];
|
|
|
|
for (i = 1; i < chip->fastchg_current_arr_size; i++) {
|
|
diff = current_ma - chip->fastchg_current_table[i];
|
|
if (diff >= 0 && diff < best_diff) {
|
|
best_diff = diff;
|
|
best = i;
|
|
}
|
|
}
|
|
i = best;
|
|
|
|
reg = i & FCC_MASK;
|
|
rc = smb135x_masked_write(chip, CFG_1C_REG, FCC_MASK, reg);
|
|
if (rc < 0)
|
|
dev_err(chip->dev, "cannot write to config c rc = %d\n", rc);
|
|
pr_debug("fastchg current set to %dma\n",
|
|
chip->fastchg_current_table[i]);
|
|
return rc;
|
|
}
|
|
|
|
static int smb135x_set_high_usb_chg_current(struct smb135x_chg *chip,
|
|
int current_ma)
|
|
{
|
|
int i, rc;
|
|
u8 usb_cur_val;
|
|
|
|
for (i = chip->usb_current_arr_size - 1; i >= 0; i--) {
|
|
if (current_ma >= chip->usb_current_table[i])
|
|
break;
|
|
}
|
|
if (i < 0) {
|
|
dev_err(chip->dev,
|
|
"Cannot find %dma current_table using %d\n",
|
|
current_ma, CURRENT_150_MA);
|
|
rc = smb135x_masked_write(chip, CFG_5_REG,
|
|
USB_2_3_BIT, USB_2_3_BIT);
|
|
rc |= smb135x_masked_write(chip, CMD_INPUT_LIMIT,
|
|
USB_100_500_AC_MASK, USB_100_VAL);
|
|
if (rc < 0)
|
|
dev_err(chip->dev, "Couldn't set %dmA rc=%d\n",
|
|
CURRENT_150_MA, rc);
|
|
else
|
|
chip->real_usb_psy_ma = CURRENT_150_MA;
|
|
return rc;
|
|
}
|
|
|
|
usb_cur_val = i & USBIN_INPUT_MASK;
|
|
rc = smb135x_masked_write(chip, CFG_C_REG,
|
|
USBIN_INPUT_MASK, usb_cur_val);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "cannot write to config c rc = %d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
rc = smb135x_masked_write(chip, CMD_INPUT_LIMIT,
|
|
USB_100_500_AC_MASK, USB_AC_VAL);
|
|
if (rc < 0)
|
|
dev_err(chip->dev, "Couldn't write cfg 5 rc = %d\n", rc);
|
|
else
|
|
chip->real_usb_psy_ma = chip->usb_current_table[i];
|
|
return rc;
|
|
}
|
|
|
|
#define MAX_VERSION 0xF
|
|
#define USB_100_PROBLEM_VERSION 0x2
|
|
/* if APSD results are used
|
|
* if SDP is detected it will look at 500mA setting
|
|
* if set it will draw 500mA
|
|
* if unset it will draw 100mA
|
|
* if CDP/DCP it will look at 0x0C setting
|
|
* i.e. values in 0x41[1, 0] does not matter
|
|
*/
|
|
static int smb135x_set_usb_chg_current(struct smb135x_chg *chip,
|
|
int current_ma)
|
|
{
|
|
int rc;
|
|
|
|
pr_debug("USB current_ma = %d\n", current_ma);
|
|
|
|
if (chip->workaround_flags & WRKARND_USB100_BIT) {
|
|
pr_info("USB requested = %dmA using %dmA\n", current_ma,
|
|
CURRENT_500_MA);
|
|
current_ma = CURRENT_500_MA;
|
|
}
|
|
|
|
if (current_ma == 0)
|
|
/* choose the lowest available value of 100mA */
|
|
current_ma = CURRENT_100_MA;
|
|
|
|
if (current_ma == SUSPEND_CURRENT_MA) {
|
|
/* force suspend bit */
|
|
rc = smb135x_path_suspend(chip, USB, CURRENT, true);
|
|
chip->real_usb_psy_ma = SUSPEND_CURRENT_MA;
|
|
goto out;
|
|
}
|
|
if (current_ma < CURRENT_150_MA) {
|
|
/* force 100mA */
|
|
rc = smb135x_masked_write(chip, CFG_5_REG, USB_2_3_BIT, 0);
|
|
rc |= smb135x_masked_write(chip, CMD_INPUT_LIMIT,
|
|
USB_100_500_AC_MASK, USB_100_VAL);
|
|
rc |= smb135x_path_suspend(chip, USB, CURRENT, false);
|
|
chip->real_usb_psy_ma = CURRENT_100_MA;
|
|
goto out;
|
|
}
|
|
/* specific current values */
|
|
if (current_ma == CURRENT_150_MA) {
|
|
rc = smb135x_masked_write(chip, CFG_5_REG,
|
|
USB_2_3_BIT, USB_2_3_BIT);
|
|
rc |= smb135x_masked_write(chip, CMD_INPUT_LIMIT,
|
|
USB_100_500_AC_MASK, USB_100_VAL);
|
|
rc |= smb135x_path_suspend(chip, USB, CURRENT, false);
|
|
chip->real_usb_psy_ma = CURRENT_150_MA;
|
|
goto out;
|
|
}
|
|
if (current_ma == CURRENT_500_MA) {
|
|
rc = smb135x_masked_write(chip, CFG_5_REG, USB_2_3_BIT, 0);
|
|
rc |= smb135x_masked_write(chip, CMD_INPUT_LIMIT,
|
|
USB_100_500_AC_MASK, USB_500_VAL);
|
|
rc |= smb135x_path_suspend(chip, USB, CURRENT, false);
|
|
chip->real_usb_psy_ma = CURRENT_500_MA;
|
|
goto out;
|
|
}
|
|
if (current_ma == CURRENT_900_MA) {
|
|
rc = smb135x_masked_write(chip, CFG_5_REG,
|
|
USB_2_3_BIT, USB_2_3_BIT);
|
|
rc |= smb135x_masked_write(chip, CMD_INPUT_LIMIT,
|
|
USB_100_500_AC_MASK, USB_500_VAL);
|
|
rc |= smb135x_path_suspend(chip, USB, CURRENT, false);
|
|
chip->real_usb_psy_ma = CURRENT_900_MA;
|
|
goto out;
|
|
}
|
|
|
|
rc = smb135x_set_high_usb_chg_current(chip, current_ma);
|
|
rc |= smb135x_path_suspend(chip, USB, CURRENT, false);
|
|
out:
|
|
if (rc < 0)
|
|
dev_err(chip->dev,
|
|
"Couldn't set %dmA rc = %d\n", current_ma, rc);
|
|
return rc;
|
|
}
|
|
|
|
static int smb135x_set_dc_chg_current(struct smb135x_chg *chip,
|
|
int current_ma)
|
|
{
|
|
int i, rc;
|
|
u8 dc_cur_val;
|
|
|
|
for (i = chip->dc_current_arr_size - 1; i >= 0; i--) {
|
|
if (chip->dc_psy_ma >= chip->dc_current_table[i])
|
|
break;
|
|
}
|
|
dc_cur_val = i & DCIN_INPUT_MASK;
|
|
rc = smb135x_masked_write(chip, CFG_A_REG,
|
|
DCIN_INPUT_MASK, dc_cur_val);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Couldn't set dc charge current rc = %d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int smb135x_set_appropriate_current(struct smb135x_chg *chip,
|
|
enum path_type path)
|
|
{
|
|
int therm_ma, current_ma;
|
|
int path_current = (path == USB) ? chip->usb_psy_ma : chip->dc_psy_ma;
|
|
int (*func)(struct smb135x_chg *chip, int current_ma);
|
|
int rc = 0;
|
|
|
|
if (!chip->usb_psy && path == USB)
|
|
return 0;
|
|
|
|
/*
|
|
* If battery is absent do not modify the current at all, these
|
|
* would be some appropriate values set by the bootloader or default
|
|
* configuration and since it is the only source of power we should
|
|
* not change it
|
|
*/
|
|
if (!chip->batt_present) {
|
|
pr_debug("ignoring current request since battery is absent\n");
|
|
return 0;
|
|
}
|
|
|
|
if (path == USB) {
|
|
path_current = chip->usb_psy_ma;
|
|
func = smb135x_set_usb_chg_current;
|
|
} else {
|
|
path_current = chip->dc_psy_ma;
|
|
func = smb135x_set_dc_chg_current;
|
|
if (chip->dc_psy_type == -EINVAL)
|
|
func = NULL;
|
|
}
|
|
|
|
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->thermal_mitigation[chip->therm_lvl_sel];
|
|
else
|
|
therm_ma = path_current;
|
|
|
|
current_ma = min(therm_ma, path_current);
|
|
if (func != NULL)
|
|
rc = func(chip, current_ma);
|
|
if (rc < 0)
|
|
dev_err(chip->dev, "Couldn't set %s current to min(%d, %d)rc = %d\n",
|
|
path == USB ? "usb" : "dc",
|
|
therm_ma, path_current,
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
static int smb135x_charging_enable(struct smb135x_chg *chip, int enable)
|
|
{
|
|
int rc;
|
|
|
|
rc = smb135x_masked_write(chip, CMD_CHG_REG,
|
|
CMD_CHG_EN, enable ? CMD_CHG_EN : 0);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev,
|
|
"Couldn't set CHG_ENABLE_BIT enable = %d rc = %d\n",
|
|
enable, rc);
|
|
return rc;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int __smb135x_charging(struct smb135x_chg *chip, int enable)
|
|
{
|
|
int rc = 0;
|
|
|
|
pr_debug("charging enable = %d\n", enable);
|
|
|
|
if (chip->chg_disabled_permanently) {
|
|
pr_debug("charging is disabled permanetly\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
rc = smb135x_charging_enable(chip, enable);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev,
|
|
"Couldn't %s charging rc = %d\n",
|
|
enable ? "enable" : "disable", rc);
|
|
return rc;
|
|
}
|
|
chip->chg_enabled = enable;
|
|
|
|
/* set the suspended status */
|
|
rc = smb135x_path_suspend(chip, DC, USER, !enable);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev,
|
|
"Couldn't set dc suspend to %d rc = %d\n",
|
|
enable, rc);
|
|
return rc;
|
|
}
|
|
rc = smb135x_path_suspend(chip, USB, USER, !enable);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev,
|
|
"Couldn't set usb suspend to %d rc = %d\n",
|
|
enable, rc);
|
|
return rc;
|
|
}
|
|
|
|
pr_debug("charging %s\n",
|
|
enable ? "enabled" : "disabled running from batt");
|
|
return rc;
|
|
}
|
|
|
|
static int smb135x_charging(struct smb135x_chg *chip, int enable)
|
|
{
|
|
int rc = 0;
|
|
|
|
pr_debug("charging enable = %d\n", enable);
|
|
|
|
__smb135x_charging(chip, enable);
|
|
|
|
if (chip->usb_psy) {
|
|
pr_debug("usb psy changed\n");
|
|
power_supply_changed(chip->usb_psy);
|
|
}
|
|
if (chip->dc_psy_type != -EINVAL) {
|
|
pr_debug("dc psy changed\n");
|
|
power_supply_changed(&chip->dc_psy);
|
|
}
|
|
pr_debug("charging %s\n",
|
|
enable ? "enabled" : "disabled running from batt");
|
|
return rc;
|
|
}
|
|
|
|
static int smb135x_system_temp_level_set(struct smb135x_chg *chip,
|
|
int lvl_sel)
|
|
{
|
|
int rc = 0;
|
|
int prev_therm_lvl;
|
|
|
|
if (!chip->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->current_change_lock);
|
|
prev_therm_lvl = chip->therm_lvl_sel;
|
|
chip->therm_lvl_sel = lvl_sel;
|
|
if (chip->therm_lvl_sel == (chip->thermal_levels - 1)) {
|
|
/*
|
|
* Disable charging if highest value selected by
|
|
* setting the DC and USB path in suspend
|
|
*/
|
|
rc = smb135x_path_suspend(chip, DC, THERMAL, true);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev,
|
|
"Couldn't set dc suspend rc %d\n", rc);
|
|
goto out;
|
|
}
|
|
rc = smb135x_path_suspend(chip, USB, THERMAL, true);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev,
|
|
"Couldn't set usb suspend rc %d\n", rc);
|
|
goto out;
|
|
}
|
|
goto out;
|
|
}
|
|
|
|
smb135x_set_appropriate_current(chip, USB);
|
|
smb135x_set_appropriate_current(chip, DC);
|
|
|
|
if (prev_therm_lvl == chip->thermal_levels - 1) {
|
|
/*
|
|
* If previously highest value was selected charging must have
|
|
* been disabed. Enable charging by taking the DC and USB path
|
|
* out of suspend.
|
|
*/
|
|
rc = smb135x_path_suspend(chip, DC, THERMAL, false);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev,
|
|
"Couldn't set dc suspend rc %d\n", rc);
|
|
goto out;
|
|
}
|
|
rc = smb135x_path_suspend(chip, USB, THERMAL, false);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev,
|
|
"Couldn't set usb suspend rc %d\n", rc);
|
|
goto out;
|
|
}
|
|
}
|
|
out:
|
|
mutex_unlock(&chip->current_change_lock);
|
|
return rc;
|
|
}
|
|
|
|
static int smb135x_battery_set_property(struct power_supply *psy,
|
|
enum power_supply_property prop,
|
|
const union power_supply_propval *val)
|
|
{
|
|
int rc = 0, update_psy = 0;
|
|
struct smb135x_chg *chip = container_of(psy,
|
|
struct smb135x_chg, batt_psy);
|
|
|
|
switch (prop) {
|
|
case POWER_SUPPLY_PROP_STATUS:
|
|
if (!chip->bms_controlled_charging) {
|
|
rc = -EINVAL;
|
|
break;
|
|
}
|
|
switch (val->intval) {
|
|
case POWER_SUPPLY_STATUS_FULL:
|
|
rc = smb135x_charging_enable(chip, false);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Couldn't disable charging rc = %d\n",
|
|
rc);
|
|
} else {
|
|
chip->chg_done_batt_full = true;
|
|
update_psy = 1;
|
|
dev_dbg(chip->dev, "status = FULL chg_done_batt_full = %d",
|
|
chip->chg_done_batt_full);
|
|
}
|
|
break;
|
|
case POWER_SUPPLY_STATUS_DISCHARGING:
|
|
chip->chg_done_batt_full = false;
|
|
update_psy = 1;
|
|
dev_dbg(chip->dev, "status = DISCHARGING chg_done_batt_full = %d",
|
|
chip->chg_done_batt_full);
|
|
break;
|
|
case POWER_SUPPLY_STATUS_CHARGING:
|
|
rc = smb135x_charging_enable(chip, true);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Couldn't enable charging rc = %d\n",
|
|
rc);
|
|
} else {
|
|
chip->chg_done_batt_full = false;
|
|
dev_dbg(chip->dev, "status = CHARGING chg_done_batt_full = %d",
|
|
chip->chg_done_batt_full);
|
|
}
|
|
break;
|
|
default:
|
|
update_psy = 0;
|
|
rc = -EINVAL;
|
|
}
|
|
break;
|
|
case POWER_SUPPLY_PROP_CHARGING_ENABLED:
|
|
smb135x_charging(chip, val->intval);
|
|
break;
|
|
case POWER_SUPPLY_PROP_CAPACITY:
|
|
chip->fake_battery_soc = val->intval;
|
|
update_psy = 1;
|
|
break;
|
|
case POWER_SUPPLY_PROP_SYSTEM_TEMP_LEVEL:
|
|
smb135x_system_temp_level_set(chip, val->intval);
|
|
break;
|
|
default:
|
|
rc = -EINVAL;
|
|
}
|
|
|
|
if (!rc && update_psy)
|
|
power_supply_changed(&chip->batt_psy);
|
|
return rc;
|
|
}
|
|
|
|
static int smb135x_battery_is_writeable(struct power_supply *psy,
|
|
enum power_supply_property prop)
|
|
{
|
|
int rc;
|
|
|
|
switch (prop) {
|
|
case POWER_SUPPLY_PROP_CHARGING_ENABLED:
|
|
case POWER_SUPPLY_PROP_CAPACITY:
|
|
case POWER_SUPPLY_PROP_SYSTEM_TEMP_LEVEL:
|
|
rc = 1;
|
|
break;
|
|
default:
|
|
rc = 0;
|
|
break;
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
static int smb135x_battery_get_property(struct power_supply *psy,
|
|
enum power_supply_property prop,
|
|
union power_supply_propval *val)
|
|
{
|
|
struct smb135x_chg *chip = container_of(psy,
|
|
struct smb135x_chg, batt_psy);
|
|
|
|
switch (prop) {
|
|
case POWER_SUPPLY_PROP_STATUS:
|
|
val->intval = smb135x_get_prop_batt_status(chip);
|
|
break;
|
|
case POWER_SUPPLY_PROP_PRESENT:
|
|
val->intval = smb135x_get_prop_batt_present(chip);
|
|
break;
|
|
case POWER_SUPPLY_PROP_CHARGING_ENABLED:
|
|
val->intval = chip->chg_enabled;
|
|
break;
|
|
case POWER_SUPPLY_PROP_CHARGE_TYPE:
|
|
val->intval = smb135x_get_prop_charge_type(chip);
|
|
break;
|
|
case POWER_SUPPLY_PROP_CAPACITY:
|
|
val->intval = smb135x_get_prop_batt_capacity(chip);
|
|
break;
|
|
case POWER_SUPPLY_PROP_HEALTH:
|
|
val->intval = smb135x_get_prop_batt_health(chip);
|
|
break;
|
|
case POWER_SUPPLY_PROP_TECHNOLOGY:
|
|
val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
|
|
break;
|
|
case POWER_SUPPLY_PROP_SYSTEM_TEMP_LEVEL:
|
|
val->intval = chip->therm_lvl_sel;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static enum power_supply_property smb135x_dc_properties[] = {
|
|
POWER_SUPPLY_PROP_PRESENT,
|
|
POWER_SUPPLY_PROP_ONLINE,
|
|
POWER_SUPPLY_PROP_HEALTH,
|
|
};
|
|
|
|
static int smb135x_dc_get_property(struct power_supply *psy,
|
|
enum power_supply_property prop,
|
|
union power_supply_propval *val)
|
|
{
|
|
struct smb135x_chg *chip = container_of(psy,
|
|
struct smb135x_chg, dc_psy);
|
|
|
|
switch (prop) {
|
|
case POWER_SUPPLY_PROP_PRESENT:
|
|
val->intval = chip->dc_present;
|
|
break;
|
|
case POWER_SUPPLY_PROP_ONLINE:
|
|
val->intval = chip->chg_enabled ? chip->dc_present : 0;
|
|
break;
|
|
case POWER_SUPPLY_PROP_HEALTH:
|
|
val->intval = chip->dc_present;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#define MIN_FLOAT_MV 3600
|
|
#define MAX_FLOAT_MV 4500
|
|
|
|
#define MID_RANGE_FLOAT_MV_MIN 3600
|
|
#define MID_RANGE_FLOAT_MIN_VAL 0x05
|
|
#define MID_RANGE_FLOAT_STEP_MV 20
|
|
|
|
#define HIGH_RANGE_FLOAT_MIN_MV 4340
|
|
#define HIGH_RANGE_FLOAT_MIN_VAL 0x2A
|
|
#define HIGH_RANGE_FLOAT_STEP_MV 10
|
|
|
|
#define VHIGH_RANGE_FLOAT_MIN_MV 4400
|
|
#define VHIGH_RANGE_FLOAT_MIN_VAL 0x2E
|
|
#define VHIGH_RANGE_FLOAT_STEP_MV 20
|
|
static int smb135x_float_voltage_set(struct smb135x_chg *chip, int vfloat_mv)
|
|
{
|
|
u8 temp;
|
|
|
|
if ((vfloat_mv < MIN_FLOAT_MV) || (vfloat_mv > MAX_FLOAT_MV)) {
|
|
dev_err(chip->dev, "bad float voltage mv =%d asked to set\n",
|
|
vfloat_mv);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (vfloat_mv <= HIGH_RANGE_FLOAT_MIN_MV) {
|
|
/* mid range */
|
|
temp = MID_RANGE_FLOAT_MIN_VAL
|
|
+ (vfloat_mv - MID_RANGE_FLOAT_MV_MIN)
|
|
/ MID_RANGE_FLOAT_STEP_MV;
|
|
} else if (vfloat_mv < VHIGH_RANGE_FLOAT_MIN_MV) {
|
|
/* high range */
|
|
temp = HIGH_RANGE_FLOAT_MIN_VAL
|
|
+ (vfloat_mv - HIGH_RANGE_FLOAT_MIN_MV)
|
|
/ HIGH_RANGE_FLOAT_STEP_MV;
|
|
} else {
|
|
/* very high range */
|
|
temp = VHIGH_RANGE_FLOAT_MIN_VAL
|
|
+ (vfloat_mv - VHIGH_RANGE_FLOAT_MIN_MV)
|
|
/ VHIGH_RANGE_FLOAT_STEP_MV;
|
|
}
|
|
|
|
return smb135x_write(chip, VFLOAT_REG, temp);
|
|
}
|
|
|
|
static int smb135x_set_resume_threshold(struct smb135x_chg *chip,
|
|
int resume_delta_mv)
|
|
{
|
|
int rc;
|
|
u8 reg;
|
|
|
|
if (!chip->inhibit_disabled) {
|
|
if (resume_delta_mv < 100)
|
|
reg = CHG_INHIBIT_50MV_VAL;
|
|
else if (resume_delta_mv < 200)
|
|
reg = CHG_INHIBIT_100MV_VAL;
|
|
else if (resume_delta_mv < 300)
|
|
reg = CHG_INHIBIT_200MV_VAL;
|
|
else
|
|
reg = CHG_INHIBIT_300MV_VAL;
|
|
|
|
rc = smb135x_masked_write(chip, CFG_4_REG, CHG_INHIBIT_MASK,
|
|
reg);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Couldn't set inhibit val rc = %d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
if (resume_delta_mv < 200)
|
|
reg = 0;
|
|
else
|
|
reg = RECHARGE_200MV_BIT;
|
|
|
|
rc = smb135x_masked_write(chip, CFG_5_REG, RECHARGE_200MV_BIT, reg);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Couldn't set recharge rc = %d\n", rc);
|
|
return rc;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static enum power_supply_property smb135x_parallel_properties[] = {
|
|
POWER_SUPPLY_PROP_CHARGING_ENABLED,
|
|
POWER_SUPPLY_PROP_STATUS,
|
|
POWER_SUPPLY_PROP_PRESENT,
|
|
POWER_SUPPLY_PROP_CURRENT_MAX,
|
|
POWER_SUPPLY_PROP_VOLTAGE_MAX,
|
|
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
|
|
};
|
|
|
|
static int smb135x_parallel_set_chg_present(struct smb135x_chg *chip,
|
|
int present)
|
|
{
|
|
u8 val;
|
|
int rc;
|
|
|
|
if (present == chip->parallel_charger_present) {
|
|
pr_debug("present %d -> %d, skipping\n",
|
|
chip->parallel_charger_present, present);
|
|
return 0;
|
|
}
|
|
|
|
if (present) {
|
|
/* Check if SMB135x is present */
|
|
rc = smb135x_read(chip, VERSION1_REG, &val);
|
|
if (rc) {
|
|
pr_debug("Failed to detect smb135x-parallel charger may be absent\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
rc = smb135x_enable_volatile_writes(chip);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev,
|
|
"Couldn't configure for volatile rc = %d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
/* set the float voltage */
|
|
if (chip->vfloat_mv != -EINVAL) {
|
|
rc = smb135x_float_voltage_set(chip, chip->vfloat_mv);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev,
|
|
"Couldn't set float voltage rc = %d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
/* resume threshold */
|
|
if (chip->resume_delta_mv != -EINVAL) {
|
|
smb135x_set_resume_threshold(chip,
|
|
chip->resume_delta_mv);
|
|
}
|
|
|
|
rc = smb135x_masked_write(chip, CMD_INPUT_LIMIT,
|
|
USE_REGISTER_FOR_CURRENT,
|
|
USE_REGISTER_FOR_CURRENT);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev,
|
|
"Couldn't set input limit cmd rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
/* set chg en by pin active low and enable auto recharge */
|
|
rc = smb135x_masked_write(chip, CFG_14_REG,
|
|
CHG_EN_BY_PIN_BIT | CHG_EN_ACTIVE_LOW_BIT
|
|
| DISABLE_AUTO_RECHARGE_BIT,
|
|
CHG_EN_BY_PIN_BIT | CHG_EN_ACTIVE_LOW_BIT);
|
|
|
|
/* set bit 0 = 100mA bit 1 = 500mA and set register control */
|
|
rc = smb135x_masked_write(chip, CFG_E_REG,
|
|
POLARITY_100_500_BIT | USB_CTRL_BY_PIN_BIT,
|
|
POLARITY_100_500_BIT);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev,
|
|
"Couldn't set usbin cfg rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
/* control USB suspend via command bits */
|
|
rc = smb135x_masked_write(chip, USBIN_DCIN_CFG_REG,
|
|
USBIN_SUSPEND_VIA_COMMAND_BIT,
|
|
USBIN_SUSPEND_VIA_COMMAND_BIT);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Couldn't set cfg rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
/* set the fastchg_current to the lowest setting */
|
|
if (chip->fastchg_current_arr_size > 0)
|
|
rc = smb135x_set_fastchg_current(chip,
|
|
chip->fastchg_current_table[0]);
|
|
|
|
/*
|
|
* enforce chip->chg_enabled since this could be the first
|
|
* time we have i2c access to the charger after
|
|
* chip->chg_enabled has been modified
|
|
*/
|
|
smb135x_charging(chip, chip->chg_enabled);
|
|
}
|
|
|
|
chip->parallel_charger_present = present;
|
|
/*
|
|
* When present is being set force USB suspend, start charging
|
|
* only when CURRENT_MAX is set.
|
|
*
|
|
* Usually the chip will be shutdown (no i2c access to the chip)
|
|
* when USB is removed, however there could be situations when
|
|
* it is not. To cover for USB reinsetions in such situations
|
|
* force USB suspend when present is being unset.
|
|
* It is likely that i2c access could fail here - do not return error.
|
|
* (It is not possible to detect whether the chip is in shutdown state
|
|
* or not except for the i2c error).
|
|
*/
|
|
chip->usb_psy_ma = SUSPEND_CURRENT_MA;
|
|
rc = smb135x_path_suspend(chip, USB, CURRENT, true);
|
|
|
|
if (present) {
|
|
if (rc) {
|
|
dev_err(chip->dev,
|
|
"Couldn't set usb suspend to true rc = %d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
/* Check if the USB is configured for suspend. If not, do it */
|
|
mutex_lock(&chip->path_suspend_lock);
|
|
rc = smb135x_read(chip, CMD_INPUT_LIMIT, &val);
|
|
if (rc) {
|
|
dev_err(chip->dev,
|
|
"Couldn't read 0x%02x rc:%d\n", CMD_INPUT_LIMIT,
|
|
rc);
|
|
mutex_unlock(&chip->path_suspend_lock);
|
|
return rc;
|
|
} else if (!(val & BIT(6))) {
|
|
rc = __smb135x_usb_suspend(chip, 1);
|
|
}
|
|
mutex_unlock(&chip->path_suspend_lock);
|
|
if (rc) {
|
|
dev_err(chip->dev,
|
|
"Couldn't set usb to suspend rc:%d\n", rc);
|
|
return rc;
|
|
}
|
|
} else {
|
|
chip->real_usb_psy_ma = SUSPEND_CURRENT_MA;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int smb135x_parallel_set_property(struct power_supply *psy,
|
|
enum power_supply_property prop,
|
|
const union power_supply_propval *val)
|
|
{
|
|
int rc = 0;
|
|
struct smb135x_chg *chip = container_of(psy,
|
|
struct smb135x_chg, parallel_psy);
|
|
|
|
switch (prop) {
|
|
case POWER_SUPPLY_PROP_CHARGING_ENABLED:
|
|
if (chip->parallel_charger_present)
|
|
smb135x_charging(chip, val->intval);
|
|
else
|
|
chip->chg_enabled = val->intval;
|
|
break;
|
|
case POWER_SUPPLY_PROP_PRESENT:
|
|
rc = smb135x_parallel_set_chg_present(chip, val->intval);
|
|
break;
|
|
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
|
|
if (chip->parallel_charger_present) {
|
|
rc = smb135x_set_fastchg_current(chip,
|
|
val->intval / 1000);
|
|
}
|
|
break;
|
|
case POWER_SUPPLY_PROP_CURRENT_MAX:
|
|
if (chip->parallel_charger_present) {
|
|
chip->usb_psy_ma = val->intval / 1000;
|
|
rc = smb135x_set_usb_chg_current(chip,
|
|
chip->usb_psy_ma);
|
|
}
|
|
break;
|
|
case POWER_SUPPLY_PROP_VOLTAGE_MAX:
|
|
if (chip->parallel_charger_present &&
|
|
(chip->vfloat_mv != val->intval)) {
|
|
rc = smb135x_float_voltage_set(chip, val->intval);
|
|
if (!rc)
|
|
chip->vfloat_mv = val->intval;
|
|
} else {
|
|
chip->vfloat_mv = val->intval;
|
|
}
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
static int smb135x_parallel_is_writeable(struct power_supply *psy,
|
|
enum power_supply_property prop)
|
|
{
|
|
int rc;
|
|
|
|
switch (prop) {
|
|
case POWER_SUPPLY_PROP_CHARGING_ENABLED:
|
|
rc = 1;
|
|
break;
|
|
default:
|
|
rc = 0;
|
|
break;
|
|
}
|
|
return rc;
|
|
}
|
|
static int smb135x_parallel_get_property(struct power_supply *psy,
|
|
enum power_supply_property prop,
|
|
union power_supply_propval *val)
|
|
{
|
|
struct smb135x_chg *chip = container_of(psy,
|
|
struct smb135x_chg, parallel_psy);
|
|
|
|
switch (prop) {
|
|
case POWER_SUPPLY_PROP_CHARGING_ENABLED:
|
|
val->intval = chip->chg_enabled;
|
|
break;
|
|
case POWER_SUPPLY_PROP_CURRENT_MAX:
|
|
if (chip->parallel_charger_present)
|
|
val->intval = smb135x_get_usb_chg_current(chip) * 1000;
|
|
else
|
|
val->intval = 0;
|
|
break;
|
|
case POWER_SUPPLY_PROP_VOLTAGE_MAX:
|
|
val->intval = chip->vfloat_mv;
|
|
break;
|
|
case POWER_SUPPLY_PROP_PRESENT:
|
|
val->intval = chip->parallel_charger_present;
|
|
break;
|
|
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
|
|
if (chip->parallel_charger_present)
|
|
val->intval = smb135x_get_fastchg_current(chip) * 1000;
|
|
else
|
|
val->intval = 0;
|
|
break;
|
|
case POWER_SUPPLY_PROP_STATUS:
|
|
if (chip->parallel_charger_present)
|
|
val->intval = smb135x_get_prop_batt_status(chip);
|
|
else
|
|
val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void smb135x_external_power_changed(struct power_supply *psy)
|
|
{
|
|
struct smb135x_chg *chip = container_of(psy,
|
|
struct smb135x_chg, batt_psy);
|
|
union power_supply_propval prop = {0,};
|
|
int rc, current_limit = 0;
|
|
|
|
if (!chip->usb_psy)
|
|
return;
|
|
|
|
if (chip->bms_psy_name)
|
|
chip->bms_psy =
|
|
power_supply_get_by_name((char *)chip->bms_psy_name);
|
|
|
|
rc = chip->usb_psy->get_property(chip->usb_psy,
|
|
POWER_SUPPLY_PROP_CURRENT_MAX, &prop);
|
|
if (rc < 0)
|
|
dev_err(chip->dev,
|
|
"could not read USB current_max property, rc=%d\n", rc);
|
|
else
|
|
current_limit = prop.intval / 1000;
|
|
|
|
pr_debug("current_limit = %d\n", current_limit);
|
|
|
|
if (chip->usb_psy_ma != current_limit) {
|
|
mutex_lock(&chip->current_change_lock);
|
|
chip->usb_psy_ma = current_limit;
|
|
rc = smb135x_set_appropriate_current(chip, USB);
|
|
mutex_unlock(&chip->current_change_lock);
|
|
if (rc < 0)
|
|
dev_err(chip->dev, "Couldn't set usb current rc = %d\n",
|
|
rc);
|
|
}
|
|
|
|
rc = chip->usb_psy->get_property(chip->usb_psy,
|
|
POWER_SUPPLY_PROP_ONLINE, &prop);
|
|
if (rc < 0)
|
|
dev_err(chip->dev,
|
|
"could not read USB ONLINE property, rc=%d\n", rc);
|
|
|
|
/* update online property */
|
|
rc = 0;
|
|
if (chip->usb_present && chip->chg_enabled && chip->usb_psy_ma != 0) {
|
|
if (prop.intval == 0)
|
|
rc = power_supply_set_online(chip->usb_psy, true);
|
|
} else {
|
|
if (prop.intval == 1)
|
|
rc = power_supply_set_online(chip->usb_psy, false);
|
|
}
|
|
if (rc < 0)
|
|
dev_err(chip->dev, "could not set usb online, rc=%d\n", rc);
|
|
}
|
|
|
|
static bool elapsed_msec_greater(struct timeval *start_time,
|
|
struct timeval *end_time, int ms)
|
|
{
|
|
int msec_elapsed;
|
|
|
|
msec_elapsed = (end_time->tv_sec - start_time->tv_sec) * 1000 +
|
|
DIV_ROUND_UP(end_time->tv_usec - start_time->tv_usec, 1000);
|
|
|
|
return (msec_elapsed > ms);
|
|
}
|
|
|
|
#define MAX_STEP_MS 10
|
|
static int smb135x_chg_otg_enable(struct smb135x_chg *chip)
|
|
{
|
|
int rc = 0;
|
|
int restart_count = 0;
|
|
struct timeval time_a, time_b, time_c, time_d;
|
|
u8 reg;
|
|
|
|
if (chip->revision == REV_2) {
|
|
/*
|
|
* Workaround for a hardware bug where the OTG needs to be
|
|
* enabled disabled and enabled for it to be actually enabled.
|
|
* The time between each step should be atmost MAX_STEP_MS
|
|
*
|
|
* Note that if enable-disable executes within the timeframe
|
|
* but the final enable takes more than MAX_STEP_ME, we treat
|
|
* it as the first enable and try disabling again. We don't
|
|
* want to issue enable back to back.
|
|
*
|
|
* Notice the instances when time is captured and the
|
|
* successive steps.
|
|
* timeA-enable-timeC-disable-timeB-enable-timeD.
|
|
* When
|
|
* (timeB - timeA) < MAX_STEP_MS AND
|
|
* (timeC - timeD) < MAX_STEP_MS
|
|
* then it is guaranteed that the successive steps
|
|
* must have executed within MAX_STEP_MS
|
|
*/
|
|
do_gettimeofday(&time_a);
|
|
restart_from_enable:
|
|
/* first step - enable otg */
|
|
rc = smb135x_masked_write(chip, CMD_CHG_REG, OTG_EN, OTG_EN);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Couldn't enable OTG mode rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
restart_from_disable:
|
|
/* second step - disable otg */
|
|
do_gettimeofday(&time_c);
|
|
rc = smb135x_masked_write(chip, CMD_CHG_REG, OTG_EN, 0);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Couldn't enable OTG mode rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
do_gettimeofday(&time_b);
|
|
|
|
if (elapsed_msec_greater(&time_a, &time_b, MAX_STEP_MS)) {
|
|
restart_count++;
|
|
if (restart_count > 10) {
|
|
dev_err(chip->dev,
|
|
"Couldn't enable OTG restart_count=%d\n",
|
|
restart_count);
|
|
return -EAGAIN;
|
|
}
|
|
time_a = time_b;
|
|
pr_debug("restarting from first enable\n");
|
|
goto restart_from_enable;
|
|
}
|
|
|
|
/* third step (first step in case of a failure) - enable otg */
|
|
time_a = time_b;
|
|
rc = smb135x_masked_write(chip, CMD_CHG_REG, OTG_EN, OTG_EN);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Couldn't enable OTG mode rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
do_gettimeofday(&time_d);
|
|
|
|
if (elapsed_msec_greater(&time_c, &time_d, MAX_STEP_MS)) {
|
|
restart_count++;
|
|
if (restart_count > 10) {
|
|
dev_err(chip->dev,
|
|
"Couldn't enable OTG restart_count=%d\n",
|
|
restart_count);
|
|
return -EAGAIN;
|
|
}
|
|
pr_debug("restarting from disable\n");
|
|
goto restart_from_disable;
|
|
}
|
|
} else {
|
|
rc = smb135x_read(chip, CMD_CHG_REG, ®);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Couldn't read cmd reg rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
if (reg & OTG_EN) {
|
|
/* if it is set, disable it before re-enabling it */
|
|
rc = smb135x_masked_write(chip, CMD_CHG_REG, OTG_EN, 0);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Couldn't disable OTG mode rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
}
|
|
rc = smb135x_masked_write(chip, CMD_CHG_REG, OTG_EN, OTG_EN);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Couldn't enable OTG mode rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int smb135x_chg_otg_regulator_enable(struct regulator_dev *rdev)
|
|
{
|
|
int rc = 0;
|
|
struct smb135x_chg *chip = rdev_get_drvdata(rdev);
|
|
|
|
chip->otg_oc_count = 0;
|
|
rc = smb135x_chg_otg_enable(chip);
|
|
if (rc)
|
|
dev_err(chip->dev, "Couldn't enable otg regulator rc=%d\n", rc);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int smb135x_chg_otg_regulator_disable(struct regulator_dev *rdev)
|
|
{
|
|
int rc = 0;
|
|
struct smb135x_chg *chip = rdev_get_drvdata(rdev);
|
|
|
|
mutex_lock(&chip->otg_oc_count_lock);
|
|
cancel_delayed_work_sync(&chip->reset_otg_oc_count_work);
|
|
mutex_unlock(&chip->otg_oc_count_lock);
|
|
rc = smb135x_masked_write(chip, CMD_CHG_REG, OTG_EN, 0);
|
|
if (rc < 0)
|
|
dev_err(chip->dev, "Couldn't disable OTG mode rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
static int smb135x_chg_otg_regulator_is_enable(struct regulator_dev *rdev)
|
|
{
|
|
int rc = 0;
|
|
u8 reg = 0;
|
|
struct smb135x_chg *chip = rdev_get_drvdata(rdev);
|
|
|
|
rc = smb135x_read(chip, CMD_CHG_REG, ®);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev,
|
|
"Couldn't read OTG enable bit rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
return (reg & OTG_EN) ? 1 : 0;
|
|
}
|
|
|
|
struct regulator_ops smb135x_chg_otg_reg_ops = {
|
|
.enable = smb135x_chg_otg_regulator_enable,
|
|
.disable = smb135x_chg_otg_regulator_disable,
|
|
.is_enabled = smb135x_chg_otg_regulator_is_enable,
|
|
};
|
|
|
|
static int smb135x_set_current_tables(struct smb135x_chg *chip)
|
|
{
|
|
switch (chip->version) {
|
|
case V_SMB1356:
|
|
chip->usb_current_table = usb_current_table_smb1356;
|
|
chip->usb_current_arr_size
|
|
= ARRAY_SIZE(usb_current_table_smb1356);
|
|
chip->dc_current_table = dc_current_table_smb1356;
|
|
chip->dc_current_arr_size
|
|
= ARRAY_SIZE(dc_current_table_smb1356);
|
|
chip->fastchg_current_table = NULL;
|
|
chip->fastchg_current_arr_size = 0;
|
|
break;
|
|
case V_SMB1357:
|
|
chip->usb_current_table = usb_current_table_smb1357_smb1358;
|
|
chip->usb_current_arr_size
|
|
= ARRAY_SIZE(usb_current_table_smb1357_smb1358);
|
|
chip->dc_current_table = dc_current_table;
|
|
chip->dc_current_arr_size = ARRAY_SIZE(dc_current_table);
|
|
chip->fastchg_current_table = fastchg_current_table;
|
|
chip->fastchg_current_arr_size
|
|
= ARRAY_SIZE(fastchg_current_table);
|
|
break;
|
|
case V_SMB1358:
|
|
chip->usb_current_table = usb_current_table_smb1357_smb1358;
|
|
chip->usb_current_arr_size
|
|
= ARRAY_SIZE(usb_current_table_smb1357_smb1358);
|
|
chip->dc_current_table = dc_current_table;
|
|
chip->dc_current_arr_size = ARRAY_SIZE(dc_current_table);
|
|
chip->fastchg_current_table = NULL;
|
|
chip->fastchg_current_arr_size = 0;
|
|
break;
|
|
case V_SMB1359:
|
|
chip->usb_current_table = usb_current_table_smb1359;
|
|
chip->usb_current_arr_size
|
|
= ARRAY_SIZE(usb_current_table_smb1359);
|
|
chip->dc_current_table = dc_current_table;
|
|
chip->dc_current_arr_size = ARRAY_SIZE(dc_current_table);
|
|
chip->fastchg_current_table = NULL;
|
|
chip->fastchg_current_arr_size = 0;
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#define SMB1356_VERSION3_BIT BIT(7)
|
|
#define SMB1357_VERSION1_VAL 0x01
|
|
#define SMB1358_VERSION1_VAL 0x02
|
|
#define SMB1359_VERSION1_VAL 0x00
|
|
#define SMB1357_VERSION2_VAL 0x01
|
|
#define SMB1358_VERSION2_VAL 0x02
|
|
#define SMB1359_VERSION2_VAL 0x00
|
|
static int smb135x_chip_version_and_revision(struct smb135x_chg *chip)
|
|
{
|
|
int rc;
|
|
u8 version1, version2, version3;
|
|
|
|
/* read the revision */
|
|
rc = read_revision(chip, &chip->revision);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Couldn't read revision rc = %d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
if (chip->revision >= REV_MAX || revision_str[chip->revision] == NULL) {
|
|
dev_err(chip->dev, "Bad revision found = %d\n", chip->revision);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* check if it is smb1356 */
|
|
rc = read_version3(chip, &version3);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Couldn't read version3 rc = %d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
if (version3 & SMB1356_VERSION3_BIT) {
|
|
chip->version = V_SMB1356;
|
|
goto wrkarnd_and_input_current_values;
|
|
}
|
|
|
|
/* check if it is smb1357, smb1358 or smb1359 based on revision */
|
|
if (chip->revision <= REV_1_1) {
|
|
rc = read_version1(chip, &version1);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev,
|
|
"Couldn't read version 1 rc = %d\n", rc);
|
|
return rc;
|
|
}
|
|
switch (version1) {
|
|
case SMB1357_VERSION1_VAL:
|
|
chip->version = V_SMB1357;
|
|
break;
|
|
case SMB1358_VERSION1_VAL:
|
|
chip->version = V_SMB1358;
|
|
break;
|
|
case SMB1359_VERSION1_VAL:
|
|
chip->version = V_SMB1359;
|
|
break;
|
|
default:
|
|
dev_err(chip->dev,
|
|
"Unknown version 1 = 0x%02x rc = %d\n",
|
|
version1, rc);
|
|
return rc;
|
|
}
|
|
} else {
|
|
rc = read_version2(chip, &version2);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev,
|
|
"Couldn't read version 2 rc = %d\n", rc);
|
|
return rc;
|
|
}
|
|
switch (version2) {
|
|
case SMB1357_VERSION2_VAL:
|
|
chip->version = V_SMB1357;
|
|
break;
|
|
case SMB1358_VERSION2_VAL:
|
|
chip->version = V_SMB1358;
|
|
break;
|
|
case SMB1359_VERSION2_VAL:
|
|
chip->version = V_SMB1359;
|
|
break;
|
|
default:
|
|
dev_err(chip->dev,
|
|
"Unknown version 2 = 0x%02x rc = %d\n",
|
|
version2, rc);
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
wrkarnd_and_input_current_values:
|
|
if (is_usb100_broken(chip))
|
|
chip->workaround_flags |= WRKARND_USB100_BIT;
|
|
/*
|
|
* Rev v1.0 and v1.1 of SMB135x fails charger type detection
|
|
* (apsd) due to interference on the D+/- lines by the USB phy.
|
|
* Set the workaround flag to disable charger type reporting
|
|
* for this revision.
|
|
*/
|
|
if (chip->revision <= REV_1_1)
|
|
chip->workaround_flags |= WRKARND_APSD_FAIL;
|
|
|
|
pr_debug("workaround_flags = %x\n", chip->workaround_flags);
|
|
|
|
return smb135x_set_current_tables(chip);
|
|
}
|
|
|
|
static int smb135x_regulator_init(struct smb135x_chg *chip)
|
|
{
|
|
int rc = 0;
|
|
struct regulator_init_data *init_data;
|
|
struct regulator_config cfg = {};
|
|
|
|
init_data = of_get_regulator_init_data(chip->dev, chip->dev->of_node);
|
|
if (!init_data) {
|
|
dev_err(chip->dev, "Unable to allocate memory\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
if (init_data->constraints.name) {
|
|
chip->otg_vreg.rdesc.owner = THIS_MODULE;
|
|
chip->otg_vreg.rdesc.type = REGULATOR_VOLTAGE;
|
|
chip->otg_vreg.rdesc.ops = &smb135x_chg_otg_reg_ops;
|
|
chip->otg_vreg.rdesc.name = init_data->constraints.name;
|
|
|
|
cfg.dev = chip->dev;
|
|
cfg.init_data = init_data;
|
|
cfg.driver_data = chip;
|
|
cfg.of_node = chip->dev->of_node;
|
|
|
|
init_data->constraints.valid_ops_mask
|
|
|= REGULATOR_CHANGE_STATUS;
|
|
|
|
chip->otg_vreg.rdev = regulator_register(
|
|
&chip->otg_vreg.rdesc, &cfg);
|
|
if (IS_ERR(chip->otg_vreg.rdev)) {
|
|
rc = PTR_ERR(chip->otg_vreg.rdev);
|
|
chip->otg_vreg.rdev = NULL;
|
|
if (rc != -EPROBE_DEFER)
|
|
dev_err(chip->dev,
|
|
"OTG reg failed, rc=%d\n", rc);
|
|
}
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static void smb135x_regulator_deinit(struct smb135x_chg *chip)
|
|
{
|
|
if (chip->otg_vreg.rdev)
|
|
regulator_unregister(chip->otg_vreg.rdev);
|
|
}
|
|
|
|
static void wireless_insertion_work(struct work_struct *work)
|
|
{
|
|
struct smb135x_chg *chip =
|
|
container_of(work, struct smb135x_chg,
|
|
wireless_insertion_work.work);
|
|
|
|
/* unsuspend dc */
|
|
smb135x_path_suspend(chip, DC, CURRENT, false);
|
|
}
|
|
|
|
static int hot_hard_handler(struct smb135x_chg *chip, u8 rt_stat)
|
|
{
|
|
pr_debug("rt_stat = 0x%02x\n", rt_stat);
|
|
chip->batt_hot = !!rt_stat;
|
|
return 0;
|
|
}
|
|
static int cold_hard_handler(struct smb135x_chg *chip, u8 rt_stat)
|
|
{
|
|
pr_debug("rt_stat = 0x%02x\n", rt_stat);
|
|
chip->batt_cold = !!rt_stat;
|
|
return 0;
|
|
}
|
|
static int hot_soft_handler(struct smb135x_chg *chip, u8 rt_stat)
|
|
{
|
|
pr_debug("rt_stat = 0x%02x\n", rt_stat);
|
|
chip->batt_warm = !!rt_stat;
|
|
return 0;
|
|
}
|
|
static int cold_soft_handler(struct smb135x_chg *chip, u8 rt_stat)
|
|
{
|
|
pr_debug("rt_stat = 0x%02x\n", rt_stat);
|
|
chip->batt_cool = !!rt_stat;
|
|
return 0;
|
|
}
|
|
static int battery_missing_handler(struct smb135x_chg *chip, u8 rt_stat)
|
|
{
|
|
pr_debug("rt_stat = 0x%02x\n", rt_stat);
|
|
chip->batt_present = !rt_stat;
|
|
return 0;
|
|
}
|
|
static int vbat_low_handler(struct smb135x_chg *chip, u8 rt_stat)
|
|
{
|
|
pr_warn("vbat low\n");
|
|
return 0;
|
|
}
|
|
static int chg_hot_handler(struct smb135x_chg *chip, u8 rt_stat)
|
|
{
|
|
pr_warn("chg hot\n");
|
|
return 0;
|
|
}
|
|
static int chg_term_handler(struct smb135x_chg *chip, u8 rt_stat)
|
|
{
|
|
pr_debug("rt_stat = 0x%02x\n", rt_stat);
|
|
|
|
/*
|
|
* This handler gets called even when the charger based termination
|
|
* is disabled (due to change in RT status). However, in a bms
|
|
* controlled design the battery status should not be updated.
|
|
*/
|
|
if (!chip->iterm_disabled)
|
|
chip->chg_done_batt_full = !!rt_stat;
|
|
return 0;
|
|
}
|
|
|
|
static int taper_handler(struct smb135x_chg *chip, u8 rt_stat)
|
|
{
|
|
pr_debug("rt_stat = 0x%02x\n", rt_stat);
|
|
return 0;
|
|
}
|
|
|
|
static int fast_chg_handler(struct smb135x_chg *chip, u8 rt_stat)
|
|
{
|
|
pr_debug("rt_stat = 0x%02x\n", rt_stat);
|
|
|
|
if (rt_stat & IRQ_C_FASTCHG_BIT)
|
|
chip->chg_done_batt_full = false;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int recharge_handler(struct smb135x_chg *chip, u8 rt_stat)
|
|
{
|
|
int rc;
|
|
|
|
pr_debug("rt_stat = 0x%02x\n", rt_stat);
|
|
|
|
if (chip->bms_controlled_charging) {
|
|
rc = smb135x_charging_enable(chip, true);
|
|
if (rc < 0)
|
|
dev_err(chip->dev, "Couldn't enable charging rc = %d\n",
|
|
rc);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int safety_timeout_handler(struct smb135x_chg *chip, u8 rt_stat)
|
|
{
|
|
pr_warn("safety timeout rt_stat = 0x%02x\n", rt_stat);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* power_ok_handler() - called when the switcher turns on or turns off
|
|
* @chip: pointer to smb135x_chg chip
|
|
* @rt_stat: the status bit indicating switcher turning on or off
|
|
*/
|
|
static int power_ok_handler(struct smb135x_chg *chip, u8 rt_stat)
|
|
{
|
|
pr_debug("rt_stat = 0x%02x\n", rt_stat);
|
|
return 0;
|
|
}
|
|
|
|
static int rid_handler(struct smb135x_chg *chip, u8 rt_stat)
|
|
{
|
|
bool usb_slave_present;
|
|
|
|
usb_slave_present = is_usb_slave_present(chip);
|
|
|
|
if (chip->usb_slave_present ^ usb_slave_present) {
|
|
chip->usb_slave_present = usb_slave_present;
|
|
if (chip->usb_psy) {
|
|
pr_debug("setting usb psy usb_otg = %d\n",
|
|
chip->usb_slave_present);
|
|
power_supply_set_usb_otg(chip->usb_psy,
|
|
chip->usb_slave_present);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#define RESET_OTG_OC_COUNT_MS 100
|
|
static void reset_otg_oc_count_work(struct work_struct *work)
|
|
{
|
|
struct smb135x_chg *chip =
|
|
container_of(work, struct smb135x_chg,
|
|
reset_otg_oc_count_work.work);
|
|
|
|
mutex_lock(&chip->otg_oc_count_lock);
|
|
pr_debug("It has been %dmS since OverCurrent interrupt resetting the count\n",
|
|
RESET_OTG_OC_COUNT_MS);
|
|
chip->otg_oc_count = 0;
|
|
mutex_unlock(&chip->otg_oc_count_lock);
|
|
}
|
|
|
|
#define MAX_OTG_RETRY 3
|
|
static int otg_oc_handler(struct smb135x_chg *chip, u8 rt_stat)
|
|
{
|
|
int rc;
|
|
|
|
mutex_lock(&chip->otg_oc_count_lock);
|
|
cancel_delayed_work_sync(&chip->reset_otg_oc_count_work);
|
|
++chip->otg_oc_count;
|
|
if (chip->otg_oc_count < MAX_OTG_RETRY) {
|
|
rc = smb135x_chg_otg_enable(chip);
|
|
if (rc < 0)
|
|
dev_err(chip->dev, "Couldn't enable OTG mode rc=%d\n",
|
|
rc);
|
|
} else {
|
|
pr_warn_ratelimited("Tried enabling OTG %d times, the USB slave is nonconformant.\n",
|
|
chip->otg_oc_count);
|
|
}
|
|
|
|
pr_debug("rt_stat = 0x%02x\n", rt_stat);
|
|
schedule_delayed_work(&chip->reset_otg_oc_count_work,
|
|
msecs_to_jiffies(RESET_OTG_OC_COUNT_MS));
|
|
mutex_unlock(&chip->otg_oc_count_lock);
|
|
return 0;
|
|
}
|
|
|
|
static int handle_dc_removal(struct smb135x_chg *chip)
|
|
{
|
|
if (chip->dc_psy_type == POWER_SUPPLY_TYPE_WIRELESS) {
|
|
cancel_delayed_work_sync(&chip->wireless_insertion_work);
|
|
smb135x_path_suspend(chip, DC, CURRENT, true);
|
|
}
|
|
|
|
if (chip->dc_psy_type != -EINVAL)
|
|
power_supply_set_online(&chip->dc_psy, chip->dc_present);
|
|
return 0;
|
|
}
|
|
|
|
#define DCIN_UNSUSPEND_DELAY_MS 1000
|
|
static int handle_dc_insertion(struct smb135x_chg *chip)
|
|
{
|
|
if (chip->dc_psy_type == POWER_SUPPLY_TYPE_WIRELESS)
|
|
schedule_delayed_work(&chip->wireless_insertion_work,
|
|
msecs_to_jiffies(DCIN_UNSUSPEND_DELAY_MS));
|
|
if (chip->dc_psy_type != -EINVAL)
|
|
power_supply_set_online(&chip->dc_psy,
|
|
chip->dc_present);
|
|
|
|
return 0;
|
|
}
|
|
/**
|
|
* dcin_uv_handler() - called when the dc voltage crosses the uv threshold
|
|
* @chip: pointer to smb135x_chg chip
|
|
* @rt_stat: the status bit indicating whether dc voltage is uv
|
|
*/
|
|
static int dcin_uv_handler(struct smb135x_chg *chip, u8 rt_stat)
|
|
{
|
|
/*
|
|
* rt_stat indicates if dc is undervolted. If so dc_present
|
|
* should be marked removed
|
|
*/
|
|
bool dc_present = !rt_stat;
|
|
|
|
pr_debug("chip->dc_present = %d dc_present = %d\n",
|
|
chip->dc_present, dc_present);
|
|
|
|
if (chip->dc_present && !dc_present) {
|
|
/* dc removed */
|
|
chip->dc_present = dc_present;
|
|
handle_dc_removal(chip);
|
|
}
|
|
|
|
if (!chip->dc_present && dc_present) {
|
|
/* dc inserted */
|
|
chip->dc_present = dc_present;
|
|
handle_dc_insertion(chip);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int dcin_ov_handler(struct smb135x_chg *chip, u8 rt_stat)
|
|
{
|
|
/*
|
|
* rt_stat indicates if dc is overvolted. If so dc_present
|
|
* should be marked removed
|
|
*/
|
|
bool dc_present = !rt_stat;
|
|
|
|
pr_debug("chip->dc_present = %d dc_present = %d\n",
|
|
chip->dc_present, dc_present);
|
|
|
|
chip->dc_ov = !!rt_stat;
|
|
|
|
if (chip->dc_present && !dc_present) {
|
|
/* dc removed */
|
|
chip->dc_present = dc_present;
|
|
handle_dc_removal(chip);
|
|
}
|
|
|
|
if (!chip->dc_present && dc_present) {
|
|
/* dc inserted */
|
|
chip->dc_present = dc_present;
|
|
handle_dc_insertion(chip);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int handle_usb_removal(struct smb135x_chg *chip)
|
|
{
|
|
if (chip->usb_psy) {
|
|
pr_debug("setting usb psy type = %d\n",
|
|
POWER_SUPPLY_TYPE_UNKNOWN);
|
|
power_supply_set_supply_type(chip->usb_psy,
|
|
POWER_SUPPLY_TYPE_UNKNOWN);
|
|
pr_debug("setting usb psy present = %d\n", chip->usb_present);
|
|
power_supply_set_present(chip->usb_psy, chip->usb_present);
|
|
pr_debug("Setting usb psy dp=r dm=r\n");
|
|
power_supply_set_dp_dm(chip->usb_psy,
|
|
POWER_SUPPLY_DP_DM_DPR_DMR);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int rerun_apsd(struct smb135x_chg *chip)
|
|
{
|
|
int rc;
|
|
|
|
pr_debug("Reruning APSD\nDisabling APSD\n");
|
|
rc = smb135x_masked_write(chip, CFG_11_REG, AUTO_SRC_DET_EN_BIT, 0);
|
|
if (rc) {
|
|
dev_err(chip->dev, "Couldn't Disable APSD rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
pr_debug("Allow only 9V chargers\n");
|
|
rc = smb135x_masked_write(chip, CFG_C_REG,
|
|
USBIN_ADAPTER_ALLOWANCE_MASK, ALLOW_9V_ONLY);
|
|
if (rc)
|
|
dev_err(chip->dev, "Couldn't Allow 9V rc=%d\n", rc);
|
|
pr_debug("Enabling APSD\n");
|
|
rc = smb135x_masked_write(chip, CFG_11_REG, AUTO_SRC_DET_EN_BIT, 1);
|
|
if (rc)
|
|
dev_err(chip->dev, "Couldn't Enable APSD rc=%d\n", rc);
|
|
pr_debug("Allow 5V-9V\n");
|
|
rc = smb135x_masked_write(chip, CFG_C_REG,
|
|
USBIN_ADAPTER_ALLOWANCE_MASK, ALLOW_5V_TO_9V);
|
|
if (rc)
|
|
dev_err(chip->dev, "Couldn't Allow 5V-9V rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
static int handle_usb_insertion(struct smb135x_chg *chip)
|
|
{
|
|
u8 reg;
|
|
int rc;
|
|
char *usb_type_name = "null";
|
|
enum power_supply_type usb_supply_type;
|
|
|
|
/* usb inserted */
|
|
rc = smb135x_read(chip, STATUS_5_REG, ®);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Couldn't read status 5 rc = %d\n", rc);
|
|
return rc;
|
|
}
|
|
/*
|
|
* Report the charger type as UNKNOWN if the
|
|
* apsd-fail flag is set. This nofifies the USB driver
|
|
* to initiate a s/w based charger type detection.
|
|
*/
|
|
if (chip->workaround_flags & WRKARND_APSD_FAIL)
|
|
reg = 0;
|
|
|
|
usb_type_name = get_usb_type_name(reg);
|
|
usb_supply_type = get_usb_supply_type(reg);
|
|
pr_debug("inserted %s, usb psy type = %d stat_5 = 0x%02x apsd_rerun = %d\n",
|
|
usb_type_name, usb_supply_type, reg, chip->apsd_rerun);
|
|
|
|
if (chip->batt_present && !chip->apsd_rerun && chip->usb_psy) {
|
|
if (usb_supply_type == POWER_SUPPLY_TYPE_USB) {
|
|
pr_debug("Setting usb psy dp=f dm=f SDP and rerun\n");
|
|
power_supply_set_dp_dm(chip->usb_psy,
|
|
POWER_SUPPLY_DP_DM_DPF_DMF);
|
|
chip->apsd_rerun = true;
|
|
rerun_apsd(chip);
|
|
/* rising edge of src detect will happen in few mS */
|
|
return 0;
|
|
} else {
|
|
pr_debug("Set usb psy dp=f dm=f DCP and no rerun\n");
|
|
power_supply_set_dp_dm(chip->usb_psy,
|
|
POWER_SUPPLY_DP_DM_DPF_DMF);
|
|
}
|
|
}
|
|
|
|
if (chip->usb_psy) {
|
|
if (chip->bms_controlled_charging) {
|
|
/* enable charging on USB insertion */
|
|
rc = smb135x_charging_enable(chip, true);
|
|
if (rc < 0)
|
|
dev_err(chip->dev, "Couldn't enable charging rc = %d\n",
|
|
rc);
|
|
}
|
|
pr_debug("setting usb psy type = %d\n", usb_supply_type);
|
|
power_supply_set_supply_type(chip->usb_psy, usb_supply_type);
|
|
pr_debug("setting usb psy present = %d\n", chip->usb_present);
|
|
power_supply_set_present(chip->usb_psy, chip->usb_present);
|
|
}
|
|
chip->apsd_rerun = false;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* usbin_uv_handler() - It is called for DCP charger removal
|
|
* @chip: pointer to smb135x_chg chip
|
|
* @rt_stat: the status bit indicating chg insertion/removal
|
|
*/
|
|
static int usbin_uv_handler(struct smb135x_chg *chip, u8 rt_stat)
|
|
{
|
|
/*
|
|
* rt_stat indicates if usb is undervolted. If so usb_present
|
|
* should be marked removed
|
|
*/
|
|
bool usb_present = !rt_stat;
|
|
union power_supply_propval prop = {0, };
|
|
|
|
pr_debug("chip->usb_present = %d usb_present = %d\n",
|
|
chip->usb_present, usb_present);
|
|
if (chip->usb_psy && !chip->usb_psy->get_property(chip->usb_psy,
|
|
POWER_SUPPLY_PROP_TYPE, &prop)) {
|
|
if (prop.intval == POWER_SUPPLY_TYPE_USB_DCP) {
|
|
if (chip->usb_present && !usb_present) {
|
|
/* For DCP and HVDCP removing */
|
|
chip->usb_present = usb_present;
|
|
handle_usb_removal(chip);
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int usbin_ov_handler(struct smb135x_chg *chip, u8 rt_stat)
|
|
{
|
|
/*
|
|
* rt_stat indicates if usb is overvolted. If so usb_present
|
|
* should be marked removed
|
|
*/
|
|
bool usb_present = !rt_stat;
|
|
int health;
|
|
|
|
pr_debug("chip->usb_present = %d usb_present = %d\n",
|
|
chip->usb_present, usb_present);
|
|
if (chip->usb_present && !usb_present) {
|
|
/* USB removed */
|
|
chip->usb_present = usb_present;
|
|
handle_usb_removal(chip);
|
|
} else if (!chip->usb_present && usb_present) {
|
|
/* USB inserted */
|
|
chip->usb_present = usb_present;
|
|
handle_usb_insertion(chip);
|
|
}
|
|
|
|
if (chip->usb_psy) {
|
|
health = rt_stat ? POWER_SUPPLY_HEALTH_OVERVOLTAGE
|
|
: POWER_SUPPLY_HEALTH_GOOD;
|
|
power_supply_set_health_state(chip->usb_psy, health);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* src_detect_handler() - this is called on rising edge when USB
|
|
* charger type is detected and on falling edge when
|
|
* USB voltage falls below the coarse detect voltage
|
|
* (1V), use it for handling USB charger insertion
|
|
* and CDP or SDP removal.
|
|
* @chip: pointer to smb135x_chg chip
|
|
* @rt_stat: the status bit indicating chg insertion/removal
|
|
*/
|
|
static int src_detect_handler(struct smb135x_chg *chip, u8 rt_stat)
|
|
{
|
|
bool usb_present = !!rt_stat;
|
|
union power_supply_propval prop = {0, };
|
|
|
|
pr_debug("chip->usb_present = %d usb_present = %d\n",
|
|
chip->usb_present, usb_present);
|
|
|
|
if (!chip->usb_present && usb_present) {
|
|
/* USB inserted */
|
|
chip->usb_present = usb_present;
|
|
handle_usb_insertion(chip);
|
|
} else if (usb_present && chip->apsd_rerun) {
|
|
handle_usb_insertion(chip);
|
|
} else if (chip->usb_psy && !chip->usb_psy->get_property(
|
|
chip->usb_psy, POWER_SUPPLY_PROP_TYPE,
|
|
&prop)) {
|
|
if (((prop.intval == POWER_SUPPLY_TYPE_USB_CDP) ||
|
|
(prop.intval == POWER_SUPPLY_TYPE_USB)) &&
|
|
chip->usb_present && !usb_present) {
|
|
/* CDP or SDP removed */
|
|
chip->usb_present = !chip->usb_present;
|
|
handle_usb_removal(chip);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int chg_inhibit_handler(struct smb135x_chg *chip, u8 rt_stat)
|
|
{
|
|
/*
|
|
* charger is inserted when the battery voltage is high
|
|
* so h/w won't start charging just yet. Treat this as
|
|
* battery full
|
|
*/
|
|
pr_debug("rt_stat = 0x%02x\n", rt_stat);
|
|
|
|
if (!chip->inhibit_disabled)
|
|
chip->chg_done_batt_full = !!rt_stat;
|
|
return 0;
|
|
}
|
|
|
|
struct smb_irq_info {
|
|
const char *name;
|
|
int (*smb_irq)(struct smb135x_chg *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];
|
|
};
|
|
|
|
static struct irq_handler_info handlers[] = {
|
|
{IRQ_A_REG, 0, 0,
|
|
{
|
|
{
|
|
.name = "cold_soft",
|
|
.smb_irq = cold_soft_handler,
|
|
},
|
|
{
|
|
.name = "hot_soft",
|
|
.smb_irq = hot_soft_handler,
|
|
},
|
|
{
|
|
.name = "cold_hard",
|
|
.smb_irq = cold_hard_handler,
|
|
},
|
|
{
|
|
.name = "hot_hard",
|
|
.smb_irq = hot_hard_handler,
|
|
},
|
|
},
|
|
},
|
|
{IRQ_B_REG, 0, 0,
|
|
{
|
|
{
|
|
.name = "chg_hot",
|
|
.smb_irq = chg_hot_handler,
|
|
},
|
|
{
|
|
.name = "vbat_low",
|
|
.smb_irq = vbat_low_handler,
|
|
},
|
|
{
|
|
.name = "battery_missing",
|
|
.smb_irq = battery_missing_handler,
|
|
},
|
|
{
|
|
.name = "battery_missing",
|
|
.smb_irq = battery_missing_handler,
|
|
},
|
|
},
|
|
},
|
|
{IRQ_C_REG, 0, 0,
|
|
{
|
|
{
|
|
.name = "chg_term",
|
|
.smb_irq = chg_term_handler,
|
|
},
|
|
{
|
|
.name = "taper",
|
|
.smb_irq = taper_handler,
|
|
},
|
|
{
|
|
.name = "recharge",
|
|
.smb_irq = recharge_handler,
|
|
},
|
|
{
|
|
.name = "fast_chg",
|
|
.smb_irq = fast_chg_handler,
|
|
},
|
|
},
|
|
},
|
|
{IRQ_D_REG, 0, 0,
|
|
{
|
|
{
|
|
.name = "prechg_timeout",
|
|
},
|
|
{
|
|
.name = "safety_timeout",
|
|
.smb_irq = safety_timeout_handler,
|
|
},
|
|
{
|
|
.name = "aicl_done",
|
|
},
|
|
{
|
|
.name = "battery_ov",
|
|
},
|
|
},
|
|
},
|
|
{IRQ_E_REG, 0, 0,
|
|
{
|
|
{
|
|
.name = "usbin_uv",
|
|
.smb_irq = usbin_uv_handler,
|
|
},
|
|
{
|
|
.name = "usbin_ov",
|
|
.smb_irq = usbin_ov_handler,
|
|
},
|
|
{
|
|
.name = "dcin_uv",
|
|
.smb_irq = dcin_uv_handler,
|
|
},
|
|
{
|
|
.name = "dcin_ov",
|
|
.smb_irq = dcin_ov_handler,
|
|
},
|
|
},
|
|
},
|
|
{IRQ_F_REG, 0, 0,
|
|
{
|
|
{
|
|
.name = "power_ok",
|
|
.smb_irq = power_ok_handler,
|
|
},
|
|
{
|
|
.name = "rid",
|
|
.smb_irq = rid_handler,
|
|
},
|
|
{
|
|
.name = "otg_fail",
|
|
},
|
|
{
|
|
.name = "otg_oc",
|
|
.smb_irq = otg_oc_handler,
|
|
},
|
|
},
|
|
},
|
|
{IRQ_G_REG, 0, 0,
|
|
{
|
|
{
|
|
.name = "chg_inhibit",
|
|
.smb_irq = chg_inhibit_handler,
|
|
},
|
|
{
|
|
.name = "chg_error",
|
|
},
|
|
{
|
|
.name = "wd_timeout",
|
|
},
|
|
{
|
|
.name = "src_detect",
|
|
.smb_irq = src_detect_handler,
|
|
},
|
|
},
|
|
},
|
|
};
|
|
|
|
static int smb135x_irq_read(struct smb135x_chg *chip)
|
|
{
|
|
int rc, i;
|
|
|
|
/*
|
|
* When dcin path is suspended the irq triggered status is not cleared
|
|
* causing a storm. To prevent this situation unsuspend dcin path while
|
|
* reading interrupts and restore its status back.
|
|
*/
|
|
mutex_lock(&chip->path_suspend_lock);
|
|
|
|
if (chip->dc_suspended)
|
|
__smb135x_dc_suspend(chip, false);
|
|
|
|
for (i = 0; i < ARRAY_SIZE(handlers); i++) {
|
|
rc = smb135x_read(chip, handlers[i].stat_reg,
|
|
&handlers[i].val);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Couldn't read %d rc = %d\n",
|
|
handlers[i].stat_reg, rc);
|
|
handlers[i].val = 0;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (chip->dc_suspended)
|
|
__smb135x_dc_suspend(chip, true);
|
|
|
|
mutex_unlock(&chip->path_suspend_lock);
|
|
|
|
return rc;
|
|
}
|
|
#define IRQ_LATCHED_MASK 0x02
|
|
#define IRQ_STATUS_MASK 0x01
|
|
#define BITS_PER_IRQ 2
|
|
static irqreturn_t smb135x_chg_stat_handler(int irq, void *dev_id)
|
|
{
|
|
struct smb135x_chg *chip = dev_id;
|
|
int i, j;
|
|
u8 triggered;
|
|
u8 changed;
|
|
u8 rt_stat, prev_rt_stat;
|
|
int rc;
|
|
int handler_count = 0;
|
|
|
|
mutex_lock(&chip->irq_complete);
|
|
chip->irq_waiting = true;
|
|
if (!chip->resume_completed) {
|
|
dev_dbg(chip->dev, "IRQ triggered before device-resume\n");
|
|
disable_irq_nosync(irq);
|
|
mutex_unlock(&chip->irq_complete);
|
|
return IRQ_HANDLED;
|
|
}
|
|
chip->irq_waiting = false;
|
|
|
|
smb135x_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)
|
|
dev_err(chip->dev,
|
|
"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);
|
|
}
|
|
if (chip->dc_psy_type != -EINVAL) {
|
|
pr_debug("dc psy changed\n");
|
|
power_supply_changed(&chip->dc_psy);
|
|
}
|
|
}
|
|
|
|
mutex_unlock(&chip->irq_complete);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
#define LAST_CNFG_REG 0x1F
|
|
static int show_cnfg_regs(struct seq_file *m, void *data)
|
|
{
|
|
struct smb135x_chg *chip = m->private;
|
|
int rc;
|
|
u8 reg;
|
|
u8 addr;
|
|
|
|
for (addr = 0; addr <= LAST_CNFG_REG; addr++) {
|
|
rc = smb135x_read(chip, addr, ®);
|
|
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 smb135x_chg *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,
|
|
};
|
|
|
|
#define FIRST_CMD_REG 0x40
|
|
#define LAST_CMD_REG 0x42
|
|
static int show_cmd_regs(struct seq_file *m, void *data)
|
|
{
|
|
struct smb135x_chg *chip = m->private;
|
|
int rc;
|
|
u8 reg;
|
|
u8 addr;
|
|
|
|
for (addr = FIRST_CMD_REG; addr <= LAST_CMD_REG; addr++) {
|
|
rc = smb135x_read(chip, addr, ®);
|
|
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 smb135x_chg *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,
|
|
};
|
|
|
|
#define FIRST_STATUS_REG 0x46
|
|
#define LAST_STATUS_REG 0x56
|
|
static int show_status_regs(struct seq_file *m, void *data)
|
|
{
|
|
struct smb135x_chg *chip = m->private;
|
|
int rc;
|
|
u8 reg;
|
|
u8 addr;
|
|
|
|
for (addr = FIRST_STATUS_REG; addr <= LAST_STATUS_REG; addr++) {
|
|
rc = smb135x_read(chip, addr, ®);
|
|
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 smb135x_chg *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 smb135x_chg *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 smb135x_chg *chip = data;
|
|
int rc;
|
|
u8 temp;
|
|
|
|
rc = smb135x_read(chip, chip->peek_poke_address, &temp);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev,
|
|
"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 smb135x_chg *chip = data;
|
|
int rc;
|
|
u8 temp;
|
|
|
|
temp = (u8) val;
|
|
rc = smb135x_write(chip, chip->peek_poke_address, temp);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev,
|
|
"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 smb135x_chg *chip = data;
|
|
|
|
smb135x_chg_stat_handler(chip->client->irq, data);
|
|
return 0;
|
|
}
|
|
DEFINE_SIMPLE_ATTRIBUTE(force_irq_ops, NULL, force_irq_set, "0x%02llx\n");
|
|
|
|
static int force_rechg_set(void *data, u64 val)
|
|
{
|
|
int rc = 0;
|
|
struct smb135x_chg *chip = data;
|
|
|
|
if (!chip->chg_enabled) {
|
|
pr_debug("Charging Disabled force recharge not allowed\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!chip->inhibit_disabled) {
|
|
rc = smb135x_masked_write(chip, CFG_14_REG, EN_CHG_INHIBIT_BIT,
|
|
0);
|
|
if (rc)
|
|
dev_err(chip->dev,
|
|
"Couldn't disable charge-inhibit rc=%d\n", rc);
|
|
|
|
/* delay for charge-inhibit to take affect */
|
|
msleep(500);
|
|
}
|
|
|
|
rc |= smb135x_charging(chip, false);
|
|
rc |= smb135x_charging(chip, true);
|
|
|
|
if (!chip->inhibit_disabled) {
|
|
rc |= smb135x_masked_write(chip, CFG_14_REG,
|
|
EN_CHG_INHIBIT_BIT, EN_CHG_INHIBIT_BIT);
|
|
if (rc)
|
|
dev_err(chip->dev,
|
|
"Couldn't enable charge-inhibit rc=%d\n", rc);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
DEFINE_SIMPLE_ATTRIBUTE(force_rechg_ops, NULL, force_rechg_set, "0x%02llx\n");
|
|
|
|
#ifdef DEBUG
|
|
static void dump_regs(struct smb135x_chg *chip)
|
|
{
|
|
int rc;
|
|
u8 reg;
|
|
u8 addr;
|
|
|
|
for (addr = 0; addr <= LAST_CNFG_REG; addr++) {
|
|
rc = smb135x_read(chip, addr, ®);
|
|
if (rc < 0)
|
|
dev_err(chip->dev, "Couldn't read 0x%02x rc = %d\n",
|
|
addr, rc);
|
|
else
|
|
pr_debug("0x%02x = 0x%02x\n", addr, reg);
|
|
}
|
|
|
|
for (addr = FIRST_STATUS_REG; addr <= LAST_STATUS_REG; addr++) {
|
|
rc = smb135x_read(chip, addr, ®);
|
|
if (rc < 0)
|
|
dev_err(chip->dev, "Couldn't read 0x%02x rc = %d\n",
|
|
addr, rc);
|
|
else
|
|
pr_debug("0x%02x = 0x%02x\n", addr, reg);
|
|
}
|
|
|
|
for (addr = FIRST_CMD_REG; addr <= LAST_CMD_REG; addr++) {
|
|
rc = smb135x_read(chip, addr, ®);
|
|
if (rc < 0)
|
|
dev_err(chip->dev, "Couldn't read 0x%02x rc = %d\n",
|
|
addr, rc);
|
|
else
|
|
pr_debug("0x%02x = 0x%02x\n", addr, reg);
|
|
}
|
|
}
|
|
#else
|
|
static void dump_regs(struct smb135x_chg *chip)
|
|
{
|
|
}
|
|
#endif
|
|
static int determine_initial_status(struct smb135x_chg *chip)
|
|
{
|
|
int rc;
|
|
u8 reg;
|
|
|
|
/*
|
|
* It is okay to read the interrupt status here since
|
|
* interrupts aren't requested. reading interrupt status
|
|
* clears the interrupt so be careful to read interrupt
|
|
* status only in interrupt handling code
|
|
*/
|
|
|
|
chip->batt_present = true;
|
|
rc = smb135x_read(chip, IRQ_B_REG, ®);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Couldn't read irq b rc = %d\n", rc);
|
|
return rc;
|
|
}
|
|
if (reg & IRQ_B_BATT_TERMINAL_BIT || reg & IRQ_B_BATT_MISSING_BIT)
|
|
chip->batt_present = false;
|
|
rc = smb135x_read(chip, STATUS_4_REG, ®);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Couldn't read status 4 rc = %d\n", rc);
|
|
return rc;
|
|
}
|
|
/* treat battery gone if less than 2V */
|
|
if (reg & BATT_LESS_THAN_2V)
|
|
chip->batt_present = false;
|
|
|
|
rc = smb135x_read(chip, IRQ_A_REG, ®);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Couldn't read irq A rc = %d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
if (reg & IRQ_A_HOT_HARD_BIT)
|
|
chip->batt_hot = true;
|
|
if (reg & IRQ_A_COLD_HARD_BIT)
|
|
chip->batt_cold = true;
|
|
if (reg & IRQ_A_HOT_SOFT_BIT)
|
|
chip->batt_warm = true;
|
|
if (reg & IRQ_A_COLD_SOFT_BIT)
|
|
chip->batt_cool = true;
|
|
|
|
rc = smb135x_read(chip, IRQ_C_REG, ®);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Couldn't read irq A rc = %d\n", rc);
|
|
return rc;
|
|
}
|
|
if (reg & IRQ_C_TERM_BIT)
|
|
chip->chg_done_batt_full = true;
|
|
|
|
rc = smb135x_read(chip, IRQ_E_REG, ®);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Couldn't read irq E rc = %d\n", rc);
|
|
return rc;
|
|
}
|
|
chip->usb_present = !(reg & IRQ_E_USB_OV_BIT)
|
|
&& !(reg & IRQ_E_USB_UV_BIT);
|
|
chip->dc_present = !(reg & IRQ_E_DC_OV_BIT) && !(reg & IRQ_E_DC_UV_BIT);
|
|
|
|
if (chip->usb_present)
|
|
handle_usb_insertion(chip);
|
|
else
|
|
handle_usb_removal(chip);
|
|
|
|
if (chip->dc_psy_type != -EINVAL) {
|
|
if (chip->dc_psy_type == POWER_SUPPLY_TYPE_WIRELESS) {
|
|
/*
|
|
* put the dc path in suspend state if it is powered
|
|
* by wireless charger
|
|
*/
|
|
if (chip->dc_present)
|
|
smb135x_path_suspend(chip, DC, CURRENT, false);
|
|
else
|
|
smb135x_path_suspend(chip, DC, CURRENT, true);
|
|
}
|
|
}
|
|
|
|
chip->usb_slave_present = is_usb_slave_present(chip);
|
|
if (chip->usb_psy && !chip->id_line_not_connected) {
|
|
pr_debug("setting usb psy usb_otg = %d\n",
|
|
chip->usb_slave_present);
|
|
power_supply_set_usb_otg(chip->usb_psy,
|
|
chip->usb_slave_present);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int smb135x_hw_init(struct smb135x_chg *chip)
|
|
{
|
|
int rc;
|
|
int i;
|
|
u8 reg, mask;
|
|
|
|
if (chip->pinctrl_state_name) {
|
|
chip->smb_pinctrl = pinctrl_get_select(chip->dev,
|
|
chip->pinctrl_state_name);
|
|
if (IS_ERR(chip->smb_pinctrl)) {
|
|
pr_err("Could not get/set %s pinctrl state rc = %ld\n",
|
|
chip->pinctrl_state_name,
|
|
PTR_ERR(chip->smb_pinctrl));
|
|
return PTR_ERR(chip->smb_pinctrl);
|
|
}
|
|
}
|
|
|
|
if (chip->therm_bias_vreg) {
|
|
rc = regulator_enable(chip->therm_bias_vreg);
|
|
if (rc) {
|
|
pr_err("Couldn't enable therm-bias rc = %d\n", rc);
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Enable USB data line pullup regulator this is needed for the D+
|
|
* line to be at proper voltage for HVDCP charger detection.
|
|
*/
|
|
if (chip->usb_pullup_vreg) {
|
|
rc = regulator_enable(chip->usb_pullup_vreg);
|
|
if (rc) {
|
|
pr_err("Unable to enable data line pull-up regulator rc=%d\n",
|
|
rc);
|
|
if (chip->therm_bias_vreg)
|
|
regulator_disable(chip->therm_bias_vreg);
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
rc = smb135x_enable_volatile_writes(chip);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Couldn't configure for volatile rc = %d\n",
|
|
rc);
|
|
goto free_regulator;
|
|
}
|
|
|
|
/*
|
|
* force using current from the register i.e. ignore auto
|
|
* power source detect (APSD) mA ratings
|
|
*/
|
|
mask = USE_REGISTER_FOR_CURRENT;
|
|
|
|
if (chip->workaround_flags & WRKARND_USB100_BIT)
|
|
reg = 0;
|
|
else
|
|
/* this ignores APSD results */
|
|
reg = USE_REGISTER_FOR_CURRENT;
|
|
|
|
rc = smb135x_masked_write(chip, CMD_INPUT_LIMIT, mask, reg);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Couldn't set input limit cmd rc=%d\n", rc);
|
|
goto free_regulator;
|
|
}
|
|
|
|
/* set bit 0 = 100mA bit 1 = 500mA and set register control */
|
|
rc = smb135x_masked_write(chip, CFG_E_REG,
|
|
POLARITY_100_500_BIT | USB_CTRL_BY_PIN_BIT,
|
|
POLARITY_100_500_BIT);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Couldn't set usbin cfg rc=%d\n", rc);
|
|
goto free_regulator;
|
|
}
|
|
|
|
/*
|
|
* set chg en by cmd register, set chg en by writing bit 1,
|
|
* enable auto pre to fast, enable current termination, enable
|
|
* auto recharge, enable chg inhibition based on the dt flag
|
|
*/
|
|
if (chip->inhibit_disabled)
|
|
reg = 0;
|
|
else
|
|
reg = EN_CHG_INHIBIT_BIT;
|
|
|
|
rc = smb135x_masked_write(chip, CFG_14_REG,
|
|
CHG_EN_BY_PIN_BIT | CHG_EN_ACTIVE_LOW_BIT
|
|
| PRE_TO_FAST_REQ_CMD_BIT | DISABLE_AUTO_RECHARGE_BIT
|
|
| EN_CHG_INHIBIT_BIT, reg);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Couldn't set cfg 14 rc=%d\n", rc);
|
|
goto free_regulator;
|
|
}
|
|
|
|
/* control USB suspend via command bits */
|
|
rc = smb135x_masked_write(chip, USBIN_DCIN_CFG_REG,
|
|
USBIN_SUSPEND_VIA_COMMAND_BIT, USBIN_SUSPEND_VIA_COMMAND_BIT);
|
|
|
|
/* set the float voltage */
|
|
if (chip->vfloat_mv != -EINVAL) {
|
|
rc = smb135x_float_voltage_set(chip, chip->vfloat_mv);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev,
|
|
"Couldn't set float voltage rc = %d\n", rc);
|
|
goto free_regulator;
|
|
}
|
|
}
|
|
|
|
/* set iterm */
|
|
if (chip->iterm_ma != -EINVAL) {
|
|
if (chip->iterm_disabled) {
|
|
dev_err(chip->dev, "Error: Both iterm_disabled and iterm_ma set\n");
|
|
rc = -EINVAL;
|
|
goto free_regulator;
|
|
} else {
|
|
if (chip->iterm_ma <= 50)
|
|
reg = CHG_ITERM_50MA;
|
|
else if (chip->iterm_ma <= 100)
|
|
reg = CHG_ITERM_100MA;
|
|
else if (chip->iterm_ma <= 150)
|
|
reg = CHG_ITERM_150MA;
|
|
else if (chip->iterm_ma <= 200)
|
|
reg = CHG_ITERM_200MA;
|
|
else if (chip->iterm_ma <= 250)
|
|
reg = CHG_ITERM_250MA;
|
|
else if (chip->iterm_ma <= 300)
|
|
reg = CHG_ITERM_300MA;
|
|
else if (chip->iterm_ma <= 500)
|
|
reg = CHG_ITERM_500MA;
|
|
else
|
|
reg = CHG_ITERM_600MA;
|
|
|
|
rc = smb135x_masked_write(chip, CFG_3_REG,
|
|
CHG_ITERM_MASK, reg);
|
|
if (rc) {
|
|
dev_err(chip->dev,
|
|
"Couldn't set iterm rc = %d\n", rc);
|
|
goto free_regulator;
|
|
}
|
|
|
|
rc = smb135x_masked_write(chip, CFG_14_REG,
|
|
DISABLE_CURRENT_TERM_BIT, 0);
|
|
if (rc) {
|
|
dev_err(chip->dev,
|
|
"Couldn't enable iterm rc = %d\n", rc);
|
|
goto free_regulator;
|
|
}
|
|
}
|
|
} else if (chip->iterm_disabled) {
|
|
rc = smb135x_masked_write(chip, CFG_14_REG,
|
|
DISABLE_CURRENT_TERM_BIT,
|
|
DISABLE_CURRENT_TERM_BIT);
|
|
if (rc) {
|
|
dev_err(chip->dev, "Couldn't set iterm rc = %d\n",
|
|
rc);
|
|
goto free_regulator;
|
|
}
|
|
}
|
|
|
|
/* set the safety time voltage */
|
|
if (chip->safety_time != -EINVAL) {
|
|
if (chip->safety_time == 0) {
|
|
/* safety timer disabled */
|
|
reg = 1 << SAFETY_TIME_EN_SHIFT;
|
|
rc = smb135x_masked_write(chip, CFG_16_REG,
|
|
SAFETY_TIME_EN_BIT, reg);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev,
|
|
"Couldn't disable safety timer rc = %d\n",
|
|
rc);
|
|
goto free_regulator;
|
|
}
|
|
} else {
|
|
for (i = 0; i < ARRAY_SIZE(chg_time); i++) {
|
|
if (chip->safety_time <= chg_time[i]) {
|
|
reg = i << SAFETY_TIME_MINUTES_SHIFT;
|
|
break;
|
|
}
|
|
}
|
|
rc = smb135x_masked_write(chip, CFG_16_REG,
|
|
SAFETY_TIME_EN_BIT | SAFETY_TIME_MINUTES_MASK,
|
|
reg);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev,
|
|
"Couldn't set safety timer rc = %d\n",
|
|
rc);
|
|
goto free_regulator;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* battery missing detection */
|
|
rc = smb135x_masked_write(chip, CFG_19_REG,
|
|
BATT_MISSING_ALGO_BIT | BATT_MISSING_THERM_BIT,
|
|
chip->bmd_algo_disabled ? BATT_MISSING_THERM_BIT :
|
|
BATT_MISSING_ALGO_BIT);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Couldn't set batt_missing config = %d\n",
|
|
rc);
|
|
goto free_regulator;
|
|
}
|
|
|
|
/* set maximum fastchg current */
|
|
if (chip->fastchg_ma != -EINVAL) {
|
|
rc = smb135x_set_fastchg_current(chip, chip->fastchg_ma);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Couldn't set fastchg current = %d\n",
|
|
rc);
|
|
goto free_regulator;
|
|
}
|
|
}
|
|
|
|
if (chip->usb_pullup_vreg) {
|
|
/* enable 9V HVDCP adapter support */
|
|
rc = smb135x_masked_write(chip, CFG_E_REG, HVDCP_5_9_BIT,
|
|
HVDCP_5_9_BIT);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev,
|
|
"Couldn't request for 5 or 9V rc=%d\n", rc);
|
|
goto free_regulator;
|
|
}
|
|
}
|
|
|
|
if (chip->gamma_setting) {
|
|
rc = smb135x_masked_write(chip, CFG_1B_REG, COLD_HARD_MASK,
|
|
chip->gamma_setting[0] << COLD_HARD_SHIFT);
|
|
|
|
rc |= smb135x_masked_write(chip, CFG_1B_REG, HOT_HARD_MASK,
|
|
chip->gamma_setting[1] << HOT_HARD_SHIFT);
|
|
|
|
rc |= smb135x_masked_write(chip, CFG_1B_REG, COLD_SOFT_MASK,
|
|
chip->gamma_setting[2] << COLD_SOFT_SHIFT);
|
|
|
|
rc |= smb135x_masked_write(chip, CFG_1B_REG, HOT_SOFT_MASK,
|
|
chip->gamma_setting[3] << HOT_SOFT_SHIFT);
|
|
if (rc < 0)
|
|
goto free_regulator;
|
|
}
|
|
|
|
__smb135x_charging(chip, chip->chg_enabled);
|
|
|
|
/* interrupt enabling - active low */
|
|
if (chip->client->irq) {
|
|
mask = CHG_STAT_IRQ_ONLY_BIT | CHG_STAT_ACTIVE_HIGH_BIT
|
|
| CHG_STAT_DISABLE_BIT;
|
|
reg = CHG_STAT_IRQ_ONLY_BIT;
|
|
rc = smb135x_masked_write(chip, CFG_17_REG, mask, reg);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Couldn't set irq config rc = %d\n",
|
|
rc);
|
|
goto free_regulator;
|
|
}
|
|
|
|
/* enabling only interesting interrupts */
|
|
rc = smb135x_write(chip, IRQ_CFG_REG,
|
|
IRQ_BAT_HOT_COLD_HARD_BIT
|
|
| IRQ_BAT_HOT_COLD_SOFT_BIT
|
|
| IRQ_OTG_OVER_CURRENT_BIT
|
|
| IRQ_INTERNAL_TEMPERATURE_BIT
|
|
| IRQ_USBIN_UV_BIT);
|
|
|
|
rc |= smb135x_write(chip, IRQ2_CFG_REG,
|
|
IRQ2_SAFETY_TIMER_BIT
|
|
| IRQ2_CHG_ERR_BIT
|
|
| IRQ2_CHG_PHASE_CHANGE_BIT
|
|
| IRQ2_POWER_OK_BIT
|
|
| IRQ2_BATT_MISSING_BIT
|
|
| IRQ2_VBAT_LOW_BIT);
|
|
|
|
rc |= smb135x_write(chip, IRQ3_CFG_REG, IRQ3_SRC_DETECT_BIT
|
|
| IRQ3_DCIN_UV_BIT | IRQ3_RID_DETECT_BIT);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Couldn't set irq enable rc = %d\n",
|
|
rc);
|
|
goto free_regulator;
|
|
}
|
|
}
|
|
|
|
/* resume threshold */
|
|
if (chip->resume_delta_mv != -EINVAL) {
|
|
smb135x_set_resume_threshold(chip, chip->resume_delta_mv);
|
|
}
|
|
|
|
/* DC path current settings */
|
|
if (chip->dc_psy_type != -EINVAL) {
|
|
rc = smb135x_set_dc_chg_current(chip, chip->dc_psy_ma);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Couldn't set dc charge current rc = %d\n",
|
|
rc);
|
|
goto free_regulator;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* on some devices the battery is powered via external sources which
|
|
* could raise its voltage above the float voltage. smb135x chips go
|
|
* in to reverse boost in such a situation and the workaround is to
|
|
* disable float voltage compensation (note that the battery will appear
|
|
* hot/cold when powered via external source).
|
|
*/
|
|
|
|
if (chip->soft_vfloat_comp_disabled) {
|
|
mask = HOT_SOFT_VFLOAT_COMP_EN_BIT
|
|
| COLD_SOFT_VFLOAT_COMP_EN_BIT;
|
|
rc = smb135x_masked_write(chip, CFG_1A_REG, mask, 0);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Couldn't disable soft vfloat rc = %d\n",
|
|
rc);
|
|
goto free_regulator;
|
|
}
|
|
}
|
|
|
|
if (chip->soft_current_comp_disabled) {
|
|
mask = HOT_SOFT_CURRENT_COMP_EN_BIT
|
|
| COLD_SOFT_CURRENT_COMP_EN_BIT;
|
|
rc = smb135x_masked_write(chip, CFG_1A_REG, mask, 0);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Couldn't disable soft current rc = %d\n",
|
|
rc);
|
|
goto free_regulator;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Command mode for OTG control. This gives us RID interrupts but keeps
|
|
* enabling the 5V OTG via i2c register control
|
|
*/
|
|
rc = smb135x_masked_write(chip, USBIN_OTG_REG, OTG_CNFG_MASK,
|
|
OTG_CNFG_COMMAND_CTRL);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Couldn't write to otg cfg reg rc = %d\n",
|
|
rc);
|
|
goto free_regulator;
|
|
}
|
|
return 0;
|
|
|
|
free_regulator:
|
|
if (chip->therm_bias_vreg)
|
|
regulator_disable(chip->therm_bias_vreg);
|
|
if (chip->usb_pullup_vreg)
|
|
regulator_disable(chip->usb_pullup_vreg);
|
|
return rc;
|
|
}
|
|
|
|
static struct of_device_id smb135x_match_table[] = {
|
|
{
|
|
.compatible = "qcom,smb1356-charger",
|
|
.data = &version_data[V_SMB1356],
|
|
},
|
|
{
|
|
.compatible = "qcom,smb1357-charger",
|
|
.data = &version_data[V_SMB1357],
|
|
},
|
|
{
|
|
.compatible = "qcom,smb1358-charger",
|
|
.data = &version_data[V_SMB1358],
|
|
},
|
|
{
|
|
.compatible = "qcom,smb1359-charger",
|
|
.data = &version_data[V_SMB1359],
|
|
},
|
|
{ },
|
|
};
|
|
|
|
#define DC_MA_MIN 300
|
|
#define DC_MA_MAX 2000
|
|
#define NUM_GAMMA_VALUES 4
|
|
static int smb_parse_dt(struct smb135x_chg *chip)
|
|
{
|
|
int rc;
|
|
struct device_node *node = chip->dev->of_node;
|
|
const char *dc_psy_type;
|
|
|
|
if (!node) {
|
|
dev_err(chip->dev, "device tree info. missing\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
rc = of_property_read_u32(node, "qcom,float-voltage-mv",
|
|
&chip->vfloat_mv);
|
|
if (rc < 0)
|
|
chip->vfloat_mv = -EINVAL;
|
|
|
|
rc = of_property_read_u32(node, "qcom,charging-timeout",
|
|
&chip->safety_time);
|
|
if (rc < 0)
|
|
chip->safety_time = -EINVAL;
|
|
|
|
if (!rc &&
|
|
(chip->safety_time > chg_time[ARRAY_SIZE(chg_time) - 1])) {
|
|
dev_err(chip->dev, "Bad charging-timeout %d\n",
|
|
chip->safety_time);
|
|
return -EINVAL;
|
|
}
|
|
|
|
chip->bmd_algo_disabled = of_property_read_bool(node,
|
|
"qcom,bmd-algo-disabled");
|
|
|
|
chip->dc_psy_type = -EINVAL;
|
|
dc_psy_type = of_get_property(node, "qcom,dc-psy-type", NULL);
|
|
if (dc_psy_type) {
|
|
if (strcmp(dc_psy_type, "Mains") == 0)
|
|
chip->dc_psy_type = POWER_SUPPLY_TYPE_MAINS;
|
|
else if (strcmp(dc_psy_type, "Wireless") == 0)
|
|
chip->dc_psy_type = POWER_SUPPLY_TYPE_WIRELESS;
|
|
}
|
|
|
|
if (chip->dc_psy_type != -EINVAL) {
|
|
rc = of_property_read_u32(node, "qcom,dc-psy-ma",
|
|
&chip->dc_psy_ma);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev,
|
|
"no mA current for dc rc = %d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
if (chip->dc_psy_ma < DC_MA_MIN
|
|
|| chip->dc_psy_ma > DC_MA_MAX) {
|
|
dev_err(chip->dev, "Bad dc mA %d\n", chip->dc_psy_ma);
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
rc = of_property_read_u32(node, "qcom,recharge-thresh-mv",
|
|
&chip->resume_delta_mv);
|
|
if (rc < 0)
|
|
chip->resume_delta_mv = -EINVAL;
|
|
|
|
rc = of_property_read_u32(node, "qcom,iterm-ma", &chip->iterm_ma);
|
|
if (rc < 0)
|
|
chip->iterm_ma = -EINVAL;
|
|
|
|
chip->iterm_disabled = of_property_read_bool(node,
|
|
"qcom,iterm-disabled");
|
|
|
|
chip->chg_disabled_permanently = (of_property_read_bool(node,
|
|
"qcom,charging-disabled"));
|
|
chip->chg_enabled = !chip->chg_disabled_permanently;
|
|
|
|
chip->inhibit_disabled = of_property_read_bool(node,
|
|
"qcom,inhibit-disabled");
|
|
|
|
chip->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;
|
|
|
|
rc = of_property_read_u32(node, "qcom,fastchg-ma", &chip->fastchg_ma);
|
|
if (rc < 0)
|
|
chip->fastchg_ma = -EINVAL;
|
|
|
|
chip->soft_vfloat_comp_disabled = of_property_read_bool(node,
|
|
"qcom,soft-vfloat-comp-disabled");
|
|
|
|
chip->soft_current_comp_disabled = of_property_read_bool(node,
|
|
"qcom,soft-current-comp-disabled");
|
|
|
|
if (of_find_property(node, "therm-bias-supply", NULL)) {
|
|
/* get the thermistor bias regulator */
|
|
chip->therm_bias_vreg = devm_regulator_get(chip->dev,
|
|
"therm-bias");
|
|
if (IS_ERR(chip->therm_bias_vreg))
|
|
return PTR_ERR(chip->therm_bias_vreg);
|
|
}
|
|
|
|
/*
|
|
* Gamma value indicates the ratio of the pull up resistors and NTC
|
|
* resistor in battery pack. There are 4 options, refer to the graphic
|
|
* user interface and choose the right one.
|
|
*/
|
|
if (of_find_property(node, "qcom,gamma-setting",
|
|
&chip->gamma_setting_num)) {
|
|
chip->gamma_setting_num = chip->gamma_setting_num /
|
|
sizeof(chip->gamma_setting_num);
|
|
if (NUM_GAMMA_VALUES != chip->gamma_setting_num) {
|
|
pr_err("Gamma setting not correct!\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
chip->gamma_setting = devm_kzalloc(chip->dev,
|
|
chip->gamma_setting_num *
|
|
sizeof(chip->gamma_setting_num), GFP_KERNEL);
|
|
if (!chip->gamma_setting) {
|
|
pr_err("gamma setting kzalloc failed!\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
rc = of_property_read_u32_array(node,
|
|
"qcom,gamma-setting",
|
|
chip->gamma_setting, chip->gamma_setting_num);
|
|
if (rc) {
|
|
pr_err("Couldn't read gamma setting, rc = %d\n", rc);
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
if (of_find_property(node, "qcom,thermal-mitigation",
|
|
&chip->thermal_levels)) {
|
|
chip->thermal_mitigation = devm_kzalloc(chip->dev,
|
|
chip->thermal_levels,
|
|
GFP_KERNEL);
|
|
|
|
if (chip->thermal_mitigation == NULL) {
|
|
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->thermal_mitigation, chip->thermal_levels);
|
|
if (rc) {
|
|
pr_err("Couldn't read threm limits rc = %d\n", rc);
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
if (of_find_property(node, "usb-pullup-supply", NULL)) {
|
|
/* get the data line pull-up regulator */
|
|
chip->usb_pullup_vreg = devm_regulator_get(chip->dev,
|
|
"usb-pullup");
|
|
if (IS_ERR(chip->usb_pullup_vreg))
|
|
return PTR_ERR(chip->usb_pullup_vreg);
|
|
}
|
|
|
|
chip->pinctrl_state_name = of_get_property(node, "pinctrl-names", NULL);
|
|
|
|
chip->id_line_not_connected = of_property_read_bool(node,
|
|
"qcom,id-line-not-connected");
|
|
return 0;
|
|
}
|
|
|
|
static int create_debugfs_entries(struct smb135x_chg *chip)
|
|
{
|
|
chip->debug_root = debugfs_create_dir("smb135x", NULL);
|
|
if (!chip->debug_root)
|
|
dev_err(chip->dev, "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)
|
|
dev_err(chip->dev,
|
|
"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)
|
|
dev_err(chip->dev,
|
|
"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)
|
|
dev_err(chip->dev,
|
|
"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)
|
|
dev_err(chip->dev,
|
|
"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)
|
|
dev_err(chip->dev,
|
|
"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)
|
|
dev_err(chip->dev,
|
|
"Couldn't create force_irq debug file\n");
|
|
|
|
ent = debugfs_create_x32("skip_writes",
|
|
S_IFREG | S_IWUSR | S_IRUGO,
|
|
chip->debug_root,
|
|
&(chip->skip_writes));
|
|
if (!ent)
|
|
dev_err(chip->dev,
|
|
"Couldn't create skip writes debug file\n");
|
|
|
|
ent = debugfs_create_x32("skip_reads",
|
|
S_IFREG | S_IWUSR | S_IRUGO,
|
|
chip->debug_root,
|
|
&(chip->skip_reads));
|
|
if (!ent)
|
|
dev_err(chip->dev,
|
|
"Couldn't create skip reads debug file\n");
|
|
|
|
ent = debugfs_create_file("irq_count", S_IFREG | S_IRUGO,
|
|
chip->debug_root, chip,
|
|
&irq_count_debugfs_ops);
|
|
if (!ent)
|
|
dev_err(chip->dev,
|
|
"Couldn't create irq_count debug file\n");
|
|
|
|
ent = debugfs_create_file("force_recharge",
|
|
S_IFREG | S_IWUSR | S_IRUGO,
|
|
chip->debug_root, chip,
|
|
&force_rechg_ops);
|
|
if (!ent)
|
|
dev_err(chip->dev,
|
|
"Couldn't create force recharge debug file\n");
|
|
|
|
ent = debugfs_create_x32("usb_suspend_votes",
|
|
S_IFREG | S_IWUSR | S_IRUGO,
|
|
chip->debug_root,
|
|
&(chip->usb_suspended));
|
|
if (!ent)
|
|
dev_err(chip->dev,
|
|
"Couldn't create usb_suspend_votes file\n");
|
|
|
|
ent = debugfs_create_x32("dc_suspend_votes",
|
|
S_IFREG | S_IWUSR | S_IRUGO,
|
|
chip->debug_root,
|
|
&(chip->dc_suspended));
|
|
if (!ent)
|
|
dev_err(chip->dev,
|
|
"Couldn't create dc_suspend_votes file\n");
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int is_parallel_charger(struct i2c_client *client)
|
|
{
|
|
struct device_node *node = client->dev.of_node;
|
|
|
|
return of_property_read_bool(node, "qcom,parallel-charger");
|
|
}
|
|
|
|
static int smb135x_main_charger_probe(struct i2c_client *client,
|
|
const struct i2c_device_id *id)
|
|
{
|
|
int rc;
|
|
struct smb135x_chg *chip;
|
|
struct power_supply *usb_psy;
|
|
u8 reg = 0;
|
|
|
|
chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
|
|
if (!chip) {
|
|
dev_err(&client->dev, "Unable to allocate memory\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
chip->client = client;
|
|
chip->dev = &client->dev;
|
|
|
|
rc = smb_parse_dt(chip);
|
|
if (rc < 0) {
|
|
dev_err(&client->dev, "Unable to parse DT nodes\n");
|
|
return rc;
|
|
}
|
|
|
|
usb_psy = power_supply_get_by_name("usb");
|
|
if (!usb_psy && chip->chg_enabled) {
|
|
dev_dbg(&client->dev, "USB supply not found; defer probe\n");
|
|
return -EPROBE_DEFER;
|
|
}
|
|
chip->usb_psy = usb_psy;
|
|
|
|
chip->fake_battery_soc = -EINVAL;
|
|
|
|
INIT_DELAYED_WORK(&chip->wireless_insertion_work,
|
|
wireless_insertion_work);
|
|
|
|
INIT_DELAYED_WORK(&chip->reset_otg_oc_count_work,
|
|
reset_otg_oc_count_work);
|
|
mutex_init(&chip->path_suspend_lock);
|
|
mutex_init(&chip->current_change_lock);
|
|
mutex_init(&chip->read_write_lock);
|
|
mutex_init(&chip->otg_oc_count_lock);
|
|
device_init_wakeup(chip->dev, true);
|
|
/* probe the device to check if its actually connected */
|
|
rc = smb135x_read(chip, CFG_4_REG, ®);
|
|
if (rc) {
|
|
pr_err("Failed to detect SMB135x, device may be absent\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
i2c_set_clientdata(client, chip);
|
|
|
|
rc = smb135x_chip_version_and_revision(chip);
|
|
if (rc) {
|
|
dev_err(&client->dev,
|
|
"Couldn't detect version/revision rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
dump_regs(chip);
|
|
|
|
rc = smb135x_regulator_init(chip);
|
|
if (rc) {
|
|
dev_err(&client->dev,
|
|
"Couldn't initialize regulator rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
rc = smb135x_hw_init(chip);
|
|
if (rc < 0) {
|
|
dev_err(&client->dev,
|
|
"Unable to intialize hardware rc = %d\n", rc);
|
|
goto free_regulator;
|
|
}
|
|
|
|
rc = determine_initial_status(chip);
|
|
if (rc < 0) {
|
|
dev_err(&client->dev,
|
|
"Unable to determine init status rc = %d\n", rc);
|
|
goto free_regulator;
|
|
}
|
|
|
|
chip->batt_psy.name = "battery";
|
|
chip->batt_psy.type = POWER_SUPPLY_TYPE_BATTERY;
|
|
chip->batt_psy.get_property = smb135x_battery_get_property;
|
|
chip->batt_psy.set_property = smb135x_battery_set_property;
|
|
chip->batt_psy.properties = smb135x_battery_properties;
|
|
chip->batt_psy.num_properties = ARRAY_SIZE(smb135x_battery_properties);
|
|
chip->batt_psy.external_power_changed = smb135x_external_power_changed;
|
|
chip->batt_psy.property_is_writeable = smb135x_battery_is_writeable;
|
|
|
|
if (chip->bms_controlled_charging) {
|
|
chip->batt_psy.supplied_to = pm_batt_supplied_to;
|
|
chip->batt_psy.num_supplicants =
|
|
ARRAY_SIZE(pm_batt_supplied_to);
|
|
}
|
|
|
|
rc = power_supply_register(chip->dev, &chip->batt_psy);
|
|
if (rc < 0) {
|
|
dev_err(&client->dev,
|
|
"Unable to register batt_psy rc = %d\n", rc);
|
|
goto free_regulator;
|
|
}
|
|
|
|
if (chip->dc_psy_type != -EINVAL) {
|
|
chip->dc_psy.name = "dc";
|
|
chip->dc_psy.type = chip->dc_psy_type;
|
|
chip->dc_psy.get_property = smb135x_dc_get_property;
|
|
chip->dc_psy.properties = smb135x_dc_properties;
|
|
chip->dc_psy.num_properties = ARRAY_SIZE(smb135x_dc_properties);
|
|
rc = power_supply_register(chip->dev, &chip->dc_psy);
|
|
if (rc < 0) {
|
|
dev_err(&client->dev,
|
|
"Unable to register dc_psy rc = %d\n", rc);
|
|
goto unregister_batt_psy;
|
|
}
|
|
}
|
|
|
|
chip->resume_completed = true;
|
|
mutex_init(&chip->irq_complete);
|
|
|
|
/* STAT irq configuration */
|
|
if (client->irq) {
|
|
rc = devm_request_threaded_irq(&client->dev, client->irq, NULL,
|
|
smb135x_chg_stat_handler,
|
|
IRQF_TRIGGER_LOW | IRQF_ONESHOT,
|
|
"smb135x_chg_stat_irq", chip);
|
|
if (rc < 0) {
|
|
dev_err(&client->dev,
|
|
"request_irq for irq=%d failed rc = %d\n",
|
|
client->irq, rc);
|
|
goto unregister_dc_psy;
|
|
}
|
|
enable_irq_wake(client->irq);
|
|
}
|
|
|
|
create_debugfs_entries(chip);
|
|
dev_info(chip->dev, "SMB135X version = %s revision = %s successfully probed batt=%d dc = %d usb = %d\n",
|
|
version_str[chip->version],
|
|
revision_str[chip->revision],
|
|
smb135x_get_prop_batt_present(chip),
|
|
chip->dc_present, chip->usb_present);
|
|
return 0;
|
|
|
|
unregister_dc_psy:
|
|
if (chip->dc_psy_type != -EINVAL)
|
|
power_supply_unregister(&chip->dc_psy);
|
|
unregister_batt_psy:
|
|
power_supply_unregister(&chip->batt_psy);
|
|
free_regulator:
|
|
smb135x_regulator_deinit(chip);
|
|
return rc;
|
|
}
|
|
|
|
static int smb135x_parallel_charger_probe(struct i2c_client *client,
|
|
const struct i2c_device_id *id)
|
|
{
|
|
int rc;
|
|
struct smb135x_chg *chip;
|
|
const struct of_device_id *match;
|
|
struct device_node *node = client->dev.of_node;
|
|
|
|
chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
|
|
if (!chip) {
|
|
dev_err(&client->dev, "Unable to allocate memory\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
chip->client = client;
|
|
chip->dev = &client->dev;
|
|
chip->parallel_charger = true;
|
|
chip->dc_psy_type = -EINVAL;
|
|
|
|
chip->chg_enabled = !(of_property_read_bool(node,
|
|
"qcom,charging-disabled"));
|
|
|
|
rc = of_property_read_u32(node, "qcom,recharge-thresh-mv",
|
|
&chip->resume_delta_mv);
|
|
if (rc < 0)
|
|
chip->resume_delta_mv = -EINVAL;
|
|
|
|
rc = of_property_read_u32(node, "qcom,float-voltage-mv",
|
|
&chip->vfloat_mv);
|
|
if (rc < 0)
|
|
chip->vfloat_mv = -EINVAL;
|
|
|
|
mutex_init(&chip->path_suspend_lock);
|
|
mutex_init(&chip->current_change_lock);
|
|
mutex_init(&chip->read_write_lock);
|
|
|
|
match = of_match_node(smb135x_match_table, node);
|
|
if (match == NULL) {
|
|
dev_err(chip->dev, "device tree match not found\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
chip->version = *(int *)match->data;
|
|
smb135x_set_current_tables(chip);
|
|
|
|
i2c_set_clientdata(client, chip);
|
|
|
|
chip->parallel_psy.name = "usb-parallel";
|
|
chip->parallel_psy.type = POWER_SUPPLY_TYPE_USB_PARALLEL;
|
|
chip->parallel_psy.get_property = smb135x_parallel_get_property;
|
|
chip->parallel_psy.set_property = smb135x_parallel_set_property;
|
|
chip->parallel_psy.properties = smb135x_parallel_properties;
|
|
chip->parallel_psy.property_is_writeable
|
|
= smb135x_parallel_is_writeable;
|
|
chip->parallel_psy.num_properties
|
|
= ARRAY_SIZE(smb135x_parallel_properties);
|
|
|
|
rc = power_supply_register(chip->dev, &chip->parallel_psy);
|
|
if (rc < 0) {
|
|
dev_err(&client->dev,
|
|
"Unable to register parallel_psy rc = %d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
chip->resume_completed = true;
|
|
mutex_init(&chip->irq_complete);
|
|
|
|
create_debugfs_entries(chip);
|
|
|
|
dev_info(chip->dev, "SMB135X USB PARALLEL CHARGER version = %s successfully probed\n",
|
|
version_str[chip->version]);
|
|
return 0;
|
|
}
|
|
|
|
static int smb135x_charger_probe(struct i2c_client *client,
|
|
const struct i2c_device_id *id)
|
|
{
|
|
if (is_parallel_charger(client))
|
|
return smb135x_parallel_charger_probe(client, id);
|
|
else
|
|
return smb135x_main_charger_probe(client, id);
|
|
}
|
|
|
|
static int smb135x_charger_remove(struct i2c_client *client)
|
|
{
|
|
int rc;
|
|
struct smb135x_chg *chip = i2c_get_clientdata(client);
|
|
|
|
debugfs_remove_recursive(chip->debug_root);
|
|
|
|
if (chip->parallel_charger) {
|
|
power_supply_unregister(&chip->parallel_psy);
|
|
goto mutex_destroy;
|
|
}
|
|
|
|
if (chip->therm_bias_vreg) {
|
|
rc = regulator_disable(chip->therm_bias_vreg);
|
|
if (rc)
|
|
pr_err("Couldn't disable therm-bias rc = %d\n", rc);
|
|
}
|
|
|
|
if (chip->usb_pullup_vreg) {
|
|
rc = regulator_disable(chip->usb_pullup_vreg);
|
|
if (rc)
|
|
pr_err("Couldn't disable data-pullup rc = %d\n", rc);
|
|
}
|
|
|
|
if (chip->dc_psy_type != -EINVAL)
|
|
power_supply_unregister(&chip->dc_psy);
|
|
|
|
power_supply_unregister(&chip->batt_psy);
|
|
|
|
smb135x_regulator_deinit(chip);
|
|
|
|
mutex_destroy:
|
|
mutex_destroy(&chip->irq_complete);
|
|
return 0;
|
|
}
|
|
|
|
static int smb135x_suspend(struct device *dev)
|
|
{
|
|
struct i2c_client *client = to_i2c_client(dev);
|
|
struct smb135x_chg *chip = i2c_get_clientdata(client);
|
|
int i, rc;
|
|
|
|
/* no suspend resume activities for parallel charger */
|
|
if (chip->parallel_charger)
|
|
return 0;
|
|
|
|
/* Save the current IRQ config */
|
|
for (i = 0; i < 3; i++) {
|
|
rc = smb135x_read(chip, IRQ_CFG_REG + i,
|
|
&chip->irq_cfg_mask[i]);
|
|
if (rc)
|
|
dev_err(chip->dev,
|
|
"Couldn't save irq cfg regs rc=%d\n", rc);
|
|
}
|
|
|
|
/* enable only important IRQs */
|
|
rc = smb135x_write(chip, IRQ_CFG_REG, IRQ_USBIN_UV_BIT);
|
|
if (rc < 0)
|
|
dev_err(chip->dev, "Couldn't set irq_cfg rc = %d\n", rc);
|
|
|
|
rc = smb135x_write(chip, IRQ2_CFG_REG, IRQ2_BATT_MISSING_BIT
|
|
| IRQ2_VBAT_LOW_BIT
|
|
| IRQ2_POWER_OK_BIT);
|
|
if (rc < 0)
|
|
dev_err(chip->dev, "Couldn't set irq2_cfg rc = %d\n", rc);
|
|
|
|
rc = smb135x_write(chip, IRQ3_CFG_REG, IRQ3_SRC_DETECT_BIT
|
|
| IRQ3_DCIN_UV_BIT | IRQ3_RID_DETECT_BIT);
|
|
if (rc < 0)
|
|
dev_err(chip->dev, "Couldn't set irq3_cfg rc = %d\n", rc);
|
|
|
|
mutex_lock(&chip->irq_complete);
|
|
chip->resume_completed = false;
|
|
mutex_unlock(&chip->irq_complete);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int smb135x_suspend_noirq(struct device *dev)
|
|
{
|
|
struct i2c_client *client = to_i2c_client(dev);
|
|
struct smb135x_chg *chip = i2c_get_clientdata(client);
|
|
|
|
/* no suspend resume activities for parallel charger */
|
|
if (chip->parallel_charger)
|
|
return 0;
|
|
|
|
if (chip->irq_waiting) {
|
|
pr_err_ratelimited("Aborting suspend, an interrupt was detected while suspending\n");
|
|
return -EBUSY;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int smb135x_resume(struct device *dev)
|
|
{
|
|
struct i2c_client *client = to_i2c_client(dev);
|
|
struct smb135x_chg *chip = i2c_get_clientdata(client);
|
|
int i, rc;
|
|
|
|
/* no suspend resume activities for parallel charger */
|
|
if (chip->parallel_charger)
|
|
return 0;
|
|
/* Restore the IRQ config */
|
|
for (i = 0; i < 3; i++) {
|
|
rc = smb135x_write(chip, IRQ_CFG_REG + i,
|
|
chip->irq_cfg_mask[i]);
|
|
if (rc)
|
|
dev_err(chip->dev,
|
|
"Couldn't restore irq cfg regs rc=%d\n", rc);
|
|
}
|
|
mutex_lock(&chip->irq_complete);
|
|
chip->resume_completed = true;
|
|
if (chip->irq_waiting) {
|
|
mutex_unlock(&chip->irq_complete);
|
|
smb135x_chg_stat_handler(client->irq, chip);
|
|
enable_irq(client->irq);
|
|
} else {
|
|
mutex_unlock(&chip->irq_complete);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static const struct dev_pm_ops smb135x_pm_ops = {
|
|
.resume = smb135x_resume,
|
|
.suspend_noirq = smb135x_suspend_noirq,
|
|
.suspend = smb135x_suspend,
|
|
};
|
|
|
|
static const struct i2c_device_id smb135x_charger_id[] = {
|
|
{"smb135x-charger", 0},
|
|
{},
|
|
};
|
|
MODULE_DEVICE_TABLE(i2c, smb135x_charger_id);
|
|
|
|
static void smb135x_shutdown(struct i2c_client *client)
|
|
{
|
|
int rc;
|
|
struct smb135x_chg *chip = i2c_get_clientdata(client);
|
|
|
|
if (chip->usb_pullup_vreg) {
|
|
/*
|
|
* switch to 5V adapter to prevent any errorneous request of 12V
|
|
* when USB D+ line pull-up regulator turns off.
|
|
*/
|
|
rc = smb135x_masked_write(chip, CFG_E_REG, HVDCP_5_9_BIT, 0);
|
|
if (rc < 0)
|
|
dev_err(chip->dev,
|
|
"Couldn't request for 5V rc=%d\n", rc);
|
|
}
|
|
}
|
|
|
|
static struct i2c_driver smb135x_charger_driver = {
|
|
.driver = {
|
|
.name = "smb135x-charger",
|
|
.owner = THIS_MODULE,
|
|
.of_match_table = smb135x_match_table,
|
|
.pm = &smb135x_pm_ops,
|
|
},
|
|
.probe = smb135x_charger_probe,
|
|
.remove = smb135x_charger_remove,
|
|
.id_table = smb135x_charger_id,
|
|
.shutdown = smb135x_shutdown,
|
|
};
|
|
|
|
module_i2c_driver(smb135x_charger_driver);
|
|
|
|
MODULE_DESCRIPTION("SMB135x Charger");
|
|
MODULE_LICENSE("GPL v2");
|
|
MODULE_ALIAS("i2c:smb135x-charger");
|