/*
 * Hardware definitions for HP iPAQ H3xxx Handheld Computers
 *
 * Copyright 2000,1 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.
 *
 * History:
 *
 * 2001-10-??   Andrew Christian   Added support for iPAQ H3800
 *                                 and abstracted EGPIO interface.
 *
 */
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/tty.h>
#include <linux/sched.h>
#include <linux/pm.h>

#include <asm/irq.h>
#include <asm/hardware.h>
#include <asm/setup.h>
#include <asm/arch/pm.h>

#include <asm/mach/irq.h>
#include <asm/mach/arch.h>
#include <asm/mach/map.h>
#include <asm/mach/serial_sa1100.h>

#include <asm/arch/irq.h>

#include <linux/serial_core.h>

#if defined (CONFIG_SA1100_H3600) || defined (CONFIG_SA1100_H3100)
#include <asm/arch/h3600_gpio.h>
#endif

#ifdef CONFIG_SA1100_H3800
#include <asm/arch/h3600_asic.h>
#endif

#include "generic.h"

struct ipaq_model_ops ipaq_model_ops;
EXPORT_SYMBOL(ipaq_model_ops);

static void msleep(unsigned int msec)
{
	current->state = TASK_INTERRUPTIBLE;
	schedule_timeout( (msec * HZ + 999) / 1000);
}

/*
 * low-level UART features
 */

static void h3600_uart_set_mctrl(struct uart_port *port, u_int mctrl)
{
	if (port->mapbase == _Ser3UTCR0) {
		if (mctrl & TIOCM_RTS)
			GPCR = GPIO_H3600_COM_RTS;
		else
			GPSR = GPIO_H3600_COM_RTS;
	}
}

static u_int h3600_uart_get_mctrl(struct uart_port *port)
{
	u_int ret = TIOCM_CD | TIOCM_CTS | TIOCM_DSR;

	if (port->mapbase == _Ser3UTCR0) {
		int gplr = GPLR;
                /* DCD and CTS bits are inverted in GPLR by RS232 transceiver */
		if (gplr & GPIO_H3600_COM_DCD)
			ret &= ~TIOCM_CD;
		if (gplr & GPIO_H3600_COM_CTS)
			ret &= ~TIOCM_CTS;
	}

	return ret;
}

static void h3600_dcd_intr(int irq, void *dev_id, struct pt_regs *regs)
{
	struct uart_info *info = dev_id;
	/* Note: should only call this if something has changed */
        /* DCD and CTS bits are inverted in GPLR by RS232 transceiver */
	uart_handle_dcd_change(info, !(GPLR & GPIO_H3600_COM_DCD));
}

static void h3600_cts_intr(int irq, void *dev_id, struct pt_regs *regs)
{
	struct uart_info *info = dev_id;
	/* Note: should only call this if something has changed */
        /* DCD and CTS bits are inverted in GPLR by RS232 transceiver */
	uart_handle_cts_change(info, !(GPLR & GPIO_H3600_COM_CTS));
}

static void h3600_uart_pm(struct uart_port *port, u_int state, u_int oldstate)
{
	if (port->mapbase == _Ser2UTCR0) { /* TODO: REMOVE THIS */
		assign_h3600_egpio( IPAQ_EGPIO_IR_ON, !state );
	} else if (port->mapbase == _Ser3UTCR0) {
		assign_h3600_egpio( IPAQ_EGPIO_RS232_ON, !state );
	}
}

/*
 * Enable/Disable wake up events for this serial port.
 * Obviously, we only support this on the normal COM port.
 */
static int h3600_uart_set_wake(struct uart_port *port, u_int enable)
{
	int err = -EINVAL;

	if (port->mapbase == _Ser3UTCR0) {
		if (enable)
			PWER |= PWER_GPIO23 | PWER_GPIO25 ; /* DCD and CTS */
		else
			PWER &= ~(PWER_GPIO23 | PWER_GPIO25); /* DCD and CTS */
		err = 0;
	}
	return err;
}

static int h3600_uart_open(struct uart_port *port, struct uart_info *info)
{
	int ret = 0;

	if (port->mapbase == _Ser2UTCR0) {  /* TODO: REMOVE THIS */
		Ser2UTCR4 = UTCR4_HSE;
		Ser2HSCR0 = 0;
		Ser2HSSR0 = HSSR0_EIF | HSSR0_TUR |
			    HSSR0_RAB | HSSR0_FRE;
	} else if (port->mapbase == _Ser3UTCR0) {
		set_GPIO_IRQ_edge(GPIO_H3600_COM_DCD|GPIO_H3600_COM_CTS,
				  GPIO_BOTH_EDGES);

		ret = request_irq(IRQ_GPIO_H3600_COM_DCD, h3600_dcd_intr,
				  0, "RS232 DCD", info);
		if (ret)
			return ret;

		ret = request_irq(IRQ_GPIO_H3600_COM_CTS, h3600_cts_intr,
				  0, "RS232 CTS", info);
		if (ret)
			free_irq(IRQ_GPIO_H3600_COM_DCD, info);
	}
	return ret;
}

static void h3600_uart_close(struct uart_port *port, struct uart_info *info)
{
	if (port->mapbase == _Ser3UTCR0) {
		free_irq(IRQ_GPIO_H3600_COM_DCD, info);
		free_irq(IRQ_GPIO_H3600_COM_CTS, info);
	}
}

static struct sa1100_port_fns h3600_port_fns __initdata = {
	.set_mctrl	= h3600_uart_set_mctrl,
	.get_mctrl	= h3600_uart_get_mctrl,
	.pm		= h3600_uart_pm,
	.set_wake	= h3600_uart_set_wake,
	.open		= h3600_uart_open,
	.close		= h3600_uart_close,
};



struct egpio_irq_info {
	int egpio_nr;
	int gpio; /* GPIO_GPIO(n) */
	int irq;  /* IRQ_GPIOn */
};

static struct egpio_irq_info h3600_egpio_irq_info[] = {
	{ IPAQ_EGPIO_PCMCIA_CD0_N, GPIO_H3600_PCMCIA_CD0, IRQ_GPIO_H3600_PCMCIA_CD0 }, 
	{ IPAQ_EGPIO_PCMCIA_CD1_N, GPIO_H3600_PCMCIA_CD1, IRQ_GPIO_H3600_PCMCIA_CD1 },
	{ IPAQ_EGPIO_PCMCIA_IRQ0, GPIO_H3600_PCMCIA_IRQ0, IRQ_GPIO_H3600_PCMCIA_IRQ0 },
	{ IPAQ_EGPIO_PCMCIA_IRQ1, GPIO_H3600_PCMCIA_IRQ1, IRQ_GPIO_H3600_PCMCIA_IRQ1 },
	{ 0, 0 }
}; 

static int h3600_common_irq_number(int egpio_nr)
{
	struct egpio_irq_info *info = h3600_egpio_irq_info;
	while (info->irq != 0) {
		if (info->egpio_nr == egpio_nr) {
			if (0) printk("%s: egpio_nr=%d irq=%d\n", __FUNCTION__, egpio_nr, info->irq);
			return info->irq;
		}
		info++;
	}

	printk("%s: unhandled egpio_nr=%d\n", __FUNCTION__, egpio_nr); 
	return -EINVAL;
}
EXPORT_SYMBOL(h3600_egpio_irq_number);

int h3600_egpio_set_irq_type(enum ipaq_egpio_type egpio_nr, unsigned int type)
{
	struct egpio_irq_info *info = h3600_egpio_irq_info;
	while (info->irq != 0) {
		if (info->egpio_nr == egpio_nr) {
			if (0) printk("%s: egpio_nr=%d gpio=%x irq=%d\n", __FUNCTION__, egpio_nr, info->gpio, info->irq);
			set_GPIO_IRQ_edge( info->gpio, type);
			return 0;
		}
		info++;
	}
	printk("%s: unhandled egpio_nr=%d\n", __FUNCTION__, egpio_nr); 
	return -EINVAL;
}
EXPORT_SYMBOL(h3600_egpio_set_irq_type);

int h3600_egpio_request_irq(enum ipaq_egpio_type egpio_number,
			    void (*handler)(int, void *, struct pt_regs *),
			    unsigned long flags, const char *name, void *data)
{
	int sa1100_irq = h3600_egpio_irq_number(egpio_number);
	return request_irq( sa1100_irq, handler, flags, name, data);
}
EXPORT_SYMBOL(h3600_egpio_request_irq);

void h3600_egpio_free_irq(unsigned int egpio_number, void *data)
{
	int sa1100_irq = h3600_egpio_irq_number(egpio_number);
	free_irq( sa1100_irq, data );
}
EXPORT_SYMBOL(h3600_egpio_free_irq);


void ipaq_set_led (enum led_color color, int duty_time, int cycle_time)
{
	if (ipaq_model_ops.set_led)
		ipaq_model_ops.set_led (color, duty_time, cycle_time);
}
EXPORT_SYMBOL(ipaq_set_led);

void ipaq_backlight_power (int on)
{
	if (ipaq_model_ops.backlight_power)
		ipaq_model_ops.backlight_power (on);
}
EXPORT_SYMBOL(ipaq_backlight_power);



static struct map_desc h3600_io_desc[] __initdata = {
 /* virtual            physical           length      domain     r  w  c  b */
  { 0xe8000000,        0x00000000,        0x02000000, DOMAIN_IO, 0, 1, 0, 0 }, /* Flash bank 0 	        CS#0 */
  { H3600_EGPIO_VIRT,  H3600_EGPIO_PHYS,  0x01000000, DOMAIN_IO, 0, 1, 0, 0 }, /* EGPIO 0	        CS#5 */
  { H3600_BANK_2_VIRT, H3600_BANK_2_PHYS, 0x02800000, DOMAIN_IO, 0, 1, 0, 0 }, /* static memory bank 2  CS#2 */
  { H3600_BANK_4_VIRT, H3600_BANK_4_PHYS, 0x00800000, DOMAIN_IO, 0, 1, 0, 0 }, /* static memory bank 4  CS#4 */
  LAST_DESC
};

/*
 * Common map_io initialization
 */

static void __init h3xxx_map_io(void)
{
	sa1100_map_io();
	iotable_init(h3600_io_desc);

	sa1100_register_uart_fns(&h3600_port_fns);
	sa1100_register_uart(0, 3); /* Common serial port */

	/* Ensure those pins are outputs and driving low  */
 	PPDR |= PPC_TXD4 | PPC_SCLK | PPC_SFRM;
 	PPSR &= ~(PPC_TXD4 | PPC_SCLK | PPC_SFRM);

	/* Configure suspend conditions */
	PGSR = 0;
	PWER = PWER_GPIO0 | PWER_RTC;
	PCFR = PCFR_OPDE;
	PSDR = 0;
}

static __inline__ void do_blank( int setp ) {
	if ( ipaq_model_ops.blank_callback )
		ipaq_model_ops.blank_callback(1-setp);
}


static unsigned long common_read_egpio( enum ipaq_egpio_type x)
{
	switch (x) {
	case IPAQ_EGPIO_PCMCIA_CD0_N:
		return(GPLR & GPIO_H3600_PCMCIA_CD0);
	case IPAQ_EGPIO_PCMCIA_CD1_N:
		return(GPLR & GPIO_H3600_PCMCIA_CD1);
	case IPAQ_EGPIO_PCMCIA_IRQ0:
		return(GPLR & GPIO_H3600_PCMCIA_IRQ0);
	case IPAQ_EGPIO_PCMCIA_IRQ1:
		return(GPLR & GPIO_H3600_PCMCIA_IRQ1);
	default:
		printk("%s: unhandled egpio_nr=%d\n", __FUNCTION__, x); 
		return 0;
	}
}

/************************* H3100 *************************/

#ifdef CONFIG_SA1100_H3100

#define H3100_EGPIO	(*(volatile unsigned int *)H3600_EGPIO_VIRT)
static unsigned int h3100_egpio = 0;

static void h3100_control_egpio( enum ipaq_egpio_type x, int setp )
{
	unsigned int egpio = 0;
	long         gpio = 0;
	unsigned long flags;

	switch (x) {
	case IPAQ_EGPIO_LCD_POWER:
		egpio |= EGPIO_H3600_LCD_ON;
		gpio  |= GPIO_H3100_LCD_3V_ON;
		do_blank(setp);
		break;
	case IPAQ_EGPIO_LCD_ENABLE:
		break;
	case IPAQ_EGPIO_CODEC_NRESET:
		egpio |= EGPIO_H3600_CODEC_NRESET;
		break;
	case IPAQ_EGPIO_AUDIO_ON:
		gpio |= GPIO_H3100_AUD_PWR_ON
			| GPIO_H3100_AUD_ON;
		break;
	case IPAQ_EGPIO_QMUTE:
		gpio |= GPIO_H3100_QMUTE;
		break;
	case IPAQ_EGPIO_OPT_NVRAM_ON:
		egpio |= EGPIO_H3600_OPT_NVRAM_ON;
		break;
	case IPAQ_EGPIO_OPT_ON:
		egpio |= EGPIO_H3600_OPT_ON;
		break;
	case IPAQ_EGPIO_CARD_RESET:
		egpio |= EGPIO_H3600_CARD_RESET;
		break;
	case IPAQ_EGPIO_OPT_RESET:
		egpio |= EGPIO_H3600_OPT_RESET;
		break;
	case IPAQ_EGPIO_IR_ON:
		gpio |= GPIO_H3100_IR_ON;
		break;
	case IPAQ_EGPIO_IR_FSEL:
		gpio |= GPIO_H3100_IR_FSEL;
		break;
	case IPAQ_EGPIO_RS232_ON:
		egpio |= EGPIO_H3600_RS232_ON;
		break;
	case IPAQ_EGPIO_VPP_ON:
		egpio |= EGPIO_H3600_VPP_ON;
		break;
	default:
		printk("%s: unhandled egpio=%d\n", __FUNCTION__, x);
	}

	if ( egpio || gpio ) {
		local_irq_save(flags);
		if ( setp ) {
			h3100_egpio |= egpio;
			GPSR = gpio;
		} else {
			h3100_egpio &= ~egpio;
			GPCR = gpio;
		}
		H3100_EGPIO = h3100_egpio;
		local_irq_restore(flags);
	}
}

static unsigned long h3100_read_egpio( enum ipaq_egpio_type x )
{
	switch (x) {
	default:
		return common_read_egpio(x);
	}
}

static int h3100_pm_callback( int req )
{
	if ( ipaq_model_ops.pm_callback_aux )
		return ipaq_model_ops.pm_callback_aux(req);
	return 0;
}

static struct ipaq_model_ops h3100_model_ops __initdata = {
	.generic_name = "3100",
	.control      = h3100_control_egpio,
	.read         = h3100_read_egpio,
	.pm_callback  = h3100_pm_callback,
	.irq_number   = h3600_common_irq_number
};

#define H3100_DIRECT_EGPIO (GPIO_H3100_BT_ON      \
                          | GPIO_H3100_GPIO3      \
                          | GPIO_H3100_QMUTE      \
                          | GPIO_H3100_LCD_3V_ON  \
	                  | GPIO_H3100_AUD_ON     \
		          | GPIO_H3100_AUD_PWR_ON \
			  | GPIO_H3100_IR_ON      \
			  | GPIO_H3100_IR_FSEL)

static void __init h3100_map_io(void)
{
	h3xxx_map_io();

	/* sa1100_register_uart(1, 1);  */ /* Microcontroller */

	/* Initialize h3100-specific values here */
	GPCR = 0x0fffffff;       /* All outputs are set low by default */
	GPDR = GPIO_H3600_COM_RTS  | GPIO_H3600_L3_CLOCK |
	       GPIO_H3600_L3_MODE  | GPIO_H3600_L3_DATA  |
	       GPIO_H3600_CLK_SET1 | GPIO_H3600_CLK_SET0 |
	       H3100_DIRECT_EGPIO;

	/* Older bootldrs put GPIO2-9 in alternate mode on the 
	   assumption that they are used for video */
	GAFR &= ~H3100_DIRECT_EGPIO;

	H3100_EGPIO = h3100_egpio;
	ipaq_model_ops = h3100_model_ops;
}

MACHINE_START(H3100, "HP iPAQ H3100")
	BOOT_MEM(0xc0000000, 0x80000000, 0xf8000000)
	BOOT_PARAMS(0xc0000100)
	MAPIO(h3100_map_io)
	INITIRQ(sa1100_init_irq)
MACHINE_END

#endif /* CONFIG_SA1100_H3100 */

/************************* H3600 *************************/

#ifdef CONFIG_SA1100_H3600

#define H3600_EGPIO	(*(volatile unsigned int *)H3600_EGPIO_VIRT)
static unsigned int h3600_egpio = EGPIO_H3600_RS232_ON;

static void h3600_control_egpio( enum ipaq_egpio_type x, int setp )
{
	unsigned int egpio = 0;
	unsigned long flags;

	switch (x) {
	case IPAQ_EGPIO_LCD_POWER:
		egpio |= EGPIO_H3600_LCD_ON |
			 EGPIO_H3600_LCD_PCI |
			 EGPIO_H3600_LCD_5V_ON |
			 EGPIO_H3600_LVDD_ON;
		do_blank(setp);
		break;
	case IPAQ_EGPIO_LCD_ENABLE:
		break;
	case IPAQ_EGPIO_CODEC_NRESET:
		egpio |= EGPIO_H3600_CODEC_NRESET;
		break;
	case IPAQ_EGPIO_AUDIO_ON:
		egpio |= EGPIO_H3600_AUD_AMP_ON |
			EGPIO_H3600_AUD_PWR_ON;
		break;
	case IPAQ_EGPIO_QMUTE:
		egpio |= EGPIO_H3600_QMUTE;
		break;
	case IPAQ_EGPIO_OPT_NVRAM_ON:
		egpio |= EGPIO_H3600_OPT_NVRAM_ON;
		break;
	case IPAQ_EGPIO_OPT_ON:
		egpio |= EGPIO_H3600_OPT_ON;
		break;
	case IPAQ_EGPIO_CARD_RESET:
		egpio |= EGPIO_H3600_CARD_RESET;
		break;
	case IPAQ_EGPIO_OPT_RESET:
		egpio |= EGPIO_H3600_OPT_RESET;
		break;
	case IPAQ_EGPIO_IR_ON:
		egpio |= EGPIO_H3600_IR_ON;
		break;
	case IPAQ_EGPIO_IR_FSEL:
		egpio |= EGPIO_H3600_IR_FSEL;
		break;
	case IPAQ_EGPIO_RS232_ON:
		egpio |= EGPIO_H3600_RS232_ON;
		break;
	case IPAQ_EGPIO_VPP_ON:
		egpio |= EGPIO_H3600_VPP_ON;
		break;
	default:
		printk("%s: unhandled egpio_nr=%d\n", __FUNCTION__, x); 
	}

	if ( egpio ) {
		local_irq_save(flags);
		if ( setp )
			h3600_egpio |= egpio;
		else
			h3600_egpio &= ~egpio;
		H3600_EGPIO = h3600_egpio;
		local_irq_restore(flags);
	}
}

static unsigned long h3600_read_egpio( enum ipaq_egpio_type x )
{
	switch (x) {
	default:
		return common_read_egpio(x);
	}
}


static int h3600_pm_callback( int req )
{
	if ( ipaq_model_ops.pm_callback_aux )
		return ipaq_model_ops.pm_callback_aux(req);
	return 0;
}

static struct ipaq_model_ops h3600_model_ops __initdata = {
	.generic_name = "3600",
	.control      = h3600_control_egpio,
	.read         = h3600_read_egpio,
	.pm_callback  = h3600_pm_callback,
	.irq_number   = h3600_common_irq_number
};

static void __init h3600_map_io(void)
{
	h3xxx_map_io();

	/* sa1100_register_uart(1, 1); */ /* Microcontroller */

	/* Initialize h3600-specific values here */

	GPCR = 0x0fffffff;       /* All outputs are set low by default */
	GPDR = GPIO_H3600_COM_RTS  | GPIO_H3600_L3_CLOCK |
	       GPIO_H3600_L3_MODE  | GPIO_H3600_L3_DATA  |
	       GPIO_H3600_CLK_SET1 | GPIO_H3600_CLK_SET0 |
	       GPIO_LDD15 | GPIO_LDD14 | GPIO_LDD13 | GPIO_LDD12 |
	       GPIO_LDD11 | GPIO_LDD10 | GPIO_LDD9  | GPIO_LDD8;

	H3600_EGPIO = h3600_egpio;         /* Maintains across sleep? */
	ipaq_model_ops = h3600_model_ops;
}

MACHINE_START(H3600, "HP iPAQ H3600")
	BOOT_MEM(0xc0000000, 0x80000000, 0xf8000000)
	BOOT_PARAMS(0xc0000100)
	MAPIO(h3600_map_io)
	INITIRQ(sa1100_init_irq)
MACHINE_END

#endif /* CONFIG_SA1100_H3600 */

/************************* H3800 *************************/

#ifdef CONFIG_SA1100_H3800

#define SET_ASIC1(x) \
   do {if ( setp ) { H3800_ASIC1_GPIO_OUT |= (x); } else { H3800_ASIC1_GPIO_OUT &= ~(x); }} while(0)

#define SET_ASIC2(x) \
   do {if ( setp ) { H3800_ASIC2_GPIOPIOD |= (x); } else { H3800_ASIC2_GPIOPIOD &= ~(x); }} while(0)

#define CLEAR_ASIC1(x) \
   do {if ( setp ) { H3800_ASIC1_GPIO_OUT &= ~(x); } else { H3800_ASIC1_GPIO_OUT |= (x); }} while(0)

#define CLEAR_ASIC2(x) \
   do {if ( setp ) { H3800_ASIC2_GPIOPIOD &= ~(x); } else { H3800_ASIC2_GPIOPIOD |= (x); }} while(0)


/*
  On screen enable, we get 
  
     h3800_video_power_on(1)
     LCD controller starts
     h3800_video_lcd_enable(1)

  On screen disable, we get
  
     h3800_video_lcd_enable(0)
     LCD controller stops
     h3800_video_power_on(0)
*/


static void h3800_video_power_on( int setp )
{
	if ( setp ) {
		H3800_ASIC1_GPIO_OUT |= GPIO1_LCD_ON;
		msleep(30);
		H3800_ASIC1_GPIO_OUT |= GPIO1_VGL_ON;
		msleep(5);
		H3800_ASIC1_GPIO_OUT |= GPIO1_VGH_ON;
		msleep(50);
		H3800_ASIC1_GPIO_OUT |= GPIO1_LCD_5V_ON;
		msleep(5);
	}
	else {
		msleep(5);
		H3800_ASIC1_GPIO_OUT &= ~GPIO1_LCD_5V_ON;
		msleep(50);
		H3800_ASIC1_GPIO_OUT &= ~GPIO1_VGL_ON;
		msleep(5);
		H3800_ASIC1_GPIO_OUT &= ~GPIO1_VGH_ON;
		msleep(100);
		H3800_ASIC1_GPIO_OUT &= ~GPIO1_LCD_ON;
	}
}

static void h3800_video_lcd_enable( int setp )
{
	if ( setp ) {
		msleep(17);     // Wait one from before turning on
		H3800_ASIC1_GPIO_OUT |= GPIO1_LCD_PCI;
	}
	else {
		H3800_ASIC1_GPIO_OUT &= ~GPIO1_LCD_PCI;
		msleep(30);     // Wait before turning off
	}
}


static void h3800_control_egpio( enum ipaq_egpio_type x, int setp )
{
	switch (x) {
	case IPAQ_EGPIO_LCD_POWER:
		h3800_video_power_on( setp );
		break;
	case IPAQ_EGPIO_LCD_ENABLE:
		h3800_video_lcd_enable( setp );
		break;
	case IPAQ_EGPIO_CODEC_NRESET:
	case IPAQ_EGPIO_AUDIO_ON:
	case IPAQ_EGPIO_QMUTE:
		printk(__FUNCTION__ ": error - should not be called\n");
		break;
	case IPAQ_EGPIO_OPT_NVRAM_ON:
		SET_ASIC2( GPIO2_OPT_ON_NVRAM );
		break;
	case IPAQ_EGPIO_OPT_ON:
		SET_ASIC2( GPIO2_OPT_ON );
		break;
	case IPAQ_EGPIO_CARD_RESET:
		SET_ASIC2( GPIO2_OPT_PCM_RESET );
		break;
	case IPAQ_EGPIO_OPT_RESET:
		SET_ASIC2( GPIO2_OPT_RESET );
		break;
	case IPAQ_EGPIO_IR_ON:
		CLEAR_ASIC1( GPIO1_IR_ON_N );
		break;
	case IPAQ_EGPIO_IR_FSEL:
		break;
	case IPAQ_EGPIO_RS232_ON:
		SET_ASIC1( GPIO1_RS232_ON );
		break;
	case IPAQ_EGPIO_VPP_ON:
		H3800_ASIC2_FlashWP_VPP_ON = setp;
		break;
	default:
		printk("%s: unhandled egpio_nr=%d\n", __FUNCTION__, x); 
	}
}

static unsigned long h3800_read_egpio( enum ipaq_egpio_type x )
{
	switch (x) {
	default:
		return common_read_egpio(x);
	}
}


/* We need to fix ASIC2 GPIO over suspend/resume.  At the moment,
   it doesn't appear that ASIC1 GPIO has the same problem */

static int h3800_pm_callback( int req )
{
	static u16 asic1_data;
	static u16 asic2_data;
	int result = 0;

	printk(__FUNCTION__ " %d\n", req);

	switch (req) {
	case PM_RESUME:
		MSC2 = (MSC2 & 0x0000ffff) | 0xE4510000;  /* Set MSC2 correctly */

		H3800_ASIC2_GPIOPIOD = asic2_data;
		H3800_ASIC2_GPIODIR = GPIO2_PEN_IRQ 
			| GPIO2_SD_DETECT
			| GPIO2_EAR_IN_N 
			| GPIO2_USB_DETECT_N 
			| GPIO2_SD_CON_SLT;

		H3800_ASIC1_GPIO_OUT = asic1_data;

		if ( ipaq_model_ops.pm_callback_aux )
			result = ipaq_model_ops.pm_callback_aux(req);
		break;

	case PM_SUSPEND:
		if ( ipaq_model_ops.pm_callback_aux &&
		     ((result = ipaq_model_ops.pm_callback_aux(req)) != 0))
			return result;

		asic1_data = H3800_ASIC1_GPIO_OUT;
		asic2_data = H3800_ASIC2_GPIOPIOD;
		break;
	default:
		printk(__FUNCTION__ ": unrecognized PM callback\n");
		break;
	}
	return result;
}

static void h3800_set_led(enum led_color color, int duty_time, int cycle_time)
{
       if (duty_time) {
               H3800_ASIC2_LED_TimeBase(color)   = LEDTBS_BLINK | LEDTBS_AUTOSTOP | LEDTBS_ALWAYS | 1;
               H3800_ASIC2_LED_PeriodTime(color) = cycle_time;
               H3800_ASIC2_LED_DutyTime(color)   = duty_time;
       } else {
               H3800_ASIC2_LED_TimeBase(color) = 0;
       }
}


static struct ipaq_model_ops h3800_model_ops __initdata = {
	.generic_name = "3800",
	.control      = h3800_control_egpio,
	.read         = h3800_read_egpio,
	.pm_callback  = h3800_pm_callback,
	.irq_number   = h3600_common_irq_number,
	.set_led      = h3800_set_led
};

#define MAX_ASIC_ISR_LOOPS    20

/* The order of these is important - see #include <asm/arch/irqs.h> */
static u32 kpio_irq_mask[] = {
	KPIO_KEY_ALL,
	KPIO_SPI_INT,
	KPIO_OWM_INT,
	KPIO_ADC_INT,
	KPIO_UART_0_INT,
	KPIO_UART_1_INT,
	KPIO_TIMER_0_INT,
	KPIO_TIMER_1_INT,
	KPIO_TIMER_2_INT
};

static u32 gpio_irq_mask[] = {
	GPIO2_PEN_IRQ,
	GPIO2_SD_DETECT,
	GPIO2_EAR_IN_N,
	GPIO2_USB_DETECT_N,
	GPIO2_SD_CON_SLT,
};

static void h3800_IRQ_demux( int irq, void *dev_id, struct pt_regs *regs )
{
	int i;

	if (0) printk(__FUNCTION__ ": interrupt received\n");

	for ( i = 0 ; i < MAX_ASIC_ISR_LOOPS && (GPLR & GPIO_H3800_ASIC) ; i++ ) {
		u32 irq;
		int j;
		
		/* KPIO */
		irq = H3800_ASIC2_KPIINTFLAG;
		if (0) printk(__FUNCTION__" KPIO 0x%08X\n", irq );
		for ( j = 0 ; j < H3800_KPIO_IRQ_COUNT ; j++ )
			if ( irq & kpio_irq_mask[j] )
				do_IRQ( j + H3800_KPIO_IRQ_START, regs );

		/* GPIO2 */
		irq = H3800_ASIC2_GPIINTFLAG;
		if (0) printk(__FUNCTION__" GPIO 0x%08X\n", irq );
		for ( j = 0 ; j < H3800_GPIO_IRQ_COUNT ; j++ )
			if ( irq & gpio_irq_mask[j] )
				do_IRQ( j + H3800_GPIO_IRQ_START, regs );
	}

	if ( i >= MAX_ASIC_ISR_LOOPS )
		printk(__FUNCTION__ ": interrupt processing overrun\n");
}

static struct irqaction h3800_irq = {
	name:     "h3800_asic",
	handler:  h3800_IRQ_demux,
	flags:    SA_INTERRUPT
};


/************************************************************************/

static int( *g_sleeve_irq_demux)(struct pt_regs *regs);

static void h3800_sleeve_IRQ_demux( int irq, void *dev_id, struct pt_regs *regs )
{
	int i=0;

	if (0) printk("%s: interrupt received\n", __FUNCTION__ );

	// Cast g_sleeve_irq_demux from dev_id 
	if ( g_sleeve_irq_demux )
	  for ( i = 0 ; i < MAX_ASIC_ISR_LOOPS && (GPLR & GPIO_H3600_OPT_IRQ) ; i++ )
	  // GPIO_H3800_SLEEVE not defined -- replaced
			(*g_sleeve_irq_demux)( regs );

	// if ( i >= MAX_SLEEVE_ISR_LOOPS )
	// MAX_SLEEVE_ISR_LOOPS not defined -- replace with MAX_ASIC_ISR_LOOPS ????
	if ( i >= MAX_ASIC_ISR_LOOPS )
		printk("%s: interrupt processing overrun: GPLR=%x\n", __FUNCTION__, GPLR );
}

static struct irqaction h3800_sleeve_irqaction = {
	name:     "h3800_sleeve_asic",
	handler:  h3800_sleeve_IRQ_demux,
	flags:    SA_INTERRUPT
};

void h3800_register_sleeve_irq( demux, mask_ack, mask, unmask )
{
	int i;

	for ( i = 0 ; i < H3800_SLEEVE_IRQ_COUNT ; i++ ) {
		int irq = i + H3800_SLEEVE_IRQ_START;
		irq_desc[irq].valid    = 1;
		irq_desc[irq].probe_ok = 1;
		irq_desc[irq].mask_ack = mask_ack;
		irq_desc[irq].mask     = mask;
		irq_desc[irq].unmask   = unmask;
	}
	
	g_sleeve_irq_demux = demux;

	GPDR &= ~GPIO_H3600_OPT_IRQ;    /* GPIO line as input */
	set_GPIO_IRQ_edge( GPIO_H3600_OPT_IRQ, GPIO_RISING_EDGE );  /* Rising edge */

#if 0
	retval = request_irq(IRQ_GPIO_H3600_OPT_IRQ,
			     fpga_interrupt, SA_INTERRUPT | SA_SAMPLE_RANDOM,
			     "Sleeve Opt IRQ", (void *)demux);
#else
	setup_arm_irq( IRQ_GPIO_H3600_OPT_IRQ, &h3800_sleeve_irqaction );
#endif
}

void h3800_unregister_sleeve_irq( void *dev )
{
        int i;

	free_irq(IRQ_GPIO_H3600_OPT_IRQ, (void *)dev);

	for ( i = 0 ; i < H3800_SLEEVE_IRQ_COUNT ; i++ ) {
		int irq = i + H3800_SLEEVE_IRQ_START;
		irq_desc[irq].valid    = 0;
		irq_desc[irq].probe_ok = 1;
		irq_desc[irq].mask_ack = NULL;
		irq_desc[irq].mask     = NULL;
		irq_desc[irq].unmask   = NULL;
	}

	g_sleeve_irq_demux = NULL;
}

EXPORT_SYMBOL( h3800_register_sleeve_irq );
EXPORT_SYMBOL( h3800_unregister_sleeve_irq );


/************************************************************************/

u32 kpio_int_shadow = 0;

/* mask_ack <- IRQ is first serviced.
       mask <- IRQ is disabled.  
     unmask <- IRQ is enabled 

     The INTCLR registers are poorly documented.  I believe that writing
     a "1" to the register clears the specific interrupt, but the documentation
     indicates writing a "0" clears the interrupt.  In any case, they shouldn't
     be read (that's the INTFLAG register)
*/

static void h3800_mask_ack_kpio_irq( unsigned int irq )
{
	u32 mask = kpio_irq_mask[irq - H3800_KPIO_IRQ_START];
	kpio_int_shadow &= ~mask;
	H3800_ASIC2_KPIINTSTAT = kpio_int_shadow;
	H3800_ASIC2_KPIINTCLR  = mask;
}

static void h3800_mask_kpio_irq( unsigned int irq )
{
	u32 mask = kpio_irq_mask[irq - H3800_KPIO_IRQ_START];
	kpio_int_shadow &= ~mask;
	H3800_ASIC2_KPIINTSTAT = kpio_int_shadow;
}

static void h3800_unmask_kpio_irq( unsigned int irq )
{
	u32 mask = kpio_irq_mask[irq - H3800_KPIO_IRQ_START];
	kpio_int_shadow |= mask;
	H3800_ASIC2_KPIINTSTAT = kpio_int_shadow;
}

static void h3800_mask_ack_gpio_irq( unsigned int irq )
{
	u32 mask = gpio_irq_mask[irq - H3800_GPIO_IRQ_START];
	H3800_ASIC2_GPIINTSTAT &= ~mask;
	H3800_ASIC2_GPIINTCLR   = mask;
}

static void h3800_mask_gpio_irq( unsigned int irq )
{
	u32 mask = gpio_irq_mask[irq - H3800_GPIO_IRQ_START];
	H3800_ASIC2_GPIINTSTAT &= ~mask;
}

static void h3800_unmask_gpio_irq( unsigned int irq )
{
	u32 mask = gpio_irq_mask[irq - H3800_GPIO_IRQ_START];
	H3800_ASIC2_GPIINTSTAT |= mask;
}

static void __init h3800_init_irq( void )
{
	int i;

	/* Initialize standard IRQs */
	sa1100_init_irq();

	/* Disable all IRQs and set up clock */
	H3800_ASIC2_KPIINTSTAT     =  0;     /* Disable all interrupts */
	H3800_ASIC2_GPIINTSTAT     =  0;

	H3800_ASIC2_KPIINTCLR      =  0;     /* Clear all KPIO interrupts */
	H3800_ASIC2_GPIINTCLR      =  0;     /* Clear all GPIO interrupts */

//	H3800_ASIC2_KPIINTCLR      =  0xffff;     /* Clear all KPIO interrupts */
//	H3800_ASIC2_GPIINTCLR      =  0xffff;     /* Clear all GPIO interrupts */

	H3800_ASIC2_CLOCK_Enable       |= ASIC2_CLOCK_EX0;   /* 32 kHZ crystal on */
	H3800_ASIC2_INTR_ClockPrescale |= ASIC2_INTCPS_SET;
	H3800_ASIC2_INTR_ClockPrescale  = ASIC2_INTCPS_CPS(0x0e) | ASIC2_INTCPS_SET;
	H3800_ASIC2_INTR_TimerSet       = 1;

	for ( i = 0 ; i < H3800_KPIO_IRQ_COUNT ; i++ ) {
		int irq = i + H3800_KPIO_IRQ_START;
		irq_desc[irq].valid    = 1;
		irq_desc[irq].probe_ok = 0;
		irq_desc[irq].mask_ack = h3800_mask_ack_kpio_irq;
		irq_desc[irq].mask     = h3800_mask_kpio_irq;
		irq_desc[irq].unmask   = h3800_unmask_kpio_irq;
	}

	for ( i = 0 ; i < H3800_GPIO_IRQ_COUNT ; i++ ) {
		int irq = i + H3800_GPIO_IRQ_START;
		irq_desc[irq].valid    = 1;
		irq_desc[irq].probe_ok = 0;
		irq_desc[irq].mask_ack = h3800_mask_ack_gpio_irq;
		irq_desc[irq].mask     = h3800_mask_gpio_irq;
		irq_desc[irq].unmask   = h3800_unmask_gpio_irq;
	}

	/* Don't start up the ADC IRQ automatically */
	irq_desc[IRQ_H3800_ADC].noautoenable = 1;

	set_GPIO_IRQ_edge( GPIO_H3800_ASIC, GPIO_RISING_EDGE );
	setup_arm_irq( IRQ_GPIO_H3800_ASIC, &h3800_irq );
}


#define ASIC1_OUTPUTS    0x7fff   /* First 15 bits are used */

static void __init h3800_map_io(void)
{
	h3xxx_map_io();

	/* Add wakeup on AC plug/unplug */
	PWER  |= PWER_GPIO12;

	/* Initialize h3800-specific values here */
	GPCR = 0x0fffffff;       /* All outputs are set low by default */
	GAFR =  GPIO_H3800_CLK_OUT | 
		GPIO_LDD15 | GPIO_LDD14 | GPIO_LDD13 | GPIO_LDD12 |
		GPIO_LDD11 | GPIO_LDD10 | GPIO_LDD9  | GPIO_LDD8;
	GPDR =  GPIO_H3800_CLK_OUT | 
		GPIO_H3600_COM_RTS  | GPIO_H3600_L3_CLOCK |
		GPIO_H3600_L3_MODE  | GPIO_H3600_L3_DATA  |
		GPIO_LDD15 | GPIO_LDD14 | GPIO_LDD13 | GPIO_LDD12 |
		GPIO_LDD11 | GPIO_LDD10 | GPIO_LDD9  | GPIO_LDD8;
	TUCR =  TUCR_3_6864MHz;   /* Seems to be used only for the Bluetooth UART */

	/* Serial port 1 is used as GPIO */
	Ser1UTCR3 = 0;       /* Older bootloaders may set this */
	PPDR  = (PPDR & ~PPC_H3800_NSD_WP) | PPC_H3800_COM_DTR;
	PPSR &= ~PPC_H3800_COM_DTR;        /* Drive it low, which will give +5v to the port */

	/* Fix the memory bus */
	MSC2 = (MSC2 & 0x0000ffff) | 0xE4510000; 

	/* Set up ASIC #1 */
	H3800_ASIC1_GPIO_DIR            = ASIC1_OUTPUTS;            /* All outputs */
	H3800_ASIC1_GPIO_MASK           = ASIC1_OUTPUTS;            /* No interrupts */
	H3800_ASIC1_GPIO_SLEEP_MASK     = ASIC1_OUTPUTS;
	H3800_ASIC1_GPIO_SLEEP_DIR      = ASIC1_OUTPUTS;
	H3800_ASIC1_GPIO_SLEEP_OUT      = GPIO1_EAR_ON_N;
	H3800_ASIC1_GPIO_BATT_FAULT_DIR = ASIC1_OUTPUTS;
	H3800_ASIC1_GPIO_BATT_FAULT_OUT = GPIO1_EAR_ON_N;

	H3800_ASIC1_GPIO_OUT = GPIO1_IR_ON_N 
		                      | GPIO1_RS232_ON
		                      | GPIO1_EAR_ON_N;
	
	/* Set up ASIC #2 */
	H3800_ASIC2_GPIOPIOD    = GPIO2_IN_Y1_N | GPIO2_IN_X1_N;
	H3800_ASIC2_GPOBFSTAT   = GPIO2_IN_Y1_N | GPIO2_IN_X1_N;

	H3800_ASIC2_GPIODIR     = GPIO2_PEN_IRQ 
                                      | GPIO2_SD_DETECT
		                      | GPIO2_EAR_IN_N 
		                      | GPIO2_USB_DETECT_N 
		                      | GPIO2_SD_CON_SLT;

	/* TODO : Set sleep states & battery fault states */

	/* Clear VPP Enable */
	H3800_ASIC2_FlashWP_VPP_ON = 0;
	ipaq_model_ops = h3800_model_ops;
}

MACHINE_START(H3800, "HP iPAQ H3800")
	BOOT_MEM(0xc0000000, 0x80000000, 0xf8000000)
	BOOT_PARAMS(0xc0000100)
	MAPIO(h3800_map_io)
	INITIRQ(h3800_init_irq)
MACHINE_END
 
#endif /* CONFIG_SA1100_H3800 */

