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.