// 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 <linux/i2c.h>
#include <linux/delay.h>
#include <linux/errno.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/iio/iio.h>
#include <linux/iio/consumer.h>
#include <linux/power_supply.h>
#include <linux/of.h>
#include <linux/bitops.h>
#include <linux/mutex.h>
#include <linux/debugfs.h>
#include <linux/pm_wakeup.h>
#include <linux/spinlock.h>
#include <linux/suspend.h>

#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, &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, &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, &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, &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, &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, &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, &reg);
		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, &reg);
		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, &reg);
		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, &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");
