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.
2662 lines
66 KiB
2662 lines
66 KiB
/* Copyright (c) 2019-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) "SMB1398: %s: " fmt, __func__
|
|
|
|
#include <linux/device.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/module.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/qpnp/qpnp-revid.h>
|
|
#include <linux/power_supply.h>
|
|
#include <linux/regmap.h>
|
|
#include <linux/iio/consumer.h>
|
|
|
|
/* Status register definition */
|
|
#define PERPH0_REVISION4 0x2603
|
|
|
|
#define INPUT_STATUS_REG 0x2609
|
|
#define INPUT_USB_IN BIT(1)
|
|
#define INPUT_WLS_IN BIT(0)
|
|
|
|
#define PERPH0_INT_RT_STS_REG 0x2610
|
|
#define USB_IN_OVLO_STS BIT(7)
|
|
#define WLS_IN_OVLO_STS BIT(6)
|
|
#define USB_IN_UVLO_STS BIT(5)
|
|
#define WLS_IN_UVLO_STS BIT(4)
|
|
#define DIV2_IREV_LATCH_STS BIT(3)
|
|
#define VOL_UV_LATCH_STS BIT(2)
|
|
#define TEMP_SHUTDOWN_STS BIT(1)
|
|
#define CFLY_HARD_FAULT_LATCH_STS BIT(0)
|
|
|
|
#define MODE_STATUS_REG 0x2641
|
|
#define SMB_EN BIT(7)
|
|
#define PRE_EN_DCDC BIT(6)
|
|
#define DIV2_EN_SLAVE BIT(5)
|
|
#define LCM_EN BIT(4)
|
|
#define DIV2_EN BIT(3)
|
|
#define BUCK_EN BIT(2)
|
|
#define CFLY_SS_DONE BIT(1)
|
|
#define DCDC_EN BIT(0)
|
|
|
|
#define SWITCHER_OFF_WIN_STATUS_REG 0x2642
|
|
#define DIV2_WIN_OV BIT(1)
|
|
#define DIV2_WIN_UV BIT(0)
|
|
|
|
#define SWITCHER_OFF_VIN_STATUS_REG 0x2643
|
|
#define USB_IN_OVLO BIT(3)
|
|
#define WLS_IN_OVLO BIT(2)
|
|
#define USB_IN_UVLO BIT(1)
|
|
#define WLS_IN_UVLO BIT(0)
|
|
|
|
#define SWITCHER_OFF_FAULT_REG 0x2644
|
|
#define VOUT_OV_3LVL_BUCK BIT(5)
|
|
#define VOUT_UV_LATCH BIT(4)
|
|
#define ITERM_3LVL_LATCH BIT(3)
|
|
#define DIV2_IREV_LATCH BIT(2)
|
|
#define TEMP_SHDWN BIT(1)
|
|
#define CFLY_HARD_FAULT_LATCH BIT(0)
|
|
|
|
#define BUCK_CC_CV_STATE_REG 0x2645
|
|
#define BUCK_IN_CC_REGULATION BIT(1)
|
|
#define BUCK_IN_CV_REGULATION BIT(0)
|
|
|
|
#define INPUT_CURRENT_REGULATION_REG 0x2646
|
|
#define BUCK_IN_ICL BIT(1)
|
|
#define DIV2_IN_ILIM BIT(0)
|
|
|
|
/* Config register definition */
|
|
#define PERPH0_MISC_CFG2_REG 0x2636
|
|
#define CFG_TEMP_PIN_ITEMP BIT(1)
|
|
|
|
#define MISC_USB_WLS_SUSPEND_REG 0x2630
|
|
#define WLS_SUSPEND BIT(1)
|
|
#define USB_SUSPEND BIT(0)
|
|
|
|
#define MISC_SL_SWITCH_EN_REG 0x2631
|
|
#define EN_SLAVE BIT(1)
|
|
#define EN_SWITCHER BIT(0)
|
|
|
|
#define MISC_DIV2_3LVL_CTRL_REG 0x2632
|
|
#define MISC_DIV2_3LVL_CTRL_MASK GENMASK(7, 0)
|
|
#define EN_DIV2_CP BIT(2)
|
|
#define EN_3LVL_BULK BIT(1)
|
|
#define EN_CHG_2X BIT(0)
|
|
|
|
#define MISC_CFG0_REG 0x2634
|
|
#define DIS_SYNC_DRV_BIT BIT(5)
|
|
#define SW_EN_SWITCHER_BIT BIT(3)
|
|
#define CFG_DIS_FPF_IREV_BIT BIT(1)
|
|
|
|
#define MISC_CFG1_REG 0x2635
|
|
#define MISC_CFG1_MASK GENMASK(7, 0)
|
|
#define CFG_OP_MODE_MASK GENMASK(2, 0)
|
|
#define OP_MODE_DISABLED 0
|
|
#define OP_MODE_3LVL_BULK 1
|
|
#define OP_MODE_COMBO 2
|
|
#define OP_MODE_DIV2_CP 3
|
|
#define OP_MODE_PRE_REG_3S 4
|
|
#define OP_MODE_ITLGS_1P 5
|
|
#define OP_MODE_ITLGS_2X 6
|
|
#define OP_MODE_PRE_REGULATOR 7
|
|
|
|
#define MISC_CFG2_REG 0x2636
|
|
|
|
#define NOLOCK_SPARE_REG 0x2637
|
|
#define EN_SLAVE_OWN_FREQ_BIT BIT(5)
|
|
#define DIV2_WIN_UV_SEL_BIT BIT(4)
|
|
#define DIV2_WIN_UV_25MV 0
|
|
#define COMBO_WIN_LO_EXIT_SEL_MASK GENMASK(3, 2)
|
|
#define EXIT_DIV2_VOUT_HI_12P5MV 0
|
|
#define EXIT_DIV2_VOUT_HI_25MV 1
|
|
#define EXIT_DIV2_VOUT_HI_50MV 2
|
|
#define EXIT_DIV2_VOUT_HI_75MV 3
|
|
#define COMBO_WIN_HI_EXIT_SEL_MASK GENMASK(1, 0)
|
|
#define EXIT_DIV2_VOUT_LO_75MV 0
|
|
#define EXIT_DIV2_VOUT_LO_100MV 1
|
|
#define EXIT_DIV2_VOUT_LO_200MV 2
|
|
#define EXIT_DIV2_VOUT_LO_250MV 3
|
|
|
|
#define SMB_EN_TRIGGER_CFG_REG 0x2639
|
|
#define SMB_EN_NEG_TRIGGER BIT(1)
|
|
#define SMB_EN_POS_TRIGGER BIT(0)
|
|
|
|
#define DIV2_LCM_CFG_REG 0x2653
|
|
#define DIV2_LCM_REFRESH_TIMER_SEL_MASK GENMASK(5, 4)
|
|
#define DIV2_WIN_BURST_HIGH_REF_MASK GENMASK(3, 2)
|
|
#define DIV2_WIN_BURST_LOW_REF_MASK GENMASK(1, 0)
|
|
|
|
#define DIV2_CURRENT_REG 0x2655
|
|
#define DIV2_EN_ILIM_DET BIT(2)
|
|
#define DIV2_EN_IREV_DET BIT(1)
|
|
#define DIV2_EN_OCP_DET BIT(0)
|
|
|
|
#define DIV2_PROTECTION_REG 0x2656
|
|
#define DIV2_WIN_OV_SEL_MASK GENMASK(1, 0)
|
|
#define WIN_OV_200_MV 0
|
|
#define WIN_OV_300_MV 1
|
|
#define WIN_OV_400_MV 2
|
|
#define WIN_OV_500_MV 3
|
|
|
|
#define DIV2_MODE_CFG_REG 0x265C
|
|
|
|
#define LCM_EXIT_CTRL_REG 0x265D
|
|
|
|
#define ICHG_SS_DAC_TARGET_REG 0x2660
|
|
#define ICHG_SS_DAC_VALUE_MASK GENMASK(5, 0)
|
|
#define ICHG_STEP_MA 100
|
|
|
|
#define VOUT_DAC_TARGET_REG 0x2663
|
|
#define VOUT_DAC_VALUE_MASK GENMASK(7, 0)
|
|
#define VOUT_1P_MIN_MV 3300
|
|
#define VOUT_1S_MIN_MV 6600
|
|
#define VOUT_1P_STEP_MV 10
|
|
#define VOUT_1S_STEP_MV 20
|
|
|
|
#define VOUT_SS_DAC_TARGET_REG 0x2666
|
|
#define VOUT_SS_DAC_VALUE_MASK GENMASK(5, 0)
|
|
#define VOUT_SS_1P_STEP_MV 90
|
|
#define VOUT_SS_1S_STEP_MV 180
|
|
|
|
#define IIN_SS_DAC_TARGET_REG 0x2669
|
|
#define IIN_SS_DAC_VALUE_MASK GENMASK(6, 0)
|
|
#define IIN_STEP_MA 50
|
|
|
|
#define PERPH0_DIV2_REF_CFG 0x2671
|
|
#define CFG_IREV_REF_BIT BIT(2)
|
|
|
|
#define PERPH0_CFG_SDCDC_REG 0x267A
|
|
#define EN_WIN_UV_BIT BIT(7)
|
|
|
|
#define PERPH0_SSUPPLY_CFG0_REG 0x2682
|
|
#define EN_HV_OV_OPTION2_BIT BIT(7)
|
|
#define EN_MV_OV_OPTION2_BIT BIT(5)
|
|
|
|
#define SSUPLY_TEMP_CTRL_REG 0x2683
|
|
#define SEL_OUT_TEMP_MAX_MASK GENMASK(7, 5)
|
|
#define SEL_OUT_TEMP_MAX_SHFT 5
|
|
#define SEL_OUT_HIGHZ (0 << SEL_OUT_TEMP_MAX_SHFT)
|
|
#define SEL_OUT_VTEMP (1 << SEL_OUT_TEMP_MAX_SHFT)
|
|
#define SEL_OUT_ICHG (2 << SEL_OUT_TEMP_MAX_SHFT)
|
|
#define SEL_OUT_IIN_FB (4 << SEL_OUT_TEMP_MAX_SHFT)
|
|
|
|
#define PERPH1_INT_RT_STS_REG 0x2710
|
|
#define DIV2_WIN_OV_STS BIT(7)
|
|
#define DIV2_WIN_UV_STS BIT(6)
|
|
#define DIV2_ILIM_STS BIT(5)
|
|
#define DIV2_CFLY_SS_DONE_STS BIT(1)
|
|
|
|
#define PERPH1_LOCK_SPARE_REG 0x27C3
|
|
#define CFG_LOCK_SPARE1_MASK GENMASK(7, 6)
|
|
#define CFG_LOCK_SPARE1_SHIFT 6
|
|
|
|
/* available voters */
|
|
#define ILIM_VOTER "ILIM_VOTER"
|
|
#define TAPER_VOTER "TAPER_VOTER"
|
|
#define STATUS_CHANGE_VOTER "STATUS_CHANGE_VOTER"
|
|
#define SHUTDOWN_VOTER "SHUTDOWN_VOTER"
|
|
#define CUTOFF_SOC_VOTER "CUTOFF_SOC_VOTER"
|
|
#define SRC_VOTER "SRC_VOTER"
|
|
#define ICL_VOTER "ICL_VOTER"
|
|
#define WIRELESS_VOTER "WIRELESS_VOTER"
|
|
#define SWITCHER_TOGGLE_VOTER "SWITCHER_TOGGLE_VOTER"
|
|
#define USER_VOTER "USER_VOTER"
|
|
#define FCC_VOTER "FCC_VOTER"
|
|
#define CP_VOTER "CP_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"
|
|
|
|
/* Constant definitions */
|
|
#define DIV2_MAX_ILIM_UA 5000000
|
|
#define DIV2_MAX_ILIM_DUAL_CP_UA 10000000
|
|
#define DIV2_ILIM_CFG_PCT 105
|
|
|
|
#define TAPER_STEPPER_UA_DEFAULT 100000
|
|
#define TAPER_STEPPER_UA_IN_CC_MODE 200000
|
|
#define CC_MODE_TAPER_MAIN_ICL_UA 500000
|
|
|
|
#define MAX_IOUT_UA 6300000
|
|
#define MAX_1S_VOUT_UV 11700000
|
|
|
|
#define THERMAL_SUSPEND_DECIDEGC 1400
|
|
|
|
#define DIV2_CP_MASTER 0
|
|
#define DIV2_CP_SLAVE 1
|
|
#define COMBO_PRE_REGULATOR 2
|
|
|
|
enum isns_mode {
|
|
ISNS_MODE_OFF = 0,
|
|
ISNS_MODE_ACTIVE,
|
|
ISNS_MODE_STANDBY,
|
|
};
|
|
|
|
enum ovp {
|
|
OVP_17P7V = 0,
|
|
OVP_14V,
|
|
OVP_22P2V,
|
|
OVP_7P3,
|
|
};
|
|
|
|
enum {
|
|
/* Perph0 IRQs */
|
|
CFLY_HARD_FAULT_LATCH_IRQ,
|
|
TEMP_SHDWN_IRQ,
|
|
VOUT_UV_LATH_IRQ,
|
|
DIV2_IREV_LATCH_IRQ,
|
|
WLS_IN_UVLO_IRQ,
|
|
USB_IN_UVLO_IRQ,
|
|
WLS_IN_OVLO_IRQ,
|
|
USB_IN_OVLO_IRQ,
|
|
/* Perph1 IRQs */
|
|
BK_IIN_REG_IRQ,
|
|
CFLY_SS_DONE_IRQ,
|
|
EN_DCDC_IRQ,
|
|
ITERM_3LVL_LATCH_IRQ,
|
|
VOUT_OV_3LB_IRQ,
|
|
DIV2_ILIM_IRQ,
|
|
DIV2_WIN_UV_IRQ,
|
|
DIV2_WIN_OV_IRQ,
|
|
/* Perph2 IRQs */
|
|
IN_3LVL_MODE_IRQ,
|
|
DIV2_MODE_IRQ,
|
|
BK_CV_REG_IRQ,
|
|
BK_CC_REG_IRQ,
|
|
SS_DAC_INT_IRQ,
|
|
SMB_EN_RISE_IRQ,
|
|
SMB_EN_FALL_IRQ,
|
|
/* End */
|
|
NUM_IRQS,
|
|
};
|
|
|
|
struct smb_irq {
|
|
const char *name;
|
|
const irq_handler_t handler;
|
|
const bool wake;
|
|
int shift;
|
|
};
|
|
|
|
static const struct smb_irq smb_irqs[];
|
|
|
|
struct smb1398_chip {
|
|
struct device *dev;
|
|
struct regmap *regmap;
|
|
struct pmic_revid_data *pmic_rev_id;
|
|
|
|
struct wakeup_source *ws;
|
|
struct iio_channel *die_temp_chan;
|
|
|
|
struct power_supply *div2_cp_master_psy;
|
|
struct power_supply *div2_cp_slave_psy;
|
|
struct power_supply *pre_regulator_psy;
|
|
struct power_supply *batt_psy;
|
|
struct power_supply *usb_psy;
|
|
struct power_supply *dc_psy;
|
|
struct notifier_block nb;
|
|
|
|
struct votable *awake_votable;
|
|
struct votable *div2_cp_disable_votable;
|
|
struct votable *div2_cp_slave_disable_votable;
|
|
struct votable *div2_cp_ilim_votable;
|
|
struct votable *pre_regulator_iout_votable;
|
|
struct votable *pre_regulator_vout_votable;
|
|
struct votable *fcc_votable;
|
|
struct votable *fv_votable;
|
|
struct votable *fcc_main_votable;
|
|
struct votable *usb_icl_votable;
|
|
|
|
struct work_struct status_change_work;
|
|
struct work_struct taper_work;
|
|
|
|
struct mutex die_chan_lock;
|
|
spinlock_t status_change_lock;
|
|
|
|
int irqs[NUM_IRQS];
|
|
int die_temp;
|
|
int div2_cp_min_ilim_ua;
|
|
int ilim_ua_disable_div2_cp_slave;
|
|
int max_cutoff_soc;
|
|
int taper_entry_fv;
|
|
int div2_irq_status;
|
|
u32 div2_cp_role;
|
|
u32 pl_output_mode;
|
|
u32 pl_input_mode;
|
|
enum isns_mode current_capability;
|
|
int cc_mode_taper_main_icl_ua;
|
|
int cp_status1;
|
|
int cp_status2;
|
|
int cp_enable;
|
|
int cp_isns_master;
|
|
int cp_isns_slave;
|
|
int cp_ilim;
|
|
|
|
bool status_change_running;
|
|
bool taper_work_running;
|
|
bool cutoff_soc_checked;
|
|
bool smb_en;
|
|
bool switcher_en;
|
|
bool slave_en;
|
|
bool in_suspend;
|
|
bool disabled;
|
|
};
|
|
|
|
static int smb1398_read(struct smb1398_chip *chip, u16 reg, u8 *val)
|
|
{
|
|
int rc = 0, value = 0;
|
|
|
|
rc = regmap_read(chip->regmap, reg, &value);
|
|
if (rc < 0)
|
|
dev_err(chip->dev, "Couldn't read register 0x%x, rc=%d\n",
|
|
reg, rc);
|
|
else
|
|
*val = (u8)value;
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int smb1398_masked_write(struct smb1398_chip *chip,
|
|
u16 reg, u8 mask, u8 val)
|
|
{
|
|
int rc = 0;
|
|
|
|
rc = regmap_update_bits(chip->regmap, reg, mask, val);
|
|
if (rc < 0)
|
|
dev_err(chip->dev, "Couldn't update register 0x%x to 0x%x with mask 0x%x, rc=%d\n",
|
|
reg, val, mask, rc);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int smb1398_get_enable_status(struct smb1398_chip *chip)
|
|
{
|
|
int rc = 0;
|
|
u8 val;
|
|
bool switcher_en = false;
|
|
|
|
rc = smb1398_read(chip, MODE_STATUS_REG, &val);
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
chip->smb_en = !!(val & SMB_EN);
|
|
chip->switcher_en = !!(val & PRE_EN_DCDC);
|
|
chip->slave_en = !!(val & DIV2_EN_SLAVE);
|
|
|
|
rc = smb1398_read(chip, MISC_SL_SWITCH_EN_REG, &val);
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
switcher_en = !!(val & EN_SWITCHER);
|
|
chip->switcher_en = switcher_en && chip->switcher_en;
|
|
|
|
dev_dbg(chip->dev, "smb_en = %d, switcher_en = %d, slave_en = %d\n",
|
|
chip->smb_en, chip->switcher_en, chip->slave_en);
|
|
return rc;
|
|
}
|
|
|
|
static int smb1398_get_iin_ma(struct smb1398_chip *chip, int *iin_ma)
|
|
{
|
|
int rc = 0;
|
|
u8 val;
|
|
|
|
rc = smb1398_read(chip, IIN_SS_DAC_TARGET_REG, &val);
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
*iin_ma = (val & IIN_SS_DAC_VALUE_MASK) * IIN_STEP_MA;
|
|
|
|
dev_dbg(chip->dev, "get iin_ma = %dmA\n", *iin_ma);
|
|
return rc;
|
|
}
|
|
|
|
static int smb1398_set_iin_ma(struct smb1398_chip *chip, int iin_ma)
|
|
{
|
|
int rc = 0;
|
|
u8 val;
|
|
|
|
val = iin_ma / IIN_STEP_MA;
|
|
rc = smb1398_masked_write(chip, IIN_SS_DAC_TARGET_REG,
|
|
IIN_SS_DAC_VALUE_MASK, val);
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
dev_dbg(chip->dev, "set iin_ma = %dmA\n", iin_ma);
|
|
return rc;
|
|
}
|
|
|
|
static int smb1398_set_ichg_ma(struct smb1398_chip *chip, int ichg_ma)
|
|
{
|
|
int rc = 0;
|
|
u8 val;
|
|
|
|
if (ichg_ma < 0 || ichg_ma > ICHG_SS_DAC_VALUE_MASK * ICHG_STEP_MA)
|
|
return rc;
|
|
|
|
val = ichg_ma / ICHG_STEP_MA;
|
|
rc = smb1398_masked_write(chip, ICHG_SS_DAC_TARGET_REG,
|
|
ICHG_SS_DAC_VALUE_MASK, val);
|
|
|
|
dev_dbg(chip->dev, "set ichg %dmA\n", ichg_ma);
|
|
return rc;
|
|
}
|
|
|
|
static int smb1398_get_ichg_ma(struct smb1398_chip *chip, int *ichg_ma)
|
|
{
|
|
int rc = 0;
|
|
u8 val;
|
|
|
|
rc = smb1398_read(chip, ICHG_SS_DAC_TARGET_REG, &val);
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
*ichg_ma = (val & ICHG_SS_DAC_VALUE_MASK) * ICHG_STEP_MA;
|
|
|
|
dev_dbg(chip->dev, "get ichg %dmA\n", *ichg_ma);
|
|
return 0;
|
|
}
|
|
|
|
static int smb1398_set_1s_vout_mv(struct smb1398_chip *chip, int vout_mv)
|
|
{
|
|
int rc = 0;
|
|
u8 val;
|
|
|
|
if (vout_mv < VOUT_1S_MIN_MV)
|
|
return -EINVAL;
|
|
|
|
val = (vout_mv - VOUT_1S_MIN_MV) / VOUT_1S_STEP_MV;
|
|
|
|
rc = smb1398_masked_write(chip, VOUT_DAC_TARGET_REG,
|
|
VOUT_DAC_VALUE_MASK, val);
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int smb1398_get_1s_vout_mv(struct smb1398_chip *chip, int *vout_mv)
|
|
{
|
|
int rc;
|
|
u8 val;
|
|
|
|
rc = smb1398_read(chip, VOUT_DAC_TARGET_REG, &val);
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
*vout_mv = (val & VOUT_DAC_VALUE_MASK) * VOUT_1S_STEP_MV +
|
|
VOUT_1S_MIN_MV;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int smb1398_get_die_temp(struct smb1398_chip *chip, int *temp)
|
|
{
|
|
int die_temp_deciC = 0, rc = 0;
|
|
|
|
rc = smb1398_get_enable_status(chip);
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
if (!chip->smb_en)
|
|
return -ENODATA;
|
|
|
|
mutex_lock(&chip->die_chan_lock);
|
|
rc = iio_read_channel_processed(chip->die_temp_chan, &die_temp_deciC);
|
|
mutex_unlock(&chip->die_chan_lock);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Couldn't read die_temp_chan, rc=%d\n", rc);
|
|
} else {
|
|
*temp = die_temp_deciC / 100;
|
|
dev_dbg(chip->dev, "die temp %d\n", *temp);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int smb1398_div2_cp_get_status1(
|
|
struct smb1398_chip *chip, u8 *status)
|
|
{
|
|
int rc = 0;
|
|
u8 val;
|
|
bool ilim, win_uv, win_ov;
|
|
|
|
rc = smb1398_read(chip, PERPH1_INT_RT_STS_REG, &val);
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
win_uv = !!(val & DIV2_WIN_UV_STS);
|
|
win_ov = !!(val & DIV2_WIN_OV_STS);
|
|
ilim = !!(val & DIV2_ILIM_STS);
|
|
*status = ilim << 5 | win_uv << 1 | win_ov;
|
|
|
|
dev_dbg(chip->dev, "status1 = 0x%x\n", *status);
|
|
return rc;
|
|
}
|
|
|
|
static int smb1398_div2_cp_get_status2(
|
|
struct smb1398_chip *chip, u8 *status)
|
|
{
|
|
int rc = 0;
|
|
u8 val;
|
|
bool smb_en, vin_ov, vin_uv, irev, tsd, switcher_off;
|
|
|
|
rc = smb1398_read(chip, MODE_STATUS_REG, &val);
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
smb_en = !!(val & SMB_EN);
|
|
switcher_off = !(val & PRE_EN_DCDC);
|
|
|
|
rc = smb1398_read(chip, PERPH1_INT_RT_STS_REG, &val);
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
switcher_off = !(val & DIV2_CFLY_SS_DONE_STS) && switcher_off;
|
|
|
|
rc = smb1398_read(chip, SWITCHER_OFF_VIN_STATUS_REG, &val);
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
vin_ov = !!(val & USB_IN_OVLO);
|
|
vin_uv = !!(val & USB_IN_UVLO);
|
|
|
|
rc = smb1398_read(chip, SWITCHER_OFF_FAULT_REG, &val);
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
irev = !!(val & DIV2_IREV_LATCH);
|
|
tsd = !!(val & TEMP_SHDWN);
|
|
|
|
*status = smb_en << 7 | vin_ov << 6 | vin_uv << 5
|
|
| irev << 3 | tsd << 2 | switcher_off;
|
|
|
|
dev_dbg(chip->dev, "status2 = 0x%x\n", *status);
|
|
return rc;
|
|
}
|
|
|
|
static int smb1398_div2_cp_get_irq_status(
|
|
struct smb1398_chip *chip, u8 *status)
|
|
{
|
|
int rc = 0;
|
|
u8 val;
|
|
bool ilim, irev, tsd, off_vin, off_win;
|
|
|
|
rc = smb1398_read(chip, PERPH1_INT_RT_STS_REG, &val);
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
ilim = !!(val & DIV2_ILIM_STS);
|
|
off_win = !!(val & (DIV2_WIN_OV_STS | DIV2_WIN_UV_STS));
|
|
|
|
rc = smb1398_read(chip, PERPH0_INT_RT_STS_REG, &val);
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
irev = !!(val & DIV2_IREV_LATCH_STS);
|
|
tsd = !!(val & TEMP_SHUTDOWN_STS);
|
|
off_vin = !!(val & (USB_IN_OVLO_STS | USB_IN_UVLO_STS));
|
|
|
|
*status = ilim << 6 | irev << 3 | tsd << 2 | off_vin << 1 | off_win;
|
|
|
|
dev_dbg(chip->dev, "irq_status = 0x%x\n", *status);
|
|
return rc;
|
|
}
|
|
|
|
static int smb1398_div2_cp_switcher_en(struct smb1398_chip *chip, bool en)
|
|
{
|
|
int rc;
|
|
|
|
rc = smb1398_masked_write(chip, MISC_SL_SWITCH_EN_REG,
|
|
EN_SWITCHER, en ? EN_SWITCHER : 0);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Couldn't write SWITCH_EN_REG, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
chip->switcher_en = en;
|
|
|
|
dev_dbg(chip->dev, "%s switcher\n", en ? "enable" : "disable");
|
|
return rc;
|
|
}
|
|
|
|
static int smb1398_div2_cp_isns_mode_control(
|
|
struct smb1398_chip *chip, enum isns_mode mode)
|
|
{
|
|
int rc = 0;
|
|
u8 mux_sel;
|
|
|
|
switch (mode) {
|
|
case ISNS_MODE_STANDBY:
|
|
/* VTEMP */
|
|
mux_sel = SEL_OUT_VTEMP;
|
|
break;
|
|
case ISNS_MODE_OFF:
|
|
/* High-Z */
|
|
mux_sel = SEL_OUT_HIGHZ;
|
|
break;
|
|
case ISNS_MODE_ACTIVE:
|
|
/* IIN_FB */
|
|
mux_sel = SEL_OUT_IIN_FB;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
rc = smb1398_masked_write(chip, SSUPLY_TEMP_CTRL_REG,
|
|
SEL_OUT_TEMP_MAX_MASK, mux_sel);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Couldn't set SSUPLY_TEMP_CTRL_REG, rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
rc = smb1398_masked_write(chip, PERPH0_MISC_CFG2_REG,
|
|
CFG_TEMP_PIN_ITEMP, 0);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Couldn't set PERPH0_MISC_CFG2_REG, rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline int calculate_div2_cp_isns_ua(int temp)
|
|
{
|
|
/* ISNS = (2850 + (0.0034 * thermal_reading) / 0.32) * 1000 uA */
|
|
return (2850 * 1000 + div_s64((s64)temp * 340, 32));
|
|
}
|
|
|
|
static bool is_cps_available(struct smb1398_chip *chip)
|
|
{
|
|
if (chip->div2_cp_slave_psy)
|
|
return true;
|
|
|
|
chip->div2_cp_slave_psy = power_supply_get_by_name("cp_slave");
|
|
if (chip->div2_cp_slave_psy)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
static int smb1398_div2_cp_get_master_isns(
|
|
struct smb1398_chip *chip, int *isns_ua)
|
|
{
|
|
union power_supply_propval pval = {0};
|
|
int rc = 0, temp;
|
|
|
|
rc = smb1398_get_enable_status(chip);
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
if (!chip->smb_en)
|
|
return -ENODATA;
|
|
|
|
/*
|
|
* Follow this procedure to read master CP ISNS:
|
|
* set slave CP TEMP_MUX to HighZ;
|
|
* set master CP TEMP_MUX to IIN_FB;
|
|
* read corresponding ADC channel in Kekaha;
|
|
* set master CP TEMP_MUX to VTEMP;
|
|
*/
|
|
mutex_lock(&chip->die_chan_lock);
|
|
if (is_cps_available(chip)) {
|
|
pval.intval = ISNS_MODE_OFF;
|
|
rc = power_supply_set_property(chip->div2_cp_slave_psy,
|
|
POWER_SUPPLY_PROP_CURRENT_CAPABILITY, &pval);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Couldn't set slave ISNS_MODE_OFF, rc=%d\n",
|
|
rc);
|
|
goto unlock;
|
|
}
|
|
}
|
|
|
|
rc = smb1398_div2_cp_isns_mode_control(chip, ISNS_MODE_ACTIVE);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Couldn't set master ISNS_MODE_ACTIVE, rc=%d\n",
|
|
rc);
|
|
goto unlock;
|
|
}
|
|
|
|
rc = iio_read_channel_processed(chip->die_temp_chan, &temp);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Couldn't read die_temp_chan, rc=%d\n", rc);
|
|
goto unlock;
|
|
}
|
|
|
|
rc = smb1398_div2_cp_isns_mode_control(chip, ISNS_MODE_STANDBY);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Couldn't set master ISNS_MODE_STANDBY, rc=%d\n",
|
|
rc);
|
|
goto unlock;
|
|
}
|
|
|
|
unlock:
|
|
mutex_unlock(&chip->die_chan_lock);
|
|
if (rc >= 0) {
|
|
*isns_ua = calculate_div2_cp_isns_ua(temp);
|
|
dev_dbg(chip->dev, "master isns = %duA\n", *isns_ua);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int smb1398_div2_cp_get_slave_isns(
|
|
struct smb1398_chip *chip, int *isns_ua)
|
|
{
|
|
union power_supply_propval pval = {0};
|
|
int temp = 0, rc;
|
|
|
|
if (!is_cps_available(chip)) {
|
|
*isns_ua = 0;
|
|
return 0;
|
|
}
|
|
|
|
rc = smb1398_get_enable_status(chip);
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
if (!chip->smb_en || !chip->slave_en)
|
|
return -ENODATA;
|
|
|
|
/*
|
|
* Follow this procedure to read slave CP ISNS:
|
|
* set master CP TEMP_MUX to HighZ;
|
|
* set slave CP TEMP_MUX to IIN_FB;
|
|
* read corresponding ADC channel in Kekaha;
|
|
* set master CP TEMP_MUX to VTEMP;
|
|
*/
|
|
mutex_lock(&chip->die_chan_lock);
|
|
rc = smb1398_div2_cp_isns_mode_control(chip, ISNS_MODE_OFF);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Couldn't set master ISNS_MODE_OFF, rc=%d\n",
|
|
rc);
|
|
goto unlock;
|
|
}
|
|
|
|
pval.intval = ISNS_MODE_ACTIVE;
|
|
rc = power_supply_set_property(chip->div2_cp_slave_psy,
|
|
POWER_SUPPLY_PROP_CURRENT_CAPABILITY, &pval);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Couldn't set slave ISNS_MODE_ACTIVE, rc=%d\n",
|
|
rc);
|
|
goto unlock;
|
|
}
|
|
|
|
rc = iio_read_channel_processed(chip->die_temp_chan, &temp);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Couldn't get die_temp_chan, rc=%d\n", rc);
|
|
goto unlock;
|
|
}
|
|
|
|
rc = smb1398_div2_cp_isns_mode_control(chip, ISNS_MODE_STANDBY);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Couldn't set master ISNS_MODE_STANDBY, rc=%d\n",
|
|
rc);
|
|
goto unlock;
|
|
}
|
|
unlock:
|
|
mutex_unlock(&chip->die_chan_lock);
|
|
|
|
if (rc >= 0) {
|
|
*isns_ua = calculate_div2_cp_isns_ua(temp);
|
|
dev_dbg(chip->dev, "slave isns = %duA\n", *isns_ua);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static void smb1398_toggle_switcher(struct smb1398_chip *chip)
|
|
{
|
|
int rc = 0;
|
|
|
|
/*
|
|
* Disable DIV2_ILIM detection before toggling the switcher
|
|
* to prevent any ILIM interrupt storm while the toggling
|
|
*/
|
|
rc = smb1398_masked_write(chip, DIV2_CURRENT_REG, DIV2_EN_ILIM_DET, 0);
|
|
if (rc < 0)
|
|
dev_err(chip->dev, "Couldn't disable EN_ILIM_DET, rc=%d\n", rc);
|
|
|
|
vote(chip->div2_cp_disable_votable, SWITCHER_TOGGLE_VOTER, true, 0);
|
|
|
|
/* Delay for toggling switcher */
|
|
usleep_range(20, 30);
|
|
vote(chip->div2_cp_disable_votable, SWITCHER_TOGGLE_VOTER, false, 0);
|
|
|
|
rc = smb1398_masked_write(chip, DIV2_CURRENT_REG,
|
|
DIV2_EN_ILIM_DET, DIV2_EN_ILIM_DET);
|
|
if (rc < 0)
|
|
dev_err(chip->dev, "Couldn't disable EN_ILIM_DET, rc=%d\n", rc);
|
|
}
|
|
|
|
static enum power_supply_property div2_cp_master_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_MODEL_NAME,
|
|
POWER_SUPPLY_PROP_PARALLEL_MODE,
|
|
POWER_SUPPLY_PROP_PARALLEL_OUTPUT_MODE,
|
|
POWER_SUPPLY_PROP_MIN_ICL,
|
|
};
|
|
|
|
static int div2_cp_master_get_prop_suspended(struct smb1398_chip *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_en;
|
|
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->div2_irq_status;
|
|
break;
|
|
case POWER_SUPPLY_PROP_CP_ILIM:
|
|
val->intval = chip->cp_ilim;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#define DEFAULT_HVDCP3_MIN_ICL_UA 1000000
|
|
static int smb1398_div2_cp_get_min_icl(struct smb1398_chip *chip)
|
|
{
|
|
union power_supply_propval pval;
|
|
int rc;
|
|
|
|
/* Use max(dt_min_icl, 1A) for HVDCP3 */
|
|
if (chip->usb_psy) {
|
|
rc = power_supply_get_property(chip->usb_psy,
|
|
POWER_SUPPLY_PROP_REAL_TYPE, &pval);
|
|
if (rc >= 0 && (pval.intval == POWER_SUPPLY_TYPE_USB_HVDCP_3))
|
|
return max(chip->div2_cp_min_ilim_ua,
|
|
DEFAULT_HVDCP3_MIN_ICL_UA);
|
|
}
|
|
|
|
return chip->div2_cp_min_ilim_ua;
|
|
}
|
|
|
|
static int div2_cp_master_get_prop(struct power_supply *psy,
|
|
enum power_supply_property prop,
|
|
union power_supply_propval *val)
|
|
{
|
|
struct smb1398_chip *chip = power_supply_get_drvdata(psy);
|
|
int rc = 0, ilim_ma, temp, isns_ua;
|
|
u8 status;
|
|
|
|
/*
|
|
* Return the cached values when the system is in suspend state
|
|
* instead of reading the registers to avoid read failures.
|
|
*/
|
|
if (chip->in_suspend) {
|
|
rc = div2_cp_master_get_prop_suspended(chip, prop, val);
|
|
if (!rc)
|
|
return rc;
|
|
rc = 0;
|
|
}
|
|
|
|
switch (prop) {
|
|
case POWER_SUPPLY_PROP_CP_STATUS1:
|
|
rc = smb1398_div2_cp_get_status1(chip, &status);
|
|
if (!rc)
|
|
chip->cp_status1 = val->intval = status;
|
|
break;
|
|
case POWER_SUPPLY_PROP_CP_STATUS2:
|
|
rc = smb1398_div2_cp_get_status2(chip, &status);
|
|
if (!rc)
|
|
chip->cp_status2 = val->intval = status;
|
|
break;
|
|
case POWER_SUPPLY_PROP_CP_ENABLE:
|
|
rc = smb1398_get_enable_status(chip);
|
|
if (!rc)
|
|
chip->cp_enable = val->intval = chip->smb_en &&
|
|
!get_effective_result(
|
|
chip->div2_cp_disable_votable);
|
|
break;
|
|
case POWER_SUPPLY_PROP_CP_SWITCHER_EN:
|
|
rc = smb1398_get_enable_status(chip);
|
|
if (!rc)
|
|
val->intval = chip->switcher_en;
|
|
break;
|
|
case POWER_SUPPLY_PROP_CP_ISNS:
|
|
rc = smb1398_div2_cp_get_master_isns(chip, &isns_ua);
|
|
if (rc >= 0)
|
|
chip->cp_isns_master = val->intval = isns_ua;
|
|
break;
|
|
case POWER_SUPPLY_PROP_CP_ISNS_SLAVE:
|
|
rc = smb1398_div2_cp_get_slave_isns(chip, &isns_ua);
|
|
if (rc >= 0)
|
|
chip->cp_isns_slave = val->intval = isns_ua;
|
|
break;
|
|
case POWER_SUPPLY_PROP_CP_TOGGLE_SWITCHER:
|
|
val->intval = 0;
|
|
break;
|
|
case POWER_SUPPLY_PROP_CP_DIE_TEMP:
|
|
rc = smb1398_get_die_temp(chip, &temp);
|
|
if (rc >= 0) {
|
|
val->intval = temp;
|
|
if (temp <= THERMAL_SUSPEND_DECIDEGC)
|
|
chip->die_temp = temp;
|
|
else if (chip->die_temp == -ENODATA)
|
|
rc = -ENODATA;
|
|
else
|
|
val->intval = chip->die_temp;
|
|
}
|
|
break;
|
|
case POWER_SUPPLY_PROP_CP_IRQ_STATUS:
|
|
val->intval = chip->div2_irq_status;
|
|
rc = smb1398_div2_cp_get_irq_status(chip, &status);
|
|
if (!rc)
|
|
val->intval |= status;
|
|
break;
|
|
case POWER_SUPPLY_PROP_CP_ILIM:
|
|
if (is_cps_available(chip)) {
|
|
if (chip->div2_cp_ilim_votable)
|
|
val->intval = get_effective_result(
|
|
chip->div2_cp_ilim_votable);
|
|
} else {
|
|
rc = smb1398_get_iin_ma(chip, &ilim_ma);
|
|
if (!rc)
|
|
val->intval = (ilim_ma * 1000 * 100)
|
|
/ DIV2_ILIM_CFG_PCT;
|
|
}
|
|
chip->cp_ilim = val->intval;
|
|
break;
|
|
case POWER_SUPPLY_PROP_CHIP_VERSION:
|
|
val->intval = chip->pmic_rev_id->rev4;
|
|
break;
|
|
case POWER_SUPPLY_PROP_MODEL_NAME:
|
|
val->strval = (chip->pmic_rev_id->rev4 > 1) ? "SMB1398_V2" :
|
|
"SMB1398_V1";
|
|
break;
|
|
case POWER_SUPPLY_PROP_PARALLEL_MODE:
|
|
val->intval = chip->pl_input_mode;
|
|
break;
|
|
case POWER_SUPPLY_PROP_PARALLEL_OUTPUT_MODE:
|
|
val->intval = chip->pl_output_mode;
|
|
break;
|
|
case POWER_SUPPLY_PROP_MIN_ICL:
|
|
val->intval = smb1398_div2_cp_get_min_icl(chip);
|
|
break;
|
|
default:
|
|
rc = -EINVAL;
|
|
break;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int div2_cp_master_set_prop(struct power_supply *psy,
|
|
enum power_supply_property prop,
|
|
const union power_supply_propval *val)
|
|
{
|
|
struct smb1398_chip *chip = power_supply_get_drvdata(psy);
|
|
int rc = 0;
|
|
|
|
switch (prop) {
|
|
case POWER_SUPPLY_PROP_CP_ENABLE:
|
|
vote(chip->div2_cp_disable_votable,
|
|
USER_VOTER, !val->intval, 0);
|
|
break;
|
|
case POWER_SUPPLY_PROP_CP_TOGGLE_SWITCHER:
|
|
if (!!val->intval)
|
|
smb1398_toggle_switcher(chip);
|
|
break;
|
|
case POWER_SUPPLY_PROP_CP_IRQ_STATUS:
|
|
chip->div2_irq_status = val->intval;
|
|
break;
|
|
case POWER_SUPPLY_PROP_CP_ILIM:
|
|
if (chip->div2_cp_ilim_votable)
|
|
vote_override(chip->div2_cp_ilim_votable, CC_MODE_VOTER,
|
|
(val->intval > 0), val->intval);
|
|
break;
|
|
default:
|
|
dev_err(chip->dev, "setprop %d is not supported\n", prop);
|
|
rc = -EINVAL;
|
|
break;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int div2_cp_master_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_CP_ILIM:
|
|
return 1;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct power_supply_desc div2_cp_master_desc = {
|
|
.name = "charge_pump_master",
|
|
.type = POWER_SUPPLY_TYPE_CHARGE_PUMP,
|
|
.properties = div2_cp_master_props,
|
|
.num_properties = ARRAY_SIZE(div2_cp_master_props),
|
|
.get_property = div2_cp_master_get_prop,
|
|
.set_property = div2_cp_master_set_prop,
|
|
.property_is_writeable = div2_cp_master_prop_is_writeable,
|
|
};
|
|
|
|
static int smb1398_init_div2_cp_master_psy(struct smb1398_chip *chip)
|
|
{
|
|
struct power_supply_config div2_cp_master_psy_cfg = {};
|
|
int rc = 0;
|
|
|
|
div2_cp_master_psy_cfg.drv_data = chip;
|
|
div2_cp_master_psy_cfg.of_node = chip->dev->of_node;
|
|
|
|
chip->div2_cp_master_psy = devm_power_supply_register(chip->dev,
|
|
&div2_cp_master_desc, &div2_cp_master_psy_cfg);
|
|
if (IS_ERR(chip->div2_cp_master_psy)) {
|
|
rc = PTR_ERR(chip->div2_cp_master_psy);
|
|
dev_err(chip->dev, "Register div2_cp_master power supply failed, rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static bool is_psy_voter_available(struct smb1398_chip *chip)
|
|
{
|
|
if (!chip->batt_psy) {
|
|
chip->batt_psy = power_supply_get_by_name("battery");
|
|
if (!chip->batt_psy) {
|
|
dev_dbg(chip->dev, "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) {
|
|
dev_dbg(chip->dev, "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) {
|
|
dev_dbg(chip->dev, "Couldn't find DC psy\n");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!chip->fcc_votable) {
|
|
chip->fcc_votable = find_votable("FCC");
|
|
if (!chip->fcc_votable) {
|
|
dev_dbg(chip->dev, "Couldn't find FCC voltable\n");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!chip->fv_votable) {
|
|
chip->fv_votable = find_votable("FV");
|
|
if (!chip->fv_votable) {
|
|
dev_dbg(chip->dev, "Couldn't find FV voltable\n");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!chip->usb_icl_votable) {
|
|
chip->usb_icl_votable = find_votable("USB_ICL");
|
|
if (!chip->usb_icl_votable) {
|
|
dev_dbg(chip->dev, "Couldn't find USB_ICL voltable\n");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!chip->fcc_main_votable) {
|
|
chip->fcc_main_votable = find_votable("FCC_MAIN");
|
|
if (!chip->fcc_main_votable) {
|
|
dev_dbg(chip->dev, "Couldn't find FCC_MAIN voltable\n");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool is_cutoff_soc_reached(struct smb1398_chip *chip)
|
|
{
|
|
int rc;
|
|
union power_supply_propval pval = {0};
|
|
|
|
if (!chip->batt_psy)
|
|
goto err;
|
|
|
|
rc = power_supply_get_property(chip->batt_psy,
|
|
POWER_SUPPLY_PROP_CAPACITY, &pval);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Couldn't get battery soc, rc=%d\n", rc);
|
|
goto err;
|
|
}
|
|
|
|
if (pval.intval >= chip->max_cutoff_soc)
|
|
return true;
|
|
err:
|
|
return false;
|
|
}
|
|
|
|
static bool is_adapter_in_cc_mode(struct smb1398_chip *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) {
|
|
dev_err(chip->dev, "Couldn't get ADAPTER_CC_MODE, rc=%d\n");
|
|
return rc;
|
|
}
|
|
|
|
return !!pval.intval;
|
|
}
|
|
|
|
static int smb1398_awake_vote_cb(struct votable *votable,
|
|
void *data, int awake, const char *client)
|
|
{
|
|
struct smb1398_chip *chip = (struct smb1398_chip *)data;
|
|
|
|
if (awake)
|
|
pm_stay_awake(chip->dev);
|
|
else
|
|
pm_relax(chip->dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int smb1398_div2_cp_disable_vote_cb(struct votable *votable,
|
|
void *data, int disable, const char *client)
|
|
{
|
|
struct smb1398_chip *chip = (struct smb1398_chip *)data;
|
|
int rc = 0;
|
|
|
|
if (!is_psy_voter_available(chip) || chip->in_suspend)
|
|
return -EAGAIN;
|
|
|
|
rc = smb1398_div2_cp_switcher_en(chip, !disable);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "%s switcher failed, rc=%d\n",
|
|
!!disable ? "disable" : "enable", rc);
|
|
return rc;
|
|
}
|
|
|
|
if (is_cps_available(chip))
|
|
vote(chip->div2_cp_slave_disable_votable, MAIN_DISABLE_VOTER,
|
|
!!disable ? true : false, 0);
|
|
|
|
if (chip->div2_cp_master_psy && (disable != chip->disabled))
|
|
power_supply_changed(chip->div2_cp_master_psy);
|
|
|
|
chip->disabled = disable;
|
|
return 0;
|
|
}
|
|
|
|
static int smb1398_div2_cp_slave_disable_vote_cb(struct votable *votable,
|
|
void *data, int disable, const char *client)
|
|
{
|
|
struct smb1398_chip *chip = (struct smb1398_chip *)data;
|
|
union power_supply_propval pval = {0};
|
|
u16 reg;
|
|
u8 val;
|
|
int rc, ilim_ua;
|
|
|
|
if (!is_cps_available(chip))
|
|
return -ENODEV;
|
|
|
|
reg = MISC_SL_SWITCH_EN_REG;
|
|
val = !!disable ? 0 : EN_SLAVE;
|
|
rc = smb1398_masked_write(chip, reg, EN_SLAVE, val);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Couldn't write slave_en, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
pval.intval = !disable;
|
|
rc = power_supply_set_property(chip->div2_cp_slave_psy,
|
|
POWER_SUPPLY_PROP_CP_ENABLE, &pval);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "%s slave switcher failed, rc=%d\n",
|
|
!!disable ? "disable" : "enable", rc);
|
|
return rc;
|
|
}
|
|
|
|
/* Re-distribute ILIM to Master CP when Slave is disabled */
|
|
if (disable && (chip->div2_cp_ilim_votable)) {
|
|
ilim_ua = get_effective_result_locked(
|
|
chip->div2_cp_ilim_votable);
|
|
|
|
ilim_ua = (ilim_ua * DIV2_ILIM_CFG_PCT) / 100;
|
|
|
|
if (ilim_ua > DIV2_MAX_ILIM_UA)
|
|
ilim_ua = DIV2_MAX_ILIM_UA;
|
|
|
|
rc = smb1398_set_iin_ma(chip, ilim_ua / 1000);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Could't set CP master ilim, rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
dev_dbg(chip->dev, "slave disabled, restore master CP ilim to %duA\n",
|
|
ilim_ua);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int smb1398_div2_cp_ilim_vote_cb(struct votable *votable,
|
|
void *data, int ilim_ua, const char *client)
|
|
{
|
|
struct smb1398_chip *chip = (struct smb1398_chip *)data;
|
|
union power_supply_propval pval = {0};
|
|
int rc = 0, max_ilim_ua, min_ilim_ua;
|
|
bool slave_dis, split_ilim = false;
|
|
|
|
if (!is_psy_voter_available(chip) || chip->in_suspend)
|
|
return -EAGAIN;
|
|
|
|
if (!client)
|
|
return -EINVAL;
|
|
|
|
min_ilim_ua = smb1398_div2_cp_get_min_icl(chip);
|
|
|
|
ilim_ua = (ilim_ua * DIV2_ILIM_CFG_PCT) / 100;
|
|
|
|
max_ilim_ua = is_cps_available(chip) ?
|
|
DIV2_MAX_ILIM_DUAL_CP_UA : DIV2_MAX_ILIM_UA;
|
|
ilim_ua = min(ilim_ua, max_ilim_ua);
|
|
if (ilim_ua < min_ilim_ua) {
|
|
dev_dbg(chip->dev, "ilim %duA is too low to config CP charging\n",
|
|
ilim_ua);
|
|
vote(chip->div2_cp_disable_votable, ILIM_VOTER, true, 0);
|
|
} else {
|
|
if (is_cps_available(chip)) {
|
|
split_ilim = true;
|
|
slave_dis = ilim_ua < (2 * min_ilim_ua);
|
|
vote(chip->div2_cp_slave_disable_votable, ILIM_VOTER,
|
|
slave_dis, 0);
|
|
slave_dis = !!get_effective_result(
|
|
chip->div2_cp_slave_disable_votable);
|
|
if (slave_dis)
|
|
split_ilim = false;
|
|
}
|
|
|
|
if (split_ilim) {
|
|
ilim_ua /= 2;
|
|
pval.intval = ilim_ua;
|
|
rc = power_supply_set_property(chip->div2_cp_slave_psy,
|
|
POWER_SUPPLY_PROP_INPUT_CURRENT_MAX, &pval);
|
|
if (rc < 0)
|
|
dev_err(chip->dev, "Couldn't set CP slave ilim, rc=%d\n",
|
|
rc);
|
|
dev_dbg(chip->dev, "set CP slave ilim to %duA\n",
|
|
ilim_ua);
|
|
}
|
|
|
|
rc = smb1398_set_iin_ma(chip, ilim_ua / 1000);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Couldn't set CP master ilim, rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
dev_dbg(chip->dev, "set CP master ilim to %duA\n", ilim_ua);
|
|
vote(chip->div2_cp_disable_votable, ILIM_VOTER, false, 0);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void smb1398_destroy_votables(struct smb1398_chip *chip)
|
|
{
|
|
destroy_votable(chip->awake_votable);
|
|
destroy_votable(chip->div2_cp_disable_votable);
|
|
destroy_votable(chip->div2_cp_ilim_votable);
|
|
destroy_votable(chip->div2_cp_slave_disable_votable);
|
|
}
|
|
|
|
static int smb1398_div2_cp_create_votables(struct smb1398_chip *chip)
|
|
{
|
|
int rc;
|
|
|
|
chip->awake_votable = create_votable("SMB1398_AWAKE",
|
|
VOTE_SET_ANY, smb1398_awake_vote_cb, chip);
|
|
if (IS_ERR_OR_NULL(chip->awake_votable))
|
|
return PTR_ERR_OR_ZERO(chip->awake_votable);
|
|
|
|
chip->div2_cp_disable_votable = create_votable("CP_DISABLE",
|
|
VOTE_SET_ANY, smb1398_div2_cp_disable_vote_cb, chip);
|
|
if (IS_ERR_OR_NULL(chip->div2_cp_disable_votable)) {
|
|
rc = PTR_ERR_OR_ZERO(chip->div2_cp_disable_votable);
|
|
goto destroy;
|
|
}
|
|
|
|
chip->div2_cp_slave_disable_votable = create_votable("CP_SLAVE_DISABLE",
|
|
VOTE_SET_ANY, smb1398_div2_cp_slave_disable_vote_cb,
|
|
chip);
|
|
if (IS_ERR_OR_NULL(chip->div2_cp_slave_disable_votable)) {
|
|
rc = PTR_ERR_OR_ZERO(chip->div2_cp_slave_disable_votable);
|
|
goto destroy;
|
|
}
|
|
|
|
chip->div2_cp_ilim_votable = create_votable("CP_ILIM",
|
|
VOTE_MIN, smb1398_div2_cp_ilim_vote_cb, chip);
|
|
if (IS_ERR_OR_NULL(chip->div2_cp_ilim_votable)) {
|
|
rc = PTR_ERR_OR_ZERO(chip->div2_cp_ilim_votable);
|
|
goto destroy;
|
|
}
|
|
|
|
vote(chip->div2_cp_disable_votable, USER_VOTER, true, 0);
|
|
vote(chip->div2_cp_disable_votable, CUTOFF_SOC_VOTER,
|
|
is_cutoff_soc_reached(chip), 0);
|
|
|
|
/*
|
|
* In case SMB1398 probe happens after FCC value has been configured,
|
|
* update ilim vote to reflect FCC / 2 value, this is only applicable
|
|
* when SMB1398 is directly connected to VBAT.
|
|
*/
|
|
if (is_psy_voter_available(chip) &&
|
|
(chip->pl_output_mode != POWER_SUPPLY_PL_OUTPUT_VPH))
|
|
vote(chip->div2_cp_ilim_votable, FCC_VOTER, true,
|
|
get_effective_result(chip->fcc_votable) / 2);
|
|
return 0;
|
|
destroy:
|
|
smb1398_destroy_votables(chip);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static irqreturn_t default_irq_handler(int irq, void *data)
|
|
{
|
|
struct smb1398_chip *chip = data;
|
|
int rc, i;
|
|
bool switcher_en = chip->switcher_en;
|
|
|
|
for (i = 0; i < NUM_IRQS; i++) {
|
|
if (irq == chip->irqs[i]) {
|
|
dev_dbg(chip->dev, "IRQ %s triggered\n",
|
|
smb_irqs[i].name);
|
|
chip->div2_irq_status |= 1 << smb_irqs[i].shift;
|
|
}
|
|
}
|
|
|
|
rc = smb1398_get_enable_status(chip);
|
|
if (rc < 0)
|
|
goto out;
|
|
|
|
if (chip->switcher_en != switcher_en)
|
|
if (chip->fcc_votable)
|
|
rerun_election(chip->fcc_votable);
|
|
out:
|
|
if (chip->div2_cp_master_psy)
|
|
power_supply_changed(chip->div2_cp_master_psy);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static const struct smb_irq smb_irqs[] = {
|
|
/* useful IRQs from perph0 */
|
|
[TEMP_SHDWN_IRQ] = {
|
|
.name = "temp-shdwn",
|
|
.handler = default_irq_handler,
|
|
.wake = true,
|
|
.shift = 2,
|
|
},
|
|
[DIV2_IREV_LATCH_IRQ] = {
|
|
.name = "div2-irev",
|
|
.handler = default_irq_handler,
|
|
.wake = true,
|
|
.shift = 3,
|
|
},
|
|
[USB_IN_UVLO_IRQ] = {
|
|
.name = "usbin-uv",
|
|
.handler = default_irq_handler,
|
|
.wake = true,
|
|
.shift = 1,
|
|
},
|
|
[USB_IN_OVLO_IRQ] = {
|
|
.name = "usbin-ov",
|
|
.handler = default_irq_handler,
|
|
.wake = true,
|
|
.shift = 1,
|
|
},
|
|
/* useful IRQs from perph1 */
|
|
[DIV2_ILIM_IRQ] = {
|
|
.name = "div2-ilim",
|
|
.handler = default_irq_handler,
|
|
.wake = true,
|
|
.shift = 6,
|
|
},
|
|
[DIV2_WIN_UV_IRQ] = {
|
|
.name = "div2-win-uv",
|
|
.handler = default_irq_handler,
|
|
.wake = true,
|
|
.shift = 0,
|
|
},
|
|
[DIV2_WIN_OV_IRQ] = {
|
|
.name = "div2-win-ov",
|
|
.handler = default_irq_handler,
|
|
.wake = true,
|
|
.shift = 0,
|
|
},
|
|
};
|
|
|
|
static int smb1398_get_irq_index_byname(const char *irq_name)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(smb_irqs); i++) {
|
|
if (smb_irqs[i].name != NULL)
|
|
if (strcmp(smb_irqs[i].name, irq_name) == 0)
|
|
return i;
|
|
}
|
|
|
|
return -ENOENT;
|
|
}
|
|
|
|
static int smb1398_request_interrupt(struct smb1398_chip *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) {
|
|
dev_err(chip->dev, "Couldn't get irq %s failed\n", irq_name);
|
|
return irq;
|
|
}
|
|
|
|
irq_index = smb1398_get_irq_index_byname(irq_name);
|
|
if (irq_index < 0) {
|
|
dev_err(chip->dev, "%s IRQ is not defined\n", irq_name);
|
|
return irq_index;
|
|
}
|
|
|
|
if (!smb_irqs[irq_index].handler)
|
|
return 0;
|
|
|
|
/*
|
|
* Do not register temp-shdwn interrupt as it may misfire on toggling
|
|
* the SMB_EN input.
|
|
*/
|
|
if (irq_index == TEMP_SHDWN_IRQ)
|
|
return 0;
|
|
|
|
rc = devm_request_threaded_irq(chip->dev, irq, NULL,
|
|
smb_irqs[irq_index].handler,
|
|
IRQF_ONESHOT, irq_name, chip);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Request interrupt for %s failed, rc=%d\n",
|
|
irq_name, rc);
|
|
return rc;
|
|
}
|
|
|
|
chip->irqs[irq_index] = irq;
|
|
if (smb_irqs[irq_index].wake)
|
|
enable_irq_wake(irq);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int smb1398_request_interrupts(struct smb1398_chip *chip)
|
|
{
|
|
struct device_node *node = chip->dev->of_node;
|
|
int rc = 0;
|
|
const char *name;
|
|
struct property *prop;
|
|
|
|
of_property_for_each_string(node, "interrupt-names", prop, name) {
|
|
rc = smb1398_request_interrupt(chip, node, name);
|
|
if (rc < 0)
|
|
return rc;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#define ILIM_NR 10
|
|
#define ILIM_DR 8
|
|
#define ILIM_FACTOR(ilim) ((ilim * ILIM_NR) / ILIM_DR)
|
|
|
|
static void smb1398_configure_ilim(struct smb1398_chip *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->div2_cp_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)) {
|
|
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->div2_cp_ilim_votable, ICL_VOTER,
|
|
true, pval.intval);
|
|
}
|
|
}
|
|
|
|
static void smb1398_status_change_work(struct work_struct *work)
|
|
{
|
|
struct smb1398_chip *chip = container_of(work,
|
|
struct smb1398_chip, status_change_work);
|
|
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 CUTOFF_SOC_VOTER.
|
|
*/
|
|
if (!is_cutoff_soc_reached(chip))
|
|
vote(chip->div2_cp_disable_votable, CUTOFF_SOC_VOTER, false, 0);
|
|
|
|
rc = power_supply_get_property(chip->usb_psy,
|
|
POWER_SUPPLY_PROP_SMB_EN_MODE, &pval);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Couldn't get SMB_EN_MODE, rc=%d\n", rc);
|
|
goto out;
|
|
}
|
|
|
|
/* If no CP charging started */
|
|
if (pval.intval != POWER_SUPPLY_CHARGER_SEC_CP) {
|
|
chip->cutoff_soc_checked = false;
|
|
vote(chip->div2_cp_slave_disable_votable, SRC_VOTER, true, 0);
|
|
vote(chip->div2_cp_slave_disable_votable,
|
|
TAPER_VOTER, false, 0);
|
|
vote(chip->div2_cp_disable_votable, TAPER_VOTER, false, 0);
|
|
vote(chip->div2_cp_disable_votable, SRC_VOTER, true, 0);
|
|
vote(chip->div2_cp_disable_votable, CUTOFF_SOC_VOTER, true, 0);
|
|
vote(chip->fcc_votable, CP_VOTER, false, 0);
|
|
vote(chip->div2_cp_ilim_votable, CC_MODE_VOTER, false, 0);
|
|
vote_override(chip->usb_icl_votable,
|
|
TAPER_MAIN_ICL_LIMIT_VOTER, false, 0);
|
|
goto out;
|
|
}
|
|
|
|
rc = power_supply_get_property(chip->usb_psy,
|
|
POWER_SUPPLY_PROP_SMB_EN_REASON, &pval);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Couldn't get SMB_EN_REASON failed, rc=%d\n",
|
|
rc);
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* Slave SMB1398 is not required for the power-rating of QC3
|
|
*/
|
|
if (pval.intval != POWER_SUPPLY_CP_HVDCP3)
|
|
vote(chip->div2_cp_slave_disable_votable, SRC_VOTER, false, 0);
|
|
|
|
if (pval.intval == POWER_SUPPLY_CP_NONE) {
|
|
vote(chip->div2_cp_disable_votable, SRC_VOTER, true, 0);
|
|
goto out;
|
|
}
|
|
|
|
vote(chip->div2_cp_disable_votable, SRC_VOTER, false, 0);
|
|
if (!chip->cutoff_soc_checked) {
|
|
vote(chip->div2_cp_disable_votable, CUTOFF_SOC_VOTER,
|
|
is_cutoff_soc_reached(chip), 0);
|
|
chip->cutoff_soc_checked = true;
|
|
}
|
|
|
|
if (pval.intval == POWER_SUPPLY_CP_WIRELESS) {
|
|
/*
|
|
* Get the max output current from the wireless PSY
|
|
* and set the DIV2 CP ilim accordingly
|
|
*/
|
|
vote(chip->div2_cp_ilim_votable, ICL_VOTER, false, 0);
|
|
rc = power_supply_get_property(chip->dc_psy,
|
|
POWER_SUPPLY_PROP_CURRENT_MAX, &pval);
|
|
if (rc < 0)
|
|
dev_err(chip->dev, "Couldn't get DC CURRENT_MAX, rc=%d\n",
|
|
rc);
|
|
else
|
|
vote(chip->div2_cp_ilim_votable, WIRELESS_VOTER,
|
|
true, pval.intval);
|
|
} else {
|
|
vote(chip->div2_cp_ilim_votable, WIRELESS_VOTER, false, 0);
|
|
smb1398_configure_ilim(chip, pval.intval);
|
|
}
|
|
|
|
/*
|
|
* Remove CP 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->div2_cp_slave_disable_votable,
|
|
TAPER_VOTER, false, 0);
|
|
vote(chip->div2_cp_disable_votable, TAPER_VOTER, false, 0);
|
|
}
|
|
|
|
/*
|
|
* all votes that would result in disabling the charge pump have
|
|
* been cast; ensure the charge pump is still enabled before
|
|
* continuing.
|
|
*/
|
|
if (get_effective_result(chip->div2_cp_disable_votable))
|
|
goto out;
|
|
|
|
rc = power_supply_get_property(chip->batt_psy,
|
|
POWER_SUPPLY_PROP_CHARGE_TYPE, &pval);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Couldn't get CHARGE_TYPE, rc=%d\n",
|
|
rc);
|
|
goto out;
|
|
}
|
|
|
|
if (pval.intval == POWER_SUPPLY_CHARGE_TYPE_TAPER) {
|
|
if (!chip->taper_work_running) {
|
|
chip->taper_work_running = true;
|
|
vote(chip->awake_votable, TAPER_VOTER, true, 0);
|
|
queue_work(system_long_wq, &chip->taper_work);
|
|
}
|
|
}
|
|
out:
|
|
pm_relax(chip->dev);
|
|
chip->status_change_running = false;
|
|
}
|
|
|
|
static int smb1398_notifier_cb(struct notifier_block *nb,
|
|
unsigned long event, void *data)
|
|
{
|
|
struct smb1398_chip *chip = container_of(nb, struct smb1398_chip, nb);
|
|
struct power_supply *psy = (struct power_supply *)data;
|
|
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);
|
|
}
|
|
|
|
return NOTIFY_OK;
|
|
}
|
|
|
|
static void smb1398_taper_work(struct work_struct *work)
|
|
{
|
|
struct smb1398_chip *chip = container_of(work,
|
|
struct smb1398_chip, taper_work);
|
|
union power_supply_propval pval = {0};
|
|
int rc, fcc_ua, fv_uv, stepper_ua, main_fcc_ua = 0, min_ilim_ua;
|
|
bool slave_en;
|
|
|
|
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);
|
|
|
|
min_ilim_ua = smb1398_div2_cp_get_min_icl(chip);
|
|
|
|
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) {
|
|
dev_err(chip->dev, "Couldn't get CHARGE_TYPE, rc=%d\n",
|
|
rc);
|
|
goto out;
|
|
}
|
|
|
|
fv_uv = get_effective_result(chip->fv_votable);
|
|
if (fv_uv > chip->taper_entry_fv) {
|
|
dev_dbg(chip->dev, "Float voltage increased (%d-->%d)uV, exit!\n",
|
|
chip->taper_entry_fv, fv_uv);
|
|
vote(chip->div2_cp_disable_votable, TAPER_VOTER,
|
|
false, 0);
|
|
goto out;
|
|
} else {
|
|
chip->taper_entry_fv = fv_uv;
|
|
}
|
|
|
|
if (pval.intval == POWER_SUPPLY_CHARGE_TYPE_TAPER) {
|
|
stepper_ua = is_adapter_in_cc_mode(chip) ?
|
|
TAPER_STEPPER_UA_IN_CC_MODE :
|
|
TAPER_STEPPER_UA_DEFAULT;
|
|
fcc_ua = get_effective_result(chip->fcc_votable)
|
|
- stepper_ua;
|
|
dev_dbg(chip->dev, "Taper stepper reduce FCC to %d\n",
|
|
fcc_ua);
|
|
vote(chip->fcc_votable, CP_VOTER, true, fcc_ua);
|
|
fcc_ua -= main_fcc_ua;
|
|
/*
|
|
* If total FCC is less than the minimum ILIM to
|
|
* keep CP master and slave online, disable CP.
|
|
*/
|
|
if (fcc_ua < (min_ilim_ua * 2)) {
|
|
vote(chip->div2_cp_disable_votable,
|
|
TAPER_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;
|
|
}
|
|
/*
|
|
* If total FCC is less than the minimum ILIM to keep
|
|
* slave CP online, disable slave, and set master CP
|
|
* ILIM to maximum to avoid ILIM IRQ storm.
|
|
*/
|
|
slave_en = !get_effective_result(
|
|
chip->div2_cp_slave_disable_votable);
|
|
if ((fcc_ua < chip->ilim_ua_disable_div2_cp_slave) &&
|
|
slave_en && is_cps_available(chip)) {
|
|
dev_dbg(chip->dev, "Disable slave CP in taper\n");
|
|
vote(chip->div2_cp_slave_disable_votable,
|
|
TAPER_VOTER, true, 0);
|
|
vote_override(chip->div2_cp_ilim_votable,
|
|
CC_MODE_VOTER,
|
|
is_adapter_in_cc_mode(chip),
|
|
DIV2_MAX_ILIM_DUAL_CP_UA);
|
|
|
|
if (chip->usb_icl_votable)
|
|
vote_override(chip->usb_icl_votable,
|
|
TAPER_MAIN_ICL_LIMIT_VOTER,
|
|
is_adapter_in_cc_mode(chip),
|
|
chip->cc_mode_taper_main_icl_ua);
|
|
}
|
|
} else {
|
|
dev_dbg(chip->dev, "Not in taper, exit!\n");
|
|
}
|
|
msleep(500);
|
|
}
|
|
out:
|
|
dev_dbg(chip->dev, "exit taper work\n");
|
|
vote(chip->fcc_votable, CP_VOTER, false, 0);
|
|
vote(chip->awake_votable, TAPER_VOTER, false, 0);
|
|
chip->taper_work_running = false;
|
|
}
|
|
|
|
static int smb1398_update_ovp(struct smb1398_chip *chip)
|
|
{
|
|
int rc = 0;
|
|
u8 reg = 0;
|
|
|
|
rc = smb1398_read(chip, PERPH0_REVISION4, ®);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev,
|
|
"Couldn't read PERPH0_REVISION4 rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
/* Ignore for REV2 and below */
|
|
if (reg <= 2)
|
|
return 0;
|
|
|
|
rc = smb1398_masked_write(chip, PERPH0_SSUPPLY_CFG0_REG,
|
|
EN_HV_OV_OPTION2_BIT | EN_MV_OV_OPTION2_BIT,
|
|
EN_HV_OV_OPTION2_BIT);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev,
|
|
"Couldn't set PERPH0_SSUPPLY_CFG0_REG rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
rc = smb1398_masked_write(chip, PERPH1_LOCK_SPARE_REG,
|
|
CFG_LOCK_SPARE1_MASK,
|
|
OVP_14V << CFG_LOCK_SPARE1_SHIFT);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev,
|
|
"Couldn't set PERPH1_LOCK_SPARE_REG rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int smb1398_div2_cp_hw_init(struct smb1398_chip *chip)
|
|
{
|
|
int rc = 0;
|
|
|
|
rc = smb1398_update_ovp(chip);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Couldn't update OVP threshold rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
/* Configure window (Vin/2 - Vout) OV level to 500mV */
|
|
rc = smb1398_masked_write(chip, DIV2_PROTECTION_REG,
|
|
DIV2_WIN_OV_SEL_MASK, WIN_OV_500_MV);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Couldn't set WIN_OV_500_MV rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
/* Configure window (Vin/2 - Vout) UV level to 10mV */
|
|
rc = smb1398_masked_write(chip, NOLOCK_SPARE_REG,
|
|
DIV2_WIN_UV_SEL_BIT, 0);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Couldn't set WIN_UV_10_MV rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
/* Configure master TEMP pin to output Vtemp signal by default */
|
|
rc = smb1398_masked_write(chip, SSUPLY_TEMP_CTRL_REG,
|
|
SEL_OUT_TEMP_MAX_MASK, SEL_OUT_VTEMP);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Couldn't set SSUPLY_TEMP_CTRL_REG, rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
/* Configure to use Vtemp signal */
|
|
rc = smb1398_masked_write(chip, PERPH0_MISC_CFG2_REG,
|
|
CFG_TEMP_PIN_ITEMP, 0);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Couldn't set PERPH0_MISC_CFG2_REG, rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
/* Configure IREV threshold to 200mA */
|
|
rc = smb1398_masked_write(chip, PERPH0_DIV2_REF_CFG,
|
|
CFG_IREV_REF_BIT, 0);
|
|
if (rc < 0) {
|
|
pr_err("Couldn't configure IREV threshold rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
/* Initial configuration needed before enabling DIV2_CP operations */
|
|
rc = smb1398_masked_write(chip, MISC_DIV2_3LVL_CTRL_REG,
|
|
MISC_DIV2_3LVL_CTRL_MASK, 0x04);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "set EN_DIV2_CP failed, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
rc = smb1398_masked_write(chip, MISC_CFG1_REG, MISC_CFG1_MASK, 0x02);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "set OP_MODE_COMBO failed, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
/* Do not disable FP_FET during IREV conditions */
|
|
rc = smb1398_masked_write(chip, MISC_CFG0_REG, CFG_DIS_FPF_IREV_BIT, 0);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Couldn't set CFG_DIS_FPF_IREV_BIT, rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
/* switcher enable controlled by register */
|
|
rc = smb1398_masked_write(chip, MISC_CFG0_REG,
|
|
SW_EN_SWITCHER_BIT, SW_EN_SWITCHER_BIT);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Couldn't set CFG_EN_SOURCE, rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int smb1398_div2_cp_parse_dt(struct smb1398_chip *chip)
|
|
{
|
|
int rc = 0;
|
|
|
|
rc = of_property_match_string(chip->dev->of_node,
|
|
"io-channel-names", "die_temp");
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "die_temp IIO channel not found\n");
|
|
return rc;
|
|
}
|
|
|
|
chip->die_temp_chan = devm_iio_channel_get(chip->dev,
|
|
"die_temp");
|
|
if (IS_ERR(chip->die_temp_chan)) {
|
|
rc = PTR_ERR(chip->die_temp_chan);
|
|
if (rc != -EPROBE_DEFER)
|
|
dev_err(chip->dev, "Couldn't get die_temp_chan, rc=%d\n",
|
|
rc);
|
|
chip->die_temp_chan = NULL;
|
|
return rc;
|
|
}
|
|
|
|
chip->div2_cp_min_ilim_ua = 750000;
|
|
of_property_read_u32(chip->dev->of_node, "qcom,div2-cp-min-ilim-ua",
|
|
&chip->div2_cp_min_ilim_ua);
|
|
|
|
chip->max_cutoff_soc = 85;
|
|
of_property_read_u32(chip->dev->of_node, "qcom,max-cutoff-soc",
|
|
&chip->max_cutoff_soc);
|
|
|
|
chip->ilim_ua_disable_div2_cp_slave = chip->div2_cp_min_ilim_ua * 3;
|
|
|
|
of_property_read_u32(chip->dev->of_node, "qcom,ilim-ua-disable-slave",
|
|
&chip->ilim_ua_disable_div2_cp_slave);
|
|
|
|
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);
|
|
|
|
/* 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);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int smb1398_div2_cp_master_probe(struct smb1398_chip *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("Couldn't get revid node\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 = smb1398_div2_cp_parse_dt(chip);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Couldn't parse devicetree, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
INIT_WORK(&chip->status_change_work, &smb1398_status_change_work);
|
|
INIT_WORK(&chip->taper_work, &smb1398_taper_work);
|
|
|
|
rc = smb1398_div2_cp_hw_init(chip);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "div2_cp_hw_init failed, rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
rc = smb1398_div2_cp_create_votables(chip);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "smb1398_div2_cp_create_votables failed, rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
rc = smb1398_init_div2_cp_master_psy(chip);
|
|
if (rc > 0) {
|
|
dev_err(chip->dev, "smb1398_init_div2_cp_master_psy failed, rc=%d\n",
|
|
rc);
|
|
goto destroy_votable;
|
|
}
|
|
|
|
chip->nb.notifier_call = smb1398_notifier_cb;
|
|
rc = power_supply_reg_notifier(&chip->nb);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "register notifier_cb failed, rc=%d\n", rc);
|
|
goto destroy_votable;
|
|
}
|
|
|
|
rc = smb1398_request_interrupts(chip);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "smb1398_request_interrupts failed, rc=%d\n",
|
|
rc);
|
|
goto destroy_votable;
|
|
}
|
|
|
|
rc = device_init_wakeup(chip->dev, true);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "init wakeup failed for div2_cp_master device, rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
dev_dbg(chip->dev, "smb1398 DIV2_CP master is probed successfully\n");
|
|
|
|
return 0;
|
|
destroy_votable:
|
|
mutex_destroy(&chip->die_chan_lock);
|
|
smb1398_destroy_votables(chip);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static enum power_supply_property div2_cp_slave_props[] = {
|
|
POWER_SUPPLY_PROP_CP_ENABLE,
|
|
POWER_SUPPLY_PROP_INPUT_CURRENT_MAX,
|
|
POWER_SUPPLY_PROP_CURRENT_CAPABILITY,
|
|
};
|
|
|
|
static int div2_cp_slave_get_prop(struct power_supply *psy,
|
|
enum power_supply_property prop,
|
|
union power_supply_propval *pval)
|
|
{
|
|
struct smb1398_chip *chip = power_supply_get_drvdata(psy);
|
|
|
|
switch (prop) {
|
|
case POWER_SUPPLY_PROP_CP_ENABLE:
|
|
pval->intval = chip->switcher_en;
|
|
break;
|
|
case POWER_SUPPLY_PROP_INPUT_CURRENT_MAX:
|
|
pval->intval = 0;
|
|
if (!chip->div2_cp_ilim_votable)
|
|
chip->div2_cp_ilim_votable = find_votable("CP_ILIM");
|
|
if (chip->div2_cp_ilim_votable)
|
|
pval->intval = get_effective_result_locked(
|
|
chip->div2_cp_ilim_votable);
|
|
break;
|
|
case POWER_SUPPLY_PROP_CURRENT_CAPABILITY:
|
|
pval->intval = (int)chip->current_capability;
|
|
break;
|
|
default:
|
|
dev_err(chip->dev, "read div2_cp_slave property %d is not supported\n",
|
|
prop);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int div2_cp_slave_set_prop(struct power_supply *psy,
|
|
enum power_supply_property prop,
|
|
const union power_supply_propval *pval)
|
|
{
|
|
struct smb1398_chip *chip = power_supply_get_drvdata(psy);
|
|
int ilim_ma, rc = 0;
|
|
enum isns_mode mode;
|
|
|
|
switch (prop) {
|
|
case POWER_SUPPLY_PROP_CP_ENABLE:
|
|
rc = smb1398_div2_cp_switcher_en(chip, !!pval->intval);
|
|
break;
|
|
case POWER_SUPPLY_PROP_INPUT_CURRENT_MAX:
|
|
ilim_ma = pval->intval / 1000;
|
|
rc = smb1398_set_iin_ma(chip, ilim_ma);
|
|
break;
|
|
case POWER_SUPPLY_PROP_CURRENT_CAPABILITY:
|
|
mode = (enum isns_mode)pval->intval;
|
|
rc = smb1398_div2_cp_isns_mode_control(chip, mode);
|
|
if (rc < 0)
|
|
return rc;
|
|
chip->current_capability = mode;
|
|
break;
|
|
default:
|
|
dev_err(chip->dev, "write div2_cp_slave property %d is not supported\n",
|
|
prop);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int div2_cp_slave_is_writeable(struct power_supply *psy,
|
|
enum power_supply_property prop)
|
|
{
|
|
switch (prop) {
|
|
case POWER_SUPPLY_PROP_CP_ENABLE:
|
|
case POWER_SUPPLY_PROP_INPUT_CURRENT_MAX:
|
|
case POWER_SUPPLY_PROP_CURRENT_CAPABILITY:
|
|
return 1;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct power_supply_desc div2_cps_psy_desc = {
|
|
.name = "cp_slave",
|
|
.type = POWER_SUPPLY_TYPE_PARALLEL,
|
|
.properties = div2_cp_slave_props,
|
|
.num_properties = ARRAY_SIZE(div2_cp_slave_props),
|
|
.get_property = div2_cp_slave_get_prop,
|
|
.set_property = div2_cp_slave_set_prop,
|
|
.property_is_writeable = div2_cp_slave_is_writeable,
|
|
};
|
|
|
|
static int smb1398_init_div2_cp_slave_psy(struct smb1398_chip *chip)
|
|
{
|
|
int rc = 0;
|
|
struct power_supply_config cps_cfg = {};
|
|
|
|
cps_cfg.drv_data = chip;
|
|
cps_cfg.of_node = chip->dev->of_node;
|
|
|
|
chip->div2_cp_slave_psy = devm_power_supply_register(chip->dev,
|
|
&div2_cps_psy_desc, &cps_cfg);
|
|
if (IS_ERR(chip->div2_cp_slave_psy)) {
|
|
rc = PTR_ERR(chip->div2_cp_slave_psy);
|
|
dev_err(chip->dev, "register div2_cp_slave_psy failed, rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int smb1398_div2_cp_slave_probe(struct smb1398_chip *chip)
|
|
{
|
|
int rc;
|
|
u8 status;
|
|
|
|
rc = smb1398_update_ovp(chip);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Couldn't update OVP threshold rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
rc = smb1398_read(chip, MODE_STATUS_REG, &status);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Couldn't read slave MODE_STATUS_REG, rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
/* Configure window (Vin/2 - Vout) UV level to 10mV */
|
|
rc = smb1398_masked_write(chip, NOLOCK_SPARE_REG,
|
|
DIV2_WIN_UV_SEL_BIT, 0);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Couldn't set WIN_UV_10_MV rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
* Disable slave WIN_UV detection, otherwise slave might not be
|
|
* enabled due to WIN_UV until master drawing very high current.
|
|
*/
|
|
rc = smb1398_masked_write(chip, PERPH0_CFG_SDCDC_REG, EN_WIN_UV_BIT, 0);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Couldn't disable DIV2_CP WIN_UV, rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
/* Configure slave TEMP pin to HIGH-Z by default */
|
|
rc = smb1398_masked_write(chip, SSUPLY_TEMP_CTRL_REG,
|
|
SEL_OUT_TEMP_MAX_MASK, SEL_OUT_HIGHZ);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Couldn't set SSUPLY_TEMP_CTRL_REG, rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
/* Configure to use Vtemp */
|
|
rc = smb1398_masked_write(chip, PERPH0_MISC_CFG2_REG,
|
|
CFG_TEMP_PIN_ITEMP, 0);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Couldn't set PERPH0_MISC_CFG2_REG, rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
/* switcher enable controlled by register */
|
|
rc = smb1398_masked_write(chip, MISC_CFG0_REG,
|
|
SW_EN_SWITCHER_BIT, SW_EN_SWITCHER_BIT);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Couldn't set MISC_CFG0_REG, rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
/* Enable slave clock on its own */
|
|
rc = smb1398_masked_write(chip, NOLOCK_SPARE_REG,
|
|
EN_SLAVE_OWN_FREQ_BIT, EN_SLAVE_OWN_FREQ_BIT);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Couldn't enable slave clock, rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
rc = smb1398_init_div2_cp_slave_psy(chip);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Initial div2_cp_slave_psy failed, rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
dev_dbg(chip->dev, "smb1398 DIV2_CP slave probe successfully\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int smb1398_pre_regulator_iout_vote_cb(struct votable *votable,
|
|
void *data, int iout_ua, const char *client)
|
|
{
|
|
struct smb1398_chip *chip = (struct smb1398_chip *)data;
|
|
int rc = 0;
|
|
|
|
if (chip->in_suspend)
|
|
return -EAGAIN;
|
|
|
|
if (!client)
|
|
return -EINVAL;
|
|
|
|
iout_ua = min(iout_ua, MAX_IOUT_UA);
|
|
rc = smb1398_set_ichg_ma(chip, do_div(iout_ua, 1000));
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
dev_dbg(chip->dev, "set iout %duA\n", iout_ua);
|
|
return 0;
|
|
}
|
|
|
|
static int smb1398_pre_regulator_vout_vote_cb(struct votable *votable,
|
|
void *data, int vout_uv, const char *client)
|
|
{
|
|
struct smb1398_chip *chip = (struct smb1398_chip *)data;
|
|
int rc = 0;
|
|
|
|
if (chip->in_suspend)
|
|
return -EAGAIN;
|
|
|
|
if (!client)
|
|
return -EINVAL;
|
|
|
|
vout_uv = min(vout_uv, MAX_1S_VOUT_UV);
|
|
rc = smb1398_set_1s_vout_mv(chip, vout_uv / 1000);
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
dev_dbg(chip->dev, "set vout %duV\n", vout_uv);
|
|
return 0;
|
|
}
|
|
|
|
static int smb1398_create_pre_regulator_votables(struct smb1398_chip *chip)
|
|
{
|
|
chip->pre_regulator_iout_votable = create_votable("PRE_REGULATOR_IOUT",
|
|
VOTE_MIN, smb1398_pre_regulator_iout_vote_cb, chip);
|
|
if (IS_ERR_OR_NULL(chip->pre_regulator_iout_votable))
|
|
return PTR_ERR_OR_ZERO(chip->pre_regulator_iout_votable);
|
|
|
|
chip->pre_regulator_vout_votable = create_votable("PRE_REGULATOR_VOUT",
|
|
VOTE_MIN, smb1398_pre_regulator_vout_vote_cb, chip);
|
|
|
|
if (IS_ERR_OR_NULL(chip->pre_regulator_vout_votable)) {
|
|
destroy_votable(chip->pre_regulator_iout_votable);
|
|
return PTR_ERR_OR_ZERO(chip->pre_regulator_vout_votable);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static enum power_supply_property pre_regulator_props[] = {
|
|
POWER_SUPPLY_PROP_INPUT_CURRENT_MAX,
|
|
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
|
|
POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX,
|
|
};
|
|
|
|
static int pre_regulator_get_prop(struct power_supply *psy,
|
|
enum power_supply_property prop,
|
|
union power_supply_propval *pval)
|
|
{
|
|
struct smb1398_chip *chip = power_supply_get_drvdata(psy);
|
|
int rc, iin_ma, iout_ma, vout_mv;
|
|
|
|
switch (prop) {
|
|
case POWER_SUPPLY_PROP_INPUT_CURRENT_MAX:
|
|
rc = smb1398_get_iin_ma(chip, &iin_ma);
|
|
if (rc < 0)
|
|
return rc;
|
|
pval->intval = iin_ma * 1000;
|
|
break;
|
|
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
|
|
if (chip->pre_regulator_iout_votable) {
|
|
pval->intval = get_effective_result(
|
|
chip->pre_regulator_iout_votable);
|
|
} else {
|
|
rc = smb1398_get_ichg_ma(chip, &iout_ma);
|
|
if (rc < 0)
|
|
return rc;
|
|
pval->intval = iout_ma * 1000;
|
|
}
|
|
break;
|
|
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
|
|
if (chip->pre_regulator_vout_votable) {
|
|
pval->intval = get_effective_result(
|
|
chip->pre_regulator_vout_votable);
|
|
} else {
|
|
rc = smb1398_get_1s_vout_mv(chip, &vout_mv);
|
|
if (rc < 0)
|
|
return rc;
|
|
pval->intval = vout_mv * 1000;
|
|
}
|
|
break;
|
|
default:
|
|
dev_err(chip->dev, "read pre_regulator property %d is not supported\n",
|
|
prop);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int pre_regulator_set_prop(struct power_supply *psy,
|
|
enum power_supply_property prop,
|
|
const union power_supply_propval *pval)
|
|
{
|
|
struct smb1398_chip *chip = power_supply_get_drvdata(psy);
|
|
int rc = 0;
|
|
|
|
switch (prop) {
|
|
case POWER_SUPPLY_PROP_INPUT_CURRENT_MAX:
|
|
rc = smb1398_set_iin_ma(chip, pval->intval / 1000);
|
|
if (rc < 0)
|
|
return rc;
|
|
break;
|
|
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
|
|
vote(chip->pre_regulator_iout_votable, CP_VOTER,
|
|
true, pval->intval);
|
|
break;
|
|
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
|
|
vote(chip->pre_regulator_vout_votable, CP_VOTER,
|
|
true, pval->intval);
|
|
break;
|
|
default:
|
|
dev_err(chip->dev, "write pre_regulator property %d is not supported\n",
|
|
prop);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int pre_regulator_is_writeable(struct power_supply *psy,
|
|
enum power_supply_property prop)
|
|
{
|
|
switch (prop) {
|
|
case POWER_SUPPLY_PROP_INPUT_CURRENT_MAX:
|
|
case POWER_SUPPLY_PROP_CURRENT_MAX:
|
|
case POWER_SUPPLY_PROP_VOLTAGE_MAX:
|
|
return 1;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct power_supply_desc pre_regulator_psy_desc = {
|
|
.name = "pre_regulator",
|
|
.type = POWER_SUPPLY_TYPE_WIRELESS,
|
|
.properties = pre_regulator_props,
|
|
.num_properties = ARRAY_SIZE(pre_regulator_props),
|
|
.get_property = pre_regulator_get_prop,
|
|
.set_property = pre_regulator_set_prop,
|
|
.property_is_writeable = pre_regulator_is_writeable,
|
|
};
|
|
|
|
static int smb1398_create_pre_regulator_psy(struct smb1398_chip *chip)
|
|
{
|
|
struct power_supply_config pre_regulator_psy_cfg = {};
|
|
int rc = 0;
|
|
|
|
pre_regulator_psy_cfg.drv_data = chip;
|
|
pre_regulator_psy_cfg.of_node = chip->dev->of_node;
|
|
|
|
chip->pre_regulator_psy = devm_power_supply_register(chip->dev,
|
|
&pre_regulator_psy_desc,
|
|
&pre_regulator_psy_cfg);
|
|
if (IS_ERR(chip->pre_regulator_psy)) {
|
|
rc = PTR_ERR(chip->pre_regulator_psy);
|
|
dev_err(chip->dev, "register pre_regulator psy failed, rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int smb1398_pre_regulator_probe(struct smb1398_chip *chip)
|
|
{
|
|
int rc = 0;
|
|
|
|
rc = smb1398_create_pre_regulator_votables(chip);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Create votable for pre_regulator failed, rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
rc = smb1398_create_pre_regulator_psy(chip);
|
|
if (rc < 0) {
|
|
dev_err(chip->dev, "Create pre-regulator failed, rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
static int smb1398_probe(struct platform_device *pdev)
|
|
{
|
|
struct smb1398_chip *chip;
|
|
int rc = 0;
|
|
|
|
chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL);
|
|
if (!chip)
|
|
return -ENOMEM;
|
|
|
|
chip->die_temp = -ENODATA;
|
|
chip->dev = &pdev->dev;
|
|
chip->regmap = dev_get_regmap(chip->dev->parent, NULL);
|
|
if (!chip->regmap) {
|
|
dev_err(chip->dev, "Get regmap failed\n");
|
|
return -EINVAL;
|
|
}
|
|
chip->disabled = true;
|
|
platform_set_drvdata(pdev, chip);
|
|
|
|
chip->div2_cp_role = (int)of_device_get_match_data(chip->dev);
|
|
switch (chip->div2_cp_role) {
|
|
case DIV2_CP_MASTER:
|
|
rc = smb1398_div2_cp_master_probe(chip);
|
|
break;
|
|
case DIV2_CP_SLAVE:
|
|
rc = smb1398_div2_cp_slave_probe(chip);
|
|
break;
|
|
case COMBO_PRE_REGULATOR:
|
|
rc = smb1398_pre_regulator_probe(chip);
|
|
break;
|
|
default:
|
|
dev_err(chip->dev, "Couldn't find a match role for %d\n",
|
|
chip->div2_cp_role);
|
|
goto cleanup;
|
|
}
|
|
|
|
if (rc < 0) {
|
|
if (rc != -EPROBE_DEFER)
|
|
dev_err(chip->dev, "Couldn't probe SMB1398 %s rc= %d\n",
|
|
!!chip->div2_cp_role ? "slave" : "master", rc);
|
|
goto cleanup;
|
|
}
|
|
|
|
return 0;
|
|
|
|
cleanup:
|
|
platform_set_drvdata(pdev, NULL);
|
|
return rc;
|
|
}
|
|
|
|
static int smb1398_remove(struct platform_device *pdev)
|
|
{
|
|
struct smb1398_chip *chip = platform_get_drvdata(pdev);
|
|
|
|
if (chip->div2_cp_role == DIV2_CP_MASTER) {
|
|
vote(chip->awake_votable, SHUTDOWN_VOTER, false, 0);
|
|
vote(chip->div2_cp_disable_votable, SHUTDOWN_VOTER, true, 0);
|
|
vote(chip->div2_cp_ilim_votable, SHUTDOWN_VOTER, true, 0);
|
|
cancel_work_sync(&chip->taper_work);
|
|
cancel_work_sync(&chip->status_change_work);
|
|
mutex_destroy(&chip->die_chan_lock);
|
|
smb1398_destroy_votables(chip);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int smb1398_suspend(struct device *dev)
|
|
{
|
|
struct smb1398_chip *chip = dev_get_drvdata(dev);
|
|
|
|
chip->in_suspend = true;
|
|
return 0;
|
|
}
|
|
|
|
static int smb1398_resume(struct device *dev)
|
|
{
|
|
struct smb1398_chip *chip = dev_get_drvdata(dev);
|
|
|
|
chip->in_suspend = false;
|
|
|
|
if (chip->div2_cp_role == DIV2_CP_MASTER) {
|
|
rerun_election(chip->div2_cp_ilim_votable);
|
|
rerun_election(chip->div2_cp_disable_votable);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void smb1398_shutdown(struct platform_device *pdev)
|
|
{
|
|
struct smb1398_chip *chip = platform_get_drvdata(pdev);
|
|
int rc;
|
|
|
|
power_supply_unreg_notifier(&chip->nb);
|
|
|
|
/* Disable SMB1398 */
|
|
rc = smb1398_div2_cp_switcher_en(chip, 0);
|
|
if (rc < 0)
|
|
dev_err(chip->dev, "Couldn't disable chip rc= %d\n", rc);
|
|
}
|
|
|
|
static const struct dev_pm_ops smb1398_pm_ops = {
|
|
.suspend = smb1398_suspend,
|
|
.resume = smb1398_resume,
|
|
};
|
|
|
|
static const struct of_device_id match_table[] = {
|
|
{ .compatible = "qcom,smb1396-div2-cp-master",
|
|
.data = (void *)DIV2_CP_MASTER,
|
|
},
|
|
{ .compatible = "qcom,smb1396-div2-cp-slave",
|
|
.data = (void *)DIV2_CP_SLAVE,
|
|
},
|
|
{ .compatible = "qcom,smb1398-pre-regulator",
|
|
.data = (void *)COMBO_PRE_REGULATOR,
|
|
},
|
|
{
|
|
},
|
|
};
|
|
|
|
static struct platform_driver smb1398_driver = {
|
|
.driver = {
|
|
.name = "qcom,smb1398-charger",
|
|
.pm = &smb1398_pm_ops,
|
|
.of_match_table = match_table,
|
|
},
|
|
.probe = smb1398_probe,
|
|
.remove = smb1398_remove,
|
|
.shutdown = smb1398_shutdown,
|
|
};
|
|
module_platform_driver(smb1398_driver);
|
|
|
|
MODULE_DESCRIPTION("SMB1398 charger driver");
|
|
MODULE_LICENSE("GPL v2");
|
|
|