coreboot-libre-fam15h-rdimm/3rdparty/chromeec/driver/tcpm/nct38xx.c

368 lines
9.0 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 "ioexpander_nct38xx.h"
#include "nct38xx.h"
#include "tcpci.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)
#define POLARITY_NORMAL 0
#define POLARITY_FLIPPED 1
#define POLARITY_NONE 3
static int cable_polarity[CONFIG_USB_PD_PORT_COUNT];
static unsigned char txBuf[33];
static unsigned char rxBuf[33];
/* Save the selected rp value */
static int selected_rp[CONFIG_USB_PD_PORT_COUNT];
static int nct38xx_tcpm_init(int port)
{
int rv = 0;
int reg;
cable_polarity[port] = POLARITY_NONE;
rv = tcpci_tcpm_init(port);
if (rv)
return rv;
/*
* 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_read(port, TCPC_REG_FAULT_CTRL, &reg);
if (rv)
return rv;
reg = reg | TCPC_REG_FAULT_CTRL_VBUS_OVP_FAULT_DIS;
rv = tcpc_write(port, TCPC_REG_FAULT_CTRL, reg);
if (rv)
return rv;
/* Enable VBus monitor */
rv = tcpc_read(port, TCPC_REG_POWER_CTRL, &reg);
if (rv)
return rv;
reg = reg & ~TCPC_REG_POWER_CTRL_VBUS_VOL_MONITOR_DIS;
rv = tcpc_write(port, TCPC_REG_POWER_CTRL, reg);
if (rv)
return rv;
/* Start VBus monitor */
rv = tcpc_write(port, TCPC_REG_COMMAND,
TCPC_REG_COMMAND_ENABLE_VBUS_DETECT);
/*
* Enable the Vendor Define alert event only when the IO expander
* feature is defined
*/
if (IS_ENABLED(CONFIG_IO_EXPANDER_NCT38XX)) {
int mask;
rv |= tcpc_read16(port, TCPC_REG_ALERT_MASK, &mask);
mask |= TCPC_REG_ALERT_VENDOR_DEF;
rv |= tcpc_write16(port, TCPC_REG_ALERT_MASK, mask);
}
return rv;
}
static int tcpci_nct38xx_select_rp_value(int port, int rp)
{
selected_rp[port] = rp;
return EC_SUCCESS;
}
static int auto_discharge_disconnect(int port, int enable)
{
int reg, rv;
rv = tcpc_read(port, TCPC_REG_POWER_CTRL, &reg);
if (rv)
return rv;
if (enable)
reg = reg | TCPC_REG_POWER_CTRL_AUTO_DISCHARGE_DISCONNECT;
else
reg = reg & ~TCPC_REG_POWER_CTRL_AUTO_DISCHARGE_DISCONNECT;
rv = tcpc_write(port, TCPC_REG_POWER_CTRL, reg);
return rv;
}
static int tcpci_nct38xx_check_cable_polarity(int port)
{
int cc, rv;
/* Try to check the polarity */
rv = tcpc_read(port, TCPC_REG_CC_STATUS, &cc);
if (rv)
return rv;
if (TCPC_REG_CC_STATUS_TERM(cc)) {
/* TCPC is presenting RD (Sink mode) */
if ((TCPC_REG_CC_STATUS_CC1(cc) != TYPEC_CC_VOLT_OPEN) &&
(TCPC_REG_CC_STATUS_CC2(cc) == TYPEC_CC_VOLT_OPEN)) {
/* CC1 active && CC2 open */
cable_polarity[port] = POLARITY_NORMAL;
}
if ((TCPC_REG_CC_STATUS_CC1(cc) == TYPEC_CC_VOLT_OPEN) &&
(TCPC_REG_CC_STATUS_CC2(cc) != TYPEC_CC_VOLT_OPEN)) {
/* CC1 open && CC2 active */
cable_polarity[port] = POLARITY_FLIPPED;
}
} else {
/* TCPC is presenting RP (Source mode) */
if ((TCPC_REG_CC_STATUS_CC1(cc) == TYPEC_CC_VOLT_RD) &&
(TCPC_REG_CC_STATUS_CC2(cc) != TYPEC_CC_VOLT_RD)) {
/* CC1 active && CC2 open */
cable_polarity[port] = POLARITY_NORMAL;
}
if ((TCPC_REG_CC_STATUS_CC1(cc) != TYPEC_CC_VOLT_RD) &&
(TCPC_REG_CC_STATUS_CC2(cc) == TYPEC_CC_VOLT_RD)) {
/* CC1 open && CC2 active */
cable_polarity[port] = POLARITY_FLIPPED;
}
}
return rv;
}
/*
* TODO(crbug.com/951681): This code can be simplified once that bug is fixed.
*/
static int tcpci_nct38xx_set_cc(int port, int pull)
{
int rv;
if (cable_polarity[port] == POLARITY_NONE) {
rv = tcpci_nct38xx_check_cable_polarity(port);
if (rv)
return rv;
}
if (cable_polarity[port] == POLARITY_NORMAL) {
rv = tcpc_write(port, TCPC_REG_ROLE_CTRL,
TCPC_REG_ROLE_CTRL_SET(0, selected_rp[port],
pull, TYPEC_CC_OPEN));
} else if (cable_polarity[port] == POLARITY_FLIPPED) {
rv = tcpc_write(port, TCPC_REG_ROLE_CTRL,
TCPC_REG_ROLE_CTRL_SET(0, selected_rp[port],
TYPEC_CC_OPEN, pull));
} else {
return -1;
}
return rv;
}
static int tcpci_nct38xx_get_cc(int port, enum tcpc_cc_voltage_status *cc1,
enum tcpc_cc_voltage_status *cc2)
{
int rv;
int rc;
rv = tcpc_read(port, TCPC_REG_ROLE_CTRL, &rc);
if (rv)
return rv;
rv = tcpci_tcpm_get_cc(port, cc1, cc2);
if (rv)
return rv;
if (!TCPC_REG_ROLE_CTRL_DRP(rc)) {
if ((*cc1 != TYPEC_CC_VOLT_OPEN) &&
(TCPC_REG_ROLE_CTRL_CC1(rc) == TYPEC_CC_RD))
*cc1 |= 0x4;
if ((*cc2 != TYPEC_CC_VOLT_OPEN) &&
(TCPC_REG_ROLE_CTRL_CC2(rc) == TYPEC_CC_RD))
*cc2 |= 0x04;
}
return rv;
}
int tcpci_nct38xx_drp_toggle(int port)
{
int rv;
cable_polarity[port] = POLARITY_NONE;
/*
* The port was disconnected so it is probably a good place to set
* auto-discharge-disconnect to '0'
*
* TODO(crbug.com/951683: this should be removed when common code adds
* auto discharge.
*/
rv = auto_discharge_disconnect(port, 0);
if (rv)
return rv;
return tcpci_tcpc_drp_toggle(port);
}
int tcpci_nct38xx_set_polarity(int port, int polarity)
{
int rv, reg;
rv = tcpc_read(port, TCPC_REG_TCPC_CTRL, &reg);
if (rv)
return rv;
reg = polarity ? (reg | TCPC_REG_TCPC_CTRL_SET(1)) :
(reg & ~TCPC_REG_TCPC_CTRL_SET(1));
rv = tcpc_write(port, TCPC_REG_TCPC_CTRL, reg);
if (rv)
return rv;
/*
* Polarity is set after connection so it is probably a good time to set
* auto-discharge-disconnect to '1'
*/
rv = auto_discharge_disconnect(port, 1);
return rv;
}
int tcpci_nct38xx_transmit(int port, enum tcpm_transmit_type type,
uint16_t header, const uint32_t *data)
{
int rv, num_obj_byte;
num_obj_byte = PD_HEADER_CNT(header) * 4;
txBuf[0] = num_obj_byte + 2;
txBuf[1] = (unsigned char)(header & 0xFF);
txBuf[2] = (unsigned char)((header >> 8) & 0xFF);
if (num_obj_byte) {
uint32_t *buf_ptr;
buf_ptr = (uint32_t *)&txBuf[3];
memcpy(buf_ptr, data, num_obj_byte);
}
/* total write size = size header (1 byte) + message size */
rv = tcpc_write_block(port, TCPC_REG_TX_BYTE_CNT,
(const uint8_t *)txBuf, txBuf[0] + 1);
rv = tcpc_write(port, TCPC_REG_TRANSMIT,
TCPC_REG_TRANSMIT_SET_WITH_RETRY(type));
return rv;
}
static int tcpci_nct38xx_get_message_raw(int port, uint32_t *payload, int *head)
{
int rv, cnt, num_obj_byte;
rv = tcpc_read(port, TCPC_REG_RX_BYTE_CNT, &cnt);
if (rv != EC_SUCCESS || cnt < 3) {
rv = EC_ERROR_UNKNOWN;
goto clear;
}
rv = tcpc_read_block(port, TCPC_REG_RX_BYTE_CNT, rxBuf, cnt + 1);
if (rv != EC_SUCCESS)
goto clear;
*head = *(int *)&rxBuf[2];
num_obj_byte = PD_HEADER_CNT(*head) * 4;
if (num_obj_byte) {
uint32_t *buf_ptr;
buf_ptr = (uint32_t *)&rxBuf[4];
memcpy(payload, buf_ptr, num_obj_byte);
}
clear:
/* Read complete, clear RX status alert bit */
tcpc_write16(port, TCPC_REG_ALERT, TCPC_REG_ALERT_RX_STATUS);
return rv;
}
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);
}
const struct tcpm_drv nct38xx_tcpm_drv = {
.init = &nct38xx_tcpm_init,
.release = &tcpci_tcpm_release,
.get_cc = &tcpci_nct38xx_get_cc,
#ifdef CONFIG_USB_PD_VBUS_DETECT_TCPC
.get_vbus_level = &tcpci_tcpm_get_vbus_level,
#endif
.select_rp_value = &tcpci_nct38xx_select_rp_value,
.set_cc = &tcpci_nct38xx_set_cc,
.set_polarity = &tcpci_nct38xx_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_nct38xx_get_message_raw,
.transmit = &tcpci_nct38xx_transmit,
.tcpc_alert = &nct38xx_tcpc_alert,
#ifdef CONFIG_USB_PD_DISCHARGE_TCPC
.tcpc_discharge_vbus = &tcpci_tcpc_discharge_vbus,
#endif
#ifdef CONFIG_USB_PD_DUAL_ROLE_AUTO_TOGGLE
.drp_toggle = &tcpci_nct38xx_drp_toggle,
#endif
#ifdef CONFIG_USBC_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 = &tcpci_enter_low_power_mode,
#endif
};