blob: 816c53d06ede48229d27af1d3ab8c24eddd67320 [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.
*/
/* USB-C Power Path Controller Common Code */
#include "atomic.h"
#include "common.h"
#include "console.h"
#include "hooks.h"
#include "timer.h"
#include "usbc_ppc.h"
#include "util.h"
#ifndef TEST_BUILD
#define CPRINTF(format, args...) cprintf(CC_USBPD, format, ## args)
#define CPRINTS(format, args...) cprints(CC_USBPD, format, ## args)
#else
#define CPRINTF(args...)
#define CPRINTS(args...)
#endif
/*
* A per-port table that indicates how many VBUS overcurrent events have
* occurred. This table is cleared after detecting a physical disconnect of the
* sink.
*/
static uint8_t oc_event_cnt_tbl[CONFIG_USB_PD_PORT_MAX_COUNT];
static uint32_t connected_ports;
/* Simple wrappers to dispatch to the drivers. */
int ppc_init(int port)
{
int rv = EC_ERROR_UNIMPLEMENTED;
const struct ppc_config_t *ppc;
if ((port < 0) || (port >= ppc_cnt)) {
CPRINTS("%s(%d) Invalid port!", __func__, port);
return EC_ERROR_INVAL;
}
ppc = &ppc_chips[port];
if (ppc->drv->init) {
rv = ppc->drv->init(port);
if (rv)
CPRINTS("p%d: PPC init failed! (%d)", port, rv);
else
CPRINTS("p%d: PPC init'd.", port);
}
return rv;
}
int ppc_add_oc_event(int port)
{
if ((port < 0) || (port >= ppc_cnt)) {
CPRINTS("%s(%d) Invalid port!", __func__, port);
return EC_ERROR_INVAL;
}
oc_event_cnt_tbl[port]++;
/* The port overcurrented, so don't clear it's OC events. */
atomic_clear(&connected_ports, 1 << port);
if (oc_event_cnt_tbl[port] >= PPC_OC_CNT_THRESH)
CPRINTS("C%d: OC event limit reached! "
"Source path disabled until physical disconnect.",
port);
return EC_SUCCESS;
}
static void clear_oc_tbl(void)
{
int port;
for (port = 0; port < ppc_cnt; port++)
/*
* Only clear the table if the port partner is no longer
* attached after debouncing.
*/
if ((!(BIT(port) & connected_ports)) &&
oc_event_cnt_tbl[port]) {
oc_event_cnt_tbl[port] = 0;
CPRINTS("C%d: OC events cleared", port);
}
}
DECLARE_DEFERRED(clear_oc_tbl);
int ppc_clear_oc_event_counter(int port)
{
if ((port < 0) || (port >= ppc_cnt)) {
CPRINTS("%s(%d) Invalid port!", __func__, port);
return EC_ERROR_INVAL;
}
/*
* If we are clearing our event table in quick succession, we may be in
* an overcurrent loop where we are also detecting a disconnect on the
* CC pins. Therefore, let's not clear it just yet and the let the
* limit be reached. This way, we won't send the hard reset and
* actually detect the physical disconnect.
*/
if (oc_event_cnt_tbl[port]) {
hook_call_deferred(&clear_oc_tbl_data,
PPC_OC_COOLDOWN_DELAY_US);
}
return EC_SUCCESS;
}
int ppc_is_sourcing_vbus(int port)
{
int rv = EC_ERROR_UNIMPLEMENTED;
const struct ppc_config_t *ppc;
if ((port < 0) || (port >= ppc_cnt)) {
CPRINTS("%s(%d) Invalid port!", __func__, port);
return EC_ERROR_INVAL;
}
ppc = &ppc_chips[port];
if (ppc->drv->is_sourcing_vbus)
rv = ppc->drv->is_sourcing_vbus(port);
return rv;
}
#ifdef CONFIG_USBC_PPC_POLARITY
int ppc_set_polarity(int port, int polarity)
{
int rv = EC_ERROR_UNIMPLEMENTED;
const struct ppc_config_t *ppc;
if ((port < 0) || (port >= ppc_cnt)) {
CPRINTS("%s(%d) Invalid port!", __func__, port);
return EC_ERROR_INVAL;
}
ppc = &ppc_chips[port];
if (ppc->drv->set_polarity)
rv = ppc->drv->set_polarity(port, polarity);
return rv;
}
#endif
int ppc_set_vbus_source_current_limit(int port, enum tcpc_rp_value rp)
{
int rv = EC_ERROR_UNIMPLEMENTED;
const struct ppc_config_t *ppc;
if ((port < 0) || (port >= ppc_cnt)) {
CPRINTS("%s(%d) Invalid port!", __func__, port);
return EC_ERROR_INVAL;
}
ppc = &ppc_chips[port];
if (ppc->drv->set_vbus_source_current_limit)
rv = ppc->drv->set_vbus_source_current_limit(port, rp);
return rv;
}
int ppc_discharge_vbus(int port, int enable)
{
int rv = EC_ERROR_UNIMPLEMENTED;
const struct ppc_config_t *ppc;
if ((port < 0) || (port >= ppc_cnt)) {
CPRINTS("%s(%d) Invalid port!", __func__, port);
return EC_ERROR_INVAL;
}
ppc = &ppc_chips[port];
if (ppc->drv->discharge_vbus)
rv = ppc->drv->discharge_vbus(port, enable);
return rv;
}
int ppc_is_port_latched_off(int port)
{
if ((port < 0) || (port >= ppc_cnt)) {
CPRINTS("%s(%d) Invalid port!", __func__, port);
return EC_ERROR_INVAL;
}
return oc_event_cnt_tbl[port] >= PPC_OC_CNT_THRESH;
}
#ifdef CONFIG_USBC_PPC_SBU
int ppc_set_sbu(int port, int enable)
{
int rv = EC_ERROR_UNIMPLEMENTED;
const struct ppc_config_t *ppc;
if ((port < 0) || (port >= ppc_cnt)) {
CPRINTS("%s(%d) Invalid port!", __func__, port);
return EC_ERROR_INVAL;
}
ppc = &ppc_chips[port];
if (ppc->drv->set_sbu)
rv = ppc->drv->set_sbu(port, enable);
return rv;
}
#endif /* defined(CONFIG_USBC_PPC_SBU) */
#ifdef CONFIG_USBC_PPC_VCONN
int ppc_set_vconn(int port, int enable)
{
int rv = EC_ERROR_UNIMPLEMENTED;
const struct ppc_config_t *ppc;
if ((port < 0) || (port >= ppc_cnt)) {
CPRINTS("%s(%d) Invalid port!", __func__, port);
return EC_ERROR_INVAL;
}
/*
* Check our OC event counter. If we've exceeded our threshold, then
* let's latch our source path off to prevent continuous cycling. When
* the PD state machine detects a disconnection on the CC lines, we will
* reset our OC event counter.
*/
if (enable && ppc_is_port_latched_off(port))
return EC_ERROR_ACCESS_DENIED;
ppc = &ppc_chips[port];
if (ppc->drv->set_vconn)
rv = ppc->drv->set_vconn(port, enable);
return rv;
}
#endif
void ppc_sink_is_connected(int port, int is_connected)
{
if ((port < 0) || (port >= ppc_cnt)) {
CPRINTS("%s(%d) Invalid port!", __func__, port);
return;
}
if (is_connected)
atomic_or(&connected_ports, 1 << port);
else
atomic_clear(&connected_ports, 1 << port);
}
int ppc_vbus_sink_enable(int port, int enable)
{
int rv = EC_ERROR_UNIMPLEMENTED;
const struct ppc_config_t *ppc;
if ((port < 0) || (port >= ppc_cnt)) {
CPRINTS("%s(%d) Invalid port!", __func__, port);
return EC_ERROR_INVAL;
}
ppc = &ppc_chips[port];
if (ppc->drv->vbus_sink_enable)
rv = ppc->drv->vbus_sink_enable(port, enable);
return rv;
}
int ppc_enter_low_power_mode(int port)
{
int rv = EC_ERROR_UNIMPLEMENTED;
const struct ppc_config_t *ppc;
if ((port < 0) || (port >= ppc_cnt)) {
CPRINTS("%s(%d) Invalid port!", __func__, port);
return EC_ERROR_INVAL;
}
ppc = &ppc_chips[port];
if (ppc->drv->enter_low_power_mode)
rv = ppc->drv->enter_low_power_mode(port);
return rv;
}
int ppc_vbus_source_enable(int port, int enable)
{
int rv = EC_ERROR_UNIMPLEMENTED;
const struct ppc_config_t *ppc;
if ((port < 0) || (port >= ppc_cnt)) {
CPRINTS("%s(%d) Invalid port!", __func__, port);
return EC_ERROR_INVAL;
}
/*
* Check our OC event counter. If we've exceeded our threshold, then
* let's latch our source path off to prevent continuous cycling. When
* the PD state machine detects a disconnection on the CC lines, we will
* reset our OC event counter.
*/
if (enable && ppc_is_port_latched_off(port))
return EC_ERROR_ACCESS_DENIED;
ppc = &ppc_chips[port];
if (ppc->drv->vbus_source_enable)
rv = ppc->drv->vbus_source_enable(port, enable);
return rv;
}
#ifdef CONFIG_USB_PD_VBUS_DETECT_PPC
int ppc_is_vbus_present(int port)
{
int rv = EC_ERROR_UNIMPLEMENTED;
const struct ppc_config_t *ppc;
if ((port < 0) || (port >= ppc_cnt)) {
CPRINTS("%s(%d) Invalid port!", __func__, port);
return EC_ERROR_INVAL;
}
ppc = &ppc_chips[port];
if (ppc->drv->is_vbus_present)
rv = ppc->drv->is_vbus_present(port);
return rv;
}
#endif /* defined(CONFIG_USB_PD_VBUS_DETECT_PPC) */
#ifdef CONFIG_CMD_PPC_DUMP
static int command_ppc_dump(int argc, char **argv)
{
int port;
int rv = EC_ERROR_UNIMPLEMENTED;
const struct ppc_config_t *ppc;
if (argc < 2)
return EC_ERROR_PARAM_COUNT;
port = atoi(argv[1]);
if ((port < 0) || (port >= ppc_cnt)) {
CPRINTS("%s(%d) Invalid port!", __func__, port);
return EC_ERROR_INVAL;
}
ppc = &ppc_chips[port];
if (ppc->drv->reg_dump)
rv = ppc->drv->reg_dump(port);
return rv;
}
DECLARE_CONSOLE_COMMAND(ppc_dump, command_ppc_dump, "<Type-C port>",
"dump the PPC regs");
#endif /* defined(CONFIG_CMD_PPC_DUMP) */