/*
 * drivers/pcmcia/sa1100_trizeps.c
 *
 * PCMCIA implementation routines for Trizeps
 *
 * Authors:
 * Andreas Hofer <ho@dsa-ac.de>,
 * Peter Lueg <pl@dsa-ac.de>,
 * Guennadi Liakhovetski <gl@dsa-ac.de>
 *
 */
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/init.h>

#include <asm/hardware.h>   // included trizeps.h
#include <asm/mach-types.h>
#include <asm/system.h>
#include <asm/irq.h>
#include "sa1100_generic.h"

#define NUMBER_OF_TRIZEPS_PCMCIA_SLOTS 1
/**
 *
 *
 ******************************************************/
static int trizeps_pcmcia_init(struct pcmcia_init *init)
{
	int res;

	/* Enable CF bus: */
	TRIZEPS_BCR_clear(TRIZEPS_BCR1, TRIZEPS_nPCM_ENA_REG);

	/* All those are inputs */
	GPDR &= ~((GPIO_GPIO(TRIZEPS_GPIO_PCMCIA_CD0))
		    | (GPIO_GPIO(TRIZEPS_GPIO_PCMCIA_IRQ0)));

	/* Set transition detect */
//	set_irq_type(SA1100_GPIO_TO_IRQ(GPIO_TRIZEPS_PCMCIA_CD0), IRQT_BOTHEDGE);
	set_irq_type(TRIZEPS_IRQ_PCMCIA_IRQ0, IRQT_FALLING);

	/* Register SOCKET interrupts */
	/* WHY? */
	set_irq_type(TRIZEPS_IRQ_PCMCIA_CD0, IRQT_NOEDGE);
	res = request_irq( TRIZEPS_IRQ_PCMCIA_CD0, init->handler, SA_INTERRUPT, "PCMCIA_CD0", NULL );
	if( res < 0 ) goto irq_err;

	//MECR = 0x00060006; // Initialised on trizeps init

	// return=sa1100_pcmcia_socket_count (sa1100_generic.c)
	//        -> number of PCMCIA Slots
	// Slot 0 -> Trizeps PCMCIA
	// Slot 1 -> Trizeps ISA-Bus
	return NUMBER_OF_TRIZEPS_PCMCIA_SLOTS;

 irq_err:
	printk( KERN_ERR __FUNCTION__ ": PCMCIA Request for IRQ %u failed\n", TRIZEPS_IRQ_PCMCIA_CD0 );
	return -1;
}

/**
 *
 *
 ******************************************************/
static int trizeps_pcmcia_shutdown(void)
{
	printk(">>>>>PCMCIA TRIZEPS shutdown\n");
	/* disable IRQs */
	free_irq(TRIZEPS_IRQ_PCMCIA_CD0, NULL );

	/* Disable CF bus: */
	TRIZEPS_BCR_set(TRIZEPS_BCR1, TRIZEPS_nPCM_ENA_REG);

	return 0;
}

/**
 *

 ******************************************************/
static int trizeps_pcmcia_socket_state(struct pcmcia_state_array
				       *state_array){
	unsigned long levels;

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

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

	levels = GPLR;

	state_array->state[0].detect = ((levels & GPIO_GPIO(TRIZEPS_GPIO_PCMCIA_CD0)) == 0) ? 1 : 0;
	state_array->state[0].ready  = ((levels & GPIO_GPIO(TRIZEPS_GPIO_PCMCIA_IRQ0)) != 0) ? 1 : 0;
	state_array->state[0].bvd1   = ((TRIZEPS_BCR1 & TRIZEPS_PCM_BVD1) !=0 ) ? 1 : 0;
	state_array->state[0].bvd2   = ((TRIZEPS_BCR1 & TRIZEPS_PCM_BVD2) != 0) ? 1 : 0;
	state_array->state[0].wrprot = 0; // not write protected
	state_array->state[0].vs_3v  = ((TRIZEPS_BCR1 & TRIZEPS_nPCM_VS1) == 0) ? 1 : 0; //VS1=0 -> vs_3v=1
	state_array->state[0].vs_Xv  = ((TRIZEPS_BCR1 & TRIZEPS_nPCM_VS2) == 0) ? 1 : 0; //VS2=0 -> vs_Xv=1

	return 1;  // success
}

/**
 *
 *
 ******************************************************/
static int trizeps_pcmcia_get_irq_info(struct pcmcia_irq_info *info){

	switch (info->sock) {
	case 0:
		info->irq=TRIZEPS_IRQ_PCMCIA_IRQ0;
		break;
	case 1:
		// MFTB2 use only one Slot
	default:
		return -1;
	}
	return 0;
}

/**
 *
 *
 ******************************************************/
static int trizeps_pcmcia_configure_socket(const struct pcmcia_configure *configure)
{
	unsigned long flags;

	if(configure->sock>1) return -1;

	local_irq_save(flags);

	switch (configure->vcc) {
	case 0:
		printk(">>> PCMCIA Power off\n");
		TRIZEPS_BCR_clear(TRIZEPS_BCR1, TRIZEPS_PCM_V3_EN_REG);
		TRIZEPS_BCR_clear(TRIZEPS_BCR1, TRIZEPS_PCM_V5_EN_REG);
		break;

	case 33:
		// 3.3V Power on
		TRIZEPS_BCR_clear(TRIZEPS_BCR1, TRIZEPS_PCM_V3_EN_REG);
		TRIZEPS_BCR_set(TRIZEPS_BCR1, TRIZEPS_PCM_V5_EN_REG);
		break;
	case 50:
		// 5.0V Power on
		TRIZEPS_BCR_set(TRIZEPS_BCR1, TRIZEPS_PCM_V3_EN_REG);
		TRIZEPS_BCR_clear(TRIZEPS_BCR1, TRIZEPS_PCM_V5_EN_REG);
		break;
	default:
		printk(KERN_ERR "%s(): unrecognized Vcc %u\n", __FUNCTION__,
		       configure->vcc);
		local_irq_restore(flags);
		return -1;
	}

	if (configure->reset)
		TRIZEPS_BCR_set(TRIZEPS_BCR1, TRIZEPS_nPCM_RESET_DISABLE);   // Reset
	else
		TRIZEPS_BCR_clear(TRIZEPS_BCR1, TRIZEPS_nPCM_RESET_DISABLE); // no Reset
	/*
	  printk(" vcc=%u vpp=%u -->reset=%i\n",
	  configure->vcc,
	  configure->vpp,
	  ((BCR_read(1) & nPCM_RESET_DISABLE)? 1:0));
	*/
	local_irq_restore(flags);

	if (configure->irq) {
		enable_irq(TRIZEPS_IRQ_PCMCIA_CD0);
		enable_irq(TRIZEPS_IRQ_PCMCIA_IRQ0);
	} else {
		disable_irq(TRIZEPS_IRQ_PCMCIA_IRQ0);
		disable_irq(TRIZEPS_IRQ_PCMCIA_CD0);
	}

	return 0;
}

static int trizeps_pcmcia_socket_init(int sock)
{
	return 0;
}

static int trizeps_pcmcia_socket_suspend(int sock)
{
	return 0;
}

/**
 * low-level PCMCIA interface
 *
 ******************************************************/
struct pcmcia_low_level trizeps_pcmcia_ops = {
	.init			= trizeps_pcmcia_init,
	.shutdown		= trizeps_pcmcia_shutdown,
	.socket_state		= trizeps_pcmcia_socket_state,
	.get_irq_info		= trizeps_pcmcia_get_irq_info,
	.configure_socket	= trizeps_pcmcia_configure_socket,
	.socket_init		= trizeps_pcmcia_socket_init,
	.socket_suspend		= trizeps_pcmcia_socket_suspend,
};

int __init pcmcia_trizeps_init(void)
{
	if (machine_is_trizeps()) {
		return sa1100_register_pcmcia(&trizeps_pcmcia_ops);
	}
	return -ENODEV;
}

void __exit pcmcia_trizeps_exit(void)
{
	sa1100_unregister_pcmcia(&trizeps_pcmcia_ops);
}
