/* Copyright (c) 2022, Canaan Bright Sight Co., Ltd
 *
 * 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 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.
 *
 * 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.
 */
#ifndef _DRIVER_UTILS_H
#define _DRIVER_UTILS_H

#ifdef __cplusplus
#include <cstdbool>
#include <cstddef>
#include <cstdint>
#else /* __cplusplus */
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <k510_log.h>
#endif /* __cplusplus */

#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */

#define KENDRYTE_MIN(a, b) ((a) > (b) ? (b) : (a))
#define KENDRYTE_MAX(a, b) ((a) > (b) ? (a) : (b))

#ifdef __ASSEMBLY__
#define KENDRYTE_CAST(type, ptr) ptr
#else /* __ASSEMBLY__ */
/**
 * @brief       Cast the pointer to specified pointer type.
 *
 * @param[in]   type        The pointer type to cast to
 * @param[in]   ptr         The pointer to apply the type cast to
 */
#define KENDRYTE_CAST(type, ptr) ((type)(ptr))
#endif /* __ASSEMBLY__ */

/**
 * @addtogroup      UTIL_RW_FUNC Memory Read/Write Utilities
 *
 * This section implements read and write functionality for various
 * memory untis. The memory unit terms used for these functions are
 * consistent with those used in the ARM Architecture Reference Manual
 * ARMv7-A and ARMv7-R edition manual. The terms used for units of memory are:
 *
 *  Unit of Memory | Abbreviation | Size in Bits
 * :---------------|:-------------|:------------:
 *  Byte           | byte         |       8
 *  Half Word      | hword        |      16
 *  Word           | word         |      32
 *  Double Word    | dword        |      64
 *
 */

/**
 * @brief       Write the 8 bit byte to the destination address in device memory.
 *
 * @param[in]   dest        Write destination pointer address
 * @param[in]   src         8 bit data byte to write to memory
 */
#define kendryte_write_byte(dest, src) \
    (*KENDRYTE_CAST(volatile uint8_t*, (dest)) = (src))

/**
 * @brief       Read and return the 8 bit byte from the source address in device memory.
 *
 * @param[in]   src     Read source pointer address
 *
 * @return      8 bit data byte value
 */
#define kendryte_read_byte(src) (*KENDRYTE_CAST(volatile uint8_t*, (src)))

/**
 * @brief       Write the 16 bit half word to the destination address in device memory.
 *
 * @param[in]   dest        Write destination pointer address
 * @param[in]   src         16 bit data half word to write to memory
 */
#define kendryte_write_hword(dest, src) \
    (*KENDRYTE_CAST(volatile uint16_t*, (dest)) = (src))

/**
 * @brief       Read and return the 16 bit half word from the source address in device
 *
 * @param[in]   src     Read source pointer address
 *
 * @return      16 bit data half word value
 */
#define kendryte_read_hword(src) (*KENDRYTE_CAST(volatile uint16_t*, (src)))

/**
 * @brief       Write the 32 bit word to the destination address in device memory.
 *
 * @param[in]   dest        Write destination pointer address
 * @param[in]   src         32 bit data word to write to memory
 */
#define kendryte_write_word(dest, src) \
    (*KENDRYTE_CAST(volatile uint32_t*, (dest)) = (src))

/**
 * @brief       Read and return the 32 bit word from the source address in device memory.
 *
 * @param[in]   src     Read source pointer address
 *
 * @return      32 bit data half word value
 */
#define kendryte_read_word(src) (*KENDRYTE_CAST(volatile uint32_t*, (src)))

/**
 * @brief       Write the 64 bit double word to the destination address in device memory.
 *
 * @param[in]   dest        Write destination pointer address
 * @param[in]   src         64 bit data word to write to memory
 */
#define kendryte_write_dword(dest, src) \
    (*KENDRYTE_CAST(volatile uint64_t*, (dest)) = (src))

/**
 * @brief       Read and return the 64 bit double word from the source address in device
 *
 * @param[in]   src     Read source pointer address
 *
 * @return      64 bit data half word value
 */
#define kendryte_read_dword(src) (*KENDRYTE_CAST(volatile uint64_t*, (src)))

/**
 * @brief       Set selected bits in the 8 bit byte at the destination address in device
 *
 * @param[in]   dest        Destination pointer address
 * @param[in]   bits        Bits to set in destination byte
 */
#define kendryte_setbits_byte(dest, bits) \
    (kendryte_write_byte(dest, kendryte_read_byte(dest) | (bits)))

/**
 * @brief       Clear selected bits in the 8 bit byte at the destination address in device
 *
 * @param[in]   dest        Destination pointer address
 * @param[in]   bits        Bits to clear in destination byte
 */
#define kendryte_clrbits_byte(dest, bits) \
    (kendryte_write_byte(dest, kendryte_read_byte(dest) & ~(bits)))

/**
 * @brief       Change or toggle selected bits in the 8 bit byte at the destination address
 *
 * @param[in]   dest        Destination pointer address
 * @param[in]   bits        Bits to change in destination byte
 */
#define kendryte_xorbits_byte(dest, bits) \
    (kendryte_write_byte(dest, kendryte_read_byte(dest) ^ (bits)))

/**
 * @brief       Replace selected bits in the 8 bit byte at the destination address in device
 *
 * @param[in]   dest        Destination pointer address
 * @param[in]   msk         Bits to replace in destination byte
 * @param[in]   src         Source bits to write to cleared bits in destination byte
 */
#define kendryte_replbits_byte(dest, msk, src) \
    (kendryte_write_byte(dest, (kendryte_read_byte(dest) & ~(msk)) | ((src) & (msk))))

/**
 * @brief       Set selected bits in the 16 bit halfword at the destination address in
 *
 * @param[in]   dest        Destination pointer address
 * @param[in]   bits        Bits to set in destination halfword
 */
#define kendryte_setbits_hword(dest, bits) \
    (kendryte_write_hword(dest, kendryte_read_hword(dest) | (bits)))

/**
 * @brief       Clear selected bits in the 16 bit halfword at the destination address in
 *
 * @param[in]   dest        Destination pointer address
 * @param[in]   bits        Bits to clear in destination halfword
 */
#define kendryte_clrbits_hword(dest, bits) \
    (kendryte_write_hword(dest, kendryte_read_hword(dest) & ~(bits)))

/**
 * @brief       Change or toggle selected bits in the 16 bit halfword at the destination
 *
 * @param[in]   dest        Destination pointer address
 * @param[in]   bits        Bits to change in destination halfword
 */
#define kendryte_xorbits_hword(dest, bits) \
    (kendryte_write_hword(dest, kendryte_read_hword(dest) ^ (bits)))

/**
 * @brief       Replace selected bits in the 16 bit halfword at the destination address in
 *
 * @param[in]   dest        Destination pointer address
 * @param[in]   msk         Bits to replace in destination byte
 * @param[in]   src         Source bits to write to cleared bits in destination halfword
 */
#define kendryte_replbits_hword(dest, msk, src) \
    (kendryte_write_hword(dest, (kendryte_read_hword(dest) & ~(msk)) | ((src) & (msk))))

/**
 * @brief       Set selected bits in the 32 bit word at the destination address in device
 *
 * @param[in]   dest        Destination pointer address
 * @param[in]   bits        Bits to set in destination word
 */
#define kendryte_setbits_word(dest, bits) \
    (kendryte_write_word(dest, kendryte_read_word(dest) | (bits)))

/**
 * @brief       Clear selected bits in the 32 bit word at the destination address in device
 *
 * @param[in]   dest        Destination pointer address
 * @param[in]   bits        Bits to clear in destination word
 */
#define kendryte_clrbits_word(dest, bits) \
    (kendryte_write_word(dest, kendryte_read_word(dest) & ~(bits)))

/**
 * @brief       Change or toggle selected bits in the 32 bit word at the destination address
 *
 * @param[in]   dest        Destination pointer address
 * @param[in]   bits        Bits to change in destination word
 */
#define kendryte_xorbits_word(dest, bits) \
    (kendryte_write_word(dest, kendryte_read_word(dest) ^ (bits)))

/**
 * @brief       Replace selected bits in the 32 bit word at the destination address in
 *
 * @param[in]   dest        Destination pointer address
 * @param[in]   msk         Bits to replace in destination word
 * @param[in]   src         Source bits to write to cleared bits in destination word
 */
#define kendryte_replbits_word(dest, msk, src) \
    (kendryte_write_word(dest, (kendryte_read_word(dest) & ~(msk)) | ((src) & (msk))))

/**
 * @brief      Set selected bits in the 64 bit doubleword at the destination address in
 *
 * @param[in]   dest     Destination pointer address
 * @param[in]   bits     Bits to set in destination doubleword
 */
#define kendryte_setbits_dword(dest, bits) \
    (kendryte_write_dword(dest, kendryte_read_dword(dest) | (bits)))

/**
 * @brief       Clear selected bits in the 64 bit doubleword at the destination address in
 *
 * @param[in]   dest        Destination pointer address
 * @param[in]   bits        Bits to clear in destination doubleword
 */
#define kendryte_clrbits_dword(dest, bits) \
    (kendryte_write_dword(dest, kendryte_read_dword(dest) & ~(bits)))

/**
 * @brief       Change or toggle selected bits in the 64 bit doubleword at the destination
 *
 * @param[in]   dest        Destination pointer address
 * @param[in]   bits        Bits to change in destination doubleword
 */
#define kendryte_xorbits_dword(dest, bits) \
    (kendryte_write_dword(dest, kendryte_read_dword(dest) ^ (bits)))

/**
 * @brief       Replace selected bits in the 64 bit doubleword at the destination address in
 *
 * @param[in]   dest        Destination pointer address
 * @param[in]   msk         its to replace in destination doubleword
 * @param[in]   src         Source bits to write to cleared bits in destination word
 */
#define kendryte_replbits_dword(dest, msk, src) \
    (kendryte_write_dword(dest, (kendryte_read_dword(dest) & ~(msk)) | ((src) & (msk))))

#define configASSERT(x)                                 \
    if ((x) == 0)                                       \
    {                                                   \
        k510_loge("(%s:%d) %s\r\n", __FILE__, __LINE__, #x); \
        for (;;)                                        \
            ;                                           \
    }

/**
 * @brief       Set value by mask
 *
 * @param[in]   bits        The one be set
 * @param[in]   mask        mask value
 * @param[in]   value       The value to set
 */
void set_bit(volatile uint32_t *bits, uint32_t mask, uint32_t value);

/**
 * @brief       Set value by mask
 *
 * @param[in]   bits        The one be set
 * @param[in]   mask        Mask value
 * @param[in]   offset      Mask's offset
 * @param[in]   value       The value to set
 */
void set_bit_offset(volatile uint32_t *bits, uint32_t mask, size_t offset, uint32_t value);

/**
 * @brief       Set bit for gpio, only set one bit
 *
 * @param[in]   bits        The one be set
 * @param[in]   idx         Offset value
 * @param[in]   value       The value to set
 */
void set_gpio_bit(volatile uint32_t *bits, size_t idx, uint32_t value);

/**
 * @brief      Get bits value of mask
 *
 * @param[in]   bits        The source data
 * @param[in]   mask        Mask value
 * @param[in]   offset      Mask's offset
 *
 * @return      The bits value of mask
 */
uint32_t get_bit(volatile uint32_t *bits, uint32_t mask, size_t offset);

/**
 * @brief       Get a bit value by offset
 *
 * @param[in]   bits        The source data
 * @param[in]   offset      Bit's offset
 *
 *
 * @return      The bit value
 */
uint32_t get_gpio_bit(volatile uint32_t *bits, size_t offset);

#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* _DRIVER_COMMON_H */

