/*
 * Amiga printer device by Michael Rausch (linux@uni-koblenz.de);
 * based upon work from
 *
 * Copyright (C) 1992 by Jim Weigand and Linus Torvalds
 * Copyright (C) 1992,1993 by Michael K. Johnson
 * - Thanks much to Gunter Windau for pointing out to me where the error
 *   checking ought to be.
 * Copyright (C) 1993 by Nigel Gamble (added interrupt code)
 */

#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/sched.h>
#include <linux/lp.h>
#include <linux/malloc.h>
#include <linux/interrupt.h>

#include <asm/segment.h>
#include <asm/system.h>

#include <linux/amigahw.h>
#include <linux/amigaints.h>

/*
 *  why bother around with the pio driver when the interrupt works;
 *  so, for "security" reasons only, it's configurable here.
 *  saves some bytes, at least ...
 */
#define FORCE_POLLING	 0
#define FORCE_INTERRUPT	 1
#define PREFER_INTERRUPT 2

#define WHICH_DRIVER	FORCE_INTERRUPT

/* 
 * All my debugging code assumes that you debug with only one printer at
 * a time. RWWH
 */

#undef LP_DEBUG

#ifdef LP_DEBUG
static int lp_max_count = 1;
#endif


#if WHICH_DRIVER != FORCE_INTERRUPT
static int lp_char_polled(char lpchar)
{
	unsigned long count  = 0; 

	do {
		count ++;
		if(need_resched)
			schedule();
	} while(LP_IS_BUSY && count < LP_CHAR);

	if (count == LP_CHAR) {
		return 0;
		/* we timed out, and the character was /not/ printed */
	}
#ifdef LP_DEBUG
	if (count > lp_max_count) {
		printk("lp success after %d counts.\n",count);
		lp_max_count=count;
	}
#endif
	LP_OUT(lpchar);	/* strobe is handled in hardware */
	return 1;
}
#endif


#ifdef LP_DEBUG
unsigned int lp_total_chars = 0;
unsigned int lp_last_call = 0;
#endif


#if WHICH_DRIVER != FORCE_POLLING
static int lp_char_interrupt(char lpchar)
{
	if (!LP_IS_BUSY) {
		LP_OUT(lpchar);
		return 1;
	}
	return 0;
}

static int do_print=0;
static unsigned long copy_size, bytes_written;

static void lp_interrupt(void)
{
	if(do_print)
	{
		if(copy_size)
		{
			cli();
			if (lp_char_interrupt(lp_table.lp_buffer[bytes_written])) {
				--copy_size;
				++bytes_written;
				sti();
			}
			else
			{
				do_print=0;
				sti();
				wake_up(&lp_table.lp_wait_q);
			}
		}
		else
		{
			do_print=0;
			wake_up(&lp_table.lp_wait_q);
		}

	}
}

#if WHICH_DRIVER == FORCE_INTERRUPT
static int lp_write(struct inode * inode, struct file * file, char * buf, int count)
#else
static int lp_write_interrupt(struct inode * inode, struct file * file, char * buf, int count)
#endif
{
	static unsigned long total_bytes_written = 0;

	do {
		bytes_written = 0;
		copy_size = (count <= LP_BUFFER_SIZE ? count : LP_BUFFER_SIZE);
		memcpy_fromfs(lp_table.lp_buffer, buf, copy_size);

		do {
			cli();
			do_print=1;

			/* initial output, to trigger the interrupt */
			if (lp_char_interrupt(lp_table.lp_buffer[bytes_written])) {
				--copy_size;
				++bytes_written;
				sti();

				/* ok, now wait for the interrupt routine to get ready */
				current->timeout = jiffies + LP_TIMEOUT_INTERRUPT;
				interruptible_sleep_on(&lp_table.lp_wait_q);

				/* just to be sure, if we timed out on this here */
				do_print=0;
			}
			else
			{
				do_print=0;
				sti();
			}

			if(copy_size) {
				int rc = total_bytes_written + bytes_written;

				if (LP_HAS_POUT) {
					printk("lp0: out of paper\n");
					if (!rc)
						rc = -ENOSPC;
				} else if (!LP_IS_ONLINE) {
					printk("lp0: off-line\n");
					if (!rc)
						rc = -EIO;
				} else if (LP_IS_BUSY) {
					printk("lp0: printer error\n");
					if (!rc)
						rc = -EIO;
				}
				if(LP_F & LP_ABORT)
					return rc;

				if (current->signal & ~current->blocked) {
					if (total_bytes_written + bytes_written)
						return total_bytes_written + bytes_written;
					else
						return -EINTR;
				}

				/* cli(); */
				current->state = TASK_INTERRUPTIBLE;
				current->timeout = jiffies + LP_TIMEOUT_POLLED;
				schedule();
			}
		} while(copy_size);

		total_bytes_written += bytes_written;
		buf += bytes_written;
		count -= bytes_written;

	} while (count > 0);

	return total_bytes_written;
}
#endif


#if WHICH_DRIVER != FORCE_INTERRUPT
#if WHICH_DRIVER == FORCE_POLLING
static int lp_write(struct inode * inode, struct file * file, char * buf, int count)
#else
static int lp_write_polled(struct inode * inode, struct file * file,
			   char * buf, int count)
#endif
{
	char *temp = buf;

#ifdef LP_DEBUG
	if (jiffies-lp_last_call > LP_TIME) {
		lp_total_chars = 0;
		lp_max_count = 1;
	}
	lp_last_call = jiffies;
#endif

	temp = buf;
	while (count > 0) {
		if (lp_char_polled( get_fs_byte(temp) )) {
			/* only update counting vars if character was printed */
			count--; temp++;
#ifdef LP_DEBUG
			lp_total_chars++;
#endif
		} else { /* if printer timed out */
			if (LP_HAS_POUT) {
				printk("lp0: out of paper\n");
				if(LP_F & LP_ABORT)
					return temp-buf?temp-buf:-ENOSPC;
				current->state = TASK_INTERRUPTIBLE;
				current->timeout = jiffies + LP_TIMEOUT_POLLED;
				schedule();
			} else
			if (!LP_IS_ONLINE) {
				printk("lp0: off-line\n");
				if(LP_F & LP_ABORT)
					return temp-buf?temp-buf:-EIO;
				current->state = TASK_INTERRUPTIBLE;
				current->timeout = jiffies + LP_TIMEOUT_POLLED;
				schedule();
			} else
	                /* not offline or out of paper. on fire? */
			if (LP_IS_BUSY) {
				printk("lp0: on fire\n");
				if(LP_F & LP_ABORT)
					return temp-buf?temp-buf:-EFAULT;
				current->state = TASK_INTERRUPTIBLE;
				current->timeout = jiffies + LP_TIMEOUT_POLLED;
				schedule();
			}

			/* check for signals before going to sleep */
			if (current->signal & ~current->blocked) {
				if (temp != buf)
					return temp-buf;
				else
					return -EINTR;
			}
#ifdef LP_DEBUG
			printk("lp sleeping at %d characters for %d jiffies\n",
				lp_total_chars, LP_TIME);
			lp_total_chars=0;
#endif
			current->state = TASK_INTERRUPTIBLE;
			current->timeout = jiffies + LP_TIME;
			schedule();
		}
	}
	return temp-buf;
}
#endif


#if WHICH_DRIVER == PREFER_INTERRUPT
static int lp_write(struct inode * inode, struct file * file, char * buf, int count)
{
	if (LP_IRQ)
		return lp_write_interrupt(inode, file, buf, count);
	else
		return lp_write_polled(inode, file, buf, count);
}
#endif

static int lp_lseek(struct inode * inode, struct file * file,
		    off_t offset, int origin)
{
	return -ESPIPE;
}

static int lp_open(struct inode * inode, struct file * file)
{
	if (MINOR(inode->i_rdev) >= LP_NO)
		return -ENODEV;
	if (!(LP_F & LP_EXIST))
		return -ENODEV;
	if (LP_F & LP_BUSY)
		return -EBUSY;

	LP_F |= LP_BUSY;

	ciaa.ddrb = 0xff;	/* set the correct data directions */
	ciab.ddra &= 0xf8;	/* maybe changed in some plip code */

	return 0;
}

static void lp_release(struct inode * inode, struct file * file)
{
	LP_F &= ~LP_BUSY;
}


static int lp_ioctl(struct inode *inode, struct file *file,
		    unsigned int cmd, unsigned long arg)
{
	unsigned int minor = MINOR(inode->i_rdev);
	int retval = 0;

#ifdef LP_DEBUG
	printk("lp%d ioctl, cmd: 0x%x, arg: 0x%x\n", minor, cmd, arg);
#endif
	if (minor >= LP_NO)
		return -ENODEV;
	if (!(LP_F & LP_EXIST))
		return -ENODEV;
	switch ( cmd ) {
		case LPTIME:
			LP_TIME = arg;
			break;
		case LPCHAR:
			LP_CHAR = arg;
			break;
		case LPABORT:
			if (arg)
				LP_F |= LP_ABORT;
			else
				LP_F &= ~LP_ABORT;
			break;
		case LPWAIT:	/* set/get interrupt is ignored, strobe handled by hardware */
		case LPSETIRQ:
		case LPGETIRQ:
			break;
		default:
			retval = -EINVAL;
	}
	return retval;
}


static struct file_operations lp_fops = {
	lp_lseek,
	NULL,		/* lp_read */
	lp_write,
	NULL,		/* lp_readdir */
	NULL,		/* lp_select */
	lp_ioctl,
	NULL,		/* lp_mmap */
	lp_open,
	lp_release
};

long lp_init(long kmem_start)
{
	if (register_chrdev(LP_MAJOR,"lp",&lp_fops)) {
		printk("unable to get major %d for line printer\n", LP_MAJOR);
		return kmem_start;
	}

#if WHICH_DRIVER == FORCE_POLLING
	lp_table.irq = 0;
	printk("lp_init: lp0 using polling driver\n");
#else

	if((lp_table.irq = mach_add_isr(IRQ_AMIGA_CIAA_FLG, (isrfunc)lp_interrupt, 0, NULL)))
	{
		lp_table.lp_buffer = (char*)kmem_start;
		kmem_start += LP_BUFFER_SIZE;
		printk("lp_init: lp0 using cia interrupt\n");
	}
	else

#if WHICH_DRIVER == PREFER_INTERRUPT
		printk("lp_init: lp0 using polling driver\n");
#else
	{
		printk("cannot get cia interrupt for lp0, and polling driver not configured.\n");
		return kmem_start;
	}
#endif
#endif

	LP_F |= LP_EXIST;
	return kmem_start;
}
