summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVincent Palatin <vpalatin@chromium.org>2014-03-13 14:28:40 -0700
committerchrome-internal-fetch <chrome-internal-fetch@google.com>2014-04-12 01:45:37 +0000
commitd4c939e9eee6ceed7549791985578a4a31e30f59 (patch)
tree6b1928fb384753ec9dc4c135b27186939f957f3a
parent3344c8e2e64fd4985f6fbb55810452f195b7b7eb (diff)
downloadchrome-ec-d4c939e9eee6ceed7549791985578a4a31e30f59.tar.gz
USB Power Delivery core code
The protocol layer implementation for USB Power Delivery messaging. Signed-off-by: Vincent Palatin <vpalatin@chromium.org> BRANCH=none BUG=none TEST=none Change-Id: I7db75b004cf9dbf13fa1df95336f206e93236fc9 Reviewed-on: https://chromium-review.googlesource.com/189866 Tested-by: Vincent Palatin <vpalatin@chromium.org> Reviewed-by: Randall Spangler <rspangler@chromium.org> Commit-Queue: Vincent Palatin <vpalatin@chromium.org>
-rw-r--r--common/build.mk1
-rw-r--r--common/console_output.c1
-rw-r--r--common/usb_pd_protocol.c818
-rw-r--r--include/console.h1
-rw-r--r--include/usb_pd.h265
5 files changed, 1086 insertions, 0 deletions
diff --git a/common/build.mk b/common/build.mk
index 93950f9495..7189a07432 100644
--- a/common/build.mk
+++ b/common/build.mk
@@ -63,6 +63,7 @@ common-$(CONFIG_SWITCH)+=switch.o
common-$(CONFIG_TEMP_SENSOR)+=temp_sensor.o thermal.o
common-$(CONFIG_USB_PORT_POWER_DUMB)+=usb_port_power_dumb.o
common-$(CONFIG_USB_PORT_POWER_SMART)+=usb_port_power_smart.o
+common-$(CONFIG_USB_POWER_DELIVERY)+=usb_pd_protocol.o
common-$(CONFIG_VBOOT_HASH)+=sha256.o vboot_hash.o
common-$(CONFIG_WIRELESS)+=wireless.o
common-$(HAS_TASK_CHIPSET)+=chipset.o throttle_ap.o
diff --git a/common/console_output.c b/common/console_output.c
index 2b52da66cf..4ff5a9c478 100644
--- a/common/console_output.c
+++ b/common/console_output.c
@@ -55,6 +55,7 @@ static const char * const channel_names[] = {
"task",
"thermal",
"usbcharge",
+ "usbpd",
"vboot",
"hook",
};
diff --git a/common/usb_pd_protocol.c b/common/usb_pd_protocol.c
new file mode 100644
index 0000000000..4d0ef0f90b
--- /dev/null
+++ b/common/usb_pd_protocol.c
@@ -0,0 +1,818 @@
+/* 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 "board.h"
+#include "common.h"
+#include "console.h"
+#include "crc.h"
+#include "gpio.h"
+#include "hooks.h"
+#include "registers.h"
+#include "task.h"
+#include "timer.h"
+#include "util.h"
+#include "usb_pd.h"
+#include "usb_pd_config.h"
+
+#ifdef CONFIG_COMMON_RUNTIME
+#define CPRINTF(format, args...) cprintf(CC_USBPD, format, ## args)
+
+/* dump full packet on RX error */
+static int debug_dump;
+#else
+#define CPRINTF(format, args...)
+const int debug_dump;
+#endif
+
+/* Control Message type */
+enum {
+ /* 0 Reserved */
+ PD_CTRL_GOOD_CRC = 1,
+ PD_CTRL_GOTO_MIN = 2,
+ PD_CTRL_ACCEPT = 3,
+ PD_CTRL_REJECT = 4,
+ PD_CTRL_PING = 5,
+ PD_CTRL_PS_RDY = 6,
+ PD_CTRL_GET_SOURCE_CAP = 7,
+ PD_CTRL_GET_SINK_CAP = 8,
+ PD_CTRL_PROTOCOL_ERR = 9,
+ PD_CTRL_SWAP = 10,
+ /* 11 Reserved */
+ PD_CTRL_WAIT = 12,
+ PD_CTRL_SOFT_RESET = 13,
+ /* 14-15 Reserved */
+};
+
+/* Data message type */
+enum {
+ /* 0 Reserved */
+ PD_DATA_SOURCE_CAP = 1,
+ PD_DATA_REQUEST = 2,
+ PD_DATA_BIST = 3,
+ PD_DATA_SINK_CAP = 4,
+ /* 5-14 Reserved */
+ PD_DATA_VENDOR_DEF = 15,
+};
+
+/* Protocol revision */
+#define PD_REV10 0
+
+/* Port role */
+#define PD_ROLE_SINK 0
+#define PD_ROLE_SOURCE 1
+
+/* build message header */
+#define PD_HEADER(type, role, id, cnt) \
+ ((type) | (PD_REV10 << 6) | \
+ ((role) << 8) | ((id) << 9) | ((cnt) << 12))
+
+#define PD_HEADER_CNT(header) (((header) >> 12) & 7)
+#define PD_HEADER_TYPE(header) ((header) & 0xF)
+#define PD_HEADER_ID(header) (((header) >> 9) & 7)
+
+/* 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 */
+};
+#define PD_SYNC1 0x18
+#define PD_SYNC2 0x11
+#define PD_RST1 0x07
+#define PD_RST2 0x19
+#define PD_EOP 0x0D
+
+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))
+
+/* 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))
+
+/* PD counter definitions */
+#define PD_MESSAGE_ID_COUNT 7
+#define PD_RETRY_COUNT 2
+#define PD_HARD_RESET_COUNT 2
+#define PD_CAPS_COUNT 50
+
+/* Timers */
+#define PD_T_SEND_SOURCE_CAP 1500000 /* us (between 1s and 2s) */
+#define PD_T_GET_SOURCE_CAP 1500000 /* us (between 1s and 2s) */
+#define PD_T_SOURCE_ACTIVITY 45000 /* us (between 40ms and 50ms) */
+
+/* Port role at startup */
+#ifdef CONFIG_USB_PD_DUAL_ROLE
+#define PD_ROLE_DEFAULT PD_ROLE_SINK
+#else
+#define PD_ROLE_DEFAULT PD_ROLE_SOURCE
+#endif
+
+/* current port role */
+static uint8_t pd_role = PD_ROLE_DEFAULT;
+/* 3-bit rolling message ID counter */
+static uint8_t pd_message_id;
+/* Port polarity : 0 => CC1 is CC line, 1 => CC2 is CC line */
+static uint8_t pd_polarity;
+
+static enum {
+ PD_STATE_DISABLED,
+#ifdef CONFIG_USB_PD_DUAL_ROLE
+ PD_STATE_SNK_DISCONNECTED,
+ PD_STATE_SNK_DISCOVERY,
+ PD_STATE_SNK_TRANSITION,
+ PD_STATE_SNK_READY,
+#endif /* CONFIG_USB_PD_DUAL_ROLE */
+
+ PD_STATE_SRC_DISCONNECTED,
+ PD_STATE_SRC_DISCOVERY,
+ PD_STATE_SRC_NEGOCIATE,
+ PD_STATE_SRC_ACCEPTED,
+ PD_STATE_SRC_TRANSITION,
+ PD_STATE_SRC_READY,
+
+ PD_STATE_HARD_RESET,
+ PD_STATE_BIST,
+} pd_task_state = PD_DEFAULT_STATE;
+
+/* increment message ID counter */
+static void inc_id(void)
+{
+ pd_message_id = (pd_message_id + 1) & PD_MESSAGE_ID_COUNT;
+}
+
+static inline int encode_short(void *ctxt, int off, uint16_t val16)
+{
+ off = pd_write_sym(ctxt, off, bmc4b5b[(val16 >> 0) & 0xF]);
+ off = pd_write_sym(ctxt, off, bmc4b5b[(val16 >> 4) & 0xF]);
+ off = pd_write_sym(ctxt, off, bmc4b5b[(val16 >> 8) & 0xF]);
+ return pd_write_sym(ctxt, off, bmc4b5b[(val16 >> 12) & 0xF]);
+}
+
+static inline int encode_word(void *ctxt, int off, uint32_t val32)
+{
+ off = encode_short(ctxt, off, (val32 >> 0) & 0xFFFF);
+ return encode_short(ctxt, off, (val32 >> 16) & 0xFFFF);
+}
+
+/* prepare a 4b/5b-encoded PD message to send */
+static int prepare_message(void *ctxt, uint16_t header, uint8_t cnt,
+ const uint32_t *data)
+{
+ int off, i;
+ crc32_init();
+ /* 64-bit preamble */
+ off = pd_write_preamble(ctxt);
+ /* Start Of Packet: 3x Sync-1 + 1x Sync-2 */
+ off = pd_write_sym(ctxt, off, BMC(PD_SYNC1));
+ off = pd_write_sym(ctxt, off, BMC(PD_SYNC1));
+ off = pd_write_sym(ctxt, off, BMC(PD_SYNC1));
+ off = pd_write_sym(ctxt, off, BMC(PD_SYNC2));
+ /* header */
+ off = encode_short(ctxt, off, header);
+ crc32_hash16(header);
+ /* data payload */
+ for (i = 0; i < cnt; i++) {
+ off = encode_word(ctxt, off, data[i]);
+ crc32_hash32(data[i]);
+ }
+ /* CRC */
+ off = encode_word(ctxt, off, crc32_result());
+ /* End Of Packet */
+ off = pd_write_sym(ctxt, off, BMC(PD_EOP));
+ /* Ensure that we have a final edge */
+ return pd_write_last_edge(ctxt, off);
+}
+
+static int analyze_rx(uint32_t *payload);
+
+static void send_hard_reset(void *ctxt)
+{
+ int off;
+
+ /* 64-bit preamble */
+ off = pd_write_preamble(ctxt);
+ /* Hard-Reset: 3x RST-1 + 1x RST-2 */
+ off = pd_write_sym(ctxt, off, BMC(PD_RST1));
+ off = pd_write_sym(ctxt, off, BMC(PD_RST1));
+ off = pd_write_sym(ctxt, off, BMC(PD_RST1));
+ off = pd_write_sym(ctxt, off, BMC(PD_RST2));
+ /* Ensure that we have a final edge */
+ off = pd_write_last_edge(ctxt, off);
+ /* Transmit the packet */
+ pd_start_tx(ctxt, off);
+ pd_tx_done();
+}
+
+static int send_validate_message(void *ctxt, uint16_t header, uint8_t cnt,
+ const uint32_t *data)
+{
+ int r;
+ static uint32_t payload[7];
+
+ /* retry 3 times if we are not getting a valid answer */
+ for (r = 0; r <= PD_RETRY_COUNT; r++) {
+ int bit_len;
+ uint16_t head;
+ /* write the encoded packet in the transmission buffer */
+ bit_len = prepare_message(ctxt, header, cnt, data);
+ /* Transmit the packet */
+ pd_start_tx(ctxt, bit_len);
+ pd_tx_done();
+ /* starting waiting for GoodCrc */
+ pd_rx_start();
+ /* read the incoming packet if any */
+ head = analyze_rx(payload);
+ pd_rx_complete();
+ 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 == pd_message_id) {
+ /* got the GoodCRC we were expecting */
+ inc_id();
+ return bit_len;
+ } else {
+ CPRINTF("ERR ACK/%d %04x\n", id, head);
+ }
+ }
+ }
+ /* we failed all the re-transmissions */
+ /* TODO: try HardReset */
+ CPRINTF("TX NO ACK %04x/%d\n", header, cnt);
+ return -1;
+}
+
+static int send_control(void *ctxt, int type)
+{
+ int bit_len;
+ uint16_t header = PD_HEADER(type, pd_role, pd_message_id, 0);
+
+ bit_len = send_validate_message(ctxt, header, 0, NULL);
+
+ CPRINTF("CTRL[%d]>%d\n", type, bit_len);
+
+ return bit_len;
+}
+
+static void send_goodcrc(void *ctxt, int id)
+{
+ uint16_t header = PD_HEADER(PD_CTRL_GOOD_CRC, pd_role, id, 0);
+ int bit_len = prepare_message(ctxt, header, 0, NULL);
+
+ pd_start_tx(ctxt, bit_len);
+ pd_tx_done();
+}
+
+static int send_source_cap(void *ctxt)
+{
+ int bit_len;
+ uint16_t header = PD_HEADER(PD_DATA_SOURCE_CAP, pd_role, pd_message_id,
+ pd_src_pdo_cnt);
+
+ bit_len = send_validate_message(ctxt, header, pd_src_pdo_cnt,
+ pd_src_pdo);
+ CPRINTF("srcCAP>%d\n", bit_len);
+
+ return bit_len;
+}
+
+#ifdef CONFIG_USB_PD_DUAL_ROLE
+static void send_sink_cap(void *ctxt)
+{
+ int bit_len;
+ uint16_t header = PD_HEADER(PD_DATA_SINK_CAP, pd_role, pd_message_id,
+ pd_snk_pdo_cnt);
+
+ bit_len = send_validate_message(ctxt, header, pd_snk_pdo_cnt,
+ pd_snk_pdo);
+ CPRINTF("snkCAP>%d\n", bit_len);
+}
+
+static void send_request(void *ctxt, uint32_t rdo)
+{
+ int bit_len;
+ uint16_t header = PD_HEADER(PD_DATA_REQUEST, pd_role, pd_message_id, 1);
+
+ bit_len = send_validate_message(ctxt, header, 1, &rdo);
+ CPRINTF("REQ%d>\n", bit_len);
+}
+#endif /* CONFIG_USB_PD_DUAL_ROLE */
+
+static int send_bist(void *ctxt)
+{
+ uint32_t bdo = BDO(BDO_MODE_TRANSMIT, 0);
+ int bit_len;
+ uint16_t header = PD_HEADER(PD_DATA_BIST, pd_role, pd_message_id, 1);
+
+ bit_len = send_validate_message(ctxt, header, 1, &bdo);
+ CPRINTF("BIST>%d\n", bit_len);
+
+ return bit_len;
+}
+
+static void handle_vdm_request(void *ctxt, int cnt, uint32_t *payload)
+{
+ CPRINTF("Unhandled VDM VID %04x CMD %04x\n", payload[0] >> 16,
+ payload[0] & 0xFFFF);
+}
+
+static void handle_data_request(void *ctxt, uint16_t head, uint32_t *payload)
+{
+ int type = PD_HEADER_TYPE(head);
+ int cnt = PD_HEADER_CNT(head);
+
+ switch (type) {
+#ifdef CONFIG_USB_PD_DUAL_ROLE
+ case PD_DATA_SOURCE_CAP:
+ if ((pd_task_state == PD_STATE_SNK_DISCOVERY)
+ || (pd_task_state == PD_STATE_SNK_TRANSITION)) {
+ uint32_t rdo;
+ int res;
+ /* we were waiting for them, let's process them */
+ res = pd_choose_voltage(cnt, payload, &rdo);
+ if (res >= 0) {
+ send_request(ctxt, rdo);
+ pd_task_state = PD_STATE_SNK_TRANSITION;
+ }
+ }
+ break;
+#endif /* CONFIG_USB_PD_DUAL_ROLE */
+ case PD_DATA_REQUEST:
+ if ((pd_role == PD_ROLE_SOURCE) && (cnt == 1))
+ if (!pd_request_voltage(payload[0])) {
+ send_control(ctxt, PD_CTRL_ACCEPT);
+ pd_task_state = PD_STATE_SRC_ACCEPTED;
+ return;
+ }
+ /* the message was incorrect or cannot be satisfied */
+ send_control(ctxt, PD_CTRL_REJECT);
+ break;
+ case PD_DATA_BIST:
+ CPRINTF("BIST not supported\n");
+ break;
+ case PD_DATA_SINK_CAP:
+ break;
+ case PD_DATA_VENDOR_DEF:
+ handle_vdm_request(ctxt, cnt, payload);
+ break;
+ default:
+ CPRINTF("Unhandled data message type %d\n", type);
+ }
+}
+
+static void handle_ctrl_request(void *ctxt, uint16_t head, uint32_t *payload)
+{
+ int type = PD_HEADER_TYPE(head);
+
+ switch (type) {
+ case PD_CTRL_GOOD_CRC:
+ /* should not get it */
+ break;
+ case PD_CTRL_PING:
+ /* Nothing else to do */
+ break;
+ case PD_CTRL_GET_SOURCE_CAP:
+ send_source_cap(ctxt);
+ break;
+#ifdef CONFIG_USB_PD_DUAL_ROLE
+ case PD_CTRL_GET_SINK_CAP:
+ send_sink_cap(ctxt);
+ break;
+ case PD_CTRL_GOTO_MIN:
+ break;
+ case PD_CTRL_PS_RDY:
+ if (pd_role == PD_ROLE_SINK)
+ pd_task_state = PD_STATE_SNK_READY;
+ break;
+#endif /* CONFIG_USB_PD_DUAL_ROLE */
+ case PD_CTRL_ACCEPT:
+ break;
+ case PD_CTRL_REJECT:
+ break;
+ case PD_CTRL_PROTOCOL_ERR:
+ case PD_CTRL_SWAP:
+ case PD_CTRL_WAIT:
+ case PD_CTRL_SOFT_RESET:
+ default:
+ CPRINTF("Unhandled ctrl message type %d\n", type);
+ }
+}
+
+static void handle_request(void *ctxt, uint16_t head, uint32_t *payload)
+{
+ int cnt = PD_HEADER_CNT(head);
+ int p;
+
+ if (PD_HEADER_TYPE(head) != 1 || cnt)
+ send_goodcrc(ctxt, PD_HEADER_ID(head));
+
+ /* dump received packet content */
+ CPRINTF("RECV %04x/%d ", head, cnt);
+ for (p = 0; p < cnt; p++)
+ CPRINTF("[%d]%08x ", p, payload[p]);
+ CPRINTF("\n");
+
+ if (cnt)
+ handle_data_request(ctxt, head, payload);
+ else
+ handle_ctrl_request(ctxt, head, payload);
+}
+
+static inline int decode_short(void *ctxt, int off, uint16_t *val16)
+{
+ uint32_t w;
+ int end;
+
+ end = pd_dequeue_bits(ctxt, off, 20, &w);
+
+#if 0 /* DEBUG */
+ CPRINTF("%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(void *ctxt, int off, uint32_t *val32)
+{
+ off = decode_short(ctxt, off, (uint16_t *)val32);
+ return decode_short(ctxt, off, ((uint16_t *)val32 + 1));
+}
+
+static int analyze_rx(uint32_t *payload)
+{
+ int bit;
+ char *msg = "---";
+ uint32_t val = 0;
+ uint16_t header;
+ uint32_t pcrc, ccrc;
+ int p, cnt;
+ /* uint32_t eop; */
+ void *ctxt;
+
+ crc32_init();
+ ctxt = pd_init_dequeue();
+
+ /* Detect preamble */
+ bit = pd_find_preamble(ctxt);
+ if (bit < 0) {
+ msg = "Preamble";
+ goto packet_err;
+ }
+
+ /* Find the Start Of Packet sequence */
+ while (bit > 0) {
+ bit = pd_dequeue_bits(ctxt, bit, 20, &val);
+ if (val == PD_SOP)
+ break;
+ /* TODO: detect SOP with 1 error code */
+ /* TODO: detect Hard reset */
+ }
+ if (bit < 0) {
+ msg = "SOP";
+ goto packet_err;
+ }
+
+ /* read header */
+ bit = decode_short(ctxt, bit, &header);
+ crc32_hash16(header);
+ cnt = PD_HEADER_CNT(header);
+
+ /* read payload data */
+ for (p = 0; p < cnt && bit > 0; p++) {
+ bit = decode_word(ctxt, bit, payload+p);
+ crc32_hash32(payload[p]);
+ }
+ if (bit < 0) {
+ msg = "len";
+ goto packet_err;
+ }
+
+ /* check transmitted CRC */
+ bit = decode_word(ctxt, bit, &pcrc);
+ ccrc = crc32_result();
+ if (bit < 0 || pcrc != ccrc) {
+ msg = "CRC";
+ if (pcrc != ccrc)
+ bit = PD_ERR_CRC;
+ /* DEBUG */CPRINTF("CRC %08x <> %08x\n", pcrc, crc32_result());
+ goto packet_err;
+ }
+
+ /* check End Of Packet */
+ /* SKIP EOP for now
+ bit = pd_dequeue_bits(ctxt, bit, 5, &eop);
+ if (bit < 0 || eop != PD_EOP) {
+ msg = "EOP";
+ goto packet_err;
+ }
+ */
+
+ return header;
+packet_err:
+ if (debug_dump)
+ pd_dump_packet(ctxt, msg);
+ else
+ CPRINTF("RX ERR (%d)\n", bit);
+ return bit;
+}
+
+static void execute_hard_reset(void)
+{
+ pd_message_id = 0;
+#ifdef CONFIG_USB_PD_DUAL_ROLE
+ pd_task_state = pd_role == PD_ROLE_SINK ? PD_STATE_SNK_DISCONNECTED
+ : PD_STATE_SRC_DISCONNECTED;
+#else
+ pd_task_state = PD_STATE_SRC_DISCONNECTED;
+#endif
+ pd_power_supply_reset();
+ CPRINTF("HARD RESET!\n");
+}
+
+void pd_task(void)
+{
+ int head;
+ void *ctxt = pd_hw_init();
+ uint32_t payload[7];
+ int timeout = 10000;
+ uint32_t evt;
+ int cc1_volt, cc2_volt;
+ int res;
+
+ /* Ensure the power supply is in the default state */
+ pd_power_supply_reset();
+
+ while (1) {
+ /* monitor for incoming packet */
+ pd_rx_enable_monitoring();
+ /* wait for next event/packet or timeout expiration */
+ evt = task_wait_event(timeout);
+ /* incoming packet ? */
+ if (evt & PD_EVENT_RX) {
+ head = analyze_rx(payload);
+ pd_rx_complete();
+ if (head > 0)
+ handle_request(ctxt, head, payload);
+ else if (head == PD_ERR_HARD_RESET)
+ execute_hard_reset();
+ }
+ timeout = -1;
+ switch (pd_task_state) {
+ case PD_STATE_DISABLED:
+ /* Nothing to do */
+ break;
+ case PD_STATE_SRC_DISCONNECTED:
+ /* Vnc monitoring */
+ cc1_volt = adc_read_channel(ADC_CH_CC1_PD);
+ cc2_volt = adc_read_channel(ADC_CH_CC2_PD);
+ if ((cc1_volt < PD_SRC_VNC) ||
+ (cc2_volt < PD_SRC_VNC)) {
+ pd_polarity = !(cc1_volt < PD_SRC_VNC);
+ pd_task_state = PD_STATE_SRC_DISCOVERY;
+ }
+ timeout = 10000;
+ break;
+ case PD_STATE_SRC_DISCOVERY:
+ /* Query capabilites of the other side */
+ res = send_source_cap(ctxt);
+ /* packet was acked => PD capable device) */
+ if (res >= 0) {
+ pd_task_state = PD_STATE_SRC_NEGOCIATE;
+ } else { /* failed, retry later */
+ timeout = PD_T_SEND_SOURCE_CAP;
+ }
+ break;
+ case PD_STATE_SRC_NEGOCIATE:
+ /* wait for a "Request" message */
+ break;
+ case PD_STATE_SRC_ACCEPTED:
+ /* Accept sent, wait for the end of transition */
+ timeout = PD_POWER_SUPPLY_TRANSITION_DELAY;
+ pd_task_state = PD_STATE_SRC_TRANSITION;
+ break;
+ case PD_STATE_SRC_TRANSITION:
+ res = pd_set_power_supply_ready();
+ /* TODO error fallback */
+ /* the voltage output is good, notify the source */
+ res = send_control(ctxt, PD_CTRL_PS_RDY);
+ if (res >= 0) {
+ timeout = PD_T_SEND_SOURCE_CAP;
+ /* it'a time to ping regularly the sink */
+ pd_task_state = PD_STATE_SRC_READY;
+ }
+ /* TODO error fallback */
+ break;
+ case PD_STATE_SRC_READY:
+ /* Verify that the sink is alive */
+ res = send_control(ctxt, PD_CTRL_PING);
+ if (res < 0) {
+ /* The sink died ... TODO */
+ pd_task_state = PD_STATE_SRC_DISCOVERY;
+ timeout = PD_T_SEND_SOURCE_CAP;
+ } else { /* schedule next keep-alive */
+ timeout = PD_T_SOURCE_ACTIVITY;
+ }
+ break;
+#ifdef CONFIG_USB_PD_DUAL_ROLE
+ case PD_STATE_SNK_DISCONNECTED:
+ /* Source connection monitoring */
+ cc1_volt = adc_read_channel(ADC_CH_CC1_PD);
+ cc2_volt = adc_read_channel(ADC_CH_CC2_PD);
+ if ((cc1_volt > PD_SNK_VA) ||
+ (cc2_volt > PD_SNK_VA)) {
+ pd_polarity = !(cc1_volt > PD_SNK_VA);
+ pd_task_state = PD_STATE_SNK_DISCOVERY;
+ }
+ timeout = 10000;
+ break;
+ case PD_STATE_SNK_DISCOVERY:
+ res = send_control(ctxt, PD_CTRL_GET_SOURCE_CAP);
+ /* packet was acked => PD capable device) */
+ if (res >= 0) {
+ pd_task_state = PD_STATE_SNK_TRANSITION;
+ } else { /* failed, retry later */
+ timeout = PD_T_GET_SOURCE_CAP;
+ }
+ break;
+ case PD_STATE_SNK_TRANSITION:
+ break;
+ case PD_STATE_SNK_READY:
+ /* we have power and we are happy */
+ break;
+#endif /* CONFIG_USB_PD_DUAL_ROLE */
+ case PD_STATE_HARD_RESET:
+ send_hard_reset(ctxt);
+ /* reset our own state machine */
+ execute_hard_reset();
+ break;
+ case PD_STATE_BIST:
+ send_bist(ctxt);
+ pd_task_state = PD_STATE_DISABLED;
+ break;
+ }
+ }
+}
+
+void pd_rx_event(void)
+{
+ task_set_event(TASK_ID_PD, PD_EVENT_RX, 0);
+}
+
+#ifdef CONFIG_COMMON_RUNTIME
+static int command_pd(int argc, char **argv)
+{
+ if (argc < 2)
+ return EC_ERROR_PARAM1;
+
+ if (!strcasecmp(argv[1], "tx")) {
+ pd_task_state = PD_STATE_SNK_DISCOVERY;
+ task_wake(TASK_ID_PD);
+ } else if (!strcasecmp(argv[1], "rx")) {
+ pd_rx_event();
+ } else if (!strcasecmp(argv[1], "bist")) {
+ pd_task_state = PD_STATE_BIST;
+ task_wake(TASK_ID_PD);
+ } else if (!strcasecmp(argv[1], "charger")) {
+ pd_role = PD_ROLE_SOURCE;
+ pd_set_host_mode(1);
+ pd_task_state = PD_STATE_SRC_DISCONNECTED;
+ task_wake(TASK_ID_PD);
+ } else if (!strncasecmp(argv[1], "dev", 3)) {
+ if (argc >= 3) {
+ unsigned max_volt;
+ char *e;
+
+ max_volt = strtoi(argv[2], &e, 10);
+ pd_set_max_voltage(max_volt * 1000);
+ }
+
+ pd_role = PD_ROLE_SINK;
+ pd_set_host_mode(0);
+ pd_task_state = PD_STATE_SNK_DISCONNECTED;
+ task_wake(TASK_ID_PD);
+ } else if (!strcasecmp(argv[1], "clock")) {
+ int freq;
+ char *e;
+
+ if (argc < 3)
+ return EC_ERROR_PARAM2;
+
+ freq = strtoi(argv[2], &e, 10);
+ if (*e)
+ return EC_ERROR_PARAM2;
+ pd_set_clock(freq);
+ ccprintf("set TX frequency to %d Hz\n", freq);
+ } else if (!strcasecmp(argv[1], "dump")) {
+ debug_dump = !debug_dump;
+ } else if (!strncasecmp(argv[1], "hard", 4)) {
+ pd_task_state = PD_STATE_HARD_RESET;
+ task_wake(TASK_ID_PD);
+ } else if (!strncasecmp(argv[1], "ping", 4)) {
+ pd_role = PD_ROLE_SOURCE;
+ pd_set_host_mode(1);
+ pd_task_state = PD_STATE_SRC_READY;
+ task_wake(TASK_ID_PD);
+ } else if (!strncasecmp(argv[1], "state", 5)) {
+ const char * const state_names[] = {
+ "DISABLED",
+ "SNK_DISCONNECTED", "SNK_DISCOVERY", "SNK_TRANSITION",
+ "SNK_READY",
+ "SRC_DISCONNECTED", "SRC_DISCOVERY", "SRC_NEGOCIATE",
+ "SRC_READY",
+ "HARD_RESET", "BIST",
+ };
+ ccprintf("Role: %s Polarity: CC%d State: %s\n",
+ pd_role == PD_ROLE_SOURCE ? "SRC" : "SNK",
+ pd_polarity + 1, state_names[pd_task_state]);
+ } else {
+ return EC_ERROR_PARAM1;
+ }
+
+ return EC_SUCCESS;
+}
+DECLARE_CONSOLE_COMMAND(pd, command_pd,
+ "[rx|tx|hardreset|clock|connect]",
+ "USB PD",
+ NULL);
+#endif /* CONFIG_COMMON_RUNTIME */
diff --git a/include/console.h b/include/console.h
index a6cce1fa8c..5cd02bb5c8 100644
--- a/include/console.h
+++ b/include/console.h
@@ -51,6 +51,7 @@ enum console_channel {
CC_TASK,
CC_THERMAL,
CC_USBCHARGE,
+ CC_USBPD,
CC_VBOOT,
CC_HOOK,
/* Channel count; not itself a channel */
diff --git a/include/usb_pd.h b/include/usb_pd.h
new file mode 100644
index 0000000000..8c70fe5d50
--- /dev/null
+++ b/include/usb_pd.h
@@ -0,0 +1,265 @@
+/* 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.
+ */
+
+/* USB Power delivery module */
+
+#ifndef __USB_PD_H
+#define __USB_PD_H
+
+#include "common.h"
+
+enum pd_errors {
+ PD_ERR_INVAL = -1, /* Invalid packet */
+ PD_ERR_HARD_RESET = -2, /* Got a Hard-Reset packet */
+ PD_ERR_CRC = -3, /* CRC mismatch */
+ PD_ERR_ID = -4, /* Invalid ID number */
+};
+
+/* incoming packet event (for the USB PD task) */
+#define PD_EVENT_RX (1<<2)
+
+/* --- PD data message helpers --- */
+
+/* PDO : Power Data Object */
+/*
+ * 1. The vSafe5V Fixed Supply Object shall always be the first object.
+ * 2. The remaining Fixed Supply Objects,
+ * if present, shall be sent in voltage order; lowest to highest.
+ * 3. The Battery Supply Objects,
+ * if present shall be sent in Minimum Voltage order; lowest to highest.
+ * 4. The Variable Supply (non battery) Objects,
+ * if present, shall be sent in Minimum Voltage order; lowest to highest.
+ */
+#define PDO_TYPE_FIXED (0 << 30)
+#define PDO_TYPE_BATTERY (1 << 30)
+#define PDO_TYPE_VARIABLE (2 << 30)
+#define PDO_TYPE_MASK (3 << 30)
+
+#define PDO_FIXED_DUAL_ROLE (1 << 29) /* Dual role device */
+#define PDO_FIXED_SUSPEND (1 << 28) /* USB Suspend supported */
+#define PDO_FIXED_EXTERNAL (1 << 27) /* Externally powered */
+#define PDO_FIXED_COMM_CAP (1 << 26) /* USB Communications Capable */
+#define PDO_FIXED_PEAK_CURR () /* [21..20] Peak current */
+#define PDO_FIXED_VOLT(mv) (((mv)/50) << 10) /* Voltage in 50mV units */
+#define PDO_FIXED_CURR(ma) (((ma)/10) << 0) /* Max current in 10mA units */
+
+#define PDO_FIXED(mv, ma, flags) (PDO_FIXED_VOLT(mv) |\
+ PDO_FIXED_CURR(ma) | (flags))
+
+#define PDO_VAR_MAX_VOLT(mv) ((((mv) / 50) & 0x3FF) << 20)
+#define PDO_VAR_MIN_VOLT(mv) ((((mv) / 50) & 0x3FF) << 10)
+#define PDO_VAR_OP_CURR(ma) ((((ma) / 10) & 0x3FF) << 0)
+
+#define PDO_VAR(min_mv, max_mv, op_ma) \
+ (PDO_VAR_MIN_VOLT(min_mv) | \
+ PDO_VAR_MAX_VOLT(max_mv) | \
+ PDO_VAR_OP_CURR(op_ma))
+
+#define PDO_BATT_MAX_VOLT(mv) ((((mv) / 50) & 0x3FF) << 20)
+#define PDO_BATT_MIN_VOLT(mv) ((((mv) / 50) & 0x3FF) << 10)
+#define PDO_BATT_OP_POWER(mw) ((((mw) / 10) & 0x3FF) << 0)
+
+#define PDO_BATT(min_mv, max_mv, op_mw) \
+ (PDO_BATT_MIN_VOLT(min_mv) | \
+ PDO_BATT_MAX_VOLT(max_mv) | \
+ PDO_BATT_OP_POWER(op_mw))
+
+/* RDO : Request Data Object */
+#define RDO_OBJ_POS(n) (((n) & 0x7) << 28)
+#define RDO_GIVE_BACK (1 << 27)
+#define RDO_CAP_MISMATCH (1 << 26)
+#define RDO_COMM_CAP (1 << 25)
+#define RDO_NO_SUSPEND (1 << 24)
+#define RDO_FIXED_VAR_OP_CURR(ma) ((((ma) / 10) & 0x3FF) << 10)
+#define RDO_FIXED_VAR_MAX_CURR(ma) ((((ma) / 10) & 0x3FF) << 0)
+
+#define RDO_BATT_OP_POWER(mw) ((((mw) / 250) & 0x3FF) << 10)
+#define RDO_BATT_MAX_POWER(mw) ((((mw) / 250) & 0x3FF) << 10)
+
+#define RDO_FIXED(n, op_ma, max_ma, flags) \
+ (RDO_OBJ_POS(n) | (flags) | \
+ RDO_FIXED_VAR_OP_CURR(op_ma) | \
+ RDO_FIXED_VAR_MAX_CURR(max_ma))
+
+
+#define RDO_BATT(n, op_mw, max_mw, flags) \
+ (RDO_OBJ_POS(n) | (flags) | \
+ RDO_BATT_OP_POWER(op_mw) | \
+ RDO_BATT_MAX_POWER(max_mw))
+
+/* BDO : BIST Data Object */
+#define BDO_MODE_RECV (0 << 28)
+#define BDO_MODE_TRANSMIT (1 << 28)
+#define BDO_MODE_COUNTERS (2 << 28)
+#define BDO_MODE_CARRIER0 (3 << 28)
+#define BDO_MODE_CARRIER1 (4 << 28)
+#define BDO_MODE_CARRIER2 (5 << 28)
+#define BDO_MODE_CARRIER3 (6 << 28)
+#define BDO_MODE_EYE (7 << 28)
+
+#define BDO(mode, cnt) ((mode) | ((cnt) & 0xFFFF))
+
+/* VDO : Vendor Defined Message Object */
+#define VDO(vid, custom) (((vid) << 16) | ((custom) & 0xFFFF))
+
+/* --- Policy layer functions --- */
+
+/**
+ * Decide which voltage to use from the source capabilities.
+ *
+ * @param cnt the number of Power Data Objects.
+ * @param src_caps Power Data Objects representing the source capabilities.
+ * @param rdo requested Request Data Object.
+ * @return EC_SUCCESS if the RDO is filled with valid data, <0 else.
+ */
+int pd_choose_voltage(int cnt, uint32_t *src_caps, uint32_t *rdo);
+
+/**
+ * Put a cap on the max voltage requested as a sink.
+ * @param mv maximum voltage in millivolts.
+ */
+void pd_set_max_voltage(unsigned mv);
+
+/**
+ * Request a new operating voltage.
+ *
+ * @param rdo Request Data Object with the selected operating point.
+ * @return EC_SUCCESS if we can get the requested voltage/OP, <0 else.
+ */
+int pd_request_voltage(uint32_t rdo);
+
+/**
+ * Go back to the default/safe state of the power supply
+ */
+void pd_power_supply_reset(void);
+
+/**
+ * Enable the power supply output after the ready delay.
+ *
+ * @return EC_SUCCESS if the power supply is ready, <0 else.
+ */
+int pd_set_power_supply_ready(void);
+
+/* Power Data Objects for the source and the sink */
+extern const uint32_t pd_src_pdo[];
+extern const int pd_src_pdo_cnt;
+extern const uint32_t pd_snk_pdo[];
+extern const int pd_snk_pdo_cnt;
+
+/* --- Physical layer functions : chip specific --- */
+
+/* Packet preparation/retrieval */
+
+/**
+ * Prepare packet reading state machine.
+ *
+ * @return opaque context for other reading functions.
+ */
+void *pd_init_dequeue(void);
+
+/**
+ * Prepare packet reading state machine.
+ *
+ * @param ctxt opaque context.
+ * @param off current position in the packet buffer.
+ * @param len minimum size to read in bits.
+ * @param val the read bits.
+ * @return new position in the packet buffer.
+ */
+int pd_dequeue_bits(void *ctxt, int off, int len, uint32_t *val);
+
+/**
+ * Advance until the end of the preamble.
+ *
+ * @param ctxt opaque context.
+ * @return new position in the packet buffer.
+ */
+int pd_find_preamble(void *ctxt);
+
+/**
+ * Write the preamble in the TX buffer.
+ *
+ * @param ctxt opaque context.
+ * @return new position in the packet buffer.
+ */
+int pd_write_preamble(void *ctxt);
+
+/**
+ * Write one 10-period symbol in the TX packet.
+ * corresponding to a quartet with 4b5b encoding
+ * and Biphase Mark Coding.
+ *
+ * @param ctxt opaque context.
+ * @param bit_off current position in the packet buffer.
+ * @param val10 the 10-bit integer.
+ * @return new position in the packet buffer.
+ */
+int pd_write_sym(void *ctxt, int bit_off, uint32_t val10);
+
+
+/**
+ * Ensure that we have an edge after EOP and we end up at level 0,
+ * also fill the last byte.
+ *
+ * @param ctxt opaque context.
+ * @param bit_off current position in the packet buffer.
+ * @return new position in the packet buffer.
+ */
+int pd_write_last_edge(void *ctxt, int bit_off);
+
+/**
+ * Dump the current PD packet on the console for debug.
+ *
+ * @param ctxt opaque context.
+ * @param msg context string.
+ */
+void pd_dump_packet(void *ctxt, const char *msg);
+
+/**
+ * Change the TX data clock frequency.
+ *
+ * @param freq frequency in hertz.
+ */
+void pd_set_clock(int freq);
+
+/**
+ * Enable/Disable the host pull-up on CC.
+ *
+ * @param enable non null if we are a host / power source.
+ */
+void pd_set_host_mode(int enable);
+
+/* TX/RX callbacks */
+
+/**
+ * Start sending over the wire the prepared packet.
+ *
+ * @param ctxt opaque context.
+ * @param bit_len size of the packet in bits.
+ */
+void pd_start_tx(void *ctxt, int bit_len);
+/* Call when we are done sending a packet */
+void pd_tx_done(void);
+
+/* Callback when the hardware has detected an incoming packet */
+void pd_rx_event(void);
+/* Start sampling the CC line for reception */
+void pd_rx_start(void);
+/* Call when we are done reading a packet */
+void pd_rx_complete(void);
+
+/* restart listening to the CC wire */
+void pd_rx_enable_monitoring(void);
+/* stop listening to the CC wire during transmissions */
+void pd_rx_disable_monitoring(void);
+
+/**
+ * Initialize the hardware used for PD RX/TX.
+ *
+ * @return opaque context for other functions.
+ */
+void *pd_hw_init(void);
+
+#endif /* __USB_PD_H */