/*
 * drivers/pcmcia/sa1100_backpaq.c
 *
 * PCMCIA implementation routines for Compaq CRL Mercury BackPAQ
 *
 */
#include <linux/config.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/sysctl.h>

#include <linux/mtd/mtd.h>
#include <linux/mtd/map.h>
#include <linux/mtd/partitions.h>

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

#include <asm/arch/backpaq.h>

static long debug;

static struct mercury_backpaq_sysctl *mercury_backpaq_sysctl = ((struct mercury_backpaq_sysctl *)BACKPAQ_SYSCTL_BASE);

extern void sa1100_bitsy_change_sleeves(int);
static int __devinit cf_probe_sleeve(struct sleeve_dev *sleeve_dev, const struct sleeve_device_id *ent)
{
        sprintf(sleeve_dev->name, "CompactFlash Sleeve");
        printk(__FUNCTION__ ": dev->name=%s\n", sleeve_dev->name);
#if 1
        sa1100_bitsy_change_sleeves(SINGLE_COMPACTFLASH_SLEEVE);
#endif
        return 0;
}

static void __devexit cf_remove_sleeve(struct sleeve_dev *sleeve_dev)
{
        printk(__FUNCTION__ ": dev->name=%s\n", sleeve_dev->driver->name);
#if 1
        sa1100_bitsy_change_sleeves(SLEEVE_NO_DEVICE);
#endif
}

static struct sleeve_device_id cf_tbl[] __devinitdata = {
        { COMPAQ_VENDOR_ID, SINGLE_COMPACTFLASH_SLEEVE, 0, 0, 0 },
        { 0, }
};

static struct sleeve_driver cf_driver = {
        name:   "Compaq Compact Flash Sleeve",
        id_table: cf_tbl,
        probe: cf_probe_sleeve,
        remove: cf_remove_sleeve,
};

static struct mtd_partition pcmcia_sleeve_flash_partitions[] = {
	{
		name: "PCMCIA Sleeve Flash",
		offset: 0x00000000,
		size: 0  /* will expand to the end of the flash */
	}
};

#define NB_OF(x)  (sizeof(x)/sizeof(x[0]))

#define WINDOW_ADDR 0xf1000000 /* static memory bank 1 */

static int offset_multiplier = 1; /* 2 for rev-A backpaqs -- don't ask -- Jamey 2/12/2001 */

static __u8 sa1100_read8(struct map_info *map, unsigned long ofs)
{
	return *(__u8 *)(WINDOW_ADDR + ofs*offset_multiplier);
}

static __u16 sa1100_read16(struct map_info *map, unsigned long ofs)
{
        __u16 d = *(__u16 *)(WINDOW_ADDR + ofs*offset_multiplier);
        if (debug >= 1) printk(__FUNCTION__ " ofs=%08lx (adr=%p) => d=%#x\n", ofs, (__u16 *)(WINDOW_ADDR + ofs*offset_multiplier), d);
	return d;
}

static __u32 sa1100_read32(struct map_info *map, unsigned long ofs)
{
	return *(__u32 *)(WINDOW_ADDR + ofs*offset_multiplier);
}

static void sa1100_copy_from(struct map_info *map, void *to, unsigned long from, ssize_t len)
{
        int i;
        if (0) printk(__FUNCTION__ " from=%08lx offset_multiplier=%d\n", WINDOW_ADDR + from, offset_multiplier);
        if (offset_multiplier == 1) {
                memcpy(to, (void *)(WINDOW_ADDR + from), len);
        } else {
                for (i = 0; i < len; i += 2) {
                        *(short *)(to++) = *(short *)(WINDOW_ADDR + from*offset_multiplier + i*offset_multiplier);
                }
        }
}

static void sa1100_write8(struct map_info *map, __u8 d, unsigned long adr)
{
	*(__u8 *)(WINDOW_ADDR + adr*offset_multiplier) = d;
}

static void sa1100_write16(struct map_info *map, __u16 d, unsigned long adr)
{
        if (debug >= 1) printk(__FUNCTION__ " adr=%08lx d=%x\n", WINDOW_ADDR + adr*offset_multiplier, d);
	*(__u16 *)(WINDOW_ADDR + adr*offset_multiplier) = d;
}

static void sa1100_write32(struct map_info *map, __u32 d, unsigned long adr)
{
	*(__u32 *)(WINDOW_ADDR + adr*offset_multiplier) = d;
}

static void sa1100_copy_to(struct map_info *map, unsigned long to, const void *from, ssize_t len)
{
        int i;
        if (0) printk(__FUNCTION__ " to=%08lx offset_multiplier=%d\n", WINDOW_ADDR + to, offset_multiplier);
        if (offset_multiplier == 1) {
                memcpy((void *)(WINDOW_ADDR + to), from, len);
        } else {
                for (i = 0; i < len; i += 2) {
                        *(short *)(WINDOW_ADDR + to*offset_multiplier + i*offset_multiplier) = *(short *)(from + i);
                }
        }
}

static void pcmcia_set_vpp(struct map_info *map, int vpp)
{
	if (vpp)
           set_bitsy_egpio(EGPIO_BITSY_OPT_RESET);
	else
           clr_bitsy_egpio(EGPIO_BITSY_OPT_RESET);
}

static void backpaq_set_vpp(struct map_info *map, int vpp)
{
	if (vpp)
          mercury_backpaq_sysctl->flash_control |= BACKPAQ_FLASH_VPPEN;
	else
          mercury_backpaq_sysctl->flash_control &= ~BACKPAQ_FLASH_VPPEN;

}


static struct map_info pcmcia_sleeve_flash_map = {
	name:		"PCMCIA Sleeve Flash ",
	read8:		sa1100_read8,
	read16:		sa1100_read16,
	read32:		sa1100_read32,
	copy_from:	sa1100_copy_from,
	write8:		sa1100_write8,
	write16:	sa1100_write16,
	write32:	sa1100_write32,
	copy_to:	sa1100_copy_to,
        size:           0x02000000,
        set_vpp:        backpaq_set_vpp,
        buswidth:       2
};

static struct mtd_info *pcmcia_sleeve_mtd;

static int pcmcia_sleeve_attach_flash(void)
{
        void *(*cfi_probe)(void *) = inter_module_get("cfi_probe");
        if (cfi_probe != NULL) {
#if 1
                MSC1 = 0xfffcfffc;
/*                 MSC2 = 0x431afff8;*/ /* 32-bit, non-burst, all wait states maxed */
/*		MSC2 = 0x431a0440; */ /* 32-bit, non-burst, wait states: recover=0, 
				       first access = 4, next access = 8 */
		MSC2 = 0x431a1290;  /* 32-bit, non-burst, wait states: recover =0,
				       first access = 18, next access = 18 */
		/* Don't change the MSC2 values without checking if the camera works correctly */

                printk("setting MSC1=0x%x (&MSC1=%p)\n",MSC1, &MSC1);
                printk("setting MSC2=0x%x (&MSC2=%p)\n",MSC2, &MSC2);
#endif

                pcmcia_sleeve_mtd = cfi_probe(&pcmcia_sleeve_flash_map);
                if (pcmcia_sleeve_mtd) {
                        pcmcia_sleeve_mtd->module = THIS_MODULE;
                        add_mtd_partitions(pcmcia_sleeve_mtd, pcmcia_sleeve_flash_partitions, NB_OF(pcmcia_sleeve_flash_partitions));
                        printk(KERN_NOTICE "PCMCIA flash access initialized\n");
                        return 0;
                }
                return -ENXIO;
        } else {
                return -EINVAL;
        }

}

static void pcmcia_sleeve_detach_flash(void)
{
        if (pcmcia_sleeve_mtd != NULL) {
                del_mtd_partitions(pcmcia_sleeve_mtd);
                map_destroy(pcmcia_sleeve_mtd);
        }
}

static int __devinit pcmcia_probe_sleeve(struct sleeve_dev *sleeve_dev, const struct sleeve_device_id *ent)
{
        sprintf(sleeve_dev->name, "PC Card Sleeve");
        printk(__FUNCTION__ ": dev->name=%s\n", sleeve_dev->name);
#if 1
        sa1100_bitsy_change_sleeves(SINGLE_PCMCIA_SLEEVE);
#endif
        offset_multiplier = 1; /* for revA sleeve */
        pcmcia_sleeve_flash_map.size = 0x02000000;
        pcmcia_sleeve_flash_map.set_vpp = &pcmcia_set_vpp;
        pcmcia_sleeve_attach_flash();
        return 0;
}

static void __devexit pcmcia_remove_sleeve(struct sleeve_dev *sleeve_dev)
{
        printk(__FUNCTION__ ": dev->name=%s\n", sleeve_dev->name);
        pcmcia_sleeve_detach_flash();
#if 1
        sa1100_bitsy_change_sleeves(SLEEVE_NO_DEVICE);
#endif
}

static struct sleeve_device_id pcmcia_tbl[] __devinitdata = {
        { COMPAQ_VENDOR_ID, SINGLE_PCMCIA_SLEEVE, 0, 0, 0 },
        { 0, }
};

static struct sleeve_driver pcmcia_driver = {
        name:   "Compaq PC Card Sleeve",
        id_table: pcmcia_tbl,
        probe: pcmcia_probe_sleeve,
        remove: pcmcia_remove_sleeve,
};



static int __devinit dual_pcmcia_probe_sleeve(struct sleeve_dev *sleeve_dev, const struct sleeve_device_id *ent)
{
        sprintf(sleeve_dev->name, "Dual PC Card Sleeve");
        printk(__FUNCTION__ ": dev->name=%s\n", sleeve_dev->name);
#if 1
        sa1100_bitsy_change_sleeves(DUAL_PCMCIA_SLEEVE);
#endif
        return 0;
}

static void __devexit dual_pcmcia_remove_sleeve(struct sleeve_dev *sleeve_dev)
{
        printk(__FUNCTION__ ": dev->name=%s\n", sleeve_dev->name);
#if 1
        sa1100_bitsy_change_sleeves(SLEEVE_NO_DEVICE);
#endif
}

static struct sleeve_device_id dual_pcmcia_tbl[] __devinitdata = {
        { COMPAQ_VENDOR_ID, DUAL_PCMCIA_SLEEVE, 0, 0, 0 },
        { 0, }
};

static struct sleeve_driver dual_pcmcia_driver = {
        name:   "Compaq Dual PC Card Sleeve",
        id_table: dual_pcmcia_tbl,
        probe: dual_pcmcia_probe_sleeve,
        remove: dual_pcmcia_remove_sleeve,
};


static int __devinit backpaq_probe_sleeve(struct sleeve_dev *sleeve_dev, const struct sleeve_device_id *ent)
{
        sprintf(sleeve_dev->name, "Compaq Mercury Backpaq");
        printk(__FUNCTION__ ": dev->name=%s\n", sleeve_dev->name);
#if 1
        sa1100_bitsy_change_sleeves(MERCURY_BACKPAQ);
#endif
        offset_multiplier = 2; /* for revA sleeve */
        offset_multiplier = 1; /* for revB sleeve */
        pcmcia_sleeve_flash_map.set_vpp = &backpaq_set_vpp;
        pcmcia_sleeve_flash_map.size = 0x02000000;

        printk(__FUNCTION__ ": flash_control=%x &flash_control=%p\n", mercury_backpaq_sysctl->flash_control, &mercury_backpaq_sysctl->flash_control);
        mercury_backpaq_sysctl->flash_control = (BACKPAQ_FLASH_ENABLE0 | BACKPAQ_FLASH_ENABLE1);
        printk(__FUNCTION__ ": flash_control=%x -1-\n", mercury_backpaq_sysctl->flash_control);

        pcmcia_sleeve_attach_flash();
        return 0;
}

static void __devexit backpaq_remove_sleeve(struct sleeve_dev *sleeve_dev)
{
        printk(__FUNCTION__ ": dev->name=%s\n", sleeve_dev->name);
#if 1
        sa1100_bitsy_change_sleeves(SLEEVE_NO_DEVICE);
#endif
        pcmcia_sleeve_detach_flash();
}

static struct sleeve_device_id backpaq_tbl[] __devinitdata = {
        { SLEEVE_ANY_ID, MERCURY_BACKPAQ, 0, 0, 0 },
        { SLEEVE_ANY_ID, 0x0, 0, 0, 0 },
        { 0, }
};

static struct sleeve_driver backpaq_driver = {
        name:   "Compaq Mercury Backpaq",
        id_table: backpaq_tbl,
        probe: backpaq_probe_sleeve,
        remove: backpaq_remove_sleeve,
};



static int backpaq_ctl_insert(void)
{
        pcmcia_sleeve_attach_flash();
        return 0;
};

static int backpaq_ctl_eject(void)
{
        return 0;
};

static int backpaq_ctl_query(void)
{
        int i;
        for (i = 0; i < 32; i+= 2) {
                printk("h3600_backpaq: probing %#08x yields %04x\n", WINDOW_ADDR+i, *(unsigned short *)(WINDOW_ADDR + i));
        }
        /* put it in query mode */
        for (i = 0x0; i < 0x40; i+= 2) {
           *(short *)WINDOW_ADDR = 0x90;
                printk("h3600_backpaq: querying %#08x yields %04x\n", WINDOW_ADDR+i, *(unsigned short *)(WINDOW_ADDR + i));
        }
        return 0;
}

static struct ctl_table backpaq_table[] = 
{
	{1, "eject", NULL, 0, 0600, NULL, (proc_handler *)&backpaq_ctl_eject},
	{2, "insert", NULL, 0, 0600, NULL, (proc_handler *)&backpaq_ctl_insert},
	{3, "query", NULL, 0, 0600, NULL, (proc_handler *)&backpaq_ctl_query},
	{4, "debug", &debug, sizeof(debug), 0600, NULL, (proc_handler *)&proc_dointvec},
	{5, "msc0", (void*)&MSC1, sizeof(int), 0600, NULL, (proc_handler *)&proc_dointvec},
	{6, "msc1", (void*)&MSC1, sizeof(int), 0600, NULL, (proc_handler *)&proc_dointvec},
	{7, "msc2", (void*)&MSC2, sizeof(int), 0600, NULL, (proc_handler *)&proc_dointvec},
	{8, "mecr", (void*)&MECR, sizeof(int), 0600, NULL, (proc_handler *)&proc_dointvec},
	{0}
};
static struct ctl_table backpaq_dir_table[] = 
{
	{22, "backpaq", NULL, 0, 0555, backpaq_table},
        {0}
};
static struct ctl_table_header *backpaq_ctl_table_header = NULL;


int __init backpaq_init_module(void)
{
        h3600_sleeve_register_driver(&cf_driver);
        h3600_sleeve_register_driver(&pcmcia_driver);
        h3600_sleeve_register_driver(&dual_pcmcia_driver);
        h3600_sleeve_register_driver(&backpaq_driver);
        backpaq_ctl_table_header = register_sysctl_table(backpaq_dir_table, 0);
	return 0;
}

void backpaq_cleanup_module(void)
{
	printk(__FUNCTION__ ":\n");
        unregister_sysctl_table(backpaq_ctl_table_header);
        h3600_sleeve_unregister_driver(&cf_driver);
        h3600_sleeve_unregister_driver(&pcmcia_driver);
        h3600_sleeve_unregister_driver(&dual_pcmcia_driver);
        h3600_sleeve_unregister_driver(&backpaq_driver);
}

module_init(backpaq_init_module);
module_exit(backpaq_cleanup_module);

