summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--board/gingerbread/ec.tasklist3
-rw-r--r--board/quiche/ec.tasklist3
-rw-r--r--chip/stm32/registers-stm32g4.h5
-rw-r--r--chip/stm32/ucpd-stm32gx.c676
-rw-r--r--chip/stm32/ucpd-stm32gx.h74
5 files changed, 733 insertions, 28 deletions
diff --git a/board/gingerbread/ec.tasklist b/board/gingerbread/ec.tasklist
index c272906fc7..9b3c3aa0a3 100644
--- a/board/gingerbread/ec.tasklist
+++ b/board/gingerbread/ec.tasklist
@@ -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)
diff --git a/board/quiche/ec.tasklist b/board/quiche/ec.tasklist
index fb51fe2088..0e18704a4c 100644
--- a/board/quiche/ec.tasklist
+++ b/board/quiche/ec.tasklist
@@ -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)
diff --git a/chip/stm32/registers-stm32g4.h b/chip/stm32/registers-stm32g4.h
index 4fe97e4c29..c774118159 100644
--- a/chip/stm32/registers-stm32g4.h
+++ b/chip/stm32/registers-stm32g4.h
@@ -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)
diff --git a/chip/stm32/ucpd-stm32gx.c b/chip/stm32/ucpd-stm32gx.c
index 96e907fad3..8a21087b05 100644
--- a/chip/stm32/ucpd-stm32gx.c
+++ b/chip/stm32/ucpd-stm32gx.c
@@ -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];
+}
+
+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;
}
-DECLARE_IRQ(STM32_IRQ_UCPD1, stm32gx_ucpd1_irq, 1);
+
+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);
diff --git a/chip/stm32/ucpd-stm32gx.h b/chip/stm32/ucpd-stm32gx.h
index f0a73cb29e..92cd28b8a0 100644
--- a/chip/stm32/ucpd-stm32gx.h
+++ b/chip/stm32/ucpd-stm32gx.h
@@ -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 */