diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index ecbaed99d16b..94bfb0980355 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -587,6 +587,30 @@ config QCOM_SPMI_ADC5 To compile this driver as a module, choose M here: the module will be called qcom-spmi-adc5. +config QCOM_TADC + tristate "Qualcomm Technologies, Inc. TADC driver" + depends on MFD_I2C_PMIC + help + Say yes here to support the Qualcomm Technologies, Inc. telemetry ADC. + The TADC provides battery temperature, skin temperature, + die temperature, battery voltage, battery current, input voltage, + input current, and OTG current. + +config QCOM_RRADC + tristate "Qualcomm Technologies Inc. PMIC Round robin ADC" + depends on SPMI + select REGMAP_SPMI + help + This is the PMIC Round Robin ADC driver. + + The driver supports multiple channels read used for telemetry + and supports clients to read batt_id, batt_therm, PMIC die + temperature, USB_IN and DC_IN voltage and current. + The RRADC is a 10-bit ADC. + + To compile this driver as a module, choose M here: the module will + be called qcom-rradc. + config QCOM_SPMI_IADC tristate "Qualcomm SPMI PMIC current ADC" depends on SPMI diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index e478b3065ecd..6164a317bad1 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -54,6 +54,8 @@ obj-$(CONFIG_MXS_LRADC_ADC) += mxs-lradc-adc.o obj-$(CONFIG_NAU7802) += nau7802.o obj-$(CONFIG_PALMAS_GPADC) += palmas_gpadc.o obj-$(CONFIG_QCOM_SPMI_ADC5) += qcom-spmi-adc5.o +obj-$(CONFIG_QCOM_TADC) += qcom-tadc.o +obj-$(CONFIG_QCOM_RRADC) += qcom-rradc.o obj-$(CONFIG_QCOM_SPMI_IADC) += qcom-spmi-iadc.o obj-$(CONFIG_QCOM_VADC_COMMON) += qcom-vadc-common.o obj-$(CONFIG_QCOM_SPMI_VADC) += qcom-spmi-vadc.o diff --git a/drivers/iio/adc/qcom-rradc.c b/drivers/iio/adc/qcom-rradc.c new file mode 100644 index 000000000000..81a61c855adc --- /dev/null +++ b/drivers/iio/adc/qcom-rradc.c @@ -0,0 +1,1223 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2016-2017, 2020, The Linux Foundation. All rights reserved. + */ + +#define pr_fmt(fmt) "RRADC: %s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define FG_ADC_RR_EN_CTL 0x46 +#define FG_ADC_RR_SKIN_TEMP_LSB 0x50 +#define FG_ADC_RR_SKIN_TEMP_MSB 0x51 +#define FG_ADC_RR_RR_ADC_CTL 0x52 +#define FG_ADC_RR_ADC_CTL_CONTINUOUS_SEL_MASK 0x8 +#define FG_ADC_RR_ADC_CTL_CONTINUOUS_SEL BIT(3) +#define FG_ADC_RR_ADC_LOG 0x53 +#define FG_ADC_RR_ADC_LOG_CLR_CTRL BIT(0) + +#define FG_ADC_RR_FAKE_BATT_LOW_LSB 0x58 +#define FG_ADC_RR_FAKE_BATT_LOW_MSB 0x59 +#define FG_ADC_RR_FAKE_BATT_HIGH_LSB 0x5A +#define FG_ADC_RR_FAKE_BATT_HIGH_MSB 0x5B + +#define FG_ADC_RR_BATT_ID_CTRL 0x60 +#define FG_ADC_RR_BATT_ID_CTRL_CHANNEL_CONV BIT(0) +#define FG_ADC_RR_BATT_ID_TRIGGER 0x61 +#define FG_ADC_RR_BATT_ID_TRIGGER_CTL BIT(0) +#define FG_ADC_RR_BATT_ID_STS 0x62 +#define FG_ADC_RR_BATT_ID_CFG 0x63 +#define FG_ADC_RR_BATT_ID_5_LSB 0x66 +#define FG_ADC_RR_BATT_ID_5_MSB 0x67 +#define FG_ADC_RR_BATT_ID_15_LSB 0x68 +#define FG_ADC_RR_BATT_ID_15_MSB 0x69 +#define FG_ADC_RR_BATT_ID_150_LSB 0x6A +#define FG_ADC_RR_BATT_ID_150_MSB 0x6B + +#define FG_ADC_RR_BATT_THERM_CTRL 0x70 +#define FG_ADC_RR_BATT_THERM_TRIGGER 0x71 +#define FG_ADC_RR_BATT_THERM_STS 0x72 +#define FG_ADC_RR_BATT_THERM_CFG 0x73 +#define FG_ADC_RR_BATT_THERM_LSB 0x74 +#define FG_ADC_RR_BATT_THERM_MSB 0x75 +#define FG_ADC_RR_BATT_THERM_FREQ 0x76 + +#define FG_ADC_RR_AUX_THERM_CTRL 0x80 +#define FG_ADC_RR_AUX_THERM_TRIGGER 0x81 +#define FG_ADC_RR_AUX_THERM_STS 0x82 +#define FG_ADC_RR_AUX_THERM_CFG 0x83 +#define FG_ADC_RR_AUX_THERM_LSB 0x84 +#define FG_ADC_RR_AUX_THERM_MSB 0x85 + +#define FG_ADC_RR_SKIN_HOT 0x86 +#define FG_ADC_RR_SKIN_TOO_HOT 0x87 + +#define FG_ADC_RR_AUX_THERM_C1 0x88 +#define FG_ADC_RR_AUX_THERM_C2 0x89 +#define FG_ADC_RR_AUX_THERM_C3 0x8A +#define FG_ADC_RR_AUX_THERM_HALF_RANGE 0x8B + +#define FG_ADC_RR_USB_IN_V_CTRL 0x90 +#define FG_ADC_RR_USB_IN_V_TRIGGER 0x91 +#define FG_ADC_RR_USB_IN_V_EVERY_CYCLE_MASK 0x80 +#define FG_ADC_RR_USB_IN_V_EVERY_CYCLE BIT(7) +#define FG_ADC_RR_USB_IN_V_STS 0x92 +#define FG_ADC_RR_USB_IN_V_LSB 0x94 +#define FG_ADC_RR_USB_IN_V_MSB 0x95 +#define FG_ADC_RR_USB_IN_I_CTRL 0x98 +#define FG_ADC_RR_USB_IN_I_TRIGGER 0x99 +#define FG_ADC_RR_USB_IN_I_STS 0x9A +#define FG_ADC_RR_USB_IN_I_LSB 0x9C +#define FG_ADC_RR_USB_IN_I_MSB 0x9D + +#define FG_ADC_RR_DC_IN_V_CTRL 0xA0 +#define FG_ADC_RR_DC_IN_V_TRIGGER 0xA1 +#define FG_ADC_RR_DC_IN_V_STS 0xA2 +#define FG_ADC_RR_DC_IN_V_LSB 0xA4 +#define FG_ADC_RR_DC_IN_V_MSB 0xA5 +#define FG_ADC_RR_DC_IN_I_CTRL 0xA8 +#define FG_ADC_RR_DC_IN_I_TRIGGER 0xA9 +#define FG_ADC_RR_DC_IN_I_STS 0xAA +#define FG_ADC_RR_DC_IN_I_LSB 0xAC +#define FG_ADC_RR_DC_IN_I_MSB 0xAD + +#define FG_ADC_RR_PMI_DIE_TEMP_CTRL 0xB0 +#define FG_ADC_RR_PMI_DIE_TEMP_TRIGGER 0xB1 +#define FG_ADC_RR_PMI_DIE_TEMP_STS 0xB2 +#define FG_ADC_RR_PMI_DIE_TEMP_CFG 0xB3 +#define FG_ADC_RR_PMI_DIE_TEMP_LSB 0xB4 +#define FG_ADC_RR_PMI_DIE_TEMP_MSB 0xB5 + +#define FG_ADC_RR_CHARGER_TEMP_CTRL 0xB8 +#define FG_ADC_RR_CHARGER_TEMP_TRIGGER 0xB9 +#define FG_ADC_RR_CHARGER_TEMP_STS 0xBA +#define FG_ADC_RR_CHARGER_TEMP_CFG 0xBB +#define FG_ADC_RR_CHARGER_TEMP_LSB 0xBC +#define FG_ADC_RR_CHARGER_TEMP_MSB 0xBD +#define FG_ADC_RR_CHARGER_HOT 0xBE +#define FG_ADC_RR_CHARGER_TOO_HOT 0xBF + +#define FG_ADC_RR_GPIO_CTRL 0xC0 +#define FG_ADC_RR_GPIO_TRIGGER 0xC1 +#define FG_ADC_RR_GPIO_STS 0xC2 +#define FG_ADC_RR_GPIO_LSB 0xC4 +#define FG_ADC_RR_GPIO_MSB 0xC5 + +#define FG_ADC_RR_ATEST_CTRL 0xC8 +#define FG_ADC_RR_ATEST_TRIGGER 0xC9 +#define FG_ADC_RR_ATEST_STS 0xCA +#define FG_ADC_RR_ATEST_LSB 0xCC +#define FG_ADC_RR_ATEST_MSB 0xCD +#define FG_ADC_RR_SEC_ACCESS 0xD0 + +#define FG_ADC_RR_PERPH_RESET_CTL2 0xD9 +#define FG_ADC_RR_PERPH_RESET_CTL3 0xDA +#define FG_ADC_RR_PERPH_RESET_CTL4 0xDB +#define FG_ADC_RR_INT_TEST1 0xE0 +#define FG_ADC_RR_INT_TEST_VAL 0xE1 + +#define FG_ADC_RR_TM_TRIGGER_CTRLS 0xE2 +#define FG_ADC_RR_TM_ADC_CTRLS 0xE3 +#define FG_ADC_RR_TM_CNL_CTRL 0xE4 +#define FG_ADC_RR_TM_BATT_ID_CTRL 0xE5 +#define FG_ADC_RR_TM_THERM_CTRL 0xE6 +#define FG_ADC_RR_TM_CONV_STS 0xE7 +#define FG_ADC_RR_TM_ADC_READ_LSB 0xE8 +#define FG_ADC_RR_TM_ADC_READ_MSB 0xE9 +#define FG_ADC_RR_TM_ATEST_MUX_1 0xEA +#define FG_ADC_RR_TM_ATEST_MUX_2 0xEB +#define FG_ADC_RR_TM_REFERENCES 0xED +#define FG_ADC_RR_TM_MISC_CTL 0xEE +#define FG_ADC_RR_TM_RR_CTRL 0xEF + +#define FG_ADC_RR_BATT_ID_5_MA 5 +#define FG_ADC_RR_BATT_ID_15_MA 15 +#define FG_ADC_RR_BATT_ID_150_MA 150 +#define FG_ADC_RR_BATT_ID_RANGE 820 + +#define FG_ADC_BITS 10 +#define FG_MAX_ADC_READINGS (1 << FG_ADC_BITS) +#define FG_ADC_RR_FS_VOLTAGE_MV 2500 + +/* BATT_THERM 0.25K/LSB */ +#define FG_ADC_RR_BATT_THERM_LSB_K 4 + +#define FG_ADC_RR_TEMP_FS_VOLTAGE_NUM 5000000 +#define FG_ADC_RR_TEMP_FS_VOLTAGE_DEN 3 +#define FG_ADC_RR_DIE_TEMP_OFFSET 601400 +#define FG_ADC_RR_DIE_TEMP_SLOPE 2 +#define FG_ADC_RR_DIE_TEMP_OFFSET_MILLI_DEGC 25000 + +#define FG_ADC_RR_CHG_TEMP_GF_OFFSET_UV 1303168 +#define FG_ADC_RR_CHG_TEMP_GF_SLOPE_UV_PER_C 3784 +#define FG_ADC_RR_CHG_TEMP_SMIC_OFFSET_UV 1338433 +#define FG_ADC_RR_CHG_TEMP_SMIC_SLOPE_UV_PER_C 3655 +#define FG_ADC_RR_CHG_TEMP_660_GF_OFFSET_UV 1309001 +#define FG_RR_CHG_TEMP_660_GF_SLOPE_UV_PER_C 3403 +#define FG_ADC_RR_CHG_TEMP_660_SMIC_OFFSET_UV 1295898 +#define FG_RR_CHG_TEMP_660_SMIC_SLOPE_UV_PER_C 3596 +#define FG_ADC_RR_CHG_TEMP_660_MGNA_OFFSET_UV 1314779 +#define FG_RR_CHG_TEMP_660_MGNA_SLOPE_UV_PER_C 3496 +#define FG_ADC_RR_CHG_TEMP_OFFSET_MILLI_DEGC 25000 +#define FG_ADC_RR_CHG_THRESHOLD_SCALE 4 + +#define FG_ADC_RR_VOLT_INPUT_FACTOR 8 +#define FG_ADC_RR_CURR_INPUT_FACTOR 2000 +#define FG_ADC_RR_CURR_USBIN_INPUT_FACTOR_MIL 1886 +#define FG_ADC_RR_CURR_USBIN_660_FACTOR_MIL 9 +#define FG_ADC_RR_CURR_USBIN_660_UV_VAL 579500 + +#define FG_ADC_SCALE_MILLI_FACTOR 1000 +#define FG_ADC_KELVINMIL_CELSIUSMIL 273150 + +#define FG_ADC_RR_GPIO_FS_RANGE 5000 +#define FG_RR_ADC_COHERENT_CHECK_RETRY 5 +#define FG_RR_ADC_MAX_CONTINUOUS_BUFFER_LEN 16 +#define FG_RR_ADC_STS_CHANNEL_READING_MASK 0x3 +#define FG_RR_ADC_STS_CHANNEL_STS 0x2 + +#define FG_RR_CONV_CONTINUOUS_TIME_MIN_MS 50 +#define FG_RR_CONV_MAX_RETRY_CNT 50 +#define FG_RR_TP_REV_VERSION1 21 +#define FG_RR_TP_REV_VERSION2 29 +#define FG_RR_TP_REV_VERSION3 32 + +/* + * The channel number is not a physical index in hardware, + * rather it's a list of supported channels and an index to + * select the respective channel properties such as scaling + * the result. Add any new additional channels supported by + * the RR ADC before RR_ADC_MAX. + */ +enum rradc_channel_id { + RR_ADC_BATT_ID = 0, + RR_ADC_BATT_THERM, + RR_ADC_SKIN_TEMP, + RR_ADC_USBIN_I, + RR_ADC_USBIN_V, + RR_ADC_DCIN_I, + RR_ADC_DCIN_V, + RR_ADC_DIE_TEMP, + RR_ADC_CHG_TEMP, + RR_ADC_GPIO, + RR_ADC_CHG_HOT_TEMP, + RR_ADC_CHG_TOO_HOT_TEMP, + RR_ADC_SKIN_HOT_TEMP, + RR_ADC_SKIN_TOO_HOT_TEMP, + RR_ADC_MAX +}; + +struct rradc_chip { + struct device *dev; + struct mutex lock; + struct regmap *regmap; + u16 base; + struct iio_chan_spec *iio_chans; + unsigned int nchannels; + struct rradc_chan_prop *chan_props; + struct device_node *revid_dev_node; + struct pmic_revid_data *pmic_fab_id; + int volt; + struct power_supply *usb_trig; +}; + +struct rradc_channels { + const char *datasheet_name; + enum iio_chan_type type; + long info_mask; + u8 lsb; + u8 msb; + u8 sts; + int (*scale)(struct rradc_chip *chip, struct rradc_chan_prop *prop, + u16 adc_code, int *result); +}; + +struct rradc_chan_prop { + enum rradc_channel_id channel; + uint32_t channel_data; + int (*scale)(struct rradc_chip *chip, struct rradc_chan_prop *prop, + u16 adc_code, int *result); +}; + +static int rradc_masked_write(struct rradc_chip *rr_adc, u16 offset, u8 mask, + u8 val) +{ + int rc; + + rc = regmap_update_bits(rr_adc->regmap, rr_adc->base + offset, + mask, val); + if (rc) { + pr_err("spmi write failed: addr=%03X, rc=%d\n", offset, rc); + return rc; + } + + return rc; +} + +static int rradc_read(struct rradc_chip *rr_adc, u16 offset, u8 *data, int len) +{ + int rc = 0, retry_cnt = 0, i = 0; + u8 data_check[FG_RR_ADC_MAX_CONTINUOUS_BUFFER_LEN]; + bool coherent_err = false; + + if (len > FG_RR_ADC_MAX_CONTINUOUS_BUFFER_LEN) { + pr_err("Increase the buffer length\n"); + return -EINVAL; + } + + while (retry_cnt < FG_RR_ADC_COHERENT_CHECK_RETRY) { + rc = regmap_bulk_read(rr_adc->regmap, rr_adc->base + offset, + data, len); + if (rc < 0) { + pr_err("rr_adc reg 0x%x failed :%d\n", offset, rc); + return rc; + } + + rc = regmap_bulk_read(rr_adc->regmap, rr_adc->base + offset, + data_check, len); + if (rc < 0) { + pr_err("rr_adc reg 0x%x failed :%d\n", offset, rc); + return rc; + } + + for (i = 0; i < len; i++) { + if (data[i] != data_check[i]) + coherent_err = true; + } + + if (coherent_err) { + retry_cnt++; + coherent_err = false; + pr_debug("retry_cnt:%d\n", retry_cnt); + } else { + break; + } + } + + if (retry_cnt == FG_RR_ADC_COHERENT_CHECK_RETRY) + pr_err("Retry exceeded for coherrency check\n"); + + return rc; +} + +static int rradc_post_process_batt_id(struct rradc_chip *chip, + struct rradc_chan_prop *prop, u16 adc_code, + int *result_ohms) +{ + uint32_t current_value; + int64_t r_id; + + current_value = prop->channel_data; + r_id = ((int64_t)adc_code * FG_ADC_RR_FS_VOLTAGE_MV); + r_id = div64_s64(r_id, (FG_MAX_ADC_READINGS * current_value)); + *result_ohms = (r_id * FG_ADC_SCALE_MILLI_FACTOR); + + return 0; +} + +static int rradc_post_process_therm(struct rradc_chip *chip, + struct rradc_chan_prop *prop, u16 adc_code, + int *result_millidegc) +{ + int64_t temp; + + /* K = code/4 */ + temp = ((int64_t)adc_code * FG_ADC_SCALE_MILLI_FACTOR); + temp = div64_s64(temp, FG_ADC_RR_BATT_THERM_LSB_K); + *result_millidegc = temp - FG_ADC_KELVINMIL_CELSIUSMIL; + + return 0; +} + +static int rradc_post_process_volt(struct rradc_chip *chip, + struct rradc_chan_prop *prop, u16 adc_code, + int *result_uv) +{ + int64_t uv = 0; + + /* 8x input attenuation; 2.5V ADC full scale */ + uv = ((int64_t)adc_code * FG_ADC_RR_VOLT_INPUT_FACTOR); + uv *= (FG_ADC_RR_FS_VOLTAGE_MV * FG_ADC_SCALE_MILLI_FACTOR); + uv = div64_s64(uv, FG_MAX_ADC_READINGS); + *result_uv = uv; + + return 0; +} + +static int rradc_post_process_usbin_curr(struct rradc_chip *chip, + struct rradc_chan_prop *prop, u16 adc_code, + int *result_ua) +{ + int64_t ua = 0, scale = 0; + + if (!prop) + return -EINVAL; + if (chip->revid_dev_node) { + switch (chip->pmic_fab_id->pmic_subtype) { + case PM660_SUBTYPE: + if (((chip->pmic_fab_id->tp_rev + >= FG_RR_TP_REV_VERSION1) + && (chip->pmic_fab_id->tp_rev + <= FG_RR_TP_REV_VERSION2)) + || (chip->pmic_fab_id->tp_rev + >= FG_RR_TP_REV_VERSION3)) { + chip->volt = div64_s64(chip->volt, 1000); + chip->volt = chip->volt * + FG_ADC_RR_CURR_USBIN_660_FACTOR_MIL; + chip->volt = FG_ADC_RR_CURR_USBIN_660_UV_VAL - + (chip->volt); + chip->volt = div64_s64(1000000000, chip->volt); + scale = chip->volt; + } else + scale = FG_ADC_RR_CURR_USBIN_INPUT_FACTOR_MIL; + break; + case PMI8998_SUBTYPE: + scale = FG_ADC_RR_CURR_USBIN_INPUT_FACTOR_MIL; + break; + default: + pr_err("No PMIC subtype found\n"); + return -EINVAL; + } + } + + /* scale * V/A; 2.5V ADC full scale */ + ua = ((int64_t)adc_code * scale); + ua *= (FG_ADC_RR_FS_VOLTAGE_MV * FG_ADC_SCALE_MILLI_FACTOR); + ua = div64_s64(ua, (FG_MAX_ADC_READINGS * 1000)); + *result_ua = ua; + + return 0; +} + +static int rradc_post_process_dcin_curr(struct rradc_chip *chip, + struct rradc_chan_prop *prop, u16 adc_code, + int *result_ua) +{ + int64_t ua = 0; + + if (!prop) + return -EINVAL; + + /* 0.5 V/A; 2.5V ADC full scale */ + ua = ((int64_t)adc_code * FG_ADC_RR_CURR_INPUT_FACTOR); + ua *= (FG_ADC_RR_FS_VOLTAGE_MV * FG_ADC_SCALE_MILLI_FACTOR); + ua = div64_s64(ua, (FG_MAX_ADC_READINGS * 1000)); + *result_ua = ua; + + return 0; +} + +static int rradc_post_process_die_temp(struct rradc_chip *chip, + struct rradc_chan_prop *prop, u16 adc_code, + int *result_millidegc) +{ + int64_t temp = 0; + + temp = ((int64_t)adc_code * FG_ADC_RR_TEMP_FS_VOLTAGE_NUM); + temp = div64_s64(temp, (FG_ADC_RR_TEMP_FS_VOLTAGE_DEN * + FG_MAX_ADC_READINGS)); + temp -= FG_ADC_RR_DIE_TEMP_OFFSET; + temp = div64_s64(temp, FG_ADC_RR_DIE_TEMP_SLOPE); + temp += FG_ADC_RR_DIE_TEMP_OFFSET_MILLI_DEGC; + *result_millidegc = temp; + + return 0; +} + +static int rradc_get_660_fab_coeff(struct rradc_chip *chip, + int64_t *offset, int64_t *slope) +{ + switch (chip->pmic_fab_id->fab_id) { + case PM660_FAB_ID_GF: + *offset = FG_ADC_RR_CHG_TEMP_660_GF_OFFSET_UV; + *slope = FG_RR_CHG_TEMP_660_GF_SLOPE_UV_PER_C; + break; + case PM660_FAB_ID_TSMC: + *offset = FG_ADC_RR_CHG_TEMP_660_SMIC_OFFSET_UV; + *slope = FG_RR_CHG_TEMP_660_SMIC_SLOPE_UV_PER_C; + break; + default: + *offset = FG_ADC_RR_CHG_TEMP_660_MGNA_OFFSET_UV; + *slope = FG_RR_CHG_TEMP_660_MGNA_SLOPE_UV_PER_C; + } + + return 0; +} + +static int rradc_get_8998_fab_coeff(struct rradc_chip *chip, + int64_t *offset, int64_t *slope) +{ + switch (chip->pmic_fab_id->fab_id) { + case PMI8998_FAB_ID_GF: + *offset = FG_ADC_RR_CHG_TEMP_GF_OFFSET_UV; + *slope = FG_ADC_RR_CHG_TEMP_GF_SLOPE_UV_PER_C; + break; + case PMI8998_FAB_ID_SMIC: + *offset = FG_ADC_RR_CHG_TEMP_SMIC_OFFSET_UV; + *slope = FG_ADC_RR_CHG_TEMP_SMIC_SLOPE_UV_PER_C; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int rradc_post_process_chg_temp_hot(struct rradc_chip *chip, + struct rradc_chan_prop *prop, u16 adc_code, + int *result_millidegc) +{ + int64_t uv = 0, offset = 0, slope = 0; + int rc = 0; + + if (chip->revid_dev_node) { + switch (chip->pmic_fab_id->pmic_subtype) { + case PM660_SUBTYPE: + rc = rradc_get_660_fab_coeff(chip, &offset, &slope); + if (rc < 0) { + pr_err("Unable to get fab id coefficients\n"); + return -EINVAL; + } + break; + case PMI8998_SUBTYPE: + rc = rradc_get_8998_fab_coeff(chip, &offset, &slope); + if (rc < 0) { + pr_err("Unable to get fab id coefficients\n"); + return -EINVAL; + } + break; + default: + pr_err("No PMIC subtype found\n"); + return -EINVAL; + } + } else { + pr_err("No temperature scaling coefficients\n"); + return -EINVAL; + } + + uv = (int64_t) adc_code * FG_ADC_RR_CHG_THRESHOLD_SCALE; + uv = uv * FG_ADC_RR_TEMP_FS_VOLTAGE_NUM; + uv = div64_s64(uv, (FG_ADC_RR_TEMP_FS_VOLTAGE_DEN * + FG_MAX_ADC_READINGS)); + uv = offset - uv; + uv = div64_s64((uv * FG_ADC_SCALE_MILLI_FACTOR), slope); + uv = uv + FG_ADC_RR_CHG_TEMP_OFFSET_MILLI_DEGC; + *result_millidegc = uv; + + return 0; +} + +static int rradc_post_process_skin_temp_hot(struct rradc_chip *chip, + struct rradc_chan_prop *prop, u16 adc_code, + int *result_millidegc) +{ + int64_t temp = 0; + + temp = (int64_t) adc_code; + temp = div64_s64(temp, 2); + temp = temp - 30; + temp *= FG_ADC_SCALE_MILLI_FACTOR; + *result_millidegc = temp; + + return 0; +} + +static int rradc_post_process_chg_temp(struct rradc_chip *chip, + struct rradc_chan_prop *prop, u16 adc_code, + int *result_millidegc) +{ + int64_t uv = 0, offset = 0, slope = 0; + int rc = 0; + + if (chip->revid_dev_node) { + switch (chip->pmic_fab_id->pmic_subtype) { + case PM660_SUBTYPE: + rc = rradc_get_660_fab_coeff(chip, &offset, &slope); + if (rc < 0) { + pr_err("Unable to get fab id coefficients\n"); + return -EINVAL; + } + break; + case PMI8998_SUBTYPE: + rc = rradc_get_8998_fab_coeff(chip, &offset, &slope); + if (rc < 0) { + pr_err("Unable to get fab id coefficients\n"); + return -EINVAL; + } + break; + default: + pr_err("No PMIC subtype found\n"); + return -EINVAL; + } + } else { + pr_err("No temperature scaling coefficients\n"); + return -EINVAL; + } + + uv = ((int64_t) adc_code * FG_ADC_RR_TEMP_FS_VOLTAGE_NUM); + uv = div64_s64(uv, (FG_ADC_RR_TEMP_FS_VOLTAGE_DEN * + FG_MAX_ADC_READINGS)); + uv = offset - uv; + uv = div64_s64((uv * FG_ADC_SCALE_MILLI_FACTOR), slope); + uv += FG_ADC_RR_CHG_TEMP_OFFSET_MILLI_DEGC; + *result_millidegc = uv; + + return 0; +} + +static int rradc_post_process_gpio(struct rradc_chip *chip, + struct rradc_chan_prop *prop, u16 adc_code, + int *result_mv) +{ + int64_t mv = 0; + + /* 5V ADC full scale, 10 bit */ + mv = ((int64_t)adc_code * FG_ADC_RR_GPIO_FS_RANGE); + mv = div64_s64(mv, FG_MAX_ADC_READINGS); + *result_mv = mv; + + return 0; +} + +#define RR_ADC_CHAN(_dname, _type, _mask, _scale, _lsb, _msb, _sts) \ + { \ + .datasheet_name = (_dname), \ + .type = _type, \ + .info_mask = _mask, \ + .scale = _scale, \ + .lsb = _lsb, \ + .msb = _msb, \ + .sts = _sts, \ + }, \ + +#define RR_ADC_CHAN_TEMP(_dname, _scale, mask, _lsb, _msb, _sts) \ + RR_ADC_CHAN(_dname, IIO_TEMP, \ + mask, \ + _scale, _lsb, _msb, _sts) \ + +#define RR_ADC_CHAN_VOLT(_dname, _scale, _lsb, _msb, _sts) \ + RR_ADC_CHAN(_dname, IIO_VOLTAGE, \ + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_PROCESSED),\ + _scale, _lsb, _msb, _sts) \ + +#define RR_ADC_CHAN_CURRENT(_dname, _scale, _lsb, _msb, _sts) \ + RR_ADC_CHAN(_dname, IIO_CURRENT, \ + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_PROCESSED),\ + _scale, _lsb, _msb, _sts) \ + +#define RR_ADC_CHAN_RESISTANCE(_dname, _scale, _lsb, _msb, _sts) \ + RR_ADC_CHAN(_dname, IIO_RESISTANCE, \ + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_PROCESSED),\ + _scale, _lsb, _msb, _sts) \ + +static const struct rradc_channels rradc_chans[] = { + RR_ADC_CHAN_RESISTANCE("batt_id", rradc_post_process_batt_id, + FG_ADC_RR_BATT_ID_5_LSB, FG_ADC_RR_BATT_ID_5_MSB, + FG_ADC_RR_BATT_ID_STS) + RR_ADC_CHAN_TEMP("batt_therm", &rradc_post_process_therm, + BIT(IIO_CHAN_INFO_RAW), + FG_ADC_RR_BATT_THERM_LSB, FG_ADC_RR_BATT_THERM_MSB, + FG_ADC_RR_BATT_THERM_STS) + RR_ADC_CHAN_TEMP("skin_temp", &rradc_post_process_therm, + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_PROCESSED), + FG_ADC_RR_SKIN_TEMP_LSB, FG_ADC_RR_SKIN_TEMP_MSB, + FG_ADC_RR_AUX_THERM_STS) + RR_ADC_CHAN_CURRENT("usbin_i", &rradc_post_process_usbin_curr, + FG_ADC_RR_USB_IN_I_LSB, FG_ADC_RR_USB_IN_I_MSB, + FG_ADC_RR_USB_IN_I_STS) + RR_ADC_CHAN_VOLT("usbin_v", &rradc_post_process_volt, + FG_ADC_RR_USB_IN_V_LSB, FG_ADC_RR_USB_IN_V_MSB, + FG_ADC_RR_USB_IN_V_STS) + RR_ADC_CHAN_CURRENT("dcin_i", &rradc_post_process_dcin_curr, + FG_ADC_RR_DC_IN_I_LSB, FG_ADC_RR_DC_IN_I_MSB, + FG_ADC_RR_DC_IN_I_STS) + RR_ADC_CHAN_VOLT("dcin_v", &rradc_post_process_volt, + FG_ADC_RR_DC_IN_V_LSB, FG_ADC_RR_DC_IN_V_MSB, + FG_ADC_RR_DC_IN_V_STS) + RR_ADC_CHAN_TEMP("die_temp", &rradc_post_process_die_temp, + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_PROCESSED), + FG_ADC_RR_PMI_DIE_TEMP_LSB, FG_ADC_RR_PMI_DIE_TEMP_MSB, + FG_ADC_RR_PMI_DIE_TEMP_STS) + RR_ADC_CHAN_TEMP("chg_temp", &rradc_post_process_chg_temp, + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_PROCESSED), + FG_ADC_RR_CHARGER_TEMP_LSB, FG_ADC_RR_CHARGER_TEMP_MSB, + FG_ADC_RR_CHARGER_TEMP_STS) + RR_ADC_CHAN_VOLT("gpio", &rradc_post_process_gpio, + FG_ADC_RR_GPIO_LSB, FG_ADC_RR_GPIO_MSB, + FG_ADC_RR_GPIO_STS) + RR_ADC_CHAN_TEMP("chg_temp_hot", &rradc_post_process_chg_temp_hot, + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_PROCESSED), + FG_ADC_RR_CHARGER_HOT, FG_ADC_RR_CHARGER_HOT, + FG_ADC_RR_CHARGER_TEMP_STS) + RR_ADC_CHAN_TEMP("chg_temp_too_hot", &rradc_post_process_chg_temp_hot, + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_PROCESSED), + FG_ADC_RR_CHARGER_TOO_HOT, FG_ADC_RR_CHARGER_TOO_HOT, + FG_ADC_RR_CHARGER_TEMP_STS) + RR_ADC_CHAN_TEMP("skin_temp_hot", &rradc_post_process_skin_temp_hot, + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_PROCESSED), + FG_ADC_RR_SKIN_HOT, FG_ADC_RR_SKIN_HOT, + FG_ADC_RR_AUX_THERM_STS) + RR_ADC_CHAN_TEMP("skin_temp_too_hot", &rradc_post_process_skin_temp_hot, + BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_PROCESSED), + FG_ADC_RR_SKIN_TOO_HOT, FG_ADC_RR_SKIN_TOO_HOT, + FG_ADC_RR_AUX_THERM_STS) +}; + +static int rradc_enable_continuous_mode(struct rradc_chip *chip) +{ + int rc = 0; + + /* Clear channel log */ + rc = rradc_masked_write(chip, FG_ADC_RR_ADC_LOG, + FG_ADC_RR_ADC_LOG_CLR_CTRL, + FG_ADC_RR_ADC_LOG_CLR_CTRL); + if (rc < 0) { + pr_err("log ctrl update to clear failed:%d\n", rc); + return rc; + } + + rc = rradc_masked_write(chip, FG_ADC_RR_ADC_LOG, + FG_ADC_RR_ADC_LOG_CLR_CTRL, 0); + if (rc < 0) { + pr_err("log ctrl update to not clear failed:%d\n", rc); + return rc; + } + + /* Switch to continuous mode */ + rc = rradc_masked_write(chip, FG_ADC_RR_RR_ADC_CTL, + FG_ADC_RR_ADC_CTL_CONTINUOUS_SEL_MASK, + FG_ADC_RR_ADC_CTL_CONTINUOUS_SEL); + if (rc < 0) { + pr_err("Update to continuous mode failed:%d\n", rc); + return rc; + } + + return rc; +} + +static int rradc_disable_continuous_mode(struct rradc_chip *chip) +{ + int rc = 0; + + /* Switch to non continuous mode */ + rc = rradc_masked_write(chip, FG_ADC_RR_RR_ADC_CTL, + FG_ADC_RR_ADC_CTL_CONTINUOUS_SEL_MASK, 0); + if (rc < 0) { + pr_err("Update to non-continuous mode failed:%d\n", rc); + return rc; + } + + return rc; +} + +static bool rradc_is_usb_present(struct rradc_chip *chip) +{ + union power_supply_propval pval; + int rc; + bool usb_present = false; + + if (!chip->usb_trig) { + pr_debug("USB property not present\n"); + return usb_present; + } + + rc = power_supply_get_property(chip->usb_trig, + POWER_SUPPLY_PROP_PRESENT, &pval); + usb_present = (rc < 0) ? 0 : pval.intval; + + return usb_present; +} + +static int rradc_check_status_ready_with_retry(struct rradc_chip *chip, + struct rradc_chan_prop *prop, u8 *buf, u16 status) +{ + int rc = 0, retry_cnt = 0, mask = 0; + + switch (prop->channel) { + case RR_ADC_BATT_ID: + /* BATT_ID STS bit does not get set initially */ + mask = FG_RR_ADC_STS_CHANNEL_STS; + break; + default: + mask = FG_RR_ADC_STS_CHANNEL_READING_MASK; + break; + } + + while (((buf[0] & mask) != mask) && + (retry_cnt < FG_RR_CONV_MAX_RETRY_CNT)) { + pr_debug("%s is not ready; nothing to read:0x%x\n", + rradc_chans[prop->channel].datasheet_name, buf[0]); + + if (((prop->channel == RR_ADC_CHG_TEMP) || + (prop->channel == RR_ADC_SKIN_TEMP) || + (prop->channel == RR_ADC_USBIN_I)) && + ((!rradc_is_usb_present(chip)))) { + pr_debug("USB not present for %d\n", prop->channel); + rc = -ENODATA; + break; + } + + msleep(FG_RR_CONV_CONTINUOUS_TIME_MIN_MS); + retry_cnt++; + rc = rradc_read(chip, status, buf, 1); + if (rc < 0) { + pr_err("status read failed:%d\n", rc); + return rc; + } + } + + if (retry_cnt >= FG_RR_CONV_MAX_RETRY_CNT) + rc = -ENODATA; + + return rc; +} + +static int rradc_read_channel_with_continuous_mode(struct rradc_chip *chip, + struct rradc_chan_prop *prop, u8 *buf) +{ + int rc = 0, ret = 0; + u16 status = 0; + + rc = rradc_enable_continuous_mode(chip); + if (rc < 0) { + pr_err("Failed to switch to continuous mode\n"); + return rc; + } + + status = rradc_chans[prop->channel].sts; + rc = rradc_read(chip, status, buf, 1); + if (rc < 0) { + pr_err("status read failed:%d\n", rc); + ret = rc; + goto disable; + } + + rc = rradc_check_status_ready_with_retry(chip, prop, + buf, status); + if (rc < 0) { + pr_err("Status read failed:%d\n", rc); + ret = rc; + } + +disable: + rc = rradc_disable_continuous_mode(chip); + if (rc < 0) { + pr_err("Failed to switch to non continuous mode\n"); + ret = rc; + } + + return ret; +} + +static int rradc_enable_batt_id_channel(struct rradc_chip *chip, bool enable) +{ + int rc = 0; + + if (enable) { + rc = rradc_masked_write(chip, FG_ADC_RR_BATT_ID_CTRL, + FG_ADC_RR_BATT_ID_CTRL_CHANNEL_CONV, + FG_ADC_RR_BATT_ID_CTRL_CHANNEL_CONV); + if (rc < 0) { + pr_err("Enabling BATT ID channel failed:%d\n", rc); + return rc; + } + } else { + rc = rradc_masked_write(chip, FG_ADC_RR_BATT_ID_CTRL, + FG_ADC_RR_BATT_ID_CTRL_CHANNEL_CONV, 0); + if (rc < 0) { + pr_err("Disabling BATT ID channel failed:%d\n", rc); + return rc; + } + } + + return rc; +} + +static int rradc_do_batt_id_conversion(struct rradc_chip *chip, + struct rradc_chan_prop *prop, u16 *data, u8 *buf) +{ + int rc = 0, ret = 0; + + rc = rradc_enable_batt_id_channel(chip, true); + if (rc < 0) { + pr_err("Enabling BATT ID channel failed:%d\n", rc); + return rc; + } + + rc = rradc_masked_write(chip, FG_ADC_RR_BATT_ID_TRIGGER, + FG_ADC_RR_BATT_ID_TRIGGER_CTL, + FG_ADC_RR_BATT_ID_TRIGGER_CTL); + if (rc < 0) { + pr_err("BATT_ID trigger set failed:%d\n", rc); + ret = rc; + rc = rradc_enable_batt_id_channel(chip, false); + if (rc < 0) + pr_err("Disabling BATT ID channel failed:%d\n", rc); + return ret; + } + + rc = rradc_read_channel_with_continuous_mode(chip, prop, buf); + if (rc < 0) { + pr_err("Error reading in continuous mode:%d\n", rc); + ret = rc; + } + + rc = rradc_masked_write(chip, FG_ADC_RR_BATT_ID_TRIGGER, + FG_ADC_RR_BATT_ID_TRIGGER_CTL, 0); + if (rc < 0) { + pr_err("BATT_ID trigger re-set failed:%d\n", rc); + ret = rc; + } + + rc = rradc_enable_batt_id_channel(chip, false); + if (rc < 0) { + pr_err("Disabling BATT ID channel failed:%d\n", rc); + ret = rc; + } + + return ret; +} + +static int rradc_do_conversion(struct rradc_chip *chip, + struct rradc_chan_prop *prop, u16 *data) +{ + int rc = 0, bytes_to_read = 0; + u8 buf[6]; + u16 offset = 0, batt_id_5 = 0, batt_id_15 = 0, batt_id_150 = 0; + u16 status = 0; + + mutex_lock(&chip->lock); + + switch (prop->channel) { + case RR_ADC_BATT_ID: + rc = rradc_do_batt_id_conversion(chip, prop, data, buf); + if (rc < 0) { + pr_err("Battery ID conversion failed:%d\n", rc); + goto fail; + } + break; + case RR_ADC_USBIN_V: + /* Force conversion every cycle */ + rc = rradc_masked_write(chip, FG_ADC_RR_USB_IN_V_TRIGGER, + FG_ADC_RR_USB_IN_V_EVERY_CYCLE_MASK, + FG_ADC_RR_USB_IN_V_EVERY_CYCLE); + if (rc < 0) { + pr_err("Force every cycle update failed:%d\n", rc); + goto fail; + } + + rc = rradc_read_channel_with_continuous_mode(chip, prop, buf); + if (rc < 0) { + pr_err("Error reading in continuous mode:%d\n", rc); + goto fail; + } + + /* Restore usb_in trigger */ + rc = rradc_masked_write(chip, FG_ADC_RR_USB_IN_V_TRIGGER, + FG_ADC_RR_USB_IN_V_EVERY_CYCLE_MASK, 0); + if (rc < 0) { + pr_err("Restore every cycle update failed:%d\n", rc); + goto fail; + } + break; + case RR_ADC_DIE_TEMP: + /* Force conversion every cycle */ + rc = rradc_masked_write(chip, FG_ADC_RR_PMI_DIE_TEMP_TRIGGER, + FG_ADC_RR_USB_IN_V_EVERY_CYCLE_MASK, + FG_ADC_RR_USB_IN_V_EVERY_CYCLE); + if (rc < 0) { + pr_err("Force every cycle update failed:%d\n", rc); + goto fail; + } + + rc = rradc_read_channel_with_continuous_mode(chip, prop, buf); + if (rc < 0) { + pr_err("Error reading in continuous mode:%d\n", rc); + goto fail; + } + + /* Restore aux_therm trigger */ + rc = rradc_masked_write(chip, FG_ADC_RR_PMI_DIE_TEMP_TRIGGER, + FG_ADC_RR_USB_IN_V_EVERY_CYCLE_MASK, 0); + if (rc < 0) { + pr_err("Restore every cycle update failed:%d\n", rc); + goto fail; + } + break; + case RR_ADC_CHG_HOT_TEMP: + case RR_ADC_CHG_TOO_HOT_TEMP: + case RR_ADC_SKIN_HOT_TEMP: + case RR_ADC_SKIN_TOO_HOT_TEMP: + pr_debug("Read only the data registers\n"); + break; + default: + status = rradc_chans[prop->channel].sts; + rc = rradc_read(chip, status, buf, 1); + if (rc < 0) { + pr_err("status read failed:%d\n", rc); + goto fail; + } + + rc = rradc_check_status_ready_with_retry(chip, prop, + buf, status); + if (rc < 0) { + pr_debug("Status read failed:%d\n", rc); + rc = -ENODATA; + goto fail; + } + break; + } + + offset = rradc_chans[prop->channel].lsb; + if (prop->channel == RR_ADC_BATT_ID) + bytes_to_read = 6; + else if ((prop->channel == RR_ADC_CHG_HOT_TEMP) || + (prop->channel == RR_ADC_CHG_TOO_HOT_TEMP) || + (prop->channel == RR_ADC_SKIN_HOT_TEMP) || + (prop->channel == RR_ADC_SKIN_TOO_HOT_TEMP)) + bytes_to_read = 1; + else + bytes_to_read = 2; + + buf[0] = 0; + rc = rradc_read(chip, offset, buf, bytes_to_read); + if (rc) { + pr_err("read data failed\n"); + goto fail; + } + + if (prop->channel == RR_ADC_BATT_ID) { + batt_id_150 = (buf[5] << 8) | buf[4]; + batt_id_15 = (buf[3] << 8) | buf[2]; + batt_id_5 = (buf[1] << 8) | buf[0]; + if ((!batt_id_150) && (!batt_id_15) && (!batt_id_5)) { + pr_err("Invalid batt_id values with all zeros\n"); + rc = -EINVAL; + goto fail; + } + + if (batt_id_150 <= FG_ADC_RR_BATT_ID_RANGE) { + pr_debug("Batt_id_150 is chosen\n"); + *data = batt_id_150; + prop->channel_data = FG_ADC_RR_BATT_ID_150_MA; + } else if (batt_id_15 <= FG_ADC_RR_BATT_ID_RANGE) { + pr_debug("Batt_id_15 is chosen\n"); + *data = batt_id_15; + prop->channel_data = FG_ADC_RR_BATT_ID_15_MA; + } else { + pr_debug("Batt_id_5 is chosen\n"); + *data = batt_id_5; + prop->channel_data = FG_ADC_RR_BATT_ID_5_MA; + } + } else if ((prop->channel == RR_ADC_CHG_HOT_TEMP) || + (prop->channel == RR_ADC_CHG_TOO_HOT_TEMP) || + (prop->channel == RR_ADC_SKIN_HOT_TEMP) || + (prop->channel == RR_ADC_SKIN_TOO_HOT_TEMP)) { + *data = buf[0]; + } else { + *data = (buf[1] << 8) | buf[0]; + } +fail: + mutex_unlock(&chip->lock); + + return rc; +} + +static int rradc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, int *val2, + long mask) +{ + struct rradc_chip *chip = iio_priv(indio_dev); + struct rradc_chan_prop *prop; + u16 adc_code; + int rc = 0; + + if (chan->address >= RR_ADC_MAX) { + pr_err("Invalid channel index:%ld\n", chan->address); + return -EINVAL; + } + + switch (mask) { + case IIO_CHAN_INFO_PROCESSED: + if (((chip->pmic_fab_id->tp_rev + >= FG_RR_TP_REV_VERSION1) + && (chip->pmic_fab_id->tp_rev + <= FG_RR_TP_REV_VERSION2)) + || (chip->pmic_fab_id->tp_rev + >= FG_RR_TP_REV_VERSION3)) { + if (chan->address == RR_ADC_USBIN_I) { + prop = &chip->chan_props[RR_ADC_USBIN_V]; + rc = rradc_do_conversion(chip, prop, &adc_code); + if (rc) + break; + prop->scale(chip, prop, adc_code, &chip->volt); + } + } + + prop = &chip->chan_props[chan->address]; + rc = rradc_do_conversion(chip, prop, &adc_code); + if (rc) + break; + + prop->scale(chip, prop, adc_code, val); + + return IIO_VAL_INT; + case IIO_CHAN_INFO_RAW: + prop = &chip->chan_props[chan->address]; + rc = rradc_do_conversion(chip, prop, &adc_code); + if (rc) + break; + + *val = (int) adc_code; + + return IIO_VAL_INT; + default: + rc = -EINVAL; + break; + } + + return rc; +} + +static const struct iio_info rradc_info = { + .read_raw = &rradc_read_raw, +}; + +static int rradc_get_dt_data(struct rradc_chip *chip, struct device_node *node) +{ + const struct rradc_channels *rradc_chan; + struct iio_chan_spec *iio_chan; + unsigned int i = 0, base; + int rc = 0; + struct rradc_chan_prop prop; + + chip->nchannels = RR_ADC_MAX; + chip->iio_chans = devm_kcalloc(chip->dev, chip->nchannels, + sizeof(*chip->iio_chans), GFP_KERNEL); + if (!chip->iio_chans) + return -ENOMEM; + + chip->chan_props = devm_kcalloc(chip->dev, chip->nchannels, + sizeof(*chip->chan_props), GFP_KERNEL); + if (!chip->chan_props) + return -ENOMEM; + + /* Get the peripheral address */ + rc = of_property_read_u32(node, "reg", &base); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't find reg in node = %s rc = %d\n", + node->name, rc); + return rc; + } + + chip->base = base; + chip->revid_dev_node = of_parse_phandle(node, "qcom,pmic-revid", 0); + if (chip->revid_dev_node) { + chip->pmic_fab_id = get_revid_data(chip->revid_dev_node); + if (IS_ERR(chip->pmic_fab_id)) { + rc = PTR_ERR(chip->pmic_fab_id); + if (rc != -EPROBE_DEFER) + pr_err("Unable to get pmic_revid rc=%d\n", rc); + return rc; + } + + if (!chip->pmic_fab_id) + return -EINVAL; + + if (chip->pmic_fab_id->fab_id == -EINVAL) { + rc = chip->pmic_fab_id->fab_id; + pr_debug("Unable to read fabid rc=%d\n", rc); + } + } + + iio_chan = chip->iio_chans; + + for (i = 0; i < RR_ADC_MAX; i++) { + prop.channel = i; + prop.scale = rradc_chans[i].scale; + /* Private channel data used for selecting batt_id */ + prop.channel_data = 0; + chip->chan_props[i] = prop; + + rradc_chan = &rradc_chans[i]; + + iio_chan->channel = prop.channel; + iio_chan->datasheet_name = rradc_chan->datasheet_name; + iio_chan->extend_name = rradc_chan->datasheet_name; + iio_chan->info_mask_separate = rradc_chan->info_mask; + iio_chan->type = rradc_chan->type; + iio_chan->address = i; + iio_chan++; + } + + return 0; +} + +static int rradc_probe(struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node; + struct device *dev = &pdev->dev; + struct iio_dev *indio_dev; + struct rradc_chip *chip; + int rc = 0; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*chip)); + if (!indio_dev) + return -ENOMEM; + + chip = iio_priv(indio_dev); + chip->regmap = dev_get_regmap(pdev->dev.parent, NULL); + if (!chip->regmap) { + dev_err(&pdev->dev, "Couldn't get parent's regmap\n"); + return -EINVAL; + } + + chip->dev = dev; + mutex_init(&chip->lock); + + rc = rradc_get_dt_data(chip, node); + if (rc) + return rc; + + indio_dev->dev.parent = dev; + indio_dev->dev.of_node = node; + indio_dev->name = pdev->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &rradc_info; + indio_dev->channels = chip->iio_chans; + indio_dev->num_channels = chip->nchannels; + + chip->usb_trig = power_supply_get_by_name("usb"); + if (!chip->usb_trig) + pr_debug("Error obtaining usb power supply\n"); + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct of_device_id rradc_match_table[] = { + { .compatible = "qcom,rradc" }, + { } +}; +MODULE_DEVICE_TABLE(of, rradc_match_table); + +static struct platform_driver rradc_driver = { + .driver = { + .name = "qcom-rradc", + .of_match_table = rradc_match_table, + }, + .probe = rradc_probe, +}; +module_platform_driver(rradc_driver); + +MODULE_DESCRIPTION("QPNP PMIC RR ADC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/adc/qcom-tadc.c b/drivers/iio/adc/qcom-tadc.c new file mode 100644 index 000000000000..76b6c12e0fe5 --- /dev/null +++ b/drivers/iio/adc/qcom-tadc.c @@ -0,0 +1,1315 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2016-2017, 2020, The Linux Foundation. All rights reserved. + */ + +#define pr_fmt(fmt) "TADC: %s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define USB_PRESENT_VOTER "USB_PRESENT_VOTER" +#define SLEEP_VOTER "SLEEP_VOTER" +#define SHUTDOWN_VOTER "SHUTDOWN_VOTER" +#define TADC_REVISION1_REG 0x00 +#define TADC_REVISION2_REG 0x01 +#define TADC_REVISION3_REG 0x02 +#define TADC_REVISION4_REG 0x03 +#define TADC_PERPH_TYPE_REG 0x04 +#define TADC_PERPH_SUBTYPE_REG 0x05 + +/* TADC register definitions */ +#define TADC_SW_CH_CONV_REG(chip) (chip->tadc_base + 0x06) +#define TADC_MBG_ERR_REG(chip) (chip->tadc_base + 0x07) +#define TADC_EN_CTL_REG(chip) (chip->tadc_base + 0x46) +#define TADC_CONV_REQ_REG(chip) (chip->tadc_base + 0x51) +#define TADC_HWTRIG_CONV_CH_EN_REG(chip) (chip->tadc_base + 0x52) +#define TADC_HW_SETTLE_DELAY_REG(chip) (chip->tadc_base + 0x53) +#define TADC_LONG_HW_SETTLE_DLY_EN_REG(chip) (chip->tadc_base + 0x54) +#define TADC_LONG_HW_SETTLE_DLY_REG(chip) (chip->tadc_base + 0x55) +#define TADC_ADC_BUF_CH_REG(chip) (chip->tadc_base + 0x56) +#define TADC_ADC_AAF_CH_REG(chip) (chip->tadc_base + 0x57) +#define TADC_ADC_DATA_RDBK_REG(chip) (chip->tadc_base + 0x58) +#define TADC_CH1_ADC_LO_REG(chip) (chip->tadc_base + 0x60) +#define TADC_CH1_ADC_HI_REG(chip) (chip->tadc_base + 0x61) +#define TADC_CH2_ADC_LO_REG(chip) (chip->tadc_base + 0x62) +#define TADC_CH2_ADC_HI_REG(chip) (chip->tadc_base + 0x63) +#define TADC_CH3_ADC_LO_REG(chip) (chip->tadc_base + 0x64) +#define TADC_CH3_ADC_HI_REG(chip) (chip->tadc_base + 0x65) +#define TADC_CH4_ADC_LO_REG(chip) (chip->tadc_base + 0x66) +#define TADC_CH4_ADC_HI_REG(chip) (chip->tadc_base + 0x67) +#define TADC_CH5_ADC_LO_REG(chip) (chip->tadc_base + 0x68) +#define TADC_CH5_ADC_HI_REG(chip) (chip->tadc_base + 0x69) +#define TADC_CH6_ADC_LO_REG(chip) (chip->tadc_base + 0x70) +#define TADC_CH6_ADC_HI_REG(chip) (chip->tadc_base + 0x71) +#define TADC_CH7_ADC_LO_REG(chip) (chip->tadc_base + 0x72) +#define TADC_CH7_ADC_HI_REG(chip) (chip->tadc_base + 0x73) +#define TADC_CH8_ADC_LO_REG(chip) (chip->tadc_base + 0x74) +#define TADC_CH8_ADC_HI_REG(chip) (chip->tadc_base + 0x75) +#define TADC_ADC_DIRECT_TST(chip) (chip->tadc_base + 0xE7) + +/* TADC_CMP register definitions */ +#define TADC_CMP_THR1_CMP_REG(chip) (chip->tadc_cmp_base + 0x51) +#define TADC_CMP_THR1_CH1_CMP_LO_REG(chip) (chip->tadc_cmp_base + 0x52) +#define TADC_CMP_THR1_CH1_CMP_HI_REG(chip) (chip->tadc_cmp_base + 0x53) +#define TADC_CMP_THR1_CH2_CMP_LO_REG(chip) (chip->tadc_cmp_base + 0x54) +#define TADC_CMP_THR1_CH2_CMP_HI_REG(chip) (chip->tadc_cmp_base + 0x55) +#define TADC_CMP_THR1_CH3_CMP_LO_REG(chip) (chip->tadc_cmp_base + 0x56) +#define TADC_CMP_THR1_CH3_CMP_HI_REG(chip) (chip->tadc_cmp_base + 0x57) +#define TADC_CMP_THR2_CMP_REG(chip) (chip->tadc_cmp_base + 0x67) +#define TADC_CMP_THR2_CH1_CMP_LO_REG(chip) (chip->tadc_cmp_base + 0x68) +#define TADC_CMP_THR2_CH1_CMP_HI_REG(chip) (chip->tadc_cmp_base + 0x69) +#define TADC_CMP_THR2_CH2_CMP_LO_REG(chip) (chip->tadc_cmp_base + 0x6A) +#define TADC_CMP_THR2_CH2_CMP_HI_REG(chip) (chip->tadc_cmp_base + 0x6B) +#define TADC_CMP_THR2_CH3_CMP_LO_REG(chip) (chip->tadc_cmp_base + 0x6C) +#define TADC_CMP_THR2_CH3_CMP_HI_REG(chip) (chip->tadc_cmp_base + 0x6D) +#define TADC_CMP_THR3_CMP_REG(chip) (chip->tadc_cmp_base + 0x7D) +#define TADC_CMP_THR3_CH1_CMP_LO_REG(chip) (chip->tadc_cmp_base + 0x7E) +#define TADC_CMP_THR3_CH1_CMP_HI_REG(chip) (chip->tadc_cmp_base + 0x7F) +#define TADC_CMP_THR3_CH2_CMP_LO_REG(chip) (chip->tadc_cmp_base + 0x80) +#define TADC_CMP_THR3_CH2_CMP_HI_REG(chip) (chip->tadc_cmp_base + 0x81) +#define TADC_CMP_THR3_CH3_CMP_LO_REG(chip) (chip->tadc_cmp_base + 0x82) +#define TADC_CMP_THR3_CH3_CMP_HI_REG(chip) (chip->tadc_cmp_base + 0x83) +#define TADC_CMP_THR4_CMP_REG(chip) (chip->tadc_cmp_base + 0x93) +#define TADC_CMP_THR4_CH1_CMP_LO_REG(chip) (chip->tadc_cmp_base + 0x94) +#define TADC_CMP_THR4_CH1_CMP_HI_REG(chip) (chip->tadc_cmp_base + 0x95) +#define TADC_CMP_THR1_CH1_HYST_REG(chip) (chip->tadc_cmp_base + 0xB0) +#define TADC_CMP_THR2_CH1_HYST_REG(chip) (chip->tadc_cmp_base + 0xB1) +#define TADC_CMP_THR3_CH1_HYST_REG(chip) (chip->tadc_cmp_base + 0xB2) +#define TADC_CMP_THR4_CH1_HYST_REG(chip) (chip->tadc_cmp_base + 0xB3) + +/* 10 bits of resolution */ +#define TADC_RESOLUTION 1024 +/* number of hardware channels */ +#define TADC_NUM_CH 8 + +enum tadc_chan_id { + TADC_THERM1 = 0, + TADC_THERM2, + TADC_DIE_TEMP, + TADC_BATT_I, + TADC_BATT_V, + TADC_INPUT_I, + TADC_INPUT_V, + TADC_OTG_I, + /* virtual channels */ + TADC_BATT_P, + TADC_INPUT_P, + TADC_THERM1_THR1, + TADC_THERM1_THR2, + TADC_THERM1_THR3, + TADC_THERM1_THR4, + TADC_THERM2_THR1, + TADC_THERM2_THR2, + TADC_THERM2_THR3, + TADC_DIE_TEMP_THR1, + TADC_DIE_TEMP_THR2, + TADC_DIE_TEMP_THR3, + TADC_CHAN_ID_MAX, +}; + +#define TADC_CHAN(_name, _type, _channel, _info_mask) \ +{ \ + .type = _type, \ + .channel = _channel, \ + .info_mask_separate = _info_mask, \ + .extend_name = _name, \ +} + +#define TADC_THERM_CHAN(_name, _channel) \ +TADC_CHAN(_name, IIO_TEMP, _channel, \ + BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_PROCESSED)) + +#define TADC_TEMP_CHAN(_name, _channel) \ +TADC_CHAN(_name, IIO_TEMP, _channel, \ + BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_PROCESSED) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_OFFSET)) + +#define TADC_CURRENT_CHAN(_name, _channel) \ +TADC_CHAN(_name, IIO_CURRENT, _channel, \ + BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_PROCESSED) | \ + BIT(IIO_CHAN_INFO_SCALE)) + + +#define TADC_VOLTAGE_CHAN(_name, _channel) \ +TADC_CHAN(_name, IIO_VOLTAGE, _channel, \ + BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_PROCESSED) | \ + BIT(IIO_CHAN_INFO_SCALE)) + +#define TADC_POWER_CHAN(_name, _channel) \ +TADC_CHAN(_name, IIO_POWER, _channel, \ + BIT(IIO_CHAN_INFO_PROCESSED)) + +static const struct iio_chan_spec tadc_iio_chans[] = { + [TADC_THERM1] = TADC_THERM_CHAN( + "batt", TADC_THERM1), + [TADC_THERM2] = TADC_THERM_CHAN( + "skin", TADC_THERM2), + [TADC_DIE_TEMP] = TADC_TEMP_CHAN( + "die", TADC_DIE_TEMP), + [TADC_BATT_I] = TADC_CURRENT_CHAN( + "batt", TADC_BATT_I), + [TADC_BATT_V] = TADC_VOLTAGE_CHAN( + "batt", TADC_BATT_V), + [TADC_INPUT_I] = TADC_CURRENT_CHAN( + "input", TADC_INPUT_I), + [TADC_INPUT_V] = TADC_VOLTAGE_CHAN( + "input", TADC_INPUT_V), + [TADC_OTG_I] = TADC_CURRENT_CHAN( + "otg", TADC_OTG_I), + [TADC_BATT_P] = TADC_POWER_CHAN( + "batt", TADC_BATT_P), + [TADC_INPUT_P] = TADC_POWER_CHAN( + "input", TADC_INPUT_P), + [TADC_THERM1_THR1] = TADC_THERM_CHAN( + "batt_warm", TADC_THERM1_THR1), + [TADC_THERM1_THR2] = TADC_THERM_CHAN( + "batt_cool", TADC_THERM1_THR2), + [TADC_THERM1_THR3] = TADC_THERM_CHAN( + "batt_cold", TADC_THERM1_THR3), + [TADC_THERM1_THR4] = TADC_THERM_CHAN( + "batt_hot", TADC_THERM1_THR4), + [TADC_THERM2_THR1] = TADC_THERM_CHAN( + "skin_lb", TADC_THERM2_THR1), + [TADC_THERM2_THR2] = TADC_THERM_CHAN( + "skin_ub", TADC_THERM2_THR2), + [TADC_THERM2_THR3] = TADC_THERM_CHAN( + "skin_rst", TADC_THERM2_THR3), + [TADC_DIE_TEMP_THR1] = TADC_THERM_CHAN( + "die_lb", TADC_DIE_TEMP_THR1), + [TADC_DIE_TEMP_THR2] = TADC_THERM_CHAN( + "die_ub", TADC_DIE_TEMP_THR2), + [TADC_DIE_TEMP_THR3] = TADC_THERM_CHAN( + "die_rst", TADC_DIE_TEMP_THR3), +}; + +struct tadc_therm_thr { + int addr_lo; + int addr_hi; +}; + +struct tadc_chan_data { + s32 scale; + s32 offset; + u32 rbias; + const struct tadc_pt *table; + size_t tablesize; + struct tadc_therm_thr thr[4]; +}; + +struct tadc_chip { + struct device *dev; + struct regmap *regmap; + u32 tadc_base; + u32 tadc_cmp_base; + struct tadc_chan_data chans[TADC_NUM_CH]; + struct completion eoc_complete; + struct mutex write_lock; + struct mutex conv_lock; + struct power_supply *usb_psy; + struct votable *tadc_disable_votable; + struct work_struct status_change_work; + struct notifier_block nb; + u8 hwtrig_conv; +}; + +struct tadc_pt { + s32 x; + s32 y; +}; + +/* + * Thermistor tables are generated by the B-parameter equation which is a + * simplifed version of the Steinhart-Hart equation. + * + * (1 / T) = (1 / T0) + (1 / B) * ln(R / R0) + * + * Where R0 is the resistance at temperature T0, and T0 is typically room + * temperature (25C). + */ +static const struct tadc_pt tadc_therm_3450b_68k[] = { + { 4151, 120000 }, + { 4648, 115000 }, + { 5220, 110000 }, + { 5880, 105000 }, + { 6644, 100000 }, + { 7533, 95000 }, + { 8571, 90000 }, + { 9786, 85000 }, + { 11216, 80000 }, + { 12906, 75000 }, + { 14910, 70000 }, + { 17300, 65000 }, + { 20163, 60000 }, + { 23609, 55000 }, + { 27780, 50000 }, + { 32855, 45000 }, + { 39065, 40000 }, + { 46712, 35000 }, + { 56185, 30000 }, + { 68000, 25000 }, + { 82837, 20000 }, + { 101604, 15000 }, + { 125525, 10000 }, + { 156261, 5000 }, + { 196090, 0 }, + { 248163, -5000 }, + { 316887, -10000 }, + { 408493, -15000 }, + { 531889, -20000 }, + { 699966, -25000 }, + { 931618, -30000 }, + { 1254910, -35000 }, + { 1712127, -40000 }, +}; + +static bool tadc_is_reg_locked(struct tadc_chip *chip, u16 reg) +{ + if ((reg & 0xFF00) == chip->tadc_cmp_base) + return true; + + if (reg >= TADC_HWTRIG_CONV_CH_EN_REG(chip)) + return true; + + return false; +} + +static int tadc_read(struct tadc_chip *chip, u16 reg, u8 *val, size_t count) +{ + int rc = 0; + + rc = regmap_bulk_read(chip->regmap, reg, val, count); + if (rc < 0) + pr_err("Couldn't read 0x%04x rc=%d\n", reg, rc); + + return rc; +} + +static int tadc_write(struct tadc_chip *chip, u16 reg, u8 data) +{ + int rc = 0; + + mutex_lock(&chip->write_lock); + if (tadc_is_reg_locked(chip, reg)) { + rc = regmap_write(chip->regmap, (reg & 0xFF00) | 0xD0, 0xA5); + if (rc < 0) { + pr_err("Couldn't unlock secure register rc=%d\n", rc); + goto unlock; + } + } + + rc = regmap_write(chip->regmap, reg, data); + if (rc < 0) { + pr_err("Couldn't write 0x%02x to 0x%04x rc=%d\n", + data, reg, rc); + goto unlock; + } + +unlock: + mutex_unlock(&chip->write_lock); + return rc; +} +static int tadc_bulk_write(struct tadc_chip *chip, u16 reg, u8 *data, + size_t count) +{ + int rc = 0, i; + + mutex_lock(&chip->write_lock); + for (i = 0; i < count; ++i, ++reg) { + if (tadc_is_reg_locked(chip, reg)) { + rc = regmap_write(chip->regmap, + (reg & 0xFF00) | 0xD0, 0xA5); + if (rc < 0) { + pr_err("Couldn't unlock secure register rc=%d\n", + rc); + goto unlock; + } + } + + rc = regmap_write(chip->regmap, reg, data[i]); + if (rc < 0) { + pr_err("Couldn't write 0x%02x to 0x%04x rc=%d\n", + data[i], reg, rc); + goto unlock; + } + } + +unlock: + mutex_unlock(&chip->write_lock); + return rc; +} + +static int tadc_masked_write(struct tadc_chip *chip, u16 reg, u8 mask, u8 data) +{ + int rc = 0; + + mutex_lock(&chip->write_lock); + if (tadc_is_reg_locked(chip, reg)) { + rc = regmap_write(chip->regmap, (reg & 0xFF00) | 0xD0, 0xA5); + if (rc < 0) { + pr_err("Couldn't unlock secure register rc=%d\n", rc); + goto unlock; + } + } + + rc = regmap_update_bits(chip->regmap, reg, mask, data); + +unlock: + mutex_unlock(&chip->write_lock); + return rc; +} + +static int tadc_lerp(const struct tadc_pt *pts, size_t size, bool inv, + s32 input, s32 *output) +{ + int i; + s64 temp; + bool ascending; + + if (pts == NULL) { + pr_err("Table is NULL\n"); + return -EINVAL; + } + + if (size < 1) { + pr_err("Table has no entries\n"); + return -ENOENT; + } + + if (size == 1) { + *output = inv ? pts[0].x : pts[0].y; + return 0; + } + + ascending = inv ? (pts[0].y < pts[1].y) : (pts[0].x < pts[1].x); + if (ascending ? (input <= (inv ? pts[0].y : pts[0].x)) : + (input >= (inv ? pts[0].y : pts[0].x))) { + *output = inv ? pts[0].x : pts[0].y; + return 0; + } + + if (ascending ? (input >= (inv ? pts[size - 1].y : pts[size - 1].x)) : + (input <= (inv ? pts[size - 1].y : pts[size - 1].x))) { + *output = inv ? pts[size - 1].x : pts[size - 1].y; + return 0; + } + + for (i = 1; i < size; i++) + if (ascending ? (input <= (inv ? pts[i].y : pts[i].x)) : + (input >= (inv ? pts[i].y : pts[i].x))) + break; + + if (inv) { + temp = (s64)(pts[i].x - pts[i - 1].x) * + (s64)(input - pts[i - 1].y); + temp = div_s64(temp, pts[i].y - pts[i - 1].y); + *output = temp + pts[i - 1].x; + } else { + temp = (s64)(pts[i].y - pts[i - 1].y) * + (s64)(input - pts[i - 1].x); + temp = div_s64(temp, pts[i].x - pts[i - 1].x); + *output = temp + pts[i - 1].y; + } + + return 0; +} + +/* + * Process the result of a thermistor reading. + * + * The voltage input to the ADC is a result of a voltage divider circuit. + * Vout = (Rtherm / (Rbias + Rtherm)) * Vbias + * + * The ADC value is based on the output voltage of the voltage divider, and the + * bias voltage. + * ADC = (Vin * 1024) / Vbias + * + * Combine these equations and solve for Rtherm + * Rtherm = (ADC * Rbias) / (1024 - ADC) + */ +static int tadc_get_processed_therm(const struct tadc_chan_data *chan_data, + s16 adc, s32 *result) +{ + s32 rtherm; + + rtherm = div_s64((s64)adc * chan_data->rbias, TADC_RESOLUTION - adc); + return tadc_lerp(chan_data->table, chan_data->tablesize, false, rtherm, + result); +} + +static int tadc_get_raw_therm(const struct tadc_chan_data *chan_data, + int mdegc, int *result) +{ + int rc; + s32 rtherm; + + rc = tadc_lerp(chan_data->table, chan_data->tablesize, true, mdegc, + &rtherm); + if (rc < 0) { + pr_err("Couldn't interpolate %d\n rc=%d\n", mdegc, rc); + return rc; + } + + *result = div64_s64((s64)rtherm * TADC_RESOLUTION, + (s64)chan_data->rbias + rtherm); + return 0; +} + +static int tadc_read_channel(struct tadc_chip *chip, u16 address, int *adc) +{ + u8 val[2]; + int rc; + + rc = tadc_read(chip, address, val, ARRAY_SIZE(val)); + if (rc < 0) { + pr_err("Couldn't read channel rc=%d\n", rc); + return rc; + } + + /* the 10th bit is the sign bit for all channels */ + *adc = sign_extend32(val[0] | val[1] << BITS_PER_BYTE, 10); + return rc; +} + +static int tadc_write_channel(struct tadc_chip *chip, u16 address, int adc) +{ + u8 val[2]; + int rc; + + /* the 10th bit is the sign bit for all channels */ + adc = sign_extend32(adc, 10); + val[0] = (u8)adc; + val[1] = (u8)(adc >> BITS_PER_BYTE); + rc = tadc_bulk_write(chip, address, val, 2); + if (rc < 0) { + pr_err("Couldn't write to channel rc=%d\n", rc); + return rc; + } + + return rc; +} + +#define CONVERSION_TIMEOUT_MS 100 +static int tadc_do_conversion(struct tadc_chip *chip, u8 channels, s16 *adc) +{ + unsigned long timeout, timeleft; + u8 val[TADC_NUM_CH * 2]; + int rc = 0, i; + + mutex_lock(&chip->conv_lock); + rc = tadc_read(chip, TADC_MBG_ERR_REG(chip), val, 1); + if (rc < 0) { + pr_err("Couldn't read mbg error status rc=%d\n", rc); + goto unlock; + } + + reinit_completion(&chip->eoc_complete); + + if (get_effective_result(chip->tadc_disable_votable)) { + /* leave it back in completed state */ + complete_all(&chip->eoc_complete); + rc = -ENODATA; + goto unlock; + } + + if (val[0] != 0) { + tadc_write(chip, TADC_EN_CTL_REG(chip), 0); + tadc_write(chip, TADC_EN_CTL_REG(chip), 0x80); + } + + rc = tadc_write(chip, TADC_CONV_REQ_REG(chip), channels); + if (rc < 0) { + pr_err("Couldn't write conversion request rc=%d\n", rc); + goto unlock; + } + + timeout = msecs_to_jiffies(CONVERSION_TIMEOUT_MS); + timeleft = wait_for_completion_timeout(&chip->eoc_complete, timeout); + + if (timeleft == 0) { + rc = tadc_read(chip, TADC_SW_CH_CONV_REG(chip), val, 1); + if (rc < 0) { + pr_err("Couldn't read conversion status rc=%d\n", rc); + goto unlock; + } + + /* + * check one last time if the channel we are requesting + * has completed conversion + */ + if (val[0] != channels) { + rc = -ETIMEDOUT; + goto unlock; + } + } + + rc = tadc_read(chip, TADC_CH1_ADC_LO_REG(chip), val, ARRAY_SIZE(val)); + if (rc < 0) { + pr_err("Couldn't read adc channels rc=%d\n", rc); + goto unlock; + } + + for (i = 0; i < TADC_NUM_CH; i++) + adc[i] = (s16)(val[i * 2] | (u16)val[i * 2 + 1] << 8); + + pr_debug("Conversion time for channels 0x%x = %dms\n", channels, + jiffies_to_msecs(timeout - timeleft)); + +unlock: + mutex_unlock(&chip->conv_lock); + return rc; +} + +static int tadc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, int *val2, + long mask) +{ + struct tadc_chip *chip = iio_priv(indio_dev); + struct tadc_chan_data *chan_data = NULL; + int rc, offset = 0, scale, scale2, scale_type; + s16 adc[TADC_NUM_CH]; + + switch (chan->channel) { + case TADC_THERM1_THR1: + case TADC_THERM1_THR2: + case TADC_THERM1_THR3: + case TADC_THERM1_THR4: + chan_data = &chip->chans[TADC_THERM1]; + break; + case TADC_THERM2_THR1: + case TADC_THERM2_THR2: + case TADC_THERM2_THR3: + chan_data = &chip->chans[TADC_THERM2]; + break; + case TADC_DIE_TEMP_THR1: + case TADC_DIE_TEMP_THR2: + case TADC_DIE_TEMP_THR3: + chan_data = &chip->chans[TADC_DIE_TEMP]; + break; + default: + if (chan->channel >= ARRAY_SIZE(chip->chans)) { + pr_err("Channel %d is out of bounds\n", chan->channel); + return -EINVAL; + } + + chan_data = &chip->chans[chan->channel]; + break; + } + + if (!chan_data) + return -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + switch (chan->channel) { + case TADC_THERM1_THR1: + case TADC_THERM2_THR1: + case TADC_DIE_TEMP_THR1: + rc = tadc_read_channel(chip, + chan_data->thr[0].addr_lo, val); + break; + case TADC_THERM1_THR2: + case TADC_THERM2_THR2: + case TADC_DIE_TEMP_THR2: + rc = tadc_read_channel(chip, + chan_data->thr[1].addr_lo, val); + break; + case TADC_THERM1_THR3: + case TADC_THERM2_THR3: + case TADC_DIE_TEMP_THR3: + rc = tadc_read_channel(chip, + chan_data->thr[2].addr_lo, val); + break; + case TADC_THERM1_THR4: + rc = tadc_read_channel(chip, + chan_data->thr[3].addr_lo, val); + break; + default: + rc = tadc_do_conversion(chip, BIT(chan->channel), adc); + if (rc < 0) { + if (rc != -ENODATA) + pr_err("Couldn't read battery current and voltage channels rc=%d\n", + rc); + return rc; + } + *val = adc[chan->channel]; + break; + } + + if (rc < 0 && rc != -ENODATA) { + pr_err("Couldn't read channel %d\n", chan->channel); + return rc; + } + + return IIO_VAL_INT; + case IIO_CHAN_INFO_PROCESSED: + switch (chan->channel) { + case TADC_THERM1: + case TADC_THERM2: + case TADC_THERM1_THR1: + case TADC_THERM1_THR2: + case TADC_THERM1_THR3: + case TADC_THERM1_THR4: + case TADC_THERM2_THR1: + case TADC_THERM2_THR2: + case TADC_THERM2_THR3: + rc = tadc_read_raw(indio_dev, chan, val, NULL, + IIO_CHAN_INFO_RAW); + if (rc < 0) + return rc; + + rc = tadc_get_processed_therm(chan_data, *val, val); + if (rc < 0) { + pr_err("Couldn't process 0x%04x from channel %d rc=%d\n", + *val, chan->channel, rc); + return rc; + } + break; + case TADC_BATT_P: + rc = tadc_do_conversion(chip, + BIT(TADC_BATT_I) | BIT(TADC_BATT_V), adc); + if (rc < 0 && rc != -ENODATA) { + pr_err("Couldn't read battery current and voltage channels rc=%d\n", + rc); + return rc; + } + + *val = adc[TADC_BATT_I] * adc[TADC_BATT_V]; + break; + case TADC_INPUT_P: + rc = tadc_do_conversion(chip, + BIT(TADC_INPUT_I) | BIT(TADC_INPUT_V), adc); + if (rc < 0 && rc != -ENODATA) { + pr_err("Couldn't read input current and voltage channels rc=%d\n", + rc); + return rc; + } + + *val = adc[TADC_INPUT_I] * adc[TADC_INPUT_V]; + break; + default: + rc = tadc_read_raw(indio_dev, chan, val, NULL, + IIO_CHAN_INFO_RAW); + if (rc < 0) + return rc; + + /* offset is optional */ + rc = tadc_read_raw(indio_dev, chan, &offset, NULL, + IIO_CHAN_INFO_OFFSET); + if (rc < 0) + return rc; + + scale_type = tadc_read_raw(indio_dev, chan, + &scale, &scale2, IIO_CHAN_INFO_SCALE); + switch (scale_type) { + case IIO_VAL_INT: + *val = *val * scale + offset; + break; + case IIO_VAL_FRACTIONAL: + *val = div_s64((s64)*val * scale + offset, + scale2); + break; + default: + return -EINVAL; + } + break; + } + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + switch (chan->channel) { + case TADC_DIE_TEMP: + case TADC_DIE_TEMP_THR1: + case TADC_DIE_TEMP_THR2: + case TADC_DIE_TEMP_THR3: + *val = chan_data->scale; + return IIO_VAL_INT; + case TADC_BATT_I: + case TADC_BATT_V: + case TADC_INPUT_I: + case TADC_INPUT_V: + case TADC_OTG_I: + *val = chan_data->scale; + *val2 = TADC_RESOLUTION; + return IIO_VAL_FRACTIONAL; + } + + return -EINVAL; + case IIO_CHAN_INFO_OFFSET: + *val = chan_data->offset; + return IIO_VAL_INT; + } + + return -EINVAL; +} + +static int tadc_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, int val2, + long mask) +{ + struct tadc_chip *chip = iio_priv(indio_dev); + const struct tadc_chan_data *chan_data; + int rc, raw; + s32 rem; + + switch (chan->channel) { + case TADC_THERM1_THR1: + case TADC_THERM1_THR2: + case TADC_THERM1_THR3: + case TADC_THERM1_THR4: + chan_data = &chip->chans[TADC_THERM1]; + break; + case TADC_THERM2_THR1: + case TADC_THERM2_THR2: + case TADC_THERM2_THR3: + chan_data = &chip->chans[TADC_THERM2]; + break; + case TADC_DIE_TEMP_THR1: + case TADC_DIE_TEMP_THR2: + case TADC_DIE_TEMP_THR3: + chan_data = &chip->chans[TADC_DIE_TEMP]; + break; + default: + if (chan->channel >= ARRAY_SIZE(chip->chans)) { + pr_err("Channel %d is out of bounds\n", chan->channel); + return -EINVAL; + } + + chan_data = &chip->chans[chan->channel]; + break; + } + + if (!chan_data) + return -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_PROCESSED: + switch (chan->channel) { + case TADC_THERM1_THR1: + case TADC_THERM1_THR2: + case TADC_THERM1_THR3: + case TADC_THERM1_THR4: + case TADC_THERM2_THR1: + case TADC_THERM2_THR2: + case TADC_THERM2_THR3: + rc = tadc_get_raw_therm(chan_data, val, &raw); + if (rc < 0) { + pr_err("Couldn't get raw value rc=%d\n", rc); + return rc; + } + break; + case TADC_DIE_TEMP_THR1: + case TADC_DIE_TEMP_THR2: + case TADC_DIE_TEMP_THR3: + /* DIV_ROUND_CLOSEST does not like negative numbers */ + raw = div_s64_rem(val - chan_data->offset, + chan_data->scale, &rem); + if (abs(rem) >= abs(chan_data->scale / 2)) + raw++; + break; + default: + return -EINVAL; + } + + rc = tadc_write_raw(indio_dev, chan, raw, 0, + IIO_CHAN_INFO_RAW); + if (rc < 0) { + pr_err("Couldn't write raw rc=%d\n", rc); + return rc; + } + + break; + case IIO_CHAN_INFO_RAW: + switch (chan->channel) { + case TADC_THERM1_THR1: + case TADC_THERM2_THR1: + case TADC_DIE_TEMP_THR1: + rc = tadc_write_channel(chip, + chan_data->thr[0].addr_lo, val); + break; + case TADC_THERM1_THR2: + case TADC_THERM2_THR2: + case TADC_DIE_TEMP_THR2: + rc = tadc_write_channel(chip, + chan_data->thr[1].addr_lo, val); + break; + case TADC_THERM1_THR3: + case TADC_THERM2_THR3: + case TADC_DIE_TEMP_THR3: + rc = tadc_write_channel(chip, + chan_data->thr[2].addr_lo, val); + break; + case TADC_THERM1_THR4: + rc = tadc_write_channel(chip, + chan_data->thr[3].addr_lo, val); + break; + default: + return -EINVAL; + } + + if (rc < 0) { + pr_err("Couldn't write channel %d\n", chan->channel); + return rc; + } + + break; + default: + return -EINVAL; + } + + return 0; +} + +static irqreturn_t handle_eoc(int irq, void *dev_id) +{ + struct tadc_chip *chip = dev_id; + + complete_all(&chip->eoc_complete); + return IRQ_HANDLED; +} + +static int tadc_disable_vote_callback(struct votable *votable, + void *data, int disable, const char *client) +{ + struct tadc_chip *chip = data; + int rc; + int timeout; + unsigned long timeleft; + + if (disable) { + timeout = msecs_to_jiffies(CONVERSION_TIMEOUT_MS); + timeleft = wait_for_completion_timeout(&chip->eoc_complete, + timeout); + if (timeleft == 0) + pr_err("Timed out waiting for eoc, disabling hw conversions regardless\n"); + + rc = tadc_read(chip, TADC_HWTRIG_CONV_CH_EN_REG(chip), + &chip->hwtrig_conv, 1); + if (rc < 0) { + pr_err("Couldn't save hw conversions rc=%d\n", rc); + return rc; + } + rc = tadc_write(chip, TADC_HWTRIG_CONV_CH_EN_REG(chip), 0x00); + if (rc < 0) { + pr_err("Couldn't disable hw conversions rc=%d\n", rc); + return rc; + } + rc = tadc_write(chip, TADC_ADC_DIRECT_TST(chip), 0x80); + if (rc < 0) { + pr_err("Couldn't enable direct test mode rc=%d\n", rc); + return rc; + } + } else { + rc = tadc_write(chip, TADC_ADC_DIRECT_TST(chip), 0x00); + if (rc < 0) { + pr_err("Couldn't disable direct test mode rc=%d\n", rc); + return rc; + } + rc = tadc_write(chip, TADC_HWTRIG_CONV_CH_EN_REG(chip), + chip->hwtrig_conv); + if (rc < 0) { + pr_err("Couldn't restore hw conversions rc=%d\n", rc); + return rc; + } + } + + pr_debug("client: %s disable: %d\n", client, disable); + return 0; +} + +static void status_change_work(struct work_struct *work) +{ + struct tadc_chip *chip = container_of(work, + struct tadc_chip, status_change_work); + union power_supply_propval pval = {0, }; + int rc; + + if (!chip->usb_psy) + chip->usb_psy = power_supply_get_by_name("usb"); + + if (!chip->usb_psy) { + /* treat usb is not present */ + vote(chip->tadc_disable_votable, USB_PRESENT_VOTER, true, 0); + return; + } + + rc = power_supply_get_property(chip->usb_psy, + POWER_SUPPLY_PROP_PRESENT, &pval); + if (rc < 0) { + pr_err("Couldn't get present status rc=%d\n", rc); + /* treat usb is not present */ + vote(chip->tadc_disable_votable, USB_PRESENT_VOTER, true, 0); + return; + } + + /* disable if usb is not present */ + vote(chip->tadc_disable_votable, USB_PRESENT_VOTER, !pval.intval, 0); +} + +static int tadc_notifier_call(struct notifier_block *nb, + unsigned long ev, void *v) +{ + struct power_supply *psy = v; + struct tadc_chip *chip = container_of(nb, struct tadc_chip, nb); + + if (ev != PSY_EVENT_PROP_CHANGED) + return NOTIFY_OK; + + if ((strcmp(psy->desc->name, "usb") == 0)) + schedule_work(&chip->status_change_work); + + return NOTIFY_OK; +} + +static int tadc_register_notifier(struct tadc_chip *chip) +{ + int rc; + + chip->nb.notifier_call = tadc_notifier_call; + rc = power_supply_reg_notifier(&chip->nb); + if (rc < 0) { + pr_err("Couldn't register psy notifier rc = %d\n", rc); + return rc; + } + + return 0; +} + +static int tadc_suspend(struct device *dev) +{ + struct tadc_chip *chip = dev_get_drvdata(dev); + + vote(chip->tadc_disable_votable, SLEEP_VOTER, true, 0); + return 0; +} + +static int tadc_resume(struct device *dev) +{ + struct tadc_chip *chip = dev_get_drvdata(dev); + + vote(chip->tadc_disable_votable, SLEEP_VOTER, false, 0); + return 0; +} + +static int tadc_set_therm_table(struct tadc_chan_data *chan_data, u32 beta, + u32 rtherm) +{ + if (beta == 3450 && rtherm == 68000) { + chan_data->table = tadc_therm_3450b_68k; + chan_data->tablesize = ARRAY_SIZE(tadc_therm_3450b_68k); + return 0; + } + + return -ENOENT; +} + +static int tadc_parse_dt(struct tadc_chip *chip) +{ + struct device_node *child, *node; + struct tadc_chan_data *chan_data; + u32 chan_id, rtherm, beta; + int rc = 0; + + node = chip->dev->of_node; + for_each_available_child_of_node(node, child) { + rc = of_property_read_u32(child, "reg", &chan_id); + if (rc < 0) { + pr_err("Couldn't find channel for %s rc=%d\n", + child->name, rc); + return rc; + } + + if (chan_id > TADC_NUM_CH - 1) { + pr_err("Channel %d is out of range [0, %d]\n", + chan_id, TADC_NUM_CH - 1); + return -EINVAL; + } + + chan_data = &chip->chans[chan_id]; + if (chan_id == TADC_THERM1 || chan_id == TADC_THERM2) { + rc = of_property_read_u32(child, + "qcom,rbias", &chan_data->rbias); + if (rc < 0) { + pr_err("Couldn't read qcom,rbias rc=%d\n", rc); + return rc; + } + + rc = of_property_read_u32(child, + "qcom,beta-coefficient", &beta); + if (rc < 0) { + pr_err("Couldn't read qcom,beta-coefficient rc=%d\n", + rc); + return rc; + } + + rc = of_property_read_u32(child, + "qcom,rtherm-at-25degc", &rtherm); + if (rc < 0) { + pr_err("Couldn't read qcom,rtherm-at-25degc rc=%d\n", + rc); + return rc; + } + + rc = tadc_set_therm_table(chan_data, beta, rtherm); + if (rc < 0) { + pr_err("Couldn't set therm table rc=%d\n", rc); + return rc; + } + } else { + rc = of_property_read_s32(child, "qcom,scale", + &chan_data->scale); + if (rc < 0) { + pr_err("Couldn't read scale rc=%d\n", rc); + return rc; + } + + of_property_read_s32(child, "qcom,offset", + &chan_data->offset); + } + } + + return rc; +} + +static int tadc_init_hw(struct tadc_chip *chip) +{ + int rc; + + chip->chans[TADC_THERM1].thr[0].addr_lo = + TADC_CMP_THR1_CH1_CMP_LO_REG(chip); + chip->chans[TADC_THERM1].thr[0].addr_hi = + TADC_CMP_THR1_CH1_CMP_HI_REG(chip); + chip->chans[TADC_THERM1].thr[1].addr_lo = + TADC_CMP_THR2_CH1_CMP_LO_REG(chip); + chip->chans[TADC_THERM1].thr[1].addr_hi = + TADC_CMP_THR2_CH1_CMP_HI_REG(chip); + chip->chans[TADC_THERM1].thr[2].addr_lo = + TADC_CMP_THR3_CH1_CMP_LO_REG(chip); + chip->chans[TADC_THERM1].thr[2].addr_hi = + TADC_CMP_THR3_CH1_CMP_HI_REG(chip); + chip->chans[TADC_THERM1].thr[3].addr_lo = + TADC_CMP_THR4_CH1_CMP_LO_REG(chip); + chip->chans[TADC_THERM1].thr[3].addr_hi = + TADC_CMP_THR4_CH1_CMP_HI_REG(chip); + + chip->chans[TADC_THERM2].thr[0].addr_lo = + TADC_CMP_THR1_CH2_CMP_LO_REG(chip); + chip->chans[TADC_THERM2].thr[0].addr_hi = + TADC_CMP_THR1_CH2_CMP_HI_REG(chip); + chip->chans[TADC_THERM2].thr[1].addr_lo = + TADC_CMP_THR2_CH2_CMP_LO_REG(chip); + chip->chans[TADC_THERM2].thr[1].addr_hi = + TADC_CMP_THR2_CH2_CMP_HI_REG(chip); + chip->chans[TADC_THERM2].thr[2].addr_lo = + TADC_CMP_THR3_CH2_CMP_LO_REG(chip); + chip->chans[TADC_THERM2].thr[2].addr_hi = + TADC_CMP_THR3_CH2_CMP_HI_REG(chip); + + chip->chans[TADC_DIE_TEMP].thr[0].addr_lo = + TADC_CMP_THR1_CH3_CMP_LO_REG(chip); + chip->chans[TADC_DIE_TEMP].thr[0].addr_hi = + TADC_CMP_THR1_CH3_CMP_HI_REG(chip); + chip->chans[TADC_DIE_TEMP].thr[1].addr_lo = + TADC_CMP_THR2_CH3_CMP_LO_REG(chip); + chip->chans[TADC_DIE_TEMP].thr[1].addr_hi = + TADC_CMP_THR2_CH3_CMP_HI_REG(chip); + chip->chans[TADC_DIE_TEMP].thr[2].addr_lo = + TADC_CMP_THR3_CH3_CMP_LO_REG(chip); + chip->chans[TADC_DIE_TEMP].thr[2].addr_hi = + TADC_CMP_THR3_CH3_CMP_HI_REG(chip); + + rc = tadc_write(chip, TADC_CMP_THR1_CMP_REG(chip), 0); + if (rc < 0) { + pr_err("Couldn't enable hardware triggers rc=%d\n", rc); + return rc; + } + + rc = tadc_write(chip, TADC_CMP_THR2_CMP_REG(chip), 0); + if (rc < 0) { + pr_err("Couldn't enable hardware triggers rc=%d\n", rc); + return rc; + } + + rc = tadc_write(chip, TADC_CMP_THR3_CMP_REG(chip), 0); + if (rc < 0) { + pr_err("Couldn't enable hardware triggers rc=%d\n", rc); + return rc; + } + + /* enable connector and die temp hardware triggers */ + rc = tadc_masked_write(chip, TADC_HWTRIG_CONV_CH_EN_REG(chip), + BIT(TADC_THERM2) | BIT(TADC_DIE_TEMP), + BIT(TADC_THERM2) | BIT(TADC_DIE_TEMP)); + if (rc < 0) { + pr_err("Couldn't enable hardware triggers rc=%d\n", rc); + return rc; + } + + /* save hw triggered conversion configuration */ + rc = tadc_read(chip, TADC_HWTRIG_CONV_CH_EN_REG(chip), + &chip->hwtrig_conv, 1); + if (rc < 0) { + pr_err("Couldn't save hw conversions rc=%d\n", rc); + return rc; + } + + return 0; +} + +static const struct iio_info tadc_info = { + .read_raw = &tadc_read_raw, + .write_raw = &tadc_write_raw, +}; + +static int tadc_probe(struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node; + struct iio_dev *indio_dev; + struct tadc_chip *chip; + int rc, irq; + + indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*chip)); + if (!indio_dev) + return -ENOMEM; + + chip = iio_priv(indio_dev); + chip->dev = &pdev->dev; + init_completion(&chip->eoc_complete); + + /* + * set the completion in "completed" state so disable of the tadc + * can progress + */ + complete_all(&chip->eoc_complete); + + rc = of_property_read_u32(node, "reg", &chip->tadc_base); + if (rc < 0) { + pr_err("Couldn't read base address rc=%d\n", rc); + return rc; + } + chip->tadc_cmp_base = chip->tadc_base + 0x100; + + mutex_init(&chip->write_lock); + mutex_init(&chip->conv_lock); + INIT_WORK(&chip->status_change_work, status_change_work); + chip->regmap = dev_get_regmap(chip->dev->parent, NULL); + if (!chip->regmap) { + pr_err("Couldn't get regmap\n"); + return -ENODEV; + } + + rc = tadc_parse_dt(chip); + if (rc < 0) { + pr_err("Couldn't parse device tree rc=%d\n", rc); + return rc; + } + + rc = tadc_init_hw(chip); + if (rc < 0) { + pr_err("Couldn't initialize hardware rc=%d\n", rc); + return rc; + } + + chip->tadc_disable_votable = create_votable("SMB_TADC_DISABLE", + VOTE_SET_ANY, + tadc_disable_vote_callback, + chip); + if (IS_ERR(chip->tadc_disable_votable)) { + rc = PTR_ERR(chip->tadc_disable_votable); + return rc; + } + /* assume usb is not present */ + vote(chip->tadc_disable_votable, USB_PRESENT_VOTER, true, 0); + vote(chip->tadc_disable_votable, SHUTDOWN_VOTER, false, 0); + vote(chip->tadc_disable_votable, SLEEP_VOTER, false, 0); + + rc = tadc_register_notifier(chip); + if (rc < 0) { + pr_err("Couldn't register notifier=%d\n", rc); + goto destroy_votable; + } + + irq = of_irq_get_byname(node, "eoc"); + if (irq < 0) { + pr_err("Couldn't get eoc irq rc=%d\n", irq); + goto destroy_votable; + } + + rc = devm_request_threaded_irq(chip->dev, irq, NULL, handle_eoc, + IRQF_ONESHOT, "eoc", chip); + if (rc < 0) { + pr_err("Couldn't request irq %d rc=%d\n", irq, rc); + goto destroy_votable; + } + + indio_dev->dev.parent = chip->dev; + indio_dev->name = pdev->name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &tadc_info; + indio_dev->channels = tadc_iio_chans; + indio_dev->num_channels = ARRAY_SIZE(tadc_iio_chans); + + rc = devm_iio_device_register(chip->dev, indio_dev); + if (rc < 0) { + pr_err("Couldn't register IIO device rc=%d\n", rc); + goto destroy_votable; + } + + platform_set_drvdata(pdev, chip); + return 0; + +destroy_votable: + destroy_votable(chip->tadc_disable_votable); + return rc; +} + +static int tadc_remove(struct platform_device *pdev) +{ + struct tadc_chip *chip = platform_get_drvdata(pdev); + + destroy_votable(chip->tadc_disable_votable); + return 0; +} + +static void tadc_shutdown(struct platform_device *pdev) +{ + struct tadc_chip *chip = platform_get_drvdata(pdev); + + vote(chip->tadc_disable_votable, SHUTDOWN_VOTER, true, 0); +} + +static const struct dev_pm_ops tadc_pm_ops = { + .resume = tadc_resume, + .suspend = tadc_suspend, +}; + +static const struct of_device_id tadc_match_table[] = { + { .compatible = "qcom,tadc" }, + { } +}; +MODULE_DEVICE_TABLE(of, tadc_match_table); + +static struct platform_driver tadc_driver = { + .driver = { + .name = "qcom-tadc", + .of_match_table = tadc_match_table, + .pm = &tadc_pm_ops, + }, + .probe = tadc_probe, + .remove = tadc_remove, + .shutdown = tadc_shutdown, +}; +module_platform_driver(tadc_driver); + +MODULE_DESCRIPTION("Qualcomm Technologies Inc. TADC driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 1679b6629641..723723a166e3 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -551,6 +551,15 @@ config MEMORY_STATE_TIME help Memory time statistics exported to /sys/kernel/memory_state_time +config QPNP_MISC + tristate "QPNP Misc Peripheral" + depends on MFD_SPMI_PMIC + help + Say 'y' here to include support for the QTI QPNP MISC + peripheral. The MISC peripheral holds the USB ID interrupt + and the driver provides an API to check if this interrupt + is available on the current PMIC chip. + config OKL4_USER_VIRQ tristate "User space accessible OKL4 virtual interrupts" depends on OKL4_GUEST diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index b79e6325a54c..6ce4288010f7 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -63,6 +63,7 @@ obj-$(CONFIG_OCXL) += ocxl/ obj-$(CONFIG_MISC_RTSX) += cardreader/ obj-$(CONFIG_UID_SYS_STATS) += uid_sys_stats.o obj-$(CONFIG_MEMORY_STATE_TIME) += memory_state_time.o +obj-$(CONFIG_QPNP_MISC) += qpnp-misc.o obj-$(CONFIG_OKL4_USER_VIRQ) += okl4-virq.o obj-$(CONFIG_OKL4_RINGBUF) += okl4-ringbuf.o diff --git a/drivers/misc/qpnp-misc.c b/drivers/misc/qpnp-misc.c new file mode 100644 index 000000000000..acf7a812cc1e --- /dev/null +++ b/drivers/misc/qpnp-misc.c @@ -0,0 +1,487 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2013-2014,2016-2017, 2019-2020, The Linux Foundation. All rights reserved. + */ +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define QPNP_MISC_DEV_NAME "qcom,qpnp-misc" + +#define REG_DIG_MAJOR_REV 0x01 +#define REG_SUBTYPE 0x05 +#define REG_PWM_SEL 0x49 +#define REG_GP_DRIVER_EN 0x4C + +#define PWM_SEL_MAX 0x03 +#define GP_DRIVER_EN_BIT BIT(0) + +enum twm { + TWM_MODE_1 = 1, + TWM_MODE_2, + TWM_MODE_3, +}; + +enum twm_attrib { + TWM_ENABLE, + TWM_EXIT, +}; + +static DEFINE_MUTEX(qpnp_misc_dev_list_mutex); +static LIST_HEAD(qpnp_misc_dev_list); +static RAW_NOTIFIER_HEAD(twm_notifier); + +struct qpnp_misc_version { + u8 subtype; + u8 dig_major_rev; +}; + +/** + * struct qpnp_misc_dev - holds controller device specific information + * @list: Doubly-linked list parameter linking to other + * qpnp_misc devices. + * @mutex: Mutex lock that is used to ensure mutual + * exclusion between probing and accessing misc + * driver information + * @dev: Device pointer to the misc device + * @regmap: Regmap pointer to the misc device + * @version: struct that holds the subtype and dig_major_rev + * of the chip. + */ +struct qpnp_misc_dev { + struct list_head list; + struct mutex mutex; + struct device *dev; + struct regmap *regmap; + struct qpnp_misc_version version; + struct class twm_class; + + u8 twm_mode; + u32 base; + u8 pwm_sel; + bool enable_gp_driver; + bool support_twm_config; + bool twm_enable; +}; + +static const struct of_device_id qpnp_misc_match_table[] = { + { .compatible = QPNP_MISC_DEV_NAME }, + {} +}; + +enum qpnp_misc_version_name { + INVALID, + PM8941, + PM8226, + PMA8084, + PMDCALIFORNIUM, +}; + +static struct qpnp_misc_version irq_support_version[] = { + {0x00, 0x00}, /* INVALID */ + {0x01, 0x02}, /* PM8941 */ + {0x07, 0x00}, /* PM8226 */ + {0x09, 0x00}, /* PMA8084 */ + {0x16, 0x00}, /* PMDCALIFORNIUM */ +}; + +static int qpnp_write_byte(struct qpnp_misc_dev *mdev, u16 addr, u8 val) +{ + int rc; + + rc = regmap_write(mdev->regmap, mdev->base + addr, val); + if (rc) + pr_err("regmap write failed rc=%d\n", rc); + + return rc; +} + +static int qpnp_read_byte(struct qpnp_misc_dev *mdev, u16 addr, u8 *val) +{ + unsigned int temp; + int rc; + + rc = regmap_read(mdev->regmap, mdev->base + addr, &temp); + if (rc) { + pr_err("regmap read failed rc=%d\n", rc); + return rc; + } + + *val = (u8)temp; + return rc; +} + +static int get_qpnp_misc_version_name(struct qpnp_misc_dev *dev) +{ + int i; + + for (i = 1; i < ARRAY_SIZE(irq_support_version); i++) + if (dev->version.subtype == irq_support_version[i].subtype && + dev->version.dig_major_rev >= + irq_support_version[i].dig_major_rev) + return i; + + return INVALID; +} + +static bool __misc_irqs_available(struct qpnp_misc_dev *dev) +{ + int version_name = get_qpnp_misc_version_name(dev); + + if (version_name == INVALID) + return false; + return true; +} + +int qpnp_misc_read_reg(struct device_node *node, u16 addr, u8 *val) +{ + struct qpnp_misc_dev *mdev = NULL; + struct qpnp_misc_dev *mdev_found = NULL; + int rc; + u8 temp = 0; + + if (IS_ERR_OR_NULL(node)) { + pr_err("Invalid device node pointer\n"); + return -EINVAL; + } + + mutex_lock(&qpnp_misc_dev_list_mutex); + list_for_each_entry(mdev, &qpnp_misc_dev_list, list) { + if (mdev->dev->of_node == node) { + mdev_found = mdev; + break; + } + } + mutex_unlock(&qpnp_misc_dev_list_mutex); + + if (!mdev_found) { + /* + * No MISC device was found. This API should only + * be called by drivers which have specified the + * misc phandle in their device tree node. + */ + pr_err("no probed misc device found\n"); + return -EPROBE_DEFER; + } + + rc = qpnp_read_byte(mdev, addr, &temp); + if (rc < 0) { + dev_err(mdev->dev, "Failed to read addr %x, rc=%d\n", addr, rc); + return rc; + } + + *val = temp; + return 0; +} + +int qpnp_misc_irqs_available(struct device *consumer_dev) +{ + struct device_node *misc_node = NULL; + struct qpnp_misc_dev *mdev = NULL; + struct qpnp_misc_dev *mdev_found = NULL; + + if (IS_ERR_OR_NULL(consumer_dev)) { + pr_err("Invalid consumer device pointer\n"); + return -EINVAL; + } + + misc_node = of_parse_phandle(consumer_dev->of_node, "qcom,misc-ref", 0); + if (!misc_node) { + pr_debug("Could not find qcom,misc-ref property in %s\n", + consumer_dev->of_node->full_name); + return 0; + } + + mutex_lock(&qpnp_misc_dev_list_mutex); + list_for_each_entry(mdev, &qpnp_misc_dev_list, list) { + if (mdev->dev->of_node == misc_node) { + mdev_found = mdev; + break; + } + } + mutex_unlock(&qpnp_misc_dev_list_mutex); + + if (!mdev_found) { + /* + * No MISC device was found. This API should only + * be called by drivers which have specified the + * misc phandle in their device tree node. + */ + pr_err("no probed misc device found\n"); + return -EPROBE_DEFER; + } + + return __misc_irqs_available(mdev_found); +} + +#define MISC_SPARE_1 0x50 +#define MISC_SPARE_2 0x51 +#define ENABLE_TWM_MODE 0x80 +#define DISABLE_TWM_MODE 0x0 +#define TWM_EXIT_BIT BIT(0) +static ssize_t twm_enable_store(struct class *c, + struct class_attribute *attr, + const char *buf, size_t count) +{ + struct qpnp_misc_dev *mdev = container_of(c, + struct qpnp_misc_dev, twm_class); + u8 val = 0; + ssize_t rc = 0; + + rc = kstrtou8(buf, 10, &val); + if (rc < 0) + return rc; + + mdev->twm_enable = val ? true : false; + + /* Notify the TWM state */ + raw_notifier_call_chain(&twm_notifier, + mdev->twm_enable ? PMIC_TWM_ENABLE : PMIC_TWM_CLEAR, NULL); + + return count; +} + +static ssize_t twm_enable_show(struct class *c, + struct class_attribute *attr, char *buf) +{ + struct qpnp_misc_dev *mdev = container_of(c, + struct qpnp_misc_dev, twm_class); + + return scnprintf(buf, PAGE_SIZE, "%d\n", mdev->twm_enable); +} +static CLASS_ATTR_RW(twm_enable); + +static ssize_t twm_exit_show(struct class *c, + struct class_attribute *attr, char *buf) +{ + struct qpnp_misc_dev *mdev = container_of(c, + struct qpnp_misc_dev, twm_class); + int rc = 0; + u8 val = 0; + + rc = qpnp_read_byte(mdev, MISC_SPARE_1, &val); + if (rc < 0) { + pr_err("Failed to read TWM enable (misc_spare_1) rc=%d\n", rc); + return rc; + } + + pr_debug("TWM_EXIT (misc_spare_1) register = 0x%02x\n", val); + + return scnprintf(buf, PAGE_SIZE, "%d\n", !!(val & TWM_EXIT_BIT)); +} +static CLASS_ATTR_RO(twm_exit); + +static struct attribute *twm_attrs[] = { + &class_attr_twm_enable.attr, + &class_attr_twm_exit.attr, + NULL, +}; +ATTRIBUTE_GROUPS(twm); + +int qpnp_misc_twm_notifier_register(struct notifier_block *nb) +{ + return raw_notifier_chain_register(&twm_notifier, nb); +} +EXPORT_SYMBOL(qpnp_misc_twm_notifier_register); + +int qpnp_misc_twm_notifier_unregister(struct notifier_block *nb) +{ + return raw_notifier_chain_unregister(&twm_notifier, nb); +} +EXPORT_SYMBOL(qpnp_misc_twm_notifier_unregister); + +static int qpnp_misc_dt_init(struct qpnp_misc_dev *mdev) +{ + struct device_node *node = mdev->dev->of_node; + u32 val; + int rc; + + if (of_property_read_bool(mdev->dev->of_node, + "qcom,support-twm-config")) { + mdev->support_twm_config = true; + mdev->twm_mode = TWM_MODE_3; + rc = of_property_read_u8(mdev->dev->of_node, "qcom,twm-mode", + &mdev->twm_mode); + if (!rc && (mdev->twm_mode < TWM_MODE_1 || + mdev->twm_mode > TWM_MODE_3)) { + pr_err("Invalid TWM mode %d\n", mdev->twm_mode); + return -EINVAL; + } + } + + rc = of_property_read_u32(node, "reg", &mdev->base); + if (rc < 0 || !mdev->base) { + dev_err(mdev->dev, "Base address not defined or invalid\n"); + return -EINVAL; + } + + if (!of_property_read_u32(node, "qcom,pwm-sel", &val)) { + if (val > PWM_SEL_MAX) { + dev_err(mdev->dev, "Invalid value for pwm-sel\n"); + return -EINVAL; + } + mdev->pwm_sel = (u8)val; + } + mdev->enable_gp_driver = of_property_read_bool(node, + "qcom,enable-gp-driver"); + + WARN((mdev->pwm_sel > 0 && !mdev->enable_gp_driver), + "Setting PWM source without enabling gp driver\n"); + WARN((mdev->pwm_sel == 0 && mdev->enable_gp_driver), + "Enabling gp driver without setting PWM source\n"); + + return 0; +} + +static int qpnp_misc_config(struct qpnp_misc_dev *mdev) +{ + int rc, version_name; + + version_name = get_qpnp_misc_version_name(mdev); + + switch (version_name) { + case PMDCALIFORNIUM: + if (mdev->pwm_sel > 0 && mdev->enable_gp_driver) { + rc = qpnp_write_byte(mdev, REG_PWM_SEL, mdev->pwm_sel); + if (rc < 0) { + dev_err(mdev->dev, + "Failed to write PWM_SEL reg\n"); + return rc; + } + + rc = qpnp_write_byte(mdev, REG_GP_DRIVER_EN, + GP_DRIVER_EN_BIT); + if (rc < 0) { + dev_err(mdev->dev, + "Failed to write GP_DRIVER_EN reg\n"); + return rc; + } + } + break; + default: + break; + } + + if (mdev->support_twm_config) { + mdev->twm_class.name = "pmic_twm", + mdev->twm_class.owner = THIS_MODULE, + mdev->twm_class.class_groups = twm_groups; + + rc = class_register(&mdev->twm_class); + if (rc < 0) { + pr_err("Failed to register pmic_twm class rc=%d\n", rc); + return rc; + } + } + + return 0; +} + +static int qpnp_misc_probe(struct platform_device *pdev) +{ + struct qpnp_misc_dev *mdev = ERR_PTR(-EINVAL); + int rc; + + mdev = devm_kzalloc(&pdev->dev, sizeof(*mdev), GFP_KERNEL); + if (!mdev) + return -ENOMEM; + + mdev->dev = &pdev->dev; + dev_set_drvdata(&pdev->dev, mdev); + mdev->regmap = dev_get_regmap(mdev->dev->parent, NULL); + if (!mdev->regmap) { + dev_err(mdev->dev, "Parent regmap is unavailable\n"); + return -ENXIO; + } + + rc = qpnp_misc_dt_init(mdev); + if (rc < 0) { + dev_err(mdev->dev, + "Error reading device tree properties, rc=%d\n", rc); + return rc; + } + + + rc = qpnp_read_byte(mdev, REG_SUBTYPE, &mdev->version.subtype); + if (rc < 0) { + dev_err(mdev->dev, "Failed to read subtype, rc=%d\n", rc); + return rc; + } + + rc = qpnp_read_byte(mdev, REG_DIG_MAJOR_REV, + &mdev->version.dig_major_rev); + if (rc < 0) { + dev_err(mdev->dev, "Failed to read dig_major_rev, rc=%d\n", rc); + return rc; + } + + mutex_lock(&qpnp_misc_dev_list_mutex); + list_add_tail(&mdev->list, &qpnp_misc_dev_list); + mutex_unlock(&qpnp_misc_dev_list_mutex); + + rc = qpnp_misc_config(mdev); + if (rc < 0) { + dev_err(mdev->dev, + "Error configuring module registers, rc=%d\n", rc); + return rc; + } + + dev_info(mdev->dev, "probe successful\n"); + return 0; +} + +static void qpnp_misc_shutdown(struct platform_device *pdev) +{ + struct qpnp_misc_dev *mdev = dev_get_drvdata(&pdev->dev); + int rc; + + if (mdev->support_twm_config) { + rc = qpnp_write_byte(mdev, MISC_SPARE_2, + mdev->twm_enable ? mdev->twm_mode : 0x0); + if (rc < 0) + pr_err("Failed to write MISC_SPARE_2 (twm_mode) val=%d rc=%d\n", + mdev->twm_enable ? mdev->twm_mode : 0x0, rc); + + rc = qpnp_write_byte(mdev, MISC_SPARE_1, + mdev->twm_enable ? ENABLE_TWM_MODE : 0x0); + if (rc < 0) + pr_err("Failed to write MISC_SPARE_1 (twm_state) val=%d rc=%d\n", + mdev->twm_enable ? ENABLE_TWM_MODE : 0x0, rc); + + pr_debug("PMIC configured for TWM-%s MODE=%d\n", + mdev->twm_enable ? "enabled" : "disabled", + mdev->twm_mode); + } +} + +static struct platform_driver qpnp_misc_driver = { + .probe = qpnp_misc_probe, + .shutdown = qpnp_misc_shutdown, + .driver = { + .name = QPNP_MISC_DEV_NAME, + .of_match_table = qpnp_misc_match_table, + }, +}; + +static int __init qpnp_misc_init(void) +{ + return platform_driver_register(&qpnp_misc_driver); +} + +static void __exit qpnp_misc_exit(void) +{ + return platform_driver_unregister(&qpnp_misc_driver); +} + +subsys_initcall(qpnp_misc_init); +module_exit(qpnp_misc_exit); + +MODULE_DESCRIPTION(QPNP_MISC_DEV_NAME); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" QPNP_MISC_DEV_NAME); diff --git a/drivers/power/supply/qcom/Kconfig b/drivers/power/supply/qcom/Kconfig index af7cf6aa702b..c52014ece27c 100644 --- a/drivers/power/supply/qcom/Kconfig +++ b/drivers/power/supply/qcom/Kconfig @@ -2,6 +2,19 @@ menu "Qualcomm Technologies, Inc. Charger and Fuel Gauge support" +config QPNP_SMB2 + tristate "SMB2 Battery Charger" + depends on MFD_SPMI_PMIC + help + Say Y to enables support for the SMB2 charging peripheral. + The QPNP SMB2 charger driver supports the charger peripheral + present in the PMICOBALT chip. + The power supply framework is used to communicate battery and + usb properties to userspace and other driver consumers such + as fuel gauge, USB, and USB-PD. + VBUS and VCONN regulators are registered for supporting OTG, + and powered Type-C cables respectively. + config QPNP_SMB5 tristate "SMB5 Battery Charger" depends on MFD_SPMI_PMIC @@ -27,6 +40,17 @@ config QPNP_SMBLITE as fuel gauge and USB. VBUS regulator is registered for supporting OTG. +config SMB138X_CHARGER + tristate "SMB138X Battery Charger" + depends on MFD_I2C_PMIC + help + Say Y to include support for SMB138X Battery Charger. + SMB1380 is a dual phase 6A battery charger, and SMB1381 is a single + phase 5A battery charger. + The driver supports charger enable/disable. + The driver reports the charger status via the power supply framework. + A charger status change triggers an IRQ via the device STAT pin. + config SMB1390_CHARGE_PUMP_PSY tristate "SMB1390 power supply framework based driver" depends on MFD_I2C_PMIC @@ -39,6 +63,16 @@ config SMB1390_CHARGE_PUMP_PSY Technologies, Inc.’s family of standalone chargers to enable a high current, low profile Li+ battery charging system. +config SMB1351_USB_CHARGER + tristate "smb1351 usb charger (with VBUS detection)" + depends on I2C + help + Say Y to enable support for the SMB1351 switching mode based charger. + The driver supports charging control (enable/disable) and + charge-current limiting. It also provides USB VBUS detection and + notification support. The driver controls SMB1351 via I2C and + supports device-tree interface. + config SMB1355_SLAVE_CHARGER tristate "SMB1355 Slave Battery Charger" depends on MFD_I2C_PMIC @@ -58,6 +92,15 @@ config QPNP_QNOVO5 the hardware module. It also allows userspace code to read diagnostics of voltage and current measured during certain phases of the pulses. +config QPNP_FG_GEN3 + tristate "QPNP GEN3 fuel gauge driver" + depends on MFD_SPMI_PMIC + help + Say Y here to enable the GEN3 Fuel Gauge driver. This adds support + for battery fuel gauging and state of charge of battery connected to + the fuel gauge. The state of charge is reported through a BMS power + supply property and also sends uevents when the capacity is updated. + config QPNP_FG_GEN4 tristate "QPNP GEN4 fuel gauge driver" depends on MFD_SPMI_PMIC diff --git a/drivers/power/supply/qcom/Makefile b/drivers/power/supply/qcom/Makefile index 1035c9d0bc0f..397decf8753a 100644 --- a/drivers/power/supply/qcom/Makefile +++ b/drivers/power/supply/qcom/Makefile @@ -3,6 +3,10 @@ obj-$(CONFIG_QPNP_SMB5) += step-chg-jeita.o battery.o qpnp-smb5.o smb5-lib.o pmic-voter.o storm-watch.o schgm-flash.o obj-$(CONFIG_SMB1390_CHARGE_PUMP_PSY) += smb1390-charger-psy.o pmic-voter.o obj-$(CONFIG_SMB1355_SLAVE_CHARGER) += smb1355-charger.o pmic-voter.o +obj-$(CONFIG_SMB1351_USB_CHARGER) += smb1351-charger.o pmic-voter.o battery.o +obj-$(CONFIG_QPNP_SMB2) += step-chg-jeita.o battery.o qpnp-smb2.o smb-lib.o pmic-voter.o storm-watch.o +obj-$(CONFIG_SMB138X_CHARGER) += step-chg-jeita.o smb138x-charger.o smb-lib.o pmic-voter.o storm-watch.o battery.o +obj-$(CONFIG_QPNP_FG_GEN3) += qpnp-fg-gen3.o fg-memif.o fg-util.o obj-$(CONFIG_QPNP_QNOVO5) += qpnp-qnovo5.o battery.o pmic-voter.o obj-$(CONFIG_QPNP_FG_GEN4) += qpnp-fg-gen4.o fg-memif.o fg-util.o fg-alg.o pmic-voter.o obj-$(CONFIG_QPNP_QG) += qpnp-qg.o pmic-voter.o qg-util.o qg-soc.o qg-sdam.o qg-battery-profile.o qg-profile-lib.o fg-alg.o diff --git a/drivers/power/supply/qcom/fg-core.h b/drivers/power/supply/qcom/fg-core.h index da5e9196e260..aa3c9b13c999 100644 --- a/drivers/power/supply/qcom/fg-core.h +++ b/drivers/power/supply/qcom/fg-core.h @@ -6,6 +6,7 @@ #ifndef __FG_CORE_H__ #define __FG_CORE_H__ +#include #include #include #include @@ -16,9 +17,11 @@ #include #include #include +#include #include #include #include +#include #include #include #include @@ -57,6 +60,7 @@ #define PROFILE_LOAD "fg_profile_load" #define TTF_PRIMING "fg_ttf_priming" #define ESR_CALIB "fg_esr_calib" +#define FG_ESR_VOTER "fg_esr_voter" /* Delta BSOC irq votable reasons */ #define DELTA_BSOC_IRQ_VOTER "fg_delta_bsoc_irq" @@ -108,6 +112,11 @@ enum fg_debug_flag { FG_FVSS = BIT(9), /* Show FVSS */ }; +enum awake_reasons { + FG_SW_ESR_WAKE = BIT(0), + FG_STATUS_NOTIFY_WAKE = BIT(1), +}; + /* SRAM access */ enum sram_access_flags { FG_IMA_DEFAULT = 0, @@ -203,6 +212,7 @@ enum fg_sram_param_id { FG_SRAM_DELTA_MSOC_THR, FG_SRAM_DELTA_BSOC_THR, FG_SRAM_RECHARGE_SOC_THR, + FG_SRAM_SYNC_SLEEP_THR, FG_SRAM_RECHARGE_VBATT_THR, FG_SRAM_KI_COEFF_LOW_DISCHG, FG_SRAM_KI_COEFF_MED_DISCHG, @@ -293,6 +303,12 @@ enum slope_limit_status { SLOPE_LIMIT_NUM_COEFFS, }; +enum esr_filter_status { + ROOM_TEMP = 1, + LOW_TEMP, + RELAX_TEMP, +}; + enum esr_timer_config { TIMER_RETRY = 0, TIMER_MAX, @@ -323,7 +339,7 @@ struct fg_cyc_ctr_data { bool started[BUCKET_COUNT]; u16 count[BUCKET_COUNT]; u8 last_soc[BUCKET_COUNT]; - int id; + char counter[BUCKET_COUNT * 8]; struct mutex lock; }; @@ -428,15 +444,21 @@ struct fg_dev { int *debug_mask; struct fg_batt_props bp; struct notifier_block nb; + struct alarm esr_sw_timer; + struct notifier_block twm_nb; struct mutex bus_lock; struct mutex sram_rw_lock; struct mutex charge_full_lock; struct mutex qnovo_esr_ctrl_lock; + spinlock_t suspend_lock; + spinlock_t awake_lock; u32 batt_soc_base; u32 batt_info_base; u32 mem_if_base; u32 rradc_base; u32 wa_flags; + u32 esr_wakeup_ms; + u32 awake_status; int batt_id_ohms; int charge_status; int prev_charge_status; @@ -450,6 +472,8 @@ struct fg_dev { int delta_soc; int last_msoc; int last_recharge_volt_mv; + int delta_temp_irq_count; + enum esr_filter_status esr_flt_sts; bool profile_available; enum prof_load_status profile_load_status; bool battery_missing; @@ -458,14 +482,21 @@ struct fg_dev { bool recharge_soc_adjusted; bool soc_reporting_ready; bool use_ima_single_mode; + bool usb_present; + bool twm_state; bool use_dma; bool qnovo_enable; enum fg_version version; + bool suspended; struct completion soc_update; struct completion soc_ready; struct delayed_work profile_load_work; struct work_struct status_change_work; + struct work_struct esr_sw_work; struct delayed_work sram_dump_work; + struct work_struct esr_filter_work; + struct alarm esr_filter_alarm; + ktime_t last_delta_temp_time; }; /* Debugfs data structures are below */ @@ -574,4 +605,6 @@ extern int fg_circ_buf_avg(struct fg_circ_buf *buf, int *avg); extern int fg_circ_buf_median(struct fg_circ_buf *buf, int *median); extern int fg_lerp(const struct fg_pt *pts, size_t tablesize, s32 input, s32 *output); +void fg_stay_awake(struct fg_dev *fg, int awake_reason); +void fg_relax(struct fg_dev *fg, int awake_reason); #endif diff --git a/drivers/power/supply/qcom/fg-reg.h b/drivers/power/supply/qcom/fg-reg.h index bd34ce3dbb35..4db8cee57e98 100644 --- a/drivers/power/supply/qcom/fg-reg.h +++ b/drivers/power/supply/qcom/fg-reg.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0 */ /* - * Copyright (c) 2016-2018, The Linux Foundation. All rights reserved. + * Copyright (c) 2016-2018, 2020, The Linux Foundation. All rights reserved. */ #ifndef __FG_REG_H__ @@ -46,6 +46,7 @@ #define BATT_SOC_SLEEP_SHUTDOWN_STS(chip) (chip->batt_soc_base + 0x08) #define BATT_SOC_FG_MONOTONIC_SOC(chip) (chip->batt_soc_base + 0x09) #define BATT_SOC_FG_MONOTONIC_SOC_CP(chip) (chip->batt_soc_base + 0x0A) +#define BATT_SOC_RST_CTRL0(chip) (chip->batt_soc_base + 0xBA) #define BATT_SOC_INT_RT_STS(chip) (chip->batt_soc_base + 0x10) #define SOC_READY_BIT BIT(1) @@ -60,6 +61,10 @@ #define BATT_SOC_STS_CLR(chip) (chip->batt_soc_base + 0x4A) #define BATT_SOC_LOW_PWR_CFG(chip) (chip->batt_soc_base + 0x52) #define BATT_SOC_LOW_PWR_STS(chip) (chip->batt_soc_base + 0x56) +/* BATT_SOC_RST_CTRL0 */ +#define BCL_RST_BIT BIT(2) +#define MEM_RST_BIT BIT(1) +#define ALG_RST_BIT BIT(0) /* FG_BATT_INFO register definitions */ #define BATT_INFO_BATT_TEMP_STS(chip) (chip->batt_info_base + 0x06) diff --git a/drivers/power/supply/qcom/fg-util.c b/drivers/power/supply/qcom/fg-util.c index 4afc575dee5d..0d152d57258e 100644 --- a/drivers/power/supply/qcom/fg-util.c +++ b/drivers/power/supply/qcom/fg-util.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Copyright (c) 2016-2019 The Linux Foundation. All rights reserved. + * Copyright (c) 2016-2020, The Linux Foundation. All rights reserved. */ #include @@ -715,7 +715,7 @@ static inline bool is_sec_access(struct fg_dev *fg, int addr) if (fg->version != GEN3_FG) return false; - return ((addr & 0x00FF) > 0xD0); + return ((addr & 0x00FF) > 0xB8); } int fg_write(struct fg_dev *fg, int addr, u8 *val, int len) @@ -1683,3 +1683,28 @@ int fg_debugfs_create(struct fg_dev *fg) debugfs_remove_recursive(fg->dfs_root); return -ENOMEM; } + +void fg_stay_awake(struct fg_dev *fg, int awake_reason) +{ + spin_lock(&fg->awake_lock); + + if (!fg->awake_status) + pm_stay_awake(fg->dev); + + fg->awake_status |= awake_reason; + + spin_unlock(&fg->awake_lock); +} + +void fg_relax(struct fg_dev *fg, int awake_reason) +{ + spin_lock(&fg->awake_lock); + + fg->awake_status &= ~awake_reason; + + if (!fg->awake_status) + pm_relax(fg->dev); + + spin_unlock(&fg->awake_lock); +} + diff --git a/drivers/power/supply/qcom/qpnp-fg-gen3.c b/drivers/power/supply/qcom/qpnp-fg-gen3.c new file mode 100644 index 000000000000..3dc1f671f92d --- /dev/null +++ b/drivers/power/supply/qcom/qpnp-fg-gen3.c @@ -0,0 +1,5639 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2016-2020, The Linux Foundation. All rights reserved. + */ + +#define pr_fmt(fmt) "FG: %s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "fg-core.h" +#include "fg-reg.h" + +#define FG_GEN3_DEV_NAME "qcom,fg-gen3" + +#define PERPH_SUBTYPE_REG 0x05 +#define FG_BATT_SOC_PMI8998 0x10 +#define FG_BATT_INFO_PMI8998 0x11 +#define FG_MEM_INFO_PMI8998 0x0D + +/* SRAM address and offset in ascending order */ +#define ESR_PULSE_THRESH_WORD 2 +#define ESR_PULSE_THRESH_OFFSET 3 +#define SLOPE_LIMIT_WORD 3 +#define SLOPE_LIMIT_OFFSET 0 +#define CUTOFF_CURR_WORD 4 +#define CUTOFF_CURR_OFFSET 0 +#define CUTOFF_VOLT_WORD 5 +#define CUTOFF_VOLT_OFFSET 0 +#define SYS_TERM_CURR_WORD 6 +#define SYS_TERM_CURR_OFFSET 0 +#define VBATT_FULL_WORD 7 +#define VBATT_FULL_OFFSET 0 +#define ESR_FILTER_WORD 8 +#define ESR_UPD_TIGHT_OFFSET 0 +#define ESR_UPD_BROAD_OFFSET 1 +#define ESR_UPD_TIGHT_LOW_TEMP_OFFSET 2 +#define ESR_UPD_BROAD_LOW_TEMP_OFFSET 3 +#define KI_COEFF_MED_DISCHG_WORD 9 +#define TIMEBASE_OFFSET 1 +#define KI_COEFF_MED_DISCHG_OFFSET 3 +#define KI_COEFF_HI_DISCHG_WORD 10 +#define KI_COEFF_HI_DISCHG_OFFSET 0 +#define KI_COEFF_LOW_DISCHG_WORD 10 +#define KI_COEFF_LOW_DISCHG_OFFSET 2 +#define KI_COEFF_FULL_SOC_WORD 12 +#define KI_COEFF_FULL_SOC_OFFSET 2 +#define DELTA_MSOC_THR_WORD 12 +#define DELTA_MSOC_THR_OFFSET 3 +#define DELTA_BSOC_THR_WORD 13 +#define DELTA_BSOC_THR_OFFSET 2 +#define RECHARGE_SOC_THR_WORD 14 +#define RECHARGE_SOC_THR_OFFSET 0 +#define CHG_TERM_CURR_WORD 14 +#define CHG_TERM_CURR_OFFSET 1 +#define SYNC_SLEEP_THR_WORD 14 +#define SYNC_SLEEP_THR_OFFSET 3 +#define EMPTY_VOLT_WORD 15 +#define EMPTY_VOLT_OFFSET 0 +#define VBATT_LOW_WORD 15 +#define VBATT_LOW_OFFSET 1 +#define ESR_TIMER_DISCHG_MAX_WORD 17 +#define ESR_TIMER_DISCHG_MAX_OFFSET 0 +#define ESR_TIMER_DISCHG_INIT_WORD 17 +#define ESR_TIMER_DISCHG_INIT_OFFSET 2 +#define ESR_TIMER_CHG_MAX_WORD 18 +#define ESR_TIMER_CHG_MAX_OFFSET 0 +#define ESR_TIMER_CHG_INIT_WORD 18 +#define ESR_TIMER_CHG_INIT_OFFSET 2 +#define ESR_EXTRACTION_ENABLE_WORD 19 +#define ESR_EXTRACTION_ENABLE_OFFSET 0 +#define PROFILE_LOAD_WORD 24 +#define PROFILE_LOAD_OFFSET 0 +#define ESR_RSLOW_DISCHG_WORD 34 +#define ESR_RSLOW_DISCHG_OFFSET 0 +#define ESR_RSLOW_CHG_WORD 51 +#define ESR_RSLOW_CHG_OFFSET 0 +#define NOM_CAP_WORD 58 +#define NOM_CAP_OFFSET 0 +#define ACT_BATT_CAP_BKUP_WORD 74 +#define ACT_BATT_CAP_BKUP_OFFSET 0 +#define CYCLE_COUNT_WORD 75 +#define CYCLE_COUNT_OFFSET 0 +#define PROFILE_INTEGRITY_WORD 79 +#define SW_CONFIG_OFFSET 0 +#define PROFILE_INTEGRITY_OFFSET 3 +#define BATT_SOC_WORD 91 +#define BATT_SOC_OFFSET 0 +#define FULL_SOC_WORD 93 +#define FULL_SOC_OFFSET 2 +#define MONOTONIC_SOC_WORD 94 +#define MONOTONIC_SOC_OFFSET 2 +#define CC_SOC_WORD 95 +#define CC_SOC_OFFSET 0 +#define CC_SOC_SW_WORD 96 +#define CC_SOC_SW_OFFSET 0 +#define VOLTAGE_PRED_WORD 97 +#define VOLTAGE_PRED_OFFSET 0 +#define OCV_WORD 97 +#define OCV_OFFSET 2 +#define ESR_WORD 99 +#define ESR_OFFSET 0 +#define RSLOW_WORD 101 +#define RSLOW_OFFSET 0 +#define ACT_BATT_CAP_WORD 117 +#define ACT_BATT_CAP_OFFSET 0 +#define LAST_BATT_SOC_WORD 119 +#define LAST_BATT_SOC_OFFSET 0 +#define LAST_MONOTONIC_SOC_WORD 119 +#define LAST_MONOTONIC_SOC_OFFSET 2 +#define ALG_FLAGS_WORD 120 +#define ALG_FLAGS_OFFSET 1 + +/* v2 SRAM address and offset in ascending order */ +#define KI_COEFF_LOW_DISCHG_v2_WORD 9 +#define KI_COEFF_LOW_DISCHG_v2_OFFSET 3 +#define KI_COEFF_MED_DISCHG_v2_WORD 10 +#define KI_COEFF_MED_DISCHG_v2_OFFSET 0 +#define KI_COEFF_HI_DISCHG_v2_WORD 10 +#define KI_COEFF_HI_DISCHG_v2_OFFSET 1 +#define KI_COEFF_HI_CHG_v2_WORD 11 +#define KI_COEFF_HI_CHG_v2_OFFSET 2 +#define DELTA_BSOC_THR_v2_WORD 12 +#define DELTA_BSOC_THR_v2_OFFSET 3 +#define DELTA_MSOC_THR_v2_WORD 13 +#define DELTA_MSOC_THR_v2_OFFSET 0 +#define RECHARGE_SOC_THR_v2_WORD 14 +#define RECHARGE_SOC_THR_v2_OFFSET 1 +#define SYNC_SLEEP_THR_v2_WORD 14 +#define SYNC_SLEEP_THR_v2_OFFSET 2 +#define CHG_TERM_CURR_v2_WORD 15 +#define CHG_TERM_BASE_CURR_v2_OFFSET 0 +#define CHG_TERM_CURR_v2_OFFSET 1 +#define EMPTY_VOLT_v2_WORD 15 +#define EMPTY_VOLT_v2_OFFSET 3 +#define VBATT_LOW_v2_WORD 16 +#define VBATT_LOW_v2_OFFSET 0 +#define RECHARGE_VBATT_THR_v2_WORD 16 +#define RECHARGE_VBATT_THR_v2_OFFSET 1 +#define FLOAT_VOLT_v2_WORD 16 +#define FLOAT_VOLT_v2_OFFSET 2 + +/* Other definitions */ +#define SLOPE_LIMIT_COEFF_MAX 31 +#define FG_SRAM_LEN 504 +#define PROFILE_LEN 224 +#define PROFILE_COMP_LEN 148 +#define KI_COEFF_MAX 62200 +#define KI_COEFF_SOC_LEVELS 3 +#define BATT_THERM_NUM_COEFFS 3 + +static struct fg_irq_info fg_irqs[FG_GEN3_IRQ_MAX]; + +/* DT parameters for FG device */ +struct fg_dt_props { + bool force_load_profile; + bool hold_soc_while_full; + bool linearize_soc; + bool auto_recharge_soc; + bool use_esr_sw; + bool disable_esr_pull_dn; + bool disable_fg_twm; + int cutoff_volt_mv; + int empty_volt_mv; + int vbatt_low_thr_mv; + int chg_term_curr_ma; + int chg_term_base_curr_ma; + int sys_term_curr_ma; + int cutoff_curr_ma; + int delta_soc_thr; + int recharge_soc_thr; + int recharge_volt_thr_mv; + int rsense_sel; + int esr_timer_charging[NUM_ESR_TIMERS]; + int esr_timer_awake[NUM_ESR_TIMERS]; + int esr_timer_asleep[NUM_ESR_TIMERS]; + int esr_timer_shutdown[NUM_ESR_TIMERS]; + int rconn_mohms; + int esr_clamp_mohms; + int cl_start_soc; + int cl_max_temp; + int cl_min_temp; + int cl_max_cap_inc; + int cl_max_cap_dec; + int cl_max_cap_limit; + int cl_min_cap_limit; + int jeita_hyst_temp; + int batt_temp_delta; + int esr_flt_switch_temp; + int esr_tight_flt_upct; + int esr_broad_flt_upct; + int esr_tight_lt_flt_upct; + int esr_broad_lt_flt_upct; + int esr_flt_rt_switch_temp; + int esr_tight_rt_flt_upct; + int esr_broad_rt_flt_upct; + int slope_limit_temp; + int esr_pulse_thresh_ma; + int esr_meas_curr_ma; + int sync_sleep_threshold_ma; + int bmd_en_delay_ms; + int ki_coeff_full_soc_dischg; + int ki_coeff_hi_chg; + int jeita_thresholds[NUM_JEITA_LEVELS]; + int ki_coeff_soc[KI_COEFF_SOC_LEVELS]; + int ki_coeff_low_dischg[KI_COEFF_SOC_LEVELS]; + int ki_coeff_med_dischg[KI_COEFF_SOC_LEVELS]; + int ki_coeff_hi_dischg[KI_COEFF_SOC_LEVELS]; + int slope_limit_coeffs[SLOPE_LIMIT_NUM_COEFFS]; + u8 batt_therm_coeffs[BATT_THERM_NUM_COEFFS]; +}; + +struct fg_gen3_chip { + struct fg_dev fg; + struct fg_dt_props dt; + struct iio_channel *batt_id_chan; + struct iio_channel *die_temp_chan; + struct votable *pl_disable_votable; + struct mutex qnovo_esr_ctrl_lock; + struct fg_cyc_ctr_data cyc_ctr; + struct fg_cap_learning cl; + struct fg_ttf ttf; + struct delayed_work ttf_work; + struct delayed_work pl_enable_work; + enum slope_limit_status slope_limit_sts; + char batt_profile[PROFILE_LEN]; + int esr_timer_charging_default[NUM_ESR_TIMERS]; + int ki_coeff_full_soc; + bool ki_coeff_dischg_en; + bool esr_fcc_ctrl_en; + bool esr_flt_cold_temp_en; + bool slope_limit_en; +}; + +static struct fg_sram_param pmi8998_v1_sram_params[] = { + PARAM(BATT_SOC, BATT_SOC_WORD, BATT_SOC_OFFSET, 4, 1, 1, 0, NULL, + fg_decode_default), + PARAM(FULL_SOC, FULL_SOC_WORD, FULL_SOC_OFFSET, 2, 1, 1, 0, NULL, + fg_decode_default), + PARAM(VOLTAGE_PRED, VOLTAGE_PRED_WORD, VOLTAGE_PRED_OFFSET, 2, 1000, + 244141, 0, NULL, fg_decode_voltage_15b), + PARAM(OCV, OCV_WORD, OCV_OFFSET, 2, 1000, 244141, 0, NULL, + fg_decode_voltage_15b), + PARAM(ESR, ESR_WORD, ESR_OFFSET, 2, 1000, 244141, 0, fg_encode_default, + fg_decode_value_16b), + PARAM(RSLOW, RSLOW_WORD, RSLOW_OFFSET, 2, 1000, 244141, 0, NULL, + fg_decode_value_16b), + PARAM(ALG_FLAGS, ALG_FLAGS_WORD, ALG_FLAGS_OFFSET, 1, 1, 1, 0, NULL, + fg_decode_default), + PARAM(CC_SOC, CC_SOC_WORD, CC_SOC_OFFSET, 4, 1, 1, 0, NULL, + fg_decode_cc_soc), + PARAM(CC_SOC_SW, CC_SOC_SW_WORD, CC_SOC_SW_OFFSET, 4, 1, 1, 0, NULL, + fg_decode_cc_soc), + PARAM(ACT_BATT_CAP, ACT_BATT_CAP_BKUP_WORD, ACT_BATT_CAP_BKUP_OFFSET, 2, + 1, 1, 0, NULL, fg_decode_default), + /* Entries below here are configurable during initialization */ + PARAM(CUTOFF_VOLT, CUTOFF_VOLT_WORD, CUTOFF_VOLT_OFFSET, 2, 1000000, + 244141, 0, fg_encode_voltage, NULL), + PARAM(EMPTY_VOLT, EMPTY_VOLT_WORD, EMPTY_VOLT_OFFSET, 1, 100000, 390625, + -2500, fg_encode_voltage, NULL), + PARAM(VBATT_LOW, VBATT_LOW_WORD, VBATT_LOW_OFFSET, 1, 100000, 390625, + -2500, fg_encode_voltage, NULL), + PARAM(VBATT_FULL, VBATT_FULL_WORD, VBATT_FULL_OFFSET, 2, 1000, + 244141, 0, fg_encode_voltage, fg_decode_voltage_15b), + PARAM(SYS_TERM_CURR, SYS_TERM_CURR_WORD, SYS_TERM_CURR_OFFSET, 3, + 1000000, 122070, 0, fg_encode_current, NULL), + PARAM(CHG_TERM_CURR, CHG_TERM_CURR_WORD, CHG_TERM_CURR_OFFSET, 1, + 100000, 390625, 0, fg_encode_current, NULL), + PARAM(CUTOFF_CURR, CUTOFF_CURR_WORD, CUTOFF_CURR_OFFSET, 3, + 1000000, 122070, 0, fg_encode_current, NULL), + PARAM(DELTA_MSOC_THR, DELTA_MSOC_THR_WORD, DELTA_MSOC_THR_OFFSET, 1, + 2048, 100, 0, fg_encode_default, NULL), + PARAM(DELTA_BSOC_THR, DELTA_BSOC_THR_WORD, DELTA_BSOC_THR_OFFSET, 1, + 2048, 100, 0, fg_encode_default, NULL), + PARAM(RECHARGE_SOC_THR, RECHARGE_SOC_THR_WORD, RECHARGE_SOC_THR_OFFSET, + 1, 256, 100, 0, fg_encode_default, NULL), + PARAM(SYNC_SLEEP_THR, SYNC_SLEEP_THR_WORD, SYNC_SLEEP_THR_OFFSET, + 1, 100000, 390625, 0, fg_encode_default, NULL), + PARAM(ESR_TIMER_DISCHG_MAX, ESR_TIMER_DISCHG_MAX_WORD, + ESR_TIMER_DISCHG_MAX_OFFSET, 2, 1, 1, 0, fg_encode_default, + NULL), + PARAM(ESR_TIMER_DISCHG_INIT, ESR_TIMER_DISCHG_INIT_WORD, + ESR_TIMER_DISCHG_INIT_OFFSET, 2, 1, 1, 0, fg_encode_default, + NULL), + PARAM(ESR_TIMER_CHG_MAX, ESR_TIMER_CHG_MAX_WORD, + ESR_TIMER_CHG_MAX_OFFSET, 2, 1, 1, 0, fg_encode_default, NULL), + PARAM(ESR_TIMER_CHG_INIT, ESR_TIMER_CHG_INIT_WORD, + ESR_TIMER_CHG_INIT_OFFSET, 2, 1, 1, 0, fg_encode_default, NULL), + PARAM(ESR_PULSE_THRESH, ESR_PULSE_THRESH_WORD, ESR_PULSE_THRESH_OFFSET, + 1, 100000, 390625, 0, fg_encode_default, NULL), + PARAM(KI_COEFF_MED_DISCHG, KI_COEFF_MED_DISCHG_WORD, + KI_COEFF_MED_DISCHG_OFFSET, 1, 1000, 244141, 0, + fg_encode_default, NULL), + PARAM(KI_COEFF_HI_DISCHG, KI_COEFF_HI_DISCHG_WORD, + KI_COEFF_HI_DISCHG_OFFSET, 1, 1000, 244141, 0, + fg_encode_default, NULL), + PARAM(KI_COEFF_FULL_SOC, KI_COEFF_FULL_SOC_WORD, + KI_COEFF_FULL_SOC_OFFSET, 1, 1000, 244141, 0, + fg_encode_default, NULL), + PARAM(ESR_TIGHT_FILTER, ESR_FILTER_WORD, ESR_UPD_TIGHT_OFFSET, + 1, 512, 1000000, 0, fg_encode_default, NULL), + PARAM(ESR_BROAD_FILTER, ESR_FILTER_WORD, ESR_UPD_BROAD_OFFSET, + 1, 512, 1000000, 0, fg_encode_default, NULL), + PARAM(SLOPE_LIMIT, SLOPE_LIMIT_WORD, SLOPE_LIMIT_OFFSET, 1, 8192, 1000, + 0, fg_encode_default, NULL), +}; + +static struct fg_sram_param pmi8998_v2_sram_params[] = { + PARAM(BATT_SOC, BATT_SOC_WORD, BATT_SOC_OFFSET, 4, 1, 1, 0, NULL, + fg_decode_default), + PARAM(FULL_SOC, FULL_SOC_WORD, FULL_SOC_OFFSET, 2, 1, 1, 0, NULL, + fg_decode_default), + PARAM(VOLTAGE_PRED, VOLTAGE_PRED_WORD, VOLTAGE_PRED_OFFSET, 2, 1000, + 244141, 0, NULL, fg_decode_voltage_15b), + PARAM(OCV, OCV_WORD, OCV_OFFSET, 2, 1000, 244141, 0, NULL, + fg_decode_voltage_15b), + PARAM(ESR, ESR_WORD, ESR_OFFSET, 2, 1000, 244141, 0, fg_encode_default, + fg_decode_value_16b), + PARAM(RSLOW, RSLOW_WORD, RSLOW_OFFSET, 2, 1000, 244141, 0, NULL, + fg_decode_value_16b), + PARAM(ALG_FLAGS, ALG_FLAGS_WORD, ALG_FLAGS_OFFSET, 1, 1, 1, 0, NULL, + fg_decode_default), + PARAM(CC_SOC, CC_SOC_WORD, CC_SOC_OFFSET, 4, 1, 1, 0, NULL, + fg_decode_cc_soc), + PARAM(CC_SOC_SW, CC_SOC_SW_WORD, CC_SOC_SW_OFFSET, 4, 1, 1, 0, NULL, + fg_decode_cc_soc), + PARAM(ACT_BATT_CAP, ACT_BATT_CAP_BKUP_WORD, ACT_BATT_CAP_BKUP_OFFSET, 2, + 1, 1, 0, NULL, fg_decode_default), + PARAM(TIMEBASE, KI_COEFF_MED_DISCHG_WORD, TIMEBASE_OFFSET, 2, 1000, + 61000, 0, fg_encode_default, NULL), + /* Entries below here are configurable during initialization */ + PARAM(CUTOFF_VOLT, CUTOFF_VOLT_WORD, CUTOFF_VOLT_OFFSET, 2, 1000000, + 244141, 0, fg_encode_voltage, NULL), + PARAM(EMPTY_VOLT, EMPTY_VOLT_v2_WORD, EMPTY_VOLT_v2_OFFSET, 1, 1000, + 15625, -2000, fg_encode_voltage, NULL), + PARAM(VBATT_LOW, VBATT_LOW_v2_WORD, VBATT_LOW_v2_OFFSET, 1, 1000, + 15625, -2000, fg_encode_voltage, NULL), + PARAM(FLOAT_VOLT, FLOAT_VOLT_v2_WORD, FLOAT_VOLT_v2_OFFSET, 1, 1000, + 15625, -2000, fg_encode_voltage, NULL), + PARAM(VBATT_FULL, VBATT_FULL_WORD, VBATT_FULL_OFFSET, 2, 1000, + 244141, 0, fg_encode_voltage, fg_decode_voltage_15b), + PARAM(SYS_TERM_CURR, SYS_TERM_CURR_WORD, SYS_TERM_CURR_OFFSET, 3, + 1000000, 122070, 0, fg_encode_current, NULL), + PARAM(CHG_TERM_CURR, CHG_TERM_CURR_v2_WORD, CHG_TERM_CURR_v2_OFFSET, 1, + 100000, 390625, 0, fg_encode_current, NULL), + PARAM(CHG_TERM_BASE_CURR, CHG_TERM_CURR_v2_WORD, + CHG_TERM_BASE_CURR_v2_OFFSET, 1, 1024, 1000, 0, + fg_encode_current, NULL), + PARAM(CUTOFF_CURR, CUTOFF_CURR_WORD, CUTOFF_CURR_OFFSET, 3, + 1000000, 122070, 0, fg_encode_current, NULL), + PARAM(DELTA_MSOC_THR, DELTA_MSOC_THR_v2_WORD, DELTA_MSOC_THR_v2_OFFSET, + 1, 2048, 100, 0, fg_encode_default, NULL), + PARAM(DELTA_BSOC_THR, DELTA_BSOC_THR_v2_WORD, DELTA_BSOC_THR_v2_OFFSET, + 1, 2048, 100, 0, fg_encode_default, NULL), + PARAM(RECHARGE_SOC_THR, RECHARGE_SOC_THR_v2_WORD, + RECHARGE_SOC_THR_v2_OFFSET, 1, 256, 100, 0, fg_encode_default, + NULL), + PARAM(SYNC_SLEEP_THR, SYNC_SLEEP_THR_v2_WORD, SYNC_SLEEP_THR_v2_OFFSET, + 1, 100000, 390625, 0, fg_encode_default, NULL), + PARAM(RECHARGE_VBATT_THR, RECHARGE_VBATT_THR_v2_WORD, + RECHARGE_VBATT_THR_v2_OFFSET, 1, 1000, 15625, -2000, + fg_encode_voltage, NULL), + PARAM(ESR_TIMER_DISCHG_MAX, ESR_TIMER_DISCHG_MAX_WORD, + ESR_TIMER_DISCHG_MAX_OFFSET, 2, 1, 1, 0, fg_encode_default, + NULL), + PARAM(ESR_TIMER_DISCHG_INIT, ESR_TIMER_DISCHG_INIT_WORD, + ESR_TIMER_DISCHG_INIT_OFFSET, 2, 1, 1, 0, fg_encode_default, + NULL), + PARAM(ESR_TIMER_CHG_MAX, ESR_TIMER_CHG_MAX_WORD, + ESR_TIMER_CHG_MAX_OFFSET, 2, 1, 1, 0, fg_encode_default, NULL), + PARAM(ESR_TIMER_CHG_INIT, ESR_TIMER_CHG_INIT_WORD, + ESR_TIMER_CHG_INIT_OFFSET, 2, 1, 1, 0, fg_encode_default, NULL), + PARAM(ESR_PULSE_THRESH, ESR_PULSE_THRESH_WORD, ESR_PULSE_THRESH_OFFSET, + 1, 100000, 390625, 0, fg_encode_default, NULL), + PARAM(KI_COEFF_LOW_DISCHG, KI_COEFF_LOW_DISCHG_v2_WORD, + KI_COEFF_LOW_DISCHG_v2_OFFSET, 1, 1000, 244141, 0, + fg_encode_default, NULL), + PARAM(KI_COEFF_MED_DISCHG, KI_COEFF_MED_DISCHG_v2_WORD, + KI_COEFF_MED_DISCHG_v2_OFFSET, 1, 1000, 244141, 0, + fg_encode_default, NULL), + PARAM(KI_COEFF_HI_DISCHG, KI_COEFF_HI_DISCHG_v2_WORD, + KI_COEFF_HI_DISCHG_v2_OFFSET, 1, 1000, 244141, 0, + fg_encode_default, NULL), + PARAM(KI_COEFF_HI_CHG, KI_COEFF_HI_CHG_v2_WORD, + KI_COEFF_HI_CHG_v2_OFFSET, 1, 1000, 244141, 0, + fg_encode_default, NULL), + PARAM(KI_COEFF_FULL_SOC, KI_COEFF_FULL_SOC_WORD, + KI_COEFF_FULL_SOC_OFFSET, 1, 1000, 244141, 0, + fg_encode_default, NULL), + PARAM(ESR_TIGHT_FILTER, ESR_FILTER_WORD, ESR_UPD_TIGHT_OFFSET, + 1, 512, 1000000, 0, fg_encode_default, NULL), + PARAM(ESR_BROAD_FILTER, ESR_FILTER_WORD, ESR_UPD_BROAD_OFFSET, + 1, 512, 1000000, 0, fg_encode_default, NULL), + PARAM(SLOPE_LIMIT, SLOPE_LIMIT_WORD, SLOPE_LIMIT_OFFSET, 1, 8192, 1000, + 0, fg_encode_default, NULL), +}; + +static struct fg_alg_flag pmi8998_v1_alg_flags[] = { + [ALG_FLAG_SOC_LT_OTG_MIN] = { + .name = "SOC_LT_OTG_MIN", + .bit = BIT(0), + }, + [ALG_FLAG_SOC_LT_RECHARGE] = { + .name = "SOC_LT_RECHARGE", + .bit = BIT(1), + }, + [ALG_FLAG_IBATT_LT_ITERM] = { + .name = "IBATT_LT_ITERM", + .bit = BIT(2), + }, + [ALG_FLAG_IBATT_GT_HPM] = { + .name = "IBATT_GT_HPM", + .bit = BIT(3), + }, + [ALG_FLAG_IBATT_GT_UPM] = { + .name = "IBATT_GT_UPM", + .bit = BIT(4), + }, + [ALG_FLAG_VBATT_LT_RECHARGE] = { + .name = "VBATT_LT_RECHARGE", + .bit = BIT(5), + }, + [ALG_FLAG_VBATT_GT_VFLOAT] = { + .invalid = true, + }, +}; + +static struct fg_alg_flag pmi8998_v2_alg_flags[] = { + [ALG_FLAG_SOC_LT_OTG_MIN] = { + .name = "SOC_LT_OTG_MIN", + .bit = BIT(0), + }, + [ALG_FLAG_SOC_LT_RECHARGE] = { + .name = "SOC_LT_RECHARGE", + .bit = BIT(1), + }, + [ALG_FLAG_IBATT_LT_ITERM] = { + .name = "IBATT_LT_ITERM", + .bit = BIT(2), + }, + [ALG_FLAG_IBATT_GT_HPM] = { + .name = "IBATT_GT_HPM", + .bit = BIT(4), + }, + [ALG_FLAG_IBATT_GT_UPM] = { + .name = "IBATT_GT_UPM", + .bit = BIT(5), + }, + [ALG_FLAG_VBATT_LT_RECHARGE] = { + .name = "VBATT_LT_RECHARGE", + .bit = BIT(6), + }, + [ALG_FLAG_VBATT_GT_VFLOAT] = { + .name = "VBATT_GT_VFLOAT", + .bit = BIT(7), + }, +}; + +static int fg_gen3_debug_mask; + +static bool fg_profile_dump; +static ssize_t profile_dump_show(struct device *dev, struct device_attribute + *attr, char *buf) +{ + return scnprintf(buf, PAGE_SIZE, "%c\n", fg_profile_dump ? 'Y' : 'N'); +} + +static ssize_t profile_dump_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + bool val; + + if (kstrtobool(buf, &val)) + return -EINVAL; + + fg_profile_dump = val; + + return count; +} +static DEVICE_ATTR_RW(profile_dump); + +static int fg_sram_dump_period_ms = 20000; +static ssize_t sram_dump_period_ms_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return scnprintf(buf, PAGE_SIZE, "%d\n", fg_sram_dump_period_ms); +} + +static ssize_t sram_dump_period_ms_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int val; + + if (kstrtos32(buf, 0, &val)) + return -EINVAL; + + fg_sram_dump_period_ms = val; + + return count; +} +static DEVICE_ATTR_RW(sram_dump_period_ms); + +static int fg_restart_mp; +static bool fg_sram_dump; + +/* All getters HERE */ + +#define CC_SOC_30BIT GENMASK(29, 0) +static int fg_get_charge_raw(struct fg_dev *fg, int *val) +{ + struct fg_gen3_chip *chip = container_of(fg, struct fg_gen3_chip, fg); + int rc, cc_soc; + + rc = fg_get_sram_prop(fg, FG_SRAM_CC_SOC, &cc_soc); + if (rc < 0) { + pr_err("Error in getting CC_SOC, rc=%d\n", rc); + return rc; + } + + *val = div_s64((int64_t)cc_soc * chip->cl.nom_cap_uah, CC_SOC_30BIT); + return 0; +} + +#define BATT_SOC_32BIT GENMASK(31, 0) +static int fg_get_charge_counter_shadow(struct fg_dev *fg, int *val) +{ + struct fg_gen3_chip *chip = container_of(fg, struct fg_gen3_chip, fg); + int rc; + unsigned int batt_soc; + + rc = fg_get_sram_prop(fg, FG_SRAM_BATT_SOC, &batt_soc); + if (rc < 0) { + pr_err("Error in getting BATT_SOC, rc=%d\n", rc); + return rc; + } + + *val = div_u64((uint64_t)batt_soc * chip->cl.learned_cc_uah, + BATT_SOC_32BIT); + return 0; +} + +static int fg_get_charge_counter(struct fg_dev *fg, int *val) +{ + struct fg_gen3_chip *chip = container_of(fg, struct fg_gen3_chip, fg); + int rc; + int cc_soc; + + rc = fg_get_sram_prop(fg, FG_SRAM_CC_SOC_SW, &cc_soc); + if (rc < 0) { + pr_err("Error in getting CC_SOC_SW, rc=%d\n", rc); + return rc; + } + + *val = div_s64((int64_t)cc_soc * chip->cl.learned_cc_uah, CC_SOC_30BIT); + return 0; +} + +static int fg_get_jeita_threshold(struct fg_dev *fg, + enum jeita_levels level, int *temp_decidegC) +{ + int rc; + u8 val; + u16 reg; + + switch (level) { + case JEITA_COLD: + reg = BATT_INFO_JEITA_TOO_COLD(fg); + break; + case JEITA_COOL: + reg = BATT_INFO_JEITA_COLD(fg); + break; + case JEITA_WARM: + reg = BATT_INFO_JEITA_HOT(fg); + break; + case JEITA_HOT: + reg = BATT_INFO_JEITA_TOO_HOT(fg); + break; + default: + return -EINVAL; + } + + rc = fg_read(fg, reg, &val, 1); + if (rc < 0) { + pr_err("Error in reading jeita level %d, rc=%d\n", level, rc); + return rc; + } + + /* Resolution is 0.5C. Base is -30C. */ + *temp_decidegC = (((5 * val) / 10) - 30) * 10; + return 0; +} + +static int fg_get_battery_temp(struct fg_dev *fg, int *val) +{ + int rc = 0, temp; + u8 buf[2]; + + rc = fg_read(fg, BATT_INFO_BATT_TEMP_LSB(fg), buf, 2); + if (rc < 0) { + pr_err("failed to read addr=0x%04x, rc=%d\n", + BATT_INFO_BATT_TEMP_LSB(fg), rc); + return rc; + } + + temp = ((buf[1] & BATT_TEMP_MSB_MASK) << 8) | + (buf[0] & BATT_TEMP_LSB_MASK); + temp = DIV_ROUND_CLOSEST(temp, 4); + + /* Value is in Kelvin; Convert it to deciDegC */ + temp = (temp - 273) * 10; + *val = temp; + return 0; +} + +static bool is_batt_empty(struct fg_dev *fg) +{ + struct fg_gen3_chip *chip = container_of(fg, struct fg_gen3_chip, fg); + u8 status; + int rc, vbatt_uv, msoc; + + rc = fg_read(fg, BATT_SOC_INT_RT_STS(fg), &status, 1); + if (rc < 0) { + pr_err("failed to read addr=0x%04x, rc=%d\n", + BATT_SOC_INT_RT_STS(fg), rc); + return false; + } + + if (!(status & MSOC_EMPTY_BIT)) + return false; + + rc = fg_get_battery_voltage(fg, &vbatt_uv); + if (rc < 0) { + pr_err("failed to get battery voltage, rc=%d\n", rc); + return false; + } + + rc = fg_get_msoc(fg, &msoc); + if (!rc) + pr_warn("batt_soc_rt_sts: %x vbatt: %d uV msoc:%d\n", status, + vbatt_uv, msoc); + + return ((vbatt_uv < chip->dt.cutoff_volt_mv * 1000) ? true : false); +} + +static int fg_get_debug_batt_id(struct fg_dev *fg, int *batt_id) +{ + int rc; + u64 temp; + u8 buf[2]; + + rc = fg_read(fg, ADC_RR_FAKE_BATT_LOW_LSB(fg), buf, 2); + if (rc < 0) { + pr_err("failed to read addr=0x%04x, rc=%d\n", + ADC_RR_FAKE_BATT_LOW_LSB(fg), rc); + return rc; + } + + /* + * Fake battery threshold is encoded in the following format. + * Threshold (code) = (battery_id in Ohms) * 0.00015 * 2^10 / 2.5 + */ + temp = (buf[1] << 8 | buf[0]) * 2500000; + do_div(temp, 150 * 1024); + batt_id[0] = temp; + rc = fg_read(fg, ADC_RR_FAKE_BATT_HIGH_LSB(fg), buf, 2); + if (rc < 0) { + pr_err("failed to read addr=0x%04x, rc=%d\n", + ADC_RR_FAKE_BATT_HIGH_LSB(fg), rc); + return rc; + } + + temp = (buf[1] << 8 | buf[0]) * 2500000; + do_div(temp, 150 * 1024); + batt_id[1] = temp; + pr_debug("debug batt_id range: [%d %d]\n", batt_id[0], batt_id[1]); + return 0; +} + +static bool is_debug_batt_id(struct fg_dev *fg) +{ + int debug_batt_id[2], rc; + + if (fg->batt_id_ohms < 0) + return false; + + rc = fg_get_debug_batt_id(fg, debug_batt_id); + if (rc < 0) { + pr_err("Failed to get debug batt_id, rc=%d\n", rc); + return false; + } + + if (is_between(debug_batt_id[0], debug_batt_id[1], + fg->batt_id_ohms)) { + fg_dbg(fg, FG_POWER_SUPPLY, "Debug battery id: %dohms\n", + fg->batt_id_ohms); + return true; + } + + return false; +} + +#define DEBUG_BATT_SOC 67 +#define BATT_MISS_SOC 50 +#define EMPTY_SOC 0 +static int fg_get_prop_capacity(struct fg_dev *fg, int *val) +{ + struct fg_gen3_chip *chip = container_of(fg, struct fg_gen3_chip, fg); + int rc, msoc; + + if (is_debug_batt_id(fg)) { + *val = DEBUG_BATT_SOC; + return 0; + } + + if (fg->fg_restarting) { + *val = fg->last_soc; + return 0; + } + + if (fg->battery_missing || !fg->soc_reporting_ready) { + *val = BATT_MISS_SOC; + return 0; + } + + if (is_batt_empty(fg)) { + *val = EMPTY_SOC; + return 0; + } + + if (fg->charge_full) { + *val = FULL_CAPACITY; + return 0; + } + + rc = fg_get_msoc(fg, &msoc); + if (rc < 0) + return rc; + + if (chip->dt.linearize_soc && fg->delta_soc > 0) + *val = fg->maint_soc; + else + *val = msoc; + return 0; +} + +static int fg_get_prop_real_capacity(struct fg_dev *fg, int *val) +{ + return fg_get_msoc(fg, val); +} + +static int fg_batt_missing_config(struct fg_dev *fg, bool enable) +{ + int rc; + + rc = fg_masked_write(fg, BATT_INFO_BATT_MISS_CFG(fg), + BM_FROM_BATT_ID_BIT, enable ? BM_FROM_BATT_ID_BIT : 0); + if (rc < 0) + pr_err("Error in writing to %04x, rc=%d\n", + BATT_INFO_BATT_MISS_CFG(fg), rc); + return rc; +} + +static int fg_get_batt_id(struct fg_dev *fg) +{ + struct fg_gen3_chip *chip = container_of(fg, struct fg_gen3_chip, fg); + int rc, ret, batt_id = 0; + + if (!chip->batt_id_chan) + return -EINVAL; + + rc = fg_batt_missing_config(fg, false); + if (rc < 0) { + pr_err("Error in disabling BMD, rc=%d\n", rc); + return rc; + } + + rc = iio_read_channel_processed(chip->batt_id_chan, &batt_id); + if (rc < 0) { + pr_err("Error in reading batt_id channel, rc:%d\n", rc); + goto out; + } + + /* Wait for BATT_ID to settle down before enabling BMD again */ + msleep(chip->dt.bmd_en_delay_ms); + + fg_dbg(fg, FG_STATUS, "batt_id: %d\n", batt_id); + fg->batt_id_ohms = batt_id; +out: + ret = fg_batt_missing_config(fg, true); + if (ret < 0) { + pr_err("Error in enabling BMD, ret=%d\n", ret); + return ret; + } + + vote(fg->batt_miss_irq_en_votable, BATT_MISS_IRQ_VOTER, true, 0); + return rc; +} + +static int fg_get_batt_profile(struct fg_dev *fg) +{ + struct fg_gen3_chip *chip = container_of(fg, struct fg_gen3_chip, fg); + struct device_node *node = fg->dev->of_node; + struct device_node *batt_node, *profile_node; + const char *data; + int rc, len; + + batt_node = of_find_node_by_name(node, "qcom,battery-data"); + if (!batt_node) { + pr_err("Batterydata not available\n"); + return -ENXIO; + } + + profile_node = of_batterydata_get_best_profile(batt_node, + fg->batt_id_ohms / 1000, NULL); + if (IS_ERR(profile_node)) + return PTR_ERR(profile_node); + + if (!profile_node) { + pr_err("couldn't find profile handle\n"); + return -ENODATA; + } + + rc = of_property_read_string(profile_node, "qcom,battery-type", + &fg->bp.batt_type_str); + if (rc < 0) { + pr_err("battery type unavailable, rc:%d\n", rc); + return rc; + } + + rc = of_property_read_u32(profile_node, "qcom,max-voltage-uv", + &fg->bp.float_volt_uv); + if (rc < 0) { + pr_err("battery float voltage unavailable, rc:%d\n", rc); + fg->bp.float_volt_uv = -EINVAL; + } + + rc = of_property_read_u32(profile_node, "qcom,fastchg-current-ma", + &fg->bp.fastchg_curr_ma); + if (rc < 0) { + pr_err("battery fastchg current unavailable, rc:%d\n", rc); + fg->bp.fastchg_curr_ma = -EINVAL; + } + + rc = of_property_read_u32(profile_node, "qcom,fg-cc-cv-threshold-mv", + &fg->bp.vbatt_full_mv); + if (rc < 0) { + pr_err("battery cc_cv threshold unavailable, rc:%d\n", rc); + fg->bp.vbatt_full_mv = -EINVAL; + } + + data = of_get_property(profile_node, "qcom,fg-profile-data", &len); + if (!data) { + pr_err("No profile data available\n"); + return -ENODATA; + } + + if (len != PROFILE_LEN) { + pr_err("battery profile incorrect size: %d\n", len); + return -EINVAL; + } + + fg->profile_available = true; + memcpy(chip->batt_profile, data, len); + + return 0; +} + +static inline void get_batt_temp_delta(int delta, u8 *val) +{ + switch (delta) { + case 2: + *val = BTEMP_DELTA_2K; + break; + case 4: + *val = BTEMP_DELTA_4K; + break; + case 6: + *val = BTEMP_DELTA_6K; + break; + case 10: + *val = BTEMP_DELTA_10K; + break; + default: + *val = BTEMP_DELTA_2K; + break; + } +} + +static inline void get_esr_meas_current(int curr_ma, u8 *val) +{ + switch (curr_ma) { + case 60: + *val = ESR_MEAS_CUR_60MA; + break; + case 120: + *val = ESR_MEAS_CUR_120MA; + break; + case 180: + *val = ESR_MEAS_CUR_180MA; + break; + case 240: + *val = ESR_MEAS_CUR_240MA; + break; + default: + *val = ESR_MEAS_CUR_120MA; + break; + } + + *val <<= ESR_PULL_DOWN_IVAL_SHIFT; +} + +/* Other functions HERE */ + +static int fg_batt_miss_irq_en_cb(struct votable *votable, void *data, + int enable, const char *client) +{ + struct fg_dev *fg = data; + + if (!fg->irqs[BATT_MISSING_IRQ].irq) + return 0; + + if (enable) { + enable_irq(fg->irqs[BATT_MISSING_IRQ].irq); + enable_irq_wake(fg->irqs[BATT_MISSING_IRQ].irq); + } else { + disable_irq_wake(fg->irqs[BATT_MISSING_IRQ].irq); + disable_irq_nosync(fg->irqs[BATT_MISSING_IRQ].irq); + } + + return 0; +} + +static int fg_delta_bsoc_irq_en_cb(struct votable *votable, void *data, + int enable, const char *client) +{ + struct fg_dev *fg = data; + + if (!fg->irqs[BSOC_DELTA_IRQ].irq) + return 0; + + if (enable) { + enable_irq(fg->irqs[BSOC_DELTA_IRQ].irq); + enable_irq_wake(fg->irqs[BSOC_DELTA_IRQ].irq); + } else { + disable_irq_wake(fg->irqs[BSOC_DELTA_IRQ].irq); + disable_irq_nosync(fg->irqs[BSOC_DELTA_IRQ].irq); + } + + return 0; +} + +static int fg_awake_cb(struct votable *votable, void *data, int awake, + const char *client) +{ + struct fg_dev *fg = data; + + if (awake) + pm_stay_awake(fg->dev); + else + pm_relax(fg->dev); + + pr_debug("client: %s awake: %d\n", client, awake); + return 0; +} + +static int fg_prime_cc_soc_sw(struct fg_dev *fg, unsigned int cc_soc_sw) +{ + int rc; + + rc = fg_sram_write(fg, fg->sp[FG_SRAM_CC_SOC_SW].addr_word, + fg->sp[FG_SRAM_CC_SOC_SW].addr_byte, (u8 *)&cc_soc_sw, + fg->sp[FG_SRAM_CC_SOC_SW].len, FG_IMA_ATOMIC); + if (rc < 0) + pr_err("Error in writing cc_soc_sw, rc=%d\n", rc); + else + fg_dbg(fg, FG_STATUS, "cc_soc_sw: %u\n", cc_soc_sw); + + return rc; +} + +static int fg_save_learned_cap_to_sram(struct fg_dev *fg) +{ + struct fg_gen3_chip *chip = container_of(fg, struct fg_gen3_chip, fg); + int16_t cc_mah; + int rc; + + if (fg->battery_missing || !chip->cl.learned_cc_uah) + return -EPERM; + + cc_mah = div64_s64(chip->cl.learned_cc_uah, 1000); + /* Write to a backup register to use across reboot */ + rc = fg_sram_write(fg, fg->sp[FG_SRAM_ACT_BATT_CAP].addr_word, + fg->sp[FG_SRAM_ACT_BATT_CAP].addr_byte, (u8 *)&cc_mah, + fg->sp[FG_SRAM_ACT_BATT_CAP].len, FG_IMA_DEFAULT); + if (rc < 0) { + pr_err("Error in writing act_batt_cap_bkup, rc=%d\n", rc); + return rc; + } + + /* Write to actual capacity register for coulomb counter operation */ + rc = fg_sram_write(fg, ACT_BATT_CAP_WORD, ACT_BATT_CAP_OFFSET, + (u8 *)&cc_mah, fg->sp[FG_SRAM_ACT_BATT_CAP].len, + FG_IMA_DEFAULT); + if (rc < 0) { + pr_err("Error in writing act_batt_cap, rc=%d\n", rc); + return rc; + } + + fg_dbg(fg, FG_CAP_LEARN, "learned capacity %llduah/%dmah stored\n", + chip->cl.learned_cc_uah, cc_mah); + return 0; +} + +#define CAPACITY_DELTA_DECIPCT 500 +static int fg_load_learned_cap_from_sram(struct fg_dev *fg) +{ + struct fg_gen3_chip *chip = container_of(fg, struct fg_gen3_chip, fg); + int rc, act_cap_mah; + int64_t delta_cc_uah, pct_nom_cap_uah; + + rc = fg_get_sram_prop(fg, FG_SRAM_ACT_BATT_CAP, &act_cap_mah); + if (rc < 0) { + pr_err("Error in getting ACT_BATT_CAP, rc=%d\n", rc); + return rc; + } + + chip->cl.learned_cc_uah = act_cap_mah * 1000; + + if (chip->cl.learned_cc_uah != chip->cl.nom_cap_uah) { + if (chip->cl.learned_cc_uah == 0) + chip->cl.learned_cc_uah = chip->cl.nom_cap_uah; + + delta_cc_uah = abs(chip->cl.learned_cc_uah - + chip->cl.nom_cap_uah); + pct_nom_cap_uah = div64_s64((int64_t)chip->cl.nom_cap_uah * + CAPACITY_DELTA_DECIPCT, 1000); + /* + * If the learned capacity is out of range by 50% from the + * nominal capacity, then overwrite the learned capacity with + * the nominal capacity. + */ + if (chip->cl.nom_cap_uah && delta_cc_uah > pct_nom_cap_uah) { + fg_dbg(fg, FG_CAP_LEARN, "learned_cc_uah: %lld is higher than expected, capping it to nominal: %lld\n", + chip->cl.learned_cc_uah, chip->cl.nom_cap_uah); + chip->cl.learned_cc_uah = chip->cl.nom_cap_uah; + } + + rc = fg_save_learned_cap_to_sram(fg); + if (rc < 0) + pr_err("Error in saving learned_cc_uah, rc=%d\n", rc); + } + + fg_dbg(fg, FG_CAP_LEARN, "learned_cc_uah:%lld nom_cap_uah: %lld\n", + chip->cl.learned_cc_uah, chip->cl.nom_cap_uah); + return 0; +} + +static bool is_temp_valid_cap_learning(struct fg_dev *fg) +{ + struct fg_gen3_chip *chip = container_of(fg, struct fg_gen3_chip, fg); + int rc, batt_temp; + + rc = fg_get_battery_temp(fg, &batt_temp); + if (rc < 0) { + pr_err("Error in getting batt_temp\n"); + return false; + } + + if (batt_temp > chip->dt.cl_max_temp || + batt_temp < chip->dt.cl_min_temp) { + fg_dbg(fg, FG_CAP_LEARN, "batt temp %d out of range [%d %d]\n", + batt_temp, chip->dt.cl_min_temp, chip->dt.cl_max_temp); + return false; + } + + return true; +} + +#define QNOVO_CL_SKEW_DECIPCT -30 +static void fg_cap_learning_post_process(struct fg_dev *fg) +{ + struct fg_gen3_chip *chip = container_of(fg, struct fg_gen3_chip, fg); + int64_t max_inc_val, min_dec_val, old_cap; + int rc; + + if (is_qnovo_en(fg)) { + fg_dbg(fg, FG_CAP_LEARN, "applying skew %d on current learnt capacity %lld\n", + QNOVO_CL_SKEW_DECIPCT, chip->cl.final_cc_uah); + chip->cl.final_cc_uah = chip->cl.final_cc_uah * + (1000 + QNOVO_CL_SKEW_DECIPCT); + chip->cl.final_cc_uah = div64_u64(chip->cl.final_cc_uah, 1000); + } + + max_inc_val = chip->cl.learned_cc_uah + * (1000 + chip->dt.cl_max_cap_inc); + max_inc_val = div64_u64(max_inc_val, 1000); + + min_dec_val = chip->cl.learned_cc_uah + * (1000 - chip->dt.cl_max_cap_dec); + min_dec_val = div64_u64(min_dec_val, 1000); + + old_cap = chip->cl.learned_cc_uah; + if (chip->cl.final_cc_uah > max_inc_val) + chip->cl.learned_cc_uah = max_inc_val; + else if (chip->cl.final_cc_uah < min_dec_val) + chip->cl.learned_cc_uah = min_dec_val; + else + chip->cl.learned_cc_uah = + chip->cl.final_cc_uah; + + if (chip->dt.cl_max_cap_limit) { + max_inc_val = (int64_t)chip->cl.nom_cap_uah * (1000 + + chip->dt.cl_max_cap_limit); + max_inc_val = div64_u64(max_inc_val, 1000); + if (chip->cl.final_cc_uah > max_inc_val) { + fg_dbg(fg, FG_CAP_LEARN, "learning capacity %lld goes above max limit %lld\n", + chip->cl.final_cc_uah, max_inc_val); + chip->cl.learned_cc_uah = max_inc_val; + } + } + + if (chip->dt.cl_min_cap_limit) { + min_dec_val = (int64_t)chip->cl.nom_cap_uah * (1000 - + chip->dt.cl_min_cap_limit); + min_dec_val = div64_u64(min_dec_val, 1000); + if (chip->cl.final_cc_uah < min_dec_val) { + fg_dbg(fg, FG_CAP_LEARN, "learning capacity %lld goes below min limit %lld\n", + chip->cl.final_cc_uah, min_dec_val); + chip->cl.learned_cc_uah = min_dec_val; + } + } + + rc = fg_save_learned_cap_to_sram(fg); + if (rc < 0) + pr_err("Error in saving learned_cc_uah, rc=%d\n", rc); + + fg_dbg(fg, FG_CAP_LEARN, "final cc_uah = %lld, learned capacity %lld -> %lld uah\n", + chip->cl.final_cc_uah, old_cap, chip->cl.learned_cc_uah); +} + +static int fg_cap_learning_process_full_data(struct fg_dev *fg) +{ + struct fg_gen3_chip *chip = container_of(fg, struct fg_gen3_chip, fg); + int rc; + unsigned int cc_soc_sw; + int64_t delta_cc_uah; + unsigned int cc_soc_delta_pct; + + rc = fg_get_sram_prop(fg, FG_SRAM_CC_SOC_SW, &cc_soc_sw); + if (rc < 0) { + pr_err("Error in getting CC_SOC_SW, rc=%d\n", rc); + return rc; + } + + cc_soc_delta_pct = + div64_s64((int64_t)(cc_soc_sw - chip->cl.init_cc_soc_sw) * 100, + CC_SOC_30BIT); + + /* If the delta is < 50%, then skip processing full data */ + if (cc_soc_delta_pct < 50) { + pr_err("cc_soc_delta_pct: %d\n", cc_soc_delta_pct); + return -ERANGE; + } + + delta_cc_uah = div64_u64(chip->cl.learned_cc_uah * cc_soc_delta_pct, + 100); + chip->cl.final_cc_uah = chip->cl.init_cc_uah + delta_cc_uah; + fg_dbg(fg, FG_CAP_LEARN, "Current cc_soc=%d cc_soc_delta_pct=%u total_cc_uah=%llu\n", + cc_soc_sw, cc_soc_delta_pct, chip->cl.final_cc_uah); + return 0; +} + +static int fg_cap_learning_begin(struct fg_dev *fg, u32 batt_soc) +{ + struct fg_gen3_chip *chip = container_of(fg, struct fg_gen3_chip, fg); + int rc; + unsigned int batt_soc_msb, cc_soc_sw; + + batt_soc_msb = batt_soc >> 24; + if (DIV_ROUND_CLOSEST(batt_soc_msb * 100, FULL_SOC_RAW) > + chip->dt.cl_start_soc) { + fg_dbg(fg, FG_CAP_LEARN, "Battery SOC %u is high!, not starting\n", + batt_soc_msb); + return -EINVAL; + } + + chip->cl.init_cc_uah = div64_u64(chip->cl.learned_cc_uah * batt_soc_msb, + FULL_SOC_RAW); + + /* Prime cc_soc_sw with battery SOC when capacity learning begins */ + cc_soc_sw = div64_u64((uint64_t)batt_soc * CC_SOC_30BIT, + BATT_SOC_32BIT); + rc = fg_prime_cc_soc_sw(fg, cc_soc_sw); + if (rc < 0) { + pr_err("Error in writing cc_soc_sw, rc=%d\n", rc); + goto out; + } + + chip->cl.init_cc_soc_sw = cc_soc_sw; + fg_dbg(fg, FG_CAP_LEARN, "Capacity learning started @ battery SOC %d init_cc_soc_sw:%d\n", + batt_soc_msb, chip->cl.init_cc_soc_sw); +out: + return rc; +} + +static int fg_cap_learning_done(struct fg_dev *fg) +{ + int rc; + unsigned int cc_soc_sw; + + rc = fg_cap_learning_process_full_data(fg); + if (rc < 0) { + pr_err("Error in processing cap learning full data, rc=%d\n", + rc); + goto out; + } + + /* Write a FULL value to cc_soc_sw */ + cc_soc_sw = CC_SOC_30BIT; + rc = fg_prime_cc_soc_sw(fg, cc_soc_sw); + if (rc < 0) { + pr_err("Error in writing cc_soc_sw, rc=%d\n", rc); + goto out; + } + + fg_cap_learning_post_process(fg); +out: + return rc; +} + +static void fg_cap_learning_update(struct fg_dev *fg) +{ + struct fg_gen3_chip *chip = container_of(fg, struct fg_gen3_chip, fg); + int rc; + unsigned int batt_soc, batt_soc_msb, cc_soc_sw; + bool input_present = is_input_present(fg); + bool prime_cc = false; + + mutex_lock(&chip->cl.lock); + + if (!is_temp_valid_cap_learning(fg) || !chip->cl.learned_cc_uah || + fg->battery_missing) { + fg_dbg(fg, FG_CAP_LEARN, "Aborting cap_learning %lld\n", + chip->cl.learned_cc_uah); + chip->cl.active = false; + chip->cl.init_cc_uah = 0; + goto out; + } + + if (fg->charge_status == fg->prev_charge_status) + goto out; + + rc = fg_get_sram_prop(fg, FG_SRAM_BATT_SOC, &batt_soc); + if (rc < 0) { + pr_err("Error in getting ACT_BATT_CAP, rc=%d\n", rc); + goto out; + } + + batt_soc_msb = (u32)batt_soc >> 24; + fg_dbg(fg, FG_CAP_LEARN, "Chg_status: %d cl_active: %d batt_soc: %d\n", + fg->charge_status, chip->cl.active, batt_soc_msb); + + /* Initialize the starting point of learning capacity */ + if (!chip->cl.active) { + if (fg->charge_status == POWER_SUPPLY_STATUS_CHARGING) { + rc = fg_cap_learning_begin(fg, batt_soc); + chip->cl.active = (rc == 0); + } else { + if ((fg->charge_status == + POWER_SUPPLY_STATUS_DISCHARGING) || + fg->charge_done) + prime_cc = true; + } + } else { + if (fg->charge_done) { + rc = fg_cap_learning_done(fg); + if (rc < 0) + pr_err("Error in completing capacity learning, rc=%d\n", + rc); + + chip->cl.active = false; + chip->cl.init_cc_uah = 0; + } + + if (fg->charge_status == POWER_SUPPLY_STATUS_DISCHARGING) { + if (!input_present) { + fg_dbg(fg, FG_CAP_LEARN, "Capacity learning aborted @ battery SOC %d\n", + batt_soc_msb); + chip->cl.active = false; + chip->cl.init_cc_uah = 0; + prime_cc = true; + } + } + + if (fg->charge_status == POWER_SUPPLY_STATUS_NOT_CHARGING) { + if (is_qnovo_en(fg) && input_present) { + /* + * Don't abort the capacity learning when qnovo + * is enabled and input is present where the + * charging status can go to "not charging" + * intermittently. + */ + } else { + fg_dbg(fg, FG_CAP_LEARN, "Capacity learning aborted @ battery SOC %d\n", + batt_soc_msb); + chip->cl.active = false; + chip->cl.init_cc_uah = 0; + prime_cc = true; + } + } + } + + /* + * Prime CC_SOC_SW when the device is not charging or during charge + * termination when the capacity learning is not active. + */ + + if (prime_cc) { + if (fg->charge_done) + cc_soc_sw = CC_SOC_30BIT; + else + cc_soc_sw = div_u64((uint64_t)batt_soc * + CC_SOC_30BIT, BATT_SOC_32BIT); + + rc = fg_prime_cc_soc_sw(fg, cc_soc_sw); + if (rc < 0) + pr_err("Error in writing cc_soc_sw, rc=%d\n", + rc); + } + +out: + mutex_unlock(&chip->cl.lock); +} + +#define KI_COEFF_LOW_DISCHG_DEFAULT 800 +#define KI_COEFF_MED_DISCHG_DEFAULT 1500 +#define KI_COEFF_HI_DISCHG_DEFAULT 2200 +static int fg_adjust_ki_coeff_dischg(struct fg_dev *fg) +{ + struct fg_gen3_chip *chip = container_of(fg, struct fg_gen3_chip, fg); + int rc, i, msoc; + int ki_coeff_low = KI_COEFF_LOW_DISCHG_DEFAULT; + int ki_coeff_med = KI_COEFF_MED_DISCHG_DEFAULT; + int ki_coeff_hi = KI_COEFF_HI_DISCHG_DEFAULT; + u8 val; + + if (!chip->ki_coeff_dischg_en) + return 0; + + rc = fg_get_prop_capacity(fg, &msoc); + if (rc < 0) { + pr_err("Error in getting capacity, rc=%d\n", rc); + return rc; + } + + if (fg->charge_status == POWER_SUPPLY_STATUS_DISCHARGING) { + for (i = KI_COEFF_SOC_LEVELS - 1; i >= 0; i--) { + if (msoc < chip->dt.ki_coeff_soc[i]) { + ki_coeff_low = chip->dt.ki_coeff_low_dischg[i]; + ki_coeff_med = chip->dt.ki_coeff_med_dischg[i]; + ki_coeff_hi = chip->dt.ki_coeff_hi_dischg[i]; + } + } + } + + fg_encode(fg->sp, FG_SRAM_KI_COEFF_LOW_DISCHG, ki_coeff_low, &val); + rc = fg_sram_write(fg, + fg->sp[FG_SRAM_KI_COEFF_LOW_DISCHG].addr_word, + fg->sp[FG_SRAM_KI_COEFF_LOW_DISCHG].addr_byte, &val, + fg->sp[FG_SRAM_KI_COEFF_LOW_DISCHG].len, + FG_IMA_DEFAULT); + if (rc < 0) { + pr_err("Error in writing ki_coeff_low, rc=%d\n", rc); + return rc; + } + + fg_encode(fg->sp, FG_SRAM_KI_COEFF_MED_DISCHG, ki_coeff_med, &val); + rc = fg_sram_write(fg, + fg->sp[FG_SRAM_KI_COEFF_MED_DISCHG].addr_word, + fg->sp[FG_SRAM_KI_COEFF_MED_DISCHG].addr_byte, &val, + fg->sp[FG_SRAM_KI_COEFF_MED_DISCHG].len, + FG_IMA_DEFAULT); + if (rc < 0) { + pr_err("Error in writing ki_coeff_med, rc=%d\n", rc); + return rc; + } + + fg_encode(fg->sp, FG_SRAM_KI_COEFF_HI_DISCHG, ki_coeff_hi, &val); + rc = fg_sram_write(fg, + fg->sp[FG_SRAM_KI_COEFF_HI_DISCHG].addr_word, + fg->sp[FG_SRAM_KI_COEFF_HI_DISCHG].addr_byte, &val, + fg->sp[FG_SRAM_KI_COEFF_HI_DISCHG].len, + FG_IMA_DEFAULT); + if (rc < 0) { + pr_err("Error in writing ki_coeff_hi, rc=%d\n", rc); + return rc; + } + + fg_dbg(fg, FG_STATUS, "Wrote ki_coeff_low %d ki_coeff_med %d ki_coeff_hi %d\n", + ki_coeff_low, ki_coeff_med, ki_coeff_hi); + return 0; +} + +#define KI_COEFF_FULL_SOC_DEFAULT 733 +static int fg_adjust_ki_coeff_full_soc(struct fg_dev *fg, int batt_temp) +{ + struct fg_gen3_chip *chip = container_of(fg, struct fg_gen3_chip, fg); + int rc, ki_coeff_full_soc; + u8 val; + + if (batt_temp < 0) + ki_coeff_full_soc = 0; + else if (fg->charge_status == POWER_SUPPLY_STATUS_DISCHARGING) + ki_coeff_full_soc = chip->dt.ki_coeff_full_soc_dischg; + else + ki_coeff_full_soc = KI_COEFF_FULL_SOC_DEFAULT; + + if (chip->ki_coeff_full_soc == ki_coeff_full_soc) + return 0; + + fg_encode(fg->sp, FG_SRAM_KI_COEFF_FULL_SOC, ki_coeff_full_soc, &val); + rc = fg_sram_write(fg, + fg->sp[FG_SRAM_KI_COEFF_FULL_SOC].addr_word, + fg->sp[FG_SRAM_KI_COEFF_FULL_SOC].addr_byte, &val, + fg->sp[FG_SRAM_KI_COEFF_FULL_SOC].len, + FG_IMA_DEFAULT); + if (rc < 0) { + pr_err("Error in writing ki_coeff_full_soc, rc=%d\n", rc); + return rc; + } + + chip->ki_coeff_full_soc = ki_coeff_full_soc; + fg_dbg(fg, FG_STATUS, "Wrote ki_coeff_full_soc %d\n", + ki_coeff_full_soc); + return 0; +} + +static int fg_set_recharge_voltage(struct fg_dev *fg, int voltage_mv) +{ + struct fg_gen3_chip *chip = container_of(fg, struct fg_gen3_chip, fg); + u8 buf; + int rc; + + if (chip->dt.auto_recharge_soc) + return 0; + + /* This configuration is available only for pmicobalt v2.0 and above */ + if (fg->wa_flags & PMI8998_V1_REV_WA) + return 0; + + if (voltage_mv == fg->last_recharge_volt_mv) + return 0; + + fg_dbg(fg, FG_STATUS, "Setting recharge voltage to %dmV\n", + voltage_mv); + fg_encode(fg->sp, FG_SRAM_RECHARGE_VBATT_THR, voltage_mv, &buf); + rc = fg_sram_write(fg, + fg->sp[FG_SRAM_RECHARGE_VBATT_THR].addr_word, + fg->sp[FG_SRAM_RECHARGE_VBATT_THR].addr_byte, + &buf, fg->sp[FG_SRAM_RECHARGE_VBATT_THR].len, + FG_IMA_DEFAULT); + if (rc < 0) { + pr_err("Error in writing recharge_vbatt_thr, rc=%d\n", + rc); + return rc; + } + + fg->last_recharge_volt_mv = voltage_mv; + return 0; +} + +static int fg_configure_full_soc(struct fg_dev *fg, int bsoc) +{ + int rc; + u8 full_soc[2] = {0xFF, 0xFF}; + + /* + * Once SOC masking condition is cleared, FULL_SOC and MONOTONIC_SOC + * needs to be updated to reflect the same. Write battery SOC to + * FULL_SOC and write a full value to MONOTONIC_SOC. + */ + rc = fg_sram_write(fg, FULL_SOC_WORD, FULL_SOC_OFFSET, + (u8 *)&bsoc, 2, FG_IMA_ATOMIC); + if (rc < 0) { + pr_err("failed to write full_soc rc=%d\n", rc); + return rc; + } + + rc = fg_sram_write(fg, MONOTONIC_SOC_WORD, MONOTONIC_SOC_OFFSET, + full_soc, 2, FG_IMA_ATOMIC); + if (rc < 0) { + pr_err("failed to write monotonic_soc rc=%d\n", rc); + return rc; + } + + return 0; +} + +#define AUTO_RECHG_VOLT_LOW_LIMIT_MV 3700 +static int fg_charge_full_update(struct fg_dev *fg) +{ + struct fg_gen3_chip *chip = container_of(fg, struct fg_gen3_chip, fg); + union power_supply_propval prop = {0, }; + int rc, msoc, bsoc, recharge_soc, msoc_raw; + + if (!chip->dt.hold_soc_while_full) + return 0; + + if (!batt_psy_initialized(fg)) + return 0; + + mutex_lock(&fg->charge_full_lock); + vote(fg->delta_bsoc_irq_en_votable, DELTA_BSOC_IRQ_VOTER, + fg->charge_done, 0); + rc = power_supply_get_property(fg->batt_psy, POWER_SUPPLY_PROP_HEALTH, + &prop); + if (rc < 0) { + pr_err("Error in getting battery health, rc=%d\n", rc); + goto out; + } + + fg->health = prop.intval; + recharge_soc = chip->dt.recharge_soc_thr; + recharge_soc = DIV_ROUND_CLOSEST(recharge_soc * FULL_SOC_RAW, + FULL_CAPACITY); + rc = fg_get_sram_prop(fg, FG_SRAM_BATT_SOC, &bsoc); + if (rc < 0) { + pr_err("Error in getting BATT_SOC, rc=%d\n", rc); + goto out; + } + + /* We need 2 most significant bytes here */ + bsoc = (u32)bsoc >> 16; + rc = fg_get_msoc_raw(fg, &msoc_raw); + if (rc < 0) { + pr_err("Error in getting msoc_raw, rc=%d\n", rc); + goto out; + } + msoc = DIV_ROUND_CLOSEST(msoc_raw * FULL_CAPACITY, FULL_SOC_RAW); + + fg_dbg(fg, FG_STATUS, "msoc: %d bsoc: %x health: %d status: %d full: %d\n", + msoc, bsoc, fg->health, fg->charge_status, + fg->charge_full); + if (fg->charge_done && !fg->charge_full) { + if (msoc >= 99 && fg->health == POWER_SUPPLY_HEALTH_GOOD) { + fg_dbg(fg, FG_STATUS, "Setting charge_full to true\n"); + fg->charge_full = true; + /* + * Lower the recharge voltage so that VBAT_LT_RECHG + * signal will not be asserted soon. + */ + rc = fg_set_recharge_voltage(fg, + AUTO_RECHG_VOLT_LOW_LIMIT_MV); + if (rc < 0) { + pr_err("Error in reducing recharge voltage, rc=%d\n", + rc); + goto out; + } + } else { + fg_dbg(fg, FG_STATUS, "Terminated charging @ SOC%d\n", + msoc); + } + } else if ((msoc_raw <= recharge_soc || !fg->charge_done) + && fg->charge_full) { + if (chip->dt.linearize_soc) { + fg->delta_soc = FULL_CAPACITY - msoc; + + /* + * We're spreading out the delta SOC over every 10% + * change in monotonic SOC. We cannot spread more than + * 9% in the range of 0-100 skipping the first 10%. + */ + if (fg->delta_soc > 9) { + fg->delta_soc = 0; + fg->maint_soc = 0; + } else { + fg->maint_soc = FULL_CAPACITY; + fg->last_msoc = msoc; + } + } + + /* + * Raise the recharge voltage so that VBAT_LT_RECHG signal + * will be asserted soon as battery SOC had dropped below + * the recharge SOC threshold. + */ + rc = fg_set_recharge_voltage(fg, + chip->dt.recharge_volt_thr_mv); + if (rc < 0) { + pr_err("Error in setting recharge voltage, rc=%d\n", + rc); + goto out; + } + + /* + * If charge_done is still set, wait for recharging or + * discharging to happen. + */ + if (fg->charge_done) + goto out; + + rc = fg_configure_full_soc(fg, bsoc); + if (rc < 0) + goto out; + + fg->charge_full = false; + fg_dbg(fg, FG_STATUS, "msoc_raw = %d bsoc: %d recharge_soc: %d delta_soc: %d\n", + msoc_raw, bsoc >> 8, recharge_soc, fg->delta_soc); + } + +out: + mutex_unlock(&fg->charge_full_lock); + return rc; +} + +#define RCONN_CONFIG_BIT BIT(0) +static int fg_rconn_config(struct fg_dev *fg) +{ + struct fg_gen3_chip *chip = container_of(fg, struct fg_gen3_chip, fg); + int rc, esr_uohms; + u64 scaling_factor; + u32 val = 0; + + if (!chip->dt.rconn_mohms) + return 0; + + rc = fg_sram_read(fg, PROFILE_INTEGRITY_WORD, + SW_CONFIG_OFFSET, (u8 *)&val, 1, FG_IMA_DEFAULT); + if (rc < 0) { + pr_err("Error in reading SW_CONFIG_OFFSET, rc=%d\n", rc); + return rc; + } + + if (val & RCONN_CONFIG_BIT) { + fg_dbg(fg, FG_STATUS, "Rconn already configured: %x\n", val); + return 0; + } + + rc = fg_get_sram_prop(fg, FG_SRAM_ESR, &esr_uohms); + if (rc < 0) { + pr_err("failed to get ESR, rc=%d\n", rc); + return rc; + } + + scaling_factor = div64_u64((u64)esr_uohms * 1000, + esr_uohms + (chip->dt.rconn_mohms * 1000)); + + rc = fg_sram_read(fg, ESR_RSLOW_CHG_WORD, + ESR_RSLOW_CHG_OFFSET, (u8 *)&val, 1, FG_IMA_DEFAULT); + if (rc < 0) { + pr_err("Error in reading ESR_RSLOW_CHG_OFFSET, rc=%d\n", rc); + return rc; + } + + val *= scaling_factor; + val = div64_u64(val, 1000); + rc = fg_sram_write(fg, ESR_RSLOW_CHG_WORD, + ESR_RSLOW_CHG_OFFSET, (u8 *)&val, 1, FG_IMA_DEFAULT); + if (rc < 0) { + pr_err("Error in writing ESR_RSLOW_CHG_OFFSET, rc=%d\n", rc); + return rc; + } + fg_dbg(fg, FG_STATUS, "esr_rslow_chg modified to %x\n", val & 0xFF); + + rc = fg_sram_read(fg, ESR_RSLOW_DISCHG_WORD, + ESR_RSLOW_DISCHG_OFFSET, (u8 *)&val, 1, FG_IMA_DEFAULT); + if (rc < 0) { + pr_err("Error in reading ESR_RSLOW_DISCHG_OFFSET, rc=%d\n", rc); + return rc; + } + + val *= scaling_factor; + val = div64_u64(val, 1000); + rc = fg_sram_write(fg, ESR_RSLOW_DISCHG_WORD, + ESR_RSLOW_DISCHG_OFFSET, (u8 *)&val, 1, FG_IMA_DEFAULT); + if (rc < 0) { + pr_err("Error in writing ESR_RSLOW_DISCHG_OFFSET, rc=%d\n", rc); + return rc; + } + fg_dbg(fg, FG_STATUS, "esr_rslow_dischg modified to %x\n", + val & 0xFF); + + val = RCONN_CONFIG_BIT; + rc = fg_sram_write(fg, PROFILE_INTEGRITY_WORD, + SW_CONFIG_OFFSET, (u8 *)&val, 1, FG_IMA_DEFAULT); + if (rc < 0) { + pr_err("Error in writing SW_CONFIG_OFFSET, rc=%d\n", rc); + return rc; + } + + return 0; +} + +static int fg_set_jeita_threshold(struct fg_dev *fg, + enum jeita_levels level, int temp_decidegC) +{ + int rc; + u8 val; + u16 reg; + + if (temp_decidegC < -300 || temp_decidegC > 970) + return -EINVAL; + + /* Resolution is 0.5C. Base is -30C. */ + val = DIV_ROUND_CLOSEST(((temp_decidegC / 10) + 30) * 10, 5); + switch (level) { + case JEITA_COLD: + reg = BATT_INFO_JEITA_TOO_COLD(fg); + break; + case JEITA_COOL: + reg = BATT_INFO_JEITA_COLD(fg); + break; + case JEITA_WARM: + reg = BATT_INFO_JEITA_HOT(fg); + break; + case JEITA_HOT: + reg = BATT_INFO_JEITA_TOO_HOT(fg); + break; + default: + return -EINVAL; + } + + rc = fg_write(fg, reg, &val, 1); + if (rc < 0) { + pr_err("Error in setting jeita level %d, rc=%d\n", level, rc); + return rc; + } + + return 0; +} + +static int fg_set_recharge_soc(struct fg_dev *fg, int recharge_soc) +{ + struct fg_gen3_chip *chip = container_of(fg, struct fg_gen3_chip, fg); + u8 buf; + int rc; + + if (!chip->dt.auto_recharge_soc) + return 0; + + if (recharge_soc < 0 || recharge_soc > FULL_CAPACITY) + return 0; + + fg_encode(fg->sp, FG_SRAM_RECHARGE_SOC_THR, recharge_soc, &buf); + rc = fg_sram_write(fg, + fg->sp[FG_SRAM_RECHARGE_SOC_THR].addr_word, + fg->sp[FG_SRAM_RECHARGE_SOC_THR].addr_byte, &buf, + fg->sp[FG_SRAM_RECHARGE_SOC_THR].len, FG_IMA_DEFAULT); + if (rc < 0) { + pr_err("Error in writing recharge_soc_thr, rc=%d\n", rc); + return rc; + } + + return 0; +} + +static int fg_adjust_recharge_soc(struct fg_dev *fg) +{ + struct fg_gen3_chip *chip = container_of(fg, struct fg_gen3_chip, fg); + union power_supply_propval prop = {0, }; + int rc, msoc, recharge_soc, new_recharge_soc = 0; + bool recharge_soc_status; + + if (!chip->dt.auto_recharge_soc) + return 0; + + rc = power_supply_get_property(fg->batt_psy, POWER_SUPPLY_PROP_HEALTH, + &prop); + if (rc < 0) { + pr_err("Error in getting battery health, rc=%d\n", rc); + return rc; + } + fg->health = prop.intval; + + recharge_soc = chip->dt.recharge_soc_thr; + recharge_soc_status = fg->recharge_soc_adjusted; + /* + * If the input is present and charging had been terminated, adjust + * the recharge SOC threshold based on the monotonic SOC at which + * the charge termination had happened. + */ + if (is_input_present(fg)) { + if (fg->charge_done) { + if (!fg->recharge_soc_adjusted) { + /* Get raw monotonic SOC for calculation */ + rc = fg_get_msoc(fg, &msoc); + if (rc < 0) { + pr_err("Error in getting msoc, rc=%d\n", + rc); + return rc; + } + + /* Adjust the recharge_soc threshold */ + new_recharge_soc = msoc - (FULL_CAPACITY - + recharge_soc); + fg->recharge_soc_adjusted = true; + } else { + /* adjusted already, do nothing */ + if (fg->health != POWER_SUPPLY_HEALTH_GOOD) + return 0; + + /* + * Device is out of JEITA so restore the + * default value + */ + new_recharge_soc = recharge_soc; + fg->recharge_soc_adjusted = false; + } + } else { + if (!fg->recharge_soc_adjusted) + return 0; + + if (fg->health != POWER_SUPPLY_HEALTH_GOOD) + return 0; + + /* Restore the default value */ + new_recharge_soc = recharge_soc; + fg->recharge_soc_adjusted = false; + } + } else { + /* Restore the default value */ + new_recharge_soc = recharge_soc; + fg->recharge_soc_adjusted = false; + } + + rc = fg_set_recharge_soc(fg, new_recharge_soc); + if (rc < 0) { + fg->recharge_soc_adjusted = recharge_soc_status; + pr_err("Couldn't set resume SOC for FG, rc=%d\n", rc); + return rc; + } + + fg_dbg(fg, FG_STATUS, "resume soc set to %d\n", new_recharge_soc); + return 0; +} + +static int fg_adjust_recharge_voltage(struct fg_dev *fg) +{ + struct fg_gen3_chip *chip = container_of(fg, struct fg_gen3_chip, fg); + int rc, recharge_volt_mv; + + if (chip->dt.auto_recharge_soc) + return 0; + + fg_dbg(fg, FG_STATUS, "health: %d chg_status: %d chg_done: %d\n", + fg->health, fg->charge_status, fg->charge_done); + + recharge_volt_mv = chip->dt.recharge_volt_thr_mv; + + /* Lower the recharge voltage in soft JEITA */ + if (fg->health == POWER_SUPPLY_HEALTH_WARM || + fg->health == POWER_SUPPLY_HEALTH_COOL) + recharge_volt_mv -= 200; + + rc = fg_set_recharge_voltage(fg, recharge_volt_mv); + if (rc < 0) { + pr_err("Error in setting recharge_voltage, rc=%d\n", + rc); + return rc; + } + + return 0; +} + +static int fg_slope_limit_config(struct fg_dev *fg, int batt_temp) +{ + struct fg_gen3_chip *chip = container_of(fg, struct fg_gen3_chip, fg); + enum slope_limit_status status; + int rc; + u8 buf; + + if (!chip->slope_limit_en) + return 0; + + if (fg->charge_status == POWER_SUPPLY_STATUS_CHARGING || + fg->charge_status == POWER_SUPPLY_STATUS_FULL) { + if (batt_temp < chip->dt.slope_limit_temp) + status = LOW_TEMP_CHARGE; + else + status = HIGH_TEMP_CHARGE; + } else { + if (batt_temp < chip->dt.slope_limit_temp) + status = LOW_TEMP_DISCHARGE; + else + status = HIGH_TEMP_DISCHARGE; + } + + if (chip->slope_limit_sts == status) + return 0; + + fg_encode(fg->sp, FG_SRAM_SLOPE_LIMIT, + chip->dt.slope_limit_coeffs[status], &buf); + rc = fg_sram_write(fg, fg->sp[FG_SRAM_SLOPE_LIMIT].addr_word, + fg->sp[FG_SRAM_SLOPE_LIMIT].addr_byte, &buf, + fg->sp[FG_SRAM_SLOPE_LIMIT].len, FG_IMA_DEFAULT); + if (rc < 0) { + pr_err("Error in configuring slope_limit coefficient, rc=%d\n", + rc); + return rc; + } + + chip->slope_limit_sts = status; + fg_dbg(fg, FG_STATUS, "Slope limit status: %d value: %x\n", status, + buf); + return 0; +} + +static int __fg_esr_filter_config(struct fg_dev *fg, + enum esr_filter_status esr_flt_sts) +{ + u8 esr_tight_flt, esr_broad_flt; + int esr_tight_flt_upct, esr_broad_flt_upct; + int rc; + struct fg_gen3_chip *chip = container_of(fg, struct fg_gen3_chip, fg); + + if (esr_flt_sts == fg->esr_flt_sts) + return 0; + + if (esr_flt_sts == ROOM_TEMP) { + esr_tight_flt_upct = chip->dt.esr_tight_flt_upct; + esr_broad_flt_upct = chip->dt.esr_broad_flt_upct; + } else if (esr_flt_sts == LOW_TEMP) { + esr_tight_flt_upct = chip->dt.esr_tight_lt_flt_upct; + esr_broad_flt_upct = chip->dt.esr_broad_lt_flt_upct; + } else if (esr_flt_sts == RELAX_TEMP) { + esr_tight_flt_upct = chip->dt.esr_tight_rt_flt_upct; + esr_broad_flt_upct = chip->dt.esr_broad_rt_flt_upct; + } else { + pr_err("Unknown esr filter config\n"); + return 0; + } + + fg_encode(fg->sp, FG_SRAM_ESR_TIGHT_FILTER, esr_tight_flt_upct, + &esr_tight_flt); + rc = fg_sram_write(fg, fg->sp[FG_SRAM_ESR_TIGHT_FILTER].addr_word, + fg->sp[FG_SRAM_ESR_TIGHT_FILTER].addr_byte, + &esr_tight_flt, + fg->sp[FG_SRAM_ESR_TIGHT_FILTER].len, FG_IMA_DEFAULT); + if (rc < 0) { + pr_err("Error in writing ESR LT tight filter, rc=%d\n", rc); + return rc; + } + + fg_encode(fg->sp, FG_SRAM_ESR_BROAD_FILTER, esr_broad_flt_upct, + &esr_broad_flt); + rc = fg_sram_write(fg, fg->sp[FG_SRAM_ESR_BROAD_FILTER].addr_word, + fg->sp[FG_SRAM_ESR_BROAD_FILTER].addr_byte, + &esr_broad_flt, + fg->sp[FG_SRAM_ESR_BROAD_FILTER].len, FG_IMA_DEFAULT); + if (rc < 0) { + pr_err("Error in writing ESR LT broad filter, rc=%d\n", rc); + return rc; + } + + fg->esr_flt_sts = esr_flt_sts; + fg_dbg(fg, FG_STATUS, "applied ESR filter %d values\n", esr_flt_sts); + return 0; +} + +#define DT_IRQ_COUNT 3 +#define DELTA_TEMP_IRQ_TIME_MS 300000 +#define ESR_FILTER_ALARM_TIME_MS 900000 +static int fg_esr_filter_config(struct fg_dev *fg, int batt_temp, + bool override) +{ + enum esr_filter_status esr_flt_sts = ROOM_TEMP; + bool qnovo_en, input_present, count_temp_irq = false; + s64 time_ms; + int rc; + struct fg_gen3_chip *chip = container_of(fg, struct fg_gen3_chip, fg); + + /* + * If the battery temperature is lower than -20 C, then skip modifying + * ESR filter. + */ + if (batt_temp < -210) + return 0; + + qnovo_en = is_qnovo_en(fg); + input_present = is_input_present(fg); + + /* + * If Qnovo is enabled, after hitting a lower battery temperature of + * say 6 C, count the delta battery temperature interrupts for a + * certain period of time when the battery temperature increases. + * Switch to relaxed filter coefficients once the temperature increase + * is qualified so that ESR accuracy can be improved. + */ + if (qnovo_en && !override) { + if (input_present) { + if (fg->esr_flt_sts == RELAX_TEMP) { + /* do nothing */ + return 0; + } + + count_temp_irq = true; + if (fg->delta_temp_irq_count) { + /* Don't count when temperature is dropping. */ + if (batt_temp <= fg->last_batt_temp) + count_temp_irq = false; + } else { + /* + * Starting point for counting. Check if the + * temperature is qualified. + */ + if (batt_temp > chip->dt.esr_flt_rt_switch_temp) + count_temp_irq = false; + else + fg->last_delta_temp_time = + ktime_get(); + } + } else { + fg->delta_temp_irq_count = 0; + rc = alarm_try_to_cancel(&fg->esr_filter_alarm); + if (rc < 0) + pr_err("Couldn't cancel esr_filter_alarm\n"); + } + } + + /* + * If battery temperature is lesser than 10 C (default), then apply the + * ESR low temperature tight and broad filter values to ESR room + * temperature tight and broad filters. If battery temperature is higher + * than 10 C, then apply back the room temperature ESR filter + * coefficients to ESR room temperature tight and broad filters. + */ + if (batt_temp > chip->dt.esr_flt_switch_temp) + esr_flt_sts = ROOM_TEMP; + else + esr_flt_sts = LOW_TEMP; + + if (count_temp_irq) { + time_ms = ktime_ms_delta(ktime_get(), + fg->last_delta_temp_time); + fg->delta_temp_irq_count++; + fg_dbg(fg, FG_STATUS, "dt_irq_count: %d\n", + fg->delta_temp_irq_count); + + if (fg->delta_temp_irq_count >= DT_IRQ_COUNT + && time_ms <= DELTA_TEMP_IRQ_TIME_MS) { + fg_dbg(fg, FG_STATUS, "%d interrupts in %lld ms\n", + fg->delta_temp_irq_count, time_ms); + esr_flt_sts = RELAX_TEMP; + } + } + + rc = __fg_esr_filter_config(fg, esr_flt_sts); + if (rc < 0) + return rc; + + if (esr_flt_sts == RELAX_TEMP) + alarm_start_relative(&fg->esr_filter_alarm, + ms_to_ktime(ESR_FILTER_ALARM_TIME_MS)); + + return 0; +} + +#define FG_ESR_FILTER_RESTART_MS 60000 +static void esr_filter_work(struct work_struct *work) +{ + struct fg_dev *fg = container_of(work, + struct fg_dev, status_change_work); + int rc, batt_temp; + + rc = fg_get_battery_temp(fg, &batt_temp); + if (rc < 0) { + pr_err("Error in getting batt_temp\n"); + alarm_start_relative(&fg->esr_filter_alarm, + ms_to_ktime(FG_ESR_FILTER_RESTART_MS)); + goto out; + } + + rc = fg_esr_filter_config(fg, batt_temp, true); + if (rc < 0) { + pr_err("Error in configuring ESR filter rc:%d\n", rc); + alarm_start_relative(&fg->esr_filter_alarm, + ms_to_ktime(FG_ESR_FILTER_RESTART_MS)); + } + +out: + fg->delta_temp_irq_count = 0; + pm_relax(fg->dev); +} + +static enum alarmtimer_restart fg_esr_filter_alarm_cb(struct alarm *alarm, + ktime_t now) +{ + struct fg_dev *fg = container_of(alarm, + struct fg_dev, esr_filter_alarm); + + fg_dbg(fg, FG_STATUS, "ESR filter alarm triggered %lld\n", + ktime_to_ms(now)); + /* + * We cannot vote for awake votable here as that takes a mutex lock + * and this is executed in an atomic context. + */ + pm_stay_awake(fg->dev); + schedule_work(&fg->esr_filter_work); + + return ALARMTIMER_NORESTART; +} + +static int fg_esr_fcc_config(struct fg_dev *fg) +{ + struct fg_gen3_chip *chip = container_of(fg, struct fg_gen3_chip, fg); + union power_supply_propval prop = {0, }; + int rc; + bool parallel_en = false, qnovo_en; + + if (is_parallel_charger_available(fg)) { + rc = power_supply_get_property(fg->parallel_psy, + POWER_SUPPLY_PROP_CHARGING_ENABLED, &prop); + if (rc < 0) { + pr_err("Error in reading charging_enabled from parallel_psy, rc=%d\n", + rc); + return rc; + } + parallel_en = prop.intval; + } + + qnovo_en = is_qnovo_en(fg); + + fg_dbg(fg, FG_POWER_SUPPLY, "chg_sts: %d par_en: %d qnov_en: %d esr_fcc_ctrl_en: %d\n", + fg->charge_status, parallel_en, qnovo_en, + chip->esr_fcc_ctrl_en); + + if (fg->charge_status == POWER_SUPPLY_STATUS_CHARGING && + (parallel_en || qnovo_en)) { + if (chip->esr_fcc_ctrl_en) + return 0; + + /* + * When parallel charging or Qnovo is enabled, configure ESR + * FCC to 300mA to trigger an ESR pulse. Without this, FG can + * request the main charger to increase FCC when it is supposed + * to decrease it. + */ + rc = fg_masked_write(fg, BATT_INFO_ESR_FAST_CRG_CFG(fg), + ESR_FAST_CRG_IVAL_MASK | + ESR_FAST_CRG_CTL_EN_BIT, + ESR_FCC_300MA | ESR_FAST_CRG_CTL_EN_BIT); + if (rc < 0) { + pr_err("Error in writing to %04x, rc=%d\n", + BATT_INFO_ESR_FAST_CRG_CFG(fg), rc); + return rc; + } + + chip->esr_fcc_ctrl_en = true; + } else { + if (!chip->esr_fcc_ctrl_en) + return 0; + + /* + * If we're here, then it means either the device is not in + * charging state or parallel charging / Qnovo is disabled. + * Disable ESR fast charge current control in SW. + */ + rc = fg_masked_write(fg, BATT_INFO_ESR_FAST_CRG_CFG(fg), + ESR_FAST_CRG_CTL_EN_BIT, 0); + if (rc < 0) { + pr_err("Error in writing to %04x, rc=%d\n", + BATT_INFO_ESR_FAST_CRG_CFG(fg), rc); + return rc; + } + + chip->esr_fcc_ctrl_en = false; + } + + fg_dbg(fg, FG_STATUS, "esr_fcc_ctrl_en set to %d\n", + chip->esr_fcc_ctrl_en); + return 0; +} + +static int fg_esr_timer_config(struct fg_dev *fg, bool sleep) +{ + struct fg_gen3_chip *chip = container_of(fg, struct fg_gen3_chip, fg); + int rc, cycles_init, cycles_max; + bool end_of_charge = false; + + end_of_charge = is_input_present(fg) && fg->charge_done; + fg_dbg(fg, FG_STATUS, "sleep: %d eoc: %d\n", sleep, end_of_charge); + + /* ESR discharging timer configuration */ + cycles_init = sleep ? chip->dt.esr_timer_asleep[TIMER_RETRY] : + chip->dt.esr_timer_awake[TIMER_RETRY]; + if (end_of_charge) + cycles_init = 0; + + cycles_max = sleep ? chip->dt.esr_timer_asleep[TIMER_MAX] : + chip->dt.esr_timer_awake[TIMER_MAX]; + + rc = fg_set_esr_timer(fg, cycles_init, cycles_max, false, + sleep ? FG_IMA_NO_WLOCK : FG_IMA_DEFAULT); + if (rc < 0) { + pr_err("Error in setting ESR timer, rc=%d\n", rc); + return rc; + } + + /* ESR charging timer configuration */ + cycles_init = cycles_max = -EINVAL; + if (end_of_charge || sleep) { + cycles_init = chip->dt.esr_timer_charging[TIMER_RETRY]; + cycles_max = chip->dt.esr_timer_charging[TIMER_MAX]; + } else if (is_input_present(fg)) { + cycles_init = chip->esr_timer_charging_default[TIMER_RETRY]; + cycles_max = chip->esr_timer_charging_default[TIMER_MAX]; + } + + rc = fg_set_esr_timer(fg, cycles_init, cycles_max, true, + sleep ? FG_IMA_NO_WLOCK : FG_IMA_DEFAULT); + if (rc < 0) { + pr_err("Error in setting ESR timer, rc=%d\n", rc); + return rc; + } + + return 0; +} + +static void fg_ttf_update(struct fg_dev *fg) +{ + struct fg_gen3_chip *chip = container_of(fg, struct fg_gen3_chip, fg); + int rc; + int delay_ms; + union power_supply_propval prop = {0, }; + int online = 0; + + if (usb_psy_initialized(fg)) { + rc = power_supply_get_property(fg->usb_psy, + POWER_SUPPLY_PROP_ONLINE, &prop); + if (rc < 0) { + pr_err("Couldn't read usb ONLINE prop rc=%d\n", rc); + return; + } + + online = online || prop.intval; + } + + if (pc_port_psy_initialized(fg)) { + rc = power_supply_get_property(fg->pc_port_psy, + POWER_SUPPLY_PROP_ONLINE, &prop); + if (rc < 0) { + pr_err("Couldn't read pc_port ONLINE prop rc=%d\n", rc); + return; + } + + online = online || prop.intval; + } + + if (dc_psy_initialized(fg)) { + rc = power_supply_get_property(fg->dc_psy, + POWER_SUPPLY_PROP_ONLINE, &prop); + if (rc < 0) { + pr_err("Couldn't read dc ONLINE prop rc=%d\n", rc); + return; + } + + online = online || prop.intval; + } + + + if (fg->online_status == online) + return; + + fg->online_status = online; + if (online) + /* wait 35 seconds for the input to settle */ + delay_ms = 35000; + else + /* wait 5 seconds for current to settle during discharge */ + delay_ms = 5000; + + vote(fg->awake_votable, TTF_PRIMING, true, 0); + cancel_delayed_work_sync(&chip->ttf_work); + mutex_lock(&chip->ttf.lock); + fg_circ_buf_clr(&chip->ttf.ibatt); + fg_circ_buf_clr(&chip->ttf.vbatt); + chip->ttf.last_ttf = 0; + chip->ttf.last_ms = 0; + mutex_unlock(&chip->ttf.lock); + schedule_delayed_work(&chip->ttf_work, msecs_to_jiffies(delay_ms)); +} + +static void restore_cycle_counter(struct fg_dev *fg) +{ + struct fg_gen3_chip *chip = container_of(fg, struct fg_gen3_chip, fg); + int rc = 0, i; + u8 data[2]; + + if (!chip->cyc_ctr.en) + return; + + mutex_lock(&chip->cyc_ctr.lock); + for (i = 0; i < BUCKET_COUNT; i++) { + rc = fg_sram_read(fg, CYCLE_COUNT_WORD + (i / 2), + CYCLE_COUNT_OFFSET + (i % 2) * 2, data, 2, + FG_IMA_DEFAULT); + if (rc < 0) + pr_err("failed to read bucket %d rc=%d\n", i, rc); + else + chip->cyc_ctr.count[i] = data[0] | data[1] << 8; + } + mutex_unlock(&chip->cyc_ctr.lock); +} + +static void clear_cycle_counter(struct fg_dev *fg) +{ + struct fg_gen3_chip *chip = container_of(fg, struct fg_gen3_chip, fg); + int rc = 0, i; + + if (!chip->cyc_ctr.en) + return; + + mutex_lock(&chip->cyc_ctr.lock); + memset(chip->cyc_ctr.count, 0, sizeof(chip->cyc_ctr.count)); + for (i = 0; i < BUCKET_COUNT; i++) { + chip->cyc_ctr.started[i] = false; + chip->cyc_ctr.last_soc[i] = 0; + } + rc = fg_sram_write(fg, CYCLE_COUNT_WORD, CYCLE_COUNT_OFFSET, + (u8 *)&chip->cyc_ctr.count, + sizeof(chip->cyc_ctr.count) / (sizeof(u8 *)), + FG_IMA_DEFAULT); + if (rc < 0) + pr_err("failed to clear cycle counter rc=%d\n", rc); + + mutex_unlock(&chip->cyc_ctr.lock); +} + +static int fg_inc_store_cycle_ctr(struct fg_dev *fg, int bucket) +{ + struct fg_gen3_chip *chip = container_of(fg, struct fg_gen3_chip, fg); + int rc = 0; + u16 cyc_count; + u8 data[2]; + + if (bucket < 0 || (bucket > BUCKET_COUNT - 1)) + return 0; + + cyc_count = chip->cyc_ctr.count[bucket]; + cyc_count++; + data[0] = cyc_count & 0xFF; + data[1] = cyc_count >> 8; + + rc = fg_sram_write(fg, CYCLE_COUNT_WORD + (bucket / 2), + CYCLE_COUNT_OFFSET + (bucket % 2) * 2, data, 2, + FG_IMA_DEFAULT); + if (rc < 0) { + pr_err("failed to write BATT_CYCLE[%d] rc=%d\n", + bucket, rc); + return rc; + } + + chip->cyc_ctr.count[bucket] = cyc_count; + fg_dbg(fg, FG_STATUS, "Stored count %d in bucket %d\n", cyc_count, + bucket); + + return rc; +} + +static void fg_cycle_counter_update(struct fg_dev *fg) +{ + struct fg_gen3_chip *chip = container_of(fg, struct fg_gen3_chip, fg); + int rc = 0, bucket, i, batt_soc; + + if (!chip->cyc_ctr.en) + return; + + mutex_lock(&chip->cyc_ctr.lock); + rc = fg_get_sram_prop(fg, FG_SRAM_BATT_SOC, &batt_soc); + if (rc < 0) { + pr_err("Failed to read battery soc rc: %d\n", rc); + goto out; + } + + /* We need only the most significant byte here */ + batt_soc = (u32)batt_soc >> 24; + + /* Find out which bucket the SOC falls in */ + bucket = batt_soc / BUCKET_SOC_PCT; + + if (fg->charge_status == POWER_SUPPLY_STATUS_CHARGING) { + if (!chip->cyc_ctr.started[bucket]) { + chip->cyc_ctr.started[bucket] = true; + chip->cyc_ctr.last_soc[bucket] = batt_soc; + } + } else if (fg->charge_done || !is_input_present(fg)) { + for (i = 0; i < BUCKET_COUNT; i++) { + if (chip->cyc_ctr.started[i] && + batt_soc > chip->cyc_ctr.last_soc[i] + 2) { + rc = fg_inc_store_cycle_ctr(fg, i); + if (rc < 0) + pr_err("Error in storing cycle_ctr rc: %d\n", + rc); + chip->cyc_ctr.last_soc[i] = 0; + chip->cyc_ctr.started[i] = false; + } + } + } + + fg_dbg(fg, FG_STATUS, "batt_soc: %d bucket: %d chg_status: %d\n", + batt_soc, bucket, fg->charge_status); +out: + mutex_unlock(&chip->cyc_ctr.lock); +} + +static int fg_get_cycle_count(struct fg_dev *fg) +{ + struct fg_gen3_chip *chip = container_of(fg, struct fg_gen3_chip, fg); + int i, len = 0; + + if (!chip->cyc_ctr.en) + return 0; + + mutex_lock(&chip->cyc_ctr.lock); + for (i = 0; i < BUCKET_COUNT; i++) + len += chip->cyc_ctr.count[i]; + + mutex_unlock(&chip->cyc_ctr.lock); + + len = len / BUCKET_COUNT; + + return len; +} + +static const char *fg_get_cycle_counts(struct fg_dev *fg) +{ + struct fg_gen3_chip *chip = container_of(fg, struct fg_gen3_chip, fg); + int i, len = 0; + char *buf; + + if (!chip->cyc_ctr.en) + return NULL; + + buf = chip->cyc_ctr.counter; + mutex_lock(&chip->cyc_ctr.lock); + for (i = 0; i < BUCKET_COUNT; i++) { + if (sizeof(chip->cyc_ctr.counter) - len < 8) { + pr_err("Invalid length %d\n", len); + mutex_unlock(&chip->cyc_ctr.lock); + return NULL; + } + + len += scnprintf(buf+len, 8, "%d ", chip->cyc_ctr.count[i]); + } + mutex_unlock(&chip->cyc_ctr.lock); + + buf[len] = '\0'; + return buf; +} + +#define ESR_SW_FCC_UA 100000 /* 100mA */ +#define ESR_EXTRACTION_ENABLE_MASK BIT(0) +static void fg_esr_sw_work(struct work_struct *work) +{ + struct fg_dev *fg = container_of(work, + struct fg_dev, esr_sw_work); + union power_supply_propval pval = {0, }; + int rc, esr_uohms = 0; + + vote(fg->awake_votable, FG_ESR_VOTER, true, 0); + /* + * Enable ESR extraction just before we reduce the FCC + * to make sure that FG extracts the ESR. Disable ESR + * extraction after FCC reduction is complete to prevent + * any further HW pulses. + */ + rc = fg_sram_masked_write(fg, ESR_EXTRACTION_ENABLE_WORD, + ESR_EXTRACTION_ENABLE_OFFSET, + ESR_EXTRACTION_ENABLE_MASK, 0x1, FG_IMA_DEFAULT); + if (rc < 0) { + pr_err("Failed to enable ESR extraction rc=%d\n", rc); + goto done; + } + + /* delay for 1 FG cycle to complete */ + msleep(1500); + + /* for FCC to 100mA */ + pval.intval = ESR_SW_FCC_UA; + rc = power_supply_set_property(fg->batt_psy, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, + &pval); + if (rc < 0) { + pr_err("Failed to set FCC to 100mA rc=%d\n", rc); + goto done; + } + + /* delay for ESR readings */ + msleep(3000); + + /* FCC to 0 (removes vote) */ + pval.intval = 0; + rc = power_supply_set_property(fg->batt_psy, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, + &pval); + if (rc < 0) { + pr_err("Failed to remove FCC vote rc=%d\n", rc); + goto done; + } + + fg_get_sram_prop(fg, FG_SRAM_ESR, &esr_uohms); + fg_dbg(fg, FG_STATUS, "SW ESR done ESR=%d\n", esr_uohms); + + /* restart the alarm timer */ + alarm_start_relative(&fg->esr_sw_timer, + ms_to_ktime(fg->esr_wakeup_ms)); +done: + rc = fg_sram_masked_write(fg, ESR_EXTRACTION_ENABLE_WORD, + ESR_EXTRACTION_ENABLE_OFFSET, + ESR_EXTRACTION_ENABLE_MASK, 0x0, FG_IMA_DEFAULT); + if (rc < 0) + pr_err("Failed to disable ESR extraction rc=%d\n", rc); + + + vote(fg->awake_votable, FG_ESR_VOTER, false, 0); + fg_relax(fg, FG_SW_ESR_WAKE); +} + +static enum alarmtimer_restart + fg_esr_sw_timer(struct alarm *alarm, ktime_t now) +{ + struct fg_dev *fg = container_of(alarm, + struct fg_dev, esr_sw_timer); + + if (!fg->usb_present) + return ALARMTIMER_NORESTART; + + fg_stay_awake(fg, FG_SW_ESR_WAKE); + schedule_work(&fg->esr_sw_work); + + return ALARMTIMER_NORESTART; +} + +static int fg_config_esr_sw(struct fg_dev *fg) +{ + int rc; + union power_supply_propval prop = {0, }; + struct fg_gen3_chip *chip = container_of(fg, struct fg_gen3_chip, fg); + + if (!chip->dt.use_esr_sw) + return 0; + + if (!usb_psy_initialized(fg)) + return 0; + + rc = power_supply_get_property(fg->usb_psy, + POWER_SUPPLY_PROP_PRESENT, &prop); + if (rc < 0) { + pr_err("Error in reading usb-status rc = %d\n", rc); + return rc; + } + + if (fg->usb_present != prop.intval) { + fg->usb_present = prop.intval; + fg_dbg(fg, FG_STATUS, "USB status changed=%d\n", + fg->usb_present); + /* cancel any pending work */ + alarm_cancel(&fg->esr_sw_timer); + cancel_work_sync(&fg->esr_sw_work); + + if (fg->usb_present) { + /* disable ESR extraction across the charging cycle */ + rc = fg_sram_masked_write(fg, + ESR_EXTRACTION_ENABLE_WORD, + ESR_EXTRACTION_ENABLE_OFFSET, + ESR_EXTRACTION_ENABLE_MASK, + 0x0, FG_IMA_DEFAULT); + if (rc < 0) + return rc; + /* wake up early for the first ESR on insertion */ + alarm_start_relative(&fg->esr_sw_timer, + ms_to_ktime(fg->esr_wakeup_ms / 2)); + } else { + /* enable ESR extraction on removal */ + rc = fg_sram_masked_write(fg, + ESR_EXTRACTION_ENABLE_WORD, + ESR_EXTRACTION_ENABLE_OFFSET, + ESR_EXTRACTION_ENABLE_MASK, + 0x1, FG_IMA_DEFAULT); + if (rc < 0) + return rc; + } + } + + return 0; +} + +static void status_change_work(struct work_struct *work) +{ + struct fg_dev *fg = container_of(work, + struct fg_dev, status_change_work); + union power_supply_propval prop = {0, }; + int rc, batt_temp; + + if (!batt_psy_initialized(fg)) { + fg_dbg(fg, FG_STATUS, "Charger not available?!\n"); + goto out; + } + + if (!fg->soc_reporting_ready) { + fg_dbg(fg, FG_STATUS, "Profile load is not complete yet\n"); + goto out; + } + + rc = power_supply_get_property(fg->batt_psy, POWER_SUPPLY_PROP_STATUS, + &prop); + rc = fg_config_esr_sw(fg); + if (rc < 0) + pr_err("Failed to config SW ESR rc=%d\n", rc); + + if (rc < 0) { + pr_err("Error in getting charging status, rc=%d\n", rc); + goto out; + } + + fg->charge_status = prop.intval; + rc = power_supply_get_property(fg->batt_psy, + POWER_SUPPLY_PROP_CHARGE_TYPE, &prop); + if (rc < 0) { + pr_err("Error in getting charge type, rc=%d\n", rc); + goto out; + } + + fg->charge_type = prop.intval; + rc = power_supply_get_property(fg->batt_psy, + POWER_SUPPLY_PROP_CHARGE_DONE, &prop); + if (rc < 0) { + pr_err("Error in getting charge_done, rc=%d\n", rc); + goto out; + } + + fg->charge_done = prop.intval; + fg_cycle_counter_update(fg); + fg_cap_learning_update(fg); + + rc = fg_charge_full_update(fg); + if (rc < 0) + pr_err("Error in charge_full_update, rc=%d\n", rc); + + rc = fg_adjust_recharge_soc(fg); + if (rc < 0) + pr_err("Error in adjusting recharge_soc, rc=%d\n", rc); + + rc = fg_adjust_recharge_voltage(fg); + if (rc < 0) + pr_err("Error in adjusting recharge_voltage, rc=%d\n", rc); + + rc = fg_adjust_ki_coeff_dischg(fg); + if (rc < 0) + pr_err("Error in adjusting ki_coeff_dischg, rc=%d\n", rc); + + rc = fg_esr_fcc_config(fg); + if (rc < 0) + pr_err("Error in adjusting FCC for ESR, rc=%d\n", rc); + + rc = fg_get_battery_temp(fg, &batt_temp); + if (!rc) { + rc = fg_slope_limit_config(fg, batt_temp); + if (rc < 0) + pr_err("Error in configuring slope limiter rc:%d\n", + rc); + + rc = fg_adjust_ki_coeff_full_soc(fg, batt_temp); + if (rc < 0) + pr_err("Error in configuring ki_coeff_full_soc rc:%d\n", + rc); + } + + fg_ttf_update(fg); + fg->prev_charge_status = fg->charge_status; +out: + fg_dbg(fg, FG_STATUS, "charge_status:%d charge_type:%d charge_done:%d\n", + fg->charge_status, fg->charge_type, fg->charge_done); + fg_relax(fg, FG_STATUS_NOTIFY_WAKE); +} + +static int fg_bp_params_config(struct fg_dev *fg) +{ + int rc = 0; + u8 buf; + + /* This SRAM register is only present in v2.0 and above */ + if (!(fg->wa_flags & PMI8998_V1_REV_WA) && + fg->bp.float_volt_uv > 0) { + fg_encode(fg->sp, FG_SRAM_FLOAT_VOLT, + fg->bp.float_volt_uv / 1000, &buf); + rc = fg_sram_write(fg, fg->sp[FG_SRAM_FLOAT_VOLT].addr_word, + fg->sp[FG_SRAM_FLOAT_VOLT].addr_byte, &buf, + fg->sp[FG_SRAM_FLOAT_VOLT].len, FG_IMA_DEFAULT); + if (rc < 0) { + pr_err("Error in writing float_volt, rc=%d\n", rc); + return rc; + } + } + + if (fg->bp.vbatt_full_mv > 0) { + rc = fg_set_constant_chg_voltage(fg, + fg->bp.vbatt_full_mv * 1000); + if (rc < 0) + return rc; + } + + return rc; +} + +#define PROFILE_LOAD_BIT BIT(0) +#define BOOTLOADER_LOAD_BIT BIT(1) +#define BOOTLOADER_RESTART_BIT BIT(2) +#define HLOS_RESTART_BIT BIT(3) +static bool is_profile_load_required(struct fg_dev *fg) +{ + struct fg_gen3_chip *chip = container_of(fg, struct fg_gen3_chip, fg); + u8 buf[PROFILE_COMP_LEN], val; + bool profiles_same = false; + int rc; + + rc = fg_sram_read(fg, PROFILE_INTEGRITY_WORD, + PROFILE_INTEGRITY_OFFSET, &val, 1, FG_IMA_DEFAULT); + if (rc < 0) { + pr_err("failed to read profile integrity rc=%d\n", rc); + return false; + } + + /* Check if integrity bit is set */ + if (val & PROFILE_LOAD_BIT) { + fg_dbg(fg, FG_STATUS, "Battery profile integrity bit is set\n"); + + /* Whitelist the values */ + val &= ~PROFILE_LOAD_BIT; + if (val != HLOS_RESTART_BIT && val != BOOTLOADER_LOAD_BIT && + val != (BOOTLOADER_LOAD_BIT | BOOTLOADER_RESTART_BIT)) { + val |= PROFILE_LOAD_BIT; + pr_warn("Garbage value in profile integrity word: 0x%x\n", + val); + return true; + } + + rc = fg_sram_read(fg, PROFILE_LOAD_WORD, PROFILE_LOAD_OFFSET, + buf, PROFILE_COMP_LEN, FG_IMA_DEFAULT); + if (rc < 0) { + pr_err("Error in reading battery profile, rc:%d\n", rc); + fg->profile_load_status = PROFILE_SKIPPED; + return false; + } + profiles_same = memcmp(chip->batt_profile, buf, + PROFILE_COMP_LEN) == 0; + if (profiles_same) { + fg_dbg(fg, FG_STATUS, "Battery profile is same, not loading it\n"); + fg->profile_load_status = PROFILE_LOADED; + return false; + } + + if (!chip->dt.force_load_profile) { + pr_warn("Profiles doesn't match, skipping loading it since force_load_profile is disabled\n"); + if (fg_profile_dump) { + pr_info("FG: loaded profile:\n"); + dump_sram(fg, buf, PROFILE_LOAD_WORD, + PROFILE_COMP_LEN); + pr_info("FG: available profile:\n"); + dump_sram(fg, chip->batt_profile, + PROFILE_LOAD_WORD, PROFILE_LEN); + } + fg->profile_load_status = PROFILE_SKIPPED; + return false; + } + + fg_dbg(fg, FG_STATUS, "Profiles are different, loading the correct one\n"); + } else { + fg_dbg(fg, FG_STATUS, "Profile integrity bit is not set\n"); + if (fg_profile_dump) { + pr_info("FG: profile to be loaded:\n"); + dump_sram(fg, chip->batt_profile, PROFILE_LOAD_WORD, + PROFILE_LEN); + } + } + return true; +} + +static void fg_update_batt_profile(struct fg_dev *fg) +{ + struct fg_gen3_chip *chip = container_of(fg, struct fg_gen3_chip, fg); + int rc, offset; + u8 val; + + rc = fg_sram_read(fg, PROFILE_INTEGRITY_WORD, + SW_CONFIG_OFFSET, &val, 1, FG_IMA_DEFAULT); + if (rc < 0) { + pr_err("Error in reading SW_CONFIG_OFFSET, rc=%d\n", rc); + return; + } + + /* + * If the RCONN had not been updated, no need to update battery + * profile. Else, update the battery profile so that the profile + * modified by bootloader or HLOS matches with the profile read + * from device tree. + */ + + if (!(val & RCONN_CONFIG_BIT)) + return; + + rc = fg_sram_read(fg, ESR_RSLOW_CHG_WORD, + ESR_RSLOW_CHG_OFFSET, &val, 1, FG_IMA_DEFAULT); + if (rc < 0) { + pr_err("Error in reading ESR_RSLOW_CHG_OFFSET, rc=%d\n", rc); + return; + } + offset = (ESR_RSLOW_CHG_WORD - PROFILE_LOAD_WORD) * 4 + + ESR_RSLOW_CHG_OFFSET; + chip->batt_profile[offset] = val; + + rc = fg_sram_read(fg, ESR_RSLOW_DISCHG_WORD, + ESR_RSLOW_DISCHG_OFFSET, &val, 1, FG_IMA_DEFAULT); + if (rc < 0) { + pr_err("Error in reading ESR_RSLOW_DISCHG_OFFSET, rc=%d\n", rc); + return; + } + offset = (ESR_RSLOW_DISCHG_WORD - PROFILE_LOAD_WORD) * 4 + + ESR_RSLOW_DISCHG_OFFSET; + chip->batt_profile[offset] = val; +} + +static void clear_battery_profile(struct fg_dev *fg) +{ + u8 val = 0; + int rc; + + rc = fg_sram_write(fg, PROFILE_INTEGRITY_WORD, + PROFILE_INTEGRITY_OFFSET, &val, 1, FG_IMA_DEFAULT); + if (rc < 0) + pr_err("failed to write profile integrity rc=%d\n", rc); +} + +static void pl_enable_work(struct work_struct *work) +{ + struct fg_gen3_chip *chip = container_of(work, + struct fg_gen3_chip, + pl_enable_work.work); + struct fg_dev *fg = &chip->fg; + + vote(chip->pl_disable_votable, ESR_FCC_VOTER, false, 0); + vote(fg->awake_votable, ESR_FCC_VOTER, false, 0); +} + +#define SOC_READY_WAIT_TIME_MS 2000 +static void profile_load_work(struct work_struct *work) +{ + struct fg_dev *fg = container_of(work, + struct fg_dev, + profile_load_work.work); + struct fg_gen3_chip *chip = container_of(fg, struct fg_gen3_chip, fg); + u8 buf[2], val; + int rc; + + vote(fg->awake_votable, PROFILE_LOAD, true, 0); + + rc = fg_get_batt_id(fg); + if (rc < 0) { + pr_err("Error in getting battery id, rc:%d\n", rc); + goto out; + } + + rc = fg_get_batt_profile(fg); + if (rc < 0) { + fg->profile_load_status = PROFILE_MISSING; + pr_warn("profile for batt_id=%dKOhms not found..using OTP, rc:%d\n", + fg->batt_id_ohms / 1000, rc); + goto out; + } + + if (!fg->profile_available) + goto out; + + fg_update_batt_profile(fg); + + if (!is_profile_load_required(fg)) + goto done; + + clear_cycle_counter(fg); + mutex_lock(&chip->cl.lock); + chip->cl.learned_cc_uah = 0; + chip->cl.active = false; + mutex_unlock(&chip->cl.lock); + + fg_dbg(fg, FG_STATUS, "profile loading started\n"); + rc = fg_masked_write(fg, BATT_SOC_RESTART(fg), RESTART_GO_BIT, 0); + if (rc < 0) { + pr_err("Error in writing to %04x, rc=%d\n", + BATT_SOC_RESTART(fg), rc); + goto out; + } + + /* load battery profile */ + rc = fg_sram_write(fg, PROFILE_LOAD_WORD, PROFILE_LOAD_OFFSET, + chip->batt_profile, PROFILE_LEN, FG_IMA_ATOMIC); + if (rc < 0) { + pr_err("Error in writing battery profile, rc:%d\n", rc); + goto out; + } + + /* Set the profile integrity bit */ + val = HLOS_RESTART_BIT | PROFILE_LOAD_BIT; + rc = fg_sram_write(fg, PROFILE_INTEGRITY_WORD, + PROFILE_INTEGRITY_OFFSET, &val, 1, FG_IMA_DEFAULT); + if (rc < 0) { + pr_err("failed to write profile integrity rc=%d\n", rc); + goto out; + } + + rc = fg_restart(fg, SOC_READY_WAIT_TIME_MS); + if (rc < 0) { + pr_err("Error in restarting FG, rc=%d\n", rc); + goto out; + } + + fg_dbg(fg, FG_STATUS, "SOC is ready\n"); + fg->profile_load_status = PROFILE_LOADED; +done: + rc = fg_bp_params_config(fg); + if (rc < 0) + pr_err("Error in configuring battery profile params, rc:%d\n", + rc); + + rc = fg_sram_read(fg, NOM_CAP_WORD, NOM_CAP_OFFSET, buf, 2, + FG_IMA_DEFAULT); + if (rc < 0) { + pr_err("Error in reading %04x[%d] rc=%d\n", NOM_CAP_WORD, + NOM_CAP_OFFSET, rc); + } else { + chip->cl.nom_cap_uah = (int)(buf[0] | buf[1] << 8) * 1000; + rc = fg_load_learned_cap_from_sram(fg); + if (rc < 0) + pr_err("Error in loading capacity learning data, rc:%d\n", + rc); + } + + rc = fg_rconn_config(fg); + if (rc < 0) + pr_err("Error in configuring Rconn, rc=%d\n", rc); + + batt_psy_initialized(fg); + fg_notify_charger(fg); + + fg_dbg(fg, FG_STATUS, "profile loaded successfully"); +out: + fg->soc_reporting_ready = true; + vote(fg->awake_votable, ESR_FCC_VOTER, true, 0); + schedule_delayed_work(&chip->pl_enable_work, msecs_to_jiffies(5000)); + vote(fg->awake_votable, PROFILE_LOAD, false, 0); + if (!work_pending(&fg->status_change_work)) { + fg_stay_awake(fg, FG_STATUS_NOTIFY_WAKE); + schedule_work(&fg->status_change_work); + } +} + +static void sram_dump_work(struct work_struct *work) +{ + struct fg_dev *fg = container_of(work, struct fg_dev, + sram_dump_work.work); + u8 buf[FG_SRAM_LEN]; + int rc; + s64 timestamp_ms, quotient; + s32 remainder; + + rc = fg_sram_read(fg, 0, 0, buf, FG_SRAM_LEN, FG_IMA_DEFAULT); + if (rc < 0) { + pr_err("Error in reading FG SRAM, rc:%d\n", rc); + goto resched; + } + + timestamp_ms = ktime_to_ms(ktime_get_boottime()); + quotient = div_s64_rem(timestamp_ms, 1000, &remainder); + fg_dbg(fg, FG_STATUS, "SRAM Dump Started at %lld.%d\n", + quotient, remainder); + dump_sram(fg, buf, 0, FG_SRAM_LEN); + timestamp_ms = ktime_to_ms(ktime_get_boottime()); + quotient = div_s64_rem(timestamp_ms, 1000, &remainder); + fg_dbg(fg, FG_STATUS, "SRAM Dump done at %lld.%d\n", + quotient, remainder); +resched: + schedule_delayed_work(&fg->sram_dump_work, + msecs_to_jiffies(fg_sram_dump_period_ms)); +} + +static ssize_t sram_dump_en_store(struct device *dev, struct device_attribute + *attr, const char *buf, size_t count) +{ + int rc; + struct power_supply *bms_psy; + struct fg_gen3_chip *chip; + struct fg_dev *fg; + bool old_val = fg_sram_dump; + bool store_val; + + if (kstrtobool(buf, &store_val)) { + pr_err("Unable to set fg_sram_dump\n"); + return -EINVAL; + } + fg_sram_dump = store_val; + + if (fg_sram_dump == old_val) + goto exit; + + bms_psy = power_supply_get_by_name("bms"); + if (!bms_psy) { + pr_err("bms psy not found\n"); + return -ENODEV; + } + + chip = power_supply_get_drvdata(bms_psy); + fg = &chip->fg; + if (fg_sram_dump) + schedule_delayed_work(&fg->sram_dump_work, + msecs_to_jiffies(fg_sram_dump_period_ms)); + else + cancel_delayed_work_sync(&fg->sram_dump_work); + +exit: + rc = count; + return rc; +} + +static ssize_t sram_dump_en_show(struct device *dev, struct device_attribute + *attr, char *buf) +{ + return scnprintf(buf, PAGE_SIZE, "%c\n", fg_sram_dump ? 'Y' : 'N'); +} +static DEVICE_ATTR_RW(sram_dump_en); + +static ssize_t restart_store(struct device *dev, struct device_attribute + *attr, const char *buf, size_t count) +{ + int rc; + struct power_supply *bms_psy; + struct fg_gen3_chip *chip; + struct fg_dev *fg; + int val; + + if (kstrtos32(buf, 10, &val)) { + pr_err("Unable to set fg_restart_mp\n"); + return -EINVAL; + } + fg_restart_mp = val; + + if (fg_restart_mp != 1) { + pr_err("Bad value %d\n", fg_restart_mp); + return -EINVAL; + } + + bms_psy = power_supply_get_by_name("bms"); + if (!bms_psy) { + pr_err("bms psy not found\n"); + goto exit; + } + + chip = power_supply_get_drvdata(bms_psy); + if (!chip) + return -ENODEV; + + fg = &chip->fg; + rc = fg_restart(fg, SOC_READY_WAIT_TIME_MS); + if (rc < 0) { + pr_err("Error in restarting FG, rc=%d\n", rc); + return rc; + } + + pr_info("FG restart done\n"); +exit: + rc = count; + return rc; +} + +static ssize_t restart_show(struct device *dev, struct device_attribute + *attr, char *buf) +{ + return scnprintf(buf, PAGE_SIZE, "%d\n", fg_restart_mp); +} +static DEVICE_ATTR_RW(restart); + +static struct attribute *fg_attrs[] = { + &dev_attr_profile_dump.attr, + &dev_attr_sram_dump_period_ms.attr, + &dev_attr_sram_dump_en.attr, + &dev_attr_restart.attr, + NULL, +}; +ATTRIBUTE_GROUPS(fg); + +#define HOURS_TO_SECONDS 3600 +#define OCV_SLOPE_UV 10869 +#define MILLI_UNIT 1000 +#define MICRO_UNIT 1000000 +#define NANO_UNIT 1000000000 +static int fg_get_time_to_full_locked(struct fg_dev *fg, int *val) +{ + struct fg_gen3_chip *chip = container_of(fg, struct fg_gen3_chip, fg); + int rc, ibatt_avg, vbatt_avg, rbatt, msoc, full_soc, act_cap_mah, + i_cc2cv = 0, soc_cc2cv, tau, divisor, iterm, ttf_mode, + i, soc_per_step, msoc_this_step, msoc_next_step, + ibatt_this_step, t_predicted_this_step, ttf_slope, + t_predicted_cv, t_predicted = 0; + s64 delta_ms; + + if (!fg->soc_reporting_ready) + return -ENODATA; + + if (fg->bp.float_volt_uv <= 0) { + pr_err("battery profile is not loaded\n"); + return -ENODATA; + } + + if (!batt_psy_initialized(fg)) { + fg_dbg(fg, FG_TTF, "charger is not available\n"); + return -ENODATA; + } + + rc = fg_get_prop_capacity(fg, &msoc); + if (rc < 0) { + pr_err("failed to get msoc rc=%d\n", rc); + return rc; + } + fg_dbg(fg, FG_TTF, "msoc=%d\n", msoc); + + /* the battery is considered full if the SOC is 100% */ + if (msoc >= 100) { + *val = 0; + return 0; + } + + if (is_qnovo_en(fg)) + ttf_mode = FG_TTF_MODE_QNOVO; + else + ttf_mode = FG_TTF_MODE_NORMAL; + + /* when switching TTF algorithms the TTF needs to be reset */ + if (chip->ttf.mode != ttf_mode) { + fg_circ_buf_clr(&chip->ttf.ibatt); + fg_circ_buf_clr(&chip->ttf.vbatt); + chip->ttf.last_ttf = 0; + chip->ttf.last_ms = 0; + chip->ttf.mode = ttf_mode; + } + + /* at least 10 samples are required to produce a stable IBATT */ + if (chip->ttf.ibatt.size < 10) { + *val = -1; + return 0; + } + + rc = fg_circ_buf_median(&chip->ttf.ibatt, &ibatt_avg); + if (rc < 0) { + pr_err("failed to get IBATT AVG rc=%d\n", rc); + return rc; + } + + rc = fg_circ_buf_median(&chip->ttf.vbatt, &vbatt_avg); + if (rc < 0) { + pr_err("failed to get VBATT AVG rc=%d\n", rc); + return rc; + } + + ibatt_avg = -ibatt_avg / MILLI_UNIT; + vbatt_avg /= MILLI_UNIT; + + /* clamp ibatt_avg to iterm */ + if (ibatt_avg < abs(chip->dt.sys_term_curr_ma)) + ibatt_avg = abs(chip->dt.sys_term_curr_ma); + + fg_dbg(fg, FG_TTF, "ibatt_avg=%d\n", ibatt_avg); + fg_dbg(fg, FG_TTF, "vbatt_avg=%d\n", vbatt_avg); + + rc = fg_get_battery_resistance(fg, &rbatt); + if (rc < 0) { + pr_err("failed to get battery resistance rc=%d\n", rc); + return rc; + } + + rbatt /= MILLI_UNIT; + fg_dbg(fg, FG_TTF, "rbatt=%d\n", rbatt); + + rc = fg_get_sram_prop(fg, FG_SRAM_ACT_BATT_CAP, &act_cap_mah); + if (rc < 0) { + pr_err("failed to get ACT_BATT_CAP rc=%d\n", rc); + return rc; + } + + rc = fg_get_sram_prop(fg, FG_SRAM_FULL_SOC, &full_soc); + if (rc < 0) { + pr_err("failed to get full soc rc=%d\n", rc); + return rc; + } + full_soc = DIV_ROUND_CLOSEST(((u16)full_soc >> 8) * FULL_CAPACITY, + FULL_SOC_RAW); + act_cap_mah = full_soc * act_cap_mah / 100; + fg_dbg(fg, FG_TTF, "act_cap_mah=%d\n", act_cap_mah); + + /* estimated battery current at the CC to CV transition */ + switch (chip->ttf.mode) { + case FG_TTF_MODE_NORMAL: + i_cc2cv = ibatt_avg * vbatt_avg / + max(MILLI_UNIT, fg->bp.float_volt_uv / MILLI_UNIT); + break; + case FG_TTF_MODE_QNOVO: + i_cc2cv = min( + chip->ttf.cc_step.arr[MAX_CC_STEPS - 1] / MILLI_UNIT, + ibatt_avg * vbatt_avg / + max(MILLI_UNIT, fg->bp.float_volt_uv / MILLI_UNIT)); + break; + default: + pr_err("TTF mode %d is not supported\n", chip->ttf.mode); + break; + } + fg_dbg(fg, FG_TTF, "i_cc2cv=%d\n", i_cc2cv); + + /* if we are already in CV state then we can skip estimating CC */ + if (fg->charge_type == POWER_SUPPLY_CHARGE_TYPE_TAPER) + goto cv_estimate; + + /* estimated SOC at the CC to CV transition */ + soc_cc2cv = DIV_ROUND_CLOSEST(rbatt * i_cc2cv, OCV_SLOPE_UV); + soc_cc2cv = 100 - soc_cc2cv; + fg_dbg(fg, FG_TTF, "soc_cc2cv=%d\n", soc_cc2cv); + + switch (chip->ttf.mode) { + case FG_TTF_MODE_NORMAL: + if (soc_cc2cv - msoc <= 0) + goto cv_estimate; + + divisor = max(100, (ibatt_avg + i_cc2cv) / 2 * 100); + t_predicted = div_s64((s64)act_cap_mah * (soc_cc2cv - msoc) * + HOURS_TO_SECONDS, divisor); + break; + case FG_TTF_MODE_QNOVO: + soc_per_step = 100 / MAX_CC_STEPS; + for (i = msoc / soc_per_step; i < MAX_CC_STEPS - 1; ++i) { + msoc_next_step = (i + 1) * soc_per_step; + if (i == msoc / soc_per_step) + msoc_this_step = msoc; + else + msoc_this_step = i * soc_per_step; + + /* scale ibatt by 85% to account for discharge pulses */ + ibatt_this_step = min( + chip->ttf.cc_step.arr[i] / MILLI_UNIT, + ibatt_avg) * 85 / 100; + divisor = max(100, ibatt_this_step * 100); + t_predicted_this_step = div_s64((s64)act_cap_mah * + (msoc_next_step - msoc_this_step) * + HOURS_TO_SECONDS, divisor); + t_predicted += t_predicted_this_step; + fg_dbg(fg, FG_TTF, "[%d, %d] ma=%d t=%d\n", + msoc_this_step, msoc_next_step, + ibatt_this_step, t_predicted_this_step); + } + break; + default: + pr_err("TTF mode %d is not supported\n", chip->ttf.mode); + break; + } + +cv_estimate: + fg_dbg(fg, FG_TTF, "t_predicted_cc=%d\n", t_predicted); + + iterm = max(100, abs(chip->dt.sys_term_curr_ma) + 200); + fg_dbg(fg, FG_TTF, "iterm=%d\n", iterm); + + if (fg->charge_type == POWER_SUPPLY_CHARGE_TYPE_TAPER) + tau = max(MILLI_UNIT, ibatt_avg * MILLI_UNIT / iterm); + else + tau = max(MILLI_UNIT, i_cc2cv * MILLI_UNIT / iterm); + + rc = fg_lerp(fg_ln_table, ARRAY_SIZE(fg_ln_table), tau, &tau); + if (rc < 0) { + pr_err("failed to interpolate tau rc=%d\n", rc); + return rc; + } + + /* tau is scaled linearly from 95% to 100% SOC */ + if (msoc >= 95) + tau = tau * 2 * (100 - msoc) / 10; + + fg_dbg(fg, FG_TTF, "tau=%d\n", tau); + t_predicted_cv = div_s64((s64)act_cap_mah * rbatt * tau * + HOURS_TO_SECONDS, NANO_UNIT); + fg_dbg(fg, FG_TTF, "t_predicted_cv=%d\n", t_predicted_cv); + t_predicted += t_predicted_cv; + + fg_dbg(fg, FG_TTF, "t_predicted_prefilter=%d\n", t_predicted); + if (chip->ttf.last_ms != 0) { + delta_ms = ktime_ms_delta(ktime_get_boottime(), + ms_to_ktime(chip->ttf.last_ms)); + if (delta_ms > 10000) { + ttf_slope = div64_s64( + (s64)(t_predicted - chip->ttf.last_ttf) * + MICRO_UNIT, delta_ms); + if (ttf_slope > -100) + ttf_slope = -100; + else if (ttf_slope < -2000) + ttf_slope = -2000; + + t_predicted = div_s64( + (s64)ttf_slope * delta_ms, MICRO_UNIT) + + chip->ttf.last_ttf; + fg_dbg(fg, FG_TTF, "ttf_slope=%d\n", ttf_slope); + } else { + t_predicted = chip->ttf.last_ttf; + } + } + + /* clamp the ttf to 0 */ + if (t_predicted < 0) + t_predicted = 0; + + fg_dbg(fg, FG_TTF, "t_predicted_postfilter=%d\n", t_predicted); + *val = t_predicted; + return 0; +} + +static int fg_get_time_to_full(struct fg_dev *fg, int *val) +{ + struct fg_gen3_chip *chip = container_of(fg, struct fg_gen3_chip, fg); + int rc; + + mutex_lock(&chip->ttf.lock); + rc = fg_get_time_to_full_locked(fg, val); + mutex_unlock(&chip->ttf.lock); + return rc; +} + +#define CENTI_ICORRECT_C0 105 +#define CENTI_ICORRECT_C1 20 +static int fg_get_time_to_empty(struct fg_dev *fg, int *val) +{ + struct fg_gen3_chip *chip = container_of(fg, struct fg_gen3_chip, fg); + int rc, ibatt_avg, msoc, full_soc, act_cap_mah, divisor; + + mutex_lock(&chip->ttf.lock); + rc = fg_circ_buf_median(&chip->ttf.ibatt, &ibatt_avg); + if (rc < 0) { + /* try to get instantaneous current */ + rc = fg_get_battery_current(fg, &ibatt_avg); + if (rc < 0) { + pr_err("failed to get battery current, rc=%d\n", rc); + mutex_unlock(&chip->ttf.lock); + return rc; + } + } + mutex_unlock(&chip->ttf.lock); + + ibatt_avg /= MILLI_UNIT; + /* clamp ibatt_avg to 100mA */ + if (ibatt_avg < 100) + ibatt_avg = 100; + + rc = fg_get_prop_capacity(fg, &msoc); + if (rc < 0) { + pr_err("Error in getting capacity, rc=%d\n", rc); + return rc; + } + + rc = fg_get_sram_prop(fg, FG_SRAM_ACT_BATT_CAP, &act_cap_mah); + if (rc < 0) { + pr_err("Error in getting ACT_BATT_CAP, rc=%d\n", rc); + return rc; + } + + rc = fg_get_sram_prop(fg, FG_SRAM_FULL_SOC, &full_soc); + if (rc < 0) { + pr_err("failed to get full soc rc=%d\n", rc); + return rc; + } + full_soc = DIV_ROUND_CLOSEST(((u16)full_soc >> 8) * FULL_CAPACITY, + FULL_SOC_RAW); + act_cap_mah = full_soc * act_cap_mah / 100; + + divisor = CENTI_ICORRECT_C0 * 100 + CENTI_ICORRECT_C1 * msoc; + divisor = ibatt_avg * divisor / 100; + divisor = max(100, divisor); + *val = act_cap_mah * msoc * HOURS_TO_SECONDS / divisor; + return 0; +} + +static int fg_update_maint_soc(struct fg_dev *fg) +{ + struct fg_gen3_chip *chip = container_of(fg, struct fg_gen3_chip, fg); + int rc = 0, msoc; + + if (!chip->dt.linearize_soc) + return 0; + + mutex_lock(&fg->charge_full_lock); + if (fg->delta_soc <= 0) + goto out; + + rc = fg_get_msoc(fg, &msoc); + if (rc < 0) { + pr_err("Error in getting msoc, rc=%d\n", rc); + goto out; + } + + if (msoc > fg->maint_soc) { + /* + * When the monotonic SOC goes above maintenance SOC, we should + * stop showing the maintenance SOC. + */ + fg->delta_soc = 0; + fg->maint_soc = 0; + } else if (msoc <= fg->last_msoc) { + /* MSOC is decreasing. Decrease maintenance SOC as well */ + fg->maint_soc -= 1; + if (!(msoc % 10)) { + /* + * Reduce the maintenance SOC additionally by 1 whenever + * it crosses a SOC multiple of 10. + */ + fg->maint_soc -= 1; + fg->delta_soc -= 1; + } + } + + fg_dbg(fg, FG_IRQ, "msoc: %d last_msoc: %d maint_soc: %d delta_soc: %d\n", + msoc, fg->last_msoc, fg->maint_soc, fg->delta_soc); + fg->last_msoc = msoc; +out: + mutex_unlock(&fg->charge_full_lock); + return rc; +} + +static int fg_esr_validate(struct fg_dev *fg) +{ + struct fg_gen3_chip *chip = container_of(fg, struct fg_gen3_chip, fg); + int rc, esr_uohms; + u8 buf[2]; + + if (chip->dt.esr_clamp_mohms <= 0) + return 0; + + rc = fg_get_sram_prop(fg, FG_SRAM_ESR, &esr_uohms); + if (rc < 0) { + pr_err("failed to get ESR, rc=%d\n", rc); + return rc; + } + + if (esr_uohms >= chip->dt.esr_clamp_mohms * 1000) { + pr_debug("ESR %d is > ESR_clamp\n", esr_uohms); + return 0; + } + + esr_uohms = chip->dt.esr_clamp_mohms * 1000; + fg_encode(fg->sp, FG_SRAM_ESR, esr_uohms, buf); + rc = fg_sram_write(fg, fg->sp[FG_SRAM_ESR].addr_word, + fg->sp[FG_SRAM_ESR].addr_byte, buf, + fg->sp[FG_SRAM_ESR].len, FG_IMA_DEFAULT); + if (rc < 0) { + pr_err("Error in writing ESR, rc=%d\n", rc); + return rc; + } + + fg_dbg(fg, FG_STATUS, "ESR clamped to %duOhms\n", esr_uohms); + return 0; +} + +static int fg_force_esr_meas(struct fg_dev *fg) +{ + int rc; + int esr_uohms; + + mutex_lock(&fg->qnovo_esr_ctrl_lock); + /* force esr extraction enable */ + rc = fg_sram_masked_write(fg, ESR_EXTRACTION_ENABLE_WORD, + ESR_EXTRACTION_ENABLE_OFFSET, BIT(0), BIT(0), + FG_IMA_DEFAULT); + if (rc < 0) { + pr_err("failed to enable esr extn rc=%d\n", rc); + goto out; + } + + rc = fg_masked_write(fg, BATT_INFO_QNOVO_CFG(fg), + LD_REG_CTRL_BIT, 0); + if (rc < 0) { + pr_err("Error in configuring qnovo_cfg rc=%d\n", rc); + goto out; + } + + rc = fg_masked_write(fg, BATT_INFO_TM_MISC1(fg), + ESR_REQ_CTL_BIT | ESR_REQ_CTL_EN_BIT, + ESR_REQ_CTL_BIT | ESR_REQ_CTL_EN_BIT); + if (rc < 0) { + pr_err("Error in configuring force ESR rc=%d\n", rc); + goto out; + } + + /* + * Release and grab the lock again after 1.5 seconds so that prepare + * callback can succeed if the request comes in between. + */ + mutex_unlock(&fg->qnovo_esr_ctrl_lock); + + /* wait 1.5 seconds for hw to measure ESR */ + msleep(1500); + + mutex_lock(&fg->qnovo_esr_ctrl_lock); + rc = fg_masked_write(fg, BATT_INFO_TM_MISC1(fg), + ESR_REQ_CTL_BIT | ESR_REQ_CTL_EN_BIT, + 0); + if (rc < 0) { + pr_err("Error in restoring force ESR rc=%d\n", rc); + goto out; + } + + /* If qnovo is disabled, then leave ESR extraction enabled */ + if (!fg->qnovo_enable) + goto done; + + rc = fg_masked_write(fg, BATT_INFO_QNOVO_CFG(fg), + LD_REG_CTRL_BIT, LD_REG_CTRL_BIT); + if (rc < 0) { + pr_err("Error in restoring qnovo_cfg rc=%d\n", rc); + goto out; + } + + /* force esr extraction disable */ + rc = fg_sram_masked_write(fg, ESR_EXTRACTION_ENABLE_WORD, + ESR_EXTRACTION_ENABLE_OFFSET, BIT(0), 0, + FG_IMA_DEFAULT); + if (rc < 0) { + pr_err("failed to disable esr extn rc=%d\n", rc); + goto out; + } + +done: + fg_get_battery_resistance(fg, &esr_uohms); + fg_dbg(fg, FG_STATUS, "ESR uohms = %d\n", esr_uohms); +out: + mutex_unlock(&fg->qnovo_esr_ctrl_lock); + return rc; +} + +static int fg_prepare_for_qnovo(struct fg_dev *fg, int qnovo_enable) +{ + int rc = 0; + + mutex_lock(&fg->qnovo_esr_ctrl_lock); + /* force esr extraction disable when qnovo enables */ + rc = fg_sram_masked_write(fg, ESR_EXTRACTION_ENABLE_WORD, + ESR_EXTRACTION_ENABLE_OFFSET, + BIT(0), qnovo_enable ? 0 : BIT(0), + FG_IMA_DEFAULT); + if (rc < 0) { + pr_err("Error in configuring esr extraction rc=%d\n", rc); + goto out; + } + + rc = fg_masked_write(fg, BATT_INFO_QNOVO_CFG(fg), + LD_REG_CTRL_BIT, + qnovo_enable ? LD_REG_CTRL_BIT : 0); + if (rc < 0) { + pr_err("Error in configuring qnovo_cfg rc=%d\n", rc); + goto out; + } + + fg_dbg(fg, FG_STATUS, "%s for Qnovo\n", + qnovo_enable ? "Prepared" : "Unprepared"); + fg->qnovo_enable = qnovo_enable; +out: + mutex_unlock(&fg->qnovo_esr_ctrl_lock); + return rc; +} + +static void ttf_work(struct work_struct *work) +{ + struct fg_gen3_chip *chip = container_of(work, + struct fg_gen3_chip, ttf_work.work); + struct fg_dev *fg = &chip->fg; + int rc, ibatt_now, vbatt_now, ttf; + ktime_t ktime_now; + + mutex_lock(&chip->ttf.lock); + if (fg->charge_status != POWER_SUPPLY_STATUS_CHARGING && + fg->charge_status != POWER_SUPPLY_STATUS_DISCHARGING) + goto end_work; + + rc = fg_get_battery_current(fg, &ibatt_now); + if (rc < 0) { + pr_err("failed to get battery current, rc=%d\n", rc); + goto end_work; + } + + rc = fg_get_battery_voltage(fg, &vbatt_now); + if (rc < 0) { + pr_err("failed to get battery voltage, rc=%d\n", rc); + goto end_work; + } + + fg_circ_buf_add(&chip->ttf.ibatt, ibatt_now); + fg_circ_buf_add(&chip->ttf.vbatt, vbatt_now); + + if (fg->charge_status == POWER_SUPPLY_STATUS_CHARGING) { + rc = fg_get_time_to_full_locked(fg, &ttf); + if (rc < 0) { + pr_err("failed to get ttf, rc=%d\n", rc); + goto end_work; + } + + /* keep the wake lock and prime the IBATT and VBATT buffers */ + if (ttf < 0) { + /* delay for one FG cycle */ + schedule_delayed_work(&chip->ttf_work, + msecs_to_jiffies(1500)); + mutex_unlock(&chip->ttf.lock); + return; + } + + /* update the TTF reference point every minute */ + ktime_now = ktime_get_boottime(); + if (ktime_ms_delta(ktime_now, + ms_to_ktime(chip->ttf.last_ms)) > 60000 || + chip->ttf.last_ms == 0) { + chip->ttf.last_ttf = ttf; + chip->ttf.last_ms = ktime_to_ms(ktime_now); + } + } + + /* recurse every 10 seconds */ + schedule_delayed_work(&chip->ttf_work, msecs_to_jiffies(10000)); +end_work: + vote(fg->awake_votable, TTF_PRIMING, false, 0); + mutex_unlock(&chip->ttf.lock); +} + +/* PSY CALLBACKS STAY HERE */ + +static int fg_psy_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *pval) +{ + struct fg_gen3_chip *chip = power_supply_get_drvdata(psy); + struct fg_dev *fg = &chip->fg; + int rc = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_CAPACITY: + rc = fg_get_prop_capacity(fg, &pval->intval); + break; + case POWER_SUPPLY_PROP_REAL_CAPACITY: + rc = fg_get_prop_real_capacity(fg, &pval->intval); + break; + case POWER_SUPPLY_PROP_CAPACITY_RAW: + rc = fg_get_msoc_raw(fg, &pval->intval); + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + if (fg->battery_missing) + pval->intval = 3700000; + else + rc = fg_get_battery_voltage(fg, &pval->intval); + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + rc = fg_get_battery_current(fg, &pval->intval); + break; + case POWER_SUPPLY_PROP_TEMP: + rc = fg_get_battery_temp(fg, &pval->intval); + break; + case POWER_SUPPLY_PROP_COLD_TEMP: + rc = fg_get_jeita_threshold(fg, JEITA_COLD, &pval->intval); + if (rc < 0) { + pr_err("Error in reading jeita_cold, rc=%d\n", rc); + return rc; + } + break; + case POWER_SUPPLY_PROP_COOL_TEMP: + rc = fg_get_jeita_threshold(fg, JEITA_COOL, &pval->intval); + if (rc < 0) { + pr_err("Error in reading jeita_cool, rc=%d\n", rc); + return rc; + } + break; + case POWER_SUPPLY_PROP_WARM_TEMP: + rc = fg_get_jeita_threshold(fg, JEITA_WARM, &pval->intval); + if (rc < 0) { + pr_err("Error in reading jeita_warm, rc=%d\n", rc); + return rc; + } + break; + case POWER_SUPPLY_PROP_HOT_TEMP: + rc = fg_get_jeita_threshold(fg, JEITA_HOT, &pval->intval); + if (rc < 0) { + pr_err("Error in reading jeita_hot, rc=%d\n", rc); + return rc; + } + break; + case POWER_SUPPLY_PROP_RESISTANCE: + rc = fg_get_battery_resistance(fg, &pval->intval); + break; + case POWER_SUPPLY_PROP_VOLTAGE_OCV: + rc = fg_get_sram_prop(fg, FG_SRAM_OCV, &pval->intval); + break; + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: + pval->intval = chip->cl.nom_cap_uah; + break; + case POWER_SUPPLY_PROP_RESISTANCE_ID: + pval->intval = fg->batt_id_ohms; + break; + case POWER_SUPPLY_PROP_BATTERY_TYPE: + pval->strval = fg_get_battery_type(fg); + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: + pval->intval = fg->bp.float_volt_uv; + break; + case POWER_SUPPLY_PROP_CYCLE_COUNT: + pval->intval = fg_get_cycle_count(fg); + break; + case POWER_SUPPLY_PROP_CYCLE_COUNTS: + pval->strval = fg_get_cycle_counts(fg); + break; + case POWER_SUPPLY_PROP_CHARGE_NOW_RAW: + rc = fg_get_charge_raw(fg, &pval->intval); + break; + case POWER_SUPPLY_PROP_CHARGE_NOW: + pval->intval = chip->cl.init_cc_uah; + break; + case POWER_SUPPLY_PROP_CHARGE_FULL: + pval->intval = chip->cl.learned_cc_uah; + break; + case POWER_SUPPLY_PROP_CHARGE_COUNTER: + rc = fg_get_charge_counter(fg, &pval->intval); + break; + case POWER_SUPPLY_PROP_CHARGE_COUNTER_SHADOW: + rc = fg_get_charge_counter_shadow(fg, &pval->intval); + break; + case POWER_SUPPLY_PROP_TIME_TO_FULL_AVG: + rc = fg_get_time_to_full(fg, &pval->intval); + break; + case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG: + rc = fg_get_time_to_empty(fg, &pval->intval); + break; + case POWER_SUPPLY_PROP_SOC_REPORTING_READY: + pval->intval = fg->soc_reporting_ready; + break; + case POWER_SUPPLY_PROP_DEBUG_BATTERY: + pval->intval = is_debug_batt_id(fg); + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: + rc = fg_get_sram_prop(fg, FG_SRAM_VBATT_FULL, &pval->intval); + break; + case POWER_SUPPLY_PROP_CC_STEP: + if ((chip->ttf.cc_step.sel >= 0) && + (chip->ttf.cc_step.sel < MAX_CC_STEPS)) { + pval->intval = + chip->ttf.cc_step.arr[chip->ttf.cc_step.sel]; + } else { + pr_err("cc_step_sel is out of bounds [0, %d]\n", + chip->ttf.cc_step.sel); + return -EINVAL; + } + break; + case POWER_SUPPLY_PROP_CC_STEP_SEL: + pval->intval = chip->ttf.cc_step.sel; + break; + default: + pr_err("unsupported property %d\n", psp); + rc = -EINVAL; + break; + } + + if (rc < 0) + return -ENODATA; + + return 0; +} + +static int fg_psy_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *pval) +{ + struct fg_gen3_chip *chip = power_supply_get_drvdata(psy); + struct fg_dev *fg = &chip->fg; + int rc = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: + rc = fg_set_constant_chg_voltage(fg, pval->intval); + break; + case POWER_SUPPLY_PROP_RESISTANCE: + rc = fg_force_esr_meas(fg); + break; + case POWER_SUPPLY_PROP_CHARGE_QNOVO_ENABLE: + rc = fg_prepare_for_qnovo(fg, pval->intval); + break; + case POWER_SUPPLY_PROP_CC_STEP: + if ((chip->ttf.cc_step.sel >= 0) && + (chip->ttf.cc_step.sel < MAX_CC_STEPS)) { + chip->ttf.cc_step.arr[chip->ttf.cc_step.sel] = + pval->intval; + } else { + pr_err("cc_step_sel is out of bounds [0, %d]\n", + chip->ttf.cc_step.sel); + return -EINVAL; + } + break; + case POWER_SUPPLY_PROP_CC_STEP_SEL: + if ((pval->intval >= 0) && (pval->intval < MAX_CC_STEPS)) { + chip->ttf.cc_step.sel = pval->intval; + } else { + pr_err("cc_step_sel is out of bounds [0, %d]\n", + pval->intval); + return -EINVAL; + } + break; + case POWER_SUPPLY_PROP_CHARGE_FULL: + if (chip->cl.active) { + pr_warn("Capacity learning active!\n"); + return 0; + } + if (pval->intval <= 0 || pval->intval > chip->cl.nom_cap_uah) { + pr_err("charge_full is out of bounds\n"); + return -EINVAL; + } + chip->cl.learned_cc_uah = pval->intval; + rc = fg_save_learned_cap_to_sram(fg); + if (rc < 0) + pr_err("Error in saving learned_cc_uah, rc=%d\n", rc); + break; + case POWER_SUPPLY_PROP_COLD_TEMP: + rc = fg_set_jeita_threshold(fg, JEITA_COLD, pval->intval); + if (rc < 0) { + pr_err("Error in writing jeita_cold, rc=%d\n", rc); + return rc; + } + break; + case POWER_SUPPLY_PROP_COOL_TEMP: + rc = fg_set_jeita_threshold(fg, JEITA_COOL, pval->intval); + if (rc < 0) { + pr_err("Error in writing jeita_cool, rc=%d\n", rc); + return rc; + } + break; + case POWER_SUPPLY_PROP_WARM_TEMP: + rc = fg_set_jeita_threshold(fg, JEITA_WARM, pval->intval); + if (rc < 0) { + pr_err("Error in writing jeita_warm, rc=%d\n", rc); + return rc; + } + break; + case POWER_SUPPLY_PROP_HOT_TEMP: + rc = fg_set_jeita_threshold(fg, JEITA_HOT, pval->intval); + if (rc < 0) { + pr_err("Error in writing jeita_hot, rc=%d\n", rc); + return rc; + } + break; + default: + break; + } + + return rc; +} + +static int fg_property_is_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + switch (psp) { + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: + case POWER_SUPPLY_PROP_CC_STEP: + case POWER_SUPPLY_PROP_CC_STEP_SEL: + case POWER_SUPPLY_PROP_CHARGE_FULL: + case POWER_SUPPLY_PROP_COLD_TEMP: + case POWER_SUPPLY_PROP_COOL_TEMP: + case POWER_SUPPLY_PROP_WARM_TEMP: + case POWER_SUPPLY_PROP_HOT_TEMP: + return 1; + default: + break; + } + + return 0; +} + +static void fg_external_power_changed(struct power_supply *psy) +{ + pr_debug("power supply changed\n"); +} + +static int fg_notifier_cb(struct notifier_block *nb, + unsigned long event, void *data) +{ + struct power_supply *psy = data; + struct fg_dev *fg = container_of(nb, struct fg_dev, nb); + + spin_lock(&fg->suspend_lock); + if (fg->suspended) { + /* Return if we are still suspended */ + spin_unlock(&fg->suspend_lock); + return NOTIFY_OK; + } + spin_unlock(&fg->suspend_lock); + + if (event != PSY_EVENT_PROP_CHANGED) + return NOTIFY_OK; + + if (work_pending(&fg->status_change_work)) + return NOTIFY_OK; + + if ((strcmp(psy->desc->name, "battery") == 0) + || (strcmp(psy->desc->name, "parallel") == 0) + || (strcmp(psy->desc->name, "usb") == 0)) { + /* + * We cannot vote for awake votable here as that takes + * a mutex lock and this is executed in an atomic context. + */ + fg_stay_awake(fg, FG_STATUS_NOTIFY_WAKE); + schedule_work(&fg->status_change_work); + } + + return NOTIFY_OK; +} + +static int twm_notifier_cb(struct notifier_block *nb, + unsigned long action, void *data) +{ + struct fg_dev *fg = container_of(nb, struct fg_dev, twm_nb); + + if (action != PMIC_TWM_CLEAR && + action != PMIC_TWM_ENABLE) { + pr_debug("Unsupported option %lu\n", action); + return NOTIFY_OK; + } + + fg->twm_state = (u8)action; + + return NOTIFY_OK; +} + +static enum power_supply_property fg_psy_props[] = { + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_REAL_CAPACITY, + POWER_SUPPLY_PROP_CAPACITY_RAW, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_COLD_TEMP, + POWER_SUPPLY_PROP_COOL_TEMP, + POWER_SUPPLY_PROP_WARM_TEMP, + POWER_SUPPLY_PROP_HOT_TEMP, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_VOLTAGE_OCV, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_RESISTANCE_ID, + POWER_SUPPLY_PROP_RESISTANCE, + POWER_SUPPLY_PROP_BATTERY_TYPE, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, + POWER_SUPPLY_PROP_CYCLE_COUNT, + POWER_SUPPLY_PROP_CYCLE_COUNTS, + POWER_SUPPLY_PROP_CHARGE_NOW_RAW, + POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_CHARGE_COUNTER, + POWER_SUPPLY_PROP_CHARGE_COUNTER_SHADOW, + POWER_SUPPLY_PROP_TIME_TO_FULL_AVG, + POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG, + POWER_SUPPLY_PROP_SOC_REPORTING_READY, + POWER_SUPPLY_PROP_DEBUG_BATTERY, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE, + POWER_SUPPLY_PROP_CC_STEP, + POWER_SUPPLY_PROP_CC_STEP_SEL, +}; + +static const struct power_supply_desc fg_psy_desc = { + .name = "bms", + .type = POWER_SUPPLY_TYPE_BMS, + .properties = fg_psy_props, + .num_properties = ARRAY_SIZE(fg_psy_props), + .get_property = fg_psy_get_property, + .set_property = fg_psy_set_property, + .external_power_changed = fg_external_power_changed, + .property_is_writeable = fg_property_is_writeable, +}; + +/* INIT FUNCTIONS STAY HERE */ + +#define DEFAULT_ESR_CHG_TIMER_RETRY 8 +#define DEFAULT_ESR_CHG_TIMER_MAX 16 +#define VOLTAGE_MODE_SAT_CLEAR_BIT BIT(3) +static int fg_hw_init(struct fg_dev *fg) +{ + struct fg_gen3_chip *chip = container_of(fg, struct fg_gen3_chip, fg); + int rc; + u8 buf[4], val; + + fg_encode(fg->sp, FG_SRAM_CUTOFF_VOLT, chip->dt.cutoff_volt_mv, buf); + rc = fg_sram_write(fg, fg->sp[FG_SRAM_CUTOFF_VOLT].addr_word, + fg->sp[FG_SRAM_CUTOFF_VOLT].addr_byte, buf, + fg->sp[FG_SRAM_CUTOFF_VOLT].len, FG_IMA_DEFAULT); + if (rc < 0) { + pr_err("Error in writing cutoff_volt, rc=%d\n", rc); + return rc; + } + + fg_encode(fg->sp, FG_SRAM_EMPTY_VOLT, chip->dt.empty_volt_mv, buf); + rc = fg_sram_write(fg, fg->sp[FG_SRAM_EMPTY_VOLT].addr_word, + fg->sp[FG_SRAM_EMPTY_VOLT].addr_byte, buf, + fg->sp[FG_SRAM_EMPTY_VOLT].len, FG_IMA_DEFAULT); + if (rc < 0) { + pr_err("Error in writing empty_volt, rc=%d\n", rc); + return rc; + } + + fg_encode(fg->sp, FG_SRAM_CHG_TERM_CURR, chip->dt.chg_term_curr_ma, + buf); + rc = fg_sram_write(fg, fg->sp[FG_SRAM_CHG_TERM_CURR].addr_word, + fg->sp[FG_SRAM_CHG_TERM_CURR].addr_byte, buf, + fg->sp[FG_SRAM_CHG_TERM_CURR].len, FG_IMA_DEFAULT); + if (rc < 0) { + pr_err("Error in writing chg_term_curr, rc=%d\n", rc); + return rc; + } + + fg_encode(fg->sp, FG_SRAM_SYS_TERM_CURR, chip->dt.sys_term_curr_ma, + buf); + rc = fg_sram_write(fg, fg->sp[FG_SRAM_SYS_TERM_CURR].addr_word, + fg->sp[FG_SRAM_SYS_TERM_CURR].addr_byte, buf, + fg->sp[FG_SRAM_SYS_TERM_CURR].len, FG_IMA_DEFAULT); + if (rc < 0) { + pr_err("Error in writing sys_term_curr, rc=%d\n", rc); + return rc; + } + + fg_encode(fg->sp, FG_SRAM_CUTOFF_CURR, chip->dt.cutoff_curr_ma, + buf); + rc = fg_sram_write(fg, fg->sp[FG_SRAM_CUTOFF_CURR].addr_word, + fg->sp[FG_SRAM_CUTOFF_CURR].addr_byte, buf, + fg->sp[FG_SRAM_CUTOFF_CURR].len, FG_IMA_DEFAULT); + if (rc < 0) { + pr_err("Error in writing cutoff_curr, rc=%d\n", rc); + return rc; + } + + if (!(fg->wa_flags & PMI8998_V1_REV_WA)) { + fg_encode(fg->sp, FG_SRAM_CHG_TERM_BASE_CURR, + chip->dt.chg_term_base_curr_ma, buf); + rc = fg_sram_write(fg, + fg->sp[FG_SRAM_CHG_TERM_BASE_CURR].addr_word, + fg->sp[FG_SRAM_CHG_TERM_BASE_CURR].addr_byte, + buf, fg->sp[FG_SRAM_CHG_TERM_BASE_CURR].len, + FG_IMA_DEFAULT); + if (rc < 0) { + pr_err("Error in writing chg_term_base_curr, rc=%d\n", + rc); + return rc; + } + } + + if (chip->dt.vbatt_low_thr_mv > 0) { + fg_encode(fg->sp, FG_SRAM_VBATT_LOW, + chip->dt.vbatt_low_thr_mv, buf); + rc = fg_sram_write(fg, fg->sp[FG_SRAM_VBATT_LOW].addr_word, + fg->sp[FG_SRAM_VBATT_LOW].addr_byte, buf, + fg->sp[FG_SRAM_VBATT_LOW].len, + FG_IMA_DEFAULT); + if (rc < 0) { + pr_err("Error in writing vbatt_low_thr, rc=%d\n", rc); + return rc; + } + } + + if (chip->dt.delta_soc_thr > 0 && chip->dt.delta_soc_thr < 100) { + fg_encode(fg->sp, FG_SRAM_DELTA_MSOC_THR, + chip->dt.delta_soc_thr, buf); + rc = fg_sram_write(fg, + fg->sp[FG_SRAM_DELTA_MSOC_THR].addr_word, + fg->sp[FG_SRAM_DELTA_MSOC_THR].addr_byte, + buf, fg->sp[FG_SRAM_DELTA_MSOC_THR].len, + FG_IMA_DEFAULT); + if (rc < 0) { + pr_err("Error in writing delta_msoc_thr, rc=%d\n", rc); + return rc; + } + + fg_encode(fg->sp, FG_SRAM_DELTA_BSOC_THR, + chip->dt.delta_soc_thr, buf); + rc = fg_sram_write(fg, + fg->sp[FG_SRAM_DELTA_BSOC_THR].addr_word, + fg->sp[FG_SRAM_DELTA_BSOC_THR].addr_byte, + buf, fg->sp[FG_SRAM_DELTA_BSOC_THR].len, + FG_IMA_DEFAULT); + if (rc < 0) { + pr_err("Error in writing delta_bsoc_thr, rc=%d\n", rc); + return rc; + } + } + + /* + * configure battery thermal coefficients c1,c2,c3 + * if its value is not zero. + */ + if (chip->dt.batt_therm_coeffs[0] > 0) { + rc = fg_write(fg, BATT_INFO_THERM_C1(fg), + chip->dt.batt_therm_coeffs, BATT_THERM_NUM_COEFFS); + if (rc < 0) { + pr_err("Error in writing battery thermal coefficients, rc=%d\n", + rc); + return rc; + } + } + + + if (chip->dt.recharge_soc_thr > 0 && chip->dt.recharge_soc_thr < 100) { + rc = fg_set_recharge_soc(fg, chip->dt.recharge_soc_thr); + if (rc < 0) { + pr_err("Error in setting recharge_soc, rc=%d\n", rc); + return rc; + } + } + + if (chip->dt.recharge_volt_thr_mv > 0) { + rc = fg_set_recharge_voltage(fg, chip->dt.recharge_volt_thr_mv); + if (rc < 0) { + pr_err("Error in setting recharge_voltage, rc=%d\n", + rc); + return rc; + } + } + + if (chip->dt.rsense_sel >= SRC_SEL_BATFET && + chip->dt.rsense_sel < SRC_SEL_RESERVED) { + rc = fg_masked_write(fg, BATT_INFO_IBATT_SENSING_CFG(fg), + SOURCE_SELECT_MASK, chip->dt.rsense_sel); + if (rc < 0) { + pr_err("Error in writing rsense_sel, rc=%d\n", rc); + return rc; + } + } + + rc = fg_set_jeita_threshold(fg, JEITA_COLD, + chip->dt.jeita_thresholds[JEITA_COLD] * 10); + if (rc < 0) { + pr_err("Error in writing jeita_cold, rc=%d\n", rc); + return rc; + } + + rc = fg_set_jeita_threshold(fg, JEITA_COOL, + chip->dt.jeita_thresholds[JEITA_COOL] * 10); + if (rc < 0) { + pr_err("Error in writing jeita_cool, rc=%d\n", rc); + return rc; + } + + rc = fg_set_jeita_threshold(fg, JEITA_WARM, + chip->dt.jeita_thresholds[JEITA_WARM] * 10); + if (rc < 0) { + pr_err("Error in writing jeita_warm, rc=%d\n", rc); + return rc; + } + + rc = fg_set_jeita_threshold(fg, JEITA_HOT, + chip->dt.jeita_thresholds[JEITA_HOT] * 10); + if (rc < 0) { + pr_err("Error in writing jeita_hot, rc=%d\n", rc); + return rc; + } + + if (fg->pmic_rev_id->pmic_subtype == PMI8998_SUBTYPE) { + chip->esr_timer_charging_default[TIMER_RETRY] = + DEFAULT_ESR_CHG_TIMER_RETRY; + chip->esr_timer_charging_default[TIMER_MAX] = + DEFAULT_ESR_CHG_TIMER_MAX; + } else { + /* We don't need this for pm660 at present */ + chip->esr_timer_charging_default[TIMER_RETRY] = -EINVAL; + chip->esr_timer_charging_default[TIMER_MAX] = -EINVAL; + } + + rc = fg_set_esr_timer(fg, chip->dt.esr_timer_charging[TIMER_RETRY], + chip->dt.esr_timer_charging[TIMER_MAX], true, FG_IMA_DEFAULT); + if (rc < 0) { + pr_err("Error in setting ESR timer, rc=%d\n", rc); + return rc; + } + + rc = fg_set_esr_timer(fg, chip->dt.esr_timer_awake[TIMER_RETRY], + chip->dt.esr_timer_awake[TIMER_MAX], false, FG_IMA_DEFAULT); + if (rc < 0) { + pr_err("Error in setting ESR timer, rc=%d\n", rc); + return rc; + } + + restore_cycle_counter(fg); + + if (chip->dt.jeita_hyst_temp >= 0) { + val = chip->dt.jeita_hyst_temp << JEITA_TEMP_HYST_SHIFT; + rc = fg_masked_write(fg, BATT_INFO_BATT_TEMP_CFG(fg), + JEITA_TEMP_HYST_MASK, val); + if (rc < 0) { + pr_err("Error in writing batt_temp_cfg, rc=%d\n", rc); + return rc; + } + } + + get_batt_temp_delta(chip->dt.batt_temp_delta, &val); + rc = fg_masked_write(fg, BATT_INFO_BATT_TMPR_INTR(fg), + CHANGE_THOLD_MASK, val); + if (rc < 0) { + pr_err("Error in writing batt_temp_delta, rc=%d\n", rc); + return rc; + } + + rc = fg_sram_masked_write(fg, ESR_EXTRACTION_ENABLE_WORD, + ESR_EXTRACTION_ENABLE_OFFSET, + VOLTAGE_MODE_SAT_CLEAR_BIT, + VOLTAGE_MODE_SAT_CLEAR_BIT, + FG_IMA_DEFAULT); + if (rc < 0) + return rc; + + fg_encode(fg->sp, FG_SRAM_ESR_TIGHT_FILTER, + chip->dt.esr_tight_flt_upct, buf); + rc = fg_sram_write(fg, fg->sp[FG_SRAM_ESR_TIGHT_FILTER].addr_word, + fg->sp[FG_SRAM_ESR_TIGHT_FILTER].addr_byte, buf, + fg->sp[FG_SRAM_ESR_TIGHT_FILTER].len, FG_IMA_DEFAULT); + if (rc < 0) { + pr_err("Error in writing ESR tight filter, rc=%d\n", rc); + return rc; + } + + fg_encode(fg->sp, FG_SRAM_ESR_BROAD_FILTER, + chip->dt.esr_broad_flt_upct, buf); + rc = fg_sram_write(fg, fg->sp[FG_SRAM_ESR_BROAD_FILTER].addr_word, + fg->sp[FG_SRAM_ESR_BROAD_FILTER].addr_byte, buf, + fg->sp[FG_SRAM_ESR_BROAD_FILTER].len, FG_IMA_DEFAULT); + if (rc < 0) { + pr_err("Error in writing ESR broad filter, rc=%d\n", rc); + return rc; + } + + fg_encode(fg->sp, FG_SRAM_ESR_PULSE_THRESH, + chip->dt.esr_pulse_thresh_ma, buf); + rc = fg_sram_write(fg, fg->sp[FG_SRAM_ESR_PULSE_THRESH].addr_word, + fg->sp[FG_SRAM_ESR_PULSE_THRESH].addr_byte, buf, + fg->sp[FG_SRAM_ESR_PULSE_THRESH].len, FG_IMA_DEFAULT); + if (rc < 0) { + pr_err("Error in writing esr_pulse_thresh_ma, rc=%d\n", rc); + return rc; + } + + get_esr_meas_current(chip->dt.esr_meas_curr_ma, &val); + rc = fg_masked_write(fg, BATT_INFO_ESR_PULL_DN_CFG(fg), + ESR_PULL_DOWN_IVAL_MASK, val); + if (rc < 0) { + pr_err("Error in writing esr_meas_curr_ma, rc=%d\n", rc); + return rc; + } + + if (is_debug_batt_id(fg) || chip->dt.disable_esr_pull_dn) { + val = ESR_NO_PULL_DOWN; + rc = fg_masked_write(fg, BATT_INFO_ESR_PULL_DN_CFG(fg), + ESR_PULL_DOWN_MODE_MASK, val); + if (rc < 0) { + pr_err("Error in writing esr_pull_down, rc=%d\n", rc); + return rc; + } + } + + if (chip->dt.ki_coeff_hi_chg != -EINVAL) { + fg_encode(fg->sp, FG_SRAM_KI_COEFF_HI_CHG, + chip->dt.ki_coeff_hi_chg, &val); + rc = fg_sram_write(fg, + fg->sp[FG_SRAM_KI_COEFF_HI_CHG].addr_word, + fg->sp[FG_SRAM_KI_COEFF_HI_CHG].addr_byte, + &val, fg->sp[FG_SRAM_KI_COEFF_HI_CHG].len, + FG_IMA_DEFAULT); + if (rc < 0) { + pr_err("Error in writing ki_coeff_hi_chg, rc=%d\n", rc); + return rc; + } + } + + if (chip->dt.use_esr_sw) { + /* Enable ESR extraction explicitly */ + rc = fg_sram_masked_write(fg, ESR_EXTRACTION_ENABLE_WORD, + ESR_EXTRACTION_ENABLE_OFFSET, + ESR_EXTRACTION_ENABLE_MASK, + 0x1, FG_IMA_DEFAULT); + if (rc < 0) { + pr_err("Error in enabling ESR extraction rc=%d\n", rc); + return rc; + } + } + + if (chip->dt.sync_sleep_threshold_ma != -EINVAL) { + fg_encode(fg->sp, FG_SRAM_SYNC_SLEEP_THR, + chip->dt.sync_sleep_threshold_ma, buf); + rc = fg_sram_write(fg, + fg->sp[FG_SRAM_SYNC_SLEEP_THR].addr_word, + fg->sp[FG_SRAM_SYNC_SLEEP_THR].addr_byte, buf, + fg->sp[FG_SRAM_SYNC_SLEEP_THR].len, + FG_IMA_DEFAULT); + if (rc < 0) { + pr_err("Error in writing sync_sleep_threshold=%d\n", + rc); + return rc; + } + } + + return 0; +} + +static int fg_adjust_timebase(struct fg_dev *fg) +{ + struct fg_gen3_chip *chip = container_of(fg, struct fg_gen3_chip, fg); + int rc = 0, die_temp; + s32 time_base = 0; + u8 buf[2] = {0}; + + if ((fg->wa_flags & PM660_TSMC_OSC_WA) && chip->die_temp_chan) { + rc = iio_read_channel_processed(chip->die_temp_chan, &die_temp); + if (rc < 0) { + pr_err("Error in reading die_temp, rc:%d\n", rc); + return rc; + } + + rc = fg_lerp(fg_tsmc_osc_table, ARRAY_SIZE(fg_tsmc_osc_table), + die_temp / 1000, &time_base); + if (rc < 0) { + pr_err("Error to lookup fg_tsmc_osc_table rc=%d\n", rc); + return rc; + } + + fg_encode(fg->sp, FG_SRAM_TIMEBASE, time_base, buf); + rc = fg_sram_write(fg, + fg->sp[FG_SRAM_TIMEBASE].addr_word, + fg->sp[FG_SRAM_TIMEBASE].addr_byte, buf, + fg->sp[FG_SRAM_TIMEBASE].len, FG_IMA_DEFAULT); + if (rc < 0) { + pr_err("Error in writing timebase, rc=%d\n", rc); + return rc; + } + } + + return 0; +} + +/* INTERRUPT HANDLERS STAY HERE */ + +static irqreturn_t fg_mem_xcp_irq_handler(int irq, void *data) +{ + struct fg_dev *fg = data; + u8 status; + int rc; + + rc = fg_read(fg, MEM_IF_INT_RT_STS(fg), &status, 1); + if (rc < 0) { + pr_err("failed to read addr=0x%04x, rc=%d\n", + MEM_IF_INT_RT_STS(fg), rc); + return IRQ_HANDLED; + } + + fg_dbg(fg, FG_IRQ, "irq %d triggered, status:%d\n", irq, status); + + mutex_lock(&fg->sram_rw_lock); + rc = fg_clear_dma_errors_if_any(fg); + if (rc < 0) + pr_err("Error in clearing DMA error, rc=%d\n", rc); + + if (status & MEM_XCP_BIT) { + rc = fg_clear_ima_errors_if_any(fg, true); + if (rc < 0 && rc != -EAGAIN) + pr_err("Error in checking IMA errors rc:%d\n", rc); + } + + mutex_unlock(&fg->sram_rw_lock); + return IRQ_HANDLED; +} + +static irqreturn_t fg_vbatt_low_irq_handler(int irq, void *data) +{ + struct fg_dev *fg = data; + + fg_dbg(fg, FG_IRQ, "irq %d triggered\n", irq); + return IRQ_HANDLED; +} + +static irqreturn_t fg_batt_missing_irq_handler(int irq, void *data) +{ + struct fg_dev *fg = data; + struct fg_gen3_chip *chip = container_of(fg, struct fg_gen3_chip, fg); + u8 status; + int rc; + + rc = fg_read(fg, BATT_INFO_INT_RT_STS(fg), &status, 1); + if (rc < 0) { + pr_err("failed to read addr=0x%04x, rc=%d\n", + BATT_INFO_INT_RT_STS(fg), rc); + return IRQ_HANDLED; + } + + fg_dbg(fg, FG_IRQ, "irq %d triggered sts:%d\n", irq, status); + fg->battery_missing = (status & BT_MISS_BIT); + + if (fg->battery_missing) { + fg->profile_available = false; + fg->profile_load_status = PROFILE_NOT_LOADED; + fg->soc_reporting_ready = false; + fg->batt_id_ohms = -EINVAL; + cancel_delayed_work_sync(&chip->pl_enable_work); + vote(chip->pl_disable_votable, ESR_FCC_VOTER, true, 0); + return IRQ_HANDLED; + } + + clear_battery_profile(fg); + schedule_delayed_work(&fg->profile_load_work, 0); + + if (fg->fg_psy) + power_supply_changed(fg->fg_psy); + + return IRQ_HANDLED; +} + +static irqreturn_t fg_delta_batt_temp_irq_handler(int irq, void *data) +{ + struct fg_dev *fg = data; + union power_supply_propval prop = {0, }; + int rc, batt_temp; + + rc = fg_get_battery_temp(fg, &batt_temp); + if (rc < 0) { + pr_err("Error in getting batt_temp\n"); + return IRQ_HANDLED; + } + fg_dbg(fg, FG_IRQ, "irq %d triggered bat_temp: %d\n", irq, batt_temp); + + rc = fg_esr_filter_config(fg, batt_temp, false); + if (rc < 0) + pr_err("Error in configuring ESR filter rc:%d\n", rc); + + rc = fg_slope_limit_config(fg, batt_temp); + if (rc < 0) + pr_err("Error in configuring slope limiter rc:%d\n", rc); + + rc = fg_adjust_ki_coeff_full_soc(fg, batt_temp); + if (rc < 0) + pr_err("Error in configuring ki_coeff_full_soc rc:%d\n", rc); + + if (!batt_psy_initialized(fg)) { + fg->last_batt_temp = batt_temp; + return IRQ_HANDLED; + } + + power_supply_get_property(fg->batt_psy, POWER_SUPPLY_PROP_HEALTH, + &prop); + fg->health = prop.intval; + + if (fg->last_batt_temp != batt_temp) { + rc = fg_adjust_timebase(fg); + if (rc < 0) + pr_err("Error in adjusting timebase, rc=%d\n", rc); + + rc = fg_adjust_recharge_voltage(fg); + if (rc < 0) + pr_err("Error in adjusting recharge_voltage, rc=%d\n", + rc); + + fg->last_batt_temp = batt_temp; + power_supply_changed(fg->batt_psy); + } + + if (abs(fg->last_batt_temp - batt_temp) > 30) + pr_warn("Battery temperature last:%d current: %d\n", + fg->last_batt_temp, batt_temp); + return IRQ_HANDLED; +} + +static irqreturn_t fg_first_est_irq_handler(int irq, void *data) +{ + struct fg_dev *fg = data; + + fg_dbg(fg, FG_IRQ, "irq %d triggered\n", irq); + complete_all(&fg->soc_ready); + return IRQ_HANDLED; +} + +static irqreturn_t fg_soc_update_irq_handler(int irq, void *data) +{ + struct fg_dev *fg = data; + + fg_dbg(fg, FG_IRQ, "irq %d triggered\n", irq); + complete_all(&fg->soc_update); + return IRQ_HANDLED; +} + +static irqreturn_t fg_delta_bsoc_irq_handler(int irq, void *data) +{ + struct fg_dev *fg = data; + int rc; + + fg_dbg(fg, FG_IRQ, "irq %d triggered\n", irq); + rc = fg_charge_full_update(fg); + if (rc < 0) + pr_err("Error in charge_full_update, rc=%d\n", rc); + + return IRQ_HANDLED; +} + +static irqreturn_t fg_delta_msoc_irq_handler(int irq, void *data) +{ + struct fg_dev *fg = data; + struct fg_gen3_chip *chip = container_of(fg, struct fg_gen3_chip, fg); + int rc; + + fg_dbg(fg, FG_IRQ, "irq %d triggered\n", irq); + fg_cycle_counter_update(fg); + + if (chip->cl.active) + fg_cap_learning_update(fg); + + rc = fg_charge_full_update(fg); + if (rc < 0) + pr_err("Error in charge_full_update, rc=%d\n", rc); + + rc = fg_adjust_ki_coeff_dischg(fg); + if (rc < 0) + pr_err("Error in adjusting ki_coeff_dischg, rc=%d\n", rc); + + rc = fg_update_maint_soc(fg); + if (rc < 0) + pr_err("Error in updating maint_soc, rc=%d\n", rc); + + rc = fg_esr_validate(fg); + if (rc < 0) + pr_err("Error in validating ESR, rc=%d\n", rc); + + rc = fg_adjust_timebase(fg); + if (rc < 0) + pr_err("Error in adjusting timebase, rc=%d\n", rc); + + if (batt_psy_initialized(fg)) + power_supply_changed(fg->batt_psy); + + return IRQ_HANDLED; +} + +static irqreturn_t fg_empty_soc_irq_handler(int irq, void *data) +{ + struct fg_dev *fg = data; + + fg_dbg(fg, FG_IRQ, "irq %d triggered\n", irq); + if (batt_psy_initialized(fg)) + power_supply_changed(fg->batt_psy); + + return IRQ_HANDLED; +} + +static irqreturn_t fg_soc_irq_handler(int irq, void *data) +{ + struct fg_dev *fg = data; + + fg_dbg(fg, FG_IRQ, "irq %d triggered\n", irq); + return IRQ_HANDLED; +} + +static irqreturn_t fg_dummy_irq_handler(int irq, void *data) +{ + pr_debug("irq %d triggered\n", irq); + return IRQ_HANDLED; +} + +static struct fg_irq_info fg_irqs[FG_GEN3_IRQ_MAX] = { + /* BATT_SOC irqs */ + [MSOC_FULL_IRQ] = { + .name = "msoc-full", + .handler = fg_soc_irq_handler, + }, + [MSOC_HIGH_IRQ] = { + .name = "msoc-high", + .handler = fg_soc_irq_handler, + .wakeable = true, + }, + [MSOC_EMPTY_IRQ] = { + .name = "msoc-empty", + .handler = fg_empty_soc_irq_handler, + .wakeable = true, + }, + [MSOC_LOW_IRQ] = { + .name = "msoc-low", + .handler = fg_soc_irq_handler, + .wakeable = true, + }, + [MSOC_DELTA_IRQ] = { + .name = "msoc-delta", + .handler = fg_delta_msoc_irq_handler, + .wakeable = true, + }, + [BSOC_DELTA_IRQ] = { + .name = "bsoc-delta", + .handler = fg_delta_bsoc_irq_handler, + .wakeable = true, + }, + [SOC_READY_IRQ] = { + .name = "soc-ready", + .handler = fg_first_est_irq_handler, + .wakeable = true, + }, + [SOC_UPDATE_IRQ] = { + .name = "soc-update", + .handler = fg_soc_update_irq_handler, + }, + /* BATT_INFO irqs */ + [BATT_TEMP_DELTA_IRQ] = { + .name = "batt-temp-delta", + .handler = fg_delta_batt_temp_irq_handler, + .wakeable = true, + }, + [BATT_MISSING_IRQ] = { + .name = "batt-missing", + .handler = fg_batt_missing_irq_handler, + .wakeable = true, + }, + [ESR_DELTA_IRQ] = { + .name = "esr-delta", + .handler = fg_dummy_irq_handler, + }, + [VBATT_LOW_IRQ] = { + .name = "vbatt-low", + .handler = fg_vbatt_low_irq_handler, + .wakeable = true, + }, + [VBATT_PRED_DELTA_IRQ] = { + .name = "vbatt-pred-delta", + .handler = fg_dummy_irq_handler, + }, + /* MEM_IF irqs */ + [DMA_GRANT_IRQ] = { + .name = "dma-grant", + .handler = fg_dummy_irq_handler, + .wakeable = true, + }, + [MEM_XCP_IRQ] = { + .name = "mem-xcp", + .handler = fg_mem_xcp_irq_handler, + }, + [IMA_RDY_IRQ] = { + .name = "ima-rdy", + .handler = fg_dummy_irq_handler, + }, +}; + +static int fg_parse_dt_property_u32_array(struct device_node *node, + const char *prop_name, int *buf, int len) +{ + int rc; + + rc = of_property_count_elems_of_size(node, prop_name, sizeof(u32)); + if (rc < 0) { + if (rc == -EINVAL) + return 0; + else + return rc; + } else if (rc != len) { + pr_err("Incorrect length %d for %s, rc=%d\n", len, prop_name, + rc); + return -EINVAL; + } + + rc = of_property_read_u32_array(node, prop_name, buf, len); + if (rc < 0) { + pr_err("Error in reading %s, rc=%d\n", prop_name, rc); + return rc; + } + + return 0; +} + +static int fg_parse_slope_limit_coefficients(struct fg_dev *fg) +{ + struct fg_gen3_chip *chip = container_of(fg, struct fg_gen3_chip, fg); + struct device_node *node = fg->dev->of_node; + int rc, i; + + rc = of_property_read_u32(node, "qcom,slope-limit-temp-threshold", + &chip->dt.slope_limit_temp); + if (rc < 0) + return 0; + + rc = fg_parse_dt_property_u32_array(node, "qcom,slope-limit-coeffs", + chip->dt.slope_limit_coeffs, SLOPE_LIMIT_NUM_COEFFS); + if (rc < 0) + return rc; + + for (i = 0; i < SLOPE_LIMIT_NUM_COEFFS; i++) { + if (chip->dt.slope_limit_coeffs[i] > SLOPE_LIMIT_COEFF_MAX || + chip->dt.slope_limit_coeffs[i] < 0) { + pr_err("Incorrect slope limit coefficient\n"); + return -EINVAL; + } + } + + chip->slope_limit_en = true; + return 0; +} + +static int fg_parse_ki_coefficients(struct fg_dev *fg) +{ + struct fg_gen3_chip *chip = container_of(fg, struct fg_gen3_chip, fg); + struct device_node *node = fg->dev->of_node; + int rc, i, temp; + + rc = of_property_read_u32(node, "qcom,ki-coeff-full-dischg", &temp); + if (!rc) + chip->dt.ki_coeff_full_soc_dischg = temp; + + chip->dt.ki_coeff_hi_chg = -EINVAL; + rc = of_property_read_u32(node, "qcom,ki-coeff-hi-chg", &temp); + if (!rc) + chip->dt.ki_coeff_hi_chg = temp; + + if (!of_find_property(node, "qcom,ki-coeff-soc-dischg", NULL) || + (!of_find_property(node, "qcom,ki-coeff-low-dischg", NULL) && + !of_find_property(node, "qcom,ki-coeff-med-dischg", NULL) && + !of_find_property(node, "qcom,ki-coeff-hi-dischg", NULL))) + return 0; + + rc = fg_parse_dt_property_u32_array(node, "qcom,ki-coeff-soc-dischg", + chip->dt.ki_coeff_soc, KI_COEFF_SOC_LEVELS); + if (rc < 0) + return rc; + + rc = fg_parse_dt_property_u32_array(node, "qcom,ki-coeff-low-dischg", + chip->dt.ki_coeff_low_dischg, KI_COEFF_SOC_LEVELS); + if (rc < 0) + return rc; + + rc = fg_parse_dt_property_u32_array(node, "qcom,ki-coeff-med-dischg", + chip->dt.ki_coeff_med_dischg, KI_COEFF_SOC_LEVELS); + if (rc < 0) + return rc; + + rc = fg_parse_dt_property_u32_array(node, "qcom,ki-coeff-hi-dischg", + chip->dt.ki_coeff_hi_dischg, KI_COEFF_SOC_LEVELS); + if (rc < 0) + return rc; + + for (i = 0; i < KI_COEFF_SOC_LEVELS; i++) { + if (chip->dt.ki_coeff_soc[i] < 0 || + chip->dt.ki_coeff_soc[i] > FULL_CAPACITY) { + pr_err("Error in ki_coeff_soc_dischg values\n"); + return -EINVAL; + } + + if (chip->dt.ki_coeff_low_dischg[i] < 0 || + chip->dt.ki_coeff_low_dischg[i] > KI_COEFF_MAX) { + pr_err("Error in ki_coeff_low_dischg values\n"); + return -EINVAL; + } + + if (chip->dt.ki_coeff_med_dischg[i] < 0 || + chip->dt.ki_coeff_med_dischg[i] > KI_COEFF_MAX) { + pr_err("Error in ki_coeff_med_dischg values\n"); + return -EINVAL; + } + + if (chip->dt.ki_coeff_hi_dischg[i] < 0 || + chip->dt.ki_coeff_hi_dischg[i] > KI_COEFF_MAX) { + pr_err("Error in ki_coeff_hi_dischg values\n"); + return -EINVAL; + } + } + chip->ki_coeff_dischg_en = true; + return 0; +} + +#ifdef CONFIG_DEBUG_FS +static void fg_create_debugfs(struct fg_dev *fg) +{ + struct dentry *entry; + + fg->dfs_root = debugfs_create_dir("fuel_gauge", NULL); + if (IS_ERR_OR_NULL(fg->dfs_root)) { + pr_err("Failed to create debugfs directory rc=%ld\n", + (long)fg->dfs_root); + return; + } + + entry = debugfs_create_u32("debug_mask", 0600, fg->dfs_root, + &fg_gen3_debug_mask); + if (IS_ERR_OR_NULL(entry)) { + pr_err("Failed to create debug_mask rc=%ld\n", (long)entry); + debugfs_remove_recursive(fg->dfs_root); + } +} +#else +static void fg_create_debugfs(struct fg_dev *fg) +{ +} +#endif + +#define DEFAULT_CUTOFF_VOLT_MV 3200 +#define DEFAULT_EMPTY_VOLT_MV 2850 +#define DEFAULT_RECHARGE_VOLT_MV 4250 +#define DEFAULT_CHG_TERM_CURR_MA 100 +#define DEFAULT_CHG_TERM_BASE_CURR_MA 75 +#define DEFAULT_SYS_TERM_CURR_MA -125 +#define DEFAULT_CUTOFF_CURR_MA 500 +#define DEFAULT_DELTA_SOC_THR 1 +#define DEFAULT_RECHARGE_SOC_THR 95 +#define DEFAULT_BATT_TEMP_COLD 0 +#define DEFAULT_BATT_TEMP_COOL 5 +#define DEFAULT_BATT_TEMP_WARM 45 +#define DEFAULT_BATT_TEMP_HOT 50 +#define DEFAULT_CL_START_SOC 15 +#define DEFAULT_CL_MIN_TEMP_DECIDEGC 150 +#define DEFAULT_CL_MAX_TEMP_DECIDEGC 500 +#define DEFAULT_CL_MAX_INC_DECIPERC 5 +#define DEFAULT_CL_MAX_DEC_DECIPERC 100 +#define DEFAULT_CL_MIN_LIM_DECIPERC 0 +#define DEFAULT_CL_MAX_LIM_DECIPERC 0 +#define BTEMP_DELTA_LOW 2 +#define BTEMP_DELTA_HIGH 10 +#define DEFAULT_ESR_FLT_TEMP_DECIDEGC 100 +#define DEFAULT_ESR_TIGHT_FLT_UPCT 3907 +#define DEFAULT_ESR_BROAD_FLT_UPCT 99610 +#define DEFAULT_ESR_TIGHT_LT_FLT_UPCT 30000 +#define DEFAULT_ESR_BROAD_LT_FLT_UPCT 30000 +#define DEFAULT_ESR_FLT_RT_DECIDEGC 60 +#define DEFAULT_ESR_TIGHT_RT_FLT_UPCT 5860 +#define DEFAULT_ESR_BROAD_RT_FLT_UPCT 156250 +#define DEFAULT_ESR_CLAMP_MOHMS 20 +#define DEFAULT_ESR_PULSE_THRESH_MA 110 +#define DEFAULT_ESR_MEAS_CURR_MA 120 +#define DEFAULT_BMD_EN_DELAY_MS 200 +static int fg_parse_dt(struct fg_gen3_chip *chip) +{ + struct fg_dev *fg = &chip->fg; + struct device_node *child, *revid_node, *node = fg->dev->of_node; + u32 base, temp; + u8 subtype; + int rc; + + if (!node) { + dev_err(fg->dev, "device tree node missing\n"); + return -ENXIO; + } + + revid_node = of_parse_phandle(node, "qcom,pmic-revid", 0); + if (!revid_node) { + pr_err("Missing qcom,pmic-revid property - driver failed\n"); + return -EINVAL; + } + + fg->pmic_rev_id = get_revid_data(revid_node); + if (IS_ERR_OR_NULL(fg->pmic_rev_id)) { + pr_err("Unable to get pmic_revid rc=%ld\n", + PTR_ERR(fg->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; + } + + pr_debug("PMIC subtype %d Digital major %d\n", + fg->pmic_rev_id->pmic_subtype, fg->pmic_rev_id->rev4); + + switch (fg->pmic_rev_id->pmic_subtype) { + case PMI8998_SUBTYPE: + fg->version = GEN3_FG; + fg->use_dma = true; + if (fg->pmic_rev_id->rev4 < PMI8998_V2P0_REV4) { + fg->sp = pmi8998_v1_sram_params; + fg->alg_flags = pmi8998_v1_alg_flags; + fg->wa_flags |= PMI8998_V1_REV_WA; + } else if (fg->pmic_rev_id->rev4 == PMI8998_V2P0_REV4) { + fg->sp = pmi8998_v2_sram_params; + fg->alg_flags = pmi8998_v2_alg_flags; + } else { + return -EINVAL; + } + break; + case PM660_SUBTYPE: + fg->version = GEN3_FG; + fg->sp = pmi8998_v2_sram_params; + fg->alg_flags = pmi8998_v2_alg_flags; + fg->use_ima_single_mode = true; + if (fg->pmic_rev_id->fab_id == PM660_FAB_ID_TSMC) + fg->wa_flags |= PM660_TSMC_OSC_WA; + break; + default: + return -EINVAL; + } + + if (of_get_available_child_count(node) == 0) { + dev_err(fg->dev, "No child nodes specified!\n"); + return -ENXIO; + } + + for_each_available_child_of_node(node, child) { + rc = of_property_read_u32(child, "reg", &base); + if (rc < 0) { + dev_err(fg->dev, "reg not specified in node %s, rc=%d\n", + child->full_name, rc); + return rc; + } + + rc = fg_read(fg, base + PERPH_SUBTYPE_REG, &subtype, 1); + if (rc < 0) { + dev_err(fg->dev, "Couldn't read subtype for base %d, rc=%d\n", + base, rc); + return rc; + } + + switch (subtype) { + case FG_BATT_SOC_PMI8998: + fg->batt_soc_base = base; + break; + case FG_BATT_INFO_PMI8998: + fg->batt_info_base = base; + break; + case FG_MEM_INFO_PMI8998: + fg->mem_if_base = base; + break; + default: + dev_err(fg->dev, "Invalid peripheral subtype 0x%x\n", + subtype); + return -ENXIO; + } + } + + rc = of_property_read_u32(node, "qcom,rradc-base", &base); + if (rc < 0) { + dev_err(fg->dev, "rradc-base not specified, rc=%d\n", rc); + return rc; + } + fg->rradc_base = base; + + /* Read all the optional properties below */ + rc = of_property_read_u32(node, "qcom,fg-cutoff-voltage", &temp); + if (rc < 0) + chip->dt.cutoff_volt_mv = DEFAULT_CUTOFF_VOLT_MV; + else + chip->dt.cutoff_volt_mv = temp; + + rc = of_property_read_u32(node, "qcom,fg-empty-voltage", &temp); + if (rc < 0) + chip->dt.empty_volt_mv = DEFAULT_EMPTY_VOLT_MV; + else + chip->dt.empty_volt_mv = temp; + + rc = of_property_read_u32(node, "qcom,fg-vbatt-low-thr", &temp); + if (rc < 0) + chip->dt.vbatt_low_thr_mv = -EINVAL; + else + chip->dt.vbatt_low_thr_mv = temp; + + rc = of_property_read_u32(node, "qcom,fg-chg-term-current", &temp); + if (rc < 0) + chip->dt.chg_term_curr_ma = DEFAULT_CHG_TERM_CURR_MA; + else + chip->dt.chg_term_curr_ma = temp; + + rc = of_property_read_u32(node, "qcom,fg-sys-term-current", &temp); + if (rc < 0) + chip->dt.sys_term_curr_ma = DEFAULT_SYS_TERM_CURR_MA; + else + chip->dt.sys_term_curr_ma = temp; + + rc = of_property_read_u32(node, "qcom,fg-chg-term-base-current", &temp); + if (rc < 0) + chip->dt.chg_term_base_curr_ma = DEFAULT_CHG_TERM_BASE_CURR_MA; + else + chip->dt.chg_term_base_curr_ma = temp; + + rc = of_property_read_u32(node, "qcom,fg-cutoff-current", &temp); + if (rc < 0) + chip->dt.cutoff_curr_ma = DEFAULT_CUTOFF_CURR_MA; + else + chip->dt.cutoff_curr_ma = temp; + + rc = of_property_read_u32(node, "qcom,fg-delta-soc-thr", &temp); + if (rc < 0) + chip->dt.delta_soc_thr = DEFAULT_DELTA_SOC_THR; + else + chip->dt.delta_soc_thr = temp; + + rc = of_property_read_u32(node, "qcom,fg-recharge-soc-thr", &temp); + if (rc < 0) + chip->dt.recharge_soc_thr = DEFAULT_RECHARGE_SOC_THR; + else + chip->dt.recharge_soc_thr = temp; + + rc = of_property_read_u32(node, "qcom,fg-recharge-voltage", &temp); + if (rc < 0) + chip->dt.recharge_volt_thr_mv = DEFAULT_RECHARGE_VOLT_MV; + else + chip->dt.recharge_volt_thr_mv = temp; + + chip->dt.auto_recharge_soc = of_property_read_bool(node, + "qcom,fg-auto-recharge-soc"); + + rc = of_property_read_u32(node, "qcom,fg-rsense-sel", &temp); + if (rc < 0) + chip->dt.rsense_sel = SRC_SEL_BATFET_SMB; + else + chip->dt.rsense_sel = (u8)temp & SOURCE_SELECT_MASK; + + chip->dt.jeita_thresholds[JEITA_COLD] = DEFAULT_BATT_TEMP_COLD; + chip->dt.jeita_thresholds[JEITA_COOL] = DEFAULT_BATT_TEMP_COOL; + chip->dt.jeita_thresholds[JEITA_WARM] = DEFAULT_BATT_TEMP_WARM; + chip->dt.jeita_thresholds[JEITA_HOT] = DEFAULT_BATT_TEMP_HOT; + if (of_property_count_elems_of_size(node, "qcom,fg-jeita-thresholds", + sizeof(u32)) == NUM_JEITA_LEVELS) { + rc = of_property_read_u32_array(node, + "qcom,fg-jeita-thresholds", + chip->dt.jeita_thresholds, NUM_JEITA_LEVELS); + if (rc < 0) + pr_warn("Error reading Jeita thresholds, default values will be used rc:%d\n", + rc); + } + + if (of_property_count_elems_of_size(node, + "qcom,battery-thermal-coefficients", + sizeof(u8)) == BATT_THERM_NUM_COEFFS) { + rc = of_property_read_u8_array(node, + "qcom,battery-thermal-coefficients", + chip->dt.batt_therm_coeffs, + BATT_THERM_NUM_COEFFS); + if (rc < 0) + pr_warn("Error reading battery thermal coefficients, rc:%d\n", + rc); + } + + rc = fg_parse_dt_property_u32_array(node, "qcom,fg-esr-timer-charging", + chip->dt.esr_timer_charging, NUM_ESR_TIMERS); + if (rc < 0) { + chip->dt.esr_timer_charging[TIMER_RETRY] = -EINVAL; + chip->dt.esr_timer_charging[TIMER_MAX] = -EINVAL; + } + + rc = fg_parse_dt_property_u32_array(node, "qcom,fg-esr-timer-awake", + chip->dt.esr_timer_awake, NUM_ESR_TIMERS); + if (rc < 0) { + chip->dt.esr_timer_awake[TIMER_RETRY] = -EINVAL; + chip->dt.esr_timer_awake[TIMER_MAX] = -EINVAL; + } + + rc = fg_parse_dt_property_u32_array(node, "qcom,fg-esr-timer-asleep", + chip->dt.esr_timer_asleep, NUM_ESR_TIMERS); + if (rc < 0) { + chip->dt.esr_timer_asleep[TIMER_RETRY] = -EINVAL; + chip->dt.esr_timer_asleep[TIMER_MAX] = -EINVAL; + } + + rc = fg_parse_dt_property_u32_array(node, "qcom,fg-esr-timer-shutdown", + chip->dt.esr_timer_shutdown, NUM_ESR_TIMERS); + if (rc < 0) { + chip->dt.esr_timer_shutdown[TIMER_RETRY] = -EINVAL; + chip->dt.esr_timer_shutdown[TIMER_MAX] = -EINVAL; + } + + chip->cyc_ctr.en = of_property_read_bool(node, "qcom,cycle-counter-en"); + + chip->dt.force_load_profile = of_property_read_bool(node, + "qcom,fg-force-load-profile"); + + rc = of_property_read_u32(node, "qcom,cl-start-capacity", &temp); + if (rc < 0) + chip->dt.cl_start_soc = DEFAULT_CL_START_SOC; + else + chip->dt.cl_start_soc = temp; + + rc = of_property_read_u32(node, "qcom,cl-min-temp", &temp); + if (rc < 0) + chip->dt.cl_min_temp = DEFAULT_CL_MIN_TEMP_DECIDEGC; + else + chip->dt.cl_min_temp = temp; + + rc = of_property_read_u32(node, "qcom,cl-max-temp", &temp); + if (rc < 0) + chip->dt.cl_max_temp = DEFAULT_CL_MAX_TEMP_DECIDEGC; + else + chip->dt.cl_max_temp = temp; + + rc = of_property_read_u32(node, "qcom,cl-max-increment", &temp); + if (rc < 0) + chip->dt.cl_max_cap_inc = DEFAULT_CL_MAX_INC_DECIPERC; + else + chip->dt.cl_max_cap_inc = temp; + + rc = of_property_read_u32(node, "qcom,cl-max-decrement", &temp); + if (rc < 0) + chip->dt.cl_max_cap_dec = DEFAULT_CL_MAX_DEC_DECIPERC; + else + chip->dt.cl_max_cap_dec = temp; + + rc = of_property_read_u32(node, "qcom,cl-min-limit", &temp); + if (rc < 0) + chip->dt.cl_min_cap_limit = DEFAULT_CL_MIN_LIM_DECIPERC; + else + chip->dt.cl_min_cap_limit = temp; + + rc = of_property_read_u32(node, "qcom,cl-max-limit", &temp); + if (rc < 0) + chip->dt.cl_max_cap_limit = DEFAULT_CL_MAX_LIM_DECIPERC; + else + chip->dt.cl_max_cap_limit = temp; + + rc = of_property_read_u32(node, "qcom,fg-jeita-hyst-temp", &temp); + if (rc < 0) + chip->dt.jeita_hyst_temp = -EINVAL; + else + chip->dt.jeita_hyst_temp = temp; + + rc = of_property_read_u32(node, "qcom,fg-batt-temp-delta", &temp); + if (rc < 0) + chip->dt.batt_temp_delta = -EINVAL; + else if (temp > BTEMP_DELTA_LOW && temp <= BTEMP_DELTA_HIGH) + chip->dt.batt_temp_delta = temp; + + chip->dt.hold_soc_while_full = of_property_read_bool(node, + "qcom,hold-soc-while-full"); + + chip->dt.linearize_soc = of_property_read_bool(node, + "qcom,linearize-soc"); + + rc = fg_parse_ki_coefficients(fg); + if (rc < 0) + pr_err("Error in parsing Ki coefficients, rc=%d\n", rc); + + rc = of_property_read_u32(node, "qcom,fg-rconn-mohms", &temp); + if (!rc) + chip->dt.rconn_mohms = temp; + + rc = of_property_read_u32(node, "qcom,fg-esr-filter-switch-temp", + &temp); + if (rc < 0) + chip->dt.esr_flt_switch_temp = DEFAULT_ESR_FLT_TEMP_DECIDEGC; + else + chip->dt.esr_flt_switch_temp = temp; + + rc = of_property_read_u32(node, "qcom,fg-esr-tight-filter-micro-pct", + &temp); + if (rc < 0) + chip->dt.esr_tight_flt_upct = DEFAULT_ESR_TIGHT_FLT_UPCT; + else + chip->dt.esr_tight_flt_upct = temp; + + rc = of_property_read_u32(node, "qcom,fg-esr-broad-filter-micro-pct", + &temp); + if (rc < 0) + chip->dt.esr_broad_flt_upct = DEFAULT_ESR_BROAD_FLT_UPCT; + else + chip->dt.esr_broad_flt_upct = temp; + + rc = of_property_read_u32(node, "qcom,fg-esr-tight-lt-filter-micro-pct", + &temp); + if (rc < 0) + chip->dt.esr_tight_lt_flt_upct = DEFAULT_ESR_TIGHT_LT_FLT_UPCT; + else + chip->dt.esr_tight_lt_flt_upct = temp; + + rc = of_property_read_u32(node, "qcom,fg-esr-broad-lt-filter-micro-pct", + &temp); + if (rc < 0) + chip->dt.esr_broad_lt_flt_upct = DEFAULT_ESR_BROAD_LT_FLT_UPCT; + else + chip->dt.esr_broad_lt_flt_upct = temp; + + rc = of_property_read_u32(node, "qcom,fg-esr-rt-filter-switch-temp", + &temp); + if (rc < 0) + chip->dt.esr_flt_rt_switch_temp = DEFAULT_ESR_FLT_RT_DECIDEGC; + else + chip->dt.esr_flt_rt_switch_temp = temp; + + rc = of_property_read_u32(node, "qcom,fg-esr-tight-rt-filter-micro-pct", + &temp); + if (rc < 0) + chip->dt.esr_tight_rt_flt_upct = DEFAULT_ESR_TIGHT_RT_FLT_UPCT; + else + chip->dt.esr_tight_rt_flt_upct = temp; + + rc = of_property_read_u32(node, "qcom,fg-esr-broad-rt-filter-micro-pct", + &temp); + if (rc < 0) + chip->dt.esr_broad_rt_flt_upct = DEFAULT_ESR_BROAD_RT_FLT_UPCT; + else + chip->dt.esr_broad_rt_flt_upct = temp; + + rc = fg_parse_slope_limit_coefficients(fg); + if (rc < 0) + pr_err("Error in parsing slope limit coeffs, rc=%d\n", rc); + + rc = of_property_read_u32(node, "qcom,fg-esr-clamp-mohms", &temp); + if (rc < 0) + chip->dt.esr_clamp_mohms = DEFAULT_ESR_CLAMP_MOHMS; + else + chip->dt.esr_clamp_mohms = temp; + + chip->dt.esr_pulse_thresh_ma = DEFAULT_ESR_PULSE_THRESH_MA; + rc = of_property_read_u32(node, "qcom,fg-esr-pulse-thresh-ma", &temp); + if (!rc) { + /* ESR pulse qualification threshold range is 1-997 mA */ + if (temp > 0 && temp < 997) + chip->dt.esr_pulse_thresh_ma = temp; + } + + chip->dt.esr_meas_curr_ma = DEFAULT_ESR_MEAS_CURR_MA; + rc = of_property_read_u32(node, "qcom,fg-esr-meas-curr-ma", &temp); + if (!rc) { + /* ESR measurement current range is 60-240 mA */ + if (temp >= 60 || temp <= 240) + chip->dt.esr_meas_curr_ma = temp; + } + + chip->dt.bmd_en_delay_ms = DEFAULT_BMD_EN_DELAY_MS; + rc = of_property_read_u32(node, "qcom,fg-bmd-en-delay-ms", &temp); + if (!rc) { + if (temp > DEFAULT_BMD_EN_DELAY_MS) + chip->dt.bmd_en_delay_ms = temp; + } + + chip->dt.sync_sleep_threshold_ma = -EINVAL; + rc = of_property_read_u32(node, + "qcom,fg-sync-sleep-threshold-ma", &temp); + if (!rc) { + if (temp >= 0 && temp < 997) + chip->dt.sync_sleep_threshold_ma = temp; + } + + chip->dt.use_esr_sw = of_property_read_bool(node, "qcom,fg-use-sw-esr"); + + chip->dt.disable_esr_pull_dn = of_property_read_bool(node, + "qcom,fg-disable-esr-pull-dn"); + + chip->dt.disable_fg_twm = of_property_read_bool(node, + "qcom,fg-disable-in-twm"); + + return 0; +} + +static void fg_cleanup(struct fg_gen3_chip *chip) +{ + struct fg_dev *fg = &chip->fg; + + power_supply_unreg_notifier(&fg->nb); + qpnp_misc_twm_notifier_unregister(&fg->twm_nb); + cancel_delayed_work_sync(&chip->ttf_work); + cancel_delayed_work_sync(&fg->sram_dump_work); + if (chip->dt.use_esr_sw) + alarm_cancel(&fg->esr_sw_timer); + cancel_work_sync(&fg->esr_sw_work); + cancel_delayed_work_sync(&fg->profile_load_work); + cancel_work_sync(&fg->status_change_work); + cancel_work_sync(&fg->esr_filter_work); + cancel_delayed_work_sync(&chip->pl_enable_work); + + fg_unregister_interrupts(fg, chip, FG_GEN3_IRQ_MAX); + alarm_try_to_cancel(&fg->esr_filter_alarm); + sysfs_remove_groups(&fg->dev->kobj, fg_groups); + debugfs_remove_recursive(fg->dfs_root); + if (fg->awake_votable) + destroy_votable(fg->awake_votable); + + if (fg->delta_bsoc_irq_en_votable) + destroy_votable(fg->delta_bsoc_irq_en_votable); + + if (fg->batt_miss_irq_en_votable) + destroy_votable(fg->batt_miss_irq_en_votable); + + if (chip->batt_id_chan) + iio_channel_release(chip->batt_id_chan); + + dev_set_drvdata(fg->dev, NULL); +} + +static int fg_gen3_probe(struct platform_device *pdev) +{ + struct fg_gen3_chip *chip; + struct fg_dev *fg; + struct power_supply_config fg_psy_cfg; + int rc, msoc, volt_uv, batt_temp; + + chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + fg = &chip->fg; + fg->dev = &pdev->dev; + fg->debug_mask = &fg_gen3_debug_mask; + fg->irqs = fg_irqs; + fg->charge_status = -EINVAL; + fg->prev_charge_status = -EINVAL; + fg->online_status = -EINVAL; + fg->batt_id_ohms = -EINVAL; + chip->ki_coeff_full_soc = -EINVAL; + fg->regmap = dev_get_regmap(fg->dev->parent, NULL); + if (!fg->regmap) { + dev_err(fg->dev, "Parent regmap is unavailable\n"); + return -ENXIO; + } + + chip->batt_id_chan = iio_channel_get(fg->dev, "rradc_batt_id"); + if (IS_ERR(chip->batt_id_chan)) { + if (PTR_ERR(chip->batt_id_chan) != -EPROBE_DEFER) + pr_err("batt_id_chan unavailable %ld\n", + PTR_ERR(chip->batt_id_chan)); + rc = PTR_ERR(chip->batt_id_chan); + chip->batt_id_chan = NULL; + return rc; + } + + rc = of_property_match_string(fg->dev->of_node, + "io-channel-names", "rradc_die_temp"); + if (rc >= 0) { + chip->die_temp_chan = iio_channel_get(fg->dev, + "rradc_die_temp"); + if (IS_ERR(chip->die_temp_chan)) { + if (PTR_ERR(chip->die_temp_chan) != -EPROBE_DEFER) + pr_err("rradc_die_temp unavailable %ld\n", + PTR_ERR(chip->die_temp_chan)); + rc = PTR_ERR(chip->die_temp_chan); + chip->die_temp_chan = NULL; + return rc; + } + } + + chip->pl_disable_votable = find_votable("PL_DISABLE"); + if (chip->pl_disable_votable == NULL) { + rc = -EPROBE_DEFER; + goto exit; + } + + fg->awake_votable = create_votable("FG_WS", VOTE_SET_ANY, fg_awake_cb, + chip); + if (IS_ERR(fg->awake_votable)) { + rc = PTR_ERR(fg->awake_votable); + fg->awake_votable = NULL; + goto exit; + } + + fg->delta_bsoc_irq_en_votable = create_votable("FG_DELTA_BSOC_IRQ", + VOTE_SET_ANY, + fg_delta_bsoc_irq_en_cb, fg); + if (IS_ERR(fg->delta_bsoc_irq_en_votable)) { + rc = PTR_ERR(fg->delta_bsoc_irq_en_votable); + fg->delta_bsoc_irq_en_votable = NULL; + goto exit; + } + + fg->batt_miss_irq_en_votable = create_votable("FG_BATT_MISS_IRQ", + VOTE_SET_ANY, + fg_batt_miss_irq_en_cb, fg); + if (IS_ERR(fg->batt_miss_irq_en_votable)) { + rc = PTR_ERR(fg->batt_miss_irq_en_votable); + fg->batt_miss_irq_en_votable = NULL; + goto exit; + } + + rc = fg_parse_dt(chip); + if (rc < 0) { + dev_err(fg->dev, "Error in reading DT parameters, rc:%d\n", + rc); + goto exit; + } + + mutex_init(&fg->bus_lock); + mutex_init(&fg->sram_rw_lock); + mutex_init(&fg->charge_full_lock); + mutex_init(&chip->cyc_ctr.lock); + mutex_init(&chip->cl.lock); + mutex_init(&chip->ttf.lock); + mutex_init(&fg->qnovo_esr_ctrl_lock); + spin_lock_init(&fg->suspend_lock); + spin_lock_init(&fg->awake_lock); + init_completion(&fg->soc_update); + init_completion(&fg->soc_ready); + INIT_DELAYED_WORK(&fg->profile_load_work, profile_load_work); + INIT_DELAYED_WORK(&chip->pl_enable_work, pl_enable_work); + INIT_WORK(&fg->status_change_work, status_change_work); + INIT_WORK(&fg->esr_sw_work, fg_esr_sw_work); + INIT_DELAYED_WORK(&chip->ttf_work, ttf_work); + INIT_DELAYED_WORK(&fg->sram_dump_work, sram_dump_work); + INIT_WORK(&fg->esr_filter_work, esr_filter_work); + alarm_init(&fg->esr_filter_alarm, ALARM_BOOTTIME, + fg_esr_filter_alarm_cb); + + fg_create_debugfs(fg); + + rc = fg_memif_init(fg); + if (rc < 0) { + dev_err(fg->dev, "Error in initializing FG_MEMIF, rc:%d\n", + rc); + goto exit; + } + + platform_set_drvdata(pdev, chip); + + rc = fg_hw_init(fg); + if (rc < 0) { + dev_err(fg->dev, "Error in initializing FG hardware, rc:%d\n", + rc); + goto exit; + } + + if (chip->dt.use_esr_sw) { + if (alarmtimer_get_rtcdev()) { + alarm_init(&fg->esr_sw_timer, ALARM_BOOTTIME, + fg_esr_sw_timer); + } else { + pr_err("Failed to get esw_sw alarm-timer\n"); + /* RTC always registers, hence defer until it passes */ + rc = -EPROBE_DEFER; + goto exit; + } + if (chip->dt.esr_timer_charging[TIMER_MAX] != -EINVAL) + fg->esr_wakeup_ms = + chip->dt.esr_timer_charging[TIMER_MAX] * 1460; + else + fg->esr_wakeup_ms = 140000; /* 140 seconds */ + } + + /* Register the power supply */ + fg_psy_cfg.drv_data = chip; + fg_psy_cfg.of_node = NULL; + fg_psy_cfg.supplied_to = NULL; + fg_psy_cfg.num_supplicants = 0; + fg->fg_psy = devm_power_supply_register(fg->dev, &fg_psy_desc, + &fg_psy_cfg); + if (IS_ERR(fg->fg_psy)) { + pr_err("failed to register fg_psy rc = %ld\n", + PTR_ERR(fg->fg_psy)); + goto exit; + } + + fg->nb.notifier_call = fg_notifier_cb; + rc = power_supply_reg_notifier(&fg->nb); + if (rc < 0) { + pr_err("Couldn't register psy notifier rc = %d\n", rc); + goto exit; + } + + fg->twm_nb.notifier_call = twm_notifier_cb; + rc = qpnp_misc_twm_notifier_register(&fg->twm_nb); + if (rc < 0) + pr_err("Failed to register twm_notifier_cb rc=%d\n", rc); + + rc = fg_register_interrupts(&chip->fg, FG_GEN3_IRQ_MAX); + if (rc < 0) { + dev_err(fg->dev, "Error in registering interrupts, rc:%d\n", + rc); + goto exit; + } + + /* Keep SOC_UPDATE irq disabled until we require it */ + if (fg->irqs[SOC_UPDATE_IRQ].irq) + disable_irq_nosync(fg->irqs[SOC_UPDATE_IRQ].irq); + + /* Keep BSOC_DELTA_IRQ disabled until we require it */ + vote(fg->delta_bsoc_irq_en_votable, DELTA_BSOC_IRQ_VOTER, false, 0); + + /* Keep BATT_MISSING_IRQ disabled until we require it */ + vote(fg->batt_miss_irq_en_votable, BATT_MISS_IRQ_VOTER, false, 0); + + rc = fg_debugfs_create(fg); + if (rc < 0) { + dev_err(fg->dev, "Error in creating debugfs entries, rc:%d\n", + rc); + goto exit; + } + + rc = sysfs_create_groups(&fg->dev->kobj, fg_groups); + if (rc < 0) { + pr_err("Failed to create sysfs files rc=%d\n", rc); + goto exit; + } + + rc = fg_get_battery_voltage(fg, &volt_uv); + if (!rc) + rc = fg_get_prop_capacity(fg, &msoc); + + if (!rc) + rc = fg_get_battery_temp(fg, &batt_temp); + + if (!rc) { + pr_info("battery SOC:%d voltage: %duV temp: %d\n", + msoc, volt_uv, batt_temp); + rc = fg_esr_filter_config(fg, batt_temp, false); + if (rc < 0) + pr_err("Error in configuring ESR filter rc:%d\n", rc); + } + + device_init_wakeup(fg->dev, true); + schedule_delayed_work(&fg->profile_load_work, 0); + + pr_debug("FG GEN3 driver probed successfully\n"); + return 0; +exit: + fg_cleanup(chip); + return rc; +} + +static int fg_gen3_suspend(struct device *dev) +{ + struct fg_gen3_chip *chip = dev_get_drvdata(dev); + struct fg_dev *fg = &chip->fg; + int rc; + + spin_lock(&fg->suspend_lock); + fg->suspended = true; + spin_unlock(&fg->suspend_lock); + + rc = fg_esr_timer_config(fg, true); + if (rc < 0) + pr_err("Error in configuring ESR timer, rc=%d\n", rc); + + cancel_delayed_work_sync(&chip->ttf_work); + if (fg_sram_dump) + cancel_delayed_work_sync(&fg->sram_dump_work); + return 0; +} + +static int fg_gen3_resume(struct device *dev) +{ + struct fg_gen3_chip *chip = dev_get_drvdata(dev); + struct fg_dev *fg = &chip->fg; + int rc; + + rc = fg_esr_timer_config(fg, false); + if (rc < 0) + pr_err("Error in configuring ESR timer, rc=%d\n", rc); + + schedule_delayed_work(&chip->ttf_work, 0); + if (fg_sram_dump) + schedule_delayed_work(&fg->sram_dump_work, + msecs_to_jiffies(fg_sram_dump_period_ms)); + + if (!work_pending(&fg->status_change_work)) { + pm_stay_awake(fg->dev); + schedule_work(&fg->status_change_work); + } + + spin_lock(&fg->suspend_lock); + fg->suspended = false; + spin_unlock(&fg->suspend_lock); + + return 0; +} + +static const struct dev_pm_ops fg_gen3_pm_ops = { + .suspend = fg_gen3_suspend, + .resume = fg_gen3_resume, +}; + +static int fg_gen3_remove(struct platform_device *pdev) +{ + struct fg_gen3_chip *chip = dev_get_drvdata(&pdev->dev); + + fg_cleanup(chip); + return 0; +} + +static void fg_gen3_shutdown(struct platform_device *pdev) +{ + struct fg_gen3_chip *chip = dev_get_drvdata(&pdev->dev); + struct fg_dev *fg = &chip->fg; + int rc, bsoc; + u8 mask; + + if (fg->charge_full) { + rc = fg_get_sram_prop(fg, FG_SRAM_BATT_SOC, &bsoc); + if (rc < 0) { + pr_err("Error in getting BATT_SOC, rc=%d\n", rc); + return; + } + + /* We need 2 most significant bytes here */ + bsoc = (u32)bsoc >> 16; + + rc = fg_configure_full_soc(fg, bsoc); + if (rc < 0) { + pr_err("Error in configuring full_soc, rc=%d\n", rc); + return; + } + } + rc = fg_set_esr_timer(fg, chip->dt.esr_timer_shutdown[TIMER_RETRY], + chip->dt.esr_timer_shutdown[TIMER_MAX], false, + FG_IMA_NO_WLOCK); + if (rc < 0) + pr_err("Error in setting ESR timer at shutdown, rc=%d\n", rc); + + if (fg->twm_state == PMIC_TWM_ENABLE && chip->dt.disable_fg_twm) { + rc = fg_masked_write(fg, BATT_SOC_EN_CTL(fg), + FG_ALGORITHM_EN_BIT, 0); + if (rc < 0) + pr_err("Error in disabling FG rc=%d\n", rc); + + mask = BCL_RST_BIT | MEM_RST_BIT | ALG_RST_BIT; + rc = fg_masked_write(fg, BATT_SOC_RST_CTRL0(fg), + mask, mask); + if (rc < 0) + pr_err("Error in disabling FG resets rc=%d\n", rc); + } +} + +static const struct of_device_id fg_gen3_match_table[] = { + {.compatible = FG_GEN3_DEV_NAME}, + {}, +}; + +static struct platform_driver fg_gen3_driver = { + .driver = { + .name = FG_GEN3_DEV_NAME, + .of_match_table = fg_gen3_match_table, + .pm = &fg_gen3_pm_ops, + }, + .probe = fg_gen3_probe, + .remove = fg_gen3_remove, + .shutdown = fg_gen3_shutdown, +}; + +static int __init fg_gen3_init(void) +{ + return platform_driver_register(&fg_gen3_driver); +} + +static void __exit fg_gen3_exit(void) +{ + return platform_driver_unregister(&fg_gen3_driver); +} + +module_init(fg_gen3_init); +module_exit(fg_gen3_exit); + +MODULE_DESCRIPTION("QPNP Fuel gauge GEN3 driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" FG_GEN3_DEV_NAME); diff --git a/drivers/power/supply/qcom/qpnp-smb2.c b/drivers/power/supply/qcom/qpnp-smb2.c new file mode 100644 index 000000000000..63f47cc1a415 --- /dev/null +++ b/drivers/power/supply/qcom/qpnp-smb2.c @@ -0,0 +1,2692 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2016-2017,2019-2020, The Linux Foundation. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "smb-reg.h" +#include "smb-lib.h" +#include "storm-watch.h" +#include + +#define SMB2_DEFAULT_WPWR_UW 8000000 + +static struct smb_params v1_params = { + .fcc = { + .name = "fast charge current", + .reg = FAST_CHARGE_CURRENT_CFG_REG, + .min_u = 0, + .max_u = 4500000, + .step_u = 25000, + }, + .fv = { + .name = "float voltage", + .reg = FLOAT_VOLTAGE_CFG_REG, + .min_u = 3487500, + .max_u = 4920000, + .step_u = 7500, + }, + .usb_icl = { + .name = "usb input current limit", + .reg = USBIN_CURRENT_LIMIT_CFG_REG, + .min_u = 0, + .max_u = 4800000, + .step_u = 25000, + }, + .icl_stat = { + .name = "input current limit status", + .reg = ICL_STATUS_REG, + .min_u = 0, + .max_u = 4800000, + .step_u = 25000, + }, + .otg_cl = { + .name = "usb otg current limit", + .reg = OTG_CURRENT_LIMIT_CFG_REG, + .min_u = 250000, + .max_u = 2000000, + .step_u = 250000, + }, + .dc_icl = { + .name = "dc input current limit", + .reg = DCIN_CURRENT_LIMIT_CFG_REG, + .min_u = 0, + .max_u = 6000000, + .step_u = 25000, + }, + .dc_icl_pt_lv = { + .name = "dc icl PT <8V", + .reg = ZIN_ICL_PT_REG, + .min_u = 0, + .max_u = 3000000, + .step_u = 25000, + }, + .dc_icl_pt_hv = { + .name = "dc icl PT >8V", + .reg = ZIN_ICL_PT_HV_REG, + .min_u = 0, + .max_u = 3000000, + .step_u = 25000, + }, + .dc_icl_div2_lv = { + .name = "dc icl div2 <5.5V", + .reg = ZIN_ICL_LV_REG, + .min_u = 0, + .max_u = 3000000, + .step_u = 25000, + }, + .dc_icl_div2_mid_lv = { + .name = "dc icl div2 5.5-6.5V", + .reg = ZIN_ICL_MID_LV_REG, + .min_u = 0, + .max_u = 3000000, + .step_u = 25000, + }, + .dc_icl_div2_mid_hv = { + .name = "dc icl div2 6.5-8.0V", + .reg = ZIN_ICL_MID_HV_REG, + .min_u = 0, + .max_u = 3000000, + .step_u = 25000, + }, + .dc_icl_div2_hv = { + .name = "dc icl div2 >8.0V", + .reg = ZIN_ICL_HV_REG, + .min_u = 0, + .max_u = 3000000, + .step_u = 25000, + }, + .jeita_cc_comp = { + .name = "jeita fcc reduction", + .reg = JEITA_CCCOMP_CFG_REG, + .min_u = 0, + .max_u = 1575000, + .step_u = 25000, + }, + .freq_buck = { + .name = "buck switching frequency", + .reg = CFG_BUCKBOOST_FREQ_SELECT_BUCK_REG, + .min_u = 600, + .max_u = 2000, + .step_u = 200, + }, + .freq_boost = { + .name = "boost switching frequency", + .reg = CFG_BUCKBOOST_FREQ_SELECT_BOOST_REG, + .min_u = 600, + .max_u = 2000, + .step_u = 200, + }, +}; + +static struct smb_params pm660_params = { + .freq_buck = { + .name = "buck switching frequency", + .reg = FREQ_CLK_DIV_REG, + .min_u = 600, + .max_u = 1600, + .set_proc = smblib_set_chg_freq, + }, + .freq_boost = { + .name = "boost switching frequency", + .reg = FREQ_CLK_DIV_REG, + .min_u = 600, + .max_u = 1600, + .set_proc = smblib_set_chg_freq, + }, +}; + +struct smb_dt_props { + int usb_icl_ua; + int dc_icl_ua; + int boost_threshold_ua; + int wipower_max_uw; + int min_freq_khz; + int max_freq_khz; + struct device_node *revid_dev_node; + int float_option; + int chg_inhibit_thr_mv; + bool no_battery; + bool hvdcp_disable; + bool auto_recharge_soc; + int wd_bark_time; + bool no_pd; +}; + +struct smb2 { + struct smb_charger chg; + struct dentry *dfs_root; + struct smb_dt_props dt; + bool bad_part; +}; + +static int __debug_mask; + +static int __weak_chg_icl_ua = 500000; +static ssize_t weak_chg_icl_ua_show(struct device *dev, struct device_attribute + *attr, char *buf) +{ + return scnprintf(buf, PAGE_SIZE, "%d\n", __weak_chg_icl_ua); +} + +static ssize_t weak_chg_icl_ua_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int val; + + if (kstrtos32(buf, 0, &val)) + return -EINVAL; + + __weak_chg_icl_ua = val; + + return count; +} +static DEVICE_ATTR_RW(weak_chg_icl_ua); + +static int __try_sink_enabled = 1; +static ssize_t try_sink_enabled_show(struct device *dev, struct device_attribute + *attr, char *buf) +{ + return scnprintf(buf, PAGE_SIZE, "%d\n", __try_sink_enabled); +} + +static ssize_t try_sink_enabled_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int val; + + if (kstrtos32(buf, 0, &val)) + return -EINVAL; + + __try_sink_enabled = val; + + return count; +} +static DEVICE_ATTR_RW(try_sink_enabled); + +static int __audio_headset_drp_wait_ms = 100; +static ssize_t audio_headset_drp_wait_ms_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return scnprintf(buf, PAGE_SIZE, "%d\n", __audio_headset_drp_wait_ms); +} + +static ssize_t audio_headset_drp_wait_ms_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int val; + + if (kstrtos32(buf, 0, &val)) + return -EINVAL; + + __audio_headset_drp_wait_ms = val; + + return count; +} +static DEVICE_ATTR_RW(audio_headset_drp_wait_ms); + +static struct attribute *smb2_attrs[] = { + &dev_attr_weak_chg_icl_ua.attr, + &dev_attr_try_sink_enabled.attr, + &dev_attr_audio_headset_drp_wait_ms.attr, + NULL, +}; +ATTRIBUTE_GROUPS(smb2); + + +#define MICRO_1P5A 1500000 +#define MICRO_P1A 100000 +#define OTG_DEFAULT_DEGLITCH_TIME_MS 50 +#define MIN_WD_BARK_TIME 16 +#define DEFAULT_WD_BARK_TIME 64 +#define BITE_WDOG_TIMEOUT_8S 0x3 +#define BARK_WDOG_TIMEOUT_MASK GENMASK(3, 2) +#define BARK_WDOG_TIMEOUT_SHIFT 2 +static int smb2_parse_dt(struct smb2 *chip) +{ + struct smb_charger *chg = &chip->chg; + struct device_node *node = chg->dev->of_node; + int rc, byte_len; + + if (!node) { + pr_err("device tree node missing\n"); + return -EINVAL; + } + + chg->reddragon_ipc_wa = of_property_read_bool(node, + "qcom,qcs605-ipc-wa"); + + chg->step_chg_enabled = of_property_read_bool(node, + "qcom,step-charging-enable"); + + chg->sw_jeita_enabled = of_property_read_bool(node, + "qcom,sw-jeita-enable"); + + rc = of_property_read_u32(node, "qcom,wd-bark-time-secs", + &chip->dt.wd_bark_time); + if (rc < 0 || chip->dt.wd_bark_time < MIN_WD_BARK_TIME) + chip->dt.wd_bark_time = DEFAULT_WD_BARK_TIME; + + chip->dt.no_battery = of_property_read_bool(node, + "qcom,batteryless-platform"); + + chip->dt.no_pd = of_property_read_bool(node, + "qcom,pd-not-supported"); + + rc = of_property_read_u32(node, + "qcom,fcc-max-ua", &chg->batt_profile_fcc_ua); + if (rc < 0) + chg->batt_profile_fcc_ua = -EINVAL; + + rc = of_property_read_u32(node, + "qcom,fv-max-uv", &chg->batt_profile_fv_uv); + if (rc < 0) + chg->batt_profile_fv_uv = -EINVAL; + + rc = of_property_read_u32(node, + "qcom,usb-icl-ua", &chip->dt.usb_icl_ua); + if (rc < 0) + chip->dt.usb_icl_ua = -EINVAL; + + rc = of_property_read_u32(node, + "qcom,otg-cl-ua", &chg->otg_cl_ua); + if (rc < 0) + chg->otg_cl_ua = MICRO_1P5A; + + rc = of_property_read_u32(node, + "qcom,dc-icl-ua", &chip->dt.dc_icl_ua); + if (rc < 0) + chip->dt.dc_icl_ua = -EINVAL; + + rc = of_property_read_u32(node, + "qcom,boost-threshold-ua", + &chip->dt.boost_threshold_ua); + if (rc < 0) + chip->dt.boost_threshold_ua = MICRO_P1A; + + rc = of_property_read_u32(node, + "qcom,min-freq-khz", + &chip->dt.min_freq_khz); + if (rc < 0) + chip->dt.min_freq_khz = -EINVAL; + + rc = of_property_read_u32(node, + "qcom,max-freq-khz", + &chip->dt.max_freq_khz); + if (rc < 0) + chip->dt.max_freq_khz = -EINVAL; + + rc = of_property_read_u32(node, "qcom,wipower-max-uw", + &chip->dt.wipower_max_uw); + if (rc < 0) + chip->dt.wipower_max_uw = -EINVAL; + + if (of_find_property(node, "qcom,thermal-mitigation", &byte_len)) { + chg->thermal_mitigation = devm_kzalloc(chg->dev, byte_len, + GFP_KERNEL); + + if (chg->thermal_mitigation == NULL) + return -ENOMEM; + + chg->thermal_levels = byte_len / sizeof(u32); + rc = of_property_read_u32_array(node, + "qcom,thermal-mitigation", + chg->thermal_mitigation, + chg->thermal_levels); + if (rc < 0) { + dev_err(chg->dev, + "Couldn't read threm limits rc = %d\n", rc); + return rc; + } + } + + of_property_read_u32(node, "qcom,float-option", &chip->dt.float_option); + if (chip->dt.float_option < 0 || chip->dt.float_option > 4) { + pr_err("qcom,float-option is out of range [0, 4]\n"); + return -EINVAL; + } + + chip->dt.hvdcp_disable = of_property_read_bool(node, + "qcom,hvdcp-disable"); + + of_property_read_u32(node, "qcom,chg-inhibit-threshold-mv", + &chip->dt.chg_inhibit_thr_mv); + if ((chip->dt.chg_inhibit_thr_mv < 0 || + chip->dt.chg_inhibit_thr_mv > 300)) { + pr_err("qcom,chg-inhibit-threshold-mv is incorrect\n"); + return -EINVAL; + } + + chip->dt.auto_recharge_soc = of_property_read_bool(node, + "qcom,auto-recharge-soc"); + + chg->use_extcon = of_property_read_bool(node, + "qcom,use-extcon"); + + chg->dcp_icl_ua = chip->dt.usb_icl_ua; + + chg->suspend_input_on_debug_batt = of_property_read_bool(node, + "qcom,suspend-input-on-debug-batt"); + + rc = of_property_read_u32(node, "qcom,otg-deglitch-time-ms", + &chg->otg_delay_ms); + if (rc < 0) + chg->otg_delay_ms = OTG_DEFAULT_DEGLITCH_TIME_MS; + + chg->disable_stat_sw_override = of_property_read_bool(node, + "qcom,disable-stat-sw-override"); + + chg->fcc_stepper_enable = of_property_read_bool(node, + "qcom,fcc-stepping-enable"); + + chg->ufp_only_mode = of_property_read_bool(node, + "qcom,ufp-only-mode"); + + return 0; +} + +/************************ + * USB PSY REGISTRATION * + ************************/ + +static enum power_supply_property smb2_usb_props[] = { + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_MAX, + POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_PD_CURRENT_MAX, + POWER_SUPPLY_PROP_CURRENT_MAX, + POWER_SUPPLY_PROP_TYPE, + POWER_SUPPLY_PROP_TYPEC_MODE, + POWER_SUPPLY_PROP_TYPEC_POWER_ROLE, + POWER_SUPPLY_PROP_TYPEC_CC_ORIENTATION, + POWER_SUPPLY_PROP_PD_ALLOWED, + POWER_SUPPLY_PROP_PD_ACTIVE, + POWER_SUPPLY_PROP_INPUT_CURRENT_SETTLED, + POWER_SUPPLY_PROP_INPUT_CURRENT_NOW, + POWER_SUPPLY_PROP_BOOST_CURRENT, + POWER_SUPPLY_PROP_PE_START, + POWER_SUPPLY_PROP_CTM_CURRENT_MAX, + POWER_SUPPLY_PROP_HW_CURRENT_MAX, + POWER_SUPPLY_PROP_REAL_TYPE, + POWER_SUPPLY_PROP_PR_SWAP, + POWER_SUPPLY_PROP_PD_VOLTAGE_MAX, + POWER_SUPPLY_PROP_PD_VOLTAGE_MIN, + POWER_SUPPLY_PROP_SDP_CURRENT_MAX, + POWER_SUPPLY_PROP_CONNECTOR_TYPE, + POWER_SUPPLY_PROP_MOISTURE_DETECTED, +}; + +static int smb2_usb_get_prop(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct smb2 *chip = power_supply_get_drvdata(psy); + struct smb_charger *chg = &chip->chg; + int rc = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_PRESENT: + if (chip->bad_part) + val->intval = 1; + else + rc = smblib_get_prop_usb_present(chg, val); + break; + case POWER_SUPPLY_PROP_ONLINE: + rc = smblib_get_prop_usb_online(chg, val); + if (!val->intval) + break; + + if (((chg->typec_mode == POWER_SUPPLY_TYPEC_SOURCE_DEFAULT) + || (chg->connector_type == POWER_SUPPLY_CONNECTOR_MICRO_USB)) + && (chg->real_charger_type == POWER_SUPPLY_TYPE_USB)) + val->intval = 0; + else + val->intval = 1; + if (chg->real_charger_type == POWER_SUPPLY_TYPE_UNKNOWN) + val->intval = 0; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + rc = smblib_get_prop_usb_voltage_max(chg, val); + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: + rc = smblib_get_prop_usb_voltage_max_design(chg, val); + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + rc = smblib_get_prop_usb_voltage_now(chg, val); + break; + case POWER_SUPPLY_PROP_PD_CURRENT_MAX: + val->intval = get_client_vote(chg->usb_icl_votable, PD_VOTER); + break; + case POWER_SUPPLY_PROP_CURRENT_MAX: + rc = smblib_get_prop_input_current_settled(chg, val); + break; + case POWER_SUPPLY_PROP_TYPE: + val->intval = POWER_SUPPLY_TYPE_USB_PD; + break; + case POWER_SUPPLY_PROP_REAL_TYPE: + if (chip->bad_part) + val->intval = POWER_SUPPLY_TYPE_USB_PD; + else + val->intval = chg->real_charger_type; + break; + case POWER_SUPPLY_PROP_TYPEC_MODE: + if (chg->connector_type == POWER_SUPPLY_CONNECTOR_MICRO_USB) + val->intval = POWER_SUPPLY_TYPEC_NONE; + else if (chip->bad_part) + val->intval = POWER_SUPPLY_TYPEC_SOURCE_DEFAULT; + else + val->intval = chg->typec_mode; + break; + case POWER_SUPPLY_PROP_TYPEC_POWER_ROLE: + if (chg->connector_type == POWER_SUPPLY_CONNECTOR_MICRO_USB) + val->intval = POWER_SUPPLY_TYPEC_PR_NONE; + else + rc = smblib_get_prop_typec_power_role(chg, val); + break; + case POWER_SUPPLY_PROP_TYPEC_CC_ORIENTATION: + if (chg->connector_type == POWER_SUPPLY_CONNECTOR_MICRO_USB) + val->intval = 0; + else + rc = smblib_get_prop_typec_cc_orientation(chg, val); + break; + case POWER_SUPPLY_PROP_PD_ALLOWED: + rc = smblib_get_prop_pd_allowed(chg, val); + break; + case POWER_SUPPLY_PROP_PD_ACTIVE: + val->intval = chg->pd_active; + break; + case POWER_SUPPLY_PROP_INPUT_CURRENT_SETTLED: + rc = smblib_get_prop_input_current_settled(chg, val); + break; + case POWER_SUPPLY_PROP_INPUT_CURRENT_NOW: + rc = smblib_get_prop_usb_current_now(chg, val); + break; + case POWER_SUPPLY_PROP_BOOST_CURRENT: + val->intval = chg->boost_current_ua; + break; + case POWER_SUPPLY_PROP_PD_IN_HARD_RESET: + rc = smblib_get_prop_pd_in_hard_reset(chg, val); + break; + case POWER_SUPPLY_PROP_PD_USB_SUSPEND_SUPPORTED: + val->intval = chg->system_suspend_supported; + break; + case POWER_SUPPLY_PROP_PE_START: + rc = smblib_get_pe_start(chg, val); + break; + case POWER_SUPPLY_PROP_CTM_CURRENT_MAX: + val->intval = get_client_vote(chg->usb_icl_votable, CTM_VOTER); + break; + case POWER_SUPPLY_PROP_HW_CURRENT_MAX: + rc = smblib_get_charge_current(chg, &val->intval); + break; + case POWER_SUPPLY_PROP_PR_SWAP: + rc = smblib_get_prop_pr_swap_in_progress(chg, val); + break; + case POWER_SUPPLY_PROP_PD_VOLTAGE_MAX: + val->intval = chg->voltage_max_uv; + break; + case POWER_SUPPLY_PROP_PD_VOLTAGE_MIN: + val->intval = chg->voltage_min_uv; + break; + case POWER_SUPPLY_PROP_SDP_CURRENT_MAX: + val->intval = get_client_vote(chg->usb_icl_votable, + USB_PSY_VOTER); + break; + case POWER_SUPPLY_PROP_CONNECTOR_TYPE: + val->intval = chg->connector_type; + break; + case POWER_SUPPLY_PROP_MOISTURE_DETECTED: + val->intval = get_client_vote(chg->disable_power_role_switch, + MOISTURE_VOTER); + break; + default: + pr_err("get prop %d is not supported in usb\n", psp); + rc = -EINVAL; + break; + } + if (rc < 0) { + pr_debug("Couldn't get prop %d rc = %d\n", psp, rc); + return -ENODATA; + } + return 0; +} + +static int smb2_usb_set_prop(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct smb2 *chip = power_supply_get_drvdata(psy); + struct smb_charger *chg = &chip->chg; + int rc = 0; + + mutex_lock(&chg->lock); + if (!chg->typec_present) { + switch (psp) { + case POWER_SUPPLY_PROP_MOISTURE_DETECTED: + vote(chg->disable_power_role_switch, MOISTURE_VOTER, + val->intval > 0, 0); + break; + default: + rc = -EINVAL; + break; + } + + goto unlock; + } + + switch (psp) { + case POWER_SUPPLY_PROP_PD_CURRENT_MAX: + rc = smblib_set_prop_pd_current_max(chg, val); + break; + case POWER_SUPPLY_PROP_TYPEC_POWER_ROLE: + rc = smblib_set_prop_typec_power_role(chg, val); + break; + case POWER_SUPPLY_PROP_PD_ACTIVE: + rc = smblib_set_prop_pd_active(chg, val); + break; + case POWER_SUPPLY_PROP_PD_IN_HARD_RESET: + rc = smblib_set_prop_pd_in_hard_reset(chg, val); + break; + case POWER_SUPPLY_PROP_PD_USB_SUSPEND_SUPPORTED: + chg->system_suspend_supported = val->intval; + break; + case POWER_SUPPLY_PROP_BOOST_CURRENT: + rc = smblib_set_prop_boost_current(chg, val); + break; + case POWER_SUPPLY_PROP_CTM_CURRENT_MAX: + rc = vote(chg->usb_icl_votable, CTM_VOTER, + val->intval >= 0, val->intval); + break; + case POWER_SUPPLY_PROP_PR_SWAP: + rc = smblib_set_prop_pr_swap_in_progress(chg, val); + break; + case POWER_SUPPLY_PROP_PD_VOLTAGE_MAX: + rc = smblib_set_prop_pd_voltage_max(chg, val); + break; + case POWER_SUPPLY_PROP_PD_VOLTAGE_MIN: + rc = smblib_set_prop_pd_voltage_min(chg, val); + break; + case POWER_SUPPLY_PROP_SDP_CURRENT_MAX: + rc = smblib_set_prop_sdp_current_max(chg, val); + break; + default: + pr_err("set prop %d is not supported\n", psp); + rc = -EINVAL; + break; + } + +unlock: + mutex_unlock(&chg->lock); + return rc; +} + +static int smb2_usb_prop_is_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + switch (psp) { + case POWER_SUPPLY_PROP_CTM_CURRENT_MAX: + return 1; + default: + break; + } + + return 0; +} + +static int smb2_init_usb_psy(struct smb2 *chip) +{ + struct power_supply_config usb_cfg = {}; + struct smb_charger *chg = &chip->chg; + + chg->usb_psy_desc.name = "usb"; + chg->usb_psy_desc.type = POWER_SUPPLY_TYPE_USB_PD; + chg->usb_psy_desc.properties = smb2_usb_props; + chg->usb_psy_desc.num_properties = ARRAY_SIZE(smb2_usb_props); + chg->usb_psy_desc.get_property = smb2_usb_get_prop; + chg->usb_psy_desc.set_property = smb2_usb_set_prop; + chg->usb_psy_desc.property_is_writeable = smb2_usb_prop_is_writeable; + + usb_cfg.drv_data = chip; + usb_cfg.of_node = chg->dev->of_node; + chg->usb_psy = power_supply_register(chg->dev, + &chg->usb_psy_desc, + &usb_cfg); + if (IS_ERR(chg->usb_psy)) { + pr_err("Couldn't register USB power supply\n"); + return PTR_ERR(chg->usb_psy); + } + + return 0; +} + +/******************************** + * USB PC_PORT PSY REGISTRATION * + ********************************/ +static enum power_supply_property smb2_usb_port_props[] = { + POWER_SUPPLY_PROP_TYPE, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_MAX, + POWER_SUPPLY_PROP_CURRENT_MAX, +}; + +static int smb2_usb_port_get_prop(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct smb2 *chip = power_supply_get_drvdata(psy); + struct smb_charger *chg = &chip->chg; + int rc = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_TYPE: + val->intval = POWER_SUPPLY_TYPE_USB; + break; + case POWER_SUPPLY_PROP_ONLINE: + rc = smblib_get_prop_usb_online(chg, val); + if (!val->intval) + break; + + if (((chg->typec_mode == POWER_SUPPLY_TYPEC_SOURCE_DEFAULT) + || (chg->connector_type == POWER_SUPPLY_CONNECTOR_MICRO_USB)) + && (chg->real_charger_type == POWER_SUPPLY_TYPE_USB)) + val->intval = 1; + else + val->intval = 0; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + val->intval = 5000000; + break; + case POWER_SUPPLY_PROP_CURRENT_MAX: + rc = smblib_get_prop_input_current_settled(chg, val); + break; + default: + pr_err_ratelimited("Get prop %d is not supported in pc_port\n", + psp); + return -EINVAL; + } + + if (rc < 0) { + pr_debug("Couldn't get prop %d rc = %d\n", psp, rc); + return -ENODATA; + } + + return 0; +} + +static int smb2_usb_port_set_prop(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + int rc = 0; + + switch (psp) { + default: + pr_err_ratelimited("Set prop %d is not supported in pc_port\n", + psp); + rc = -EINVAL; + break; + } + + return rc; +} + +static const struct power_supply_desc usb_port_psy_desc = { + .name = "pc_port", + .type = POWER_SUPPLY_TYPE_USB, + .properties = smb2_usb_port_props, + .num_properties = ARRAY_SIZE(smb2_usb_port_props), + .get_property = smb2_usb_port_get_prop, + .set_property = smb2_usb_port_set_prop, +}; + +static int smb2_init_usb_port_psy(struct smb2 *chip) +{ + struct power_supply_config usb_port_cfg = {}; + struct smb_charger *chg = &chip->chg; + + usb_port_cfg.drv_data = chip; + usb_port_cfg.of_node = chg->dev->of_node; + chg->usb_port_psy = power_supply_register(chg->dev, + &usb_port_psy_desc, + &usb_port_cfg); + if (IS_ERR(chg->usb_port_psy)) { + pr_err("Couldn't register USB pc_port power supply\n"); + return PTR_ERR(chg->usb_port_psy); + } + + return 0; +} + +/***************************** + * USB MAIN PSY REGISTRATION * + *****************************/ + +static enum power_supply_property smb2_usb_main_props[] = { + POWER_SUPPLY_PROP_VOLTAGE_MAX, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, + POWER_SUPPLY_PROP_TYPE, + POWER_SUPPLY_PROP_INPUT_CURRENT_SETTLED, + POWER_SUPPLY_PROP_INPUT_VOLTAGE_SETTLED, + POWER_SUPPLY_PROP_FCC_DELTA, + POWER_SUPPLY_PROP_CURRENT_MAX, + POWER_SUPPLY_PROP_TOGGLE_STAT, + /* + * TODO move the TEMP and TEMP_MAX properties here, + * and update the thermal balancer to look here + */ +}; + +static int smb2_usb_main_get_prop(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct smb2 *chip = power_supply_get_drvdata(psy); + struct smb_charger *chg = &chip->chg; + int rc = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + rc = smblib_get_charge_param(chg, &chg->param.fv, &val->intval); + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: + rc = smblib_get_charge_param(chg, &chg->param.fcc, + &val->intval); + break; + case POWER_SUPPLY_PROP_TYPE: + val->intval = POWER_SUPPLY_TYPE_MAIN; + break; + case POWER_SUPPLY_PROP_INPUT_CURRENT_SETTLED: + rc = smblib_get_prop_input_current_settled(chg, val); + break; + case POWER_SUPPLY_PROP_INPUT_VOLTAGE_SETTLED: + rc = smblib_get_prop_input_voltage_settled(chg, val); + break; + case POWER_SUPPLY_PROP_FCC_DELTA: + rc = smblib_get_prop_fcc_delta(chg, val); + break; + case POWER_SUPPLY_PROP_CURRENT_MAX: + rc = smblib_get_icl_current(chg, &val->intval); + break; + case POWER_SUPPLY_PROP_TOGGLE_STAT: + val->intval = 0; + break; + default: + pr_debug("get prop %d is not supported in usb-main\n", psp); + rc = -EINVAL; + break; + } + if (rc < 0) { + pr_debug("Couldn't get prop %d rc = %d\n", psp, rc); + return -ENODATA; + } + return 0; +} + +static int smb2_usb_main_set_prop(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct smb2 *chip = power_supply_get_drvdata(psy); + struct smb_charger *chg = &chip->chg; + int rc = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + rc = smblib_set_charge_param(chg, &chg->param.fv, val->intval); + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: + rc = smblib_set_charge_param(chg, &chg->param.fcc, val->intval); + break; + case POWER_SUPPLY_PROP_CURRENT_MAX: + rc = smblib_set_icl_current(chg, val->intval); + break; + case POWER_SUPPLY_PROP_TOGGLE_STAT: + rc = smblib_toggle_stat(chg, val->intval); + break; + default: + pr_err("set prop %d is not supported\n", psp); + rc = -EINVAL; + break; + } + + return rc; +} + +static int smb2_usb_main_prop_is_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + int rc; + + switch (psp) { + case POWER_SUPPLY_PROP_TOGGLE_STAT: + rc = 1; + break; + default: + rc = 0; + break; + } + + return rc; +} + +static const struct power_supply_desc usb_main_psy_desc = { + .name = "main", + .type = POWER_SUPPLY_TYPE_MAIN, + .properties = smb2_usb_main_props, + .num_properties = ARRAY_SIZE(smb2_usb_main_props), + .get_property = smb2_usb_main_get_prop, + .set_property = smb2_usb_main_set_prop, + .property_is_writeable = smb2_usb_main_prop_is_writeable, +}; + +static int smb2_init_usb_main_psy(struct smb2 *chip) +{ + struct power_supply_config usb_main_cfg = {}; + struct smb_charger *chg = &chip->chg; + + usb_main_cfg.drv_data = chip; + usb_main_cfg.of_node = chg->dev->of_node; + chg->usb_main_psy = power_supply_register(chg->dev, + &usb_main_psy_desc, + &usb_main_cfg); + if (IS_ERR(chg->usb_main_psy)) { + pr_err("Couldn't register USB main power supply\n"); + return PTR_ERR(chg->usb_main_psy); + } + + return 0; +} + +/************************* + * DC PSY REGISTRATION * + *************************/ + +static enum power_supply_property smb2_dc_props[] = { + POWER_SUPPLY_PROP_INPUT_SUSPEND, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_CURRENT_MAX, + POWER_SUPPLY_PROP_REAL_TYPE, +}; + +static int smb2_dc_get_prop(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct smb2 *chip = power_supply_get_drvdata(psy); + struct smb_charger *chg = &chip->chg; + int rc = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_INPUT_SUSPEND: + val->intval = get_effective_result(chg->dc_suspend_votable); + break; + case POWER_SUPPLY_PROP_PRESENT: + rc = smblib_get_prop_dc_present(chg, val); + break; + case POWER_SUPPLY_PROP_ONLINE: + rc = smblib_get_prop_dc_online(chg, val); + break; + case POWER_SUPPLY_PROP_CURRENT_MAX: + rc = smblib_get_prop_dc_current_max(chg, val); + break; + case POWER_SUPPLY_PROP_REAL_TYPE: + val->intval = POWER_SUPPLY_TYPE_WIPOWER; + break; + default: + return -EINVAL; + } + if (rc < 0) { + pr_debug("Couldn't get prop %d rc = %d\n", psp, rc); + return -ENODATA; + } + return 0; +} + +static int smb2_dc_set_prop(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct smb2 *chip = power_supply_get_drvdata(psy); + struct smb_charger *chg = &chip->chg; + int rc = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_INPUT_SUSPEND: + rc = vote(chg->dc_suspend_votable, WBC_VOTER, + (bool)val->intval, 0); + break; + case POWER_SUPPLY_PROP_CURRENT_MAX: + rc = smblib_set_prop_dc_current_max(chg, val); + break; + default: + return -EINVAL; + } + + return rc; +} + +static int smb2_dc_prop_is_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + int rc; + + switch (psp) { + case POWER_SUPPLY_PROP_CURRENT_MAX: + rc = 1; + break; + default: + rc = 0; + break; + } + + return rc; +} + +static const struct power_supply_desc dc_psy_desc = { + .name = "dc", + .type = POWER_SUPPLY_TYPE_WIRELESS, + .properties = smb2_dc_props, + .num_properties = ARRAY_SIZE(smb2_dc_props), + .get_property = smb2_dc_get_prop, + .set_property = smb2_dc_set_prop, + .property_is_writeable = smb2_dc_prop_is_writeable, +}; + +static int smb2_init_dc_psy(struct smb2 *chip) +{ + struct power_supply_config dc_cfg = {}; + struct smb_charger *chg = &chip->chg; + + dc_cfg.drv_data = chip; + dc_cfg.of_node = chg->dev->of_node; + chg->dc_psy = power_supply_register(chg->dev, + &dc_psy_desc, + &dc_cfg); + if (IS_ERR(chg->dc_psy)) { + pr_err("Couldn't register USB power supply\n"); + return PTR_ERR(chg->dc_psy); + } + + return 0; +} + +/************************* + * BATT PSY REGISTRATION * + *************************/ + +static enum power_supply_property smb2_batt_props[] = { + POWER_SUPPLY_PROP_INPUT_SUSPEND, + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_CHARGE_TYPE, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_CHARGER_TEMP, + POWER_SUPPLY_PROP_CHARGER_TEMP_MAX, + POWER_SUPPLY_PROP_INPUT_CURRENT_LIMITED, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_VOLTAGE_MAX, + POWER_SUPPLY_PROP_VOLTAGE_QNOVO, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CURRENT_QNOVO, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_STEP_CHARGING_ENABLED, + POWER_SUPPLY_PROP_SW_JEITA_ENABLED, + POWER_SUPPLY_PROP_CHARGE_DONE, + POWER_SUPPLY_PROP_PARALLEL_DISABLE, + POWER_SUPPLY_PROP_SET_SHIP_MODE, + POWER_SUPPLY_PROP_DIE_HEALTH, + POWER_SUPPLY_PROP_RERUN_AICL, + POWER_SUPPLY_PROP_DP_DM, + POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX, + POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT, + POWER_SUPPLY_PROP_CHARGE_COUNTER, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_CYCLE_COUNT, + POWER_SUPPLY_PROP_FCC_STEPPER_ENABLE, +}; + +static int smb2_batt_get_prop(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct smb_charger *chg = power_supply_get_drvdata(psy); + int rc = 0; + union power_supply_propval pval = {0, }; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + rc = smblib_get_prop_batt_status(chg, val); + break; + case POWER_SUPPLY_PROP_HEALTH: + rc = smblib_get_prop_batt_health(chg, val); + break; + case POWER_SUPPLY_PROP_PRESENT: + rc = smblib_get_prop_batt_present(chg, val); + break; + case POWER_SUPPLY_PROP_INPUT_SUSPEND: + rc = smblib_get_prop_input_suspend(chg, val); + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + rc = smblib_get_prop_batt_charge_type(chg, val); + break; + case POWER_SUPPLY_PROP_CAPACITY: + rc = smblib_get_prop_batt_capacity(chg, val); + break; + case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT: + rc = smblib_get_prop_system_temp_level(chg, val); + break; + case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX: + rc = smblib_get_prop_system_temp_level_max(chg, val); + break; + case POWER_SUPPLY_PROP_CHARGER_TEMP: + /* do not query RRADC if charger is not present */ + rc = smblib_get_prop_usb_present(chg, &pval); + if (rc < 0) + pr_err("Couldn't get usb present rc=%d\n", rc); + + rc = -ENODATA; + if (pval.intval) + rc = smblib_get_prop_charger_temp(chg, val); + break; + case POWER_SUPPLY_PROP_CHARGER_TEMP_MAX: + rc = smblib_get_prop_charger_temp_max(chg, val); + break; + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMITED: + rc = smblib_get_prop_input_current_limited(chg, val); + break; + case POWER_SUPPLY_PROP_STEP_CHARGING_ENABLED: + val->intval = chg->step_chg_enabled; + break; + case POWER_SUPPLY_PROP_SW_JEITA_ENABLED: + val->intval = chg->sw_jeita_enabled; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + val->intval = get_client_vote(chg->fv_votable, + BATT_PROFILE_VOTER); + break; + case POWER_SUPPLY_PROP_CHARGE_QNOVO_ENABLE: + rc = smblib_get_prop_charge_qnovo_enable(chg, val); + break; + case POWER_SUPPLY_PROP_VOLTAGE_QNOVO: + val->intval = get_client_vote_locked(chg->fv_votable, + QNOVO_VOTER); + break; + case POWER_SUPPLY_PROP_CURRENT_QNOVO: + val->intval = get_client_vote_locked(chg->fcc_votable, + QNOVO_VOTER); + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: + val->intval = get_client_vote(chg->fcc_votable, + BATT_PROFILE_VOTER); + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: + val->intval = get_client_vote(chg->fcc_votable, + FG_ESR_VOTER); + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = POWER_SUPPLY_TECHNOLOGY_LION; + break; + case POWER_SUPPLY_PROP_CHARGE_DONE: + rc = smblib_get_prop_batt_charge_done(chg, val); + break; + case POWER_SUPPLY_PROP_PARALLEL_DISABLE: + val->intval = get_client_vote(chg->pl_disable_votable, + USER_VOTER); + break; + case POWER_SUPPLY_PROP_SET_SHIP_MODE: + /* Not in ship mode as long as device is active */ + val->intval = 0; + break; + case POWER_SUPPLY_PROP_DIE_HEALTH: + if (chg->die_health == -EINVAL) + rc = smblib_get_prop_die_health(chg, val); + else + val->intval = chg->die_health; + break; + case POWER_SUPPLY_PROP_DP_DM: + val->intval = chg->pulse_cnt; + break; + case POWER_SUPPLY_PROP_RERUN_AICL: + val->intval = 0; + break; + case POWER_SUPPLY_PROP_CHARGE_COUNTER: + case POWER_SUPPLY_PROP_CHARGE_FULL: + case POWER_SUPPLY_PROP_CYCLE_COUNT: + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + case POWER_SUPPLY_PROP_TEMP: + rc = smblib_get_prop_from_bms(chg, psp, val); + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + rc = smblib_get_prop_from_bms(chg, psp, val); + if (!rc) + val->intval *= (-1); + break; + case POWER_SUPPLY_PROP_FCC_STEPPER_ENABLE: + val->intval = chg->fcc_stepper_enable; + break; + default: + pr_err("batt power supply prop %d not supported\n", psp); + return -EINVAL; + } + + if (rc < 0) { + pr_debug("Couldn't get prop %d rc = %d\n", psp, rc); + return -ENODATA; + } + + return 0; +} + +static int smb2_batt_set_prop(struct power_supply *psy, + enum power_supply_property prop, + const union power_supply_propval *val) +{ + int rc = 0; + struct smb_charger *chg = power_supply_get_drvdata(psy); + + switch (prop) { + case POWER_SUPPLY_PROP_STATUS: + rc = smblib_set_prop_batt_status(chg, val); + break; + case POWER_SUPPLY_PROP_INPUT_SUSPEND: + rc = smblib_set_prop_input_suspend(chg, val); + break; + case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT: + rc = smblib_set_prop_system_temp_level(chg, val); + break; + case POWER_SUPPLY_PROP_CAPACITY: + rc = smblib_set_prop_batt_capacity(chg, val); + break; + case POWER_SUPPLY_PROP_PARALLEL_DISABLE: + vote(chg->pl_disable_votable, USER_VOTER, (bool)val->intval, 0); + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + chg->batt_profile_fv_uv = val->intval; + vote(chg->fv_votable, BATT_PROFILE_VOTER, true, val->intval); + break; + case POWER_SUPPLY_PROP_CHARGE_QNOVO_ENABLE: + rc = smblib_set_prop_charge_qnovo_enable(chg, val); + break; + case POWER_SUPPLY_PROP_VOLTAGE_QNOVO: + vote(chg->fv_votable, QNOVO_VOTER, + (val->intval >= 0), val->intval); + break; + case POWER_SUPPLY_PROP_CURRENT_QNOVO: + vote(chg->pl_disable_votable, PL_QNOVO_VOTER, + val->intval != -EINVAL && val->intval < 2000000, 0); + if (val->intval == -EINVAL) { + vote(chg->fcc_votable, BATT_PROFILE_VOTER, + true, chg->batt_profile_fcc_ua); + vote(chg->fcc_votable, QNOVO_VOTER, false, 0); + } else { + vote(chg->fcc_votable, QNOVO_VOTER, true, val->intval); + vote(chg->fcc_votable, BATT_PROFILE_VOTER, false, 0); + } + break; + case POWER_SUPPLY_PROP_STEP_CHARGING_ENABLED: + chg->step_chg_enabled = !!val->intval; + break; + case POWER_SUPPLY_PROP_SW_JEITA_ENABLED: + if (chg->sw_jeita_enabled != (!!val->intval)) { + rc = smblib_disable_hw_jeita(chg, !!val->intval); + if (rc == 0) + chg->sw_jeita_enabled = !!val->intval; + } + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: + chg->batt_profile_fcc_ua = val->intval; + vote(chg->fcc_votable, BATT_PROFILE_VOTER, true, val->intval); + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: + if (val->intval) + vote(chg->fcc_votable, FG_ESR_VOTER, true, val->intval); + else + vote(chg->fcc_votable, FG_ESR_VOTER, false, 0); + break; + case POWER_SUPPLY_PROP_SET_SHIP_MODE: + /* Not in ship mode as long as the device is active */ + if (!val->intval) + break; + if (chg->pl.psy) + power_supply_set_property(chg->pl.psy, + POWER_SUPPLY_PROP_SET_SHIP_MODE, val); + rc = smblib_set_prop_ship_mode(chg, val); + break; + case POWER_SUPPLY_PROP_RERUN_AICL: + rc = smblib_rerun_aicl(chg); + break; + case POWER_SUPPLY_PROP_DP_DM: + rc = smblib_dp_dm(chg, val->intval); + break; + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMITED: + rc = smblib_set_prop_input_current_limited(chg, val); + break; + case POWER_SUPPLY_PROP_DIE_HEALTH: + chg->die_health = val->intval; + power_supply_changed(chg->batt_psy); + break; + default: + rc = -EINVAL; + } + + return rc; +} + +static int smb2_batt_prop_is_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + case POWER_SUPPLY_PROP_INPUT_SUSPEND: + case POWER_SUPPLY_PROP_SYSTEM_TEMP_LEVEL: + case POWER_SUPPLY_PROP_CAPACITY: + case POWER_SUPPLY_PROP_PARALLEL_DISABLE: + case POWER_SUPPLY_PROP_DP_DM: + case POWER_SUPPLY_PROP_RERUN_AICL: + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMITED: + case POWER_SUPPLY_PROP_STEP_CHARGING_ENABLED: + case POWER_SUPPLY_PROP_SW_JEITA_ENABLED: + case POWER_SUPPLY_PROP_DIE_HEALTH: + return 1; + default: + break; + } + + return 0; +} + +static const struct power_supply_desc batt_psy_desc = { + .name = "battery", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = smb2_batt_props, + .num_properties = ARRAY_SIZE(smb2_batt_props), + .get_property = smb2_batt_get_prop, + .set_property = smb2_batt_set_prop, + .property_is_writeable = smb2_batt_prop_is_writeable, +}; + +static int smb2_init_batt_psy(struct smb2 *chip) +{ + struct power_supply_config batt_cfg = {}; + struct smb_charger *chg = &chip->chg; + int rc = 0; + + batt_cfg.drv_data = chg; + batt_cfg.of_node = chg->dev->of_node; + chg->batt_psy = power_supply_register(chg->dev, + &batt_psy_desc, + &batt_cfg); + if (IS_ERR(chg->batt_psy)) { + pr_err("Couldn't register battery power supply\n"); + return PTR_ERR(chg->batt_psy); + } + + return rc; +} + +/****************************** + * VBUS REGULATOR REGISTRATION * + ******************************/ + +static struct regulator_ops smb2_vbus_reg_ops = { + .enable = smblib_vbus_regulator_enable, + .disable = smblib_vbus_regulator_disable, + .is_enabled = smblib_vbus_regulator_is_enabled, +}; + +static int smb2_init_vbus_regulator(struct smb2 *chip) +{ + struct smb_charger *chg = &chip->chg; + struct regulator_config cfg = {}; + int rc = 0; + + chg->vbus_vreg = devm_kzalloc(chg->dev, sizeof(*chg->vbus_vreg), + GFP_KERNEL); + if (!chg->vbus_vreg) + return -ENOMEM; + + cfg.dev = chg->dev; + cfg.driver_data = chip; + + chg->vbus_vreg->rdesc.owner = THIS_MODULE; + chg->vbus_vreg->rdesc.type = REGULATOR_VOLTAGE; + chg->vbus_vreg->rdesc.ops = &smb2_vbus_reg_ops; + chg->vbus_vreg->rdesc.of_match = "qcom,smb2-vbus"; + chg->vbus_vreg->rdesc.name = "qcom,smb2-vbus"; + + chg->vbus_vreg->rdev = devm_regulator_register(chg->dev, + &chg->vbus_vreg->rdesc, &cfg); + if (IS_ERR(chg->vbus_vreg->rdev)) { + rc = PTR_ERR(chg->vbus_vreg->rdev); + chg->vbus_vreg->rdev = NULL; + if (rc != -EPROBE_DEFER) + pr_err("Couldn't register VBUS regualtor rc=%d\n", rc); + } + + return rc; +} + +/****************************** + * VCONN REGULATOR REGISTRATION * + ******************************/ + +static struct regulator_ops smb2_vconn_reg_ops = { + .enable = smblib_vconn_regulator_enable, + .disable = smblib_vconn_regulator_disable, + .is_enabled = smblib_vconn_regulator_is_enabled, +}; + +static int smb2_init_vconn_regulator(struct smb2 *chip) +{ + struct smb_charger *chg = &chip->chg; + struct regulator_config cfg = {}; + int rc = 0; + + if (chg->connector_type == POWER_SUPPLY_CONNECTOR_MICRO_USB) + return 0; + + chg->vconn_vreg = devm_kzalloc(chg->dev, sizeof(*chg->vconn_vreg), + GFP_KERNEL); + if (!chg->vconn_vreg) + return -ENOMEM; + + cfg.dev = chg->dev; + cfg.driver_data = chip; + + chg->vconn_vreg->rdesc.owner = THIS_MODULE; + chg->vconn_vreg->rdesc.type = REGULATOR_VOLTAGE; + chg->vconn_vreg->rdesc.ops = &smb2_vconn_reg_ops; + chg->vconn_vreg->rdesc.of_match = "qcom,smb2-vconn"; + chg->vconn_vreg->rdesc.name = "qcom,smb2-vconn"; + + chg->vconn_vreg->rdev = devm_regulator_register(chg->dev, + &chg->vconn_vreg->rdesc, &cfg); + if (IS_ERR(chg->vconn_vreg->rdev)) { + rc = PTR_ERR(chg->vconn_vreg->rdev); + chg->vconn_vreg->rdev = NULL; + if (rc != -EPROBE_DEFER) + pr_err("Couldn't register VCONN regualtor rc=%d\n", rc); + } + + return rc; +} + +/*************************** + * HARDWARE INITIALIZATION * + ***************************/ +static int smb2_config_wipower_input_power(struct smb2 *chip, int uw) +{ + int rc; + int ua; + struct smb_charger *chg = &chip->chg; + s64 nw = (s64)uw * 1000; + + if (uw < 0) + return 0; + + ua = div_s64(nw, ZIN_ICL_PT_MAX_MV); + rc = smblib_set_charge_param(chg, &chg->param.dc_icl_pt_lv, ua); + if (rc < 0) { + pr_err("Couldn't configure dc_icl_pt_lv rc = %d\n", rc); + return rc; + } + + ua = div_s64(nw, ZIN_ICL_PT_HV_MAX_MV); + rc = smblib_set_charge_param(chg, &chg->param.dc_icl_pt_hv, ua); + if (rc < 0) { + pr_err("Couldn't configure dc_icl_pt_hv rc = %d\n", rc); + return rc; + } + + ua = div_s64(nw, ZIN_ICL_LV_MAX_MV); + rc = smblib_set_charge_param(chg, &chg->param.dc_icl_div2_lv, ua); + if (rc < 0) { + pr_err("Couldn't configure dc_icl_div2_lv rc = %d\n", rc); + return rc; + } + + ua = div_s64(nw, ZIN_ICL_MID_LV_MAX_MV); + rc = smblib_set_charge_param(chg, &chg->param.dc_icl_div2_mid_lv, ua); + if (rc < 0) { + pr_err("Couldn't configure dc_icl_div2_mid_lv rc = %d\n", rc); + return rc; + } + + ua = div_s64(nw, ZIN_ICL_MID_HV_MAX_MV); + rc = smblib_set_charge_param(chg, &chg->param.dc_icl_div2_mid_hv, ua); + if (rc < 0) { + pr_err("Couldn't configure dc_icl_div2_mid_hv rc = %d\n", rc); + return rc; + } + + ua = div_s64(nw, ZIN_ICL_HV_MAX_MV); + rc = smblib_set_charge_param(chg, &chg->param.dc_icl_div2_hv, ua); + if (rc < 0) { + pr_err("Couldn't configure dc_icl_div2_hv rc = %d\n", rc); + return rc; + } + + return 0; +} + +static int smb2_configure_typec(struct smb_charger *chg) +{ + int rc; + + /* + * trigger the usb-typec-change interrupt only when the CC state + * changes + */ + rc = smblib_write(chg, TYPE_C_INTRPT_ENB_REG, + TYPEC_CCSTATE_CHANGE_INT_EN_BIT); + if (rc < 0) { + dev_err(chg->dev, + "Couldn't configure Type-C interrupts rc=%d\n", rc); + return rc; + } + + /* + * disable Type-C factory mode and stay in Attached.SRC state when VCONN + * over-current happens + */ + rc = smblib_masked_write(chg, TYPE_C_CFG_REG, + FACTORY_MODE_DETECTION_EN_BIT | VCONN_OC_CFG_BIT, 0); + if (rc < 0) { + dev_err(chg->dev, "Couldn't configure Type-C rc=%d\n", rc); + return rc; + } + + /* increase VCONN softstart */ + rc = smblib_masked_write(chg, TYPE_C_CFG_2_REG, + VCONN_SOFTSTART_CFG_MASK, VCONN_SOFTSTART_CFG_MASK); + if (rc < 0) { + dev_err(chg->dev, "Couldn't increase VCONN softstart rc=%d\n", + rc); + return rc; + } + + /* disable try.SINK mode and legacy cable IRQs */ + rc = smblib_masked_write(chg, TYPE_C_CFG_3_REG, EN_TRYSINK_MODE_BIT | + TYPEC_NONCOMPLIANT_LEGACY_CABLE_INT_EN_BIT | + TYPEC_LEGACY_CABLE_INT_EN_BIT, 0); + if (rc < 0) { + dev_err(chg->dev, "Couldn't set Type-C config rc=%d\n", rc); + return rc; + } + + /* Set CC threshold to 1.6 V in source mode */ + rc = smblib_masked_write(chg, TYPE_C_CFG_2_REG, DFP_CC_1P4V_OR_1P6V_BIT, + DFP_CC_1P4V_OR_1P6V_BIT); + if (rc < 0) + dev_err(chg->dev, + "Couldn't configure CC threshold voltage rc=%d\n", rc); + + return rc; +} + +static int smb2_disable_typec(struct smb_charger *chg) +{ + int rc; + + /* Move to typeC mode */ + /* configure FSM in idle state and disable UFP_ENABLE bit */ + rc = smblib_masked_write(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG, + TYPEC_DISABLE_CMD_BIT | UFP_EN_CMD_BIT, + TYPEC_DISABLE_CMD_BIT); + if (rc < 0) { + dev_err(chg->dev, "Couldn't put FSM in idle rc=%d\n", rc); + return rc; + } + + /* wait for FSM to enter idle state */ + msleep(200); + /* configure TypeC mode */ + rc = smblib_masked_write(chg, TYPE_C_CFG_REG, + TYPE_C_OR_U_USB_BIT, 0); + if (rc < 0) { + dev_err(chg->dev, "Couldn't enable micro USB mode rc=%d\n", rc); + return rc; + } + + /* wait for mode change before enabling FSM */ + usleep_range(10000, 11000); + /* release FSM from idle state */ + rc = smblib_masked_write(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG, + TYPEC_DISABLE_CMD_BIT, 0); + if (rc < 0) { + dev_err(chg->dev, "Couldn't release FSM rc=%d\n", rc); + return rc; + } + + /* wait for FSM to start */ + msleep(100); + /* move to uUSB mode */ + /* configure FSM in idle state */ + rc = smblib_masked_write(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG, + TYPEC_DISABLE_CMD_BIT, TYPEC_DISABLE_CMD_BIT); + if (rc < 0) { + dev_err(chg->dev, "Couldn't put FSM in idle rc=%d\n", rc); + return rc; + } + + /* wait for FSM to enter idle state */ + msleep(200); + /* configure micro USB mode */ + rc = smblib_masked_write(chg, TYPE_C_CFG_REG, + TYPE_C_OR_U_USB_BIT, TYPE_C_OR_U_USB_BIT); + if (rc < 0) { + dev_err(chg->dev, "Couldn't enable micro USB mode rc=%d\n", rc); + return rc; + } + + /* wait for mode change before enabling FSM */ + usleep_range(10000, 11000); + /* release FSM from idle state */ + rc = smblib_masked_write(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG, + TYPEC_DISABLE_CMD_BIT, 0); + if (rc < 0) { + dev_err(chg->dev, "Couldn't release FSM rc=%d\n", rc); + return rc; + } + + return rc; +} + +static int smb2_init_hw(struct smb2 *chip) +{ + struct smb_charger *chg = &chip->chg; + int rc; + u8 stat, val; + + if (chip->dt.no_battery) + chg->fake_capacity = 50; + + if (chg->batt_profile_fcc_ua < 0) + smblib_get_charge_param(chg, &chg->param.fcc, + &chg->batt_profile_fcc_ua); + + if (chg->batt_profile_fv_uv < 0) + smblib_get_charge_param(chg, &chg->param.fv, + &chg->batt_profile_fv_uv); + + smblib_get_charge_param(chg, &chg->param.usb_icl, + &chg->default_icl_ua); + if (chip->dt.usb_icl_ua < 0) + chip->dt.usb_icl_ua = chg->default_icl_ua; + + if (chip->dt.dc_icl_ua < 0) + smblib_get_charge_param(chg, &chg->param.dc_icl, + &chip->dt.dc_icl_ua); + + if (chip->dt.min_freq_khz > 0) { + chg->param.freq_buck.min_u = chip->dt.min_freq_khz; + chg->param.freq_boost.min_u = chip->dt.min_freq_khz; + } + + if (chip->dt.max_freq_khz > 0) { + chg->param.freq_buck.max_u = chip->dt.max_freq_khz; + chg->param.freq_boost.max_u = chip->dt.max_freq_khz; + } + + /* set a slower soft start setting for OTG */ + rc = smblib_masked_write(chg, DC_ENG_SSUPPLY_CFG2_REG, + ENG_SSUPPLY_IVREF_OTG_SS_MASK, OTG_SS_SLOW); + if (rc < 0) { + pr_err("Couldn't set otg soft start rc=%d\n", rc); + return rc; + } + + /* set OTG current limit */ + rc = smblib_set_charge_param(chg, &chg->param.otg_cl, + (chg->wa_flags & OTG_WA) ? + chg->param.otg_cl.min_u : chg->otg_cl_ua); + if (rc < 0) { + pr_err("Couldn't set otg current limit rc=%d\n", rc); + return rc; + } + + chg->boost_threshold_ua = chip->dt.boost_threshold_ua; + + rc = smblib_read(chg, APSD_RESULT_STATUS_REG, &stat); + if (rc < 0) { + pr_err("Couldn't read APSD_RESULT_STATUS rc=%d\n", rc); + return rc; + } + + smblib_rerun_apsd_if_required(chg); + + /* clear the ICL override if it is set */ + if (smblib_icl_override(chg, false) < 0) { + pr_err("Couldn't disable ICL override rc=%d\n", rc); + return rc; + } + + /* votes must be cast before configuring software control */ + /* vote 0mA on usb_icl for non battery platforms */ + vote(chg->usb_icl_votable, + DEFAULT_VOTER, chip->dt.no_battery, 0); + vote(chg->dc_suspend_votable, + DEFAULT_VOTER, chip->dt.no_battery, 0); + vote(chg->fcc_votable, + BATT_PROFILE_VOTER, true, chg->batt_profile_fcc_ua); + vote(chg->fv_votable, + BATT_PROFILE_VOTER, true, chg->batt_profile_fv_uv); + vote(chg->dc_icl_votable, + DEFAULT_VOTER, true, chip->dt.dc_icl_ua); + vote(chg->hvdcp_disable_votable_indirect, DEFAULT_VOTER, + chip->dt.hvdcp_disable, 0); + vote(chg->pd_disallowed_votable_indirect, CC_DETACHED_VOTER, + true, 0); + vote(chg->pd_disallowed_votable_indirect, HVDCP_TIMEOUT_VOTER, + true, 0); + vote(chg->pd_disallowed_votable_indirect, PD_NOT_SUPPORTED_VOTER, + chip->dt.no_pd, 0); + /* + * AICL configuration: + * start from min and AICL ADC disable + */ + rc = smblib_masked_write(chg, USBIN_AICL_OPTIONS_CFG_REG, + USBIN_AICL_START_AT_MAX_BIT + | USBIN_AICL_ADC_EN_BIT, 0); + if (rc < 0) { + dev_err(chg->dev, "Couldn't configure AICL rc=%d\n", rc); + return rc; + } + + /* Configure charge enable for software control; active high */ + rc = smblib_masked_write(chg, CHGR_CFG2_REG, + CHG_EN_POLARITY_BIT | + CHG_EN_SRC_BIT, 0); + if (rc < 0) { + dev_err(chg->dev, "Couldn't configure charger rc=%d\n", rc); + return rc; + } + + /* enable the charging path */ + rc = vote(chg->chg_disable_votable, DEFAULT_VOTER, false, 0); + if (rc < 0) { + dev_err(chg->dev, "Couldn't enable charging rc=%d\n", rc); + return rc; + } + + /* Check USB connector type (typeC/microUSB) */ + rc = smblib_read(chg, RID_CC_CONTROL_7_0_REG, &val); + if (rc < 0) { + dev_err(chg->dev, "Couldn't read RID_CC_CONTROL_7_0 rc=%d\n", + rc); + return rc; + } + chg->connector_type = (val & EN_MICRO_USB_MODE_BIT) ? + POWER_SUPPLY_CONNECTOR_MICRO_USB + : POWER_SUPPLY_CONNECTOR_TYPEC; + if (chg->connector_type == POWER_SUPPLY_CONNECTOR_MICRO_USB) + rc = smb2_disable_typec(chg); + else + rc = smb2_configure_typec(chg); + if (rc < 0) { + dev_err(chg->dev, + "Couldn't configure Type-C interrupts rc=%d\n", rc); + return rc; + } + + /* Connector types based votes */ + vote(chg->hvdcp_disable_votable_indirect, PD_INACTIVE_VOTER, + (chg->connector_type == POWER_SUPPLY_CONNECTOR_TYPEC), 0); + vote(chg->hvdcp_disable_votable_indirect, VBUS_CC_SHORT_VOTER, + (chg->connector_type == POWER_SUPPLY_CONNECTOR_TYPEC), 0); + vote(chg->pd_disallowed_votable_indirect, MICRO_USB_VOTER, + (chg->connector_type == POWER_SUPPLY_CONNECTOR_MICRO_USB), 0); + vote(chg->hvdcp_enable_votable, MICRO_USB_VOTER, + (chg->connector_type == POWER_SUPPLY_CONNECTOR_MICRO_USB), 0); + + /* configure VCONN for software control */ + rc = smblib_masked_write(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG, + VCONN_EN_SRC_BIT | VCONN_EN_VALUE_BIT, + VCONN_EN_SRC_BIT); + if (rc < 0) { + dev_err(chg->dev, + "Couldn't configure VCONN for SW control rc=%d\n", rc); + return rc; + } + + /* configure VBUS for software control */ + rc = smblib_masked_write(chg, OTG_CFG_REG, OTG_EN_SRC_CFG_BIT, 0); + if (rc < 0) { + dev_err(chg->dev, + "Couldn't configure VBUS for SW control rc=%d\n", rc); + return rc; + } + + val = (ilog2(chip->dt.wd_bark_time / 16) << BARK_WDOG_TIMEOUT_SHIFT) & + BARK_WDOG_TIMEOUT_MASK; + val |= BITE_WDOG_TIMEOUT_8S; + rc = smblib_masked_write(chg, SNARL_BARK_BITE_WD_CFG_REG, + BITE_WDOG_DISABLE_CHARGING_CFG_BIT | + BARK_WDOG_TIMEOUT_MASK | BITE_WDOG_TIMEOUT_MASK, + val); + if (rc) { + pr_err("Couldn't configue WD config rc=%d\n", rc); + return rc; + } + + /* enable WD BARK and enable it on plugin */ + rc = smblib_masked_write(chg, WD_CFG_REG, + WATCHDOG_TRIGGER_AFP_EN_BIT | + WDOG_TIMER_EN_ON_PLUGIN_BIT | + BARK_WDOG_INT_EN_BIT, + WDOG_TIMER_EN_ON_PLUGIN_BIT | + BARK_WDOG_INT_EN_BIT); + if (rc) { + pr_err("Couldn't configue WD config rc=%d\n", rc); + return rc; + } + + /* configure wipower watts */ + rc = smb2_config_wipower_input_power(chip, chip->dt.wipower_max_uw); + if (rc < 0) { + dev_err(chg->dev, "Couldn't configure wipower rc=%d\n", rc); + return rc; + } + + /* disable h/w autonomous parallel charging control */ + rc = smblib_masked_write(chg, MISC_CFG_REG, + STAT_PARALLEL_1400MA_EN_CFG_BIT, 0); + if (rc < 0) { + dev_err(chg->dev, + "Couldn't disable h/w autonomous parallel control rc=%d\n", + rc); + return rc; + } + + /* + * allow DRP.DFP time to exceed by tPDdebounce time. + */ + rc = smblib_masked_write(chg, TAPER_TIMER_SEL_CFG_REG, + TYPEC_DRP_DFP_TIME_CFG_BIT, + TYPEC_DRP_DFP_TIME_CFG_BIT); + if (rc < 0) { + dev_err(chg->dev, "Couldn't configure DRP.DFP time rc=%d\n", + rc); + return rc; + } + + /* configure float charger options */ + switch (chip->dt.float_option) { + case 1: + rc = smblib_masked_write(chg, USBIN_OPTIONS_2_CFG_REG, + FLOAT_OPTIONS_MASK, 0); + break; + case 2: + rc = smblib_masked_write(chg, USBIN_OPTIONS_2_CFG_REG, + FLOAT_OPTIONS_MASK, FORCE_FLOAT_SDP_CFG_BIT); + break; + case 3: + rc = smblib_masked_write(chg, USBIN_OPTIONS_2_CFG_REG, + FLOAT_OPTIONS_MASK, FLOAT_DIS_CHGING_CFG_BIT); + break; + case 4: + rc = smblib_masked_write(chg, USBIN_OPTIONS_2_CFG_REG, + FLOAT_OPTIONS_MASK, SUSPEND_FLOAT_CFG_BIT); + break; + default: + rc = 0; + break; + } + + if (rc < 0) { + dev_err(chg->dev, "Couldn't configure float charger options rc=%d\n", + rc); + return rc; + } + + rc = smblib_read(chg, USBIN_OPTIONS_2_CFG_REG, &chg->float_cfg); + if (rc < 0) { + dev_err(chg->dev, "Couldn't read float charger options rc=%d\n", + rc); + return rc; + } + + switch (chip->dt.chg_inhibit_thr_mv) { + case 50: + rc = smblib_masked_write(chg, CHARGE_INHIBIT_THRESHOLD_CFG_REG, + CHARGE_INHIBIT_THRESHOLD_MASK, + CHARGE_INHIBIT_THRESHOLD_50MV); + break; + case 100: + rc = smblib_masked_write(chg, CHARGE_INHIBIT_THRESHOLD_CFG_REG, + CHARGE_INHIBIT_THRESHOLD_MASK, + CHARGE_INHIBIT_THRESHOLD_100MV); + break; + case 200: + rc = smblib_masked_write(chg, CHARGE_INHIBIT_THRESHOLD_CFG_REG, + CHARGE_INHIBIT_THRESHOLD_MASK, + CHARGE_INHIBIT_THRESHOLD_200MV); + break; + case 300: + rc = smblib_masked_write(chg, CHARGE_INHIBIT_THRESHOLD_CFG_REG, + CHARGE_INHIBIT_THRESHOLD_MASK, + CHARGE_INHIBIT_THRESHOLD_300MV); + break; + case 0: + rc = smblib_masked_write(chg, CHGR_CFG2_REG, + CHARGER_INHIBIT_BIT, 0); + default: + break; + } + + if (rc < 0) { + dev_err(chg->dev, "Couldn't configure charge inhibit threshold rc=%d\n", + rc); + return rc; + } + + if (chip->dt.auto_recharge_soc) { + rc = smblib_masked_write(chg, FG_UPDATE_CFG_2_SEL_REG, + SOC_LT_CHG_RECHARGE_THRESH_SEL_BIT | + VBT_LT_CHG_RECHARGE_THRESH_SEL_BIT, + VBT_LT_CHG_RECHARGE_THRESH_SEL_BIT); + if (rc < 0) { + dev_err(chg->dev, "Couldn't configure FG_UPDATE_CFG2_SEL_REG rc=%d\n", + rc); + return rc; + } + } else { + rc = smblib_masked_write(chg, FG_UPDATE_CFG_2_SEL_REG, + SOC_LT_CHG_RECHARGE_THRESH_SEL_BIT | + VBT_LT_CHG_RECHARGE_THRESH_SEL_BIT, + SOC_LT_CHG_RECHARGE_THRESH_SEL_BIT); + if (rc < 0) { + dev_err(chg->dev, "Couldn't configure FG_UPDATE_CFG2_SEL_REG rc=%d\n", + rc); + return rc; + } + } + + if (chg->sw_jeita_enabled) { + rc = smblib_disable_hw_jeita(chg, true); + if (rc < 0) { + dev_err(chg->dev, "Couldn't set hw jeita rc=%d\n", rc); + return rc; + } + } + + if (chg->disable_stat_sw_override) { + rc = smblib_masked_write(chg, STAT_CFG_REG, + STAT_SW_OVERRIDE_CFG_BIT, 0); + if (rc < 0) { + dev_err(chg->dev, "Couldn't disable STAT SW override rc=%d\n", + rc); + return rc; + } + } + + return rc; +} + +static int smb2_post_init(struct smb2 *chip) +{ + struct smb_charger *chg = &chip->chg; + int rc; + u8 stat; + + /* In case the usb path is suspended, we would have missed disabling + * the icl change interrupt because the interrupt could have been + * not requested + */ + rerun_election(chg->usb_icl_votable); + + /* Force charger in Sink Only mode */ + if (chg->ufp_only_mode) { + rc = smblib_read(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG, + &stat); + if (rc < 0) { + dev_err(chg->dev, + "Couldn't read SOFTWARE_CTRL_REG rc=%d\n", rc); + return rc; + } + + if (!(stat & UFP_EN_CMD_BIT)) { + /* configure charger in UFP only mode */ + rc = smblib_force_ufp(chg); + if (rc < 0) { + dev_err(chg->dev, + "Couldn't force UFP mode rc=%d\n", rc); + return rc; + } + } + } else { + /* configure power role for dual-role */ + rc = smblib_masked_write(chg, + TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG, + TYPEC_POWER_ROLE_CMD_MASK, 0); + if (rc < 0) { + dev_err(chg->dev, + "Couldn't configure power role for DRP rc=%d\n", + rc); + return rc; + } + } + + rerun_election(chg->usb_irq_enable_votable); + + return 0; +} + +static int smb2_chg_config_init(struct smb2 *chip) +{ + struct smb_charger *chg = &chip->chg; + struct pmic_revid_data *pmic_rev_id; + struct device_node *revid_dev_node; + + revid_dev_node = of_parse_phandle(chip->chg.dev->of_node, + "qcom,pmic-revid", 0); + if (!revid_dev_node) { + pr_err("Missing qcom,pmic-revid property\n"); + return -EINVAL; + } + + pmic_rev_id = get_revid_data(revid_dev_node); + 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; + } + + switch (pmic_rev_id->pmic_subtype) { + case PMI8998_SUBTYPE: + chip->chg.chg_param.smb_version = PMI8998_SUBTYPE; + chip->chg.wa_flags |= BOOST_BACK_WA | QC_AUTH_INTERRUPT_WA_BIT + | TYPEC_PBS_WA_BIT; + if (pmic_rev_id->rev4 == PMI8998_V1P1_REV4) /* PMI rev 1.1 */ + chg->wa_flags |= QC_CHARGER_DETECTION_WA_BIT; + if (pmic_rev_id->rev4 == PMI8998_V2P0_REV4) /* PMI rev 2.0 */ + chg->wa_flags |= TYPEC_CC2_REMOVAL_WA_BIT; + chg->chg_freq.freq_5V = 600; + chg->chg_freq.freq_6V_8V = 800; + chg->chg_freq.freq_9V = 1000; + chg->chg_freq.freq_12V = 1200; + chg->chg_freq.freq_removal = 1000; + chg->chg_freq.freq_below_otg_threshold = 2000; + chg->chg_freq.freq_above_otg_threshold = 800; + break; + case PM660_SUBTYPE: + chip->chg.chg_param.smb_version = PM660_SUBTYPE; + chip->chg.wa_flags |= BOOST_BACK_WA | OTG_WA | OV_IRQ_WA_BIT + | TYPEC_PBS_WA_BIT; + chg->param.freq_buck = pm660_params.freq_buck; + chg->param.freq_boost = pm660_params.freq_boost; + chg->chg_freq.freq_5V = 650; + chg->chg_freq.freq_6V_8V = 850; + chg->chg_freq.freq_9V = 1050; + chg->chg_freq.freq_12V = 1200; + chg->chg_freq.freq_removal = 1050; + chg->chg_freq.freq_below_otg_threshold = 1600; + chg->chg_freq.freq_above_otg_threshold = 800; + break; + default: + pr_err("PMIC subtype %d not supported\n", + pmic_rev_id->pmic_subtype); + return -EINVAL; + } + + return 0; +} + +/**************************** + * DETERMINE INITIAL STATUS * + ****************************/ + +static int smb2_determine_initial_status(struct smb2 *chip) +{ + struct smb_irq_data irq_data = {chip, "determine-initial-status"}; + struct smb_charger *chg = &chip->chg; + + if (chg->bms_psy) + smblib_suspend_on_debug_battery(chg); + smblib_handle_usb_plugin(0, &irq_data); + smblib_handle_usb_typec_change(0, &irq_data); + smblib_handle_usb_source_change(0, &irq_data); + smblib_handle_chg_state_change(0, &irq_data); + smblib_handle_icl_change(0, &irq_data); + smblib_handle_batt_temp_changed(0, &irq_data); + smblib_handle_wdog_bark(0, &irq_data); + + return 0; +} + +/************************** + * INTERRUPT REGISTRATION * + **************************/ + +static struct smb_irq_info smb2_irqs[] = { +/* CHARGER IRQs */ + [CHG_ERROR_IRQ] = { + .name = "chg-error", + .handler = smblib_handle_debug, + }, + [CHG_STATE_CHANGE_IRQ] = { + .name = "chg-state-change", + .handler = smblib_handle_chg_state_change, + .wake = true, + }, + [STEP_CHG_STATE_CHANGE_IRQ] = { + .name = "step-chg-state-change", + .handler = NULL, + }, + [STEP_CHG_SOC_UPDATE_FAIL_IRQ] = { + .name = "step-chg-soc-update-fail", + .handler = NULL, + }, + [STEP_CHG_SOC_UPDATE_REQ_IRQ] = { + .name = "step-chg-soc-update-request", + .handler = NULL, + }, +/* OTG IRQs */ + [OTG_FAIL_IRQ] = { + .name = "otg-fail", + .handler = smblib_handle_debug, + }, + [OTG_OVERCURRENT_IRQ] = { + .name = "otg-overcurrent", + .handler = smblib_handle_otg_overcurrent, + }, + [OTG_OC_DIS_SW_STS_IRQ] = { + .name = "otg-oc-dis-sw-sts", + .handler = smblib_handle_debug, + }, + [TESTMODE_CHANGE_DET_IRQ] = { + .name = "testmode-change-detect", + .handler = smblib_handle_debug, + }, +/* BATTERY IRQs */ + [BATT_TEMP_IRQ] = { + .name = "bat-temp", + .handler = smblib_handle_batt_temp_changed, + .wake = true, + }, + [BATT_OCP_IRQ] = { + .name = "bat-ocp", + .handler = smblib_handle_batt_psy_changed, + }, + [BATT_OV_IRQ] = { + .name = "bat-ov", + .handler = smblib_handle_batt_psy_changed, + }, + [BATT_LOW_IRQ] = { + .name = "bat-low", + .handler = smblib_handle_batt_psy_changed, + }, + [BATT_THERM_ID_MISS_IRQ] = { + .name = "bat-therm-or-id-missing", + .handler = smblib_handle_batt_psy_changed, + }, + [BATT_TERM_MISS_IRQ] = { + .name = "bat-terminal-missing", + .handler = smblib_handle_batt_psy_changed, + }, +/* USB INPUT IRQs */ + [USBIN_COLLAPSE_IRQ] = { + .name = "usbin-collapse", + .handler = smblib_handle_debug, + }, + [USBIN_LT_3P6V_IRQ] = { + .name = "usbin-lt-3p6v", + .handler = smblib_handle_debug, + }, + [USBIN_UV_IRQ] = { + .name = "usbin-uv", + .handler = smblib_handle_usbin_uv, + }, + [USBIN_OV_IRQ] = { + .name = "usbin-ov", + .handler = smblib_handle_debug, + }, + [USBIN_PLUGIN_IRQ] = { + .name = "usbin-plugin", + .handler = smblib_handle_usb_plugin, + .wake = true, + }, + [USBIN_SRC_CHANGE_IRQ] = { + .name = "usbin-src-change", + .handler = smblib_handle_usb_source_change, + .wake = true, + }, + [USBIN_ICL_CHANGE_IRQ] = { + .name = "usbin-icl-change", + .handler = smblib_handle_icl_change, + .wake = true, + }, + [TYPE_C_CHANGE_IRQ] = { + .name = "type-c-change", + .handler = smblib_handle_usb_typec_change, + .wake = true, + }, +/* DC INPUT IRQs */ + [DCIN_COLLAPSE_IRQ] = { + .name = "dcin-collapse", + .handler = smblib_handle_debug, + }, + [DCIN_LT_3P6V_IRQ] = { + .name = "dcin-lt-3p6v", + .handler = smblib_handle_debug, + }, + [DCIN_UV_IRQ] = { + .name = "dcin-uv", + .handler = smblib_handle_debug, + }, + [DCIN_OV_IRQ] = { + .name = "dcin-ov", + .handler = smblib_handle_debug, + }, + [DCIN_PLUGIN_IRQ] = { + .name = "dcin-plugin", + .handler = smblib_handle_dc_plugin, + .wake = true, + }, + [DIV2_EN_DG_IRQ] = { + .name = "div2-en-dg", + .handler = smblib_handle_debug, + }, + [DCIN_ICL_CHANGE_IRQ] = { + .name = "dcin-icl-change", + .handler = smblib_handle_debug, + }, +/* MISCELLANEOUS IRQs */ + [WDOG_SNARL_IRQ] = { + .name = "wdog-snarl", + .handler = NULL, + }, + [WDOG_BARK_IRQ] = { + .name = "wdog-bark", + .handler = smblib_handle_wdog_bark, + .wake = true, + }, + [AICL_FAIL_IRQ] = { + .name = "aicl-fail", + .handler = smblib_handle_debug, + }, + [AICL_DONE_IRQ] = { + .name = "aicl-done", + .handler = smblib_handle_debug, + }, + [HIGH_DUTY_CYCLE_IRQ] = { + .name = "high-duty-cycle", + .handler = smblib_handle_high_duty_cycle, + .wake = true, + }, + [INPUT_CURRENT_LIMIT_IRQ] = { + .name = "input-current-limiting", + .handler = smblib_handle_debug, + }, + [TEMPERATURE_CHANGE_IRQ] = { + .name = "temperature-change", + .handler = smblib_handle_debug, + }, + [SWITCH_POWER_OK_IRQ] = { + .name = "switcher-power-ok", + .handler = smblib_handle_switcher_power_ok, + .wake = true, + .storm_data = {true, 1000, 8}, + }, +}; + +static int smb2_get_irq_index_byname(const char *irq_name) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(smb2_irqs); i++) { + if (strcmp(smb2_irqs[i].name, irq_name) == 0) + return i; + } + + return -ENOENT; +} + +static int smb2_request_interrupt(struct smb2 *chip, + struct device_node *node, const char *irq_name) +{ + struct smb_charger *chg = &chip->chg; + int rc, irq, irq_index; + struct smb_irq_data *irq_data; + + irq = of_irq_get_byname(node, irq_name); + if (irq < 0) { + pr_err("Couldn't get irq %s byname\n", irq_name); + return irq; + } + + irq_index = smb2_get_irq_index_byname(irq_name); + if (irq_index < 0) { + pr_err("%s is not a defined irq\n", irq_name); + return irq_index; + } + + if (!smb2_irqs[irq_index].handler) + return 0; + + irq_data = devm_kzalloc(chg->dev, sizeof(*irq_data), GFP_KERNEL); + if (!irq_data) + return -ENOMEM; + + irq_data->parent_data = chip; + irq_data->name = irq_name; + irq_data->storm_data = smb2_irqs[irq_index].storm_data; + mutex_init(&irq_data->storm_data.storm_lock); + + rc = devm_request_threaded_irq(chg->dev, irq, NULL, + smb2_irqs[irq_index].handler, + IRQF_ONESHOT, irq_name, irq_data); + if (rc < 0) { + pr_err("Couldn't request irq %d\n", irq); + return rc; + } + + smb2_irqs[irq_index].irq = irq; + smb2_irqs[irq_index].irq_data = irq_data; + if (smb2_irqs[irq_index].wake) + enable_irq_wake(irq); + + return rc; +} + +static int smb2_request_interrupts(struct smb2 *chip) +{ + struct smb_charger *chg = &chip->chg; + struct device_node *node = chg->dev->of_node; + struct device_node *child; + int rc = 0; + const char *name; + struct property *prop; + + for_each_available_child_of_node(node, child) { + of_property_for_each_string(child, "interrupt-names", + prop, name) { + rc = smb2_request_interrupt(chip, child, name); + if (rc < 0) + return rc; + } + } + if (chg->irq_info[USBIN_ICL_CHANGE_IRQ].irq) + chg->usb_icl_change_irq_enabled = true; + + return rc; +} + +static void smb2_free_interrupts(struct smb_charger *chg) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(smb2_irqs); i++) { + if (smb2_irqs[i].irq > 0) { + if (smb2_irqs[i].wake) + disable_irq_wake(smb2_irqs[i].irq); + } + } +} + +static void smb2_disable_interrupts(struct smb_charger *chg) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(smb2_irqs); i++) { + if (smb2_irqs[i].irq > 0) + disable_irq(smb2_irqs[i].irq); + } +} + +#if defined(CONFIG_DEBUG_FS) + +static int force_batt_psy_update_write(void *data, u64 val) +{ + struct smb_charger *chg = data; + + power_supply_changed(chg->batt_psy); + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(force_batt_psy_update_ops, NULL, + force_batt_psy_update_write, "0x%02llx\n"); + +static int force_usb_psy_update_write(void *data, u64 val) +{ + struct smb_charger *chg = data; + + power_supply_changed(chg->usb_psy); + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(force_usb_psy_update_ops, NULL, + force_usb_psy_update_write, "0x%02llx\n"); + +static int force_dc_psy_update_write(void *data, u64 val) +{ + struct smb_charger *chg = data; + + power_supply_changed(chg->dc_psy); + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(force_dc_psy_update_ops, NULL, + force_dc_psy_update_write, "0x%02llx\n"); + +static void smb2_create_debugfs(struct smb2 *chip) +{ + struct dentry *file; + + chip->dfs_root = debugfs_create_dir("charger", NULL); + if (IS_ERR_OR_NULL(chip->dfs_root)) { + pr_err("Couldn't create charger debugfs rc=%ld\n", + (long)chip->dfs_root); + return; + } + + file = debugfs_create_file("force_batt_psy_update", 0600, + chip->dfs_root, chip, &force_batt_psy_update_ops); + if (IS_ERR_OR_NULL(file)) + pr_err("Couldn't create force_batt_psy_update file rc=%ld\n", + (long)file); + + file = debugfs_create_file("force_usb_psy_update", 0600, + chip->dfs_root, chip, &force_usb_psy_update_ops); + if (IS_ERR_OR_NULL(file)) + pr_err("Couldn't create force_usb_psy_update file rc=%ld\n", + (long)file); + + file = debugfs_create_file("force_dc_psy_update", 0600, + chip->dfs_root, chip, &force_dc_psy_update_ops); + if (IS_ERR_OR_NULL(file)) + pr_err("Couldn't create force_dc_psy_update file rc=%ld\n", + (long)file); + + file = debugfs_create_u32("debug_mask", 0600, chip->dfs_root, + &__debug_mask); + if (IS_ERR_OR_NULL(file)) + pr_err("Failed to create debug_mask rc=%ld\n", (long)file); +} + +#else + +static void smb2_create_debugfs(struct smb2 *chip) +{} + +#endif + +static int smb2_probe(struct platform_device *pdev) +{ + struct smb2 *chip; + struct smb_charger *chg; + int rc = 0; + union power_supply_propval val; + int usb_present, batt_present, batt_health, batt_charge_type; + + chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chg = &chip->chg; + chg->dev = &pdev->dev; + chg->param = v1_params; + chg->debug_mask = &__debug_mask; + chg->try_sink_enabled = &__try_sink_enabled; + chg->weak_chg_icl_ua = &__weak_chg_icl_ua; + chg->mode = PARALLEL_MASTER; + chg->irq_info = smb2_irqs; + chg->die_health = -EINVAL; + chg->name = "PMI"; + chg->audio_headset_drp_wait_ms = &__audio_headset_drp_wait_ms; + + chg->regmap = dev_get_regmap(chg->dev->parent, NULL); + if (!chg->regmap) { + pr_err("parent regmap is missing\n"); + return -EINVAL; + } + + rc = smb2_chg_config_init(chip); + if (rc < 0) { + if (rc != -EPROBE_DEFER) + pr_err("Couldn't setup chg_config rc=%d\n", rc); + return rc; + } + + rc = smb2_parse_dt(chip); + if (rc < 0) { + pr_err("Couldn't parse device tree rc=%d\n", rc); + goto cleanup; + } + + rc = smblib_init(chg); + if (rc < 0) { + pr_err("Smblib_init failed rc=%d\n", rc); + goto cleanup; + } + + /* set driver data before resources request it */ + platform_set_drvdata(pdev, chip); + + rc = smb2_init_vbus_regulator(chip); + if (rc < 0) { + pr_err("Couldn't initialize vbus regulator rc=%d\n", + rc); + goto cleanup; + } + + rc = smb2_init_vconn_regulator(chip); + if (rc < 0) { + pr_err("Couldn't initialize vconn regulator rc=%d\n", + rc); + goto cleanup; + } + + /* extcon registration */ + chg->extcon = devm_extcon_dev_allocate(chg->dev, smblib_extcon_cable); + if (IS_ERR(chg->extcon)) { + rc = PTR_ERR(chg->extcon); + dev_err(chg->dev, "failed to allocate extcon device rc=%d\n", + rc); + goto cleanup; + } + + rc = devm_extcon_dev_register(chg->dev, chg->extcon); + if (rc < 0) { + dev_err(chg->dev, "failed to register extcon device rc=%d\n", + rc); + goto cleanup; + } + + rc = smb2_init_hw(chip); + if (rc < 0) { + pr_err("Couldn't initialize hardware rc=%d\n", rc); + goto cleanup; + } + + rc = smb2_init_dc_psy(chip); + if (rc < 0) { + pr_err("Couldn't initialize dc psy rc=%d\n", rc); + goto cleanup; + } + + rc = smb2_init_usb_psy(chip); + if (rc < 0) { + pr_err("Couldn't initialize usb psy rc=%d\n", rc); + goto cleanup; + } + + rc = smb2_init_usb_main_psy(chip); + if (rc < 0) { + pr_err("Couldn't initialize usb main psy rc=%d\n", rc); + goto cleanup; + } + + rc = smb2_init_usb_port_psy(chip); + if (rc < 0) { + pr_err("Couldn't initialize usb pc_port psy rc=%d\n", rc); + goto cleanup; + } + + rc = smb2_init_batt_psy(chip); + if (rc < 0) { + pr_err("Couldn't initialize batt psy rc=%d\n", rc); + goto cleanup; + } + + rc = smb2_determine_initial_status(chip); + if (rc < 0) { + pr_err("Couldn't determine initial status rc=%d\n", + rc); + goto cleanup; + } + + rc = smb2_request_interrupts(chip); + if (rc < 0) { + pr_err("Couldn't request interrupts rc=%d\n", rc); + goto cleanup; + } + + rc = smb2_post_init(chip); + if (rc < 0) { + pr_err("Failed in post init rc=%d\n", rc); + goto cleanup; + } + + smb2_create_debugfs(chip); + + rc = smblib_get_prop_usb_present(chg, &val); + if (rc < 0) { + pr_err("Couldn't get usb present rc=%d\n", rc); + goto cleanup; + } + usb_present = val.intval; + + rc = sysfs_create_groups(&chg->dev->kobj, smb2_groups); + if (rc < 0) { + pr_err("Failed to create sysfs files rc=%d\n", rc); + goto cleanup; + } + + rc = smblib_get_prop_batt_present(chg, &val); + if (rc < 0) { + pr_err("Couldn't get batt present rc=%d\n", rc); + goto cleanup; + } + batt_present = val.intval; + + rc = smblib_get_prop_batt_health(chg, &val); + if (rc < 0) { + pr_err("Couldn't get batt health rc=%d\n", rc); + val.intval = POWER_SUPPLY_HEALTH_UNKNOWN; + } + batt_health = val.intval; + + rc = smblib_get_prop_batt_charge_type(chg, &val); + if (rc < 0) { + pr_err("Couldn't get batt charge type rc=%d\n", rc); + goto cleanup; + } + batt_charge_type = val.intval; + + device_init_wakeup(chg->dev, true); + + pr_info("QPNP SMB2 probed successfully usb:present=%d type=%d batt:present = %d health = %d charge = %d\n", + usb_present, chg->real_charger_type, + batt_present, batt_health, batt_charge_type); + return rc; + +cleanup: + smb2_free_interrupts(chg); + if (chg->batt_psy) + power_supply_unregister(chg->batt_psy); + if (chg->usb_main_psy) + power_supply_unregister(chg->usb_main_psy); + if (chg->usb_psy) + power_supply_unregister(chg->usb_psy); + if (chg->usb_port_psy) + power_supply_unregister(chg->usb_port_psy); + if (chg->dc_psy) + power_supply_unregister(chg->dc_psy); + if (chg->vconn_vreg && chg->vconn_vreg->rdev) + devm_regulator_unregister(chg->dev, chg->vconn_vreg->rdev); + if (chg->vbus_vreg && chg->vbus_vreg->rdev) + devm_regulator_unregister(chg->dev, chg->vbus_vreg->rdev); + + smblib_deinit(chg); + + platform_set_drvdata(pdev, NULL); + return rc; +} + +static int smb2_remove(struct platform_device *pdev) +{ + struct smb2 *chip = platform_get_drvdata(pdev); + struct smb_charger *chg = &chip->chg; + + power_supply_unregister(chg->batt_psy); + power_supply_unregister(chg->usb_psy); + power_supply_unregister(chg->usb_port_psy); + regulator_unregister(chg->vconn_vreg->rdev); + regulator_unregister(chg->vbus_vreg->rdev); + sysfs_remove_groups(&chg->dev->kobj, smb2_groups); + debugfs_remove_recursive(chip->dfs_root); + platform_set_drvdata(pdev, NULL); + return 0; +} + +static void smb2_shutdown(struct platform_device *pdev) +{ + struct smb2 *chip = platform_get_drvdata(pdev); + struct smb_charger *chg = &chip->chg; + + /* disable all interrupts */ + smb2_disable_interrupts(chg); + + if (!chg->ufp_only_mode) + /* configure power role for UFP */ + smblib_masked_write(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG, + TYPEC_POWER_ROLE_CMD_MASK, UFP_EN_CMD_BIT); + + /* force HVDCP to 5V */ + smblib_masked_write(chg, USBIN_OPTIONS_1_CFG_REG, + HVDCP_AUTONOMOUS_MODE_EN_CFG_BIT, 0); + smblib_write(chg, CMD_HVDCP_2_REG, FORCE_5V_BIT); + + /* force enable APSD */ + smblib_masked_write(chg, USBIN_OPTIONS_1_CFG_REG, + AUTO_SRC_DETECT_BIT, AUTO_SRC_DETECT_BIT); +} + +static const struct of_device_id match_table[] = { + { .compatible = "qcom,qpnp-smb2", }, + { }, +}; + +static struct platform_driver smb2_driver = { + .driver = { + .name = "qcom,qpnp-smb2", + .of_match_table = match_table, + }, + .probe = smb2_probe, + .remove = smb2_remove, + .shutdown = smb2_shutdown, +}; +module_platform_driver(smb2_driver); + +MODULE_DESCRIPTION("QPNP SMB2 Charger Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/power/supply/qcom/smb-lib.c b/drivers/power/supply/qcom/smb-lib.c new file mode 100644 index 000000000000..00e05b7ee38f --- /dev/null +++ b/drivers/power/supply/qcom/smb-lib.c @@ -0,0 +1,5482 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2016-2020 The Linux Foundation. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "smb-lib.h" +#include "smb-reg.h" +#include "battery.h" +#include "step-chg-jeita.h" +#include "storm-watch.h" + +#define smblib_err(chg, fmt, ...) \ + pr_err("%s: %s: " fmt, chg->name, \ + __func__, ##__VA_ARGS__) \ + +#define smblib_dbg(chg, reason, fmt, ...) \ + do { \ + if (*chg->debug_mask & (reason)) \ + pr_info("%s: %s: " fmt, chg->name, \ + __func__, ##__VA_ARGS__); \ + else \ + pr_debug("%s: %s: " fmt, chg->name, \ + __func__, ##__VA_ARGS__); \ + } while (0) + +static bool is_secure(struct smb_charger *chg, int addr) +{ + if (addr == SHIP_MODE_REG || addr == FREQ_CLK_DIV_REG) + return true; + /* assume everything above 0xA0 is secure */ + return (bool)((addr & 0xFF) >= 0xA0); +} + +int smblib_read(struct smb_charger *chg, u16 addr, u8 *val) +{ + unsigned int temp; + int rc = 0; + + rc = regmap_read(chg->regmap, addr, &temp); + if (rc >= 0) + *val = (u8)temp; + + return rc; +} + +int smblib_multibyte_read(struct smb_charger *chg, u16 addr, u8 *val, + int count) +{ + return regmap_bulk_read(chg->regmap, addr, val, count); +} + +int smblib_masked_write(struct smb_charger *chg, u16 addr, u8 mask, u8 val) +{ + int rc = 0; + + mutex_lock(&chg->write_lock); + if (is_secure(chg, addr)) { + rc = regmap_write(chg->regmap, (addr & 0xFF00) | 0xD0, 0xA5); + if (rc < 0) + goto unlock; + } + + rc = regmap_update_bits(chg->regmap, addr, mask, val); + +unlock: + mutex_unlock(&chg->write_lock); + return rc; +} + +int smblib_write(struct smb_charger *chg, u16 addr, u8 val) +{ + int rc = 0; + + mutex_lock(&chg->write_lock); + + if (is_secure(chg, addr)) { + rc = regmap_write(chg->regmap, (addr & ~(0xFF)) | 0xD0, 0xA5); + if (rc < 0) + goto unlock; + } + + rc = regmap_write(chg->regmap, addr, val); + +unlock: + mutex_unlock(&chg->write_lock); + return rc; +} + +static int smblib_get_jeita_cc_delta(struct smb_charger *chg, int *cc_delta_ua) +{ + int rc, cc_minus_ua; + u8 stat; + + rc = smblib_read(chg, BATTERY_CHARGER_STATUS_2_REG, &stat); + if (rc < 0) { + smblib_err(chg, "Couldn't read BATTERY_CHARGER_STATUS_2 rc=%d\n", + rc); + return rc; + } + + if (!(stat & BAT_TEMP_STATUS_SOFT_LIMIT_MASK)) { + *cc_delta_ua = 0; + return 0; + } + + rc = smblib_get_charge_param(chg, &chg->param.jeita_cc_comp, + &cc_minus_ua); + if (rc < 0) { + smblib_err(chg, "Couldn't get jeita cc minus rc=%d\n", rc); + return rc; + } + + *cc_delta_ua = -cc_minus_ua; + return 0; +} + +int smblib_icl_override(struct smb_charger *chg, bool override) +{ + int rc; + + rc = smblib_masked_write(chg, USBIN_LOAD_CFG_REG, + ICL_OVERRIDE_AFTER_APSD_BIT, + override ? ICL_OVERRIDE_AFTER_APSD_BIT : 0); + if (rc < 0) + smblib_err(chg, "Couldn't override ICL rc=%d\n", rc); + + return rc; +} + +int smblib_stat_sw_override_cfg(struct smb_charger *chg, bool override) +{ + int rc; + + /* override = 1, SW STAT override; override = 0, HW auto mode */ + rc = smblib_masked_write(chg, STAT_CFG_REG, + STAT_SW_OVERRIDE_CFG_BIT, + override ? STAT_SW_OVERRIDE_CFG_BIT : 0); + if (rc < 0) { + dev_err(chg->dev, "Couldn't configure SW STAT override rc=%d\n", + rc); + return rc; + } + + return rc; +} + +/******************** + * REGISTER GETTERS * + ********************/ + +int smblib_get_charge_param(struct smb_charger *chg, + struct smb_chg_param *param, int *val_u) +{ + int rc = 0; + u8 val_raw; + + rc = smblib_read(chg, param->reg, &val_raw); + if (rc < 0) { + smblib_err(chg, "%s: Couldn't read from 0x%04x rc=%d\n", + param->name, param->reg, rc); + return rc; + } + + if (param->get_proc) + *val_u = param->get_proc(param, val_raw); + else + *val_u = val_raw * param->step_u + param->min_u; + smblib_dbg(chg, PR_REGISTER, "%s = %d (0x%02x)\n", + param->name, *val_u, val_raw); + + return rc; +} + +int smblib_get_usb_suspend(struct smb_charger *chg, int *suspend) +{ + int rc = 0; + u8 temp; + + rc = smblib_read(chg, USBIN_CMD_IL_REG, &temp); + if (rc < 0) { + smblib_err(chg, "Couldn't read USBIN_CMD_IL rc=%d\n", rc); + return rc; + } + *suspend = temp & USBIN_SUSPEND_BIT; + + return rc; +} + +struct apsd_result { + const char * const name; + const u8 bit; + const enum power_supply_type pst; +}; + +enum { + UNKNOWN, + SDP, + CDP, + DCP, + OCP, + FLOAT, + HVDCP2, + HVDCP3, + MAX_TYPES +}; + +static const struct apsd_result const smblib_apsd_results[] = { + [UNKNOWN] = { + .name = "UNKNOWN", + .bit = 0, + .pst = POWER_SUPPLY_TYPE_UNKNOWN + }, + [SDP] = { + .name = "SDP", + .bit = SDP_CHARGER_BIT, + .pst = POWER_SUPPLY_TYPE_USB + }, + [CDP] = { + .name = "CDP", + .bit = CDP_CHARGER_BIT, + .pst = POWER_SUPPLY_TYPE_USB_CDP + }, + [DCP] = { + .name = "DCP", + .bit = DCP_CHARGER_BIT, + .pst = POWER_SUPPLY_TYPE_USB_DCP + }, + [OCP] = { + .name = "OCP", + .bit = OCP_CHARGER_BIT, + .pst = POWER_SUPPLY_TYPE_USB_DCP + }, + [FLOAT] = { + .name = "FLOAT", + .bit = FLOAT_CHARGER_BIT, + .pst = POWER_SUPPLY_TYPE_USB_FLOAT + }, + [HVDCP2] = { + .name = "HVDCP2", + .bit = DCP_CHARGER_BIT | QC_2P0_BIT, + .pst = POWER_SUPPLY_TYPE_USB_HVDCP + }, + [HVDCP3] = { + .name = "HVDCP3", + .bit = DCP_CHARGER_BIT | QC_3P0_BIT, + .pst = POWER_SUPPLY_TYPE_USB_HVDCP_3, + }, +}; + +static const struct apsd_result *smblib_get_apsd_result(struct smb_charger *chg) +{ + int rc, i; + u8 apsd_stat, stat; + const struct apsd_result *result = &smblib_apsd_results[UNKNOWN]; + + rc = smblib_read(chg, APSD_STATUS_REG, &apsd_stat); + if (rc < 0) { + smblib_err(chg, "Couldn't read APSD_STATUS rc=%d\n", rc); + return result; + } + smblib_dbg(chg, PR_REGISTER, "APSD_STATUS = 0x%02x\n", apsd_stat); + + if (!(apsd_stat & APSD_DTC_STATUS_DONE_BIT)) + return result; + + rc = smblib_read(chg, APSD_RESULT_STATUS_REG, &stat); + if (rc < 0) { + smblib_err(chg, "Couldn't read APSD_RESULT_STATUS rc=%d\n", + rc); + return result; + } + stat &= APSD_RESULT_STATUS_MASK; + + for (i = 0; i < ARRAY_SIZE(smblib_apsd_results); i++) { + if (smblib_apsd_results[i].bit == stat) + result = &smblib_apsd_results[i]; + } + + if (apsd_stat & QC_CHARGER_BIT) { + /* since its a qc_charger, either return HVDCP3 or HVDCP2 */ + if (result != &smblib_apsd_results[HVDCP3]) + result = &smblib_apsd_results[HVDCP2]; + } + + return result; +} + +/******************** + * REGISTER SETTERS * + ********************/ + +static int chg_freq_list[] = { + 9600, 9600, 6400, 4800, 3800, 3200, 2700, 2400, 2100, 1900, 1700, + 1600, 1500, 1400, 1300, 1200, +}; + +int smblib_set_chg_freq(struct smb_chg_param *param, + int val_u, u8 *val_raw) +{ + u8 i; + + if (val_u > param->max_u || val_u < param->min_u) + return -EINVAL; + + /* Charger FSW is the configured freqency / 2 */ + val_u *= 2; + for (i = 0; i < ARRAY_SIZE(chg_freq_list); i++) { + if (chg_freq_list[i] == val_u) + break; + } + if (i == ARRAY_SIZE(chg_freq_list)) { + pr_err("Invalid frequency %d Hz\n", val_u / 2); + return -EINVAL; + } + + *val_raw = i; + + return 0; +} + +static int smblib_set_opt_freq_buck(struct smb_charger *chg, int fsw_khz) +{ + union power_supply_propval pval = {0, }; + int rc = 0; + + rc = smblib_set_charge_param(chg, &chg->param.freq_buck, fsw_khz); + if (rc < 0) + dev_err(chg->dev, "Error in setting freq_buck rc=%d\n", rc); + + if (chg->mode == PARALLEL_MASTER && chg->pl.psy) { + pval.intval = fsw_khz; + /* + * Some parallel charging implementations may not have + * PROP_BUCK_FREQ property - they could be running + * with a fixed frequency + */ + rc = power_supply_set_property(chg->pl.psy, + POWER_SUPPLY_PROP_BUCK_FREQ, &pval); + } + + return rc; +} + +int smblib_set_charge_param(struct smb_charger *chg, + struct smb_chg_param *param, int val_u) +{ + int rc = 0; + u8 val_raw; + + if (param->set_proc) { + rc = param->set_proc(param, val_u, &val_raw); + if (rc < 0) + return -EINVAL; + } else { + if (val_u > param->max_u || val_u < param->min_u) { + smblib_err(chg, "%s: %d is out of range [%d, %d]\n", + param->name, val_u, param->min_u, param->max_u); + return -EINVAL; + } + + val_raw = (val_u - param->min_u) / param->step_u; + } + + rc = smblib_write(chg, param->reg, val_raw); + if (rc < 0) { + smblib_err(chg, "%s: Couldn't write 0x%02x to 0x%04x rc=%d\n", + param->name, val_raw, param->reg, rc); + return rc; + } + + smblib_dbg(chg, PR_REGISTER, "%s = %d (0x%02x)\n", + param->name, val_u, val_raw); + + return rc; +} + +int smblib_set_usb_suspend(struct smb_charger *chg, bool suspend) +{ + int rc = 0; + int irq = chg->irq_info[USBIN_ICL_CHANGE_IRQ].irq; + + if (suspend && irq) { + if (chg->usb_icl_change_irq_enabled) { + disable_irq_nosync(irq); + chg->usb_icl_change_irq_enabled = false; + } + } + + rc = smblib_masked_write(chg, USBIN_CMD_IL_REG, USBIN_SUSPEND_BIT, + suspend ? USBIN_SUSPEND_BIT : 0); + if (rc < 0) + smblib_err(chg, "Couldn't write %s to USBIN_SUSPEND_BIT rc=%d\n", + suspend ? "suspend" : "resume", rc); + + if (!suspend && irq) { + if (!chg->usb_icl_change_irq_enabled) { + enable_irq(irq); + chg->usb_icl_change_irq_enabled = true; + } + } + + return rc; +} + +int smblib_set_dc_suspend(struct smb_charger *chg, bool suspend) +{ + int rc = 0; + + rc = smblib_masked_write(chg, DCIN_CMD_IL_REG, DCIN_SUSPEND_BIT, + suspend ? DCIN_SUSPEND_BIT : 0); + if (rc < 0) + smblib_err(chg, "Couldn't write %s to DCIN_SUSPEND_BIT rc=%d\n", + suspend ? "suspend" : "resume", rc); + + return rc; +} + +static int smblib_set_adapter_allowance(struct smb_charger *chg, + u8 allowed_voltage) +{ + int rc = 0; + + /* PM660 only support max. 9V */ + if (chg->chg_param.smb_version == PM660_SUBTYPE) { + switch (allowed_voltage) { + case USBIN_ADAPTER_ALLOW_12V: + case USBIN_ADAPTER_ALLOW_9V_TO_12V: + allowed_voltage = USBIN_ADAPTER_ALLOW_9V; + break; + case USBIN_ADAPTER_ALLOW_5V_OR_12V: + case USBIN_ADAPTER_ALLOW_5V_OR_9V_TO_12V: + allowed_voltage = USBIN_ADAPTER_ALLOW_5V_OR_9V; + break; + case USBIN_ADAPTER_ALLOW_5V_TO_12V: + allowed_voltage = USBIN_ADAPTER_ALLOW_5V_TO_9V; + break; + } + } + + rc = smblib_write(chg, USBIN_ADAPTER_ALLOW_CFG_REG, allowed_voltage); + if (rc < 0) { + smblib_err(chg, "Couldn't write 0x%02x to USBIN_ADAPTER_ALLOW_CFG rc=%d\n", + allowed_voltage, rc); + return rc; + } + + return rc; +} + +#define MICRO_5V 5000000 +#define MICRO_9V 9000000 +#define MICRO_12V 12000000 +static int smblib_set_usb_pd_allowed_voltage(struct smb_charger *chg, + int min_allowed_uv, int max_allowed_uv) +{ + int rc; + u8 allowed_voltage; + + if (min_allowed_uv == MICRO_5V && max_allowed_uv == MICRO_5V) { + allowed_voltage = USBIN_ADAPTER_ALLOW_5V; + smblib_set_opt_freq_buck(chg, chg->chg_freq.freq_5V); + } else if (min_allowed_uv == MICRO_9V && max_allowed_uv == MICRO_9V) { + allowed_voltage = USBIN_ADAPTER_ALLOW_9V; + smblib_set_opt_freq_buck(chg, chg->chg_freq.freq_9V); + } else if (min_allowed_uv == MICRO_12V && max_allowed_uv == MICRO_12V) { + allowed_voltage = USBIN_ADAPTER_ALLOW_12V; + smblib_set_opt_freq_buck(chg, chg->chg_freq.freq_12V); + } else if (min_allowed_uv < MICRO_9V && max_allowed_uv <= MICRO_9V) { + allowed_voltage = USBIN_ADAPTER_ALLOW_5V_TO_9V; + } else if (min_allowed_uv < MICRO_9V && max_allowed_uv <= MICRO_12V) { + allowed_voltage = USBIN_ADAPTER_ALLOW_5V_TO_12V; + } else if (min_allowed_uv < MICRO_12V && max_allowed_uv <= MICRO_12V) { + allowed_voltage = USBIN_ADAPTER_ALLOW_9V_TO_12V; + } else { + smblib_err(chg, "invalid allowed voltage [%d, %d]\n", + min_allowed_uv, max_allowed_uv); + return -EINVAL; + } + + rc = smblib_set_adapter_allowance(chg, allowed_voltage); + if (rc < 0) { + smblib_err(chg, "Couldn't configure adapter allowance rc=%d\n", + rc); + return rc; + } + + return rc; +} + +/******************** + * HELPER FUNCTIONS * + ********************/ + +int smblib_force_ufp(struct smb_charger *chg) +{ + int rc; + + /* force FSM in IDLE state */ + rc = smblib_masked_write(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG, + TYPEC_DISABLE_CMD_BIT, TYPEC_DISABLE_CMD_BIT); + if (rc < 0) { + smblib_err(chg, "Couldn't put FSM in idle rc=%d\n", rc); + return rc; + } + + /* wait for FSM to enter idle state */ + msleep(200); + + rc = smblib_masked_write(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG, + VCONN_EN_VALUE_BIT | UFP_EN_CMD_BIT, UFP_EN_CMD_BIT); + if (rc < 0) { + smblib_err(chg, "Couldn't force UFP mode rc=%d\n", rc); + return rc; + } + + /* wait for mode change before enabling FSM */ + usleep_range(10000, 11000); + + /* release FSM from idle state */ + rc = smblib_masked_write(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG, + TYPEC_DISABLE_CMD_BIT, 0); + if (rc < 0) { + smblib_err(chg, "Couldn't release FSM from idle rc=%d\n", rc); + return rc; + } + + return 0; +} + +static int smblib_request_dpdm(struct smb_charger *chg, bool enable) +{ + int rc = 0; + + if (chg->pr_swap_in_progress) + return 0; + + /* fetch the DPDM regulator */ + if (!chg->dpdm_reg && of_get_property(chg->dev->of_node, + "dpdm-supply", NULL)) { + chg->dpdm_reg = devm_regulator_get(chg->dev, "dpdm"); + if (IS_ERR(chg->dpdm_reg)) { + rc = PTR_ERR(chg->dpdm_reg); + smblib_err(chg, "Couldn't get dpdm regulator rc=%d\n", + rc); + chg->dpdm_reg = NULL; + return rc; + } + } + + if (enable) { + if (chg->dpdm_reg && !regulator_is_enabled(chg->dpdm_reg)) { + smblib_dbg(chg, PR_MISC, "enabling DPDM regulator\n"); + rc = regulator_enable(chg->dpdm_reg); + if (rc < 0) + smblib_err(chg, + "Couldn't enable dpdm regulator rc=%d\n", + rc); + } + } else { + if (chg->dpdm_reg && regulator_is_enabled(chg->dpdm_reg)) { + smblib_dbg(chg, PR_MISC, "disabling DPDM regulator\n"); + rc = regulator_disable(chg->dpdm_reg); + if (rc < 0) + smblib_err(chg, + "Couldn't disable dpdm regulator rc=%d\n", + rc); + } + } + + return rc; +} + +static void smblib_rerun_apsd(struct smb_charger *chg) +{ + int rc; + + smblib_dbg(chg, PR_MISC, "re-running APSD\n"); + if (chg->wa_flags & QC_AUTH_INTERRUPT_WA_BIT) { + rc = smblib_masked_write(chg, + USBIN_SOURCE_CHANGE_INTRPT_ENB_REG, + AUTH_IRQ_EN_CFG_BIT, AUTH_IRQ_EN_CFG_BIT); + if (rc < 0) + smblib_err(chg, "Couldn't enable HVDCP auth IRQ rc=%d\n", + rc); + } + + rc = smblib_masked_write(chg, CMD_APSD_REG, + APSD_RERUN_BIT, APSD_RERUN_BIT); + if (rc < 0) + smblib_err(chg, "Couldn't re-run APSD rc=%d\n", rc); +} + +static const struct apsd_result *smblib_update_usb_type(struct smb_charger *chg) +{ + const struct apsd_result *apsd_result = smblib_get_apsd_result(chg); + + /* if PD is active, APSD is disabled so won't have a valid result */ + if (chg->pd_active) { + chg->real_charger_type = POWER_SUPPLY_TYPE_USB_PD; + } else { + /* + * Update real charger type only if its not FLOAT + * detected as as SDP + */ + if (!(apsd_result->pst == POWER_SUPPLY_TYPE_USB_FLOAT && + chg->real_charger_type == POWER_SUPPLY_TYPE_USB)) + chg->real_charger_type = apsd_result->pst; + } + + smblib_dbg(chg, PR_MISC, "APSD=%s PD=%d\n", + apsd_result->name, chg->pd_active); + return apsd_result; +} + +static int smblib_notifier_call(struct notifier_block *nb, + unsigned long ev, void *v) +{ + struct power_supply *psy = v; + struct smb_charger *chg = container_of(nb, struct smb_charger, nb); + + if (!strcmp(psy->desc->name, "bms")) { + if (!chg->bms_psy) + chg->bms_psy = psy; + if (ev == PSY_EVENT_PROP_CHANGED) + schedule_work(&chg->bms_update_work); + } + + if (!chg->pl.psy && !strcmp(psy->desc->name, "parallel")) { + chg->pl.psy = psy; + schedule_work(&chg->pl_update_work); + } + + return NOTIFY_OK; +} + +static int smblib_register_notifier(struct smb_charger *chg) +{ + int rc; + + chg->nb.notifier_call = smblib_notifier_call; + rc = power_supply_reg_notifier(&chg->nb); + if (rc < 0) { + smblib_err(chg, "Couldn't register psy notifier rc = %d\n", rc); + return rc; + } + + return 0; +} + +int smblib_mapping_soc_from_field_value(struct smb_chg_param *param, + int val_u, u8 *val_raw) +{ + if (val_u > param->max_u || val_u < param->min_u) + return -EINVAL; + + *val_raw = val_u << 1; + + return 0; +} + +int smblib_mapping_cc_delta_to_field_value(struct smb_chg_param *param, + u8 val_raw) +{ + int val_u = val_raw * param->step_u + param->min_u; + + if (val_u > param->max_u) + val_u -= param->max_u * 2; + + return val_u; +} + +int smblib_mapping_cc_delta_from_field_value(struct smb_chg_param *param, + int val_u, u8 *val_raw) +{ + if (val_u > param->max_u || val_u < param->min_u - param->max_u) + return -EINVAL; + + val_u += param->max_u * 2 - param->min_u; + val_u %= param->max_u * 2; + *val_raw = val_u / param->step_u; + + return 0; +} + +static void smblib_uusb_removal(struct smb_charger *chg) +{ + int rc; + struct smb_irq_data *data; + struct storm_watch *wdata; + + cancel_delayed_work_sync(&chg->pl_enable_work); + + rc = smblib_request_dpdm(chg, false); + if (rc < 0) + smblib_err(chg, "Couldn't to disable DPDM rc=%d\n", rc); + + if (chg->wa_flags & BOOST_BACK_WA) { + data = chg->irq_info[SWITCH_POWER_OK_IRQ].irq_data; + if (data) { + wdata = &data->storm_data; + update_storm_count(wdata, WEAK_CHG_STORM_COUNT); + vote(chg->usb_icl_votable, BOOST_BACK_VOTER, false, 0); + vote(chg->usb_icl_votable, WEAK_CHARGER_VOTER, + false, 0); + } + } + vote(chg->pl_disable_votable, PL_DELAY_VOTER, true, 0); + vote(chg->awake_votable, PL_DELAY_VOTER, false, 0); + + /* reset both usbin current and voltage votes */ + vote(chg->pl_enable_votable_indirect, USBIN_I_VOTER, false, 0); + vote(chg->pl_enable_votable_indirect, USBIN_V_VOTER, false, 0); + vote(chg->usb_icl_votable, SW_QC3_VOTER, false, 0); + vote(chg->usb_icl_votable, USBIN_USBIN_BOOST_VOTER, false, 0); + vote(chg->usb_icl_votable, HVDCP2_ICL_VOTER, false, 0); + vote(chg->hvdcp_hw_inov_dis_votable, OV_VOTER, false, 0); + + cancel_delayed_work_sync(&chg->hvdcp_detect_work); + + if (chg->wa_flags & QC_AUTH_INTERRUPT_WA_BIT) { + /* re-enable AUTH_IRQ_EN_CFG_BIT */ + rc = smblib_masked_write(chg, + USBIN_SOURCE_CHANGE_INTRPT_ENB_REG, + AUTH_IRQ_EN_CFG_BIT, AUTH_IRQ_EN_CFG_BIT); + if (rc < 0) + smblib_err(chg, + "Couldn't enable QC auth setting rc=%d\n", rc); + } + + /* reconfigure allowed voltage for HVDCP */ + rc = smblib_set_adapter_allowance(chg, + USBIN_ADAPTER_ALLOW_5V_OR_9V_TO_12V); + if (rc < 0) + smblib_err(chg, "Couldn't set USBIN_ADAPTER_ALLOW_5V_OR_9V_TO_12V rc=%d\n", + rc); + + chg->voltage_min_uv = MICRO_5V; + chg->voltage_max_uv = MICRO_5V; + chg->usb_icl_delta_ua = 0; + chg->pulse_cnt = 0; + chg->uusb_apsd_rerun_done = false; + + /* clear USB ICL vote for USB_PSY_VOTER */ + rc = vote(chg->usb_icl_votable, USB_PSY_VOTER, false, 0); + if (rc < 0) + smblib_err(chg, "Couldn't un-vote for USB ICL rc=%d\n", rc); + + /* clear USB ICL vote for DCP_VOTER */ + rc = vote(chg->usb_icl_votable, DCP_VOTER, false, 0); + if (rc < 0) + smblib_err(chg, + "Couldn't un-vote DCP from USB ICL rc=%d\n", rc); +} + +void smblib_suspend_on_debug_battery(struct smb_charger *chg) +{ + int rc; + union power_supply_propval val; + + if (!chg->suspend_input_on_debug_batt) + return; + + rc = power_supply_get_property(chg->bms_psy, + POWER_SUPPLY_PROP_DEBUG_BATTERY, &val); + if (rc < 0) { + smblib_err(chg, "Couldn't get debug battery prop rc=%d\n", rc); + return; + } + + vote(chg->usb_icl_votable, DEBUG_BOARD_VOTER, val.intval, 0); + vote(chg->dc_suspend_votable, DEBUG_BOARD_VOTER, val.intval, 0); + if (val.intval) + pr_info("Input suspended: Fake battery\n"); +} + +int smblib_rerun_apsd_if_required(struct smb_charger *chg) +{ + union power_supply_propval val; + int rc; + + rc = smblib_get_prop_usb_present(chg, &val); + if (rc < 0) { + smblib_err(chg, "Couldn't get usb present rc = %d\n", rc); + return rc; + } + + if (!val.intval) + return 0; + + rc = smblib_request_dpdm(chg, true); + if (rc < 0) + smblib_err(chg, "Couldn't to enable DPDM rc=%d\n", rc); + + chg->uusb_apsd_rerun_done = true; + smblib_rerun_apsd(chg); + + return 0; +} + +static int smblib_get_hw_pulse_cnt(struct smb_charger *chg, int *count) +{ + int rc; + u8 val[2]; + + switch (chg->chg_param.smb_version) { + case PMI8998_SUBTYPE: + rc = smblib_read(chg, QC_PULSE_COUNT_STATUS_REG, val); + if (rc) { + pr_err("failed to read QC_PULSE_COUNT_STATUS_REG rc=%d\n", + rc); + return rc; + } + *count = val[0] & QC_PULSE_COUNT_MASK; + break; + case PM660_SUBTYPE: + rc = smblib_multibyte_read(chg, + QC_PULSE_COUNT_STATUS_1_REG, val, 2); + if (rc) { + pr_err("failed to read QC_PULSE_COUNT_STATUS_1_REG rc=%d\n", + rc); + return rc; + } + *count = (val[1] << 8) | val[0]; + break; + default: + smblib_dbg(chg, PR_PARALLEL, "unknown SMB chip %d\n", + chg->chg_param.smb_version); + return -EINVAL; + } + + return 0; +} + +static int smblib_get_pulse_cnt(struct smb_charger *chg, int *count) +{ + int rc; + + /* Use software based pulse count if HW INOV is disabled */ + if (get_effective_result(chg->hvdcp_hw_inov_dis_votable) > 0) { + *count = chg->pulse_cnt; + return 0; + } + + /* Use h/w pulse count if autonomous mode is enabled */ + rc = smblib_get_hw_pulse_cnt(chg, count); + if (rc < 0) + smblib_err(chg, "failed to read h/w pulse count rc=%d\n", rc); + + return rc; +} + +#define USBIN_25MA 25000 +#define USBIN_100MA 100000 +#define USBIN_150MA 150000 +#define USBIN_500MA 500000 +#define USBIN_900MA 900000 + +static int set_sdp_current(struct smb_charger *chg, int icl_ua) +{ + int rc; + u8 icl_options; + const struct apsd_result *apsd_result = smblib_get_apsd_result(chg); + + /* power source is SDP */ + switch (icl_ua) { + case USBIN_100MA: + /* USB 2.0 100mA */ + icl_options = 0; + break; + case USBIN_150MA: + /* USB 3.0 150mA */ + icl_options = CFG_USB3P0_SEL_BIT; + break; + case USBIN_500MA: + /* USB 2.0 500mA */ + icl_options = USB51_MODE_BIT; + break; + case USBIN_900MA: + /* USB 3.0 900mA */ + icl_options = CFG_USB3P0_SEL_BIT | USB51_MODE_BIT; + break; + default: + smblib_err(chg, "ICL %duA isn't supported for SDP\n", icl_ua); + return -EINVAL; + } + + if (chg->real_charger_type == POWER_SUPPLY_TYPE_USB && + apsd_result->pst == POWER_SUPPLY_TYPE_USB_FLOAT) { + /* + * change the float charger configuration to SDP, if this + * is the case of SDP being detected as FLOAT + */ + rc = smblib_masked_write(chg, USBIN_OPTIONS_2_CFG_REG, + FORCE_FLOAT_SDP_CFG_BIT, FORCE_FLOAT_SDP_CFG_BIT); + if (rc < 0) { + smblib_err(chg, "Couldn't set float ICL options rc=%d\n", + rc); + return rc; + } + } + + rc = smblib_masked_write(chg, USBIN_ICL_OPTIONS_REG, + CFG_USB3P0_SEL_BIT | USB51_MODE_BIT, icl_options); + if (rc < 0) { + smblib_err(chg, "Couldn't set ICL options rc=%d\n", rc); + return rc; + } + + return rc; +} + +static int get_sdp_current(struct smb_charger *chg, int *icl_ua) +{ + int rc; + u8 icl_options; + bool usb3 = false; + + rc = smblib_read(chg, USBIN_ICL_OPTIONS_REG, &icl_options); + if (rc < 0) { + smblib_err(chg, "Couldn't get ICL options rc=%d\n", rc); + return rc; + } + + usb3 = (icl_options & CFG_USB3P0_SEL_BIT); + + if (icl_options & USB51_MODE_BIT) + *icl_ua = usb3 ? USBIN_900MA : USBIN_500MA; + else + *icl_ua = usb3 ? USBIN_150MA : USBIN_100MA; + + return rc; +} + +int smblib_set_icl_current(struct smb_charger *chg, int icl_ua) +{ + int rc = 0; + bool override; + + /* suspend and return if 25mA or less is requested */ + if (icl_ua <= USBIN_25MA) + return smblib_set_usb_suspend(chg, true); + + if (icl_ua == INT_MAX) + goto override_suspend_config; + + /* configure current */ + if (chg->typec_mode == POWER_SUPPLY_TYPEC_SOURCE_DEFAULT + && (chg->real_charger_type == POWER_SUPPLY_TYPE_USB)) { + rc = set_sdp_current(chg, icl_ua); + if (rc < 0) { + smblib_err(chg, "Couldn't set SDP ICL rc=%d\n", rc); + goto enable_icl_changed_interrupt; + } + } else { + set_sdp_current(chg, 100000); + rc = smblib_set_charge_param(chg, &chg->param.usb_icl, icl_ua); + if (rc < 0) { + smblib_err(chg, "Couldn't set HC ICL rc=%d\n", rc); + goto enable_icl_changed_interrupt; + } + } + +override_suspend_config: + /* determine if override needs to be enforced */ + override = true; + if (icl_ua == INT_MAX) { + /* remove override if no voters - hw defaults is desired */ + override = false; + } else if (chg->typec_mode == POWER_SUPPLY_TYPEC_SOURCE_DEFAULT) { + if (chg->real_charger_type == POWER_SUPPLY_TYPE_USB) + /* For std cable with type = SDP never override */ + override = false; + else if (chg->real_charger_type == POWER_SUPPLY_TYPE_USB_CDP + && icl_ua == 1500000) + /* + * For std cable with type = CDP override only if + * current is not 1500mA + */ + override = false; + } + + /* enforce override */ + rc = smblib_masked_write(chg, USBIN_ICL_OPTIONS_REG, + USBIN_MODE_CHG_BIT, override ? USBIN_MODE_CHG_BIT : 0); + + rc = smblib_icl_override(chg, override); + if (rc < 0) { + smblib_err(chg, "Couldn't set ICL override rc=%d\n", rc); + goto enable_icl_changed_interrupt; + } + + /* unsuspend after configuring current and override */ + rc = smblib_set_usb_suspend(chg, false); + if (rc < 0) { + smblib_err(chg, "Couldn't resume input rc=%d\n", rc); + goto enable_icl_changed_interrupt; + } + +enable_icl_changed_interrupt: + return rc; +} + +int smblib_get_icl_current(struct smb_charger *chg, int *icl_ua) +{ + int rc = 0; + u8 load_cfg; + bool override; + + if (((chg->typec_mode == POWER_SUPPLY_TYPEC_SOURCE_DEFAULT) + || (chg->connector_type == POWER_SUPPLY_CONNECTOR_MICRO_USB)) + && (chg->usb_psy_desc.type == POWER_SUPPLY_TYPE_USB)) { + rc = get_sdp_current(chg, icl_ua); + if (rc < 0) { + smblib_err(chg, "Couldn't get SDP ICL rc=%d\n", rc); + return rc; + } + } else { + rc = smblib_read(chg, USBIN_LOAD_CFG_REG, &load_cfg); + if (rc < 0) { + smblib_err(chg, "Couldn't get load cfg rc=%d\n", rc); + return rc; + } + override = load_cfg & ICL_OVERRIDE_AFTER_APSD_BIT; + if (!override) + return INT_MAX; + + /* override is set */ + rc = smblib_get_charge_param(chg, &chg->param.usb_icl, icl_ua); + if (rc < 0) { + smblib_err(chg, "Couldn't get HC ICL rc=%d\n", rc); + return rc; + } + } + + return 0; +} + +int smblib_toggle_stat(struct smb_charger *chg, int reset) +{ + int rc = 0; + + if (reset) { + rc = smblib_masked_write(chg, STAT_CFG_REG, + STAT_SW_OVERRIDE_CFG_BIT | STAT_SW_OVERRIDE_VALUE_BIT, + STAT_SW_OVERRIDE_CFG_BIT | 0); + if (rc < 0) { + smblib_err(chg, + "Couldn't pull STAT pin low rc=%d\n", rc); + return rc; + } + + /* + * A minimum of 20us delay is expected before switching on STAT + * pin + */ + usleep_range(20, 30); + + rc = smblib_masked_write(chg, STAT_CFG_REG, + STAT_SW_OVERRIDE_CFG_BIT | STAT_SW_OVERRIDE_VALUE_BIT, + STAT_SW_OVERRIDE_CFG_BIT | STAT_SW_OVERRIDE_VALUE_BIT); + if (rc < 0) { + smblib_err(chg, + "Couldn't pull STAT pin high rc=%d\n", rc); + return rc; + } + + rc = smblib_masked_write(chg, STAT_CFG_REG, + STAT_SW_OVERRIDE_CFG_BIT | STAT_SW_OVERRIDE_VALUE_BIT, + 0); + if (rc < 0) { + smblib_err(chg, + "Couldn't set hardware control rc=%d\n", rc); + return rc; + } + } + + return rc; +} + +static int smblib_micro_usb_disable_power_role_switch(struct smb_charger *chg, + bool disable) +{ + int rc = 0; + u8 power_role; + + power_role = disable ? TYPEC_DISABLE_CMD_BIT : 0; + /* Disable pullup on CC1_ID pin and stop detection on CC pins */ + rc = smblib_masked_write(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG, + (uint8_t)TYPEC_POWER_ROLE_CMD_MASK, + power_role); + if (rc < 0) { + smblib_err(chg, "Couldn't write 0x%02x to TYPE_C_INTRPT_ENB_SOFTWARE_CTRL rc=%d\n", + power_role, rc); + return rc; + } + + if (disable) { + /* configure TypeC mode */ + rc = smblib_masked_write(chg, TYPE_C_CFG_REG, + TYPE_C_OR_U_USB_BIT, 0); + if (rc < 0) { + smblib_err(chg, "Couldn't configure typec mode rc=%d\n", + rc); + return rc; + } + + /* wait for FSM to enter idle state */ + usleep_range(5000, 5100); + + /* configure micro USB mode */ + rc = smblib_masked_write(chg, TYPE_C_CFG_REG, + TYPE_C_OR_U_USB_BIT, + TYPE_C_OR_U_USB_BIT); + if (rc < 0) { + smblib_err(chg, "Couldn't configure micro USB mode rc=%d\n", + rc); + return rc; + } + } + + return rc; +} + +static int __smblib_set_prop_typec_power_role(struct smb_charger *chg, + const union power_supply_propval *val) +{ + int rc = 0; + u8 power_role; + + switch (val->intval) { + case POWER_SUPPLY_TYPEC_PR_NONE: + power_role = TYPEC_DISABLE_CMD_BIT; + break; + case POWER_SUPPLY_TYPEC_PR_DUAL: + power_role = 0; + break; + case POWER_SUPPLY_TYPEC_PR_SINK: + power_role = UFP_EN_CMD_BIT; + break; + case POWER_SUPPLY_TYPEC_PR_SOURCE: + power_role = DFP_EN_CMD_BIT; + break; + default: + smblib_err(chg, "power role %d not supported\n", val->intval); + return -EINVAL; + } + + if (power_role != TYPEC_DISABLE_CMD_BIT) { + if (chg->ufp_only_mode) + power_role = UFP_EN_CMD_BIT; + } + + if (chg->wa_flags & TYPEC_PBS_WA_BIT) { + if (power_role == UFP_EN_CMD_BIT) { + /* disable PBS workaround when forcing sink mode */ + rc = smblib_write(chg, TM_IO_DTEST4_SEL, 0x0); + if (rc < 0) { + smblib_err(chg, "Couldn't write to TM_IO_DTEST4_SEL rc=%d\n", + rc); + } + } else { + /* restore it back to 0xA5 */ + rc = smblib_write(chg, TM_IO_DTEST4_SEL, 0xA5); + if (rc < 0) { + smblib_err(chg, "Couldn't write to TM_IO_DTEST4_SEL rc=%d\n", + rc); + } + } + } + + rc = smblib_masked_write(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG, + TYPEC_POWER_ROLE_CMD_MASK, power_role); + if (rc < 0) { + smblib_err(chg, "Couldn't write 0x%02x to TYPE_C_INTRPT_ENB_SOFTWARE_CTRL rc=%d\n", + power_role, rc); + return rc; + } + + return rc; +} + +/********************* + * VOTABLE CALLBACKS * + *********************/ + +static int smblib_dc_suspend_vote_callback(struct votable *votable, void *data, + int suspend, const char *client) +{ + struct smb_charger *chg = data; + + /* resume input if suspend is invalid */ + if (suspend < 0) + suspend = 0; + + return smblib_set_dc_suspend(chg, (bool)suspend); +} + +static int smblib_dc_icl_vote_callback(struct votable *votable, void *data, + int icl_ua, const char *client) +{ + struct smb_charger *chg = data; + int rc = 0; + bool suspend; + + if (icl_ua < 0) { + smblib_dbg(chg, PR_MISC, "No Voter hence suspending\n"); + icl_ua = 0; + } + + suspend = (icl_ua <= USBIN_25MA); + if (suspend) + goto suspend; + + rc = smblib_set_charge_param(chg, &chg->param.dc_icl, icl_ua); + if (rc < 0) { + smblib_err(chg, "Couldn't set DC input current limit rc=%d\n", + rc); + return rc; + } + +suspend: + rc = vote(chg->dc_suspend_votable, USER_VOTER, suspend, 0); + if (rc < 0) { + smblib_err(chg, "Couldn't vote to %s DC rc=%d\n", + suspend ? "suspend" : "resume", rc); + return rc; + } + return rc; +} + +static int smblib_pd_disallowed_votable_indirect_callback( + struct votable *votable, void *data, int disallowed, const char *client) +{ + struct smb_charger *chg = data; + int rc; + + rc = vote(chg->pd_allowed_votable, PD_DISALLOWED_INDIRECT_VOTER, + !disallowed, 0); + + return rc; +} + +static int smblib_awake_vote_callback(struct votable *votable, void *data, + int awake, const char *client) +{ + struct smb_charger *chg = data; + + if (awake) + pm_stay_awake(chg->dev); + else + pm_relax(chg->dev); + + return 0; +} + +static int smblib_chg_disable_vote_callback(struct votable *votable, void *data, + int chg_disable, const char *client) +{ + struct smb_charger *chg = data; + int rc; + + rc = smblib_masked_write(chg, CHARGING_ENABLE_CMD_REG, + CHARGING_ENABLE_CMD_BIT, + chg_disable ? 0 : CHARGING_ENABLE_CMD_BIT); + if (rc < 0) { + smblib_err(chg, "Couldn't %s charging rc=%d\n", + chg_disable ? "disable" : "enable", rc); + return rc; + } + + return 0; +} + +static int smblib_hvdcp_enable_vote_callback(struct votable *votable, + void *data, + int hvdcp_enable, const char *client) +{ + struct smb_charger *chg = data; + int rc; + u8 val = HVDCP_AUTH_ALG_EN_CFG_BIT | HVDCP_EN_BIT; + u8 stat; + + /* vote to enable/disable HW autonomous INOV */ + vote(chg->hvdcp_hw_inov_dis_votable, client, !hvdcp_enable, 0); + + /* + * Disable the autonomous bit and auth bit for disabling hvdcp. + * This ensures only qc 2.0 detection runs but no vbus + * negotiation happens. + */ + if (!hvdcp_enable) + val = HVDCP_EN_BIT; + + rc = smblib_masked_write(chg, USBIN_OPTIONS_1_CFG_REG, + HVDCP_EN_BIT | HVDCP_AUTH_ALG_EN_CFG_BIT, + val); + if (rc < 0) { + smblib_err(chg, "Couldn't %s hvdcp rc=%d\n", + hvdcp_enable ? "enable" : "disable", rc); + return rc; + } + + rc = smblib_read(chg, APSD_STATUS_REG, &stat); + if (rc < 0) { + smblib_err(chg, "Couldn't read APSD status rc=%d\n", rc); + return rc; + } + + /* re-run APSD if HVDCP was detected */ + if (stat & QC_CHARGER_BIT) + smblib_rerun_apsd(chg); + + return 0; +} + +static int smblib_hvdcp_disable_indirect_vote_callback(struct votable *votable, + void *data, int hvdcp_disable, const char *client) +{ + struct smb_charger *chg = data; + + vote(chg->hvdcp_enable_votable, HVDCP_INDIRECT_VOTER, + !hvdcp_disable, 0); + + return 0; +} + +static int smblib_apsd_disable_vote_callback(struct votable *votable, + void *data, + int apsd_disable, const char *client) +{ + struct smb_charger *chg = data; + int rc; + + if (apsd_disable) { + rc = smblib_masked_write(chg, USBIN_OPTIONS_1_CFG_REG, + AUTO_SRC_DETECT_BIT, + 0); + if (rc < 0) { + smblib_err(chg, "Couldn't disable APSD rc=%d\n", rc); + return rc; + } + } else { + rc = smblib_masked_write(chg, USBIN_OPTIONS_1_CFG_REG, + AUTO_SRC_DETECT_BIT, + AUTO_SRC_DETECT_BIT); + if (rc < 0) { + smblib_err(chg, "Couldn't enable APSD rc=%d\n", rc); + return rc; + } + } + + return 0; +} + +static int smblib_hvdcp_hw_inov_dis_vote_callback(struct votable *votable, + void *data, int disable, const char *client) +{ + struct smb_charger *chg = data; + int rc; + + if (disable) { + /* + * the pulse count register get zeroed when autonomous mode is + * disabled. Track that in variables before disabling + */ + rc = smblib_get_hw_pulse_cnt(chg, &chg->pulse_cnt); + if (rc < 0) { + pr_err("failed to read QC_PULSE_COUNT_STATUS_REG rc=%d\n", + rc); + return rc; + } + } + + rc = smblib_masked_write(chg, USBIN_OPTIONS_1_CFG_REG, + HVDCP_AUTONOMOUS_MODE_EN_CFG_BIT, + disable ? 0 : HVDCP_AUTONOMOUS_MODE_EN_CFG_BIT); + if (rc < 0) { + smblib_err(chg, "Couldn't %s hvdcp rc=%d\n", + disable ? "disable" : "enable", rc); + return rc; + } + + return rc; +} + +static int smblib_usb_irq_enable_vote_callback(struct votable *votable, + void *data, int enable, const char *client) +{ + struct smb_charger *chg = data; + + if (!chg->irq_info[INPUT_CURRENT_LIMIT_IRQ].irq || + !chg->irq_info[HIGH_DUTY_CYCLE_IRQ].irq) + return 0; + + if (enable) { + enable_irq(chg->irq_info[INPUT_CURRENT_LIMIT_IRQ].irq); + enable_irq(chg->irq_info[HIGH_DUTY_CYCLE_IRQ].irq); + } else { + disable_irq(chg->irq_info[INPUT_CURRENT_LIMIT_IRQ].irq); + disable_irq(chg->irq_info[HIGH_DUTY_CYCLE_IRQ].irq); + } + + return 0; +} + +static int smblib_typec_irq_disable_vote_callback(struct votable *votable, + void *data, int disable, const char *client) +{ + struct smb_charger *chg = data; + + if (!chg->irq_info[TYPE_C_CHANGE_IRQ].irq) + return 0; + + if (disable) + disable_irq_nosync(chg->irq_info[TYPE_C_CHANGE_IRQ].irq); + else + enable_irq(chg->irq_info[TYPE_C_CHANGE_IRQ].irq); + + return 0; +} + +static int smblib_disable_power_role_switch_callback(struct votable *votable, + void *data, int disable, const char *client) +{ + struct smb_charger *chg = data; + union power_supply_propval pval; + int rc = 0; + + if (chg->connector_type == POWER_SUPPLY_CONNECTOR_MICRO_USB) { + rc = smblib_micro_usb_disable_power_role_switch(chg, disable); + } else { + pval.intval = disable ? POWER_SUPPLY_TYPEC_PR_SINK + : POWER_SUPPLY_TYPEC_PR_DUAL; + rc = __smblib_set_prop_typec_power_role(chg, &pval); + } + + if (rc) + smblib_err(chg, "power_role_switch = %s failed, rc=%d\n", + disable ? "disabled" : "enabled", rc); + else + smblib_dbg(chg, PR_MISC, "power_role_switch = %s\n", + disable ? "disabled" : "enabled"); + + return rc; +} + +/******************* + * VCONN REGULATOR * + * *****************/ + +#define MAX_OTG_SS_TRIES 2 +static int _smblib_vconn_regulator_enable(struct regulator_dev *rdev) +{ + struct smb_charger *chg = rdev_get_drvdata(rdev); + int rc = 0; + u8 val; + + /* + * When enabling VCONN using the command register the CC pin must be + * selected. VCONN should be supplied to the inactive CC pin hence using + * the opposite of the CC_ORIENTATION_BIT. + */ + smblib_dbg(chg, PR_OTG, "enabling VCONN\n"); + val = chg->typec_status[3] & + CC_ORIENTATION_BIT ? 0 : VCONN_EN_ORIENTATION_BIT; + rc = smblib_masked_write(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG, + VCONN_EN_VALUE_BIT | VCONN_EN_ORIENTATION_BIT, + VCONN_EN_VALUE_BIT | val); + if (rc < 0) { + smblib_err(chg, "Couldn't enable vconn setting rc=%d\n", rc); + return rc; + } + + return rc; +} + +int smblib_vconn_regulator_enable(struct regulator_dev *rdev) +{ + struct smb_charger *chg = rdev_get_drvdata(rdev); + int rc = 0; + + mutex_lock(&chg->vconn_oc_lock); + if (chg->vconn_en) + goto unlock; + + rc = _smblib_vconn_regulator_enable(rdev); + if (rc >= 0) + chg->vconn_en = true; + +unlock: + mutex_unlock(&chg->vconn_oc_lock); + return rc; +} + +static int _smblib_vconn_regulator_disable(struct regulator_dev *rdev) +{ + struct smb_charger *chg = rdev_get_drvdata(rdev); + int rc = 0; + + smblib_dbg(chg, PR_OTG, "disabling VCONN\n"); + rc = smblib_masked_write(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG, + VCONN_EN_VALUE_BIT, 0); + if (rc < 0) + smblib_err(chg, "Couldn't disable vconn regulator rc=%d\n", rc); + + return rc; +} + +int smblib_vconn_regulator_disable(struct regulator_dev *rdev) +{ + struct smb_charger *chg = rdev_get_drvdata(rdev); + int rc = 0; + + mutex_lock(&chg->vconn_oc_lock); + if (!chg->vconn_en) + goto unlock; + + rc = _smblib_vconn_regulator_disable(rdev); + if (rc >= 0) + chg->vconn_en = false; + +unlock: + mutex_unlock(&chg->vconn_oc_lock); + return rc; +} + +int smblib_vconn_regulator_is_enabled(struct regulator_dev *rdev) +{ + struct smb_charger *chg = rdev_get_drvdata(rdev); + int ret; + + mutex_lock(&chg->vconn_oc_lock); + ret = chg->vconn_en; + mutex_unlock(&chg->vconn_oc_lock); + return ret; +} + +/***************** + * OTG REGULATOR * + *****************/ +#define MAX_RETRY 15 +#define MIN_DELAY_US 2000 +#define MAX_DELAY_US 9000 +static int otg_current[] = {250000, 500000, 1000000, 1500000}; +static int smblib_enable_otg_wa(struct smb_charger *chg) +{ + u8 stat; + int rc, i, retry_count = 0, min_delay = MIN_DELAY_US; + + for (i = 0; i < ARRAY_SIZE(otg_current); i++) { + smblib_dbg(chg, PR_OTG, "enabling OTG with %duA\n", + otg_current[i]); + rc = smblib_set_charge_param(chg, &chg->param.otg_cl, + otg_current[i]); + if (rc < 0) { + smblib_err(chg, "Couldn't set otg limit rc=%d\n", rc); + return rc; + } + + rc = smblib_write(chg, CMD_OTG_REG, OTG_EN_BIT); + if (rc < 0) { + smblib_err(chg, "Couldn't enable OTG rc=%d\n", rc); + return rc; + } + + retry_count = 0; + min_delay = MIN_DELAY_US; + do { + usleep_range(min_delay, min_delay + 100); + rc = smblib_read(chg, OTG_STATUS_REG, &stat); + if (rc < 0) { + smblib_err(chg, "Couldn't read OTG status rc=%d\n", + rc); + goto out; + } + + if (stat & BOOST_SOFTSTART_DONE_BIT) { + rc = smblib_set_charge_param(chg, + &chg->param.otg_cl, chg->otg_cl_ua); + if (rc < 0) { + smblib_err(chg, "Couldn't set otg limit rc=%d\n", + rc); + goto out; + } + break; + } + /* increase the delay for following iterations */ + if (retry_count > 5) + min_delay = MAX_DELAY_US; + + } while (retry_count++ < MAX_RETRY); + + if (retry_count >= MAX_RETRY) { + smblib_dbg(chg, PR_OTG, "OTG enable failed with %duA\n", + otg_current[i]); + rc = smblib_write(chg, CMD_OTG_REG, 0); + if (rc < 0) { + smblib_err(chg, "disable OTG rc=%d\n", rc); + goto out; + } + } else { + smblib_dbg(chg, PR_OTG, "OTG enabled\n"); + return 0; + } + } + + if (i == ARRAY_SIZE(otg_current)) { + rc = -EINVAL; + goto out; + } + + return 0; +out: + smblib_write(chg, CMD_OTG_REG, 0); + return rc; +} + +static int _smblib_vbus_regulator_enable(struct regulator_dev *rdev) +{ + struct smb_charger *chg = rdev_get_drvdata(rdev); + int rc; + + smblib_dbg(chg, PR_OTG, "halt 1 in 8 mode\n"); + rc = smblib_masked_write(chg, OTG_ENG_OTG_CFG_REG, + ENG_BUCKBOOST_HALT1_8_MODE_BIT, + ENG_BUCKBOOST_HALT1_8_MODE_BIT); + if (rc < 0) { + smblib_err(chg, "Couldn't set OTG_ENG_OTG_CFG_REG rc=%d\n", + rc); + return rc; + } + + smblib_dbg(chg, PR_OTG, "enabling OTG\n"); + + if ((chg->wa_flags & OTG_WA) && (!chg->reddragon_ipc_wa)) { + rc = smblib_enable_otg_wa(chg); + if (rc < 0) + smblib_err(chg, "Couldn't enable OTG rc=%d\n", rc); + } else { + rc = smblib_write(chg, CMD_OTG_REG, OTG_EN_BIT); + if (rc < 0) + smblib_err(chg, "Couldn't enable OTG rc=%d\n", rc); + } + + return rc; +} + +int smblib_vbus_regulator_enable(struct regulator_dev *rdev) +{ + struct smb_charger *chg = rdev_get_drvdata(rdev); + int rc = 0; + + mutex_lock(&chg->otg_oc_lock); + if (chg->otg_en) + goto unlock; + + if (!chg->usb_icl_votable) { + chg->usb_icl_votable = find_votable("USB_ICL"); + + if (!chg->usb_icl_votable) { + rc = -EINVAL; + goto unlock; + } + } + vote(chg->usb_icl_votable, USBIN_USBIN_BOOST_VOTER, true, 0); + + rc = _smblib_vbus_regulator_enable(rdev); + if (rc >= 0) + chg->otg_en = true; + else + vote(chg->usb_icl_votable, USBIN_USBIN_BOOST_VOTER, false, 0); + +unlock: + mutex_unlock(&chg->otg_oc_lock); + return rc; +} + +static int _smblib_vbus_regulator_disable(struct regulator_dev *rdev) +{ + struct smb_charger *chg = rdev_get_drvdata(rdev); + int rc; + + if (chg->wa_flags & OTG_WA) { + /* set OTG current limit to minimum value */ + rc = smblib_set_charge_param(chg, &chg->param.otg_cl, + chg->param.otg_cl.min_u); + if (rc < 0) { + smblib_err(chg, + "Couldn't set otg current limit rc=%d\n", rc); + return rc; + } + } + + smblib_dbg(chg, PR_OTG, "disabling OTG\n"); + rc = smblib_write(chg, CMD_OTG_REG, 0); + if (rc < 0) { + smblib_err(chg, "Couldn't disable OTG regulator rc=%d\n", rc); + return rc; + } + + smblib_dbg(chg, PR_OTG, "start 1 in 8 mode\n"); + rc = smblib_masked_write(chg, OTG_ENG_OTG_CFG_REG, + ENG_BUCKBOOST_HALT1_8_MODE_BIT, 0); + if (rc < 0) { + smblib_err(chg, "Couldn't set OTG_ENG_OTG_CFG_REG rc=%d\n", rc); + return rc; + } + + return 0; +} + +int smblib_vbus_regulator_disable(struct regulator_dev *rdev) +{ + struct smb_charger *chg = rdev_get_drvdata(rdev); + int rc = 0; + + mutex_lock(&chg->otg_oc_lock); + if (!chg->otg_en) + goto unlock; + + rc = _smblib_vbus_regulator_disable(rdev); + if (rc >= 0) + chg->otg_en = false; + + if (chg->usb_icl_votable) + vote(chg->usb_icl_votable, USBIN_USBIN_BOOST_VOTER, false, 0); +unlock: + mutex_unlock(&chg->otg_oc_lock); + return rc; +} + +int smblib_vbus_regulator_is_enabled(struct regulator_dev *rdev) +{ + struct smb_charger *chg = rdev_get_drvdata(rdev); + int ret; + + mutex_lock(&chg->otg_oc_lock); + ret = chg->otg_en; + mutex_unlock(&chg->otg_oc_lock); + return ret; +} + +/******************** + * BATT PSY GETTERS * + ********************/ + +int smblib_get_prop_input_suspend(struct smb_charger *chg, + union power_supply_propval *val) +{ + val->intval + = (get_client_vote(chg->usb_icl_votable, USER_VOTER) == 0) + && get_client_vote(chg->dc_suspend_votable, USER_VOTER); + return 0; +} + +int smblib_get_prop_batt_present(struct smb_charger *chg, + union power_supply_propval *val) +{ + int rc; + u8 stat; + + rc = smblib_read(chg, BATIF_BASE + INT_RT_STS_OFFSET, &stat); + if (rc < 0) { + smblib_err(chg, "Couldn't read BATIF_INT_RT_STS rc=%d\n", rc); + return rc; + } + + val->intval = !(stat & (BAT_THERM_OR_ID_MISSING_RT_STS_BIT + | BAT_TERMINAL_MISSING_RT_STS_BIT)); + + return rc; +} + +int smblib_get_prop_batt_capacity(struct smb_charger *chg, + union power_supply_propval *val) +{ + int rc = -EINVAL; + + if (chg->fake_capacity >= 0) { + val->intval = chg->fake_capacity; + return 0; + } + + if (chg->bms_psy) + rc = power_supply_get_property(chg->bms_psy, + POWER_SUPPLY_PROP_CAPACITY, val); + return rc; +} + +int smblib_get_prop_batt_status(struct smb_charger *chg, + union power_supply_propval *val) +{ + union power_supply_propval pval = {0, }; + bool usb_online, dc_online, qnovo_en; + u8 stat, pt_en_cmd; + int rc; + + rc = smblib_get_prop_usb_online(chg, &pval); + if (rc < 0) { + smblib_err(chg, "Couldn't get usb online property rc=%d\n", + rc); + return rc; + } + usb_online = (bool)pval.intval; + + rc = smblib_get_prop_dc_online(chg, &pval); + if (rc < 0) { + smblib_err(chg, "Couldn't get dc online property rc=%d\n", + rc); + return rc; + } + dc_online = (bool)pval.intval; + + rc = smblib_read(chg, BATTERY_CHARGER_STATUS_1_REG, &stat); + if (rc < 0) { + smblib_err(chg, "Couldn't read BATTERY_CHARGER_STATUS_1 rc=%d\n", + rc); + return rc; + } + stat = stat & BATTERY_CHARGER_STATUS_MASK; + + if (!usb_online && !dc_online) { + switch (stat) { + case TERMINATE_CHARGE: + case INHIBIT_CHARGE: + val->intval = POWER_SUPPLY_STATUS_FULL; + break; + default: + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + break; + } + return rc; + } + + switch (stat) { + case TRICKLE_CHARGE: + case PRE_CHARGE: + case FAST_CHARGE: + case FULLON_CHARGE: + case TAPER_CHARGE: + val->intval = POWER_SUPPLY_STATUS_CHARGING; + break; + case TERMINATE_CHARGE: + case INHIBIT_CHARGE: + val->intval = POWER_SUPPLY_STATUS_FULL; + break; + case DISABLE_CHARGE: + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + break; + default: + val->intval = POWER_SUPPLY_STATUS_UNKNOWN; + break; + } + + if (val->intval != POWER_SUPPLY_STATUS_CHARGING) + return 0; + + if (!usb_online && dc_online + && chg->fake_batt_status == POWER_SUPPLY_STATUS_FULL) { + val->intval = POWER_SUPPLY_STATUS_FULL; + return 0; + } + + rc = smblib_read(chg, BATTERY_CHARGER_STATUS_7_REG, &stat); + if (rc < 0) { + smblib_err(chg, "Couldn't read BATTERY_CHARGER_STATUS_2 rc=%d\n", + rc); + return rc; + } + + stat &= ENABLE_TRICKLE_BIT | ENABLE_PRE_CHARGING_BIT | + ENABLE_FAST_CHARGING_BIT | ENABLE_FULLON_MODE_BIT; + + rc = smblib_read(chg, QNOVO_PT_ENABLE_CMD_REG, &pt_en_cmd); + if (rc < 0) { + smblib_err(chg, "Couldn't read QNOVO_PT_ENABLE_CMD_REG rc=%d\n", + rc); + return rc; + } + + qnovo_en = (bool)(pt_en_cmd & QNOVO_PT_ENABLE_CMD_BIT); + + /* ignore stat7 when qnovo is enabled */ + if (!qnovo_en && !stat) + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + + return 0; +} + +int smblib_get_prop_batt_charge_type(struct smb_charger *chg, + union power_supply_propval *val) +{ + int rc; + u8 stat; + + rc = smblib_read(chg, BATTERY_CHARGER_STATUS_1_REG, &stat); + if (rc < 0) { + smblib_err(chg, "Couldn't read BATTERY_CHARGER_STATUS_1 rc=%d\n", + rc); + return rc; + } + + switch (stat & BATTERY_CHARGER_STATUS_MASK) { + case TRICKLE_CHARGE: + case PRE_CHARGE: + val->intval = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; + break; + case FAST_CHARGE: + case FULLON_CHARGE: + val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST; + break; + case TAPER_CHARGE: + val->intval = POWER_SUPPLY_CHARGE_TYPE_TAPER; + break; + default: + val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE; + } + + return rc; +} + +int smblib_get_prop_batt_health(struct smb_charger *chg, + union power_supply_propval *val) +{ + union power_supply_propval pval; + int rc; + int effective_fv_uv; + u8 stat; + + rc = smblib_read(chg, BATTERY_CHARGER_STATUS_2_REG, &stat); + if (rc < 0) { + smblib_err(chg, "Couldn't read BATTERY_CHARGER_STATUS_2 rc=%d\n", + rc); + return rc; + } + smblib_dbg(chg, PR_REGISTER, "BATTERY_CHARGER_STATUS_2 = 0x%02x\n", + stat); + + if (stat & CHARGER_ERROR_STATUS_BAT_OV_BIT) { + rc = smblib_get_prop_from_bms(chg, + POWER_SUPPLY_PROP_VOLTAGE_NOW, &pval); + if (!rc) { + /* + * If Vbatt is within 40mV above Vfloat, then don't + * treat it as overvoltage. + */ + effective_fv_uv = get_effective_result(chg->fv_votable); + if (pval.intval >= effective_fv_uv + 40000) { + val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + smblib_err(chg, "battery over-voltage vbat_fg = %duV, fv = %duV\n", + pval.intval, effective_fv_uv); + goto done; + } + } + } + + if (stat & BAT_TEMP_STATUS_TOO_COLD_BIT) + val->intval = POWER_SUPPLY_HEALTH_COLD; + else if (stat & BAT_TEMP_STATUS_TOO_HOT_BIT) + val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; + else if (stat & BAT_TEMP_STATUS_COLD_SOFT_LIMIT_BIT) + val->intval = POWER_SUPPLY_HEALTH_COOL; + else if (stat & BAT_TEMP_STATUS_HOT_SOFT_LIMIT_BIT) + val->intval = POWER_SUPPLY_HEALTH_WARM; + else + val->intval = POWER_SUPPLY_HEALTH_GOOD; + +done: + return rc; +} + +int smblib_get_prop_system_temp_level(struct smb_charger *chg, + union power_supply_propval *val) +{ + val->intval = chg->system_temp_level; + return 0; +} + +int smblib_get_prop_system_temp_level_max(struct smb_charger *chg, + union power_supply_propval *val) +{ + val->intval = chg->thermal_levels; + return 0; +} + +int smblib_get_prop_input_current_limited(struct smb_charger *chg, + union power_supply_propval *val) +{ + u8 stat; + int rc; + + if (chg->fake_input_current_limited >= 0) { + val->intval = chg->fake_input_current_limited; + return 0; + } + + rc = smblib_read(chg, AICL_STATUS_REG, &stat); + if (rc < 0) { + smblib_err(chg, "Couldn't read AICL_STATUS rc=%d\n", rc); + return rc; + } + val->intval = (stat & SOFT_ILIMIT_BIT) || chg->is_hdc; + return 0; +} + +int smblib_get_prop_batt_charge_done(struct smb_charger *chg, + union power_supply_propval *val) +{ + int rc; + u8 stat; + + rc = smblib_read(chg, BATTERY_CHARGER_STATUS_1_REG, &stat); + if (rc < 0) { + smblib_err(chg, "Couldn't read BATTERY_CHARGER_STATUS_1 rc=%d\n", + rc); + return rc; + } + + stat = stat & BATTERY_CHARGER_STATUS_MASK; + val->intval = (stat == TERMINATE_CHARGE); + return 0; +} + +int smblib_get_prop_charge_qnovo_enable(struct smb_charger *chg, + union power_supply_propval *val) +{ + int rc; + u8 stat; + + rc = smblib_read(chg, QNOVO_PT_ENABLE_CMD_REG, &stat); + if (rc < 0) { + smblib_err(chg, "Couldn't read QNOVO_PT_ENABLE_CMD rc=%d\n", + rc); + return rc; + } + + val->intval = (bool)(stat & QNOVO_PT_ENABLE_CMD_BIT); + return 0; +} + +int smblib_get_prop_from_bms(struct smb_charger *chg, + enum power_supply_property psp, + union power_supply_propval *val) +{ + int rc; + + if (!chg->bms_psy) + return -EINVAL; + + rc = power_supply_get_property(chg->bms_psy, psp, val); + + return rc; +} + +/*********************** + * BATTERY PSY SETTERS * + ***********************/ + +int smblib_set_prop_input_suspend(struct smb_charger *chg, + const union power_supply_propval *val) +{ + int rc; + + /* vote 0mA when suspended */ + rc = vote(chg->usb_icl_votable, USER_VOTER, (bool)val->intval, 0); + if (rc < 0) { + smblib_err(chg, "Couldn't vote to %s USB rc=%d\n", + (bool)val->intval ? "suspend" : "resume", rc); + return rc; + } + + rc = vote(chg->dc_suspend_votable, USER_VOTER, (bool)val->intval, 0); + if (rc < 0) { + smblib_err(chg, "Couldn't vote to %s DC rc=%d\n", + (bool)val->intval ? "suspend" : "resume", rc); + return rc; + } + + power_supply_changed(chg->batt_psy); + return rc; +} + +int smblib_set_prop_batt_capacity(struct smb_charger *chg, + const union power_supply_propval *val) +{ + chg->fake_capacity = val->intval; + + power_supply_changed(chg->batt_psy); + + return 0; +} + +int smblib_set_prop_batt_status(struct smb_charger *chg, + const union power_supply_propval *val) +{ + /* Faking battery full */ + if (val->intval == POWER_SUPPLY_STATUS_FULL) + chg->fake_batt_status = val->intval; + else + chg->fake_batt_status = -EINVAL; + + power_supply_changed(chg->batt_psy); + + return 0; +} + +int smblib_set_prop_system_temp_level(struct smb_charger *chg, + const union power_supply_propval *val) +{ + if (val->intval < 0) + return -EINVAL; + + if (chg->thermal_levels <= 0) + return -EINVAL; + + if (val->intval > chg->thermal_levels) + return -EINVAL; + + chg->system_temp_level = val->intval; + + if (chg->system_temp_level == chg->thermal_levels) + return vote(chg->chg_disable_votable, + THERMAL_DAEMON_VOTER, true, 0); + + vote(chg->chg_disable_votable, THERMAL_DAEMON_VOTER, false, 0); + if (chg->system_temp_level == 0) + return vote(chg->fcc_votable, THERMAL_DAEMON_VOTER, false, 0); + + vote(chg->fcc_votable, THERMAL_DAEMON_VOTER, true, + chg->thermal_mitigation[chg->system_temp_level]); + return 0; +} + +int smblib_set_prop_charge_qnovo_enable(struct smb_charger *chg, + const union power_supply_propval *val) +{ + int rc = 0; + + rc = smblib_masked_write(chg, QNOVO_PT_ENABLE_CMD_REG, + QNOVO_PT_ENABLE_CMD_BIT, + val->intval ? QNOVO_PT_ENABLE_CMD_BIT : 0); + if (rc < 0) { + dev_err(chg->dev, "Couldn't enable qnovo rc=%d\n", rc); + return rc; + } + + return rc; +} + +int smblib_set_prop_input_current_limited(struct smb_charger *chg, + const union power_supply_propval *val) +{ + chg->fake_input_current_limited = val->intval; + return 0; +} + +int smblib_rerun_aicl(struct smb_charger *chg) +{ + int rc, settled_icl_ua; + u8 stat; + + rc = smblib_read(chg, POWER_PATH_STATUS_REG, &stat); + if (rc < 0) { + smblib_err(chg, "Couldn't read POWER_PATH_STATUS rc=%d\n", + rc); + return rc; + } + + /* USB is suspended so skip re-running AICL */ + if (stat & USBIN_SUSPEND_STS_BIT) + return rc; + + smblib_dbg(chg, PR_MISC, "re-running AICL\n"); + rc = smblib_get_charge_param(chg, &chg->param.icl_stat, + &settled_icl_ua); + if (rc < 0) { + smblib_err(chg, "Couldn't get settled ICL rc=%d\n", rc); + return rc; + } + + vote(chg->usb_icl_votable, AICL_RERUN_VOTER, true, + max(settled_icl_ua - chg->param.usb_icl.step_u, + chg->param.usb_icl.step_u)); + vote(chg->usb_icl_votable, AICL_RERUN_VOTER, false, 0); + + return 0; +} + +static int smblib_dp_pulse(struct smb_charger *chg) +{ + int rc; + + /* QC 3.0 increment */ + rc = smblib_masked_write(chg, CMD_HVDCP_2_REG, SINGLE_INCREMENT_BIT, + SINGLE_INCREMENT_BIT); + if (rc < 0) + smblib_err(chg, "Couldn't write to CMD_HVDCP_2_REG rc=%d\n", + rc); + + return rc; +} + +static int smblib_dm_pulse(struct smb_charger *chg) +{ + int rc; + + /* QC 3.0 decrement */ + rc = smblib_masked_write(chg, CMD_HVDCP_2_REG, SINGLE_DECREMENT_BIT, + SINGLE_DECREMENT_BIT); + if (rc < 0) + smblib_err(chg, "Couldn't write to CMD_HVDCP_2_REG rc=%d\n", + rc); + + return rc; +} + +static int smblib_force_vbus_voltage(struct smb_charger *chg, u8 val) +{ + int rc; + + rc = smblib_masked_write(chg, CMD_HVDCP_2_REG, val, val); + if (rc < 0) + smblib_err(chg, "Couldn't write to CMD_HVDCP_2_REG rc=%d\n", + rc); + + return rc; +} + +int smblib_dp_dm(struct smb_charger *chg, int val) +{ + int target_icl_ua, rc = 0; + union power_supply_propval pval; + + switch (val) { + case POWER_SUPPLY_DP_DM_DP_PULSE: + rc = smblib_dp_pulse(chg); + if (!rc) + chg->pulse_cnt++; + smblib_dbg(chg, PR_PARALLEL, "DP_DM_DP_PULSE rc=%d cnt=%d\n", + rc, chg->pulse_cnt); + break; + case POWER_SUPPLY_DP_DM_DM_PULSE: + rc = smblib_dm_pulse(chg); + if (!rc && chg->pulse_cnt) + chg->pulse_cnt--; + smblib_dbg(chg, PR_PARALLEL, "DP_DM_DM_PULSE rc=%d cnt=%d\n", + rc, chg->pulse_cnt); + break; + case POWER_SUPPLY_DP_DM_ICL_DOWN: + target_icl_ua = get_effective_result(chg->usb_icl_votable); + if (target_icl_ua < 0) { + /* no client vote, get the ICL from charger */ + rc = power_supply_get_property(chg->usb_psy, + POWER_SUPPLY_PROP_HW_CURRENT_MAX, + &pval); + if (rc < 0) { + smblib_err(chg, + "Couldn't get max current rc=%d\n", + rc); + return rc; + } + target_icl_ua = pval.intval; + } + + /* + * Check if any other voter voted on USB_ICL in case of + * voter other than SW_QC3_VOTER reset and restart reduction + * again. + */ + if (target_icl_ua != get_client_vote(chg->usb_icl_votable, + SW_QC3_VOTER)) + chg->usb_icl_delta_ua = 0; + + chg->usb_icl_delta_ua += 100000; + vote(chg->usb_icl_votable, SW_QC3_VOTER, true, + target_icl_ua - 100000); + smblib_dbg(chg, PR_PARALLEL, "ICL DOWN ICL=%d reduction=%d\n", + target_icl_ua, chg->usb_icl_delta_ua); + break; + case POWER_SUPPLY_DP_DM_FORCE_5V: + rc = smblib_force_vbus_voltage(chg, FORCE_5V_BIT); + if (rc < 0) + pr_err("Failed to force 5V\n"); + break; + case POWER_SUPPLY_DP_DM_FORCE_9V: + /* Force 1A ICL before requesting higher voltage */ + vote(chg->usb_icl_votable, HVDCP2_ICL_VOTER, true, 1000000); + rc = smblib_force_vbus_voltage(chg, FORCE_9V_BIT); + if (rc < 0) + pr_err("Failed to force 9V\n"); + break; + case POWER_SUPPLY_DP_DM_FORCE_12V: + /* Force 1A ICL before requesting higher voltage */ + vote(chg->usb_icl_votable, HVDCP2_ICL_VOTER, true, 1000000); + rc = smblib_force_vbus_voltage(chg, FORCE_12V_BIT); + if (rc < 0) + pr_err("Failed to force 12V\n"); + break; + case POWER_SUPPLY_DP_DM_ICL_UP: + default: + break; + } + + return rc; +} + +int smblib_disable_hw_jeita(struct smb_charger *chg, bool disable) +{ + int rc; + u8 mask; + + /* + * Disable h/w base JEITA compensation if s/w JEITA is enabled + */ + mask = JEITA_EN_COLD_SL_FCV_BIT + | JEITA_EN_HOT_SL_FCV_BIT + | JEITA_EN_HOT_SL_CCC_BIT + | JEITA_EN_COLD_SL_CCC_BIT, + rc = smblib_masked_write(chg, JEITA_EN_CFG_REG, mask, + disable ? 0 : mask); + if (rc < 0) { + dev_err(chg->dev, + "Couldn't configure s/w jeita rc=%d\n", + rc); + return rc; + } + return 0; +} + +/******************* + * DC PSY GETTERS * + *******************/ + +int smblib_get_prop_dc_present(struct smb_charger *chg, + union power_supply_propval *val) +{ + int rc; + u8 stat; + + rc = smblib_read(chg, DCIN_BASE + INT_RT_STS_OFFSET, &stat); + if (rc < 0) { + smblib_err(chg, "Couldn't read DCIN_RT_STS rc=%d\n", rc); + return rc; + } + + val->intval = (bool)(stat & DCIN_PLUGIN_RT_STS_BIT); + return 0; +} + +int smblib_get_prop_dc_online(struct smb_charger *chg, + union power_supply_propval *val) +{ + int rc = 0; + u8 stat; + + if (get_client_vote(chg->dc_suspend_votable, USER_VOTER)) { + val->intval = false; + return rc; + } + + rc = smblib_read(chg, POWER_PATH_STATUS_REG, &stat); + if (rc < 0) { + smblib_err(chg, "Couldn't read POWER_PATH_STATUS rc=%d\n", + rc); + return rc; + } + smblib_dbg(chg, PR_REGISTER, "POWER_PATH_STATUS = 0x%02x\n", + stat); + + val->intval = (stat & USE_DCIN_BIT) && + (stat & VALID_INPUT_POWER_SOURCE_STS_BIT); + + return rc; +} + +int smblib_get_prop_dc_current_max(struct smb_charger *chg, + union power_supply_propval *val) +{ + val->intval = get_effective_result_locked(chg->dc_icl_votable); + return 0; +} + +/******************* + * DC PSY SETTERS * + * *****************/ + +int smblib_set_prop_dc_current_max(struct smb_charger *chg, + const union power_supply_propval *val) +{ + int rc; + + rc = vote(chg->dc_icl_votable, USER_VOTER, true, val->intval); + return rc; +} + +/******************* + * USB PSY GETTERS * + *******************/ + +int smblib_get_prop_usb_present(struct smb_charger *chg, + union power_supply_propval *val) +{ + int rc; + u8 stat; + + rc = smblib_read(chg, USBIN_BASE + INT_RT_STS_OFFSET, &stat); + if (rc < 0) { + smblib_err(chg, "Couldn't read USBIN_RT_STS rc=%d\n", rc); + return rc; + } + + val->intval = (bool)(stat & USBIN_PLUGIN_RT_STS_BIT); + return 0; +} + +int smblib_get_prop_usb_online(struct smb_charger *chg, + union power_supply_propval *val) +{ + int rc = 0; + u8 stat; + + if (get_client_vote_locked(chg->usb_icl_votable, USER_VOTER) == 0) { + val->intval = false; + return rc; + } + + rc = smblib_read(chg, POWER_PATH_STATUS_REG, &stat); + if (rc < 0) { + smblib_err(chg, "Couldn't read POWER_PATH_STATUS rc=%d\n", + rc); + return rc; + } + smblib_dbg(chg, PR_REGISTER, "POWER_PATH_STATUS = 0x%02x\n", + stat); + + val->intval = (stat & USE_USBIN_BIT) && + (stat & VALID_INPUT_POWER_SOURCE_STS_BIT); + return rc; +} + +int smblib_get_prop_usb_voltage_max(struct smb_charger *chg, + union power_supply_propval *val) +{ + switch (chg->real_charger_type) { + case POWER_SUPPLY_TYPE_USB_HVDCP: + case POWER_SUPPLY_TYPE_USB_HVDCP_3: + if (chg->chg_param.smb_version == PM660_SUBTYPE) + val->intval = MICRO_9V; + else + val->intval = MICRO_12V; + break; + case POWER_SUPPLY_TYPE_USB_PD: + val->intval = chg->voltage_max_uv; + break; + default: + val->intval = MICRO_5V; + break; + } + + return 0; +} + +int smblib_get_prop_usb_voltage_max_design(struct smb_charger *chg, + union power_supply_propval *val) +{ + switch (chg->real_charger_type) { + case POWER_SUPPLY_TYPE_USB_HVDCP: + case POWER_SUPPLY_TYPE_USB_HVDCP_3: + case POWER_SUPPLY_TYPE_USB_PD: + if (chg->chg_param.smb_version == PM660_SUBTYPE) + val->intval = MICRO_9V; + else + val->intval = MICRO_12V; + break; + default: + val->intval = MICRO_5V; + break; + } + + return 0; +} + +int smblib_get_prop_usb_voltage_now(struct smb_charger *chg, + union power_supply_propval *val) +{ + if (!chg->iio.usbin_v_chan || + PTR_ERR(chg->iio.usbin_v_chan) == -EPROBE_DEFER) + chg->iio.usbin_v_chan = iio_channel_get(chg->dev, "usbin_v"); + + if (IS_ERR(chg->iio.usbin_v_chan)) + return PTR_ERR(chg->iio.usbin_v_chan); + + return iio_read_channel_processed(chg->iio.usbin_v_chan, &val->intval); +} + +int smblib_get_prop_usb_current_now(struct smb_charger *chg, + union power_supply_propval *val) +{ + int rc = 0; + + rc = smblib_get_prop_usb_present(chg, val); + if (rc < 0 || !val->intval) + return rc; + + if (!chg->iio.usbin_i_chan || + PTR_ERR(chg->iio.usbin_i_chan) == -EPROBE_DEFER) + chg->iio.usbin_i_chan = iio_channel_get(chg->dev, "usbin_i"); + + if (IS_ERR(chg->iio.usbin_i_chan)) + return PTR_ERR(chg->iio.usbin_i_chan); + + return iio_read_channel_processed(chg->iio.usbin_i_chan, &val->intval); +} + +int smblib_get_prop_charger_temp(struct smb_charger *chg, + union power_supply_propval *val) +{ + int rc; + + if (!chg->iio.temp_chan || + PTR_ERR(chg->iio.temp_chan) == -EPROBE_DEFER) + chg->iio.temp_chan = iio_channel_get(chg->dev, "charger_temp"); + + if (IS_ERR(chg->iio.temp_chan)) + return PTR_ERR(chg->iio.temp_chan); + + rc = iio_read_channel_processed(chg->iio.temp_chan, &val->intval); + val->intval /= 100; + return rc; +} + +int smblib_get_prop_charger_temp_max(struct smb_charger *chg, + union power_supply_propval *val) +{ + int rc; + + if (!chg->iio.temp_max_chan || + PTR_ERR(chg->iio.temp_max_chan) == -EPROBE_DEFER) + chg->iio.temp_max_chan = iio_channel_get(chg->dev, + "charger_temp_max"); + if (IS_ERR(chg->iio.temp_max_chan)) + return PTR_ERR(chg->iio.temp_max_chan); + + rc = iio_read_channel_processed(chg->iio.temp_max_chan, &val->intval); + val->intval /= 100; + return rc; +} + +int smblib_get_prop_typec_cc_orientation(struct smb_charger *chg, + union power_supply_propval *val) +{ + if (chg->typec_status[3] & CC_ATTACHED_BIT) + val->intval = + (bool)(chg->typec_status[3] & CC_ORIENTATION_BIT) + 1; + else + val->intval = 0; + + return 0; +} + +static const char * const smblib_typec_mode_name[] = { + [POWER_SUPPLY_TYPEC_NONE] = "NONE", + [POWER_SUPPLY_TYPEC_SOURCE_DEFAULT] = "SOURCE_DEFAULT", + [POWER_SUPPLY_TYPEC_SOURCE_MEDIUM] = "SOURCE_MEDIUM", + [POWER_SUPPLY_TYPEC_SOURCE_HIGH] = "SOURCE_HIGH", + [POWER_SUPPLY_TYPEC_NON_COMPLIANT] = "NON_COMPLIANT", + [POWER_SUPPLY_TYPEC_SINK] = "SINK", + [POWER_SUPPLY_TYPEC_SINK_POWERED_CABLE] = "SINK_POWERED_CABLE", + [POWER_SUPPLY_TYPEC_SINK_DEBUG_ACCESSORY] = "SINK_DEBUG_ACCESSORY", + [POWER_SUPPLY_TYPEC_SINK_AUDIO_ADAPTER] = "SINK_AUDIO_ADAPTER", + [POWER_SUPPLY_TYPEC_POWERED_CABLE_ONLY] = "POWERED_CABLE_ONLY", +}; + +static int smblib_get_prop_ufp_mode(struct smb_charger *chg) +{ + switch (chg->typec_status[0]) { + case UFP_TYPEC_RDSTD_BIT: + return POWER_SUPPLY_TYPEC_SOURCE_DEFAULT; + case UFP_TYPEC_RD1P5_BIT: + return POWER_SUPPLY_TYPEC_SOURCE_MEDIUM; + case UFP_TYPEC_RD3P0_BIT: + return POWER_SUPPLY_TYPEC_SOURCE_HIGH; + default: + break; + } + + return POWER_SUPPLY_TYPEC_NONE; +} + +static int smblib_get_prop_dfp_mode(struct smb_charger *chg) +{ + switch (chg->typec_status[1] & DFP_TYPEC_MASK) { + case DFP_RA_RA_BIT: + return POWER_SUPPLY_TYPEC_SINK_AUDIO_ADAPTER; + case DFP_RD_RD_BIT: + return POWER_SUPPLY_TYPEC_SINK_DEBUG_ACCESSORY; + case DFP_RD_RA_VCONN_BIT: + return POWER_SUPPLY_TYPEC_SINK_POWERED_CABLE; + case DFP_RD_OPEN_BIT: + return POWER_SUPPLY_TYPEC_SINK; + default: + break; + } + + return POWER_SUPPLY_TYPEC_NONE; +} + +static int smblib_get_prop_typec_mode(struct smb_charger *chg) +{ + if (chg->typec_status[3] & UFP_DFP_MODE_STATUS_BIT) + return smblib_get_prop_dfp_mode(chg); + else + return smblib_get_prop_ufp_mode(chg); +} + +int smblib_get_prop_typec_power_role(struct smb_charger *chg, + union power_supply_propval *val) +{ + int rc = 0; + u8 ctrl; + + rc = smblib_read(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG, &ctrl); + if (rc < 0) { + smblib_err(chg, "Couldn't read TYPE_C_INTRPT_ENB_SOFTWARE_CTRL rc=%d\n", + rc); + return rc; + } + smblib_dbg(chg, PR_REGISTER, "TYPE_C_INTRPT_ENB_SOFTWARE_CTRL = 0x%02x\n", + ctrl); + + if (ctrl & TYPEC_DISABLE_CMD_BIT) { + val->intval = POWER_SUPPLY_TYPEC_PR_NONE; + return rc; + } + + switch (ctrl & (DFP_EN_CMD_BIT | UFP_EN_CMD_BIT)) { + case 0: + val->intval = POWER_SUPPLY_TYPEC_PR_DUAL; + break; + case DFP_EN_CMD_BIT: + val->intval = POWER_SUPPLY_TYPEC_PR_SOURCE; + break; + case UFP_EN_CMD_BIT: + val->intval = POWER_SUPPLY_TYPEC_PR_SINK; + break; + default: + val->intval = POWER_SUPPLY_TYPEC_PR_NONE; + smblib_err(chg, "unsupported power role 0x%02lx\n", + ctrl & (DFP_EN_CMD_BIT | UFP_EN_CMD_BIT)); + return -EINVAL; + } + + return rc; +} + +int smblib_get_prop_pd_allowed(struct smb_charger *chg, + union power_supply_propval *val) +{ + val->intval = get_effective_result(chg->pd_allowed_votable); + return 0; +} + +int smblib_get_prop_input_current_settled(struct smb_charger *chg, + union power_supply_propval *val) +{ + return smblib_get_charge_param(chg, &chg->param.icl_stat, &val->intval); +} + +#define HVDCP3_STEP_UV 200000 +int smblib_get_prop_input_voltage_settled(struct smb_charger *chg, + union power_supply_propval *val) +{ + int rc, pulses; + + switch (chg->real_charger_type) { + case POWER_SUPPLY_TYPE_USB_HVDCP_3: + rc = smblib_get_pulse_cnt(chg, &pulses); + if (rc < 0) { + smblib_err(chg, + "Couldn't read QC_PULSE_COUNT rc=%d\n", rc); + return 0; + } + val->intval = MICRO_5V + HVDCP3_STEP_UV * pulses; + break; + case POWER_SUPPLY_TYPE_USB_PD: + val->intval = chg->voltage_min_uv; + break; + default: + val->intval = MICRO_5V; + break; + } + + return 0; +} + +int smblib_get_prop_pd_in_hard_reset(struct smb_charger *chg, + union power_supply_propval *val) +{ + val->intval = chg->pd_hard_reset; + return 0; +} + +int smblib_get_pe_start(struct smb_charger *chg, + union power_supply_propval *val) +{ + /* + * hvdcp timeout voter is the last one to allow pd. Use its vote + * to indicate start of pe engine + */ + val->intval + = !get_client_vote_locked(chg->pd_disallowed_votable_indirect, + HVDCP_TIMEOUT_VOTER); + return 0; +} + +int smblib_get_prop_die_health(struct smb_charger *chg, + union power_supply_propval *val) +{ + int rc; + u8 stat; + + rc = smblib_read(chg, TEMP_RANGE_STATUS_REG, &stat); + if (rc < 0) { + smblib_err(chg, "Couldn't read TEMP_RANGE_STATUS_REG rc=%d\n", + rc); + return rc; + } + + if (stat & ALERT_LEVEL_BIT) + val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; + else if (stat & TEMP_ABOVE_RANGE_BIT) + val->intval = POWER_SUPPLY_HEALTH_HOT; + else if (stat & TEMP_WITHIN_RANGE_BIT) + val->intval = POWER_SUPPLY_HEALTH_WARM; + else if (stat & TEMP_BELOW_RANGE_BIT) + val->intval = POWER_SUPPLY_HEALTH_COOL; + else + val->intval = POWER_SUPPLY_HEALTH_UNKNOWN; + + return 0; +} + +#define SDP_CURRENT_UA 500000 +#define CDP_CURRENT_UA 1500000 +#define DCP_CURRENT_UA 1500000 +#define HVDCP_CURRENT_UA 3000000 +#define TYPEC_DEFAULT_CURRENT_UA 900000 +#define TYPEC_MEDIUM_CURRENT_UA 1500000 +#define TYPEC_HIGH_CURRENT_UA 3000000 +static int get_rp_based_dcp_current(struct smb_charger *chg, int typec_mode) +{ + int rp_ua; + + switch (typec_mode) { + case POWER_SUPPLY_TYPEC_SOURCE_HIGH: + rp_ua = TYPEC_HIGH_CURRENT_UA; + break; + case POWER_SUPPLY_TYPEC_SOURCE_MEDIUM: + case POWER_SUPPLY_TYPEC_SOURCE_DEFAULT: + /* fall through */ + default: + rp_ua = DCP_CURRENT_UA; + } + + return rp_ua; +} + +/******************* + * USB PSY SETTERS * + * *****************/ + +int smblib_set_prop_pd_current_max(struct smb_charger *chg, + const union power_supply_propval *val) +{ + int rc; + + if (chg->pd_active) + rc = vote(chg->usb_icl_votable, PD_VOTER, true, val->intval); + else + rc = -EPERM; + + return rc; +} + +static int smblib_handle_usb_current(struct smb_charger *chg, + int usb_current) +{ + int rc = 0, rp_ua, typec_mode; + + if (chg->real_charger_type == POWER_SUPPLY_TYPE_USB_FLOAT) { + if (usb_current == -ETIMEDOUT) { + /* + * Valid FLOAT charger, report the current based + * of Rp + */ + typec_mode = smblib_get_prop_typec_mode(chg); + rp_ua = get_rp_based_dcp_current(chg, typec_mode); + rc = vote(chg->usb_icl_votable, LEGACY_UNKNOWN_VOTER, + true, rp_ua); + if (rc < 0) + return rc; + } else { + /* + * FLOAT charger detected as SDP by USB driver, + * charge with the requested current and update the + * real_charger_type + */ + chg->real_charger_type = POWER_SUPPLY_TYPE_USB; + rc = vote(chg->usb_icl_votable, USB_PSY_VOTER, + true, usb_current); + if (rc < 0) + return rc; + rc = vote(chg->usb_icl_votable, LEGACY_UNKNOWN_VOTER, + false, 0); + if (rc < 0) + return rc; + } + } else if (chg->real_charger_type == POWER_SUPPLY_TYPE_USB && + usb_current == -ETIMEDOUT) { + rc = vote(chg->usb_icl_votable, USB_PSY_VOTER, + true, USBIN_100MA); + } else { + rc = vote(chg->usb_icl_votable, USB_PSY_VOTER, + true, usb_current); + } + + return rc; +} + +int smblib_set_prop_sdp_current_max(struct smb_charger *chg, + const union power_supply_propval *val) +{ + int rc = 0; + + if (!chg->pd_active) { + rc = smblib_handle_usb_current(chg, val->intval); + } else if (chg->system_suspend_supported) { + if (val->intval <= USBIN_25MA) + rc = vote(chg->usb_icl_votable, + PD_SUSPEND_SUPPORTED_VOTER, true, val->intval); + else + rc = vote(chg->usb_icl_votable, + PD_SUSPEND_SUPPORTED_VOTER, false, 0); + } + return rc; +} + +int smblib_set_prop_boost_current(struct smb_charger *chg, + const union power_supply_propval *val) +{ + int rc = 0; + + rc = smblib_set_charge_param(chg, &chg->param.freq_boost, + val->intval <= chg->boost_threshold_ua ? + chg->chg_freq.freq_below_otg_threshold : + chg->chg_freq.freq_above_otg_threshold); + if (rc < 0) { + dev_err(chg->dev, "Error in setting freq_boost rc=%d\n", rc); + return rc; + } + + chg->boost_current_ua = val->intval; + return rc; +} + +int smblib_set_prop_typec_power_role(struct smb_charger *chg, + const union power_supply_propval *val) +{ + /* Check if power role switch is disabled */ + if (!get_effective_result(chg->disable_power_role_switch)) + return __smblib_set_prop_typec_power_role(chg, val); + + return 0; +} + +int smblib_set_prop_pd_voltage_min(struct smb_charger *chg, + const union power_supply_propval *val) +{ + int rc, min_uv; + + min_uv = min(val->intval, chg->voltage_max_uv); + rc = smblib_set_usb_pd_allowed_voltage(chg, min_uv, + chg->voltage_max_uv); + if (rc < 0) { + smblib_err(chg, "invalid max voltage %duV rc=%d\n", + val->intval, rc); + return rc; + } + + chg->voltage_min_uv = min_uv; + power_supply_changed(chg->usb_main_psy); + return rc; +} + +int smblib_set_prop_pd_voltage_max(struct smb_charger *chg, + const union power_supply_propval *val) +{ + int rc, max_uv; + + max_uv = max(val->intval, chg->voltage_min_uv); + rc = smblib_set_usb_pd_allowed_voltage(chg, chg->voltage_min_uv, + max_uv); + if (rc < 0) { + smblib_err(chg, "invalid min voltage %duV rc=%d\n", + val->intval, rc); + return rc; + } + + chg->voltage_max_uv = max_uv; + return rc; +} + +static int __smblib_set_prop_pd_active(struct smb_charger *chg, bool pd_active) +{ + int rc; + bool orientation, sink_attached, hvdcp; + u8 stat; + + chg->pd_active = pd_active; + if (chg->pd_active) { + chg->real_charger_type = POWER_SUPPLY_TYPE_USB_PD; + vote(chg->apsd_disable_votable, PD_VOTER, true, 0); + vote(chg->pd_allowed_votable, PD_VOTER, true, 0); + vote(chg->usb_irq_enable_votable, PD_VOTER, true, 0); + + /* + * VCONN_EN_ORIENTATION_BIT controls whether to use CC1 or CC2 + * line when TYPEC_SPARE_CFG_BIT (CC pin selection s/w override) + * is set or when VCONN_EN_VALUE_BIT is set. + */ + orientation = chg->typec_status[3] & CC_ORIENTATION_BIT; + rc = smblib_masked_write(chg, + TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG, + VCONN_EN_ORIENTATION_BIT, + orientation ? 0 : VCONN_EN_ORIENTATION_BIT); + if (rc < 0) + smblib_err(chg, + "Couldn't enable vconn on CC line rc=%d\n", rc); + + /* SW controlled CC_OUT */ + rc = smblib_masked_write(chg, TAPER_TIMER_SEL_CFG_REG, + TYPEC_SPARE_CFG_BIT, TYPEC_SPARE_CFG_BIT); + if (rc < 0) + smblib_err(chg, "Couldn't enable SW cc_out rc=%d\n", + rc); + + /* + * Enforce 500mA for PD until the real vote comes in later. + * It is guaranteed that pd_active is set prior to + * pd_current_max + */ + rc = vote(chg->usb_icl_votable, PD_VOTER, true, USBIN_500MA); + if (rc < 0) + smblib_err(chg, "Couldn't vote for USB ICL rc=%d\n", + rc); + + /* since PD was found the cable must be non-legacy */ + vote(chg->usb_icl_votable, LEGACY_UNKNOWN_VOTER, false, 0); + + /* clear USB ICL vote for DCP_VOTER */ + rc = vote(chg->usb_icl_votable, DCP_VOTER, false, 0); + if (rc < 0) + smblib_err(chg, "Couldn't un-vote DCP from USB ICL rc=%d\n", + rc); + + /* remove USB_PSY_VOTER */ + rc = vote(chg->usb_icl_votable, USB_PSY_VOTER, false, 0); + if (rc < 0) + smblib_err(chg, "Couldn't unvote USB_PSY rc=%d\n", rc); + } else { + rc = smblib_read(chg, APSD_STATUS_REG, &stat); + if (rc < 0) { + smblib_err(chg, "Couldn't read APSD status rc=%d\n", + rc); + return rc; + } + + hvdcp = stat & QC_CHARGER_BIT; + vote(chg->apsd_disable_votable, PD_VOTER, false, 0); + vote(chg->pd_allowed_votable, PD_VOTER, false, 0); + vote(chg->usb_irq_enable_votable, PD_VOTER, false, 0); + vote(chg->hvdcp_disable_votable_indirect, PD_INACTIVE_VOTER, + false, 0); + + /* HW controlled CC_OUT */ + rc = smblib_masked_write(chg, TAPER_TIMER_SEL_CFG_REG, + TYPEC_SPARE_CFG_BIT, 0); + if (rc < 0) + smblib_err(chg, "Couldn't enable HW cc_out rc=%d\n", + rc); + + /* + * This WA should only run for HVDCP. Non-legacy SDP/CDP could + * draw more, but this WA will remove Rd causing VBUS to drop, + * and data could be interrupted. Non-legacy DCP could also draw + * more, but it may impact compliance. + */ + sink_attached = chg->typec_status[3] & UFP_DFP_MODE_STATUS_BIT; + if ((chg->connector_type != POWER_SUPPLY_CONNECTOR_MICRO_USB) + && !chg->typec_legacy_valid + && !sink_attached && hvdcp) + schedule_work(&chg->legacy_detection_work); + } + + smblib_update_usb_type(chg); + power_supply_changed(chg->usb_psy); + return rc; +} + +int smblib_set_prop_pd_active(struct smb_charger *chg, + const union power_supply_propval *val) +{ + if (!get_effective_result(chg->pd_allowed_votable)) + return -EINVAL; + + return __smblib_set_prop_pd_active(chg, val->intval); +} + +int smblib_set_prop_ship_mode(struct smb_charger *chg, + const union power_supply_propval *val) +{ + int rc; + + smblib_dbg(chg, PR_MISC, "Set ship mode: %d!!\n", !!val->intval); + + rc = smblib_masked_write(chg, SHIP_MODE_REG, SHIP_MODE_EN_BIT, + !!val->intval ? SHIP_MODE_EN_BIT : 0); + if (rc < 0) + dev_err(chg->dev, "Couldn't %s ship mode, rc=%d\n", + !!val->intval ? "enable" : "disable", rc); + + return rc; +} + +int smblib_reg_block_update(struct smb_charger *chg, + struct reg_info *entry) +{ + int rc = 0; + + while (entry && entry->reg) { + rc = smblib_read(chg, entry->reg, &entry->bak); + if (rc < 0) { + dev_err(chg->dev, "Error in reading %s rc=%d\n", + entry->desc, rc); + break; + } + entry->bak &= entry->mask; + + rc = smblib_masked_write(chg, entry->reg, + entry->mask, entry->val); + if (rc < 0) { + dev_err(chg->dev, "Error in writing %s rc=%d\n", + entry->desc, rc); + break; + } + entry++; + } + + return rc; +} + +int smblib_reg_block_restore(struct smb_charger *chg, + struct reg_info *entry) +{ + int rc = 0; + + while (entry && entry->reg) { + rc = smblib_masked_write(chg, entry->reg, + entry->mask, entry->bak); + if (rc < 0) { + dev_err(chg->dev, "Error in writing %s rc=%d\n", + entry->desc, rc); + break; + } + entry++; + } + + return rc; +} + +static struct reg_info cc2_detach_settings[] = { + { + .reg = TYPE_C_CFG_2_REG, + .mask = TYPE_C_UFP_MODE_BIT | EN_TRY_SOURCE_MODE_BIT, + .val = TYPE_C_UFP_MODE_BIT, + .desc = "TYPE_C_CFG_2_REG", + }, + { + .reg = TYPE_C_CFG_3_REG, + .mask = EN_TRYSINK_MODE_BIT, + .val = 0, + .desc = "TYPE_C_CFG_3_REG", + }, + { + .reg = TAPER_TIMER_SEL_CFG_REG, + .mask = TYPEC_SPARE_CFG_BIT, + .val = TYPEC_SPARE_CFG_BIT, + .desc = "TAPER_TIMER_SEL_CFG_REG", + }, + { + .reg = TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG, + .mask = VCONN_EN_ORIENTATION_BIT, + .val = 0, + .desc = "TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG", + }, + { + .reg = MISC_CFG_REG, + .mask = TCC_DEBOUNCE_20MS_BIT, + .val = TCC_DEBOUNCE_20MS_BIT, + .desc = "Tccdebounce time" + }, + { + }, +}; + +static int smblib_cc2_sink_removal_enter(struct smb_charger *chg) +{ + int rc, ccout, ufp_mode; + u8 stat; + + if ((chg->wa_flags & TYPEC_CC2_REMOVAL_WA_BIT) == 0) + return 0; + + if (chg->cc2_detach_wa_active) + return 0; + + rc = smblib_read(chg, TYPE_C_STATUS_4_REG, &stat); + if (rc < 0) { + smblib_err(chg, "Couldn't read TYPE_C_STATUS_4 rc=%d\n", rc); + return rc; + } + + ccout = (stat & CC_ATTACHED_BIT) ? + (!!(stat & CC_ORIENTATION_BIT) + 1) : 0; + ufp_mode = (stat & TYPEC_DEBOUNCE_DONE_STATUS_BIT) ? + !(stat & UFP_DFP_MODE_STATUS_BIT) : 0; + + if (ccout != 2) + return 0; + + if (!ufp_mode) + return 0; + + chg->cc2_detach_wa_active = true; + /* The CC2 removal WA will cause a type-c-change IRQ storm */ + smblib_reg_block_update(chg, cc2_detach_settings); + schedule_work(&chg->rdstd_cc2_detach_work); + return rc; +} + +static int smblib_cc2_sink_removal_exit(struct smb_charger *chg) +{ + if ((chg->wa_flags & TYPEC_CC2_REMOVAL_WA_BIT) == 0) + return 0; + + if (!chg->cc2_detach_wa_active) + return 0; + + chg->cc2_detach_wa_active = false; + chg->in_chg_lock = true; + cancel_work_sync(&chg->rdstd_cc2_detach_work); + chg->in_chg_lock = false; + smblib_reg_block_restore(chg, cc2_detach_settings); + return 0; +} + +int smblib_set_prop_pd_in_hard_reset(struct smb_charger *chg, + const union power_supply_propval *val) +{ + int rc = 0; + + if (chg->pd_hard_reset == val->intval) + return rc; + + chg->pd_hard_reset = val->intval; + rc = smblib_masked_write(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG, + EXIT_SNK_BASED_ON_CC_BIT, + (chg->pd_hard_reset) ? EXIT_SNK_BASED_ON_CC_BIT : 0); + if (rc < 0) + smblib_err(chg, "Couldn't set EXIT_SNK_BASED_ON_CC rc=%d\n", + rc); + + vote(chg->apsd_disable_votable, PD_HARD_RESET_VOTER, + chg->pd_hard_reset, 0); + + return rc; +} + +static int smblib_recover_from_soft_jeita(struct smb_charger *chg) +{ + u8 stat_1, stat_2; + int rc; + + rc = smblib_read(chg, BATTERY_CHARGER_STATUS_1_REG, &stat_1); + if (rc < 0) { + smblib_err(chg, "Couldn't read BATTERY_CHARGER_STATUS_1 rc=%d\n", + rc); + return rc; + } + + rc = smblib_read(chg, BATTERY_CHARGER_STATUS_2_REG, &stat_2); + if (rc < 0) { + smblib_err(chg, "Couldn't read BATTERY_CHARGER_STATUS_2 rc=%d\n", + rc); + return rc; + } + + if ((chg->jeita_status && !(stat_2 & BAT_TEMP_STATUS_SOFT_LIMIT_MASK) && + ((stat_1 & BATTERY_CHARGER_STATUS_MASK) == TERMINATE_CHARGE))) { + /* + * We are moving from JEITA soft -> Normal and charging + * is terminated + */ + rc = smblib_write(chg, CHARGING_ENABLE_CMD_REG, 0); + if (rc < 0) { + smblib_err(chg, "Couldn't disable charging rc=%d\n", + rc); + return rc; + } + rc = smblib_write(chg, CHARGING_ENABLE_CMD_REG, + CHARGING_ENABLE_CMD_BIT); + if (rc < 0) { + smblib_err(chg, "Couldn't enable charging rc=%d\n", + rc); + return rc; + } + } + + chg->jeita_status = stat_2 & BAT_TEMP_STATUS_SOFT_LIMIT_MASK; + + return 0; +} + +/************************ + * USB MAIN PSY GETTERS * + ************************/ +int smblib_get_prop_fcc_delta(struct smb_charger *chg, + union power_supply_propval *val) +{ + int rc, jeita_cc_delta_ua = 0; + + if (chg->sw_jeita_enabled) { + val->intval = 0; + return 0; + } + + rc = smblib_get_jeita_cc_delta(chg, &jeita_cc_delta_ua); + if (rc < 0) { + smblib_err(chg, "Couldn't get jeita cc delta rc=%d\n", rc); + jeita_cc_delta_ua = 0; + } + + val->intval = jeita_cc_delta_ua; + return 0; +} + +/************************ + * USB MAIN PSY SETTERS * + ************************/ +int smblib_get_charge_current(struct smb_charger *chg, + int *total_current_ua) +{ + const struct apsd_result *apsd_result = smblib_get_apsd_result(chg); + union power_supply_propval val = {0, }; + int rc = 0, typec_source_rd, current_ua; + bool non_compliant; + u8 stat5; + + if (chg->pd_active) { + *total_current_ua = + get_client_vote_locked(chg->usb_icl_votable, PD_VOTER); + return rc; + } + + rc = smblib_read(chg, TYPE_C_STATUS_5_REG, &stat5); + if (rc < 0) { + smblib_err(chg, "Couldn't read TYPE_C_STATUS_5 rc=%d\n", rc); + return rc; + } + non_compliant = stat5 & TYPEC_NONCOMP_LEGACY_CABLE_STATUS_BIT; + + /* get settled ICL */ + rc = smblib_get_prop_input_current_settled(chg, &val); + if (rc < 0) { + smblib_err(chg, "Couldn't get settled ICL rc=%d\n", rc); + return rc; + } + + typec_source_rd = smblib_get_prop_ufp_mode(chg); + + /* QC 2.0/3.0 adapter */ + if (apsd_result->bit & (QC_3P0_BIT | QC_2P0_BIT)) { + *total_current_ua = HVDCP_CURRENT_UA; + return 0; + } + + if (non_compliant) { + switch (apsd_result->bit) { + case CDP_CHARGER_BIT: + current_ua = CDP_CURRENT_UA; + break; + case DCP_CHARGER_BIT: + case OCP_CHARGER_BIT: + case FLOAT_CHARGER_BIT: + current_ua = DCP_CURRENT_UA; + break; + default: + current_ua = 0; + break; + } + + *total_current_ua = max(current_ua, val.intval); + return 0; + } + + switch (typec_source_rd) { + case POWER_SUPPLY_TYPEC_SOURCE_DEFAULT: + switch (apsd_result->bit) { + case CDP_CHARGER_BIT: + current_ua = CDP_CURRENT_UA; + break; + case DCP_CHARGER_BIT: + case OCP_CHARGER_BIT: + case FLOAT_CHARGER_BIT: + current_ua = chg->default_icl_ua; + break; + default: + current_ua = 0; + break; + } + break; + case POWER_SUPPLY_TYPEC_SOURCE_MEDIUM: + current_ua = TYPEC_MEDIUM_CURRENT_UA; + break; + case POWER_SUPPLY_TYPEC_SOURCE_HIGH: + current_ua = TYPEC_HIGH_CURRENT_UA; + break; + case POWER_SUPPLY_TYPEC_NON_COMPLIANT: + case POWER_SUPPLY_TYPEC_NONE: + default: + current_ua = 0; + break; + } + + *total_current_ua = max(current_ua, val.intval); + return 0; +} + +/************************ + * PARALLEL PSY GETTERS * + ************************/ + +int smblib_get_prop_slave_current_now(struct smb_charger *chg, + union power_supply_propval *pval) +{ + if (IS_ERR_OR_NULL(chg->iio.batt_i_chan)) + chg->iio.batt_i_chan = iio_channel_get(chg->dev, "batt_i"); + + if (IS_ERR(chg->iio.batt_i_chan)) + return PTR_ERR(chg->iio.batt_i_chan); + + return iio_read_channel_processed(chg->iio.batt_i_chan, &pval->intval); +} + +/********************** + * INTERRUPT HANDLERS * + **********************/ + +irqreturn_t smblib_handle_debug(int irq, void *data) +{ + struct smb_irq_data *irq_data = data; + struct smb_charger *chg = irq_data->parent_data; + + smblib_dbg(chg, PR_INTERRUPT, "IRQ: %s\n", irq_data->name); + return IRQ_HANDLED; +} + +irqreturn_t smblib_handle_otg_overcurrent(int irq, void *data) +{ + struct smb_irq_data *irq_data = data; + struct smb_charger *chg = irq_data->parent_data; + int rc; + u8 stat; + + rc = smblib_read(chg, OTG_BASE + INT_RT_STS_OFFSET, &stat); + if (rc < 0) { + dev_err(chg->dev, "Couldn't read OTG_INT_RT_STS rc=%d\n", rc); + return IRQ_HANDLED; + } + + if (chg->wa_flags & OTG_WA) { + if (stat & OTG_OC_DIS_SW_STS_RT_STS_BIT) + smblib_err(chg, "OTG disabled by hw\n"); + + /* not handling software based hiccups for PM660 */ + return IRQ_HANDLED; + } + + if (stat & OTG_OVERCURRENT_RT_STS_BIT) + schedule_work(&chg->otg_oc_work); + + return IRQ_HANDLED; +} + +irqreturn_t smblib_handle_chg_state_change(int irq, void *data) +{ + struct smb_irq_data *irq_data = data; + struct smb_charger *chg = irq_data->parent_data; + u8 stat; + int rc; + + smblib_dbg(chg, PR_INTERRUPT, "IRQ: %s\n", irq_data->name); + + rc = smblib_read(chg, BATTERY_CHARGER_STATUS_1_REG, &stat); + if (rc < 0) { + smblib_err(chg, "Couldn't read BATTERY_CHARGER_STATUS_1 rc=%d\n", + rc); + return IRQ_HANDLED; + } + + stat = stat & BATTERY_CHARGER_STATUS_MASK; + power_supply_changed(chg->batt_psy); + return IRQ_HANDLED; +} + +irqreturn_t smblib_handle_batt_temp_changed(int irq, void *data) +{ + struct smb_irq_data *irq_data = data; + struct smb_charger *chg = irq_data->parent_data; + int rc; + + rc = smblib_recover_from_soft_jeita(chg); + if (rc < 0) { + smblib_err(chg, "Couldn't recover chg from soft jeita rc=%d\n", + rc); + return IRQ_HANDLED; + } + + rerun_election(chg->fcc_votable); + power_supply_changed(chg->batt_psy); + return IRQ_HANDLED; +} + +irqreturn_t smblib_handle_batt_psy_changed(int irq, void *data) +{ + struct smb_irq_data *irq_data = data; + struct smb_charger *chg = irq_data->parent_data; + + smblib_dbg(chg, PR_INTERRUPT, "IRQ: %s\n", irq_data->name); + power_supply_changed(chg->batt_psy); + return IRQ_HANDLED; +} + +irqreturn_t smblib_handle_usb_psy_changed(int irq, void *data) +{ + struct smb_irq_data *irq_data = data; + struct smb_charger *chg = irq_data->parent_data; + + smblib_dbg(chg, PR_INTERRUPT, "IRQ: %s\n", irq_data->name); + power_supply_changed(chg->usb_psy); + return IRQ_HANDLED; +} + +irqreturn_t smblib_handle_usbin_uv(int irq, void *data) +{ + struct smb_irq_data *irq_data = data; + struct smb_charger *chg = irq_data->parent_data; + struct storm_watch *wdata; + const struct apsd_result *apsd = smblib_get_apsd_result(chg); + int rc; + u8 stat = 0, max_pulses = 0; + + smblib_dbg(chg, PR_INTERRUPT, "IRQ: %s\n", irq_data->name); + if (!chg->irq_info[SWITCH_POWER_OK_IRQ].irq_data) + return IRQ_HANDLED; + + wdata = &chg->irq_info[SWITCH_POWER_OK_IRQ].irq_data->storm_data; + reset_storm_count(wdata); + + if (!chg->non_compliant_chg_detected && + apsd->pst == POWER_SUPPLY_TYPE_USB_HVDCP) { + rc = smblib_read(chg, QC_CHANGE_STATUS_REG, &stat); + if (rc < 0) + smblib_err(chg, + "Couldn't read CHANGE_STATUS_REG rc=%d\n", rc); + + if (stat & QC_5V_BIT) + return IRQ_HANDLED; + + rc = smblib_read(chg, HVDCP_PULSE_COUNT_MAX_REG, &max_pulses); + if (rc < 0) + smblib_err(chg, + "Couldn't read QC2 max pulses rc=%d\n", rc); + + chg->non_compliant_chg_detected = true; + chg->qc2_max_pulses = (max_pulses & + HVDCP_PULSE_COUNT_MAX_QC2_MASK); + + if (stat & QC_12V_BIT) { + rc = smblib_masked_write(chg, HVDCP_PULSE_COUNT_MAX_REG, + HVDCP_PULSE_COUNT_MAX_QC2_MASK, + HVDCP_PULSE_COUNT_MAX_QC2_9V); + if (rc < 0) + smblib_err(chg, "Couldn't force max pulses to 9V rc=%d\n", + rc); + + } else if (stat & QC_9V_BIT) { + rc = smblib_masked_write(chg, HVDCP_PULSE_COUNT_MAX_REG, + HVDCP_PULSE_COUNT_MAX_QC2_MASK, + HVDCP_PULSE_COUNT_MAX_QC2_5V); + if (rc < 0) + smblib_err(chg, "Couldn't force max pulses to 5V rc=%d\n", + rc); + + } + + rc = smblib_masked_write(chg, USBIN_AICL_OPTIONS_CFG_REG, + SUSPEND_ON_COLLAPSE_USBIN_BIT, + 0); + if (rc < 0) + smblib_err(chg, "Couldn't turn off SUSPEND_ON_COLLAPSE_USBIN_BIT rc=%d\n", + rc); + + smblib_rerun_apsd(chg); + } + + return IRQ_HANDLED; +} + +static void smblib_micro_usb_plugin(struct smb_charger *chg, bool vbus_rising) +{ + if (vbus_rising) { + /* use the typec flag even though its not typec */ + chg->typec_present = true; + } else { + chg->typec_present = false; + smblib_update_usb_type(chg); + extcon_set_state_sync(chg->extcon, EXTCON_USB, false); + smblib_uusb_removal(chg); + } +} + +void smblib_usb_plugin_hard_reset_locked(struct smb_charger *chg) +{ + int rc; + u8 stat; + bool vbus_rising; + struct smb_irq_data *data; + struct storm_watch *wdata; + + rc = smblib_read(chg, USBIN_BASE + INT_RT_STS_OFFSET, &stat); + if (rc < 0) { + smblib_err(chg, "Couldn't read USB_INT_RT_STS rc=%d\n", rc); + return; + } + + vbus_rising = (bool)(stat & USBIN_PLUGIN_RT_STS_BIT); + + if (vbus_rising) { + /* Remove FCC_STEPPER 1.5A init vote to allow FCC ramp up */ + if (chg->fcc_stepper_enable) + vote(chg->fcc_votable, FCC_STEPPER_VOTER, false, 0); + + smblib_cc2_sink_removal_exit(chg); + } else { + /* Force 1500mA FCC on USB removal if fcc stepper is enabled */ + if (chg->fcc_stepper_enable) + vote(chg->fcc_votable, FCC_STEPPER_VOTER, + true, 1500000); + + smblib_cc2_sink_removal_enter(chg); + if (chg->wa_flags & BOOST_BACK_WA) { + data = chg->irq_info[SWITCH_POWER_OK_IRQ].irq_data; + if (data) { + wdata = &data->storm_data; + update_storm_count(wdata, + WEAK_CHG_STORM_COUNT); + vote(chg->usb_icl_votable, BOOST_BACK_VOTER, + false, 0); + vote(chg->usb_icl_votable, WEAK_CHARGER_VOTER, + false, 0); + } + } + } + + power_supply_changed(chg->usb_psy); + smblib_dbg(chg, PR_INTERRUPT, "IRQ: usbin-plugin %s\n", + vbus_rising ? "attached" : "detached"); +} + +#define PL_DELAY_MS 30000 +void smblib_usb_plugin_locked(struct smb_charger *chg) +{ + int rc; + u8 stat; + bool vbus_rising; + struct smb_irq_data *data; + struct storm_watch *wdata; + + rc = smblib_read(chg, USBIN_BASE + INT_RT_STS_OFFSET, &stat); + if (rc < 0) { + smblib_err(chg, "Couldn't read USB_INT_RT_STS rc=%d\n", rc); + return; + } + + vbus_rising = (bool)(stat & USBIN_PLUGIN_RT_STS_BIT); + smblib_set_opt_freq_buck(chg, vbus_rising ? chg->chg_freq.freq_5V : + chg->chg_freq.freq_removal); + + if (vbus_rising) { + if (smblib_get_prop_dfp_mode(chg) != POWER_SUPPLY_TYPEC_NONE) { + chg->fake_usb_insertion = true; + return; + } + + rc = smblib_request_dpdm(chg, true); + if (rc < 0) + smblib_err(chg, "Couldn't to enable DPDM rc=%d\n", rc); + + /* Remove FCC_STEPPER 1.5A init vote to allow FCC ramp up */ + if (chg->fcc_stepper_enable) + vote(chg->fcc_votable, FCC_STEPPER_VOTER, false, 0); + + /* Schedule work to enable parallel charger */ + vote(chg->awake_votable, PL_DELAY_VOTER, true, 0); + schedule_delayed_work(&chg->pl_enable_work, + msecs_to_jiffies(PL_DELAY_MS)); + /* vbus rising when APSD was disabled and PD_ACTIVE = 0 */ + if (get_effective_result(chg->apsd_disable_votable) && + !chg->pd_active) + pr_err("APSD disabled on vbus rising without PD\n"); + } else { + if (chg->fake_usb_insertion) { + chg->fake_usb_insertion = false; + return; + } + + if (chg->wa_flags & BOOST_BACK_WA) { + data = chg->irq_info[SWITCH_POWER_OK_IRQ].irq_data; + if (data) { + wdata = &data->storm_data; + update_storm_count(wdata, + WEAK_CHG_STORM_COUNT); + vote(chg->usb_icl_votable, BOOST_BACK_VOTER, + false, 0); + vote(chg->usb_icl_votable, WEAK_CHARGER_VOTER, + false, 0); + } + } + + /* Force 1500mA FCC on removal if fcc stepper is enabled */ + if (chg->fcc_stepper_enable) + vote(chg->fcc_votable, FCC_STEPPER_VOTER, + true, 1500000); + + rc = smblib_request_dpdm(chg, false); + if (rc < 0) + smblib_err(chg, "Couldn't disable DPDM rc=%d\n", rc); + } + + if (chg->connector_type == POWER_SUPPLY_CONNECTOR_MICRO_USB) + smblib_micro_usb_plugin(chg, vbus_rising); + + power_supply_changed(chg->usb_psy); + smblib_dbg(chg, PR_INTERRUPT, "IRQ: usbin-plugin %s\n", + vbus_rising ? "attached" : "detached"); +} + +irqreturn_t smblib_handle_usb_plugin(int irq, void *data) +{ + struct smb_irq_data *irq_data = data; + struct smb_charger *chg = irq_data->parent_data; + + mutex_lock(&chg->lock); + if (chg->pd_hard_reset) + smblib_usb_plugin_hard_reset_locked(chg); + else + smblib_usb_plugin_locked(chg); + mutex_unlock(&chg->lock); + return IRQ_HANDLED; +} + +#define USB_WEAK_INPUT_UA 1400000 +#define ICL_CHANGE_DELAY_MS 1000 +irqreturn_t smblib_handle_icl_change(int irq, void *data) +{ + u8 stat; + int rc, settled_ua, delay = ICL_CHANGE_DELAY_MS; + struct smb_irq_data *irq_data = data; + struct smb_charger *chg = irq_data->parent_data; + + if (chg->mode == PARALLEL_MASTER) { + rc = smblib_read(chg, AICL_STATUS_REG, &stat); + if (rc < 0) { + smblib_err(chg, "Couldn't read AICL_STATUS rc=%d\n", + rc); + return IRQ_HANDLED; + } + + rc = smblib_get_charge_param(chg, &chg->param.icl_stat, + &settled_ua); + if (rc < 0) { + smblib_err(chg, "Couldn't get ICL status rc=%d\n", rc); + return IRQ_HANDLED; + } + + /* If AICL settled then schedule work now */ + if ((settled_ua == get_effective_result(chg->usb_icl_votable)) + || (stat & AICL_DONE_BIT)) + delay = 0; + + cancel_delayed_work_sync(&chg->icl_change_work); + schedule_delayed_work(&chg->icl_change_work, + msecs_to_jiffies(delay)); + } + + return IRQ_HANDLED; +} + +static void smblib_handle_slow_plugin_timeout(struct smb_charger *chg, + bool rising) +{ + smblib_dbg(chg, PR_INTERRUPT, "IRQ: slow-plugin-timeout %s\n", + rising ? "rising" : "falling"); +} + +static void smblib_handle_sdp_enumeration_done(struct smb_charger *chg, + bool rising) +{ + smblib_dbg(chg, PR_INTERRUPT, "IRQ: sdp-enumeration-done %s\n", + rising ? "rising" : "falling"); +} + +#define MICRO_10P3V 10300000 +static void smblib_check_ov_condition(struct smb_charger *chg) +{ + union power_supply_propval pval = {0, }; + int rc; + + if (chg->wa_flags & OV_IRQ_WA_BIT) { + rc = power_supply_get_property(chg->usb_psy, + POWER_SUPPLY_PROP_VOLTAGE_NOW, &pval); + if (rc < 0) { + smblib_err(chg, "Couldn't get current voltage, rc=%d\n", + rc); + return; + } + + if (pval.intval > MICRO_10P3V) { + smblib_err(chg, "USBIN OV detected\n"); + vote(chg->hvdcp_hw_inov_dis_votable, OV_VOTER, true, + 0); + pval.intval = POWER_SUPPLY_DP_DM_FORCE_5V; + rc = power_supply_set_property(chg->batt_psy, + POWER_SUPPLY_PROP_DP_DM, &pval); + return; + } + } +} + +#define QC3_PULSES_FOR_6V 5 +#define QC3_PULSES_FOR_9V 20 +#define QC3_PULSES_FOR_12V 35 +static void smblib_hvdcp_adaptive_voltage_change(struct smb_charger *chg) +{ + int rc; + u8 stat; + int pulses; + + smblib_check_ov_condition(chg); + power_supply_changed(chg->usb_main_psy); + if (chg->real_charger_type == POWER_SUPPLY_TYPE_USB_HVDCP) { + rc = smblib_read(chg, QC_CHANGE_STATUS_REG, &stat); + if (rc < 0) { + smblib_err(chg, + "Couldn't read QC_CHANGE_STATUS rc=%d\n", rc); + return; + } + + switch (stat & QC_2P0_STATUS_MASK) { + case QC_5V_BIT: + smblib_set_opt_freq_buck(chg, + chg->chg_freq.freq_5V); + break; + case QC_9V_BIT: + smblib_set_opt_freq_buck(chg, + chg->chg_freq.freq_9V); + vote(chg->usb_icl_votable, HVDCP2_ICL_VOTER, false, 0); + break; + case QC_12V_BIT: + smblib_set_opt_freq_buck(chg, + chg->chg_freq.freq_12V); + vote(chg->usb_icl_votable, HVDCP2_ICL_VOTER, false, 0); + break; + default: + smblib_set_opt_freq_buck(chg, + chg->chg_freq.freq_removal); + break; + } + } + + if (chg->real_charger_type == POWER_SUPPLY_TYPE_USB_HVDCP_3) { + rc = smblib_get_pulse_cnt(chg, &pulses); + if (rc < 0) { + smblib_err(chg, + "Couldn't read QC_PULSE_COUNT rc=%d\n", rc); + return; + } + + if (pulses < QC3_PULSES_FOR_6V) + smblib_set_opt_freq_buck(chg, + chg->chg_freq.freq_5V); + else if (pulses < QC3_PULSES_FOR_9V) + smblib_set_opt_freq_buck(chg, + chg->chg_freq.freq_6V_8V); + else if (pulses < QC3_PULSES_FOR_12V) + smblib_set_opt_freq_buck(chg, + chg->chg_freq.freq_9V); + else + smblib_set_opt_freq_buck(chg, + chg->chg_freq.freq_12V); + } +} + +/* triggers when HVDCP 3.0 authentication has finished */ +static void smblib_handle_hvdcp_3p0_auth_done(struct smb_charger *chg, + bool rising) +{ + const struct apsd_result *apsd_result; + int rc; + + if (!rising) + return; + + if (chg->wa_flags & QC_AUTH_INTERRUPT_WA_BIT) { + /* + * Disable AUTH_IRQ_EN_CFG_BIT to receive adapter voltage + * change interrupt. + */ + rc = smblib_masked_write(chg, + USBIN_SOURCE_CHANGE_INTRPT_ENB_REG, + AUTH_IRQ_EN_CFG_BIT, 0); + if (rc < 0) + smblib_err(chg, + "Couldn't enable QC auth setting rc=%d\n", rc); + } + + if (chg->mode == PARALLEL_MASTER) + vote(chg->pl_enable_votable_indirect, USBIN_V_VOTER, true, 0); + + /* the APSD done handler will set the USB supply type */ + apsd_result = smblib_get_apsd_result(chg); + + smblib_dbg(chg, PR_INTERRUPT, "IRQ: hvdcp-3p0-auth-done rising; %s detected\n", + apsd_result->name); +} + +static void smblib_handle_hvdcp_check_timeout(struct smb_charger *chg, + bool rising, bool qc_charger) +{ + const struct apsd_result *apsd_result = smblib_get_apsd_result(chg); + + /* Hold off PD only until hvdcp 2.0 detection timeout */ + if (rising) { + vote(chg->pd_disallowed_votable_indirect, HVDCP_TIMEOUT_VOTER, + false, 0); + + /* enable HDC and ICL irq for QC2/3 charger */ + if (qc_charger) + vote(chg->usb_irq_enable_votable, QC_VOTER, true, 0); + + /* + * HVDCP detection timeout done + * If adapter is not QC2.0/QC3.0 - it is a plain old DCP. + */ + if (!qc_charger && (apsd_result->bit & DCP_CHARGER_BIT)) + /* enforce DCP ICL if specified */ + vote(chg->usb_icl_votable, DCP_VOTER, + chg->dcp_icl_ua != -EINVAL, chg->dcp_icl_ua); + + /* + * if pd is not allowed, then set pd_active = false right here, + * so that it starts the hvdcp engine + */ + if (!get_effective_result(chg->pd_allowed_votable)) + __smblib_set_prop_pd_active(chg, 0); + } + + smblib_dbg(chg, PR_INTERRUPT, "IRQ: hvdcp_check_timeout %s\n", + rising ? "rising" : "falling"); +} + +/* triggers when HVDCP is detected */ +static void smblib_handle_hvdcp_detect_done(struct smb_charger *chg, + bool rising) +{ + if (!rising) + return; + + /* the APSD done handler will set the USB supply type */ + cancel_delayed_work_sync(&chg->hvdcp_detect_work); + smblib_dbg(chg, PR_INTERRUPT, "IRQ: hvdcp-detect-done %s\n", + rising ? "rising" : "falling"); +} + +static void smblib_force_legacy_icl(struct smb_charger *chg, int pst) +{ + int typec_mode; + int rp_ua; + + /* while PD is active it should have complete ICL control */ + if (chg->pd_active) + return; + + switch (pst) { + case POWER_SUPPLY_TYPE_USB: + /* + * USB_PSY will vote to increase the current to 500/900mA once + * enumeration is done. Ensure that USB_PSY has at least voted + * for 100mA before releasing the LEGACY_UNKNOWN vote + */ + if (!is_client_vote_enabled(chg->usb_icl_votable, + USB_PSY_VOTER)) + vote(chg->usb_icl_votable, USB_PSY_VOTER, true, 100000); + vote(chg->usb_icl_votable, LEGACY_UNKNOWN_VOTER, false, 0); + break; + case POWER_SUPPLY_TYPE_USB_CDP: + vote(chg->usb_icl_votable, LEGACY_UNKNOWN_VOTER, true, 1500000); + break; + case POWER_SUPPLY_TYPE_USB_DCP: + typec_mode = smblib_get_prop_typec_mode(chg); + rp_ua = get_rp_based_dcp_current(chg, typec_mode); + vote(chg->usb_icl_votable, LEGACY_UNKNOWN_VOTER, true, rp_ua); + break; + case POWER_SUPPLY_TYPE_USB_FLOAT: + /* + * limit ICL to 100mA, the USB driver will enumerate to check + * if this is a SDP and appropriately set the current + */ + vote(chg->usb_icl_votable, LEGACY_UNKNOWN_VOTER, true, 100000); + break; + case POWER_SUPPLY_TYPE_USB_HVDCP: + case POWER_SUPPLY_TYPE_USB_HVDCP_3: + vote(chg->usb_icl_votable, LEGACY_UNKNOWN_VOTER, true, 3000000); + break; + default: + smblib_err(chg, "Unknown APSD %d; forcing 500mA\n", pst); + vote(chg->usb_icl_votable, LEGACY_UNKNOWN_VOTER, true, 500000); + break; + } +} + +static void smblib_notify_extcon_props(struct smb_charger *chg, int id) +{ + union extcon_property_value val; + union power_supply_propval prop_val; + + smblib_get_prop_typec_cc_orientation(chg, &prop_val); + val.intval = ((prop_val.intval == 2) ? 1 : 0); + extcon_set_property(chg->extcon, id, + EXTCON_PROP_USB_TYPEC_POLARITY, val); + + val.intval = true; + extcon_set_property(chg->extcon, id, + EXTCON_PROP_USB_SS, val); +} + +static void smblib_notify_device_mode(struct smb_charger *chg, bool enable) +{ + if (enable) + smblib_notify_extcon_props(chg, EXTCON_USB); + + extcon_set_state_sync(chg->extcon, EXTCON_USB, enable); +} + +static void smblib_notify_usb_host(struct smb_charger *chg, bool enable) +{ + if (enable) + smblib_notify_extcon_props(chg, EXTCON_USB_HOST); + + extcon_set_state_sync(chg->extcon, EXTCON_USB_HOST, enable); +} + +#define HVDCP_DET_MS 2500 +static void smblib_handle_apsd_done(struct smb_charger *chg, bool rising) +{ + const struct apsd_result *apsd_result; + + if (!rising) + return; + + apsd_result = smblib_update_usb_type(chg); + + if (!chg->typec_legacy_valid) + smblib_force_legacy_icl(chg, apsd_result->pst); + + switch (apsd_result->bit) { + case SDP_CHARGER_BIT: + case CDP_CHARGER_BIT: + /* if not DCP, Enable pd here */ + vote(chg->pd_disallowed_votable_indirect, HVDCP_TIMEOUT_VOTER, + false, 0); + if (chg->connector_type == POWER_SUPPLY_CONNECTOR_MICRO_USB + || chg->use_extcon) + smblib_notify_device_mode(chg, true); + break; + case OCP_CHARGER_BIT: + case FLOAT_CHARGER_BIT: + /* if not DCP then no hvdcp timeout happens, Enable pd here. */ + vote(chg->pd_disallowed_votable_indirect, HVDCP_TIMEOUT_VOTER, + false, 0); + break; + case DCP_CHARGER_BIT: + if (chg->wa_flags & QC_CHARGER_DETECTION_WA_BIT) + schedule_delayed_work(&chg->hvdcp_detect_work, + msecs_to_jiffies(HVDCP_DET_MS)); + break; + default: + break; + } + + smblib_dbg(chg, PR_INTERRUPT, "IRQ: apsd-done rising; %s detected\n", + apsd_result->name); +} + +irqreturn_t smblib_handle_usb_source_change(int irq, void *data) +{ + struct smb_irq_data *irq_data = data; + struct smb_charger *chg = irq_data->parent_data; + int rc = 0; + u8 stat; + + if (chg->fake_usb_insertion) + return IRQ_HANDLED; + + rc = smblib_read(chg, APSD_STATUS_REG, &stat); + if (rc < 0) { + smblib_err(chg, "Couldn't read APSD_STATUS rc=%d\n", rc); + return IRQ_HANDLED; + } + smblib_dbg(chg, PR_REGISTER, "APSD_STATUS = 0x%02x\n", stat); + + if ((chg->connector_type == POWER_SUPPLY_CONNECTOR_MICRO_USB) + && (stat & APSD_DTC_STATUS_DONE_BIT) + && !chg->uusb_apsd_rerun_done) { + /* + * Force re-run APSD to handle slow insertion related + * charger-mis-detection. + */ + chg->uusb_apsd_rerun_done = true; + smblib_rerun_apsd(chg); + return IRQ_HANDLED; + } + + smblib_handle_apsd_done(chg, + (bool)(stat & APSD_DTC_STATUS_DONE_BIT)); + + smblib_handle_hvdcp_detect_done(chg, + (bool)(stat & QC_CHARGER_BIT)); + + smblib_handle_hvdcp_check_timeout(chg, + (bool)(stat & HVDCP_CHECK_TIMEOUT_BIT), + (bool)(stat & QC_CHARGER_BIT)); + + smblib_handle_hvdcp_3p0_auth_done(chg, + (bool)(stat & QC_AUTH_DONE_STATUS_BIT)); + + smblib_handle_sdp_enumeration_done(chg, + (bool)(stat & ENUMERATION_DONE_BIT)); + + smblib_handle_slow_plugin_timeout(chg, + (bool)(stat & SLOW_PLUGIN_TIMEOUT_BIT)); + + smblib_hvdcp_adaptive_voltage_change(chg); + + power_supply_changed(chg->usb_psy); + + rc = smblib_read(chg, APSD_STATUS_REG, &stat); + if (rc < 0) { + smblib_err(chg, "Couldn't read APSD_STATUS rc=%d\n", rc); + return IRQ_HANDLED; + } + smblib_dbg(chg, PR_REGISTER, "APSD_STATUS = 0x%02x\n", stat); + + return IRQ_HANDLED; +} + +static int typec_try_sink(struct smb_charger *chg) +{ + union power_supply_propval val; + bool debounce_done, vbus_detected, sink; + u8 stat; + int exit_mode = ATTACHED_SRC, rc; + int typec_mode; + + if (!(*chg->try_sink_enabled)) + return ATTACHED_SRC; + + typec_mode = smblib_get_prop_typec_mode(chg); + if (typec_mode == POWER_SUPPLY_TYPEC_SINK_AUDIO_ADAPTER + || typec_mode == POWER_SUPPLY_TYPEC_SINK_DEBUG_ACCESSORY) + return ATTACHED_SRC; + + /* + * Try.SNK entry status - ATTACHWAIT.SRC state and detected Rd-open + * or RD-Ra for TccDebounce time. + */ + + /* ignore typec interrupt while try.snk WIP */ + chg->try_sink_active = true; + + /* force SNK mode */ + val.intval = POWER_SUPPLY_TYPEC_PR_SINK; + rc = smblib_set_prop_typec_power_role(chg, &val); + if (rc < 0) { + smblib_err(chg, "Couldn't set UFP mode rc=%d\n", rc); + goto try_sink_exit; + } + + /* reduce Tccdebounce time to ~20ms */ + rc = smblib_masked_write(chg, MISC_CFG_REG, + TCC_DEBOUNCE_20MS_BIT, TCC_DEBOUNCE_20MS_BIT); + if (rc < 0) { + smblib_err(chg, "Couldn't set MISC_CFG_REG rc=%d\n", rc); + goto try_sink_exit; + } + + /* + * give opportunity to the other side to be a SRC, + * for tDRPTRY + Tccdebounce time + */ + msleep(120); + + rc = smblib_read(chg, TYPE_C_STATUS_4_REG, &stat); + if (rc < 0) { + smblib_err(chg, "Couldn't read TYPE_C_STATUS_4 rc=%d\n", + rc); + goto try_sink_exit; + } + + debounce_done = stat & TYPEC_DEBOUNCE_DONE_STATUS_BIT; + + if (!debounce_done) + /* + * The other side didn't switch to source, either it + * is an adamant sink or is removed go back to showing Rp + */ + goto try_wait_src; + + /* + * We are in force sink mode and the other side has switched to + * showing Rp. Config DRP in case the other side removes Rp so we + * can quickly (20ms) switch to showing our Rp. Note that the spec + * needs us to show Rp for 80mS while the drp DFP residency is just + * 54mS. But 54mS is plenty time for us to react and force Rp for + * the remaining 26mS. + */ + val.intval = POWER_SUPPLY_TYPEC_PR_DUAL; + rc = smblib_set_prop_typec_power_role(chg, &val); + if (rc < 0) { + smblib_err(chg, "Couldn't set DFP mode rc=%d\n", + rc); + goto try_sink_exit; + } + + /* + * while other side is Rp, wait for VBUS from it; exit if other side + * removes Rp + */ + do { + rc = smblib_read(chg, TYPE_C_STATUS_4_REG, &stat); + if (rc < 0) { + smblib_err(chg, "Couldn't read TYPE_C_STATUS_4 rc=%d\n", + rc); + goto try_sink_exit; + } + + debounce_done = stat & TYPEC_DEBOUNCE_DONE_STATUS_BIT; + vbus_detected = stat & TYPEC_VBUS_STATUS_BIT; + + /* Successfully transitioned to ATTACHED.SNK */ + if (vbus_detected && debounce_done) { + exit_mode = ATTACHED_SINK; + goto try_sink_exit; + } + + /* + * Ensure sink since drp may put us in source if other + * side switches back to Rd + */ + sink = !(stat & UFP_DFP_MODE_STATUS_BIT); + + usleep_range(1000, 2000); + } while (debounce_done && sink); + +try_wait_src: + /* + * Transition to trywait.SRC state. check if other side still wants + * to be SNK or has been removed. + */ + val.intval = POWER_SUPPLY_TYPEC_PR_SOURCE; + rc = smblib_set_prop_typec_power_role(chg, &val); + if (rc < 0) { + smblib_err(chg, "Couldn't set UFP mode rc=%d\n", rc); + goto try_sink_exit; + } + + /* Need to be in this state for tDRPTRY time, 75ms~150ms */ + msleep(80); + + rc = smblib_read(chg, TYPE_C_STATUS_4_REG, &stat); + if (rc < 0) { + smblib_err(chg, "Couldn't read TYPE_C_STATUS_4 rc=%d\n", rc); + goto try_sink_exit; + } + + debounce_done = stat & TYPEC_DEBOUNCE_DONE_STATUS_BIT; + + if (debounce_done) + /* the other side wants to be a sink */ + exit_mode = ATTACHED_SRC; + else + /* the other side is detached */ + exit_mode = UNATTACHED_SINK; + +try_sink_exit: + /* release forcing of SRC/SNK mode */ + val.intval = POWER_SUPPLY_TYPEC_PR_DUAL; + rc = smblib_set_prop_typec_power_role(chg, &val); + if (rc < 0) + smblib_err(chg, "Couldn't set DFP mode rc=%d\n", rc); + + /* revert Tccdebounce time back to ~120ms */ + rc = smblib_masked_write(chg, MISC_CFG_REG, TCC_DEBOUNCE_20MS_BIT, 0); + if (rc < 0) + smblib_err(chg, "Couldn't set MISC_CFG_REG rc=%d\n", rc); + + chg->try_sink_active = false; + + return exit_mode; +} + +static void typec_sink_insertion(struct smb_charger *chg) +{ + int exit_mode; + int typec_mode; + + exit_mode = typec_try_sink(chg); + + if (exit_mode != ATTACHED_SRC) { + smblib_usb_typec_change(chg); + return; + } + + typec_mode = smblib_get_prop_typec_mode(chg); + if (typec_mode == POWER_SUPPLY_TYPEC_SINK_AUDIO_ADAPTER) + chg->is_audio_adapter = true; + + /* when a sink is inserted we should not wait on hvdcp timeout to + * enable pd + */ + vote(chg->pd_disallowed_votable_indirect, HVDCP_TIMEOUT_VOTER, + false, 0); + if (chg->use_extcon) { + smblib_notify_usb_host(chg, true); + chg->otg_present = true; + } +} + +static void typec_sink_removal(struct smb_charger *chg) +{ + smblib_set_charge_param(chg, &chg->param.freq_boost, + chg->chg_freq.freq_above_otg_threshold); + chg->boost_current_ua = 0; +} + +static void smblib_handle_typec_removal(struct smb_charger *chg) +{ + int rc; + struct smb_irq_data *data; + struct storm_watch *wdata; + union power_supply_propval val; + + chg->cc2_detach_wa_active = false; + + rc = smblib_request_dpdm(chg, false); + if (rc < 0) + smblib_err(chg, "Couldn't disable DPDM rc=%d\n", rc); + + if (chg->wa_flags & BOOST_BACK_WA) { + data = chg->irq_info[SWITCH_POWER_OK_IRQ].irq_data; + if (data) { + wdata = &data->storm_data; + update_storm_count(wdata, WEAK_CHG_STORM_COUNT); + vote(chg->usb_icl_votable, BOOST_BACK_VOTER, false, 0); + vote(chg->usb_icl_votable, WEAK_CHARGER_VOTER, + false, 0); + } + } + + /* reset APSD voters */ + vote(chg->apsd_disable_votable, PD_HARD_RESET_VOTER, false, 0); + vote(chg->apsd_disable_votable, PD_VOTER, false, 0); + + cancel_delayed_work_sync(&chg->pl_enable_work); + cancel_delayed_work_sync(&chg->hvdcp_detect_work); + + /* reset input current limit voters */ + vote(chg->usb_icl_votable, LEGACY_UNKNOWN_VOTER, true, 100000); + vote(chg->usb_icl_votable, PD_VOTER, false, 0); + vote(chg->usb_icl_votable, USB_PSY_VOTER, false, 0); + vote(chg->usb_icl_votable, DCP_VOTER, false, 0); + vote(chg->usb_icl_votable, PL_USBIN_USBIN_VOTER, false, 0); + vote(chg->usb_icl_votable, SW_QC3_VOTER, false, 0); + vote(chg->usb_icl_votable, OTG_VOTER, false, 0); + vote(chg->usb_icl_votable, CTM_VOTER, false, 0); + vote(chg->usb_icl_votable, HVDCP2_ICL_VOTER, false, 0); + + /* reset hvdcp voters */ + vote(chg->hvdcp_disable_votable_indirect, VBUS_CC_SHORT_VOTER, true, 0); + vote(chg->hvdcp_disable_votable_indirect, PD_INACTIVE_VOTER, true, 0); + vote(chg->hvdcp_hw_inov_dis_votable, OV_VOTER, false, 0); + + /* reset power delivery voters */ + vote(chg->pd_allowed_votable, PD_VOTER, false, 0); + vote(chg->pd_disallowed_votable_indirect, CC_DETACHED_VOTER, true, 0); + vote(chg->pd_disallowed_votable_indirect, HVDCP_TIMEOUT_VOTER, true, 0); + + /* reset usb irq voters */ + vote(chg->usb_irq_enable_votable, PD_VOTER, false, 0); + vote(chg->usb_irq_enable_votable, QC_VOTER, false, 0); + + /* reset parallel voters */ + vote(chg->pl_disable_votable, PL_DELAY_VOTER, true, 0); + vote(chg->pl_disable_votable, PL_FCC_LOW_VOTER, false, 0); + vote(chg->pl_enable_votable_indirect, USBIN_I_VOTER, false, 0); + vote(chg->pl_enable_votable_indirect, USBIN_V_VOTER, false, 0); + vote(chg->awake_votable, PL_DELAY_VOTER, false, 0); + + vote(chg->usb_icl_votable, USBIN_USBIN_BOOST_VOTER, false, 0); + chg->vconn_attempts = 0; + chg->otg_attempts = 0; + chg->pulse_cnt = 0; + chg->usb_icl_delta_ua = 0; + chg->voltage_min_uv = MICRO_5V; + chg->voltage_max_uv = MICRO_5V; + chg->pd_active = 0; + chg->pd_hard_reset = false; + chg->typec_legacy_valid = false; + + /* write back the default FLOAT charger configuration */ + rc = smblib_masked_write(chg, USBIN_OPTIONS_2_CFG_REG, + (u8)FLOAT_OPTIONS_MASK, chg->float_cfg); + if (rc < 0) + smblib_err(chg, "Couldn't write float charger options rc=%d\n", + rc); + + /* reset back to 120mS tCC debounce */ + rc = smblib_masked_write(chg, MISC_CFG_REG, TCC_DEBOUNCE_20MS_BIT, 0); + if (rc < 0) + smblib_err(chg, "Couldn't set 120mS tCC debounce rc=%d\n", rc); + + /* + * if non-compliant charger caused UV, restore original max pulses + * and turn SUSPEND_ON_COLLAPSE_USBIN_BIT back on. + */ + if (chg->non_compliant_chg_detected) { + rc = smblib_masked_write(chg, HVDCP_PULSE_COUNT_MAX_REG, + HVDCP_PULSE_COUNT_MAX_QC2_MASK, + chg->qc2_max_pulses); + if (rc < 0) + smblib_err(chg, "Couldn't restore max pulses rc=%d\n", + rc); + + rc = smblib_masked_write(chg, USBIN_AICL_OPTIONS_CFG_REG, + SUSPEND_ON_COLLAPSE_USBIN_BIT, + SUSPEND_ON_COLLAPSE_USBIN_BIT); + if (rc < 0) + smblib_err(chg, "Couldn't turn on SUSPEND_ON_COLLAPSE_USBIN_BIT rc=%d\n", + rc); + + chg->non_compliant_chg_detected = false; + } + + /* enable APSD CC trigger for next insertion */ + rc = smblib_masked_write(chg, TYPE_C_CFG_REG, + APSD_START_ON_CC_BIT, APSD_START_ON_CC_BIT); + if (rc < 0) + smblib_err(chg, "Couldn't enable APSD_START_ON_CC rc=%d\n", rc); + + if (chg->wa_flags & QC_AUTH_INTERRUPT_WA_BIT) { + /* re-enable AUTH_IRQ_EN_CFG_BIT */ + rc = smblib_masked_write(chg, + USBIN_SOURCE_CHANGE_INTRPT_ENB_REG, + AUTH_IRQ_EN_CFG_BIT, AUTH_IRQ_EN_CFG_BIT); + if (rc < 0) + smblib_err(chg, + "Couldn't enable QC auth setting rc=%d\n", rc); + } + + /* reconfigure allowed voltage for HVDCP */ + rc = smblib_set_adapter_allowance(chg, + USBIN_ADAPTER_ALLOW_5V_OR_9V_TO_12V); + if (rc < 0) + smblib_err(chg, "Couldn't set USBIN_ADAPTER_ALLOW_5V_OR_9V_TO_12V rc=%d\n", + rc); + + if (chg->is_audio_adapter) + /* wait for the audio driver to lower its en gpio */ + msleep(*chg->audio_headset_drp_wait_ms); + + chg->is_audio_adapter = false; + + /* enable DRP */ + val.intval = POWER_SUPPLY_TYPEC_PR_DUAL; + rc = smblib_set_prop_typec_power_role(chg, &val); + if (rc < 0) + smblib_err(chg, "Couldn't enable DRP rc=%d\n", rc); + + /* HW controlled CC_OUT */ + rc = smblib_masked_write(chg, TAPER_TIMER_SEL_CFG_REG, + TYPEC_SPARE_CFG_BIT, 0); + if (rc < 0) + smblib_err(chg, "Couldn't enable HW cc_out rc=%d\n", rc); + + /* restore crude sensor if PM660/PMI8998 */ + if (chg->wa_flags & TYPEC_PBS_WA_BIT) { + rc = smblib_write(chg, TM_IO_DTEST4_SEL, 0xA5); + if (rc < 0) + smblib_err(chg, "Couldn't restore crude sensor rc=%d\n", + rc); + } + + mutex_lock(&chg->vconn_oc_lock); + if (!chg->vconn_en) + goto unlock; + + smblib_masked_write(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG, + VCONN_EN_VALUE_BIT, 0); + chg->vconn_en = false; + +unlock: + mutex_unlock(&chg->vconn_oc_lock); + + /* clear exit sink based on cc */ + rc = smblib_masked_write(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG, + EXIT_SNK_BASED_ON_CC_BIT, 0); + if (rc < 0) + smblib_err(chg, "Couldn't clear exit_sink_based_on_cc rc=%d\n", + rc); + + typec_sink_removal(chg); + smblib_update_usb_type(chg); + + if (chg->use_extcon) { + if (chg->otg_present) + smblib_notify_usb_host(chg, false); + else + smblib_notify_device_mode(chg, false); + } + chg->otg_present = false; +} + +static void smblib_handle_typec_insertion(struct smb_charger *chg) +{ + int rc; + + vote(chg->pd_disallowed_votable_indirect, CC_DETACHED_VOTER, false, 0); + + /* disable APSD CC trigger since CC is attached */ + rc = smblib_masked_write(chg, TYPE_C_CFG_REG, APSD_START_ON_CC_BIT, 0); + if (rc < 0) + smblib_err(chg, "Couldn't disable APSD_START_ON_CC rc=%d\n", + rc); + + if (chg->typec_status[3] & UFP_DFP_MODE_STATUS_BIT) { + typec_sink_insertion(chg); + } else { + rc = smblib_request_dpdm(chg, true); + if (rc < 0) + smblib_err(chg, "Couldn't to enable DPDM rc=%d\n", rc); + typec_sink_removal(chg); + } +} + +static void smblib_handle_rp_change(struct smb_charger *chg, int typec_mode) +{ + int rp_ua; + const struct apsd_result *apsd = smblib_get_apsd_result(chg); + + if ((apsd->pst != POWER_SUPPLY_TYPE_USB_DCP) + && (apsd->pst != POWER_SUPPLY_TYPE_USB_FLOAT)) + return; + + /* + * if APSD indicates FLOAT and the USB stack had detected SDP, + * do not respond to Rp changes as we do not confirm that its + * a legacy cable + */ + if (chg->real_charger_type == POWER_SUPPLY_TYPE_USB) + return; + /* + * We want the ICL vote @ 100mA for a FLOAT charger + * until the detection by the USB stack is complete. + * Ignore the Rp changes unless there is a + * pre-existing valid vote. + */ + if (apsd->pst == POWER_SUPPLY_TYPE_USB_FLOAT && + get_client_vote(chg->usb_icl_votable, + LEGACY_UNKNOWN_VOTER) <= 100000) + return; + + /* + * handle Rp change for DCP/FLOAT/OCP. + * Update the current only if the Rp is different from + * the last Rp value. + */ + smblib_dbg(chg, PR_MISC, "CC change old_mode=%d new_mode=%d\n", + chg->typec_mode, typec_mode); + + rp_ua = get_rp_based_dcp_current(chg, typec_mode); + vote(chg->usb_icl_votable, LEGACY_UNKNOWN_VOTER, true, rp_ua); +} + +static void smblib_handle_typec_cc_state_change(struct smb_charger *chg) +{ + int typec_mode; + + if (chg->pr_swap_in_progress) + return; + + typec_mode = smblib_get_prop_typec_mode(chg); + if (chg->typec_present && (typec_mode != chg->typec_mode)) + smblib_handle_rp_change(chg, typec_mode); + + chg->typec_mode = typec_mode; + + if (!chg->typec_present && chg->typec_mode != POWER_SUPPLY_TYPEC_NONE) { + chg->typec_present = true; + smblib_dbg(chg, PR_MISC, "TypeC %s insertion\n", + smblib_typec_mode_name[chg->typec_mode]); + smblib_handle_typec_insertion(chg); + } else if (chg->typec_present && + chg->typec_mode == POWER_SUPPLY_TYPEC_NONE) { + chg->typec_present = false; + smblib_dbg(chg, PR_MISC, "TypeC removal\n"); + smblib_handle_typec_removal(chg); + } + + /* suspend usb if sink */ + if ((chg->typec_status[3] & UFP_DFP_MODE_STATUS_BIT) + && chg->typec_present) + vote(chg->usb_icl_votable, OTG_VOTER, true, 0); + else + vote(chg->usb_icl_votable, OTG_VOTER, false, 0); + + smblib_dbg(chg, PR_INTERRUPT, "IRQ: cc-state-change; Type-C %s detected\n", + smblib_typec_mode_name[chg->typec_mode]); +} + +void smblib_usb_typec_change(struct smb_charger *chg) +{ + int rc; + + rc = smblib_multibyte_read(chg, TYPE_C_STATUS_1_REG, + chg->typec_status, 5); + if (rc < 0) { + smblib_err(chg, "Couldn't cache USB Type-C status rc=%d\n", rc); + return; + } + + smblib_handle_typec_cc_state_change(chg); + + if (chg->typec_status[3] & TYPEC_VBUS_ERROR_STATUS_BIT) + smblib_dbg(chg, PR_INTERRUPT, "IRQ: vbus-error\n"); + + if (chg->typec_status[3] & TYPEC_VCONN_OVERCURR_STATUS_BIT) + schedule_work(&chg->vconn_oc_work); + + power_supply_changed(chg->usb_psy); +} + +irqreturn_t smblib_handle_usb_typec_change(int irq, void *data) +{ + struct smb_irq_data *irq_data = data; + struct smb_charger *chg = irq_data->parent_data; + + if (chg->connector_type == POWER_SUPPLY_CONNECTOR_MICRO_USB) { + cancel_delayed_work_sync(&chg->uusb_otg_work); + vote(chg->awake_votable, OTG_DELAY_VOTER, true, 0); + smblib_dbg(chg, PR_INTERRUPT, "Scheduling OTG work\n"); + schedule_delayed_work(&chg->uusb_otg_work, + msecs_to_jiffies(chg->otg_delay_ms)); + return IRQ_HANDLED; + } + + if (chg->cc2_detach_wa_active || chg->typec_en_dis_active || + chg->try_sink_active) { + smblib_dbg(chg, PR_MISC | PR_INTERRUPT, "Ignoring since %s active\n", + chg->cc2_detach_wa_active ? + "cc2_detach_wa" : "typec_en_dis"); + return IRQ_HANDLED; + } + + if (chg->pr_swap_in_progress) { + smblib_dbg(chg, PR_INTERRUPT, + "Ignoring since pr_swap_in_progress\n"); + return IRQ_HANDLED; + } + + mutex_lock(&chg->lock); + smblib_usb_typec_change(chg); + mutex_unlock(&chg->lock); + return IRQ_HANDLED; +} + +irqreturn_t smblib_handle_dc_plugin(int irq, void *data) +{ + struct smb_irq_data *irq_data = data; + struct smb_charger *chg = irq_data->parent_data; + + power_supply_changed(chg->dc_psy); + return IRQ_HANDLED; +} + +irqreturn_t smblib_handle_high_duty_cycle(int irq, void *data) +{ + struct smb_irq_data *irq_data = data; + struct smb_charger *chg = irq_data->parent_data; + + chg->is_hdc = true; + /* + * Disable usb IRQs after the flag set and re-enable IRQs after + * the flag cleared in the delayed work queue, to avoid any IRQ + * storming during the delays + */ + if (chg->irq_info[HIGH_DUTY_CYCLE_IRQ].irq) + disable_irq_nosync(chg->irq_info[HIGH_DUTY_CYCLE_IRQ].irq); + + schedule_delayed_work(&chg->clear_hdc_work, msecs_to_jiffies(60)); + + return IRQ_HANDLED; +} + +static void smblib_bb_removal_work(struct work_struct *work) +{ + struct smb_charger *chg = container_of(work, struct smb_charger, + bb_removal_work.work); + + vote(chg->usb_icl_votable, BOOST_BACK_VOTER, false, 0); + vote(chg->awake_votable, BOOST_BACK_VOTER, false, 0); +} + +#define BOOST_BACK_UNVOTE_DELAY_MS 750 +#define BOOST_BACK_STORM_COUNT 3 +#define WEAK_CHG_STORM_COUNT 8 +irqreturn_t smblib_handle_switcher_power_ok(int irq, void *data) +{ + struct smb_irq_data *irq_data = data; + struct smb_charger *chg = irq_data->parent_data; + struct storm_watch *wdata = &irq_data->storm_data; + int rc, usb_icl; + u8 stat; + + if (!(chg->wa_flags & BOOST_BACK_WA)) + return IRQ_HANDLED; + + rc = smblib_read(chg, POWER_PATH_STATUS_REG, &stat); + if (rc < 0) { + smblib_err(chg, "Couldn't read POWER_PATH_STATUS rc=%d\n", rc); + return IRQ_HANDLED; + } + + /* skip suspending input if its already suspended by some other voter */ + usb_icl = get_effective_result(chg->usb_icl_votable); + if ((stat & USE_USBIN_BIT) && usb_icl >= 0 && usb_icl <= USBIN_25MA) + return IRQ_HANDLED; + + if (stat & USE_DCIN_BIT) + return IRQ_HANDLED; + + if (is_storming(&irq_data->storm_data)) { + /* This could be a weak charger reduce ICL */ + if (!is_client_vote_enabled(chg->usb_icl_votable, + WEAK_CHARGER_VOTER)) { + smblib_err(chg, + "Weak charger detected: voting %dmA ICL\n", + *chg->weak_chg_icl_ua / 1000); + vote(chg->usb_icl_votable, WEAK_CHARGER_VOTER, + true, *chg->weak_chg_icl_ua); + /* + * reset storm data and set the storm threshold + * to 3 for reverse boost detection. + */ + update_storm_count(wdata, BOOST_BACK_STORM_COUNT); + } else { + smblib_err(chg, + "Reverse boost detected: voting 0mA to suspend input\n"); + vote(chg->usb_icl_votable, BOOST_BACK_VOTER, true, 0); + vote(chg->awake_votable, BOOST_BACK_VOTER, true, 0); + /* + * Remove the boost-back vote after a delay, to avoid + * permanently suspending the input if the boost-back + * condition is unintentionally hit. + */ + schedule_delayed_work(&chg->bb_removal_work, + msecs_to_jiffies(BOOST_BACK_UNVOTE_DELAY_MS)); + } + } + + return IRQ_HANDLED; +} + +irqreturn_t smblib_handle_wdog_bark(int irq, void *data) +{ + struct smb_irq_data *irq_data = data; + struct smb_charger *chg = irq_data->parent_data; + int rc; + + smblib_dbg(chg, PR_INTERRUPT, "IRQ: %s\n", irq_data->name); + + rc = smblib_write(chg, BARK_BITE_WDOG_PET_REG, BARK_BITE_WDOG_PET_BIT); + if (rc < 0) + smblib_err(chg, "Couldn't pet the dog rc=%d\n", rc); + + if (chg->step_chg_enabled || chg->sw_jeita_enabled) + power_supply_changed(chg->batt_psy); + + return IRQ_HANDLED; +} + +/************** + * Additional USB PSY getters/setters + * that call interrupt functions + ***************/ + +int smblib_get_prop_pr_swap_in_progress(struct smb_charger *chg, + union power_supply_propval *val) +{ + val->intval = chg->pr_swap_in_progress; + return 0; +} + +int smblib_set_prop_pr_swap_in_progress(struct smb_charger *chg, + const union power_supply_propval *val) +{ + int rc; + + chg->pr_swap_in_progress = val->intval; + /* + * call the cc changed irq to handle real removals while + * PR_SWAP was in progress + */ + smblib_usb_typec_change(chg); + rc = smblib_masked_write(chg, MISC_CFG_REG, TCC_DEBOUNCE_20MS_BIT, + val->intval ? TCC_DEBOUNCE_20MS_BIT : 0); + if (rc < 0) + smblib_err(chg, "Couldn't set tCC debounce rc=%d\n", rc); + return 0; +} + +/*************** + * Work Queues * + ***************/ +static void smblib_uusb_otg_work(struct work_struct *work) +{ + struct smb_charger *chg = container_of(work, struct smb_charger, + uusb_otg_work.work); + int rc; + u8 stat; + bool otg; + + rc = smblib_read(chg, TYPE_C_STATUS_3_REG, &stat); + if (rc < 0) { + smblib_err(chg, "Couldn't read TYPE_C_STATUS_3 rc=%d\n", rc); + goto out; + } + + otg = !!(stat & (U_USB_GND_NOVBUS_BIT | U_USB_GND_BIT)); + extcon_set_state_sync(chg->extcon, EXTCON_USB_HOST, otg); + smblib_dbg(chg, PR_REGISTER, "TYPE_C_STATUS_3 = 0x%02x OTG=%d\n", + stat, otg); + power_supply_changed(chg->usb_psy); + +out: + vote(chg->awake_votable, OTG_DELAY_VOTER, false, 0); +} + + +static void smblib_hvdcp_detect_work(struct work_struct *work) +{ + struct smb_charger *chg = container_of(work, struct smb_charger, + hvdcp_detect_work.work); + + vote(chg->pd_disallowed_votable_indirect, HVDCP_TIMEOUT_VOTER, + false, 0); + power_supply_changed(chg->usb_psy); +} + +static void bms_update_work(struct work_struct *work) +{ + struct smb_charger *chg = container_of(work, struct smb_charger, + bms_update_work); + + smblib_suspend_on_debug_battery(chg); + + if (chg->batt_psy) + power_supply_changed(chg->batt_psy); +} + +static void pl_update_work(struct work_struct *work) +{ + struct smb_charger *chg = container_of(work, struct smb_charger, + pl_update_work); + + smblib_stat_sw_override_cfg(chg, false); +} + +static void clear_hdc_work(struct work_struct *work) +{ + struct smb_charger *chg = container_of(work, struct smb_charger, + clear_hdc_work.work); + + chg->is_hdc = false; + if (chg->irq_info[HIGH_DUTY_CYCLE_IRQ].irq) + enable_irq(chg->irq_info[HIGH_DUTY_CYCLE_IRQ].irq); +} + +static void rdstd_cc2_detach_work(struct work_struct *work) +{ + int rc; + u8 stat4, stat5; + struct smb_charger *chg = container_of(work, struct smb_charger, + rdstd_cc2_detach_work); + + if (!chg->cc2_detach_wa_active) + return; + + /* + * WA steps - + * 1. Enable both UFP and DFP, wait for 10ms. + * 2. Disable DFP, wait for 30ms. + * 3. Removal detected if both TYPEC_DEBOUNCE_DONE_STATUS + * and TIMER_STAGE bits are gone, otherwise repeat all by + * work rescheduling. + * Note, work will be cancelled when USB_PLUGIN rises. + */ + + rc = smblib_masked_write(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG, + UFP_EN_CMD_BIT | DFP_EN_CMD_BIT, + UFP_EN_CMD_BIT | DFP_EN_CMD_BIT); + if (rc < 0) { + smblib_err(chg, "Couldn't write TYPE_C_CTRL_REG rc=%d\n", rc); + return; + } + + usleep_range(10000, 11000); + + rc = smblib_masked_write(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG, + UFP_EN_CMD_BIT | DFP_EN_CMD_BIT, + UFP_EN_CMD_BIT); + if (rc < 0) { + smblib_err(chg, "Couldn't write TYPE_C_CTRL_REG rc=%d\n", rc); + return; + } + + usleep_range(30000, 31000); + + rc = smblib_read(chg, TYPE_C_STATUS_4_REG, &stat4); + if (rc < 0) { + smblib_err(chg, "Couldn't read TYPE_C_STATUS_4 rc=%d\n", rc); + return; + } + + rc = smblib_read(chg, TYPE_C_STATUS_5_REG, &stat5); + if (rc < 0) { + smblib_err(chg, + "Couldn't read TYPE_C_STATUS_5_REG rc=%d\n", rc); + return; + } + + if ((stat4 & TYPEC_DEBOUNCE_DONE_STATUS_BIT) + || (stat5 & TIMER_STAGE_2_BIT)) { + smblib_dbg(chg, PR_MISC, "rerunning DD=%d TS2BIT=%d\n", + (int)(stat4 & TYPEC_DEBOUNCE_DONE_STATUS_BIT), + (int)(stat5 & TIMER_STAGE_2_BIT)); + goto rerun; + } + + smblib_dbg(chg, PR_MISC, "Bingo CC2 Removal detected\n"); + chg->cc2_detach_wa_active = false; + rc = smblib_masked_write(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG, + EXIT_SNK_BASED_ON_CC_BIT, 0); + smblib_reg_block_restore(chg, cc2_detach_settings); + + /* + * Mutex acquisition deadlock can happen while cancelling this work + * during pd_hard_reset from the function smblib_cc2_sink_removal_exit + * which is called in the same lock context that we try to acquire in + * this work routine. + * Check if this work is running during pd_hard_reset and skip holding + * mutex if lock is already held. + */ + if (!chg->in_chg_lock) + mutex_lock(&chg->lock); + smblib_usb_typec_change(chg); + if (!chg->in_chg_lock) + mutex_unlock(&chg->lock); + + return; + +rerun: + schedule_work(&chg->rdstd_cc2_detach_work); +} + +static void smblib_otg_oc_exit(struct smb_charger *chg, bool success) +{ + int rc; + + chg->otg_attempts = 0; + if (!success) { + smblib_err(chg, "OTG soft start failed\n"); + chg->otg_en = false; + } + + smblib_dbg(chg, PR_OTG, "enabling VBUS < 1V check\n"); + rc = smblib_masked_write(chg, OTG_CFG_REG, + QUICKSTART_OTG_FASTROLESWAP_BIT, 0); + if (rc < 0) + smblib_err(chg, "Couldn't enable VBUS < 1V check rc=%d\n", rc); +} + +#define MAX_OC_FALLING_TRIES 10 +static void smblib_otg_oc_work(struct work_struct *work) +{ + struct smb_charger *chg = container_of(work, struct smb_charger, + otg_oc_work); + int rc, i; + u8 stat; + + if (!chg->vbus_vreg || !chg->vbus_vreg->rdev) + return; + + smblib_err(chg, "over-current detected on VBUS\n"); + mutex_lock(&chg->otg_oc_lock); + if (!chg->otg_en) + goto unlock; + + smblib_dbg(chg, PR_OTG, "disabling VBUS < 1V check\n"); + smblib_masked_write(chg, OTG_CFG_REG, + QUICKSTART_OTG_FASTROLESWAP_BIT, + QUICKSTART_OTG_FASTROLESWAP_BIT); + + /* + * If 500ms has passed and another over-current interrupt has not + * triggered then it is likely that the software based soft start was + * successful and the VBUS < 1V restriction should be re-enabled. + */ + schedule_delayed_work(&chg->otg_ss_done_work, msecs_to_jiffies(500)); + + rc = _smblib_vbus_regulator_disable(chg->vbus_vreg->rdev); + if (rc < 0) { + smblib_err(chg, "Couldn't disable VBUS rc=%d\n", rc); + goto unlock; + } + + if (++chg->otg_attempts > OTG_MAX_ATTEMPTS) { + cancel_delayed_work_sync(&chg->otg_ss_done_work); + smblib_err(chg, "OTG failed to enable after %d attempts\n", + chg->otg_attempts - 1); + smblib_otg_oc_exit(chg, false); + goto unlock; + } + + /* + * The real time status should go low within 10ms. Poll every 1-2ms to + * minimize the delay when re-enabling OTG. + */ + for (i = 0; i < MAX_OC_FALLING_TRIES; ++i) { + usleep_range(1000, 2000); + rc = smblib_read(chg, OTG_BASE + INT_RT_STS_OFFSET, &stat); + if (rc >= 0 && !(stat & OTG_OVERCURRENT_RT_STS_BIT)) + break; + } + + if (i >= MAX_OC_FALLING_TRIES) { + cancel_delayed_work_sync(&chg->otg_ss_done_work); + smblib_err(chg, "OTG OC did not fall after %dms\n", + 2 * MAX_OC_FALLING_TRIES); + smblib_otg_oc_exit(chg, false); + goto unlock; + } + + smblib_dbg(chg, PR_OTG, "OTG OC fell after %dms\n", 2 * i + 1); + rc = _smblib_vbus_regulator_enable(chg->vbus_vreg->rdev); + if (rc < 0) { + smblib_err(chg, "Couldn't enable VBUS rc=%d\n", rc); + goto unlock; + } + +unlock: + mutex_unlock(&chg->otg_oc_lock); +} + +static void smblib_vconn_oc_work(struct work_struct *work) +{ + struct smb_charger *chg = container_of(work, struct smb_charger, + vconn_oc_work); + int rc, i; + u8 stat; + + if (chg->connector_type == POWER_SUPPLY_CONNECTOR_MICRO_USB) + return; + + smblib_err(chg, "over-current detected on VCONN\n"); + if (!chg->vconn_vreg || !chg->vconn_vreg->rdev) + return; + + mutex_lock(&chg->vconn_oc_lock); + rc = _smblib_vconn_regulator_disable(chg->vconn_vreg->rdev); + if (rc < 0) { + smblib_err(chg, "Couldn't disable VCONN rc=%d\n", rc); + goto unlock; + } + + if (++chg->vconn_attempts > VCONN_MAX_ATTEMPTS) { + smblib_err(chg, "VCONN failed to enable after %d attempts\n", + chg->vconn_attempts - 1); + chg->vconn_en = false; + chg->vconn_attempts = 0; + goto unlock; + } + + /* + * The real time status should go low within 10ms. Poll every 1-2ms to + * minimize the delay when re-enabling OTG. + */ + for (i = 0; i < MAX_OC_FALLING_TRIES; ++i) { + usleep_range(1000, 2000); + rc = smblib_read(chg, TYPE_C_STATUS_4_REG, &stat); + if (rc >= 0 && !(stat & TYPEC_VCONN_OVERCURR_STATUS_BIT)) + break; + } + + if (i >= MAX_OC_FALLING_TRIES) { + smblib_err(chg, "VCONN OC did not fall after %dms\n", + 2 * MAX_OC_FALLING_TRIES); + chg->vconn_en = false; + chg->vconn_attempts = 0; + goto unlock; + } + smblib_dbg(chg, PR_OTG, "VCONN OC fell after %dms\n", 2 * i + 1); + + rc = _smblib_vconn_regulator_enable(chg->vconn_vreg->rdev); + if (rc < 0) { + smblib_err(chg, "Couldn't enable VCONN rc=%d\n", rc); + goto unlock; + } + +unlock: + mutex_unlock(&chg->vconn_oc_lock); +} + +static void smblib_otg_ss_done_work(struct work_struct *work) +{ + struct smb_charger *chg = container_of(work, struct smb_charger, + otg_ss_done_work.work); + int rc; + bool success = false; + u8 stat; + + mutex_lock(&chg->otg_oc_lock); + rc = smblib_read(chg, OTG_STATUS_REG, &stat); + if (rc < 0) + smblib_err(chg, "Couldn't read OTG status rc=%d\n", rc); + else if (stat & BOOST_SOFTSTART_DONE_BIT) + success = true; + + smblib_otg_oc_exit(chg, success); + mutex_unlock(&chg->otg_oc_lock); +} + +static void smblib_icl_change_work(struct work_struct *work) +{ + struct smb_charger *chg = container_of(work, struct smb_charger, + icl_change_work.work); + int rc, settled_ua; + + rc = smblib_get_charge_param(chg, &chg->param.icl_stat, &settled_ua); + if (rc < 0) { + smblib_err(chg, "Couldn't get ICL status rc=%d\n", rc); + return; + } + + power_supply_changed(chg->usb_main_psy); + + smblib_dbg(chg, PR_INTERRUPT, "icl_settled=%d\n", settled_ua); +} + +static void smblib_pl_enable_work(struct work_struct *work) +{ + struct smb_charger *chg = container_of(work, struct smb_charger, + pl_enable_work.work); + + smblib_dbg(chg, PR_PARALLEL, "timer expired, enabling parallel\n"); + vote(chg->pl_disable_votable, PL_DELAY_VOTER, false, 0); + vote(chg->awake_votable, PL_DELAY_VOTER, false, 0); +} + +static void smblib_legacy_detection_work(struct work_struct *work) +{ + struct smb_charger *chg = container_of(work, struct smb_charger, + legacy_detection_work); + int rc; + u8 stat; + bool legacy, rp_high; + + mutex_lock(&chg->lock); + chg->typec_en_dis_active = true; + smblib_dbg(chg, PR_MISC, "running legacy unknown workaround\n"); + rc = smblib_masked_write(chg, + TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG, + TYPEC_DISABLE_CMD_BIT, + TYPEC_DISABLE_CMD_BIT); + if (rc < 0) + smblib_err(chg, "Couldn't disable type-c rc=%d\n", rc); + + /* wait for the adapter to turn off VBUS */ + msleep(1000); + + smblib_dbg(chg, PR_MISC, "legacy workaround enabling typec\n"); + + rc = smblib_masked_write(chg, + TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG, + TYPEC_DISABLE_CMD_BIT, 0); + if (rc < 0) + smblib_err(chg, "Couldn't enable type-c rc=%d\n", rc); + + /* wait for type-c detection to complete */ + msleep(400); + + rc = smblib_read(chg, TYPE_C_STATUS_5_REG, &stat); + if (rc < 0) { + smblib_err(chg, "Couldn't read typec stat5 rc = %d\n", rc); + goto unlock; + } + + chg->typec_legacy_valid = true; + vote(chg->usb_icl_votable, LEGACY_UNKNOWN_VOTER, false, 0); + legacy = stat & TYPEC_LEGACY_CABLE_STATUS_BIT; + rp_high = chg->typec_mode == POWER_SUPPLY_TYPEC_SOURCE_HIGH; + smblib_dbg(chg, PR_MISC, "legacy workaround done legacy = %d rp_high = %d\n", + legacy, rp_high); + if (!legacy || !rp_high) + vote(chg->hvdcp_disable_votable_indirect, VBUS_CC_SHORT_VOTER, + false, 0); + +unlock: + chg->typec_en_dis_active = false; + smblib_usb_typec_change(chg); + mutex_unlock(&chg->lock); +} + +static int smblib_create_votables(struct smb_charger *chg) +{ + int rc = 0; + + chg->fcc_votable = find_votable("FCC"); + if (chg->fcc_votable == NULL) { + rc = -EINVAL; + smblib_err(chg, "Couldn't find FCC votable rc=%d\n", rc); + return rc; + } + + chg->fv_votable = find_votable("FV"); + if (chg->fv_votable == NULL) { + rc = -EINVAL; + smblib_err(chg, "Couldn't find FV votable rc=%d\n", rc); + return rc; + } + + chg->usb_icl_votable = find_votable("USB_ICL"); + if (!chg->usb_icl_votable) { + rc = -EINVAL; + smblib_err(chg, "Couldn't find USB_ICL votable rc=%d\n", rc); + return rc; + } + + chg->pl_disable_votable = find_votable("PL_DISABLE"); + if (chg->pl_disable_votable == NULL) { + rc = -EINVAL; + smblib_err(chg, "Couldn't find votable PL_DISABLE rc=%d\n", rc); + return rc; + } + + chg->pl_enable_votable_indirect = find_votable("PL_ENABLE_INDIRECT"); + if (chg->pl_enable_votable_indirect == NULL) { + rc = -EINVAL; + smblib_err(chg, + "Couldn't find votable PL_ENABLE_INDIRECT rc=%d\n", + rc); + return rc; + } + + vote(chg->pl_disable_votable, PL_DELAY_VOTER, true, 0); + + chg->dc_suspend_votable = create_votable("DC_SUSPEND", VOTE_SET_ANY, + smblib_dc_suspend_vote_callback, + chg); + if (IS_ERR(chg->dc_suspend_votable)) { + rc = PTR_ERR(chg->dc_suspend_votable); + return rc; + } + + chg->dc_icl_votable = create_votable("DC_ICL", VOTE_MIN, + smblib_dc_icl_vote_callback, + chg); + if (IS_ERR(chg->dc_icl_votable)) { + rc = PTR_ERR(chg->dc_icl_votable); + return rc; + } + + chg->pd_disallowed_votable_indirect + = create_votable("PD_DISALLOWED_INDIRECT", VOTE_SET_ANY, + smblib_pd_disallowed_votable_indirect_callback, chg); + if (IS_ERR(chg->pd_disallowed_votable_indirect)) { + rc = PTR_ERR(chg->pd_disallowed_votable_indirect); + return rc; + } + + chg->pd_allowed_votable = create_votable("PD_ALLOWED", + VOTE_SET_ANY, NULL, NULL); + if (IS_ERR(chg->pd_allowed_votable)) { + rc = PTR_ERR(chg->pd_allowed_votable); + return rc; + } + + chg->awake_votable = create_votable("AWAKE", VOTE_SET_ANY, + smblib_awake_vote_callback, + chg); + if (IS_ERR(chg->awake_votable)) { + rc = PTR_ERR(chg->awake_votable); + return rc; + } + + chg->chg_disable_votable = create_votable("CHG_DISABLE", VOTE_SET_ANY, + smblib_chg_disable_vote_callback, + chg); + if (IS_ERR(chg->chg_disable_votable)) { + rc = PTR_ERR(chg->chg_disable_votable); + return rc; + } + + + chg->hvdcp_disable_votable_indirect = create_votable( + "HVDCP_DISABLE_INDIRECT", + VOTE_SET_ANY, + smblib_hvdcp_disable_indirect_vote_callback, + chg); + if (IS_ERR(chg->hvdcp_disable_votable_indirect)) { + rc = PTR_ERR(chg->hvdcp_disable_votable_indirect); + return rc; + } + + chg->hvdcp_enable_votable = create_votable("HVDCP_ENABLE", + VOTE_SET_ANY, + smblib_hvdcp_enable_vote_callback, + chg); + if (IS_ERR(chg->hvdcp_enable_votable)) { + rc = PTR_ERR(chg->hvdcp_enable_votable); + return rc; + } + + chg->apsd_disable_votable = create_votable("APSD_DISABLE", + VOTE_SET_ANY, + smblib_apsd_disable_vote_callback, + chg); + if (IS_ERR(chg->apsd_disable_votable)) { + rc = PTR_ERR(chg->apsd_disable_votable); + return rc; + } + + chg->hvdcp_hw_inov_dis_votable = create_votable("HVDCP_HW_INOV_DIS", + VOTE_SET_ANY, + smblib_hvdcp_hw_inov_dis_vote_callback, + chg); + if (IS_ERR(chg->hvdcp_hw_inov_dis_votable)) { + rc = PTR_ERR(chg->hvdcp_hw_inov_dis_votable); + return rc; + } + + chg->usb_irq_enable_votable = create_votable("USB_IRQ_DISABLE", + VOTE_SET_ANY, + smblib_usb_irq_enable_vote_callback, + chg); + if (IS_ERR(chg->usb_irq_enable_votable)) { + rc = PTR_ERR(chg->usb_irq_enable_votable); + return rc; + } + + chg->typec_irq_disable_votable = create_votable("TYPEC_IRQ_DISABLE", + VOTE_SET_ANY, + smblib_typec_irq_disable_vote_callback, + chg); + if (IS_ERR(chg->typec_irq_disable_votable)) { + rc = PTR_ERR(chg->typec_irq_disable_votable); + return rc; + } + + chg->disable_power_role_switch + = create_votable("DISABLE_POWER_ROLE_SWITCH", + VOTE_SET_ANY, + smblib_disable_power_role_switch_callback, + chg); + if (IS_ERR(chg->disable_power_role_switch)) { + rc = PTR_ERR(chg->disable_power_role_switch); + return rc; + } + vote(chg->disable_power_role_switch, DEFAULT_VOTER, + chg->ufp_only_mode, 0); + + return rc; +} + +static void smblib_destroy_votables(struct smb_charger *chg) +{ + if (chg->dc_suspend_votable) + destroy_votable(chg->dc_suspend_votable); + if (chg->usb_icl_votable) + destroy_votable(chg->usb_icl_votable); + if (chg->dc_icl_votable) + destroy_votable(chg->dc_icl_votable); + if (chg->pd_disallowed_votable_indirect) + destroy_votable(chg->pd_disallowed_votable_indirect); + if (chg->pd_allowed_votable) + destroy_votable(chg->pd_allowed_votable); + if (chg->awake_votable) + destroy_votable(chg->awake_votable); + if (chg->chg_disable_votable) + destroy_votable(chg->chg_disable_votable); + if (chg->apsd_disable_votable) + destroy_votable(chg->apsd_disable_votable); + if (chg->hvdcp_hw_inov_dis_votable) + destroy_votable(chg->hvdcp_hw_inov_dis_votable); + if (chg->typec_irq_disable_votable) + destroy_votable(chg->typec_irq_disable_votable); + if (chg->disable_power_role_switch) + destroy_votable(chg->disable_power_role_switch); +} + +static void smblib_iio_deinit(struct smb_charger *chg) +{ + if (!IS_ERR_OR_NULL(chg->iio.temp_chan)) + iio_channel_release(chg->iio.temp_chan); + if (!IS_ERR_OR_NULL(chg->iio.temp_max_chan)) + iio_channel_release(chg->iio.temp_max_chan); + if (!IS_ERR_OR_NULL(chg->iio.usbin_i_chan)) + iio_channel_release(chg->iio.usbin_i_chan); + if (!IS_ERR_OR_NULL(chg->iio.usbin_v_chan)) + iio_channel_release(chg->iio.usbin_v_chan); + if (!IS_ERR_OR_NULL(chg->iio.batt_i_chan)) + iio_channel_release(chg->iio.batt_i_chan); +} + +int smblib_init(struct smb_charger *chg) +{ + int rc = 0; + + mutex_init(&chg->lock); + mutex_init(&chg->write_lock); + mutex_init(&chg->otg_oc_lock); + mutex_init(&chg->vconn_oc_lock); + INIT_WORK(&chg->bms_update_work, bms_update_work); + INIT_WORK(&chg->pl_update_work, pl_update_work); + INIT_WORK(&chg->rdstd_cc2_detach_work, rdstd_cc2_detach_work); + INIT_DELAYED_WORK(&chg->hvdcp_detect_work, smblib_hvdcp_detect_work); + INIT_DELAYED_WORK(&chg->clear_hdc_work, clear_hdc_work); + INIT_WORK(&chg->otg_oc_work, smblib_otg_oc_work); + INIT_WORK(&chg->vconn_oc_work, smblib_vconn_oc_work); + INIT_DELAYED_WORK(&chg->otg_ss_done_work, smblib_otg_ss_done_work); + INIT_DELAYED_WORK(&chg->icl_change_work, smblib_icl_change_work); + INIT_DELAYED_WORK(&chg->pl_enable_work, smblib_pl_enable_work); + INIT_WORK(&chg->legacy_detection_work, smblib_legacy_detection_work); + INIT_DELAYED_WORK(&chg->uusb_otg_work, smblib_uusb_otg_work); + INIT_DELAYED_WORK(&chg->bb_removal_work, smblib_bb_removal_work); + chg->fake_capacity = -EINVAL; + chg->fake_input_current_limited = -EINVAL; + chg->fake_batt_status = -EINVAL; + + switch (chg->mode) { + case PARALLEL_MASTER: + rc = qcom_batt_init(&chg->chg_param); + if (rc < 0) { + smblib_err(chg, "Couldn't init qcom_batt_init rc=%d\n", + rc); + return rc; + } + + rc = qcom_step_chg_init(chg->dev, chg->step_chg_enabled, + chg->sw_jeita_enabled, true); + if (rc < 0) { + smblib_err(chg, "Couldn't init qcom_step_chg_init rc=%d\n", + rc); + return rc; + } + + rc = smblib_create_votables(chg); + if (rc < 0) { + smblib_err(chg, "Couldn't create votables rc=%d\n", + rc); + return rc; + } + + rc = smblib_register_notifier(chg); + if (rc < 0) { + smblib_err(chg, + "Couldn't register notifier rc=%d\n", rc); + return rc; + } + + chg->bms_psy = power_supply_get_by_name("bms"); + chg->pl.psy = power_supply_get_by_name("parallel"); + if (chg->pl.psy) { + rc = smblib_stat_sw_override_cfg(chg, false); + if (rc < 0) { + smblib_err(chg, + "Couldn't config stat sw rc=%d\n", rc); + return rc; + } + } + break; + case PARALLEL_SLAVE: + break; + default: + smblib_err(chg, "Unsupported mode %d\n", chg->mode); + return -EINVAL; + } + + return rc; +} + +int smblib_deinit(struct smb_charger *chg) +{ + switch (chg->mode) { + case PARALLEL_MASTER: + cancel_work_sync(&chg->bms_update_work); + cancel_work_sync(&chg->pl_update_work); + cancel_work_sync(&chg->rdstd_cc2_detach_work); + cancel_delayed_work_sync(&chg->hvdcp_detect_work); + cancel_delayed_work_sync(&chg->clear_hdc_work); + cancel_work_sync(&chg->otg_oc_work); + cancel_work_sync(&chg->vconn_oc_work); + cancel_delayed_work_sync(&chg->otg_ss_done_work); + cancel_delayed_work_sync(&chg->icl_change_work); + cancel_delayed_work_sync(&chg->pl_enable_work); + cancel_work_sync(&chg->legacy_detection_work); + cancel_delayed_work_sync(&chg->uusb_otg_work); + cancel_delayed_work_sync(&chg->bb_removal_work); + power_supply_unreg_notifier(&chg->nb); + smblib_destroy_votables(chg); + qcom_step_chg_deinit(); + qcom_batt_deinit(); + break; + case PARALLEL_SLAVE: + break; + default: + smblib_err(chg, "Unsupported mode %d\n", chg->mode); + return -EINVAL; + } + + smblib_iio_deinit(chg); + + return 0; +} diff --git a/drivers/power/supply/qcom/smb-lib.h b/drivers/power/supply/qcom/smb-lib.h new file mode 100644 index 000000000000..cdacd92f2ae3 --- /dev/null +++ b/drivers/power/supply/qcom/smb-lib.h @@ -0,0 +1,553 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (c) 2016-2018, 2020, The Linux Foundation. All rights reserved. + */ + +#ifndef __SMB2_CHARGER_H +#define __SMB2_CHARGER_H +#include +#include +#include +#include +#include +#include +#include "storm-watch.h" +#include "battery.h" + +enum print_reason { + PR_INTERRUPT = BIT(0), + PR_REGISTER = BIT(1), + PR_MISC = BIT(2), + PR_PARALLEL = BIT(3), + PR_OTG = BIT(4), +}; + +#define DEFAULT_VOTER "DEFAULT_VOTER" +#define USER_VOTER "USER_VOTER" +#define PD_VOTER "PD_VOTER" +#define DCP_VOTER "DCP_VOTER" +#define QC_VOTER "QC_VOTER" +#define PL_USBIN_USBIN_VOTER "PL_USBIN_USBIN_VOTER" +#define USB_PSY_VOTER "USB_PSY_VOTER" +#define PL_TAPER_WORK_RUNNING_VOTER "PL_TAPER_WORK_RUNNING_VOTER" +#define PL_QNOVO_VOTER "PL_QNOVO_VOTER" +#define USBIN_V_VOTER "USBIN_V_VOTER" +#define CHG_STATE_VOTER "CHG_STATE_VOTER" +#define TYPEC_SRC_VOTER "TYPEC_SRC_VOTER" +#define TAPER_END_VOTER "TAPER_END_VOTER" +#define THERMAL_DAEMON_VOTER "THERMAL_DAEMON_VOTER" +#define CC_DETACHED_VOTER "CC_DETACHED_VOTER" +#define HVDCP_TIMEOUT_VOTER "HVDCP_TIMEOUT_VOTER" +#define PD_DISALLOWED_INDIRECT_VOTER "PD_DISALLOWED_INDIRECT_VOTER" +#define PD_HARD_RESET_VOTER "PD_HARD_RESET_VOTER" +#define VBUS_CC_SHORT_VOTER "VBUS_CC_SHORT_VOTER" +#define PD_INACTIVE_VOTER "PD_INACTIVE_VOTER" +#define BOOST_BACK_VOTER "BOOST_BACK_VOTER" +#define USBIN_USBIN_BOOST_VOTER "USBIN_USBIN_BOOST_VOTER" +#define HVDCP_INDIRECT_VOTER "HVDCP_INDIRECT_VOTER" +#define MICRO_USB_VOTER "MICRO_USB_VOTER" +#define DEBUG_BOARD_VOTER "DEBUG_BOARD_VOTER" +#define PD_SUSPEND_SUPPORTED_VOTER "PD_SUSPEND_SUPPORTED_VOTER" +#define PL_DELAY_VOTER "PL_DELAY_VOTER" +#define CTM_VOTER "CTM_VOTER" +#define SW_QC3_VOTER "SW_QC3_VOTER" +#define AICL_RERUN_VOTER "AICL_RERUN_VOTER" +#define LEGACY_UNKNOWN_VOTER "LEGACY_UNKNOWN_VOTER" +#define CC2_WA_VOTER "CC2_WA_VOTER" +#define QNOVO_VOTER "QNOVO_VOTER" +#define BATT_PROFILE_VOTER "BATT_PROFILE_VOTER" +#define OTG_DELAY_VOTER "OTG_DELAY_VOTER" +#define USBIN_I_VOTER "USBIN_I_VOTER" +#define WEAK_CHARGER_VOTER "WEAK_CHARGER_VOTER" +#define OTG_VOTER "OTG_VOTER" +#define PL_FCC_LOW_VOTER "PL_FCC_LOW_VOTER" +#define WBC_VOTER "WBC_VOTER" +#define MOISTURE_VOTER "MOISTURE_VOTER" +#define HVDCP2_ICL_VOTER "HVDCP2_ICL_VOTER" +#define OV_VOTER "OV_VOTER" +#define FG_ESR_VOTER "FG_ESR_VOTER" +#define FCC_STEPPER_VOTER "FCC_STEPPER_VOTER" +#define PD_NOT_SUPPORTED_VOTER "PD_NOT_SUPPORTED_VOTER" + +#define VCONN_MAX_ATTEMPTS 3 +#define OTG_MAX_ATTEMPTS 3 +#define BOOST_BACK_STORM_COUNT 3 +#define WEAK_CHG_STORM_COUNT 8 + +enum smb_mode { + PARALLEL_MASTER = 0, + PARALLEL_SLAVE, + NUM_MODES, +}; + +enum { + QC_CHARGER_DETECTION_WA_BIT = BIT(0), + BOOST_BACK_WA = BIT(1), + TYPEC_CC2_REMOVAL_WA_BIT = BIT(2), + QC_AUTH_INTERRUPT_WA_BIT = BIT(3), + OTG_WA = BIT(4), + OV_IRQ_WA_BIT = BIT(5), + TYPEC_PBS_WA_BIT = BIT(6), +}; + +enum smb_irq_index { + CHG_ERROR_IRQ = 0, + CHG_STATE_CHANGE_IRQ, + STEP_CHG_STATE_CHANGE_IRQ, + STEP_CHG_SOC_UPDATE_FAIL_IRQ, + STEP_CHG_SOC_UPDATE_REQ_IRQ, + OTG_FAIL_IRQ, + OTG_OVERCURRENT_IRQ, + OTG_OC_DIS_SW_STS_IRQ, + TESTMODE_CHANGE_DET_IRQ, + BATT_TEMP_IRQ, + BATT_OCP_IRQ, + BATT_OV_IRQ, + BATT_LOW_IRQ, + BATT_THERM_ID_MISS_IRQ, + BATT_TERM_MISS_IRQ, + USBIN_COLLAPSE_IRQ, + USBIN_LT_3P6V_IRQ, + USBIN_UV_IRQ, + USBIN_OV_IRQ, + USBIN_PLUGIN_IRQ, + USBIN_SRC_CHANGE_IRQ, + USBIN_ICL_CHANGE_IRQ, + TYPE_C_CHANGE_IRQ, + DCIN_COLLAPSE_IRQ, + DCIN_LT_3P6V_IRQ, + DCIN_UV_IRQ, + DCIN_OV_IRQ, + DCIN_PLUGIN_IRQ, + DIV2_EN_DG_IRQ, + DCIN_ICL_CHANGE_IRQ, + WDOG_SNARL_IRQ, + WDOG_BARK_IRQ, + AICL_FAIL_IRQ, + AICL_DONE_IRQ, + HIGH_DUTY_CYCLE_IRQ, + INPUT_CURRENT_LIMIT_IRQ, + TEMPERATURE_CHANGE_IRQ, + SWITCH_POWER_OK_IRQ, + SMB_IRQ_MAX, +}; + +enum try_sink_exit_mode { + ATTACHED_SRC = 0, + ATTACHED_SINK, + UNATTACHED_SINK, +}; + +struct smb_irq_info { + const char *name; + const irq_handler_t handler; + const bool wake; + const struct storm_watch storm_data; + struct smb_irq_data *irq_data; + int irq; +}; + +static const unsigned int smblib_extcon_cable[] = { + EXTCON_USB, + EXTCON_USB_HOST, + EXTCON_NONE, +}; + +/* EXTCON_USB and EXTCON_USB_HOST are mutually exclusive */ +static const u32 smblib_extcon_exclusive[] = {0x3, 0}; + +struct smb_regulator { + struct regulator_dev *rdev; + struct regulator_desc rdesc; +}; + +struct smb_irq_data { + void *parent_data; + const char *name; + struct storm_watch storm_data; +}; + +struct smb_chg_param { + const char *name; + u16 reg; + int min_u; + int max_u; + int step_u; + int (*get_proc)(struct smb_chg_param *param, + u8 val_raw); + int (*set_proc)(struct smb_chg_param *param, + int val_u, + u8 *val_raw); +}; + +struct smb_chg_freq { + unsigned int freq_5V; + unsigned int freq_6V_8V; + unsigned int freq_9V; + unsigned int freq_12V; + unsigned int freq_removal; + unsigned int freq_below_otg_threshold; + unsigned int freq_above_otg_threshold; +}; + +struct smb_params { + struct smb_chg_param fcc; + struct smb_chg_param fv; + struct smb_chg_param usb_icl; + struct smb_chg_param icl_stat; + struct smb_chg_param otg_cl; + struct smb_chg_param dc_icl; + struct smb_chg_param dc_icl_pt_lv; + struct smb_chg_param dc_icl_pt_hv; + struct smb_chg_param dc_icl_div2_lv; + struct smb_chg_param dc_icl_div2_mid_lv; + struct smb_chg_param dc_icl_div2_mid_hv; + struct smb_chg_param dc_icl_div2_hv; + struct smb_chg_param jeita_cc_comp; + struct smb_chg_param freq_buck; + struct smb_chg_param freq_boost; +}; + +struct parallel_params { + struct power_supply *psy; +}; + +struct smb_iio { + struct iio_channel *temp_chan; + struct iio_channel *temp_max_chan; + struct iio_channel *usbin_i_chan; + struct iio_channel *usbin_v_chan; + struct iio_channel *batt_i_chan; + struct iio_channel *connector_temp_chan; + struct iio_channel *connector_temp_thr1_chan; + struct iio_channel *connector_temp_thr2_chan; + struct iio_channel *connector_temp_thr3_chan; +}; + +struct reg_info { + u16 reg; + u8 mask; + u8 val; + u8 bak; + const char *desc; +}; + +struct smb_charger { + struct device *dev; + char *name; + struct regmap *regmap; + struct smb_irq_info *irq_info; + struct smb_params param; + struct smb_iio iio; + int *debug_mask; + int *try_sink_enabled; + int *audio_headset_drp_wait_ms; + enum smb_mode mode; + struct smb_chg_freq chg_freq; + struct charger_param chg_param; + int otg_delay_ms; + int *weak_chg_icl_ua; + + /* locks */ + struct mutex lock; + struct mutex write_lock; + struct mutex ps_change_lock; + struct mutex otg_oc_lock; + struct mutex vconn_oc_lock; + + /* power supplies */ + struct power_supply *batt_psy; + struct power_supply *usb_psy; + struct power_supply *dc_psy; + struct power_supply *bms_psy; + struct power_supply_desc usb_psy_desc; + struct power_supply *usb_main_psy; + struct power_supply *usb_port_psy; + enum power_supply_type real_charger_type; + + /* notifiers */ + struct notifier_block nb; + + /* parallel charging */ + struct parallel_params pl; + + /* regulators */ + struct smb_regulator *vbus_vreg; + struct smb_regulator *vconn_vreg; + struct regulator *dpdm_reg; + + /* votables */ + struct votable *dc_suspend_votable; + struct votable *fcc_votable; + struct votable *fv_votable; + struct votable *usb_icl_votable; + struct votable *dc_icl_votable; + struct votable *pd_disallowed_votable_indirect; + struct votable *pd_allowed_votable; + struct votable *awake_votable; + struct votable *pl_disable_votable; + struct votable *chg_disable_votable; + struct votable *pl_enable_votable_indirect; + struct votable *hvdcp_disable_votable_indirect; + struct votable *hvdcp_enable_votable; + struct votable *apsd_disable_votable; + struct votable *hvdcp_hw_inov_dis_votable; + struct votable *usb_irq_enable_votable; + struct votable *typec_irq_disable_votable; + struct votable *disable_power_role_switch; + + /* work */ + struct work_struct bms_update_work; + struct work_struct pl_update_work; + struct work_struct rdstd_cc2_detach_work; + struct delayed_work hvdcp_detect_work; + struct delayed_work ps_change_timeout_work; + struct delayed_work clear_hdc_work; + struct work_struct otg_oc_work; + struct work_struct vconn_oc_work; + struct delayed_work otg_ss_done_work; + struct delayed_work icl_change_work; + struct delayed_work pl_enable_work; + struct work_struct legacy_detection_work; + struct delayed_work uusb_otg_work; + struct delayed_work bb_removal_work; + + /* cached status */ + int voltage_min_uv; + int voltage_max_uv; + int pd_active; + bool system_suspend_supported; + int boost_threshold_ua; + int system_temp_level; + int thermal_levels; + int *thermal_mitigation; + int dcp_icl_ua; + int fake_capacity; + int fake_batt_status; + bool step_chg_enabled; + bool sw_jeita_enabled; + bool is_hdc; + bool chg_done; + bool connector_type; + bool otg_en; + bool vconn_en; + bool suspend_input_on_debug_batt; + int otg_attempts; + int vconn_attempts; + int default_icl_ua; + int otg_cl_ua; + bool uusb_apsd_rerun_done; + bool pd_hard_reset; + bool typec_present; + u8 typec_status[5]; + bool typec_legacy_valid; + int fake_input_current_limited; + bool pr_swap_in_progress; + int typec_mode; + int usb_icl_change_irq_enabled; + u32 jeita_status; + u8 float_cfg; + bool use_extcon; + bool otg_present; + bool is_audio_adapter; + bool disable_stat_sw_override; + bool in_chg_lock; + bool fcc_stepper_enable; + bool ufp_only_mode; + + /* workaround flag */ + u32 wa_flags; + bool cc2_detach_wa_active; + bool typec_en_dis_active; + bool try_sink_active; + int boost_current_ua; + int temp_speed_reading_count; + int qc2_max_pulses; + bool non_compliant_chg_detected; + bool fake_usb_insertion; + bool reddragon_ipc_wa; + + /* extcon for VBUS / ID notification to USB for uUSB */ + struct extcon_dev *extcon; + + /* battery profile */ + int batt_profile_fcc_ua; + int batt_profile_fv_uv; + + /* qnovo */ + int usb_icl_delta_ua; + int pulse_cnt; + + int die_health; +}; + +int smblib_read(struct smb_charger *chg, u16 addr, u8 *val); +int smblib_masked_write(struct smb_charger *chg, u16 addr, u8 mask, u8 val); +int smblib_write(struct smb_charger *chg, u16 addr, u8 val); + +int smblib_get_charge_param(struct smb_charger *chg, + struct smb_chg_param *param, int *val_u); +int smblib_get_usb_suspend(struct smb_charger *chg, int *suspend); + +int smblib_enable_charging(struct smb_charger *chg, bool enable); +int smblib_set_charge_param(struct smb_charger *chg, + struct smb_chg_param *param, int val_u); +int smblib_set_usb_suspend(struct smb_charger *chg, bool suspend); +int smblib_set_dc_suspend(struct smb_charger *chg, bool suspend); + +int smblib_mapping_soc_from_field_value(struct smb_chg_param *param, + int val_u, u8 *val_raw); +int smblib_mapping_cc_delta_to_field_value(struct smb_chg_param *param, + u8 val_raw); +int smblib_mapping_cc_delta_from_field_value(struct smb_chg_param *param, + int val_u, u8 *val_raw); +int smblib_set_chg_freq(struct smb_chg_param *param, + int val_u, u8 *val_raw); + +int smblib_vbus_regulator_enable(struct regulator_dev *rdev); +int smblib_vbus_regulator_disable(struct regulator_dev *rdev); +int smblib_vbus_regulator_is_enabled(struct regulator_dev *rdev); + +int smblib_vconn_regulator_enable(struct regulator_dev *rdev); +int smblib_vconn_regulator_disable(struct regulator_dev *rdev); +int smblib_vconn_regulator_is_enabled(struct regulator_dev *rdev); + +irqreturn_t smblib_handle_debug(int irq, void *data); +irqreturn_t smblib_handle_otg_overcurrent(int irq, void *data); +irqreturn_t smblib_handle_chg_state_change(int irq, void *data); +irqreturn_t smblib_handle_batt_temp_changed(int irq, void *data); +irqreturn_t smblib_handle_batt_psy_changed(int irq, void *data); +irqreturn_t smblib_handle_usb_psy_changed(int irq, void *data); +irqreturn_t smblib_handle_usbin_uv(int irq, void *data); +irqreturn_t smblib_handle_usb_plugin(int irq, void *data); +irqreturn_t smblib_handle_usb_source_change(int irq, void *data); +irqreturn_t smblib_handle_icl_change(int irq, void *data); +irqreturn_t smblib_handle_usb_typec_change(int irq, void *data); +irqreturn_t smblib_handle_dc_plugin(int irq, void *data); +irqreturn_t smblib_handle_high_duty_cycle(int irq, void *data); +irqreturn_t smblib_handle_switcher_power_ok(int irq, void *data); +irqreturn_t smblib_handle_wdog_bark(int irq, void *data); + +int smblib_get_prop_input_suspend(struct smb_charger *chg, + union power_supply_propval *val); +int smblib_get_prop_batt_present(struct smb_charger *chg, + union power_supply_propval *val); +int smblib_get_prop_batt_capacity(struct smb_charger *chg, + union power_supply_propval *val); +int smblib_get_prop_batt_status(struct smb_charger *chg, + union power_supply_propval *val); +int smblib_get_prop_batt_charge_type(struct smb_charger *chg, + union power_supply_propval *val); +int smblib_get_prop_batt_charge_done(struct smb_charger *chg, + union power_supply_propval *val); +int smblib_get_prop_batt_health(struct smb_charger *chg, + union power_supply_propval *val); +int smblib_get_prop_system_temp_level(struct smb_charger *chg, + union power_supply_propval *val); +int smblib_get_prop_system_temp_level_max(struct smb_charger *chg, + union power_supply_propval *val); +int smblib_get_prop_input_current_limited(struct smb_charger *chg, + union power_supply_propval *val); +int smblib_set_prop_input_suspend(struct smb_charger *chg, + const union power_supply_propval *val); +int smblib_set_prop_batt_capacity(struct smb_charger *chg, + const union power_supply_propval *val); +int smblib_set_prop_batt_status(struct smb_charger *chg, + const union power_supply_propval *val); +int smblib_set_prop_system_temp_level(struct smb_charger *chg, + const union power_supply_propval *val); +int smblib_set_prop_input_current_limited(struct smb_charger *chg, + const union power_supply_propval *val); + +int smblib_get_prop_dc_present(struct smb_charger *chg, + union power_supply_propval *val); +int smblib_get_prop_dc_online(struct smb_charger *chg, + union power_supply_propval *val); +int smblib_get_prop_dc_current_max(struct smb_charger *chg, + union power_supply_propval *val); +int smblib_set_prop_dc_current_max(struct smb_charger *chg, + const union power_supply_propval *val); + +int smblib_get_prop_usb_present(struct smb_charger *chg, + union power_supply_propval *val); +int smblib_get_prop_usb_online(struct smb_charger *chg, + union power_supply_propval *val); +int smblib_get_prop_usb_suspend(struct smb_charger *chg, + union power_supply_propval *val); +int smblib_get_prop_usb_voltage_max(struct smb_charger *chg, + union power_supply_propval *val); +int smblib_get_prop_usb_voltage_max_design(struct smb_charger *chg, + union power_supply_propval *val); +int smblib_get_prop_usb_voltage_now(struct smb_charger *chg, + union power_supply_propval *val); +int smblib_get_prop_usb_current_now(struct smb_charger *chg, + union power_supply_propval *val); +int smblib_get_prop_typec_cc_orientation(struct smb_charger *chg, + union power_supply_propval *val); +int smblib_get_prop_typec_power_role(struct smb_charger *chg, + union power_supply_propval *val); +int smblib_get_prop_pd_allowed(struct smb_charger *chg, + union power_supply_propval *val); +int smblib_get_prop_input_current_settled(struct smb_charger *chg, + union power_supply_propval *val); +int smblib_get_prop_input_voltage_settled(struct smb_charger *chg, + union power_supply_propval *val); +int smblib_get_prop_pd_in_hard_reset(struct smb_charger *chg, + union power_supply_propval *val); +int smblib_get_pe_start(struct smb_charger *chg, + union power_supply_propval *val); +int smblib_get_prop_charger_temp(struct smb_charger *chg, + union power_supply_propval *val); +int smblib_get_prop_charger_temp_max(struct smb_charger *chg, + union power_supply_propval *val); +int smblib_get_prop_die_health(struct smb_charger *chg, + union power_supply_propval *val); +int smblib_get_prop_charge_qnovo_enable(struct smb_charger *chg, + union power_supply_propval *val); +int smblib_set_prop_pd_current_max(struct smb_charger *chg, + const union power_supply_propval *val); +int smblib_set_prop_sdp_current_max(struct smb_charger *chg, + const union power_supply_propval *val); +int smblib_set_prop_pd_voltage_max(struct smb_charger *chg, + const union power_supply_propval *val); +int smblib_set_prop_pd_voltage_min(struct smb_charger *chg, + const union power_supply_propval *val); +int smblib_set_prop_boost_current(struct smb_charger *chg, + const union power_supply_propval *val); +int smblib_set_prop_typec_power_role(struct smb_charger *chg, + const union power_supply_propval *val); +int smblib_set_prop_pd_active(struct smb_charger *chg, + const union power_supply_propval *val); +int smblib_set_prop_pd_in_hard_reset(struct smb_charger *chg, + const union power_supply_propval *val); +int smblib_get_prop_slave_current_now(struct smb_charger *chg, + union power_supply_propval *val); +int smblib_set_prop_ship_mode(struct smb_charger *chg, + const union power_supply_propval *val); +int smblib_set_prop_charge_qnovo_enable(struct smb_charger *chg, + const union power_supply_propval *val); +void smblib_suspend_on_debug_battery(struct smb_charger *chg); +int smblib_rerun_apsd_if_required(struct smb_charger *chg); +int smblib_get_prop_fcc_delta(struct smb_charger *chg, + union power_supply_propval *val); +int smblib_icl_override(struct smb_charger *chg, bool override); +int smblib_dp_dm(struct smb_charger *chg, int val); +int smblib_disable_hw_jeita(struct smb_charger *chg, bool disable); +int smblib_rerun_aicl(struct smb_charger *chg); +int smblib_set_icl_current(struct smb_charger *chg, int icl_ua); +int smblib_get_icl_current(struct smb_charger *chg, int *icl_ua); +int smblib_get_charge_current(struct smb_charger *chg, int *total_current_ua); +int smblib_get_prop_pr_swap_in_progress(struct smb_charger *chg, + union power_supply_propval *val); +int smblib_get_prop_from_bms(struct smb_charger *chg, + enum power_supply_property psp, + union power_supply_propval *val); +int smblib_set_prop_pr_swap_in_progress(struct smb_charger *chg, + const union power_supply_propval *val); +int smblib_stat_sw_override_cfg(struct smb_charger *chg, bool override); +void smblib_usb_typec_change(struct smb_charger *chg); +int smblib_toggle_stat(struct smb_charger *chg, int reset); +int smblib_force_ufp(struct smb_charger *chg); + +int smblib_init(struct smb_charger *chg); +int smblib_deinit(struct smb_charger *chg); +#endif /* __SMB2_CHARGER_H */ diff --git a/drivers/power/supply/qcom/smb-reg.h b/drivers/power/supply/qcom/smb-reg.h new file mode 100644 index 000000000000..718e51b1fa13 --- /dev/null +++ b/drivers/power/supply/qcom/smb-reg.h @@ -0,0 +1,1033 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (c) 2016-2020, The Linux Foundation. All rights reserved. + */ + +#ifndef __SMB2_CHARGER_REG_H +#define __SMB2_CHARGER_REG_H + +#include + +#define CHGR_BASE 0x1000 +#define OTG_BASE 0x1100 +#define BATIF_BASE 0x1200 +#define USBIN_BASE 0x1300 +#define DCIN_BASE 0x1400 +#define MISC_BASE 0x1600 +#define CHGR_FREQ_BASE 0x1900 + +#define PERPH_TYPE_OFFSET 0x04 +#define TYPE_MASK GENMASK(7, 0) +#define PERPH_SUBTYPE_OFFSET 0x05 +#define SUBTYPE_MASK GENMASK(7, 0) +#define INT_RT_STS_OFFSET 0x10 + +/* CHGR Peripheral Registers */ +#define BATTERY_CHARGER_STATUS_1_REG (CHGR_BASE + 0x06) +#define BVR_INITIAL_RAMP_BIT BIT(7) +#define CC_SOFT_TERMINATE_BIT BIT(6) +#define STEP_CHARGING_STATUS_SHIFT 3 +#define STEP_CHARGING_STATUS_MASK GENMASK(5, 3) +#define BATTERY_CHARGER_STATUS_MASK GENMASK(2, 0) +enum { + TRICKLE_CHARGE = 0, + PRE_CHARGE, + FAST_CHARGE, + FULLON_CHARGE, + TAPER_CHARGE, + TERMINATE_CHARGE, + INHIBIT_CHARGE, + DISABLE_CHARGE, +}; + +#define BATTERY_CHARGER_STATUS_2_REG (CHGR_BASE + 0x07) +#define INPUT_CURRENT_LIMITED_BIT BIT(7) +#define CHARGER_ERROR_STATUS_SFT_EXPIRE_BIT BIT(6) +#define CHARGER_ERROR_STATUS_BAT_OV_BIT BIT(5) +#define CHARGER_ERROR_STATUS_BAT_TERM_MISSING_BIT BIT(4) +#define BAT_TEMP_STATUS_MASK GENMASK(3, 0) +#define BAT_TEMP_STATUS_SOFT_LIMIT_MASK GENMASK(3, 2) +#define BAT_TEMP_STATUS_HOT_SOFT_LIMIT_BIT BIT(3) +#define BAT_TEMP_STATUS_COLD_SOFT_LIMIT_BIT BIT(2) +#define BAT_TEMP_STATUS_TOO_HOT_BIT BIT(1) +#define BAT_TEMP_STATUS_TOO_COLD_BIT BIT(0) + +#define CHG_OPTION_REG (CHGR_BASE + 0x08) +#define PIN_BIT BIT(7) + +#define BATTERY_CHARGER_STATUS_3_REG (CHGR_BASE + 0x09) +#define FV_POST_JEITA_MASK GENMASK(7, 0) + +#define BATTERY_CHARGER_STATUS_4_REG (CHGR_BASE + 0x0A) +#define CHARGE_CURRENT_POST_JEITA_MASK GENMASK(7, 0) + +#define BATTERY_CHARGER_STATUS_5_REG (CHGR_BASE + 0x0B) +#define VALID_INPUT_POWER_SOURCE_BIT BIT(7) +#define DISABLE_CHARGING_BIT BIT(6) +#define FORCE_ZERO_CHARGE_CURRENT_BIT BIT(5) +#define CHARGING_ENABLE_BIT BIT(4) +#define TAPER_BIT BIT(3) +#define ENABLE_CHG_SENSORS_BIT BIT(2) +#define ENABLE_TAPER_SENSOR_BIT BIT(1) +#define TAPER_REGION_BIT BIT(0) + +#define BATTERY_CHARGER_STATUS_6_REG (CHGR_BASE + 0x0C) +#define GF_BATT_OV_BIT BIT(7) +#define DROP_IN_BATTERY_VOLTAGE_REFERENCE_BIT BIT(6) +#define VBATT_LTET_RECHARGE_BIT BIT(5) +#define VBATT_GTET_INHIBIT_BIT BIT(4) +#define VBATT_GTET_FLOAT_VOLTAGE_BIT BIT(3) +#define BATT_GT_PRE_TO_FAST_BIT BIT(2) +#define BATT_GT_FULL_ON_BIT BIT(1) +#define VBATT_LT_2V_BIT BIT(0) + +#define BATTERY_CHARGER_STATUS_7_REG (CHGR_BASE + 0x0D) +#define ENABLE_TRICKLE_BIT BIT(7) +#define ENABLE_PRE_CHARGING_BIT BIT(6) +#define ENABLE_FAST_CHARGING_BIT BIT(5) +#define ENABLE_FULLON_MODE_BIT BIT(4) +#define TOO_COLD_ADC_BIT BIT(3) +#define TOO_HOT_ADC_BIT BIT(2) +#define HOT_SL_ADC_BIT BIT(1) +#define COLD_SL_ADC_BIT BIT(0) + +#define BATTERY_CHARGER_STATUS_8_REG (CHGR_BASE + 0x0E) +#define PRE_FAST_BIT BIT(7) +#define PRE_FULLON_BIT BIT(6) +#define PRE_RCHG_BIT BIT(5) +#define PRE_INHIBIT_BIT BIT(4) +#define PRE_OVRV_BIT BIT(3) +#define PRE_TERM_BIT BIT(2) +#define BAT_ID_BMISS_CMP_BIT BIT(1) +#define THERM_CMP_BIT BIT(0) + +/* CHGR Interrupt Bits */ +#define CHGR_7_RT_STS_BIT BIT(7) +#define CHGR_6_RT_STS_BIT BIT(6) +#define FG_FVCAL_QUALIFIED_RT_STS_BIT BIT(5) +#define STEP_CHARGING_SOC_UPDATE_REQUEST_RT_STS_BIT BIT(4) +#define STEP_CHARGING_SOC_UPDATE_FAIL_RT_STS_BIT BIT(3) +#define STEP_CHARGING_STATE_CHANGE_RT_STS_BIT BIT(2) +#define CHARGING_STATE_CHANGE_RT_STS_BIT BIT(1) +#define CHGR_ERROR_RT_STS_BIT BIT(0) + +#define STEP_CHG_SOC_VBATT_V_REG (CHGR_BASE + 0x40) +#define STEP_CHG_SOC_VBATT_V_MASK GENMASK(7, 0) + +#define STEP_CHG_SOC_VBATT_V_UPDATE_REG (CHGR_BASE + 0x41) +#define STEP_CHG_SOC_VBATT_V_UPDATE_BIT BIT(0) + +#define CHARGING_ENABLE_CMD_REG (CHGR_BASE + 0x42) +#define CHARGING_ENABLE_CMD_BIT BIT(0) + +#define ALLOW_FAST_CHARGING_CMD_REG (CHGR_BASE + 0x43) +#define ALLOW_FAST_CHARGING_CMD_BIT BIT(0) + +#define QNOVO_PT_ENABLE_CMD_REG (CHGR_BASE + 0x44) +#define QNOVO_PT_ENABLE_CMD_BIT BIT(0) + +#define CHGR_CFG1_REG (CHGR_BASE + 0x50) +#define INCREASE_RCHG_TIMEOUT_CFG_BIT BIT(1) +#define LOAD_BAT_BIT BIT(0) + +#define CHGR_CFG2_REG (CHGR_BASE + 0x51) +#define CHG_EN_SRC_BIT BIT(7) +#define CHG_EN_POLARITY_BIT BIT(6) +#define PRETOFAST_TRANSITION_CFG_BIT BIT(5) +#define BAT_OV_ECC_BIT BIT(4) +#define I_TERM_BIT BIT(3) +#define AUTO_RECHG_BIT BIT(2) +#define EN_ANALOG_DROP_IN_VBATT_BIT BIT(1) +#define CHARGER_INHIBIT_BIT BIT(0) + +#define CHARGER_ENABLE_CFG_REG (CHGR_BASE + 0x52) +#define CHG_ENB_TIMEOUT_SETTING_BIT BIT(1) +#define FORCE_ZERO_CFG_BIT BIT(0) + +#define CFG_REG (CHGR_BASE + 0x53) +#define CHG_OPTION_PIN_TRIM_BIT BIT(7) +#define BATN_SNS_CFG_BIT BIT(4) +#define CFG_TAPER_DIS_AFVC_BIT BIT(3) +#define BATFET_SHUTDOWN_CFG_BIT BIT(2) +#define VDISCHG_EN_CFG_BIT BIT(1) +#define VCHG_EN_CFG_BIT BIT(0) + +#define CHARGER_SPARE_REG (CHGR_BASE + 0x54) +#define CHARGER_SPARE_MASK GENMASK(5, 0) + +#define PRE_CHARGE_CURRENT_CFG_REG (CHGR_BASE + 0x60) +#define PRE_CHARGE_CURRENT_SETTING_MASK GENMASK(5, 0) + +#define FAST_CHARGE_CURRENT_CFG_REG (CHGR_BASE + 0x61) +#define FAST_CHARGE_CURRENT_SETTING_MASK GENMASK(7, 0) + +#define CHARGE_CURRENT_TERMINATION_CFG_REG (CHGR_BASE + 0x62) +#define ANALOG_CHARGE_CURRENT_TERMINATION_SETTING_MASK GENMASK(2, 0) + +#define TCCC_CHARGE_CURRENT_TERMINATION_CFG_REG (CHGR_BASE + 0x63) +#define TCCC_CHARGE_CURRENT_TERMINATION_SETTING_MASK GENMASK(3, 0) + +#define CHARGE_CURRENT_SOFTSTART_SETTING_CFG_REG (CHGR_BASE + 0x64) +#define CHARGE_CURRENT_SOFTSTART_SETTING_MASK GENMASK(1, 0) + +#define FLOAT_VOLTAGE_CFG_REG (CHGR_BASE + 0x70) +#define FLOAT_VOLTAGE_SETTING_MASK GENMASK(7, 0) + +#define AUTO_FLOAT_VOLTAGE_COMPENSATION_CFG_REG (CHGR_BASE + 0x71) +#define AUTO_FLOAT_VOLTAGE_COMPENSATION_MASK GENMASK(2, 0) + +#define CHARGE_INHIBIT_THRESHOLD_CFG_REG (CHGR_BASE + 0x72) +#define CHARGE_INHIBIT_THRESHOLD_MASK GENMASK(1, 0) +#define CHARGE_INHIBIT_THRESHOLD_50MV 0 +#define CHARGE_INHIBIT_THRESHOLD_100MV 1 +#define CHARGE_INHIBIT_THRESHOLD_200MV 2 +#define CHARGE_INHIBIT_THRESHOLD_300MV 3 + +#define RECHARGE_THRESHOLD_CFG_REG (CHGR_BASE + 0x73) +#define RECHARGE_THRESHOLD_MASK GENMASK(1, 0) + +#define PRE_TO_FAST_CHARGE_THRESHOLD_CFG_REG (CHGR_BASE + 0x74) +#define PRE_TO_FAST_CHARGE_THRESHOLD_MASK GENMASK(1, 0) + +#define FV_HYSTERESIS_CFG_REG (CHGR_BASE + 0x75) +#define FV_DROP_HYSTERESIS_CFG_MASK GENMASK(7, 4) +#define THRESH_HYSTERESIS_CFG_MASK GENMASK(3, 0) + +#define FVC_CHARGE_INHIBIT_THRESHOLD_CFG_REG (CHGR_BASE + 0x80) +#define FVC_CHARGE_INHIBIT_THRESHOLD_MASK GENMASK(5, 0) + +#define FVC_RECHARGE_THRESHOLD_CFG_REG (CHGR_BASE + 0x81) +#define FVC_RECHARGE_THRESHOLD_MASK GENMASK(7, 0) + +#define FVC_PRE_TO_FAST_CHARGE_THRESHOLD_CFG_REG (CHGR_BASE + 0x82) +#define FVC_PRE_TO_FAST_CHARGE_THRESHOLD_MASK GENMASK(7, 0) + +#define FVC_FULL_ON_THRESHOLD_CFG_REG (CHGR_BASE + 0x83) +#define FVC_FULL_ON_THRESHOLD_MASK GENMASK(7, 0) + +#define FVC_CC_MODE_GLITCH_FILTER_SEL_CFG_REG (CHGR_BASE + 0x84) +#define FVC_CC_MODE_GLITCH_FILTER_SEL_MASK GENMASK(1, 0) + +#define FVC_TERMINATION_GLITCH_FILTER_SEL_CFG_REG (CHGR_BASE + 0x85) +#define FVC_TERMINATION_GLITCH_FILTER_SEL_MASK GENMASK(1, 0) + +#define JEITA_EN_CFG_REG (CHGR_BASE + 0x90) +#define JEITA_EN_HARDLIMIT_BIT BIT(4) +#define JEITA_EN_HOT_SL_FCV_BIT BIT(3) +#define JEITA_EN_COLD_SL_FCV_BIT BIT(2) +#define JEITA_EN_HOT_SL_CCC_BIT BIT(1) +#define JEITA_EN_COLD_SL_CCC_BIT BIT(0) + +#define JEITA_FVCOMP_CFG_REG (CHGR_BASE + 0x91) +#define JEITA_FVCOMP_MASK GENMASK(7, 0) + +#define JEITA_CCCOMP_CFG_REG (CHGR_BASE + 0x92) +#define JEITA_CCCOMP_MASK GENMASK(7, 0) + +#define FV_CAL_CFG_REG (CHGR_BASE + 0x76) +#define FV_CALIBRATION_CFG_MASK GENMASK(2, 0) + +#define FV_ADJUST_REG (CHGR_BASE + 0x77) +#define FLOAT_VOLTAGE_ADJUSTMENT_MASK GENMASK(4, 0) + +#define FG_VADC_DISQ_THRESH_REG (CHGR_BASE + 0x78) +#define VADC_DISQUAL_THRESH_MASK GENMASK(7, 0) + +#define FG_IADC_DISQ_THRESH_REG (CHGR_BASE + 0x79) +#define IADC_DISQUAL_THRESH_MASK GENMASK(7, 0) + +#define FG_UPDATE_CFG_1_REG (CHGR_BASE + 0x7A) +#define BT_TMPR_TCOLD_BIT BIT(7) +#define BT_TMPR_COLD_BIT BIT(6) +#define BT_TMPR_HOT_BIT BIT(5) +#define BT_TMPR_THOT_BIT BIT(4) +#define CHG_DIE_TMPR_HOT_BIT BIT(3) +#define CHG_DIE_TMPR_THOT_BIT BIT(2) +#define SKIN_TMPR_HOT_BIT BIT(1) +#define SKIN_TMPR_THOT_BIT BIT(0) + +#define FG_UPDATE_CFG_1_SEL_REG (CHGR_BASE + 0x7B) +#define BT_TMPR_TCOLD_SEL_BIT BIT(7) +#define BT_TMPR_COLD_SEL_BIT BIT(6) +#define BT_TMPR_HOT_SEL_BIT BIT(5) +#define BT_TMPR_THOT_SEL_BIT BIT(4) +#define CHG_DIE_TMPR_HOT_SEL_BIT BIT(3) +#define CHG_DIE_TMPR_THOT_SEL_BIT BIT(2) +#define SKIN_TMPR_HOT_SEL_BIT BIT(1) +#define SKIN_TMPR_THOT_SEL_BIT BIT(0) + +#define FG_UPDATE_CFG_2_REG (CHGR_BASE + 0x7C) +#define SOC_LT_OTG_THRESH_BIT BIT(3) +#define SOC_LT_CHG_RECHARGE_THRESH_BIT BIT(2) +#define VBT_LT_CHG_RECHARGE_THRESH_BIT BIT(1) +#define IBT_LT_CHG_TERM_THRESH_BIT BIT(0) + +#define FG_UPDATE_CFG_2_SEL_REG (CHGR_BASE + 0x7D) +#define SOC_LT_OTG_THRESH_SEL_BIT BIT(3) +#define SOC_LT_CHG_RECHARGE_THRESH_SEL_BIT BIT(2) +#define VBT_LT_CHG_RECHARGE_THRESH_SEL_BIT BIT(1) +#define IBT_LT_CHG_TERM_THRESH_SEL_BIT BIT(0) + +#define FG_CHG_INTERFACE_CFG_REG (CHGR_BASE + 0x7E) +#define ESR_ISINK_CFG_MASK GENMASK(7, 6) +#define ESR_FASTCHG_DECR_CFG_MASK GENMASK(5, 4) +#define FG_CHARGER_INHIBIT_BIT BIT(3) +#define FG_BATFET_BIT BIT(2) +#define IADC_SYNC_CNV_BIT BIT(1) +#define VADC_SYNC_CNV_BIT BIT(0) + +#define FG_CHG_INTERFACE_CFG_SEL_REG (CHGR_BASE + 0x7F) +#define ESR_ISINK_CFG_SEL_BIT BIT(5) +#define ESR_FASTCHG_DECR_CFG_SEL_BIT BIT(4) +#define FG_CHARGER_INHIBIT_SEL_BIT BIT(3) +#define FG_BATFET_SEL_BIT BIT(2) +#define IADC_SYNC_CNV_SEL_BIT BIT(1) +#define VADC_SYNC_CNV_SEL_BIT BIT(0) + +#define CHGR_STEP_CHG_MODE_CFG_REG (CHGR_BASE + 0xB0) +#define STEP_CHARGING_SOC_FAIL_OPTION_BIT BIT(3) +#define STEP_CHARGING_MODE_SELECT_BIT BIT(2) +#define STEP_CHARGING_SOURCE_SELECT_BIT BIT(1) +#define STEP_CHARGING_ENABLE_BIT BIT(0) + +#define STEP_CHG_UPDATE_REQUEST_TIMEOUT_CFG_REG (CHGR_BASE + 0xB1) +#define STEP_CHG_UPDATE_REQUEST_TIMEOUT_CFG_MASK GENMASK(0, 1) +#define STEP_CHG_UPDATE_REQUEST_TIMEOUT_5S 0 +#define STEP_CHG_UPDATE_REQUEST_TIMEOUT_10S 1 +#define STEP_CHG_UPDATE_REQUEST_TIMEOUT_20S 2 +#define STEP_CHG_UPDATE_REQUEST_TIMEOUT_40S 3 + +#define STEP_CHG_UPDATE_FAIL_TIMEOUT_CFG_REG (CHGR_BASE + 0xB2) +#define STEP_CHG_UPDATE_FAIL_TIMEOUT_CFG_MASK GENMASK(0, 1) +#define STEP_CHG_UPDATE_FAIL_TIMEOUT_10S 0 +#define STEP_CHG_UPDATE_FAIL_TIMEOUT_30S 1 +#define STEP_CHG_UPDATE_FAIL_TIMEOUT_60S 2 +#define STEP_CHG_UPDATE_FAIL_TIMEOUT_120S 3 + +#define STEP_CHG_SOC_OR_BATT_V_TH1_REG (CHGR_BASE + 0xB3) +#define STEP_CHG_SOC_OR_BATT_V_TH2_REG (CHGR_BASE + 0xB4) +#define STEP_CHG_SOC_OR_BATT_V_TH3_REG (CHGR_BASE + 0xB5) +#define STEP_CHG_SOC_OR_BATT_V_TH4_REG (CHGR_BASE + 0xB6) +#define STEP_CHG_CURRENT_DELTA1_REG (CHGR_BASE + 0xB7) +#define STEP_CHG_CURRENT_DELTA2_REG (CHGR_BASE + 0xB8) +#define STEP_CHG_CURRENT_DELTA3_REG (CHGR_BASE + 0xB9) +#define STEP_CHG_CURRENT_DELTA4_REG (CHGR_BASE + 0xBA) +#define STEP_CHG_CURRENT_DELTA5_REG (CHGR_BASE + 0xBB) + +/* OTG Peripheral Registers */ +#define RID_CC_CONTROL_23_16_REG (OTG_BASE + 0x06) +#define RID_CC_CONTROL_23_BIT BIT(7) +#define VCONN_SOFTSTART_EN_BIT BIT(6) +#define VCONN_SFTST_CFG_MASK GENMASK(5, 4) +#define CONNECT_RIDCC_SENSOR_TO_CC_MASK GENMASK(3, 2) +#define EN_CC_1P1CLAMP_BIT BIT(1) +#define ENABLE_CRUDESEN_CC_1_BIT BIT(0) + +#define RID_CC_CONTROL_15_8_REG (OTG_BASE + 0x07) +#define ENABLE_CRUDESEN_CC_0_BIT BIT(7) +#define EN_FMB_2P5UA_CC_MASK GENMASK(6, 5) +#define EN_ISRC_180UA_BIT BIT(4) +#define ENABLE_CURRENTSOURCE_CC_MASK GENMASK(3, 2) +#define EN_BANDGAP_RID_C_DET_BIT BIT(1) +#define ENABLE_RD_CC_1_BIT BIT(0) + +#define RID_CC_CONTROL_7_0_REG (OTG_BASE + 0x08) +#define ENABLE_RD_CC_0_BIT BIT(7) +#define VCONN_ILIM500MA_BIT BIT(6) +#define EN_MICRO_USB_MODE_BIT BIT(5) +#define UFP_DFP_MODE_BIT BIT(4) +#define VCONN_EN_CC_MASK GENMASK(3, 2) +#define VREF_SEL_RIDCC_SENSOR_MASK GENMASK(1, 0) + +#define OTG_STATUS_REG (OTG_BASE + 0x09) +#define BOOST_SOFTSTART_DONE_BIT BIT(3) +#define OTG_STATE_MASK GENMASK(2, 0) +#define OTG_STATE_ENABLED 0x2 + +/* OTG Interrupt Bits */ +#define TESTMODE_CHANGE_DETECT_RT_STS_BIT BIT(3) +#define OTG_OC_DIS_SW_STS_RT_STS_BIT BIT(2) +#define OTG_OVERCURRENT_RT_STS_BIT BIT(1) +#define OTG_FAIL_RT_STS_BIT BIT(0) + +#define CMD_OTG_REG (OTG_BASE + 0x40) +#define OTG_EN_BIT BIT(0) + +#define BAT_UVLO_THRESHOLD_CFG_REG (OTG_BASE + 0x51) +#define BAT_UVLO_THRESHOLD_MASK GENMASK(1, 0) + +#define OTG_CURRENT_LIMIT_CFG_REG (OTG_BASE + 0x52) +#define OTG_CURRENT_LIMIT_MASK GENMASK(2, 0) + +#define OTG_CFG_REG (OTG_BASE + 0x53) +#define OTG_RESERVED_MASK GENMASK(7, 6) +#define DIS_OTG_ON_TLIM_BIT BIT(5) +#define QUICKSTART_OTG_FASTROLESWAP_BIT BIT(4) +#define INCREASE_DFP_TIME_BIT BIT(3) +#define ENABLE_OTG_IN_DEBUG_MODE_BIT BIT(2) +#define OTG_EN_SRC_CFG_BIT BIT(1) +#define CONCURRENT_MODE_CFG_BIT BIT(0) + +#define OTG_ENG_OTG_CFG_REG (OTG_BASE + 0xC0) +#define ENG_BUCKBOOST_HALT1_8_MODE_BIT BIT(0) + +/* BATIF Peripheral Registers */ +/* BATIF Interrupt Bits */ +#define BAT_7_RT_STS_BIT BIT(7) +#define BAT_6_RT_STS_BIT BIT(6) +#define BAT_TERMINAL_MISSING_RT_STS_BIT BIT(5) +#define BAT_THERM_OR_ID_MISSING_RT_STS_BIT BIT(4) +#define BAT_LOW_RT_STS_BIT BIT(3) +#define BAT_OV_RT_STS_BIT BIT(2) +#define BAT_OCP_RT_STS_BIT BIT(1) +#define BAT_TEMP_RT_STS_BIT BIT(0) + +#define SHIP_MODE_REG (BATIF_BASE + 0x40) +#define SHIP_MODE_EN_BIT BIT(0) + +#define BATOCP_THRESHOLD_CFG_REG (BATIF_BASE + 0x50) +#define BATOCP_ENABLE_CFG_BIT BIT(3) +#define BATOCP_THRESHOLD_MASK GENMASK(2, 0) + +#define BATOCP_INTRPT_DELAY_TMR_CFG_REG (BATIF_BASE + 0x51) +#define BATOCP_INTRPT_TIMEOUT_MASK GENMASK(5, 3) +#define BATOCP_DELAY_TIMEOUT_MASK GENMASK(2, 0) + +#define BATOCP_RESET_TMR_CFG_REG (BATIF_BASE + 0x52) +#define EN_BATOCP_RESET_TMR_BIT BIT(3) +#define BATOCP_RESET_TIMEOUT_MASK GENMASK(2, 0) + +#define LOW_BATT_DETECT_EN_CFG_REG (BATIF_BASE + 0x60) +#define LOW_BATT_DETECT_EN_BIT BIT(0) + +#define LOW_BATT_THRESHOLD_CFG_REG (BATIF_BASE + 0x61) +#define LOW_BATT_THRESHOLD_MASK GENMASK(3, 0) + +#define BAT_FET_CFG_REG (BATIF_BASE + 0x62) +#define BAT_FET_CFG_BIT BIT(0) + +#define BAT_MISS_SRC_CFG_REG (BATIF_BASE + 0x70) +#define BAT_MISS_ALG_EN_BIT BIT(2) +#define BAT_MISS_RESERVED_BIT BIT(1) +#define BAT_MISS_PIN_SRC_EN_BIT BIT(0) + +#define BAT_MISS_ALG_OPTIONS_CFG_REG (BATIF_BASE + 0x71) +#define BAT_MISS_INPUT_PLUGIN_BIT BIT(2) +#define BAT_MISS_TMR_START_OPTION_BIT BIT(1) +#define BAT_MISS_POLL_EN_BIT BIT(0) + +#define BAT_MISS_PIN_GF_CFG_REG (BATIF_BASE + 0x72) +#define BAT_MISS_PIN_GF_MASK GENMASK(1, 0) + +/* USBIN Peripheral Registers */ +#define USBIN_INPUT_STATUS_REG (USBIN_BASE + 0x06) +#define USBIN_INPUT_STATUS_7_BIT BIT(7) +#define USBIN_INPUT_STATUS_6_BIT BIT(6) +#define USBIN_12V_BIT BIT(5) +#define USBIN_9V_TO_12V_BIT BIT(4) +#define USBIN_9V_BIT BIT(3) +#define USBIN_5V_TO_12V_BIT BIT(2) +#define USBIN_5V_TO_9V_BIT BIT(1) +#define USBIN_5V_BIT BIT(0) +#define QC_2P0_STATUS_MASK GENMASK(2, 0) + +#define APSD_STATUS_REG (USBIN_BASE + 0x07) +#define APSD_STATUS_7_BIT BIT(7) +#define HVDCP_CHECK_TIMEOUT_BIT BIT(6) +#define SLOW_PLUGIN_TIMEOUT_BIT BIT(5) +#define ENUMERATION_DONE_BIT BIT(4) +#define VADP_CHANGE_DONE_AFTER_AUTH_BIT BIT(3) +#define QC_AUTH_DONE_STATUS_BIT BIT(2) +#define QC_CHARGER_BIT BIT(1) +#define APSD_DTC_STATUS_DONE_BIT BIT(0) + +#define APSD_RESULT_STATUS_REG (USBIN_BASE + 0x08) +#define ICL_OVERRIDE_LATCH_BIT BIT(7) +#define APSD_RESULT_STATUS_MASK GENMASK(6, 0) +#define QC_3P0_BIT BIT(6) +#define QC_2P0_BIT BIT(5) +#define FLOAT_CHARGER_BIT BIT(4) +#define DCP_CHARGER_BIT BIT(3) +#define CDP_CHARGER_BIT BIT(2) +#define OCP_CHARGER_BIT BIT(1) +#define SDP_CHARGER_BIT BIT(0) + +#define QC_CHANGE_STATUS_REG (USBIN_BASE + 0x09) +#define QC_CHANGE_STATUS_7_BIT BIT(7) +#define QC_CHANGE_STATUS_6_BIT BIT(6) +#define QC_9V_TO_12V_REASON_BIT BIT(5) +#define QC_5V_TO_9V_REASON_BIT BIT(4) +#define QC_CONTINUOUS_BIT BIT(3) +#define QC_12V_BIT BIT(2) +#define QC_9V_BIT BIT(1) +#define QC_5V_BIT BIT(0) + +#define QC_PULSE_COUNT_STATUS_REG (USBIN_BASE + 0x0A) +#define QC_PULSE_COUNT_STATUS_7_BIT BIT(7) +#define QC_PULSE_COUNT_STATUS_6_BIT BIT(6) +#define QC_PULSE_COUNT_MASK GENMASK(5, 0) + +#define TYPE_C_STATUS_1_REG (USBIN_BASE + 0x0B) +#define UFP_TYPEC_MASK GENMASK(7, 5) +#define UFP_TYPEC_RDSTD_BIT BIT(7) +#define UFP_TYPEC_RD1P5_BIT BIT(6) +#define UFP_TYPEC_RD3P0_BIT BIT(5) +#define UFP_TYPEC_FMB_255K_BIT BIT(4) +#define UFP_TYPEC_FMB_301K_BIT BIT(3) +#define UFP_TYPEC_FMB_523K_BIT BIT(2) +#define UFP_TYPEC_FMB_619K_BIT BIT(1) +#define UFP_TYPEC_OPEN_OPEN_BIT BIT(0) + +#define TYPE_C_STATUS_2_REG (USBIN_BASE + 0x0C) +#define DFP_RA_OPEN_BIT BIT(7) +#define TIMER_STAGE_BIT BIT(6) +#define EXIT_UFP_MODE_BIT BIT(5) +#define EXIT_DFP_MODE_BIT BIT(4) +#define DFP_TYPEC_MASK GENMASK(3, 0) +#define DFP_RD_OPEN_BIT BIT(3) +#define DFP_RD_RA_VCONN_BIT BIT(2) +#define DFP_RD_RD_BIT BIT(1) +#define DFP_RA_RA_BIT BIT(0) + +#define TYPE_C_STATUS_3_REG (USBIN_BASE + 0x0D) +#define ENABLE_BANDGAP_BIT BIT(7) +#define U_USB_GND_NOVBUS_BIT BIT(6) +#define U_USB_FLOAT_NOVBUS_BIT BIT(5) +#define U_USB_GND_BIT BIT(4) +#define U_USB_FMB1_BIT BIT(3) +#define U_USB_FLOAT1_BIT BIT(2) +#define U_USB_FMB2_BIT BIT(1) +#define U_USB_FLOAT2_BIT BIT(0) + +#define TYPE_C_STATUS_4_REG (USBIN_BASE + 0x0E) +#define UFP_DFP_MODE_STATUS_BIT BIT(7) +#define TYPEC_VBUS_STATUS_BIT BIT(6) +#define TYPEC_VBUS_ERROR_STATUS_BIT BIT(5) +#define TYPEC_DEBOUNCE_DONE_STATUS_BIT BIT(4) +#define TYPEC_UFP_AUDIO_ADAPT_STATUS_BIT BIT(3) +#define TYPEC_VCONN_OVERCURR_STATUS_BIT BIT(2) +#define CC_ORIENTATION_BIT BIT(1) +#define CC_ATTACHED_BIT BIT(0) + +#define TYPE_C_STATUS_5_REG (USBIN_BASE + 0x0F) +#define TRY_SOURCE_FAILED_BIT BIT(6) +#define TRY_SINK_FAILED_BIT BIT(5) +#define TIMER_STAGE_2_BIT BIT(4) +#define TYPEC_LEGACY_CABLE_STATUS_BIT BIT(3) +#define TYPEC_NONCOMP_LEGACY_CABLE_STATUS_BIT BIT(2) +#define TYPEC_TRYSOURCE_DETECT_STATUS_BIT BIT(1) +#define TYPEC_TRYSINK_DETECT_STATUS_BIT BIT(0) + +/* USBIN Interrupt Bits */ +#define TYPE_C_CHANGE_RT_STS_BIT BIT(7) +#define USBIN_ICL_CHANGE_RT_STS_BIT BIT(6) +#define USBIN_SOURCE_CHANGE_RT_STS_BIT BIT(5) +#define USBIN_PLUGIN_RT_STS_BIT BIT(4) +#define USBIN_OV_RT_STS_BIT BIT(3) +#define USBIN_UV_RT_STS_BIT BIT(2) +#define USBIN_LT_3P6V_RT_STS_BIT BIT(1) +#define USBIN_COLLAPSE_RT_STS_BIT BIT(0) + +#define QC_PULSE_COUNT_STATUS_1_REG (USBIN_BASE + 0x30) + +#define USBIN_CMD_IL_REG (USBIN_BASE + 0x40) +#define BAT_2_SYS_FET_DIS_BIT BIT(1) +#define USBIN_SUSPEND_BIT BIT(0) + +#define CMD_APSD_REG (USBIN_BASE + 0x41) +#define ICL_OVERRIDE_BIT BIT(1) +#define APSD_RERUN_BIT BIT(0) + +#define CMD_HVDCP_2_REG (USBIN_BASE + 0x43) +#define RESTART_AICL_BIT BIT(7) +#define TRIGGER_AICL_BIT BIT(6) +#define FORCE_12V_BIT BIT(5) +#define FORCE_9V_BIT BIT(4) +#define FORCE_5V_BIT BIT(3) +#define IDLE_BIT BIT(2) +#define SINGLE_DECREMENT_BIT BIT(1) +#define SINGLE_INCREMENT_BIT BIT(0) + +#define USB_MISC2_REG (USBIN_BASE + 0x57) +#define USB_MISC2_MASK GENMASK(1, 0) + +#define TYPE_C_CFG_REG (USBIN_BASE + 0x58) +#define APSD_START_ON_CC_BIT BIT(7) +#define WAIT_FOR_APSD_BIT BIT(6) +#define FACTORY_MODE_DETECTION_EN_BIT BIT(5) +#define FACTORY_MODE_ICL_3A_4A_BIT BIT(4) +#define FACTORY_MODE_DIS_CHGING_CFG_BIT BIT(3) +#define SUSPEND_NON_COMPLIANT_CFG_BIT BIT(2) +#define VCONN_OC_CFG_BIT BIT(1) +#define TYPE_C_OR_U_USB_BIT BIT(0) + +#define TYPE_C_CFG_2_REG (USBIN_BASE + 0x59) +#define TYPE_C_DFP_CURRSRC_MODE_BIT BIT(7) +#define DFP_CC_1P4V_OR_1P6V_BIT BIT(6) +#define VCONN_SOFTSTART_CFG_MASK GENMASK(5, 4) +#define EN_TRY_SOURCE_MODE_BIT BIT(3) +#define USB_FACTORY_MODE_ENABLE_BIT BIT(2) +#define TYPE_C_UFP_MODE_BIT BIT(1) +#define EN_80UA_180UA_CUR_SOURCE_BIT BIT(0) + +#define TYPE_C_CFG_3_REG (USBIN_BASE + 0x5A) +#define TVBUS_DEBOUNCE_BIT BIT(7) +#define TYPEC_LEGACY_CABLE_INT_EN_BIT BIT(6) +#define TYPEC_NONCOMPLIANT_LEGACY_CABLE_INT_EN_BIT BIT(5) +#define TYPEC_TRYSOURCE_DETECT_INT_EN_BIT BIT(4) +#define TYPEC_TRYSINK_DETECT_INT_EN_BIT BIT(3) +#define EN_TRYSINK_MODE_BIT BIT(2) +#define EN_LEGACY_CABLE_DETECTION_BIT BIT(1) +#define ALLOW_PD_DRING_UFP_TCCDB_BIT BIT(0) + +#define HVDCP_PULSE_COUNT_MAX_REG (USBIN_BASE + 0x5B) +#define HVDCP_PULSE_COUNT_MAX_QC2_MASK GENMASK(7, 6) +enum { + HVDCP_PULSE_COUNT_MAX_QC2_5V, + HVDCP_PULSE_COUNT_MAX_QC2_9V, + HVDCP_PULSE_COUNT_MAX_QC2_12V, + HVDCP_PULSE_COUNT_MAX_QC2_INVALID +}; + +#define USBIN_ADAPTER_ALLOW_CFG_REG (USBIN_BASE + 0x60) +#define USBIN_ADAPTER_ALLOW_MASK GENMASK(3, 0) +enum { + USBIN_ADAPTER_ALLOW_5V = 0, + USBIN_ADAPTER_ALLOW_9V = 2, + USBIN_ADAPTER_ALLOW_5V_OR_9V = 3, + USBIN_ADAPTER_ALLOW_12V = 4, + USBIN_ADAPTER_ALLOW_5V_OR_12V = 5, + USBIN_ADAPTER_ALLOW_9V_TO_12V = 6, + USBIN_ADAPTER_ALLOW_5V_OR_9V_TO_12V = 7, + USBIN_ADAPTER_ALLOW_5V_TO_9V = 8, + USBIN_ADAPTER_ALLOW_5V_TO_12V = 12, +}; + +#define USBIN_OPTIONS_1_CFG_REG (USBIN_BASE + 0x62) +#define CABLE_R_SEL_BIT BIT(7) +#define HVDCP_AUTH_ALG_EN_CFG_BIT BIT(6) +#define HVDCP_AUTONOMOUS_MODE_EN_CFG_BIT BIT(5) +#define INPUT_PRIORITY_BIT BIT(4) +#define AUTO_SRC_DETECT_BIT BIT(3) +#define HVDCP_EN_BIT BIT(2) +#define VADP_INCREMENT_VOLTAGE_LIMIT_BIT BIT(1) +#define VADP_TAPER_TIMER_EN_BIT BIT(0) + +#define USBIN_OPTIONS_2_CFG_REG (USBIN_BASE + 0x63) +#define WIPWR_RST_EUD_CFG_BIT BIT(7) +#define SWITCHER_START_CFG_BIT BIT(6) +#define DCD_TIMEOUT_SEL_BIT BIT(5) +#define OCD_CURRENT_SEL_BIT BIT(4) +#define SLOW_PLUGIN_TIMER_EN_CFG_BIT BIT(3) +#define FLOAT_OPTIONS_MASK GENMASK(2, 0) +#define FLOAT_DIS_CHGING_CFG_BIT BIT(2) +#define SUSPEND_FLOAT_CFG_BIT BIT(1) +#define FORCE_FLOAT_SDP_CFG_BIT BIT(0) + +#define TAPER_TIMER_SEL_CFG_REG (USBIN_BASE + 0x64) +#define TYPEC_SPARE_CFG_BIT BIT(7) +#define TYPEC_DRP_DFP_TIME_CFG_BIT BIT(5) +#define TAPER_TIMER_SEL_MASK GENMASK(1, 0) + +#define USBIN_LOAD_CFG_REG (USBIN_BASE + 0x65) +#define USBIN_OV_CH_LOAD_OPTION_BIT BIT(7) +#define ICL_OVERRIDE_AFTER_APSD_BIT BIT(4) + +#define USBIN_ICL_OPTIONS_REG (USBIN_BASE + 0x66) +#define CFG_USB3P0_SEL_BIT BIT(2) +#define USB51_MODE_BIT BIT(1) +#define USBIN_MODE_CHG_BIT BIT(0) + +#define TYPE_C_INTRPT_ENB_REG (USBIN_BASE + 0x67) +#define TYPEC_CCOUT_DETACH_INT_EN_BIT BIT(7) +#define TYPEC_CCOUT_ATTACH_INT_EN_BIT BIT(6) +#define TYPEC_VBUS_ERROR_INT_EN_BIT BIT(5) +#define TYPEC_UFP_AUDIOADAPT_INT_EN_BIT BIT(4) +#define TYPEC_DEBOUNCE_DONE_INT_EN_BIT BIT(3) +#define TYPEC_CCSTATE_CHANGE_INT_EN_BIT BIT(2) +#define TYPEC_VBUS_DEASSERT_INT_EN_BIT BIT(1) +#define TYPEC_VBUS_ASSERT_INT_EN_BIT BIT(0) + +#define TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG (USBIN_BASE + 0x68) +#define EXIT_SNK_BASED_ON_CC_BIT BIT(7) +#define VCONN_EN_ORIENTATION_BIT BIT(6) +#define TYPEC_VCONN_OVERCURR_INT_EN_BIT BIT(5) +#define VCONN_EN_SRC_BIT BIT(4) +#define VCONN_EN_VALUE_BIT BIT(3) +#define TYPEC_POWER_ROLE_CMD_MASK GENMASK(2, 0) +#define UFP_EN_CMD_BIT BIT(2) +#define DFP_EN_CMD_BIT BIT(1) +#define TYPEC_DISABLE_CMD_BIT BIT(0) + +#define USBIN_SOURCE_CHANGE_INTRPT_ENB_REG (USBIN_BASE + 0x69) +#define SLOW_IRQ_EN_CFG_BIT BIT(5) +#define ENUMERATION_IRQ_EN_CFG_BIT BIT(4) +#define VADP_IRQ_EN_CFG_BIT BIT(3) +#define AUTH_IRQ_EN_CFG_BIT BIT(2) +#define HVDCP_IRQ_EN_CFG_BIT BIT(1) +#define APSD_IRQ_EN_CFG_BIT BIT(0) + +#define USBIN_CURRENT_LIMIT_CFG_REG (USBIN_BASE + 0x70) +#define USBIN_CURRENT_LIMIT_MASK GENMASK(7, 0) + +#define USBIN_AICL_OPTIONS_CFG_REG (USBIN_BASE + 0x80) +#define SUSPEND_ON_COLLAPSE_USBIN_BIT BIT(7) +#define USBIN_AICL_HDC_EN_BIT BIT(6) +#define USBIN_AICL_START_AT_MAX_BIT BIT(5) +#define USBIN_AICL_RERUN_EN_BIT BIT(4) +#define USBIN_AICL_ADC_EN_BIT BIT(3) +#define USBIN_AICL_EN_BIT BIT(2) +#define USBIN_HV_COLLAPSE_RESPONSE_BIT BIT(1) +#define USBIN_LV_COLLAPSE_RESPONSE_BIT BIT(0) + +#define USBIN_5V_AICL_THRESHOLD_CFG_REG (USBIN_BASE + 0x81) +#define USBIN_5V_AICL_THRESHOLD_CFG_MASK GENMASK(2, 0) + +#define USBIN_9V_AICL_THRESHOLD_CFG_REG (USBIN_BASE + 0x82) +#define USBIN_9V_AICL_THRESHOLD_CFG_MASK GENMASK(2, 0) + +#define USBIN_12V_AICL_THRESHOLD_CFG_REG (USBIN_BASE + 0x83) +#define USBIN_12V_AICL_THRESHOLD_CFG_MASK GENMASK(2, 0) + +#define USBIN_CONT_AICL_THRESHOLD_CFG_REG (USBIN_BASE + 0x84) +#define USBIN_CONT_AICL_THRESHOLD_CFG_MASK GENMASK(5, 0) + +/* DCIN Peripheral Registers */ +#define DCIN_INPUT_STATUS_REG (DCIN_BASE + 0x06) +#define DCIN_INPUT_STATUS_7_BIT BIT(7) +#define DCIN_INPUT_STATUS_6_BIT BIT(6) +#define DCIN_12V_BIT BIT(5) +#define DCIN_9V_TO_12V_BIT BIT(4) +#define DCIN_9V_BIT BIT(3) +#define DCIN_5V_TO_12V_BIT BIT(2) +#define DCIN_5V_TO_9V_BIT BIT(1) +#define DCIN_5V_BIT BIT(0) + +#define WIPWR_STATUS_REG (DCIN_BASE + 0x07) +#define WIPWR_STATUS_7_BIT BIT(7) +#define WIPWR_STATUS_6_BIT BIT(6) +#define WIPWR_STATUS_5_BIT BIT(5) +#define DCIN_WIPWR_OV_DG_BIT BIT(4) +#define DIV2_EN_DG_BIT BIT(3) +#define SHUTDOWN_N_LATCH_BIT BIT(2) +#define CHG_OK_PIN_BIT BIT(1) +#define WIPWR_CHARGING_ENABLED_BIT BIT(0) + +#define WIPWR_RANGE_STATUS_REG (DCIN_BASE + 0x08) +#define WIPWR_RANGE_STATUS_MASK GENMASK(4, 0) + +/* DCIN Interrupt Bits */ +#define WIPWR_VOLTAGE_RANGE_RT_STS_BIT BIT(7) +#define DCIN_ICL_CHANGE_RT_STS_BIT BIT(6) +#define DIV2_EN_DG_RT_STS_BIT BIT(5) +#define DCIN_PLUGIN_RT_STS_BIT BIT(4) +#define DCIN_OV_RT_STS_BIT BIT(3) +#define DCIN_UV_RT_STS_BIT BIT(2) +#define DCIN_LT_3P6V_RT_STS_BIT BIT(1) +#define DCIN_COLLAPSE_RT_STS_BIT BIT(0) + +#define DCIN_CMD_IL_REG (DCIN_BASE + 0x40) +#define WIRELESS_CHG_DIS_BIT BIT(3) +#define SHDN_N_CLEAR_CMD_BIT BIT(2) +#define SHDN_N_SET_CMD_BIT BIT(1) +#define DCIN_SUSPEND_BIT BIT(0) + +#define DC_SPARE_REG (DCIN_BASE + 0x58) +#define DC_SPARE_MASK GENMASK(3, 0) + +#define DCIN_ADAPTER_ALLOW_CFG_REG (DCIN_BASE + 0x60) +#define DCIN_ADAPTER_ALLOW_MASK GENMASK(3, 0) + +#define DCIN_LOAD_CFG_REG (DCIN_BASE + 0x65) +#define DCIN_OV_CH_LOAD_OPTION_BIT BIT(7) + +#define DCIN_CURRENT_LIMIT_CFG_REG (DCIN_BASE + 0x70) +#define DCIN_CURRENT_LIMIT_MASK GENMASK(7, 0) + +#define DCIN_AICL_OPTIONS_CFG_REG (DCIN_BASE + 0x80) +#define SUSPEND_ON_COLLAPSE_DCIN_BIT BIT(7) +#define DCIN_AICL_HDC_EN_BIT BIT(6) +#define DCIN_AICL_START_AT_MAX_BIT BIT(5) +#define DCIN_AICL_RERUN_EN_BIT BIT(4) +#define DCIN_AICL_ADC_EN_BIT BIT(3) +#define DCIN_AICL_EN_BIT BIT(2) +#define DCIN_HV_COLLAPSE_RESPONSE_BIT BIT(1) +#define DCIN_LV_COLLAPSE_RESPONSE_BIT BIT(0) + +#define DCIN_AICL_REF_SEL_CFG_REG (DCIN_BASE + 0x81) +#define DCIN_CONT_AICL_THRESHOLD_CFG_MASK GENMASK(5, 0) + +#define DCIN_ICL_START_CFG_REG (DCIN_BASE + 0x82) +#define DCIN_ICL_START_CFG_BIT BIT(0) + +#define DIV2_EN_GF_TIME_CFG_REG (DCIN_BASE + 0x90) +#define DIV2_EN_GF_TIME_CFG_MASK GENMASK(1, 0) + +#define WIPWR_IRQ_TMR_CFG_REG (DCIN_BASE + 0x91) +#define WIPWR_IRQ_TMR_MASK GENMASK(2, 0) + +#define ZIN_ICL_PT_REG (DCIN_BASE + 0x92) +#define ZIN_ICL_PT_MASK GENMASK(7, 0) + +#define ZIN_ICL_LV_REG (DCIN_BASE + 0x93) +#define ZIN_ICL_LV_MASK GENMASK(7, 0) + +#define ZIN_ICL_HV_REG (DCIN_BASE + 0x94) +#define ZIN_ICL_HV_MASK GENMASK(7, 0) + +#define WI_PWR_OPTIONS_REG (DCIN_BASE + 0x95) +#define CHG_OK_BIT BIT(7) +#define WIPWR_UVLO_IRQ_OPT_BIT BIT(6) +#define BUCK_HOLDOFF_ENABLE_BIT BIT(5) +#define CHG_OK_HW_SW_SELECT_BIT BIT(4) +#define WIPWR_RST_ENABLE_BIT BIT(3) +#define DCIN_WIPWR_IRQ_SELECT_BIT BIT(2) +#define AICL_SWITCH_ENABLE_BIT BIT(1) +#define ZIN_ICL_ENABLE_BIT BIT(0) + +#define ZIN_ICL_PT_HV_REG (DCIN_BASE + 0x96) +#define ZIN_ICL_PT_HV_MASK GENMASK(7, 0) + +#define ZIN_ICL_MID_LV_REG (DCIN_BASE + 0x97) +#define ZIN_ICL_MID_LV_MASK GENMASK(7, 0) + +#define ZIN_ICL_MID_HV_REG (DCIN_BASE + 0x98) +#define ZIN_ICL_MID_HV_MASK GENMASK(7, 0) + +enum { + ZIN_ICL_PT_MAX_MV = 8000, + ZIN_ICL_PT_HV_MAX_MV = 9000, + ZIN_ICL_LV_MAX_MV = 5500, + ZIN_ICL_MID_LV_MAX_MV = 6500, + ZIN_ICL_MID_HV_MAX_MV = 8000, + ZIN_ICL_HV_MAX_MV = 11000, +}; + +#define DC_ENG_SSUPPLY_CFG2_REG (DCIN_BASE + 0xC1) +#define ENG_SSUPPLY_IVREF_OTG_SS_MASK GENMASK(2, 0) +#define OTG_SS_SLOW 0x3 + +#define DC_ENG_SSUPPLY_CFG3_REG (DCIN_BASE + 0xC2) +#define ENG_SSUPPLY_HI_CAP_BIT BIT(6) +#define ENG_SSUPPLY_HI_RES_BIT BIT(5) +#define ENG_SSUPPLY_CFG_SKIP_TH_V0P2_BIT BIT(3) +#define ENG_SSUPPLY_CFG_SYSOV_TH_4P8_BIT BIT(2) +#define ENG_SSUPPLY_5V_OV_OPT_BIT BIT(0) + +/* MISC Peripheral Registers */ +#define REVISION1_REG (MISC_BASE + 0x00) +#define DIG_MINOR_MASK GENMASK(7, 0) + +#define REVISION2_REG (MISC_BASE + 0x01) +#define DIG_MAJOR_MASK GENMASK(7, 0) + +#define REVISION3_REG (MISC_BASE + 0x02) +#define ANA_MINOR_MASK GENMASK(7, 0) + +#define REVISION4_REG (MISC_BASE + 0x03) +#define ANA_MAJOR_MASK GENMASK(7, 0) + +#define TEMP_RANGE_STATUS_REG (MISC_BASE + 0x06) +#define TEMP_RANGE_STATUS_7_BIT BIT(7) +#define THERM_REG_ACTIVE_BIT BIT(6) +#define TLIM_BIT BIT(5) +#define TEMP_RANGE_MASK GENMASK(4, 1) +#define ALERT_LEVEL_BIT BIT(4) +#define TEMP_ABOVE_RANGE_BIT BIT(3) +#define TEMP_WITHIN_RANGE_BIT BIT(2) +#define TEMP_BELOW_RANGE_BIT BIT(1) +#define THERMREG_DISABLED_BIT BIT(0) + +#define ICL_STATUS_REG (MISC_BASE + 0x07) +#define INPUT_CURRENT_LIMIT_MASK GENMASK(7, 0) + +#define ADAPTER_5V_ICL_STATUS_REG (MISC_BASE + 0x08) +#define ADAPTER_5V_ICL_MASK GENMASK(7, 0) + +#define ADAPTER_9V_ICL_STATUS_REG (MISC_BASE + 0x09) +#define ADAPTER_9V_ICL_MASK GENMASK(7, 0) + +#define AICL_STATUS_REG (MISC_BASE + 0x0A) +#define AICL_STATUS_7_BIT BIT(7) +#define SOFT_ILIMIT_BIT BIT(6) +#define HIGHEST_DC_BIT BIT(5) +#define USBIN_CH_COLLAPSE_BIT BIT(4) +#define DCIN_CH_COLLAPSE_BIT BIT(3) +#define ICL_IMIN_BIT BIT(2) +#define AICL_FAIL_BIT BIT(1) +#define AICL_DONE_BIT BIT(0) + +#define POWER_PATH_STATUS_REG (MISC_BASE + 0x0B) +#define INPUT_SS_DONE_BIT BIT(7) +#define USBIN_SUSPEND_STS_BIT BIT(6) +#define DCIN_SUSPEND_STS_BIT BIT(5) +#define USE_USBIN_BIT BIT(4) +#define USE_DCIN_BIT BIT(3) +#define POWER_PATH_MASK GENMASK(2, 1) +#define VALID_INPUT_POWER_SOURCE_STS_BIT BIT(0) + +#define WDOG_STATUS_REG (MISC_BASE + 0x0C) +#define WDOG_STATUS_7_BIT BIT(7) +#define WDOG_STATUS_6_BIT BIT(6) +#define WDOG_STATUS_5_BIT BIT(5) +#define WDOG_STATUS_4_BIT BIT(4) +#define WDOG_STATUS_3_BIT BIT(3) +#define WDOG_STATUS_2_BIT BIT(2) +#define WDOG_STATUS_1_BIT BIT(1) +#define BARK_BITE_STATUS_BIT BIT(0) + +#define SYSOK_REASON_STATUS_REG (MISC_BASE + 0x0D) +#define SYSOK_REASON_DCIN_BIT BIT(1) +#define SYSOK_REASON_USBIN_BIT BIT(0) + +/* MISC Interrupt Bits */ +#define SWITCHER_POWER_OK_RT_STS_BIT BIT(7) +#define TEMPERATURE_CHANGE_RT_STS_BIT BIT(6) +#define INPUT_CURRENT_LIMITING_RT_STS_BIT BIT(5) +#define HIGH_DUTY_CYCLE_RT_STS_BIT BIT(4) +#define AICL_DONE_RT_STS_BIT BIT(3) +#define AICL_FAIL_RT_STS_BIT BIT(2) +#define WDOG_BARK_RT_STS_BIT BIT(1) +#define WDOG_SNARL_RT_STS_BIT BIT(0) + +#define WDOG_RST_REG (MISC_BASE + 0x40) +#define WDOG_RST_BIT BIT(0) + +#define AFP_MODE_REG (MISC_BASE + 0x41) +#define AFP_MODE_EN_BIT BIT(0) + +#define GSM_PA_ON_ADJ_EN_REG (MISC_BASE + 0x42) +#define GSM_PA_ON_ADJ_EN_BIT BIT(0) + +#define BARK_BITE_WDOG_PET_REG (MISC_BASE + 0x43) +#define BARK_BITE_WDOG_PET_BIT BIT(0) + +#define PHYON_CMD_REG (MISC_BASE + 0x44) +#define PHYON_CMD_BIT BIT(0) + +#define SHDN_CMD_REG (MISC_BASE + 0x45) +#define SHDN_CMD_BIT BIT(0) + +#define FINISH_COPY_COMMAND_REG (MISC_BASE + 0x4F) +#define START_COPY_BIT BIT(0) + +#define WD_CFG_REG (MISC_BASE + 0x51) +#define WATCHDOG_TRIGGER_AFP_EN_BIT BIT(7) +#define BARK_WDOG_INT_EN_BIT BIT(6) +#define BITE_WDOG_INT_EN_BIT BIT(5) +#define SFT_AFTER_WDOG_IRQ_MASK GENMASK(4, 3) +#define WDOG_IRQ_SFT_BIT BIT(2) +#define WDOG_TIMER_EN_ON_PLUGIN_BIT BIT(1) +#define WDOG_TIMER_EN_BIT BIT(0) + +#define MISC_CFG_REG (MISC_BASE + 0x52) +#define GSM_PA_ON_ADJ_SEL_BIT BIT(0) +#define STAT_PARALLEL_1400MA_EN_CFG_BIT BIT(3) +#define TCC_DEBOUNCE_20MS_BIT BIT(5) + +#define SNARL_BARK_BITE_WD_CFG_REG (MISC_BASE + 0x53) +#define BITE_WDOG_DISABLE_CHARGING_CFG_BIT BIT(7) +#define SNARL_WDOG_TIMEOUT_MASK GENMASK(6, 4) +#define BARK_WDOG_TIMEOUT_MASK GENMASK(3, 2) +#define BITE_WDOG_TIMEOUT_MASK GENMASK(1, 0) + +#define PHYON_CFG_REG (MISC_BASE + 0x54) +#define USBPHYON_PUSHPULL_CFG_BIT BIT(1) +#define PHYON_SW_SEL_BIT BIT(0) + +#define CHGR_TRIM_OPTIONS_7_0_REG (MISC_BASE + 0x55) +#define TLIM_DIS_TBIT_BIT BIT(0) + +#define CH_OV_OPTION_CFG_REG (MISC_BASE + 0x56) +#define OV_OPTION_TBIT_BIT BIT(0) + +#define AICL_CFG_REG (MISC_BASE + 0x60) +#define TREG_ALLOW_DECREASE_BIT BIT(1) +#define AICL_HIGH_DC_INC_BIT BIT(0) + +#define AICL_RERUN_TIME_CFG_REG (MISC_BASE + 0x61) +#define AICL_RERUN_TIME_MASK GENMASK(1, 0) + +#define AICL_RERUN_TEMP_TIME_CFG_REG (MISC_BASE + 0x62) +#define AICL_RERUN_TEMP_TIME_MASK GENMASK(1, 0) + +#define THERMREG_SRC_CFG_REG (MISC_BASE + 0x70) +#define SKIN_ADC_CFG_BIT BIT(3) +#define THERMREG_SKIN_ADC_SRC_EN_BIT BIT(2) +#define THERMREG_DIE_ADC_SRC_EN_BIT BIT(1) +#define THERMREG_DIE_CMP_SRC_EN_BIT BIT(0) + +#define TREG_DIE_CMP_INC_CYCLE_TIME_CFG_REG (MISC_BASE + 0x71) +#define TREG_DIE_CMP_INC_CYCLE_TIME_MASK GENMASK(1, 0) + +#define TREG_DIE_CMP_DEC_CYCLE_TIME_CFG_REG (MISC_BASE + 0x72) +#define TREG_DIE_CMP_DEC_CYCLE_TIME_MASK GENMASK(1, 0) + +#define TREG_DIE_ADC_INC_CYCLE_TIME_CFG_REG (MISC_BASE + 0x73) +#define TREG_DIE_ADC_INC_CYCLE_TIME_MASK GENMASK(1, 0) + +#define TREG_DIE_ADC_DEC_CYCLE_TIME_CFG_REG (MISC_BASE + 0x74) +#define TREG_DIE_ADC_DEC_CYCLE_TIME_MASK GENMASK(1, 0) + +#define TREG_SKIN_ADC_INC_CYCLE_TIME_CFG_REG (MISC_BASE + 0x75) +#define TREG_SKIN_ADC_INC_CYCLE_TIME_MASK GENMASK(1, 0) + +#define TREG_SKIN_ADC_DEC_CYCLE_TIME_CFG_REG (MISC_BASE + 0x76) +#define TREG_SKIN_ADC_DEC_CYCLE_TIME_MASK GENMASK(1, 0) + +#define BUCK_OPTIONS_CFG_REG (MISC_BASE + 0x80) +#define CHG_EN_PIN_SUSPEND_CFG_BIT BIT(6) +#define HICCUP_OPTIONS_MASK GENMASK(5, 4) +#define INPUT_CURRENT_LIMIT_SOFTSTART_EN_BIT BIT(3) +#define HV_HIGH_DUTY_CYCLE_PROTECT_EN_BIT BIT(2) +#define BUCK_OC_PROTECT_EN_BIT BIT(1) +#define INPUT_MISS_POLL_EN_BIT BIT(0) + +#define ICL_SOFTSTART_RATE_CFG_REG (MISC_BASE + 0x81) +#define ICL_SOFTSTART_RATE_MASK GENMASK(1, 0) + +#define ICL_SOFTSTOP_RATE_CFG_REG (MISC_BASE + 0x82) +#define ICL_SOFTSTOP_RATE_MASK GENMASK(1, 0) + +#define VSYS_MIN_SEL_CFG_REG (MISC_BASE + 0x83) +#define VSYS_MIN_SEL_MASK GENMASK(1, 0) + +#define TRACKING_VOLTAGE_SEL_CFG_REG (MISC_BASE + 0x84) +#define TRACKING_VOLTAGE_SEL_BIT BIT(0) + +#define STAT_CFG_REG (MISC_BASE + 0x90) +#define STAT_SW_OVERRIDE_VALUE_BIT BIT(7) +#define STAT_SW_OVERRIDE_CFG_BIT BIT(6) +#define STAT_PARALLEL_OFF_DG_CFG_MASK GENMASK(5, 4) +#define STAT_POLARITY_CFG_BIT BIT(3) +#define STAT_PARALLEL_CFG_BIT BIT(2) +#define STAT_FUNCTION_CFG_BIT BIT(1) +#define STAT_IRQ_PULSING_EN_BIT BIT(0) + +#define LBC_EN_CFG_REG (MISC_BASE + 0x91) +#define LBC_DURING_CHARGING_CFG_BIT BIT(1) +#define LBC_EN_BIT BIT(0) + +#define LBC_PERIOD_CFG_REG (MISC_BASE + 0x92) +#define LBC_PERIOD_MASK GENMASK(2, 0) + +#define LBC_DUTY_CYCLE_CFG_REG (MISC_BASE + 0x93) +#define LBC_DUTY_CYCLE_MASK GENMASK(2, 0) + +#define SYSOK_CFG_REG (MISC_BASE + 0x94) +#define SYSOK_PUSHPULL_CFG_BIT BIT(5) +#define SYSOK_B_OR_C_SEL_BIT BIT(4) +#define SYSOK_POL_BIT BIT(3) +#define SYSOK_OPTIONS_MASK GENMASK(2, 0) + +#define CFG_BUCKBOOST_FREQ_SELECT_BUCK_REG (MISC_BASE + 0xA0) +#define CFG_BUCKBOOST_FREQ_SELECT_BOOST_REG (MISC_BASE + 0xA1) + +#define TM_IO_DTEST4_SEL (MISC_BASE + 0xE9) + +#define ENG_SDCDC_CFG7_REG (MISC_BASE + 0xC6) +#define ENG_SDCDC_BST_SET_POINT_MASK GENMASK(7, 6) + +/* CHGR FREQ Peripheral registers */ +#define FREQ_CLK_DIV_REG (CHGR_FREQ_BASE + 0x50) + +#endif /* __SMB2_CHARGER_REG_H */ diff --git a/drivers/power/supply/qcom/smb1351-charger.c b/drivers/power/supply/qcom/smb1351-charger.c new file mode 100644 index 000000000000..ab6120ab10f6 --- /dev/null +++ b/drivers/power/supply/qcom/smb1351-charger.c @@ -0,0 +1,3124 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2016-2020, The Linux Foundation. All rights reserved. + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Mask/Bit helpers */ +#define _SMB1351_MASK(BITS, POS) \ + ((unsigned char)(((1 << (BITS)) - 1) << (POS))) +#define SMB1351_MASK(LEFT_BIT_POS, RIGHT_BIT_POS) \ + _SMB1351_MASK((LEFT_BIT_POS) - (RIGHT_BIT_POS) + 1, \ + (RIGHT_BIT_POS)) + +/* Configuration registers */ +#define CHG_CURRENT_CTRL_REG 0x0 +#define FAST_CHG_CURRENT_MASK SMB1351_MASK(7, 4) +#define AC_INPUT_CURRENT_LIMIT_MASK SMB1351_MASK(3, 0) + +#define CHG_OTH_CURRENT_CTRL_REG 0x1 +#define PRECHG_CURRENT_MASK SMB1351_MASK(7, 5) +#define ITERM_MASK SMB1351_MASK(4, 2) +#define USB_2_3_MODE_SEL_BIT BIT(1) +#define USB_2_3_MODE_SEL_BY_I2C 0 +#define USB_2_3_MODE_SEL_BY_PIN 0x2 +#define USB_5_1_CMD_POLARITY_BIT BIT(0) +#define USB_CMD_POLARITY_500_1_100_0 0 +#define USB_CMD_POLARITY_500_0_100_1 0x1 + +#define VARIOUS_FUNC_REG 0x2 +#define SUSPEND_MODE_CTRL_BIT BIT(7) +#define SUSPEND_MODE_CTRL_BY_PIN 0 +#define SUSPEND_MODE_CTRL_BY_I2C 0x80 +#define BATT_TO_SYS_POWER_CTRL_BIT BIT(6) +#define MAX_SYS_VOLTAGE BIT(5) +#define AICL_EN_BIT BIT(4) +#define AICL_DET_TH_BIT BIT(3) +#define APSD_EN_BIT BIT(2) +#define BATT_OV_BIT BIT(1) +#define VCHG_FUNC_BIT BIT(0) + +#define VFLOAT_REG 0x3 +#define PRECHG_TO_FAST_VOLTAGE_CFG_MASK SMB1351_MASK(7, 6) +#define VFLOAT_MASK SMB1351_MASK(5, 0) + +#define CHG_CTRL_REG 0x4 +#define AUTO_RECHG_BIT BIT(7) +#define AUTO_RECHG_ENABLE 0 +#define AUTO_RECHG_DISABLE 0x80 +#define ITERM_EN_BIT BIT(6) +#define ITERM_ENABLE 0 +#define ITERM_DISABLE 0x40 +#define MAPPED_AC_INPUT_CURRENT_LIMIT_MASK SMB1351_MASK(5, 4) +#define AUTO_RECHG_TH_BIT BIT(3) +#define AUTO_RECHG_TH_50MV 0 +#define AUTO_RECHG_TH_100MV 0x8 +#define AFCV_MASK SMB1351_MASK(2, 0) + +#define CHG_STAT_TIMERS_CTRL_REG 0x5 +#define STAT_OUTPUT_POLARITY_BIT BIT(7) +#define STAT_OUTPUT_MODE_BIT BIT(6) +#define STAT_OUTPUT_CTRL_BIT BIT(5) +#define OTH_CHG_IL_BIT BIT(4) +#define COMPLETE_CHG_TIMEOUT_MASK SMB1351_MASK(3, 2) +#define PRECHG_TIMEOUT_MASK SMB1351_MASK(1, 0) + +#define CHG_PIN_EN_CTRL_REG 0x6 +#define LED_BLINK_FUNC_BIT BIT(7) +#define EN_PIN_CTRL_MASK SMB1351_MASK(6, 5) +#define EN_BY_I2C_0_DISABLE 0 +#define EN_BY_I2C_0_ENABLE 0x20 +#define EN_BY_PIN_HIGH_ENABLE 0x40 +#define EN_BY_PIN_LOW_ENABLE 0x60 +#define USBCS_CTRL_BIT BIT(4) +#define USBCS_CTRL_BY_I2C 0 +#define USBCS_CTRL_BY_PIN 0x10 +#define USBCS_INPUT_STATE_BIT BIT(3) +#define CHG_ERR_BIT BIT(2) +#define APSD_DONE_BIT BIT(1) +#define USB_FAIL_BIT BIT(0) + +#define THERM_A_CTRL_REG 0x7 +#define MIN_SYS_VOLTAGE_MASK SMB1351_MASK(7, 6) +#define LOAD_BATT_10MA_FVC_BIT BIT(5) +#define THERM_MONITOR_BIT BIT(4) +#define THERM_MONITOR_EN 0 +#define SOFT_COLD_TEMP_LIMIT_MASK SMB1351_MASK(3, 2) +#define SOFT_HOT_TEMP_LIMIT_MASK SMB1351_MASK(1, 0) + +#define WDOG_SAFETY_TIMER_CTRL_REG 0x8 +#define AICL_FAIL_OPTION_BIT BIT(7) +#define AICL_FAIL_TO_SUSPEND 0 +#define AICL_FAIL_TO_150_MA 0x80 +#define WDOG_TIMEOUT_MASK SMB1351_MASK(6, 5) +#define WDOG_IRQ_SAFETY_TIMER_MASK SMB1351_MASK(4, 3) +#define WDOG_IRQ_SAFETY_TIMER_EN_BIT BIT(2) +#define WDOG_OPTION_BIT BIT(1) +#define WDOG_TIMER_EN_BIT BIT(0) + +#define OTG_USBIN_AICL_CTRL_REG 0x9 +#define OTG_ID_PIN_CTRL_MASK SMB1351_MASK(7, 6) +#define OTG_PIN_POLARITY_BIT BIT(5) +#define DCIN_IC_GLITCH_FILTER_HV_ADAPTER_MASK SMB1351_MASK(4, 3) +#define DCIN_IC_GLITCH_FILTER_LV_ADAPTER_BIT BIT(2) +#define USBIN_AICL_CFG1_BIT BIT(1) +#define USBIN_AICL_CFG0_BIT BIT(0) + +#define OTG_TLIM_CTRL_REG 0xA +#define SWITCH_FREQ_MASK SMB1351_MASK(7, 6) +#define THERM_LOOP_TEMP_SEL_MASK SMB1351_MASK(5, 4) +#define OTG_OC_LIMIT_MASK SMB1351_MASK(3, 2) +#define OTG_BATT_UVLO_TH_MASK SMB1351_MASK(1, 0) + +#define HARD_SOFT_LIMIT_CELL_TEMP_REG 0xB +#define HARD_LIMIT_COLD_TEMP_ALARM_TRIP_MASK SMB1351_MASK(7, 6) +#define HARD_LIMIT_HOT_TEMP_ALARM_TRIP_MASK SMB1351_MASK(5, 4) +#define SOFT_LIMIT_COLD_TEMP_ALARM_TRIP_MASK SMB1351_MASK(3, 2) +#define SOFT_LIMIT_HOT_TEMP_ALARM_TRIP_MASK SMB1351_MASK(1, 0) + +#define FAULT_INT_REG 0xC +#define HOT_COLD_HARD_LIMIT_BIT BIT(7) +#define HOT_COLD_SOFT_LIMIT_BIT BIT(6) +#define BATT_UVLO_IN_OTG_BIT BIT(5) +#define OTG_OC_BIT BIT(4) +#define INPUT_OVLO_BIT BIT(3) +#define INPUT_UVLO_BIT BIT(2) +#define AICL_DONE_FAIL_BIT BIT(1) +#define INTERNAL_OVER_TEMP_BIT BIT(0) + +#define STATUS_INT_REG 0xD +#define CHG_OR_PRECHG_TIMEOUT_BIT BIT(7) +#define RID_CHANGE_BIT BIT(6) +#define BATT_OVP_BIT BIT(5) +#define FAST_TERM_TAPER_RECHG_INHIBIT_BIT BIT(4) +#define WDOG_TIMER_BIT BIT(3) +#define POK_BIT BIT(2) +#define BATT_MISSING_BIT BIT(1) +#define BATT_LOW_BIT BIT(0) + +#define VARIOUS_FUNC_2_REG 0xE +#define CHG_HOLD_OFF_TIMER_AFTER_PLUGIN_BIT BIT(7) +#define CHG_INHIBIT_BIT BIT(6) +#define FAST_CHG_CC_IN_BATT_SOFT_LIMIT_MODE_BIT BIT(5) +#define FVCL_IN_BATT_SOFT_LIMIT_MODE_MASK SMB1351_MASK(4, 3) +#define HARD_TEMP_LIMIT_BEHAVIOR_BIT BIT(2) +#define PRECHG_TO_FASTCHG_BIT BIT(1) +#define STAT_PIN_CONFIG_BIT BIT(0) + +#define FLEXCHARGER_REG 0x10 +#define AFVC_IRQ_BIT BIT(7) +#define CHG_CONFIG_MASK SMB1351_MASK(6, 4) +#define LOW_BATT_VOLTAGE_DET_TH_MASK SMB1351_MASK(3, 0) + +#define VARIOUS_FUNC_3_REG 0x11 +#define SAFETY_TIMER_EN_MASK SMB1351_MASK(7, 6) +#define BLOCK_SUSPEND_DURING_VBATT_LOW_BIT BIT(5) +#define TIMEOUT_SEL_FOR_APSD_BIT BIT(4) +#define SDP_SUSPEND_BIT BIT(3) +#define QC_2P1_AUTO_INCREMENT_MODE_BIT BIT(2) +#define QC_2P1_AUTH_ALGO_BIT BIT(1) +#define DCD_EN_BIT BIT(0) + +#define HVDCP_BATT_MISSING_CTRL_REG 0x12 +#define HVDCP_ADAPTER_SEL_MASK SMB1351_MASK(7, 6) +#define HVDCP_EN_BIT BIT(5) +#define HVDCP_AUTO_INCREMENT_LIMIT_BIT BIT(4) +#define BATT_MISSING_ON_INPUT_PLUGIN_BIT BIT(3) +#define BATT_MISSING_2P6S_POLLER_BIT BIT(2) +#define BATT_MISSING_ALGO_BIT BIT(1) +#define BATT_MISSING_THERM_PIN_SOURCE_BIT BIT(0) + +#define PON_OPTIONS_REG 0x13 +#define SYSOK_INOK_POLARITY_BIT BIT(7) +#define SYSOK_OPTIONS_MASK SMB1351_MASK(6, 4) +#define INPUT_MISSING_POLLER_CONFIG_BIT BIT(3) +#define VBATT_LOW_DISABLED_OR_RESET_STATE_BIT BIT(2) +#define QC_2P1_AUTH_ALGO_IRQ_EN_BIT BIT(0) + +#define OTG_MODE_POWER_OPTIONS_REG 0x14 +#define ADAPTER_CONFIG_MASK SMB1351_MASK(7, 6) +#define MAP_HVDCP_BIT BIT(5) +#define SDP_LOW_BATT_FORCE_USB5_OVER_USB1_BIT BIT(4) +#define OTG_HICCUP_MODE_BIT BIT(2) +#define INPUT_CURRENT_LIMIT_MASK SMB1351_MASK(1, 0) + +#define CHARGER_I2C_CTRL_REG 0x15 +#define FULLON_MODE_EN_BIT BIT(7) +#define I2C_HS_MODE_EN_BIT BIT(6) +#define SYSON_LDO_OUTPUT_SEL_BIT BIT(5) +#define VBATT_TRACKING_VOLTAGE_DIFF_BIT BIT(4) +#define DISABLE_AFVC_WHEN_ENTER_TAPER_BIT BIT(3) +#define VCHG_IINV_BIT BIT(2) +#define AFVC_OVERRIDE_BIT BIT(1) +#define SYSOK_PIN_CONFIG_BIT BIT(0) + +#define VERSION_REG 0x2E +#define VERSION_MASK BIT(1) + +/* Command registers */ +#define CMD_I2C_REG 0x30 +#define CMD_RELOAD_BIT BIT(7) +#define CMD_BQ_CFG_ACCESS_BIT BIT(6) + +#define CMD_INPUT_LIMIT_REG 0x31 +#define CMD_OVERRIDE_BIT BIT(7) +#define CMD_SUSPEND_MODE_BIT BIT(6) +#define CMD_INPUT_CURRENT_MODE_BIT BIT(3) +#define CMD_INPUT_CURRENT_MODE_APSD 0 +#define CMD_INPUT_CURRENT_MODE_CMD 0x08 +#define CMD_USB_2_3_SEL_BIT BIT(2) +#define CMD_USB_2_MODE 0 +#define CMD_USB_3_MODE 0x4 +#define CMD_USB_1_5_AC_CTRL_MASK SMB1351_MASK(1, 0) +#define CMD_USB_100_MODE 0 +#define CMD_USB_500_MODE 0x2 +#define CMD_USB_AC_MODE 0x1 + +#define CMD_CHG_REG 0x32 +#define CMD_DISABLE_THERM_MONITOR_BIT BIT(4) +#define CMD_TURN_OFF_STAT_PIN_BIT BIT(3) +#define CMD_PRE_TO_FAST_EN_BIT BIT(2) +#define CMD_CHG_EN_BIT BIT(1) +#define CMD_CHG_DISABLE 0 +#define CMD_CHG_ENABLE 0x2 +#define CMD_OTG_EN_BIT BIT(0) + +#define CMD_DEAD_BATT_REG 0x33 +#define CMD_STOP_DEAD_BATT_TIMER_MASK SMB1351_MASK(7, 0) + +#define CMD_HVDCP_REG 0x34 +#define CMD_APSD_RE_RUN_BIT BIT(7) +#define CMD_FORCE_HVDCP_2P0_BIT BIT(5) +#define CMD_HVDCP_MODE_MASK SMB1351_MASK(5, 0) + +/* Status registers */ +#define STATUS_0_REG 0x36 +#define STATUS_AICL_BIT BIT(7) +#define STATUS_INPUT_CURRENT_LIMIT_MASK SMB1351_MASK(6, 5) +#define STATUS_DCIN_INPUT_CURRENT_LIMIT_MASK SMB1351_MASK(4, 0) + +#define STATUS_1_REG 0x37 +#define STATUS_INPUT_RANGE_MASK SMB1351_MASK(7, 4) +#define STATUS_INPUT_USB_BIT BIT(0) + +#define STATUS_2_REG 0x38 +#define STATUS_FAST_CHG_BIT BIT(7) +#define STATUS_HARD_LIMIT_BIT BIT(6) +#define STATUS_FLOAT_VOLTAGE_MASK SMB1351_MASK(5, 0) + +#define STATUS_3_REG 0x39 +#define STATUS_CHG_BIT BIT(7) +#define STATUS_PRECHG_CURRENT_MASK SMB1351_MASK(6, 4) +#define STATUS_FAST_CHG_CURRENT_MASK SMB1351_MASK(3, 0) + +#define STATUS_4_REG 0x3A +#define STATUS_OTG_BIT BIT(7) +#define STATUS_AFVC_BIT BIT(6) +#define STATUS_DONE_BIT BIT(5) +#define STATUS_BATT_LESS_THAN_2V_BIT BIT(4) +#define STATUS_HOLD_OFF_BIT BIT(3) +#define STATUS_CHG_MASK SMB1351_MASK(2, 1) +#define STATUS_NO_CHARGING 0 +#define STATUS_FAST_CHARGING 0x4 +#define STATUS_PRE_CHARGING 0x2 +#define STATUS_TAPER_CHARGING 0x6 +#define STATUS_CHG_EN_STATUS_BIT BIT(0) + +#define STATUS_5_REG 0x3B +#define STATUS_SOURCE_DETECTED_MASK SMB1351_MASK(7, 0) +#define STATUS_PORT_CDP 0x80 +#define STATUS_PORT_DCP 0x40 +#define STATUS_PORT_OTHER 0x20 +#define STATUS_PORT_SDP 0x10 +#define STATUS_PORT_ACA_A 0x8 +#define STATUS_PORT_ACA_B 0x4 +#define STATUS_PORT_ACA_C 0x2 +#define STATUS_PORT_ACA_DOCK 0x1 + +#define STATUS_6_REG 0x3C +#define STATUS_DCD_TIMEOUT_BIT BIT(7) +#define STATUS_DCD_GOOD_DG_BIT BIT(6) +#define STATUS_OCD_GOOD_DG_BIT BIT(5) +#define STATUS_RID_ABD_DG_BIT BIT(4) +#define STATUS_RID_FLOAT_STATE_MACHINE_BIT BIT(3) +#define STATUS_RID_A_STATE_MACHINE_BIT BIT(2) +#define STATUS_RID_B_STATE_MACHINE_BIT BIT(1) +#define STATUS_RID_C_STATE_MACHINE_BIT BIT(0) + +#define STATUS_7_REG 0x3D +#define STATUS_HVDCP_MASK SMB1351_MASK(7, 0) + +#define STATUS_8_REG 0x3E +#define STATUS_USNIN_HV_INPUT_SEL_BIT BIT(5) +#define STATUS_USBIN_LV_UNDER_INPUT_SEL_BIT BIT(4) +#define STATUS_USBIN_LV_INPUT_SEL_BIT BIT(3) + +/* Revision register */ +#define CHG_REVISION_REG 0x3F +#define GUI_REVISION_MASK SMB1351_MASK(7, 4) +#define DEVICE_REVISION_MASK SMB1351_MASK(3, 0) + +/* IRQ status registers */ +#define IRQ_A_REG 0x40 +#define IRQ_HOT_HARD_BIT BIT(6) +#define IRQ_COLD_HARD_BIT BIT(4) +#define IRQ_HOT_SOFT_BIT BIT(2) +#define IRQ_COLD_SOFT_BIT BIT(0) + +#define IRQ_B_REG 0x41 +#define IRQ_BATT_TERMINAL_REMOVED_BIT BIT(6) +#define IRQ_BATT_MISSING_BIT BIT(4) +#define IRQ_LOW_BATT_VOLTAGE_BIT BIT(2) +#define IRQ_INTERNAL_TEMP_LIMIT_BIT BIT(0) + +#define IRQ_C_REG 0x42 +#define IRQ_PRE_TO_FAST_VOLTAGE_BIT BIT(6) +#define IRQ_RECHG_BIT BIT(4) +#define IRQ_TAPER_BIT BIT(2) +#define IRQ_TERM_BIT BIT(0) + +#define IRQ_D_REG 0x43 +#define IRQ_BATT_OV_BIT BIT(6) +#define IRQ_CHG_ERROR_BIT BIT(4) +#define IRQ_CHG_TIMEOUT_BIT BIT(2) +#define IRQ_PRECHG_TIMEOUT_BIT BIT(0) + +#define IRQ_E_REG 0x44 +#define IRQ_USBIN_OV_BIT BIT(6) +#define IRQ_USBIN_UV_BIT BIT(4) +#define IRQ_AFVC_BIT BIT(2) +#define IRQ_POWER_OK_BIT BIT(0) + +#define IRQ_F_REG 0x45 +#define IRQ_OTG_OVER_CURRENT_BIT BIT(6) +#define IRQ_OTG_FAIL_BIT BIT(4) +#define IRQ_RID_BIT BIT(2) +#define IRQ_OTG_OC_RETRY_BIT BIT(0) + +#define IRQ_G_REG 0x46 +#define IRQ_SOURCE_DET_BIT BIT(6) +#define IRQ_AICL_DONE_BIT BIT(4) +#define IRQ_AICL_FAIL_BIT BIT(2) +#define IRQ_CHG_INHIBIT_BIT BIT(0) + +#define IRQ_H_REG 0x47 +#define IRQ_IC_LIMIT_STATUS_BIT BIT(5) +#define IRQ_HVDCP_2P1_STATUS_BIT BIT(4) +#define IRQ_HVDCP_AUTH_DONE_BIT BIT(2) +#define IRQ_WDOG_TIMEOUT_BIT BIT(0) + +/* constants */ +#define USB2_MIN_CURRENT_MA 100 +#define USB2_MAX_CURRENT_MA 500 +#define USB3_MIN_CURRENT_MA 150 +#define USB3_MAX_CURRENT_MA 900 +#define DCP_MAX_CURRENT_MA 1500 +#define SMB1351_IRQ_REG_COUNT 8 +#define SMB1351_CHG_PRE_MIN_MA 100 +#define SMB1351_CHG_FAST_MIN_MA 1000 +#define SMB1351_CHG_FAST_MAX_MA 4500 +#define SMB1351_CHG_PRE_SHIFT 5 +#define SMB1351_CHG_FAST_SHIFT 4 +#define DEFAULT_BATT_CAPACITY 50 +#define DEFAULT_BATT_TEMP 250 +#define SUSPEND_CURRENT_MA 2 + +#define CHG_ITERM_200MA 0x0 +#define CHG_ITERM_300MA 0x04 +#define CHG_ITERM_400MA 0x08 +#define CHG_ITERM_500MA 0x0C +#define CHG_ITERM_600MA 0x10 +#define CHG_ITERM_700MA 0x14 + +#define OTG_ID_PIN_CTRL_SHIFT 6 + +enum otg_control { + RID_DISABLED_OTG_I2C = 0, + RID_DISABLED_OTG_PIN, + RID_ENABLED_OTG_I2C, + RID_ENABLED_OTG_AUTO, +}; + +enum reason { + USER = BIT(0), + THERMAL = BIT(1), + CURRENT = BIT(2), + SOC = BIT(3), +}; + +static char *pm_batt_supplied_to[] = { + "bms", +}; + +struct smb1351_regulator { + struct regulator_desc rdesc; + struct regulator_dev *rdev; +}; + +enum chip_version { + SMB_UNKNOWN = 0, + SMB1350, + SMB1351, + SMB_MAX_TYPE, +}; + +static const char *smb1351_version_str[SMB_MAX_TYPE] = { + [SMB_UNKNOWN] = "Unknown", + [SMB1350] = "SMB1350", + [SMB1351] = "SMB1351", +}; + +static const unsigned int smb1351_extcon_cable[] = { + EXTCON_USB, + EXTCON_USB_HOST, + EXTCON_NONE, +}; + +struct smb1351_charger { + struct i2c_client *client; + struct device *dev; + + bool recharge_disabled; + int recharge_mv; + bool iterm_disabled; + int iterm_ma; + int vfloat_mv; + int chg_present; + int fake_battery_soc; + bool chg_autonomous_mode; + bool disable_apsd; + bool battery_missing; + const char *bms_psy_name; + bool resume_completed; + bool irq_waiting; + struct delayed_work chg_remove_work; + struct delayed_work hvdcp_det_work; + + /* status tracking */ + bool batt_full; + bool batt_hot; + bool batt_cold; + bool batt_warm; + bool batt_cool; + + int battchg_disabled_status; + int usb_suspended_status; + int target_fastchg_current_max_ma; + int fastchg_current_max_ma; + int workaround_flags; + + int parallel_pin_polarity_setting; + int parallel_mode; + int pl_batfet_mode; + bool parallel_charger; + bool parallel_charger_suspended; + bool bms_controlled_charging; + bool apsd_rerun; + bool usbin_ov; + bool chg_remove_work_scheduled; + bool force_hvdcp_2p0; + enum chip_version version; + + /* psy */ + struct power_supply_desc usb_psy_d; + struct power_supply *usb_psy; + int usb_psy_ma; + struct power_supply *bms_psy; + struct power_supply_desc batt_psy_d; + struct power_supply *batt_psy; + struct power_supply *parallel_psy; + struct power_supply_desc parallel_psy_d; + + struct smb1351_regulator otg_vreg; + struct mutex irq_complete; + + struct dentry *debug_root; + u32 peek_poke_address; + + /* pinctrl parameters */ + const char *pinctrl_state_name; + struct pinctrl *smb_pinctrl; + + /* standalone */ + bool charger_present; + struct extcon_dev *extcon; + struct regulator *dpdm_reg; + enum power_supply_type charger_type; + bool otg_enable; +}; + +struct smb_irq_info { + const char *name; + int (*smb_irq)(struct smb1351_charger *chip, u8 rt_stat); + int high; + int low; +}; + +struct irq_handler_info { + u8 stat_reg; + u8 val; + u8 prev_val; + struct smb_irq_info irq_info[4]; +}; + +/* USB input charge current */ +static int usb_chg_current[] = { + 500, 685, 1000, 1100, 1200, 1300, 1500, 1600, + 1700, 1800, 2000, 2200, 2500, 3000, +}; + +static int fast_chg_current[] = { + 1000, 1200, 1400, 1600, 1800, 2000, 2200, + 2400, 2600, 2800, 3000, 3400, 3600, 3800, + 4000, 4640, +}; + +static int pre_chg_current[] = { + 200, 300, 400, 500, 600, 700, +}; + +static int smb1351_read_reg(struct smb1351_charger *chip, int reg, u8 *val) +{ + s32 ret; + + pm_stay_awake(chip->dev); + ret = i2c_smbus_read_byte_data(chip->client, reg); + if (ret < 0) { + pr_err("i2c read fail: can't read from %02x: %d\n", reg, ret); + pm_relax(chip->dev); + return ret; + } + + *val = ret; + + pm_relax(chip->dev); + pr_debug("Reading 0x%02x=0x%02x\n", reg, *val); + return 0; +} + +static int smb1351_write_reg(struct smb1351_charger *chip, int reg, u8 val) +{ + s32 ret; + + pm_stay_awake(chip->dev); + ret = i2c_smbus_write_byte_data(chip->client, reg, val); + if (ret < 0) { + pr_err("i2c write fail: can't write %02x to %02x: %d\n", + val, reg, ret); + pm_relax(chip->dev); + return ret; + } + pm_relax(chip->dev); + pr_debug("Writing 0x%02x=0x%02x\n", reg, val); + return 0; +} + +static int smb1351_masked_write(struct smb1351_charger *chip, int reg, + u8 mask, u8 val) +{ + s32 rc; + u8 temp; + + rc = smb1351_read_reg(chip, reg, &temp); + if (rc) { + pr_err("read failed: reg=%03X, rc=%d\n", reg, rc); + return rc; + } + temp &= ~mask; + temp |= val & mask; + rc = smb1351_write_reg(chip, reg, temp); + if (rc) { + pr_err("write failed: reg=%03X, rc=%d\n", reg, rc); + return rc; + } + return 0; +} + +static int smb1351_enable_volatile_writes(struct smb1351_charger *chip) +{ + int rc; + + rc = smb1351_masked_write(chip, CMD_I2C_REG, CMD_BQ_CFG_ACCESS_BIT, + CMD_BQ_CFG_ACCESS_BIT); + if (rc) + pr_err("Couldn't write CMD_BQ_CFG_ACCESS_BIT rc=%d\n", rc); + + return rc; +} + +static int smb1351_get_closest_usb_setpoint(int val) +{ + int i; + + for (i = ARRAY_SIZE(usb_chg_current) - 1; i >= 0; i--) { + if (usb_chg_current[i] <= val) + break; + } + if (i < 0) + i = 0; + + if (i >= ARRAY_SIZE(usb_chg_current) - 1) + return ARRAY_SIZE(usb_chg_current) - 1; + + /* check what is closer, i or i + 1 */ + if (abs(usb_chg_current[i] - val) < abs(usb_chg_current[i + 1] - val)) + return i; + else + return i + 1; +} + +static int smb1351_request_dpdm(struct smb1351_charger *chip, bool enable) +{ + int rc = 0; + + /* fetch the DPDM regulator */ + if (!chip->dpdm_reg && of_get_property(chip->dev->of_node, + "dpdm-supply", NULL)) { + chip->dpdm_reg = devm_regulator_get(chip->dev, "dpdm"); + if (IS_ERR(chip->dpdm_reg)) { + rc = PTR_ERR(chip->dpdm_reg); + pr_err("Couldn't get dpdm regulator rc=%d\n", + rc); + chip->dpdm_reg = NULL; + return rc; + } + } + + if (enable) { + if (chip->dpdm_reg && !regulator_is_enabled(chip->dpdm_reg)) { + pr_err("enabling DPDM regulator\n"); + rc = regulator_enable(chip->dpdm_reg); + if (rc < 0) + pr_err("Couldn't enable dpdm regulator rc=%d\n", + rc); + } + } else { + if (chip->dpdm_reg && regulator_is_enabled(chip->dpdm_reg)) { + pr_err("disabling DPDM regulator\n"); + rc = regulator_disable(chip->dpdm_reg); + if (rc < 0) + pr_err("Couldn't disable dpdm regulator rc=%d\n", + rc); + } + } + + return rc; +} + +static int smb1351_usb_suspend(struct smb1351_charger *chip, int reason, + bool suspend) +{ + int rc = 0; + int suspended; + + suspended = chip->usb_suspended_status; + + pr_debug("reason = %d requested_suspend = %d suspended_status = %d\n", + reason, suspend, suspended); + + if (!suspend) + suspended &= ~reason; + else + suspended |= reason; + + pr_debug("new suspended_status = %d\n", suspended); + + rc = smb1351_masked_write(chip, CMD_INPUT_LIMIT_REG, + CMD_SUSPEND_MODE_BIT, + suspended ? CMD_SUSPEND_MODE_BIT : 0); + if (rc) + pr_err("Couldn't suspend rc = %d\n", rc); + else + chip->usb_suspended_status = suspended; + + return rc; +} + +static int smb1351_battchg_disable(struct smb1351_charger *chip, + int reason, int disable) +{ + int rc = 0; + int disabled; + + if (chip->chg_autonomous_mode) { + pr_debug("Charger in autonomous mode\n"); + return 0; + } + + disabled = chip->battchg_disabled_status; + + pr_debug("reason = %d requested_disable = %d disabled_status = %d\n", + reason, disable, disabled); + if (disable == true) + disabled |= reason; + else + disabled &= ~reason; + + pr_debug("new disabled_status = %d\n", disabled); + + rc = smb1351_masked_write(chip, CMD_CHG_REG, CMD_CHG_EN_BIT, + disabled ? 0 : CMD_CHG_ENABLE); + if (rc) + pr_err("Couldn't %s charging rc=%d\n", + disable ? "disable" : "enable", rc); + else + chip->battchg_disabled_status = disabled; + + return rc; +} + +static int smb1351_fastchg_current_set(struct smb1351_charger *chip, + unsigned int fastchg_current) +{ + int i, rc; + bool is_pre_chg = false; + + + if ((fastchg_current < SMB1351_CHG_PRE_MIN_MA) || + (fastchg_current > SMB1351_CHG_FAST_MAX_MA)) { + pr_err("bad pre_fastchg current mA=%d asked to set\n", + fastchg_current); + return -EINVAL; + } + + /* + * fast chg current could not support less than 1000mA + * use pre chg to instead for the parallel charging + */ + if (fastchg_current < SMB1351_CHG_FAST_MIN_MA) { + is_pre_chg = true; + pr_debug("is_pre_chg true, current is %d\n", fastchg_current); + } + + if (is_pre_chg) { + /* set prechg current */ + for (i = ARRAY_SIZE(pre_chg_current) - 1; i >= 0; i--) { + if (pre_chg_current[i] <= fastchg_current) + break; + } + if (i < 0) + i = 0; + chip->fastchg_current_max_ma = pre_chg_current[i]; + pr_debug("prechg setting %02x\n", i); + + i = i << SMB1351_CHG_PRE_SHIFT; + + rc = smb1351_masked_write(chip, CHG_OTH_CURRENT_CTRL_REG, + PRECHG_CURRENT_MASK, i); + if (rc) + pr_err("Couldn't write CHG_OTH_CURRENT_CTRL_REG rc=%d\n", + rc); + + return smb1351_masked_write(chip, VARIOUS_FUNC_2_REG, + PRECHG_TO_FASTCHG_BIT, PRECHG_TO_FASTCHG_BIT); + } else { + if (chip->version == SMB_UNKNOWN) + return -EINVAL; + + /* SMB1350 supports FCC upto 2600 mA */ + if (chip->version == SMB1350 && fastchg_current > 2600) + fastchg_current = 2600; + + /* set fastchg current */ + for (i = ARRAY_SIZE(fast_chg_current) - 1; i >= 0; i--) { + if (fast_chg_current[i] <= fastchg_current) + break; + } + if (i < 0) + i = 0; + chip->fastchg_current_max_ma = fast_chg_current[i]; + + i = i << SMB1351_CHG_FAST_SHIFT; + pr_debug("fastchg limit=%d setting %02x\n", + chip->fastchg_current_max_ma, i); + + /* make sure pre chg mode is disabled */ + rc = smb1351_masked_write(chip, VARIOUS_FUNC_2_REG, + PRECHG_TO_FASTCHG_BIT, 0); + if (rc) + pr_err("Couldn't write VARIOUS_FUNC_2_REG rc=%d\n", rc); + + return smb1351_masked_write(chip, CHG_CURRENT_CTRL_REG, + FAST_CHG_CURRENT_MASK, i); + } +} + +#define MIN_FLOAT_MV 3500 +#define MAX_FLOAT_MV 4500 +#define VFLOAT_STEP_MV 20 + +static int smb1351_float_voltage_set(struct smb1351_charger *chip, + int vfloat_mv) +{ + u8 temp; + + if ((vfloat_mv < MIN_FLOAT_MV) || (vfloat_mv > MAX_FLOAT_MV)) { + pr_err("bad float voltage mv =%d asked to set\n", vfloat_mv); + return -EINVAL; + } + + temp = (vfloat_mv - MIN_FLOAT_MV) / VFLOAT_STEP_MV; + + return smb1351_masked_write(chip, VFLOAT_REG, VFLOAT_MASK, temp); +} + +static int smb1351_iterm_set(struct smb1351_charger *chip, int iterm_ma) +{ + int rc; + u8 reg; + + if (iterm_ma <= 200) + reg = CHG_ITERM_200MA; + else if (iterm_ma <= 300) + reg = CHG_ITERM_300MA; + else if (iterm_ma <= 400) + reg = CHG_ITERM_400MA; + else if (iterm_ma <= 500) + reg = CHG_ITERM_500MA; + else if (iterm_ma <= 600) + reg = CHG_ITERM_600MA; + else + reg = CHG_ITERM_700MA; + + rc = smb1351_masked_write(chip, CHG_OTH_CURRENT_CTRL_REG, + ITERM_MASK, reg); + if (rc) { + pr_err("Couldn't set iterm rc = %d\n", rc); + return rc; + } + /* enable the iterm */ + rc = smb1351_masked_write(chip, CHG_CTRL_REG, + ITERM_EN_BIT, ITERM_ENABLE); + if (rc) { + pr_err("Couldn't enable iterm rc = %d\n", rc); + return rc; + } + return 0; +} + +static int smb1351_chg_otg_regulator_enable(struct regulator_dev *rdev) +{ + int rc = 0; + struct smb1351_charger *chip = rdev_get_drvdata(rdev); + + rc = smb1351_masked_write(chip, CMD_CHG_REG, CMD_OTG_EN_BIT, + CMD_OTG_EN_BIT); + if (rc) + pr_err("Couldn't enable OTG mode rc=%d\n", rc); + return rc; +} + +static int smb1351_chg_otg_regulator_disable(struct regulator_dev *rdev) +{ + int rc = 0; + struct smb1351_charger *chip = rdev_get_drvdata(rdev); + + rc = smb1351_masked_write(chip, CMD_CHG_REG, CMD_OTG_EN_BIT, 0); + if (rc) + pr_err("Couldn't disable OTG mode rc=%d\n", rc); + return rc; +} + +static int smb1351_chg_otg_regulator_is_enable(struct regulator_dev *rdev) +{ + int rc = 0; + u8 reg = 0; + struct smb1351_charger *chip = rdev_get_drvdata(rdev); + + rc = smb1351_read_reg(chip, CMD_CHG_REG, ®); + if (rc) { + pr_err("Couldn't read OTG enable bit rc=%d\n", rc); + return rc; + } + + return (reg & CMD_OTG_EN_BIT) ? 1 : 0; +} + +static struct regulator_ops smb1351_chg_otg_reg_ops = { + .enable = smb1351_chg_otg_regulator_enable, + .disable = smb1351_chg_otg_regulator_disable, + .is_enabled = smb1351_chg_otg_regulator_is_enable, +}; + +static int smb1351_regulator_init(struct smb1351_charger *chip) +{ + int rc = 0; + struct regulator_config cfg = {}; + struct regulator_init_data *init_data; + + chip->otg_vreg.rdesc.owner = THIS_MODULE; + chip->otg_vreg.rdesc.type = REGULATOR_VOLTAGE; + chip->otg_vreg.rdesc.ops = &smb1351_chg_otg_reg_ops; + chip->otg_vreg.rdesc.name = + chip->dev->of_node->name; + chip->otg_vreg.rdesc.of_match = + chip->dev->of_node->name; + + init_data = of_get_regulator_init_data(chip->dev, chip->dev->of_node, + &chip->otg_vreg.rdesc); + if (!init_data) { + pr_err("regulator init data is missing\n"); + return -EINVAL; + } + + cfg.dev = chip->dev; + cfg.driver_data = chip; + cfg.init_data = init_data; + cfg.of_node = chip->dev->of_node; + + chip->otg_vreg.rdev = regulator_register( + &chip->otg_vreg.rdesc, &cfg); + if (IS_ERR(chip->otg_vreg.rdev)) { + rc = PTR_ERR(chip->otg_vreg.rdev); + chip->otg_vreg.rdev = NULL; + if (rc != -EPROBE_DEFER) + pr_err("OTG reg failed, rc=%d\n", rc); + } + return rc; +} + +static int smb_chip_get_version(struct smb1351_charger *chip) +{ + u8 ver; + int rc = 0; + + if (chip->version == SMB_UNKNOWN) { + rc = smb1351_read_reg(chip, VERSION_REG, &ver); + if (rc) { + pr_err("Couldn't read version rc=%d\n", rc); + return rc; + } + + /* If bit 1 is set, it is SMB1350 */ + if (ver & VERSION_MASK) + chip->version = SMB1350; + else + chip->version = SMB1351; + } + + return rc; +} + +static int smb1351_hw_init(struct smb1351_charger *chip) +{ + int rc; + u8 reg = 0, mask = 0; + + /* configure smb_pinctrl to enable irqs */ + if (chip->pinctrl_state_name) { + chip->smb_pinctrl = pinctrl_get_select(chip->dev, + chip->pinctrl_state_name); + if (IS_ERR(chip->smb_pinctrl)) { + pr_err("Could not get/set %s pinctrl state rc = %ld\n", + chip->pinctrl_state_name, + PTR_ERR(chip->smb_pinctrl)); + return PTR_ERR(chip->smb_pinctrl); + } + } + + /* + * If the charger is pre-configured for autonomous operation, + * do not apply additional settings + */ + if (chip->chg_autonomous_mode) { + pr_debug("Charger configured for autonomous mode\n"); + return 0; + } + + rc = smb_chip_get_version(chip); + if (rc) { + pr_err("Couldn't get version rc = %d\n", rc); + return rc; + } + + rc = smb1351_enable_volatile_writes(chip); + if (rc) { + pr_err("Couldn't configure volatile writes rc=%d\n", rc); + return rc; + } + + /* Disable watchdog */ + rc = smb1351_masked_write(chip, WDOG_SAFETY_TIMER_CTRL_REG, + WDOG_TIMER_EN_BIT, 0); + if (rc) { + pr_err("Couldn't disable watchdog rc = %d\n", rc); + return rc; + } + + /* enable/disable charging by suspending usb */ + rc = smb1351_usb_suspend(chip, USER, chip->usb_suspended_status); + if (rc) { + pr_err("Unable to %ssuspend usb. rc=%d\n", + chip->usb_suspended_status ? "" : "un-", rc); + return rc; + } + + /* enable/disable battery charging */ + rc = smb1351_battchg_disable(chip, USER, chip->battchg_disabled_status); + if (rc) { + pr_err("Unable to %s battery charging. rc=%d\n", + chip->battchg_disabled_status ? "disable" : "enable", + rc); + return rc; + } + + /* setup battery missing source */ + reg = BATT_MISSING_THERM_PIN_SOURCE_BIT; + mask = BATT_MISSING_THERM_PIN_SOURCE_BIT; + rc = smb1351_masked_write(chip, HVDCP_BATT_MISSING_CTRL_REG, + mask, reg); + if (rc) { + pr_err("Couldn't set HVDCP_BATT_MISSING_CTRL_REG rc=%d\n", rc); + return rc; + } + /* setup defaults for CHG_PIN_EN_CTRL_REG */ + reg = EN_BY_I2C_0_DISABLE | USBCS_CTRL_BY_I2C | CHG_ERR_BIT | + APSD_DONE_BIT | LED_BLINK_FUNC_BIT; + mask = EN_PIN_CTRL_MASK | USBCS_CTRL_BIT | CHG_ERR_BIT | + APSD_DONE_BIT | LED_BLINK_FUNC_BIT; + rc = smb1351_masked_write(chip, CHG_PIN_EN_CTRL_REG, mask, reg); + if (rc) { + pr_err("Couldn't set CHG_PIN_EN_CTRL_REG rc=%d\n", rc); + return rc; + } + /* setup USB 2.0/3.0 detection and USB 500/100 command polarity */ + reg = USB_2_3_MODE_SEL_BY_I2C | USB_CMD_POLARITY_500_1_100_0; + mask = USB_2_3_MODE_SEL_BIT | USB_5_1_CMD_POLARITY_BIT; + rc = smb1351_masked_write(chip, CHG_OTH_CURRENT_CTRL_REG, mask, reg); + if (rc) { + pr_err("Couldn't set CHG_OTH_CURRENT_CTRL_REG rc=%d\n", rc); + return rc; + } + /* setup USB suspend, AICL and APSD */ + reg = SUSPEND_MODE_CTRL_BY_I2C | AICL_EN_BIT; + if (!chip->disable_apsd) + reg |= APSD_EN_BIT; + mask = SUSPEND_MODE_CTRL_BIT | AICL_EN_BIT | APSD_EN_BIT; + rc = smb1351_masked_write(chip, VARIOUS_FUNC_REG, mask, reg); + if (rc) { + pr_err("Couldn't set VARIOUS_FUNC_REG rc=%d\n", rc); + return rc; + } + /* Fault and Status IRQ configuration */ + reg = HOT_COLD_HARD_LIMIT_BIT | HOT_COLD_SOFT_LIMIT_BIT + | INPUT_OVLO_BIT | INPUT_UVLO_BIT | AICL_DONE_FAIL_BIT; + rc = smb1351_write_reg(chip, FAULT_INT_REG, reg); + if (rc) { + pr_err("Couldn't set FAULT_INT_REG rc=%d\n", rc); + return rc; + } + reg = CHG_OR_PRECHG_TIMEOUT_BIT | BATT_OVP_BIT | + FAST_TERM_TAPER_RECHG_INHIBIT_BIT | + BATT_MISSING_BIT | BATT_LOW_BIT; + if (chip->otg_enable) + reg = reg | RID_CHANGE_BIT; + rc = smb1351_write_reg(chip, STATUS_INT_REG, reg); + if (rc) { + pr_err("Couldn't set STATUS_INT_REG rc=%d\n", rc); + return rc; + } + /* setup THERM Monitor */ + rc = smb1351_masked_write(chip, THERM_A_CTRL_REG, + THERM_MONITOR_BIT, THERM_MONITOR_EN); + if (rc) { + pr_err("Couldn't set THERM_A_CTRL_REG rc=%d\n", rc); + return rc; + } + /* set the fast charge current limit */ + rc = smb1351_fastchg_current_set(chip, + chip->target_fastchg_current_max_ma); + if (rc) { + pr_err("Couldn't set fastchg current rc=%d\n", rc); + return rc; + } + + /* set the float voltage */ + if (chip->vfloat_mv != -EINVAL) { + rc = smb1351_float_voltage_set(chip, chip->vfloat_mv); + if (rc) { + pr_err("Couldn't set float voltage rc = %d\n", rc); + return rc; + } + } + + /* set iterm */ + if (chip->iterm_ma != -EINVAL) { + if (chip->iterm_disabled) { + pr_err("Error: Both iterm_disabled and iterm_ma set\n"); + return -EINVAL; + } + rc = smb1351_iterm_set(chip, chip->iterm_ma); + if (rc) { + pr_err("Couldn't set iterm rc = %d\n", rc); + return rc; + } + } else if (chip->iterm_disabled) { + rc = smb1351_masked_write(chip, CHG_CTRL_REG, + ITERM_EN_BIT, ITERM_DISABLE); + if (rc) { + pr_err("Couldn't set iterm rc = %d\n", rc); + return rc; + } + } + + /* set recharge-threshold */ + if (chip->recharge_mv != -EINVAL) { + if (chip->recharge_disabled) { + pr_err("Error: Both recharge_disabled and recharge_mv set\n"); + return -EINVAL; + } + + reg = AUTO_RECHG_ENABLE; + if (chip->recharge_mv > 50) + reg |= AUTO_RECHG_TH_100MV; + else + reg |= AUTO_RECHG_TH_50MV; + + rc = smb1351_masked_write(chip, CHG_CTRL_REG, + AUTO_RECHG_BIT | + AUTO_RECHG_TH_BIT, reg); + if (rc) { + pr_err("Couldn't set rechg-cfg rc = %d\n", rc); + return rc; + } + } else if (chip->recharge_disabled) { + rc = smb1351_masked_write(chip, CHG_CTRL_REG, + AUTO_RECHG_BIT, + AUTO_RECHG_DISABLE); + if (rc) { + pr_err("Couldn't disable auto-rechg rc = %d\n", rc); + return rc; + } + } + + if (chip->otg_enable) { + /* Configure OTG/ID pin control: RID enabled, OTG I2C control */ + rc = smb1351_masked_write(chip, OTG_USBIN_AICL_CTRL_REG, + OTG_ID_PIN_CTRL_MASK, + RID_ENABLED_OTG_I2C << OTG_ID_PIN_CTRL_SHIFT); + if (rc) { + pr_err("Couldn't configure RID enable rc = %d\n", rc); + return rc; + } + } + + return rc; +} + +static enum power_supply_property smb1351_battery_properties[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_CHARGING_ENABLED, + POWER_SUPPLY_PROP_BATTERY_CHARGING_ENABLED, + POWER_SUPPLY_PROP_CHARGE_TYPE, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_MODEL_NAME, +}; + +static int smb1351_get_prop_batt_status(struct smb1351_charger *chip) +{ + int rc; + u8 reg = 0; + + if (chip->batt_full) + return POWER_SUPPLY_STATUS_FULL; + + rc = smb1351_read_reg(chip, STATUS_4_REG, ®); + if (rc) { + pr_err("Couldn't read STATUS_4 rc = %d\n", rc); + return POWER_SUPPLY_STATUS_UNKNOWN; + } + + pr_debug("STATUS_4_REG(0x3A)=%x\n", reg); + + if (reg & STATUS_HOLD_OFF_BIT) + return POWER_SUPPLY_STATUS_NOT_CHARGING; + + if (reg & STATUS_CHG_MASK) + return POWER_SUPPLY_STATUS_CHARGING; + + return POWER_SUPPLY_STATUS_DISCHARGING; +} + +static int smb1351_get_prop_batt_present(struct smb1351_charger *chip) +{ + return !chip->battery_missing; +} + +static int smb1351_get_prop_batt_capacity(struct smb1351_charger *chip) +{ + union power_supply_propval ret = {0, }; + + if (chip->fake_battery_soc >= 0) + return chip->fake_battery_soc; + + if (chip->bms_psy) { + power_supply_get_property(chip->bms_psy, + POWER_SUPPLY_PROP_CAPACITY, &ret); + return ret.intval; + } + pr_debug("return DEFAULT_BATT_CAPACITY\n"); + return DEFAULT_BATT_CAPACITY; +} + +static int smb1351_get_prop_batt_temp(struct smb1351_charger *chip) +{ + union power_supply_propval ret = {0, }; + + if (chip->bms_psy) { + power_supply_get_property(chip->bms_psy, + POWER_SUPPLY_PROP_TEMP, &ret); + return ret.intval; + } + + pr_debug("return default temperature\n"); + return DEFAULT_BATT_TEMP; +} + +static int smb1351_get_prop_charge_type(struct smb1351_charger *chip) +{ + int rc; + u8 reg = 0; + + rc = smb1351_read_reg(chip, STATUS_4_REG, ®); + if (rc) { + pr_err("Couldn't read STATUS_4 rc = %d\n", rc); + return POWER_SUPPLY_CHARGE_TYPE_UNKNOWN; + } + + pr_debug("STATUS_4_REG(0x3A)=%x\n", reg); + + reg &= STATUS_CHG_MASK; + + if (reg == STATUS_FAST_CHARGING) + return POWER_SUPPLY_CHARGE_TYPE_FAST; + else if (reg == STATUS_TAPER_CHARGING) + return POWER_SUPPLY_CHARGE_TYPE_TAPER; + else if (reg == STATUS_PRE_CHARGING) + return POWER_SUPPLY_CHARGE_TYPE_TRICKLE; + else + return POWER_SUPPLY_CHARGE_TYPE_NONE; +} + +static int smb1351_get_prop_batt_health(struct smb1351_charger *chip) +{ + union power_supply_propval ret = {0, }; + + if (chip->batt_hot) + ret.intval = POWER_SUPPLY_HEALTH_OVERHEAT; + else if (chip->batt_cold) + ret.intval = POWER_SUPPLY_HEALTH_COLD; + else if (chip->batt_warm) + ret.intval = POWER_SUPPLY_HEALTH_WARM; + else if (chip->batt_cool) + ret.intval = POWER_SUPPLY_HEALTH_COOL; + else + ret.intval = POWER_SUPPLY_HEALTH_GOOD; + + return ret.intval; +} + +static int smb1351_set_usb_chg_current(struct smb1351_charger *chip, + int current_ma) +{ + int i, rc = 0; + u8 reg = 0, mask = 0; + + pr_debug("USB current_ma = %d\n", current_ma); + + if (chip->chg_autonomous_mode) { + pr_debug("Charger in autonomous mode\n"); + return 0; + } + + /* set suspend bit when urrent_ma <= 2 */ + if (current_ma <= SUSPEND_CURRENT_MA) { + smb1351_usb_suspend(chip, CURRENT, true); + pr_debug("USB suspend\n"); + return 0; + } + + if (current_ma > SUSPEND_CURRENT_MA && + current_ma < USB2_MIN_CURRENT_MA) + current_ma = USB2_MIN_CURRENT_MA; + + if (current_ma == USB2_MIN_CURRENT_MA) { + /* USB 2.0 - 100mA */ + reg = CMD_USB_2_MODE | CMD_USB_100_MODE; + } else if (current_ma == USB3_MIN_CURRENT_MA) { + /* USB 3.0 - 150mA */ + reg = CMD_USB_3_MODE | CMD_USB_100_MODE; + } else if (current_ma == USB2_MAX_CURRENT_MA) { + /* USB 2.0 - 500mA */ + reg = CMD_USB_2_MODE | CMD_USB_500_MODE; + } else if (current_ma == USB3_MAX_CURRENT_MA) { + /* USB 3.0 - 900mA */ + reg = CMD_USB_3_MODE | CMD_USB_500_MODE; + } else if (current_ma > USB2_MAX_CURRENT_MA) { + /* HC mode - if none of the above */ + reg = CMD_USB_AC_MODE; + + for (i = ARRAY_SIZE(usb_chg_current) - 1; i >= 0; i--) { + if (usb_chg_current[i] <= current_ma) + break; + } + if (i < 0) + i = 0; + rc = smb1351_masked_write(chip, CHG_CURRENT_CTRL_REG, + AC_INPUT_CURRENT_LIMIT_MASK, i); + if (rc) { + pr_err("Couldn't set input mA rc=%d\n", rc); + return rc; + } + } + /* control input current mode by command */ + reg |= CMD_INPUT_CURRENT_MODE_CMD; + mask = CMD_INPUT_CURRENT_MODE_BIT | CMD_USB_2_3_SEL_BIT | + CMD_USB_1_5_AC_CTRL_MASK; + rc = smb1351_masked_write(chip, CMD_INPUT_LIMIT_REG, mask, reg); + if (rc) { + pr_err("Couldn't set charging mode rc = %d\n", rc); + return rc; + } + + /* unset the suspend bit here */ + smb1351_usb_suspend(chip, CURRENT, false); + + return rc; +} + +static char *smb1351_usb_supplicants[] = { + "bms", +}; + +static enum power_supply_property smb1351_usb_properties[] = { + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_CURRENT_MAX, + POWER_SUPPLY_PROP_TYPE, + POWER_SUPPLY_PROP_REAL_TYPE, + POWER_SUPPLY_PROP_SDP_CURRENT_MAX, +}; + +static int smb1351_usb_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct smb1351_charger *chip = power_supply_get_drvdata(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_CURRENT_MAX: + case POWER_SUPPLY_PROP_SDP_CURRENT_MAX: + val->intval = chip->usb_psy_ma * 1000; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = chip->chg_present; + break; + case POWER_SUPPLY_PROP_ONLINE: + val->intval = chip->chg_present && !chip->usb_suspended_status; + break; + case POWER_SUPPLY_PROP_TYPE: + val->intval = chip->charger_type; + break; + case POWER_SUPPLY_PROP_REAL_TYPE: + if (chip->charger_type == POWER_SUPPLY_TYPE_USB_HVDCP) + val->intval = POWER_SUPPLY_TYPE_USB_DCP; + else if (chip->charger_type == POWER_SUPPLY_TYPE_UNKNOWN) + val->intval = POWER_SUPPLY_TYPE_USB; + else + val->intval = chip->charger_type; + break; + default: + return -EINVAL; + } + return 0; +} + +static int smb1351_usb_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct smb1351_charger *chip = power_supply_get_drvdata(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_CURRENT_MAX: + case POWER_SUPPLY_PROP_SDP_CURRENT_MAX: + chip->usb_psy_ma = val->intval / 1000; + smb1351_enable_volatile_writes(chip); + smb1351_set_usb_chg_current(chip, chip->usb_psy_ma); + break; + default: + return -EINVAL; + } + + power_supply_changed(psy); + return 0; +} + +static int smb1351_usb_is_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + switch (psp) { + case POWER_SUPPLY_PROP_CURRENT_MAX: + return 1; + default: + break; + } + + return 0; +} + +static int smb1351_batt_property_is_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + switch (psp) { + case POWER_SUPPLY_PROP_CHARGING_ENABLED: + case POWER_SUPPLY_PROP_BATTERY_CHARGING_ENABLED: + case POWER_SUPPLY_PROP_CAPACITY: + return 1; + default: + break; + } + return 0; +} + +static int smb1351_battery_set_property(struct power_supply *psy, + enum power_supply_property prop, + const union power_supply_propval *val) +{ + int rc; + struct smb1351_charger *chip = power_supply_get_drvdata(psy); + + switch (prop) { + case POWER_SUPPLY_PROP_STATUS: + if (!chip->bms_controlled_charging) + return -EINVAL; + switch (val->intval) { + case POWER_SUPPLY_STATUS_FULL: + rc = smb1351_battchg_disable(chip, SOC, true); + if (rc) { + pr_err("Couldn't disable charging rc = %d\n", + rc); + } else { + chip->batt_full = true; + pr_debug("status = FULL, batt_full = %d\n", + chip->batt_full); + } + break; + case POWER_SUPPLY_STATUS_DISCHARGING: + chip->batt_full = false; + power_supply_changed(chip->batt_psy); + pr_debug("status = DISCHARGING, batt_full = %d\n", + chip->batt_full); + break; + case POWER_SUPPLY_STATUS_CHARGING: + rc = smb1351_battchg_disable(chip, SOC, false); + if (rc) { + pr_err("Couldn't enable charging rc = %d\n", + rc); + } else { + chip->batt_full = false; + pr_debug("status = CHARGING, batt_full = %d\n", + chip->batt_full); + } + break; + default: + return -EINVAL; + } + break; + case POWER_SUPPLY_PROP_CHARGING_ENABLED: + smb1351_usb_suspend(chip, USER, !val->intval); + break; + case POWER_SUPPLY_PROP_BATTERY_CHARGING_ENABLED: + smb1351_battchg_disable(chip, USER, !val->intval); + break; + case POWER_SUPPLY_PROP_CAPACITY: + chip->fake_battery_soc = val->intval; + power_supply_changed(chip->batt_psy); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int smb1351_battery_get_property(struct power_supply *psy, + enum power_supply_property prop, + union power_supply_propval *val) +{ + struct smb1351_charger *chip = power_supply_get_drvdata(psy); + + switch (prop) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = smb1351_get_prop_batt_status(chip); + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = smb1351_get_prop_batt_present(chip); + break; + case POWER_SUPPLY_PROP_CAPACITY: + val->intval = smb1351_get_prop_batt_capacity(chip); + break; + case POWER_SUPPLY_PROP_CHARGING_ENABLED: + val->intval = !chip->usb_suspended_status; + break; + case POWER_SUPPLY_PROP_BATTERY_CHARGING_ENABLED: + val->intval = !chip->battchg_disabled_status; + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + val->intval = smb1351_get_prop_charge_type(chip); + break; + case POWER_SUPPLY_PROP_HEALTH: + val->intval = smb1351_get_prop_batt_health(chip); + break; + case POWER_SUPPLY_PROP_TEMP: + val->intval = smb1351_get_prop_batt_temp(chip); + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = POWER_SUPPLY_TECHNOLOGY_LION; + break; + case POWER_SUPPLY_PROP_MODEL_NAME: + val->strval = "smb1351"; + break; + default: + return -EINVAL; + } + return 0; +} + +static enum power_supply_property smb1351_parallel_properties[] = { + POWER_SUPPLY_PROP_CHARGING_ENABLED, + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_CURRENT_MAX, + POWER_SUPPLY_PROP_VOLTAGE_MAX, + POWER_SUPPLY_PROP_INPUT_CURRENT_LIMITED, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, + POWER_SUPPLY_PROP_CHARGE_TYPE, + POWER_SUPPLY_PROP_PARALLEL_MODE, + POWER_SUPPLY_PROP_INPUT_SUSPEND, + POWER_SUPPLY_PROP_PARALLEL_BATFET_MODE, +}; + +static int smb1351_parallel_set_chg_suspend(struct smb1351_charger *chip, + int suspend) +{ + int rc; + u8 reg, mask = 0; + + if (chip->parallel_charger_suspended == suspend) { + pr_debug("Skip same state request suspended = %d suspend=%d\n", + chip->parallel_charger_suspended, !suspend); + return 0; + } + + if (!suspend) { + rc = smb_chip_get_version(chip); + if (rc) { + pr_err("Couldn't get version rc = %d\n", rc); + return rc; + } + + rc = smb1351_enable_volatile_writes(chip); + if (rc) { + pr_err("Couldn't configure for volatile rc = %d\n", rc); + return rc; + } + + /* set the float voltage */ + if (chip->vfloat_mv != -EINVAL) { + rc = smb1351_float_voltage_set(chip, chip->vfloat_mv); + if (rc) { + pr_err("Couldn't set float voltage rc = %d\n", + rc); + return rc; + } + } + + /* set recharge-threshold and enable auto recharge */ + if (chip->recharge_mv != -EINVAL) { + reg = AUTO_RECHG_ENABLE; + if (chip->recharge_mv > 50) + reg |= AUTO_RECHG_TH_100MV; + else + reg |= AUTO_RECHG_TH_50MV; + + rc = smb1351_masked_write(chip, CHG_CTRL_REG, + AUTO_RECHG_BIT | + AUTO_RECHG_TH_BIT, reg); + if (rc) { + pr_err("Couldn't set rechg-cfg rc = %d\n", rc); + return rc; + } + } + + /* control USB suspend via command bits */ + rc = smb1351_masked_write(chip, VARIOUS_FUNC_REG, + APSD_EN_BIT | SUSPEND_MODE_CTRL_BIT, + SUSPEND_MODE_CTRL_BY_I2C); + if (rc) { + pr_err("Couldn't set USB suspend rc=%d\n", rc); + return rc; + } + + /* + * When present is being set force USB suspend, start charging + * only when POWER_SUPPLY_PROP_CURRENT_MAX is set. + */ + rc = smb1351_usb_suspend(chip, CURRENT, true); + if (rc) { + pr_err("failed to suspend rc=%d\n", rc); + return rc; + } + chip->usb_psy_ma = SUSPEND_CURRENT_MA; + + /* set chg en by pin active low */ + reg = chip->parallel_pin_polarity_setting | USBCS_CTRL_BY_I2C; + rc = smb1351_masked_write(chip, CHG_PIN_EN_CTRL_REG, + EN_PIN_CTRL_MASK | USBCS_CTRL_BIT, reg); + if (rc) { + pr_err("Couldn't set en pin rc=%d\n", rc); + return rc; + } + + /* + * setup USB 2.0/3.0 detection and USB 500/100 + * command polarity + */ + reg = USB_2_3_MODE_SEL_BY_I2C | USB_CMD_POLARITY_500_1_100_0; + mask = USB_2_3_MODE_SEL_BIT | USB_5_1_CMD_POLARITY_BIT; + rc = smb1351_masked_write(chip, + CHG_OTH_CURRENT_CTRL_REG, mask, reg); + if (rc) { + pr_err("Couldn't set CHG_OTH_CURRENT_CTRL_REG rc=%d\n", + rc); + return rc; + } + + rc = smb1351_fastchg_current_set(chip, + chip->target_fastchg_current_max_ma); + if (rc) { + pr_err("Couldn't set fastchg current rc=%d\n", rc); + return rc; + } + chip->parallel_charger_suspended = false; + } else { + rc = smb1351_usb_suspend(chip, CURRENT, true); + if (rc) + pr_debug("failed to suspend rc=%d\n", rc); + + chip->usb_psy_ma = SUSPEND_CURRENT_MA; + chip->parallel_charger_suspended = true; + } + + return 0; +} + +static bool smb1351_is_input_current_limited(struct smb1351_charger *chip) +{ + int rc; + u8 reg; + + rc = smb1351_read_reg(chip, IRQ_H_REG, ®); + if (rc) { + pr_err("Failed to read IRQ_H_REG for ICL status: %d\n", rc); + return false; + } + + return !!(reg & IRQ_IC_LIMIT_STATUS_BIT); +} + +static bool smb1351_is_usb_present(struct smb1351_charger *chip) +{ + int rc; + union power_supply_propval val = {0, }; + + if (!chip->usb_psy) + chip->usb_psy = power_supply_get_by_name("usb"); + if (!chip->usb_psy) { + pr_err("USB psy not found\n"); + return false; + } + + rc = power_supply_get_property(chip->usb_psy, + POWER_SUPPLY_PROP_ONLINE, &val); + if (rc < 0) { + pr_err("Failed to get present property rc=%d\n", rc); + return false; + } + + if (val.intval) + return true; + + return false; +} + +static int smb1351_parallel_set_property(struct power_supply *psy, + enum power_supply_property prop, + const union power_supply_propval *val) +{ + int rc = 0, index; + struct smb1351_charger *chip = power_supply_get_drvdata(psy); + + switch (prop) { + case POWER_SUPPLY_PROP_CHARGING_ENABLED: + /* + *CHG EN is controlled by pin in the parallel charging. + *Use suspend if disable charging by command. + */ + if (!chip->parallel_charger_suspended) + rc = smb1351_usb_suspend(chip, USER, !val->intval); + break; + case POWER_SUPPLY_PROP_INPUT_SUSPEND: + rc = smb1351_parallel_set_chg_suspend(chip, val->intval); + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: + chip->target_fastchg_current_max_ma = + val->intval / 1000; + if (!chip->parallel_charger_suspended) + rc = smb1351_fastchg_current_set(chip, + chip->target_fastchg_current_max_ma); + break; + case POWER_SUPPLY_PROP_CURRENT_MAX: + index = smb1351_get_closest_usb_setpoint(val->intval / 1000); + chip->usb_psy_ma = usb_chg_current[index]; + if (!chip->parallel_charger_suspended) + rc = smb1351_set_usb_chg_current(chip, + chip->usb_psy_ma); + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + chip->vfloat_mv = val->intval / 1000; + if (!chip->parallel_charger_suspended) + rc = smb1351_float_voltage_set(chip, val->intval); + break; + default: + return -EINVAL; + } + return rc; +} + +static int smb1351_parallel_is_writeable(struct power_supply *psy, + enum power_supply_property prop) +{ + switch (prop) { + case POWER_SUPPLY_PROP_CHARGING_ENABLED: + return 1; + default: + return 0; + } +} + +static int smb1351_parallel_get_property(struct power_supply *psy, + enum power_supply_property prop, + union power_supply_propval *val) +{ + struct smb1351_charger *chip = power_supply_get_drvdata(psy); + + switch (prop) { + case POWER_SUPPLY_PROP_CHARGING_ENABLED: + val->intval = !chip->parallel_charger_suspended; + break; + case POWER_SUPPLY_PROP_CURRENT_MAX: + if (!chip->parallel_charger_suspended) + val->intval = chip->usb_psy_ma * 1000; + else + val->intval = 0; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + if (!chip->parallel_charger_suspended) + val->intval = chip->vfloat_mv; + else + val->intval = 0; + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE; + /* Check if SMB1351 is present */ + if (smb1351_is_usb_present(chip)) { + val->intval = smb1351_get_prop_charge_type(chip); + if (val->intval == POWER_SUPPLY_CHARGE_TYPE_UNKNOWN) { + pr_debug("Failed to charge type, charger may be absent\n"); + return -ENODEV; + } + } + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: + if (!chip->parallel_charger_suspended) + val->intval = chip->fastchg_current_max_ma * 1000; + else + val->intval = 0; + break; + case POWER_SUPPLY_PROP_STATUS: + if (!chip->parallel_charger_suspended) + val->intval = smb1351_get_prop_batt_status(chip); + else + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + break; + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMITED: + if (!chip->parallel_charger_suspended) + val->intval = + smb1351_is_input_current_limited(chip) ? 1 : 0; + else + val->intval = 0; + break; + case POWER_SUPPLY_PROP_PARALLEL_MODE: + val->intval = chip->parallel_mode; + break; + case POWER_SUPPLY_PROP_INPUT_SUSPEND: + val->intval = chip->parallel_charger_suspended; + break; + case POWER_SUPPLY_PROP_PARALLEL_BATFET_MODE: + val->intval = chip->pl_batfet_mode; + break; + default: + return -EINVAL; + } + return 0; +} + +static int rerun_apsd(struct smb1351_charger *chip) +{ + int rc; + + pr_debug("Reruning APSD\nDisabling APSD\n"); + + rc = smb1351_masked_write(chip, CMD_HVDCP_REG, CMD_APSD_RE_RUN_BIT, + CMD_APSD_RE_RUN_BIT); + if (rc) + pr_err("Couldn't re-run APSD algo\n"); + + return 0; +} + +static void smb1351_hvdcp_det_work(struct work_struct *work) +{ + int rc; + u8 reg; + union power_supply_propval pval = {0, }; + struct smb1351_charger *chip = container_of(work, + struct smb1351_charger, + hvdcp_det_work.work); + + rc = smb1351_read_reg(chip, STATUS_7_REG, ®); + if (rc) { + pr_err("Couldn't read STATUS_7_REG rc == %d\n", rc); + goto end; + } + pr_debug("STATUS_7_REG = 0x%02X\n", reg); + + if (reg) { + pr_debug("HVDCP detected; notifying USB PSY\n"); + pval.intval = POWER_SUPPLY_TYPE_USB_HVDCP; + power_supply_set_property(chip->usb_psy, + POWER_SUPPLY_PROP_TYPE, &pval); + } +end: + pm_relax(chip->dev); +} + +#define HVDCP_NOTIFY_MS 2500 +static int smb1351_apsd_complete_handler(struct smb1351_charger *chip, + u8 status) +{ + int rc, usb_psy_ma = 0; + u8 reg = 0; + enum power_supply_type type = POWER_SUPPLY_TYPE_UNKNOWN; + union extcon_property_value val; + + /* + * If apsd is disabled, charger detection is done by + * USB phy driver. + */ + if (chip->disable_apsd || chip->usbin_ov) { + pr_debug("APSD %s, status = %d\n", + chip->disable_apsd ? "disabled" : "enabled", !!status); + pr_debug("USBIN ov, status = %d\n", chip->usbin_ov); + return 0; + } + + rc = smb1351_read_reg(chip, STATUS_5_REG, ®); + if (rc) { + pr_err("Couldn't read STATUS_5 rc = %d\n", rc); + return rc; + } + + pr_debug("STATUS_5_REG(0x3B)=%x\n", reg); + + switch (reg) { + case STATUS_PORT_ACA_DOCK: + case STATUS_PORT_ACA_C: + case STATUS_PORT_ACA_B: + case STATUS_PORT_ACA_A: + type = POWER_SUPPLY_TYPE_USB_ACA; + break; + case STATUS_PORT_CDP: + type = POWER_SUPPLY_TYPE_USB_CDP; + break; + case STATUS_PORT_DCP: + type = POWER_SUPPLY_TYPE_USB_DCP; + break; + case STATUS_PORT_SDP: + type = POWER_SUPPLY_TYPE_USB; + break; + case STATUS_PORT_OTHER: + type = POWER_SUPPLY_TYPE_USB_DCP; + break; + default: + type = POWER_SUPPLY_TYPE_USB; + break; + } + + if (status) { + chip->chg_present = true; + pr_debug("APSD complete. USB type detected=%d chg_present=%d\n", + type, chip->chg_present); + if (!chip->battery_missing && !chip->apsd_rerun) { + if (type == POWER_SUPPLY_TYPE_USB) { + smb1351_request_dpdm(chip, false); + smb1351_request_dpdm(chip, true); + chip->apsd_rerun = true; + rerun_apsd(chip); + return 0; + } + } + /* + * If defined force hvdcp 2p0 property, + * we force to hvdcp 2p0 in the APSD handler. + */ + if (chip->force_hvdcp_2p0) { + pr_debug("Force set to HVDCP 2.0 mode\n"); + smb1351_masked_write(chip, VARIOUS_FUNC_3_REG, + QC_2P1_AUTH_ALGO_BIT, 0); + smb1351_masked_write(chip, CMD_HVDCP_REG, + CMD_FORCE_HVDCP_2P0_BIT, + CMD_FORCE_HVDCP_2P0_BIT); + type = POWER_SUPPLY_TYPE_USB_HVDCP; + } else if (type == POWER_SUPPLY_TYPE_USB_DCP) { + pr_debug("schedule hvdcp detection worker\n"); + pm_stay_awake(chip->dev); + schedule_delayed_work(&chip->hvdcp_det_work, + msecs_to_jiffies(HVDCP_NOTIFY_MS)); + } + + chip->charger_type = type; + if (type == POWER_SUPPLY_TYPE_USB + || type == POWER_SUPPLY_TYPE_USB_CDP) { + val.intval = true; + extcon_set_property(chip->extcon, EXTCON_USB, + EXTCON_PROP_USB_SS, val); + extcon_set_state_sync(chip->extcon, EXTCON_USB, true); + pr_debug("extcon notify: EXTCON_USB present = 1\n"); + } + chip->apsd_rerun = false; + + /* set the charge current as required */ + if (type == POWER_SUPPLY_TYPE_USB) + usb_psy_ma = USB2_MIN_CURRENT_MA; + else /* DCP */ + usb_psy_ma = DCP_MAX_CURRENT_MA; + + chip->usb_psy_ma = usb_psy_ma; + smb1351_enable_volatile_writes(chip); + rc = smb1351_set_usb_chg_current(chip, chip->usb_psy_ma); + if (rc < 0) + pr_err("Failed to set USB current rc=%d\n", rc); + + } else if (!chip->apsd_rerun) { + /* Handle Charger removal */ + chip->chg_present = 0; + chip->charger_type = POWER_SUPPLY_TYPE_UNKNOWN; + val.intval = false; + extcon_set_property(chip->extcon, EXTCON_USB, + EXTCON_PROP_USB_SS, val); + extcon_set_state_sync(chip->extcon, EXTCON_USB, false); + pr_debug("extcon notify: EXTCON_USB present = 0\n"); + smb1351_request_dpdm(chip, false); + } + + return 0; +} + +/* + * As source detect interrupt is not triggered on the falling edge, + * we need to schedule a work for checking source detect status after + * charger UV interrupt fired. + */ +#define FIRST_CHECK_DELAY 100 +#define SECOND_CHECK_DELAY 1000 +static void smb1351_chg_remove_work(struct work_struct *work) +{ + int rc; + u8 reg; + struct smb1351_charger *chip = container_of(work, + struct smb1351_charger, chg_remove_work.work); + + rc = smb1351_read_reg(chip, IRQ_G_REG, ®); + if (rc) { + pr_err("Couldn't read IRQ_G_REG rc = %d\n", rc); + goto end; + } + + if (!(reg & IRQ_SOURCE_DET_BIT)) { + pr_debug("chg removed\n"); + smb1351_apsd_complete_handler(chip, 0); + } else if (!chip->chg_remove_work_scheduled) { + chip->chg_remove_work_scheduled = true; + goto reschedule; + } else { + pr_debug("charger is present\n"); + } +end: + chip->chg_remove_work_scheduled = false; + pm_relax(chip->dev); + return; + +reschedule: + pr_debug("reschedule after 1s\n"); + schedule_delayed_work(&chip->chg_remove_work, + msecs_to_jiffies(SECOND_CHECK_DELAY)); +} + +static int smb1351_usbin_uv_handler(struct smb1351_charger *chip, u8 status) +{ + smb1351_request_dpdm(chip, !!status); + + if (status) { + cancel_delayed_work_sync(&chip->hvdcp_det_work); + pm_relax(chip->dev); + pr_debug("schedule charger remove worker\n"); + schedule_delayed_work(&chip->chg_remove_work, + msecs_to_jiffies(FIRST_CHECK_DELAY)); + pm_stay_awake(chip->dev); + } + + pr_debug("chip->chg_present = %d\n", chip->chg_present); + + return 0; +} + +static int smb1351_usbin_ov_handler(struct smb1351_charger *chip, u8 status) +{ + int rc; + u8 reg = 0; + + rc = smb1351_read_reg(chip, IRQ_E_REG, ®); + if (rc) + pr_err("Couldn't read IRQ_E rc = %d\n", rc); + + if (status != 0) { + chip->usbin_ov = true; + chip->charger_type = POWER_SUPPLY_TYPE_UNKNOWN; + if (chip->chg_present) { + extcon_set_state_sync(chip->extcon, EXTCON_USB, false); + pr_debug("extcon notify: EXTCON_USB present = 0\n"); + } + chip->chg_present = false; + + } else { + chip->usbin_ov = false; + if (reg & IRQ_USBIN_UV_BIT) + pr_debug("Charger unplugged from OV\n"); + else + smb1351_apsd_complete_handler(chip, 1); + } + + pr_debug("chip->chg_present = %d\n", chip->chg_present); + + return 0; +} + +static int smb1351_fast_chg_handler(struct smb1351_charger *chip, u8 status) +{ + pr_debug("enter\n"); + return 0; +} + +static int smb1351_chg_term_handler(struct smb1351_charger *chip, u8 status) +{ + pr_debug("enter\n"); + if (!chip->bms_controlled_charging) + chip->batt_full = !!status; + return 0; +} + +static int smb1351_safety_timeout_handler(struct smb1351_charger *chip, + u8 status) +{ + pr_debug("safety_timeout triggered\n"); + return 0; +} + +static int smb1351_aicl_done_handler(struct smb1351_charger *chip, u8 status) +{ + pr_debug("aicl_done triggered\n"); + return 0; +} + +static int smb1351_hot_hard_handler(struct smb1351_charger *chip, u8 status) +{ + pr_debug("status = 0x%02x\n", status); + chip->batt_hot = !!status; + return 0; +} +static int smb1351_cold_hard_handler(struct smb1351_charger *chip, u8 status) +{ + pr_debug("status = 0x%02x\n", status); + chip->batt_cold = !!status; + return 0; +} +static int smb1351_hot_soft_handler(struct smb1351_charger *chip, u8 status) +{ + pr_debug("status = 0x%02x\n", status); + chip->batt_warm = !!status; + return 0; +} +static int smb1351_cold_soft_handler(struct smb1351_charger *chip, u8 status) +{ + pr_debug("status = 0x%02x\n", status); + chip->batt_cool = !!status; + return 0; +} + +static int smb1351_battery_missing_handler(struct smb1351_charger *chip, + u8 status) +{ + if (status) + chip->battery_missing = true; + else + chip->battery_missing = false; + + return 0; +} + +static int smb1351_rid_handler(struct smb1351_charger *chip, + u8 status) +{ + union extcon_property_value val; + bool rid_status; + u8 reg = 0; + int rc; + + rc = smb1351_read_reg(chip, STATUS_6_REG, ®); + if (rc < 0) + pr_err("Couldn't read status_6_reg, rc=%d\n", rc); + pr_debug("rt_status = 0x%02x, status_6_reg=0x%x\n", status, reg); + + rid_status = (!!status) || !(reg & STATUS_RID_FLOAT_STATE_MACHINE_BIT); + if (rid_status) { + val.intval = true; + extcon_set_property(chip->extcon, EXTCON_USB_HOST, + EXTCON_PROP_USB_SS, val); + } + + extcon_set_state_sync(chip->extcon, EXTCON_USB_HOST, rid_status); + pr_debug("extcon notify: EXTCON_USB_HOST present = %d\n", rid_status); + + return 0; +} + +static struct irq_handler_info handlers[] = { + [0] = { + .stat_reg = IRQ_A_REG, + .val = 0, + .prev_val = 0, + .irq_info = { + { .name = "cold_soft", + .smb_irq = smb1351_cold_soft_handler, + }, + { .name = "hot_soft", + .smb_irq = smb1351_hot_soft_handler, + }, + { .name = "cold_hard", + .smb_irq = smb1351_cold_hard_handler, + }, + { .name = "hot_hard", + .smb_irq = smb1351_hot_hard_handler, + }, + }, + }, + [1] = { + .stat_reg = IRQ_B_REG, + .val = 0, + .prev_val = 0, + .irq_info = { + { .name = "internal_temp_limit", + }, + { .name = "vbatt_low", + }, + { .name = "battery_missing", + .smb_irq = smb1351_battery_missing_handler, + }, + { .name = "batt_therm_removed", + }, + }, + }, + [2] = { + .stat_reg = IRQ_C_REG, + .val = 0, + .prev_val = 0, + .irq_info = { + { .name = "chg_term", + .smb_irq = smb1351_chg_term_handler, + }, + { .name = "taper", + }, + { .name = "recharge", + }, + { .name = "fast_chg", + .smb_irq = smb1351_fast_chg_handler, + }, + }, + }, + [3] = { + .stat_reg = IRQ_D_REG, + .val = 0, + .prev_val = 0, + .irq_info = { + { .name = "prechg_timeout", + }, + { .name = "safety_timeout", + .smb_irq = smb1351_safety_timeout_handler, + }, + { .name = "chg_error", + }, + { .name = "batt_ov", + }, + }, + }, + [4] = { + .stat_reg = IRQ_E_REG, + .val = 0, + .prev_val = 0, + .irq_info = { + { .name = "power_ok", + }, + { .name = "afvc", + }, + { .name = "usbin_uv", + .smb_irq = smb1351_usbin_uv_handler, + }, + { .name = "usbin_ov", + .smb_irq = smb1351_usbin_ov_handler, + }, + }, + }, + [5] = { + .stat_reg = IRQ_F_REG, + .val = 0, + .prev_val = 0, + .irq_info = { + { .name = "otg_oc_retry", + }, + { .name = "rid", + .smb_irq = smb1351_rid_handler, + }, + { .name = "otg_fail", + }, + { .name = "otg_oc", + }, + }, + }, + [6] = { + .stat_reg = IRQ_G_REG, + .val = 0, + .prev_val = 0, + .irq_info = { + { .name = "chg_inhibit", + }, + { .name = "aicl_fail", + }, + { .name = "aicl_done", + .smb_irq = smb1351_aicl_done_handler, + }, + { .name = "apsd_complete", + .smb_irq = smb1351_apsd_complete_handler, + }, + }, + }, + [7] = { + .stat_reg = IRQ_H_REG, + .val = 0, + .prev_val = 0, + .irq_info = { + { .name = "wdog_timeout", + }, + { .name = "hvdcp_auth_done", + }, + }, + }, +}; + +#define IRQ_LATCHED_MASK 0x02 +#define IRQ_STATUS_MASK 0x01 +#define BITS_PER_IRQ 2 +static irqreturn_t smb1351_chg_stat_handler(int irq, void *dev_id) +{ + struct smb1351_charger *chip = dev_id; + int i, j; + u8 triggered; + u8 changed; + u8 rt_stat, prev_rt_stat; + int rc; + int handler_count = 0; + + mutex_lock(&chip->irq_complete); + + chip->irq_waiting = true; + if (!chip->resume_completed) { + pr_debug("IRQ triggered before device-resume\n"); + disable_irq_nosync(irq); + mutex_unlock(&chip->irq_complete); + return IRQ_HANDLED; + } + chip->irq_waiting = false; + + for (i = 0; i < ARRAY_SIZE(handlers); i++) { + rc = smb1351_read_reg(chip, handlers[i].stat_reg, + &handlers[i].val); + if (rc) { + pr_err("Couldn't read %d rc = %d\n", + handlers[i].stat_reg, rc); + continue; + } + + for (j = 0; j < ARRAY_SIZE(handlers[i].irq_info); j++) { + triggered = handlers[i].val + & (IRQ_LATCHED_MASK << (j * BITS_PER_IRQ)); + rt_stat = handlers[i].val + & (IRQ_STATUS_MASK << (j * BITS_PER_IRQ)); + prev_rt_stat = handlers[i].prev_val + & (IRQ_STATUS_MASK << (j * BITS_PER_IRQ)); + changed = prev_rt_stat ^ rt_stat; + + if (triggered || changed) + rt_stat ? handlers[i].irq_info[j].high++ : + handlers[i].irq_info[j].low++; + + if ((triggered || changed) + && handlers[i].irq_info[j].smb_irq != NULL) { + handler_count++; + rc = handlers[i].irq_info[j].smb_irq(chip, + rt_stat); + if (rc) + pr_err("Couldn't handle %d irq for reg 0x%02x rc = %d\n", + j, handlers[i].stat_reg, rc); + } + } + handlers[i].prev_val = handlers[i].val; + } + + pr_debug("handler count = %d\n", handler_count); + if (handler_count) { + pr_debug("batt psy changed\n"); + power_supply_changed(chip->batt_psy); + } + + mutex_unlock(&chip->irq_complete); + + return IRQ_HANDLED; +} + +#define LAST_CNFG_REG 0x16 +static int show_cnfg_regs(struct seq_file *m, void *data) +{ + struct smb1351_charger *chip = m->private; + int rc; + u8 reg; + u8 addr; + + for (addr = 0; addr <= LAST_CNFG_REG; addr++) { + rc = smb1351_read_reg(chip, addr, ®); + if (!rc) + seq_printf(m, "0x%02x = 0x%02x\n", addr, reg); + } + + return 0; +} + +static int cnfg_debugfs_open(struct inode *inode, struct file *file) +{ + struct smb1351_charger *chip = inode->i_private; + + return single_open(file, show_cnfg_regs, chip); +} + +static const struct file_operations cnfg_debugfs_ops = { + .owner = THIS_MODULE, + .open = cnfg_debugfs_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +#define FIRST_CMD_REG 0x30 +#define LAST_CMD_REG 0x34 +static int show_cmd_regs(struct seq_file *m, void *data) +{ + struct smb1351_charger *chip = m->private; + int rc; + u8 reg; + u8 addr; + + for (addr = FIRST_CMD_REG; addr <= LAST_CMD_REG; addr++) { + rc = smb1351_read_reg(chip, addr, ®); + if (!rc) + seq_printf(m, "0x%02x = 0x%02x\n", addr, reg); + } + + return 0; +} + +static int cmd_debugfs_open(struct inode *inode, struct file *file) +{ + struct smb1351_charger *chip = inode->i_private; + + return single_open(file, show_cmd_regs, chip); +} + +static const struct file_operations cmd_debugfs_ops = { + .owner = THIS_MODULE, + .open = cmd_debugfs_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +#define FIRST_STATUS_REG 0x36 +#define LAST_STATUS_REG 0x3F +static int show_status_regs(struct seq_file *m, void *data) +{ + struct smb1351_charger *chip = m->private; + int rc; + u8 reg; + u8 addr; + + for (addr = FIRST_STATUS_REG; addr <= LAST_STATUS_REG; addr++) { + rc = smb1351_read_reg(chip, addr, ®); + if (!rc) + seq_printf(m, "0x%02x = 0x%02x\n", addr, reg); + } + + return 0; +} + +static int status_debugfs_open(struct inode *inode, struct file *file) +{ + struct smb1351_charger *chip = inode->i_private; + + return single_open(file, show_status_regs, chip); +} + +static const struct file_operations status_debugfs_ops = { + .owner = THIS_MODULE, + .open = status_debugfs_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int show_irq_count(struct seq_file *m, void *data) +{ + int i, j, total = 0; + + for (i = 0; i < ARRAY_SIZE(handlers); i++) + for (j = 0; j < 4; j++) { + seq_printf(m, "%s=%d\t(high=%d low=%d)\n", + handlers[i].irq_info[j].name, + handlers[i].irq_info[j].high + + handlers[i].irq_info[j].low, + handlers[i].irq_info[j].high, + handlers[i].irq_info[j].low); + total += (handlers[i].irq_info[j].high + + handlers[i].irq_info[j].low); + } + + seq_printf(m, "\n\tTotal = %d\n", total); + + return 0; +} + +static int irq_count_debugfs_open(struct inode *inode, struct file *file) +{ + struct smb1351_charger *chip = inode->i_private; + + return single_open(file, show_irq_count, chip); +} + +static const struct file_operations irq_count_debugfs_ops = { + .owner = THIS_MODULE, + .open = irq_count_debugfs_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int get_reg(void *data, u64 *val) +{ + struct smb1351_charger *chip = data; + int rc; + u8 temp; + + rc = smb1351_read_reg(chip, chip->peek_poke_address, &temp); + if (rc) { + pr_err("Couldn't read reg %x rc = %d\n", + chip->peek_poke_address, rc); + return -EAGAIN; + } + *val = temp; + return 0; +} + +static int set_reg(void *data, u64 val) +{ + struct smb1351_charger *chip = data; + int rc; + u8 temp; + + temp = (u8) val; + rc = smb1351_write_reg(chip, chip->peek_poke_address, temp); + if (rc) { + pr_err("Couldn't write 0x%02x to 0x%02x rc= %d\n", + temp, chip->peek_poke_address, rc); + return -EAGAIN; + } + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(poke_poke_debug_ops, get_reg, set_reg, "0x%02llx\n"); + +static int force_irq_set(void *data, u64 val) +{ + struct smb1351_charger *chip = data; + + smb1351_chg_stat_handler(chip->client->irq, data); + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(force_irq_ops, NULL, force_irq_set, "0x%02llx\n"); + +#ifdef DEBUG +static void dump_regs(struct smb1351_charger *chip) +{ + int rc; + u8 reg; + u8 addr; + + for (addr = 0; addr <= LAST_CNFG_REG; addr++) { + rc = smb1351_read_reg(chip, addr, ®); + if (rc) + pr_err("Couldn't read 0x%02x rc = %d\n", addr, rc); + else + pr_debug("0x%02x = 0x%02x\n", addr, reg); + } + + for (addr = FIRST_STATUS_REG; addr <= LAST_STATUS_REG; addr++) { + rc = smb1351_read_reg(chip, addr, ®); + if (rc) + pr_err("Couldn't read 0x%02x rc = %d\n", addr, rc); + else + pr_debug("0x%02x = 0x%02x\n", addr, reg); + } + + for (addr = FIRST_CMD_REG; addr <= LAST_CMD_REG; addr++) { + rc = smb1351_read_reg(chip, addr, ®); + if (rc) + pr_err("Couldn't read 0x%02x rc = %d\n", addr, rc); + else + pr_debug("0x%02x = 0x%02x\n", addr, reg); + } +} +#else +static void dump_regs(struct smb1351_charger *chip) +{ +} +#endif + +static int smb1351_parse_dt(struct smb1351_charger *chip) +{ + int rc; + struct device_node *node = chip->dev->of_node; + + if (!node) { + pr_err("device tree info. missing\n"); + return -EINVAL; + } + + chip->usb_suspended_status = of_property_read_bool(node, + "qcom,charging-disabled"); + + chip->battchg_disabled_status = of_property_read_bool(node, + "qcom,batt-charging-disabled"); + + chip->chg_autonomous_mode = of_property_read_bool(node, + "qcom,chg-autonomous-mode"); + + chip->disable_apsd = of_property_read_bool(node, "qcom,disable-apsd"); + + chip->bms_controlled_charging = of_property_read_bool(node, + "qcom,bms-controlled-charging"); + chip->force_hvdcp_2p0 = of_property_read_bool(node, + "qcom,force-hvdcp-2p0"); + + rc = of_property_read_string(node, "qcom,bms-psy-name", + &chip->bms_psy_name); + if (rc) + chip->bms_psy_name = NULL; + + rc = of_property_read_u32(node, "qcom,fastchg-current-max-ma", + &chip->target_fastchg_current_max_ma); + if (rc) + chip->target_fastchg_current_max_ma = SMB1351_CHG_FAST_MAX_MA; + + chip->iterm_disabled = of_property_read_bool(node, + "qcom,iterm-disabled"); + + rc = of_property_read_u32(node, "qcom,iterm-ma", &chip->iterm_ma); + if (rc) + chip->iterm_ma = -EINVAL; + + rc = of_property_read_u32(node, "qcom,float-voltage-mv", + &chip->vfloat_mv); + if (rc) + chip->vfloat_mv = -EINVAL; + + rc = of_property_read_u32(node, "qcom,recharge-mv", + &chip->recharge_mv); + if (rc) + chip->recharge_mv = -EINVAL; + + chip->recharge_disabled = of_property_read_bool(node, + "qcom,recharge-disabled"); + + chip->pinctrl_state_name = of_get_property(node, "pinctrl-names", NULL); + chip->otg_enable = of_property_read_bool(node, "qcom,otg-enable"); + + return 0; +} + +static int smb1351_determine_initial_state(struct smb1351_charger *chip) +{ + int rc; + u8 reg = 0; + + /* + * It is okay to read the interrupt status here since + * interrupts aren't requested. Reading interrupt status + * clears the interrupt so be careful to read interrupt + * status only in interrupt handling code + */ + + rc = smb1351_read_reg(chip, IRQ_B_REG, ®); + if (rc) { + pr_err("Couldn't read IRQ_B rc = %d\n", rc); + goto fail_init_status; + } + + chip->battery_missing = (reg & IRQ_BATT_MISSING_BIT) ? true : false; + + rc = smb1351_read_reg(chip, IRQ_C_REG, ®); + if (rc) { + pr_err("Couldn't read IRQ_C rc = %d\n", rc); + goto fail_init_status; + } + chip->batt_full = (reg & IRQ_TERM_BIT) ? true : false; + + rc = smb1351_read_reg(chip, IRQ_A_REG, ®); + if (rc) { + pr_err("Couldn't read irq A rc = %d\n", rc); + return rc; + } + + if (reg & IRQ_HOT_HARD_BIT) + chip->batt_hot = true; + if (reg & IRQ_COLD_HARD_BIT) + chip->batt_cold = true; + if (reg & IRQ_HOT_SOFT_BIT) + chip->batt_warm = true; + if (reg & IRQ_COLD_SOFT_BIT) + chip->batt_cool = true; + + /* check initial state of OTG */ + rc = smb1351_read_reg(chip, IRQ_F_REG, ®); + if (rc) { + pr_err("Couldn't read IRQ_F rc = %d\n", rc); + goto fail_init_status; + } + smb1351_rid_handler(chip, reg & IRQ_RID_BIT); + + rc = smb1351_read_reg(chip, IRQ_E_REG, ®); + if (rc) { + pr_err("Couldn't read IRQ_E rc = %d\n", rc); + goto fail_init_status; + } + + if (reg & IRQ_USBIN_UV_BIT) { + smb1351_usbin_uv_handler(chip, 1); + } else { + smb1351_usbin_uv_handler(chip, 0); + smb1351_apsd_complete_handler(chip, 1); + } + + return 0; + +fail_init_status: + pr_err("Couldn't determine initial status\n"); + return rc; +} + +static int is_parallel_charger(struct i2c_client *client) +{ + struct device_node *node = client->dev.of_node; + + return of_property_read_bool(node, "qcom,parallel-charger"); +} + +static int create_debugfs_entries(struct smb1351_charger *chip) +{ + struct dentry *ent; + + chip->debug_root = debugfs_create_dir("smb1351", NULL); + if (!chip->debug_root) { + pr_err("Couldn't create debug dir\n"); + } else { + ent = debugfs_create_file("config_registers", S_IFREG | 0444, + chip->debug_root, chip, + &cnfg_debugfs_ops); + if (!ent) + pr_err("Couldn't create cnfg debug file\n"); + + ent = debugfs_create_file("status_registers", S_IFREG | 0444, + chip->debug_root, chip, + &status_debugfs_ops); + if (!ent) + pr_err("Couldn't create status debug file\n"); + + ent = debugfs_create_file("cmd_registers", S_IFREG | 0444, + chip->debug_root, chip, + &cmd_debugfs_ops); + if (!ent) + pr_err("Couldn't create cmd debug file\n"); + + ent = debugfs_create_x32("address", S_IFREG | 0644, + chip->debug_root, + &(chip->peek_poke_address)); + if (!ent) + pr_err("Couldn't create address debug file\n"); + + ent = debugfs_create_file("data", S_IFREG | 0644, + chip->debug_root, chip, + &poke_poke_debug_ops); + if (!ent) + pr_err("Couldn't create data debug file\n"); + + ent = debugfs_create_file("force_irq", + S_IFREG | 0644, + chip->debug_root, chip, + &force_irq_ops); + if (!ent) + pr_err("Couldn't create data debug file\n"); + + ent = debugfs_create_file("irq_count", S_IFREG | 0444, + chip->debug_root, chip, + &irq_count_debugfs_ops); + if (!ent) + pr_err("Couldn't create count debug file\n"); + } + return 0; +} + +static int smb1351_main_charger_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int rc; + struct smb1351_charger *chip; + struct power_supply_config batt_psy_cfg = {}; + struct power_supply_config usb_psy_cfg = {}; + u8 reg = 0; + + chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->client = client; + chip->dev = &client->dev; + chip->fake_battery_soc = -EINVAL; + + chip->extcon = devm_extcon_dev_allocate(chip->dev, + smb1351_extcon_cable); + if (IS_ERR(chip->extcon)) { + pr_err("failed to allocate extcon device\n"); + rc = PTR_ERR(chip->extcon); + return rc; + } + + rc = devm_extcon_dev_register(chip->dev, chip->extcon); + if (rc) { + pr_err("failed to register extcon device\n"); + return rc; + } + + rc = extcon_set_property_capability(chip->extcon, + EXTCON_USB, EXTCON_PROP_USB_SS); + rc |= extcon_set_property_capability(chip->extcon, + EXTCON_USB_HOST, EXTCON_PROP_USB_SS); + if (rc < 0) { + pr_err("Failed to register extcon capability rc=%d\n", rc); + return rc; + } + + chip->usb_psy_d.name = "usb"; + chip->usb_psy_d.type = POWER_SUPPLY_TYPE_USB; + chip->usb_psy_d.get_property = smb1351_usb_get_property; + chip->usb_psy_d.set_property = smb1351_usb_set_property; + chip->usb_psy_d.properties = smb1351_usb_properties; + chip->usb_psy_d.num_properties = ARRAY_SIZE(smb1351_usb_properties); + chip->usb_psy_d.property_is_writeable = smb1351_usb_is_writeable; + + usb_psy_cfg.drv_data = chip; + usb_psy_cfg.supplied_to = smb1351_usb_supplicants; + usb_psy_cfg.num_supplicants = ARRAY_SIZE(smb1351_usb_supplicants); + + chip->usb_psy = devm_power_supply_register(chip->dev, + &chip->usb_psy_d, &usb_psy_cfg); + if (IS_ERR(chip->usb_psy)) { + pr_err("Unable to register usb_psy rc = %ld\n", + PTR_ERR(chip->usb_psy)); + rc = PTR_ERR(chip->usb_psy); + return rc; + } + + INIT_DELAYED_WORK(&chip->chg_remove_work, smb1351_chg_remove_work); + INIT_DELAYED_WORK(&chip->hvdcp_det_work, smb1351_hvdcp_det_work); + device_init_wakeup(chip->dev, true); + /* probe the device to check if its actually connected */ + rc = smb1351_read_reg(chip, CHG_REVISION_REG, ®); + if (rc) { + pr_err("Failed to detect smb1351, device may be absent\n"); + return -ENODEV; + } + pr_debug("smb1351 chip revision is %d\n", reg); + + rc = smb1351_parse_dt(chip); + if (rc) { + pr_err("Couldn't parse DT nodes rc=%d\n", rc); + return rc; + } + + i2c_set_clientdata(client, chip); + + chip->batt_psy_d.name = "battery"; + chip->batt_psy_d.type = POWER_SUPPLY_TYPE_BATTERY; + chip->batt_psy_d.get_property = smb1351_battery_get_property; + chip->batt_psy_d.set_property = smb1351_battery_set_property; + chip->batt_psy_d.property_is_writeable = + smb1351_batt_property_is_writeable; + chip->batt_psy_d.properties = smb1351_battery_properties; + chip->batt_psy_d.num_properties = + ARRAY_SIZE(smb1351_battery_properties); + + chip->resume_completed = true; + mutex_init(&chip->irq_complete); + + batt_psy_cfg.drv_data = chip; + batt_psy_cfg.supplied_to = pm_batt_supplied_to; + batt_psy_cfg.num_supplicants = ARRAY_SIZE(pm_batt_supplied_to); + chip->batt_psy = devm_power_supply_register(chip->dev, + &chip->batt_psy_d, + &batt_psy_cfg); + if (IS_ERR(chip->batt_psy)) { + pr_err("Couldn't register batt psy rc=%ld\n", + PTR_ERR(chip->batt_psy)); + return rc; + } + + dump_regs(chip); + + rc = smb1351_regulator_init(chip); + if (rc) { + pr_err("Couldn't initialize smb1351 ragulator rc=%d\n", rc); + goto fail_smb1351_regulator_init; + } + + rc = smb1351_hw_init(chip); + if (rc) { + pr_err("Couldn't initialize hardware rc=%d\n", rc); + goto fail_smb1351_hw_init; + } + + rc = smb1351_determine_initial_state(chip); + if (rc) { + pr_err("Couldn't determine initial state rc=%d\n", rc); + goto fail_smb1351_hw_init; + } + + /* STAT irq configuration */ + if (client->irq) { + rc = devm_request_threaded_irq(&client->dev, client->irq, NULL, + smb1351_chg_stat_handler, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + "smb1351_chg_stat_irq", chip); + if (rc) { + pr_err("Failed STAT irq=%d request rc = %d\n", + client->irq, rc); + goto fail_smb1351_hw_init; + } + enable_irq_wake(client->irq); + } + + create_debugfs_entries(chip); + + dump_regs(chip); + + pr_info("smb1351 successfully probed. charger=%d, batt=%d version=%s\n", + chip->chg_present, + smb1351_get_prop_batt_present(chip), + smb1351_version_str[chip->version]); + return 0; + +fail_smb1351_hw_init: + regulator_unregister(chip->otg_vreg.rdev); +fail_smb1351_regulator_init: + return rc; +} + +static int smb1351_parallel_charger_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int rc; + struct smb1351_charger *chip; + struct device_node *node = client->dev.of_node; + struct power_supply_config parallel_psy_cfg = {}; + + chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->client = client; + chip->dev = &client->dev; + chip->parallel_charger = true; + chip->parallel_charger_suspended = true; + + chip->usb_suspended_status = of_property_read_bool(node, + "qcom,charging-disabled"); + rc = of_property_read_u32(node, "qcom,float-voltage-mv", + &chip->vfloat_mv); + if (rc) + chip->vfloat_mv = -EINVAL; + rc = of_property_read_u32(node, "qcom,recharge-mv", + &chip->recharge_mv); + if (rc) + chip->recharge_mv = -EINVAL; + + rc = of_property_read_u32(node, "qcom,parallel-en-pin-polarity", + &chip->parallel_pin_polarity_setting); + if (rc) + chip->parallel_pin_polarity_setting = EN_BY_PIN_LOW_ENABLE; + else + chip->parallel_pin_polarity_setting = + chip->parallel_pin_polarity_setting ? + EN_BY_PIN_HIGH_ENABLE : EN_BY_PIN_LOW_ENABLE; + + if (of_property_read_bool(node, + "qcom,parallel-external-current-sense")) + chip->parallel_mode = POWER_SUPPLY_PL_USBIN_USBIN_EXT; + else + chip->parallel_mode = POWER_SUPPLY_PL_USBIN_USBIN; + + chip->pl_batfet_mode = POWER_SUPPLY_PL_NON_STACKED_BATFET; + if (of_property_read_bool(node, "qcom,stacked-batfet")) + chip->pl_batfet_mode = POWER_SUPPLY_PL_STACKED_BATFET; + + i2c_set_clientdata(client, chip); + + chip->parallel_psy_d.name = "parallel"; + chip->parallel_psy_d.type = POWER_SUPPLY_TYPE_PARALLEL; + chip->parallel_psy_d.get_property = smb1351_parallel_get_property; + chip->parallel_psy_d.set_property = smb1351_parallel_set_property; + chip->parallel_psy_d.properties = smb1351_parallel_properties; + chip->parallel_psy_d.property_is_writeable + = smb1351_parallel_is_writeable; + chip->parallel_psy_d.num_properties + = ARRAY_SIZE(smb1351_parallel_properties); + + parallel_psy_cfg.drv_data = chip; + parallel_psy_cfg.num_supplicants = 0; + chip->parallel_psy = devm_power_supply_register(chip->dev, + &chip->parallel_psy_d, + ¶llel_psy_cfg); + if (IS_ERR(chip->parallel_psy)) { + pr_err("Couldn't register parallel psy rc=%ld\n", + PTR_ERR(chip->parallel_psy)); + return rc; + } + + chip->resume_completed = true; + mutex_init(&chip->irq_complete); + + create_debugfs_entries(chip); + + pr_info("smb1351 parallel successfully probed.\n"); + + return 0; +} + +static int smb1351_charger_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + if (is_parallel_charger(client)) + return smb1351_parallel_charger_probe(client, id); + else + return smb1351_main_charger_probe(client, id); +} + +static int smb1351_charger_remove(struct i2c_client *client) +{ + struct smb1351_charger *chip = i2c_get_clientdata(client); + + cancel_delayed_work_sync(&chip->chg_remove_work); + + mutex_destroy(&chip->irq_complete); + debugfs_remove_recursive(chip->debug_root); + return 0; +} + +static int smb1351_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct smb1351_charger *chip = i2c_get_clientdata(client); + + /* no suspend resume activities for parallel charger */ + if (chip->parallel_charger) + return 0; + + mutex_lock(&chip->irq_complete); + chip->resume_completed = false; + mutex_unlock(&chip->irq_complete); + + return 0; +} + +static int smb1351_suspend_noirq(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct smb1351_charger *chip = i2c_get_clientdata(client); + + /* no suspend resume activities for parallel charger */ + if (chip->parallel_charger) + return 0; + + if (chip->irq_waiting) { + pr_err_ratelimited("Aborting suspend, an interrupt was detected while suspending\n"); + return -EBUSY; + } + return 0; +} + +static int smb1351_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct smb1351_charger *chip = i2c_get_clientdata(client); + + /* no suspend resume activities for parallel charger */ + if (chip->parallel_charger) + return 0; + + mutex_lock(&chip->irq_complete); + chip->resume_completed = true; + if (chip->irq_waiting) { + mutex_unlock(&chip->irq_complete); + smb1351_chg_stat_handler(client->irq, chip); + enable_irq(client->irq); + } else { + mutex_unlock(&chip->irq_complete); + } + return 0; +} + +static const struct dev_pm_ops smb1351_pm_ops = { + .suspend = smb1351_suspend, + .suspend_noirq = smb1351_suspend_noirq, + .resume = smb1351_resume, +}; + +static const struct of_device_id smb1351_match_table[] = { + { .compatible = "qcom,smb1351-charger",}, + { }, +}; + +static const struct i2c_device_id smb1351_charger_id[] = { + {"smb1351-charger", 0}, + {}, +}; +MODULE_DEVICE_TABLE(i2c, smb1351_charger_id); + +static struct i2c_driver smb1351_charger_driver = { + .driver = { + .name = "smb1351-charger", + .of_match_table = smb1351_match_table, + .pm = &smb1351_pm_ops, + }, + .probe = smb1351_charger_probe, + .remove = smb1351_charger_remove, + .id_table = smb1351_charger_id, +}; + +module_i2c_driver(smb1351_charger_driver); + +MODULE_DESCRIPTION("smb1351 Charger"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("i2c:smb1351-charger"); diff --git a/drivers/power/supply/qcom/smb138x-charger.c b/drivers/power/supply/qcom/smb138x-charger.c new file mode 100644 index 000000000000..b9c3e177690c --- /dev/null +++ b/drivers/power/supply/qcom/smb138x-charger.c @@ -0,0 +1,1951 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2016-2018, 2020, The Linux Foundation. All rights reserved. + */ + +#define pr_fmt(fmt) "SMB138X: %s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "smb-reg.h" +#include "smb-lib.h" +#include "storm-watch.h" +#include + +#define SMB138X_DEFAULT_FCC_UA 1000000 +#define SMB138X_DEFAULT_ICL_UA 1500000 + +/* Registers that are not common to be mentioned in smb-reg.h */ +#define SMB2CHG_MISC_ENG_SDCDC_CFG2 (MISC_BASE + 0xC1) +#define ENG_SDCDC_SEL_OOB_VTH_BIT BIT(0) + +#define SMB2CHG_MISC_ENG_SDCDC_CFG6 (MISC_BASE + 0xC5) +#define DEAD_TIME_MASK GENMASK(7, 4) +#define HIGH_DEAD_TIME_MASK GENMASK(7, 4) + +#define SMB2CHG_DC_TM_SREFGEN (DCIN_BASE + 0xE2) +#define STACKED_DIODE_EN_BIT BIT(2) + +#define TDIE_AVG_COUNT 10 +#define MAX_SPEED_READING_TIMES 5 + +enum { + OOB_COMP_WA_BIT = BIT(0), +}; + +static struct smb_params v1_params = { + .fcc = { + .name = "fast charge current", + .reg = FAST_CHARGE_CURRENT_CFG_REG, + .min_u = 0, + .max_u = 6000000, + .step_u = 25000, + }, + .fv = { + .name = "float voltage", + .reg = FLOAT_VOLTAGE_CFG_REG, + .min_u = 2450000, + .max_u = 4950000, + .step_u = 10000, + }, + .usb_icl = { + .name = "usb input current limit", + .reg = USBIN_CURRENT_LIMIT_CFG_REG, + .min_u = 0, + .max_u = 6000000, + .step_u = 25000, + }, + .dc_icl = { + .name = "dc input current limit", + .reg = DCIN_CURRENT_LIMIT_CFG_REG, + .min_u = 0, + .max_u = 6000000, + .step_u = 25000, + }, + .freq_buck = { + .name = "buck switching frequency", + .reg = CFG_BUCKBOOST_FREQ_SELECT_BUCK_REG, + .min_u = 500, + .max_u = 2000, + .step_u = 100, + }, +}; + +struct smb_dt_props { + bool suspend_input; + int fcc_ua; + int usb_icl_ua; + int dc_icl_ua; + int chg_temp_max_mdegc; + int connector_temp_max_mdegc; + int pl_mode; + int pl_batfet_mode; +}; + +struct smb138x { + struct smb_charger chg; + struct dentry *dfs_root; + struct smb_dt_props dt; + struct power_supply *parallel_psy; + u32 wa_flags; +}; + +static int __debug_mask; + +static int __try_sink_enabled; +static ssize_t try_sink_enabled_show(struct device *dev, struct device_attribute + *attr, char *buf) +{ + return scnprintf(buf, PAGE_SIZE, "%d\n", __try_sink_enabled); +} + +static ssize_t try_sink_enabled_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int val; + + if (kstrtos32(buf, 0, &val)) + return -EINVAL; + + __try_sink_enabled = val; + + return count; +} +static DEVICE_ATTR_RW(try_sink_enabled); + +static int __audio_headset_drp_wait_ms = 100; +static ssize_t audio_headset_drp_wait_ms_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return scnprintf(buf, PAGE_SIZE, "%d\n", __audio_headset_drp_wait_ms); +} + +static ssize_t audio_headset_drp_wait_ms_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int val; + + if (kstrtos32(buf, 0, &val)) + return -EINVAL; + + __audio_headset_drp_wait_ms = val; + + return count; +} +static DEVICE_ATTR_RW(audio_headset_drp_wait_ms); + +static struct attribute *smb138x_attrs[] = { + &dev_attr_try_sink_enabled.attr, + &dev_attr_audio_headset_drp_wait_ms.attr, + NULL, +}; +ATTRIBUTE_GROUPS(smb138x); + +static irqreturn_t smb138x_handle_slave_chg_state_change(int irq, void *data) +{ + struct smb_irq_data *irq_data = data; + struct smb138x *chip = irq_data->parent_data; + + if (chip->parallel_psy) + power_supply_changed(chip->parallel_psy); + + return IRQ_HANDLED; +} + +static int smb138x_get_prop_charger_temp(struct smb138x *chip, + union power_supply_propval *val) +{ + union power_supply_propval pval; + int rc = 0, avg = 0, i; + struct smb_charger *chg = &chip->chg; + int die_avg_count; + + if (chg->temp_speed_reading_count < MAX_SPEED_READING_TIMES) { + chg->temp_speed_reading_count++; + die_avg_count = 1; + } else { + die_avg_count = TDIE_AVG_COUNT; + } + + for (i = 0; i < die_avg_count; i++) { + pval.intval = 0; + rc = smblib_get_prop_charger_temp(chg, &pval); + if (rc < 0) { + pr_err("Couldnt read chg temp at %dth iteration rc = %d\n", + i + 1, rc); + return rc; + } + avg += pval.intval; + } + val->intval = avg / die_avg_count; + return rc; +} + +static int smb138x_parse_dt(struct smb138x *chip) +{ + struct smb_charger *chg = &chip->chg; + struct device_node *node = chg->dev->of_node; + int rc; + + if (!node) { + pr_err("device tree node missing\n"); + return -EINVAL; + } + + rc = of_property_read_u32(node, + "qcom,parallel-mode", &chip->dt.pl_mode); + if (rc < 0) + chip->dt.pl_mode = POWER_SUPPLY_PL_USBMID_USBMID; + + chip->dt.suspend_input = of_property_read_bool(node, + "qcom,suspend-input"); + + chg->use_extcon = of_property_read_bool(node, + "qcom,use-extcon"); + + rc = of_property_read_u32(node, + "qcom,fcc-max-ua", &chip->dt.fcc_ua); + if (rc < 0) + chip->dt.fcc_ua = SMB138X_DEFAULT_FCC_UA; + + rc = of_property_read_u32(node, + "qcom,usb-icl-ua", &chip->dt.usb_icl_ua); + if (rc < 0) + chip->dt.usb_icl_ua = SMB138X_DEFAULT_ICL_UA; + + rc = of_property_read_u32(node, + "qcom,dc-icl-ua", &chip->dt.dc_icl_ua); + if (rc < 0) + chip->dt.dc_icl_ua = SMB138X_DEFAULT_ICL_UA; + + rc = of_property_read_u32(node, + "qcom,charger-temp-max-mdegc", + &chip->dt.chg_temp_max_mdegc); + if (rc < 0) + chip->dt.chg_temp_max_mdegc = 80000; + + rc = of_property_read_u32(node, + "qcom,connector-temp-max-mdegc", + &chip->dt.connector_temp_max_mdegc); + if (rc < 0) + chip->dt.connector_temp_max_mdegc = 105000; + + chip->dt.pl_batfet_mode = POWER_SUPPLY_PL_NON_STACKED_BATFET; + if (of_property_read_bool(node, "qcom,stacked-batfet")) + chip->dt.pl_batfet_mode = POWER_SUPPLY_PL_STACKED_BATFET; + + return 0; +} + +/************************ + * USB PSY REGISTRATION * + ************************/ + +static enum power_supply_property smb138x_usb_props[] = { + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_MIN, + POWER_SUPPLY_PROP_VOLTAGE_MAX, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_MAX, + POWER_SUPPLY_PROP_TYPE, + POWER_SUPPLY_PROP_REAL_TYPE, + POWER_SUPPLY_PROP_TYPEC_MODE, + POWER_SUPPLY_PROP_TYPEC_POWER_ROLE, + POWER_SUPPLY_PROP_TYPEC_CC_ORIENTATION, + POWER_SUPPLY_PROP_SDP_CURRENT_MAX, +}; + +static int smb138x_usb_get_prop(struct power_supply *psy, + enum power_supply_property prop, + union power_supply_propval *val) +{ + struct smb138x *chip = power_supply_get_drvdata(psy); + struct smb_charger *chg = &chip->chg; + int rc = 0; + + switch (prop) { + case POWER_SUPPLY_PROP_PRESENT: + rc = smblib_get_prop_usb_present(chg, val); + break; + case POWER_SUPPLY_PROP_ONLINE: + rc = smblib_get_prop_usb_online(chg, val); + break; + case POWER_SUPPLY_PROP_VOLTAGE_MIN: + val->intval = chg->voltage_min_uv; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + val->intval = chg->voltage_max_uv; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + rc = smblib_get_prop_usb_voltage_now(chg, val); + break; + case POWER_SUPPLY_PROP_CURRENT_MAX: + val->intval = get_effective_result(chg->usb_icl_votable); + break; + case POWER_SUPPLY_PROP_TYPE: + val->intval = chg->usb_psy_desc.type; + break; + case POWER_SUPPLY_PROP_REAL_TYPE: + val->intval = chg->real_charger_type; + break; + case POWER_SUPPLY_PROP_TYPEC_MODE: + val->intval = chg->typec_mode; + break; + case POWER_SUPPLY_PROP_TYPEC_POWER_ROLE: + rc = smblib_get_prop_typec_power_role(chg, val); + break; + case POWER_SUPPLY_PROP_TYPEC_CC_ORIENTATION: + rc = smblib_get_prop_typec_cc_orientation(chg, val); + break; + case POWER_SUPPLY_PROP_SDP_CURRENT_MAX: + val->intval = get_client_vote(chg->usb_icl_votable, + USB_PSY_VOTER); + break; + default: + pr_err("get prop %d is not supported\n", prop); + return -EINVAL; + } + + if (rc < 0) { + pr_debug("Couldn't get prop %d rc = %d\n", prop, rc); + return -ENODATA; + } + + return rc; +} + +static int smb138x_usb_set_prop(struct power_supply *psy, + enum power_supply_property prop, + const union power_supply_propval *val) +{ + struct smb138x *chip = power_supply_get_drvdata(psy); + struct smb_charger *chg = &chip->chg; + int rc = 0; + + switch (prop) { + case POWER_SUPPLY_PROP_TYPEC_POWER_ROLE: + rc = smblib_set_prop_typec_power_role(chg, val); + break; + case POWER_SUPPLY_PROP_SDP_CURRENT_MAX: + rc = smblib_set_prop_sdp_current_max(chg, val); + break; + default: + pr_err("set prop %d is not supported\n", prop); + return -EINVAL; + } + + return rc; +} + +static int smb138x_usb_prop_is_writeable(struct power_supply *psy, + enum power_supply_property prop) +{ + return 0; +} + +static int smb138x_init_usb_psy(struct smb138x *chip) +{ + struct power_supply_config usb_cfg = {}; + struct smb_charger *chg = &chip->chg; + + chg->usb_psy_desc.name = "usb"; + chg->usb_psy_desc.type = POWER_SUPPLY_TYPE_UNKNOWN; + chg->usb_psy_desc.properties = smb138x_usb_props; + chg->usb_psy_desc.num_properties = ARRAY_SIZE(smb138x_usb_props); + chg->usb_psy_desc.get_property = smb138x_usb_get_prop; + chg->usb_psy_desc.set_property = smb138x_usb_set_prop; + chg->usb_psy_desc.property_is_writeable = smb138x_usb_prop_is_writeable; + + usb_cfg.drv_data = chip; + usb_cfg.of_node = chg->dev->of_node; + chg->usb_psy = devm_power_supply_register(chg->dev, + &chg->usb_psy_desc, + &usb_cfg); + if (IS_ERR(chg->usb_psy)) { + pr_err("Couldn't register USB power supply\n"); + return PTR_ERR(chg->usb_psy); + } + + return 0; +} + +/***************************** + * USB MAIN PSY REGISTRATION * + *****************************/ + +static enum power_supply_property smb138x_usb_main_props[] = { + POWER_SUPPLY_PROP_VOLTAGE_MAX, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, + POWER_SUPPLY_PROP_TYPE, + POWER_SUPPLY_PROP_INPUT_CURRENT_SETTLED, + POWER_SUPPLY_PROP_INPUT_VOLTAGE_SETTLED, + POWER_SUPPLY_PROP_FCC_DELTA, + POWER_SUPPLY_PROP_CURRENT_MAX, +}; + +static int smb138x_usb_main_get_prop(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct smb138x *chip = power_supply_get_drvdata(psy); + struct smb_charger *chg = &chip->chg; + int rc = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + rc = smblib_get_charge_param(chg, &chg->param.fv, &val->intval); + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: + rc = smblib_get_charge_param(chg, &chg->param.fcc, + &val->intval); + break; + case POWER_SUPPLY_PROP_TYPE: + val->intval = POWER_SUPPLY_TYPE_MAIN; + break; + case POWER_SUPPLY_PROP_INPUT_CURRENT_SETTLED: + rc = smblib_get_prop_input_current_settled(chg, val); + break; + case POWER_SUPPLY_PROP_INPUT_VOLTAGE_SETTLED: + rc = smblib_get_prop_input_voltage_settled(chg, val); + break; + case POWER_SUPPLY_PROP_FCC_DELTA: + rc = smblib_get_prop_fcc_delta(chg, val); + break; + case POWER_SUPPLY_PROP_CURRENT_MAX: + rc = smblib_get_icl_current(chg, &val->intval); + break; + default: + pr_debug("get prop %d is not supported in usb-main\n", psp); + rc = -EINVAL; + break; + } + + if (rc < 0) { + pr_debug("Couldn't get prop %d rc = %d\n", psp, rc); + return -ENODATA; + } + return 0; +} + +static int smb138x_usb_main_set_prop(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct smb138x *chip = power_supply_get_drvdata(psy); + struct smb_charger *chg = &chip->chg; + int rc = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + rc = smblib_set_charge_param(chg, &chg->param.fv, val->intval); + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: + rc = smblib_set_charge_param(chg, &chg->param.fcc, val->intval); + break; + case POWER_SUPPLY_PROP_CURRENT_MAX: + rc = smblib_set_icl_current(chg, val->intval); + break; + default: + rc = -EINVAL; + break; + } + + if (rc < 0) + pr_err("Couldn't set prop %d, rc=%d\n", psp, rc); + + return rc; +} + +static const struct power_supply_desc usb_main_psy_desc = { + .name = "main", + .type = POWER_SUPPLY_TYPE_MAIN, + .properties = smb138x_usb_main_props, + .num_properties = ARRAY_SIZE(smb138x_usb_main_props), + .get_property = smb138x_usb_main_get_prop, + .set_property = smb138x_usb_main_set_prop, +}; + +static int smb138x_init_usb_main_psy(struct smb138x *chip) +{ + struct power_supply_config usb_main_cfg = {}; + struct smb_charger *chg = &chip->chg; + + usb_main_cfg.drv_data = chip; + usb_main_cfg.of_node = chg->dev->of_node; + chg->usb_main_psy = devm_power_supply_register(chg->dev, + &usb_main_psy_desc, + &usb_main_cfg); + if (IS_ERR(chg->usb_main_psy)) { + pr_err("Couldn't register USB main power supply\n"); + return PTR_ERR(chg->usb_main_psy); + } + + return 0; +} + +/************************* + * BATT PSY REGISTRATION * + *************************/ + +static enum power_supply_property smb138x_batt_props[] = { + POWER_SUPPLY_PROP_INPUT_SUSPEND, + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_CHARGE_TYPE, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_CHARGER_TEMP, + POWER_SUPPLY_PROP_CHARGER_TEMP_MAX, + POWER_SUPPLY_PROP_SET_SHIP_MODE, +}; + +static int smb138x_batt_get_prop(struct power_supply *psy, + enum power_supply_property prop, + union power_supply_propval *val) +{ + struct smb138x *chip = power_supply_get_drvdata(psy); + struct smb_charger *chg = &chip->chg; + int rc = 0; + + switch (prop) { + case POWER_SUPPLY_PROP_STATUS: + rc = smblib_get_prop_batt_status(chg, val); + break; + case POWER_SUPPLY_PROP_HEALTH: + rc = smblib_get_prop_batt_health(chg, val); + break; + case POWER_SUPPLY_PROP_PRESENT: + rc = smblib_get_prop_batt_present(chg, val); + break; + case POWER_SUPPLY_PROP_INPUT_SUSPEND: + rc = smblib_get_prop_input_suspend(chg, val); + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + rc = smblib_get_prop_batt_charge_type(chg, val); + break; + case POWER_SUPPLY_PROP_CAPACITY: + rc = smblib_get_prop_batt_capacity(chg, val); + break; + case POWER_SUPPLY_PROP_CHARGER_TEMP: + rc = smb138x_get_prop_charger_temp(chip, val); + break; + case POWER_SUPPLY_PROP_CHARGER_TEMP_MAX: + rc = smblib_get_prop_charger_temp_max(chg, val); + break; + case POWER_SUPPLY_PROP_SET_SHIP_MODE: + /* Not in ship mode as long as device is active */ + val->intval = 0; + break; + default: + pr_err("batt power supply get prop %d not supported\n", prop); + return -EINVAL; + } + + if (rc < 0) { + pr_debug("Couldn't get prop %d rc = %d\n", prop, rc); + return -ENODATA; + } + + return rc; +} + +static int smb138x_batt_set_prop(struct power_supply *psy, + enum power_supply_property prop, + const union power_supply_propval *val) +{ + struct smb138x *chip = power_supply_get_drvdata(psy); + struct smb_charger *chg = &chip->chg; + int rc = 0; + + switch (prop) { + case POWER_SUPPLY_PROP_INPUT_SUSPEND: + rc = smblib_set_prop_input_suspend(chg, val); + break; + case POWER_SUPPLY_PROP_CAPACITY: + rc = smblib_set_prop_batt_capacity(chg, val); + break; + case POWER_SUPPLY_PROP_SET_SHIP_MODE: + /* Not in ship mode as long as the device is active */ + if (!val->intval) + break; + rc = smblib_set_prop_ship_mode(chg, val); + break; + default: + pr_err("batt power supply set prop %d not supported\n", prop); + return -EINVAL; + } + + return rc; +} + +static int smb138x_batt_prop_is_writeable(struct power_supply *psy, + enum power_supply_property prop) +{ + switch (prop) { + case POWER_SUPPLY_PROP_INPUT_SUSPEND: + case POWER_SUPPLY_PROP_CAPACITY: + return 1; + default: + break; + } + + return 0; +} + +static const struct power_supply_desc batt_psy_desc = { + .name = "battery", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = smb138x_batt_props, + .num_properties = ARRAY_SIZE(smb138x_batt_props), + .get_property = smb138x_batt_get_prop, + .set_property = smb138x_batt_set_prop, + .property_is_writeable = smb138x_batt_prop_is_writeable, +}; + +static int smb138x_init_batt_psy(struct smb138x *chip) +{ + struct power_supply_config batt_cfg = {}; + struct smb_charger *chg = &chip->chg; + int rc = 0; + + batt_cfg.drv_data = chip; + batt_cfg.of_node = chg->dev->of_node; + chg->batt_psy = devm_power_supply_register(chg->dev, + &batt_psy_desc, + &batt_cfg); + if (IS_ERR(chg->batt_psy)) { + pr_err("Couldn't register battery power supply\n"); + return PTR_ERR(chg->batt_psy); + } + + return rc; +} + +/***************************** + * PARALLEL PSY REGISTRATION * + *****************************/ + +static int smb138x_get_prop_connector_health(struct smb138x *chip) +{ + struct smb_charger *chg = &chip->chg; + int rc, lb_mdegc, ub_mdegc, rst_mdegc, connector_mdegc; + + if (!chg->iio.connector_temp_chan || + PTR_ERR(chg->iio.connector_temp_chan) == -EPROBE_DEFER) + chg->iio.connector_temp_chan = iio_channel_get(chg->dev, + "connector_temp"); + + if (IS_ERR(chg->iio.connector_temp_chan)) + return POWER_SUPPLY_HEALTH_UNKNOWN; + + rc = iio_read_channel_processed(chg->iio.connector_temp_thr1_chan, + &lb_mdegc); + if (rc < 0) { + pr_err("Couldn't read connector lower bound rc=%d\n", rc); + return POWER_SUPPLY_HEALTH_UNKNOWN; + } + + rc = iio_read_channel_processed(chg->iio.connector_temp_thr2_chan, + &ub_mdegc); + if (rc < 0) { + pr_err("Couldn't read connector upper bound rc=%d\n", rc); + return POWER_SUPPLY_HEALTH_UNKNOWN; + } + + rc = iio_read_channel_processed(chg->iio.connector_temp_thr3_chan, + &rst_mdegc); + if (rc < 0) { + pr_err("Couldn't read connector reset bound rc=%d\n", rc); + return POWER_SUPPLY_HEALTH_UNKNOWN; + } + + rc = iio_read_channel_processed(chg->iio.connector_temp_chan, + &connector_mdegc); + if (rc < 0) { + pr_err("Couldn't read connector temperature rc=%d\n", rc); + return POWER_SUPPLY_HEALTH_UNKNOWN; + } + + if (connector_mdegc < lb_mdegc) + return POWER_SUPPLY_HEALTH_COOL; + else if (connector_mdegc < ub_mdegc) + return POWER_SUPPLY_HEALTH_WARM; + else if (connector_mdegc < rst_mdegc) + return POWER_SUPPLY_HEALTH_HOT; + + return POWER_SUPPLY_HEALTH_OVERHEAT; +} + +static enum power_supply_property smb138x_parallel_props[] = { + POWER_SUPPLY_PROP_CHARGE_TYPE, + POWER_SUPPLY_PROP_CHARGING_ENABLED, + POWER_SUPPLY_PROP_PIN_ENABLED, + POWER_SUPPLY_PROP_INPUT_SUSPEND, + POWER_SUPPLY_PROP_INPUT_CURRENT_LIMITED, + POWER_SUPPLY_PROP_CURRENT_MAX, + POWER_SUPPLY_PROP_VOLTAGE_MAX, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CHARGER_TEMP, + POWER_SUPPLY_PROP_CHARGER_TEMP_MAX, + POWER_SUPPLY_PROP_MODEL_NAME, + POWER_SUPPLY_PROP_PARALLEL_MODE, + POWER_SUPPLY_PROP_CONNECTOR_HEALTH, + POWER_SUPPLY_PROP_SET_SHIP_MODE, + POWER_SUPPLY_PROP_PARALLEL_BATFET_MODE, +}; + +static int smb138x_parallel_get_prop(struct power_supply *psy, + enum power_supply_property prop, + union power_supply_propval *val) +{ + struct smb138x *chip = power_supply_get_drvdata(psy); + struct smb_charger *chg = &chip->chg; + int rc = 0; + u8 temp; + + switch (prop) { + case POWER_SUPPLY_PROP_CHARGE_TYPE: + rc = smblib_get_prop_batt_charge_type(chg, val); + break; + case POWER_SUPPLY_PROP_CHARGING_ENABLED: + rc = smblib_read(chg, BATTERY_CHARGER_STATUS_5_REG, + &temp); + if (rc >= 0) + val->intval = (bool)(temp & CHARGING_ENABLE_BIT); + break; + case POWER_SUPPLY_PROP_PIN_ENABLED: + rc = smblib_read(chg, BATTERY_CHARGER_STATUS_5_REG, + &temp); + if (rc >= 0) + val->intval = !(temp & DISABLE_CHARGING_BIT); + break; + case POWER_SUPPLY_PROP_INPUT_SUSPEND: + rc = smblib_get_usb_suspend(chg, &val->intval); + break; + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMITED: + if ((chip->dt.pl_mode == POWER_SUPPLY_PL_USBIN_USBIN) + || (chip->dt.pl_mode == POWER_SUPPLY_PL_USBIN_USBIN_EXT)) + rc = smblib_get_prop_input_current_limited(chg, val); + else + val->intval = 0; + break; + case POWER_SUPPLY_PROP_CURRENT_MAX: + if ((chip->dt.pl_mode == POWER_SUPPLY_PL_USBIN_USBIN) + || (chip->dt.pl_mode == POWER_SUPPLY_PL_USBIN_USBIN_EXT)) + rc = smblib_get_charge_param(chg, &chg->param.usb_icl, + &val->intval); + else + val->intval = 0; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + rc = smblib_get_charge_param(chg, &chg->param.fv, &val->intval); + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: + rc = smblib_get_charge_param(chg, &chg->param.fcc, + &val->intval); + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + rc = smblib_get_prop_slave_current_now(chg, val); + break; + case POWER_SUPPLY_PROP_CHARGER_TEMP: + rc = smb138x_get_prop_charger_temp(chip, val); + break; + case POWER_SUPPLY_PROP_CHARGER_TEMP_MAX: + rc = smblib_get_prop_charger_temp_max(chg, val); + break; + case POWER_SUPPLY_PROP_MODEL_NAME: + val->strval = "smb138x"; + break; + case POWER_SUPPLY_PROP_PARALLEL_MODE: + val->intval = chip->dt.pl_mode; + break; + case POWER_SUPPLY_PROP_CONNECTOR_HEALTH: + val->intval = smb138x_get_prop_connector_health(chip); + break; + case POWER_SUPPLY_PROP_SET_SHIP_MODE: + /* Not in ship mode as long as device is active */ + val->intval = 0; + break; + case POWER_SUPPLY_PROP_PARALLEL_BATFET_MODE: + val->intval = chip->dt.pl_batfet_mode; + break; + default: + pr_err("parallel power supply get prop %d not supported\n", + prop); + return -EINVAL; + } + + if (rc < 0) { + pr_debug("Couldn't get prop %d rc = %d\n", prop, rc); + return -ENODATA; + } + + return rc; +} + +static int smb138x_set_parallel_suspend(struct smb138x *chip, bool suspend) +{ + struct smb_charger *chg = &chip->chg; + int rc = 0; + + rc = smblib_masked_write(chg, WD_CFG_REG, WDOG_TIMER_EN_BIT, + suspend ? 0 : WDOG_TIMER_EN_BIT); + if (rc < 0) { + pr_err("Couldn't %s watchdog rc=%d\n", + suspend ? "disable" : "enable", rc); + suspend = true; + } + + rc = smblib_masked_write(chg, USBIN_CMD_IL_REG, USBIN_SUSPEND_BIT, + suspend ? USBIN_SUSPEND_BIT : 0); + if (rc < 0) { + pr_err("Couldn't %s parallel charger rc=%d\n", + suspend ? "suspend" : "resume", rc); + return rc; + } + + return rc; +} + +static int smb138x_parallel_set_prop(struct power_supply *psy, + enum power_supply_property prop, + const union power_supply_propval *val) +{ + struct smb138x *chip = power_supply_get_drvdata(psy); + struct smb_charger *chg = &chip->chg; + int rc = 0; + + switch (prop) { + case POWER_SUPPLY_PROP_INPUT_SUSPEND: + rc = smb138x_set_parallel_suspend(chip, (bool)val->intval); + break; + case POWER_SUPPLY_PROP_CURRENT_MAX: + if ((chip->dt.pl_mode == POWER_SUPPLY_PL_USBIN_USBIN) + || (chip->dt.pl_mode == POWER_SUPPLY_PL_USBIN_USBIN_EXT)) + rc = smblib_set_charge_param(chg, &chg->param.usb_icl, + val->intval); + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + rc = smblib_set_charge_param(chg, &chg->param.fv, val->intval); + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: + rc = smblib_set_charge_param(chg, &chg->param.fcc, val->intval); + break; + case POWER_SUPPLY_PROP_SET_SHIP_MODE: + /* Not in ship mode as long as the device is active */ + if (!val->intval) + break; + rc = smblib_set_prop_ship_mode(chg, val); + break; + default: + pr_debug("parallel power supply set prop %d not supported\n", + prop); + return -EINVAL; + } + + return rc; +} + +static int smb138x_parallel_prop_is_writeable(struct power_supply *psy, + enum power_supply_property prop) +{ + return 0; +} + +static const struct power_supply_desc parallel_psy_desc = { + .name = "parallel", + .type = POWER_SUPPLY_TYPE_PARALLEL, + .properties = smb138x_parallel_props, + .num_properties = ARRAY_SIZE(smb138x_parallel_props), + .get_property = smb138x_parallel_get_prop, + .set_property = smb138x_parallel_set_prop, + .property_is_writeable = smb138x_parallel_prop_is_writeable, +}; + +static int smb138x_init_parallel_psy(struct smb138x *chip) +{ + struct power_supply_config parallel_cfg = {}; + struct smb_charger *chg = &chip->chg; + + parallel_cfg.drv_data = chip; + parallel_cfg.of_node = chg->dev->of_node; + chip->parallel_psy = devm_power_supply_register(chg->dev, + ¶llel_psy_desc, + ¶llel_cfg); + if (IS_ERR(chip->parallel_psy)) { + pr_err("Couldn't register parallel power supply\n"); + return PTR_ERR(chip->parallel_psy); + } + + return 0; +} + +/****************************** + * VBUS REGULATOR REGISTRATION * + ******************************/ + +static struct regulator_ops smb138x_vbus_reg_ops = { + .enable = smblib_vbus_regulator_enable, + .disable = smblib_vbus_regulator_disable, + .is_enabled = smblib_vbus_regulator_is_enabled, +}; + +static int smb138x_init_vbus_regulator(struct smb138x *chip) +{ + struct smb_charger *chg = &chip->chg; + struct regulator_config cfg = {}; + int rc = 0; + + chg->vbus_vreg = devm_kzalloc(chg->dev, sizeof(*chg->vbus_vreg), + GFP_KERNEL); + if (!chg->vbus_vreg) + return -ENOMEM; + + cfg.dev = chg->dev; + cfg.driver_data = chip; + + chg->vbus_vreg->rdesc.owner = THIS_MODULE; + chg->vbus_vreg->rdesc.type = REGULATOR_VOLTAGE; + chg->vbus_vreg->rdesc.ops = &smb138x_vbus_reg_ops; + chg->vbus_vreg->rdesc.of_match = "qcom,smb138x-vbus"; + chg->vbus_vreg->rdesc.name = "qcom,smb138x-vbus"; + + chg->vbus_vreg->rdev = devm_regulator_register(chg->dev, + &chg->vbus_vreg->rdesc, &cfg); + if (IS_ERR(chg->vbus_vreg->rdev)) { + rc = PTR_ERR(chg->vbus_vreg->rdev); + chg->vbus_vreg->rdev = NULL; + if (rc != -EPROBE_DEFER) + pr_err("Couldn't register VBUS regualtor rc=%d\n", rc); + } + + return rc; +} + +/****************************** + * VCONN REGULATOR REGISTRATION * + ******************************/ + +static struct regulator_ops smb138x_vconn_reg_ops = { + .enable = smblib_vconn_regulator_enable, + .disable = smblib_vconn_regulator_disable, + .is_enabled = smblib_vconn_regulator_is_enabled, +}; + +static int smb138x_init_vconn_regulator(struct smb138x *chip) +{ + struct smb_charger *chg = &chip->chg; + struct regulator_config cfg = {}; + int rc = 0; + + chg->vconn_vreg = devm_kzalloc(chg->dev, sizeof(*chg->vconn_vreg), + GFP_KERNEL); + if (!chg->vconn_vreg) + return -ENOMEM; + + cfg.dev = chg->dev; + cfg.driver_data = chip; + + chg->vconn_vreg->rdesc.owner = THIS_MODULE; + chg->vconn_vreg->rdesc.type = REGULATOR_VOLTAGE; + chg->vconn_vreg->rdesc.ops = &smb138x_vconn_reg_ops; + chg->vconn_vreg->rdesc.of_match = "qcom,smb138x-vconn"; + chg->vconn_vreg->rdesc.name = "qcom,smb138x-vconn"; + + chg->vconn_vreg->rdev = devm_regulator_register(chg->dev, + &chg->vconn_vreg->rdesc, &cfg); + if (IS_ERR(chg->vconn_vreg->rdev)) { + rc = PTR_ERR(chg->vconn_vreg->rdev); + chg->vconn_vreg->rdev = NULL; + if (rc != -EPROBE_DEFER) + pr_err("Couldn't register VCONN regualtor rc=%d\n", rc); + } + + return rc; +} + +/*************************** + * HARDWARE INITIALIZATION * + ***************************/ + +#define MDEGC_3 3000 +#define MDEGC_15 15000 +static int smb138x_init_slave_hw(struct smb138x *chip) +{ + struct smb_charger *chg = &chip->chg; + int rc; + + if (chip->wa_flags & OOB_COMP_WA_BIT) { + rc = smblib_masked_write(chg, SMB2CHG_MISC_ENG_SDCDC_CFG2, + ENG_SDCDC_SEL_OOB_VTH_BIT, + ENG_SDCDC_SEL_OOB_VTH_BIT); + if (rc < 0) { + pr_err("Couldn't configure the OOB comp threshold rc = %d\n", + rc); + return rc; + } + + rc = smblib_masked_write(chg, SMB2CHG_MISC_ENG_SDCDC_CFG6, + DEAD_TIME_MASK, HIGH_DEAD_TIME_MASK); + if (rc < 0) { + pr_err("Couldn't configure the sdcdc cfg 6 reg rc = %d\n", + rc); + return rc; + } + } + + /* configure to a fixed 700khz freq to avoid tdie errors */ + rc = smblib_set_charge_param(chg, &chg->param.freq_buck, 700); + if (rc < 0) { + pr_err("Couldn't configure 700Khz switch freq rc=%d\n", rc); + return rc; + } + + /* enable watchdog bark and bite interrupts, and disable the watchdog */ + rc = smblib_masked_write(chg, WD_CFG_REG, WDOG_TIMER_EN_BIT + | WDOG_TIMER_EN_ON_PLUGIN_BIT | BITE_WDOG_INT_EN_BIT + | BARK_WDOG_INT_EN_BIT, + BITE_WDOG_INT_EN_BIT | BARK_WDOG_INT_EN_BIT); + if (rc < 0) { + pr_err("Couldn't configure the watchdog rc=%d\n", rc); + return rc; + } + + /* disable charging when watchdog bites */ + rc = smblib_masked_write(chg, SNARL_BARK_BITE_WD_CFG_REG, + BITE_WDOG_DISABLE_CHARGING_CFG_BIT, + BITE_WDOG_DISABLE_CHARGING_CFG_BIT); + if (rc < 0) { + pr_err("Couldn't configure the watchdog bite rc=%d\n", rc); + return rc; + } + + /* Disable OTG */ + rc = smblib_masked_write(chg, CMD_OTG_REG, OTG_EN_BIT, 0); + if (rc < 0) { + pr_err("Couldn't disable OTG rc=%d\n", rc); + return rc; + } + + /* suspend parallel charging */ + rc = smb138x_set_parallel_suspend(chip, true); + if (rc < 0) { + pr_err("Couldn't suspend parallel charging rc=%d\n", rc); + return rc; + } + + /* initialize FCC to 0 */ + rc = smblib_set_charge_param(chg, &chg->param.fcc, 0); + if (rc < 0) { + pr_err("Couldn't set 0 FCC rc=%d\n", rc); + return rc; + } + + /* enable the charging path */ + rc = smblib_masked_write(chg, CHARGING_ENABLE_CMD_REG, + CHARGING_ENABLE_CMD_BIT, + CHARGING_ENABLE_CMD_BIT); + if (rc < 0) { + pr_err("Couldn't enable charging rc=%d\n", rc); + return rc; + } + + /* configure charge enable for software control; active high */ + rc = smblib_masked_write(chg, CHGR_CFG2_REG, + CHG_EN_POLARITY_BIT | CHG_EN_SRC_BIT, 0); + if (rc < 0) { + pr_err("Couldn't configure charge enable source rc=%d\n", + rc); + return rc; + } + + /* enable parallel current sensing */ + rc = smblib_masked_write(chg, CFG_REG, + VCHG_EN_CFG_BIT, VCHG_EN_CFG_BIT); + if (rc < 0) { + pr_err("Couldn't enable parallel current sensing rc=%d\n", + rc); + return rc; + } + + /* enable stacked diode */ + rc = smblib_write(chg, SMB2CHG_DC_TM_SREFGEN, STACKED_DIODE_EN_BIT); + if (rc < 0) { + pr_err("Couldn't enable stacked diode rc=%d\n", rc); + return rc; + } + + /* initialize charger temperature threshold */ + rc = iio_write_channel_processed(chg->iio.temp_max_chan, + chip->dt.chg_temp_max_mdegc); + if (rc < 0) { + pr_err("Couldn't set charger temp threshold rc=%d\n", rc); + return rc; + } + + rc = iio_write_channel_processed(chg->iio.connector_temp_thr1_chan, + chip->dt.connector_temp_max_mdegc); + if (rc < 0) { + pr_err("Couldn't set connector temp threshold1 rc=%d\n", rc); + return rc; + } + + rc = iio_write_channel_processed(chg->iio.connector_temp_thr2_chan, + chip->dt.connector_temp_max_mdegc + MDEGC_3); + if (rc < 0) { + pr_err("Couldn't set connector temp threshold2 rc=%d\n", rc); + return rc; + } + + rc = iio_write_channel_processed(chg->iio.connector_temp_thr3_chan, + chip->dt.connector_temp_max_mdegc + MDEGC_15); + if (rc < 0) { + pr_err("Couldn't set connector temp threshold3 rc=%d\n", rc); + return rc; + } + + /* increase the concurrent mode threshold */ + rc = smblib_masked_write(chg, ENG_SDCDC_CFG7_REG, + ENG_SDCDC_BST_SET_POINT_MASK, 0); + if (rc < 0) { + pr_err("Couldn't set concurrent mode threshold\n"); + return rc; + } + + return 0; +} + +static int smb138x_init_hw(struct smb138x *chip) +{ + struct smb_charger *chg = &chip->chg; + int rc = 0; + + /* votes must be cast before configuring software control */ + vote(chg->dc_suspend_votable, + DEFAULT_VOTER, chip->dt.suspend_input, 0); + vote(chg->fcc_votable, + DEFAULT_VOTER, true, chip->dt.fcc_ua); + vote(chg->usb_icl_votable, + DCP_VOTER, true, chip->dt.usb_icl_ua); + vote(chg->dc_icl_votable, + DEFAULT_VOTER, true, chip->dt.dc_icl_ua); + + chg->dcp_icl_ua = chip->dt.usb_icl_ua; + + /* Disable OTG */ + rc = smblib_masked_write(chg, CMD_OTG_REG, OTG_EN_BIT, 0); + if (rc < 0) { + pr_err("Couldn't disable OTG rc=%d\n", rc); + return rc; + } + + /* Unsuspend USB input */ + rc = smblib_masked_write(chg, USBIN_CMD_IL_REG, USBIN_SUSPEND_BIT, 0); + if (rc < 0) { + pr_err("Couldn't unsuspend USB, rc=%d\n", rc); + return rc; + } + + /* enable usb-src-change interrupt sources */ + rc = smblib_masked_write(chg, USBIN_SOURCE_CHANGE_INTRPT_ENB_REG, + APSD_IRQ_EN_CFG_BIT | HVDCP_IRQ_EN_CFG_BIT + | AUTH_IRQ_EN_CFG_BIT | VADP_IRQ_EN_CFG_BIT, + APSD_IRQ_EN_CFG_BIT | HVDCP_IRQ_EN_CFG_BIT + | AUTH_IRQ_EN_CFG_BIT | VADP_IRQ_EN_CFG_BIT); + if (rc < 0) { + pr_err("Couldn't configure Type-C interrupts rc=%d\n", rc); + return rc; + } + + /* configure to a fixed 700khz freq to avoid tdie errors */ + rc = smblib_set_charge_param(chg, &chg->param.freq_buck, 700); + if (rc < 0) { + pr_err("Couldn't configure 700Khz switch freq rc=%d\n", rc); + return rc; + } + + /* configure charge enable for software control; active high */ + rc = smblib_masked_write(chg, CHGR_CFG2_REG, + CHG_EN_POLARITY_BIT | CHG_EN_SRC_BIT, 0); + if (rc < 0) { + pr_err("Couldn't configure charge enable source rc=%d\n", rc); + return rc; + } + + /* enable the charging path */ + rc = vote(chg->chg_disable_votable, DEFAULT_VOTER, false, 0); + if (rc < 0) { + pr_err("Couldn't enable charging rc=%d\n", rc); + return rc; + } + + /* + * trigger the usb-typec-change interrupt only when the CC state + * changes, or there was a VBUS error + */ + rc = smblib_write(chg, TYPE_C_INTRPT_ENB_REG, + TYPEC_CCSTATE_CHANGE_INT_EN_BIT + | TYPEC_VBUS_ERROR_INT_EN_BIT); + if (rc < 0) { + pr_err("Couldn't configure Type-C interrupts rc=%d\n", rc); + return rc; + } + + /* configure VCONN for software control */ + rc = smblib_masked_write(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG, + VCONN_EN_SRC_BIT | VCONN_EN_VALUE_BIT, + VCONN_EN_SRC_BIT); + if (rc < 0) { + pr_err("Couldn't configure VCONN for SW control rc=%d\n", rc); + return rc; + } + + /* configure VBUS for software control */ + rc = smblib_masked_write(chg, OTG_CFG_REG, OTG_EN_SRC_CFG_BIT, 0); + if (rc < 0) { + pr_err("Couldn't configure VBUS for SW control rc=%d\n", rc); + return rc; + } + + /* configure power role for dual-role */ + rc = smblib_masked_write(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG, + TYPEC_POWER_ROLE_CMD_MASK, 0); + if (rc < 0) { + pr_err("Couldn't configure power role for DRP rc=%d\n", rc); + return rc; + } + + if (chip->wa_flags & OOB_COMP_WA_BIT) { + rc = smblib_masked_write(chg, SMB2CHG_MISC_ENG_SDCDC_CFG2, + ENG_SDCDC_SEL_OOB_VTH_BIT, + ENG_SDCDC_SEL_OOB_VTH_BIT); + if (rc < 0) { + pr_err("Couldn't configure the OOB comp threshold rc = %d\n", + rc); + return rc; + } + + rc = smblib_masked_write(chg, SMB2CHG_MISC_ENG_SDCDC_CFG6, + DEAD_TIME_MASK, HIGH_DEAD_TIME_MASK); + if (rc < 0) { + pr_err("Couldn't configure the sdcdc cfg 6 reg rc = %d\n", + rc); + return rc; + } + } + + return rc; +} + +static int smb138x_setup_wa_flags(struct smb138x *chip) +{ + struct pmic_revid_data *pmic_rev_id; + struct device_node *revid_dev_node; + + revid_dev_node = of_parse_phandle(chip->chg.dev->of_node, + "qcom,pmic-revid", 0); + if (!revid_dev_node) { + pr_err("Missing qcom,pmic-revid property\n"); + return -EINVAL; + } + + pmic_rev_id = get_revid_data(revid_dev_node); + 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; + } + + switch (pmic_rev_id->pmic_subtype) { + case SMB1381_SUBTYPE: + if (pmic_rev_id->rev4 < 2) /* SMB1381 rev 1.0 */ + chip->wa_flags |= OOB_COMP_WA_BIT; + break; + default: + pr_err("PMIC subtype %d not supported\n", + pmic_rev_id->pmic_subtype); + return -EINVAL; + } + + return 0; +} + +/**************************** + * DETERMINE INITIAL STATUS * + ****************************/ + +static irqreturn_t smb138x_handle_temperature_change(int irq, void *data) +{ + struct smb_irq_data *irq_data = data; + struct smb138x *chip = irq_data->parent_data; + + if (chip->parallel_psy) + power_supply_changed(chip->parallel_psy); + + return IRQ_HANDLED; +} + +static int smb138x_determine_initial_slave_status(struct smb138x *chip) +{ + struct smb_irq_data irq_data = {chip, "determine-initial-status"}; + + smb138x_handle_temperature_change(0, &irq_data); + return 0; +} + +static int smb138x_determine_initial_status(struct smb138x *chip) +{ + struct smb_irq_data irq_data = {chip, "determine-initial-status"}; + + smblib_handle_usb_plugin(0, &irq_data); + smblib_handle_usb_typec_change(0, &irq_data); + smblib_handle_usb_source_change(0, &irq_data); + return 0; +} + +/************************** + * INTERRUPT REGISTRATION * + **************************/ + +static struct smb_irq_info smb138x_irqs[] = { +/* CHARGER IRQs */ + [CHG_ERROR_IRQ] = { + .name = "chg-error", + .handler = smblib_handle_debug, + }, + [CHG_STATE_CHANGE_IRQ] = { + .name = "chg-state-change", + .handler = smb138x_handle_slave_chg_state_change, + .wake = true, + }, + [STEP_CHG_STATE_CHANGE_IRQ] = { + .name = "step-chg-state-change", + .handler = smblib_handle_debug, + }, + [STEP_CHG_SOC_UPDATE_FAIL_IRQ] = { + .name = "step-chg-soc-update-fail", + .handler = smblib_handle_debug, + }, + [STEP_CHG_SOC_UPDATE_REQ_IRQ] = { + .name = "step-chg-soc-update-request", + .handler = smblib_handle_debug, + }, +/* OTG IRQs */ + [OTG_FAIL_IRQ] = { + .name = "otg-fail", + .handler = smblib_handle_debug, + }, + [OTG_OVERCURRENT_IRQ] = { + .name = "otg-overcurrent", + .handler = smblib_handle_otg_overcurrent, + }, + [OTG_OC_DIS_SW_STS_IRQ] = { + .name = "otg-oc-dis-sw-sts", + .handler = smblib_handle_debug, + }, + [TESTMODE_CHANGE_DET_IRQ] = { + .name = "testmode-change-detect", + .handler = smblib_handle_debug, + }, +/* BATTERY IRQs */ + [BATT_TEMP_IRQ] = { + .name = "bat-temp", + .handler = smblib_handle_batt_psy_changed, + }, + [BATT_OCP_IRQ] = { + .name = "bat-ocp", + .handler = smblib_handle_batt_psy_changed, + }, + [BATT_OV_IRQ] = { + .name = "bat-ov", + .handler = smblib_handle_batt_psy_changed, + }, + [BATT_LOW_IRQ] = { + .name = "bat-low", + .handler = smblib_handle_batt_psy_changed, + }, + [BATT_THERM_ID_MISS_IRQ] = { + .name = "bat-therm-or-id-missing", + .handler = smblib_handle_batt_psy_changed, + }, + [BATT_TERM_MISS_IRQ] = { + .name = "bat-terminal-missing", + .handler = smblib_handle_batt_psy_changed, + }, +/* USB INPUT IRQs */ + [USBIN_COLLAPSE_IRQ] = { + .name = "usbin-collapse", + .handler = smblib_handle_debug, + }, + [USBIN_LT_3P6V_IRQ] = { + .name = "usbin-lt-3p6v", + .handler = smblib_handle_debug, + }, + [USBIN_UV_IRQ] = { + .name = "usbin-uv", + .handler = smblib_handle_debug, + }, + [USBIN_OV_IRQ] = { + .name = "usbin-ov", + .handler = smblib_handle_debug, + }, + [USBIN_PLUGIN_IRQ] = { + .name = "usbin-plugin", + .handler = smblib_handle_usb_plugin, + .wake = true, + }, + [USBIN_SRC_CHANGE_IRQ] = { + .name = "usbin-src-change", + .handler = smblib_handle_usb_source_change, + .wake = true, + }, + [USBIN_ICL_CHANGE_IRQ] = { + .name = "usbin-icl-change", + .handler = smblib_handle_debug, + }, + [TYPE_C_CHANGE_IRQ] = { + .name = "type-c-change", + .handler = smblib_handle_usb_typec_change, + .wake = true, + }, +/* DC INPUT IRQs */ + [DCIN_COLLAPSE_IRQ] = { + .name = "dcin-collapse", + .handler = smblib_handle_debug, + }, + [DCIN_LT_3P6V_IRQ] = { + .name = "dcin-lt-3p6v", + .handler = smblib_handle_debug, + }, + [DCIN_UV_IRQ] = { + .name = "dcin-uv", + .handler = smblib_handle_debug, + }, + [DCIN_OV_IRQ] = { + .name = "dcin-ov", + .handler = smblib_handle_debug, + }, + [DCIN_PLUGIN_IRQ] = { + .name = "dcin-plugin", + .handler = smblib_handle_debug, + }, + [DIV2_EN_DG_IRQ] = { + .name = "div2-en-dg", + .handler = smblib_handle_debug, + }, + [DCIN_ICL_CHANGE_IRQ] = { + .name = "dcin-icl-change", + .handler = smblib_handle_debug, + }, +/* MISCELLANEOUS IRQs */ + [WDOG_SNARL_IRQ] = { + .name = "wdog-snarl", + .handler = smblib_handle_debug, + }, + [WDOG_BARK_IRQ] = { + .name = "wdog-bark", + .handler = smblib_handle_wdog_bark, + .wake = true, + }, + [AICL_FAIL_IRQ] = { + .name = "aicl-fail", + .handler = smblib_handle_debug, + }, + [AICL_DONE_IRQ] = { + .name = "aicl-done", + .handler = smblib_handle_debug, + }, + [HIGH_DUTY_CYCLE_IRQ] = { + .name = "high-duty-cycle", + .handler = smblib_handle_debug, + }, + [INPUT_CURRENT_LIMIT_IRQ] = { + .name = "input-current-limiting", + .handler = smblib_handle_debug, + }, + [TEMPERATURE_CHANGE_IRQ] = { + .name = "temperature-change", + .handler = smb138x_handle_temperature_change, + }, + [SWITCH_POWER_OK_IRQ] = { + .name = "switcher-power-ok", + .handler = smblib_handle_debug, + }, +}; + +static int smb138x_get_irq_index_byname(const char *irq_name) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(smb138x_irqs); i++) { + if (strcmp(smb138x_irqs[i].name, irq_name) == 0) + return i; + } + + return -ENOENT; +} + +static int smb138x_request_interrupt(struct smb138x *chip, + struct device_node *node, + const char *irq_name) +{ + struct smb_charger *chg = &chip->chg; + int rc = 0, irq, irq_index; + struct smb_irq_data *irq_data; + + irq = of_irq_get_byname(node, irq_name); + if (irq < 0) { + pr_err("Couldn't get irq %s byname\n", irq_name); + return irq; + } + + irq_index = smb138x_get_irq_index_byname(irq_name); + if (irq_index < 0) { + pr_err("%s is not a defined irq\n", irq_name); + return irq_index; + } + + if (!smb138x_irqs[irq_index].handler) + return 0; + + irq_data = devm_kzalloc(chg->dev, sizeof(*irq_data), GFP_KERNEL); + if (!irq_data) + return -ENOMEM; + + irq_data->parent_data = chip; + irq_data->name = irq_name; + irq_data->storm_data = smb138x_irqs[irq_index].storm_data; + mutex_init(&irq_data->storm_data.storm_lock); + + rc = devm_request_threaded_irq(chg->dev, irq, NULL, + smb138x_irqs[irq_index].handler, + IRQF_ONESHOT, irq_name, irq_data); + if (rc < 0) { + pr_err("Couldn't request irq %d\n", irq); + return rc; + } + + if (smb138x_irqs[irq_index].wake) + enable_irq_wake(irq); + + return rc; +} + +static int smb138x_request_interrupts(struct smb138x *chip) +{ + struct smb_charger *chg = &chip->chg; + struct device_node *node = chg->dev->of_node; + struct device_node *child; + int rc = 0; + const char *name; + struct property *prop; + + for_each_available_child_of_node(node, child) { + of_property_for_each_string(child, "interrupt-names", + prop, name) { + rc = smb138x_request_interrupt(chip, child, name); + if (rc < 0) { + pr_err("Couldn't request interrupt %s rc=%d\n", + name, rc); + return rc; + } + } + } + + return rc; +} + +static void smb138x_free_interrupts(struct smb_charger *chg) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(smb138x_irqs); i++) { + if (smb138x_irqs[i].irq > 0) { + if (smb138x_irqs[i].wake) + disable_irq_wake(smb138x_irqs[i].irq); + } + } +} + +#ifdef CONFIG_DEBUG_FS +static void smb138x_create_debugfs(struct smb138x *chip) +{ + struct dentry *entry; + + chip->dfs_root = debugfs_create_dir("smb138x", NULL); + if (IS_ERR_OR_NULL(chip->dfs_root)) { + pr_err("Failed to create debugfs directory rc=%ld\n", + (long)chip->dfs_root); + return; + } + + entry = debugfs_create_u32("debug_mask", 0600, chip->dfs_root, + &__debug_mask); + if (IS_ERR_OR_NULL(entry)) { + pr_err("Failed to create debug_mask rc=%ld\n", (long)entry); + debugfs_remove_recursive(chip->dfs_root); + } +} +#else +static void smb138x_create_debugfs(struct smb138x *chip) +{ +} +#endif + +/********* + * PROBE * + *********/ + +static int smb138x_master_probe(struct smb138x *chip) +{ + struct smb_charger *chg = &chip->chg; + int rc = 0; + + chg->param = v1_params; + + rc = smblib_init(chg); + if (rc < 0) { + pr_err("Couldn't initialize smblib rc=%d\n", rc); + return rc; + } + + rc = smb138x_parse_dt(chip); + if (rc < 0) { + pr_err("Couldn't parse device tree rc=%d\n", rc); + goto cleanup; + } + + rc = smb138x_init_vbus_regulator(chip); + if (rc < 0) { + pr_err("Couldn't initialize vbus regulator rc=%d\n", + rc); + goto cleanup; + } + + rc = smb138x_init_vconn_regulator(chip); + if (rc < 0) { + pr_err("Couldn't initialize vconn regulator rc=%d\n", + rc); + goto cleanup; + } + + if (chg->use_extcon) { + /* extcon registration */ + chg->extcon = devm_extcon_dev_allocate(chg->dev, + smblib_extcon_cable); + if (IS_ERR(chg->extcon)) { + rc = PTR_ERR(chg->extcon); + dev_err(chg->dev, "failed to allocate extcon device rc=%d\n", + rc); + goto cleanup; + } + + extcon_set_mutually_exclusive(chg->extcon, + smblib_extcon_exclusive); + rc = devm_extcon_dev_register(chg->dev, chg->extcon); + if (rc < 0) { + dev_err(chg->dev, "failed to register extcon device rc=%d\n", + rc); + goto cleanup; + } + + /* Support reporting polarity and speed via properties */ + rc = extcon_set_property_capability(chg->extcon, + EXTCON_USB, EXTCON_PROP_USB_TYPEC_POLARITY); + rc |= extcon_set_property_capability(chg->extcon, + EXTCON_USB, EXTCON_PROP_USB_SS); + rc |= extcon_set_property_capability(chg->extcon, + EXTCON_USB_HOST, + EXTCON_PROP_USB_TYPEC_POLARITY); + rc |= extcon_set_property_capability(chg->extcon, + EXTCON_USB_HOST, EXTCON_PROP_USB_SS); + if (rc < 0) { + dev_err(chg->dev, + "failed to configure extcon capabilities\n"); + goto cleanup; + } + } + + smb138x_create_debugfs(chip); + + rc = smb138x_init_usb_psy(chip); + if (rc < 0) { + pr_err("Couldn't initialize usb psy rc=%d\n", rc); + goto cleanup; + } + + rc = smb138x_init_usb_main_psy(chip); + if (rc < 0) { + pr_err("Couldn't initialize main usb psy rc=%d\n", rc); + goto cleanup; + } + + rc = sysfs_create_groups(&chg->dev->kobj, smb138x_groups); + if (rc < 0) { + pr_err("Failed to create sysfs files rc=%d\n", rc); + goto cleanup; + } + + rc = smb138x_init_batt_psy(chip); + if (rc < 0) { + pr_err("Couldn't initialize batt psy rc=%d\n", rc); + goto cleanup; + } + + rc = smb138x_init_hw(chip); + if (rc < 0) { + pr_err("Couldn't initialize hardware rc=%d\n", rc); + goto cleanup; + } + + rc = smb138x_determine_initial_status(chip); + if (rc < 0) { + pr_err("Couldn't determine initial status rc=%d\n", + rc); + goto cleanup; + } + + rc = smb138x_request_interrupts(chip); + if (rc < 0) { + pr_err("Couldn't request interrupts rc=%d\n", rc); + goto cleanup; + } + + return rc; + +cleanup: + smb138x_free_interrupts(chg); + smblib_deinit(chg); + + return rc; +} + +static int smb138x_slave_probe(struct smb138x *chip) +{ + struct smb_charger *chg = &chip->chg; + int rc = 0; + + chg->param = v1_params; + + rc = smblib_init(chg); + if (rc < 0) { + pr_err("Couldn't initialize smblib rc=%d\n", rc); + goto cleanup; + } + + chg->iio.temp_max_chan = iio_channel_get(chg->dev, "charger_temp_max"); + if (IS_ERR(chg->iio.temp_max_chan)) { + rc = PTR_ERR(chg->iio.temp_max_chan); + goto cleanup; + } + + chg->iio.connector_temp_thr1_chan = iio_channel_get(chg->dev, + "connector_temp_thr1"); + if (IS_ERR(chg->iio.connector_temp_thr1_chan)) { + rc = PTR_ERR(chg->iio.connector_temp_thr1_chan); + goto cleanup; + } + + chg->iio.connector_temp_thr2_chan = iio_channel_get(chg->dev, + "connector_temp_thr2"); + if (IS_ERR(chg->iio.connector_temp_thr2_chan)) { + rc = PTR_ERR(chg->iio.connector_temp_thr2_chan); + goto cleanup; + } + + chg->iio.connector_temp_thr3_chan = iio_channel_get(chg->dev, + "connector_temp_thr3"); + if (IS_ERR(chg->iio.connector_temp_thr3_chan)) { + rc = PTR_ERR(chg->iio.connector_temp_thr3_chan); + goto cleanup; + } + + rc = smb138x_parse_dt(chip); + if (rc < 0) { + pr_err("Couldn't parse device tree rc=%d\n", rc); + goto cleanup; + } + + rc = smb138x_init_slave_hw(chip); + if (rc < 0) { + pr_err("Couldn't initialize hardware rc=%d\n", rc); + goto cleanup; + } + + if ((chip->dt.pl_mode == POWER_SUPPLY_PL_USBIN_USBIN) + || (chip->dt.pl_mode == POWER_SUPPLY_PL_USBIN_USBIN_EXT)) { + rc = smb138x_init_vbus_regulator(chip); + if (rc < 0) { + pr_err("Couldn't initialize vbus regulator rc=%d\n", + rc); + return rc; + } + } + + rc = smb138x_init_parallel_psy(chip); + if (rc < 0) { + pr_err("Couldn't initialize parallel psy rc=%d\n", rc); + goto cleanup; + } + + rc = smb138x_determine_initial_slave_status(chip); + if (rc < 0) { + pr_err("Couldn't determine initial status rc=%d\n", rc); + goto cleanup; + } + + rc = smb138x_request_interrupts(chip); + if (rc < 0) { + pr_err("Couldn't request interrupts rc=%d\n", rc); + goto cleanup; + } + + return rc; + +cleanup: + smblib_deinit(chg); + if (chip->parallel_psy) + power_supply_unregister(chip->parallel_psy); + if (chg->vbus_vreg && chg->vbus_vreg->rdev) + regulator_unregister(chg->vbus_vreg->rdev); + return rc; +} + +static const struct of_device_id match_table[] = { + { + .compatible = "qcom,smb138x-charger", + .data = (void *) PARALLEL_MASTER + }, + { + .compatible = "qcom,smb138x-parallel-slave", + .data = (void *) PARALLEL_SLAVE + }, + { }, +}; + +static int smb138x_probe(struct platform_device *pdev) +{ + struct smb138x *chip; + const struct of_device_id *id; + int rc = 0; + + chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->chg.dev = &pdev->dev; + chip->chg.debug_mask = &__debug_mask; + chip->chg.try_sink_enabled = &__try_sink_enabled; + chip->chg.irq_info = smb138x_irqs; + chip->chg.name = "SMB"; + chip->chg.audio_headset_drp_wait_ms = &__audio_headset_drp_wait_ms; + + chip->chg.regmap = dev_get_regmap(chip->chg.dev->parent, NULL); + if (!chip->chg.regmap) { + pr_err("parent regmap is missing\n"); + return -EINVAL; + } + + id = of_match_device(of_match_ptr(match_table), chip->chg.dev); + if (!id) { + pr_err("Couldn't find a matching device\n"); + return -ENODEV; + } + + platform_set_drvdata(pdev, chip); + + rc = smb138x_setup_wa_flags(chip); + if (rc < 0) { + if (rc != -EPROBE_DEFER) + pr_err("Couldn't setup wa flags rc = %d\n", rc); + return rc; + } + + chip->chg.mode = (enum smb_mode) id->data; + switch (chip->chg.mode) { + case PARALLEL_MASTER: + rc = smb138x_master_probe(chip); + break; + case PARALLEL_SLAVE: + rc = smb138x_slave_probe(chip); + break; + default: + pr_err("Couldn't find a matching mode %d\n", chip->chg.mode); + rc = -EINVAL; + goto cleanup; + } + + if (rc < 0) { + if (rc != -EPROBE_DEFER) + pr_err("Couldn't probe SMB138X rc=%d\n", rc); + goto cleanup; + } + + device_init_wakeup(chip->chg.dev, true); + + pr_info("SMB138X probed successfully mode=%d\n", chip->chg.mode); + return rc; + +cleanup: + platform_set_drvdata(pdev, NULL); + return rc; +} + +static int smb138x_remove(struct platform_device *pdev) +{ + struct smb138x *chip = platform_get_drvdata(pdev); + struct smb_charger *chg = &chip->chg; + + sysfs_remove_groups(&chg->dev->kobj, smb138x_groups); + debugfs_remove_recursive(chip->dfs_root); + platform_set_drvdata(pdev, NULL); + return 0; +} + +static void smb138x_shutdown(struct platform_device *pdev) +{ + struct smb138x *chip = platform_get_drvdata(pdev); + struct smb_charger *chg = &chip->chg; + int rc; + + /* Suspend charging */ + rc = smb138x_set_parallel_suspend(chip, true); + if (rc < 0) + pr_err("Couldn't suspend charging rc=%d\n", rc); + + /* Disable OTG */ + rc = smblib_masked_write(chg, CMD_OTG_REG, OTG_EN_BIT, 0); + if (rc < 0) + pr_err("Couldn't disable OTG rc=%d\n", rc); + +} + +static struct platform_driver smb138x_driver = { + .driver = { + .name = "qcom,smb138x-charger", + .of_match_table = match_table, + }, + .probe = smb138x_probe, + .remove = smb138x_remove, + .shutdown = smb138x_shutdown, +}; +module_platform_driver(smb138x_driver); + +MODULE_DESCRIPTION("QPNP SMB138X Charger Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/video/backlight/qcom-spmi-wled.c b/drivers/video/backlight/qcom-spmi-wled.c index ccabc384c43a..5e286cbbc71b 100644 --- a/drivers/video/backlight/qcom-spmi-wled.c +++ b/drivers/video/backlight/qcom-spmi-wled.c @@ -3,7 +3,7 @@ * Copyright (c) 2015, Sony Mobile Communications, AB. */ /* - * Copyright (c) 2018-2019, The Linux Foundation. All rights reserved. + * Copyright (c) 2018-2020, The Linux Foundation. All rights reserved. */ #define pr_fmt(fmt) "WLED: %s: " fmt, __func__ @@ -2370,6 +2370,7 @@ static const struct of_device_id wled_match_table[] = { { .compatible = "qcom,pmi8998-spmi-wled", .data = &version_table[0] }, { .compatible = "qcom,pm8150l-spmi-wled", .data = &version_table[2] }, { .compatible = "qcom,pm6150l-spmi-wled", .data = &version_table[2] }, + { .compatible = "qcom,pm660l-spmi-wled", .data = &version_table[1] }, { }, }; diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h index f6ebcb59ac26..7f3b376c691f 100644 --- a/include/linux/power_supply.h +++ b/include/linux/power_supply.h @@ -398,6 +398,7 @@ enum power_supply_type { POWER_SUPPLY_TYPE_BMS, /* Battery Monitor System */ POWER_SUPPLY_TYPE_PARALLEL, /* Parallel Path */ POWER_SUPPLY_TYPE_MAIN, /* Main Path */ + POWER_SUPPLY_TYPE_WIPOWER, /* Wipower */ POWER_SUPPLY_TYPE_UFP, /* Type-C UFP */ POWER_SUPPLY_TYPE_DFP, /* Type-C DFP */ POWER_SUPPLY_TYPE_CHARGE_PUMP, /* Charge Pump */ diff --git a/include/linux/qpnp/qpnp-misc.h b/include/linux/qpnp/qpnp-misc.h new file mode 100644 index 000000000000..318856883263 --- /dev/null +++ b/include/linux/qpnp/qpnp-misc.h @@ -0,0 +1,80 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (c) 2013-2014, 2017, 2019-2020, The Linux Foundation. All rights reserved. + */ + +#ifndef __QPNP_MISC_H +#define __QPNP_MISC_H + +#include + +enum twm_state { + PMIC_TWM_CLEAR, + PMIC_TWM_ENABLE, +}; + +#ifdef CONFIG_QPNP_MISC +/** + * qpnp_misc_irqs_available - check if IRQs are available + * + * @consumer_dev: device struct + * + * This function returns true if the MISC interrupts are available + * based on a check in the MISC peripheral revision registers. + * + * Any consumer of this function needs to reference a MISC device phandle + * using the "qcom,misc-ref" property in their device tree node. + */ + +int qpnp_misc_irqs_available(struct device *consumer_dev); + +/** + * qpnp_misc_read_reg - read register from misc device + * + * @node: device node pointer + * @address: address offset in misc peripheral to be read + * @val: data read from register + * + * This function returns zero if reading the MISC register succeeds. + * + */ + +int qpnp_misc_read_reg(struct device_node *node, u16 addr, u8 *val); +/** + * qpnp_misc_twm_notifier_register - register to the twm mode notifier + * + * @nb: pointer to the client's notifier handle + * + * This function returns 0 if the client is successfully added to the + * notifer list. + */ +int qpnp_misc_twm_notifier_register(struct notifier_block *nb); + +/** + * qpnp_misc_twm_notifier_unregister - unregister to the twm mode notifier + * + * @nb: pointer to the client's notifier handle + * + * This function returns 0 if the client is successfully removed from the + * notifer list. + */ +int qpnp_misc_twm_notifier_unregister(struct notifier_block *nb); +#else +static inline int qpnp_misc_irqs_available(struct device *consumer_dev) +{ + return 0; +} +static inline int qpnp_misc_read_reg(struct device_node *node, u16 addr, + u8 *val) +{ + return 0; +} +static inline int qpnp_misc_twm_notifier_register(struct notifier_block *nb) +{ + return 0; +} +static inline int qpnp_misc_twm_notifier_unregister(struct notifier_block *nb) +{ + return 0; +} +#endif +#endif