/*
 * drivers/pcmcia/sa1100_h3600.c
 *
 * PCMCIA implementation routines for H3600
 * All true functionality is shuttled off to the
 * pcmcia implementation for the current sleeve
 */
#include <linux/config.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/i2c.h>
#include <linux/pm.h>
#include <linux/sysctl.h>

#include <asm/hardware.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/arch-sa1100/h3600-sleeve.h>

#ifdef CONFIG_PXA_IPAQ
/* nasty ... */
#define GPIO_H3600_PCMCIA_CD0		GPIO_H3900_PCMCIA_CD0
#define GPIO_H3600_PCMCIA_CD1		GPIO_H3900_PCMCIA_CD1
#define GPIO_H3600_PCMCIA_IRQ0		GPIO_H3900_PCMCIA_IRQ0
#define GPIO_H3600_PCMCIA_IRQ1		GPIO_H3900_PCMCIA_IRQ1

#define IRQ_GPIO_H3600_PCMCIA_CD0	IRQ_GPIO_H3900_PCMCIA_CD0
#define IRQ_GPIO_H3600_PCMCIA_CD1	IRQ_GPIO_H3900_PCMCIA_CD1
#define IRQ_GPIO_H3600_PCMCIA_IRQ0	IRQ_GPIO_H3900_PCMCIA_IRQ0
#define IRQ_GPIO_H3600_PCMCIA_IRQ1	IRQ_GPIO_H3900_PCMCIA_IRQ1

#include <pcmcia/ss.h>
#include <asm/arch/pcmcia.h>
#else
#include "sa1100_generic.h"
#endif

static struct pcmcia_init  stored_init; // Store the interrupt handler
struct pcmcia_low_level   *sleeve_pcmcia_ops = NULL; // Initialize with no sleeve

int timing_increment_ns = 0;
static unsigned int verbose = 0;
EXPORT_SYMBOL(timing_increment_ns);

extern int pcmcia_handle_pm_event(struct pm_dev *, pm_request_t, void *);

/* Forward declaractions */
int h3600_common_pcmcia_get_irq_info( struct pcmcia_irq_info *info );

/***********************************************/
/* /proc/sys/bus/pcmcia/...                    */
/***********************************************/

static struct ctl_table pcmcia_ops_table[] = 
{
	{1, "timing_increment_ns", &timing_increment_ns, sizeof(int), 0644, NULL, &proc_dointvec },
	{2, "verbose",             &verbose,             sizeof(int), 0644, NULL, &proc_dointvec },
	{0}
};

static struct ctl_table pcmcia_table[] = 
{
	{BUS_PCMCIA, "pcmcia", NULL, 0, 0555, pcmcia_ops_table},
	{0}
};
static struct ctl_table bus_dir_table[] = 
{
	{CTL_BUS, "bus", NULL, 0, 0555, pcmcia_table},
        {0}
};

static struct ctl_table_header *pcmcia_bus_ctl_table_header = NULL;

/***********************************************/
/* Stub routines called by the PCMCIA device.  */
/***********************************************/

static int h3600_pcmcia_init(struct pcmcia_init *init)
{
        SLEEVE_DEBUG(1, "init=%p ops=%p\n", init, sleeve_pcmcia_ops);
	stored_init = *init;
        pcmcia_bus_ctl_table_header = register_sysctl_table(bus_dir_table, 0);
	return 2;   /* Return by default */
}

static int h3600_pcmcia_shutdown(void)
{
        SLEEVE_DEBUG(1, "ops=%p\n", sleeve_pcmcia_ops);
        unregister_sysctl_table(pcmcia_bus_ctl_table_header);
	return 0;   /* Not examined */
}

static int h3600_pcmcia_socket_state_stub(struct pcmcia_state_array *state_array)
{
/*	SLEEVE_DEBUG(1, "ops=%p\n", sleeve_pcmcia_ops); */

	if ( sleeve_pcmcia_ops != NULL 
	     && sleeve_pcmcia_ops->socket_state != NULL )
		return sleeve_pcmcia_ops->socket_state( state_array );

	/* Default actions */
        if ( state_array->size < 2 ) 
		return -1;

        memset(state_array->state, 0, (state_array->size)*sizeof(struct pcmcia_state));
	return 0;
}

static int h3600_pcmcia_get_irq_info_stub(struct pcmcia_irq_info *info)
{
	if ( sleeve_pcmcia_ops != NULL 
	     && sleeve_pcmcia_ops->get_irq_info != NULL )
		return sleeve_pcmcia_ops->get_irq_info( info );

	return
		h3600_common_pcmcia_get_irq_info( info );
}

#ifdef CONFIG_PXA_IPAQ
static int h3600_pcmcia_configure_socket_stub(unsigned int sock, socket_state_t *state)
{
	SLEEVE_DEBUG(1, "%d %p\n", sock, state);

	if ( sleeve_pcmcia_ops != NULL 
	     && sleeve_pcmcia_ops->configure_socket != NULL )
		return sleeve_pcmcia_ops->configure_socket(sock, state);

        return 0;
}
#else
static int h3600_pcmcia_configure_socket_stub(const struct pcmcia_configure *configure)
{
	SLEEVE_DEBUG(1, "%p\n", configure);

	if ( sleeve_pcmcia_ops != NULL 
	     && sleeve_pcmcia_ops->configure_socket != NULL )
		return sleeve_pcmcia_ops->configure_socket( configure );

        return 0;
}
#endif

static int h3600_pcmcia_socket_init_stub(int sock)
{
	SLEEVE_DEBUG(1, "%d\n", sock);

	if ( sleeve_pcmcia_ops != NULL 
	     && sleeve_pcmcia_ops->socket_init != NULL )
		return sleeve_pcmcia_ops->socket_init( sock );

        return 0;
}

static int h3600_pcmcia_socket_suspend_stub(int sock)
{
	SLEEVE_DEBUG(1, "%d\n", sock);

	if ( sleeve_pcmcia_ops != NULL 
	     && sleeve_pcmcia_ops->socket_suspend != NULL )
		return sleeve_pcmcia_ops->socket_suspend( sock );

        return 0;
}

static int h3600_pcmcia_socket_get_timing_stub(unsigned int sock, unsigned int cpu_speed, unsigned int cmd_time)
{
	if ( sleeve_pcmcia_ops != NULL 
	     && sleeve_pcmcia_ops->socket_get_timing != NULL )
		return sleeve_pcmcia_ops->socket_get_timing( sock, cpu_speed, cmd_time );

        return 0;
}

struct pcmcia_low_level h3600_pcmcia_ops = { 
        init:             h3600_pcmcia_init,
        shutdown:         h3600_pcmcia_shutdown,
        socket_state:     h3600_pcmcia_socket_state_stub,
        get_irq_info:     h3600_pcmcia_get_irq_info_stub,
        configure_socket: h3600_pcmcia_configure_socket_stub,
        socket_init:      h3600_pcmcia_socket_init_stub,
        socket_suspend:   h3600_pcmcia_socket_suspend_stub,
        socket_get_timing: h3600_pcmcia_socket_get_timing_stub,
};

/****************************************************/
/*  Swapping functions for PCMCIA operations        */
/****************************************************/

void h3600_pcmcia_change_sleeves(struct pcmcia_low_level *ops)
{
	if ( ops != sleeve_pcmcia_ops ) {
		if ( sleeve_pcmcia_ops && sleeve_pcmcia_ops->shutdown )
			sleeve_pcmcia_ops->shutdown();
		sleeve_pcmcia_ops = ops;
		if ( sleeve_pcmcia_ops && sleeve_pcmcia_ops->init )
			sleeve_pcmcia_ops->init( &stored_init );
	}
}
  
void h3600_pcmcia_remove_sleeve( void )
{
	if ( sleeve_pcmcia_ops != NULL ) {
		if ( sleeve_pcmcia_ops && sleeve_pcmcia_ops->shutdown )
			sleeve_pcmcia_ops->shutdown();
		sleeve_pcmcia_ops = NULL;
	}
}

void h3600_pcmcia_suspend_sockets( void )
{
        pcmcia_handle_pm_event( NULL, PM_SUSPEND, NULL );
}

void h3600_pcmcia_resume_sockets( void )
{
        pcmcia_handle_pm_event( NULL, PM_RESUME, NULL );
}

EXPORT_SYMBOL(h3600_pcmcia_change_sleeves);
EXPORT_SYMBOL(h3600_pcmcia_remove_sleeve);
EXPORT_SYMBOL(h3600_pcmcia_suspend_sockets);
EXPORT_SYMBOL(h3600_pcmcia_resume_sockets);



/****************************************************/
/*  Common functions used by the different sleeves */
/****************************************************/

int h3600_common_pcmcia_init( struct pcmcia_init *init )
{
	int egpio_nr, res;

	SLEEVE_DEBUG(1,"\n");

	/* Enable PCMCIA/CF bus: */
	clr_h3600_egpio(IPAQ_EGPIO_OPT_RESET);
	clr_h3600_egpio(IPAQ_EGPIO_CARD_RESET);

	/* Set transition detect */
	res = h3600_egpio_set_irq_type( IPAQ_EGPIO_PCMCIA_IRQ0, IRQT_FALLING );
	if (res)
		printk("%s: h3600_egpio_set_irq_type failed for egpio_nr=%d type=%d\n", 
		       __FUNCTION__, IPAQ_EGPIO_PCMCIA_IRQ0, IRQT_FALLING );
	res = h3600_egpio_set_irq_type( IPAQ_EGPIO_PCMCIA_IRQ1, IRQT_FALLING );
	if (res)
		printk("%s: h3600_egpio_set_irq_type failed for egpio_nr=%d type=%d\n", 
		       __FUNCTION__, IPAQ_EGPIO_PCMCIA_IRQ1, IRQT_FALLING );

	if (init->handler) {

		res = h3600_egpio_set_irq_type( IPAQ_EGPIO_PCMCIA_CD0_N, IRQT_BOTHEDGE );
		if (res)
			printk("%s: h3600_egpio_set_irq_type failed for egpio_nr=%d type=%d\n", 
			       __FUNCTION__, IPAQ_EGPIO_PCMCIA_CD0_N, IRQT_BOTHEDGE );
		res = h3600_egpio_set_irq_type( IPAQ_EGPIO_PCMCIA_CD1_N, IRQT_BOTHEDGE );
		if (res)
			printk("%s: h3600_egpio_set_irq_type failed for egpio_nr=%d type=%d\n", 
			       __FUNCTION__, IPAQ_EGPIO_PCMCIA_CD1_N, IRQT_BOTHEDGE );

		/* Register interrupts */
		egpio_nr = IPAQ_EGPIO_PCMCIA_CD0_N;
		res = h3600_egpio_request_irq( egpio_nr, init->handler, SA_INTERRUPT, "PCMCIA_CD0", NULL );
		if( res < 0 ) { 
			printk( KERN_ERR "%s: Request for EGPIO=%d IRQ %u failed, rc=%d\n", __FUNCTION__, 
				egpio_nr, h3600_egpio_irq_number(egpio_nr),  res );
			if (0) return -1;
		}

		egpio_nr = IPAQ_EGPIO_PCMCIA_CD1_N;
		res = h3600_egpio_request_irq( egpio_nr, init->handler, SA_INTERRUPT, "PCMCIA_CD1", NULL );
		if( res < 0 ) { 
			printk( KERN_ERR "%s: Request for EGPIO=%d IRQ %u failed, rc=%d\n", __FUNCTION__, 
				egpio_nr, h3600_egpio_irq_number(egpio_nr),  res );
			if (0) return -1;
		}
	}

	return 2;  /* Always allow for two PCMCIA devices */
}

int h3600_common_pcmcia_shutdown( void )
{
	SLEEVE_DEBUG(1,"\n");

	/* disable IRQs */
	h3600_egpio_free_irq( IPAQ_EGPIO_PCMCIA_CD0_N, NULL );
	h3600_egpio_free_irq( IPAQ_EGPIO_PCMCIA_CD1_N, NULL );
  
	return 0;
}

int h3600_common_pcmcia_socket_state( struct pcmcia_state_array *state_array )
{

        if (state_array->size < 2) 
		return -1;

        memset(state_array->state, 0, (state_array->size)*sizeof(struct pcmcia_state));
	
	state_array->state[0].detect=(read_h3600_egpio(IPAQ_EGPIO_PCMCIA_CD0_N)==0)?1:0;
	state_array->state[0].ready=(read_h3600_egpio(IPAQ_EGPIO_PCMCIA_IRQ0))?1:0;
	state_array->state[1].detect=(read_h3600_egpio(IPAQ_EGPIO_PCMCIA_CD1_N)==0)?1:0;
	state_array->state[1].ready=(read_h3600_egpio(IPAQ_EGPIO_PCMCIA_IRQ1))?1:0;

	return 0;
}

int h3600_common_pcmcia_get_irq_info( struct pcmcia_irq_info *info )
{
        switch (info->sock) {
        case 0:
                info->irq=h3600_egpio_irq_number(IPAQ_EGPIO_PCMCIA_IRQ0);
                break;
        case 1:
                info->irq=h3600_egpio_irq_number(IPAQ_EGPIO_PCMCIA_IRQ1);
                break;
        default:
                return -1;
        }
        return 0;
}

int h3600_common_pcmcia_socket_init(int sock)
{
	SLEEVE_DEBUG(1, "%d\n", sock);

	/* Enable CF bus: */
	clr_h3600_egpio(IPAQ_EGPIO_OPT_RESET);

	set_current_state(TASK_UNINTERRUPTIBLE);
	schedule_timeout(10*HZ / 1000);

	switch (sock) {
	case 0:
		h3600_egpio_set_irq_type(IPAQ_EGPIO_PCMCIA_IRQ0, IRQT_FALLING);
		h3600_egpio_set_irq_type(IPAQ_EGPIO_PCMCIA_CD0_N, IRQT_BOTHEDGE);
		break;
	case 1:
		h3600_egpio_set_irq_type(IPAQ_EGPIO_PCMCIA_IRQ1, IRQT_FALLING);
		h3600_egpio_set_irq_type(IPAQ_EGPIO_PCMCIA_CD1_N, IRQT_BOTHEDGE);
		break;
	}

	return 0;
}

int h3600_common_pcmcia_socket_suspend(int sock)
{
	SLEEVE_DEBUG(1, "%d\n", sock);

	switch (sock) {
	case 0:
		h3600_egpio_set_irq_type(IPAQ_EGPIO_PCMCIA_IRQ0, IRQT_NOEDGE);
		h3600_egpio_set_irq_type(IPAQ_EGPIO_PCMCIA_CD0_N, IRQT_NOEDGE);
		break;
	case 1:
		h3600_egpio_set_irq_type(IPAQ_EGPIO_PCMCIA_IRQ1, IRQT_NOEDGE);
		h3600_egpio_set_irq_type(IPAQ_EGPIO_PCMCIA_CD1_N, IRQT_NOEDGE);
		break;
	}

	return 0;
}

/* This function implements the BS value calculation for setting the MECR
 * using integer arithmetic:
 */

static inline unsigned int pcmcia_mecr_bs(unsigned int pcmcia_cycle_ns,
						 unsigned int cpu_clock_khz)
{
	unsigned int t = ((pcmcia_cycle_ns * cpu_clock_khz) / 6) - 1000000;
	return (t / 1000000) + (((t % 1000000) == 0) ? 0 : 1);
}

int h3600_common_pcmcia_mecr_timing(unsigned int sock, unsigned int cpu_speed, 
					   unsigned int cmd_time )
{
        unsigned int timing = pcmcia_mecr_bs( cmd_time + timing_increment_ns, cpu_speed );
	SLEEVE_DEBUG(1, "sock=%d cpu_speed=%d cmd_time=%d timing=%x\n",
		     sock, cpu_speed, cmd_time + timing_increment_ns, timing);
        return timing;
}


EXPORT_SYMBOL(h3600_common_pcmcia_init);
EXPORT_SYMBOL(h3600_common_pcmcia_shutdown);
EXPORT_SYMBOL(h3600_common_pcmcia_socket_state);
EXPORT_SYMBOL(h3600_common_pcmcia_get_irq_info);
EXPORT_SYMBOL(h3600_common_pcmcia_socket_init);
EXPORT_SYMBOL(h3600_common_pcmcia_socket_suspend);
EXPORT_SYMBOL(h3600_common_pcmcia_mecr_timing);
EXPORT_SYMBOL(h3600_pcmcia_ops);
