/* Copyright 2020 The Chromium OS Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 *
 * Silicon Mitus SM5803 Buck-Boost Charger
 */
#include "atomic.h"
#include "battery.h"
#include "battery_smart.h"
#include "charge_state_v2.h"
#include "charger.h"
#include "extpower.h"
#include "gpio.h"
#include "hooks.h"
#include "i2c.h"
#include "sm5803.h"
#include "system.h"
#include "stdbool.h"
#include "throttle_ap.h"
#include "timer.h"
#include "usb_charge.h"
#include "usb_pd.h"
#include "usbc_ocp.h"
#include "util.h"
#include "watchdog.h"

#ifndef CONFIG_CHARGER_NARROW_VDC
#error "SM5803 is a NVDC charger, please enable CONFIG_CHARGER_NARROW_VDC."
#endif

/* Console output macros */
#define CPRINTF(format, args...) cprintf(CC_CHARGER, format, ## args)
#define CPRINTS(format, args...) cprints(CC_CHARGER, format, ## args)

#define UNKNOWN_DEV_ID -1
static int dev_id = UNKNOWN_DEV_ID;

static const struct charger_info sm5803_charger_info = {
	.name         = CHARGER_NAME,
	.voltage_max  = CHARGE_V_MAX,
	.voltage_min  = CHARGE_V_MIN,
	.voltage_step = CHARGE_V_STEP,
	.current_max  = CHARGE_I_MAX,
	.current_min  = CHARGE_I_MIN,
	.current_step = CHARGE_I_STEP,
	.input_current_max  = INPUT_I_MAX,
	.input_current_min  = INPUT_I_MIN,
	.input_current_step = INPUT_I_STEP,
};

static atomic_t irq_pending; /* Bitmask of chips with interrupts pending */

static struct mutex flow1_access_lock[CHARGER_NUM];
static struct mutex flow2_access_lock[CHARGER_NUM];

static int charger_vbus[CHARGER_NUM];

/* Tracker for charging failures per port */
struct {
	int count;
	timestamp_t time;
} failure_tracker[CHARGER_NUM] = {};

/* Port to restart charging on */
static int active_restart_port = CHARGE_PORT_NONE;

/*
 * If powered from the sub board port, we need to attempt to enable the BFET
 * before proceeding with charging.
 */
static int attempt_bfet_enable;

/*
 * Note if auto fast charge for the primary port is disabled due to a
 * disconnected battery, at re-enable auto fast charge later when the battery
 * has connected.
 */
static bool fast_charge_disabled;


#define CHARGING_FAILURE_MAX_COUNT	5
#define CHARGING_FAILURE_INTERVAL	MINUTE

static int sm5803_is_sourcing_otg_power(int chgnum, int port);
static enum ec_error_list sm5803_get_dev_id(int chgnum, int *id);
static enum ec_error_list sm5803_set_current(int chgnum, int current);

static inline enum ec_error_list chg_read8(int chgnum, int offset, int *value)
{
	return i2c_read8(chg_chips[chgnum].i2c_port,
			 chg_chips[chgnum].i2c_addr_flags,
			 offset, value);
}

static inline enum ec_error_list chg_write8(int chgnum, int offset, int value)
{
	return i2c_write8(chg_chips[chgnum].i2c_port,
			  chg_chips[chgnum].i2c_addr_flags,
			  offset, value);
}

static inline enum ec_error_list meas_read8(int chgnum, int offset, int *value)
{
	return i2c_read8(chg_chips[chgnum].i2c_port,
			 SM5803_ADDR_MEAS_FLAGS,
			 offset, value);
}

static inline enum ec_error_list meas_write8(int chgnum, int offset, int value)
{
	return i2c_write8(chg_chips[chgnum].i2c_port,
			 SM5803_ADDR_MEAS_FLAGS,
			 offset, value);
}

static inline enum ec_error_list main_read8(int chgnum, int offset, int *value)
{
	return i2c_read8(chg_chips[chgnum].i2c_port,
			 SM5803_ADDR_MAIN_FLAGS,
			 offset, value);
}

static inline enum ec_error_list main_write8(int chgnum, int offset, int value)
{
	return i2c_write8(chg_chips[chgnum].i2c_port,
			 SM5803_ADDR_MAIN_FLAGS,
			 offset, value);
}

static inline enum ec_error_list test_write8(int chgnum, int offset, int value)
{
	return i2c_write8(chg_chips[chgnum].i2c_port,
			 SM5803_ADDR_TEST_FLAGS,
			 offset, value);
}

static inline enum ec_error_list test_update8(int chgnum, const int offset,
					      const uint8_t mask,
					const enum mask_update_action action)
{
	return i2c_update8(chg_chips[chgnum].i2c_port,
			   SM5803_ADDR_TEST_FLAGS, offset, mask, action);
}

static enum ec_error_list sm5803_flow1_update(int chgnum, const uint8_t mask,
					const enum mask_update_action action)
{
	int rv;

	/* Safety checks done, onto the actual register update */
	mutex_lock(&flow1_access_lock[chgnum]);

	rv = i2c_update8(chg_chips[chgnum].i2c_port,
			 chg_chips[chgnum].i2c_addr_flags,
			 SM5803_REG_FLOW1,
			 mask, action);

	mutex_unlock(&flow1_access_lock[chgnum]);

	return rv;
}

static enum ec_error_list sm5803_flow2_update(int chgnum, const uint8_t mask,
					const enum mask_update_action action)
{
	int rv;

	mutex_lock(&flow2_access_lock[chgnum]);

	rv = i2c_update8(chg_chips[chgnum].i2c_port,
			 chg_chips[chgnum].i2c_addr_flags,
			 SM5803_REG_FLOW2,
			 mask, action);

	mutex_unlock(&flow2_access_lock[chgnum]);

	return rv;
}

static bool is_platform_id_2s(uint32_t platform_id)
{
	return platform_id >= 0x06 && platform_id <= 0x0D;
}

static bool is_platform_id_3s(uint32_t platform_id)
{
	return platform_id >= 0x0E && platform_id <= 0x16;
}

int sm5803_is_vbus_present(int chgnum)
{
	return charger_vbus[chgnum];
}

enum ec_error_list sm5803_configure_gpio0(int chgnum,
					  enum sm5803_gpio0_modes mode, int od)
{
	enum ec_error_list rv;
	int reg;

	rv = main_read8(chgnum, SM5803_REG_GPIO0_CTRL, &reg);
	if (rv)
		return rv;

	reg &= ~SM5803_GPIO0_MODE_MASK;
	reg |= mode << 1;

	if (od)
		reg |= SM5803_GPIO0_OPEN_DRAIN_EN;
	else
		reg &= ~SM5803_GPIO0_OPEN_DRAIN_EN;

	rv = main_write8(chgnum, SM5803_REG_GPIO0_CTRL, reg);
	return rv;
}

enum ec_error_list sm5803_set_gpio0_level(int chgnum, int level)
{
	enum ec_error_list rv;
	int reg;

	rv = main_read8(chgnum, SM5803_REG_GPIO0_CTRL, &reg);
	if (rv)
		return rv;

	if (level)
		reg |= SM5803_GPIO0_VAL;
	else
		reg &= ~SM5803_GPIO0_VAL;

	rv = main_write8(chgnum, SM5803_REG_GPIO0_CTRL, reg);
	return rv;
}

enum ec_error_list sm5803_configure_chg_det_od(int chgnum, int enable)
{
	enum ec_error_list rv;
	int reg;

	rv = main_read8(chgnum, SM5803_REG_GPIO0_CTRL, &reg);
	if (rv)
		return rv;

	if (enable)
		reg |= SM5803_CHG_DET_OPEN_DRAIN_EN;
	else
		reg &= ~SM5803_CHG_DET_OPEN_DRAIN_EN;

	rv = main_write8(chgnum, SM5803_REG_GPIO0_CTRL, reg);
	return rv;
}

enum ec_error_list sm5803_get_chg_det(int chgnum, int *chg_det)
{
	enum ec_error_list rv;
	int reg;

	rv = main_read8(chgnum, SM5803_REG_STATUS1, &reg);
	if (rv)
		return rv;

	*chg_det = (reg & SM5803_STATUS1_CHG_DET) != 0;

	return EC_SUCCESS;
}

enum ec_error_list sm5803_set_vbus_disch(int chgnum, int enable)
{
	enum ec_error_list rv;
	int reg;

	rv = main_read8(chgnum, SM5803_REG_PORTS_CTRL, &reg);
	if (rv)
		return rv;

	if (enable)
		reg |= SM5803_PORTS_VBUS_DISCH;
	else
		reg &= ~SM5803_PORTS_VBUS_DISCH;

	rv = main_write8(chgnum, SM5803_REG_PORTS_CTRL, reg);
	return rv;
}

enum ec_error_list sm5803_vbus_sink_enable(int chgnum, int enable)
{
	enum ec_error_list rv;
	int regval;

	rv = sm5803_get_dev_id(chgnum, &dev_id);
	if (rv)
		return rv;

	if (enable) {
		if (chgnum == CHARGER_PRIMARY) {
			/* Magic for new silicon */
			if (dev_id >= 3) {
				rv |= main_write8(chgnum, 0x1F, 0x1);
				rv |= test_write8(chgnum, 0x44, 0x2);
				rv |= main_write8(chgnum, 0x1F, 0);
			}
			/*
			 * Only enable auto fast charge when a battery is
			 * connected and out of cutoff.
			 */
			if (battery_get_disconnect_state() ==
			    BATTERY_NOT_DISCONNECTED) {
				rv = sm5803_flow2_update(chgnum,
						 SM5803_FLOW2_AUTO_ENABLED,
						 MASK_SET);
				fast_charge_disabled = false;
			} else {
				rv = sm5803_flow2_update(chgnum,
						SM5803_FLOW2_AUTO_TRKL_EN |
						SM5803_FLOW2_AUTO_PRECHG_EN,
						MASK_SET);
				fast_charge_disabled = true;
			}
		} else {
			if (dev_id >= 3) {
				/* Touch of magic on the primary charger */
				rv |= main_write8(CHARGER_PRIMARY, 0x1F, 0x1);
				rv |= test_write8(CHARGER_PRIMARY, 0x44, 0x20);
				rv |= main_write8(CHARGER_PRIMARY, 0x1F, 0x0);

				/*
				 * Disable linear, pre-charge, and linear fast
				 * charge for primary charger.
				 */
				rv = chg_read8(CHARGER_PRIMARY,
					       SM5803_REG_FLOW3, &regval);
				regval &= ~(BIT(6) | BIT(5) | BIT(4));

				rv |= chg_write8(CHARGER_PRIMARY,
						 SM5803_REG_FLOW3, regval);
			}
		}

		/* Last but not least, enable sinking */
		rv |= sm5803_flow1_update(chgnum, CHARGER_MODE_SINK, MASK_SET);
	} else {
		if (chgnum == CHARGER_PRIMARY)
			rv |= sm5803_flow2_update(chgnum,
						  SM5803_FLOW2_AUTO_ENABLED,
						  MASK_CLR);

		if (chgnum == CHARGER_SECONDARY) {
			rv |= sm5803_flow1_update(CHARGER_PRIMARY,
						  SM5803_FLOW1_LINEAR_CHARGE_EN,
						  MASK_CLR);

			rv = chg_read8(CHARGER_PRIMARY, SM5803_REG_FLOW3,
				       &regval);
			regval &= ~(BIT(6) | BIT(5) | BIT(4));
			rv |= chg_write8(CHARGER_PRIMARY, SM5803_REG_FLOW3,
					 regval);
		}


		/* Disable sink mode, unless currently sourcing out */
		if (!sm5803_is_sourcing_otg_power(chgnum, chgnum))
			rv |= sm5803_flow1_update(chgnum, CHARGER_MODE_SINK,
						  MASK_CLR);
	}

	return rv;

}

/*
 * Track and store whether we've initialized the charger chips already on this
 * boot.  This should prevent us from re-running inits after sysjumps.
 */
static bool chip_inited[CHARGER_NUM];
#define SM5803_SYSJUMP_TAG	0x534D /* SM */
#define SM5803_HOOK_VERSION	1

static void init_status_preserve(void)
{
	system_add_jump_tag(SM5803_SYSJUMP_TAG, SM5803_HOOK_VERSION,
			    sizeof(chip_inited), &chip_inited);
}
DECLARE_HOOK(HOOK_SYSJUMP, init_status_preserve, HOOK_PRIO_DEFAULT);

static void init_status_retrieve(void)
{
	const uint8_t *tag_contents;
	int version, size;

	tag_contents = system_get_jump_tag(SM5803_SYSJUMP_TAG,
						  &version, &size);
	if (tag_contents && (version == SM5803_HOOK_VERSION) &&
					(size == sizeof(chip_inited)))
		/* Valid init status found, restore before charger chip init */
		memcpy(&chip_inited, tag_contents, size);
}
DECLARE_HOOK(HOOK_INIT, init_status_retrieve, HOOK_PRIO_FIRST);

static void sm5803_init(int chgnum)
{
	enum ec_error_list rv;
	int reg;
	int vbus_mv;
	const struct battery_info *batt_info;
	int pre_term;
	int cells;

	/*
	 * If a charger is not currently present, disable switching per OCPC
	 * requirements
	 */
	rv = charger_get_vbus_voltage(chgnum, &vbus_mv);
	if (rv == EC_SUCCESS) {
		if (vbus_mv < 4000) {
			/*
			 * No charger connected, disable CHG_EN
			 * (note other bits default to 0)
			 */
			rv = chg_write8(chgnum, SM5803_REG_FLOW1, 0);
		} else if (!sm5803_is_sourcing_otg_power(chgnum, chgnum)) {
			charger_vbus[chgnum] = 1;
		}
	} else {
		CPRINTS("%s %d: Failed to read VBUS voltage during init",
			CHARGER_NAME, chgnum);
		return;
	}

	/*
	 * A previous boot already ran inits, safe to return now that we've
	 * checked i2c communication to the chip and cached Vbus presence
	 */
	if (chip_inited[chgnum]) {
		CPRINTS("%s %d: Already initialized", CHARGER_NAME, chgnum);
		return;
	}

	rv |= charger_device_id(&reg);
	if (reg == 0x02) {
		/* --- Special register init ---
		 * For early silicon (ID 2) with 3S batteries
		 */
		rv |= main_write8(chgnum, 0x20, 0x08);
		rv |= main_write8(chgnum, 0x30, 0xC0);
		rv |= main_write8(chgnum, 0x80, 0x01);

		rv |= meas_write8(chgnum, 0x08, 0xC2);

		rv |= chg_write8(chgnum, 0x1D, 0x40);
		rv |= chg_write8(chgnum, 0x1F, 0x09);

		rv |= chg_write8(chgnum, 0x22, 0xB3);
		rv |= chg_write8(chgnum, 0x23, 0x81);
		rv |= chg_write8(chgnum, 0x28, 0xB7);

		rv |= chg_write8(chgnum, 0x4A, 0x82);
		rv |= chg_write8(chgnum, 0x4B, 0xA3);
		rv |= chg_write8(chgnum, 0x4C, 0xA8);
		rv |= chg_write8(chgnum, 0x4D, 0xCA);
		rv |= chg_write8(chgnum, 0x4E, 0x07);
		rv |= chg_write8(chgnum, 0x4F, 0xFF);

		rv |= chg_write8(chgnum, 0x50, 0x98);
		rv |= chg_write8(chgnum, 0x51, 0x00);
		rv |= chg_write8(chgnum, 0x52, 0x77);
		rv |= chg_write8(chgnum, 0x53, 0xD2);
		rv |= chg_write8(chgnum, 0x54, 0x02);
		rv |= chg_write8(chgnum, 0x55, 0xD1);
		rv |= chg_write8(chgnum, 0x56, 0x7F);
		rv |= chg_write8(chgnum, 0x57, 0x02);
		rv |= chg_write8(chgnum, 0x58, 0xD1);
		rv |= chg_write8(chgnum, 0x59, 0x7F);
		rv |= chg_write8(chgnum, 0x5A, 0x13);
		rv |= chg_write8(chgnum, 0x5B, 0x50);
		rv |= chg_write8(chgnum, 0x5C, 0x5B);
		rv |= chg_write8(chgnum, 0x5D, 0xB0);
		rv |= chg_write8(chgnum, 0x5E, 0x3C);
		rv |= chg_write8(chgnum, 0x5F, 0x3C);

		rv |= chg_write8(chgnum, 0x60, 0x44);
		rv |= chg_write8(chgnum, 0x61, 0x20);
		rv |= chg_write8(chgnum, 0x65, 0x35);
		rv |= chg_write8(chgnum, 0x66, 0x29);
		rv |= chg_write8(chgnum, 0x67, 0x64);
		rv |= chg_write8(chgnum, 0x68, 0x88);
		rv |= chg_write8(chgnum, 0x69, 0xC7);

		/* Inits to access page 0x37 and enable trickle charging */
		rv |= main_write8(chgnum, 0x1F, 0x01);
		rv |= test_update8(chgnum, 0x8E, BIT(5), MASK_SET);
		rv |= main_write8(chgnum, 0x1F, 0x00);
	} else if (reg == 0x03) {
		uint32_t platform_id;

		rv = main_read8(chgnum, SM5803_REG_PLATFORM, &platform_id);
		if (rv) {
			CPRINTS("%s %d: Failed to read platform during init",
							CHARGER_NAME, chgnum);
			return;
		}
		platform_id &= SM5803_PLATFORM_ID;

		if (is_platform_id_3s(platform_id)) {
			/* 3S Battery inits */
			/* set 13.3V VBAT_SNSP TH GPADC THRESHOLD*/
			rv |= meas_write8(chgnum, 0x26,
				SM5803_VBAT_SNSP_MAXTH_3S_LEVEL);
			/* OV_VBAT HW second level (14.1V) */
			rv |= chg_write8(chgnum, 0x21,
				SM5803_VBAT_PWR_MINTH_3S_LEVEL);
			rv |= main_write8(chgnum, 0x30, 0xC0);
			rv |= main_write8(chgnum, 0x80, 0x01);
			rv |= main_write8(chgnum, 0x1A, 0x08);

			rv |= meas_write8(chgnum, 0x08, 0xC2);

			rv |= chg_write8(chgnum, 0x1D, 0x40);

			rv |= chg_write8(chgnum, 0x22, 0xB3);

			rv |= chg_write8(chgnum, 0x3E, 0x3C);

			rv |= chg_write8(chgnum, 0x4B, 0xA6);
			rv |= chg_write8(chgnum, 0x4F, 0xBF);

			rv |= chg_write8(chgnum, 0x52, 0x77);
			rv |= chg_write8(chgnum, 0x53, 0xD2);
			rv |= chg_write8(chgnum, 0x54, 0x02);
			rv |= chg_write8(chgnum, 0x55, 0xD1);
			rv |= chg_write8(chgnum, 0x56, 0x7F);
			rv |= chg_write8(chgnum, 0x57, 0x01);
			rv |= chg_write8(chgnum, 0x58, 0x50);
			rv |= chg_write8(chgnum, 0x59, 0x7F);
			rv |= chg_write8(chgnum, 0x5A, 0x13);
			rv |= chg_write8(chgnum, 0x5B, 0x50);
			rv |= chg_write8(chgnum, 0x5D, 0xB0);

			rv |= chg_write8(chgnum, 0x60, 0x44);
			rv |= chg_write8(chgnum, 0x65, 0x35);
			rv |= chg_write8(chgnum, 0x66, 0x29);

			rv |= chg_write8(chgnum, 0x7D, 0x67);
			rv |= chg_write8(chgnum, 0x7E, 0x04);

			rv |= chg_write8(chgnum, 0x33, 0x3C);

			rv |= chg_write8(chgnum, 0x5C, 0x7A);
		} else if (is_platform_id_2s(platform_id)) {
			/* 2S Battery inits */

			/*
			 * Set 9V as higher threshold for VBATSNSP_MAX_TH GPADC
			 * threshold for interrupt generation.
			 */
			rv |= meas_write8(chgnum, 0x26,
				SM5803_VBAT_SNSP_MAXTH_2S_LEVEL);

			/* Set OV_VBAT HW second level threshold as 9.4V */
			rv |= chg_write8(chgnum, 0x21,
				SM5803_VBAT_PWR_MINTH_2S_LEVEL);

			rv |= main_write8(chgnum, 0x30, 0xC0);
			rv |= main_write8(chgnum, 0x80, 0x01);
			rv |= main_write8(chgnum, 0x1A, 0x08);

			rv |= meas_write8(chgnum, 0x08, 0xC2);

			rv |= chg_write8(chgnum, 0x1D, 0x40);

			rv |= chg_write8(chgnum, 0x22, 0xB3);

			rv |= chg_write8(chgnum, 0x3E, 0x3C);

			rv |= chg_write8(chgnum, 0x4F, 0xBF);

			rv |= chg_write8(chgnum, 0x52, 0x77);
			rv |= chg_write8(chgnum, 0x53, 0xD2);
			rv |= chg_write8(chgnum, 0x54, 0x02);
			rv |= chg_write8(chgnum, 0x55, 0xD1);
			rv |= chg_write8(chgnum, 0x56, 0x7F);
			rv |= chg_write8(chgnum, 0x57, 0x01);
			rv |= chg_write8(chgnum, 0x58, 0x50);
			rv |= chg_write8(chgnum, 0x59, 0x7F);
			rv |= chg_write8(chgnum, 0x5A, 0x13);
			rv |= chg_write8(chgnum, 0x5B, 0x52);
			rv |= chg_write8(chgnum, 0x5D, 0xD0);

			rv |= chg_write8(chgnum, 0x60, 0x44);
			rv |= chg_write8(chgnum, 0x65, 0x35);
			rv |= chg_write8(chgnum, 0x66, 0x29);

			rv |= chg_write8(chgnum, 0x7D, 0x97);
			rv |= chg_write8(chgnum, 0x7E, 0x07);

			rv |= chg_write8(chgnum, 0x33, 0x3C);

			rv |= chg_write8(chgnum, 0x5C, 0x7A);
		}

		rv |= chg_write8(chgnum, 0x73, 0x22);
		rv |= chg_write8(chgnum, 0x50, 0x88);
		rv |= chg_read8(chgnum, 0x34, &reg);
		reg |= BIT(7);
		rv |= chg_write8(chgnum, 0x34, reg);
		rv |= main_write8(chgnum, 0x1F, 0x1);
		rv |= test_write8(chgnum, 0x43, 0x10);
		rv |= test_write8(chgnum, 0x47, 0x10);
		rv |= test_write8(chgnum, 0x48, 0x04);
		rv |= main_write8(chgnum, 0x1F, 0x0);
	}

	/* Enable LDO bits */
	rv |= main_read8(chgnum, SM5803_REG_REFERENCE, &reg);
	reg &= ~(BIT(0) | BIT(1));
	rv |= main_write8(chgnum, SM5803_REG_REFERENCE, reg);

	/* Set a higher clock speed in case it was lowered for z-state */
	rv |= main_read8(chgnum, SM5803_REG_CLOCK_SEL, &reg);
	reg &= ~SM5803_CLOCK_SEL_LOW;
	rv |= main_write8(chgnum, SM5803_REG_CLOCK_SEL, reg);

	/*
	 * Turn on GPADCs to default.  Enable the IBAT_CHG ADC in order to
	 * measure battery current and calculate system resistance.
	 */
	reg = SM5803_GPADCC1_TINT_EN     |
	      SM5803_GPADCC1_VSYS_EN     |
	      SM5803_GPADCC1_VCHGPWR_EN  |
	      SM5803_GPADCC1_VBUS_EN     |
	      SM5803_GPADCC1_IBAT_CHG_EN |
	      SM5803_GPADCC1_IBAT_DIS_EN |
	      SM5803_GPADCC1_VBATSNSP_EN;
	rv |= meas_write8(chgnum, SM5803_REG_GPADC_CONFIG1, reg);

	/* Enable Psys DAC */
	rv |= meas_read8(chgnum, SM5803_REG_PSYS1, &reg);
	reg |= SM5803_PSYS1_DAC_EN;
	rv |= meas_write8(chgnum, SM5803_REG_PSYS1, reg);

	/* Enable ADC sigma delta */
	rv |= chg_read8(chgnum, SM5803_REG_CC_CONFIG1, &reg);
	reg |= SM5803_CC_CONFIG1_SD_PWRUP;
	rv |= chg_write8(chgnum, SM5803_REG_CC_CONFIG1, reg);

	/* Enable PROCHOT comparators except Ibus */
	rv |= chg_read8(chgnum, SM5803_REG_PHOT1, &reg);
	reg |= SM5803_PHOT1_COMPARATOR_EN;
	reg &= ~SM5803_PHOT1_IBUS_PHOT_COMP_EN;
	rv |= chg_write8(chgnum, SM5803_REG_PHOT1, reg);

	/* Set DPM Voltage to 4200 mv, see b:172173517 */
	reg = SM5803_VOLTAGE_TO_REG(4200);
	rv = chg_write8(chgnum, SM5803_REG_DPM_VL_SET_MSB, (reg >> 3));
	rv |= chg_write8(chgnum, SM5803_REG_DPM_VL_SET_LSB, (reg & 0x7));

	/* Set default input current */
	reg = SM5803_CURRENT_TO_REG(CONFIG_CHARGER_INPUT_CURRENT)
		& SM5803_CHG_ILIM_RAW;
	rv |= chg_write8(chgnum, SM5803_REG_CHG_ILIM, reg);

	/* Configure charger insertion interrupts */
	rv |= main_write8(chgnum, SM5803_REG_INT1_EN, SM5803_INT1_CHG);
	/* Enable end of charge interrupts for logging */
	rv |= main_write8(chgnum, SM5803_REG_INT4_EN, SM5803_INT4_CHG_FAIL |
						      SM5803_INT4_CHG_DONE |
						      SM5803_INT4_OTG_FAIL);

	/* Set TINT interrupts for higher threshold 360 K */
	rv |= meas_write8(chgnum, SM5803_REG_TINT_HIGH_TH,
						SM5803_TINT_HIGH_LEVEL);
	/*
	 * Set TINT interrupts for lower threshold to 0 when not
	 * throttled to prevent trigger interrupts continually
	 */
	rv |= meas_write8(chgnum, SM5803_REG_TINT_LOW_TH,
						SM5803_TINT_MIN_LEVEL);

	/*
	 * Configure VBAT_SNSP high interrupt to fire after thresholds are set.
	 */
	rv |= main_read8(chgnum, SM5803_REG_INT2_EN, &reg);
	reg |= SM5803_INT2_VBATSNSP;
	rv |= main_write8(chgnum, SM5803_REG_INT2_EN, reg);


	/* Configure TINT interrupts to fire after thresholds are set */
	rv |= main_write8(chgnum, SM5803_REG_INT2_EN, SM5803_INT2_TINT);

	/*
	 * Configure CHG_ENABLE to only be set through I2C by setting
	 * HOST_MODE_EN bit (all other register bits are 0 by default)
	 */
	rv |= chg_write8(chgnum, SM5803_REG_FLOW2, SM5803_FLOW2_HOST_MODE_EN);

	if (chgnum == CHARGER_PRIMARY) {
		int ibat_eoc_ma;

		/* Set end of fast charge threshold */
		batt_info = battery_get_info();
		ibat_eoc_ma = batt_info->precharge_current - 50;
		ibat_eoc_ma /= 100;
		ibat_eoc_ma = CLAMP(ibat_eoc_ma, 0, SM5803_CONF5_IBAT_EOC_TH);
		rv |= chg_read8(chgnum, SM5803_REG_FAST_CONF5, &reg);
		reg &= ~SM5803_CONF5_IBAT_EOC_TH;
		reg |= ibat_eoc_ma;
		rv |= chg_write8(CHARGER_PRIMARY, SM5803_REG_FAST_CONF5, reg);

		/* Setup the proper precharge thresholds. */
		cells = batt_info->voltage_max / 4;
		pre_term = batt_info->voltage_min / cells;
		pre_term /= 100; /* Convert to decivolts. */
		pre_term = CLAMP(pre_term, SM5803_VBAT_PRE_TERM_MIN_DV,
				 SM5803_VBAT_PRE_TERM_MAX_DV);
		pre_term -= SM5803_VBAT_PRE_TERM_MIN_DV; /* Convert to regval */

		rv |= chg_read8(chgnum, SM5803_REG_PRE_FAST_CONF_REG1, &reg);
		reg &= ~SM5803_VBAT_PRE_TERM;
		reg |= pre_term << SM5803_VBAT_PRE_TERM_SHIFT;
		rv |= chg_write8(chgnum, SM5803_REG_PRE_FAST_CONF_REG1, reg);

		/*
		 * Set up precharge current
		 * Note it is preferred to under-shoot the precharge current
		 * requested. Upper bits of this register are read/write 1 to
		 * clear
		 */
		reg = SM5803_CURRENT_TO_REG(batt_info->precharge_current);
		reg = MIN(reg, SM5803_PRECHG_ICHG_PRE_SET);
		rv |= chg_write8(chgnum, SM5803_REG_PRECHG, reg);

		/*
		 * Set up BFET alerts
		 *
		 * We'll set the soft limit at 1.5W and the hard limit at 6W.
		 *
		 * The register is 29.2 mW per bit.
		 */
		reg = (1500 * 10) / 292;
		rv |= meas_write8(chgnum, SM5803_REG_BFET_PWR_MAX_TH, reg);
		reg = (6000 * 10) / 292;
		rv |= meas_write8(chgnum, SM5803_REG_BFET_PWR_HWSAFE_MAX_TH,
				 reg);
		rv |= main_read8(chgnum, SM5803_REG_INT3_EN, &reg);
		reg |= SM5803_INT3_BFET_PWR_LIMIT |
		       SM5803_INT3_BFET_PWR_HWSAFE_LIMIT;
		rv |= main_write8(chgnum, SM5803_REG_INT3_EN, reg);

		rv |= chg_read8(chgnum, SM5803_REG_FLOW3, &reg);
		reg &= ~SM5803_FLOW3_SWITCH_BCK_BST;
		rv |= chg_write8(chgnum, SM5803_REG_FLOW3, reg);

		rv |= chg_read8(chgnum, SM5803_REG_SWITCHER_CONF, &reg);
		reg |= SM5803_SW_BCK_BST_CONF_AUTO;
		rv |= chg_write8(chgnum, SM5803_REG_SWITCHER_CONF, reg);
	}

	if (rv)
		CPRINTS("%s %d: Failed initialization", CHARGER_NAME, chgnum);
	else
		chip_inited[chgnum] = true;
}

static enum ec_error_list sm5803_post_init(int chgnum)
{
	/* Nothing to do, charger is always powered */
	return EC_SUCCESS;
}

void sm5803_hibernate(int chgnum)
{
	enum ec_error_list rv;
	int reg;

	rv = main_read8(chgnum, SM5803_REG_REFERENCE, &reg);
	if (rv) {
		CPRINTS("%s %d: Failed to read REFERENCE reg", CHARGER_NAME,
			chgnum);
		return;
	}

	/* Disable LDO bits - note the primary LDO should not be disabled */
	if (chgnum != CHARGER_PRIMARY) {
		reg |= (BIT(0) | BIT(1));
		rv |= main_write8(chgnum, SM5803_REG_REFERENCE, reg);
	}

	/* Slow the clock speed */
	rv |= main_read8(chgnum, SM5803_REG_CLOCK_SEL, &reg);
	reg |= SM5803_CLOCK_SEL_LOW;
	rv |= main_write8(chgnum, SM5803_REG_CLOCK_SEL, reg);

	/* Turn off GPADCs */
	rv |= meas_write8(chgnum, SM5803_REG_GPADC_CONFIG1, 0);
	rv |= meas_write8(chgnum, SM5803_REG_GPADC_CONFIG2, 0);

	/* Disable Psys DAC */
	rv |= meas_read8(chgnum, SM5803_REG_PSYS1, &reg);
	reg &= ~SM5803_PSYS1_DAC_EN;
	rv |= meas_write8(chgnum, SM5803_REG_PSYS1, reg);

	/* Disable ADC sigma delta */
	rv |= chg_read8(chgnum, SM5803_REG_CC_CONFIG1, &reg);
	reg &= ~SM5803_CC_CONFIG1_SD_PWRUP;
	rv |= chg_write8(chgnum, SM5803_REG_CC_CONFIG1, reg);

	/* Disable PROCHOT comparators */
	rv |= chg_read8(chgnum, SM5803_REG_PHOT1, &reg);
	reg &= ~SM5803_PHOT1_COMPARATOR_EN;
	rv |= chg_write8(chgnum, SM5803_REG_PHOT1, reg);

	if (rv)
		CPRINTS("%s %d: Failed to set hibernate", CHARGER_NAME, chgnum);
}

static void sm5803_disable_runtime_low_power_mode(void)
{
	enum ec_error_list rv;
	int reg;
	int chgnum = TASK_ID_TO_PD_PORT(task_get_current());

	CPRINTS("%s %d: disable runtime low power mode", CHARGER_NAME, chgnum);
	rv = main_read8(chgnum, SM5803_REG_REFERENCE, &reg);
	if (rv) {
		CPRINTS("%s %d: Failed to read REFERENCE reg", CHARGER_NAME,
			chgnum);
		return;
	}
	/* Set a higher clock speed */
	rv |= main_read8(chgnum, SM5803_REG_CLOCK_SEL, &reg);
	reg &= ~SM5803_CLOCK_SEL_LOW;
	rv |= main_write8(chgnum, SM5803_REG_CLOCK_SEL, reg);

	/* Enable ADC sigma delta */
	rv |= chg_read8(chgnum, SM5803_REG_CC_CONFIG1, &reg);
	reg |= SM5803_CC_CONFIG1_SD_PWRUP;
	rv |= chg_write8(chgnum, SM5803_REG_CC_CONFIG1, reg);

	if (rv)
		CPRINTS("%s %d: Failed to set in disable runtime LPM",
			CHARGER_NAME, chgnum);
}
DECLARE_HOOK(HOOK_USB_PD_CONNECT,
		sm5803_disable_runtime_low_power_mode,
		HOOK_PRIO_FIRST);

static enum ec_error_list sm5803_enable_linear_charge(int chgnum, bool enable)
{
	int rv;
	int regval;
	const struct battery_info *batt_info;

	if (enable) {
		/*
		 * We need to wait for the BFET enable attempt to complete,
		 * otherwise we may end up disabling linear charge.
		 */
		if (!attempt_bfet_enable)
			return EC_ERROR_TRY_AGAIN;
		rv = main_write8(chgnum, 0x1F, 0x1);
		rv |= test_write8(chgnum, 0x44, 0x20);
		rv |= main_write8(chgnum, 0x1F, 0);

		/*
		 * Precharge thresholds have already been set up as a part of
		 * init, however set fast charge current equal to the precharge
		 * current in case the battery moves beyond that threshold.
		 */
		batt_info = battery_get_info();
		rv |= sm5803_set_current(CHARGER_PRIMARY,
					 batt_info->precharge_current);

		/* Enable linear charge mode. */
		rv |= sm5803_flow1_update(chgnum,
					  SM5803_FLOW1_LINEAR_CHARGE_EN,
					 MASK_SET);
		rv |= chg_read8(chgnum, SM5803_REG_FLOW3, &regval);
		regval |= BIT(6) | BIT(5) | BIT(4);
		rv |= chg_write8(chgnum, SM5803_REG_FLOW3, regval);
	} else {
		rv = sm5803_flow1_update(chgnum,
					 SM5803_FLOW1_LINEAR_CHARGE_EN,
					 MASK_CLR);
		rv |= sm5803_flow2_update(chgnum,
					  SM5803_FLOW2_AUTO_ENABLED,
					  MASK_CLR);
		rv |= chg_read8(chgnum, SM5803_REG_FLOW3, &regval);
		regval &= ~(BIT(6) | BIT(5) | BIT(4) |
			    SM5803_FLOW3_SWITCH_BCK_BST);
		rv |= chg_write8(chgnum, SM5803_REG_FLOW3, regval);
		rv |= chg_read8(chgnum, SM5803_REG_SWITCHER_CONF, &regval);
		regval |= SM5803_SW_BCK_BST_CONF_AUTO;
		rv |= chg_write8(chgnum, SM5803_REG_SWITCHER_CONF, regval);
	}

	return rv;
}

static void sm5803_enable_runtime_low_power_mode(void)
{
	enum ec_error_list rv;
	int reg;
	int chgnum = TASK_ID_TO_PD_PORT(task_get_current());

	CPRINTS("%s %d: enable runtime low power mode", CHARGER_NAME, chgnum);
	rv = main_read8(chgnum, SM5803_REG_REFERENCE, &reg);
	if (rv) {
		CPRINTS("%s %d: Failed to read REFERENCE reg", CHARGER_NAME,
			chgnum);
		return;
	}
	/* Slow the clock speed */
	rv |= main_read8(chgnum, SM5803_REG_CLOCK_SEL, &reg);
	reg |= SM5803_CLOCK_SEL_LOW;
	rv |= main_write8(chgnum, SM5803_REG_CLOCK_SEL, reg);

	/* Disable ADC sigma delta */
	rv |= chg_read8(chgnum, SM5803_REG_CC_CONFIG1, &reg);
	reg &= ~SM5803_CC_CONFIG1_SD_PWRUP;
	rv |= chg_write8(chgnum, SM5803_REG_CC_CONFIG1, reg);

	/* If the system is off, all PROCHOT comparators may be turned off */
	if (chipset_in_state(CHIPSET_STATE_ANY_OFF |
			     CHIPSET_STATE_ANY_SUSPEND)) {
		rv |= chg_read8(chgnum, SM5803_REG_PHOT1, &reg);
		reg &= ~SM5803_PHOT1_COMPARATOR_EN;
		rv |= chg_write8(chgnum, SM5803_REG_PHOT1, reg);
	}

	if (rv)
		CPRINTS("%s %d: Failed to set in enable runtime LPM",
			CHARGER_NAME, chgnum);
}
DECLARE_HOOK(HOOK_USB_PD_DISCONNECT,
		sm5803_enable_runtime_low_power_mode,
		HOOK_PRIO_LAST);

void sm5803_disable_low_power_mode(int chgnum)
{
	enum ec_error_list rv;
	int reg;

	CPRINTS("%s %d: disable low power mode", CHARGER_NAME, chgnum);
	rv = main_read8(chgnum, SM5803_REG_REFERENCE, &reg);
	if (rv) {
		CPRINTS("%s %d: Failed to read REFERENCE reg", CHARGER_NAME,
			chgnum);
		return;
	}
	/* Enable Psys DAC */
	rv |= meas_read8(chgnum, SM5803_REG_PSYS1, &reg);
	reg |= SM5803_PSYS1_DAC_EN;
	rv |= meas_write8(chgnum, SM5803_REG_PSYS1, reg);

	/* Enable PROCHOT comparators except Ibus */
	rv |= chg_read8(chgnum, SM5803_REG_PHOT1, &reg);
	reg |= SM5803_PHOT1_COMPARATOR_EN;
	reg &= ~SM5803_PHOT1_IBUS_PHOT_COMP_EN;
	rv |= chg_write8(chgnum, SM5803_REG_PHOT1, reg);

	if (rv)
		CPRINTS("%s %d: Failed to set in disable low power mode",
			CHARGER_NAME, chgnum);
}

void sm5803_enable_low_power_mode(int chgnum)
{
	enum ec_error_list rv;
	int reg;

	CPRINTS("%s %d: enable low power mode", CHARGER_NAME, chgnum);
	rv = main_read8(chgnum, SM5803_REG_REFERENCE, &reg);
	if (rv) {
		CPRINTS("%s %d: Failed to read REFERENCE reg", CHARGER_NAME,
			chgnum);
		return;
	}
	/* Disable Psys DAC */
	rv |= meas_read8(chgnum, SM5803_REG_PSYS1, &reg);
	reg &= ~SM5803_PSYS1_DAC_EN;
	rv |= meas_write8(chgnum, SM5803_REG_PSYS1, reg);

	/*
	 * Disable all PROCHOT comparators only if port is inactive.  Vbus
	 * sourcing requires that the Vbus comparator be enabled, and it
	 * cannot be enabled from HOOK_USB_PD_CONNECT since that is
	 * called after Vbus has turned on.
	 */
	rv |= chg_read8(chgnum, SM5803_REG_PHOT1, &reg);
	reg &= ~SM5803_PHOT1_COMPARATOR_EN;
	if (pd_is_connected(chgnum))
		reg |= SM5803_PHOT1_VBUS_MON_EN;
	rv |= chg_write8(chgnum, SM5803_REG_PHOT1, reg);


	if (rv)
		CPRINTS("%s %d: Failed to set in enable low power mode",
			CHARGER_NAME, chgnum);
}

/*
 * Restart charging on the active port, if it's still active and it hasn't
 * exceeded our maximum number of restarts.
 */
void sm5803_restart_charging(void)
{
	int act_chg = charge_manager_get_active_charge_port();
	timestamp_t now = get_time();

	if (act_chg == active_restart_port) {
		if (timestamp_expired(failure_tracker[act_chg].time, &now)) {
			/*
			 * Enough time has passed since our last failure,
			 * restart the timing and count from now.
			 */
			failure_tracker[act_chg].time.val = now.val +
						CHARGING_FAILURE_INTERVAL;
			failure_tracker[act_chg].count = 1;

			sm5803_vbus_sink_enable(act_chg, 1);
		} else if (++failure_tracker[act_chg].count >
			   CHARGING_FAILURE_MAX_COUNT) {
			CPRINTS("%s %d: Exceeded charging failure retries",
				CHARGER_NAME, act_chg);
		} else {
			sm5803_vbus_sink_enable(act_chg, 1);
		}
	}

	active_restart_port = CHARGE_PORT_NONE;
}
DECLARE_DEFERRED(sm5803_restart_charging);

/*
 * Process interrupt registers and report any Vbus changes.  Alert the AP if the
 * charger has become too hot.
 */
void sm5803_handle_interrupt(int chgnum)
{
	enum ec_error_list rv;
	int int_reg, meas_reg;
	static bool throttled;
	struct batt_params bp;
	int act_chg, val;

	/* Note: Interrupt registers are clear on read */
	rv = main_read8(chgnum, SM5803_REG_INT1_REQ, &int_reg);
	if (rv) {
		CPRINTS("%s %d: Failed read int1 register", CHARGER_NAME,
			chgnum);
		return;
	}

	if (int_reg & SM5803_INT1_CHG) {
		rv = main_read8(chgnum, SM5803_REG_STATUS1, &meas_reg);
		if (!(meas_reg & SM5803_STATUS1_CHG_DET)) {
			charger_vbus[chgnum] = 0;
			if (IS_ENABLED(CONFIG_USB_CHARGER))
				usb_charger_vbus_change(chgnum, 0);
		} else {
			charger_vbus[chgnum] = 1;
			if (IS_ENABLED(CONFIG_USB_CHARGER))
				usb_charger_vbus_change(chgnum, 1);
		}
		board_check_extpower();
	}

	rv = main_read8(chgnum, SM5803_REG_INT2_REQ, &int_reg);
	if (rv) {
		CPRINTS("%s %d: Failed read int2 register", CHARGER_NAME,
			chgnum);
		return;
	}

	if (int_reg & SM5803_INT2_TINT) {
		rv = meas_read8(chgnum, SM5803_REG_TINT_MEAS_MSB, &meas_reg);
		if ((meas_reg <= SM5803_TINT_LOW_LEVEL) && throttled) {
			throttled = false;
			throttle_ap(THROTTLE_OFF, THROTTLE_HARD,
							THROTTLE_SRC_THERMAL);
			/*
			 * Set back higher threshold to 360 K and set lower
			 * threshold to 0.
			 */
			rv |= meas_write8(chgnum, SM5803_REG_TINT_LOW_TH,
							SM5803_TINT_MIN_LEVEL);
			rv |= meas_write8(chgnum, SM5803_REG_TINT_HIGH_TH,
							SM5803_TINT_HIGH_LEVEL);
		} else if (meas_reg >= SM5803_TINT_HIGH_LEVEL) {
			throttled = true;
			throttle_ap(THROTTLE_ON, THROTTLE_HARD,
							THROTTLE_SRC_THERMAL);
			/*
			 * Set back lower threshold to 330 K and set higher
			 * threshold to maximum.
			 */
			rv |= meas_write8(chgnum, SM5803_REG_TINT_HIGH_TH,
							SM5803_TINT_MAX_LEVEL);
			rv |= meas_write8(chgnum, SM5803_REG_TINT_LOW_TH,
							SM5803_TINT_LOW_LEVEL);
		}
		/*
		 * If the interrupt came in and we're not currently throttling
		 * or the level is below the upper threshold, it can likely be
		 * ignored.
		 */
	}

	if (int_reg & SM5803_INT2_VBATSNSP) {
		int meas_volt;
		uint32_t platform_id;

		rv = main_read8(chgnum, SM5803_REG_PLATFORM, &platform_id);
		if (rv) {
			CPRINTS("%s %d: Failed to read platform in interrupt",
							CHARGER_NAME, chgnum);
			return;
		}
		platform_id &= SM5803_PLATFORM_ID;
		act_chg = charge_manager_get_active_charge_port();
		rv = meas_read8(CHARGER_PRIMARY,
					SM5803_REG_VBATSNSP_MEAS_MSB,
					&meas_reg);
		if (rv)
			return;
		meas_volt = meas_reg << 2;
		rv = meas_read8(CHARGER_PRIMARY,
					SM5803_REG_VBATSNSP_MEAS_LSB,
					&meas_reg);
		if (rv)
			return;
		meas_volt |= meas_reg & 0x03;
		rv = meas_read8(CHARGER_PRIMARY,
			SM5803_REG_VBATSNSP_MAX_TH, &meas_reg);
		if (rv)
			return;

		if (is_platform_id_2s(platform_id)) {
			/* 2S Battery */
			CPRINTS("%s %d : VBAT_SNSP_HIGH_TH: %d mV ! - "
				"VBAT %d mV",
				CHARGER_NAME, CHARGER_PRIMARY,
				meas_reg * 408/10,
				meas_volt * 102/10);
		}

		if (is_platform_id_3s(platform_id)) {
			/* 3S Battery */
			CPRINTS("%s %d : VBAT_SNSP_HIGH_TH: %d mV ! "
				"- VBAT %d mV",
				CHARGER_NAME, CHARGER_PRIMARY,
				meas_reg * 616/10,
				meas_volt * 154/10);
		}

		/* Set Vbat Threshold to Max value to re-arm the interrupt */
		rv = meas_write8(CHARGER_PRIMARY,
			SM5803_REG_VBATSNSP_MAX_TH, 0xFF);

		/* Disable battery charge */
		rv |= sm5803_flow1_update(chgnum, CHARGER_MODE_DISABLED,
			MASK_CLR);
		if (is_platform_id_2s(platform_id)) {
			/* 2S battery: set VBAT_SENSP TH 9V */
			rv |= meas_write8(CHARGER_PRIMARY,
				SM5803_REG_VBATSNSP_MAX_TH,
				SM5803_VBAT_SNSP_MAXTH_2S_LEVEL);
		}
		if (is_platform_id_3s(platform_id)) {
			/* 3S battery: set VBAT_SENSP TH 13.3V */
			rv |= meas_write8(CHARGER_PRIMARY,
				SM5803_REG_VBATSNSP_MAX_TH,
				SM5803_VBAT_SNSP_MAXTH_3S_LEVEL);
		}

		active_restart_port = act_chg;
		hook_call_deferred(&sm5803_restart_charging_data, 1 * SECOND);
	}

	/* TODO(b/159376384): Take action on fatal BFET power alert. */
	rv = main_read8(chgnum, SM5803_REG_INT3_REQ, &int_reg);
	if (rv) {
		CPRINTS("%s %d: Failed to read int3 register", CHARGER_NAME,
			chgnum);
		return;
	}

	if ((int_reg & SM5803_INT3_BFET_PWR_LIMIT) ||
	    (int_reg & SM5803_INT3_BFET_PWR_HWSAFE_LIMIT)) {
		battery_get_params(&bp);
		act_chg = charge_manager_get_active_charge_port();
		CPRINTS("%s BFET power limit reached! (%s)", CHARGER_NAME,
			(int_reg & SM5803_INT3_BFET_PWR_LIMIT) ? "warn" :
			"FATAL");
		CPRINTS("\tVbat: %dmV", bp.voltage);
		CPRINTS("\tIbat: %dmA", bp.current);
		charger_get_voltage(act_chg, &val);
		CPRINTS("\tVsys(aux): %dmV", val);
		charger_get_current(act_chg, &val);
		CPRINTS("\tIsys: %dmA", val);
		cflush();
	}

	rv = main_read8(chgnum, SM5803_REG_INT4_REQ, &int_reg);
	if (rv) {
		CPRINTS("%s %d: Failed to read int4 register", CHARGER_NAME,
			chgnum);
		return;
	}

	if (int_reg & SM5803_INT4_CHG_FAIL) {
		int status_reg;

		act_chg = charge_manager_get_active_charge_port();
		chg_read8(chgnum, SM5803_REG_STATUS_CHG_REG, &status_reg);
		CPRINTS("%s %d: CHG_FAIL_INT fired.  Status 0x%02x",
			CHARGER_NAME, chgnum, status_reg);

		/* Write 1 to clear status interrupts */
		chg_write8(chgnum, SM5803_REG_STATUS_CHG_REG, status_reg);

		/*
		 * If a survivable fault happened, re-start sinking on the
		 * active charger after an appropriate delay.
		 */
		if (status_reg & SM5803_STATUS_CHG_OV_ITEMP) {
			active_restart_port = act_chg;
			hook_call_deferred(&sm5803_restart_charging_data,
					   30 * SECOND);
		} else if ((status_reg & SM5803_STATUS_CHG_OV_VBAT) &&
						act_chg == CHARGER_PRIMARY) {
			active_restart_port = act_chg;
			hook_call_deferred(&sm5803_restart_charging_data,
					   1 * SECOND);
		}
	}

	if (int_reg & SM5803_INT4_CHG_DONE)
		CPRINTS("%s %d: CHG_DONE_INT fired!!!", CHARGER_NAME, chgnum);

	if (int_reg & SM5803_INT4_OTG_FAIL) {
		int status_reg;

		/*
		 * Gather status to detect if this was overcurrent
		 *
		 * Note: a status of 0 with this interrupt also indicates an
		 * overcurrent (see b/170517117)
		 */
		chg_read8(chgnum, SM5803_REG_STATUS_DISCHG, &status_reg);
		CPRINTS("%s %d: OTG_FAIL_INT fired. Status 0x%02x",
			CHARGER_NAME, chgnum, status_reg);
		if ((status_reg == 0) ||
		    (status_reg == SM5803_STATUS_DISCHG_VBUS_SHORT)) {
			pd_handle_overcurrent(chgnum);
		}

		/*
		 * Clear source mode here when status is 0, since OTG disable
		 * will detect us as sinking in this failure case.
		 */
		if (status_reg == 0)
			rv = sm5803_flow1_update(chgnum, CHARGER_MODE_SOURCE |
						 SM5803_FLOW1_DIRECTCHG_SRC_EN,
						 MASK_CLR);
	}

}

static void sm5803_irq_deferred(void)
{
	int i;
	uint32_t pending = atomic_clear(&irq_pending);

	for (i = 0; i < CHARGER_NUM; i++)
		if (BIT(i) & pending)
			sm5803_handle_interrupt(i);
}
DECLARE_DEFERRED(sm5803_irq_deferred);

void sm5803_interrupt(int chgnum)
{
	atomic_or(&irq_pending, BIT(chgnum));
	hook_call_deferred(&sm5803_irq_deferred_data, 0);
}

static enum ec_error_list sm5803_get_dev_id(int chgnum, int *id)
{
	int rv = EC_SUCCESS;

	if (dev_id == UNKNOWN_DEV_ID)
		rv = main_read8(chgnum, SM5803_REG_CHIP_ID, &dev_id);

	if (!rv)
		*id = dev_id;

	return rv;

}

static const struct charger_info *sm5803_get_info(int chgnum)
{
	return &sm5803_charger_info;
}

static enum ec_error_list sm5803_get_status(int chgnum, int *status)
{
	enum ec_error_list rv;
	int reg;

	/* Charger obeys smart battery requests - making it level 2 */
	*status = CHARGER_LEVEL_2;

	rv = chg_read8(chgnum, SM5803_REG_FLOW1, &reg);
	if (rv)
		return rv;


	if ((reg & SM5803_FLOW1_MODE) == CHARGER_MODE_DISABLED &&
	    !(reg & SM5803_FLOW1_LINEAR_CHARGE_EN))
		*status |= CHARGER_CHARGE_INHIBITED;

	return EC_SUCCESS;
}

static enum ec_error_list sm5803_set_mode(int chgnum, int mode)
{
	enum ec_error_list rv = EC_SUCCESS;

	if (mode & CHARGE_FLAG_INHIBIT_CHARGE) {
		rv = sm5803_flow1_update(chgnum, 0xFF, MASK_CLR);
		rv |= sm5803_flow2_update(chgnum, SM5803_FLOW2_AUTO_ENABLED,
					  MASK_CLR);
	}

	return rv;
}

static enum ec_error_list sm5803_get_actual_current(int chgnum, int *current)
{
	enum ec_error_list rv;
	int reg;
	int curr;

	rv = meas_read8(chgnum, SM5803_REG_IBAT_CHG_AVG_MEAS_MSB, &reg);
	if (rv)
		return rv;
	curr = reg << 2;

	rv = meas_read8(chgnum, SM5803_REG_IBAT_CHG_AVG_MEAS_LSB, &reg);
	if (rv)
		return rv;
	curr |= reg & SM5803_IBAT_CHG_MEAS_LSB;

	/* The LSB is 7.32mA */
	*current = curr * 732 / 100;
	return EC_SUCCESS;
}

static enum ec_error_list sm5803_get_current(int chgnum, int *current)
{
	enum ec_error_list rv;
	int reg;

	rv = chg_read8(chgnum, SM5803_REG_FAST_CONF4, &reg);
	if (rv)
		return rv;

	reg &= SM5803_CONF4_ICHG_FAST;
	*current = SM5803_REG_TO_CURRENT(reg);

	return EC_SUCCESS;
}

static enum ec_error_list sm5803_set_current(int chgnum, int current)
{
	enum ec_error_list rv;
	int reg;

	rv = chg_read8(chgnum, SM5803_REG_FAST_CONF4, &reg);
	if (rv)
		return rv;

	reg &= ~SM5803_CONF4_ICHG_FAST;
	reg |= SM5803_CURRENT_TO_REG(current);

	rv = chg_write8(chgnum, SM5803_REG_FAST_CONF4, reg);
	return rv;
}

static enum ec_error_list sm5803_get_actual_voltage(int chgnum, int *voltage)
{
	enum ec_error_list rv;
	int reg;
	int volt_bits;

	rv = meas_read8(chgnum, SM5803_REG_VSYS_AVG_MEAS_MSB, &reg);
	if (rv)
		return rv;
	volt_bits = reg << 2;

	rv = meas_read8(chgnum, SM5803_REG_VSYS_AVG_MEAS_LSB, &reg);
	if (rv)
		return rv;
	volt_bits |= reg & 0x3;

	/* The LSB is 23.4mV */
	*voltage = volt_bits * 234 / 10;

	return EC_SUCCESS;
}

static enum ec_error_list sm5803_get_voltage(int chgnum, int *voltage)
{
	enum ec_error_list rv;
	int regval;
	int v;

	rv = chg_read8(chgnum, SM5803_REG_VBAT_FAST_MSB, &regval);
	v = regval << 3;
	rv |= chg_read8(chgnum, SM5803_REG_VBAT_FAST_LSB, &regval);
	v |= (regval & 0x3);

	*voltage = SM5803_REG_TO_VOLTAGE(v);

	if (rv)
		return EC_ERROR_UNKNOWN;

	return EC_SUCCESS;
}

static enum ec_error_list sm5803_set_voltage(int chgnum, int voltage)
{
	enum ec_error_list rv;
	int regval;

	regval = SM5803_VOLTAGE_TO_REG(voltage);

	/*
	 * Note: Set both voltages on both chargers.  Vbat will only be used on
	 * primary, which enables charging.
	 */
	rv = chg_write8(chgnum, SM5803_REG_VSYS_PREREG_MSB, (regval >> 3));
	rv |= chg_write8(chgnum, SM5803_REG_VSYS_PREREG_LSB, (regval & 0x7));
	rv |= chg_write8(chgnum, SM5803_REG_VBAT_FAST_MSB, (regval >> 3));
	rv |= chg_write8(chgnum, SM5803_REG_VBAT_FAST_LSB, (regval & 0x7));

	/* Once battery is connected, set up fast charge enable */
	if (fast_charge_disabled && chgnum == CHARGER_PRIMARY &&
	    battery_get_disconnect_state() == BATTERY_NOT_DISCONNECTED) {
		rv = sm5803_flow2_update(chgnum,
					 SM5803_FLOW2_AUTO_ENABLED,
					 MASK_SET);
		fast_charge_disabled = false;
	}

	if (IS_ENABLED(CONFIG_OCPC) && chgnum != CHARGER_PRIMARY) {
		/*
		 * Check to see if the BFET is enabled.  If not, enable it by
		 * toggling linear mode on the primary charger.  The BFET can be
		 * disabled if the system is powered up from an auxiliary charge
		 * port and the battery is dead.
		 */
		rv |= chg_read8(CHARGER_PRIMARY, SM5803_REG_LOG1, &regval);
		if (!(regval & SM5803_BATFET_ON) && !attempt_bfet_enable) {
			CPRINTS("SM5803: Attempting to turn on BFET");
			cflush();
			rv |= sm5803_flow1_update(CHARGER_PRIMARY,
						  SM5803_FLOW1_LINEAR_CHARGE_EN,
						  MASK_SET);
			rv |= sm5803_flow1_update(CHARGER_PRIMARY,
						  SM5803_FLOW1_LINEAR_CHARGE_EN,
						  MASK_CLR);
			attempt_bfet_enable = 1;
			sm5803_vbus_sink_enable(chgnum, 1);
		}
		/* There's no need to attempt it if the BFET's already on. */
		if (regval & SM5803_BATFET_ON)
			attempt_bfet_enable = 1;
	}

	return rv;
}

static enum ec_error_list sm5803_discharge_on_ac(int chgnum, int enable)
{
	enum ec_error_list rv = EC_SUCCESS;

	if (enable) {
		rv = sm5803_vbus_sink_enable(chgnum, 0);
	} else {
		if (chgnum == charge_manager_get_active_charge_port())
			rv = sm5803_vbus_sink_enable(chgnum, 1);
	}

	return rv;
}

static enum ec_error_list sm5803_get_vbus_voltage(int chgnum, int port,
						   int *voltage)
{
	enum ec_error_list rv;
	int reg;
	int volt_bits;

	rv = meas_read8(chgnum, SM5803_REG_VBUS_MEAS_MSB, &reg);
	if (rv)
		return rv;

	volt_bits = reg << 2;

	rv = meas_read8(chgnum, SM5803_REG_VBUS_MEAS_LSB, &reg);

	volt_bits |= reg & SM5803_VBUS_MEAS_LSB;

	/* Vbus ADC is in 23.4 mV steps */
	*voltage = (volt_bits * 234) / 10;
	return rv;
}

static enum ec_error_list sm5803_set_input_current_limit(int chgnum,
							 int input_current)
{
	int reg;

	reg = SM5803_CURRENT_TO_REG(input_current) & SM5803_CHG_ILIM_RAW;

	return chg_write8(chgnum, SM5803_REG_CHG_ILIM, reg);
}

static enum ec_error_list sm5803_get_input_current_limit(int chgnum,
							 int *input_current)
{
	int rv;
	int val;

	rv = chg_read8(chgnum, SM5803_REG_CHG_ILIM, &val);
	if (rv)
		return rv;

	*input_current = SM5803_REG_TO_CURRENT(val & SM5803_CHG_ILIM_RAW);
	return rv;
}

static enum ec_error_list sm5803_get_input_current(int chgnum,
						   int *input_current)
{
	enum ec_error_list rv;
	int reg;
	int curr;

	rv = meas_read8(chgnum, SM5803_REG_IBUS_CHG_MEAS_MSB, &reg);
	if (rv)
		return rv;
	curr = reg << 2;

	rv = meas_read8(chgnum, SM5803_REG_IBUS_CHG_MEAS_LSB, &reg);
	if (rv)
		return rv;
	curr |= reg & 0x3;

	/* The LSB is 7.32mA */
	*input_current = curr * 732 / 100;
	return EC_SUCCESS;
}

static enum ec_error_list sm5803_get_option(int chgnum, int *option)
{
	enum ec_error_list rv;
	uint32_t control;
	int reg;

	rv = chg_read8(chgnum, SM5803_REG_FLOW1, &reg);
	control = reg;

	rv |= chg_read8(chgnum, SM5803_REG_FLOW2, &reg);
	control |= reg << 8;

	rv |= chg_read8(chgnum, SM5803_REG_FLOW3, &reg);
	control |= reg << 16;

	return rv;
}

enum ec_error_list sm5803_is_acok(int chgnum, bool *acok)
{
	int rv;
	int reg, vbus_mv;

	rv = main_read8(chgnum, SM5803_REG_STATUS1, &reg);

	if (rv)
		return rv;

	/* If we're not sinking, then AC can't be OK. */
	if (!(reg & SM5803_STATUS1_CHG_DET)) {
		*acok = false;
		return EC_SUCCESS;
	}

	/*
	 * Okay, we're sinking. Check that VBUS has some voltage. This
	 * should indicate that the path is good.
	 */
	rv = charger_get_vbus_voltage(chgnum, &vbus_mv);

	if (rv)
		return rv;

	/* Assume that ACOK would be asserted if VBUS is higher than ~4V. */
	*acok = vbus_mv >= 4000;

	return EC_SUCCESS;
}

static enum ec_error_list sm5803_is_input_current_limit_reached(int chgnum,
								bool *reached)
{
	enum ec_error_list rv;
	int reg;

	rv = chg_read8(chgnum, SM5803_REG_LOG2, &reg);
	if (rv)
		return rv;

	*reached = (reg & SM5803_ISOLOOP_ON) ? true : false;

	return EC_SUCCESS;
}

static enum ec_error_list sm5803_set_option(int chgnum, int option)
{
	enum ec_error_list rv;
	int reg;

	mutex_lock(&flow1_access_lock[chgnum]);

	reg = option & 0xFF;
	rv = chg_write8(chgnum, SM5803_REG_FLOW1, reg);

	mutex_unlock(&flow1_access_lock[chgnum]);
	if (rv)
		return rv;

	reg = (option >> 8) & 0xFF;
	rv = chg_write8(chgnum, SM5803_REG_FLOW2, reg);
	if (rv)
		return rv;

	reg = (option >> 16) & 0xFF;
	rv = chg_write8(chgnum, SM5803_REG_FLOW3, reg);

	return rv;
}

static enum ec_error_list sm5803_set_otg_current_voltage(int chgnum,
							 int output_current,
							 int output_voltage)
{
	enum ec_error_list rv;
	int reg;

	rv = chg_read8(chgnum, SM5803_REG_DISCH_CONF5, &reg);
	if (rv)
		return rv;

	reg &= ~SM5803_DISCH_CONF5_CLS_LIMIT;
	reg |= MIN((output_current / SM5803_CLS_CURRENT_STEP),
						SM5803_DISCH_CONF5_CLS_LIMIT);
	rv |= chg_write8(chgnum, SM5803_REG_DISCH_CONF5, reg);

	reg = SM5803_VOLTAGE_TO_REG(output_voltage);
	rv = chg_write8(chgnum, SM5803_REG_VPWR_MSB, (reg >> 3));
	rv |= chg_write8(chgnum, SM5803_REG_DISCH_CONF2,
					reg & SM5803_DISCH_CONF5_VPWR_LSB);

	return rv;
}

static enum ec_error_list sm5803_enable_otg_power(int chgnum, int enabled)
{
	enum ec_error_list rv;
	int reg, status;

	if (enabled) {
		int selected_current;

		rv = chg_read8(chgnum, SM5803_REG_ANA_EN1, &reg);
		if (rv)
			return rv;

		/* Enable current limit */
		reg &= ~SM5803_ANA_EN1_CLS_DISABLE;
		rv = chg_write8(chgnum, SM5803_REG_ANA_EN1, reg);

		/* Disable ramps on current set in discharge */
		rv |= chg_read8(chgnum, SM5803_REG_DISCH_CONF6, &reg);
		reg |= SM5803_DISCH_CONF6_RAMPS_DIS;
		rv |= chg_write8(chgnum, SM5803_REG_DISCH_CONF6, reg);

		/*
		 * In order to ensure the Vbus output doesn't overshoot too
		 * much, turn the starting voltage down to 4.8 V and ramp up
		 * after 4 ms
		 */
		rv = chg_read8(chgnum, SM5803_REG_DISCH_CONF5, &reg);
		if (rv)
			return rv;

		selected_current = (reg & SM5803_DISCH_CONF5_CLS_LIMIT) *
			SM5803_CLS_CURRENT_STEP;
		sm5803_set_otg_current_voltage(chgnum, selected_current, 4800);

		/*
		 * Enable: SOURCE_MODE - enable sourcing out
		 *	   DIRECTCHG_SOURCE_EN - enable current loop
		 *	   (for designs with no external Vbus FET)
		 */
		rv = sm5803_flow1_update(chgnum, CHARGER_MODE_SOURCE |
					 SM5803_FLOW1_DIRECTCHG_SRC_EN,
					 MASK_SET);
		usleep(4000);

		sm5803_set_otg_current_voltage(chgnum, selected_current, 5000);
	} else {
		/* Always clear out discharge status before clearing FLOW1 */
		rv = chg_read8(chgnum, SM5803_REG_STATUS_DISCHG, &status);
		if (rv)
			return rv;

		if (status)
			CPRINTS("%s %d: Discharge failure 0x%02x", CHARGER_NAME,
				chgnum, status);

		rv |= chg_write8(chgnum, SM5803_REG_STATUS_DISCHG, status);

		/* Re-enable ramps on current set in discharge */
		rv |= chg_read8(chgnum, SM5803_REG_DISCH_CONF6, &reg);
		reg &= ~SM5803_DISCH_CONF6_RAMPS_DIS;
		rv |= chg_write8(chgnum, SM5803_REG_DISCH_CONF6, reg);

		/*
		 * PD tasks will always turn off previous sourcing on init.
		 * Protect ourselves from brown out on init by checking if we're
		 * sinking right now.  The init process should only leave sink
		 * mode enabled if a charger is plugged in; otherwise it's
		 * expected to be 0.
		 *
		 * Always clear out sourcing if the previous source-out failed.
		 */
		rv |= chg_read8(chgnum, SM5803_REG_FLOW1, &reg);
		if (rv)
			return rv;

		if ((reg & SM5803_FLOW1_MODE) != CHARGER_MODE_SINK || status)
			rv = sm5803_flow1_update(chgnum, CHARGER_MODE_SOURCE |
						 SM5803_FLOW1_DIRECTCHG_SRC_EN,
						 MASK_CLR);
	}

	return rv;
}

static int sm5803_is_sourcing_otg_power(int chgnum, int port)
{
	enum ec_error_list rv;
	int reg;

	rv = chg_read8(chgnum, SM5803_REG_FLOW1, &reg);
	if (rv)
		return 0;

	/*
	 * Note: In linear mode, MB charger will read a reserved mode when
	 * sourcing, so bit 1 is the most reliable way to detect sourcing.
	 */
	return (reg & BIT(1));
}

static enum ec_error_list sm5803_set_vsys_compensation(int chgnum,
						       struct ocpc_data *ocpc,
						       int current_ma,
						       int voltage_mv)
{

	int rv;
	int regval;
	int r;

	/* Set IR drop compensation */
	r = ocpc->combined_rsys_rbatt_mo * 100 / 167; /* 1.67mOhm steps */
	r = MAX(0, r);
	rv = chg_write8(chgnum, SM5803_REG_IR_COMP2, r & 0x7F);
	rv |= chg_read8(chgnum, SM5803_REG_IR_COMP1, &regval);
	regval &= ~SM5803_IR_COMP_RES_SET_MSB;
	r = r >> 8; /* Bits 9:8 */
	regval |= (r & 0x3) << SM5803_IR_COMP_RES_SET_MSB_SHIFT;
	regval |= SM5803_IR_COMP_EN;
	rv |= chg_write8(chgnum, SM5803_REG_IR_COMP1, regval);

	if (rv)
		return EC_ERROR_UNKNOWN;

	return EC_ERROR_UNIMPLEMENTED;
}

/* Hardware current ramping (aka DPM: Dynamic Power Management) */

#ifdef CONFIG_CHARGE_RAMP_HW
static enum ec_error_list sm5803_set_hw_ramp(int chgnum, int enable)
{
	enum ec_error_list rv;
	int reg;

	rv = chg_read8(chgnum, SM5803_REG_CHG_MON_REG, &reg);

	if (enable)
		reg |= SM5803_DPM_LOOP_EN;
	else
		reg &= ~SM5803_DPM_LOOP_EN;

	rv |= chg_write8(chgnum, SM5803_REG_CHG_MON_REG, reg);

	return rv;
}

static int sm5803_ramp_is_stable(int chgnum)
{
	/*
	 * There is no way to read current limit that the ramp has
	 * settled on with sm5803, so we don't consider the ramp stable,
	 * because we never know what the stable limit is.
	 */
	return 0;
}

static int sm5803_ramp_is_detected(int chgnum)
{
	return 1;
}

static int sm5803_ramp_get_current_limit(int chgnum)
{
	int rv;
	int input_current = 0;

	rv = sm5803_get_input_current_limit(chgnum, &input_current);

	return rv ? -1 : input_current;
}
#endif /* CONFIG_CHARGE_RAMP_HW */

#ifdef CONFIG_CMD_CHARGER_DUMP
static void command_sm5803_dump(int chgnum)
{
	int reg;
	int regval;

	/* Dump base regs */
	ccprintf("BASE regs\n");
	for (reg = 0x01; reg <= 0x30; reg++) {
		if (!main_read8(chgnum, reg, &regval))
			ccprintf("[0x%02X] = 0x%02x\n", reg, regval);
		if (reg & 0xf) {
			cflush(); /* Flush periodically */
			watchdog_reload();
		}
	}

	/* Dump measure regs */
	ccprintf("MEAS regs\n");
	for (reg = 0x01; reg <= 0xED; reg++) {
		if (!meas_read8(chgnum, reg, &regval))
			ccprintf("[0x%02X] = 0x%02x\n", reg, regval);
		if (reg & 0xf) {
			cflush(); /* Flush periodically */
			watchdog_reload();
		}
	}

	/* Dump Charger regs from 0x1C to 0x7F */
	ccprintf("CHG regs\n");
	for (reg = 0x1C; reg <= 0x7F; reg++) {
		if (!chg_read8(chgnum, reg, &regval))
			ccprintf("[0x%02X] = 0x%02x\n", reg, regval);
		if (reg & 0xf) {
			cflush(); /* Flush periodically */
			watchdog_reload();
		}
	}
}
#endif /* CONFIG_CMD_CHARGER_DUMP */

const struct charger_drv sm5803_drv = {
	.init = &sm5803_init,
	.post_init = &sm5803_post_init,
	.get_info = &sm5803_get_info,
	.get_status = &sm5803_get_status,
	.set_mode = &sm5803_set_mode,
	.get_actual_current = &sm5803_get_actual_current,
	.get_current = &sm5803_get_current,
	.set_current = &sm5803_set_current,
	.get_actual_voltage = &sm5803_get_actual_voltage,
	.get_voltage = &sm5803_get_voltage,
	.set_voltage = &sm5803_set_voltage,
	.discharge_on_ac = &sm5803_discharge_on_ac,
	.get_vbus_voltage = &sm5803_get_vbus_voltage,
	.set_input_current_limit = &sm5803_set_input_current_limit,
	.get_input_current_limit = &sm5803_get_input_current_limit,
	.get_input_current = &sm5803_get_input_current,
	.device_id = &sm5803_get_dev_id,
	.get_option = &sm5803_get_option,
	.set_option = &sm5803_set_option,
	.set_otg_current_voltage = &sm5803_set_otg_current_voltage,
	.enable_otg_power = &sm5803_enable_otg_power,
	.is_sourcing_otg_power = &sm5803_is_sourcing_otg_power,
	.set_vsys_compensation = &sm5803_set_vsys_compensation,
	.is_icl_reached = &sm5803_is_input_current_limit_reached,
	.enable_linear_charge = &sm5803_enable_linear_charge,
#ifdef CONFIG_CHARGE_RAMP_HW
	.set_hw_ramp = &sm5803_set_hw_ramp,
	.ramp_is_stable = &sm5803_ramp_is_stable,
	.ramp_is_detected = &sm5803_ramp_is_detected,
	.ramp_get_current_limit = &sm5803_ramp_get_current_limit,
#endif
#ifdef CONFIG_CMD_CHARGER_DUMP
	.dump_registers = &command_sm5803_dump,
#endif
};
