summaryrefslogtreecommitdiff
path: root/common/usbc/usb_pd_dp_ufp.c
diff options
context:
space:
mode:
Diffstat (limited to 'common/usbc/usb_pd_dp_ufp.c')
-rw-r--r--common/usbc/usb_pd_dp_ufp.c448
1 files changed, 0 insertions, 448 deletions
diff --git a/common/usbc/usb_pd_dp_ufp.c b/common/usbc/usb_pd_dp_ufp.c
deleted file mode 100644
index 0009b5c710..0000000000
--- a/common/usbc/usb_pd_dp_ufp.c
+++ /dev/null
@@ -1,448 +0,0 @@
-/* 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;
- int send_enable;
- 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 int alt_dp_mode_opos[CONFIG_USB_PD_PORT_MAX_COUNT];
-
-void pd_ufp_set_dp_opos(int port, int opos)
-{
- alt_dp_mode_opos[port] = opos;
-}
-
-int pd_ufp_get_dp_opos(int port)
-{
- return alt_dp_mode_opos[port];
-}
-
-void pd_ufp_enable_hpd_send(int port)
-{
- /*
- * This control is used ensure that a DP_ATTENTION message is not sent
- * to the DFP-D before a DP_CONFIG messaage has been received. This
- * control is not strictly required by the spec, but some port partners
- * will get confused if DP_ATTENTION is sent prior to DP_CONFIG.
- */
- hpd.send_enable = 1;
-}
-
-static void hpd_to_dp_attention(void)
-{
- int port = hpd_config.port;
- int evt_index = hpd.count - 1;
- uint32_t vdm[2];
- uint32_t svdm_header;
- enum hpd_event evt;
- int opos = pd_ufp_get_dp_opos(port);
-
- if (!opos)
- return;
-
- /* Get the next hpd event from the queue */
- evt = hpd.queue[evt_index];
- /* Save timestamp of when most recent DP attention message was sent */
- hpd.last_send_ts = get_time().val;
-
- /*
- * Construct DP Attention message. This consists of the VDM header and
- * the DP_STATUS VDO.
- */
- svdm_header = VDO_SVDM_VERS(pd_get_vdo_ver(port, TCPCI_MSG_SOP)) |
- VDO_OPOS(opos) | CMD_ATTENTION;
- vdm[0] = VDO(USB_SID_DISPLAYPORT, 1, svdm_header);
-
- vdm[1] = VDO_DP_STATUS((evt == hpd_irq), /* IRQ_HPD */
- (evt != hpd_low), /* HPD_HI|LOW */
- 0, /* request exit DP */
- 0, /* request exit USB */
- dock_get_mf_preference(), /* MF pref */
- 1, /* enabled */
- 0, /* power low */
- 0x2);
-
- /* Send request to DPM to send an attention VDM */
- pd_request_vdm_attention(port, vdm, ARRAY_SIZE(vdm));
-
- /* 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 at least one hpd event is pending in the queue, send
- * a DP_ATTENTION message if a DP_CONFIG message has been
- * received and have passed the minimum spacing interval.
- */
- if (hpd.send_enable &&
- ((get_time().val - hpd.last_send_ts) >
- HPD_T_MIN_DP_ATTEN)) {
- /* Generate DP_ATTENTION event pending in queue */
- hpd_to_dp_attention();
- } else {
- uint32_t callback_us;
-
- /*
- * 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.
- *
- * This wait is also used to prevent a DP_ATTENTION
- * message from being sent before at least one DP_CONFIG
- * message has been received. If DP_ATTENTION messages
- * need to be delayed for this reason, then just wait
- * the minimum time spacing.
- */
- callback_us = HPD_T_MIN_DP_ATTEN -
- (get_time().val - hpd.last_send_ts);
- if (callback_us <= 0 ||
- callback_us > HPD_T_MIN_DP_ATTEN)
- callback_us = HPD_T_MIN_DP_ATTEN;
- hook_call_deferred(&manage_hpd_data, callback_us);
- }
- }
-
- /*
- * 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;
- hpd.send_enable = 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);
-}