summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlec Berg <alecaberg@chromium.org>2014-10-28 14:26:51 -0700
committerchrome-internal-fetch <chrome-internal-fetch@google.com>2014-12-17 03:27:17 +0000
commit6c980a4dbf2e37b83e868c0eb12774190ea8c94d (patch)
treeda414fe9f2f5609525ba950d5a05c54accfee475
parent3b101e56a9e058aee19933f9adfae1c8bb2dbedd (diff)
downloadchrome-ec-6c980a4dbf2e37b83e868c0eb12774190ea8c94d.tar.gz
pd: check for collisions before transmitting
Added check for collision just before transmitting on CC line. To check for collision, RX monitoring is left on all the time (except when in the act of receiving or transmitting, or in between receiving and sending a goodCRC), and a simple check for RX transmission started is used to see if the CC line is idle or not. RX monitoring is also changed to only trigger on 3 edges within 20us, as per the PD spec. When a collision is detected by seeing that CC is not idle, the transmitting packet is dropped. BUG=chrome-os-partner:30135 BRANCH=samus TEST=load onto samus and zinger. make sure we negotiate and make sure custom VDMs succeed. enabled pings and made sure we stay alive with pings for a few min. Also added code to pd_rx_handler to toggle a test point on EVT board to verify the timing of when we get RX interrupts: Change-Id: I22d172163319437d3d901e019eda79d4d592f6b8 Signed-off-by: Alec Berg <alecaberg@chromium.org> Reviewed-on: https://chromium-review.googlesource.com/226118 Reviewed-by: Vincent Palatin <vpalatin@chromium.org>
-rw-r--r--board/zinger/usb_pd_policy.c10
-rw-r--r--chip/host/usb_pd_phy.c10
-rw-r--r--chip/stm32/usb_pd_phy.c67
-rw-r--r--common/usb_pd_protocol.c91
-rw-r--r--include/usb_pd.h10
-rw-r--r--test/usb_pd.c1
-rw-r--r--test/usb_pd_test_util.h1
7 files changed, 157 insertions, 33 deletions
diff --git a/board/zinger/usb_pd_policy.c b/board/zinger/usb_pd_policy.c
index c1b24b4b08..22274f1569 100644
--- a/board/zinger/usb_pd_policy.c
+++ b/board/zinger/usb_pd_policy.c
@@ -144,7 +144,7 @@ static void discharge_voltage(int target_volt)
discharge_deadline.val = get_time().val + DISCHARGE_TIMEOUT;
/* Monitor VBUS voltage */
target_volt -= DISCHARGE_OVERSHOOT_MV;
- disable_sleep(SLEEP_MASK_USB_PD);
+ disable_sleep(SLEEP_MASK_USB_PWR);
adc_enable_watchdog(ADC_CH_V_SENSE, 0xFFF, target_volt);
}
@@ -304,6 +304,10 @@ int pd_board_checks(void)
}
#endif
+ /* if it's been a while since last RX edge, then allow deep sleep */
+ if (get_time_since_last_edge(0) > PD_RX_TRANSITION_WINDOW)
+ enable_sleep(SLEEP_MASK_USB_PD);
+
vbus_volt = adc_read_channel(ADC_CH_V_SENSE);
vbus_amp = adc_read_channel(ADC_CH_A_SENSE);
@@ -340,9 +344,9 @@ int pd_board_checks(void)
*/
if (vbus_amp < SINK_IDLE_CURRENT && !discharge_is_enabled())
/* override the PD state machine sleep mask */
- enable_sleep(SLEEP_MASK_USB_PD);
+ enable_sleep(SLEEP_MASK_USB_PWR);
else if (vbus_amp > SINK_IDLE_CURRENT)
- disable_sleep(SLEEP_MASK_USB_PD);
+ disable_sleep(SLEEP_MASK_USB_PWR);
/*
* Set the voltage index to use for checking OVP. During a down step
diff --git a/chip/host/usb_pd_phy.c b/chip/host/usb_pd_phy.c
index 1cb770bf8d..bd75bd2d6b 100644
--- a/chip/host/usb_pd_phy.c
+++ b/chip/host/usb_pd_phy.c
@@ -76,6 +76,12 @@ void pd_test_rx_msg_append_eop(int port)
pd_test_rx_msg_append_kcode(port, PD_EOP);
}
+void pd_test_rx_msg_append_last_edge(int port)
+{
+ /* end with 1, 1, 0 similar to pd_write_last_edge() */
+ pd_test_rx_msg_append_bits(port, 3, 6);
+}
+
void pd_test_rx_msg_append_4b(int port, uint8_t val)
{
pd_test_rx_msg_append_bits(port, enc4b5b[val & 0xF], 5);
@@ -224,7 +230,7 @@ void pd_tx_set_circular_mode(int port)
/* Not implemented */
}
-void pd_start_tx(int port, int polarity, int bit_len)
+int pd_start_tx(int port, int polarity, int bit_len)
{
ASSERT(pd_phy[port].hw_init_done);
pd_phy[port].has_msg = 0;
@@ -237,6 +243,8 @@ void pd_start_tx(int port, int polarity, int bit_len)
*/
task_wake(TASK_ID_TEST_RUNNER);
task_wait_event(-1);
+
+ return bit_len;
}
void pd_tx_done(int port, int polarity)
diff --git a/chip/stm32/usb_pd_phy.c b/chip/stm32/usb_pd_phy.c
index 7079709e99..55c465986d 100644
--- a/chip/stm32/usb_pd_phy.c
+++ b/chip/stm32/usb_pd_phy.c
@@ -13,6 +13,7 @@
#include "hwtimer.h"
#include "hooks.h"
#include "registers.h"
+#include "system.h"
#include "task.h"
#include "timer.h"
#include "util.h"
@@ -71,6 +72,10 @@ static struct pd_physical {
timer_ctlr_t *tim_rx;
} pd_phy[PD_PORT_COUNT];
+/* keep track of RX edge timing in order to trigger receive */
+static timestamp_t rx_edge_ts[PD_PORT_COUNT][PD_RX_TRANSITION_COUNT];
+static int rx_edge_ts_idx[PD_PORT_COUNT];
+
void pd_init_dequeue(int port)
{
/* preamble ends with 1 */
@@ -295,10 +300,17 @@ void pd_tx_set_circular_mode(int port)
pd_phy[port].dma_tx_option.flags |= STM32_DMA_CCR_CIRC;
}
-void pd_start_tx(int port, int polarity, int bit_len)
+int pd_start_tx(int port, int polarity, int bit_len)
{
stm32_dma_chan_t *tx = dma_get_channel(DMAC_SPI_TX(port));
+ /* disable RX detection interrupt */
+ pd_rx_disable_monitoring(port);
+
+ /* Check that we are not receiving a frame to avoid collisions */
+ if (pd_rx_started(port))
+ return -5;
+
/* Initialize spi peripheral to prepare for transmission. */
pd_tx_spi_init(port);
@@ -315,9 +327,6 @@ void pd_start_tx(int port, int polarity, int bit_len)
/* Flush data in write buffer so that DMA can get the latest data */
asm volatile("dmb;");
- /* disable RX detection interrupt */
- pd_rx_disable_monitoring(port);
-
/* Kick off the DMA to send the data */
dma_clear_isr(DMAC_SPI_TX(port));
#ifdef CONFIG_COMMON_RUNTIME
@@ -338,6 +347,8 @@ void pd_start_tx(int port, int polarity, int bit_len)
/* Start counting at 300Khz*/
pd_phy[port].tim_tx->cr1 |= 1;
#endif
+
+ return bit_len;
}
void pd_tx_done(int port, int polarity)
@@ -436,23 +447,57 @@ void pd_rx_disable_monitoring(int port)
STM32_EXTI_PR = EXTI_COMP_MASK(port);
}
+uint64_t get_time_since_last_edge(int port)
+{
+ int prev_idx = (rx_edge_ts_idx[port] == 0) ?
+ PD_RX_TRANSITION_COUNT - 1 :
+ rx_edge_ts_idx[port] - 1;
+ return get_time().val - rx_edge_ts[port][prev_idx].val;
+}
+
/* detect an edge on the PD RX pin */
void pd_rx_handler(void)
{
int pending, i;
+ int next_idx;
pending = STM32_EXTI_PR;
for (i = 0; i < PD_PORT_COUNT; i++) {
if (pending & EXTI_COMP_MASK(i)) {
- /* start sampling */
- pd_rx_start(i);
+ rx_edge_ts[i][rx_edge_ts_idx[i]].val = get_time().val;
+ next_idx = (rx_edge_ts_idx[i] ==
+ PD_RX_TRANSITION_COUNT - 1) ?
+ 0 : rx_edge_ts_idx[i] + 1;
+
/*
- * ignore the comparator IRQ until we are done with
- * current message
+ * Do not deep sleep while waiting for more edges. For
+ * most boards, sleep is already disabled due to being
+ * in PD connected state, but other boards can sleep
+ * while connected.
*/
- pd_rx_disable_monitoring(i);
- /* trigger the analysis in the task */
- pd_rx_event(i);
+ disable_sleep(SLEEP_MASK_USB_PD);
+
+ /*
+ * If we have seen enough edges in a certain amount of
+ * time, then trigger RX start.
+ */
+ if ((rx_edge_ts[i][rx_edge_ts_idx[i]].val -
+ rx_edge_ts[i][next_idx].val)
+ < PD_RX_TRANSITION_WINDOW) {
+ /* start sampling */
+ pd_rx_start(i);
+ /*
+ * ignore the comparator IRQ until we are done
+ * with current message
+ */
+ pd_rx_disable_monitoring(i);
+ /* trigger the analysis in the task */
+ pd_rx_event(i);
+ } else {
+ /* do not trigger RX start, just clear int */
+ STM32_EXTI_PR = EXTI_COMP_MASK(i);
+ }
+ rx_edge_ts_idx[i] = next_idx;
}
}
}
diff --git a/common/usb_pd_protocol.c b/common/usb_pd_protocol.c
index b9722e3cfe..4513ed5087 100644
--- a/common/usb_pd_protocol.c
+++ b/common/usb_pd_protocol.c
@@ -243,6 +243,8 @@ static struct pd_protocol {
uint64_t timeout;
/* Time for source recovery after hard reset */
uint64_t src_recover;
+ /* Error sending message and message was dropped */
+ int8_t send_error;
/* last requested voltage PDO index */
int requested_idx;
@@ -423,13 +425,13 @@ int prepare_message(int port, uint16_t header, uint8_t cnt,
static int analyze_rx(int port, uint32_t *payload);
static void analyze_rx_bist(int port);
-void send_hard_reset(int port)
+int send_hard_reset(int port)
{
int off;
/* If PD communication is disabled, return */
if (!pd_comm_enabled)
- return;
+ return 0;
if (debug_level >= 1)
CPRINTF("Sending hard reset\n");
@@ -444,8 +446,14 @@ void send_hard_reset(int port)
/* Ensure that we have a final edge */
off = pd_write_last_edge(port, off);
/* Transmit the packet */
- pd_start_tx(port, pd[port].polarity, off);
+ if (pd_start_tx(port, pd[port].polarity, off) < 0) {
+ pd[port].send_error = -5;
+ return -5;
+ }
pd_tx_done(port, pd[port].polarity);
+ /* Keep RX monitoring on */
+ pd_rx_enable_monitoring(port);
+ return 0;
}
static int send_validate_message(int port, uint16_t header,
@@ -464,11 +472,21 @@ static int send_validate_message(int port, uint16_t header,
/* write the encoded packet in the transmission buffer */
bit_len = prepare_message(port, header, cnt, data);
/* Transmit the packet */
- pd_start_tx(port, pd[port].polarity, bit_len);
+ if (pd_start_tx(port, pd[port].polarity, bit_len) < 0) {
+ /*
+ * Collision detected, return immediately so we can
+ * respond to what we have received.
+ */
+ pd[port].send_error = -5;
+ return -5;
+ }
pd_tx_done(port, pd[port].polarity);
/*
- * If we failed the first try, enable interrupt and yield
- * to other tasks, so that we don't starve them.
+ * If this is the first attempt, leave RX monitoring off,
+ * and do a blocking read of the channel until timeout or
+ * packet received. If we failed the first try, enable
+ * interrupt and yield to other tasks, so that we don't
+ * starve them.
*/
if (r) {
pd_rx_enable_monitoring(port);
@@ -491,6 +509,8 @@ static int send_validate_message(int port, uint16_t header,
/* read the incoming packet if any */
head = analyze_rx(port, payload);
pd_rx_complete(port);
+ /* keep RX monitoring on to avoid collisions */
+ pd_rx_enable_monitoring(port);
if (head > 0) { /* we got a good packet, analyze it */
int type = PD_HEADER_TYPE(head);
int nb = PD_HEADER_CNT(head);
@@ -509,8 +529,8 @@ static int send_validate_message(int port, uint16_t header,
* the other side is trying to contact us,
* bail out immediatly so we can get the retry.
*/
+ pd[port].send_error = -4;
return -4;
- /* CPRINTF("ERR ACK/%d %04x\n", id, head); */
}
}
}
@@ -545,8 +565,13 @@ static void send_goodcrc(int port, int id)
if (!pd_comm_enabled)
return;
- pd_start_tx(port, pd[port].polarity, bit_len);
+ if (pd_start_tx(port, pd[port].polarity, bit_len) < 0) {
+ pd[port].send_error = -6;
+ return;
+ }
pd_tx_done(port, pd[port].polarity);
+ /* Keep RX monitoring on */
+ pd_rx_enable_monitoring(port);
}
static int send_source_cap(int port)
@@ -1068,6 +1093,9 @@ static void handle_request(int port, uint16_t head,
if (PD_HEADER_TYPE(head) != PD_CTRL_GOOD_CRC || cnt)
send_goodcrc(port, PD_HEADER_ID(head));
+ else
+ /* keep RX monitoring on to avoid collisions */
+ pd_rx_enable_monitoring(port);
/* dump received packet content (only dump ping at debug level 2) */
if ((debug_level == 1 && PD_HEADER_TYPE(head) != PD_CTRL_PING) ||
@@ -1171,7 +1199,7 @@ static int analyze_rx(int port, uint32_t *payload)
uint16_t header;
uint32_t pcrc, ccrc;
int p, cnt;
- /* uint32_t eop; */
+ uint32_t eop;
pd_init_dequeue(port);
@@ -1238,21 +1266,23 @@ static int analyze_rx(int port, uint32_t *payload)
goto packet_err;
}
- /* check End Of Packet */
- /* SKIP EOP for now
- bit = pd_dequeue_bits(port, bit, 5, &eop);
+ /*
+ * Check EOP. EOP is 5 bits, but last bit may not be able to
+ * be dequeued, depending on ending state of CC line, so stop
+ * at 4 bits (assumes last bit is 0).
+ */
+ bit = pd_dequeue_bits(port, bit, 4, &eop);
if (bit < 0 || eop != PD_EOP) {
msg = "EOP";
goto packet_err;
}
- */
return header;
packet_err:
if (debug_level >= 2)
pd_dump_packet(port, msg);
else
- CPRINTF("RX ERR (%d)\n", bit);
+ CPRINTF("RXERR %s\n", msg);
return bit;
}
@@ -1489,7 +1519,7 @@ void pd_task(void)
#endif /* CONFIG_USB_PD_DUAL_ROLE */
enum pd_states this_state;
timestamp_t now;
- int caps_count = 0, src_connected = 0;
+ int caps_count = 0, src_connected = 0, hard_reset_sent = 0;
/* Initialize TX pins and put them in Hi-Z */
pd_tx_init();
@@ -1525,6 +1555,23 @@ void pd_task(void)
/* notify the other side of the issue */
send_hard_reset(port);
}
+
+#ifdef CONFIG_USB_PD_DUAL_ROLE
+ /* Print error if did not transmit last message */
+ if (pd[port].send_error < 0) {
+ if (pd[port].send_error == -5)
+ /* Bus was not idle */
+ ccprintf("TX ERR NIDLE\n");
+ else if (pd[port].send_error == -4)
+ /* Incoming packet recvd instead of ack */
+ ccprintf("TX ERR ACK\n");
+ else if (pd[port].send_error == -6)
+ /* Incoming packet before we can send goodCRC */
+ ccprintf("TX ERR CRC\n");
+ pd[port].send_error = 0;
+ }
+#endif
+
/* wait for next event/packet or timeout expiration */
task_wait_event(timeout);
/* incoming packet ? */
@@ -2173,8 +2220,18 @@ void pd_task(void)
PD_STATE_SNK_HARD_RESET_RECOVER)
hard_reset_count++;
#endif
- if (pd[port].last_state != pd[port].task_state) {
- send_hard_reset(port);
+ if (pd[port].last_state != pd[port].task_state)
+ hard_reset_sent = 0;
+
+ /* try sending hard reset until it succeeds */
+ if (!hard_reset_sent) {
+ if (send_hard_reset(port) < 0) {
+ timeout = 10*MSEC;
+ return;
+ }
+
+ /* successfully sent hard reset */
+ hard_reset_sent = 1;
/*
* If we are source, delay before cutting power
* to allow sink time to get hard reset.
diff --git a/include/usb_pd.h b/include/usb_pd.h
index b7c41c7b93..31ef9fb296 100644
--- a/include/usb_pd.h
+++ b/include/usb_pd.h
@@ -130,6 +130,10 @@ enum pd_errors {
#define PD_T_SRC_TURN_ON (275*MSEC) /* 275ms */
#define PD_T_SAFE_0V (650*MSEC) /* 650ms */
+/* number of edges and time window to detect CC line is not idle */
+#define PD_RX_TRANSITION_COUNT 3
+#define PD_RX_TRANSITION_WINDOW 20 /* between 12us and 20us */
+
/* from USB Type-C Specification Table 5-1 */
#define PD_T_AME (1*SECOND) /* timeout from UFP attach to Alt Mode Entry */
@@ -1073,8 +1077,9 @@ void pd_set_clock(int port, int freq);
* @param port USB-C port number
* @param polarity plug polarity (0=CC1, 1=CC2).
* @param bit_len size of the packet in bits.
+ * @return length transmitted or negative if error
*/
-void pd_start_tx(int port, int polarity, int bit_len);
+int pd_start_tx(int port, int polarity, int bit_len);
/**
* Set PD TX DMA to use circular mode. Call this before pd_start_tx() to
@@ -1119,6 +1124,9 @@ void pd_rx_enable_monitoring(int port);
/* stop listening to the CC wire during transmissions */
void pd_rx_disable_monitoring(int port);
+/* get time since last RX edge interrupt */
+uint64_t get_time_since_last_edge(int port);
+
/**
* Deinitialize the hardware used for PD.
*
diff --git a/test/usb_pd.c b/test/usb_pd.c
index 2916d4eb94..9da5bfc85c 100644
--- a/test/usb_pd.c
+++ b/test/usb_pd.c
@@ -95,6 +95,7 @@ static void simulate_rx_msg(int port, uint16_t header, int cnt,
pd_test_rx_msg_append_word(port, crc32_result());
pd_test_rx_msg_append_eop(port);
+ pd_test_rx_msg_append_last_edge(port);
pd_simulate_rx(port);
}
diff --git a/test/usb_pd_test_util.h b/test/usb_pd_test_util.h
index 5be8e0984a..d54296ef17 100644
--- a/test/usb_pd_test_util.h
+++ b/test/usb_pd_test_util.h
@@ -14,6 +14,7 @@ void pd_test_rx_msg_append_bits(int port, uint32_t bits, int nb);
void pd_test_rx_msg_append_kcode(int port, uint8_t kcode);
void pd_test_rx_msg_append_sop(int port);
void pd_test_rx_msg_append_eop(int port);
+void pd_test_rx_msg_append_last_edge(int port);
void pd_test_rx_msg_append_4b(int port, uint8_t val);
void pd_test_rx_msg_append_short(int port, uint16_t val);
void pd_test_rx_msg_append_word(int port, uint32_t val);