diff --git a/drivers/serial/CMakeLists.txt b/drivers/serial/CMakeLists.txt index 1d1f21025f7..3b9339022d0 100644 --- a/drivers/serial/CMakeLists.txt +++ b/drivers/serial/CMakeLists.txt @@ -43,6 +43,7 @@ zephyr_library_sources_ifdef(CONFIG_UART_APBUART uart_apbuart.c) zephyr_library_sources_ifdef(CONFIG_USB_CDC_ACM ${ZEPHYR_BASE}/misc/empty_file.c) zephyr_library_sources_ifdef(CONFIG_UART_RCAR uart_rcar.c) zephyr_library_sources_ifdef(CONFIG_UART_XEC uart_mchp_xec.c) +zephyr_library_sources_ifdef(CONFIG_UART_NEORV32 uart_neorv32.c) zephyr_library_sources_ifdef(CONFIG_USERSPACE uart_handlers.c) diff --git a/drivers/serial/Kconfig b/drivers/serial/Kconfig index 08250244e2b..c1bba4c4ed2 100644 --- a/drivers/serial/Kconfig +++ b/drivers/serial/Kconfig @@ -161,4 +161,6 @@ source "drivers/serial/Kconfig.xec" source "drivers/serial/Kconfig.test" +source "drivers/serial/Kconfig.neorv32" + endif # SERIAL diff --git a/drivers/serial/Kconfig.neorv32 b/drivers/serial/Kconfig.neorv32 new file mode 100644 index 00000000000..5ab1f869771 --- /dev/null +++ b/drivers/serial/Kconfig.neorv32 @@ -0,0 +1,21 @@ +# NEORV32 UART configuration + +# Copyright (c) 2021 Henrik Brix Andersen +# SPDX-License-Identifier: Apache-2.0 + +config UART_NEORV32 + bool "NEORV32 UART" + default $(dt_compat_enabled,neorv32-uart) + depends on SOC_SERIES_NEORV32 && SYSCON + select SERIAL_HAS_DRIVER + select SERIAL_SUPPORT_INTERRUPT + help + This option enables the UART driver for the NEORV32. + +config UART_NEORV32_INIT_PRIORITY + int "Driver inititalization priority" + default 55 + depends on UART_NEORV32 + help + Device driver initialization priority. This driver must be + initialized after the syscon driver. diff --git a/drivers/serial/uart_neorv32.c b/drivers/serial/uart_neorv32.c new file mode 100644 index 00000000000..40967af0bc2 --- /dev/null +++ b/drivers/serial/uart_neorv32.c @@ -0,0 +1,552 @@ +/* + * Copyright (c) 2021 Henrik Brix Andersen + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT neorv32_uart + +#include +#include +#include +#include + +#include +LOG_MODULE_REGISTER(uart_neorv32, CONFIG_LOG_DEFAULT_LEVEL); + +/* NEORV32 UART registers offsets */ +#define NEORV32_UART_CTRL_OFFSET 0x00 +#define NEORV32_UART_DATA_OFFSET 0x04 + +/* UART_CTRL register bits */ +#define NEORV32_UART_CTRL_BAUD_MASK BIT_MASK(12) +#define NEORV32_UART_CTRL_BAUD_POS 0U +#define NEORV32_UART_CTRL_PRSC_MASK BIT_MASK(3) +#define NEORV32_UART_CTRL_PRSC_POS 24U +#define NEORV32_UART_CTRL_RTS_EN BIT(20) +#define NEORV32_UART_CTRL_CTS_EN BIT(21) +#define NEORV32_UART_CTRL_PMODE_NONE BIT(22) +#define NEORV32_UART_CTRL_PMODE_EVEN BIT(23) +#define NEORV32_UART_CTRL_PMODE_ODD (BIT(22) | BIT(23)) +#define NEORV32_UART_CTRL_EN BIT(28) +#define NEORV32_UART_CTRL_TX_BUSY BIT(31) + +/* UART_DATA register status bits */ +#define NEORV32_UART_DATA_PERR BIT(28) +#define NEORV32_UART_DATA_FERR BIT(29) +#define NEORV32_UART_DATA_OVERR BIT(30) +#define NEORV32_UART_DATA_AVAIL BIT(31) + +struct neorv32_uart_config { + const struct device *syscon; + uint32_t feature_mask; + mm_reg_t base; +#ifdef CONFIG_UART_INTERRUPT_DRIVEN + void (*irq_config_func)(const struct device *dev); + unsigned int tx_irq; + unsigned int rx_irq; +#endif /* CONFIG_UART_INTERRUPT_DRIVEN */ +}; + +struct neorv32_uart_data { + struct uart_config uart_cfg; + uint32_t last_data; +#ifdef CONFIG_UART_INTERRUPT_DRIVEN + struct k_timer timer; + uart_irq_callback_user_data_t callback; + void *callback_data; +#endif /* CONFIG_UART_INTERRUPT_DRIVEN */ +}; + +static inline uint32_t neorv32_uart_read_ctrl(const struct device *dev) +{ + const struct neorv32_uart_config *config = dev->config; + + return sys_read32(config->base + NEORV32_UART_CTRL_OFFSET); +} + +static inline void neorv32_uart_write_ctrl(const struct device *dev, uint32_t ctrl) +{ + const struct neorv32_uart_config *config = dev->config; + + sys_write32(ctrl, config->base + NEORV32_UART_CTRL_OFFSET); +} + +static inline uint32_t neorv32_uart_read_data(const struct device *dev) +{ + const struct neorv32_uart_config *config = dev->config; + struct neorv32_uart_data *data = dev->data; + uint32_t reg; + + /* Cache status bits as they are cleared upon read */ + reg = sys_read32(config->base + NEORV32_UART_DATA_OFFSET); + data->last_data = reg; + + return reg; +} + +static inline void neorv32_uart_write_data(const struct device *dev, uint32_t data) +{ + const struct neorv32_uart_config *config = dev->config; + + sys_write32(data, config->base + NEORV32_UART_DATA_OFFSET); +} + +static int neorv32_uart_poll_in(const struct device *dev, unsigned char *c) +{ + uint32_t data; + + data = neorv32_uart_read_data(dev); + + if ((data & NEORV32_UART_DATA_AVAIL) != 0) { + *c = data & BIT_MASK(8); + return 0; + } + + return -1; +} + +static void neorv32_uart_poll_out(const struct device *dev, unsigned char c) +{ + while ((neorv32_uart_read_ctrl(dev) & NEORV32_UART_CTRL_TX_BUSY) != 0) { + } + + neorv32_uart_write_data(dev, c); +} + +static int neorv32_uart_err_check(const struct device *dev) +{ + struct neorv32_uart_data *data = dev->data; + int err = 0; + + if ((data->last_data & NEORV32_UART_DATA_OVERR) != 0) { + err |= UART_ERROR_OVERRUN; + } + + if ((data->last_data & NEORV32_UART_DATA_PERR) != 0) { + err |= UART_ERROR_PARITY; + } + + if ((data->last_data & NEORV32_UART_DATA_FERR) != 0) { + err |= UART_ERROR_FRAMING; + } + + data->last_data &= ~(NEORV32_UART_DATA_OVERR | NEORV32_UART_DATA_PERR | + NEORV32_UART_DATA_FERR); + + return err; +} + +static int neorv32_uart_configure(const struct device *dev, const struct uart_config *cfg) +{ + const struct neorv32_uart_config *config = dev->config; + struct neorv32_uart_data *data = dev->data; + uint32_t ctrl = NEORV32_UART_CTRL_EN; + uint16_t baudxx = 0; + uint8_t prscx = 0; + uint32_t clk; + int err; + + __ASSERT_NO_MSG(cfg != NULL); + + if (cfg->stop_bits != UART_CFG_STOP_BITS_1) { + LOG_ERR("hardware only supports one stop bit"); + return -ENOTSUP; + } + + if (cfg->data_bits != UART_CFG_DATA_BITS_8) { + LOG_ERR("hardware only supports 8 data bits"); + return -ENOTSUP; + } + + switch (cfg->parity) { + case UART_CFG_PARITY_NONE: + ctrl |= NEORV32_UART_CTRL_PMODE_NONE; + break; + case UART_CFG_PARITY_ODD: + ctrl |= NEORV32_UART_CTRL_PMODE_ODD; + break; + case UART_CFG_PARITY_EVEN: + ctrl |= NEORV32_UART_CTRL_PMODE_EVEN; + break; + default: + LOG_ERR("unsupported parity mode %d", cfg->parity); + return -ENOTSUP; + } + + switch (cfg->flow_ctrl) { + case UART_CFG_FLOW_CTRL_NONE: + ctrl |= 0; + break; + case UART_CFG_FLOW_CTRL_RTS_CTS: + ctrl |= NEORV32_UART_CTRL_RTS_EN | NEORV32_UART_CTRL_CTS_EN; + break; + default: + LOG_ERR("unsupported flow control mode %d", cfg->flow_ctrl); + return -ENOTSUP; + } + + err = syscon_read_reg(config->syscon, NEORV32_SYSINFO_CLK, &clk); + if (err < 0) { + LOG_ERR("failed to determine clock rate (err %d)", err); + return -EIO; + } + + if (cfg->baudrate == 0) { + LOG_ERR("invalid baud rate 0"); + return -EINVAL; + } + + /* + * Calculate clock prescaler and baud prescaler. Initial prscx = 0 is + * clock / 2. + */ + baudxx = clk / (2 * cfg->baudrate); + while (baudxx >= NEORV32_UART_CTRL_BAUD_MASK) { + if ((prscx == 2) || (prscx == 4)) { + baudxx >>= 3; + } else { + baudxx >>= 1; + } + + prscx++; + } + + if (prscx > NEORV32_UART_CTRL_PRSC_MASK) { + LOG_ERR("unsupported baud rate %d", cfg->baudrate); + return -ENOTSUP; + } + + ctrl |= (baudxx - 1) << NEORV32_UART_CTRL_BAUD_POS; + ctrl |= prscx << NEORV32_UART_CTRL_PRSC_POS; + + data->uart_cfg = *cfg; + neorv32_uart_write_ctrl(dev, ctrl); + + return 0; +} + +static int neorv32_uart_config_get(const struct device *dev, struct uart_config *cfg) +{ + struct neorv32_uart_data *data = dev->data; + + __ASSERT_NO_MSG(cfg != NULL); + + *cfg = data->uart_cfg; + + return 0; +} + +#ifdef CONFIG_UART_INTERRUPT_DRIVEN +static int neorv32_uart_fifo_fill(const struct device *dev, const uint8_t *tx_data, int len) +{ + uint32_t ctrl; + + if (len <= 0) { + return 0; + } + + __ASSERT_NO_MSG(tx_data != NULL); + + ctrl = neorv32_uart_read_ctrl(dev); + if ((ctrl & NEORV32_UART_CTRL_TX_BUSY) == 0) { + neorv32_uart_write_data(dev, *tx_data); + return 1; + } + + return 0; +} + +static int neorv32_uart_fifo_read(const struct device *dev, uint8_t *rx_data, const int size) +{ + struct neorv32_uart_data *data = dev->data; + int count = 0; + + if (size <= 0) { + return 0; + } + + __ASSERT_NO_MSG(rx_data != NULL); + + while ((data->last_data & NEORV32_UART_DATA_AVAIL) != 0) { + rx_data[count++] = data->last_data & BIT_MASK(8); + data->last_data &= ~(NEORV32_UART_DATA_AVAIL); + + if (count >= size) { + break; + } + + (void)neorv32_uart_read_data(dev); + } + + return count; +} + +static void neorv32_uart_tx_soft_isr(struct k_timer *timer) +{ + const struct device *dev = k_timer_user_data_get(timer); + struct neorv32_uart_data *data = dev->data; + uart_irq_callback_user_data_t callback = data->callback; + + if (callback) { + callback(dev, data->callback_data); + } +} + +static void neorv32_uart_irq_tx_enable(const struct device *dev) +{ + const struct neorv32_uart_config *config = dev->config; + struct neorv32_uart_data *data = dev->data; + uint32_t ctrl; + + irq_enable(config->tx_irq); + + ctrl = neorv32_uart_read_ctrl(dev); + if ((ctrl & NEORV32_UART_CTRL_TX_BUSY) == 0) { + /* + * TX done event already generated an edge interrupt. Generate a + * soft interrupt and have it call the callback function in + * timer isr context. + */ + k_timer_start(&data->timer, K_NO_WAIT, K_NO_WAIT); + } +} + +static void neorv32_uart_irq_tx_disable(const struct device *dev) +{ + const struct neorv32_uart_config *config = dev->config; + + irq_disable(config->tx_irq); +} + +static int neorv32_uart_irq_tx_ready(const struct device *dev) +{ + const struct neorv32_uart_config *config = dev->config; + uint32_t ctrl; + + if (!irq_is_enabled(config->tx_irq)) { + return 0; + } + + ctrl = neorv32_uart_read_ctrl(dev); + + return (ctrl & NEORV32_UART_CTRL_TX_BUSY) == 0; +} + +static void neorv32_uart_irq_rx_enable(const struct device *dev) +{ + const struct neorv32_uart_config *config = dev->config; + + irq_enable(config->rx_irq); +} + +static void neorv32_uart_irq_rx_disable(const struct device *dev) +{ + const struct neorv32_uart_config *config = dev->config; + + irq_disable(config->rx_irq); +} + +static int neorv32_uart_irq_tx_complete(const struct device *dev) +{ + uint32_t ctrl; + + ctrl = neorv32_uart_read_ctrl(dev); + + return (ctrl & NEORV32_UART_CTRL_TX_BUSY) == 0; +} + +static int neorv32_uart_irq_rx_ready(const struct device *dev) +{ + const struct neorv32_uart_config *config = dev->config; + struct neorv32_uart_data *data = dev->data; + + if (!irq_is_enabled(config->rx_irq)) { + return 0; + } + + return (data->last_data & NEORV32_UART_DATA_AVAIL) != 0; +} + +static int neorv32_uart_irq_is_pending(const struct device *dev) +{ + return (neorv32_uart_irq_tx_ready(dev) || + neorv32_uart_irq_rx_ready(dev)); +} + +static int neorv32_uart_irq_update(const struct device *dev) +{ + const struct neorv32_uart_config *config = dev->config; + + if (irq_is_enabled(config->rx_irq)) { + /* Cache data for use by rx_ready() and fifo_read() */ + (void)neorv32_uart_read_data(dev); + } + + return 1; +} + +static void neorv32_uart_irq_callback_set(const struct device *dev, + uart_irq_callback_user_data_t cb, void *user_data) +{ + struct neorv32_uart_data *data = dev->data; + + data->callback = cb; + data->callback_data = user_data; +} + +static void neorv32_uart_isr(const struct device *dev) +{ + struct neorv32_uart_data *data = dev->data; + uart_irq_callback_user_data_t callback = data->callback; + + if (callback) { + callback(dev, data->callback_data); + } +} +#endif /* CONFIG_UART_INTERRUPT_DRIVEN */ + +static int neorv32_uart_init(const struct device *dev) +{ + const struct neorv32_uart_config *config = dev->config; + struct neorv32_uart_data *data = dev->data; + uint32_t features; + int err; + + if (!device_is_ready(config->syscon)) { + LOG_ERR("syscon device not ready"); + return -EINVAL; + } + + err = syscon_read_reg(config->syscon, NEORV32_SYSINFO_FEATURES, &features); + if (err < 0) { + LOG_ERR("failed to determine implemented features (err %d)", err); + return -EIO; + } + + if ((features & config->feature_mask) == 0) { + LOG_ERR("neorv32 uart instance not supported"); + return -ENODEV; + } + +#ifdef CONFIG_UART_INTERRUPT_DRIVEN + k_timer_init(&data->timer, &neorv32_uart_tx_soft_isr, NULL); + k_timer_user_data_set(&data->timer, (void *)dev); + + config->irq_config_func(dev); +#endif /* CONFIG_UART_INTERRUPT_DRIVEN */ + + return neorv32_uart_configure(dev, &data->uart_cfg); +} + +#ifdef CONFIG_PM_DEVICE +static int neorv32_uart_pm_control(const struct device *dev, enum pm_device_action action) +{ + uint32_t ctrl = neorv32_uart_read_ctrl(dev); + + switch (action) { + case PM_DEVICE_ACTION_SUSPEND: + ctrl &= ~(NEORV32_UART_CTRL_EN); + break; + case PM_DEVICE_ACTION_RESUME: + ctrl |= NEORV32_UART_CTRL_EN; + break; + default: + return -ENOTSUP; + } + + neorv32_uart_write_ctrl(dev, ctrl); + + return 0; +} +#endif /* CONFIG_PM_DEVICE */ + +static const struct uart_driver_api neorv32_uart_driver_api = { + .poll_in = neorv32_uart_poll_in, + .poll_out = neorv32_uart_poll_out, + .err_check = neorv32_uart_err_check, + .configure = neorv32_uart_configure, + .config_get = neorv32_uart_config_get, +#ifdef CONFIG_UART_INTERRUPT_DRIVEN + .fifo_fill = neorv32_uart_fifo_fill, + .fifo_read = neorv32_uart_fifo_read, + .irq_tx_enable = neorv32_uart_irq_tx_enable, + .irq_tx_disable = neorv32_uart_irq_tx_disable, + .irq_tx_ready = neorv32_uart_irq_tx_ready, + .irq_rx_enable = neorv32_uart_irq_rx_enable, + .irq_rx_disable = neorv32_uart_irq_rx_disable, + .irq_tx_complete = neorv32_uart_irq_tx_complete, + .irq_rx_ready = neorv32_uart_irq_rx_ready, + .irq_is_pending = neorv32_uart_irq_is_pending, + .irq_update = neorv32_uart_irq_update, + .irq_callback_set = neorv32_uart_irq_callback_set, +#endif /* CONFIG_UART_INTERRUPT_DRIVEN */ +}; + +#ifdef CONFIG_PM_DEVICE +#define NEORV32_UART_PM_CONTROL_FUNC neorv32_uart_pm_control +#else /* CONFIG_PM_DEVICE */ +#define NEORV32_UART_PM_CONTROL_FUNC NULL +#endif /* ! CONFIG_PM_DEVICE */ + +#ifdef CONFIG_UART_INTERRUPT_DRIVEN +#define NEORV32_UART_CONFIG_FUNC(node_id, n) \ + static void neorv32_uart_config_func_##n(const struct device *dev) \ + { \ + IRQ_CONNECT(DT_IRQ_BY_NAME(node_id, tx, irq), \ + DT_IRQ_BY_NAME(node_id, tx, priority), \ + neorv32_uart_isr, \ + DEVICE_DT_GET(node_id), 0); \ + \ + IRQ_CONNECT(DT_IRQ_BY_NAME(node_id, rx, irq), \ + DT_IRQ_BY_NAME(node_id, rx, priority), \ + neorv32_uart_isr, \ + DEVICE_DT_GET(node_id), 0); \ + } +#define NEORV32_UART_CONFIG_INIT(node_id, n) \ + .irq_config_func = neorv32_uart_config_func_##n, \ + .tx_irq = DT_IRQ_BY_NAME(node_id, tx, irq), \ + .rx_irq = DT_IRQ_BY_NAME(node_id, rx, irq), +#else +#define NEORV32_UART_CONFIG_FUNC(node_id, n) +#define NEORV32_UART_CONFIG_INIT(node_id, n) +#endif /* CONFIG_UART_INTERRUPT_DRIVEN */ + +#define NEORV32_UART_INIT(node_id, n) \ + NEORV32_UART_CONFIG_FUNC(node_id, n) \ + \ + static struct neorv32_uart_data neorv32_uart_##n##_data = { \ + .uart_cfg = { \ + .baudrate = DT_PROP(node_id, current_speed), \ + .parity = DT_ENUM_IDX_OR(node_id, parity, \ + UART_CFG_PARITY_NONE), \ + .stop_bits = UART_CFG_STOP_BITS_1, \ + .data_bits = UART_CFG_DATA_BITS_8, \ + .flow_ctrl = DT_PROP(node_id, hw_flow_control) ? \ + UART_CFG_FLOW_CTRL_RTS_CTS : \ + UART_CFG_FLOW_CTRL_NONE, \ + }, \ + }; \ + \ + static const struct neorv32_uart_config neorv32_uart_##n##_config = { \ + .syscon = DEVICE_DT_GET(DT_PHANDLE(node_id, syscon)), \ + .feature_mask = NEORV32_SYSINFO_FEATURES_IO_UART##n, \ + .base = DT_REG_ADDR(node_id), \ + NEORV32_UART_CONFIG_INIT(node_id, n) \ + }; \ + \ + DEVICE_DT_DEFINE(node_id, &neorv32_uart_init, \ + NEORV32_UART_PM_CONTROL_FUNC, \ + &neorv32_uart_##n##_data, \ + &neorv32_uart_##n##_config, \ + PRE_KERNEL_1, \ + CONFIG_UART_NEORV32_INIT_PRIORITY, \ + &neorv32_uart_driver_api) + +#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(uart0), DT_DRV_COMPAT, okay) +NEORV32_UART_INIT(DT_NODELABEL(uart0), 0); +#endif + +#if DT_NODE_HAS_COMPAT_STATUS(DT_NODELABEL(uart1), DT_DRV_COMPAT, okay) +NEORV32_UART_INIT(DT_NODELABEL(uart1), 1); +#endif diff --git a/dts/bindings/serial/neorv32-uart.yaml b/dts/bindings/serial/neorv32-uart.yaml new file mode 100644 index 00000000000..c60e6066b96 --- /dev/null +++ b/dts/bindings/serial/neorv32-uart.yaml @@ -0,0 +1,24 @@ +description: NEORV32 UART + +compatible: "neorv32-uart" + +include: uart-controller.yaml + +properties: + reg: + required: true + + interrupts: + required: true + + interrupt-names: + required: true + + current-speed: + required: true + + syscon: + type: phandle + required: true + description: | + phandle to syscon (NEORV32 SYSINFO) node.