patch-2.4.22 linux-2.4.22/drivers/media/video/swarm_saa7114h.c

Next file: linux-2.4.22/drivers/media/video/tda7432.c
Previous file: linux-2.4.22/drivers/media/video/msp3400.c
Back to the patch index
Back to the overall index

diff -urN linux-2.4.21/drivers/media/video/swarm_saa7114h.c linux-2.4.22/drivers/media/video/swarm_saa7114h.c
@@ -0,0 +1,1695 @@
+/* 
+   saa7114h - Philips SAA7114H video decoder driver
+
+   Copyright (C) 2001,2002,2003 Broadcom Corporation
+
+   From saa7111.c:
+     Copyright (C) 1998 Dave Perks <dperks@ibm.net>
+   From cpia.c:
+     (C) Copyright 1999-2000 Peter Pregler
+     (C) Copyright 1999-2000 Scott J. Bertin
+     (C) Copyright 1999-2000 Johannes Erdfelt <johannes@erdfelt.com>
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Important note: this driver is reasonably functional, and has been
+ * tested with the "camserv" v4l application.  But it primarily a
+ * proof-of-concept, and example for setting up FIFO-mode.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/ctype.h>
+#include <linux/fs.h>
+#include <linux/vmalloc.h>
+#include <linux/kernel.h>
+#include <linux/major.h>
+#include <linux/slab.h>
+#include <linux/mm.h>
+#include <linux/pci.h>
+#include <linux/signal.h>
+#include <linux/proc_fs.h>
+#include <asm/io.h>
+#include <asm/pgtable.h>
+#include <asm/page.h>
+#include <linux/sched.h>
+#include <asm/segment.h>
+#include <linux/types.h>
+#include <linux/wrapper.h>
+#include <linux/smp_lock.h>
+#include <asm/hardirq.h>
+
+#include <linux/i2c.h>
+#include <linux/videodev.h>
+#include <linux/version.h>
+#include <asm/uaccess.h>
+
+#include <linux/i2c-algo-sibyte.h>
+
+#include <asm/sibyte/64bit.h>
+#include <asm/sibyte/sb1250_regs.h>
+#include <asm/sibyte/sb1250_int.h>
+#include <asm/sibyte/sb1250_mac.h>
+#include <asm/sibyte/sb1250_dma.h>
+
+#define SAA_BRIGHTNESS	 0x0a
+#define SAA_CONTRAST	 0x0b
+#define SAA_SATURATION	 0x0c
+#define SAA_HUE		 0x0d
+
+#define DECODER_STATUS	 0x1f
+#define SLICER_STATUS_0	 0x60
+#define SLICER_STATUS_1	 0x61
+#define SLICER_STATUS_2	 0x62
+#define SCALER_STATUS	 0x8f
+
+#define NUM_FRAME	 2
+#define MAX_HORIZ	 720
+#define MAX_VERT	 480
+#define MIN_HORIZ	 180
+#define MIN_VERT	 120
+#define MAX_PER_PIXEL	 3
+#define MAX_FRAME_SIZE	 (MAX_HORIZ*MAX_VERT*MAX_PER_PIXEL)
+#define MAX_MMAP_SIZE	 (PAGE_ALIGN(MAX_FRAME_SIZE*NUM_FRAME))
+#define RAW_PER_PIXEL	 2
+#define RAW_LINE_PAD	 8
+#define RAW_LINE_SIZE	 (((MAX_HORIZ*RAW_PER_PIXEL)+RAW_LINE_PAD+0x1f) & ~0x1f)
+#define RAW_FRAME_SIZE	 (RAW_LINE_SIZE*MAX_VERT)
+
+#define NUM_DESCR	 64
+#define INTR_PKT_CNT	 8
+
+/* Extensions to videodev.h IOCTL definitions */
+#define VIDIOREADREG	_IOR('v', 50, int)
+#define VIDIOWRITEREG	_IOW('v', 50, int)
+#define VIDIOGRABFRAME	_IOR('v', 51, int)
+#define VIDIOSHOWEAV	_IOR('v', 52, int)
+
+#define IF_NAME "saa7114h"
+
+#define MAC2_CSR(r)	   (KSEG1 + A_MAC_REGISTER(2, r))
+#define MAC2_DMARX0_CSR(r) (KSEG1 + A_MAC_DMA_REGISTER(2, DMA_RX, 0, r))
+
+/* Options */
+#define DMA_DEINTERLACE	 1
+#define LAZY_READ	 1
+#define NULL_DMA	 0
+
+/* Debug filters */
+#define DBG_NULL	 0x0000
+#define DBG_IO		 0x0001
+#define DBG_DESCR	 0x0002
+#define DBG_INTR	 0x0004
+#define DBG_CONVERT	 0x0008
+#define DBG_FRAMING	 0x0010
+#define DBG_REGISTER	 0x0020
+#define DBG_CALL	 0x0040
+#define DBG_FRAMING_LOUD 0x0080
+
+/* XXXKW make this settable through /proc... */
+#define DEBUG_LVL	 (DBG_NULL)
+
+#if DEBUG_LVL
+#define DBG(l, p) do { if (DEBUG_LVL & l) p; } while (0)
+#else
+#define DBG(l, p)
+#endif
+
+/* ----------------------------------------------------------------------- */
+
+enum {
+	FRAME_READY,		/* Ready to grab into */
+	FRAME_GRABBING,		/* In the process of being grabbed into */
+	FRAME_DONE,		/* Finished grabbing, but not been synced yet */
+	FRAME_UNUSED,		/* Unused (belongs to driver, but can't be used) */
+};
+
+struct saa_frame {
+	uint8_t		 *data;
+	uint8_t		 *pos;
+	int		  width;
+	int		  height;
+	uint32_t	  size;
+	volatile int	  state;
+	wait_queue_head_t read_wait;
+};
+
+typedef struct fifo_descr_s {
+	uint64_t descr_a;
+	uint64_t descr_b;
+} fifo_descr_t;
+
+typedef unsigned long paddr_t;
+
+typedef struct fifo_s {
+	unsigned	 ringsz;
+	fifo_descr_t	*descrtab;
+	fifo_descr_t	*descrtab_end;
+	fifo_descr_t	*next_descr;
+	paddr_t		 descrtab_phys;
+	void		*dma_buf;	    /* DMA buffer */
+} fifo_t;
+
+struct saa7114h {
+	struct i2c_client    *client;
+	struct video_device  *vd;
+	struct video_window   vw;
+	struct video_picture  vp;
+	uint8_t		      reg[256];
+
+	fifo_t		 ff;
+	void		*frame_buf; /* hold frames for the client */
+	struct saa_frame frame[NUM_FRAME]; /* point into frame_buf */
+	int		 hwframe;
+	int		 swframe;
+
+	uint16_t depth;
+	uint16_t palette;
+	uint8_t	 bright;
+	uint8_t	 contrast;
+	uint8_t	 hue;
+	uint8_t	 sat;
+
+	struct proc_dir_entry *proc_entry;
+	struct semaphore       param_lock;
+	struct semaphore       busy_lock;
+
+	int	dma_enable;
+	int	opened;
+	int	irq;
+	int	interlaced;
+};
+
+static int saa7114h_probe(struct i2c_adapter *adap);
+static int saa7114h_detach(struct i2c_client *device);
+
+struct i2c_driver i2c_driver_saa7114h =
+{
+	name:		"saa7114h",		/* name */
+	id:		I2C_DRIVERID_SAA7114H,	/* ID */
+	flags:		I2C_DF_NOTIFY,		/* XXXKW do I care? */
+	attach_adapter: saa7114h_probe,
+	detach_client:	saa7114h_detach
+};
+
+/* -----------------------------------------------------------------------
+ * VM assist for MMAPed space
+ * ----------------------------------------------------------------------- */
+
+/* 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));
+			}
+		}
+	}
+	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);
+	return ret;
+}
+
+static void *rvmalloc(unsigned long size)
+{
+	void *mem;
+	unsigned long adr, page;
+
+	/* Round it off to PAGE_SIZE */
+	size += (PAGE_SIZE - 1);
+	size &= ~(PAGE_SIZE - 1);
+
+	mem = vmalloc_32(size);
+	if (!mem)
+		return NULL;
+
+	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;
+		if (size > PAGE_SIZE)
+			size -= PAGE_SIZE;
+		else
+			size = 0;
+	}
+
+	return mem;
+}
+
+static void rvfree(void *mem, unsigned long size)
+{
+	unsigned long adr, page;
+
+	if (!mem)
+		return;
+
+	size += (PAGE_SIZE - 1);
+	size &= ~(PAGE_SIZE - 1);
+
+	adr = (unsigned long) mem;
+	while (size > 0) {
+		page = kvirt_to_pa(adr);
+		mem_map_unreserve(virt_to_page(__va(page)));
+		adr += PAGE_SIZE;
+		if (size > PAGE_SIZE)
+			size -= PAGE_SIZE;
+		else
+			size = 0;
+	}
+	vfree(mem);
+}
+
+/* -----------------------------------------------------------------------
+ * Control interface (i2c)
+ * ----------------------------------------------------------------------- */
+
+static int saa7114h_reg_read(struct saa7114h *dev, unsigned char subaddr)
+{
+	return i2c_smbus_read_byte_data(dev->client, subaddr);
+}
+
+static int saa7114h_reg_write(struct saa7114h *dev, unsigned char subaddr, int data)
+{
+	return i2c_smbus_write_byte_data(dev->client, subaddr, data & 0xff);
+}
+
+static int saa7114h_reg_init(struct saa7114h *dev, unsigned const char *data, unsigned int len)
+{
+	int rc = 0;
+	int val;
+
+	while (len && !rc) {
+		dev->reg[data[0]] = data[1];
+		rc = saa7114h_reg_write(dev, data[0], data[1]);
+		if (!rc && (data[0] != 0)) {
+			val = saa7114h_reg_read(dev, data[0]);
+			if ((val < 0) || (val != data[1])) {
+				printk(KERN_ERR
+				       IF_NAME ": init readback mismatch reg %02x = %02x (should be %02x)\n",
+				       data[0], val, data[1]);
+			}
+		}
+		len -= 2;
+		data += 2;
+	}
+	return rc;
+}
+
+/* -----------------------------------------------------------------------
+ * /proc interface
+ * ----------------------------------------------------------------------- */
+
+#ifdef CONFIG_PROC_FS
+static struct proc_dir_entry *saa7114h_proc_root=NULL;
+
+static int decoder_read_proc(char *page, char **start, off_t off,
+			     int count, int *eof, void *data)
+{
+	char *out = page;
+	int len, status;
+	struct saa7114h *decoder = data;
+
+	out += sprintf(out, "  SWARM saa7114h\n------------------\n");
+	status = saa7114h_reg_read(decoder, DECODER_STATUS);
+	out += sprintf(out, "  Decoder status = %02x\n", status);
+	if (status & 0x80)
+		out += sprintf(out, "	 interlaced\n");
+	if (status & 0x40)
+		out += sprintf(out, "	 not locked\n");
+	if (status & 0x02)
+		out += sprintf(out, "	 Macrovision detected\n");
+	if (status & 0x01)
+		out += sprintf(out, "	 color\n");
+	out += sprintf(out, "  Brightness = %02x\n", decoder->bright);
+	out += sprintf(out, "  Contrast	  = %02x\n", decoder->contrast);
+	out += sprintf(out, "  Saturation = %02x\n", decoder->sat);
+	out += sprintf(out, "  Hue	  = %02x\n\n", decoder->hue);
+
+	out += sprintf(out, "  Scaler status  = %02x\n", 
+		       (int)saa7114h_reg_read(decoder, SCALER_STATUS));
+
+	len = out - page;
+	len -= off;
+	if (len < count) {
+		*eof = 1;
+		if (len <= 0) return 0;
+	} else
+		len = count;
+
+	*start = page + off;
+	return len;
+}
+
+static int decoder_write_proc(struct file *file, const char *buffer,
+			       unsigned long count, void *data)
+{
+	struct saa7114h *d = data;
+	int retval;
+	unsigned int cmd, reg, reg_val;
+	
+	if (down_interruptible(&d->param_lock))
+		return -ERESTARTSYS;
+
+#define VALUE \
+	({ \
+		char *_p; \
+		unsigned long int _ret; \
+		while (count && isspace(*buffer)) { \
+			buffer++; \
+			count--; \
+		} \
+		_ret = simple_strtoul(buffer, &_p, 16); \
+		if (_p == buffer) \
+			retval = -EINVAL; \
+		else { \
+			count -= _p - buffer; \
+			buffer = _p; \
+		} \
+		_ret; \
+	})
+	
+	retval = 0;
+	while (count && !retval) {
+		cmd = VALUE;
+		if (retval)
+			break;
+		switch (cmd) {
+		case 1:
+			reg = VALUE;
+			if (retval)
+				break;
+			reg_val = VALUE;
+			if (retval)
+				break;
+			printk(IF_NAME ": write reg %x <- %x\n", reg, reg_val);
+			if (saa7114h_reg_write(d, reg, reg_val) == -1)
+				retval = -EINVAL;
+			break;
+		case 2:
+			reg = VALUE;
+			if (retval)
+				break;
+			reg_val = saa7114h_reg_read(d, reg);
+			if (reg_val == -1)
+				retval = -EINVAL;
+			else
+				printk(IF_NAME ": read reg %x -> %x\n", reg, reg_val);
+			break;
+		default:
+			break;
+		}
+	}
+	up(&d->param_lock);
+	
+	return retval;
+}
+
+static void create_proc_decoder(struct saa7114h *decoder)
+{
+	char name[8];
+	struct proc_dir_entry *ent;
+	
+	if (!saa7114h_proc_root || !decoder)
+		return;
+
+	sprintf(name, "video%d", decoder->vd->minor);
+	
+	ent = create_proc_entry(name, S_IFREG|S_IRUGO|S_IWUSR, saa7114h_proc_root);
+	if (!ent) {
+		printk(KERN_INFO IF_NAME ": Unable to initialize /proc/saa7114h/%s\n", name);
+		return;
+	}
+
+	ent->data = decoder;
+	ent->read_proc = decoder_read_proc;
+	ent->write_proc = decoder_write_proc;
+	ent->size = 3626;	/* XXXKW ??? */
+	decoder->proc_entry = ent;
+}
+
+static void destroy_proc_decoder(struct saa7114h *decoder)
+{
+	char name[7];
+	
+	if (!decoder || !decoder->proc_entry)
+		return;
+	
+	sprintf(name, "video%d", decoder->vd->minor);
+	remove_proc_entry(name, saa7114h_proc_root);
+	decoder->proc_entry = NULL;
+}
+
+static void proc_saa7114h_create(void)
+{
+	saa7114h_proc_root = create_proc_entry("saa7114h", S_IFDIR, 0);
+
+	if (saa7114h_proc_root)
+		saa7114h_proc_root->owner = THIS_MODULE;
+	else
+		printk(KERN_INFO IF_NAME ": Unable to initialize /proc/saa7114h\n");
+}
+
+static void proc_saa7114h_destroy(void)
+{
+	remove_proc_entry("saa7114h", 0);
+}
+#endif /* CONFIG_PROC_FS */
+
+
+/* -----------------------------------------------------------------------
+ * Initialization
+ * ----------------------------------------------------------------------- */
+
+static int dma_setup(struct saa7114h *d)
+{
+	int i;
+	void *curbuf;
+
+	/* Reset the port */
+	out64(M_MAC_PORT_RESET, MAC2_CSR(R_MAC_ENABLE));
+	in64(MAC2_CSR(R_MAC_ENABLE));
+
+	/* Zero everything out, disable filters */
+	out64(0, MAC2_CSR(R_MAC_TXD_CTL));
+	out64(M_MAC_ALLPKT_EN, MAC2_CSR(R_MAC_ADFILTER_CFG));
+	out64(V_MAC_RX_RD_THRSH(4) | V_MAC_RX_RL_THRSH(4),
+	      MAC2_CSR(R_MAC_THRSH_CFG));
+	for (i=0; i<MAC_CHMAP_COUNT; i++) {
+		out64(0, MAC2_CSR(R_MAC_CHLO0_BASE+(i*8)));
+		out64(0, MAC2_CSR(R_MAC_CHUP0_BASE+(i*8)));
+	}
+	for (i=0; i<MAC_HASH_COUNT; i++) {
+		out64(0, MAC2_CSR(R_MAC_HASH_BASE+(i*8)));
+	}
+	for (i=0; i<MAC_ADDR_COUNT; i++) {
+		out64(0, MAC2_CSR(R_MAC_ADDR_BASE+(i*8)));
+	}	 
+	
+	out64(V_MAC_MAX_FRAMESZ(16*1024) | V_MAC_MIN_FRAMESZ(0),
+	      MAC2_CSR(R_MAC_FRAMECFG));
+
+	/* Select bypass mode */
+	out64((M_MAC_BYPASS_SEL | V_MAC_BYPASS_CFG(K_MAC_BYPASS_EOP) | 
+	       M_MAC_FC_SEL | M_MAC_SS_EN | V_MAC_SPEED_SEL_1000MBPS),
+	      MAC2_CSR(R_MAC_CFG));
+
+	/* Set up the descriptor table */
+	d->ff.descrtab = kmalloc(NUM_DESCR * sizeof(fifo_descr_t), GFP_KERNEL);
+	d->ff.descrtab_phys = __pa(d->ff.descrtab);
+	d->ff.descrtab_end = d->ff.descrtab + NUM_DESCR;
+	d->ff.next_descr = d->ff.descrtab;
+	d->ff.ringsz = NUM_DESCR;
+#if 0
+	/* XXXKW this won't work because the physical may not be
+	   contiguous; how do I handle a bigger alloc then? */
+	d->ff.dma_buf = rvmalloc(RAW_LINE_SIZE*NUM_DESCR);
+	printk(KERN_DEBUG IF_NAME ": DMA buffer allocated (%p)\n",
+	       d->ff.dma_buf);
+#else
+	d->ff.dma_buf = kmalloc(RAW_LINE_SIZE*NUM_DESCR, GFP_KERNEL);
+#endif
+	if (!d->ff.dma_buf) {
+		printk(KERN_ERR IF_NAME ": couldn't allocate DMA buffer\n");
+		return -ENOMEM;
+	}
+	memset(d->ff.dma_buf, 0, RAW_LINE_SIZE*NUM_DESCR);
+
+	for (i=0, curbuf=d->ff.dma_buf; i<d->ff.ringsz; i++, curbuf+=RAW_LINE_SIZE) {
+		d->ff.descrtab[i].descr_a = (__pa(curbuf) |
+					     V_DMA_DSCRA_A_SIZE(RAW_LINE_SIZE >> 5));
+		d->ff.descrtab[i].descr_b = 0;
+	}
+
+	out64(V_DMA_INT_PKTCNT(INTR_PKT_CNT) | M_DMA_EOP_INT_EN |
+	      V_DMA_RINGSZ(d->ff.ringsz) | M_DMA_TDX_EN,
+	      MAC2_DMARX0_CSR(R_MAC_DMA_CONFIG0));
+	out64(M_DMA_L2CA, MAC2_DMARX0_CSR(R_MAC_DMA_CONFIG1));
+	out64(d->ff.descrtab_phys, MAC2_DMARX0_CSR(R_MAC_DMA_DSCR_BASE));
+
+	/* Enable interrupts and DMA */
+	out64(M_MAC_INT_EOP_COUNT<<S_MAC_RX_CH0, MAC2_CSR(R_MAC_INT_MASK));
+	out64(M_MAC_RXDMA_EN0 | M_MAC_BYP_RX_ENABLE, MAC2_CSR(R_MAC_ENABLE));
+
+	return 0;
+}
+
+/* -----------------------------------------------------------------------
+ * v4linux helpers - color conversion, etc  (taken from cpia.c)
+ * ----------------------------------------------------------------------- */
+
+#define LIMIT(x) ((((x)>0xffffff)?0xff0000:(((x)<=0xffff)?0:(x)&0xff0000))>>16)
+
+static void yuvconvert_inplace(uint8_t *data, uint32_t in_uyvy, int out_fmt, int mmap)
+{
+	int y, u, v, r, g, b, y1;
+	uint8_t *src, *dst;
+
+	if (out_fmt == VIDEO_PALETTE_RGB24) {
+		src = (uint8_t *)((int)data + in_uyvy);
+		dst = (uint8_t *)((int)data + in_uyvy + (in_uyvy >> 1));
+		DBG(DBG_CONVERT, printk(KERN_DEBUG "inplace: %p %p %p\n", data, src, dst));
+		while (src > data) {
+			if ((int)(src-data) < 4)
+				break;
+				//printk("freaky %p %p\n", src, data);
+			y1 = (*(--src) - 16) * 76310;
+			v = *(--src) - 128;
+			y = (*(--src) - 16) * 76310;
+			u = *(--src) - 128;
+			r = 104635 * v;
+			g = -25690 * u + -53294 * v;
+			b = 132278 * u;
+			/* XXXKW what on earth is up with mmap? */
+			if (mmap) {
+				*(--dst) = LIMIT(r+y1);
+				*(--dst) = LIMIT(g+y1);
+				*(--dst) = LIMIT(b+y1);
+				*(--dst) = LIMIT(r+y);
+				*(--dst) = LIMIT(g+y);
+				*(--dst) = LIMIT(b+y);
+			} else {
+				*(--dst) = LIMIT(b+y1);
+				*(--dst) = LIMIT(g+y1);
+				*(--dst) = LIMIT(r+y1);
+				*(--dst) = LIMIT(b+y);
+				*(--dst) = LIMIT(g+y);
+				*(--dst) = LIMIT(r+y);
+			}
+		}
+	}
+}
+
+static int saa7114h_get_cparams(struct saa7114h *decoder)
+{
+	/* XXX check for error code */
+	decoder->bright	    = saa7114h_reg_read(decoder, SAA_BRIGHTNESS);
+	decoder->contrast   = saa7114h_reg_read(decoder, SAA_CONTRAST);
+	decoder->sat	    = saa7114h_reg_read(decoder, SAA_SATURATION);
+	decoder->hue	    = saa7114h_reg_read(decoder, SAA_HUE);
+
+	decoder->vp.brightness = (uint16_t)decoder->bright << 8;
+	decoder->vp.contrast   = (uint16_t)decoder->contrast << 9;
+	decoder->vp.colour     = decoder->sat << 9;
+	decoder->vp.hue	       = ((int16_t)decoder->hue + 128) << 8;
+	return 0;
+}
+
+static int saa7114h_set_cparams(struct saa7114h *decoder)
+{
+	decoder->bright	  = decoder->vp.brightness >> 8;
+	decoder->contrast = decoder->vp.contrast >> 9;
+	decoder->sat	  = decoder->vp.colour >> 9;
+	decoder->hue	  = (uint8_t)((int8_t)(decoder->vp.hue >> 8) - 128);
+
+	return (saa7114h_reg_write(decoder, SAA_BRIGHTNESS, decoder->bright) ||
+		saa7114h_reg_write(decoder, SAA_CONTRAST, decoder->contrast) ||
+		saa7114h_reg_write(decoder, SAA_SATURATION, decoder->sat) ||
+		saa7114h_reg_write(decoder, SAA_HUE, decoder->hue));
+}
+
+/* -----------------------------------------------------------------------
+ * Custom IOCTL support
+ * ----------------------------------------------------------------------- */
+
+unsigned char eav[625][2];
+static int grab_frame(struct saa7114h *d, void *user_buf, int print_eav)
+{
+	int cur_idx = 0;
+	int to_go = 625;
+	int delta;
+	int i, len, eav_val, sav_val;
+	int started = 0;
+	uint8_t *buf;
+	fifo_descr_t *cur_d;
+	int swptr = d->ff.next_descr - d->ff.descrtab;
+	int hwptr;
+
+	DBG(DBG_CALL, printk(IF_NAME ": grabbing frame\n"));
+
+	/* Check for Macrovision -- if it's on, DMA won't happen */
+	if (saa7114h_reg_read(d, DECODER_STATUS) & 0x2)
+		return -EACCES;
+
+	out64(d->ff.ringsz, MAC2_DMARX0_CSR(R_MAC_DMA_DSCR_CNT));
+	do {
+		hwptr = (unsigned) (((in64(MAC2_DMARX0_CSR(R_MAC_DMA_CUR_DSCRADDR)) &
+				      M_DMA_CURDSCR_ADDR) -
+				     d->ff.descrtab_phys) /
+				    sizeof(fifo_descr_t));
+		delta = (hwptr + d->ff.ringsz - swptr) % d->ff.ringsz;
+		
+		if (delta == 0) {
+#if 0
+			uint64_t val = in64(MAC2_DMARX0_CSR(R_MAC_STATUS));
+			printk("mac status: %08x%08x\n",
+			       (u32)(val >> 32), (u32)(val&0xffffffff));
+#endif
+		}
+
+		for (i=0; i<delta; i++) {
+			cur_d = d->ff.next_descr;
+			if (++d->ff.next_descr == d->ff.descrtab_end)
+				d->ff.next_descr = d->ff.descrtab;
+			
+			if (!(cur_d->descr_a & M_DMA_ETHRX_SOP)) {
+				printk("bogus RX\n");
+				continue;
+			}
+			cur_d->descr_a &= ~M_DMA_ETHRX_SOP;
+			len = G_DMA_DSCRB_PKT_SIZE(cur_d->descr_b);
+			buf = (uint8_t *)__va(cur_d->descr_a & M_DMA_DSCRA_A_ADDR);
+			if (len != (d->vw.width*RAW_PER_PIXEL)+RAW_LINE_PAD) {
+				printk("funny size %d\n", len);
+				continue;
+			}
+			eav_val = buf[1];
+			sav_val = buf[5];
+			if (eav_val == 0xf1) { /* end of field 2, V-blank */
+				if (started) {
+					started = 0;
+					delta = to_go = 0;
+					/* just let DMA finish in background */
+				} else {
+					started = 1;
+				}
+			}
+			if (started) {
+				eav[cur_idx][0] = eav_val;
+				eav[cur_idx++][1] = sav_val;
+				if (copy_to_user(user_buf, &buf[6], 1440))
+					return -EFAULT;
+				user_buf += 1440;
+			}
+		}
+		swptr = hwptr;
+		if (delta) {
+			if (started)
+				to_go -= delta;
+			if (delta > to_go)
+				delta = to_go;
+			out64(delta, MAC2_DMARX0_CSR(R_MAC_DMA_DSCR_CNT));
+		}
+	} while (to_go);
+
+	if (print_eav) {
+		for (i=0; i<cur_idx; i++) {
+			printk("%3d: %02x | %02x\n", i, eav[i][0], eav[i][1]);
+		}
+	}
+
+	return cur_idx;
+}
+
+/* -----------------------------------------------------------------------
+ * Interrupt handler
+ * ----------------------------------------------------------------------- */
+
+unsigned long int_count = 0;
+
+static void saa7114h_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+	struct saa7114h *d = dev_id;
+	uint64_t status_val;
+	fifo_descr_t *cur_d;
+	int i, delta, len;
+	uint8_t *buf, eav_val;
+	int swptr = d->ff.next_descr - d->ff.descrtab;
+	int hwptr;
+
+	status_val = in64(MAC2_CSR(R_MAC_STATUS));
+
+	/* Process finished decsriptors */
+	hwptr = (unsigned) (((in64(MAC2_DMARX0_CSR(R_MAC_DMA_CUR_DSCRADDR)) &
+			      M_DMA_CURDSCR_ADDR) - d->ff.descrtab_phys) /
+			    sizeof(fifo_descr_t));
+	delta = (hwptr + d->ff.ringsz - swptr) % d->ff.ringsz;
+	if (!delta) {
+		if (status_val & M_MAC_INT_EOP_SEEN<<S_MAC_RX_CH0) {
+			/* Must have wrapped since the last interrupt */
+			delta = d->ff.ringsz;
+		} else {
+			/* XXXKW why would this happen? */
+			return;
+		}
+	}
+
+	for (i=0; i<delta; i++) {
+		cur_d = d->ff.next_descr;
+		if (++d->ff.next_descr == d->ff.descrtab_end)
+			d->ff.next_descr = d->ff.descrtab;
+		
+		if (!(cur_d->descr_a & M_DMA_ETHRX_SOP)) {
+			printk(KERN_DEBUG "bogus RX\n");
+			continue;
+		}
+		cur_d->descr_a &= ~M_DMA_ETHRX_SOP;
+		if (!d->dma_enable)
+			continue;
+		
+		len = G_DMA_DSCRB_PKT_SIZE(cur_d->descr_b);
+		buf = (uint8_t *)__va(cur_d->descr_a & M_DMA_DSCRA_A_ADDR);
+		if (len != (d->vw.width*RAW_PER_PIXEL)+RAW_LINE_PAD) {
+			printk(KERN_DEBUG "funny size %d\n", len);
+//				  continue;
+		}
+		len -= RAW_LINE_PAD;
+		eav_val = buf[1];
+		DBG(DBG_FRAMING_LOUD,
+		    printk(KERN_DEBUG "eav: %02x len: %d\n", eav_val, len));
+		if (eav_val == 0xf1) { /* end of field 2, V-blank: start-of-frame */
+			switch (d->frame[d->hwframe].state) {
+			case FRAME_UNUSED:
+				DBG(DBG_FRAMING,
+				    printk(KERN_ERR "capture to unused frame %d\n", 
+					   d->hwframe));
+				break;
+			case FRAME_READY:
+				DBG(DBG_FRAMING,
+				    printk(KERN_DEBUG "frame started %d\n",
+					   d->hwframe));
+				/* start this frame (skip eav/sav) */
+				memcpy(d->frame[d->hwframe].pos, &buf[6], len);
+#if DMA_DEINTERLACE
+				if (!d->interlaced)
+					memcpy(d->frame[d->hwframe].pos-len, &buf[6], len);
+				d->frame[d->hwframe].pos += len*2;
+#else
+				d->frame[d->hwframe].pos += len;
+#endif
+				d->frame[d->hwframe].state = FRAME_GRABBING;
+				/* XXXKW check pos overflow */
+				break;
+			case FRAME_GRABBING:
+				/* kick over to new frame */
+				d->frame[d->hwframe].size = d->frame[d->hwframe].pos -
+					d->frame[d->hwframe].data;
+				d->frame[d->hwframe].state = FRAME_DONE;
+				DBG(DBG_FRAMING,
+				    printk(KERN_DEBUG "frame finished %d\n",
+					   d->frame[d->hwframe].size));
+				/* wake up a waiting reader */
+				DBG(DBG_IO, printk(KERN_DEBUG "wakeup\n"));
+				wake_up(&d->frame[d->hwframe].read_wait);
+				d->hwframe = (d->hwframe + 1) % NUM_FRAME;
+				if (d->frame[d->hwframe].state == FRAME_READY) {
+					/* start this frame */
+					DBG(DBG_FRAMING,
+					    printk(KERN_DEBUG "frame bumped %d\n",
+						   d->hwframe));
+					memcpy(d->frame[d->hwframe].pos, &buf[6], len);
+#if DMA_DEINTERLACE
+					if (!d->interlaced)
+						memcpy(d->frame[d->hwframe].pos-len, &buf[6], len);
+					d->frame[d->hwframe].pos += len*2;
+#else
+					d->frame[d->hwframe].pos += len;
+#endif
+					d->frame[d->hwframe].state = FRAME_GRABBING;
+				} else {
+					/* drop on the floor,
+					   note that we've stopped DMA'ing */
+					DBG(DBG_FRAMING,
+					    printk(KERN_DEBUG "frame capture halted\n"));
+					d->dma_enable = 0;
+				}
+				break;
+			case FRAME_DONE:
+				/* drop on the floor (must be waiting for sw) */
+				DBG(DBG_FRAMING,
+				    printk(KERN_DEBUG "frame capture halted\n"));
+				d->dma_enable = 0;
+				break;
+			}
+		} else {
+			switch (d->frame[d->hwframe].state) {
+			case FRAME_UNUSED:
+				DBG(DBG_FRAMING,
+				    printk(KERN_ERR "capture to unused frame %d\n",
+					   d->hwframe));
+				break;
+			case FRAME_READY:
+				/* drop on the floor (must have dropped something) */
+				DBG(DBG_FRAMING_LOUD,
+				    printk(KERN_DEBUG "missed SOF\n"));
+				break;
+			case FRAME_DONE:
+				/* drop on the floor (must be waiting for sw) */
+				DBG(DBG_FRAMING,
+				    printk(KERN_DEBUG "frame overflow\n"));
+				d->dma_enable = 0;
+				break;
+			case FRAME_GRABBING:
+#if DMA_DEINTERLACE
+				if (eav_val == 0xb6) {
+					d->frame[d->hwframe].pos = d->frame[d->hwframe].data;
+				}
+				memcpy(d->frame[d->hwframe].pos, &buf[6], len);
+				if (!d->interlaced)
+					memcpy(d->frame[d->hwframe].pos-len, &buf[6], len);
+				d->frame[d->hwframe].pos += len*2;
+#else
+				memcpy(d->frame[d->hwframe].pos, &buf[6], len);
+				d->frame[d->hwframe].pos += len;
+#endif
+				/* XXXKW check pos overflow */
+				break;
+			}
+		}
+	}
+	
+	if (d->dma_enable) {
+		out64(delta, MAC2_DMARX0_CSR(R_MAC_DMA_DSCR_CNT));
+		DBG(DBG_DESCR,
+		    printk(KERN_DEBUG IF_NAME ": interrupt adds %d -> %d descrs\n",
+			   delta, (int)in64(MAC2_DMARX0_CSR(R_MAC_DMA_DSCR_CNT))));
+	}
+}
+
+/* -----------------------------------------------------------------------
+ * /dev/video interface
+ * ----------------------------------------------------------------------- */
+
+static int saa7114h_open(struct video_device *vd, int nb)
+{
+	struct saa7114h *d = vd->priv;
+	uint32_t status;
+
+	if (!d || d->opened)
+		return -EBUSY;
+
+	d->opened = 1;
+	DBG(DBG_CALL, printk(KERN_DEBUG IF_NAME ": open\n"));
+
+	/* XXKW Should check this periodically!? */
+	status = saa7114h_reg_read(d, DECODER_STATUS);
+	d->interlaced = ((status & 0x80) != 0);
+
+#if !NULL_DMA
+	if (d->dma_enable) {
+		printk(IF_NAME ": open found DMA on?!\n");
+#if LAZY_READ
+	}
+#else
+	} else {
+		int descr;
+		d->dma_enable = 1;
+		DBG(DBG_DESCR, printk(IF_NAME ": open enabling DMA\n"));
+		/* Force capture to start into frame buffer 0 */
+		descr = in64(MAC2_DMARX0_CSR(R_MAC_DMA_DSCR_CNT));
+		DBG(DBG_DESCR,
+		    printk(IF_NAME ": open adds %d -> %d descrs\n",
+			   d->ff.ringsz-desc, descr));
+		out64(d->ff.ringsz-descr, MAC2_DMARX0_CSR(R_MAC_DMA_DSCR_CNT));
+	}
+#endif
+#endif
+
+	return 0;
+}
+
+static void saa7114h_release(struct video_device *vd)
+{
+	struct saa7114h *d = vd->priv;
+
+	DBG(DBG_CALL, printk(KERN_DEBUG IF_NAME ": release\n"));
+	d->opened = 0;
+	d->dma_enable = 0;
+
+	/* XXXKW do a clean drain of outstanding DMAs? toss leftover
+	   buffer contents to avoid stale pictures? */
+
+	return;
+}
+
+static long saa7114h_read(struct video_device *vd, char *buf,
+			  unsigned long count, int noblock)
+{
+	struct saa7114h *d = vd->priv;
+	int descr, status;
+
+	if (!d)
+		return -ENODEV;
+
+	/* XXKW Should check this periodically!? */
+	status = saa7114h_reg_read(d, DECODER_STATUS);
+//	  d->interlaced = ((status & 0x80) != 0);
+	
+#if !NULL_DMA
+#if LAZY_READ
+	if (!d->dma_enable) {
+		DBG(DBG_DESCR, printk(KERN_DEBUG IF_NAME ": enabling DMA\n"));
+		/* Give the buffer to the DMA engine (force ptr reset) */
+		d->swframe = d->hwframe;
+		d->frame[d->swframe].state = FRAME_READY;
+#if DMA_DEINTERLACE
+		d->frame[d->swframe].pos = d->frame[d->swframe].data+d->vw.width*RAW_PER_PIXEL;
+#else
+		d->frame[d->swframe].pos = d->frame[d->swframe].data;
+#endif
+		/* Fire up the DMA engine again if it stopped */
+		d->dma_enable = 1;
+		descr = in64(MAC2_DMARX0_CSR(R_MAC_DMA_DSCR_CNT));
+		out64(d->ff.ringsz-descr, MAC2_DMARX0_CSR(R_MAC_DMA_DSCR_CNT));
+	}
+#endif
+#endif
+
+	/* XXXKW mmap/read mixture could break the swframe sequence */
+
+	if (d->frame[d->swframe].state != FRAME_DONE) {
+		if (noblock)
+			return -EAGAIN;
+		else {
+			DBG(DBG_IO,
+			    printk(KERN_DEBUG IF_NAME ": sleeping for frame\n"));
+			interruptible_sleep_on(&d->frame[d->swframe].read_wait);
+			DBG(DBG_IO,
+			    printk(KERN_DEBUG IF_NAME ": awakened\n"));
+			if (signal_pending(current))
+				return -ERESTARTSYS;
+		}
+	}
+
+	if (count < d->frame[d->swframe].size)
+		return -EFAULT;
+
+	count = d->frame[d->swframe].size;
+	yuvconvert_inplace(d->frame[d->swframe].data, d->frame[d->swframe].size, d->vp.palette, 0);
+	copy_to_user(buf, d->frame[d->swframe].data, d->frame[d->swframe].size);
+	d->swframe = (d->swframe + 1) % NUM_FRAME;
+	/* XXXKW doesn't do format conversion!!! */
+#if !NULL_DMA
+#if !LAZY_READ
+	/* XXXKW Fire up the DMA engine again if it stopped ??? */
+	if (!d->dma_enable) {
+		DBG(DBG_DESCR, printk(KERN_DEBUG IF_NAME ": enabling DMA\n"));
+		/* Fire up the DMA engine again if it stopped */
+		d->dma_enable = 1;
+		descr = in64(MAC2_DMARX0_CSR(R_MAC_DMA_DSCR_CNT));
+		out64(d->ff.ringsz-descr, MAC2_DMARX0_CSR(R_MAC_DMA_DSCR_CNT));
+	}
+#endif
+#endif
+
+	return count;
+}
+
+static int saa7114h_ioctl(struct video_device *vd, unsigned int cmd, void *arg)
+{
+	struct saa7114h *d = vd->priv;
+	int val, reg, retval = 0;
+
+	if (!d)
+		return -ENODEV;
+
+	switch (cmd) {
+	case VIDIOCGCHAN:
+	{
+		struct video_channel v;
+
+		if (copy_from_user(&v, arg, sizeof(v))) {
+			retval = -EFAULT;
+			break;
+		}
+		if (v.channel != 0) {
+			retval = -EINVAL;
+			break;
+		}
+
+		v.channel = 0;
+		strcpy(v.name, "Camera");
+		v.tuners = 0;
+		v.flags = 0;
+		v.type = VIDEO_TYPE_CAMERA;
+		v.norm = 0;
+
+		if (copy_to_user(arg, &v, sizeof(v)))
+			retval = -EFAULT;
+		break;
+	}
+	
+	case VIDIOCSCHAN:
+	{
+		int v;
+
+		if (copy_from_user(&v, arg, sizeof(v)))
+			retval = -EFAULT;
+
+		if (retval == 0 && v != 0)
+			retval = -EINVAL;
+
+		break;
+	}
+
+	case VIDIOCGCAP:
+	{
+		struct video_capability b;
+
+		strcpy(b.name, "Philips SAA7114H Decoder");
+		b.type = VID_TYPE_CAPTURE /* | VID_TYPE_TELETEXT */ | VID_TYPE_SCALES;
+		b.channels = 1;
+		b.audios = 0;
+		b.maxwidth = MAX_HORIZ;
+		b.maxheight = MAX_VERT;
+		/* XXXKW find real values */
+		b.minwidth = 48;
+		b.minheight = 48;
+
+		if (copy_to_user(arg, &b, sizeof(b)))
+			retval = -EFAULT;
+
+		break;
+	}
+
+	/* image properties */
+	case VIDIOCGPICT:
+		if (copy_to_user(arg, &d->vp, sizeof(struct video_picture)))
+			retval = -EFAULT;
+		break;
+		
+	case VIDIOCSPICT:
+	{
+		struct video_picture vp;
+
+		/* copy_from_user */
+		if (copy_from_user(&vp, arg, sizeof(vp))) {
+			retval = -EFAULT;
+			break;
+		}
+
+		down(&d->param_lock);
+		/* brightness, colour, contrast need not check 0-65535 */
+		memcpy( &d->vp, &vp, sizeof(vp) );
+		/* update cam->params.colourParams */
+		saa7114h_set_cparams(d);
+		up(&d->param_lock);
+		break;
+	}
+
+	/* get/set capture window */
+	case VIDIOCGWIN:
+		if (copy_to_user(arg, &d->vw, sizeof(struct video_window)))
+			retval = -EFAULT;
+		break;
+	
+	case VIDIOCSWIN:
+	{
+		/* copy_from_user, check validity, copy to internal structure */
+		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;
+		}
+		if ((vw.width > MAX_HORIZ || vw.width < MIN_HORIZ) ||
+		    (vw.height > MAX_VERT || vw.height < MIN_VERT)) {
+			retval = -EINVAL;
+			break;
+		}
+
+		/* we set the video window to something smaller or equal to what
+		 * is requested by the user???
+		 */
+		down(&d->param_lock);
+		if (vw.width != d->vw.width || vw.height != d->vw.height) {
+			uint32_t scale_factor;
+			/* XXXKW base percentage on input stream, not MAX? */
+
+			/* Assert scaler reset */
+			saa7114h_reg_write(d, 0x88, 0x98);
+
+			/* Vertical scaling */
+			scale_factor = (MAX_VERT*1024) / vw.height;
+			saa7114h_reg_write(d, 0x9e, vw.height & 0xff);
+			saa7114h_reg_write(d, 0x9f, (vw.height >> 8) & 0xf);
+			saa7114h_reg_write(d, 0xb0, scale_factor & 0xff);
+			saa7114h_reg_write(d, 0xb1, (scale_factor >> 8) & 0xff);
+			saa7114h_reg_write(d, 0xb2, scale_factor & 0xff);
+			saa7114h_reg_write(d, 0xb3, (scale_factor >> 8) & 0xff);
+			/* Horizontal scaling */
+			scale_factor = (MAX_HORIZ*1024) / vw.width;
+			saa7114h_reg_write(d, 0x9c, vw.width & 0xff);
+			saa7114h_reg_write(d, 0x9d, (vw.width >> 8) & 0xf);
+			saa7114h_reg_write(d, 0xa8, scale_factor & 0xff);
+			saa7114h_reg_write(d, 0xa9, (scale_factor >> 8) & 0xff);
+			saa7114h_reg_write(d, 0xac, (scale_factor >> 1) & 0xff);
+			saa7114h_reg_write(d, 0xad, (scale_factor >> 9) & 0xff);
+#if 0
+			/* prescaler
+			saa7114h_reg_write(d, 0xa0, 2);
+			saa7114h_reg_write(d, 0xa1, 1);
+			saa7114h_reg_write(d, 0xa2, 1);
+			*/
+#endif
+
+			/* Release scaler reset */
+			saa7114h_reg_write(d, 0x88, 0xb8);
+			d->vw.width = vw.width;
+			d->vw.height = vw.height;
+		}
+		up(&d->param_lock);
+		break;
+	}
+
+	/* mmap interface */
+	case VIDIOCGMBUF:
+	{
+		struct video_mbuf vm;
+		int i;
+
+		memset(&vm, 0, sizeof(vm));
+		vm.size = MAX_FRAME_SIZE*NUM_FRAME;
+		vm.frames = NUM_FRAME;
+		for (i = 0; i < NUM_FRAME; i++)
+			vm.offsets[i] = MAX_FRAME_SIZE * i;
+
+		if (copy_to_user((void *)arg, (void *)&vm, sizeof(vm)))
+			retval = -EFAULT;
+
+		break;
+	}
+
+	case VIDIOCMCAPTURE:
+	{
+		struct video_mmap vm;
+		int descr, status;
+
+		if (copy_from_user((void *)&vm, (void *)arg, sizeof(vm))) {
+			retval = -EFAULT;
+			break;
+		}
+		if (vm.frame<0||vm.frame>NUM_FRAME) {
+			retval = -EINVAL;
+			break;
+		}
+
+		DBG(DBG_CALL,
+		    printk(KERN_DEBUG IF_NAME ":ioctl MCAPTURE %d\n", vm.frame));
+
+		d->vp.palette = vm.format;
+		/* XXXKW set depth? */
+		/* XXXKW match/update for vm.width, vm.height */
+
+		/* XXKW Should check this periodically!? */
+		status = saa7114h_reg_read(d, DECODER_STATUS);
+//		  d->interlaced = ((status & 0x80) != 0);
+
+		/* Give the buffer to the DMA engine */
+		/* XXXKW vm.frame vs d->swframe!!  mmap/read mismatch */
+#if DMA_DEINTERLACE
+		d->frame[vm.frame].pos = d->frame[vm.frame].data + d->vw.width*RAW_PER_PIXEL;
+#else
+		d->frame[vm.frame].pos = d->frame[vm.frame].data;
+#endif
+#if !NULL_DMA
+		d->frame[vm.frame].state = FRAME_READY;
+		/* Fire up the DMA engine again if it stopped */
+		if (!d->dma_enable) {
+			d->dma_enable = 1;
+			d->hwframe = d->swframe = vm.frame;
+			descr = in64(MAC2_DMARX0_CSR(R_MAC_DMA_DSCR_CNT));
+			DBG(DBG_DESCR,
+			    printk(KERN_DEBUG IF_NAME ": capture adds %d -> %d descrs\n",
+				   d->ff.ringsz-descr, descr));
+			out64(d->ff.ringsz-descr, MAC2_DMARX0_CSR(R_MAC_DMA_DSCR_CNT));
+		}
+#endif
+		break;
+	}
+
+	case VIDIOCSYNC:
+	{
+		int frame;
+
+		if (copy_from_user((void *)&frame, arg, sizeof(int))) {
+			retval = -EFAULT;
+			break;
+		}
+
+		if (frame<0 || frame >= NUM_FRAME) {
+			retval = -EINVAL;
+			break;
+		}
+
+		DBG(DBG_CALL, printk(KERN_DEBUG IF_NAME ":ioctl CSYNC %d\n", frame));
+
+		switch (d->frame[frame].state) {
+		case FRAME_UNUSED:
+			DBG(DBG_IO,
+			    printk(KERN_ERR IF_NAME ":sync to unused frame %d\n", frame));
+			retval = -EINVAL;
+			break;
+
+		case FRAME_READY:
+		case FRAME_GRABBING:
+			DBG(DBG_IO,
+			    printk(KERN_DEBUG IF_NAME ": sleeping for frame %d\n", frame));
+			interruptible_sleep_on(&d->frame[frame].read_wait);
+			DBG(DBG_IO,
+			    printk(KERN_DEBUG IF_NAME ": awakened\n"));
+			if (signal_pending(current))
+				return -ERESTARTSYS;
+		case FRAME_DONE:
+#if !NULL_DMA
+			yuvconvert_inplace(d->frame[frame].data,
+					   d->frame[frame].size,
+					   d->vp.palette, 1);
+			d->frame[frame].state = FRAME_UNUSED;
+#endif
+			DBG(DBG_IO,
+			    printk(KERN_DEBUG IF_NAME ": sync finished %d\n",
+				   frame));
+			break;
+		}
+		break;
+	}
+
+	case VIDIOREADREG:
+		reg = *(int *)arg;
+		DBG(DBG_REGISTER, printk(KERN_DEBUG IF_NAME ": read of %02x\n", reg));
+		if ((reg > 0xEF) || (reg < 0))
+			return -EINVAL;
+		val = saa7114h_reg_read((struct saa7114h *)vd->priv, reg);
+		if (val == -1)
+			return -EIO;
+		*(int *)arg = val;
+		break;
+	case VIDIOWRITEREG:
+		if (copy_from_user(&reg, arg, sizeof(int)) ||
+		    copy_from_user(&val, arg+sizeof(int), sizeof(int)))
+			return -EFAULT;
+		DBG(DBG_REGISTER, printk(KERN_DEBUG IF_NAME ": write of %02x <- %02x\n", reg, val));
+		if ((reg > 0xEF) || (reg < 0))
+			return -EINVAL;
+		val = saa7114h_reg_write((struct saa7114h *)vd->priv, reg, val);
+		if (val == -1)
+			return -EIO;
+		break;
+	case VIDIOGRABFRAME:
+		return grab_frame((struct saa7114h *)vd->priv, arg, 0);
+	case VIDIOSHOWEAV:
+		return grab_frame((struct saa7114h *)vd->priv, arg, 1);
+	default:
+		retval = -EINVAL;
+		break;
+	}
+
+	return retval;
+}
+
+static int saa7114h_mmap(struct video_device *vd, const char *adr,
+			 unsigned long size)
+{
+	struct saa7114h *d = vd->priv;
+	unsigned long start = (unsigned long)adr;
+	unsigned long page, pos;
+
+	if (!d)
+		return -ENODEV;
+
+	if (size > MAX_MMAP_SIZE) {
+		printk("mmap: bad size %lu > %lu\n", size, MAX_MMAP_SIZE);
+		return -EINVAL;
+	}
+
+	/* make this _really_ smp-safe */
+	if (down_interruptible(&d->busy_lock))
+		return -EINTR;
+
+	pos = (unsigned long)(d->frame_buf);
+	while (size > 0) {
+		page = kvirt_to_pa(pos);
+		if (remap_page_range(start, page, PAGE_SIZE, PAGE_SHARED)) {
+			up(&d->busy_lock);
+			return -EAGAIN;
+		}
+		start += PAGE_SIZE;
+		pos += PAGE_SIZE;
+		if (size > PAGE_SIZE)
+			size -= PAGE_SIZE;
+		else
+			size = 0;
+	}
+	up(&d->busy_lock);
+
+	return 0;
+}
+
+/* -----------------------------------------------------------------------
+ * Device probing and initialization
+ * ----------------------------------------------------------------------- */
+
+/* Default values to program into SAA7114H */
+static const unsigned char reg_init[] =	{
+	0x00, 0x00,	/* 00 - ID byte */
+
+	/*front end */
+	0x01, 0x08,	/* 01 - Horizontal increment -> recommended delay */
+	0x02, 0xC4,	/* 02 - AI Control 1 (CVBS AI23) */
+	0x03, 0x10,	/* 03 - AI Control 2 */
+	0x04, 0x90,	/* 04 - AI Control 3 (Gain ch 1) */
+	0x05, 0x90,	/* 05 - AI Control 4 (Gain ch 2) */
+	
+	/* decoder */
+	0x06, 0xEB,	/* 06 - Horiz sync start */
+	0x07, 0xE0,	/* 07 - Horiz sync stop */
+	0x08, 0x98,	/* 08 - Sync control */
+	0x09, 0x40,	/* 09 - L Control */
+	0x0a, 0x80,	/* 0a - L Brightness */
+	0x0b, 0x44,	/* 0b - L Contrast */
+	0x0c, 0x40,	/* 0c - C Saturation */
+	0x0d, 0x00,	/* 0d - C Hue */
+	0x0e, 0x89,	/* 0e - C Control 1 */
+	0x0f, 0x0f,	/* 0f - C Gain (??? 0x2A recommended) */
+	0x10, 0x0E,	/* 10 - C Control 2 */
+	0x11, 0x00,	/* 11 - Mode/Delay */
+	0x12, 0x00,	/* 12 - RT signal control */
+	0x13, 0x00,	/* 13 - RT/X output */
+	0x14, 0x00,	/* 14 - Analog, Compat */
+	0x15, 0x11,	/* 15 - VGATE start */
+	0x16, 0xFE,	/* 16 - VGATE stop */
+	0x17, 0x40,	/* 17 - Misc VGATE (disable LLC2) */
+	0x18, 0x40,	/* 18 - Raw data gain - 128 */
+	0x19, 0x80,	/* 19 - Raw data offset - 0 */
+
+	/* Global settings */
+	0x88, 0x98,	/* 88 - AI1x on, AI2x off; decoder/slicer off; ACLK gen off */
+	0x83, 0x00,	/* 83 - X-port output disabled */
+	0x84, 0xF0,	/* 84 - I-port V/G output framing, IGP1=0=IGP0=0 */
+	0x85, 0x00,	/* 85 - I-port default polarities, X-port signals */
+	0x86, 0x40,	/* 86 - more IGP1/0, FIFO level, only video transmitted */
+	0x87, 0x01,	/* 87 - ICK default, IDQ default, I-port output enabled */
+
+	/* Task A: scaler input config and output format */
+	0x90, 0x00,	/* 90 - Task handling */
+	0x91, 0x08,	/* 91 - Scalar input and format */
+	0x92, 0x10,	/* 92 - Reference signal def */
+	0x93, 0x80,	/* 93 - I-port output */
+
+	/* Task B */
+	0xc0, 0x42,	/* 90 - Task handling */
+	0xc1, 0x08,	/* 91 - Scalar input and format */
+	0xc2, 0x10,	/* 92 - Reference signal def */
+	0xc3, 0x80,	/* 93 - I-port output */
+
+	/* Input and Output windows */
+	0x94, 0x10,	/*  - */
+	0x95, 0x00,	/*  - */
+	0x96, 0xD0,	/*  - */
+	0x97, 0x02,	/*  - */
+	0x98, 0x0A,	/*  - */
+	0x99, 0x00,	/*  - */
+	0x9a, 0xF2,	/*  - */
+	0x9b, 0x00,	/*  - */
+	0x9c, 0xD0,	/*  - */
+	0x9d, 0x02,	/*  - */
+	0xc4, 0x10,	/*  - */
+	0xc5, 0x00,	/*  - */
+	0xc6, 0xD0,	/*  - */
+	0xc7, 0x02,	/*  - */
+	0xc8, 0x0A,	/*  - */
+	0xc9, 0x00,	/*  - */
+	0xca, 0xF2,	/*  - */
+	0xcb, 0x00,	/*  - */
+	0xcc, 0xD0,	/*  - */
+	0xcd, 0x02,	/*  - */
+
+	0x9e, 0xf0,	/*  - */
+	0x9f, 0x00,	/*  - */
+	0xce, 0xf0,	/*  - */
+	0xcf, 0x00,	/*  - */
+
+	/* Prefiltering and prescaling */
+	0xa0, 0x01,	/*  - */
+	0xa1, 0x00,	/*  - */
+	0xa2, 0x00,	/*  - */
+	0xa4, 0x80,	/*  - */
+	0xa5, 0x40,	/*  - */
+	0xa6, 0x40,	/*  - */
+	0xd4, 0x80,	/*  - */
+	0xd5, 0x40,	/*  - */
+	0xd6, 0x40,	/*  - */
+
+	/* Horizontal phase scaling */
+	0xa8, 0x00,	/*  - */
+	0xa9, 0x04,	/*  - */
+	0xaa, 0x00,	/*  - */
+	0xd8, 0x00,	/*  - */
+	0xd9, 0x04,	/*  - */
+	0xda, 0x00,	/*  - */
+
+	0xac, 0x00,	/*  - */
+	0xad, 0x02,	/*  - */
+	0xae, 0x00,	/*  - */
+	0xdc, 0x00,	/*  - */
+	0xdd, 0x02,	/*  - */
+	0xde, 0x00,	/*  - */
+
+	/* Vertical phase scaling */
+	0xb0, 0x00,	/*  - */
+	0xb1, 0x04,	/*  - */
+	0xb2, 0x00,	/*  - */
+	0xb3, 0x04,	/*  - */
+	0xe0, 0x00,	/*  - */
+	0xe1, 0x04,	/*  - */
+	0xe2, 0x00,	/*  - */
+	0xe3, 0x04,	/*  - */
+	0xb4, 0x00,	/* b4 - vscale mode control */
+	0xe4, 0x00,	/* b4 - vscale mode control */
+
+	/* Task enables */
+	0x80, 0x10,	/* 80 - LLC->ICLK, dq->IDQ, scaler->F/V timing, task enables */
+
+	/* Reset the slicer */
+	0x88, 0xb8,	/* 88 - AI1x on, AI2x off; decoder/slicer on; ACLK gen off */
+};
+
+static int saa7114h_attach(struct i2c_adapter *adap, int addr, unsigned short flags, int kind)
+{
+	struct i2c_client *client;
+	struct video_device *vd;
+	struct saa7114h *decoder;
+	int err;
+	int val, i;
+
+	client = kmalloc(sizeof(*client), GFP_KERNEL);
+	if (client == NULL)
+		return -ENOMEM;
+	client->adapter = adap;
+	client->addr = addr;
+	client->driver = &i2c_driver_saa7114h;
+	strcpy(client->name, IF_NAME);
+
+	decoder = kmalloc(sizeof(*decoder), GFP_KERNEL);
+	if (decoder == NULL) {
+		kfree(client);
+		return -ENOMEM;
+	}
+	memset(decoder, 0, sizeof(struct saa7114h));
+	decoder->client = client;
+	decoder->dma_enable = 0;
+	decoder->palette = VIDEO_PALETTE_UYVY;
+	decoder->depth = 16;
+	decoder->vw.width = MAX_HORIZ;
+	decoder->vw.height = MAX_VERT;
+	decoder->frame_buf = rvmalloc(MAX_FRAME_SIZE*NUM_FRAME);
+	if (!decoder->frame_buf) {
+		kfree(decoder);
+		kfree(client);
+		return -ENOMEM;
+	}
+	/* XXXKW use clear_page? */
+	memset(decoder->frame_buf, 0, MAX_FRAME_SIZE*NUM_FRAME);
+	printk("saa7114h_attach: frame_buf = (fb=%8p / %08lx)\n",
+	       decoder->frame_buf, kvirt_to_pa((int)decoder->frame_buf));
+	for (i=0; i<NUM_FRAME; i++) {
+		decoder->frame[i].data = decoder->frame_buf+i*MAX_FRAME_SIZE;
+#if NULL_DMA
+		decoder->frame[i].state = FRAME_DONE;
+#else
+		decoder->frame[i].state = FRAME_UNUSED;
+#endif
+		init_waitqueue_head(&decoder->frame[i].read_wait);
+	}
+	decoder->irq = K_INT_MAC_2;
+	if (request_irq
+	    (decoder->irq, saa7114h_interrupt, 0, "Philips SAA7114h", decoder)) {
+		rvfree(decoder->frame_buf, MAX_FRAME_SIZE*NUM_FRAME);
+		kfree(decoder);
+		kfree(client);
+		return -ENOMEM;
+	}
+	init_MUTEX(&decoder->param_lock);
+	init_MUTEX(&decoder->busy_lock);
+
+	if ((err = i2c_attach_client(client)) < 0) {
+		kfree(client);
+		kfree(decoder);
+		return err;
+	}
+
+	if (saa7114h_reg_init(decoder, reg_init, sizeof(reg_init)) ||
+	    saa7114h_get_cparams(decoder)) {
+		i2c_detach_client(client);
+		kfree(client);
+		kfree(decoder);
+		return -ENODEV;
+	}
+
+	vd = kmalloc(sizeof(*vd), GFP_KERNEL);
+	memset(vd, 0, sizeof(*vd));
+	if (vd == NULL) {
+		i2c_detach_client(client);
+		kfree(client);
+		kfree(decoder);
+		return -ENOMEM;
+	}
+	vd->priv = decoder;
+	strcpy(vd->name, IF_NAME);
+	vd->type = VID_TYPE_CAPTURE;
+	vd->hardware = VID_HARDWARE_SAA7114H;
+	vd->open =  saa7114h_open;
+	vd->close = saa7114h_release;
+	vd->read =  saa7114h_read;
+	vd->ioctl = saa7114h_ioctl;
+	vd->mmap =  saa7114h_mmap;
+
+	if ((err = video_register_device(vd, VFL_TYPE_GRABBER, -1)) < 0) {
+		i2c_detach_client(client);
+		kfree(client);
+		kfree(decoder);
+		kfree(vd);
+		return err;
+	}
+
+	client->data = vd;
+	decoder->vd = vd;
+
+	/* Turn on the ITRDY - preserve the GENO pin for syncser */
+	val = in64(KSEG1 + A_MAC_REGISTER(2, R_MAC_MDIO));
+	out64(M_MAC_MDIO_OUT | (val & M_MAC_GENC),
+	      KSEG1 + A_MAC_REGISTER(2, R_MAC_MDIO));
+
+	if ((err = dma_setup(decoder))) {
+		i2c_detach_client(client);
+		kfree(client);
+		kfree(decoder);
+		kfree(vd);
+		return err;
+	}
+
+	printk("saa7114h_attach successful\n");
+
+#ifdef CONFIG_PROC_FS
+	proc_saa7114h_create();
+	create_proc_decoder(vd->priv);
+#endif
+
+	MOD_INC_USE_COUNT;
+
+	return 0;
+}
+
+/* Addresses to scan */
+static unsigned short normal_i2c[] = {I2C_CLIENT_END};
+static unsigned short normal_i2c_range[] = {0x20, 0x21, I2C_CLIENT_END};
+static unsigned short probe[2]	      = { I2C_CLIENT_END, I2C_CLIENT_END };
+static unsigned short probe_range[2]  = { I2C_CLIENT_END, I2C_CLIENT_END };
+static unsigned short ignore[2]	      = { I2C_CLIENT_END, I2C_CLIENT_END };
+static unsigned short ignore_range[2] = { I2C_CLIENT_END, I2C_CLIENT_END };
+static unsigned short force[2]	      = { I2C_CLIENT_END, I2C_CLIENT_END };
+
+static struct i2c_client_address_data addr_data = {
+	normal_i2c, normal_i2c_range,
+	probe, probe_range,
+	ignore, ignore_range,
+	force
+};
+
+static int saa7114h_probe(struct i2c_adapter *adap)
+{
+	/* Look for this device on the given adapter (bus) */
+	if (adap->id == (I2C_ALGO_SIBYTE | I2C_HW_SIBYTE))
+		return i2c_probe(adap, &addr_data, &saa7114h_attach);
+	else
+		return 0;
+}
+
+static int saa7114h_detach(struct i2c_client *device)
+{
+#if 0
+	kfree(device->data);
+	MOD_DEC_USE_COUNT;
+#endif
+#ifdef CONFIG_PROC_FS
+	destroy_proc_decoder(((struct video_device *)device->data)->priv);
+	proc_saa7114h_destroy();
+#endif
+	return 0;
+}
+
+/* ----------------------------------------------------------------------- */
+
+static int __init swarm_7114h_init(void)
+{
+	return i2c_add_driver(&i2c_driver_saa7114h);
+}
+
+static void __exit swarm_7114h_cleanup(void)
+{
+}
+
+MODULE_AUTHOR("Kip Walker, Broadcom Corp.");
+MODULE_DESCRIPTION("Philips SAA7114H Driver for Broadcom SWARM board");
+
+module_init(swarm_7114h_init);
+module_exit(swarm_7114h_cleanup);

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)