/*-----------------------------------------------------------------------------
 * cxd4105_gpio.c
 *
 * a device driver file for gpio
 *
 * Copyright 2008 Sony Corporation
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA  02110-1301, USA.
 *---------------------------------------------------------------------------*/

#include <linux/module.h>
#include <linux/proc_fs.h>
#include <linux/device.h>
#include <linux/amba/bus.h>
#include <linux/spinlock.h>

#include <asm/io.h>
#include <asm/arch/sonymisc.h>
#include <asm/arch/cxd4105_gpio.h>
#include <asm/arch/regs-gpio.h>

#define MINOR_NO_GPIO		1
#define	CXD4105_GPIO_EASY_BASE	CXD4105_GPIO(0)
#define	CXD4105_GPIO_EASY_SIZE	0x00001400
#define	CXD4105_GPIO_SYS_BASE	CXD4105_GPIOSYS
#define	CXD4105_GPIO_SYS_SIZE	0x00000100
#define CXD4105_GPIO_BASE	CXD4105_GPIO_EASY_BASE	
#define CXD4105_GPIO_MEMSIZE	(CXD4105_GPIO_EASY_SIZE+CXD4105_GPIO_SYS_SIZE)

#define GPIO_PROC	"gpio"
#define CMD_GPIOSYS	"sys"
#define CMD_GPIOEASY	"easy"
#define GPIO_NAME	"cxd4105-gpio"
#define GPIO_VERSION	"00.01.006"
#define	DEV_NAME	"CXD4105 GPIO driver"
#define GPIO_PID	CXD4105_GPIO_BASE

/*--------------------------------------------------------------------------*/
// Register base
/*--------------------------------------------------------------------------*/
#define	GPIO_SYS_BASE		VA_GPIOSYS
#define	GPIO_EASY_BASE(x)	VA_GPIO(x)
#define	GPIO_SYS_NUM		1
#define	GPIO_EASY_NUM		17
#define	GPIO_SYS_PORT		GPIO_PORT_17

/*--------------------------------------------------------------------------*/
// Register assign
/*--------------------------------------------------------------------------*/
// GPIO SYS
#define GPIO_SYS_DIR		( GPIO_SYS_BASE + GPIO_DIR )
#define GPIO_SYS_RDATA		( GPIO_SYS_BASE + GPIO_DATA_READ )
#define GPIO_SYS_DATASET	( GPIO_SYS_BASE + GPIO_DATA_SET )
#define GPIO_SYS_DATACLR	( GPIO_SYS_BASE + GPIO_DATA_CLEAR )
#define GPIO_SYS_INTS		( GPIO_SYS_BASE + GPIO_INTLS )
#define GPIO_SYS_INTHE		( GPIO_SYS_BASE + GPIO_INTHE )
#define GPIO_SYS_INTLE		( GPIO_SYS_BASE + GPIO_INTLE )
#define GPIO_SYS_INTMASK	( GPIO_SYS_BASE + GPIO_INTEN )
#define GPIO_SYS_INTST		( GPIO_SYS_BASE + GPIO_INTST )
#define GPIO_SYS_INTSTCLR	( GPIO_SYS_BASE + GPIO_INTCL )
#define GPIO_SYS_INEN		( GPIO_SYS_BASE + GPIO_INPUTEN )
#define GPIO_SYS_RESEN		( GPIO_SYS_BASE + GPIO_RESEN )
#define GPIO_SYS_PUPD		( GPIO_SYS_BASE + GPIO_PUPD )
// GPIO EASY
#define GPIO_EASY_DIR(x)	( GPIO_EASY_BASE(x) + GPIO_DIR )
#define GPIO_EASY_RDATA(x)	( GPIO_EASY_BASE(x) + GPIO_DATA_READ )
#define GPIO_EASY_DATASET(x)	( GPIO_EASY_BASE(x) + GPIO_DATA_SET )
#define GPIO_EASY_DATACLR(x)	( GPIO_EASY_BASE(x) + GPIO_DATA_CLEAR )
#define GPIO_EASY_INEN(x)	( GPIO_EASY_BASE(x) + GPIOE_INEN )
#define GPIO_EASY_PORTSEL(x,y)	( GPIO_EASY_BASE(x) + GPIOE_PORTSEL(y))
#define GPIO_EASY_HOLDEN(x)	( GPIO_EASY_BASE(x) + GPIOE_HOLDEN )
#define GPIO_EASY_RESEN(x)	( GPIO_EASY_BASE(x) + GPIOE_RESEN )
#define GPIO_EASY_PUPD(x)	( GPIO_EASY_BASE(x) + GPIOE_PUPD )

// return 0 if x is GPIO_EASY
#define check_gpio_sys(x)	( ( x == GPIO_SYS_PORT ) ? 1 : 0 )

#define GPIO_DIRREG(x)		( ( check_gpio_sys(x) != 0 ) ? \
					GPIO_SYS_DIR : GPIO_EASY_DIR(x) )
#define GPIO_RDATA(x)		( ( check_gpio_sys(x) != 0 ) ? \
					GPIO_SYS_RDATA : GPIO_EASY_RDATA(x) )
#define GPIO_DATASET(x)		( ( check_gpio_sys(x) != 0 ) ? \
					GPIO_SYS_DATASET:GPIO_EASY_DATASET(x))
#define GPIO_DATACLR(x)		( ( check_gpio_sys(x) != 0 ) ? \
					GPIO_SYS_DATACLR:GPIO_EASY_DATACLR(x))
#define GPIO_INEN(x)		( ( check_gpio_sys(x) != 0 ) ? \
					GPIO_SYS_INEN : GPIO_EASY_INEN(x) )
#define GPIO_RESENREG(x)		( ( check_gpio_sys(x) != 0 ) ? \
					GPIO_SYS_RESEN : GPIO_EASY_RESEN(x) )
#define GPIO_PUPDREG(x)		( ( check_gpio_sys(x) != 0 ) ? \
					GPIO_SYS_PUPD : GPIO_EASY_PUPD(x) )

/*--------------------------------------------------------------------------*/
// Global variable
/*--------------------------------------------------------------------------*/

struct gpio_sys_reg{
    gpio_bitmap dir;
    gpio_bitmap rdata;
    gpio_bitmap ints;
    gpio_bitmap inthe;
    gpio_bitmap intle;
    gpio_bitmap intmask;
    gpio_bitmap inen;
    gpio_bitmap resen;
    gpio_bitmap pupd;
};

struct gpio_easy_reg{
    gpio_bitmap dir;
    gpio_bitmap rdata;
    gpio_bitmap inen;
    gpio_bitmap portsel;
    gpio_bitmap holden;
    gpio_bitmap resen;
    gpio_bitmap pupd;
};

struct gpio_reg{
    struct gpio_sys_reg  sys;
    struct gpio_easy_reg easy[GPIO_EASY_NUM];
};

struct port_status{
	const gpio_bitmap valid;	// valid bits
	gpio_bitmap 	  regist;	// registered bits
};

static struct gpio_reg*		reg_bkup = NULL;
static spinlock_t 		gpio_lock[GPIO_PORT_MAX];

static struct port_status port_infos[GPIO_PORT_MAX] = {
	{ .valid = 0xFFFF, .regist = 0x0000},	// GPIO_PORT_0
	{ .valid = 0xFFFF, .regist = 0x0000},	// GPIO_PORT_1
	{ .valid = 0x07FF, .regist = 0x0000},	// GPIO_PORT_2
	{ .valid = 0xFFFF, .regist = 0x0000},	// GPIO_PORT_3
	{ .valid = 0xFFFF, .regist = 0x0000},	// GPIO_PORT_4
	{ .valid = 0xFFFF, .regist = 0x0000},	// GPIO_PORT_5
	{ .valid = 0xFFFF, .regist = 0x0000},	// GPIO_PORT_6
	{ .valid = 0x00BF, .regist = 0x0000},	// GPIO_PORT_7
	{ .valid = 0xFFFF, .regist = 0x0000},	// GPIO_PORT_8
	{ .valid = 0x0FFF, .regist = 0x0000},	// GPIO_PORT_9
	{ .valid = 0xFBFB, .regist = 0x0000},	// GPIO_PORT_10
	{ .valid = 0x0FFF, .regist = 0x0000},	// GPIO_PORT_11
	{ .valid = 0xFFFF, .regist = 0x0000},	// GPIO_PORT_12
	{ .valid = 0x037F, .regist = 0x0000},	// GPIO_PORT_13
	{ .valid = 0xFFFF, .regist = 0x0000},	// GPIO_PORT_14
	{ .valid = 0xFFFF, .regist = 0x0000},	// GPIO_PORT_15
	{ .valid = 0x06FF, .regist = 0x0000},	// GPIO_PORT_16
	{ .valid = 0xFFFF, .regist = 0x0000},	// GPIO_PORT_17(GPIO_SYS)
};

#define	GPIO_CMD_PROC_STR	7
static char gpio_cmd_proc[GPIO_CMD_PROC_STR+1];

/*--------------------------------------------------------------------------*/
// MACRO / inline
/*--------------------------------------------------------------------------*/
//#define DEBUG_GPIO
#ifdef DEBUG_GPIO
#define DBG_MSG(arg...)		do{ printk(arg); } while (0)
#else
#define DBG_MSG(arg...)		do{  }while(0)
#endif

#ifdef DEBUG_GPIO
enum{
	GPIO_CMD_TEST_SUSPEND = (GPIO_CMD_SET_MODE+1),
	GPIO_CMD_TEST_RESUME
};
#endif

#define GPIOMODE_FUNC		( GPIOMODE_GPIO |GPIOMODE_PERIPHERAL )
#define GPIOMODE_DIR		( GPIOMODE_INPUT |\
					GPIOMODE_OUTPUT_H | GPIOMODE_OUTPUT_L )
#define GPIOMODE_BUSCTRL	( GPIOMODE_PULLUP | GPIOMODE_PULLDOWN | \
					GPIOMODE_BUSHOLD | GPIOMODE_PUPDOFF )

static inline gpio_bitmask mask_val( gpio_bitnum bit )
{
	return ( 1 << bit );
}

/*--------------------------------------------------------------------------*/
// IO access inline functions
/*--------------------------------------------------------------------------*/
static inline gpio_bitmap gpio_read( const void __iomem *addr ){

#if GPIO_MAXBIT_WIDTH <= 8
	return readb( addr );
#elif GPIO_MAXBIT_WIDTH <= 16
	return readw( addr );
#else
	return readl( addr );
#endif
}

static inline void gpio_write( gpio_bitmap value, 
					const void __iomem *addr ){
#if GPIO_MAXBIT_WIDTH <= 8
	writeb( value, addr );
#elif GPIO_MAXBIT_WIDTH <= 16
	writew( value, addr );
#else
	writel( value, addr );
#endif
}

/*--------------------------------------------------------------------------*/
// get/set/clr register inline functions
/*--------------------------------------------------------------------------*/
static inline gpio_bitmap gpio_get_bits( gpio_bitmask bitmask,
					const void __iomem* addr ){

	return ( gpio_read( addr ) & bitmask );
}

static inline void gpio_set_bits( gpio_portid port, gpio_bitmask bitmask,
					const void __iomem* addr ){
	unsigned long	flags;
	//lock 
	spin_lock_irqsave( &gpio_lock[port], flags );
	// write register
	gpio_write( ( gpio_read( addr ) | bitmask ), addr );	
	//unlock 
	spin_unlock_irqrestore( &gpio_lock[port], flags );	
}

static void gpio_clr_bits( gpio_portid port, gpio_bitmask bitmask,
					const void __iomem* addr ){
	unsigned long	flags;
	//lock 
	spin_lock_irqsave( &gpio_lock[port], flags );
	// write register
	gpio_write( ( gpio_read( addr ) & ~bitmask ), addr );	
	//unlock 
	spin_unlock_irqrestore( &gpio_lock[port], flags );	
}

/*--------------------------------------------------------------------------*/
// port/ bit check
/*--------------------------------------------------------------------------*/
static inline int check_valid_port( gpio_portid port )
{
	return ((port>=GPIO_PORT_0)&&(port<GPIO_PORT_MAX)) ? 0 : -EINVAL;
}

static inline int check_valid_bits( gpio_portid port, gpio_bitmask bitmask )
{
	return ((check_valid_port(port)==0) && ( bitmask != 0 ) &&
		((bitmask & port_infos[port].valid) == bitmask)) ? 0 : -EINVAL;
}

// return 0 when all bits are not registerd
// return -EBUSY when all bits are registerd
// return -EINVAL when invalid arguments
static int check_alloc_bits( gpio_portid port, gpio_bitmask bitmask )
{
#ifdef DEBUG_GPIO
	if( check_valid_bits( port, bitmask ) != 0 ){
		return -EINVAL;
	}
#endif
	return (( bitmask & port_infos[port].regist)==bitmask) ? -EBUSY : 0;
}

/*--------------------------------------------------------------------------*/
// for setting mode
/*--------------------------------------------------------------------------*/ 
// return the number of high bits.
#ifdef DEBUG_GPIO
static int count_bits( gpio_mode mode, gpio_mode check )
{
	unsigned int	i, bitnum, cnt = 0;

	bitnum = sizeof( gpio_mode )*8;

	mode &= check;

	for( i = 0; i < bitnum; i++ ){
		if( mode & ( 1 << i ) ){
			cnt++;
		}
	}
	return cnt;
}
#endif

static int set_mode_bits( gpio_portid port, 
				gpio_bitmask bitmask, gpio_mode mode )
{
	unsigned int 	i;

#ifdef DEBUG_GPIO
	// GPIOSYS has no port select and bus hold functions.
	if( ( check_gpio_sys( port ) != 0 ) &&
	    ( mode & (GPIOMODE_GPIO|GPIOMODE_PERIPHERAL|GPIOMODE_BUSHOLD) ) ){
		return -EINVAL;
	}

	if( (count_bits( mode, GPIOMODE_FUNC ) > 1) ||
	    (count_bits( mode, GPIOMODE_DIR ) > 1) ||
	    (count_bits( mode, GPIOMODE_BUSCTRL ) > 1) ){
		return -EINVAL;
	}
#endif
	// function
	switch( mode & GPIOMODE_FUNC ){
	case GPIOMODE_GPIO:
		for( i = 0; i < GPIO_MAXBIT_WIDTH; i++ ){
			if( bitmask & (1 << i) ){
				gpio_clr_bits( port, 1, 
					GPIO_EASY_PORTSEL( port, i ) );
			}
		}
		break;
	case GPIOMODE_PERIPHERAL:
		for( i = 0; i < GPIO_MAXBIT_WIDTH; i++ ){
			if( bitmask & (1 << i) ){
				gpio_set_bits( port, 1, 
					GPIO_EASY_PORTSEL( port, i ) );
			}
		}
		break;
	default:
		break;
	}

	// Pullup/down, Budhold
	switch( mode & GPIOMODE_BUSCTRL ){
	case GPIOMODE_PULLUP:
		if( check_gpio_sys( port ) == 0 ){
			// bus hold disable
			gpio_clr_bits( port, bitmask, 
					GPIO_EASY_HOLDEN( port ) );
		}
		gpio_set_bits( port, bitmask, GPIO_PUPDREG( port ) );
		gpio_set_bits( port, bitmask, GPIO_RESENREG( port ) );
		break;
	case GPIOMODE_PULLDOWN:
		if( check_gpio_sys( port ) == 0 ){
			// bus hold disable
			gpio_clr_bits( port, bitmask, 
					GPIO_EASY_HOLDEN( port ) );
		}
		gpio_clr_bits( port, bitmask, GPIO_PUPDREG( port ) );
		gpio_set_bits( port, bitmask, GPIO_RESENREG( port ) );
		break;
	case GPIOMODE_BUSHOLD:
		// bus hold enable
		gpio_set_bits( port, bitmask, GPIO_EASY_HOLDEN( port ) );
		break;
	case GPIOMODE_PUPDOFF:
		if( check_gpio_sys( port ) == 0 ){
			// bus hold disable
			gpio_clr_bits( port, bitmask, 
					GPIO_EASY_HOLDEN( port ) );
		}
		// clear Pullup/down enable register
		gpio_clr_bits( port, bitmask, GPIO_RESENREG( port ) );
		break;
	default:
		break;
	}

	// Direction
	switch( mode & GPIOMODE_DIR ){
	case GPIOMODE_INPUT:
		gpio_set_bits( port, bitmask, GPIO_INEN( port ) );
		gpio_clr_bits( port, bitmask, GPIO_DIRREG( port ) );
		break;
	case GPIOMODE_OUTPUT_H:
		gpio_clr_bits( port, bitmask, GPIO_INEN( port ) );
		gpio_set_bits( port, bitmask, GPIO_DATASET( port ) );
		gpio_set_bits( port, bitmask, GPIO_DIRREG( port ) );
		break;
	case GPIOMODE_OUTPUT_L:
		gpio_clr_bits( port, bitmask, GPIO_INEN( port ) );
		gpio_set_bits( port, bitmask, GPIO_DATACLR( port ) );
		gpio_set_bits( port, bitmask, GPIO_DIRREG( port ) );
		break;
	default:
		break;
	}

	return 0;
}


/*--------------------------------------------------------------------------*/
//
//	Kernel API
//
/*--------------------------------------------------------------------------*/
// register/unregister gpio bits
int gpio_register_bit( gpio_portid port, gpio_bitnum bit )
{
	return gpio_register_port( port, mask_val(bit) );
}

int gpio_unregister_bit( gpio_portid port, gpio_bitnum bit )          
{
	return gpio_unregister_port( port, mask_val(bit) );
}

int gpio_register_port( gpio_portid port, gpio_bitmask bitmask ) 
{
	unsigned long	flags;
	int	status = 0;
	// check input arguments
	if( check_valid_bits( port, bitmask ) != 0 ){
		return -EINVAL;
	}

	//lock 
	spin_lock_irqsave( &gpio_lock[port], flags );

	if( ( bitmask & port_infos[port].regist ) == 0 ){
		// register gpio bits
		port_infos[port].regist |= bitmask;
	}else{
		status = -EBUSY;
	}

	//unlock 
	spin_unlock_irqrestore( &gpio_lock[port], flags );

	return status;
}

int gpio_unregister_port( gpio_portid port, gpio_bitmask bitmask ) 
{
	gpio_bitnum bit;
	unsigned long	flags;
#ifdef DEBUG_GPIO
	// check input arguments
	if( check_valid_bits( port, bitmask ) != 0 ){
		return -EINVAL;
	}
#endif
	if(  check_gpio_sys( port ) != 0 ){
		// set interrupt disable when gpio is unregisterd.
		for( bit = 0; bit < GPIO_MAXBIT_WIDTH; bit++ ){
			if( bitmask & ( 1 << bit ) ){
				gpio_ctrl_intr_en_bit( port, bit, 0 );
			}
		}
	}

	//lock 
	spin_lock_irqsave( &gpio_lock[port], flags );

	// unregister
	port_infos[port].regist &= ~bitmask;

	//unlock 
	spin_unlock_irqrestore( &gpio_lock[port], flags );

	return 0;
}

// set mode
int gpio_set_mode_bit( gpio_portid port, 
				gpio_bitnum bit, gpio_mode mode ) 
{
	return gpio_set_mode_port( port, mask_val(bit), mode );
}

int gpio_set_mode_port( gpio_portid port, 
                              gpio_bitmask bitmask, gpio_mode mode ) 
{
	// check input arguments
	if( check_alloc_bits( port, bitmask ) != -EBUSY ){
		return -EINVAL;
	}

	// return -EBUSY when interrupt	is enabled
	if(  check_gpio_sys( port ) != 0 ){
		if( gpio_get_bits( bitmask, GPIO_SYS_INTMASK ) != 0 ){
			return -EBUSY;
		}
	}

	return set_mode_bits( port, bitmask, mode );
}

// get mode
int gpio_get_mode_bit( gpio_portid port, 
				gpio_bitnum bit, gpio_mode* mode ) 
{
	gpio_bitmask mask = mask_val( bit );

#ifdef DEBUG_GPIO
	if( mode == NULL ){
		return -EINVAL;
	} 
	if( check_valid_bits( port, mask ) != 0 ){
		return -EINVAL;
	}
#endif
	*mode = 0;

	// GPIOSYS has no port select and bus hold functions.
	if( check_gpio_sys( port ) == 0 ){
		// Peripehal mode if Port Select Register is set.
		if( gpio_read( GPIO_EASY_PORTSEL( port, bit ) ) != 0 ){
			*mode |= GPIOMODE_PERIPHERAL;
		}else{
			*mode |= GPIOMODE_GPIO;
		}
		// bus hold enable
		if( gpio_get_bits( mask, GPIO_EASY_HOLDEN( port ) ) != 0 ){
			*mode |= GPIOMODE_BUSHOLD;
		}
	}

	// bus hold disable
	if( !(*mode & GPIOMODE_BUSHOLD) ){

		if( gpio_get_bits( mask, GPIO_RESENREG( port ) ) != 0 ){
			// Pullup mode if PullUp/Down Register is set.
			if( gpio_get_bits( mask, GPIO_PUPDREG( port ) ) != 0 ){
				*mode |= GPIOMODE_PULLUP;
			}else{
				*mode |= GPIOMODE_PULLDOWN;
			}
		}else{
			//Pullup/down is disable
			*mode |= GPIOMODE_PUPDOFF;
		}
	}

	// Direction
	if( gpio_get_bits( mask, GPIO_DIRREG( port ) ) != 0 ){

		if( gpio_get_bits( mask, GPIO_RDATA( port ) ) != 0 ){
			// output(high)
			*mode |= GPIOMODE_OUTPUT_H;
		}else{
			// output(low)
			*mode |= GPIOMODE_OUTPUT_L;
		}
	}else{
		// input
		*mode |= GPIOMODE_INPUT;
	}

	return 0;
}

// get/set gpio
int gpio_get_data_bit( gpio_portid port, 
				gpio_bitnum bit, gpio_bit* bit_value )
{
	gpio_bitmask bitmask = mask_val(bit);

#ifdef DEBUG_GPIO
	if( bit_value == NULL ){
		return -EINVAL;
	} 
#endif
	*bit_value = (gpio_get_bits(bitmask, GPIO_RDATA(port)) != 0) ? 1 : 0;

	return 0;
}

int gpio_get_data_port( gpio_portid port, 
				gpio_bitmask bitmask, 
				gpio_bitmap* bitmap_value )
{
	// check input arguments
#ifdef DEBUG_GPIO
	if( bitmap_value == NULL ){
		return -EINVAL;
	} 
#endif

	*bitmap_value = gpio_get_bits( bitmask, GPIO_RDATA( port ) );

	return 0;
}

int gpio_set_data_bit( gpio_portid port, 
				gpio_bitnum bit, gpio_bit bit_value ) 
{
	gpio_bitmask bitmask = mask_val(bit);

	// check input arguments
	if( check_alloc_bits( port, bitmask ) != -EBUSY ){
		return -EINVAL;
	}
	
	if( bit_value > 0 ){
		gpio_write( bitmask, GPIO_DATASET(port) );
	}else{
		gpio_write( bitmask, GPIO_DATACLR(port) );
	}

	return 0;
}

int gpio_set_data_port( gpio_portid port, 
				gpio_bitmask bitmask, 
				gpio_bitmap bitmap_value ) 
{
	// check input arguments
	if( check_alloc_bits( port, bitmask ) != -EBUSY ){
		return -EINVAL;
	}

	gpio_write( bitmask & bitmap_value, GPIO_DATASET(port) );
	gpio_write( bitmask & ~bitmap_value, GPIO_DATACLR(port) );

	return 0;
}


// set interrupt enable/disable
int gpio_ctrl_intr_en_bit( gpio_portid port, 
				gpio_bitnum bit, gpio_bit intr_bit )
{
	gpio_bitmap  intr_bitmap = 0;

	if( intr_bit > 0 ){
		intr_bitmap = mask_val( bit );
	}

	return gpio_ctrl_intr_en_port( port, mask_val(bit), 
							intr_bitmap );
}

int gpio_ctrl_intr_en_port( gpio_portid port, 
				gpio_bitmask bitmask, 
				gpio_bitmap intr_bitmap )
{
#ifdef DEBUG_GPIO
	if( check_gpio_sys( port ) == 0 ){
		return -EINVAL;
	}
	// return -EINVAL if gpio is set to output direction.
	if( gpio_get_bits( bitmask, GPIO_DIRREG( port ) ) ){
		return -EINVAL;		
	}
#endif
	if( check_alloc_bits( port, bitmask ) != -EBUSY ){
		return -EINVAL;
	}

	// clear int status
	gpio_set_bits( port, bitmask, GPIO_SYS_INTSTCLR );

	gpio_set_bits( port, bitmask & intr_bitmap, GPIO_SYS_INTMASK );
	gpio_clr_bits( port, bitmask & ~intr_bitmap, GPIO_SYS_INTMASK );

	return 0;
}

// get interrupt enable/disable status. 
int gpio_get_intr_en_bit( gpio_portid port, 
				gpio_bitnum bit, gpio_bit* stat_bit )
{
	int status;
	gpio_bitmap  stat_bitmap;

	status = gpio_get_intr_en_port( port, mask_val(bit), 
							&stat_bitmap );

	if( status == 0 ){
		*stat_bit = ( mask_val(bit) & stat_bitmap ) ? 1 : 0;
	}

	return status;
}

int gpio_get_intr_en_port( gpio_portid port, 
				gpio_bitmask bitmask, 
				gpio_bitmap* stat_bitmap ) 
{
	// check input arguments
#ifdef DEBUG_GPIO
	if( stat_bitmap == NULL ){
		return -EINVAL;
	}

	if( check_gpio_sys( port ) == 0 ){
		return -EINVAL;
	}
	// return -EINVAL if gpio is set to output direction.
	if( gpio_get_bits( bitmask, GPIO_DIRREG( port ) ) ){
		return -EINVAL;		
	}
#endif
	if( check_alloc_bits( port, bitmask ) != -EBUSY ){
		return -EINVAL;
	}

	*stat_bitmap = gpio_get_bits( bitmask, GPIO_SYS_INTMASK );

	return 0;
}

// set interrupt trigger
int gpio_set_intr_trig_bit( gpio_portid port, 
				gpio_bitnum bit, 
				gpio_trg_status trg_status ) 
{
	return gpio_set_intr_trig_port( port, mask_val(bit), 
							trg_status );

}

int gpio_set_intr_trig_port( gpio_portid port, 
				gpio_bitmask bitmask, 
				gpio_trg_status trg_status ) 
{
	int	    status      = 0;
	gpio_bitmap stat_bitmap = 0;

#ifdef DEBUG_GPIO
	if( ( trg_status < GPIO_TRG_HIGH_EDGE ) ||
	    ( trg_status > GPIO_TRG_ACTIVE_LOW ) ){ 
		return -EINVAL;
	}
#endif
	status = gpio_get_intr_en_port( port, bitmask, &stat_bitmap );

	if( status != 0 ){
		return status;
	}

	// return -EINVAL if gpio is set to enable interrupt
	if( stat_bitmap != 0 ){
		return -EINVAL;
	}

	switch( trg_status ){
	case GPIO_TRG_HIGH_EDGE:
		gpio_clr_bits( port, bitmask, GPIO_SYS_INTS );  // edge
		gpio_set_bits( port, bitmask, GPIO_SYS_INTHE ); // high = 1
		gpio_clr_bits( port, bitmask, GPIO_SYS_INTLE ); // low = 0
		break;
	case GPIO_TRG_LOW_EDGE:
		gpio_clr_bits( port, bitmask, GPIO_SYS_INTS );  // edge 
		gpio_clr_bits( port, bitmask, GPIO_SYS_INTHE ); // high = 0
		gpio_set_bits( port, bitmask, GPIO_SYS_INTLE ); // low = 1
		break;
	case GPIO_TRG_EDGE:
		gpio_clr_bits( port, bitmask, GPIO_SYS_INTS );  // edge 
		gpio_set_bits( port, bitmask, GPIO_SYS_INTHE ); // high = 1
		gpio_set_bits( port, bitmask, GPIO_SYS_INTLE ); // low = 1
		break;
	case GPIO_TRG_ACTIVE_HIGH:
		gpio_set_bits( port, bitmask, GPIO_SYS_INTS );  // level 
		gpio_set_bits( port, bitmask, GPIO_SYS_INTHE ); // high = 1
		gpio_clr_bits( port, bitmask, GPIO_SYS_INTLE ); // low = 0
		break;
	case GPIO_TRG_ACTIVE_LOW:
		gpio_set_bits( port, bitmask, GPIO_SYS_INTS );  // level 
		gpio_clr_bits( port, bitmask, GPIO_SYS_INTHE ); // high = 0
		gpio_set_bits( port, bitmask, GPIO_SYS_INTLE ); // low = 1
		break;
	default:
		break;
	}

	return 0;
}

// clear interrupt status.
int gpio_clr_intr_st_bit( gpio_portid port, gpio_bitnum bit )
{

	gpio_bitmask bitmask = mask_val(bit);

	// check input arguments
#ifdef DEBUG_GPIO
	if( check_gpio_sys( port ) == 0 ){
		return -EINVAL;
	}
	// return -EINVAL if gpio is set to output direction.
	if( gpio_get_bits( bitmask, GPIO_DIRREG( port ) ) ){
		return -EINVAL;		
	}
#endif
	if( check_alloc_bits( port, bitmask ) != -EBUSY ){
		return -EINVAL;
	}

	// clear int status
	gpio_set_bits( port, bitmask, GPIO_SYS_INTSTCLR );

	return 0;
}


/*--------------------------------------------------------------------------*/
//
// for system call
//
/*--------------------------------------------------------------------------*/
// open()
static int gpio_open(struct inode *inode, struct file *file)
{
	return 0;
}

// close()
static int gpio_release(struct inode *inode, struct file *file)
{
	return 0;
}

// read_proc
static int gpio_read_proc(char *buffer, char **start, off_t offset, 
					int length, int *eof, void *data)
{
	unsigned int len = 0;

	if( strcmp( CMD_GPIOSYS, (const char *)data ) == 0 ){

		len += sprintf(buffer + len, "GPIOSYS\n");
		len += sprintf(buffer + len, "DIR    :0x%04x\n", 
						gpio_read(GPIO_SYS_DIR) );
		len += sprintf(buffer + len, "RDATA  :0x%04x\n", 
						gpio_read(GPIO_SYS_RDATA) );
		len += sprintf(buffer + len, "INTS   :0x%04x\n", 
						gpio_read(GPIO_SYS_INTS) );
		len += sprintf(buffer + len, "INTHE  :0x%04x\n", 
						gpio_read(GPIO_SYS_INTHE) );
		len += sprintf(buffer + len, "INTLE  :0x%04x\n", 
						gpio_read(GPIO_SYS_INTLE) );
		len += sprintf(buffer + len, "INTMASK:0x%04x\n", 
						gpio_read(GPIO_SYS_INTMASK) );
		len += sprintf(buffer + len, "INTST  :0x%04x\n", 
						gpio_read(GPIO_SYS_INTST) );
		len += sprintf(buffer + len, "INEN   :0x%04x\n", 
						gpio_read(GPIO_SYS_INEN) );
		len += sprintf(buffer + len, "RESEN  :0x%04x\n", 
						gpio_read(GPIO_SYS_RESEN) );
		len += sprintf(buffer + len, "PUPD   :0x%04x\n", 
						gpio_read(GPIO_SYS_PUPD) );
		len -= offset;
	}else{	
		char		cmd[GPIO_CMD_PROC_STR];
		unsigned int	j, num = 0;
		gpio_bitmap 	portsel = 0;

		for( num = 0; num < GPIO_EASY_NUM; num++ ){
			sprintf( cmd, "%s%d", CMD_GPIOEASY, num );
			if( strcmp( cmd, (const char *)data ) == 0 ){
				break;
			}
		}

		if( num == GPIO_EASY_NUM ){
			return 0;
		}

		len += sprintf(buffer + len, "GPIOEASY%d\n",  num);
		len += sprintf(buffer + len, "DIR    :0x%04x\n", 
				        gpio_read( GPIO_EASY_DIR(num) ) );
		len += sprintf(buffer + len, "RDATA  :0x%04x\n", 
	        			gpio_read( GPIO_EASY_RDATA(num) ) );
	        len += sprintf(buffer + len, "INEN   :0x%04x\n", 
					gpio_read( GPIO_EASY_INEN(num) ) );
		for( j = 0; j < GPIO_MAXBIT_WIDTH; j++ ){
			portsel |= 
				(gpio_read(GPIO_EASY_PORTSEL(num,j)) & 0x1)<<j;
		}
		len += sprintf(buffer + len, "PORTSEL:0x%04x\n", portsel);
		len += sprintf(buffer + len, "HOLDEN :0x%04x\n", 
					gpio_read( GPIO_EASY_HOLDEN(num) ) );
		len += sprintf(buffer + len, "RESEN  :0x%04x\n", 
					gpio_read( GPIO_EASY_RESEN(num) ) );
		len += sprintf(buffer + len, "PUPD   :0x%04x\n", 
					gpio_read( GPIO_EASY_PUPD(num) ) );
		len -= offset;
	}

	if( len < length ){
		*eof = 1;
	}else{
        	len = length;
	}
	
	*start = buffer + offset;

	return len;
}

// write_proc
static int gpio_write_proc(struct file *file, const char __user *buffer, 
    					unsigned long count, void *data)
{
	unsigned long len;
	char*	 ptr;

	len = ( count > GPIO_CMD_PROC_STR ) ? GPIO_CMD_PROC_STR : count;

	if( copy_from_user( gpio_cmd_proc, buffer, len ) != 0){
		return 0;
	}

	gpio_cmd_proc[len] = '\0';

	if( (ptr = strchr( gpio_cmd_proc, '\n' )) != NULL ){
		*ptr = '\0';
	}

	return len;
}


// Suspend
static int gpio_suspend(struct amba_device *dev, pm_message_t state)       
{
	unsigned int	i;

	reg_bkup = kmalloc( sizeof(struct gpio_reg), GFP_KERNEL );

	if(  reg_bkup == NULL ){
		return -ENOMEM;
	}

	memset( reg_bkup, 0, sizeof(struct gpio_reg) );

	// GPIO SYS
	reg_bkup->sys.dir = gpio_read( GPIO_SYS_DIR );
	reg_bkup->sys.rdata = gpio_read( GPIO_SYS_RDATA );
	reg_bkup->sys.intmask = gpio_read( GPIO_SYS_INTMASK );
	reg_bkup->sys.intle = gpio_read( GPIO_SYS_INTLE );
	reg_bkup->sys.inthe = gpio_read( GPIO_SYS_INTHE );
	reg_bkup->sys.ints = gpio_read( GPIO_SYS_INTS );
	reg_bkup->sys.inen = gpio_read( GPIO_SYS_INEN );
	reg_bkup->sys.resen = gpio_read( GPIO_SYS_RESEN );
	reg_bkup->sys.pupd = gpio_read( GPIO_SYS_PUPD );

	// GPIO EASY
	for( i = 0; i < GPIO_EASY_NUM; i++ ){
		int	j;

		reg_bkup->easy[i].dir = gpio_read( GPIO_EASY_DIR(i) );
		reg_bkup->easy[i].rdata = gpio_read( GPIO_EASY_RDATA(i) );
		reg_bkup->easy[i].inen = gpio_read( GPIO_EASY_INEN(i) );
		for( j = GPIO_MAXBIT_WIDTH; j >= 0; j-- ){
			reg_bkup->easy[i].portsel |= 
				(gpio_read(GPIO_EASY_PORTSEL(i,j)) & 0x1)<<j;
		}
		reg_bkup->easy[i].holden = gpio_read( GPIO_EASY_HOLDEN(i) );
		reg_bkup->easy[i].resen = gpio_read( GPIO_EASY_RESEN(i) );
		reg_bkup->easy[i].pupd = gpio_read( GPIO_EASY_PUPD(i) );
	}

	return 0;
}

// Resume
static int gpio_resume(struct amba_device *dev)
{
	unsigned int	i;

	if(  reg_bkup == NULL ){
		return 0;
	}

	// GPIO SYS
	gpio_write( reg_bkup->sys.pupd, GPIO_SYS_PUPD );
	gpio_write( reg_bkup->sys.resen, GPIO_SYS_RESEN );
	gpio_write( reg_bkup->sys.inen, GPIO_SYS_INEN );
	gpio_write( reg_bkup->sys.ints, GPIO_SYS_INTS );
	gpio_write( reg_bkup->sys.inthe, GPIO_SYS_INTHE );
	gpio_write( reg_bkup->sys.intle, GPIO_SYS_INTLE );
	gpio_write( reg_bkup->sys.intmask, GPIO_SYS_INTMASK );
	gpio_write( reg_bkup->sys.rdata, GPIO_SYS_DATASET );
	gpio_write( ~reg_bkup->sys.rdata, GPIO_SYS_DATACLR );
	gpio_write( reg_bkup->sys.dir, GPIO_SYS_DIR );

	// GPIO EASY
	for( i = 0; i < GPIO_EASY_NUM; i++ ){

		unsigned int j;
		gpio_write( reg_bkup->easy[i].pupd, GPIO_EASY_PUPD(i) );
		gpio_write( reg_bkup->easy[i].resen, GPIO_EASY_RESEN(i) );
		gpio_write( reg_bkup->easy[i].holden, GPIO_EASY_HOLDEN(i) );
		for( j = 0; j < GPIO_MAXBIT_WIDTH; j++ ){
			gpio_write( (reg_bkup->easy[i].portsel >> j) & 0x1,
						GPIO_EASY_PORTSEL(i,j) );
		}
		gpio_write( reg_bkup->easy[i].inen, GPIO_EASY_INEN(i) );
		gpio_write( reg_bkup->easy[i].rdata, GPIO_EASY_DATASET(i) );
		gpio_write( ~reg_bkup->easy[i].rdata, GPIO_EASY_DATACLR(i) );
		gpio_write( reg_bkup->easy[i].dir, GPIO_EASY_DIR(i) );
	}

	kfree( reg_bkup );

	reg_bkup = NULL;

	return 0;
}

// ioctl()
static int gpio_ioctl(struct inode *inode, struct file *file, 
					unsigned int cmd, unsigned long arg )
{
	int status = 0;
	gpio_ioctl_port		port_info;
	gpio_ioctl_value	value_info;
	gpio_ioctl_mode		mode_info;

	switch( cmd ){
	case GPIO_CMD_OPEN:	
		if( copy_from_user( (void*)&port_info, 
					(const void __user *)arg, 
					sizeof(port_info) ) != 0){
			status = -EFAULT;
			break;	
		}

		if( port_info.portctl == 0 ){
			status = gpio_register_bit( 
						port_info.portid, 
						port_info.bitnum );
		}else{
			status = gpio_register_port( 
						port_info.portid, 
						port_info.bitmask );
		}
		break;
	case GPIO_CMD_CLOSE:	
		if( copy_from_user( (void*)&port_info, 
					(const void __user *)arg,
					sizeof(port_info) ) != 0){
			status = -EFAULT;
			break;	
		}

		if( port_info.portctl == 0 ){
			status = gpio_unregister_bit( 
						port_info.portid, 
						port_info.bitnum );
		}else{
			status = gpio_unregister_port( 
						port_info.portid, 
						port_info.bitmask );
		}
		break;
	case GPIO_CMD_READ:	
		if( copy_from_user( (void*)&value_info, 
					(const void __user *)arg, 
					sizeof(value_info) ) != 0){
			status = -EFAULT;
			break;	
		}

		if( value_info.port.portctl == 0 ){
			gpio_bit bit_value = 0;
			status = gpio_get_data_bit( 
						value_info.port.portid,
						value_info.port.bitnum, 
						&bit_value );
			value_info.value = bit_value;
		}else{
			status = gpio_get_data_port(  
						value_info.port.portid,
						value_info.port.bitmask, 
						&value_info.value );
		}

		if( status == 0 ){
			if( copy_to_user((void __user *)arg,
					(void*)&value_info, 
					sizeof(value_info) ) != 0){
				status = -EFAULT;
			}
		}
		break;
	case GPIO_CMD_WRITE:	
		if( copy_from_user((void*)&value_info, 
					(const void __user *)arg,
					sizeof(value_info) ) != 0){
			status = -EFAULT;
			break;	
		}
		if( value_info.port.portctl == 0 ){
			status = gpio_set_data_bit( 
						value_info.port.portid,
						value_info.port.bitnum, 
						value_info.value );
		}else{
			status = gpio_set_data_port(  
						value_info.port.portid,
						value_info.port.bitmask, 
						value_info.value );
		}
		break;
	case GPIO_CMD_SET_MODE:	
		if( copy_from_user((void*)&mode_info, 
					(const void __user *)arg, 
					sizeof(mode_info) ) != 0){
			status = -EFAULT;
			break;	
		}
		if( mode_info.port.portctl == 0 ){
			status = gpio_set_mode_bit( 
						mode_info.port.portid,
						mode_info.port.bitnum, 
						mode_info.mode );
		}else{
			status = gpio_set_mode_port( 
						mode_info.port.portid,
						mode_info.port.bitmask, 
						mode_info.mode );
		}
		break;
	case GPIO_CMD_GET_MODE:	
		if( copy_from_user((void*)&mode_info, 
					(const void __user *)arg, 
					sizeof(mode_info) ) != 0){
			status = -EFAULT;
			break;	
		}

		status = gpio_get_mode_bit( mode_info.port.portid,
					    mode_info.port.bitnum, 
					    &mode_info.mode );
		if( status == 0 ){
			if( copy_to_user((void __user *)arg,
					(void*)&mode_info, 
					sizeof(mode_info) ) != 0){
				status = -EFAULT;
			}
		}
		break;
#ifdef DEBUG_GPIO
	// test ioctl cmd for suspend/resume
	case GPIO_CMD_TEST_SUSPEND:
		{
			struct amba_device dev;
			pm_message_t state;
			gpio_suspend( &dev, state );
		}
		break;
	case GPIO_CMD_TEST_RESUME:
		{
			struct amba_device dev;
			gpio_resume( &dev );
		}
		break;
#endif
	default:
		status = -EINVAL;
		break;
	}

	return status;
}

// Device Info
static struct amba_id gpio_ids[] = {
    {
	.id = GPIO_PID,
	.mask = 0xFFFFFFFF,
    },
    { 0, 0, },
};

static struct amba_driver gpio_driver = {
	.drv = {
		.name = "cxd4105 gpio",
	},
	.id_table = gpio_ids,
	.suspend = gpio_suspend,
	.resume  = gpio_resume,
};

static struct amba_device gpio_device = {
	.dev = {
		.bus_id = "cxd4105 gpio",
	},
	.res = {
		.start	= CXD4105_GPIO_BASE,
		.end 	= CXD4105_GPIO_BASE + CXD4105_GPIO_MEMSIZE - 1,
		.flags	= IORESOURCE_MEM,
	},
	.periphid = GPIO_PID,
};

static struct file_operations gpio_dev_fops = {
	.owner 		= THIS_MODULE,
	.open 		= gpio_open,
	.release 	= gpio_release,
	.ioctl 		= gpio_ioctl,
};

static struct sonymisc_device gpio_dev = {
	.class	= DEV_CLASS_CHR,
	.minor	= MINOR_NO_GPIO,
	.name	= GPIO_NAME,
	.fops	= &gpio_dev_fops,
	.gd	= NULL,
};

/*--------------------------------------------------------------------------*/
//
// init/exit_module
//
/*--------------------------------------------------------------------------*/

static void gpio_release_amba( void )
{
	amba_driver_unregister( &gpio_driver );
	amba_device_unregister( &gpio_device );
}

static void gpio_release_dev_mem( void )
{
	release_mem_region( gpio_device.res.start, 
			gpio_device.res.end - gpio_device.res.start + 1 );
}

static int gpio_request_dev_mem( void )
{
	struct amba_device* p_dev;

	p_dev = &gpio_device;

	if( request_mem_region( p_dev->res.start, 
				( p_dev->res.end - p_dev->res.start + 1),
				p_dev->dev.bus_id ) == NULL ){
		DBG_MSG( KERN_INFO "err request mem region\n" ); 
		return -ENOMEM;
	}

	return 0;
}

int __init gpio_init(void)
{
	int i, st = 0;
	struct proc_dir_entry *ent;

	// init global variables
	for( i = 0; i < GPIO_PORT_MAX; i++ ){
		spin_lock_init( &gpio_lock[i] );
	}

	// mem_region
	st = gpio_request_dev_mem();
	if( st < 0 ){
		return st;
	}

	st = amba_device_register( &gpio_device, &gpio_device.res );
	if( st < 0 ){
		DBG_MSG(KERN_INFO "amba_device_register error\n");
		gpio_release_dev_mem();
		return st;
	}

	st = amba_driver_register( &gpio_driver );
	if( st < 0 ){
		DBG_MSG(KERN_INFO "amba_driver_register error\n");
		amba_device_unregister( &gpio_device );
		gpio_release_dev_mem();
		return st;
	}

	if( !(ent = create_proc_entry( "gpio", 
					S_IFREG | S_IRUGO | S_IWUSR, 
			        	proc_root_driver )) ){
		DBG_MSG(KERN_INFO "create_proc_entry error\n");
		gpio_release_amba();
		gpio_release_dev_mem();
		return -EACCES;
	}

	ent->read_proc = gpio_read_proc;
	ent->write_proc = gpio_write_proc;
	ent->data = (void *)&gpio_cmd_proc;
	strcpy( gpio_cmd_proc, CMD_GPIOSYS );	//default

	st = sonymisc_register( &gpio_dev );
	if( st < 0 ){
		DBG_MSG(KERN_INFO "sonymisc_register error %d\n", st );
		remove_proc_entry( "gpio", proc_root_driver );
		gpio_release_amba();
		gpio_release_dev_mem();
		return st;
	}


	return 0;
}

/*------------------------------------------------------------------------
 * exit_module
 *----------------------------------------------------------------------*/
static void __exit gpio_exit(void)
{

	sonymisc_deregister(&gpio_dev);
	remove_proc_entry( "gpio", proc_root_driver );
	gpio_release_amba();
	gpio_release_dev_mem();

	return;
}


EXPORT_SYMBOL( gpio_register_bit );
EXPORT_SYMBOL( gpio_unregister_bit );        
EXPORT_SYMBOL( gpio_register_port ); 
EXPORT_SYMBOL( gpio_unregister_port );
EXPORT_SYMBOL( gpio_set_mode_bit );
EXPORT_SYMBOL( gpio_set_mode_port );
EXPORT_SYMBOL( gpio_get_mode_bit );
EXPORT_SYMBOL( gpio_get_data_bit );
EXPORT_SYMBOL( gpio_set_data_bit );
EXPORT_SYMBOL( gpio_get_data_port );
EXPORT_SYMBOL( gpio_set_data_port );
EXPORT_SYMBOL( gpio_ctrl_intr_en_bit );
EXPORT_SYMBOL( gpio_ctrl_intr_en_port );
EXPORT_SYMBOL( gpio_get_intr_en_bit );
EXPORT_SYMBOL( gpio_get_intr_en_port );
EXPORT_SYMBOL( gpio_set_intr_trig_bit );
EXPORT_SYMBOL( gpio_set_intr_trig_port );
EXPORT_SYMBOL( gpio_clr_intr_st_bit );

#if 0 /* call by target_init */
module_init( gpio_init );
#endif
module_exit( gpio_exit );

MODULE_VERSION( GPIO_VERSION );
MODULE_LICENSE( "GPL" );
MODULE_AUTHOR( "Sony Corporation" );
MODULE_DESCRIPTION( DEV_NAME );
