Commit c8639e86 authored by Alan Cox's avatar Alan Cox Committed by Linus Torvalds

[PATCH] add drivers/media/common for mixed dvb/analog device stuff

(Martin Hunold)
parent 8826bb90
config VIDEO_SAA7146
tristate
default y if DVB_AV7110=y || DVB_BUDGET=y || DVB_BUDGET_AV=y || VIDEO_MXB=y || VIDEO_DPC=y
default m if DVB_AV7110=m || DVB_BUDGET=m || DVB_BUDGET_AV=m || VIDEO_MXB=m || VIDEO_DPC=m
depends on VIDEO_DEV && PCI
config VIDEO_VIDEOBUF
tristate
default y if VIDEO_SAA7134=y || VIDEO_BT848=y || VIDEO_SAA7146=y
default m if VIDEO_SAA7134=m || VIDEO_BT848=m || VIDEO_SAA7146=m
depends on VIDEO_DEV
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 saa7146_vv.o
/*
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.
*/
#include <media/saa7146.h>
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,51)
#define KBUILD_MODNAME saa7146
#endif
/* global variables */
struct list_head saa7146_devices;
struct semaphore saa7146_devices_lock;
static int initialized = 0;
int saa7146_num = 0;
unsigned int saa7146_debug = 0;
MODULE_PARM(saa7146_debug,"i");
MODULE_PARM_DESC(saa7146_debug, "debug level (default: 0)");
#if 0
static void dump_registers(struct saa7146_dev* dev)
{
int i = 0;
INFO((" @ %li jiffies:\n",jiffies));
for(i = 0; i <= 0x148; i+=4) {
printk("0x%03x: 0x%08x\n",i,saa7146_read(dev,i));
}
}
#endif
/****************************************************************************
* general helper functions
****************************************************************************/
/* this is videobuf_vmalloc_to_sg() from video-buf.c */
static
struct scatterlist* vmalloc_to_sg(unsigned char *virt, int nr_pages)
{
struct scatterlist *sglist;
struct page *pg;
int i;
sglist = kmalloc(sizeof(struct scatterlist)*nr_pages, GFP_KERNEL);
if (NULL == sglist)
return NULL;
memset(sglist,0,sizeof(struct scatterlist)*nr_pages);
for (i = 0; i < nr_pages; i++, virt += PAGE_SIZE) {
pg = vmalloc_to_page(virt);
if (NULL == pg)
goto err;
if (PageHighMem(pg))
BUG();
sglist[i].page = pg;
sglist[i].length = PAGE_SIZE;
}
return sglist;
err:
kfree(sglist);
return NULL;
}
/********************************************************************************/
/* common page table functions */
#define SAA7146_PGTABLE_SIZE 4096
char *saa7146_vmalloc_build_pgtable(struct pci_dev *pci, long length, struct saa7146_pgtable *pt)
{
struct scatterlist *slist = NULL;
int pages = (length+PAGE_SIZE-1)/PAGE_SIZE;
char *mem = vmalloc(length);
int slen = 0;
if (NULL == mem) {
return NULL;
}
if (!(slist = vmalloc_to_sg(mem, pages))) {
vfree(mem);
return NULL;
}
if (saa7146_pgtable_alloc(pci, pt)) {
kfree(slist);
vfree(mem);
return NULL;
}
slen = pci_map_sg(pci,slist,pages,PCI_DMA_FROMDEVICE);
saa7146_pgtable_build_single(pci, pt, slist, slen);
/* fixme: here's a memory leak: slist never gets freed by any other
function ...*/
return mem;
}
void saa7146_pgtable_free(struct pci_dev *pci, struct saa7146_pgtable *pt)
{
//fm DEB_EE(("pci:%p, pt:%p\n",pci,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)
{
u32 *cpu;
dma_addr_t dma_addr;
//fm DEB_EE(("pci:%p, pt:%p\n",pci,pt));
cpu = pci_alloc_consistent(pci, SAA7146_PGTABLE_SIZE, &dma_addr);
if (NULL == cpu) {
//fm ERR(("pci_alloc_consistent() failed."));
return -ENOMEM;
}
pt->size = SAA7146_PGTABLE_SIZE;
pt->cpu = cpu;
pt->dma = dma_addr;
return 0;
}
void saa7146_pgtable_build_single(struct pci_dev *pci, struct saa7146_pgtable *pt, struct scatterlist *list, int length )
{
u32 *ptr, fill;
int i,p;
//fm DEB_EE(("pci:%p, pt:%p, sl:%p, len:%d\n",pci,pt,list,length));
/* 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 < length; i++, list++) {
for (p = 0; p * 4096 < list->length; p++, ptr++) {
*ptr = sg_dma_address(list) - list->offset;
}
}
/* safety; fill the page table up with the last valid page */
fill = *(ptr-1);
for(;i<1024;i++) {
*ptr++ = fill;
}
/*
ptr = pt->cpu;
for(j=0;j<60;j++) {
printk("ptr1 %d: 0x%08x\n",j,ptr[j]);
}
*/
}
/********************************************************************************/
/* gpio functions */
void saa7146_setgpio(struct saa7146_dev *dev, int port, u32 data)
{
u32 val = 0;
val=saa7146_read(dev,GPIO_CTRL);
val&=~(0xff << (8*(port)));
val|=(data)<<(8*(port));
saa7146_write(dev, GPIO_CTRL, val);
}
/********************************************************************************/
/* interrupt handler */
static void interrupt_hw(int irq, void *dev_id, struct pt_regs *regs)
{
struct saa7146_dev *dev = (struct saa7146_dev*)dev_id;
u32 isr = 0;
/* read out the interrupt status register */
isr = saa7146_read(dev, ISR);
/* is this our interrupt? */
if ( 0 == isr ) {
/* nope, some other device */
return;
}
saa7146_write(dev, ISR, isr);
// DEB_INT(("0x%08x\n",isr));
if( 0 != (dev->ext)) {
if( 0 != (dev->ext->irq_mask & isr )) {
if( 0 != 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( 0 != dev->vv_data && 0 != dev->vv_callback) {
dev->vv_callback(dev,isr);
}
isr &= ~MASK_27;
}
if (0 != (isr & (MASK_28))) {
if( 0 != dev->vv_data && 0 != dev->vv_callback) {
dev->vv_callback(dev,isr);
}
isr &= ~MASK_28;
}
if (0 != (isr & (MASK_16|MASK_17))) {
u32 status = saa7146_read(dev, I2C_STATUS);
if( (0x3 == (status & 0x3)) || (0 == (status & 0x1)) ) {
IER_DISABLE(dev, MASK_16|MASK_17);
/* only wake up if we expect something */
if( 0 != dev->i2c_op ) {
u32 psr = (saa7146_read(dev, PSR) >> 16) & 0x2;
u32 ssr = (saa7146_read(dev, SSR) >> 17) & 0x1f;
DEB_I2C(("irq: i2c, status: 0x%08x, psr:0x%02x, ssr:0x%02x).\n",status,psr,ssr));
dev->i2c_op = 0;
wake_up(&dev->i2c_wq);
} else {
DEB_I2C(("unexpected irq: i2c, status: 0x%08x, isr %#x\n",status, isr));
}
} else {
DEB_I2C(("unhandled irq: i2c, status: 0x%08x, isr %#x\n",status, isr));
}
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"));
IER_DISABLE(dev,isr);
}
}
/*********************************************************************************/
/* configuration-functions */
static
int saa7146_init_one(struct pci_dev *pci, const struct pci_device_id *ent)
{
unsigned long adr = 0, len = 0;
struct saa7146_dev* dev = kmalloc (sizeof(struct saa7146_dev),GFP_KERNEL);
struct saa7146_pci_extension_data *pci_ext = (struct saa7146_pci_extension_data *)ent->driver_data;
struct saa7146_extension* ext = pci_ext->ext;
int err = 0;
if (!(dev = kmalloc (sizeof(struct saa7146_dev),GFP_KERNEL))) {
ERR(("out of memory.\n"));
return -ENOMEM;
}
/* clear out mem for sure */
memset(dev, 0x0, sizeof(struct saa7146_dev));
DEB_EE(("pci:%p\n",pci));
if (pci_enable_device(pci)) {
ERR(("pci_enable_device() failed.\n"));
err = -EIO;
goto pci_error;
}
/* enable bus-mastering */
pci_set_master(pci);
dev->pci = pci;
/* get chip-revision; this is needed to enable bug-fixes */
if( 0 > pci_read_config_dword(dev->pci, PCI_CLASS_REVISION, &dev->revision)) {
ERR(("pci_read_config_dword() failed.\n"));
err = -ENODEV;
goto pci_error;
}
dev->revision &= 0xf;
/* remap the memory from virtual to physical adress */
adr = pci_resource_start(pci,0);
len = pci_resource_len(pci,0);
if (!request_mem_region(pci_resource_start(pci,0), pci_resource_len(pci,0), "saa7146")) {
ERR(("request_mem_region() failed.\n"));
err = -ENODEV;
goto pci_error;
}
if (!(dev->mem = ioremap(adr,len))) {
ERR(("ioremap() failed.\n"));
err = -ENODEV;
goto ioremap_error;
}
/* 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 alle irqs */
saa7146_write(dev, IER, 0);
/* shut down all dma transfers */
saa7146_write(dev, MC1, 0x00ff0000);
/* clear out any rps-signals pending */
saa7146_write(dev, MC2, 0xf8000000);
/* request an interrupt for the saa7146 */
if (request_irq(dev->pci->irq, interrupt_hw, SA_SHIRQ | SA_INTERRUPT,
dev->name, dev))
{
ERR(("request_irq() failed.\n"));
err = -ENODEV;
goto irq_error;
}
/* get memory for various stuff */
dev->rps0 = (u32*)kmalloc(SAA7146_RPS_MEM, GFP_KERNEL);
if( NULL == dev->rps0 ) {
err = -ENOMEM;
goto kmalloc_error_1;
}
memset(dev->rps0, 0x0, SAA7146_RPS_MEM);
dev->rps1 = (u32*)kmalloc(SAA7146_RPS_MEM, GFP_KERNEL);
if( NULL == dev->rps1 ) {
err = -ENOMEM;
goto kmalloc_error_2;
}
memset(dev->rps1, 0x0, SAA7146_RPS_MEM);
dev->i2c_mem = (u32*)kmalloc(SAA7146_I2C_MEM, GFP_KERNEL);
if( NULL == dev->i2c_mem ) {
err = -ENOMEM;
goto kmalloc_error_3;
}
memset(dev->i2c_mem, 0x00, SAA7146_I2C_MEM);
/* the rest + print status message */
/* create a nice device name */
sprintf(&dev->name[0], "saa7146 (%d)",saa7146_num);
INFO(("found saa7146 @ mem 0x%08x (revision %d, irq %d) (0x%04x,0x%04x).\n", (unsigned int)dev->mem, dev->revision,dev->pci->irq,dev->pci->subsystem_vendor,dev->pci->subsystem_device));
dev->ext = ext;
pci_set_drvdata(pci,dev);
init_MUTEX(&dev->lock);
dev->int_slock = SPIN_LOCK_UNLOCKED;
dev->slock = SPIN_LOCK_UNLOCKED;
init_MUTEX(&dev->i2c_lock);
dev->module = THIS_MODULE;
init_waitqueue_head(&dev->i2c_wq);
if( 0 != ext->probe) {
if( 0 != ext->probe(dev) ) {
DEB_D(("ext->probe() failed for %p. skipping device.\n",dev));
err = -ENODEV;
goto probe_error;
}
}
if( 0 != ext->attach(dev,pci_ext) ) {
DEB_D(("ext->attach() failed for %p. skipping device.\n",dev));
err = -ENODEV;
goto attach_error;
}
INIT_LIST_HEAD(&dev->item);
list_add_tail(&dev->item,&saa7146_devices);
saa7146_num++;
/* set some default values */
saa7146_write(dev, BCS_CTRL, 0x80400040);
err = 0;
goto out;
attach_error:
probe_error:
pci_set_drvdata(pci,NULL);
kfree( dev->i2c_mem );
kmalloc_error_3:
kfree( dev->rps1 );
kmalloc_error_2:
kfree( dev->rps0 );
kmalloc_error_1:
free_irq(dev->pci->irq, (void *)dev);
irq_error:
iounmap(dev->mem);
ioremap_error:
release_mem_region(adr,len);
pci_error:
kfree(dev);
out:
return err;
}
static
void saa7146_remove_one(struct pci_dev *pdev)
{
struct saa7146_dev* dev = (struct saa7146_dev*) pci_get_drvdata(pdev);
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(dev->pci->irq, (void *)dev);
/* free kernel memory */
kfree(dev->rps0 );
kfree(dev->rps1 );
kfree(dev->i2c_mem);
iounmap(dev->mem);
release_mem_region(pci_resource_start(dev->pci,0), pci_resource_len(dev->pci,0));
list_del(&dev->item);
kfree(dev);
saa7146_num--;
}
/*********************************************************************************/
/* extension handling functions */
int saa7146_register_extension(struct saa7146_extension* ext)
{
DEB_EE(("ext:%p\n",ext));
if( 0 == initialized ) {
INIT_LIST_HEAD(&saa7146_devices);
init_MUTEX(&saa7146_devices_lock);
initialized = 1;
}
ext->driver.name = ext->name;
ext->driver.id_table = ext->pci_tbl;
ext->driver.probe = saa7146_init_one;
ext->driver.remove = saa7146_remove_one;
printk("saa7146: register extension '%s'.\n",ext->name);
return pci_module_init(&ext->driver);
}
int saa7146_unregister_extension(struct saa7146_extension* ext)
{
DEB_EE(("ext:%p\n",ext));
printk("saa7146: unregister extension '%s'.\n",ext->name);
pci_unregister_driver(&ext->driver);
return 0;
}
static
int __init saa7146_init_module(void)
{
if( 0 == initialized ) {
INIT_LIST_HEAD(&saa7146_devices);
init_MUTEX(&saa7146_devices_lock);
initialized = 1;
}
return 0;
}
static
void __exit saa7146_cleanup_module(void)
{
}
module_init(saa7146_init_module);
module_exit(saa7146_cleanup_module);
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_setgpio);
EXPORT_SYMBOL_GPL(saa7146_i2c_transfer);
EXPORT_SYMBOL_GPL(saa7146_i2c_adapter_prepare);
EXPORT_SYMBOL_GPL(saa7146_debug);
EXPORT_SYMBOL_GPL(saa7146_devices);
EXPORT_SYMBOL_GPL(saa7146_devices_lock);
MODULE_AUTHOR("Michael Hunold <michael@mihu.de>");
MODULE_DESCRIPTION("driver for generic saa7146-based hardware");
MODULE_LICENSE("GPL");
#include <media/saa7146_vv.h>
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,51)
#define KBUILD_MODNAME saa7146
#endif
#define BOARD_CAN_DO_VBI(dev) (dev->revision != 0 && dev->vv_data->vbi_minor != -1)
/********************************************************************************/
/* common dma functions */
void saa7146_dma_free(struct saa7146_dev *dev,struct saa7146_buf *buf)
{
DEB_EE(("dev:%p, buf:%p\n",dev,buf));
if (in_interrupt())
BUG();
videobuf_waiton(&buf->vb,0,0);
videobuf_dma_pci_unmap(dev->pci, &buf->vb.dma);
videobuf_dma_free(&buf->vb.dma);
buf->vb.state = STATE_NEEDS_INIT;
}
/********************************************************************************/
/* common buffer functions */
int saa7146_buffer_queue(struct saa7146_dev *dev,
struct saa7146_dmaqueue *q,
struct saa7146_buf *buf)
{
#if DEBUG_SPINLOCKS
BUG_ON(!spin_is_locked(&dev->slock));
#endif
DEB_EE(("dev:%p, dmaq:%p, buf:%p\n", dev, q, buf));
if( NULL == q ) {
ERR(("internal error: fatal NULL pointer for q.\n"));
return 0;
}
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 = STATE_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)
{
#if DEBUG_SPINLOCKS
BUG_ON(!spin_is_locked(&dev->slock));
#endif
if( NULL == q->curr ) {
ERR(("internal error: fatal NULL pointer for q->curr.\n"));
return;
}
DEB_EE(("dev:%p, dmaq:%p, state:%d\n", dev, q, state));
/* finish current buffer */
q->curr->vb.state = state;
do_gettimeofday(&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;
if( NULL == q ) {
ERR(("internal error: fatal NULL pointer for q.\n"));
return;
}
DEB_EE(("dev:%p, dmaq:%p, vbi:%d\n", dev, q, vbi));
#if DEBUG_SPINLOCKS
BUG_ON(!spin_is_locked(&dev->slock));
#endif
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_D(("next buffer: buf:%p, prev:%p, next:%p\n", buf, q->queue.prev,q->queue.next));
buf->activate(dev,buf,next);
} else {
DEB_D(("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);
/* write the address of the rps-program */
saa7146_write(dev, RPS_ADDR0, virt_to_bus(&dev->rps0[ 0]));
/* turn on rps */
saa7146_write(dev, MC1, (MASK_12 | MASK_28));
}
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,STATE_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 inode *inode, struct file *file)
{
unsigned int minor = minor(inode->i_rdev);
struct saa7146_dev *h = NULL, *dev = NULL;
struct list_head *list;
struct saa7146_fh *fh = NULL;
int result = 0;
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
DEB_EE(("inode:%p, file:%p, minor:%d\n",inode,file,minor));
if (down_interruptible(&saa7146_devices_lock))
return -ERESTARTSYS;
list_for_each(list,&saa7146_devices) {
h = list_entry(list, struct saa7146_dev, item);
if( NULL == h->vv_data ) {
DEB_D(("device %p has not registered video devices.\n",h));
continue;
}
DEB_D(("trying: %p @ major %d,%d\n",h,h->vv_data->video_minor,h->vv_data->vbi_minor));
if (h->vv_data->video_minor == minor) {
dev = h;
}
if (h->vv_data->vbi_minor == minor) {
type = V4L2_BUF_TYPE_VBI_CAPTURE;
dev = h;
}
}
if (NULL == dev) {
DEB_S(("no such video device.\n"));
result = -ENODEV;
goto out;
}
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 = kmalloc(sizeof(*fh),GFP_KERNEL);
if (NULL == fh) {
DEB_S(("cannot allocate memory for per open data.\n"));
result = -ENOMEM;
goto out;
}
memset(fh,0,sizeof(*fh));
// FIXME: do we need to increase *our* usage count?
if( 0 == try_module_get(dev->ext->module)) {
result = -EINVAL;
goto out;
}
file->private_data = fh;
fh->dev = dev;
fh->type = type;
saa7146_video_uops.open(dev,fh);
if( 0 != BOARD_CAN_DO_VBI(dev) ) {
saa7146_vbi_uops.open(dev,fh);
}
result = 0;
out:
if( fh != 0 && result != 0 ) {
kfree(fh);
}
up(&saa7146_devices_lock);
return result;
}
static int fops_release(struct inode *inode, struct file *file)
{
struct saa7146_fh *fh = file->private_data;
struct saa7146_dev *dev = fh->dev;
DEB_EE(("inode:%p, file:%p\n",inode,file));
if (down_interruptible(&saa7146_devices_lock))
return -ERESTARTSYS;
saa7146_video_uops.release(dev,fh,file);
if( 0 != BOARD_CAN_DO_VBI(dev) ) {
saa7146_vbi_uops.release(dev,fh,file);
}
module_put(dev->ext->module);
file->private_data = NULL;
kfree(fh);
up(&saa7146_devices_lock);
return 0;
}
int saa7146_video_do_ioctl(struct inode *inode, struct file *file, unsigned int cmd, void *arg);
static int fops_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{
/*
DEB_EE(("inode:%p, file:%p, cmd:%d, arg:%li\n",inode, file, cmd, arg));
*/
return video_usercopy(inode, file, cmd, arg, saa7146_video_do_ioctl);
}
static int fops_mmap(struct file *file, struct vm_area_struct * vma)
{
struct saa7146_fh *fh = file->private_data;
struct videobuf_queue *q;
switch (fh->type) {
case V4L2_BUF_TYPE_VIDEO_CAPTURE: {
DEB_EE(("V4L2_BUF_TYPE_VIDEO_CAPTURE: file:%p, vma:%p\n",file, vma));
q = &fh->video_q;
break;
}
case V4L2_BUF_TYPE_VBI_CAPTURE: {
DEB_EE(("V4L2_BUF_TYPE_VBI_CAPTURE: file:%p, vma:%p\n",file, vma));
q = &fh->vbi_q;
break;
}
default:
BUG();
return 0;
}
return videobuf_mmap_mapper(vma,q);
}
static unsigned int fops_poll(struct file *file, struct poll_table_struct *wait)
{
struct saa7146_fh *fh = file->private_data;
struct videobuf_buffer *buf = NULL;
struct videobuf_queue *q;
DEB_EE(("file:%p, poll:%p\n",file, wait));
if (V4L2_BUF_TYPE_VBI_CAPTURE == fh->type) {
if( 0 == fh->vbi_q.streaming )
return videobuf_poll_stream(file, &fh->vbi_q, wait);
q = &fh->vbi_q;
} else {
q = &fh->video_q;
}
if (!list_empty(&q->stream))
buf = list_entry(q->stream.next, struct videobuf_buffer, stream);
if (!buf) {
return POLLERR;
}
poll_wait(file, &buf->done, wait);
if (buf->state == STATE_DONE || buf->state == STATE_ERROR) {
return POLLIN|POLLRDNORM;
}
return 0;
}
static ssize_t fops_read(struct file *file, char *data, size_t count, loff_t *ppos)
{
struct saa7146_fh *fh = file->private_data;
switch (fh->type) {
case V4L2_BUF_TYPE_VIDEO_CAPTURE: {
DEB_EE(("V4L2_BUF_TYPE_VIDEO_CAPTURE: file:%p, data:%p, count:%d\n",file, data, count));
return saa7146_video_uops.read(file,data,count,ppos);
}
case V4L2_BUF_TYPE_VBI_CAPTURE: {
DEB_EE(("V4L2_BUF_TYPE_VBI_CAPTURE: file:%p, data:%p, count:%d\n",file, data, count));
return saa7146_vbi_uops.read(file,data,count,ppos);
}
break;
default:
BUG();
return 0;
}
}
static struct file_operations video_fops =
{
.owner = THIS_MODULE,
.open = fops_open,
.release = fops_release,
.read = fops_read,
.poll = fops_poll,
.mmap = fops_mmap,
.ioctl = fops_ioctl,
.llseek = no_llseek,
};
void vv_callback(struct saa7146_dev *dev, unsigned long status)
{
u32 isr = status;
DEB_EE(("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 struct video_device device_template =
{
.hardware = VID_HARDWARE_SAA7146,
.fops = &video_fops,
.minor = -1,
};
int saa7146_vv_init(struct saa7146_dev* dev)
{
struct saa7146_vv *vv = kmalloc (sizeof(struct saa7146_vv),GFP_KERNEL);
if( NULL == vv ) {
ERR(("out of memory. aborting.\n"));
return -1;
}
memset(vv, 0x0, sizeof(*vv));
DEB_EE(("dev:%p\n",dev));
vv->video_minor = -1;
vv->vbi_minor = -1;
vv->clipping = (u32*)kmalloc(SAA7146_CLIPPING_MEM, GFP_KERNEL);
if( NULL == vv->clipping ) {
ERR(("out of memory. aborting.\n"));
kfree(vv);
return -1;
}
memset(vv->clipping, 0x0, SAA7146_CLIPPING_MEM);
saa7146_video_uops.init(dev,vv);
saa7146_vbi_uops.init(dev,vv);
dev->vv_data = vv;
dev->vv_callback = &vv_callback;
return 0;
}
int saa7146_vv_release(struct saa7146_dev* dev)
{
struct saa7146_vv *vv = dev->vv_data;
DEB_EE(("dev:%p\n",dev));
kfree(vv);
dev->vv_data = NULL;
dev->vv_callback = NULL;
return 0;
}
int saa7146_register_device(struct video_device *vid, struct saa7146_dev* dev, char *name, int type)
{
struct saa7146_vv *vv = dev->vv_data;
DEB_EE(("dev:%p, name:'%s'\n",dev,name));
*vid = device_template;
strncpy(vid->name, name, 32);
vid->priv = dev;
// fixme: -1 should be an insmod parameter *for the extension* (like "video_nr");
if (video_register_device(vid,type,-1) < 0) {
ERR(("cannot register vbi v4l2 device. skipping.\n"));
return -1;
}
if( VFL_TYPE_GRABBER == type ) {
vv->video_minor = vid->minor;
INFO(("%s: registered device video%d [v4l2]\n", dev->name,vid->minor & 0x1f));
} else {
vv->vbi_minor = vid->minor;
INFO(("%s: registered device vbi%d [v4l2]\n", dev->name,vid->minor & 0x1f));
}
return 0;
}
int saa7146_unregister_device(struct video_device *vid, struct saa7146_dev* dev)
{
struct saa7146_vv *vv = dev->vv_data;
DEB_EE(("dev:%p\n",dev));
if( VFL_TYPE_GRABBER == vid->type ) {
vv->video_minor = -1;
} else {
vv->vbi_minor = -1;
}
video_unregister_device(vid);
return 0;
}
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);
EXPORT_SYMBOL_GPL(saa7146_set_hps_source_and_sync);
EXPORT_SYMBOL_GPL(saa7146_register_device);
EXPORT_SYMBOL_GPL(saa7146_unregister_device);
EXPORT_SYMBOL_GPL(saa7146_vv_init);
EXPORT_SYMBOL_GPL(saa7146_vv_release);
MODULE_AUTHOR("Michael Hunold <michael@mihu.de>");
MODULE_DESCRIPTION("video4linux driver for saa7146-based hardware");
MODULE_LICENSE("GPL");
#include <media/saa7146_vv.h>
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,51)
#define KBUILD_MODNAME saa7146
#endif
#define my_min(type,x,y) \
({ type __x = (x), __y = (y); __x < __y ? __x: __y; })
#define my_max(type,x,y) \
({ type __x = (x), __y = (y); __x > __y ? __x: __y; })
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_bcs_ctrl_register(struct saa7146_dev *dev, int brightness, int contrast, int colour, u32 *bcs_ctrl)
{
*bcs_ctrl = ((brightness << 24) | (contrast << 16) | (colour << 0));
}
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 */
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 bandwith 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 */
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_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;
u32 *clipping = vv->clipping;
int width = fh->ov.win.w.width;
int height = fh->ov.win.w.height;
int clipcount = fh->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] = fh->ov.clips[i].c.left;
y[i] = fh->ov.clips[i].c.top;
w[i] = fh->ov.clips[i].c.width;
h[i] = fh->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 ] = my_min(int, l, width);
pixel_list[(2*i)+1] = my_min(int, r, width);
/* insert top/bottom coordinates */
line_list[ 2*i ] = my_min(int, t, height);
line_list[(2*i)+1] = my_min(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 = my_max(int, (cnt_line+1), (cnt_pixel+1))*2;
numdwords = my_max(int, 4, numdwords);
numdwords = my_min(int, 64, numdwords);
/* fill up cliptable */
for(i = 0; i < cnt_pixel; i++) {
clipping[2*i] |= (pixel_list[i] << 16);
}
for(i = 0; i < cnt_line; i++) {
clipping[(2*i)+1] |= (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] |= (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] |= (1 << j);
}
}
}
}
/* adjust arbitration control register */
*arbtr_ctrl &= 0xffff00ff;
*arbtr_ctrl |= 0x00001c00;
vdma2->base_even = virt_to_bus(clipping);
vdma2->base_odd = virt_to_bus(clipping);
vdma2->prot_addr = virt_to_bus(clipping)+((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 if (field == V4L2_FIELD_TOP) {
*clip_format |= 0x00000000;
} else if (field == V4L2_FIELD_BOTTOM) {
*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_dev *dev, struct saa7146_fh *fh)
{
enum v4l2_field field = fh->ov.win.field;
int clipcount = fh->ov.nclips;
struct saa7146_video_dma vdma2;
u32 clip_format = saa7146_read(dev, CLIP_FORMAT_CTRL);
u32 arbtr_ctrl = saa7146_read(dev, PCI_BT_V1);
// fixme: is this used at all? SAA7146_CLIPPING_RECT_INVERTED;
u32 type = SAA7146_CLIPPING_RECT;
/* check clipcount, disable clipping if clipcount == 0*/
if( clipcount == 0 ) {
saa7146_disable_clipping(dev);
return;
}
calculate_clipping_registers_rect(dev, fh, &vdma2, &clip_format, &arbtr_ctrl, field);
/* set clipping format */
clip_format &= 0xffff0008;
clip_format |= (type << 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_calc, 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_calc, 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)
{
struct saa7146_vv *vv = dev->vv_data;
int b_depth = vv->ov_fmt->depth;
int b_bpl = vv->ov_fb.fmt.bytesperline;
u32 base = (u32)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 = (u32)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 = (u32)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_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 = 0;
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));
}
void saa7146_set_picture_prop(struct saa7146_dev *dev, int brightness, int contrast, int colour)
{
u32 bcs_ctrl = 0;
calculate_bcs_ctrl_register(dev, brightness, contrast, colour, &bcs_ctrl);
saa7146_write(dev, BCS_CTRL, bcs_ctrl);
/* update the bcs register */
saa7146_write(dev, MC2, (MASK_06 | MASK_22));
}
/* 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;
}
/* write "data" to the gpio-pin "pin" */
void saa7146_set_gpio(struct saa7146_dev *dev, u8 pin, u8 data)
{
u32 value = 0;
/* sanity check */
if(pin > 3)
return;
/* read old register contents */
value = saa7146_read(dev, GPIO_CTRL );
value &= ~(0xff << (8*pin));
value |= (data << (8*pin));
saa7146_write(dev, GPIO_CTRL, value);
}
/* reprogram hps, enable(1) / disable(0) video */
void saa7146_set_overlay(struct saa7146_dev *dev, struct saa7146_fh *fh, int v)
{
struct saa7146_vv *vv = dev->vv_data;
/* enable ? */
if( 0 == v) {
/* disable video dma1 */
saa7146_write(dev, MC1, MASK_22);
return;
}
saa7146_set_window(dev, fh->ov.win.w.width, fh->ov.win.w.height, fh->ov.win.field);
saa7146_set_position(dev, fh->ov.win.w.left, fh->ov.win.w.top, fh->ov.win.w.height, fh->ov.win.field);
saa7146_set_output_format(dev, vv->ov_fmt->trans);
saa7146_set_clipping_rect(dev, fh);
/* enable video dma1 */
saa7146_write(dev, MC1, (MASK_06 | 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;
if( 0 != (dev->ext->ext_vv_data->flags & SAA7146_EXT_SWAP_ODD_EVEN)) {
saa7146_write(dev, where, vdma->base_even);
saa7146_write(dev, where+0x04, vdma->base_odd);
} else {
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 = format_by_fourcc(dev,buf->fmt->pixelformat);
int width = buf->fmt->width;
int height = buf->fmt->height;
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]));
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;
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_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;
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 = 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: 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_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->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;
/* write beginning of rps-program */
count = 0;
/* wait for o_fid_a/b / e_fid_a/b toggle only if bit 0 is not set*/
dev->rps0[ count++ ] = CMD_PAUSE | CMD_OAN | CMD_SIG0 | e_wait;
dev->rps0[ count++ ] = CMD_PAUSE | CMD_OAN | CMD_SIG0 | o_wait;
/* set bit 0 */
dev->rps0[ count++ ] = CMD_WR_REG | (1 << 8) | (MC2/4);
dev->rps0[ count++ ] = MASK_27 | MASK_11;
/* turn on video-dma1 */
dev->rps0[ count++ ] = CMD_WR_REG_MASK | (MC1/4);
dev->rps0[ count++ ] = MASK_06 | MASK_22; /* => mask */
dev->rps0[ count++ ] = MASK_06 | MASK_22; /* => values */
if( 0 != planar ) {
/* turn on video-dma2 */
dev->rps0[ count++ ] = CMD_WR_REG_MASK | (MC1/4);
dev->rps0[ count++ ] = MASK_05 | MASK_21; /* => mask */
dev->rps0[ count++ ] = MASK_05 | MASK_21; /* => values */
/* turn on video-dma3 */
dev->rps0[ count++ ] = CMD_WR_REG_MASK | (MC1/4);
dev->rps0[ count++ ] = MASK_04 | MASK_20; /* => mask */
dev->rps0[ count++ ] = MASK_04 | MASK_20; /* => values */
}
/* wait for o_fid_a/b / e_fid_a/b toggle */
dev->rps0[ count++ ] = CMD_PAUSE | e_wait;
dev->rps0[ count++ ] = CMD_PAUSE | o_wait;
/* turn off video-dma1 */
dev->rps0[ count++ ] = CMD_WR_REG_MASK | (MC1/4);
dev->rps0[ count++ ] = MASK_22 | MASK_06; /* => mask */
dev->rps0[ count++ ] = MASK_22; /* => values */
if( 0 != planar ) {
/* turn off video-dma2 */
dev->rps0[ count++ ] = CMD_WR_REG_MASK | (MC1/4);
dev->rps0[ count++ ] = MASK_05 | MASK_21; /* => mask */
dev->rps0[ count++ ] = MASK_21; /* => values */
/* turn off video-dma3 */
dev->rps0[ count++ ] = CMD_WR_REG_MASK | (MC1/4);
dev->rps0[ count++ ] = MASK_04 | MASK_20; /* => mask */
dev->rps0[ count++ ] = MASK_20; /* => values */
}
/* generate interrupt */
dev->rps0[ count++ ] = CMD_INTERRUPT;
/* stop */
dev->rps0[ count++ ] = CMD_STOP;
}
void saa7146_set_capture(struct saa7146_dev *dev, struct saa7146_buf *buf, struct saa7146_buf *next)
{
struct saa7146_format *sfmt = format_by_fourcc(dev,buf->fmt->pixelformat);
DEB_CAP(("buf:%p, next:%p\n",buf,next));
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( 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);
}
/* write the address of the rps-program */
saa7146_write(dev, RPS_ADDR0, virt_to_bus(&dev->rps0[ 0]));
/* turn on rps */
saa7146_write(dev, MC1, (MASK_12 | MASK_28));
}
#include <media/saa7146_vv.h>
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,51)
#define KBUILD_MODNAME saa7146
#endif
/* helper function */
static
void my_wait(struct saa7146_dev *dev, long ms)
{
set_current_state(TASK_INTERRUPTIBLE);
schedule_timeout((((ms+10)/10)*HZ)/1000);
}
u32 saa7146_i2c_func(struct i2c_adapter *adapter)
{
//fm 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, u32 *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 ) {
//fm DEB_I2C(("cannot prepare i2c-message.\n"));
return -ENOMEM;
}
/* be careful: clear out the i2c-mem first */
memset(op,0,sizeof(u32)*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] |= ( (u8)addr << ((3-h2)*8));
op[h1] |= (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] |= ( (u32)((u8)m[i].buf[j]) << ((3-h2)*8));
op[h1] |= ( 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 & (op[h1] >> ((3-h2)*2))) ) {
op[h1] &= ~(0x2 << ((3-h2)*2));
op[h1] |= (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, u32 *op)
{
int i, j;
int op_count = 0;
/* loop through all messages */
for(i = 0; i < num; i++) {
op_count++;
/* loop throgh 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] = (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));
my_wait(dev,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));
my_wait(dev,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));
my_wait(dev,SAA7146_I2C_DELAY);
/* clear all error-bits pending */
saa7146_write(dev, I2C_STATUS, dev->i2c_bitrate);
saa7146_write(dev, MC2, (MASK_00 | MASK_16));
my_wait(dev,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));
my_wait(dev,SAA7146_I2C_DELAY);
}
/* if any error is still present, a fatal error has occured ... */
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, u32* dword)
{
u32 status = 0, mc2 = 0;
int 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, *dword);
dev->i2c_op = 1;
IER_ENABLE(dev, MASK_16|MASK_17);
saa7146_write(dev, MC2, (MASK_00 | MASK_16));
wait_event_interruptible(dev->i2c_wq, dev->i2c_op == 0);
if (signal_pending (current)) {
/* a signal arrived */
return -ERESTARTSYS;
}
status = saa7146_read(dev, I2C_STATUS);
} else {
saa7146_write(dev, I2C_STATUS, dev->i2c_bitrate);
saa7146_write(dev, I2C_TRANSFER, *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 (jiffies > timeout) {
printk(KERN_WARNING "saa7146_i2c_writeout: timed out waiting for MC2\n");
return -EIO;
}
}
/* wait until we get a transfer done or error */
timeout = jiffies + HZ/100 + 1; /* 10ms */
while(1) {
status = saa7146_i2c_status(dev);
if( (0x3 == (status & 0x3)) || (0 == (status & 0x1)) ) {
break;
}
if (jiffies > timeout) {
/* this is normal when probing the bus
* (no answer from nonexisistant device...)
*/
DEB_I2C(("saa7146_i2c_writeout: timed out waiting for end of xfer\n"));
return -EIO;
}
my_wait(dev,1);
}
}
/* give a detailed status report */
if ( 0 != (status & SAA7146_I2C_ERR)) {
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 = saa7146_read(dev, I2C_TRANSFER);
DEB_I2C(("after: 0x%08x\n",*dword));
return 0;
}
int saa7146_i2c_transfer(struct saa7146_dev *dev, const struct i2c_msg msgs[], int num, int retries)
{
int i = 0, count = 0;
u32* buffer = dev->i2c_mem;
int err = 0;
int address_err = 0;
if (down_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;
}
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] );
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 occured. in that case, the
transaction should be retrying, even if an address error
occured. 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. because of the fact, that only
analog based cards use irq based i2c transactions (for dvb
cards, this screwes up other interrupt sources), we bail out
completely for analog cards after an address error and trust
the saa7146 address error detection. */
if ( -EREMOTEIO == err ) {
if( 0 != (SAA7146_USE_I2C_IRQ & dev->ext->flags)) {
goto out;
}
address_err++;
}
DEB_I2C(("error while sending message(s). starting again.\n"));
break;
}
}
if( 0 == err ) {
err = num;
break;
}
/* delay a bit before retrying */
my_wait(dev, 10);
} while (err != num && retries--);
/* if every retry had an address error, exit right away */
if (address_err == retries) {
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 continueing */
if( 0 == dev->revision ) {
u32 zero = 0;
saa7146_i2c_reset(dev);
if( 0 != saa7146_i2c_writeout(dev, &zero)) {
INFO(("revision 0 error. this should never happen.\n"));
}
}
up(&dev->i2c_lock);
return err;
}
/* utility functions */
static
int saa7146_i2c_xfer(struct i2c_adapter* adapter, struct i2c_msg msg[], int num)
{
struct saa7146_dev* dev = i2c_get_adapdata(adapter);
DEB_I2C(("adapter: '%s'.\n", adapter->dev.name));
/* use helper function to transfer data */
return saa7146_i2c_transfer(dev, msg, num, adapter->retries);
}
/*****************************************************************************/
/* i2c-adapter helper functions */
#include <linux/i2c-id.h>
/* exported algorithm data */
static
struct i2c_algorithm saa7146_algo = {
.name = "saa7146 i2c algorithm",
.id = I2C_ALGO_SAA7146,
.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));
dev->i2c_bitrate = bitrate;
saa7146_i2c_reset(dev);
if( NULL != i2c_adapter ) {
memset(i2c_adapter,0,sizeof(struct i2c_adapter));
strcpy(i2c_adapter->dev.name, dev->name);
i2c_set_adapdata(i2c_adapter,dev);
i2c_adapter->algo = &saa7146_algo;
i2c_adapter->algo_data = NULL;
i2c_adapter->id = I2C_ALGO_SAA7146;
i2c_adapter->timeout = SAA7146_I2C_TIMEOUT;
i2c_adapter->retries = SAA7146_I2C_RETRIES;
}
return 0;
}
#include <media/saa7146_vv.h>
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,51)
#define KBUILD_MODNAME saa7146
#endif
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 i, index;
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);
/* we have to do the workaround two times 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);
index = 0;
/* load brs-control register */
dev->rps1[index++] = CMD_WR_REG | (1 << 8) | (BRS_CTRL/4);
/* BXO = 1h, BRS to outbound */
dev->rps1[index++]=0xc000008c;
/* wait for vbi_a */
dev->rps1[index++] = CMD_PAUSE | MASK_10;
/* upload brs */
dev->rps1[index++] = CMD_UPLOAD | MASK_08;
/* load brs-control register */
dev->rps1[index++] = CMD_WR_REG | (1 << 8) | (BRS_CTRL/4);
/* BYO = 1, BXO = NQBIL (=1728 for PAL, for NTSC this is 858*2) - NumByte3 (=1440) = 288 */
dev->rps1[index++] = ((1728-(vbi_pixel_to_capture)) << 7) | MASK_19;
/* wait for brs_done */
dev->rps1[index++] = CMD_PAUSE | MASK_08;
/* upload brs */
dev->rps1[index++] = CMD_UPLOAD | MASK_08;
/* load video-dma3 NumLines3 and NumBytes3 */
dev->rps1[index++] = CMD_WR_REG | (1 << 8) | (NUM_LINE_BYTE3/4);
/* dev->vbi_count*2 lines, 720 pixel (= 1440 Bytes) */
dev->rps1[index++]= (2 << 16) | (vbi_pixel_to_capture);
/* load brs-control register */
dev->rps1[index++] = CMD_WR_REG | (1 << 8) | (BRS_CTRL/4);
/* Set BRS right: note: this is an experimental value for BXO (=> PAL!) */
dev->rps1[index++] = (540 << 7) | (5 << 19); // 5 == vbi_start
/* wait for brs_done */
dev->rps1[index++] = CMD_PAUSE | MASK_08;
/* upload brs and video-dma3*/
dev->rps1[index++] = CMD_UPLOAD | MASK_08 | MASK_04;
/* load mc2 register: enable dma3 */
dev->rps1[index++] = CMD_WR_REG | (1 << 8) | (MC1/4);
dev->rps1[index++] = MASK_20 | MASK_04;
/* generate interrupt */
dev->rps1[index++] = CMD_INTERRUPT;
/* stop rps1 */
dev->rps1[index++] = CMD_STOP;
/* enable rps1 irqs */
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, virt_to_bus(&dev->rps1[ 0]));
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 */
IER_DISABLE(dev,MASK_28);
/* stop video-dma3 */
saa7146_write(dev, MC1, MASK_20);
if(signal_pending(current)) {
DEB_VBI(("aborted.\n"));
/* 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;
}
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 = (u32)dev->ov_fb.base+2048*70;
vdma3.base_odd = (u32)dev->ov_fb.base;
vdma3.prot_addr = (u32)dev->ov_fb.base+2048*164;
vdma3.pitch = 2048;
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 video-buf.c... */
/*
dev->rps1[ count++ ] = CMD_PAUSE | CMD_OAN | CMD_SIG1 | e_wait;
dev->rps1[ count++ ] = CMD_PAUSE | CMD_OAN | CMD_SIG1 | o_wait;
*/
/* set bit 1 */
dev->rps1[ count++ ] = CMD_WR_REG | (1 << 8) | (MC2/4);
dev->rps1[ count++ ] = MASK_28 | MASK_12;
/* turn on video-dma3 */
dev->rps1[ count++ ] = CMD_WR_REG_MASK | (MC1/4);
dev->rps1[ count++ ] = MASK_04 | MASK_20; /* => mask */
dev->rps1[ count++ ] = MASK_04 | MASK_20; /* => values */
/* wait for o_fid_a/b / e_fid_a/b toggle */
dev->rps1[ count++ ] = CMD_PAUSE | o_wait;
dev->rps1[ count++ ] = CMD_PAUSE | e_wait;
/* generate interrupt */
dev->rps1[ count++ ] = CMD_INTERRUPT;
/* stop */
dev->rps1[ count++ ] = CMD_STOP;
/* enable rps1 irqs */
IER_ENABLE(dev, MASK_28);
/* write the address of the rps-program */
saa7146_write(dev, RPS_ADDR1, virt_to_bus(&dev->rps1[ 0]));
/* 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 = STATE_ACTIVE;
DEB_VBI(("dev:%p, buf:%p, next:%p\n",dev,buf,next));
saa7146_set_vbi_capture(dev,buf,next);
mod_timer(&vv->vbi_q.timeout, jiffies+BUFFER_TIMEOUT);
return 0;
}
static
int buffer_prepare(struct file *file, struct videobuf_buffer *vb,enum v4l2_field field)
{
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,buf);
if (STATE_NEEDS_INIT == buf->vb.state) {
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(dev->pci,&buf->vb);
if (err)
goto oops;
saa7146_pgtable_build_single(dev->pci, &buf->pt[2], buf->vb.dma.sglist, buf->vb.dma.sglen);
}
buf->vb.state = STATE_PREPARED;
buf->activate = buffer_activate;
return 0;
oops:
DEB_VBI(("error out.\n"));
saa7146_dma_free(dev,buf);
return err;
}
static int
buffer_setup(struct file *file, 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 file *file, struct videobuf_buffer *vb)
{
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_q,buf);
}
static
void buffer_release(struct file *file, struct videobuf_buffer *vb)
{
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,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 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 */
IER_DISABLE(dev, MASK_28);
/* shut down dma 3 transfers */
saa7146_write(dev, MC1, MASK_20);
vv->vbi_streaming = NULL;
spin_unlock_irqrestore(&dev->slock, flags);
}
static
void vbi_read_timeout(unsigned long data)
{
struct saa7146_fh *fh = (struct saa7146_fh *)data;
struct saa7146_dev *dev = fh->dev;
DEB_VBI(("dev:%p, fh:%p\n",dev, fh));
vbi_stop(fh);
}
static
void vbi_init(struct saa7146_dev *dev, struct saa7146_vv *vv)
{
DEB_VBI(("dev:%p\n",dev));
INIT_LIST_HEAD(&vv->vbi_q.queue);
init_timer(&vv->vbi_q.timeout);
vv->vbi_q.timeout.function = saa7146_buffer_timeout;
vv->vbi_q.timeout.data = (unsigned long)(&vv->vbi_q);
vv->vbi_q.dev = dev;
init_waitqueue_head(&vv->vbi_wq);
}
static
void vbi_open(struct saa7146_dev *dev, struct saa7146_fh *fh)
{
DEB_VBI(("dev:%p, fh:%p\n",dev,fh));
memset(&fh->vbi_fmt,0,sizeof(fh->vbi_fmt));
fh->vbi_fmt.sampling_rate = 27000000;
fh->vbi_fmt.offset = 248; /* todo */
fh->vbi_fmt.samples_per_line = vbi_pixel_to_capture;
fh->vbi_fmt.sample_format = V4L2_PIX_FMT_GREY;
/* fixme: this only works for PAL */
fh->vbi_fmt.start[0] = 5;
fh->vbi_fmt.count[0] = 16;
fh->vbi_fmt.start[1] = 312;
fh->vbi_fmt.count[1] = 16;
videobuf_queue_init(&fh->vbi_q, &vbi_qops,
dev->pci, &dev->slock,
V4L2_BUF_TYPE_VBI_CAPTURE,
V4L2_FIELD_SEQ_TB, // FIXME: does this really work?
sizeof(struct saa7146_buf));
init_MUTEX(&fh->vbi_q.lock);
init_timer(&fh->vbi_read_timeout);
fh->vbi_read_timeout.function = vbi_read_timeout;
fh->vbi_read_timeout.data = (unsigned long)fh;
vbi_workaround(dev);
}
static
void vbi_close(struct saa7146_dev *dev, struct saa7146_fh *fh, struct file *file)
{
struct saa7146_vv *vv = dev->vv_data;
DEB_VBI(("dev:%p, fh:%p\n",dev,fh));
if( fh == vv->vbi_streaming ) {
vbi_stop(fh);
}
}
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_q.curr) {
DEB_VBI(("dev:%p, curr:%p\n",dev,vv->vbi_q.curr));
/* this must be += 2, one count for each field */
vv->vbi_fieldcount+=2;
vv->vbi_q.curr->vb.field_count = vv->vbi_fieldcount;
saa7146_buffer_finish(dev,&vv->vbi_q,STATE_DONE);
} else {
DEB_VBI(("dev:%p\n",dev));
}
saa7146_buffer_next(dev,&vv->vbi_q,1);
spin_unlock(&dev->slock);
}
static
ssize_t vbi_read(struct file *file, char *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.",vv->vbi_streaming));
return -EBUSY;
}
mod_timer(&fh->vbi_read_timeout, jiffies+BUFFER_TIMEOUT);
ret = videobuf_read_stream(file, &fh->vbi_q, data, count, ppos, 1);
/*
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,
};
EXPORT_SYMBOL_GPL(saa7146_vbi_uops);
#include <media/saa7146_vv.h>
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,51)
#define KBUILD_MODNAME saa7146
#endif
static
int memory = 32;
MODULE_PARM(memory,"i");
MODULE_PARM_DESC(memory, "maximum memory usage for capture buffers (default: 32Mb)");
/* 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,
}, {
.name = "RGB-16 (5/B-6/G-5/R)", /* really? */
.pixelformat = V4L2_PIX_FMT_RGB565,
.trans = RGB16_COMPOSED,
.depth = 16,
}, {
.name = "RGB-24 (B-G-R)",
.pixelformat = V4L2_PIX_FMT_BGR24,
.trans = RGB24_COMPOSED,
.depth = 24,
}, {
.name = "RGB-32 (B-G-R)",
.pixelformat = V4L2_PIX_FMT_BGR32,
.trans = RGB32_COMPOSED,
.depth = 32,
}, {
.name = "Greyscale-8",
.pixelformat = V4L2_PIX_FMT_GREY,
.trans = Y8,
.depth = 8,
}, {
.name = "YUV 4:2:2 planar (Y-Cb-Cr)",
.pixelformat = V4L2_PIX_FMT_YUV422P,
.trans = YUV422_DECOMPOSED,
.depth = 16,
.swap = 1,
}, {
.name = "YVU 4:2:0 planar (Y-Cb-Cr)",
.pixelformat = V4L2_PIX_FMT_YVU420,
.trans = YUV420_DECOMPOSED,
.depth = 12,
.swap = 1,
}, {
.name = "YUV 4:2:0 planar (Y-Cb-Cr)",
.pixelformat = V4L2_PIX_FMT_YUV420,
.trans = YUV420_DECOMPOSED,
.depth = 12,
}, {
.name = "YUV 4:2:2 (U-Y-V-Y)",
.pixelformat = V4L2_PIX_FMT_UYVY,
.trans = YUV422_COMPOSED,
.depth = 16,
}
};
/* 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* 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 g_fmt(struct saa7146_fh *fh, struct v4l2_format *f)
{
struct saa7146_dev *dev = fh->dev;
DEB_EE(("dev:%p, fh:%p\n",dev,fh));
switch (f->type) {
case V4L2_BUF_TYPE_VIDEO_CAPTURE:
f->fmt.pix = fh->video_fmt;
return 0;
case V4L2_BUF_TYPE_VIDEO_OVERLAY:
f->fmt.win = fh->ov.win;
return 0;
case V4L2_BUF_TYPE_VBI_CAPTURE:
{
f->fmt.vbi = fh->vbi_fmt;
return 0;
}
default:
DEB_D(("invalid format type '%d'.\n",f->type));
return -EINVAL;
}
}
static
int try_win(struct saa7146_dev *dev, struct v4l2_window *win)
{
struct saa7146_vv *vv = dev->vv_data;
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 < 64 || win->w.height < 64) {
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:
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 try_fmt(struct saa7146_fh *fh, struct v4l2_format *f)
{
struct saa7146_dev *dev = fh->dev;
struct saa7146_vv *vv = dev->vv_data;
int err;
switch (f->type) {
case V4L2_BUF_TYPE_VIDEO_CAPTURE:
{
struct saa7146_format *fmt;
enum v4l2_field field;
int maxw, maxh;
DEB_EE(("V4L2_BUF_TYPE_VIDEO_CAPTURE: dev:%p, fh:%p\n",dev,fh));
fmt = 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_TOP:
case V4L2_FIELD_BOTTOM:
maxh = maxh / 2;
break;
case V4L2_FIELD_INTERLACED:
break;
default: {
DEB_D(("no known field mode '%d'.\n",field));
return -EINVAL;
}
}
f->fmt.pix.field = field;
if (f->fmt.pix.width > maxw)
f->fmt.pix.width = maxw;
if (f->fmt.pix.height > maxh)
f->fmt.pix.height = maxh;
f->fmt.pix.sizeimage =
(f->fmt.pix.width * f->fmt.pix.height * fmt->depth)/8;
return 0;
}
case V4L2_BUF_TYPE_VIDEO_OVERLAY:
DEB_EE(("V4L2_BUF_TYPE_VIDEO_OVERLAY: dev:%p, fh:%p\n",dev,fh));
err = try_win(dev,&f->fmt.win);
if (0 != err) {
return err;
}
return 0;
default:
DEB_EE(("unknown format type '%d'\n",f->type));
return -EINVAL;
}
}
static
int start_preview(struct saa7146_fh *fh)
{
struct saa7146_dev *dev = fh->dev;
struct saa7146_vv *vv = dev->vv_data;
int err = 0;
DEB_EE(("dev:%p, fh:%p\n",dev,fh));
/* check if we have overlay informations */
if( NULL == fh->ov.fh ) {
DEB_D(("not overlay data available. try S_FMT first.\n"));
return -EAGAIN;
}
/* check if overlay is running */
if( 0 != vv->ov_data ) {
if( fh != vv->ov_data->fh ) {
DEB_D(("overlay is running in another open.\n"));
return -EAGAIN;
}
DEB_D(("overlay is already active.\n"));
return 0;
}
if( 0 != vv->streaming ) {
DEB_D(("streaming capture is active.\n"));
return -EBUSY;
}
err = try_win(dev,&fh->ov.win);
if (0 != err) {
return err;
}
vv->ov_data = &fh->ov;
DEB_D(("%dx%d+%d+%d %s field=%s\n",
fh->ov.win.w.width,fh->ov.win.w.height,
fh->ov.win.w.left,fh->ov.win.w.top,
vv->ov_fmt->name,v4l2_field_names[fh->ov.win.field]));
saa7146_set_overlay(dev, fh, 1);
return 0;
}
static
int stop_preview(struct saa7146_fh *fh)
{
struct saa7146_dev *dev = fh->dev;
struct saa7146_vv *vv = dev->vv_data;
DEB_EE(("saa7146.o: stop_preview()\n"));
/* check if overlay is running */
if( 0 == vv->ov_data ) {
DEB_D(("overlay is not active.\n"));
return 0;
}
if( fh != vv->ov_data->fh ) {
DEB_D(("overlay is active, but for another open.\n"));
return -EBUSY;
}
saa7146_set_overlay(dev, fh, 0);
vv->ov_data = NULL;
return 0;
}
static
int s_fmt(struct saa7146_fh *fh, struct v4l2_format *f)
{
struct saa7146_dev *dev = fh->dev;
struct saa7146_vv *vv = dev->vv_data;
unsigned long flags;
int err;
switch (f->type) {
case V4L2_BUF_TYPE_VIDEO_CAPTURE:
DEB_EE(("V4L2_BUF_TYPE_VIDEO_CAPTURE: dev:%p, fh:%p\n",dev,fh));
if( fh == vv->streaming ) {
DEB_EE(("streaming capture is active"));
return -EAGAIN;
}
err = try_fmt(fh,f);
if (0 != err)
return err;
fh->video_fmt = f->fmt.pix;
DEB_EE(("set to pixelformat '%4.4s'\n",(char *)&fh->video_fmt.pixelformat));
return 0;
case V4L2_BUF_TYPE_VIDEO_OVERLAY:
DEB_EE(("V4L2_BUF_TYPE_VIDEO_OVERLAY: dev:%p, fh:%p\n",dev,fh));
err = try_win(dev,&f->fmt.win);
if (0 != err)
return err;
down(&dev->lock);
fh->ov.win = f->fmt.win;
fh->ov.nclips = f->fmt.win.clipcount;
if (fh->ov.nclips > 16)
fh->ov.nclips = 16;
if (copy_from_user(fh->ov.clips,f->fmt.win.clips,sizeof(struct v4l2_clip)*fh->ov.nclips)) {
up(&dev->lock);
return -EFAULT;
}
/* fh->ov.fh is used to indicate that we have valid overlay informations, too */
fh->ov.fh = fh;
/* check if we have an active overlay */
if( vv->ov_data != NULL ) {
if( fh == vv->ov_data->fh) {
spin_lock_irqsave(&dev->slock,flags);
stop_preview(fh);
start_preview(fh);
spin_unlock_irqrestore(&dev->slock,flags);
}
}
up(&dev->lock);
return 0;
default:
DEB_D(("unknown format type '%d'\n",f->type));
return -EINVAL;
}
}
/********************************************************************************/
/* device controls */
static
struct v4l2_queryctrl controls[] = {
{
id: V4L2_CID_BRIGHTNESS,
name: "Brightness",
minimum: 0,
maximum: 255,
step: 1,
default_value: 128,
type: V4L2_CTRL_TYPE_INTEGER,
},{
id: V4L2_CID_CONTRAST,
name: "Contrast",
minimum: 0,
maximum: 127,
step: 1,
default_value: 64,
type: V4L2_CTRL_TYPE_INTEGER,
},{
id: V4L2_CID_SATURATION,
name: "Saturation",
minimum: 0,
maximum: 127,
step: 1,
default_value: 64,
type: V4L2_CTRL_TYPE_INTEGER,
},{
id: V4L2_CID_VFLIP,
name: "Vertical flip",
minimum: 0,
maximum: 1,
type: V4L2_CTRL_TYPE_BOOLEAN,
},{
id: V4L2_CID_HFLIP,
name: "Horizontal flip",
minimum: 0,
maximum: 1,
type: V4L2_CTRL_TYPE_BOOLEAN,
},
};
static
int NUM_CONTROLS = sizeof(controls)/sizeof(struct v4l2_queryctrl);
#define V4L2_CID_PRIVATE_LASTP1 (V4L2_CID_PRIVATE_BASE + 0)
static
struct v4l2_queryctrl* ctrl_by_id(int id)
{
int i;
for (i = 0; i < NUM_CONTROLS; i++)
if (controls[i].id == id)
return controls+i;
return NULL;
}
static
int get_control(struct saa7146_fh *fh, struct v4l2_control *c)
{
struct saa7146_dev *dev = fh->dev;
struct saa7146_vv *vv = dev->vv_data;
const struct v4l2_queryctrl* ctrl;
u32 value = 0;
ctrl = ctrl_by_id(c->id);
if (NULL == ctrl)
return -EINVAL;
switch (c->id) {
case V4L2_CID_BRIGHTNESS:
value = saa7146_read(dev, BCS_CTRL);
c->value = 0xff & (value >> 24);
break;
case V4L2_CID_CONTRAST:
value = saa7146_read(dev, BCS_CTRL);
c->value = 0x7f & (value >> 16);
break;
case V4L2_CID_SATURATION:
value = saa7146_read(dev, BCS_CTRL);
c->value = 0x7f & (value >> 0);
break;
case V4L2_CID_VFLIP:
c->value = vv->vflip;
break;
case V4L2_CID_HFLIP:
c->value = vv->hflip;
break;
default:
return -EINVAL;
}
return 0;
}
static
int set_control(struct saa7146_fh *fh, struct v4l2_control *c)
{
struct saa7146_dev *dev = fh->dev;
struct saa7146_vv *vv = dev->vv_data;
const struct v4l2_queryctrl* ctrl;
unsigned long flags;
int restart_overlay = 0;
ctrl = ctrl_by_id(c->id);
if (NULL == ctrl) {
DEB_D(("unknown control %d\n",c->id));
return -EINVAL;
}
switch (ctrl->type) {
case V4L2_CTRL_TYPE_BOOLEAN:
case V4L2_CTRL_TYPE_MENU:
case V4L2_CTRL_TYPE_INTEGER:
if (c->value < ctrl->minimum)
c->value = ctrl->minimum;
if (c->value > ctrl->maximum)
c->value = ctrl->maximum;
break;
default:
/* nothing */;
};
switch (c->id) {
case V4L2_CID_BRIGHTNESS: {
u32 value = saa7146_read(dev, BCS_CTRL);
value &= 0x00ffffff;
value |= (c->value << 24);
saa7146_write(dev, BCS_CTRL, value);
saa7146_write(dev, MC2, MASK_22 | MASK_06 );
break;
}
case V4L2_CID_CONTRAST: {
u32 value = saa7146_read(dev, BCS_CTRL);
value &= 0xff00ffff;
value |= (c->value << 16);
saa7146_write(dev, BCS_CTRL, value);
saa7146_write(dev, MC2, MASK_22 | MASK_06 );
break;
}
case V4L2_CID_SATURATION: {
u32 value = saa7146_read(dev, BCS_CTRL);
value &= 0xffffff00;
value |= (c->value << 0);
saa7146_write(dev, BCS_CTRL, value);
saa7146_write(dev, MC2, MASK_22 | MASK_06 );
break;
}
case V4L2_CID_HFLIP:
/* fixme: we can supfhrt changing VFLIP and HFLIP here... */
if( 0 != vv->streaming ) {
DEB_D(("V4L2_CID_HFLIP while active capture.\n"));
return -EINVAL;
}
vv->hflip = c->value;
restart_overlay = 1;
break;
case V4L2_CID_VFLIP:
if( 0 != vv->streaming ) {
DEB_D(("V4L2_CID_VFLIP while active capture.\n"));
return -EINVAL;
}
vv->vflip = c->value;
restart_overlay = 1;
break;
default: {
return -EINVAL;
}
}
if( 0 != restart_overlay ) {
if( 0 != vv->ov_data ) {
if( fh == vv->ov_data->fh ) {
spin_lock_irqsave(&dev->slock,flags);
stop_preview(fh);
start_preview(fh);
spin_unlock_irqrestore(&dev->slock,flags);
}
}
}
return 0;
}
/********************************************************************************/
/* common pagetable functions */
static
int saa7146_pgtable_build(struct saa7146_dev *dev, struct saa7146_buf *buf)
{
struct pci_dev *pci = dev->pci;
struct scatterlist *list = buf->vb.dma.sglist;
int length = buf->vb.dma.sglen;
struct saa7146_format *sfmt = format_by_fourcc(dev,buf->fmt->pixelformat);
DEB_EE(("dev:%p, buf:%p\n",dev,buf));
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];
u32 *ptr1, *ptr2, *ptr3;
u32 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;
printk("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;
printk("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 = 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 = buf->vb.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];
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;
unsigned long flags;
DEB_EE(("dev:%p, fh:%p\n",dev,fh));
if( fh == vv->streaming ) {
DEB_S(("already capturing.\n"));
return 0;
}
if( vv->streaming != 0 ) {
DEB_S(("already capturing, but in another open.\n"));
return -EBUSY;
}
/* fixme: check for planar formats here, if we will interfere with
vbi capture for example */
spin_lock_irqsave(&dev->slock,flags);
/* clear out beginning of streaming bit */
saa7146_write(dev, MC2, MASK_27 );
/* enable rps0 irqs */
IER_ENABLE(dev, MASK_27);
vv->streaming = fh;
spin_unlock_irqrestore(&dev->slock,flags);
return 0;
}
static
int video_end(struct saa7146_fh *fh)
{
struct saa7146_dev *dev = fh->dev;
struct saa7146_vv *vv = dev->vv_data;
unsigned long flags;
DEB_EE(("dev:%p, fh:%p\n",dev,fh));
if( vv->streaming != fh ) {
DEB_S(("not capturing.\n"));
return -EINVAL;
}
spin_lock_irqsave(&dev->slock,flags);
/* disable rps0 */
saa7146_write(dev, MC1, MASK_28);
/* disable rps0 irqs */
IER_DISABLE(dev, MASK_27);
// fixme: only used formats here!
/* fixme: look at planar formats here, especially at the
shutdown of planar formats! */
/* shut down all used video dma transfers */
/* fixme: what about the budget-dvb cards? they use
video-dma3, but video_end should not get called anyway ...*/
saa7146_write(dev, MC1, 0x00700000);
vv->streaming = NULL;
spin_unlock_irqrestore(&dev->slock, flags);
return 0;
}
/*
* This function is _not_ called directly, but from
* video_generic_ioctl (and maybe others). userspace
* copying is done already, arg is a kernel fhinter.
*/
int saa7146_video_do_ioctl(struct inode *inode, struct file *file, unsigned int cmd, void *arg)
{
struct saa7146_fh *fh = file->private_data;
struct saa7146_dev *dev = fh->dev;
struct saa7146_vv *vv = dev->vv_data;
unsigned long flags;
int err = 0, result = 0, ee = 0;
struct saa7146_use_ops *ops;
struct videobuf_queue *q;
/* check if extension handles the command */
for(ee = 0; dev->ext->ext_vv_data->ioctls[ee].flags != 0; ee++) {
if( cmd == dev->ext->ext_vv_data->ioctls[ee].cmd )
break;
}
if( 0 != (dev->ext->ext_vv_data->ioctls[ee].flags & SAA7146_EXCLUSIVE) ) {
DEB_D(("extension handles ioctl exclusive.\n"));
result = dev->ext->ext_vv_data->ioctl(dev, cmd, arg);
return result;
}
if( 0 != (dev->ext->ext_vv_data->ioctls[ee].flags & SAA7146_BEFORE) ) {
DEB_D(("extension handles ioctl before.\n"));
result = dev->ext->ext_vv_data->ioctl(dev, cmd, arg);
if( -EAGAIN != result ) {
return result;
}
}
/* fixme: add handle "after" case (is it still needed?) */
switch (fh->type) {
case V4L2_BUF_TYPE_VIDEO_CAPTURE: {
ops = &saa7146_video_uops;
q = &fh->video_q;
break;
}
case V4L2_BUF_TYPE_VBI_CAPTURE: {
ops = &saa7146_vbi_uops;
q = &fh->vbi_q;
break;
}
default:
BUG();
return 0;
}
switch (cmd) {
case VIDIOC_QUERYCAP:
{
struct v4l2_capability *cap = arg;
memset(cap,0,sizeof(*cap));
DEB_EE(("VIDIOC_QUERYCAP\n"));
strcpy(cap->driver, "saa7146 v4l2");
strncpy(cap->card, dev->ext->name, sizeof(cap->card));
sprintf(cap->bus_info,"PCI:%s",dev->pci->slot_name);
cap->version = SAA7146_VERSION_CODE;
cap->capabilities =
V4L2_CAP_VIDEO_CAPTURE |
V4L2_CAP_VIDEO_OVERLAY |
V4L2_CAP_READWRITE |
V4L2_CAP_STREAMING;
cap->capabilities |= dev->ext->ext_vv_data->capabilities;
return 0;
}
case VIDIOC_G_FBUF:
{
struct v4l2_framebuffer *fb = arg;
DEB_EE(("VIDIOC_G_FBUF\n"));
*fb = vv->ov_fb;
fb->capability = V4L2_FBUF_CAP_LIST_CLIPPING;
return 0;
}
case VIDIOC_S_FBUF:
{
struct v4l2_framebuffer *fb = arg;
struct saa7146_format *fmt;
DEB_EE(("VIDIOC_S_FBUF\n"));
/*
if(!capable(CAP_SYS_ADMIN)) { // && !capable(CAP_SYS_RAWIO)) {
DEB_D(("VIDIOC_S_FBUF: not CAP_SYS_ADMIN or CAP_SYS_RAWIO.\n"));
return -EPERM;
}
*/
if( 0 != vv->ov_data ) {
DEB_D(("VIDIOC_S_FBUF: overlay is active.\n"));
return -EPERM;
}
/* check args */
fmt = format_by_fourcc(dev,fb->fmt.pixelformat);
if (NULL == fmt) {
return -EINVAL;
}
/* ok, accept it */
vv->ov_fb = *fb;
vv->ov_fmt = fmt;
if (0 == vv->ov_fb.fmt.bytesperline)
vv->ov_fb.fmt.bytesperline =
vv->ov_fb.fmt.width*fmt->depth/8;
return 0;
}
case VIDIOC_ENUM_FMT:
{
struct v4l2_fmtdesc *f = arg;
int index;
switch (f->type) {
case V4L2_BUF_TYPE_VIDEO_CAPTURE:
case V4L2_BUF_TYPE_VIDEO_OVERLAY: {
index = f->index;
if (index < 0 || index >= NUM_FORMATS) {
return -EINVAL;
}
memset(f,0,sizeof(*f));
f->index = index;
strncpy(f->description,formats[index].name,31);
f->pixelformat = formats[index].pixelformat;
break;
}
default:
return -EINVAL;
}
DEB_EE(("VIDIOC_ENUMSTD: type:%d, index:%d\n",f->type,f->index));
return 0;
}
case VIDIOC_QUERYCTRL:
{
const struct v4l2_queryctrl *ctrl;
struct v4l2_queryctrl *c = arg;
if ((c->id < V4L2_CID_BASE ||
c->id >= V4L2_CID_LASTP1) &&
(c->id < V4L2_CID_PRIVATE_BASE ||
c->id >= V4L2_CID_PRIVATE_LASTP1))
return -EINVAL;
ctrl = ctrl_by_id(c->id);
if( NULL == ctrl ) {
c->flags = V4L2_CTRL_FLAG_DISABLED;
return 0;
}
DEB_EE(("VIDIOC_QUERYCTRL: id:%d\n",c->id));
*c = *ctrl;
return 0;
}
case VIDIOC_G_CTRL: {
DEB_EE(("VIDIOC_G_CTRL\n"));
return get_control(fh,arg);
}
case VIDIOC_S_CTRL:
{
DEB_EE(("VIDIOC_S_CTRL\n"));
down(&dev->lock);
err = set_control(fh,arg);
up(&dev->lock);
return err;
}
case VIDIOC_G_PARM:
{
struct v4l2_streamparm *parm = arg;
if( parm->type != V4L2_BUF_TYPE_VIDEO_CAPTURE ) {
return -EINVAL;
}
memset(&parm->parm.capture,0,sizeof(struct v4l2_captureparm));
parm->parm.capture.readbuffers = 1;
// fixme: only for PAL!
parm->parm.capture.timeperframe.numerator = 1;
parm->parm.capture.timeperframe.denominator = 25;
return 0;
}
case VIDIOC_G_FMT:
{
struct v4l2_format *f = arg;
DEB_EE(("VIDIOC_G_FMT\n"));
return g_fmt(fh,f);
}
case VIDIOC_S_FMT:
{
struct v4l2_format *f = arg;
DEB_EE(("VIDIOC_S_FMT\n"));
return s_fmt(fh,f);
}
case VIDIOC_TRY_FMT:
{
struct v4l2_format *f = arg;
DEB_EE(("VIDIOC_TRY_FMT\n"));
return try_fmt(fh,f);
}
case VIDIOC_G_STD:
{
v4l2_std_id *id = arg;
DEB_EE(("VIDIOC_G_STD\n"));
*id = 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->ext_vv_data->num_stds ) {
DEB_EE(("VIDIOC_ENUMSTD: index:%d\n",e->index));
return v4l2_video_std_construct(e, dev->ext->ext_vv_data->stds[e->index].id, dev->ext->ext_vv_data->stds[e->index].name);
}
return -EINVAL;
}
case VIDIOC_S_STD:
{
v4l2_std_id *id = arg;
int i;
int restart_overlay = 0;
int found = 0;
struct saa7146_fh *ov_fh = NULL;
if( 0 != vv->streaming ) {
return -EBUSY;
}
down(&dev->lock);
if( vv->ov_data != NULL ) {
ov_fh = vv->ov_data->fh;
stop_preview(ov_fh);
restart_overlay = 1;
}
for(i = 0; i < dev->ext->ext_vv_data->num_stds; i++)
if (*id & dev->ext->ext_vv_data->stds[i].id)
break;
if (i != dev->ext->ext_vv_data->num_stds) {
vv->standard = &dev->ext->ext_vv_data->stds[i];
if( NULL != dev->ext->ext_vv_data->std_callback )
dev->ext->ext_vv_data->std_callback(dev, vv->standard);
found = 1;
}
if( 0 != restart_overlay ) {
start_preview(ov_fh);
}
up(&dev->lock);
if( 0 == 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;
}
case VIDIOC_OVERLAY:
{
int on = *(int *)arg;
int err = 0;
if( NULL == vv->ov_fmt ) {
DEB_D(("VIDIOC_OVERLAY: no framebuffer informations. call S_FBUF first!\n"));
return -EAGAIN;
}
DEB_D(("VIDIOC_OVERLAY on:%d\n",on));
if( 0 != on ) {
if( vv->ov_data != NULL ) {
if( fh != vv->ov_data->fh) {
return -EAGAIN;
}
}
spin_lock_irqsave(&dev->slock,flags);
err = start_preview(fh);
spin_unlock_irqrestore(&dev->slock,flags);
} else {
if( vv->ov_data != NULL ) {
if( fh != vv->ov_data->fh) {
return -EAGAIN;
}
}
spin_lock_irqsave(&dev->slock,flags);
err = stop_preview(fh);
spin_unlock_irqrestore(&dev->slock,flags);
}
return err;
}
case VIDIOC_REQBUFS: {
DEB_D(("VIDIOC_REQBUFS \n"));
return videobuf_reqbufs(file,q,arg);
}
case VIDIOC_QUERYBUF: {
DEB_D(("VIDIOC_QUERYBUF \n"));
return videobuf_querybuf(q,arg);
}
case VIDIOC_QBUF: {
DEB_D(("VIDIOC_QBUF \n"));
return videobuf_qbuf(file,q,arg);
}
case VIDIOC_DQBUF: {
DEB_D(("VIDIOC_DQBUF \n"));
return videobuf_dqbuf(file,q,arg);
}
case VIDIOC_STREAMON: {
DEB_D(("VIDIOC_STREAMON \n"));
if( 0 != ops->capture_begin ) {
if( 0 != (err = ops->capture_begin(fh))) {
return err;
}
}
err = videobuf_streamon(file,q);
return err;
}
case VIDIOC_STREAMOFF: {
DEB_D(("VIDIOC_STREAMOFF \n"));
if( 0 != ops->capture_end ) {
ops->capture_end(fh);
}
err = videobuf_streamoff(file,q);
return 0;
}
case VIDIOCGMBUF:
{
struct video_mbuf *mbuf = arg;
struct videobuf_queue *q;
int i;
/* fixme: number of capture buffers and sizes for v4l apps */
int gbuffers = 2;
int gbufsize = 768*576*4;
DEB_D(("VIDIOCGMBUF \n"));
q = &fh->video_q;
down(&q->lock);
err = videobuf_mmap_setup(file,q,gbuffers,gbufsize);
if (err < 0) {
up(&q->lock);
return err;
}
memset(mbuf,0,sizeof(*mbuf));
mbuf->frames = gbuffers;
mbuf->size = gbuffers * gbufsize;
for (i = 0; i < gbuffers; i++)
mbuf->offsets[i] = i * gbufsize;
up(&q->lock);
return 0;
}
default:
return v4l_compat_translate_ioctl(inode,file,cmd,arg,
saa7146_video_do_ioctl);
}
return 0;
}
/*********************************************************************************/
/* 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 = STATE_ACTIVE;
saa7146_set_capture(dev,buf,next);
mod_timer(&vv->video_q.timeout, jiffies+BUFFER_TIMEOUT);
return 0;
}
static
int buffer_prepare(struct file *file, struct videobuf_buffer *vb, enum v4l2_field field)
{
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;
/* sanity checks */
if (fh->video_fmt.width < 64 ||
fh->video_fmt.height < 64 ||
fh->video_fmt.width > vv->standard->h_max_out ||
fh->video_fmt.height > vv->standard->v_max_out) {
DEB_D(("w (%d) / h (%d) out of bounds.\n",fh->video_fmt.width,fh->video_fmt.height));
return -EINVAL;
}
size = fh->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",
fh->video_fmt.width,fh->video_fmt.height,size,v4l2_field_names[fh->video_fmt.field]));
if (buf->vb.width != fh->video_fmt.width ||
buf->vb.height != fh->video_fmt.height ||
buf->vb.size != size ||
buf->vb.field != field ||
buf->vb.field != fh->video_fmt.field ||
buf->fmt != &fh->video_fmt) {
saa7146_dma_free(dev,buf);
}
if (STATE_NEEDS_INIT == buf->vb.state) {
struct saa7146_format *sfmt;
buf->vb.width = fh->video_fmt.width;
buf->vb.height = fh->video_fmt.height;
buf->vb.size = size;
buf->vb.field = field;
buf->fmt = &fh->video_fmt;
buf->vb.field = fh->video_fmt.field;
sfmt = format_by_fourcc(dev,buf->fmt->pixelformat);
if( 0 != IS_PLANAR(sfmt->trans)) {
saa7146_pgtable_free(dev->pci, &buf->pt[0]);
saa7146_pgtable_free(dev->pci, &buf->pt[1]);
saa7146_pgtable_free(dev->pci, &buf->pt[2]);
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_free(dev->pci, &buf->pt[0]);
saa7146_pgtable_alloc(dev->pci, &buf->pt[0]);
}
err = videobuf_iolock(dev->pci,&buf->vb);
if (err)
goto oops;
err = saa7146_pgtable_build(dev,buf);
if (err)
goto oops;
}
buf->vb.state = STATE_PREPARED;
buf->activate = buffer_activate;
return 0;
oops:
DEB_D(("error out.\n"));
saa7146_dma_free(dev,buf);
return err;
}
static
int buffer_setup(struct file *file, unsigned int *count, unsigned int *size)
{
struct saa7146_fh *fh = file->private_data;
if (0 == *count || *count > MAX_SAA7146_CAPTURE_BUFFERS)
*count = MAX_SAA7146_CAPTURE_BUFFERS;
*size = fh->video_fmt.sizeimage;
/* check if we exceed the "memory" parameter */
if( (*count * *size) > (memory*1048576) ) {
*count = (memory*1048576) / *size;
}
DEB_CAP(("%d buffers, %d bytes each.\n",*count,*size));
return 0;
}
static
void buffer_queue(struct file *file, struct videobuf_buffer *vb)
{
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_q,buf);
}
static
void buffer_release(struct file *file, struct videobuf_buffer *vb)
{
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,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_q.queue);
init_timer(&vv->video_q.timeout);
vv->video_q.timeout.function = saa7146_buffer_timeout;
vv->video_q.timeout.data = (unsigned long)(&vv->video_q);
vv->video_q.dev = dev;
/* set some default values */
vv->standard = &dev->ext->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
void video_open(struct saa7146_dev *dev, struct saa7146_fh *fh)
{
struct saa7146_format *sfmt;
fh->video_fmt.width = 384;
fh->video_fmt.height = 288;
fh->video_fmt.pixelformat = V4L2_PIX_FMT_BGR24;
fh->video_fmt.field = V4L2_FIELD_ANY;
sfmt = format_by_fourcc(dev,fh->video_fmt.pixelformat);
fh->video_fmt.sizeimage = (fh->video_fmt.width * fh->video_fmt.height * sfmt->depth)/8;
videobuf_queue_init(&fh->video_q, &video_qops,
dev->pci, &dev->slock,
V4L2_BUF_TYPE_VIDEO_CAPTURE,
V4L2_FIELD_INTERLACED,
sizeof(struct saa7146_buf));
init_MUTEX(&fh->video_q.lock);
}
static
void video_close(struct saa7146_dev *dev, struct saa7146_fh *fh, struct file *file)
{
struct saa7146_vv *vv = dev->vv_data;
unsigned long flags;
if( 0 != vv->ov_data ) {
if( fh == vv->ov_data->fh ) {
spin_lock_irqsave(&dev->slock,flags);
stop_preview(fh);
spin_unlock_irqrestore(&dev->slock,flags);
}
}
if( fh == vv->streaming ) {
video_end(fh);
}
videobuf_queue_cancel(file,&fh->video_q);
}
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_q;
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,STATE_DONE);
}
saa7146_buffer_next(dev,q,0);
spin_unlock(&dev->slock);
}
static
ssize_t video_read(struct file *file, char *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;
int restart_overlay = 0;
struct saa7146_fh *ov_fh = NULL;
DEB_EE(("called.\n"));
if( vv->ov_data != NULL ) {
ov_fh = vv->ov_data->fh;
stop_preview(ov_fh);
restart_overlay = 1;
}
if( 0 != video_begin(fh)) {
return -EAGAIN;
}
ret = videobuf_read_one(file,&fh->video_q , data, count, ppos);
video_end(fh);
/* restart overlay if it was active before */
if( 0 != restart_overlay ) {
start_preview(ov_fh);
}
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,
.capture_begin = video_begin,
.capture_end = video_end,
};
EXPORT_SYMBOL_GPL(saa7146_video_uops);
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment