/*
* Copyright (C) 2017-2020 Alibaba Group Holding Limited
*
* SPDX-License-Identifier: GPL-2.0+
*/

#include <common.h>
#include <irq_func.h>
#include <asm/arch/interrupt.h>

#define CONFIG_SYS_NUM_IRQS     32

#define PCK_INTC ((PCKStruct_INTC)INTC_BASEADDRESS)

void mask_irq(int irq);
void unmask_irq(int irq);

static void enter_critical(int *psr);
static void exit_critical(int psr);


struct IRQ_Handler {
    interrupt_handler_t *handler;
    void *arg;
};

/*
* PCK_INTC: The base address of interrupt controller registers
*/
volatile CKStruct_INTC *icrp = PCK_INTC;

static struct IRQ_Handler ckirq_handlers[CONFIG_SYS_NUM_IRQS] = { {0} };

int interrupt_init (void)
{
#if 0
    /* initialize PR0-PR31, big endian */
    icrp->PR[0] = 0x00010203;
    icrp->PR[1] = 0x04050607;
    icrp->PR[2] = 0x08090a0b;
    icrp->PR[3] = 0x0c0d0e0f;
    icrp->PR[4] = 0x10111213;
    icrp->PR[5] = 0x14151617;
    icrp->PR[6] = 0x18191a1b;
    icrp->PR[7] = 0x1c1d1e1f;

    icrp->ICR |= ICR_AVE;
    mask_irq(31);		/* first mask all the interrupt */
    enable_interrupts();
#endif
    return 0;
}

/*
* Install an interrupt handler
*/
void irq_install_handler(int irq, interrupt_handler_t *handler, void *arg)
{
    int status;

    if (irq < 0 || irq >= CONFIG_SYS_NUM_IRQS){
        printf("irq_install_handler: bad irq number %d\n", irq);
        return;
    }

    status = disable_interrupts();

    ckirq_handlers[irq].handler = handler;
    ckirq_handlers[irq].arg = arg;

    enable_normalirq(irq);

    unmask_irq(irq);

    if (status){
        enable_interrupts();
    }
}

/*
* Free an interrupt handler
*/
void irq_free_handler(int irq)
{
    int status;

    if (irq < 0 || irq >= CONFIG_SYS_NUM_IRQS) {
        printf("irq_free_handler: bad irq number %d\n", irq);
        return;
    }

    status = disable_interrupts ();

    disable_normalirq(irq);

    mask_irq(irq);

    ckirq_handlers[irq].handler = NULL;
    ckirq_handlers[irq].arg = NULL;

    if (status){
        enable_interrupts();
    }
}

void enable_interrupts(void)
{
    asm("psrset ee,ie");
}

int disable_interrupts(void)
{
    asm("psrclr ie");
    return 1;
}

/*
* This function enables a priority level of normal interrupt.
* irq: A priority of normal interrupt which between 0 to 31.
*/
void enable_normalirq(int irq)
{
    int psrbk;

    enter_critical(&psrbk);
    icrp->NIER |= (1 << irq);
    exit_critical(psrbk);
}

/*
* This function disables a priority level of normal interrupt.
* irq: A priority of normal interrupt which between 0 to 31.
*/
void disable_normalirq(int irq)
{
    int psrbk;

    enter_critical(&psrbk);
    icrp->NIER &= ~(1 << irq);
    exit_critical(psrbk);
}

/*
* This function enables normal interrupt masking.
*
* irq: The priority level that would be masked,
* leading to mask those priority levels below primask.
*/
void mask_irq(int irq)
{
    int temp_icr;
    int psrbk;

    temp_icr = icrp->ICR;

    /*
    * This function will be implemented When fast interrupt masking is disabled,
    * or return at once.
    */
    if((temp_icr &= 0x10000000) == 0x10000000){
        return;
    }
    else{
        enter_critical(&psrbk);
        icrp->ICR &= 0xffe0ffff;
        icrp->ICR |= ((irq & 0x0000001f) << 16);
        icrp->ICR |= ICR_ME;
        exit_critical(psrbk);
    }
}

/*
* This function disables normal interrupt masking.
*
* irq: The priority level that would be unmasked.
*/
void unmask_irq(int irq)
{
    int temp_icr;
    int psrbk;

    temp_icr = icrp->ICR;

    enter_critical(&psrbk);
    icrp->ICR &= 0xffe0ffff;
    icrp->ICR |= ((irq & 0x0000001f) << 16);
    icrp->ICR &= ~ICR_ME;
    exit_critical(psrbk);
}

/*
* Execute the interrupt service.
*
*irq :
*RETURN VALUE: None
*/
void ck_irq_service(int irq)
{
    if (irq < 0 || irq >= CONFIG_SYS_NUM_IRQS) {
        printf("ck_irq_service: bad irq number %d\n", irq);
        return;
    }

    if (ckirq_handlers[irq].handler) {
        mask_irq(irq);

        ckirq_handlers[irq].handler(ckirq_handlers[irq].arg);

        unmask_irq(irq);
    }
}

/*
* This function should be called before executing critical code which should not be
*interrupted by other interrupts
*
*INPUT: psr, to store the value of psr
*RETURN VALUE: None
*/
static void enter_critical(int *psr)
{
    asm volatile ("mfcr    %0, psr\n\r"
                "psrclr  ie, fe"
                : "=r" (*psr) );
}

/*This function should be called after exiting critical area.
*
*INPUT: psr, contain the backup value of psr
*RETURN VALUE: None
*/
static void exit_critical(int psr)
{
    asm volatile ("mtcr   %0, psr"
                :
                :"r"(psr));
}
