/* Copyright 2021 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.
 */

/* Kinetic KTU1125 USB-C Power Path Controller */

#include "common.h"
#include "console.h"
#include "ktu1125.h"
#include "hooks.h"
#include "i2c.h"
#include "system.h"
#include "timer.h"
#include "usb_charge.h"
#include "usb_pd_tcpm.h"
#include "usb_pd.h"
#include "usbc_ppc.h"
#include "util.h"

#define CPRINTF(format, args...) cprintf(CC_USBPD, format, ## args)
#define CPRINTS(format, args...) cprints(CC_USBPD, format, ## args)

static uint32_t irq_pending; /* Bitmask of ports signaling an interrupt. */

static int read_reg(uint8_t port, int reg, int *regval)
{
	return i2c_read8(ppc_chips[port].i2c_port,
			 ppc_chips[port].i2c_addr_flags,
			 reg,
			 regval);
}

static int write_reg(uint8_t port, int reg, int regval)
{
	return i2c_write8(ppc_chips[port].i2c_port,
			  ppc_chips[port].i2c_addr_flags,
			  reg,
			  regval);
}

static int set_flags(const int port, const int addr, const int flags_to_set)
{
	int val, rv;

	rv = read_reg(port, addr, &val);
	if (rv)
		return rv;

	val |= flags_to_set;

	return write_reg(port, addr, val);
}

static int clr_flags(const int port, const int addr, const int flags_to_clear)
{
	int val, rv;

	rv = read_reg(port, addr, &val);
	if (rv)
		return rv;

	val &= ~flags_to_clear;

	return write_reg(port, addr, val);
}

static int set_field(const int port, const int addr, const int shift,
		     const int field_length, const int field_to_set)
{
	int val, rv, mask;

	mask = ((1 << field_length) - 1) << shift;
	val = (field_to_set << shift) & mask;

	rv = clr_flags(port, addr, mask);
	if (rv)
		return rv;

	return set_flags(port, addr, val);
}


#ifdef CONFIG_CMD_PPC_DUMP
static int ktu1125_dump(int port)
{
	int i;
	int data;

	for (i = KTU1125_ID; i <= KTU1125_INT_DATA; i++) {
		read_reg(port, i, &data);
		CPRINTF("REG %02Xh = 0x%02x\n", i, data);
	}

	cflush();
	return EC_SUCCESS;
}
#endif /* defined(CONFIG_CMD_PPC_DUMP) */

/* helper */
static int ktu1125_power_path_control(int port, int enable)
{
	int status = enable ? clr_flags(port, KTU1125_CTRL_SW_CFG,
					KTU1125_SW_AB_EN)
			    : set_flags(port, KTU1125_CTRL_SW_CFG,
					KTU1125_SW_AB_EN);

	if (status) {
		CPRINTS("ppc p%d: Failed to %s power path",
			port, enable ? "enable" : "disable");
	}

	return status;
}

static int ktu1125_init(int port)
{
	int regval;
	int ctrl_sw_cfg = 0;
	int set_sw_cfg = 0;
	int set_sw2_cfg = 0;
	int sysb_clp;
	int status;

	CPRINTF("KTU1125 init\n");

	/* Read and verify KTU1125 Vendor and Chip ID */
	status = read_reg(port, KTU1125_ID, &regval);

	if (status) {
		ppc_prints("Failed to read device ID!", port);
		return status;
	}

	if (regval != KTU1125_VENDOR_DIE_IDS) {
		ppc_err_prints("KTU1125 ID mismatch!", port, regval);
		return regval;
	}


	/*
	 * Setting control register CTRL_SW_CFG
	 */

	/* Check if VBUS is present and set SW_AB_EN accordingly */
	status = read_reg(port, KTU1125_MONITOR_SNK, &regval);
	if (status) {
		ppc_err_prints("VBUS present error", port, status);
		return 0;
	}

	if (regval & KTU1125_SYSA_OK)
		ctrl_sw_cfg = KTU1125_SW_AB_EN;

	status = write_reg(port, KTU1125_CTRL_SW_CFG, ctrl_sw_cfg);
	if (status) {
		ppc_err_prints("Failed to write CTRL_SW_CFG!", port, status);
		return status;
	}

	/*
	 * Setting control register SET_SW_CFG
	 */

#ifdef CONFIG_USB_PD_MAX_SINGLE_SOURCE_CURRENT
	/* Set the sourcing current limit value */
	switch (CONFIG_USB_PD_MAX_SINGLE_SOURCE_CURRENT) {
	case TYPEC_RP_3A0:
		/* Set current limit to ~3A */
		sysb_clp = KTU1125_SYSB_ILIM_3_30;
		break;

	case TYPEC_RP_1A5:
	default:
		/* Set current limit to ~1.5A */
		sysb_clp = KTU1125_SYSB_ILIM_1_70;
		break;
	}
#else /* !defined(CONFIG_USB_PD_MAX_SINGLE_SOURCE_CURRENT) */
	/* Default SRC current limit to ~1.5A */
	sysb_clp = KTU1125_SYSB_ILIM_1_70;
#endif /* defined(CONFIG_USB_PD_MAX_SINGLE_SOURCE_CURRENT) */

	/* Set SYSB Current Limit Protection */
	set_sw_cfg |= sysb_clp << KTU1125_SYSB_CLP_SHIFT;
	/* Set VCONN Current Limit Protection.
	 * Note: might be changed to 600mA in future
	 */
	set_sw_cfg |= KTU1125_VCONN_ILIM_0_40 << KTU1125_VCONN_CLP_SHIFT;
	/* Disable Dead Battery resistance, because CC FETs are ON */
	set_sw_cfg |= KTU1125_RDB_DIS;

	status = write_reg(port, KTU1125_SET_SW_CFG, set_sw_cfg);
	if (status) {
		ppc_err_prints("Failed to write SET_SW_CFG!", port, status);
		return status;
	}

	/*
	 * Setting control register SET_SW2_CFG
	 */

	/* Set T_HIC */
	set_sw2_cfg |= (KTU_T_HIC_MS_17 << KTU1125_T_HIC_SHIFT);
	/* Set vbus discharge resistance */
	set_sw2_cfg |= (KTU1125_DIS_RES_1400 << KTU1125_DIS_RES_SHIFT);
	/* Set Vbus OVP threshold to ~5V */
	set_sw2_cfg |= (KTU1125_SYSB_VLIM_6_00 << KTU1125_OVP_BUS_SHIFT);

	status = write_reg(port, KTU1125_SET_SW2_CFG, set_sw2_cfg);
	if (status) {
		ppc_err_prints("Failed to write SET_SW2_CFG!", port, status);
		return status;
	}

	/*
	 * Don't proceed with the rest of initialization if we're sysjumping.
	 * We would have already done this before
	 */
	if (system_jumped_late())
		return EC_SUCCESS;

	/*
	 * Enable interrupts
	 */

	/* Leave SYSA_OK and FRS masked for SNK group of interrupts */
	regval =  KTU1125_SNK_MASK_ALL & ~(KTU1125_SYSA_OK | KTU1125_FR_SWAP);
	status = write_reg(port, KTU1125_INTMASK_SNK, regval);
	if (status) {
		ppc_err_prints("Failed to write INTMASK_SNK!", port, status);
		return status;
	}

	/* Only leave VBUS_OK masked for SRC group of interrupts */
	regval = KTU1125_SRC_MASK_ALL & ~KTU1125_VBUS_OK;
	status = write_reg(port, KTU1125_INTMASK_SRC, regval);
	if (status) {
		ppc_err_prints("Failed to write INTMASK_SRC!", port, status);
		return status;
	}

	/* Unmask the entire DATA group of interrupts */
	status = write_reg(port, KTU1125_INTMASK_DATA, KTU1125_DATA_MASK_ALL);
	if (status) {
		ppc_err_prints("Failed to write INTMASK_DATA!", port, status);
		return status;
	}

	return EC_SUCCESS;
}

#ifdef CONFIG_USB_PD_VBUS_DETECT_PPC
static int ktu1125_is_vbus_present(int port)
{
	int regval;
	int rv;

	rv = read_reg(port, KTU1125_MONITOR_SNK, &regval);
	if (rv) {
		ppc_err_prints("VBUS present error", port, rv);
		return 0;
	}

	return regval & KTU1125_SYSA_OK;
}
#endif /* defined(CONFIG_USB_PD_VBUS_DETECT_PPC) */

static int ktu1125_is_sourcing_vbus(int port)
{
	int regval;
	int rv;

	rv = read_reg(port, KTU1125_MONITOR_SRC, &regval);
	if (rv) {
		ppc_err_prints("Sourcing VBUS error", port, rv);
		return 0;
	}

	return regval & KTU1125_VBUS_OK;
}

#ifdef CONFIG_USBC_PPC_POLARITY
static int ktu1125_set_polarity(int port, int polarity)
{
	if (polarity) {
		/* CC2 active. */
		clr_flags(port, KTU1125_CTRL_SW_CFG, KTU1125_CC2S_VCONN);
		return set_flags(port, KTU1125_CTRL_SW_CFG,
				 KTU1125_CC1S_VCONN);
	}

	/* else CC1 active. */
	clr_flags(port, KTU1125_CTRL_SW_CFG, KTU1125_CC1S_VCONN);
	return set_flags(port, KTU1125_CTRL_SW_CFG, KTU1125_CC2S_VCONN);
}
#endif

static int ktu1125_set_vbus_src_current_limit(int port, enum tcpc_rp_value rp)
{
	int regval;
	int status;

	/*
	 * Note that we chose the lowest current limit setting that is just
	 * above indicated Rp value. This is because these are minimum values
	 * and we must be able to provide the current that we advertise
	 */
	switch (rp) {
	case TYPEC_RP_3A0:
		regval = KTU1125_SYSB_ILIM_3_30;
		break;

	case TYPEC_RP_1A5:
		regval = KTU1125_SYSB_ILIM_1_70;
		break;

	case TYPEC_RP_USB:
	default:
		regval = KTU1125_SYSB_ILIM_0_6;
		break;
	};


	status = set_field(port, KTU1125_SET_SW_CFG, KTU1125_SYSB_CLP_SHIFT,
			    KTU1125_SYSB_CLP_LEN, regval);
	if (status)
		ppc_prints("Failed to set KTU1125_SET_SW_CFG!", port);

	return status;
}

static int ktu1125_discharge_vbus(int port, int enable)
{
	int status = enable ? set_flags(port, KTU1125_SET_SW2_CFG,
					KTU1125_VBUS_DIS_EN)
			    : clr_flags(port, KTU1125_SET_SW2_CFG,
					KTU1125_VBUS_DIS_EN);

	if (status) {
		CPRINTS("ppc p%d: Failed to %s vbus discharge",
			port, enable ? "enable" : "disable");
		return status;
	}

	return EC_SUCCESS;
}

#ifdef CONFIG_USBC_PPC_VCONN
static int ktu1125_set_vconn(int port, int enable)
{
	int status = enable ? set_flags(port, KTU1125_CTRL_SW_CFG,
					KTU1125_VCONN_EN)
			    : clr_flags(port, KTU1125_CTRL_SW_CFG,
					KTU1125_VCONN_EN);

	return status;
}
#endif

#ifdef CONFIG_USB_PD_FRS_PPC
static int ktu1125_set_frs_enable(int port, int enable)
{
	/* Enable/Disable FR_SWAP Interrupt */
	int status = enable ? clr_flags(port, KTU1125_INTMASK_SNK,
					KTU1125_FR_SWAP)
			    : set_flags(port, KTU1125_INTMASK_SNK,
					KTU1125_FR_SWAP);

	if (status) {
		ppc_prints("Failed to write KTU1125_INTMASK_SNK!", port);
		return status;
	}

	/* Set the FRS_EN bit */
	status = enable ? set_flags(port, KTU1125_CTRL_SW_CFG,
				    KTU1125_FRS_EN)
			: clr_flags(port, KTU1125_CTRL_SW_CFG,
				    KTU1125_FRS_EN);

	return status;
}
#endif

static int ktu1125_vbus_sink_enable(int port, int enable)
{
	/* Select active sink */
	int rv = clr_flags(port, KTU1125_CTRL_SW_CFG, KTU1125_POW_MODE);

	if (rv) {
		ppc_err_prints("Could not select SNK path", port, rv);
		return rv;
	}

	return ktu1125_power_path_control(port, enable);
}

static int ktu1125_vbus_source_enable(int port, int enable)
{
	/* Select active source */
	int rv = set_flags(port, KTU1125_CTRL_SW_CFG, KTU1125_POW_MODE);

	if (rv) {
		ppc_err_prints("Could not select SRC path", port, rv);
		return rv;
	}

	return ktu1125_power_path_control(port, enable);
}

#ifdef CONFIG_USBC_PPC_SBU
static int ktu1125_set_sbu(int port, int enable)
{
	int status = enable ? clr_flags(port, KTU1125_CTRL_SW_CFG,
					KTU1125_SBU_SHUT)
			    : set_flags(port, KTU1125_CTRL_SW_CFG,
					KTU1125_SBU_SHUT);

	if (status) {
		CPRINTS("ppc p%d: Failed to %s sbu",
			port, enable ? "enable" : "disable");
	}

	return status;
}
#endif /* CONFIG_USBC_PPC_SBU */

static void ktu1125_handle_interrupt(int port)
{
	int attempt = 0;

	/*
	 * KTU1135's /INT pin is level, so process interrupts until it
	 * deasserts if the chip has a dedicated interrupt pin.
	 */
#ifdef CONFIG_USBC_PPC_DEDICATED_INT
	while (ppc_get_alert_status(port))
#endif
	{
		int ovp_int_count = 0;
		int snk = 0;
		int src = 0;
		int data = 0;

		attempt++;
		if (attempt > 1)
			ppc_prints("Could not clear interrupts on first "
				"try, retrying", port);

		/* Clear the interrupt by reading all 3 registers */
		read_reg(port, KTU1125_INT_SNK, &snk);
		read_reg(port, KTU1125_INT_SRC, &src);
		read_reg(port, KTU1125_INT_DATA, &data);

		CPRINTS("ppc p%d: INTERRUPT snk=%02X src=%02X data=%02X",
			port, snk, src, data);

		if (snk & KTU1125_FR_SWAP)
			pd_got_frs_signal(port);

		if (snk & (KTU1125_SYSA_SCP |
			   KTU1125_SYSA_OCP |
			   KTU1125_VBUS_OVP)) {
			/* Log and PD reset */
			pd_handle_overcurrent(port);
		}

		if (src & (KTU1125_SYSB_CLP |
			   KTU1125_SYSB_OCP |
			   KTU1125_SYSB_SCP |
			   KTU1125_VCONN_CLP |
			   KTU1125_VCONN_SCP)) {
			/* Log and PD reset */
			pd_handle_overcurrent(port);
		}

		if (data & (KTU1125_SBU2_OVP | KTU1125_SBU1_OVP)) {
			/* Log and PD reset */
			pd_handle_overcurrent(port);
		}

		if (data & (KTU1125_CC1_OVP | KTU1125_CC2_OVP)) {
			ppc_prints("CC Over Voltage!", port);
			/*
			 * Bug on ktu1125 Rev A:
			 * OVP interrupts are falsely triggered
			 * after IC reset (RST_L 0-> 1)
			 */
			if (ovp_int_count++)
				pd_handle_cc_overvoltage(port);
		}
	}
}

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

	for (i = 0; i < board_get_usb_pd_port_count(); i++)
		if (BIT(i) & pending)
			ktu1125_handle_interrupt(i);
}
DECLARE_DEFERRED(ktu1125_irq_deferred);

void ktu1125_interrupt(int port)
{
	atomic_or(&irq_pending, BIT(port));
	hook_call_deferred(&ktu1125_irq_deferred_data, 0);
}

const struct ppc_drv ktu1125_drv = {
	.init = &ktu1125_init,
	.is_sourcing_vbus = &ktu1125_is_sourcing_vbus,
	.vbus_sink_enable = &ktu1125_vbus_sink_enable,
	.vbus_source_enable = &ktu1125_vbus_source_enable,
#ifdef CONFIG_USBC_PPC_POLARITY
	.set_polarity = &ktu1125_set_polarity,
#endif
	.set_vbus_source_current_limit = &ktu1125_set_vbus_src_current_limit,
	.discharge_vbus = &ktu1125_discharge_vbus,
#ifdef CONFIG_USBC_PPC_SBU
	.set_sbu = &ktu1125_set_sbu,
#endif
#ifdef CONFIG_USBC_PPC_VCONN
	.set_vconn = &ktu1125_set_vconn,
#endif
#ifdef CONFIG_USB_PD_FRS_PPC
	.set_frs_enable = &ktu1125_set_frs_enable,
#endif
#ifdef CONFIG_CMD_PPC_DUMP
	.reg_dump = &ktu1125_dump,
#endif
#ifdef CONFIG_USB_PD_VBUS_DETECT_PPC
	.is_vbus_present = &ktu1125_is_vbus_present,
#endif
	.interrupt = &ktu1125_interrupt,
};
