mirror of
https://github.com/Telecominfraproject/OpenCellular.git
synced 2026-01-08 16:41:55 +00:00
Add support for toggling between source and sink as dual-role port. When transitioning to S0, we turn toggling on, when transitioning to S3, we turn toggling off but remain in the same PD state, and when transitioning to S5, we turn toggling off and force the PD role to a sink. Note, when toggling is off, the source disconnected state is allowed to transition to sink disconnected, but not vice versa. This means that if you go into S3 as a source, it will remain a source until the device is unplugged, at which point it will transition to a sink until the next transition to S0. The spec specifies: tDRP: 50ms - 100ms, Period a DRP shall complete a DFP to UFP and back dcDRP: 30% - 70%, Percent of time that a DRP shall advertise DFP tDRPHold: 100ms - 150ms, time to hold VBUS on after a DRP detects a UFP tDRPLock: 100ms - 150ms, time to stay in DFP after detecting loss of UFP This CL uses 40ms for time as a UFP (sink), 30ms for time as a DFP (source), and 120ms for hold and lock times. Also, if advertising as a DFP (source) and VBUS is detected, this automatically switches to a UFP (sink). BUG=chrome-os-partner:28782 BRANCH=none TEST=test on samus, make sure we are toggling between source and sink when disconnected. make sure plugging in zinger switches state machine through to sink_ready and make sure plugging in a USB switches to source_discovery. tested on a fruitpie by scoping the CC line and verifying the timing (except the hold time which I can't easily test). tested that dual role toggling is off in s3 and s5. also verified that going into s3 as a source keeps the port as a source and going into s5 switches it to a sink. Change-Id: I478634861f694164301d71359da35142ee7ebf75 Signed-off-by: Alec Berg <alecaberg@chromium.org> Reviewed-on: https://chromium-review.googlesource.com/207154 Reviewed-by: Vincent Palatin <vpalatin@chromium.org>
1163 lines
30 KiB
C
1163 lines
30 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 "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
|
|
|
|
/* BMC-supported bit : we are using the baseband variant of the protocol */
|
|
#define PD_BMC_SUPPORTED (1 << 15)
|
|
|
|
/* 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) | \
|
|
PD_BMC_SUPPORTED)
|
|
|
|
#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 (1500*MSEC) /* between 1s and 2s */
|
|
#define PD_T_GET_SOURCE_CAP (1500*MSEC) /* between 1s and 2s */
|
|
#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
|
|
|
|
/* 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 */
|
|
uint8_t pd_polarity;
|
|
|
|
#ifdef CONFIG_USB_PD_DUAL_ROLE
|
|
static uint8_t pd_dual_role_toggle_on;
|
|
#endif
|
|
|
|
static enum {
|
|
PD_STATE_DISABLED,
|
|
#ifdef CONFIG_USB_PD_DUAL_ROLE
|
|
PD_STATE_SUSPENDED,
|
|
PD_STATE_SNK_DISCONNECTED,
|
|
PD_STATE_SNK_DISCOVERY,
|
|
PD_STATE_SNK_REQUESTED,
|
|
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 analyze_rx_bist(void);
|
|
|
|
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, pd_polarity, off);
|
|
pd_tx_done(pd_polarity);
|
|
}
|
|
|
|
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, pd_polarity, bit_len);
|
|
pd_tx_done(pd_polarity);
|
|
/* 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();
|
|
/* 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 */
|
|
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, pd_polarity, bit_len);
|
|
pd_tx_done(pd_polarity);
|
|
}
|
|
|
|
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 int 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);
|
|
|
|
return bit_len;
|
|
}
|
|
#endif /* CONFIG_USB_PD_DUAL_ROLE */
|
|
|
|
static int send_bist_cmd(void *ctxt)
|
|
{
|
|
/* 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_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 bist_mode_2_tx(void *ctxt)
|
|
{
|
|
int bit;
|
|
|
|
CPRINTF("BIST carrier 2 - sending\n");
|
|
|
|
/*
|
|
* build context buffer with 5 bytes, where the data is
|
|
* alternating 1's and 0's.
|
|
*/
|
|
bit = pd_write_sym(ctxt, 0, BMC(0x15));
|
|
bit = pd_write_sym(ctxt, bit, BMC(0x0a));
|
|
bit = pd_write_sym(ctxt, bit, BMC(0x15));
|
|
bit = pd_write_sym(ctxt, bit, BMC(0x0a));
|
|
|
|
/* start a circular DMA transfer (will never end) */
|
|
pd_tx_set_circular_mode();
|
|
pd_start_tx(ctxt, pd_polarity, bit);
|
|
|
|
/* do not let pd task state machine run anymore */
|
|
while (1)
|
|
task_wait_event(-1);
|
|
}
|
|
|
|
static void bist_mode_2_rx(void)
|
|
{
|
|
/* monitor for incoming packet */
|
|
pd_rx_enable_monitoring();
|
|
|
|
/* loop until we start receiving data */
|
|
while (1) {
|
|
task_wait_event(500*MSEC);
|
|
/* incoming packet ? */
|
|
if (pd_rx_started())
|
|
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();
|
|
pd_rx_complete();
|
|
msleep(250);
|
|
pd_rx_enable_monitoring();
|
|
}
|
|
}
|
|
|
|
static void handle_vdm_request(void *ctxt, int cnt, uint32_t *payload)
|
|
{
|
|
uint16_t vid = PD_VDO_VID(payload[0]);
|
|
#ifdef CONFIG_USB_PD_CUSTOM_VDM
|
|
int rlen;
|
|
uint32_t *rdata;
|
|
|
|
if (vid == USB_VID_GOOGLE) {
|
|
rlen = pd_custom_vdm(ctxt, cnt, payload, &rdata);
|
|
if (rlen > 0) {
|
|
uint16_t header = PD_HEADER(PD_DATA_VENDOR_DEF,
|
|
pd_role, pd_message_id, rlen);
|
|
send_validate_message(ctxt, header, rlen, rdata);
|
|
}
|
|
return;
|
|
}
|
|
#endif
|
|
CPRINTF("Unhandled VDM VID %04x CMD %04x\n",
|
|
vid, 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) {
|
|
res = send_request(ctxt, rdo);
|
|
if (res >= 0)
|
|
pd_task_state = PD_STATE_SNK_REQUESTED;
|
|
else
|
|
/*
|
|
* for now: ignore failure here,
|
|
* we will retry ...
|
|
* TODO(crosbug.com/p/28332)
|
|
*/
|
|
pd_task_state = PD_STATE_SNK_REQUESTED;
|
|
}
|
|
/*
|
|
* TODO(crosbug.com/p/28332): if pd_choose_voltage
|
|
* returns an error, ignore failure for now.
|
|
*/
|
|
}
|
|
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:
|
|
/* currently only support sending bist carrier mode 2 */
|
|
if ((payload[0] >> 28) == 5)
|
|
/* bist data object mode is 2 */
|
|
bist_mode_2_tx(ctxt);
|
|
|
|
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);
|
|
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(ctxt);
|
|
if ((res >= 0) && (pd_task_state == PD_STATE_SRC_DISCOVERY))
|
|
pd_task_state = PD_STATE_SRC_NEGOCIATE;
|
|
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;
|
|
case PD_CTRL_REJECT:
|
|
pd_task_state = PD_STATE_SNK_DISCOVERY;
|
|
break;
|
|
#endif /* CONFIG_USB_PD_DUAL_ROLE */
|
|
case PD_CTRL_ACCEPT:
|
|
break;
|
|
case PD_CTRL_SOFT_RESET:
|
|
/* Just reset message counters */
|
|
pd_message_id = 0;
|
|
CPRINTF("Soft Reset\n");
|
|
/* We are done, acknowledge with an Accept packet */
|
|
send_control(ctxt, 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(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 */
|
|
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(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 count_set_bits(int n)
|
|
{
|
|
int count = 0;
|
|
while (n) {
|
|
n &= (n - 1);
|
|
count++;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
static void analyze_rx_bist(void)
|
|
{
|
|
void *ctxt;
|
|
int i = 0, bit = -1;
|
|
uint32_t w, match;
|
|
int invalid_bits = 0;
|
|
static int total_invalid_bits;
|
|
|
|
ctxt = pd_init_dequeue();
|
|
|
|
/* 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(ctxt, 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(ctxt, 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(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");
|
|
}
|
|
|
|
#ifdef CONFIG_USB_PD_DUAL_ROLE
|
|
void pd_set_dual_role(enum pd_dual_role_states dr_state)
|
|
{
|
|
pd_dual_role_toggle_on = (dr_state == PD_DRP_TOGGLE_ON);
|
|
|
|
/* Change to sink if in source disconnected state or if force sink */
|
|
if (pd_role == PD_ROLE_SOURCE &&
|
|
(pd_task_state == PD_STATE_SRC_DISCONNECTED ||
|
|
dr_state == PD_DRP_FORCE_SINK)) {
|
|
pd_role = PD_ROLE_SINK;
|
|
pd_task_state = PD_STATE_SNK_DISCONNECTED;
|
|
pd_set_host_mode(0);
|
|
task_wake(TASK_ID_PD);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/* Return flag for pd state is connected */
|
|
static int pd_is_connected(void)
|
|
{
|
|
if (pd_task_state == PD_STATE_DISABLED)
|
|
return 0;
|
|
|
|
#ifdef CONFIG_USB_PD_DUAL_ROLE
|
|
/* Check if sink is connected */
|
|
if (pd_role == PD_ROLE_SINK)
|
|
return pd_task_state != PD_STATE_SNK_DISCONNECTED;
|
|
#endif
|
|
/* Must be a source */
|
|
return pd_task_state != PD_STATE_SRC_DISCONNECTED;
|
|
}
|
|
|
|
void pd_task(void)
|
|
{
|
|
int head;
|
|
void *ctxt = pd_hw_init();
|
|
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
|
|
|
|
/* Ensure the power supply is in the default state */
|
|
pd_power_supply_reset();
|
|
|
|
while (1) {
|
|
/* monitor for incoming packet if in a connected state */
|
|
if (pd_is_connected())
|
|
pd_rx_enable_monitoring();
|
|
else
|
|
pd_rx_disable_monitoring();
|
|
|
|
/* Verify board specific health status : current, voltages... */
|
|
res = pd_board_checks();
|
|
if (res != EC_SUCCESS) {
|
|
/* cut the power */
|
|
execute_hard_reset();
|
|
/* notify the other side of the issue */
|
|
/* send_hard_reset(ctxt); */
|
|
}
|
|
/* wait for next event/packet or timeout expiration */
|
|
task_wait_event(timeout);
|
|
/* incoming packet ? */
|
|
if (pd_rx_started()) {
|
|
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();
|
|
}
|
|
/* if nothing to do, verify the state of the world in 500ms */
|
|
timeout = 500*MSEC;
|
|
switch (pd_task_state) {
|
|
case PD_STATE_DISABLED:
|
|
/* Nothing to do */
|
|
break;
|
|
case PD_STATE_SRC_DISCONNECTED:
|
|
timeout = 10*MSEC;
|
|
|
|
/* Vnc monitoring */
|
|
cc1_volt = pd_adc_read(0);
|
|
cc2_volt = pd_adc_read(1);
|
|
if ((cc1_volt < PD_SRC_VNC) ||
|
|
(cc2_volt < PD_SRC_VNC)) {
|
|
pd_polarity = !(cc1_volt < PD_SRC_VNC);
|
|
pd_select_polarity(pd_polarity);
|
|
/* Enable VBUS */
|
|
pd_set_power_supply_ready();
|
|
pd_task_state = PD_STATE_SRC_DISCOVERY;
|
|
#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 ((get_time().val >= next_role_swap ||
|
|
pd_snk_is_vbus_provided())) {
|
|
pd_role = PD_ROLE_SINK;
|
|
pd_task_state = PD_STATE_SNK_DISCONNECTED;
|
|
pd_set_host_mode(0);
|
|
next_role_swap = get_time().val + PD_T_DRP_SNK;
|
|
|
|
/* Swap states quickly */
|
|
timeout = 2*MSEC;
|
|
}
|
|
#endif
|
|
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 */
|
|
timeout = 500*MSEC;
|
|
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;
|
|
} else {
|
|
/* The sink did not ack, cut the power... */
|
|
pd_power_supply_reset();
|
|
pd_task_state = PD_STATE_SRC_DISCONNECTED;
|
|
}
|
|
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 ... */
|
|
pd_power_supply_reset();
|
|
pd_task_state = PD_STATE_SRC_DISCONNECTED;
|
|
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_SUSPENDED:
|
|
pd_rx_disable_monitoring();
|
|
pd_hw_release();
|
|
pd_power_supply_reset();
|
|
|
|
/* Wait for resume */
|
|
while (pd_task_state == PD_STATE_SUSPENDED)
|
|
task_wait_event(-1);
|
|
|
|
pd_hw_init();
|
|
break;
|
|
case PD_STATE_SNK_DISCONNECTED:
|
|
timeout = 10*MSEC;
|
|
|
|
/* Source connection monitoring */
|
|
if (pd_snk_is_vbus_provided()) {
|
|
cc1_volt = pd_adc_read(0);
|
|
cc2_volt = pd_adc_read(1);
|
|
if ((cc1_volt >= PD_SNK_VA) ||
|
|
(cc2_volt >= PD_SNK_VA)) {
|
|
pd_polarity = !(cc1_volt >= PD_SNK_VA);
|
|
pd_select_polarity(pd_polarity);
|
|
pd_task_state = PD_STATE_SNK_DISCOVERY;
|
|
}
|
|
} else if (pd_dual_role_toggle_on &&
|
|
get_time().val >= next_role_swap) {
|
|
/* Swap roles to source */
|
|
pd_role = PD_ROLE_SOURCE;
|
|
pd_task_state = PD_STATE_SRC_DISCONNECTED;
|
|
pd_set_host_mode(1);
|
|
next_role_swap = get_time().val + PD_T_DRP_SRC;
|
|
|
|
/* Swap states quickly */
|
|
timeout = 2*MSEC;
|
|
}
|
|
|
|
break;
|
|
case PD_STATE_SNK_DISCOVERY:
|
|
/* Don't continue if power negotiation is not allowed */
|
|
if (!pd_power_negotiation_allowed()) {
|
|
timeout = PD_T_GET_SOURCE_CAP;
|
|
break;
|
|
}
|
|
|
|
res = send_control(ctxt, PD_CTRL_GET_SOURCE_CAP);
|
|
/* packet was acked => PD capable device) */
|
|
if (res >= 0) {
|
|
/*
|
|
* we should a SOURCE_CAP package which will
|
|
* switch to the PD_STATE_SNK_REQUESTED state,
|
|
* else retry after the response timeout.
|
|
*/
|
|
timeout = PD_T_SENDER_RESPONSE;
|
|
} else { /* failed, retry later */
|
|
timeout = PD_T_GET_SOURCE_CAP;
|
|
}
|
|
break;
|
|
case PD_STATE_SNK_REQUESTED:
|
|
/* Ensure the power supply actually becomes ready */
|
|
pd_task_state = PD_STATE_SNK_TRANSITION;
|
|
timeout = PD_T_PS_TRANSITION;
|
|
break;
|
|
case PD_STATE_SNK_TRANSITION:
|
|
/*
|
|
* did not get the PS_READY,
|
|
* try again to whole request cycle.
|
|
*/
|
|
pd_task_state = PD_STATE_SNK_DISCOVERY;
|
|
timeout = 10*MSEC;
|
|
break;
|
|
case PD_STATE_SNK_READY:
|
|
/* we have power, check vitals from time to time */
|
|
timeout = 100*MSEC;
|
|
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_cmd(ctxt);
|
|
bist_mode_2_rx();
|
|
break;
|
|
}
|
|
|
|
/* Check for disconnection */
|
|
if (!pd_is_connected())
|
|
continue;
|
|
if (pd_role == PD_ROLE_SOURCE) {
|
|
/* Source: detect disconnect by monitoring CC */
|
|
cc1_volt = pd_adc_read(pd_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();
|
|
pd_task_state = PD_STATE_SRC_DISCONNECTED;
|
|
/* Debouncing */
|
|
timeout = 50*MSEC;
|
|
}
|
|
}
|
|
#ifdef CONFIG_USB_PD_DUAL_ROLE
|
|
if (pd_role == PD_ROLE_SINK && !pd_snk_is_vbus_provided()) {
|
|
/* Sink: detect disconnect by monitoring VBUS */
|
|
pd_task_state = PD_STATE_SNK_DISCONNECTED;
|
|
/* set timeout small to reconnect fast */
|
|
timeout = 5*MSEC;
|
|
}
|
|
#endif /* CONFIG_USB_PD_DUAL_ROLE */
|
|
}
|
|
}
|
|
|
|
void pd_rx_event(void)
|
|
{
|
|
task_set_event(TASK_ID_PD, PD_EVENT_RX, 0);
|
|
}
|
|
|
|
#ifdef CONFIG_COMMON_RUNTIME
|
|
void pd_set_suspend(int enable)
|
|
{
|
|
pd_task_state = enable ? PD_STATE_SUSPENDED : PD_DEFAULT_STATE;
|
|
|
|
task_wake(TASK_ID_PD);
|
|
}
|
|
|
|
void pd_request_source_voltage(int mv)
|
|
{
|
|
pd_set_max_voltage(mv);
|
|
pd_role = PD_ROLE_SINK;
|
|
pd_set_host_mode(0);
|
|
pd_task_state = PD_STATE_SNK_DISCONNECTED;
|
|
task_wake(TASK_ID_PD);
|
|
}
|
|
|
|
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], "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)) {
|
|
int max_volt = -1;
|
|
if (argc >= 3) {
|
|
char *e;
|
|
max_volt = strtoi(argv[2], &e, 10) * 1000;
|
|
}
|
|
pd_request_source_voltage(max_volt);
|
|
} 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 (!strcasecmp(argv[1], "dualrole")) {
|
|
int on;
|
|
char *e;
|
|
|
|
if (argc < 3) {
|
|
ccprintf("dual-role toggling: %d\n",
|
|
pd_dual_role_toggle_on);
|
|
} else {
|
|
on = strtoi(argv[2], &e, 10);
|
|
if (*e)
|
|
return EC_ERROR_PARAM3;
|
|
|
|
pd_set_dual_role(on ? PD_DRP_TOGGLE_ON :
|
|
PD_DRP_FORCE_SINK);
|
|
}
|
|
} else if (!strncasecmp(argv[1], "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",
|
|
"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 */
|