diff options
Diffstat (limited to 'common/usbc/usb_pd_dp_ufp.c')
-rw-r--r-- | common/usbc/usb_pd_dp_ufp.c | 448 |
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); -} |