/*
 ******************************************************************************************
 * @file        irq.c
 * @author      GowinSemicoductor
 * @device      Gowin_PicoRV32
 * @brief       Interrupt request.
 ******************************************************************************************
 */

/* Includes ------------------------------------------------------------------------ */
#include <stdio.h>
#include "firmware.h"
#include "simpleuart.h"
#include "picorv32.h"
#include "wbuart.h"
#include "irq.h"
#include "config.h"
#include "project.h"


/* Definitions --------------------------------------------------------------------- */

uint32_t tmp = 0;
uint32_t val = 0;

bool volatile irq_ebreak_flag = false ;             /* Execute debug breakpoint instruction (ebreak) */
bool volatile irq_misalign_flag = false ;           /* Misalignment access error of memory (bus error) */
bool volatile irq_illegalinst_flag = false ;        /* Illegal instruction error */
bool volatile irq_debug_flag = false ;              /* Debug module interrupt */
bool volatile irq_stackprot_flag = false ;          /* Hardware stack protection interrupt */
bool volatile irq_wbspimaster_flag = false ;        /* Wishbone SPI Master interrupt */
bool volatile irq_wbspislave_flag = false ;         /* Wishbone SPI Slave interrupt */
bool volatile irq_wbi2cmaster_flag = false ;        /* Wishbone I2C Master interrupt */
bool volatile irq_wbuart_flag = false ;             /* Wishbone UART interrupt */
//Can add other interrupts by user's need

#ifdef USE_WBUART
#define get_char()  wbuart_getc()
#define put_char(c)  wbuart_putc(c)
#else
#define get_char()  uart_getchar()
#define put_char(c)  uart_putchar(c)
#endif

extern void user0_int() ;
extern void user1_int() ;
extern void timer_int() ;

uint32_t *irq(uint32_t *regs, uint32_t irqs)
{
    volatile uint32_t mask = mask_irq(0xffffffff) | irqs;

    mask_irq(mask);

    if ((irqs & (1<<INTNUM_TIMER)) != 0)
    {
        timer_int() ;
    }

    if ((irqs & (1<<INTNUM_WB_UART)) != 0)
    {
        irq_wbuart_flag = true ;
    }

    if ((irqs & (1<<INTNUM_WB_SPIMASTER)) != 0)
    {
        irq_wbspimaster_flag = true ;
    }

    if ((irqs & (1<<INTNUM_USER0)) != 0)
    { /* Tx buffer empty */
        *AT_TXBE_ACK = 0x00000001 ;  /* ack, will invalidate request signal */
        user0_int() ;           /* call interrupt handler */
    }

    if ((irqs & (1<<INTNUM_USER1)) != 0)
    { /* timer1 overflow interrupt */
        *T1_ACK = 0x00000001 ;   /* ack, will invalidate request signal */
        user1_int() ;           /* call interrupt handler */
    }

    if ((irqs & (1<<INTNUM_EBREAK | 1<<INTNUM_MISALIGNED_INSTRUCTION | 1<<INTNUM_ILLEGAL_INSTRUCTION)) != 0)
    {
        uint32_t pc = (regs[0] & 1) ? regs[0] - 3 : regs[0] - 4;
        uint32_t instr = *(uint16_t*)pc;

        if ((instr & 3) == 3)
        { /* ??? */
            instr = instr | (*(uint16_t*)(pc + 2)) << 16;
        }

        if ((irqs & (1<<INTNUM_ILLEGAL_INSTRUCTION)) != 0)
        {
            printf("Illegal Ins at 0x");
            printf("%x\r\n", pc);
            printf(": 0x");
            printf("%x\r\n", instr);
        }

        if ((irqs & (1<<INTNUM_MISALIGNED_INSTRUCTION)) != 0)
        {
            printf("Bus error at 0x");
            printf("%x\r\n", pc);
            printf(": 0x");
            printf("%x\r\n", instr);
        }
    }

    return regs;
}

void __attribute__((naked, optimize("O0"))) return_from_irq (void)
{
    __asm__ __volatile__(".word(0x0000c08b)");
    __asm__ __volatile__(".word(0x0001410b)");
    __asm__ __volatile__(".word(0x0400000b)");
}

//Mask IRQ
inline uint32_t mask_irq(uint32_t mask)
{
    uint32_t old_mask;

    __asm__ __volatile__("mv a4, a0");
    __asm__ __volatile__(".word(0x0607678b)");
    __asm__ __volatile__("add %[dest], x0, a5" :[dest]"=r"(old_mask));

    return old_mask;
}

//Enable a local interrupt
inline void irq_enable_one_bit(uint32_t irq_num)
{
    uint32_t mask = 0;

    mask = mask_irq(0xffffffff);
    mask = mask & (~(1 << irq_num));
    mask_irq(mask);

    return;
}

//Disable a local interrupt
inline void irq_disable_one_bit(uint32_t irq_num)
{
    uint32_t mask = 0;

    mask = mask_irq(0xffffffff);
    mask = mask | (1 << irq_num);
    mask_irq(mask);

    return;
}

//Enable external interrupts
//Register 'mie' 's external interrupt bit
inline void enable_external_interrupt(void)
{
    __asm__ __volatile__("csrr   t4, mie");
    __asm__ __volatile__("li     t5, 0x800");
    __asm__ __volatile__("or    t4, t4, t5");
    __asm__ __volatile__("csrrw  t5, mie, t4");
}

//Disable external interrupts
//Register 'mie' 's external interrupt bit
inline void disable_external_interrupt(void)
{
    __asm__ __volatile__("csrr   t4, mie");
    __asm__ __volatile__("li     t5, 0xfffff7ff");
    __asm__ __volatile__("and    t4, t4, t5");
    __asm__ __volatile__("csrrw  t5, mie, t4");
}

//Enable timer interrupt
//Register 'mie' 's timer interrupt bit
inline void enable_timer_interrupt(void)
{
    __asm__ __volatile__("csrr   t4, mie");
    __asm__ __volatile__("li     t5, 0x80");
    __asm__ __volatile__("or     t4, t4, t5");
    __asm__ __volatile__("csrrw  t5, mie, t4");
}

//Disable timer interrupt
//Register 'mie' 's mie interrupt bit
inline void disable_timer_interrupt(void)
{
    __asm__ __volatile__("csrr   t4, mie");
    __asm__ __volatile__("li     t5, 0xffffff7f");
    __asm__ __volatile__("and    t4, t4, t5");
    __asm__ __volatile__("csrrw  t5, mie, t4");
}

//Enable global interrupt
//Register 'mstatus' 's 'MIE' bit (global interrupt)
inline void enable_interrupt_global(void)
{
    __asm__ __volatile__("csrrwi t5, mstatus, 8");
}

//Disable global interrupt
//Register 'mstatus' 's 'MIE' bit (global interrupt)
inline void disable_interrupt_global(void)
{
    __asm__ __volatile__("csrrwi t5, mstatus, 0");
}

//Set timer
uint32_t set_timer(uint32_t timer_val)
{
    uint32_t old_val = 0;

    __asm__ __volatile__("mv a4, a0");
    __asm__ __volatile__(".word(0x0a07078b)");
    __asm__ __volatile__("mv %[dest], a5"  :[dest]"=r"(old_val));

    return old_val;
}
