mirror of
https://github.com/Telecominfraproject/OpenCellular.git
synced 2026-01-10 17:41:54 +00:00
BUG=b:72292985 BRANCH=None TEST=Flash meowth; Verify with twinkie that Vconn is provided for a sink that requires it. Change-Id: I8168d2e4c46e04810dcf5c2898b2c337424eefec Signed-off-by: Aseda Aboagye <aaboagye@google.com> Reviewed-on: https://chromium-review.googlesource.com/888224 Commit-Ready: Aseda Aboagye <aaboagye@chromium.org> Tested-by: Aseda Aboagye <aaboagye@chromium.org> Reviewed-by: Edward Hill <ecgh@chromium.org> Reviewed-by: Jett Rink <jettrink@chromium.org>
630 lines
15 KiB
C
630 lines
15 KiB
C
/* 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 "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 read_reg(uint8_t port, int reg, int *regval)
|
|
{
|
|
return i2c_read8(ppc_chips[port].i2c_port,
|
|
ppc_chips[port].i2c_addr,
|
|
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,
|
|
reg,
|
|
regval);
|
|
}
|
|
|
|
#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 int i2c_addr = ppc_chips[port].i2c_addr;
|
|
|
|
for (i = SN5S330_FUNC_SET1; i <= SN5S330_FUNC_SET12; i++) {
|
|
i2c_read8(i2c_port, i2c_addr, i, &data);
|
|
ccprintf("FUNC_SET%d [%02Xh] = 0x%02x\n",
|
|
i - SN5S330_FUNC_SET1 + 1,
|
|
i,
|
|
data);
|
|
}
|
|
|
|
for (i = SN5S330_INT_STATUS_REG1; i <= SN5S330_INT_STATUS_REG4; i++) {
|
|
i2c_read8(i2c_port, i2c_addr, i, &data);
|
|
ccprintf("INT_STATUS_REG%d [%02Xh] = 0x%02x\n",
|
|
i - SN5S330_INT_STATUS_REG1 + 1,
|
|
i,
|
|
data);
|
|
}
|
|
|
|
for (i = SN5S330_INT_TRIP_RISE_REG1; i <= SN5S330_INT_TRIP_RISE_REG3;
|
|
i++) {
|
|
i2c_read8(i2c_port, i2c_addr, i, &data);
|
|
ccprintf("INT_TRIP_RISE_REG%d [%02Xh] = 0x%02x\n",
|
|
i - SN5S330_INT_TRIP_RISE_REG1 + 1,
|
|
i,
|
|
data);
|
|
}
|
|
|
|
for (i = SN5S330_INT_TRIP_FALL_REG1; i <= SN5S330_INT_TRIP_FALL_REG3;
|
|
i++) {
|
|
i2c_read8(i2c_port, i2c_addr, i, &data);
|
|
ccprintf("INT_TRIP_FALL_REG%d [%02Xh] = 0x%02x\n",
|
|
i - SN5S330_INT_TRIP_FALL_REG1 + 1,
|
|
i,
|
|
data);
|
|
}
|
|
|
|
for (i = SN5S330_INT_MASK_RISE_REG1; i <= SN5S330_INT_MASK_RISE_REG3;
|
|
i++) {
|
|
i2c_read8(i2c_port, i2c_addr, i, &data);
|
|
ccprintf("INT_MASK_RISE_REG%d [%02Xh] = 0x%02x\n",
|
|
i - SN5S330_INT_MASK_RISE_REG1 + 1,
|
|
i,
|
|
data);
|
|
}
|
|
|
|
for (i = SN5S330_INT_MASK_FALL_REG1; i <= SN5S330_INT_MASK_FALL_REG3;
|
|
i++) {
|
|
i2c_read8(i2c_port, i2c_addr, i, &data);
|
|
ccprintf("INT_MASK_FALL_REG%d [%02Xh] = 0x%02x\n",
|
|
i - SN5S330_INT_MASK_FALL_REG1 + 1,
|
|
i,
|
|
data);
|
|
}
|
|
|
|
return EC_SUCCESS;
|
|
}
|
|
#endif /* defined(CONFIG_CMD_PPC_DUMP) */
|
|
|
|
static int get_func_set3(uint8_t port, int *regval)
|
|
{
|
|
int status;
|
|
|
|
status = read_reg(port, SN5S330_FUNC_SET3, regval);
|
|
if (status)
|
|
CPRINTS("Failed to read FUNC_SET3!");
|
|
|
|
return status;
|
|
}
|
|
|
|
static int sn5s330_is_pp_fet_enabled(uint8_t port, enum sn5s330_pp_idx pp,
|
|
int *is_enabled)
|
|
{
|
|
int pp_bit;
|
|
int status;
|
|
int regval;
|
|
|
|
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 = get_func_set3(port, ®val);
|
|
if (status)
|
|
return status;
|
|
|
|
*is_enabled = !!(pp_bit & regval);
|
|
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
static int sn5s330_pp_fet_enable(uint8_t port, enum sn5s330_pp_idx pp,
|
|
int enable)
|
|
{
|
|
int regval;
|
|
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 = get_func_set3(port, ®val);
|
|
if (status)
|
|
return status;
|
|
|
|
if (enable)
|
|
regval |= pp_bit;
|
|
else
|
|
regval &= ~pp_bit;
|
|
|
|
status = write_reg(port, SN5S330_FUNC_SET3, regval);
|
|
if (status) {
|
|
CPRINTS("Failed to set FUNC_SET3!");
|
|
return status;
|
|
}
|
|
|
|
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 int i2c_addr = ppc_chips[port].i2c_addr;
|
|
|
|
#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, SN5S330_FUNC_SET1,
|
|
regval);
|
|
if (status) {
|
|
CPRINTS("Failed to set FUNC_SET1! Retrying...");
|
|
retries++;
|
|
msleep(1);
|
|
} else {
|
|
break;
|
|
}
|
|
} while (retries < 10);
|
|
|
|
/* Set Vbus OVP threshold to ~22.325V. */
|
|
regval = 0x37;
|
|
status = i2c_write8(i2c_port, i2c_addr, SN5S330_FUNC_SET5, regval);
|
|
if (status) {
|
|
CPRINTS("Failed to set FUNC_SET5!");
|
|
return status;
|
|
}
|
|
|
|
/* Set Vbus UVP threshold to ~2.75V. */
|
|
status = i2c_read8(i2c_port, i2c_addr, SN5S330_FUNC_SET6, ®val);
|
|
if (status) {
|
|
CPRINTS("Failed to read FUNC_SET6!");
|
|
return status;
|
|
}
|
|
regval &= ~0x3F;
|
|
regval |= 1;
|
|
status = i2c_write8(i2c_port, i2c_addr, SN5S330_FUNC_SET6, regval);
|
|
if (status) {
|
|
CPRINTS("Failed to write FUNC_SET6!");
|
|
return status;
|
|
}
|
|
|
|
/* Enable SBU Fets and set PP2 current limit to ~3A. */
|
|
regval = SN5S330_SBU_EN | 0xf;
|
|
status = i2c_write8(i2c_port, i2c_addr, SN5S330_FUNC_SET2, regval);
|
|
if (status) {
|
|
CPRINTS("Failed to set FUNC_SET2!");
|
|
return status;
|
|
}
|
|
|
|
/* TODO(aaboagye): What about Vconn? */
|
|
|
|
/*
|
|
* Indicate we are using PP2 configuration 2 and enable OVP comparator
|
|
* for CC lines.
|
|
*/
|
|
regval = SN5S330_OVP_EN_CC | SN5S330_PP2_CONFIG;
|
|
status = i2c_write8(i2c_port, i2c_addr, SN5S330_FUNC_SET9, regval);
|
|
if (status) {
|
|
CPRINTS("Failed to set FUNC_SET9!");
|
|
return status;
|
|
}
|
|
|
|
/* Set analog current limit delay to 200 us for both PP1 & PP2. */
|
|
regval = (PPX_ILIM_DEGLITCH_0_US_200 << 3) | PPX_ILIM_DEGLITCH_0_US_200;
|
|
status = i2c_write8(i2c_port, i2c_addr, SN5S330_FUNC_SET11,
|
|
regval);
|
|
if (status) {
|
|
CPRINTS("Failed to set FUNC_SET11");
|
|
return status;
|
|
}
|
|
|
|
/* Turn off dead battery resistors and turn on CC FETs. */
|
|
status = i2c_read8(i2c_port, i2c_addr, SN5S330_FUNC_SET4, ®val);
|
|
if (status) {
|
|
CPRINTS("Failed to read FUNC_SET4!");
|
|
return status;
|
|
}
|
|
regval |= SN5S330_CC_EN;
|
|
status = i2c_write8(i2c_port, i2c_addr, SN5S330_FUNC_SET4, regval);
|
|
if (status) {
|
|
CPRINTS("Failed to set FUNC_SET4!");
|
|
return status;
|
|
}
|
|
|
|
/* Set ideal diode mode for both PP1 and PP2. */
|
|
status = i2c_read8(i2c_port, i2c_addr, SN5S330_FUNC_SET3, ®val);
|
|
if (status) {
|
|
CPRINTS("Failed to read FUNC_SET3!");
|
|
return status;
|
|
}
|
|
regval |= SN5S330_SET_RCP_MODE_PP1 | SN5S330_SET_RCP_MODE_PP2;
|
|
status = i2c_write8(i2c_port, i2c_addr, SN5S330_FUNC_SET3, regval);
|
|
if (status) {
|
|
CPRINTS("Failed to set FUNC_SET3!");
|
|
return status;
|
|
}
|
|
|
|
/* Turn off PP1 FET. */
|
|
status = sn5s330_pp_fet_enable(port, SN5S330_PP1, 0);
|
|
if (status) {
|
|
CPRINTS("Failed to turn off PP1 FET!");
|
|
}
|
|
|
|
/*
|
|
* Don't proceed with the rest of initialization if we're sysjumping.
|
|
* We would have already done this before.
|
|
*/
|
|
if (system_jumped_to_this_image())
|
|
return EC_SUCCESS;
|
|
|
|
/* Clear the digital reset bit. */
|
|
status = i2c_read8(i2c_port, i2c_addr, SN5S330_INT_STATUS_REG4,
|
|
®val);
|
|
if (status) {
|
|
CPRINTS("Failed to read INT_STATUS_REG4!");
|
|
return status;
|
|
}
|
|
regval |= SN5S330_DIG_RES;
|
|
status = i2c_write8(i2c_port, i2c_addr, SN5S330_INT_STATUS_REG4,
|
|
regval);
|
|
if (status) {
|
|
CPRINTS("Failed to write INT_STATUS_REG4!");
|
|
return status;
|
|
}
|
|
|
|
/*
|
|
* Before turning on the PP2 FET, let's mask off all interrupts except
|
|
* for the PP1 overcurrent condition and then clear all pending
|
|
* interrupts. If PPC is being used to detect VBUS, then also enable
|
|
* interrupts for VBUS presence.
|
|
*
|
|
* TODO(aaboagye): Unmask fast-role swap events once fast-role swap is
|
|
* implemented in the PD stack.
|
|
*/
|
|
|
|
regval = ~SN5S330_ILIM_PP1_MASK;
|
|
status = i2c_write8(i2c_port, i2c_addr, SN5S330_INT_MASK_RISE_REG1,
|
|
regval);
|
|
if (status) {
|
|
CPRINTS("Failed to write INT_MASK_RISE1!");
|
|
return status;
|
|
}
|
|
|
|
status = i2c_write8(i2c_port, i2c_addr, SN5S330_INT_MASK_FALL_REG1,
|
|
regval);
|
|
if (status) {
|
|
CPRINTS("Failed to write INT_MASK_FALL1!");
|
|
return status;
|
|
}
|
|
|
|
/* Now mask all the other interrupts. */
|
|
status = i2c_write8(i2c_port, i2c_addr, SN5S330_INT_MASK_RISE_REG2,
|
|
0xFF);
|
|
if (status) {
|
|
CPRINTS("Failed to write INT_MASK_RISE2!");
|
|
return status;
|
|
}
|
|
|
|
status = i2c_write8(i2c_port, i2c_addr, SN5S330_INT_MASK_FALL_REG2,
|
|
0xFF);
|
|
if (status) {
|
|
CPRINTS("Failed to write INT_MASK_FALL2!");
|
|
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, SN5S330_INT_MASK_RISE_REG3,
|
|
regval);
|
|
if (status) {
|
|
CPRINTS("Failed to write INT_MASK_RISE3!");
|
|
return status;
|
|
}
|
|
|
|
status = i2c_write8(i2c_port, i2c_addr, SN5S330_INT_MASK_FALL_REG3,
|
|
regval);
|
|
if (status) {
|
|
CPRINTS("Failed to write INT_MASK_FALL3!");
|
|
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, reg, 0xFF);
|
|
if (status) {
|
|
CPRINTS("Failed to write reg 0x%2x!");
|
|
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, SN5S330_INT_STATUS_REG4,
|
|
®val);
|
|
if (status) {
|
|
CPRINTS("Failed to read INT_STATUS_REG4!");
|
|
return status;
|
|
}
|
|
|
|
if (regval & SN5S330_DB_BOOT) {
|
|
/* Clear the bit. */
|
|
i2c_write8(i2c_port, i2c_addr, SN5S330_INT_STATUS_REG4,
|
|
SN5S330_DB_BOOT);
|
|
|
|
/* Turn on PP2 FET. */
|
|
status = sn5s330_pp_fet_enable(port, SN5S330_PP2, 1);
|
|
if (status) {
|
|
CPRINTS("Failed to turn on PP2 FET!");
|
|
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, ®val);
|
|
if (rv) {
|
|
CPRINTS("p%d: VBUS present error (%d)", 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)
|
|
{
|
|
int is_sourcing_vbus = 0;
|
|
int rv;
|
|
|
|
rv = sn5s330_is_pp_fet_enabled(port, SN5S330_PP1, &is_sourcing_vbus);
|
|
if (rv) {
|
|
CPRINTS("p%d: Failed to determine source FET status! (%d)",
|
|
port, rv);
|
|
return 0;
|
|
}
|
|
|
|
return is_sourcing_vbus;
|
|
}
|
|
|
|
static int sn5s330_set_polarity(int port, int polarity)
|
|
{
|
|
int regval;
|
|
int status;
|
|
|
|
status = read_reg(port, SN5S330_FUNC_SET4, ®val);
|
|
if (status)
|
|
return status;
|
|
|
|
if (polarity)
|
|
regval |= SN5S330_CC_POLARITY; /* CC2 active. */
|
|
else
|
|
regval &= ~SN5S330_CC_POLARITY; /* CC1 active. */
|
|
|
|
return write_reg(port, SN5S330_FUNC_SET4, regval);
|
|
}
|
|
|
|
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, ®val);
|
|
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 regval;
|
|
int status;
|
|
|
|
status = get_func_set3(port, ®val);
|
|
if (status)
|
|
return status;
|
|
|
|
if (enable)
|
|
regval |= SN5S330_VBUS_DISCH_EN;
|
|
else
|
|
regval &= ~SN5S330_VBUS_DISCH_EN;
|
|
|
|
status = write_reg(port, SN5S330_FUNC_SET3, regval);
|
|
if (status) {
|
|
CPRINTS("Failed to %s vbus discharge",
|
|
enable ? "enable" : "disable");
|
|
return status;
|
|
}
|
|
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
static int sn5s330_set_vconn(int port, int enable)
|
|
{
|
|
int regval;
|
|
int status;
|
|
|
|
status = read_reg(port, SN5S330_FUNC_SET4, ®val);
|
|
if (status)
|
|
return status;
|
|
|
|
if (enable)
|
|
regval |= SN5S330_VCONN_EN;
|
|
else
|
|
regval &= ~SN5S330_VCONN_EN;
|
|
|
|
return write_reg(port, SN5S330_FUNC_SET4, regval);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
static void sn5s330_handle_interrupt(int port)
|
|
{
|
|
int rise = 0;
|
|
int fall = 0;
|
|
|
|
/*
|
|
* The only interrupts that should be enabled are the PP1 overcurrent
|
|
* condition, and for VBUS_GOOD if PPC is being used to detect VBUS.
|
|
*/
|
|
read_reg(port, SN5S330_INT_TRIP_RISE_REG1, &rise);
|
|
read_reg(port, SN5S330_INT_TRIP_FALL_REG1, &fall);
|
|
|
|
/* Let the board know about the overcurrent event. */
|
|
if (rise & SN5S330_ILIM_PP1_MASK)
|
|
board_overcurrent_event(port);
|
|
|
|
/* Clear the interrupt sources. */
|
|
write_reg(port, SN5S330_INT_TRIP_RISE_REG1, rise);
|
|
write_reg(port, SN5S330_INT_TRIP_FALL_REG1, 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_read_clear(&irq_pending);
|
|
|
|
for (i = 0; i < CONFIG_USB_PD_PORT_COUNT; i++)
|
|
if ((1 << i) & pending)
|
|
sn5s330_handle_interrupt(i);
|
|
}
|
|
DECLARE_DEFERRED(sn5s330_irq_deferred);
|
|
|
|
void sn5s330_interrupt(int port)
|
|
{
|
|
atomic_or(&irq_pending, (1 << 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,
|
|
#ifdef CONFIG_CMD_PPC_DUMP
|
|
.reg_dump = &sn5s330_dump,
|
|
#endif /* defined(CONFIG_CMD_PPC_DUMP) */
|
|
#ifdef CONFIG_USB_PD_VBUS_DETECT_PPC
|
|
.is_vbus_present = &sn5s330_is_vbus_present,
|
|
#endif /* defined(CONFIG_USB_PD_VBUS_DETECT_PPC) */
|
|
.set_polarity = &sn5s330_set_polarity,
|
|
.set_vbus_source_current_limit = &sn5s330_set_vbus_source_current_limit,
|
|
.discharge_vbus = &sn5s330_discharge_vbus,
|
|
.set_vconn = &sn5s330_set_vconn,
|
|
};
|