drivers: serial: nrfx_uarte: Add support for non ISR PM mode

When fast UARTE instance is used (e.g. UARTE120 in nrf54h20), PM actions
are not ISR safe because they include communication over IPC so they can
only be called from the thread context. Extend driver to support both
PM modes. When non ISR mode is used then uart_rx_enable() and uart_tx()
will return error if they are called from ISR and resume operation
would need to be called because device is suspended. On completion,
driver is calling pm_device_runtime_put_async which can be called from
the ISR context.

Additionally, suspending in the TXSTOPPED and RXTO events has been
moved after user callback. It allows to support the case where
uart_rx_enable() or uart_tx() are called from that callback context.
Since suspending is called after returning from the callback it will
not trigger suspend action because API called in the callback context
will increment the usage counter (when pm_device_runtime_get() is
called).

Signed-off-by: Krzysztof Chruściński <krzysztof.chruscinski@nordicsemi.no>
This commit is contained in:
Krzysztof Chruściński 2024-11-18 08:42:50 +01:00 committed by Fabio Baltieri
commit c8d50e4ef1

View file

@ -110,6 +110,19 @@ LOG_MODULE_REGISTER(uart_nrfx_uarte, CONFIG_UART_LOG_LEVEL);
#define UARTE_ANY_LOW_POWER 1
#endif
BUILD_ASSERT(NRF_GPD_FAST_ACTIVE1 == 0);
/* Macro must resolve to literal 0 or 1 */
#define INSTANCE_IS_FAST(unused, prefix, idx, _) \
COND_CODE_1(DT_NODE_HAS_STATUS_OKAY(UARTE(idx)), \
(COND_CODE_1(UTIL_AND(IS_ENABLED(CONFIG_SOC_NRF54H20_GPD), \
DT_NODE_HAS_PROP(UARTE(idx), power_domains)), \
(COND_CODE_0(DT_PHA(UARTE(idx), power_domains, id), (1), (0))),\
(0))), (0))
#if UARTE_FOR_EACH_INSTANCE(INSTANCE_IS_FAST, (||), (0))
#define UARTE_ANY_FAST 1
#endif
#ifdef UARTE_ANY_CACHE
/* uart120 instance does not retain BAUDRATE register when ENABLE=0. When this instance
* is used then baudrate must be set after enabling the peripheral and not before.
@ -269,6 +282,21 @@ struct uarte_nrfx_data {
(IS_ENABLED(UARTE_ANY_LOW_POWER) && \
!IS_ENABLED(CONFIG_PM_DEVICE) && \
(_config->flags & UARTE_CFG_FLAG_LOW_POWER))
/** @brief Check if device has PM that works in ISR safe mode.
*
* Only fast UARTE instance does not work in that mode so check PM configuration
* flags only if there is any fast instance present.
*
* @retval true if device PM is ISR safe.
* @retval false if device PM is not ISR safe.
*/
#define IS_PM_ISR_SAFE(dev) \
(!IS_ENABLED(UARTE_ANY_FAST) ||\
COND_CODE_1(CONFIG_PM_DEVICE,\
((dev->pm_base->flags & BIT(PM_DEVICE_FLAG_ISR_SAFE))), \
(0)))
/**
* @brief Structure for UARTE configuration.
*/
@ -873,6 +901,20 @@ static int uarte_nrfx_tx(const struct device *dev, const uint8_t *buf,
}
if (IS_ENABLED(CONFIG_PM_DEVICE_RUNTIME)) {
if (!IS_PM_ISR_SAFE(dev) && k_is_in_isr()) {
/* If instance does not support PM from ISR device shall
* already be turned on.
*/
enum pm_device_state state;
int err;
err = pm_device_state_get(dev, &state);
(void)err;
__ASSERT_NO_MSG(err == 0);
if (state != PM_DEVICE_STATE_ACTIVE) {
return -ENOTSUP;
}
}
pm_device_runtime_get(dev);
}
@ -941,6 +983,10 @@ static void notify_rx_disable(const struct device *dev)
};
user_callback(dev, (struct uart_event *)&evt);
if (IS_ENABLED(CONFIG_PM_DEVICE_RUNTIME)) {
pm_device_runtime_put_async(dev, K_NO_WAIT);
}
}
#ifdef UARTE_HAS_FRAME_TIMEOUT
@ -1015,6 +1061,24 @@ static int uarte_nrfx_rx_enable(const struct device *dev, uint8_t *buf,
async_rx->next_buf = NULL;
async_rx->next_buf_len = 0;
if (IS_ENABLED(CONFIG_PM_DEVICE_RUNTIME)) {
if (!IS_PM_ISR_SAFE(dev) && k_is_in_isr()) {
/* If instance does not support PM from ISR device shall
* already be turned on.
*/
enum pm_device_state state;
int err;
err = pm_device_state_get(dev, &state);
(void)err;
__ASSERT_NO_MSG(err == 0);
if (state != PM_DEVICE_STATE_ACTIVE) {
return -ENOTSUP;
}
}
pm_device_runtime_get(dev);
}
if (IS_ENABLED(CONFIG_PM_DEVICE_RUNTIME) || LOW_POWER_ENABLED(cfg)) {
if (async_rx->flush_cnt) {
int cpy_len = MIN(len, async_rx->flush_cnt);
@ -1068,9 +1132,7 @@ static int uarte_nrfx_rx_enable(const struct device *dev, uint8_t *buf,
async_rx->enabled = true;
if (IS_ENABLED(CONFIG_PM_DEVICE_RUNTIME)) {
pm_device_runtime_get(dev);
} else if (LOW_POWER_ENABLED(cfg)) {
if (LOW_POWER_ENABLED(cfg)) {
unsigned int key = irq_lock();
uarte_enable_locked(dev, UARTE_FLAG_LOW_POWER_RX);
@ -1543,9 +1605,7 @@ static void rxto_isr(const struct device *dev)
nrf_uarte_event_clear(uarte, NRF_UARTE_EVENT_RXDRDY);
#endif
if (IS_ENABLED(CONFIG_PM_DEVICE_RUNTIME)) {
pm_device_runtime_put(dev);
} else if (LOW_POWER_ENABLED(config)) {
if (LOW_POWER_ENABLED(config)) {
uint32_t key = irq_lock();
uarte_disable_locked(dev, UARTE_FLAG_LOW_POWER_RX);
@ -1570,7 +1630,7 @@ static void txstopped_isr(const struct device *dev)
if (IS_ENABLED(CONFIG_PM_DEVICE_RUNTIME)) {
nrf_uarte_int_disable(uarte, NRF_UARTE_INT_TXSTOPPED_MASK);
if (data->flags & UARTE_FLAG_POLL_OUT) {
pm_device_runtime_put(dev);
pm_device_runtime_put_async(dev, K_NO_WAIT);
data->flags &= ~UARTE_FLAG_POLL_OUT;
}
} else if (LOW_POWER_ENABLED(config)) {
@ -1633,11 +1693,11 @@ static void txstopped_isr(const struct device *dev)
data->async->tx.buf = NULL;
data->async->tx.len = 0;
if (IS_ENABLED(CONFIG_PM_DEVICE_RUNTIME)) {
pm_device_runtime_put(dev);
}
user_callback(dev, &evt);
if (IS_ENABLED(CONFIG_PM_DEVICE_RUNTIME)) {
pm_device_runtime_put_async(dev, K_NO_WAIT);
}
}
static void rxdrdy_isr(const struct device *dev)
@ -1824,6 +1884,22 @@ static void uarte_nrfx_poll_out(const struct device *dev, unsigned char c)
}
if (IS_ENABLED(CONFIG_PM_DEVICE_RUNTIME)) {
if (!IS_PM_ISR_SAFE(dev) && k_is_in_isr()) {
/* If instance does not support PM from ISR device shall
* already be turned on.
*/
enum pm_device_state state;
int err;
err = pm_device_state_get(dev, &state);
(void)err;
__ASSERT_NO_MSG(err == 0);
if (state != PM_DEVICE_STATE_ACTIVE) {
irq_unlock(key);
return;
}
}
if (!(data->flags & UARTE_FLAG_POLL_OUT)) {
data->flags |= UARTE_FLAG_POLL_OUT;
pm_device_runtime_get(dev);
@ -2417,7 +2493,8 @@ static int uarte_instance_init(const struct device *dev,
} \
\
PM_DEVICE_DT_DEFINE(UARTE(idx), uarte_nrfx_pm_action, \
PM_DEVICE_ISR_SAFE); \
COND_CODE_1(INSTANCE_IS_FAST(_, /*empty*/, idx, _),\
(0), (PM_DEVICE_ISR_SAFE))); \
\
DEVICE_DT_DEFINE(UARTE(idx), \
uarte_##idx##_init, \