/* 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.
 */
/* Richtek RT1715 Type-C port controller */

#include "common.h"
#include "rt1715.h"
#include "tcpm/tcpci.h"
#include "tcpm/tcpm.h"
#include "timer.h"
#include "usb_pd.h"
#include "usb_pd_tcpm.h"

#ifndef CONFIG_USB_PD_TCPM_TCPCI
#error "RT1715 is using a standard TCPCI interface"
#error "Please upgrade your board configuration"
#endif

static int rt1715_polarity[CONFIG_USB_PD_PORT_MAX_COUNT];
static bool rt1715_initialized[CONFIG_USB_PD_PORT_MAX_COUNT];


static int rt1715_enable_ext_messages(int port, int enable)
{
	return tcpc_update8(port, RT1715_REG_VENDOR_5,
		  RT1715_REG_VENDOR_5_ENEXTMSG,
		  enable ? MASK_SET : MASK_CLR);
}

static int rt1715_tcpci_tcpm_init(int port)
{
	int rv;
	/*
	 * Do not fully reinitialize the registers when leaving low-power mode.
	 * TODO(b/179234089): Generalize this concept in the tcpm_drv API.
	 */

	/* Only do soft-reset on first init. */
	if (!(rt1715_initialized[port])) {
		/* RT1715 has a vendor-defined register reset */
		rv = tcpc_update8(port, RT1715_REG_VENDOR_7,
			RT1715_REG_VENDOR_7_SOFT_RESET, MASK_SET);
		if (rv)
			return rv;
		rt1715_initialized[port] = true;
		msleep(10);
	}

	rv = tcpc_update8(port, RT1715_REG_VENDOR_5,
		  RT1715_REG_VENDOR_5_SHUTDOWN_OFF, MASK_SET);
	if (rv)
		return rv;

	if (IS_ENABLED(CONFIG_USB_PD_REV30))
		rt1715_enable_ext_messages(port, 1);

	rv = tcpc_write(port, RT1715_REG_I2CRST_CTRL,
		  (RT1715_REG_I2CRST_CTRL_EN |
		  RT1715_REG_I2CRST_CTRL_TOUT_200MS));
	if (rv)
		return rv;

	/* Unmask interrupt for LPM wakeup */
	rv = tcpc_write(port, RT1715_REG_RT_MASK, RT1715_REG_RT_MASK_M_WAKEUP);
	if (rv)
		return rv;

	/*
	 * Set tTCPCFilter (CC debounce time) to 400 us
	 * (min 250 us, max 500 us).
	 */
	rv = tcpc_write(port, RT1715_REG_TTCPC_FILTER,
		  RT1715_REG_TTCPC_FILTER_400US);
	if (rv)
		return rv;

	rv = tcpc_write(port, RT1715_REG_DRP_TOGGLE_CYCLE,
		  RT1715_REG_DRP_TOGGLE_CYCLE_76MS);
	if (rv)
		return rv;

	/* PHY control */
	/* Set PHY control registers to Richtek recommended values */
	rv = tcpc_write(port, RT1715_REG_PHY_CTRL1,
		  (RT1715_REG_PHY_CTRL1_ENRETRY |
		  RT1715_REG_PHY_CTRL1_TRANSCNT_7 |
		  RT1715_REG_PHY_CTRL1_TRXFILTER_125NS));
	if (rv)
		return rv;

	/* Set PHY control registers to Richtek recommended values */
	rv = tcpc_write(port, RT1715_REG_PHY_CTRL2,
		  RT1715_REG_PHY_CTRL2_CDRTHRESH_2_58US);
	if (rv)
		return rv;

	return tcpci_tcpm_init(port);
}

/*
 * Selects the CC PHY noise filter voltage level according to the current
 * CC voltage level.
 *
 * @param cc_level The CC voltage level for the port's current role
 * @return EC_SUCCESS if writes succeed; failure code otherwise
 */
static inline int rt1715_init_cc_params(int port, int cc_level)
{
	int rv, en, sel;

	if (cc_level == TYPEC_CC_VOLT_RP_DEF) {
		/* RXCC threshold : 0.55V */
		en = RT1715_REG_BMCIO_RXDZEN_DISABLE;

		sel = RT1715_REG_BMCIO_RXDZSEL_OCCTRL_600MA
		  | RT1715_REG_BMCIO_RXDZSEL_SEL;
	} else {
		/* RD threshold : 0.35V & RP threshold : 0.75V */
		en = RT1715_REG_BMCIO_RXDZEN_ENABLE;

		sel = RT1715_REG_BMCIO_RXDZSEL_OCCTRL_600MA
		  | RT1715_REG_BMCIO_RXDZSEL_SEL;
	}

	rv = tcpc_write(port, RT1715_REG_BMCIO_RXDZEN, en);
	if (!rv)
		rv = tcpc_write(port, RT1715_REG_BMCIO_RXDZSEL, sel);

	return rv;
}

static int rt1715_get_cc(int port, enum tcpc_cc_voltage_status *cc1,
	enum tcpc_cc_voltage_status *cc2)
{
	int rv;

	rv = tcpci_tcpm_get_cc(port, cc1, cc2);
	if (rv)
		return rv;

	return rt1715_init_cc_params(port, rt1715_polarity[port] ? *cc2 : *cc1);
}

/*
 * See b/179256608#comment26 for explanation.
 * Disable 24MHz oscillator and enable LPM. Upon exit from LPM, the LPEN will be
 * reset to 0.
 *
 * The exit condition for LPM is CC status change, and the wakeup interrupt will
 * be set.
 */
#ifdef CONFIG_USB_PD_TCPC_LOW_POWER
static int rt1715_enter_low_power_mode(int port)
{
	int regval;
	int rv;

	rv = tcpc_read(port, RT1715_REG_PWR, &regval);
	if (rv)
		return rv;

	regval |= RT1715_REG_PWR_BMCIO_LPEN;
	regval &= ~RT1715_REG_PWR_BMCIO_OSCEN;
	rv = tcpc_write(port, RT1715_REG_PWR, regval);
	if (rv)
		return rv;

	return tcpci_enter_low_power_mode(port);
}
#endif

static int rt1715_set_vconn(int port, int enable)
{
	int rv;
	int regval;

	/*
	 * Auto-idle cannot be used while sourcing Vconn.
	 * See b/179256608#comment26 for explanation.
	 */
	rv = tcpc_read(port, RT1715_REG_VENDOR_5, &regval);
	if (rv)
		return rv;

	if (enable)
		regval &= ~RT1715_REG_VENDOR_5_AUTOIDLE_EN;
	else
		regval |= RT1715_REG_VENDOR_5_AUTOIDLE_EN;

	rv = tcpc_write(port, RT1715_REG_VENDOR_5, regval);
	if (rv)
		return rv;

	return tcpci_tcpm_set_vconn(port, enable);
}

static int rt1715_set_polarity(int port, enum tcpc_cc_polarity polarity)
{
	int rv;
	enum tcpc_cc_voltage_status cc1, cc2;

	rt1715_polarity[port] = polarity;

	rv = tcpci_tcpm_get_cc(port, &cc1, &cc2);
	if (rv)
		return rv;

	rv = rt1715_init_cc_params(port, polarity ? cc2 : cc1);
	if (rv)
		return rv;

	return tcpci_tcpm_set_polarity(port, polarity);
}

static void rt1715_alert(int port)
{
	/*
	 * Make sure the wakeup interrupt is cleared. This bit is set on wakeup
	 * from LPM. See b/179256608#comment16 for explanation.
	 */
	tcpc_write(port, RT1715_REG_RT_INT, RT1715_REG_RT_INT_WAKEUP);

	tcpci_tcpc_alert(port);
}

const struct tcpm_drv rt1715_tcpm_drv = {
	.init = &rt1715_tcpci_tcpm_init,
	.release = &tcpci_tcpm_release,
	.get_cc = &rt1715_get_cc,
#ifdef CONFIG_USB_PD_VBUS_DETECT_TCPC
	.check_vbus_level = &tcpci_tcpm_check_vbus_level,
#endif
	.select_rp_value = &tcpci_tcpm_select_rp_value,
	.set_cc = &tcpci_tcpm_set_cc,
	.set_polarity = &rt1715_set_polarity,
#ifdef CONFIG_USB_PD_DECODE_SOP
	.sop_prime_enable   = &tcpci_tcpm_sop_prime_enable,
#endif
	.set_vconn = &rt1715_set_vconn,
	.set_msg_header = &tcpci_tcpm_set_msg_header,
	.set_rx_enable = &tcpci_tcpm_set_rx_enable,
	.get_message_raw = &tcpci_tcpm_get_message_raw,
	.transmit = &tcpci_tcpm_transmit,
	.tcpc_alert = &rt1715_alert,
#ifdef CONFIG_USB_PD_DISCHARGE_TCPC
	.tcpc_discharge_vbus = &tcpci_tcpc_discharge_vbus,
#endif
	.tcpc_enable_auto_discharge_disconnect =
		&tcpci_tcpc_enable_auto_discharge_disconnect,
#ifdef CONFIG_USB_PD_DUAL_ROLE_AUTO_TOGGLE
	.drp_toggle = &tcpci_tcpc_drp_toggle,
#endif
#ifdef CONFIG_USB_PD_PPC
	.set_snk_ctrl = &tcpci_tcpm_set_snk_ctrl,
	.set_src_ctrl = &tcpci_tcpm_set_src_ctrl,
#endif
	.get_chip_info = &tcpci_get_chip_info,
#ifdef CONFIG_USB_PD_TCPC_LOW_POWER
	.enter_low_power_mode = &rt1715_enter_low_power_mode,
#endif
	.set_bist_test_mode	= &tcpci_set_bist_test_mode,
};
