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

#ifndef HPM_PLIC_DRV_H
#define HPM_PLIC_DRV_H

/**
 * @brief PLIC driver APIs
 * @defgroup plic_interface PLIC driver APIs
 * @{
 */

#define HPM_PLIC_TARGET_M_MODE 0
#define HPM_PLIC_TARGET_S_MODE 1

/* Feature Register */
#define HPM_PLIC_FEATURE_OFFSET (0x00000000UL)
#define HPM_PLIC_FEATURE_VECTORED_MODE (0x2UL)
#define HPM_PLIC_FEATURE_PREEMPTIVE_PRIORITY_IRQ (0x1UL)

/* Priority Register - 32 bits per irq */
#define HPM_PLIC_PRIORITY_OFFSET (0x00000004UL)
#define HPM_PLIC_PRIORITY_SHIFT_PER_SOURCE 2

/* Pending Register - 1 bit per source */
#define HPM_PLIC_PENDING_OFFSET (0x00001000UL)
#define HPM_PLIC_PENDING_SHIFT_PER_SOURCE 0

#define HPM_PLIC_TRIGGER_TYPE_OFFSET (0x00001080UL)
#define HPM_PLIC_TRIGGER_TYPE_SHIFT_PER_SORUCE 1

/* Enable Register - 0x80 per target */
#define HPM_PLIC_ENABLE_OFFSET (0x00002000UL)
#define HPM_PLIC_ENABLE_SHIFT_PER_TARGET 7

/* Priority Threshold Register - 0x1000 per target */
#define HPM_PLIC_THRESHOLD_OFFSET (0x00200000UL)
#define HPM_PLIC_THRESHOLD_SHIFT_PER_TARGET 12


/* Claim Register - 0x1000 per target */
#define HPM_PLIC_CLAIM_OFFSET (0x00200004UL)
#define HPM_PLIC_CLAIM_SHIFT_PER_TARGET 12

#if !defined(__ASSEMBLER__)

/**
 * @brief   Set plic feature
 *
 * @param[in] base PLIC base address
 * @param[in] feature Specific feature to be set
 *
 */
ATTR_ALWAYS_INLINE static inline void __plic_set_feature(uint32_t base, uint32_t feature)
{
    *(volatile uint32_t *) (base + HPM_PLIC_FEATURE_OFFSET) = feature;
}

/**
 * @brief   Set plic threshold
 *
 * @param[in] base PLIC base address
 * @param[in] target Target to handle specific interrupt
 * @param[in] threshold Threshold of IRQ can be serviced
 *
 */
ATTR_ALWAYS_INLINE static inline void __plic_set_threshold(uint32_t base,
                                                           uint32_t target,
                                                           uint32_t threshold)
{
    volatile uint32_t *threshold_ptr = (volatile uint32_t *) (base +
                                                              HPM_PLIC_THRESHOLD_OFFSET +
                                                              (target << HPM_PLIC_THRESHOLD_SHIFT_PER_TARGET));
    *threshold_ptr = threshold;
}

/**
 * @brief   Get plic threshold
 *
 * @param[in] base PLIC base address
 * @param[in] target Target to handle specific interrupt
 *
 */
ATTR_ALWAYS_INLINE static inline uint32_t __plic_get_threshold(uint32_t base,
                                                           uint32_t target)
{
    volatile uint32_t *threshold_ptr = (volatile uint32_t *)(base +
            HPM_PLIC_THRESHOLD_OFFSET +
            (target << HPM_PLIC_THRESHOLD_SHIFT_PER_TARGET));
    return *threshold_ptr;
}

/**
 * @brief   Set interrupt priority
 *
 * @param[in] base PLIC base address
 * @param[in] irq Target interrupt number
 * @param[in] priority Priority to be assigned
 *
 */
ATTR_ALWAYS_INLINE static inline void __plic_set_irq_priority(uint32_t base,
                                                              uint32_t irq,
                                                              uint32_t priority)
{
    volatile uint32_t *priority_ptr = (volatile uint32_t *) (base +
                                                             HPM_PLIC_PRIORITY_OFFSET +
                                                             ((irq - 1) << HPM_PLIC_PRIORITY_SHIFT_PER_SOURCE));
    *priority_ptr = priority;
}

/**
 * @brief   Set interrupt pending bit
 *
 * @param[in] base PLIC base address
 * @param[in] irq Target interrupt number
 *
 */
ATTR_ALWAYS_INLINE static inline void __plic_set_irq_pending(uint32_t base, uint32_t irq)
{
    volatile uint32_t *current_ptr = (volatile uint32_t *) (base +
                                                            HPM_PLIC_PENDING_OFFSET + ((irq >> 5) << 2));
    *current_ptr = (1 << (irq & 0x1F));
}

/**
 * @brief   Set interrupt trigger type to edge-triggerred
 *
 * @param[in] base PLIC base address
 * @param[in] irq Target interrupt number
 *
 */
ATTR_ALWAYS_INLINE static inline void __plic_enable_irq_edge_trigger(uint32_t base, uint32_t irq)
{
    volatile uint32_t *current_ptr = (volatile uint32_t *) (base +
                                                            HPM_PLIC_TRIGGER_TYPE_OFFSET + ((irq >> 5) << 2));
    *current_ptr |= (1UL << (irq & 0x1F));
}

/**
 * @brief   Set interrupt trigger type to level-triggerred
 *
 * @param[in] base PLIC base address
 * @param[in] irq Target interrupt number
 *
 */
ATTR_ALWAYS_INLINE static inline void __plic_enable_irq_level_trigger(uint32_t base, uint32_t irq)
{
    volatile uint32_t *current_ptr = (volatile uint32_t *) (base +
                                                            HPM_PLIC_TRIGGER_TYPE_OFFSET + ((irq >> 5) << 2));
    *current_ptr &= ~(1UL << (irq & 0x1F));
}

/**
 * @brief   Enable interrupt
 *
 * @param[in] base PLIC base address
 * @param[in] target Target to handle specific interrupt
 * @param[in] irq Interrupt number to be enabled
 *
 */
ATTR_ALWAYS_INLINE static inline void __plic_enable_irq(uint32_t base,
                                                        uint32_t target,
                                                        uint32_t irq)
{
    volatile uint32_t *current_ptr = (volatile uint32_t *) (base +
                                                            HPM_PLIC_ENABLE_OFFSET +
                                                            (target << HPM_PLIC_ENABLE_SHIFT_PER_TARGET) +
                                                            ((irq >> 5) << 2));
    uint32_t current = *current_ptr;
    current = current | (1 << (irq & 0x1F));
    *current_ptr = current;
}

/**
 * @brief   Disable interrupt
 *
 * @param[in] base PLIC base address
 * @param[in] target Target to handle specific interrupt
 * @param[in] irq Interrupt number to be disabled
 *
 */
ATTR_ALWAYS_INLINE static inline void __plic_disable_irq(uint32_t base,
                                                         uint32_t target,
                                                         uint32_t irq)
{
    volatile uint32_t *current_ptr = (volatile uint32_t *) (base +
                                                            HPM_PLIC_ENABLE_OFFSET +
                                                            (target << HPM_PLIC_ENABLE_SHIFT_PER_TARGET) +
                                                            ((irq >> 5) << 2));
    uint32_t current = *current_ptr;
    current = current & ~((1 << (irq & 0x1F)));
    *current_ptr = current;
}

/**
 * @brief   Claim interrupt
 *
 * @param[in] base PLIC base address
 * @param[in] target Target to claim interrupt
 *
 */
ATTR_ALWAYS_INLINE static inline uint32_t __plic_claim_irq(uint32_t base, uint32_t target)
{
    volatile uint32_t *claim_addr = (volatile uint32_t *) (base +
                                                           HPM_PLIC_CLAIM_OFFSET +
                                                           (target << HPM_PLIC_CLAIM_SHIFT_PER_TARGET));
    return *claim_addr;
}

/**
 * @brief   Complete interrupt
 *
 * @param[in] base PLIC base address
 * @param[in] target Target to handle specific interrupt
 * @param[in] irq Interrupt number
 *
 */
ATTR_ALWAYS_INLINE static inline void __plic_complete_irq(uint32_t base,
                                                          uint32_t target,
                                                          uint32_t irq)
{
    volatile uint32_t *claim_addr = (volatile uint32_t *) (base +
                                                           HPM_PLIC_CLAIM_OFFSET +
                                                           (target << HPM_PLIC_CLAIM_SHIFT_PER_TARGET));
    *claim_addr = irq;
}

#endif /* __ASSEMBLER__ */
/**
 * @}
 */
#endif    /* HPM_PLIC_DRV_H */
