| /* Copyright 2020 The ChromiumOS Authors |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| /* STM32GX UCPD module for Chrome EC */ |
| |
| #include "clock.h" |
| #include "common.h" |
| #include "console.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) |
| |
| #define USB_VID_STM32 0x0483 |
| |
| /* |
| * 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_ICR_TX_INT_MASK \ |
| (STM32_UCPD_ICR_TXMSGDISCCF | STM32_UCPD_ICR_TXMSGSENTCF | \ |
| STM32_UCPD_ICR_TXMSGABTCF | STM32_UCPD_ICR_TXUNDCF) |
| |
| #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_DISC BIT(4) |
| #define UCPD_EVT_TX_MSG_SUCCESS BIT(5) |
| #define UCPD_EVT_HR_DONE BIT(6) |
| #define UCPD_EVT_HR_FAIL BIT(7) |
| #define UCPD_EVT_RX_GOOD_CRC BIT(8) |
| #define UCPD_EVT_RX_MSG BIT(9) |
| |
| #define UCPD_T_RECEIVE_US (1 * MSEC) |
| |
| #define UCPD_N_RETRY_COUNT_REV20 3 |
| #define UCPD_N_RETRY_COUNT_REV30 2 |
| |
| /* |
| * 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 tcpci_msg_type type; |
| int msg_len; |
| int msg_index; |
| union buffer data; |
| }; |
| |
| /* Track VCONN on/off state */ |
| static int ucpd_vconn_enable; |
| |
| /* 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 tx_retry_max; |
| |
| 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 bool ucpd_rx_sop_prime_enabled; |
| static int ucpd_rx_msg_active; |
| static bool ucpd_rx_bist_mode; |
| |
| #ifdef CONFIG_STM32G4_UCPD_DEBUG |
| /* Defines and macros for ucpd state logging */ |
| #define TX_STATE_LOG_LEN BIT(5) |
| #define TX_STATE_LOG_MASK (TX_STATE_LOG_LEN - 1) |
| |
| struct ucpd_tx_state { |
| uint32_t ts; |
| int tx_request; |
| int timeout_us; |
| enum ucpd_state enter_state; |
| enum ucpd_state exit_state; |
| uint32_t evt; |
| }; |
| |
| struct ucpd_tx_state ucpd_tx_statelog[TX_STATE_LOG_LEN]; |
| int ucpd_tx_state_log_idx; |
| int ucpd_tx_state_log_freeze; |
| |
| static char ucpd_names[][12] = { |
| "TX_IDLE", "ACT_TCPM", "ACT_CRC", "HARD_RST", "WAIT_CRC", |
| }; |
| /* Defines and macros used for ucpd pd message logging */ |
| #define MSG_LOG_LEN 64 |
| #define MSG_BUF_LEN 10 |
| |
| struct msg_info { |
| uint8_t dir; |
| uint8_t comp; |
| uint8_t crc; |
| uint16_t header; |
| uint32_t ts; |
| uint8_t buf[MSG_BUF_LEN]; |
| }; |
| static int msg_log_cnt; |
| static int msg_log_idx; |
| static struct msg_info msg_log[MSG_LOG_LEN]; |
| |
| #define UCPD_CC_STRING_LEN 5 |
| |
| static char ccx[4][UCPD_CC_STRING_LEN] = { |
| "Ra", |
| "Rp", |
| "Rd", |
| "Open", |
| }; |
| static char rp_string[][8] = { |
| "Rp_usb", |
| "Rp_1.5", |
| "Rp_3.0", |
| "Open", |
| }; |
| static int ucpd_sr_cc_event; |
| static int ucpd_cc_set_save; |
| static int ucpd_cc_change_log; |
| |
| static int ucpd_is_cc_pull_active(int port, enum usbpd_cc_pin cc_line); |
| |
| static void ucpd_log_add_msg(uint16_t header, int dir) |
| { |
| uint32_t ts = __hw_clock_source_read(); |
| int idx = msg_log_idx; |
| uint8_t *buf = dir ? ucpd_rx_buffer : ucpd_tx_active_buffer->data.msg; |
| |
| /* |
| * Add a msg entry in the history log. The log is currently designed to |
| * be from reset until MSG_LOG_LEN messages have been added. |
| * ts -> lower 32 bits of 1 uSec running clock |
| * dir -> 0 = tx message, 1 = rx message |
| * comp -> ucpd transmit success |
| * crc -> GoodCrc received following tx message |
| */ |
| if (msg_log_cnt++ < MSG_LOG_LEN) { |
| int msg_bytes = |
| MIN((PD_HEADER_CNT(header) << 2) + 2, MSG_BUF_LEN); |
| |
| msg_log[idx].header = header; |
| msg_log[idx].ts = ts; |
| msg_log[idx].dir = dir; |
| msg_log[idx].comp = 0; |
| msg_log[idx].crc = 0; |
| msg_log_idx++; |
| memcpy(msg_log[idx].buf, buf, msg_bytes); |
| } |
| } |
| |
| static void ucpd_log_mark_tx_comp(void) |
| { |
| /* |
| * This msg logging utility function is used to mark when a message was |
| * successfully transmitted when transmit interrupt occurs and the tx |
| * message sent status was set. Because the transmit message is added |
| * before it's sent by ucpd, the index has to back up one to mark the |
| * correct log entry. |
| */ |
| if (msg_log_cnt < MSG_LOG_LEN) { |
| if (msg_log_idx > 0) |
| msg_log[msg_log_idx - 1].comp = 1; |
| } |
| } |
| |
| static void ucpd_log_mark_crc(void) |
| { |
| /* |
| * This msg logging utility function is used to mark when a GoodCRC |
| * message is received following a tx message. This status is displayed |
| * in column s2. Because this indication follows both transmit message |
| * and GoodCRC rx, the index must be back up 2 rows to mark the correct |
| * tx message entry. |
| */ |
| if (msg_log_cnt < MSG_LOG_LEN) { |
| if (msg_log_idx >= 2) |
| msg_log[msg_log_idx - 2].crc = 1; |
| } |
| } |
| |
| static void ucpd_cc_status(int port) |
| { |
| int rc = stm32gx_ucpd_get_role_control(port); |
| int cc1_pull, cc2_pull; |
| enum tcpc_cc_voltage_status v_cc1, v_cc2; |
| int rv; |
| char *rp_name; |
| |
| cc1_pull = rc & 0x3; |
| cc2_pull = (rc >> 2) & 0x3; |
| |
| /* |
| * This function is used to display CC settings, including pull type, |
| * and if Rp, what the Rp value is set to. In addition, the current |
| * values of CC voltage detector, polarity, and PD enable status are |
| * displayed. |
| */ |
| rv = stm32gx_ucpd_get_cc(port, &v_cc1, &v_cc2); |
| rp_name = rp_string[(rc >> 4) % 0x3]; |
| ccprintf("\tcc1\t = %s\n\tcc2\t = %s\n\tRp\t = %s\n", ccx[cc1_pull], |
| ccx[cc2_pull], rp_name); |
| if (!rv) |
| ccprintf("\tcc1_v\t = %d\n\tcc2_v\t = %d\n", v_cc1, v_cc2); |
| } |
| |
| void ucpd_cc_detect_notify_enable(int enable) |
| { |
| /* |
| * This variable is used to control when a CC detach detector is |
| * active. |
| */ |
| ucpd_cc_change_log = enable; |
| } |
| |
| static void ucpd_log_invalidate_entry(void) |
| { |
| /* |
| * This is a msg log utility function which is triggered when an |
| * unexpected detach event is detected. |
| */ |
| if (msg_log_idx < (MSG_LOG_LEN - 1)) { |
| int idx = msg_log_idx; |
| |
| msg_log[idx].header = 0xabcd; |
| msg_log[idx].ts = __hw_clock_source_read(); |
| msg_log[idx].dir = 0; |
| msg_log[idx].comp = 0; |
| msg_log[idx].crc = 0; |
| msg_log_cnt++; |
| msg_log_idx++; |
| } |
| } |
| |
| /* |
| * This function will mark in the msg log when a detach event occurs. It will |
| * only be active if ucpd_cc_change_log is set which can be controlled via the |
| * ucpd console command. |
| */ |
| static void ucpd_cc_change_notify(void) |
| { |
| if (ucpd_cc_change_log) { |
| uint32_t sr = ucpd_sr_cc_event; |
| |
| ucpd_log_invalidate_entry(); |
| |
| ccprintf("vstate: cc1 = %x, cc2 = %x, Rp = %d\n", |
| (sr >> STM32_UCPD_SR_VSTATE_CC1_SHIFT) & 0x3, |
| (sr >> STM32_UCPD_SR_VSTATE_CC2_SHIFT) & 0x3, |
| (ucpd_cc_set_save >> STM32_UCPD_CR_ANASUBMODE_SHIFT) & |
| 0x3); |
| /* Display CC status on EC console */ |
| ucpd_cc_status(0); |
| } |
| } |
| DECLARE_DEFERRED(ucpd_cc_change_notify); |
| #endif /* CONFIG_STM32G4_UCPD_DEBUG */ |
| |
| 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) |
| STM32_UCPD_CFGR1(port) |= STM32_UCPD_CFGR1_UCPDEN; |
| else |
| STM32_UCPD_CFGR1(port) &= ~STM32_UCPD_CFGR1_UCPDEN; |
| } |
| |
| static int ucpd_is_cc_pull_active(int port, enum usbpd_cc_pin cc_line) |
| { |
| int cc_enable = (STM32_UCPD_CR(port) & STM32_UCPD_CR_CCENABLE_MASK) >> |
| STM32_UCPD_CR_CCENABLE_SHIFT; |
| |
| return ((cc_enable >> cc_line) & 0x1); |
| } |
| |
| static void ucpd_tx_data_byte(int port) |
| { |
| int index = ucpd_tx_active_buffer->msg_index++; |
| |
| 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_ICR(port) = UCPD_ICR_TX_INT_MASK; |
| 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); |
| |
| static void stm32gx_ucpd_state_init(int port) |
| { |
| /* Init variables used to manage tx process */ |
| ucpd_tx_request = 0; |
| tx_retry_count = 0; |
| ucpd_tx_state = STATE_IDLE; |
| ucpd_timeout_us = -1; |
| |
| /* Init variables used to manage rx */ |
| ucpd_rx_sop_prime_enabled = 0; |
| ucpd_rx_msg_active = 0; |
| ucpd_rx_bist_mode = 0; |
| |
| /* Vconn tracking variable */ |
| ucpd_vconn_enable = 0; |
| } |
| |
| int stm32gx_ucpd_init(int port) |
| { |
| uint32_t cfgr1_reg; |
| uint32_t moder_reg; |
| |
| /* Disable UCPD interrupts */ |
| task_disable_irq(STM32_IRQ_UCPD1); |
| |
| /* |
| * After exiting reset, stm32gx will have dead battery mode enabled by |
| * default which connects Rd to CC1/CC2. This should be disabled when EC |
| * is powered up. |
| */ |
| STM32_PWR_CR3 |= STM32_PWR_CR3_UCPD1_DBDIS; |
| |
| /* 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 |
| * sets all ucpd registers back their default values. |
| */ |
| ucpd_port_enable(port, 0); |
| |
| cfgr1_reg = STM32_UCPD_CFGR1_PSC_CLK_VAL(UCPD_PSC_DIV - 1) | |
| STM32_UCPD_CFGR1_TRANSWIN_VAL(UCPD_TRANSWIN_CNT - 1) | |
| STM32_UCPD_CFGR1_IFRGAP_VAL(UCPD_IFRGAP_CNT - 1) | |
| 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); |
| |
| /* Configure CC change interrupts */ |
| STM32_UCPD_IMR(port) = STM32_UCPD_IMR_TYPECEVT1IE | |
| STM32_UCPD_IMR_TYPECEVT2IE; |
| STM32_UCPD_ICR(port) = STM32_UCPD_ICR_TYPECEVT1CF | |
| STM32_UCPD_ICR_TYPECEVT2CF; |
| |
| /* SOP'/SOP'' must be enabled via TCPCI call */ |
| ucpd_rx_sop_prime_enabled = false; |
| |
| stm32gx_ucpd_state_init(port); |
| |
| /* Enable UCPD interrupts */ |
| task_enable_irq(STM32_IRQ_UCPD1); |
| |
| return EC_SUCCESS; |
| } |
| |
| int stm32gx_ucpd_release(int port) |
| { |
| ucpd_port_enable(port, 0); |
| |
| return EC_SUCCESS; |
| } |
| |
| int stm32gx_ucpd_get_cc(int port, enum tcpc_cc_voltage_status *cc1, |
| enum tcpc_cc_voltage_status *cc2) |
| { |
| int vstate_cc1; |
| int vstate_cc2; |
| int anamode; |
| uint32_t sr; |
| |
| /* |
| * cc_voltage_status is determined from vstate_cc bit field in the |
| * status register. The meaning of the value vstate_cc depends on |
| * current value of ANAMODE (src/snk). |
| * |
| * vstate_cc maps directly to cc_state from tcpci spec when ANAMODE = 1, |
| * but needs to be modified slightly for case ANAMODE = 0. |
| * |
| * If presenting Rp (source), then need to to a circular shift of |
| * vstate_ccx value: |
| * vstate_cc | cc_state |
| * ------------------ |
| * 0 -> 1 |
| * 1 -> 2 |
| * 2 -> 0 |
| */ |
| |
| /* Get vstate_ccx values and power role */ |
| sr = STM32_UCPD_SR(port); |
| /* Get Rp or Rd active */ |
| anamode = !!(STM32_UCPD_CR(port) & STM32_UCPD_CR_ANAMODE); |
| vstate_cc1 = (sr & STM32_UCPD_SR_VSTATE_CC1_MASK) >> |
| STM32_UCPD_SR_VSTATE_CC1_SHIFT; |
| vstate_cc2 = (sr & STM32_UCPD_SR_VSTATE_CC2_MASK) >> |
| STM32_UCPD_SR_VSTATE_CC2_SHIFT; |
| |
| /* Do circular shift if port == source */ |
| if (anamode) { |
| if (vstate_cc1 != STM32_UCPD_SR_VSTATE_RA) |
| vstate_cc1 += 4; |
| if (vstate_cc2 != STM32_UCPD_SR_VSTATE_RA) |
| vstate_cc2 += 4; |
| } else { |
| if (vstate_cc1 != STM32_UCPD_SR_VSTATE_OPEN) |
| vstate_cc1 = (vstate_cc1 + 1) % 3; |
| if (vstate_cc2 != STM32_UCPD_SR_VSTATE_OPEN) |
| vstate_cc2 = (vstate_cc2 + 1) % 3; |
| } |
| |
| *cc1 = vstate_cc1; |
| *cc2 = vstate_cc2; |
| |
| return EC_SUCCESS; |
| } |
| |
| int stm32gx_ucpd_get_role_control(int port) |
| { |
| int role_control; |
| int cc1; |
| int cc2; |
| int anamode = !!(STM32_UCPD_CR(port) & STM32_UCPD_CR_ANAMODE); |
| int anasubmode = |
| (STM32_UCPD_CR(port) & STM32_UCPD_CR_ANASUBMODE_MASK) >> |
| STM32_UCPD_CR_ANASUBMODE_SHIFT; |
| |
| /* |
| * Role control register is defined as: |
| * R_cc1 -> b 1:0 |
| * R_cc2 -> b 3:2 |
| * Rp -> b 5:4 |
| * |
| * In TCPCI, CCx is defined as: |
| * 00b -> Ra |
| * 01b -> Rp |
| * 10b -> Rd |
| * 11b -> Open (don't care) |
| * |
| * For ucpd, this information is encoded in ANAMODE and ANASUBMODE |
| * fields as follows: |
| * ANAMODE CCx |
| * 0 -> Rp -> 1 |
| * 1 -> Rd -> 2 |
| * |
| * ANASUBMODE: |
| * 00b -> TYPEC_RP_RESERVED (open) |
| * 01b -> TYPEC_RP_USB |
| * 10b -> TYPEC_RP_1A5 |
| * 11b -> TYPEC_RP_3A0 |
| * |
| * CCx = ANAMODE + 1, if CCx is enabled |
| * Rp = (ANASUBMODE - 1) & 0x3 |
| */ |
| cc1 = ucpd_is_cc_pull_active(port, USBPD_CC_PIN_1) ? anamode + 1 : |
| TYPEC_CC_OPEN; |
| cc2 = ucpd_is_cc_pull_active(port, USBPD_CC_PIN_2) ? anamode + 1 : |
| TYPEC_CC_OPEN; |
| role_control = cc1 | (cc2 << 2); |
| /* Circular shift anasubmode to convert to Rp range */ |
| role_control |= (UCPD_ANASUB_TO_RP(anasubmode) << 4); |
| |
| return role_control; |
| } |
| |
| static uint32_t ucpd_get_cc_enable_mask(int port) |
| { |
| uint32_t mask = STM32_UCPD_CR_CCENABLE_MASK; |
| |
| if (ucpd_vconn_enable) { |
| uint32_t cr = STM32_UCPD_CR(port); |
| int pol = !!(cr & STM32_UCPD_CR_PHYCCSEL); |
| |
| mask &= ~(1 << (STM32_UCPD_CR_CCENABLE_SHIFT + !pol)); |
| } |
| |
| return mask; |
| } |
| |
| int stm32gx_ucpd_vconn_disc_rp(int port, int enable) |
| { |
| int cr; |
| |
| /* Update VCONN on/off status. Do this before getting cc enable mask */ |
| ucpd_vconn_enable = enable; |
| |
| cr = STM32_UCPD_CR(port); |
| cr &= ~STM32_UCPD_CR_CCENABLE_MASK; |
| cr |= ucpd_get_cc_enable_mask(port); |
| |
| /* Apply cc pull resistor change */ |
| STM32_UCPD_CR(port) = cr; |
| |
| return EC_SUCCESS; |
| } |
| |
| int stm32gx_ucpd_set_cc(int port, int cc_pull, int rp) |
| { |
| uint32_t cr = STM32_UCPD_CR(port); |
| |
| /* |
| * Always set ANASUBMODE to match desired Rp. TCPM layer has a valid |
| * range of 0, 1, or 2. This range maps to 1, 2, or 3 in ucpd for |
| * ANASUBMODE. |
| */ |
| cr &= ~STM32_UCPD_CR_ANASUBMODE_MASK; |
| cr |= STM32_UCPD_CR_ANASUBMODE_VAL(UCPD_RP_TO_ANASUB(rp)); |
| |
| /* Disconnect both pull from both CC lines for R_open case */ |
| cr &= ~STM32_UCPD_CR_CCENABLE_MASK; |
| /* Set ANAMODE if cc_pull is Rd */ |
| if (cc_pull == TYPEC_CC_RD) { |
| cr |= STM32_UCPD_CR_ANAMODE | STM32_UCPD_CR_CCENABLE_MASK; |
| /* Clear ANAMODE if cc_pull is Rp */ |
| } else if (cc_pull == TYPEC_CC_RP) { |
| cr &= ~(STM32_UCPD_CR_ANAMODE); |
| cr |= ucpd_get_cc_enable_mask(port); |
| } |
| |
| #ifdef CONFIG_STM32G4_UCPD_DEBUG |
| if (ucpd_cc_change_log) { |
| CPRINTS("ucpd: set_cc: pull = %d, rp = %d", cc_pull, rp); |
| } |
| #endif |
| /* Update pull values */ |
| STM32_UCPD_CR(port) = cr; |
| |
| return EC_SUCCESS; |
| } |
| |
| 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 |
| * only supports POLARITY_CC1 or POLARITY_CC2 and this is stored in the |
| * PHYCCSEL bit in the CR register. |
| */ |
| if (polarity > POLARITY_CC2) |
| return EC_ERROR_UNIMPLEMENTED; |
| |
| if (polarity == POLARITY_CC1) |
| STM32_UCPD_CR(port) &= ~STM32_UCPD_CR_PHYCCSEL; |
| else if (polarity == POLARITY_CC2) |
| STM32_UCPD_CR(port) |= STM32_UCPD_CR_PHYCCSEL; |
| |
| #ifdef CONFIG_STM32G4_UCPD_DEBUG |
| ucpd_cc_set_save = STM32_UCPD_CR(port); |
| #endif |
| |
| 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_ICR(port) = UCPD_IMR_RX_INT_MASK; |
| STM32_UCPD_IMR(port) |= UCPD_IMR_RX_INT_MASK; |
| STM32_UCPD_CR(port) |= STM32_UCPD_CR_PHYRXEN; |
| } 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; |
| } |
| |
| int stm32gx_ucpd_sop_prime_enable(int port, bool enable) |
| { |
| /* Update static varialbe used to filter SOP//SOP'' messages */ |
| ucpd_rx_sop_prime_enabled = enable; |
| |
| return EC_SUCCESS; |
| } |
| |
| int stm32gx_ucpd_get_chip_info(int port, int live, |
| struct ec_response_pd_chip_info_v1 *chip_info) |
| { |
| chip_info->vendor_id = USB_VID_STM32; |
| chip_info->product_id = 0; |
| chip_info->device_id = STM32_DBGMCU_IDCODE & 0xfff; |
| chip_info->fw_version_number = 0xEC; |
| |
| return EC_SUCCESS; |
| } |
| |
| static int stm32gx_ucpd_start_transmit(int port, enum ucpd_tx_msg msg_type) |
| { |
| enum tcpci_msg_type type; |
| |
| /* Select the correct tx desciptor */ |
| ucpd_tx_active_buffer = &ucpd_tx_buffers[msg_type]; |
| type = ucpd_tx_active_buffer->type; |
| |
| if (type == TCPCI_MSG_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_ICR(port) = STM32_UCPD_ICR_HRSTDISCCF | |
| STM32_UCPD_ICR_HRSTSENTCF; |
| 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 != TCPCI_MSG_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 == TCPCI_MSG_TX_BIST_MODE_2) { |
| mode = STM32_UCPD_CR_TXMODE_BIST; |
| } else if (type == TCPCI_MSG_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 <= TCPCI_MSG_CABLE_RESET) |
| STM32_UCPD_TX_ORDSETR(port) = ucpd_txorderset[type]; |
| |
| /* 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; |
| } |
| |
| #ifdef CONFIG_STM32G4_UCPD_DEBUG |
| static void ucpd_task_log(int timeout, enum ucpd_state enter, |
| enum ucpd_state exit, int req, uint32_t evt) |
| { |
| static int same_count = 0; |
| int idx = ucpd_tx_state_log_idx; |
| |
| if (ucpd_tx_state_log_freeze) |
| return; |
| |
| ucpd_tx_statelog[idx].ts = get_time().le.lo; |
| ucpd_tx_statelog[idx].tx_request = req; |
| ucpd_tx_statelog[idx].timeout_us = timeout; |
| ucpd_tx_statelog[idx].enter_state = enter; |
| ucpd_tx_statelog[idx].exit_state = exit; |
| ucpd_tx_statelog[idx].evt = evt; |
| |
| ucpd_tx_state_log_idx = (idx + 1) & TX_STATE_LOG_MASK; |
| |
| if (enter == exit) { |
| same_count++; |
| } else { |
| same_count = 0; |
| } |
| |
| /* |
| * Should not have same enter/exit states. If this happens, then freeze |
| * state log to help in debugging. |
| */ |
| if (same_count > 5) |
| ucpd_tx_state_log_freeze = 1; |
| } |
| |
| static void ucpd_task_log_dump(void) |
| { |
| int n; |
| int idx; |
| |
| ucpd_tx_state_log_freeze = 1; |
| |
| /* current index will be oldest entry in the log */ |
| idx = ucpd_tx_state_log_idx; |
| |
| ccprintf("\n\t UCDP Task Log\n"); |
| for (n = 0; n < TX_STATE_LOG_LEN; n++) { |
| ccprintf("[%d]:\t\%8s\t%8s\t%02x\t%08x\t%09d\t%d\n", n, |
| ucpd_names[ucpd_tx_statelog[idx].enter_state], |
| ucpd_names[ucpd_tx_statelog[idx].exit_state], |
| ucpd_tx_statelog[idx].tx_request, |
| ucpd_tx_statelog[idx].evt, ucpd_tx_statelog[idx].ts, |
| ucpd_tx_statelog[idx].timeout_us); |
| |
| idx = (idx + 1) & TX_STATE_LOG_MASK; |
| crec_msleep(5); |
| } |
| |
| ucpd_tx_state_log_freeze = 0; |
| } |
| #endif |
| |
| static void ucpd_manage_tx(int port, int evt) |
| { |
| enum ucpd_tx_msg msg_src = TX_MSG_NONE; |
| uint16_t hdr; |
| #ifdef CONFIG_STM32G4_UCPD_DEBUG |
| enum ucpd_state enter = ucpd_tx_state; |
| int req = ucpd_tx_request; |
| #endif |
| |
| if (evt & UCPD_EVT_HR_REQ) { |
| /* |
| * Hard reset control messages are treated as a priority. The |
| * control message will already be set up as it comes from the |
| * PRL layer like any other PD ctrl/data message. So just need |
| * to indicate the correct message source and set the state to |
| * hard reset here. |
| */ |
| ucpd_set_tx_state(STATE_HARD_RESET); |
| msg_src = TX_MSG_TCPM; |
| ucpd_tx_request &= ~(1 << msg_src); |
| } |
| |
| 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) { |
| if (evt & UCPD_EVT_RX_MSG) { |
| /* |
| * USB-PD Specification rev 3.0, section 6.10 |
| * On receiving a received message, the protocol |
| * layer shall discard any pending message. |
| * |
| * Since the pending message from the PRL has |
| * not been sent yet, it needs to be discarded |
| * based on the received message event. |
| */ |
| pd_transmit_complete( |
| port, TCPC_TX_COMPLETE_DISCARDED); |
| ucpd_tx_request &= ~MSG_TCPM_MASK; |
| } else if (!ucpd_rx_msg_active) { |
| 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); |
| tx_retry_max = |
| PD_HEADER_REV(hdr) == PD_REV30 ? |
| UCPD_N_RETRY_COUNT_REV30 : |
| UCPD_N_RETRY_COUNT_REV20; |
| } |
| } |
| |
| /* 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_DISC || |
| evt & UCPD_EVT_TX_MSG_FAIL) { |
| if (tx_retry_count < tx_retry_max) { |
| if (evt & UCPD_EVT_RX_MSG) { |
| /* |
| * A message was received so there is no |
| * need to retry this tx message which |
| * had failed to send previously. |
| * Likely, due to the wire |
| * being active from the message that |
| * was just received. |
| */ |
| ucpd_set_tx_state(STATE_IDLE); |
| pd_transmit_complete( |
| port, |
| TCPC_TX_COMPLETE_DISCARDED); |
| ucpd_set_tx_state(STATE_IDLE); |
| } else { |
| /* |
| * Tx attempt failed. Remain in this |
| * state, but trigger new tx attempt. |
| */ |
| msg_src = TX_MSG_TCPM; |
| tx_retry_count++; |
| } |
| } else { |
| enum tcpc_transmit_complete status; |
| |
| status = (evt & UCPD_EVT_TX_MSG_FAIL) ? |
| TCPC_TX_COMPLETE_FAILED : |
| TCPC_TX_COMPLETE_DISCARDED; |
| ucpd_set_tx_state(STATE_IDLE); |
| pd_transmit_complete(port, status); |
| } |
| } |
| break; |
| |
| case STATE_ACTIVE_CRC: |
| if (evt & (UCPD_EVT_TX_MSG_SUCCESS | UCPD_EVT_TX_MSG_FAIL | |
| UCPD_EVT_TX_MSG_DISC)) { |
| ucpd_set_tx_state(STATE_IDLE); |
| if (evt & UCPD_EVT_TX_MSG_FAIL) |
| CPRINTS("ucpd: Failed to send GoodCRC!"); |
| else if (evt & UCPD_EVT_TX_MSG_DISC) |
| CPRINTS("ucpd: GoodCRC message discarded!"); |
| } |
| 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 < tx_retry_max) { |
| 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); |
| } |
| } else if (evt & UCPD_EVT_RX_MSG) { |
| /* |
| * In the case of a collsion, it's possible the port |
| * partner may not send a GoodCRC and instead send the |
| * message that was colliding. If a message is received |
| * in this state, then treat it as a discard from an |
| * incoming message. |
| */ |
| pd_transmit_complete(port, TCPC_TX_COMPLETE_DISCARDED); |
| ucpd_set_tx_state(STATE_IDLE); |
| } |
| 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); |
| } |
| |
| #ifdef CONFIG_STM32G4_UCPD_DEBUG |
| ucpd_task_log(ucpd_timeout_us, enter, ucpd_tx_state, req, evt); |
| #endif |
| } |
| |
| /* |
| * 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 */ |
| stm32gx_ucpd_state_init(port); |
| |
| 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 && |
| !ucpd_rx_msg_active); |
| } |
| } |
| |
| static void ucpd_send_good_crc(int port, uint16_t rx_header) |
| { |
| int msg_id; |
| int rev_id; |
| uint16_t tx_header; |
| enum tcpci_msg_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 tcpci_msg_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 == TCPCI_MSG_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); |
| } |
| |
| int stm32gx_ucpd_transmit(int port, enum tcpci_msg_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); |
| |
| /* |
| * Check for hard reset message here. A different event is used for hard |
| * resets as they are able to interrupt ongoing transmit, and should |
| * have priority over any pending message. |
| */ |
| if (type == TCPCI_MSG_TX_HARD_RESET) |
| task_set_event(TASK_ID_UCPD, UCPD_EVT_HR_REQ); |
| else |
| task_set_event(TASK_ID_UCPD, UCPD_EVT_TCPM_MSG_REQ); |
| |
| 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; |
| } |
| |
| enum ec_error_list stm32gx_ucpd_set_bist_test_mode(const int port, |
| const bool enable) |
| { |
| ucpd_rx_bist_mode = enable; |
| CPRINTS("ucpd: Bist test mode = %d", enable); |
| |
| return EC_SUCCESS; |
| } |
| |
| static 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); |
| #ifdef CONFIG_STM32G4_UCPD_DEBUG |
| ucpd_sr_cc_event = sr; |
| hook_call_deferred(&ucpd_cc_change_notify_data, 0); |
| #endif |
| } |
| |
| /* |
| * 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); |
| #ifdef CONFIG_STM32G4_UCPD_DEBUG |
| ucpd_log_mark_tx_comp(); |
| #endif |
| } else if (sr & |
| (STM32_UCPD_SR_TXMSGABT | STM32_UCPD_SR_TXUND)) { |
| task_set_event(TASK_ID_UCPD, UCPD_EVT_TX_MSG_FAIL); |
| } else if (sr & STM32_UCPD_SR_TXMSGDISC) { |
| task_set_event(TASK_ID_UCPD, UCPD_EVT_TX_MSG_DISC); |
| #ifdef CONFIG_STM32G4_UCPD_DEBUG |
| ucpd_log_mark_tx_comp(); |
| #endif |
| } else if (sr & STM32_UCPD_SR_HRSTSENT) { |
| task_set_event(TASK_ID_UCPD, UCPD_EVT_HR_DONE); |
| } else if (sr & STM32_UCPD_SR_HRSTDISC) { |
| task_set_event(TASK_ID_UCPD, UCPD_EVT_HR_FAIL); |
| } |
| /* 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; |
| ucpd_rx_msg_active = 1; |
| } |
| /* 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) { |
| ucpd_rx_msg_active = 0; |
| /* Check for errors */ |
| if (!(sr & STM32_UCPD_SR_RXERR)) { |
| uint16_t *rx_header = (uint16_t *)ucpd_rx_buffer; |
| enum tcpci_msg_type type; |
| int good_crc = 0; |
| |
| type = STM32_UCPD_RX_ORDSETR(port) & |
| STM32_UCPD_RXORDSETR_MASK; |
| |
| good_crc = ucpd_msg_is_good_crc(*rx_header); |
| |
| #ifdef CONFIG_STM32G4_UCPD_DEBUG |
| ucpd_log_add_msg(*rx_header, 1); |
| #endif |
| /* |
| * Don't pass GoodCRC control messages to the TCPM |
| * layer. In addition, need to filter for SOP'/SOP'' |
| * packets if those are not enabled. SOP'/SOP'' |
| * reception is controlled by a static variable. The |
| * hardware orderset detection pattern can't be changed |
| * without disabling the ucpd peripheral. |
| */ |
| if (!good_crc && (ucpd_rx_sop_prime_enabled || |
| type == TCPCI_MSG_SOP)) { |
| /* |
| * If BIST test mode is active, then still need |
| * to send GoodCRC reply, but there is no need |
| * to send the message up to the tcpm layer. |
| */ |
| if (!ucpd_rx_bist_mode) { |
| if (tcpm_enqueue_message(port)) |
| hook_call_deferred( |
| &ucpd_rx_enque_error_data, |
| 0); |
| } |
| |
| task_set_event(TASK_ID_UCPD, UCPD_EVT_RX_MSG); |
| |
| /* Send GoodCRC message (if required) */ |
| ucpd_send_good_crc(port, *rx_header); |
| } else if (good_crc) { |
| task_set_event(TASK_ID_UCPD, |
| UCPD_EVT_RX_GOOD_CRC); |
| ucpd_crc_id = PD_HEADER_ID(*rx_header); |
| } |
| } else { |
| /* Rx message is complete, but there were bit errors */ |
| CPRINTS("ucpd: rx message error"); |
| } |
| } |
| /* 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); |
| 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); |
| |
| #ifdef CONFIG_STM32G4_UCPD_DEBUG |
| static char ctrl_names[][12] = { |
| "rsvd", "GoodCRC", "Goto Min", "Accept", "Reject", |
| "Ping", "PS_Rdy", "Get_SRC", "Get_SNK", "DR_Swap", |
| "PR_Swap", "VCONN_Swp", "Wait", "Soft_Rst", "RSVD", |
| "RSVD", "Not_Sup", "Get_SRC_Ext", "Get_Status", |
| }; |
| |
| static char data_names[][10] = { |
| "RSVD", "SRC_CAP", "REQUEST", "BIST", "SINK_CAP", "BATTERY", |
| "ALERT", "GET_INFO", "ENTER_USB", "RSVD", "RSVD", "RSVD", |
| "RSVD", "RSVD", "RSVD", "VDM", |
| }; |
| |
| static void ucpd_dump_msg_log(void) |
| { |
| int i; |
| int type; |
| int len; |
| int dir; |
| uint16_t header; |
| char *name; |
| |
| ccprintf("ucpd: msg_total = %d\n", msg_log_cnt); |
| ccprintf("Idx\t Delta(us)\tDir\t Type\t\tLen\t s1 s2 PR\t DR\n"); |
| ccprintf("-----------------------------------------------------------" |
| "-----------------\n"); |
| |
| for (i = 0; i < msg_log_idx; i++) { |
| uint32_t delta_ts = 0; |
| int j; |
| |
| header = msg_log[i].header; |
| |
| if (header != 0xabcd) { |
| type = PD_HEADER_TYPE(header); |
| len = PD_HEADER_CNT(header); |
| name = len ? data_names[type] : ctrl_names[type]; |
| dir = msg_log[i].dir; |
| if (i) { |
| delta_ts = msg_log[i].ts - msg_log[i - 1].ts; |
| } |
| |
| ccprintf("msg[%02d]: %08d\t %s\t %8s\t %02d\t %d %d\t" |
| "%s\t %s", |
| i, delta_ts, dir ? "Rx" : "Tx", name, len, |
| msg_log[i].comp, msg_log[i].crc, |
| PD_HEADER_PROLE(header) ? "SRC" : "SNK", |
| PD_HEADER_DROLE(header) ? "DFP" : "UFP"); |
| len = MIN((len * 4) + 2, MSG_BUF_LEN); |
| for (j = 0; j < len; j++) |
| ccprintf(" %02x", msg_log[i].buf[j]); |
| } else { |
| if (i) { |
| delta_ts = msg_log[i].ts - msg_log[i - 1].ts; |
| } |
| ccprintf("msg[%02d]: %08d\t CC Voltage Change!", i, |
| delta_ts); |
| } |
| ccprintf("\n"); |
| crec_msleep(5); |
| } |
| } |
| |
| static void stm32gx_ucpd_set_cc_debug(int port, int cc_mask, int pull, int rp) |
| { |
| int cc_enable; |
| uint32_t cr = STM32_UCPD_CR(port); |
| |
| /* |
| * Only update ANASUBMODE if specified pull type is Rp. |
| */ |
| if (pull == TYPEC_CC_RP) { |
| cr &= ~STM32_UCPD_CR_ANASUBMODE_MASK; |
| cr |= STM32_UCPD_CR_ANASUBMODE_VAL(UCPD_RP_TO_ANASUB(rp)); |
| } |
| |
| /* |
| * Can't independently set pull value for CC1 from CC2. But, can |
| * independently connect or disconnect pull for CC1 and CC2. Enable here |
| * the CC lines specified by cc_mask. If desired pull is TYPEC_CC_OPEN, |
| * then the CC lines specified in cc_mask will be disabled. |
| */ |
| /* Get existing cc enable value */ |
| cc_enable = (cr & STM32_UCPD_CR_CCENABLE_MASK) >> |
| STM32_UCPD_CR_CCENABLE_SHIFT; |
| /* Apply cc_mask (enable CC line specified) */ |
| cc_enable |= cc_mask; |
| |
| /* Set ANAMODE if cc_pull is Rd */ |
| if (pull == TYPEC_CC_RD) |
| cr |= STM32_UCPD_CR_ANAMODE; |
| /* Clear ANAMODE if cc_pull is Rp */ |
| else if (pull == TYPEC_CC_RP) |
| cr &= ~(STM32_UCPD_CR_ANAMODE); |
| else if (pull == TYPEC_CC_OPEN) |
| cc_enable &= ~cc_mask; |
| |
| /* The value for this field needs to be OR'd in */ |
| cr &= ~STM32_UCPD_CR_CCENABLE_MASK; |
| cr |= STM32_UCPD_CR_CCENABLE_VAL(cc_enable); |
| /* Update pull values */ |
| STM32_UCPD_CR(port) = cr; |
| /* Display updated settings */ |
| ucpd_cc_status(port); |
| } |
| |
| void ucpd_info(int port) |
| { |
| ucpd_cc_status(port); |
| ccprintf("\trx_en\t = %d\n\tpol\t = %d\n", |
| !!(STM32_UCPD_CR(port) & STM32_UCPD_CR_PHYRXEN), |
| !!(STM32_UCPD_CR(port) & STM32_UCPD_CR_PHYCCSEL)); |
| |
| /* Dump ucpd task state info */ |
| ccprintf("ucpd: tx_state = %s, tx_req = %02x, timeout_us = %d\n", |
| ucpd_names[ucpd_tx_state], ucpd_tx_request, ucpd_timeout_us); |
| |
| ucpd_task_log_dump(); |
| } |
| |
| static int command_ucpd(int argc, const char **argv) |
| { |
| uint32_t tx_data = 0; |
| char *e; |
| int val; |
| int port = 0; |
| |
| if (argc < 2) |
| return EC_ERROR_PARAM_COUNT; |
| |
| if (!strcasecmp(argv[1], "rst")) { |
| /* Force reset of ucpd peripheral */ |
| stm32gx_ucpd_init(port); |
| pd_execute_hard_reset(port); |
| task_set_event(PD_PORT_TO_TASK_ID(port), TASK_EVENT_WAKE); |
| } else if (!strcasecmp(argv[1], "info")) { |
| ucpd_info(port); |
| } else if (!strcasecmp(argv[1], "bist")) { |
| /* Need to initiate via DPM to have a timer */ |
| /* TODO(b/182861002): uncomment when Gingerbread has |
| * full PD support landed. |
| * pd_dpm_request(port, DPM_REQUEST_BIST_TX); |
| */ |
| } else if (!strcasecmp(argv[1], "hard")) { |
| stm32gx_ucpd_transmit(port, TCPCI_MSG_TX_HARD_RESET, 0, |
| &tx_data); |
| } else if (!strcasecmp(argv[1], "pol")) { |
| if (argc < 3) |
| return EC_ERROR_PARAM_COUNT; |
| val = strtoi(argv[2], &e, 10); |
| if (val > 1) |
| val = 0; |
| stm32gx_ucpd_set_polarity(port, val); |
| stm32gx_ucpd_set_rx_enable(port, 1); |
| ccprintf("ucpd: set pol = %d, PHYRXEN = 1\n", val); |
| } else if (!strcasecmp(argv[1], "cc")) { |
| int cc_mask; |
| int pull; |
| int rp = 0; /* needs to be initialized */ |
| |
| if (argc < 3) { |
| ucpd_cc_status(port); |
| return EC_SUCCESS; |
| } |
| cc_mask = strtoi(argv[2], &e, 10); |
| if (cc_mask < 1 || cc_mask > 3) |
| return EC_ERROR_PARAM2; |
| /* cc_mask has determines which cc setting to apply */ |
| if (!strcasecmp(argv[3], "rd")) { |
| pull = TYPEC_CC_RD; |
| } else if (!strcasecmp(argv[3], "rp")) { |
| pull = TYPEC_CC_RP; |
| rp = strtoi(argv[4], &e, 10); |
| if (rp < 0 || rp > 2) |
| return EC_ERROR_PARAM4; |
| } else if (!strcasecmp(argv[3], "open")) { |
| pull = TYPEC_CC_OPEN; |
| } else { |
| return EC_ERROR_PARAM3; |
| } |
| stm32gx_ucpd_set_cc_debug(port, cc_mask, pull, rp); |
| |
| } else if (!strcasecmp(argv[1], "log")) { |
| if (argc < 3) { |
| ucpd_dump_msg_log(); |
| } else if (!strcasecmp(argv[2], "clr")) { |
| msg_log_cnt = 0; |
| msg_log_idx = 0; |
| } |
| } else { |
| return EC_ERROR_PARAM1; |
| } |
| return EC_SUCCESS; |
| } |
| DECLARE_CONSOLE_COMMAND(ucpd, command_ucpd, |
| "[rst|info|bist|hard|pol <0|1>|cc xx <rd|rp|open>|log", |
| "ucpd peripheral debug and control options"); |
| #endif |