/**
 * Copyright (c) 2016 - 2018, 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 form under this license must not be reverse
 *    engineered, decompiled, modified and/or disassembled.
 *
 * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA 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.
 *
 */
#ifndef NRF_ATOMIC_INTERNAL_H__
#define NRF_ATOMIC_INTERNAL_H__

#include "sdk_common.h"
#include <stdbool.h>
#include <stdint.h>

#ifdef __cplusplus
extern "C" {
#endif

/**
 *
 * @defgroup nrf_atomic_internal Atomic operations internals
 * @ingroup nrf_atomic
 * @{
 *
 */

/* Only Cortex M cores > 3 support LDREX/STREX instructions*/
#if ((__CORTEX_M >= 0x03U) || (__CORTEX_SC >= 300U)) == 0
#error "Unsupported core version"
#endif

#if defined ( __CC_ARM )
static __asm uint32_t nrf_atomic_internal_mov(nrf_atomic_u32_t * p_ptr,
                                              uint32_t value,
                                              uint32_t * p_new)
{
    /* The base standard provides for passing arguments in core registers (r0-r3) and on the stack.
     * Registers r4 and r5 have to be saved on stack. Note that only even number of register push are
     * allowed. This is a requirement of the Procedure Call Standard for the ARM Architecture [AAPCS].
     * */
    push  {r4, r5}
    mov   r4, r0

loop_mov
    ldrex r0, [r4]
    mov   r5, r1
    strex r3, r5, [r4]
    cmp   r3, #0
    bne   loop_mov

    str   r5, [r2]
    pop   {r4, r5}
    bx    lr
}


static __asm uint32_t nrf_atomic_internal_orr(nrf_atomic_u32_t * p_ptr,
                                              uint32_t value,
                                              uint32_t * p_new)
{
    push  {r4, r5}
    mov   r4, r0

loop_orr
    ldrex r0, [r4]
    orr   r5, r0, r1
    strex r3, r5, [r4]
    cmp   r3, #0
    bne   loop_orr

    str   r5, [r2]
    pop   {r4, r5}
    bx    lr
}

static __asm uint32_t nrf_atomic_internal_and(nrf_atomic_u32_t * p_ptr,
                                              uint32_t value,
                                              uint32_t * p_new)
{
    push  {r4, r5}
    mov   r4, r0

loop_and
    ldrex r0, [r4]
    and   r5, r0, r1
    strex r3, r5, [r4]
    cmp   r3, #0
    bne   loop_and

    str   r5, [r2]
    pop   {r4, r5}
    bx    lr
}

static __asm uint32_t nrf_atomic_internal_eor(nrf_atomic_u32_t * p_ptr,
                                              uint32_t value,
                                              uint32_t * p_new)
{
    push  {r4, r5}
    mov   r4, r0

loop_eor
    ldrex r0, [r4]
    eor   r5, r0, r1
    strex r3, r5, [r4]
    cmp   r3, #0
    bne   loop_eor

    str   r5, [r2]
    pop   {r4, r5}
    bx    lr
}

static __asm uint32_t nrf_atomic_internal_add(nrf_atomic_u32_t * p_ptr,
                                              uint32_t value,
                                              uint32_t * p_new)
{
    push  {r4, r5}
    mov   r4, r0

loop_add
    ldrex r0, [r4]
    add   r5, r0, r1
    strex r3, r5, [r4]
    cmp   r3, #0
    bne   loop_add

    str   r5, [r2]
    pop   {r4, r5}
    bx    lr
}

static __asm uint32_t nrf_atomic_internal_sub(nrf_atomic_u32_t * p_ptr,
                                              uint32_t value,
                                              uint32_t * p_new)
{
    push  {r4, r5}
    mov   r4, r0

loop_sub
    ldrex r0, [r4]
    sub   r5, r0, r1
    strex r3, r5, [r4]
    cmp   r3, #0
    bne   loop_sub

    str   r5, [r2]
    pop   {r4, r5}
    bx    lr
}

static __asm bool nrf_atomic_internal_cmp_exch(nrf_atomic_u32_t * p_data,
                                               uint32_t *         p_expected,
                                               uint32_t           value)
{
#define RET_REG  r0
#define P_EXPC   r1
#define VALUE    r2
#define STR_RES  r3
#define P_DATA   r4
#define EXPC_VAL r5
#define ACT_VAL  r6

    push {r4-r6}
    mov  P_DATA, r0
    mov  RET_REG, #0

loop_cmp_exch
    ldrex   ACT_VAL, [P_DATA]
    ldr     EXPC_VAL, [P_EXPC]
    cmp     ACT_VAL, EXPC_VAL
    ittee   eq
    strexeq STR_RES, VALUE, [P_DATA]
    moveq   RET_REG, #1
    strexne STR_RES, ACT_VAL, [P_DATA]
    strne   ACT_VAL, [P_EXPC]
    cmp     STR_RES, #0
    itt     ne
    movne   RET_REG, #0
    bne     loop_cmp_exch

    pop {r4-r6}
    bx    lr

#undef RET_REG
#undef P_EXPC
#undef VALUE
#undef STR_RES
#undef P_DATA
#undef EXPC_VAL
#undef ACT_VAL
}

static __asm uint32_t nrf_atomic_internal_sub_hs(nrf_atomic_u32_t * p_ptr,
                                                 uint32_t value,
                                                 uint32_t * p_new)
{
    push  {r4, r5}
    mov   r4, r0

loop_sub_ge
    ldrex r0, [r4]
    cmp   r0, r1
    ite   hs
    subhs r5, r0, r1
    movlo r5, r0
    strex r3, r5, [r4]
    cmp   r3, #0
    bne   loop_sub_ge

    str   r5, [r2]
    pop   {r4, r5}
    bx    lr
}


#define NRF_ATOMIC_OP(asm_op, old_val, new_val, ptr, value)          \
        old_val = nrf_atomic_internal_##asm_op(ptr, value, &new_val)

#elif defined ( __ICCARM__ ) || defined ( __GNUC__ )

/**
 * @brief Atomic operation generic macro
 * @param[in] asm_op operation: mov, orr, and, eor, add, sub
 * @param[out] old_val atomic object output (uint32_t), value before operation
 * @param[out] new_val atomic object output (uint32_t), value after operation
 * @param[in] value atomic operation operand
 * */
#define NRF_ATOMIC_OP(asm_op, old_val, new_val, ptr, value)                 \
{                                                                           \
    uint32_t str_res;                                                       \
            __ASM volatile(                                                 \
    "1:     ldrex   %["#old_val"], [%["#ptr"]]\n"                           \
    NRF_ATOMIC_OP_##asm_op(new_val, old_val, value)                         \
    "       strex   %[str_res], %["#new_val"], [%["#ptr"]]\n"               \
    "       teq     %[str_res], #0\n"                                       \
    "       bne.n     1b"                                                   \
            :                                                               \
        [old_val]"=&r" (old_val),                                           \
        [new_val]"=&r" (new_val),                                           \
        [str_res]"=&r" (str_res)                                            \
            :                                                               \
        [ptr]"r" (ptr),                                                     \
        [value]"r" (value)                                                  \
            : "cc");                                                        \
    UNUSED_PARAMETER(str_res);                                              \
}

#define NRF_ATOMIC_OP_mov(new_val, old_val, value) "mov %["#new_val"], %["#value"]\n"
#define NRF_ATOMIC_OP_orr(new_val, old_val, value) "orr %["#new_val"], %["#old_val"], %["#value"]\n"
#define NRF_ATOMIC_OP_and(new_val, old_val, value) "and %["#new_val"], %["#old_val"], %["#value"]\n"
#define NRF_ATOMIC_OP_eor(new_val, old_val, value) "eor %["#new_val"], %["#old_val"], %["#value"]\n"
#define NRF_ATOMIC_OP_add(new_val, old_val, value) "add %["#new_val"], %["#old_val"], %["#value"]\n"
#define NRF_ATOMIC_OP_sub(new_val, old_val, value) "sub %["#new_val"], %["#old_val"], %["#value"]\n"
#define NRF_ATOMIC_OP_sub_hs(new_val, old_val, value)                                              \
    "cmp %["#old_val"], %["#value"]\n "                                                            \
    "ite hs\n"                                                                                     \
    "subhs %["#new_val"], %["#old_val"], %["#value"]\n"                                            \
    "movlo %["#new_val"], %["#old_val"]\n"

static inline bool nrf_atomic_internal_cmp_exch(nrf_atomic_u32_t * p_data,
                                                uint32_t *         p_expected,
                                                uint32_t           value)
{
    bool res = false;
    uint32_t str_res = 0;
    uint32_t act_val = 0;
    uint32_t exp_val = 0;
    UNUSED_VARIABLE(str_res);
    UNUSED_VARIABLE(act_val);
    UNUSED_VARIABLE(exp_val);
    __ASM volatile(
    "1:     ldrex   %[act_val], [%[ptr]]\n"
    "       ldr     %[exp_val], [%[expc]]\n"
    "       cmp     %[act_val], %[exp_val]\n"
    "       ittee   eq\n"
    "       strexeq %[str_res], %[value], [%[ptr]]\n"
    "       moveq   %[res], #1\n"
    "       strexne %[str_res], %[act_val], [%[ptr]]\n"
    "       strne   %[act_val], [%[expc]]\n"
    "       cmp     %[str_res], #0\n"
    "       itt     ne\n"
    "       movne   %[res], #0\n"
    "       bne.n     1b"
            :
        [res]     "=&r" (res),
        [exp_val] "=&r" (exp_val),
        [act_val]    "=&r" (act_val),
        [str_res] "=&r" (str_res)
            :
        "0" (res),
        "1" (exp_val),
        "2" (act_val),
        [expc]  "r" (p_expected),
        [ptr]   "r" (p_data),
        [value] "r" (value)
            : "cc");
    return res;
}

#else
#error "Unsupported compiler"
#endif

#ifdef __cplusplus
}
#endif

#endif /* NRF_ATOMIC_INTERNAL_H__ */

/** @} */
