From b5b934a6ddcbbff0f13b532fd7701f242c1b806a Mon Sep 17 00:00:00 2001 From: Seppo Takalo Date: Fri, 12 Sep 2025 09:49:44 +0300 Subject: [PATCH] drivers: serial: Implement serial wrapper to add DTR This driver wraps an existing UART device and adds DTR (Data Terminal Ready) GPIO output for a runtime power management. When the UART is powered off, DTR is deasserted. When the UART is powered on, DTR is asserted. This allows remote end to shut down the UART when DTR is deasserted. Signed-off-by: Seppo Takalo --- drivers/serial/CMakeLists.txt | 1 + drivers/serial/Kconfig | 1 + drivers/serial/Kconfig.dtr | 26 ++ drivers/serial/uart_dtr.c | 513 +++++++++++++++++++++++ dts/bindings/serial/zephyr,uart-dtr.yaml | 47 +++ 5 files changed, 588 insertions(+) create mode 100644 drivers/serial/Kconfig.dtr create mode 100644 drivers/serial/uart_dtr.c create mode 100644 dts/bindings/serial/zephyr,uart-dtr.yaml diff --git a/drivers/serial/CMakeLists.txt b/drivers/serial/CMakeLists.txt index 63520ac12639d..206d2597326d3 100644 --- a/drivers/serial/CMakeLists.txt +++ b/drivers/serial/CMakeLists.txt @@ -32,6 +32,7 @@ zephyr_library_sources_ifdef(CONFIG_UART_CC23X0 uart_cc23x0.c) zephyr_library_sources_ifdef(CONFIG_UART_CC32XX uart_cc32xx.c) zephyr_library_sources_ifdef(CONFIG_UART_CDNS uart_cdns.c) zephyr_library_sources_ifdef(CONFIG_UART_CMSDK_APB uart_cmsdk_apb.c) +zephyr_library_sources_ifdef(CONFIG_UART_DTR uart_dtr.c) zephyr_library_sources_ifdef(CONFIG_UART_EFINIX_SAPPIHIRE uart_efinix_sapphire.c) zephyr_library_sources_ifdef(CONFIG_UART_EMUL uart_emul.c) zephyr_library_sources_ifdef(CONFIG_UART_ENE_KB106X uart_ene_kb106x.c) diff --git a/drivers/serial/Kconfig b/drivers/serial/Kconfig index 0d58009f31eb2..32755d317dae0 100644 --- a/drivers/serial/Kconfig +++ b/drivers/serial/Kconfig @@ -173,6 +173,7 @@ rsource "Kconfig.cc23x0" rsource "Kconfig.cc32xx" rsource "Kconfig.cdns" rsource "Kconfig.cmsdk_apb" +rsource "Kconfig.dtr" rsource "Kconfig.efinix_sapphire" rsource "Kconfig.emul" rsource "Kconfig.ene" diff --git a/drivers/serial/Kconfig.dtr b/drivers/serial/Kconfig.dtr new file mode 100644 index 0000000000000..ca8092dbfaeb5 --- /dev/null +++ b/drivers/serial/Kconfig.dtr @@ -0,0 +1,26 @@ +# DTR wrapper UART configuration options + +# Copyright (c) 2025 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +config UART_DTR + bool "UART DTR wrapper driver" + default y + depends on DT_HAS_ZEPHYR_UART_DTR_ENABLED + depends on GPIO + depends on PM_DEVICE_RUNTIME + help + Enable UART DTR wrapper driver. This driver wraps an existing UART + device and adds DTR (Data Terminal Ready) functionality for runtime + power management. When UART is powered, the DTR signal is asserted, + and when the UART is powered down, the DTR signal is de-asserted. + The driver acts as a child node of the actual UART. + +config UART_DTR_INIT_PRIORITY + int "UART DTR driver init priority" + default 60 + depends on UART_DTR + help + UART DTR wrapper driver initialization priority. This should be + higher than the underlying UART driver but lower than devices + that depend on it. Generally set higher than CONFIG_UART_INIT_PRIORITY. diff --git a/drivers/serial/uart_dtr.c b/drivers/serial/uart_dtr.c new file mode 100644 index 0000000000000..d081aeff16668 --- /dev/null +++ b/drivers/serial/uart_dtr.c @@ -0,0 +1,513 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * UART DTR Wrapper Driver + * + * This driver wraps an existing UART device and adds DTR (Data Terminal Ready) + * functionality for runtime power management. When the UART is powered off, DTR is deasserted. + * When the UART is powered on, DTR is asserted. + * This allows remote end to shut down the UART when DTR is deasserted. + */ + +#define DT_DRV_COMPAT zephyr_uart_dtr + +#include +#include +#include +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(uart_dtr, CONFIG_UART_LOG_LEVEL); + +/* Device configuration structure */ +struct uart_dtr_config { + struct gpio_dt_spec dtr_gpio; + const struct device *uart_dev; /* Parent UART device */ +}; + +/* Device data structure */ +struct uart_dtr_data { + bool uart_powered; + uart_callback_t user_callback; + void *user_data; + const struct uart_dtr_config *config; +}; + +/* Forward declarations */ +static int uart_dtr_configure(const struct device *dev, const struct uart_config *cfg); +static int uart_dtr_config_get(const struct device *dev, struct uart_config *cfg); + +static int uart_dtr_poll_in(const struct device *dev, unsigned char *c); +static void uart_dtr_poll_out(const struct device *dev, unsigned char c); + +static int uart_dtr_err_check(const struct device *dev); + +#ifdef CONFIG_UART_USE_RUNTIME_CONFIGURE +static int uart_dtr_configure(const struct device *dev, const struct uart_config *cfg); +#endif + +#ifdef CONFIG_UART_ASYNC_API +static int uart_dtr_callback_set(const struct device *dev, uart_callback_t callback, + void *user_data); +static int uart_dtr_tx(const struct device *dev, const uint8_t *buf, size_t len, int32_t timeout); +static int uart_dtr_tx_abort(const struct device *dev); +static int uart_dtr_rx_enable(const struct device *dev, uint8_t *buf, size_t len, int32_t timeout); +static int uart_dtr_rx_buf_rsp(const struct device *dev, uint8_t *buf, size_t len); +static int uart_dtr_rx_disable(const struct device *dev); +#endif + +static int power_on_uart(const struct device *dev) +{ + const struct uart_dtr_config *config = dev->config; + struct uart_dtr_data *data = dev->data; + int ret; + + /* Power on the UART */ + ret = pm_device_runtime_get(config->uart_dev); + if (ret < 0) { + LOG_ERR("Failed to power on UART: %d", ret); + return ret; + } + + data->uart_powered = true; + return 0; +} + +static int power_off_uart(const struct device *dev) +{ + const struct uart_dtr_config *config = dev->config; + struct uart_dtr_data *data = dev->data; + int ret; + + ret = pm_device_runtime_put_async(config->uart_dev, K_NO_WAIT); + + if (ret < 0) { + LOG_ERR("Failed to power off UART: %d", ret); + return ret; + } + + data->uart_powered = false; + return 0; +} + +static bool uart_dtr_is_powered(const struct device *dev) +{ + struct uart_dtr_data *data = dev->data; + + return data->uart_powered; +} + +/* UART API implementations */ +static int uart_dtr_poll_in(const struct device *dev, unsigned char *c) +{ + const struct uart_dtr_config *config = dev->config; + + if (!uart_dtr_is_powered(dev)) { + return -ENODATA; + } + + return uart_poll_in(config->uart_dev, c); +} + +static void uart_dtr_poll_out(const struct device *dev, unsigned char c) +{ + const struct uart_dtr_config *config = dev->config; + + if (!uart_dtr_is_powered(dev)) { + return; + } + + uart_poll_out(config->uart_dev, c); +} + +static int uart_dtr_err_check(const struct device *dev) +{ + const struct uart_dtr_config *config = dev->config; + + if (!uart_dtr_is_powered(dev)) { + return 0; + } + + return uart_err_check(config->uart_dev); +} + +#ifdef CONFIG_UART_INTERRUPT_DRIVEN +/* UART interrupt API wrappers */ +static int uart_dtr_fifo_fill(const struct device *dev, const uint8_t *tx_data, int len) +{ + const struct uart_dtr_config *config = dev->config; + + if (!uart_dtr_is_powered(dev)) { + return 0; + } + + return uart_fifo_fill(config->uart_dev, tx_data, len); +} + +static int uart_dtr_fifo_read(const struct device *dev, uint8_t *rx_data, const int size) +{ + const struct uart_dtr_config *config = dev->config; + + if (!uart_dtr_is_powered(dev)) { + return 0; + } + + return uart_fifo_read(config->uart_dev, rx_data, size); +} +static void uart_dtr_irq_tx_enable(const struct device *dev) +{ + const struct uart_dtr_config *config = dev->config; + + if (!uart_dtr_is_powered(dev)) { + return; + } + uart_irq_tx_enable(config->uart_dev); +} + +static void uart_dtr_irq_tx_disable(const struct device *dev) +{ + const struct uart_dtr_config *config = dev->config; + + if (!uart_dtr_is_powered(dev)) { + return; + } + uart_irq_tx_disable(config->uart_dev); +} + +static void uart_dtr_irq_rx_enable(const struct device *dev) +{ + const struct uart_dtr_config *config = dev->config; + + if (!uart_dtr_is_powered(dev)) { + return; + } + uart_irq_rx_enable(config->uart_dev); +} + +static void uart_dtr_irq_rx_disable(const struct device *dev) +{ + const struct uart_dtr_config *config = dev->config; + + if (!uart_dtr_is_powered(dev)) { + return; + } + uart_irq_rx_disable(config->uart_dev); +} + +static int uart_dtr_irq_tx_ready(const struct device *dev) +{ + const struct uart_dtr_config *config = dev->config; + + if (!uart_dtr_is_powered(dev)) { + return 0; + } + return uart_irq_tx_ready(config->uart_dev); +} + +static int uart_dtr_irq_rx_ready(const struct device *dev) +{ + const struct uart_dtr_config *config = dev->config; + + if (!uart_dtr_is_powered(dev)) { + return 0; + } + return uart_irq_rx_ready(config->uart_dev); +} + +static void uart_dtr_irq_err_enable(const struct device *dev) +{ + const struct uart_dtr_config *config = dev->config; + + if (!uart_dtr_is_powered(dev)) { + return; + } + uart_irq_err_enable(config->uart_dev); +} + +static void uart_dtr_irq_err_disable(const struct device *dev) +{ + const struct uart_dtr_config *config = dev->config; + + if (!uart_dtr_is_powered(dev)) { + return; + } + uart_irq_err_disable(config->uart_dev); +} + +static int uart_dtr_irq_is_pending(const struct device *dev) +{ + const struct uart_dtr_config *config = dev->config; + + if (!uart_dtr_is_powered(dev)) { + return 0; + } + return uart_irq_is_pending(config->uart_dev); +} + +static int uart_dtr_irq_update(const struct device *dev) +{ + const struct uart_dtr_config *config = dev->config; + + if (!uart_dtr_is_powered(dev)) { + return 0; + } + return uart_irq_update(config->uart_dev); +} + +static void uart_dtr_irq_callback_set(const struct device *dev, uart_irq_callback_user_data_t cb, + void *user_data) +{ + const struct uart_dtr_config *config = dev->config; + + if (!uart_dtr_is_powered(dev)) { + return; + } + uart_irq_callback_set(config->uart_dev, cb, user_data); +} +#endif /* CONFIG_UART_INTERRUPT_DRIVEN */ + +#ifdef CONFIG_UART_USE_RUNTIME_CONFIGURE +static int uart_dtr_configure(const struct device *dev, const struct uart_config *cfg) +{ + const struct uart_dtr_config *config = dev->config; + + if (!uart_dtr_is_powered(dev)) { + return -ENODEV; + } + + return uart_configure(config->uart_dev, cfg); +} + +static int uart_dtr_config_get(const struct device *dev, struct uart_config *cfg) +{ + const struct uart_dtr_config *config = dev->config; + + if (!uart_dtr_is_powered(dev)) { + return -ENODEV; + } + + return uart_config_get(config->uart_dev, cfg); +} +#endif + +#ifdef CONFIG_UART_ASYNC_API +static void uart_dtr_async_callback_wrapper(const struct device *dev, struct uart_event *evt, + void *user_data) +{ + const struct uart_dtr_config *config = dev->config; + struct uart_dtr_data *data = user_data; + + if (!data->user_callback) { + return; + } + + data->user_callback(config->uart_dev, evt, data->user_data); +} + +static int uart_dtr_callback_set(const struct device *dev, uart_callback_t callback, + void *user_data) +{ + const struct uart_dtr_config *config = dev->config; + struct uart_dtr_data *data = dev->data; + + data->user_callback = callback; + data->user_data = user_data; + + return uart_callback_set(config->uart_dev, uart_dtr_async_callback_wrapper, data); +} + +static int uart_dtr_tx(const struct device *dev, const uint8_t *buf, size_t len, int32_t timeout) +{ + const struct uart_dtr_config *config = dev->config; + + if (!uart_dtr_is_powered(dev)) { + return -EBUSY; + } + + return uart_tx(config->uart_dev, buf, len, timeout); +} + +static int uart_dtr_tx_abort(const struct device *dev) +{ + const struct uart_dtr_config *config = dev->config; + + if (!uart_dtr_is_powered(dev)) { + return -EBUSY; + } + + return uart_tx_abort(config->uart_dev); +} + +static int uart_dtr_rx_enable(const struct device *dev, uint8_t *buf, size_t len, int32_t timeout) +{ + const struct uart_dtr_config *config = dev->config; + + if (!uart_dtr_is_powered(dev)) { + return -EBUSY; + } + + return uart_rx_enable(config->uart_dev, buf, len, timeout); +} + +static int uart_dtr_rx_buf_rsp(const struct device *dev, uint8_t *buf, size_t len) +{ + const struct uart_dtr_config *config = dev->config; + + if (!uart_dtr_is_powered(dev)) { + return -EACCES; + } + + return uart_rx_buf_rsp(config->uart_dev, buf, len); +} + +static int uart_dtr_rx_disable(const struct device *dev) +{ + const struct uart_dtr_config *config = dev->config; + + if (!uart_dtr_is_powered(dev)) { + return -EFAULT; + } + + return uart_rx_disable(config->uart_dev); +} +#endif /* CONFIG_UART_ASYNC_API */ + +#ifdef CONFIG_UART_LINE_CTRL +static int uart_dtr_line_ctrl_set(const struct device *dev, uint32_t ctrl, uint32_t val) +{ + const struct uart_dtr_config *config = dev->config; + + if (ctrl == UART_LINE_CTRL_DTR) { + return gpio_pin_set_dt(&config->dtr_gpio, val ? 1 : 0); + } + + return uart_line_ctrl_set(config->uart_dev, ctrl, val); +} +#endif + +/* Power management */ +static int uart_dtr_pm_action(const struct device *dev, enum pm_device_action action) +{ + const struct uart_dtr_config *config = dev->config; + int ret; + + switch (action) { + case PM_DEVICE_ACTION_SUSPEND: + LOG_DBG("PM SUSPEND - Deasserting DTR"); + ret = gpio_pin_set_dt(&config->dtr_gpio, false); + if (ret < 0) { + LOG_ERR("Failed to set DTR GPIO: %d", ret); + return ret; + } + return power_off_uart(dev); + case PM_DEVICE_ACTION_RESUME: + LOG_DBG("PM RESUME - Asserting DTR"); + ret = gpio_pin_set_dt(&config->dtr_gpio, true); + if (ret < 0) { + LOG_ERR("Failed to set DTR GPIO: %d", ret); + return ret; + } + return power_on_uart(dev); + default: + return -ENOTSUP; + } +} + +/* Initialization */ +static int uart_dtr_init(const struct device *dev) +{ + const struct uart_dtr_config *config = dev->config; + struct uart_dtr_data *data = dev->data; + int ret; + + if (!config->uart_dev) { + LOG_ERR("Parent UART device not found"); + return -ENODEV; + } + + if (!device_is_ready(config->uart_dev)) { + LOG_ERR("Parent UART device not ready"); + return -ENODEV; + } + + if (!gpio_is_ready_dt(&config->dtr_gpio)) { + LOG_ERR("DTR GPIO not ready"); + return -ENODEV; + } + + /* Configure DTR GPIO as output, initially inactive */ + ret = gpio_pin_configure_dt(&config->dtr_gpio, GPIO_OUTPUT_INACTIVE); + if (ret < 0) { + LOG_ERR("Failed to configure DTR GPIO: %d", ret); + return ret; + } + + /* Initialize data */ + *data = (struct uart_dtr_data){ + .config = config, + }; + + pm_device_init_suspended(dev); + return 0; +} + +/* UART driver API */ +static const struct uart_driver_api uart_dtr_driver_api = { + .poll_in = uart_dtr_poll_in, + .poll_out = uart_dtr_poll_out, + .err_check = uart_dtr_err_check, +#ifdef CONFIG_UART_USE_RUNTIME_CONFIGURE + .configure = uart_dtr_configure, + .config_get = uart_dtr_config_get, +#endif +#ifdef CONFIG_UART_ASYNC_API + .callback_set = uart_dtr_callback_set, + .tx = uart_dtr_tx, + .tx_abort = uart_dtr_tx_abort, + .rx_enable = uart_dtr_rx_enable, + .rx_buf_rsp = uart_dtr_rx_buf_rsp, + .rx_disable = uart_dtr_rx_disable, +#endif +#ifdef CONFIG_UART_INTERRUPT_DRIVEN + .fifo_fill = uart_dtr_fifo_fill, + .fifo_read = uart_dtr_fifo_read, + .irq_tx_enable = uart_dtr_irq_tx_enable, + .irq_tx_disable = uart_dtr_irq_tx_disable, + .irq_rx_enable = uart_dtr_irq_rx_enable, + .irq_rx_disable = uart_dtr_irq_rx_disable, + .irq_tx_ready = uart_dtr_irq_tx_ready, + .irq_rx_ready = uart_dtr_irq_rx_ready, + .irq_err_enable = uart_dtr_irq_err_enable, + .irq_err_disable = uart_dtr_irq_err_disable, + .irq_is_pending = uart_dtr_irq_is_pending, + .irq_update = uart_dtr_irq_update, + .irq_callback_set = uart_dtr_irq_callback_set, +#endif +#ifdef CONFIG_UART_LINE_CTRL + .line_ctrl_set = uart_dtr_line_ctrl_set, +#endif +}; + +/* Device macro */ +#define UART_DTR_INIT(n) \ + static const struct uart_dtr_config uart_dtr_config_##n = { \ + .dtr_gpio = GPIO_DT_SPEC_INST_GET(n, dtr_gpios), \ + .uart_dev = DEVICE_DT_GET(DT_PARENT(DT_DRV_INST(n))), \ + }; \ + \ + static struct uart_dtr_data uart_dtr_data_##n; \ + \ + PM_DEVICE_DT_INST_DEFINE(n, uart_dtr_pm_action); \ + \ + DEVICE_DT_INST_DEFINE(n, uart_dtr_init, PM_DEVICE_DT_INST_GET(n), &uart_dtr_data_##n, \ + &uart_dtr_config_##n, POST_KERNEL, CONFIG_UART_DTR_INIT_PRIORITY, \ + &uart_dtr_driver_api); + +DT_INST_FOREACH_STATUS_OKAY(UART_DTR_INIT) diff --git a/dts/bindings/serial/zephyr,uart-dtr.yaml b/dts/bindings/serial/zephyr,uart-dtr.yaml new file mode 100644 index 0000000000000..33fc9d9b312dc --- /dev/null +++ b/dts/bindings/serial/zephyr,uart-dtr.yaml @@ -0,0 +1,47 @@ +description: | + DTR output for a UART. + + This driver wraps an existing UART device and adds DTR (Data Terminal Ready) functionality + which follows the runtime power state. + + When UART is powered, the DTR signal is asserted (logical high), + and when the UART is powered down, the DTR signal is de-asserted (logical low). + This allows a remote end to detect whether the UART is powered or not and manage + its own power state accordingly. + + The driver acts as a child node of the actual UART. The application should use the DTR UART device + which will manage the DTR signal and forward all UART operations to the parent UART. + + Example where DTR state follows the runtime power state of another device that uses UART: + &uart1 { + zephyr,pm-device-runtime-auto; + dtr_uart: dtr_uart { + compatible = "zephyr,uart-dtr"; + dtr-gpios = <&gpio0 15 GPIO_ACTIVE_LOW>; + zephyr,pm-device-runtime-auto; + + modem: modem { + /* Can be any device that uses UART and supports PM_DEVICE_RUNTIME */ + zephyr,pm-device-runtime-auto; + }; + }; + }; + + In the example above, zephyr,pm-device-runtime-auto is used to enable runtime power + management for devices when they are initialized. + DTR GPIO is typically active low but this depends on the HW design. + + +compatible: "zephyr,uart-dtr" + +include: base.yaml + +on-bus: uart +bus: uart + +properties: + dtr-gpios: + type: phandle-array + required: true + description: | + DTR (Data Terminal Ready) GPIO pin.