/*
 * 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/module.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/device.h>
#include <linux/init.h>

#include <asm/hardware.h>
#include <asm/arch/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 struct pcmcia_irqs irqs[] = {
	{ 0, TRIZEPS_IRQ_PCMCIA_CD0, "PCMCIA_CD0" },
};

/**
 *
 *
 ******************************************************/
static int trizeps_pcmcia_init(struct soc_pcmcia_socket *skt)
{
	skt->irq = TRIZEPS_IRQ_PCMCIA_IRQ0;

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

	return soc_pcmcia_request_irqs(skt, irqs, ARRAY_SIZE(irqs));
}

/**
 *
 *
 ******************************************************/
static void trizeps_pcmcia_shutdown(struct soc_pcmcia_socket *skt)
{
	printk(">>>>>PCMCIA TRIZEPS shutdown\n");

	soc_pcmcia_free_irqs(skt, irqs, ARRAY_SIZE(irqs));

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

/**
 *
 ******************************************************/
static void
trizeps_pcmcia_socket_state(struct soc_pcmcia_socket *skt,
			    struct pcmcia_state *state_array)
{
	unsigned long levels = GPLR;

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

/**
 *
 *
 ******************************************************/
static int
trizeps_pcmcia_configure_socket(struct soc_pcmcia_socket *skt,
				const socket_state_t *state)
{
	unsigned long flags;

	local_irq_save(flags);

	switch (state->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__,
		       state->Vcc);
		local_irq_restore(flags);
		return -1;
	}

	if (state->flags & SS_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",
	  state->Vcc,
	  state->Vpp,
	  ((BCR_read(1) & nPCM_RESET_DISABLE)? 1:0));
	*/
	local_irq_restore(flags);

	return 0;
}

static void trizeps_pcmcia_socket_init(struct soc_pcmcia_socket *skt)
{
	soc_pcmcia_enable_irqs(skt, irqs, ARRAY_SIZE(irqs));
}

static void trizeps_pcmcia_socket_suspend(struct soc_pcmcia_socket *skt)
{
	soc_pcmcia_disable_irqs(skt, irqs, ARRAY_SIZE(irqs));
}

/**
 * low-level PCMCIA interface
 *
 ******************************************************/
struct pcmcia_low_level trizeps_pcmcia_ops = {
	.owner			= THIS_MODULE,
	.hw_init		= trizeps_pcmcia_hw_init,
	.hw_shutdown		= trizeps_pcmcia_hw_shutdown,
	.socket_state		= trizeps_pcmcia_socket_state,
	.configure_socket	= trizeps_pcmcia_configure_socket,
	.socket_init		= trizeps_pcmcia_socket_init,
	.socket_suspend		= trizeps_pcmcia_socket_suspend,
};

int __init pcmcia_trizeps_init(struct device *dev)
{
	int ret = -ENODEV;

	if (machine_is_trizeps())
		ret = sa11xx_drv_pcmcia_probe(dev, &trizeps_pcmcia_ops, 0,
					      NUMBER_OF_TRIZEPS_PCMCIA_SLOTS);

	return ret;
}
