/*
 * Copyright (c) 2006-2018, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2018-11-30     yangjie      The first version for LPC54114
 */

#include <rthw.h>
#include <rtthread.h>
#include <rtdevice.h>

#include "fsl_gpio.h"
#include "LPC54114_cm4.h"
#include "core_cm4.h"
#include "fsl_inputmux.h"
#include "fsl_pint.h"
#include "fsl_iocon.h"

#ifdef RT_USING_PIN

#define get_port(x)      (x / 32)
#define get_pin(x)       (x % 32)
#define PIN_MAX_VAL            63
#define IRQ_MAX_VAL             8

struct rt_pin_irq_hdr pin_irq_hdr_tab[] =
{
    {-1, 0, RT_NULL, RT_NULL},
    {-1, 0, RT_NULL, RT_NULL},
    {-1, 0, RT_NULL, RT_NULL},
    {-1, 0, RT_NULL, RT_NULL},
    {-1, 0, RT_NULL, RT_NULL},
    {-1, 0, RT_NULL, RT_NULL},
    {-1, 0, RT_NULL, RT_NULL},
    {-1, 0, RT_NULL, RT_NULL},    
};

/* Configure pin mode. pin 0~63 means PIO0_0 ~ PIO1_31 */
static void lpc_pin_mode(rt_device_t dev, rt_base_t pin, rt_base_t mode)
{
    int portx, piny, dir;
    uint32_t pin_cfg;
    
    if(pin > PIN_MAX_VAL)
        return;
    
    portx = get_port(pin);
    piny  = get_pin(pin);    
    
    switch(mode)
    {
    case PIN_MODE_OUTPUT: 
        dir = kGPIO_DigitalOutput;
        pin_cfg = IOCON_FUNC0 | IOCON_DIGITAL_EN;
        break;
    case PIN_MODE_OUTPUT_OD:
        dir = kGPIO_DigitalOutput;        
        pin_cfg = IOCON_FUNC0 | IOCON_OPENDRAIN_EN | IOCON_DIGITAL_EN;        
        break;
    
    case PIN_MODE_INPUT:   
        dir = kGPIO_DigitalInput;
        pin_cfg = IOCON_FUNC0 | IOCON_INPFILT_OFF | IOCON_DIGITAL_EN;
        break;        
    case PIN_MODE_INPUT_PULLUP:
        dir = kGPIO_DigitalInput;        
        pin_cfg = IOCON_FUNC0 | IOCON_INPFILT_OFF | IOCON_DIGITAL_EN | IOCON_MODE_PULLUP;
        break;
    case PIN_MODE_INPUT_PULLDOWN: 
        dir = kGPIO_DigitalInput;
        pin_cfg = IOCON_FUNC0 | IOCON_INPFILT_OFF | IOCON_DIGITAL_EN | IOCON_MODE_PULLDOWN;        
        break;
    default: break;
    }      
    
    CLOCK_EnableClock(kCLOCK_Iocon); 
    
    IOCON_PinMuxSet(IOCON, portx, piny, pin_cfg);        
    GPIO_PortInit(GPIO, portx);    
    
    gpio_pin_config_t pin_config = {(gpio_pin_direction_t)dir, 0};
    GPIO_PinInit(GPIO, portx, piny, &pin_config); 

    CLOCK_DisableClock(kCLOCK_Iocon);
}

static void lpc_pin_write(rt_device_t dev, rt_base_t pin, rt_base_t value)
{
    int portx, piny;
    portx = get_port(pin);
    piny  = get_pin(pin);   

    if(pin > PIN_MAX_VAL)
       return;

    GPIO_PinWrite(GPIO, portx, piny, value);        
}

static int lpc_pin_read(rt_device_t dev, rt_base_t pin)
{
    int  portx, piny, value;    
    
    if(pin > PIN_MAX_VAL)
        return RT_ERROR; 
    
    portx = get_port(pin);
    piny  = get_pin(pin);  
    
    value = (int)(GPIO_PinRead(GPIO, portx, piny));
    
    return value;    
}

static void pin_irq_hdr(pint_pin_int_t pintr, uint32_t pmatch_status)
{
    int irqno = 0;
    for(irqno = 0; irqno < IRQ_MAX_VAL; irqno ++)
    {
        if((irqno) == pintr)
        {
            break;
        }
    }
    
    if(irqno >= IRQ_MAX_VAL)
        return;
    
    if (pin_irq_hdr_tab[irqno].hdr)
    {
        pin_irq_hdr_tab[irqno].hdr(pin_irq_hdr_tab[irqno].args);
    }
}

void callback(pint_pin_int_t pintr, uint32_t pmatch_status)
{
    pin_irq_hdr(pintr, pmatch_status);
}

static rt_err_t lpc_pin_attach_irq(struct rt_device *device, rt_int32_t pin,
                                   rt_uint32_t mode, void (*hdr)(void *args), void *args)
{
    int portx, piny, trigger_mode, pin_initx, pintsel, pin_cfg, i;    
    
    if(pin > PIN_MAX_VAL)
        return RT_ERROR; 
    
    portx = get_port(pin);
    piny  = get_pin(pin); 
    
    switch (mode)
        {
        case PIN_IRQ_MODE_RISING:
            trigger_mode = kPINT_PinIntEnableRiseEdge;
            break;
        case PIN_IRQ_MODE_FALLING:
            trigger_mode = kPINT_PinIntEnableFallEdge;
            break;
        case PIN_IRQ_MODE_RISING_FALLING:
            trigger_mode = kPINT_PinIntEnableBothEdges;
            break;
        case PIN_IRQ_MODE_HIGH_LEVEL:
            trigger_mode = kPINT_PinIntEnableHighLevel;
            break;
        case PIN_IRQ_MODE_LOW_LEVEL:
            trigger_mode = kPINT_PinIntEnableLowLevel;
            break;
        }
     
    /* Get inputmux_connection_t */        
    pintsel = (pin + (0xC0U << 20));
    
    for(i = 0; i < IRQ_MAX_VAL; i++)
    {
        if(pin_irq_hdr_tab[i].pin == -1)
        {
            pin_initx = kPINT_PinInt0 + i;
            pin_irq_hdr_tab[i].pin = pin;
            pin_irq_hdr_tab[i].mode = trigger_mode;
            pin_irq_hdr_tab[i].hdr = hdr;
            pin_irq_hdr_tab[i].args = args;
            break;
        }
    }
        
    if(i >= IRQ_MAX_VAL)
        return RT_ERROR;
    
    /* open clk */
    CLOCK_EnableClock(kCLOCK_InputMux);
    CLOCK_EnableClock(kCLOCK_Iocon);
    
    /* AttachSignal */    
    INPUTMUX_AttachSignal(INPUTMUX, i, (inputmux_connection_t)pintsel);
    pin_cfg = ((IOCON->PIO[portx][piny] &
              (~(IOCON_PIO_FUNC_MASK | IOCON_PIO_DIGIMODE_MASK | IOCON_PIO_FILTEROFF_MASK))) /* Mask bits to zero which are setting */
              | IOCON_PIO_FUNC(0)                /* Selects pin function.: PORT18 (pin 28) is configured as PIO1_8 */
              | IOCON_PIO_DIGIMODE(1)            /* Select Analog/Digital mode.: Digital mode. */
              | IOCON_PIO_FILTEROFF(0));         /* Controls input glitch filter.: Filter enabled. Noise pulses below approximately 10 ns are filtered out. */
                   
    IOCON_PinMuxSet(IOCON, portx, piny, pin_cfg);    
    
    /* PINT_PinInterruptConfig */
    PINT_PinInterruptConfig(PINT, (pint_pin_int_t)pin_initx, (pint_pin_enable_t)(pin_irq_hdr_tab[i].mode), callback);    
    
    CLOCK_DisableClock(kCLOCK_InputMux);
    CLOCK_DisableClock(kCLOCK_Iocon);
    
    return RT_EOK;
}

static rt_err_t lpc_pin_detach_irq(struct rt_device *device, rt_int32_t pin)
{
    int i;    
    
    if(pin > PIN_MAX_VAL)
        return RT_ERROR; 
    
    for(i = 0; i < IRQ_MAX_VAL; i++)
    {
        if(pin_irq_hdr_tab[i].pin == pin)
        {
            pin_irq_hdr_tab[i].pin = -1;
            pin_irq_hdr_tab[i].hdr = RT_NULL;
            pin_irq_hdr_tab[i].mode = 0;
            pin_irq_hdr_tab[i].args = RT_NULL;
            break;
        }
    }        
    return RT_EOK;
}

static rt_err_t lpc_pin_irq_enable(struct rt_device *device, rt_base_t pin,
                                   rt_uint32_t enabled)
{  
    int irqn_type, i;
    
    if(pin > PIN_MAX_VAL)
        return RT_ERROR; 
    
    for(i = 0; i < IRQ_MAX_VAL; i++)
    {
        if(pin_irq_hdr_tab[i].pin == pin)
        {
            switch(i)
            {
                case 0: irqn_type = PIN_INT0_IRQn; break;
                case 1: irqn_type = PIN_INT1_IRQn; break;
                case 2: irqn_type = PIN_INT2_IRQn; break;
                case 3: irqn_type = PIN_INT3_IRQn; break;
                case 4: irqn_type = PIN_INT4_IRQn; break;
                case 5: irqn_type = PIN_INT5_IRQn; break;
                case 6: irqn_type = PIN_INT6_IRQn; break;
                case 7: irqn_type = PIN_INT7_IRQn; break;
                default:break;
            }
            if(enabled)
            {
                /* PINT_EnableCallback */
                PINT_PinInterruptClrStatusAll(PINT);
                NVIC_ClearPendingIRQ((IRQn_Type)irqn_type);
                PINT_PinInterruptClrStatus(PINT, (pint_pin_int_t)(kPINT_PinInt0 + i));
                EnableIRQ((IRQn_Type)irqn_type);
            }
            else
            {
                /* PINT_DisableCallback */
                DisableIRQ((IRQn_Type)irqn_type);
                PINT_PinInterruptClrStatus(PINT, (pint_pin_int_t)(kPINT_PinInt0 + i));
                NVIC_ClearPendingIRQ((IRQn_Type)irqn_type);
            }
            break;
        }
    }  
        
    if(i >= IRQ_MAX_VAL)
        return RT_ERROR;

      return RT_EOK;
}

const static struct rt_pin_ops _lpc_pin_ops =
{
    lpc_pin_mode,
    lpc_pin_write,
    lpc_pin_read,
    lpc_pin_attach_irq,
    lpc_pin_detach_irq,
    lpc_pin_irq_enable,    
};

int rt_hw_pin_init(void)
{
    int result;
    PINT_Init(PINT);
    result = rt_device_pin_register("pin", &_lpc_pin_ops, RT_NULL);
    return result;
}
INIT_BOARD_EXPORT(rt_hw_pin_init);

#endif