// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2015-2016 The Linux Foundation. All rights reserved. * Copyright (c) 2022-2023, Qualcomm Innovation Center, Inc. All rights reserved. */ #define pr_fmt(fmt) "SMB:%s: " fmt, __func__ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define is_between(left, right, value) \ (((left) >= (right) && (left) >= (value) \ && (value) >= (right)) \ || ((left) <= (right) && (left) <= (value) \ && (value) <= (right))) struct smb23x_chip { struct i2c_client *client; struct device *dev; /* charger configurations -- via devicetree */ bool cfg_charging_disabled; bool cfg_recharge_disabled; bool cfg_chg_inhibit_disabled; bool cfg_iterm_disabled; bool cfg_aicl_disabled; bool cfg_apsd_disabled; bool cfg_bms_controlled_charging; int cfg_vfloat_mv; int cfg_resume_delta_mv; int cfg_chg_inhibit_delta_mv; int cfg_iterm_ma; int cfg_fastchg_ma; int cfg_cold_bat_decidegc; int cfg_cool_bat_decidegc; int cfg_warm_bat_decidegc; int cfg_hot_bat_decidegc; int cfg_temp_comp_mv; int cfg_temp_comp_ma; int cfg_safety_time; int *cfg_thermal_mitigation; /* status */ bool batt_present; bool batt_full; bool batt_hot; bool batt_cold; bool batt_warm; bool batt_cool; bool usb_present; bool apsd_enabled; bool debug_batt; int chg_disabled_status; int usb_suspended_status; int usb_psy_ma; enum power_supply_type charger_type; /* others */ bool irq_waiting; bool resume_completed; u8 irq_cfg_mask; u32 peek_poke_address; int fake_battery_soc; int therm_lvl_sel; int thermal_levels; u32 workaround_flags; int batt_id_ohm; bool usb_poll; const char *bms_psy_name; struct power_supply *usb_psy; struct power_supply *bms_psy; struct power_supply *batt_psy; struct power_supply_desc usb_psy_desc; struct iio_channel *batt_id_chan; struct mutex read_write_lock; struct mutex irq_complete; struct mutex chg_disable_lock; struct mutex usb_suspend_lock; struct mutex icl_set_lock; struct dentry *debug_root; struct delayed_work irq_polling_work; }; static int usbin_current_ma_table[] = { 100, 300, 500, 650, 900, 1000, 1500, }; static int fastchg_current_ma_table[] = { 100, 250, 300, 370, 500, 600, 700, 1000, }; static int iterm_ma_table[] = { 20, 30, 50, 75, }; static int recharge_mv_table[] = { 150, 80, }; static int safety_time_min_table[] = { 180, 270, 360, 0, }; static int vfloat_compensation_mv_table[] = { 80, 120, 160, 200, }; static int inhibit_mv_table[] = { 0, 265, 385, 512, }; static int cold_bat_decidegc_table[] = { 100, 50, 0, -50, }; static int cool_bat_decidegc_table[] = { 150, 100, 50, 0, }; static int warm_bat_decidegc_table[] = { 400, 450, 500, 550, }; static int hot_bat_decidegc_table[] = { 500, 550, 600, 650, }; #define MIN_FLOAT_MV 3480 #define MAX_FLOAT_MV 4720 #define FLOAT_STEP_MV 40 #define _SMB_MASK(BITS, POS) \ ((unsigned char)(((1 << (BITS)) - 1) << (POS))) #define SMB_MASK(LEFT_BIT_POS, RIGHT_BIT_POS) \ _SMB_MASK((LEFT_BIT_POS) - (RIGHT_BIT_POS) + 1, \ (RIGHT_BIT_POS)) /* register mapping definitions */ #define CFG_REG_0 0x00 #define USBIN_ICL_MASK SMB_MASK(4, 2) #define USBIN_ICL_OFFSET 2 #define ITERM_MASK SMB_MASK(1, 0) #define CFG_REG_2 0x02 #define RECHARGE_DIS_BIT BIT(7) #define RECHARGE_THRESH_MASK BIT(6) #define RECHARGE_THRESH_OFFSET 6 #define ITERM_DIS_BIT BIT(5) #define FASTCHG_CURR_MASK SMB_MASK(2, 0) #define CFG_REG_3 0x03 #define FASTCHG_CURR_SOFT_COMP SMB_MASK(7, 5) #define FASTCHG_CURR_SOFT_COMP_OFFSET 5 #define FLOAT_VOLTAGE_MASK SMB_MASK(4, 0) #define CFG_REG_4 0x04 #define CHG_EN_ACTIVE_LOW_BIT BIT(5) #define SAFETY_TIMER_MASK SMB_MASK(4, 3) #define SAFETY_TIMER_OFFSET 3 #define SAFETY_TIMER_DISABLE SMB_MASK(4, 3) #define SYSTEM_VOLATGE_MASK SMB_MASK(1, 0) #define TEMP_MASK SMB_MASK(7, 0) #define CFG_REG_5 0x05 #define BAT_THERM_DIS_BIT BIT(7) #define HARD_THERM_NOT_SUSPEND BIT(6) #define SOFT_THERM_BEHAVIOR_MASK SMB_MASK(5, 4) #define SOFT_THERM_VFLT_CHG_COMP SMB_MASK(5, 4) #define VFLOAT_COMP_MASK SMB_MASK(3, 2) #define VFLOAT_COMP_OFFSET 2 #define APSD_EN_BIT BIT(1) #define AICL_EN_BIT BIT(0) #define CFG_REG_6 0x06 #define CHG_INHIBIT_THRESH_MASK SMB_MASK(7, 6) #define SYS_THRESHOLD_MASK SMB_MASK(5, 4) #define INHIBIT_THRESH_OFFSET 6 #define BMD_ALGO_MASK SMB_MASK(1, 0) #define BMD_ALGO_THERM_IO SMB_MASK(1, 0) #define CFG_REG_7 0x07 #define USB1_5_PIN_CNTRL_BIT BIT(7) #define USB_AC_PIN_CNTRL_BIT BIT(6) #define CHG_EN_PIN_CNTRL_BIT BIT(5) #define SUSPEND_SW_CNTRL_BIT BIT(3) #define CFG_REG_8 0x08 #define HARD_COLD_TEMP_MASK SMB_MASK(7, 6) #define HARD_COLD_TEMP_OFFSET 6 #define HARD_HOT_TEMP_MASK SMB_MASK(5, 4) #define HARD_HOT_TEMP_OFFSET 4 #define SOFT_COLD_TEMP_MASK SMB_MASK(3, 2) #define SOFT_COLD_TEMP_OFFSET 2 #define SOFT_HOT_TEMP_MASK SMB_MASK(1, 0) #define SOFT_HOT_TEMP_OFFSET 0 #define IRQ_CFG_REG_9 0x09 #define SAFETY_TIMER_IRQ_EN_BIT BIT(7) #define BATT_OV_IRQ_EN_BIT BIT(6) #define BATT_MISSING_IRQ_EN_BIT BIT(5) #define ITERM_IRQ_EN_BIT BIT(4) #define HARD_TEMP_IRQ_EN_BIT BIT(3) #define SOFT_TEMP_IRQ_EN_BIT BIT(2) #define AICL_DONE_IRQ_EN_BIT BIT(1) #define INOK_IRQ_EN_BIT BIT(0) #define I2C_COMM_CFG_REG 0x0a #define VOLATILE_WRITE_EN_BIT BIT(0) #define NV_CFG_REG 0x14 #define UNPLUG_RELOAD_DIS_BIT BIT(2) #define CMD_REG_0 0x30 #define VOLATILE_WRITE_ALLOW BIT(7) #define USB_SUSPEND_BIT BIT(2) #define CHARGE_EN_BIT BIT(1) #define STATE_PIN_OUT_DIS_BIT BIT(0) #define CMD_REG_1 0x31 #define USB500_MODE_BIT BIT(1) #define USBAC_MODE_BIT BIT(0) #define IRQ_A_STATUS_REG 0x38 #define HOT_HARD_BIT BIT(6) #define COLD_HARD_BIT BIT(4) #define HOT_SOFT_BIT BIT(2) #define COLD_SOFT_BIT BIT(0) #define IRQ_B_STATUS_REG 0x39 #define BATT_OV_BIT BIT(6) #define BATT_ABSENT_BIT BIT(4) #define BATT_UV_BIT BIT(2) #define BATT_P2F_BIT BIT(0) #define IRQ_C_STATUS_REG 0x3A #define CHG_ERROR_BIT BIT(6) #define RECHG_THRESH_BIT BIT(4) #define TAPER_CHG_BIT BIT(2) #define ITERM_BIT BIT(0) #define IRQ_D_STATUS_REG 0x3B #define APSD_DONE_BIT BIT(6) #define AICL_DONE_BIT BIT(4) #define SAFETYTIMER_EXPIRED_BIT BIT(2) #define CHG_STATUS_A_REG 0x3C #define USBIN_OV_BIT BIT(6) #define USBIN_UV_BIT BIT(4) #define POWER_OK_BIT BIT(2) #define DIE_TEMP_LIMIT_BIT BIT(0) #define CHG_STATUS_B_REG 0x3D #define CHARGE_TYPE_MASK SMB_MASK(2, 1) #define CHARGE_TYPE_OFFSET 1 #define NO_CHARGE_VAL 0x00 #define PRE_CHARGE_VAL BIT(1) #define FAST_CHARGE_VAL BIT(2) #define TAPER_CHARGE_VAL SMB_MASK(2, 1) #define CHARGE_EN_STS_BIT BIT(0) #define CHG_STATUS_C_REG 0x3E #define APSD_STATUS_BIT BIT(4) #define APSD_RESULT_MASK SMB_MASK(3, 0) #define CDP_TYPE_VAL BIT(3) #define DCP_TYPE_VAL BIT(2) #define SDP_TYPE_VAL BIT(0) #define OTHERS_TYPE_VAL BIT(1) #define AICL_STATUS_REG 0x3F #define AICL_DONE_STS_BIT BIT(6) #define AICL_RESULT_MASK SMB_MASK(5, 0) #define AICL_75_MA BIT(3) #define AICL_100_MA BIT(4) #define AICL_300_MA BIT(0) #define AICL_500_MA BIT(1) #define AICL_650_MA SMB_MASK(1, 0) #define AICL_900_MA BIT(2) #define AICL_1000_MA (BIT(2) | BIT(0)) #define AICL_1500_MA (BIT(2) | BIT(1)) #define USB5_LIMIT BIT(4) #define USB1_LIMIT BIT(5) struct smb_irq_info { const char *name; int (*smb_irq)(struct smb23x_chip *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]; }; enum { USER = BIT(0), CURRENT = BIT(1), BMS = BIT(2), THERMAL = BIT(3), }; enum { WRKRND_IRQ_POLLING = BIT(0), WRKRND_USB_SUSPEND = BIT(1), }; static irqreturn_t smb23x_stat_handler(int irq, void *dev_id); #define MAX_RW_RETRIES 3 static int __smb23x_read(struct smb23x_chip *chip, u8 reg, u8 *val) { int rc, i; for (i = 0; i < MAX_RW_RETRIES; i++) { rc = i2c_smbus_read_byte_data(chip->client, reg); if (rc >= 0) break; /* delay between i2c retries */ msleep(20); } if (rc < 0) { pr_err("Reading 0x%02x failed, rc = %d\n", reg, rc); return rc; } *val = rc; pr_debug("Reading 0x%02x = 0x%02x\n", reg, *val); return 0; } static int __smb23x_write(struct smb23x_chip *chip, u8 reg, u8 val) { int rc, i; for (i = 0; i < MAX_RW_RETRIES; i++) { rc = i2c_smbus_write_byte_data(chip->client, reg, val); if (!rc) break; /* delay between i2c retries */ msleep(20); } if (rc < 0) { pr_err("Writing val 0x%02x to reg 0x%02x failed, rc = %d\n", val, reg, rc); return rc; } pr_debug("Writing 0x%02x = 0x%02x\n", reg, val); return 0; } static int smb23x_read(struct smb23x_chip *chip, u8 reg, u8 *val) { int rc; mutex_lock(&chip->read_write_lock); rc = __smb23x_read(chip, reg, val); mutex_unlock(&chip->read_write_lock); return rc; } static int smb23x_write(struct smb23x_chip *chip, int reg, int val) { int rc; mutex_lock(&chip->read_write_lock); rc = __smb23x_write(chip, reg, val); mutex_unlock(&chip->read_write_lock); return rc; } static int smb23x_masked_write(struct smb23x_chip *chip, u8 reg, u8 mask, u8 val) { int rc; u8 tmp; mutex_lock(&chip->read_write_lock); rc = __smb23x_read(chip, reg, &tmp); if (rc < 0) { pr_err("Read failed\n"); goto i2c_error; } tmp &= ~mask; tmp |= val & mask; rc = __smb23x_write(chip, reg, tmp); if (rc < 0) pr_err("Write failed\n"); i2c_error: mutex_unlock(&chip->read_write_lock); return rc; } static int smb23x_parse_dt(struct smb23x_chip *chip) { int rc = 0; struct device_node *node = chip->dev->of_node; if (!node) { pr_err("device node invalid\n"); return (-EINVAL); } chip->cfg_charging_disabled = of_property_read_bool(node, "qcom,charging-disabled"); chip->cfg_recharge_disabled = of_property_read_bool(node, "qcom,recharge-disabled"); chip->cfg_chg_inhibit_disabled = of_property_read_bool(node, "qcom,chg-inhibit-disabled"); chip->cfg_iterm_disabled = of_property_read_bool(node, "qcom,iterm-disabled"); chip->cfg_aicl_disabled = of_property_read_bool(node, "qcom,aicl-disabled"); chip->cfg_apsd_disabled = of_property_read_bool(node, "qcom,apsd-disabled"); chip->cfg_bms_controlled_charging = of_property_read_bool(node, "qcom,bms-controlled-charging"); rc = of_property_read_string(node, "qcom,bms-psy-name", &chip->bms_psy_name); if (rc) { chip->bms_psy_name = NULL; chip->cfg_bms_controlled_charging = false; } /* * If bms-controlled-charging is defined, then the charging * termination and recharge behavior will be controlled by * BMS power supply instead of the SMB chip itself, so we * need to keep iterm and recharge on the chip disabled. */ if (chip->cfg_bms_controlled_charging) { chip->cfg_iterm_disabled = true; chip->cfg_recharge_disabled = true; } rc = of_property_read_u32(node, "qcom,float-voltage-mv", &chip->cfg_vfloat_mv); if (rc < 0) { chip->cfg_vfloat_mv = -EINVAL; } else { if (chip->cfg_vfloat_mv > MAX_FLOAT_MV || chip->cfg_vfloat_mv < MIN_FLOAT_MV) { pr_err("Float voltage out of range\n"); return (-EINVAL); } } rc = of_property_read_u32(node, "qcom,recharge-thresh-mv", &chip->cfg_resume_delta_mv); if (rc < 0) chip->cfg_resume_delta_mv = -EINVAL; rc = of_property_read_u32(node, "qcom,chg-inhibit-thresh-mv", &chip->cfg_chg_inhibit_delta_mv); if (rc < 0) chip->cfg_chg_inhibit_delta_mv = -EINVAL; rc = of_property_read_u32(node, "qcom,iterm-ma", &chip->cfg_iterm_ma); if (rc < 0) chip->cfg_iterm_ma = -EINVAL; rc = of_property_read_u32(node, "qcom,fastchg-ma", &chip->cfg_fastchg_ma); if (rc < 0) chip->cfg_fastchg_ma = -EINVAL; rc = of_property_read_u32(node, "qcom,cold-bat-decidegc", &chip->cfg_cold_bat_decidegc); if (rc < 0) chip->cfg_cold_bat_decidegc = -EINVAL; rc = of_property_read_u32(node, "qcom,cool-bat-decidegc", &chip->cfg_cool_bat_decidegc); if (rc < 0) chip->cfg_cool_bat_decidegc = -EINVAL; rc = of_property_read_u32(node, "qcom,warm-bat-decidegc", &chip->cfg_warm_bat_decidegc); if (rc < 0) chip->cfg_warm_bat_decidegc = -EINVAL; rc = of_property_read_u32(node, "qcom,hot-bat-decidegc", &chip->cfg_hot_bat_decidegc); if (rc < 0) chip->cfg_hot_bat_decidegc = -EINVAL; rc = of_property_read_u32(node, "qcom,soft-temp-vfloat-comp-mv", &chip->cfg_temp_comp_mv); if (rc < 0) chip->cfg_temp_comp_mv = -EINVAL; rc = of_property_read_u32(node, "qcom,soft-temp-current-comp-ma", &chip->cfg_temp_comp_ma); if (rc < 0) chip->cfg_temp_comp_ma = -EINVAL; rc = of_property_read_u32(node, "qcom,charging-timeout", &chip->cfg_safety_time); if (rc < 0) chip->cfg_safety_time = -EINVAL; if (of_find_property(node, "qcom,thermal-mitigation", &chip->thermal_levels)) { chip->cfg_thermal_mitigation = devm_kzalloc(chip->dev, chip->thermal_levels, GFP_KERNEL); if (chip->cfg_thermal_mitigation == NULL) { pr_err("thermal mitigation kzalloc() failed.\n"); return (-ENOMEM); } chip->thermal_levels /= sizeof(int); rc = of_property_read_u32_array(node, "qcom,thermal-mitigation", chip->cfg_thermal_mitigation, chip->thermal_levels); if (rc) { pr_err("Read therm limits failed, rc=%d\n", rc); return rc; } } return 0; } static int smb23x_enable_volatile_writes(struct smb23x_chip *chip) { int rc; u8 reg; rc = smb23x_read(chip, I2C_COMM_CFG_REG, ®); if (rc < 0) { pr_err("Read I2C_COMM_CFG_REG, rc=%d\n", rc); return rc; } if (!(reg & VOLATILE_WRITE_EN_BIT)) { pr_err("Volatile write is not allowed!\n"); return (-EACCES); } rc = smb23x_masked_write(chip, CMD_REG_0, VOLATILE_WRITE_ALLOW, VOLATILE_WRITE_ALLOW); if (rc < 0) { pr_err("Set VOLATILE_WRITE_ALLOW bit failed\n"); return rc; } return 0; } static inline int find_closest_in_ascendant_list( int val, int lst[], int length) { int i; for (i = 0; i < length; i++) { if (val <= lst[i]) break; } if (i == length) i--; return i; } static inline int find_closest_in_descendant_list( int val, int lst[], int length) { int i; for (i = 0; i < length; i++) { if (val >= lst[i]) break; } if (i == length) i--; return i; } static int __smb23x_charging_disable(struct smb23x_chip *chip, bool disable) { int rc; rc = smb23x_masked_write(chip, CMD_REG_0, CHARGE_EN_BIT, disable ? 0 : CHARGE_EN_BIT); if (rc < 0) pr_err("%s charging failed, rc=%d\n", disable ? "Disable" : "Enable", rc); return rc; } static int smb23x_charging_disable(struct smb23x_chip *chip, int reason, int disable) { int rc = 0; int disabled; mutex_lock(&chip->chg_disable_lock); disabled = chip->chg_disabled_status; if (disable) disabled |= reason; else disabled &= ~reason; rc = __smb23x_charging_disable(chip, disabled ? true : false); if (rc < 0) pr_err("%s charging for reason 0x%x failed\n", disable ? "Disable" : "Enable", reason); else chip->chg_disabled_status = disabled; mutex_unlock(&chip->chg_disable_lock); return rc; } static int smb23x_suspend_usb(struct smb23x_chip *chip, int reason, bool suspend) { int rc = 0; int suspended; mutex_lock(&chip->usb_suspend_lock); suspended = chip->usb_suspended_status; if (suspend) suspended |= reason; else suspended &= ~reason; rc = smb23x_masked_write(chip, CMD_REG_0, USB_SUSPEND_BIT, suspended ? USB_SUSPEND_BIT : 0); if (rc < 0) { pr_err("Write USB_SUSPEND failed, rc=%d\n", rc); } else { chip->usb_suspended_status = suspended; pr_debug("%suspend USB!\n", suspend ? "S" : "Un-s"); } mutex_unlock(&chip->usb_suspend_lock); return rc; } #define CURRENT_SUSPEND 2 #define CURRENT_100_MA 100 #define CURRENT_500_MA 500 #define CURRENT_1500_MA 1500 static int smb23x_set_appropriate_usb_current(struct smb23x_chip *chip) { int rc = 0, therm_ma, current_ma; int usb_current; u8 tmp; if ((chip->charger_type == POWER_SUPPLY_TYPE_USB_CDP) || (chip->charger_type == POWER_SUPPLY_TYPE_USB_DCP)) chip->usb_psy_ma = CURRENT_1500_MA; usb_current = chip->usb_psy_ma; if (chip->therm_lvl_sel > 0 && chip->therm_lvl_sel < (chip->thermal_levels - 1)) /* * consider thermal limit only when it is active and not at * the highest level */ therm_ma = chip->cfg_thermal_mitigation[chip->therm_lvl_sel]; else therm_ma = usb_current; current_ma = min(therm_ma, usb_current); if (current_ma <= CURRENT_SUSPEND) { if (chip->workaround_flags & WRKRND_USB_SUSPEND) { current_ma = CURRENT_100_MA; } else { /* suspend USB input */ rc = smb23x_suspend_usb(chip, CURRENT, true); if (rc) pr_err("Suspend USB failed, rc=%d\n", rc); return rc; } } if (current_ma <= CURRENT_100_MA) { /* USB 100 */ rc = smb23x_masked_write(chip, CMD_REG_1, USB500_MODE_BIT | USBAC_MODE_BIT, 0); if (rc) pr_err("Set USB100 failed, rc=%d\n", rc); pr_debug("Setting USB 100\n"); } else if (current_ma <= CURRENT_500_MA) { /* USB 500 */ rc = smb23x_masked_write(chip, CMD_REG_1, USB500_MODE_BIT | USBAC_MODE_BIT, USB500_MODE_BIT); if (rc) pr_err("Set USB500 failed, rc=%d\n", rc); pr_debug("Setting USB 500\n"); } else { /* USB AC */ rc = smb23x_masked_write(chip, CMD_REG_1, USBAC_MODE_BIT, USBAC_MODE_BIT); if (rc) pr_err("Set USBAC failed, rc=%d\n", rc); pr_debug("Setting USB AC\n"); } /* set ICL */ tmp = find_closest_in_ascendant_list(current_ma, usbin_current_ma_table, ARRAY_SIZE(usbin_current_ma_table)); tmp = tmp << USBIN_ICL_OFFSET; rc = smb23x_masked_write(chip, CFG_REG_0, USBIN_ICL_MASK, tmp); if (rc < 0) { pr_err("Set ICL failed\n, rc=%d\n", rc); return rc; } pr_debug("ICL set to = %d\n", usbin_current_ma_table[tmp >> USBIN_ICL_OFFSET]); if (!(chip->workaround_flags & WRKRND_USB_SUSPEND)) { /* un-suspend USB input */ rc = smb23x_suspend_usb(chip, CURRENT, false); if (rc < 0) pr_err("Un-suspend USB failed, rc=%d\n", rc); } return rc; } static int smb23x_hw_init(struct smb23x_chip *chip) { int rc, i = 0; u8 tmp = 0; rc = smb23x_enable_volatile_writes(chip); if (rc < 0) { pr_err("Enable volatile writes failed, rc=%d\n", rc); return rc; } /* iterm setting */ if (chip->cfg_iterm_disabled) { rc = smb23x_masked_write(chip, CFG_REG_2, ITERM_DIS_BIT, ITERM_DIS_BIT); if (rc < 0) { pr_err("Disable ITERM failed, rc=%d\n", rc); return rc; } } else if (chip->cfg_iterm_ma != -EINVAL) { i = find_closest_in_ascendant_list(chip->cfg_iterm_ma, iterm_ma_table, ARRAY_SIZE(iterm_ma_table)); tmp = i; rc = smb23x_masked_write(chip, CFG_REG_0, ITERM_MASK, tmp); if (rc < 0) { pr_err("Set ITERM failed, rc=%d\n", rc); return rc; } } /* recharging setting */ if (chip->cfg_recharge_disabled) { rc = smb23x_masked_write(chip, CFG_REG_2, RECHARGE_DIS_BIT, RECHARGE_DIS_BIT); if (rc < 0) { pr_err("Disable recharging failed, rc=%d\n", rc); return rc; } } else if (chip->cfg_resume_delta_mv != -EINVAL) { i = find_closest_in_descendant_list( chip->cfg_resume_delta_mv, recharge_mv_table, ARRAY_SIZE(recharge_mv_table)); tmp = i << RECHARGE_THRESH_OFFSET; rc = smb23x_masked_write(chip, CFG_REG_2, RECHARGE_THRESH_MASK, tmp); if (rc < 0) { pr_err("Set recharge thresh failed, rc=%d\n", rc); return rc; } } /* charging inhibit setting */ if (chip->cfg_chg_inhibit_disabled) { rc = smb23x_masked_write(chip, CFG_REG_6, CHG_INHIBIT_THRESH_MASK, 0); if (rc < 0) { pr_err("Disable charge inhibit failed, rc=%d\n", rc); return rc; } } else if (chip->cfg_chg_inhibit_delta_mv != -EINVAL) { i = find_closest_in_ascendant_list( chip->cfg_chg_inhibit_delta_mv, inhibit_mv_table, ARRAY_SIZE(inhibit_mv_table)); tmp = i << INHIBIT_THRESH_OFFSET; rc = smb23x_masked_write(chip, CFG_REG_6, CHG_INHIBIT_THRESH_MASK, tmp); if (rc < 0) { pr_err("Set inhibit threshold failed, rc=%d\n", rc); return rc; } } /* disable AICL */ if (chip->cfg_aicl_disabled) { rc = smb23x_masked_write(chip, CFG_REG_5, AICL_EN_BIT, 0); if (rc < 0) { pr_err("Disable AICL failed, rc=%d\n", rc); return rc; } } /* disable APSD */ if (chip->cfg_apsd_disabled) { rc = smb23x_masked_write(chip, CFG_REG_5, APSD_EN_BIT, 0); if (rc < 0) { pr_err("Disable APSD failed, rc=%d\n", rc); return rc; } chip->apsd_enabled = false; rc = smb23x_masked_write(chip, CFG_REG_5, AICL_EN_BIT, 0); if (rc < 0) { pr_err("Disable CFG_5 failed, rc=%d\n", rc); return rc; } } else { rc = smb23x_read(chip, CFG_REG_5, &tmp); if (rc < 0) { pr_err("read CFG_REG_5 failed, rc=%d\n", rc); return rc; } chip->apsd_enabled = !!(tmp & APSD_EN_BIT); } /* float voltage setting */ if (chip->cfg_vfloat_mv != -EINVAL) { tmp = (chip->cfg_vfloat_mv - MIN_FLOAT_MV) / FLOAT_STEP_MV; rc = smb23x_masked_write(chip, CFG_REG_3, FLOAT_VOLTAGE_MASK, tmp); if (rc < 0) { pr_err("Set float voltage failed, rc=%d\n", rc); return rc; } } /* fastchg current setting */ if (chip->cfg_fastchg_ma != -EINVAL) { i = find_closest_in_ascendant_list( chip->cfg_fastchg_ma, fastchg_current_ma_table, ARRAY_SIZE(fastchg_current_ma_table)); tmp = i; rc = smb23x_masked_write(chip, CFG_REG_2, FASTCHG_CURR_MASK, tmp); if (rc < 0) { pr_err("Set fastchg current failed, rc=%d\n", rc); return rc; } } /* hard JEITA settings */ if (chip->cfg_cold_bat_decidegc != -EINVAL || chip->cfg_hot_bat_decidegc != -EINVAL) { u8 mask = 0; rc = smb23x_masked_write(chip, CFG_REG_5, BAT_THERM_DIS_BIT | HARD_THERM_NOT_SUSPEND, 0); if (rc < 0) { pr_err("Enable thermal monitor failed, rc=%d\n", rc); return rc; } if (chip->cfg_cold_bat_decidegc != -EINVAL) { i = find_closest_in_descendant_list( chip->cfg_cold_bat_decidegc, cold_bat_decidegc_table, ARRAY_SIZE(cold_bat_decidegc_table)); mask |= HARD_COLD_TEMP_MASK; tmp = i << HARD_COLD_TEMP_OFFSET; } if (chip->cfg_hot_bat_decidegc != -EINVAL) { i = find_closest_in_ascendant_list( chip->cfg_hot_bat_decidegc, hot_bat_decidegc_table, ARRAY_SIZE(hot_bat_decidegc_table)); mask |= HARD_HOT_TEMP_MASK; tmp |= i << HARD_HOT_TEMP_OFFSET; } rc = smb23x_masked_write(chip, CFG_REG_8, mask, tmp); if (rc < 0) { pr_err("Set hard cold/hot temperature failed, rc=%d\n", rc); return rc; } } /* soft JEITA settings */ if (chip->cfg_cool_bat_decidegc != -EINVAL || chip->cfg_warm_bat_decidegc != -EINVAL) { u8 mask = 0; rc = smb23x_masked_write(chip, CFG_REG_5, SOFT_THERM_BEHAVIOR_MASK, SOFT_THERM_VFLT_CHG_COMP); if (rc < 0) { pr_err("Set soft JEITA behavior failed, rc=%d\n", rc); return rc; } if (chip->cfg_cool_bat_decidegc != -EINVAL) { i = find_closest_in_descendant_list( chip->cfg_cool_bat_decidegc, cool_bat_decidegc_table, ARRAY_SIZE(cool_bat_decidegc_table)); mask |= SOFT_COLD_TEMP_MASK; tmp = i << SOFT_COLD_TEMP_OFFSET; } if (chip->cfg_warm_bat_decidegc != -EINVAL) { i = find_closest_in_ascendant_list( chip->cfg_warm_bat_decidegc, warm_bat_decidegc_table, ARRAY_SIZE(warm_bat_decidegc_table)); mask |= SOFT_HOT_TEMP_MASK; tmp |= i << SOFT_HOT_TEMP_OFFSET; } rc = smb23x_masked_write(chip, CFG_REG_8, mask, tmp); if (rc < 0) { pr_err("Set soft cold/hot temperature failed, rc=%d\n", rc); return rc; } } /* float voltage and fastchg current compensation for soft JEITA */ if (chip->cfg_temp_comp_mv != -EINVAL) { i = find_closest_in_ascendant_list( chip->cfg_temp_comp_mv, vfloat_compensation_mv_table, ARRAY_SIZE(vfloat_compensation_mv_table)); tmp = i << VFLOAT_COMP_OFFSET; rc = smb23x_masked_write(chip, CFG_REG_5, VFLOAT_COMP_MASK, tmp); if (rc < 0) { pr_err("Set VFLOAT_COMP failed, rc=%d\n", rc); return rc; } } if (chip->cfg_temp_comp_ma != -EINVAL) { int compensated_ma; compensated_ma = chip->cfg_fastchg_ma - chip->cfg_temp_comp_ma; i = find_closest_in_ascendant_list( compensated_ma, fastchg_current_ma_table, ARRAY_SIZE(fastchg_current_ma_table)); tmp = i << FASTCHG_CURR_SOFT_COMP_OFFSET; rc = smb23x_masked_write(chip, CFG_REG_3, FASTCHG_CURR_SOFT_COMP, tmp); if (rc < 0) { pr_err("Set FASTCHG_COMP failed, rc=%d\n", rc); return rc; } } /* safety timer setting */ if (chip->cfg_safety_time != -EINVAL) { i = find_closest_in_ascendant_list( chip->cfg_safety_time, safety_time_min_table, ARRAY_SIZE(safety_time_min_table)); tmp = i << SAFETY_TIMER_OFFSET; /* disable safety timer if the value equals 0 */ if (chip->cfg_safety_time == 0) tmp = SAFETY_TIMER_DISABLE; rc = smb23x_masked_write(chip, CFG_REG_4, SAFETY_TIMER_MASK, tmp); if (rc < 0) { pr_err("Set safety timer failed, rc=%d\n", rc); return rc; } } /* * Disable the STAT pin output, to make the pin keep at open drain * state and detect the IRQ on the falling edge */ rc = smb23x_masked_write(chip, CMD_REG_0, STATE_PIN_OUT_DIS_BIT, STATE_PIN_OUT_DIS_BIT); if (rc < 0) { pr_err("Disable state pin output failed, rc=%d\n", rc); return rc; } rc = smb23x_masked_write(chip, CFG_REG_4, CHG_EN_ACTIVE_LOW_BIT, 0); /* EN active high */ rc |= smb23x_masked_write(chip, CFG_REG_7, USB1_5_PIN_CNTRL_BIT | USB_AC_PIN_CNTRL_BIT | CHG_EN_PIN_CNTRL_BIT | SUSPEND_SW_CNTRL_BIT, SUSPEND_SW_CNTRL_BIT); /* suspend ctrl by sw */ if (rc < 0) { pr_err("Configure board setting failed, rc=%d\n", rc); return rc; } /* Enable necessary IRQs */ rc = smb23x_masked_write(chip, IRQ_CFG_REG_9, 0xff, SAFETY_TIMER_IRQ_EN_BIT | BATT_MISSING_IRQ_EN_BIT | ITERM_IRQ_EN_BIT | HARD_TEMP_IRQ_EN_BIT | SOFT_TEMP_IRQ_EN_BIT | AICL_DONE_IRQ_EN_BIT | INOK_IRQ_EN_BIT); if (rc < 0) { pr_err("Configure IRQ failed, rc=%d\n", rc); return rc; } return rc; } static int hot_hard_irq_handler(struct smb23x_chip *chip, u8 rt_sts) { pr_warn("rt_sts = 0x02%x\n", rt_sts); chip->batt_hot = !!rt_sts; return 0; } static int cold_hard_irq_handler(struct smb23x_chip *chip, u8 rt_sts) { pr_debug("rt_sts = 0x02%x\n", rt_sts); chip->batt_cold = !!rt_sts; return 0; } static int hot_soft_irq_handler(struct smb23x_chip *chip, u8 rt_sts) { pr_debug("rt_sts = 0x02%x\n", rt_sts); chip->batt_warm = !!rt_sts; return 0; } static int cold_soft_irq_handler(struct smb23x_chip *chip, u8 rt_sts) { pr_debug("rt_sts = 0x02%x\n", rt_sts); chip->batt_cool = !!rt_sts; return 0; } static int batt_ov_irq_handler(struct smb23x_chip *chip, u8 rt_sts) { pr_debug("rt_sts = 0x02%x\n", rt_sts); return 0; } static int batt_missing_irq_handler(struct smb23x_chip *chip, u8 rt_sts) { pr_debug("rt_sts = 0x02%x\n", rt_sts); chip->batt_present = !rt_sts; return 0; } static int batt_low_irq_handler(struct smb23x_chip *chip, u8 rt_sts) { pr_warn("rt_sts = 0x02%x\n", rt_sts); return 0; } static int pre_to_fast_irq_handler(struct smb23x_chip *chip, u8 rt_sts) { pr_debug("rt_sts = 0x02%x\n", rt_sts); chip->batt_full = false; return 0; } static int chg_error_irq_handler(struct smb23x_chip *chip, u8 rt_sts) { pr_warn("rt_sts = 0x02%x\n", rt_sts); return 0; } static int recharge_irq_handler(struct smb23x_chip *chip, u8 rt_sts) { pr_debug("rt_sts = 0x02%x\n", rt_sts); chip->batt_full = !rt_sts; return 0; } static int taper_irq_handler(struct smb23x_chip *chip, u8 rt_sts) { pr_debug("rt_sts = 0x02%x\n", rt_sts); return 0; } static int iterm_irq_handler(struct smb23x_chip *chip, u8 rt_sts) { pr_debug("rt_sts = 0x02%x\n", rt_sts); chip->batt_full = !!rt_sts; return 0; } static const char * const usb_type_str[] = { "SDP", "UNKNOWN", "DCP", "CDP", }; static int src_detect_irq_handler(struct smb23x_chip *chip, u8 rt_sts) { bool usb_present = !!rt_sts; if (!chip->apsd_enabled) return 0; pr_debug("chip->usb_present = %d, usb_present = %d\n", chip->usb_present, usb_present); return 0; } static int aicl_done_irq_handler(struct smb23x_chip *chip, u8 rt_sts) { pr_debug("rt_sts = 0x02%x\n", rt_sts); return 0; } static int chg_timeout_irq_handler(struct smb23x_chip *chip, u8 rt_sts) { pr_debug("rt_sts = 0x02%x\n", rt_sts); return 0; } static int pre_chg_timeout_irq_handler(struct smb23x_chip *chip, u8 rt_sts) { pr_debug("rt_sts = 0x02%x\n", rt_sts); return 0; } static int usbin_ov_irq_handler(struct smb23x_chip *chip, u8 rt_sts) { bool usb_present = !rt_sts; int health = !!rt_sts ? POWER_SUPPLY_HEALTH_OVERVOLTAGE : POWER_SUPPLY_HEALTH_GOOD; union power_supply_propval pval = {0, }; pr_debug("chip->usb_present = %d, usb_present = %d\n", chip->usb_present, usb_present); if (chip->usb_present != usb_present) { pval.intval = usb_present; power_supply_set_property(chip->usb_psy, POWER_SUPPLY_PROP_PRESENT, &pval); pval.intval = health; power_supply_set_property(chip->usb_psy, POWER_SUPPLY_PROP_HEALTH, &pval); } return 0; } #define IRQ_POLLING_MS 500 static void smb23x_irq_polling_work_fn(struct work_struct *work) { struct smb23x_chip *chip = container_of(work, struct smb23x_chip, irq_polling_work.work); smb23x_stat_handler(chip->client->irq, (void *)chip); if (chip->usb_present) schedule_delayed_work(&chip->irq_polling_work, msecs_to_jiffies(IRQ_POLLING_MS)); } #define DEBUG_BATT_ID_LOW 6000 #define DEBUG_BATT_ID_HIGH 9000 static bool is_debug_batt_id(struct smb23x_chip *chip) { pr_info("DEBUG_BATT: DEBUG BATT ID value is %d\n", chip->batt_id_ohm); if (is_between(DEBUG_BATT_ID_LOW, DEBUG_BATT_ID_HIGH, chip->batt_id_ohm)) return true; return false; } /* * On some of the parts, the Non-volatile register values will * be reloaded upon unplug event. Even the unplug event won't be * detected because the IRQ isn't enabled by default in chip * internally. * Use polling to detect the unplug event, and after that, redo * hw_init() to repropgram the software configurations. */ static void reconfig_upon_unplug(struct smb23x_chip *chip) { int rc; int reason; if (is_debug_batt_id(chip)) return; if (chip->workaround_flags & WRKRND_IRQ_POLLING) { if (chip->usb_present && chip->usb_poll) { pm_stay_awake(chip->dev); schedule_delayed_work(&chip->irq_polling_work, msecs_to_jiffies(IRQ_POLLING_MS)); chip->usb_poll = false; } else { pr_debug("restore software settings after unplug\n"); pm_relax(chip->dev); rc = smb23x_hw_init(chip); if (rc) pr_err("smb23x init upon unplug failed, rc=%d\n", rc); /* * Retore the CHARGE_EN && USB_SUSPEND bit * according to the status maintained in sw. */ mutex_lock(&chip->chg_disable_lock); reason = chip->chg_disabled_status; mutex_unlock(&chip->chg_disable_lock); rc = smb23x_charging_disable(chip, reason, !!reason ? true : false); if (rc < 0) pr_err("%s charging failed\n", !!reason ? "Disable" : "Enable"); mutex_lock(&chip->usb_suspend_lock); reason = chip->usb_suspended_status; mutex_unlock(&chip->usb_suspend_lock); if (!(chip->workaround_flags & WRKRND_USB_SUSPEND)) { rc = smb23x_suspend_usb(chip, reason, !!reason ? true : false); if (rc < 0) pr_err("%suspend USB failed\n", !!reason ? "S" : "Un-s"); } } } } static int usbin_uv_irq_handler(struct smb23x_chip *chip, u8 rt_sts) { bool usb_present = !rt_sts; union power_supply_propval pval = {0, }; pr_debug("chip->usb_present = %d, usb_present = %d\n", chip->usb_present, usb_present); if (chip->usb_present == usb_present) return 0; chip->usb_present = usb_present; if (!chip->cfg_apsd_disabled) reconfig_upon_unplug(chip); pval.intval = usb_present; power_supply_set_property(chip->usb_psy, POWER_SUPPLY_PROP_PRESENT, &pval); return 0; } static int power_ok_irq_handler(struct smb23x_chip *chip, u8 rt_sts) { pr_debug("rt_sts = 0x02%x\n", rt_sts); return 0; } static int die_temp_irq_handler(struct smb23x_chip *chip, u8 rt_sts) { pr_warn("rt_sts = 0x02%x\n", rt_sts); return 0; } static struct irq_handler_info handlers[] = { { IRQ_A_STATUS_REG, 0, 0, { { .name = "cold_soft", .smb_irq = cold_soft_irq_handler, }, { .name = "hot_soft", .smb_irq = hot_soft_irq_handler, }, { .name = "cold_hard", .smb_irq = cold_hard_irq_handler, }, { .name = "hot_hard", .smb_irq = hot_hard_irq_handler, }, }, }, { IRQ_B_STATUS_REG, 0, 0, { { .name = "p2f", .smb_irq = pre_to_fast_irq_handler, }, { .name = "batt_low", .smb_irq = batt_low_irq_handler, }, { .name = "batt_missing", .smb_irq = batt_missing_irq_handler, }, { .name = "batt_ov", .smb_irq = batt_ov_irq_handler, }, }, }, { IRQ_C_STATUS_REG, 0, 0, { { .name = "iterm", .smb_irq = iterm_irq_handler, }, { .name = "taper", .smb_irq = taper_irq_handler, }, { .name = "recharge", .smb_irq = recharge_irq_handler, }, { .name = "chg_error", .smb_irq = chg_error_irq_handler, }, }, }, { IRQ_D_STATUS_REG, 0, 0, { { .name = "pre_chg_timeout", .smb_irq = pre_chg_timeout_irq_handler, }, { .name = "chg_timeout", .smb_irq = chg_timeout_irq_handler, }, { .name = "aicl_done", .smb_irq = aicl_done_irq_handler, }, { .name = "src_detect", .smb_irq = src_detect_irq_handler, }, }, }, { CHG_STATUS_A_REG, 0, 0, { { .name = "die_temp", .smb_irq = die_temp_irq_handler, }, { .name = "power_ok", .smb_irq = power_ok_irq_handler, }, { .name = "usbin_uv", .smb_irq = usbin_uv_irq_handler, }, { .name = "usbin_ov", .smb_irq = usbin_ov_irq_handler, }, }, }, }; #define UPDATE_IRQ_STAT(irq_reg, value) \ handlers[irq_reg - IRQ_A_STATUS_REG].prev_val = value static int smb23x_determine_initial_status(struct smb23x_chip *chip) { int rc = 0; u8 reg; union power_supply_propval pval = {0, }; rc = smb23x_read(chip, IRQ_A_STATUS_REG, ®); if (rc < 0) { pr_err("Read IRQ_A failed, rc=%d\n", rc); return rc; } UPDATE_IRQ_STAT(IRQ_A_STATUS_REG, reg); if (reg & HOT_HARD_BIT) chip->batt_hot = true; else if (reg & COLD_HARD_BIT) chip->batt_cold = true; else if (reg & HOT_SOFT_BIT) chip->batt_warm = true; else if (reg & COLD_SOFT_BIT) chip->batt_cool = true; rc = iio_read_channel_processed(chip->batt_id_chan, &chip->batt_id_ohm); if (rc < 0) { pr_err("Failed to read BATT_ID over ADC, rc=%d\n", rc); return rc; } if (!is_debug_batt_id(chip)) chip->batt_present = true; else chip->batt_present = false; rc = smb23x_read(chip, IRQ_B_STATUS_REG, ®); if (rc < 0) { pr_err("Read IRQ_B failed, rc=%d\n", rc); return rc; } UPDATE_IRQ_STAT(IRQ_B_STATUS_REG, reg); if (reg & BATT_ABSENT_BIT) chip->batt_present = false; rc = smb23x_read(chip, IRQ_C_STATUS_REG, ®); if (rc < 0) { pr_err("Read IRQ_C failed, rc=%d\n", rc); return rc; } UPDATE_IRQ_STAT(IRQ_C_STATUS_REG, reg); if (reg & ITERM_BIT) chip->batt_full = true; rc = smb23x_read(chip, IRQ_D_STATUS_REG, ®); if (rc < 0) { pr_err("Read IRQ_D failed, rc=%d\n", rc); return rc; } UPDATE_IRQ_STAT(IRQ_D_STATUS_REG, reg); if (chip->apsd_enabled && (reg & APSD_DONE_BIT)) chip->usb_present = true; rc = smb23x_read(chip, CHG_STATUS_A_REG, ®); if (rc < 0) { pr_err("Read CHG_STATUS_A failed, rc=%d\n", rc); return rc; } UPDATE_IRQ_STAT(CHG_STATUS_A_REG, reg); if (!(reg & USBIN_OV_BIT) && !(reg & USBIN_UV_BIT)) { chip->usb_present = true; } else if (reg & USBIN_OV_BIT) { pval.intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE; power_supply_set_property(chip->usb_psy, POWER_SUPPLY_PROP_HEALTH, &pval); } if (chip->usb_present) { pval.intval = chip->usb_present; power_supply_set_property(chip->usb_psy, POWER_SUPPLY_PROP_PRESENT, &pval); chip->usb_poll = true; reconfig_upon_unplug(chip); } return rc; } static int smb23x_irq_read(struct smb23x_chip *chip) { int rc, i; for (i = 0; i < ARRAY_SIZE(handlers); i++) { rc = smb23x_read(chip, handlers[i].stat_reg, &handlers[i].val); if (rc < 0) { pr_err("Couldn't read %d rc = %d\n", handlers[i].stat_reg, rc); handlers[i].val = 0; continue; } } return rc; } #define IRQ_LATCHED_MASK 0x02 #define IRQ_STATUS_MASK 0x01 #define BITS_PER_IRQ 2 static irqreturn_t smb23x_stat_handler(int irq, void *dev_id) { struct smb23x_chip *chip = dev_id; int i, j; u8 triggered; u8 changed; u8 rt_stat, prev_rt_stat; int rc; int handler_count = 0; pr_debug("entering\n"); 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; smb23x_irq_read(chip); for (i = 0; i < ARRAY_SIZE(handlers); i++) { 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 < 0) 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); if (chip->usb_psy) { pr_debug("usb psy changed\n"); power_supply_changed(chip->usb_psy); } } mutex_unlock(&chip->irq_complete); return IRQ_HANDLED; } static enum power_supply_property smb23x_battery_properties[] = { POWER_SUPPLY_PROP_HEALTH, POWER_SUPPLY_PROP_STATUS, POWER_SUPPLY_PROP_PRESENT, POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, POWER_SUPPLY_PROP_CHARGE_TYPE, POWER_SUPPLY_PROP_CAPACITY, POWER_SUPPLY_PROP_TEMP, POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT, }; static int smb23x_get_prop_batt_health(struct smb23x_chip *chip) { int health; if (chip->batt_hot) health = POWER_SUPPLY_HEALTH_OVERHEAT; else if (chip->batt_cold) health = POWER_SUPPLY_HEALTH_COLD; else if (chip->batt_warm) health = POWER_SUPPLY_HEALTH_WARM; else if (chip->batt_cool) health = POWER_SUPPLY_HEALTH_COOL; else health = POWER_SUPPLY_HEALTH_GOOD; return health; } static int smb23x_get_prop_batt_status(struct smb23x_chip *chip) { int rc, status; u8 tmp; if (is_debug_batt_id(chip)) return POWER_SUPPLY_STATUS_NOT_CHARGING; if (chip->batt_full) return POWER_SUPPLY_STATUS_FULL; rc = smb23x_read(chip, CHG_STATUS_B_REG, &tmp); if (rc < 0) { pr_err("Read STATUS_B failed, rc=%d\n", rc); return POWER_SUPPLY_STATUS_UNKNOWN; } status = tmp & CHARGE_TYPE_MASK; return (status == NO_CHARGE_VAL) ? POWER_SUPPLY_STATUS_DISCHARGING : POWER_SUPPLY_STATUS_CHARGING; } static int smb23x_get_prop_charging_enabled(struct smb23x_chip *chip) { return !chip->usb_suspended_status; } static int smb23x_get_prop_batt_present(struct smb23x_chip *chip) { return chip->batt_present ? 1 : 0; } static int smb23x_get_prop_charge_type(struct smb23x_chip *chip) { int rc, status; u8 tmp; rc = smb23x_read(chip, CHG_STATUS_B_REG, &tmp); if (rc < 0) { pr_err("Read STATUS_B failed, rc=%d\n", rc); goto exit; } status = tmp & CHARGE_TYPE_MASK; if (status == NO_CHARGE_VAL) return POWER_SUPPLY_CHARGE_TYPE_NONE; else if (status == PRE_CHARGE_VAL) return POWER_SUPPLY_CHARGE_TYPE_TRICKLE; else if (status == FAST_CHARGE_VAL) return POWER_SUPPLY_CHARGE_TYPE_FAST; else if (status == TAPER_CHARGE_VAL) return POWER_SUPPLY_CHARGE_TYPE_ADAPTIVE; exit: return POWER_SUPPLY_CHARGE_TYPE_UNKNOWN; } #define DEFAULT_BATT_CAPACITY 50 #define DEBUG_BATT_CAPACITY 67 static int smb23x_get_prop_batt_capacity(struct smb23x_chip *chip) { union power_supply_propval ret = {0, }; if (is_debug_batt_id(chip)) return DEBUG_BATT_CAPACITY; if (chip->fake_battery_soc != -EINVAL) return chip->fake_battery_soc; if (chip->bms_psy) { power_supply_get_property(chip->bms_psy, POWER_SUPPLY_PROP_CAPACITY, &ret); return ret.intval; } return DEFAULT_BATT_CAPACITY; } #define DEFAULT_BATT_TEMP 280 static int smb23x_get_prop_batt_temp(struct smb23x_chip *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; } return DEFAULT_BATT_TEMP; } static int smb23x_system_temp_level_set(struct smb23x_chip *chip, int lvl_sel) { int rc = 0; if (!chip->cfg_thermal_mitigation) { pr_err("Thermal mitigation not supported\n"); return (-EINVAL); } if (lvl_sel < 0) { pr_err("Unsupported level selected %d\n", lvl_sel); return (-EINVAL); } if (lvl_sel >= chip->thermal_levels) { pr_err("Unsupported level selected %d forcing %d\n", lvl_sel, chip->thermal_levels - 1); lvl_sel = chip->thermal_levels - 1; } if (lvl_sel == chip->therm_lvl_sel) return 0; mutex_lock(&chip->icl_set_lock); chip->therm_lvl_sel = lvl_sel; rc = smb23x_set_appropriate_usb_current(chip); if (rc) pr_err("Couldn't set USB current rc = %d\n", rc); mutex_unlock(&chip->icl_set_lock); return rc; } static enum power_supply_usb_type smb23x_usb_psy_supported_types[] = { POWER_SUPPLY_USB_TYPE_UNKNOWN, POWER_SUPPLY_USB_TYPE_SDP, POWER_SUPPLY_USB_TYPE_DCP, POWER_SUPPLY_USB_TYPE_CDP, POWER_SUPPLY_USB_TYPE_ACA, }; static enum power_supply_property smb23x_usb_properties[] = { POWER_SUPPLY_PROP_PRESENT, POWER_SUPPLY_PROP_ONLINE, POWER_SUPPLY_PROP_CURRENT_MAX, POWER_SUPPLY_PROP_USB_TYPE, POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, }; static void smb23x_update_desc_type(struct smb23x_chip *chip) { switch (chip->charger_type) { case POWER_SUPPLY_TYPE_USB_CDP: case POWER_SUPPLY_TYPE_USB_DCP: case POWER_SUPPLY_TYPE_USB_ACA: chip->usb_psy_desc.type = chip->charger_type; reconfig_upon_unplug(chip); break; case POWER_SUPPLY_TYPE_USB: chip->usb_psy_desc.type = chip->charger_type; break; default: chip->usb_psy_desc.type = POWER_SUPPLY_TYPE_USB; break; } } static void smb23x_get_usb_type(struct smb23x_chip *chip, union power_supply_propval *val) { switch (chip->charger_type) { case POWER_SUPPLY_TYPE_USB_CDP: val->intval = POWER_SUPPLY_USB_TYPE_CDP; break; case POWER_SUPPLY_TYPE_USB_DCP: val->intval = POWER_SUPPLY_USB_TYPE_DCP; break; case POWER_SUPPLY_TYPE_USB: val->intval = POWER_SUPPLY_USB_TYPE_SDP; break; case POWER_SUPPLY_TYPE_USB_ACA: val->intval = POWER_SUPPLY_USB_TYPE_ACA; break; default: val->intval = POWER_SUPPLY_USB_TYPE_UNKNOWN; break; } } static int smb23x_usb_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) { struct smb23x_chip *chip = power_supply_get_drvdata(psy); switch (psp) { case POWER_SUPPLY_PROP_CURRENT_MAX: case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: val->intval = chip->usb_psy_ma; break; case POWER_SUPPLY_PROP_PRESENT: val->intval = chip->usb_present; break; case POWER_SUPPLY_PROP_ONLINE: val->intval = chip->usb_present && !chip->usb_suspended_status; break; case POWER_SUPPLY_PROP_USB_TYPE: smb23x_get_usb_type(chip, val); break; default: return -EINVAL; } return 0; } static int smb23x_usb_set_property(struct power_supply *psy, enum power_supply_property psp, const union power_supply_propval *val) { int rc = 0; struct smb23x_chip *chip = power_supply_get_drvdata(psy); switch (psp) { case POWER_SUPPLY_PROP_USB_TYPE: chip->charger_type = val->intval; smb23x_update_desc_type(chip); case POWER_SUPPLY_PROP_CURRENT_MAX: case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: if (!is_debug_batt_id(chip)) { chip->usb_psy_ma = val->intval / 1000; smb23x_enable_volatile_writes(chip); rc = smb23x_set_appropriate_usb_current(chip); if (rc) pr_err("Couldn't set USB current rc = %d\n", rc); break; } case POWER_SUPPLY_PROP_PRESENT: chip->usb_present = val->intval; break; default: return -EINVAL; } return 0; } static int smb23x_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 smb23x_init_usb_psy(struct smb23x_chip *chip) { struct power_supply_config usb_cfg = {}; int rc = 0; chip->usb_psy_desc.name = "usb", chip->usb_psy_desc.type = POWER_SUPPLY_TYPE_USB, chip->usb_psy_desc.properties = smb23x_usb_properties, chip->usb_psy_desc.num_properties = ARRAY_SIZE(smb23x_usb_properties), chip->usb_psy_desc.get_property = smb23x_usb_get_property, chip->usb_psy_desc.set_property = smb23x_usb_set_property, chip->usb_psy_desc.property_is_writeable = smb23x_usb_is_writeable, chip->usb_psy_desc.usb_types = smb23x_usb_psy_supported_types, chip->usb_psy_desc.num_usb_types = ARRAY_SIZE(smb23x_usb_psy_supported_types); chip->usb_psy_desc.num_properties = ARRAY_SIZE(smb23x_usb_properties); usb_cfg.drv_data = chip; usb_cfg.of_node = chip->dev->of_node; chip->usb_psy = devm_power_supply_register(chip->dev, &chip->usb_psy_desc, &usb_cfg); if (IS_ERR(chip->usb_psy)) { pr_err("Couldn't register usb power supply\n"); return PTR_ERR(chip->usb_psy); } return rc; } static int smb23x_battery_get_property(struct power_supply *psy, enum power_supply_property prop, union power_supply_propval *val) { struct smb23x_chip *chip = power_supply_get_drvdata(psy); switch (prop) { case POWER_SUPPLY_PROP_HEALTH: val->intval = smb23x_get_prop_batt_health(chip); break; case POWER_SUPPLY_PROP_STATUS: val->intval = smb23x_get_prop_batt_status(chip); break; case POWER_SUPPLY_PROP_PRESENT: val->intval = smb23x_get_prop_batt_present(chip); break; case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: val->intval = smb23x_get_prop_charging_enabled(chip); break; case POWER_SUPPLY_PROP_CHARGE_TYPE: val->intval = smb23x_get_prop_charge_type(chip); break; case POWER_SUPPLY_PROP_CAPACITY: val->intval = smb23x_get_prop_batt_capacity(chip); break; case POWER_SUPPLY_PROP_TEMP: val->intval = smb23x_get_prop_batt_temp(chip); break; case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT: val->intval = chip->therm_lvl_sel; break; default: return (-EINVAL); } pr_debug("get_property: prop(%d) = %d\n", (int)prop, (int)val->intval); return 0; } static int smb23x_battery_set_property(struct power_supply *psy, enum power_supply_property prop, const union power_supply_propval *val) { struct smb23x_chip *chip = power_supply_get_drvdata(psy); int rc; pr_debug("set_property: prop(%d) = %d\n", (int)prop, (int)val->intval); switch (prop) { case POWER_SUPPLY_PROP_STATUS: if (!chip->cfg_bms_controlled_charging) return (-EINVAL); switch (val->intval) { case POWER_SUPPLY_STATUS_FULL: pr_debug("BMS notify: battery FULL!\n"); chip->batt_full = true; rc = smb23x_charging_disable(chip, BMS, true); if (rc < 0) { pr_err("Disable charging for BMS failed, rc=%d\n", rc); return rc; } break; case POWER_SUPPLY_STATUS_CHARGING: pr_debug("BMS notify: battery CHARGING!\n"); chip->batt_full = false; rc = smb23x_charging_disable(chip, BMS, false); if (rc < 0) { pr_err("Enable charging for BMS failed, rc=%d\n", rc); return rc; } break; case POWER_SUPPLY_STATUS_DISCHARGING: pr_debug("BMS notify: battery DISCHARGING!\n"); chip->batt_full = false; break; default: break; } case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: smb23x_suspend_usb(chip, USER, !val->intval); power_supply_changed(chip->batt_psy); power_supply_changed(chip->usb_psy); break; case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT: smb23x_system_temp_level_set(chip, 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 smb23x_battery_is_writeable(struct power_supply *psy, enum power_supply_property prop) { int rc; switch (prop) { case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT: case POWER_SUPPLY_PROP_CAPACITY: rc = 1; break; default: rc = 0; break; } return rc; } #define LAST_CNFG_REG 0x1F static int show_cnfg_regs(struct seq_file *m, void *data) { struct smb23x_chip *chip = m->private; int rc; u8 reg; u8 addr; for (addr = CFG_REG_0; addr <= I2C_COMM_CFG_REG; addr++) { rc = smb23x_read(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 smb23x_chip *chip = inode->i_private; return single_open(file, show_cnfg_regs, chip); } static const struct file_operations cnfg_debugfs_ops = { .open = cnfg_debugfs_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; static int show_cmd_regs(struct seq_file *m, void *data) { struct smb23x_chip *chip = m->private; int rc; u8 reg; u8 addr; for (addr = CMD_REG_0; addr <= CMD_REG_1; addr++) { rc = smb23x_read(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 smb23x_chip *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, }; static int show_status_regs(struct seq_file *m, void *data) { struct smb23x_chip *chip = m->private; int rc; u8 reg; u8 addr; for (addr = IRQ_A_STATUS_REG; addr <= AICL_STATUS_REG; addr++) { rc = smb23x_read(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 smb23x_chip *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 smb23x_chip *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 smb23x_chip *chip = data; int rc; u8 temp; rc = smb23x_read(chip, chip->peek_poke_address, &temp); if (rc < 0) { 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 smb23x_chip *chip = data; int rc; u8 temp; temp = (u8) val; rc = smb23x_write(chip, chip->peek_poke_address, temp); if (rc < 0) { pr_err("Couldn't write 0x%02x to 0x%02x rc= %d\n", chip->peek_poke_address, temp, 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 smb23x_chip *chip = data; smb23x_stat_handler(chip->client->irq, data); return 0; } DEFINE_DEBUGFS_ATTRIBUTE(force_irq_ops, NULL, force_irq_set, "0x%02llx\n"); static int create_debugfs_entries(struct smb23x_chip *chip) { chip->debug_root = debugfs_create_dir("smb23x", NULL); if (!chip->debug_root) pr_err("Couldn't create debug dir\n"); if (chip->debug_root) { struct dentry *ent; ent = debugfs_create_file("config_registers", 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", 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", 0444, chip->debug_root, chip, &cmd_debugfs_ops); if (!ent) pr_err("Couldn't create cmd debug file\n"); debugfs_create_x32("address", 0644, chip->debug_root, &(chip->peek_poke_address)); ent = debugfs_create_file("data", 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", 0644, chip->debug_root, chip, &force_irq_ops); if (!ent) pr_err("Couldn't create force_irq debug file\n"); ent = debugfs_create_file("irq_count", 0444, chip->debug_root, chip, &irq_count_debugfs_ops); if (!ent) pr_err("Couldn't create irq_count debug file\n"); } return 0; } static void smb23x_irq_polling_wa_check(struct smb23x_chip *chip) { int rc; u8 reg; rc = smb23x_read(chip, NV_CFG_REG, ®); if (rc) { pr_err("Read NV_CFG_REG failed, rc=%d\n", rc); return; } if (!(reg & UNPLUG_RELOAD_DIS_BIT)) chip->workaround_flags |= WRKRND_IRQ_POLLING; pr_debug("use polling: %d\n", !(reg & UNPLUG_RELOAD_DIS_BIT)); } static const struct power_supply_desc batt_psy_desc = { .name = "battery", .type = POWER_SUPPLY_TYPE_BATTERY, .properties = smb23x_battery_properties, .num_properties = ARRAY_SIZE(smb23x_battery_properties), .get_property = smb23x_battery_get_property, .set_property = smb23x_battery_set_property, .property_is_writeable = smb23x_battery_is_writeable, }; static int smb23x_init_batt_psy(struct smb23x_chip *chip) { struct power_supply_config batt_cfg = {}; int rc = 0; batt_cfg.drv_data = chip; batt_cfg.of_node = chip->dev->of_node; chip->batt_psy = devm_power_supply_register(chip->dev, &batt_psy_desc, &batt_cfg); if (IS_ERR(chip->batt_psy)) { pr_err("Couldn't register battery power supply\n"); return PTR_ERR(chip->batt_psy); } return rc; } static int smb23x_probe(struct i2c_client *client, const struct i2c_device_id *id) { int rc; struct smb23x_chip *chip; chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL); if (chip == NULL) return (-ENOMEM); chip->client = client; chip->dev = &client->dev; chip->fake_battery_soc = -EINVAL; i2c_set_clientdata(client, chip); mutex_init(&chip->read_write_lock); mutex_init(&chip->irq_complete); mutex_init(&chip->chg_disable_lock); mutex_init(&chip->usb_suspend_lock); mutex_init(&chip->icl_set_lock); rc = device_init_wakeup(chip->dev, true); if (rc < 0) { dev_err(chip->dev, "init wakeup failed for smb23x chip, rc=%d\n", rc); return rc; } INIT_DELAYED_WORK(&chip->irq_polling_work, smb23x_irq_polling_work_fn); /* enable the USB_SUSPEND always */ chip->workaround_flags |= WRKRND_USB_SUSPEND; rc = smb23x_parse_dt(chip); if (rc < 0) { pr_err("Parse DT nodes failed!\n"); goto destroy_mutex; } chip->batt_id_chan = devm_iio_channel_get(chip->dev, "batt-id"); if (IS_ERR(chip->batt_id_chan)) { rc = PTR_ERR(chip->batt_id_chan); if (rc != -EPROBE_DEFER) dev_err(chip->dev, "batt-id channel unavailable, rc=%d\n", rc); chip->batt_id_chan = NULL; return rc; } /* * Enable register based battery charging as the hw_init moves CHG_EN * control from pin-based to register based. */ rc = smb23x_charging_disable(chip, USER, false); if (rc < 0) { pr_err("Register control based charging enable failed\n"); goto destroy_mutex; } rc = smb23x_hw_init(chip); if (rc < 0) { pr_err("Initialize hardware failed!\n"); goto destroy_mutex; } smb23x_irq_polling_wa_check(chip); smb23x_init_batt_psy(chip); smb23x_init_usb_psy(chip); rc = smb23x_determine_initial_status(chip); if (rc < 0) { pr_err("Update initial status failed\n"); goto destroy_mutex; } if (is_debug_batt_id(chip)) chip->debug_batt = true; /* * Disable charging if device tree (USER) requested: * set USB_SUSPEND to cutoff USB power completely */ rc = smb23x_suspend_usb(chip, USER, (chip->cfg_charging_disabled | chip->debug_batt) ? true : false); if (rc < 0) { pr_err("%suspend USB failed\n", chip->cfg_charging_disabled ? "S" : "Un-s"); goto destroy_mutex; } chip->resume_completed = true; /* Register IRQ */ if (client->irq) { rc = devm_request_threaded_irq(&client->dev, client->irq, NULL, smb23x_stat_handler, IRQF_ONESHOT, "smb23x_stat_irq", chip); if (rc < 0) { pr_err("Request IRQ(%d) failed, rc = %d\n", client->irq, rc); goto unregister_batt_psy; } enable_irq_wake(client->irq); } create_debugfs_entries(chip); pr_info("SMB23x successfully probed batt=%d usb = %d\n", smb23x_get_prop_batt_present(chip), chip->usb_present); return 0; unregister_batt_psy: power_supply_unregister(chip->batt_psy); destroy_mutex: mutex_destroy(&chip->read_write_lock); mutex_destroy(&chip->irq_complete); mutex_destroy(&chip->chg_disable_lock); mutex_destroy(&chip->usb_suspend_lock); mutex_destroy(&chip->icl_set_lock); return rc; } static int smb23x_freeze(struct device *dev) { struct i2c_client *client = to_i2c_client(dev); struct smb23x_chip *chip = i2c_get_clientdata(client); pr_debug("Entering hibernation via smb23x freeze\n"); disable_irq(client->irq); cancel_delayed_work_sync(&chip->irq_polling_work); return 0; } static int smb23x_restore(struct device *dev) { struct i2c_client *client = to_i2c_client(dev); struct smb23x_chip *chip = i2c_get_clientdata(client); int rc; pr_debug("Resuming from hibernation via smb23x restore\n"); devm_free_irq(&client->dev, client->irq, chip); /* * Enable register based battery charging as the hw_init moves CHG_EN * control from pin-based to register based. */ rc = smb23x_charging_disable(chip, USER, false); if (rc < 0) { pr_err("Register control based charging enable failed\n"); return rc; } rc = smb23x_hw_init(chip); if (rc < 0) { pr_err("smb23x hw init failed rc=%d\n", rc); return rc; } /* * Disable charging if device tree (USER) requested: * set USB_SUSPEND to cutoff USB power completely */ rc = smb23x_suspend_usb(chip, USER, chip->cfg_charging_disabled ? true : false); if (rc < 0) { pr_err("%suspend USB failed\n", chip->cfg_charging_disabled ? "S" : "Un-s"); return rc; } rc = smb23x_determine_initial_status(chip); if (rc < 0) { pr_err("Update initial status failed\n"); return rc; } smb23x_irq_polling_wa_check(chip); /* Register IRQ */ if (client->irq) { rc = devm_request_threaded_irq(&client->dev, client->irq, NULL, smb23x_stat_handler, IRQF_ONESHOT, "smb23x_stat_irq", chip); if (rc < 0) { pr_err("Request IRQ(%d) failed, rc = %d\n", client->irq, rc); return rc; } enable_irq_wake(client->irq); } pr_debug("Restoring all hardware context in smb23x restore\n"); return 0; } static int smb23x_suspend(struct device *dev) { struct i2c_client *client = to_i2c_client(dev); struct smb23x_chip *chip = i2c_get_clientdata(client); int rc; #ifdef CONFIG_DEEPSLEEP if (pm_suspend_via_firmware()) { pr_debug("Entering deepsleep via smb23x suspend\n"); smb23x_freeze(dev); } #endif /* Save the current IRQ config */ rc = smb23x_read(chip, IRQ_CFG_REG_9, &chip->irq_cfg_mask); if (rc < 0) pr_err("Save irq config failed, rc=%d\n", rc); /* enable only important IRQs */ rc = smb23x_write(chip, IRQ_CFG_REG_9, BATT_MISSING_IRQ_EN_BIT | INOK_IRQ_EN_BIT); if (rc < 0) pr_err("Set irq_cfg failed, rc = %d\n", rc); mutex_lock(&chip->irq_complete); chip->resume_completed = false; mutex_unlock(&chip->irq_complete); return 0; } static int smb23x_suspend_noirq(struct device *dev) { struct i2c_client *client = to_i2c_client(dev); struct smb23x_chip *chip = i2c_get_clientdata(client); if (chip->irq_waiting) { pr_err_ratelimited("Aborting suspend, an interrupt was detected while suspending\n"); return (-EBUSY); } return 0; } static int smb23x_resume(struct device *dev) { struct i2c_client *client = to_i2c_client(dev); struct smb23x_chip *chip = i2c_get_clientdata(client); int rc; rc = smb23x_write(chip, IRQ_CFG_REG_9, chip->irq_cfg_mask); if (rc) pr_err("Restore irq cfg reg failed, rc=%d\n", rc); #ifdef CONFIG_DEEPSLEEP if (pm_suspend_via_firmware()) { pr_debug("Resuming from Deepsleep via smb23x resume\n"); smb23x_restore(dev); } #endif mutex_lock(&chip->irq_complete); chip->resume_completed = true; if (chip->irq_waiting) { mutex_unlock(&chip->irq_complete); smb23x_stat_handler(client->irq, chip); enable_irq(client->irq); } else { mutex_unlock(&chip->irq_complete); } return 0; } static int smb23x_remove(struct i2c_client *client) { struct smb23x_chip *chip = i2c_get_clientdata(client); power_supply_unregister(chip->batt_psy); mutex_destroy(&chip->read_write_lock); mutex_destroy(&chip->irq_complete); mutex_destroy(&chip->chg_disable_lock); mutex_destroy(&chip->usb_suspend_lock); mutex_destroy(&chip->icl_set_lock); return 0; } static const struct dev_pm_ops smb23x_pm_ops = { .freeze = smb23x_freeze, .restore = smb23x_restore, .resume = smb23x_resume, .suspend_noirq = smb23x_suspend_noirq, .suspend = smb23x_suspend, }; static const struct of_device_id smb23x_match_table[] = { { .compatible = "qcom,smb231-lbc",}, { .compatible = "qcom,smb232-lbc",}, { .compatible = "qcom,smb233-lbc",}, { }, }; static const struct i2c_device_id smb23x_id[] = { {"smb23x-lbc", 0}, {}, }; MODULE_DEVICE_TABLE(i2c, smb23x_id); static struct i2c_driver smb23x_driver = { .driver = { .name = "smb23x-lbc", .of_match_table = smb23x_match_table, .pm = &smb23x_pm_ops, }, .probe = smb23x_probe, .remove = smb23x_remove, .id_table = smb23x_id, }; module_i2c_driver(smb23x_driver); MODULE_DESCRIPTION("SMB23x Linear Battery Charger"); MODULE_LICENSE("GPL v2"); MODULE_ALIAS("i2c:smb23x-lbc");