diff options
Diffstat (limited to 'drivers/media/common/saa7146')
| -rw-r--r-- | drivers/media/common/saa7146/Kconfig | 9 | ||||
| -rw-r--r-- | drivers/media/common/saa7146/Makefile | 5 | ||||
| -rw-r--r-- | drivers/media/common/saa7146/saa7146_core.c | 590 | ||||
| -rw-r--r-- | drivers/media/common/saa7146/saa7146_fops.c | 663 | ||||
| -rw-r--r-- | drivers/media/common/saa7146/saa7146_hlp.c | 1048 | ||||
| -rw-r--r-- | drivers/media/common/saa7146/saa7146_i2c.c | 423 | ||||
| -rw-r--r-- | drivers/media/common/saa7146/saa7146_vbi.c | 498 | ||||
| -rw-r--r-- | drivers/media/common/saa7146/saa7146_video.c | 1309 | 
8 files changed, 4545 insertions, 0 deletions
diff --git a/drivers/media/common/saa7146/Kconfig b/drivers/media/common/saa7146/Kconfig new file mode 100644 index 00000000000..769c6f8142d --- /dev/null +++ b/drivers/media/common/saa7146/Kconfig @@ -0,0 +1,9 @@ +config VIDEO_SAA7146 +	tristate +	depends on I2C && PCI + +config VIDEO_SAA7146_VV +	tristate +	depends on VIDEO_V4L2 +	select VIDEOBUF_DMA_SG +	select VIDEO_SAA7146 diff --git a/drivers/media/common/saa7146/Makefile b/drivers/media/common/saa7146/Makefile new file mode 100644 index 00000000000..3219b00a877 --- /dev/null +++ b/drivers/media/common/saa7146/Makefile @@ -0,0 +1,5 @@ +saa7146-objs    := saa7146_i2c.o saa7146_core.o +saa7146_vv-objs := saa7146_fops.o saa7146_video.o saa7146_hlp.o saa7146_vbi.o + +obj-$(CONFIG_VIDEO_SAA7146) += saa7146.o +obj-$(CONFIG_VIDEO_SAA7146_VV) += saa7146_vv.o diff --git a/drivers/media/common/saa7146/saa7146_core.c b/drivers/media/common/saa7146/saa7146_core.c new file mode 100644 index 00000000000..34b0d0ddeef --- /dev/null +++ b/drivers/media/common/saa7146/saa7146_core.c @@ -0,0 +1,590 @@ +/* +    saa7146.o - driver for generic saa7146-based hardware + +    Copyright (C) 1998-2003 Michael Hunold <michael@mihu.de> + +    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. +*/ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <media/saa7146.h> +#include <linux/module.h> + +static int saa7146_num; + +unsigned int saa7146_debug; + +module_param(saa7146_debug, uint, 0644); +MODULE_PARM_DESC(saa7146_debug, "debug level (default: 0)"); + +#if 0 +static void dump_registers(struct saa7146_dev* dev) +{ +	int i = 0; + +	pr_info(" @ %li jiffies:\n", jiffies); +	for (i = 0; i <= 0x148; i += 4) +		pr_info("0x%03x: 0x%08x\n", i, saa7146_read(dev, i)); +} +#endif + +/**************************************************************************** + * gpio and debi helper functions + ****************************************************************************/ + +void saa7146_setgpio(struct saa7146_dev *dev, int port, u32 data) +{ +	u32 value = 0; + +	BUG_ON(port > 3); + +	value = saa7146_read(dev, GPIO_CTRL); +	value &= ~(0xff << (8*port)); +	value |= (data << (8*port)); +	saa7146_write(dev, GPIO_CTRL, value); +} + +/* This DEBI code is based on the saa7146 Stradis driver by Nathan Laredo */ +static inline int saa7146_wait_for_debi_done_sleep(struct saa7146_dev *dev, +				unsigned long us1, unsigned long us2) +{ +	unsigned long timeout; +	int err; + +	/* wait for registers to be programmed */ +	timeout = jiffies + usecs_to_jiffies(us1); +	while (1) { +		err = time_after(jiffies, timeout); +		if (saa7146_read(dev, MC2) & 2) +			break; +		if (err) { +			pr_err("%s: %s timed out while waiting for registers getting programmed\n", +			       dev->name, __func__); +			return -ETIMEDOUT; +		} +		msleep(1); +	} + +	/* wait for transfer to complete */ +	timeout = jiffies + usecs_to_jiffies(us2); +	while (1) { +		err = time_after(jiffies, timeout); +		if (!(saa7146_read(dev, PSR) & SPCI_DEBI_S)) +			break; +		saa7146_read(dev, MC2); +		if (err) { +			DEB_S("%s: %s timed out while waiting for transfer completion\n", +			      dev->name, __func__); +			return -ETIMEDOUT; +		} +		msleep(1); +	} + +	return 0; +} + +static inline int saa7146_wait_for_debi_done_busyloop(struct saa7146_dev *dev, +				unsigned long us1, unsigned long us2) +{ +	unsigned long loops; + +	/* wait for registers to be programmed */ +	loops = us1; +	while (1) { +		if (saa7146_read(dev, MC2) & 2) +			break; +		if (!loops--) { +			pr_err("%s: %s timed out while waiting for registers getting programmed\n", +			       dev->name, __func__); +			return -ETIMEDOUT; +		} +		udelay(1); +	} + +	/* wait for transfer to complete */ +	loops = us2 / 5; +	while (1) { +		if (!(saa7146_read(dev, PSR) & SPCI_DEBI_S)) +			break; +		saa7146_read(dev, MC2); +		if (!loops--) { +			DEB_S("%s: %s timed out while waiting for transfer completion\n", +			      dev->name, __func__); +			return -ETIMEDOUT; +		} +		udelay(5); +	} + +	return 0; +} + +int saa7146_wait_for_debi_done(struct saa7146_dev *dev, int nobusyloop) +{ +	if (nobusyloop) +		return saa7146_wait_for_debi_done_sleep(dev, 50000, 250000); +	else +		return saa7146_wait_for_debi_done_busyloop(dev, 50000, 250000); +} + +/**************************************************************************** + * general helper functions + ****************************************************************************/ + +/* this is videobuf_vmalloc_to_sg() from videobuf-dma-sg.c +   make sure virt has been allocated with vmalloc_32(), otherwise the BUG() +   may be triggered on highmem machines */ +static struct scatterlist* vmalloc_to_sg(unsigned char *virt, int nr_pages) +{ +	struct scatterlist *sglist; +	struct page *pg; +	int i; + +	sglist = kcalloc(nr_pages, sizeof(struct scatterlist), GFP_KERNEL); +	if (NULL == sglist) +		return NULL; +	sg_init_table(sglist, nr_pages); +	for (i = 0; i < nr_pages; i++, virt += PAGE_SIZE) { +		pg = vmalloc_to_page(virt); +		if (NULL == pg) +			goto err; +		BUG_ON(PageHighMem(pg)); +		sg_set_page(&sglist[i], pg, PAGE_SIZE, 0); +	} +	return sglist; + + err: +	kfree(sglist); +	return NULL; +} + +/********************************************************************************/ +/* common page table functions */ + +void *saa7146_vmalloc_build_pgtable(struct pci_dev *pci, long length, struct saa7146_pgtable *pt) +{ +	int pages = (length+PAGE_SIZE-1)/PAGE_SIZE; +	void *mem = vmalloc_32(length); +	int slen = 0; + +	if (NULL == mem) +		goto err_null; + +	if (!(pt->slist = vmalloc_to_sg(mem, pages))) +		goto err_free_mem; + +	if (saa7146_pgtable_alloc(pci, pt)) +		goto err_free_slist; + +	pt->nents = pages; +	slen = pci_map_sg(pci,pt->slist,pt->nents,PCI_DMA_FROMDEVICE); +	if (0 == slen) +		goto err_free_pgtable; + +	if (0 != saa7146_pgtable_build_single(pci, pt, pt->slist, slen)) +		goto err_unmap_sg; + +	return mem; + +err_unmap_sg: +	pci_unmap_sg(pci, pt->slist, pt->nents, PCI_DMA_FROMDEVICE); +err_free_pgtable: +	saa7146_pgtable_free(pci, pt); +err_free_slist: +	kfree(pt->slist); +	pt->slist = NULL; +err_free_mem: +	vfree(mem); +err_null: +	return NULL; +} + +void saa7146_vfree_destroy_pgtable(struct pci_dev *pci, void *mem, struct saa7146_pgtable *pt) +{ +	pci_unmap_sg(pci, pt->slist, pt->nents, PCI_DMA_FROMDEVICE); +	saa7146_pgtable_free(pci, pt); +	kfree(pt->slist); +	pt->slist = NULL; +	vfree(mem); +} + +void saa7146_pgtable_free(struct pci_dev *pci, struct saa7146_pgtable *pt) +{ +	if (NULL == pt->cpu) +		return; +	pci_free_consistent(pci, pt->size, pt->cpu, pt->dma); +	pt->cpu = NULL; +} + +int saa7146_pgtable_alloc(struct pci_dev *pci, struct saa7146_pgtable *pt) +{ +	__le32       *cpu; +	dma_addr_t   dma_addr = 0; + +	cpu = pci_alloc_consistent(pci, PAGE_SIZE, &dma_addr); +	if (NULL == cpu) { +		return -ENOMEM; +	} +	pt->size = PAGE_SIZE; +	pt->cpu  = cpu; +	pt->dma  = dma_addr; + +	return 0; +} + +int saa7146_pgtable_build_single(struct pci_dev *pci, struct saa7146_pgtable *pt, +	struct scatterlist *list, int sglen  ) +{ +	__le32 *ptr, fill; +	int nr_pages = 0; +	int i,p; + +	BUG_ON(0 == sglen); +	BUG_ON(list->offset > PAGE_SIZE); + +	/* if we have a user buffer, the first page may not be +	   aligned to a page boundary. */ +	pt->offset = list->offset; + +	ptr = pt->cpu; +	for (i = 0; i < sglen; i++, list++) { +/* +		pr_debug("i:%d, adr:0x%08x, len:%d, offset:%d\n", +			 i, sg_dma_address(list), sg_dma_len(list), +			 list->offset); +*/ +		for (p = 0; p * 4096 < list->length; p++, ptr++) { +			*ptr = cpu_to_le32(sg_dma_address(list) + p * 4096); +			nr_pages++; +		} +	} + + +	/* safety; fill the page table up with the last valid page */ +	fill = *(ptr-1); +	for(i=nr_pages;i<1024;i++) { +		*ptr++ = fill; +	} + +/* +	ptr = pt->cpu; +	pr_debug("offset: %d\n", pt->offset); +	for(i=0;i<5;i++) { +		pr_debug("ptr1 %d: 0x%08x\n", i, ptr[i]); +	} +*/ +	return 0; +} + +/********************************************************************************/ +/* interrupt handler */ +static irqreturn_t interrupt_hw(int irq, void *dev_id) +{ +	struct saa7146_dev *dev = dev_id; +	u32 isr; +	u32 ack_isr; + +	/* read out the interrupt status register */ +	ack_isr = isr = saa7146_read(dev, ISR); + +	/* is this our interrupt? */ +	if ( 0 == isr ) { +		/* nope, some other device */ +		return IRQ_NONE; +	} + +	if (dev->ext) { +		if (dev->ext->irq_mask & isr) { +			if (dev->ext->irq_func) +				dev->ext->irq_func(dev, &isr); +			isr &= ~dev->ext->irq_mask; +		} +	} +	if (0 != (isr & (MASK_27))) { +		DEB_INT("irq: RPS0 (0x%08x)\n", isr); +		if (dev->vv_data && dev->vv_callback) +			dev->vv_callback(dev,isr); +		isr &= ~MASK_27; +	} +	if (0 != (isr & (MASK_28))) { +		if (dev->vv_data && dev->vv_callback) +			dev->vv_callback(dev,isr); +		isr &= ~MASK_28; +	} +	if (0 != (isr & (MASK_16|MASK_17))) { +		SAA7146_IER_DISABLE(dev, MASK_16|MASK_17); +		/* only wake up if we expect something */ +		if (0 != dev->i2c_op) { +			dev->i2c_op = 0; +			wake_up(&dev->i2c_wq); +		} else { +			u32 psr = saa7146_read(dev, PSR); +			u32 ssr = saa7146_read(dev, SSR); +			pr_warn("%s: unexpected i2c irq: isr %08x psr %08x ssr %08x\n", +				dev->name, isr, psr, ssr); +		} +		isr &= ~(MASK_16|MASK_17); +	} +	if( 0 != isr ) { +		ERR("warning: interrupt enabled, but not handled properly.(0x%08x)\n", +		    isr); +		ERR("disabling interrupt source(s)!\n"); +		SAA7146_IER_DISABLE(dev,isr); +	} +	saa7146_write(dev, ISR, ack_isr); +	return IRQ_HANDLED; +} + +/*********************************************************************************/ +/* configuration-functions                                                       */ + +static int saa7146_init_one(struct pci_dev *pci, const struct pci_device_id *ent) +{ +	struct saa7146_pci_extension_data *pci_ext = (struct saa7146_pci_extension_data *)ent->driver_data; +	struct saa7146_extension *ext = pci_ext->ext; +	struct saa7146_dev *dev; +	int err = -ENOMEM; + +	/* clear out mem for sure */ +	dev = kzalloc(sizeof(struct saa7146_dev), GFP_KERNEL); +	if (!dev) { +		ERR("out of memory\n"); +		goto out; +	} + +	DEB_EE("pci:%p\n", pci); + +	err = pci_enable_device(pci); +	if (err < 0) { +		ERR("pci_enable_device() failed\n"); +		goto err_free; +	} + +	/* enable bus-mastering */ +	pci_set_master(pci); + +	dev->pci = pci; + +	/* get chip-revision; this is needed to enable bug-fixes */ +	dev->revision = pci->revision; + +	/* remap the memory from virtual to physical address */ + +	err = pci_request_region(pci, 0, "saa7146"); +	if (err < 0) +		goto err_disable; + +	dev->mem = ioremap(pci_resource_start(pci, 0), +			   pci_resource_len(pci, 0)); +	if (!dev->mem) { +		ERR("ioremap() failed\n"); +		err = -ENODEV; +		goto err_release; +	} + +	/* we don't do a master reset here anymore, it screws up +	   some boards that don't have an i2c-eeprom for configuration +	   values */ +/* +	saa7146_write(dev, MC1, MASK_31); +*/ + +	/* disable all irqs */ +	saa7146_write(dev, IER, 0); + +	/* shut down all dma transfers and rps tasks */ +	saa7146_write(dev, MC1, 0x30ff0000); + +	/* clear out any rps-signals pending */ +	saa7146_write(dev, MC2, 0xf8000000); + +	/* request an interrupt for the saa7146 */ +	err = request_irq(pci->irq, interrupt_hw, IRQF_SHARED, +			  dev->name, dev); +	if (err < 0) { +		ERR("request_irq() failed\n"); +		goto err_unmap; +	} + +	err = -ENOMEM; + +	/* get memory for various stuff */ +	dev->d_rps0.cpu_addr = pci_alloc_consistent(pci, SAA7146_RPS_MEM, +						    &dev->d_rps0.dma_handle); +	if (!dev->d_rps0.cpu_addr) +		goto err_free_irq; +	memset(dev->d_rps0.cpu_addr, 0x0, SAA7146_RPS_MEM); + +	dev->d_rps1.cpu_addr = pci_alloc_consistent(pci, SAA7146_RPS_MEM, +						    &dev->d_rps1.dma_handle); +	if (!dev->d_rps1.cpu_addr) +		goto err_free_rps0; +	memset(dev->d_rps1.cpu_addr, 0x0, SAA7146_RPS_MEM); + +	dev->d_i2c.cpu_addr = pci_alloc_consistent(pci, SAA7146_RPS_MEM, +						   &dev->d_i2c.dma_handle); +	if (!dev->d_i2c.cpu_addr) +		goto err_free_rps1; +	memset(dev->d_i2c.cpu_addr, 0x0, SAA7146_RPS_MEM); + +	/* the rest + print status message */ + +	/* create a nice device name */ +	sprintf(dev->name, "saa7146 (%d)", saa7146_num); + +	pr_info("found saa7146 @ mem %p (revision %d, irq %d) (0x%04x,0x%04x)\n", +		dev->mem, dev->revision, pci->irq, +		pci->subsystem_vendor, pci->subsystem_device); +	dev->ext = ext; + +	mutex_init(&dev->v4l2_lock); +	spin_lock_init(&dev->int_slock); +	spin_lock_init(&dev->slock); + +	mutex_init(&dev->i2c_lock); + +	dev->module = THIS_MODULE; +	init_waitqueue_head(&dev->i2c_wq); + +	/* set some sane pci arbitrition values */ +	saa7146_write(dev, PCI_BT_V1, 0x1c00101f); + +	/* TODO: use the status code of the callback */ + +	err = -ENODEV; + +	if (ext->probe && ext->probe(dev)) { +		DEB_D("ext->probe() failed for %p. skipping device.\n", dev); +		goto err_free_i2c; +	} + +	if (ext->attach(dev, pci_ext)) { +		DEB_D("ext->attach() failed for %p. skipping device.\n", dev); +		goto err_free_i2c; +	} +	/* V4L extensions will set the pci drvdata to the v4l2_device in the +	   attach() above. So for those cards that do not use V4L we have to +	   set it explicitly. */ +	pci_set_drvdata(pci, &dev->v4l2_dev); + +	saa7146_num++; + +	err = 0; +out: +	return err; + +err_free_i2c: +	pci_free_consistent(pci, SAA7146_RPS_MEM, dev->d_i2c.cpu_addr, +			    dev->d_i2c.dma_handle); +err_free_rps1: +	pci_free_consistent(pci, SAA7146_RPS_MEM, dev->d_rps1.cpu_addr, +			    dev->d_rps1.dma_handle); +err_free_rps0: +	pci_free_consistent(pci, SAA7146_RPS_MEM, dev->d_rps0.cpu_addr, +			    dev->d_rps0.dma_handle); +err_free_irq: +	free_irq(pci->irq, (void *)dev); +err_unmap: +	iounmap(dev->mem); +err_release: +	pci_release_region(pci, 0); +err_disable: +	pci_disable_device(pci); +err_free: +	kfree(dev); +	goto out; +} + +static void saa7146_remove_one(struct pci_dev *pdev) +{ +	struct v4l2_device *v4l2_dev = pci_get_drvdata(pdev); +	struct saa7146_dev *dev = to_saa7146_dev(v4l2_dev); +	struct { +		void *addr; +		dma_addr_t dma; +	} dev_map[] = { +		{ dev->d_i2c.cpu_addr, dev->d_i2c.dma_handle }, +		{ dev->d_rps1.cpu_addr, dev->d_rps1.dma_handle }, +		{ dev->d_rps0.cpu_addr, dev->d_rps0.dma_handle }, +		{ NULL, 0 } +	}, *p; + +	DEB_EE("dev:%p\n", dev); + +	dev->ext->detach(dev); + +	/* shut down all video dma transfers */ +	saa7146_write(dev, MC1, 0x00ff0000); + +	/* disable all irqs, release irq-routine */ +	saa7146_write(dev, IER, 0); + +	free_irq(pdev->irq, dev); + +	for (p = dev_map; p->addr; p++) +		pci_free_consistent(pdev, SAA7146_RPS_MEM, p->addr, p->dma); + +	iounmap(dev->mem); +	pci_release_region(pdev, 0); +	pci_disable_device(pdev); +	kfree(dev); + +	saa7146_num--; +} + +/*********************************************************************************/ +/* extension handling functions                                                  */ + +int saa7146_register_extension(struct saa7146_extension* ext) +{ +	DEB_EE("ext:%p\n", ext); + +	ext->driver.name = ext->name; +	ext->driver.id_table = ext->pci_tbl; +	ext->driver.probe = saa7146_init_one; +	ext->driver.remove = saa7146_remove_one; + +	pr_info("register extension '%s'\n", ext->name); +	return pci_register_driver(&ext->driver); +} + +int saa7146_unregister_extension(struct saa7146_extension* ext) +{ +	DEB_EE("ext:%p\n", ext); +	pr_info("unregister extension '%s'\n", ext->name); +	pci_unregister_driver(&ext->driver); +	return 0; +} + +EXPORT_SYMBOL_GPL(saa7146_register_extension); +EXPORT_SYMBOL_GPL(saa7146_unregister_extension); + +/* misc functions used by extension modules */ +EXPORT_SYMBOL_GPL(saa7146_pgtable_alloc); +EXPORT_SYMBOL_GPL(saa7146_pgtable_free); +EXPORT_SYMBOL_GPL(saa7146_pgtable_build_single); +EXPORT_SYMBOL_GPL(saa7146_vmalloc_build_pgtable); +EXPORT_SYMBOL_GPL(saa7146_vfree_destroy_pgtable); +EXPORT_SYMBOL_GPL(saa7146_wait_for_debi_done); + +EXPORT_SYMBOL_GPL(saa7146_setgpio); + +EXPORT_SYMBOL_GPL(saa7146_i2c_adapter_prepare); + +EXPORT_SYMBOL_GPL(saa7146_debug); + +MODULE_AUTHOR("Michael Hunold <michael@mihu.de>"); +MODULE_DESCRIPTION("driver for generic saa7146-based hardware"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/common/saa7146/saa7146_fops.c b/drivers/media/common/saa7146/saa7146_fops.c new file mode 100644 index 00000000000..eda01bc68ab --- /dev/null +++ b/drivers/media/common/saa7146/saa7146_fops.c @@ -0,0 +1,663 @@ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <media/saa7146_vv.h> +#include <linux/module.h> + +/****************************************************************************/ +/* resource management functions, shamelessly stolen from saa7134 driver */ + +int saa7146_res_get(struct saa7146_fh *fh, unsigned int bit) +{ +	struct saa7146_dev *dev = fh->dev; +	struct saa7146_vv *vv = dev->vv_data; + +	if (fh->resources & bit) { +		DEB_D("already allocated! want: 0x%02x, cur:0x%02x\n", +		      bit, vv->resources); +		/* have it already allocated */ +		return 1; +	} + +	/* is it free? */ +	if (vv->resources & bit) { +		DEB_D("locked! vv->resources:0x%02x, we want:0x%02x\n", +		      vv->resources, bit); +		/* no, someone else uses it */ +		return 0; +	} +	/* it's free, grab it */ +	fh->resources |= bit; +	vv->resources |= bit; +	DEB_D("res: get 0x%02x, cur:0x%02x\n", bit, vv->resources); +	return 1; +} + +void saa7146_res_free(struct saa7146_fh *fh, unsigned int bits) +{ +	struct saa7146_dev *dev = fh->dev; +	struct saa7146_vv *vv = dev->vv_data; + +	BUG_ON((fh->resources & bits) != bits); + +	fh->resources &= ~bits; +	vv->resources &= ~bits; +	DEB_D("res: put 0x%02x, cur:0x%02x\n", bits, vv->resources); +} + + +/********************************************************************************/ +/* common dma functions */ + +void saa7146_dma_free(struct saa7146_dev *dev,struct videobuf_queue *q, +						struct saa7146_buf *buf) +{ +	struct videobuf_dmabuf *dma=videobuf_to_dma(&buf->vb); +	DEB_EE("dev:%p, buf:%p\n", dev, buf); + +	BUG_ON(in_interrupt()); + +	videobuf_waiton(q, &buf->vb, 0, 0); +	videobuf_dma_unmap(q->dev, dma); +	videobuf_dma_free(dma); +	buf->vb.state = VIDEOBUF_NEEDS_INIT; +} + + +/********************************************************************************/ +/* common buffer functions */ + +int saa7146_buffer_queue(struct saa7146_dev *dev, +			 struct saa7146_dmaqueue *q, +			 struct saa7146_buf *buf) +{ +	assert_spin_locked(&dev->slock); +	DEB_EE("dev:%p, dmaq:%p, buf:%p\n", dev, q, buf); + +	BUG_ON(!q); + +	if (NULL == q->curr) { +		q->curr = buf; +		DEB_D("immediately activating buffer %p\n", buf); +		buf->activate(dev,buf,NULL); +	} else { +		list_add_tail(&buf->vb.queue,&q->queue); +		buf->vb.state = VIDEOBUF_QUEUED; +		DEB_D("adding buffer %p to queue. (active buffer present)\n", +		      buf); +	} +	return 0; +} + +void saa7146_buffer_finish(struct saa7146_dev *dev, +			   struct saa7146_dmaqueue *q, +			   int state) +{ +	assert_spin_locked(&dev->slock); +	DEB_EE("dev:%p, dmaq:%p, state:%d\n", dev, q, state); +	DEB_EE("q->curr:%p\n", q->curr); + +	BUG_ON(!q->curr); + +	/* finish current buffer */ +	if (NULL == q->curr) { +		DEB_D("aiii. no current buffer\n"); +		return; +	} + +	q->curr->vb.state = state; +	v4l2_get_timestamp(&q->curr->vb.ts); +	wake_up(&q->curr->vb.done); + +	q->curr = NULL; +} + +void saa7146_buffer_next(struct saa7146_dev *dev, +			 struct saa7146_dmaqueue *q, int vbi) +{ +	struct saa7146_buf *buf,*next = NULL; + +	BUG_ON(!q); + +	DEB_INT("dev:%p, dmaq:%p, vbi:%d\n", dev, q, vbi); + +	assert_spin_locked(&dev->slock); +	if (!list_empty(&q->queue)) { +		/* activate next one from queue */ +		buf = list_entry(q->queue.next,struct saa7146_buf,vb.queue); +		list_del(&buf->vb.queue); +		if (!list_empty(&q->queue)) +			next = list_entry(q->queue.next,struct saa7146_buf, vb.queue); +		q->curr = buf; +		DEB_INT("next buffer: buf:%p, prev:%p, next:%p\n", +			buf, q->queue.prev, q->queue.next); +		buf->activate(dev,buf,next); +	} else { +		DEB_INT("no next buffer. stopping.\n"); +		if( 0 != vbi ) { +			/* turn off video-dma3 */ +			saa7146_write(dev,MC1, MASK_20); +		} else { +			/* nothing to do -- just prevent next video-dma1 transfer +			   by lowering the protection address */ + +			// fixme: fix this for vflip != 0 + +			saa7146_write(dev, PROT_ADDR1, 0); +			saa7146_write(dev, MC2, (MASK_02|MASK_18)); + +			/* write the address of the rps-program */ +			saa7146_write(dev, RPS_ADDR0, dev->d_rps0.dma_handle); +			/* turn on rps */ +			saa7146_write(dev, MC1, (MASK_12 | MASK_28)); + +/* +			printk("vdma%d.base_even:     0x%08x\n", 1,saa7146_read(dev,BASE_EVEN1)); +			printk("vdma%d.base_odd:      0x%08x\n", 1,saa7146_read(dev,BASE_ODD1)); +			printk("vdma%d.prot_addr:     0x%08x\n", 1,saa7146_read(dev,PROT_ADDR1)); +			printk("vdma%d.base_page:     0x%08x\n", 1,saa7146_read(dev,BASE_PAGE1)); +			printk("vdma%d.pitch:         0x%08x\n", 1,saa7146_read(dev,PITCH1)); +			printk("vdma%d.num_line_byte: 0x%08x\n", 1,saa7146_read(dev,NUM_LINE_BYTE1)); +*/ +		} +		del_timer(&q->timeout); +	} +} + +void saa7146_buffer_timeout(unsigned long data) +{ +	struct saa7146_dmaqueue *q = (struct saa7146_dmaqueue*)data; +	struct saa7146_dev *dev = q->dev; +	unsigned long flags; + +	DEB_EE("dev:%p, dmaq:%p\n", dev, q); + +	spin_lock_irqsave(&dev->slock,flags); +	if (q->curr) { +		DEB_D("timeout on %p\n", q->curr); +		saa7146_buffer_finish(dev,q,VIDEOBUF_ERROR); +	} + +	/* we don't restart the transfer here like other drivers do. when +	   a streaming capture is disabled, the timeout function will be +	   called for the current buffer. if we activate the next buffer now, +	   we mess up our capture logic. if a timeout occurs on another buffer, +	   then something is seriously broken before, so no need to buffer the +	   next capture IMHO... */ +/* +	saa7146_buffer_next(dev,q); +*/ +	spin_unlock_irqrestore(&dev->slock,flags); +} + +/********************************************************************************/ +/* file operations */ + +static int fops_open(struct file *file) +{ +	struct video_device *vdev = video_devdata(file); +	struct saa7146_dev *dev = video_drvdata(file); +	struct saa7146_fh *fh = NULL; +	int result = 0; + +	DEB_EE("file:%p, dev:%s\n", file, video_device_node_name(vdev)); + +	if (mutex_lock_interruptible(vdev->lock)) +		return -ERESTARTSYS; + +	DEB_D("using: %p\n", dev); + +	/* check if an extension is registered */ +	if( NULL == dev->ext ) { +		DEB_S("no extension registered for this device\n"); +		result = -ENODEV; +		goto out; +	} + +	/* allocate per open data */ +	fh = kzalloc(sizeof(*fh),GFP_KERNEL); +	if (NULL == fh) { +		DEB_S("cannot allocate memory for per open data\n"); +		result = -ENOMEM; +		goto out; +	} + +	v4l2_fh_init(&fh->fh, vdev); + +	file->private_data = &fh->fh; +	fh->dev = dev; + +	if (vdev->vfl_type == VFL_TYPE_VBI) { +		DEB_S("initializing vbi...\n"); +		if (dev->ext_vv_data->capabilities & V4L2_CAP_VBI_CAPTURE) +			result = saa7146_vbi_uops.open(dev,file); +		if (dev->ext_vv_data->vbi_fops.open) +			dev->ext_vv_data->vbi_fops.open(file); +	} else { +		DEB_S("initializing video...\n"); +		result = saa7146_video_uops.open(dev,file); +	} + +	if (0 != result) { +		goto out; +	} + +	if( 0 == try_module_get(dev->ext->module)) { +		result = -EINVAL; +		goto out; +	} + +	result = 0; +	v4l2_fh_add(&fh->fh); +out: +	if (fh && result != 0) { +		kfree(fh); +		file->private_data = NULL; +	} +	mutex_unlock(vdev->lock); +	return result; +} + +static int fops_release(struct file *file) +{ +	struct video_device *vdev = video_devdata(file); +	struct saa7146_fh  *fh  = file->private_data; +	struct saa7146_dev *dev = fh->dev; + +	DEB_EE("file:%p\n", file); + +	mutex_lock(vdev->lock); + +	if (vdev->vfl_type == VFL_TYPE_VBI) { +		if (dev->ext_vv_data->capabilities & V4L2_CAP_VBI_CAPTURE) +			saa7146_vbi_uops.release(dev,file); +		if (dev->ext_vv_data->vbi_fops.release) +			dev->ext_vv_data->vbi_fops.release(file); +	} else { +		saa7146_video_uops.release(dev,file); +	} + +	v4l2_fh_del(&fh->fh); +	v4l2_fh_exit(&fh->fh); +	module_put(dev->ext->module); +	file->private_data = NULL; +	kfree(fh); + +	mutex_unlock(vdev->lock); + +	return 0; +} + +static int fops_mmap(struct file *file, struct vm_area_struct * vma) +{ +	struct video_device *vdev = video_devdata(file); +	struct saa7146_fh *fh = file->private_data; +	struct videobuf_queue *q; +	int res; + +	switch (vdev->vfl_type) { +	case VFL_TYPE_GRABBER: { +		DEB_EE("V4L2_BUF_TYPE_VIDEO_CAPTURE: file:%p, vma:%p\n", +		       file, vma); +		q = &fh->video_q; +		break; +		} +	case VFL_TYPE_VBI: { +		DEB_EE("V4L2_BUF_TYPE_VBI_CAPTURE: file:%p, vma:%p\n", +		       file, vma); +		if (fh->dev->ext_vv_data->capabilities & V4L2_CAP_SLICED_VBI_OUTPUT) +			return -ENODEV; +		q = &fh->vbi_q; +		break; +		} +	default: +		BUG(); +		return 0; +	} + +	if (mutex_lock_interruptible(vdev->lock)) +		return -ERESTARTSYS; +	res = videobuf_mmap_mapper(q, vma); +	mutex_unlock(vdev->lock); +	return res; +} + +static unsigned int __fops_poll(struct file *file, struct poll_table_struct *wait) +{ +	struct video_device *vdev = video_devdata(file); +	struct saa7146_fh *fh = file->private_data; +	struct videobuf_buffer *buf = NULL; +	struct videobuf_queue *q; +	unsigned int res = v4l2_ctrl_poll(file, wait); + +	DEB_EE("file:%p, poll:%p\n", file, wait); + +	if (vdev->vfl_type == VFL_TYPE_VBI) { +		if (fh->dev->ext_vv_data->capabilities & V4L2_CAP_SLICED_VBI_OUTPUT) +			return res | POLLOUT | POLLWRNORM; +		if( 0 == fh->vbi_q.streaming ) +			return res | videobuf_poll_stream(file, &fh->vbi_q, wait); +		q = &fh->vbi_q; +	} else { +		DEB_D("using video queue\n"); +		q = &fh->video_q; +	} + +	if (!list_empty(&q->stream)) +		buf = list_entry(q->stream.next, struct videobuf_buffer, stream); + +	if (!buf) { +		DEB_D("buf == NULL!\n"); +		return res | POLLERR; +	} + +	poll_wait(file, &buf->done, wait); +	if (buf->state == VIDEOBUF_DONE || buf->state == VIDEOBUF_ERROR) { +		DEB_D("poll succeeded!\n"); +		return res | POLLIN | POLLRDNORM; +	} + +	DEB_D("nothing to poll for, buf->state:%d\n", buf->state); +	return res; +} + +static unsigned int fops_poll(struct file *file, struct poll_table_struct *wait) +{ +	struct video_device *vdev = video_devdata(file); +	unsigned int res; + +	mutex_lock(vdev->lock); +	res = __fops_poll(file, wait); +	mutex_unlock(vdev->lock); +	return res; +} + +static ssize_t fops_read(struct file *file, char __user *data, size_t count, loff_t *ppos) +{ +	struct video_device *vdev = video_devdata(file); +	struct saa7146_fh *fh = file->private_data; +	int ret; + +	switch (vdev->vfl_type) { +	case VFL_TYPE_GRABBER: +/* +		DEB_EE("V4L2_BUF_TYPE_VIDEO_CAPTURE: file:%p, data:%p, count:%lun", +		       file, data, (unsigned long)count); +*/ +		return saa7146_video_uops.read(file,data,count,ppos); +	case VFL_TYPE_VBI: +/* +		DEB_EE("V4L2_BUF_TYPE_VBI_CAPTURE: file:%p, data:%p, count:%lu\n", +		       file, data, (unsigned long)count); +*/ +		if (fh->dev->ext_vv_data->capabilities & V4L2_CAP_VBI_CAPTURE) { +			if (mutex_lock_interruptible(vdev->lock)) +				return -ERESTARTSYS; +			ret = saa7146_vbi_uops.read(file, data, count, ppos); +			mutex_unlock(vdev->lock); +			return ret; +		} +		return -EINVAL; +	default: +		BUG(); +		return 0; +	} +} + +static ssize_t fops_write(struct file *file, const char __user *data, size_t count, loff_t *ppos) +{ +	struct video_device *vdev = video_devdata(file); +	struct saa7146_fh *fh = file->private_data; +	int ret; + +	switch (vdev->vfl_type) { +	case VFL_TYPE_GRABBER: +		return -EINVAL; +	case VFL_TYPE_VBI: +		if (fh->dev->ext_vv_data->vbi_fops.write) { +			if (mutex_lock_interruptible(vdev->lock)) +				return -ERESTARTSYS; +			ret = fh->dev->ext_vv_data->vbi_fops.write(file, data, count, ppos); +			mutex_unlock(vdev->lock); +			return ret; +		} +		return -EINVAL; +	default: +		BUG(); +		return -EINVAL; +	} +} + +static const struct v4l2_file_operations video_fops = +{ +	.owner		= THIS_MODULE, +	.open		= fops_open, +	.release	= fops_release, +	.read		= fops_read, +	.write		= fops_write, +	.poll		= fops_poll, +	.mmap		= fops_mmap, +	.unlocked_ioctl	= video_ioctl2, +}; + +static void vv_callback(struct saa7146_dev *dev, unsigned long status) +{ +	u32 isr = status; + +	DEB_INT("dev:%p, isr:0x%08x\n", dev, (u32)status); + +	if (0 != (isr & (MASK_27))) { +		DEB_INT("irq: RPS0 (0x%08x)\n", isr); +		saa7146_video_uops.irq_done(dev,isr); +	} + +	if (0 != (isr & (MASK_28))) { +		u32 mc2 = saa7146_read(dev, MC2); +		if( 0 != (mc2 & MASK_15)) { +			DEB_INT("irq: RPS1 vbi workaround (0x%08x)\n", isr); +			wake_up(&dev->vv_data->vbi_wq); +			saa7146_write(dev,MC2, MASK_31); +			return; +		} +		DEB_INT("irq: RPS1 (0x%08x)\n", isr); +		saa7146_vbi_uops.irq_done(dev,isr); +	} +} + +static const struct v4l2_ctrl_ops saa7146_ctrl_ops = { +	.s_ctrl = saa7146_s_ctrl, +}; + +int saa7146_vv_init(struct saa7146_dev* dev, struct saa7146_ext_vv *ext_vv) +{ +	struct v4l2_ctrl_handler *hdl = &dev->ctrl_handler; +	struct v4l2_pix_format *fmt; +	struct v4l2_vbi_format *vbi; +	struct saa7146_vv *vv; +	int err; + +	err = v4l2_device_register(&dev->pci->dev, &dev->v4l2_dev); +	if (err) +		return err; + +	v4l2_ctrl_handler_init(hdl, 6); +	v4l2_ctrl_new_std(hdl, &saa7146_ctrl_ops, +		V4L2_CID_BRIGHTNESS, 0, 255, 1, 128); +	v4l2_ctrl_new_std(hdl, &saa7146_ctrl_ops, +		V4L2_CID_CONTRAST, 0, 127, 1, 64); +	v4l2_ctrl_new_std(hdl, &saa7146_ctrl_ops, +		V4L2_CID_SATURATION, 0, 127, 1, 64); +	v4l2_ctrl_new_std(hdl, &saa7146_ctrl_ops, +		V4L2_CID_VFLIP, 0, 1, 1, 0); +	v4l2_ctrl_new_std(hdl, &saa7146_ctrl_ops, +		V4L2_CID_HFLIP, 0, 1, 1, 0); +	if (hdl->error) { +		err = hdl->error; +		v4l2_ctrl_handler_free(hdl); +		return err; +	} +	dev->v4l2_dev.ctrl_handler = hdl; + +	vv = kzalloc(sizeof(struct saa7146_vv), GFP_KERNEL); +	if (vv == NULL) { +		ERR("out of memory. aborting.\n"); +		v4l2_ctrl_handler_free(hdl); +		return -ENOMEM; +	} +	ext_vv->vid_ops = saa7146_video_ioctl_ops; +	ext_vv->vbi_ops = saa7146_vbi_ioctl_ops; +	ext_vv->core_ops = &saa7146_video_ioctl_ops; + +	DEB_EE("dev:%p\n", dev); + +	/* set default values for video parts of the saa7146 */ +	saa7146_write(dev, BCS_CTRL, 0x80400040); + +	/* enable video-port pins */ +	saa7146_write(dev, MC1, (MASK_10 | MASK_26)); + +	/* save per-device extension data (one extension can +	   handle different devices that might need different +	   configuration data) */ +	dev->ext_vv_data = ext_vv; + +	vv->d_clipping.cpu_addr = pci_alloc_consistent(dev->pci, SAA7146_CLIPPING_MEM, &vv->d_clipping.dma_handle); +	if( NULL == vv->d_clipping.cpu_addr ) { +		ERR("out of memory. aborting.\n"); +		kfree(vv); +		v4l2_ctrl_handler_free(hdl); +		return -1; +	} +	memset(vv->d_clipping.cpu_addr, 0x0, SAA7146_CLIPPING_MEM); + +	saa7146_video_uops.init(dev,vv); +	if (dev->ext_vv_data->capabilities & V4L2_CAP_VBI_CAPTURE) +		saa7146_vbi_uops.init(dev,vv); + +	fmt = &vv->ov_fb.fmt; +	fmt->width = vv->standard->h_max_out; +	fmt->height = vv->standard->v_max_out; +	fmt->pixelformat = V4L2_PIX_FMT_RGB565; +	fmt->bytesperline = 2 * fmt->width; +	fmt->sizeimage = fmt->bytesperline * fmt->height; +	fmt->colorspace = V4L2_COLORSPACE_SRGB; + +	fmt = &vv->video_fmt; +	fmt->width = 384; +	fmt->height = 288; +	fmt->pixelformat = V4L2_PIX_FMT_BGR24; +	fmt->field = V4L2_FIELD_ANY; +	fmt->colorspace = V4L2_COLORSPACE_SMPTE170M; +	fmt->bytesperline = 3 * fmt->width; +	fmt->sizeimage = fmt->bytesperline * fmt->height; + +	vbi = &vv->vbi_fmt; +	vbi->sampling_rate	= 27000000; +	vbi->offset		= 248; /* todo */ +	vbi->samples_per_line	= 720 * 2; +	vbi->sample_format	= V4L2_PIX_FMT_GREY; + +	/* fixme: this only works for PAL */ +	vbi->start[0] = 5; +	vbi->count[0] = 16; +	vbi->start[1] = 312; +	vbi->count[1] = 16; + +	init_timer(&vv->vbi_read_timeout); + +	vv->ov_fb.capability = V4L2_FBUF_CAP_LIST_CLIPPING; +	vv->ov_fb.flags = V4L2_FBUF_FLAG_PRIMARY; +	dev->vv_data = vv; +	dev->vv_callback = &vv_callback; + +	return 0; +} +EXPORT_SYMBOL_GPL(saa7146_vv_init); + +int saa7146_vv_release(struct saa7146_dev* dev) +{ +	struct saa7146_vv *vv = dev->vv_data; + +	DEB_EE("dev:%p\n", dev); + +	v4l2_device_unregister(&dev->v4l2_dev); +	pci_free_consistent(dev->pci, SAA7146_CLIPPING_MEM, vv->d_clipping.cpu_addr, vv->d_clipping.dma_handle); +	v4l2_ctrl_handler_free(&dev->ctrl_handler); +	kfree(vv); +	dev->vv_data = NULL; +	dev->vv_callback = NULL; + +	return 0; +} +EXPORT_SYMBOL_GPL(saa7146_vv_release); + +int saa7146_register_device(struct video_device **vid, struct saa7146_dev* dev, +			    char *name, int type) +{ +	struct video_device *vfd; +	int err; +	int i; + +	DEB_EE("dev:%p, name:'%s', type:%d\n", dev, name, type); + +	// released by vfd->release +	vfd = video_device_alloc(); +	if (vfd == NULL) +		return -ENOMEM; + +	vfd->fops = &video_fops; +	if (type == VFL_TYPE_GRABBER) +		vfd->ioctl_ops = &dev->ext_vv_data->vid_ops; +	else +		vfd->ioctl_ops = &dev->ext_vv_data->vbi_ops; +	vfd->release = video_device_release; +	vfd->lock = &dev->v4l2_lock; +	vfd->v4l2_dev = &dev->v4l2_dev; +	vfd->tvnorms = 0; +	set_bit(V4L2_FL_USE_FH_PRIO, &vfd->flags); +	for (i = 0; i < dev->ext_vv_data->num_stds; i++) +		vfd->tvnorms |= dev->ext_vv_data->stds[i].id; +	strlcpy(vfd->name, name, sizeof(vfd->name)); +	video_set_drvdata(vfd, dev); + +	err = video_register_device(vfd, type, -1); +	if (err < 0) { +		ERR("cannot register v4l2 device. skipping.\n"); +		video_device_release(vfd); +		return err; +	} + +	pr_info("%s: registered device %s [v4l2]\n", +		dev->name, video_device_node_name(vfd)); + +	*vid = vfd; +	return 0; +} +EXPORT_SYMBOL_GPL(saa7146_register_device); + +int saa7146_unregister_device(struct video_device **vid, struct saa7146_dev* dev) +{ +	DEB_EE("dev:%p\n", dev); + +	video_unregister_device(*vid); +	*vid = NULL; + +	return 0; +} +EXPORT_SYMBOL_GPL(saa7146_unregister_device); + +static int __init saa7146_vv_init_module(void) +{ +	return 0; +} + + +static void __exit saa7146_vv_cleanup_module(void) +{ +} + +module_init(saa7146_vv_init_module); +module_exit(saa7146_vv_cleanup_module); + +MODULE_AUTHOR("Michael Hunold <michael@mihu.de>"); +MODULE_DESCRIPTION("video4linux driver for saa7146-based hardware"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/common/saa7146/saa7146_hlp.c b/drivers/media/common/saa7146/saa7146_hlp.c new file mode 100644 index 00000000000..be746d1aee9 --- /dev/null +++ b/drivers/media/common/saa7146/saa7146_hlp.c @@ -0,0 +1,1048 @@ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/kernel.h> +#include <linux/export.h> +#include <media/saa7146_vv.h> + +static void calculate_output_format_register(struct saa7146_dev* saa, u32 palette, u32* clip_format) +{ +	/* clear out the necessary bits */ +	*clip_format &= 0x0000ffff; +	/* set these bits new */ +	*clip_format |=  (( ((palette&0xf00)>>8) << 30) | ((palette&0x00f) << 24) | (((palette&0x0f0)>>4) << 16)); +} + +static void calculate_hps_source_and_sync(struct saa7146_dev *dev, int source, int sync, u32* hps_ctrl) +{ +	*hps_ctrl &= ~(MASK_30 | MASK_31 | MASK_28); +	*hps_ctrl |= (source << 30) | (sync << 28); +} + +static void calculate_hxo_and_hyo(struct saa7146_vv *vv, u32* hps_h_scale, u32* hps_ctrl) +{ +	int hyo = 0, hxo = 0; + +	hyo = vv->standard->v_offset; +	hxo = vv->standard->h_offset; + +	*hps_h_scale	&= ~(MASK_B0 | 0xf00); +	*hps_h_scale	|= (hxo <<  0); + +	*hps_ctrl	&= ~(MASK_W0 | MASK_B2); +	*hps_ctrl	|= (hyo << 12); +} + +/* helper functions for the calculation of the horizontal- and vertical +   scaling registers, clip-format-register etc ... +   these functions take pointers to the (most-likely read-out +   original-values) and manipulate them according to the requested +   changes. +*/ + +/* hps_coeff used for CXY and CXUV; scale 1/1 -> scale 1/64 */ +static struct { +	u16 hps_coeff; +	u16 weight_sum; +} hps_h_coeff_tab [] = { +	{0x00,   2}, {0x02,   4}, {0x00,   4}, {0x06,   8}, {0x02,   8}, +	{0x08,   8}, {0x00,   8}, {0x1E,  16}, {0x0E,   8}, {0x26,   8}, +	{0x06,   8}, {0x42,   8}, {0x02,   8}, {0x80,   8}, {0x00,   8}, +	{0xFE,  16}, {0xFE,   8}, {0x7E,   8}, {0x7E,   8}, {0x3E,   8}, +	{0x3E,   8}, {0x1E,   8}, {0x1E,   8}, {0x0E,   8}, {0x0E,   8}, +	{0x06,   8}, {0x06,   8}, {0x02,   8}, {0x02,   8}, {0x00,   8}, +	{0x00,   8}, {0xFE,  16}, {0xFE,   8}, {0xFE,   8}, {0xFE,   8}, +	{0xFE,   8}, {0xFE,   8}, {0xFE,   8}, {0xFE,   8}, {0xFE,   8}, +	{0xFE,   8}, {0xFE,   8}, {0xFE,   8}, {0xFE,   8}, {0xFE,   8}, +	{0xFE,   8}, {0xFE,   8}, {0xFE,   8}, {0xFE,   8}, {0x7E,   8}, +	{0x7E,   8}, {0x3E,   8}, {0x3E,   8}, {0x1E,   8}, {0x1E,   8}, +	{0x0E,   8}, {0x0E,   8}, {0x06,   8}, {0x06,   8}, {0x02,   8}, +	{0x02,   8}, {0x00,   8}, {0x00,   8}, {0xFE,  16} +}; + +/* table of attenuation values for horizontal scaling */ +static u8 h_attenuation[] = { 1, 2, 4, 8, 2, 4, 8, 16, 0}; + +/* calculate horizontal scale registers */ +static int calculate_h_scale_registers(struct saa7146_dev *dev, +	int in_x, int out_x, int flip_lr, +	u32* hps_ctrl, u32* hps_v_gain, u32* hps_h_prescale, u32* hps_h_scale) +{ +	/* horizontal prescaler */ +	u32 dcgx = 0, xpsc = 0, xacm = 0, cxy = 0, cxuv = 0; +	/* horizontal scaler */ +	u32 xim = 0, xp = 0, xsci =0; +	/* vertical scale & gain */ +	u32 pfuv = 0; + +	/* helper variables */ +	u32 h_atten = 0, i = 0; + +	if ( 0 == out_x ) { +		return -EINVAL; +	} + +	/* mask out vanity-bit */ +	*hps_ctrl &= ~MASK_29; + +	/* calculate prescale-(xspc)-value:	[n   .. 1/2) : 1 +						[1/2 .. 1/3) : 2 +						[1/3 .. 1/4) : 3 +						...		*/ +	if (in_x > out_x) { +		xpsc = in_x / out_x; +	} +	else { +		/* zooming */ +		xpsc = 1; +	} + +	/* if flip_lr-bit is set, number of pixels after +	   horizontal prescaling must be < 384 */ +	if ( 0 != flip_lr ) { + +		/* set vanity bit */ +		*hps_ctrl |= MASK_29; + +		while (in_x / xpsc >= 384 ) +			xpsc++; +	} +	/* if zooming is wanted, number of pixels after +	   horizontal prescaling must be < 768 */ +	else { +		while ( in_x / xpsc >= 768 ) +			xpsc++; +	} + +	/* maximum prescale is 64 (p.69) */ +	if ( xpsc > 64 ) +		xpsc = 64; + +	/* keep xacm clear*/ +	xacm = 0; + +	/* set horizontal filter parameters (CXY = CXUV) */ +	cxy = hps_h_coeff_tab[( (xpsc - 1) < 63 ? (xpsc - 1) : 63 )].hps_coeff; +	cxuv = cxy; + +	/* calculate and set horizontal fine scale (xsci) */ + +	/* bypass the horizontal scaler ? */ +	if ( (in_x == out_x) && ( 1 == xpsc ) ) +		xsci = 0x400; +	else +		xsci = ( (1024 * in_x) / (out_x * xpsc) ) + xpsc; + +	/* set start phase for horizontal fine scale (xp) to 0 */ +	xp = 0; + +	/* set xim, if we bypass the horizontal scaler */ +	if ( 0x400 == xsci ) +		xim = 1; +	else +		xim = 0; + +	/* if the prescaler is bypassed, enable horizontal +	   accumulation mode (xacm) and clear dcgx */ +	if( 1 == xpsc ) { +		xacm = 1; +		dcgx = 0; +	} else { +		xacm = 0; +		/* get best match in the table of attenuations +		   for horizontal scaling */ +		h_atten = hps_h_coeff_tab[( (xpsc - 1) < 63 ? (xpsc - 1) : 63 )].weight_sum; + +		for (i = 0; h_attenuation[i] != 0; i++) { +			if (h_attenuation[i] >= h_atten) +				break; +		} + +		dcgx = i; +	} + +	/* the horizontal scaling increment controls the UV filter +	   to reduce the bandwidth to improve the display quality, +	   so set it ... */ +	if ( xsci == 0x400) +		pfuv = 0x00; +	else if ( xsci < 0x600) +		pfuv = 0x01; +	else if ( xsci < 0x680) +		pfuv = 0x11; +	else if ( xsci < 0x700) +		pfuv = 0x22; +	else +		pfuv = 0x33; + + +	*hps_v_gain  &= MASK_W0|MASK_B2; +	*hps_v_gain  |= (pfuv << 24); + +	*hps_h_scale	&= ~(MASK_W1 | 0xf000); +	*hps_h_scale	|= (xim << 31) | (xp << 24) | (xsci << 12); + +	*hps_h_prescale	|= (dcgx << 27) | ((xpsc-1) << 18) | (xacm << 17) | (cxy << 8) | (cxuv << 0); + +	return 0; +} + +static struct { +	u16 hps_coeff; +	u16 weight_sum; +} hps_v_coeff_tab [] = { + {0x0100,   2},  {0x0102,   4},  {0x0300,   4},  {0x0106,   8},  {0x0502,   8}, + {0x0708,   8},  {0x0F00,   8},  {0x011E,  16},  {0x110E,  16},  {0x1926,  16}, + {0x3906,  16},  {0x3D42,  16},  {0x7D02,  16},  {0x7F80,  16},  {0xFF00,  16}, + {0x01FE,  32},  {0x01FE,  32},  {0x817E,  32},  {0x817E,  32},  {0xC13E,  32}, + {0xC13E,  32},  {0xE11E,  32},  {0xE11E,  32},  {0xF10E,  32},  {0xF10E,  32}, + {0xF906,  32},  {0xF906,  32},  {0xFD02,  32},  {0xFD02,  32},  {0xFF00,  32}, + {0xFF00,  32},  {0x01FE,  64},  {0x01FE,  64},  {0x01FE,  64},  {0x01FE,  64}, + {0x01FE,  64},  {0x01FE,  64},  {0x01FE,  64},  {0x01FE,  64},  {0x01FE,  64}, + {0x01FE,  64},  {0x01FE,  64},  {0x01FE,  64},  {0x01FE,  64},  {0x01FE,  64}, + {0x01FE,  64},  {0x01FE,  64},  {0x01FE,  64},  {0x01FE,  64},  {0x817E,  64}, + {0x817E,  64},  {0xC13E,  64},  {0xC13E,  64},  {0xE11E,  64},  {0xE11E,  64}, + {0xF10E,  64},  {0xF10E,  64},  {0xF906,  64},  {0xF906,  64},  {0xFD02,  64}, + {0xFD02,  64},  {0xFF00,  64},  {0xFF00,  64},  {0x01FE, 128} +}; + +/* table of attenuation values for vertical scaling */ +static u16 v_attenuation[] = { 2, 4, 8, 16, 32, 64, 128, 256, 0}; + +/* calculate vertical scale registers */ +static int calculate_v_scale_registers(struct saa7146_dev *dev, enum v4l2_field field, +	int in_y, int out_y, u32* hps_v_scale, u32* hps_v_gain) +{ +	int lpi = 0; + +	/* vertical scaling */ +	u32 yacm = 0, ysci = 0, yacl = 0, ypo = 0, ype = 0; +	/* vertical scale & gain */ +	u32 dcgy = 0, cya_cyb = 0; + +	/* helper variables */ +	u32 v_atten = 0, i = 0; + +	/* error, if vertical zooming */ +	if ( in_y < out_y ) { +		return -EINVAL; +	} + +	/* linear phase interpolation may be used +	   if scaling is between 1 and 1/2 (both fields used) +	   or scaling is between 1/2 and 1/4 (if only one field is used) */ + +	if (V4L2_FIELD_HAS_BOTH(field)) { +		if( 2*out_y >= in_y) { +			lpi = 1; +		} +	} else if (field == V4L2_FIELD_TOP +		|| field == V4L2_FIELD_ALTERNATE +		|| field == V4L2_FIELD_BOTTOM) { +		if( 4*out_y >= in_y ) { +			lpi = 1; +		} +		out_y *= 2; +	} +	if( 0 != lpi ) { + +		yacm = 0; +		yacl = 0; +		cya_cyb = 0x00ff; + +		/* calculate scaling increment */ +		if ( in_y > out_y ) +			ysci = ((1024 * in_y) / (out_y + 1)) - 1024; +		else +			ysci = 0; + +		dcgy = 0; + +		/* calculate ype and ypo */ +		ype = ysci / 16; +		ypo = ype + (ysci / 64); + +	} else { +		yacm = 1; + +		/* calculate scaling increment */ +		ysci = (((10 * 1024 * (in_y - out_y - 1)) / in_y) + 9) / 10; + +		/* calculate ype and ypo */ +		ypo = ype = ((ysci + 15) / 16); + +		/* the sequence length interval (yacl) has to be set according +		   to the prescale value, e.g.	[n   .. 1/2) : 0 +						[1/2 .. 1/3) : 1 +						[1/3 .. 1/4) : 2 +						... */ +		if ( ysci < 512) { +			yacl = 0; +		} else { +			yacl = ( ysci / (1024 - ysci) ); +		} + +		/* get filter coefficients for cya, cyb from table hps_v_coeff_tab */ +		cya_cyb = hps_v_coeff_tab[ (yacl < 63 ? yacl : 63 ) ].hps_coeff; + +		/* get best match in the table of attenuations for vertical scaling */ +		v_atten = hps_v_coeff_tab[ (yacl < 63 ? yacl : 63 ) ].weight_sum; + +		for (i = 0; v_attenuation[i] != 0; i++) { +			if (v_attenuation[i] >= v_atten) +				break; +		} + +		dcgy = i; +	} + +	/* ypo and ype swapped in spec ? */ +	*hps_v_scale	|= (yacm << 31) | (ysci << 21) | (yacl << 15) | (ypo << 8 ) | (ype << 1); + +	*hps_v_gain	&= ~(MASK_W0|MASK_B2); +	*hps_v_gain	|= (dcgy << 16) | (cya_cyb << 0); + +	return 0; +} + +/* simple bubble-sort algorithm with duplicate elimination */ +static int sort_and_eliminate(u32* values, int* count) +{ +	int low = 0, high = 0, top = 0, temp = 0; +	int cur = 0, next = 0; + +	/* sanity checks */ +	if( (0 > *count) || (NULL == values) ) { +		return -EINVAL; +	} + +	/* bubble sort the first @count items of the array @values */ +	for( top = *count; top > 0; top--) { +		for( low = 0, high = 1; high < top; low++, high++) { +			if( values[low] > values[high] ) { +				temp = values[low]; +				values[low] = values[high]; +				values[high] = temp; +			} +		} +	} + +	/* remove duplicate items */ +	for( cur = 0, next = 1; next < *count; next++) { +		if( values[cur] != values[next]) +			values[++cur] = values[next]; +	} + +	*count = cur + 1; + +	return 0; +} + +static void calculate_clipping_registers_rect(struct saa7146_dev *dev, struct saa7146_fh *fh, +	struct saa7146_video_dma *vdma2, u32* clip_format, u32* arbtr_ctrl, enum v4l2_field field) +{ +	struct saa7146_vv *vv = dev->vv_data; +	__le32 *clipping = vv->d_clipping.cpu_addr; + +	int width = vv->ov.win.w.width; +	int height =  vv->ov.win.w.height; +	int clipcount = vv->ov.nclips; + +	u32 line_list[32]; +	u32 pixel_list[32]; +	int numdwords = 0; + +	int i = 0, j = 0; +	int cnt_line = 0, cnt_pixel = 0; + +	int x[32], y[32], w[32], h[32]; + +	/* clear out memory */ +	memset(&line_list[0],  0x00, sizeof(u32)*32); +	memset(&pixel_list[0], 0x00, sizeof(u32)*32); +	memset(clipping,  0x00, SAA7146_CLIPPING_MEM); + +	/* fill the line and pixel-lists */ +	for(i = 0; i < clipcount; i++) { +		int l = 0, r = 0, t = 0, b = 0; + +		x[i] = vv->ov.clips[i].c.left; +		y[i] = vv->ov.clips[i].c.top; +		w[i] = vv->ov.clips[i].c.width; +		h[i] = vv->ov.clips[i].c.height; + +		if( w[i] < 0) { +			x[i] += w[i]; w[i] = -w[i]; +		} +		if( h[i] < 0) { +			y[i] += h[i]; h[i] = -h[i]; +		} +		if( x[i] < 0) { +			w[i] += x[i]; x[i] = 0; +		} +		if( y[i] < 0) { +			h[i] += y[i]; y[i] = 0; +		} +		if( 0 != vv->vflip ) { +			y[i] = height - y[i] - h[i]; +		} + +		l = x[i]; +		r = x[i]+w[i]; +		t = y[i]; +		b = y[i]+h[i]; + +		/* insert left/right coordinates */ +		pixel_list[ 2*i   ] = min_t(int, l, width); +		pixel_list[(2*i)+1] = min_t(int, r, width); +		/* insert top/bottom coordinates */ +		line_list[ 2*i   ] = min_t(int, t, height); +		line_list[(2*i)+1] = min_t(int, b, height); +	} + +	/* sort and eliminate lists */ +	cnt_line = cnt_pixel = 2*clipcount; +	sort_and_eliminate( &pixel_list[0], &cnt_pixel ); +	sort_and_eliminate( &line_list[0], &cnt_line ); + +	/* calculate the number of used u32s */ +	numdwords = max_t(int, (cnt_line+1), (cnt_pixel+1))*2; +	numdwords = max_t(int, 4, numdwords); +	numdwords = min_t(int, 64, numdwords); + +	/* fill up cliptable */ +	for(i = 0; i < cnt_pixel; i++) { +		clipping[2*i] |= cpu_to_le32(pixel_list[i] << 16); +	} +	for(i = 0; i < cnt_line; i++) { +		clipping[(2*i)+1] |= cpu_to_le32(line_list[i] << 16); +	} + +	/* fill up cliptable with the display infos */ +	for(j = 0; j < clipcount; j++) { + +		for(i = 0; i < cnt_pixel; i++) { + +			if( x[j] < 0) +				x[j] = 0; + +			if( pixel_list[i] < (x[j] + w[j])) { + +				if ( pixel_list[i] >= x[j] ) { +					clipping[2*i] |= cpu_to_le32(1 << j); +				} +			} +		} +		for(i = 0; i < cnt_line; i++) { + +			if( y[j] < 0) +				y[j] = 0; + +			if( line_list[i] < (y[j] + h[j]) ) { + +				if( line_list[i] >= y[j] ) { +					clipping[(2*i)+1] |= cpu_to_le32(1 << j); +				} +			} +		} +	} + +	/* adjust arbitration control register */ +	*arbtr_ctrl &= 0xffff00ff; +	*arbtr_ctrl |= 0x00001c00; + +	vdma2->base_even	= vv->d_clipping.dma_handle; +	vdma2->base_odd		= vv->d_clipping.dma_handle; +	vdma2->prot_addr	= vv->d_clipping.dma_handle+((sizeof(u32))*(numdwords)); +	vdma2->base_page	= 0x04; +	vdma2->pitch		= 0x00; +	vdma2->num_line_byte	= (0 << 16 | (sizeof(u32))*(numdwords-1) ); + +	/* set clipping-mode. this depends on the field(s) used */ +	*clip_format &= 0xfffffff7; +	if (V4L2_FIELD_HAS_BOTH(field)) { +		*clip_format |= 0x00000008; +	} else { +		*clip_format |= 0x00000000; +	} +} + +/* disable clipping */ +static void saa7146_disable_clipping(struct saa7146_dev *dev) +{ +	u32 clip_format	= saa7146_read(dev, CLIP_FORMAT_CTRL); + +	/* mask out relevant bits (=lower word)*/ +	clip_format &= MASK_W1; + +	/* upload clipping-registers*/ +	saa7146_write(dev, CLIP_FORMAT_CTRL,clip_format); +	saa7146_write(dev, MC2, (MASK_05 | MASK_21)); + +	/* disable video dma2 */ +	saa7146_write(dev, MC1, MASK_21); +} + +static void saa7146_set_clipping_rect(struct saa7146_fh *fh) +{ +	struct saa7146_dev *dev = fh->dev; +	struct saa7146_vv *vv = dev->vv_data; +	enum v4l2_field field = vv->ov.win.field; +	struct	saa7146_video_dma vdma2; +	u32 clip_format; +	u32 arbtr_ctrl; + +	/* check clipcount, disable clipping if clipcount == 0*/ +	if (vv->ov.nclips == 0) { +		saa7146_disable_clipping(dev); +		return; +	} + +	clip_format = saa7146_read(dev, CLIP_FORMAT_CTRL); +	arbtr_ctrl = saa7146_read(dev, PCI_BT_V1); + +	calculate_clipping_registers_rect(dev, fh, &vdma2, &clip_format, &arbtr_ctrl, field); + +	/* set clipping format */ +	clip_format &= 0xffff0008; +	clip_format |= (SAA7146_CLIPPING_RECT << 4); + +	/* prepare video dma2 */ +	saa7146_write(dev, BASE_EVEN2,		vdma2.base_even); +	saa7146_write(dev, BASE_ODD2,		vdma2.base_odd); +	saa7146_write(dev, PROT_ADDR2,		vdma2.prot_addr); +	saa7146_write(dev, BASE_PAGE2,		vdma2.base_page); +	saa7146_write(dev, PITCH2,		vdma2.pitch); +	saa7146_write(dev, NUM_LINE_BYTE2,	vdma2.num_line_byte); + +	/* prepare the rest */ +	saa7146_write(dev, CLIP_FORMAT_CTRL,clip_format); +	saa7146_write(dev, PCI_BT_V1, arbtr_ctrl); + +	/* upload clip_control-register, clipping-registers, enable video dma2 */ +	saa7146_write(dev, MC2, (MASK_05 | MASK_21 | MASK_03 | MASK_19)); +	saa7146_write(dev, MC1, (MASK_05 | MASK_21)); +} + +static void saa7146_set_window(struct saa7146_dev *dev, int width, int height, enum v4l2_field field) +{ +	struct saa7146_vv *vv = dev->vv_data; + +	int source = vv->current_hps_source; +	int sync = vv->current_hps_sync; + +	u32 hps_v_scale = 0, hps_v_gain  = 0, hps_ctrl = 0, hps_h_prescale = 0, hps_h_scale = 0; + +	/* set vertical scale */ +	hps_v_scale = 0; /* all bits get set by the function-call */ +	hps_v_gain  = 0; /* fixme: saa7146_read(dev, HPS_V_GAIN);*/ +	calculate_v_scale_registers(dev, field, vv->standard->v_field*2, height, &hps_v_scale, &hps_v_gain); + +	/* set horizontal scale */ +	hps_ctrl	= 0; +	hps_h_prescale	= 0; /* all bits get set in the function */ +	hps_h_scale	= 0; +	calculate_h_scale_registers(dev, vv->standard->h_pixels, width, vv->hflip, &hps_ctrl, &hps_v_gain, &hps_h_prescale, &hps_h_scale); + +	/* set hyo and hxo */ +	calculate_hxo_and_hyo(vv, &hps_h_scale, &hps_ctrl); +	calculate_hps_source_and_sync(dev, source, sync, &hps_ctrl); + +	/* write out new register contents */ +	saa7146_write(dev, HPS_V_SCALE,	hps_v_scale); +	saa7146_write(dev, HPS_V_GAIN,	hps_v_gain); +	saa7146_write(dev, HPS_CTRL,	hps_ctrl); +	saa7146_write(dev, HPS_H_PRESCALE,hps_h_prescale); +	saa7146_write(dev, HPS_H_SCALE,	hps_h_scale); + +	/* upload shadow-ram registers */ +	saa7146_write(dev, MC2, (MASK_05 | MASK_06 | MASK_21 | MASK_22) ); +} + +/* calculate the new memory offsets for a desired position */ +static void saa7146_set_position(struct saa7146_dev *dev, int w_x, int w_y, int w_height, enum v4l2_field field, u32 pixelformat) +{ +	struct saa7146_vv *vv = dev->vv_data; +	struct saa7146_format *sfmt = saa7146_format_by_fourcc(dev, pixelformat); + +	int b_depth = vv->ov_fmt->depth; +	int b_bpl = vv->ov_fb.fmt.bytesperline; +	/* The unsigned long cast is to remove a 64-bit compile warning since +	   it looks like a 64-bit address is cast to a 32-bit value, even +	   though the base pointer is really a 32-bit physical address that +	   goes into a 32-bit DMA register. +	   FIXME: might not work on some 64-bit platforms, but see the FIXME +	   in struct v4l2_framebuffer (videodev2.h) for that. +	 */ +	u32 base = (u32)(unsigned long)vv->ov_fb.base; + +	struct	saa7146_video_dma vdma1; + +	/* calculate memory offsets for picture, look if we shall top-down-flip */ +	vdma1.pitch	= 2*b_bpl; +	if ( 0 == vv->vflip ) { +		vdma1.base_even = base + (w_y * (vdma1.pitch/2)) + (w_x * (b_depth / 8)); +		vdma1.base_odd  = vdma1.base_even + (vdma1.pitch / 2); +		vdma1.prot_addr = vdma1.base_even + (w_height * (vdma1.pitch / 2)); +	} +	else { +		vdma1.base_even = base + ((w_y+w_height) * (vdma1.pitch/2)) + (w_x * (b_depth / 8)); +		vdma1.base_odd  = vdma1.base_even - (vdma1.pitch / 2); +		vdma1.prot_addr = vdma1.base_odd - (w_height * (vdma1.pitch / 2)); +	} + +	if (V4L2_FIELD_HAS_BOTH(field)) { +	} else if (field == V4L2_FIELD_ALTERNATE) { +		/* fixme */ +		vdma1.base_odd = vdma1.prot_addr; +		vdma1.pitch /= 2; +	} else if (field == V4L2_FIELD_TOP) { +		vdma1.base_odd = vdma1.prot_addr; +		vdma1.pitch /= 2; +	} else if (field == V4L2_FIELD_BOTTOM) { +		vdma1.base_odd = vdma1.base_even; +		vdma1.base_even = vdma1.prot_addr; +		vdma1.pitch /= 2; +	} + +	if ( 0 != vv->vflip ) { +		vdma1.pitch *= -1; +	} + +	vdma1.base_page = sfmt->swap; +	vdma1.num_line_byte = (vv->standard->v_field<<16)+vv->standard->h_pixels; + +	saa7146_write_out_dma(dev, 1, &vdma1); +} + +static void saa7146_set_output_format(struct saa7146_dev *dev, unsigned long palette) +{ +	u32 clip_format = saa7146_read(dev, CLIP_FORMAT_CTRL); + +	/* call helper function */ +	calculate_output_format_register(dev,palette,&clip_format); + +	/* update the hps registers */ +	saa7146_write(dev, CLIP_FORMAT_CTRL, clip_format); +	saa7146_write(dev, MC2, (MASK_05 | MASK_21)); +} + +/* select input-source */ +void saa7146_set_hps_source_and_sync(struct saa7146_dev *dev, int source, int sync) +{ +	struct saa7146_vv *vv = dev->vv_data; +	u32 hps_ctrl = 0; + +	/* read old state */ +	hps_ctrl = saa7146_read(dev, HPS_CTRL); + +	hps_ctrl &= ~( MASK_31 | MASK_30 | MASK_28 ); +	hps_ctrl |= (source << 30) | (sync << 28); + +	/* write back & upload register */ +	saa7146_write(dev, HPS_CTRL, hps_ctrl); +	saa7146_write(dev, MC2, (MASK_05 | MASK_21)); + +	vv->current_hps_source = source; +	vv->current_hps_sync = sync; +} +EXPORT_SYMBOL_GPL(saa7146_set_hps_source_and_sync); + +int saa7146_enable_overlay(struct saa7146_fh *fh) +{ +	struct saa7146_dev *dev = fh->dev; +	struct saa7146_vv *vv = dev->vv_data; + +	saa7146_set_window(dev, vv->ov.win.w.width, vv->ov.win.w.height, vv->ov.win.field); +	saa7146_set_position(dev, vv->ov.win.w.left, vv->ov.win.w.top, vv->ov.win.w.height, vv->ov.win.field, vv->ov_fmt->pixelformat); +	saa7146_set_output_format(dev, vv->ov_fmt->trans); +	saa7146_set_clipping_rect(fh); + +	/* enable video dma1 */ +	saa7146_write(dev, MC1, (MASK_06 | MASK_22)); +	return 0; +} + +void saa7146_disable_overlay(struct saa7146_fh *fh) +{ +	struct saa7146_dev *dev = fh->dev; + +	/* disable clipping + video dma1 */ +	saa7146_disable_clipping(dev); +	saa7146_write(dev, MC1, MASK_22); +} + +void saa7146_write_out_dma(struct saa7146_dev* dev, int which, struct saa7146_video_dma* vdma) +{ +	int where = 0; + +	if( which < 1 || which > 3) { +		return; +	} + +	/* calculate starting address */ +	where  = (which-1)*0x18; + +	saa7146_write(dev, where,	vdma->base_odd); +	saa7146_write(dev, where+0x04,	vdma->base_even); +	saa7146_write(dev, where+0x08,	vdma->prot_addr); +	saa7146_write(dev, where+0x0c,	vdma->pitch); +	saa7146_write(dev, where+0x10,	vdma->base_page); +	saa7146_write(dev, where+0x14,	vdma->num_line_byte); + +	/* upload */ +	saa7146_write(dev, MC2, (MASK_02<<(which-1))|(MASK_18<<(which-1))); +/* +	printk("vdma%d.base_even:     0x%08x\n", which,vdma->base_even); +	printk("vdma%d.base_odd:      0x%08x\n", which,vdma->base_odd); +	printk("vdma%d.prot_addr:     0x%08x\n", which,vdma->prot_addr); +	printk("vdma%d.base_page:     0x%08x\n", which,vdma->base_page); +	printk("vdma%d.pitch:         0x%08x\n", which,vdma->pitch); +	printk("vdma%d.num_line_byte: 0x%08x\n", which,vdma->num_line_byte); +*/ +} + +static int calculate_video_dma_grab_packed(struct saa7146_dev* dev, struct saa7146_buf *buf) +{ +	struct saa7146_vv *vv = dev->vv_data; +	struct saa7146_video_dma vdma1; + +	struct saa7146_format *sfmt = saa7146_format_by_fourcc(dev,buf->fmt->pixelformat); + +	int width = buf->fmt->width; +	int height = buf->fmt->height; +	int bytesperline = buf->fmt->bytesperline; +	enum v4l2_field field = buf->fmt->field; + +	int depth = sfmt->depth; + +	DEB_CAP("[size=%dx%d,fields=%s]\n", +		width, height, v4l2_field_names[field]); + +	if( bytesperline != 0) { +		vdma1.pitch = bytesperline*2; +	} else { +		vdma1.pitch = (width*depth*2)/8; +	} +	vdma1.num_line_byte	= ((vv->standard->v_field<<16) + vv->standard->h_pixels); +	vdma1.base_page		= buf->pt[0].dma | ME1 | sfmt->swap; + +	if( 0 != vv->vflip ) { +		vdma1.prot_addr	= buf->pt[0].offset; +		vdma1.base_even	= buf->pt[0].offset+(vdma1.pitch/2)*height; +		vdma1.base_odd	= vdma1.base_even - (vdma1.pitch/2); +	} else { +		vdma1.base_even	= buf->pt[0].offset; +		vdma1.base_odd	= vdma1.base_even + (vdma1.pitch/2); +		vdma1.prot_addr	= buf->pt[0].offset+(vdma1.pitch/2)*height; +	} + +	if (V4L2_FIELD_HAS_BOTH(field)) { +	} else if (field == V4L2_FIELD_ALTERNATE) { +		/* fixme */ +		if ( vv->last_field == V4L2_FIELD_TOP ) { +			vdma1.base_odd	= vdma1.prot_addr; +			vdma1.pitch /= 2; +		} else if ( vv->last_field == V4L2_FIELD_BOTTOM ) { +			vdma1.base_odd	= vdma1.base_even; +			vdma1.base_even = vdma1.prot_addr; +			vdma1.pitch /= 2; +		} +	} else if (field == V4L2_FIELD_TOP) { +		vdma1.base_odd	= vdma1.prot_addr; +		vdma1.pitch /= 2; +	} else if (field == V4L2_FIELD_BOTTOM) { +		vdma1.base_odd	= vdma1.base_even; +		vdma1.base_even = vdma1.prot_addr; +		vdma1.pitch /= 2; +	} + +	if( 0 != vv->vflip ) { +		vdma1.pitch *= -1; +	} + +	saa7146_write_out_dma(dev, 1, &vdma1); +	return 0; +} + +static int calc_planar_422(struct saa7146_vv *vv, struct saa7146_buf *buf, struct saa7146_video_dma *vdma2, struct saa7146_video_dma *vdma3) +{ +	int height = buf->fmt->height; +	int width = buf->fmt->width; + +	vdma2->pitch	= width; +	vdma3->pitch	= width; + +	/* fixme: look at bytesperline! */ + +	if( 0 != vv->vflip ) { +		vdma2->prot_addr	= buf->pt[1].offset; +		vdma2->base_even	= ((vdma2->pitch/2)*height)+buf->pt[1].offset; +		vdma2->base_odd		= vdma2->base_even - (vdma2->pitch/2); + +		vdma3->prot_addr	= buf->pt[2].offset; +		vdma3->base_even	= ((vdma3->pitch/2)*height)+buf->pt[2].offset; +		vdma3->base_odd		= vdma3->base_even - (vdma3->pitch/2); +	} else { +		vdma3->base_even	= buf->pt[2].offset; +		vdma3->base_odd		= vdma3->base_even + (vdma3->pitch/2); +		vdma3->prot_addr	= (vdma3->pitch/2)*height+buf->pt[2].offset; + +		vdma2->base_even	= buf->pt[1].offset; +		vdma2->base_odd		= vdma2->base_even + (vdma2->pitch/2); +		vdma2->prot_addr	= (vdma2->pitch/2)*height+buf->pt[1].offset; +	} + +	return 0; +} + +static int calc_planar_420(struct saa7146_vv *vv, struct saa7146_buf *buf, struct saa7146_video_dma *vdma2, struct saa7146_video_dma *vdma3) +{ +	int height = buf->fmt->height; +	int width = buf->fmt->width; + +	vdma2->pitch	= width/2; +	vdma3->pitch	= width/2; + +	if( 0 != vv->vflip ) { +		vdma2->prot_addr	= buf->pt[2].offset; +		vdma2->base_even	= ((vdma2->pitch/2)*height)+buf->pt[2].offset; +		vdma2->base_odd		= vdma2->base_even - (vdma2->pitch/2); + +		vdma3->prot_addr	= buf->pt[1].offset; +		vdma3->base_even	= ((vdma3->pitch/2)*height)+buf->pt[1].offset; +		vdma3->base_odd		= vdma3->base_even - (vdma3->pitch/2); + +	} else { +		vdma3->base_even	= buf->pt[2].offset; +		vdma3->base_odd		= vdma3->base_even + (vdma3->pitch); +		vdma3->prot_addr	= (vdma3->pitch/2)*height+buf->pt[2].offset; + +		vdma2->base_even	= buf->pt[1].offset; +		vdma2->base_odd		= vdma2->base_even + (vdma2->pitch); +		vdma2->prot_addr	= (vdma2->pitch/2)*height+buf->pt[1].offset; +	} +	return 0; +} + +static int calculate_video_dma_grab_planar(struct saa7146_dev* dev, struct saa7146_buf *buf) +{ +	struct saa7146_vv *vv = dev->vv_data; +	struct saa7146_video_dma vdma1; +	struct saa7146_video_dma vdma2; +	struct saa7146_video_dma vdma3; + +	struct saa7146_format *sfmt = saa7146_format_by_fourcc(dev,buf->fmt->pixelformat); + +	int width = buf->fmt->width; +	int height = buf->fmt->height; +	enum v4l2_field field = buf->fmt->field; + +	BUG_ON(0 == buf->pt[0].dma); +	BUG_ON(0 == buf->pt[1].dma); +	BUG_ON(0 == buf->pt[2].dma); + +	DEB_CAP("[size=%dx%d,fields=%s]\n", +		width, height, v4l2_field_names[field]); + +	/* fixme: look at bytesperline! */ + +	/* fixme: what happens for user space buffers here?. The offsets are +	   most likely wrong, this version here only works for page-aligned +	   buffers, modifications to the pagetable-functions are necessary...*/ + +	vdma1.pitch		= width*2; +	vdma1.num_line_byte	= ((vv->standard->v_field<<16) + vv->standard->h_pixels); +	vdma1.base_page		= buf->pt[0].dma | ME1; + +	if( 0 != vv->vflip ) { +		vdma1.prot_addr	= buf->pt[0].offset; +		vdma1.base_even	= ((vdma1.pitch/2)*height)+buf->pt[0].offset; +		vdma1.base_odd	= vdma1.base_even - (vdma1.pitch/2); +	} else { +		vdma1.base_even	= buf->pt[0].offset; +		vdma1.base_odd	= vdma1.base_even + (vdma1.pitch/2); +		vdma1.prot_addr	= (vdma1.pitch/2)*height+buf->pt[0].offset; +	} + +	vdma2.num_line_byte	= 0; /* unused */ +	vdma2.base_page		= buf->pt[1].dma | ME1; + +	vdma3.num_line_byte	= 0; /* unused */ +	vdma3.base_page		= buf->pt[2].dma | ME1; + +	switch( sfmt->depth ) { +		case 12: { +			calc_planar_420(vv,buf,&vdma2,&vdma3); +			break; +		} +		case 16: { +			calc_planar_422(vv,buf,&vdma2,&vdma3); +			break; +		} +		default: { +			return -1; +		} +	} + +	if (V4L2_FIELD_HAS_BOTH(field)) { +	} else if (field == V4L2_FIELD_ALTERNATE) { +		/* fixme */ +		vdma1.base_odd	= vdma1.prot_addr; +		vdma1.pitch /= 2; +		vdma2.base_odd	= vdma2.prot_addr; +		vdma2.pitch /= 2; +		vdma3.base_odd	= vdma3.prot_addr; +		vdma3.pitch /= 2; +	} else if (field == V4L2_FIELD_TOP) { +		vdma1.base_odd	= vdma1.prot_addr; +		vdma1.pitch /= 2; +		vdma2.base_odd	= vdma2.prot_addr; +		vdma2.pitch /= 2; +		vdma3.base_odd	= vdma3.prot_addr; +		vdma3.pitch /= 2; +	} else if (field == V4L2_FIELD_BOTTOM) { +		vdma1.base_odd	= vdma1.base_even; +		vdma1.base_even = vdma1.prot_addr; +		vdma1.pitch /= 2; +		vdma2.base_odd	= vdma2.base_even; +		vdma2.base_even = vdma2.prot_addr; +		vdma2.pitch /= 2; +		vdma3.base_odd	= vdma3.base_even; +		vdma3.base_even = vdma3.prot_addr; +		vdma3.pitch /= 2; +	} + +	if( 0 != vv->vflip ) { +		vdma1.pitch *= -1; +		vdma2.pitch *= -1; +		vdma3.pitch *= -1; +	} + +	saa7146_write_out_dma(dev, 1, &vdma1); +	if( (sfmt->flags & FORMAT_BYTE_SWAP) != 0 ) { +		saa7146_write_out_dma(dev, 3, &vdma2); +		saa7146_write_out_dma(dev, 2, &vdma3); +	} else { +		saa7146_write_out_dma(dev, 2, &vdma2); +		saa7146_write_out_dma(dev, 3, &vdma3); +	} +	return 0; +} + +static void program_capture_engine(struct saa7146_dev *dev, int planar) +{ +	struct saa7146_vv *vv = dev->vv_data; +	int count = 0; + +	unsigned long e_wait = vv->current_hps_sync == SAA7146_HPS_SYNC_PORT_A ? CMD_E_FID_A : CMD_E_FID_B; +	unsigned long o_wait = vv->current_hps_sync == SAA7146_HPS_SYNC_PORT_A ? CMD_O_FID_A : CMD_O_FID_B; + +	/* wait for o_fid_a/b / e_fid_a/b toggle only if rps register 0 is not set*/ +	WRITE_RPS0(CMD_PAUSE | CMD_OAN | CMD_SIG0 | o_wait); +	WRITE_RPS0(CMD_PAUSE | CMD_OAN | CMD_SIG0 | e_wait); + +	/* set rps register 0 */ +	WRITE_RPS0(CMD_WR_REG | (1 << 8) | (MC2/4)); +	WRITE_RPS0(MASK_27 | MASK_11); + +	/* turn on video-dma1 */ +	WRITE_RPS0(CMD_WR_REG_MASK | (MC1/4)); +	WRITE_RPS0(MASK_06 | MASK_22);			/* => mask */ +	WRITE_RPS0(MASK_06 | MASK_22);			/* => values */ +	if( 0 != planar ) { +		/* turn on video-dma2 */ +		WRITE_RPS0(CMD_WR_REG_MASK | (MC1/4)); +		WRITE_RPS0(MASK_05 | MASK_21);			/* => mask */ +		WRITE_RPS0(MASK_05 | MASK_21);			/* => values */ + +		/* turn on video-dma3 */ +		WRITE_RPS0(CMD_WR_REG_MASK | (MC1/4)); +		WRITE_RPS0(MASK_04 | MASK_20);			/* => mask */ +		WRITE_RPS0(MASK_04 | MASK_20);			/* => values */ +	} + +	/* wait for o_fid_a/b / e_fid_a/b toggle */ +	if ( vv->last_field == V4L2_FIELD_INTERLACED ) { +		WRITE_RPS0(CMD_PAUSE | o_wait); +		WRITE_RPS0(CMD_PAUSE | e_wait); +	} else if ( vv->last_field == V4L2_FIELD_TOP ) { +		WRITE_RPS0(CMD_PAUSE | (vv->current_hps_sync == SAA7146_HPS_SYNC_PORT_A ? MASK_10 : MASK_09)); +		WRITE_RPS0(CMD_PAUSE | o_wait); +	} else if ( vv->last_field == V4L2_FIELD_BOTTOM ) { +		WRITE_RPS0(CMD_PAUSE | (vv->current_hps_sync == SAA7146_HPS_SYNC_PORT_A ? MASK_10 : MASK_09)); +		WRITE_RPS0(CMD_PAUSE | e_wait); +	} + +	/* turn off video-dma1 */ +	WRITE_RPS0(CMD_WR_REG_MASK | (MC1/4)); +	WRITE_RPS0(MASK_22 | MASK_06);			/* => mask */ +	WRITE_RPS0(MASK_22);				/* => values */ +	if( 0 != planar ) { +		/* turn off video-dma2 */ +		WRITE_RPS0(CMD_WR_REG_MASK | (MC1/4)); +		WRITE_RPS0(MASK_05 | MASK_21);			/* => mask */ +		WRITE_RPS0(MASK_21);				/* => values */ + +		/* turn off video-dma3 */ +		WRITE_RPS0(CMD_WR_REG_MASK | (MC1/4)); +		WRITE_RPS0(MASK_04 | MASK_20);			/* => mask */ +		WRITE_RPS0(MASK_20);				/* => values */ +	} + +	/* generate interrupt */ +	WRITE_RPS0(CMD_INTERRUPT); + +	/* stop */ +	WRITE_RPS0(CMD_STOP); +} + +void saa7146_set_capture(struct saa7146_dev *dev, struct saa7146_buf *buf, struct saa7146_buf *next) +{ +	struct saa7146_format *sfmt = saa7146_format_by_fourcc(dev,buf->fmt->pixelformat); +	struct saa7146_vv *vv = dev->vv_data; +	u32 vdma1_prot_addr; + +	DEB_CAP("buf:%p, next:%p\n", buf, next); + +	vdma1_prot_addr = saa7146_read(dev, PROT_ADDR1); +	if( 0 == vdma1_prot_addr ) { +		/* clear out beginning of streaming bit (rps register 0)*/ +		DEB_CAP("forcing sync to new frame\n"); +		saa7146_write(dev, MC2, MASK_27 ); +	} + +	saa7146_set_window(dev, buf->fmt->width, buf->fmt->height, buf->fmt->field); +	saa7146_set_output_format(dev, sfmt->trans); +	saa7146_disable_clipping(dev); + +	if ( vv->last_field == V4L2_FIELD_INTERLACED ) { +	} else if ( vv->last_field == V4L2_FIELD_TOP ) { +		vv->last_field = V4L2_FIELD_BOTTOM; +	} else if ( vv->last_field == V4L2_FIELD_BOTTOM ) { +		vv->last_field = V4L2_FIELD_TOP; +	} + +	if( 0 != IS_PLANAR(sfmt->trans)) { +		calculate_video_dma_grab_planar(dev, buf); +		program_capture_engine(dev,1); +	} else { +		calculate_video_dma_grab_packed(dev, buf); +		program_capture_engine(dev,0); +	} + +/* +	printk("vdma%d.base_even:     0x%08x\n", 1,saa7146_read(dev,BASE_EVEN1)); +	printk("vdma%d.base_odd:      0x%08x\n", 1,saa7146_read(dev,BASE_ODD1)); +	printk("vdma%d.prot_addr:     0x%08x\n", 1,saa7146_read(dev,PROT_ADDR1)); +	printk("vdma%d.base_page:     0x%08x\n", 1,saa7146_read(dev,BASE_PAGE1)); +	printk("vdma%d.pitch:         0x%08x\n", 1,saa7146_read(dev,PITCH1)); +	printk("vdma%d.num_line_byte: 0x%08x\n", 1,saa7146_read(dev,NUM_LINE_BYTE1)); +	printk("vdma%d => vptr      : 0x%08x\n", 1,saa7146_read(dev,PCI_VDP1)); +*/ + +	/* write the address of the rps-program */ +	saa7146_write(dev, RPS_ADDR0, dev->d_rps0.dma_handle); + +	/* turn on rps */ +	saa7146_write(dev, MC1, (MASK_12 | MASK_28)); +} diff --git a/drivers/media/common/saa7146/saa7146_i2c.c b/drivers/media/common/saa7146/saa7146_i2c.c new file mode 100644 index 00000000000..22027198129 --- /dev/null +++ b/drivers/media/common/saa7146/saa7146_i2c.c @@ -0,0 +1,423 @@ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <media/saa7146_vv.h> + +static u32 saa7146_i2c_func(struct i2c_adapter *adapter) +{ +	/* DEB_I2C("'%s'\n", adapter->name); */ + +	return	  I2C_FUNC_I2C +		| I2C_FUNC_SMBUS_QUICK +		| I2C_FUNC_SMBUS_READ_BYTE	| I2C_FUNC_SMBUS_WRITE_BYTE +		| I2C_FUNC_SMBUS_READ_BYTE_DATA | I2C_FUNC_SMBUS_WRITE_BYTE_DATA; +} + +/* this function returns the status-register of our i2c-device */ +static inline u32 saa7146_i2c_status(struct saa7146_dev *dev) +{ +	u32 iicsta = saa7146_read(dev, I2C_STATUS); +	/* DEB_I2C("status: 0x%08x\n", iicsta); */ +	return iicsta; +} + +/* this function runs through the i2c-messages and prepares the data to be +   sent through the saa7146. have a look at the specifications p. 122 ff +   to understand this. it returns the number of u32s to send, or -1 +   in case of an error. */ +static int saa7146_i2c_msg_prepare(const struct i2c_msg *m, int num, __le32 *op) +{ +	int h1, h2; +	int i, j, addr; +	int mem = 0, op_count = 0; + +	/* first determine size of needed memory */ +	for(i = 0; i < num; i++) { +		mem += m[i].len + 1; +	} + +	/* worst case: we need one u32 for three bytes to be send +	   plus one extra byte to address the device */ +	mem = 1 + ((mem-1) / 3); + +	/* we assume that op points to a memory of at least +	 * SAA7146_I2C_MEM bytes size. if we exceed this limit... +	 */ +	if ((4 * mem) > SAA7146_I2C_MEM) { +		/* DEB_I2C("cannot prepare i2c-message\n"); */ +		return -ENOMEM; +	} + +	/* be careful: clear out the i2c-mem first */ +	memset(op,0,sizeof(__le32)*mem); + +	/* loop through all messages */ +	for(i = 0; i < num; i++) { + +		/* insert the address of the i2c-slave. +		   note: we get 7 bit i2c-addresses, +		   so we have to perform a translation */ +		addr = (m[i].addr*2) + ( (0 != (m[i].flags & I2C_M_RD)) ? 1 : 0); +		h1 = op_count/3; h2 = op_count%3; +		op[h1] |= cpu_to_le32(	    (u8)addr << ((3-h2)*8)); +		op[h1] |= cpu_to_le32(SAA7146_I2C_START << ((3-h2)*2)); +		op_count++; + +		/* loop through all bytes of message i */ +		for(j = 0; j < m[i].len; j++) { +			/* insert the data bytes */ +			h1 = op_count/3; h2 = op_count%3; +			op[h1] |= cpu_to_le32( (u32)((u8)m[i].buf[j]) << ((3-h2)*8)); +			op[h1] |= cpu_to_le32(       SAA7146_I2C_CONT << ((3-h2)*2)); +			op_count++; +		} + +	} + +	/* have a look at the last byte inserted: +	  if it was: ...CONT change it to ...STOP */ +	h1 = (op_count-1)/3; h2 = (op_count-1)%3; +	if ( SAA7146_I2C_CONT == (0x3 & (le32_to_cpu(op[h1]) >> ((3-h2)*2))) ) { +		op[h1] &= ~cpu_to_le32(0x2 << ((3-h2)*2)); +		op[h1] |= cpu_to_le32(SAA7146_I2C_STOP << ((3-h2)*2)); +	} + +	/* return the number of u32s to send */ +	return mem; +} + +/* this functions loops through all i2c-messages. normally, it should determine +   which bytes were read through the adapter and write them back to the corresponding +   i2c-message. but instead, we simply write back all bytes. +   fixme: this could be improved. */ +static int saa7146_i2c_msg_cleanup(const struct i2c_msg *m, int num, __le32 *op) +{ +	int i, j; +	int op_count = 0; + +	/* loop through all messages */ +	for(i = 0; i < num; i++) { + +		op_count++; + +		/* loop through all bytes of message i */ +		for(j = 0; j < m[i].len; j++) { +			/* write back all bytes that could have been read */ +			m[i].buf[j] = (le32_to_cpu(op[op_count/3]) >> ((3-(op_count%3))*8)); +			op_count++; +		} +	} + +	return 0; +} + +/* this functions resets the i2c-device and returns 0 if everything was fine, otherwise -1 */ +static int saa7146_i2c_reset(struct saa7146_dev *dev) +{ +	/* get current status */ +	u32 status = saa7146_i2c_status(dev); + +	/* clear registers for sure */ +	saa7146_write(dev, I2C_STATUS, dev->i2c_bitrate); +	saa7146_write(dev, I2C_TRANSFER, 0); + +	/* check if any operation is still in progress */ +	if ( 0 != ( status & SAA7146_I2C_BUSY) ) { + +		/* yes, kill ongoing operation */ +		DEB_I2C("busy_state detected\n"); + +		/* set "ABORT-OPERATION"-bit (bit 7)*/ +		saa7146_write(dev, I2C_STATUS, (dev->i2c_bitrate | MASK_07)); +		saa7146_write(dev, MC2, (MASK_00 | MASK_16)); +		msleep(SAA7146_I2C_DELAY); + +		/* clear all error-bits pending; this is needed because p.123, note 1 */ +		saa7146_write(dev, I2C_STATUS, dev->i2c_bitrate); +		saa7146_write(dev, MC2, (MASK_00 | MASK_16)); +		msleep(SAA7146_I2C_DELAY); +	} + +	/* check if any error is (still) present. (this can be necessary because p.123, note 1) */ +	status = saa7146_i2c_status(dev); + +	if ( dev->i2c_bitrate != status ) { + +		DEB_I2C("error_state detected. status:0x%08x\n", status); + +		/* Repeat the abort operation. This seems to be necessary +		   after serious protocol errors caused by e.g. the SAA7740 */ +		saa7146_write(dev, I2C_STATUS, (dev->i2c_bitrate | MASK_07)); +		saa7146_write(dev, MC2, (MASK_00 | MASK_16)); +		msleep(SAA7146_I2C_DELAY); + +		/* clear all error-bits pending */ +		saa7146_write(dev, I2C_STATUS, dev->i2c_bitrate); +		saa7146_write(dev, MC2, (MASK_00 | MASK_16)); +		msleep(SAA7146_I2C_DELAY); + +		/* the data sheet says it might be necessary to clear the status +		   twice after an abort */ +		saa7146_write(dev, I2C_STATUS, dev->i2c_bitrate); +		saa7146_write(dev, MC2, (MASK_00 | MASK_16)); +		msleep(SAA7146_I2C_DELAY); +	} + +	/* if any error is still present, a fatal error has occurred ... */ +	status = saa7146_i2c_status(dev); +	if ( dev->i2c_bitrate != status ) { +		DEB_I2C("fatal error. status:0x%08x\n", status); +		return -1; +	} + +	return 0; +} + +/* this functions writes out the data-byte 'dword' to the i2c-device. +   it returns 0 if ok, -1 if the transfer failed, -2 if the transfer +   failed badly (e.g. address error) */ +static int saa7146_i2c_writeout(struct saa7146_dev *dev, __le32 *dword, int short_delay) +{ +	u32 status = 0, mc2 = 0; +	int trial = 0; +	unsigned long timeout; + +	/* write out i2c-command */ +	DEB_I2C("before: 0x%08x (status: 0x%08x), %d\n", +		*dword, saa7146_read(dev, I2C_STATUS), dev->i2c_op); + +	if( 0 != (SAA7146_USE_I2C_IRQ & dev->ext->flags)) { + +		saa7146_write(dev, I2C_STATUS,	 dev->i2c_bitrate); +		saa7146_write(dev, I2C_TRANSFER, le32_to_cpu(*dword)); + +		dev->i2c_op = 1; +		SAA7146_ISR_CLEAR(dev, MASK_16|MASK_17); +		SAA7146_IER_ENABLE(dev, MASK_16|MASK_17); +		saa7146_write(dev, MC2, (MASK_00 | MASK_16)); + +		timeout = HZ/100 + 1; /* 10ms */ +		timeout = wait_event_interruptible_timeout(dev->i2c_wq, dev->i2c_op == 0, timeout); +		if (timeout == -ERESTARTSYS || dev->i2c_op) { +			SAA7146_IER_DISABLE(dev, MASK_16|MASK_17); +			SAA7146_ISR_CLEAR(dev, MASK_16|MASK_17); +			if (timeout == -ERESTARTSYS) +				/* a signal arrived */ +				return -ERESTARTSYS; + +			pr_warn("%s %s [irq]: timed out waiting for end of xfer\n", +				dev->name, __func__); +			return -EIO; +		} +		status = saa7146_read(dev, I2C_STATUS); +	} else { +		saa7146_write(dev, I2C_STATUS,	 dev->i2c_bitrate); +		saa7146_write(dev, I2C_TRANSFER, le32_to_cpu(*dword)); +		saa7146_write(dev, MC2, (MASK_00 | MASK_16)); + +		/* do not poll for i2c-status before upload is complete */ +		timeout = jiffies + HZ/100 + 1; /* 10ms */ +		while(1) { +			mc2 = (saa7146_read(dev, MC2) & 0x1); +			if( 0 != mc2 ) { +				break; +			} +			if (time_after(jiffies,timeout)) { +				pr_warn("%s %s: timed out waiting for MC2\n", +					dev->name, __func__); +				return -EIO; +			} +		} +		/* wait until we get a transfer done or error */ +		timeout = jiffies + HZ/100 + 1; /* 10ms */ +		/* first read usually delivers bogus results... */ +		saa7146_i2c_status(dev); +		while(1) { +			status = saa7146_i2c_status(dev); +			if ((status & 0x3) != 1) +				break; +			if (time_after(jiffies,timeout)) { +				/* this is normal when probing the bus +				 * (no answer from nonexisistant device...) +				 */ +				pr_warn("%s %s [poll]: timed out waiting for end of xfer\n", +					dev->name, __func__); +				return -EIO; +			} +			if (++trial < 50 && short_delay) +				udelay(10); +			else +				msleep(1); +		} +	} + +	/* give a detailed status report */ +	if ( 0 != (status & (SAA7146_I2C_SPERR | SAA7146_I2C_APERR | +			     SAA7146_I2C_DTERR | SAA7146_I2C_DRERR | +			     SAA7146_I2C_AL    | SAA7146_I2C_ERR   | +			     SAA7146_I2C_BUSY)) ) { + +		if ( 0 == (status & SAA7146_I2C_ERR) || +		     0 == (status & SAA7146_I2C_BUSY) ) { +			/* it may take some time until ERR goes high - ignore */ +			DEB_I2C("unexpected i2c status %04x\n", status); +		} +		if( 0 != (status & SAA7146_I2C_SPERR) ) { +			DEB_I2C("error due to invalid start/stop condition\n"); +		} +		if( 0 != (status & SAA7146_I2C_DTERR) ) { +			DEB_I2C("error in data transmission\n"); +		} +		if( 0 != (status & SAA7146_I2C_DRERR) ) { +			DEB_I2C("error when receiving data\n"); +		} +		if( 0 != (status & SAA7146_I2C_AL) ) { +			DEB_I2C("error because arbitration lost\n"); +		} + +		/* we handle address-errors here */ +		if( 0 != (status & SAA7146_I2C_APERR) ) { +			DEB_I2C("error in address phase\n"); +			return -EREMOTEIO; +		} + +		return -EIO; +	} + +	/* read back data, just in case we were reading ... */ +	*dword = cpu_to_le32(saa7146_read(dev, I2C_TRANSFER)); + +	DEB_I2C("after: 0x%08x\n", *dword); +	return 0; +} + +static int saa7146_i2c_transfer(struct saa7146_dev *dev, const struct i2c_msg *msgs, int num, int retries) +{ +	int i = 0, count = 0; +	__le32 *buffer = dev->d_i2c.cpu_addr; +	int err = 0; +	int short_delay = 0; + +	if (mutex_lock_interruptible(&dev->i2c_lock)) +		return -ERESTARTSYS; + +	for(i=0;i<num;i++) { +		DEB_I2C("msg:%d/%d\n", i+1, num); +	} + +	/* prepare the message(s), get number of u32s to transfer */ +	count = saa7146_i2c_msg_prepare(msgs, num, buffer); +	if ( 0 > count ) { +		err = -1; +		goto out; +	} + +	if ( count > 3 || 0 != (SAA7146_I2C_SHORT_DELAY & dev->ext->flags) ) +		short_delay = 1; + +	do { +		/* reset the i2c-device if necessary */ +		err = saa7146_i2c_reset(dev); +		if ( 0 > err ) { +			DEB_I2C("could not reset i2c-device\n"); +			goto out; +		} + +		/* write out the u32s one after another */ +		for(i = 0; i < count; i++) { +			err = saa7146_i2c_writeout(dev, &buffer[i], short_delay); +			if ( 0 != err) { +				/* this one is unsatisfying: some i2c slaves on some +				   dvb cards don't acknowledge correctly, so the saa7146 +				   thinks that an address error occurred. in that case, the +				   transaction should be retrying, even if an address error +				   occurred. analog saa7146 based cards extensively rely on +				   i2c address probing, however, and address errors indicate that a +				   device is really *not* there. retrying in that case +				   increases the time the device needs to probe greatly, so +				   it should be avoided. So we bail out in irq mode after an +				   address error and trust the saa7146 address error detection. */ +				if (-EREMOTEIO == err && 0 != (SAA7146_USE_I2C_IRQ & dev->ext->flags)) +					goto out; +				DEB_I2C("error while sending message(s). starting again\n"); +				break; +			} +		} +		if( 0 == err ) { +			err = num; +			break; +		} + +		/* delay a bit before retrying */ +		msleep(10); + +	} while (err != num && retries--); + +	/* quit if any error occurred */ +	if (err != num) +		goto out; + +	/* if any things had to be read, get the results */ +	if ( 0 != saa7146_i2c_msg_cleanup(msgs, num, buffer)) { +		DEB_I2C("could not cleanup i2c-message\n"); +		err = -1; +		goto out; +	} + +	/* return the number of delivered messages */ +	DEB_I2C("transmission successful. (msg:%d)\n", err); +out: +	/* another bug in revision 0: the i2c-registers get uploaded randomly by other +	   uploads, so we better clear them out before continuing */ +	if( 0 == dev->revision ) { +		__le32 zero = 0; +		saa7146_i2c_reset(dev); +		if( 0 != saa7146_i2c_writeout(dev, &zero, short_delay)) { +			pr_info("revision 0 error. this should never happen\n"); +		} +	} + +	mutex_unlock(&dev->i2c_lock); +	return err; +} + +/* utility functions */ +static int saa7146_i2c_xfer(struct i2c_adapter* adapter, struct i2c_msg *msg, int num) +{ +	struct v4l2_device *v4l2_dev = i2c_get_adapdata(adapter); +	struct saa7146_dev *dev = to_saa7146_dev(v4l2_dev); + +	/* use helper function to transfer data */ +	return saa7146_i2c_transfer(dev, msg, num, adapter->retries); +} + + +/*****************************************************************************/ +/* i2c-adapter helper functions                                              */ + +/* exported algorithm data */ +static struct i2c_algorithm saa7146_algo = { +	.master_xfer	= saa7146_i2c_xfer, +	.functionality	= saa7146_i2c_func, +}; + +int saa7146_i2c_adapter_prepare(struct saa7146_dev *dev, struct i2c_adapter *i2c_adapter, u32 bitrate) +{ +	DEB_EE("bitrate: 0x%08x\n", bitrate); + +	/* enable i2c-port pins */ +	saa7146_write(dev, MC1, (MASK_08 | MASK_24)); + +	dev->i2c_bitrate = bitrate; +	saa7146_i2c_reset(dev); + +	if (i2c_adapter) { +		i2c_set_adapdata(i2c_adapter, &dev->v4l2_dev); +		i2c_adapter->dev.parent    = &dev->pci->dev; +		i2c_adapter->algo	   = &saa7146_algo; +		i2c_adapter->algo_data     = NULL; +		i2c_adapter->timeout = SAA7146_I2C_TIMEOUT; +		i2c_adapter->retries = SAA7146_I2C_RETRIES; +	} + +	return 0; +} diff --git a/drivers/media/common/saa7146/saa7146_vbi.c b/drivers/media/common/saa7146/saa7146_vbi.c new file mode 100644 index 00000000000..1e71e374bbf --- /dev/null +++ b/drivers/media/common/saa7146/saa7146_vbi.c @@ -0,0 +1,498 @@ +#include <media/saa7146_vv.h> + +static int vbi_pixel_to_capture = 720 * 2; + +static int vbi_workaround(struct saa7146_dev *dev) +{ +	struct saa7146_vv *vv = dev->vv_data; + +	u32          *cpu; +	dma_addr_t   dma_addr; + +	int count = 0; +	int i; + +	DECLARE_WAITQUEUE(wait, current); + +	DEB_VBI("dev:%p\n", dev); + +	/* once again, a bug in the saa7146: the brs acquisition +	   is buggy and especially the BXO-counter does not work +	   as specified. there is this workaround, but please +	   don't let me explain it. ;-) */ + +	cpu = pci_alloc_consistent(dev->pci, 4096, &dma_addr); +	if (NULL == cpu) +		return -ENOMEM; + +	/* setup some basic programming, just for the workaround */ +	saa7146_write(dev, BASE_EVEN3,	dma_addr); +	saa7146_write(dev, BASE_ODD3,	dma_addr+vbi_pixel_to_capture); +	saa7146_write(dev, PROT_ADDR3,	dma_addr+4096); +	saa7146_write(dev, PITCH3,	vbi_pixel_to_capture); +	saa7146_write(dev, BASE_PAGE3,	0x0); +	saa7146_write(dev, NUM_LINE_BYTE3, (2<<16)|((vbi_pixel_to_capture)<<0)); +	saa7146_write(dev, MC2, MASK_04|MASK_20); + +	/* load brs-control register */ +	WRITE_RPS1(CMD_WR_REG | (1 << 8) | (BRS_CTRL/4)); +	/* BXO = 1h, BRS to outbound */ +	WRITE_RPS1(0xc000008c); +	/* wait for vbi_a or vbi_b*/ +	if ( 0 != (SAA7146_USE_PORT_B_FOR_VBI & dev->ext_vv_data->flags)) { +		DEB_D("...using port b\n"); +		WRITE_RPS1(CMD_PAUSE | CMD_OAN | CMD_SIG1 | CMD_E_FID_B); +		WRITE_RPS1(CMD_PAUSE | CMD_OAN | CMD_SIG1 | CMD_O_FID_B); +/* +		WRITE_RPS1(CMD_PAUSE | MASK_09); +*/ +	} else { +		DEB_D("...using port a\n"); +		WRITE_RPS1(CMD_PAUSE | MASK_10); +	} +	/* upload brs */ +	WRITE_RPS1(CMD_UPLOAD | MASK_08); +	/* load brs-control register */ +	WRITE_RPS1(CMD_WR_REG | (1 << 8) | (BRS_CTRL/4)); +	/* BYO = 1, BXO = NQBIL (=1728 for PAL, for NTSC this is 858*2) - NumByte3 (=1440) = 288 */ +	WRITE_RPS1(((1728-(vbi_pixel_to_capture)) << 7) | MASK_19); +	/* wait for brs_done */ +	WRITE_RPS1(CMD_PAUSE | MASK_08); +	/* upload brs */ +	WRITE_RPS1(CMD_UPLOAD | MASK_08); +	/* load video-dma3 NumLines3 and NumBytes3 */ +	WRITE_RPS1(CMD_WR_REG | (1 << 8) | (NUM_LINE_BYTE3/4)); +	/* dev->vbi_count*2 lines, 720 pixel (= 1440 Bytes) */ +	WRITE_RPS1((2 << 16) | (vbi_pixel_to_capture)); +	/* load brs-control register */ +	WRITE_RPS1(CMD_WR_REG | (1 << 8) | (BRS_CTRL/4)); +	/* Set BRS right: note: this is an experimental value for BXO (=> PAL!) */ +	WRITE_RPS1((540 << 7) | (5 << 19));  // 5 == vbi_start +	/* wait for brs_done */ +	WRITE_RPS1(CMD_PAUSE | MASK_08); +	/* upload brs and video-dma3*/ +	WRITE_RPS1(CMD_UPLOAD | MASK_08 | MASK_04); +	/* load mc2 register: enable dma3 */ +	WRITE_RPS1(CMD_WR_REG | (1 << 8) | (MC1/4)); +	WRITE_RPS1(MASK_20 | MASK_04); +	/* generate interrupt */ +	WRITE_RPS1(CMD_INTERRUPT); +	/* stop rps1 */ +	WRITE_RPS1(CMD_STOP); + +	/* we have to do the workaround twice to be sure that +	   everything is ok */ +	for(i = 0; i < 2; i++) { + +		/* indicate to the irq handler that we do the workaround */ +		saa7146_write(dev, MC2, MASK_31|MASK_15); + +		saa7146_write(dev, NUM_LINE_BYTE3, (1<<16)|(2<<0)); +		saa7146_write(dev, MC2, MASK_04|MASK_20); + +		/* enable rps1 irqs */ +		SAA7146_IER_ENABLE(dev,MASK_28); + +		/* prepare to wait to be woken up by the irq-handler */ +		add_wait_queue(&vv->vbi_wq, &wait); +		current->state = TASK_INTERRUPTIBLE; + +		/* start rps1 to enable workaround */ +		saa7146_write(dev, RPS_ADDR1, dev->d_rps1.dma_handle); +		saa7146_write(dev, MC1, (MASK_13 | MASK_29)); + +		schedule(); + +		DEB_VBI("brs bug workaround %d/1\n", i); + +		remove_wait_queue(&vv->vbi_wq, &wait); +		current->state = TASK_RUNNING; + +		/* disable rps1 irqs */ +		SAA7146_IER_DISABLE(dev,MASK_28); + +		/* stop video-dma3 */ +		saa7146_write(dev, MC1, MASK_20); + +		if(signal_pending(current)) { + +			DEB_VBI("aborted (rps:0x%08x)\n", +				saa7146_read(dev, RPS_ADDR1)); + +			/* stop rps1 for sure */ +			saa7146_write(dev, MC1, MASK_29); + +			pci_free_consistent(dev->pci, 4096, cpu, dma_addr); +			return -EINTR; +		} +	} + +	pci_free_consistent(dev->pci, 4096, cpu, dma_addr); +	return 0; +} + +static void saa7146_set_vbi_capture(struct saa7146_dev *dev, struct saa7146_buf *buf, struct saa7146_buf *next) +{ +	struct saa7146_vv *vv = dev->vv_data; + +	struct saa7146_video_dma vdma3; + +	int count = 0; +	unsigned long e_wait = vv->current_hps_sync == SAA7146_HPS_SYNC_PORT_A ? CMD_E_FID_A : CMD_E_FID_B; +	unsigned long o_wait = vv->current_hps_sync == SAA7146_HPS_SYNC_PORT_A ? CMD_O_FID_A : CMD_O_FID_B; + +/* +	vdma3.base_even	= 0xc8000000+2560*70; +	vdma3.base_odd	= 0xc8000000; +	vdma3.prot_addr	= 0xc8000000+2560*164; +	vdma3.pitch	= 2560; +	vdma3.base_page	= 0; +	vdma3.num_line_byte = (64<<16)|((vbi_pixel_to_capture)<<0); // set above! +*/ +	vdma3.base_even	= buf->pt[2].offset; +	vdma3.base_odd	= buf->pt[2].offset + 16 * vbi_pixel_to_capture; +	vdma3.prot_addr	= buf->pt[2].offset + 16 * 2 * vbi_pixel_to_capture; +	vdma3.pitch	= vbi_pixel_to_capture; +	vdma3.base_page	= buf->pt[2].dma | ME1; +	vdma3.num_line_byte = (16 << 16) | vbi_pixel_to_capture; + +	saa7146_write_out_dma(dev, 3, &vdma3); + +	/* write beginning of rps-program */ +	count = 0; + +	/* wait for o_fid_a/b / e_fid_a/b toggle only if bit 1 is not set */ + +	/* we don't wait here for the first field anymore. this is different from the video +	   capture and might cause that the first buffer is only half filled (with only +	   one field). but since this is some sort of streaming data, this is not that negative. +	   but by doing this, we can use the whole engine from videobuf-dma-sg.c... */ + +/* +	WRITE_RPS1(CMD_PAUSE | CMD_OAN | CMD_SIG1 | e_wait); +	WRITE_RPS1(CMD_PAUSE | CMD_OAN | CMD_SIG1 | o_wait); +*/ +	/* set bit 1 */ +	WRITE_RPS1(CMD_WR_REG | (1 << 8) | (MC2/4)); +	WRITE_RPS1(MASK_28 | MASK_12); + +	/* turn on video-dma3 */ +	WRITE_RPS1(CMD_WR_REG_MASK | (MC1/4)); +	WRITE_RPS1(MASK_04 | MASK_20);			/* => mask */ +	WRITE_RPS1(MASK_04 | MASK_20);			/* => values */ + +	/* wait for o_fid_a/b / e_fid_a/b toggle */ +	WRITE_RPS1(CMD_PAUSE | o_wait); +	WRITE_RPS1(CMD_PAUSE | e_wait); + +	/* generate interrupt */ +	WRITE_RPS1(CMD_INTERRUPT); + +	/* stop */ +	WRITE_RPS1(CMD_STOP); + +	/* enable rps1 irqs */ +	SAA7146_IER_ENABLE(dev, MASK_28); + +	/* write the address of the rps-program */ +	saa7146_write(dev, RPS_ADDR1, dev->d_rps1.dma_handle); + +	/* turn on rps */ +	saa7146_write(dev, MC1, (MASK_13 | MASK_29)); +} + +static int buffer_activate(struct saa7146_dev *dev, +			   struct saa7146_buf *buf, +			   struct saa7146_buf *next) +{ +	struct saa7146_vv *vv = dev->vv_data; +	buf->vb.state = VIDEOBUF_ACTIVE; + +	DEB_VBI("dev:%p, buf:%p, next:%p\n", dev, buf, next); +	saa7146_set_vbi_capture(dev,buf,next); + +	mod_timer(&vv->vbi_dmaq.timeout, jiffies+BUFFER_TIMEOUT); +	return 0; +} + +static int buffer_prepare(struct videobuf_queue *q, struct videobuf_buffer *vb,enum v4l2_field field) +{ +	struct file *file = q->priv_data; +	struct saa7146_fh *fh = file->private_data; +	struct saa7146_dev *dev = fh->dev; +	struct saa7146_buf *buf = (struct saa7146_buf *)vb; + +	int err = 0; +	int lines, llength, size; + +	lines   = 16 * 2 ; /* 2 fields */ +	llength = vbi_pixel_to_capture; +	size = lines * llength; + +	DEB_VBI("vb:%p\n", vb); + +	if (0 != buf->vb.baddr  &&  buf->vb.bsize < size) { +		DEB_VBI("size mismatch\n"); +		return -EINVAL; +	} + +	if (buf->vb.size != size) +		saa7146_dma_free(dev,q,buf); + +	if (VIDEOBUF_NEEDS_INIT == buf->vb.state) { +		struct videobuf_dmabuf *dma=videobuf_to_dma(&buf->vb); + +		buf->vb.width  = llength; +		buf->vb.height = lines; +		buf->vb.size   = size; +		buf->vb.field  = field;	// FIXME: check this + +		saa7146_pgtable_free(dev->pci, &buf->pt[2]); +		saa7146_pgtable_alloc(dev->pci, &buf->pt[2]); + +		err = videobuf_iolock(q,&buf->vb, NULL); +		if (err) +			goto oops; +		err = saa7146_pgtable_build_single(dev->pci, &buf->pt[2], +						 dma->sglist, dma->sglen); +		if (0 != err) +			return err; +	} +	buf->vb.state = VIDEOBUF_PREPARED; +	buf->activate = buffer_activate; + +	return 0; + + oops: +	DEB_VBI("error out\n"); +	saa7146_dma_free(dev,q,buf); + +	return err; +} + +static int buffer_setup(struct videobuf_queue *q, unsigned int *count, unsigned int *size) +{ +	int llength,lines; + +	lines   = 16 * 2 ; /* 2 fields */ +	llength = vbi_pixel_to_capture; + +	*size = lines * llength; +	*count = 2; + +	DEB_VBI("count:%d, size:%d\n", *count, *size); + +	return 0; +} + +static void buffer_queue(struct videobuf_queue *q, struct videobuf_buffer *vb) +{ +	struct file *file = q->priv_data; +	struct saa7146_fh *fh = file->private_data; +	struct saa7146_dev *dev = fh->dev; +	struct saa7146_vv *vv = dev->vv_data; +	struct saa7146_buf *buf = (struct saa7146_buf *)vb; + +	DEB_VBI("vb:%p\n", vb); +	saa7146_buffer_queue(dev, &vv->vbi_dmaq, buf); +} + +static void buffer_release(struct videobuf_queue *q, struct videobuf_buffer *vb) +{ +	struct file *file = q->priv_data; +	struct saa7146_fh *fh   = file->private_data; +	struct saa7146_dev *dev = fh->dev; +	struct saa7146_buf *buf = (struct saa7146_buf *)vb; + +	DEB_VBI("vb:%p\n", vb); +	saa7146_dma_free(dev,q,buf); +} + +static struct videobuf_queue_ops vbi_qops = { +	.buf_setup    = buffer_setup, +	.buf_prepare  = buffer_prepare, +	.buf_queue    = buffer_queue, +	.buf_release  = buffer_release, +}; + +/* ------------------------------------------------------------------ */ + +static void vbi_stop(struct saa7146_fh *fh, struct file *file) +{ +	struct saa7146_dev *dev = fh->dev; +	struct saa7146_vv *vv = dev->vv_data; +	unsigned long flags; +	DEB_VBI("dev:%p, fh:%p\n", dev, fh); + +	spin_lock_irqsave(&dev->slock,flags); + +	/* disable rps1  */ +	saa7146_write(dev, MC1, MASK_29); + +	/* disable rps1 irqs */ +	SAA7146_IER_DISABLE(dev, MASK_28); + +	/* shut down dma 3 transfers */ +	saa7146_write(dev, MC1, MASK_20); + +	if (vv->vbi_dmaq.curr) +		saa7146_buffer_finish(dev, &vv->vbi_dmaq, VIDEOBUF_DONE); + +	videobuf_queue_cancel(&fh->vbi_q); + +	vv->vbi_streaming = NULL; + +	del_timer(&vv->vbi_dmaq.timeout); +	del_timer(&vv->vbi_read_timeout); + +	spin_unlock_irqrestore(&dev->slock, flags); +} + +static void vbi_read_timeout(unsigned long data) +{ +	struct file *file = (struct file*)data; +	struct saa7146_fh *fh = file->private_data; +	struct saa7146_dev *dev = fh->dev; + +	DEB_VBI("dev:%p, fh:%p\n", dev, fh); + +	vbi_stop(fh, file); +} + +static void vbi_init(struct saa7146_dev *dev, struct saa7146_vv *vv) +{ +	DEB_VBI("dev:%p\n", dev); + +	INIT_LIST_HEAD(&vv->vbi_dmaq.queue); + +	init_timer(&vv->vbi_dmaq.timeout); +	vv->vbi_dmaq.timeout.function = saa7146_buffer_timeout; +	vv->vbi_dmaq.timeout.data     = (unsigned long)(&vv->vbi_dmaq); +	vv->vbi_dmaq.dev              = dev; + +	init_waitqueue_head(&vv->vbi_wq); +} + +static int vbi_open(struct saa7146_dev *dev, struct file *file) +{ +	struct saa7146_fh *fh = file->private_data; +	struct saa7146_vv *vv = fh->dev->vv_data; + +	u32 arbtr_ctrl	= saa7146_read(dev, PCI_BT_V1); +	int ret = 0; + +	DEB_VBI("dev:%p, fh:%p\n", dev, fh); + +	ret = saa7146_res_get(fh, RESOURCE_DMA3_BRS); +	if (0 == ret) { +		DEB_S("cannot get vbi RESOURCE_DMA3_BRS resource\n"); +		return -EBUSY; +	} + +	/* adjust arbitrition control for video dma 3 */ +	arbtr_ctrl &= ~0x1f0000; +	arbtr_ctrl |=  0x1d0000; +	saa7146_write(dev, PCI_BT_V1, arbtr_ctrl); +	saa7146_write(dev, MC2, (MASK_04|MASK_20)); + +	videobuf_queue_sg_init(&fh->vbi_q, &vbi_qops, +			    &dev->pci->dev, &dev->slock, +			    V4L2_BUF_TYPE_VBI_CAPTURE, +			    V4L2_FIELD_SEQ_TB, // FIXME: does this really work? +			    sizeof(struct saa7146_buf), +			    file, &dev->v4l2_lock); + +	vv->vbi_read_timeout.function = vbi_read_timeout; +	vv->vbi_read_timeout.data = (unsigned long)file; + +	/* initialize the brs */ +	if ( 0 != (SAA7146_USE_PORT_B_FOR_VBI & dev->ext_vv_data->flags)) { +		saa7146_write(dev, BRS_CTRL, MASK_30|MASK_29 | (7 << 19)); +	} else { +		saa7146_write(dev, BRS_CTRL, 0x00000001); + +		if (0 != (ret = vbi_workaround(dev))) { +			DEB_VBI("vbi workaround failed!\n"); +			/* return ret;*/ +		} +	} + +	/* upload brs register */ +	saa7146_write(dev, MC2, (MASK_08|MASK_24)); +	return 0; +} + +static void vbi_close(struct saa7146_dev *dev, struct file *file) +{ +	struct saa7146_fh *fh = file->private_data; +	struct saa7146_vv *vv = dev->vv_data; +	DEB_VBI("dev:%p, fh:%p\n", dev, fh); + +	if( fh == vv->vbi_streaming ) { +		vbi_stop(fh, file); +	} +	saa7146_res_free(fh, RESOURCE_DMA3_BRS); +} + +static void vbi_irq_done(struct saa7146_dev *dev, unsigned long status) +{ +	struct saa7146_vv *vv = dev->vv_data; +	spin_lock(&dev->slock); + +	if (vv->vbi_dmaq.curr) { +		DEB_VBI("dev:%p, curr:%p\n", dev, vv->vbi_dmaq.curr); +		/* this must be += 2, one count for each field */ +		vv->vbi_fieldcount+=2; +		vv->vbi_dmaq.curr->vb.field_count = vv->vbi_fieldcount; +		saa7146_buffer_finish(dev, &vv->vbi_dmaq, VIDEOBUF_DONE); +	} else { +		DEB_VBI("dev:%p\n", dev); +	} +	saa7146_buffer_next(dev, &vv->vbi_dmaq, 1); + +	spin_unlock(&dev->slock); +} + +static ssize_t vbi_read(struct file *file, char __user *data, size_t count, loff_t *ppos) +{ +	struct saa7146_fh *fh = file->private_data; +	struct saa7146_dev *dev = fh->dev; +	struct saa7146_vv *vv = dev->vv_data; +	ssize_t ret = 0; + +	DEB_VBI("dev:%p, fh:%p\n", dev, fh); + +	if( NULL == vv->vbi_streaming ) { +		// fixme: check if dma3 is available +		// fixme: activate vbi engine here if necessary. (really?) +		vv->vbi_streaming = fh; +	} + +	if( fh != vv->vbi_streaming ) { +		DEB_VBI("open %p is already using vbi capture\n", +			vv->vbi_streaming); +		return -EBUSY; +	} + +	mod_timer(&vv->vbi_read_timeout, jiffies+BUFFER_TIMEOUT); +	ret = videobuf_read_stream(&fh->vbi_q, data, count, ppos, 1, +				   file->f_flags & O_NONBLOCK); +/* +	printk("BASE_ODD3:      0x%08x\n", saa7146_read(dev, BASE_ODD3)); +	printk("BASE_EVEN3:     0x%08x\n", saa7146_read(dev, BASE_EVEN3)); +	printk("PROT_ADDR3:     0x%08x\n", saa7146_read(dev, PROT_ADDR3)); +	printk("PITCH3:         0x%08x\n", saa7146_read(dev, PITCH3)); +	printk("BASE_PAGE3:     0x%08x\n", saa7146_read(dev, BASE_PAGE3)); +	printk("NUM_LINE_BYTE3: 0x%08x\n", saa7146_read(dev, NUM_LINE_BYTE3)); +	printk("BRS_CTRL:       0x%08x\n", saa7146_read(dev, BRS_CTRL)); +*/ +	return ret; +} + +struct saa7146_use_ops saa7146_vbi_uops = { +	.init		= vbi_init, +	.open		= vbi_open, +	.release	= vbi_close, +	.irq_done	= vbi_irq_done, +	.read		= vbi_read, +}; diff --git a/drivers/media/common/saa7146/saa7146_video.c b/drivers/media/common/saa7146/saa7146_video.c new file mode 100644 index 00000000000..30779498c17 --- /dev/null +++ b/drivers/media/common/saa7146/saa7146_video.c @@ -0,0 +1,1309 @@ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <media/saa7146_vv.h> +#include <media/v4l2-event.h> +#include <media/v4l2-ctrls.h> +#include <linux/module.h> + +static int max_memory = 32; + +module_param(max_memory, int, 0644); +MODULE_PARM_DESC(max_memory, "maximum memory usage for capture buffers (default: 32Mb)"); + +#define IS_CAPTURE_ACTIVE(fh) \ +	(((vv->video_status & STATUS_CAPTURE) != 0) && (vv->video_fh == fh)) + +#define IS_OVERLAY_ACTIVE(fh) \ +	(((vv->video_status & STATUS_OVERLAY) != 0) && (vv->video_fh == fh)) + +/* format descriptions for capture and preview */ +static struct saa7146_format formats[] = { +	{ +		.name		= "RGB-8 (3-3-2)", +		.pixelformat	= V4L2_PIX_FMT_RGB332, +		.trans		= RGB08_COMPOSED, +		.depth		= 8, +		.flags		= 0, +	}, { +		.name		= "RGB-16 (5/B-6/G-5/R)", +		.pixelformat	= V4L2_PIX_FMT_RGB565, +		.trans		= RGB16_COMPOSED, +		.depth		= 16, +		.flags		= 0, +	}, { +		.name		= "RGB-24 (B-G-R)", +		.pixelformat	= V4L2_PIX_FMT_BGR24, +		.trans		= RGB24_COMPOSED, +		.depth		= 24, +		.flags		= 0, +	}, { +		.name		= "RGB-32 (B-G-R)", +		.pixelformat	= V4L2_PIX_FMT_BGR32, +		.trans		= RGB32_COMPOSED, +		.depth		= 32, +		.flags		= 0, +	}, { +		.name		= "RGB-32 (R-G-B)", +		.pixelformat	= V4L2_PIX_FMT_RGB32, +		.trans		= RGB32_COMPOSED, +		.depth		= 32, +		.flags		= 0, +		.swap		= 0x2, +	}, { +		.name		= "Greyscale-8", +		.pixelformat	= V4L2_PIX_FMT_GREY, +		.trans		= Y8, +		.depth		= 8, +		.flags		= 0, +	}, { +		.name		= "YUV 4:2:2 planar (Y-Cb-Cr)", +		.pixelformat	= V4L2_PIX_FMT_YUV422P, +		.trans		= YUV422_DECOMPOSED, +		.depth		= 16, +		.flags		= FORMAT_BYTE_SWAP|FORMAT_IS_PLANAR, +	}, { +		.name		= "YVU 4:2:0 planar (Y-Cb-Cr)", +		.pixelformat	= V4L2_PIX_FMT_YVU420, +		.trans		= YUV420_DECOMPOSED, +		.depth		= 12, +		.flags		= FORMAT_BYTE_SWAP|FORMAT_IS_PLANAR, +	}, { +		.name		= "YUV 4:2:0 planar (Y-Cb-Cr)", +		.pixelformat	= V4L2_PIX_FMT_YUV420, +		.trans		= YUV420_DECOMPOSED, +		.depth		= 12, +		.flags		= FORMAT_IS_PLANAR, +	}, { +		.name		= "YUV 4:2:2 (U-Y-V-Y)", +		.pixelformat	= V4L2_PIX_FMT_UYVY, +		.trans		= YUV422_COMPOSED, +		.depth		= 16, +		.flags		= 0, +	} +}; + +/* unfortunately, the saa7146 contains a bug which prevents it from doing on-the-fly byte swaps. +   due to this, it's impossible to provide additional *packed* formats, which are simply byte swapped +   (like V4L2_PIX_FMT_YUYV) ... 8-( */ + +static int NUM_FORMATS = sizeof(formats)/sizeof(struct saa7146_format); + +struct saa7146_format* saa7146_format_by_fourcc(struct saa7146_dev *dev, int fourcc) +{ +	int i, j = NUM_FORMATS; + +	for (i = 0; i < j; i++) { +		if (formats[i].pixelformat == fourcc) { +			return formats+i; +		} +	} + +	DEB_D("unknown pixelformat:'%4.4s'\n", (char *)&fourcc); +	return NULL; +} + +static int vidioc_try_fmt_vid_overlay(struct file *file, void *fh, struct v4l2_format *f); + +int saa7146_start_preview(struct saa7146_fh *fh) +{ +	struct saa7146_dev *dev = fh->dev; +	struct saa7146_vv *vv = dev->vv_data; +	struct v4l2_format fmt; +	int ret = 0, err = 0; + +	DEB_EE("dev:%p, fh:%p\n", dev, fh); + +	/* check if we have overlay information */ +	if (vv->ov.fh == NULL) { +		DEB_D("no overlay data available. try S_FMT first.\n"); +		return -EAGAIN; +	} + +	/* check if streaming capture is running */ +	if (IS_CAPTURE_ACTIVE(fh) != 0) { +		DEB_D("streaming capture is active\n"); +		return -EBUSY; +	} + +	/* check if overlay is running */ +	if (IS_OVERLAY_ACTIVE(fh) != 0) { +		if (vv->video_fh == fh) { +			DEB_D("overlay is already active\n"); +			return 0; +		} +		DEB_D("overlay is already active in another open\n"); +		return -EBUSY; +	} + +	if (0 == saa7146_res_get(fh, RESOURCE_DMA1_HPS|RESOURCE_DMA2_CLP)) { +		DEB_D("cannot get necessary overlay resources\n"); +		return -EBUSY; +	} + +	fmt.fmt.win = vv->ov.win; +	err = vidioc_try_fmt_vid_overlay(NULL, fh, &fmt); +	if (0 != err) { +		saa7146_res_free(vv->video_fh, RESOURCE_DMA1_HPS|RESOURCE_DMA2_CLP); +		return -EBUSY; +	} +	vv->ov.win = fmt.fmt.win; + +	DEB_D("%dx%d+%d+%d %s field=%s\n", +	      vv->ov.win.w.width, vv->ov.win.w.height, +	      vv->ov.win.w.left, vv->ov.win.w.top, +	      vv->ov_fmt->name, v4l2_field_names[vv->ov.win.field]); + +	if (0 != (ret = saa7146_enable_overlay(fh))) { +		DEB_D("enabling overlay failed: %d\n", ret); +		saa7146_res_free(vv->video_fh, RESOURCE_DMA1_HPS|RESOURCE_DMA2_CLP); +		return ret; +	} + +	vv->video_status = STATUS_OVERLAY; +	vv->video_fh = fh; + +	return 0; +} +EXPORT_SYMBOL_GPL(saa7146_start_preview); + +int saa7146_stop_preview(struct saa7146_fh *fh) +{ +	struct saa7146_dev *dev = fh->dev; +	struct saa7146_vv *vv = dev->vv_data; + +	DEB_EE("dev:%p, fh:%p\n", dev, fh); + +	/* check if streaming capture is running */ +	if (IS_CAPTURE_ACTIVE(fh) != 0) { +		DEB_D("streaming capture is active\n"); +		return -EBUSY; +	} + +	/* check if overlay is running at all */ +	if ((vv->video_status & STATUS_OVERLAY) == 0) { +		DEB_D("no active overlay\n"); +		return 0; +	} + +	if (vv->video_fh != fh) { +		DEB_D("overlay is active, but in another open\n"); +		return -EBUSY; +	} + +	vv->video_status = 0; +	vv->video_fh = NULL; + +	saa7146_disable_overlay(fh); + +	saa7146_res_free(fh, RESOURCE_DMA1_HPS|RESOURCE_DMA2_CLP); + +	return 0; +} +EXPORT_SYMBOL_GPL(saa7146_stop_preview); + +/********************************************************************************/ +/* common pagetable functions */ + +static int saa7146_pgtable_build(struct saa7146_dev *dev, struct saa7146_buf *buf) +{ +	struct pci_dev *pci = dev->pci; +	struct videobuf_dmabuf *dma=videobuf_to_dma(&buf->vb); +	struct scatterlist *list = dma->sglist; +	int length = dma->sglen; +	struct saa7146_format *sfmt = saa7146_format_by_fourcc(dev,buf->fmt->pixelformat); + +	DEB_EE("dev:%p, buf:%p, sg_len:%d\n", dev, buf, length); + +	if( 0 != IS_PLANAR(sfmt->trans)) { +		struct saa7146_pgtable *pt1 = &buf->pt[0]; +		struct saa7146_pgtable *pt2 = &buf->pt[1]; +		struct saa7146_pgtable *pt3 = &buf->pt[2]; +		__le32  *ptr1, *ptr2, *ptr3; +		__le32 fill; + +		int size = buf->fmt->width*buf->fmt->height; +		int i,p,m1,m2,m3,o1,o2; + +		switch( sfmt->depth ) { +			case 12: { +				/* create some offsets inside the page table */ +				m1 = ((size+PAGE_SIZE)/PAGE_SIZE)-1; +				m2 = ((size+(size/4)+PAGE_SIZE)/PAGE_SIZE)-1; +				m3 = ((size+(size/2)+PAGE_SIZE)/PAGE_SIZE)-1; +				o1 = size%PAGE_SIZE; +				o2 = (size+(size/4))%PAGE_SIZE; +				DEB_CAP("size:%d, m1:%d, m2:%d, m3:%d, o1:%d, o2:%d\n", +					size, m1, m2, m3, o1, o2); +				break; +			} +			case 16: { +				/* create some offsets inside the page table */ +				m1 = ((size+PAGE_SIZE)/PAGE_SIZE)-1; +				m2 = ((size+(size/2)+PAGE_SIZE)/PAGE_SIZE)-1; +				m3 = ((2*size+PAGE_SIZE)/PAGE_SIZE)-1; +				o1 = size%PAGE_SIZE; +				o2 = (size+(size/2))%PAGE_SIZE; +				DEB_CAP("size:%d, m1:%d, m2:%d, m3:%d, o1:%d, o2:%d\n", +					size, m1, m2, m3, o1, o2); +				break; +			} +			default: { +				return -1; +			} +		} + +		ptr1 = pt1->cpu; +		ptr2 = pt2->cpu; +		ptr3 = pt3->cpu; + +		/* walk all pages, copy all page addresses to ptr1 */ +		for (i = 0; i < length; i++, list++) { +			for (p = 0; p * 4096 < list->length; p++, ptr1++) { +				*ptr1 = cpu_to_le32(sg_dma_address(list) - list->offset); +			} +		} +/* +		ptr1 = pt1->cpu; +		for(j=0;j<40;j++) { +			printk("ptr1 %d: 0x%08x\n",j,ptr1[j]); +		} +*/ + +		/* if we have a user buffer, the first page may not be +		   aligned to a page boundary. */ +		pt1->offset = dma->sglist->offset; +		pt2->offset = pt1->offset+o1; +		pt3->offset = pt1->offset+o2; + +		/* create video-dma2 page table */ +		ptr1 = pt1->cpu; +		for(i = m1; i <= m2 ; i++, ptr2++) { +			*ptr2 = ptr1[i]; +		} +		fill = *(ptr2-1); +		for(;i<1024;i++,ptr2++) { +			*ptr2 = fill; +		} +		/* create video-dma3 page table */ +		ptr1 = pt1->cpu; +		for(i = m2; i <= m3; i++,ptr3++) { +			*ptr3 = ptr1[i]; +		} +		fill = *(ptr3-1); +		for(;i<1024;i++,ptr3++) { +			*ptr3 = fill; +		} +		/* finally: finish up video-dma1 page table */ +		ptr1 = pt1->cpu+m1; +		fill = pt1->cpu[m1]; +		for(i=m1;i<1024;i++,ptr1++) { +			*ptr1 = fill; +		} +/* +		ptr1 = pt1->cpu; +		ptr2 = pt2->cpu; +		ptr3 = pt3->cpu; +		for(j=0;j<40;j++) { +			printk("ptr1 %d: 0x%08x\n",j,ptr1[j]); +		} +		for(j=0;j<40;j++) { +			printk("ptr2 %d: 0x%08x\n",j,ptr2[j]); +		} +		for(j=0;j<40;j++) { +			printk("ptr3 %d: 0x%08x\n",j,ptr3[j]); +		} +*/ +	} else { +		struct saa7146_pgtable *pt = &buf->pt[0]; +		return saa7146_pgtable_build_single(pci, pt, list, length); +	} + +	return 0; +} + + +/********************************************************************************/ +/* file operations */ + +static int video_begin(struct saa7146_fh *fh) +{ +	struct saa7146_dev *dev = fh->dev; +	struct saa7146_vv *vv = dev->vv_data; +	struct saa7146_format *fmt = NULL; +	unsigned int resource; +	int ret = 0, err = 0; + +	DEB_EE("dev:%p, fh:%p\n", dev, fh); + +	if ((vv->video_status & STATUS_CAPTURE) != 0) { +		if (vv->video_fh == fh) { +			DEB_S("already capturing\n"); +			return 0; +		} +		DEB_S("already capturing in another open\n"); +		return -EBUSY; +	} + +	if ((vv->video_status & STATUS_OVERLAY) != 0) { +		DEB_S("warning: suspending overlay video for streaming capture\n"); +		vv->ov_suspend = vv->video_fh; +		err = saa7146_stop_preview(vv->video_fh); /* side effect: video_status is now 0, video_fh is NULL */ +		if (0 != err) { +			DEB_D("suspending video failed. aborting\n"); +			return err; +		} +	} + +	fmt = saa7146_format_by_fourcc(dev, vv->video_fmt.pixelformat); +	/* we need to have a valid format set here */ +	BUG_ON(NULL == fmt); + +	if (0 != (fmt->flags & FORMAT_IS_PLANAR)) { +		resource = RESOURCE_DMA1_HPS|RESOURCE_DMA2_CLP|RESOURCE_DMA3_BRS; +	} else { +		resource = RESOURCE_DMA1_HPS; +	} + +	ret = saa7146_res_get(fh, resource); +	if (0 == ret) { +		DEB_S("cannot get capture resource %d\n", resource); +		if (vv->ov_suspend != NULL) { +			saa7146_start_preview(vv->ov_suspend); +			vv->ov_suspend = NULL; +		} +		return -EBUSY; +	} + +	/* clear out beginning of streaming bit (rps register 0)*/ +	saa7146_write(dev, MC2, MASK_27 ); + +	/* enable rps0 irqs */ +	SAA7146_IER_ENABLE(dev, MASK_27); + +	vv->video_fh = fh; +	vv->video_status = STATUS_CAPTURE; + +	return 0; +} + +static int video_end(struct saa7146_fh *fh, struct file *file) +{ +	struct saa7146_dev *dev = fh->dev; +	struct saa7146_vv *vv = dev->vv_data; +	struct saa7146_format *fmt = NULL; +	unsigned long flags; +	unsigned int resource; +	u32 dmas = 0; +	DEB_EE("dev:%p, fh:%p\n", dev, fh); + +	if ((vv->video_status & STATUS_CAPTURE) != STATUS_CAPTURE) { +		DEB_S("not capturing\n"); +		return 0; +	} + +	if (vv->video_fh != fh) { +		DEB_S("capturing, but in another open\n"); +		return -EBUSY; +	} + +	fmt = saa7146_format_by_fourcc(dev, vv->video_fmt.pixelformat); +	/* we need to have a valid format set here */ +	BUG_ON(NULL == fmt); + +	if (0 != (fmt->flags & FORMAT_IS_PLANAR)) { +		resource = RESOURCE_DMA1_HPS|RESOURCE_DMA2_CLP|RESOURCE_DMA3_BRS; +		dmas = MASK_22 | MASK_21 | MASK_20; +	} else { +		resource = RESOURCE_DMA1_HPS; +		dmas = MASK_22; +	} +	spin_lock_irqsave(&dev->slock,flags); + +	/* disable rps0  */ +	saa7146_write(dev, MC1, MASK_28); + +	/* disable rps0 irqs */ +	SAA7146_IER_DISABLE(dev, MASK_27); + +	/* shut down all used video dma transfers */ +	saa7146_write(dev, MC1, dmas); + +	spin_unlock_irqrestore(&dev->slock, flags); + +	vv->video_fh = NULL; +	vv->video_status = 0; + +	saa7146_res_free(fh, resource); + +	if (vv->ov_suspend != NULL) { +		saa7146_start_preview(vv->ov_suspend); +		vv->ov_suspend = NULL; +	} + +	return 0; +} + +static int vidioc_querycap(struct file *file, void *fh, struct v4l2_capability *cap) +{ +	struct video_device *vdev = video_devdata(file); +	struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; + +	strcpy((char *)cap->driver, "saa7146 v4l2"); +	strlcpy((char *)cap->card, dev->ext->name, sizeof(cap->card)); +	sprintf((char *)cap->bus_info, "PCI:%s", pci_name(dev->pci)); +	cap->device_caps = +		V4L2_CAP_VIDEO_CAPTURE | +		V4L2_CAP_VIDEO_OVERLAY | +		V4L2_CAP_READWRITE | +		V4L2_CAP_STREAMING; +	cap->device_caps |= dev->ext_vv_data->capabilities; +	cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS; +	if (vdev->vfl_type == VFL_TYPE_GRABBER) +		cap->device_caps &= +			~(V4L2_CAP_VBI_CAPTURE | V4L2_CAP_SLICED_VBI_OUTPUT); +	else +		cap->device_caps &= +			~(V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_VIDEO_OVERLAY | V4L2_CAP_AUDIO); +	return 0; +} + +static int vidioc_g_fbuf(struct file *file, void *fh, struct v4l2_framebuffer *fb) +{ +	struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; +	struct saa7146_vv *vv = dev->vv_data; + +	*fb = vv->ov_fb; +	fb->capability = V4L2_FBUF_CAP_LIST_CLIPPING; +	fb->flags = V4L2_FBUF_FLAG_PRIMARY; +	return 0; +} + +static int vidioc_s_fbuf(struct file *file, void *fh, const struct v4l2_framebuffer *fb) +{ +	struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; +	struct saa7146_vv *vv = dev->vv_data; +	struct saa7146_format *fmt; + +	DEB_EE("VIDIOC_S_FBUF\n"); + +	if (!capable(CAP_SYS_ADMIN) && !capable(CAP_SYS_RAWIO)) +		return -EPERM; + +	/* check args */ +	fmt = saa7146_format_by_fourcc(dev, fb->fmt.pixelformat); +	if (NULL == fmt) +		return -EINVAL; + +	/* planar formats are not allowed for overlay video, clipping and video dma would clash */ +	if (fmt->flags & FORMAT_IS_PLANAR) +		DEB_S("planar pixelformat '%4.4s' not allowed for overlay\n", +		      (char *)&fmt->pixelformat); + +	/* check if overlay is running */ +	if (IS_OVERLAY_ACTIVE(fh) != 0) { +		if (vv->video_fh != fh) { +			DEB_D("refusing to change framebuffer informations while overlay is active in another open\n"); +			return -EBUSY; +		} +	} + +	/* ok, accept it */ +	vv->ov_fb = *fb; +	vv->ov_fmt = fmt; + +	if (vv->ov_fb.fmt.bytesperline < vv->ov_fb.fmt.width) { +		vv->ov_fb.fmt.bytesperline = vv->ov_fb.fmt.width * fmt->depth / 8; +		DEB_D("setting bytesperline to %d\n", vv->ov_fb.fmt.bytesperline); +	} +	return 0; +} + +static int vidioc_enum_fmt_vid_cap(struct file *file, void *fh, struct v4l2_fmtdesc *f) +{ +	if (f->index >= NUM_FORMATS) +		return -EINVAL; +	strlcpy((char *)f->description, formats[f->index].name, +			sizeof(f->description)); +	f->pixelformat = formats[f->index].pixelformat; +	return 0; +} + +int saa7146_s_ctrl(struct v4l2_ctrl *ctrl) +{ +	struct saa7146_dev *dev = container_of(ctrl->handler, +				struct saa7146_dev, ctrl_handler); +	struct saa7146_vv *vv = dev->vv_data; +	u32 val; + +	switch (ctrl->id) { +	case V4L2_CID_BRIGHTNESS: +		val = saa7146_read(dev, BCS_CTRL); +		val &= 0x00ffffff; +		val |= (ctrl->val << 24); +		saa7146_write(dev, BCS_CTRL, val); +		saa7146_write(dev, MC2, MASK_22 | MASK_06); +		break; + +	case V4L2_CID_CONTRAST: +		val = saa7146_read(dev, BCS_CTRL); +		val &= 0xff00ffff; +		val |= (ctrl->val << 16); +		saa7146_write(dev, BCS_CTRL, val); +		saa7146_write(dev, MC2, MASK_22 | MASK_06); +		break; + +	case V4L2_CID_SATURATION: +		val = saa7146_read(dev, BCS_CTRL); +		val &= 0xffffff00; +		val |= (ctrl->val << 0); +		saa7146_write(dev, BCS_CTRL, val); +		saa7146_write(dev, MC2, MASK_22 | MASK_06); +		break; + +	case V4L2_CID_HFLIP: +		/* fixme: we can support changing VFLIP and HFLIP here... */ +		if ((vv->video_status & STATUS_CAPTURE)) +			return -EBUSY; +		vv->hflip = ctrl->val; +		break; + +	case V4L2_CID_VFLIP: +		if ((vv->video_status & STATUS_CAPTURE)) +			return -EBUSY; +		vv->vflip = ctrl->val; +		break; + +	default: +		return -EINVAL; +	} + +	if ((vv->video_status & STATUS_OVERLAY) != 0) { /* CHECK: && (vv->video_fh == fh)) */ +		struct saa7146_fh *fh = vv->video_fh; + +		saa7146_stop_preview(fh); +		saa7146_start_preview(fh); +	} +	return 0; +} + +static int vidioc_g_parm(struct file *file, void *fh, +		struct v4l2_streamparm *parm) +{ +	struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; +	struct saa7146_vv *vv = dev->vv_data; + +	if (parm->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) +		return -EINVAL; +	parm->parm.capture.readbuffers = 1; +	v4l2_video_std_frame_period(vv->standard->id, +				    &parm->parm.capture.timeperframe); +	return 0; +} + +static int vidioc_g_fmt_vid_cap(struct file *file, void *fh, struct v4l2_format *f) +{ +	struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; +	struct saa7146_vv *vv = dev->vv_data; + +	f->fmt.pix = vv->video_fmt; +	return 0; +} + +static int vidioc_g_fmt_vid_overlay(struct file *file, void *fh, struct v4l2_format *f) +{ +	struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; +	struct saa7146_vv *vv = dev->vv_data; + +	f->fmt.win = vv->ov.win; +	return 0; +} + +static int vidioc_g_fmt_vbi_cap(struct file *file, void *fh, struct v4l2_format *f) +{ +	struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; +	struct saa7146_vv *vv = dev->vv_data; + +	f->fmt.vbi = vv->vbi_fmt; +	return 0; +} + +static int vidioc_try_fmt_vid_cap(struct file *file, void *fh, struct v4l2_format *f) +{ +	struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; +	struct saa7146_vv *vv = dev->vv_data; +	struct saa7146_format *fmt; +	enum v4l2_field field; +	int maxw, maxh; +	int calc_bpl; + +	DEB_EE("V4L2_BUF_TYPE_VIDEO_CAPTURE: dev:%p, fh:%p\n", dev, fh); + +	fmt = saa7146_format_by_fourcc(dev, f->fmt.pix.pixelformat); +	if (NULL == fmt) +		return -EINVAL; + +	field = f->fmt.pix.field; +	maxw  = vv->standard->h_max_out; +	maxh  = vv->standard->v_max_out; + +	if (V4L2_FIELD_ANY == field) { +		field = (f->fmt.pix.height > maxh / 2) +			? V4L2_FIELD_INTERLACED +			: V4L2_FIELD_BOTTOM; +	} +	switch (field) { +	case V4L2_FIELD_ALTERNATE: +		vv->last_field = V4L2_FIELD_TOP; +		maxh = maxh / 2; +		break; +	case V4L2_FIELD_TOP: +	case V4L2_FIELD_BOTTOM: +		vv->last_field = V4L2_FIELD_INTERLACED; +		maxh = maxh / 2; +		break; +	case V4L2_FIELD_INTERLACED: +		vv->last_field = V4L2_FIELD_INTERLACED; +		break; +	default: +		DEB_D("no known field mode '%d'\n", field); +		return -EINVAL; +	} + +	f->fmt.pix.field = field; +	f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M; +	if (f->fmt.pix.width > maxw) +		f->fmt.pix.width = maxw; +	if (f->fmt.pix.height > maxh) +		f->fmt.pix.height = maxh; + +	calc_bpl = (f->fmt.pix.width * fmt->depth) / 8; + +	if (f->fmt.pix.bytesperline < calc_bpl) +		f->fmt.pix.bytesperline = calc_bpl; + +	if (f->fmt.pix.bytesperline > (2 * PAGE_SIZE * fmt->depth) / 8) /* arbitrary constraint */ +		f->fmt.pix.bytesperline = calc_bpl; + +	f->fmt.pix.sizeimage = f->fmt.pix.bytesperline * f->fmt.pix.height; +	DEB_D("w:%d, h:%d, bytesperline:%d, sizeimage:%d\n", +	      f->fmt.pix.width, f->fmt.pix.height, +	      f->fmt.pix.bytesperline, f->fmt.pix.sizeimage); + +	return 0; +} + + +static int vidioc_try_fmt_vid_overlay(struct file *file, void *fh, struct v4l2_format *f) +{ +	struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; +	struct saa7146_vv *vv = dev->vv_data; +	struct v4l2_window *win = &f->fmt.win; +	enum v4l2_field field; +	int maxw, maxh; + +	DEB_EE("dev:%p\n", dev); + +	if (NULL == vv->ov_fb.base) { +		DEB_D("no fb base set\n"); +		return -EINVAL; +	} +	if (NULL == vv->ov_fmt) { +		DEB_D("no fb fmt set\n"); +		return -EINVAL; +	} +	if (win->w.width < 48 || win->w.height < 32) { +		DEB_D("min width/height. (%d,%d)\n", +		      win->w.width, win->w.height); +		return -EINVAL; +	} +	if (win->clipcount > 16) { +		DEB_D("clipcount too big\n"); +		return -EINVAL; +	} + +	field = win->field; +	maxw  = vv->standard->h_max_out; +	maxh  = vv->standard->v_max_out; + +	if (V4L2_FIELD_ANY == field) { +		field = (win->w.height > maxh / 2) +			? V4L2_FIELD_INTERLACED +			: V4L2_FIELD_TOP; +		} +	switch (field) { +	case V4L2_FIELD_TOP: +	case V4L2_FIELD_BOTTOM: +	case V4L2_FIELD_ALTERNATE: +		maxh = maxh / 2; +		break; +	case V4L2_FIELD_INTERLACED: +		break; +	default: +		DEB_D("no known field mode '%d'\n", field); +		return -EINVAL; +	} + +	win->field = field; +	if (win->w.width > maxw) +		win->w.width = maxw; +	if (win->w.height > maxh) +		win->w.height = maxh; + +	return 0; +} + +static int vidioc_s_fmt_vid_cap(struct file *file, void *__fh, struct v4l2_format *f) +{ +	struct saa7146_fh *fh = __fh; +	struct saa7146_dev *dev = fh->dev; +	struct saa7146_vv *vv = dev->vv_data; +	int err; + +	DEB_EE("V4L2_BUF_TYPE_VIDEO_CAPTURE: dev:%p, fh:%p\n", dev, fh); +	if (IS_CAPTURE_ACTIVE(fh) != 0) { +		DEB_EE("streaming capture is active\n"); +		return -EBUSY; +	} +	err = vidioc_try_fmt_vid_cap(file, fh, f); +	if (0 != err) +		return err; +	vv->video_fmt = f->fmt.pix; +	DEB_EE("set to pixelformat '%4.4s'\n", +	       (char *)&vv->video_fmt.pixelformat); +	return 0; +} + +static int vidioc_s_fmt_vid_overlay(struct file *file, void *__fh, struct v4l2_format *f) +{ +	struct saa7146_fh *fh = __fh; +	struct saa7146_dev *dev = fh->dev; +	struct saa7146_vv *vv = dev->vv_data; +	int err; + +	DEB_EE("V4L2_BUF_TYPE_VIDEO_OVERLAY: dev:%p, fh:%p\n", dev, fh); +	err = vidioc_try_fmt_vid_overlay(file, fh, f); +	if (0 != err) +		return err; +	vv->ov.win    = f->fmt.win; +	vv->ov.nclips = f->fmt.win.clipcount; +	if (vv->ov.nclips > 16) +		vv->ov.nclips = 16; +	if (copy_from_user(vv->ov.clips, f->fmt.win.clips, +				sizeof(struct v4l2_clip) * vv->ov.nclips)) { +		return -EFAULT; +	} + +	/* vv->ov.fh is used to indicate that we have valid overlay informations, too */ +	vv->ov.fh = fh; + +	/* check if our current overlay is active */ +	if (IS_OVERLAY_ACTIVE(fh) != 0) { +		saa7146_stop_preview(fh); +		saa7146_start_preview(fh); +	} +	return 0; +} + +static int vidioc_g_std(struct file *file, void *fh, v4l2_std_id *norm) +{ +	struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; +	struct saa7146_vv *vv = dev->vv_data; + +	*norm = vv->standard->id; +	return 0; +} + +	/* the saa7146 supfhrts (used in conjunction with the saa7111a for example) +	   PAL / NTSC / SECAM. if your hardware does not (or does more) +	   -- override this function in your extension */ +/* +	case VIDIOC_ENUMSTD: +	{ +		struct v4l2_standard *e = arg; +		if (e->index < 0 ) +			return -EINVAL; +		if( e->index < dev->ext_vv_data->num_stds ) { +			DEB_EE("VIDIOC_ENUMSTD: index:%d\n", e->index); +			v4l2_video_std_construct(e, dev->ext_vv_data->stds[e->index].id, dev->ext_vv_data->stds[e->index].name); +			return 0; +		} +		return -EINVAL; +	} +	*/ + +static int vidioc_s_std(struct file *file, void *fh, v4l2_std_id id) +{ +	struct saa7146_dev *dev = ((struct saa7146_fh *)fh)->dev; +	struct saa7146_vv *vv = dev->vv_data; +	int found = 0; +	int err, i; + +	DEB_EE("VIDIOC_S_STD\n"); + +	if ((vv->video_status & STATUS_CAPTURE) == STATUS_CAPTURE) { +		DEB_D("cannot change video standard while streaming capture is active\n"); +		return -EBUSY; +	} + +	if ((vv->video_status & STATUS_OVERLAY) != 0) { +		vv->ov_suspend = vv->video_fh; +		err = saa7146_stop_preview(vv->video_fh); /* side effect: video_status is now 0, video_fh is NULL */ +		if (0 != err) { +			DEB_D("suspending video failed. aborting\n"); +			return err; +		} +	} + +	for (i = 0; i < dev->ext_vv_data->num_stds; i++) +		if (id & dev->ext_vv_data->stds[i].id) +			break; +	if (i != dev->ext_vv_data->num_stds) { +		vv->standard = &dev->ext_vv_data->stds[i]; +		if (NULL != dev->ext_vv_data->std_callback) +			dev->ext_vv_data->std_callback(dev, vv->standard); +		found = 1; +	} + +	if (vv->ov_suspend != NULL) { +		saa7146_start_preview(vv->ov_suspend); +		vv->ov_suspend = NULL; +	} + +	if (!found) { +		DEB_EE("VIDIOC_S_STD: standard not found\n"); +		return -EINVAL; +	} + +	DEB_EE("VIDIOC_S_STD: set to standard to '%s'\n", vv->standard->name); +	return 0; +} + +static int vidioc_overlay(struct file *file, void *fh, unsigned int on) +{ +	int err; + +	DEB_D("VIDIOC_OVERLAY on:%d\n", on); +	if (on) +		err = saa7146_start_preview(fh); +	else +		err = saa7146_stop_preview(fh); +	return err; +} + +static int vidioc_reqbufs(struct file *file, void *__fh, struct v4l2_requestbuffers *b) +{ +	struct saa7146_fh *fh = __fh; + +	if (b->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) +		return videobuf_reqbufs(&fh->video_q, b); +	if (b->type == V4L2_BUF_TYPE_VBI_CAPTURE) +		return videobuf_reqbufs(&fh->vbi_q, b); +	return -EINVAL; +} + +static int vidioc_querybuf(struct file *file, void *__fh, struct v4l2_buffer *buf) +{ +	struct saa7146_fh *fh = __fh; + +	if (buf->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) +		return videobuf_querybuf(&fh->video_q, buf); +	if (buf->type == V4L2_BUF_TYPE_VBI_CAPTURE) +		return videobuf_querybuf(&fh->vbi_q, buf); +	return -EINVAL; +} + +static int vidioc_qbuf(struct file *file, void *__fh, struct v4l2_buffer *buf) +{ +	struct saa7146_fh *fh = __fh; + +	if (buf->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) +		return videobuf_qbuf(&fh->video_q, buf); +	if (buf->type == V4L2_BUF_TYPE_VBI_CAPTURE) +		return videobuf_qbuf(&fh->vbi_q, buf); +	return -EINVAL; +} + +static int vidioc_dqbuf(struct file *file, void *__fh, struct v4l2_buffer *buf) +{ +	struct saa7146_fh *fh = __fh; + +	if (buf->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) +		return videobuf_dqbuf(&fh->video_q, buf, file->f_flags & O_NONBLOCK); +	if (buf->type == V4L2_BUF_TYPE_VBI_CAPTURE) +		return videobuf_dqbuf(&fh->vbi_q, buf, file->f_flags & O_NONBLOCK); +	return -EINVAL; +} + +static int vidioc_streamon(struct file *file, void *__fh, enum v4l2_buf_type type) +{ +	struct saa7146_fh *fh = __fh; +	int err; + +	DEB_D("VIDIOC_STREAMON, type:%d\n", type); + +	err = video_begin(fh); +	if (err) +		return err; +	if (type == V4L2_BUF_TYPE_VIDEO_CAPTURE) +		return videobuf_streamon(&fh->video_q); +	if (type == V4L2_BUF_TYPE_VBI_CAPTURE) +		return videobuf_streamon(&fh->vbi_q); +	return -EINVAL; +} + +static int vidioc_streamoff(struct file *file, void *__fh, enum v4l2_buf_type type) +{ +	struct saa7146_fh *fh = __fh; +	struct saa7146_dev *dev = fh->dev; +	struct saa7146_vv *vv = dev->vv_data; +	int err; + +	DEB_D("VIDIOC_STREAMOFF, type:%d\n", type); + +	/* ugly: we need to copy some checks from video_end(), +	   because videobuf_streamoff() relies on the capture running. +	   check and fix this */ +	if ((vv->video_status & STATUS_CAPTURE) != STATUS_CAPTURE) { +		DEB_S("not capturing\n"); +		return 0; +	} + +	if (vv->video_fh != fh) { +		DEB_S("capturing, but in another open\n"); +		return -EBUSY; +	} + +	err = -EINVAL; +	if (type == V4L2_BUF_TYPE_VIDEO_CAPTURE) +		err = videobuf_streamoff(&fh->video_q); +	else if (type == V4L2_BUF_TYPE_VBI_CAPTURE) +		err = videobuf_streamoff(&fh->vbi_q); +	if (0 != err) { +		DEB_D("warning: videobuf_streamoff() failed\n"); +		video_end(fh, file); +	} else { +		err = video_end(fh, file); +	} +	return err; +} + +const struct v4l2_ioctl_ops saa7146_video_ioctl_ops = { +	.vidioc_querycap             = vidioc_querycap, +	.vidioc_enum_fmt_vid_cap     = vidioc_enum_fmt_vid_cap, +	.vidioc_enum_fmt_vid_overlay = vidioc_enum_fmt_vid_cap, +	.vidioc_g_fmt_vid_cap        = vidioc_g_fmt_vid_cap, +	.vidioc_try_fmt_vid_cap      = vidioc_try_fmt_vid_cap, +	.vidioc_s_fmt_vid_cap        = vidioc_s_fmt_vid_cap, +	.vidioc_g_fmt_vid_overlay    = vidioc_g_fmt_vid_overlay, +	.vidioc_try_fmt_vid_overlay  = vidioc_try_fmt_vid_overlay, +	.vidioc_s_fmt_vid_overlay    = vidioc_s_fmt_vid_overlay, + +	.vidioc_overlay 	     = vidioc_overlay, +	.vidioc_g_fbuf  	     = vidioc_g_fbuf, +	.vidioc_s_fbuf  	     = vidioc_s_fbuf, +	.vidioc_reqbufs              = vidioc_reqbufs, +	.vidioc_querybuf             = vidioc_querybuf, +	.vidioc_qbuf                 = vidioc_qbuf, +	.vidioc_dqbuf                = vidioc_dqbuf, +	.vidioc_g_std                = vidioc_g_std, +	.vidioc_s_std                = vidioc_s_std, +	.vidioc_streamon             = vidioc_streamon, +	.vidioc_streamoff            = vidioc_streamoff, +	.vidioc_g_parm 		     = vidioc_g_parm, +	.vidioc_subscribe_event      = v4l2_ctrl_subscribe_event, +	.vidioc_unsubscribe_event    = v4l2_event_unsubscribe, +}; + +const struct v4l2_ioctl_ops saa7146_vbi_ioctl_ops = { +	.vidioc_querycap             = vidioc_querycap, +	.vidioc_g_fmt_vbi_cap        = vidioc_g_fmt_vbi_cap, + +	.vidioc_reqbufs              = vidioc_reqbufs, +	.vidioc_querybuf             = vidioc_querybuf, +	.vidioc_qbuf                 = vidioc_qbuf, +	.vidioc_dqbuf                = vidioc_dqbuf, +	.vidioc_g_std                = vidioc_g_std, +	.vidioc_s_std                = vidioc_s_std, +	.vidioc_streamon             = vidioc_streamon, +	.vidioc_streamoff            = vidioc_streamoff, +	.vidioc_g_parm		     = vidioc_g_parm, +	.vidioc_subscribe_event      = v4l2_ctrl_subscribe_event, +	.vidioc_unsubscribe_event    = v4l2_event_unsubscribe, +}; + +/*********************************************************************************/ +/* buffer handling functions                                                  */ + +static int buffer_activate (struct saa7146_dev *dev, +		     struct saa7146_buf *buf, +		     struct saa7146_buf *next) +{ +	struct saa7146_vv *vv = dev->vv_data; + +	buf->vb.state = VIDEOBUF_ACTIVE; +	saa7146_set_capture(dev,buf,next); + +	mod_timer(&vv->video_dmaq.timeout, jiffies+BUFFER_TIMEOUT); +	return 0; +} + +static void release_all_pagetables(struct saa7146_dev *dev, struct saa7146_buf *buf) +{ +	saa7146_pgtable_free(dev->pci, &buf->pt[0]); +	saa7146_pgtable_free(dev->pci, &buf->pt[1]); +	saa7146_pgtable_free(dev->pci, &buf->pt[2]); +} + +static int buffer_prepare(struct videobuf_queue *q, +			  struct videobuf_buffer *vb, enum v4l2_field field) +{ +	struct file *file = q->priv_data; +	struct saa7146_fh *fh = file->private_data; +	struct saa7146_dev *dev = fh->dev; +	struct saa7146_vv *vv = dev->vv_data; +	struct saa7146_buf *buf = (struct saa7146_buf *)vb; +	int size,err = 0; + +	DEB_CAP("vbuf:%p\n", vb); + +	/* sanity checks */ +	if (vv->video_fmt.width  < 48 || +	    vv->video_fmt.height < 32 || +	    vv->video_fmt.width  > vv->standard->h_max_out || +	    vv->video_fmt.height > vv->standard->v_max_out) { +		DEB_D("w (%d) / h (%d) out of bounds\n", +		      vv->video_fmt.width, vv->video_fmt.height); +		return -EINVAL; +	} + +	size = vv->video_fmt.sizeimage; +	if (0 != buf->vb.baddr && buf->vb.bsize < size) { +		DEB_D("size mismatch\n"); +		return -EINVAL; +	} + +	DEB_CAP("buffer_prepare [size=%dx%d,bytes=%d,fields=%s]\n", +		vv->video_fmt.width, vv->video_fmt.height, +		size, v4l2_field_names[vv->video_fmt.field]); +	if (buf->vb.width  != vv->video_fmt.width  || +	    buf->vb.bytesperline != vv->video_fmt.bytesperline || +	    buf->vb.height != vv->video_fmt.height || +	    buf->vb.size   != size || +	    buf->vb.field  != field      || +	    buf->vb.field  != vv->video_fmt.field  || +	    buf->fmt       != &vv->video_fmt) { +		saa7146_dma_free(dev,q,buf); +	} + +	if (VIDEOBUF_NEEDS_INIT == buf->vb.state) { +		struct saa7146_format *sfmt; + +		buf->vb.bytesperline  = vv->video_fmt.bytesperline; +		buf->vb.width  = vv->video_fmt.width; +		buf->vb.height = vv->video_fmt.height; +		buf->vb.size   = size; +		buf->vb.field  = field; +		buf->fmt       = &vv->video_fmt; +		buf->vb.field  = vv->video_fmt.field; + +		sfmt = saa7146_format_by_fourcc(dev,buf->fmt->pixelformat); + +		release_all_pagetables(dev, buf); +		if( 0 != IS_PLANAR(sfmt->trans)) { +			saa7146_pgtable_alloc(dev->pci, &buf->pt[0]); +			saa7146_pgtable_alloc(dev->pci, &buf->pt[1]); +			saa7146_pgtable_alloc(dev->pci, &buf->pt[2]); +		} else { +			saa7146_pgtable_alloc(dev->pci, &buf->pt[0]); +		} + +		err = videobuf_iolock(q,&buf->vb, &vv->ov_fb); +		if (err) +			goto oops; +		err = saa7146_pgtable_build(dev,buf); +		if (err) +			goto oops; +	} +	buf->vb.state = VIDEOBUF_PREPARED; +	buf->activate = buffer_activate; + +	return 0; + + oops: +	DEB_D("error out\n"); +	saa7146_dma_free(dev,q,buf); + +	return err; +} + +static int buffer_setup(struct videobuf_queue *q, unsigned int *count, unsigned int *size) +{ +	struct file *file = q->priv_data; +	struct saa7146_fh *fh = file->private_data; +	struct saa7146_vv *vv = fh->dev->vv_data; + +	if (0 == *count || *count > MAX_SAA7146_CAPTURE_BUFFERS) +		*count = MAX_SAA7146_CAPTURE_BUFFERS; + +	*size = vv->video_fmt.sizeimage; + +	/* check if we exceed the "max_memory" parameter */ +	if( (*count * *size) > (max_memory*1048576) ) { +		*count = (max_memory*1048576) / *size; +	} + +	DEB_CAP("%d buffers, %d bytes each\n", *count, *size); + +	return 0; +} + +static void buffer_queue(struct videobuf_queue *q, struct videobuf_buffer *vb) +{ +	struct file *file = q->priv_data; +	struct saa7146_fh *fh = file->private_data; +	struct saa7146_dev *dev = fh->dev; +	struct saa7146_vv *vv = dev->vv_data; +	struct saa7146_buf *buf = (struct saa7146_buf *)vb; + +	DEB_CAP("vbuf:%p\n", vb); +	saa7146_buffer_queue(fh->dev, &vv->video_dmaq, buf); +} + +static void buffer_release(struct videobuf_queue *q, struct videobuf_buffer *vb) +{ +	struct file *file = q->priv_data; +	struct saa7146_fh *fh = file->private_data; +	struct saa7146_dev *dev = fh->dev; +	struct saa7146_buf *buf = (struct saa7146_buf *)vb; + +	DEB_CAP("vbuf:%p\n", vb); + +	saa7146_dma_free(dev,q,buf); + +	release_all_pagetables(dev, buf); +} + +static struct videobuf_queue_ops video_qops = { +	.buf_setup    = buffer_setup, +	.buf_prepare  = buffer_prepare, +	.buf_queue    = buffer_queue, +	.buf_release  = buffer_release, +}; + +/********************************************************************************/ +/* file operations */ + +static void video_init(struct saa7146_dev *dev, struct saa7146_vv *vv) +{ +	INIT_LIST_HEAD(&vv->video_dmaq.queue); + +	init_timer(&vv->video_dmaq.timeout); +	vv->video_dmaq.timeout.function = saa7146_buffer_timeout; +	vv->video_dmaq.timeout.data     = (unsigned long)(&vv->video_dmaq); +	vv->video_dmaq.dev              = dev; + +	/* set some default values */ +	vv->standard = &dev->ext_vv_data->stds[0]; + +	/* FIXME: what's this? */ +	vv->current_hps_source = SAA7146_HPS_SOURCE_PORT_A; +	vv->current_hps_sync = SAA7146_HPS_SYNC_PORT_A; +} + + +static int video_open(struct saa7146_dev *dev, struct file *file) +{ +	struct saa7146_fh *fh = file->private_data; + +	videobuf_queue_sg_init(&fh->video_q, &video_qops, +			    &dev->pci->dev, &dev->slock, +			    V4L2_BUF_TYPE_VIDEO_CAPTURE, +			    V4L2_FIELD_INTERLACED, +			    sizeof(struct saa7146_buf), +			    file, &dev->v4l2_lock); + +	return 0; +} + + +static void video_close(struct saa7146_dev *dev, struct file *file) +{ +	struct saa7146_fh *fh = file->private_data; +	struct saa7146_vv *vv = dev->vv_data; +	struct videobuf_queue *q = &fh->video_q; + +	if (IS_CAPTURE_ACTIVE(fh) != 0) +		video_end(fh, file); +	else if (IS_OVERLAY_ACTIVE(fh) != 0) +		saa7146_stop_preview(fh); + +	videobuf_stop(q); +	/* hmm, why is this function declared void? */ +} + + +static void video_irq_done(struct saa7146_dev *dev, unsigned long st) +{ +	struct saa7146_vv *vv = dev->vv_data; +	struct saa7146_dmaqueue *q = &vv->video_dmaq; + +	spin_lock(&dev->slock); +	DEB_CAP("called\n"); + +	/* only finish the buffer if we have one... */ +	if( NULL != q->curr ) { +		saa7146_buffer_finish(dev,q,VIDEOBUF_DONE); +	} +	saa7146_buffer_next(dev,q,0); + +	spin_unlock(&dev->slock); +} + +static ssize_t video_read(struct file *file, char __user *data, size_t count, loff_t *ppos) +{ +	struct saa7146_fh *fh = file->private_data; +	struct saa7146_dev *dev = fh->dev; +	struct saa7146_vv *vv = dev->vv_data; +	ssize_t ret = 0; + +	DEB_EE("called\n"); + +	if ((vv->video_status & STATUS_CAPTURE) != 0) { +		/* fixme: should we allow read() captures while streaming capture? */ +		if (vv->video_fh == fh) { +			DEB_S("already capturing\n"); +			return -EBUSY; +		} +		DEB_S("already capturing in another open\n"); +		return -EBUSY; +	} + +	ret = video_begin(fh); +	if( 0 != ret) { +		goto out; +	} + +	ret = videobuf_read_one(&fh->video_q , data, count, ppos, +				file->f_flags & O_NONBLOCK); +	if (ret != 0) { +		video_end(fh, file); +	} else { +		ret = video_end(fh, file); +	} +out: +	/* restart overlay if it was active before */ +	if (vv->ov_suspend != NULL) { +		saa7146_start_preview(vv->ov_suspend); +		vv->ov_suspend = NULL; +	} + +	return ret; +} + +struct saa7146_use_ops saa7146_video_uops = { +	.init = video_init, +	.open = video_open, +	.release = video_close, +	.irq_done = video_irq_done, +	.read = video_read, +};  | 
