| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * Copyright (c) 2020 The Linux Foundation. All rights reserved. |
| */ |
| |
| #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 PERPH0_DIV2_SLAVE 0x2652 |
| #define CFG_DIV2_SYNC_CLK_PHASE_90 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; |
| * set DIV2_CP switch phase-shift to 0 deg; |
| * read corresponding ADC channel in Kekaha; |
| * set DIV2_CP switch phase-shif back to 90 deg; |
| * 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 = smb1398_masked_write(chip, PERPH0_DIV2_SLAVE, |
| CFG_DIV2_SYNC_CLK_PHASE_90, 0); |
| if (rc < 0) { |
| dev_err(chip->dev, "Couldn't set PERPH0_DIV2_SLAVE, rc=%d\n", |
| rc); |
| goto unlock; |
| } |
| |
| /* Delay for the phase switch to take effect */ |
| msleep(20); |
| |
| 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_masked_write(chip, PERPH0_DIV2_SLAVE, |
| CFG_DIV2_SYNC_CLK_PHASE_90, CFG_DIV2_SYNC_CLK_PHASE_90); |
| if (rc < 0) { |
| dev_err(chip->dev, "Couldn't set PERPH0_DIV2_SLAVE, 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; |
| * set DIV2_CP switch phase-shift to 0 deg; |
| * read corresponding ADC channel in Kekaha; |
| * set DIV2_CP switch phase-shif back to 90 deg; |
| * 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 = smb1398_masked_write(chip, PERPH0_DIV2_SLAVE, |
| CFG_DIV2_SYNC_CLK_PHASE_90, 0); |
| if (rc < 0) { |
| dev_err(chip->dev, "Couldn't set PERPH0_DIV2_SLAVE, rc=%d\n", |
| rc); |
| goto unlock; |
| } |
| |
| /* Delay for the phase switch to take effect */ |
| msleep(20); |
| |
| 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_masked_write(chip, PERPH0_DIV2_SLAVE, |
| CFG_DIV2_SYNC_CLK_PHASE_90, CFG_DIV2_SYNC_CLK_PHASE_90); |
| if (rc < 0) { |
| dev_err(chip->dev, "Couldn't set PERPH0_DIV2_SLAVE, 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; |
| } |
| |
| #define DIV2_CP_MIN_ILIM_UA 1000000 |
| 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; |
| } |
| |
| of_property_read_u32(chip->dev->of_node, "qcom,div2-cp-min-ilim-ua", |
| &chip->div2_cp_min_ilim_ua); |
| /* |
| * Set minimum allowed ilim configuration to 1A for DIV2_CP |
| * operation. |
| */ |
| if (chip->div2_cp_min_ilim_ua < DIV2_CP_MIN_ILIM_UA) |
| chip->div2_cp_min_ilim_ua = 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"); |