| /* |
| * Copyright (c) 2023 Intel Corporation. |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <errno.h> |
| #include <zephyr/device.h> |
| #include <zephyr/drivers/uart.h> |
| #include <zephyr/pm/device.h> |
| #include "sedi_driver_uart.h" |
| |
| #ifdef CONFIG_UART_INTERRUPT_DRIVEN |
| static void uart_sedi_isr(void *arg); |
| static void uart_sedi_cb(struct device *port); |
| #endif |
| |
| #define DT_DRV_COMPAT intel_sedi_uart |
| |
| #ifdef CONFIG_UART_INTERRUPT_DRIVEN |
| /* UART IRQ handler declaration. */ |
| #define UART_IRQ_HANDLER_DECL(n) \ |
| static void irq_config_uart_##n(const struct device *dev) |
| |
| /* Setting configuration function. */ |
| #define UART_CONFIG_IRQ_HANDLER_SET(n) \ |
| .uart_irq_config_func = irq_config_uart_##n |
| #define UART_IRQ_HANDLER_DEFINE(n) \ |
| static void irq_config_uart_##n(const struct device *dev) \ |
| { \ |
| ARG_UNUSED(dev); \ |
| IRQ_CONNECT(DT_INST_IRQN(n), \ |
| DT_INST_IRQ(n, priority), uart_sedi_isr, \ |
| DEVICE_DT_GET(DT_NODELABEL(uart##n)), \ |
| DT_INST_IRQ(n, sense)); \ |
| irq_enable(DT_INST_IRQN(n)); \ |
| } |
| #else /*CONFIG_UART_INTERRUPT_DRIVEN */ |
| #define UART_IRQ_HANDLER_DECL(n) |
| #define UART_CONFIG_IRQ_HANDLER_SET(n) (0) |
| |
| #define UART_IRQ_HANDLER_DEFINE(n) |
| #endif /* !CONFIG_UART_INTERRUPT_DRIVEN */ |
| |
| /* Device init macro for UART instance. As multiple uart instances follow a |
| * similar definition of data structures differing only in the instance |
| * number.This macro makes adding instances simpler. |
| */ |
| #define UART_SEDI_DEVICE_INIT(n) \ |
| UART_IRQ_HANDLER_DECL(n); \ |
| static K_MUTEX_DEFINE(uart_##n##_mutex); \ |
| static K_SEM_DEFINE(uart_##n##_tx_sem, 1, 1); \ |
| static K_SEM_DEFINE(uart_##n##_rx_sem, 1, 1); \ |
| static K_SEM_DEFINE(uart_##n##_sync_read_sem, 0, 1); \ |
| static const struct uart_sedi_config_info config_info_##n = { \ |
| DEVICE_MMIO_ROM_INIT(DT_DRV_INST(n)), \ |
| .instance = DT_INST_PROP(n, peripheral_id), \ |
| .baud_rate = DT_INST_PROP(n, current_speed), \ |
| .hw_fc = DT_INST_PROP(n, hw_flow_control), \ |
| .line_ctrl = SEDI_UART_LC_8N1, \ |
| .mutex = &uart_##n##_mutex, \ |
| UART_CONFIG_IRQ_HANDLER_SET(n) \ |
| }; \ |
| \ |
| static struct uart_sedi_drv_data drv_data_##n; \ |
| PM_DEVICE_DT_DEFINE(DT_NODELABEL(uart##n), \ |
| uart_sedi_pm_action); \ |
| DEVICE_DT_DEFINE(DT_NODELABEL(uart##n), \ |
| &uart_sedi_init, \ |
| PM_DEVICE_DT_GET(DT_NODELABEL(uart##n)), \ |
| &drv_data_##n, &config_info_##n, \ |
| PRE_KERNEL_1, \ |
| CONFIG_SERIAL_INIT_PRIORITY, &api); \ |
| UART_IRQ_HANDLER_DEFINE(n) |
| |
| |
| |
| /* Convenient macro to get the controller instance. */ |
| #define GET_CONTROLLER_INSTANCE(dev) \ |
| (((const struct uart_sedi_config_info *) \ |
| dev->config)->instance) |
| |
| #define GET_MUTEX(dev) \ |
| (((const struct uart_sedi_config_info *) \ |
| dev->config)->mutex) |
| |
| struct uart_sedi_config_info { |
| DEVICE_MMIO_ROM; |
| /* Specifies the uart instance for configuration. */ |
| sedi_uart_t instance; |
| |
| /* Specifies the baudrate for the uart instance. */ |
| uint32_t baud_rate; |
| |
| /* Specifies the port line control settings */ |
| sedi_uart_lc_t line_ctrl; |
| |
| struct k_mutex *mutex; |
| |
| /* Enable / disable hardware flow control for UART. */ |
| bool hw_fc; |
| |
| /* UART irq configuration function when supporting interrupt |
| * mode. |
| */ |
| uart_irq_config_func_t uart_irq_config_func; |
| }; |
| |
| |
| static int uart_sedi_init(const struct device *dev); |
| |
| struct uart_sedi_drv_data { |
| DEVICE_MMIO_RAM; |
| uart_irq_callback_user_data_t user_cb; |
| void *unsol_rx_usr_cb_param; |
| uint32_t sync_rx_len; |
| uint32_t sync_rx_status; |
| void *user_data; |
| void *usr_rx_buff; |
| uint32_t usr_rx_size; |
| uint8_t iir_cache; |
| uint8_t busy_count; |
| }; |
| |
| #ifdef CONFIG_UART_INTERRUPT_DRIVEN |
| static void uart_busy_set(const struct device *dev) |
| { |
| |
| struct uart_sedi_drv_data *context = dev->data; |
| |
| context->busy_count++; |
| |
| if (context->busy_count == 1) { |
| pm_device_busy_set(dev); |
| } |
| } |
| |
| static void uart_busy_clear(const struct device *dev) |
| { |
| |
| struct uart_sedi_drv_data *context = dev->data; |
| |
| context->busy_count--; |
| |
| if (context->busy_count == 0) { |
| pm_device_busy_clear(dev); |
| } |
| } |
| #endif |
| |
| #ifdef CONFIG_PM_DEVICE |
| |
| #ifndef CONFIG_UART_CONSOLE |
| |
| static int uart_suspend_device(const struct device *dev) |
| { |
| const struct uart_sedi_config_info *config = dev->config; |
| |
| if (pm_device_is_busy(dev)) { |
| return -EBUSY; |
| } |
| |
| int ret = sedi_uart_set_power(config->instance, SEDI_POWER_SUSPEND); |
| |
| if (ret != SEDI_DRIVER_OK) { |
| return -EIO; |
| } |
| |
| return 0; |
| } |
| |
| static int uart_resume_device_from_suspend(const struct device *dev) |
| { |
| const struct uart_sedi_config_info *config = dev->config; |
| int ret; |
| |
| ret = sedi_uart_set_power(config->instance, SEDI_POWER_FULL); |
| if (ret != SEDI_DRIVER_OK) { |
| return -EIO; |
| } |
| |
| return 0; |
| } |
| |
| static int uart_sedi_pm_action(const struct device *dev, |
| enum pm_device_action action) |
| { |
| int ret = 0; |
| |
| switch (action) { |
| case PM_DEVICE_ACTION_SUSPEND: |
| ret = uart_suspend_device(dev); |
| break; |
| case PM_DEVICE_ACTION_RESUME: |
| ret = uart_resume_device_from_suspend(dev); |
| break; |
| |
| default: |
| ret = -ENOTSUP; |
| } |
| return ret; |
| } |
| |
| #else |
| |
| static int uart_sedi_pm_action(const struct device *dev, |
| enum pm_device_action action) |
| { |
| /* do nothing if using UART print log to avoid clock gating |
| * pm driver already handled power management for uart. |
| */ |
| return 0; |
| } |
| |
| #endif /* CONFIG_UART_CONSOLE */ |
| |
| #endif /* CONFIG_PM_DEVICE */ |
| |
| static int uart_sedi_poll_in(const struct device *dev, unsigned char *data) |
| { |
| sedi_uart_t instance = GET_CONTROLLER_INSTANCE(dev); |
| uint32_t status; |
| int ret = 0; |
| |
| sedi_uart_get_status(instance, (uint32_t *) &status); |
| |
| /* In order to check if there is any data to read from UART |
| * controller we should check if the SEDI_UART_RX_BUSY bit from |
| * 'status' is not set. This bit is set only if there is any |
| * pending character to read. |
| */ |
| if (!(status & SEDI_UART_RX_BUSY)) { |
| ret = -1; |
| } else { |
| if (sedi_uart_read(instance, data, (uint32_t *)&status)) { |
| ret = -1; |
| } |
| } |
| return ret; |
| } |
| |
| static void uart_sedi_poll_out(const struct device *dev, |
| unsigned char data) |
| { |
| sedi_uart_t instance = GET_CONTROLLER_INSTANCE(dev); |
| |
| sedi_uart_write(instance, data); |
| } |
| |
| #ifdef CONFIG_UART_LINE_CTRL |
| static int get_xfer_error(int bsp_err) |
| { |
| int err; |
| |
| switch (bsp_err) { |
| case SEDI_DRIVER_OK: |
| err = 0; |
| break; |
| case SEDI_USART_ERROR_CANCELED: |
| err = -ECANCELED; |
| break; |
| case SEDI_DRIVER_ERROR: |
| err = -EIO; |
| break; |
| case SEDI_DRIVER_ERROR_PARAMETER: |
| err = -EINVAL; |
| break; |
| case SEDI_DRIVER_ERROR_UNSUPPORTED: |
| err = -ENOTSUP; |
| break; |
| default: |
| err = -EFAULT; |
| } |
| return err; |
| } |
| #endif /* CONFIG_UART_LINE_CTRL */ |
| |
| static int uart_sedi_err_check(const struct device *dev) |
| { |
| sedi_uart_t instance = GET_CONTROLLER_INSTANCE(dev); |
| uint32_t status; |
| int ret_status = 0; |
| |
| sedi_uart_get_status(instance, (uint32_t *const)&status); |
| if (status & SEDI_UART_RX_OE) { |
| ret_status = UART_ERROR_OVERRUN; |
| } |
| |
| if (status & SEDI_UART_RX_PE) { |
| ret_status = UART_ERROR_PARITY; |
| } |
| |
| if (status & SEDI_UART_RX_FE) { |
| ret_status = UART_ERROR_FRAMING; |
| } |
| |
| if (status & SEDI_UART_RX_BI) { |
| ret_status = UART_BREAK; |
| } |
| |
| return ret_status; |
| } |
| |
| |
| #ifdef CONFIG_UART_INTERRUPT_DRIVEN |
| |
| static int uart_sedi_fifo_fill(const struct device *dev, const uint8_t *tx_data, |
| int size) |
| { |
| sedi_uart_t instance = GET_CONTROLLER_INSTANCE(dev); |
| |
| return sedi_uart_fifo_fill(instance, tx_data, size); |
| } |
| |
| static int uart_sedi_fifo_read(const struct device *dev, uint8_t *rx_data, |
| const int size) |
| { |
| sedi_uart_t instance = GET_CONTROLLER_INSTANCE(dev); |
| |
| return sedi_uart_fifo_read(instance, rx_data, size); |
| } |
| |
| static void uart_sedi_irq_tx_enable(const struct device *dev) |
| { |
| sedi_uart_t instance = GET_CONTROLLER_INSTANCE(dev); |
| |
| sedi_uart_irq_tx_enable(instance); |
| } |
| |
| static void uart_sedi_irq_tx_disable(const struct device *dev) |
| { |
| sedi_uart_t instance = GET_CONTROLLER_INSTANCE(dev); |
| |
| sedi_uart_irq_tx_disable(instance); |
| } |
| |
| static int uart_sedi_irq_tx_ready(const struct device *dev) |
| { |
| sedi_uart_t instance = GET_CONTROLLER_INSTANCE(dev); |
| |
| return sedi_uart_irq_tx_ready(instance); |
| } |
| |
| static int uart_sedi_irq_tx_complete(const struct device *dev) |
| { |
| sedi_uart_t instance = GET_CONTROLLER_INSTANCE(dev); |
| |
| return sedi_uart_is_tx_complete(instance); |
| } |
| |
| static void uart_sedi_irq_rx_enable(const struct device *dev) |
| { |
| sedi_uart_t instance = GET_CONTROLLER_INSTANCE(dev); |
| |
| uart_busy_set(dev); |
| sedi_uart_irq_rx_enable(instance); |
| } |
| |
| static void uart_sedi_irq_rx_disable(const struct device *dev) |
| { |
| sedi_uart_t instance = GET_CONTROLLER_INSTANCE(dev); |
| |
| sedi_uart_irq_rx_disable(instance); |
| uart_busy_clear(dev); |
| } |
| |
| static int uart_sedi_irq_rx_ready(const struct device *dev) |
| { |
| sedi_uart_t instance = GET_CONTROLLER_INSTANCE(dev); |
| |
| return sedi_uart_is_irq_rx_ready(instance); |
| } |
| |
| static void uart_sedi_irq_err_enable(const struct device *dev) |
| { |
| sedi_uart_t instance = GET_CONTROLLER_INSTANCE(dev); |
| |
| sedi_uart_irq_err_enable(instance); |
| } |
| |
| static void uart_sedi_irq_err_disable(const struct device *dev) |
| { |
| sedi_uart_t instance = GET_CONTROLLER_INSTANCE(dev); |
| |
| sedi_uart_irq_err_disable(instance); |
| } |
| |
| static int uart_sedi_irq_is_pending(const struct device *dev) |
| { |
| |
| sedi_uart_t instance = GET_CONTROLLER_INSTANCE(dev); |
| |
| return sedi_uart_is_irq_pending(instance); |
| } |
| |
| static int uart_sedi_irq_update(const struct device *dev) |
| { |
| sedi_uart_t instance = GET_CONTROLLER_INSTANCE(dev); |
| |
| sedi_uart_update_irq_cache(instance); |
| return 1; |
| } |
| |
| static void uart_sedi_irq_callback_set(const struct device *dev, |
| uart_irq_callback_user_data_t cb, |
| void *user_data) |
| { |
| struct uart_sedi_drv_data *drv_data = dev->data; |
| |
| drv_data->user_cb = cb; |
| drv_data->user_data = user_data; |
| |
| } |
| |
| static void uart_sedi_isr(void *arg) |
| { |
| struct device *dev = arg; |
| struct uart_sedi_drv_data *drv_data = dev->data; |
| |
| if (drv_data->user_cb) { |
| drv_data->user_cb(dev, drv_data->user_data); |
| } else { |
| uart_sedi_cb(dev); |
| } |
| } |
| |
| /* Called from generic callback of zephyr , set by set_cb. */ |
| static void uart_sedi_cb(struct device *port) |
| { |
| sedi_uart_t instance = GET_CONTROLLER_INSTANCE(port); |
| |
| sedi_uart_isr_handler(instance); |
| } |
| |
| #endif /* CONFIG_UART_INTERRUPT_DRIVEN */ |
| |
| #ifdef CONFIG_UART_LINE_CTRL |
| static int uart_sedi_line_ctrl_set(struct device *dev, |
| uint32_t ctrl, uint32_t val) |
| { |
| sedi_uart_t instance = GET_CONTROLLER_INSTANCE(dev); |
| sedi_uart_config_t cfg; |
| uint32_t mask; |
| int ret; |
| |
| k_mutex_lock(GET_MUTEX(dev), K_FOREVER); |
| switch (ctrl) { |
| case UART_LINE_CTRL_BAUD_RATE: |
| sedi_uart_get_config(instance, &cfg); |
| cfg.baud_rate = val; |
| ret = sedi_uart_set_config(instance, &cfg); |
| break; |
| |
| default: |
| ret = -ENODEV; |
| } |
| k_mutex_unlock(GET_MUTEX(dev)); |
| ret = get_xfer_error(ret); |
| return ret; |
| } |
| |
| static int uart_sedi_line_ctrl_get(struct device *dev, |
| uint32_t ctrl, uint32_t *val) |
| { |
| sedi_uart_t instance = GET_CONTROLLER_INSTANCE(dev); |
| sedi_uart_config_t cfg; |
| uint32_t mask; |
| int ret; |
| |
| k_mutex_lock(GET_MUTEX(dev), K_FOREVER); |
| switch (ctrl) { |
| case UART_LINE_CTRL_BAUD_RATE: |
| ret = sedi_uart_get_config(instance, &cfg); |
| *val = cfg.baud_rate; |
| break; |
| |
| case UART_LINE_CTRL_LOOPBACK: |
| ret = sedi_uart_get_loopback_mode(instance, (uint32_t *)val); |
| break; |
| |
| case UART_LINE_CTRL_AFCE: |
| ret = sedi_uart_get_config(instance, &cfg); |
| *val = cfg.hw_fc; |
| break; |
| |
| case UART_LINE_CTRL_LINE_STATUS_REPORT_MASK: |
| mask = 0; |
| *val = 0; |
| ret = sedi_get_ln_status_report_mask(instance, |
| (uint32_t *)&mask); |
| *val |= ((mask & SEDI_UART_RX_OE) ? UART_ERROR_OVERRUN : 0); |
| *val |= ((mask & SEDI_UART_RX_PE) ? UART_ERROR_PARITY : 0); |
| *val |= ((mask & SEDI_UART_RX_FE) ? UART_ERROR_FRAMING : 0); |
| *val |= ((mask & SEDI_UART_RX_BI) ? UART_BREAK : 0); |
| break; |
| |
| case UART_LINE_CTRL_RTS: |
| ret = sedi_uart_read_rts(instance, (uint32_t *)val); |
| break; |
| |
| case UART_LINE_CTRL_CTS: |
| ret = sedi_uart_read_cts(instance, (uint32_t *)val); |
| break; |
| |
| |
| default: |
| ret = -ENODEV; |
| } |
| k_mutex_unlock(GET_MUTEX(dev)); |
| ret = get_xfer_error(ret); |
| return ret; |
| } |
| |
| #endif /* CONFIG_UART_LINE_CTRL */ |
| |
| static const struct uart_driver_api api = { |
| .poll_in = uart_sedi_poll_in, |
| .poll_out = uart_sedi_poll_out, |
| .err_check = uart_sedi_err_check, |
| #ifdef CONFIG_UART_INTERRUPT_DRIVEN |
| .fifo_fill = uart_sedi_fifo_fill, |
| .fifo_read = uart_sedi_fifo_read, |
| .irq_tx_enable = uart_sedi_irq_tx_enable, |
| .irq_tx_disable = uart_sedi_irq_tx_disable, |
| .irq_tx_ready = uart_sedi_irq_tx_ready, |
| .irq_tx_complete = uart_sedi_irq_tx_complete, |
| .irq_rx_enable = uart_sedi_irq_rx_enable, |
| .irq_rx_disable = uart_sedi_irq_rx_disable, |
| .irq_rx_ready = uart_sedi_irq_rx_ready, |
| .irq_err_enable = uart_sedi_irq_err_enable, |
| .irq_err_disable = uart_sedi_irq_err_disable, |
| .irq_is_pending = uart_sedi_irq_is_pending, |
| .irq_update = uart_sedi_irq_update, |
| .irq_callback_set = uart_sedi_irq_callback_set, |
| #endif /* CONFIG_UART_INTERRUPT_DRIVEN */ |
| #ifdef CONFIG_UART_LINE_CTRL |
| .line_ctrl_set = uart_sedi_line_ctrl_set, |
| .line_ctrl_get = uart_sedi_line_ctrl_get, |
| #endif /* CONFIG_UART_LINE_CTRL */ |
| |
| }; |
| |
| static int uart_sedi_init(const struct device *dev) |
| { |
| |
| const struct uart_sedi_config_info *config = dev->config; |
| sedi_uart_config_t cfg; |
| |
| DEVICE_MMIO_MAP(dev, K_MEM_CACHE_NONE); |
| sedi_uart_init(config->instance, (void *)DEVICE_MMIO_GET(dev)); |
| |
| cfg.line_control = config->line_ctrl; |
| cfg.baud_rate = config->baud_rate; |
| cfg.hw_fc = config->hw_fc; |
| |
| /* Setting to full power and enabling clk. */ |
| sedi_uart_set_power(config->instance, SEDI_POWER_FULL); |
| |
| sedi_uart_set_config(config->instance, &cfg); |
| |
| #ifdef CONFIG_UART_INTERRUPT_DRIVEN |
| config->uart_irq_config_func(dev); |
| #endif /* CONFIG_UART_INTERRUPT_DRIVEN */ |
| |
| return 0; |
| } |
| |
| DT_INST_FOREACH_STATUS_OKAY(UART_SEDI_DEVICE_INIT) |