/*
 * linux/driver/char/gs4500.c
 *
 *	Scanner driver for GS4500: version 1.4
 *	19.5.95 Jan Willamowius (jan@janhh.shnet.org)
 *
 *	Changes:
 *	- modified for kernels > 1.1.80 and modutils-1.1.87
 *      - safe allocation of DMA buffer (finally)
 *      - added request_region()
 *
 *	Scanner driver for GS4500: version 1.3
 *	14.12.94 Jan Willamowius
 *
 *	Changes:
 *	- Check if system has more than 8 MB ram on module load
 *	  and give a warning.
 *	- updated request_dma for newer kernels
 *	- fixed cli()/sti()
 *
 *	Scanner driver for GS4500: version 1.2
 *	5.10.94	Jan Willamowius
 *
 *	Changes:
 *	- changed buffer allocation to make
 *	  loadable module work
 *	- changed ioctls to be compatible with M105, AC4096 and
 * 	  Logitech driver
 *
 *
 *	Scanner driver for GS4500: version 1.1
 *	1.10.94	Jan Willamowius
 *
 *	Changes:
 *	- added automatic detection what io adresses and
 *	  dma channel is set on the interface board
 *	- fixed a bug with dma channel selection
 *	- scanner dosn't flash when driver loads
 *	- changed device major to 26 like the other handheld
 *	  scanner drivers use (avoids conflict with ibcs and
 * 	  sound driver)
 *	- added fsync to file_operations table (= NULL)
 *	- adapted scandemo from logitech driver for gs4500
 *	- added the option to create a loadable kernel module
 *	- small changes here and there...
 *
 *	Supported Scanner:
 *	- Genius GeniScan GS4500
 *	? Genius GeniScan GS4000 (DOS drivers for the GS4000 work
 *	  with my GS4500 so I'd assume this driver works with a
 *	  GS4000)
 *	? Genius GeniScan GS4500A (again, just a guess)
 *
 *	
 *	Scanner driver for GS4500: version 1.0
 *
 *	Depending on how much scanner support we want, the
 *	following should be done:
 *
 *	    Split this file into device independent code (scanner.c)
 *	    and device dependent stuff (gs4500.c, artec.c, etc).
 *
 *	    Get an official major # allocated by RICK@EE.UWM.EDU.
 *
 *	    Create include/scanner.h with major/minors and
 *	    struct scan_info.
 *
 *	    Add configuration to toplevel makefile.
 *
 *	(C) Copyright 1992,1993 Richard Lyons
 *	      pclink@qus102.qld.tne.oz.au
 *	      rick@razorback.brisnet.org.au
 */

#ifdef MODULE
#include <linux/module.h>
#include <linux/version.h>
#include <linux/config.h>
#else	/* provide dummies */
#define MOD_INC_USE_COUNT
#define MOD_DEC_USE_COUNT
#endif
#include <scanner.h>
#include <linux/sched.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/malloc.h>
#include <linux/ioport.h>

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

#define SCANNER_NAME    "GeniScan GS4500"

#ifndef SCANNER_MAJOR
#define	SCANNER_MAJOR	26	/* major device */
#endif

#define BUFFER_SIZE	256	/* size of buffer for one scanline */

static struct {
	int data;
	int status;
	int control;
	int dmahold;
} gs_port[] = { 0x272, 0x273, 0x27a, 0x27b,
		0x2e1, 0x2e2, 0x2e3, 0x2e4,
		0x371, 0x372, 0x373, 0x374,
		0x3e1, 0x3e2, 0x3e3, 0x3e4 };

static unsigned int gs_dma, gs_data, gs_status, gs_control, gs_dmahold;

static	int	isopen = 0;
static	int	resolution = -1;
static 	char	*scanline = NULL;
static	int	cnt = 0;
static	int	dpi[] = { 100, 200, 300, 400 };
static	int	bytesperline[] = { 52, 103, 153, 206 };
static	int	gs_mode[] = { 0xF0, 0xA0, 0x80, 0xE0 };

static struct modeinfo_struct scan_info;

static struct scanner_capabilities scc_gs4500 = {

        SCC_HARDSWITCH,   		/* O_monochrome mode */
        SCC_HARDSWITCH,   		/* O_greyscale  mode */
        0,				/* O_true-color mode */

        SCC_HARDSWITCH | SCC_SOFTDETECT,/* Options for x resolution */
        SCC_HARDSWITCH | SCC_SOFTDETECT,/* Options for y resolution */

        0,                 		/* Options for brightness */
        {0,0},
        0,                              /* Options for contrast */
        {0,0},
        0,                              /* Options for hue */
        {0,0},
        0,                              /* Options for saturation */
        {0,0},

        105,                            /* Width in mm */
        0,                              /* Length in mm (unlimited) */
        0                               /* No options */
};

static struct scanner_type sctxt = { "Genius", "GS4500" };
 

static int scan1line(char *buf)
{
	unsigned long flags;

        save_flags(flags);      /* Save the current state here... */
	cli();
	disable_dma(gs_dma);
	clear_dma_ff(gs_dma);
	set_dma_mode(gs_dma, DMA_MODE_READ);
	set_dma_addr(gs_dma, (unsigned int)buf);
	set_dma_count(gs_dma, scan_info.bpl);
	enable_dma(gs_dma);
	restore_flags(flags);  /* Use this instead of sti() */
	outb(0, gs_dmahold);
	/* mask has to be set according to DMA channel ! (done now) */
	while (!(inb(DMA1_STAT_REG) & (1 << gs_dma)))
		if (current->signal & ~current->blocked)
			return 0;
		else
			schedule();
	return scan_info.bpl;
}

/* check for interface card and determin DMA and IO settings */
static int gs4500_detect(void) 
{
	int set = 3;
	int val;

	do {
		check_region(gs_port[set].data, 
			     (gs_port[set].dmahold-gs_port[set].data)+1);
		val = inb(gs_port[set].status);
		if(val != 0xff) break;
		set--;
	} while(set >= 0);
	if(set >= 0) {
		gs_data = gs_port[set].data;
		gs_status = gs_port[set].status;
		gs_control = gs_port[set].control;
		gs_dmahold = gs_port[set].dmahold;
	  	if((val & 0x02) == 0x00) { gs_dma = 1; return set; };
	  	if((val & 0x08) == 0x00) { gs_dma = 3; return set; };
		set = -1;   /* invalid DMA setting */
	}
	return set;	/* -1 means no device found, otherwise index */
}

static int gs4500_resolution(void)
{
	int	tries;
	int	cc = 0;

	outb(1, gs_control);
	outb(1, gs_dmahold);
	/* wait until AR is 1 fail after MANY reads */
	for (tries = 100000; --tries && !(inb(gs_status) & 0x80); ) ;
	/* wait until AR is 0 fail after MANY reads */
	if (tries)
		for (tries = 100000; --tries && ((cc = inb(gs_status)) & 0x80); ) ;
	outb(0, gs_control);
	return tries?cc:-EIO;
}

static int gs4500_open(struct inode * inode, struct file * file)
{
	int	cc;

	MOD_INC_USE_COUNT;
	if (isopen)
		return -EBUSY;
	if ((cc = gs4500_resolution()) < 0)
		return cc;
	switch (cc & 0xA4) {			/* determine dpi */
		case 0x24:	resolution = 0;
				break;
		case 0x20:	resolution = 1;
				break;
		case 0x04:	resolution = 2;
				break;
		case 0x00:	resolution = 3;
				break;
		default:	return -EIO;
				break;
	}
	scan_info.xdpi = scan_info.ydpi = dpi[resolution];
	scan_info.sc_mode = SCM_MONO;
	scan_info.depth = 1;
	scan_info.bpl = bytesperline[resolution];
	scan_info.left = scan_info.right = 0;
	scan_info.top = scan_info.bottom = 0;
	scan_info.bright = scan_info.contrast = 0;
	scan_info.hue = scan_info.sat = 0;
	scan_info.options = 0;
	isopen = 1;
	cnt = 0;
	outb(gs_mode[resolution]|1, gs_control);/* turn scanner on */
	outb(1, gs_dmahold);			/* disable DMA */
	return 0; 
}

static void gs4500_release(struct inode * inode, struct file * file)
{
	isopen = 0;
	outb(0, gs_control);
	MOD_DEC_USE_COUNT;
}

/*
 *	gs4500_read() and gs4500_ioctl() assume that they are only
 *	called after a successful gs4500_open().
 */

static int gs4500_read(struct inode * inode, struct file * file, char *buf, int size)
{
	int scanned = 0;
	int chars;
	char *p = NULL;

	while (size) {
		if (!cnt && (cnt = scan1line(p = scanline)) == 0)
			return scanned?scanned:-EIO;
		chars = cnt < size ? cnt : size;
		memcpy_tofs(buf, p, chars);
		size -= chars;
		cnt -= chars;
		p += chars;
		buf += chars;
		scanned += chars;
	}
	return scanned;
}

static int gs4500_ioctl(struct inode * inode, struct file * file, unsigned int cmd, unsigned long arg)
{
	switch (cmd) {
	case HSIOCGOPT: break;	/* no options supported */
	case HSIOCGSCC: if (arg)
			  memcpy_tofs((char *)arg, (char *)&scc_gs4500,
			              sizeof(struct scanner_capabilities));
			break;
	case HSIOCGSCT: if (arg)
			  memcpy_tofs((char *)arg, (char *)&sctxt,
			              sizeof(struct scanner_type));
			break;
	case HSIOCGMOD: if (arg)
			  memcpy_tofs((char *)arg, (char *)&scan_info,
			              sizeof(struct modeinfo_struct));
	                break;
	case HSIOCGBUF: break;	/* change of buffersize not supported */
	case HSIOCSBUF: if (arg)
			  put_fs_long((unsigned long) 1, arg);
			break;
	case HSIOCGSIB: if (arg)
			  put_fs_long((unsigned long) 0, arg);
			break;
	default:	return -EINVAL;
			break;
	}

	return 0;
}

static struct file_operations gs4500_fops = {
        NULL,		/* lseek */
	gs4500_read,	/* read */
	NULL,		/* write */
	NULL,		/* readdir */
	NULL,		/* select */
	gs4500_ioctl,	/* ioctl */
	NULL,		/* mmap */
        gs4500_open,	/* open */
        gs4500_release,	/* release */
	NULL		/* fsync */
};

static struct file_operations *gs4500_init(void)
{
	if(gs4500_detect() != -1) {
		if (request_dma(gs_dma, "gs4500") == 0) {
			printk("GS4500: DMA %d, at 0x%04x - 0x%04x\n", gs_dma, gs_data, gs_dmahold);
			return &gs4500_fops;
		}
		printk("GS4500: unable to get DMA %d\n", gs_dma);
	}
	else printk("GS4500: no usable scanner found.\n");
	return NULL;
}

#ifdef MODULE

char kernel_version[] = UTS_RELEASE;  /* needed for module installer */

int init_module(void)
{
	printk("GeniScan GS4500 handheld scanner driver, version 1.4\n");
  	if(register_chrdev(SCANNER_MAJOR, SCANNER_NAME, gs4500_init())) {
    		printk("GS4500: cannot register major number %d\n", SCANNER_MAJOR);
    		return -EIO;
  	}
	request_region(gs_data, (gs_dmahold-gs_data)+1, "gs4500");
	scanline = (char *)kmalloc(BUFFER_SIZE, GFP_KERNEL | GFP_DMA);
	if (scanline == NULL) {
		printk("GS4500: cannot allocate buffer");
		return -EIO;
	}
  	return 0;
}

void cleanup_module(void)
{
  	printk("Removing GeniScan GS4500 driver from memory\n");
  	if(MOD_IN_USE)
    		printk("GS4500: busy, remove delayed\n");
        else {
  		unregister_chrdev(SCANNER_MAJOR, SCANNER_NAME);
		release_region(gs_data, (gs_dmahold-gs_data)+1);
		kfree_s(scanline, BUFFER_SIZE);  /* release buffer memory */
  		free_dma(gs_dma);  /* release DMA channel */
	}
}

#else	/* part of the kernel */

unsigned long scanner_init(unsigned long kmem_start)
{
	struct file_operations *f;

	if ((f = gs4500_init()) != NULL) {
		if (register_chrdev(SCANNER_MAJOR, SCANNER_NAME, f))
    			printk("GS4500: cannot register major number %d\n", SCANNER_MAJOR);
	}
	request_region(gs_data, (gs_dmahold-gs_data)+1, "gs4500");
	scanline = (char *)kmem_start;
	return kmem_start += BUFFER_SIZE;
}
#endif
