/*-----------------------------------------------------------------------------
 * cxd4115_gpio.c
 *
 * a device driver file for gpio
 *
 * Copyright 2008,2009 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/cxd4115_gpio.h>
#include <asm/arch/regs-gpio.h>

#define MINOR_NO_GPIO	1
#define GPIO_BASE	CXD4115_GPIO(0)
#define GPIO_MEMSIZE	(0x100*GPIO_PORT_MAX)
#define GPIO_NAME	"cxd4115-gpio"
#define GPIO_VERSION	"00.01.005"
#define	DEV_NAME	"CXD4115 GPIO driver"

#define GPIO_PID	GPIO_BASE	

/*--------------------------------------------------------------------------*/
// Register base
/*--------------------------------------------------------------------------*/
#define	GPIO_BASE_(x)		VA_GPIO(x)

/*--------------------------------------------------------------------------*/
// Register assign
/*--------------------------------------------------------------------------*/
// GPIO REGISTER
#define GPIO_DIRECTION(x)	( GPIO_BASE_(x) + GPIO_DIR )
#define GPIO_RDATA(x)		( GPIO_BASE_(x) + GPIO_DATA_READ )
#define GPIO_DATASET(x)		( GPIO_BASE_(x) + GPIO_DATA_SET )
#define GPIO_DATACLR(x)		( GPIO_BASE_(x) + GPIO_DATA_CLEAR )
#define GPIO_INPUTEN(x)		( GPIO_BASE_(x) + GPIO_INEN )
#define GPIO_PORTSELDAT(x)	( GPIO_BASE_(x) + GPIO_PORTSEL )
#define GPIO_PORTSELSET(x)	( GPIO_BASE_(x) + GPIO_PORTSEL_SET )
#define GPIO_PORTSELCLR(x)	( GPIO_BASE_(x) + GPIO_PORTSEL_CLR )

/*--------------------------------------------------------------------------*/
// Global variable
/*--------------------------------------------------------------------------*/
struct port_status{
	const gpio_bitmap valid;	// valid bits
	gpio_bitmap 	  regist;	// registered bits
};

struct gpio_reg{
    gpio_bitmap dir[GPIO_PORT_MAX];
    gpio_bitmap rdata[GPIO_PORT_MAX];
    gpio_bitmap inen[GPIO_PORT_MAX];
    gpio_bitmap portsel[GPIO_PORT_MAX];
};

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

static struct port_status 	port_infos[GPIO_PORT_MAX] = {
#ifdef CONFIG_ARCH_CXD4115
	{ .valid = 0x0007FFFF, .regist = 0x00000000},	// GPIO_PORT_0
	{ .valid = 0xFFFFFFFF, .regist = 0x00000000},	// GPIO_PORT_1
	{ .valid = 0xFFFFFFFF, .regist = 0x00000000},	// GPIO_PORT_2
	{ .valid = 0x7FFFFFFF, .regist = 0x00000000},	// GPIO_PORT_3
	{ .valid = 0x01FFFFFF, .regist = 0x00000000},	// GPIO_PORT_4
	{ .valid = 0xFFFFFFFF, .regist = 0x00000000},	// GPIO_PORT_5
	{ .valid = 0x0001FFFF, .regist = 0x00000000},	// GPIO_PORT_6
	{ .valid = 0x00000FFF, .regist = 0x00000000},	// GPIO_PORT_7
#endif
#ifdef CONFIG_ARCH_CXD4120
	{ .valid = 0x00FFFFFF, .regist = 0x00000000},	// GPIO_PORT_0
	{ .valid = 0x00000360, .regist = 0x00000000},	// GPIO_PORT_1
	{ .valid = 0x0000003F, .regist = 0x00000000},	// GPIO_PORT_2
	{ .valid = 0x000003FF, .regist = 0x00000000},	// GPIO_PORT_3
	{ .valid = 0x04FFFFFF, .regist = 0x00000000},	// GPIO_PORT_4
	{ .valid = 0x0000FFFF, .regist = 0x00000000},	// GPIO_PORT_5
	{ .valid = 0x00000000, .regist = 0x00000000},	// GPIO_PORT_6
	{ .valid = 0x00000000, .regist = 0x00000000},	// GPIO_PORT_7
	{ .valid = 0x00040000, .regist = 0x00000000},	// GPIO_PORT_8
	{ .valid = 0x00000000, .regist = 0x00000000},	// GPIO_PORT_9
	{ .valid = 0x00000000, .regist = 0x00000000},	// GPIO_PORT_10
	{ .valid = 0x00000000, .regist = 0x00000000},	// GPIO_PORT_11
	{ .valid = 0x00000000, .regist = 0x00000000},	// GPIO_PORT_12
	{ .valid = 0x00000000, .regist = 0x00000000},	// GPIO_PORT_13
	{ .valid = 0x00000000, .regist = 0x00000000},	// GPIO_PORT_14
	{ .valid = 0x01FFFFFF, .regist = 0x00000000},	// GPIO_PORT_15
	{ .valid = 0x07FFFFFF, .regist = 0x00000000},	// GPIO_PORT_16
	{ .valid = 0x00000000, .regist = 0x00000000},	// GPIO_PORT_17
	{ .valid = 0x00000000, .regist = 0x00000000},	// GPIO_PORT_18
	{ .valid = 0x00000000, .regist = 0x00000000},	// GPIO_PORT_19
	{ .valid = 0x0FFFFFFF, .regist = 0x00000000},	// GPIO_PORT_20
	{ .valid = 0x0000000F, .regist = 0x00000000},	// GPIO_PORT_21
	{ .valid = 0x006FFFF0, .regist = 0x00000000},	// GPIO_PORT_22
	{ .valid = 0x3FFFF800, .regist = 0x00000000},	// GPIO_PORT_23
	{ .valid = 0x01FFFFE0, .regist = 0x00000000},	// GPIO_PORT_24
#endif
};


/*--------------------------------------------------------------------------*/
// 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 )

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 )
{
#ifdef DEBUG_GPIO
	if( (count_bits( mode, GPIOMODE_FUNC ) > 1) ||
	    (count_bits( mode, GPIOMODE_DIR ) > 1)  ){
		return -EINVAL;
	}
#endif
	// function
	switch( mode & GPIOMODE_FUNC ){
	case GPIOMODE_GPIO:
		gpio_write( bitmask, GPIO_PORTSELSET( port ) );
		break;
	case GPIOMODE_PERIPHERAL:
		gpio_write( bitmask, GPIO_PORTSELCLR( port ) );
		break;
	default:
		break;
	}

	// Direction
	switch( mode & GPIOMODE_DIR ){
	case GPIOMODE_INPUT:
		gpio_set_bits( port, bitmask, GPIO_INPUTEN( port ) );
		gpio_clr_bits( port, bitmask, GPIO_DIRECTION( port ) );
		break;
	case GPIOMODE_OUTPUT_H:
		gpio_clr_bits( port, bitmask, GPIO_INPUTEN( port ) );
		gpio_write( bitmask, GPIO_DATASET( port ) );
		gpio_set_bits( port, bitmask, GPIO_DIRECTION( port ) );
		break;
	case GPIOMODE_OUTPUT_L:
		gpio_clr_bits( port, bitmask, GPIO_INPUTEN( port ) );
		gpio_write( bitmask, GPIO_DATACLR( port ) );
		gpio_set_bits( port, bitmask, GPIO_DIRECTION( 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 ) 
{
	unsigned long	flags;
#ifdef DEBUG_GPIO
	// check input arguments
	if( check_valid_bits( port, bitmask ) != 0 ){
		return -EINVAL;
	}
#endif
	//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 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;

	// no port select function.
#ifdef CONFIG_ARCH_CXD4115
	if( port != GPIO_PORT_0 ){
#endif
#ifdef CONFIG_ARCH_CXD4120
	if( (port != GPIO_PORT_0) && (port != GPIO_PORT_8) ){
#endif
		// GPIO mode if PORTSEL register is set.
		if( gpio_get_bits( mask, GPIO_PORTSELDAT( port ) ) != 0 ){
			*mode |= GPIOMODE_GPIO;
		}else{
			*mode |= GPIOMODE_PERIPHERAL;
		}
	}

	// Direction
	if( gpio_get_bits( mask, GPIO_DIRECTION( 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;
}

/*--------------------------------------------------------------------------*/
//
// 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;
}

#ifdef DEBUG_GPIO
// read_proc
static int gpio_read_proc(char *buffer, char **start, off_t offset, 
					int length, int *eof, void *data)
{
	return 0;
}

// write_proc
static int gpio_write_proc(struct file *file, const char *buffer, 
    					unsigned long count, void *data)
{
	return 0;
}
#endif

// 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) );

	//save GPIO register values to mem.
	for( i = 0; i < GPIO_PORT_MAX; i++ ){
		reg_bkup->dir[i] = gpio_read( GPIO_DIRECTION(i) );
		reg_bkup->rdata[i] = gpio_read( GPIO_RDATA(i) );
		reg_bkup->inen[i] = gpio_read( GPIO_INPUTEN(i) );
#ifdef CONFIG_ARCH_CXD4115
		if( i != GPIO_PORT_0 ){
#endif
#ifdef CONFIG_ARCH_CXD4120
		if( (i != GPIO_PORT_0) && (i != GPIO_PORT_8) ){
#endif
			reg_bkup->portsel[i] = gpio_read( GPIO_PORTSELDAT(i) );
		}
	}

	return 0;
}

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

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

	//load GPIO register values from mem.
	for( i = 0; i < GPIO_PORT_MAX; i++ ){
#ifdef CONFIG_ARCH_CXD4115
		if( i != GPIO_PORT_0 ){
#endif
#ifdef CONFIG_ARCH_CXD4120
		if( (i != GPIO_PORT_0) && (i != GPIO_PORT_8) ){
#endif
			gpio_write(reg_bkup->portsel[i], GPIO_PORTSELSET(i));
			gpio_write(~reg_bkup->portsel[i], GPIO_PORTSELCLR(i));
		}
		gpio_write( reg_bkup->inen[i], GPIO_INPUTEN(i) );
		gpio_write( reg_bkup->rdata[i], GPIO_DATASET(i) );
		gpio_write( ~reg_bkup->rdata[i], GPIO_DATACLR(i) );
		gpio_write( reg_bkup->dir[i], GPIO_DIRECTION(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 = "cxd4115 gpio",
	},
	.id_table = gpio_ids,
	.suspend = gpio_suspend,
	.resume  = gpio_resume,
};

static struct amba_device gpio_device = {
	.dev = {
		.bus_id = "cxd4115 gpio",
	},
	.res = {
		.start	= GPIO_BASE,
		.end 	= GPIO_BASE + 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;
#ifdef DEBUG_GPIO
	struct proc_dir_entry *ent;
#endif
	// 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;
	}
#ifdef DEBUG_GPIO
	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;
#endif

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


	return 0;
}

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

	sonymisc_deregister(&gpio_dev);
#ifdef DEBUG_GPIO
	remove_proc_entry( "gpio", proc_root_driver );
#endif
	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 );

#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 );
