/*
 * Driver for the Compaq iPAQ Mercury Backpaq camera
 * Video4Linux interface
 *
 * Copyright 2001 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: Andrew Christian 
 *         <andyc@handhelds.org>
 *         4 May 2001
 *
 * Driver for Mercury BackPAQ camera
 *
 * Issues to be addressed:
 *    1. Writing to the FPGA when we need to do a functionality change
 *    2. Sampling the pixels correctly and building a pixel array
 *    3. Handling different pixel formats correctly
 *    4. Changing the contrast, brightness, white balance, and so forth.
 *    5. Specifying a subregion (i.e., setting "top, left" and SUBCAPTURE)
 */

/*
 * Driver for the Compaq iPAQ Mercury Backpaq camera
 * Video4Linux interface
 *
 * Copyright 2001 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: Andrew Christian 
 *         <andyc@handhelds.org>
 *         4 May 2001
 *
 */

#include "h3600_backpaq_camera.h"
#include <linux/module.h>
#include <linux/version.h>

#include <linux/init.h>
#include <asm/uaccess.h>         /* get_user,copy_to_user*/

#include <linux/vmalloc.h>
#include <linux/proc_fs.h>

#include <asm/io.h>
#include <linux/wrapper.h>
/* #include <asm/arch/hardware.h>*/  /* Included in <asm/io.h> */
#include <asm/arch/backpaq.h>
#include <asm/irq.h>

/* Actual chip specifications */
#define HC_TRUE_WIDTH           644    /* Last two columns are black */
#define HC_TRUE_HEIGHT          482
#define HC_RAW_BUFFER_SIZE      ((u32) HC_TRUE_WIDTH * (u32) HC_TRUE_HEIGHT )

/* Largest frame buffer for 24 bit color (we don't do 32 bit color */
#define HC_MAX_ALLOWED_WIDTH        640
#define HC_MAX_ALLOWED_HEIGHT       480
#define HC_MIN_ALLOWED_WIDTH        160
#define HC_MIN_ALLOWED_HEIGHT       120

/* Memory-mapped frame size */
#define HC_MAX_COOKED_FRAME_UNALIGNED   (HC_MAX_ALLOWED_WIDTH * HC_MAX_ALLOWED_HEIGHT * 3)
#define HC_MAX_COOKED_FRAME_SIZE        ((HC_MAX_COOKED_FRAME_UNALIGNED + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1))

/* Camera modes */
#define HC_MODE_RAW             0
#define HC_MODE_DECIMATION_1	1
#define HC_MODE_DECIMATION_2	2
#define HC_MODE_DECIMATION_4	3

struct h3600_size_mode {
	int width;
	int height;
	int mode;
};

/* Core states */
#define HC_V4L_IDLE              0
#define HC_V4L_GRABBING          1  /* A "read" command has been issued */
#define HC_V4L_STREAMING         2  /* VIDIOCMCAPTURE active and there is an available buffer */
#define HC_V4L_STREAMING_PAUSED  3  /* VIDIOCMCAPTURE active, no buffers available */

/* HC_V4L_GRABBING : substate */
#define GRAB_WAIT_FOR_REFRESH    0
#define GRAB_ACTIVE              1
#define GRAB_DONE                2
#define GRAB_ERROR               3

/* A single frame of raw data from the camera       */
/* We can always tell the active mode from the size */
struct raw_frame {
	unsigned char *data;
	int            bytes_read;     /* How full it is */
	int            bytes_to_read;  /* How much to get */
	int            width;
	int            height;
	int            row_width;
	volatile int   state;  /* idle, active, done */
};

#define NUMBER_OF_RAW_FRAMES  3

/* A processed batch of data */

#define COOKED_FRAME_IDLE     0
#define COOKED_FRAME_ACTIVE   1
#define COOKED_FRAME_DONE     2
#define COOKED_FRAME_ERROR    3

struct cooked_frame {
	unsigned char *data;   /* Points into "frame_buf" */
	int            count;  /* Bytes of data */
	int            width;  /* Picture width */
	int            height; /* Picture height */
	volatile int   state;  /* Current state COOKED_FRAME_XXX */
};

#define NUMBER_OF_COOKED_FRAMES	2	/* double buffering for now */

  
struct h3600_camera_struct {
	struct video_device     vdev;   /* This must be first */
	struct video_picture    vpic;   /* v4l camera settings */
	struct video_window     vwin;   /* v4l capture area */
	struct h3600_backpaq_camera_params params;   /* Our special settings */

	volatile int            state;	/* v4l current state */
	struct raw_frame        frame;

	int mode;                  /* Mode index selection                   */

	struct semaphore        lock;          /* Force single access */
	wait_queue_head_t       capq;          /* Wait queue */
	int                     usage_count;   /* How many are open */
	int                     fifo_high;     /* Tracks how deep the fifo goes */
	int                     fifo_low;

	/* Count interrupts */
	int                     vblank_count;
	int                     fifo_count;

	/* mmap interface */
	int                     curframe;       /* the current frame to grab into */
	unsigned char *         frame_buf;      /* frame buffer data (raw bits stored here) */
        struct cooked_frame     cooked_frame[NUMBER_OF_COOKED_FRAMES];
};


#define BACKPAQ_CAMERA_DEBUG
// #undef BACKPAQ_CAMERA_DEBUG

#ifdef BACKPAQ_CAMERA_DEBUG
#define CAMDEBUG(format,args...)  printk(KERN_ALERT __FILE__ format, ## args)
#else
#define CAMDEBUG(x)     do { } while(0)
#endif

#define CAMERROR(format,args...)  printk(KERN_ERR  __FILE__ format, ## args)

/* Useful statics */

static struct h3600_size_mode hc_default_resolutions[] = {
	{ 160, 120, HC_MODE_DECIMATION_4 },
	{ 320, 240, HC_MODE_DECIMATION_2 },
	{ 640, 480, HC_MODE_DECIMATION_1 },
	{ 0, 0, 0 }
};

/* Useful external variables */
/* Defined in h3600_backpaq_fpga.c */

extern struct proc_dir_entry *proc_backpaq_dir;
extern devfs_handle_t         devfs_backpaq_dir;

/* Global variables */

static struct h3600_camera_struct hc_camera;     /* We only have a single camera */
static struct proc_dir_entry     *proc_camera = NULL;

static volatile struct mercury_backpaq_socket *mercury_backpaq_socket 
     = ((struct mercury_backpaq_socket *)BACKPAQ_SOCKET_STATUS_BASE);

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

unsigned int h3600_camera_debug = 0;

/* insmod options */

MODULE_PARM(h3600_camera_debug,"i");
MODULE_PARM_DESC(h3600_camera_debug,"debug messages, default is 0 (no)");

#define BANNER "Compaq iPAQ H3600 Mercury BackPAQ Camera for Video4Linux"

MODULE_DESCRIPTION(BANNER);
MODULE_AUTHOR("Andrew Christian <andyc@handhelds.org>");

/*******************************
 *  Utility routines 
 *******************************/

static void set_camera_resolution( struct h3600_camera_struct *cam,
				   int width, int height )
{
	struct h3600_size_mode *m = hc_default_resolutions;

	if ( cam->vpic.palette == VIDEO_PALETTE_RAW ) {
		cam->vwin.width  = HC_TRUE_WIDTH;
		cam->vwin.height = HC_TRUE_HEIGHT;
		cam->mode   = HC_MODE_RAW;
		return;
	}

	do {
		cam->vwin.width = m->width;
		cam->vwin.height = m->height;
		cam->mode = m->mode;
		m++;
	} while ( m->width > 0 && width >= m->width && height >= m->height );
}

static unsigned long required_buf_size( struct h3600_camera_struct *cam )
{
	return ((unsigned long) cam->vwin.width) * cam->vwin.height 
		* ( cam->vpic.palette == VIDEO_PALETTE_RGB24 ? 3 : 1 );
}


/*******************************
 * Memory management functions  
 *  
 * Copied from bttv-driver.c
 *******************************/

#define MDEBUG(x)	do { } while(0)		/* Debug memory management */

/* Given PGD from the address space's page table, return the kernel
 * virtual mapping of the physical memory mapped at ADR.
 */
static inline unsigned long uvirt_to_kva(pgd_t *pgd, unsigned long adr)
{
        unsigned long ret = 0UL;
	pmd_t *pmd;
	pte_t *ptep, pte;
  
	if (!pgd_none(*pgd)) {
                pmd = pmd_offset(pgd, adr);
                if (!pmd_none(*pmd)) {
                        ptep = pte_offset(pmd, adr);
                        pte = *ptep;
                        if(pte_present(pte)) {
				ret  = (unsigned long) page_address(pte_page(pte));
				ret |= (adr & (PAGE_SIZE - 1));
				
			}
                }
        }
        MDEBUG(printk("uv2kva(%lx-->%lx)", adr, ret));
	return ret;
}

static inline unsigned long uvirt_to_bus(unsigned long adr) 
{
        unsigned long kva, ret;

        kva = uvirt_to_kva(pgd_offset(current->mm, adr), adr);
	ret = virt_to_bus((void *)kva);
        MDEBUG(printk("uv2b(%lx-->%lx)", adr, ret));
        return ret;
}

static inline unsigned long kvirt_to_bus(unsigned long adr) 
{
        unsigned long va, kva, ret;

        va = VMALLOC_VMADDR(adr);
        kva = uvirt_to_kva(pgd_offset_k(va), va);
	ret = virt_to_bus((void *)kva);
        MDEBUG(printk("kv2b(%lx-->%lx)", adr, ret));
        return ret;
}

/* Here we want the physical address of the memory.
 * This is used when initializing the contents of the
 * area and marking the pages as reserved.
 */
static inline unsigned long kvirt_to_pa(unsigned long adr) 
{
        unsigned long va, kva, ret;

        va = VMALLOC_VMADDR(adr);
        kva = uvirt_to_kva(pgd_offset_k(va), va);
	ret = __pa(kva);
        MDEBUG(printk("kv2pa(%lx-->%lx)", adr, ret));
        return ret;
}

static void * rvmalloc(signed long size)
{
	void * mem;
	unsigned long adr, page;

	mem=vmalloc_32(size);
	if (mem) 
	{
		memset(mem, 0, size); /* Clear the ram out, no junk to the user */
	        adr=(unsigned long) mem;
		while (size > 0) 
                {
	                page = kvirt_to_pa(adr);
			mem_map_reserve(virt_to_page(__va(page)));
			adr+=PAGE_SIZE;
			size-=PAGE_SIZE;
		}
	}
	return mem;
}

static void rvfree(void * mem, signed long size)
{
        unsigned long adr, page;
        
	if (mem) 
	{
	        adr=(unsigned long) mem;
		while (size > 0) 
                {
	                page = kvirt_to_pa(adr);
			mem_map_unreserve(virt_to_page(__va(page)));
			adr+=PAGE_SIZE;
			size-=PAGE_SIZE;
		}
		vfree(mem);
	}
}

static int allocate_frame_buf(struct h3600_camera_struct *cam)
{
	int i;

	cam->frame_buf = rvmalloc(NUMBER_OF_COOKED_FRAMES * HC_MAX_COOKED_FRAME_SIZE);
	if (!cam->frame_buf)
		return -ENOBUFS;

	for (i = 0; i < NUMBER_OF_COOKED_FRAMES; i++)
		cam->cooked_frame[i].data = cam->frame_buf + i * HC_MAX_COOKED_FRAME_SIZE;

	return 0;
}

static int free_frame_buf(struct h3600_camera_struct *cam)
{
	int i;
	
	rvfree(cam->frame_buf, NUMBER_OF_COOKED_FRAMES * HC_MAX_COOKED_FRAME_SIZE);
	cam->frame_buf = 0;
	for (i=0; i < NUMBER_OF_COOKED_FRAMES; i++)
		cam->cooked_frame[i].data = NULL;

	return 0;
}

/*
static void inline free_frames(struct cooked_frame frame[NUMBER_OF_COOKED_FRAMES])
{
	int i;

	for (i=0; i < NUMBER_OF_COOKED_FRAMES; i++)
		frame[i].state = FRAME_UNUSED;
	return;
}
*/

/* 
   Decimate in place, assuming a standard Bayer pattern starting at 0,0
   The subsampled image starts at offset x,y.
   We update "columns" and "rows" to return the new number of columns 
   and rows in the image.

   Bayer pattern is assumed to be:

       R G 
       G B

   starting at the top left corner.
   "Width" and "Height" should be multiples of two with x + width <= *columns, etc.

   We make _tons_ of assumptions

   Assumptions
   1. Error checking of bounds has been done.
   2. Width and height are multiples of 4
*/

static void decimate( struct raw_frame *frame )
{
	/* Pick results that make sense */
	int width  = (frame->width / 2) & 0xfffc;
	int height = (frame->height / 2) & 0xfffc;

	unsigned char *p    = frame->data;   /* We'll store data at here */
	unsigned char *colp = frame->data;

	unsigned char *q;   /* We'll read data from here */
	int onecol = frame->width;
	int twocol = frame->width * 2;
	int i,j;

	for ( j = 0 ; j < height ; j+= 2) {
		q = colp;
		for ( i = 0 ; i < width ; i+=2 ) {
			/* Do red */
			*p++ = (((unsigned short) *q) * 2 
				+ (unsigned short) *(q + 2) 
				+ (unsigned short) *(q + twocol)) / 4;
			q+=2;
			/* Do green */
			*p++ = (((unsigned short) *(q + 1)
				 + (unsigned short) *(q + onecol)) / 2 );
			q+=2;
		}
		colp += twocol;
		q = colp;
		for ( i = 0 ; i < width ; i+=2 ) {
			/* Do green */
			*p++ = (((unsigned short) *(q + 1)
				 + (unsigned short) *(q + onecol)) / 2 );
			q+=2;
			/* Do blue */
			*p++ = (((unsigned short) *(q + onecol + 1)) * 2 
				+ (unsigned short) *(q + onecol - 1) 
				+ (unsigned short) *(q - onecol + 1)) / 4;
			q+=2;
		}
		colp += twocol;
	}
	frame->width = width;
	frame->height = height;
	frame->row_width = width;
}


/******************************************************************************
 *
 * Processing
 *
 ******************************************************************************/

/* Uses "from", "width" */
#define PIXEL_CALCULATE(a1,a2,a3,b1,b2,b3,c1,c2,c3) \
  (( \
    a1 * ((uint)*(from-width-1)) + \
    a2 * ((uint)*(from-width)) +   \
    a3 * ((uint)*(from-width+1)) + \
    b1 * ((uint)*(from-1)) +       \
    b2 * ((uint)*(from)) +         \
    b3 * ((uint)*(from+1)) +       \
    c1 * ((uint)*(from+width-1)) + \
    c2 * ((uint)*(from+width)) +   \
    c3 * ((uint)*(from+width+1))   \
  ) / (a1 + a2 + a3 + b1 + b2 + b3 + c1 + c2 + c3 ))

/* See "Video Demystified, pg 16, third edition */

#define FLOAT_SCALE_FACTOR   16
#define FLOAT_SCALE_VALUE   (1<<FLOAT_SCALE_FACTOR)
#define R_TO_Y  ((uint)(0.299 * FLOAT_SCALE_VALUE + 0.5))
#define G_TO_Y  ((uint)(0.587 * FLOAT_SCALE_VALUE + 0.5))
#define B_TO_Y  ((uint)(0.114 * FLOAT_SCALE_VALUE + 0.5))

#define Y_VALUE(red,green,blue) \
     (((R_TO_Y * red) + (G_TO_Y * green) + (B_TO_Y * blue)) >> FLOAT_SCALE_FACTOR)

#define DO_RED_PIXEL \
    *to++ = *from;                                                   \
    *to++ = ((ushort)*(from-width) + (ushort)*(from+width) +         \
             (ushort)*(from-1) + (ushort)*(from+1)) / 4;             \
    *to++ = ((ushort)*(from-width-1) + (ushort)*(from-width+1) +     \
             (ushort)*(from+width-1) + (ushort)*(from+width+1)) / 4; \
     from++

#define DO_RED_TOP_PIXEL \
    *to++ = *from;                                                   \
    *to++ = ((ushort)*(from+width) * 2 +                             \
             (ushort)*(from-1) + (ushort)*(from+1)) / 4;             \
    *to++ = ((ushort)*(from+width-1) + (ushort)*(from+width+1)) / 2; \
     from++

#define DO_RED_LEFT_PIXEL \
    *to++ = *from;                                                   \
    *to++ = ((ushort)*(from-width) + (ushort)*(from+width) +         \
             + 2 * (ushort)*(from+1)) / 4;                           \
    *to++ = ((ushort)*(from-width+1) + (ushort)*(from+width+1)) / 2; \
     from++

#define DO_RED_TOP_LEFT_PIXEL \
    *to++ = *from;                                                   \
    *to++ = ((ushort)*(from+width) + (ushort)*(from+1)) / 2;         \
    *to++ = *(from+width+1);                                         \
     from++

#define DO_BLUE_PIXEL \
    *to++ = ((ushort)*(from-width-1) + (ushort)*(from-width+1) +     \
             (ushort)*(from+width-1) + (ushort)*(from+width+1)) / 4; \
    *to++ = ((ushort)*(from-width) + (ushort)*(from+width) +         \
             (ushort)*(from-1) + (ushort)*(from+1)) / 4;             \
    *to++ = *from++;  

#define DO_BLUE_RIGHT_PIXEL \
    *to++ = ((ushort)*(from-width-1) + (ushort)*(from+width-1)) / 2; \
    *to++ = ((ushort)*(from-width) + (ushort)*(from+width) +         \
             (ushort)*(from-1) * 2) / 4;                             \
    *to++ = *from++

#define DO_BLUE_BOTTOM_PIXEL \
    *to++ = ((ushort)*(from-width-1) + (ushort)*(from-width+1)) / 2; \
    *to++ = ((ushort)*(from-width) * 2 +                             \
             (ushort)*(from-1) + (ushort)*(from+1)) / 4;             \
    *to++ = *from++

#define DO_BLUE_BOTTOM_RIGHT_PIXEL \
    *to++ = *(from-width-1);                                         \
    *to++ = ((ushort)*(from-width) + (ushort)*(from-1)) / 2;         \
    *to++ = *from++

#define DO_GREEN_ODD_PIXEL \
    *to++ = ((ushort)*(from-1) + (ushort)*(from+1)) / 2;         \
    *to++ = *from;                                               \
    *to++ = ((ushort)*(from-width) + (ushort)*(from+width)) / 2; \
     from++

#define DO_GREEN_EVEN_PIXEL \
    *to++ = ((ushort)*(from-width) + (ushort)*(from+width)) / 2; \
    *to++ = *from;                                               \
    *to++ = ((ushort)*(from-1) + (ushort)*(from+1)) / 2; \
     from++

#define DO_GREEN_TOP_PIXEL \
    *to++ = ((ushort)*(from-1) + (ushort)*(from+1)) / 2;         \
    *to++ = *from;                                               \
    *to++ = *(from+width); \
     from++

#define DO_GREEN_TOP_RIGHT_PIXEL \
    *to++ = *(from-1);      \
    *to++ = *from;          \
    *to++ = *(from+width); \
     from++

#define DO_GREEN_LEFT_PIXEL \
    *to++ = ((ushort)*(from-width) + (ushort)*(from+width)) / 2; \
    *to++ = *from;                                               \
    *to++ = *(from+1);     \
     from++

#define DO_GREEN_BOTTOM_LEFT_PIXEL \
    *to++ = *(from-width);           \
    *to++ = *from;                   \
    *to++ = *(from+1);               \
     from++

#define DO_GREEN_RIGHT_PIXEL \
    *to++ = *(from-1);                                           \
    *to++ = *from;                                               \
    *to++ = ((ushort)*(from-width) + (ushort)*(from+width)) / 2; \
     from++

#define DO_GREEN_BOTTOM_PIXEL \
    *to++ = *(from-width);                                       \
    *to++ = *from;                                               \
    *to++ = ((ushort)*(from-1) + (ushort)*(from+1)) / 2;         \
     from++

#define FLUSH_PIXELS \
    __copy_to_user( buf, rgb_buf, width * 3 );     \
    buf += width * 3; \
    result += width * 3


/* This function assumes that we have a completely full buffer */
static unsigned long write_full_rgb( struct raw_frame *frame, char *buf )
{
	unsigned char *from = frame->data;
	int width = frame->width;
	int height = frame->height;
	unsigned char rgb_buf[644 * 3];
	unsigned char *to;
	int i, j;
	unsigned long result = 0;

	/* Write the top row */
	to = rgb_buf;
	DO_RED_TOP_LEFT_PIXEL;
	for ( i = 1 ; i < width - 1 ; i+=2 ) {
		DO_GREEN_TOP_PIXEL;
		DO_RED_TOP_PIXEL;
	}
	DO_GREEN_TOP_RIGHT_PIXEL;
	FLUSH_PIXELS;

	for ( j = 1 ; j < height - 1 ; j += 2 ) {
		/* Write an even row */
		to = rgb_buf;
		DO_GREEN_LEFT_PIXEL;
		for ( i = 1 ; i < width - 1 ; i+= 2 ) {
			DO_BLUE_PIXEL;
			DO_GREEN_EVEN_PIXEL;
		}
		DO_BLUE_RIGHT_PIXEL;
		FLUSH_PIXELS;

		/* Write an odd row */
		to = rgb_buf;
		DO_RED_LEFT_PIXEL;
		for ( i = 1 ; i < width - 1 ; i+= 2 ) {
			DO_GREEN_ODD_PIXEL;
			DO_RED_PIXEL;
		}
		DO_GREEN_RIGHT_PIXEL;
		FLUSH_PIXELS;
	}
	
	/* Write the bottom row */

	to = rgb_buf;
	DO_GREEN_BOTTOM_LEFT_PIXEL;
	for ( i = 1 ; i < width - 1 ; i+= 2 ) {
		DO_BLUE_BOTTOM_PIXEL;
		DO_GREEN_BOTTOM_PIXEL;
	}
	DO_BLUE_BOTTOM_RIGHT_PIXEL;
	FLUSH_PIXELS;
	
	return result;
}

/* This function assumes that we're clipped in by one pixel on top and left */
static unsigned long write_inset_rgb( struct raw_frame *frame, char *buf )
{
	unsigned char *source = frame->data;
	int width = frame->width;
	int height = frame->height;
	int row_width = frame->row_width;

	unsigned char rgb_buf[644 * 3];
	unsigned char *to;
	unsigned char *from;
	unsigned long result = 0;
	int i, j;

	for ( j = 0 ; j < height ; ) {
		/* Write an even row */
		from = source + (++j) * row_width + 1;
		to = rgb_buf;
		for ( i = 0 ; i < width ; i += 2 ) {
			DO_BLUE_PIXEL;
			DO_GREEN_EVEN_PIXEL;
		}
		FLUSH_PIXELS;

		/* Write an odd row */
		from = source + (++j) * row_width + 1;
		to = rgb_buf;
		for ( i = 0 ; i < width ; i += 2 ) {
			DO_GREEN_ODD_PIXEL;
			DO_RED_PIXEL;
		}
		FLUSH_PIXELS;
	}
	return result;
}


#define GREY_RED_PIXEL \
    *to++ = Y_VALUE( *from, \
                      PIXEL_CALCULATE(0,1,0, 1,0,1, 0,1,0), \
		      PIXEL_CALCULATE(1,0,1, 0,0,0, 1,0,1)); \
     from++

#define GREY_GREEN_EVEN_PIXEL \
    *to++ = Y_VALUE( PIXEL_CALCULATE(0,1,0, 0,0,0, 0,1,0), \
                     *from, \
                     PIXEL_CALCULATE(0,0,0, 1,0,1, 0,0,0)); \
     from++

#define GREY_GREEN_ODD_PIXEL \
    *to++ = Y_VALUE( PIXEL_CALCULATE(0,0,0, 1,0,1, 0,0,0), \
                     *from, \
                     PIXEL_CALCULATE(0,1,0, 0,0,0, 0,1,0)); \
     from++;

#define	GREY_BLUE_PIXEL \
    *to++ = Y_VALUE( PIXEL_CALCULATE(1,0,1, 0,0,0, 1,0,1), \
                     PIXEL_CALCULATE(0,1,0, 1,0,1, 0,1,0), \
                     *from); \
     from++

#define	GREY_FLUSH_PIXELS \
    __copy_to_user( buf, grey_buf, width );     \
    buf += width; \
    result += width

static unsigned long write_inset_grey( struct raw_frame *frame, char *buf )
{
	unsigned char *source = frame->data;
	int width = frame->width;
	int height = frame->height;
	int row_width = frame->row_width;

	unsigned char grey_buf[644];
	unsigned char *to;
	unsigned char *from;
	unsigned long result = 0;
	int i, j;

	for ( j = 0 ; j < height ; ) {
		/* Write an even row */
		from = source + (++j) * row_width + 1;
		to = grey_buf;
		for ( i = 0 ; i < width ; i += 2 ) {
			GREY_BLUE_PIXEL;
			GREY_GREEN_EVEN_PIXEL;
		}
		GREY_FLUSH_PIXELS;

		/* Write an odd row */
		from = source + (++j) * row_width + 1;
		to = grey_buf;
		for ( i = 0 ; i < width ; i += 2 ) {
			GREY_GREEN_ODD_PIXEL;
			GREY_RED_PIXEL;
		}
		GREY_FLUSH_PIXELS;
	}
	return result;
}

/* Write the state out into a buffer */
/* We've already checked that the buffer is of the required size and has been verified */

static long process_frame( struct h3600_camera_struct *cam, unsigned char *buf )
{
	struct raw_frame *frame = &cam->frame;
	unsigned long result = 0;

	switch (cam->mode) {
	case HC_MODE_RAW:
		__copy_to_user( buf, frame->data, HC_RAW_BUFFER_SIZE );
		result = HC_RAW_BUFFER_SIZE; 
		break;

	case HC_MODE_DECIMATION_1:
		frame->width = cam->vwin.width;
		frame->height = cam->vwin.height;
		if ( cam->vpic.palette == VIDEO_PALETTE_GREY )
			result = write_inset_grey(frame,buf);
		else
			result = write_inset_rgb(frame,buf);
		break;

	case HC_MODE_DECIMATION_4:
		decimate( frame );
		/* Intentional fall through */
	case HC_MODE_DECIMATION_2:
		decimate( frame );
		if ( cam->vpic.palette == VIDEO_PALETTE_GREY ) {
			result = frame->width * frame->height;
			__copy_to_user(buf, frame->data, result);
		}
		else
			result = write_full_rgb(frame,buf);
		break;
	}
	return result;
}

/******************************************************************************
 *
 * Capture routines
 *
 ******************************************************************************/

#define ENABLE_OPT_INT(x) \
		mercury_backpaq_socket->fpga_interrupt_mask &= ~(x)
#define DISABLE_OPT_INT(x) \
		mercury_backpaq_socket->fpga_interrupt_mask |= (x)
#define READ_OPT_INT(x) \
		mercury_backpaq_socket->fpga_interrupt_status & (x) \
                & ~mercury_backpaq_socket->fpga_interrupt_mask

#define ENABLE_VBLANK_INT   ENABLE_OPT_INT(BACKPAQ_SOCKET_INT_VBLANK)
#define DISABLE_VBLANK_INT  DISABLE_OPT_INT(BACKPAQ_SOCKET_INT_VBLANK)
#define READ_VBLANK_INT     READ_OPT_INT(BACKPAQ_SOCKET_INT_VBLANK)

#define ENABLE_FIFO_INT     ENABLE_OPT_INT(BACKPAQ_SOCKET_INT_FIFO)
#define DISABLE_FIFO_INT    DISABLE_OPT_INT(BACKPAQ_SOCKET_INT_FIFO)
#define READ_FIFO_INT       READ_OPT_INT(BACKPAQ_SOCKET_INT_FIFO)

#define ENABLE_BOTH_INT     ENABLE_OPT_INT(BACKPAQ_SOCKET_INT_FIFO | BACKPAQ_SOCKET_INT_VBLANK)
#define DISABLE_BOTH_INT    DISABLE_OPT_INT(BACKPAQ_SOCKET_INT_FIFO | BACKPAQ_SOCKET_INT_VBLANK)
#define READ_BOTH_INT       READ_OPT_INT(BACKPAQ_SOCKET_INT_FIFO | BACKPAQ_SOCKET_INT_VBLANK)

#define IMAGER_CTL_GAIN_FORMAT         0x70
#define IMAGER_CTL_POWER_SETTING       0x80

/* Wait until the interrupts have filled the frame */

/* Turn off interrupts before calling this */
static void prep_camera( struct h3600_camera_struct *cam )
{
	/* We set up for grabbing at true width and height */
	cam->frame.state         = GRAB_WAIT_FOR_REFRESH;  /* Waiting for top of frame */
	cam->frame.bytes_to_read = HC_RAW_BUFFER_SIZE;
	cam->frame.width         = HC_TRUE_WIDTH;          
	cam->frame.height        = HC_TRUE_HEIGHT;
	cam->frame.row_width     = HC_TRUE_WIDTH;
	cam->frame.bytes_read    = 0;                      /* No data grabbed yet */

	mercury_backpaq_socket->camera_clock_divisor = cam->params.clock_divisor; 
	mercury_backpaq_socket->camera_writethru     
		= IMAGER_CTL_POWER_SETTING | cam->params.power_setting;
	mercury_backpaq_socket->camera_writethru     
		= IMAGER_CTL_GAIN_FORMAT | cam->params.gain_format;
	mercury_backpaq_socket->camera_interrupt_fifo = cam->params.interrupt_fifo;
	mercury_backpaq_socket->camera_integration_time = 512 - (cam->vpic.brightness / 128);
	mercury_backpaq_socket->camera_live_mode     = 0x01;    /* Turn on live video mode */

	cam->state = HC_V4L_GRABBING; 
	cam->fifo_high = 0;
	cam->fifo_low = 0xffff;  /* An impossible number */
	cam->fifo_count = 0;
	cam->vblank_count = 0;
}

static long grab_frame( struct h3600_camera_struct *cam )
{
	int retval;
	unsigned long flags;

	save_flags(flags); cli();
	prep_camera(cam);
	DISABLE_FIFO_INT;       /* Disable the regular FIFO interrupt */
	ENABLE_VBLANK_INT;	/* Turn on just the vertical retrace interrupt */
	restore_flags(flags);

	/* Sit on a wait queue until the GRAB is done */
	/* Wait until this is true */
	retval = wait_event_interruptible(cam->capq,
					  cam->state != HC_V4L_GRABBING 
					  || cam->frame.state == GRAB_DONE
		                          || cam->frame.state == GRAB_ERROR);

	/* Turn off all interrupts */
	save_flags(flags); cli();
	DISABLE_BOTH_INT;
	cam->state = HC_V4L_IDLE;
	restore_flags(flags);

	if ( retval < 0 ) {
		CAMDEBUG(": Return value error %d\n", retval);
		return retval;
	}

	if ( cam->frame.state == GRAB_ERROR ) {
		CAMDEBUG(": Grabbing error\n");
		return -EAGAIN;
	}

#ifdef BACKPAQ_CAMERA_DEBUG
	printk(KERN_ALERT __FILE__ ": Read image (size %d)\n" 
	       "  Number of interrupts:   FIFO=%d  VBLANK=%d\n"
	       "  On interrupt, fifo       min=%d max=%d status=%x\n",
	       cam->frame.bytes_read,
	       cam->fifo_count, cam->vblank_count,
	       cam->fifo_low, cam->fifo_high,
	       mercury_backpaq_socket->fpga_interrupt_status);
#endif

	return cam->frame.bytes_read;
}

static int polling_read_picture( struct h3600_camera_struct *cam )
{
	struct raw_frame     *frame = &cam->frame;
	unsigned int         *lbuf = (unsigned int *) (frame->data);
	unsigned short        data_avail;
	int i, count;

	/* Wait until rowcount == 1 */
	while ( mercury_backpaq_socket->camera_rowcount != 1 )
		;
	
	/* Load picture */
	while (frame->bytes_read < frame->bytes_to_read) {
		data_avail = mercury_backpaq_socket->camera_fifo_data_avail;

		if ( data_avail > cam->fifo_high )
			cam->fifo_high = data_avail;

		if ( data_avail < cam->fifo_low )
			cam->fifo_low = data_avail;

		count = data_avail * 4;  /* Bytes available to read */
		if ( count + frame->bytes_read > frame->bytes_to_read )
			count = frame->bytes_to_read - frame->bytes_read;
		
		for ( i = 0 ; i < count ; i+=4 ) 
			*lbuf++ = mercury_backpaq_socket->camera_fifo_read;
		
		frame->bytes_read += i;
	}
	return 0;
}

static long grab_frame_polling( struct h3600_camera_struct *cam )
{
	int retval;
	unsigned long flags;

	/* Set up the grabbing state */
	save_flags(flags); cli();
	prep_camera(cam);
	DISABLE_BOTH_INT;       /* Disable all camera interrupts */
	restore_flags(flags);

	/* Grab data */
	retval = polling_read_picture( cam );

	save_flags(flags); cli();
	cam->state = HC_V4L_IDLE;
	restore_flags(flags);

	if ( retval < 0 ) {
		CAMDEBUG(": Return value error %d\n", retval);
		return retval;
	}

#ifdef BACKPAQ_CAMERA_DEBUG
	printk(KERN_ALERT __FILE__ ": Polling read image (size %d)\n" 
	       "  Fifo  min=%d  max=%d\n",
	       cam->frame.bytes_read,
	       cam->fifo_low, cam->fifo_high ); 
	printk(KERN_ALERT __FILE__ ": fifo status = %x\n", 
	       mercury_backpaq_socket->fpga_interrupt_status);
#endif

	return cam->frame.bytes_read;
}

/******************************************************************************
 *
 * Interrupt routines
 *
 ******************************************************************************/

#define MAXIMUM_FIFO_DEPTH 255

static int get_fpga_data( struct h3600_camera_struct *cam )
{
	struct raw_frame *frame = &cam->frame;
	unsigned int         *lbuf = (unsigned int *) (frame->data + frame->bytes_read);
	unsigned short        data_avail;
	int i, count;

	while (frame->bytes_read < frame->bytes_to_read) {
		data_avail = mercury_backpaq_socket->camera_fifo_data_avail;

		if ( data_avail > cam->fifo_high ) {
			cam->fifo_high = data_avail;
			if ( data_avail >= MAXIMUM_FIFO_DEPTH ) {
				CAMDEBUG(": too much data available%d\n",data_avail);
				return -1;
			}
		}

		if ( data_avail < cam->fifo_low )
			cam->fifo_low = data_avail;

		if ( data_avail <= 0 )
			return 0;

		count = data_avail * 4;  /* Bytes available to read */
		if ( count + frame->bytes_read > frame->bytes_to_read )
			count = frame->bytes_to_read - frame->bytes_read;
		
		for ( i = 0 ; i < count ; i+=4 )  /* used to be i+=2 */
			*lbuf++ = mercury_backpaq_socket->camera_fifo_read;
		
		frame->bytes_read += i;
	}
	
	return 0;
}

static void fpga_fifo_interrupt( struct h3600_camera_struct *cam )
{
	struct raw_frame *frame = &cam->frame;
	cam->fifo_count++;

	if ( frame->state != GRAB_ACTIVE ) {
		printk(KERN_ALERT __FILE__ ": fifo int on non-active %d\n", frame->state);
		printk(KERN_ALERT __FILE__ ": fifo mask = %x\n", 
		       mercury_backpaq_socket->fpga_interrupt_mask);
		printk(KERN_ALERT __FILE__ ": fifo status = %x\n", 
		       mercury_backpaq_socket->fpga_interrupt_status);
		       printk(KERN_ALERT __FILE__ ": read both = %x\n", READ_BOTH_INT );
		return; 
	}

	if ( get_fpga_data( cam ) != 0 ) {
		frame->state = GRAB_ERROR;
		CAMDEBUG(": fifo overrun in FIFO interrupt\n");
		DISABLE_VBLANK_INT;
		wake_up_interruptible(&cam->capq);
		return;
	}

	if ( frame->bytes_read == frame->bytes_to_read ) {
		frame->state = GRAB_DONE;
		DISABLE_VBLANK_INT;
		wake_up_interruptible(&cam->capq);
	}
}

static void fpga_clear_fifo( void )
{
	u16 data_avail;
	u16 fifo_read = 0;

	while ( (data_avail = mercury_backpaq_socket->camera_fifo_data_avail) > 0 ) {
		for ( ; data_avail > 0 ; data_avail-- ) {
			int dummy;
			dummy = mercury_backpaq_socket->camera_fifo_read;
			fifo_read++;
		}
	}
/*	printk(KERN_ALERT __FILE__ ": clear fifo read %d times\n", fifo_read); */
}

static void fpga_vblank_interrupt( struct h3600_camera_struct *cam )
{
	struct raw_frame *frame = &cam->frame;
	cam->vblank_count++;

	switch (frame->state) {
	case GRAB_WAIT_FOR_REFRESH:
		ENABLE_FIFO_INT;
		frame->state = GRAB_ACTIVE;
		break;

	case GRAB_ACTIVE:  /* Suck in the last bit of data */
		CAMDEBUG(": Active grab VBLANK event\n");
		if ( get_fpga_data(cam) ) {
			frame->state = GRAB_ERROR;
			CAMDEBUG("  and FIFO overrun\n");
		}
		else if ( frame->bytes_read == frame->bytes_to_read ) {
			frame->state = GRAB_DONE;
		}
		else {
			CAMDEBUG(": VBLANK occurred before entire frame has been read (%d of %d bytes\n", 
				 frame->bytes_read, frame->bytes_to_read);
			frame->state = GRAB_ERROR;
		}
		wake_up_interruptible(&cam->capq);
		break;

	case GRAB_DONE: /* If we get this, we probably have a problem */
	case GRAB_ERROR:
		printk(KERN_ALERT __FILE__ ": VBLANK occurred on frame state %s\n", 
		       (frame->state == GRAB_DONE ? "done" : "error"));
		break;
	}
}

static void fpga_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
	unsigned short irq_value = READ_BOTH_INT;
	struct h3600_camera_struct *cam = (struct h3600_camera_struct *) dev_id;
	
	if (!cam ) {
		printk(KERN_ALERT __FILE__ ": interrupt %d without valid cam\n",irq);
		return;
	}
	
	if ( irq != IRQ_GPIO_BITSY_OPT_IRQ ) {
		printk(KERN_ALERT __FILE__ ": Unknown IRQ %d\n",irq);
		return;
	}

	/* Handle the FIFO interrupt */
	if ( irq_value & BACKPAQ_SOCKET_INT_FIFO ) { 
		DISABLE_FIFO_INT;
		fpga_fifo_interrupt(cam);
		ENABLE_FIFO_INT;
	} 

	/* Handle the VBLANK interrupt */
	if ( irq_value & BACKPAQ_SOCKET_INT_VBLANK )
		fpga_vblank_interrupt(cam);

	GEDR = GPIO_BITSY_OPT_IRQ; /* Clear the interrupt */
}


/******************************************************************************
 *
 * Video 4 Linux interface
 *
 ******************************************************************************/

static long h3600_camera_read( struct video_device *dev, char *buf, 
			       unsigned long count, int noblock )
{
	struct h3600_camera_struct *cam = (struct h3600_camera_struct *) dev;
	long retval;

	CAMDEBUG(": read called with len = %ld (%ld)\n", count, required_buf_size(cam));

	/* We're ignoring "noblock" */
	/* Check for adequate buffer size */
	if ( count < required_buf_size( cam )) 
		return -EIO;

	if ( verify_area(VERIFY_WRITE, buf, count))
		return -EFAULT;

	/* Lock the camera - no one else can capture while I have this */
	down(&cam->lock);
	
	/* Grab the next frame and stash in the local buffer */
	if ( cam->params.read_polling_mode )
		retval = grab_frame_polling(cam);
	else 
		retval = grab_frame(cam);
	
	/* Do color space processing and write it out */
	if ( retval > 0 ) 
		retval = process_frame( cam, buf );

	/* Release the mutex */
	up(&cam->lock);

	return retval;
}


static int h3600_camera_ioctl( struct video_device *dev, unsigned int cmd, void *arg )
{
	struct h3600_camera_struct *cam = (struct h3600_camera_struct *) dev;
	int retval = 0;

	switch(cmd) {
	case VIDIOCGCAP:     /* Get core camera capabilities */
	{
		struct video_capability b;
		strcpy(b.name, "iPAQ H3600 Mercury BackPAQ");
		b.type      = VID_TYPE_CAPTURE;
		b.channels  = 1;
		b.audios    = 0;
		b.maxwidth  = HC_TRUE_WIDTH;
		b.maxheight = HC_TRUE_HEIGHT;
		b.minwidth  = HC_MIN_ALLOWED_WIDTH;
		b.minheight = HC_MIN_ALLOWED_HEIGHT;
		if (copy_to_user(arg, &b,sizeof(b)))
			retval = -EFAULT;
		break;
	}
	case VIDIOCGCHAN:     /* Get channel info (sources) - We have just one channel */
	{
		struct video_channel v;
		if (copy_from_user(&v, arg, sizeof(v)))
			return -EFAULT;
		if (v.channel !=0)       /* We only have a single channel */
			return -EINVAL;
		v.tuners = 0;
		v.flags  = 0;
		v.type   = VIDEO_TYPE_CAMERA;
		v.norm   = 0;              /* What is this? */
		strcpy(v.name, "Camera");  /* Generic camera */
		if (copy_to_user(arg, &v, sizeof(v)))
			retval = -EFAULT;
		break;
	}
	case VIDIOCSCHAN:     /* Set channel - must be 0 */
	{
		int v;
		if (copy_from_user(&v, arg,sizeof(v)))
			retval = -EFAULT;
		else if (v!=0)
			retval = -EINVAL;
		break;
	}
	case VIDIOCGPICT:     /* Get picture properties */
	{
		if (copy_to_user(arg, &cam->vpic, sizeof(struct video_picture)))
			retval = -EFAULT;
		break;
	}
	case VIDIOCSPICT:      /* Set picture properties */
	{
		struct video_picture p;
		if (copy_from_user(&p, arg, sizeof(p))) {
			retval = -EFAULT;
			break;
		}

		/* For the moment, we force the following defaults */
		if (! ((p.depth == 24 && p.palette == VIDEO_PALETTE_RGB24)
		       || (p.depth == 8 && p.palette == VIDEO_PALETTE_GREY)
		       || (p.depth == 8 && p.palette == VIDEO_PALETTE_RAW ))) {
			retval = -EINVAL;
			break;
		}
			
		/* Load the camera */
		down(&cam->lock);			
		cam->vpic.brightness = p.brightness;
		cam->vpic.depth = p.depth;
		cam->vpic.palette = p.palette;
		/* Fix camera resolution */
		set_camera_resolution( cam, cam->vwin.width, cam->vwin.height ); 
		up(&cam->lock);
		break;
	}
	case VIDIOCGWIN:       /* Get the video capture window */
	{
		if (copy_to_user(arg, &cam->vwin, sizeof(struct video_window)))
			retval = -EFAULT;
		break;
	}
	case VIDIOCSWIN:       /* Set the capture area */
	{
		struct video_window vw;

		if (copy_from_user(&vw, arg,sizeof(vw))) {
			retval = -EFAULT;
			break;
		}

		if (vw.clipcount != 0) {    /* clipping not supported */
			retval = -EINVAL;
			break;
		}

		if (vw.clips != NULL) {     /* clipping not supported */
			retval = -EINVAL;
			break;
		}

		/* In raw mode, you can't change the resolution */
		if ( cam->vpic.palette == VIDEO_PALETTE_RAW )
			break;

		if (vw.height < HC_MIN_ALLOWED_HEIGHT 
		    || vw.height > HC_TRUE_HEIGHT
		    || vw.width < HC_MIN_ALLOWED_WIDTH 
		    || vw.width > HC_TRUE_WIDTH) {
			retval = -EINVAL;
			break;
		}
				
		/* Fix the camera resolution */
		down(&cam->lock);
		set_camera_resolution( cam, vw.width, vw.height );
		up(&cam->lock);
		break;
	}
	/* Private interface */
	case H3600CAM_G_PARAMS:
	{
		if (copy_to_user(arg, &cam->params, sizeof(struct h3600_backpaq_camera_params)))
			retval = -EFAULT;
		break;
	}
	case H3600CAM_S_PARAMS:
	{ 
		struct h3600_backpaq_camera_params params;
		if (copy_from_user(&params, arg, sizeof(params))) {
			retval = -EFAULT;
			break;
		}
		/* Some sanity checking */
		if (params.clock_divisor < 16   /* About 160 Hz */ 
			|| params.interrupt_fifo > 255
			|| params.power_setting > 15
			|| params.gain_format > 5 ) {
			retval = -EINVAL; 
			break;
		}
		down(&cam->lock);
		cam->params.clock_divisor = params.clock_divisor & 0xfffe; /* make even */
		cam->params.interrupt_fifo = params.interrupt_fifo;
		cam->params.power_setting = params.power_setting;
		cam->params.gain_format = params.gain_format;
		cam->params.read_polling_mode = params.read_polling_mode;
		up(&cam->lock);
		break;
	}
		
	/* mmap interface */
	case VIDIOCGMBUF:
	{
		struct video_mbuf vm;
		int i;

		memset(&vm, 0, sizeof(vm));
		vm.size = HC_MAX_COOKED_FRAME_SIZE * NUMBER_OF_COOKED_FRAMES; 
		vm.frames = NUMBER_OF_COOKED_FRAMES;
		for (i = 0; i < NUMBER_OF_COOKED_FRAMES; i++)
			vm.offsets[i] = HC_MAX_COOKED_FRAME_SIZE * i;

		if (copy_to_user((void *)arg, (void *)&vm, sizeof(vm)))
			retval = -EFAULT;
		break;
	}

	case VIDIOCMCAPTURE:
	{
		struct video_mmap vm;

		if (copy_from_user((void *)&vm, (void *)arg, sizeof(vm))) {
			retval = -EFAULT;
			break;
		}
		if ( vm.frame < 0 || vm.frame > NUMBER_OF_COOKED_FRAMES ) { 
			retval = -EINVAL;
			break;
		}

		/* We should set all of the properties and begin capturing */
		break;
	}
	
	case VIDIOCSYNC:
	{
		int frame;

		if (copy_from_user((void *)&frame, arg, sizeof(int))) {
			retval = -EFAULT;
			break;
		}

		if (frame<0 || frame >= NUMBER_OF_COOKED_FRAMES) {
			retval = -EINVAL;
			break;
		}

		/* Do our image mapping and copy the data into the mmap'ed buffer */
		break;
	}
	/* We don't implement overlay with this camera */
	case VIDIOCCAPTURE:
		retval = -EINVAL;
		break;
	case VIDIOCGFBUF:
		retval = -EINVAL;
		break;
	case VIDIOCSFBUF:
		retval = -EINVAL;
		break;
	case VIDIOCKEY:
		retval = -EINVAL;
		break;

		/* We have no tuner interface */
	case VIDIOCGTUNER:
		retval = -EINVAL;
		break;
	case VIDIOCSTUNER:
		retval = -EINVAL;
		break;
	case VIDIOCGFREQ:
		retval = -EINVAL;
		break;
	case VIDIOCSFREQ:
		retval = -EINVAL;
		break;

		/* We have no audio interface */
	case VIDIOCGAUDIO:
		retval = -EINVAL;
		break;
	case VIDIOCSAUDIO:
		retval = -EINVAL;
		break;
	default:
		retval = -ENOIOCTLCMD;
		break;
	}

	return retval;
}

static int h3600_camera_mmap(struct video_device *dev, const char *adr,
			     unsigned long size)
{
	return -ENODEV;
}

static int h3600_camera_open( struct video_device *dev, int flags )
{
	struct h3600_camera_struct *cam = (struct h3600_camera_struct *) dev;
	int retval = 0;

/*
	if ( mercury_backpaq_sysctl->hardware_version != BACKPAQ_HARDWARE_VERSION_1 ) {
 		printk(KERN_ALERT __FILE__ ": Unable to find backpaq\n");
 		return -ENXIO;
 	}
*/
	if ( !(mercury_backpaq_sysctl->fpga_status & BACKPAQ_FPGASTATUS_DONE )) {
		printk(KERN_ALERT __FILE__ ": Backpaq FPGA not programmed\n");
		return -EIO;
	}

	down(&cam->lock);
	if ( cam->usage_count++ == 0 ) {    /* Set up interrupts */
		unsigned long flags;
		save_flags_cli(flags);

		GPDR &= ~GPIO_BITSY_OPT_IRQ;    /* GPIO line as input */
		set_GPIO_IRQ_edge( GPIO_BITSY_OPT_IRQ, GPIO_RISING_EDGE );  /* Rising edge */

		retval = request_irq(IRQ_GPIO_BITSY_OPT_IRQ,
				     fpga_interrupt,
				     SA_SHIRQ | SA_INTERRUPT | SA_SAMPLE_RANDOM,
				     "Backpaq FPGA", (void *)dev);
		restore_flags(flags);
	}
	up(&cam->lock);

	MOD_INC_USE_COUNT;
	return retval;
}

static void h3600_camera_close( struct video_device *dev )
{
	struct h3600_camera_struct *cam = (struct h3600_camera_struct *) dev;

	down(&cam->lock);
	if ( --cam->usage_count == 0 ) {
		unsigned long flags;
		save_flags_cli(flags);
		free_irq(IRQ_GPIO_BITSY_OPT_IRQ, (void *)dev);
		restore_flags(flags);
	}
	up(&cam->lock);
	MOD_DEC_USE_COUNT;
}


int h3600_camera_video_init(struct video_device *vdev)
{
#ifdef CONFIG_PROC_FS
/*	create_proc_cpia_cam(vdev->priv);*/
#endif
	return 0;
}

static struct video_device h3600_camera_template =
{
	owner:		THIS_MODULE,
	name:		"iPAQ H3600 Mercury BackPAQ",
	type:		VID_TYPE_CAPTURE,
	hardware:	VID_HARDWARE_H3600_BACKPAQ,
	open:		h3600_camera_open,
	close:		h3600_camera_close,
	read:		h3600_camera_read,
	ioctl:		h3600_camera_ioctl,
	mmap:		h3600_camera_mmap, 
	initialize:	h3600_camera_video_init,
};

/******************************************************************************
 *
 * Standard initialization
 *
 * We should add a check to see if the camera responds and if it is B/W or color
 *
 ******************************************************************************/

static int h3600_camera_startup( struct h3600_camera_struct *cam )
{
	memset(cam, 0, sizeof(struct h3600_camera_struct));
	memcpy(&cam->vdev, &h3600_camera_template, sizeof(h3600_camera_template));

	cam->frame.data = vmalloc( HC_RAW_BUFFER_SIZE );
	if ( !cam->frame.data ) {
		printk(KERN_ALERT __FILE__ ": Unable to allocate frame buffer\n");
		return -ENOBUFS;
	}

 	init_MUTEX(&cam->lock);
        init_waitqueue_head(&cam->capq);

	/* Set up some plausible defaults */
	cam->state           = HC_V4L_IDLE;
	cam->vpic.palette    = VIDEO_PALETTE_RGB24;
	cam->vpic.depth      = 24;
	cam->vpic.brightness = 0x8000;
	set_camera_resolution( cam, 10000, 10000 );   /* Will set to 640 x 480 */

	cam->params.clock_divisor  = 0x100;   /* 5 fps default */
	cam->params.power_setting  = 0xc;     /* Normally "12" */
	cam->params.gain_format    = 0x0;     /* 8 bpp, gain = 0 */
	cam->params.interrupt_fifo = 0x20;    /* 32 deep (32x8 bytes) */

	return 0;
}

static int h3600_camera_shutdown( struct h3600_camera_struct *cam )
{
	/* Toss the interrupt routine if we've messed up a shutdown somewhere */
	if ( cam->usage_count > 0 ) {
		unsigned long flags;
		save_flags_cli(flags);
		free_irq(IRQ_GPIO_BITSY_COM_DCD, (void *)cam);
		restore_flags(flags);
	}

	/* Should we kill the wait queue? */

	if ( cam->frame.data)
		vfree( cam->frame.data );

	return 0;
}



/******************************************************************************
 *
 * /proc interface
 *
 ******************************************************************************/

#ifdef CONFIG_PROC_FS

static char *palette_names[] = {
	"","Grey","HI240","RGB565",
	"RGB24","RGB32","RGB444","YUV422",
	"YUYV","UYVY","YUV420","YUV411",
	"RAW","YUV422P","YUV411P","YUV420P",
	"YUV410P"
};

static int h3600_camera_read_proc( char *page, char **start, off_t off,
				   int count, int *eof, void *data )
{
	struct h3600_camera_struct *cam = &hc_camera;
	char *out = page;
	int len;

	out += sprintf(out, "%s\n", BANNER);
	out += sprintf(out, "CMOS Image Size %d %d\n", HC_TRUE_WIDTH, HC_TRUE_HEIGHT);
	out += sprintf(out, "Capture window\n");
	out += sprintf(out, "  x          : %d\n",cam->vwin.x);
	out += sprintf(out, "  y          : %d\n",cam->vwin.y);
	out += sprintf(out, "  width      : %d\n",cam->vwin.width);
	out += sprintf(out, "  height     : %d\n",cam->vwin.height);
	out += sprintf(out, "Image properties\n");
	out += sprintf(out, "  brightness : 0x%04x\n",cam->vpic.brightness);
	if ( cam->vpic.palette != VIDEO_PALETTE_GREY ) {
		out += sprintf(out, "  hue        : 0x%04x\n",cam->vpic.hue);
		out += sprintf(out, "  colour     : 0x%.4x\n",cam->vpic.colour);
	}
	out += sprintf(out, "  contrast   : 0x%04x\n",cam->vpic.contrast);
	if ( cam->vpic.palette == VIDEO_PALETTE_GREY ) {
		out += sprintf(out, "  whiteness  : 0x%04x\n",cam->vpic.whiteness);
	}
	out += sprintf(out, "  depth      : %d\n",cam->vpic.depth);
	out += sprintf(out, "  palette    : %s\n",palette_names[cam->vpic.palette]);
	out += sprintf(out, "Imager params counters\n");
	out += sprintf(out, "  clk divide : %d (%d Hz)\n", cam->params.clock_divisor,
		       1280 / cam->params.clock_divisor);
	out += sprintf(out, "  intr fifo  : %d\n", cam->params.interrupt_fifo);
	out += sprintf(out, "  power set  : %d\n", cam->params.power_setting);
	out += sprintf(out, "  gain fmt   : %d\n", cam->params.gain_format);
	out += sprintf(out, "Internal counters\n");
	out += sprintf(out, "  vblank     : %d\n",cam->vblank_count);
	out += sprintf(out, "  fifo       : %d\n",cam->fifo_count);
		    
	len = out - page;
	len -= off;
	if (len < count) {
		*eof = 1;
		if (len <= 0) return 0;
	} else
		len = count;

	*start = page + off;
	return len;

}


#endif /* CONFIG_PROC_FS */

/******************************************************************************
 *
 * Module interface
 *
 ******************************************************************************/

#define PROC_FS_NAME "camera"

#ifdef MODULE
static int __init h3600_camera_init( void )
{
	int retval = 0;

	printk(KERN_INFO BANNER "\n");

	retval = h3600_camera_startup( &hc_camera );
	if ( retval )
		return retval;

	if (video_register_device(&hc_camera.vdev, VFL_TYPE_GRABBER)) {
		printk(KERN_ERR __FILE__ "Unable to H3600 BackPAQ camera\n");
		return -ENODEV;
	}

#ifdef CONFIG_PROC_FS
	/* Initialize proc file system...this should be a subdirectory */
	if ( !proc_backpaq_dir ) {
		printk(KERN_ERR __FILE__ "Proc file system incorrectly initialized\n");
		return -ENODEV;
	}
	proc_camera = create_proc_entry(PROC_FS_NAME, 0, proc_backpaq_dir);    
	if (proc_camera)    
		proc_camera->read_proc = h3600_camera_read_proc;    
	else {
		printk(KERN_ALERT __FILE__ ": unable to create proc entry\n");
		return -ENODEV;
	}
#endif

	return 0;  /* No error */
}

static void __exit h3600_camera_cleanup( void )
{
#ifdef CONFIG_PROC_FS
	if (proc_camera)
		remove_proc_entry(PROC_FS_NAME,0);
#endif

	h3600_camera_shutdown( &hc_camera );

	video_unregister_device(&hc_camera.vdev);
}

module_init(h3600_camera_init);
module_exit(h3600_camera_cleanup);

#endif /* MODULE */
