Files
OpenCellular/common/usb_pd_protocol.c
Vincent Palatin c817472c07 Update Zinger flashing process
Convert the Zinger flash update commands to the new RSA signature
mechanism.

Signed-off-by: Vincent Palatin <vpalatin@chromium.org>

BRANCH=samus
BUG=chrome-os-partner:28336
TEST=from the workstation:
./util/flash_pd.py -m 1 build/zinger/ec.RW.bin
from Samus command-line :
ectool --name=cros_pd flashpd 0 1 ec.RW.bin

Change-Id: Ie8cd7f644ec94e461c5775a4dbbcd408782c72e1
Reviewed-on: https://chromium-review.googlesource.com/221560
Reviewed-by: Alec Berg <alecaberg@chromium.org>
Tested-by: Vincent Palatin <vpalatin@chromium.org>
Commit-Queue: Vincent Palatin <vpalatin@chromium.org>
2014-10-07 03:37:48 +00:00

2035 lines
52 KiB
C

/* 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 "chipset.h"
#include "common.h"
#include "console.h"
#include "crc.h"
#include "ec_commands.h"
#include "gpio.h"
#include "hooks.h"
#include "host_command.h"
#include "registers.h"
#include "sha1.h"
#include "system.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)
#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;
#else
#define CPRINTF(format, args...)
const int debug_level;
#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))
/* 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 is based '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 1
* Ra open pwr cable no UFP 2
* 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)
* TODO(crosbug.com/p/31197): Need to identify necessary polarity switching for
* debug dongle.
*
*/
#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 GET_POLARITY(cc1, cc2) (CC_RD(cc2) || CC_RA(cc1))
/* 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 (100*MSEC) /* between 100ms and 200ms */
#define PD_T_SINK_WAIT_CAP (240*MSEC) /* between 210ms and 250ms */
#define PD_T_SOURCE_ACTIVITY (45*MSEC) /* between 40ms and 50ms */
#define PD_T_SENDER_RESPONSE (30*MSEC) /* between 24ms and 30ms */
#define PD_T_PS_TRANSITION (220*MSEC) /* between 200ms and 220ms */
#define PD_T_DRP_HOLD (120*MSEC) /* between 100ms and 150ms */
#define PD_T_DRP_LOCK (120*MSEC) /* between 100ms and 150ms */
/* DRP_SNK + DRP_SRC must be between 50ms and 100ms with 30%-70% duty cycle */
#define PD_T_DRP_SNK (40*MSEC) /* toggle time for sink DRP */
#define PD_T_DRP_SRC (30*MSEC) /* toggle time for source DRP */
/* 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
enum vdm_states {
VDM_STATE_ERR_BUSY = -3,
VDM_STATE_ERR_SEND = -2,
VDM_STATE_ERR_TMOUT = -1,
VDM_STATE_DONE = 0,
/* Anything >0 represents an active state */
VDM_STATE_READY = 1,
VDM_STATE_BUSY = 2,
};
#ifdef CONFIG_USB_PD_DUAL_ROLE
/* Port dual-role state */
enum pd_dual_role_states drp_state = PD_DRP_TOGGLE_OFF;
/* Last received source cap */
static uint32_t pd_src_caps[PD_PORT_COUNT][PDO_MAX_OBJECTS];
static int pd_src_cap_cnt[PD_PORT_COUNT];
static int new_power_request;
#endif
static struct pd_protocol {
/* current port role */
uint8_t role;
/* 3-bit rolling message ID counter */
uint8_t msg_id;
/* Port polarity : 0 => CC1 is CC line, 1 => CC2 is CC line */
uint8_t polarity;
/* PD state for port */
enum pd_states task_state;
/* PD state when we run state handler the last time */
enum pd_states last_state;
/* The state to go to after timeout */
enum pd_states timeout_state;
/* Timeout for the current state. Set to 0 for no timeout. */
uint64_t timeout;
/* Flag for sending pings in SRC_READY */
uint8_t ping_enabled;
#ifdef CONFIG_USB_PD_DUAL_ROLE
/* Current limit based on the last request message */
uint32_t curr_limit;
#endif
/* PD state for Vendor Defined Messages */
enum vdm_states vdm_state;
/* next Vendor Defined Message to send */
uint32_t vdo_data[VDO_MAX_SIZE];
uint8_t vdo_count;
/* Attached ChromeOS device id & RW hash */
uint8_t dev_id;
uint32_t dev_rw_hash[SHA1_DIGEST_SIZE/4];
} pd[PD_PORT_COUNT];
/*
* PD communication enabled flag. When false, PD state machine still
* detects source/sink connection and disconnection, and will still
* provide VBUS, but never sends any PD communication.
*/
static uint8_t pd_comm_enabled = CONFIG_USB_PD_COMM_ENABLED;
struct mutex pd_crc_lock;
/*
* 4 entry rw_hash table of type-C devices that AP has firmware updates for.
*/
#ifdef CONFIG_COMMON_RUNTIME
#define RW_HASH_ENTRIES 4
static struct ec_params_usb_pd_rw_hash_entry rw_hash_table[RW_HASH_ENTRIES];
#endif
static inline void set_state_timeout(int port,
uint64_t timeout,
enum pd_states timeout_state)
{
pd[port].timeout = timeout;
pd[port].timeout_state = timeout_state;
}
/* Return flag for pd state is connected */
int pd_is_connected(int port)
{
if (pd[port].task_state == PD_STATE_DISABLED)
return 0;
#ifdef CONFIG_USB_PD_DUAL_ROLE
/* Check if sink is connected */
if (pd[port].role == PD_ROLE_SINK)
return pd[port].task_state != PD_STATE_SNK_DISCONNECTED;
#endif
/* Must be a source */
return pd[port].task_state != PD_STATE_SRC_DISCONNECTED;
}
static inline void set_state(int port, enum pd_states next_state)
{
enum pd_states last_state = pd[port].task_state;
#ifdef CONFIG_LOW_POWER_IDLE
int i;
#endif
set_state_timeout(port, 0, 0);
pd[port].task_state = next_state;
#ifdef CONFIG_USBC_SS_MUX
if (next_state == PD_STATE_SRC_DISCONNECTED) {
pd[port].dev_id = 0;
board_set_usb_mux(port, TYPEC_MUX_NONE,
pd[port].polarity);
}
#endif
#ifdef CONFIG_LOW_POWER_IDLE
/* If any PD port is connected, then disable deep sleep */
for (i = 0; i < PD_PORT_COUNT; i++) {
if (pd_is_connected(i))
break;
}
if (i == PD_PORT_COUNT)
enable_sleep(SLEEP_MASK_USB_PD);
else
disable_sleep(SLEEP_MASK_USB_PD);
#endif
/* Log state transition, except for toggling between sink and source */
if (last_state == next_state)
return;
#ifdef CONFIG_USB_PD_DUAL_ROLE
if ((last_state == PD_STATE_SNK_DISCONNECTED &&
next_state == PD_STATE_SRC_DISCONNECTED) ||
(last_state == PD_STATE_SRC_DISCONNECTED &&
next_state == PD_STATE_SNK_DISCONNECTED))
return;
#endif
CPRINTF("C%d st%d\n", port, next_state);
}
/* increment message ID counter */
static void inc_id(int port)
{
pd[port].msg_id = (pd[port].msg_id + 1) & PD_MESSAGE_ID_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]);
}
static inline 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 */
static 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 analyze_rx(int port, uint32_t *payload);
static void analyze_rx_bist(int port);
static void send_hard_reset(int port)
{
int off;
/* If PD communication is disabled, return */
if (!pd_comm_enabled)
return;
if (debug_level >= 1)
CPRINTF("Sending hard reset\n");
/* 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 */
pd_start_tx(port, pd[port].polarity, off);
pd_tx_done(port, pd[port].polarity);
}
static int send_validate_message(int port, uint16_t header,
uint8_t cnt, const uint32_t *data)
{
int r;
static uint32_t payload[7];
/* If PD communication is disabled, return error */
if (!pd_comm_enabled)
return -2;
/* 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 */
pd_start_tx(port, pd[port].polarity, bit_len);
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 (r) {
pd_rx_enable_monitoring(port);
/* Message receive timeout is 2.7ms */
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 = analyze_rx(port, payload);
pd_rx_complete(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 == pd[port].msg_id) {
/* got the GoodCRC we were expecting */
inc_id(port);
/* 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 -4;
/* CPRINTF("ERR ACK/%d %04x\n", id, head); */
}
}
}
/* we failed all the re-transmissions */
/* TODO: try HardReset */
if (debug_level >= 1)
CPRINTF("TX NO ACK %04x/%d\n", header, cnt);
return -1;
}
static int send_control(int port, int type)
{
int bit_len;
uint16_t header = PD_HEADER(type, pd[port].role,
pd[port].msg_id, 0);
bit_len = send_validate_message(port, header, 0, NULL);
if (debug_level >= 1)
CPRINTF("CTRL[%d]>%d\n", type, bit_len);
return bit_len;
}
static void send_goodcrc(int port, int id)
{
uint16_t header = PD_HEADER(PD_CTRL_GOOD_CRC, pd[port].role, id, 0);
int bit_len = prepare_message(port, header, 0, NULL);
/* If PD communication is disabled, return */
if (!pd_comm_enabled)
return;
pd_start_tx(port, pd[port].polarity, bit_len);
pd_tx_done(port, pd[port].polarity);
}
static int send_source_cap(int port)
{
int bit_len;
#ifdef CONFIG_USB_PD_DYNAMIC_SRC_CAP
const uint32_t *src_pdo;
const int src_pdo_cnt = pd_get_source_pdo(&src_pdo);
#else
const uint32_t *src_pdo = pd_src_pdo;
const int src_pdo_cnt = pd_src_pdo_cnt;
#endif
uint16_t header = PD_HEADER(PD_DATA_SOURCE_CAP, pd[port].role,
pd[port].msg_id, src_pdo_cnt);
bit_len = send_validate_message(port, header, src_pdo_cnt, src_pdo);
if (debug_level >= 1)
CPRINTF("srcCAP>%d\n", bit_len);
return bit_len;
}
#ifdef CONFIG_USB_PD_DUAL_ROLE
static void send_sink_cap(int port)
{
int bit_len;
uint16_t header = PD_HEADER(PD_DATA_SINK_CAP, pd[port].role,
pd[port].msg_id, pd_snk_pdo_cnt);
bit_len = send_validate_message(port, header, pd_snk_pdo_cnt,
pd_snk_pdo);
if (debug_level >= 1)
CPRINTF("snkCAP>%d\n", bit_len);
}
static int send_request(int port, uint32_t rdo)
{
int bit_len;
uint16_t header = PD_HEADER(PD_DATA_REQUEST, pd[port].role,
pd[port].msg_id, 1);
bit_len = send_validate_message(port, header, 1, &rdo);
if (debug_level >= 1)
CPRINTF("REQ%d>\n", bit_len);
return bit_len;
}
#endif /* CONFIG_USB_PD_DUAL_ROLE */
static int send_bist_cmd(int port)
{
/* currently only support sending bist carrier 2 */
uint32_t bdo = BDO(BDO_MODE_CARRIER2, 0);
int bit_len;
uint16_t header = PD_HEADER(PD_DATA_BIST, pd[port].role,
pd[port].msg_id, 1);
bit_len = send_validate_message(port, header, 1, &bdo);
CPRINTF("BIST>%d\n", bit_len);
return bit_len;
}
static void bist_mode_2_tx(int port)
{
int bit;
/* If PD communication is not allowed, return */
if (!pd_comm_enabled)
return;
CPRINTF("BIST carrier 2 - sending on port %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 (will never end) */
pd_tx_set_circular_mode(port);
pd_start_tx(port, pd[port].polarity, bit);
/* do not let pd task state machine run anymore */
while (1)
task_wait_event(-1);
}
static void bist_mode_2_rx(int port)
{
/* monitor for incoming packet */
pd_rx_enable_monitoring(port);
/* loop until we start receiving data */
while (1) {
task_wait_event(500*MSEC);
/* incoming packet ? */
if (pd_rx_started(port))
break;
}
/*
* once we start receiving bist data, do not
* let state machine run again. stay here, and
* analyze a chunk of data every 250ms.
*/
while (1) {
analyze_rx_bist(port);
pd_rx_complete(port);
msleep(250);
pd_rx_enable_monitoring(port);
}
}
static void handle_vdm_request(int port, int cnt, uint32_t *payload)
{
uint16_t vid = PD_VDO_VID(payload[0]);
#ifdef CONFIG_USB_PD_CUSTOM_VDM
int rlen;
uint32_t *rdata;
#endif
if (vid == USB_VID_GOOGLE) {
if (pd[port].vdm_state == VDM_STATE_BUSY)
pd[port].vdm_state = VDM_STATE_DONE;
#ifdef CONFIG_USB_PD_CUSTOM_VDM
rlen = pd_custom_vdm(port, cnt, payload, &rdata);
if (rlen > 0) {
uint16_t header = PD_HEADER(PD_DATA_VENDOR_DEF,
pd[port].role, pd[port].msg_id,
rlen);
send_validate_message(port, header, rlen, rdata);
}
#endif
return;
}
if (debug_level >= 1)
CPRINTF("Unhandled VDM VID %04x CMD %04x\n",
vid, payload[0] & 0xFFFF);
}
static void execute_hard_reset(int port)
{
pd[port].msg_id = 0;
#ifdef CONFIG_USB_PD_DUAL_ROLE
set_state(port, pd[port].role == PD_ROLE_SINK ?
PD_STATE_SNK_DISCONNECTED : PD_STATE_SRC_DISCONNECTED);
/* Clear the input current limit */
pd_set_input_current_limit(0);
#else
set_state(port, PD_STATE_SRC_DISCONNECTED);
#endif
pd_power_supply_reset(port);
CPRINTF("HARD RESET!\n");
}
static void execute_soft_reset(int port)
{
pd[port].msg_id = 0;
#ifdef CONFIG_USB_PD_DUAL_ROLE
set_state(port, pd[port].role == PD_ROLE_SINK ?
PD_STATE_SNK_DISCOVERY : PD_STATE_SRC_DISCOVERY);
#else
set_state(port, PD_STATE_SRC_DISCOVERY);
#endif
CPRINTF("Soft Reset\n");
}
void pd_soft_reset(void)
{
int i;
for (i = 0; i < PD_PORT_COUNT; ++i)
if (pd_is_connected(i)) {
set_state(i, PD_STATE_SOFT_RESET);
task_wake(PORT_TO_TASK_ID(i));
}
}
#ifdef CONFIG_USB_PD_DUAL_ROLE
static void pd_store_src_cap(int port, int cnt, uint32_t *src_caps)
{
int i;
pd_src_cap_cnt[port] = cnt;
for (i = 0; i < cnt; i++)
pd_src_caps[port][i] = *src_caps++;
}
static void pd_send_request_msg(int port)
{
uint32_t rdo;
int res;
/* we were waiting for them, let's process them */
res = pd_choose_voltage(pd_src_cap_cnt[port], pd_src_caps[port], &rdo);
if (res >= 0) {
pd[port].curr_limit = res;
res = send_request(port, rdo);
if (res >= 0)
set_state(port, PD_STATE_SNK_REQUESTED);
else
/*
* for now: ignore failure here,
* we will retry ...
* TODO(crosbug.com/p/28332)
*/
set_state(port, PD_STATE_SNK_REQUESTED);
}
/*
* TODO(crosbug.com/p/28332): if pd_choose_voltage
* returns an error, ignore failure for now.
*/
}
#endif
static void handle_data_request(int port, 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[port].task_state == PD_STATE_SNK_DISCOVERY)
|| (pd[port].task_state == PD_STATE_SNK_TRANSITION)
|| (pd[port].task_state == PD_STATE_SNK_READY)) {
pd_store_src_cap(port, cnt, payload);
pd_send_request_msg(port);
}
break;
#endif /* CONFIG_USB_PD_DUAL_ROLE */
case PD_DATA_REQUEST:
if ((pd[port].role == PD_ROLE_SOURCE) && (cnt == 1))
if (!pd_request_voltage(payload[0])) {
send_control(port, PD_CTRL_ACCEPT);
set_state(port, PD_STATE_SRC_ACCEPTED);
return;
}
/* the message was incorrect or cannot be satisfied */
send_control(port, PD_CTRL_REJECT);
break;
case PD_DATA_BIST:
/* currently only support sending bist carrier mode 2 */
if ((payload[0] >> 28) == 5)
/* bist data object mode is 2 */
bist_mode_2_tx(port);
break;
case PD_DATA_SINK_CAP:
break;
case PD_DATA_VENDOR_DEF:
handle_vdm_request(port, cnt, payload);
break;
default:
CPRINTF("Unhandled data message type %d\n", type);
}
}
static void handle_ctrl_request(int port, uint16_t head,
uint32_t *payload)
{
int type = PD_HEADER_TYPE(head);
int res;
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:
res = send_source_cap(port);
if ((res >= 0) &&
(pd[port].task_state == PD_STATE_SRC_DISCOVERY))
set_state(port, PD_STATE_SRC_NEGOCIATE);
break;
#ifdef CONFIG_USB_PD_DUAL_ROLE
case PD_CTRL_GET_SINK_CAP:
send_sink_cap(port);
break;
case PD_CTRL_GOTO_MIN:
break;
case PD_CTRL_PS_RDY:
if (pd[port].task_state == PD_STATE_SNK_DISCOVERY) {
/* Don't know what power source is ready. Reset. */
set_state(port, PD_STATE_HARD_RESET);
} else if (pd[port].role == PD_ROLE_SINK) {
set_state(port, PD_STATE_SNK_READY);
pd_set_input_current_limit(pd[port].curr_limit);
}
break;
case PD_CTRL_REJECT:
set_state(port, PD_STATE_SNK_DISCOVERY);
break;
#endif /* CONFIG_USB_PD_DUAL_ROLE */
case PD_CTRL_ACCEPT:
if (pd[port].task_state == PD_STATE_SOFT_RESET) {
#ifdef CONFIG_USB_PD_DUAL_ROLE
set_state(port, pd[port].role == PD_ROLE_SINK ?
PD_STATE_SNK_DISCOVERY :
PD_STATE_SRC_DISCOVERY);
#else
set_state(port, PD_STATE_SRC_DISCOVERY);
#endif
}
break;
case PD_CTRL_SOFT_RESET:
execute_soft_reset(port);
/* We are done, acknowledge with an Accept packet */
send_control(port, PD_CTRL_ACCEPT);
break;
case PD_CTRL_PROTOCOL_ERR:
case PD_CTRL_SWAP:
case PD_CTRL_WAIT:
default:
CPRINTF("Unhandled ctrl message type %d\n", type);
}
}
static void handle_request(int port, uint16_t head,
uint32_t *payload)
{
int cnt = PD_HEADER_CNT(head);
int p;
if (PD_HEADER_TYPE(head) != PD_CTRL_GOOD_CRC || cnt)
send_goodcrc(port, PD_HEADER_ID(head));
/* dump received packet content (only dump ping at debug level 2) */
if ((debug_level == 1 && PD_HEADER_TYPE(head) != PD_CTRL_PING) ||
debug_level >= 2) {
CPRINTF("RECV %04x/%d ", head, cnt);
for (p = 0; p < cnt; p++)
CPRINTF("[%d]%08x ", p, payload[p]);
CPRINTF("\n");
}
/*
* If we are in disconnected state, we shouldn't get a request. Do
* a hard reset if we get one.
*/
if (!pd_is_connected(port))
set_state(port, PD_STATE_HARD_RESET);
if (cnt)
handle_data_request(port, head, payload);
else
handle_ctrl_request(port, head, payload);
}
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));
}
static int count_set_bits(int n)
{
int count = 0;
while (n) {
n &= (n - 1);
count++;
}
return count;
}
static void analyze_rx_bist(int port)
{
int i = 0, bit = -1;
uint32_t w, match;
int invalid_bits = 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("Could not find any bytes of alternating bits\n");
return;
}
/*
* 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 % 20 == 0)
CPRINTF("\n");
CPRINTF("%02x ", w);
invalid_bits += count_set_bits(w ^ match);
}
total_invalid_bits += invalid_bits;
CPRINTF("- incorrect bits: %d / %d\n", invalid_bits,
total_invalid_bits);
}
static int 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 < 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;
/* TODO: detect SOP with 1 error code */
/* TODO: detect Hard reset */
}
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_ERR_CRC;
if (debug_level >= 1)
/* DEBUG */CPRINTF("CRC %08x <> %08x\n", pcrc, ccrc);
goto packet_err;
}
/* check End Of Packet */
/* SKIP EOP for now
bit = pd_dequeue_bits(port, bit, 5, &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);
return bit;
}
void pd_send_vdm(int port, uint32_t vid, int cmd, const uint32_t *data,
int count)
{
int i;
if (count > VDO_MAX_SIZE - 1) {
CPRINTF("VDM over max size\n");
return;
}
pd[port].vdo_data[0] = VDO(vid, cmd);
pd[port].vdo_count = count + 1;
for (i = 1; i < count + 1; i++)
pd[port].vdo_data[i] = data[i-1];
/* Set ready, pd task will actually send */
pd[port].vdm_state = VDM_STATE_READY;
task_wake(PORT_TO_TASK_ID(port));
}
static void pd_vdm_send_state_machine(int port)
{
int res;
uint16_t header;
static uint64_t vdm_timeout;
switch (pd[port].vdm_state) {
case VDM_STATE_READY:
/* Only transmit VDM if connected */
if (!pd_is_connected(port)) {
pd[port].vdm_state = VDM_STATE_ERR_BUSY;
break;
}
/* Prepare and send VDM */
header = PD_HEADER(PD_DATA_VENDOR_DEF, pd[port].role,
pd[port].msg_id, (int)pd[port].vdo_count);
res = send_validate_message(port, header,
pd[port].vdo_count,
pd[port].vdo_data);
if (res < 0) {
pd[port].vdm_state = VDM_STATE_ERR_SEND;
} else {
pd[port].vdm_state = VDM_STATE_BUSY;
vdm_timeout = get_time().val + 500*MSEC;
}
break;
case VDM_STATE_BUSY:
/* Wait for VDM response or timeout */
if (get_time().val > vdm_timeout)
pd[port].vdm_state = VDM_STATE_ERR_TMOUT;
break;
default:
break;
}
}
static inline void pd_dev_dump_info(uint8_t dev_id, uint32_t *hash)
{
int j;
ccprintf("Device:%d Hash:", dev_id);
for (j = 0; j < SHA1_DIGEST_SIZE/4; j++)
ccprintf(" 0x%08x", hash[j]);
ccprintf("\n");
}
void pd_dev_store_rw_hash(int port, uint8_t dev_id, uint32_t *rw_hash)
{
pd[port].dev_id = dev_id;
memcpy(pd[port].dev_rw_hash, rw_hash, SHA1_DIGEST_SIZE);
if (debug_level >= 1)
pd_dev_dump_info(dev_id, rw_hash);
}
#ifdef CONFIG_USB_PD_DUAL_ROLE
void pd_set_dual_role(enum pd_dual_role_states state)
{
int i;
drp_state = state;
for (i = 0; i < PD_PORT_COUNT; i++) {
/*
* Change to sink if port is currently a source AND (new DRP
* state is force sink OR new DRP state is toggle off and we
* are in the source disconnected state).
*/
if (pd[i].role == PD_ROLE_SOURCE &&
(drp_state == PD_DRP_FORCE_SINK ||
(drp_state == PD_DRP_TOGGLE_OFF
&& pd[i].task_state == PD_STATE_SRC_DISCONNECTED))) {
pd[i].role = PD_ROLE_SINK;
set_state(i, PD_STATE_SNK_DISCONNECTED);
pd_set_host_mode(i, 0);
task_wake(PORT_TO_TASK_ID(i));
}
/*
* Change to source if port is currently a sink and the
* new DRP state is force source.
*/
if (pd[i].role == PD_ROLE_SINK &&
drp_state == PD_DRP_FORCE_SOURCE) {
pd[i].role = PD_ROLE_SOURCE;
set_state(i, PD_STATE_SRC_DISCONNECTED);
pd_set_host_mode(i, 1);
task_wake(PORT_TO_TASK_ID(i));
}
}
}
#endif
int pd_get_polarity(int port)
{
return pd[port].polarity;
}
void pd_comm_enable(int enable)
{
pd_comm_enabled = enable;
#ifdef CONFIG_USB_PD_DUAL_ROLE
/*
* If communications are enabled, start hard reset timer for
* any port in PD_SNK_DISCOVERY.
*/
if (enable) {
int i;
for (i = 0; i < PD_PORT_COUNT; i++) {
if (pd[i].task_state == PD_STATE_SNK_DISCOVERY)
set_state_timeout(i,
get_time().val +
PD_T_SINK_WAIT_CAP,
PD_STATE_HARD_RESET);
}
}
#endif
}
void pd_ping_enable(int port, int enable)
{
pd[port].ping_enabled = enable;
}
void pd_task(void)
{
int head;
int port = TASK_ID_TO_PORT(task_get_current());
uint32_t payload[7];
int timeout = 10*MSEC;
int cc1_volt, cc2_volt;
int res;
#ifdef CONFIG_USB_PD_DUAL_ROLE
uint64_t next_role_swap = PD_T_DRP_SNK;
#endif
enum pd_states this_state;
timestamp_t now;
int caps_count = 0;
/* Initialize TX pins and put them in Hi-Z */
pd_tx_init();
/* Initialize PD protocol state variables for each port. */
pd[port].role = PD_ROLE_DEFAULT;
pd[port].vdm_state = VDM_STATE_DONE;
pd[port].ping_enabled = 0;
set_state(port, PD_DEFAULT_STATE);
/* Ensure the power supply is in the default state */
pd_power_supply_reset(port);
/* Initialize physical layer */
pd_hw_init(port);
while (1) {
/* process VDM messages last */
pd_vdm_send_state_machine(port);
/* monitor for incoming packet if in a connected state */
if (pd_is_connected(port) && pd_comm_enabled)
pd_rx_enable_monitoring(port);
else
pd_rx_disable_monitoring(port);
/* Verify board specific health status : current, voltages... */
res = pd_board_checks();
if (res != EC_SUCCESS) {
/* cut the power */
execute_hard_reset(port);
/* notify the other side of the issue */
send_hard_reset(port);
}
/* wait for next event/packet or timeout expiration */
task_wait_event(timeout);
/* incoming packet ? */
if (pd_rx_started(port) && pd_comm_enabled) {
head = analyze_rx(port, payload);
pd_rx_complete(port);
if (head > 0)
handle_request(port, head, payload);
else if (head == PD_ERR_HARD_RESET)
execute_hard_reset(port);
}
/* if nothing to do, verify the state of the world in 500ms */
this_state = pd[port].task_state;
timeout = 500*MSEC;
switch (this_state) {
case PD_STATE_DISABLED:
/* Nothing to do */
break;
case PD_STATE_SRC_DISCONNECTED:
timeout = 10*MSEC;
/* Vnc monitoring */
cc1_volt = pd_adc_read(port, 0);
cc2_volt = pd_adc_read(port, 1);
if ((cc1_volt < PD_SRC_VNC) ||
(cc2_volt < PD_SRC_VNC)) {
pd[port].polarity =
GET_POLARITY(cc1_volt, cc2_volt);
pd_select_polarity(port, pd[port].polarity);
/* Set to USB SS initially */
#ifdef CONFIG_USBC_SS_MUX
board_set_usb_mux(port, TYPEC_MUX_USB,
pd[port].polarity);
#endif
/* Enable VBUS */
if (pd_set_power_supply_ready(port)) {
#ifdef CONFIG_USBC_SS_MUX
board_set_usb_mux(port, TYPEC_MUX_NONE,
pd[port].polarity);
#endif
break;
}
set_state(port, PD_STATE_SRC_DISCOVERY);
caps_count = 0;
#ifdef CONFIG_USB_PD_DUAL_ROLE
/* Keep VBUS up for the hold period */
next_role_swap = get_time().val + PD_T_DRP_HOLD;
#endif
}
#ifdef CONFIG_USB_PD_DUAL_ROLE
/* Swap roles if time expired or VBUS is present */
else if (drp_state != PD_DRP_FORCE_SOURCE &&
(get_time().val >= next_role_swap ||
pd_snk_is_vbus_provided(port))) {
pd[port].role = PD_ROLE_SINK;
set_state(port, PD_STATE_SNK_DISCONNECTED);
pd_set_host_mode(port, 0);
next_role_swap = get_time().val + PD_T_DRP_SNK;
/* Swap states quickly */
timeout = 2*MSEC;
}
#endif
break;
case PD_STATE_SRC_DISCOVERY:
/* Send source cap some minimum number of times */
if (caps_count < PD_CAPS_COUNT) {
/* Query capabilites of the other side */
res = send_source_cap(port);
/* packet was acked => PD capable device) */
if (res >= 0) {
set_state(port,
PD_STATE_SRC_NEGOCIATE);
caps_count = 0;
} else { /* failed, retry later */
timeout = PD_T_SEND_SOURCE_CAP;
caps_count++;
}
}
break;
case PD_STATE_SRC_NEGOCIATE:
/* wait for a "Request" message */
timeout = 500*MSEC;
break;
case PD_STATE_SRC_ACCEPTED:
/* Accept sent, wait for the end of transition */
if (pd[port].last_state != pd[port].task_state)
set_state_timeout(
port,
get_time().val +
PD_POWER_SUPPLY_TRANSITION_DELAY,
PD_STATE_SRC_TRANSITION);
timeout = 10 * MSEC;
break;
case PD_STATE_SRC_TRANSITION:
res = pd_set_power_supply_ready(port);
/* TODO error fallback */
/* the voltage output is good, notify the source */
res = send_control(port, PD_CTRL_PS_RDY);
if (res >= 0) {
timeout = PD_T_SEND_SOURCE_CAP;
/* it'a time to ping regularly the sink */
set_state(port, PD_STATE_SRC_READY);
} else {
/* The sink did not ack, cut the power... */
pd_power_supply_reset(port);
set_state(port, PD_STATE_SRC_DISCONNECTED);
}
break;
case PD_STATE_SRC_READY:
if (!pd[port].ping_enabled) {
timeout = PD_T_SOURCE_ACTIVITY;
break;
}
/* Verify that the sink is alive */
res = send_control(port, PD_CTRL_PING);
if (res >= 0) {
/* schedule next keep-alive */
timeout = PD_T_SOURCE_ACTIVITY;
break;
}
/* Ping dropped. Try soft reset. */
set_state(port, PD_STATE_SOFT_RESET);
timeout = 10 * MSEC;
break;
#ifdef CONFIG_USB_PD_DUAL_ROLE
case PD_STATE_SUSPENDED:
pd_rx_disable_monitoring(port);
pd_hw_release(port);
pd_power_supply_reset(port);
/* Wait for resume */
while (pd[port].task_state == PD_STATE_SUSPENDED)
task_wait_event(-1);
pd_hw_init(port);
break;
case PD_STATE_SNK_DISCONNECTED:
timeout = 10*MSEC;
/* Source connection monitoring */
if (pd_snk_is_vbus_provided(port)) {
cc1_volt = pd_adc_read(port, 0);
cc2_volt = pd_adc_read(port, 1);
if ((cc1_volt >= PD_SNK_VA) ||
(cc2_volt >= PD_SNK_VA)) {
pd[port].polarity =
GET_POLARITY(cc1_volt,
cc2_volt);
pd_select_polarity(port,
pd[port].polarity);
#ifdef CONFIG_USB_PD_READ_INFO_ON_CONNECT
/* Send google VDM to read info */
pd_send_vdm(port, USB_VID_GOOGLE,
VDO_CMD_READ_INFO, NULL, 0);
#endif
set_state(port, PD_STATE_SNK_DISCOVERY);
timeout = 10*MSEC;
}
} else if (drp_state == PD_DRP_TOGGLE_ON &&
get_time().val >= next_role_swap) {
/* Swap roles to source */
pd[port].role = PD_ROLE_SOURCE;
set_state(port, PD_STATE_SRC_DISCONNECTED);
pd_set_host_mode(port, 1);
next_role_swap = get_time().val + PD_T_DRP_SRC;
/* Swap states quickly */
timeout = 2*MSEC;
}
break;
case PD_STATE_SNK_DISCOVERY:
/* Wait for source cap expired only if we are enabled */
if ((pd[port].last_state != pd[port].task_state)
&& pd_comm_enabled)
set_state_timeout(port,
get_time().val +
PD_T_SINK_WAIT_CAP,
PD_STATE_HARD_RESET);
timeout = 10 * MSEC;
break;
case PD_STATE_SNK_REQUESTED:
/* Ensure the power supply actually becomes ready */
set_state(port, PD_STATE_SNK_TRANSITION);
timeout = 10 * MSEC;
break;
case PD_STATE_SNK_TRANSITION:
/* Wait for PS_READY */
if (pd[port].last_state != pd[port].task_state)
set_state_timeout(port,
get_time().val +
PD_T_PS_TRANSITION,
PD_STATE_SNK_DISCOVERY);
timeout = 10 * MSEC;
break;
case PD_STATE_SNK_READY:
/* we have power, check vitals from time to time */
if (new_power_request) {
pd_send_request_msg(port);
new_power_request = 0;
}
timeout = 100*MSEC;
break;
#endif /* CONFIG_USB_PD_DUAL_ROLE */
case PD_STATE_SOFT_RESET:
if (pd[port].last_state != pd[port].task_state)
execute_soft_reset(port);
res = send_control(port, PD_CTRL_SOFT_RESET);
/* if soft reset failed, try hard reset. */
if (res < 0) {
set_state(port, PD_STATE_HARD_RESET);
break;
}
set_state_timeout(
port,
get_time().val + PD_T_SENDER_RESPONSE,
PD_STATE_HARD_RESET);
break;
case PD_STATE_HARD_RESET:
send_hard_reset(port);
/* reset our own state machine */
execute_hard_reset(port);
break;
case PD_STATE_BIST:
send_bist_cmd(port);
bist_mode_2_rx(port);
break;
default:
break;
}
pd[port].last_state = this_state;
/*
* Check for state timeout, and if not check if need to adjust
* timeout value to wake up on the next state timeout.
*/
now = get_time();
if (pd[port].timeout && now.val >= pd[port].timeout)
set_state(port, pd[port].timeout_state);
else if (pd[port].timeout - now.val < timeout)
timeout = pd[port].timeout - now.val;
/* Check for disconnection */
if (!pd_is_connected(port))
continue;
if (pd[port].role == PD_ROLE_SOURCE) {
/* Source: detect disconnect by monitoring CC */
cc1_volt = pd_adc_read(port, pd[port].polarity);
#ifdef CONFIG_USB_PD_DUAL_ROLE
if (cc1_volt > PD_SRC_VNC &&
get_time().val >= next_role_swap) {
/* Stay a source port for lock period */
next_role_swap = get_time().val + PD_T_DRP_LOCK;
#else
if (cc1_volt > PD_SRC_VNC) {
#endif
pd_power_supply_reset(port);
set_state(port, PD_STATE_SRC_DISCONNECTED);
/* Debouncing */
timeout = 50*MSEC;
}
}
#ifdef CONFIG_USB_PD_DUAL_ROLE
if (pd[port].role == PD_ROLE_SINK &&
!pd_snk_is_vbus_provided(port)) {
/* Sink: detect disconnect by monitoring VBUS */
set_state(port, PD_STATE_SNK_DISCONNECTED);
/* Clear the input current limit */
pd_set_input_current_limit(0);
/* set timeout small to reconnect fast */
timeout = 5*MSEC;
}
#endif /* CONFIG_USB_PD_DUAL_ROLE */
}
}
void pd_rx_event(int port)
{
task_set_event(PORT_TO_TASK_ID(port), PD_EVENT_RX, 0);
}
#ifdef CONFIG_USB_PD_DUAL_ROLE
static void dual_role_on(void)
{
pd_set_dual_role(PD_DRP_TOGGLE_ON);
CPRINTS("chipset -> S0, enable dual-role toggling");
}
DECLARE_HOOK(HOOK_CHIPSET_RESUME, dual_role_on, HOOK_PRIO_DEFAULT);
static void dual_role_off(void)
{
pd_set_dual_role(PD_DRP_TOGGLE_OFF);
CPRINTS("chipset -> S3, disable dual-role toggling");
}
DECLARE_HOOK(HOOK_CHIPSET_SUSPEND, dual_role_off, HOOK_PRIO_DEFAULT);
DECLARE_HOOK(HOOK_CHIPSET_STARTUP, dual_role_off, HOOK_PRIO_DEFAULT);
static void dual_role_force_sink(void)
{
pd_set_dual_role(PD_DRP_FORCE_SINK);
CPRINTS("chipset -> S5, force dual-role port to sink");
}
DECLARE_HOOK(HOOK_CHIPSET_SHUTDOWN, dual_role_force_sink, HOOK_PRIO_DEFAULT);
#ifdef HAS_TASK_CHIPSET
static void dual_role_init(void)
{
if (chipset_in_state(CHIPSET_STATE_ANY_OFF))
dual_role_force_sink();
else if (chipset_in_state(CHIPSET_STATE_SUSPEND))
dual_role_off();
else /* CHIPSET_STATE_ON */
dual_role_on();
}
DECLARE_HOOK(HOOK_INIT, dual_role_init, HOOK_PRIO_DEFAULT);
#endif /* HAS_TASK_CHIPSET */
#endif /* CONFIG_USB_PD_DUAL_ROLE */
#ifdef CONFIG_COMMON_RUNTIME
void pd_set_suspend(int port, int enable)
{
set_state(port, enable ? PD_STATE_SUSPENDED : PD_DEFAULT_STATE);
task_wake(PORT_TO_TASK_ID(port));
}
static int hex8tou32(char *str, uint32_t *val)
{
char *ptr = str;
uint32_t tmp = 0;
while (*ptr) {
char c = *ptr++;
if (c >= '0' && c <= '9')
tmp = (tmp << 4) + (c - '0');
else if (c >= 'A' && c <= 'F')
tmp = (tmp << 4) + (c - 'A' + 10);
else if (c >= 'a' && c <= 'f')
tmp = (tmp << 4) + (c - 'a' + 10);
else
return EC_ERROR_INVAL;
}
if (ptr != str + 8)
return EC_ERROR_INVAL;
*val = tmp;
return EC_SUCCESS;
}
static int remote_flashing(int argc, char **argv)
{
int port, cnt, cmd;
uint32_t data[VDO_MAX_SIZE-1];
char *e;
static int flash_offset[PD_PORT_COUNT];
if (argc < 4 || argc > (VDO_MAX_SIZE + 4 - 1))
return EC_ERROR_PARAM_COUNT;
port = strtoi(argv[1], &e, 10);
if (*e || port >= PD_PORT_COUNT)
return EC_ERROR_PARAM2;
cnt = 0;
if (!strcasecmp(argv[3], "erase")) {
cmd = VDO_CMD_FLASH_ERASE;
flash_offset[port] = 0;
ccprintf("ERASE ...");
} else if (!strcasecmp(argv[3], "reboot")) {
cmd = VDO_CMD_REBOOT;
ccprintf("REBOOT ...");
} else if (!strcasecmp(argv[3], "signature")) {
cmd = VDO_CMD_ERASE_SIG;
ccprintf("ERASE SIG ...");
} else if (!strcasecmp(argv[3], "info")) {
cmd = VDO_CMD_READ_INFO;
ccprintf("INFO...");
} else if (!strcasecmp(argv[3], "version")) {
cmd = VDO_CMD_VERSION;
ccprintf("VERSION...");
} else {
int i;
argc -= 3;
for (i = 0; i < argc; i++)
if (hex8tou32(argv[i+3], data + i))
return EC_ERROR_INVAL;
cmd = VDO_CMD_FLASH_WRITE;
cnt = argc;
ccprintf("WRITE %d @%04x ...", argc * 4,
flash_offset[port]);
flash_offset[port] += argc * 4;
}
pd_send_vdm(port, USB_VID_GOOGLE, cmd, data, cnt);
/* Wait until VDM is done */
while (pd[port].vdm_state > 0)
task_wait_event(100*MSEC);
ccprintf("DONE %d\n", pd[port].vdm_state);
return EC_SUCCESS;
}
void pd_request_source_voltage(int port, int mv)
{
pd_set_max_voltage(mv);
if (pd[port].task_state == PD_STATE_SNK_READY) {
/* Set flag to send new power request in pd_task */
new_power_request = 1;
} else {
pd[port].role = PD_ROLE_SINK;
pd_set_host_mode(port, 0);
set_state(port, PD_STATE_SNK_DISCONNECTED);
}
task_wake(PORT_TO_TASK_ID(port));
}
static int command_pd(int argc, char **argv)
{
int port;
char *e;
if (argc < 2)
return EC_ERROR_PARAM_COUNT;
/* command: pd <subcmd> <args> */
if (!strcasecmp(argv[1], "dualrole")) {
if (argc < 3) {
ccprintf("dual-role toggling: ");
switch (drp_state) {
case PD_DRP_TOGGLE_ON:
ccprintf("on\n");
break;
case PD_DRP_TOGGLE_OFF:
ccprintf("off\n");
break;
case PD_DRP_FORCE_SINK:
ccprintf("force sink\n");
break;
case PD_DRP_FORCE_SOURCE:
ccprintf("force source\n");
break;
}
} else {
if (!strcasecmp(argv[2], "on"))
pd_set_dual_role(PD_DRP_TOGGLE_ON);
else if (!strcasecmp(argv[2], "off"))
pd_set_dual_role(PD_DRP_TOGGLE_OFF);
else if (!strcasecmp(argv[2], "sink"))
pd_set_dual_role(PD_DRP_FORCE_SINK);
else if (!strcasecmp(argv[2], "source"))
pd_set_dual_role(PD_DRP_FORCE_SOURCE);
else
return EC_ERROR_PARAM3;
}
return EC_SUCCESS;
} else if (!strcasecmp(argv[1], "dump")) {
int level;
if (argc < 3)
ccprintf("dump level: %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_enable(enable);
ccprintf("Ports %s\n", enable ? "enabled" : "disabled");
return EC_SUCCESS;
} else if (!strncasecmp(argv[1], "rwhashtable", 3)) {
int i;
struct ec_params_usb_pd_rw_hash_entry *p;
for (i = 0; i < RW_HASH_ENTRIES; i++) {
p = &rw_hash_table[i];
pd_dev_dump_info(p->dev_id, p->dev_rw_hash.w);
}
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], "tx")) {
set_state(port, PD_STATE_SNK_DISCOVERY);
task_wake(PORT_TO_TASK_ID(port));
} else if (!strcasecmp(argv[2], "bist")) {
set_state(port, PD_STATE_BIST);
task_wake(PORT_TO_TASK_ID(port));
} else if (!strcasecmp(argv[2], "charger")) {
pd[port].role = PD_ROLE_SOURCE;
pd_set_host_mode(port, 1);
set_state(port, PD_STATE_SRC_DISCONNECTED);
task_wake(PORT_TO_TASK_ID(port));
} else if (!strncasecmp(argv[2], "dev", 3)) {
int max_volt = -1;
if (argc >= 4)
max_volt = strtoi(argv[3], &e, 10) * 1000;
pd_request_source_voltage(port, max_volt);
} else 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);
} else if (!strncasecmp(argv[2], "hard", 4)) {
set_state(port, PD_STATE_HARD_RESET);
task_wake(PORT_TO_TASK_ID(port));
} else if (!strncasecmp(argv[2], "hash", 4)) {
int i;
for (i = 0; i < SHA1_DIGEST_SIZE / 4; i++)
ccprintf("%08x ", pd[port].dev_rw_hash[i]);
ccprintf("\n");
} else if (!strncasecmp(argv[2], "soft", 4)) {
set_state(port, PD_STATE_SOFT_RESET);
task_wake(PORT_TO_TASK_ID(port));
} else if (!strncasecmp(argv[2], "ping", 4)) {
int enable;
if (argc > 3) {
enable = strtoi(argv[3], &e, 10);
if (*e)
return EC_ERROR_PARAM3;
pd[port].ping_enabled = enable;
}
ccprintf("Pings %s\n", pd[port].ping_enabled ? "on" : "off");
} else if (!strncasecmp(argv[2], "vdm", 3)) {
if (argc < 4)
return EC_ERROR_PARAM_COUNT;
if (!strncasecmp(argv[3], "ping", 4)) {
uint32_t enable;
if (argc < 5)
return EC_ERROR_PARAM_COUNT;
enable = strtoi(argv[4], &e, 10);
if (*e)
return EC_ERROR_PARAM4;
pd_send_vdm(port, USB_VID_GOOGLE, VDO_CMD_PING_ENABLE,
&enable, 1);
} else if (!strncasecmp(argv[3], "curr", 4)) {
pd_send_vdm(port, USB_VID_GOOGLE, VDO_CMD_CURRENT,
NULL, 0);
} else {
return EC_ERROR_PARAM_COUNT;
}
} else if (!strncasecmp(argv[2], "flash", 4)) {
return remote_flashing(argc, argv);
} else if (!strncasecmp(argv[2], "state", 5)) {
const char * const state_names[] = {
"DISABLED", "SUSPENDED",
"SNK_DISCONNECTED", "SNK_DISCOVERY", "SNK_REQUESTED",
"SNK_TRANSITION", "SNK_READY",
"SRC_DISCONNECTED", "SRC_DISCOVERY", "SRC_NEGOCIATE",
"SRC_ACCEPTED", "SRC_TRANSITION", "SRC_READY",
"SOFT_RESET", "HARD_RESET", "BIST",
};
BUILD_ASSERT(ARRAY_SIZE(state_names) == PD_STATE_COUNT);
ccprintf("Port C%d, %s - Role: %s Polarity: CC%d State: %s\n",
port, pd_comm_enabled ? "Enabled" : "Disabled",
pd[port].role == PD_ROLE_SOURCE ? "SRC" : "SNK",
pd[port].polarity + 1,
state_names[pd[port].task_state]);
} else {
return EC_ERROR_PARAM1;
}
return EC_SUCCESS;
}
DECLARE_CONSOLE_COMMAND(pd, command_pd,
"dualrole|dump|enable [0|1]|rwhashtable|\n\t<port> "
"[tx|bist|charger|clock|dev"
"|soft|hash|hard|ping|state|vdm [ping | curr]]",
"USB PD",
NULL);
#ifdef CONFIG_USBC_SS_MUX
static int command_typec(int argc, char **argv)
{
const char * const mux_name[] = {"none", "usb", "dp", "dock"};
char *e;
int port;
enum typec_mux mux = TYPEC_MUX_NONE;
int i;
if (argc < 2)
return EC_ERROR_PARAM_COUNT;
port = strtoi(argv[1], &e, 10);
if (*e || port >= PD_PORT_COUNT)
return EC_ERROR_PARAM1;
if (argc < 3) {
const char *dp_str, *usb_str;
ccprintf("Port C%d: CC1 %d mV CC2 %d mV (polarity:CC%d)\n",
port, pd_adc_read(port, 0), pd_adc_read(port, 1),
pd_get_polarity(port) + 1);
if (board_get_usb_mux(port, &dp_str, &usb_str))
ccprintf("Superspeed %s%s%s\n",
dp_str ? dp_str : "",
dp_str && usb_str ? "+" : "",
usb_str ? usb_str : "");
else
ccprintf("No Superspeed connection\n");
return EC_SUCCESS;
}
for (i = 0; i < ARRAY_SIZE(mux_name); i++)
if (!strcasecmp(argv[2], mux_name[i]))
mux = i;
board_set_usb_mux(port, mux, pd_get_polarity(port));
return EC_SUCCESS;
}
DECLARE_CONSOLE_COMMAND(typec, command_typec,
"<port> [none|usb|dp|dock]",
"Control type-C connector muxing",
NULL);
#endif /* CONFIG_USBC_SS_MUX */
static const enum pd_dual_role_states dual_role_map[USB_PD_CTRL_ROLE_COUNT] = {
[USB_PD_CTRL_ROLE_TOGGLE_ON] = PD_DRP_TOGGLE_ON,
[USB_PD_CTRL_ROLE_TOGGLE_OFF] = PD_DRP_TOGGLE_OFF,
[USB_PD_CTRL_ROLE_FORCE_SINK] = PD_DRP_FORCE_SINK,
[USB_PD_CTRL_ROLE_FORCE_SOURCE] = PD_DRP_FORCE_SOURCE,
};
#ifdef CONFIG_USBC_SS_MUX
static const enum typec_mux typec_mux_map[USB_PD_CTRL_MUX_COUNT] = {
[USB_PD_CTRL_MUX_NONE] = TYPEC_MUX_NONE,
[USB_PD_CTRL_MUX_USB] = TYPEC_MUX_USB,
[USB_PD_CTRL_MUX_AUTO] = TYPEC_MUX_DP,
[USB_PD_CTRL_MUX_DP] = TYPEC_MUX_DP,
[USB_PD_CTRL_MUX_DOCK] = TYPEC_MUX_DOCK,
};
#endif
static int hc_usb_pd_control(struct host_cmd_handler_args *args)
{
const struct ec_params_usb_pd_control *p = args->params;
struct ec_response_usb_pd_control *r = args->response;
if (p->role >= USB_PD_CTRL_ROLE_COUNT ||
p->mux >= USB_PD_CTRL_MUX_COUNT)
return EC_RES_INVALID_PARAM;
if (p->role != USB_PD_CTRL_ROLE_NO_CHANGE)
pd_set_dual_role(dual_role_map[p->role]);
#ifdef CONFIG_USBC_SS_MUX
if (p->mux != USB_PD_CTRL_MUX_NO_CHANGE)
board_set_usb_mux(p->port, typec_mux_map[p->mux],
pd_get_polarity(p->port));
#endif /* CONFIG_USBC_SS_MUX */
r->enabled = pd_comm_enabled;
r->role = pd[p->port].role;
r->polarity = pd[p->port].polarity;
r->state = pd[p->port].task_state;
args->response_size = sizeof(*r);
return EC_RES_SUCCESS;
}
DECLARE_HOST_COMMAND(EC_CMD_USB_PD_CONTROL,
hc_usb_pd_control,
EC_VER_MASK(0));
static int hc_remote_flash(struct host_cmd_handler_args *args)
{
const struct ec_params_usb_pd_fw_update *p = args->params;
int port = p->port;
const uint32_t *data = &(p->size) + 1;
int i, size;
if (p->size + sizeof(*p) > args->params_size)
return EC_RES_INVALID_PARAM;
switch (p->cmd) {
case USB_PD_FW_REBOOT:
ccprintf("PD Update - Reboot\n");
pd_send_vdm(port, USB_VID_GOOGLE, VDO_CMD_REBOOT, NULL, 0);
/* Delay to give time for device to reboot */
usleep(750 * MSEC);
return EC_RES_SUCCESS;
case USB_PD_FW_FLASH_ERASE:
ccprintf("PD Update - Erase RW flash\n");
pd_send_vdm(port, USB_VID_GOOGLE, VDO_CMD_FLASH_ERASE, NULL, 0);
/* Wait until VDM is done */
while (pd[port].vdm_state > 0)
task_wait_event(100*MSEC);
break;
case USB_PD_FW_ERASE_SIG:
ccprintf("PD Update - Erase RW RSA signature\n");
pd_send_vdm(port, USB_VID_GOOGLE, VDO_CMD_ERASE_SIG, NULL, 0);
/* Wait until VDM is done */
while (pd[port].vdm_state > 0)
task_wait_event(100*MSEC);
break;
case USB_PD_FW_FLASH_WRITE:
/* Data size must be a multiple of 4 */
if (!p->size || p->size % 4)
return EC_RES_INVALID_PARAM;
size = p->size / 4;
ccprintf("PD Update - Write RW flash\n");
for (i = 0; i < size; i += VDO_MAX_SIZE - 1) {
pd_send_vdm(port, USB_VID_GOOGLE, VDO_CMD_FLASH_WRITE,
data + i, MIN(size - i, VDO_MAX_SIZE - 1));
/* Wait until VDM is done */
while (pd[port].vdm_state > 0)
task_wait_event(10*MSEC);
}
break;
default:
return EC_RES_INVALID_PARAM;
break;
}
if (pd[port].vdm_state < 0)
return EC_RES_ERROR;
else
return EC_RES_SUCCESS;
}
DECLARE_HOST_COMMAND(EC_CMD_USB_PD_FW_UPDATE,
hc_remote_flash,
EC_VER_MASK(0));
static int hc_remote_rw_hash_entry(struct host_cmd_handler_args *args)
{
int i, idx = 0, found = 0;
const struct ec_params_usb_pd_rw_hash_entry *p = args->params;
static int rw_hash_next_idx;
if (!p->dev_id) {
ccprintf("PD RW_HASH - 0 is not a valid device id");
return EC_RES_INVALID_PARAM;
}
for (i = 0; i < RW_HASH_ENTRIES; i++) {
if (p->dev_id == rw_hash_table[i].dev_id) {
idx = i;
found = 1;
break;
}
}
if (!found) {
idx = rw_hash_next_idx;
rw_hash_next_idx = rw_hash_next_idx + 1;
if (rw_hash_next_idx == RW_HASH_ENTRIES)
rw_hash_next_idx = 0;
}
memcpy(&rw_hash_table[idx], p, sizeof(*p));
ccprintf("PD RW_HASH - stored dev_id:%d at index:%d\n",
rw_hash_table[idx].dev_id, idx);
return EC_RES_SUCCESS;
}
DECLARE_HOST_COMMAND(EC_CMD_USB_PD_RW_HASH_ENTRY,
hc_remote_rw_hash_entry,
EC_VER_MASK(0));
static int hc_remote_pd_dev_info(struct host_cmd_handler_args *args)
{
const uint8_t *port = args->params;
struct ec_params_usb_pd_rw_hash_entry *r = args->response;
if (*port >= PD_PORT_COUNT) {
ccprintf("PD DEV_INFO - Port:%d >= %d (max ports)\n",
*port, PD_PORT_COUNT);
return EC_RES_INVALID_PARAM;
}
r->dev_id = pd[*port].dev_id;
ccprintf("PD DEV_INFO - requested Port:%d has Device:%d\n",
*port, r->dev_id);
if (r->dev_id) {
memcpy(r->dev_rw_hash.b, pd[*port].dev_rw_hash,
SHA1_DIGEST_SIZE);
}
args->response_size = sizeof(*r);
return EC_RES_SUCCESS;
}
DECLARE_HOST_COMMAND(EC_CMD_USB_PD_DEV_INFO,
hc_remote_pd_dev_info,
EC_VER_MASK(0));
#endif /* CONFIG_COMMON_RUNTIME */