diff --git a/drivers/serial/uart_mspm0.c b/drivers/serial/uart_mspm0.c index f95346a4894c..9b88723210a0 100644 --- a/drivers/serial/uart_mspm0.c +++ b/drivers/serial/uart_mspm0.c @@ -28,6 +28,9 @@ #include #include #include +#include +#include +#include /* * This driver uses its own native register definitions (mspm0_uart_regs and @@ -89,6 +92,7 @@ #define UART_STAT_TXFE_MASK GENMASK(6, 6) #define UART_STAT_TXFF_MASK GENMASK(7, 7) #define UART_STAT_RXFE_MASK GENMASK(2, 2) +#define UART_STAT_BUSY_MASK GENMASK(0, 0) /* * IFLS Interrupt FIFO Level Select Register @@ -137,12 +141,14 @@ #endif /* !CONFIG_HAS_MSPM0_SDK */ -#define UART_IFLS_TXIFLSEL_LVL_1_ENTRY FIELD_PREP(UART_IFLS_TXIFLSEL_MASK, 7) +#define UART_IFLS_TXIFLSEL_LVL_1_ENTRY FIELD_PREP(UART_IFLS_TXIFLSEL_MASK, 7) #define UART_IFLS_UNICOMM_TXIFLSEL_LVL_1_ENTRY FIELD_PREP(UART_IFLS_TXIFLSEL_MASK, 4) -#define UART_IFLS_RXIFLSEL_LVL_1_ENTRY UART_IFLS_TXIFLSEL_LVL_1_ENTRY +#define UART_IFLS_RXIFLSEL_LVL_1_ENTRY UART_IFLS_TXIFLSEL_LVL_1_ENTRY #define UART_IFLS_UNICOMM_RXIFLSEL_LVL_1_ENTRY UART_IFLS_UNICOMM_TXIFLSEL_LVL_1_ENTRY +#define UART_MSPM0_RX_FIFO_DEPTH 4U + /* * UART register-map structs. */ @@ -239,7 +245,7 @@ struct uart_mspm0_config { * Grouped here so they can be accessed as data->uart_config.parity, etc. */ struct uart_mspm0_line_config { - uint32_t parity; /* LCRH parity bits */ + uint32_t parity; /* LCRH parity bits */ uint32_t word_length; /* LCRH WLEN field */ uint32_t stop_bits; /* LCRH STP2 field */ uint32_t flow_control; /* CTL0 RTSEN/CTSEN bits */ @@ -250,6 +256,10 @@ struct uart_mspm0_data { uint32_t current_speed; /* UART line configuration - raw register field values */ struct uart_mspm0_line_config uart_config; +#ifdef CONFIG_PM_DEVICE + /* Maximum time needed to transmit one frame. */ + uint32_t frame_time_us; +#endif #ifdef CONFIG_UART_INTERRUPT_DRIVEN /* Callback function pointer */ uart_irq_callback_user_data_t cb; @@ -291,6 +301,15 @@ static const uint32_t uart_mspm0_tx_fifo_level[] = { }; #endif /* CONFIG_UART_INTERRUPT_DRIVEN */ +static inline int uart_mspm0_pm_is_active(const struct device *dev) +{ + enum pm_device_state state = PM_DEVICE_STATE_OFF; + int err; + + err = pm_device_state_get(dev, &state); + return state == PM_DEVICE_STATE_ACTIVE || err < 0; +} + static inline void uart_mspm0_set_clock(mspm0_uart_regs *regs, uint32_t clk_sel, uint32_t clk_div) { regs->clksel = clk_sel; @@ -299,9 +318,13 @@ static inline void uart_mspm0_set_clock(mspm0_uart_regs *regs, uint32_t clk_sel, /* Configure line control: parity, word length, stop bits */ static void uart_mspm0_configure_line(mspm0_uart_regs *regs, uint32_t parity, uint32_t word_length, - uint32_t stop_bits, uint32_t flow_control) + uint32_t stop_bits, uint32_t flow_control, bool unicommuart) { + /* Standard UART instances operate in FIFO mode. Configure FEN together with + * the other CTL0 fields to avoid transitioning through character mode. + */ uint32_t ctl0 = UART_CTL0_MODE_UART | UART_CTL0_RXE_ENABLE | UART_CTL0_TXE_ENABLE | + (unicommuart ? 0U : UART_CTL0_FEN_ENABLE) | (flow_control & (UART_CTL0_RTSEN_MASK | UART_CTL0_CTSEN_MASK)); uint32_t lcrh = (parity & (UART_LCRH_PEN_MASK | UART_LCRH_EPS_MASK | UART_LCRH_SPS_MASK)) | @@ -356,7 +379,7 @@ static int uart_mspm0_poll_in(const struct device *dev, unsigned char *c) const struct uart_mspm0_config *config = dev->config; mspm0_uart_regs *regs = config->regs; - if ((regs->stat & UART_STAT_RXFE_MASK) != 0U) { + if (uart_mspm0_pm_is_active(dev) == 0U || (regs->stat & UART_STAT_RXFE_MASK) != 0U) { return -1; } @@ -370,12 +393,25 @@ static void uart_mspm0_poll_out(const struct device *dev, unsigned char c) mspm0_uart_regs *regs = config->regs; unsigned int key; + if (pm_device_runtime_get(dev) < 0) { + return; + } + pm_policy_device_power_lock_get(dev); + + /* Runtime PM resumes an opted-in device. Discard transmission if the device + * was suspended directly through the device PM API, preventing data from + * being trapped in the TX FIFO and unexpectedly transmitted after resume. + */ + if (uart_mspm0_pm_is_active(dev) == 0U) { + goto out; + } + /* Skip the irq_lock()s below when in ISR context. */ if (k_is_in_isr()) { while ((regs->stat & config->stat_txff_mask) != 0U) { } regs->txdata = (uint32_t)c; - return; + goto out; } /* Wait until TX FIFO has space, then write atomically. @@ -390,22 +426,107 @@ static void uart_mspm0_poll_out(const struct device *dev, unsigned char c) if ((regs->stat & config->stat_txff_mask) == 0U) { regs->txdata = (uint32_t)c; irq_unlock(key); - return; + break; } irq_unlock(key); } while (true); + +out: + pm_policy_device_power_lock_put(dev); + (void)pm_device_runtime_put(dev); } +#if defined(CONFIG_PM_DEVICE) + +static uint32_t uart_mspm0_frame_time_us(const struct uart_mspm0_data *data) +{ + uint32_t bits; + + bits = 1U; /* Start bit */ + bits += FIELD_GET(UART_LCRH_WLEN_MASK, data->uart_config.word_length) + 5U; + bits += (data->uart_config.parity & UART_LCRH_PEN_MASK) != 0U; + bits += (data->uart_config.stop_bits & UART_LCRH_STP2_MASK) != 0U ? 2U : 1U; + + return DIV_ROUND_UP(bits * USEC_PER_SEC, data->current_speed) + 10U; +} + +static int uart_mspm0_suspend_hw(const struct device *dev) +{ + const struct uart_mspm0_config *config = dev->config; + int err; + + /* Move pins to sleep state. */ + err = pinctrl_apply_state(config->pinctrl, PINCTRL_STATE_SLEEP); + if ((err < 0) && (err != -ENOENT)) { + return err; + } + + /* Unselect all parent clocks, effectively gating the entire peripheral. */ + uart_mspm0_set_clock(config->regs, 0U, config->clk_div); + + return 0; +} + +static int uart_mspm_pm_action(const struct device *dev, enum pm_device_action action) +{ + const struct uart_mspm0_data *data = dev->data; + int err; + const struct uart_mspm0_config *config = dev->config; + + switch (action) { + case PM_DEVICE_ACTION_RESUME: + /* Set pins to active state */ + err = pinctrl_apply_state(config->pinctrl, PINCTRL_STATE_DEFAULT); + if (err < 0) { + return err; + } + + /* Restore selection of parent clock that may been previously gated */ + uart_mspm0_set_clock(config->regs, config->clk_sel, config->clk_div); + + /* Flush the RX FIFO after the peripheral clock has been restored. */ + for (uint32_t i = 0U; (i < UART_MSPM0_RX_FIFO_DEPTH) && + ((config->regs->stat & UART_STAT_RXFE_MASK) == 0U); + i++) { + (void)config->regs->rxdata; + } + + break; + case PM_DEVICE_ACTION_SUSPEND: + /* Wait until all queued data has moved out of the TX FIFO. */ + while ((config->regs->stat & config->stat_txfe_mask) == 0U) { + } + + /* Wait until frame is fully transmitted and the TX line is idle. + * BUSY flag is also for RX so don't wait indefinitely for it. + */ + (void)WAIT_FOR((config->regs->stat & UART_STAT_BUSY_MASK) == 0U, + data->frame_time_us, k_busy_wait(1)); + + err = uart_mspm0_suspend_hw(dev); + if (err < 0) { + return err; + } + + break; + default: + return -ENOTSUP; + } + + return 0; +} +#endif + static int uart_mspm0_install_configuration(const struct device *dev) { const struct device *clk_dev = DEVICE_DT_GET(DT_NODELABEL(ckm)); const struct uart_mspm0_config *config = dev->config; struct uart_mspm0_data *data = dev->data; + const bool unicommuart = config->skip_power_on; #ifdef CONFIG_UART_INTERRUPT_DRIVEN uint32_t rx_level; uint32_t tx_level; #endif - const bool unicommuart = config->skip_power_on; uint32_t clock_rate; uint32_t uart_clk; int ret; @@ -416,7 +537,7 @@ static int uart_mspm0_install_configuration(const struct device *dev) /* Configure line control */ uart_mspm0_configure_line(config->regs, data->uart_config.parity, data->uart_config.word_length, data->uart_config.stop_bits, - data->uart_config.flow_control); + data->uart_config.flow_control, unicommuart); /* Get source clock rate */ ret = clock_control_get_rate(clk_dev, (struct mspm0_sys_clock *)config->clock_subsys, @@ -432,14 +553,9 @@ static int uart_mspm0_install_configuration(const struct device *dev) /* Configure baud rate */ uart_mspm0_configure_baudrate(config->regs, uart_clk, data->current_speed); - /* Re-enable FIFOs: uart_mspm0_configure_line() overwrites CTL0 entirely, - * clearing the FEN bit. Always re-enable FIFOs after reconfiguring the - * line control registers so that FIFO-based interrupt thresholds (IFLS) - * remain operative. The UNICOMMUART has no FEN bit. - */ - if (!unicommuart) { - config->regs->ctl0 |= UART_CTL0_FEN_ENABLE; - } +#ifdef CONFIG_PM_DEVICE + data->frame_time_us = uart_mspm0_frame_time_us(data); +#endif #ifdef CONFIG_UART_INTERRUPT_DRIVEN /* Restore FIFO interrupt level select thresholds. These are also wiped @@ -536,54 +652,64 @@ static int uart_mspm0_configure(const struct device *dev, const struct uart_conf { const struct uart_mspm0_config *config = dev->config; struct uart_mspm0_data *data = dev->data; - uint32_t value; + struct uart_mspm0_line_config new_config; + struct uart_mspm0_line_config previous_config; + uint32_t previous_speed; int ret; - UART_MSPM0_DISABLE(config->regs); - - data->current_speed = cfg->baudrate; + if (cfg->baudrate == 0U) { + return -EINVAL; + } ret = uart_mspm0_translate_in(uart_parity_to_mspm0, ARRAY_SIZE(uart_parity_to_mspm0), - cfg->parity, &value); + cfg->parity, &new_config.parity); if (ret != 0) { return ret; } - data->uart_config.parity = value; - ret = uart_mspm0_translate_in(uart_stop_bits_to_mspm0, ARRAY_SIZE(uart_stop_bits_to_mspm0), - cfg->stop_bits, &value); + cfg->stop_bits, &new_config.stop_bits); if (ret != 0) { return ret; } - data->uart_config.stop_bits = value; - ret = uart_mspm0_translate_in(uart_data_bits_to_mspm0, ARRAY_SIZE(uart_data_bits_to_mspm0), - cfg->data_bits, &value); + cfg->data_bits, &new_config.word_length); if (ret != 0) { return ret; } - data->uart_config.word_length = value; - ret = uart_mspm0_translate_in(uart_flow_control_to_mspm0, ARRAY_SIZE(uart_flow_control_to_mspm0), cfg->flow_ctrl, - &value); + &new_config.flow_control); if (ret != 0) { return ret; } - data->uart_config.flow_control = value; + ret = pm_device_runtime_get(dev); + if (ret < 0) { + return ret; + } + + UART_MSPM0_DISABLE(config->regs); + + previous_speed = data->current_speed; + previous_config = data->uart_config; + data->current_speed = cfg->baudrate; + data->uart_config = new_config; ret = uart_mspm0_install_configuration(dev); if (ret != 0) { - return ret; + data->current_speed = previous_speed; + data->uart_config = previous_config; + (void)uart_mspm0_install_configuration(dev); + goto out; } +out: UART_MSPM0_ENABLE(config->regs); - - return 0; + (void)pm_device_runtime_put(dev); + return ret; } static int uart_mspm0_config_get(const struct device *dev, struct uart_config *cfg) @@ -645,6 +771,13 @@ static int uart_mspm0_fifo_fill(const struct device *dev, const uint8_t *tx_data const struct uart_mspm0_config *config = dev->config; int count = 0; + /* Discard transmission if the peripheral is inactive. This prevents data from + * getting trapped in the TX buffer and unexpectedly transmitting later upon wake-up. + */ + if (uart_mspm0_pm_is_active(dev) == 0U) { + return 0; + } + while (count < size && ((config->regs->stat & config->stat_txff_mask) == 0)) { config->regs->txdata = tx_data[count]; count++; @@ -658,6 +791,10 @@ static int uart_mspm0_fifo_read(const struct device *dev, uint8_t *rx_data, cons const struct uart_mspm0_config *config = dev->config; int count = 0; + if (uart_mspm0_pm_is_active(dev) == 0U) { + return 0; + } + while (count < size && ((config->regs->stat & UART_STAT_RXFE_MASK) == 0)) { rx_data[count] = (uint8_t)(config->regs->rxdata & 0xFF); count++; @@ -670,7 +807,13 @@ static void uart_mspm0_irq_tx_enable(const struct device *dev) { const struct uart_mspm0_config *config = dev->config; - config->regs->cpu_int.imask |= UART_MSPM0_TX_INTERRUPTS; + if ((config->regs->cpu_int.imask & UART_MSPM0_TX_INTERRUPTS) == 0U) { + if (pm_device_runtime_get(dev) < 0) { + return; + } + pm_policy_device_power_lock_get(dev); + config->regs->cpu_int.imask |= UART_MSPM0_TX_INTERRUPTS; + } /* If the TX FIFO is already empty (below the LVL_EMPTY threshold) the * TXIFG flag may have been cleared by a prior IIDX read without a new @@ -687,7 +830,11 @@ static void uart_mspm0_irq_tx_disable(const struct device *dev) { const struct uart_mspm0_config *config = dev->config; - config->regs->cpu_int.imask &= ~UART_MSPM0_TX_INTERRUPTS; + if ((config->regs->cpu_int.imask & UART_MSPM0_TX_INTERRUPTS) != 0U) { + config->regs->cpu_int.imask &= ~UART_MSPM0_TX_INTERRUPTS; + pm_policy_device_power_lock_put(dev); + (void)pm_device_runtime_put(dev); + } } static int uart_mspm0_irq_tx_ready(const struct device *dev) @@ -703,7 +850,13 @@ static void uart_mspm0_irq_rx_enable(const struct device *dev) { const struct uart_mspm0_config *config = dev->config; - config->regs->cpu_int.imask |= UART_MSPM0_RX_INTERRUPTS; + if ((config->regs->cpu_int.imask & UART_MSPM0_RX_INTERRUPTS) == 0U) { + if (pm_device_runtime_get(dev) < 0) { + return; + } + pm_policy_device_power_lock_get(dev); + config->regs->cpu_int.imask |= UART_MSPM0_RX_INTERRUPTS; + } /* If the RX FIFO already contains data the RXIFG flag may have been * cleared by a prior IIDX read while the interrupt was masked, without @@ -720,7 +873,11 @@ static void uart_mspm0_irq_rx_disable(const struct device *dev) { const struct uart_mspm0_config *config = dev->config; - config->regs->cpu_int.imask &= ~UART_MSPM0_RX_INTERRUPTS; + if ((config->regs->cpu_int.imask & UART_MSPM0_RX_INTERRUPTS) != 0U) { + config->regs->cpu_int.imask &= ~UART_MSPM0_RX_INTERRUPTS; + pm_policy_device_power_lock_put(dev); + (void)pm_device_runtime_put(dev); + } } static int uart_mspm0_irq_tx_complete(const struct device *dev) @@ -769,14 +926,24 @@ static void uart_mspm0_irq_error_enable(const struct device *dev) { const struct uart_mspm0_config *config = dev->config; - config->regs->cpu_int.imask |= UART_MSPM0_ERROR_INTERRUPTS; + if ((config->regs->cpu_int.imask & UART_MSPM0_ERROR_INTERRUPTS) == 0U) { + if (pm_device_runtime_get(dev) < 0) { + return; + } + pm_policy_device_power_lock_get(dev); + config->regs->cpu_int.imask |= UART_MSPM0_ERROR_INTERRUPTS; + } } static void uart_mspm0_irq_error_disable(const struct device *dev) { const struct uart_mspm0_config *config = dev->config; - config->regs->cpu_int.imask &= ~UART_MSPM0_ERROR_INTERRUPTS; + if ((config->regs->cpu_int.imask & UART_MSPM0_ERROR_INTERRUPTS) != 0U) { + config->regs->cpu_int.imask &= ~UART_MSPM0_ERROR_INTERRUPTS; + pm_policy_device_power_lock_put(dev); + (void)pm_device_runtime_put(dev); + } } static void uart_mspm0_isr(const struct device *dev) @@ -821,7 +988,21 @@ static int uart_mspm0_init(const struct device *dev) /* Enable UART */ UART_MSPM0_ENABLE(config->regs); +#if defined(CONFIG_PM_DEVICE) + /* + * pm_device_driver_init() expects runtime-managed hardware to start suspended. + * No transmission can be pending during initialization, and timer-based waits + * are not available yet because this driver initializes at PRE_KERNEL_1. + */ + ret = uart_mspm0_suspend_hw(dev); + if (ret < 0) { + return ret; + } + + return pm_device_driver_init(dev, uart_mspm_pm_action); +#else return 0; +#endif } static DEVICE_API(uart, uart_mspm0_driver_api) = { @@ -864,15 +1045,16 @@ static DEVICE_API(uart, uart_mspm0_driver_api) = { #define MSPM0_CLK_DIV_REG(n) (DT_INST_PROP(n, clk_div) - 1) +/* clang-format off */ #define MSPM0_UART_INIT_FN(index) \ - \ + \ PINCTRL_DT_INST_DEFINE(index); \ - \ + \ static const struct mspm0_sys_clock mspm0_uart_sys_clock##index = \ MSPM0_CLOCK_SUBSYS_FN(index); \ - \ + \ MSP_UART_IRQ_DEFINE(index); \ - \ + \ static const struct uart_mspm0_config uart_mspm0_cfg_##index = { \ .regs = (mspm0_uart_regs *)(DT_INST_REG_ADDR(index) - MSPM0_UART_REGS_OFFSET), \ .pinctrl = PINCTRL_DT_INST_DEV_CONFIG_GET(index), \ @@ -880,10 +1062,10 @@ static DEVICE_API(uart, uart_mspm0_driver_api) = { .clk_sel = MSPM0_CLOCK_PERIPH_REG_MASK(DT_INST_CLOCKS_CELL(index, clk)), \ .clk_div = MSPM0_CLK_DIV_REG(index), \ .stat_txff_mask = COND_CODE_1(DT_INST_PROP(index, is_unicomm_uart), \ - (UART_STAT_UNICOMM_TXFF_MASK), \ + (UART_STAT_UNICOMM_TXFF_MASK), \ (UART_STAT_TXFF_MASK)), \ .stat_txfe_mask = COND_CODE_1(DT_INST_PROP(index, is_unicomm_uart), \ - (UART_STAT_UNICOMM_TXFE_MASK), \ + (UART_STAT_UNICOMM_TXFE_MASK), \ (UART_STAT_TXFE_MASK)), \ .skip_power_on = COND_CODE_1(DT_INST_PROP(index, is_unicomm_uart), \ (true), \ @@ -904,7 +1086,7 @@ static DEVICE_API(uart, uart_mspm0_driver_api) = { .parity = 0, \ .word_length = UART_LCRH_WLEN_DATABIT8, \ .stop_bits = UART_LCRH_STP2_DISABLE, \ - .flow_control = (DT_INST_PROP(index, hw_flow_control) \ + .flow_control = (DT_INST_PROP(index, hw_flow_control) \ ? (UART_CTL0_RTSEN_ENABLE | \ UART_CTL0_CTSEN_ENABLE) \ : (UART_CTL0_RTSEN_DISABLE | \ @@ -912,8 +1094,11 @@ static DEVICE_API(uart, uart_mspm0_driver_api) = { }, \ }; \ \ - DEVICE_DT_INST_DEFINE(index, &uart_mspm0_init, NULL, &uart_mspm0_data_##index, \ + PM_DEVICE_DT_INST_DEFINE(index, uart_mspm_pm_action); \ + DEVICE_DT_INST_DEFINE(index, &uart_mspm0_init, PM_DEVICE_DT_INST_GET(index), \ + &uart_mspm0_data_##index, \ &uart_mspm0_cfg_##index, PRE_KERNEL_1, \ CONFIG_SERIAL_INIT_PRIORITY, &uart_mspm0_driver_api); +/* clang-format on */ DT_INST_FOREACH_STATUS_OKAY(MSPM0_UART_INIT_FN) diff --git a/dts/arm/ti/mspm0/g/mspm0g.dtsi b/dts/arm/ti/mspm0/g/mspm0g.dtsi index 1fcf7d388074..a55f1337b86e 100644 --- a/dts/arm/ti/mspm0/g/mspm0g.dtsi +++ b/dts/arm/ti/mspm0/g/mspm0g.dtsi @@ -231,6 +231,7 @@ current-speed = <115200>; clocks = <&ckm MSPM0_CLOCK_ULPCLK>; clk-div = <1>; + zephyr,disabling-power-states = <&stdby1>; status = "disabled"; }; @@ -241,6 +242,7 @@ current-speed = <115200>; clocks = <&ckm MSPM0_CLOCK_ULPCLK>; clk-div = <1>; + zephyr,disabling-power-states = <&stdby1>; status = "disabled"; }; @@ -251,6 +253,7 @@ current-speed = <115200>; clocks = <&ckm MSPM0_CLOCK_ULPCLK>; clk-div = <1>; + zephyr,disabling-power-states = <&stdby1>; status = "disabled"; }; diff --git a/dts/arm/ti/mspm0/g/mspm0g110x.dtsi b/dts/arm/ti/mspm0/g/mspm0g110x.dtsi index 7662a074329d..fedc9fadbc13 100644 --- a/dts/arm/ti/mspm0/g/mspm0g110x.dtsi +++ b/dts/arm/ti/mspm0/g/mspm0g110x.dtsi @@ -1,3 +1,11 @@ -/* SPDX-License-Identifier: Apache-2.0 */ +/* + * SPDX-FileCopyrightText: 2026 Texas Instruments Incorporated + * + * SPDX-License-Identifier: Apache-2.0 + */ #include + +&uart3 { + zephyr,disabling-power-states = <&stop0 &stop1 &stop2 &stdby0 &stdby1>; +}; diff --git a/dts/arm/ti/mspm0/g/mspm0g150x.dtsi b/dts/arm/ti/mspm0/g/mspm0g150x.dtsi index dd4f526a9b1f..d073c1bf4810 100644 --- a/dts/arm/ti/mspm0/g/mspm0g150x.dtsi +++ b/dts/arm/ti/mspm0/g/mspm0g150x.dtsi @@ -6,6 +6,10 @@ #include +&uart3 { + zephyr,disabling-power-states = <&stop0 &stop1 &stop2 &stdby0 &stdby1>; +}; + / { soc { dac0: dac@40018000 { diff --git a/dts/arm/ti/mspm0/g/mspm0g310x.dtsi b/dts/arm/ti/mspm0/g/mspm0g310x.dtsi index ae1f984363cb..b2ace4b1cfcf 100644 --- a/dts/arm/ti/mspm0/g/mspm0g310x.dtsi +++ b/dts/arm/ti/mspm0/g/mspm0g310x.dtsi @@ -1,7 +1,15 @@ -/* SPDX-License-Identifier: Apache-2.0 */ +/* + * SPDX-FileCopyrightText: 2026 Texas Instruments Incorporated + * + * SPDX-License-Identifier: Apache-2.0 + */ #include +&uart3 { + zephyr,disabling-power-states = <&stop0 &stop1 &stop2 &stdby0 &stdby1>; +}; + / { soc { aes: aes@40442000 { diff --git a/dts/arm/ti/mspm0/g/mspm0gx51x.dtsi b/dts/arm/ti/mspm0/g/mspm0gx51x.dtsi index 45062c5b0ebd..fecfb57a17dd 100644 --- a/dts/arm/ti/mspm0/g/mspm0gx51x.dtsi +++ b/dts/arm/ti/mspm0/g/mspm0gx51x.dtsi @@ -229,3 +229,7 @@ <&gpio_none>, <&gpio_none>; }; + +&uart3 { + zephyr,disabling-power-states = <&stop0 &stop1 &stop2 &stdby0 &stdby1>; +}; diff --git a/dts/arm/ti/mspm0/l/mspm0l110x.dtsi b/dts/arm/ti/mspm0/l/mspm0l110x.dtsi index 258a24cbd44f..925388ab859b 100644 --- a/dts/arm/ti/mspm0/l/mspm0l110x.dtsi +++ b/dts/arm/ti/mspm0/l/mspm0l110x.dtsi @@ -59,6 +59,7 @@ current-speed = <115200>; clocks = <&ckm MSPM0_CLOCK_ULPCLK>; clk-div = <1>; + zephyr,disabling-power-states = <&stdby1>; status = "disabled"; }; }; diff --git a/dts/arm/ti/mspm0/l/mspm0lx22x.dtsi b/dts/arm/ti/mspm0/l/mspm0lx22x.dtsi index 6d8588597c54..a35ef2194f52 100644 --- a/dts/arm/ti/mspm0/l/mspm0lx22x.dtsi +++ b/dts/arm/ti/mspm0/l/mspm0lx22x.dtsi @@ -173,6 +173,7 @@ current-speed = <9600>; clocks = <&ckm MSPM0_CLOCK_ULPCLK>; clk-div = <1>; + zephyr,disabling-power-states = <&stdby1>; status = "disabled"; }; @@ -183,6 +184,7 @@ current-speed = <9600>; clocks = <&ckm MSPM0_CLOCK_ULPCLK>; clk-div = <1>; + zephyr,disabling-power-states = <&stdby1>; status = "disabled"; }; @@ -193,6 +195,7 @@ current-speed = <9600>; clocks = <&ckm MSPM0_CLOCK_ULPCLK>; clk-div = <1>; + zephyr,disabling-power-states = <&stdby1>; status = "disabled"; }; @@ -203,6 +206,7 @@ current-speed = <9600>; clocks = <&ckm MSPM0_CLOCK_ULPCLK>; clk-div = <1>; + zephyr,disabling-power-states = <&stdby1>; status = "disabled"; }; diff --git a/dts/arm/ti/mspm0/mspm0.dtsi b/dts/arm/ti/mspm0/mspm0.dtsi index c6568d08b642..39247b7800d7 100644 --- a/dts/arm/ti/mspm0/mspm0.dtsi +++ b/dts/arm/ti/mspm0/mspm0.dtsi @@ -264,6 +264,7 @@ current-speed = <115200>; clocks = <&ckm MSPM0_CLOCK_ULPCLK>; clk-div = <1>; + zephyr,disabling-power-states = <&stdby1>; status = "disabled"; }; diff --git a/subsys/pm/device_runtime.c b/subsys/pm/device_runtime.c index 12688bcbdec5..9752871ad8af 100644 --- a/subsys/pm/device_runtime.c +++ b/subsys/pm/device_runtime.c @@ -21,6 +21,13 @@ LOG_MODULE_DECLARE(pm_device, CONFIG_PM_DEVICE_LOG_LEVEL); #define PM_DOMAIN(_pm) NULL #endif +/* + * Serializes all pm->base.usage updates for devices without the ISR-safe + * flag. The get/put fast paths read and update the usage counter under this + * lock, possibly from interrupt context, so the slow paths must not modify + * it under the per-device semaphore alone. + */ +static struct k_spinlock lock; #ifdef CONFIG_PM_DEVICE_RUNTIME_ASYNC #ifdef CONFIG_PM_DEVICE_RUNTIME_USE_DEDICATED_WQ K_THREAD_STACK_DEFINE(pm_device_runtime_stack, CONFIG_PM_DEVICE_RUNTIME_DEDICATED_WQ_STACK_SIZE); @@ -33,6 +40,82 @@ static struct k_work_q pm_device_runtime_wq; #define EVENT_MASK (EVENT_STATE_ACTIVE | EVENT_STATE_SUSPENDED) +/* Increment the usage counter of a device under the global lock. */ +static void runtime_usecount_inc(struct pm_device *pm) +{ + __ASSERT_NO_MSG(pm != NULL); + + K_SPINLOCK(&lock) { + pm->base.usage++; + } +} + +/* Decrement the usage counter of a device under the global lock. */ +static void runtime_usecount_dec(struct pm_device *pm) +{ + __ASSERT_NO_MSG(pm != NULL); + + K_SPINLOCK(&lock) { + pm->base.usage--; + } +} + +/* + * Release one usage reference under the global lock, unless this is the + * last reference. Last-user handling needs the per-device semaphore and + * is left to the caller. + * + * @retval true If the reference was released. + * @retval false If this is the last reference. + */ +static bool runtime_usage_put_fast(struct pm_device *pm) +{ + bool released = false; + + __ASSERT_NO_MSG(pm != NULL); + + K_SPINLOCK(&lock) { + if (pm->base.usage > 1U) { + pm->base.usage--; + released = true; + } + } + + return released; +} + +/* + * Release one usage reference under the global lock, with the per-device + * semaphore held by the caller. + * + * @retval 1 If this was the last reference and the device has to be + * suspended. + * @retval 0 If other references remain and nothing else has to be done. + * @retval -EALREADY If the usage counter was already zero. + */ +static int runtime_usage_put(struct pm_device *pm) +{ + int ret; + + __ASSERT_NO_MSG(pm != NULL); + /* Caller must hold the per-device semaphore, except in pre-kernel + * mode where it is never taken (single-threaded boot). + */ + __ASSERT(k_is_pre_kernel() || (k_sem_count_get(&pm->lock) == 0), + "pm lock not owned"); + + K_SPINLOCK(&lock) { + if (pm->base.usage == 0U) { + ret = -EALREADY; + } else { + pm->base.usage--; + ret = (pm->base.usage == 0U) ? 1 : 0; + } + } + + return ret; +} + /** * @brief Suspend a device * @@ -65,6 +148,11 @@ static int runtime_suspend(const struct device *dev, bool async, return 0; } + /* If we are not the last user, return. */ + if (runtime_usage_put_fast(pm)) { + return 0; + } + if (k_is_pre_kernel()) { async = false; } else { @@ -74,16 +162,14 @@ static int runtime_suspend(const struct device *dev, bool async, } } - if (pm->base.usage == 0U) { - LOG_WRN("Unbalanced suspend"); - ret = -EALREADY; - goto unlock; - } - - pm->base.usage--; - if (pm->base.usage > 0U) { + ret = runtime_usage_put(pm); + if (ret <= 0) { + if (ret < 0) { + LOG_WRN("Unbalanced suspend"); + } goto unlock; } + ret = 0; if (async) { /* queue suspend */ @@ -99,7 +185,7 @@ static int runtime_suspend(const struct device *dev, bool async, /* suspend now */ ret = pm->base.action_cb(pm->dev, PM_DEVICE_ACTION_SUSPEND); if (ret < 0) { - pm->base.usage++; + runtime_usecount_inc(pm); goto unlock; } @@ -131,7 +217,7 @@ static void runtime_suspend_work(struct k_work *work) (void)k_sem_take(&pm->lock, K_FOREVER); if (ret < 0) { - pm->base.usage++; + runtime_usecount_inc(pm); pm->base.state = PM_DEVICE_STATE_ACTIVE; } else { pm->base.state = PM_DEVICE_STATE_SUSPENDED; @@ -219,6 +305,21 @@ int pm_device_runtime_get(const struct device *dev) } if (!k_is_pre_kernel()) { + bool early_exit = false; + + K_SPINLOCK(&lock) { + /* If we are not the first user and device is active, return. */ + if ((pm->base.usage > 0U) && (pm->base.state == PM_DEVICE_STATE_ACTIVE)) { + pm->base.usage++; + early_exit = true; + } + } + + if (early_exit == true) { + ret = 0; + goto end; + } + ret = k_sem_take(&pm->lock, k_is_in_isr() ? K_NO_WAIT : K_FOREVER); if (ret < 0) { return -EWOULDBLOCK; @@ -251,7 +352,7 @@ int pm_device_runtime_get(const struct device *dev) atomic_set_bit(&pm->base.flags, PM_DEVICE_FLAG_PD_CLAIMED); } - pm->base.usage++; + runtime_usecount_inc(pm); #ifdef CONFIG_PM_DEVICE_RUNTIME_ASYNC /* @@ -287,7 +388,7 @@ int pm_device_runtime_get(const struct device *dev) ret = pm->base.action_cb(pm->dev, PM_DEVICE_ACTION_RESUME); if (ret < 0) { - pm->base.usage--; + runtime_usecount_dec(pm); if (domain != NULL) { (void)pm_device_runtime_put(domain); atomic_clear_bit(&dev->pm_base->flags, PM_DEVICE_FLAG_PD_CLAIMED); diff --git a/tests/drivers/uart/uart_elementary/boards/lp_mspm0g3519.conf b/tests/drivers/uart/uart_elementary/boards/lp_mspm0g3519.conf new file mode 100644 index 000000000000..74cc8d7691e1 --- /dev/null +++ b/tests/drivers/uart/uart_elementary/boards/lp_mspm0g3519.conf @@ -0,0 +1,2 @@ +CONFIG_PM_DEVICE=y +CONFIG_PM_DEVICE_RUNTIME=y diff --git a/tests/drivers/uart/uart_elementary/boards/lp_mspm0g3519.overlay b/tests/drivers/uart/uart_elementary/boards/lp_mspm0g3519.overlay index d0f3af819bb2..615f2380c5e5 100644 --- a/tests/drivers/uart/uart_elementary/boards/lp_mspm0g3519.overlay +++ b/tests/drivers/uart/uart_elementary/boards/lp_mspm0g3519.overlay @@ -10,4 +10,5 @@ dut: &uart1 { current-speed = <115200>; hw-flow-control; status = "okay"; + zephyr,pm-device-runtime-auto; }; diff --git a/tests/drivers/uart/uart_elementary/boards/lp_mspm0l2228.conf b/tests/drivers/uart/uart_elementary/boards/lp_mspm0l2228.conf new file mode 100644 index 000000000000..74cc8d7691e1 --- /dev/null +++ b/tests/drivers/uart/uart_elementary/boards/lp_mspm0l2228.conf @@ -0,0 +1,2 @@ +CONFIG_PM_DEVICE=y +CONFIG_PM_DEVICE_RUNTIME=y diff --git a/tests/drivers/uart/uart_elementary/boards/lp_mspm0l2228.overlay b/tests/drivers/uart/uart_elementary/boards/lp_mspm0l2228.overlay index b0a54564b9cd..1573ffb833de 100644 --- a/tests/drivers/uart/uart_elementary/boards/lp_mspm0l2228.overlay +++ b/tests/drivers/uart/uart_elementary/boards/lp_mspm0l2228.overlay @@ -20,6 +20,7 @@ pinctrl-names = "default"; hw-flow-control; status = "okay"; + zephyr,pm-device-runtime-auto; }; }; }; diff --git a/tests/drivers/uart/uart_mix_fifo_poll/boards/lp_mspm0g3519.conf b/tests/drivers/uart/uart_mix_fifo_poll/boards/lp_mspm0g3519.conf index d94b91b6981d..a731d8916433 100644 --- a/tests/drivers/uart/uart_mix_fifo_poll/boards/lp_mspm0g3519.conf +++ b/tests/drivers/uart/uart_mix_fifo_poll/boards/lp_mspm0g3519.conf @@ -1,4 +1,6 @@ CONFIG_STRESS_TEST_REPS=1000 +CONFIG_PM_DEVICE=y +CONFIG_PM_DEVICE_RUNTIME=y # On Cortex-M0+ _IRQ_PRIO_OFFSET is 0, so SysTick shares the highest NVIC # priority with all peripherals. The k_timer expiry (uart_poll_out() spinning # on a full TX FIFO) then starves the counter ISR that drains the 4-deep RX diff --git a/tests/drivers/uart/uart_mix_fifo_poll/boards/lp_mspm0g3519.overlay b/tests/drivers/uart/uart_mix_fifo_poll/boards/lp_mspm0g3519.overlay index d6a6db85554f..2b451368884c 100644 --- a/tests/drivers/uart/uart_mix_fifo_poll/boards/lp_mspm0g3519.overlay +++ b/tests/drivers/uart/uart_mix_fifo_poll/boards/lp_mspm0g3519.overlay @@ -14,6 +14,7 @@ dut: &uart1 { current-speed = <9600>; rx-fifo-threshold = ; status = "okay"; + zephyr,pm-device-runtime-auto; }; counter_dev: &counterg6 { diff --git a/tests/drivers/uart/uart_mix_fifo_poll/boards/lp_mspm0l2228.conf b/tests/drivers/uart/uart_mix_fifo_poll/boards/lp_mspm0l2228.conf index d94b91b6981d..a731d8916433 100644 --- a/tests/drivers/uart/uart_mix_fifo_poll/boards/lp_mspm0l2228.conf +++ b/tests/drivers/uart/uart_mix_fifo_poll/boards/lp_mspm0l2228.conf @@ -1,4 +1,6 @@ CONFIG_STRESS_TEST_REPS=1000 +CONFIG_PM_DEVICE=y +CONFIG_PM_DEVICE_RUNTIME=y # On Cortex-M0+ _IRQ_PRIO_OFFSET is 0, so SysTick shares the highest NVIC # priority with all peripherals. The k_timer expiry (uart_poll_out() spinning # on a full TX FIFO) then starves the counter ISR that drains the 4-deep RX diff --git a/tests/drivers/uart/uart_mix_fifo_poll/boards/lp_mspm0l2228.overlay b/tests/drivers/uart/uart_mix_fifo_poll/boards/lp_mspm0l2228.overlay index 223808054a60..4ce21a9d3b12 100644 --- a/tests/drivers/uart/uart_mix_fifo_poll/boards/lp_mspm0l2228.overlay +++ b/tests/drivers/uart/uart_mix_fifo_poll/boards/lp_mspm0l2228.overlay @@ -13,6 +13,7 @@ dut: &uart1 { pinctrl-0 = <&uart1_tx_pb4 &uart1_rx_pb5>; pinctrl-names = "default"; status = "okay"; + zephyr,pm-device-runtime-auto; }; &timg8 { diff --git a/tests/drivers/uart/uart_pm/boards/lp_mspm0g3519.overlay b/tests/drivers/uart/uart_pm/boards/lp_mspm0g3519.overlay new file mode 100644 index 000000000000..fcfb7c704ed0 --- /dev/null +++ b/tests/drivers/uart/uart_pm/boards/lp_mspm0g3519.overlay @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2026 Texas Instruments Incorporated + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +/* TX (PB4) and RX (PB5) must be connected together for loopback */ +/*zephyr,pm-device-runtime-auto;*/ +dut: &uart1 { + pinctrl-0 = <&uart1_tx_pb4 &uart1_rx_pb5>; + pinctrl-names = "default"; + current-speed = <9600>; + rx-fifo-threshold = ; + status = "okay"; +}; + +&cpu0 { + cpu-power-states = <&run0 &run1 &run2>; +}; diff --git a/tests/drivers/uart/uart_pm/boards/lp_mspm0l2228.overlay b/tests/drivers/uart/uart_pm/boards/lp_mspm0l2228.overlay new file mode 100644 index 000000000000..9ac0ad886292 --- /dev/null +++ b/tests/drivers/uart/uart_pm/boards/lp_mspm0l2228.overlay @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2026 Texas Instruments Incorporated + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +/* TX (PB4) and RX (PB5) must be connected together for loopback */ + +dut: &uart1 { + rx-fifo-threshold = ; + pinctrl-0 = <&uart1_tx_pb4 &uart1_rx_pb5>; + pinctrl-names = "default"; + status = "okay"; + current-speed = <9600>; + zephyr,pm-device-runtime-auto; +}; + +&timg8 { + status = "okay"; +}; + +counter_dev: &counterg8 { + status = "okay"; +}; diff --git a/tests/drivers/uart/uart_pm/src/main.c b/tests/drivers/uart/uart_pm/src/main.c index e28d55efb331..f6eb748acf5a 100644 --- a/tests/drivers/uart/uart_pm/src/main.c +++ b/tests/drivers/uart/uart_pm/src/main.c @@ -10,7 +10,7 @@ #include #define UART_NODE DT_NODELABEL(dut) -#define DISABLED_RX DT_PROP(UART_NODE, disable_rx) +#define DISABLED_RX DT_PROP_OR(UART_NODE, disable_rx, 0) static void polling_verify(const struct device *dev, bool is_async, bool active) { diff --git a/tests/drivers/uart/uart_pm/testcase.yaml b/tests/drivers/uart/uart_pm/testcase.yaml index 6bb6c1bb6e46..25c07e983030 100644 --- a/tests/drivers/uart/uart_pm/testcase.yaml +++ b/tests/drivers/uart/uart_pm/testcase.yaml @@ -16,6 +16,8 @@ common: - nrf54lm20bsim/nrf54lm20a/cpuapp - nrf7120dk/nrf7120/cpuapp - ophelia4ev/nrf54l15/cpuapp + - lp_mspm0g3519 + - lp_mspm0l2228 harness_config: fixture: gpio_loopback depends_on: gpio @@ -45,6 +47,8 @@ tests: - nrf54lm20bsim/nrf54lm20a/cpuapp - nrf7120dk/nrf7120/cpuapp - ophelia4ev/nrf54l15/cpuapp + - lp_mspm0g3519 + - lp_mspm0l2228 drivers.uart.pm.enhanced_poll: extra_configs: @@ -85,6 +89,9 @@ tests: - CONFIG_UART_ASYNC_API=y - CONFIG_UART_0_ASYNC=y - CONFIG_UART_0_ENHANCED_POLL_OUT=n + platform_exclude: + - lp_mspm0g3519 + - lp_mspm0l2228 drivers.uart.pm.async.enhanced_poll: extra_configs: @@ -99,3 +106,5 @@ tests: - nrf54lm20dk/nrf54lm20b/cpuapp - nrf7120dk/nrf7120/cpuapp - ophelia4ev/nrf54l15/cpuapp + - lp_mspm0g3519 + - lp_mspm0l2228