You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2414 lines
61 KiB
2414 lines
61 KiB
/* Copyright (c) 2017-2020 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) "SMB1390: %s: " fmt, __func__
|
|
|
|
#include <linux/debugfs.h>
|
|
#include <linux/device.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/module.h>
|
|
#include <linux/qpnp/qpnp-revid.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_device.h>
|
|
#include <linux/of_irq.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/pmic-voter.h>
|
|
#include <linux/power_supply.h>
|
|
#include <linux/regmap.h>
|
|
#include <linux/iio/consumer.h>
|
|
#if defined(CONFIG_BATTERY_SAMSUNG_USING_QC)
|
|
#include "../../../battery_qc/include/sec_battery_qc.h"
|
|
#endif
|
|
|
|
#define MISC_CSIR_LSB_REG 0x9F1
|
|
#define MISC_CSIR_MSB_REG 0x9F2
|
|
#define CORE_STATUS1_REG 0x1006
|
|
#define WIN_OV_BIT BIT(0)
|
|
#define WIN_UV_BIT BIT(1)
|
|
#define EN_PIN_OUT_BIT BIT(2)
|
|
#define LCM_AUTO_BIT BIT(3)
|
|
#define LCM_PIN_BIT BIT(4)
|
|
#define ILIM_BIT BIT(5)
|
|
#define TEMP_ALARM_BIT BIT(6)
|
|
#define VPH_OV_SOFT_BIT BIT(7)
|
|
|
|
#define CORE_STATUS2_REG 0x1007
|
|
#define SWITCHER_HOLD_OFF_BIT BIT(0)
|
|
#define VPH_OV_HARD_BIT BIT(1)
|
|
#define TSD_BIT BIT(2)
|
|
#define IREV_BIT BIT(3)
|
|
#define IOC_BIT BIT(4)
|
|
#define VIN_UV_BIT BIT(5)
|
|
#define VIN_OV_BIT BIT(6)
|
|
#define EN_PIN_OUT2_BIT BIT(7)
|
|
|
|
#define CORE_STATUS3_REG 0x1008
|
|
#define EN_SL_BIT BIT(0)
|
|
#define IIN_REF_SS_DONE_BIT BIT(1)
|
|
#define FLYCAP_SS_DONE_BIT BIT(2)
|
|
#define SL_DETECTED_BIT BIT(3)
|
|
|
|
#define CORE_INT_RT_STS_REG 0x1010
|
|
#define SWITCHER_OFF_WINDOW_STS_BIT BIT(0)
|
|
#define SWITCHER_OFF_FAULT_STS_BIT BIT(1)
|
|
#define TSD_STS_BIT BIT(2)
|
|
#define IREV_STS_BIT BIT(3)
|
|
#define VPH_OV_HARD_STS_BIT BIT(4)
|
|
#define VPH_OV_SOFT_STS_BIT BIT(5)
|
|
#define ILIM_STS_BIT BIT(6)
|
|
#define TEMP_ALARM_STS_BIT BIT(7)
|
|
|
|
#define CORE_CONTROL1_REG 0x1020
|
|
#define CMD_EN_SWITCHER_BIT BIT(0)
|
|
#define CMD_EN_SL_BIT BIT(1)
|
|
|
|
#define CORE_FTRIM_ILIM_REG 0x1030
|
|
#define CFG_ILIM_MASK GENMASK(4, 0)
|
|
|
|
#define CORE_FTRIM_CTRL_REG 0x1031
|
|
#define TEMP_ALERT_LVL_MASK GENMASK(6, 5)
|
|
#define TEMP_ALERT_LVL_SHIFT 5
|
|
#define TEMP_BUFFER_OUTPUT_BIT BIT(7)
|
|
#define CORE_FTRIM_CTRL 0x1031
|
|
#define CFG_TEMP_ALERT_LVL GENMASK(6, 5)
|
|
#define CFG_TEMP_ALERT_100C BIT(6)
|
|
|
|
#define CORE_FTRIM_LVL_REG 0x1033
|
|
#define CFG_WIN_HI_MASK GENMASK(3, 2)
|
|
#define WIN_OV_LVL_1000MV 0x08
|
|
|
|
#define CORE_FTRIM_MISC_REG 0x1034
|
|
#define TR_WIN_1P5X_BIT BIT(0)
|
|
#define TR_IREV_BIT BIT(1)
|
|
#define WINDOW_DETECTION_DELTA_X1P0 0
|
|
#define WINDOW_DETECTION_DELTA_X1P5 1
|
|
|
|
#define CORE_FTRIM_DIS_REG 0x1035
|
|
#define TR_DIS_ILIM_DET_BIT BIT(4)
|
|
#if defined(CONFIG_BATTERY_SAMSUNG_USING_QC)
|
|
#define TR_DIS_ILIM_DET_SHIFT 4
|
|
#endif
|
|
|
|
#define CORE_ATEST1_SEL_REG 0x10E2
|
|
#define ATEST1_OUTPUT_ENABLE_BIT BIT(7)
|
|
#define ATEST1_SEL_MASK GENMASK(6, 0)
|
|
#define ISNS_INT_VAL 0x09
|
|
|
|
#define CP_VOTER "CP_VOTER"
|
|
#define USER_VOTER "USER_VOTER"
|
|
#define ILIM_VOTER "ILIM_VOTER"
|
|
#define TAPER_END_VOTER "TAPER_END_VOTER"
|
|
#define FCC_VOTER "FCC_VOTER"
|
|
#define ICL_VOTER "ICL_VOTER"
|
|
#define WIRELESS_VOTER "WIRELESS_VOTER"
|
|
#define SRC_VOTER "SRC_VOTER"
|
|
#define SWITCHER_TOGGLE_VOTER "SWITCHER_TOGGLE_VOTER"
|
|
#define SOC_LEVEL_VOTER "SOC_LEVEL_VOTER"
|
|
#define HW_DISABLE_VOTER "HW_DISABLE_VOTER"
|
|
#define CC_MODE_VOTER "CC_MODE_VOTER"
|
|
#define MAIN_DISABLE_VOTER "MAIN_DISABLE_VOTER"
|
|
#define TAPER_MAIN_ICL_LIMIT_VOTER "TAPER_MAIN_ICL_LIMIT_VOTER"
|
|
#if defined(CONFIG_BATTERY_SAMSUNG_USING_QC)
|
|
#define INIT_VOTER "INIT_VOTER"
|
|
#endif
|
|
|
|
#define CP_MASTER 0
|
|
#define CP_SLAVE 1
|
|
#define THERMAL_SUSPEND_DECIDEGC 1400
|
|
#define MAX_ILIM_UA 3200000
|
|
#define MAX_ILIM_DUAL_CP_UA 6400000
|
|
#define CC_MODE_TAPER_DELTA_UA 200000
|
|
#define DEFAULT_TAPER_DELTA_UA 100000
|
|
#define CC_MODE_TAPER_MAIN_ICL_UA 500000
|
|
|
|
#define smb1390_dbg(chip, reason, fmt, ...) \
|
|
do { \
|
|
if (chip->debug_mask & (reason)) \
|
|
pr_info("SMB1390: %s: " fmt, __func__, \
|
|
##__VA_ARGS__); \
|
|
else \
|
|
pr_debug("SMB1390: %s: " fmt, __func__, \
|
|
##__VA_ARGS__); \
|
|
} while (0)
|
|
|
|
enum {
|
|
SWITCHER_OFF_WINDOW_IRQ = 0,
|
|
SWITCHER_OFF_FAULT_IRQ,
|
|
TSD_IRQ,
|
|
IREV_IRQ,
|
|
VPH_OV_HARD_IRQ,
|
|
VPH_OV_SOFT_IRQ,
|
|
ILIM_IRQ,
|
|
TEMP_ALARM_IRQ,
|
|
NUM_IRQS,
|
|
};
|
|
|
|
enum isns_mode {
|
|
ISNS_MODE_OFF = 0,
|
|
ISNS_MODE_ACTIVE,
|
|
ISNS_MODE_STANDBY,
|
|
};
|
|
|
|
enum {
|
|
SWITCHER_EN = 0,
|
|
SMB_PIN_EN,
|
|
};
|
|
|
|
enum print_reason {
|
|
PR_INTERRUPT = BIT(0),
|
|
PR_REGISTER = BIT(1),
|
|
PR_INFO = BIT(2),
|
|
PR_EXT_DEPENDENCY = BIT(3),
|
|
PR_MISC = BIT(4),
|
|
};
|
|
|
|
struct smb1390_iio {
|
|
struct iio_channel *die_temp_chan;
|
|
};
|
|
|
|
struct smb1390 {
|
|
struct device *dev;
|
|
struct regmap *regmap;
|
|
struct notifier_block nb;
|
|
struct wakeup_source *cp_ws;
|
|
struct dentry *dfs_root;
|
|
struct pmic_revid_data *pmic_rev_id;
|
|
|
|
/* work structs */
|
|
struct work_struct status_change_work;
|
|
struct work_struct taper_work;
|
|
#if defined(CONFIG_BATTERY_SAMSUNG_USING_QC)
|
|
struct delayed_work init_work;
|
|
#endif
|
|
|
|
/* mutexes */
|
|
spinlock_t status_change_lock;
|
|
struct mutex die_chan_lock;
|
|
|
|
/* votables */
|
|
struct votable *disable_votable;
|
|
struct votable *ilim_votable;
|
|
struct votable *fcc_votable;
|
|
struct votable *fv_votable;
|
|
struct votable *cp_awake_votable;
|
|
struct votable *slave_disable_votable;
|
|
struct votable *usb_icl_votable;
|
|
struct votable *fcc_main_votable;
|
|
|
|
/* power supplies */
|
|
struct power_supply *cps_psy;
|
|
struct power_supply *usb_psy;
|
|
struct power_supply *batt_psy;
|
|
struct power_supply *dc_psy;
|
|
struct power_supply *cp_master_psy;
|
|
|
|
int irqs[NUM_IRQS];
|
|
bool status_change_running;
|
|
bool taper_work_running;
|
|
bool smb_init_done;
|
|
struct smb1390_iio iio;
|
|
int irq_status;
|
|
int taper_entry_fv;
|
|
bool switcher_enabled;
|
|
int cp_status1;
|
|
int cp_status2;
|
|
int cp_enable;
|
|
int cp_isns_master;
|
|
int cp_isns_slave;
|
|
int cp_ilim;
|
|
int die_temp;
|
|
int min_ilim_ua;
|
|
#if defined(CONFIG_BATTERY_SAMSUNG_USING_QC)
|
|
int die_temp_adc;
|
|
#endif
|
|
bool suspended;
|
|
bool disabled;
|
|
u32 debug_mask;
|
|
u32 max_temp_alarm_degc;
|
|
u32 max_cutoff_soc;
|
|
u32 pl_output_mode;
|
|
u32 pl_input_mode;
|
|
u32 cp_role;
|
|
enum isns_mode current_capability;
|
|
bool batt_soc_validated;
|
|
int cp_slave_thr_taper_ua;
|
|
int cc_mode_taper_main_icl_ua;
|
|
};
|
|
|
|
struct smb_cfg {
|
|
u16 address;
|
|
u8 mask;
|
|
u8 val;
|
|
};
|
|
|
|
/* SMB1390 rev2/3 for dual charge */
|
|
static const struct smb_cfg smb1390_dual[] = {
|
|
{0x1031, 0xff, 0x7A},
|
|
{0x1032, 0xff, 0x07},
|
|
{0x1035, 0xff, 0x63},
|
|
{0x1036, 0xff, 0x80},
|
|
{0x103A, 0xff, 0x44},
|
|
};
|
|
|
|
/* SMB1390 rev3, CSIR2500, for triple charge */
|
|
static const struct smb_cfg smb1390_csir2500_triple[] = {
|
|
{0x1030, 0x80, 0x80},
|
|
{0x1031, 0xff, 0x72},
|
|
{0x1032, 0xff, 0x03},
|
|
{0x1033, 0x04, 0x04},
|
|
{0x1034, 0x80, 0x00},
|
|
{0x1035, 0xff, 0xE3},
|
|
{0x1036, 0xff, 0xA0},
|
|
{0x1037, 0xff, 0x80},
|
|
{0x1039, 0xff, 0x30},
|
|
{0x103A, 0xff, 0x40},
|
|
{0x103B, 0xff, 0x20},
|
|
{0x103E, 0xff, 0x00},
|
|
};
|
|
|
|
/* SMB1390 rev3, CSIR 2515 or 2519 for triple charge */
|
|
static const struct smb_cfg smb1390_triple[] = {
|
|
{0x1031, 0xff, 0x72},
|
|
{0x1032, 0xff, 0x03},
|
|
{0x1035, 0xff, 0xE3},
|
|
{0x1036, 0xff, 0xA0},
|
|
{0x103A, 0xff, 0x40},
|
|
{0x1037, 0x04, 0x00},
|
|
};
|
|
|
|
struct smb_irq {
|
|
const char *name;
|
|
const irq_handler_t handler;
|
|
const bool wake;
|
|
};
|
|
|
|
static const struct smb_irq smb_irqs[];
|
|
|
|
static int smb1390_read(struct smb1390 *chip, int reg, int *val)
|
|
{
|
|
int rc;
|
|
|
|
rc = regmap_read(chip->regmap, reg, val);
|
|
if (rc < 0)
|
|
pr_err("Couldn't read 0x%04x\n", reg);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int smb1390_masked_write(struct smb1390 *chip, int reg, int mask,
|
|
int val)
|
|
{
|
|
int rc;
|
|
|
|
smb1390_dbg(chip, PR_REGISTER, "Writing 0x%02x to 0x%04x with mask 0x%02x\n",
|
|
val, reg, mask);
|
|
rc = regmap_update_bits(chip->regmap, reg, mask, val);
|
|
if (rc < 0)
|
|
pr_err("Couldn't write 0x%02x to 0x%04x with mask 0x%02x\n",
|
|
val, reg, mask);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static bool is_psy_voter_available(struct smb1390 *chip)
|
|
{
|
|
if (!chip->batt_psy) {
|
|
chip->batt_psy = power_supply_get_by_name("battery");
|
|
if (!chip->batt_psy) {
|
|
smb1390_dbg(chip, PR_EXT_DEPENDENCY, "Couldn't find battery psy\n");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!chip->usb_psy) {
|
|
chip->usb_psy = power_supply_get_by_name("usb");
|
|
if (!chip->usb_psy) {
|
|
smb1390_dbg(chip, PR_EXT_DEPENDENCY, "Couldn't find usb psy\n");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!chip->dc_psy) {
|
|
chip->dc_psy = power_supply_get_by_name("dc");
|
|
if (!chip->dc_psy) {
|
|
smb1390_dbg(chip, PR_EXT_DEPENDENCY, "Couldn't find dc psy\n");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!chip->fcc_votable) {
|
|
chip->fcc_votable = find_votable("FCC");
|
|
if (!chip->fcc_votable) {
|
|
smb1390_dbg(chip, PR_EXT_DEPENDENCY, "Couldn't find FCC votable\n");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!chip->fv_votable) {
|
|
chip->fv_votable = find_votable("FV");
|
|
if (!chip->fv_votable) {
|
|
smb1390_dbg(chip, PR_EXT_DEPENDENCY, "Couldn't find FV votable\n");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!chip->usb_icl_votable) {
|
|
chip->usb_icl_votable = find_votable("USB_ICL");
|
|
if (!chip->usb_icl_votable) {
|
|
smb1390_dbg(chip, PR_EXT_DEPENDENCY, "Couldn't find ICL votable\n");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!chip->disable_votable) {
|
|
smb1390_dbg(chip, PR_MISC, "Couldn't find CP DISABLE votable\n");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static int smb1390_isns_mode_control(struct smb1390 *chip, enum isns_mode mode)
|
|
{
|
|
int rc;
|
|
u8 val;
|
|
|
|
switch (mode) {
|
|
case ISNS_MODE_ACTIVE:
|
|
val = ATEST1_OUTPUT_ENABLE_BIT | ISNS_INT_VAL;
|
|
break;
|
|
case ISNS_MODE_STANDBY:
|
|
val = ATEST1_OUTPUT_ENABLE_BIT;
|
|
break;
|
|
case ISNS_MODE_OFF:
|
|
default:
|
|
val = 0;
|
|
break;
|
|
}
|
|
|
|
rc = smb1390_masked_write(chip, CORE_ATEST1_SEL_REG,
|
|
ATEST1_OUTPUT_ENABLE_BIT | ATEST1_SEL_MASK,
|
|
val);
|
|
if (rc < 0)
|
|
pr_err("Couldn't set CORE_ATEST1_SEL_REG, rc = %d\n", rc);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static bool smb1390_is_adapter_cc_mode(struct smb1390 *chip)
|
|
{
|
|
int rc;
|
|
union power_supply_propval pval = {0, };
|
|
|
|
if (!chip->usb_psy) {
|
|
chip->usb_psy = power_supply_get_by_name("usb");
|
|
if (!chip->usb_psy)
|
|
return false;
|
|
}
|
|
|
|
rc = power_supply_get_property(chip->usb_psy,
|
|
POWER_SUPPLY_PROP_ADAPTER_CC_MODE,
|
|
&pval);
|
|
if (rc < 0) {
|
|
pr_err("Couldn't get PPS CC mode status rc=%d\n", rc);
|
|
return false;
|
|
}
|
|
|
|
return pval.intval;
|
|
}
|
|
|
|
static bool is_cps_available(struct smb1390 *chip)
|
|
{
|
|
if (!chip->cps_psy)
|
|
chip->cps_psy = power_supply_get_by_name("cp_slave");
|
|
else
|
|
return true;
|
|
|
|
if (!chip->cps_psy)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
static void cp_toggle_switcher(struct smb1390 *chip)
|
|
{
|
|
int rc;
|
|
|
|
/*
|
|
* Disable ILIM detection before toggling the switcher
|
|
* to prevent any ILIM interrupt storm while toggling
|
|
* the switcher.
|
|
*/
|
|
rc = regmap_update_bits(chip->regmap, CORE_FTRIM_DIS_REG,
|
|
TR_DIS_ILIM_DET_BIT, TR_DIS_ILIM_DET_BIT);
|
|
if (rc < 0)
|
|
pr_err("Couldn't disable ILIM rc=%d\n", rc);
|
|
|
|
vote(chip->disable_votable, SWITCHER_TOGGLE_VOTER, true, 0);
|
|
|
|
/* Delay for toggling switcher */
|
|
usleep_range(20, 30);
|
|
|
|
vote(chip->disable_votable, SWITCHER_TOGGLE_VOTER, false, 0);
|
|
|
|
rc = regmap_update_bits(chip->regmap, CORE_FTRIM_DIS_REG,
|
|
TR_DIS_ILIM_DET_BIT, 0);
|
|
if (rc < 0)
|
|
pr_err("Couldn't enable ILIM rc=%d\n", rc);
|
|
}
|
|
|
|
static int smb1390_get_cp_en_status(struct smb1390 *chip, int id, bool *enable)
|
|
{
|
|
int rc = 0, status;
|
|
|
|
rc = smb1390_read(chip, CORE_STATUS2_REG, &status);
|
|
if (rc < 0) {
|
|
pr_err("Couldn't read CP_STATUS_2 register, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
switch (id) {
|
|
case SWITCHER_EN:
|
|
*enable = !!(status & EN_PIN_OUT2_BIT) &&
|
|
!(status & SWITCHER_HOLD_OFF_BIT);
|
|
break;
|
|
case SMB_PIN_EN:
|
|
*enable = !!(status & EN_PIN_OUT2_BIT);
|
|
break;
|
|
default:
|
|
smb1390_dbg(chip, PR_MISC, "cp_en status %d is not supported",
|
|
id);
|
|
rc = -EINVAL;
|
|
break;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int smb1390_set_ilim(struct smb1390 *chip, int ilim_ua)
|
|
{
|
|
int rc;
|
|
|
|
rc = smb1390_masked_write(chip, CORE_FTRIM_ILIM_REG,
|
|
CFG_ILIM_MASK, ilim_ua);
|
|
if (rc < 0)
|
|
pr_err("Failed to write ILIM Register, rc=%d\n", rc);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static irqreturn_t default_irq_handler(int irq, void *data)
|
|
{
|
|
struct smb1390 *chip = data;
|
|
int i, rc;
|
|
bool enable;
|
|
|
|
for (i = 0; i < NUM_IRQS; ++i) {
|
|
if (irq == chip->irqs[i]) {
|
|
smb1390_dbg(chip, PR_INTERRUPT, "%s IRQ triggered\n",
|
|
smb_irqs[i].name);
|
|
chip->irq_status |= 1 << i;
|
|
}
|
|
}
|
|
|
|
rc = smb1390_get_cp_en_status(chip, SWITCHER_EN, &enable);
|
|
if (!rc) {
|
|
if (chip->switcher_enabled != enable) {
|
|
chip->switcher_enabled = enable;
|
|
if (chip->fcc_votable)
|
|
rerun_election(chip->fcc_votable);
|
|
}
|
|
}
|
|
|
|
if (chip->cp_master_psy)
|
|
power_supply_changed(chip->cp_master_psy);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static const struct smb_irq smb_irqs[] = {
|
|
[SWITCHER_OFF_WINDOW_IRQ] = {
|
|
.name = "switcher-off-window",
|
|
.handler = default_irq_handler,
|
|
.wake = true,
|
|
},
|
|
[SWITCHER_OFF_FAULT_IRQ] = {
|
|
.name = "switcher-off-fault",
|
|
.handler = default_irq_handler,
|
|
.wake = true,
|
|
},
|
|
[TSD_IRQ] = {
|
|
.name = "tsd-fault",
|
|
.handler = default_irq_handler,
|
|
.wake = true,
|
|
},
|
|
[IREV_IRQ] = {
|
|
.name = "irev-fault",
|
|
.handler = default_irq_handler,
|
|
.wake = true,
|
|
},
|
|
[VPH_OV_HARD_IRQ] = {
|
|
.name = "vph-ov-hard",
|
|
.handler = default_irq_handler,
|
|
.wake = true,
|
|
},
|
|
[VPH_OV_SOFT_IRQ] = {
|
|
.name = "vph-ov-soft",
|
|
.handler = default_irq_handler,
|
|
.wake = true,
|
|
},
|
|
[ILIM_IRQ] = {
|
|
.name = "ilim",
|
|
.handler = default_irq_handler,
|
|
.wake = true,
|
|
},
|
|
[TEMP_ALARM_IRQ] = {
|
|
.name = "temp-alarm",
|
|
.handler = default_irq_handler,
|
|
.wake = true,
|
|
},
|
|
};
|
|
|
|
static int smb1390_get_die_temp(struct smb1390 *chip,
|
|
union power_supply_propval *val)
|
|
{
|
|
int die_temp_deciC = 0;
|
|
int rc = 0;
|
|
bool enable;
|
|
|
|
/*
|
|
* If SMB1390 chip is not enabled, adc channel read may render
|
|
* erroneous value. Return error to signify, adc read is not admissible
|
|
*/
|
|
rc = smb1390_get_cp_en_status(chip, SMB_PIN_EN, &enable);
|
|
if (rc < 0) {
|
|
pr_err("Couldn't get SMB_PIN enable status, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
if (!enable)
|
|
return -ENODATA;
|
|
|
|
mutex_lock(&chip->die_chan_lock);
|
|
rc = iio_read_channel_processed(chip->iio.die_temp_chan,
|
|
&die_temp_deciC);
|
|
#if defined(CONFIG_BATTERY_SAMSUNG_USING_QC)
|
|
rc = iio_read_channel_raw(chip->iio.die_temp_chan,
|
|
&chip->die_temp_adc);
|
|
#endif
|
|
mutex_unlock(&chip->die_chan_lock);
|
|
|
|
rc = smb1390_get_cp_en_status(chip, SMB_PIN_EN, &enable);
|
|
if (rc < 0) {
|
|
pr_err("Couldn't get SMB_PIN enable status, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
//Check Enable Status again to debounce Die Temp read */
|
|
if (!enable)
|
|
return -ENODATA;
|
|
|
|
if (rc < 0)
|
|
pr_err("Couldn't read die chan, rc = %d\n", rc);
|
|
else
|
|
val->intval = die_temp_deciC / 100;
|
|
|
|
if(val->intval > 3000)
|
|
{
|
|
pr_err("SMB off, read >300degC: %d\n", val->intval);
|
|
return -ENODATA;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int smb1390_get_isns(int temp)
|
|
{
|
|
/* ISNS = 2 * (1496 - 1390_therm_input * 0.00356) * 1000 uA */
|
|
return ((1496 * 1000 - div_s64((s64)temp * 3560, 1000)) * 2);
|
|
}
|
|
|
|
static int smb1390_get_isns_master(struct smb1390 *chip,
|
|
union power_supply_propval *val)
|
|
{
|
|
int temp = 0;
|
|
int rc;
|
|
bool enable;
|
|
|
|
/*
|
|
* If SMB1390 chip is not enabled, adc channel read may render
|
|
* erroneous value. Return error to signify, adc read is not admissible
|
|
*/
|
|
rc = smb1390_get_cp_en_status(chip, SMB_PIN_EN, &enable);
|
|
if (rc < 0) {
|
|
pr_err("Couldn't get SMB_PIN enable status, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
if (!enable)
|
|
return -ENODATA;
|
|
|
|
/*
|
|
* Since master and slave share temp_pin line
|
|
* which is re-used to measure isns, configure the
|
|
* master as follows:
|
|
* 1. Put slave in standby mode
|
|
* 2. Configure master to provide current reading
|
|
* 3. Read current value
|
|
* 4. Configure master back to report temperature
|
|
*/
|
|
mutex_lock(&chip->die_chan_lock);
|
|
if (is_cps_available(chip)) {
|
|
val->intval = ISNS_MODE_STANDBY;
|
|
rc = power_supply_set_property(chip->cps_psy,
|
|
POWER_SUPPLY_PROP_CURRENT_CAPABILITY, val);
|
|
if (rc < 0) {
|
|
pr_err("Couldn't change slave charging state rc=%d\n",
|
|
rc);
|
|
goto unlock;
|
|
}
|
|
}
|
|
|
|
rc = smb1390_isns_mode_control(chip, ISNS_MODE_ACTIVE);
|
|
if (rc < 0) {
|
|
pr_err("Failed to set master in Active mode, rc=%d\n", rc);
|
|
goto unlock;
|
|
}
|
|
|
|
rc = iio_read_channel_processed(chip->iio.die_temp_chan,
|
|
&temp);
|
|
if (rc < 0) {
|
|
pr_err("Couldn't read die_temp chan for isns, rc = %d\n", rc);
|
|
goto unlock;
|
|
}
|
|
|
|
rc = smb1390_isns_mode_control(chip, ISNS_MODE_OFF);
|
|
if (rc < 0)
|
|
pr_err("Couldn't set master to off mode, rc = %d\n", rc);
|
|
|
|
if (is_cps_available(chip)) {
|
|
val->intval = ISNS_MODE_OFF;
|
|
rc = power_supply_set_property(chip->cps_psy,
|
|
POWER_SUPPLY_PROP_CURRENT_CAPABILITY, val);
|
|
if (rc < 0)
|
|
pr_err("Couldn't change slave charging state rc=%d\n",
|
|
rc);
|
|
}
|
|
|
|
unlock:
|
|
mutex_unlock(&chip->die_chan_lock);
|
|
|
|
if (rc >= 0)
|
|
val->intval = smb1390_get_isns(temp);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int smb1390_get_isns_slave(struct smb1390 *chip,
|
|
union power_supply_propval *val)
|
|
{
|
|
int temp = 0;
|
|
int rc;
|
|
bool enable;
|
|
|
|
if (!is_cps_available(chip)) {
|
|
val->intval = 0;
|
|
return 0;
|
|
}
|
|
/*
|
|
* If SMB1390 chip is not enabled, adc channel read may render
|
|
* erroneous value. Return error to signify, adc read is not admissible
|
|
*/
|
|
rc = smb1390_get_cp_en_status(chip, SMB_PIN_EN, &enable);
|
|
if (rc < 0) {
|
|
pr_err("Couldn't get SMB_PIN enable status, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
if (!enable)
|
|
return -ENODATA;
|
|
|
|
/*
|
|
* Since master and slave share temp_pin line
|
|
* which is re-used to measure isns, configure the
|
|
* slave as follows:
|
|
* 1. Put slave in standby mode
|
|
* 2. Configure slave to in Active mode to provide current reading
|
|
* 3. Read current value
|
|
*/
|
|
mutex_lock(&chip->die_chan_lock);
|
|
rc = smb1390_isns_mode_control(chip, ISNS_MODE_STANDBY);
|
|
if (rc < 0)
|
|
goto unlock;
|
|
|
|
val->intval = ISNS_MODE_ACTIVE;
|
|
rc = power_supply_set_property(chip->cps_psy,
|
|
POWER_SUPPLY_PROP_CURRENT_CAPABILITY, val);
|
|
if (rc < 0) {
|
|
pr_err("Couldn't change slave charging state rc=%d\n",
|
|
rc);
|
|
goto unlock;
|
|
}
|
|
|
|
rc = iio_read_channel_processed(chip->iio.die_temp_chan,
|
|
&temp);
|
|
if (rc < 0) {
|
|
pr_err("Couldn't read die chan for isns, rc = %d\n", rc);
|
|
goto unlock;
|
|
}
|
|
|
|
val->intval = ISNS_MODE_OFF;
|
|
rc = power_supply_set_property(chip->cps_psy,
|
|
POWER_SUPPLY_PROP_CURRENT_CAPABILITY, val);
|
|
if (rc < 0)
|
|
pr_err("Couldn't change slave charging state rc=%d\n",
|
|
rc);
|
|
|
|
rc = smb1390_isns_mode_control(chip, ISNS_MODE_OFF);
|
|
if (rc < 0)
|
|
pr_err("Couldn't set CORE_ATEST1_SEL_REG, rc = %d\n", rc);
|
|
|
|
|
|
unlock:
|
|
mutex_unlock(&chip->die_chan_lock);
|
|
|
|
if (rc >= 0)
|
|
val->intval = smb1390_get_isns(temp);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int smb1390_get_cp_ilim(struct smb1390 *chip,
|
|
union power_supply_propval *val)
|
|
{
|
|
int rc = 0, status;
|
|
|
|
if (is_cps_available(chip)) {
|
|
if (!chip->ilim_votable) {
|
|
chip->ilim_votable = find_votable("CP_ILIM");
|
|
if (!chip->ilim_votable)
|
|
return -EINVAL;
|
|
}
|
|
|
|
val->intval = get_effective_result(chip->ilim_votable);
|
|
} else {
|
|
rc = smb1390_read(chip, CORE_FTRIM_ILIM_REG, &status);
|
|
if (!rc)
|
|
val->intval =
|
|
((status & CFG_ILIM_MASK) * 100000)
|
|
+ 500000;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int smb1390_is_batt_soc_valid(struct smb1390 *chip)
|
|
{
|
|
int rc;
|
|
union power_supply_propval pval = {0, };
|
|
|
|
if (!chip->batt_psy)
|
|
goto out;
|
|
|
|
rc = power_supply_get_property(chip->batt_psy,
|
|
POWER_SUPPLY_PROP_CAPACITY, &pval);
|
|
if (rc < 0) {
|
|
pr_err("Couldn't get CAPACITY rc=%d\n", rc);
|
|
goto out;
|
|
}
|
|
|
|
if (pval.intval >= chip->max_cutoff_soc)
|
|
return false;
|
|
|
|
out:
|
|
return true;
|
|
}
|
|
|
|
static int smb1390_triple_init_hw(struct smb1390 *chip)
|
|
{
|
|
int i, rc = 0;
|
|
int csir_lsb = 0, csir_msb = 0;
|
|
u16 csir = 0;
|
|
|
|
smb1390_read(chip, MISC_CSIR_LSB_REG, &csir_lsb);
|
|
smb1390_read(chip, MISC_CSIR_MSB_REG, &csir_msb);
|
|
csir = ((csir_msb << 8) | csir_lsb);
|
|
smb1390_dbg(chip, PR_INFO, "CSIR register = 0x%04x\n", csir);
|
|
|
|
if (csir == 0x2500) {
|
|
for (i = 0; i < ARRAY_SIZE(smb1390_csir2500_triple); i++) {
|
|
rc = smb1390_masked_write(chip,
|
|
smb1390_csir2500_triple[i].address,
|
|
smb1390_csir2500_triple[i].mask,
|
|
smb1390_csir2500_triple[i].val);
|
|
if (rc < 0) {
|
|
pr_err("Failed to configure SMB1390 for triple chg config for address 0x%04x rc=%d\n",
|
|
smb1390_csir2500_triple[i].address, rc);
|
|
return rc;
|
|
}
|
|
}
|
|
} else {
|
|
for (i = 0; i < ARRAY_SIZE(smb1390_triple); i++) {
|
|
rc = smb1390_masked_write(chip,
|
|
smb1390_triple[i].address,
|
|
smb1390_triple[i].mask,
|
|
smb1390_triple[i].val);
|
|
if (rc < 0) {
|
|
pr_err("Failed to configure SMB1390 for triple chg config for address 0x%04x rc=%d\n",
|
|
smb1390_triple[i].address, rc);
|
|
return rc;
|
|
}
|
|
}
|
|
}
|
|
|
|
smb1390_dbg(chip, PR_INFO, "Configured SMB1390 charge pump for triple chg config\n");
|
|
chip->smb_init_done = true;
|
|
return rc;
|
|
}
|
|
|
|
static int smb1390_dual_init_hw(struct smb1390 *chip)
|
|
{
|
|
int rc = 0, i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(smb1390_dual); i++) {
|
|
rc = smb1390_masked_write(chip,
|
|
smb1390_dual[i].address,
|
|
smb1390_dual[i].mask,
|
|
smb1390_dual[i].val);
|
|
if (rc < 0) {
|
|
pr_err("Failed to configure SMB1390 for dual chg config for address 0x%04x rc=%d\n",
|
|
smb1390_dual[i].address, rc);
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
smb1390_dbg(chip, PR_INFO, "Configured SMB1390 charge pump for Dual chg config\n");
|
|
return rc;
|
|
}
|
|
|
|
/* voter callbacks */
|
|
static int smb1390_disable_vote_cb(struct votable *votable, void *data,
|
|
int disable, const char *client)
|
|
{
|
|
struct smb1390 *chip = data;
|
|
int rc = 0;
|
|
|
|
if (!is_psy_voter_available(chip) || chip->suspended)
|
|
return -EAGAIN;
|
|
|
|
if (is_cps_available(chip))
|
|
vote(chip->slave_disable_votable, MAIN_DISABLE_VOTER,
|
|
disable ? true : false, 0);
|
|
|
|
rc = smb1390_masked_write(chip, CORE_CONTROL1_REG, CMD_EN_SWITCHER_BIT,
|
|
disable ? 0 : CMD_EN_SWITCHER_BIT);
|
|
if (rc < 0) {
|
|
pr_err("Couldn't write CORE_CONTROL1_REG, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
smb1390_dbg(chip, PR_INFO, "client: %s, master: %s\n",
|
|
client, (disable ? "disabled" : "enabled"));
|
|
|
|
/* charging may have been disabled by ILIM; send uevent */
|
|
if (chip->cp_master_psy && (disable != chip->disabled))
|
|
power_supply_changed(chip->cp_master_psy);
|
|
|
|
chip->disabled = disable;
|
|
return rc;
|
|
}
|
|
|
|
static int smb1390_slave_disable_vote_cb(struct votable *votable, void *data,
|
|
int disable, const char *client)
|
|
{
|
|
struct smb1390 *chip = data;
|
|
int rc = 0, ilim_ua = 0;
|
|
|
|
rc = smb1390_masked_write(chip, CORE_CONTROL1_REG, CMD_EN_SL_BIT,
|
|
disable ? 0 : CMD_EN_SL_BIT);
|
|
if (rc < 0) {
|
|
pr_err("Couldn't %s slave rc=%d\n",
|
|
disable ? "disable" : "enable", rc);
|
|
return rc;
|
|
}
|
|
|
|
smb1390_dbg(chip, PR_INFO, "client: %s, slave: %s\n",
|
|
client, (disable ? "disabled" : "enabled"));
|
|
|
|
/* Re-distribute ILIM to Master CP when Slave is disabled */
|
|
if (disable && (chip->ilim_votable)) {
|
|
ilim_ua = get_effective_result_locked(chip->ilim_votable);
|
|
if (ilim_ua > MAX_ILIM_UA)
|
|
ilim_ua = MAX_ILIM_UA;
|
|
|
|
if (ilim_ua < 500000) {
|
|
smb1390_dbg(chip, PR_INFO, "ILIM too low, not re-distributing, ilim=%duA\n",
|
|
ilim_ua);
|
|
return 0;
|
|
}
|
|
|
|
rc = smb1390_set_ilim(chip,
|
|
DIV_ROUND_CLOSEST(ilim_ua - 500000, 100000));
|
|
if (rc < 0) {
|
|
pr_err("Failed to set ILIM, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
smb1390_dbg(chip, PR_INFO, "Master ILIM set to %duA\n",
|
|
ilim_ua);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int smb1390_ilim_vote_cb(struct votable *votable, void *data,
|
|
int ilim_uA, const char *client)
|
|
{
|
|
struct smb1390 *chip = data;
|
|
#if !defined(CONFIG_BATTERY_SAMSUNG_USING_QC)
|
|
union power_supply_propval pval = {0, };
|
|
#endif
|
|
int rc = 0;
|
|
bool slave_enabled = false;
|
|
|
|
if (!is_psy_voter_available(chip) || chip->suspended)
|
|
return -EAGAIN;
|
|
|
|
/* ILIM should always have at least one active vote */
|
|
if (!client) {
|
|
pr_err("Client missing\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
#if defined(CONFIG_BATTERY_SAMSUNG_USING_QC)
|
|
rc = smb1390_masked_write(chip, CORE_FTRIM_ILIM_REG,
|
|
CFG_ILIM_MASK,
|
|
min(DIV_ROUND_CLOSEST(3200000 - 500000, 100000),
|
|
DIV_ROUND_CLOSEST(max(ilim_uA, 500000) - 500000, 100000)));
|
|
|
|
if (rc < 0) {
|
|
pr_err("Failed to write ILIM Register, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
#else
|
|
ilim_uA = min(ilim_uA, (is_cps_available(chip) ?
|
|
MAX_ILIM_DUAL_CP_UA : MAX_ILIM_UA));
|
|
#endif
|
|
/* ILIM less than min_ilim_ua, disable charging */
|
|
if (ilim_uA < chip->min_ilim_ua) {
|
|
smb1390_dbg(chip, PR_INFO, "ILIM %duA is too low to allow charging\n",
|
|
ilim_uA);
|
|
vote(chip->disable_votable, ILIM_VOTER, true, 0);
|
|
} else {
|
|
/* Disable Slave CP if ILIM is < 2 * min ILIM */
|
|
#if !defined(CONFIG_BATTERY_SAMSUNG_USING_QC)
|
|
if (is_cps_available(chip)) {
|
|
vote(chip->slave_disable_votable, ILIM_VOTER,
|
|
(ilim_uA < (2 * chip->min_ilim_ua)), 0);
|
|
|
|
if (get_effective_result(chip->slave_disable_votable)
|
|
== 0)
|
|
slave_enabled = true;
|
|
}
|
|
|
|
if (slave_enabled) {
|
|
ilim_uA /= 2;
|
|
pval.intval = DIV_ROUND_CLOSEST(ilim_uA - 500000,
|
|
100000);
|
|
rc = power_supply_set_property(chip->cps_psy,
|
|
POWER_SUPPLY_PROP_INPUT_CURRENT_MAX,
|
|
&pval);
|
|
if (rc < 0)
|
|
pr_err("Couldn't change slave ilim rc=%d\n",
|
|
rc);
|
|
}
|
|
|
|
rc = smb1390_set_ilim(chip,
|
|
DIV_ROUND_CLOSEST(ilim_uA - 500000, 100000));
|
|
if (rc < 0) {
|
|
pr_err("Failed to set ILIM, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
#endif
|
|
smb1390_dbg(chip, PR_INFO, "ILIM set to %duA slave_enabled = %d\n",
|
|
ilim_uA, slave_enabled);
|
|
vote(chip->disable_votable, ILIM_VOTER, false, 0);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int smb1390_awake_vote_cb(struct votable *votable, void *data,
|
|
int awake, const char *client)
|
|
{
|
|
struct smb1390 *chip = data;
|
|
|
|
if (awake)
|
|
__pm_stay_awake(chip->cp_ws);
|
|
else
|
|
__pm_relax(chip->cp_ws);
|
|
|
|
smb1390_dbg(chip, PR_INFO, "client: %s awake: %d\n", client, awake);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int smb1390_notifier_cb(struct notifier_block *nb,
|
|
unsigned long event, void *data)
|
|
{
|
|
struct smb1390 *chip = container_of(nb, struct smb1390, nb);
|
|
struct power_supply *psy = data;
|
|
int rc;
|
|
unsigned long flags;
|
|
|
|
if (event != PSY_EVENT_PROP_CHANGED)
|
|
return NOTIFY_OK;
|
|
|
|
if (strcmp(psy->desc->name, "battery") == 0
|
|
|| strcmp(psy->desc->name, "usb") == 0
|
|
|| strcmp(psy->desc->name, "main") == 0
|
|
|| strcmp(psy->desc->name, "cp_slave") == 0) {
|
|
spin_lock_irqsave(&chip->status_change_lock, flags);
|
|
|
|
if (!chip->status_change_running) {
|
|
chip->status_change_running = true;
|
|
pm_stay_awake(chip->dev);
|
|
schedule_work(&chip->status_change_work);
|
|
}
|
|
spin_unlock_irqrestore(&chip->status_change_lock, flags);
|
|
|
|
/*
|
|
* If not already configured for triple chg, configure master
|
|
* SMB1390 here for triple chg, if slave is detected.
|
|
*/
|
|
if (is_cps_available(chip) && !chip->smb_init_done) {
|
|
smb1390_dbg(chip, PR_INFO, "SMB1390 slave has registered, configure for triple charging\n");
|
|
rc = smb1390_triple_init_hw(chip);
|
|
if (rc < 0)
|
|
pr_err("Couldn't configure SMB1390 for triple-chg config rc=%d\n",
|
|
rc);
|
|
}
|
|
}
|
|
|
|
return NOTIFY_OK;
|
|
}
|
|
|
|
#define ILIM_NR 10
|
|
#define ILIM_DR 8
|
|
#define ILIM_FACTOR(ilim) ((ilim * ILIM_NR) / ILIM_DR)
|
|
|
|
#if !defined(CONFIG_BATTERY_SAMSUNG_USING_QC)
|
|
static void smb1390_configure_ilim(struct smb1390 *chip, int mode)
|
|
{
|
|
int rc;
|
|
union power_supply_propval pval = {0, };
|
|
|
|
/* PPS adapter reply on the current advertised by the adapter */
|
|
if ((chip->pl_output_mode == POWER_SUPPLY_PL_OUTPUT_VPH)
|
|
&& (mode == POWER_SUPPLY_CP_PPS)) {
|
|
rc = power_supply_get_property(chip->usb_psy,
|
|
POWER_SUPPLY_PROP_PD_CURRENT_MAX, &pval);
|
|
if (rc < 0)
|
|
pr_err("Couldn't get PD CURRENT MAX rc=%d\n", rc);
|
|
else
|
|
vote(chip->ilim_votable, ICL_VOTER,
|
|
true, ILIM_FACTOR(pval.intval));
|
|
}
|
|
|
|
/* QC3.0/Wireless adapter rely on the settled AICL for USBMID_USBMID */
|
|
if ((chip->pl_input_mode == POWER_SUPPLY_PL_USBMID_USBMID)
|
|
&& (mode == POWER_SUPPLY_CP_HVDCP3)) {
|
|
if (!chip->fcc_main_votable)
|
|
chip->fcc_main_votable = find_votable("FCC_MAIN");
|
|
|
|
rc = power_supply_get_property(chip->usb_psy,
|
|
POWER_SUPPLY_PROP_INPUT_CURRENT_SETTLED, &pval);
|
|
if (rc < 0) {
|
|
pr_err("Couldn't get usb aicl rc=%d\n", rc);
|
|
} else {
|
|
vote(chip->ilim_votable, ICL_VOTER, true, pval.intval);
|
|
/*
|
|
* Rerun FCC votable to ensure offset for ILIM
|
|
* compensation is recalculated based on new ILIM.
|
|
*/
|
|
if (chip->fcc_main_votable)
|
|
rerun_election(chip->fcc_main_votable);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
|
|
static void smb1390_status_change_work(struct work_struct *work)
|
|
{
|
|
struct smb1390 *chip = container_of(work, struct smb1390,
|
|
status_change_work);
|
|
#if defined(CONFIG_BATTERY_SAMSUNG_USING_QC)
|
|
union power_supply_propval pval_cap = {0, };
|
|
#endif
|
|
union power_supply_propval pval = {0, };
|
|
int rc;
|
|
|
|
if (!is_psy_voter_available(chip))
|
|
goto out;
|
|
|
|
/*
|
|
* If batt soc is not valid upon bootup, but becomes
|
|
* valid due to the battery discharging later, remove
|
|
* vote from SOC_LEVEL_VOTER.
|
|
*/
|
|
if (smb1390_is_batt_soc_valid(chip))
|
|
vote(chip->disable_votable, SOC_LEVEL_VOTER, false, 0);
|
|
#if defined(CONFIG_BATTERY_SAMSUNG_USING_QC)
|
|
if(get_effective_result(chip->fcc_votable) < (chip->min_ilim_ua * 2))
|
|
vote(chip->disable_votable, FCC_VOTER, true, 0);
|
|
else
|
|
vote(chip->disable_votable, FCC_VOTER, false, 0);
|
|
|
|
rc = power_supply_get_property(chip->batt_psy,
|
|
POWER_SUPPLY_PROP_CAPACITY, &pval_cap);
|
|
if (rc < 0) {
|
|
pr_err("Couldn't get CAPACITY rc=%d\n", rc);
|
|
goto out;
|
|
}
|
|
#endif
|
|
|
|
rc = power_supply_get_property(chip->usb_psy,
|
|
POWER_SUPPLY_PROP_SMB_EN_MODE, &pval);
|
|
if (rc < 0) {
|
|
pr_err("Couldn't get usb present rc=%d\n", rc);
|
|
goto out;
|
|
}
|
|
|
|
if (pval.intval == POWER_SUPPLY_CHARGER_SEC_CP) {
|
|
rc = power_supply_get_property(chip->usb_psy,
|
|
POWER_SUPPLY_PROP_SMB_EN_REASON, &pval);
|
|
if (rc < 0) {
|
|
pr_err("Couldn't get cp reason rc=%d\n", rc);
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* Slave SMB1390 is not required for the power-rating of QC3
|
|
*/
|
|
if (pval.intval != POWER_SUPPLY_CP_HVDCP3)
|
|
vote(chip->slave_disable_votable, SRC_VOTER, false, 0);
|
|
|
|
/* Check for SOC threshold only once before enabling CP */
|
|
vote(chip->disable_votable, SRC_VOTER, false, 0);
|
|
if (!chip->batt_soc_validated) {
|
|
vote(chip->disable_votable, SOC_LEVEL_VOTER,
|
|
smb1390_is_batt_soc_valid(chip) ?
|
|
false : true, 0);
|
|
chip->batt_soc_validated = true;
|
|
}
|
|
#if defined(CONFIG_SEC_A90Q_PROJECT)
|
|
vote(chip->fcc_votable, SOC_LEVEL_VOTER,
|
|
(pval_cap.intval >= chip->max_cutoff_soc) ? true : false, 2550000);
|
|
#endif
|
|
|
|
if (pval.intval == POWER_SUPPLY_CP_WIRELESS) {
|
|
vote(chip->ilim_votable, ICL_VOTER, false, 0);
|
|
rc = power_supply_get_property(chip->dc_psy,
|
|
POWER_SUPPLY_PROP_CURRENT_MAX, &pval);
|
|
if (rc < 0)
|
|
pr_err("Couldn't get dc icl rc=%d\n", rc);
|
|
else
|
|
vote(chip->ilim_votable, WIRELESS_VOTER, true,
|
|
pval.intval);
|
|
} else {
|
|
vote(chip->ilim_votable, WIRELESS_VOTER, false, 0);
|
|
#if defined(CONFIG_BATTERY_SAMSUNG_USING_QC)
|
|
if (pval.intval == POWER_SUPPLY_CP_PPS){
|
|
rc = power_supply_get_property(chip->usb_psy,
|
|
POWER_SUPPLY_PROP_PD_CURRENT_MAX, &pval);
|
|
pval.intval = pval.intval * 10 / 8;
|
|
}
|
|
else {
|
|
rc = power_supply_get_property(chip->usb_psy,
|
|
POWER_SUPPLY_PROP_INPUT_CURRENT_SETTLED, &pval);
|
|
if (rc < 0)
|
|
pr_err("Couldn't get usb icl rc=%d\n", rc);
|
|
else
|
|
vote(chip->ilim_votable, ICL_VOTER, true,
|
|
pval.intval);
|
|
}
|
|
#else
|
|
smb1390_configure_ilim(chip, pval.intval);
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* Remove SMB1390 Taper condition disable vote if float voltage
|
|
* increased in comparison to voltage at which it entered taper.
|
|
*/
|
|
if (chip->taper_entry_fv <
|
|
get_effective_result(chip->fv_votable))
|
|
vote(chip->disable_votable, TAPER_END_VOTER, false, 0);
|
|
|
|
/*
|
|
* all votes that would result in disabling the charge pump have
|
|
* been cast; ensure the charhe pump is still enabled before
|
|
* continuing.
|
|
*/
|
|
if (get_effective_result(chip->disable_votable))
|
|
goto out;
|
|
|
|
rc = power_supply_get_property(chip->batt_psy,
|
|
POWER_SUPPLY_PROP_CHARGE_TYPE, &pval);
|
|
if (rc < 0) {
|
|
pr_err("Couldn't get charge type rc=%d\n", rc);
|
|
} else if (pval.intval ==
|
|
POWER_SUPPLY_CHARGE_TYPE_TAPER) {
|
|
/*
|
|
* mutual exclusion is already guaranteed by
|
|
* chip->status_change_running
|
|
*/
|
|
if (!chip->taper_work_running) {
|
|
chip->taper_work_running = true;
|
|
queue_work(system_long_wq,
|
|
&chip->taper_work);
|
|
}
|
|
}
|
|
} else {
|
|
chip->batt_soc_validated = false;
|
|
vote(chip->slave_disable_votable, SRC_VOTER, true, 0);
|
|
vote(chip->disable_votable, SRC_VOTER, true, 0);
|
|
vote(chip->disable_votable, TAPER_END_VOTER, false, 0);
|
|
vote(chip->fcc_votable, CP_VOTER, false, 0);
|
|
vote(chip->disable_votable, SOC_LEVEL_VOTER, true, 0);
|
|
vote_override(chip->ilim_votable, CC_MODE_VOTER, false, 0);
|
|
vote(chip->slave_disable_votable, TAPER_END_VOTER, false, 0);
|
|
vote(chip->slave_disable_votable, MAIN_DISABLE_VOTER, true, 0);
|
|
vote_override(chip->usb_icl_votable, TAPER_MAIN_ICL_LIMIT_VOTER,
|
|
false, 0);
|
|
}
|
|
|
|
out:
|
|
pm_relax(chip->dev);
|
|
chip->status_change_running = false;
|
|
}
|
|
|
|
static int smb1390_validate_slave_chg_taper(struct smb1390 *chip, int fcc_uA)
|
|
{
|
|
int rc = 0;
|
|
|
|
/*
|
|
* In Collapse mode, while in Taper, Disable the slave SMB1390
|
|
* when FCC drops below a specified threshold.
|
|
*/
|
|
if (fcc_uA < (chip->cp_slave_thr_taper_ua) && is_cps_available(chip)) {
|
|
vote(chip->slave_disable_votable, TAPER_END_VOTER,
|
|
true, 0);
|
|
/*
|
|
* Set ILIM of master SMB1390 to Max value = 3.2A once slave is
|
|
* disabled to prevent ILIM irq storm.
|
|
*/
|
|
smb1390_dbg(chip, PR_INFO, "Set Master ILIM to MAX, post Slave disable in taper, fcc=%d\n",
|
|
fcc_uA);
|
|
vote_override(chip->ilim_votable, CC_MODE_VOTER,
|
|
smb1390_is_adapter_cc_mode(chip),
|
|
MAX_ILIM_DUAL_CP_UA);
|
|
|
|
if (chip->usb_icl_votable)
|
|
vote_override(chip->usb_icl_votable,
|
|
TAPER_MAIN_ICL_LIMIT_VOTER,
|
|
smb1390_is_adapter_cc_mode(chip),
|
|
chip->cc_mode_taper_main_icl_ua);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
#if defined(CONFIG_BATTERY_SAMSUNG_USING_QC)
|
|
static void smb1390_init_work(struct work_struct *work)
|
|
{
|
|
struct smb1390 *chip = container_of(work, struct smb1390, init_work.work);
|
|
|
|
smb1390_dbg(chip, PR_INFO, "init work exit\n");
|
|
vote(chip->disable_votable, INIT_VOTER, false, 0);
|
|
}
|
|
#endif
|
|
static void smb1390_taper_work(struct work_struct *work)
|
|
{
|
|
struct smb1390 *chip = container_of(work, struct smb1390, taper_work);
|
|
union power_supply_propval pval = {0, };
|
|
int rc, fcc_uA, delta_fcc_uA, main_fcc_ua = 0, fcc_cp_ua;
|
|
|
|
if (!is_psy_voter_available(chip))
|
|
goto out;
|
|
|
|
if (!chip->fcc_main_votable)
|
|
chip->fcc_main_votable = find_votable("FCC_MAIN");
|
|
|
|
if (chip->fcc_main_votable)
|
|
main_fcc_ua = get_effective_result(chip->fcc_main_votable);
|
|
|
|
if (main_fcc_ua < 0)
|
|
main_fcc_ua = 0;
|
|
|
|
chip->taper_entry_fv = get_effective_result(chip->fv_votable);
|
|
while (true) {
|
|
rc = power_supply_get_property(chip->batt_psy,
|
|
POWER_SUPPLY_PROP_CHARGE_TYPE, &pval);
|
|
if (rc < 0) {
|
|
pr_err("Couldn't get charge type rc=%d\n", rc);
|
|
goto out;
|
|
}
|
|
|
|
if (get_effective_result(chip->fv_votable) >
|
|
chip->taper_entry_fv) {
|
|
smb1390_dbg(chip, PR_INFO, "Float voltage increased. Exiting taper\n");
|
|
goto out;
|
|
} else {
|
|
chip->taper_entry_fv =
|
|
get_effective_result(chip->fv_votable);
|
|
}
|
|
|
|
if (pval.intval == POWER_SUPPLY_CHARGE_TYPE_TAPER) {
|
|
delta_fcc_uA =
|
|
(smb1390_is_adapter_cc_mode(chip) ?
|
|
CC_MODE_TAPER_DELTA_UA :
|
|
DEFAULT_TAPER_DELTA_UA);
|
|
fcc_uA = get_effective_result(chip->fcc_votable)
|
|
- delta_fcc_uA;
|
|
smb1390_dbg(chip, PR_INFO, "taper work reducing FCC to %duA\n",
|
|
fcc_uA);
|
|
vote(chip->fcc_votable, CP_VOTER, true, fcc_uA);
|
|
rc = smb1390_validate_slave_chg_taper(chip, (fcc_uA -
|
|
main_fcc_ua));
|
|
if (rc < 0) {
|
|
pr_err("Couldn't Disable slave in Taper, rc=%d\n",
|
|
rc);
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* fcc and fcc_main are the same for VPH config, hence
|
|
* reduce fcc_main from fcc only in VBAT (output config)
|
|
* where fcc_main is a portion of full-fcc.
|
|
*/
|
|
fcc_cp_ua = fcc_uA;
|
|
if (chip->pl_output_mode == POWER_SUPPLY_PL_OUTPUT_VBAT)
|
|
fcc_cp_ua = fcc_uA - main_fcc_ua;
|
|
|
|
smb1390_dbg(chip, PR_INFO,
|
|
"Taper: fcc_ua=%d fcc_cp_ua=%d fcc_main_ua=%d min_ilim_ua(x2) = %u\n",
|
|
fcc_uA, fcc_cp_ua, main_fcc_ua,
|
|
(chip->min_ilim_ua*2));
|
|
|
|
if (fcc_cp_ua < (chip->min_ilim_ua * 2)) {
|
|
vote(chip->disable_votable, TAPER_END_VOTER,
|
|
true, 0);
|
|
/*
|
|
* When master CP is disabled, reset all votes
|
|
* on ICL to enable Main charger to pump
|
|
* charging current.
|
|
*/
|
|
if (chip->usb_icl_votable)
|
|
vote_override(chip->usb_icl_votable,
|
|
TAPER_MAIN_ICL_LIMIT_VOTER,
|
|
false, 0);
|
|
goto out;
|
|
}
|
|
} else {
|
|
smb1390_dbg(chip, PR_INFO, "In fast charging. Wait for next taper\n");
|
|
}
|
|
|
|
msleep(500);
|
|
}
|
|
out:
|
|
smb1390_dbg(chip, PR_INFO, "taper work exit\n");
|
|
vote(chip->fcc_votable, CP_VOTER, false, 0);
|
|
chip->taper_work_running = false;
|
|
}
|
|
|
|
static enum power_supply_property smb1390_charge_pump_props[] = {
|
|
POWER_SUPPLY_PROP_CP_STATUS1,
|
|
POWER_SUPPLY_PROP_CP_STATUS2,
|
|
POWER_SUPPLY_PROP_CP_ENABLE,
|
|
POWER_SUPPLY_PROP_CP_SWITCHER_EN,
|
|
POWER_SUPPLY_PROP_CP_DIE_TEMP,
|
|
POWER_SUPPLY_PROP_CP_ISNS,
|
|
POWER_SUPPLY_PROP_CP_ISNS_SLAVE,
|
|
POWER_SUPPLY_PROP_CP_TOGGLE_SWITCHER,
|
|
POWER_SUPPLY_PROP_CP_IRQ_STATUS,
|
|
POWER_SUPPLY_PROP_CP_ILIM,
|
|
#if defined(CONFIG_BATTERY_SAMSUNG_USING_QC)
|
|
POWER_SUPPLY_PROP_CP_DISABLE_ILIM,
|
|
#endif
|
|
POWER_SUPPLY_PROP_CHIP_VERSION,
|
|
POWER_SUPPLY_PROP_PARALLEL_OUTPUT_MODE,
|
|
POWER_SUPPLY_PROP_MIN_ICL,
|
|
POWER_SUPPLY_PROP_MODEL_NAME,
|
|
POWER_SUPPLY_PROP_PARALLEL_MODE,
|
|
};
|
|
|
|
static int smb1390_get_prop_suspended(struct smb1390 *chip,
|
|
enum power_supply_property prop,
|
|
union power_supply_propval *val)
|
|
{
|
|
switch (prop) {
|
|
case POWER_SUPPLY_PROP_CP_STATUS1:
|
|
val->intval = chip->cp_status1;
|
|
break;
|
|
case POWER_SUPPLY_PROP_CP_STATUS2:
|
|
val->intval = chip->cp_status2;
|
|
break;
|
|
case POWER_SUPPLY_PROP_CP_ENABLE:
|
|
val->intval = chip->cp_enable;
|
|
break;
|
|
case POWER_SUPPLY_PROP_CP_SWITCHER_EN:
|
|
val->intval = chip->switcher_enabled;
|
|
break;
|
|
case POWER_SUPPLY_PROP_CP_DIE_TEMP:
|
|
val->intval = chip->die_temp;
|
|
break;
|
|
case POWER_SUPPLY_PROP_CP_ISNS:
|
|
val->intval = chip->cp_isns_master;
|
|
break;
|
|
case POWER_SUPPLY_PROP_CP_ISNS_SLAVE:
|
|
val->intval = chip->cp_isns_slave;
|
|
break;
|
|
case POWER_SUPPLY_PROP_CP_IRQ_STATUS:
|
|
val->intval = chip->irq_status;
|
|
break;
|
|
case POWER_SUPPLY_PROP_CP_ILIM:
|
|
val->intval = chip->cp_ilim;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int smb1390_get_prop(struct power_supply *psy,
|
|
enum power_supply_property prop,
|
|
union power_supply_propval *val)
|
|
{
|
|
struct smb1390 *chip = power_supply_get_drvdata(psy);
|
|
int rc = 0, status;
|
|
bool enable;
|
|
#if defined(CONFIG_BATTERY_SAMSUNG_USING_QC)
|
|
enum power_supply_ext_property ext_psp = (enum power_supply_ext_property)prop;
|
|
#endif
|
|
|
|
/*
|
|
* Return the cached values when the system is in suspend state
|
|
* instead of reading the registers to avoid read failures.
|
|
*/
|
|
if (chip->suspended) {
|
|
rc = smb1390_get_prop_suspended(chip, prop, val);
|
|
if (!rc)
|
|
return rc;
|
|
rc = 0;
|
|
}
|
|
|
|
switch (prop) {
|
|
case POWER_SUPPLY_PROP_CP_STATUS1:
|
|
rc = smb1390_read(chip, CORE_STATUS1_REG, &status);
|
|
if (!rc)
|
|
chip->cp_status1 = val->intval = status;
|
|
break;
|
|
case POWER_SUPPLY_PROP_CP_STATUS2:
|
|
rc = smb1390_read(chip, CORE_STATUS2_REG, &status);
|
|
if (!rc)
|
|
chip->cp_status2 = val->intval = status;
|
|
break;
|
|
case POWER_SUPPLY_PROP_CP_ENABLE:
|
|
rc = smb1390_get_cp_en_status(chip, SMB_PIN_EN,
|
|
&enable);
|
|
if (!rc)
|
|
chip->cp_enable = val->intval = enable &&
|
|
!get_effective_result(chip->disable_votable);
|
|
break;
|
|
case POWER_SUPPLY_PROP_CP_SWITCHER_EN:
|
|
rc = smb1390_get_cp_en_status(chip, SWITCHER_EN,
|
|
&enable);
|
|
if (!rc)
|
|
val->intval = enable;
|
|
break;
|
|
case POWER_SUPPLY_PROP_CP_DIE_TEMP:
|
|
/*
|
|
* Add a filter to the die temp value read:
|
|
* If temp > THERMAL_SUSPEND_DECIDEGC then
|
|
* - treat it as an error and report last valid
|
|
* cached temperature.
|
|
* - return -ENODATA if the cached value is
|
|
* invalid.
|
|
*/
|
|
|
|
rc = smb1390_get_die_temp(chip, val);
|
|
if (rc >= 0) {
|
|
if (val->intval <= THERMAL_SUSPEND_DECIDEGC)
|
|
chip->die_temp = val->intval;
|
|
else if (chip->die_temp == -ENODATA)
|
|
rc = -ENODATA;
|
|
else
|
|
val->intval = chip->die_temp;
|
|
}
|
|
break;
|
|
case POWER_SUPPLY_PROP_CP_ISNS:
|
|
rc = smb1390_get_isns_master(chip, val);
|
|
if (!rc)
|
|
chip->cp_isns_master = val->intval;
|
|
break;
|
|
case POWER_SUPPLY_PROP_CP_ISNS_SLAVE:
|
|
rc = smb1390_get_isns_slave(chip, val);
|
|
if (!rc)
|
|
chip->cp_isns_slave = val->intval;
|
|
break;
|
|
case POWER_SUPPLY_PROP_CP_TOGGLE_SWITCHER:
|
|
val->intval = 0;
|
|
break;
|
|
case POWER_SUPPLY_PROP_CP_IRQ_STATUS:
|
|
/*
|
|
* irq_status variable stores provious IRQs that have been
|
|
* handled by kernel, but not addressed by user space daemon.
|
|
*/
|
|
val->intval = chip->irq_status;
|
|
rc = smb1390_read(chip, CORE_INT_RT_STS_REG, &status);
|
|
if (!rc)
|
|
val->intval |= status;
|
|
break;
|
|
case POWER_SUPPLY_PROP_CP_ILIM:
|
|
rc = smb1390_get_cp_ilim(chip, val);
|
|
if (!rc)
|
|
chip->cp_ilim = val->intval;
|
|
break;
|
|
#if defined(CONFIG_BATTERY_SAMSUNG_USING_QC)
|
|
case POWER_SUPPLY_PROP_CP_DISABLE_ILIM:
|
|
rc = smb1390_read(chip, CORE_FTRIM_DIS_REG, &status);
|
|
if (!rc)
|
|
val->intval = ((status & TR_DIS_ILIM_DET_BIT) >> TR_DIS_ILIM_DET_SHIFT);
|
|
break;
|
|
#endif
|
|
case POWER_SUPPLY_PROP_CHIP_VERSION:
|
|
val->intval = chip->pmic_rev_id->rev4;
|
|
break;
|
|
case POWER_SUPPLY_PROP_PARALLEL_OUTPUT_MODE:
|
|
val->intval = chip->pl_output_mode;
|
|
break;
|
|
case POWER_SUPPLY_PROP_MIN_ICL:
|
|
val->intval = chip->min_ilim_ua;
|
|
break;
|
|
case POWER_SUPPLY_PROP_MODEL_NAME:
|
|
val->strval = (chip->pmic_rev_id->rev4 > 2) ? "SMB1390_V3" :
|
|
"SMB1390_V2";
|
|
break;
|
|
#if defined(CONFIG_BATTERY_SAMSUNG_USING_QC)
|
|
case POWER_SUPPLY_PROP_MAX ... POWER_SUPPLY_EXT_PROP_MAX:
|
|
switch (ext_psp) {
|
|
case POWER_SUPPLY_EXT_PROP_CP_DIE_TEMP_ADC:
|
|
val->intval = chip->die_temp_adc;
|
|
break;
|
|
default:
|
|
rc = -EINVAL;
|
|
}
|
|
break;
|
|
#endif
|
|
case POWER_SUPPLY_PROP_PARALLEL_MODE:
|
|
val->intval = chip->pl_input_mode;
|
|
break;
|
|
default:
|
|
smb1390_dbg(chip, PR_MISC, "charge pump power supply get prop %d not supported\n",
|
|
prop);
|
|
rc = -EINVAL;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int smb1390_set_prop(struct power_supply *psy,
|
|
enum power_supply_property prop,
|
|
const union power_supply_propval *val)
|
|
{
|
|
struct smb1390 *chip = power_supply_get_drvdata(psy);
|
|
int rc = 0;
|
|
|
|
switch (prop) {
|
|
case POWER_SUPPLY_PROP_CP_ENABLE:
|
|
vote(chip->disable_votable, USER_VOTER, !val->intval, 0);
|
|
break;
|
|
case POWER_SUPPLY_PROP_CP_TOGGLE_SWITCHER:
|
|
if (val->intval)
|
|
cp_toggle_switcher(chip);
|
|
break;
|
|
case POWER_SUPPLY_PROP_CP_IRQ_STATUS:
|
|
chip->irq_status = val->intval;
|
|
break;
|
|
case POWER_SUPPLY_PROP_CP_ILIM:
|
|
if (chip->ilim_votable)
|
|
vote_override(chip->ilim_votable, CC_MODE_VOTER,
|
|
(val->intval > 0), val->intval);
|
|
#if defined(CONFIG_BATTERY_SAMSUNG_USING_QC)
|
|
case POWER_SUPPLY_PROP_CP_DISABLE_ILIM:
|
|
rc = smb1390_masked_write(chip, CORE_FTRIM_DIS_REG,
|
|
TR_DIS_ILIM_DET_BIT, val->intval ? TR_DIS_ILIM_DET_BIT : 0);
|
|
break;
|
|
#endif
|
|
default:
|
|
smb1390_dbg(chip, PR_MISC, "charge pump power supply set prop %d not supported\n",
|
|
prop);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int smb1390_prop_is_writeable(struct power_supply *psy,
|
|
enum power_supply_property prop)
|
|
{
|
|
switch (prop) {
|
|
case POWER_SUPPLY_PROP_CP_ENABLE:
|
|
case POWER_SUPPLY_PROP_CP_TOGGLE_SWITCHER:
|
|
case POWER_SUPPLY_PROP_CP_IRQ_STATUS:
|
|
case POWER_SUPPLY_PROP_INPUT_CURRENT_MAX:
|
|
case POWER_SUPPLY_PROP_CURRENT_CAPABILITY:
|
|
case POWER_SUPPLY_PROP_CP_ILIM:
|
|
#if defined(CONFIG_BATTERY_SAMSUNG_USING_QC)
|
|
case POWER_SUPPLY_PROP_CP_DISABLE_ILIM:
|
|
#endif
|
|
return 1;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct power_supply_desc charge_pump_psy_desc = {
|
|
.name = "charge_pump_master",
|
|
.type = POWER_SUPPLY_TYPE_CHARGE_PUMP,
|
|
.properties = smb1390_charge_pump_props,
|
|
.num_properties = ARRAY_SIZE(smb1390_charge_pump_props),
|
|
.get_property = smb1390_get_prop,
|
|
.set_property = smb1390_set_prop,
|
|
.property_is_writeable = smb1390_prop_is_writeable,
|
|
};
|
|
|
|
static int smb1390_init_charge_pump_psy(struct smb1390 *chip)
|
|
{
|
|
struct power_supply_config charge_pump_cfg = {};
|
|
|
|
charge_pump_cfg.drv_data = chip;
|
|
charge_pump_cfg.of_node = chip->dev->of_node;
|
|
|
|
chip->cp_master_psy = devm_power_supply_register(chip->dev,
|
|
&charge_pump_psy_desc,
|
|
&charge_pump_cfg);
|
|
if (IS_ERR(chip->cp_master_psy)) {
|
|
pr_err("Couldn't register charge pump power supply\n");
|
|
return PTR_ERR(chip->cp_master_psy);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int smb1390_parse_dt(struct smb1390 *chip)
|
|
{
|
|
int rc;
|
|
|
|
rc = of_property_match_string(chip->dev->of_node, "io-channel-names",
|
|
"cp_die_temp");
|
|
if (rc >= 0) {
|
|
chip->iio.die_temp_chan =
|
|
iio_channel_get(chip->dev, "cp_die_temp");
|
|
if (IS_ERR(chip->iio.die_temp_chan)) {
|
|
rc = PTR_ERR(chip->iio.die_temp_chan);
|
|
if (rc != -EPROBE_DEFER)
|
|
dev_err(chip->dev,
|
|
"cp_die_temp channel unavailable %d\n",
|
|
rc);
|
|
chip->iio.die_temp_chan = NULL;
|
|
return rc;
|
|
}
|
|
} else {
|
|
return rc;
|
|
}
|
|
|
|
chip->min_ilim_ua = 1000000; /* 1A */
|
|
of_property_read_u32(chip->dev->of_node, "qcom,min-ilim-ua",
|
|
&chip->min_ilim_ua);
|
|
|
|
chip->max_temp_alarm_degc = 110;
|
|
of_property_read_u32(chip->dev->of_node, "qcom,max-temp-alarm-degc",
|
|
&chip->max_temp_alarm_degc);
|
|
|
|
chip->max_cutoff_soc = 85; /* 85% */
|
|
of_property_read_u32(chip->dev->of_node, "qcom,max-cutoff-soc",
|
|
&chip->max_cutoff_soc);
|
|
|
|
/* Default parallel output configuration is VPH connection */
|
|
chip->pl_output_mode = POWER_SUPPLY_PL_OUTPUT_VPH;
|
|
of_property_read_u32(chip->dev->of_node, "qcom,parallel-output-mode",
|
|
&chip->pl_output_mode);
|
|
|
|
/* Default parallel input configuration is USBMID connection */
|
|
chip->pl_input_mode = POWER_SUPPLY_PL_USBMID_USBMID;
|
|
of_property_read_u32(chip->dev->of_node, "qcom,parallel-input-mode",
|
|
&chip->pl_input_mode);
|
|
|
|
chip->cp_slave_thr_taper_ua = 3 * chip->min_ilim_ua;
|
|
of_property_read_u32(chip->dev->of_node, "qcom,cp-slave-thr-taper-ua",
|
|
&chip->cp_slave_thr_taper_ua);
|
|
|
|
chip->cc_mode_taper_main_icl_ua = CC_MODE_TAPER_MAIN_ICL_UA;
|
|
of_property_read_u32(chip->dev->of_node,
|
|
"qcom,cc-mode-taper-main-icl-ua",
|
|
&chip->cc_mode_taper_main_icl_ua);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void smb1390_release_channels(struct smb1390 *chip)
|
|
{
|
|
if (!IS_ERR_OR_NULL(chip->iio.die_temp_chan))
|
|
iio_channel_release(chip->iio.die_temp_chan);
|
|
}
|
|
|
|
static int smb1390_create_votables(struct smb1390 *chip)
|
|
{
|
|
#if defined(CONFIG_BATTERY_SAMSUNG_USING_QC)
|
|
int rc;
|
|
union power_supply_propval pval = {0, };
|
|
#endif
|
|
chip->cp_awake_votable = create_votable("CP_AWAKE",
|
|
VOTE_SET_ANY, smb1390_awake_vote_cb, chip);
|
|
|
|
chip->disable_votable = create_votable("CP_DISABLE",
|
|
VOTE_SET_ANY, smb1390_disable_vote_cb, chip);
|
|
if (IS_ERR(chip->disable_votable))
|
|
return PTR_ERR(chip->disable_votable);
|
|
|
|
chip->ilim_votable = create_votable("CP_ILIM",
|
|
VOTE_MIN, smb1390_ilim_vote_cb, chip);
|
|
if (IS_ERR(chip->ilim_votable))
|
|
return PTR_ERR(chip->ilim_votable);
|
|
|
|
chip->slave_disable_votable = create_votable("CP_SLAVE_DISABLE",
|
|
VOTE_SET_ANY, smb1390_slave_disable_vote_cb, chip);
|
|
if (IS_ERR(chip->slave_disable_votable))
|
|
return PTR_ERR(chip->slave_disable_votable);
|
|
|
|
/* Keep slave SMB disabled */
|
|
vote(chip->slave_disable_votable, SRC_VOTER, true, 0);
|
|
/*
|
|
* charge pump is initially disabled; this indirectly votes to allow
|
|
* traditional parallel charging if present
|
|
*/
|
|
vote(chip->disable_votable, USER_VOTER, true, 0);
|
|
/* keep charge pump disabled if SOC is above threshold */
|
|
vote(chip->disable_votable, SOC_LEVEL_VOTER,
|
|
smb1390_is_batt_soc_valid(chip) ? false : true, 0);
|
|
|
|
/*
|
|
* In case SMB1390 probe happens after FCC value has been configured,
|
|
* update ilim vote to reflect FCC / 2 value, this is only applicable
|
|
* when SMB1390 is directly connected to VBAT.
|
|
*/
|
|
#if defined(CONFIG_BATTERY_SAMSUNG_USING_QC)
|
|
if (is_psy_voter_available(chip)) {
|
|
rc = power_supply_get_property(chip->usb_psy,
|
|
POWER_SUPPLY_PROP_SMB_EN_MODE, &pval);
|
|
if (rc < 0) {
|
|
pr_err("Couldn't get smb en mode rc=%d\n", rc);
|
|
}
|
|
else if ((pval.intval == POWER_SUPPLY_CHARGER_SEC_CP) || (pval.intval == POWER_SUPPLY_CHARGER_SEC_CP_PL)) {
|
|
rc = power_supply_get_property(chip->usb_psy,
|
|
POWER_SUPPLY_PROP_SMB_EN_REASON, &pval);
|
|
if (rc < 0)
|
|
pr_err("Couldn't get cp reason rc=%d\n", rc);
|
|
else {
|
|
switch (pval.intval) {
|
|
case POWER_SUPPLY_CP_PPS:
|
|
vote(chip->ilim_votable, WIRELESS_VOTER, false, 0);
|
|
rc = power_supply_get_property(chip->usb_psy,
|
|
POWER_SUPPLY_PROP_PD_CURRENT_MAX, &pval);
|
|
if(rc < 0)
|
|
pr_err("Couldn't get pd icl rc=%d\n", rc);
|
|
else
|
|
vote(chip->ilim_votable, ICL_VOTER, true,
|
|
(pval.intval * 10 / 8));
|
|
break;
|
|
case POWER_SUPPLY_CP_HVDCP3:
|
|
vote(chip->ilim_votable, WIRELESS_VOTER, false, 0);
|
|
rc = power_supply_get_property(chip->usb_psy,
|
|
POWER_SUPPLY_PROP_INPUT_CURRENT_SETTLED, &pval);
|
|
if(rc < 0)
|
|
pr_err("Couldn't get settled aicl rc=%d\n", rc);
|
|
else
|
|
vote(chip->ilim_votable, ICL_VOTER, true,
|
|
pval.intval);
|
|
break;
|
|
case POWER_SUPPLY_CP_WIRELESS:
|
|
vote(chip->ilim_votable, ICL_VOTER, false, 0);
|
|
rc = power_supply_get_property(chip->dc_psy,
|
|
POWER_SUPPLY_PROP_CURRENT_MAX, &pval);
|
|
if (rc < 0)
|
|
pr_err("Couldn't get dc icl rc=%d\n", rc);
|
|
else
|
|
vote(chip->ilim_votable, WIRELESS_VOTER, true,
|
|
pval.intval);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#else
|
|
if ((chip->pl_output_mode != POWER_SUPPLY_PL_OUTPUT_VPH)
|
|
&& chip->fcc_votable)
|
|
vote(chip->ilim_votable, FCC_VOTER, true,
|
|
get_effective_result(chip->fcc_votable) / 2);
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void smb1390_destroy_votables(struct smb1390 *chip)
|
|
{
|
|
destroy_votable(chip->disable_votable);
|
|
destroy_votable(chip->ilim_votable);
|
|
}
|
|
|
|
#define SMB1390_FSW_700KHZ 0x06
|
|
static int smb1390_init_hw(struct smb1390 *chip)
|
|
{
|
|
int rc = 0, val;
|
|
|
|
/*
|
|
* Improve ILIM accuracy:
|
|
* - Configure window (Vin - 2Vout) OV level to 1000mV
|
|
* - Configure VOUT tracking value to 1.0
|
|
*/
|
|
rc = smb1390_masked_write(chip, CORE_FTRIM_LVL_REG,
|
|
CFG_WIN_HI_MASK, WIN_OV_LVL_1000MV);
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
rc = smb1390_masked_write(chip, CORE_FTRIM_MISC_REG,
|
|
TR_WIN_1P5X_BIT, WINDOW_DETECTION_DELTA_X1P0);
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
switch (chip->max_temp_alarm_degc) {
|
|
case 125:
|
|
val = 0x00;
|
|
break;
|
|
case 95:
|
|
val = 0x02;
|
|
break;
|
|
case 85:
|
|
val = 0x03;
|
|
break;
|
|
case 110:
|
|
default:
|
|
val = 0x01;
|
|
break;
|
|
}
|
|
rc = smb1390_masked_write(chip, CORE_FTRIM_CTRL_REG,
|
|
TEMP_ALERT_LVL_MASK, val << TEMP_ALERT_LVL_SHIFT);
|
|
if (rc < 0) {
|
|
pr_err("Failed to write CORE_FTRIM_CTRL_REG rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
/* Configure IREV threshold to 200mA */
|
|
rc = smb1390_masked_write(chip, CORE_FTRIM_MISC_REG, TR_IREV_BIT, 0);
|
|
if (rc < 0) {
|
|
pr_err("Couldn't configure IREV threshold rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
/*
|
|
* If the slave charger has registered, configure Master SMB1390 for
|
|
* triple-chg config, else configure for dual. Later, if the slave
|
|
* charger registers, re-configure for triple chg config from the
|
|
* power-supply notifier.
|
|
*/
|
|
if (!chip->smb_init_done) {
|
|
if (is_cps_available(chip))
|
|
rc = smb1390_triple_init_hw(chip);
|
|
else
|
|
rc = smb1390_dual_init_hw(chip);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int smb1390_get_irq_index_byname(const char *irq_name)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(smb_irqs); i++) {
|
|
if (strcmp(smb_irqs[i].name, irq_name) == 0)
|
|
return i;
|
|
}
|
|
|
|
return -ENOENT;
|
|
}
|
|
|
|
static int smb1390_request_interrupt(struct smb1390 *chip,
|
|
struct device_node *node,
|
|
const char *irq_name)
|
|
{
|
|
int rc = 0, irq, irq_index;
|
|
|
|
irq = of_irq_get_byname(node, irq_name);
|
|
if (irq < 0) {
|
|
pr_err("Couldn't get irq %s byname\n", irq_name);
|
|
return irq;
|
|
}
|
|
|
|
irq_index = smb1390_get_irq_index_byname(irq_name);
|
|
if (irq_index < 0) {
|
|
pr_err("%s is not a defined irq\n", irq_name);
|
|
return irq_index;
|
|
}
|
|
|
|
if (!smb_irqs[irq_index].handler)
|
|
return 0;
|
|
|
|
rc = devm_request_threaded_irq(chip->dev, irq, NULL,
|
|
smb_irqs[irq_index].handler,
|
|
IRQF_ONESHOT, irq_name, chip);
|
|
if (rc < 0) {
|
|
pr_err("Couldn't request irq %d rc=%d\n", irq, rc);
|
|
return rc;
|
|
}
|
|
|
|
chip->irqs[irq_index] = irq;
|
|
if (smb_irqs[irq_index].wake)
|
|
enable_irq_wake(irq);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int smb1390_request_interrupts(struct smb1390 *chip)
|
|
{
|
|
struct device_node *node = chip->dev->of_node;
|
|
struct device_node *child;
|
|
int rc = 0;
|
|
const char *name;
|
|
struct property *prop;
|
|
|
|
for_each_available_child_of_node(node, child) {
|
|
of_property_for_each_string(child, "interrupt-names",
|
|
prop, name) {
|
|
rc = smb1390_request_interrupt(chip, child, name);
|
|
if (rc < 0) {
|
|
pr_err("Couldn't request interrupt %s rc=%d\n",
|
|
name, rc);
|
|
return rc;
|
|
}
|
|
}
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
#ifdef CONFIG_DEBUG_FS
|
|
static void smb1390_create_debugfs(struct smb1390 *chip)
|
|
{
|
|
struct dentry *entry;
|
|
|
|
chip->dfs_root = debugfs_create_dir("smb1390_charger_psy", NULL);
|
|
if (IS_ERR_OR_NULL(chip->dfs_root)) {
|
|
pr_err("Failed to create debugfs directory, rc=%ld\n",
|
|
(long)chip->dfs_root);
|
|
return;
|
|
}
|
|
|
|
entry = debugfs_create_u32("debug_mask", 0600, chip->dfs_root,
|
|
&chip->debug_mask);
|
|
if (IS_ERR_OR_NULL(entry)) {
|
|
pr_err("Failed to create debug_mask, rc=%ld\n", (long)entry);
|
|
debugfs_remove_recursive(chip->dfs_root);
|
|
}
|
|
}
|
|
#else
|
|
static void smb1390_create_debugfs(struct smb1390 *chip)
|
|
{
|
|
}
|
|
#endif
|
|
|
|
static const struct of_device_id match_table[] = {
|
|
{ .compatible = "qcom,smb1390-charger-psy",
|
|
.data = (void *)CP_MASTER
|
|
},
|
|
{ .compatible = "qcom,smb1390-slave",
|
|
.data = (void *)CP_SLAVE
|
|
},
|
|
{ },
|
|
};
|
|
|
|
static int smb1390_master_probe(struct smb1390 *chip)
|
|
{
|
|
int rc;
|
|
struct device_node *revid_dev_node;
|
|
struct pmic_revid_data *pmic_rev_id;
|
|
|
|
revid_dev_node = of_parse_phandle(chip->dev->of_node,
|
|
"qcom,pmic-revid", 0);
|
|
if (!revid_dev_node) {
|
|
pr_err("Missing qcom,pmic-revid property\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
pmic_rev_id = get_revid_data(revid_dev_node);
|
|
of_node_put(revid_dev_node);
|
|
if (IS_ERR_OR_NULL(pmic_rev_id)) {
|
|
/*
|
|
* the revid peripheral must be registered, any failure
|
|
* here only indicates that the rev-id module has not
|
|
* probed yet.
|
|
*/
|
|
return -EPROBE_DEFER;
|
|
}
|
|
|
|
chip->pmic_rev_id = pmic_rev_id;
|
|
spin_lock_init(&chip->status_change_lock);
|
|
mutex_init(&chip->die_chan_lock);
|
|
chip->suspended = false;
|
|
|
|
rc = smb1390_parse_dt(chip);
|
|
if (rc < 0) {
|
|
pr_err("Couldn't parse device tree rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
chip->cp_ws = wakeup_source_register(NULL, "qcom-chargepump");
|
|
if (!chip->cp_ws)
|
|
return -ENOMEM;
|
|
|
|
INIT_WORK(&chip->status_change_work, smb1390_status_change_work);
|
|
INIT_WORK(&chip->taper_work, smb1390_taper_work);
|
|
|
|
rc = smb1390_init_hw(chip);
|
|
if (rc < 0) {
|
|
pr_err("Couldn't init hardware rc=%d\n", rc);
|
|
goto out_work;
|
|
}
|
|
|
|
rc = smb1390_create_votables(chip);
|
|
if (rc < 0) {
|
|
pr_err("Couldn't create votables rc=%d\n", rc);
|
|
goto out_work;
|
|
}
|
|
|
|
smb1390_dbg(chip, PR_INFO, "Detected revid=0x%02x\n",
|
|
chip->pmic_rev_id->rev4);
|
|
if (chip->pmic_rev_id->rev4 <= 0x02 && chip->pl_output_mode !=
|
|
POWER_SUPPLY_PL_OUTPUT_VPH) {
|
|
pr_err("Incompatible SMB1390 HW detected, Disabling the charge pump\n");
|
|
if (chip->disable_votable)
|
|
vote(chip->disable_votable, HW_DISABLE_VOTER,
|
|
true, 0);
|
|
}
|
|
|
|
rc = smb1390_init_charge_pump_psy(chip);
|
|
if (rc < 0) {
|
|
pr_err("Couldn't initialize charge pump psy rc=%d\n", rc);
|
|
goto out_votables;
|
|
}
|
|
|
|
chip->nb.notifier_call = smb1390_notifier_cb;
|
|
rc = power_supply_reg_notifier(&chip->nb);
|
|
if (rc < 0) {
|
|
pr_err("Couldn't register psy notifier rc=%d\n", rc);
|
|
goto out_votables;
|
|
}
|
|
|
|
rc = smb1390_request_interrupts(chip);
|
|
if (rc < 0) {
|
|
pr_err("Couldn't request interrupts rc=%d\n", rc);
|
|
goto out_notifier;
|
|
}
|
|
|
|
smb1390_create_debugfs(chip);
|
|
|
|
#if defined(CONFIG_BATTERY_SAMSUNG_USING_QC)
|
|
vote(chip->disable_votable, INIT_VOTER, true, 0);
|
|
INIT_DELAYED_WORK(&chip->init_work, smb1390_init_work);
|
|
schedule_delayed_work(&chip->init_work, msecs_to_jiffies(60000));
|
|
schedule_work(&chip->status_change_work);
|
|
#endif
|
|
|
|
return 0;
|
|
|
|
out_notifier:
|
|
power_supply_unreg_notifier(&chip->nb);
|
|
out_votables:
|
|
smb1390_destroy_votables(chip);
|
|
out_work:
|
|
cancel_work(&chip->taper_work);
|
|
cancel_work(&chip->status_change_work);
|
|
wakeup_source_unregister(chip->cp_ws);
|
|
return rc;
|
|
}
|
|
|
|
static enum power_supply_property smb1390_cp_slave_props[] = {
|
|
POWER_SUPPLY_PROP_INPUT_CURRENT_MAX,
|
|
POWER_SUPPLY_PROP_CURRENT_CAPABILITY,
|
|
};
|
|
|
|
static int smb1390_slave_prop_is_writeable(struct power_supply *psy,
|
|
enum power_supply_property prop)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int smb1390_cp_slave_get_prop(struct power_supply *psy,
|
|
enum power_supply_property psp,
|
|
union power_supply_propval *val)
|
|
{
|
|
struct smb1390 *chip = power_supply_get_drvdata(psy);
|
|
|
|
switch (psp) {
|
|
case POWER_SUPPLY_PROP_INPUT_CURRENT_MAX:
|
|
val->intval = 0;
|
|
if (!chip->ilim_votable)
|
|
chip->ilim_votable = find_votable("CP_ILIM");
|
|
if (chip->ilim_votable)
|
|
val->intval =
|
|
get_effective_result_locked(chip->ilim_votable);
|
|
break;
|
|
case POWER_SUPPLY_PROP_CURRENT_CAPABILITY:
|
|
val->intval = (int)chip->current_capability;
|
|
break;
|
|
default:
|
|
smb1390_dbg(chip, PR_MISC, "SMB 1390 slave power supply get prop %d not supported\n",
|
|
psp);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int smb1390_cp_slave_set_prop(struct power_supply *psy,
|
|
enum power_supply_property psp,
|
|
const union power_supply_propval *val)
|
|
{
|
|
struct smb1390 *chip = power_supply_get_drvdata(psy);
|
|
int rc = 0;
|
|
|
|
switch (psp) {
|
|
case POWER_SUPPLY_PROP_INPUT_CURRENT_MAX:
|
|
rc = smb1390_set_ilim(chip, val->intval);
|
|
break;
|
|
case POWER_SUPPLY_PROP_CURRENT_CAPABILITY:
|
|
chip->current_capability = (enum isns_mode)val->intval;
|
|
rc = smb1390_isns_mode_control(chip, val->intval);
|
|
break;
|
|
default:
|
|
smb1390_dbg(chip, PR_MISC, "SMB 1390 slave power supply set prop %d not supported\n",
|
|
psp);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
};
|
|
|
|
static const struct power_supply_desc cps_psy_desc = {
|
|
.name = "cp_slave",
|
|
.type = POWER_SUPPLY_TYPE_PARALLEL,
|
|
.properties = smb1390_cp_slave_props,
|
|
.num_properties = ARRAY_SIZE(smb1390_cp_slave_props),
|
|
.get_property = smb1390_cp_slave_get_prop,
|
|
.set_property = smb1390_cp_slave_set_prop,
|
|
.property_is_writeable = smb1390_slave_prop_is_writeable,
|
|
};
|
|
|
|
static int smb1390_init_cps_psy(struct smb1390 *chip)
|
|
{
|
|
struct power_supply_config cps_cfg = {};
|
|
|
|
cps_cfg.drv_data = chip;
|
|
cps_cfg.of_node = chip->dev->of_node;
|
|
chip->cps_psy = devm_power_supply_register(chip->dev,
|
|
&cps_psy_desc,
|
|
&cps_cfg);
|
|
if (IS_ERR(chip->cps_psy)) {
|
|
pr_err("Couldn't register CP slave power supply\n");
|
|
return PTR_ERR(chip->cps_psy);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int smb1390_slave_probe(struct smb1390 *chip)
|
|
{
|
|
int stat, rc;
|
|
|
|
/* a "hello" read to test the presence of the slave PMIC */
|
|
rc = smb1390_read(chip, CORE_STATUS1_REG, &stat);
|
|
if (rc < 0) {
|
|
pr_err("Couldn't find slave SMB1390\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
rc = smb1390_triple_init_hw(chip);
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
/* Configure Slave CP Temp buffer O/P to High Impedance */
|
|
rc = smb1390_masked_write(chip, CORE_FTRIM_CTRL_REG,
|
|
TEMP_BUFFER_OUTPUT_BIT,
|
|
TEMP_BUFFER_OUTPUT_BIT);
|
|
if (rc < 0) {
|
|
pr_err("Couldn't configure Slave temp Buffer rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
rc = smb1390_init_cps_psy(chip);
|
|
if (rc < 0)
|
|
pr_err("Couldn't initialize cps psy rc=%d\n", rc);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int smb1390_probe(struct platform_device *pdev)
|
|
{
|
|
struct smb1390 *chip;
|
|
int rc;
|
|
|
|
chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL);
|
|
if (!chip)
|
|
return -ENOMEM;
|
|
|
|
chip->dev = &pdev->dev;
|
|
chip->die_temp = -ENODATA;
|
|
chip->disabled = true;
|
|
|
|
chip->regmap = dev_get_regmap(chip->dev->parent, NULL);
|
|
if (!chip->regmap) {
|
|
pr_err("Couldn't get regmap\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
platform_set_drvdata(pdev, chip);
|
|
chip->cp_role = (int)of_device_get_match_data(chip->dev);
|
|
switch (chip->cp_role) {
|
|
case CP_MASTER:
|
|
rc = smb1390_master_probe(chip);
|
|
break;
|
|
case CP_SLAVE:
|
|
rc = smb1390_slave_probe(chip);
|
|
break;
|
|
default:
|
|
pr_err("Couldn't find a matching role %d\n", chip->cp_role);
|
|
rc = -EINVAL;
|
|
goto cleanup;
|
|
}
|
|
|
|
if (rc < 0) {
|
|
if (rc != -EPROBE_DEFER)
|
|
pr_err("Couldn't probe SMB1390 %s rc=%d\n",
|
|
chip->cp_role ? "Slave" : "Master", rc);
|
|
goto cleanup;
|
|
}
|
|
|
|
pr_info("smb1390 %s probed successfully\n", chip->cp_role ? "Slave" :
|
|
"Master");
|
|
return 0;
|
|
|
|
cleanup:
|
|
platform_set_drvdata(pdev, NULL);
|
|
return rc;
|
|
}
|
|
|
|
static int smb1390_remove(struct platform_device *pdev)
|
|
{
|
|
struct smb1390 *chip = platform_get_drvdata(pdev);
|
|
|
|
if (chip->cp_role != CP_MASTER) {
|
|
platform_set_drvdata(pdev, NULL);
|
|
return 0;
|
|
}
|
|
|
|
power_supply_unreg_notifier(&chip->nb);
|
|
|
|
/* explicitly disable charging */
|
|
vote(chip->disable_votable, USER_VOTER, true, 0);
|
|
vote(chip->disable_votable, SOC_LEVEL_VOTER, true, 0);
|
|
cancel_work(&chip->taper_work);
|
|
cancel_work(&chip->status_change_work);
|
|
wakeup_source_unregister(chip->cp_ws);
|
|
smb1390_destroy_votables(chip);
|
|
smb1390_release_channels(chip);
|
|
platform_set_drvdata(pdev, NULL);
|
|
return 0;
|
|
}
|
|
|
|
static void smb1390_shutdown(struct platform_device *pdev)
|
|
{
|
|
struct smb1390 *chip = platform_get_drvdata(pdev);
|
|
int rc;
|
|
|
|
power_supply_unreg_notifier(&chip->nb);
|
|
/* Disable SMB1390 */
|
|
smb1390_dbg(chip, PR_MISC, "Disabling SMB1390\n");
|
|
rc = smb1390_masked_write(chip, CORE_CONTROL1_REG,
|
|
CMD_EN_SWITCHER_BIT, 0);
|
|
if (rc < 0)
|
|
pr_err("Couldn't disable chip rc=%d\n", rc);
|
|
}
|
|
|
|
static int smb1390_suspend(struct device *dev)
|
|
{
|
|
struct smb1390 *chip = dev_get_drvdata(dev);
|
|
|
|
chip->suspended = true;
|
|
return 0;
|
|
}
|
|
|
|
static int smb1390_resume(struct device *dev)
|
|
{
|
|
struct smb1390 *chip = dev_get_drvdata(dev);
|
|
|
|
chip->suspended = false;
|
|
|
|
/* ILIM rerun is applicable for both master and slave */
|
|
if (!chip->ilim_votable)
|
|
chip->ilim_votable = find_votable("CP_ILIM");
|
|
|
|
if (chip->ilim_votable)
|
|
rerun_election(chip->ilim_votable);
|
|
|
|
/* Run disable votable for master only */
|
|
if (chip->cp_role == CP_MASTER)
|
|
rerun_election(chip->disable_votable);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct dev_pm_ops smb1390_pm_ops = {
|
|
.suspend = smb1390_suspend,
|
|
.resume = smb1390_resume,
|
|
};
|
|
|
|
static struct platform_driver smb1390_driver = {
|
|
.driver = {
|
|
.name = "qcom,smb1390-charger-psy",
|
|
.owner = THIS_MODULE,
|
|
.pm = &smb1390_pm_ops,
|
|
.of_match_table = match_table,
|
|
},
|
|
.probe = smb1390_probe,
|
|
.remove = smb1390_remove,
|
|
.shutdown = smb1390_shutdown,
|
|
};
|
|
module_platform_driver(smb1390_driver);
|
|
|
|
MODULE_DESCRIPTION("SMB1390 Charge Pump Driver");
|
|
MODULE_LICENSE("GPL v2");
|
|
|