Skip to content

drivers: serial: nrfx_uarte: Add workaround for FRAMETIMEOUT corner case #85392

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 45 additions & 2 deletions drivers/serial/uart_nrfx_uarte.c
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,17 @@ LOG_MODULE_REGISTER(uart_nrfx_uarte, CONFIG_UART_LOG_LEVEL);
#define UARTE_HAS_FRAME_TIMEOUT 1
#endif

/* Frame timeout has a bug that countdown counter may not be triggered in some
* specific condition. It may happen if RX is manually started after ENDRX (STOPRX
* task was not triggered) and there is ongoing reception of a byte. RXDRDY event
* triggered by the reception of that byte may not trigger frame timeout counter.
* If this is the last byte of a transfer then without the workaround there will
* be no expected RX timeout.
*/
#ifdef UARTE_HAS_FRAME_TIMEOUT
#define RX_FRAMETIMEOUT_WORKAROUND 1
#endif

#define INSTANCE_NEEDS_CACHE_MGMT(unused, prefix, i, prop) UARTE_IS_CACHEABLE(prefix##i)

#if UARTE_FOR_EACH_INSTANCE(INSTANCE_NEEDS_CACHE_MGMT, (+), (0), _)
Expand Down Expand Up @@ -251,6 +262,8 @@ struct uarte_nrfx_data {
#define UARTE_FLAG_LOW_POWER (UARTE_FLAG_LOW_POWER_TX | UARTE_FLAG_LOW_POWER_RX)
#define UARTE_FLAG_TRIG_RXTO BIT(2)
#define UARTE_FLAG_POLL_OUT BIT(3)
/* Flag indicating that a workaround for not working frame timeout is active. */
#define UARTE_FLAG_FTIMEOUT_WATCH BIT(4)

/* If enabled then ENDTX is PPI'ed to TXSTOP */
#define UARTE_CFG_FLAG_PPI_ENDTX BIT(0)
Expand Down Expand Up @@ -1304,9 +1317,22 @@ static void rx_timeout(struct k_timer *timer)
NRF_UARTE_Type *uarte = get_uarte_instance(dev);

#ifdef UARTE_HAS_FRAME_TIMEOUT
if (!nrf_uarte_event_check(uarte, NRF_UARTE_EVENT_RXDRDY)) {
nrf_uarte_task_trigger(uarte, NRF_UARTE_TASK_STOPRX);
struct uarte_nrfx_data *data = dev->data;
struct uarte_async_rx *async_rx = &data->async->rx;
bool rxdrdy = nrf_uarte_event_check(uarte, NRF_UARTE_EVENT_RXDRDY);

if (IS_ENABLED(RX_FRAMETIMEOUT_WORKAROUND) &&
(atomic_and(&data->flags, ~UARTE_FLAG_FTIMEOUT_WATCH) & UARTE_FLAG_FTIMEOUT_WATCH)) {
if (rxdrdy) {
nrf_uarte_event_clear(uarte, NRF_UARTE_EVENT_RXDRDY);
k_timer_start(&async_rx->timer, async_rx->timeout, K_NO_WAIT);
}
} else {
if (!rxdrdy) {
nrf_uarte_task_trigger(uarte, NRF_UARTE_TASK_STOPRX);
}
}

return;
#else /* UARTE_HAS_FRAME_TIMEOUT */
struct uarte_nrfx_data *data = dev->data;
Expand Down Expand Up @@ -1532,6 +1558,7 @@ static void endrx_isr(const struct device *dev)
async_rx->offset = 0;

if (async_rx->enabled) {
bool start_timeout = false;
/* If there is a next buffer, then STARTRX will have already been
* invoked by the short (the next buffer will be filling up already)
* and here we just do the swap of which buffer the driver is following,
Expand All @@ -1546,6 +1573,11 @@ static void endrx_isr(const struct device *dev)
*/
if (!nrf_uarte_event_check(uarte, NRF_UARTE_EVENT_RXSTARTED)) {
nrf_uarte_task_trigger(uarte, NRF_UARTE_TASK_STARTRX);
nrf_uarte_event_clear(uarte, NRF_UARTE_EVENT_RXTO);
if (IS_ENABLED(RX_FRAMETIMEOUT_WORKAROUND)) {
data->flags |= UARTE_FLAG_FTIMEOUT_WATCH;
start_timeout = true;
}
}
/* Remove the short until the subsequent next buffer is setup */
nrf_uarte_shorts_disable(uarte, NRF_UARTE_SHORT_ENDRX_STARTRX);
Expand All @@ -1554,6 +1586,11 @@ static void endrx_isr(const struct device *dev)
}

irq_unlock(key);
if (IS_ENABLED(UARTE_HAS_FRAME_TIMEOUT)) {
if (start_timeout && !K_TIMEOUT_EQ(async_rx->timeout, K_NO_WAIT)) {
k_timer_start(&async_rx->timer, async_rx->timeout, K_NO_WAIT);
}
}
}

#if !defined(CONFIG_UART_NRFX_UARTE_ENHANCED_RX)
Expand Down Expand Up @@ -1611,6 +1648,12 @@ static void rxto_isr(const struct device *dev)
struct uarte_nrfx_data *data = dev->data;
struct uarte_async_rx *async_rx = &data->async->rx;

if (IS_ENABLED(RX_FRAMETIMEOUT_WORKAROUND)) {
if (atomic_test_and_clear_bit(&data->flags, UARTE_FLAG_FTIMEOUT_WATCH)) {
Copy link
Member

@anangl anangl Mar 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (atomic_test_and_clear_bit(&data->flags, UARTE_FLAG_FTIMEOUT_WATCH)) {
if (atomic_test_and_clear_bit(&data->flags, UARTE_FLAG_FTIMEOUT_WATCH_BIT)) {

But perhaps it would be better, for consistency with other flags, to not use UARTE_FLAG_FTIMEOUT_WATCH_BIT at all, just UARTE_FLAG_FTIMEOUT_WATCH and atomic_and().

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

k_timer_stop(&async_rx->timer);
}
}

if (async_rx->buf) {
#ifdef CONFIG_HAS_NORDIC_DMM
(void)dmm_buffer_in_release(config->mem_reg, async_rx->usr_buf, 0, async_rx->buf);
Expand Down
Loading