blob: 925151cc09fd1f4bb5c919b98b03421de94fb42b [file] [log] [blame] [edit]
/* Copyright 2016 The ChromiumOS Authors
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "ccd_config.h"
#include "clock.h"
#include "console.h"
#include "extension.h"
#include "gpio.h"
#include "hooks.h"
#include "rdd.h"
#include "registers.h"
#include "system.h"
#include "task.h"
#include "timer.h"
#include "usb_api.h"
#define CPRINTS(format, args...) cprints(CC_USB, format, ## args)
/*
* The default PROG_DEBUG_STATE_MAP value. Used to tell the to controller send
* an interrupt when CC1/2 are detected to be in the defined voltage range of a
* debug accessory.
*/
#define DETECT_DEBUG 0x420
/*
* The interrupt only triggers when the debug state is detected. If we want to
* trigger an interrupt when the debug state is *not* detected, we need to
* program the bit-inverse.
*/
#define DETECT_DISCONNECT (~DETECT_DEBUG & 0xffff)
/* State of RDD CC detection */
static enum device_state state = DEVICE_STATE_DISCONNECTED;
/* Force detecting a debug accessory (ignore RDD CC detect hardware) */
static int force_detected;
/*
* The Rdd state. This is saved in the rdd_interrupt to make sure the state is
* stable.
*/
static uint8_t rdd_is_detected_shadow;
/**
* Get instantaneous cable detect state
*
* @return 1 if debug accessory is detected, 0 if not detected
*/
uint8_t rdd_is_detected(void)
{
return rdd_is_detected_shadow;
}
uint8_t get_rdd_metrics(void)
{
uint8_t status = 0;
if (rdd_is_detected())
status |= (1 << CR50_METRICSV_RDD_IS_DETECTED_SHIFT);
if (force_detected)
status |= (1 << CR50_METRICSV_RDD_KEEPALIVE_EN_SHIFT);
if (ccd_get_flag(CCD_FLAG_RDDKEEPALIVE_AT_BOOT))
status |= (1 << CR50_METRICSV_RDD_KEEPALIVE_EN_ATBOOT_SHIFT);
if (!gpio_get_level(GPIO_CCD_MODE_L))
status |= (1 << CR50_METRICSV_CCD_MODE_EN_SHIFT);
return status;
}
void print_rdd_state(void)
{
ccprintf("Rdd: %s\n", device_state_name(state));
ccprintf("KeepAlive: %sabled%s\n", force_detected ? "en" : "dis",
ccd_get_flag(CCD_FLAG_RDDKEEPALIVE_AT_BOOT) ? " (atboot)" :
"");
}
/**
* Handle debug accessory disconnecting
*/
static void rdd_disconnect(void)
{
CPRINTS("Rdd disconnect");
state = DEVICE_STATE_DISCONNECTED;
/*
* Stop pulling CCD_MODE_L low. The internal pullup configured in the
* pinmux will pull the signal back high, unless the EC is also pulling
* it low.
*
* This disables the SBUx muxes, if we were the only one driving
* CCD_MODE_L.
*/
gpio_set_level(GPIO_CCD_MODE_L, 1);
}
DECLARE_DEFERRED(rdd_disconnect);
/**
* Handle debug accessory connecting
*
* This can be deferred from both rdd_detect() and the interrupt handler, so
* it needs to check the current state to determine whether we're already
* connected.
*/
static void rdd_connect(void)
{
/* If we were debouncing, we're done, and still connected */
if (state == DEVICE_STATE_DEBOUNCING)
state = DEVICE_STATE_CONNECTED;
/* If we're already connected, done */
if (state == DEVICE_STATE_CONNECTED)
return;
/* We were previously disconnected, so connect */
CPRINTS("Rdd connect");
state = DEVICE_STATE_CONNECTED;
/* Assert CCD_MODE_L to enable the SBUx muxes */
gpio_set_level(GPIO_CCD_MODE_L, 0);
}
DECLARE_DEFERRED(rdd_connect);
/**
* Debug accessory detect interrupt
*/
static void rdd_interrupt(void)
{
uint8_t cc1 = GREAD_FIELD(RDD, INPUT_PIN_VALUES, CC1);
uint8_t cc2 = GREAD_FIELD(RDD, INPUT_PIN_VALUES, CC2);
/* Save the rdd state while the cc lines are stable. */
rdd_is_detected_shadow = (cc1 == cc2 && (cc1 == 3 || cc1 == 1));
/*
* The Rdd detector is level-sensitive with debounce. That is, it
* samples the RDCCx pin states. If they're different, it resets the
* wait counter. If they're the same, it decrements the wait counter.
* Then if the counter is zero, and the state we're looking for matches
* the map, it fires the interrupt.
*
* Note that the counter *remains* zero until the pin states change.
*
* If we want to be able to wake on Rdd change, then interrupts need to
* remain enabled. Each time we get an interrupt, we'll toggle the map
* we're looking for to the opposite state. That stops the interrupt
* from continuing to fire on the current state. When the pins settle
* into a new state, we'll fire the interrupt again.
*
* Even with that, we can still get a double interrupt now and then,
* because the Rdd module runs on a different clock than we do. So the
* write we do to change the state map may not be picked up until the
* next clock, when the Rdd module has already generated its next
* interrupt based on the old map. This is harmless, because we're
* unlikely to actually trigger the deferred function twice, and it
* doesn't care if we do anyway because on the second call it'll
* already be in the connected state.
*/
if (rdd_is_detected()) {
/* Accessory detected; toggle to looking for disconnect */
GWRITE(RDD, PROG_DEBUG_STATE_MAP, DETECT_DISCONNECT);
/* Cancel any pending disconnects */
hook_call_deferred(&rdd_disconnect_data, -1);
/*
* Trigger the deferred handler so that we move back into the
* connected state before our debounce interval expires.
*/
hook_call_deferred(&rdd_connect_data, 0);
} else {
/*
* Skip disconnecting Rdd, if rdd is force detected. If Rdd is
* already disconnected, no need to do it again.
*/
if (!force_detected && state != DEVICE_STATE_DISCONNECTED) {
/* Debounce disconnect for 1 second */
state = DEVICE_STATE_DEBOUNCING;
hook_call_deferred(&rdd_disconnect_data, SECOND);
}
/* Not detected; toggle to looking for connect. */
GWRITE(RDD, PROG_DEBUG_STATE_MAP, DETECT_DEBUG);
}
/* Make sure we stay awake long enough to advance the state machine */
delay_sleep_by(1 * SECOND);
/* Clear the interrupt */
GWRITE_FIELD(RDD, INT_STATE, INTR_DEBUG_STATE_DETECTED, 1);
}
DECLARE_IRQ(GC_IRQNUM_RDD0_INTR_DEBUG_STATE_DETECTED_INT, rdd_interrupt, 1);
void init_rdd_state(void)
{
/* Enable RDD hardware */
clock_enable_module(MODULE_RDD, 1);
GWRITE(RDD, POWER_DOWN_B, 1);
/*
* Note that there is currently (ha, see what I did there) a leakage
* path out of Cr50 into the CC lines. On some systems, this can cause
* false Rdd detection when the TCPCs are turned off. This may require
* a software workaround where RDD hardware must be powered down
* whenever the TCPCs are off, and can only be powered up for brief
* periods to do a quick check. See b/38019839 and b/64582597.
*/
/* Configure to detect accessory connected */
GWRITE(RDD, PROG_DEBUG_STATE_MAP, DETECT_DEBUG);
/*
* Set the 0.4V comparator reference to 0.3V instead. The voltage is
* marginal near 0.4V for example with VBUS at 4.75V and a SuzyQable See
* b/64847312.
*/
GWRITE_FIELD(RDD, REF_ADJ, LVL0P4V, 0x2);
/*
* Enable interrupt for detecting CC. This minimizes the time before
* we transition to cable-detected at boot, and will cause us to wake
* from deep sleep if a cable is plugged in.
*/
task_enable_irq(GC_IRQNUM_RDD0_INTR_DEBUG_STATE_DETECTED_INT);
GWRITE_FIELD(RDD, INT_STATE, INTR_DEBUG_STATE_DETECTED, 1);
GWRITE_FIELD(RDD, INT_ENABLE, INTR_DEBUG_STATE_DETECTED, 1);
/* Restore the rddkeepalive atboot state from the ccd flags. */
force_detected = ccd_get_flag(CCD_FLAG_RDDKEEPALIVE_AT_BOOT);
#ifdef H1_RED_BOARD
CPRINTS("Red board rddkeepalive enabled.");
force_detected = 1;
#endif
if (force_detected)
hook_call_deferred(&rdd_connect_data, 0);
}
static int command_rdd_keepalive(int argc, char **argv)
{
if (argc == 1) {
print_rdd_state();
return EC_SUCCESS;
}
if (console_is_restricted())
return EC_ERROR_ACCESS_DENIED;
if (!parse_bool(argv[1], &force_detected))
return EC_ERROR_PARAM1;
if (force_detected) {
/* Force Rdd detect */
ccprintf("Forcing Rdd detect keepalive");
if (argc > 2 && !strcasecmp(argv[2], "atboot")) {
/* Change rddkeeplalive at boot to match */
ccprintf(" atboot");
ccd_set_flag(CCD_FLAG_RDDKEEPALIVE_AT_BOOT, 1);
}
ccprintf("\n");
hook_call_deferred(&rdd_connect_data, 0);
} else {
/* Go back to actual hardware state */
ccprintf("Using actual Rdd state\n");
ccd_set_flag(CCD_FLAG_RDDKEEPALIVE_AT_BOOT, 0);
/*
* Cancel rdd_connect and call rdd_interrupt to use the
* actual state.
*/
hook_call_deferred(&rdd_connect_data, -1);
rdd_interrupt();
}
return EC_SUCCESS;
}
DECLARE_SAFE_CONSOLE_COMMAND(rddkeepalive, command_rdd_keepalive,
"[BOOLEAN] [atboot]",
"Get Rdd state or force keepalive");