| // Copyright 2024 The Pigweed Authors |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| // use this file except in compliance with the License. You may obtain a copy of |
| // the License at |
| // |
| // https://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| // License for the specific language governing permissions and limitations under |
| // the License. |
| |
| #include "pw_uart_mcuxpresso/uart_nonblocking.h" |
| |
| #include "fsl_flexcomm.h" |
| #include "pw_assert/check.h" |
| #include "pw_log/log.h" |
| |
| namespace pw::uart { |
| |
| // Deinitialize the USART. |
| void UartMcuxpressoNonBlocking::Deinit() { |
| std::lock_guard lock(interrupt_lock_); |
| |
| if (!initialized_) { |
| return; |
| } |
| |
| DoCancelWriteLockHeld(); |
| DoCancelFlushOutputLockHeld(); |
| |
| DoCancelReadLockHeld(); |
| // Cancel read into ring buffer as DoCancelRead starts it again. |
| USART_TransferAbortReceive(config_.usart_base, &uart_handle_); |
| |
| USART_Deinit(config_.usart_base); |
| clock_tree_element_.Release().IgnoreError(); |
| initialized_ = false; |
| } |
| |
| UartMcuxpressoNonBlocking::~UartMcuxpressoNonBlocking() { Deinit(); } |
| |
| // Initialize the USART based on the configuration |
| // specified during object creation. |
| Status UartMcuxpressoNonBlocking::Init() { |
| { |
| std::lock_guard lock(interrupt_lock_); |
| if (initialized_) { |
| return Status::FailedPrecondition(); |
| } |
| } |
| |
| if (config_.usart_base == nullptr) { |
| return Status::InvalidArgument(); |
| } |
| if (config_.baud_rate == 0) { |
| return Status::InvalidArgument(); |
| } |
| |
| usart_config_t defconfig; |
| USART_GetDefaultConfig(&defconfig); |
| |
| defconfig.baudRate_Bps = config_.baud_rate; |
| defconfig.enableHardwareFlowControl = config_.flow_control; |
| defconfig.parityMode = config_.parity; |
| defconfig.enableTx = true; |
| defconfig.enableRx = true; |
| |
| PW_TRY(clock_tree_element_.Acquire()); |
| flexcomm_clock_freq_ = |
| CLOCK_GetFlexcommClkFreq(FLEXCOMM_GetInstance(config_.usart_base)); |
| status_t status = |
| USART_Init(config_.usart_base, &defconfig, flexcomm_clock_freq_); |
| if (status != kStatus_Success) { |
| clock_tree_element_.Release().IgnoreError(); |
| return Status::Internal(); |
| } |
| |
| { |
| std::lock_guard lock(interrupt_lock_); |
| // Initialized enough for Deinit code to handle any errors from here. |
| initialized_ = true; |
| } |
| |
| status = USART_TransferCreateHandle( |
| config_.usart_base, &uart_handle_, UsartCallback, this); |
| |
| if (status != kStatus_Success) { |
| Deinit(); |
| return Status::Internal(); |
| } |
| |
| { |
| std::lock_guard lock(interrupt_lock_); |
| |
| rx_data_.request.valid = false; |
| tx_data_.request.valid = false; |
| tx_data_.flush_request.valid = false; |
| |
| // Begin reading into the ring buffer. |
| USART_TransferStartRingBuffer( |
| config_.usart_base, |
| &uart_handle_, |
| reinterpret_cast<uint8_t*>(config_.buffer.data()), |
| config_.buffer.size()); |
| } |
| |
| return OkStatus(); |
| } |
| |
| Status UartMcuxpressoNonBlocking::DoEnable(bool enable) { |
| { |
| std::lock_guard lock(interrupt_lock_); |
| if (enable == initialized_) { |
| return OkStatus(); |
| } |
| } |
| |
| if (enable) { |
| return Init(); |
| } else { |
| Deinit(); |
| return OkStatus(); |
| } |
| } |
| |
| // Trigger a TX DMA from the user's buffer. |
| void UartMcuxpressoNonBlocking::TriggerWrite() { |
| PW_DCHECK(tx_data_.request.valid); |
| } |
| |
| Status UartMcuxpressoNonBlocking::DoRead( |
| ByteSpan rx_buffer, |
| size_t min_bytes, |
| Function<void(Status status, ConstByteSpan buffer)>&& callback) { |
| size_t max_bytes = rx_buffer.size(); |
| if (min_bytes == 0 || max_bytes == 0 || min_bytes > max_bytes) { |
| return Status::InvalidArgument(); |
| } |
| |
| // We must grab the interrupt lock before reading the `valid` flag to avoid |
| // racing with `TxRxCompletionCallback()`. |
| { |
| std::lock_guard lock(interrupt_lock_); |
| |
| if (!initialized_) { |
| return Status::FailedPrecondition(); |
| } |
| |
| if (rx_data_.request.valid) { |
| return Status::Unavailable(); |
| } |
| |
| PW_LOG_DEBUG("DoRead: size(%u)", min_bytes); |
| |
| rx_data_.request.valid = true; |
| rx_data_.request.buffer = rx_buffer; |
| rx_data_.request.size = min_bytes; |
| rx_data_.request.callback = std::move(callback); |
| |
| rx_data_.transfer.data = |
| reinterpret_cast<uint8_t*>(rx_data_.request.buffer.data()); |
| rx_data_.transfer.dataSize = rx_data_.request.size; |
| } |
| |
| // Call outside of interrupt lock since it could call our callback right away. |
| // |
| // This should only fail if we try and start a transfer when already started, |
| // which would be a bug in this driver. |
| PW_CHECK(USART_TransferReceiveNonBlocking(config_.usart_base, |
| &uart_handle_, |
| &rx_data_.transfer, |
| nullptr) == kStatus_Success); |
| return OkStatus(); |
| } |
| |
| Status UartMcuxpressoNonBlocking::DoWrite( |
| ConstByteSpan tx_buffer, Function<void(StatusWithSize status)>&& callback) { |
| if (tx_buffer.size() == 0) { |
| return Status::InvalidArgument(); |
| } |
| |
| PW_LOG_DEBUG("DoWrite: size(%u)", tx_buffer.size()); |
| |
| std::lock_guard lock(interrupt_lock_); |
| |
| if (!initialized_) { |
| return Status::FailedPrecondition(); |
| } |
| |
| if (tx_data_.request.valid) { |
| return Status::Unavailable(); |
| } |
| tx_data_.request.valid = true; |
| tx_data_.request.buffer = tx_buffer; |
| tx_data_.request.callback = std::move(callback); |
| |
| tx_data_.transfer.txData = |
| reinterpret_cast<const uint8_t*>(tx_data_.request.buffer.data()); |
| tx_data_.transfer.dataSize = tx_data_.request.buffer.size(); |
| |
| // This should only fail if we try and start a transfer when already started, |
| // which would be a bug in this driver. |
| PW_CHECK(USART_TransferSendNonBlocking( |
| config_.usart_base, &uart_handle_, &tx_data_.transfer) == |
| kStatus_Success); |
| return OkStatus(); |
| } |
| |
| // Static wrapper method called by the completion ISR. |
| void UartMcuxpressoNonBlocking::UsartCallback(USART_Type* base, |
| usart_handle_t* handle, |
| status_t status, |
| void* userdata) { |
| auto* uart = static_cast<UartMcuxpressoNonBlocking*>(userdata); |
| PW_CHECK_PTR_EQ(base, uart->config_.usart_base); |
| PW_CHECK_PTR_EQ(handle, &uart->uart_handle_); |
| |
| return uart->TxRxCompletionCallback(status); |
| } |
| |
| // This callback is called by both the RX and TX interrupt handlers. |
| // It is called upon completion of a transfer. |
| void UartMcuxpressoNonBlocking::TxRxCompletionCallback(status_t status) { |
| std::lock_guard lock(interrupt_lock_); |
| |
| if (status == kStatus_USART_RxIdle && rx_data_.request.valid) { |
| // RX transaction complete |
| // Call the user's completion callback and invalidate the request. |
| rx_data_.request.callback( |
| OkStatus(), rx_data_.request.buffer.first(rx_data_.request.size)); |
| rx_data_.request.valid = false; |
| } |
| |
| if (status == kStatus_USART_TxIdle && tx_data_.request.valid) { |
| // TX transaction complete |
| tx_data_.request.callback(StatusWithSize(tx_data_.request.buffer.size())); |
| tx_data_.request.valid = false; |
| CompleteFlushRequest(OkStatus()); |
| } |
| } |
| |
| bool UartMcuxpressoNonBlocking::DoCancelRead() { |
| std::lock_guard lock(interrupt_lock_); |
| return DoCancelReadLockHeld(); |
| } |
| |
| bool UartMcuxpressoNonBlocking::DoCancelReadLockHeld() { |
| if (!rx_data_.request.valid) { |
| return false; |
| } |
| |
| // Cancel the in-flight transfer. |
| USART_TransferAbortReceive(config_.usart_base, &uart_handle_); |
| |
| rx_data_.request.callback(Status::Cancelled(), {}); |
| rx_data_.request.valid = false; |
| |
| // Ring buffer still running |
| |
| return true; |
| } |
| |
| bool UartMcuxpressoNonBlocking::DoCancelWrite() { |
| std::lock_guard lock(interrupt_lock_); |
| return DoCancelWriteLockHeld(); |
| } |
| |
| bool UartMcuxpressoNonBlocking::DoCancelWriteLockHeld() { |
| if (!tx_data_.request.valid) { |
| return false; |
| } |
| |
| // There is a TX in-flight. |
| // We know this because we are in a critical section and the request is valid. |
| |
| // Cancel the in-flight transfer. |
| USART_TransferAbortSend(config_.usart_base, &uart_handle_); |
| |
| tx_data_.request.callback(StatusWithSize::Cancelled(0)); |
| tx_data_.request.valid = false; |
| |
| CompleteFlushRequest(Status::Aborted()); |
| |
| return true; |
| } |
| |
| size_t UartMcuxpressoNonBlocking::DoConservativeReadAvailable() { |
| return USART_TransferGetRxRingBufferLength(&uart_handle_); |
| } |
| |
| Status UartMcuxpressoNonBlocking::DoClearPendingReceiveBytes() { |
| std::lock_guard lock(interrupt_lock_); |
| return DoClearPendingReceiveBytesLockHeld(); |
| } |
| |
| Status UartMcuxpressoNonBlocking::DoClearPendingReceiveBytesLockHeld() { |
| if (!initialized_) { |
| return OkStatus(); |
| } |
| |
| if (rx_data_.request.valid) { |
| return Status::FailedPrecondition(); |
| } |
| |
| USART_TransferStopRingBuffer(config_.usart_base, &uart_handle_); |
| USART_TransferStartRingBuffer( |
| config_.usart_base, |
| &uart_handle_, |
| reinterpret_cast<uint8_t*>(config_.buffer.data()), |
| config_.buffer.size()); |
| |
| return OkStatus(); |
| } |
| |
| Status UartMcuxpressoNonBlocking::DoSetBaudRate(uint32_t baud_rate) { |
| if (baud_rate == 0) { |
| return Status::InvalidArgument(); |
| } |
| |
| config_.baud_rate = baud_rate; |
| |
| if (!initialized_) { |
| return OkStatus(); |
| } |
| |
| status_t status = USART_SetBaudRate( |
| config_.usart_base, config_.baud_rate, flexcomm_clock_freq_); |
| switch (status) { |
| default: |
| return Status::Unknown(); |
| case kStatus_USART_BaudrateNotSupport: |
| case kStatus_InvalidArgument: |
| return Status::InvalidArgument(); |
| case kStatus_Success: |
| return OkStatus(); |
| } |
| } |
| |
| Status UartMcuxpressoNonBlocking::DoSetFlowControl(bool enable) { |
| config_.flow_control = enable; |
| |
| if (initialized_) { |
| USART_EnableCTS(config_.usart_base, enable); |
| } |
| |
| return OkStatus(); |
| } |
| |
| bool UartMcuxpressoNonBlocking::CompleteFlushRequest(Status status) { |
| if (!tx_data_.flush_request.valid) { |
| return false; |
| } |
| |
| tx_data_.flush_request.callback(status); |
| tx_data_.flush_request.valid = false; |
| tx_data_.flush_request.callback = nullptr; |
| |
| PW_DCHECK(USART_FIFOSTAT_TXEMPTY(config_.usart_base->FIFOSTAT)); |
| |
| return true; |
| } |
| |
| Status UartMcuxpressoNonBlocking::DoFlushOutput( |
| Function<void(Status status)>&& callback) { |
| std::lock_guard lock(interrupt_lock_); |
| |
| if (tx_data_.flush_request.valid) { |
| return Status::FailedPrecondition(); |
| } |
| |
| if (!tx_data_.request.valid) { |
| callback(OkStatus()); |
| return OkStatus(); |
| } |
| |
| tx_data_.flush_request.callback = std::move(callback); |
| tx_data_.flush_request.valid = true; |
| |
| return OkStatus(); |
| } |
| |
| bool UartMcuxpressoNonBlocking::DoCancelFlushOutput() { |
| std::lock_guard lock(interrupt_lock_); |
| return DoCancelFlushOutputLockHeld(); |
| } |
| |
| bool UartMcuxpressoNonBlocking::DoCancelFlushOutputLockHeld() { |
| return CompleteFlushRequest(Status::Cancelled()); |
| } |
| |
| } // namespace pw::uart |