/*
*
* Driver for H3600 Extension Packs
*
* Copyright 2000 Compaq Computer Corporation.
*
* Use consistent with the GNU GPL is permitted,
* provided that this copyright notice is
* preserved in its entirety in all copies and derived works.
*
* COMPAQ COMPUTER CORPORATION MAKES NO WARRANTIES, EXPRESSED OR IMPLIED,
* AS TO THE USEFULNESS OR CORRECTNESS OF THIS CODE OR ITS
* FITNESS FOR ANY PARTICULAR PURPOSE.
*
* Author: Jamey Hicks.
*
*/

#include <linux/config.h>
#include <linux/module.h>
#include <linux/version.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/interrupt.h>
#include <linux/proc_fs.h>
#include <linux/sysctl.h>

/* SA1100 serial defines */
#include <asm/arch/hardware.h>
#include <asm/arch/serial_reg.h>
#include <asm/arch/irqs.h>
#include <asm/arch/h3600-sleeve.h>

#include <linux/h3600_ts.h>

static void msleep(unsigned int msec)
{
	current->state = TASK_INTERRUPTIBLE;
	schedule_timeout( (msec * HZ + 999) / 1000);
}


#ifdef CONFIG_HOTPLUG

#ifndef FALSE
#define FALSE	(0)
#define TRUE	(!FALSE)
#endif

extern char hotplug_path[];

static void
run_sbin_hotplug(struct sleeve_dev *sdev, int insert)
{
	int i;
	char *argv[3], *envp[8];
	char id[64], sub_id[64];

	if (!hotplug_path[0])
		return;

	i = 0;
	argv[i++] = hotplug_path;
	argv[i++] = "sleeve";
	argv[i] = 0;

        printk(KERN_CRIT __FUNCTION__ ":%d hotplug_path=%s\n", __LINE__, hotplug_path);
	i = 0;
	/* minimal command environment */
	envp[i++] = "HOME=/";
	envp[i++] = "PATH=/sbin:/bin:/usr/sbin:/usr/bin";
	
	/* other stuff we want to pass to /sbin/hotplug */
        sprintf(id, "VENDOR_ID=%x\n", sdev->vendor);
        sprintf(sub_id, "DEVICE_ID=%x\n", sdev->device);
	envp[i++] = id;
	envp[i++] = sub_id;
	if (insert)
		envp[i++] = "ACTION=add";
	else
		envp[i++] = "ACTION=remove";
	envp[i] = 0;

	call_usermodehelper (argv [0], argv, envp);
}
#else
static void run_sbin_hotplug(struct sleeve_dev *sdev, int insert) { }
#endif


static int h3600_sleeve_major = 0;

struct {
        char start_of_id; /* 0xaa */
        int data_len;
        char version;
        short vendor_id;
        short device_id;
} sleeve_header;

struct proc_dir_entry *proc_sleeve_dir = NULL;

static int h3600_sleeve_proc_read_eeprom(char *page, char **start, off_t off, int count, int *eof, void *data)
{
        char *p = page;
        int len = 0;
        int i;
        char mark = 0;
        int offset = 1;

        h3600_spi_read(0, &mark, 1);
        h3600_spi_read(1, (char*)&len, 4);
        if (mark != 0xaa || len == 0xFFFFFFFF)
                return 0;
        if (0) printk(__FUNCTION__ ": len=%d\n", len);
        for (i = 0; i < len; i+=8) {
                h3600_spi_read(offset, p, 8);
                p += 8;
                offset += 8;
        }
        if (len != (p - page))
                h3600_spi_read(offset, p, len-(p - page));
        return len;
}

static struct sleeve_sections {
  unsigned long start;
  const char *name;
  int length;
} sleeve_sections[] = {
  { 0xf1000000, "FLASH1", 20 },
  { 0xf2000000, "FLASH2", 20 },
  { 0xf3000000, "SYSCTL", 20 },
  { 0xf4000000, "SM4", 0x20 },
#if 0
  { 0xf6000000, "PCMCIA SKT1 MEM", 16 },
  { 0xf6400000, "PCMCIA SKT1 ATT", 16 },
  { 0xf6800000, "PCMCIA SKT1 ATT", 16 },
  { 0xf6c00000, "PCMCIA SKT1 IO ", 16 },
  { 0xf7000000, "PCMCIA SKT2 MEM", 16 },
  { 0xf7400000, "PCMCIA SKT2 ATT", 16 },
  { 0xf7800000, "PCMCIA SKT2 ATT", 16 },
  { 0xf7c00000, "PCMCIA SKT2 IO ", 16 },
#endif
  { 0, NULL },
};

static int h3600_sleeve_proc_read_backpaq(char *page, char **start, off_t off, int count, int *eof, void *data)
{
        char *p = page;
        int i;
        int r = 0;

        while (sleeve_sections[r].start != 0) {
                const char *name = sleeve_sections[r].name;
                unsigned long start = sleeve_sections[r].start;
                for (i = 0; i < 0x40; i += 4)
                        p += sprintf(p, "%s: %p  %#04x\n", name, (short *)(start + i), *(short *)(start + i));
                r++;
        }
        return p-page;
}

static int h3600_sleeve_proc_read_attr(char *page, char **start, off_t off, int count, int *eof, void *data)
{
	int sock = (int)data;
        char *p = page;
        int i = 0;
	short *base = (short *)((sock == 0) ? 0xf6800000 : 0xf7800000);

	while  ((p-page) < count - 16) {
		p += sprintf(p, "%p  %#04x\n", base + i, *(base + i));
        }
        return p-page;
}

int h3600_sleeve_proc_attach_device(struct sleeve_dev *dev)
{
        dev->procent = NULL;
	return 0;
}

int h3600_sleeve_proc_detach_device(struct sleeve_dev *dev)
{
	struct proc_dir_entry *e;

	if ((e = dev->procent) != NULL) {
#if Broken 
		if (atomic_read(e->count) != 0)
			return -EBUSY;
#endif
		remove_proc_entry(e->name, proc_sleeve_dir);
		dev->procent = NULL;
	}
	return 0;
}


const struct sleeve_device_id *h3600_sleeve_match_device(const struct sleeve_device_id *ids, struct sleeve_dev *dev)
{
        while (ids->vendor || ids->subvendor) {
                if ((ids->vendor == SLEEVE_ANY_ID || ids->vendor == dev->vendor) &&
		    (ids->device == SLEEVE_ANY_ID || ids->device == dev->device))
			return ids;
                ids++;
        }
        return NULL;
}

static LIST_HEAD(sleeve_drivers);

static struct sleeve_device_id *sleeve_device_id;
static struct sleeve_dev *sleeve_dev;

int h3600_sleeve_register_driver(struct sleeve_driver *drv)
{
 	list_add_tail(&drv->node, &sleeve_drivers);
	if (sleeve_device_id->device != SLEEVE_NO_DEVICE ) {
                const struct sleeve_device_id *did = NULL;
                if ((did = h3600_sleeve_match_device(&drv->id_table[0], sleeve_dev)) != NULL) {
                        if (drv->probe) {
                                if (0) printk(__FUNCTION__ ": about to call %p\n", drv->probe);
                                drv->probe(sleeve_dev, did);
                        }
                }
	}
        return 0; 
}
EXPORT_SYMBOL(h3600_sleeve_register_driver);

void
h3600_sleeve_unregister_driver(struct sleeve_driver *drv)
{
        list_del(&drv->node);
        if (sleeve_dev->driver == drv) {
                if (drv->remove)
                        drv->remove(sleeve_dev);
                sleeve_dev->driver = NULL;
        }
}
EXPORT_SYMBOL(h3600_sleeve_unregister_driver);

static int
h3600_sleeve_announce_device(struct sleeve_driver *drv, struct sleeve_dev *dev)
{
	const struct sleeve_device_id *id;

	if (drv->id_table) {
		id = h3600_sleeve_match_device(drv->id_table, dev);
		if (!id)
			return 0;
	} else
		id = NULL;
	if (drv->probe(dev, id) >= 0) {
		dev->driver = drv;
		return 1;
	}
	return 0;
}

void
h3600_sleeve_insert_device(struct sleeve_dev *dev)
{
        struct list_head *ln;
#ifdef CONFIG_PROC_FS
        h3600_sleeve_proc_attach_device(dev);
#endif

        for (ln=sleeve_drivers.next; ln != &sleeve_drivers; ln=ln->next) {
                struct sleeve_driver *drv = list_entry(ln, struct sleeve_driver, node);
                if (h3600_sleeve_announce_device(drv, dev))
                        break;
        }

        /* notify userspace of new hotplug device */
        run_sbin_hotplug(dev, TRUE);
}
EXPORT_SYMBOL(h3600_sleeve_insert_device);

void
h3600_sleeve_remove_device(struct sleeve_dev *dev)
{
	if (dev->driver) {
		if (dev->driver->remove)
			dev->driver->remove(dev);
		dev->driver = NULL;
	}
#if 0
	h3600_sleeve_free_resources(dev);
#endif
#ifdef CONFIG_PROC_FS
	h3600_sleeve_proc_detach_device(dev);
#endif
	/* notify userspace of hotplug device removal */
	run_sbin_hotplug(dev, FALSE);
}
EXPORT_SYMBOL(h3600_sleeve_remove_device);

static void h3600_sleeve_insert(void) 
{
        set_bitsy_egpio(EGPIO_BITSY_OPT_NVRAM_ON | EGPIO_BITSY_OPT_ON);
        msleep(100);
        h3600_spi_read(6, (char*)&sleeve_dev->vendor, 2);
        h3600_spi_read(8, (char*)&sleeve_dev->device, 2);
        printk(KERN_CRIT __FUNCTION__ ": vendorid=%#x devid=%#x\n", sleeve_dev->vendor, sleeve_dev->device);
			
        h3600_sleeve_insert_device(sleeve_dev);
}

static void h3600_sleeve_eject(void) 
{
        printk(KERN_CRIT __FUNCTION__ "-9- sleeve_dev->driver=%p sleeve removed\n", sleeve_dev->driver);
        if (sleeve_dev->driver && sleeve_dev->driver->remove) {
                sleeve_dev->driver->remove(sleeve_dev);
        }

        memset(sleeve_dev, 0, sizeof(struct sleeve_dev));
        clr_bitsy_egpio(EGPIO_BITSY_OPT_NVRAM_ON | EGPIO_BITSY_OPT_ON);

}

static void h3600_sleeve_task_handler(void *x);
static struct tq_struct h3600_sleeve_task = {
	routine: h3600_sleeve_task_handler
};
static void h3600_sleeve_task_handler(void *x)
{
        int opt_ndet;

        /* debounce */
        msleep(100);
        opt_ndet = (GPLR & GPIO_BITSY_OPT_DET);

        if (!opt_ndet) {
                h3600_sleeve_insert();
        } else {
                h3600_sleeve_eject();
        }

}


static void h3600_sleeve_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
        int opt_ndet = (GPLR & GPIO_BITSY_OPT_DET);
        /* now clear the interrupt */
        GEDR = GPIO_BITSY_OPT_DET;
        schedule_task(&h3600_sleeve_task);
}

static struct ctl_table sleeve_table[] = 
{
	{1, "eject", NULL, 0, 0600, NULL, (proc_handler *)&h3600_sleeve_eject},
	{2, "insert", NULL, 0, 0600, NULL, (proc_handler *)&h3600_sleeve_insert},
	{0}
};
static struct ctl_table sleeve_dir_table[] = 
{
	{3, "sleeve", NULL, 0, 0555, sleeve_table},
        {0}
};
static struct ctl_table_header *sleeve_ctl_table_header = NULL;

int __init h3600_sleeve_init_module(void)
{
	int result;

        if (!machine_is_bitsy())
          return -ENODEV;

        clr_bitsy_egpio(EGPIO_BITSY_OPT_NVRAM_ON | EGPIO_BITSY_OPT_ON);

        sleeve_device_id = (struct sleeve_device_id *)kmalloc(sizeof(struct sleeve_device_id), GFP_KERNEL);
        sleeve_dev = (struct sleeve_dev *)kmalloc(sizeof(struct sleeve_dev), GFP_KERNEL);
        if (sleeve_device_id == NULL || sleeve_dev == NULL) {
          printk(__FUNCTION__ ": kmalloc failed sleeve_device_id=%p sleeve_dev=%p\n", sleeve_device_id, sleeve_dev);
          return -ENOMEM;
        }
        memset(sleeve_device_id, 0, sizeof(struct sleeve_device_id));
        memset(sleeve_dev, 0, sizeof(struct sleeve_dev));

        set_GPIO_IRQ_edge( GPIO_BITSY_OPT_DET, GPIO_BOTH_EDGES );
	result = request_irq(IRQ_GPIO_BITSY_OPT_DET, h3600_sleeve_interrupt, SA_SHIRQ | SA_INTERRUPT | SA_SAMPLE_RANDOM,
                             "h3600_sleeve", h3600_sleeve_interrupt);

#ifdef CONFIG_PROC_FS
        proc_sleeve_dir = proc_mkdir("sleeve", proc_bus);
        if (proc_sleeve_dir) {
                create_proc_read_entry("eeprom", 0, proc_sleeve_dir, h3600_sleeve_proc_read_eeprom, 0);
                create_proc_read_entry("backpaq", 0, proc_sleeve_dir, h3600_sleeve_proc_read_backpaq, 0);
                create_proc_read_entry("attr0", 0, proc_sleeve_dir, h3600_sleeve_proc_read_attr, 0);
                create_proc_read_entry("attr1", 0, proc_sleeve_dir, h3600_sleeve_proc_read_attr, 1);
        }
#endif

        sleeve_ctl_table_header = register_sysctl_table(sleeve_dir_table, 0);

        schedule_task(&h3600_sleeve_task);

	if (!result)
	{	
                printk(KERN_CRIT "init_module successful init major= %d irq=%d\n",
                       h3600_sleeve_major, IRQ_GPIO_BITSY_OPT_DET);

	}
	else
		printk(__FUNCTION__ ": error from irq %d\n", result);
	return 0;
}

void h3600_sleeve_cleanup_module(void)
{
	flush_scheduled_tasks(); /* make sure all tasks have run */
        free_irq(IRQ_GPIO_BITSY_OPT_DET, h3600_sleeve_interrupt);

#ifdef CONFIG_PROC_FS
        if (proc_sleeve_dir) {
                remove_proc_entry("eeprom", proc_sleeve_dir);
                remove_proc_entry("backpaq", proc_sleeve_dir);
                remove_proc_entry("attr0", proc_sleeve_dir);
                remove_proc_entry("attr1", proc_sleeve_dir);
                remove_proc_entry("sleeve", proc_bus);
        }
#endif

        unregister_sysctl_table(sleeve_ctl_table_header);

}

module_init(h3600_sleeve_init_module);
module_exit(h3600_sleeve_cleanup_module);

