diff options
Diffstat (limited to 'common/usb_pd_tcpc.c')
-rw-r--r-- | common/usb_pd_tcpc.c | 963 |
1 files changed, 963 insertions, 0 deletions
diff --git a/common/usb_pd_tcpc.c b/common/usb_pd_tcpc.c new file mode 100644 index 0000000000..13e78f5743 --- /dev/null +++ b/common/usb_pd_tcpc.c @@ -0,0 +1,963 @@ +/* Copyright 2015 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 "config.h" +#include "console.h" +#include "crc.h" +#include "ec_commands.h" +#include "gpio.h" +#include "host_command.h" +#include "registers.h" +#include "task.h" +#include "timer.h" +#include "util.h" +#include "usb_pd.h" +#include "usb_pd_config.h" +#include "usb_pd_tcpm.h" + +#ifdef CONFIG_COMMON_RUNTIME +#define CPRINTF(format, args...) cprintf(CC_USBPD, format, ## args) +#define CPRINTS(format, args...) cprints(CC_USBPD, format, ## args) + +/* + * Debug log level - higher number == more log + * Level 0: Log state transitions + * Level 1: Level 0, plus packet info + * Level 2: Level 1, plus ping packet and packet dump on error + * + * Note that higher log level causes timing changes and thus may affect + * performance. + */ +static int debug_level; + +/* + * TODO: disable in RO? can we remove enable var from protocol layer? + * do we need to send a hard reset when we transition to enabled because + * source could have given up sending source cap and may need hard reset + * in order to establish a contract. + */ +static uint8_t pd_comm_enabled = 1; + +static struct mutex pd_crc_lock; +#else +#define CPRINTF(format, args...) +static const int debug_level; +static const int pd_comm_enabled = 1; +#endif + +/* Encode 5 bits using Biphase Mark Coding */ +#define BMC(x) ((x & 1 ? 0x001 : 0x3FF) \ + ^ (x & 2 ? 0x004 : 0x3FC) \ + ^ (x & 4 ? 0x010 : 0x3F0) \ + ^ (x & 8 ? 0x040 : 0x3C0) \ + ^ (x & 16 ? 0x100 : 0x300)) + +/* 4b/5b + Bimark Phase encoding */ +static const uint16_t bmc4b5b[] = { +/* 0 = 0000 */ BMC(0x1E) /* 11110 */, +/* 1 = 0001 */ BMC(0x09) /* 01001 */, +/* 2 = 0010 */ BMC(0x14) /* 10100 */, +/* 3 = 0011 */ BMC(0x15) /* 10101 */, +/* 4 = 0100 */ BMC(0x0A) /* 01010 */, +/* 5 = 0101 */ BMC(0x0B) /* 01011 */, +/* 6 = 0110 */ BMC(0x0E) /* 01110 */, +/* 7 = 0111 */ BMC(0x0F) /* 01111 */, +/* 8 = 1000 */ BMC(0x12) /* 10010 */, +/* 9 = 1001 */ BMC(0x13) /* 10011 */, +/* A = 1010 */ BMC(0x16) /* 10110 */, +/* B = 1011 */ BMC(0x17) /* 10111 */, +/* C = 1100 */ BMC(0x1A) /* 11010 */, +/* D = 1101 */ BMC(0x1B) /* 11011 */, +/* E = 1110 */ BMC(0x1C) /* 11100 */, +/* F = 1111 */ BMC(0x1D) /* 11101 */, +/* Sync-1 K-code 11000 Startsynch #1 */ +/* Sync-2 K-code 10001 Startsynch #2 */ +/* RST-1 K-code 00111 Hard Reset #1 */ +/* RST-2 K-code 11001 Hard Reset #2 */ +/* EOP K-code 01101 EOP End Of Packet */ +/* Reserved Error 00000 */ +/* Reserved Error 00001 */ +/* Reserved Error 00010 */ +/* Reserved Error 00011 */ +/* Reserved Error 00100 */ +/* Reserved Error 00101 */ +/* Reserved Error 00110 */ +/* Reserved Error 01000 */ +/* Reserved Error 01100 */ +/* Reserved Error 10000 */ +/* Reserved Error 11111 */ +}; + +static const uint8_t dec4b5b[] = { +/* Error */ 0x10 /* 00000 */, +/* Error */ 0x10 /* 00001 */, +/* Error */ 0x10 /* 00010 */, +/* Error */ 0x10 /* 00011 */, +/* Error */ 0x10 /* 00100 */, +/* Error */ 0x10 /* 00101 */, +/* Error */ 0x10 /* 00110 */, +/* RST-1 */ 0x13 /* 00111 K-code: Hard Reset #1 */, +/* Error */ 0x10 /* 01000 */, +/* 1 = 0001 */ 0x01 /* 01001 */, +/* 4 = 0100 */ 0x04 /* 01010 */, +/* 5 = 0101 */ 0x05 /* 01011 */, +/* Error */ 0x10 /* 01100 */, +/* EOP */ 0x15 /* 01101 K-code: EOP End Of Packet */, +/* 6 = 0110 */ 0x06 /* 01110 */, +/* 7 = 0111 */ 0x07 /* 01111 */, +/* Error */ 0x10 /* 10000 */, +/* Sync-2 */ 0x12 /* 10001 K-code: Startsynch #2 */, +/* 8 = 1000 */ 0x08 /* 10010 */, +/* 9 = 1001 */ 0x09 /* 10011 */, +/* 2 = 0010 */ 0x02 /* 10100 */, +/* 3 = 0011 */ 0x03 /* 10101 */, +/* A = 1010 */ 0x0A /* 10110 */, +/* B = 1011 */ 0x0B /* 10111 */, +/* Sync-1 */ 0x11 /* 11000 K-code: Startsynch #1 */, +/* RST-2 */ 0x14 /* 11001 K-code: Hard Reset #2 */, +/* C = 1100 */ 0x0C /* 11010 */, +/* D = 1101 */ 0x0D /* 11011 */, +/* E = 1110 */ 0x0E /* 11100 */, +/* F = 1111 */ 0x0F /* 11101 */, +/* 0 = 0000 */ 0x00 /* 11110 */, +/* Error */ 0x10 /* 11111 */, +}; + +/* Start of Packet sequence : three Sync-1 K-codes, then one Sync-2 K-code */ +#define PD_SOP (PD_SYNC1 | (PD_SYNC1<<5) | (PD_SYNC1<<10) | (PD_SYNC2<<15)) +#define PD_SOP_PRIME (PD_SYNC1 | (PD_SYNC1<<5) | \ + (PD_SYNC3<<10) | (PD_SYNC3<<15)) +#define PD_SOP_PRIME_PRIME (PD_SYNC1 | (PD_SYNC3<<5) | \ + (PD_SYNC1<<10) | (PD_SYNC3<<15)) + +/* Hard Reset sequence : three RST-1 K-codes, then one RST-2 K-code */ +#define PD_HARD_RESET (PD_RST1 | (PD_RST1 << 5) |\ + (PD_RST1 << 10) | (PD_RST2 << 15)) + +/* + * Polarity based on 'DFP Perspective' (see table USB Type-C Cable and Connector + * Specification) + * + * CC1 CC2 STATE POSITION + * ---------------------------------------- + * open open NC N/A + * Rd open UFP attached 1 + * open Rd UFP attached 2 + * open Ra pwr cable no UFP N/A + * Ra open pwr cable no UFP N/A + * Rd Ra pwr cable & UFP 1 + * Ra Rd pwr cable & UFP 2 + * Rd Rd dbg accessory N/A + * Ra Ra audio accessory N/A + * + * Note, V(Rd) > V(Ra) + */ +#ifndef PD_SRC_RD_THRESHOLD +#define PD_SRC_RD_THRESHOLD 200 /* mV */ +#endif +#define CC_RA(cc) (cc < PD_SRC_RD_THRESHOLD) +#define CC_RD(cc) ((cc >= PD_SRC_RD_THRESHOLD) && (cc < PD_SRC_VNC)) +#define CC_NC(cc) (cc >= PD_SRC_VNC) + +/* + * Polarity based on 'UFP Perspective'. + * + * CC1 CC2 STATE POSITION + * ---------------------------------------- + * open open NC N/A + * Rp open DFP attached 1 + * open Rp DFP attached 2 + * Rp Rp Accessory attached N/A + */ +#define CC_RP(cc) (cc >= PD_SNK_VA) + +/* + * Type C power source charge current limits are identified by their cc + * voltage (set by selecting the proper Rd resistor). Any voltage below + * TYPE_C_SRC_500_THRESHOLD will not be identified as a type C charger. + */ +#define TYPE_C_SRC_500_THRESHOLD PD_SRC_RD_THRESHOLD +#define TYPE_C_SRC_1500_THRESHOLD 660 /* mV */ +#define TYPE_C_SRC_3000_THRESHOLD 1230 /* mV */ + +/* PD transmit errors */ +enum pd_tx_errors { + PD_TX_ERR_GOODCRC = -1, /* Failed to receive goodCRC */ + PD_TX_ERR_DISABLED = -2, /* Attempted transmit even though disabled */ + PD_TX_ERR_INV_ACK = -4, /* Received different packet instead of gCRC */ + PD_TX_ERR_COLLISION = -5 /* Collision detected during transmit */ +}; + +static struct pd_port_controller { + /* current port power role (SOURCE or SINK) */ + uint8_t power_role; + /* current port data role (DFP or UFP) */ + uint8_t data_role; + /* Port polarity : 0 => CC1 is CC line, 1 => CC2 is CC line */ + uint8_t polarity; + /* Our CC pull resistor setting */ + uint8_t cc_pull; + /* CC status */ + uint8_t cc_status[2]; + /* TCPC alert status */ + uint8_t alert[2]; + + /* Last received */ + int rx_head; + uint32_t rx_payload[7]; + + /* Next transmit */ + enum tcpm_transmit_type tx_type; + uint16_t tx_head; + const uint32_t *tx_data; +} pd[PD_PORT_COUNT]; + +static inline int encode_short(int port, int off, uint16_t val16) +{ + off = pd_write_sym(port, off, bmc4b5b[(val16 >> 0) & 0xF]); + off = pd_write_sym(port, off, bmc4b5b[(val16 >> 4) & 0xF]); + off = pd_write_sym(port, off, bmc4b5b[(val16 >> 8) & 0xF]); + return pd_write_sym(port, off, bmc4b5b[(val16 >> 12) & 0xF]); +} + +int encode_word(int port, int off, uint32_t val32) +{ + off = encode_short(port, off, (val32 >> 0) & 0xFFFF); + return encode_short(port, off, (val32 >> 16) & 0xFFFF); +} + +/* prepare a 4b/5b-encoded PD message to send */ +int prepare_message(int port, uint16_t header, uint8_t cnt, + const uint32_t *data) +{ + int off, i; + /* 64-bit preamble */ + off = pd_write_preamble(port); + /* Start Of Packet: 3x Sync-1 + 1x Sync-2 */ + off = pd_write_sym(port, off, BMC(PD_SYNC1)); + off = pd_write_sym(port, off, BMC(PD_SYNC1)); + off = pd_write_sym(port, off, BMC(PD_SYNC1)); + off = pd_write_sym(port, off, BMC(PD_SYNC2)); + /* header */ + off = encode_short(port, off, header); + +#ifdef CONFIG_COMMON_RUNTIME + mutex_lock(&pd_crc_lock); +#endif + + crc32_init(); + crc32_hash16(header); + /* data payload */ + for (i = 0; i < cnt; i++) { + off = encode_word(port, off, data[i]); + crc32_hash32(data[i]); + } + /* CRC */ + off = encode_word(port, off, crc32_result()); + +#ifdef CONFIG_COMMON_RUNTIME + mutex_unlock(&pd_crc_lock); +#endif + + /* End Of Packet */ + off = pd_write_sym(port, off, BMC(PD_EOP)); + /* Ensure that we have a final edge */ + return pd_write_last_edge(port, off); +} + +static int send_hard_reset(int port) +{ + int off; + + if (debug_level >= 1) + CPRINTF("C%d Send hard reset\n", port); + + /* 64-bit preamble */ + off = pd_write_preamble(port); + /* Hard-Reset: 3x RST-1 + 1x RST-2 */ + off = pd_write_sym(port, off, BMC(PD_RST1)); + off = pd_write_sym(port, off, BMC(PD_RST1)); + off = pd_write_sym(port, off, BMC(PD_RST1)); + off = pd_write_sym(port, off, BMC(PD_RST2)); + /* Ensure that we have a final edge */ + off = pd_write_last_edge(port, off); + /* Transmit the packet */ + if (pd_start_tx(port, pd[port].polarity, off) < 0) + return PD_TX_ERR_COLLISION; + 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, + const uint32_t *data) +{ + int r; + static uint32_t payload[7]; + uint8_t expected_msg_id = PD_HEADER_ID(header); + uint8_t cnt = PD_HEADER_CNT(header); + + /* retry 3 times if we are not getting a valid answer */ + for (r = 0; r <= PD_RETRY_COUNT; r++) { + int bit_len, head; + /* write the encoded packet in the transmission buffer */ + bit_len = prepare_message(port, header, cnt, data); + /* Transmit the packet */ + if (pd_start_tx(port, pd[port].polarity, bit_len) < 0) { + /* + * Collision detected, return immediately so we can + * respond to what we have received. + */ + return PD_TX_ERR_COLLISION; + } + pd_tx_done(port, pd[port].polarity); + /* + * 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); + /* Wait for message receive timeout */ + if (task_wait_event(USB_PD_RX_TMOUT_US) == + TASK_EVENT_TIMER) + continue; + /* + * Make sure we woke up due to rx recd, otherwise + * we need to manually start + */ + if (!pd_rx_started(port)) { + pd_rx_disable_monitoring(port); + pd_rx_start(port); + } + } else { + /* starting waiting for GoodCrc */ + pd_rx_start(port); + } + /* read the incoming packet if any */ + head = pd_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); + uint8_t id = PD_HEADER_ID(head); + if (type == PD_CTRL_GOOD_CRC && nb == 0 && + id == expected_msg_id) { + /* got the GoodCRC we were expecting */ + /* do not catch last edges as a new packet */ + udelay(20); + return bit_len; + } else { + /* + * we have received a good packet + * but not the expected GoodCRC, + * the other side is trying to contact us, + * bail out immediatly so we can get the retry. + */ + return PD_TX_ERR_INV_ACK; + } + } + } + /* we failed all the re-transmissions */ + if (debug_level >= 1) + CPRINTF("TX NOACK%d %04x/%d\n", port, header, cnt); + return PD_TX_ERR_GOODCRC; +} + +static void send_goodcrc(int port, int id) +{ + uint16_t header = PD_HEADER(PD_CTRL_GOOD_CRC, pd[port].power_role, + pd[port].data_role, id, 0); + int bit_len = prepare_message(port, header, 0, NULL); + + if (pd_start_tx(port, pd[port].polarity, bit_len) < 0) + /* another packet recvd before we could send goodCRC */ + return; + pd_tx_done(port, pd[port].polarity); + /* Keep RX monitoring on */ + pd_rx_enable_monitoring(port); +} + +#if 0 +/* TODO: when/how do we trigger this ? */ +static int analyze_rx_bist(int port); + +void bist_mode_2_rx(int port) +{ + int analyze_bist = 0; + int num_bits; + timestamp_t start_time; + + /* monitor for incoming packet */ + pd_rx_enable_monitoring(port); + + /* loop until we start receiving data */ + start_time.val = get_time().val; + while ((get_time().val - start_time.val) < (500*MSEC)) { + task_wait_event(10*MSEC); + /* incoming packet ? */ + if (pd_rx_started(port)) { + analyze_bist = 1; + break; + } + } + + if (analyze_bist) { + /* + * once we start receiving bist data, analyze 40 bytes + * every 10 msec. Continue analyzing until BIST data + * is no longer received. The standard limits the max + * BIST length to 60 msec. + */ + start_time.val = get_time().val; + while ((get_time().val - start_time.val) + < (PD_T_BIST_RECEIVE)) { + num_bits = analyze_rx_bist(port); + pd_rx_complete(port); + /* + * If no data was received, then analyze_rx_bist() + * will return a -1 and there is no need to stay + * in this mode + */ + if (num_bits == -1) + break; + msleep(10); + pd_rx_enable_monitoring(port); + } + } else { + CPRINTF("BIST RX TO\n"); + } +} +#endif + +static void bist_mode_2_tx(int port) +{ + int bit; + + CPRINTF("BIST 2: p%d\n", port); + /* + * build context buffer with 5 bytes, where the data is + * alternating 1's and 0's. + */ + bit = pd_write_sym(port, 0, BMC(0x15)); + bit = pd_write_sym(port, bit, BMC(0x0a)); + bit = pd_write_sym(port, bit, BMC(0x15)); + bit = pd_write_sym(port, bit, BMC(0x0a)); + + /* start a circular DMA transfer */ + pd_tx_set_circular_mode(port); + pd_start_tx(port, pd[port].polarity, bit); + + task_wait_event(PD_T_BIST_TRANSMIT); + + /* clear dma circular mode, will also stop dma */ + pd_tx_clear_circular_mode(port); + /* finish and cleanup transmit */ + pd_tx_done(port, pd[port].polarity); +} + +static inline int decode_short(int port, int off, uint16_t *val16) +{ + uint32_t w; + int end; + + end = pd_dequeue_bits(port, off, 20, &w); + +#if 0 /* DEBUG */ + CPRINTS("%d-%d: %05x %x:%x:%x:%x\n", + off, end, w, + dec4b5b[(w >> 15) & 0x1f], dec4b5b[(w >> 10) & 0x1f], + dec4b5b[(w >> 5) & 0x1f], dec4b5b[(w >> 0) & 0x1f]); +#endif + *val16 = dec4b5b[w & 0x1f] | + (dec4b5b[(w >> 5) & 0x1f] << 4) | + (dec4b5b[(w >> 10) & 0x1f] << 8) | + (dec4b5b[(w >> 15) & 0x1f] << 12); + return end; +} + +static inline int decode_word(int port, int off, uint32_t *val32) +{ + off = decode_short(port, off, (uint16_t *)val32); + return decode_short(port, off, ((uint16_t *)val32 + 1)); +} + +#ifdef CONFIG_COMMON_RUNTIME +#if 0 +/* + * TODO: when/how do we trigger this ? Could add custom vendor command + * to TCPCI to enter bist verification? Is there an easier way? + */ +static int count_set_bits(int n) +{ + int count = 0; + while (n) { + n &= (n - 1); + count++; + } + return count; +} + +static int analyze_rx_bist(int port) +{ + int i = 0, bit = -1; + uint32_t w, match; + int invalid_bits = 0; + int bits_analyzed = 0; + static int total_invalid_bits; + + /* dequeue bits until we see a full byte of alternating 1's and 0's */ + while (i < 10 && (bit < 0 || (w != 0xaa && w != 0x55))) + bit = pd_dequeue_bits(port, i++, 8, &w); + + /* if we didn't find any bytes that match criteria, display error */ + if (i == 10) { + CPRINTF("invalid pattern\n"); + return -1; + } + /* + * now we know what matching byte we are looking for, dequeue a bunch + * more data and count how many bits differ from expectations. + */ + match = w; + bit = i - 1; + for (i = 0; i < 40; i++) { + bit = pd_dequeue_bits(port, bit, 8, &w); + if (i && (i % 20 == 0)) + CPRINTF("\n"); + CPRINTF("%02x ", w); + bits_analyzed += 8; + invalid_bits += count_set_bits(w ^ match); + } + + total_invalid_bits += invalid_bits; + + CPRINTF("\nInvalid: %d/%d\n", + invalid_bits, total_invalid_bits); + return bits_analyzed; +} +#endif +#endif + +int pd_analyze_rx(int port, uint32_t *payload) +{ + int bit; + char *msg = "---"; + uint32_t val = 0; + uint16_t header; + uint32_t pcrc, ccrc; + int p, cnt; + uint32_t eop; + + pd_init_dequeue(port); + + /* Detect preamble */ + bit = pd_find_preamble(port); + if (bit == PD_RX_ERR_HARD_RESET || bit == PD_RX_ERR_CABLE_RESET) { + /* Hard reset or cable reset */ + return bit; + } else if (bit < 0) { + msg = "Preamble"; + goto packet_err; + } + + /* Find the Start Of Packet sequence */ + while (bit > 0) { + bit = pd_dequeue_bits(port, bit, 20, &val); + if (val == PD_SOP) { + break; + } else if (val == PD_SOP_PRIME) { + CPRINTF("SOP'\n"); + return PD_RX_ERR_UNSUPPORTED_SOP; + } else if (val == PD_SOP_PRIME_PRIME) { + CPRINTF("SOP''\n"); + return PD_RX_ERR_UNSUPPORTED_SOP; + } + } + if (bit < 0) { + msg = "SOP"; + goto packet_err; + } + + /* read header */ + bit = decode_short(port, bit, &header); + +#ifdef CONFIG_COMMON_RUNTIME + mutex_lock(&pd_crc_lock); +#endif + + crc32_init(); + crc32_hash16(header); + cnt = PD_HEADER_CNT(header); + + /* read payload data */ + for (p = 0; p < cnt && bit > 0; p++) { + bit = decode_word(port, bit, payload+p); + crc32_hash32(payload[p]); + } + ccrc = crc32_result(); + +#ifdef CONFIG_COMMON_RUNTIME + mutex_unlock(&pd_crc_lock); +#endif + + if (bit < 0) { + msg = "len"; + goto packet_err; + } + + /* check transmitted CRC */ + bit = decode_word(port, bit, &pcrc); + if (bit < 0 || pcrc != ccrc) { + msg = "CRC"; + if (pcrc != ccrc) + bit = PD_RX_ERR_CRC; + if (debug_level >= 1) + CPRINTF("CRC%d %08x <> %08x\n", port, pcrc, ccrc); + goto packet_err; + } + + /* + * 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("RXERR%d %s\n", port, msg); + return bit; +} + +static void handle_request(int port, uint16_t head, + uint32_t *payload) +{ + int cnt = PD_HEADER_CNT(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); +} + +/* Convert CC voltage to CC status */ +static int cc_voltage_to_status(int port, int cc_volt) +{ + /* If we have a pull-up, then we are source, check for Rd. */ + if (pd[port].cc_pull == TYPEC_CC_RP) { + if (CC_NC(cc_volt)) + return TYPEC_CC_SRC_OPEN; + else if (CC_RA(cc_volt)) + return TYPEC_CC_SRC_RA; + else + return TYPEC_CC_SRC_RD; + /* If we have a pull-down, then we are sink, check for Rp. */ + } +#ifdef CONFIG_USB_PD_DUAL_ROLE + else if (pd[port].cc_pull == TYPEC_CC_RD) { + if (cc_volt >= TYPE_C_SRC_3000_THRESHOLD) + return TYPEC_CC_SNK_PWR_3_0; + else if (cc_volt >= TYPE_C_SRC_1500_THRESHOLD) + return TYPEC_CC_SNK_PWR_1_5; + else if (CC_RP(cc_volt)) + return TYPEC_CC_SNK_PWR_DEFAULT; + else + return TYPEC_CC_SNK_OPEN; + } +#endif + /* If we are open, then always return 0 */ + else + return 0; +} + +static void alert(int port, int reg, int mask) +{ + pd[port].alert[reg] |= mask; + tcpc_alert(); +} + +void tcpc_init(int port) +{ + /* Initialize physical layer */ + pd_hw_init(port, PD_ROLE_DEFAULT); + + /* make sure PD monitoring is enabled to wake on PD RX */ + if (pd_comm_enabled) + pd_rx_enable_monitoring(port); + +} + +int tcpc_run(int port, int evt) +{ + int cc, i, res; + + /* incoming packet ? */ + if (pd_rx_started(port) && pd_comm_enabled) { + pd[port].rx_head = pd_analyze_rx(port, + pd[port].rx_payload); + pd_rx_complete(port); + if (pd[port].rx_head > 0) { + handle_request(port, + pd[port].rx_head, + pd[port].rx_payload); + alert(port, TCPC_ALERT0, TCPC_ALERT0_RX_STATUS); + } else if (pd[port].rx_head == PD_RX_ERR_HARD_RESET) { + alert(port, TCPC_ALERT0, + TCPC_ALERT0_RX_HARD_RST); + } + } + + /* outgoing packet ? */ + if ((evt & PD_EVENT_TX) && pd_comm_enabled) { + switch (pd[port].tx_type) { + case TRANSMIT_SOP: + res = send_validate_message(port, + pd[port].tx_head, + pd[port].tx_data); + break; + case TRANSMIT_BIST_MODE_2: + bist_mode_2_tx(port); + res = 0; + break; + case TRANSMIT_HARD_RESET: + res = send_hard_reset(port); + break; + default: + break; + } + + /* send appropriate alert for tx completion */ + if (res >= 0) + alert(port, TCPC_ALERT0, + TCPC_ALERT0_TX_SUCCESS); + else if (res == PD_TX_ERR_GOODCRC) + alert(port, TCPC_ALERT0, + TCPC_ALERT0_TX_FAILED); + else + alert(port, TCPC_ALERT0, + TCPC_ALERT0_TX_DISCARDED); + } + + /* CC pull changed, wait 1ms for CC voltage to stabilize */ + if (evt & PD_EVENT_CC) + usleep(MSEC); + + /* check CC lines */ + for (i = 0; i < 2; i++) { + /* read CC voltage */ + cc = pd_adc_read(port, i); + + /* convert voltage to status, and check status change */ + cc = cc_voltage_to_status(port, cc); + if (pd[port].cc_status[i] != cc) { + pd[port].cc_status[i] = cc; + alert(port, TCPC_ALERT0, TCPC_ALERT0_CC_STATUS); + } + } + + /* make sure PD monitoring is enabled to wake on PD RX */ + if (pd_comm_enabled) + pd_rx_enable_monitoring(port); + + /* TODO: adjust timeout based on how often to sample CC */ + return 10*MSEC; +} + +#if 0 +/* TODO: if we don't have TCPM on same CPU, we will need this task */ +void pd_phy_task(void) +{ + int port = TASK_ID_TO_PORT_PHY(task_get_current()); + int timeout = 10*MSEC; + int evt; + + /* initialize phy task */ + tcpc_init(port); + + while (1) { + /* wait for next event/packet or timeout expiration */ + evt = task_wait_event(timeout); + + /* run phy task once */ + timeout = tcpc_run(port, evt); + } +} +#endif + +void pd_rx_event(int port) +{ + task_set_event(PORT_TO_TASK_ID(port), PD_EVENT_RX, 0); +} + +int tcpc_alert_status(int port, int alert_reg) +{ + int ret = pd[port].alert[alert_reg]; + + /* TODO: Alert register is read-clear for now, but shouldn't be */ + pd[port].alert[alert_reg] = 0; + return ret; +} + +void tcpc_set_cc(int port, int pull) +{ + /* If CC pull resistor not changing, then nothing to do */ + if (pd[port].cc_pull == pull) + return; + + /* Change CC pull resistor */ + pd[port].cc_pull = pull; +#ifdef CONFIG_USB_PD_DUAL_ROLE + pd_set_host_mode(port, pull == TYPEC_CC_RP); +#endif + + /* + * Before CC pull can be changed and the task can read the new + * status, we should set the CC status to open, in case TCPM + * asks before it is known for sure. + */ + pd[port].cc_status[0] = pull == TYPEC_CC_RP ? TYPEC_CC_SRC_OPEN : + TYPEC_CC_SNK_OPEN; + pd[port].cc_status[1] = pd[port].cc_status[0]; + + /* Wake the PD phy task with special CC event mask */ + /* TODO: use top case if no TCPM on same CPU */ +#if 0 + task_set_event(PORT_PHY_TO_TASK_ID(port), PD_EVENT_CC, 0); +#else + tcpc_run(port, PD_EVENT_CC); +#endif +} + +int tcpc_get_cc(int port, int polarity) +{ + return pd[port].cc_status[polarity]; +} + +void tcpc_set_polarity(int port, int polarity) +{ + pd[port].polarity = polarity; + pd_select_polarity(port, pd[port].polarity); +} + +void tcpc_set_vconn(int port, int enable) +{ +#ifdef CONFIG_USBC_VCONN + pd_set_vconn(port, pd[port].polarity, enable); +#endif +} + +void tcpc_transmit(int port, enum tcpm_transmit_type type, uint16_t header, + const uint32_t *data) +{ + /* Store data to transmit and wake task to send it */ + pd[port].tx_type = type; + pd[port].tx_head = header; + pd[port].tx_data = data; + /* TODO: use top case if no TCPM on same CPU */ +#if 0 + task_set_event(PORT_PHY_TO_TASK_ID(port), PD_EVENT_TX, 0); +#else + tcpc_run(port, PD_EVENT_TX); +#endif +} + +void tcpc_set_msg_header(int port, int power_role, int data_role) +{ + pd[port].power_role = power_role; + pd[port].data_role = data_role; +} + +int tcpc_get_message(int port, uint32_t *payload) +{ + memcpy(payload, pd[port].rx_payload, sizeof(pd[port].rx_payload)); + return pd[port].rx_head; +} + +#ifdef CONFIG_COMMON_RUNTIME +static int command_tcpc(int argc, char **argv) +{ + int port; + char *e; + + if (argc < 2) + return EC_ERROR_PARAM_COUNT; + + if (!strcasecmp(argv[1], "dump")) { + int level; + + if (argc < 3) + ccprintf("lvl: %d\n", debug_level); + else { + level = strtoi(argv[2], &e, 10); + if (*e) + return EC_ERROR_PARAM2; + debug_level = level; + } + return EC_SUCCESS; + } else if (!strcasecmp(argv[1], "enable")) { + int enable; + + if (argc < 3) + return EC_ERROR_PARAM_COUNT; + + enable = strtoi(argv[2], &e, 10); + if (*e) + return EC_ERROR_PARAM3; + pd_comm_enabled = enable; + ccprintf("Ports %s\n", enable ? "enabled" : "disabled"); + return EC_SUCCESS; + } + + /* command: pd <port> <subcmd> [args] */ + port = strtoi(argv[1], &e, 10); + if (argc < 3) + return EC_ERROR_PARAM_COUNT; + if (*e || port >= PD_PORT_COUNT) + return EC_ERROR_PARAM2; + + if (!strcasecmp(argv[2], "clock")) { + int freq; + + if (argc < 4) + return EC_ERROR_PARAM2; + + freq = strtoi(argv[3], &e, 10); + if (*e) + return EC_ERROR_PARAM2; + pd_set_clock(port, freq); + ccprintf("set TX frequency to %d Hz\n", freq); + return EC_SUCCESS; + } else if (!strncasecmp(argv[2], "state", 5)) { + ccprintf("Port C%d, %s - CC:%d, CC0:%d, CC1:%d, " + "Alert: 0x%02x 0x%02x\n", port, + pd_comm_enabled ? "Ena" : "Dis", + pd[port].cc_pull, + pd[port].cc_status[0], pd[port].cc_status[1], + pd[port].alert[0], pd[port].alert[1]); + } + + return EC_SUCCESS; +} +DECLARE_CONSOLE_COMMAND(tcpc, command_tcpc, + "dump|enable [0|1]\n\t<port> [clock|state]", + "Type-C Port Controller", + NULL); +#endif |