/*
* Driver interface to the ASIC Companion chip on the iPAQ H5400
*
* Copyright 2003 Compaq Computer Corporation.
*
* Use consistent with the GNU GPL is permitted,
* provided that this copyright notice is
* preserved in its entirety in all copies and derived works.
*
* COMPAQ COMPUTER CORPORATION MAKES NO WARRANTIES, EXPRESSED OR IMPLIED,
* AS TO THE USEFULNESS OR CORRECTNESS OF THIS CODE OR ITS
* FITNESS FOR ANY PARTICULAR PURPOSE.
*
* Author:  Jamey Hicks <jamey.hicks@hp.com
*          March 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 <asm/irq.h>

#include <asm/arch-pxa/h5400-gpio.h>
#include <asm/arch-pxa/h5400-asic.h>
#include <asm/arch-pxa/h5400-irqs.h>
#include "asm/arch-sa1100/ipaq-mtd-asset.h"

#include "../mach-sa1100/h3600_asic_core.h"
#include "../mach-ipaq/h3600_asic_battery.h"
#include "h5400_asic_io.h"
#include "h5400_asic_sleeve.h"

extern struct asic2_battery_ops h5400_battery_ops;

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

MODULE_AUTHOR("Jamey Hicks");
MODULE_DESCRIPTION("Hardware abstraction layer for the iPAQ H5400");

#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

/* Statistics */
struct asic_statistics g_h5400_asic_statistics;

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

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

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

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

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

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

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

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

int h5400_asic_audio_power( long samplerate )
{
	int on = (samplerate > 0);

	SET_H5400_ASIC_GPIO(GPB, CODEC_POWER_ON, on);
	SET_H5400_ASIC_GPIO(GPB, AUDIO_POWER_ON, on);

	return 0;
}

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

int h5400_asic_set_ebat( void )
{
	return -EINVAL;
}

static struct h3600_hal_ops h5400_asic_ops = {
	get_version         : h5400_asic_get_version,
	eeprom_read         : h5400_asic_eeprom_read,
	eeprom_write        : h5400_asic_eeprom_write,
	get_thermal_sensor  : h5400_asic_get_thermal_sensor,
	set_notify_led      : h5400_asic_set_notify_led,
	read_light_sensor   : h5400_asic_read_light_sensor,
	get_battery         : h3600_asic_battery_read,
	spi_read            : h5400_asic_spi_read,
	spi_write           : h5400_asic_spi_write,
	sleeve_battery_read : h5400_asic_spi_read_pcmcia_battery,
	get_option_detect   : h5400_asic_get_option_detect,
	audio_clock         : h5400_asic_audio_clock,
	audio_power         : h5400_asic_audio_power,
	audio_mute          : h5400_asic_audio_mute,
	backlight_control   : h5400_asic_backlight_control,
	asset_read          : ipaq_mtd_asset_read,
	set_ebat            : h5400_asic_set_ebat,
        owner               : THIS_MODULE,
};




/***********************************************************************************
 *      Sleeve ISR
 *
 *   Resources used:     
 *                       
 ***********************************************************************************/

void h5400_asic_sleeve_isr(int irq, void *dev_id, struct pt_regs *regs)
{
        int present = (h5400_asic_read_register(H5400_ASIC_PCMCIA_EPS) & ((1 << 4) | (1 << 5))) ? 0 : 1;
	h3600_hal_option_detect( present );
	H5400_ASIC_SET_BIT (H5400_ASIC_PCMCIA_IP, ((1 << 4) | (1 << 5)));
}



static struct proc_dir_entry   *asic_proc_dir;

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

static const struct simple_proc_entry sproc_list[] = {
	{ "battery",    h3600_asic_proc_battery },
	{ "ds2760",     h3600_asic_proc_battery_ds2760 },
	{ "ds2760_raw", h3600_asic_proc_battery_raw },
};

static int __init h5400_asic_register_procfs( void )
{
	int i;

	printk("register_procfs\n");

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

	printk("register_procfs: done\n");

	return 0;
}

static void h5400_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;
	}
}



/***********************************************************************************
 *      Power button handling
 ***********************************************************************************/

static void h5400_asic_power_isr(int irq, void *dev_id, struct pt_regs *regs)
{
        int down = (GPLR0 & (1 << GPIO_NR_H5400_POWER_BUTTON)) ? 1 : 0;
	h3600_hal_keypress( H3600_MAKEKEY( H3600_KEYCODE_SUSPEND, down ) );
}



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

static int h5400_asic_init_isr( void )
{
	int result;
	DEBUG_INIT();

	/* Request IRQs */
        set_GPIO_IRQ_edge(GPIO_NR_H5400_POWER_BUTTON, GPIO_BOTH_EDGES);
	result = request_irq(IRQ_GPIO(GPIO_NR_H5400_POWER_BUTTON), 
			     h5400_asic_power_isr, 
			     SA_INTERRUPT | SA_SAMPLE_RANDOM,
			     "power button", NULL);

	if ( result ) {
		printk(KERN_CRIT "%s: unable to grab power button IRQ\n", __FUNCTION__);
		return result;
	}

	result = request_irq(IRQ_H5400_AC_IN,
			     h3600_asic_ac_in_isr,
			     SA_INTERRUPT | SA_SAMPLE_RANDOM,
			     "AC presence", NULL );
	if ( result ) {
		printk(KERN_CRIT "%s: unable to grab AC in IRQ\n", __FUNCTION__);
		free_irq(IRQ_GPIO(GPIO_NR_H5400_POWER_BUTTON), NULL);
		return result;
	}

	result = request_irq(IRQ_GPIO_H5400_EPS_ODET,
			     h5400_asic_sleeve_isr,
			     SA_INTERRUPT | SA_SAMPLE_RANDOM,
			     "sleeve detect", NULL );
	if ( result ) {
		printk(KERN_CRIT "%s: unable to grab sleeve insertion IRQ\n", __FUNCTION__);
		free_irq(IRQ_GPIO(GPIO_NR_H5400_POWER_BUTTON), NULL);
		free_irq(IRQ_H5400_AC_IN, NULL );
		return result;
	}
	
	return 0;
}

static void h5400_asic_release_isr( void )
{
	DEBUG_INIT();

	free_irq(IRQ_GPIO(GPIO_NR_H5400_POWER_BUTTON), NULL);
	free_irq(IRQ_H5400_AC_IN, NULL);
	free_irq(IRQ_GPIO_H5400_EPS_ODET, NULL);
}


/***********************************************************************************
 *   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 h5400_asic_system_handlers[] = {
#if 0
	{ 
		name:    "shared",
		init:    h3600_asic_shared_init,      
		cleanup: h3600_asic_shared_cleanup 
	},
#endif
	{ 
		name:   "adc",
		init:    h5400_asic_adc_init,
		cleanup: h5400_asic_adc_cleanup,
		suspend: h5400_asic_adc_suspend,
		resume:  h5400_asic_adc_resume,
	},
	{ 
		name:    "key",
		init:    h5400_asic_key_init,
		cleanup: h5400_asic_key_cleanup,
	},
	{ 
		name:    "backlight",
		init:    h5400_asic_backlight_init,   
		cleanup: h5400_asic_backlight_cleanup 
	},
	{ 
		name:    "touchscreen",
		init:    h5400_asic_touchscreen_init, 
		cleanup: h5400_asic_touchscreen_cleanup,
		suspend: h5400_asic_touchscreen_suspend, 
		resume:  h5400_asic_touchscreen_resume 
	},
	{ 
		name:    "owm",
		init:    h3600_asic_owm_init, 
		cleanup: h3600_asic_owm_cleanup,
		suspend: h3600_asic_owm_suspend, 
		resume:  h3600_asic_owm_resume 
	},
	{ 
		name:    "battery",
		init:    h3600_asic_battery_init, 
		cleanup: h3600_asic_battery_cleanup,
		suspend: h3600_asic_battery_suspend, 
		resume:  h3600_asic_battery_resume 
	},
	{ 
		name:    "asset",
		init:    ipaq_mtd_asset_init,   
		cleanup: ipaq_mtd_asset_cleanup 
	},
#if 0
#if defined(CONFIG_MMC) || defined(CONFIG_MMC_MODULE)
	{
		name:    "mmc",
		init:    h5400_asic_mmc_init,
		cleanup: h5400_asic_mmc_cleanup,
		suspend: h5400_asic_mmc_suspend,
		resume:  h5400_asic_mmc_resume
	}
#endif
#endif
};

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

static int handlers_initialized;

static int h5400_asic_suspend_handlers( void )
{
	int i;
	int result;

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

static void h5400_asic_resume_handlers( void )
{
	int i;

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

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

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

	handlers_initialized = 1;

	return 0;
}

static void h5400_asic_cleanup_handlers( void )
{
	int i;

	handlers_initialized = 0;

	for ( i = SYS_HANDLER_SIZE - 1 ; i >= 0 ; i-- )
		if ( h5400_asic_system_handlers[i].cleanup )
			h5400_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 h5400_asic_pm_callback(pm_request_t req)
{
	int result = 0;

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

	case PM_RESUME:
		result = h3600_asic_check_wakeup ();
		if ( !result ) {
			h5400_asic_resume_handlers();
		}
		break;
	}
	return result;
}

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



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

static void h5400_asic_cleanup( void )
{
	h3600_unregister_pm_callback( h5400_asic_pm_callback );
	h5400_asic_unregister_procfs();
	h3600_hal_unregister_interface( &h5400_asic_ops );
	if (handlers_initialized)
		h5400_asic_cleanup_handlers();
	h5400_asic_release_isr();
}

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

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

	h3600_asic_register_battery_ops (&h5400_battery_ops);

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

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

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

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

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

	result = h3600_register_pm_callback( h5400_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);
	h5400_asic_cleanup();
	return result;
}

void h5400_asic_exit( void )
{
	h5400_asic_cleanup();
}

module_init(h5400_asic_init)
module_exit(h5400_asic_exit)
