From a8f5aaa5286fe9693ed524ff69daab3fcb417183 Mon Sep 17 00:00:00 2001 From: Ravi Ranjan Kumar Date: Wed, 29 Nov 2023 20:19:04 +0530 Subject: [PATCH] power: smb358: Add snapshot of smb358 charger Add snapshot of the smb358 charger as of msm-5.4 commit b4f92443227f8 ("smb358: Add snapshot of smb358 charger"). Fix minor static analysis tool errors. Change-Id: I3610831152cc545a9fc2c20e4abb5c04989e7408 Signed-off-by: Ravi Ranjan Kumar --- drivers/power/supply/qcom/Kconfig | 10 + drivers/power/supply/qcom/Makefile | 1 + drivers/power/supply/qcom/smb358-charger.c | 2721 ++++++++++++++++++++ 3 files changed, 2732 insertions(+) create mode 100644 drivers/power/supply/qcom/smb358-charger.c diff --git a/drivers/power/supply/qcom/Kconfig b/drivers/power/supply/qcom/Kconfig index a37d24204b49..c0700c6e0caf 100644 --- a/drivers/power/supply/qcom/Kconfig +++ b/drivers/power/supply/qcom/Kconfig @@ -62,5 +62,15 @@ config SMB1355_SLAVE_CHARGER The driver reports the charger status via the power supply framework. A charger status change triggers an IRQ via the device STAT pin. +config SMB358_CHARGER + tristate "SMB358 Battery Charger" + depends on I2C + help + Say Y to include support for SMB358 Battery Charger. + SMB358 is a single path switching mode charger capable of charging + the battery with 2Amps of current. + 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. endif diff --git a/drivers/power/supply/qcom/Makefile b/drivers/power/supply/qcom/Makefile index 8afc991e4572..60880d3e430d 100644 --- a/drivers/power/supply/qcom/Makefile +++ b/drivers/power/supply/qcom/Makefile @@ -8,3 +8,4 @@ obj-$(CONFIG_SMB1398_CHARGER) += qcom-smb1398-charger.o qcom-smb1398-charger-y += smb1398-charger.o obj-$(CONFIG_SMB1355_SLAVE_CHARGER) += qcom-smb1355-charger.o qcom-smb1355-charger-y += smb1355-charger.o +obj-$(CONFIG_SMB358_CHARGER) += smb358-charger.o diff --git a/drivers/power/supply/qcom/smb358-charger.c b/drivers/power/supply/qcom/smb358-charger.c new file mode 100644 index 000000000000..b73a53f77093 --- /dev/null +++ b/drivers/power/supply/qcom/smb358-charger.c @@ -0,0 +1,2721 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2016-2021 The Linux Foundation. All rights reserved. + * Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved. + */ +#define pr_fmt(fmt) "SMB358 %s: " fmt, __func__ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define _SMB358_MASK(BITS, POS) \ + ((unsigned char)(((1 << (BITS)) - 1) << (POS))) +#define SMB358_MASK(LEFT_BIT_POS, RIGHT_BIT_POS) \ + _SMB358_MASK((LEFT_BIT_POS) - (RIGHT_BIT_POS) + 1, \ + (RIGHT_BIT_POS)) + +/* Config/Control registers */ +#define CHG_CURRENT_CTRL_REG 0x0 +#define CHG_OTH_CURRENT_CTRL_REG 0x1 +#define VARIOUS_FUNC_REG 0x2 +#define VFLOAT_REG 0x3 +#define CHG_CTRL_REG 0x4 +#define STAT_AND_TIMER_CTRL_REG 0x5 +#define CHG_PIN_EN_CTRL_REG 0x6 +#define THERM_A_CTRL_REG 0x7 +#define SYSOK_AND_USB3_REG 0x8 +#define OTHER_CTRL_REG 0x9 +#define FAULT_INT_REG 0xC +#define STATUS_INT_REG 0xD + +/* Command registers */ +#define CMD_A_REG 0x30 +#define CMD_B_REG 0x31 + +/* IRQ status registers */ +#define IRQ_A_REG 0x35 +#define IRQ_B_REG 0x36 +#define IRQ_C_REG 0x37 +#define IRQ_D_REG 0x38 +#define IRQ_E_REG 0x39 +#define IRQ_F_REG 0x3A + +/* Status registers */ +#define STATUS_C_REG 0x3D +#define STATUS_D_REG 0x3E +#define STATUS_E_REG 0x3F + +/* Config bits */ +#define CHG_INHI_EN_MASK BIT(1) +#define CHG_INHI_EN_BIT BIT(1) +#define CMD_A_CHG_ENABLE_BIT BIT(1) +#define CMD_A_VOLATILE_W_PERM_BIT BIT(7) +#define CMD_A_CHG_SUSP_EN_BIT BIT(2) +#define CMD_A_CHG_SUSP_EN_MASK BIT(2) +#define CMD_A_OTG_ENABLE_BIT BIT(4) +#define CMD_A_OTG_ENABLE_MASK BIT(4) +#define CMD_B_CHG_HC_ENABLE_BIT BIT(0) +#define USB3_ENABLE_BIT BIT(5) +#define USB3_ENABLE_MASK BIT(5) +#define CMD_B_CHG_USB_500_900_ENABLE_BIT BIT(1) +#define CHG_CTRL_AUTO_RECHARGE_ENABLE_BIT 0x0 +#define CHG_CTRL_CURR_TERM_END_CHG_BIT 0x0 +#define CHG_CTRL_BATT_MISSING_DET_THERM_IO SMB358_MASK(5, 4) +#define CHG_CTRL_AUTO_RECHARGE_MASK BIT(7) +#define CHG_AUTO_RECHARGE_DIS_BIT BIT(7) +#define CHG_CTRL_CURR_TERM_END_MASK BIT(6) +#define CHG_CTRL_BATT_MISSING_DET_MASK SMB358_MASK(5, 4) +#define CHG_CTRL_APSD_EN_BIT BIT(2) +#define CHG_CTRL_APSD_EN_MASK BIT(2) +#define CHG_ITERM_MASK 0x07 +#define CHG_PIN_CTRL_USBCS_REG_BIT 0x0 +/* This is to select if use external pin EN to control CHG */ +#define CHG_PIN_CTRL_CHG_EN_LOW_PIN_BIT SMB358_MASK(6, 5) +#define CHG_PIN_CTRL_CHG_EN_LOW_REG_BIT 0x0 +#define CHG_PIN_CTRL_CHG_EN_MASK SMB358_MASK(6, 5) + +#define CHG_LOW_BATT_THRESHOLD \ + SMB358_MASK(3, 0) +#define CHG_PIN_CTRL_USBCS_REG_MASK BIT(4) +#define CHG_PIN_CTRL_APSD_IRQ_BIT BIT(1) +#define CHG_PIN_CTRL_APSD_IRQ_MASK BIT(1) +#define CHG_PIN_CTRL_CHG_ERR_IRQ_BIT BIT(2) +#define CHG_PIN_CTRL_CHG_ERR_IRQ_MASK BIT(2) +#define VARIOUS_FUNC_USB_SUSP_EN_REG_BIT BIT(6) +#define VARIOUS_FUNC_USB_SUSP_MASK BIT(6) +#define FAULT_INT_HOT_COLD_HARD_BIT BIT(7) +#define FAULT_INT_HOT_COLD_SOFT_BIT BIT(6) +#define FAULT_INT_INPUT_OV_BIT BIT(3) +#define FAULT_INT_INPUT_UV_BIT BIT(2) +#define FAULT_INT_AICL_COMPLETE_BIT BIT(1) +#define STATUS_INT_CHG_TIMEOUT_BIT BIT(7) +#define STATUS_INT_OTG_DETECT_BIT BIT(6) +#define STATUS_INT_BATT_OV_BIT BIT(5) +#define STATUS_INT_CHGING_BIT BIT(4) +#define STATUS_INT_CHG_INHI_BIT BIT(3) +#define STATUS_INT_INOK_BIT BIT(2) +#define STATUS_INT_MISSING_BATT_BIT BIT(1) +#define STATUS_INT_LOW_BATT_BIT BIT(0) +#define THERM_A_THERM_MONITOR_EN_BIT 0x0 +#define THERM_A_THERM_MONITOR_EN_MASK BIT(4) +#define VFLOAT_MASK 0x3F + +/* IRQ status bits */ +#define IRQ_A_HOT_HARD_BIT BIT(6) +#define IRQ_A_COLD_HARD_BIT BIT(4) +#define IRQ_A_HOT_SOFT_BIT BIT(2) +#define IRQ_A_COLD_SOFT_BIT BIT(0) +#define IRQ_B_BATT_MISSING_BIT BIT(4) +#define IRQ_B_BATT_LOW_BIT BIT(2) +#define IRQ_B_BATT_OV_BIT BIT(6) +#define IRQ_B_PRE_FAST_CHG_BIT BIT(0) +#define IRQ_C_TAPER_CHG_BIT BIT(2) +#define IRQ_C_TERM_BIT BIT(0) +#define IRQ_C_INT_OVER_TEMP_BIT BIT(6) +#define IRQ_D_CHG_TIMEOUT_BIT (BIT(0) | BIT(2)) +#define IRQ_D_AICL_DONE_BIT BIT(4) +#define IRQ_D_APSD_COMPLETE BIT(6) +#define IRQ_E_INPUT_UV_BIT BIT(0) +#define IRQ_E_INPUT_OV_BIT BIT(2) +#define IRQ_E_AFVC_ACTIVE BIT(4) +#define IRQ_F_OTG_VALID_BIT BIT(2) +#define IRQ_F_OTG_BATT_FAIL_BIT BIT(4) +#define IRQ_F_OTG_OC_BIT BIT(6) +#define IRQ_F_POWER_OK BIT(0) + +/* Status bits */ +#define STATUS_C_CHARGING_MASK SMB358_MASK(2, 1) +#define STATUS_C_FAST_CHARGING BIT(2) +#define STATUS_C_PRE_CHARGING BIT(1) +#define STATUS_C_TAPER_CHARGING SMB358_MASK(2, 1) +#define STATUS_C_CHG_ERR_STATUS_BIT BIT(6) +#define STATUS_C_CHG_ENABLE_STATUS_BIT BIT(0) +#define STATUS_C_CHG_HOLD_OFF_BIT BIT(3) +#define STATUS_D_CHARGING_PORT_MASK \ + SMB358_MASK(3, 0) +#define STATUS_D_PORT_ACA_DOCK BIT(3) +#define STATUS_D_PORT_SDP BIT(2) +#define STATUS_D_PORT_DCP BIT(1) +#define STATUS_D_PORT_CDP BIT(0) +#define STATUS_D_PORT_OTHER SMB358_MASK(1, 0) +#define STATUS_D_PORT_ACA_A (BIT(2) | BIT(0)) +#define STATUS_D_PORT_ACA_B SMB358_MASK(2, 1) +#define STATUS_D_PORT_ACA_C SMB358_MASK(2, 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 AC_CHG_CURRENT_MASK 0x70 +#define AC_CHG_CURRENT_SHIFT 4 +#define SMB358_IRQ_REG_COUNT 6 +#define SMB358_FAST_CHG_MIN_MA 200 +#define SMB358_FAST_CHG_MAX_MA 2000 +#define SMB358_FAST_CHG_SHIFT 5 +#define SMB_FAST_CHG_CURRENT_MASK 0xE0 +#define SMB358_DEFAULT_BATT_CAPACITY 50 +#define SMB358_BATT_GOOD_THRE_2P5 0x1 + +enum { + USER = BIT(0), + THERMAL = BIT(1), + CURRENT = BIT(2), + SOC = BIT(3), + FAKE_BATTERY = BIT(4), +}; + +struct smb358_regulator { + struct regulator_desc rdesc; + struct regulator_dev *rdev; +}; + +static const unsigned int smb358_extcon_cable[] = { + EXTCON_USB, + EXTCON_USB_HOST, + EXTCON_CHG_USB_SDP, + EXTCON_CHG_USB_CDP, + EXTCON_CHG_USB_DCP, + EXTCON_CHG_USB_ACA, + EXTCON_NONE +}; + +struct smb358_iio { + struct iio_channel *batt_id_therm; + struct iio_channel *vbat_sns; +}; + +struct smb358_charger { + struct i2c_client *client; + struct device *dev; + + bool inhibit_disabled; + bool recharge_disabled; + int recharge_mv; + bool iterm_disabled; + int iterm_ma; + int vfloat_mv; + int chg_valid_gpio; + int chg_valid_act_low; + 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 smb358_iio iio; + bool bms_controlled_charging; + bool skip_usb_suspend_for_fake_battery; + struct mutex read_write_lock; + struct mutex path_suspend_lock; + struct mutex irq_complete; + u8 irq_cfg_mask[2]; + int irq_gpio; + int charging_disabled; + int fastchg_current_max_ma; + unsigned int connected_rid; + + /* debugfs related */ +#if defined(CONFIG_DEBUG_FS) + struct dentry *debug_root; + u32 peek_poke_address; +#endif + /* status tracking */ + bool batt_full; + bool batt_hot; + bool batt_cold; + bool batt_warm; + bool batt_cool; + int charging_disabled_status; + int usb_suspended; + + /* psy */ + struct power_supply_desc usb_psy_d; + struct power_supply *usb_psy; + struct power_supply *bms_psy; + struct power_supply_desc batt_psy_d; + struct power_supply *batt_psy; + int usb_psy_ma; + u8 usb_psy_health; + + /* otg 5V regulator */ + struct smb358_regulator otg_vreg; + + /* pinctrl parameters */ + const char *pinctrl_state_name; + struct pinctrl *smb_pinctrl; + + /* i2c pull up regulator */ + struct regulator *vcc_i2c; + struct extcon_dev *extcon; + u32 cable_id; + enum power_supply_type charger_type; +}; + +struct smb_irq_info { + const char *name; + int (*smb_irq)(struct smb358_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]; +}; + +static int chg_current[] = { + 300, 500, 700, 1000, 1200, 1500, 1800, 2000, +}; + +static int fast_chg_current[] = { + 200, 450, 600, 900, 1300, 1500, 1800, 2000, +}; + +/* add supplied to "bms" function */ +static char *pm_batt_supplied_to[] = { + "bms", +}; + +static int __smb358_read_reg(struct smb358_charger *chip, u8 reg, u8 *val) +{ + s32 ret; + + ret = i2c_smbus_read_byte_data(chip->client, reg); + if (ret < 0) { + dev_err(chip->dev, + "i2c read fail: can't read from %02x: %d\n", reg, ret); + return ret; + } + + *val = ret; + return 0; +} + +static int __smb358_write_reg(struct smb358_charger *chip, int reg, u8 val) +{ + s32 ret; + + ret = i2c_smbus_write_byte_data(chip->client, reg, val); + if (ret < 0) { + dev_err(chip->dev, + "i2c write fail: can't write %02x to %02x: %d\n", + val, reg, ret); + return ret; + } + return 0; +} + +static int smb358_read_reg(struct smb358_charger *chip, int reg, + u8 *val) +{ + int rc; + + mutex_lock(&chip->read_write_lock); + rc = __smb358_read_reg(chip, reg, val); + mutex_unlock(&chip->read_write_lock); + + return rc; +} + +static int smb358_write_reg(struct smb358_charger *chip, int reg, + u8 val) +{ + int rc; + + mutex_lock(&chip->read_write_lock); + rc = __smb358_write_reg(chip, reg, val); + mutex_unlock(&chip->read_write_lock); + + return rc; +} + +static int smb358_masked_write(struct smb358_charger *chip, int reg, + u8 mask, u8 val) +{ + s32 rc; + u8 temp; + + mutex_lock(&chip->read_write_lock); + rc = __smb358_read_reg(chip, reg, &temp); + if (rc) { + dev_err(chip->dev, + "smb358_read_reg Failed: reg=%03X, rc=%d\n", reg, rc); + goto out; + } + temp &= ~mask; + temp |= val & mask; + rc = __smb358_write_reg(chip, reg, temp); + if (rc) { + dev_err(chip->dev, + "smb358_write Failed: reg=%03X, rc=%d\n", reg, rc); + } +out: + mutex_unlock(&chip->read_write_lock); + return rc; +} + +static int smb358_enable_volatile_writes(struct smb358_charger *chip) +{ + int rc; + + rc = smb358_masked_write(chip, CMD_A_REG, CMD_A_VOLATILE_W_PERM_BIT, + CMD_A_VOLATILE_W_PERM_BIT); + if (rc) + dev_err(chip->dev, "Couldn't write VOLATILE_W_PERM_BIT rc=%d\n", + rc); + + return rc; +} + +static int smb358_fastchg_current_set(struct smb358_charger *chip, + unsigned int fastchg_current) +{ + int i; + + if ((fastchg_current < SMB358_FAST_CHG_MIN_MA) || + (fastchg_current > SMB358_FAST_CHG_MAX_MA)) { + dev_dbg(chip->dev, "bad fastchg current mA=%d asked to set\n", + fastchg_current); + return -EINVAL; + } + + for (i = ARRAY_SIZE(fast_chg_current) - 1; i >= 0; i--) { + if (fast_chg_current[i] <= fastchg_current) + break; + } + + if (i < 0) { + dev_err(chip->dev, "Invalid current setting %dmA\n", + fastchg_current); + i = 0; + } + + i = i << SMB358_FAST_CHG_SHIFT; + dev_dbg(chip->dev, "fastchg limit=%d setting %02x\n", + fastchg_current, i); + + return smb358_masked_write(chip, CHG_CURRENT_CTRL_REG, + SMB_FAST_CHG_CURRENT_MASK, i); +} + +#define MIN_FLOAT_MV 3500 +#define MAX_FLOAT_MV 4500 +#define VFLOAT_STEP_MV 20 +#define VFLOAT_4350MV (4300 + 50) +static int smb358_float_voltage_set(struct smb358_charger *chip, int vfloat_mv) +{ + u8 temp; + + if ((vfloat_mv < MIN_FLOAT_MV) || (vfloat_mv > MAX_FLOAT_MV)) { + dev_err(chip->dev, "bad float voltage mv =%d asked to set\n", + vfloat_mv); + return -EINVAL; + } + + if (vfloat_mv == VFLOAT_4350MV) + temp = 0x2B; + else if (vfloat_mv > VFLOAT_4350MV) + temp = (vfloat_mv - MIN_FLOAT_MV) / VFLOAT_STEP_MV + 1; + else + temp = (vfloat_mv - MIN_FLOAT_MV) / VFLOAT_STEP_MV; + + return smb358_masked_write(chip, VFLOAT_REG, VFLOAT_MASK, temp); +} + +#define CHG_ITERM_30MA 0x00 +#define CHG_ITERM_40MA 0x01 +#define CHG_ITERM_60MA 0x02 +#define CHG_ITERM_80MA 0x03 +#define CHG_ITERM_100MA 0x04 +#define CHG_ITERM_125MA 0x05 +#define CHG_ITERM_150MA 0x06 +#define CHG_ITERM_200MA 0x07 +static int smb358_term_current_set(struct smb358_charger *chip) +{ + u8 reg = 0; + int rc; + + if (chip->iterm_ma != -EINVAL) { + if (chip->iterm_disabled) + dev_err(chip->dev, "Error: Both iterm_disabled and iterm_ma set\n"); + + if (chip->iterm_ma <= 30) + reg = CHG_ITERM_30MA; + else if (chip->iterm_ma <= 40) + reg = CHG_ITERM_40MA; + else if (chip->iterm_ma <= 60) + reg = CHG_ITERM_60MA; + else if (chip->iterm_ma <= 80) + reg = CHG_ITERM_80MA; + else if (chip->iterm_ma <= 100) + reg = CHG_ITERM_100MA; + else if (chip->iterm_ma <= 125) + reg = CHG_ITERM_125MA; + else if (chip->iterm_ma <= 150) + reg = CHG_ITERM_150MA; + else + reg = CHG_ITERM_200MA; + + rc = smb358_masked_write(chip, CHG_CURRENT_CTRL_REG, + CHG_ITERM_MASK, reg); + if (rc) { + dev_err(chip->dev, + "Couldn't set iterm rc = %d\n", rc); + return rc; + } + } + + if (chip->iterm_disabled) { + rc = smb358_masked_write(chip, CHG_CTRL_REG, + CHG_CTRL_CURR_TERM_END_MASK, + CHG_CTRL_CURR_TERM_END_MASK); + if (rc) { + dev_err(chip->dev, "Couldn't set iterm rc = %d\n", + rc); + return rc; + } + } else { + rc = smb358_masked_write(chip, CHG_CTRL_REG, + CHG_CTRL_CURR_TERM_END_MASK, 0); + if (rc) { + dev_err(chip->dev, + "Couldn't enable iterm rc = %d\n", rc); + return rc; + } + } + + return 0; +} + +#define VFLT_300MV 0x0C +#define VFLT_200MV 0x08 +#define VFLT_100MV 0x04 +#define VFLT_50MV 0x00 +#define VFLT_MASK 0x0C +static int smb358_recharge_and_inhibit_set(struct smb358_charger *chip) +{ + u8 reg = 0; + int rc; + + if (chip->recharge_disabled) + rc = smb358_masked_write(chip, CHG_CTRL_REG, + CHG_CTRL_AUTO_RECHARGE_MASK, CHG_AUTO_RECHARGE_DIS_BIT); + else + rc = smb358_masked_write(chip, CHG_CTRL_REG, + CHG_CTRL_AUTO_RECHARGE_MASK, 0x0); + if (rc) { + dev_err(chip->dev, + "Couldn't set auto recharge en reg rc = %d\n", rc); + } + + if (chip->inhibit_disabled) + rc = smb358_masked_write(chip, CHG_OTH_CURRENT_CTRL_REG, + CHG_INHI_EN_MASK, 0x0); + else + rc = smb358_masked_write(chip, CHG_OTH_CURRENT_CTRL_REG, + CHG_INHI_EN_MASK, CHG_INHI_EN_BIT); + if (rc) { + dev_err(chip->dev, + "Couldn't set inhibit en reg rc = %d\n", rc); + } + + if (chip->recharge_mv != -EINVAL) { + if (chip->recharge_mv <= 50) + reg = VFLT_50MV; + else if (chip->recharge_mv <= 100) + reg = VFLT_100MV; + else if (chip->recharge_mv <= 200) + reg = VFLT_200MV; + else + reg = VFLT_300MV; + + rc = smb358_masked_write(chip, CHG_OTH_CURRENT_CTRL_REG, + VFLT_MASK, reg); + if (rc) { + dev_err(chip->dev, + "Couldn't set inhibit threshold rc = %d\n", rc); + return rc; + } + } + + return 0; +} + +static int smb358_chg_otg_regulator_enable(struct regulator_dev *rdev) +{ + int rc = 0; + struct smb358_charger *chip = rdev_get_drvdata(rdev); + + rc = smb358_masked_write(chip, CMD_A_REG, CMD_A_OTG_ENABLE_BIT, + CMD_A_OTG_ENABLE_BIT); + if (rc) + dev_err(chip->dev, "Couldn't enable OTG mode rc=%d, reg=%2x\n", + rc, CMD_A_REG); + return rc; +} + +static int smb358_chg_otg_regulator_disable(struct regulator_dev *rdev) +{ + int rc = 0; + struct smb358_charger *chip = rdev_get_drvdata(rdev); + + rc = smb358_masked_write(chip, CMD_A_REG, CMD_A_OTG_ENABLE_BIT, 0); + if (rc) + dev_err(chip->dev, "Couldn't disable OTG mode rc=%d, reg=%2x\n", + rc, CMD_A_REG); + return rc; +} + +static int smb358_chg_otg_regulator_is_enable(struct regulator_dev *rdev) +{ + int rc = 0; + u8 reg = 0; + struct smb358_charger *chip = rdev_get_drvdata(rdev); + + rc = smb358_read_reg(chip, CMD_A_REG, ®); + if (rc) { + dev_err(chip->dev, + "Couldn't read OTG enable bit rc=%d, reg=%2x\n", + rc, CMD_A_REG); + return rc; + } + + return (reg & CMD_A_OTG_ENABLE_BIT) ? 1 : 0; +} + +const struct regulator_ops smb358_chg_otg_reg_ops = { + .enable = smb358_chg_otg_regulator_enable, + .disable = smb358_chg_otg_regulator_disable, + .is_enabled = smb358_chg_otg_regulator_is_enable, +}; + +static int smb358_regulator_init(struct smb358_charger *chip) +{ + int rc = 0; + struct regulator_init_data *init_data; + struct regulator_config cfg = {}; + + chip->otg_vreg.rdesc.owner = THIS_MODULE; + chip->otg_vreg.rdesc.type = REGULATOR_VOLTAGE; + chip->otg_vreg.rdesc.ops = &smb358_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) { + dev_err(chip->dev, "Allocate memory failed\n"); + return -ENOMEM; + } + + cfg.dev = chip->dev; + cfg.init_data = init_data; + cfg.driver_data = chip; + cfg.of_node = chip->dev->of_node; + chip->otg_vreg.rdev = + devm_regulator_register(chip->dev, + &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) + dev_err(chip->dev, + "OTG reg failed, rc=%d\n", rc); + } + + return rc; +} + +static int __smb358_path_suspend(struct smb358_charger *chip, bool suspend) +{ + int rc; + + rc = smb358_masked_write(chip, CMD_A_REG, CMD_A_CHG_SUSP_EN_MASK, + suspend ? CMD_A_CHG_SUSP_EN_BIT : 0); + if (rc < 0) + dev_err(chip->dev, "Couldn't set CMD_A reg, rc = %d\n", rc); + + return rc; +} + +static int smb358_path_suspend(struct smb358_charger *chip, int reason, + bool suspend) +{ + int rc = 0; + int suspended; + + mutex_lock(&chip->path_suspend_lock); + suspended = chip->usb_suspended; + + if (!suspend) + suspended &= ~reason; + else + suspended |= reason; + + if (!chip->usb_suspended && suspended) { + rc = __smb358_path_suspend(chip, true); + chip->usb_suspended = suspended; + power_supply_changed(chip->usb_psy); + } else if (chip->usb_suspended && !suspended) { + rc = __smb358_path_suspend(chip, false); + chip->usb_suspended = suspended; + power_supply_changed(chip->usb_psy); + } + + if (rc) + dev_err(chip->dev, "Couldn't set/unset suspend rc = %d\n", rc); + + mutex_unlock(&chip->path_suspend_lock); + + return rc; +} + + +static int __smb358_charging_disable(struct smb358_charger *chip, bool disable) +{ + int rc; + + rc = smb358_masked_write(chip, CMD_A_REG, CMD_A_CHG_ENABLE_BIT, + disable ? 0 : CMD_A_CHG_ENABLE_BIT); + if (rc < 0) + pr_err("Couldn't set CHG_ENABLE_BIT disable = %d, rc = %d\n", + disable, rc); + return rc; +} + +static int smb358_charging_disable(struct smb358_charger *chip, + int reason, int disable) +{ + int rc = 0; + int disabled; + + disabled = chip->charging_disabled_status; + + pr_debug("reason = %d requested_disable = %d disabled_status = %d\n", + reason, disable, disabled); + + if (disable == true) + disabled |= reason; + else + disabled &= ~reason; + + if (!!disabled == !!chip->charging_disabled_status) + goto skip; + + rc = __smb358_charging_disable(chip, !!disabled); + if (rc) { + pr_err("Failed to disable charging rc = %d\n", rc); + return rc; + } + + /* will not modify online status in this condition */ + power_supply_changed(chip->batt_psy); + +skip: + chip->charging_disabled_status = disabled; + return rc; +} + +#define MAX_INV_BATT_ID 7700 +#define MIN_INV_BATT_ID 7300 +static int smb358_hw_init(struct smb358_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) { + dev_dbg(chip->dev, "Charger configured for autonomous mode\n"); + return 0; + } + + rc = smb358_enable_volatile_writes(chip); + if (rc) { + dev_err(chip->dev, "Couldn't configure volatile writes rc=%d\n", + rc); + return rc; + } + + /* setup defaults for CHG_CNTRL_REG */ + reg = CHG_CTRL_BATT_MISSING_DET_THERM_IO; + mask = CHG_CTRL_BATT_MISSING_DET_MASK; + rc = smb358_masked_write(chip, CHG_CTRL_REG, mask, reg); + if (rc) { + dev_err(chip->dev, "Couldn't set CHG_CTRL_REG rc=%d\n", rc); + return rc; + } + /* setup defaults for PIN_CTRL_REG */ + reg = CHG_PIN_CTRL_USBCS_REG_BIT | CHG_PIN_CTRL_CHG_EN_LOW_REG_BIT | + CHG_PIN_CTRL_APSD_IRQ_BIT | CHG_PIN_CTRL_CHG_ERR_IRQ_BIT; + mask = CHG_PIN_CTRL_CHG_EN_MASK | CHG_PIN_CTRL_USBCS_REG_MASK | + CHG_PIN_CTRL_APSD_IRQ_MASK | CHG_PIN_CTRL_CHG_ERR_IRQ_MASK; + rc = smb358_masked_write(chip, CHG_PIN_EN_CTRL_REG, mask, reg); + if (rc) { + dev_err(chip->dev, "Couldn't set CHG_PIN_EN_CTRL_REG rc=%d\n", + rc); + return rc; + } + + /* setup USB suspend and APSD */ + rc = smb358_masked_write(chip, VARIOUS_FUNC_REG, + VARIOUS_FUNC_USB_SUSP_MASK, VARIOUS_FUNC_USB_SUSP_EN_REG_BIT); + if (rc) { + dev_err(chip->dev, "Couldn't set VARIOUS_FUNC_REG rc=%d\n", + rc); + return rc; + } + + if (!chip->disable_apsd) + reg = CHG_CTRL_APSD_EN_BIT; + else + reg = 0; + + rc = smb358_masked_write(chip, CHG_CTRL_REG, + CHG_CTRL_APSD_EN_MASK, reg); + if (rc) { + dev_err(chip->dev, "Couldn't set CHG_CTRL_REG rc=%d\n", + rc); + return rc; + } + /* Fault and Status IRQ configuration */ + reg = FAULT_INT_HOT_COLD_HARD_BIT | FAULT_INT_HOT_COLD_SOFT_BIT + | FAULT_INT_INPUT_UV_BIT | FAULT_INT_AICL_COMPLETE_BIT + | FAULT_INT_INPUT_OV_BIT; + rc = smb358_write_reg(chip, FAULT_INT_REG, reg); + if (rc) { + dev_err(chip->dev, "Couldn't set FAULT_INT_REG rc=%d\n", rc); + return rc; + } + reg = STATUS_INT_CHG_TIMEOUT_BIT | STATUS_INT_OTG_DETECT_BIT | + STATUS_INT_BATT_OV_BIT | STATUS_INT_CHGING_BIT | + STATUS_INT_CHG_INHI_BIT | STATUS_INT_INOK_BIT | + STATUS_INT_LOW_BATT_BIT | STATUS_INT_MISSING_BATT_BIT; + rc = smb358_write_reg(chip, STATUS_INT_REG, reg); + if (rc) { + dev_err(chip->dev, "Couldn't set STATUS_INT_REG rc=%d\n", rc); + return rc; + } + /* setup THERM Monitor */ + rc = smb358_masked_write(chip, THERM_A_CTRL_REG, + THERM_A_THERM_MONITOR_EN_MASK, THERM_A_THERM_MONITOR_EN_BIT); + if (rc) { + dev_err(chip->dev, "Couldn't set THERM_A_CTRL_REG rc=%d\n", + rc); + return rc; + } + /* set the fast charge current limit */ + rc = smb358_fastchg_current_set(chip, chip->fastchg_current_max_ma); + if (rc) { + dev_err(chip->dev, "Couldn't set fastchg current rc=%d\n", rc); + return rc; + } + + /* set the float voltage */ + rc = smb358_float_voltage_set(chip, chip->vfloat_mv); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't set float voltage rc = %d\n", rc); + return rc; + } + + /* set iterm */ + rc = smb358_term_current_set(chip); + if (rc) + dev_err(chip->dev, "Couldn't set term current rc=%d\n", rc); + + /* set recharge */ + rc = smb358_recharge_and_inhibit_set(chip); + if (rc) + dev_err(chip->dev, "Couldn't set recharge para rc=%d\n", rc); + + /* suspend USB path for fake battery */ + if (!chip->skip_usb_suspend_for_fake_battery) { + if ((chip->connected_rid >= MIN_INV_BATT_ID) && + (chip->connected_rid <= MAX_INV_BATT_ID)) { + rc = smb358_path_suspend(chip, FAKE_BATTERY, true); + if (!rc) + dev_info(chip->dev, + "Suspended USB path reason FAKE_BATTERY\n"); + } + } + + /* enable/disable charging */ + if (chip->charging_disabled) { + rc = smb358_charging_disable(chip, USER, 1); + if (rc) + dev_err(chip->dev, "Couldn't '%s' charging rc = %d\n", + chip->charging_disabled ? "disable" : "enable", rc); + } else { + /* + * Enable charging explicitly, + * because not sure the default behavior. + */ + rc = __smb358_charging_disable(chip, 0); + if (rc) + dev_err(chip->dev, "Couldn't enable charging\n"); + } + + /* + * Workaround for recharge frequent issue: When battery is + * greater than 4.2v, and charging is disabled, charger + * stops switching. In such a case, system load is provided + * by battery rather than input, even though input is still + * there. Make reg09[0:3] to be a non-zero value which can + * keep the switcher active + */ + rc = smb358_masked_write(chip, OTHER_CTRL_REG, CHG_LOW_BATT_THRESHOLD, + SMB358_BATT_GOOD_THRE_2P5); + if (rc) + dev_err(chip->dev, "Couldn't write OTHER_CTRL_REG, rc = %d\n", + rc); + + return rc; +} + +static enum power_supply_property smb358_battery_properties[] = { + 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_HEALTH, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_MODEL_NAME, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_VOLTAGE_NOW, +}; + +static int smb358_get_prop_batt_status(struct smb358_charger *chip) +{ + int rc; + u8 reg = 0; + + if (chip->batt_full) + return POWER_SUPPLY_STATUS_FULL; + + rc = smb358_read_reg(chip, STATUS_C_REG, ®); + if (rc) { + dev_err(chip->dev, "Couldn't read STAT_C rc = %d\n", rc); + return POWER_SUPPLY_STATUS_UNKNOWN; + } + + dev_dbg(chip->dev, "%s: STATUS_C_REG=%x\n", __func__, reg); + + if (reg & STATUS_C_CHG_HOLD_OFF_BIT) + return POWER_SUPPLY_STATUS_NOT_CHARGING; + + if ((reg & STATUS_C_CHARGING_MASK) && + !(reg & STATUS_C_CHG_ERR_STATUS_BIT)) + return POWER_SUPPLY_STATUS_CHARGING; + + return POWER_SUPPLY_STATUS_DISCHARGING; +} + +static int smb358_get_prop_batt_present(struct smb358_charger *chip) +{ + return !chip->battery_missing; +} + +static int smb358_get_prop_batt_capacity(struct smb358_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 capacity\n"); + return SMB358_DEFAULT_BATT_CAPACITY; +} + +static int smb358_get_prop_charge_type(struct smb358_charger *chip) +{ + int rc; + u8 reg = 0; + + rc = smb358_read_reg(chip, STATUS_C_REG, ®); + if (rc) { + dev_err(chip->dev, "Couldn't read STAT_C rc = %d\n", rc); + return POWER_SUPPLY_CHARGE_TYPE_UNKNOWN; + } + + dev_dbg(chip->dev, "%s: STATUS_C_REG=%x\n", __func__, reg); + + reg &= STATUS_C_CHARGING_MASK; + + if (reg == STATUS_C_FAST_CHARGING) + return POWER_SUPPLY_CHARGE_TYPE_FAST; + else if (reg == STATUS_C_TAPER_CHARGING) + return POWER_SUPPLY_CHARGE_TYPE_ADAPTIVE; + else if (reg == STATUS_C_PRE_CHARGING) + return POWER_SUPPLY_CHARGE_TYPE_TRICKLE; + else + return POWER_SUPPLY_CHARGE_TYPE_NONE; +} + +static int smb358_get_prop_batt_health(struct smb358_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; +} + +#define DEFAULT_TEMP 250 +static int smb358_get_prop_batt_temp(struct smb358_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_TEMP; +} + + +static int +smb358_get_prop_battery_voltage_now(struct smb358_charger *chip) +{ + int vbat_sns_result = 0; + int rc = 0; + + if (chip->iio.vbat_sns) { + rc = iio_read_channel_processed(chip->iio.vbat_sns, + &vbat_sns_result); + if (rc < 0) { + pr_err("Unable to read vbat, rc = %d\n", rc); + return 0; + } + } + return vbat_sns_result; +} + +static int smb358_get_iio_channel(struct smb358_charger *chip, + const char *propname, struct iio_channel **chan) +{ + int rc = 0; + + rc = of_property_match_string(chip->dev->of_node, + "io-channel-names", propname); + if (rc < 0) + return 0; + + *chan = iio_channel_get(chip->dev, propname); + if (IS_ERR(*chan)) { + rc = PTR_ERR(*chan); + if (rc != -EPROBE_DEFER) + pr_err("%s channel unavailable, %d\n", + propname, rc); + *chan = NULL; + } + + return rc; +} + +static int smb358_set_usb_chg_current(struct smb358_charger *chip, + int current_ma) +{ + int i, rc = 0; + u8 reg1 = 0, reg2 = 0, mask = 0; + + dev_dbg(chip->dev, "%s: USB current_ma = %d\n", __func__, current_ma); + + if (chip->chg_autonomous_mode) { + dev_dbg(chip->dev, "%s: Charger in autonmous mode\n", __func__); + return 0; + } + + if (current_ma < USB3_MIN_CURRENT_MA && current_ma != 2) + current_ma = USB2_MIN_CURRENT_MA; + + if (current_ma == USB2_MIN_CURRENT_MA) { + /* USB 2.0 - 100mA */ + reg1 &= ~USB3_ENABLE_BIT; + reg2 &= ~CMD_B_CHG_USB_500_900_ENABLE_BIT; + } else if (current_ma == USB2_MAX_CURRENT_MA) { + /* USB 2.0 - 500mA */ + reg1 &= ~USB3_ENABLE_BIT; + reg2 |= CMD_B_CHG_USB_500_900_ENABLE_BIT; + } else if (current_ma == USB3_MAX_CURRENT_MA) { + /* USB 3.0 - 900mA */ + reg1 |= USB3_ENABLE_BIT; + reg2 |= CMD_B_CHG_USB_500_900_ENABLE_BIT; + } else if (current_ma > USB2_MAX_CURRENT_MA) { + /* HC mode - if none of the above */ + reg2 |= CMD_B_CHG_HC_ENABLE_BIT; + + for (i = ARRAY_SIZE(chg_current) - 1; i >= 0; i--) { + if (chg_current[i] <= current_ma) + break; + } + if (i < 0) { + dev_err(chip->dev, "Cannot find %dmA\n", current_ma); + i = 0; + } + + i = i << AC_CHG_CURRENT_SHIFT; + rc = smb358_masked_write(chip, CHG_OTH_CURRENT_CTRL_REG, + AC_CHG_CURRENT_MASK, i); + if (rc) + dev_err(chip->dev, "Couldn't set input mA rc=%d\n", rc); + } + + mask = CMD_B_CHG_HC_ENABLE_BIT | CMD_B_CHG_USB_500_900_ENABLE_BIT; + rc = smb358_masked_write(chip, CMD_B_REG, mask, reg2); + if (rc < 0) + dev_err(chip->dev, "Couldn't set charging mode rc = %d\n", rc); + + mask = USB3_ENABLE_MASK; + rc = smb358_masked_write(chip, SYSOK_AND_USB3_REG, mask, reg1); + if (rc < 0) + dev_err(chip->dev, "Couldn't set USB3 mode rc = %d\n", rc); + + /* Only set suspend bit when chg present and current_ma = 2 */ + if (current_ma == 2 && chip->chg_present) { + rc = smb358_path_suspend(chip, CURRENT, true); + if (rc < 0) + dev_err(chip->dev, "Couldn't suspend rc = %d\n", rc); + } else { + rc = smb358_path_suspend(chip, CURRENT, false); + if (rc < 0) + dev_err(chip->dev, "Couldn't set susp rc = %d\n", rc); + } + + return rc; +} + +static int +smb358_batt_property_is_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + switch (psp) { + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + case POWER_SUPPLY_PROP_CAPACITY: + return 1; + default: + break; + } + + return 0; +} + +static int bound_soc(int soc) +{ + soc = max(0, soc); + soc = min(soc, 100); + return soc; +} + +static int smb358_battery_set_property(struct power_supply *psy, + enum power_supply_property prop, + const union power_supply_propval *val) +{ + int rc; + struct smb358_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 = smb358_charging_disable(chip, SOC, true); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't set charging disable rc = %d\n", + rc); + } else { + chip->batt_full = true; + dev_dbg(chip->dev, "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); + dev_dbg(chip->dev, "status = DISCHARGING, batt_full = %d\n", + chip->batt_full); + break; + case POWER_SUPPLY_STATUS_CHARGING: + rc = smb358_charging_disable(chip, SOC, false); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't set charging disable rc = %d\n", + rc); + } else { + chip->batt_full = false; + dev_dbg(chip->dev, "status = CHARGING, batt_full = %d\n", + chip->batt_full); + } + break; + default: + return -EINVAL; + } + break; + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + smb358_charging_disable(chip, USER, !val->intval); + smb358_path_suspend(chip, USER, !val->intval); + break; + case POWER_SUPPLY_PROP_CAPACITY: + chip->fake_battery_soc = bound_soc(val->intval); + power_supply_changed(chip->batt_psy); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int smb358_battery_get_property(struct power_supply *psy, + enum power_supply_property prop, + union power_supply_propval *val) +{ + struct smb358_charger *chip = power_supply_get_drvdata(psy); + + switch (prop) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = smb358_get_prop_batt_status(chip); + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = smb358_get_prop_batt_present(chip); + break; + case POWER_SUPPLY_PROP_CAPACITY: + val->intval = smb358_get_prop_batt_capacity(chip); + break; + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + val->intval = !(chip->charging_disabled_status & USER); + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + val->intval = smb358_get_prop_charge_type(chip); + break; + case POWER_SUPPLY_PROP_HEALTH: + val->intval = smb358_get_prop_batt_health(chip); + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = POWER_SUPPLY_TECHNOLOGY_LION; + break; + case POWER_SUPPLY_PROP_MODEL_NAME: + val->strval = "SMB358"; + break; + case POWER_SUPPLY_PROP_TEMP: + val->intval = smb358_get_prop_batt_temp(chip); + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = smb358_get_prop_battery_voltage_now(chip); + break; + default: + return -EINVAL; + } + return 0; +} + +static void smb358_set_cable_id(struct smb358_charger *chip, + u32 id, bool state) +{ + + dev_dbg(chip->dev, "extcon notify cable %d state %d\n", id, state); + + extcon_set_state_sync(chip->extcon, chip->cable_id, false); + if (chip->cable_id == EXTCON_CHG_USB_SDP) + extcon_set_state_sync(chip->extcon, EXTCON_USB, false); + + extcon_set_state_sync(chip->extcon, id, state); + if (id == EXTCON_CHG_USB_SDP) + extcon_set_state_sync(chip->extcon, EXTCON_USB, state); + + chip->cable_id = id; +} + +static void smb358_update_desc_type(struct smb358_charger *chip) +{ + switch (chip->charger_type) { + case POWER_SUPPLY_TYPE_USB_CDP: + case POWER_SUPPLY_TYPE_USB_DCP: + case POWER_SUPPLY_TYPE_USB: + case POWER_SUPPLY_TYPE_USB_ACA: + chip->usb_psy_d.type = chip->charger_type; + break; + default: + chip->usb_psy_d.type = POWER_SUPPLY_TYPE_USB; + break; + } +} + +static int apsd_complete(struct smb358_charger *chip, u8 status) +{ + int rc = 0; + u8 reg = 0; + enum power_supply_type type = POWER_SUPPLY_TYPE_UNKNOWN; + u32 id = EXTCON_NONE; + + /* + * If apsd is disabled, charger detection is done by + * DCIN UV irq. + * status = ZERO - indicates charger removed, handled + * by DCIN UV irq + */ + if (chip->disable_apsd || status == 0) { + dev_dbg(chip->dev, "APSD %s, status = %d\n", + chip->disable_apsd ? "disabled" : "enabled", !!status); + return 0; + } + + rc = smb358_read_reg(chip, STATUS_D_REG, ®); + if (rc) { + dev_err(chip->dev, "Couldn't read STATUS D rc = %d\n", rc); + return rc; + } + + dev_dbg(chip->dev, "%s: STATUS_D_REG(0x3E)=%x\n", __func__, reg); + + switch (reg & STATUS_D_CHARGING_PORT_MASK) { + case STATUS_D_PORT_ACA_DOCK: + case STATUS_D_PORT_ACA_C: + case STATUS_D_PORT_ACA_B: + case STATUS_D_PORT_ACA_A: + type = POWER_SUPPLY_TYPE_USB_ACA; + id = EXTCON_CHG_USB_ACA; + break; + case STATUS_D_PORT_CDP: + type = POWER_SUPPLY_TYPE_USB_CDP; + id = EXTCON_CHG_USB_CDP; + break; + case STATUS_D_PORT_DCP: + type = POWER_SUPPLY_TYPE_USB_DCP; + id = EXTCON_CHG_USB_DCP; + break; + case STATUS_D_PORT_SDP: + type = POWER_SUPPLY_TYPE_USB; + id = EXTCON_CHG_USB_SDP; + break; + case STATUS_D_PORT_OTHER: + type = POWER_SUPPLY_TYPE_USB_DCP; + id = EXTCON_CHG_USB_DCP; + break; + default: + type = POWER_SUPPLY_TYPE_USB; + id = EXTCON_USB; + break; + } + + chip->chg_present = true; + chip->charger_type = type; + dev_dbg(chip->dev, + "APSD complete. USB type detected=%d chg_present=%d", + type, chip->chg_present); + + /* set the charge current as required */ + if (type == POWER_SUPPLY_TYPE_USB) + chip->usb_psy_ma = USB2_MAX_CURRENT_MA; + else /* DCP or CDP */ + chip->usb_psy_ma = DCP_MAX_CURRENT_MA; + + smb358_enable_volatile_writes(chip); + rc = smb358_set_usb_chg_current(chip, chip->usb_psy_ma); + if (rc < 0) + dev_dbg(chip->dev, "Failed to set USB current rc=%d\n", rc); + + smb358_set_cable_id(chip, id, true); + smb358_update_desc_type(chip); + + return 0; +} + +static int chg_uv(struct smb358_charger *chip, u8 status) +{ + int rc; + + /* use this to detect USB insertion only if !apsd */ + if (chip->disable_apsd && status == 0) { + chip->chg_present = true; + chip->charger_type = POWER_SUPPLY_TYPE_USB; + smb358_set_cable_id(chip, EXTCON_USB, true); + + if (chip->bms_controlled_charging) { + /* + * Disable SOC based USB suspend to enable + * charging on USB insertion. + */ + rc = smb358_charging_disable(chip, SOC, false); + if (rc < 0) + dev_err(chip->dev, + "Couldn't disable usb suspend rc = %d\n", + rc); + } + } + + if (status != 0) { + chip->chg_present = false; + /* + * we can't set usb_psy as UNKNOWN here, will lead + * USERSPACE issue + */ + smb358_set_cable_id(chip, EXTCON_USB, false); + } + + dev_dbg(chip->dev, "chip->chg_present = %d\n", chip->chg_present); + + return 0; +} + +static int chg_ov(struct smb358_charger *chip, u8 status) +{ + if (status) + chip->usb_psy_health = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + else + chip->usb_psy_health = POWER_SUPPLY_HEALTH_GOOD; + + power_supply_changed(chip->usb_psy); + return 0; +} + +#define STATUS_FAST_CHARGING BIT(6) +static int fast_chg(struct smb358_charger *chip, u8 status) +{ + if (status & STATUS_FAST_CHARGING) + chip->batt_full = false; + return 0; +} + +static int chg_term(struct smb358_charger *chip, u8 status) +{ + if (!chip->iterm_disabled) + chip->batt_full = !!status; + return 0; +} + +static int taper_chg(struct smb358_charger *chip, u8 status) +{ + return 0; +} + +static int chg_recharge(struct smb358_charger *chip, u8 status) +{ + /* to check the status mean */ + chip->batt_full = !status; + return 0; +} + +/* only for SMB thermal */ +static int hot_hard_handler(struct smb358_charger *chip, u8 status) +{ + pr_debug("status = 0x%02x\n", status); + chip->batt_hot = !!status; + return 0; +} +static int cold_hard_handler(struct smb358_charger *chip, u8 status) +{ + pr_debug("status = 0x%02x\n", status); + chip->batt_cold = !!status; + return 0; +} +static int hot_soft_handler(struct smb358_charger *chip, u8 status) +{ + pr_debug("status = 0x%02x\n", status); + chip->batt_warm = !!status; + return 0; +} +static int cold_soft_handler(struct smb358_charger *chip, u8 status) +{ + pr_debug("status = 0x%02x\n", status); + chip->batt_cool = !!status; + return 0; +} + +static int battery_missing(struct smb358_charger *chip, u8 status) +{ + chip->battery_missing = !!status; + return 0; +} + +static int otg_handler(struct smb358_charger *chip, u8 status) +{ + smb358_set_cable_id(chip, EXTCON_USB_HOST, !!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 = cold_soft_handler, + }, + { + .name = "hot_soft", + .smb_irq = hot_soft_handler, + }, + { + .name = "cold_hard", + .smb_irq = cold_hard_handler, + }, + { + .name = "hot_hard", + .smb_irq = hot_hard_handler, + }, + }, + }, + [1] = { + .stat_reg = IRQ_B_REG, + .val = 0, + .prev_val = 0, + .irq_info = { + { + .name = "chg_hot", + }, + { + .name = "vbat_low", + }, + { + .name = "battery_missing", + .smb_irq = battery_missing + }, + { + .name = "battery_ov", + }, + }, + }, + [2] = { + .stat_reg = IRQ_C_REG, + .val = 0, + .prev_val = 0, + .irq_info = { + { + .name = "chg_term", + .smb_irq = chg_term, + }, + { + .name = "taper", + .smb_irq = taper_chg, + }, + { + .name = "recharge", + .smb_irq = chg_recharge, + }, + { + .name = "fast_chg", + .smb_irq = fast_chg, + }, + }, + }, + [3] = { + .stat_reg = IRQ_D_REG, + .val = 0, + .prev_val = 0, + .irq_info = { + { + .name = "prechg_timeout", + }, + { + .name = "safety_timeout", + }, + { + .name = "aicl_complete", + }, + { + .name = "src_detect", + .smb_irq = apsd_complete, + }, + }, + }, + [4] = { + .stat_reg = IRQ_E_REG, + .val = 0, + .prev_val = 0, + .irq_info = { + { + .name = "usbin_uv", + .smb_irq = chg_uv, + }, + { + .name = "usbin_ov", + .smb_irq = chg_ov, + }, + { + .name = "unknown", + }, + { + .name = "unknown", + }, + }, + }, + [5] = { + .stat_reg = IRQ_F_REG, + .val = 0, + .prev_val = 0, + .irq_info = { + { + .name = "power_ok", + }, + { + .name = "otg_det", + .smb_irq = otg_handler, + }, + { + .name = "otg_batt_uv", + }, + { + .name = "otg_oc", + }, + }, + }, +}; + +#define IRQ_LATCHED_MASK 0x02 +#define IRQ_STATUS_MASK 0x01 +#define BITS_PER_IRQ 2 +static irqreturn_t smb358_chg_stat_handler(int irq, void *dev_id) +{ + struct smb358_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) { + dev_dbg(chip->dev, "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 = smb358_read_reg(chip, handlers[i].stat_reg, + &handlers[i].val); + if (rc < 0) { + dev_err(chip->dev, "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 < 0) + dev_err(chip->dev, + "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; + } + + dev_dbg(chip->dev, "handler count = %d\n", handler_count); + if (handler_count) { + dev_dbg(chip->dev, "batt psy changed\n"); + power_supply_changed(chip->batt_psy); + } + + mutex_unlock(&chip->irq_complete); + + return IRQ_HANDLED; +} + +static irqreturn_t smb358_chg_valid_handler(int irq, void *dev_id) +{ + struct smb358_charger *chip = dev_id; + int present; + + present = gpio_get_value_cansleep(chip->chg_valid_gpio); + if (present < 0) { + dev_err(chip->dev, "Couldn't read chg_valid gpio=%d\n", + chip->chg_valid_gpio); + return IRQ_HANDLED; + } + present ^= chip->chg_valid_act_low; + + dev_dbg(chip->dev, "%s: chg_present = %d\n", __func__, present); + + if (present != chip->chg_present) { + chip->chg_present = present; + dev_dbg(chip->dev, "%s updating usb_psy present=%d", + __func__, chip->chg_present); + power_supply_changed(chip->usb_psy); + } + + return IRQ_HANDLED; +} + +static void smb358_external_power_changed(struct power_supply *psy) +{ + struct smb358_charger *chip = power_supply_get_drvdata(psy); + union power_supply_propval prop = {0,}; + int rc, current_limit = 0; + + if (chip->bms_psy_name) + chip->bms_psy = + power_supply_get_by_name((char *)chip->bms_psy_name); + + rc = power_supply_get_property(chip->usb_psy, + POWER_SUPPLY_PROP_CURRENT_MAX, &prop); + if (rc) + dev_err(chip->dev, + "Couldn't read USB current_max property, rc=%d\n", + rc); + else + current_limit = prop.intval / 1000; + + smb358_enable_volatile_writes(chip); + smb358_set_usb_chg_current(chip, current_limit); + + dev_dbg(chip->dev, "current_limit = %d\n", current_limit); +} + +#if defined(CONFIG_DEBUG_FS) +#define LAST_CNFG_REG 0x13 +static int show_cnfg_regs(struct seq_file *m, void *data) +{ + struct smb358_charger *chip = m->private; + int rc; + u8 reg; + u8 addr; + + for (addr = 0; addr <= LAST_CNFG_REG; addr++) { + rc = smb358_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 smb358_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 0x33 +static int show_cmd_regs(struct seq_file *m, void *data) +{ + struct smb358_charger *chip = m->private; + int rc; + u8 reg; + u8 addr; + + for (addr = FIRST_CMD_REG; addr <= LAST_CMD_REG; addr++) { + rc = smb358_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 smb358_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 0x35 +#define LAST_STATUS_REG 0x3F +static int show_status_regs(struct seq_file *m, void *data) +{ + struct smb358_charger *chip = m->private; + int rc; + u8 reg; + u8 addr; + + for (addr = FIRST_STATUS_REG; addr <= LAST_STATUS_REG; addr++) { + rc = smb358_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 smb358_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 smb358_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 smb358_charger *chip = data; + int rc; + u8 temp; + + rc = smb358_read_reg(chip, chip->peek_poke_address, &temp); + if (rc < 0) { + dev_err(chip->dev, + "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 smb358_charger *chip = data; + int rc; + u8 temp; + + temp = (u8) val; + rc = smb358_write_reg(chip, chip->peek_poke_address, temp); + if (rc < 0) { + dev_err(chip->dev, + "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 smb358_charger *chip = data; + + smb358_chg_stat_handler(chip->client->irq, data); + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(force_irq_ops, NULL, force_irq_set, "0x%02llx\n"); +#endif + +#ifdef DEBUG +static void dump_regs(struct smb358_charger *chip) +{ + int rc; + u8 reg; + u8 addr; + + for (addr = 0; addr <= LAST_CNFG_REG; addr++) { + rc = smb358_read_reg(chip, addr, ®); + if (rc) + dev_err(chip->dev, "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 = smb358_read_reg(chip, addr, ®); + if (rc) + dev_err(chip->dev, "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 = smb358_read_reg(chip, addr, ®); + if (rc) + dev_err(chip->dev, "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 smb358_charger *chip) +{ +} +#endif + +static int smb_parse_batt_id(struct smb358_charger *chip) +{ + int rc = 0, rpull = 0, vref = 0; + int64_t denom, batt_id_uv, numerator; + struct device_node *node = chip->dev->of_node; + int batt_id_result = 0; + + rc = of_property_read_u32(node, "qcom,batt-id-vref-uv", &vref); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't read batt-id-vref-uv rc=%d\n", rc); + return rc; + } + + rc = of_property_read_u32(node, "qcom,batt-id-rpullup-kohm", &rpull); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't read batt-id-rpullup-kohm rc=%d\n", rc); + return rc; + } + + if (chip->iio.batt_id_therm) { + rc = iio_read_channel_processed(chip->iio.batt_id_therm, + &batt_id_result); + if (rc < 0) { + pr_err("Couldn't read batt id channel=%d, rc = %d\n", + VADC_LR_MUX2_BAT_ID, rc); + return -EINVAL; + } + } + + /* batt_id_result is in mv */ + batt_id_uv = batt_id_result * 1000; + + if (batt_id_uv == 0) { + /* vadc not correct or batt id line grounded, report 0 kohms */ + dev_warn(chip->dev, "batt_id_uv=0, batt-id grounded\n"); + return 0; + } + + numerator = batt_id_uv * rpull * 1000; + denom = vref - batt_id_uv; + + /* batt id connector might be open, return 0 kohms */ + if (denom == 0) + return 0; + + chip->connected_rid = div64_s64(numerator, denom); + + dev_dbg(chip->dev, + "batt_id_voltage=%lld numerator=%lld denom=%lld connected_rid=%d\n", + batt_id_uv, numerator, denom, chip->connected_rid); + return 0; +} + +static int smb_parse_dt(struct smb358_charger *chip) +{ + int rc; + enum of_gpio_flags gpio_flags; + struct device_node *node = chip->dev->of_node; + + if (!node) { + dev_err(chip->dev, "device tree info. missing\n"); + return -EINVAL; + } + + chip->charging_disabled = of_property_read_bool(node, + "qcom,charger-disabled"); + + chip->inhibit_disabled = of_property_read_bool(node, + "qcom,chg-inhibit-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"); + + rc = of_property_read_string(node, "qcom,bms-psy-name", + &chip->bms_psy_name); + if (rc) + chip->bms_psy_name = NULL; + + chip->chg_valid_gpio = of_get_named_gpio_flags(node, + "qcom,chg-valid-gpio", 0, &gpio_flags); + if (!gpio_is_valid(chip->chg_valid_gpio)) + dev_dbg(chip->dev, "Invalid chg-valid-gpio"); + else + chip->chg_valid_act_low = gpio_flags & OF_GPIO_ACTIVE_LOW; + + rc = of_property_read_u32(node, "qcom,fastchg-current-max-ma", + &chip->fastchg_current_max_ma); + if (rc) + chip->fastchg_current_max_ma = SMB358_FAST_CHG_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 < 0) + chip->iterm_ma = -EINVAL; + + rc = of_property_read_u32(node, "qcom,float-voltage-mv", + &chip->vfloat_mv); + if (rc < 0) { + chip->vfloat_mv = -EINVAL; + pr_err("float-voltage-mv property missing, exit\n"); + return -EINVAL; + } + + rc = of_property_read_u32(node, "qcom,recharge-mv", + &chip->recharge_mv); + if (rc < 0) + 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); + + /* Extract ADC channels */ + rc = smb358_get_iio_channel(chip, "vbat_sns", &chip->iio.vbat_sns); + if (rc < 0) + return rc; + + rc = smb358_get_iio_channel(chip, + "batt_id_therm", &chip->iio.batt_id_therm); + if (rc < 0) + return rc; + + if (of_get_property(node, "qcom,vcc-i2c-supply", NULL)) { + chip->vcc_i2c = devm_regulator_get(chip->dev, "vcc-i2c"); + if (IS_ERR(chip->vcc_i2c)) { + dev_err(chip->dev, + "%s: Failed to get vcc_i2c regulator\n", + __func__); + return PTR_ERR(chip->vcc_i2c); + } + } + + chip->skip_usb_suspend_for_fake_battery = of_property_read_bool(node, + "qcom,skip-usb-suspend-for-fake-battery"); + if (!chip->skip_usb_suspend_for_fake_battery) { + rc = smb_parse_batt_id(chip); + if (rc) { + dev_err(chip->dev, + "failed to read batt-id rc=%d\n", rc); + return rc; + } + } + + pr_debug("inhibit-disabled = %d, recharge-disabled = %d, recharge-mv = %d\n", + chip->inhibit_disabled, chip->recharge_disabled, + chip->recharge_mv); + pr_debug("vfloat-mv = %d, iterm-disabled = %d\n", + chip->vfloat_mv, chip->iterm_disabled); + pr_debug("fastchg-current = %d, charging-disabled = %d\n", + chip->fastchg_current_max_ma, chip->charging_disabled); + pr_debug("disable-apsd = %d bms = %s\n", + chip->disable_apsd, chip->bms_psy_name); + return 0; +} + +static int determine_initial_state(struct smb358_charger *chip) +{ + int rc; + u8 reg = 0; + + rc = smb358_read_reg(chip, IRQ_B_REG, ®); + if (rc) { + dev_err(chip->dev, "Couldn't read IRQ_B rc = %d\n", rc); + goto fail_init_status; + } + + rc = smb358_read_reg(chip, IRQ_C_REG, ®); + if (rc) { + dev_err(chip->dev, "Couldn't read IRQ_C rc = %d\n", rc); + goto fail_init_status; + } + chip->batt_full = (reg & IRQ_C_TERM_BIT) ? true : false; + + rc = smb358_read_reg(chip, IRQ_A_REG, ®); + if (rc < 0) { + dev_err(chip->dev, "Couldn't read irq A rc = %d\n", rc); + return rc; + } + + /* For current design, can ignore this */ + if (reg & IRQ_A_HOT_HARD_BIT) + chip->batt_hot = true; + if (reg & IRQ_A_COLD_HARD_BIT) + chip->batt_cold = true; + if (reg & IRQ_A_HOT_SOFT_BIT) + chip->batt_warm = true; + if (reg & IRQ_A_COLD_SOFT_BIT) + chip->batt_cool = true; + + rc = smb358_read_reg(chip, IRQ_E_REG, ®); + if (rc) { + dev_err(chip->dev, "Couldn't read IRQ_E rc = %d\n", rc); + goto fail_init_status; + } + + if (reg & IRQ_E_INPUT_UV_BIT) { + chg_uv(chip, 1); + } else { + chg_uv(chip, 0); + apsd_complete(chip, 1); + } + + return 0; + +fail_init_status: + dev_err(chip->dev, "Couldn't determine initial status\n"); + return rc; +} + +#if defined(CONFIG_DEBUG_FS) +static void smb358_debugfs_init(struct smb358_charger *chip) +{ + int rc; + + chip->debug_root = debugfs_create_dir("smb358", NULL); + if (!chip->debug_root) + dev_err(chip->dev, "Couldn't create debug dir\n"); + + if (chip->debug_root) { + struct dentry *ent; + + ent = debugfs_create_file("config_registers", S_IFREG | 0444, + chip->debug_root, chip, + &cnfg_debugfs_ops); + if (!ent || IS_ERR(ent)) { + rc = PTR_ERR(ent); + dev_err(chip->dev, + "Couldn't create cnfg debug file rc = %d\n", + rc); + } + + ent = debugfs_create_file("status_registers", S_IFREG | 0444, + chip->debug_root, chip, + &status_debugfs_ops); + if (!ent || IS_ERR(ent)) { + rc = PTR_ERR(ent); + dev_err(chip->dev, + "Couldn't create status debug file rc = %d\n", + rc); + } + + ent = debugfs_create_file("cmd_registers", S_IFREG | 0444, + chip->debug_root, chip, + &cmd_debugfs_ops); + if (!ent || IS_ERR(ent)) { + rc = PTR_ERR(ent); + dev_err(chip->dev, + "Couldn't create cmd debug file rc = %d\n", + rc); + } + + debugfs_create_x32("address", S_IFREG | 0644, + chip->debug_root, + &(chip->peek_poke_address)); + + ent = debugfs_create_file("data", S_IFREG | 0644, + chip->debug_root, chip, + &poke_poke_debug_ops); + if (!ent || IS_ERR(ent)) { + rc = PTR_ERR(ent); + dev_err(chip->dev, + "Couldn't create data debug file rc = %d\n", + rc); + } + + ent = debugfs_create_file("force_irq", + S_IFREG | 0644, + chip->debug_root, chip, + &force_irq_ops); + if (!ent || IS_ERR(ent)) { + rc = PTR_ERR(ent); + dev_err(chip->dev, + "Couldn't create force_irq debug file rc =%d\n", + rc); + } + + ent = debugfs_create_file("irq_count", S_IFREG | 0444, + chip->debug_root, chip, + &irq_count_debugfs_ops); + if (!ent || IS_ERR(ent)) { + rc = PTR_ERR(ent); + dev_err(chip->dev, + "Couldn't create cnfg irq_count file rc = %d\n", + rc); + } + } +} +#else +static void smb358_debugfs_init(struct smb358_charger *chip) +{ +} +#endif + +static char *smb358_usb_supplicants[] = { + "bms", +}; + +static enum power_supply_usb_type smb358_usb_psy_supported_types[] = { + POWER_SUPPLY_USB_TYPE_UNKNOWN, + POWER_SUPPLY_USB_TYPE_SDP, + POWER_SUPPLY_USB_TYPE_CDP, + POWER_SUPPLY_USB_TYPE_DCP, + POWER_SUPPLY_USB_TYPE_ACA, +}; + +static enum power_supply_property smb358_usb_properties[] = { + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_CURRENT_MAX, + POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, + POWER_SUPPLY_PROP_USB_TYPE, +}; + +static void smb358_get_usb_type(struct smb358_charger *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 smb358_usb_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct smb358_charger *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 * 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; + break; + case POWER_SUPPLY_PROP_USB_TYPE: + smb358_get_usb_type(chip, val); + break; + default: + return -EINVAL; + } + return 0; +} + +static int smb358_usb_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct smb358_charger *chip = power_supply_get_drvdata(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_CURRENT_MAX: + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + chip->usb_psy_ma = val->intval / 1000; + smb358_enable_volatile_writes(chip); + smb358_set_usb_chg_current(chip, chip->usb_psy_ma); + break; + default: + return -EINVAL; + } + + power_supply_changed(psy); + return 0; +} + +static int smb358_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; +} + +#define SMB_I2C_VTG_MIN_UV 1800000 +#define SMB_I2C_VTG_MAX_UV 1800000 +static int smb358_charger_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + u8 reg = 0; + int rc, irq; + struct smb358_charger *chip; + struct power_supply_config batt_psy_cfg = {}; + struct power_supply_config usb_psy_cfg = {}; + + 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, + smb358_extcon_cable); + if (IS_ERR(chip->extcon)) { + rc = PTR_ERR(chip->extcon); + dev_err(chip->dev, + "failed to allocate extcon device rc = %ld\n", + rc); + return rc; + } + + rc = smb_parse_dt(chip); + if (rc) { + dev_err(chip->dev, + "Couldn't parse DT nodes rc=%d\n", rc); + return rc; + } + + /* i2c pull up regulator configuration */ + if (chip->vcc_i2c) { + if (regulator_count_voltages(chip->vcc_i2c) > 0) { + rc = regulator_set_voltage(chip->vcc_i2c, + SMB_I2C_VTG_MIN_UV, SMB_I2C_VTG_MAX_UV); + if (rc) { + dev_err(&client->dev, + "regulator vcc_i2c set failed, rc = %d\n", + rc); + return rc; + } + } + + rc = regulator_enable(chip->vcc_i2c); + if (rc) { + dev_err(&client->dev, + "Regulator vcc_i2c enable failed rc = %d\n", + rc); + goto err_set_vtg_i2c; + } + } + + mutex_init(&chip->read_write_lock); + mutex_init(&chip->path_suspend_lock); + + /* probe the device to check if its actually connected */ + rc = smb358_read_reg(chip, CHG_OTH_CURRENT_CTRL_REG, ®); + if (rc) { + pr_err("Failed to detect SMB358, device absent, rc = %d\n", rc); + goto err_set_vtg_i2c; + } + + i2c_set_clientdata(client, chip); + + chip->usb_psy_d.name = "usb"; + chip->usb_psy_d.type = POWER_SUPPLY_TYPE_USB; + chip->usb_psy_d.get_property = smb358_usb_get_property; + chip->usb_psy_d.set_property = smb358_usb_set_property; + chip->usb_psy_d.properties = smb358_usb_properties; + chip->usb_psy_d.usb_types = smb358_usb_psy_supported_types; + chip->usb_psy_d.num_usb_types = ARRAY_SIZE(smb358_usb_psy_supported_types); + chip->usb_psy_d.num_properties = ARRAY_SIZE(smb358_usb_properties); + chip->usb_psy_d.property_is_writeable = smb358_usb_is_writeable; + + usb_psy_cfg.drv_data = chip; + usb_psy_cfg.supplied_to = smb358_usb_supplicants; + usb_psy_cfg.num_supplicants = ARRAY_SIZE(smb358_usb_supplicants); + + chip->usb_psy = devm_power_supply_register(chip->dev, + &chip->usb_psy_d, &usb_psy_cfg); + if (IS_ERR(chip->usb_psy)) { + rc = PTR_ERR(chip->usb_psy); + dev_err(chip->dev, + "Unable to register usb_psy rc = %ld\n", + rc); + return rc; + } + + rc = devm_extcon_dev_register(chip->dev, chip->extcon); + if (rc) { + dev_err(chip->dev, + "failed to register extcon device rc = %ld\n", + rc); + return rc; + } + + chip->batt_psy_d.name = "battery"; + chip->batt_psy_d.type = POWER_SUPPLY_TYPE_BATTERY; + chip->batt_psy_d.get_property = smb358_battery_get_property; + chip->batt_psy_d.set_property = smb358_battery_set_property; + chip->batt_psy_d.property_is_writeable = + smb358_batt_property_is_writeable; + chip->batt_psy_d.properties = smb358_battery_properties; + chip->batt_psy_d.num_properties = ARRAY_SIZE(smb358_battery_properties); + chip->batt_psy_d.external_power_changed = smb358_external_power_changed; + + 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)) { + rc = PTR_ERR(chip->batt_psy); + dev_err(chip->dev, + "Couldn't register batt psy rc=%ld\n", rc); + return rc; + } + + dump_regs(chip); + + rc = smb358_regulator_init(chip); + if (rc) { + dev_err(&client->dev, + "Couldn't initialize smb358 ragulator rc=%d\n", rc); + goto err_set_vtg_i2c; + } + + rc = smb358_hw_init(chip); + if (rc) { + dev_err(&client->dev, + "Couldn't initialize hardware rc=%d\n", rc); + goto err_set_vtg_i2c; + } + + rc = determine_initial_state(chip); + if (rc) { + dev_err(&client->dev, + "Couldn't determine initial state rc=%d\n", rc); + goto err_set_vtg_i2c; + } + + /* We will not use it by default */ + if (gpio_is_valid(chip->chg_valid_gpio)) { + rc = devm_gpio_request(chip->dev, + chip->chg_valid_gpio, + "smb358_chg_valid"); + if (rc) { + dev_err(&client->dev, + "gpio_request for %d failed rc=%d\n", + chip->chg_valid_gpio, rc); + goto err_set_vtg_i2c; + } + irq = gpio_to_irq(chip->chg_valid_gpio); + if (irq < 0) { + dev_err(&client->dev, + "Invalid chg_valid irq = %d\n", irq); + goto err_set_vtg_i2c; + } + rc = devm_request_threaded_irq(&client->dev, irq, + NULL, smb358_chg_valid_handler, + IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING | + IRQF_ONESHOT, + "smb358_chg_valid_irq", chip); + if (rc) { + dev_err(&client->dev, + "Failed request_irq irq=%d, gpio=%d rc=%d\n", + irq, chip->chg_valid_gpio, rc); + goto err_set_vtg_i2c; + } + smb358_chg_valid_handler(irq, chip); + enable_irq_wake(irq); + } + + /* STAT irq configuration */ + if (client->irq) { + rc = devm_request_threaded_irq(&client->dev, + client->irq, NULL, smb358_chg_stat_handler, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + "smb358_chg_stat_irq", chip); + if (rc) { + dev_err(&client->dev, + "Failed STAT irq=%d request rc = %d\n", + irq, rc); + goto err_set_vtg_i2c; + } + enable_irq_wake(client->irq); + } else { + goto err_set_vtg_i2c; + } + + smb358_debugfs_init(chip); + dump_regs(chip); + dev_info(chip->dev, + "SMB358 successfully probed. charger=%d, batt_present=%d\n", + chip->chg_present, smb358_get_prop_batt_present(chip)); + + return 0; + +err_set_vtg_i2c: + if (chip->vcc_i2c) + if (regulator_count_voltages(chip->vcc_i2c) > 0) + regulator_set_voltage(chip->vcc_i2c, 0, + SMB_I2C_VTG_MAX_UV); + return rc; +} + +static void smb358_charger_remove(struct i2c_client *client) +{ + struct smb358_charger *chip = i2c_get_clientdata(client); + + if (chip->vcc_i2c) + regulator_disable(chip->vcc_i2c); + + mutex_destroy(&chip->irq_complete); + debugfs_remove_recursive(chip->debug_root); +} + +static int smb358_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct smb358_charger *chip = i2c_get_clientdata(client); + int rc; + int i; + + for (i = 0; i < 2; i++) { + rc = smb358_read_reg(chip, FAULT_INT_REG + i, + &chip->irq_cfg_mask[i]); + if (rc) + dev_err(chip->dev, + "Couldn't save irq cfg regs rc = %d\n", rc); + } + + /* enable wake up IRQs */ + rc = smb358_write_reg(chip, FAULT_INT_REG, + FAULT_INT_HOT_COLD_HARD_BIT | FAULT_INT_INPUT_UV_BIT); + if (rc < 0) + dev_err(chip->dev, "Couldn't set fault_irq_cfg rc = %d\n", rc); + + rc = smb358_write_reg(chip, STATUS_INT_REG, + STATUS_INT_LOW_BATT_BIT | STATUS_INT_MISSING_BATT_BIT | + STATUS_INT_CHGING_BIT | STATUS_INT_INOK_BIT | + STATUS_INT_OTG_DETECT_BIT | STATUS_INT_CHG_INHI_BIT); + if (rc < 0) + dev_err(chip->dev, + "Couldn't set status_irq_cfg rc = %d\n", rc); + + mutex_lock(&chip->irq_complete); + if (chip->vcc_i2c) { + rc = regulator_disable(chip->vcc_i2c); + if (rc) { + dev_err(chip->dev, + "Regulator vcc_i2c disable failed rc=%d\n", rc); + mutex_unlock(&chip->irq_complete); + return rc; + } + } + + chip->resume_completed = false; + mutex_unlock(&chip->irq_complete); + return 0; +} + +static int smb358_suspend_noirq(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct smb358_charger *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 smb358_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct smb358_charger *chip = i2c_get_clientdata(client); + int rc; + int i; + + if (chip->vcc_i2c) { + rc = regulator_enable(chip->vcc_i2c); + if (rc) { + dev_err(chip->dev, + "Regulator vcc_i2c enable failed rc=%d\n", rc); + return rc; + } + } + /* Restore IRQ config */ + for (i = 0; i < 2; i++) { + rc = smb358_write_reg(chip, FAULT_INT_REG + i, + chip->irq_cfg_mask[i]); + if (rc) + dev_err(chip->dev, + "Couldn't restore irq cfg regs rc=%d\n", rc); + } + + mutex_lock(&chip->irq_complete); + chip->resume_completed = true; + mutex_unlock(&chip->irq_complete); + if (chip->irq_waiting) { + smb358_chg_stat_handler(client->irq, chip); + enable_irq(client->irq); + } + return 0; +} + +static const struct dev_pm_ops smb358_pm_ops = { + .suspend = smb358_suspend, + .suspend_noirq = smb358_suspend_noirq, + .resume = smb358_resume, +}; + +static const struct of_device_id smb358_match_table[] = { + { .compatible = "qcom,smb358-charger",}, + { }, +}; + +static const struct i2c_device_id smb358_charger_id[] = { + {"smb358-charger", 0}, + {}, +}; +MODULE_DEVICE_TABLE(i2c, smb358_charger_id); + +static struct i2c_driver smb358_charger_driver = { + .driver = { + .name = "smb358-charger", + .of_match_table = smb358_match_table, + .pm = &smb358_pm_ops, + }, + .probe = smb358_charger_probe, + .remove = smb358_charger_remove, + .id_table = smb358_charger_id, +}; + +module_i2c_driver(smb358_charger_driver); + +MODULE_DESCRIPTION("SMB358 Charger"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("i2c:smb358-charger");