/* Copyright 2016 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 "clock.h" #include "console.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; /** * Get instantaneous cable detect state * * @return 1 if debug accessory is detected, 0 if not detected */ static int rdd_is_detected(void) { uint8_t cc1 = GREAD_FIELD(RDD, INPUT_PIN_VALUES, CC1); uint8_t cc2 = GREAD_FIELD(RDD, INPUT_PIN_VALUES, CC2); return (cc1 == cc2 && (cc1 == 3 || cc1 == 1)); } void print_rdd_state(void) { ccprintf("Rdd: %s\n", force_detected ? "keepalive" : device_state_name(state)); } /** * 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_flags(GPIO_CCD_MODE_L, GPIO_INPUT); } /** * 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; /* Start pulling CCD_MODE_L low to enable the SBUx muxes */ gpio_set_flags(GPIO_CCD_MODE_L, GPIO_OUT_LOW); } DECLARE_DEFERRED(rdd_connect); /** * Debug accessory detect interrupt */ static void rdd_interrupt(void) { /* * 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); /* * 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 { /* * Not detected; toggle to looking for connect. We'll start * debouncing disconnect the next time HOOK_SECOND triggers * rdd_detect() below. */ 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); /** * RDD CC detect state machine */ static void rdd_detect(void) { /* Handle detecting device */ if (force_detected || rdd_is_detected()) { rdd_connect(); return; } /* CC wasn't detected. If we're already disconnected, done. */ if (state == DEVICE_STATE_DISCONNECTED) return; /* If we were debouncing, we're now sure we're disconnected */ if (state == DEVICE_STATE_DEBOUNCING) { rdd_disconnect(); return; } /* * Otherwise, we were connected but the accessory seems to be * disconnected right now. PD negotiation (e.g. during EC reset or * sysjump) can alter the RDCCx voltages, so we need to debounce this * signal for longer than the Rdd hardware does to make sure it's * really disconnected before we deassert CCD_MODE_L. */ state = DEVICE_STATE_DEBOUNCING; } /* * Bump up priority so this runs before the CCD_MODE_L state machine, because * we can change CCD_MODE_L. */ DECLARE_HOOK(HOOK_SECOND, rdd_detect, HOOK_PRIO_DEFAULT - 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); } static int command_rdd_keepalive(int argc, char **argv) { if (argc == 1) { print_rdd_state(); return EC_SUCCESS; } if (!parse_bool(argv[1], &force_detected)) return EC_ERROR_PARAM1; if (force_detected) { /* Force Rdd detect */ ccprintf("Forcing Rdd detect keepalive\n"); hook_call_deferred(&rdd_connect_data, 0); } else { /* Go back to actual hardware state */ ccprintf("Using actual Rdd state\n"); } return EC_SUCCESS; } DECLARE_CONSOLE_COMMAND(rddkeepalive, command_rdd_keepalive, "[BOOLEAN]", "Get Rdd state or force keepalive");