/* * 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, ®); 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, ®); 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, ®); 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, ®); 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 };