chrome-ec/driver/tcpm/nct38xx.c

235 lines
6.3 KiB
C

/*
* Copyright 2019 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.
*/
/* Type-C port manager for Nuvoton NCT38XX. */
#include "common.h"
#include "console.h"
#include "hooks.h"
#include "nct38xx.h"
#include "task.h"
#include "tcpci.h"
#include "usb_common.h"
#if !defined(CONFIG_USB_PD_TCPM_TCPCI)
#error "NCT38XX is using part of standard TCPCI control"
#error "Please upgrade your board configuration"
#endif
#define CPRINTF(format, args...) cprintf(CC_USBPD, format, ## args)
#define CPRINTS(format, args...) cprints(CC_USBPD, format, ## args)
static int nct38xx_init(int port)
{
int rv;
int reg;
/*
* Write to the CONTROL_OUT_EN register to enable:
* [6] - CONNDIREN : Connector direction indication output enable
* [2] - SNKEN : VBUS sink enable output enable
* [0] - SRCEN : VBUS source voltage enable output enable
*/
reg = NCT38XX_REG_CTRL_OUT_EN_SRCEN |
NCT38XX_REG_CTRL_OUT_EN_SNKEN |
NCT38XX_REG_CTRL_OUT_EN_CONNDIREN;
rv = tcpc_write(port, NCT38XX_REG_CTRL_OUT_EN, reg);
if (rv)
return rv;
/* Disable OVP */
rv = tcpc_update8(port,
TCPC_REG_FAULT_CTRL,
TCPC_REG_FAULT_CTRL_VBUS_OVP_FAULT_DIS,
MASK_SET);
if (rv)
return rv;
/* Enable VBus monitor and Disable FRS */
rv = tcpc_update8(port,
TCPC_REG_POWER_CTRL,
(TCPC_REG_POWER_CTRL_VBUS_VOL_MONITOR_DIS |
TCPC_REG_POWER_CTRL_FRS_ENABLE),
MASK_CLR);
if (rv)
return rv;
/* Set FRS direction for SNK detect, if FRS is enabled */
if (IS_ENABLED(CONFIG_USB_PD_FRS_TCPC)) {
reg = TCPC_REG_DEV_CAP_2_SNK_FR_SWAP;
rv = tcpc_write(port, TCPC_REG_DEV_CAP_2, reg);
if (rv)
return rv;
reg = TCPC_REG_CONFIG_EXT_1_FR_SWAP_SNK_DIR;
rv = tcpc_write(port, TCPC_REG_CONFIG_EXT_1, reg);
if (rv)
return rv;
}
/* Start VBus monitor */
rv = tcpc_write(port, TCPC_REG_COMMAND,
TCPC_REG_COMMAND_ENABLE_VBUS_DETECT);
if (rv)
return rv;
/**
* Set driver specific ALERT mask bits
*
* Wake up on faults
*/
reg = TCPC_REG_ALERT_FAULT;
/*
* Enable the Vendor Define alert event only when the IO expander
* feature is defined
*/
if (IS_ENABLED(CONFIG_IO_EXPANDER_NCT38XX))
reg |= TCPC_REG_ALERT_VENDOR_DEF;
rv = tcpc_update16(port,
TCPC_REG_ALERT_MASK,
reg,
MASK_SET);
return rv;
}
static int nct38xx_tcpm_init(int port)
{
int rv;
rv = tcpci_tcpm_init(port);
if (rv)
return rv;
return nct38xx_init(port);
}
int nct38xx_tcpm_set_cc(int port, int pull)
{
/*
* Setting the CC lines to open/open requires that the NCT CTRL_OUT
* register has sink disabled. Otherwise the following happens, as
* described by Nuvoton:
*
* 1. You set CC lines to Open/Open. This is physically happening on
* the CC line.
* 2. Since CC is now Open/Open, the internal TCPC HW state machine
* is no longer in Attached.Snk and therefore our TCPC HW
* automatically opens the sink switch (de-assert the VBSNK_EN pin)
* 3. Since sink switch is open, the TCPC VCC voltage starts to drop.
* 4. When TCPC VCC gets below ~2.7V the TCPC will reset and therefore
* it will present Rd/Rd on the CC lines. Also the VBSNK_EN pin
* after reset is Hi-Z, so the sink switch will get closed again.
*/
if (pull == TYPEC_CC_OPEN) {
int rv;
/* Disable SNKEN, it will be re-enabled in tcpm_init path */
rv = tcpc_update8(port,
NCT38XX_REG_CTRL_OUT_EN,
NCT38XX_REG_CTRL_OUT_EN_SNKEN,
MASK_CLR);
if (rv)
return rv;
}
return tcpci_tcpm_set_cc(port, pull);
}
static void nct38xx_tcpc_alert(int port)
{
int alert, rv;
/*
* If IO expander feature is defined, read the ALERT register first to
* keep the status of Vendor Define bit. Otherwise, the status of ALERT
* register will be cleared after tcpci_tcpc_alert() is executed.
*/
if (IS_ENABLED(CONFIG_IO_EXPANDER_NCT38XX))
rv = tcpc_read16(port, TCPC_REG_ALERT, &alert);
/* Process normal TCPC ALERT event and clear status */
tcpci_tcpc_alert(port);
/*
* If IO expander feature is defined, check the Vendor Define bit to
* handle the IOEX IO's interrupt event
*/
if (IS_ENABLED(CONFIG_IO_EXPANDER_NCT38XX))
if (!rv && (alert & TCPC_REG_ALERT_VENDOR_DEF))
nct38xx_ioex_event_handler(port);
}
static int nct3807_handle_fault(int port, int fault)
{
int rv = EC_SUCCESS;
/* Registers are set to default, initialize for our use */
if (fault & TCPC_REG_FAULT_STATUS_ALL_REGS_RESET) {
rv = nct38xx_init(port);
} else {
/* We don't use TCPC OVP, so just disable it */
if (fault & TCPC_REG_FAULT_STATUS_VBUS_OVER_VOLTAGE) {
/* Disable OVP */
rv = tcpc_update8(port,
TCPC_REG_FAULT_CTRL,
TCPC_REG_FAULT_CTRL_VBUS_OVP_FAULT_DIS,
MASK_SET);
if (rv)
return rv;
}
/* Failing AutoDischargeDisconnect should disable it */
if (fault & TCPC_REG_FAULT_STATUS_AUTO_DISCHARGE_FAIL)
tcpm_enable_auto_discharge_disconnect(port, 0);
}
return rv;
}
const struct tcpm_drv nct38xx_tcpm_drv = {
.init = &nct38xx_tcpm_init,
.release = &tcpci_tcpm_release,
.get_cc = &tcpci_tcpm_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 = &nct38xx_tcpm_set_cc,
.set_polarity = &tcpci_tcpm_set_polarity,
.set_vconn = &tcpci_tcpm_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 = &nct38xx_tcpc_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,
.debug_accessory = &tcpci_tcpc_debug_accessory,
#ifdef CONFIG_USB_PD_DUAL_ROLE_AUTO_TOGGLE
.drp_toggle = &tcpci_tcpc_drp_toggle,
#endif
#ifdef CONFIG_USBC_PPC
.get_snk_ctrl = &tcpci_tcpm_get_snk_ctrl,
.set_snk_ctrl = &tcpci_tcpm_set_snk_ctrl,
.get_src_ctrl = &tcpci_tcpm_get_src_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 = &tcpci_enter_low_power_mode,
#endif
#ifdef CONFIG_USB_PD_FRS_TCPC
.set_frs_enable = &tcpci_tcpc_fast_role_swap_enable,
#endif
.handle_fault = &nct3807_handle_fault,
};