coreboot-libre-fam15h-rdimm/3rdparty/chromeec/common/usbc/usbc_task.c

189 lines
5.1 KiB
C

/* Copyright 2019 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.
*/
#include "battery.h"
#include "battery_smart.h"
#include "board.h"
#include "charge_manager.h"
#include "charge_state.h"
#include "chipset.h"
#include "common.h"
#include "console.h"
#include "ec_commands.h"
#include "gpio.h"
#include "hooks.h"
#include "host_command.h"
#include "registers.h"
#include "system.h"
#include "task.h"
#include "timer.h"
#include "util.h"
#include "usb_charge.h"
#include "usb_mux.h"
#include "usb_pd.h"
#include "usb_prl_sm.h"
#include "tcpm.h"
#include "usb_pe_sm.h"
#include "usb_prl_sm.h"
#include "usb_sm.h"
#include "usb_tc_sm.h"
#include "usbc_ppc.h"
#include "version.h"
int tc_restart_tcpc(int port)
{
return tcpm_init(port);
}
void set_polarity(int port, int polarity)
{
tcpm_set_polarity(port, polarity);
if (IS_ENABLED(CONFIG_USBC_PPC_POLARITY))
ppc_set_polarity(port, polarity);
}
void set_usb_mux_with_current_data_role(int port)
{
#ifdef CONFIG_USBC_SS_MUX
/*
* If the SoC is down, then we disconnect the MUX to save power since
* no one cares about the data lines.
*/
#ifdef CONFIG_POWER_COMMON
if (chipset_in_or_transitioning_to_state(CHIPSET_STATE_ANY_OFF)) {
usb_mux_set(port, TYPEC_MUX_NONE, USB_SWITCH_DISCONNECT,
tc_get_polarity(port));
return;
}
#endif /* CONFIG_POWER_COMMON */
/*
* When PD stack is disconnected, then mux should be disconnected, which
* is also what happens in the set_state disconnection code. Once the
* PD state machine progresses out of disconnect, the MUX state will
* be set correctly again.
*/
if (!pd_is_connected(port))
usb_mux_set(port, TYPEC_MUX_NONE, USB_SWITCH_DISCONNECT,
tc_get_polarity(port));
/*
* If new data role isn't DFP and we only support DFP, also disconnect.
*/
else if (IS_ENABLED(CONFIG_USBC_SS_MUX_DFP_ONLY) &&
tc_get_data_role(port) != PD_ROLE_DFP)
usb_mux_set(port, TYPEC_MUX_NONE, USB_SWITCH_DISCONNECT,
tc_get_polarity(port));
/*
* Otherwise connect mux since we are in S3+
*/
else
usb_mux_set(port, TYPEC_MUX_USB, USB_SWITCH_CONNECT,
tc_get_polarity(port));
#endif /* CONFIG_USBC_SS_MUX */
}
/* High-priority interrupt tasks implementations */
#if defined(HAS_TASK_PD_INT_C0) || defined(HAS_TASK_PD_INT_C1) || \
defined(HAS_TASK_PD_INT_C2)
/* Used to conditionally compile code in main pd task. */
#define HAS_DEFFERED_INTERRUPT_HANDLER
/* Events for pd_interrupt_handler_task */
#define PD_PROCESS_INTERRUPT (1<<0)
static uint8_t pd_int_task_id[CONFIG_USB_PD_PORT_COUNT];
void schedule_deferred_pd_interrupt(const int port)
{
task_set_event(pd_int_task_id[port], PD_PROCESS_INTERRUPT, 0);
}
/*
* Main task entry point that handles PD interrupts for a single port
*
* @param p The PD port number for which to handle interrupts (pointer is
* reinterpreted as an integer directly).
*/
void pd_interrupt_handler_task(void *p)
{
const int port = (int) p;
const int port_mask = (PD_STATUS_TCPC_ALERT_0 << port);
ASSERT(port >= 0 && port < CONFIG_USB_PD_PORT_COUNT);
pd_int_task_id[port] = task_get_current();
while (1) {
const int evt = task_wait_event(-1);
if (evt & PD_PROCESS_INTERRUPT) {
/*
* While the interrupt signal is asserted; we have more
* work to do. This effectively makes the interrupt a
* level-interrupt instead of an edge-interrupt without
* having to enable/disable a real level-interrupt in
* multiple locations.
*
* Also, if the port is disabled do not process
* interrupts. Upon existing suspend, we schedule a
* PD_PROCESS_INTERRUPT to check if we missed anything.
*/
while ((tcpc_get_alert_status() & port_mask) &&
pd_is_port_enabled(port))
tcpc_alert(port);
}
}
}
#endif /* HAS_TASK_PD_INT_C0 || HAS_TASK_PD_INT_C1 || HAS_TASK_PD_INT_C2 */
void pd_task(void *u)
{
int port = TASK_ID_TO_PD_PORT(task_get_current());
tc_state_init(port);
if (IS_ENABLED(CONFIG_USBC_PPC))
ppc_init(port);
/*
* Since most boards configure the TCPC interrupt as edge
* and it is possible that the interrupt line was asserted between init
* and calling set_state, we need to process any pending interrupts now.
* Otherwise future interrupts will never fire because another edge
* never happens. Note this needs to happen after set_state() is called.
*/
if (IS_ENABLED(HAS_DEFFERED_INTERRUPT_HANDLER))
schedule_deferred_pd_interrupt(port);
while (1) {
/* wait for next event/packet or timeout expiration */
const uint32_t evt = task_wait_event(tc_get_timeout(port));
/* handle events that affect the state machine as a whole */
tc_event_check(port, evt);
/*
* run port controller task to check CC and/or read incoming
* messages
*/
if (IS_ENABLED(CONFIG_USB_PD_TCPC))
tcpc_run(port, evt);
if (IS_ENABLED(CONFIG_USB_PE_SM))
/* Run policy engine state machine */
pe_run(port, evt, tc_get_pd_enabled(port));
if (IS_ENABLED(CONFIG_USB_PRL_SM))
/* Run protocol state machine */
prl_run(port, evt, tc_get_pd_enabled(port));
/* Run TypeC state machine */
tc_run(port);
}
}