summaryrefslogtreecommitdiff
path: root/board/cr50/servo_state.c
diff options
context:
space:
mode:
Diffstat (limited to 'board/cr50/servo_state.c')
-rw-r--r--board/cr50/servo_state.c207
1 files changed, 207 insertions, 0 deletions
diff --git a/board/cr50/servo_state.c b/board/cr50/servo_state.c
new file mode 100644
index 0000000000..8c574f5a4e
--- /dev/null
+++ b/board/cr50/servo_state.c
@@ -0,0 +1,207 @@
+/* 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.
+ *
+ * Servo state machine.
+ */
+#include "common.h"
+#include "console.h"
+#include "gpio.h"
+#include "hooks.h"
+#include "uart_bitbang.h"
+#include "uartn.h"
+#include "usb_i2c.h"
+
+#define CPRINTS(format, args...) cprints(CC_SYSTEM, format, ## args)
+
+static enum device_state state = DEVICE_STATE_INIT;
+
+void print_servo_state(void)
+{
+ ccprintf("Servo: %s\n", device_state_name(state));
+}
+
+int servo_is_connected(void)
+{
+ /*
+ * If we're connected, we definitely know we are. If we're debouncing,
+ * then we were connected and might still be. If we haven't
+ * initialized yet, we'd bettter assume we're connected until we prove
+ * otherwise. In any of these cases, it's not safe to allow ports to
+ * be connected because that would block detecting servo.
+ */
+ return (state == DEVICE_STATE_CONNECTED ||
+ state == DEVICE_STATE_DEBOUNCING ||
+ state == DEVICE_STATE_INIT ||
+ state == DEVICE_STATE_INIT_DEBOUNCING);
+}
+
+/**
+ * Set the servo state.
+ *
+ * Done as a function to make it easier to debug state transitions. Note that
+ * this ONLY sets the state (and possibly prints debug info), and doesn't do
+ * all the additional transition work that servo_disconnect(), etc. do.
+ *
+ * @param new_state State to set.
+ */
+static void set_state(enum device_state new_state)
+{
+#ifdef CR50_DEBUG_SERVO_STATE
+ /* Print all state transitions. May spam the console. */
+ if (state != new_state)
+ CPRINTS("Servo %s -> %s",
+ device_state_name(state), device_state_name(new_state));
+#endif
+ state = new_state;
+}
+
+/**
+ * Check if we can tell servo is connected.
+ *
+ * @return 1 if we can tell if servo is connected, 0 if we can't tell.
+ */
+static int servo_detectable(void)
+{
+ /*
+ * If we are driving the UART transmit line to the EC, then we can't
+ * check to see if servo is also doing so.
+ *
+ * We also need to check if we're bit-banging the EC UART, because in
+ * that case, the UART transmit line is directly controlled as a GPIO
+ * and can be high even if UART TX is disconnected.
+ */
+ return !(uart_tx_is_connected(UART_EC) ||
+ uart_bitbang_is_enabled(UART_EC));
+}
+
+/**
+ * Handle servo being disconnected
+ */
+static void servo_disconnect(void)
+{
+ if (!servo_is_connected())
+ return;
+
+ CPRINTS("Servo disconnect");
+ set_state(DEVICE_STATE_DISCONNECTED);
+ ccd_update_state();
+}
+
+/**
+ * Handle servo being connected.
+ *
+ * This can be called directly by servo_detect(), or as a deferred function.
+ * Both are in the HOOK task, so can't preempt each other.
+ */
+static void servo_connect(void)
+{
+ /*
+ * If we were debouncing disconnect, go back to connected. We never
+ * finished disconnecting, so nothing else is necessary.
+ */
+ if (state == DEVICE_STATE_DEBOUNCING)
+ set_state(DEVICE_STATE_CONNECTED);
+
+ /* If we're already connected, nothing else needs to be done */
+ if (state == DEVICE_STATE_CONNECTED)
+ return;
+
+ /*
+ * If we're still here, this is a real transition from a disconnected
+ * state, so we need to configure ports.
+ */
+ CPRINTS("Servo connect");
+ set_state(DEVICE_STATE_CONNECTED);
+ ccd_update_state();
+}
+DECLARE_DEFERRED(servo_connect);
+
+/**
+ * Servo state machine
+ */
+static void servo_detect(void)
+{
+ /* Disable interrupts if we had them on for debouncing */
+ gpio_disable_interrupt(GPIO_DETECT_SERVO);
+
+ /* If we're driving EC UART TX, we can't detect servo */
+ if (!servo_detectable()) {
+ /* We're driving one port; might as well drive them all */
+ servo_disconnect();
+
+ set_state(DEVICE_STATE_UNDETECTABLE);
+ return;
+ }
+
+ /* Handle detecting servo */
+ if (gpio_get_level(GPIO_DETECT_SERVO)) {
+ servo_connect();
+ return;
+ }
+
+ /*
+ * If servo has become detectable but wasn't detected above, assume
+ * it's disconnected.
+ *
+ * We know we were driving EC UART TX, so we want to give priority to
+ * our ability to drive it again. If we went to the debouncing state
+ * here, then we'd need to wait a second before we could drive it.
+ *
+ * This is similar to how if servo was driving EC UART TX, we go to the
+ * debouncing state below, because we want to give priority to servo
+ * being able to drive it again.
+ */
+ if (state == DEVICE_STATE_UNDETECTABLE)
+ set_state(DEVICE_STATE_DISCONNECTED);
+
+ /* Servo 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 ||
+ state == DEVICE_STATE_INIT_DEBOUNCING) {
+ servo_disconnect();
+ return;
+ }
+
+ /*
+ * Otherwise, we were connected or initializing, and we're not sure if
+ * we're now disconnected or just sending a 0-bit. So start
+ * debouncing.
+ *
+ * During debouncing, servo_is_connected() will still return true, so
+ * that if both CCD and servo cables are connected, we won't start
+ * driving EC UART TX and become unable to determine the servo connect
+ * state.
+ */
+ if (state == DEVICE_STATE_INIT)
+ set_state(DEVICE_STATE_INIT_DEBOUNCING);
+ else
+ set_state(DEVICE_STATE_DEBOUNCING);
+ gpio_enable_interrupt(GPIO_DETECT_SERVO);
+}
+/*
+ * Do this at slightly elevated priority so it runs before rdd_check_pin() and
+ * ec_detect(). This increases the odds that we'll detect servo before
+ * detecting the EC. If ec_detect() ran first, it could turn on TX to the EC
+ * UART before we had a chance to detect servo. This is still a little bit of
+ * a race condition.
+ */
+DECLARE_HOOK(HOOK_SECOND, servo_detect, HOOK_PRIO_DEFAULT - 1);
+
+/**
+ * Interrupt handler for servo detect asserted
+ */
+void servo_detect_asserted(enum gpio_signal signal)
+{
+ gpio_disable_interrupt(GPIO_DETECT_SERVO);
+
+ /*
+ * If this interrupt is because servo is actually detectable (vs. we're
+ * driving the detect pin now), queue a transition back to connected.
+ */
+ if (servo_detectable())
+ hook_call_deferred(&servo_connect_data, 0);
+}