/*
 * Copyright (c) 2013 Nordic Semiconductor ASA
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 *   1. Redistributions of source code must retain the above copyright notice, this list
 *      of conditions and the following disclaimer.
 *
 *   2. Redistributions in binary form, except as embedded into a Nordic Semiconductor ASA
 *      integrated circuit in a product or a software update for such product, must reproduce
 *      the above copyright notice, this list of conditions and the following disclaimer in
 *      the documentation and/or other materials provided with the distribution.
 *
 *   3. Neither the name of Nordic Semiconductor ASA nor the names of its contributors may be
 *      used to endorse or promote products derived from this software without specific prior
 *      written permission.
 *
 *   4. This software, with or without modification, must only be used with a
 *      Nordic Semiconductor ASA integrated circuit.
 *
 *   5. Any software provided in binary or object form under this license must not be reverse
 *      engineered, decompiled, modified and/or disassembled.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */

#if DEVICE_SERIAL

#include "hal/serial_api.h"

#include "nrf_uarte.h"
#include "nrfx_uarte.h"
#include "nrfx_uart.h"
#include "nrf_atfifo.h"
#include "app_util_platform.h"
#include "pinmap_ex.h"
#include "nrfx_gpiote.h"
#include "nrfx_ppi.h"
#include "PeripheralPins.h"

#include "platform/mbed_atomic.h"
#include "platform/mbed_critical.h"

#if UART0_ENABLED == 0
#error UART0 is disabled. DEVICE_SERIAL must also be disabled to continue.
#endif


/***
 *       _____             __ _                       _   _
 *      / ____|           / _(_)                     | | (_)
 *     | |     ___  _ __ | |_ _  __ _ _   _ _ __ __ _| |_ _  ___  _ __
 *     | |    / _ \| '_ \|  _| |/ _` | | | | '__/ _` | __| |/ _ \| '_ \
 *     | |___| (_) | | | | | | | (_| | |_| | | | (_| | |_| | (_) | | | |
 *      \_____\___/|_| |_|_| |_|\__, |\__,_|_|  \__,_|\__|_|\___/|_| |_|
 *                               __/ |
 *                              |___/
 */

/**
 * Default FIFO buffer size for UARTE0.
 */
#ifndef MBED_CONF_NORDIC_UART_0_FIFO_SIZE
#define MBED_CONF_NORDIC_UART_0_FIFO_SIZE 32
#endif

/**
 * Default FIFO buffer size for UARTE1.
 */
#ifndef MBED_CONF_NORDIC_UART_1_FIFO_SIZE
#define MBED_CONF_NORDIC_UART_1_FIFO_SIZE 32
#endif

/**
 * Internal short names.
 */
#define UART0_FIFO_BUFFER_SIZE  MBED_CONF_NORDIC_UART_0_FIFO_SIZE
#define UART1_FIFO_BUFFER_SIZE  MBED_CONF_NORDIC_UART_1_FIFO_SIZE
#define DMA_BUFFER_SIZE         1
#define NUMBER_OF_BANKS         2
#define FIFO_MIN                3

/***
 *      _______                   _       __
 *     |__   __|                 | |     / _|
 *        | |_   _ _ __   ___  __| | ___| |_ ___
 *        | | | | | '_ \ / _ \/ _` |/ _ \  _/ __|
 *        | | |_| | |_) |  __/ (_| |  __/ | \__ \
 *        |_|\__, | .__/ \___|\__,_|\___|_| |___/
 *            __/ | |
 *           |___/|_|
 */

/**
 * Missing event typedefs.
 */
typedef enum
{
    NRF_UARTE_EVENT_TXDRDY    = offsetof(NRF_UARTE_Type, EVENTS_TXDRDY),
} nrf_uarte_event_extra_t;


/**
 * Internal struct for storing each UARTE instance's state:
 *
 *  owner: pointer to serial object currently using instance.
 *  buffer: buffers assigned to EasyDMA.
 *  tx_data: 1 byte Tx buffer for blocking putc.
 *  tx_in_progress: mutex for atomic Tx.
 *  rx_in_progress: mutex for atomic Rx when using async API.
 *  tx_asynch: set synch or asynch mode for Tx.
 *  rx_asynch: set synch or asynch mode for Rx.
 *  callback_posted: flag for posting only one callback.
 *  active_bank: flag for buffer swapping.
 *  fifo: pointer to the FIFO buffer.
 */
typedef struct {
    struct serial_s *owner;
    uint8_t buffer[NUMBER_OF_BANKS][DMA_BUFFER_SIZE];
    uint32_t usage_counter;
    uint8_t tx_data;
    bool tx_in_progress;
    bool rx_in_progress;
    bool tx_asynch;
    bool rx_asynch;
    bool callback_posted;
    uint8_t active_bank;
    nrf_atfifo_t *fifo;
    uint32_t fifo_free_count;
    nrf_ppi_channel_t ppi_rts;
    nrfx_gpiote_pin_t rts;
    bool rx_suspended;
} nordic_uart_state_t;

/**
 * Turn Mbed HAL IRQ flags into maskable bit masks.
 */
typedef enum {
    NORDIC_TX_IRQ = (1 << 0),
    NORDIC_RX_IRQ = (1 << 1),
} nordic_irq_t;


/***
 *       _____ _       _           _  __      __        _       _     _
 *      / ____| |     | |         | | \ \    / /       (_)     | |   | |
 *     | |  __| | ___ | |__   __ _| |  \ \  / /_ _ _ __ _  __ _| |__ | | ___  ___
 *     | | |_ | |/ _ \| '_ \ / _` | |   \ \/ / _` | '__| |/ _` | '_ \| |/ _ \/ __|
 *     | |__| | | (_) | |_) | (_| | |    \  / (_| | |  | | (_| | |_) | |  __/\__ \
 *      \_____|_|\___/|_.__/ \__,_|_|     \/ \__,_|_|  |_|\__,_|_.__/|_|\___||___/
 *
 *
 */

/**
 * UARTE state. One for each instance.
 */
static nordic_uart_state_t nordic_nrf5_uart_state[NRFX_UARTE_ENABLED_COUNT] = { 0 };

/**
 * Array with UARTE register pointers for easy access.
 */
static NRF_UARTE_Type *nordic_nrf5_uart_register[NRFX_UARTE_ENABLED_COUNT] = {
    NRF_UARTE0,
#if UART1_ENABLED
    NRF_UARTE1,
#endif
};

/**
 * @brief      Create atomic fifo using macro. Macro defines static arrays
 *             for buffer and internal state.
 */
NRF_ATFIFO_DEF(nordic_nrf5_uart_fifo_0, uint8_t, UART0_FIFO_BUFFER_SIZE);

#if UART1_ENABLED
NRF_ATFIFO_DEF(nordic_nrf5_uart_fifo_1, uint8_t, UART1_FIFO_BUFFER_SIZE);
#endif

/**
 * SWI IRQ mask.
 */
static uint8_t nordic_nrf5_uart_swi_mask_tx_0 = 0;
static uint8_t nordic_nrf5_uart_swi_mask_rx_0 = 0;
#if UART1_ENABLED
static uint8_t nordic_nrf5_uart_swi_mask_tx_1 = 0;
static uint8_t nordic_nrf5_uart_swi_mask_rx_1 = 0;
#endif

/**
 * Global variables expected by mbed_retarget.cpp for STDOUT.
 */
int stdio_uart_inited = 0;
serial_t stdio_uart = { 0 };


/***
 *       _____        __ _                            _____       _                             _
 *      / ____|      / _| |                          |_   _|     | |                           | |
 *     | (___   ___ | |_| |___      ____ _ _ __ ___    | |  _ __ | |_ ___ _ __ _ __ _   _ _ __ | |_
 *      \___ \ / _ \|  _| __\ \ /\ / / _` | '__/ _ \   | | | '_ \| __/ _ \ '__| '__| | | | '_ \| __|
 *      ____) | (_) | | | |_ \ V  V / (_| | | |  __/  _| |_| | | | ||  __/ |  | |  | |_| | |_) | |_
 *     |_____/ \___/|_|  \__| \_/\_/ \__,_|_|  \___| |_____|_| |_|\__\___|_|  |_|   \__,_| .__/ \__|
 *                                                                                       | |
 *                                                                                       |_|
 */

/**
 * @brief      SWI interrupt handler for signaling RxIrq handler.
 *
 * @param[in]  instance  The instance
 */
static void nordic_nrf5_uart_callback_handler(uint32_t instance)
{
    /* Flag that no callback is posted. */
    nordic_nrf5_uart_state[instance].callback_posted = false;

    /* Check if callback handler is set and if event mask match. */
    uart_irq_handler callback = (uart_irq_handler) nordic_nrf5_uart_state[instance].owner->handler;
    uint32_t mask = nordic_nrf5_uart_state[instance].owner->mask;

    if (callback && (mask & NORDIC_RX_IRQ)) {

        /* Invoke callback function. */
        uint32_t context = nordic_nrf5_uart_state[instance].owner->context;
        callback(context, RxIrq);
    }
}

/**
 * @brief      SWI interrupt handler for when the Tx buffer has been transmitted.
 *
 * @param[in]  instance  The instance
 */
static void nordic_nrf5_uart_event_handler_endtx(int instance)
{
    /* Release mutex. As the owner this call is safe. */
    core_util_atomic_store_bool(&nordic_nrf5_uart_state[instance].tx_in_progress, false);

    /* Check if callback handler and Tx event mask is set. */
    uart_irq_handler callback = (uart_irq_handler) nordic_nrf5_uart_state[instance].owner->handler;
    uint32_t mask = nordic_nrf5_uart_state[instance].owner->mask;

    if (callback && (mask & NORDIC_TX_IRQ)) {

        /* Invoke callback function. */
        uint32_t context = nordic_nrf5_uart_state[instance].owner->context;
        callback(context, TxIrq);
    }
}

/**
 * @brief      Asynchronous event handler for when Tx DMA buffer has been sent.
 *
 * @param[in]  instance  The instance
 */
#if DEVICE_SERIAL_ASYNCH
static void nordic_nrf5_uart_event_handler_endtx_asynch(int instance)
{
    /* Set Tx done and reset Tx mode to be not asynchronous. */
    nordic_nrf5_uart_state[instance].tx_asynch = false;
    core_util_atomic_store_bool(&nordic_nrf5_uart_state[instance].tx_in_progress, false);

    /* Cast handler to callback function pointer. */
    void (*callback)(void) = (void (*)(void)) nordic_nrf5_uart_state[instance].owner->tx_handler;
    uint32_t mask = nordic_nrf5_uart_state[instance].owner->tx_mask;

    /* Signal error if event mask matches and event handler is set. */
    if (callback && (mask & SERIAL_EVENT_TX_COMPLETE)) {

        /* Store event value so it can be read back. */
        nordic_nrf5_uart_state[instance].owner->tx_event = SERIAL_EVENT_TX_COMPLETE;

        /* Signal callback handler. */
        callback();
    }
}
#endif

static void nordic_nrf5_uart_swi0(void)
{
    if (nordic_nrf5_uart_swi_mask_tx_0) {

        nordic_nrf5_uart_swi_mask_tx_0 = 0;

#if DEVICE_SERIAL_ASYNCH
        if (nordic_nrf5_uart_state[0].tx_asynch) {

            nordic_nrf5_uart_event_handler_endtx_asynch(0);
        } else
#endif
        {
            nordic_nrf5_uart_event_handler_endtx(0);
        }
    }

    if (nordic_nrf5_uart_swi_mask_rx_0) {

        nordic_nrf5_uart_swi_mask_rx_0 = 0;

        nordic_nrf5_uart_callback_handler(0);
    }


#if UART1_ENABLED
    if (nordic_nrf5_uart_swi_mask_tx_1) {

        nordic_nrf5_uart_swi_mask_tx_1 = 0;

#if DEVICE_SERIAL_ASYNCH
        if (nordic_nrf5_uart_state[1].tx_asynch) {

            nordic_nrf5_uart_event_handler_endtx_asynch(1);
        } else
#endif
        {
            nordic_nrf5_uart_event_handler_endtx(1);
        }
    }

    if (nordic_nrf5_uart_swi_mask_rx_1) {

        nordic_nrf5_uart_swi_mask_rx_1 = 0;

        nordic_nrf5_uart_callback_handler(1);
    }
#endif
}

/**
 * @brief      Trigger Tx SWI.
 *
 * @param[in]  instance  The instance.
 */
static void nordic_swi_tx_trigger(int instance)
{
    if (instance == 0) {

        nordic_nrf5_uart_swi_mask_tx_0 = 1;
        NVIC_SetPendingIRQ(SWI0_EGU0_IRQn);
    }
#if UART1_ENABLED
    else if (instance == 1) {

        nordic_nrf5_uart_swi_mask_tx_1 = 1;
        NVIC_SetPendingIRQ(SWI0_EGU0_IRQn);
    }
#endif
}

/**
 * @brief      Trigger Rx SWI.
 *
 * @param[in]  instance  The instance
 */
static void nordic_swi_rx_trigger(int instance)
{
    if (instance == 0) {

        nordic_nrf5_uart_swi_mask_rx_0 = 1;
        NVIC_SetPendingIRQ(SWI0_EGU0_IRQn);
    }
#if UART1_ENABLED
    else if (instance == 1) {

        nordic_nrf5_uart_swi_mask_rx_1 = 1;
        NVIC_SetPendingIRQ(SWI0_EGU0_IRQn);
    }
#endif
}

/***
 *      _    _         _____ _______   ______               _     _    _                 _ _
 *     | |  | |  /\   |  __ \__   __| |  ____|             | |   | |  | |               | | |
 *     | |  | | /  \  | |__) | | |    | |____   _____ _ __ | |_  | |__| | __ _ _ __   __| | | ___ _ __
 *     | |  | |/ /\ \ |  _  /  | |    |  __\ \ / / _ \ '_ \| __| |  __  |/ _` | '_ \ / _` | |/ _ \ '__|
 *     | |__| / ____ \| | \ \  | |    | |___\ V /  __/ | | | |_  | |  | | (_| | | | | (_| | |  __/ |
 *      \____/_/    \_\_|  \_\ |_|    |______\_/ \___|_| |_|\__| |_|  |_|\__,_|_| |_|\__,_|_|\___|_|
 *
 *
 */

/**
 * @brief      Event handler for when Rx buffer is full or buffer swap has been
 *             triggered by idle task.
 *
 *             Copy data from DMA buffer to FIFO buffer.
 *             Post callback if not already posted.
 *
 * @param[in]  instance  The instance
 */
static void nordic_nrf5_uart_event_handler_endrx(int instance)
{
    /* Read out active bank flag and swap DMA buffers. */
    uint8_t active_bank = nordic_nrf5_uart_state[instance].active_bank;
    nordic_nrf5_uart_state[instance].active_bank = active_bank ^ 0x01;

    /* Get number of bytes in DMA buffer. */
    uint32_t available = nrf_uarte_rx_amount_get(nordic_nrf5_uart_register[instance]);

    if (available > 0) {

        /* Copy data from DMA buffer to FIFO buffer. */
        for (size_t index = 0; index < available; index++) {

            /* Atomic FIFO can be used safely without disabling interrutps. */
            nrf_atfifo_item_put_t fifo_context;

            /* Get pointer to available space. */
            uint8_t *byte = (uint8_t *) nrf_atfifo_item_alloc(nordic_nrf5_uart_state[instance].fifo, &fifo_context);

            if (byte != NULL) {

                /* Copy 1 byte from DMA buffer and commit to FIFO buffer. */
                *byte = nordic_nrf5_uart_state[instance].buffer[active_bank][index];
                nrf_atfifo_item_put(nordic_nrf5_uart_state[instance].fifo, &fifo_context);
                core_util_atomic_decr_u32(&nordic_nrf5_uart_state[instance].fifo_free_count, 1);

            } else {

                /* Buffer overflow. */
                break;
            }
        }

        /* Signal callback through lower priority SWI if not already posted. */
        if (nordic_nrf5_uart_state[instance].callback_posted == false) {

            nordic_nrf5_uart_state[instance].callback_posted = true;
            nordic_swi_rx_trigger(instance);
        }
    }
}

/**
 * @brief      Event handler for when DMA has been armed with Rx buffer.
 *
 *             Arm Rx buffer with second buffer for optimal reception.
 *
 * @param[in]  instance  The instance
 */
static void nordic_nrf5_uart_event_handler_rxstarted(int instance)
{
    uint8_t next_bank = nordic_nrf5_uart_state[instance].active_bank ^ 0x01;

    nrf_uarte_rx_buffer_set(nordic_nrf5_uart_register[instance], nordic_nrf5_uart_state[instance].buffer[next_bank], DMA_BUFFER_SIZE);
    if (nordic_nrf5_uart_state[instance].rts != NRF_UART_PSEL_DISCONNECTED) {
        if (nordic_nrf5_uart_state[instance].fifo_free_count > FIFO_MIN) {
            /* Clear rts since we are ready to receive the next byte */
            nrfx_gpiote_clr_task_trigger(nordic_nrf5_uart_state[instance].rts);
        } else {
            /* Suspend reception since there isn't enough buffer space.
             * The function serial_getc will restart reception. */
            nordic_nrf5_uart_state[instance].rx_suspended = true;
        }
    }
}

#if DEVICE_SERIAL_ASYNCH
/**
 * @brief      Asynchronous event handler for when Rx DMA buffer is full.
 *
 * @param[in]  instance  The instance
 */
static void nordic_nrf5_uart_event_handler_endrx_asynch(int instance)
{
    /* Set Rx done and reset Rx mode to be not asynchronous. */
    nordic_nrf5_uart_state[instance].rx_asynch = false;
    core_util_atomic_store_bool(&nordic_nrf5_uart_state[instance].rx_in_progress, false);

    /* Cast handler to callback function pointer. */
    void (*callback)(void) = (void (*)(void)) nordic_nrf5_uart_state[instance].owner->rx_handler;
    uint32_t mask = nordic_nrf5_uart_state[instance].owner->rx_mask;

    /* Signal error if event mask matches and event handler is set. */
    if (callback && (mask & SERIAL_EVENT_RX_COMPLETE)) {

        /* Store event value so it can be read back. */
        nordic_nrf5_uart_state[instance].owner->rx_event = SERIAL_EVENT_RX_COMPLETE;

        /* Signal callback handler. */
        callback();
    }
}
#endif

/**
 * @brief      UARTE event handler.
 *
 *             Collect signals from UARTE0 and UARTE1 ISR and translate to instance.
 *
 * @param[in]  instance  The instance
 */
static void nordic_nrf5_uart_event_handler(int instance)
{
    /* DMA buffer is full or has been swapped out by idle timeout. */
    if (nrf_uarte_event_check(nordic_nrf5_uart_register[instance], NRF_UARTE_EVENT_ENDRX))
    {
        nrf_uarte_event_clear(nordic_nrf5_uart_register[instance], NRF_UARTE_EVENT_ENDRX);

#if DEVICE_SERIAL_ASYNCH
        /* Call appropriate event handler based on current mode. */
        if (nordic_nrf5_uart_state[instance].rx_asynch) {

            nordic_nrf5_uart_event_handler_endrx_asynch(instance);
        } else
#endif
        {
            nordic_nrf5_uart_event_handler_endrx(instance);
        }
    }

    /* Rx DMA buffer has been armed.
     *
     * Warning - Do not process NRF_UARTE_EVENT_RXSTARTED if NRF_UARTE_EVENT_ENDRX is pending.
     * NRF_UARTE_EVENT_RXSTARTED must be processed first or nordic_nrf5_uart_event_handler_rxstarted
     * will setup the wrong DMA buffer and cause data to be lost.
     */
    if (nrf_uarte_event_check(nordic_nrf5_uart_register[instance], NRF_UARTE_EVENT_RXSTARTED) &&
            !nrf_uarte_event_check(nordic_nrf5_uart_register[instance], NRF_UARTE_EVENT_ENDRX))
    {
        nrf_uarte_event_clear(nordic_nrf5_uart_register[instance], NRF_UARTE_EVENT_RXSTARTED);

        nordic_nrf5_uart_event_handler_rxstarted(instance);
    }

    /* Tx DMA buffer has been sent. */
    if (nrf_uarte_event_check(nordic_nrf5_uart_register[instance], NRF_UARTE_EVENT_ENDTX))
    {
        nrf_uarte_event_clear(nordic_nrf5_uart_register[instance], NRF_UARTE_EVENT_ENDTX);

        /* Use SWI to de-prioritize callback. */
        nordic_swi_tx_trigger(instance);
    }
}

/**
 * @brief      UARTE0 ISR.
 */
static void nordic_nrf5_uart0_handler(void)
{
    /* Call event handler with instance ID. */
    nordic_nrf5_uart_event_handler(0);
}

#if UART1_ENABLED
/**
 * @brief      UARTE1 ISR.
 */
static void nordic_nrf5_uart1_handler(void)
{
    /* Call event handler with instance ID. */
    nordic_nrf5_uart_event_handler(1);
}
#endif


/***
 *       _____             __ _                       _   _
 *      / ____|           / _(_)                     | | (_)
 *     | |     ___  _ __ | |_ _  __ _ _   _ _ __ __ _| |_ _  ___  _ __
 *     | |    / _ \| '_ \|  _| |/ _` | | | | '__/ _` | __| |/ _ \| '_ \
 *     | |___| (_) | | | | | | | (_| | |_| | | | (_| | |_| | (_) | | | |
 *      \_____\___/|_| |_|_| |_|\__, |\__,_|_|  \__,_|\__|_|\___/|_| |_|
 *                               __/ |
 *                              |___/
 */

/**
 * @brief      Configure UARTE based on serial object settings.
 *
 *             Common for both Rx and Tx.
 *
 * @param      obj   The object
 */
static void nordic_nrf5_uart_configure_object(serial_t *obj)
{
    MBED_ASSERT(obj);

#if DEVICE_SERIAL_ASYNCH
    struct serial_s *uart_object = &obj->serial;
#else
    struct serial_s *uart_object = obj;
#endif

    /* Configure Tx and Rx pins. */
    if (uart_object->tx != NRF_UART_PSEL_DISCONNECTED) {

        nrf_gpio_pin_set(uart_object->tx);
        nrf_gpio_cfg_output(uart_object->tx);
    }

    if (uart_object->rx != NRF_UART_PSEL_DISCONNECTED) {

        nrf_gpio_cfg_input(uart_object->rx, NRF_GPIO_PIN_NOPULL);
    }

    nrf_uarte_txrx_pins_set(nordic_nrf5_uart_register[uart_object->instance],
                            uart_object->tx,
                            uart_object->rx);

    /* Set hardware flow control pins. */
    if (uart_object->hwfc == NRF_UART_HWFC_ENABLED) {

        /* Check if pin is set before configuring it. */
        if (uart_object->cts != NRF_UART_PSEL_DISCONNECTED) {

            nrf_gpio_cfg_input(uart_object->cts, NRF_GPIO_PIN_NOPULL);
        }

        /* Only let UARTE module handle CTS, RTS is handled manually due to buggy UARTE logic. */
        nrf_uarte_hwfc_pins_set(nordic_nrf5_uart_register[uart_object->instance],
                                NRF_UART_PSEL_DISCONNECTED,
                                uart_object->cts);
    }

    /* Check if the rts pin changed */
    if (uart_object->rts != nordic_nrf5_uart_state[uart_object->instance].rts) {
        uint32_t ret;

        /* Disable the PPI interconnect */
        ret = nrfx_ppi_channel_disable(nordic_nrf5_uart_state[uart_object->instance].ppi_rts);
        MBED_ASSERT(ret == NRF_SUCCESS);

        /* Free flow control gpiote pin if it was previously set */
        if (nordic_nrf5_uart_state[uart_object->instance].rts != NRF_UART_PSEL_DISCONNECTED) {
            nrfx_gpiote_out_uninit((nrfx_gpiote_pin_t)uart_object->rts);
        }

        /* Allocate and enable flow control gpiote pin if it is being used */
        if (uart_object->rts != NRF_UART_PSEL_DISCONNECTED) {

            static const nrfx_gpiote_out_config_t config = {
                .init_state = NRF_GPIOTE_INITIAL_VALUE_HIGH,
                .task_pin   = true,
                .action     = NRF_GPIOTE_POLARITY_LOTOHI
            };

            /* Allocate gpiote channel */
            ret = nrfx_gpiote_out_init((nrfx_gpiote_pin_t)uart_object->rts, &config);
            if (ret == NRF_ERROR_INVALID_STATE) {
                /* Pin was previously set to GPIO so uninitialize it */
                nrfx_gpiote_out_uninit((nrfx_gpiote_pin_t)uart_object->rts);
                ret = nrfx_gpiote_out_init((nrfx_gpiote_pin_t)uart_object->rts, &config);
            }
            MBED_ASSERT(ret == NRF_SUCCESS);

            /* Set RTS high on the ENDRX event */
            ret = nrfx_ppi_channel_assign(nordic_nrf5_uart_state[uart_object->instance].ppi_rts,
                                          nrf_uarte_event_address_get(nordic_nrf5_uart_register[uart_object->instance], NRF_UARTE_EVENT_ENDRX),
                                          nrfx_gpiote_out_task_addr_get(uart_object->rts));
            MBED_ASSERT(ret == NRF_SUCCESS);

            ret = nrfx_ppi_channel_enable(nordic_nrf5_uart_state[uart_object->instance].ppi_rts);
            MBED_ASSERT(ret == NRF_SUCCESS);

            /* Enable gpiote task - rts pin can no longer be used as GPIO at this point */
            nrfx_gpiote_out_task_enable((nrfx_gpiote_pin_t)uart_object->rts);
        }

        nordic_nrf5_uart_state[uart_object->instance].rts = uart_object->rts;
    }

    /* Enable flow control and parity. */
    nrf_uarte_configure(nordic_nrf5_uart_register[uart_object->instance],
                        (nrf_uarte_parity_t) uart_object->parity,
                        (nrf_uarte_hwfc_t) uart_object->hwfc);

    /* Set baudrate. */
    nrf_uarte_baudrate_set(nordic_nrf5_uart_register[uart_object->instance],
                           (nrf_uarte_baudrate_t) uart_object->baudrate);
}

/**
 * @brief      Setup non-asynchronous reception.
 *
 * @param[in]  instance  The instance
 */
static void nordic_nrf5_uart_configure_rx(int instance)
{
    /* Disable interrupts during confiration. */
    nrf_uarte_int_disable(nordic_nrf5_uart_register[instance], NRF_UARTE_INT_RXSTARTED_MASK |
                                                               NRF_UARTE_INT_ENDRX_MASK);

    /* Clear FIFO buffer. */
    nrf_atfifo_clear(nordic_nrf5_uart_state[instance].fifo);
    nordic_nrf5_uart_state[instance].fifo_free_count = UART0_FIFO_BUFFER_SIZE;

    /* Clear Rx related events. */
    nrf_uarte_event_clear(nordic_nrf5_uart_register[instance], NRF_UARTE_EVENT_RXSTARTED);
    nrf_uarte_event_clear(nordic_nrf5_uart_register[instance], NRF_UARTE_EVENT_ENDRX);

    /* Enable shortcut between buffer full and begin reception on next buffer armed. */
    nrf_uarte_shorts_enable(nordic_nrf5_uart_register[instance], NRF_UARTE_SHORT_ENDRX_STARTRX);

    /* Reset bank flag. */
    nordic_nrf5_uart_state[instance].active_bank = 0;

    /* Arm first DMA buffer. */
    nrf_uarte_rx_buffer_set(nordic_nrf5_uart_register[instance],
                            nordic_nrf5_uart_state[instance].buffer[0],
                            DMA_BUFFER_SIZE);

    /* Set non-asynchronous mode. */
    nordic_nrf5_uart_state[instance].rx_asynch = false;

    /* Clear suspend condition */
    nordic_nrf5_uart_state[instance].rx_suspended = false;

    /* Enable interrupts again. */
    nrf_uarte_int_enable(nordic_nrf5_uart_register[instance], NRF_UARTE_INT_RXSTARTED_MASK |
                                                              NRF_UARTE_INT_ENDRX_MASK);
}

#if DEVICE_SERIAL_ASYNCH
/**
 * @brief      Setup asynchronous reception.
 *
 * @param[in]  instance  The instance
 */
static void nordic_nrf5_uart_configure_rx_asynch(int instance)
{
    /* Disable Rx related interrupts. */
    nrf_uarte_int_disable(nordic_nrf5_uart_register[instance], NRF_UARTE_INT_RXSTARTED_MASK |
                                                               NRF_UARTE_INT_ENDRX_MASK);

    /* Clear Rx related events. */
    nrf_uarte_event_clear(nordic_nrf5_uart_register[instance], NRF_UARTE_EVENT_RXSTARTED);
    nrf_uarte_event_clear(nordic_nrf5_uart_register[instance], NRF_UARTE_EVENT_ENDRX);

    /* Disable shortcut. Next Rx buffer must be manually started. */
    nrf_uarte_shorts_disable(nordic_nrf5_uart_register[instance], NRF_UARTE_SHORT_ENDRX_STARTRX);

    /* Set asynchronous mode. */
    nordic_nrf5_uart_state[instance].rx_asynch = true;

    /* Clear suspend condition */
    nordic_nrf5_uart_state[instance].rx_suspended = false;

    /* Enable Rx interrupt. */
    nrf_uarte_int_enable(nordic_nrf5_uart_register[instance], NRF_UARTE_INT_ENDRX_MASK);
}
#endif

/**
 * @brief      Main configuration function.
 *
 * @param      obj   The serial object
 */
static void nordic_nrf5_serial_configure(serial_t *obj)
{
    MBED_ASSERT(obj);

#if DEVICE_SERIAL_ASYNCH
    struct serial_s *uart_object = &obj->serial;
#else
    struct serial_s *uart_object = obj;
#endif

    /* Get object instance. */
    int instance = uart_object->instance;

    /* Only configure if instance owner has changed or an update is forced. */
    if ((uart_object != nordic_nrf5_uart_state[instance].owner) || (uart_object->update)) {

        /* Configure common setting. */
        nordic_nrf5_uart_configure_object(obj);

        /* Set new owner. */
        nordic_nrf5_uart_state[instance].owner = uart_object;
        uart_object->update = false;

#if DEVICE_SERIAL_ASYNCH
        /* Set asynchronous mode. */
        if (uart_object->rx_asynch == true) {

            nordic_nrf5_uart_configure_rx_asynch(instance);
        } else
#endif
        {
            /* Set non-asynchronous mode. */
            nordic_nrf5_uart_configure_rx(instance);
            nrf_uarte_task_trigger(nordic_nrf5_uart_register[instance],
                                   NRF_UARTE_TASK_STARTRX);
        }
    }
#if DEVICE_SERIAL_ASYNCH
    /* Owner hasn't changed but mode has. Reconfigure. */
    else if ((uart_object->rx_asynch == false) && (nordic_nrf5_uart_state[instance].rx_asynch == true)) {

        nordic_nrf5_uart_configure_rx(instance);
        nrf_uarte_task_trigger(nordic_nrf5_uart_register[instance],
                               NRF_UARTE_TASK_STARTRX);

    /* Owner hasn't changed but mode has. Reconfigure. */
    } else if ((uart_object->rx_asynch == true) && (nordic_nrf5_uart_state[instance].rx_asynch == false)) {

        nordic_nrf5_uart_configure_rx_asynch(instance);
    }
#endif
}

/***
 *      __  __ _              _   _    _          _                 _____ _____
 *     |  \/  | |            | | | |  | |   /\   | |          /\   |  __ \_   _|
 *     | \  / | |__   ___  __| | | |__| |  /  \  | |         /  \  | |__) || |
 *     | |\/| | '_ \ / _ \/ _` | |  __  | / /\ \ | |        / /\ \ |  ___/ | |
 *     | |  | | |_) |  __/ (_| | | |  | |/ ____ \| |____   / ____ \| |    _| |_
 *     |_|  |_|_.__/ \___|\__,_| |_|  |_/_/    \_\______| /_/    \_\_|   |_____|
 *
 *
 */

/** Initialize the serial peripheral. It sets the default parameters for serial
 *  peripheral, and configures its specifieds pins.
 *
 * Param obj The serial object
 * Param tx  The TX pin name
 * Param rx  The RX pin name
 */
void serial_init(serial_t *obj, PinName tx, PinName rx)
{
    MBED_ASSERT(obj);

#if DEVICE_SERIAL_ASYNCH
    struct serial_s *uart_object = &obj->serial;
#else
    struct serial_s *uart_object = obj;
#endif

    /* Only initialize on first call. */
    static bool first_init = true;
    if (first_init) {
        uint32_t ret;
        first_init = false;

        /* Initialize components that serial relies on. */
        if (!nrfx_gpiote_is_init()) {
            nrfx_gpiote_init();
        }

        /* Enable interrupts for SWI. */
        NVIC_SetVector(SWI0_EGU0_IRQn, (uint32_t) nordic_nrf5_uart_swi0);
        NRFX_IRQ_PRIORITY_SET(SWI0_EGU0_IRQn, APP_IRQ_PRIORITY_LOWEST);
        NRFX_IRQ_ENABLE(SWI0_EGU0_IRQn);

        /* Initialize FIFO buffer for UARTE0. */
        NRF_ATFIFO_INIT(nordic_nrf5_uart_fifo_0);
        nordic_nrf5_uart_state[0].fifo = nordic_nrf5_uart_fifo_0;

        /* Initialize owner to NULL. */
        nordic_nrf5_uart_state[0].owner = NULL;

        /* Allocate a PPI channel for flow control */
        ret = nrfx_ppi_channel_alloc(&nordic_nrf5_uart_state[0].ppi_rts);
        MBED_ASSERT(ret == NRF_SUCCESS);

        /* Clear RTS */
        nordic_nrf5_uart_state[0].rts = NRF_UART_PSEL_DISCONNECTED;

        /* Clear any old events and enable interrupts for UARTE0. */
        nrf_uarte_int_disable(nordic_nrf5_uart_register[0], 0xFFFFFFFF);

        NVIC_SetVector(UARTE0_UART0_IRQn, (uint32_t) nordic_nrf5_uart0_handler);
        NRFX_IRQ_PRIORITY_SET(UARTE0_UART0_IRQn, APP_IRQ_PRIORITY_HIGHEST);
        NRFX_IRQ_ENABLE(UARTE0_UART0_IRQn);

#if UART1_ENABLED
        /* Initialize FIFO buffer for UARTE1. */
        NRF_ATFIFO_INIT(nordic_nrf5_uart_fifo_1);
        nordic_nrf5_uart_state[1].fifo = nordic_nrf5_uart_fifo_1;

        /* Initialize owner to NULL. */
        nordic_nrf5_uart_state[1].owner = NULL;

        /* Allocate a PPI channel for flow control */
        ret = nrfx_ppi_channel_alloc(&nordic_nrf5_uart_state[1].ppi_rts);
        MBED_ASSERT(ret == NRF_SUCCESS);

        /* Clear RTS */
        nordic_nrf5_uart_state[1].rts = NRF_UART_PSEL_DISCONNECTED;

        /* Clear any old events and enable interrupts for UARTE1. */
        nrf_uarte_int_disable(nordic_nrf5_uart_register[1], 0xFFFFFFFF);

        NVIC_SetVector(UARTE1_IRQn, (uint32_t) nordic_nrf5_uart1_handler);
        NRFX_IRQ_PRIORITY_SET(UARTE1_IRQn, APP_IRQ_PRIORITY_HIGHEST);
        NRFX_IRQ_ENABLE(UARTE1_IRQn);
#endif
    }

    /* Get instance ID based on provided pins. */
    int instance = pin_instance_uart(tx, rx);

    uart_object->instance = instance;

    /* Increment usage counter for this instance. */
    nordic_nrf5_uart_state[instance].usage_counter++;

    /* Enable instance on first usage. */
    if (nordic_nrf5_uart_state[instance].usage_counter == 1) {

        nrf_uarte_enable(nordic_nrf5_uart_register[instance]);

        /* In order to support printing with interrupts disabled serial_putc
         * must busy wait on NRF_UARTE_EVENT_TXDRDY. This event cannot be set
         * manually but must be set by the UARTE module after a character has
         * been sent.
         *
         * The following code sends a dummy character into the void so that
         * NRF_UARTE_EVENT_TXDRDY is correctly set.
         */

        /* Ensure pins are disconnected. */
        nrf_uarte_txrx_pins_set(nordic_nrf5_uart_register[instance],
                                NRF_UART_PSEL_DISCONNECTED,
                                NRF_UART_PSEL_DISCONNECTED);

        /* Set maximum baud rate to minimize waiting. */
        nrf_uarte_baudrate_set(nordic_nrf5_uart_register[instance],
                               NRF_UARTE_BAUDRATE_1000000);

        /* Send character. */
        nrf_uarte_tx_buffer_set(nordic_nrf5_uart_register[instance],
                                &nordic_nrf5_uart_state[instance].tx_data,
                                1);
        nrf_uarte_event_clear(nordic_nrf5_uart_register[instance], NRF_UARTE_EVENT_ENDTX);
        nrf_uarte_task_trigger(nordic_nrf5_uart_register[instance], NRF_UARTE_TASK_STARTTX);

        /* Wait until NRF_UARTE_EVENT_TXDRDY is set before proceeding. */
        bool done = false;
        do {
            done = nrf_uarte_event_check(nordic_nrf5_uart_register[instance],
                                (nrf_uarte_event_t) NRF_UARTE_EVENT_TXDRDY);
        } while(done == false);
    }

    /* Store pins in serial object. */
    if (tx == NC) {

        uart_object->tx = NRF_UART_PSEL_DISCONNECTED;
    } else {

        uart_object->tx = tx;
    }

    if (rx == NC) {

        uart_object->rx = NRF_UART_PSEL_DISCONNECTED;
    } else {

        uart_object->rx = rx;
    }

    /* Set default parity, baud rate, and callback handler. */
    uart_object->parity = NRF_UART_PARITY_EXCLUDED;
    uart_object->baudrate = NRF_UART_BAUDRATE_9600;
    uart_object->cts = NRF_UART_PSEL_DISCONNECTED;
    uart_object->rts = NRF_UART_PSEL_DISCONNECTED;
    uart_object->hwfc = NRF_UART_HWFC_DISABLED;
    uart_object->handler = 0;

    /* The STDIO object is stored in this file. Set the flag once initialized. */
    if (obj == &stdio_uart) {
        stdio_uart_inited = 1;
    }

    /* Take ownership and configure UART. */
    uart_object->update = true;
    nordic_nrf5_serial_configure(obj);
}

/** Release the serial peripheral, not currently invoked. It requires further
 *  resource management.
 *
 * Param obj The serial object
 */
void serial_free(serial_t *obj)
{
    MBED_ASSERT(obj);

#if DEVICE_SERIAL_ASYNCH
    struct serial_s *uart_object = &obj->serial;
#else
    struct serial_s *uart_object = obj;
#endif

    int instance = uart_object->instance;

    /* Only consider disabling UARTE if number of users is not zero. */
    if (nordic_nrf5_uart_state[instance].usage_counter > 0) {

        /* Decrement usage counter for this instance. */
        nordic_nrf5_uart_state[instance].usage_counter--;

        /* Disable instance when not in use. */
        if (nordic_nrf5_uart_state[instance].usage_counter == 0) {

            nrf_uarte_disable(nordic_nrf5_uart_register[instance]);
            
            /* Turn NRF_UARTE0_BASE or NRF_UARTE1_BASE power off and on to reset peripheral. */
            if (instance == 0) {
                *(volatile uint32_t *)0x40002FFC = 0;
                *(volatile uint32_t *)0x40002FFC;
                *(volatile uint32_t *)0x40002FFC = 1;
            }
#if UART1_ENABLED
            else {
                *(volatile uint32_t *)0x40028FFC = 0;
                *(volatile uint32_t *)0x40028FFC;
                *(volatile uint32_t *)0x40028FFC = 1;
            }
#endif
            
        }
    }
}

/** Configure the baud rate
 *
 * Param obj      The serial object
 * Param baudrate The baud rate to be configured
 */
void serial_baud(serial_t *obj, int baudrate)
{
    MBED_ASSERT(obj);

#if DEVICE_SERIAL_ASYNCH
    struct serial_s *uart_object = &obj->serial;
#else
    struct serial_s *uart_object = obj;
#endif

    nrf_uarte_baudrate_t new_rate = NRF_UART_BAUDRATE_9600;

    /* Round down to nearest supported baud rate. */
    if (baudrate < 2400) {
        new_rate = NRF_UARTE_BAUDRATE_1200;
    } else if (baudrate < 4800) {
        new_rate = NRF_UARTE_BAUDRATE_2400;
    } else if (baudrate < 9600) {
        new_rate = NRF_UARTE_BAUDRATE_4800;
    } else if (baudrate < 14400) {
        new_rate = NRF_UARTE_BAUDRATE_9600;
    } else if (baudrate < 19200) {
        new_rate = NRF_UARTE_BAUDRATE_14400;
    } else if (baudrate < 28800) {
        new_rate = NRF_UARTE_BAUDRATE_19200;
    } else if (baudrate < 38400) {
        new_rate = NRF_UARTE_BAUDRATE_28800;
    } else if (baudrate < 57600) {
        new_rate = NRF_UARTE_BAUDRATE_38400;
    } else if (baudrate < 76800) {
        new_rate = NRF_UARTE_BAUDRATE_57600;
    } else if (baudrate < 115200) {
        new_rate = NRF_UARTE_BAUDRATE_76800;
    } else if (baudrate < 230400) {
        new_rate = NRF_UARTE_BAUDRATE_115200;
    } else if (baudrate < 250000) {
        new_rate = NRF_UARTE_BAUDRATE_230400;
    } else if (baudrate < 460800) {
        new_rate = NRF_UARTE_BAUDRATE_250000;
    } else if (baudrate < 921600) {
        new_rate = NRF_UARTE_BAUDRATE_460800;
    } else if (baudrate < 1000000) {
        new_rate = NRF_UARTE_BAUDRATE_921600;
    } else {
        new_rate = NRF_UARTE_BAUDRATE_1000000;
    }

    /* Force reconfiguration next time serial object is owner if baud rate has changed. */
    if (uart_object->baudrate != new_rate) {

        uart_object->baudrate = new_rate;
        uart_object->update = true;
    }
}

/** Configure the format. Set the number of bits, parity and the number of stop bits
 *
 * Param obj       The serial object
 * Param data_bits The number of data bits
 * Param parity    The parity
 * Param stop_bits The number of stop bits
 */
void serial_format(serial_t *obj, int data_bits, SerialParity parity, int stop_bits)
{
    MBED_ASSERT(obj);

    /**
     * Only 8-bit mode, None/Even parity, and 1 stop bit supported by hardware.
     */
    MBED_ASSERT(data_bits == 8);
    MBED_ASSERT((parity == ParityNone) || (parity == ParityEven));
    MBED_ASSERT(stop_bits == 1);

#if DEVICE_SERIAL_ASYNCH
    struct serial_s *uart_object = &obj->serial;
#else
    struct serial_s *uart_object = obj;
#endif

    /**
     * Only force change if parity has changed.
     */
    if ((uart_object->parity != NRF_UART_PARITY_EXCLUDED) && (parity == ParityNone)) {

        uart_object->parity = NRF_UART_PARITY_EXCLUDED;
        uart_object->update = true;

    } else if ((uart_object->parity != NRF_UART_PARITY_INCLUDED) && (parity == ParityEven)) {

        uart_object->parity = NRF_UART_PARITY_INCLUDED;
        uart_object->update = true;
    }
}

/** Configure the serial for the flow control. It sets flow control in the hardware
 *  if a serial peripheral supports it, otherwise software emulation is used.
 *
 * Param obj    The serial object
 * Param type   The type of the flow control. Look at the available FlowControl types.
 * Param rxflow The TX pin name
 * Param txflow The RX pin name
 */
void serial_set_flow_control(serial_t *obj, FlowControl type, PinName rxflow, PinName txflow)
{
    MBED_ASSERT(obj);

#if DEVICE_SERIAL_ASYNCH
    struct serial_s *uart_object = &obj->serial;
#else
    struct serial_s *uart_object = obj;
#endif

    /**
     * Convert Mbed pin names to Nordic pin names.
     */
    uart_object->cts = ((txflow == NC) || (type == FlowControlRTS)) ? NRF_UART_PSEL_DISCONNECTED : (uint32_t) txflow;
    uart_object->rts = ((rxflow == NC) || (type == FlowControlCTS)) ? NRF_UART_PSEL_DISCONNECTED : (uint32_t) rxflow;
    uart_object->hwfc = (type == FlowControlNone) ? NRF_UART_HWFC_DISABLED : NRF_UART_HWFC_ENABLED;

    /* Force reconfiguration next time object is owner. */
    uart_object->update = true;
    nordic_nrf5_serial_configure(obj);
}

/** Clear the serial peripheral
 *
 * Param obj The serial object
 */
void serial_clear(serial_t *obj)
{
    MBED_ASSERT(obj);

#if DEVICE_SERIAL_ASYNCH
    struct serial_s *uart_object = &obj->serial;
#else
    struct serial_s *uart_object = obj;
#endif

    /**
     * Reconfigure UART.
     */
    uart_object->update = true;
    nordic_nrf5_uart_configure_object(obj);
}

/** Set the break
 *
 * Param obj The serial object
 */
void serial_break_set(serial_t *obj)
{
    MBED_ASSERT(obj);

#if DEVICE_SERIAL_ASYNCH
    struct serial_s *uart_object = &obj->serial;
#else
    struct serial_s *uart_object = obj;
#endif

    /* Set Tx pin low. */
    nrf_gpio_pin_clear(uart_object->tx);
}

/** Clear the break
 *
 * Param obj The serial object
 */
void serial_break_clear(serial_t *obj)
{
    MBED_ASSERT(obj);

#if DEVICE_SERIAL_ASYNCH
    struct serial_s *uart_object = &obj->serial;
#else
    struct serial_s *uart_object = obj;
#endif

    /* Set Tx pin high (default). */
    nrf_gpio_pin_set(uart_object->tx);
}

/** Configure the TX pin for UART function.
 *
 * Param tx The pin name used for TX
 */
void serial_pinout_tx(PinName tx)
{
    /**
     * Legacy API. Not used by Mbed.
     */
    MBED_ASSERT(0);
}

/***
 *       _____ _                 _                 _____ _____
 *      / ____(_)               | |          /\   |  __ \_   _|
 *     | (___  _ _ __ ___  _ __ | | ___     /  \  | |__) || |
 *      \___ \| | '_ ` _ \| '_ \| |/ _ \   / /\ \ |  ___/ | |
 *      ____) | | | | | | | |_) | |  __/  / ____ \| |    _| |_
 *     |_____/|_|_| |_| |_| .__/|_|\___| /_/    \_\_|   |_____|
 *                        | |
 *                        |_|
 */

/** The serial interrupt handler registration
 *
 * Param obj     The serial object
 * Param handler The interrupt handler which will be invoked when the interrupt fires
 * Param id      The SerialBase object
 */
void serial_irq_handler(serial_t *obj, uart_irq_handler handler, uint32_t id)
{
    MBED_ASSERT(obj);

#if DEVICE_SERIAL_ASYNCH
    struct serial_s *uart_object = &obj->serial;
#else
    struct serial_s *uart_object = obj;
#endif

    /* Store handler and ID in serial object. */
    uart_object->handler = (uint32_t) handler;
    uart_object->context = id;
}

/** Configure serial interrupt. This function is used for word-approach
 *
 * Param obj    The serial object
 * Param irq    The serial IRQ type (RX or TX)
 * Param enable Set to non-zero to enable events, or zero to disable them
 */
void serial_irq_set(serial_t *obj, SerialIrq irq, uint32_t enable)
{
    MBED_ASSERT(obj);

#if DEVICE_SERIAL_ASYNCH
    struct serial_s *uart_object = &obj->serial;
#else
    struct serial_s *uart_object = obj;
#endif

    /* Convert Mbed type to Nordic IRQ mask. */
    uint32_t type = (irq == TxIrq) ? NORDIC_TX_IRQ : NORDIC_RX_IRQ;

    /* Enable/disable interrupt bit mask. */
    if (enable) {

        uart_object->mask |= type;
        nordic_nrf5_serial_configure(obj);

    } else {

        uart_object->mask &= ~type;
    }
}

/** Get character. This is a blocking call, waiting for a character
 *
 * Param obj The serial object
 */
int serial_getc(serial_t *obj)
{
    MBED_ASSERT(obj);

#if DEVICE_SERIAL_ASYNCH
    struct serial_s *uart_object = &obj->serial;
    uart_object->rx_asynch = false;
#else
    struct serial_s *uart_object = obj;
#endif

    int instance = uart_object->instance;

    /* Take ownership and configure UART if necessary. */
    nordic_nrf5_serial_configure(obj);

    /**
     * Use head and tail pointer in FIFO to determine whether there is data available.
     */
    nrf_atfifo_t *fifo = nordic_nrf5_uart_state[instance].fifo;

    volatile uint16_t *head = &fifo->head.pos.rd;
    volatile uint16_t *tail = &fifo->tail.pos.rd;

    /* serial_getc is a blocking call. */
    while (*head == *tail);

    /* Get 1 byte from FIFO buffer. The buffer is atomic
     * and doesn't need to be protected in a critical section.
     */
    nrf_atfifo_item_get_t context;
    uint8_t *byte = (uint8_t *) nrf_atfifo_item_get(fifo, &context);
    nrf_atfifo_item_free(fifo, &context);
    core_util_atomic_incr_u32(&nordic_nrf5_uart_state[instance].fifo_free_count, 1);
    if (nordic_nrf5_uart_state[instance].rx_suspended) {
        nordic_nrf5_uart_state[instance].rx_suspended = false;
        nrfx_gpiote_clr_task_trigger(nordic_nrf5_uart_state[instance].rts);
    }

    return *byte;
}

/** Send a character. This is a blocking call, waiting for a peripheral to be available
 *  for writing
 *
 * Param obj The serial object
 * Param c   The character to be sent
 */
void serial_putc(serial_t *obj, int character)
{
    bool done = false;
    MBED_ASSERT(obj);

#if DEVICE_SERIAL_ASYNCH
    struct serial_s *uart_object = &obj->serial;
#else
    struct serial_s *uart_object = obj;
#endif

    int instance = uart_object->instance;

    nordic_nrf5_serial_configure(obj);

    /* Wait until UART is ready to send next character. */
    do {
        done = nrf_uarte_event_check(nordic_nrf5_uart_register[instance], NRF_UARTE_EVENT_TXDRDY);
    } while(done == false);

    nrf_uarte_event_clear(nordic_nrf5_uart_register[instance], NRF_UARTE_EVENT_TXDRDY);

    /* Arm Tx DMA buffer. */
    nordic_nrf5_uart_state[instance].tx_data = character;
    nrf_uarte_tx_buffer_set(nordic_nrf5_uart_register[instance],
                            &nordic_nrf5_uart_state[instance].tx_data,
                            1);

    /* Clear Tx event and enable Tx interrupts. */
    nrf_uarte_event_clear(nordic_nrf5_uart_register[instance], NRF_UARTE_EVENT_ENDTX);
    nrf_uarte_int_enable(nordic_nrf5_uart_register[instance], NRF_UARTE_INT_ENDTX_MASK);

    /* Start transfer. */
    nrf_uarte_task_trigger(nordic_nrf5_uart_register[instance], NRF_UARTE_TASK_STARTTX);
}

/** Check if the serial peripheral is readable
 *
 * Param obj The serial object
 * Return Non-zero value if a character can be read, 0 if nothing to read
 */
int serial_readable(serial_t *obj)
{
    MBED_ASSERT(obj);

#if DEVICE_SERIAL_ASYNCH
    struct serial_s *uart_object = &obj->serial;
    uart_object->rx_asynch = false;
#else
    struct serial_s *uart_object = obj;
#endif

    int instance = uart_object->instance;

    /* Take ownership and configure UART if necessary. */
    nordic_nrf5_serial_configure(obj);

    /**
     * Use head and tail pointer in FIFO to determine whether there is data available.
     */
    nrf_atfifo_t *fifo = nordic_nrf5_uart_state[instance].fifo;

    return (fifo->head.pos.rd != fifo->tail.pos.rd);
}

/** Check if the serial peripheral is writable
 *
 * Param obj The serial object
 * Return Non-zero value if a character can be written, 0 otherwise.
 */
int serial_writable(serial_t *obj)
{
    MBED_ASSERT(obj);

#if DEVICE_SERIAL_ASYNCH
    struct serial_s *uart_object = &obj->serial;
#else
    struct serial_s *uart_object = obj;
#endif

    int instance = uart_object->instance;

    return (!core_util_atomic_load_bool(&nordic_nrf5_uart_state[instance].tx_in_progress) &&
            (nrf_uarte_event_check(nordic_nrf5_uart_register[instance], NRF_UARTE_EVENT_TXDRDY)));
}

const PinMap *serial_tx_pinmap()
{
    return PinMap_UART_testing;
}

const PinMap *serial_rx_pinmap()
{
    return PinMap_UART_testing;
}

const PinMap *serial_cts_pinmap()
{
    return PinMap_UART_testing;
}

const PinMap *serial_rts_pinmap()
{
    return PinMap_UART_testing;
}

/***
 *                                    _                                                _____ _____
 *         /\                        | |                                         /\   |  __ \_   _|
 *        /  \   ___ _   _ _ __   ___| |__  _ __ ___  _ __   ___  _   _ ___     /  \  | |__) || |
 *       / /\ \ / __| | | | '_ \ / __| '_ \| '__/ _ \| '_ \ / _ \| | | / __|   / /\ \ |  ___/ | |
 *      / ____ \\__ \ |_| | | | | (__| | | | | | (_) | | | | (_) | |_| \__ \  / ____ \| |    _| |_
 *     /_/    \_\___/\__, |_| |_|\___|_| |_|_|  \___/|_| |_|\___/ \__,_|___/ /_/    \_\_|   |_____|
 *                    __/ |
 *                   |___/
 */

#if DEVICE_SERIAL_ASYNCH

/** Begin asynchronous TX transfer. The used buffer is specified in the serial object,
 *  tx_buff
 *
 * Param obj       The serial object
 * Param tx        The transmit buffer
 * Param tx_length The number of bytes to transmit
 * Param tx_width  Deprecated argument
 * Param handler   The serial handler
 * Param event     The logical OR of events to be registered
 * Param hint      A suggestion for how to use DMA with this transfer
 * Return Returns number of data transfered, otherwise returns 0
 */
int serial_tx_asynch(serial_t *obj, const void *tx, size_t tx_length, uint8_t tx_width, uint32_t handler, uint32_t mask, DMAUsage hint)
{
    MBED_ASSERT(obj);
    MBED_ASSERT(tx_width == 8);
    MBED_ASSERT(tx_length < 256);

    int instance = obj->serial.instance;

    /**
     * tx_in_progress acts like a mutex to ensure only one transmission can be active at a time.
     * The flag is modified using the atomic exchange function - only proceed when we see the
     * flag clear and we set it to true.
     */
    bool old_mutex;

    do {
        old_mutex = core_util_atomic_exchange_bool(&nordic_nrf5_uart_state[instance].tx_in_progress, true);
    } while (old_mutex == true);

    /* State variables. */
    int result = 0;
    bool valid = false;

    /**
     * EasyDMA can only access RAM. Check if provided buffer is in RAM or flash.
     * If the buffer is in flash, check if the FIFO buffer is large enough to store
     * the Tx data.
     */
    if (instance == 0) {

        if (nrfx_is_in_ram(tx) || (tx_length <= UART0_FIFO_BUFFER_SIZE)) {
            valid = true;
        }
    }
#if UART1_ENABLED
    else {
        if (nrfx_is_in_ram(tx) || (tx_length <= UART1_FIFO_BUFFER_SIZE)) {
            valid = true;
        }
    }
#endif

    if (valid) {

        /* Setup buffers for transfer. */
        uint8_t *buffer = NULL;

        /* Tx buffer is in RAM. */
        if (nrfx_is_in_ram(tx)) {

            buffer = (uint8_t *) tx;
        } else {

            /**
             * Tx buffer is in flash. Copy Tx buffer to FIFO buffer.
             * NOTE: this prevents simultaneous Rx using non-asynchronous API.
             */
            const uint8_t *pointer = (const uint8_t *) tx;

            for (size_t index = 0; index < tx_length; index++) {
                nordic_nrf5_uart_fifo_0_data[index] = pointer[index];
            }

            buffer = (uint8_t *) nordic_nrf5_uart_fifo_0_data;
        }

        /* Store callback handler, mask and reset event value. */
        obj->serial.tx_handler = handler;
        obj->serial.tx_mask = mask;
        obj->serial.tx_event = 0;

        /* Enable asynchronous mode and configure UART. */
        nordic_nrf5_uart_state[instance].tx_asynch = true;
        nordic_nrf5_serial_configure(obj);

        /* Clear Tx event and enable Tx interrupts. */
        nrf_uarte_event_clear(nordic_nrf5_uart_register[instance], NRF_UARTE_EVENT_ENDTX);
        nrf_uarte_int_enable(nordic_nrf5_uart_register[instance], NRF_UARTE_INT_ENDTX_MASK);

        /* Set Tx DMA buffer. */
        nrf_uarte_tx_buffer_set(nordic_nrf5_uart_register[obj->serial.instance],
                                buffer,
                                tx_length);

        /* Trigger DMA transfer. */
        nrf_uarte_task_trigger(nordic_nrf5_uart_register[obj->serial.instance],
                               NRF_UARTE_TASK_STARTTX);

        /* Setup complete, return length as sign of success. */
        result = tx_length;

    } else {

        /* Signal error if event mask matches and event handler is set. */
        if ((mask & SERIAL_EVENT_ERROR) && handler) {

            /* Cast handler to callback function pointer. */
            void (*callback)(void) = (void (*)(void)) handler;

            /* Store event value so it can be read back. */
            obj->serial.event = SERIAL_EVENT_ERROR;

            /* Signal callback handler. */
            callback();
        }
    }

    return result;
}

/** Begin asynchronous RX transfer (enable interrupt for data collecting)
 *  The used buffer is specified in the serial object - rx_buff
 *
 * Param obj        The serial object
 * Param rx         The receive buffer
 * Param rx_length  The number of bytes to receive
 * Param rx_width   Deprecated argument
 * Param handler    The serial handler
 * Param event      The logical OR of events to be registered
 * Param handler    The serial handler
 * Param char_match A character in range 0-254 to be matched
 * Param hint       A suggestion for how to use DMA with this transfer
 */
void serial_rx_asynch(serial_t *obj, void *rx, size_t rx_length, uint8_t rx_width, uint32_t handler, uint32_t mask, uint8_t char_match, DMAUsage hint)
{
    MBED_ASSERT(obj);
    MBED_ASSERT(rx_width == 8);
    MBED_ASSERT(rx_length < 256);
    MBED_ASSERT(char_match == SERIAL_RESERVED_CHAR_MATCH);  // EasyDMA based UART handling does not support char_match

    int instance = obj->serial.instance;

    /**
     * rx_in_progress acts like a mutex to ensure only one asynchronous reception can be active at a time.
     * The flag is modified using the atomic exchange function - only proceed when we see the
     * flag clear and we set it to true.
     */
    bool old_mutex;

    do {
        old_mutex = core_util_atomic_exchange_bool(&nordic_nrf5_uart_state[instance].rx_in_progress, true);
    } while (old_mutex == true);

    /* Store callback handler, mask and reset event value. */
    obj->serial.rx_handler = handler;
    obj->serial.rx_mask = mask;
    obj->serial.rx_event = 0;

    /* Enable asynchronous mode and configure UART. */
    obj->serial.rx_asynch = true;
    nordic_nrf5_serial_configure(obj);

    /* Set Rx DMA buffer. */
    nrf_uarte_rx_buffer_set(nordic_nrf5_uart_register[instance],
                            (uint8_t *) rx,
                            rx_length);

    /* Enable reception. */
    nrf_uarte_task_trigger(nordic_nrf5_uart_register[instance],
                           NRF_UARTE_TASK_STARTRX);

}

/** Attempts to determine if the serial peripheral is already in use for TX
 *
 * Param obj The serial object
 * Return Non-zero if the RX transaction is ongoing, 0 otherwise
 */
uint8_t serial_tx_active(serial_t *obj)
{
    MBED_ASSERT(obj);

    return nordic_nrf5_uart_state[obj->serial.instance].tx_asynch;
}

/** Attempts to determine if the serial peripheral is already in use for RX
 *
 * Param obj The serial object
 * Return Non-zero if the RX transaction is ongoing, 0 otherwise
 */
uint8_t serial_rx_active(serial_t *obj)
{
    MBED_ASSERT(obj);

    return nordic_nrf5_uart_state[obj->serial.instance].rx_asynch;
}

/** The asynchronous TX and RX handler.
 *
 * Param obj The serial object
 * Return Returns event flags if an RX transfer termination condition was met; otherwise returns 0
 */
int serial_irq_handler_asynch(serial_t *obj)
{
    MBED_ASSERT(obj);

    return (obj->serial.tx_event | obj->serial.rx_event);
}

/** Abort the ongoing TX transaction. It disables the enabled interupt for TX and
 *  flushes the TX hardware buffer if TX FIFO is used
 *
 * Param obj The serial object
 */
void serial_tx_abort_asynch(serial_t *obj)
{
    MBED_ASSERT(obj);

    /* Transmission might be in progress. Disable interrupts to prevent ISR from firing. */
    core_util_critical_section_enter();

    int instance = obj->serial.instance;

    /* Disable ENDTX interrupts. */
    nrf_uarte_int_disable(nordic_nrf5_uart_register[instance], NRF_UARTE_INT_ENDTX_MASK);

    /* Clear ENDTX event. */
    nrf_uarte_event_clear(nordic_nrf5_uart_register[instance], NRF_UARTE_EVENT_ENDTX);

    /* Reset Tx flags. */
    nordic_nrf5_uart_state[instance].tx_asynch = false;
    nordic_nrf5_uart_state[instance].tx_in_progress = false;

    /* Force reconfiguration. */
    obj->serial.update = true;
    nordic_nrf5_serial_configure(obj);

    /* Trigger STOP task. */
    nrf_uarte_task_trigger(nordic_nrf5_uart_register[instance],
                           NRF_UARTE_TASK_STOPTX);

    /* Enable interrupts again. */
    core_util_critical_section_exit();
}

/** Abort the ongoing RX transaction. It disables the enabled interrupt for RX and
 *  flushes the RX hardware buffer if RX FIFO is used
 *
 * Param obj The serial object
 */
void serial_rx_abort_asynch(serial_t *obj)
{
    MBED_ASSERT(obj);

    /* Transmission might be in progress. Disable interrupts to prevent ISR from firing. */
    core_util_critical_section_enter();

    /* Reset Rx flags. */
    nordic_nrf5_uart_state[obj->serial.instance].rx_asynch = false;
    nordic_nrf5_uart_state[obj->serial.instance].rx_in_progress = false;
    obj->serial.rx_asynch = false;

    /* Force reconfiguration. */
    obj->serial.update = true;
    nordic_nrf5_serial_configure(obj);

    /* Enable interrupts again. */
    core_util_critical_section_exit();
}

#endif // DEVICE_SERIAL_ASYNCH

#endif // DEVICE_SERIAL
