| /* Copyright (c) 2014 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. |
| */ |
| |
| #include "adc.h" |
| #include "common.h" |
| #include "console.h" |
| #include "dma.h" |
| #include "gpio.h" |
| #include "hooks.h" |
| #include "hwtimer.h" |
| #include "injector.h" |
| #include "registers.h" |
| #include "system.h" |
| #include "task.h" |
| #include "timer.h" |
| #include "usb_pd.h" |
| #include "usb_pd_config.h" |
| #include "usb_pd_tcpm.h" |
| #include "util.h" |
| |
| /* PD packet text tracing state : TRACE_MODE_OFF/RAW/ON */ |
| int trace_mode; |
| |
| /* The FSM is waiting for the following command (0 == None) */ |
| uint8_t expected_cmd; |
| |
| static const char * const ctrl_msg_name[] = { |
| [0] = "RSVD-C0", |
| [PD_CTRL_GOOD_CRC] = "GOODCRC", |
| [PD_CTRL_GOTO_MIN] = "GOTOMIN", |
| [PD_CTRL_ACCEPT] = "ACCEPT", |
| [PD_CTRL_REJECT] = "REJECT", |
| [PD_CTRL_PING] = "PING", |
| [PD_CTRL_PS_RDY] = "PSRDY", |
| [PD_CTRL_GET_SOURCE_CAP] = "GSRCCAP", |
| [PD_CTRL_GET_SINK_CAP] = "GSNKCAP", |
| [PD_CTRL_DR_SWAP] = "DRSWAP", |
| [PD_CTRL_PR_SWAP] = "PRSWAP", |
| [PD_CTRL_VCONN_SWAP] = "VCONNSW", |
| [PD_CTRL_WAIT] = "WAIT", |
| [PD_CTRL_SOFT_RESET] = "SFT-RST", |
| [14] = "RSVD-C14", |
| [15] = "RSVD-C15", |
| }; |
| |
| static const char * const data_msg_name[] = { |
| [0] = "RSVD-D0", |
| [PD_DATA_SOURCE_CAP] = "SRCCAP", |
| [PD_DATA_REQUEST] = "REQUEST", |
| [PD_DATA_BIST] = "BIST", |
| [PD_DATA_SINK_CAP] = "SNKCAP", |
| /* 5-14 Reserved */ |
| [PD_DATA_VENDOR_DEF] = "VDM", |
| }; |
| |
| static const char * const svdm_cmd_name[] = { |
| [CMD_DISCOVER_IDENT] = "DISCID", |
| [CMD_DISCOVER_SVID] = "DISCSVID", |
| [CMD_DISCOVER_MODES] = "DISCMODE", |
| [CMD_ENTER_MODE] = "ENTER", |
| [CMD_EXIT_MODE] = "EXIT", |
| [CMD_ATTENTION] = "ATTN", |
| [CMD_DP_STATUS] = "DPSTAT", |
| [CMD_DP_CONFIG] = "DPCFG", |
| }; |
| |
| static const char * const svdm_cmdt_name[] = { |
| [CMDT_INIT] = "INI", |
| [CMDT_RSP_ACK] = "ACK", |
| [CMDT_RSP_NAK] = "NAK", |
| [CMDT_RSP_BUSY] = "BSY", |
| }; |
| |
| static void print_pdo(uint32_t word) |
| { |
| if ((word & PDO_TYPE_MASK) == PDO_TYPE_BATTERY) |
| ccprintf(" %dmV/%dmW", ((word>>10)&0x3ff)*50, |
| (word&0x3ff)*250); |
| else |
| ccprintf(" %dmV/%dmA", ((word>>10)&0x3ff)*50, |
| (word&0x3ff)*10); |
| } |
| |
| static void print_rdo(uint32_t word) |
| { |
| ccprintf("{%d} %08x", RDO_POS(word), word); |
| } |
| |
| static void print_vdo(int idx, uint32_t word) |
| { |
| if (idx == 0 && (word & VDO_SVDM_TYPE)) { |
| const char *cmd = svdm_cmd_name[PD_VDO_CMD(word)]; |
| const char *cmdt = svdm_cmdt_name[PD_VDO_CMDT(word)]; |
| uint16_t vid = PD_VDO_VID(word); |
| if (!cmd) |
| cmd = "????"; |
| ccprintf(" V%04x:%s,%s:%08x", vid, cmd, cmdt, word); |
| } else { |
| ccprintf(" %08x", word); |
| } |
| } |
| |
| static void print_packet(struct rx_header rx, uint32_t *payload) |
| { |
| int i; |
| uint16_t head = rx.head; |
| int cnt = PD_HEADER_CNT(head); |
| int typ = PD_HEADER_TYPE(head); |
| int id = PD_HEADER_ID(head); |
| const char *name; |
| const char *prole; |
| |
| switch (rx.packet_type) { |
| case TCPC_TX_SOP: |
| case TCPC_TX_SOP_PRIME: |
| case TCPC_TX_SOP_PRIME_PRIME: |
| break; |
| case PD_RX_ERR_INVAL: |
| ccprintf("%T TMOUT\n"); return; |
| case TCPC_TX_HARD_RESET: |
| ccprintf("%T HARD-RST\n"); return; |
| case TCPC_TX_CABLE_RESET: |
| ccprintf("%T CABLE-RST\n"); return; |
| default: |
| ccprintf("ERR %d\n", rx.packet_type); return; |
| } |
| |
| name = cnt ? data_msg_name[typ] : ctrl_msg_name[typ]; |
| prole = head & (PD_ROLE_SOURCE << 8) ? "SRC" : "SNK"; |
| ccprintf("%T %s/%d [%04x]%s", prole, id, head, name); |
| if (!cnt) { /* Control message : we are done */ |
| ccputs("\n"); |
| return; |
| } |
| /* Print payload for data message */ |
| for (i = 0; i < cnt; i++) |
| switch (typ) { |
| case PD_DATA_SOURCE_CAP: |
| case PD_DATA_SINK_CAP: |
| print_pdo(payload[i]); |
| break; |
| case PD_DATA_REQUEST: |
| print_rdo(payload[i]); |
| break; |
| case PD_DATA_BIST: |
| ccprintf("mode %d cnt %04x", payload[i] >> 28, |
| payload[i] & 0xffff); |
| break; |
| case PD_DATA_VENDOR_DEF: |
| print_vdo(i, payload[i]); |
| break; |
| default: |
| ccprintf(" %08x", payload[i]); |
| } |
| ccputs("\n"); |
| } |
| |
| /* keep track of RX edge timing in order to trigger receive */ |
| static timestamp_t rx_edge_ts[2][PD_RX_TRANSITION_COUNT]; |
| static int rx_edge_ts_idx[2]; |
| |
| void rx_event(void) |
| { |
| int pending, i; |
| int next_idx; |
| pending = STM32_EXTI_PR; |
| |
| /* Iterate over the 2 CC lines */ |
| for (i = 0; i < 2; i++) { |
| if (pending & (1 << (21 + 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; |
| |
| /* |
| * 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) { |
| /* acquire the message only on the active CC */ |
| STM32_COMP_CSR &= ~(i ? STM32_COMP_CMP1EN |
| : STM32_COMP_CMP2EN); |
| /* start sampling */ |
| pd_rx_start(0); |
| /* |
| * ignore the comparator IRQ until we are done |
| * with current message |
| */ |
| pd_rx_disable_monitoring(0); |
| /* trigger the analysis in the task */ |
| #ifdef HAS_TASK_SNIFFER |
| task_set_event(TASK_ID_SNIFFER, 4 << i, 0); |
| #endif |
| /* start reception only one CC line */ |
| break; |
| } else { |
| /* do not trigger RX start, just clear int */ |
| STM32_EXTI_PR = EXTI_COMP_MASK(0); |
| } |
| rx_edge_ts_idx[i] = next_idx; |
| } |
| } |
| } |
| #ifdef HAS_TASK_SNIFFER |
| DECLARE_IRQ(STM32_IRQ_COMP, rx_event, 1); |
| #endif |
| |
| void trace_packets(void) |
| { |
| struct rx_header rx; |
| uint32_t payload[7]; |
| uint32_t evt; |
| |
| #ifdef HAS_TASK_SNIFFER |
| /* Disable sniffer DMA configuration */ |
| dma_disable(STM32_DMAC_CH6); |
| dma_disable(STM32_DMAC_CH7); |
| task_disable_irq(STM32_IRQ_DMA_CHANNEL_4_7); |
| /* remove TIM1 CH1/2/3 DMA remapping */ |
| STM32_SYSCFG_CFGR1 &= ~(1 << 28); |
| #endif |
| |
| /* "classical" PD RX configuration */ |
| pd_hw_init_rx(0); |
| pd_select_polarity(0, 0); |
| /* detect messages on both CCx lines */ |
| STM32_COMP_CSR |= STM32_COMP_CMP2EN | STM32_COMP_CMP1EN; |
| /* Enable the RX interrupts */ |
| pd_rx_enable_monitoring(0); |
| |
| while (1) { |
| evt = task_wait_event(-1); |
| if (trace_mode == TRACE_MODE_OFF) |
| break; |
| if (evt < 4) { /* USB event only */ |
| sniffer_trace_reload(); |
| continue; |
| } |
| /* incoming packet processing */ |
| rx = pd_analyze_rx(0, payload); |
| pd_rx_complete(0); |
| /* re-enabled detection on both CCx lines */ |
| STM32_COMP_CSR |= STM32_COMP_CMP2EN | STM32_COMP_CMP1EN; |
| pd_rx_enable_monitoring(0); |
| /* print the last packet content */ |
| if (trace_mode == TRACE_MODE_RAW) |
| sniffer_trace_packet(rx, payload); |
| else |
| print_packet(rx, payload); |
| if (rx.packet_type >= 0 && |
| expected_cmd == PD_HEADER_TYPE(rx.head)) |
| task_wake(TASK_ID_CONSOLE); |
| } |
| |
| task_disable_irq(STM32_IRQ_COMP); |
| /* Disable tracer DMA configuration */ |
| dma_disable(STM32_DMAC_CH2); |
| /* Put back : sniffer RX hardware configuration */ |
| #ifdef HAS_TASK_SNIFFER |
| sniffer_init(); |
| #endif |
| } |
| |
| int expect_packet(int pol, uint8_t cmd, uint32_t timeout_us) |
| { |
| uint32_t evt; |
| |
| expected_cmd = cmd; |
| evt = task_wait_event(timeout_us); |
| |
| return !(evt == TASK_EVENT_TIMER); |
| } |
| |
| void set_trace_mode(int mode) |
| { |
| /* No change */ |
| if (mode == trace_mode) |
| return; |
| |
| trace_mode = mode; |
| /* kick the task to take into account the new value */ |
| #ifdef HAS_TASK_SNIFFER |
| task_wake(TASK_ID_SNIFFER); |
| #endif |
| } |