mirror of
https://github.com/Telecominfraproject/OpenCellular.git
synced 2026-01-10 01:21:49 +00:00
PS8751 does not restore all register contents when resuming from low power mode. This change makes tcpm call board_init when it stops auto-toggling so that register contents can be restored. BUG=b:35585399 BRACH=none TEST=On Snappy, the board_init funciton is called every time a device is plugged in and register contents are restored. Change-Id: I50c51334f43c02e3c4d8453e1e966bf6eb3ce769 Signed-off-by: Daisuke Nojiri <dnojiri@chromium.org> Reviewed-on: https://chromium-review.googlesource.com/454139 Reviewed-by: Vincent Palatin <vpalatin@chromium.org>
522 lines
13 KiB
C
522 lines
13 KiB
C
/* Copyright 2015 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 */
|
|
|
|
#include "anx74xx.h"
|
|
#include "ec_commands.h"
|
|
#include "ps8751.h"
|
|
#include "task.h"
|
|
#include "tcpci.h"
|
|
#include "tcpm.h"
|
|
#include "timer.h"
|
|
#include "usb_charge.h"
|
|
#include "usb_mux.h"
|
|
#include "usb_pd.h"
|
|
#include "usb_pd_tcpc.h"
|
|
#include "util.h"
|
|
|
|
static int tcpc_vbus[CONFIG_USB_PD_PORT_COUNT];
|
|
|
|
/* Save the selected rp value */
|
|
static int selected_rp[CONFIG_USB_PD_PORT_COUNT];
|
|
|
|
static int init_alert_mask(int port)
|
|
{
|
|
uint16_t mask;
|
|
|
|
/*
|
|
* Create mask of alert events that will cause the TCPC to
|
|
* signal the TCPM via the Alert# gpio line.
|
|
*/
|
|
mask = TCPC_REG_ALERT_TX_SUCCESS | TCPC_REG_ALERT_TX_FAILED |
|
|
TCPC_REG_ALERT_TX_DISCARDED | TCPC_REG_ALERT_RX_STATUS |
|
|
TCPC_REG_ALERT_RX_HARD_RST | TCPC_REG_ALERT_CC_STATUS
|
|
#ifdef CONFIG_USB_PD_VBUS_DETECT_TCPC
|
|
| TCPC_REG_ALERT_POWER_STATUS
|
|
#endif
|
|
;
|
|
/* Set the alert mask in TCPC */
|
|
return tcpc_write16(port, TCPC_REG_ALERT_MASK, mask);
|
|
}
|
|
|
|
static int init_power_status_mask(int port)
|
|
{
|
|
uint8_t mask;
|
|
int rv;
|
|
|
|
#ifdef CONFIG_USB_PD_VBUS_DETECT_TCPC
|
|
mask = TCPC_REG_POWER_STATUS_VBUS_PRES;
|
|
#else
|
|
mask = 0;
|
|
#endif
|
|
rv = tcpc_write(port, TCPC_REG_POWER_STATUS_MASK , mask);
|
|
|
|
return rv;
|
|
}
|
|
|
|
int tcpci_tcpm_get_cc(int port, int *cc1, int *cc2)
|
|
{
|
|
int status;
|
|
int rv;
|
|
|
|
rv = tcpc_read(port, TCPC_REG_CC_STATUS, &status);
|
|
|
|
/* If tcpc read fails, return error and CC as open */
|
|
if (rv) {
|
|
*cc1 = TYPEC_CC_VOLT_OPEN;
|
|
*cc2 = TYPEC_CC_VOLT_OPEN;
|
|
return rv;
|
|
}
|
|
|
|
*cc1 = TCPC_REG_CC_STATUS_CC1(status);
|
|
*cc2 = TCPC_REG_CC_STATUS_CC2(status);
|
|
|
|
/*
|
|
* If status is not open, then OR in termination to convert to
|
|
* enum tcpc_cc_voltage_status.
|
|
*/
|
|
if (*cc1 != TYPEC_CC_VOLT_OPEN)
|
|
*cc1 |= TCPC_REG_CC_STATUS_TERM(status) << 2;
|
|
if (*cc2 != TYPEC_CC_VOLT_OPEN)
|
|
*cc2 |= TCPC_REG_CC_STATUS_TERM(status) << 2;
|
|
|
|
return rv;
|
|
}
|
|
|
|
static int tcpci_tcpm_get_power_status(int port, int *status)
|
|
{
|
|
return tcpc_read(port, TCPC_REG_POWER_STATUS, status);
|
|
}
|
|
|
|
int tcpci_tcpm_select_rp_value(int port, int rp)
|
|
{
|
|
selected_rp[port] = rp;
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
#ifdef CONFIG_USB_PD_DISCHARGE_TCPC
|
|
static void tcpci_tcpc_discharge_vbus(int port, int enable)
|
|
{
|
|
int reg;
|
|
|
|
if (tcpc_read(port, TCPC_REG_POWER_CTRL, ®))
|
|
return;
|
|
|
|
if (enable)
|
|
reg |= TCPC_REG_POWER_CTRL_FORCE_DISCHARGE;
|
|
else
|
|
reg &= ~TCPC_REG_POWER_CTRL_FORCE_DISCHARGE;
|
|
|
|
tcpc_write(port, TCPC_REG_POWER_CTRL, reg);
|
|
}
|
|
#endif
|
|
|
|
static int set_role_ctrl(int port, int toggle, int rp, int pull)
|
|
{
|
|
return tcpc_write(port, TCPC_REG_ROLE_CTRL,
|
|
TCPC_REG_ROLE_CTRL_SET(toggle, rp, pull, pull));
|
|
}
|
|
|
|
int tcpci_tcpm_set_cc(int port, int pull)
|
|
{
|
|
/* Set manual control, and set both CC lines to the same pull */
|
|
return set_role_ctrl(port, 0, selected_rp[port], pull);
|
|
}
|
|
|
|
#ifdef CONFIG_USB_PD_DUAL_ROLE_AUTO_TOGGLE
|
|
static int tcpci_tcpc_drp_toggle(int port, int enable)
|
|
{
|
|
int rv;
|
|
|
|
if (!enable) {
|
|
#ifdef CONFIG_USB_PD_TCPC_LOW_POWER
|
|
struct usb_mux *mux = &usb_muxes[port];
|
|
if (mux->board_init)
|
|
return mux->board_init(mux);
|
|
#endif
|
|
return EC_SUCCESS;
|
|
}
|
|
/* Set auto drp toggle */
|
|
rv = set_role_ctrl(port, 1, TYPEC_RP_USB, TYPEC_CC_OPEN);
|
|
|
|
/* Set Look4Connection command */
|
|
rv |= tcpc_write(port, TCPC_REG_COMMAND,
|
|
TCPC_REG_COMMAND_LOOK4CONNECTION);
|
|
|
|
#ifdef CONFIG_USB_PD_TCPC_LOW_POWER
|
|
rv |= tcpc_write(port, TCPC_REG_COMMAND, TCPC_REG_COMMAND_I2CIDLE);
|
|
#endif
|
|
return rv;
|
|
}
|
|
#endif
|
|
|
|
int tcpci_tcpm_set_polarity(int port, int polarity)
|
|
{
|
|
return tcpc_write(port, TCPC_REG_TCPC_CTRL,
|
|
TCPC_REG_TCPC_CTRL_SET(polarity));
|
|
}
|
|
|
|
int tcpci_tcpm_set_vconn(int port, int enable)
|
|
{
|
|
int reg, rv;
|
|
|
|
rv = tcpc_read(port, TCPC_REG_POWER_CTRL, ®);
|
|
if (rv)
|
|
return rv;
|
|
reg &= ~TCPC_REG_POWER_CTRL_VCONN(1);
|
|
reg |= TCPC_REG_POWER_CTRL_VCONN(enable);
|
|
return tcpc_write(port, TCPC_REG_POWER_CTRL, reg);
|
|
}
|
|
|
|
int tcpci_tcpm_set_msg_header(int port, int power_role, int data_role)
|
|
{
|
|
return tcpc_write(port, TCPC_REG_MSG_HDR_INFO,
|
|
TCPC_REG_MSG_HDR_INFO_SET(data_role, power_role));
|
|
}
|
|
|
|
static int tcpm_alert_status(int port, int *alert)
|
|
{
|
|
/* Read TCPC Alert register */
|
|
return tcpc_read16(port, TCPC_REG_ALERT, alert);
|
|
}
|
|
|
|
int tcpci_tcpm_set_rx_enable(int port, int enable)
|
|
{
|
|
/* If enable, then set RX detect for SOP and HRST */
|
|
return tcpc_write(port, TCPC_REG_RX_DETECT,
|
|
enable ? TCPC_REG_RX_DETECT_SOP_HRST_MASK : 0);
|
|
}
|
|
|
|
#ifdef CONFIG_USB_PD_VBUS_DETECT_TCPC
|
|
int tcpci_tcpm_get_vbus_level(int port)
|
|
{
|
|
return tcpc_vbus[port];
|
|
}
|
|
#endif
|
|
|
|
int tcpci_tcpm_get_message(int port, uint32_t *payload, int *head)
|
|
{
|
|
int rv, cnt, reg = TCPC_REG_RX_DATA;
|
|
|
|
rv = tcpc_read(port, TCPC_REG_RX_BYTE_CNT, &cnt);
|
|
|
|
/* RX_BYTE_CNT includes 3 bytes for frame type and header */
|
|
if (rv != EC_SUCCESS || cnt < 3) {
|
|
rv = EC_ERROR_UNKNOWN;
|
|
goto clear;
|
|
}
|
|
|
|
rv = tcpc_read16(port, TCPC_REG_RX_HDR, (int *)head);
|
|
|
|
cnt = cnt - 3;
|
|
if (rv == EC_SUCCESS && cnt > 0) {
|
|
tcpc_lock(port, 1);
|
|
rv = tcpc_xfer(port,
|
|
(uint8_t *)®, 1, (uint8_t *)payload,
|
|
cnt, I2C_XFER_SINGLE);
|
|
tcpc_lock(port, 0);
|
|
}
|
|
|
|
clear:
|
|
/* Read complete, clear RX status alert bit */
|
|
tcpc_write16(port, TCPC_REG_ALERT, TCPC_REG_ALERT_RX_STATUS);
|
|
|
|
return rv;
|
|
}
|
|
|
|
int tcpci_tcpm_transmit(int port, enum tcpm_transmit_type type,
|
|
uint16_t header, const uint32_t *data)
|
|
{
|
|
int reg = TCPC_REG_TX_DATA;
|
|
int rv, cnt = 4*PD_HEADER_CNT(header);
|
|
|
|
/* TX_BYTE_CNT includes 2 bytes for message header */
|
|
rv = tcpc_write(port, TCPC_REG_TX_BYTE_CNT, cnt + 2);
|
|
|
|
rv |= tcpc_write16(port, TCPC_REG_TX_HDR, header);
|
|
|
|
/* If tcpc read fails, return error */
|
|
if (rv)
|
|
return rv;
|
|
|
|
if (cnt > 0) {
|
|
tcpc_lock(port, 1);
|
|
rv = tcpc_xfer(port,
|
|
(uint8_t *)®, 1, NULL, 0, I2C_XFER_START);
|
|
rv |= tcpc_xfer(port,
|
|
(uint8_t *)data, cnt, NULL, 0, I2C_XFER_STOP);
|
|
tcpc_lock(port, 0);
|
|
}
|
|
|
|
/* If tcpc read fails, return error */
|
|
if (rv)
|
|
return rv;
|
|
|
|
rv = tcpc_write(port, TCPC_REG_TRANSMIT, TCPC_REG_TRANSMIT_SET(type));
|
|
|
|
return rv;
|
|
}
|
|
|
|
void tcpci_tcpc_alert(int port)
|
|
{
|
|
int status;
|
|
|
|
/* Read the Alert register from the TCPC */
|
|
tcpm_alert_status(port, &status);
|
|
|
|
/*
|
|
* Clear alert status for everything except RX_STATUS, which shouldn't
|
|
* be cleared until we have successfully retrieved message.
|
|
*/
|
|
if (status & ~TCPC_REG_ALERT_RX_STATUS)
|
|
tcpc_write16(port, TCPC_REG_ALERT,
|
|
status & ~TCPC_REG_ALERT_RX_STATUS);
|
|
|
|
if (status & TCPC_REG_ALERT_CC_STATUS) {
|
|
/* CC status changed, wake task */
|
|
task_set_event(PD_PORT_TO_TASK_ID(port), PD_EVENT_CC, 0);
|
|
}
|
|
if (status & TCPC_REG_ALERT_POWER_STATUS) {
|
|
int reg = 0;
|
|
|
|
tcpc_read(port, TCPC_REG_POWER_STATUS_MASK, ®);
|
|
|
|
if (reg == TCPC_REG_POWER_STATUS_MASK_ALL) {
|
|
/*
|
|
* If power status mask has been reset, then the TCPC
|
|
* has reset.
|
|
*/
|
|
task_set_event(PD_PORT_TO_TASK_ID(port),
|
|
PD_EVENT_TCPC_RESET, 0);
|
|
} else {
|
|
/* Read Power Status register */
|
|
tcpci_tcpm_get_power_status(port, ®);
|
|
/* Update VBUS status */
|
|
tcpc_vbus[port] = reg &
|
|
TCPC_REG_POWER_STATUS_VBUS_PRES ? 1 : 0;
|
|
#if defined(CONFIG_USB_PD_VBUS_DETECT_TCPC) && defined(CONFIG_USB_CHARGER)
|
|
/* Update charge manager with new VBUS state */
|
|
usb_charger_vbus_change(port, tcpc_vbus[port]);
|
|
task_wake(PD_PORT_TO_TASK_ID(port));
|
|
#endif /* CONFIG_USB_PD_VBUS_DETECT_TCPC && CONFIG_USB_CHARGER */
|
|
}
|
|
}
|
|
if (status & TCPC_REG_ALERT_RX_STATUS) {
|
|
/* message received */
|
|
task_set_event(PD_PORT_TO_TASK_ID(port), PD_EVENT_RX, 0);
|
|
}
|
|
if (status & TCPC_REG_ALERT_RX_HARD_RST) {
|
|
/* hard reset received */
|
|
pd_execute_hard_reset(port);
|
|
task_wake(PD_PORT_TO_TASK_ID(port));
|
|
}
|
|
if (status & TCPC_REG_ALERT_TX_COMPLETE) {
|
|
/* transmit complete */
|
|
pd_transmit_complete(port, status & TCPC_REG_ALERT_TX_SUCCESS ?
|
|
TCPC_TX_COMPLETE_SUCCESS :
|
|
TCPC_TX_COMPLETE_FAILED);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* For PS8751, this function will fail if the chip is in low power mode.
|
|
* PS8751 has to be woken up by reading a random register first then wait for
|
|
* 10ms.
|
|
*
|
|
* This code doesn't have the wake-up read to avoid 10ms delay. Instead, we
|
|
* call this function immediately after the chip is reset or initialized
|
|
* because it'll gurantee the chip is awake. Once it's called, the chip info
|
|
* will be stored in cache, which can be accessed by tcpm_get_chip_info without
|
|
* worrying about chip states.
|
|
*/
|
|
int tcpci_get_chip_info(int port, int renew,
|
|
struct ec_response_pd_chip_info **chip_info)
|
|
{
|
|
static struct ec_response_pd_chip_info info[CONFIG_USB_PD_PORT_COUNT];
|
|
struct ec_response_pd_chip_info *i;
|
|
int error;
|
|
int val;
|
|
|
|
if (port >= CONFIG_USB_PD_PORT_COUNT)
|
|
return EC_ERROR_INVAL;
|
|
|
|
i = &info[port];
|
|
|
|
/* If chip_info is NULL, chip info will be stored in cache and can be
|
|
* read later by another call. */
|
|
if (chip_info)
|
|
*chip_info = i;
|
|
|
|
/* If already populated and renewal is not asked, return cache value */
|
|
if (i->vendor_id && !renew)
|
|
return EC_SUCCESS;
|
|
|
|
error = tcpc_read16(port, TCPC_REG_VENDOR_ID, &val);
|
|
if (error)
|
|
return error;
|
|
i->vendor_id = val;
|
|
|
|
error = tcpc_read16(port, TCPC_REG_PRODUCT_ID, &val);
|
|
if (error)
|
|
return error;
|
|
i->product_id = val;
|
|
|
|
error = tcpc_read16(port, TCPC_REG_BCD_DEV, &val);
|
|
if (error)
|
|
return error;
|
|
i->device_id = val;
|
|
|
|
switch (i->vendor_id) {
|
|
#ifdef CONFIG_USB_PD_TCPM_ANX74XX
|
|
case ANX74XX_VENDOR_ID:
|
|
error = anx74xx_tcpc_get_fw_version(port, &val);
|
|
break;
|
|
#endif
|
|
#ifdef CONFIG_USB_PD_TCPM_PS8751
|
|
case PS8751_VENDOR_ID:
|
|
error = ps8751_tcpc_get_fw_version(port, &val);
|
|
break;
|
|
#endif
|
|
default:
|
|
/* Even if the chip doesn't implement get_fw_version, we
|
|
* return success.*/
|
|
val = -1;
|
|
error = EC_SUCCESS;
|
|
}
|
|
if (error)
|
|
return error;
|
|
/* This may vary chip to chip. For now everything fits in this format */
|
|
i->fw_version_number = val;
|
|
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* On TCPC i2c failure, make 30 tries (at least 300ms) before giving up
|
|
* in order to allow the TCPC time to boot / reset.
|
|
*/
|
|
#define TCPM_INIT_TRIES 30
|
|
|
|
int tcpci_tcpm_init(int port)
|
|
{
|
|
int error;
|
|
int power_status;
|
|
int tries = TCPM_INIT_TRIES;
|
|
|
|
while (1) {
|
|
error = tcpc_read(port, TCPC_REG_POWER_STATUS, &power_status);
|
|
/*
|
|
* If read succeeds and the uninitialized bit is clear, then
|
|
* initalization is complete, clear all alert bits and write
|
|
* the initial alert mask.
|
|
*/
|
|
if (!error && !(power_status & TCPC_REG_POWER_STATUS_UNINIT))
|
|
break;
|
|
else if (error && --tries == 0)
|
|
return error;
|
|
msleep(10);
|
|
}
|
|
|
|
tcpc_write16(port, TCPC_REG_ALERT, 0xffff);
|
|
/* Initialize power_status_mask */
|
|
init_power_status_mask(port);
|
|
/* Update VBUS status */
|
|
tcpc_vbus[port] = power_status &
|
|
TCPC_REG_POWER_STATUS_VBUS_PRES ? 1 : 0;
|
|
error = init_alert_mask(port);
|
|
if (error)
|
|
return error;
|
|
|
|
/* Read chip info here when we know the chip is awake. */
|
|
tcpm_get_chip_info(port, 1, NULL);
|
|
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
#ifdef CONFIG_USB_PD_TCPM_MUX
|
|
|
|
int tcpci_tcpm_mux_init(int i2c_addr)
|
|
{
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
int tcpci_tcpm_mux_set(int i2c_addr, mux_state_t mux_state)
|
|
{
|
|
int reg = 0;
|
|
int rv;
|
|
int port = i2c_addr; /* use port index in port_addr field */
|
|
|
|
rv = tcpc_read(port, TCPC_REG_CONFIG_STD_OUTPUT, ®);
|
|
if (rv != EC_SUCCESS)
|
|
return rv;
|
|
|
|
reg &= ~(TCPC_REG_CONFIG_STD_OUTPUT_MUX_MASK |
|
|
TCPC_REG_CONFIG_STD_OUTPUT_CONNECTOR_FLIPPED);
|
|
if (mux_state & MUX_USB_ENABLED)
|
|
reg |= TCPC_REG_CONFIG_STD_OUTPUT_MUX_USB;
|
|
if (mux_state & MUX_DP_ENABLED)
|
|
reg |= TCPC_REG_CONFIG_STD_OUTPUT_MUX_DP;
|
|
if (mux_state & MUX_POLARITY_INVERTED)
|
|
reg |= TCPC_REG_CONFIG_STD_OUTPUT_CONNECTOR_FLIPPED;
|
|
|
|
return tcpc_write(port, TCPC_REG_CONFIG_STD_OUTPUT, reg);
|
|
}
|
|
|
|
/* Reads control register and updates mux_state accordingly */
|
|
int tcpci_tcpm_mux_get(int i2c_addr, mux_state_t *mux_state)
|
|
{
|
|
int reg = 0;
|
|
int rv;
|
|
int port = i2c_addr; /* use port index in port_addr field */
|
|
|
|
*mux_state = 0;
|
|
rv = tcpc_read(port, TCPC_REG_CONFIG_STD_OUTPUT, ®);
|
|
if (rv != EC_SUCCESS)
|
|
return rv;
|
|
|
|
if (reg & TCPC_REG_CONFIG_STD_OUTPUT_MUX_USB)
|
|
*mux_state |= MUX_USB_ENABLED;
|
|
if (reg & TCPC_REG_CONFIG_STD_OUTPUT_MUX_DP)
|
|
*mux_state |= MUX_DP_ENABLED;
|
|
if (reg & TCPC_REG_CONFIG_STD_OUTPUT_CONNECTOR_FLIPPED)
|
|
*mux_state |= MUX_POLARITY_INVERTED;
|
|
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
|
|
const struct usb_mux_driver tcpci_tcpm_usb_mux_driver = {
|
|
.init = tcpci_tcpm_mux_init,
|
|
.set = tcpci_tcpm_mux_set,
|
|
.get = tcpci_tcpm_mux_get,
|
|
};
|
|
|
|
#endif /* CONFIG_USB_PD_TCPM_MUX */
|
|
|
|
const struct tcpm_drv tcpci_tcpm_drv = {
|
|
.init = &tcpci_tcpm_init,
|
|
.get_cc = &tcpci_tcpm_get_cc,
|
|
#ifdef CONFIG_USB_PD_VBUS_DETECT_TCPC
|
|
.get_vbus_level = &tcpci_tcpm_get_vbus_level,
|
|
#endif
|
|
.select_rp_value = &tcpci_tcpm_select_rp_value,
|
|
.set_cc = &tcpci_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 = &tcpci_tcpm_get_message,
|
|
.transmit = &tcpci_tcpm_transmit,
|
|
.tcpc_alert = &tcpci_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_tcpc_drp_toggle,
|
|
#endif
|
|
.get_chip_info = &tcpci_get_chip_info,
|
|
};
|