zephyr/drivers/serial/uart_mcux_lpuart.c
Declan Snyder 9320907fdf drivers: uart_mcux_lpuart: Fix APIs used together
Handle controller instances wanting to use different
API instances, need to change init flow so that they dont
interfere with each other, trick for now is returning
either the interrupt driven or async apis as not enabled
if the other has already been used on that controller.

Previously, there was an issue where enabling CONFIG_UART_ASYNC_API
would overwrite the init even if controller was meant to use
interrupt driven (as kconfig is global to affect all the controllers)

Signed-off-by: Declan Snyder <declan.snyder@nxp.com>
2024-04-30 08:53:02 +02:00

1358 lines
39 KiB
C

/*
* Copyright 2017,2021,2023-2024 NXP
* Copyright (c) 2020 Softube
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT nxp_kinetis_lpuart
#include <errno.h>
#include <zephyr/device.h>
#include <zephyr/drivers/uart.h>
#include <zephyr/drivers/clock_control.h>
#include <zephyr/irq.h>
#include <zephyr/kernel.h>
#include <zephyr/pm/policy.h>
#include <zephyr/drivers/pinctrl.h>
#ifdef CONFIG_UART_ASYNC_API
#include <zephyr/drivers/dma.h>
#endif
#include <zephyr/logging/log.h>
#include <fsl_lpuart.h>
#if CONFIG_NXP_LP_FLEXCOMM
#include <zephyr/drivers/mfd/nxp_lp_flexcomm.h>
#endif
LOG_MODULE_REGISTER(uart_mcux_lpuart, LOG_LEVEL_ERR);
#define PINCTRL_STATE_FLOWCONTROL PINCTRL_STATE_PRIV_START
#if defined(CONFIG_UART_ASYNC_API) && defined(CONFIG_UART_INTERRUPT_DRIVEN)
/* there are already going to be build errors, but at least this message will
* be the first error from this driver making the reason clear
*/
BUILD_ASSERT(IS_ENABLED(CONFIG_UART_EXCLUSIVE_API_CALLBACKS), ""
"LPUART must use exclusive api callbacks");
#endif
#ifdef CONFIG_UART_ASYNC_API
struct lpuart_dma_config {
const struct device *dma_dev;
const uint32_t dma_channel;
struct dma_config dma_cfg;
};
#endif /* CONFIG_UART_ASYNC_API */
struct mcux_lpuart_config {
LPUART_Type *base;
#ifdef CONFIG_NXP_LP_FLEXCOMM
const struct device *parent_dev;
#endif
const struct device *clock_dev;
const struct pinctrl_dev_config *pincfg;
clock_control_subsys_t clock_subsys;
uint32_t baud_rate;
uint8_t flow_ctrl;
uint8_t parity;
bool rs485_de_active_low;
bool loopback_en;
#ifdef CONFIG_UART_MCUX_LPUART_ISR_SUPPORT
void (*irq_config_func)(const struct device *dev);
#endif
#ifdef CONFIG_UART_ASYNC_API
const struct lpuart_dma_config rx_dma_config;
const struct lpuart_dma_config tx_dma_config;
#endif /* CONFIG_UART_ASYNC_API */
};
#ifdef CONFIG_UART_ASYNC_API
struct mcux_lpuart_rx_dma_params {
struct dma_block_config active_dma_block;
uint8_t *buf;
size_t buf_len;
size_t offset;
size_t counter;
struct k_work_delayable timeout_work;
size_t timeout_us;
};
struct mcux_lpuart_tx_dma_params {
struct dma_block_config active_dma_block;
const uint8_t *buf;
size_t buf_len;
struct k_work_delayable timeout_work;
size_t timeout_us;
};
struct mcux_lpuart_async_data {
const struct device *uart_dev;
struct mcux_lpuart_tx_dma_params tx_dma_params;
struct mcux_lpuart_rx_dma_params rx_dma_params;
uint8_t *next_rx_buffer;
size_t next_rx_buffer_len;
uart_callback_t user_callback;
void *user_data;
};
#endif
#if defined(CONFIG_UART_EXCLUSIVE_API_CALLBACKS)
enum mcux_lpuart_api {
LPUART_NONE,
LPUART_IRQ_DRIVEN,
LPUART_ASYNC
};
#endif
struct mcux_lpuart_data {
#ifdef CONFIG_UART_INTERRUPT_DRIVEN
uart_irq_callback_user_data_t callback;
void *cb_data;
#endif
#ifdef CONFIG_PM
bool pm_state_lock_on;
bool tx_poll_stream_on;
bool tx_int_stream_on;
#endif /* CONFIG_PM */
#ifdef CONFIG_UART_ASYNC_API
struct mcux_lpuart_async_data async;
#endif
struct uart_config uart_config;
#if defined(CONFIG_UART_EXCLUSIVE_API_CALLBACKS)
enum mcux_lpuart_api api_type;
#endif
};
#ifdef CONFIG_PM
static void mcux_lpuart_pm_policy_state_lock_get(const struct device *dev)
{
struct mcux_lpuart_data *data = dev->data;
if (!data->pm_state_lock_on) {
data->pm_state_lock_on = true;
pm_policy_state_lock_get(PM_STATE_SUSPEND_TO_IDLE, PM_ALL_SUBSTATES);
}
}
static void mcux_lpuart_pm_policy_state_lock_put(const struct device *dev)
{
struct mcux_lpuart_data *data = dev->data;
if (data->pm_state_lock_on) {
data->pm_state_lock_on = false;
pm_policy_state_lock_put(PM_STATE_SUSPEND_TO_IDLE, PM_ALL_SUBSTATES);
}
}
#endif /* CONFIG_PM */
static int mcux_lpuart_poll_in(const struct device *dev, unsigned char *c)
{
const struct mcux_lpuart_config *config = dev->config;
uint32_t flags = LPUART_GetStatusFlags(config->base);
int ret = -1;
if (flags & kLPUART_RxDataRegFullFlag) {
*c = LPUART_ReadByte(config->base);
ret = 0;
}
return ret;
}
static void mcux_lpuart_poll_out(const struct device *dev, unsigned char c)
{
const struct mcux_lpuart_config *config = dev->config;
unsigned int key;
#ifdef CONFIG_PM
struct mcux_lpuart_data *data = dev->data;
#endif
while (!(LPUART_GetStatusFlags(config->base)
& LPUART_STAT_TDRE_MASK)) {
}
/* Lock interrupts while we send data */
key = irq_lock();
#ifdef CONFIG_PM
/*
* We must keep the part from entering lower power mode until the
* transmission completes. Set the power constraint, and enable
* the transmission complete interrupt so we know when transmission is
* completed.
*/
if (!data->tx_poll_stream_on && !data->tx_int_stream_on) {
data->tx_poll_stream_on = true;
mcux_lpuart_pm_policy_state_lock_get(dev);
/* Enable TC interrupt */
LPUART_EnableInterrupts(config->base,
kLPUART_TransmissionCompleteInterruptEnable);
}
#endif /* CONFIG_PM */
LPUART_WriteByte(config->base, c);
irq_unlock(key);
}
static int mcux_lpuart_err_check(const struct device *dev)
{
const struct mcux_lpuart_config *config = dev->config;
uint32_t flags = LPUART_GetStatusFlags(config->base);
int err = 0;
if (flags & kLPUART_RxOverrunFlag) {
err |= UART_ERROR_OVERRUN;
}
if (flags & kLPUART_ParityErrorFlag) {
err |= UART_ERROR_PARITY;
}
if (flags & kLPUART_FramingErrorFlag) {
err |= UART_ERROR_FRAMING;
}
if (flags & kLPUART_NoiseErrorFlag) {
err |= UART_ERROR_PARITY;
}
LPUART_ClearStatusFlags(config->base, kLPUART_RxOverrunFlag |
kLPUART_ParityErrorFlag |
kLPUART_FramingErrorFlag |
kLPUART_NoiseErrorFlag);
return err;
}
#ifdef CONFIG_UART_INTERRUPT_DRIVEN
static int mcux_lpuart_fifo_fill(const struct device *dev,
const uint8_t *tx_data,
int len)
{
const struct mcux_lpuart_config *config = dev->config;
uint8_t num_tx = 0U;
while ((len - num_tx > 0) &&
(LPUART_GetStatusFlags(config->base)
& LPUART_STAT_TDRE_MASK)) {
LPUART_WriteByte(config->base, tx_data[num_tx++]);
}
return num_tx;
}
static int mcux_lpuart_fifo_read(const struct device *dev, uint8_t *rx_data,
const int len)
{
const struct mcux_lpuart_config *config = dev->config;
uint8_t num_rx = 0U;
while ((len - num_rx > 0) &&
(LPUART_GetStatusFlags(config->base)
& kLPUART_RxDataRegFullFlag)) {
rx_data[num_rx++] = LPUART_ReadByte(config->base);
}
return num_rx;
}
static void mcux_lpuart_irq_tx_enable(const struct device *dev)
{
const struct mcux_lpuart_config *config = dev->config;
uint32_t mask = kLPUART_TxDataRegEmptyInterruptEnable;
#ifdef CONFIG_PM
struct mcux_lpuart_data *data = dev->data;
unsigned int key;
#endif
#ifdef CONFIG_PM
key = irq_lock();
data->tx_poll_stream_on = false;
data->tx_int_stream_on = true;
/* Transmission complete interrupt no longer required */
LPUART_DisableInterrupts(config->base,
kLPUART_TransmissionCompleteInterruptEnable);
/* Do not allow system to sleep while UART tx is ongoing */
mcux_lpuart_pm_policy_state_lock_get(dev);
#endif
LPUART_EnableInterrupts(config->base, mask);
#ifdef CONFIG_PM
irq_unlock(key);
#endif
}
static void mcux_lpuart_irq_tx_disable(const struct device *dev)
{
const struct mcux_lpuart_config *config = dev->config;
uint32_t mask = kLPUART_TxDataRegEmptyInterruptEnable;
#ifdef CONFIG_PM
struct mcux_lpuart_data *data = dev->data;
unsigned int key;
key = irq_lock();
#endif
LPUART_DisableInterrupts(config->base, mask);
#ifdef CONFIG_PM
data->tx_int_stream_on = false;
/*
* If transmission IRQ is no longer enabled,
* transmission is complete. Release pm constraint.
*/
mcux_lpuart_pm_policy_state_lock_put(dev);
irq_unlock(key);
#endif
}
static int mcux_lpuart_irq_tx_complete(const struct device *dev)
{
const struct mcux_lpuart_config *config = dev->config;
uint32_t flags = LPUART_GetStatusFlags(config->base);
return (flags & kLPUART_TransmissionCompleteFlag) != 0U;
}
static int mcux_lpuart_irq_tx_ready(const struct device *dev)
{
const struct mcux_lpuart_config *config = dev->config;
uint32_t mask = kLPUART_TxDataRegEmptyInterruptEnable;
uint32_t flags = LPUART_GetStatusFlags(config->base);
return (LPUART_GetEnabledInterrupts(config->base) & mask)
&& (flags & LPUART_STAT_TDRE_MASK);
}
static void mcux_lpuart_irq_rx_enable(const struct device *dev)
{
const struct mcux_lpuart_config *config = dev->config;
uint32_t mask = kLPUART_RxDataRegFullInterruptEnable;
LPUART_EnableInterrupts(config->base, mask);
}
static void mcux_lpuart_irq_rx_disable(const struct device *dev)
{
const struct mcux_lpuart_config *config = dev->config;
uint32_t mask = kLPUART_RxDataRegFullInterruptEnable;
LPUART_DisableInterrupts(config->base, mask);
}
static int mcux_lpuart_irq_rx_full(const struct device *dev)
{
const struct mcux_lpuart_config *config = dev->config;
uint32_t flags = LPUART_GetStatusFlags(config->base);
return (flags & kLPUART_RxDataRegFullFlag) != 0U;
}
static int mcux_lpuart_irq_rx_pending(const struct device *dev)
{
const struct mcux_lpuart_config *config = dev->config;
uint32_t mask = kLPUART_RxDataRegFullInterruptEnable;
return (LPUART_GetEnabledInterrupts(config->base) & mask)
&& mcux_lpuart_irq_rx_full(dev);
}
static void mcux_lpuart_irq_err_enable(const struct device *dev)
{
const struct mcux_lpuart_config *config = dev->config;
uint32_t mask = kLPUART_NoiseErrorInterruptEnable |
kLPUART_FramingErrorInterruptEnable |
kLPUART_ParityErrorInterruptEnable;
LPUART_EnableInterrupts(config->base, mask);
}
static void mcux_lpuart_irq_err_disable(const struct device *dev)
{
const struct mcux_lpuart_config *config = dev->config;
uint32_t mask = kLPUART_NoiseErrorInterruptEnable |
kLPUART_FramingErrorInterruptEnable |
kLPUART_ParityErrorInterruptEnable;
LPUART_DisableInterrupts(config->base, mask);
}
static int mcux_lpuart_irq_is_pending(const struct device *dev)
{
return (mcux_lpuart_irq_tx_ready(dev)
|| mcux_lpuart_irq_rx_pending(dev));
}
static int mcux_lpuart_irq_update(const struct device *dev)
{
return 1;
}
static void mcux_lpuart_irq_callback_set(const struct device *dev,
uart_irq_callback_user_data_t cb,
void *cb_data)
{
struct mcux_lpuart_data *data = dev->data;
#if defined(CONFIG_UART_EXCLUSIVE_API_CALLBACKS)
if (data->api_type == LPUART_ASYNC) {
LOG_ERR("UART irq and async api are exclusive");
}
#endif
data->callback = cb;
data->cb_data = cb_data;
#if defined(CONFIG_UART_EXCLUSIVE_API_CALLBACKS)
data->async.user_callback = NULL;
data->async.user_data = NULL;
data->api_type = LPUART_IRQ_DRIVEN;
#endif
}
#endif /* CONFIG_UART_INTERRUPT_DRIVEN */
#ifdef CONFIG_UART_ASYNC_API
static inline void async_timer_start(struct k_work_delayable *work, size_t timeout_us)
{
if ((timeout_us != SYS_FOREVER_US) && (timeout_us != 0)) {
LOG_DBG("async timer started for %d us", timeout_us);
k_work_reschedule(work, K_USEC(timeout_us));
}
}
static void async_user_callback(const struct device *dev, struct uart_event *evt)
{
const struct mcux_lpuart_data *data = dev->data;
if (data->async.user_callback) {
data->async.user_callback(dev, evt, data->async.user_data);
}
}
static void async_evt_tx_done(struct device *dev)
{
struct mcux_lpuart_data *data = dev->data;
(void)k_work_cancel_delayable(&data->async.tx_dma_params.timeout_work);
LOG_DBG("TX done: %d", data->async.tx_dma_params.buf_len);
struct uart_event event = {
.type = UART_TX_DONE,
.data.tx.buf = data->async.tx_dma_params.buf,
.data.tx.len = data->async.tx_dma_params.buf_len
};
/* Reset TX Buffer */
data->async.tx_dma_params.buf = NULL;
data->async.tx_dma_params.buf_len = 0U;
async_user_callback(dev, &event);
}
static void async_evt_rx_rdy(const struct device *dev)
{
struct mcux_lpuart_data *data = dev->data;
struct mcux_lpuart_rx_dma_params *dma_params = &data->async.rx_dma_params;
struct uart_event event = {
.type = UART_RX_RDY,
.data.rx.buf = dma_params->buf,
.data.rx.len = dma_params->counter - dma_params->offset,
.data.rx.offset = dma_params->offset
};
LOG_DBG("RX Ready: (len: %d off: %d buf: %x)", event.data.rx.len, event.data.rx.offset,
(uint32_t)event.data.rx.buf);
/* Update the current pos for new data */
dma_params->offset = dma_params->counter;
/* Only send event for new data */
if (event.data.rx.len > 0) {
async_user_callback(dev, &event);
}
}
static void async_evt_rx_buf_request(const struct device *dev)
{
struct uart_event evt = {
.type = UART_RX_BUF_REQUEST,
};
async_user_callback(dev, &evt);
}
static void async_evt_rx_buf_release(const struct device *dev)
{
struct mcux_lpuart_data *data = (struct mcux_lpuart_data *)dev->data;
struct uart_event evt = {
.type = UART_RX_BUF_RELEASED,
.data.rx_buf.buf = data->async.rx_dma_params.buf,
};
async_user_callback(dev, &evt);
data->async.rx_dma_params.buf = NULL;
data->async.rx_dma_params.buf_len = 0U;
data->async.rx_dma_params.offset = 0U;
data->async.rx_dma_params.counter = 0U;
}
static void mcux_lpuart_async_rx_flush(const struct device *dev)
{
struct dma_status status;
struct mcux_lpuart_data *data = dev->data;
const struct mcux_lpuart_config *config = dev->config;
const int get_status_result = dma_get_status(config->rx_dma_config.dma_dev,
config->rx_dma_config.dma_channel,
&status);
if (get_status_result == 0) {
const size_t rx_rcv_len = data->async.rx_dma_params.buf_len -
status.pending_length;
if (rx_rcv_len > data->async.rx_dma_params.counter) {
data->async.rx_dma_params.counter = rx_rcv_len;
async_evt_rx_rdy(dev);
}
} else {
LOG_ERR("Error getting DMA status");
}
}
static int mcux_lpuart_rx_disable(const struct device *dev)
{
LOG_INF("Disabling UART RX DMA");
const struct mcux_lpuart_config *config = dev->config;
struct mcux_lpuart_data *data = (struct mcux_lpuart_data *)dev->data;
LPUART_Type *lpuart = config->base;
const unsigned int key = irq_lock();
LPUART_EnableRx(lpuart, false);
(void)k_work_cancel_delayable(&data->async.rx_dma_params.timeout_work);
LPUART_DisableInterrupts(lpuart, kLPUART_IdleLineInterruptEnable);
LPUART_ClearStatusFlags(lpuart, kLPUART_IdleLineFlag);
LPUART_EnableRxDMA(lpuart, false);
/* No active RX buffer, cannot disable */
if (!data->async.rx_dma_params.buf) {
LOG_ERR("No buffers to release from RX DMA!");
} else {
mcux_lpuart_async_rx_flush(dev);
async_evt_rx_buf_release(dev);
if (data->async.next_rx_buffer != NULL) {
data->async.rx_dma_params.buf = data->async.next_rx_buffer;
data->async.rx_dma_params.buf_len = data->async.next_rx_buffer_len;
data->async.next_rx_buffer = NULL;
data->async.next_rx_buffer_len = 0;
/* Release the next buffer as well */
async_evt_rx_buf_release(dev);
}
}
const int ret = dma_stop(config->rx_dma_config.dma_dev,
config->rx_dma_config.dma_channel);
if (ret != 0) {
LOG_ERR("Error stopping rx DMA. Reason: %x", ret);
}
LOG_DBG("RX: Disabled");
struct uart_event disabled_event = {
.type = UART_RX_DISABLED
};
async_user_callback(dev, &disabled_event);
irq_unlock(key);
return ret;
}
static void prepare_rx_dma_block_config(const struct device *dev)
{
struct mcux_lpuart_data *data = (struct mcux_lpuart_data *)dev->data;
const struct mcux_lpuart_config *config = dev->config;
LPUART_Type *lpuart = config->base;
struct mcux_lpuart_rx_dma_params *rx_dma_params = &data->async.rx_dma_params;
assert(rx_dma_params->buf != NULL);
assert(rx_dma_params->buf_len > 0);
struct dma_block_config *head_block_config = &rx_dma_params->active_dma_block;
head_block_config->dest_address = (uint32_t)rx_dma_params->buf;
head_block_config->source_address = LPUART_GetDataRegisterAddress(lpuart);
head_block_config->block_size = rx_dma_params->buf_len;
head_block_config->dest_scatter_en = true;
}
static int configure_and_start_rx_dma(
const struct mcux_lpuart_config *config, struct mcux_lpuart_data *data,
LPUART_Type *lpuart)
{
LOG_DBG("Configuring and Starting UART RX DMA");
int ret = dma_config(config->rx_dma_config.dma_dev,
config->rx_dma_config.dma_channel,
(struct dma_config *)&config->rx_dma_config.dma_cfg);
if (ret != 0) {
LOG_ERR("Failed to Configure RX DMA: err: %d", ret);
return ret;
}
ret = dma_start(config->rx_dma_config.dma_dev, config->rx_dma_config.dma_channel);
if (ret < 0) {
LOG_ERR("Failed to start DMA(Rx) Ch %d(%d)",
config->rx_dma_config.dma_channel,
ret);
}
LPUART_EnableRxDMA(lpuart, true);
return ret;
}
static int uart_mcux_lpuart_dma_replace_rx_buffer(const struct device *dev)
{
struct mcux_lpuart_data *data = (struct mcux_lpuart_data *)dev->data;
const struct mcux_lpuart_config *config = dev->config;
LPUART_Type *lpuart = config->base;
LOG_DBG("Replacing RX buffer, new length: %d", data->async.next_rx_buffer_len);
/* There must be a buffer to replace this one with */
assert(data->async.next_rx_buffer != NULL);
assert(data->async.next_rx_buffer_len != 0U);
const int success = dma_reload(config->rx_dma_config.dma_dev,
config->rx_dma_config.dma_channel,
LPUART_GetDataRegisterAddress(lpuart),
(uint32_t)data->async.next_rx_buffer,
data->async.next_rx_buffer_len);
if (success != 0) {
LOG_ERR("Error %d reloading DMA with next RX buffer", success);
}
return success;
}
static void dma_callback(const struct device *dma_dev, void *callback_arg, uint32_t channel,
int dma_status)
{
struct device *dev = (struct device *)callback_arg;
const struct mcux_lpuart_config *config = dev->config;
LPUART_Type *lpuart = config->base;
struct mcux_lpuart_data *data = (struct mcux_lpuart_data *)dev->data;
LOG_DBG("DMA call back on channel %d", channel);
struct dma_status status;
const int get_status_result = dma_get_status(dma_dev, channel, &status);
if (get_status_result < 0) {
LOG_ERR("error on status get: %d", get_status_result);
} else {
LOG_DBG("DMA Status: b: %d dir: %d len_remain: %d", status.busy, status.dir,
status.pending_length);
}
if (dma_status < 0) {
LOG_ERR("Got error : %d", dma_status);
}
if (channel == config->tx_dma_config.dma_channel) {
LOG_DBG("TX Channel");
LPUART_EnableTxDMA(lpuart, false);
async_evt_tx_done(dev);
} else if (channel == config->rx_dma_config.dma_channel) {
LOG_DBG("RX Channel");
struct mcux_lpuart_rx_dma_params *rx_dma_params = &data->async.rx_dma_params;
/* The RX Event indicates DMA transfer is complete and full buffer is available. */
rx_dma_params->counter = rx_dma_params->buf_len;
LOG_DBG("Current Buf (%x) full, swapping to new buf: %x",
(uint32_t)rx_dma_params->buf,
(uint32_t)data->async.next_rx_buffer);
async_evt_rx_rdy(dev);
async_evt_rx_buf_release(dev);
rx_dma_params->buf = data->async.next_rx_buffer;
rx_dma_params->buf_len = data->async.next_rx_buffer_len;
data->async.next_rx_buffer = NULL;
data->async.next_rx_buffer_len = 0U;
/* A new buffer was available (and already loaded into the DMA engine) */
if (rx_dma_params->buf != NULL &&
rx_dma_params->buf_len > 0) {
/* Request the next buffer */
async_evt_rx_buf_request(dev);
} else {
/* Buffer full without valid next buffer, disable RX DMA */
LOG_INF("Disabled RX DMA, no valid next buffer ");
mcux_lpuart_rx_disable(dev);
}
} else {
LOG_ERR("Got unexpected DMA Channel: %d", channel);
}
}
static int mcux_lpuart_configure_async(const struct device *dev);
static int mcux_lpuart_callback_set(const struct device *dev, uart_callback_t callback,
void *user_data)
{
struct mcux_lpuart_data *data = dev->data;
#if defined(CONFIG_UART_EXCLUSIVE_API_CALLBACKS)
if (data->api_type == LPUART_IRQ_DRIVEN) {
LOG_ERR("UART irq and async api are exclusive");
return -ENOTSUP;
}
#endif
data->async.user_callback = callback;
data->async.user_data = user_data;
#if defined(CONFIG_UART_EXCLUSIVE_API_CALLBACKS)
data->callback = NULL;
data->cb_data = NULL;
data->api_type = LPUART_ASYNC;
return mcux_lpuart_configure_async(dev);
#else
return 0;
#endif
}
static int mcux_lpuart_tx(const struct device *dev, const uint8_t *buf, size_t len,
int32_t timeout_us)
{
struct mcux_lpuart_data *data = dev->data;
const struct mcux_lpuart_config *config = dev->config;
LPUART_Type *lpuart = config->base;
unsigned int key = irq_lock();
/* Check for an ongiong transfer and abort if it is pending */
struct dma_status status;
const int get_status_result = dma_get_status(config->tx_dma_config.dma_dev,
config->tx_dma_config.dma_channel,
&status);
if (get_status_result < 0 || status.busy) {
irq_unlock(key);
LOG_ERR("Unable to submit UART DMA Transfer.");
return get_status_result < 0 ? get_status_result : -EBUSY;
}
int ret;
LPUART_EnableTxDMA(lpuart, false);
data->async.tx_dma_params.buf = buf;
data->async.tx_dma_params.buf_len = len;
data->async.tx_dma_params.active_dma_block.source_address = (uint32_t)buf;
data->async.tx_dma_params.active_dma_block.dest_address =
LPUART_GetDataRegisterAddress(lpuart);
data->async.tx_dma_params.active_dma_block.block_size = len;
data->async.tx_dma_params.active_dma_block.next_block = NULL;
ret = dma_config(config->tx_dma_config.dma_dev,
config->tx_dma_config.dma_channel,
(struct dma_config *)&config->tx_dma_config.dma_cfg);
if (ret == 0) {
LOG_DBG("Starting UART DMA TX Ch %u", config->tx_dma_config.dma_channel);
ret = dma_start(config->tx_dma_config.dma_dev,
config->tx_dma_config.dma_channel);
LPUART_EnableTxDMA(lpuart, true);
if (ret != 0) {
LOG_ERR("Failed to start DMA(Tx) Ch %d",
config->tx_dma_config.dma_channel);
}
async_timer_start(&data->async.tx_dma_params.timeout_work, timeout_us);
} else {
LOG_ERR("Error configuring UART DMA: %x", ret);
}
irq_unlock(key);
return ret;
}
static int mcux_lpuart_tx_abort(const struct device *dev)
{
struct mcux_lpuart_data *data = dev->data;
const struct mcux_lpuart_config *config = dev->config;
LPUART_Type *lpuart = config->base;
LPUART_EnableTxDMA(lpuart, false);
(void)k_work_cancel_delayable(&data->async.tx_dma_params.timeout_work);
struct dma_status status;
const int get_status_result = dma_get_status(config->tx_dma_config.dma_dev,
config->tx_dma_config.dma_channel,
&status);
if (get_status_result < 0) {
LOG_ERR("Error querying TX DMA Status during abort.");
}
const size_t bytes_transmitted = (get_status_result == 0) ?
data->async.tx_dma_params.buf_len - status.pending_length : 0;
const int ret = dma_stop(config->tx_dma_config.dma_dev, config->tx_dma_config.dma_channel);
if (ret == 0) {
struct uart_event tx_aborted_event = {
.type = UART_TX_ABORTED,
.data.tx.buf = data->async.tx_dma_params.buf,
.data.tx.len = bytes_transmitted
};
async_user_callback(dev, &tx_aborted_event);
}
return ret;
}
static int mcux_lpuart_rx_enable(const struct device *dev, uint8_t *buf, const size_t len,
const int32_t timeout_us)
{
LOG_DBG("Enabling UART RX DMA");
struct mcux_lpuart_data *data = dev->data;
const struct mcux_lpuart_config *config = dev->config;
LPUART_Type *lpuart = config->base;
struct mcux_lpuart_rx_dma_params *rx_dma_params = &data->async.rx_dma_params;
unsigned int key = irq_lock();
struct dma_status status;
const int get_status_result = dma_get_status(config->rx_dma_config.dma_dev,
config->rx_dma_config.dma_channel,
&status);
if (get_status_result < 0 || status.busy) {
LOG_ERR("Unable to start receive on UART.");
irq_unlock(key);
return get_status_result < 0 ? get_status_result : -EBUSY;
}
rx_dma_params->timeout_us = timeout_us;
rx_dma_params->buf = buf;
rx_dma_params->buf_len = len;
LPUART_EnableInterrupts(config->base, kLPUART_IdleLineInterruptEnable);
prepare_rx_dma_block_config(dev);
const int ret = configure_and_start_rx_dma(config, data, lpuart);
/* Request the next buffer for when this buffer is full for continuous reception */
async_evt_rx_buf_request(dev);
/* Clear these status flags as they can prevent the UART device from receiving data */
LPUART_ClearStatusFlags(config->base, kLPUART_RxOverrunFlag |
kLPUART_ParityErrorFlag |
kLPUART_FramingErrorFlag |
kLPUART_NoiseErrorFlag);
LPUART_EnableRx(lpuart, true);
irq_unlock(key);
return ret;
}
static int mcux_lpuart_rx_buf_rsp(const struct device *dev, uint8_t *buf, size_t len)
{
struct mcux_lpuart_data *data = dev->data;
assert(data->async.next_rx_buffer == NULL);
assert(data->async.next_rx_buffer_len == 0);
data->async.next_rx_buffer = buf;
data->async.next_rx_buffer_len = len;
uart_mcux_lpuart_dma_replace_rx_buffer(dev);
return 0;
}
static void mcux_lpuart_async_rx_timeout(struct k_work *work)
{
struct k_work_delayable *dwork = k_work_delayable_from_work(work);
struct mcux_lpuart_rx_dma_params *rx_params = CONTAINER_OF(dwork,
struct mcux_lpuart_rx_dma_params,
timeout_work);
struct mcux_lpuart_async_data *async_data = CONTAINER_OF(rx_params,
struct mcux_lpuart_async_data,
rx_dma_params);
const struct device *dev = async_data->uart_dev;
LOG_DBG("RX timeout");
mcux_lpuart_async_rx_flush(dev);
}
static void mcux_lpuart_async_tx_timeout(struct k_work *work)
{
struct k_work_delayable *dwork = k_work_delayable_from_work(work);
struct mcux_lpuart_tx_dma_params *tx_params = CONTAINER_OF(dwork,
struct mcux_lpuart_tx_dma_params,
timeout_work);
struct mcux_lpuart_async_data *async_data = CONTAINER_OF(tx_params,
struct mcux_lpuart_async_data,
tx_dma_params);
const struct device *dev = async_data->uart_dev;
LOG_DBG("TX timeout");
(void)mcux_lpuart_tx_abort(dev);
}
#endif /* CONFIG_UART_ASYNC_API */
#if CONFIG_UART_MCUX_LPUART_ISR_SUPPORT
#ifdef CONFIG_UART_INTERRUPT_DRIVEN
static inline void mcux_lpuart_irq_driven_isr(const struct device *dev,
struct mcux_lpuart_data *data,
const struct mcux_lpuart_config *config,
const uint32_t status) {
if (data->callback) {
data->callback(dev, data->cb_data);
}
if (status & kLPUART_RxOverrunFlag) {
LPUART_ClearStatusFlags(config->base, kLPUART_RxOverrunFlag);
}
}
#endif
#ifdef CONFIG_UART_ASYNC_API
static inline void mcux_lpuart_async_isr(struct mcux_lpuart_data *data,
const struct mcux_lpuart_config *config,
const uint32_t status) {
if (status & kLPUART_IdleLineFlag) {
async_timer_start(&data->async.rx_dma_params.timeout_work,
data->async.rx_dma_params.timeout_us);
LPUART_ClearStatusFlags(config->base, kLPUART_IdleLineFlag);
}
}
#endif
static void mcux_lpuart_isr(const struct device *dev)
{
struct mcux_lpuart_data *data = dev->data;
const struct mcux_lpuart_config *config = dev->config;
const uint32_t status = LPUART_GetStatusFlags(config->base);
#if CONFIG_PM
if (status & kLPUART_TransmissionCompleteFlag) {
if (data->tx_poll_stream_on) {
/* Poll transmission complete. Allow system to sleep */
LPUART_DisableInterrupts(config->base,
kLPUART_TransmissionCompleteInterruptEnable);
data->tx_poll_stream_on = false;
mcux_lpuart_pm_policy_state_lock_put(dev);
}
}
#endif /* CONFIG_PM */
#if defined(CONFIG_UART_ASYNC_API) && defined(CONFIG_UART_INTERRUPT_DRIVEN)
if (data->api_type == LPUART_IRQ_DRIVEN) {
mcux_lpuart_irq_driven_isr(dev, data, config, status);
} else if (data->api_type == LPUART_ASYNC) {
mcux_lpuart_async_isr(data, config, status);
}
#elif defined(CONFIG_UART_INTERRUPT_DRIVEN)
mcux_lpuart_irq_driven_isr(dev, data, config, status);
#elif defined(CONFIG_UART_ASYNC_API)
mcux_lpuart_async_isr(data, config, status);
#endif /* API */
}
#endif /* CONFIG_UART_MCUX_LPUART_ISR_SUPPORT */
static int mcux_lpuart_configure_basic(const struct device *dev, const struct uart_config *cfg,
lpuart_config_t *uart_config)
{
/* Translate UART API enum to LPUART enum from HAL */
switch (cfg->parity) {
case UART_CFG_PARITY_NONE:
uart_config->parityMode = kLPUART_ParityDisabled;
break;
case UART_CFG_PARITY_ODD:
uart_config->parityMode = kLPUART_ParityOdd;
break;
case UART_CFG_PARITY_EVEN:
uart_config->parityMode = kLPUART_ParityEven;
break;
default:
return -ENOTSUP;
}
switch (cfg->data_bits) {
#if defined(FSL_FEATURE_LPUART_HAS_7BIT_DATA_SUPPORT) && \
FSL_FEATURE_LPUART_HAS_7BIT_DATA_SUPPORT
case UART_CFG_DATA_BITS_7:
uart_config->dataBitsCount = kLPUART_SevenDataBits;
break;
#endif
case UART_CFG_DATA_BITS_8:
uart_config->dataBitsCount = kLPUART_EightDataBits;
break;
default:
return -ENOTSUP;
}
#if defined(FSL_FEATURE_LPUART_HAS_STOP_BIT_CONFIG_SUPPORT) && \
FSL_FEATURE_LPUART_HAS_STOP_BIT_CONFIG_SUPPORT
switch (cfg->stop_bits) {
case UART_CFG_STOP_BITS_1:
uart_config->stopBitCount = kLPUART_OneStopBit;
break;
case UART_CFG_STOP_BITS_2:
uart_config->stopBitCount = kLPUART_TwoStopBit;
break;
default:
return -ENOTSUP;
}
#endif
#if defined(FSL_FEATURE_LPUART_HAS_MODEM_SUPPORT) && \
FSL_FEATURE_LPUART_HAS_MODEM_SUPPORT
switch (cfg->flow_ctrl) {
case UART_CFG_FLOW_CTRL_NONE:
case UART_CFG_FLOW_CTRL_RS485:
uart_config->enableTxCTS = false;
uart_config->enableRxRTS = false;
break;
case UART_CFG_FLOW_CTRL_RTS_CTS:
uart_config->enableTxCTS = true;
uart_config->enableRxRTS = true;
break;
default:
return -ENOTSUP;
}
#endif
uart_config->baudRate_Bps = cfg->baudrate;
uart_config->enableRx = true;
/* Tx will be enabled manually after set tx-rts */
uart_config->enableTx = false;
return 0;
}
#ifdef CONFIG_UART_ASYNC_API
static int mcux_lpuart_configure_async(const struct device *dev)
{
const struct mcux_lpuart_config *config = dev->config;
struct mcux_lpuart_data *data = dev->data;
lpuart_config_t uart_config;
int ret;
LPUART_GetDefaultConfig(&uart_config);
ret = mcux_lpuart_configure_basic(dev, &data->uart_config, &uart_config);
if (ret) {
return ret;
}
uart_config.rxIdleType = kLPUART_IdleTypeStopBit;
uart_config.rxIdleConfig = kLPUART_IdleCharacter1;
data->async.next_rx_buffer = NULL;
data->async.next_rx_buffer_len = 0;
data->async.uart_dev = dev;
k_work_init_delayable(&data->async.rx_dma_params.timeout_work,
mcux_lpuart_async_rx_timeout);
k_work_init_delayable(&data->async.tx_dma_params.timeout_work,
mcux_lpuart_async_tx_timeout);
/* Disable the UART Receiver until the async API provides a buffer to
* to receive into with rx_enable
*/
uart_config.enableRx = false;
/* Clearing the fifo of any junk received before the async rx enable was called */
while (LPUART_GetRxFifoCount(config->base) > 0) {
LPUART_ReadByte(config->base);
}
return 0;
}
#endif
static int mcux_lpuart_configure_init(const struct device *dev, const struct uart_config *cfg)
{
const struct mcux_lpuart_config *config = dev->config;
struct mcux_lpuart_data *data = dev->data;
lpuart_config_t uart_config;
uint32_t clock_freq;
int ret;
if (!device_is_ready(config->clock_dev)) {
return -ENODEV;
}
if (clock_control_get_rate(config->clock_dev, config->clock_subsys,
&clock_freq)) {
return -EINVAL;
}
LPUART_GetDefaultConfig(&uart_config);
ret = mcux_lpuart_configure_basic(dev, cfg, &uart_config);
if (ret) {
return ret;
}
LPUART_Init(config->base, &uart_config, clock_freq);
if (cfg->flow_ctrl == UART_CFG_FLOW_CTRL_RS485) {
/* Set the LPUART into RS485 mode (tx driver enable using RTS) */
config->base->MODIR |= LPUART_MODIR_TXRTSE(true);
if (!config->rs485_de_active_low) {
config->base->MODIR |= LPUART_MODIR_TXRTSPOL(1);
}
}
/* Now can enable tx */
config->base->CTRL |= LPUART_CTRL_TE(true);
if (config->loopback_en) {
/* Set the LPUART into loopback mode */
config->base->CTRL |= LPUART_CTRL_LOOPS_MASK;
config->base->CTRL &= ~LPUART_CTRL_RSRC_MASK;
}
/* update internal uart_config */
data->uart_config = *cfg;
return 0;
}
#ifdef CONFIG_UART_USE_RUNTIME_CONFIGURE
static int mcux_lpuart_config_get(const struct device *dev, struct uart_config *cfg)
{
struct mcux_lpuart_data *data = dev->data;
*cfg = data->uart_config;
return 0;
}
static int mcux_lpuart_configure(const struct device *dev,
const struct uart_config *cfg)
{
const struct mcux_lpuart_config *config = dev->config;
/* disable LPUART */
LPUART_Deinit(config->base);
int ret = mcux_lpuart_configure_init(dev, cfg);
if (ret) {
return ret;
}
/* wait for hardware init */
k_sleep(K_MSEC(1));
return 0;
}
#endif /* CONFIG_UART_USE_RUNTIME_CONFIGURE */
static int mcux_lpuart_init(const struct device *dev)
{
const struct mcux_lpuart_config *config = dev->config;
struct mcux_lpuart_data *data = dev->data;
struct uart_config *uart_api_config = &data->uart_config;
int err;
uart_api_config->baudrate = config->baud_rate;
uart_api_config->parity = config->parity;
uart_api_config->stop_bits = UART_CFG_STOP_BITS_1;
uart_api_config->data_bits = UART_CFG_DATA_BITS_8;
uart_api_config->flow_ctrl = config->flow_ctrl;
/* set initial configuration */
mcux_lpuart_configure_init(dev, uart_api_config);
if (config->flow_ctrl) {
const struct pinctrl_state *state;
err = pinctrl_lookup_state(config->pincfg, PINCTRL_STATE_FLOWCONTROL, &state);
if (err < 0) {
err = pinctrl_apply_state(config->pincfg, PINCTRL_STATE_DEFAULT);
}
} else {
err = pinctrl_apply_state(config->pincfg, PINCTRL_STATE_DEFAULT);
}
if (err < 0) {
return err;
}
#ifdef CONFIG_UART_MCUX_LPUART_ISR_SUPPORT
#if CONFIG_NXP_LP_FLEXCOMM
/* When using LP Flexcomm driver, register the interrupt handler
* so we receive notification from the LP Flexcomm interrupt handler.
*/
nxp_lp_flexcomm_setirqhandler(config->parent_dev, dev,
LP_FLEXCOMM_PERIPH_LPUART, mcux_lpuart_isr);
#else
/* Interrupt is managed by this driver */
config->irq_config_func(dev);
#endif
#ifdef CONFIG_UART_EXCLUSIVE_API_CALLBACKS
data->api_type = LPUART_NONE;
#endif
#endif
#ifdef CONFIG_PM
data->pm_state_lock_on = false;
data->tx_poll_stream_on = false;
data->tx_int_stream_on = false;
#endif
return 0;
}
static const struct uart_driver_api mcux_lpuart_driver_api = {
.poll_in = mcux_lpuart_poll_in,
.poll_out = mcux_lpuart_poll_out,
.err_check = mcux_lpuart_err_check,
#ifdef CONFIG_UART_USE_RUNTIME_CONFIGURE
.configure = mcux_lpuart_configure,
.config_get = mcux_lpuart_config_get,
#endif
#ifdef CONFIG_UART_INTERRUPT_DRIVEN
.fifo_fill = mcux_lpuart_fifo_fill,
.fifo_read = mcux_lpuart_fifo_read,
.irq_tx_enable = mcux_lpuart_irq_tx_enable,
.irq_tx_disable = mcux_lpuart_irq_tx_disable,
.irq_tx_complete = mcux_lpuart_irq_tx_complete,
.irq_tx_ready = mcux_lpuart_irq_tx_ready,
.irq_rx_enable = mcux_lpuart_irq_rx_enable,
.irq_rx_disable = mcux_lpuart_irq_rx_disable,
.irq_rx_ready = mcux_lpuart_irq_rx_full,
.irq_err_enable = mcux_lpuart_irq_err_enable,
.irq_err_disable = mcux_lpuart_irq_err_disable,
.irq_is_pending = mcux_lpuart_irq_is_pending,
.irq_update = mcux_lpuart_irq_update,
.irq_callback_set = mcux_lpuart_irq_callback_set,
#endif
#ifdef CONFIG_UART_ASYNC_API
.callback_set = mcux_lpuart_callback_set,
.tx = mcux_lpuart_tx,
.tx_abort = mcux_lpuart_tx_abort,
.rx_enable = mcux_lpuart_rx_enable,
.rx_buf_rsp = mcux_lpuart_rx_buf_rsp,
.rx_disable = mcux_lpuart_rx_disable,
#endif /* CONFIG_UART_ASYNC_API */
};
#ifdef CONFIG_UART_MCUX_LPUART_ISR_SUPPORT
#define MCUX_LPUART_IRQ_INSTALL(n, i) \
do { \
IRQ_CONNECT(DT_INST_IRQN_BY_IDX(n, i), \
DT_INST_IRQ_BY_IDX(n, i, priority), \
mcux_lpuart_isr, DEVICE_DT_INST_GET(n), 0); \
\
irq_enable(DT_INST_IRQ_BY_IDX(n, i, irq)); \
} while (false)
#define MCUX_LPUART_IRQ_INIT(n) .irq_config_func = mcux_lpuart_config_func_##n,
#define MCUX_LPUART_IRQ_DEFINE(n) \
static void mcux_lpuart_config_func_##n(const struct device *dev) \
{ \
IF_ENABLED(DT_INST_IRQ_HAS_IDX(n, 0), \
(MCUX_LPUART_IRQ_INSTALL(n, 0);)) \
\
IF_ENABLED(DT_INST_IRQ_HAS_IDX(n, 1), \
(MCUX_LPUART_IRQ_INSTALL(n, 1);)) \
}
#else
#define MCUX_LPUART_IRQ_INIT(n)
#define MCUX_LPUART_IRQ_DEFINE(n)
#endif /* CONFIG_UART_MCUX_LPUART_ISR_SUPPORT */
#ifdef CONFIG_UART_ASYNC_API
#define TX_DMA_CONFIG(id) \
.tx_dma_config = { \
.dma_dev = \
DEVICE_DT_GET(DT_INST_DMAS_CTLR_BY_NAME(id, tx)), \
.dma_channel = \
DT_INST_DMAS_CELL_BY_NAME(id, tx, mux), \
.dma_cfg = { \
.source_burst_length = 1, \
.dest_burst_length = 1, \
.source_data_size = 1, \
.dest_data_size = 1, \
.complete_callback_en = 1, \
.error_callback_dis = 0, \
.block_count = 1, \
.head_block = \
&mcux_lpuart_##id##_data.async.tx_dma_params.active_dma_block, \
.channel_direction = MEMORY_TO_PERIPHERAL, \
.dma_slot = DT_INST_DMAS_CELL_BY_NAME( \
id, tx, source), \
.dma_callback = dma_callback, \
.user_data = (void *)DEVICE_DT_INST_GET(id) \
}, \
},
#define RX_DMA_CONFIG(id) \
.rx_dma_config = { \
.dma_dev = \
DEVICE_DT_GET(DT_INST_DMAS_CTLR_BY_NAME(id, rx)), \
.dma_channel = \
DT_INST_DMAS_CELL_BY_NAME(id, rx, mux), \
.dma_cfg = { \
.source_burst_length = 1, \
.dest_burst_length = 1, \
.source_data_size = 1, \
.dest_data_size = 1, \
.complete_callback_en = 1, \
.error_callback_dis = 0, \
.block_count = 1, \
.head_block = \
&mcux_lpuart_##id##_data.async.rx_dma_params.active_dma_block, \
.channel_direction = PERIPHERAL_TO_MEMORY, \
.dma_slot = DT_INST_DMAS_CELL_BY_NAME( \
id, rx, source), \
.dma_callback = dma_callback, \
.user_data = (void *)DEVICE_DT_INST_GET(id) \
}, \
},
#else
#define RX_DMA_CONFIG(n)
#define TX_DMA_CONFIG(n)
#endif /* CONFIG_UART_ASYNC_API */
#define FLOW_CONTROL(n) \
DT_INST_PROP(n, hw_flow_control) \
? UART_CFG_FLOW_CTRL_RTS_CTS \
: DT_INST_PROP(n, nxp_rs485_mode)\
? UART_CFG_FLOW_CTRL_RS485 \
: UART_CFG_FLOW_CTRL_NONE
#ifdef CONFIG_NXP_LP_FLEXCOMM
#define PARENT_DEV(n) \
.parent_dev = DEVICE_DT_GET(DT_INST_PARENT(n)),
#else
#define PARENT_DEV(n)
#endif /* CONFIG_NXP_LP_FLEXCOMM */
#define LPUART_MCUX_DECLARE_CFG(n) \
static const struct mcux_lpuart_config mcux_lpuart_##n##_config = { \
.base = (LPUART_Type *) DT_INST_REG_ADDR(n), \
PARENT_DEV(n) \
.clock_dev = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR(n)), \
.clock_subsys = (clock_control_subsys_t)DT_INST_CLOCKS_CELL(n, name), \
.baud_rate = DT_INST_PROP(n, current_speed), \
.flow_ctrl = FLOW_CONTROL(n), \
.parity = DT_INST_ENUM_IDX_OR(n, parity, UART_CFG_PARITY_NONE), \
.rs485_de_active_low = DT_INST_PROP(n, nxp_rs485_de_active_low), \
.loopback_en = DT_INST_PROP(n, nxp_loopback), \
.pincfg = PINCTRL_DT_INST_DEV_CONFIG_GET(n), \
MCUX_LPUART_IRQ_INIT(n) \
RX_DMA_CONFIG(n) \
TX_DMA_CONFIG(n) \
};
#define LPUART_MCUX_INIT(n) \
\
static struct mcux_lpuart_data mcux_lpuart_##n##_data; \
\
PINCTRL_DT_INST_DEFINE(n); \
MCUX_LPUART_IRQ_DEFINE(n) \
\
LPUART_MCUX_DECLARE_CFG(n) \
\
DEVICE_DT_INST_DEFINE(n, \
&mcux_lpuart_init, \
NULL, \
&mcux_lpuart_##n##_data, \
&mcux_lpuart_##n##_config, \
PRE_KERNEL_1, \
CONFIG_SERIAL_INIT_PRIORITY, \
&mcux_lpuart_driver_api); \
DT_INST_FOREACH_STATUS_OKAY(LPUART_MCUX_INIT)