| /* |
| * Copyright (c) 2023-2024 Analog Devices, Inc. |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #ifdef CONFIG_UART_ASYNC_API |
| #include <zephyr/drivers/dma.h> |
| #include <wrap_max32_dma.h> |
| #endif |
| #include <zephyr/drivers/pinctrl.h> |
| #include <zephyr/drivers/uart.h> |
| #include <zephyr/kernel.h> |
| #include <zephyr/logging/log.h> |
| #include <zephyr/drivers/clock_control/adi_max32_clock_control.h> |
| |
| #include <wrap_max32_uart.h> |
| |
| #define DT_DRV_COMPAT adi_max32_uart |
| |
| LOG_MODULE_REGISTER(uart_max32, CONFIG_UART_LOG_LEVEL); |
| |
| #ifdef CONFIG_UART_ASYNC_API |
| struct max32_uart_dma_config { |
| const struct device *dev; |
| const uint32_t channel; |
| const uint32_t slot; |
| }; |
| #endif /* CONFIG_UART_ASYNC_API */ |
| |
| struct max32_uart_config { |
| mxc_uart_regs_t *regs; |
| const struct pinctrl_dev_config *pctrl; |
| const struct device *clock; |
| struct max32_perclk perclk; |
| struct uart_config uart_conf; |
| #if defined(CONFIG_UART_INTERRUPT_DRIVEN) || defined(CONFIG_UART_ASYNC_API) |
| uart_irq_config_func_t irq_config_func; |
| #endif /* CONFIG_UART_INTERRUPT_DRIVEN || CONFIG_UART_ASYNC_API */ |
| #ifdef CONFIG_UART_ASYNC_API |
| const struct max32_uart_dma_config tx_dma; |
| const struct max32_uart_dma_config rx_dma; |
| #endif /* CONFIG_UART_ASYNC_API */ |
| }; |
| |
| #ifdef CONFIG_UART_ASYNC_API |
| #define MAX32_UART_TX_CACHE_NUM 2 |
| struct max32_uart_async_tx { |
| const uint8_t *buf; |
| const uint8_t *src; |
| size_t len; |
| uint8_t cache[MAX32_UART_TX_CACHE_NUM][CONFIG_UART_TX_CACHE_LEN]; |
| uint8_t cache_id; |
| struct dma_block_config dma_blk; |
| int32_t timeout; |
| struct k_work_delayable timeout_work; |
| }; |
| |
| struct max32_uart_async_rx { |
| uint8_t *buf; |
| size_t len; |
| size_t offset; |
| size_t counter; |
| uint8_t *next_buf; |
| size_t next_len; |
| int32_t timeout; |
| struct k_work_delayable timeout_work; |
| }; |
| |
| struct max32_uart_async_data { |
| const struct device *uart_dev; |
| struct max32_uart_async_tx tx; |
| struct max32_uart_async_rx rx; |
| uart_callback_t cb; |
| void *user_data; |
| }; |
| #endif |
| |
| struct max32_uart_data { |
| #ifdef CONFIG_UART_INTERRUPT_DRIVEN |
| uart_irq_callback_user_data_t cb; /* Interrupt callback */ |
| void *cb_data; /* Interrupt callback arg */ |
| uint32_t flags; /* Cached interrupt flags */ |
| uint32_t status; /* Cached status flags */ |
| #endif |
| #ifdef CONFIG_UART_ASYNC_API |
| struct max32_uart_async_data async; |
| #endif |
| struct uart_config conf; /* baudrate, stopbits, ... */ |
| }; |
| |
| #ifdef CONFIG_UART_INTERRUPT_DRIVEN |
| static void uart_max32_isr(const struct device *dev); |
| #endif |
| |
| #ifdef CONFIG_UART_ASYNC_API |
| static int uart_max32_tx_dma_load(const struct device *dev, uint8_t *buf, size_t len); |
| #endif |
| |
| static void api_poll_out(const struct device *dev, unsigned char c) |
| { |
| const struct max32_uart_config *cfg = dev->config; |
| |
| MXC_UART_WriteCharacter(cfg->regs, c); |
| } |
| |
| static int api_poll_in(const struct device *dev, unsigned char *c) |
| { |
| int val; |
| const struct max32_uart_config *cfg = dev->config; |
| |
| val = MXC_UART_ReadCharacterRaw(cfg->regs); |
| if (val >= 0) { |
| *c = (unsigned char)val; |
| } else { |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| static int api_err_check(const struct device *dev) |
| { |
| int err = 0; |
| uint32_t flags; |
| const struct max32_uart_config *cfg = dev->config; |
| |
| flags = MXC_UART_GetFlags(cfg->regs); |
| |
| if (flags & ADI_MAX32_UART_ERROR_FRAMING) { |
| err |= UART_ERROR_FRAMING; |
| } |
| |
| if (flags & ADI_MAX32_UART_ERROR_PARITY) { |
| err |= UART_ERROR_PARITY; |
| } |
| |
| if (flags & ADI_MAX32_UART_ERROR_OVERRUN) { |
| err |= UART_ERROR_OVERRUN; |
| } |
| |
| return err; |
| } |
| |
| static int api_configure(const struct device *dev, const struct uart_config *uart_cfg) |
| { |
| int err; |
| const struct max32_uart_config *const cfg = dev->config; |
| mxc_uart_regs_t *regs = cfg->regs; |
| struct max32_uart_data *data = dev->data; |
| |
| /* |
| * Set parity |
| */ |
| if (data->conf.parity != uart_cfg->parity) { |
| mxc_uart_parity_t mxc_parity; |
| |
| switch (uart_cfg->parity) { |
| case UART_CFG_PARITY_NONE: |
| mxc_parity = ADI_MAX32_UART_CFG_PARITY_NONE; |
| break; |
| case UART_CFG_PARITY_ODD: |
| mxc_parity = ADI_MAX32_UART_CFG_PARITY_ODD; |
| break; |
| case UART_CFG_PARITY_EVEN: |
| mxc_parity = ADI_MAX32_UART_CFG_PARITY_EVEN; |
| break; |
| case UART_CFG_PARITY_MARK: |
| #if defined(ADI_MAX32_UART_CFG_PARITY_MARK) |
| mxc_parity = ADI_MAX32_UART_CFG_PARITY_MARK; |
| break; |
| #else |
| return -ENOTSUP; |
| #endif |
| case UART_CFG_PARITY_SPACE: |
| #if defined(ADI_MAX32_UART_CFG_PARITY_SPACE) |
| mxc_parity = ADI_MAX32_UART_CFG_PARITY_SPACE; |
| break; |
| #else |
| return -ENOTSUP; |
| #endif |
| default: |
| return -EINVAL; |
| } |
| |
| err = MXC_UART_SetParity(regs, mxc_parity); |
| if (err < 0) { |
| return -ENOTSUP; |
| } |
| /* incase of success keep configuration */ |
| data->conf.parity = uart_cfg->parity; |
| } |
| |
| /* |
| * Set stop bit |
| */ |
| if (data->conf.stop_bits != uart_cfg->stop_bits) { |
| if (uart_cfg->stop_bits == UART_CFG_STOP_BITS_1) { |
| err = MXC_UART_SetStopBits(regs, MXC_UART_STOP_1); |
| } else if (uart_cfg->stop_bits == UART_CFG_STOP_BITS_2) { |
| err = MXC_UART_SetStopBits(regs, MXC_UART_STOP_2); |
| } else { |
| return -ENOTSUP; |
| } |
| if (err < 0) { |
| return -ENOTSUP; |
| } |
| /* incase of success keep configuration */ |
| data->conf.stop_bits = uart_cfg->stop_bits; |
| } |
| |
| /* |
| * Set data bit |
| * Valid data for MAX32 is 5-6-7-8 |
| * Valid data for Zepyhr is 0-1-2-3 |
| * Added +5 to index match. |
| */ |
| if (data->conf.data_bits != uart_cfg->data_bits) { |
| err = MXC_UART_SetDataSize(regs, (5 + uart_cfg->data_bits)); |
| if (err < 0) { |
| return -ENOTSUP; |
| } |
| /* incase of success keep configuration */ |
| data->conf.data_bits = uart_cfg->data_bits; |
| } |
| |
| /* |
| * Set flow control |
| * Flow control not implemented yet so that only support no flow mode |
| */ |
| if (data->conf.flow_ctrl != uart_cfg->flow_ctrl) { |
| if (uart_cfg->flow_ctrl != UART_CFG_FLOW_CTRL_NONE) { |
| return -ENOTSUP; |
| } |
| data->conf.flow_ctrl = uart_cfg->flow_ctrl; |
| } |
| |
| /* |
| * Set baudrate |
| */ |
| if (data->conf.baudrate != uart_cfg->baudrate) { |
| err = Wrap_MXC_UART_SetFrequency(regs, uart_cfg->baudrate, cfg->perclk.clk_src); |
| if (err < 0) { |
| return -ENOTSUP; |
| } |
| /* In case of success keep configuration */ |
| data->conf.baudrate = uart_cfg->baudrate; |
| } |
| return 0; |
| } |
| |
| #ifdef CONFIG_UART_USE_RUNTIME_CONFIGURE |
| |
| static int api_config_get(const struct device *dev, struct uart_config *uart_cfg) |
| { |
| struct max32_uart_data *data = dev->data; |
| |
| /* copy configs from global setting */ |
| *uart_cfg = data->conf; |
| |
| return 0; |
| } |
| |
| #endif /* CONFIG_UART_USE_RUNTIME_CONFIGURE */ |
| |
| #ifdef CONFIG_UART_ASYNC_API |
| static void uart_max32_async_tx_timeout(struct k_work *work); |
| static void uart_max32_async_rx_timeout(struct k_work *work); |
| #endif /* CONFIG_UART_ASYNC_API */ |
| |
| static int uart_max32_init(const struct device *dev) |
| { |
| int ret; |
| const struct max32_uart_config *const cfg = dev->config; |
| mxc_uart_regs_t *regs = cfg->regs; |
| #ifdef CONFIG_UART_ASYNC_API |
| struct max32_uart_data *data = dev->data; |
| #endif |
| |
| if (!device_is_ready(cfg->clock)) { |
| LOG_ERR("Clock control device not ready"); |
| return -ENODEV; |
| } |
| |
| ret = MXC_UART_Shutdown(regs); |
| if (ret) { |
| return ret; |
| } |
| |
| ret = clock_control_on(cfg->clock, (clock_control_subsys_t)&cfg->perclk); |
| if (ret != 0) { |
| LOG_ERR("Cannot enable UART clock"); |
| return ret; |
| } |
| |
| ret = Wrap_MXC_UART_SetClockSource(regs, cfg->perclk.clk_src); |
| if (ret != 0) { |
| LOG_ERR("Cannot set UART clock source"); |
| return ret; |
| } |
| |
| ret = pinctrl_apply_state(cfg->pctrl, PINCTRL_STATE_DEFAULT); |
| if (ret) { |
| return ret; |
| } |
| |
| ret = api_configure(dev, &cfg->uart_conf); |
| if (ret) { |
| return ret; |
| } |
| |
| ret = Wrap_MXC_UART_Init(regs); |
| if (ret) { |
| return ret; |
| } |
| |
| #if defined(CONFIG_UART_INTERRUPT_DRIVEN) || defined(CONFIG_UART_ASYNC_API) |
| /* Clear any pending UART RX/TX interrupts */ |
| MXC_UART_ClearFlags(regs, (ADI_MAX32_UART_INT_RX | ADI_MAX32_UART_INT_TX)); |
| cfg->irq_config_func(dev); |
| #endif |
| |
| #ifdef CONFIG_UART_ASYNC_API |
| data->async.uart_dev = dev; |
| k_work_init_delayable(&data->async.tx.timeout_work, uart_max32_async_tx_timeout); |
| k_work_init_delayable(&data->async.rx.timeout_work, uart_max32_async_rx_timeout); |
| data->async.rx.len = 0; |
| data->async.rx.offset = 0; |
| #endif |
| |
| return ret; |
| } |
| |
| #ifdef CONFIG_UART_INTERRUPT_DRIVEN |
| |
| static int api_fifo_fill(const struct device *dev, const uint8_t *tx_data, int size) |
| { |
| unsigned int num_tx = 0; |
| const struct max32_uart_config *cfg = dev->config; |
| |
| num_tx = MXC_UART_WriteTXFIFO(cfg->regs, (unsigned char *)tx_data, size); |
| |
| return (int)num_tx; |
| } |
| |
| static int api_fifo_read(const struct device *dev, uint8_t *rx_data, const int size) |
| { |
| unsigned int num_rx = 0; |
| const struct max32_uart_config *cfg = dev->config; |
| |
| num_rx = MXC_UART_ReadRXFIFO(cfg->regs, (unsigned char *)rx_data, size); |
| if (num_rx == 0) { |
| MXC_UART_ClearFlags(cfg->regs, ADI_MAX32_UART_INT_RX); |
| } |
| |
| return num_rx; |
| } |
| |
| static void api_irq_tx_enable(const struct device *dev) |
| { |
| const struct max32_uart_config *cfg = dev->config; |
| unsigned int key; |
| |
| MXC_UART_EnableInt(cfg->regs, ADI_MAX32_UART_INT_TX | ADI_MAX32_UART_INT_TX_OEM); |
| |
| key = irq_lock(); |
| uart_max32_isr(dev); |
| irq_unlock(key); |
| } |
| |
| static void api_irq_tx_disable(const struct device *dev) |
| { |
| const struct max32_uart_config *cfg = dev->config; |
| |
| MXC_UART_DisableInt(cfg->regs, ADI_MAX32_UART_INT_TX | ADI_MAX32_UART_INT_TX_OEM); |
| } |
| |
| static int api_irq_tx_ready(const struct device *dev) |
| { |
| struct max32_uart_data *const data = dev->data; |
| const struct max32_uart_config *cfg = dev->config; |
| uint32_t inten = Wrap_MXC_UART_GetRegINTEN(cfg->regs); |
| |
| return ((inten & (ADI_MAX32_UART_INT_TX | ADI_MAX32_UART_INT_TX_OEM)) && |
| !(data->status & ADI_MAX32_UART_STATUS_TX_FULL)); |
| } |
| |
| static int api_irq_tx_complete(const struct device *dev) |
| { |
| const struct max32_uart_config *cfg = dev->config; |
| |
| if (MXC_UART_GetActive(cfg->regs) == E_BUSY) { |
| return 0; |
| } else { |
| return 1; /* transmission completed */ |
| } |
| } |
| |
| static int api_irq_rx_ready(const struct device *dev) |
| { |
| struct max32_uart_data *const data = dev->data; |
| const struct max32_uart_config *cfg = dev->config; |
| uint32_t inten = Wrap_MXC_UART_GetRegINTEN(cfg->regs); |
| |
| return ((inten & ADI_MAX32_UART_INT_RX) && !(data->status & ADI_MAX32_UART_RX_EMPTY)); |
| } |
| |
| static void api_irq_err_enable(const struct device *dev) |
| { |
| const struct max32_uart_config *cfg = dev->config; |
| |
| MXC_UART_EnableInt(cfg->regs, ADI_MAX32_UART_ERROR_INTERRUPTS); |
| } |
| |
| static void api_irq_err_disable(const struct device *dev) |
| { |
| const struct max32_uart_config *cfg = dev->config; |
| |
| MXC_UART_DisableInt(cfg->regs, ADI_MAX32_UART_ERROR_INTERRUPTS); |
| } |
| |
| static int api_irq_is_pending(const struct device *dev) |
| { |
| struct max32_uart_data *const data = dev->data; |
| |
| return (data->flags & (ADI_MAX32_UART_INT_RX | ADI_MAX32_UART_INT_TX)); |
| } |
| |
| static int api_irq_update(const struct device *dev) |
| { |
| struct max32_uart_data *const data = dev->data; |
| const struct max32_uart_config *const cfg = dev->config; |
| |
| data->flags = MXC_UART_GetFlags(cfg->regs); |
| data->status = MXC_UART_GetStatus(cfg->regs); |
| |
| MXC_UART_ClearFlags(cfg->regs, data->flags); |
| |
| return 1; |
| } |
| |
| static void api_irq_callback_set(const struct device *dev, uart_irq_callback_user_data_t cb, |
| void *cb_data) |
| { |
| struct max32_uart_data *const dev_data = dev->data; |
| |
| dev_data->cb = cb; |
| dev_data->cb_data = cb_data; |
| } |
| |
| #endif /* CONFIG_UART_INTERRUPT_DRIVEN */ |
| |
| #if defined(CONFIG_UART_INTERRUPT_DRIVEN) || defined(CONFIG_UART_ASYNC_API) |
| static void api_irq_rx_enable(const struct device *dev) |
| { |
| const struct max32_uart_config *cfg = dev->config; |
| |
| MXC_UART_EnableInt(cfg->regs, ADI_MAX32_UART_INT_RX); |
| } |
| |
| static void api_irq_rx_disable(const struct device *dev) |
| { |
| const struct max32_uart_config *cfg = dev->config; |
| |
| MXC_UART_DisableInt(cfg->regs, ADI_MAX32_UART_INT_RX); |
| } |
| |
| static void uart_max32_isr(const struct device *dev) |
| { |
| struct max32_uart_data *data = dev->data; |
| const struct max32_uart_config *cfg = dev->config; |
| uint32_t intfl; |
| |
| intfl = MXC_UART_GetFlags(cfg->regs); |
| |
| #ifdef CONFIG_UART_INTERRUPT_DRIVEN |
| if (data->cb) { |
| data->cb(dev, data->cb_data); |
| } |
| #endif /* CONFIG_UART_INTERRUPT_DRIVEN */ |
| |
| #ifdef CONFIG_UART_ASYNC_API |
| if (data->async.rx.timeout != SYS_FOREVER_US && data->async.rx.timeout != 0 && |
| (intfl & ADI_MAX32_UART_INT_RX)) { |
| k_work_reschedule(&data->async.rx.timeout_work, K_USEC(data->async.rx.timeout)); |
| } |
| #endif /* CONFIG_UART_ASYNC_API */ |
| |
| /* Clear RX/TX interrupts flag after cb is called */ |
| MXC_UART_ClearFlags(cfg->regs, intfl); |
| } |
| #endif /* CONFIG_UART_INTERRUPT_DRIVEN || CONFIG_UART_ASYNC_API */ |
| |
| #if defined(CONFIG_UART_ASYNC_API) |
| |
| static inline void async_timer_start(struct k_work_delayable *work, int32_t timeout) |
| { |
| if ((timeout != SYS_FOREVER_US) && (timeout != 0)) { |
| k_work_reschedule(work, K_USEC(timeout)); |
| } |
| } |
| |
| static void async_user_callback(const struct device *dev, struct uart_event *evt) |
| { |
| const struct max32_uart_data *data = dev->data; |
| |
| if (data->async.cb) { |
| data->async.cb(dev, evt, data->async.user_data); |
| } |
| } |
| |
| static uint32_t load_tx_cache(const uint8_t *src, size_t len, uint8_t *dest) |
| { |
| memcpy(dest, src, MIN(len, CONFIG_UART_TX_CACHE_LEN)); |
| |
| return MIN(len, CONFIG_UART_TX_CACHE_LEN); |
| } |
| |
| static void uart_max32_async_tx_callback(const struct device *dma_dev, void *user_data, |
| uint32_t channel, int status) |
| { |
| const struct device *dev = user_data; |
| const struct max32_uart_config *config = dev->config; |
| struct max32_uart_data *data = dev->data; |
| struct max32_uart_async_tx *tx = &data->async.tx; |
| struct dma_status dma_stat; |
| int ret; |
| |
| unsigned int key = irq_lock(); |
| |
| dma_get_status(config->tx_dma.dev, config->tx_dma.channel, &dma_stat); |
| /* Skip callback if channel is still busy */ |
| if (dma_stat.busy) { |
| irq_unlock(key); |
| return; |
| } |
| |
| k_work_cancel_delayable(&tx->timeout_work); |
| Wrap_MXC_UART_DisableTxDMA(config->regs); |
| |
| irq_unlock(key); |
| |
| tx->len -= tx->dma_blk.block_size; |
| if (tx->len > 0) { |
| tx->cache_id = !(tx->cache_id); |
| ret = uart_max32_tx_dma_load(dev, tx->cache[tx->cache_id], |
| MIN(tx->len, CONFIG_UART_TX_CACHE_LEN)); |
| if (ret < 0) { |
| LOG_ERR("Error configuring Tx DMA (%d)", ret); |
| return; |
| } |
| |
| ret = dma_start(config->tx_dma.dev, config->tx_dma.channel); |
| if (ret < 0) { |
| LOG_ERR("Error starting Tx DMA (%d)", ret); |
| return; |
| } |
| |
| async_timer_start(&tx->timeout_work, tx->timeout); |
| |
| Wrap_MXC_UART_SetTxDMALevel(config->regs, 2); |
| Wrap_MXC_UART_EnableTxDMA(config->regs); |
| |
| /* Load next chunk as well */ |
| if (tx->len > CONFIG_UART_TX_CACHE_LEN) { |
| tx->src += load_tx_cache(tx->src, tx->len - CONFIG_UART_TX_CACHE_LEN, |
| tx->cache[!(tx->cache_id)]); |
| } |
| } else { |
| struct uart_event tx_done = { |
| .type = status == 0 ? UART_TX_DONE : UART_TX_ABORTED, |
| .data.tx.buf = tx->buf, |
| .data.tx.len = tx->len, |
| }; |
| async_user_callback(dev, &tx_done); |
| } |
| } |
| |
| static int uart_max32_tx_dma_load(const struct device *dev, uint8_t *buf, size_t len) |
| { |
| int ret; |
| const struct max32_uart_config *config = dev->config; |
| struct max32_uart_data *data = dev->data; |
| struct dma_config dma_cfg = {0}; |
| struct dma_block_config *dma_blk = &data->async.tx.dma_blk; |
| |
| dma_cfg.channel_direction = MEMORY_TO_PERIPHERAL; |
| dma_cfg.dma_callback = uart_max32_async_tx_callback; |
| dma_cfg.user_data = (void *)dev; |
| dma_cfg.dma_slot = config->tx_dma.slot; |
| dma_cfg.block_count = 1; |
| dma_cfg.source_data_size = 1U; |
| dma_cfg.source_burst_length = 1U; |
| dma_cfg.dest_data_size = 1U; |
| dma_cfg.head_block = dma_blk; |
| dma_blk->block_size = len; |
| dma_blk->source_address = (uint32_t)buf; |
| |
| ret = dma_config(config->tx_dma.dev, config->tx_dma.channel, &dma_cfg); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static int api_callback_set(const struct device *dev, uart_callback_t callback, void *user_data) |
| { |
| struct max32_uart_data *data = dev->data; |
| |
| data->async.cb = callback; |
| data->async.user_data = user_data; |
| |
| return 0; |
| } |
| |
| static int api_tx(const struct device *dev, const uint8_t *buf, size_t len, int32_t timeout) |
| { |
| struct max32_uart_data *data = dev->data; |
| const struct max32_uart_config *config = dev->config; |
| struct dma_status dma_stat; |
| int ret; |
| bool use_cache = false; |
| unsigned int key = irq_lock(); |
| |
| if (config->tx_dma.channel == 0xFF) { |
| LOG_ERR("Tx DMA channel is not configured"); |
| ret = -ENOTSUP; |
| goto unlock; |
| } |
| |
| ret = dma_get_status(config->tx_dma.dev, config->tx_dma.channel, &dma_stat); |
| if (ret < 0 || dma_stat.busy) { |
| LOG_ERR("DMA Tx %s", ret < 0 ? "error" : "busy"); |
| irq_unlock(key); |
| return ret < 0 ? ret : -EBUSY; |
| } |
| |
| data->async.tx.buf = buf; |
| data->async.tx.len = len; |
| data->async.tx.src = data->async.tx.buf; |
| |
| if (((uint32_t)buf < MXC_SRAM_MEM_BASE) || |
| (((uint32_t)buf + len) > (MXC_SRAM_MEM_BASE + MXC_SRAM_MEM_SIZE))) { |
| use_cache = true; |
| len = load_tx_cache(data->async.tx.src, MIN(len, CONFIG_UART_TX_CACHE_LEN), |
| data->async.tx.cache[0]); |
| data->async.tx.src += len; |
| data->async.tx.cache_id = 0; |
| } |
| |
| ret = uart_max32_tx_dma_load(dev, use_cache ? data->async.tx.cache[0] : ((uint8_t *)buf), |
| len); |
| if (ret < 0) { |
| LOG_ERR("Error configuring Tx DMA (%d)", ret); |
| goto unlock; |
| } |
| |
| ret = dma_start(config->tx_dma.dev, config->tx_dma.channel); |
| if (ret < 0) { |
| LOG_ERR("Error starting Tx DMA (%d)", ret); |
| goto unlock; |
| } |
| |
| data->async.tx.timeout = timeout; |
| async_timer_start(&data->async.tx.timeout_work, timeout); |
| |
| Wrap_MXC_UART_SetTxDMALevel(config->regs, 2); |
| Wrap_MXC_UART_EnableTxDMA(config->regs); |
| |
| unlock: |
| irq_unlock(key); |
| |
| return ret; |
| } |
| |
| static int api_tx_abort(const struct device *dev) |
| { |
| int ret; |
| struct max32_uart_data *data = dev->data; |
| const struct max32_uart_config *config = dev->config; |
| struct dma_status dma_stat; |
| size_t bytes_sent; |
| |
| unsigned int key = irq_lock(); |
| |
| k_work_cancel_delayable(&data->async.tx.timeout_work); |
| |
| Wrap_MXC_UART_DisableTxDMA(config->regs); |
| |
| ret = dma_get_status(config->tx_dma.dev, config->tx_dma.channel, &dma_stat); |
| if (!dma_stat.busy) { |
| irq_unlock(key); |
| return 0; |
| } |
| |
| bytes_sent = (ret == 0) ? (data->async.tx.len - dma_stat.pending_length) : 0; |
| |
| ret = dma_stop(config->tx_dma.dev, config->tx_dma.channel); |
| |
| irq_unlock(key); |
| |
| if (ret == 0) { |
| struct uart_event tx_aborted = { |
| .type = UART_TX_ABORTED, |
| .data.tx.buf = data->async.tx.buf, |
| .data.tx.len = bytes_sent, |
| }; |
| async_user_callback(dev, &tx_aborted); |
| } |
| |
| return 0; |
| } |
| |
| static void uart_max32_async_tx_timeout(struct k_work *work) |
| { |
| struct k_work_delayable *dwork = k_work_delayable_from_work(work); |
| struct max32_uart_async_tx *tx = |
| CONTAINER_OF(dwork, struct max32_uart_async_tx, timeout_work); |
| struct max32_uart_async_data *async = CONTAINER_OF(tx, struct max32_uart_async_data, tx); |
| struct max32_uart_data *data = CONTAINER_OF(async, struct max32_uart_data, async); |
| |
| api_tx_abort(data->async.uart_dev); |
| } |
| |
| static int api_rx_disable(const struct device *dev) |
| { |
| struct max32_uart_data *data = dev->data; |
| const struct max32_uart_config *config = dev->config; |
| int ret; |
| unsigned int key = irq_lock(); |
| |
| k_work_cancel_delayable(&data->async.rx.timeout_work); |
| |
| Wrap_MXC_UART_DisableRxDMA(config->regs); |
| |
| ret = dma_stop(config->rx_dma.dev, config->rx_dma.channel); |
| if (ret) { |
| LOG_ERR("Error stopping Rx DMA (%d)", ret); |
| irq_unlock(key); |
| return ret; |
| } |
| |
| api_irq_rx_disable(dev); |
| |
| irq_unlock(key); |
| |
| /* Release current buffer event */ |
| struct uart_event rel_event = { |
| .type = UART_RX_BUF_RELEASED, |
| .data.rx_buf.buf = data->async.rx.buf, |
| }; |
| async_user_callback(dev, &rel_event); |
| |
| /* Disable RX event */ |
| struct uart_event rx_disabled = {.type = UART_RX_DISABLED}; |
| |
| async_user_callback(dev, &rx_disabled); |
| |
| data->async.rx.buf = NULL; |
| data->async.rx.len = 0; |
| data->async.rx.counter = 0; |
| data->async.rx.offset = 0; |
| |
| if (data->async.rx.next_buf) { |
| /* Release next buffer event */ |
| struct uart_event next_rel_event = { |
| .type = UART_RX_BUF_RELEASED, |
| .data.rx_buf.buf = data->async.rx.next_buf, |
| }; |
| async_user_callback(dev, &next_rel_event); |
| data->async.rx.next_buf = NULL; |
| data->async.rx.next_len = 0; |
| } |
| |
| return 0; |
| } |
| |
| static void uart_max32_async_rx_callback(const struct device *dma_dev, void *user_data, |
| uint32_t channel, int status) |
| { |
| const struct device *dev = user_data; |
| const struct max32_uart_config *config = dev->config; |
| struct max32_uart_data *data = dev->data; |
| struct max32_uart_async_data *async = &data->async; |
| struct dma_status dma_stat; |
| size_t total_rx; |
| |
| unsigned int key = irq_lock(); |
| |
| dma_get_status(config->rx_dma.dev, config->rx_dma.channel, &dma_stat); |
| |
| if (dma_stat.pending_length > 0) { |
| irq_unlock(key); |
| return; |
| } |
| |
| total_rx = async->rx.len - dma_stat.pending_length; |
| |
| api_irq_rx_disable(dev); |
| |
| irq_unlock(key); |
| |
| if (total_rx > async->rx.offset) { |
| async->rx.counter = total_rx - async->rx.offset; |
| struct uart_event rdy_event = { |
| .type = UART_RX_RDY, |
| .data.rx.buf = async->rx.buf, |
| .data.rx.len = async->rx.counter, |
| .data.rx.offset = async->rx.offset, |
| }; |
| async_user_callback(dev, &rdy_event); |
| } |
| |
| if (async->rx.next_buf) { |
| async->rx.offset = 0; |
| async->rx.counter = 0; |
| |
| struct uart_event rel_event = { |
| .type = UART_RX_BUF_RELEASED, |
| .data.rx_buf.buf = async->rx.buf, |
| }; |
| async_user_callback(dev, &rel_event); |
| |
| async->rx.buf = async->rx.next_buf; |
| async->rx.len = async->rx.next_len; |
| |
| async->rx.next_buf = NULL; |
| async->rx.next_len = 0; |
| struct uart_event req_event = { |
| .type = UART_RX_BUF_REQUEST, |
| }; |
| async_user_callback(dev, &req_event); |
| |
| dma_reload(config->rx_dma.dev, config->rx_dma.channel, config->rx_dma.slot, |
| (uint32_t)async->rx.buf, async->rx.len); |
| dma_start(config->rx_dma.dev, config->rx_dma.channel); |
| |
| api_irq_rx_enable(dev); |
| async_timer_start(&async->rx.timeout_work, async->rx.timeout); |
| } else { |
| api_rx_disable(dev); |
| } |
| } |
| |
| static int api_rx_enable(const struct device *dev, uint8_t *buf, size_t len, int32_t timeout) |
| { |
| struct max32_uart_data *data = dev->data; |
| const struct max32_uart_config *config = dev->config; |
| struct dma_status dma_stat; |
| struct dma_config dma_cfg = {0}; |
| struct dma_block_config dma_blk = {0}; |
| int ret; |
| |
| unsigned int key = irq_lock(); |
| |
| if (config->rx_dma.channel == 0xFF) { |
| LOG_ERR("Rx DMA channel is not configured"); |
| irq_unlock(key); |
| return -ENOTSUP; |
| } |
| |
| ret = dma_get_status(config->rx_dma.dev, config->rx_dma.channel, &dma_stat); |
| if (ret < 0 || dma_stat.busy) { |
| LOG_ERR("DMA Rx %s", ret < 0 ? "error" : "busy"); |
| irq_unlock(key); |
| return ret < 0 ? ret : -EBUSY; |
| } |
| |
| data->async.rx.buf = buf; |
| data->async.rx.len = len; |
| |
| dma_cfg.channel_direction = MEMORY_TO_PERIPHERAL; |
| dma_cfg.dma_callback = uart_max32_async_rx_callback; |
| dma_cfg.user_data = (void *)dev; |
| dma_cfg.dma_slot = config->rx_dma.slot; |
| dma_cfg.block_count = 1; |
| dma_cfg.source_data_size = 1U; |
| dma_cfg.source_burst_length = 1U; |
| dma_cfg.dest_data_size = 1U; |
| dma_cfg.head_block = &dma_blk; |
| dma_blk.block_size = len; |
| dma_blk.dest_address = (uint32_t)buf; |
| |
| ret = dma_config(config->rx_dma.dev, config->rx_dma.channel, &dma_cfg); |
| if (ret < 0) { |
| LOG_ERR("Error configuring Rx DMA (%d)", ret); |
| irq_unlock(key); |
| return ret; |
| } |
| |
| ret = dma_start(config->rx_dma.dev, config->rx_dma.channel); |
| if (ret < 0) { |
| LOG_ERR("Error starting Rx DMA (%d)", ret); |
| irq_unlock(key); |
| return ret; |
| } |
| |
| data->async.rx.timeout = timeout; |
| |
| Wrap_MXC_UART_SetRxDMALevel(config->regs, 1); |
| Wrap_MXC_UART_EnableRxDMA(config->regs); |
| |
| struct uart_event buf_req = { |
| .type = UART_RX_BUF_REQUEST, |
| }; |
| |
| async_user_callback(dev, &buf_req); |
| |
| api_irq_rx_enable(dev); |
| async_timer_start(&data->async.rx.timeout_work, timeout); |
| |
| irq_unlock(key); |
| return ret; |
| } |
| |
| static int api_rx_buf_rsp(const struct device *dev, uint8_t *buf, size_t len) |
| { |
| struct max32_uart_data *data = dev->data; |
| |
| data->async.rx.next_buf = buf; |
| data->async.rx.next_len = len; |
| |
| return 0; |
| } |
| |
| static void uart_max32_async_rx_timeout(struct k_work *work) |
| { |
| struct k_work_delayable *dwork = k_work_delayable_from_work(work); |
| struct max32_uart_async_rx *rx = |
| CONTAINER_OF(dwork, struct max32_uart_async_rx, timeout_work); |
| struct max32_uart_async_data *async = CONTAINER_OF(rx, struct max32_uart_async_data, rx); |
| struct max32_uart_data *data = CONTAINER_OF(async, struct max32_uart_data, async); |
| const struct max32_uart_config *config = data->async.uart_dev->config; |
| struct dma_status dma_stat; |
| uint32_t total_rx; |
| |
| unsigned int key = irq_lock(); |
| |
| dma_get_status(config->rx_dma.dev, config->rx_dma.channel, &dma_stat); |
| |
| api_irq_rx_disable(data->async.uart_dev); |
| k_work_cancel_delayable(&data->async.rx.timeout_work); |
| |
| irq_unlock(key); |
| |
| total_rx = async->rx.len - dma_stat.pending_length; |
| |
| if (total_rx > async->rx.offset) { |
| async->rx.counter = total_rx - async->rx.offset; |
| struct uart_event rdy_event = { |
| .type = UART_RX_RDY, |
| .data.rx.buf = async->rx.buf, |
| .data.rx.len = async->rx.counter, |
| .data.rx.offset = async->rx.offset, |
| }; |
| async_user_callback(async->uart_dev, &rdy_event); |
| } |
| async->rx.offset += async->rx.counter; |
| async->rx.counter = 0; |
| |
| api_irq_rx_enable(data->async.uart_dev); |
| } |
| |
| #endif |
| |
| static DEVICE_API(uart, uart_max32_driver_api) = { |
| .poll_in = api_poll_in, |
| .poll_out = api_poll_out, |
| .err_check = api_err_check, |
| #ifdef CONFIG_UART_USE_RUNTIME_CONFIGURE |
| .configure = api_configure, |
| .config_get = api_config_get, |
| #endif /* CONFIG_UART_USE_RUNTIME_CONFIGURE */ |
| #ifdef CONFIG_UART_INTERRUPT_DRIVEN |
| .fifo_fill = api_fifo_fill, |
| .fifo_read = api_fifo_read, |
| .irq_tx_enable = api_irq_tx_enable, |
| .irq_tx_disable = api_irq_tx_disable, |
| .irq_tx_ready = api_irq_tx_ready, |
| .irq_rx_enable = api_irq_rx_enable, |
| .irq_rx_disable = api_irq_rx_disable, |
| .irq_tx_complete = api_irq_tx_complete, |
| .irq_rx_ready = api_irq_rx_ready, |
| .irq_err_enable = api_irq_err_enable, |
| .irq_err_disable = api_irq_err_disable, |
| .irq_is_pending = api_irq_is_pending, |
| .irq_update = api_irq_update, |
| .irq_callback_set = api_irq_callback_set, |
| #endif /* CONFIG_UART_INTERRUPT_DRIVEN */ |
| #ifdef CONFIG_UART_ASYNC_API |
| .callback_set = api_callback_set, |
| .tx = api_tx, |
| .tx_abort = api_tx_abort, |
| .rx_enable = api_rx_enable, |
| .rx_buf_rsp = api_rx_buf_rsp, |
| .rx_disable = api_rx_disable, |
| #endif /* CONFIG_UART_ASYNC_API */ |
| }; |
| |
| #ifdef CONFIG_UART_ASYNC_API |
| #define MAX32_DT_INST_DMA_CTLR(n, name) \ |
| COND_CODE_1(DT_INST_NODE_HAS_PROP(n, dmas), \ |
| (DEVICE_DT_GET(DT_INST_DMAS_CTLR_BY_NAME(n, name))), (NULL)) |
| |
| #define MAX32_DT_INST_DMA_CELL(n, name, cell) \ |
| COND_CODE_1(DT_INST_NODE_HAS_PROP(n, dmas), (DT_INST_DMAS_CELL_BY_NAME(n, name, cell)), \ |
| (0xff)) |
| |
| #define MAX32_UART_DMA_INIT(n) \ |
| .tx_dma.dev = MAX32_DT_INST_DMA_CTLR(n, tx), \ |
| .tx_dma.channel = MAX32_DT_INST_DMA_CELL(n, tx, channel), \ |
| .tx_dma.slot = MAX32_DT_INST_DMA_CELL(n, tx, slot), \ |
| .rx_dma.dev = MAX32_DT_INST_DMA_CTLR(n, rx), \ |
| .rx_dma.channel = MAX32_DT_INST_DMA_CELL(n, rx, channel), \ |
| .rx_dma.slot = MAX32_DT_INST_DMA_CELL(n, rx, slot), |
| #else |
| #define MAX32_UART_DMA_INIT(n) |
| #endif |
| |
| #if defined(CONFIG_UART_INTERRUPT_DRIVEN) || defined(CONFIG_UART_ASYNC_API) |
| #define MAX32_UART_USE_IRQ 1 |
| #else |
| #define MAX32_UART_USE_IRQ 0 |
| #endif |
| |
| #define MAX32_UART_INIT(_num) \ |
| PINCTRL_DT_INST_DEFINE(_num); \ |
| IF_ENABLED(MAX32_UART_USE_IRQ, \ |
| (static void uart_max32_irq_init_##_num(const struct device *dev) \ |
| { \ |
| IRQ_CONNECT(DT_INST_IRQN(_num), DT_INST_IRQ(_num, priority), \ |
| uart_max32_isr, DEVICE_DT_INST_GET(_num), 0); \ |
| irq_enable(DT_INST_IRQN(_num)); \ |
| })); \ |
| static const struct max32_uart_config max32_uart_config_##_num = { \ |
| .regs = (mxc_uart_regs_t *)DT_INST_REG_ADDR(_num), \ |
| .pctrl = PINCTRL_DT_INST_DEV_CONFIG_GET(_num), \ |
| .clock = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR(_num)), \ |
| .perclk.bus = DT_INST_CLOCKS_CELL(_num, offset), \ |
| .perclk.bit = DT_INST_CLOCKS_CELL(_num, bit), \ |
| .perclk.clk_src = \ |
| DT_INST_PROP_OR(_num, clock_source, ADI_MAX32_PRPH_CLK_SRC_PCLK), \ |
| .uart_conf.baudrate = DT_INST_PROP(_num, current_speed), \ |
| .uart_conf.parity = DT_INST_ENUM_IDX(_num, parity), \ |
| .uart_conf.data_bits = DT_INST_ENUM_IDX(_num, data_bits), \ |
| .uart_conf.stop_bits = DT_INST_ENUM_IDX(_num, stop_bits), \ |
| .uart_conf.flow_ctrl = \ |
| DT_INST_PROP_OR(index, hw_flow_control, UART_CFG_FLOW_CTRL_NONE), \ |
| MAX32_UART_DMA_INIT(_num) IF_ENABLED( \ |
| MAX32_UART_USE_IRQ, (.irq_config_func = uart_max32_irq_init_##_num,))}; \ |
| static struct max32_uart_data max32_uart_data##_num = { \ |
| IF_ENABLED(CONFIG_UART_INTERRUPT_DRIVEN, (.cb = NULL,))}; \ |
| DEVICE_DT_INST_DEFINE(_num, uart_max32_init, NULL, &max32_uart_data##_num, \ |
| &max32_uart_config_##_num, PRE_KERNEL_1, \ |
| CONFIG_SERIAL_INIT_PRIORITY, (void *)&uart_max32_driver_api); |
| |
| DT_INST_FOREACH_STATUS_OKAY(MAX32_UART_INIT) |