/* * Copyright 2022 The Chromium OS Authors * * SPDX-License-Identifier: Apache-2.0 */ #define DT_DRV_COMPAT st_stm32_ucpd #include LOG_MODULE_REGISTER(ucpd_stm32, CONFIG_USBC_LOG_LEVEL); #include #include #include #include #include #include #include #include #include "ucpd_stm32_priv.h" static void config_tcpc_irq(void); /** * @brief UCPD TX ORDSET values */ static int ucpd_txorderset[] = { /* SOP ORDSET */ LL_UCPD_ORDERED_SET_SOP, /* SOP PRIME ORDSET */ LL_UCPD_ORDERED_SET_SOP1, /* SOP PRIME PRIME ORDSET */ LL_UCPD_ORDERED_SET_SOP2, /* SOP PRIME DEBUG ORDSET */ LL_UCPD_ORDERED_SET_SOP1_DEBUG, /* SOP PRIME PRIME DEBUG ORDSET */ LL_UCPD_ORDERED_SET_SOP2_DEBUG, /* HARD RESET ORDSET */ LL_UCPD_ORDERED_SET_HARD_RESET, /* CABLE RESET ORDSET */ LL_UCPD_ORDERED_SET_CABLE_RESET, }; /** * @brief Test for a goodCRC message * * @retval true if message is goodCRC, else false */ static bool ucpd_msg_is_good_crc(union pd_header header) { /* * Good CRC is a control message (no data objects) with GOOD_CRC * message type in the header. */ return (header.number_of_data_objects == 0 && header.extended == 0 && header.message_type == PD_CTRL_GOOD_CRC); } #ifdef CONFIG_SOC_SERIES_STM32G0X /** * @brief Apply the UCPD CC1 and CC2 pin configurations. * * UCPDx_STROBE: UCPDx pull-down configuration strobe: * when UCPDx is enabled, with CC1 and CC2 pin UCPD * control bits configured: apply that configuration. */ static void update_stm32g0x_cc_line(UCPD_TypeDef *ucpd_port) { if ((uint32_t)(ucpd_port) == UCPD1_BASE) { SYSCFG->CFGR1 |= SYSCFG_CFGR1_UCPD1_STROBE_Msk; } else { SYSCFG->CFGR1 |= SYSCFG_CFGR1_UCPD2_STROBE_Msk; } } #endif /** * @brief Transmits a data byte from the TX data buffer */ static void ucpd_tx_data_byte(const struct device *dev) { struct tcpc_data *data = dev->data; const struct tcpc_config *const config = dev->config; int index = data->ucpd_tx_active_buffer->msg_index++; LL_UCPD_WriteData(config->ucpd_port, data->ucpd_tx_active_buffer->data.msg[index]); } /** * @brief Receives a data byte and store it in the RX data buffer */ static void ucpd_rx_data_byte(const struct device *dev) { struct tcpc_data *data = dev->data; const struct tcpc_config *const config = dev->config; if (data->ucpd_rx_byte_count < UCPD_BUF_LEN) { data->ucpd_rx_buffer[data->ucpd_rx_byte_count++] = LL_UCPD_ReadData(config->ucpd_port); } } /** * @brief Enables or Disables TX interrupts */ static void ucpd_tx_interrupts_enable(const struct device *dev, bool enable) { const struct tcpc_config *const config = dev->config; uint32_t imr; imr = LL_UCPD_ReadReg(config->ucpd_port, IMR); if (enable) { LL_UCPD_WriteReg(config->ucpd_port, ICR, UCPD_ICR_TX_INT_MASK); LL_UCPD_WriteReg(config->ucpd_port, IMR, imr | UCPD_IMR_TX_INT_MASK); } else { LL_UCPD_WriteReg(config->ucpd_port, IMR, imr & ~UCPD_IMR_TX_INT_MASK); } } /** * @brief Initializes the RX and TX state machine variables */ static void stm32_ucpd_state_init(const struct device *dev) { struct tcpc_data *data = dev->data; /* Init variables used to manage tx process */ data->ucpd_tx_request = 0; data->tx_retry_count = 0; data->ucpd_tx_state = STATE_IDLE; /* Init variables used to manage rx */ data->ucpd_rx_sop_prime_enabled = false; data->ucpd_rx_msg_active = false; data->ucpd_rx_bist_mode = false; /* Vconn tracking variable */ data->ucpd_vconn_enable = false; } /** * @brief Get the CC enable mask. The mask indicates which CC line * is enabled. * * @retval CC Enable mask (bit 0: CC1, bit 1: CC2) */ static uint32_t ucpd_get_cc_enable_mask(const struct device *dev) { struct tcpc_data *data = dev->data; const struct tcpc_config *const config = dev->config; uint32_t mask = UCPD_CR_CCENABLE_Msk; /* * When VCONN is enabled, it is supplied on the CC line that's * not being used for Power Delivery messages. */ if (data->ucpd_vconn_enable) { uint32_t cr = LL_UCPD_ReadReg(config->ucpd_port, CR); int pol = (cr & UCPD_CR_PHYCCSEL); /* Dissable CC line that's used for VCONN */ mask &= ~BIT(UCPD_CR_CCENABLE_Pos + !pol); } return mask; } /** * @brief Get the state of the CC1 and CC2 lines * * @retval 0 on success * @retval -EIO on failure */ static int ucpd_get_cc(const struct device *dev, enum tc_cc_voltage_state *cc1, enum tc_cc_voltage_state *cc2) { const struct tcpc_config *const config = dev->config; int vstate_cc1; int vstate_cc2; int anamode; uint32_t sr; uint32_t cc_msk; /* * cc_voltage_state 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(snk) = 1, but needs to be modified slightly for case * ANAMODE(src) = 0. * * If presenting Rp (source), then need to do 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 = LL_UCPD_ReadReg(config->ucpd_port, SR); /* Get Rp or Rd active */ anamode = LL_UCPD_GetRole(config->ucpd_port); vstate_cc1 = (sr & UCPD_SR_TYPEC_VSTATE_CC1_Msk) >> UCPD_SR_TYPEC_VSTATE_CC1_Pos; vstate_cc2 = (sr & UCPD_SR_TYPEC_VSTATE_CC2_Msk) >> UCPD_SR_TYPEC_VSTATE_CC2_Pos; /* 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; } } /* CC connection detection */ cc_msk = ucpd_get_cc_enable_mask(dev); /* CC1 connection detection */ if (cc_msk & UCPD_CR_CCENABLE_0) { *cc1 = vstate_cc1; } else { *cc1 = TC_CC_VOLT_OPEN; } /* CC2 connection detection */ if (cc_msk & UCPD_CR_CCENABLE_1) { *cc2 = vstate_cc2; } else { *cc2 = TC_CC_VOLT_OPEN; } return 0; } /** * @brief Enable or Disable VCONN * * @retval 0 on success * @retval -EIO on failure * @retval -ENOTSUP if not supported */ static int ucpd_set_vconn(const struct device *dev, bool enable) { struct tcpc_data *data = dev->data; const struct tcpc_config *const config = dev->config; int cr; int ret; if (data->vconn_cb == NULL) { return -ENOTSUP; } /* Update VCONN on/off status. Do this before getting cc enable mask */ data->ucpd_vconn_enable = enable; cr = LL_UCPD_ReadReg(config->ucpd_port, CR); cr &= ~UCPD_CR_CCENABLE_Msk; cr |= ucpd_get_cc_enable_mask(dev); /* Apply cc pull resistor change */ LL_UCPD_WriteReg(config->ucpd_port, CR, cr); #ifdef CONFIG_SOC_SERIES_STM32G0X update_stm32g0x_cc_line(config->ucpd_port); #endif /* Get CC line that VCONN is active on */ data->ucpd_vconn_cc = (cr & UCPD_CR_CCENABLE_0) ? TC_POLARITY_CC2 : TC_POLARITY_CC1; /* Call user supplied callback to set vconn */ ret = data->vconn_cb(dev, data->ucpd_vconn_cc, enable); return ret; } /** * @brief Discharge VCONN * * @retval 0 on success * @retval -EIO on failure * @retval -ENOTSUP if not supported */ static int ucpd_vconn_discharge(const struct device *dev, bool enable) { struct tcpc_data *data = dev->data; const struct tcpc_config *const config = dev->config; /* VCONN should not be discharged while it's enabled */ if (data->ucpd_vconn_enable) { return -EIO; } if (data->vconn_discharge_cb) { /* Use DPM supplied VCONN Discharge */ return data->vconn_discharge_cb(dev, data->ucpd_vconn_cc, enable); } /* Use TCPC VCONN Discharge */ if (enable) { LL_UCPD_VconnDischargeEnable(config->ucpd_port); } else { LL_UCPD_VconnDischargeDisable(config->ucpd_port); } #ifdef CONFIG_SOC_SERIES_STM32G0X update_stm32g0x_cc_line(config->ucpd_port); #endif return 0; } /** * @brief Sets the value of the CC pull up resistor used when operating as a Source * * @retval 0 on success */ static int ucpd_select_rp_value(const struct device *dev, enum tc_rp_value rp) { struct tcpc_data *data = dev->data; data->rp = rp; return 0; } /** * @brief Gets the value of the CC pull up resistor used when operating as a Source * * @retval 0 on success */ static int ucpd_get_rp_value(const struct device *dev, enum tc_rp_value *rp) { struct tcpc_data *data = dev->data; *rp = data->rp; return 0; } /** * @brief Enable or disable Dead Battery resistors */ static void dead_battery(const struct device *dev, bool en) { struct tcpc_data *data = dev->data; #ifdef CONFIG_SOC_SERIES_STM32G0X const struct tcpc_config *const config = dev->config; uint32_t cr; cr = LL_UCPD_ReadReg(config->ucpd_port, CR); if (en) { cr |= UCPD_CR_DBATTEN; } else { cr &= ~UCPD_CR_DBATTEN; } LL_UCPD_WriteReg(config->ucpd_port, CR, cr); update_stm32g0x_cc_line(config->ucpd_port); #else if (en) { CLEAR_BIT(PWR->CR3, PWR_CR3_UCPD_DBDIS); } else { SET_BIT(PWR->CR3, PWR_CR3_UCPD_DBDIS); } #endif data->dead_battery_active = en; } /** * @brief Set the CC pull up or pull down resistors * * @retval 0 on success * @retval -EIO on failure */ static int ucpd_set_cc(const struct device *dev, enum tc_cc_pull cc_pull) { const struct tcpc_config *const config = dev->config; struct tcpc_data *data = dev->data; uint32_t cr; /* Disable dead battery if it's active */ if (data->dead_battery_active) { dead_battery(dev, false); } cr = LL_UCPD_ReadReg(config->ucpd_port, CR); /* * 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 &= ~UCPD_CR_ANASUBMODE_Msk; cr |= STM32_UCPD_CR_ANASUBMODE_VAL(UCPD_RP_TO_ANASUB(data->rp)); /* Disconnect both pull from both CC lines for R_open case */ cr &= ~UCPD_CR_CCENABLE_Msk; /* Set ANAMODE if cc_pull is Rd */ if (cc_pull == TC_CC_RD) { cr |= (UCPD_CR_ANAMODE | UCPD_CR_CCENABLE_Msk); /* Clear ANAMODE if cc_pull is Rp */ } else if (cc_pull == TC_CC_RP) { cr &= ~(UCPD_CR_ANAMODE); cr |= ucpd_get_cc_enable_mask(dev); } /* Update pull values */ LL_UCPD_WriteReg(config->ucpd_port, CR, cr); #ifdef CONFIG_SOC_SERIES_STM32G0X update_stm32g0x_cc_line(config->ucpd_port); #endif return 0; } /** * @brief Set the polarity of the CC line, which is the active CC line * used for power delivery. * * @retval 0 on success * @retval -EIO on failure * @retval -ENOTSUP if polarity is not supported */ static int ucpd_cc_set_polarity(const struct device *dev, enum tc_cc_polarity polarity) { const struct tcpc_config *const config = dev->config; uint32_t cr; cr = LL_UCPD_ReadReg(config->ucpd_port, CR); /* * 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 == TC_POLARITY_CC1) { cr &= ~UCPD_CR_PHYCCSEL; } else if (polarity == TC_POLARITY_CC2) { cr |= UCPD_CR_PHYCCSEL; } else { return -ENOTSUP; } /* Update polarity */ LL_UCPD_WriteReg(config->ucpd_port, CR, cr); return 0; } /** * @brief Enable or Disable Power Delivery * * @retval 0 on success * @retval -EIO on failure */ static int ucpd_set_rx_enable(const struct device *dev, bool enable) { const struct tcpc_config *const config = dev->config; uint32_t imr; uint32_t cr; imr = LL_UCPD_ReadReg(config->ucpd_port, IMR); cr = LL_UCPD_ReadReg(config->ucpd_port, CR); /* * USB PD receiver enable is controlled by the bit PHYRXEN in * UCPD_CR. Enable Rx interrupts when RX PD decoder is active. */ if (enable) { /* Clear the RX alerts bits */ LL_UCPD_WriteReg(config->ucpd_port, ICR, UCPD_ICR_RX_INT_MASK); imr |= UCPD_IMR_RX_INT_MASK; cr |= UCPD_CR_PHYRXEN; LL_UCPD_WriteReg(config->ucpd_port, IMR, imr); LL_UCPD_WriteReg(config->ucpd_port, CR, cr); } else { imr &= ~UCPD_IMR_RX_INT_MASK; cr &= ~UCPD_CR_PHYRXEN; LL_UCPD_WriteReg(config->ucpd_port, CR, cr); LL_UCPD_WriteReg(config->ucpd_port, IMR, imr); } return 0; } /** * @brief Set the Power and Data role used when sending goodCRC messages * * @retval 0 on success * @retval -EIO on failure */ static int ucpd_set_roles(const struct device *dev, enum tc_power_role power_role, enum tc_data_role data_role) { struct tcpc_data *data = dev->data; data->msg_header.pr = power_role; data->msg_header.dr = data_role; return 0; } /** * @brief Enable the reception of SOP Prime messages * * @retval 0 on success * @retval -EIO on failure */ static int ucpd_sop_prime_enable(const struct device *dev, bool enable) { struct tcpc_data *data = dev->data; /* Update static variable used to filter SOP//SOP'' messages */ data->ucpd_rx_sop_prime_enabled = enable; return 0; } /** * @brief State transmitting a message */ static void ucpd_start_transmit(const struct device *dev, enum ucpd_tx_msg msg_type) { struct tcpc_data *data = dev->data; const struct tcpc_config *const config = dev->config; enum pd_packet_type type; uint32_t cr; uint32_t imr; cr = LL_UCPD_ReadReg(config->ucpd_port, CR); /* Select the correct tx descriptor */ data->ucpd_tx_active_buffer = &data->ucpd_tx_buffers[msg_type]; type = data->ucpd_tx_active_buffer->type; if (type == PD_PACKET_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 */ LL_UCPD_WriteReg(config->ucpd_port, ICR, UCPD_ICR_HRSTDISCCF | UCPD_ICR_HRSTSENTCF); imr = LL_UCPD_ReadReg(config->ucpd_port, IMR); imr |= UCPD_IMR_HRSTDISCIE | UCPD_IMR_HRSTSENTIE; LL_UCPD_WriteReg(config->ucpd_port, IMR, imr); /* Initiate Hard Reset */ cr |= UCPD_CR_TXHRST; LL_UCPD_WriteReg(config->ucpd_port, CR, cr); } else if (type != PD_PACKET_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 == PD_PACKET_TX_BIST_MODE_2) { mode = LL_UCPD_TXMODE_BIST_CARRIER2; } else if (type == PD_PACKET_CABLE_RESET) { mode = LL_UCPD_TXMODE_CABLE_RESET; } else { mode = LL_UCPD_TXMODE_NORMAL; msg_len = data->ucpd_tx_active_buffer->msg_len; } LL_UCPD_WriteTxPaySize(config->ucpd_port, msg_len); /* Set tx mode */ cr &= ~UCPD_CR_TXMODE_Msk; cr |= mode; LL_UCPD_WriteReg(config->ucpd_port, CR, cr); /* Index into ordset enum for start of packet */ if (type <= PD_PACKET_CABLE_RESET) { LL_UCPD_WriteTxOrderSet(config->ucpd_port, ucpd_txorderset[type]); } /* Reset msg byte index */ data->ucpd_tx_active_buffer->msg_index = 0; /* Enable interrupts */ ucpd_tx_interrupts_enable(dev, 1); /* Trigger ucpd peripheral to start pd message transmit */ LL_UCPD_SendMessage(config->ucpd_port); } } /** * @brief Set the current state of the TX state machine */ static void ucpd_set_tx_state(const struct device *dev, enum ucpd_state state) { struct tcpc_data *data = dev->data; data->ucpd_tx_state = state; } /** * @brief Wrapper function for calling alert handler */ static void ucpd_notify_handler(struct alert_info *info, enum tcpc_alert alert) { if (info->handler) { info->handler(info->dev, info->data, alert); } } /** * @brief This is the TX state machine */ static void ucpd_manage_tx(struct alert_info *info) { struct tcpc_data *data = info->dev->data; enum ucpd_tx_msg msg_src = TX_MSG_NONE; union pd_header hdr; if (atomic_test_and_clear_bit(&info->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(info->dev, STATE_HARD_RESET); msg_src = TX_MSG_TCPM; data->ucpd_tx_request &= ~BIT(msg_src); } switch (data->ucpd_tx_state) { case STATE_IDLE: if (data->ucpd_tx_request & MSG_GOOD_CRC_MASK) { ucpd_set_tx_state(info->dev, STATE_ACTIVE_CRC); msg_src = TX_MSG_GOOD_CRC; } else if (data->ucpd_tx_request & MSG_TCPM_MASK) { if (atomic_test_and_clear_bit(&info->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. */ ucpd_notify_handler(info, TCPC_ALERT_TRANSMIT_MSG_DISCARDED); data->ucpd_tx_request &= ~MSG_TCPM_MASK; } else if (!data->ucpd_rx_msg_active) { ucpd_set_tx_state(info->dev, STATE_ACTIVE_TCPM); msg_src = TX_MSG_TCPM; /* Save msgID required for GoodCRC check */ hdr.raw_value = data->ucpd_tx_buffers[TX_MSG_TCPM].data.header; data->msg_id_match = hdr.message_id; data->tx_retry_max = hdr.specification_revision == PD_REV30 ? UCPD_N_RETRY_COUNT_REV30 : UCPD_N_RETRY_COUNT_REV20; } } /* If state is not idle, then start tx message */ if (data->ucpd_tx_state != STATE_IDLE) { data->ucpd_tx_request &= ~BIT(msg_src); data->tx_retry_count = 0; } break; case STATE_ACTIVE_TCPM: /* * Check if tx msg has finished. 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 (atomic_test_and_clear_bit(&info->evt, UCPD_EVT_TX_MSG_SUCCESS)) { ucpd_set_tx_state(info->dev, STATE_WAIT_CRC_ACK); /* Start the GoodCRC RX Timer */ k_timer_start(&data->goodcrc_rx_timer, K_USEC(1000), K_NO_WAIT); } else if (atomic_test_and_clear_bit(&info->evt, UCPD_EVT_TX_MSG_DISC) || atomic_test_and_clear_bit(&info->evt, UCPD_EVT_TX_MSG_FAIL)) { if (data->tx_retry_count < data->tx_retry_max) { if (atomic_test_and_clear_bit(&info->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(info->dev, STATE_IDLE); ucpd_notify_handler(info, TCPC_ALERT_TRANSMIT_MSG_DISCARDED); ucpd_set_tx_state(info->dev, STATE_IDLE); } else { /* * Tx attempt failed. Remain in this * state, but trigger new tx attempt. */ msg_src = TX_MSG_TCPM; data->tx_retry_count++; } } else { enum tcpc_alert status; status = (atomic_test_and_clear_bit(&info->evt, UCPD_EVT_TX_MSG_FAIL)) ? TCPC_ALERT_TRANSMIT_MSG_FAILED : TCPC_ALERT_TRANSMIT_MSG_DISCARDED; ucpd_set_tx_state(info->dev, STATE_IDLE); ucpd_notify_handler(info, status); } } break; case STATE_ACTIVE_CRC: if (atomic_test_bit(&info->evt, UCPD_EVT_TX_MSG_SUCCESS) || atomic_test_bit(&info->evt, UCPD_EVT_TX_MSG_FAIL) || atomic_test_bit(&info->evt, UCPD_EVT_TX_MSG_DISC)) { atomic_clear_bit(&info->evt, UCPD_EVT_TX_MSG_SUCCESS); atomic_clear_bit(&info->evt, UCPD_EVT_TX_MSG_FAIL); atomic_clear_bit(&info->evt, UCPD_EVT_TX_MSG_DISC); ucpd_set_tx_state(info->dev, STATE_IDLE); if (atomic_test_and_clear_bit(&info->evt, UCPD_EVT_TX_MSG_FAIL)) { LOG_INF("ucpd: Failed to send GoodCRC!"); } else if (atomic_test_and_clear_bit(&info->evt, UCPD_EVT_TX_MSG_DISC)) { LOG_INF("ucpd: GoodCRC message discarded!"); } } break; case STATE_WAIT_CRC_ACK: if (atomic_test_and_clear_bit(&info->evt, UCPD_EVT_RX_GOOD_CRC) && data->ucpd_crc_id == data->msg_id_match) { /* GoodCRC with matching ID was received */ ucpd_notify_handler(info, TCPC_ALERT_TRANSMIT_MSG_SUCCESS); ucpd_set_tx_state(info->dev, STATE_IDLE); } else if (k_timer_status_get(&data->goodcrc_rx_timer)) { /* Stop the GoodCRC RX Timer */ k_timer_stop(&data->goodcrc_rx_timer); /* GoodCRC w/out match or timeout waiting */ if (data->tx_retry_count < data->tx_retry_max) { ucpd_set_tx_state(info->dev, STATE_ACTIVE_TCPM); msg_src = TX_MSG_TCPM; data->tx_retry_count++; } else { ucpd_set_tx_state(info->dev, STATE_IDLE); ucpd_notify_handler(info, TCPC_ALERT_TRANSMIT_MSG_FAILED); } } else if (atomic_test_and_clear_bit(&info->evt, UCPD_EVT_RX_MSG)) { /* * In the case of a collision, 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. */ ucpd_notify_handler(info, TCPC_ALERT_TRANSMIT_MSG_DISCARDED); ucpd_set_tx_state(info->dev, STATE_IDLE); } break; case STATE_HARD_RESET: if (atomic_test_bit(&info->evt, UCPD_EVT_HR_DONE) || atomic_test_bit(&info->evt, UCPD_EVT_HR_FAIL)) { atomic_clear_bit(&info->evt, UCPD_EVT_HR_DONE); atomic_clear_bit(&info->evt, UCPD_EVT_HR_FAIL); /* HR complete, reset tx state values */ ucpd_set_tx_state(info->dev, STATE_IDLE); data->ucpd_tx_request = 0; data->tx_retry_count = 0; } break; } /* * NOTE: TX_MSG_GOOD_CRC messages are sent from the ISR to reduce latency * when sending those messages, so don't resend them here. * * If msg_src is valid and not a TX_MSG_GOOD_CRC, then start transmit. */ if (msg_src != TX_MSG_GOOD_CRC && msg_src > TX_MSG_NONE) { ucpd_start_transmit(info->dev, msg_src); } } /** * @brief Alert handler */ static void ucpd_alert_handler(struct k_work *item) { struct alert_info *info = CONTAINER_OF(item, struct alert_info, work); struct tcpc_data *data = info->dev->data; if (atomic_test_and_clear_bit(&info->evt, UCPD_EVT_EVENT_CC)) { ucpd_notify_handler(info, TCPC_ALERT_CC_STATUS); } if (atomic_test_and_clear_bit(&info->evt, UCPD_EVT_HARD_RESET_RECEIVED)) { ucpd_notify_handler(info, TCPC_ALERT_HARD_RESET_RECEIVED); } if (atomic_test_and_clear_bit(&info->evt, UCPD_EVT_RX_MSG)) { ucpd_notify_handler(info, TCPC_ALERT_MSG_STATUS); } /* * USB-PD messages are initiated 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 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. */ if (atomic_test_and_clear_bit(&info->evt, UCPD_EVT_GOOD_CRC_REQ)) { data->ucpd_tx_request |= MSG_GOOD_CRC_MASK; } if (atomic_test_and_clear_bit(&info->evt, UCPD_EVT_TCPM_MSG_REQ)) { data->ucpd_tx_request |= MSG_TCPM_MASK; } /* * Manage PD tx messages. The state machine may need to be * called more than once. 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(info); } while (data->ucpd_tx_state != STATE_IDLE); } /** * @brief Sends a goodCRC message */ static void ucpd_send_good_crc(const struct device *dev, union pd_header rx_header) { struct tcpc_data *data = dev->data; const struct tcpc_config *const config = dev->config; union pd_header tx_header; enum pd_packet_type tx_type; struct alert_info *info = &data->alert_info; /* * 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 tcpc_packet_type and so can be used * directly. */ tx_type = LL_UCPD_ReadRxOrderSet(config->ucpd_port); /* * 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 */ tx_header.message_type = PD_CTRL_GOOD_CRC; if (tx_type == PD_PACKET_SOP) { tx_header.port_power_role = data->msg_header.pr; tx_header.port_data_role = data->msg_header.dr; } else { tx_header.port_power_role = 0; tx_header.port_data_role = 0; } tx_header.message_id = rx_header.message_id; tx_header.number_of_data_objects = 0; tx_header.specification_revision = rx_header.specification_revision; tx_header.extended = 0; /* Good CRC is header with no other objects */ data->ucpd_tx_buffers[TX_MSG_GOOD_CRC].msg_len = MSG_HEADER_SIZE; data->ucpd_tx_buffers[TX_MSG_GOOD_CRC].data.header = tx_header.raw_value; data->ucpd_tx_buffers[TX_MSG_GOOD_CRC].type = tx_type; /* Notify ucpd task that a GoodCRC message tx request is pending */ atomic_set_bit(&info->evt, UCPD_EVT_GOOD_CRC_REQ); /* Send TX_MSG_GOOD_CRC message here to reduce latency */ ucpd_start_transmit(dev, TX_MSG_GOOD_CRC); } /** * @brief Transmit a power delivery message * * @retval 0 on success * @retval -EFAULT on failure */ static int ucpd_transmit_data(const struct device *dev, struct pd_msg *msg) { struct tcpc_data *data = dev->data; /* Length in bytes = (4 * object len) + 2 header bytes */ int len = PD_CONVERT_PD_HEADER_COUNT_TO_BYTES(msg->header.number_of_data_objects) + 2; if (len > UCPD_BUF_LEN) { return -EFAULT; } /* Store tx msg info in TCPM msg descriptor */ data->ucpd_tx_buffers[TX_MSG_TCPM].msg_len = len; data->ucpd_tx_buffers[TX_MSG_TCPM].type = msg->type; data->ucpd_tx_buffers[TX_MSG_TCPM].data.header = msg->header.raw_value; /* Copy msg objects to ucpd data buffer, after 2 header bytes */ memcpy(data->ucpd_tx_buffers[TX_MSG_TCPM].data.msg + 2, (uint8_t *)msg->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 (msg->type == PD_PACKET_TX_HARD_RESET) { atomic_set_bit(&data->alert_info.evt, UCPD_EVT_HR_REQ); } else { atomic_set_bit(&data->alert_info.evt, UCPD_EVT_TCPM_MSG_REQ); } /* Start transmission */ k_work_submit(&data->alert_info.work); return 0; } /** * @brief Tests if a received Power Delivery message is pending * * @retval true if message is pending, else false */ static bool ucpd_is_rx_pending_msg(const struct device *dev, enum pd_packet_type *type) { struct tcpc_data *data = dev->data; bool ret; ret = (*(uint32_t *)data->ucpd_rx_buffer > 0); if (ret & (type != NULL)) { *type = *(uint16_t *)data->ucpd_rx_buffer; } return ret; } /** * @brief Retrieves the Power Delivery message from the TCPC * * @retval number of bytes received * @retval -EIO on no message to retrieve * @retval -EFAULT on buf being NULL */ static int ucpd_receive_data(const struct device *dev, struct pd_msg *msg) { struct tcpc_data *data = dev->data; int ret = 0; if (msg == NULL) { return -EFAULT; } /* Make sure we have a message to retrieve */ if (!ucpd_is_rx_pending_msg(dev, NULL)) { return -EIO; } msg->type = *(uint16_t *)data->ucpd_rx_buffer; msg->header.raw_value = *((uint16_t *)data->ucpd_rx_buffer + 1); msg->len = PD_CONVERT_PD_HEADER_COUNT_TO_BYTES(msg->header.number_of_data_objects); memcpy(msg->data, (data->ucpd_rx_buffer + PACKET_TYPE_SIZE + MSG_HEADER_SIZE), msg->len); ret = msg->len + MSG_HEADER_SIZE; /* All done. Clear type and header */ *(uint32_t *)data->ucpd_rx_buffer = 0; return ret; } /** * @brief Enable or Disable BIST Test mode * * return 0 on success * return -EIO on failure */ static int ucpd_set_bist_test_mode(const struct device *dev, bool enable) { struct tcpc_data *data = dev->data; data->ucpd_rx_bist_mode = enable; LOG_INF("ucpd: Bist test mode = %d", enable); return 0; } /** * @brief UCPD interrupt handler */ static void ucpd_isr(const struct device *dev_inst[]) { const struct device *dev; const struct tcpc_config *config; struct tcpc_data *data; uint32_t sr; struct alert_info *info; uint32_t tx_done_mask = UCPD_SR_TXUND | UCPD_SR_TXMSGSENT | UCPD_SR_TXMSGABT | UCPD_SR_TXMSGDISC | UCPD_SR_HRSTSENT | UCPD_SR_HRSTDISC; #if DT_NUM_INST_STATUS_OKAY(DT_DRV_COMPAT) > 1 /* * Multiple UCPD ports are available */ uint32_t sr0; uint32_t sr1; /* * Since the UCPD peripherals share the same interrupt line, determine * which one generated the interrupt. */ /* Read UCPD1 and UCPD2 Status Registers */ sr0 = LL_UCPD_ReadReg(((const struct tcpc_config *)dev_inst[0]->config)->ucpd_port, SR); sr1 = LL_UCPD_ReadReg(((const struct tcpc_config *)dev_inst[1]->config)->ucpd_port, SR); if (sr0) { dev = dev_inst[0]; } else if (sr1) { dev = dev_inst[1]; } else { /* * The interrupt was triggered by some other device sharing this * interrupt line. */ return; } #else /* * Only one UCPD port available */ dev = dev_inst[0]; #endif /* Get the UCPD port that initiated that interrupt */ config = dev->config; data = dev->data; info = &data->alert_info; /* Read the status register */ sr = LL_UCPD_ReadReg(config->ucpd_port, SR); /* Check for CC events, set event to wake PD task */ if (sr & (UCPD_SR_TYPECEVT1 | UCPD_SR_TYPECEVT2)) { /* Set CC event bit */ atomic_set_bit(&info->evt, UCPD_EVT_EVENT_CC); } /* * 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 & UCPD_SR_TXMSGSENT) { atomic_set_bit(&info->evt, UCPD_EVT_TX_MSG_SUCCESS); } else if (sr & (UCPD_SR_TXMSGABT | UCPD_SR_TXUND)) { atomic_set_bit(&info->evt, UCPD_EVT_TX_MSG_FAIL); } else if (sr & (UCPD_SR_TXMSGDISC | UCPD_SR_HRSTDISC)) { atomic_set_bit(&info->evt, UCPD_EVT_TX_MSG_DISC); } else if (sr & UCPD_SR_HRSTSENT) { atomic_set_bit(&info->evt, UCPD_EVT_HR_DONE); } else if (sr & UCPD_SR_HRSTDISC) { atomic_set_bit(&info->evt, UCPD_EVT_HR_FAIL); } /* Disable Tx interrupts */ ucpd_tx_interrupts_enable(dev, 0); } /* Check for data register empty */ if (sr & UCPD_SR_TXIS) { ucpd_tx_data_byte(dev); } /* Check for Rx Events */ /* Check first for start of new message */ if (sr & UCPD_SR_RXORDDET) { /* Add message type to pd message buffer */ *(uint16_t *)data->ucpd_rx_buffer = LL_UCPD_ReadRxOrderSet(config->ucpd_port); data->ucpd_rx_byte_count = 2; data->ucpd_rx_msg_active = true; } /* Check for byte received */ if (sr & UCPD_SR_RXNE) { ucpd_rx_data_byte(dev); } /* Check for end of message */ if (sr & UCPD_SR_RXMSGEND) { data->ucpd_rx_msg_active = false; /* Check for errors */ if (!(sr & UCPD_SR_RXERR)) { enum pd_packet_type type; union pd_header rx_header; int good_crc; type = *(uint16_t *)data->ucpd_rx_buffer; rx_header.raw_value = *((uint16_t *)data->ucpd_rx_buffer + 1); good_crc = ucpd_msg_is_good_crc(rx_header); /* * 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 && (data->ucpd_rx_sop_prime_enabled || type == PD_PACKET_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 (!data->ucpd_rx_bist_mode) { atomic_set_bit(&info->evt, UCPD_EVT_RX_MSG); } /* Send GoodCRC message (if required) */ ucpd_send_good_crc(dev, rx_header); } else if (good_crc) { atomic_set_bit(&info->evt, UCPD_EVT_RX_GOOD_CRC); data->ucpd_crc_id = rx_header.message_id; } } else { /* Rx message is complete, but there were bit errors */ LOG_ERR("ucpd: rx message error"); } } /* Check for fault conditions */ if (sr & UCPD_SR_RXHRSTDET) { /* hard reset received */ atomic_set_bit(&info->evt, UCPD_EVT_HARD_RESET_RECEIVED); } /* Clear interrupts now that PD events have been set */ LL_UCPD_WriteReg(config->ucpd_port, ICR, sr & UCPD_ICR_ALL_INT_MASK); /* Notify application of events */ k_work_submit(&info->work); } /** * @brief Dump a set of TCPC registers * * @retval 0 on success * @retval -EIO on failure */ static int ucpd_dump_std_reg(const struct device *dev) { const struct tcpc_config *const config = dev->config; LOG_INF("CFGR1: %08x", LL_UCPD_ReadReg(config->ucpd_port, CFG1)); LOG_INF("CFGR2: %08x", LL_UCPD_ReadReg(config->ucpd_port, CFG2)); LOG_INF("CR: %08x", LL_UCPD_ReadReg(config->ucpd_port, CR)); LOG_INF("IMR: %08x", LL_UCPD_ReadReg(config->ucpd_port, IMR)); LOG_INF("SR: %08x", LL_UCPD_ReadReg(config->ucpd_port, SR)); LOG_INF("ICR: %08x\n", LL_UCPD_ReadReg(config->ucpd_port, ICR)); return 0; } /** * @brief Sets the alert function that's called when an interrupt is triggered * due to a TCPC alert * * @retval 0 on success * @retval -EINVAL on failure */ static int ucpd_set_alert_handler_cb(const struct device *dev, tcpc_alert_handler_cb_t handler, void *alert_data) { struct tcpc_data *data = dev->data; data->alert_info.handler = handler; data->alert_info.data = alert_data; return 0; } /** * @brief Sets a callback that can enable or disable VCONN if the TCPC is * unable to or the system is configured in a way that does not use * the VCONN control capabilities of the TCPC * */ static void ucpd_set_vconn_cb(const struct device *dev, tcpc_vconn_control_cb_t vconn_cb) { struct tcpc_data *data = dev->data; data->vconn_cb = vconn_cb; } /** * @brief Sets a callback that can discharge VCONN if the TCPC is * unable to or the system is configured in a way that does not use * the VCONN discharge capabilities of the TCPC * */ static void ucpd_set_vconn_discharge_cb(const struct device *dev, tcpc_vconn_discharge_cb_t cb) { struct tcpc_data *data = dev->data; data->vconn_discharge_cb = cb; } /** * @brief UCPD interrupt init */ static void ucpd_isr_init(const struct device *dev) { const struct tcpc_config *const config = dev->config; struct tcpc_data *data = dev->data; struct alert_info *info = &data->alert_info; /* Init GoodCRC Receive timer */ k_timer_init(&data->goodcrc_rx_timer, NULL, NULL); /* Disable all alert bits */ LL_UCPD_WriteReg(config->ucpd_port, IMR, 0); /* Clear all alert handler */ ucpd_set_alert_handler_cb(dev, NULL, NULL); /* Save device structure for use in the alert handlers */ info->dev = dev; /* Initialize the work handler */ k_work_init(&info->work, ucpd_alert_handler); /* Configure CC change alerts */ LL_UCPD_WriteReg(config->ucpd_port, IMR, UCPD_IMR_TYPECEVT1IE | UCPD_IMR_TYPECEVT2IE); LL_UCPD_WriteReg(config->ucpd_port, ICR, UCPD_ICR_TYPECEVT1CF | UCPD_ICR_TYPECEVT2CF); /* SOP'/SOP'' must be enabled via TCPCI call */ data->ucpd_rx_sop_prime_enabled = false; stm32_ucpd_state_init(dev); /* Configure and enable the IRQ */ config_tcpc_irq(); } /** * @brief Initializes the TCPC * * @retval 0 on success * @retval -EIO on failure */ static int ucpd_init(const struct device *dev) { const struct tcpc_config *const config = dev->config; struct tcpc_data *data = dev->data; uint32_t cfg1; int ret; LOG_DBG("Pinctrl signals configuration"); ret = pinctrl_apply_state(config->ucpd_pcfg, PINCTRL_STATE_DEFAULT); if (ret < 0) { LOG_ERR("USB pinctrl setup failed (%d)", ret); return ret; } /* * The UCPD port is disabled in the LL_UCPD_Init function * * NOTE: For proper Power Management operation, this function * should not be used because it circumvents the zephyr * clock API. Instead, DTS clock settings and the zephyr * clock API should be used to enable clocks. */ ret = LL_UCPD_Init(config->ucpd_port, (LL_UCPD_InitTypeDef *)&config->ucpd_params); if (ret == SUCCESS) { /* Init Rp to USB */ data->rp = TC_RP_USB; /* * Set RXORDSETEN field to control which types of ordered sets the PD * receiver must receive. */ cfg1 = LL_UCPD_ReadReg(config->ucpd_port, CFG1); cfg1 |= LL_UCPD_ORDERSET_SOP | LL_UCPD_ORDERSET_SOP1 | LL_UCPD_ORDERSET_SOP2 | LL_UCPD_ORDERSET_HARDRST; LL_UCPD_WriteReg(config->ucpd_port, CFG1, cfg1); /* Enable UCPD port */ LL_UCPD_Enable(config->ucpd_port); /* Enable Dead Battery Support */ if (config->ucpd_dead_battery) { dead_battery(dev, true); } else { /* * Some devices have dead battery enabled by default * after power up, so disable it */ dead_battery(dev, false); } /* Initialize the isr */ ucpd_isr_init(dev); } else { return -EIO; } return 0; } static const struct tcpc_driver_api driver_api = { .init = ucpd_init, .set_alert_handler_cb = ucpd_set_alert_handler_cb, .get_cc = ucpd_get_cc, .set_rx_enable = ucpd_set_rx_enable, .is_rx_pending_msg = ucpd_is_rx_pending_msg, .receive_data = ucpd_receive_data, .transmit_data = ucpd_transmit_data, .select_rp_value = ucpd_select_rp_value, .get_rp_value = ucpd_get_rp_value, .set_cc = ucpd_set_cc, .set_roles = ucpd_set_roles, .set_vconn_cb = ucpd_set_vconn_cb, .set_vconn_discharge_cb = ucpd_set_vconn_discharge_cb, .set_vconn = ucpd_set_vconn, .vconn_discharge = ucpd_vconn_discharge, .set_cc_polarity = ucpd_cc_set_polarity, .dump_std_reg = ucpd_dump_std_reg, .set_bist_test_mode = ucpd_set_bist_test_mode, .sop_prime_enable = ucpd_sop_prime_enable, }; #define DEV_INST_INIT(n) dev_inst[n] = DEVICE_DT_INST_GET(n); static void config_tcpc_irq(void) { static int inst_num; static const struct device *dev_inst[DT_NUM_INST_STATUS_OKAY(DT_DRV_COMPAT)]; /* Initialize and enable shared irq on last instance */ if (++inst_num == DT_NUM_INST_STATUS_OKAY(DT_DRV_COMPAT)) { DT_INST_FOREACH_STATUS_OKAY(DEV_INST_INIT) IRQ_CONNECT(DT_INST_IRQN(0), DT_INST_IRQ(0, priority), ucpd_isr, dev_inst, 0); irq_enable(DT_INST_IRQN(0)); } } BUILD_ASSERT(DT_NUM_INST_STATUS_OKAY(DT_DRV_COMPAT) > 0, "No compatible STM32 TCPC instance found"); #define TCPC_DRIVER_INIT(inst) \ PINCTRL_DT_INST_DEFINE(inst); \ static struct tcpc_data drv_data_##inst; \ static const struct tcpc_config drv_config_##inst = { \ .ucpd_pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(inst), \ .ucpd_port = (UCPD_TypeDef *)DT_INST_REG_ADDR(inst), \ .ucpd_params.psc_ucpdclk = ilog2(DT_INST_PROP(inst, psc_ucpdclk)), \ .ucpd_params.transwin = DT_INST_PROP(inst, transwin) - 1, \ .ucpd_params.IfrGap = DT_INST_PROP(inst, ifrgap) - 1, \ .ucpd_params.HbitClockDiv = DT_INST_PROP(inst, hbitclkdiv) - 1, \ .ucpd_dead_battery = DT_INST_PROP(inst, dead_battery), \ }; \ DEVICE_DT_INST_DEFINE(inst, \ &ucpd_init, \ NULL, \ &drv_data_##inst, \ &drv_config_##inst, \ POST_KERNEL, \ CONFIG_USBC_TCPC_INIT_PRIORITY, \ &driver_api); DT_INST_FOREACH_STATUS_OKAY(TCPC_DRIVER_INIT)