blob: 4d9a25d43bf6236de4d79841905c3b19c45687b2 [file] [log] [blame]
/* Copyright 2017 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.
*/
/* TI SN5S330 USB-C Power Path Controller */
/*
* PP1 : Sourcing power path.
* PP2 : Sinking power path.
*/
#include "common.h"
#include "console.h"
#include "driver/ppc/sn5s330.h"
#include "hooks.h"
#include "i2c.h"
#include "system.h"
#include "timer.h"
#include "usb_charge.h"
#include "usb_pd_tcpm.h"
#include "usb_pd.h"
#include "usbc_ppc.h"
#include "util.h"
#define CPRINTF(format, args...) cprintf(CC_USBPD, format, ## args)
#define CPRINTS(format, args...) cprints(CC_USBPD, format, ## args)
static uint32_t irq_pending; /* Bitmask of ports signaling an interrupt. */
static int source_enabled[CONFIG_USB_PD_PORT_MAX_COUNT];
static int read_reg(uint8_t port, int reg, int *regval)
{
return i2c_read8(ppc_chips[port].i2c_port,
ppc_chips[port].i2c_addr_flags,
reg,
regval);
}
static int write_reg(uint8_t port, int reg, int regval)
{
return i2c_write8(ppc_chips[port].i2c_port,
ppc_chips[port].i2c_addr_flags,
reg,
regval);
}
static int set_flags(const int port, const int addr, const int flags_to_set)
{
int val, rv;
rv = read_reg(port, addr, &val);
if (rv)
return rv;
val |= flags_to_set;
return write_reg(port, addr, val);
}
static int clr_flags(const int port, const int addr, const int flags_to_clear)
{
int val, rv;
rv = read_reg(port, addr, &val);
if (rv)
return rv;
val &= ~flags_to_clear;
return write_reg(port, addr, val);
}
#ifdef CONFIG_CMD_PPC_DUMP
static int sn5s330_dump(int port)
{
int i;
int data;
const int i2c_port = ppc_chips[port].i2c_port;
const uint16_t i2c_addr_flags = ppc_chips[port].i2c_addr_flags;
/* Flush after every set otherwise console buffer may get full. */
for (i = SN5S330_FUNC_SET1; i <= SN5S330_FUNC_SET12; i++) {
i2c_read8(i2c_port, i2c_addr_flags, i, &data);
ccprintf("FUNC_SET%d [%02Xh] = 0x%02x\n",
i - SN5S330_FUNC_SET1 + 1,
i,
data);
}
cflush();
for (i = SN5S330_INT_STATUS_REG1; i <= SN5S330_INT_STATUS_REG4; i++) {
i2c_read8(i2c_port, i2c_addr_flags, i, &data);
ccprintf("INT_STATUS_REG%d [%02Xh] = 0x%02x\n",
i - SN5S330_INT_STATUS_REG1 + 1,
i,
data);
}
cflush();
for (i = SN5S330_INT_TRIP_RISE_REG1; i <= SN5S330_INT_TRIP_RISE_REG3;
i++) {
i2c_read8(i2c_port, i2c_addr_flags, i, &data);
ccprintf("INT_TRIP_RISE_REG%d [%02Xh] = 0x%02x\n",
i - SN5S330_INT_TRIP_RISE_REG1 + 1,
i,
data);
}
cflush();
for (i = SN5S330_INT_TRIP_FALL_REG1; i <= SN5S330_INT_TRIP_FALL_REG3;
i++) {
i2c_read8(i2c_port, i2c_addr_flags, i, &data);
ccprintf("INT_TRIP_FALL_REG%d [%02Xh] = 0x%02x\n",
i - SN5S330_INT_TRIP_FALL_REG1 + 1,
i,
data);
}
cflush();
for (i = SN5S330_INT_MASK_RISE_REG1; i <= SN5S330_INT_MASK_RISE_REG3;
i++) {
i2c_read8(i2c_port, i2c_addr_flags, i, &data);
ccprintf("INT_MASK_RISE_REG%d [%02Xh] = 0x%02x\n",
i - SN5S330_INT_MASK_RISE_REG1 + 1,
i,
data);
}
cflush();
for (i = SN5S330_INT_MASK_FALL_REG1; i <= SN5S330_INT_MASK_FALL_REG3;
i++) {
i2c_read8(i2c_port, i2c_addr_flags, i, &data);
ccprintf("INT_MASK_FALL_REG%d [%02Xh] = 0x%02x\n",
i - SN5S330_INT_MASK_FALL_REG1 + 1,
i,
data);
}
cflush();
return EC_SUCCESS;
}
#endif /* defined(CONFIG_CMD_PPC_DUMP) */
static int sn5s330_pp_fet_enable(uint8_t port, enum sn5s330_pp_idx pp,
int enable)
{
int status;
int pp_bit;
if (pp == SN5S330_PP1)
pp_bit = SN5S330_PP1_EN;
else if (pp == SN5S330_PP2)
pp_bit = SN5S330_PP2_EN;
else
return EC_ERROR_INVAL;
status = enable ? set_flags(port, SN5S330_FUNC_SET3, pp_bit)
: clr_flags(port, SN5S330_FUNC_SET3, pp_bit);
if (status) {
ppc_prints("Failed to set FUNC_SET3!", port);
return status;
}
if (pp == SN5S330_PP1)
source_enabled[port] = enable;
return EC_SUCCESS;
}
static int sn5s330_init(int port)
{
int regval;
int status;
int retries;
int reg;
const int i2c_port = ppc_chips[port].i2c_port;
const uint16_t i2c_addr_flags = ppc_chips[port].i2c_addr_flags;
#ifdef CONFIG_USB_PD_MAX_SINGLE_SOURCE_CURRENT
/* Set the sourcing current limit value. */
switch (CONFIG_USB_PD_MAX_SINGLE_SOURCE_CURRENT) {
case TYPEC_RP_3A0:
/* Set current limit to ~3A. */
regval = SN5S330_ILIM_3_06;
break;
case TYPEC_RP_1A5:
default:
/* Set current limit to ~1.5A. */
regval = SN5S330_ILIM_1_62;
break;
}
#else /* !defined(CONFIG_USB_PD_MAX_SINGLE_SOURCE_CURRENT) */
/* Default SRC current limit to ~1.5A. */
regval = SN5S330_ILIM_1_62;
#endif /* defined(CONFIG_USB_PD_MAX_SINGLE_SOURCE_CURRENT) */
/*
* It seems that sometimes setting the FUNC_SET1 register fails
* initially. Therefore, we'll retry a couple of times.
*/
retries = 0;
do {
status = i2c_write8(i2c_port, i2c_addr_flags,
SN5S330_FUNC_SET1, regval);
if (status) {
ppc_prints("Failed to set FUNC_SET1! Retrying..",
port);
retries++;
msleep(1);
} else {
break;
}
} while (retries < 10);
/* Set Vbus OVP threshold to ~22.325V. */
regval = 0x37;
status = i2c_write8(i2c_port, i2c_addr_flags,
SN5S330_FUNC_SET5, regval);
if (status) {
ppc_prints("Failed to set FUNC_SET5!", port);
return status;
}
/* Set Vbus UVP threshold to ~2.75V. */
status = i2c_read8(i2c_port, i2c_addr_flags,
SN5S330_FUNC_SET6, &regval);
if (status) {
ppc_prints("Failed to read FUNC_SET6!", port);
return status;
}
regval &= ~0x3F;
regval |= 1;
status = i2c_write8(i2c_port, i2c_addr_flags,
SN5S330_FUNC_SET6, regval);
if (status) {
ppc_prints("Failed to write FUNC_SET6!", port);
return status;
}
/* Enable SBU Fets and set PP2 current limit to ~3A. */
regval = SN5S330_SBU_EN | 0x8;
status = i2c_write8(i2c_port, i2c_addr_flags,
SN5S330_FUNC_SET2, regval);
if (status) {
ppc_prints("Failed to set FUNC_SET2!", port);
return status;
}
/*
* Indicate we are using PP2 configuration 2 and enable OVP comparator
* for CC lines.
*
* Also, turn off under-voltage protection for incoming Vbus as it would
* prevent us from enabling SNK path before we hibernate the ec. We
* need to enable the SNK path so USB power will assert ACOK and wake
* the EC up went inserting USB power. We always turn off under-voltage
* protection because the battery charger will boost the voltage up
* to the needed battery voltage either way (and it will have its own
* low voltage protection).
*/
regval = SN5S330_OVP_EN_CC | SN5S330_PP2_CONFIG | SN5S330_CONFIG_UVP;
status = i2c_write8(i2c_port, i2c_addr_flags,
SN5S330_FUNC_SET9, regval);
if (status) {
ppc_prints("Failed to set FUNC_SET9!", port);
return status;
}
/*
* Set analog current limit delay to 200 us for PP1,
* set 1000 us for PP2 for compatibility.
*/
regval = (PPX_ILIM_DEGLITCH_0_US_200 << 3) |
PPX_ILIM_DEGLITCH_0_US_1000;
status = i2c_write8(i2c_port, i2c_addr_flags, SN5S330_FUNC_SET11,
regval);
if (status) {
ppc_prints("Failed to set FUNC_SET11", port);
return status;
}
#ifdef CONFIG_USBC_PPC_VCONN
/*
* Set the deglitch timeout on the Vconn current limit to 640us. This
* improves compatibility with some USB C -> HDMI devices versus the
* reset default (20 us).
*/
regval = 0;
status = i2c_read8(i2c_port, i2c_addr_flags,
SN5S330_FUNC_SET8, &regval);
if (status) {
ppc_prints("Failed to read FUNC_SET8!", port);
return status;
}
regval &= ~SN5S330_VCONN_DEGLITCH_MASK;
regval |= SN5S330_VCONN_DEGLITCH_640_US;
status = i2c_write8(i2c_port, i2c_addr_flags,
SN5S330_FUNC_SET8, regval);
if (status) {
ppc_prints("Failed to set FUNC_SET8!", port);
return status;
}
#endif /* CONFIG_USBC_PPC_VCONN */
/*
* Turn off dead battery resistors, turn on CC FETs, and set the higher
* of the two VCONN current limits (min 0.6A). Many VCONN accessories
* trip the default current limit of min 0.35A.
*/
status = set_flags(port, SN5S330_FUNC_SET4,
SN5S330_CC_EN | SN5S330_VCONN_ILIM_SEL);
if (status) {
ppc_prints("Failed to set FUNC_SET4!", port);
return status;
}
/* Set ideal diode mode for both PP1 and PP2. */
status = set_flags(port, SN5S330_FUNC_SET3,
SN5S330_SET_RCP_MODE_PP1 | SN5S330_SET_RCP_MODE_PP2);
if (status) {
ppc_prints("Failed to set FUNC_SET3!", port);
return status;
}
/* Turn off PP1 FET. */
status = sn5s330_pp_fet_enable(port, SN5S330_PP1, 0);
if (status) {
ppc_prints("Failed to turn off PP1 FET!", port);
}
/*
* Don't proceed with the rest of initialization if we're sysjumping.
* We would have already done this before.
*/
if (system_jumped_late())
return EC_SUCCESS;
/*
* Clear the digital reset bit, and mask off and clear vSafe0V
* interrupts. Leave the dead battery mode bit unchanged since it
* is checked below.
*/
regval = SN5S330_DIG_RES | SN5S330_VSAFE0V_MASK;
status = i2c_write8(i2c_port, i2c_addr_flags,
SN5S330_INT_STATUS_REG4, regval);
if (status) {
ppc_prints("Failed to write INT_STATUS_REG4!", port);
return status;
}
/*
* Before turning on the PP2 FET, mask off all unwanted interrupts and
* then clear all pending interrupts.
*
* TODO(aaboagye): Unmask fast-role swap events once fast-role swap is
* implemented in the PD stack.
*/
/* Enable PP1 overcurrent interrupts. */
regval = ~SN5S330_ILIM_PP1_MASK;
status = i2c_write8(i2c_port, i2c_addr_flags,
SN5S330_INT_MASK_RISE_REG1, regval);
if (status) {
ppc_prints("Failed to write INT_MASK_RISE1!", port);
return status;
}
status = i2c_write8(i2c_port, i2c_addr_flags,
SN5S330_INT_MASK_FALL_REG1, 0xFF);
if (status) {
ppc_prints("Failed to write INT_MASK_FALL1!", port);
return status;
}
/* Enable VCONN overcurrent and CC1/CC2 overvoltage interrupts. */
regval = ~(SN5S330_VCONN_ILIM | SN5S330_CC1_CON | SN5S330_CC2_CON);
status = i2c_write8(i2c_port, i2c_addr_flags,
SN5S330_INT_MASK_RISE_REG2, regval);
if (status) {
ppc_prints("Failed to write INT_MASK_RISE2!", port);
return status;
}
status = i2c_write8(i2c_port, i2c_addr_flags,
SN5S330_INT_MASK_FALL_REG2, 0xFF);
if (status) {
ppc_prints("Failed to write INT_MASK_FALL2!", port);
return status;
}
#if defined(CONFIG_USB_PD_VBUS_DETECT_PPC) && defined(CONFIG_USB_CHARGER)
/* If PPC is being used to detect VBUS, enable VBUS interrupts. */
regval = ~SN5S330_VBUS_GOOD_MASK;
#else
regval = 0xFF;
#endif /* CONFIG_USB_PD_VBUS_DETECT_PPC && CONFIG_USB_CHARGER */
status = i2c_write8(i2c_port, i2c_addr_flags,
SN5S330_INT_MASK_RISE_REG3, regval);
if (status) {
ppc_prints("Failed to write INT_MASK_RISE3!", port);
return status;
}
status = i2c_write8(i2c_port, i2c_addr_flags,
SN5S330_INT_MASK_FALL_REG3, regval);
if (status) {
ppc_prints("Failed to write INT_MASK_FALL3!", port);
return status;
}
/* Now clear any pending interrupts. */
for (reg = SN5S330_INT_TRIP_RISE_REG1;
reg <= SN5S330_INT_TRIP_FALL_REG3;
reg++) {
status = i2c_write8(i2c_port, i2c_addr_flags,
reg, 0xFF);
if (status) {
CPRINTS("ppc p%d: Failed to write reg 0x%2x!",
port, reg);
return status;
}
}
/*
* For PP2, check to see if we booted in dead battery mode. If we
* booted in dead battery mode, the PP2 FET will already be enabled.
*/
status = i2c_read8(i2c_port, i2c_addr_flags,
SN5S330_INT_STATUS_REG4, &regval);
if (status) {
ppc_prints("Failed to read INT_STATUS_REG4!", port);
return status;
}
if (regval & SN5S330_DB_BOOT) {
/*
* Clear the bit by writing 1 and keep vSafe0V_MASK
* unchanged.
*/
i2c_write8(i2c_port, i2c_addr_flags,
SN5S330_INT_STATUS_REG4, regval);
/* Turn on PP2 FET. */
status = sn5s330_pp_fet_enable(port, SN5S330_PP2, 1);
if (status) {
ppc_prints("Failed to turn on PP2 FET!", port);
return status;
}
}
return EC_SUCCESS;
}
#ifdef CONFIG_USB_PD_VBUS_DETECT_PPC
static int sn5s330_is_vbus_present(int port)
{
int regval;
int rv;
rv = read_reg(port, SN5S330_INT_STATUS_REG3, &regval);
if (rv) {
ppc_err_prints("VBUS present error", port, rv);
return 0;
}
return !!(regval & SN5S330_VBUS_GOOD);
}
#endif /* defined(CONFIG_USB_PD_VBUS_DETECT_PPC) */
static int sn5s330_is_sourcing_vbus(int port)
{
return source_enabled[port];
}
#ifdef CONFIG_USBC_PPC_POLARITY
static int sn5s330_set_polarity(int port, int polarity)
{
if (polarity)
/* CC2 active. */
return set_flags(port, SN5S330_FUNC_SET4, SN5S330_CC_POLARITY);
else
/* CC1 active. */
return clr_flags(port, SN5S330_FUNC_SET4, SN5S330_CC_POLARITY);
}
#endif
static int sn5s330_set_vbus_source_current_limit(int port,
enum tcpc_rp_value rp)
{
int regval;
int status;
status = read_reg(port, SN5S330_FUNC_SET1, &regval);
if (status)
return status;
/*
* Note that we chose the lowest current limit setting that is just
* above indicated Rp value. This is because these are minimum values
* and we must be able to provide the current that we advertise.
*/
regval &= ~0x1F; /* The current limit settings are 4:0. */
switch (rp) {
case TYPEC_RP_3A0:
regval |= SN5S330_ILIM_3_06;
break;
case TYPEC_RP_1A5:
regval |= SN5S330_ILIM_1_62;
break;
case TYPEC_RP_USB:
default:
regval |= SN5S330_ILIM_0_63;
break;
};
status = write_reg(port, SN5S330_FUNC_SET1, regval);
return status;
}
static int sn5s330_discharge_vbus(int port, int enable)
{
int status = enable ? set_flags(port, SN5S330_FUNC_SET3,
SN5S330_VBUS_DISCH_EN)
: clr_flags(port, SN5S330_FUNC_SET3,
SN5S330_VBUS_DISCH_EN);
if (status) {
CPRINTS("ppc p%d: Failed to %s vbus discharge",
port, enable ? "enable" : "disable");
return status;
}
return EC_SUCCESS;
}
static int sn5s330_enter_low_power_mode(int port)
{
int rv;
/* Turn off both SRC and SNK FETs */
rv = clr_flags(port, SN5S330_FUNC_SET3,
SN5S330_PP1_EN | SN5S330_PP2_EN);
if (rv) {
ppc_err_prints("Could not disable both FETS", port, rv);
return rv;
}
/* Turn off Vconn power */
rv = clr_flags(port, SN5S330_FUNC_SET4, SN5S330_VCONN_EN);
if (rv) {
ppc_err_prints("Could not disable Vconn", port, rv);
return rv;
}
/* Turn off SBU path */
rv = clr_flags(port, SN5S330_FUNC_SET2, SN5S330_SBU_EN);
if (rv) {
ppc_err_prints("Could not disable SBU path", port, rv);
return rv;
}
/*
* Turn off the Over Voltage Protection circuits. Needs to happen after
* FETs are disabled, otherwise OVP can automatically turned back on.
* Since FETs are off, any over voltage does not make it to the board
* side of the PPC.
*/
rv = clr_flags(port, SN5S330_FUNC_SET9,
SN5S330_FORCE_OVP_EN_SBU | SN5S330_FORCE_ON_VBUS_OVP |
SN5S330_FORCE_ON_VBUS_UVP);
if (rv) {
ppc_err_prints("Could not disable OVP circuit", port, rv);
return rv;
}
return EC_SUCCESS;
}
#ifdef CONFIG_USBC_PPC_VCONN
static int sn5s330_set_vconn(int port, int enable)
{
int regval;
int status;
status = read_reg(port, SN5S330_FUNC_SET4, &regval);
if (status)
return status;
if (enable)
regval |= SN5S330_VCONN_EN;
else
regval &= ~SN5S330_VCONN_EN;
return write_reg(port, SN5S330_FUNC_SET4, regval);
}
#endif
static int sn5s330_vbus_sink_enable(int port, int enable)
{
return sn5s330_pp_fet_enable(port, SN5S330_PP2, !!enable);
}
static int sn5s330_vbus_source_enable(int port, int enable)
{
return sn5s330_pp_fet_enable(port, SN5S330_PP1, !!enable);
}
#ifdef CONFIG_USBC_PPC_SBU
static int sn5s330_set_sbu(int port, int enable)
{
int rv;
if (enable)
rv = set_flags(port, SN5S330_FUNC_SET2, SN5S330_SBU_EN);
else
rv = clr_flags(port, SN5S330_FUNC_SET2, SN5S330_SBU_EN);
return rv;
}
#endif /* CONFIG_USBC_PPC_SBU */
static void sn5s330_handle_interrupt(int port)
{
int attempt = 0;
/*
* SN5S330's /INT pin is level, so process interrupts until it
* deasserts if the chip has a dedicated interrupt pin.
*/
#ifdef CONFIG_USBC_PPC_DEDICATED_INT
while (ppc_get_alert_status(port))
#endif
{
int rise = 0;
int fall = 0;
attempt++;
if (attempt > 1)
ppc_prints("Could not clear interrupts on first "
"try, retrying", port);
read_reg(port, SN5S330_INT_TRIP_RISE_REG1, &rise);
read_reg(port, SN5S330_INT_TRIP_FALL_REG1, &fall);
/* Notify the system about the overcurrent event. */
if (rise & SN5S330_ILIM_PP1_MASK)
pd_handle_overcurrent(port);
/* Clear the interrupt sources. */
write_reg(port, SN5S330_INT_TRIP_RISE_REG1, rise);
write_reg(port, SN5S330_INT_TRIP_FALL_REG1, fall);
read_reg(port, SN5S330_INT_TRIP_RISE_REG2, &rise);
read_reg(port, SN5S330_INT_TRIP_FALL_REG2, &fall);
/*
* VCONN may be latched off due to an overcurrent. Indicate
* when the VCONN overcurrent happens.
*/
if (rise & SN5S330_VCONN_ILIM)
ppc_prints("VCONN OC!", port);
/* Notify the system about the CC overvoltage event. */
if (rise & SN5S330_CC1_CON || rise & SN5S330_CC2_CON) {
ppc_prints("CC OV!", port);
pd_handle_cc_overvoltage(port);
}
/* Clear the interrupt sources. */
write_reg(port, SN5S330_INT_TRIP_RISE_REG2, rise);
write_reg(port, SN5S330_INT_TRIP_FALL_REG2, fall);
#if defined(CONFIG_USB_PD_VBUS_DETECT_PPC) && defined(CONFIG_USB_CHARGER)
read_reg(port, SN5S330_INT_TRIP_RISE_REG3, &rise);
read_reg(port, SN5S330_INT_TRIP_FALL_REG3, &fall);
/* Inform other modules about VBUS level */
if (rise & SN5S330_VBUS_GOOD_MASK
|| fall & SN5S330_VBUS_GOOD_MASK)
usb_charger_vbus_change(port,
sn5s330_is_vbus_present(port));
/* Clear the interrupt sources. */
write_reg(port, SN5S330_INT_TRIP_RISE_REG3, rise);
write_reg(port, SN5S330_INT_TRIP_FALL_REG3, fall);
#endif /* CONFIG_USB_PD_VBUS_DETECT_PPC && CONFIG_USB_CHARGER */
}
}
static void sn5s330_irq_deferred(void)
{
int i;
uint32_t pending = atomic_clear(&irq_pending);
for (i = 0; i < board_get_usb_pd_port_count(); i++)
if (BIT(i) & pending)
sn5s330_handle_interrupt(i);
}
DECLARE_DEFERRED(sn5s330_irq_deferred);
void sn5s330_interrupt(int port)
{
atomic_or(&irq_pending, BIT(port));
hook_call_deferred(&sn5s330_irq_deferred_data, 0);
}
const struct ppc_drv sn5s330_drv = {
.init = &sn5s330_init,
.is_sourcing_vbus = &sn5s330_is_sourcing_vbus,
.vbus_sink_enable = &sn5s330_vbus_sink_enable,
.vbus_source_enable = &sn5s330_vbus_source_enable,
.set_vbus_source_current_limit = &sn5s330_set_vbus_source_current_limit,
.discharge_vbus = &sn5s330_discharge_vbus,
.enter_low_power_mode = &sn5s330_enter_low_power_mode,
#ifdef CONFIG_CMD_PPC_DUMP
.reg_dump = &sn5s330_dump,
#endif
#ifdef CONFIG_USB_PD_VBUS_DETECT_PPC
.is_vbus_present = &sn5s330_is_vbus_present,
#endif
#ifdef CONFIG_USBC_PPC_POLARITY
.set_polarity = &sn5s330_set_polarity,
#endif
#ifdef CONFIG_USBC_PPC_SBU
.set_sbu = &sn5s330_set_sbu,
#endif /* defined(CONFIG_USBC_PPC_SBU) */
#ifdef CONFIG_USBC_PPC_VCONN
.set_vconn = &sn5s330_set_vconn,
#endif
};