stm32g4: ucpd: Add support for USB-PD messaging

This CL adds routines required to support USB-PD messaging to the UCPD
driver. UCPD is performs TCPC type functions, but is not a TCPC. UCPD
does utilize the common TCPCI APIs as entry points called from the
port's PD task. In addition, UCPD has its own task to manage more
easily transmit requests from TCPM layer and GoodCRC messages which
must be intiated in the UCPD driver itself.

BUG=b:167601672
BRANCH=None
TEST=verfied type-c attaches properly on quiche

Signed-off-by: Scott Collyer <scollyer@google.com>
Change-Id: I0c02bee4badc479125832a6b5a6fa156e998c201
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/ec/+/2247117
Tested-by: Scott Collyer <scollyer@chromium.org>
Auto-Submit: Scott Collyer <scollyer@chromium.org>
Commit-Queue: Scott Collyer <scollyer@chromium.org>
Reviewed-by: Diana Z <dzigterman@chromium.org>
This commit is contained in:
Scott Collyer 2020-11-06 21:12:27 -08:00 committed by Commit Bot
parent 3a03483f80
commit ff4f8fd9a4
5 changed files with 733 additions and 28 deletions

View File

@ -9,4 +9,5 @@
#define CONFIG_TASK_LIST \
TASK_ALWAYS(HOOKS, hook_task, NULL, LARGER_TASK_STACK_SIZE) \
TASK_ALWAYS(CONSOLE, console_task, NULL, VENTI_TASK_STACK_SIZE)
TASK_ALWAYS(CONSOLE, console_task, NULL, VENTI_TASK_STACK_SIZE) \
TASK_ALWAYS(UCPD, ucpd_task, 0, LARGER_TASK_STACK_SIZE)

View File

@ -10,4 +10,5 @@
#define CONFIG_TASK_LIST \
TASK_ALWAYS(HOOKS, hook_task, NULL, LARGER_TASK_STACK_SIZE) \
TASK_ALWAYS(CONSOLE, console_task, NULL, VENTI_TASK_STACK_SIZE) \
TASK_ALWAYS(PD_C0, pd_task, NULL, VENTI_TASK_STACK_SIZE)
TASK_ALWAYS(PD_C0, pd_task, NULL, VENTI_TASK_STACK_SIZE) \
TASK_ALWAYS(UCPD, ucpd_task, 0, LARGER_TASK_STACK_SIZE)

View File

@ -334,6 +334,11 @@
#define STM32_UCPD_CR_CC1TCDIS BIT(20)
#define STM32_UCPD_CR_CC2TCDIS BIT(21)
/* TX mode message types */
#define STM32_UCPD_CR_TXMODE_DEF 0
#define STM32_UCPD_CR_TXMODE_CBL_RST 1
#define STM32_UCPD_CR_TXMODE_BIST 2
/* --- UCPD IMR Bit Definitions --- */
#define STM32_UCPD_IMR_TXISIE BIT(0)
#define STM32_UCPD_IMR_TXMSGDISCIE BIT(1)

View File

@ -6,17 +6,22 @@
/* STM32GX UCPD module for Chrome EC */
#include "clock.h"
#include "console.h"
#include "common.h"
#include "driver/tcpm/tcpm.h"
#include "gpio.h"
#include "hooks.h"
#include "hwtimer.h"
#include "registers.h"
#include "task.h"
#include "timer.h"
#include "ucpd-stm32gx.h"
#include "usb_pd.h"
#include "usb_pd_tcpm.h"
#include "util.h"
#define CPRINTF(format, args...) cprintf(CC_USBPD, format, ## args)
#define CPRINTS(format, args...) cprints(CC_USBPD, format, ## args)
/*
* UCPD is fed directly from HSI which is @ 16MHz. The ucpd_clk goes to
* a prescaler who's output feeds the 'half-bit' divider which is used
@ -44,9 +49,127 @@
#define UCPD_TRANSWIN_CNT 8
#define UCPD_IFRGAP_CNT 17
/*
* USB PD message buffer length. Absent extended messages, the longest PD
* message will be 7 objects (4 bytes each) plus a 2 byte header. TCPMv2
* suports extended messages via chunking so the data buffer length is
* set assumign that extended messages are chunked.
*/
#define UCPD_BUF_LEN 30
#define UCPD_IMR_RX_INT_MASK (STM32_UCPD_IMR_RXNEIE| \
STM32_UCPD_IMR_RXORDDETIE | \
STM32_UCPD_IMR_RXHRSTDETIE | \
STM32_UCPD_IMR_RXOVRIE | \
STM32_UCPD_IMR_RXMSGENDIE)
#define UCPD_IMR_TX_INT_MASK (STM32_UCPD_IMR_TXISIE | \
STM32_UCPD_IMR_TXMSGDISCIE | \
STM32_UCPD_IMR_TXMSGSENTIE | \
STM32_UCPD_IMR_TXMSGABTIE | \
STM32_UCPD_IMR_TXUNDIE)
#define UCPD_ANASUB_TO_RP(r) ((r - 1) & 0x3)
#define UCPD_RP_TO_ANASUB(r) ((r + 1) & 0x3)
struct msg_header_info {
enum pd_power_role pr;
enum pd_data_role dr;
};
static struct msg_header_info msg_header;
/* States for managing tx messages in ucpd task */
enum ucpd_state {
STATE_IDLE,
STATE_ACTIVE_TCPM,
STATE_ACTIVE_CRC,
STATE_HARD_RESET,
STATE_WAIT_CRC_ACK,
};
/* Events for pd_interrupt_handler_task */
#define UCPD_EVT_GOOD_CRC_REQ BIT(0)
#define UCPD_EVT_TCPM_MSG_REQ BIT(1)
#define UCPD_EVT_HR_REQ BIT(2)
#define UCPD_EVT_TX_MSG_FAIL BIT(3)
#define UCPD_EVT_TX_MSG_SUCCESS BIT(4)
#define UCPD_EVT_HR_DONE BIT(5)
#define UCPD_EVT_HR_FAIL BIT(6)
#define UCPD_EVT_RX_GOOD_CRC BIT(7)
#define UCPD_T_RECEIVE_US (1 * MSEC)
#ifdef CONFIG_USB_PD_REV30
#define UCPD_N_RETRY_COUNT 2
#else
#define UCPD_N_RETRY_COUNT 3
#endif
/*
* Tx messages are iniated either by TCPM/PRL layer or from ucpd when a GoodCRC
* ack message needs to be sent.
*/
enum ucpd_tx_msg {
TX_MSG_NONE = -1,
TX_MSG_TCPM = 0,
TX_MSG_GOOD_CRC = 1,
TX_MSG_TOTAL = 2,
};
#define MSG_TCPM_MASK BIT(TX_MSG_TCPM)
#define MSG_GOOD_CRC_MASK BIT(TX_MSG_GOOD_CRC)
union buffer {
uint16_t header;
uint8_t msg[UCPD_BUF_LEN];
};
struct ucpd_tx_desc {
enum tcpm_transmit_type type;
int msg_len;
int msg_index;
union buffer data;
};
/* Tx message variables */
struct ucpd_tx_desc ucpd_tx_buffers[TX_MSG_TOTAL];
struct ucpd_tx_desc *ucpd_tx_active_buffer;
static int ucpd_tx_request;
static int ucpd_timeout_us;
static enum ucpd_state ucpd_tx_state;
static int msg_id_match;
static int tx_retry_count;
static int ucpd_txorderset[] = {
TX_ORDERSET_SOP,
TX_ORDERSET_SOP_PRIME,
TX_ORDERSET_SOP_PRIME_PRIME,
TX_ORDERSET_SOP_PRIME_DEBUG,
TX_ORDERSET_SOP_PRIME_PRIME_DEBUG,
TX_ORDERSET_HARD_RESET,
TX_ORDERSET_CABLE_RESET,
};
/* PD Rx variables */
static int ucpd_rx_byte_count;
static uint8_t ucpd_rx_buffer[UCPD_BUF_LEN];
static int ucpd_crc_id;
static int ucpd_msg_is_good_crc(uint16_t header)
{
/*
* Good CRC is a control message (no data objects) with GOOD_CRC message
* type in the header.
*/
return ((PD_HEADER_CNT(header) == 0) && (PD_HEADER_EXT(header) == 0) &&
(PD_HEADER_TYPE(header) == PD_CTRL_GOOD_CRC)) ? 1 : 0;
}
static void ucpd_hard_reset_rx_log(void)
{
CPRINTS("ucpd: hard reset recieved");
}
DECLARE_DEFERRED(ucpd_hard_reset_rx_log);
static void ucpd_port_enable(int port, int enable)
{
if (enable)
@ -63,23 +186,37 @@ static int ucpd_is_cc_pull_active(int port, enum usbpd_cc_pin cc_line)
return ((cc_enable >> cc_line) & 0x1);
}
void stm32gx_ucpd1_irq(void)
static void ucpd_tx_data_byte(int port)
{
/* STM32_IRQ_UCPD indicates this is from UCPD1, so port = 0 */
int port = 0;
uint32_t sr = STM32_UCPD_SR(port);
int index = ucpd_tx_active_buffer->msg_index++;
if (sr & (STM32_UCPD_SR_TYPECEVT1 | STM32_UCPD_SR_TYPECEVT2)) {
task_set_event(PD_PORT_TO_TASK_ID(port), PD_EVENT_CC, 0);
}
/* Clear interrupts now that PD events have been set */
STM32_UCPD_ICR(port) = sr;
STM32_UCPD_TXDR(port) = ucpd_tx_active_buffer->data.msg[index];
}
DECLARE_IRQ(STM32_IRQ_UCPD1, stm32gx_ucpd1_irq, 1);
static void ucpd_rx_data_byte(int port)
{
if (ucpd_rx_byte_count < UCPD_BUF_LEN)
ucpd_rx_buffer[ucpd_rx_byte_count++] = STM32_UCPD_RXDR(port);
}
static void ucpd_tx_interrupts_enable(int port, int enable)
{
if (enable)
STM32_UCPD_IMR(port) |= UCPD_IMR_TX_INT_MASK;
else
STM32_UCPD_IMR(port) &= ~UCPD_IMR_TX_INT_MASK;
}
static void ucpd_rx_enque_error(void)
{
CPRINTS("ucpd: TCPM Enque Error!!");
}
DECLARE_DEFERRED(ucpd_rx_enque_error);
int stm32gx_ucpd_init(int port)
{
uint32_t cfgr1_reg;
uint32_t moder_reg;
/*
* After exiting reset, stm32gx will have dead battery mode enabled by
@ -91,6 +228,10 @@ int stm32gx_ucpd_init(int port)
/* Ensure that clock to UCPD is enabled */
STM32_RCC_APB1ENR2 |= STM32_RCC_APB1ENR2_UPCD1EN;
/* Make sure CC1/CC2 pins PB4/PB6 are set for analog mode */
moder_reg = STM32_GPIO_MODER(GPIO_B);
moder_reg |= 0x3300;
STM32_GPIO_MODER(GPIO_B) = moder_reg;
/*
* CFGR1 must be written when UCPD peripheral is disabled. Note that
* disabling ucpd causes the peripheral to quit any ongoing activity and
@ -104,6 +245,13 @@ int stm32gx_ucpd_init(int port)
STM32_UCPD_CFGR1_HBITCLKD_VAL(UCPD_HBIT_DIV - 1);
STM32_UCPD_CFGR1(port) = cfgr1_reg;
/*
* Set RXORDSETEN field to control which types of ordered sets the PD
* receiver must receive.
* SOP, SOP', Hard Reset Det, Cable Reset Det enabled
*/
STM32_UCPD_CFGR1(port) |= STM32_UCPD_CFGR1_RXORDSETEN_VAL(0x1B);
/* Enable ucpd */
ucpd_port_enable(port, 1);
@ -252,12 +400,10 @@ int stm32gx_ucpd_set_cc(int port, int cc_pull, int rp)
/* Update pull values */
STM32_UCPD_CR(port) = cr;
/* TODO(b/): Should this return error if cc_pull == Ra */
return EC_SUCCESS;
}
int stm32gx_ucpd_set_polarity(int port, enum tcpc_cc_polarity polarity)
{
int stm32gx_ucpd_set_polarity(int port, enum tcpc_cc_polarity polarity) {
/*
* Polarity impacts the PHYCCSEL, CCENABLE, and CCxTCDIS fields. This
* function is called when polarity is updated at TCPM layer. STM32Gx
@ -275,3 +421,505 @@ int stm32gx_ucpd_set_polarity(int port, enum tcpc_cc_polarity polarity)
return EC_SUCCESS;
}
int stm32gx_ucpd_set_rx_enable(int port, int enable)
{
/*
* USB PD receiver enable is controlled by the bit PHYRXEN in
* UCPD_CR. Enable Rx interrupts when RX PD decoder is active.
*/
if (enable) {
STM32_UCPD_CR(port) |= STM32_UCPD_CR_PHYRXEN;
STM32_UCPD_ICR(port) |= UCPD_IMR_RX_INT_MASK;
STM32_UCPD_IMR(port) |= UCPD_IMR_RX_INT_MASK;
} else {
STM32_UCPD_CR(port) &= ~STM32_UCPD_CR_PHYRXEN;
STM32_UCPD_IMR(port) &= ~UCPD_IMR_RX_INT_MASK;
}
return EC_SUCCESS;
}
int stm32gx_ucpd_set_msg_header(int port, int power_role, int data_role)
{
msg_header.pr = power_role;
msg_header.dr = data_role;
return EC_SUCCESS;
}
static int stm32gx_ucpd_start_transmit(int port, enum ucpd_tx_msg msg_type)
{
enum tcpm_transmit_type type;
/* Select the correct tx desciptor */
ucpd_tx_active_buffer = &ucpd_tx_buffers[msg_type];
type = ucpd_tx_active_buffer->type;
if (type == TCPC_TX_HARD_RESET) {
/*
* From RM0440 45.4.4:
* In order to facilitate generation of a Hard Reset, a special
* code of TXMODE field is used. No other fields need to be
* written. On writing the correct code, the hardware forces
* Hard Reset Tx under the correct (optimal) timings with
* respect to an on-going Tx message, which (if still in
* progress) is cleanly terminated by truncating the current
* sequence and directly appending an EOP K-code sequence. No
* specific interrupt is generated relating to this truncation
* event.
*
* Because Hard Reset can interrupt ongoing Tx operations, it is
* started differently than all other tx messages. Only need to
* enable hard reset interrupts, and then set a bit in the CR
* register to initiate.
*/
/* Enable interrupt for Hard Reset sent/discarded */
STM32_UCPD_IMR(port) |= STM32_UCPD_IMR_HRSTDISCIE |
STM32_UCPD_IMR_HRSTSENTIE;
/* Initiate Hard Reset */
STM32_UCPD_CR(port) |= STM32_UCPD_CR_TXHRST;
} else if (type != TCPC_TX_INVALID) {
int msg_len = 0;
int mode;
/*
* These types are normal transmission, TXMODE = 0. To transmit
* regular message, control or data, requires the following:
* 1. Set TXMODE:
* Normal -> 0
* Cable Reset -> 1
* Bist -> 2
* 2. Set TX_ORDSETR based on message type
* 3. Set TX_PAYSZR which must account for 2 bytes of header
* 4. Configure DMA (optional if DMA is desired)
* 5. Enable transmit interrupts
* 6. Start TX by setting TXSEND in CR
*
*/
/*
* Set tx length parameter (in bytes). Note the count field in
* the header is number of 32 bit objects. Also, the length
* field must account for the 2 header bytes.
*/
if (type == TCPC_TX_BIST_MODE_2) {
mode = STM32_UCPD_CR_TXMODE_BIST;
} else if (type == TCPC_TX_CABLE_RESET) {
mode = STM32_UCPD_CR_TXMODE_CBL_RST;
} else {
mode = STM32_UCPD_CR_TXMODE_DEF;
msg_len = ucpd_tx_active_buffer->msg_len;
}
STM32_UCPD_TX_PAYSZR(port) = msg_len;
/* Set tx mode */
STM32_UCPD_CR(port) &= ~STM32_UCPD_CR_TXMODE_MASK;
STM32_UCPD_CR(port) |= STM32_UCPD_CR_TXMODE_VAL(mode);
/* Index into ordset enum for start of packet */
if (type <= TCPC_TX_CABLE_RESET )
STM32_UCPD_TX_ORDSETR(port) = ucpd_txorderset[type];
else
STM32_UCPD_TX_ORDSETR(port) =
ucpd_txorderset[TX_ORDERSET_SOP];
/* Reset msg byte index */
ucpd_tx_active_buffer-> msg_index = 0;
/* Enable interrupts */
ucpd_tx_interrupts_enable(port, 1);
/* Trigger ucpd peripheral to start pd message transmit */
STM32_UCPD_CR(port) |= STM32_UCPD_CR_TXSEND;
#ifdef CONFIG_STM32G4_UCPD_DEBUG
ucpd_log_add_msg(ucpd_tx_active_buffer->data.header, 0);
#endif
}
return EC_SUCCESS;
}
static void ucpd_set_tx_state(enum ucpd_state state)
{
ucpd_tx_state = state;
}
static void ucpd_manage_tx(int port, int evt)
{
enum ucpd_tx_msg msg_src = TX_MSG_NONE;
if (evt & UCPD_EVT_HR_REQ) {
ucpd_set_tx_state(STATE_HARD_RESET);
msg_src = MSG_TCPM_MASK;
}
switch (ucpd_tx_state) {
case STATE_IDLE:
if (ucpd_tx_request & MSG_GOOD_CRC_MASK) {
ucpd_set_tx_state(STATE_ACTIVE_CRC);
msg_src = TX_MSG_GOOD_CRC;
} else if (ucpd_tx_request & MSG_TCPM_MASK) {
uint16_t hdr;
ucpd_set_tx_state(STATE_ACTIVE_TCPM);
msg_src = TX_MSG_TCPM;
/* Save msgID required for GoodCRC check */
hdr = ucpd_tx_buffers[TX_MSG_TCPM].data.header;
msg_id_match = PD_HEADER_ID(hdr);
}
/* If state is not idle, then start tx message */
if (ucpd_tx_state != STATE_IDLE) {
ucpd_tx_request &= ~(1 << msg_src);
tx_retry_count = 0;
}
break;
case STATE_ACTIVE_TCPM:
/*
* Check if tx msg has finsihed. For TCPM messages
* transmit is not complete until a GoodCRC message
* matching the msgID just sent is received. But, a tx
* message can fail due to collision or underrun,
* etc. If that failure occurs, dont' wait for GoodCrc
* and just go to failure path.
*/
if (evt & UCPD_EVT_TX_MSG_SUCCESS) {
ucpd_set_tx_state(STATE_WAIT_CRC_ACK);
ucpd_timeout_us = UCPD_T_RECEIVE_US;
} else if (evt & UCPD_EVT_TX_MSG_FAIL) {
if (tx_retry_count < UCPD_N_RETRY_COUNT) {
/*
* Tx attempt failed. Remain in this
* state, but trigger new tx attempt.
*/
msg_src = TX_MSG_TCPM;
tx_retry_count++;
} else {
ucpd_set_tx_state(STATE_IDLE);
pd_transmit_complete(
port, TCPC_TX_COMPLETE_FAILED);
}
}
break;
case STATE_ACTIVE_CRC:
if (evt & (UCPD_EVT_TX_MSG_SUCCESS | UCPD_EVT_TX_MSG_FAIL)) {
ucpd_set_tx_state(STATE_IDLE);
if (evt & UCPD_EVT_TX_MSG_FAIL)
CPRINTS("ucpd: Failed to send GoodCRC!");
}
break;
case STATE_WAIT_CRC_ACK:
if (evt & UCPD_EVT_RX_GOOD_CRC &&
ucpd_crc_id == msg_id_match) {
/* GoodCRC with matching ID was received */
pd_transmit_complete(port,
TCPC_TX_COMPLETE_SUCCESS);
ucpd_set_tx_state(STATE_IDLE);
#ifdef CONFIG_STM32G4_UCPD_DEBUG
ucpd_log_mark_crc();
#endif
} else if ((evt & UCPD_EVT_RX_GOOD_CRC) ||
(evt & TASK_EVENT_TIMER)) {
/* GoodCRC w/out match or timeout waiting */
if (tx_retry_count < UCPD_N_RETRY_COUNT) {
ucpd_set_tx_state(STATE_ACTIVE_TCPM);
msg_src = TX_MSG_TCPM;
tx_retry_count++;
} else {
ucpd_set_tx_state(STATE_IDLE);
pd_transmit_complete(port,
TCPC_TX_COMPLETE_FAILED);
}
}
break;
case STATE_HARD_RESET:
if (evt & UCPD_EVT_HR_DONE) {
/* HR complete, reset tx state values */
ucpd_set_tx_state(STATE_IDLE);
ucpd_tx_request = 0;
tx_retry_count = 0;
} else if (evt & UCPD_EVT_HR_FAIL) {
ucpd_set_tx_state(STATE_IDLE);
ucpd_tx_request = 0;
tx_retry_count = 0;
}
break;
}
/* If msg_src is valid, then start transmit */
if (msg_src > TX_MSG_NONE) {
stm32gx_ucpd_start_transmit(port, msg_src);
}
}
/*
* Main task entry point for UCPD task
*
* @param p The PD port number for which to handle interrupts (pointer is
* reinterpreted as an integer directly).
*/
void ucpd_task(void *p)
{
const int port = (int) ((intptr_t) p);
/* Init variables used to manage tx process */
ucpd_tx_request = 0;
tx_retry_count = 0;
ucpd_tx_state = STATE_IDLE;
ucpd_timeout_us = -1;
while (1) {
/*
* Note that ucpd_timeout_us is file scope and may be modified
* in the tx state machine when entering the STATE_WAIT_CRC_ACK
* state. Otherwise, the expectation is that the task is woken
* only upon non-timer events.
*/
int evt = task_wait_event(ucpd_timeout_us);
/*
* USB-PD messages are intiated in TCPM stack (PRL
* layer). However, GoodCRC messages are initiated within the
* UCPD driver based on USB-PD rx messages. These 2 types of
* transmit paths are managed via task events.
*
* UCPD generated GoodCRC messages, are the priority path as
* they must be sent immediately following a successful USB-PD
* rx message. As long as a transmit operation is not underway,
* then a transmit message will be started upon request. The ISR
* routine sets the event to indicate that the transmit
* operation is complete.
*
* Hard reset requests are sent as a TCPM message, but in terms
* of the ucpd transmitter, they are treated as a 3rd tx msg
* source since they can interrupt an ongoing tx msg, and there
* is no requirement to wait for a GoodCRC reply message.
*/
/* Assume there is no timer for next task wake */
ucpd_timeout_us = -1;
if (evt & UCPD_EVT_GOOD_CRC_REQ)
ucpd_tx_request |= MSG_GOOD_CRC_MASK;
if (evt & UCPD_EVT_TCPM_MSG_REQ)
ucpd_tx_request |= MSG_TCPM_MASK;
/*
* Manage PD tx messages. The state machine may need to be
* called more than once when the task wakes. For instance, if
* the task is woken at the completion of sending a GoodCRC,
* there may be a TCPM message request pending and just changing
* the state back to idle would not trigger start of transmit.
*/
do {
ucpd_manage_tx(port, evt);
/* Look at task events only once. */
evt = 0;
} while (ucpd_tx_request && ucpd_tx_state == STATE_IDLE);
}
}
static void ucpd_send_good_crc(int port, uint16_t rx_header)
{
int msg_id;
int rev_id;
uint16_t tx_header;
enum tcpm_transmit_type tx_type;
enum pd_power_role pr = 0;
enum pd_data_role dr = 0;
/*
* A GoodCRC message shall be sent by receiver to ack that the previous
* message was correctly received. The GoodCRC message shall return the
* rx message's msg_id field. The one exception is for GoodCRC messages,
* which do not generate a GoodCRC response
*/
if (ucpd_msg_is_good_crc(rx_header)) {
return;
}
/*
* Get the rx ordered set code just detected. SOP -> SOP''_Debug are in
* the same order as enum tcpm_transmit_type and so can be used
* directly.
*/
tx_type = STM32_UCPD_RX_ORDSETR(port) & STM32_UCPD_RXORDSETR_MASK;
/*
* PD Header(SOP):
* Extended b15 -> set to 0 for control messages
* Count b14:12 -> number of 32 bit data objects = 0 for ctrl msg
* MsgID b11:9 -> running byte counter (extracted from rx msg)
* Power Role b8 -> stored in static, from set_msg_header()
* Spec Rev b7:b6 -> PD spec revision (extracted from rx msg)
* Data Role b5 -> stored in static, from set_msg_header
* Msg Type b4:b0 -> data or ctrl type = PD_CTRL_GOOD_CRC
*/
/* construct header message */
msg_id = PD_HEADER_ID(rx_header);
rev_id = PD_HEADER_REV(rx_header);
if (tx_type == TCPC_TX_SOP) {
pr = msg_header.pr;
dr = msg_header.dr;
}
tx_header = PD_HEADER(PD_CTRL_GOOD_CRC, pr, dr, msg_id, 0, rev_id, 0);
/* Good CRC is header with no other objects */
ucpd_tx_buffers[TX_MSG_GOOD_CRC].msg_len = 2;
ucpd_tx_buffers[TX_MSG_GOOD_CRC].data.header = tx_header;
ucpd_tx_buffers[TX_MSG_GOOD_CRC].type = tx_type;
/* Notify ucpd task that a GoodCRC message tx request is pending */
task_set_event(TASK_ID_UCPD, UCPD_EVT_GOOD_CRC_REQ, 0);
}
int stm32gx_ucpd_transmit(int port,
enum tcpm_transmit_type type,
uint16_t header,
const uint32_t *data)
{
/* Length in bytes = (4 * object len) + 2 header byes */
int len = (PD_HEADER_CNT(header) << 2) + 2;
if (len > UCPD_BUF_LEN)
return EC_ERROR_OVERFLOW;
/* Store tx msg info in TCPM msg descriptor */
ucpd_tx_buffers[TX_MSG_TCPM].msg_len = len;
ucpd_tx_buffers[TX_MSG_TCPM].type = type;
ucpd_tx_buffers[TX_MSG_TCPM].data.header = header;
/* Copy msg objects to ucpd data buffer, after 2 header bytes */
memcpy(ucpd_tx_buffers[TX_MSG_TCPM].data.msg + 2, (uint8_t *)data,
len - 2);
/* Notify ucpd task that a TCPM message tx request is pending */
task_set_event(TASK_ID_UCPD, UCPD_EVT_TCPM_MSG_REQ, 0);
return EC_SUCCESS;
}
int stm32gx_ucpd_get_message_raw(int port, uint32_t *payload, int *head)
{
uint16_t *rx_header = (uint16_t *)ucpd_rx_buffer;
int rxpaysz;
#ifdef CONFIG_USB_PD_DECODE_SOP
int sop;
#endif
/* First 2 bytes of data buffer are the header */
*head = *rx_header;
#ifdef CONFIG_USB_PD_DECODE_SOP
/*
* The message header is a 16-bit value that's stored in a 32-bit data type.
* SOP* is encoded in bits 31 to 28 of the 32-bit data type.
* NOTE: The 4 byte header is not part of the PD spec.
*/
/* Get SOP value */
sop = STM32_UCPD_RX_ORDSETR(port) & STM32_UCPD_RXORDSETR_MASK;
/* Put SOP in bits 31:28 of 32 bit header */
*head |= PD_HEADER_SOP(sop);
#endif
rxpaysz = STM32_UCPD_RX_PAYSZR(port) & STM32_UCPD_RX_PAYSZR_MASK;
/* This size includes 2 bytes for message header */
rxpaysz -= 2;
/* Copy payload (src/dst are both 32 bit aligned) */
memcpy(payload, ucpd_rx_buffer + 2, rxpaysz);
return EC_SUCCESS;
}
void stm32gx_ucpd1_irq(void)
{
/* STM32_IRQ_UCPD indicates this is from UCPD1, so port = 0 */
int port = 0;
uint32_t sr = STM32_UCPD_SR(port);
uint32_t tx_done_mask = STM32_UCPD_SR_TXMSGSENT | STM32_UCPD_SR_TXMSGABT
| STM32_UCPD_SR_TXMSGDISC | STM32_UCPD_SR_HRSTSENT |
STM32_UCPD_SR_HRSTDISC;
/* Check for CC events, set event to wake PD task */
if (sr & (STM32_UCPD_SR_TYPECEVT1 | STM32_UCPD_SR_TYPECEVT2))
task_set_event(PD_PORT_TO_TASK_ID(port), PD_EVENT_CC, 0);
/*
* Check for Tx events. tx_mask includes all status bits related to the
* end of a USB-PD tx message. If any of these bits are set, the
* transmit attempt is completed. Set an event to notify ucpd tx state
* machine that transmit operation is complete.
*/
if (sr & tx_done_mask) {
/* Check for tx message complete */
if (sr & STM32_UCPD_SR_TXMSGSENT) {
task_set_event(TASK_ID_UCPD, UCPD_EVT_TX_MSG_SUCCESS,
0);
#ifdef CONFIG_STM32G4_UCPD_DEBUG
ucpd_log_mark_tx_comp();
#endif
} else if (sr & (STM32_UCPD_SR_TXMSGABT |
STM32_UCPD_SR_TXMSGDISC |STM32_UCPD_SR_TXUND)) {
task_set_event(TASK_ID_UCPD, UCPD_EVT_TX_MSG_FAIL, 0);
} else if (sr & STM32_UCPD_SR_HRSTSENT) {
task_set_event(TASK_ID_UCPD, UCPD_EVT_HR_DONE, 0);
} else if (sr & STM32_UCPD_SR_HRSTDISC) {
task_set_event(TASK_ID_UCPD, UCPD_EVT_HR_FAIL, 0);
}
/* Disable Tx interrupts */
ucpd_tx_interrupts_enable(port, 0);
}
/* Check for data register empty */
if (sr & STM32_UCPD_SR_TXIS)
ucpd_tx_data_byte(port);
/* Check for Rx Events */
/* Check first for start of new message */
if (sr & STM32_UCPD_SR_RXORDDET) {
ucpd_rx_byte_count = 0;
}
/* Check for byte received */
if (sr & STM32_UCPD_SR_RXNE)
ucpd_rx_data_byte(port);
/* Check for end of message */
if (sr & STM32_UCPD_SR_RXMSGEND) {
/* Check for errors */
if (!(sr & STM32_UCPD_SR_RXERR)) {
int rv;
uint16_t *rx_header = (uint16_t *)ucpd_rx_buffer;
/* Don't pass GoodCRC control messages TCPM */
if (!ucpd_msg_is_good_crc(*rx_header)) {
/* TODO - Add error checking here */
rv = tcpm_enqueue_message(port);
if (rv)
hook_call_deferred(&ucpd_rx_enque_error_data,
0);
/* Send GoodCRC message (if required) */
ucpd_send_good_crc(port, *rx_header);
} else {
task_set_event(TASK_ID_UCPD,
UCPD_EVT_RX_GOOD_CRC, 0);
ucpd_crc_id = PD_HEADER_ID(*rx_header);
}
}
}
/* Check for fault conditions */
if (sr & STM32_UCPD_SR_RXHRSTDET) {
/* hard reset received */
pd_execute_hard_reset(port);
task_set_event(PD_PORT_TO_TASK_ID(port), TASK_EVENT_WAKE, 0);
hook_call_deferred(&ucpd_hard_reset_rx_log_data, 0);
}
/* Clear interrupts now that PD events have been set */
STM32_UCPD_ICR(port) = sr;
}
DECLARE_IRQ(STM32_IRQ_UCPD1, stm32gx_ucpd1_irq, 1);

View File

@ -22,38 +22,45 @@
#define UCPD_RST2 0x19u
#define UCPD_EOP 0x0Du
/* This order of this enum matches tcpm_transmit_type */
enum ucpd_tx_ordset {
TX_ORDERSET_SOP = (UCPD_SYNC1 |
(UCPD_SYNC1<<5u) |
(UCPD_SYNC1<<10u) |
(UCPD_SYNC2<<15u)),
TX_ORDERSET_SOP1 = (UCPD_SYNC1 |
TX_ORDERSET_SOP_PRIME = (UCPD_SYNC1 |
(UCPD_SYNC1<<5u) |
(UCPD_SYNC3<<10u) |
(UCPD_SYNC3<<15u)),
TX_ORDERSET_SOP2 = (UCPD_SYNC1 |
TX_ORDERSET_SOP_PRIME_PRIME = (UCPD_SYNC1 |
(UCPD_SYNC3<<5u) |
(UCPD_SYNC1<<10u) |
(UCPD_SYNC3<<15u)),
TX_ORDERSET_SOP_PRIME_DEBUG = (UCPD_SYNC1 |
(UCPD_RST2<<5u) |
(UCPD_RST2<<10u) |
(UCPD_SYNC3<<15u)),
TX_ORDERSET_SOP_PRIME_PRIME_DEBUG = (UCPD_SYNC1 |
(UCPD_RST2<<5u) |
(UCPD_SYNC3<<10u) |
(UCPD_SYNC2<<15u)),
TX_ORDERSET_HARD_RESET = (UCPD_RST1 |
(UCPD_RST1<<5u) |
(UCPD_RST1<<10u) |
(UCPD_RST2<<15u)),
TX_ORDERSET_CABLE_RESET =
(UCPD_RST1 |
TX_ORDERSET_CABLE_RESET = (UCPD_RST1 |
(UCPD_SYNC1<<5u) |
(UCPD_RST1<<10u) |
(UCPD_SYNC3<<15u)),
TX_ORDERSET_SOP1_DEBUG = (UCPD_SYNC1 |
(UCPD_RST2<<5u) |
(UCPD_RST2<<10u) |
(UCPD_SYNC3<<15u)),
TX_ORDERSET_SOP2_DEBUG = (UCPD_SYNC1 |
(UCPD_RST2<<5u) |
(UCPD_SYNC3<<10u) |
(UCPD_SYNC2<<15u)),
};
/**
* STM32Gx UCPD implementation of tcpci .init method
*
@ -108,4 +115,47 @@ int stm32gx_ucpd_set_cc(int usbc_port, int cc_pull, int rp);
*/
int stm32gx_ucpd_set_polarity(int usbc_port, enum tcpc_cc_polarity polarity);
/**
* STM32Gx UCPD implementation of tcpci .set_rx_enable method
*
* @param usbc_port -> USB-C Port number
* @param enable -> on/off for USB-PD messages
* @return EC_SUCCESS
*/
int stm32gx_ucpd_set_rx_enable(int port, int enable);
/**
* STM32Gx UCPD implementation of tcpci .set_msg_header method
*
* @param usbc_port -> USB-C Port number
* @param power_role -> port's current power role
* @param data_role -> port's current data role
* @return EC_SUCCESS
*/
int stm32gx_ucpd_set_msg_header(int port, int power_role, int data_role);
/**
* STM32Gx UCPD implementation of tcpci .transmit method
*
* @param usbc_port -> USB-C Port number
* @param type -> SOP/SOP'/SOP'' etc
* @param header -> usb pd message header
* @param *data -> pointer to message contents
* @return EC_SUCCESS
*/
int stm32gx_ucpd_transmit(int port,
enum tcpm_transmit_type type,
uint16_t header,
const uint32_t *data);
/**
* STM32Gx UCPD implementation of tcpci .get_message_raw method
*
* @param usbc_port -> USB-C Port number
* @param *payload -> pointer to where message should be written
* @param *head -> pointer to message header
* @return EC_SUCCESS
*/
int stm32gx_ucpd_get_message_raw(int port, uint32_t *payload, int *head);
#endif /* __CROS_EC_UCPD_STM32GX_H */