/**
 * @file wm_gpio.c
 *
 * @brief GPIO Driver Module
 *
 * @author dave
 *
 * Copyright (c) 2014 Winner Microelectronics Co., Ltd.
 */
#include "wm_gpio.h"
#include "wm_regs.h"
#include "wm_irq.h"
#include "wm_osal.h"
#include "tls_common.h"

struct gpio_irq_context{
    tls_gpio_irq_callback callback;
    void *arg;
};


static struct gpio_irq_context gpio_context[WM_IO_PB_30 - WM_IO_PA_00 + 1] = {{0,0}};


void GPIOA_IRQHandler(void)
{
	u8  i     = 0;
	u8  found = 0;
	u32 reg   = 0;

    reg = tls_reg_read32(HR_GPIO_MIS);

    for (i = 0; i <= WM_IO_PA_15; i++)
    {
        if (reg & BIT(i))
        {
            found = 1;
            break;
        }
    }

    if (found)
    {
        if (NULL != gpio_context[i].callback)
            gpio_context[i].callback(gpio_context[i].arg);
    }
	return;

}

void GPIOB_IRQHandler(void)
    {
	u8  i     = 0;
	u8  found = 0;
    u32 reg   = 0;
	
    reg = tls_reg_read32(HR_GPIO_MIS + TLS_IO_AB_OFFSET);


    for (i = WM_IO_PB_00; i <= WM_IO_PB_31; i++)
    {
        if (reg & BIT(i - WM_IO_PB_00))
        {
            found = 1;
            break;
        }
    }

    if (found)
    {
        if (NULL != gpio_context[i].callback)
            gpio_context[i].callback(gpio_context[i].arg);
    }

    return;
}

/**
 * @brief          This function is used to config gpio function
 *
 * @param[in]      gpio_pin    gpio pin num
 * @param[in]      dir         gpio direction
 * @param[in]      attr        gpio attribute
 *
 * @return         None
 *
 * @note
 * All gpio's attribute can only be set as WM_GPIO_ATTR_FLOATING or WM_GPIO_ATTR_PULLHIGH on W600/W60X.
 */
void tls_gpio_cfg(enum tls_io_name gpio_pin, enum tls_gpio_dir dir, enum tls_gpio_attr attr)
{
    u8  pin;
    u16 offset;

    if (gpio_pin >= WM_IO_PB_00)
    {
        pin = gpio_pin - WM_IO_PB_00;
        offset = TLS_IO_AB_OFFSET;
    }
    else
    {
        pin = gpio_pin;
        offset = 0;
    }

    /* enable gpio function */
    tls_io_cfg_set(gpio_pin, WM_IO_OPT5_GPIO);

    /* gpio direction */
    if (WM_GPIO_DIR_OUTPUT == dir) 
	{
        tls_reg_write32(HR_GPIO_DIR + offset, tls_reg_read32(HR_GPIO_DIR + offset) | BIT(pin));      /* 1 set output */
	}
    else if (WM_GPIO_DIR_INPUT == dir) 
	{
        tls_reg_write32(HR_GPIO_DIR + offset, tls_reg_read32(HR_GPIO_DIR + offset) & (~BIT(pin)));   /* 0 set input */
	}

    /* gpio pull attribute */
    if (WM_GPIO_ATTR_FLOATING == attr) 
	{
        tls_reg_write32(HR_GPIO_PULL_EN + offset, tls_reg_read32(HR_GPIO_PULL_EN + offset) | BIT(pin));  	   /* 1 disable pull */
	}
    else if (WM_GPIO_ATTR_PULLHIGH == attr) 
	{
        tls_reg_write32(HR_GPIO_PULL_EN + offset, tls_reg_read32(HR_GPIO_PULL_EN + offset) & (~BIT(pin)));      /* 0 enable pull */
	}
}

/**
 * @brief          This function is used to read gpio status
 *
 * @param[in]      gpio_pin    gpio pin num
 *
 * @retval         0     low level
 * @retval         1     high level
 *
 * @note           None
 */
u8 tls_gpio_read(enum tls_io_name gpio_pin)
{
	u32 reg_en;
	u32 reg;
	u8  pin;
    u16 offset;

    if (gpio_pin >= WM_IO_PB_00)
    {
        pin    = gpio_pin - WM_IO_PB_00;
        offset = TLS_IO_AB_OFFSET;
    }
    else
    {
        pin    = gpio_pin;
        offset = 0;
    }

	reg_en = tls_reg_read32(HR_GPIO_DATA_EN + offset);
	tls_reg_write32(HR_GPIO_DATA_EN + offset, reg_en | (1 << pin));
	reg = tls_reg_read32(HR_GPIO_DATA + offset);
	tls_reg_write32(HR_GPIO_DATA_EN + offset, reg_en);
	if(reg & (0x1 << pin))
		return 1;
	else
		return 0;
}

/**
 * @brief          This function is used to modify gpio status
 *
 * @param[in]      gpio_pin    gpio pin num
 * @param[in]      value       power level
 *                             0: low level
 * 					           1: high level
 *
 * @return         None
 *
 * @note           None
 */
void tls_gpio_write(enum tls_io_name gpio_pin, u8 value)
{
	u32 cpu_sr = 0;
	u32 reg;
	u32	reg_en;
  u8  pin;
    u16 offset;

    if (gpio_pin >= WM_IO_PB_00)
    {
        pin    = gpio_pin - WM_IO_PB_00;
        offset = TLS_IO_AB_OFFSET;
    }
    else
    {
        pin    = gpio_pin;
        offset = 0;
    }

	
	cpu_sr = tls_os_set_critical();
	
	reg_en = tls_reg_read32(HR_GPIO_DATA_EN + offset);
	tls_reg_write32(HR_GPIO_DATA_EN + offset, reg_en | (1 << pin));

	reg = tls_reg_read32(HR_GPIO_DATA + offset);
	if(value)
		tls_reg_write32(HR_GPIO_DATA + offset, reg |  (1 << pin));	/* write high */
	else
		tls_reg_write32(HR_GPIO_DATA + offset, reg & (~(1 << pin)));/* write low */

    tls_reg_write32(HR_GPIO_DATA_EN + offset, reg_en);

	tls_os_release_critical(cpu_sr);
}

/**
 * @brief          This function is used to config gpio interrupt
 *
 * @param[in]      gpio_pin    gpio pin num
 * @param[in]      mode        interrupt trigger type
 *
 * @return         None
 *
 * @note           None
 */
void tls_gpio_irq_enable(enum tls_io_name gpio_pin, enum tls_gpio_irq_trig mode)
{
	u32 reg;
	u8  pin;
    u16 offset;
    u8  vec_no;

    if (gpio_pin >= WM_IO_PB_00)
    {
        pin    = gpio_pin - WM_IO_PB_00;
        offset = TLS_IO_AB_OFFSET;
        vec_no = GPIOB_INT;
    }
    else
    {
        pin    = gpio_pin;
        offset = 0;
        vec_no = GPIO_INT;
    }
	switch(mode)
	{
		case WM_GPIO_IRQ_TRIG_RISING_EDGE:
			reg = tls_reg_read32(HR_GPIO_IS + offset);
			reg &= (~(0x1 << pin));
			tls_reg_write32(HR_GPIO_IS + offset, reg);		/* 0 edge trigger */
			reg = tls_reg_read32(HR_GPIO_IBE + offset);
			reg &= (~(0x1 << pin));
			tls_reg_write32(HR_GPIO_IBE + offset, reg);	/* 0 edge trigger */
			reg = tls_reg_read32(HR_GPIO_IEV + offset);
			reg |= (0x1 << pin);
			tls_reg_write32(HR_GPIO_IEV + offset, reg);	/* 1 rising edge trigger */
			break;
		case WM_GPIO_IRQ_TRIG_FALLING_EDGE:
			reg = tls_reg_read32(HR_GPIO_IS + offset);
			reg &= (~(0x1 << pin));
			tls_reg_write32(HR_GPIO_IS + offset, reg);		/* 0 edge trigger */
			reg = tls_reg_read32(HR_GPIO_IBE + offset);
			reg &= (~(0x1 << pin));
			tls_reg_write32(HR_GPIO_IBE + offset, reg);	/* 0 edge trigger */
			reg = tls_reg_read32(HR_GPIO_IEV + offset);
			reg &= (~(0x1 << pin));
			tls_reg_write32(HR_GPIO_IEV + offset, reg);	/* 0 falling edge trigger */
			break;
		case WM_GPIO_IRQ_TRIG_DOUBLE_EDGE:
			reg = tls_reg_read32(HR_GPIO_IS + offset);
			tls_reg_write32(HR_GPIO_IS + offset, reg & (~(0x1 << pin)));	/* 0 edge trigger */
			reg = tls_reg_read32(HR_GPIO_IBE + offset);
			tls_reg_write32(HR_GPIO_IBE + offset, reg | (0x1 << pin));       /* 1 double edge trigger */
			break;
		case WM_GPIO_IRQ_TRIG_HIGH_LEVEL:
			reg = tls_reg_read32(HR_GPIO_IS + offset);
			tls_reg_write32(HR_GPIO_IS + offset, reg | (0x1 << pin));		/* 1 level trigger */
			reg = tls_reg_read32(HR_GPIO_IEV + offset);
			tls_reg_write32(HR_GPIO_IEV + offset, reg | (0x1 << pin));		/* 1 high level trigger */
			break;
		case WM_GPIO_IRQ_TRIG_LOW_LEVEL:
			reg = tls_reg_read32(HR_GPIO_IS + offset);
			tls_reg_write32(HR_GPIO_IS + offset, reg | (0x1 << pin));		/* 1 level trigger */
			reg = tls_reg_read32(HR_GPIO_IEV + offset);
			tls_reg_write32(HR_GPIO_IEV + offset, reg & (~(0x1 << pin)));	/* 0 low level trigger */
			break;
	}

	reg = tls_reg_read32(HR_GPIO_IE + offset);
	reg |= (0x1 << pin);
	tls_reg_write32(HR_GPIO_IE + offset, reg);		/* enable interrupt */
	tls_irq_enable(vec_no);
}

/**
 * @brief          This function is used to disable gpio interrupt
 *
 * @param[in]      gpio_pin    gpio pin num
 *
 * @return         None
 *
 * @note           None
 */
void tls_gpio_irq_disable(enum tls_io_name gpio_pin)
{
	u32 reg;
	u8  pin;
    u16 offset;

    if (gpio_pin >= WM_IO_PB_00)
    {
        pin    = gpio_pin - WM_IO_PB_00;
        offset = TLS_IO_AB_OFFSET;
    }
    else
    {
        pin    = gpio_pin;
        offset = 0;
    }

	reg = tls_reg_read32(HR_GPIO_IE + offset);
	tls_reg_write32(HR_GPIO_IE + offset, reg & (~(0x1 << pin)));	/* disable interrupt */
	tls_irq_disable(GPIO_INT);
}

/**
 * @brief          This function is used to get gpio interrupt status
 *
 * @param[in]      gpio_pin    gpio pin num
 *
 * @retval         0     no interrupt happened
 * @retval         1     interrupt happened
 *
 * @note           None
 */
u8 tls_get_gpio_irq_status(enum tls_io_name gpio_pin)
{
	u32 reg;
	u8  pin;
    u16 offset;

    if (gpio_pin >= WM_IO_PB_00)
    {
        pin    = gpio_pin - WM_IO_PB_00;
        offset = TLS_IO_AB_OFFSET;
    }
    else
    {
        pin    = gpio_pin;
        offset = 0;
    }

	reg = tls_reg_read32(HR_GPIO_RIS + offset);
	if(reg & (0x1 << pin))
		return	1;
	else
		return	0;
}

/**
 * @brief          This function is used to clear gpio interrupt flag
 *
 * @param[in]      gpio_pin    gpio pin num
 *
 * @return         None
 *
 * @note           None
 */
void tls_clr_gpio_irq_status(enum tls_io_name gpio_pin)
{
	u32 reg;
	u8  pin;
    u16 offset;

    if (gpio_pin >= WM_IO_PB_00)
    {
        pin    = gpio_pin - WM_IO_PB_00;
        offset = TLS_IO_AB_OFFSET;
    }
    else
    {
        pin    = gpio_pin;
        offset = 0;
    }

	reg = tls_reg_read32(HR_GPIO_IC + offset);
	tls_reg_write32(HR_GPIO_IC + offset, reg | (0x1 << pin));		/* 1 clear interrupt status */
}

/**
 * @brief          This function is used to register gpio interrupt
 *
 * @param[in]      gpio_pin    gpio pin num
 * @param[in]      callback    the gpio interrupt call back function
 * @param[in]      arg         parammeter for the callback
 *
 * @return         None
 *
 * @note
 * gpio callback function is called in interrupt,
 * so can not operate the critical data in the callback fuuction,
 * recommendation to send messages to other tasks to operate it.
 */
void tls_gpio_isr_register(enum tls_io_name gpio_pin,
                           tls_gpio_irq_callback callback,
                           void *arg)
{
    gpio_context[gpio_pin].callback = callback;
    gpio_context[gpio_pin].arg = arg;
}

