diff options
author | Scott Collyer <scollyer@google.com> | 2020-12-18 04:45:20 -0800 |
---|---|---|
committer | Commit Bot <commit-bot@chromium.org> | 2021-04-01 17:59:34 +0000 |
commit | 6c9e2e410a3dddf65b4616d549ecb8abc25ddd6c (patch) | |
tree | 543cac63cec6fa058931f1bd1795325befbea5b4 | |
parent | bfb93637fbc5237a3119bee05882aedbaa94a2b0 (diff) | |
download | chrome-ec-6c9e2e410a3dddf65b4616d549ecb8abc25ddd6c.tar.gz |
TCPMv2: Add common hpd->dp_atten converter
This CL adds a new file for UFP_D ports. The inclusion of this file is
controlled via a new config option CONFIG_USB_PD_ALT_MODE_UFP. Note
that this is not intended to be used with TCPMv1 UFP_D legacy devices.
The initial version of this file includes the hpd to usb pd
DP_ATTENTION message converter. The implementation follows the spec in
terms of both states and hpd event queueing rules.
BUG=b:175660576
BRANCH=None
TEST=Tested further down the CL stack. But verified that this CL fixes
the issue where too many hpd_irq messages are sent with gingerbread
which was causing a port part reset for chromebook ports.
Signed-off-by: Scott Collyer <scollyer@google.com>
Change-Id: I5c7f4a3d226eb7b33553c90f04c69c3d80bd8672
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/ec/+/2598028
Reviewed-by: Diana Z <dzigterman@chromium.org>
Commit-Queue: Scott Collyer <scollyer@chromium.org>
Tested-by: Scott Collyer <scollyer@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/ec/+/2799915
Tested-by: Abe Levkoy <alevkoy@chromium.org>
Reviewed-by: Abe Levkoy <alevkoy@chromium.org>
Commit-Queue: Abe Levkoy <alevkoy@chromium.org>
-rw-r--r-- | common/usbc/build.mk | 3 | ||||
-rw-r--r-- | common/usbc/usb_pd_dp_ufp.c | 376 | ||||
-rw-r--r-- | include/config.h | 7 | ||||
-rw-r--r-- | include/usb_pd_dp_ufp.h | 35 |
4 files changed, 419 insertions, 2 deletions
diff --git a/common/usbc/build.mk b/common/usbc/build.mk index d8a97f31c0..82d9928f8e 100644 --- a/common/usbc/build.mk +++ b/common/usbc/build.mk @@ -39,6 +39,9 @@ endif # CONFIG_USB_PE_SM # Retimer firmware update all-obj-$(CONFIG_USBC_RETIMER_FW_UPDATE)+=$(_usbc_dir)usb_retimer_fw_update.o + +# ALT-DP mode for UFP ports +all-obj-$(CONFIG_USB_PD_ALT_MODE_UFP_DP)+=$(_usbc_dir)usb_pd_dp_ufp.o endif # CONFIG_USB_PD_TCPMV2 # For testing diff --git a/common/usbc/usb_pd_dp_ufp.c b/common/usbc/usb_pd_dp_ufp.c new file mode 100644 index 0000000000..e9da5b55e0 --- /dev/null +++ b/common/usbc/usb_pd_dp_ufp.c @@ -0,0 +1,376 @@ +/* Copyright 2021 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. + */ + +/* + * Functions required for UFP_D operation + */ + +#include "console.h" +#include "gpio.h" +#include "hooks.h" +#include "system.h" +#include "task.h" +#include "usb_pd.h" +#include "usb_pd_dp_ufp.h" + + +#define CPRINTF(format, args...) cprintf(CC_USBPD, format, ## args) +#define CPRINTS(format, args...) cprints(CC_USBPD, format, ## args) + +enum hpd_state { + LOW_WAIT, + HIGH_CHECK, + HIGH_WAIT, + LOW_CHECK, + IRQ_CHECK, +}; + +#define EDGE_QUEUE_DEPTH BIT(3) +#define EDGE_QUEUE_MASK (EDGE_QUEUE_DEPTH - 1) +#define HPD_QUEUE_DEPTH BIT(2) +#define HPD_QUEUE_MASK (HPD_QUEUE_DEPTH - 1) +#define HPD_T_IRQ_MIN_PULSE 250 +#define HPD_T_IRQ_MAX_PULSE (2 * MSEC) +#define HPD_T_MIN_DP_ATTEN (10 * MSEC) + +struct hpd_mark { + int level; + uint64_t ts; +}; + +struct hpd_edge { + int overflow; + uint32_t head; + uint32_t tail; + struct hpd_mark buffer[EDGE_QUEUE_DEPTH]; +}; + +struct hpd_info { + enum hpd_state state; + int count; + uint64_t timer; + uint64_t last_send_ts; + enum hpd_event queue[HPD_QUEUE_DEPTH]; + struct hpd_edge edges; +}; + +static struct hpd_info hpd; +static struct mutex hpd_mutex; + + +static void hpd_to_dp_attention(void) +{ + int evt_index = hpd.count - 1; + + hpd.last_send_ts = get_time().val; + pd_send_hpd(hpd_config.port, hpd.queue[evt_index]); + + /* If there are still events, need to shift the buffer */ + if (--hpd.count) { + int i; + + for (i = 0; i < hpd.count; i++) + hpd.queue[i] = hpd.queue[i + 1]; + } +} + +static void hpd_queue_event(enum hpd_event evt) +{ + /* + * HPD events are put into a queue. However, this queue is not a typical + * FIFO queue. Instead there are special rules based on which type of + * event is being added. + * HPD_LOW -> always resets the queue and must be in slot 0 + * HPD_HIGH -> must follow a HPD_LOW, so can only be in slot 0 or + * slot 1. + * HPD_IRQ -> There shall never be more than 2 HPD_IRQ events + * stored in the queue and HPD_IRQ must follow HPD_HIGH + * + * Worst case for queueing HPD events is 4 events in the queue: + * 0 - HPD_LOW + * 1 - HPD_HIGH + * 2 - HPD_IRQ + * 3 - HPD_IRQ + * + * The above rules mean that HPD_LOW and HPD_HIGH events can always be + * added to the queue since high must follow low and a low event resets + * the queue. HPD_IRQ events are checked to make sure that they don't + * overflow the queue and to ensure that no more than 2 hpd_irq events + * are kept in the queue. + */ + if (evt == hpd_irq) { + if ((hpd.count >= HPD_QUEUE_DEPTH) || ((hpd.count >= 2) && + (hpd.queue[hpd.count - 2] == hpd_irq))) { + CPRINTS("hpd: discard hpd: count - %d", + hpd.count); + return; + } + } + + if (evt == hpd_low) { + hpd.count = 0; + } + + /* Add event to the queue */ + hpd.queue[hpd.count++] = evt; +} + +static void hpd_to_pd_converter(int level, uint64_t ts) +{ + /* + * HPD edges are marked in the irq routine. The converter state machine + * runs in the hooks task and so there will be some delay between when + * the edge was captured and when that edge is processed here in the + * state machine. This means that the delitch timer (250 uSec) may have + * already expired or is about to expire. + * + * If transitioning to timing dependent state, need to ensure the state + * machine is executed again. All timers are relative to the ts value + * passed into this routine. The timestamps passed into this routine + * are either the values latched in the irq routine, or the current + * time latched by the calling function. From the perspective of the + * state machine, ts represents the current time. + * + * Note that all hpd queue events are contingent on detecting edges + * on the incoming hpd gpio signal. The hpd->dp attention converter is + * enabled/disabled as part of the svdm dp enter/exit response handler + * functions. When the converter is disabled, gpio interrupts for the + * hpd gpio signal are disabled so it will never execute, unless the + * converter is enabled, and the converter is only enabled when the + * UFP-D is actively in ALT-DP mode. + */ + switch (hpd.state) { + case LOW_WAIT: + /* + * In this state only expected event is a level change from low + * to high. + */ + if (level) { + hpd.state = HIGH_CHECK; + hpd.timer = ts + HPD_T_IRQ_MIN_PULSE; + } + break; + case HIGH_CHECK: + /* + * In this state if level is high and deglitch timer is + * exceeded, then state advances to HIGH_WAIT, otherwise return + * to LOW_WAIT state. + */ + if (!level || (ts <= hpd.timer)) { + hpd.state = LOW_WAIT; + } else { + hpd.state = HIGH_WAIT; + hpd_queue_event(hpd_high); + } + break; + case HIGH_WAIT: + /* + * In this state, only expected event is a level change from + * high to low. If current level is low, then advance to + * LOW_CHECK for deglitch checking. + */ + if (!level) { + hpd.state = LOW_CHECK; + hpd.timer = ts + HPD_T_IRQ_MIN_PULSE; + } + break; + case LOW_CHECK: + /* + * This state is used to deglitch high->low level + * change. However, due to processing latency, it's possible to + * detect hpd_irq event if level is high and low pulse width was + * valid. + */ + if (!level) { + /* Still low, now wait for IRQ or LOW determination */ + hpd.timer = ts + (HPD_T_IRQ_MAX_PULSE - + HPD_T_IRQ_MIN_PULSE); + hpd.state = IRQ_CHECK; + + } else { + uint64_t irq_ts = hpd.timer + HPD_T_IRQ_MAX_PULSE - + HPD_T_IRQ_MIN_PULSE; + /* + * If hpd is high now, this must have been an edge + * event, but still need to determine if the pulse width + * is longer than hpd_irq min pulse width. State will + * advance to HIGH_WAIT, but if pulse width is < 2 msec, + * must send hpd_irq event. + */ + if ((ts >= hpd.timer) && (ts <= irq_ts)) { + /* hpd irq detected */ + hpd_queue_event(hpd_irq); + } + hpd.state = HIGH_WAIT; + } + break; + case IRQ_CHECK: + /* + * In this state deglitch time has already passed. If current + * level is low and hpd_irq timer has expired, then go to + * LOW_WAIT as hpd_low event has been detected. If level is high + * and low pulse is < hpd_irq, hpd_irq event has been detected. + */ + if (level) { + hpd.state = HIGH_WAIT; + if (ts <= hpd.timer) { + hpd_queue_event(hpd_irq); + } + } else if (ts > hpd.timer) { + hpd.state = LOW_WAIT; + hpd_queue_event(hpd_low); + } + break; + } +} + +static void manage_hpd(void); +DECLARE_DEFERRED(manage_hpd); + +static void manage_hpd(void) +{ + int level; + uint64_t ts = get_time().val; + uint32_t num_hpd_events = (hpd.edges.head - hpd.edges.tail) & + EDGE_QUEUE_MASK; + + /* + * HPD edges are detected via GPIO interrupts. The ISR routine adds edge + * info to a queue and scheudles this routine. If this routine is called + * without a new edge detected, then it is being called due to a timer + * event. + */ + + /* First check to see overflow condition has occurred */ + if (hpd.edges.overflow) { + /* Disable hpd interrupts */ + usb_pd_hpd_converter_enable(0); + /* Re-enable hpd converter */ + usb_pd_hpd_converter_enable(1); + } + + if (num_hpd_events) { + while(num_hpd_events-- > 0) { + int idx = hpd.edges.tail; + + level = hpd.edges.buffer[idx].level; + ts = hpd.edges.buffer[idx].ts; + + hpd_to_pd_converter(level, ts); + hpd.edges.tail = (hpd.edges.tail + 1) & EDGE_QUEUE_MASK; + } + } else { + /* no new edge event, so get current time and level */ + level = gpio_get_level(hpd_config.signal); + ts = get_time().val; + hpd_to_pd_converter(level, ts); + } + + /* + * If min time spacing requirement is exceeded and a hpd_event is + * queued, then send DP_ATTENTION message. + */ + if (hpd.count > 0) { + if ((get_time().val - hpd.last_send_ts) > HPD_T_MIN_DP_ATTEN) { + /* Generate DP_ATTENTION event pending in queue */ + hpd_to_dp_attention(); + } else { + /* + * Need to wait until until min spacing requirement of + * DP attention messages. Set callback time to the min + * value required. This callback time could be changed + * based on hpd interrupts. + */ + hook_call_deferred(&manage_hpd_data, + HPD_T_MIN_DP_ATTEN - + (get_time().val - hpd.last_send_ts)); + } + } + + /* + * Because of the delay between gpio edge irq, and when those edge + * events are processed here, all timers must be done relative to the + * timing marker stored in the hpd edge queue. If the state machine + * required a new timer, then hpd.timer will be advanced relative to the + * ts that was passed into the state machine. + * + * If the deglitch timer is active, then it can likely already have been + * expired when the edge gets processed. So if the timer is active the + * deferred callback must be requested. + *. + */ + if (hpd.timer > ts) { + uint64_t callback_us = 0; + uint64_t now = get_time().val; + + /* If timer is in the future, adjust the callback timer */ + if (now < hpd.timer) + callback_us = (hpd.timer - now) & 0xffffffff; + + hook_call_deferred(&manage_hpd_data, callback_us); + } +} + +void usb_pd_hpd_converter_enable(int enable) +{ + /* + * The hpd converter should be enabled as part of the UFP-D enter mode + * response function. Likewise, the converter should be disabled by the + * exit mode function. In addition, the coverter may get disabled so + * that it can be reset in the case that the input gpio edges queue + * overflows. A muxtex must be used here since this function may be + * called from the PD task (enter/exit response mode functions) or from + * the hpd event handler state machine (hook task). + */ + mutex_lock(&hpd_mutex); + + if (enable) { + gpio_disable_interrupt(hpd_config.signal); + /* Reset HPD event queue */ + hpd.state = LOW_WAIT; + hpd.count = 0; + hpd.timer = 0; + hpd.last_send_ts = 0; + + /* Reset hpd signal edges queue */ + hpd.edges.head = 0; + hpd.edges.tail = 0; + hpd.edges.overflow = 0; + + /* If signal is high, need to ensure state machine executes */ + if (gpio_get_level(hpd_config.signal)) + hook_call_deferred(&manage_hpd_data, 0); + + /* Enable hpd edge detection */ + gpio_enable_interrupt(hpd_config.signal); + } else { + gpio_disable_interrupt(hpd_config.signal); + hook_call_deferred(&manage_hpd_data, -1); + } + + mutex_unlock(&hpd_mutex); +} + +void usb_pd_hpd_edge_event(int signal) +{ + int next_head = (hpd.edges.head + 1) & EDGE_QUEUE_MASK; + struct hpd_mark mark; + + /* Get current timestamp and level */ + mark.ts = get_time().val; + mark.level = gpio_get_level(hpd_config.signal); + + /* Add this edge to the buffer if there is space */ + if (next_head != hpd.edges.tail) { + hpd.edges.buffer[hpd.edges.head].ts = mark.ts; + hpd.edges.buffer[hpd.edges.head].level = mark.level; + hpd.edges.head = next_head; + } else { + /* Edge queue is overflowing, need to reset the converter */ + hpd.edges.overflow = 1; + } + /* Schedule HPD state machine to run ASAP */ + hook_call_deferred(&manage_hpd_data, 0); +} diff --git a/include/config.h b/include/config.h index 2f171dec22..446eee62b4 100644 --- a/include/config.h +++ b/include/config.h @@ -4031,14 +4031,17 @@ /* Support for USB PD alternate mode of Downward Facing Port */ #undef CONFIG_USB_PD_ALT_MODE_DFP +/* Support for USB PD alternate mode of Upward Facing Port */ +#undef CONFIG_USB_PD_ALT_MODE_UFP + /* * Do not enter USB PD alternate modes or USB4 automatically. Wait for the AP to * direct the EC to enter a mode. This requires AP software support. */ #undef CONFIG_USB_PD_REQUIRE_AP_MODE_ENTRY -/* Support for USB PD alternate mode of Upward Facing Port */ -#undef CONFIG_USB_PD_ALT_MODE_UFP +/* Supports DP as UFP-D and requires HPD to DP_ATTEN converter */ +#undef CONFIG_USB_PD_ALT_MODE_UFP_DP /* HPD is sent to the GPU from the EC via a GPIO */ #undef CONFIG_USB_PD_DP_HPD_GPIO diff --git a/include/usb_pd_dp_ufp.h b/include/usb_pd_dp_ufp.h new file mode 100644 index 0000000000..64728d948e --- /dev/null +++ b/include/usb_pd_dp_ufp.h @@ -0,0 +1,35 @@ +/* Copyright 2021 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. + */ + +/* + * Common functions for UFP-D devices. + */ + +#ifndef __CROS_EC_USB_PD_DP_UFP_H +#define __CROS_EC_USB_PD_DP_UFP_H + +struct hpd_to_pd_config_t { + int port; + enum gpio_signal signal; +}; + +extern const struct hpd_to_pd_config_t hpd_config; +/* + * Function used to handle hpd gpio interrupts. + * + * @param signal -> gpio signal associated with hpd interrupt + */ +void usb_pd_hpd_edge_event(int signal); + +/* + * Function used to enable/disable the hpd->dp attention protocol converter + * - called with enable when enter mode command is processed. + * - called with disable when exit mode command is processed. + * + * @param enable -> converter on/off + */ +void usb_pd_hpd_converter_enable(int enable); + +#endif /* __CROS_EC_USB_PD_DP_UFP_H */ |