/*
 * Copyright (c) 2023 HPMicro
 *
 * SPDX-License-Identifier: BSD-3-Clause
 *
 */

#ifndef HPM_PLB_DRV_H
#define HPM_PLB_DRV_H

#include "hpm_common.h"
#include "hpm_plb_regs.h"

/**
 * @brief PLB driver APIs
 * @defgroup plb_interface PLB driver APIs
 * @ingroup io_interfaces
 * @{
 */

#define PLB_SLICE_MASK  (0xf)
#define PLB_SLICE_HIGH_BIT_MASK_SET(slice) (PLB_SLICE_MASK << ((slice - plb_type_b_slice_8) << 2))
#define PLB_SLICE_HIGH_BIT_SHIFT(slice) ((slice - plb_type_b_slice_8) << 2)
#define PLB_SLICE_LOW_BIT_MASK_SET(slice) (PLB_SLICE_MASK << (slice << 2))
#define PLB_SLICE_LOW_BIT_SHIFT(slice) (slice << 2)

/**
 * @brief plb channels
 *
 */
typedef enum plb_chn {
#ifdef PLB_TYPE_B_0
    plb_chn0 = PLB_TYPE_B_0,
#endif
#ifdef PLB_TYPE_B_1
    plb_chn1 = PLB_TYPE_B_1,
#endif
#ifdef PLB_TYPE_B_2
    plb_chn2 = PLB_TYPE_B_2,
#endif
#ifdef PLB_TYPE_B_3
    plb_chn3 = PLB_TYPE_B_3,
#endif
#ifdef PLB_TYPE_B_4
    plb_chn4 = PLB_TYPE_B_4,
#endif
#ifdef PLB_TYPE_B_5
    plb_chn5 = PLB_TYPE_B_5,
#endif
#ifdef PLB_TYPE_B_6
    plb_chn6 = PLB_TYPE_B_6,
#endif
#ifdef PLB_TYPE_B_7
    plb_chn7 = PLB_TYPE_B_7,
#endif
} plb_chn_t;

/**
 * @brief PLB look-up table unit
 *
 */
typedef enum plb_type_a_lut_num {
#ifdef PLB_TYPE_A_LOOKUP_TABLE_0
    plb_type_a_table0 = PLB_TYPE_A_LOOKUP_TABLE_0,
#endif
#ifdef PLB_TYPE_A_LOOKUP_TABLE_1
    plb_type_a_table1 = PLB_TYPE_A_LOOKUP_TABLE_1,
#endif
#ifdef PLB_TYPE_A_LOOKUP_TABLE_2
    plb_type_a_table2 = PLB_TYPE_A_LOOKUP_TABLE_2,
#endif
#ifdef PLB_TYPE_A_LOOKUP_TABLE_3
    plb_type_a_table3 = PLB_TYPE_A_LOOKUP_TABLE_3,
#endif
} plb_type_a_lut_num_t;

/**
 * @brief PLB truth table configuration unit
 *
 */
typedef union {
    struct {
        uint16_t index0_1bit_out: 1;
        uint16_t index1_1bit_out: 1;
        uint16_t index2_1bit_out: 1;
        uint16_t index3_1bit_out: 1;
        uint16_t index4_1bit_out: 1;
        uint16_t index5_1bit_out: 1;
        uint16_t index6_1bit_out: 1;
        uint16_t index7_1bit_out: 1;
        uint16_t index8_1bit_out: 1;
        uint16_t index9_1bit_out: 1;
        uint16_t index10_1bit_out: 1;
        uint16_t index11_1bit_out: 1;
        uint16_t index12_1bit_out: 1;
        uint16_t index13_1bit_out: 1;
        uint16_t index14_1bit_out: 1;
        uint16_t index15_1bit_out: 1;
    };
    uint16_t val;
} plb_type_a_truth_t;

/**
 * @brief Index of slice
 *
 */
typedef enum plb_type_b_lut_slice {
    plb_type_b_slice_0 = 0,
    plb_type_b_slice_1 = 1,
    plb_type_b_slice_2 = 2,
    plb_type_b_slice_3 = 3,
    plb_type_b_slice_4 = 4,
    plb_type_b_slice_5 = 5,
    plb_type_b_slice_6 = 6,
    plb_type_b_slice_7 = 7,
    plb_type_b_slice_8 = 8,
    plb_type_b_slice_9 = 9,
    plb_type_b_slice_10 = 10,
    plb_type_b_slice_11 = 11,
    plb_type_b_slice_12 = 12,
    plb_type_b_slice_13 = 13,
    plb_type_b_slice_14 = 14,
    plb_type_b_slice_15 = 15,
} plb_type_b_lut_slice_t;

/**
 * @brief Configuration of slice
 *
 */
typedef enum plb_type_b_slice_opt {
    plb_slice_opt_keep = 0,                 /**< The data unit keeps the value of the previous cycle */
    plb_slice_opt_get_cmp0_val = 1,         /**< The data unit will take the value of the cmp0 register as the value for the next cycle */
    plb_slice_opt_get_cmp1_val = 2,         /**< The data unit will take the value of the cmp1 register as the value for the next cycle */
    plb_slice_opt_get_cmp2_val = 3,         /**< The data unit will take the value of the cmp2 register as the value for the next cycle */
    plb_slice_opt_add_one = 4,              /**< The next cycle value of the data cell is the current value plus 1 */
    plb_slice_opt_add_two = 5,              /**< The next cycle value of the data cell is the current value plus 2 */
    plb_slice_opt_sub_one = 6,              /**< The next cycle value of the data cell is the current value minus 1 */
    plb_slice_opt_sub_two = 7,              /**< The next cycle value of the data cell is the current value minus 2 */
    plb_slice_opt_shift_left = 4 << 8,      /**< The value of the next cycle of the data cell is shifted one place to the left of the current value */
    plb_slice_opt_shift_left_add_one = 5 << 8,  /**< The next cycle value of the data cell is the current value shifted one place to the left, with the lower bit complemented by one */
    plb_slice_opt_shift_right = 6 << 8,     /**< The value of the next cycle of the data cell is shifted one place to the right of the current value */
    plb_slice_opt_shift_right_add_one = 7 << 8, /**< The next cycle value of the data cell is the current value shifted one place to the right, with the lower bit complemented by one */
} plb_type_b_slice_opt_t;

/**
 * @brief Comparator index
 *
 */
typedef enum plb_type_b_cmp {
    plb_type_b_cmp0 = PLB_TYPE_B_CMP_0,
    plb_type_b_cmp1 = PLB_TYPE_B_CMP_1,
    plb_type_b_cmp2 = PLB_TYPE_B_CMP_2,
    plb_type_b_cmp3 = PLB_TYPE_B_CMP_3,
} plb_type_b_cmp_t;

/**
 * @brief Comparator operation
 *
 */
typedef enum plb_type_b_cmp_mode {
    plb_cmp_mode_out_zero = 0,          /**< output zero */
    plb_cmp_mode_out_one = 1,           /**< output one */
    plb_cmp_mode_gt = 2,                /**< Data unit greater than cmp output one, otherwise output zero */
    plb_cmp_mode_lt = 3,                /**< Data unit less than cmp output one, otherwise output zero */
    plb_cmp_mode_eq = 4,                /**< Data unit equal to cmp output one, otherwise output zero */
    plb_cmp_mode_ne = 5,                /**< Data unit not equal to cmp output one, otherwise output zero */
    plb_cmp_mode_ge = 6,                /**< Data unit greater than or equal to cmp output one, otherwise output zero */
    plb_cmp_mode_le = 7,                /**< Data unit less than or equal to cmp output one, otherwise output zero */
    plb_cmp_mode_and_mask = 10,         /**< The data cell corresponding to the bit set to one by cmp is and */
    plb_cmp_mode_or_mask = 11,          /**< The data cell corresponding to the bit set to one by cmp is or */
    plb_cmp_mode_xor_mask = 12,         /**< The data cell corresponding to the bit set to one by cmp is xor */
    plb_cmp_mode_nand_mask = 13,        /**< The data cell corresponding to the bit set to one by cmp is nand */
    plb_cmp_mode_nor_mask = 14,         /**< The data cell corresponding to the bit set to one by cmp is nor */
    plb_cmp_mode_xnor_mask = 15,        /**< The data cell corresponding to the bit set to one by cmp is xnor */
} plb_type_b_cmp_mode_t;


#ifdef __cplusplus
extern "C" {
#endif

/**
 * @brief Configuring the truth table for lookup tables
 *
 * @param plb @ref PLB_Type plb base
 * @param chn @ref plb_chn_t
 * @param lut_num @ref plb_type_a_lut_num_t
 * @param truth @ref plb_type_a_truth_t
 */
static inline void plb_type_a_set_lut(PLB_Type *plb, plb_chn_t chn, plb_type_a_lut_num_t lut_num, plb_type_a_truth_t *truth)
{
    plb->TYPE_A[chn].LOOKUP_TABLE[lut_num] = PLB_TYPE_A_LOOKUP_TABLE_LOOKUP_TABLE_SET(truth->val);
}

/**
 * @brief The software injects a cycle value into the TYPE A channel.
 *
 * @param plb @ref PLB_Type plb base
 * @param chn @ref plb_chn_t
 * @param inject_val Injected values
 */
static inline void plb_type_a_inject_by_sw(PLB_Type *plb, plb_chn_t chn, uint8_t inject_val)
{
    plb->TYPE_A[chn].SW_INJECT = PLB_TYPE_A_SW_INJECT_SW_INJECT_SET(inject_val);
}

/**
 * @brief Configure the value of the CMP
 *
 * @param plb @ref PLB_Type plb base
 * @param chn @ref plb_chn_t
 * @param cmp_index @ref plb_type_b_cmp_t
 * @param val CMP value
 */
static inline void plb_type_b_set_cmp_val(PLB_Type *plb, plb_chn_t chn, plb_type_b_cmp_t cmp_index, uint32_t val)
{
    plb->TYPE_B[chn].CMP[cmp_index] = PLB_TYPE_B_CMP_CMP_VALUE_SET(val);
}

/**
 * @brief Setting the mode of the CMP
 *
 * @param plb @ref PLB_Type plb base
 * @param chn @ref plb_chn_t
 * @param cmp_index @ref plb_type_b_cmp_t
 * @param cmp_mode @ref plb_type_b_cmp_mode_t
 */
static inline void plb_type_b_set_cmp_mode(PLB_Type *plb, plb_chn_t chn, plb_type_b_cmp_t cmp_index, plb_type_b_cmp_mode_t cmp_mode)
{
    plb->TYPE_B[chn].MODE = (plb->TYPE_B[chn].MODE & (~(PLB_TYPE_B_MODE_OUT0_SEL_MASK << (cmp_index << 2)))) |
                            ((PLB_TYPE_B_MODE_OUT0_SEL_MASK & cmp_mode) << (cmp_index << 2));
}

/**
 * @brief Software injection values
 *
 * @param plb @ref PLB_Type plb base
 * @param chn @ref plb_chn_t
 * @param val value
 */
static inline void plb_type_b_inject_by_sw(PLB_Type *plb, plb_chn_t chn, uint32_t val)
{
    plb->TYPE_B[chn].SW_INJECT = val;
}

#ifdef PLB_TYPE_B_LUT_CMP_LUT_CMP_MASK
/**
 * @brief  Set this cmp lut as cmp load control's true table
 *
 * @param plb @ref PLB_Type plb base
 * @param chn @ref plb_chn_t
 * @param cmp_index @ref plb_type_b_cmp_t
 * @param val cmp value
 */
static inline void plb_type_b_set_cmp_lut(PLB_Type *plb, plb_chn_t chn, plb_type_b_cmp_t cmp_index, uint16_t val)
{
    plb->TYPE_B[chn].LUT_CMP[cmp_index] = PLB_TYPE_B_LUT_CMP_LUT_CMP_SET(val);
}
#endif

/**
 * @brief Get the counter value for PLB Type B
 *
 * Retrieve the value of the SW_INJECT field for the specified channel from the given PLB Type B structure.
 *
 * @param plb Pointer to the PLB Type B structure
 * @param chn Channel number
 *
 * @return The value of the SW_INJECT field for the corresponding channel
 */
static inline uint32_t plb_type_b_get_counter(PLB_Type *plb, plb_chn_t chn)
{
    return plb->TYPE_B[chn].SW_INJECT;
}

/**
 * @brief Configuring the PLB type_b's lookup table
 *
 * @param plb @ref PLB_Type plb base
 * @param chn @ref plb_chn_t
 * @param slice @ref plb_type_b_lut_slice_t
 * @param opt @ref plb_type_b_slice_opt_t
 */
void plb_type_b_set_lut(PLB_Type *plb, plb_chn_t chn, plb_type_b_lut_slice_t slice, plb_type_b_slice_opt_t opt);

/**
 * @brief Batch set plb lut b type data, the unit corresponding to the bit marked with a mask of 1 is configured
 *
 * @param plb @ref PLB_Type plb base
 * @param chn @ref plb_chn_t
 * @param mask bit16
 * @param opt @ref plb_type_b_slice_opt_t
 */
void plb_type_b_set_lut_range_mask(PLB_Type *plb, plb_chn_t chn, uint16_t mask, plb_type_b_slice_opt_t opt);

/**
 * @brief Configure all lut to the same configuration
 *
 * @param plb @ref PLB_Type plb base
 * @param chn @ref plb_chn_t
 * @param opt @ref plb_type_b_slice_opt_t
 */
void plb_type_b_set_all_slice(PLB_Type *plb, plb_chn_t chn, plb_type_b_slice_opt_t opt);

#ifdef __cplusplus
}
#endif

/**
 * @}
 */

#endif /* HPM_PLB_DRV_H */

