/**************************************************************************//**
 * @file     libgpio.c
 * @version  V3.00
 * @brief    N9H20 series GPIO driver source file
 *
 * SPDX-License-Identifier: Apache-2.0
 * @copyright (C) 2020 Nuvoton Technology Corp. All rights reserved.
*****************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "wblib.h"
#include "N9H20_GPIO.h"

// accetiable debounce clock
static const unsigned int _clk[16] = {1, 2, 4, 8, 16, 32, 64, 128, 256, 2*256, 4*256, 8*256, 16*256, 32*256, 64*256, 128*256};

int gpio_open(unsigned char port)
{

	switch (port) {			
		case GPIO_PORTA:
			outpw(REG_GPAFUN , inpw(REG_GPAFUN) &~ (0xF00000));
			break;			
		case GPIO_PORTD:
			outpw(REG_GPDFUN , inpw(REG_GPDFUN) &~ (0xFF0003FF));
			break;
		case GPIO_PORTE:
			outpw(REG_GPEFUN , inpw(REG_GPEFUN) &~ (0xFF0));
			break;	
		case GPIO_PORTB:				
		case GPIO_PORTC:			
			break;	
		default:
			return(-1);

	}
	return(0);

}

int gpio_configure(unsigned char port, unsigned short num)
{

	switch (port) {			
		case GPIO_PORTA:
			if(num<=11)
				outpw(REG_GPAFUN , inpw(REG_GPAFUN) &~ (0x3 << (num<<1)));
			else
				return(-1);
			break;	
		case GPIO_PORTB:
			outpw(REG_GPBFUN , inpw(REG_GPBFUN) &~ (0x3 << (num<<1)));
			break;
		case GPIO_PORTC:
			outpw(REG_GPCFUN , inpw(REG_GPCFUN) &~ (0x3 << (num<<1)));			
			break;
		case GPIO_PORTD:
			outpw(REG_GPDFUN , inpw(REG_GPDFUN) &~ (0x3 << (num<<1)));			
			break;
		case GPIO_PORTE:
			if(num<=11)
				outpw(REG_GPEFUN , inpw(REG_GPEFUN) &~ (0x3 << (num<<1)));		
			else
				return(-1);
			break;	
			
		default:
			return(-1);

	}
	return(0);

}

int gpio_readport(unsigned char port, unsigned short *val)
{
	switch (port) {
	
		case GPIO_PORTA:
			*val = (inpw(REG_GPIOA_PIN) & 0x0fff);
			break;
		case GPIO_PORTB:
			*val = (inpw(REG_GPIOB_PIN) & 0xffff);
			break;			
		case GPIO_PORTC:
			*val = (inpw(REG_GPIOC_PIN) & 0xffff);
			break;		
		case GPIO_PORTD:
			*val = (inpw(REG_GPIOD_PIN) & 0xffff);
			break;	
		case GPIO_PORTE:
			*val = (inpw(REG_GPIOE_PIN) & 0x0fff);
			break;		
		default:
			return(-1);

	}
	return(0);
}

int gpio_setportdir(unsigned char port, unsigned short mask, unsigned short dir)
{
	switch (port) {

		case GPIO_PORTA:
			outpw(REG_GPIOA_OMD , inpw(REG_GPIOA_OMD) & ~(mask & (mask ^ dir)));
			outpw(REG_GPIOA_OMD , inpw(REG_GPIOA_OMD) | (mask & dir));			
			break;
		case GPIO_PORTB:
			outpw(REG_GPIOB_OMD , inpw(REG_GPIOB_OMD) & ~(mask & (mask ^ dir)));
			outpw(REG_GPIOB_OMD , inpw(REG_GPIOB_OMD) | (mask & dir));	
			break;			
		case GPIO_PORTC:
			outpw(REG_GPIOC_OMD , inpw(REG_GPIOC_OMD) & ~(mask & (mask ^ dir)));
			outpw(REG_GPIOC_OMD , inpw(REG_GPIOC_OMD) | (mask & dir));	
			break;		
		case GPIO_PORTD:
			outpw(REG_GPIOD_OMD , inpw(REG_GPIOD_OMD) & ~(mask & (mask ^ dir)));
			outpw(REG_GPIOD_OMD , inpw(REG_GPIOD_OMD) | (mask & dir));	
			break;	
		case GPIO_PORTE:
			outpw(REG_GPIOE_OMD , inpw(REG_GPIOE_OMD) & ~(mask & (mask ^ dir)));
			outpw(REG_GPIOE_OMD , inpw(REG_GPIOE_OMD) | (mask & dir));	
			break;		
		default:
			return(-1);

	}
	return(0);
	

}

int gpio_setportval(unsigned char port, unsigned short mask, unsigned short val)
{
	switch (port) {
	
		case GPIO_PORTA:
			outpw(REG_GPIOA_DOUT , inpw(REG_GPIOA_DOUT) & ~(mask & (mask ^ val)));
			outpw(REG_GPIOA_DOUT , inpw(REG_GPIOA_DOUT) | (mask & val));			
			break;
		case GPIO_PORTB:
			outpw(REG_GPIOB_DOUT , inpw(REG_GPIOB_DOUT) & ~(mask & (mask ^ val)));
			outpw(REG_GPIOB_DOUT , inpw(REG_GPIOB_DOUT) | (mask & val));
			break;			
		case GPIO_PORTC:
			outpw(REG_GPIOC_DOUT , inpw(REG_GPIOC_DOUT) & ~(mask & (mask ^ val)));
			outpw(REG_GPIOC_DOUT , inpw(REG_GPIOC_DOUT) | (mask & val));
			break;		
		case GPIO_PORTD:
			outpw(REG_GPIOD_DOUT , inpw(REG_GPIOD_DOUT) & ~(mask & (mask ^ val)));
			outpw(REG_GPIOD_DOUT , inpw(REG_GPIOD_DOUT) | (mask & val));
			break;	
		case GPIO_PORTE:
			outpw(REG_GPIOE_DOUT , inpw(REG_GPIOE_DOUT) & ~(mask & (mask ^ val)));
			outpw(REG_GPIOE_DOUT , inpw(REG_GPIOE_DOUT) | (mask & val));
			break;		
		default:
			return(-1);

	}
	return(0);

}

int gpio_setportpull(unsigned char port, unsigned short mask, unsigned short pull)
{
	switch (port) {
	
		case GPIO_PORTA:
			outpw(REG_GPIOA_PUEN , inpw(REG_GPIOA_PUEN) & ~(mask & (mask ^ pull)));
			outpw(REG_GPIOA_PUEN , inpw(REG_GPIOA_PUEN) | (mask & pull));			
			break;
		case GPIO_PORTB:
			outpw(REG_GPIOB_PUEN , inpw(REG_GPIOB_PUEN) & ~(mask & (mask ^ pull)));
			outpw(REG_GPIOB_PUEN , inpw(REG_GPIOB_PUEN) | (mask & pull));	
			break;			
		case GPIO_PORTC:
			outpw(REG_GPIOC_PUEN , inpw(REG_GPIOC_PUEN) & ~(mask & (mask ^ pull)));
			outpw(REG_GPIOC_PUEN , inpw(REG_GPIOC_PUEN) | (mask & pull));	
			break;	
		case GPIO_PORTD:
			outpw(REG_GPIOD_PUEN , inpw(REG_GPIOD_PUEN) & ~(mask & (mask ^ pull)));
			outpw(REG_GPIOD_PUEN , inpw(REG_GPIOD_PUEN) | (mask & pull));	
			break;
		case GPIO_PORTE:
			outpw(REG_GPIOE_PUEN , inpw(REG_GPIOE_PUEN) & ~(mask & (mask ^ pull)));
			outpw(REG_GPIOE_PUEN , inpw(REG_GPIOE_PUEN) | (mask & pull));	
			break;		
		default:
			return(-1);

	}
	return(0);

}

int gpio_setdebounce(unsigned int clk, unsigned char src)
{
	int i;
	
	if(clk > 128*256 || src > 0xf)
		return(-1);
	
	// clk could only be 1, 2, 4, 8, ... 128*256
	for(i = 0 ; i < 16; i++) {
		if(_clk[i] == clk)
			break;
	}
	if(i == 16)
		return(-1);

	outpw(REG_DBNCECON , (i << 4 | src));
		
	return(0);				
}

void gpio_getdebounce(unsigned int *clk, unsigned char *src)
{
	*clk = _clk[(inpw(REG_DBNCECON) >> 4) & 0xf];
	*src = inpw(REG_DBNCECON) & 0xf;
	
	return;
}

int gpio_setsrcgrp(unsigned char port, unsigned short mask, unsigned char irq)
{

	const unsigned int _irq[4] = {0, 0x55555555, 0xaaaaaaaa, 0xffffffff};
	unsigned int _mask = 0;
	int i;
	
	
	if(irq > 3)
		return(-1);	
	
	if(mask > 0xffff)
		return(-1);
		
	for(i = 0; i < 16; i++) {
		if(mask & (1 << i))
			_mask += (3 << (i << 1));	
	}
		
	switch (port) {	
		case GPIO_PORTA:
			outpw(REG_IRQSRCGPA , inpw(REG_IRQSRCGPA) & ~_mask);
			outpw(REG_IRQSRCGPA , inpw(REG_IRQSRCGPA) | (_mask & _irq[irq]));			
			break;
		case GPIO_PORTB:
			outpw(REG_IRQSRCGPB , inpw(REG_IRQSRCGPB) & ~_mask);
			outpw(REG_IRQSRCGPB , inpw(REG_IRQSRCGPB) | (_mask & _irq[irq]));
			break;			
		case GPIO_PORTC:
			outpw(REG_IRQSRCGPC , inpw(REG_IRQSRCGPC) & ~_mask);
			outpw(REG_IRQSRCGPC , inpw(REG_IRQSRCGPC) | (_mask & _irq[irq]));
			break;	
		case GPIO_PORTD:
			outpw(REG_IRQSRCGPD , inpw(REG_IRQSRCGPD) & ~_mask);
			outpw(REG_IRQSRCGPD , inpw(REG_IRQSRCGPD) | (_mask & _irq[irq]));
			break;
		case GPIO_PORTE:
			outpw(REG_IRQSRCGPE , inpw(REG_IRQSRCGPE) & ~_mask);
			outpw(REG_IRQSRCGPE , inpw(REG_IRQSRCGPE) | (_mask & _irq[irq]));
			break;		
		default:
			return(-1);

	}
	return(0);
}

int gpio_getsrcgrp(unsigned char port, unsigned int *val)
{
	switch (port) {	
		case GPIO_PORTA:		
			*val = inpw(REG_IRQSRCGPA);
			break;
		case GPIO_PORTB:
			*val = inpw(REG_IRQSRCGPB);
			break;			
		case GPIO_PORTC:
			*val = inpw(REG_IRQSRCGPC);
			break;	
		case GPIO_PORTD:
			*val = inpw(REG_IRQSRCGPD);
			break;
		case GPIO_PORTE:			
			*val = inpw(REG_IRQSRCGPE);
			break;		
		default:
			return(-1);

	}
	return(0);

}

int gpio_setintmode(unsigned char port, unsigned short mask, unsigned short falling, unsigned short rising)
{
	
	if(mask > 0xffff)
		return(-1);
		
	switch (port) {	
		case GPIO_PORTA:	
			outpw(REG_IRQENGPA , inpw(REG_IRQENGPA) & ~((mask << 16) | mask));
			outpw(REG_IRQENGPA , inpw(REG_IRQENGPA) | (((mask & rising) << 16) | (mask & falling)));			
			break;
		case GPIO_PORTB:
			outpw(REG_IRQENGPB , inpw(REG_IRQENGPB) & ~((mask << 16) | mask));
			outpw(REG_IRQENGPB , inpw(REG_IRQENGPB) | (((mask & rising) << 16) | (mask & falling)));
			break;			
		case GPIO_PORTC:
			outpw(REG_IRQENGPC , inpw(REG_IRQENGPC) & ~((mask << 16) | mask));
			outpw(REG_IRQENGPC , inpw(REG_IRQENGPC) | (((mask & rising) << 16) | (mask & falling)));
			break;	
		case GPIO_PORTD:
			outpw(REG_IRQENGPD , inpw(REG_IRQENGPD) & ~((mask << 16) | mask));
			outpw(REG_IRQENGPD , inpw(REG_IRQENGPD) | (((mask & rising) << 16) | (mask & falling)));
			break;	
		case GPIO_PORTE:
			outpw(REG_IRQENGPE , inpw(REG_IRQENGPE) & ~((mask << 16) | mask));
			outpw(REG_IRQENGPE , inpw(REG_IRQENGPE) | (((mask & rising) << 16) | (mask & falling)));		
			break;		
		default:
			return(-1);

	}
	return(0);		
		
}


int gpio_getintmode(unsigned char port, unsigned short *falling, unsigned short *rising)
{

	switch (port) {	
		case GPIO_PORTA:		
			*rising = inpw(REG_IRQENGPA) >> 16;
			*falling = inpw(REG_IRQENGPA) & 0xffff;
			break;
		case GPIO_PORTB:
			*rising = inpw(REG_IRQENGPB) >> 16;
			*falling = inpw(REG_IRQENGPB) & 0xffff;
			break;			
		case GPIO_PORTC:
			*rising = inpw(REG_IRQENGPC) >> 16;
			*falling = inpw(REG_IRQENGPC) & 0xffff;
			break;	
		case GPIO_PORTD:
			*rising = inpw(REG_IRQENGPD) >> 16;
			*falling = inpw(REG_IRQENGPD) & 0xffff;
			break;
		case GPIO_PORTE:
			*rising = inpw(REG_IRQENGPE)  >> 16;
			*falling = inpw(REG_IRQENGPE) & 0xffff;		
			break;		
		default:
			return(-1);

	}
	return(0);
}


int gpio_setlatchtrigger(unsigned char src)
{
	if(src > 0xf)
		return(-1);

	outpw(REG_IRQLHSEL , src);		
	return(0);

}

void gpio_getlatchtrigger(unsigned char *src)
{

	*src = inpw(REG_IRQLHSEL) & 0xf;
	return;
}

int gpio_getlatchval(unsigned char port, unsigned short *val)
{

	switch (port) {	
		case GPIO_PORTA:		
			*val = inpw(REG_IRQLHGPA) & 0xffff;
			break;
		case GPIO_PORTB:
			*val = inpw(REG_IRQLHGPB) & 0xffff;
			break;			
		case GPIO_PORTC:
			*val = inpw(REG_IRQLHGPC) & 0xffff;
			break;	
		case GPIO_PORTD:
			*val = inpw(REG_IRQLHGPD) & 0xffff;
			break;
		case GPIO_PORTE:
			*val = inpw(REG_IRQLHGPE) & 0xffff;
			break;		
		default:
			return(-1);

	}

	return(0);
}


int gpio_gettriggersrc(unsigned char port, unsigned short *src)
{

	switch (port) {	
		case GPIO_PORTA:		
			*src = inpw(REG_IRQTGSRC0) & 0xffff;						
			break;
		case GPIO_PORTB:
			*src = (inpw(REG_IRQTGSRC0) & 0xffff0000) >> 16;						
			break;			
		case GPIO_PORTC:
			*src = inpw(REG_IRQTGSRC1) & 0xffff;						
			break;	
		case GPIO_PORTD:
			*src = (inpw(REG_IRQTGSRC1) & 0xffff0000) >> 16;						
			break;	
		case GPIO_PORTE:
			*src = inpw(REG_IRQTGSRC2) & 0xffff;						
			break;		
		default:
			return(-1);

	}

	return(0);
}

int gpio_cleartriggersrc(unsigned char port)
{

	switch (port) {	
		case GPIO_PORTA:					
			outpw(REG_IRQTGSRC0 , inpw(REG_IRQTGSRC0) & 0xffff);			
			break;
		case GPIO_PORTB:			
			outpw(REG_IRQTGSRC0 , inpw(REG_IRQTGSRC0) & 0xffff0000);			
			break;			
		case GPIO_PORTC:			
			outpw(REG_IRQTGSRC1 , inpw(REG_IRQTGSRC1) & 0xffff);			
			break;	
		case GPIO_PORTD:			
			outpw(REG_IRQTGSRC1 , inpw(REG_IRQTGSRC1) & 0xffff0000);			
			break;	
		case GPIO_PORTE:			
			outpw(REG_IRQTGSRC2 , inpw(REG_IRQTGSRC2) & 0xffff);			
			break;		
		default:
			return(-1);

	}

	return(0);
}