coreboot-libre-fam15h-rdimm/3rdparty/chromeec/driver/ppc/syv682x.c

352 lines
8.6 KiB
C

/* Copyright 2018 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.
*/
/* Silergy SYV682x USB-C Power Path Controller */
#include "common.h"
#include "console.h"
#include "driver/ppc/syv682x.h"
#include "i2c.h"
#include "usb_charge.h"
#include "usb_pd_tcpm.h"
#include "usbc_ppc.h"
#include "util.h"
#define SYV682X_FLAGS_SOURCE_ENABLED BIT(0)
/* 0 -> CC1, 1 -> CC2 */
#define SYV682X_FLAGS_CC_POLARITY BIT(1)
#define SYV682X_FLAGS_VBUS_PRESENT BIT(2)
static uint8_t flags[CONFIG_USB_PD_PORT_COUNT];
#define SYV682X_VBUS_DET_THRESH_MV 4000
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 syv682x_is_sourcing_vbus(int port)
{
return flags[port] & SYV682X_FLAGS_SOURCE_ENABLED;
}
static int syv682x_vbus_sink_enable(int port, int enable)
{
int regval;
int rv;
/*
* For sink mode need to make sure high voltage power path is connected
* and sink mode is selected.
*/
rv = read_reg(port, SYV682X_CONTROL_1_REG, &regval);
if (rv)
return rv;
if (enable) {
/* Select high voltage path */
regval |= SYV682X_CONTROL_1_CH_SEL;
/* Select Sink mode and turn on the channel */
regval &= ~(SYV682X_CONTROL_1_HV_DR |
SYV682X_CONTROL_1_PWR_ENB);
} else {
/*
* No need to change the voltage path or channel direction. But,
* turn both paths off.
*/
regval |= SYV682X_CONTROL_1_PWR_ENB;
}
return write_reg(port, SYV682X_CONTROL_1_REG, regval);
}
#ifdef CONFIG_USB_PD_VBUS_DETECT_PPC
static int syv682x_is_vbus_present(int port)
{
int val;
int vbus = 0;
/*
* TODO (b/112661747): This PPC doesn't fully support VBUS detection.
* It can detect both VSafe5V and VSafe0V. This function is intended
* here until detecting VBUS differently per channel is supported.
*/
if (read_reg(port, SYV682X_STATUS_REG, &val))
return vbus;
/*
* VBUS is considered present if VSafe5V is detected or neither VSafe5V
* or VSafe0V is detected, which implies VBUS > 5V.
*/
if ((val & SYV682X_STATUS_VSAFE_5V) ||
!(val & (SYV682X_STATUS_VSAFE_5V | SYV682X_STATUS_VSAFE_0V)))
vbus = 1;
#ifdef CONFIG_USB_CHARGER
if (!!(flags[port] & SYV682X_FLAGS_VBUS_PRESENT) != vbus)
usb_charger_vbus_change(port, vbus);
if (vbus)
flags[port] |= SYV682X_FLAGS_VBUS_PRESENT;
else
flags[port] &= ~SYV682X_FLAGS_VBUS_PRESENT;
#endif
return vbus;
}
#endif
static int syv682x_vbus_source_enable(int port, int enable)
{
int regval;
int rv;
/*
* For source mode need to make sure 5V power path is connected
* and source mode is selected.
*/
rv = read_reg(port, SYV682X_CONTROL_1_REG, &regval);
if (rv)
return rv;
if (enable) {
/* Select 5V path and turn on channel */
regval &= ~(SYV682X_CONTROL_1_CH_SEL |
SYV682X_CONTROL_1_PWR_ENB);
/* Disable HV Sink path */
regval |= SYV682X_CONTROL_1_HV_DR;
} else if (flags[port] & SYV682X_FLAGS_SOURCE_ENABLED) {
/*
* For the disable case, make sure that VBUS was being sourced
* prior to disabling the source path. Because the source/sink
* paths can't be independently disabled, and this function will
* get called as part of USB PD initialization, setting the
* PWR_ENB always can lead to broken dead battery behavior.
*
* No need to change the voltage path or channel direction. But,
* turn both paths off.
*/
regval |= SYV682X_CONTROL_1_PWR_ENB;
}
rv = write_reg(port, SYV682X_CONTROL_1_REG, regval);
if (rv)
return rv;
if (enable)
flags[port] |= SYV682X_FLAGS_SOURCE_ENABLED;
else
flags[port] &= ~SYV682X_FLAGS_SOURCE_ENABLED;
#if defined(CONFIG_USB_CHARGER) && defined(CONFIG_USB_PD_VBUS_DETECT_PPC)
/*
* Since the VBUS state could be changing here, need to wake the
* USB_CHG_N task so that BC 1.2 detection will be triggered.
*/
usb_charger_vbus_change(port, enable);
#endif
return EC_SUCCESS;
}
static int syv682x_set_vbus_source_current_limit(int port,
enum tcpc_rp_value rp)
{
int rv;
int limit;
int regval;
rv = read_reg(port, SYV682X_CONTROL_1_REG, &regval);
if (rv)
return rv;
/* We need buffer room for all current values. */
switch (rp) {
case TYPEC_RP_3A0:
limit = SYV682X_ILIM_3_30;
break;
case TYPEC_RP_1A5:
limit = SYV682X_ILIM_1_75;
break;
case TYPEC_RP_USB:
default:
/* 1.25 A is lowest current limit setting for SVY682 */
limit = SYV682X_ILIM_1_25;
break;
};
regval &= ~SYV682X_ILIM_MASK;
regval |= (limit << SYV682X_ILIM_BIT_SHIFT);
return write_reg(port, SYV682X_CONTROL_1_REG, regval);
}
static int syv682x_discharge_vbus(int port, int enable)
{
int regval;
int rv;
rv = read_reg(port, SYV682X_CONTROL_2_REG, &regval);
if (rv)
return rv;
if (enable)
regval |= SYV682X_CONTROL_2_FDSG;
else
regval &= ~SYV682X_CONTROL_2_FDSG;
return write_reg(port, SYV682X_CONTROL_2_REG, regval);
}
#ifdef CONFIG_USBC_PPC_POLARITY
static int syv682x_set_polarity(int port, int polarity)
{
/*
* The SYV682x does not explicitly set CC polarity. However, if VCONN is
* being used then the polarity is required to connect 5V to the correct
* CC line. So this function saves the CC polarity as a bit in the flags
* variable so VCONN is connected the correct CC line. The flag bit
* being set means polarity = CC2, the flag bit clear means
* polarity = CC1.
*/
if (polarity)
flags[port] |= SYV682X_FLAGS_CC_POLARITY;
else
flags[port] &= ~SYV682X_FLAGS_CC_POLARITY;
return EC_SUCCESS;
}
#endif
#ifdef CONFIG_USBC_PPC_VCONN
static int syv682x_set_vconn(int port, int enable)
{
int regval;
int rv;
rv = read_reg(port, SYV682X_CONTROL_4_REG, &regval);
if (rv)
return rv;
if (enable)
regval |= flags[port] & SYV682X_FLAGS_CC_POLARITY ?
SYV682X_CONTROL_4_VCONN1 : SYV682X_CONTROL_4_VCONN2;
else
regval &= ~(SYV682X_CONTROL_4_VCONN2 |
SYV682X_CONTROL_4_VCONN1);
return write_reg(port, SYV682X_CONTROL_4_REG, regval);
}
#endif
#ifdef CONFIG_CMD_PPC_DUMP
static int syv682x_dump(int port)
{
int reg_addr;
int data;
int rv;
const int i2c_port = ppc_chips[port].i2c_port;
const int i2c_addr_flags = ppc_chips[port].i2c_addr_flags;
for (reg_addr = SYV682X_STATUS_REG; reg_addr <= SYV682X_CONTROL_4_REG;
reg_addr++) {
rv = i2c_read8(i2c_port, i2c_addr_flags, reg_addr, &data);
if (rv)
ccprintf("ppc_syv682[p%d]: Failed to read reg 0x%02x\n",
port, reg_addr);
else
ccprintf("ppc_syv682[p%d]: reg 0x%02x = 0x%02x\n",
port, reg_addr, data);
}
cflush();
return EC_SUCCESS;
}
#endif /* defined(CONFIG_CMD_PPC_DUMP) */
static int syv682x_init(int port)
{
int rv;
int regval;
/* Set VBUS discharge to manual mode */
rv = read_reg(port, SYV682X_CONTROL_2_REG, &regval);
if (rv)
return rv;
regval &= ~SYV682X_CONTROL_2_SDSG;
rv = write_reg(port, SYV682X_CONTROL_2_REG, regval);
if (rv)
return rv;
/* Select max voltage for OVP */
rv = read_reg(port, SYV682X_CONTROL_3_REG, &regval);
if (rv)
return rv;
regval &= ~SYV682X_OVP_MASK;
regval |= (SYV682X_OVP_23_7 << SYV682X_OVP_BIT_SHIFT);
rv = write_reg(port, SYV682X_CONTROL_3_REG, regval);
if (rv)
return rv;
/* Check if this if dead battery case */
rv = read_reg(port, SYV682X_CONTROL_1_REG, &regval);
if (rv)
return rv;
if (regval & SYV682X_STATUS_VSAFE_0V) {
/* Not dead battery case, so disable channel */
regval |= SYV682X_CONTROL_1_PWR_ENB;
rv = write_reg(port, SYV682X_CONTROL_1_REG, regval);
if (rv)
return rv;
} else {
syv682x_vbus_sink_enable(port, 1);
}
rv = read_reg(port, SYV682X_CONTROL_4_REG, &regval);
if (rv)
return rv;
/* Remove Rd and connect CC1/CC2 lines to TCPC */
regval |= SYV682X_CONTROL_4_CC1_BPS | SYV682X_CONTROL_4_CC2_BPS;
/* Disable Fast Role Swap (FRS) */
regval |= SYV682X_CONTROL_4_CC_FRS;
rv = write_reg(port, SYV682X_CONTROL_4_REG, regval);
if (rv)
return rv;
return EC_SUCCESS;
}
const struct ppc_drv syv682x_drv = {
.init = &syv682x_init,
.is_sourcing_vbus = &syv682x_is_sourcing_vbus,
.vbus_sink_enable = &syv682x_vbus_sink_enable,
.vbus_source_enable = &syv682x_vbus_source_enable,
#ifdef CONFIG_CMD_PPC_DUMP
.reg_dump = &syv682x_dump,
#endif /* defined(CONFIG_CMD_PPC_DUMP) */
#ifdef CONFIG_USB_PD_VBUS_DETECT_PPC
.is_vbus_present = &syv682x_is_vbus_present,
#endif /* defined(CONFIG_USB_PD_VBUS_DETECT_PPC) */
.set_vbus_source_current_limit = &syv682x_set_vbus_source_current_limit,
.discharge_vbus = &syv682x_discharge_vbus,
#ifdef CONFIG_USBC_PPC_POLARITY
.set_polarity = &syv682x_set_polarity,
#endif
#ifdef CONFIG_USBC_PPC_VCONN
.set_vconn = &syv682x_set_vconn,
#endif
};