/*
 * drivers/pcmcia/sa1100_bitsy.c
 *
 * PCMCIA implementation routines for Bitsy
 *
 */
#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 <asm/hardware.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/arch/pcmcia.h>
#include <asm/arch/h3600-sleeve.h>
#include <asm/arch/linkup-l1110.h>

static int debug = 0;

#if defined(CONFIG_MERCURY_BACKPAQ) || defined(CONFIG_MERCURY_BACKPAQ_MODULE)
#include <asm/arch/backpaq.h>

struct mercury_backpaq_sysctl *mercury_backpaq_sysctl = ((struct mercury_backpaq_sysctl *)BACKPAQ_SYSCTL_BASE);
static struct { 
  volatile short status;
  short pad1;
  volatile short versionid; 
  short pad2;
} *mercury_backpaq_socket[2];

#endif /* CONFIG_MERCURY_BACKPAQ */

#ifdef CONFIG_H3600_SLEEVE
static enum sleeve_device sleeve_type = SLEEVE_NO_DEVICE;
#else
/* default to behavior of PCMCIA sleeve (also works for CF) */
static enum sleeve_device sleeve_type = SINGLE_PCMCIA_SLEEVE;

static struct linkup_l1110 *dual_pcmcia_sleeve[2]; 


#endif

static struct pcmcia_init sa1100_bitsy_pcmcia_init;

static int bitsy_pcmcia_init(struct pcmcia_init *init)
{
        int irq, res;
	int nsockets = 1;

        sa1100_bitsy_pcmcia_init = *init;
        switch (sleeve_type) {
        case SLEEVE_NO_DEVICE: 
                break;

        case MERCURY_BACKPAQ: 
#if defined(CONFIG_MERCURY_BACKPAQ) || defined(CONFIG_MERCURY_BACKPAQ_MODULE)
        {
                int i;
                nsockets = 2;
                for (i = 0; i < 2; i++) {
                        if (mercury_backpaq_socket[i] == NULL) {
                                /* config and version appear in static memory bank 4 */
                                mercury_backpaq_socket[i] = (void*)(0xf3800300 + (i * 8));
                        }
                        if (0) printk(__FUNCTION__ ":%d &socket_status[%d]=%p status=%x versionid=%x\n",
                                      __LINE__, 
                                      i, mercury_backpaq_socket[i],
                                      mercury_backpaq_socket[i]->status,
                                      mercury_backpaq_socket[i]->versionid
                                );
                }
                set_bitsy_egpio(EGPIO_BITSY_OPT_ON);
        }
        goto common_init;
#endif
        case DUAL_PCMCIA_SLEEVE: {
                nsockets = 2;
                dual_pcmcia_sleeve[0] = (struct linkup_l1110 *)__ioremap(0x1a000000, PAGE_SIZE, 0);
                dual_pcmcia_sleeve[1] = (struct linkup_l1110 *)__ioremap(0x19000000, PAGE_SIZE, 0);
                if (0) printk(__FUNCTION__ ":%d &prc0=%x &prc1=%x\n",
                              __LINE__, 
                              dual_pcmcia_sleeve[0], dual_pcmcia_sleeve[1]);
                dual_pcmcia_sleeve[0]->prc = (LINKUP_PRC_S2|LINKUP_PRC_S1);
                dual_pcmcia_sleeve[1]->prc = (LINKUP_PRC_S2|LINKUP_PRC_S1|LINKUP_PRC_SSP);
                set_bitsy_egpio(EGPIO_BITSY_OPT_NVRAM_ON|EGPIO_BITSY_OPT_ON);
	} goto common_init;

        case SINGLE_COMPACTFLASH_SLEEVE:
        case SINGLE_PCMCIA_SLEEVE:
	common_init:
        /* Enable PCMCIA/CF bus: */
        set_bitsy_egpio(EGPIO_BITSY_OPT_NVRAM_ON);
        clr_bitsy_egpio(EGPIO_BITSY_OPT_RESET);
        clr_bitsy_egpio(EGPIO_BITSY_CARD_RESET);

        /* All those are inputs */
        GPDR &= ~(GPIO_BITSY_PCMCIA_CD0 | GPIO_BITSY_PCMCIA_CD1 | GPIO_BITSY_PCMCIA_IRQ0| GPIO_BITSY_PCMCIA_IRQ1);

        /* Set transition detect */
        set_GPIO_IRQ_edge( GPIO_BITSY_PCMCIA_CD0 | GPIO_BITSY_PCMCIA_CD1, GPIO_BOTH_EDGES );
        set_GPIO_IRQ_edge( GPIO_BITSY_PCMCIA_IRQ0| GPIO_BITSY_PCMCIA_IRQ1, GPIO_FALLING_EDGE );

        /* Register interrupts */
        irq = IRQ_GPIO_BITSY_PCMCIA_CD0;
        res = request_irq( irq, init->handler, SA_INTERRUPT, "PCMCIA_CD0", NULL );
        if( res < 0 ) goto irq_err;
        irq = IRQ_GPIO_BITSY_PCMCIA_CD1;
        res = request_irq( irq, init->handler, SA_INTERRUPT, "PCMCIA_CD1", NULL );
        if( res < 0 ) goto irq_err;
        }

        return 2;

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

static int bitsy_pcmcia_shutdown(void)
{
        if (0) printk(__FUNCTION__ "\n");
        switch (sleeve_type) {
        case DUAL_PCMCIA_SLEEVE: {
		__iounmap(dual_pcmcia_sleeve[0]);
		__iounmap(dual_pcmcia_sleeve[1]);
	} goto common_shutdown;
        case MERCURY_BACKPAQ:
		goto common_shutdown;
        case SINGLE_COMPACTFLASH_SLEEVE:
        case SINGLE_PCMCIA_SLEEVE:
	common_shutdown:
                /* disable IRQs */
                free_irq( IRQ_GPIO_BITSY_PCMCIA_CD0, NULL );
                free_irq( IRQ_GPIO_BITSY_PCMCIA_CD1, NULL );
  
                /* Disable CF bus: */
                clr_bitsy_egpio(EGPIO_BITSY_OPT_NVRAM_ON|EGPIO_BITSY_OPT_ON);
                set_bitsy_egpio(EGPIO_BITSY_OPT_RESET);
                break;
        case SLEEVE_NO_DEVICE:
        }

        return 0;
}

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

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

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

        switch (sleeve_type) {
        case SINGLE_PCMCIA_SLEEVE:
        case SINGLE_COMPACTFLASH_SLEEVE: 
        case DUAL_PCMCIA_SLEEVE: 
        case MERCURY_BACKPAQ: {
		int sock;
                levels=GPLR;

                state_array->state[0].detect=((levels & GPIO_BITSY_PCMCIA_CD0)==0)?1:0;
                state_array->state[0].ready=(levels & GPIO_BITSY_PCMCIA_IRQ0)?1:0;
                state_array->state[1].detect=((levels & GPIO_BITSY_PCMCIA_CD1)==0)?1:0;
                state_array->state[1].ready=(levels & GPIO_BITSY_PCMCIA_IRQ1)?1:0;

		for (sock = 0; sock < 2; sock++) { 
			if (sleeve_type == MERCURY_BACKPAQ) {
#if defined(CONFIG_MERCURY_BACKPAQ) || defined(CONFIG_MERCURY_BACKPAQ_MODULE)
				int status = mercury_backpaq_socket[sock]->status;
				int bvd1 = ((status & BACKPAQ_SOCKET_BVD1) ? 1 : 0);
				int bvd2 = ((status & BACKPAQ_SOCKET_BVD2) ? 1 : 0);
				if (debug) printk(__FUNCTION__ ":%d socket[1]->status=%x\n",
						  __LINE__, 
						  mercury_backpaq_socket[1]->status);
				state_array->state[sock].bvd1 = bvd1;
				state_array->state[sock].bvd2 = bvd2;
				state_array->state[sock].wrprot = 0;
                                if ((status & BACKPAQ_SOCKET_VS1) == 0)
                                   state_array->state[sock].vs_3v = 1;
                                if ((status & BACKPAQ_SOCKET_VS2) == 0)
                                   state_array->state[sock].vs_Xv = 1;
#endif
			} else if (sleeve_type == DUAL_PCMCIA_SLEEVE) {
				short prs = dual_pcmcia_sleeve[sock]->prc;
				state_array->state[sock].bvd1 = prs & LINKUP_PRS_BVD1;
				state_array->state[sock].bvd2 = prs & LINKUP_PRS_BVD2;
				state_array->state[sock].wrprot = 0;
                                if ((prs & LINKUP_PRS_VS1) == 0)
                                   state_array->state[sock].vs_3v = 1;
                                if ((prs & LINKUP_PRS_VS2) == 0)
                                   state_array->state[sock].vs_Xv = 1;
			} else {
				/* no bvd or vs bits on single pcmcia sleeve or CF sleeve */
				state_array->state[sock].bvd1=1;
				state_array->state[sock].bvd2=1;
				state_array->state[sock].wrprot=0; /* Not available on Bitsy. */
				state_array->state[sock].vs_3v=0;
				state_array->state[sock].vs_Xv=0;
			}
		}
        } break;

        case SLEEVE_NO_DEVICE:
        default:
                break;
        }
        return 1;
}

static int bitsy_pcmcia_get_irq_info(struct pcmcia_irq_info *info)
{

        switch (info->sock) {
        case 0:
                info->irq=IRQ_GPIO_BITSY_PCMCIA_IRQ0;
                break;
        case 1:
                info->irq=IRQ_GPIO_BITSY_PCMCIA_IRQ1;
                break;
        default:
                return -1;
        }
        return 0;
}

static int bitsy_pcmcia_configure_socket(const struct pcmcia_configure *configure)
{

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

        if (sleeve_type != SLEEVE_NO_DEVICE)
                printk(__FUNCTION__ ": socket=%d vcc=%d vpp=%d reset=%d\n", 
                       sock, configure->vcc, configure->vpp, configure->reset);

        switch (sleeve_type) {
        case SINGLE_COMPACTFLASH_SLEEVE:
        case SINGLE_PCMCIA_SLEEVE: { 
                unsigned long flags;

                save_flags_cli(flags);

                switch (configure->vcc) {
                case 0:
                        clr_bitsy_egpio(EGPIO_BITSY_OPT_ON);
                        break;

                case 50:
                case 33:
                        set_bitsy_egpio(EGPIO_BITSY_OPT_ON);
                        break;

                default:
                        printk(KERN_ERR "%s(): unrecognized Vcc %u\n", __FUNCTION__,
                               configure->vcc);
                        restore_flags(flags);
                        return -1;
                }

                if (configure->reset)
                        set_bitsy_egpio(EGPIO_BITSY_CARD_RESET);
                else
                        clr_bitsy_egpio(EGPIO_BITSY_CARD_RESET);
                restore_flags(flags);
        } break;

        case DUAL_PCMCIA_SLEEVE: {
                unsigned long flags;
		unsigned int prc = 
			(LINKUP_PRC_APOE | LINKUP_PRC_SOE | LINKUP_PRC_S1 | LINKUP_PRC_S2
			 | (sock * LINKUP_PRC_SSP));
                save_flags_cli(flags);

		/* Linkup Systems L1110 with TI TPS2205 PCMCIA Power Switch */
		/* S1 is VCC5#, S2 is VCC3# */ 
		/* S3 is VPP_VCC, S4 is VPP_PGM */
		/* PWR_ON is wired to #SHDN */
                switch (configure->vcc) {
                case 0:
                        break;
                case 50:
			prc &= ~LINKUP_PRC_S1;
			break;
                case 33:
			prc &= ~LINKUP_PRC_S2;
                        break;
                default:
                        printk(KERN_ERR "%s(): unrecognized Vcc %u\n", __FUNCTION__,
                               configure->vcc);
                        restore_flags(flags);
                        return -1;
                }
		if (configure->vpp == 12) {
			prc |= LINKUP_PRC_S4;
		} else if (configure->vpp == configure->vcc) {
			prc |= LINKUP_PRC_S3;
		}

                if (configure->reset)
                        prc |= LINKUP_PRC_RESET;

		dual_pcmcia_sleeve[sock]->prc = prc;

                restore_flags(flags);
	} break;

        case MERCURY_BACKPAQ: {
#if defined(CONFIG_MERCURY_BACKPAQ) || defined(CONFIG_MERCURY_BACKPAQ_MODULE)
                unsigned long flags;

		int shift = (sock ? 4 : 0);
	        int mask = 0xf << shift;
		int val = 0;

                save_flags_cli(flags);

                switch (configure->vcc) {
                case 0:
                        break;
                case 50:
			val |= (BACKPAQ_PCMCIA_A5VEN << shift);
			val |= BACKPAQ_PWRCTL_50VEN;
                        break;
                case 33:
			val |= (BACKPAQ_PCMCIA_A3VEN << shift);
                        break;
                default:
                        printk(KERN_ERR "%s(): unrecognized Vcc %u\n", __FUNCTION__,
                               configure->vcc);
                        restore_flags(flags);
                        return -1;
                }

                switch (configure->vpp) {
                case 0:
                        break;
                case 50:
                        break;
                case 33:
                        break;
                default:
                        printk(KERN_ERR "%s(): unrecognized Vpp %u\n", __FUNCTION__,
                               configure->vpp);
                        restore_flags(flags);
                        return -1;
                }

                if (0) printk("     mercury_backpaq_sysctl->pcmcia_power=%x mask=%x val=%x vcc=%d\n", 
                              mercury_backpaq_sysctl->pcmcia_power, mask, val, configure->vcc);
                mercury_backpaq_sysctl->pcmcia_power &= ~(short) mask;
                mercury_backpaq_sysctl->pcmcia_power |= val;

                if (configure->reset)
                        set_bitsy_egpio(EGPIO_BITSY_CARD_RESET);
                else
                        clr_bitsy_egpio(EGPIO_BITSY_CARD_RESET);
                restore_flags(flags);
#endif /* CONFIG_MERCURY_BACKPAQ */
        } break;

        case SLEEVE_NO_DEVICE: 
           printk("    no pcmcia sleeve\n");
        }

        /* silently ignore vpp and speaker enables. */

        return 0;
}

struct pcmcia_low_level bitsy_pcmcia_ops = { 
        bitsy_pcmcia_init,
        bitsy_pcmcia_shutdown,
        bitsy_pcmcia_socket_state,
        bitsy_pcmcia_get_irq_info,
        bitsy_pcmcia_configure_socket
};

void sa1100_bitsy_change_sleeves(enum sleeve_device new_sleeve_type) 
{
        if (sleeve_type == new_sleeve_type)
                return;
        if (sleeve_type != SLEEVE_NO_DEVICE)
                bitsy_pcmcia_shutdown();
        sleeve_type = new_sleeve_type;
        if (sa1100_bitsy_pcmcia_init.handler != NULL) {
                bitsy_pcmcia_init(&sa1100_bitsy_pcmcia_init);
        }
}
EXPORT_SYMBOL(sa1100_bitsy_change_sleeves);

