/*
* Driver interface to the ASIC Companion chip on the iPAQ H4000
* Based off of said interface for the iPAQ H5400
*
* Copyright 2003 Joshua Wise
*
* Use consistent with the GNU GPL is permitted,
* provided that this copyright notice is
* preserved in its entirety in all copies and derived works.
*
* Author:  Joshua Wise <joshua at joshuawise.com>
*          June 2003
*/

#include <linux/module.h>
#include <linux/version.h>
#include <linux/config.h>

#include <linux/init.h>
#include <linux/fs.h>
#include <linux/interrupt.h>
#include <linux/sched.h>
#include <linux/pm.h>
#include <linux/sysctl.h>
#include <linux/proc_fs.h>
#include <linux/delay.h>

#include <asm/arch/hardware.h>
//#include <asm/arch-sa1100/h3600_hal.h>
#include <linux/h3600_keyboard.h>
#include <asm/irq.h>

#include <asm/mach/irq.h>
#include <asm/arch/ipaq.h>
#include <asm/arch-pxa/h4000-gpio.h>
#include <asm/hardware/ipaq-asic3.h>

#define H3600_ASIC_PROC_DIR     "asic"
#define H3600_ASIC_PROC_STATS   "stats"
#define REG_DIRNAME "registers"

#define GPIO_NR_H4000_ASIC_IRQ_1_N      (9)  /* ASIC IRQ? */
#define GPIO_NR_H4000_ASIC_IRQ_2_N      (13) /* ASIC IRQ? */


MODULE_AUTHOR("Joshua Wise");
MODULE_DESCRIPTION("Hardware abstraction layer for the iPAQ H4000");

#ifndef MODULE /* hack so we can compile this into the kernel */
static struct module __this_module;
#undef THIS_MODULE
#define THIS_MODULE		(&__this_module)
#endif


//taken from h3600_keyboard.h from 2.4 kernel
/* keyboard interface */
#define H3600_MAKEKEY(index, down)  ((down) ? (index) : ((index) | 0x80))
enum
{
        H3600_KEYCODE_RECORD = 1,       /* 1 -> record button */
        H3600_KEYCODE_CALENDAR,         /* 2 -> calendar */
        H3600_KEYCODE_CONTACTS,         /* 3 -> contact */
        H3600_KEYCODE_Q,                /* 4 -> Q button */
        H3600_KEYCODE_START,            /* 5 -> start menu */
        H3600_KEYCODE_UP,               /* 6 -> up */
        H3600_KEYCODE_RIGHT,            /* 7 -> right */
        H3600_KEYCODE_LEFT,             /* 8 -> left */
        H3600_KEYCODE_DOWN,             /* 9 -> down */
        H3600_KEYCODE_ACTION,           /* 10 -> action button */
        H3600_KEYCODE_SUSPEND,          /* 11 -> power button */
        H3600_KEYCODE_VOLUME_UP,        /* 12 -> volume up */
        H3600_KEYCODE_VOLUME_DOWN,      /* 13 -> volume down */
};

#define IPAQ_ASIC3_VIRT              H3900_ASIC3_VIRT

/* Statistics */
//struct asic_statistics g_h3600_asic_statistics;

/***********************************************************************************/
/*      Standard entry points for HAL requests                                     */
/***********************************************************************************/

//int h4000_asic_get_version( struct h3600_ts_version *result )
//{
//	return -EINVAL;
//}

int h4000_asic_eeprom_read( unsigned short address, unsigned char *data, unsigned short len )
{
	return -EINVAL;
}

int h4000_asic_eeprom_write( unsigned short address, unsigned char *data, unsigned short len )
{
	return -EINVAL;
}

int h4000_asic_get_thermal_sensor( unsigned short *result )
{
	return -EINVAL;
}

int h4000_asic_set_notify_led( unsigned char mode, unsigned char duration, 
			       unsigned char ontime, unsigned char offtime )
{
	return -EINVAL;
}

int h4000_asic_read_light_sensor( unsigned char *result )
{
	return -EINVAL;
}

int h4000_asic_spi_read( unsigned short address, unsigned char *data, unsigned short len )
{
	return -EINVAL;
}

int h4000_asic_spi_write( unsigned short address, unsigned char *data, unsigned short len )
{
	return -EINVAL;
}


int h4000_asic_codec_control( unsigned char command, unsigned char level)
{
	return -EINVAL;
}

int h4000_asic_get_option_detect( int *result )
{
	return -EINVAL;
}

int h4000_asic_audio_clock( long samplerate )
{
	return -EINVAL;
}

int h4000_asic_audio_power( long samplerate )
{
	return -EINVAL;
}

int h4000_asic_audio_mute( int mute )
{
	return -EINVAL;
}

int h4000_asic_set_ebat( void )
{
	return -EINVAL;
}

//int h4000_asic_battery_read( struct h3600_battery *query )
//{
//	query->battery_count=0;
//	return 0;
//};

/*
static struct h3600_hal_ops h4000_asic_ops = {
	get_version         : h4000_asic_get_version,
	eeprom_read         : h4000_asic_eeprom_read,
	eeprom_write        : h4000_asic_eeprom_write,
	get_thermal_sensor  : h4000_asic_get_thermal_sensor,
	set_notify_led      : h4000_asic_set_notify_led,
	read_light_sensor   : h4000_asic_read_light_sensor,
	get_battery         : h4000_asic_battery_read,
	spi_read            : h4000_asic_spi_read,
	spi_write           : h4000_asic_spi_write,
	get_option_detect   : h4000_asic_get_option_detect,
	audio_clock         : h4000_asic_audio_clock,
	audio_power         : h4000_asic_audio_power,
	audio_mute          : h4000_asic_audio_mute,
#if 0
	backlight_control   : h4000_asic_backlight_control,
#endif
	asset_read          : NULL,
	set_ebat            : h4000_asic_set_ebat,
        owner               : THIS_MODULE,
};
*/

static struct proc_dir_entry   *asic_proc_dir;

struct simple_proc_entry {
	char *        name;
	read_proc_t * read_proc;
};

const struct simple_proc_entry sproc_list[] = {
};

static int __init h4000_asic_register_procfs( void )
{
	int i;

	asic_proc_dir = proc_mkdir(H3600_ASIC_PROC_DIR, NULL);
	if ( !asic_proc_dir ) {
		printk(KERN_ALERT  
		       "%s: unable to create proc entry %s\n", __FUNCTION__, H3600_ASIC_PROC_DIR);
		return -ENOMEM;
	}

	for ( i = 0 ; i < ARRAY_SIZE(sproc_list) ; i++ )
		create_proc_read_entry(sproc_list[i].name, 0, asic_proc_dir, 
				       sproc_list[i].read_proc, NULL);

	return 0;
}

static void  h4000_asic_unregister_procfs( void )
{
	int i;
	if ( asic_proc_dir ) {
		for (i=0 ; i<ARRAY_SIZE(sproc_list) ; i++ )
			remove_proc_entry(sproc_list[i].name, asic_proc_dir);

		remove_proc_entry(H3600_ASIC_PROC_DIR, NULL );
		asic_proc_dir = NULL;
	}
}



#define MAKEKEY(index, down)  ((down) ? (index) : ((index) | 0x80))

struct kpad {
	int keyno;
	int gpio;
	int irq;
};

#define KPTAB(sc,gp) {sc,gp,IRQ_GPIO(gp)}
static struct kpad gpiopadtab[] = {
	KPTAB(H3600_KEYCODE_UP,GPIO_NR_H4000_UP_BUTTON_N),
	KPTAB(H3600_KEYCODE_RIGHT,GPIO_NR_H4000_RIGHT_BUTTON_N),
	KPTAB(H3600_KEYCODE_LEFT,GPIO_NR_H4000_LEFT_BUTTON_N),
	KPTAB(H3600_KEYCODE_DOWN,GPIO_NR_H4000_DOWN_BUTTON_N),
	KPTAB(H3600_KEYCODE_ACTION,GPIO_NR_H4000_ACTION_BUTTON_N),
	KPTAB(H3600_KEYCODE_SUSPEND,GPIO_NR_H4000_POWER_BUTTON_N),
	{-1,-1,-1}
};

static irqreturn_t h4000_keypad(int irq, void* data, struct pt_regs *regs)
{
	int button;
	int gpiono;
	int ispushed;
	
	/* look up the keyno */
	for (button = 0; gpiopadtab[button].irq != -1; button++)
		if (gpiopadtab[button].irq == irq)
			break;
	
	if (gpiopadtab[button].irq == -1)
	{
		printk("keypad: unhandled irq %d (%d-%d)\n", irq, irq-22, GPLR(irq-22) & GPIO_bit(irq-22));
		return 1;
	};
	
	gpiono = gpiopadtab[button].gpio;
	button = gpiopadtab[button].keyno;
	
	ispushed = GPLR(gpiono) & GPIO_bit(gpiono);
	ispushed = !ispushed;
	
//	h3600_hal_keypress( H3600_MAKEKEY( (unsigned char)button, (unsigned char)ispushed ) );
	return IRQ_HANDLED;
};

/**********************************************************************************
 *      Keypad handling (asic)
 **********************************************************************************/

#define BIT_RECORD   0x04
#define BIT_HOME     0x08
#define BIT_MAIL     0x10
#define BIT_CONTACTS 0x20
#define BIT_CALENDAR 0x40

static struct kpad bittokey[] = {
	{BIT_RECORD,H3600_KEYCODE_RECORD,0},
	{BIT_HOME,H3600_KEYCODE_START,0},
	{BIT_MAIL,H3600_KEYCODE_Q,0},
	{BIT_CONTACTS,H3600_KEYCODE_CONTACTS,0},
	{BIT_CALENDAR,H3600_KEYCODE_CALENDAR,0},
	{-1,-1,-1}
};

static void h4000_asickey(int bit)
{
	int pressed;
	int i;

	if (bit == 0)
		return;
		
	pressed = IPAQ_ASIC3_GPIO_OFFSET(IPAQ_ASIC3_VIRT,unsigned int,D,Status) & bit;
	pressed = !pressed;
	
	IPAQ_ASIC3_GPIO_OFFSET(IPAQ_ASIC3_VIRT,unsigned int,D,EdgeTrigger) = ((pressed) ? (IPAQ_ASIC3_GPIO_OFFSET(IPAQ_ASIC3_VIRT,unsigned int,D,EdgeTrigger) | bit) : (IPAQ_ASIC3_GPIO_OFFSET(IPAQ_ASIC3_VIRT,unsigned int,D,EdgeTrigger) & ~bit));
	
	for (i=0;bittokey[i].keyno != -1;i++)
		if (bittokey[i].keyno == bit)
			break;
			
	if (bittokey[i].keyno == -1)
	{
		printk("h4000_asickey: unhandled bit 0x%02X!\n", bit);
		return;
	};
	printk("h4000_asickey: handled bit 0x%02X!\n", bit);
	
	// needs to found an hal replacement... perhaps may be useful copying from axim5_button.c
//	h3600_hal_keypress( H3600_MAKEKEY( (unsigned char)bittokey[i].gpio, (unsigned char)pressed ) );
};

static irqreturn_t h4000_asic(int irq, void* data, struct pt_regs *regs)
{
	unsigned int gpiostat = IPAQ_ASIC3_GPIO_OFFSET(IPAQ_ASIC3_VIRT,unsigned int,D,IntStatus);
	
	
	gpiostat &= 0x7C;
	printk("%s: h4000_asic irq %d has called me... %03x\n",__FUNCTION__,irq,gpiostat);
	if (gpiostat)
	{
		IPAQ_ASIC3_GPIO_OFFSET(IPAQ_ASIC3_VIRT,unsigned int,D,IntStatus) = 0x0;
		
		h4000_asickey(gpiostat&BIT_RECORD);
		h4000_asickey(gpiostat&BIT_HOME);
		h4000_asickey(gpiostat&BIT_MAIL);
		h4000_asickey(gpiostat&BIT_CONTACTS);
		h4000_asickey(gpiostat&BIT_CALENDAR);
	};
	return IRQ_HANDLED;
}

/***********************************************************************************
 *      Generic IRQ handling
 ***********************************************************************************/

#define buttonirq(x) 	set_irq_type (IRQ_GPIO(x), IRQT_BOTHEDGE); \
			request_irq(IRQ_GPIO(x), h4000_keypad, SA_SAMPLE_RANDOM, "h4000_keypad_act", NULL);


static int h4000_asic_init_isr( void )
{
	set_irq_type(IRQ_GPIO(GPIO_NR_H4000_ASIC_IRQ_1_N), IRQT_BOTHEDGE);
	set_irq_type(IRQ_GPIO(GPIO_NR_H4000_ASIC_IRQ_2_N), IRQT_BOTHEDGE);
	request_irq(IRQ_GPIO(GPIO_NR_H4000_ASIC_IRQ_1_N), h4000_asic, SA_INTERRUPT, "h4000_asic", NULL);
	request_irq(IRQ_GPIO(GPIO_NR_H4000_ASIC_IRQ_2_N), h4000_asic, SA_INTERRUPT, "h4000_asic", NULL);
	
	buttonirq(GPIO_NR_H4000_ACTION_BUTTON_N)
	buttonirq(GPIO_NR_H4000_POWER_BUTTON_N)
	buttonirq(GPIO_NR_H4000_UP_BUTTON_N)
	buttonirq(GPIO_NR_H4000_DOWN_BUTTON_N)
	buttonirq(GPIO_NR_H4000_LEFT_BUTTON_N)
	buttonirq(GPIO_NR_H4000_RIGHT_BUTTON_N)

	return 0;
}

static void  h4000_asic_release_isr( void )
{
}


/***********************************************************************************
 *   Sub-module support
 ***********************************************************************************/

struct asic_system_handler { 
	char *name;
	int  (*init)( void );
	void (*cleanup)( void );
	int  (*suspend)( void );
	void (*resume)( void );
};

/* 
   We initialize and resume from top to bottom,
   cleanup and suspend from bottom to top 
*/
   
const struct asic_system_handler h4000_asic_system_handlers[] = {
#if 0
#if defined(CONFIG_MMC) || defined(CONFIG_MMC_MODULE)
	{
		name:    "mmc",
		init:    h4000_asic_mmc_init,
		cleanup: h4000_asic_mmc_cleanup,
		suspend: h4000_asic_mmc_suspend,
		resume:  h4000_asic_mmc_resume
	}
#endif
#endif
};

#define SYS_HANDLER_SIZE   (sizeof(h4000_asic_system_handlers)/sizeof(struct asic_system_handler))

/*static int h4000_asic_suspend_handlers( void )
{
	int i;
	int result;

	for ( i = SYS_HANDLER_SIZE - 1 ; i >= 0 ; i-- ) {
		if ( h4000_asic_system_handlers[i].suspend ) {
			if ((result = h4000_asic_system_handlers[i].suspend()) != 0 ) {
				while ( ++i < SYS_HANDLER_SIZE )
					if ( h4000_asic_system_handlers[i].resume )
						h4000_asic_system_handlers[i].resume();
				return result;
			}
		}
	}
	return 0;
}

static void h4000_asic_resume_handlers( void )
{
	int i;

	for ( i = 0 ; i < SYS_HANDLER_SIZE ; i++ )
		if ( h4000_asic_system_handlers[i].resume )
			h4000_asic_system_handlers[i].resume();
}
*/

static int __init h4000_asic_init_handlers( void )
{
	int i;
	int result;

	for ( i = 0 ; i < SYS_HANDLER_SIZE ; i++ ) {
		if ( h4000_asic_system_handlers[i].init ) {
			if ( (result = h4000_asic_system_handlers[i].init()) != 0 ) {
				while ( --i >= 0 )
					if ( h4000_asic_system_handlers[i].cleanup )
						h4000_asic_system_handlers[i].cleanup();
				return result;
			}
		}
	}
	return 0;
}

static void  h4000_asic_cleanup_handlers( void )
{
	int i;

	for ( i = SYS_HANDLER_SIZE - 1 ; i >= 0 ; i-- )
		if ( h4000_asic_system_handlers[i].cleanup )
			h4000_asic_system_handlers[i].cleanup();
}

/***********************************************************************************
 *   Power management
 *
 *   On sleep, if we return anything other than "0", we will cancel sleeping.
 *
 *   On resume, if we return anything other than "0", we will put the iPAQ
 *     back to sleep immediately.
 ***********************************************************************************/

/*static int h4000_asic_pm_callback(pm_request_t req)
{
	int result = 0;

	switch (req) {
	case PM_SUSPEND:
		result = h4000_asic_suspend_handlers();
//		h3600_asic_initiate_sleep ();
		break;

	case PM_RESUME:
//		result = h3600_asic_check_wakeup ();
		if ( !result ) {
			h4000_asic_resume_handlers();
		}
		break;
	}
	return result;
}
*/

//static int h4000_asic_reset_handler(ctl_table *ctl, int write, struct file * filp,
//				    void *buffer, size_t *lenp)
//{
//	MOD_INC_USE_COUNT;
//	h4000_asic_pm_callback( PM_SUSPEND );
//	h4000_asic_pm_callback( PM_RESUME );
//	MOD_DEC_USE_COUNT;
//
//	return 0;
//}

/***********************************************************************************
 *   Initialization code
 ***********************************************************************************/

static  void h4000_asic_cleanup( void )
{
//	h3600_unregister_pm_callback( h4000_asic_pm_callback );
	h4000_asic_unregister_procfs();
//	h3600_hal_unregister_interface( &h4000_asic_ops );
	h4000_asic_cleanup_handlers();
	h4000_asic_release_isr();
//	ipaq_mtd_asset_cleanup();
}

int h4000_asic_init( void )
{
	int result;
	int line;

//	if ( !machine_is_h4000() ) {
//		printk("%s: unknown iPAQ model %s\n", __FUNCTION__, h3600_generic_name() );
//		return -ENODEV;
//	}

//	h3600_asic_register_battery_ops (&h4000_battery_ops);

//	result = ipaq_mtd_asset_init();
//	if ( result ) { line = __LINE__; goto init_fail; };

	printk("%s: Initialization...", __FUNCTION__);
	result = h4000_asic_init_isr();  
	if ( result ) { line = __LINE__; goto init_fail; };

	result = h4000_asic_init_handlers();
	if ( result ) { line = __LINE__; goto init_fail; };

//	result = h3600_hal_register_interface( &h4000_asic_ops );
//	if ( result ) { line = __LINE__; goto init_fail; };

	result = h4000_asic_register_procfs();
	if ( result ) { line = __LINE__; goto init_fail; };

//	result = h3600_register_pm_callback( h4000_asic_pm_callback );
//	if ( result ) { line = __LINE__; goto init_fail; };

	return 0;

init_fail:
	printk("%s:%d: %s: FAILURE!  result=%d. Exiting...\n", __FILE__, line, __FUNCTION__, result);
	h4000_asic_cleanup();
	return result;
}

void  h4000_asic_exit( void )
{
	printk("%s: Exiting...", __FUNCTION__);
	h4000_asic_cleanup();
}

module_init(h4000_asic_init)
module_exit(h4000_asic_exit)

MODULE_AUTHOR(" ");
MODULE_DESCRIPTION(" ");
MODULE_LICENSE("Dual BSD/GPL");
                                                                                                                            
