#include <linux/config.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/spinlock.h>
#include <linux/completion.h>
#include <linux/interrupt.h>
#include <linux/proc_fs.h>

#ifdef CONFIG_PM
#include <linux/pm.h>
#endif

#ifdef CONFIG_HOTPLUG
#include <linux/kmod.h>
#endif

#include <asm/semaphore.h>
#include <asm/hardware.h>
#include <asm/io.h>
#include <asm/irq.h>
#include <asm/uaccess.h>
#include <asm/arch/pm.h>
#include <asm/arch/gpio.h>
#include <asm/arch/hwtimer.h>

#include "av500_hd.h"

#define MAJOR_NR av500_ide_major
static int av500_ide_major = 243;

#define AV500_IDE_SHIFT	6
#define AV500_IDE_MAXNRDEV	(1<<AV500_IDE_SHIFT)

#define DEVICE_NR(device) 	(MINOR(device)>>AV500_IDE_SHIFT)
#define DEVICE_NAME 		"ahd"
#define DEVICE_NO_RANDOM
#define DEVICE_REQUEST 		av500_ide_request
#define DEVICE_OFF(d)

#include <linux/delay.h>
#include <linux/blk.h>
#include <linux/blkpg.h>
#include <linux/genhd.h>
#include <linux/blkdev.h>
#include <linux/notifier.h>
#include <linux/reboot.h>

//#define DEBUG_WAKEUP
//#define DEBUG

#ifdef DEBUG
#define DBG(f, a...)	printk(f, ## a)
#else
#define DBG(f, a...)	do { } while (0)
#endif

#define USB2_ATTACH_IRQ INT_MPUIO1

/* iomapped base of hd registers */
unsigned long hd_base;

/* module parameters */
static int param_rahead;

static int av500_ide_maxsect[AV500_IDE_MAXNRDEV];
static int av500_ide_blocksizes[AV500_IDE_MAXNRDEV];
static int av500_ide_hardsectsizes[AV500_IDE_MAXNRDEV];
static int av500_ide_inuse[AV500_IDE_MAXNRDEV];
static int av500_ide_maxreadahead[AV500_IDE_MAXNRDEV];

static int av500_ide_sizes[AV500_IDE_MAXNRDEV];
static struct hd_struct av500_ide_partitions[AV500_IDE_MAXNRDEV];

static unsigned long long av500_ide_sectors;
static int av500_ide_size;

/* usb2 bridge handling */
static int disk_changed;
typedef enum { USB2_DETACHED, USB2_ATTACH_WAIT, USB2_ATTACHED } usb2_state_t;
static usb2_state_t usb2_state;
static void usb2_check(void);

/* power management */
typedef enum { DISK_ONLINE=0, DISK_FLUSHED, DISK_ABOUT_TO_SLEEP, DISK_SLEEPING, DISK_WAKING_UP, DISK_FAILED } hd_pwr_state_t;
static void pwr_check(void);
static int pwr_check_thread(void*);
static int hd_pwr_request(void);
static DECLARE_WAIT_QUEUE_HEAD(hdpwrd_wait);
static int hdpwrd_pid = 0;
static DECLARE_COMPLETION(hdpwrd_exited);
static spinlock_t hdreq_lock = SPIN_LOCK_UNLOCKED;
static unsigned long av500_hd_last_sleep;
static int user_activity_timeout;
static int disk_standby_timeout;
static volatile int user_was_active;
static volatile int pm_disk_suspended;

/* these variables must be accessed from interrupt context or with disabled IRQs */
static volatile int av500_hd_requested;
static volatile hd_pwr_state_t hd_pwr_state;

/* module initialisation and cleanup */
int init_driver(void);
void cleanup_driver(void);

/* block device interface */
static int av500_ide_open(struct inode*, struct file*);
static int av500_ide_release(struct inode*, struct file*);
static int av500_ide_ioctl(struct inode*, struct file*, unsigned int, unsigned long);
static int av500_ide_check_change(kdev_t);
static int av500_ide_revalidate(kdev_t);
static void av500_ide_request(request_queue_t *q);

static volatile int av500_ide_request_busy;
static int disk_error_count;

static devfs_handle_t devfs_handle = NULL;

/* module/kernel interface */
static struct block_device_operations av500_ide_bdops = {
	open:			av500_ide_open,
	release:		av500_ide_release,
	ioctl:			av500_ide_ioctl,
	check_media_change:	av500_ide_check_change,
	revalidate:		av500_ide_revalidate
};

static struct gendisk av500_ide_gendisk = {
	major:		0,
	major_name:	DEVICE_NAME,
	minor_shift:	AV500_IDE_SHIFT,
	max_p:		1 << AV500_IDE_SHIFT,
	fops:		&av500_ide_bdops
};

/* implementation of module-local functions */

/* implementation of interface functions */
static int av500_ide_open(struct inode* inode, struct file* filp)
{
        int minor;

        DBG(KERN_DEBUG "av500_ide: open(%p, %p)\n", inode, filp);

        if (usb2_state != USB2_DETACHED)
                return -EBUSY;

        minor = MINOR(inode->i_rdev);
        av500_ide_inuse[minor]++;
	MOD_INC_USE_COUNT;
	return 0;
}

static int av500_ide_release(struct inode* inode, struct file* filp)
{
        int minor;

	DBG(KERN_DEBUG "av500_ide: release(%p, %p)\n", inode, filp);

        minor = MINOR(inode->i_rdev);
        av500_ide_inuse[minor]--;
	MOD_DEC_USE_COUNT;

        return 0;
}

static int av500_ide_ioctl(struct inode* inode, struct file* filp, unsigned int cmd, unsigned long arg)
{
	int err = 0;
	long size;

	DBG(KERN_DEBUG "av500_ide: ioctl(%p, %p, %u, %lu)\n", inode, filp, cmd, arg);

	switch(cmd) {
	case BLKGETSIZE:
                DBG(KERN_DEBUG "ioctl(BLKGETSIZE)");
		err = !access_ok(VERIFY_WRITE, arg, sizeof(long));
		if (err)
			break;
		size = av500_ide_gendisk.part[MINOR(inode->i_rdev)].nr_sects;
		err = copy_to_user((long*)arg, &size, sizeof (long));
                DBG(" size: %li\n", size);
		break;

	case BLKRRPART:
		err = av500_ide_revalidate(inode->i_rdev);
		break;

	default:
		err = blk_ioctl(inode->i_rdev, cmd, arg);
		break;
	}
	return err;
}

static int av500_ide_check_change(kdev_t dev)
{
	DBG(KERN_DEBUG "av500_ide: check_change()\n");
	return disk_changed;
}

static int av500_ide_revalidate(kdev_t i_rdev)
{
	int part1 = (DEVICE_NR(i_rdev) << AV500_IDE_SHIFT) + 1;
	int npart = (1 << AV500_IDE_SHIFT) - 1;

	DBG(KERN_DEBUG "av500_ide: revalidate()\n");
	DBG(KERN_DEBUG "av500_ide: part1:%d npart:%d\n", part1, npart);

	memset(av500_ide_gendisk.sizes+part1, 0, npart*sizeof(int));
	memset(av500_ide_gendisk.part +part1, 0, npart*sizeof(struct hd_struct));
	av500_ide_gendisk.part[part1-1].nr_sects = av500_ide_sectors;

	printk(KERN_INFO "av500_ide partition check (%d):\n", DEVICE_NR(i_rdev));
	register_disk(&av500_ide_gendisk, i_rdev, AV500_IDE_MAXNRDEV, &av500_ide_bdops, av500_ide_sectors);

        disk_changed = 0;

	return 0;
}

static void av500_ide_request(request_queue_t *q)
{
	int err;

	struct list_head *head;
	
#ifdef DEBUG
	if (q && !list_empty(&q->queue_head)) {
		list_for_each(head, &q->queue_head) {
			struct request *req;
		
			req = list_entry(head, struct request, queue);
			printk("req cmd:%i sec:%i nrsect:%i nrsegs:%i\n", req->cmd, req->sector, req->nr_sectors, req->nr_segments);
		}
	}
#endif

	while (1) {
		int sector;
		int nr_sectors;
		int minor;
		int devnr;

                if (av500_ide_request_busy) {
                        DBG(KERN_DEBUG "request busy\n");
                        break;
                }

                INIT_REQUEST;

		if (disk_error_count > 10) {
			hd_reset(0);
			hd_pwr_state = DISK_FAILED;
		}
		
                err = hd_pwr_request();
                if (err < 0) {
                        DBG(KERN_DEBUG "Disk down, cannot handle request now\n");
#ifdef DEBUG_WAKEUP
			if (waitqueue_active(&CURRENT->bh->b_wait)) {
				wait_queue_head_t *q = &CURRENT->bh->b_wait;
				struct list_head *tmp;
				struct task_struct *p;
				
				printk(KERN_DEBUG "pids on buffer waitq: ");
				
				list_for_each(tmp,&q->task_list) {
					wait_queue_t *curr = list_entry(tmp, wait_queue_t, task_list);

					p = curr->task;
					printk("%i ", p->pid);
				}
				printk("\n");
			}
			printk("current: %i\n", current->pid);
#endif
			goto out;
                }

                /* prevent hdprwd from running while we're serving a request */
                spin_lock(&hdreq_lock);

                /* we're serving a request */
                av500_ide_request_busy = 1;
                //hd_led_force_on();

		minor = MINOR(CURRENT->rq_dev);
		devnr = DEVICE_NR(CURRENT->rq_dev) << AV500_IDE_SHIFT;
		sector = CURRENT->sector + av500_ide_partitions[minor].start_sect;
		nr_sectors = CURRENT->current_nr_sectors;

		if (sector > av500_ide_partitions[devnr].nr_sects) {
			printk(KERN_ERR "av500_ide.o: request past end of device\n");
			err = -EIO;
			goto out;
		}
		if (CURRENT->sector + nr_sectors > av500_ide_partitions[minor].nr_sects) {
			printk(KERN_ERR "av500_ide.o: request past end of partition\n");
			err = -EIO;
			goto out;
		}

		switch (CURRENT->cmd) {
		case READ:
			err = av500_hd_read_sectors(CURRENT, av500_ide_partitions[minor].start_sect);
			break;

		case WRITE:
			err = av500_hd_write_sectors(CURRENT, av500_ide_partitions[minor].start_sect);
			break;

		default:
			break;
		}

out:
                /* we're not serving a request, and not holding any locks */
                if (err == -EAGAIN)
                        break;

                /* serving a request, exit with the lock held */
                if (err == -EINPROGRESS)
                        break;

                /* request completed, release the lock */
                av500_ide_request_busy = 0;
                spin_unlock(&hdreq_lock);
                //hd_led_tristate();

		if (err < 0) {
			disk_error_count++;
                        end_request(0); /* any other error is fatal */
		} else {
			disk_error_count = 0;
                        end_request(1); /* request completed OK */
		}
        }
}

void av500_ide_next_request(struct request *req)
{
        unsigned long flags;

        spin_lock_irqsave(&io_request_lock, flags);

        /* request completed, release the lock */
        av500_ide_request_busy = 0;
        spin_unlock(&hdreq_lock);
        //hd_led_tristate();

        /* signal request completion and start the next one */
	blkdev_dequeue_request(req);
	end_that_request_last(req);
	av500_ide_request(NULL);

        spin_unlock_irqrestore(&io_request_lock, flags);
}

static void av500_ide_gpios_config(void)
{
    /* config GPIO14 for CPLD reset/enable output and */
    omap_gpio_dir(14, GPIO_DIR_OUT);
    /* GPIO15 for USB2_RESET */
    omap_gpio_dir(15, GPIO_DIR_OUT);

    /* configure MPUIO 1 as USB2_ATTACH */
    omap_mpuio_dir(1, GPIO_DIR_IN);
    omap_mpuio_irq(1, GPIO_IRQTRIGGER_RISING);
}

static void av500_ide_cpld_reset(void)
{
	hd_reset(0);        
	switch_usb(0);
	switch_hd(0);
	usb2_enable(0);
	usb2_reset(0);       
}

static int av500_ide_config(void)
{
        unsigned long tmp;

        /* enable ULPD DPLL */
        *ULPD_DPLL_CTRL_REG |= (1<<4);
        /* software-request for the DPLL */
        *ULPD_SOFT_REQ_REG  |= (1<<0);

        while ( ( (*ULPD_DPLL_CTRL_REG) & 0x0001) == 0)
		;
        /* end of MCLK set */

        tmp  = inl(BUS_CONFIG_REG);
        tmp &= ~((1 << 20) | (1 << 21) | (0xf << 4) | (0xf << 8) | (0xf << 12));
        tmp |= (1 << 0) | (0x5 << 4) | (0x1 << 8) | (0x2 << 12);
        outl(tmp, BUS_CONFIG_REG);

        av500_ide_gpios_config();
        av500_ide_cpld_reset();
        
	// before doing anything else, force the USB2 Detect line to low
	// to prevent a possibly connected host from recognizing us.
	omap_mpuio_dir(1, GPIO_DIR_OUT);
	omap_mpuio_clr(1);
	
	hd_reset(1);
	
	/* CPLD lines HIGH-Z ... */
	switch_hd(0);

	/* enable power for the USB2 bridge, release RESET and enable ATA2 */
	usb2_reset(0);
	switch_usb(1);
	usb2_enable(1);
	mdelay(1);
	usb2_reset(1);

	/* wait for 50ms so that the chip can initialize properly, then disable ATA2 */
	set_current_state(TASK_UNINTERRUPTIBLE);
	schedule_timeout(HZ/20);
	usb2_enable(0);

	set_current_state(TASK_UNINTERRUPTIBLE);
	schedule_timeout(HZ/20);
	
	/* give OMAP control over HDD and reset HDD */
	switch_hd(1);
	av500_hd_hw_reset();

	// ok, now the USB2 bridge should be in a sane state
	omap_mpuio_dir(1, GPIO_DIR_IN);

	return av500_hd_init();
}

/* power management functions */
static int av500_ide_pm_callback(struct pm_dev *dev, pm_request_t rqst, void *data)
{
        if (rqst == PM_SUSPEND) {
                DBG(KERN_DEBUG __FUNCTION__ " SUSPEND\n");
		pm_disk_suspended = 1;
                /* wait for any pending request to complete */
                local_irq_disable();
                while (hd_pwr_state != DISK_SLEEPING) {
			local_irq_enable();
			DBG(KERN_DEBUG "waiting for request to complete\n");
			set_current_state(TASK_UNINTERRUPTIBLE);
			schedule_timeout(HZ/10);
			local_irq_disable();
                }

                /*
                 * lock the request queue and enable the IRQs again. The disk is now off.
		 */
                av500_ide_request_busy = 1;
                local_irq_enable();

		/*
		 * Tristate the HD LED enable pin and disable CPLD clock
		 * before going to sleep
		 */
		//hd_led_tristate();
                outl(inl(MOD_CONF_CTRL_0) & ~(1UL<<12), MOD_CONF_CTRL_0);
        }
	else
        if (rqst == PM_RESUME) {
                DBG(KERN_DEBUG __FUNCTION__ " RESUME\n");
                /* 
		 * enable CPLD clock and unlock the request queue. 
		 */
                outl(inl(MOD_CONF_CTRL_0) |  (1UL<<12), MOD_CONF_CTRL_0);
                av500_ide_request_busy = 0;
		/* make sure HD LED is off */
		//hd_led_force_off();
		
		pm_disk_suspended = 0;
        }

        return 0;
}

/*
 * hd_pwr_request()
 *
 * use this function to request the disk
 * will prevent power management to spin down the disk
 * will wake up the disk if needed
 * takes hdreq_lock
 *
 * returns:
 *       0:     disk is awake, can complete request
 *      <0:     disk is down, cannot complete request
 */
static int hd_pwr_request(void)
{
        spin_lock(&hdreq_lock);
        av500_hd_requested = 1;

        if (usb2_state == USB2_ATTACHED) {
                spin_unlock(&hdreq_lock);
                return -EAGAIN;
        }

	if (user_activity_timeout)
		disk_standby_timeout = 15;

        if (hd_pwr_state > DISK_FLUSHED) {
                spin_unlock(&hdreq_lock);
                wake_up_interruptible(&hdpwrd_wait);
                return -EAGAIN;
        }

        spin_unlock(&hdreq_lock);
        return 0;
}

static int pwr_check_thread(void* dummy)
{
	long time_out = HZ;
	
	daemonize();
	reparent_to_init();

	strcpy(current->comm, "hdpwrd");
	do {
                long time_left = interruptible_sleep_on_timeout(&hdpwrd_wait, HZ/2);

		if (user_was_active) {
			user_was_active = 0;
			user_activity_timeout = 3;
		}
		
		// count down timeouts, but not faster than HZ
		if (time_left >= 0) {
			time_out -= (HZ/2-time_left);
			// don't do USB2 handling until the resume cycle has ended
			if (!pm_disk_suspended)
				usb2_check();
			if (time_left == 0 || av500_hd_requested)
				pwr_check();	
		}
		if  (time_out <= 0) {
			time_out = HZ;
			
			if (user_activity_timeout)
				user_activity_timeout--;
			if (disk_standby_timeout)
				disk_standby_timeout--;
		}
		
	} while (!signal_pending(current));

	complete_and_exit(&hdpwrd_exited, 0);
}

static void pwr_check(void)
{
        unsigned long flags;

        spin_lock_irqsave(&hdreq_lock, flags);

        do {
		/*
		 * this loop expects to be entered with interrupts disabled and
		 * the hdreq_lock spinlock locked.
		 */

                /* check what we need to do */
                switch (hd_pwr_state) {

                case DISK_ONLINE:
                        if (av500_hd_requested == 1) {
                                av500_hd_requested = 0;
                                break;
                        }
                        if (usb2_state != USB2_DETACHED)
                                break;

                        /* disk was idle for 1 seconds, flush dirty buffers to disk */
                        spin_unlock_irqrestore(&hdreq_lock, flags);
                        DBG(KERN_DEBUG "sync\n");
                        fsync_dev(0);
                        spin_lock_irqsave(&hdreq_lock, flags);
			
			/* promote to DISK_FLUSHED */
                        hd_pwr_state++;
			break;

		case DISK_FLUSHED:
			/* if disk was requested, demote to DISK_ONLINE */
                        if (av500_hd_requested == 1) {
                                av500_hd_requested = 0;
                                hd_pwr_state = DISK_ONLINE;
                                break;
                        }
			/* if USB2 is attached, demote to DISK_ONLINE */
                        if (usb2_state != USB2_DETACHED) {
                                hd_pwr_state = DISK_ONLINE;
                                break;
                        }
			/* if user was active recently, stay in DISK_FLUSHED */                        
			if (disk_standby_timeout && !pm_disk_suspended)
				break;

			/* 
			 * promote to DISK_ABOUT_TO_SLEEP
			 * must be done before sending the sleep command to avoid
			 * a race with the request queue
			 */
			hd_pwr_state = DISK_ABOUT_TO_SLEEP;
                        
			/* disk is idle long enough, send a sleep command */
                        spin_unlock_irqrestore(&hdreq_lock, flags);
                        if (unlikely(av500_hd_sleep() < 0)) {
                                spin_lock_irqsave(&hdreq_lock, flags);
                                hd_pwr_state = DISK_ONLINE;
                                break;
                        }

			hd_reset(0);
			switch_usb(0);
			switch_hd(0);
			usb2_reset(0);
			usb2_enable(0);

			hd_pwr_state = DISK_SLEEPING;
                        DBG(KERN_DEBUG "hdpwrd: disk sleeping\n");

                        spin_lock_irqsave(&hdreq_lock, flags);                        
			av500_hd_last_sleep = jiffies;
			
			
			break;

                case DISK_SLEEPING:
		case DISK_FAILED:
			if (av500_hd_requested == 1) {
				long sleep_time = HZ/2 - (jiffies-av500_hd_last_sleep);
				unsigned long spinup_time;
                                
				/*
				 * change to DISK_WAKING_UP before messing with the disk, needed to keep
				 * the USB2 state machine from messing with us while we're waking up
				 * the drive. We cannot fail to wake the drive anyway so it's safe
				 */
				hd_pwr_state = DISK_WAKING_UP;
				
				spin_unlock_irqrestore(&hdreq_lock, flags);
				
				if (sleep_time > 0) {
					DBG(KERN_DEBUG "disk down too recently, sleeping %i jiffies\n", sleep_time);
					set_current_state(TASK_UNINTERRUPTIBLE);
					schedule_timeout(sleep_time);
				}

                                DBG(KERN_DEBUG "hdpwrd: disk waking up\n");

				// before doing anything else, force the USB2 Detect line to low
				// to prevent a possibly connected host from recognizing us.
				omap_mpuio_dir(1, GPIO_DIR_OUT);
				omap_mpuio_clr(1);
				
				hd_reset(1);
				
				/* CPLD lines HIGH-Z ... */
				switch_hd(0);

				/* enable power for the USB2 bridge, release RESET and enable ATA2 */
				usb2_reset(0);
				switch_usb(1);
				usb2_enable(1);
				mdelay(1);
				usb2_reset(1);

				/* wait for 50ms so that the chip can initialize properly, then disable ATA2 */
				set_current_state(TASK_UNINTERRUPTIBLE);
				schedule_timeout(HZ/20);
				usb2_enable(0);

				set_current_state(TASK_UNINTERRUPTIBLE);
				schedule_timeout(HZ/20);
				
				/* give OMAP control over HDD and reset HDD */
				switch_hd(1);
				hd_reset(0);
				udelay(200);
				hd_reset(1);

				// ok, now the USB2 bridge should be in a sane state
				omap_mpuio_dir(1, GPIO_DIR_IN);
				DBG(KERN_DEBUG "hdpwrd: disk reset, waiting ...\n");

				// the disk needs at least 2.5-2.6 seconds to spin up
				set_current_state(TASK_UNINTERRUPTIBLE);
				schedule_timeout(2*HZ+HZ/2+HZ/10);

                                av500_hd_set_multcount(av500_hd_multcount);
				DBG(KERN_DEBUG "hdpwrd: multcount set\n");
                                av500_hd_set_features();
				DBG(KERN_DEBUG "hdpwrd: features set\n");

                                spin_lock_irqsave(&hdreq_lock, flags);

				hd_pwr_state = DISK_ONLINE;

                                spin_lock(&io_request_lock);
                                /* re-run pending requests */
                                av500_ide_request(NULL);
				DBG(KERN_DEBUG "hdpwrd: pending requests rerun\n");
                                spin_unlock(&io_request_lock);

                                
				av500_hd_requested = 0;
				
                        } 
                }

        } while (av500_hd_requested == 1);

	spin_unlock_irqrestore(&hdreq_lock, flags);
}

static void call_policy(const char *verb)
{
	char *argv [3], **envp, *buf, *scratch;
	int i = 0, value;

	if (!hotplug_path [0])
		return;
	if (in_interrupt ()) {
		DBG("In_interrupt\n");
		return;
	}
	if (!current->fs->root) {
		/* statically linked USB is initted rather early */
		DBG("call_policy %s -- no FS yet\n", verb);
		return;
	}
	if (!(envp = (char **) kmalloc (20 * sizeof (char *), GFP_KERNEL))) {
		DBG("enomem\n");
		return;
	}
	if (!(buf = kmalloc (256, GFP_KERNEL))) {
		kfree (envp);
		DBG("enomem2\n");
		return;
	}

	/* only one standardized param to hotplug command: type */
	argv [0] = hotplug_path;
	argv [1] = "exthost";
	argv [2] = 0;

	/* minimal command environment */
	envp [i++] = "HOME=/";
	envp [i++] = "PATH=/sbin:/bin:/usr/sbin:/usr/bin";

#ifdef	DEBUG
	/* hint that policy agent should enter no-stdout debug mode */
	envp [i++] = "DEBUG=kernel";
#endif
	/* extensible set of named bus-specific parameters,
	 * supporting multiple driver selection algorithms.
	 */
	scratch = buf;

	/* action:  add, remove */
	envp [i++] = scratch;
	scratch += sprintf (scratch, "ACTION=%s", verb) + 1;

	envp [i++] = 0;
	/* assert: (scratch - buf) < sizeof buf */

	/* NOTE: user mode daemons can call the agents too */

	DBG("hdpwrd: %s %s\n", argv [0], verb);
	value = call_usermodehelper (argv [0], argv, envp);
	kfree (buf);
	kfree (envp);
	if (value != 0)
		DBG("hdpwrd policy returned 0x%x\n", value);
}

static void av500_usb2_interrupt(int irq, void* data, struct pt_regs *regs)
{
        /* spurious interrupt, disregard */
        if (!omap_mpuio_get(1))
                return;

        disable_irq(USB2_ATTACH_IRQ);
	
        DBG(KERN_DEBUG "USB2 ATTACHED\n");
        wake_up_interruptible(&hdpwrd_wait);
}

/* usb2 plug check */
static void usb2_check(void)
{
        unsigned short usb2_attach;
        unsigned short tmp;
        unsigned long flags;

        usb2_attach = omap_mpuio_get(1);

        switch (usb2_state) {

        case USB2_DETACHED:
                if (usb2_attach) {
			/*
			 * don't promote to attach wait if the disk is not online.
			 * instead, request the drive to be woken up.
			 */
			if (hd_pwr_state > DISK_FLUSHED) {
				DBG(KERN_DEBUG "disk is down. requesting wake up\n");
				hd_pwr_request();
				return;
			}
				
			DBG(KERN_DEBUG "USB2 attached, waiting for disk idle\n");
			usb2_state = USB2_ATTACH_WAIT;
			call_policy("add");
		}
		break;

        case USB2_ATTACH_WAIT:
                if (!usb2_attach) {
                        DBG(KERN_DEBUG "USB2 detached\n");
                        usb2_state = USB2_DETACHED;
                        call_policy("remove");
	                enable_irq(USB2_ATTACH_IRQ);
			break;
                }

                if (av500_ide_inuse[1])
                        break;

		// this loop tests if there's a disk request pending. if 
		// 'ide_request_busy' becomes 0, the loop is terminated with
		// interrupts locked to prevent further disk activity.
		// the lock can be safely released after moving to state
		// USB2_ATTACHED
                spin_lock_irqsave(&hdreq_lock, flags);
                while (av500_ide_request_busy) {
                        spin_unlock_irqrestore(&hdreq_lock, flags);
                        DBG(KERN_DEBUG "waiting for request to complete\n");
                        schedule();
                        spin_lock_irqsave(&hdreq_lock, flags);
                }
                
		DBG(KERN_DEBUG "disk idle, turning on USB2\n");

                usb2_state = USB2_ATTACHED;
                spin_unlock_irqrestore(&hdreq_lock, flags);

		switch_hd(0);
		usb2_enable(1);
				
                break;

        case USB2_ATTACHED:
                if (usb2_attach)
                        break;

                DBG(KERN_DEBUG "USB2 detached\n");

                /* disable usb2 bridge (USB2_ENABLE=0) */
                usb2_enable(0);
		mdelay(1);
		/* give OMAP control over HD */
		switch_hd(1);

                av500_hd_hw_reset();
                DBG(KERN_DEBUG "Drive Reset\n");
                av500_hd_set_multcount(16);
                DBG(KERN_DEBUG "Multcount set\n");
                av500_hd_set_features();
                DBG(KERN_DEBUG "Features set\n");

                usb2_state = USB2_DETACHED;

                /* re-run pending requests */
                spin_lock_irqsave(&io_request_lock, flags);
                av500_ide_request(NULL);
                spin_unlock_irqrestore(&io_request_lock, flags);

                DBG(KERN_DEBUG "pending request re-run\n");

                call_policy("remove");

                enable_irq(USB2_ATTACH_IRQ);
        }
}

void av500_report_user_activity(void)
{
	/* in case of user activity, prolong ide spindown timeout */
	user_was_active = 1;
}

static ssize_t hd_proc_read(struct file* file, char* buf, size_t count, loff_t* offs)
{
	ssize_t len = snprintf(buf, count, "%i", hd_pwr_state);
	if (len < count)
		len++; 
	return len;
}

static ssize_t hd_proc_write(struct file* file, char* buf, size_t count, loff_t* offs)
{
	user_activity_timeout = simple_strtoul(buf, NULL, 0);
	hd_pwr_request();
	return count;
}

static struct file_operations hd_proc_operations = {
	read:	hd_proc_read,
	write:	hd_proc_write
};

static int hd_reboot_handler(struct notifier_block *, unsigned long, void *);
static struct notifier_block hd_reboot_notifier = {
	hd_reboot_handler,
	NULL,
	0
};

static int hd_reboot_handler(struct notifier_block *this, unsigned long code, void *x)
{
	switch (code) {
	
	case SYS_RESTART:
	case SYS_POWER_OFF:
		av500_hd_sleep();
		break;
		
	default:
			break;
	
	}

	return NOTIFY_DONE;
}

/* driver entry point for initialization */
int init_driver(void)
{
	int err;
        int drive;
	struct proc_dir_entry *p;
	
	printk(KERN_INFO "av500_ide.o: initializing\n");
	
	if ( (p = create_proc_entry("driver/hdd", 0, NULL)) == NULL)
		return -ENOMEM;
	
	p->proc_fops = &hd_proc_operations;
	
        hd_base = (  unsigned long )ioremap(  HD_PHYS_BASE, SZ_1M + SZ_4K );
        if (  hd_base == 0 ) {
                printk(KERN_ERR "unable to map HD I/O region\n" );
                return -EBUSY;
        }

        printk(KERN_DEBUG "mapped HD I/O region to %08lx\n", hd_base );

	if (av500_ide_config() < 0)
                goto out_unmap;

	err = av500_hd_identify(&av500_ide_sectors);
	if (err < 0) {
		printk(KERN_ERR "av500_ide.o: unable to find ide device\n");
		goto out_release;
	}

        /* compute the size of the card in 1KB units */
	av500_ide_size = av500_ide_sectors / 2;

	devfs_handle = devfs_mk_dir (NULL, av500_ide_gendisk.major_name, NULL);
	err = devfs_register_blkdev(av500_ide_major, av500_ide_gendisk.major_name, &av500_ide_bdops);
	if ( err < 0) {
		printk(KERN_ERR "av500_ide.o: can't register major number %d\n", av500_ide_major);
		goto out_release;
	}

	if (av500_ide_major == 0) {
		/* dynamic allocation of major number */
		av500_ide_major = err;
	}
        
	if (request_irq(USB2_ATTACH_IRQ, av500_usb2_interrupt, SA_INTERRUPT, "av500_usb2", 0) != 0) {
                printk(KERN_ERR "av500_ide: request irq failed\n");
                goto out_unregister;
        }

        /* kernel thread for disk power management */
        hd_pwr_state = DISK_ONLINE;
        usb2_state = USB2_DETACHED;
        hdpwrd_pid = kernel_thread(pwr_check_thread, NULL, CLONE_FS | CLONE_FILES | CLONE_SIGHAND);
        if (hdpwrd_pid < 0)
                goto out_freeirq;

	/* initialize the device request queue */
	blk_init_queue(BLK_DEFAULT_QUEUE(av500_ide_major), av500_ide_request);

	for(drive=0; drive < AV500_IDE_MAXNRDEV; drive++) {
		av500_ide_blocksizes[drive] = 1024;
		av500_ide_hardsectsizes[drive] = 512;
		av500_ide_maxsect[drive]=16;
		av500_ide_maxreadahead[drive] = vm_max_readahead;
	}
	blksize_size[av500_ide_major] = av500_ide_blocksizes;
	hardsect_size[av500_ide_major] = av500_ide_hardsectsizes;
	max_sectors[av500_ide_major] = av500_ide_maxsect;
	max_readahead[av500_ide_major] = av500_ide_maxreadahead;

	av500_ide_sizes[0] = av500_ide_size;
	blk_size[av500_ide_major] = av500_ide_gendisk.sizes = av500_ide_sizes;

	memset(av500_ide_partitions, 0, (1<<AV500_IDE_SHIFT)*sizeof (struct hd_struct));
	av500_ide_partitions[0].nr_sects = av500_ide_sectors;
	av500_ide_gendisk.major = av500_ide_major;
	av500_ide_gendisk.part = av500_ide_partitions;
	av500_ide_gendisk.nr_real = 1;

	add_gendisk(&av500_ide_gendisk);

#ifdef CONFIG_PM
	pm_register(PM_SYS_DEV, PM_SYS_ARCH_SPECIFIC, av500_ide_pm_callback);
	register_reboot_notifier(&hd_reboot_notifier);
#endif

        disk_changed = 1;
	check_disk_change(MKDEV(av500_ide_major, 0));

	disk_error_count = 0;
	err = 0;
	return err;

out_freeirq:
        free_irq(USB2_ATTACH_IRQ, 0);
out_unregister:
	devfs_unregister_blkdev(av500_ide_major, av500_ide_gendisk.major_name);
out_release:
        av500_hd_release();
out_unmap:
        iounmap( ( void* )hd_base );
        return err;
}

void cleanup_driver(void)
{
	int i;
	struct gendisk **gdp;

	printk(KERN_DEBUG "av500_ide.o: cleanup\n");

	/* sync all instances */
	for (i = 0; i < (1<<AV500_IDE_SHIFT); i++)
		fsync_dev(MKDEV(av500_ide_major, i));

	devfs_unregister_blkdev(av500_ide_major, av500_ide_gendisk.major_name);
        devfs_unregister (devfs_handle);

	/* Fix up the request queue(s) */
	blk_cleanup_queue(BLK_DEFAULT_QUEUE(av500_ide_major));

	/* clean up global arrays */
	read_ahead[av500_ide_major] = 0;
	blk_size[av500_ide_major] = NULL;
	blksize_size[av500_ide_major] = NULL;
	hardsect_size[av500_ide_major] = NULL;
        max_sectors[av500_ide_major] = NULL;

	/* remove gendisk structure */
	for (gdp = &gendisk_head; *gdp; gdp = &((*gdp)->next))
		if (*gdp == &av500_ide_gendisk) {
			*gdp = (*gdp)->next;
			break;
		}

        av500_hd_release();

        /* Kill the thread */
	kill_proc(hdpwrd_pid, SIGTERM, 1);
	wait_for_completion(&hdpwrd_exited);

	iounmap( ( void* )hd_base );
}

module_init(init_driver);
module_exit(cleanup_driver);

MODULE_AUTHOR("Matthias Welwarsky, LSE GmbH, Niklas Schroeter <niki@nikishome.de>");
MODULE_DESCRIPTION("IDE blockdevice driver for the Jukebox Multimedia AV500");
MODULE_LICENSE("GPL");

EXPORT_SYMBOL(av500_report_user_activity);
