214 lines
5.3 KiB
C
214 lines
5.3 KiB
C
/* Copyright 2016 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.
|
|
*/
|
|
|
|
/* ANX7688 port manager */
|
|
|
|
#include "hooks.h"
|
|
#include "tcpci.h"
|
|
#include "tcpm.h"
|
|
#include "timer.h"
|
|
#include "usb_mux.h"
|
|
|
|
#if defined(CONFIG_USB_PD_DUAL_ROLE_AUTO_TOGGLE) || \
|
|
defined(CONFIG_USB_PD_TCPC_LOW_POWER) || \
|
|
defined(CONFIG_USB_PD_DISCHARGE_TCPC)
|
|
#error "Unsupported config options of anx7688 PD driver"
|
|
#endif
|
|
|
|
#define ANX7688_VENDOR_ALERT BIT(15)
|
|
|
|
#define ANX7688_REG_STATUS 0x82
|
|
#define ANX7688_REG_STATUS_LINK BIT(0)
|
|
|
|
#define ANX7688_REG_HPD 0x83
|
|
#define ANX7688_REG_HPD_HIGH BIT(0)
|
|
#define ANX7688_REG_HPD_IRQ BIT(1)
|
|
#define ANX7688_REG_HPD_ENABLE BIT(2)
|
|
|
|
#define ANX7688_USBC_ADDR_FLAGS 0x28
|
|
#define ANX7688_REG_RAMCTRL 0xe7
|
|
#define ANX7688_REG_RAMCTRL_BOOT_DONE BIT(6)
|
|
|
|
static int anx7688_init(int port)
|
|
{
|
|
int rv = 0;
|
|
int mask = 0;
|
|
|
|
/*
|
|
* 7688 POWER_STATUS[6] is not reliable for tcpci_tcpm_init() to poll
|
|
* due to it is default 0 in HW, and we cannot write TCPC until it is
|
|
* ready, or something goes wrong. (Issue 52772)
|
|
* Instead we poll TCPC 0x50:0xe7 bit6 here to make sure bootdone is
|
|
* ready(50ms). Then PD main flow can process cc debounce in 50ms ~
|
|
* 100ms to follow cts.
|
|
*/
|
|
while (1) {
|
|
rv = i2c_read8(I2C_PORT_TCPC, ANX7688_USBC_ADDR_FLAGS,
|
|
ANX7688_REG_RAMCTRL, &mask);
|
|
|
|
if (rv == EC_SUCCESS && (mask & ANX7688_REG_RAMCTRL_BOOT_DONE))
|
|
break;
|
|
msleep(10);
|
|
}
|
|
|
|
rv = tcpci_tcpm_drv.init(port);
|
|
if (rv)
|
|
return rv;
|
|
|
|
rv = tcpc_read16(port, TCPC_REG_ALERT_MASK, &mask);
|
|
if (rv)
|
|
return rv;
|
|
|
|
/* enable vendor specific alert */
|
|
mask |= ANX7688_VENDOR_ALERT;
|
|
rv = tcpc_write16(port, TCPC_REG_ALERT_MASK, mask);
|
|
return rv;
|
|
}
|
|
|
|
static int anx7688_release(int port)
|
|
{
|
|
return EC_ERROR_UNIMPLEMENTED;
|
|
}
|
|
|
|
static void anx7688_update_hpd_enable(int port)
|
|
{
|
|
int status, reg, rv;
|
|
|
|
rv = tcpc_read(port, ANX7688_REG_STATUS, &status);
|
|
rv |= tcpc_read(port, ANX7688_REG_HPD, ®);
|
|
if (rv)
|
|
return;
|
|
|
|
if (!(reg & ANX7688_REG_HPD_ENABLE) ||
|
|
!(status & ANX7688_REG_STATUS_LINK)) {
|
|
reg &= ~ANX7688_REG_HPD_IRQ;
|
|
tcpc_write(port, ANX7688_REG_HPD,
|
|
(status & ANX7688_REG_STATUS_LINK)
|
|
? reg | ANX7688_REG_HPD_ENABLE
|
|
: reg & ~ANX7688_REG_HPD_ENABLE);
|
|
}
|
|
}
|
|
|
|
int anx7688_hpd_disable(int port)
|
|
{
|
|
return tcpc_write(port, ANX7688_REG_HPD, 0);
|
|
}
|
|
|
|
int anx7688_update_hpd(int port, int level, int irq)
|
|
{
|
|
int reg, rv;
|
|
|
|
rv = tcpc_read(port, ANX7688_REG_HPD, ®);
|
|
if (rv)
|
|
return rv;
|
|
|
|
if (level)
|
|
reg |= ANX7688_REG_HPD_HIGH;
|
|
else
|
|
reg &= ~ANX7688_REG_HPD_HIGH;
|
|
|
|
if (irq)
|
|
reg |= ANX7688_REG_HPD_IRQ;
|
|
else
|
|
reg &= ~ANX7688_REG_HPD_IRQ;
|
|
|
|
return tcpc_write(port, ANX7688_REG_HPD, reg);
|
|
}
|
|
|
|
int anx7688_enable_cable_detection(int port)
|
|
{
|
|
return tcpc_write(port, TCPC_REG_COMMAND, 0xff);
|
|
}
|
|
|
|
int anx7688_set_power_supply_ready(int port)
|
|
{
|
|
return tcpc_write(port, TCPC_REG_COMMAND, 0x77);
|
|
}
|
|
|
|
int anx7688_power_supply_reset(int port)
|
|
{
|
|
return tcpc_write(port, TCPC_REG_COMMAND, 0x66);
|
|
}
|
|
|
|
static void anx7688_tcpc_alert(int port)
|
|
{
|
|
int alert, rv;
|
|
|
|
rv = tcpc_read16(port, TCPC_REG_ALERT, &alert);
|
|
/* process and clear alert status */
|
|
tcpci_tcpc_alert(port);
|
|
|
|
if (!rv && (alert & ANX7688_VENDOR_ALERT))
|
|
anx7688_update_hpd_enable(port);
|
|
}
|
|
|
|
static int anx7688_mux_set(int port, mux_state_t mux_state)
|
|
{
|
|
int reg = 0;
|
|
int rv, polarity;
|
|
|
|
rv = mux_read(port, TCPC_REG_CONFIG_STD_OUTPUT, ®);
|
|
if (rv != EC_SUCCESS)
|
|
return rv;
|
|
|
|
reg &= ~TCPC_REG_CONFIG_STD_OUTPUT_MUX_MASK;
|
|
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;
|
|
|
|
/* ANX7688 needs to set bit0 */
|
|
rv = mux_read(port, TCPC_REG_TCPC_CTRL, &polarity);
|
|
if (rv != EC_SUCCESS)
|
|
return rv;
|
|
|
|
/* copy the polarity from TCPC_CTRL[0], take care clear then set */
|
|
reg &= ~TCPC_REG_TCPC_CTRL_POLARITY(1);
|
|
reg |= TCPC_REG_TCPC_CTRL_POLARITY(polarity);
|
|
return mux_write(port, TCPC_REG_CONFIG_STD_OUTPUT, reg);
|
|
}
|
|
|
|
#ifdef CONFIG_USB_PD_VBUS_DETECT_TCPC
|
|
static int anx7688_tcpm_get_vbus_level(int port)
|
|
{
|
|
int reg = 0;
|
|
|
|
/* On ANX7688, POWER_STATUS.VBusPresent (bit 2) is averaged 16 times, so
|
|
* its value may not be set to 1 quickly enough during power role swap.
|
|
* Therefore, we use a proprietary register to read the unfiltered VBus
|
|
* value. See crosbug.com/p/55221 .
|
|
*/
|
|
i2c_read8(I2C_PORT_TCPC, 0x28, 0x40, ®);
|
|
return ((reg & 0x10) ? 1 : 0);
|
|
}
|
|
#endif
|
|
|
|
/* ANX7688 is a TCPCI compatible port controller */
|
|
const struct tcpm_drv anx7688_tcpm_drv = {
|
|
.init = &anx7688_init,
|
|
.release = &anx7688_release,
|
|
.get_cc = &tcpci_tcpm_get_cc,
|
|
#ifdef CONFIG_USB_PD_VBUS_DETECT_TCPC
|
|
.get_vbus_level = &anx7688_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_raw = &tcpci_tcpm_get_message_raw,
|
|
.transmit = &tcpci_tcpm_transmit,
|
|
.tcpc_alert = &anx7688_tcpc_alert,
|
|
};
|
|
|
|
#ifdef CONFIG_USB_PD_TCPM_MUX
|
|
const struct usb_mux_driver anx7688_usb_mux_driver = {
|
|
.init = tcpci_tcpm_mux_init,
|
|
.set = anx7688_mux_set,
|
|
.get = tcpci_tcpm_mux_get,
|
|
};
|
|
#endif /* CONFIG_USB_PD_TCPM_MUX */
|