diff --git a/drivers/power/supply/qcom/Kconfig b/drivers/power/supply/qcom/Kconfig index 585bf7222392..993b1b51823f 100644 --- a/drivers/power/supply/qcom/Kconfig +++ b/drivers/power/supply/qcom/Kconfig @@ -69,4 +69,16 @@ config SMB1355_SLAVE_CHARGER The driver reports the charger status via the power supply framework. A charger status change triggers an IRQ via the device STAT pin. +config SMB1390_CHARGE_PUMP_PSY + tristate "SMB1390 power supply framework based driver" + depends on MFD_I2C_PMIC + help + Say Y to include support for SMB1390 Charge Pump driver based on + power supply framework. + SMB1390 is a div2 charge pump capable of delivering 6A charge current + with very high efficiency. It can work either as 2s to 1s pre-regulator + or as a companion charger when paired with Qualcomm Technologies, Inc.’s + family of standalone chargers to enable a high current, low profile + Li+ battery charging system. + endif diff --git a/drivers/power/supply/qcom/Makefile b/drivers/power/supply/qcom/Makefile index 65c04bce8e59..6d9efdd61be7 100644 --- a/drivers/power/supply/qcom/Makefile +++ b/drivers/power/supply/qcom/Makefile @@ -9,3 +9,4 @@ qcom-smb1398-charger-y += smb1398-charger.o pmic-voter.o obj-$(CONFIG_QPNP_SMBLITE) += step-chg-jeita.o battery.o qpnp-smblite.o smblite-lib.o pmic-voter.o storm-watch.o schgm-falshlite.o obj-$(CONFIG_SMB1355_SLAVE_CHARGER) += qcom-smb1355-charger.o qcom-smb1355-charger-y += smb1355-charger.o pmic-voter.o +obj-$(CONFIG_SMB1390_CHARGE_PUMP_PSY) += smb1390-charger-psy.o pmic-voter.o diff --git a/drivers/power/supply/qcom/smb1390-charger-psy.c b/drivers/power/supply/qcom/smb1390-charger-psy.c new file mode 100644 index 000000000000..d260b14de572 --- /dev/null +++ b/drivers/power/supply/qcom/smb1390-charger-psy.c @@ -0,0 +1,2201 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2017-2020 The Linux Foundation. All rights reserved. + */ + +#define pr_fmt(fmt) "SMB1390: %s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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_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) + +#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" + +#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; + + /* 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; + bool suspended; + bool disabled; + u32 debug_mask; + u32 min_ilim_ua; + 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\n", + 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); + mutex_unlock(&chip->die_chan_lock); + + if (rc < 0) + pr_err("Couldn't read die chan, rc = %d\n", rc); + else + val->intval = die_temp_deciC / 100; + + 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; + union power_supply_propval pval = {0, }; + 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; + } + + ilim_uA = min(ilim_uA, (is_cps_available(chip) ? + MAX_ILIM_DUAL_CP_UA : MAX_ILIM_UA)); + /* 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 (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; + } + + 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) + +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); + } + } +} + +static void smb1390_status_change_work(struct work_struct *work) +{ + struct smb1390 *chip = container_of(work, struct smb1390, + status_change_work); + union power_supply_propval pval = {0, }; + int rc, dc_current_max = 0; + + 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); + + 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 (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 { + dc_current_max = pval.intval; + + rc = power_supply_get_property(chip->dc_psy, + POWER_SUPPLY_PROP_AICL_DONE, + &pval); + if (rc < 0) + pr_err("Couldn't get aicl done rc=%d\n", + rc); + else if (pval.intval) + vote(chip->ilim_votable, WIRELESS_VOTER, + true, dc_current_max); + } + } else { + vote(chip->ilim_votable, WIRELESS_VOTER, false, 0); + smb1390_configure_ilim(chip, pval.intval); + } + + /* + * 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->ilim_votable, WIRELESS_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) +{ + /* + * 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 CP 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 0; +} + +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; + + 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; + } + + if ((fcc_uA - main_fcc_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, + 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; + + /* + * 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; + 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; + 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); + break; + 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: + 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 = 105; + 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) +{ + 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 ((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); + + return 0; +} + +static void smb1390_destroy_votables(struct smb1390 *chip) +{ + destroy_votable(chip->disable_votable); + destroy_votable(chip->ilim_votable); +} + +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 115: + val = 0x00; + break; + case 90: + val = 0x02; + break; + case 80: + val = 0x03; + break; + case 105: + 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); + + 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); + return 0; + +out_notifier: + power_supply_unreg_notifier(&chip->nb); +out_votables: + smb1390_destroy_votables(chip); +out_work: + cancel_work_sync(&chip->taper_work); + cancel_work_sync(&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_sync(&chip->taper_work); + cancel_work_sync(&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", + .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");