serial: uart_nrfx_uarte: graceful async power down

Gracefully shutdown the UARTE peripheral when the async API is used.
Failure to do so results in the driver being unusable when powered back
up as the required events (ENDTX & TXSTOPPED) are not set. This also
ensures that the last byte sent out via `poll_out` is properly output
on the serial line before powering down.

Fixes #31930.

Signed-off-by: Jordan Yates <jordan.yates@data61.csiro.au>
This commit is contained in:
Jordan Yates 2021-02-04 21:41:10 +10:00 committed by Anas Nashif
commit 238067dfc1

View file

@ -424,6 +424,33 @@ static bool is_tx_ready(const struct device *dev)
nrf_uarte_event_check(uarte, NRF_UARTE_EVENT_ENDTX) : 0);
}
/* Wait until the transmitter is in the idle state. When this function returns,
* IRQ's are locked with the returned key.
*/
static int wait_tx_ready(const struct device *dev)
{
int key;
do {
/* wait arbitrary time before back off. */
bool res;
NRFX_WAIT_FOR(is_tx_ready(dev), 100, 1, res);
if (res) {
key = irq_lock();
if (is_tx_ready(dev)) {
break;
}
irq_unlock(key);
}
k_msleep(1);
} while (1);
return key;
}
static void tx_start(NRF_UARTE_Type *uarte, const uint8_t *buf, size_t len)
{
nrf_uarte_tx_buffer_set(uarte, buf, len);
@ -1148,22 +1175,7 @@ static void uarte_nrfx_poll_out(const struct device *dev, unsigned char c)
irq_unlock(key);
}
} else {
do {
/* wait arbitrary time before back off. */
bool res;
NRFX_WAIT_FOR(is_tx_ready(dev), 100, 1, res);
if (res) {
key = irq_lock();
if (is_tx_ready(dev)) {
break;
}
irq_unlock(key);
}
k_msleep(1);
} while (1);
key = wait_tx_ready(dev);
}
/* At this point we should have irq locked and any previous transfer
@ -1587,6 +1599,16 @@ static void uarte_nrfx_set_power_state(const struct device *dev,
data->async->rx_total_user_byte_cnt = 0;
}
if (get_dev_data(dev)->async) {
/* Wait for the transmitter to go idle.
* While the async API can wait for transmission to
* complete before disabling the peripheral, the poll
* API exits before the character is sent on the wire.
* If the transmission is ongoing when the peripheral
* is disabled, the ENDTX and TXSTOPPED events will
* never be generated. This will block the driver in
* any future calls to poll_out.
*/
irq_unlock(wait_tx_ready(dev));
nrf_uarte_disable(uarte);
uarte_nrfx_pins_enable(dev, false);
return;