/shark/trunk/drivers/bttv/include/drivers/bttv.h |
---|
17,8 → 17,8 |
#include "drivers/compbttv.h" |
#include "drivers/videodev.h" |
#include "drivers/i2c.h" |
#include "drivers/i2c-algo-bit.h" |
#include "linux/i2c.h" |
#include "linux/i2c-algo-bit.h" |
#include "drivers/bt848.h" |
#include "drivers/audiochip.h" |
/shark/trunk/drivers/bttv/makefile |
---|
12,7 → 12,7 |
OBJS = gpio.o bttv-if.o bttv-cards.o bttv-driver.o fg.o |
OTHERINCL += -I$(BASE)/drivers/bttv/include -I$(BASE)/drivers/linuxc24/include -I$(BASE)/drivers/i2c/include |
OTHERINCL += -I$(BASE)/drivers/bttv/include -I$(BASE)/drivers/linuxc24/include |
C_OPT += -D__KERNEL__ |
/shark/trunk/drivers/cm7326/saa7146_core.c |
---|
0,0 → 1,583 |
/* |
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> |
/* 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 |
/**************************************************************************** |
* gpio and debi helper functions |
****************************************************************************/ |
/* 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); |
} |
/* This DEBI code is based on the saa7146 Stradis driver by Nathan Laredo */ |
int saa7146_wait_for_debi_done(struct saa7146_dev *dev) |
{ |
int start; |
/* wait for registers to be programmed */ |
start = jiffies; |
while (1) { |
if (saa7146_read(dev, MC2) & 2) |
break; |
if (jiffies-start > HZ/20) { |
DEB_S(("timed out while waiting for registers getting programmed\n")); |
return -ETIMEDOUT; |
} |
} |
/* wait for transfer to complete */ |
start = jiffies; |
while (1) { |
if (!(saa7146_read(dev, PSR) & SPCI_DEBI_S)) |
break; |
saa7146_read(dev, MC2); |
if (jiffies-start > HZ/4) { |
DEB_S(("timed out while waiting for transfer completion\n")); |
return -ETIMEDOUT; |
} |
} |
return 0; |
} |
/**************************************************************************** |
* general helper functions |
****************************************************************************/ |
/* this is videobuf_vmalloc_to_sg() from video-buf.c |
make sure virt has been allocated with vmalloc_32(), otherwise the BUG() |
may be triggered on highmem machines */ |
static struct scatterlist* vmalloc_to_sg(unsigned char *virt, int nr_pages) |
{ |
struct scatterlist *sglist; |
struct page *pg; |
int i; |
sglist = 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) |
{ |
int pages = (length+PAGE_SIZE-1)/PAGE_SIZE; |
char *mem = vmalloc_32(length); |
int slen = 0; |
if (NULL == mem) { |
return NULL; |
} |
if (!(pt->slist = vmalloc_to_sg(mem, pages))) { |
vfree(mem); |
return NULL; |
} |
if (saa7146_pgtable_alloc(pci, pt)) { |
kfree(pt->slist); |
pt->slist = NULL; |
vfree(mem); |
return NULL; |
} |
slen = pci_map_sg(pci,pt->slist,pages,PCI_DMA_FROMDEVICE); |
if (0 != saa7146_pgtable_build_single(pci, pt, pt->slist, slen)) { |
return NULL; |
} |
return mem; |
} |
void saa7146_pgtable_free(struct pci_dev *pci, struct saa7146_pgtable *pt) |
{ |
if (NULL == pt->cpu) |
return; |
pci_free_consistent(pci, pt->size, pt->cpu, pt->dma); |
pt->cpu = NULL; |
if (NULL != pt->slist) { |
kfree(pt->slist); |
pt->slist = NULL; |
} |
} |
int saa7146_pgtable_alloc(struct pci_dev *pci, struct saa7146_pgtable *pt) |
{ |
u32 *cpu; |
dma_addr_t dma_addr; |
cpu = pci_alloc_consistent(pci, SAA7146_PGTABLE_SIZE, &dma_addr); |
if (NULL == cpu) { |
return -ENOMEM; |
} |
pt->size = SAA7146_PGTABLE_SIZE; |
pt->cpu = cpu; |
pt->dma = dma_addr; |
return 0; |
} |
int saa7146_pgtable_build_single(struct pci_dev *pci, struct saa7146_pgtable *pt, |
struct scatterlist *list, int sglen ) |
{ |
u32 *ptr, fill; |
int nr_pages = 0; |
int i,p; |
BUG_ON( 0 == sglen); |
if (list->offset > PAGE_SIZE) { |
DEB_D(("offset > PAGE_SIZE. this should not happen.")); |
return -EINVAL; |
} |
/* if we have a user buffer, the first page may not be |
aligned to a page boundary. */ |
pt->offset = list->offset; |
ptr = pt->cpu; |
for (i = 0; i < sglen; i++, list++) { |
/* |
printk("i:%d, adr:0x%08x, len:%d, offset:%d\n", i,sg_dma_address(list), sg_dma_len(list), list->offset); |
*/ |
for (p = 0; p * 4096 < list->length; p++, ptr++) { |
*ptr = sg_dma_address(list) + p * 4096; |
nr_pages++; |
} |
} |
/* safety; fill the page table up with the last valid page */ |
fill = *(ptr-1); |
for(i=nr_pages;i<1024;i++) { |
*ptr++ = fill; |
} |
/* |
ptr = pt->cpu; |
printk("offset: %d\n",pt->offset); |
for(i=0;i<5;i++) { |
printk("ptr1 %d: 0x%08x\n",i,ptr[i]); |
} |
*/ |
return 0; |
} |
/********************************************************************************/ |
/* 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 irqreturn_t 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 IRQ_NONE; |
} |
saa7146_write(dev, ISR, 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); |
} |
return IRQ_HANDLED; |
} |
/*********************************************************************************/ |
/* 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 all 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->d_rps0.cpu_addr = pci_alloc_consistent(dev->pci, SAA7146_RPS_MEM, &dev->d_rps0.dma_handle); |
if( NULL == dev->d_rps0.cpu_addr ) { |
err = -ENOMEM; |
goto kmalloc_error_1; |
} |
memset(dev->d_rps0.cpu_addr, 0x0, SAA7146_RPS_MEM); |
dev->d_rps1.cpu_addr = pci_alloc_consistent(dev->pci, SAA7146_RPS_MEM, &dev->d_rps1.dma_handle); |
if( NULL == dev->d_rps1.cpu_addr ) { |
err = -ENOMEM; |
goto kmalloc_error_2; |
} |
memset(dev->d_rps1.cpu_addr, 0x0, SAA7146_RPS_MEM); |
dev->d_i2c.cpu_addr = pci_alloc_consistent(dev->pci, SAA7146_RPS_MEM, &dev->d_i2c.dma_handle); |
if( NULL == dev->d_i2c.cpu_addr ) { |
err = -ENOMEM; |
goto kmalloc_error_3; |
} |
memset(dev->d_i2c.cpu_addr, 0x0, SAA7146_RPS_MEM); |
/* the rest + print status message */ |
/* create a nice device name */ |
sprintf(&dev->name[0], "saa7146 (%d)",saa7146_num); |
INFO(("found saa7146 @ mem %p (revision %d, irq %d) (0x%04x,0x%04x).\n", 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); |
/* set some sane pci arbitrition values */ |
saa7146_write(dev, PCI_BT_V1, 0x1c00101f); |
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++; |
err = 0; |
goto out; |
attach_error: |
probe_error: |
pci_set_drvdata(pci,NULL); |
pci_free_consistent(dev->pci, SAA7146_RPS_MEM, dev->d_i2c.cpu_addr, dev->d_i2c.dma_handle); |
kmalloc_error_3: |
pci_free_consistent(dev->pci, SAA7146_RPS_MEM, dev->d_rps1.cpu_addr, dev->d_rps1.dma_handle); |
kmalloc_error_2: |
pci_free_consistent(dev->pci, SAA7146_RPS_MEM, dev->d_rps0.cpu_addr, dev->d_rps0.dma_handle); |
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 */ |
pci_free_consistent(dev->pci, SAA7146_RPS_MEM, dev->d_i2c.cpu_addr, dev->d_i2c.dma_handle); |
pci_free_consistent(dev->pci, SAA7146_RPS_MEM, dev->d_rps1.cpu_addr, dev->d_rps1.dma_handle); |
pci_free_consistent(dev->pci, SAA7146_RPS_MEM, dev->d_rps0.cpu_addr, dev->d_rps0.dma_handle); |
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_wait_for_debi_done); |
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"); |
/shark/trunk/drivers/cm7326/hexium_orion.c |
---|
0,0 → 1,514 |
/* |
hexium_orion.c - v4l2 driver for the Hexium Orion frame grabber cards |
Visit http://www.mihu.de/linux/saa7146/ and follow the link |
to "hexium" for further details about this card. |
Copyright (C) 2003 Michael Hunold <michael@mihu.de> |
This program is free software; you can redistribute it and/or modify |
it under the terms of the GNU General Public License as published by |
the Free Software Foundation; either version 2 of the License, or |
(at your option) any later version. |
This program is distributed in the hope that it will be useful, |
but WITHOUT ANY WARRANTY; without even the implied warranty of |
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
GNU General Public License for more details. |
You should have received a copy of the GNU General Public License |
along with this program; if not, write to the Free Software |
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. |
*/ |
#define DEBUG_VARIABLE debug |
#include <media/saa7146_vv.h> |
static int debug = 0; |
MODULE_PARM(debug, "i"); |
MODULE_PARM_DESC(debug, "debug verbosity"); |
/* global variables */ |
int hexium_num = 0; |
#define HEXIUM_HV_PCI6_ORION 1 |
#define HEXIUM_ORION_1SVHS_3BNC 2 |
#define HEXIUM_ORION_4BNC 3 |
#define HEXIUM_INPUTS 9 |
static struct v4l2_input hexium_inputs[HEXIUM_INPUTS] = { |
{ 0, "CVBS 1", V4L2_INPUT_TYPE_CAMERA, 2, 0, V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, 0 }, |
{ 1, "CVBS 2", V4L2_INPUT_TYPE_CAMERA, 2, 0, V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, 0 }, |
{ 2, "CVBS 3", V4L2_INPUT_TYPE_CAMERA, 2, 0, V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, 0 }, |
{ 3, "CVBS 4", V4L2_INPUT_TYPE_CAMERA, 2, 0, V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, 0 }, |
{ 4, "CVBS 5", V4L2_INPUT_TYPE_CAMERA, 2, 0, V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, 0 }, |
{ 5, "CVBS 6", V4L2_INPUT_TYPE_CAMERA, 2, 0, V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, 0 }, |
{ 6, "Y/C 1", V4L2_INPUT_TYPE_CAMERA, 2, 0, V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, 0 }, |
{ 7, "Y/C 2", V4L2_INPUT_TYPE_CAMERA, 2, 0, V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, 0 }, |
{ 8, "Y/C 3", V4L2_INPUT_TYPE_CAMERA, 2, 0, V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, 0 }, |
}; |
#define HEXIUM_AUDIOS 0 |
struct hexium_data |
{ |
s8 adr; |
u8 byte; |
}; |
static struct saa7146_extension_ioctls ioctls[] = { |
{ VIDIOC_G_INPUT, SAA7146_EXCLUSIVE }, |
{ VIDIOC_S_INPUT, SAA7146_EXCLUSIVE }, |
{ VIDIOC_ENUMINPUT, SAA7146_EXCLUSIVE }, |
{ VIDIOC_S_STD, SAA7146_AFTER }, |
{ 0, 0 } |
}; |
struct hexium |
{ |
int type; |
struct video_device video_dev; |
struct i2c_adapter i2c_adapter; |
int cur_input; /* current input */ |
}; |
/* Philips SAA7110 decoder default registers */ |
static u8 hexium_saa7110[53]={ |
/*00*/ 0x4C,0x3C,0x0D,0xEF,0xBD,0xF0,0x00,0x00, |
/*08*/ 0xF8,0xF8,0x60,0x60,0x40,0x86,0x18,0x90, |
/*10*/ 0x00,0x2C,0x40,0x46,0x42,0x1A,0xFF,0xDA, |
/*18*/ 0xF0,0x8B,0x00,0x00,0x00,0x00,0x00,0x00, |
/*20*/ 0xD9,0x17,0x40,0x41,0x80,0x41,0x80,0x4F, |
/*28*/ 0xFE,0x01,0x0F,0x0F,0x03,0x01,0x81,0x03, |
/*30*/ 0x44,0x75,0x01,0x8C,0x03 |
}; |
static struct { |
struct hexium_data data[8]; |
} hexium_input_select[] = { |
{ |
{ /* cvbs 1 */ |
{ 0x06, 0x00 }, |
{ 0x20, 0xD9 }, |
{ 0x21, 0x17 }, // 0x16, |
{ 0x22, 0x40 }, |
{ 0x2C, 0x03 }, |
{ 0x30, 0x44 }, |
{ 0x31, 0x75 }, // ?? |
{ 0x21, 0x16 }, // 0x03, |
} |
}, { |
{ /* cvbs 2 */ |
{ 0x06, 0x00 }, |
{ 0x20, 0x78 }, |
{ 0x21, 0x07 }, // 0x03, |
{ 0x22, 0xD2 }, |
{ 0x2C, 0x83 }, |
{ 0x30, 0x60 }, |
{ 0x31, 0xB5 }, // ? |
{ 0x21, 0x03 }, |
} |
}, { |
{ /* cvbs 3 */ |
{ 0x06, 0x00 }, |
{ 0x20, 0xBA }, |
{ 0x21, 0x07 }, // 0x05, |
{ 0x22, 0x91 }, |
{ 0x2C, 0x03 }, |
{ 0x30, 0x60 }, |
{ 0x31, 0xB5 }, // ?? |
{ 0x21, 0x05 }, // 0x03, |
} |
}, { |
{ /* cvbs 4 */ |
{ 0x06, 0x00 }, |
{ 0x20, 0xD8 }, |
{ 0x21, 0x17 }, // 0x16, |
{ 0x22, 0x40 }, |
{ 0x2C, 0x03 }, |
{ 0x30, 0x44 }, |
{ 0x31, 0x75 }, // ?? |
{ 0x21, 0x16 }, // 0x03, |
} |
}, { |
{ /* cvbs 5 */ |
{ 0x06, 0x00 }, |
{ 0x20, 0xB8 }, |
{ 0x21, 0x07 }, // 0x05, |
{ 0x22, 0x91 }, |
{ 0x2C, 0x03 }, |
{ 0x30, 0x60 }, |
{ 0x31, 0xB5 }, // ?? |
{ 0x21, 0x05 }, // 0x03, |
} |
}, { |
{ /* cvbs 6 */ |
{ 0x06, 0x00 }, |
{ 0x20, 0x7C }, |
{ 0x21, 0x07 }, // 0x03 |
{ 0x22, 0xD2 }, |
{ 0x2C, 0x83 }, |
{ 0x30, 0x60 }, |
{ 0x31, 0xB5 }, // ?? |
{ 0x21, 0x03 }, |
} |
}, { |
{ /* y/c 1 */ |
{ 0x06, 0x80 }, |
{ 0x20, 0x59 }, |
{ 0x21, 0x17 }, |
{ 0x22, 0x42 }, |
{ 0x2C, 0xA3 }, |
{ 0x30, 0x44 }, |
{ 0x31, 0x75 }, |
{ 0x21, 0x12 }, |
} |
}, { |
{ /* y/c 2 */ |
{ 0x06, 0x80 }, |
{ 0x20, 0x9A }, |
{ 0x21, 0x17 }, |
{ 0x22, 0xB1 }, |
{ 0x2C, 0x13 }, |
{ 0x30, 0x60 }, |
{ 0x31, 0xB5 }, |
{ 0x21, 0x14 }, |
} |
}, { |
{ /* y/c 3 */ |
{ 0x06, 0x80 }, |
{ 0x20, 0x3C }, |
{ 0x21, 0x27 }, |
{ 0x22, 0xC1 }, |
{ 0x2C, 0x23 }, |
{ 0x30, 0x44 }, |
{ 0x31, 0x75 }, |
{ 0x21, 0x21 }, |
} |
} |
}; |
static struct saa7146_standard hexium_standards[] = { |
{ |
.name = "PAL", .id = V4L2_STD_PAL, |
.v_offset = 16, .v_field = 288, .v_calc = 576, |
.h_offset = 1, .h_pixels = 680, .h_calc = 680+1, |
.v_max_out = 576, .h_max_out = 768, |
}, { |
.name = "NTSC", .id = V4L2_STD_NTSC, |
.v_offset = 16, .v_field = 240, .v_calc = 480, |
.h_offset = 1, .h_pixels = 640, .h_calc = 641+1, |
.v_max_out = 480, .h_max_out = 640, |
}, { |
.name = "SECAM", .id = V4L2_STD_SECAM, |
.v_offset = 16, .v_field = 288, .v_calc = 576, |
.h_offset = 1, .h_pixels = 720, .h_calc = 720+1, |
.v_max_out = 576, .h_max_out = 768, |
} |
}; |
/* this is only called for old HV-PCI6/Orion cards |
without eeprom */ |
static int hexium_probe(struct saa7146_dev *dev) |
{ |
struct hexium *hexium = 0; |
union i2c_smbus_data data; |
int err = 0; |
DEB_EE((".\n")); |
/* there are no hexium orion cards with revision 0 saa7146s */ |
if (0 == dev->revision) { |
return -EFAULT; |
} |
hexium = (struct hexium *) kmalloc(sizeof(struct hexium), GFP_KERNEL); |
if (NULL == hexium) { |
printk("hexium_orion: hexium_probe: not enough kernel memory.\n"); |
return -ENOMEM; |
} |
memset(hexium, 0x0, sizeof(struct hexium)); |
saa7146_write(dev, DD1_INIT, 0x01000100); |
saa7146_write(dev, DD1_STREAM_B, 0x00000000); |
saa7146_write(dev, MC2, (MASK_09 | MASK_25 | MASK_10 | MASK_26)); |
saa7146_i2c_adapter_prepare(dev, &hexium->i2c_adapter, SAA7146_I2C_BUS_BIT_RATE_480); |
if (i2c_add_adapter(&hexium->i2c_adapter) < 0) { |
DEB_S(("cannot register i2c-device. skipping.\n")); |
kfree(hexium); |
return -EFAULT; |
} |
/* set SAA7110 control GPIO 0 */ |
saa7146_setgpio(dev, 0, SAA7146_GPIO_OUTHI); |
/* set HWControl GPIO number 2 */ |
saa7146_setgpio(dev, 2, SAA7146_GPIO_OUTHI); |
mdelay(10); |
/* detect newer Hexium Orion cards by subsystem ids */ |
if (0x17c8 == dev->pci->subsystem_vendor && 0x0101 == dev->pci->subsystem_device) { |
printk("hexium_orion: device is a Hexium Orion w/ 1 SVHS + 3 BNC inputs.\n"); |
/* we store the pointer in our private data field */ |
(struct hexium *) dev->ext_priv = hexium; |
hexium->type = HEXIUM_ORION_1SVHS_3BNC; |
return 0; |
} |
if (0x17c8 == dev->pci->subsystem_vendor && 0x2101 == dev->pci->subsystem_device) { |
printk("hexium_orion: device is a Hexium Orion w/ 4 BNC inputs.\n"); |
/* we store the pointer in our private data field */ |
(struct hexium *) dev->ext_priv = hexium; |
hexium->type = HEXIUM_ORION_4BNC; |
return 0; |
} |
/* check if this is an old hexium Orion card by looking at |
a saa7110 at address 0x4e */ |
if (0 == (err = i2c_smbus_xfer(&hexium->i2c_adapter, 0x4e, 0, I2C_SMBUS_READ, 0x00, I2C_SMBUS_BYTE_DATA, &data))) { |
printk("hexium_orion: device is a Hexium HV-PCI6/Orion (old).\n"); |
/* we store the pointer in our private data field */ |
(struct hexium *) dev->ext_priv = hexium; |
hexium->type = HEXIUM_HV_PCI6_ORION; |
return 0; |
} |
i2c_del_adapter(&hexium->i2c_adapter); |
kfree(hexium); |
return -EFAULT; |
} |
/* bring hardware to a sane state. this has to be done, just in case someone |
wants to capture from this device before it has been properly initialized. |
the capture engine would badly fail, because no valid signal arrives on the |
saa7146, thus leading to timeouts and stuff. */ |
static int hexium_init_done(struct saa7146_dev *dev) |
{ |
struct hexium *hexium = (struct hexium *) dev->ext_priv; |
union i2c_smbus_data data; |
int i = 0; |
DEB_D(("hexium_init_done called.\n")); |
/* initialize the helper ics to useful values */ |
for (i = 0; i < sizeof(hexium_saa7110); i++) { |
data.byte = hexium_saa7110[i]; |
if (0 != i2c_smbus_xfer(&hexium->i2c_adapter, 0x4e, 0, I2C_SMBUS_WRITE, i, I2C_SMBUS_BYTE_DATA, &data)) { |
printk("hexium_orion: failed for address 0x%02x\n", i); |
} |
} |
return 0; |
} |
static int hexium_set_input(struct hexium *hexium, int input) |
{ |
union i2c_smbus_data data; |
int i = 0; |
DEB_D((".\n")); |
for (i = 0; i < 8; i++) { |
int adr = hexium_input_select[input].data[i].adr; |
data.byte = hexium_input_select[input].data[i].byte; |
if (0 != i2c_smbus_xfer(&hexium->i2c_adapter, 0x4e, 0, I2C_SMBUS_WRITE, adr, I2C_SMBUS_BYTE_DATA, &data)) { |
return -1; |
} |
printk("%d: 0x%02x => 0x%02x\n",input, adr,data.byte); |
} |
return 0; |
} |
static struct saa7146_ext_vv vv_data; |
/* this function only gets called when the probing was successful */ |
static int hexium_attach(struct saa7146_dev *dev, struct saa7146_pci_extension_data *info) |
{ |
struct hexium *hexium = (struct hexium *) dev->ext_priv; |
DEB_EE((".\n")); |
saa7146_vv_init(dev, &vv_data); |
if (0 != saa7146_register_device(&hexium->video_dev, dev, "hexium orion", VFL_TYPE_GRABBER)) { |
printk("hexium_orion: cannot register capture v4l2 device. skipping.\n"); |
return -1; |
} |
printk("hexium_orion: found 'hexium orion' frame grabber-%d.\n", hexium_num); |
hexium_num++; |
/* the rest */ |
hexium->cur_input = 0; |
hexium_init_done(dev); |
return 0; |
} |
static int hexium_detach(struct saa7146_dev *dev) |
{ |
struct hexium *hexium = (struct hexium *) dev->ext_priv; |
DEB_EE(("dev:%p\n", dev)); |
saa7146_unregister_device(&hexium->video_dev, dev); |
saa7146_vv_release(dev); |
hexium_num--; |
i2c_del_adapter(&hexium->i2c_adapter); |
kfree(hexium); |
return 0; |
} |
static int hexium_ioctl(struct saa7146_fh *fh, unsigned int cmd, void *arg) |
{ |
struct saa7146_dev *dev = fh->dev; |
struct hexium *hexium = (struct hexium *) dev->ext_priv; |
/* |
struct saa7146_vv *vv = dev->vv_data; |
*/ |
switch (cmd) { |
case VIDIOC_ENUMINPUT: |
{ |
struct v4l2_input *i = arg; |
DEB_EE(("VIDIOC_ENUMINPUT %d.\n", i->index)); |
if (i->index < 0 || i->index >= HEXIUM_INPUTS) { |
return -EINVAL; |
} |
memcpy(i, &hexium_inputs[i->index], sizeof(struct v4l2_input)); |
DEB_D(("v4l2_ioctl: VIDIOC_ENUMINPUT %d.\n", i->index)); |
return 0; |
} |
case VIDIOC_G_INPUT: |
{ |
int *input = (int *) arg; |
*input = hexium->cur_input; |
DEB_D(("VIDIOC_G_INPUT: %d\n", *input)); |
return 0; |
} |
case VIDIOC_S_INPUT: |
{ |
int input = *(int *) arg; |
if (input < 0 || input >= HEXIUM_INPUTS) { |
return -EINVAL; |
} |
hexium->cur_input = input; |
hexium_set_input(hexium, input); |
return 0; |
} |
default: |
/* |
DEB_D(("hexium_ioctl() does not handle this ioctl.\n")); |
*/ |
return -ENOIOCTLCMD; |
} |
return 0; |
} |
static int std_callback(struct saa7146_dev *dev, struct saa7146_standard *std) |
{ |
return 0; |
} |
static struct saa7146_extension extension; |
static struct saa7146_pci_extension_data hexium_hv_pci6 = { |
.ext_priv = "Hexium HV-PCI6 / Orion", |
.ext = &extension, |
}; |
static struct saa7146_pci_extension_data hexium_orion_1svhs_3bnc = { |
.ext_priv = "Hexium HV-PCI6 / Orion (1 SVHS/3 BNC)", |
.ext = &extension, |
}; |
static struct saa7146_pci_extension_data hexium_orion_4bnc = { |
.ext_priv = "Hexium HV-PCI6 / Orion (4 BNC)", |
.ext = &extension, |
}; |
static struct pci_device_id pci_tbl[] = { |
{ |
.vendor = PCI_VENDOR_ID_PHILIPS, |
.device = PCI_DEVICE_ID_PHILIPS_SAA7146, |
.subvendor = 0x0000, |
.subdevice = 0x0000, |
.driver_data = (unsigned long) &hexium_hv_pci6, |
}, |
{ |
.vendor = PCI_VENDOR_ID_PHILIPS, |
.device = PCI_DEVICE_ID_PHILIPS_SAA7146, |
.subvendor = 0x17c8, |
.subdevice = 0x0101, |
.driver_data = (unsigned long) &hexium_orion_1svhs_3bnc, |
}, |
{ |
.vendor = PCI_VENDOR_ID_PHILIPS, |
.device = PCI_DEVICE_ID_PHILIPS_SAA7146, |
.subvendor = 0x17c8, |
.subdevice = 0x2101, |
.driver_data = (unsigned long) &hexium_orion_4bnc, |
}, |
{ |
.vendor = 0, |
} |
}; |
MODULE_DEVICE_TABLE(pci, pci_tbl); |
static struct saa7146_ext_vv vv_data = { |
.inputs = HEXIUM_INPUTS, |
.capabilities = 0, |
.stds = &hexium_standards[0], |
.num_stds = sizeof(hexium_standards) / sizeof(struct saa7146_standard), |
.std_callback = &std_callback, |
.ioctls = &ioctls[0], |
.ioctl = hexium_ioctl, |
}; |
static struct saa7146_extension extension = { |
.name = "hexium HV-PCI6/Orion", |
.flags = 0, // SAA7146_USE_I2C_IRQ, |
.pci_tbl = &pci_tbl[0], |
.module = THIS_MODULE, |
.probe = hexium_probe, |
.attach = hexium_attach, |
.detach = hexium_detach, |
.irq_mask = 0, |
.irq_func = NULL, |
}; |
int __init hexium_init_module(void) |
{ |
if (0 != saa7146_register_extension(&extension)) { |
DEB_S(("failed to register extension.\n")); |
return -ENODEV; |
} |
return 0; |
} |
void __exit hexium_cleanup_module(void) |
{ |
saa7146_unregister_extension(&extension); |
} |
module_init(hexium_init_module); |
module_exit(hexium_cleanup_module); |
MODULE_DESCRIPTION("video4linux-2 driver for Hexium Orion frame grabber cards"); |
MODULE_AUTHOR("Michael Hunold <michael@mihu.de>"); |
MODULE_LICENSE("GPL"); |
/shark/trunk/drivers/cm7326/saa7146_video.c |
---|
0,0 → 1,1498 |
#include <media/saa7146_vv.h> |
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, |
.flags = 0, |
}, { |
.name = "RGB-16 (5/B-6/G-5/R)", |
.pixelformat = V4L2_PIX_FMT_RGB565, |
.trans = RGB16_COMPOSED, |
.depth = 16, |
.flags = 0, |
}, { |
.name = "RGB-24 (B-G-R)", |
.pixelformat = V4L2_PIX_FMT_BGR24, |
.trans = RGB24_COMPOSED, |
.depth = 24, |
.flags = 0, |
}, { |
.name = "RGB-32 (B-G-R)", |
.pixelformat = V4L2_PIX_FMT_BGR32, |
.trans = RGB32_COMPOSED, |
.depth = 32, |
.flags = 0, |
}, { |
.name = "Greyscale-8", |
.pixelformat = V4L2_PIX_FMT_GREY, |
.trans = Y8, |
.depth = 8, |
.flags = 0, |
}, { |
.name = "YUV 4:2:2 planar (Y-Cb-Cr)", |
.pixelformat = V4L2_PIX_FMT_YUV422P, |
.trans = YUV422_DECOMPOSED, |
.depth = 16, |
.flags = FORMAT_BYTE_SWAP|FORMAT_IS_PLANAR, |
}, { |
.name = "YVU 4:2:0 planar (Y-Cb-Cr)", |
.pixelformat = V4L2_PIX_FMT_YVU420, |
.trans = YUV420_DECOMPOSED, |
.depth = 12, |
.flags = FORMAT_BYTE_SWAP|FORMAT_IS_PLANAR, |
}, { |
.name = "YUV 4:2:0 planar (Y-Cb-Cr)", |
.pixelformat = V4L2_PIX_FMT_YUV420, |
.trans = YUV420_DECOMPOSED, |
.depth = 12, |
.flags = FORMAT_IS_PLANAR, |
}, { |
.name = "YUV 4:2:2 (U-Y-V-Y)", |
.pixelformat = V4L2_PIX_FMT_UYVY, |
.trans = YUV422_COMPOSED, |
.depth = 16, |
.flags = 0, |
} |
}; |
/* unfortunately, the saa7146 contains a bug which prevents it from doing on-the-fly byte swaps. |
due to this, it's impossible to provide additional *packed* formats, which are simply byte swapped |
(like V4L2_PIX_FMT_YUYV) ... 8-( */ |
static int NUM_FORMATS = sizeof(formats)/sizeof(struct saa7146_format); |
struct saa7146_format* 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 < 48 || win->w.height < 32) { |
DEB_D(("min width/height. (%d,%d)\n",win->w.width,win->w.height)); |
return -EINVAL; |
} |
if (win->clipcount > 16) { |
DEB_D(("clipcount too big.\n")); |
return -EINVAL; |
} |
field = win->field; |
maxw = vv->standard->h_max_out; |
maxh = vv->standard->v_max_out; |
if (V4L2_FIELD_ANY == field) { |
field = (win->w.height > maxh/2) |
? V4L2_FIELD_INTERLACED |
: V4L2_FIELD_TOP; |
} |
switch (field) { |
case V4L2_FIELD_TOP: |
case V4L2_FIELD_BOTTOM: |
case V4L2_FIELD_ALTERNATE: |
maxh = maxh / 2; |
break; |
case V4L2_FIELD_INTERLACED: |
break; |
default: { |
DEB_D(("no known field mode '%d'.\n",field)); |
return -EINVAL; |
} |
} |
win->field = field; |
if (win->w.width > maxw) |
win->w.width = maxw; |
if (win->w.height > maxh) |
win->w.height = maxh; |
return 0; |
} |
static int 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; |
int calc_bpl; |
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_ALTERNATE: { |
vv->last_field = V4L2_FIELD_TOP; |
maxh = maxh / 2; |
break; |
} |
case V4L2_FIELD_TOP: |
case V4L2_FIELD_BOTTOM: |
vv->last_field = V4L2_FIELD_INTERLACED; |
maxh = maxh / 2; |
break; |
case V4L2_FIELD_INTERLACED: |
vv->last_field = V4L2_FIELD_INTERLACED; |
break; |
default: { |
DEB_D(("no known field mode '%d'.\n",field)); |
return -EINVAL; |
} |
} |
f->fmt.pix.field = field; |
if (f->fmt.pix.width > maxw) |
f->fmt.pix.width = maxw; |
if (f->fmt.pix.height > maxh) |
f->fmt.pix.height = maxh; |
calc_bpl = (f->fmt.pix.width * fmt->depth)/8; |
if (f->fmt.pix.bytesperline < calc_bpl) |
f->fmt.pix.bytesperline = calc_bpl; |
if (f->fmt.pix.bytesperline > (2*PAGE_SIZE * fmt->depth)/8) /* arbitrary constraint */ |
f->fmt.pix.bytesperline = calc_bpl; |
f->fmt.pix.sizeimage = f->fmt.pix.bytesperline * f->fmt.pix.height; |
DEB_D(("w:%d, h:%d, bytesperline:%d, sizeimage:%d\n",f->fmt.pix.width,f->fmt.pix.height,f->fmt.pix.bytesperline,f->fmt.pix.sizeimage)); |
return 0; |
} |
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; |
} |
} |
int saa7146_start_preview(struct saa7146_fh *fh) |
{ |
struct saa7146_dev *dev = fh->dev; |
struct saa7146_vv *vv = dev->vv_data; |
int ret = 0, err = 0; |
DEB_EE(("dev:%p, fh:%p\n",dev,fh)); |
/* check if we have overlay informations */ |
if( NULL == fh->ov.fh ) { |
DEB_D(("no 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])); |
if (0 != (ret = saa7146_enable_overlay(fh))) { |
vv->ov_data = NULL; |
DEB_D(("enabling overlay failed: %d\n",ret)); |
return ret; |
} |
return 0; |
} |
int saa7146_stop_preview(struct saa7146_fh *fh) |
{ |
struct saa7146_dev *dev = fh->dev; |
struct saa7146_vv *vv = dev->vv_data; |
DEB_EE(("dev:%p, fh:%p\n",dev,fh)); |
/* check if 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 0; |
} |
vv->ov_data = NULL; |
saa7146_disable_overlay(fh); |
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); |
saa7146_stop_preview(fh); |
saa7146_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); |
DEB_D(("V4L2_CID_BRIGHTNESS: %d\n",c->value)); |
break; |
case V4L2_CID_CONTRAST: |
value = saa7146_read(dev, BCS_CTRL); |
c->value = 0x7f & (value >> 16); |
DEB_D(("V4L2_CID_CONTRAST: %d\n",c->value)); |
break; |
case V4L2_CID_SATURATION: |
value = saa7146_read(dev, BCS_CTRL); |
c->value = 0x7f & (value >> 0); |
DEB_D(("V4L2_CID_SATURATION: %d\n",c->value)); |
break; |
case V4L2_CID_VFLIP: |
c->value = vv->vflip; |
DEB_D(("V4L2_CID_VFLIP: %d\n",c->value)); |
break; |
case V4L2_CID_HFLIP: |
c->value = vv->hflip; |
DEB_D(("V4L2_CID_HFLIP: %d\n",c->value)); |
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); |
saa7146_stop_preview(fh); |
saa7146_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, sg_len:%d\n",dev,buf,length)); |
if( 0 != IS_PLANAR(sfmt->trans)) { |
struct saa7146_pgtable *pt1 = &buf->pt[0]; |
struct saa7146_pgtable *pt2 = &buf->pt[1]; |
struct saa7146_pgtable *pt3 = &buf->pt[2]; |
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; |
DEB_CAP(("size:%d, m1:%d, m2:%d, m3:%d, o1:%d, o2:%d\n",size,m1,m2,m3,o1,o2)); |
break; |
} |
case 16: { |
/* create some offsets inside the page table */ |
m1 = ((size+PAGE_SIZE)/PAGE_SIZE)-1; |
m2 = ((size+(size/2)+PAGE_SIZE)/PAGE_SIZE)-1; |
m3 = ((2*size+PAGE_SIZE)/PAGE_SIZE)-1; |
o1 = size%PAGE_SIZE; |
o2 = (size+(size/2))%PAGE_SIZE; |
DEB_CAP(("size:%d, m1:%d, m2:%d, m3:%d, o1:%d, o2:%d\n",size,m1,m2,m3,o1,o2)); |
break; |
} |
default: { |
return -1; |
} |
} |
ptr1 = pt1->cpu; |
ptr2 = pt2->cpu; |
ptr3 = pt3->cpu; |
/* walk all pages, copy all page addresses to ptr1 */ |
for (i = 0; i < length; i++, list++) { |
for (p = 0; p * 4096 < list->length; p++, ptr1++) { |
*ptr1 = 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]; |
return saa7146_pgtable_build_single(pci, pt, list, length); |
} |
return 0; |
} |
/********************************************************************************/ |
/* file operations */ |
static int video_begin(struct saa7146_fh *fh) |
{ |
struct saa7146_dev *dev = fh->dev; |
struct saa7146_vv *vv = dev->vv_data; |
struct saa7146_format *fmt = NULL; |
unsigned long flags; |
unsigned int resource; |
int ret = 0; |
DEB_EE(("dev:%p, fh:%p\n",dev,fh)); |
if( fh == vv->streaming ) { |
DEB_S(("already capturing.\n")); |
return -EBUSY; |
} |
if( vv->streaming != 0 ) { |
DEB_S(("already capturing, but in another open.\n")); |
return -EBUSY; |
} |
fmt = format_by_fourcc(dev,fh->video_fmt.pixelformat); |
/* we need to have a valid format set here */ |
BUG_ON(NULL == fmt); |
if (0 != (fmt->flags & FORMAT_IS_PLANAR)) { |
resource = RESOURCE_DMA1_HPS|RESOURCE_DMA2_CLP|RESOURCE_DMA3_BRS; |
} else { |
resource = RESOURCE_DMA1_HPS; |
} |
ret = saa7146_res_get(fh, resource); |
if (0 == ret) { |
DEB_S(("cannot get capture resource %d\n",resource)); |
return -EBUSY; |
} |
spin_lock_irqsave(&dev->slock,flags); |
/* clear out beginning of streaming bit (rps register 0)*/ |
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 file *file) |
{ |
struct saa7146_dev *dev = fh->dev; |
struct saa7146_vv *vv = dev->vv_data; |
struct saa7146_format *fmt = NULL; |
unsigned long flags; |
unsigned int resource; |
u32 dmas = 0; |
DEB_EE(("dev:%p, fh:%p\n",dev,fh)); |
if( vv->streaming != fh ) { |
DEB_S(("not capturing.\n")); |
return -EINVAL; |
} |
fmt = format_by_fourcc(dev,fh->video_fmt.pixelformat); |
/* we need to have a valid format set here */ |
BUG_ON(NULL == fmt); |
if (0 != (fmt->flags & FORMAT_IS_PLANAR)) { |
resource = RESOURCE_DMA1_HPS|RESOURCE_DMA2_CLP|RESOURCE_DMA3_BRS; |
dmas = 0x00700000; |
} else { |
resource = RESOURCE_DMA1_HPS; |
dmas = 0x00100000; |
} |
saa7146_res_free(fh, resource); |
spin_lock_irqsave(&dev->slock,flags); |
/* disable rps0 */ |
saa7146_write(dev, MC1, MASK_28); |
/* disable rps0 irqs */ |
IER_DISABLE(dev, MASK_27); |
/* shut down all used video dma transfers */ |
saa7146_write(dev, MC1, dmas); |
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 pointer. |
*/ |
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_vv_data->ioctls[ee].flags != 0; ee++) { |
if( cmd == dev->ext_vv_data->ioctls[ee].cmd ) |
break; |
} |
if( 0 != (dev->ext_vv_data->ioctls[ee].flags & SAA7146_EXCLUSIVE) ) { |
DEB_D(("extension handles ioctl exclusive.\n")); |
result = dev->ext_vv_data->ioctl(fh, cmd, arg); |
return result; |
} |
if( 0 != (dev->ext_vv_data->ioctls[ee].flags & SAA7146_BEFORE) ) { |
DEB_D(("extension handles ioctl before.\n")); |
result = dev->ext_vv_data->ioctl(fh, 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"); |
strlcpy(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_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; |
struct saa7146_fh *ov_fh = NULL; |
int restart_overlay = 0; |
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; |
} |
*/ |
/* check args */ |
fmt = format_by_fourcc(dev,fb->fmt.pixelformat); |
if (NULL == fmt) { |
return -EINVAL; |
} |
/* planar formats are not allowed for overlay video, clipping and video dma would clash */ |
if (0 != (fmt->flags & FORMAT_IS_PLANAR)) { |
DEB_S(("planar pixelformat '%4.4s' not allowed for overlay\n",(char *)&fmt->pixelformat)); |
} |
down(&dev->lock); |
if( vv->ov_data != NULL ) { |
ov_fh = vv->ov_data->fh; |
saa7146_stop_preview(ov_fh); |
restart_overlay = 1; |
} |
/* 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; |
if( 0 != restart_overlay ) { |
saa7146_start_preview(ov_fh); |
} |
up(&dev->lock); |
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; |
strlcpy(f->description,formats[index].name,sizeof(f->description)); |
f->pixelformat = formats[index].pixelformat; |
break; |
} |
default: |
return -EINVAL; |
} |
DEB_EE(("VIDIOC_ENUM_FMT: 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 ) { |
return -EINVAL; |
/* |
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: |
/* FIXME: remove when videodev2.h update is in kernel */ |
#ifdef VIDIOC_S_CTRL_OLD |
case VIDIOC_S_CTRL_OLD: |
#endif |
{ |
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_vv_data->num_stds ) { |
DEB_EE(("VIDIOC_ENUMSTD: index:%d\n",e->index)); |
v4l2_video_std_construct(e, dev->ext_vv_data->stds[e->index].id, dev->ext_vv_data->stds[e->index].name); |
return 0; |
} |
return -EINVAL; |
} |
case VIDIOC_S_STD: |
{ |
v4l2_std_id *id = arg; |
int i; |
int restart_overlay = 0; |
int found = 0; |
struct saa7146_fh *ov_fh = NULL; |
DEB_EE(("VIDIOC_S_STD\n")); |
if( 0 != vv->streaming ) { |
return -EBUSY; |
} |
DEB_D(("before getting lock...\n")); |
down(&dev->lock); |
DEB_D(("got lock\n")); |
if( vv->ov_data != NULL ) { |
ov_fh = vv->ov_data->fh; |
saa7146_stop_preview(ov_fh); |
restart_overlay = 1; |
} |
for(i = 0; i < dev->ext_vv_data->num_stds; i++) |
if (*id & dev->ext_vv_data->stds[i].id) |
break; |
if (i != dev->ext_vv_data->num_stds) { |
vv->standard = &dev->ext_vv_data->stds[i]; |
if( NULL != dev->ext_vv_data->std_callback ) |
dev->ext_vv_data->std_callback(dev, vv->standard); |
found = 1; |
} |
if( 0 != restart_overlay ) { |
saa7146_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: |
/* FIXME: remove when videodev2.h update is in kernel */ |
#ifdef VIDIOC_OVERLAY_OLD |
case VIDIOC_OVERLAY_OLD: |
#endif |
{ |
int on = *(int *)arg; |
int err = 0; |
if( NULL == vv->ov_fmt && on != 0 ) { |
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) { |
DEB_D(("overlay already active in another open\n")); |
return -EAGAIN; |
} |
} |
if (0 == saa7146_res_get(fh, RESOURCE_DMA1_HPS|RESOURCE_DMA2_CLP)) { |
DEB_D(("cannot get overlay resources\n")); |
return -EBUSY; |
} |
spin_lock_irqsave(&dev->slock,flags); |
err = saa7146_start_preview(fh); |
spin_unlock_irqrestore(&dev->slock,flags); |
return err; |
} |
if( vv->ov_data != NULL ) { |
if( fh != vv->ov_data->fh) { |
DEB_D(("overlay is active, but in another open\n")); |
return -EAGAIN; |
} |
} |
spin_lock_irqsave(&dev->slock,flags); |
err = saa7146_stop_preview(fh); |
spin_unlock_irqrestore(&dev->slock,flags); |
/* free resources */ |
saa7146_res_free(fh, RESOURCE_DMA1_HPS|RESOURCE_DMA2_CLP); |
return err; |
} |
case VIDIOC_REQBUFS: { |
struct v4l2_requestbuffers *req = arg; |
DEB_D(("VIDIOC_REQBUFS, type:%d\n",req->type)); |
return videobuf_reqbufs(file,q,req); |
} |
case VIDIOC_QUERYBUF: { |
struct v4l2_buffer *buf = arg; |
DEB_D(("VIDIOC_QUERYBUF, type:%d, offset:%d\n",buf->type,buf->m.offset)); |
return videobuf_querybuf(q,buf); |
} |
case VIDIOC_QBUF: { |
struct v4l2_buffer *buf = arg; |
int ret = 0; |
ret = videobuf_qbuf(file,q,buf); |
DEB_D(("VIDIOC_QBUF: ret:%d, index:%d\n",ret,buf->index)); |
return ret; |
} |
case VIDIOC_DQBUF: { |
struct v4l2_buffer *buf = arg; |
int ret = 0; |
ret = videobuf_dqbuf(file,q,buf); |
DEB_D(("VIDIOC_DQBUF: ret:%d, index:%d\n",ret,buf->index)); |
return ret; |
} |
case VIDIOC_STREAMON: { |
int *type = arg; |
DEB_D(("VIDIOC_STREAMON, type:%d\n",*type)); |
if( fh == vv->streaming ) { |
DEB_D(("already capturing.\n")); |
return 0; |
} |
err = video_begin(fh); |
if( 0 != err) { |
return err; |
} |
err = videobuf_streamon(file,q); |
return err; |
} |
case VIDIOC_STREAMOFF: { |
int *type = arg; |
DEB_D(("VIDIOC_STREAMOFF, type:%d\n",*type)); |
if( fh != vv->streaming ) { |
DEB_D(("this open is not capturing.\n")); |
return -EINVAL; |
} |
err = videobuf_streamoff(file,q); |
video_end(fh, file); |
return err; |
} |
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, |
V4L2_MEMORY_MMAP); |
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; |
DEB_CAP(("vbuf:%p\n",vb)); |
/* sanity checks */ |
if (fh->video_fmt.width < 48 || |
fh->video_fmt.height < 32 || |
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.bytesperline != fh->video_fmt.bytesperline || |
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.bytesperline = fh->video_fmt.bytesperline; |
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, &vv->ov_fb); |
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_vv_data->stds[0]; |
/* FIXME: what's this? */ |
vv->current_hps_source = SAA7146_HPS_SOURCE_PORT_A; |
vv->current_hps_sync = SAA7146_HPS_SYNC_PORT_A; |
} |
static int video_open(struct saa7146_dev *dev, struct file *file) |
{ |
struct saa7146_fh *fh = (struct saa7146_fh *)file->private_data; |
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.bytesperline = 0; |
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); |
return 0; |
} |
static void video_close(struct saa7146_dev *dev, struct file *file) |
{ |
struct saa7146_fh *fh = (struct saa7146_fh *)file->private_data; |
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); |
saa7146_stop_preview(fh); |
spin_unlock_irqrestore(&dev->slock,flags); |
} |
} |
if( fh == vv->streaming ) { |
video_end(fh, file); |
} |
} |
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")); |
/* fixme: should we allow read() captures while streaming capture? */ |
if( 0 != vv->streaming ) { |
DEB_S(("already capturing.\n")); |
return -EBUSY; |
} |
/* stop any active overlay */ |
if( vv->ov_data != NULL ) { |
ov_fh = vv->ov_data->fh; |
saa7146_stop_preview(ov_fh); |
saa7146_res_free(ov_fh, RESOURCE_DMA1_HPS|RESOURCE_DMA2_CLP); |
restart_overlay = 1; |
} |
ret = video_begin(fh); |
if( 0 != ret) { |
goto out; |
} |
ret = videobuf_read_one(file,&fh->video_q , data, count, ppos); |
video_end(fh, file); |
out: |
/* restart overlay if it was active before */ |
if( 0 != restart_overlay ) { |
if (0 == saa7146_res_get(ov_fh, RESOURCE_DMA1_HPS|RESOURCE_DMA2_CLP)) { |
DEB_D(("cannot get overlay resources again!\n")); |
BUG(); |
} |
saa7146_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, |
}; |
/shark/trunk/drivers/cm7326/saa7146_fops.c |
---|
0,0 → 1,578 |
#include <media/saa7146_vv.h> |
#define BOARD_CAN_DO_VBI(dev) (dev->revision != 0 && dev->vv_data->vbi_minor != -1) |
/****************************************************************************/ |
/* resource management functions, shamelessly stolen from saa7134 driver */ |
int saa7146_res_get(struct saa7146_fh *fh, unsigned int bit) |
{ |
struct saa7146_dev *dev = fh->dev; |
struct saa7146_vv *vv = dev->vv_data; |
if (fh->resources & bit) |
/* have it already allocated */ |
return 1; |
/* is it free? */ |
DEB_D(("getting lock...\n")); |
down(&dev->lock); |
DEB_D(("got lock\n")); |
if (vv->resources & bit) { |
DEB_D(("locked! vv->resources:0x%02x, we want:0x%02x\n",vv->resources,bit)); |
/* no, someone else uses it */ |
up(&dev->lock); |
return 0; |
} |
/* it's free, grab it */ |
fh->resources |= bit; |
vv->resources |= bit; |
DEB_D(("res: get %d\n",bit)); |
up(&dev->lock); |
return 1; |
} |
int saa7146_res_check(struct saa7146_fh *fh, unsigned int bit) |
{ |
return (fh->resources & bit); |
} |
int saa7146_res_locked(struct saa7146_dev *dev, unsigned int bit) |
{ |
struct saa7146_vv *vv = dev->vv_data; |
return (vv->resources & bit); |
} |
void saa7146_res_free(struct saa7146_fh *fh, unsigned int bits) |
{ |
struct saa7146_dev *dev = fh->dev; |
struct saa7146_vv *vv = dev->vv_data; |
if ((fh->resources & bits) != bits) |
BUG(); |
DEB_D(("getting lock...\n")); |
down(&dev->lock); |
DEB_D(("got lock\n")); |
fh->resources &= ~bits; |
vv->resources &= ~bits; |
DEB_D(("res: put %d\n",bits)); |
up(&dev->lock); |
} |
/********************************************************************************/ |
/* 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)); |
DEB_EE(("q->curr:%p\n",q->curr)); |
/* finish current buffer */ |
if (NULL == q->curr) { |
DEB_D(("aiii. no current buffer\n")); |
return; |
} |
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_INT(("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_INT(("next buffer: buf:%p, prev:%p, next:%p\n", buf, q->queue.prev,q->queue.next)); |
buf->activate(dev,buf,next); |
} else { |
DEB_INT(("no next buffer. stopping.\n")); |
if( 0 != vbi ) { |
/* turn off video-dma3 */ |
saa7146_write(dev,MC1, MASK_20); |
} else { |
/* nothing to do -- just prevent next video-dma1 transfer |
by lowering the protection address */ |
// fixme: fix this for vflip != 0 |
saa7146_write(dev, PROT_ADDR1, 0); |
saa7146_write(dev, MC2, (MASK_02|MASK_18)); |
/* write the address of the rps-program */ |
saa7146_write(dev, RPS_ADDR0, dev->d_rps0.dma_handle); |
/* turn on rps */ |
saa7146_write(dev, MC1, (MASK_12 | MASK_28)); |
/* |
printk("vdma%d.base_even: 0x%08x\n", 1,saa7146_read(dev,BASE_EVEN1)); |
printk("vdma%d.base_odd: 0x%08x\n", 1,saa7146_read(dev,BASE_ODD1)); |
printk("vdma%d.prot_addr: 0x%08x\n", 1,saa7146_read(dev,PROT_ADDR1)); |
printk("vdma%d.base_page: 0x%08x\n", 1,saa7146_read(dev,BASE_PAGE1)); |
printk("vdma%d.pitch: 0x%08x\n", 1,saa7146_read(dev,PITCH1)); |
printk("vdma%d.num_line_byte: 0x%08x\n", 1,saa7146_read(dev,NUM_LINE_BYTE1)); |
*/ |
} |
del_timer(&q->timeout); |
} |
} |
void saa7146_buffer_timeout(unsigned long data) |
{ |
struct saa7146_dmaqueue *q = (struct saa7146_dmaqueue*)data; |
struct saa7146_dev *dev = q->dev; |
unsigned long flags; |
DEB_EE(("dev:%p, dmaq:%p\n", dev, q)); |
spin_lock_irqsave(&dev->slock,flags); |
if (q->curr) { |
DEB_D(("timeout on %p\n", q->curr)); |
saa7146_buffer_finish(dev,q,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 = iminor(inode); |
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)); |
file->private_data = fh; |
fh->dev = dev; |
fh->type = type; |
if( fh->type == V4L2_BUF_TYPE_VBI_CAPTURE) { |
DEB_S(("initializing vbi...\n")); |
result = saa7146_vbi_uops.open(dev,file); |
} else { |
DEB_S(("initializing video...\n")); |
result = saa7146_video_uops.open(dev,file); |
} |
if (0 != result) { |
goto out; |
} |
if( 0 == try_module_get(dev->ext->module)) { |
result = -EINVAL; |
goto out; |
} |
result = 0; |
out: |
if( fh != 0 && result != 0 ) { |
kfree(fh); |
file->private_data = NULL; |
} |
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; |
if( fh->type == V4L2_BUF_TYPE_VBI_CAPTURE) { |
saa7146_vbi_uops.release(dev,file); |
} else { |
saa7146_video_uops.release(dev,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 { |
DEB_D(("using video queue.\n")); |
q = &fh->video_q; |
} |
if (!list_empty(&q->stream)) |
buf = list_entry(q->stream.next, struct videobuf_buffer, stream); |
if (!buf) { |
DEB_D(("buf == NULL!\n")); |
return POLLERR; |
} |
poll_wait(file, &buf->done, wait); |
if (buf->state == STATE_DONE || buf->state == STATE_ERROR) { |
DEB_D(("poll succeeded!\n")); |
return POLLIN|POLLRDNORM; |
} |
DEB_D(("nothing to poll for, buf->state:%d\n",buf->state)); |
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:%lun", file, data, (unsigned long)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:%lu\n", file, data, (unsigned long)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_INT(("dev:%p, isr:0x%08x\n",dev,(u32)status)); |
if (0 != (isr & (MASK_27))) { |
DEB_INT(("irq: RPS0 (0x%08x).\n",isr)); |
saa7146_video_uops.irq_done(dev,isr); |
} |
if (0 != (isr & (MASK_28))) { |
u32 mc2 = saa7146_read(dev, MC2); |
if( 0 != (mc2 & MASK_15)) { |
DEB_INT(("irq: RPS1 vbi workaround (0x%08x).\n",isr)); |
wake_up(&dev->vv_data->vbi_wq); |
saa7146_write(dev,MC2, MASK_31); |
return; |
} |
DEB_INT(("irq: RPS1 (0x%08x).\n",isr)); |
saa7146_vbi_uops.irq_done(dev,isr); |
} |
} |
static struct video_device device_template = |
{ |
.hardware = VID_HARDWARE_SAA7146, |
.fops = &video_fops, |
.minor = -1, |
}; |
int saa7146_vv_init(struct saa7146_dev* dev, struct saa7146_ext_vv *ext_vv) |
{ |
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)); |
/* set default values for video parts of the saa7146 */ |
saa7146_write(dev, BCS_CTRL, 0x80400040); |
/* enable video-port pins */ |
saa7146_write(dev, MC1, (MASK_10 | MASK_26)); |
/* save per-device extension data (one extension can |
handle different devices that might need different |
configuration data) */ |
dev->ext_vv_data = ext_vv; |
vv->video_minor = -1; |
vv->vbi_minor = -1; |
vv->d_clipping.cpu_addr = pci_alloc_consistent(dev->pci, SAA7146_CLIPPING_MEM, &vv->d_clipping.dma_handle); |
if( NULL == vv->d_clipping.cpu_addr ) { |
ERR(("out of memory. aborting.\n")); |
kfree(vv); |
return -1; |
} |
memset(vv->d_clipping.cpu_addr, 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)); |
pci_free_consistent(dev->pci, SAA7146_RPS_MEM, vv->d_clipping.cpu_addr, vv->d_clipping.dma_handle); |
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', type:%d\n",dev,name,type)); |
*vid = device_template; |
strlcpy(vid->name, name, sizeof(vid->name)); |
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 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); |
MODULE_AUTHOR("Michael Hunold <michael@mihu.de>"); |
MODULE_DESCRIPTION("video4linux driver for saa7146-based hardware"); |
MODULE_LICENSE("GPL"); |
/shark/trunk/drivers/cm7326/saa7111.c |
---|
0,0 → 1,460 |
/* |
saa7111 - Philips SAA7111A video decoder driver version 0.0.3 |
Copyright (C) 1998 Dave Perks <dperks@ibm.net> |
Slight changes for video timing and attachment output by |
Wolfgang Scherr <scherr@net4you.net> |
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 <linux/module.h> |
#include <linux/init.h> |
#include <linux/delay.h> |
#include <linux/errno.h> |
#include <linux/fs.h> |
#include <linux/kernel.h> |
#include <linux/major.h> |
#include <linux/slab.h> |
#include <linux/mm.h> |
#include <linux/sched.h> |
#include <linux/videodev.h> |
#include <linux/version.h> |
#include <linux/i2c.h> |
#include <linux/video_decoder.h> |
#define DEBUG(x) /* Debug driver */ |
/* ----------------------------------------------------------------------- */ |
struct saa7111 { |
struct i2c_client *client; |
int addr; |
struct semaphore lock; |
unsigned char reg[32]; |
int norm; |
int input; |
int enable; |
int bright; |
int contrast; |
int hue; |
int sat; |
}; |
static unsigned short normal_i2c[] = { 0x24, 0x25, I2C_CLIENT_END }; |
static unsigned short normal_i2c_range[] = { I2C_CLIENT_END }; |
I2C_CLIENT_INSMOD; |
static struct i2c_client client_template; |
/* ----------------------------------------------------------------------- */ |
static int saa7111_attach(struct i2c_adapter *adap, int addr, unsigned short flags, int kind) |
{ |
int i; |
struct saa7111 *decoder; |
struct i2c_client *client; |
/* who wrote this? init[] is used for i2c_master_send() which expects an array that |
will be used for the 'buf' part of an i2c message unchanged. so, the first byte |
needs to be the subaddress to start with, then follow the data bytes... */ |
static const unsigned char init[] = { |
0x00, /* start address */ |
0x00, /* 00 - ID byte */ |
0x00, /* 01 - reserved */ |
/*front end */ |
0xd0, /* 02 - FUSE=3, GUDL=2, MODE=0 */ |
0x23, /* 03 - HLNRS=0, VBSL=1, WPOFF=0, HOLDG=0, GAFIX=0, GAI1=256, GAI2=256 */ |
0x00, /* 04 - GAI1=256 */ |
0x00, /* 05 - GAI2=256 */ |
/* decoder */ |
0xf3, /* 06 - HSB at 13(50Hz) / 17(60Hz) pixels after end of last line */ |
0x13, /* 07 - HSS at 113(50Hz) / 117(60Hz) pixels after end of last line */ |
0xc8, /* 08 - AUFD=1, FSEL=1, EXFIL=0, VTRC=1, HPLL=0, VNOI=0 */ |
0x01, /* 09 - BYPS=0, PREF=0, BPSS=0, VBLB=0, UPTCV=0, APER=1 */ |
0x80, /* 0a - BRIG=128 */ |
0x47, /* 0b - CONT=1.109 */ |
0x40, /* 0c - SATN=1.0 */ |
0x00, /* 0d - HUE=0 */ |
0x01, /* 0e - CDTO=0, CSTD=0, DCCF=0, FCTC=0, CHBW=1 */ |
0x00, /* 0f - reserved */ |
0x48, /* 10 - OFTS=1, HDEL=0, VRLN=1, YDEL=0 */ |
0x1c, /* 11 - GPSW=0, CM99=0, FECO=0, COMPO=1, OEYC=1, OEHV=1, VIPB=0, COLO=0 */ |
0x00, /* 12 - output control 2 */ |
0x00, /* 13 - output control 3 */ |
0x00, /* 14 - reserved */ |
0x00, /* 15 - VBI */ |
0x00, /* 16 - VBI */ |
0x00, /* 17 - VBI */ |
}; |
client = kmalloc(sizeof(*client), GFP_KERNEL); |
if(client == NULL) |
return -ENOMEM; |
client_template.adapter = adap; |
client_template.addr = addr; |
memcpy(client, &client_template, sizeof(*client)); |
decoder = kmalloc(sizeof(*decoder), GFP_KERNEL); |
if (decoder == NULL) |
{ |
kfree(client); |
return -ENOMEM; |
} |
memset(decoder, 0, sizeof(*decoder)); |
decoder->client = client; |
#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)) |
client->data = decoder; |
#else |
i2c_set_clientdata(client, decoder); |
#endif |
decoder->addr = addr; |
decoder->norm = VIDEO_MODE_NTSC; |
decoder->input = 0; |
decoder->enable = 1; |
decoder->bright = 32768; |
decoder->contrast = 32768; |
decoder->hue = 32768; |
decoder->sat = 32768; |
i = i2c_master_send(client, init, sizeof(init)); |
if (i < 0) { |
printk(KERN_ERR "%s_attach: init status %d\n", |
#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)) |
client->name, i); |
#else |
client->dev.name, i); |
#endif |
} else { |
printk(KERN_INFO "%s_attach: chip version %x @ 0x%08x\n", |
#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)) |
client->name, i2c_smbus_read_byte_data(client, 0x00) >> 4,addr); |
#else |
client->dev.name, i2c_smbus_read_byte_data(client, 0x00) >> 4,addr); |
#endif |
} |
init_MUTEX(&decoder->lock); |
i2c_attach_client(client); |
MOD_INC_USE_COUNT; |
return 0; |
} |
static int saa7111_probe(struct i2c_adapter *adap) |
{ |
/* probing unknown devices on any Matrox i2c-bus takes ages due to the |
slow bit banging algorithm used. because of the fact a saa7111(a) |
is *never* present on a Matrox gfx card, we can skip such adapters |
here */ |
if( 0 != (adap->id & I2C_HW_B_G400)) { |
return -ENODEV; |
} |
#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)) |
printk("saa7111: probing %s i2c adapter [id=0x%x]\n", |
adap->name,adap->id); |
#else |
printk("saa7111: probing %s i2c adapter [id=0x%x]\n", |
adap->dev.name,adap->id); |
#endif |
return i2c_probe(adap, &addr_data, saa7111_attach); |
} |
static int saa7111_detach(struct i2c_client *client) |
{ |
#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)) |
struct saa7111 *decoder = client->data; |
#else |
struct saa7111 *decoder = i2c_get_clientdata(client); |
#endif |
i2c_detach_client(client); |
kfree(decoder); |
kfree(client); |
MOD_DEC_USE_COUNT; |
return 0; |
} |
static int saa7111_command(struct i2c_client *client, unsigned int cmd, |
void *arg) |
{ |
#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)) |
struct saa7111 *decoder = client->data; |
#else |
struct saa7111 *decoder = i2c_get_clientdata(client); |
#endif |
switch (cmd) { |
#if defined(DECODER_DUMP) |
case DECODER_DUMP: |
{ |
int i; |
for (i = 0; i < 32; i += 16) { |
int j; |
#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)) |
printk("KERN_DEBUG %s: %03x", client->name, |
#else |
printk("KERN_DEBUG %s: %03x", client->dev.name, |
#endif |
i); |
for (j = 0; j < 16; ++j) { |
printk(" %02x", |
i2c_smbus_read_byte_data(client, |
i + j)); |
} |
printk("\n"); |
} |
} |
break; |
#endif /* defined(DECODER_DUMP) */ |
case DECODER_GET_CAPABILITIES: |
{ |
struct video_decoder_capability *cap = arg; |
cap->flags |
= VIDEO_DECODER_PAL |
| VIDEO_DECODER_NTSC |
| VIDEO_DECODER_AUTO | VIDEO_DECODER_CCIR; |
cap->inputs = 8; |
cap->outputs = 1; |
} |
break; |
case DECODER_GET_STATUS: |
{ |
int *iarg = arg; |
int status; |
int res; |
status = i2c_smbus_read_byte_data(client, 0x1f); |
res = 0; |
if ((status & (1 << 6)) == 0) { |
res |= DECODER_STATUS_GOOD; |
} |
switch (decoder->norm) { |
case VIDEO_MODE_NTSC: |
res |= DECODER_STATUS_NTSC; |
break; |
case VIDEO_MODE_PAL: |
res |= DECODER_STATUS_PAL; |
break; |
default: |
case VIDEO_MODE_AUTO: |
if ((status & (1 << 5)) != 0) { |
res |= DECODER_STATUS_NTSC; |
} else { |
res |= DECODER_STATUS_PAL; |
} |
break; |
} |
if ((status & (1 << 0)) != 0) { |
res |= DECODER_STATUS_COLOR; |
} |
*iarg = res; |
} |
break; |
case DECODER_SET_NORM: |
{ |
int *iarg = arg; |
switch (*iarg) { |
case VIDEO_MODE_NTSC: |
i2c_smbus_write_byte_data(client, 0x08, |
(decoder-> |
reg[0x08] & 0x3f) | 0x40); |
break; |
case VIDEO_MODE_PAL: |
i2c_smbus_write_byte_data(client, 0x08, |
(decoder-> |
reg[0x08] & 0x3f) | 0x00); |
break; |
case VIDEO_MODE_AUTO: |
i2c_smbus_write_byte_data(client, 0x08, |
(decoder-> |
reg[0x08] & 0x3f) | 0x80); |
break; |
default: |
return -EINVAL; |
} |
decoder->norm = *iarg; |
} |
break; |
case DECODER_SET_INPUT: |
{ |
int *iarg = arg; |
if (*iarg < 0 || *iarg > 7) { |
return -EINVAL; |
} |
if (decoder->input != *iarg) { |
decoder->input = *iarg; |
/* select mode */ |
i2c_smbus_write_byte_data(client, 0x02, |
(decoder-> |
reg[0x02] & 0xf8) | |
decoder->input); |
/* bypass chrominance trap for modes 4..7 */ |
i2c_smbus_write_byte_data(client, 0x09, |
(decoder-> |
reg[0x09] & 0x7f) | |
((decoder->input > |
3) ? 0x80 : 0)); |
} |
} |
break; |
case DECODER_SET_OUTPUT: |
{ |
int *iarg = arg; |
/* not much choice of outputs */ |
if (*iarg != 0) { |
return -EINVAL; |
} |
} |
break; |
case DECODER_ENABLE_OUTPUT: |
{ |
int *iarg = arg; |
int enable = (*iarg != 0); |
if (decoder->enable != enable) { |
decoder->enable = enable; |
// RJ: If output should be disabled (for playing videos), we also need a open PLL. |
// The input is set to 0 (where no input source is connected), although this |
// is not necessary. |
// |
// If output should be enabled, we have to reverse the above. |
if (decoder->enable) { |
i2c_smbus_write_byte_data(client, 0x02, |
(decoder-> |
reg[0x02] & 0xf8) | |
decoder->input); |
i2c_smbus_write_byte_data(client, 0x08, |
(decoder-> |
reg[0x08] & 0xfb)); |
i2c_smbus_write_byte_data(client, 0x11, |
(decoder-> |
reg[0x11] & 0xf3) | |
0x0c); |
} else { |
i2c_smbus_write_byte_data(client, 0x02, |
(decoder-> |
reg[0x02] & 0xf8)); |
i2c_smbus_write_byte_data(client, 0x08, |
(decoder-> |
reg[0x08] & 0xfb) | |
0x04); |
i2c_smbus_write_byte_data(client, 0x11, |
(decoder-> |
reg[0x11] & 0xf3)); |
} |
} |
} |
break; |
case DECODER_SET_PICTURE: |
{ |
struct video_picture *pic = arg; |
if (decoder->bright != pic->brightness) { |
/* We want 0 to 255 we get 0-65535 */ |
decoder->bright = pic->brightness; |
i2c_smbus_write_byte_data(client, 0x0a, |
decoder->bright >> 8); |
} |
if (decoder->contrast != pic->contrast) { |
/* We want 0 to 127 we get 0-65535 */ |
decoder->contrast = pic->contrast; |
i2c_smbus_write_byte_data(client, 0x0b, |
decoder->contrast >> 9); |
} |
if (decoder->sat != pic->colour) { |
/* We want 0 to 127 we get 0-65535 */ |
decoder->sat = pic->colour; |
i2c_smbus_write_byte_data(client, 0x0c, |
decoder->sat >> 9); |
} |
if (decoder->hue != pic->hue) { |
/* We want -128 to 127 we get 0-65535 */ |
decoder->hue = pic->hue; |
i2c_smbus_write_byte_data(client, 0x0d, |
(decoder->hue - 32768) >> 8); |
} |
} |
break; |
default: |
return -EINVAL; |
} |
return 0; |
} |
/* ----------------------------------------------------------------------- */ |
static struct i2c_driver i2c_driver_saa7111 = { |
#if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,54) |
.owner = THIS_MODULE, |
#endif |
.name = "saa7111", /* name */ |
.id = I2C_DRIVERID_SAA7111A, /* ID */ |
.flags = I2C_DF_NOTIFY, |
.attach_adapter = saa7111_probe, |
.detach_client = saa7111_detach, |
.command = saa7111_command |
}; |
static struct i2c_client client_template = { |
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) |
.name = "saa7111_client", |
#else |
.dev = { |
.name = "saa7111_client", |
}, |
#endif |
.id = -1, |
.driver = &i2c_driver_saa7111 |
}; |
static int saa7111_init(void) |
{ |
return i2c_add_driver(&i2c_driver_saa7111); |
} |
static void saa7111_exit(void) |
{ |
i2c_del_driver(&i2c_driver_saa7111); |
} |
module_init(saa7111_init); |
module_exit(saa7111_exit); |
MODULE_LICENSE("GPL"); |
/shark/trunk/drivers/cm7326/cm7326.c |
---|
0,0 → 1,487 |
/* |
cm7326.c - v4l2 driver for pc104+ framegrabber module |
http://www.rtdusa.com/PC104/UM/video/cm7326.htm |
Copyright (C) 2002 Miguel Freitas |
Copyright (C) 2003 Michael Hunold <michael@mihu.de> |
Visit http://www.mihu.de/linux/saa7146 |
for further details about this card. |
This program is free software; you can redistribute it and/or modify |
it under the terms of the GNU General Public License as published by |
the Free Software Foundation; either version 2 of the License, or |
(at your option) any later version. |
This program is distributed in the hope that it will be useful, |
but WITHOUT ANY WARRANTY; without even the implied warranty of |
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
GNU General Public License for more details. |
You should have received a copy of the GNU General Public License |
along with this program; if not, write to the Free Software |
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. |
*/ |
#define DEBUG_VARIABLE debug |
#include "drivers/saa7146_vv.h" |
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,51) |
#define KBUILD_MODNAME cm7326 |
#endif |
/* module parameters */ |
static int debug = 0; |
MODULE_PARM(debug,"i"); |
MODULE_PARM_DESC(debug, "debug verbosity"); |
/* global variables */ |
static int cm7326_num = 0; |
/* we simply use the two video-interfaces (aka port a and port b) */ |
#define CM7326_INPUTS 6 |
static struct v4l2_input cm7326_inputs[CM7326_INPUTS] = |
{ |
{ 0, "VID1", V4L2_INPUT_TYPE_CAMERA, 2, 0, V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, 0 }, |
{ 1, "VID2", V4L2_INPUT_TYPE_CAMERA, 2, 0, V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, 0 }, |
{ 2, "VID3", V4L2_INPUT_TYPE_CAMERA, 2, 0, V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, 0 }, |
{ 3, "VID4", V4L2_INPUT_TYPE_CAMERA, 2, 0, V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, 0 }, |
{ 4, "VID5", V4L2_INPUT_TYPE_CAMERA, 2, 0, V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, 0 }, |
{ 5, "VID6", V4L2_INPUT_TYPE_CAMERA, 2, 0, V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, 0 }, |
}; |
#define CM7326_AUDIOS 0 |
/* you can demand that your extension get's called for any v4l2-ioctl. here, |
we want to be called exclusively when the ioctls VIDIOC_S_INPUT and |
VIDIOC_ENUMINPUT get called by an user application */ |
static struct saa7146_extension_ioctls ioctls[] = |
{ |
{ VIDIOC_ENUMINPUT, SAA7146_EXCLUSIVE }, |
{ VIDIOC_G_INPUT, SAA7146_EXCLUSIVE }, |
{ VIDIOC_S_INPUT, SAA7146_EXCLUSIVE }, |
{ VIDIOC_S_STD, SAA7146_AFTER }, |
{ 0, 0 } |
}; |
struct cm7326 |
{ |
struct video_device video_dev; |
struct video_device vbi_dev; |
struct i2c_adapter i2c_adapter; |
int cur_input; /* current input */ |
}; |
static unsigned char saa7110_initseq[] = { |
0x4C, 0x3C, 0x0D, 0xEF, 0xBD, 0xF0, 0x00, 0x00, |
0xF8, 0xF8, 0x60, 0x60, 0x00, 0x06, 0x18, 0x90, |
0x00, 0x2C, 0x40, 0x46, 0x42, 0x1A, 0xFF, 0xDA, |
0xF0, 0x8B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
0xD9, 0x17, 0x40, 0x41, 0x80, 0x41, 0x80, 0x4F, |
0xFE, 0x01, 0xCF, 0x0F, 0x03, 0x01, 0x81, 0x03, |
0x40, 0x75, 0x01, 0x8C, 0x03 |
}; |
static int saa7110_init(struct cm7326 *cm7326) |
{ |
union i2c_smbus_data data; |
int ret = 0; |
int i = 0; |
for (i = 0; i < sizeof(saa7110_initseq); i++) { |
data.byte = saa7110_initseq[i]; |
if (0 != i2c_smbus_xfer(&cm7326->i2c_adapter, 0x4e, 0, I2C_SMBUS_WRITE, i, I2C_SMBUS_BYTE_DATA, &data)) { |
DEB_D(("failed for address 0x%02x\n", i)); |
return -EFAULT; |
} |
} |
data.byte = 0x16; |
ret += i2c_smbus_xfer(&cm7326->i2c_adapter, 0x4e, 0, I2C_SMBUS_WRITE, 0x21, I2C_SMBUS_BYTE_DATA, &data); |
/* fixme: unclear why there are two writes to register 0x0d here ... */ |
data.byte = 0x04; |
ret += i2c_smbus_xfer(&cm7326->i2c_adapter, 0x4e, 0, I2C_SMBUS_WRITE, 0x0D, I2C_SMBUS_BYTE_DATA, &data); |
data.byte = 0x06; |
ret += i2c_smbus_xfer(&cm7326->i2c_adapter, 0x4e, 0, I2C_SMBUS_WRITE, 0x0D, I2C_SMBUS_BYTE_DATA, &data); |
if (0 != ret) { |
DEB_S(("writing to saa7110 failed.\n")); |
return -EFAULT; |
} |
return 0; |
} |
static const unsigned char input_modes[9][8] = { |
/* mode 0 */ { 0x00, 0xD9, 0x17, 0x40, 0x03, 0x44, 0x75, 0x16 }, |
/* mode 1 */ { 0x00, 0xD8, 0x17, 0x40, 0x03, 0x44, 0x75, 0x16 }, |
/* mode 2 */ { 0x00, 0xBA, 0x07, 0x91, 0x03, 0x60, 0xB5, 0x05 }, |
/* mode 3 */ { 0x00, 0xB8, 0x07, 0x91, 0x03, 0x60, 0xB5, 0x05 }, |
/* mode 4 */ { 0x00, 0x7C, 0x07, 0xD2, 0x83, 0x60, 0xB5, 0x03 }, |
/* mode 5 */ { 0x00, 0x78, 0x07, 0xD2, 0x83, 0x60, 0xB5, 0x03 }, |
/* mode 6 */ { 0x80, 0x59, 0x17, 0x42, 0xA3, 0x44, 0x75, 0x12 }, |
/* mode 7 */ { 0x80, 0x9A, 0x17, 0xB1, 0x13, 0x60, 0xB5, 0x14 }, |
/* mode 8 */ { 0x80, 0x3C, 0x27, 0xC1, 0x23, 0x44, 0x75, 0x21 } |
}; |
static int saa7110_selmux(struct cm7326 *cm7326, int chan) |
{ |
const unsigned char* ptr = input_modes[chan]; |
union i2c_smbus_data data; |
int ret = 0; |
data.byte = ptr[0]; /* Luminance control */ |
ret += i2c_smbus_xfer(&cm7326->i2c_adapter, 0x4e, 0, I2C_SMBUS_WRITE, 0x06, I2C_SMBUS_BYTE_DATA, &data); |
data.byte = ptr[1]; /* Analog Control #1 */ |
ret += i2c_smbus_xfer(&cm7326->i2c_adapter, 0x4e, 0, I2C_SMBUS_WRITE, 0x20, I2C_SMBUS_BYTE_DATA, &data); |
data.byte = ptr[2]; /* Analog Control #2 */ |
ret += i2c_smbus_xfer(&cm7326->i2c_adapter, 0x4e, 0, I2C_SMBUS_WRITE, 0x21, I2C_SMBUS_BYTE_DATA, &data); |
data.byte = ptr[3]; /* Mixer Control #1 */ |
ret += i2c_smbus_xfer(&cm7326->i2c_adapter, 0x4e, 0, I2C_SMBUS_WRITE, 0x22, I2C_SMBUS_BYTE_DATA, &data); |
data.byte = ptr[4]; /* Mixer Control #2 */ |
ret += i2c_smbus_xfer(&cm7326->i2c_adapter, 0x4e, 0, I2C_SMBUS_WRITE, 0x2c, I2C_SMBUS_BYTE_DATA, &data); |
data.byte = ptr[5]; /* ADCs gain control */ |
ret += i2c_smbus_xfer(&cm7326->i2c_adapter, 0x4e, 0, I2C_SMBUS_WRITE, 0x30, I2C_SMBUS_BYTE_DATA, &data); |
data.byte = ptr[6]; /* Mixer Control #3 */ |
ret += i2c_smbus_xfer(&cm7326->i2c_adapter, 0x4e, 0, I2C_SMBUS_WRITE, 0x31, I2C_SMBUS_BYTE_DATA, &data); |
/* fixme: unclear why there are two writes analog control #2 above and here */ |
data.byte = ptr[7]; /* Analog Control #2 */ |
ret += i2c_smbus_xfer(&cm7326->i2c_adapter, 0x4e, 0, I2C_SMBUS_WRITE, 0x21, I2C_SMBUS_BYTE_DATA, &data); |
if (0 != ret) { |
DEB_S(("writing to saa7110 failed.\n")); |
return -EFAULT; |
} |
return 0; |
} |
static int cm7326_probe(struct saa7146_dev *dev) |
{ |
struct cm7326 *cm7326 = NULL; |
union i2c_smbus_data data; |
int err = 0; |
DEB_D(("cm7326_probe called.\n")); |
cm7326 = (struct cm7326*)kmalloc(sizeof(struct cm7326), GFP_KERNEL); |
if( NULL == cm7326 ) { |
printk("cm7326: cm7326_probe: not enough kernel memory.\n"); |
return -ENOMEM; |
} |
memset(cm7326, 0x0, sizeof(struct cm7326)); |
saa7146_i2c_adapter_prepare(dev, &cm7326->i2c_adapter, SAA7146_I2C_BUS_BIT_RATE_480); |
if(i2c_add_adapter(&cm7326->i2c_adapter) < 0) { |
DEB_S(("cannot register i2c-device. skipping.\n")); |
kfree(cm7326); |
return -EFAULT; |
} |
/* have a look if a saa7110 is present */ |
if (0 != (err = i2c_smbus_xfer(&cm7326->i2c_adapter, 0x4e, 0, I2C_SMBUS_READ, 0x00, I2C_SMBUS_BYTE_DATA, &data))) { |
DEB_D(("cm7326_probe failed for this device.\n")); |
i2c_del_adapter(&cm7326->i2c_adapter); |
kfree(cm7326); |
return -ENODEV; |
} |
DEB_D(("cm7326_probe succeeded for this device.\n")); |
/* we store the pointer in our private data field */ |
(struct cm7326*)dev->ext_priv = cm7326; |
return 0; |
} |
/* bring hardware to a sane state. this has to be done, just in case someone |
wants to capture from this device before it has been properly initialized. |
the capture engine would badly fail, because no valid signal arrives on the |
saa7146, thus leading to timeouts and stuff. */ |
static int cm7326_init_done(struct saa7146_dev *dev) |
{ |
struct cm7326* cm7326 = (struct cm7326*)dev->ext_priv; |
int ret = 0; |
DEB_D(("cm7326_init_done called.\n")); |
/* initialize the helper ics to useful values */ |
if (0 != (ret = saa7110_init(cm7326))) { |
DEB_S(("initialization of saa7110 failed\n")); |
return -EFAULT; |
} |
/* the rest for saa7146: you should definitely set some basic values |
for the input-port handling of the saa7146. */ |
/* some stuff is done via variables */ |
saa7146_set_hps_source_and_sync(dev, SAA7146_HPS_SOURCE_PORT_A, SAA7146_HPS_SYNC_PORT_A); |
/* some stuff is done via direct write to the registers */ |
/* this is ugly, but because of the fact that this is completely |
hardware dependend, it should be done directly... */ |
saa7146_write(dev, DD1_STREAM_B, 0x00000000); |
saa7146_write(dev, DD1_INIT, 0x02000200); |
saa7146_write(dev, MC2, (MASK_09 | MASK_25 | MASK_10 | MASK_26)); |
return 0; |
} |
static struct saa7146_ext_vv vv_data; |
/* this function only gets called when the probing was successful */ |
static int cm7326_attach(struct saa7146_dev *dev, struct saa7146_pci_extension_data *info) |
{ |
struct cm7326* cm7326 = (struct cm7326*)dev->ext_priv; |
DEB_D(("cm7326_attach called.\n")); |
/* checking for i2c-devices can be omitted here, because we |
already did this in "cm7326_vl42_probe" */ |
saa7146_vv_init(dev,&vv_data); |
if( 0 != saa7146_register_device(&cm7326->video_dev, dev, "cm7326", VFL_TYPE_GRABBER)) { |
ERR(("cannot register capture v4l2 device. skipping.\n")); |
return -1; |
} |
printk("cm7326: found 'cm7326'-%d.\n",cm7326_num); |
cm7326_num++; |
/* the rest */ |
cm7326->cur_input = 0; |
return cm7326_init_done(dev); |
} |
static int cm7326_detach(struct saa7146_dev *dev) |
{ |
struct cm7326* cm7326 = (struct cm7326*)dev->ext_priv; |
DEB_EE(("dev:%p\n",dev)); |
saa7146_unregister_device(&cm7326->video_dev,dev); |
saa7146_vv_release(dev); |
cm7326_num--; |
i2c_del_adapter(&cm7326->i2c_adapter); |
kfree(cm7326); |
return 0; |
} |
static int cm7326_ioctl(struct saa7146_fh *fh, unsigned int cmd, void *arg) |
{ |
struct saa7146_dev *dev = fh->dev; |
struct cm7326* card = (struct cm7326*)dev->ext_priv; |
switch(cmd) |
{ |
case VIDIOC_G_INPUT: |
{ |
int* input = (int *)arg; |
*input = card->cur_input; |
DEB_D(("VIDIOC_G_INPUT %d.\n",*input)); |
return 0; |
} |
case VIDIOC_S_INPUT: |
{ |
int input = *(int *)arg; |
int source = 0, sync = 0; |
int i; |
static int saa7110_inputs[6] = {5,4,3,2,1,0}; |
if (input < 0 || input >= CM7326_INPUTS) { |
DEB_D(("v4l2_ioctl: VIDIOC_S_INPUT: invalid input %d.\n",input)); |
return -EINVAL; |
} |
DEB_D(("v4l2_ioctl: VIDIOC_S_INPUT %d.\n",input)); |
source = SAA7146_HPS_SOURCE_PORT_A; |
sync = SAA7146_HPS_SYNC_PORT_A; |
card->cur_input = input; |
/* switch video in saa7110 */ |
saa7146_set_hps_source_and_sync(dev, source, sync); |
i = saa7110_inputs[input]; |
return saa7110_selmux(card, i); |
} |
case VIDIOC_ENUMINPUT: |
{ |
struct v4l2_input *i = arg; |
/* sanity check to satisfy xawtv */ |
if( i->index < 0 || i->index >= CM7326_INPUTS) { |
DEB_D(("v4l2_ioctl: VIDIOC_ENUMINPUT: invalid input %d.\n",i->index)); |
return -EINVAL; |
} |
memcpy(i, &cm7326_inputs[i->index], sizeof(struct v4l2_input)); |
DEB_D(("v4l2_ioctl: VIDIOC_ENUMINPUT %d.\n",i->index)); |
return 0; |
} |
default: |
DEB_D(("v4l2_ioctl does not handle this ioctl.\n")); |
return -ENOIOCTLCMD; |
} |
return 0; |
} |
static int std_callback(struct saa7146_dev *dev, struct saa7146_standard *std) |
{ |
struct cm7326* cm7326 = (struct cm7326*)dev->ext_priv; |
union i2c_smbus_data data; |
int ret = 0; |
data.byte = 0x00; |
ret += i2c_smbus_xfer(&cm7326->i2c_adapter, 0x4e, 0, I2C_SMBUS_WRITE, 0x06, I2C_SMBUS_BYTE_DATA, &data); |
switch (std->id) { |
case V4L2_STD_NTSC: |
{ |
data.byte = 0x06; |
ret += i2c_smbus_xfer(&cm7326->i2c_adapter, 0x4e, 0, I2C_SMBUS_WRITE, 0x0D, I2C_SMBUS_BYTE_DATA, &data); |
data.byte = 0x2C; |
ret += i2c_smbus_xfer(&cm7326->i2c_adapter, 0x4e, 0, I2C_SMBUS_WRITE, 0x11, I2C_SMBUS_BYTE_DATA, &data); |
data.byte = 0x81; |
ret += i2c_smbus_xfer(&cm7326->i2c_adapter, 0x4e, 0, I2C_SMBUS_WRITE, 0x30, I2C_SMBUS_BYTE_DATA, &data); |
data.byte = 0xDF; |
ret += i2c_smbus_xfer(&cm7326->i2c_adapter, 0x4e, 0, I2C_SMBUS_WRITE, 0x2A, I2C_SMBUS_BYTE_DATA, &data); |
break; |
} |
case V4L2_STD_PAL: |
{ |
data.byte = 0x06; |
ret += i2c_smbus_xfer(&cm7326->i2c_adapter, 0x4e, 0, I2C_SMBUS_WRITE, 0x0D, I2C_SMBUS_BYTE_DATA, &data); |
data.byte = 0x59; |
ret += i2c_smbus_xfer(&cm7326->i2c_adapter, 0x4e, 0, I2C_SMBUS_WRITE, 0x11, I2C_SMBUS_BYTE_DATA, &data); |
data.byte = 0x9A; |
ret += i2c_smbus_xfer(&cm7326->i2c_adapter, 0x4e, 0, I2C_SMBUS_WRITE, 0x2E, I2C_SMBUS_BYTE_DATA, &data); |
break; |
} |
case V4L2_STD_SECAM: |
{ |
data.byte = 0x07; |
ret += i2c_smbus_xfer(&cm7326->i2c_adapter, 0x4e, 0, I2C_SMBUS_WRITE, 0x0D, I2C_SMBUS_BYTE_DATA, &data); |
data.byte = 0x59; |
ret += i2c_smbus_xfer(&cm7326->i2c_adapter, 0x4e, 0, I2C_SMBUS_WRITE, 0x11, I2C_SMBUS_BYTE_DATA, &data); |
data.byte = 0x9A; |
ret += i2c_smbus_xfer(&cm7326->i2c_adapter, 0x4e, 0, I2C_SMBUS_WRITE, 0x2E, I2C_SMBUS_BYTE_DATA, &data); |
break; |
} |
default: |
{ |
DEB_S(("invalid standard.\n")); |
return -EFAULT; |
} |
} |
if (0 != ret) { |
DEB_S(("writing to saa7110 failed.\n")); |
return -EFAULT; |
} |
return 0; |
} |
static struct saa7146_standard standard[] = { |
{ |
.name = "PAL", .id = V4L2_STD_PAL, |
.v_offset = 0x17, .v_field = 288, .v_calc = 576, |
.h_offset = 0x14, .h_pixels = 680, .h_calc = 680+1, |
.v_max_out = 576, .h_max_out = 768, |
}, { |
.name = "NTSC", .id = V4L2_STD_NTSC, |
.v_offset = 0x14, .v_field = 240, .v_calc = 480, |
.h_offset = 0x00, .h_pixels = 640, .h_calc = 640+1, |
.v_max_out = 480, .h_max_out = 640, |
}, { |
.name = "SECAM", .id = V4L2_STD_SECAM, |
.v_offset = 0x14, .v_field = 288, .v_calc = 576, |
.h_offset = 0x14, .h_pixels = 720, .h_calc = 720+1, |
.v_max_out = 576, .h_max_out = 768, |
} |
}; |
static struct saa7146_extension extension; |
static struct saa7146_pci_extension_data cm7326 = { |
.ext_priv = "cm7326", |
.ext = &extension, |
}; |
static struct pci_device_id pci_tbl[] = { |
{ |
.vendor = PCI_VENDOR_ID_PHILIPS, |
.device = PCI_DEVICE_ID_PHILIPS_SAA7146, |
.subvendor = 0x1435, |
.subdevice = 0x3303, |
.driver_data = (unsigned long)&cm7326, |
}, { |
.vendor = 0, |
} |
}; |
static struct saa7146_ext_vv vv_data = { |
.inputs = CM7326_INPUTS, |
.capabilities = 0, |
.stds = &standard[0], |
.num_stds = sizeof(standard)/sizeof(struct saa7146_standard), |
.std_callback = &std_callback, |
.ioctls = &ioctls[0], |
.ioctl = cm7326_ioctl, |
}; |
static struct saa7146_extension extension = { |
.name = "cm7326", |
.flags = SAA7146_USE_I2C_IRQ, |
.pci_tbl = &pci_tbl[0], |
.module = THIS_MODULE, |
.probe = cm7326_probe, |
.attach = cm7326_attach, |
.detach = cm7326_detach, |
.irq_mask = 0, |
.irq_func = NULL, |
}; |
int __init cm7326_init_module(void) |
{ |
int ret = 0; |
ret = saa7146_register_extension(&extension); |
if (0 != ret) { |
DEB_S(("failed to register extension.\n")); |
} |
return ret; |
} |
void __exit cm7326_cleanup_module(void) |
{ |
saa7146_unregister_extension(&extension); |
} |
module_init(cm7326_init_module); |
module_exit(cm7326_cleanup_module); |
MODULE_AUTHOR("Miguel Freitas, Michael Hunold"); |
MODULE_DESCRIPTION("CM7326 frame grabber v4l2-driver"); |
MODULE_LICENSE("GPL"); |
/shark/trunk/drivers/cm7326/saa7146_i2c.c |
---|
0,0 → 1,433 |
#include <linux/version.h> |
#include <media/saa7146_vv.h> |
/* 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, int short_delay) |
{ |
u32 status = 0, mc2 = 0; |
int trial = 0; |
unsigned long timeout; |
/* write out i2c-command */ |
DEB_I2C(("before: 0x%08x (status: 0x%08x), %d\n",*dword,saa7146_read(dev, I2C_STATUS), dev->i2c_op)); |
if( 0 != (SAA7146_USE_I2C_IRQ & dev->ext->flags)) { |
saa7146_write(dev, I2C_STATUS, dev->i2c_bitrate); |
saa7146_write(dev, I2C_TRANSFER, *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 (time_after(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) { |
/** |
* first read usually delivers bogus results... |
*/ |
saa7146_i2c_status(dev); |
status = saa7146_i2c_status(dev); |
if ((status & 0x3) != 1) |
break; |
if (time_after(jiffies,timeout)) { |
/* this is normal when probing the bus |
* (no answer from nonexisistant device...) |
*/ |
DEB_I2C(("saa7146_i2c_writeout: timed out waiting for end of xfer\n")); |
return -EIO; |
} |
if ((++trial < 20) && short_delay) |
udelay(10); |
else |
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->d_i2c.cpu_addr; |
int err = 0; |
int address_err = 0; |
int short_delay = 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; |
} |
if ( count > 3 || 0 != (SAA7146_I2C_SHORT_DELAY & dev->ext->flags) ) |
short_delay = 1; |
do { |
/* reset the i2c-device if necessary */ |
err = saa7146_i2c_reset(dev); |
if ( 0 > err ) { |
DEB_I2C(("could not reset i2c-device.\n")); |
goto out; |
} |
/* write out the u32s one after another */ |
for(i = 0; i < count; i++) { |
err = saa7146_i2c_writeout(dev, &buffer[i], short_delay); |
if ( 0 != err) { |
/* this one is unsatisfying: some i2c slaves on some |
dvb cards don't acknowledge correctly, so the saa7146 |
thinks that an address error 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, short_delay)) { |
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); |
/* 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)); |
/* enable i2c-port pins */ |
saa7146_write(dev, MC1, (MASK_08 | MASK_24)); |
dev->i2c_bitrate = bitrate; |
saa7146_i2c_reset(dev); |
if( NULL != i2c_adapter ) { |
memset(i2c_adapter,0,sizeof(struct i2c_adapter)); |
strcpy(i2c_adapter->name, dev->name); |
#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)) |
i2c_adapter->data = dev; |
#else |
i2c_set_adapdata(i2c_adapter,dev); |
#endif |
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; |
#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)) |
#else |
i2c_adapter->class = I2C_ADAP_CLASS_TV_ANALOG; |
#endif |
} |
return 0; |
} |
/shark/trunk/drivers/cm7326/tea6420.c |
---|
0,0 → 1,243 |
/* |
tea6420.o - i2c-driver for the tea6420 by SGS Thomson |
Copyright (C) 1998-2003 Michael Hunold <michael@mihu.de> |
The tea6420 is a bus controlled audio-matrix with 5 stereo inputs, |
4 stereo outputs and gain control for each output. |
It is cascadable, i.e. it can be found at the adresses 0x98 |
and 0x9a on the i2c-bus. |
For detailed informations download the specifications directly |
from SGS Thomson at http://www.st.com |
This program is free software; you can redistribute it and/or modify |
it under the terms of the GNU General Public License as published by |
the Free Software Foundation; either version 2 of the License, or |
(at your option) any later version. |
This program is distributed in the hope that it will be useful, |
but WITHOUT ANY WARRANTY; without even the implied warranty of |
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
GNU General Public License for more details. |
You should have received a copy of the GNU General Public License |
along with this program; if not, write to the Free Software |
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. |
*/ |
#include <linux/version.h> |
#include <linux/module.h> |
#include <linux/kernel.h> |
#include <linux/poll.h> |
#include <linux/slab.h> |
#include <linux/i2c.h> |
#include <linux/init.h> |
#include "tea6420.h" |
static int debug = 0; /* insmod parameter */ |
MODULE_PARM(debug,"i"); |
#define dprintk if (debug) printk |
/* addresses to scan, found only at 0x4c and/or 0x4d (7-Bit) */ |
static unsigned short normal_i2c[] = {I2C_TEA6420_1, I2C_TEA6420_2, I2C_CLIENT_END}; |
static unsigned short normal_i2c_range[] = {I2C_CLIENT_END}; |
/* magic definition of all other variables and things */ |
I2C_CLIENT_INSMOD; |
static struct i2c_driver driver; |
/* unique ID allocation */ |
static int tea6420_id = 0; |
/* make a connection between the input 'i' and the output 'o' |
with gain 'g' for the tea6420-client 'client' (note: i = 6 means 'mute') */ |
static int tea6420_switch(struct i2c_client *client, int i, int o, int g) |
{ |
u8 byte = 0; |
int result = 0; |
dprintk("tea6420.o: tea6420_switch: adr:0x%02x, i:%d, o:%d, g:%d\n",client->addr,i,o,g); |
/* check if the paramters are valid */ |
if ( i < 1 || i > 6 || o < 1 || o > 4 || g < 0 || g > 6 || g%2 != 0 ) |
return -1; |
byte = ((o-1)<<5); |
byte |= (i-1); |
/* to understand this, have a look at the tea6420-specs (p.5) */ |
switch(g) { |
case 0: |
byte |= (3<<3); |
break; |
case 2: |
byte |= (2<<3); |
break; |
case 4: |
byte |= (1<<3); |
break; |
case 6: |
break; |
} |
/* fixme?: 1 != ... => 0 != */ |
if ( 0 != (result = i2c_smbus_write_byte(client,byte))) { |
printk("tea6402:%d\n",result); |
dprintk(KERN_ERR "tea6420.o: could not switch, result:%d\n",result); |
return -EFAULT; |
} |
return 0; |
} |
/* this function is called by i2c_probe */ |
static int tea6420_detect(struct i2c_adapter *adapter, int address, unsigned short flags, int kind) |
{ |
struct i2c_client *client; |
int err = 0, i = 0; |
/* let's see whether this adapter can support what we need */ |
if ( 0 == i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WRITE_BYTE)) { |
return 0; |
} |
/* allocate memory for client structure */ |
client = kmalloc(sizeof(struct i2c_client), GFP_KERNEL); |
if (0 == client) { |
return -ENOMEM; |
} |
memset(client, 0, sizeof(struct i2c_client)); |
/* fill client structure */ |
#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)) |
sprintf(client->name,"tea6420 (0x%02x)", address); |
#else |
sprintf(client->dev.name,"tea6420 (0x%02x)", address); |
#endif |
client->id = tea6420_id++; |
client->flags = 0; |
client->addr = address; |
client->adapter = adapter; |
client->driver = &driver; |
/* tell the i2c layer a new client has arrived */ |
if (0 != (err = i2c_attach_client(client))) { |
kfree(client); |
return err; |
} |
/* set initial values: set "mute"-input to all outputs at gain 0 */ |
err = 0; |
for(i = 1; i < 5; i++) { |
err += tea6420_switch(client, 6, i, 0); |
} |
if( 0 != err) { |
printk("tea6420.o: could not initialize chipset. continuing anyway.\n"); |
} |
#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)) |
printk("tea6420.o: detected @ 0x%02x on adapter %s\n",address,&client->adapter->name[0]); |
#else |
printk("tea6420.o: detected @ 0x%02x on adapter %s\n",address,&client->adapter->dev.name[0]); |
#endif |
return 0; |
} |
static int tea6420_attach(struct i2c_adapter *adapter) |
{ |
/* let's see whether this is a know adapter we can attach to */ |
if( adapter->id != I2C_ALGO_SAA7146 ) { |
#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)) |
dprintk("tea6420.o: refusing to probe on unknown adapter [name='%s',id=0x%x]\n",adapter->name,adapter->id); |
#else |
dprintk("tea6420.o: refusing to probe on unknown adapter [name='%s',id=0x%x]\n",adapter->dev.name,adapter->id); |
#endif |
return -ENODEV; |
} |
return i2c_probe(adapter,&addr_data,&tea6420_detect); |
} |
static int tea6420_detach(struct i2c_client *client) |
{ |
int err = 0; |
if ( 0 != (err = i2c_detach_client(client))) { |
printk("tea6420.o: Client deregistration failed, client not detached.\n"); |
return err; |
} |
kfree(client); |
return 0; |
} |
static int tea6420_command(struct i2c_client *client, unsigned int cmd, void* arg) |
{ |
struct tea6420_multiplex *a = (struct tea6420_multiplex*)arg; |
int result = 0; |
switch (cmd) { |
case TEA6420_SWITCH: { |
result = tea6420_switch(client,a->in,a->out,a->gain); |
break; |
} |
default: { |
return -ENOIOCTLCMD; |
} |
} |
if ( 0 != result ) |
return result; |
return 0; |
} |
static void tea6420_inc_use(struct i2c_client *client) |
{ |
#ifdef MODULE |
MOD_INC_USE_COUNT; |
#endif |
} |
static void tea6420_dec_use(struct i2c_client *client) |
{ |
#ifdef MODULE |
MOD_DEC_USE_COUNT; |
#endif |
} |
static struct i2c_driver driver = { |
#if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,54) |
.owner = THIS_MODULE, |
#endif |
.name = "tea6420 driver", |
.id = I2C_DRIVERID_TEA6420, |
.flags = I2C_DF_NOTIFY, |
.attach_adapter = tea6420_attach, |
.detach_client = tea6420_detach, |
.command = tea6420_command, |
.inc_use = tea6420_inc_use, |
.dec_use = tea6420_dec_use, |
}; |
static int tea6420_init_module(void) |
{ |
i2c_add_driver(&driver); |
return 0; |
} |
static void tea6420_cleanup_module(void) |
{ |
i2c_del_driver(&driver); |
} |
module_init(tea6420_init_module); |
module_exit(tea6420_cleanup_module); |
MODULE_AUTHOR("Michael Hunold <michael@mihu.de>"); |
MODULE_DESCRIPTION("tea6420 driver"); |
MODULE_LICENSE("GPL"); |
/shark/trunk/drivers/cm7326/mxb.c |
---|
0,0 → 1,1092 |
/* |
mxb.c - v4l2 driver for the Multimedia eXtension Board |
Copyright (C) 1998-2003 Michael Hunold <michael@mihu.de> |
Visit http://www.mihu.de/linux/saa7146/mxb/ |
for further details about this card. |
This program is free software; you can redistribute it and/or modify |
it under the terms of the GNU General Public License as published by |
the Free Software Foundation; either version 2 of the License, or |
(at your option) any later version. |
This program is distributed in the hope that it will be useful, |
but WITHOUT ANY WARRANTY; without even the implied warranty of |
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
GNU General Public License for more details. |
You should have received a copy of the GNU General Public License |
along with this program; if not, write to the Free Software |
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. |
*/ |
#define DEBUG_VARIABLE debug |
#include <media/saa7146_vv.h> |
#include <linux/video_decoder.h> /* for saa7111a */ |
#include "mxb.h" |
#include "tea6415c.h" |
#include "tea6420.h" |
#include "tda9840.h" |
#include <media/tuner.h> |
#define I2C_SAA7111A 0x24 |
/* All unused bytes are reserverd. */ |
#define SAA711X_CHIP_VERSION 0x00 |
#define SAA711X_ANALOG_INPUT_CONTROL_1 0x02 |
#define SAA711X_ANALOG_INPUT_CONTROL_2 0x03 |
#define SAA711X_ANALOG_INPUT_CONTROL_3 0x04 |
#define SAA711X_ANALOG_INPUT_CONTROL_4 0x05 |
#define SAA711X_HORIZONTAL_SYNC_START 0x06 |
#define SAA711X_HORIZONTAL_SYNC_STOP 0x07 |
#define SAA711X_SYNC_CONTROL 0x08 |
#define SAA711X_LUMINANCE_CONTROL 0x09 |
#define SAA711X_LUMINANCE_BRIGHTNESS 0x0A |
#define SAA711X_LUMINANCE_CONTRAST 0x0B |
#define SAA711X_CHROMA_SATURATION 0x0C |
#define SAA711X_CHROMA_HUE_CONTROL 0x0D |
#define SAA711X_CHROMA_CONTROL 0x0E |
#define SAA711X_FORMAT_DELAY_CONTROL 0x10 |
#define SAA711X_OUTPUT_CONTROL_1 0x11 |
#define SAA711X_OUTPUT_CONTROL_2 0x12 |
#define SAA711X_OUTPUT_CONTROL_3 0x13 |
#define SAA711X_V_GATE_1_START 0x15 |
#define SAA711X_V_GATE_1_STOP 0x16 |
#define SAA711X_V_GATE_1_MSB 0x17 |
#define SAA711X_TEXT_SLICER_STATUS 0x1A |
#define SAA711X_DECODED_BYTES_OF_TS_1 0x1B |
#define SAA711X_DECODED_BYTES_OF_TS_2 0x1C |
#define SAA711X_STATUS_BYTE 0x1F |
#define MXB_BOARD_CAN_DO_VBI(dev) (dev->revision != 0) |
/* global variable */ |
static int mxb_num = 0; |
/* initial frequence the tuner will be tuned to. |
in verden (lower saxony, germany) 4148 is a |
channel called "phoenix" */ |
static int freq = 4148; |
MODULE_PARM(freq,"i"); |
MODULE_PARM_DESC(freq, "initial frequency the tuner will be tuned to while setup"); |
static int debug = 0; |
MODULE_PARM(debug,"i"); |
MODULE_PARM_DESC(debug, "debug verbosity"); |
#define MXB_INPUTS 4 |
enum { TUNER, AUX1, AUX3, AUX3_YC }; |
static struct v4l2_input mxb_inputs[MXB_INPUTS] = { |
{ TUNER, "Tuner", V4L2_INPUT_TYPE_TUNER, 1, 0, V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, 0 }, |
{ AUX1, "AUX1", V4L2_INPUT_TYPE_CAMERA, 2, 0, V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, 0 }, |
{ AUX3, "AUX3 Composite", V4L2_INPUT_TYPE_CAMERA, 4, 0, V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, 0 }, |
{ AUX3_YC, "AUX3 S-Video", V4L2_INPUT_TYPE_CAMERA, 4, 0, V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, 0 }, |
}; |
/* this array holds the information, which port of the saa7146 each |
input actually uses. the mxb uses port 0 for every input */ |
static struct { |
int hps_source; |
int hps_sync; |
} input_port_selection[MXB_INPUTS] = { |
{ SAA7146_HPS_SOURCE_PORT_A, SAA7146_HPS_SYNC_PORT_A }, |
{ SAA7146_HPS_SOURCE_PORT_A, SAA7146_HPS_SYNC_PORT_A }, |
{ SAA7146_HPS_SOURCE_PORT_A, SAA7146_HPS_SYNC_PORT_A }, |
{ SAA7146_HPS_SOURCE_PORT_A, SAA7146_HPS_SYNC_PORT_A }, |
}; |
/* this array holds the information of the audio source (mxb_audios), |
which has to be switched corresponding to the video source (mxb_channels) */ |
static int video_audio_connect[MXB_INPUTS] = |
{ 0, 1, 3, 3 }; |
/* these are the necessary input-output-pins for bringing one audio source |
(see above) to the CD-output */ |
static struct tea6420_multiplex TEA6420_cd[MXB_AUDIOS+1][2] = |
{ |
{{1,1,0},{1,1,0}}, /* Tuner */ |
{{5,1,0},{6,1,0}}, /* AUX 1 */ |
{{4,1,0},{6,1,0}}, /* AUX 2 */ |
{{3,1,0},{6,1,0}}, /* AUX 3 */ |
{{1,1,0},{3,1,0}}, /* Radio */ |
{{1,1,0},{2,1,0}}, /* CD-Rom */ |
{{6,1,0},{6,1,0}} /* Mute */ |
}; |
/* these are the necessary input-output-pins for bringing one audio source |
(see above) to the line-output */ |
static struct tea6420_multiplex TEA6420_line[MXB_AUDIOS+1][2] = |
{ |
{{2,3,0},{1,2,0}}, |
{{5,3,0},{6,2,0}}, |
{{4,3,0},{6,2,0}}, |
{{3,3,0},{6,2,0}}, |
{{2,3,0},{3,2,0}}, |
{{2,3,0},{2,2,0}}, |
{{6,3,0},{6,2,0}} /* Mute */ |
}; |
#define MAXCONTROLS 1 |
static struct v4l2_queryctrl mxb_controls[] = { |
{ V4L2_CID_AUDIO_MUTE, V4L2_CTRL_TYPE_BOOLEAN, "Mute", 0, 1, 1, 0, 0 }, |
}; |
static struct saa7146_extension_ioctls ioctls[] = { |
{ VIDIOC_ENUMINPUT, SAA7146_EXCLUSIVE }, |
{ VIDIOC_G_INPUT, SAA7146_EXCLUSIVE }, |
{ VIDIOC_S_INPUT, SAA7146_EXCLUSIVE }, |
{ VIDIOC_QUERYCTRL, SAA7146_BEFORE }, |
{ VIDIOC_G_CTRL, SAA7146_BEFORE }, |
{ VIDIOC_S_CTRL, SAA7146_BEFORE }, |
{ VIDIOC_G_TUNER, SAA7146_EXCLUSIVE }, |
{ VIDIOC_S_TUNER, SAA7146_EXCLUSIVE }, |
{ VIDIOC_G_FREQUENCY, SAA7146_EXCLUSIVE }, |
{ VIDIOC_S_FREQUENCY, SAA7146_EXCLUSIVE }, |
{ VIDIOC_G_AUDIO, SAA7146_EXCLUSIVE }, |
{ VIDIOC_S_AUDIO, SAA7146_EXCLUSIVE }, |
{ MXB_S_AUDIO_CD, SAA7146_EXCLUSIVE }, /* custom control */ |
{ MXB_S_AUDIO_LINE, SAA7146_EXCLUSIVE }, /* custom control */ |
{ 0, 0 } |
}; |
struct mxb |
{ |
struct video_device video_dev; |
struct video_device vbi_dev; |
struct i2c_adapter i2c_adapter; |
struct i2c_client* saa7111a; |
struct i2c_client* tda9840; |
struct i2c_client* tea6415c; |
struct i2c_client* tuner; |
struct i2c_client* tea6420_1; |
struct i2c_client* tea6420_2; |
int cur_mode; /* current audio mode (mono, stereo, ...) */ |
int cur_input; /* current input */ |
int cur_freq; /* current frequency the tuner is tuned to */ |
int cur_mute; /* current mute status */ |
}; |
static struct saa7146_extension extension; |
static int mxb_vbi_bypass(struct saa7146_dev* dev) |
{ |
struct mxb* mxb = (struct mxb*)dev->ext_priv; |
s32 byte = 0x0; |
int result = 0; |
DEB_EE(("dev:%p\n",dev)); |
/* switch bypass in saa7111a, this should be done in the |
saa7111a driver of course... */ |
if ( -1 == (result = i2c_smbus_read_byte_data(mxb->saa7111a, SAA711X_OUTPUT_CONTROL_3))) { |
DEB_D(("could not read from saa7111a.\n")); |
return -EFAULT; |
} |
byte = result; |
byte &= 0xf0; |
byte |= 0x0a; |
if ( 0 != (result = i2c_smbus_write_byte_data(mxb->saa7111a, SAA711X_OUTPUT_CONTROL_3, byte))) { |
DEB_D(("could not write to saa7111a.\n")); |
return -EFAULT; |
} |
return 0; |
} |
static int mxb_probe(struct saa7146_dev* dev) |
{ |
struct mxb* mxb = 0; |
int i = 0; |
request_module("tuner"); |
request_module("tea6420"); |
request_module("tea6415c"); |
request_module("tda9840"); |
request_module("saa7111"); |
mxb = (struct mxb*)kmalloc(sizeof(struct mxb), GFP_KERNEL); |
if( NULL == mxb ) { |
DEB_D(("not enough kernel memory.\n")); |
return -ENOMEM; |
} |
memset(mxb, 0x0, sizeof(struct mxb)); |
saa7146_i2c_adapter_prepare(dev, &mxb->i2c_adapter, SAA7146_I2C_BUS_BIT_RATE_480); |
if(i2c_add_adapter(&mxb->i2c_adapter) < 0) { |
DEB_S(("cannot register i2c-device. skipping.\n")); |
kfree(mxb); |
return -EFAULT; |
} |
/* loop through all i2c-devices on the bus and look who is there */ |
for(i = 0; i < I2C_CLIENT_MAX; i++) { |
if( NULL == mxb->i2c_adapter.clients[i] ) { |
continue; |
} |
if( I2C_TEA6420_1 == mxb->i2c_adapter.clients[i]->addr ) |
mxb->tea6420_1 = mxb->i2c_adapter.clients[i]; |
if( I2C_TEA6420_2 == mxb->i2c_adapter.clients[i]->addr ) |
mxb->tea6420_2 = mxb->i2c_adapter.clients[i]; |
if( I2C_TEA6415C_2 == mxb->i2c_adapter.clients[i]->addr ) |
mxb->tea6415c = mxb->i2c_adapter.clients[i]; |
if( I2C_TDA9840 == mxb->i2c_adapter.clients[i]->addr ) |
mxb->tda9840 = mxb->i2c_adapter.clients[i]; |
if( I2C_SAA7111A == mxb->i2c_adapter.clients[i]->addr ) |
mxb->saa7111a = mxb->i2c_adapter.clients[i]; |
if( 0x60 == mxb->i2c_adapter.clients[i]->addr ) |
mxb->tuner = mxb->i2c_adapter.clients[i]; |
} |
/* check if all devices are present */ |
if( 0 == mxb->tea6420_1 || 0 == mxb->tea6420_2 || 0 == mxb->tea6415c |
|| 0 == mxb->tda9840 || 0 == mxb->saa7111a || 0 == mxb->tuner ) { |
printk("mxb: did not find all i2c devices. are you sure you\n"); |
printk("mxb: insmod'ed tea6420, tea6415c, saa7111, tea6415c and tuner?\n"); |
i2c_del_adapter(&mxb->i2c_adapter); |
kfree(mxb); |
return -ENODEV; |
} |
/* all devices are present, probe was successful */ |
/* we store the pointer in our private data field */ |
(struct mxb*)dev->ext_priv = mxb; |
return 0; |
} |
/* some init data for the saa7740, the so-called 'sound arena module'. |
there are no specs available, so we simply use some init values */ |
static struct { |
int length; |
char data[9]; |
} mxb_saa7740_init[] = { |
{ 3, { 0x80, 0x00, 0x00 } },{ 3, { 0x80, 0x89, 0x00 } }, |
{ 3, { 0x80, 0xb0, 0x0a } },{ 3, { 0x00, 0x00, 0x00 } }, |
{ 3, { 0x49, 0x00, 0x00 } },{ 3, { 0x4a, 0x00, 0x00 } }, |
{ 3, { 0x4b, 0x00, 0x00 } },{ 3, { 0x4c, 0x00, 0x00 } }, |
{ 3, { 0x4d, 0x00, 0x00 } },{ 3, { 0x4e, 0x00, 0x00 } }, |
{ 3, { 0x4f, 0x00, 0x00 } },{ 3, { 0x50, 0x00, 0x00 } }, |
{ 3, { 0x51, 0x00, 0x00 } },{ 3, { 0x52, 0x00, 0x00 } }, |
{ 3, { 0x53, 0x00, 0x00 } },{ 3, { 0x54, 0x00, 0x00 } }, |
{ 3, { 0x55, 0x00, 0x00 } },{ 3, { 0x56, 0x00, 0x00 } }, |
{ 3, { 0x57, 0x00, 0x00 } },{ 3, { 0x58, 0x00, 0x00 } }, |
{ 3, { 0x59, 0x00, 0x00 } },{ 3, { 0x5a, 0x00, 0x00 } }, |
{ 3, { 0x5b, 0x00, 0x00 } },{ 3, { 0x5c, 0x00, 0x00 } }, |
{ 3, { 0x5d, 0x00, 0x00 } },{ 3, { 0x5e, 0x00, 0x00 } }, |
{ 3, { 0x5f, 0x00, 0x00 } },{ 3, { 0x60, 0x00, 0x00 } }, |
{ 3, { 0x61, 0x00, 0x00 } },{ 3, { 0x62, 0x00, 0x00 } }, |
{ 3, { 0x63, 0x00, 0x00 } },{ 3, { 0x64, 0x00, 0x00 } }, |
{ 3, { 0x65, 0x00, 0x00 } },{ 3, { 0x66, 0x00, 0x00 } }, |
{ 3, { 0x67, 0x00, 0x00 } },{ 3, { 0x68, 0x00, 0x00 } }, |
{ 3, { 0x69, 0x00, 0x00 } },{ 3, { 0x6a, 0x00, 0x00 } }, |
{ 3, { 0x6b, 0x00, 0x00 } },{ 3, { 0x6c, 0x00, 0x00 } }, |
{ 3, { 0x6d, 0x00, 0x00 } },{ 3, { 0x6e, 0x00, 0x00 } }, |
{ 3, { 0x6f, 0x00, 0x00 } },{ 3, { 0x70, 0x00, 0x00 } }, |
{ 3, { 0x71, 0x00, 0x00 } },{ 3, { 0x72, 0x00, 0x00 } }, |
{ 3, { 0x73, 0x00, 0x00 } },{ 3, { 0x74, 0x00, 0x00 } }, |
{ 3, { 0x75, 0x00, 0x00 } },{ 3, { 0x76, 0x00, 0x00 } }, |
{ 3, { 0x77, 0x00, 0x00 } },{ 3, { 0x41, 0x00, 0x42 } }, |
{ 3, { 0x42, 0x10, 0x42 } },{ 3, { 0x43, 0x20, 0x42 } }, |
{ 3, { 0x44, 0x30, 0x42 } },{ 3, { 0x45, 0x00, 0x01 } }, |
{ 3, { 0x46, 0x00, 0x01 } },{ 3, { 0x47, 0x00, 0x01 } }, |
{ 3, { 0x48, 0x00, 0x01 } }, |
{ 9, { 0x01, 0x03, 0xc5, 0x5c, 0x7a, 0x85, 0x01, 0x00, 0x54 } }, |
{ 9, { 0x21, 0x03, 0xc5, 0x5c, 0x7a, 0x85, 0x01, 0x00, 0x54 } }, |
{ 9, { 0x09, 0x0b, 0xb4, 0x6b, 0x74, 0x85, 0x95, 0x00, 0x34 } }, |
{ 9, { 0x29, 0x0b, 0xb4, 0x6b, 0x74, 0x85, 0x95, 0x00, 0x34 } }, |
{ 9, { 0x11, 0x17, 0x43, 0x62, 0x68, 0x89, 0xd1, 0xff, 0xb0 } }, |
{ 9, { 0x31, 0x17, 0x43, 0x62, 0x68, 0x89, 0xd1, 0xff, 0xb0 } }, |
{ 9, { 0x19, 0x20, 0x62, 0x51, 0x5a, 0x95, 0x19, 0x01, 0x50 } }, |
{ 9, { 0x39, 0x20, 0x62, 0x51, 0x5a, 0x95, 0x19, 0x01, 0x50 } }, |
{ 9, { 0x05, 0x3e, 0xd2, 0x69, 0x4e, 0x9a, 0x51, 0x00, 0xf0 } }, |
{ 9, { 0x25, 0x3e, 0xd2, 0x69, 0x4e, 0x9a, 0x51, 0x00, 0xf0 } }, |
{ 9, { 0x0d, 0x3d, 0xa1, 0x40, 0x7d, 0x9f, 0x29, 0xfe, 0x14 } }, |
{ 9, { 0x2d, 0x3d, 0xa1, 0x40, 0x7d, 0x9f, 0x29, 0xfe, 0x14 } }, |
{ 9, { 0x15, 0x73, 0xa1, 0x50, 0x5d, 0xa6, 0xf5, 0xfe, 0x38 } }, |
{ 9, { 0x35, 0x73, 0xa1, 0x50, 0x5d, 0xa6, 0xf5, 0xfe, 0x38 } }, |
{ 9, { 0x1d, 0xed, 0xd0, 0x68, 0x29, 0xb4, 0xe1, 0x00, 0xb8 } }, |
{ 9, { 0x3d, 0xed, 0xd0, 0x68, 0x29, 0xb4, 0xe1, 0x00, 0xb8 } }, |
{ 3, { 0x80, 0xb3, 0x0a } }, |
{-1, { 0} } |
}; |
static unsigned char mxb_saa7111_init[25] = { |
0x00, |
0x00, /* 00 - ID byte */ |
0x00, /* 01 - reserved */ |
/*front end */ |
0xd8, /* 02 - FUSE=x, GUDL=x, MODE=x */ |
0x23, /* 03 - HLNRS=0, VBSL=1, WPOFF=0, HOLDG=0, GAFIX=0, GAI1=256, GAI2=256 */ |
0x00, /* 04 - GAI1=256 */ |
0x00, /* 05 - GAI2=256 */ |
/* decoder */ |
0xf0, /* 06 - HSB at xx(50Hz) / xx(60Hz) pixels after end of last line */ |
0x30, /* 07 - HSS at xx(50Hz) / xx(60Hz) pixels after end of last line */ |
0xa8, /* 08 - AUFD=x, FSEL=x, EXFIL=x, VTRC=x, HPLL=x, VNOI=x */ |
0x02, /* 09 - BYPS=x, PREF=x, BPSS=x, VBLB=x, UPTCV=x, APER=x */ |
0x80, /* 0a - BRIG=128 */ |
0x47, /* 0b - CONT=1.109 */ |
0x40, /* 0c - SATN=1.0 */ |
0x00, /* 0d - HUE=0 */ |
0x01, /* 0e - CDTO=0, CSTD=0, DCCF=0, FCTC=0, CHBW=1 */ |
0x00, /* 0f - reserved */ |
0xd0, /* 10 - OFTS=x, HDEL=x, VRLN=x, YDEL=x */ |
0x8c, /* 11 - GPSW=x, CM99=x, FECO=x, COMPO=x, OEYC=1, OEHV=1, VIPB=0, COLO=0 */ |
0x80, /* 12 - xx output control 2 */ |
0x30, /* 13 - xx output control 3 */ |
0x00, /* 14 - reserved */ |
0x15, /* 15 - VBI */ |
0x04, /* 16 - VBI */ |
0x00, /* 17 - VBI */ |
}; |
/* bring hardware to a sane state. this has to be done, just in case someone |
wants to capture from this device before it has been properly initialized. |
the capture engine would badly fail, because no valid signal arrives on the |
saa7146, thus leading to timeouts and stuff. */ |
static int mxb_init_done(struct saa7146_dev* dev) |
{ |
struct mxb* mxb = (struct mxb*)dev->ext_priv; |
struct i2c_msg msg; |
int i = 0, err = 0; |
struct tea6415c_multiplex vm; |
/* write configuration to saa7111a */ |
i = i2c_master_send(mxb->saa7111a, mxb_saa7111_init, sizeof(mxb_saa7111_init)); |
if (i < 0) { |
printk("failed to initialize saa7111a. this should never happen.\n"); |
} |
/* select tuner-output on saa7111a */ |
i = 0; |
mxb->saa7111a->driver->command(mxb->saa7111a,DECODER_SET_INPUT, &i); |
// i = VIDEO_MODE_PAL; |
// mxb->saa7111a->driver->command(mxb->saa7111a,DECODER_SET_NORM, &i); |
mxb_vbi_bypass(dev); |
/* select a tuner type */ |
i = 5; |
mxb->tuner->driver->command(mxb->tuner,TUNER_SET_TYPE, &i); |
/* mute audio on tea6420s */ |
mxb->tea6420_1->driver->command(mxb->tea6420_1,TEA6420_SWITCH, &TEA6420_line[6][0]); |
mxb->tea6420_2->driver->command(mxb->tea6420_2,TEA6420_SWITCH, &TEA6420_line[6][1]); |
mxb->tea6420_1->driver->command(mxb->tea6420_1,TEA6420_SWITCH, &TEA6420_cd[6][0]); |
mxb->tea6420_2->driver->command(mxb->tea6420_2,TEA6420_SWITCH, &TEA6420_cd[6][1]); |
/* switch to tuner-channel on tea6415c*/ |
vm.out = 17; |
vm.in = 3; |
mxb->tea6415c->driver->command(mxb->tea6415c,TEA6415C_SWITCH, &vm); |
/* select tuner-output on multicable on tea6415c*/ |
vm.in = 3; |
vm.out = 13; |
mxb->tea6415c->driver->command(mxb->tea6415c,TEA6415C_SWITCH, &vm); |
/* tune in some frequency on tuner */ |
mxb->tuner->driver->command(mxb->tuner, VIDIOCSFREQ, &freq); |
/* the rest for mxb */ |
mxb->cur_input = 0; |
mxb->cur_freq = freq; |
mxb->cur_mute = 1; |
mxb->cur_mode = V4L2_TUNER_MODE_STEREO; |
mxb->tda9840->driver->command(mxb->tda9840, TDA9840_SWITCH, &mxb->cur_mode); |
/* check if the saa7740 (aka 'sound arena module') is present |
on the mxb. if so, we must initialize it. due to lack of |
informations about the saa7740, the values were reverse |
engineered. */ |
msg.addr = 0x1b; |
msg.flags = 0; |
msg.len = mxb_saa7740_init[0].length; |
msg.buf = &mxb_saa7740_init[0].data[0]; |
if( 1 == (err = i2c_transfer(&mxb->i2c_adapter, &msg, 1))) { |
/* the sound arena module is a pos, that's probably the reason |
philips refuses to hand out a datasheet for the saa7740... |
it seems to screw up the i2c bus, so we disable fast irq |
based i2c transactions here and rely on the slow and safe |
polling method ... */ |
extension.flags &= ~SAA7146_USE_I2C_IRQ; |
for(i = 1;;i++) { |
if( -1 == mxb_saa7740_init[i].length ) { |
break; |
} |
msg.len = mxb_saa7740_init[i].length; |
msg.buf = &mxb_saa7740_init[i].data[0]; |
if( 1 != (err = i2c_transfer(&mxb->i2c_adapter, &msg, 1))) { |
DEB_D(("failed to initialize 'sound arena module'.\n")); |
goto err; |
} |
} |
INFO(("'sound arena module' detected.\n")); |
} |
err: |
/* the rest for saa7146: you should definitely set some basic values |
for the input-port handling of the saa7146. */ |
/* ext->saa has been filled by the core driver */ |
/* some stuff is done via variables */ |
saa7146_set_hps_source_and_sync(dev, input_port_selection[mxb->cur_input].hps_source, input_port_selection[mxb->cur_input].hps_sync); |
/* some stuff is done via direct write to the registers */ |
/* this is ugly, but because of the fact that this is completely |
hardware dependend, it should be done directly... */ |
saa7146_write(dev, DD1_STREAM_B, 0x00000000); |
saa7146_write(dev, DD1_INIT, 0x02000200); |
saa7146_write(dev, MC2, (MASK_09 | MASK_25 | MASK_10 | MASK_26)); |
return 0; |
} |
/* interrupt-handler. this gets called when irq_mask is != 0. |
it must clear the interrupt-bits in irq_mask it has handled */ |
/* |
void mxb_irq_bh(struct saa7146_dev* dev, u32* irq_mask) |
{ |
struct mxb* mxb = (struct mxb*)dev->ext_priv; |
} |
*/ |
static struct saa7146_ext_vv vv_data; |
/* this function only gets called when the probing was successful */ |
static int mxb_attach(struct saa7146_dev* dev, struct saa7146_pci_extension_data *info) |
{ |
struct mxb* mxb = (struct mxb*)dev->ext_priv; |
DEB_EE(("dev:%p\n",dev)); |
/* checking for i2c-devices can be omitted here, because we |
already did this in "mxb_vl42_probe" */ |
saa7146_vv_init(dev,&vv_data); |
if( 0 != saa7146_register_device(&mxb->video_dev, dev, "mxb", VFL_TYPE_GRABBER)) { |
ERR(("cannot register capture v4l2 device. skipping.\n")); |
return -1; |
} |
/* initialization stuff (vbi) (only for revision > 0 and for extensions which want it)*/ |
if( 0 != MXB_BOARD_CAN_DO_VBI(dev)) { |
if( 0 != saa7146_register_device(&mxb->vbi_dev, dev, "mxb", VFL_TYPE_VBI)) { |
ERR(("cannot register vbi v4l2 device. skipping.\n")); |
} |
} |
i2c_use_client(mxb->tea6420_1); |
i2c_use_client(mxb->tea6420_2); |
i2c_use_client(mxb->tea6415c); |
i2c_use_client(mxb->tda9840); |
i2c_use_client(mxb->saa7111a); |
i2c_use_client(mxb->tuner); |
printk("mxb: found 'Multimedia eXtension Board'-%d.\n",mxb_num); |
mxb_num++; |
mxb_init_done(dev); |
return 0; |
} |
static int mxb_detach(struct saa7146_dev* dev) |
{ |
struct mxb* mxb = (struct mxb*)dev->ext_priv; |
DEB_EE(("dev:%p\n",dev)); |
i2c_release_client(mxb->tea6420_1); |
i2c_release_client(mxb->tea6420_2); |
i2c_release_client(mxb->tea6415c); |
i2c_release_client(mxb->tda9840); |
i2c_release_client(mxb->saa7111a); |
i2c_release_client(mxb->tuner); |
saa7146_unregister_device(&mxb->video_dev,dev); |
if( 0 != MXB_BOARD_CAN_DO_VBI(dev)) { |
saa7146_unregister_device(&mxb->vbi_dev,dev); |
} |
saa7146_vv_release(dev); |
mxb_num--; |
i2c_del_adapter(&mxb->i2c_adapter); |
kfree(mxb); |
return 0; |
} |
/* hack: this should go into saa711x */ |
static int saa7111_set_gpio(struct saa7146_dev *dev, int bl) |
{ |
struct mxb* mxb = (struct mxb*)dev->ext_priv; |
s32 byte = 0x0; |
int result = 0; |
DEB_EE(("dev:%p\n",dev)); |
/* get the old register contents */ |
if ( -1 == (byte = i2c_smbus_read_byte_data(mxb->saa7111a, SAA711X_OUTPUT_CONTROL_1))) { |
DEB_D(("could not read from saa711x\n")); |
return -EFAULT; |
} |
if( 0 == bl ) { |
byte &= 0x7f; |
} else { |
byte |= 0x80; |
} |
/* write register contents back */ |
if ( 0 != (result = i2c_smbus_write_byte_data(mxb->saa7111a, SAA711X_OUTPUT_CONTROL_1, byte))) { |
DEB_D(("could not write to saa711x\n")); |
return -EFAULT; |
} |
return 0; |
} |
static int mxb_ioctl(struct saa7146_fh *fh, unsigned int cmd, void *arg) |
{ |
struct saa7146_dev *dev = fh->dev; |
struct mxb* mxb = (struct mxb*)dev->ext_priv; |
struct saa7146_vv *vv = dev->vv_data; |
switch(cmd) { |
case VIDIOC_ENUMINPUT: |
{ |
struct v4l2_input *i = arg; |
DEB_EE(("VIDIOC_ENUMINPUT %d.\n",i->index)); |
if( i->index < 0 || i->index >= MXB_INPUTS) { |
return -EINVAL; |
} |
memcpy(i, &mxb_inputs[i->index], sizeof(struct v4l2_input)); |
return 0; |
} |
/* the saa7146 provides some controls (brightness, contrast, saturation) |
which gets registered *after* this function. because of this we have |
to return with a value != 0 even if the function succeded.. */ |
case VIDIOC_QUERYCTRL: |
{ |
struct v4l2_queryctrl *qc = arg; |
int i; |
for (i = MAXCONTROLS - 1; i >= 0; i--) { |
if (mxb_controls[i].id == qc->id) { |
*qc = mxb_controls[i]; |
DEB_D(("VIDIOC_QUERYCTRL %d.\n",qc->id)); |
return 0; |
} |
} |
return -EAGAIN; |
} |
case VIDIOC_G_CTRL: |
{ |
struct v4l2_control *vc = arg; |
int i; |
for (i = MAXCONTROLS - 1; i >= 0; i--) { |
if (mxb_controls[i].id == vc->id) { |
break; |
} |
} |
if( i < 0 ) { |
return -EAGAIN; |
} |
switch (vc->id ) { |
case V4L2_CID_AUDIO_MUTE: { |
vc->value = mxb->cur_mute; |
DEB_D(("VIDIOC_G_CTRL V4L2_CID_AUDIO_MUTE:%d.\n",vc->value)); |
return 0; |
} |
} |
DEB_EE(("VIDIOC_G_CTRL V4L2_CID_AUDIO_MUTE:%d.\n",vc->value)); |
return 0; |
} |
case VIDIOC_S_CTRL: |
{ |
struct v4l2_control *vc = arg; |
int i = 0; |
for (i = MAXCONTROLS - 1; i >= 0; i--) { |
if (mxb_controls[i].id == vc->id) { |
break; |
} |
} |
if( i < 0 ) { |
return -EAGAIN; |
} |
switch (vc->id ) { |
case V4L2_CID_AUDIO_MUTE: { |
mxb->cur_mute = vc->value; |
if( 0 == vc->value ) { |
/* switch the audio-source */ |
mxb->tea6420_1->driver->command(mxb->tea6420_1,TEA6420_SWITCH, &TEA6420_line[video_audio_connect[mxb->cur_input]][0]); |
mxb->tea6420_2->driver->command(mxb->tea6420_2,TEA6420_SWITCH, &TEA6420_line[video_audio_connect[mxb->cur_input]][1]); |
} else { |
mxb->tea6420_1->driver->command(mxb->tea6420_1,TEA6420_SWITCH, &TEA6420_line[6][0]); |
mxb->tea6420_2->driver->command(mxb->tea6420_2,TEA6420_SWITCH, &TEA6420_line[6][1]); |
} |
DEB_EE(("VIDIOC_S_CTRL, V4L2_CID_AUDIO_MUTE: %d.\n",vc->value)); |
break; |
} |
} |
return 0; |
} |
case VIDIOC_G_INPUT: |
{ |
int *input = (int *)arg; |
*input = mxb->cur_input; |
DEB_EE(("VIDIOC_G_INPUT %d.\n",*input)); |
return 0; |
} |
case VIDIOC_S_INPUT: |
{ |
int input = *(int *)arg; |
struct tea6415c_multiplex vm; |
int i = 0; |
DEB_EE(("VIDIOC_S_INPUT %d.\n",input)); |
if (input < 0 || input >= MXB_INPUTS) { |
return -EINVAL; |
} |
/* fixme: locke das setzen des inputs mit hilfe des mutexes |
down(&dev->lock); |
video_mux(dev,*i); |
up(&dev->lock); |
*/ |
/* fixme: check if streaming capture |
if ( 0 != dev->streaming ) { |
DEB_D(("VIDIOC_S_INPUT illegal while streaming.\n")); |
return -EPERM; |
} |
*/ |
mxb->cur_input = input; |
saa7146_set_hps_source_and_sync(dev, input_port_selection[input].hps_source, input_port_selection[input].hps_sync); |
/* prepare switching of tea6415c and saa7111a; |
have a look at the 'background'-file for further informations */ |
switch( input ) { |
case TUNER: |
{ |
i = 0; |
vm.in = 3; |
vm.out = 17; |
if ( 0 != mxb->tea6415c->driver->command(mxb->tea6415c,TEA6415C_SWITCH, &vm)) { |
printk("VIDIOC_S_INPUT: could not address tea6415c #1\n"); |
return -EFAULT; |
} |
/* connect tuner-output always to multicable */ |
vm.in = 3; |
vm.out = 13; |
break; |
} |
case AUX3_YC: |
{ |
/* nothing to be done here. aux3_yc is |
directly connected to the saa711a */ |
i = 5; |
break; |
} |
case AUX3: |
{ |
/* nothing to be done here. aux3 is |
directly connected to the saa711a */ |
i = 1; |
break; |
} |
case AUX1: |
{ |
i = 0; |
vm.in = 1; |
vm.out = 17; |
break; |
} |
} |
/* switch video in tea6415c only if necessary */ |
switch( input ) { |
case TUNER: |
case AUX1: |
{ |
if ( 0 != mxb->tea6415c->driver->command(mxb->tea6415c,TEA6415C_SWITCH, &vm)) { |
printk("VIDIOC_S_INPUT: could not address tea6415c #3\n"); |
return -EFAULT; |
} |
break; |
} |
default: |
{ |
break; |
} |
} |
/* switch video in saa7111a */ |
if ( 0 != mxb->saa7111a->driver->command(mxb->saa7111a,DECODER_SET_INPUT, &i)) { |
printk("VIDIOC_S_INPUT: could not address saa7111a #1.\n"); |
} |
/* switch the audio-source only if necessary */ |
if( 0 == mxb->cur_mute ) { |
mxb->tea6420_1->driver->command(mxb->tea6420_1,TEA6420_SWITCH, &TEA6420_line[video_audio_connect[input]][0]); |
mxb->tea6420_2->driver->command(mxb->tea6420_2,TEA6420_SWITCH, &TEA6420_line[video_audio_connect[input]][1]); |
} |
return 0; |
} |
case VIDIOC_G_TUNER: |
{ |
struct v4l2_tuner *t = arg; |
int byte = 0; |
if( 0 != t->index ) { |
DEB_D(("VIDIOC_G_TUNER: channel %d does not have a tuner attached.\n", t->index)); |
return -EINVAL; |
} |
DEB_EE(("VIDIOC_G_TUNER: %d\n", t->index)); |
memset(t,0,sizeof(*t)); |
strcpy(t->name, "Television"); |
t->type = V4L2_TUNER_ANALOG_TV; |
t->capability = V4L2_TUNER_CAP_NORM | V4L2_TUNER_CAP_STEREO | V4L2_TUNER_CAP_LANG1 | V4L2_TUNER_CAP_LANG2 | V4L2_TUNER_CAP_SAP; |
t->rangelow = 772; /* 48.25 MHZ / 62.5 kHz = 772, see fi1216mk2-specs, page 2 */ |
t->rangehigh = 13684; /* 855.25 MHz / 62.5 kHz = 13684 */ |
/* FIXME: add the real signal strength here */ |
t->signal = 0xffff; |
t->afc = 0; |
byte = mxb->tda9840->driver->command(mxb->tda9840,TDA9840_DETECT, NULL); |
t->audmode = mxb->cur_mode; |
if( byte < 0 ) { |
t->rxsubchans = V4L2_TUNER_SUB_MONO; |
} else { |
switch(byte) { |
case TDA9840_MONO_DETECT: { |
t->rxsubchans = V4L2_TUNER_SUB_MONO; |
DEB_D(("VIDIOC_G_TUNER: V4L2_TUNER_MODE_MONO.\n")); |
break; |
} |
case TDA9840_DUAL_DETECT: { |
t->rxsubchans = V4L2_TUNER_SUB_LANG1 | V4L2_TUNER_SUB_LANG2; |
DEB_D(("VIDIOC_G_TUNER: V4L2_TUNER_MODE_LANG1.\n")); |
break; |
} |
case TDA9840_STEREO_DETECT: { |
t->rxsubchans = V4L2_TUNER_SUB_STEREO | V4L2_TUNER_SUB_MONO; |
DEB_D(("VIDIOC_G_TUNER: V4L2_TUNER_MODE_STEREO.\n")); |
break; |
} |
default: { /* TDA9840_INCORRECT_DETECT */ |
t->rxsubchans = V4L2_TUNER_MODE_MONO; |
DEB_D(("VIDIOC_G_TUNER: TDA9840_INCORRECT_DETECT => V4L2_TUNER_MODE_MONO\n")); |
break; |
} |
} |
} |
return 0; |
} |
case VIDIOC_S_TUNER: |
{ |
struct v4l2_tuner *t = arg; |
int result = 0; |
int byte = 0; |
if( 0 != t->index ) { |
DEB_D(("VIDIOC_S_TUNER: channel %d does not have a tuner attached.\n",t->index)); |
return -EINVAL; |
} |
switch(t->audmode) { |
case V4L2_TUNER_MODE_STEREO: { |
mxb->cur_mode = V4L2_TUNER_MODE_STEREO; |
byte = TDA9840_SET_STEREO; |
DEB_D(("VIDIOC_S_TUNER: V4L2_TUNER_MODE_STEREO\n")); |
break; |
} |
case V4L2_TUNER_MODE_LANG1: { |
mxb->cur_mode = V4L2_TUNER_MODE_LANG1; |
byte = TDA9840_SET_LANG1; |
DEB_D(("VIDIOC_S_TUNER: V4L2_TUNER_MODE_LANG1\n")); |
break; |
} |
case V4L2_TUNER_MODE_LANG2: { |
mxb->cur_mode = V4L2_TUNER_MODE_LANG2; |
byte = TDA9840_SET_LANG2; |
DEB_D(("VIDIOC_S_TUNER: V4L2_TUNER_MODE_LANG2\n")); |
break; |
} |
default: { /* case V4L2_TUNER_MODE_MONO: {*/ |
mxb->cur_mode = V4L2_TUNER_MODE_MONO; |
byte = TDA9840_SET_MONO; |
DEB_D(("VIDIOC_S_TUNER: TDA9840_SET_MONO\n")); |
break; |
} |
} |
if( 0 != (result = mxb->tda9840->driver->command(mxb->tda9840, TDA9840_SWITCH, &byte))) { |
printk("VIDIOC_S_TUNER error. result:%d, byte:%d\n",result,byte); |
} |
return 0; |
} |
case VIDIOC_G_FREQUENCY: |
{ |
struct v4l2_frequency *f = arg; |
if(0 != mxb->cur_input) { |
DEB_D(("VIDIOC_G_FREQ: channel %d does not have a tuner!\n",mxb->cur_input)); |
return -EINVAL; |
} |
memset(f,0,sizeof(*f)); |
f->type = V4L2_TUNER_ANALOG_TV; |
f->frequency = mxb->cur_freq; |
DEB_EE(("VIDIOC_G_FREQ: freq:0x%08x.\n", mxb->cur_freq)); |
return 0; |
} |
case VIDIOC_S_FREQUENCY: |
{ |
struct v4l2_frequency *f = arg; |
int t_locked = 0; |
int v_byte = 0; |
if (0 != f->tuner) |
return -EINVAL; |
if (V4L2_TUNER_ANALOG_TV != f->type) |
return -EINVAL; |
if(0 != mxb->cur_input) { |
DEB_D(("VIDIOC_S_FREQ: channel %d does not have a tuner!\n",mxb->cur_input)); |
return -EINVAL; |
} |
DEB_EE(("VIDIOC_S_FREQUENCY: freq:0x%08x.\n",f->frequency)); |
mxb->cur_freq = f->frequency; |
/* tune in desired frequency */ |
mxb->tuner->driver->command(mxb->tuner, VIDIOCSFREQ, &mxb->cur_freq); |
/* check if pll of tuner & saa7111a is locked */ |
// mxb->tuner->driver->command(mxb->tuner,TUNER_IS_LOCKED, &t_locked); |
mxb->saa7111a->driver->command(mxb->saa7111a,DECODER_GET_STATUS, &v_byte); |
/* not locked -- anything to do here ? */ |
if( 0 == t_locked || 0 == (v_byte & DECODER_STATUS_GOOD)) { |
} |
/* hack: changing the frequency should invalidate the vbi-counter (=> alevt) */ |
spin_lock(&dev->slock); |
vv->vbi_fieldcount = 0; |
spin_unlock(&dev->slock); |
return 0; |
} |
case MXB_S_AUDIO_CD: |
{ |
int i = *(int*)arg; |
if( i < 0 || i >= MXB_AUDIOS ) { |
DEB_D(("illegal argument to MXB_S_AUDIO_CD: i:%d.\n",i)); |
return -EINVAL; |
} |
DEB_EE(("MXB_S_AUDIO_CD: i:%d.\n",i)); |
mxb->tea6420_1->driver->command(mxb->tea6420_1,TEA6420_SWITCH, &TEA6420_cd[i][0]); |
mxb->tea6420_2->driver->command(mxb->tea6420_2,TEA6420_SWITCH, &TEA6420_cd[i][1]); |
return 0; |
} |
case MXB_S_AUDIO_LINE: |
{ |
int i = *(int*)arg; |
if( i < 0 || i >= MXB_AUDIOS ) { |
DEB_D(("illegal argument to MXB_S_AUDIO_LINE: i:%d.\n",i)); |
return -EINVAL; |
} |
DEB_EE(("MXB_S_AUDIO_LINE: i:%d.\n",i)); |
mxb->tea6420_1->driver->command(mxb->tea6420_1,TEA6420_SWITCH, &TEA6420_line[i][0]); |
mxb->tea6420_2->driver->command(mxb->tea6420_2,TEA6420_SWITCH, &TEA6420_line[i][1]); |
return 0; |
} |
case VIDIOC_G_AUDIO: |
{ |
struct v4l2_audio *a = arg; |
if( a->index < 0 || a->index > MXB_INPUTS ) { |
DEB_D(("VIDIOC_G_AUDIO %d out of range.\n",a->index)); |
return -EINVAL; |
} |
DEB_EE(("VIDIOC_G_AUDIO %d.\n",a->index)); |
memcpy(a, &mxb_audios[video_audio_connect[mxb->cur_input]], sizeof(struct v4l2_audio)); |
return 0; |
} |
case VIDIOC_S_AUDIO: |
{ |
struct v4l2_audio *a = arg; |
DEB_D(("VIDIOC_S_AUDIO %d.\n",a->index)); |
return 0; |
} |
default: |
/* |
DEB2(printk("does not handle this ioctl.\n")); |
*/ |
return -ENOIOCTLCMD; |
} |
return 0; |
} |
static int std_callback(struct saa7146_dev* dev, struct saa7146_standard *std) |
{ |
if(V4L2_STD_PAL_I == std->id ) { |
DEB_D(("VIDIOC_S_STD: setting mxb for PAL_I.\n")); |
/* set the 7146 gpio register -- I don't know what this does exactly */ |
saa7146_write(dev, GPIO_CTRL, 0x00404050); |
/* unset the 7111 gpio register -- I don't know what this does exactly */ |
saa7111_set_gpio(dev,0); |
} else { |
DEB_D(("VIDIOC_S_STD: setting mxb for PAL/NTSC/SECAM.\n")); |
/* set the 7146 gpio register -- I don't know what this does exactly */ |
saa7146_write(dev, GPIO_CTRL, 0x00404050); |
/* set the 7111 gpio register -- I don't know what this does exactly */ |
saa7111_set_gpio(dev,1); |
} |
return 0; |
} |
static struct saa7146_standard standard[] = { |
{ |
.name = "PAL-BG", .id = V4L2_STD_PAL_BG, |
.v_offset = 0x17, .v_field = 288, .v_calc = 576, |
.h_offset = 0x14, .h_pixels = 680, .h_calc = 680+1, |
.v_max_out = 576, .h_max_out = 768, |
}, { |
.name = "PAL-I", .id = V4L2_STD_PAL_I, |
.v_offset = 0x17, .v_field = 288, .v_calc = 576, |
.h_offset = 0x14, .h_pixels = 680, .h_calc = 680+1, |
.v_max_out = 576, .h_max_out = 768, |
}, { |
.name = "NTSC", .id = V4L2_STD_NTSC, |
.v_offset = 0x16, .v_field = 240, .v_calc = 480, |
.h_offset = 0x06, .h_pixels = 708, .h_calc = 708+1, |
.v_max_out = 480, .h_max_out = 640, |
}, { |
.name = "SECAM", .id = V4L2_STD_SECAM, |
.v_offset = 0x14, .v_field = 288, .v_calc = 576, |
.h_offset = 0x14, .h_pixels = 720, .h_calc = 720+1, |
.v_max_out = 576, .h_max_out = 768, |
} |
}; |
static struct saa7146_pci_extension_data mxb = { |
.ext_priv = "Multimedia eXtension Board", |
.ext = &extension, |
}; |
static struct pci_device_id pci_tbl[] = { |
{ |
.vendor = PCI_VENDOR_ID_PHILIPS, |
.device = PCI_DEVICE_ID_PHILIPS_SAA7146, |
.subvendor = 0x0000, |
.subdevice = 0x0000, |
.driver_data = (unsigned long)&mxb, |
}, { |
.vendor = 0, |
} |
}; |
MODULE_DEVICE_TABLE(pci, pci_tbl); |
static struct saa7146_ext_vv vv_data = { |
.inputs = MXB_INPUTS, |
.capabilities = V4L2_CAP_TUNER | V4L2_CAP_VBI_CAPTURE, |
.stds = &standard[0], |
.num_stds = sizeof(standard)/sizeof(struct saa7146_standard), |
.std_callback = &std_callback, |
.ioctls = &ioctls[0], |
.ioctl = mxb_ioctl, |
}; |
static struct saa7146_extension extension = { |
.name = MXB_IDENTIFIER, |
.flags = SAA7146_USE_I2C_IRQ, |
.pci_tbl = &pci_tbl[0], |
.module = THIS_MODULE, |
.probe = mxb_probe, |
.attach = mxb_attach, |
.detach = mxb_detach, |
.irq_mask = 0, |
.irq_func = NULL, |
}; |
int __init mxb_init_module(void) |
{ |
if( 0 != saa7146_register_extension(&extension)) { |
DEB_S(("failed to register extension.\n")); |
return -ENODEV; |
} |
return 0; |
} |
void __exit mxb_cleanup_module(void) |
{ |
saa7146_unregister_extension(&extension); |
} |
module_init(mxb_init_module); |
module_exit(mxb_cleanup_module); |
MODULE_DESCRIPTION("video4linux-2 driver for the Siemens-Nixdorf 'Multimedia eXtension board'"); |
MODULE_AUTHOR("Michael Hunold <michael@mihu.de>"); |
MODULE_LICENSE("GPL"); |
/shark/trunk/drivers/cm7326/saa7146_vbi.c |
---|
0,0 → 1,503 |
#include <media/saa7146_vv.h> |
static int vbi_pixel_to_capture = 720 * 2; |
static int vbi_workaround(struct saa7146_dev *dev) |
{ |
struct saa7146_vv *vv = dev->vv_data; |
u32 *cpu; |
dma_addr_t dma_addr; |
int count = 0; |
int i; |
DECLARE_WAITQUEUE(wait, current); |
DEB_VBI(("dev:%p\n",dev)); |
/* once again, a bug in the saa7146: the brs acquisition |
is buggy and especially the BXO-counter does not work |
as specified. there is this workaround, but please |
don't let me explain it. ;-) */ |
cpu = pci_alloc_consistent(dev->pci, 4096, &dma_addr); |
if (NULL == cpu) |
return -ENOMEM; |
/* setup some basic programming, just for the workaround */ |
saa7146_write(dev, BASE_EVEN3, dma_addr); |
saa7146_write(dev, BASE_ODD3, dma_addr+vbi_pixel_to_capture); |
saa7146_write(dev, PROT_ADDR3, dma_addr+4096); |
saa7146_write(dev, PITCH3, vbi_pixel_to_capture); |
saa7146_write(dev, BASE_PAGE3, 0x0); |
saa7146_write(dev, NUM_LINE_BYTE3, (2<<16)|((vbi_pixel_to_capture)<<0)); |
saa7146_write(dev, MC2, MASK_04|MASK_20); |
/* load brs-control register */ |
WRITE_RPS1(CMD_WR_REG | (1 << 8) | (BRS_CTRL/4)); |
/* BXO = 1h, BRS to outbound */ |
WRITE_RPS1(0xc000008c); |
/* wait for vbi_a or vbi_b*/ |
if ( 0 != (SAA7146_USE_PORT_B_FOR_VBI & dev->ext_vv_data->flags)) { |
DEB_D(("...using port b\n")); |
WRITE_RPS1(CMD_PAUSE | CMD_OAN | CMD_SIG1 | CMD_E_FID_B); |
WRITE_RPS1(CMD_PAUSE | CMD_OAN | CMD_SIG1 | CMD_O_FID_B); |
/* |
WRITE_RPS1(CMD_PAUSE | MASK_09); |
*/ |
} else { |
DEB_D(("...using port a\n")); |
WRITE_RPS1(CMD_PAUSE | MASK_10); |
} |
/* upload brs */ |
WRITE_RPS1(CMD_UPLOAD | MASK_08); |
/* load brs-control register */ |
WRITE_RPS1(CMD_WR_REG | (1 << 8) | (BRS_CTRL/4)); |
/* BYO = 1, BXO = NQBIL (=1728 for PAL, for NTSC this is 858*2) - NumByte3 (=1440) = 288 */ |
WRITE_RPS1(((1728-(vbi_pixel_to_capture)) << 7) | MASK_19); |
/* wait for brs_done */ |
WRITE_RPS1(CMD_PAUSE | MASK_08); |
/* upload brs */ |
WRITE_RPS1(CMD_UPLOAD | MASK_08); |
/* load video-dma3 NumLines3 and NumBytes3 */ |
WRITE_RPS1(CMD_WR_REG | (1 << 8) | (NUM_LINE_BYTE3/4)); |
/* dev->vbi_count*2 lines, 720 pixel (= 1440 Bytes) */ |
WRITE_RPS1((2 << 16) | (vbi_pixel_to_capture)); |
/* load brs-control register */ |
WRITE_RPS1(CMD_WR_REG | (1 << 8) | (BRS_CTRL/4)); |
/* Set BRS right: note: this is an experimental value for BXO (=> PAL!) */ |
WRITE_RPS1((540 << 7) | (5 << 19)); // 5 == vbi_start |
/* wait for brs_done */ |
WRITE_RPS1(CMD_PAUSE | MASK_08); |
/* upload brs and video-dma3*/ |
WRITE_RPS1(CMD_UPLOAD | MASK_08 | MASK_04); |
/* load mc2 register: enable dma3 */ |
WRITE_RPS1(CMD_WR_REG | (1 << 8) | (MC1/4)); |
WRITE_RPS1(MASK_20 | MASK_04); |
/* generate interrupt */ |
WRITE_RPS1(CMD_INTERRUPT); |
/* stop rps1 */ |
WRITE_RPS1(CMD_STOP); |
/* we have to do the workaround twice to be sure that |
everything is ok */ |
for(i = 0; i < 2; i++) { |
/* indicate to the irq handler that we do the workaround */ |
saa7146_write(dev, MC2, MASK_31|MASK_15); |
saa7146_write(dev, NUM_LINE_BYTE3, (1<<16)|(2<<0)); |
saa7146_write(dev, MC2, MASK_04|MASK_20); |
/* enable rps1 irqs */ |
IER_ENABLE(dev,MASK_28); |
/* prepare to wait to be woken up by the irq-handler */ |
add_wait_queue(&vv->vbi_wq, &wait); |
current->state = TASK_INTERRUPTIBLE; |
/* start rps1 to enable workaround */ |
saa7146_write(dev, RPS_ADDR1, dev->d_rps1.dma_handle); |
saa7146_write(dev, MC1, (MASK_13 | MASK_29)); |
schedule(); |
DEB_VBI(("brs bug workaround %d/1.\n",i)); |
remove_wait_queue(&vv->vbi_wq, &wait); |
current->state = TASK_RUNNING; |
/* disable rps1 irqs */ |
IER_DISABLE(dev,MASK_28); |
/* stop video-dma3 */ |
saa7146_write(dev, MC1, MASK_20); |
if(signal_pending(current)) { |
DEB_VBI(("aborted (rps:0x%08x).\n",saa7146_read(dev,RPS_ADDR1))); |
/* stop rps1 for sure */ |
saa7146_write(dev, MC1, MASK_29); |
pci_free_consistent(dev->pci, 4096, cpu, dma_addr); |
return -EINTR; |
} |
} |
pci_free_consistent(dev->pci, 4096, cpu, dma_addr); |
return 0; |
} |
void saa7146_set_vbi_capture(struct saa7146_dev *dev, struct saa7146_buf *buf, struct saa7146_buf *next) |
{ |
struct saa7146_vv *vv = dev->vv_data; |
struct saa7146_video_dma vdma3; |
int count = 0; |
unsigned long e_wait = vv->current_hps_sync == SAA7146_HPS_SYNC_PORT_A ? CMD_E_FID_A : CMD_E_FID_B; |
unsigned long o_wait = vv->current_hps_sync == SAA7146_HPS_SYNC_PORT_A ? CMD_O_FID_A : CMD_O_FID_B; |
/* |
vdma3.base_even = 0xc8000000+2560*70; |
vdma3.base_odd = 0xc8000000; |
vdma3.prot_addr = 0xc8000000+2560*164; |
vdma3.pitch = 2560; |
vdma3.base_page = 0; |
vdma3.num_line_byte = (64<<16)|((vbi_pixel_to_capture)<<0); // set above! |
*/ |
vdma3.base_even = buf->pt[2].offset; |
vdma3.base_odd = buf->pt[2].offset + 16 * vbi_pixel_to_capture; |
vdma3.prot_addr = buf->pt[2].offset + 16 * 2 * vbi_pixel_to_capture; |
vdma3.pitch = vbi_pixel_to_capture; |
vdma3.base_page = buf->pt[2].dma | ME1; |
vdma3.num_line_byte = (16 << 16) | vbi_pixel_to_capture; |
saa7146_write_out_dma(dev, 3, &vdma3); |
/* write beginning of rps-program */ |
count = 0; |
/* wait for o_fid_a/b / e_fid_a/b toggle only if bit 1 is not set */ |
/* we don't wait here for the first field anymore. this is different from the video |
capture and might cause that the first buffer is only half filled (with only |
one field). but since this is some sort of streaming data, this is not that negative. |
but by doing this, we can use the whole engine from video-buf.c... */ |
/* |
WRITE_RPS1(CMD_PAUSE | CMD_OAN | CMD_SIG1 | e_wait); |
WRITE_RPS1(CMD_PAUSE | CMD_OAN | CMD_SIG1 | o_wait); |
*/ |
/* set bit 1 */ |
WRITE_RPS1(CMD_WR_REG | (1 << 8) | (MC2/4)); |
WRITE_RPS1(MASK_28 | MASK_12); |
/* turn on video-dma3 */ |
WRITE_RPS1(CMD_WR_REG_MASK | (MC1/4)); |
WRITE_RPS1(MASK_04 | MASK_20); /* => mask */ |
WRITE_RPS1(MASK_04 | MASK_20); /* => values */ |
/* wait for o_fid_a/b / e_fid_a/b toggle */ |
WRITE_RPS1(CMD_PAUSE | o_wait); |
WRITE_RPS1(CMD_PAUSE | e_wait); |
/* generate interrupt */ |
WRITE_RPS1(CMD_INTERRUPT); |
/* stop */ |
WRITE_RPS1(CMD_STOP); |
/* enable rps1 irqs */ |
IER_ENABLE(dev, MASK_28); |
/* write the address of the rps-program */ |
saa7146_write(dev, RPS_ADDR1, dev->d_rps1.dma_handle); |
/* turn on rps */ |
saa7146_write(dev, MC1, (MASK_13 | MASK_29)); |
} |
static int buffer_activate(struct saa7146_dev *dev, |
struct saa7146_buf *buf, |
struct saa7146_buf *next) |
{ |
struct saa7146_vv *vv = dev->vv_data; |
buf->vb.state = 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, NULL); |
if (err) |
goto oops; |
err = saa7146_pgtable_build_single(dev->pci, &buf->pt[2], buf->vb.dma.sglist, buf->vb.dma.sglen); |
if (0 != err) |
return err; |
} |
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 file *file) |
{ |
struct saa7146_dev *dev = fh->dev; |
struct saa7146_vv *vv = dev->vv_data; |
unsigned long flags; |
DEB_VBI(("dev:%p, fh:%p\n",dev, fh)); |
spin_lock_irqsave(&dev->slock,flags); |
/* disable rps1 */ |
saa7146_write(dev, MC1, MASK_29); |
/* disable rps1 irqs */ |
IER_DISABLE(dev, MASK_28); |
/* shut down dma 3 transfers */ |
saa7146_write(dev, MC1, MASK_20); |
if (vv->vbi_q.curr) { |
saa7146_buffer_finish(dev,&vv->vbi_q,STATE_DONE); |
} |
videobuf_queue_cancel(file,&fh->vbi_q); |
vv->vbi_streaming = NULL; |
del_timer(&vv->vbi_q.timeout); |
del_timer(&fh->vbi_read_timeout); |
spin_unlock_irqrestore(&dev->slock, flags); |
} |
static void vbi_read_timeout(unsigned long data) |
{ |
struct file *file = (struct file*)data; |
struct saa7146_fh *fh = file->private_data; |
struct saa7146_dev *dev = fh->dev; |
DEB_VBI(("dev:%p, fh:%p\n",dev, fh)); |
vbi_stop(fh, file); |
} |
static void vbi_init(struct saa7146_dev *dev, struct saa7146_vv *vv) |
{ |
DEB_VBI(("dev:%p\n",dev)); |
INIT_LIST_HEAD(&vv->vbi_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 int vbi_open(struct saa7146_dev *dev, struct file *file) |
{ |
struct saa7146_fh *fh = (struct saa7146_fh *)file->private_data; |
u32 arbtr_ctrl = saa7146_read(dev, PCI_BT_V1); |
int ret = 0; |
DEB_VBI(("dev:%p, fh:%p\n",dev,fh)); |
ret = saa7146_res_get(fh, RESOURCE_DMA3_BRS); |
if (0 == ret) { |
DEB_S(("cannot get vbi RESOURCE_DMA3_BRS resource\n")); |
return -EBUSY; |
} |
/* adjust arbitrition control for video dma 3 */ |
arbtr_ctrl &= ~0x1f0000; |
arbtr_ctrl |= 0x1d0000; |
saa7146_write(dev, PCI_BT_V1, arbtr_ctrl); |
saa7146_write(dev, MC2, (MASK_04|MASK_20)); |
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)file; |
/* initialize the brs */ |
if ( 0 != (SAA7146_USE_PORT_B_FOR_VBI & dev->ext_vv_data->flags)) { |
saa7146_write(dev, BRS_CTRL, MASK_30|MASK_29 | (7 << 19)); |
} else { |
saa7146_write(dev, BRS_CTRL, 0x00000001); |
if (0 != (ret = vbi_workaround(dev))) { |
DEB_VBI(("vbi workaround failed!\n")); |
/* return ret;*/ |
} |
} |
/* upload brs register */ |
saa7146_write(dev, MC2, (MASK_08|MASK_24)); |
return 0; |
} |
static void vbi_close(struct saa7146_dev *dev, struct file *file) |
{ |
struct saa7146_fh *fh = (struct saa7146_fh *)file->private_data; |
struct saa7146_vv *vv = dev->vv_data; |
DEB_VBI(("dev:%p, fh:%p\n",dev,fh)); |
if( fh == vv->vbi_streaming ) { |
vbi_stop(fh, file); |
} |
saa7146_res_free(fh, RESOURCE_DMA3_BRS); |
} |
static void vbi_irq_done(struct saa7146_dev *dev, unsigned long status) |
{ |
struct saa7146_vv *vv = dev->vv_data; |
spin_lock(&dev->slock); |
if (vv->vbi_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, |
}; |
/shark/trunk/drivers/cm7326/hexium_gemini.c |
---|
0,0 → 1,550 |
/* |
hexium_gemini.c - v4l2 driver for Hexium Gemini frame grabber cards |
Visit http://www.mihu.de/linux/saa7146/ and follow the link |
to "hexium" for further details about this card. |
Copyright (C) 2003 Michael Hunold <michael@mihu.de> |
This program is free software; you can redistribute it and/or modify |
it under the terms of the GNU General Public License as published by |
the Free Software Foundation; either version 2 of the License, or |
(at your option) any later version. |
This program is distributed in the hope that it will be useful, |
but WITHOUT ANY WARRANTY; without even the implied warranty of |
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
GNU General Public License for more details. |
You should have received a copy of the GNU General Public License |
along with this program; if not, write to the Free Software |
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. |
*/ |
#define DEBUG_VARIABLE debug |
#include <media/saa7146_vv.h> |
static int debug = 0; |
MODULE_PARM(debug, "i"); |
MODULE_PARM_DESC(debug, "debug verbosity"); |
/* global variables */ |
static int hexium_num = 0; |
#define HEXIUM_GEMUINI 4 |
#define HEXIUM_GEMUINI_DUAL 5 |
#define HEXIUM_INPUTS 9 |
static struct v4l2_input hexium_inputs[HEXIUM_INPUTS] = { |
{ 0, "CVBS 1", V4L2_INPUT_TYPE_CAMERA, 2, 0, V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, 0 }, |
{ 1, "CVBS 2", V4L2_INPUT_TYPE_CAMERA, 2, 0, V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, 0 }, |
{ 2, "CVBS 3", V4L2_INPUT_TYPE_CAMERA, 2, 0, V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, 0 }, |
{ 3, "CVBS 4", V4L2_INPUT_TYPE_CAMERA, 2, 0, V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, 0 }, |
{ 4, "CVBS 5", V4L2_INPUT_TYPE_CAMERA, 2, 0, V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, 0 }, |
{ 5, "CVBS 6", V4L2_INPUT_TYPE_CAMERA, 2, 0, V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, 0 }, |
{ 6, "Y/C 1", V4L2_INPUT_TYPE_CAMERA, 2, 0, V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, 0 }, |
{ 7, "Y/C 2", V4L2_INPUT_TYPE_CAMERA, 2, 0, V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, 0 }, |
{ 8, "Y/C 3", V4L2_INPUT_TYPE_CAMERA, 2, 0, V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, 0 }, |
}; |
#define HEXIUM_AUDIOS 0 |
struct hexium_data |
{ |
s8 adr; |
u8 byte; |
}; |
static struct saa7146_extension_ioctls ioctls[] = { |
{ VIDIOC_G_INPUT, SAA7146_EXCLUSIVE }, |
{ VIDIOC_S_INPUT, SAA7146_EXCLUSIVE }, |
{ VIDIOC_QUERYCTRL, SAA7146_BEFORE }, |
{ VIDIOC_ENUMINPUT, SAA7146_EXCLUSIVE }, |
{ VIDIOC_S_STD, SAA7146_AFTER }, |
{ VIDIOC_G_CTRL, SAA7146_BEFORE }, |
{ VIDIOC_S_CTRL, SAA7146_BEFORE }, |
{ 0, 0 } |
}; |
#define HEXIUM_CONTROLS 1 |
static struct v4l2_queryctrl hexium_controls[] = { |
{ V4L2_CID_PRIVATE_BASE, V4L2_CTRL_TYPE_BOOLEAN, "B/W", 0, 1, 1, 0, 0 }, |
}; |
#define HEXIUM_GEMUINI_V_1_0 1 |
#define HEXIUM_GEMUINI_DUAL_V_1_0 2 |
struct hexium |
{ |
int type; |
struct video_device video_dev; |
struct i2c_adapter i2c_adapter; |
int cur_input; /* current input */ |
v4l2_std_id cur_std; /* current standard */ |
int cur_bw; /* current black/white status */ |
}; |
/* Samsung KS0127B decoder default registers */ |
static u8 hexium_ks0127b[0x100]={ |
/*00*/ 0x00,0x52,0x30,0x40,0x01,0x0C,0x2A,0x10, |
/*08*/ 0x00,0x00,0x00,0x60,0x00,0x00,0x0F,0x06, |
/*10*/ 0x00,0x00,0xE4,0xC0,0x00,0x00,0x00,0x00, |
/*18*/ 0x14,0x9B,0xFE,0xFF,0xFC,0xFF,0x03,0x22, |
/*20*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, |
/*28*/ 0x00,0x00,0x00,0x00,0x00,0x2C,0x9B,0x00, |
/*30*/ 0x00,0x00,0x10,0x80,0x80,0x10,0x80,0x80, |
/*38*/ 0x01,0x04,0x00,0x00,0x00,0x29,0xC0,0x00, |
/*40*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, |
/*48*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, |
/*50*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, |
/*58*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, |
/*60*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, |
/*68*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, |
/*70*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, |
/*78*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, |
/*80*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, |
/*88*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, |
/*90*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, |
/*98*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, |
/*A0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, |
/*A8*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, |
/*B0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, |
/*B8*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, |
/*C0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, |
/*C8*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, |
/*D0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, |
/*D8*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, |
/*E0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, |
/*E8*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, |
/*F0*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, |
/*F8*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 |
}; |
static struct hexium_data hexium_pal[] = { |
{ 0x01, 0x52 }, { 0x12, 0x64 }, { 0x2D, 0x2C }, { 0x2E, 0x9B }, { -1 , 0xFF } |
}; |
static struct hexium_data hexium_pal_bw[] = { |
{ 0x01, 0x52 }, { 0x12, 0x64 }, { 0x2D, 0x2C }, { 0x2E, 0x9B }, { -1 , 0xFF } |
}; |
static struct hexium_data hexium_ntsc[] = { |
{ 0x01, 0x53 }, { 0x12, 0x04 }, { 0x2D, 0x23 }, { 0x2E, 0x81 }, { -1 , 0xFF } |
}; |
static struct hexium_data hexium_ntsc_bw[] = { |
{ 0x01, 0x53 }, { 0x12, 0x04 }, { 0x2D, 0x23 }, { 0x2E, 0x81 }, { -1 , 0xFF } |
}; |
static struct hexium_data hexium_secam[] = { |
{ 0x01, 0x52 }, { 0x12, 0x64 }, { 0x2D, 0x2C }, { 0x2E, 0x9B }, { -1 , 0xFF } |
}; |
static struct hexium_data hexium_input_select[] = { |
{ 0x02, 0x60 }, |
{ 0x02, 0x64 }, |
{ 0x02, 0x61 }, |
{ 0x02, 0x65 }, |
{ 0x02, 0x62 }, |
{ 0x02, 0x66 }, |
{ 0x02, 0x68 }, |
{ 0x02, 0x69 }, |
{ 0x02, 0x6A }, |
}; |
/* fixme: h_offset = 0 for Hexium Gemini Dual */ |
static struct saa7146_standard hexium_standards[] = { |
{ |
.name = "PAL", .id = V4L2_STD_PAL, |
.v_offset = 28, .v_field = 288, .v_calc = 576, |
.h_offset = 1, .h_pixels = 680, .h_calc = 680+1, |
.v_max_out = 576, .h_max_out = 768, |
}, { |
.name = "NTSC", .id = V4L2_STD_NTSC, |
.v_offset = 28, .v_field = 240, .v_calc = 480, |
.h_offset = 1, .h_pixels = 640, .h_calc = 641+1, |
.v_max_out = 480, .h_max_out = 640, |
}, { |
.name = "SECAM", .id = V4L2_STD_SECAM, |
.v_offset = 28, .v_field = 288, .v_calc = 576, |
.h_offset = 1, .h_pixels = 720, .h_calc = 720+1, |
.v_max_out = 576, .h_max_out = 768, |
} |
}; |
/* bring hardware to a sane state. this has to be done, just in case someone |
wants to capture from this device before it has been properly initialized. |
the capture engine would badly fail, because no valid signal arrives on the |
saa7146, thus leading to timeouts and stuff. */ |
static int hexium_init_done(struct saa7146_dev *dev) |
{ |
struct hexium *hexium = (struct hexium *) dev->ext_priv; |
union i2c_smbus_data data; |
int i = 0; |
DEB_D(("hexium_init_done called.\n")); |
/* initialize the helper ics to useful values */ |
for (i = 0; i < sizeof(hexium_ks0127b); i++) { |
data.byte = hexium_ks0127b[i]; |
if (0 != i2c_smbus_xfer(&hexium->i2c_adapter, 0x6c, 0, I2C_SMBUS_WRITE, i, I2C_SMBUS_BYTE_DATA, &data)) { |
printk("hexium_gemini: hexium_init_done() failed for address 0x%02x\n", i); |
} |
} |
return 0; |
} |
static int hexium_set_input(struct hexium *hexium, int input) |
{ |
union i2c_smbus_data data; |
DEB_D((".\n")); |
data.byte = hexium_input_select[input].byte; |
if (0 != i2c_smbus_xfer(&hexium->i2c_adapter, 0x6c, 0, I2C_SMBUS_WRITE, hexium_input_select[input].adr, I2C_SMBUS_BYTE_DATA, &data)) { |
return -1; |
} |
return 0; |
} |
static int hexium_set_standard(struct hexium *hexium, struct hexium_data *vdec) |
{ |
union i2c_smbus_data data; |
int i = 0; |
DEB_D((".\n")); |
while (vdec[i].adr != -1) { |
data.byte = vdec[i].byte; |
if (0 != i2c_smbus_xfer(&hexium->i2c_adapter, 0x6c, 0, I2C_SMBUS_WRITE, vdec[i].adr, I2C_SMBUS_BYTE_DATA, &data)) { |
printk("hexium_init_done: hexium_set_standard() failed for address 0x%02x\n", i); |
return -1; |
} |
i++; |
} |
return 0; |
} |
static struct saa7146_ext_vv vv_data; |
/* this function only gets called when the probing was successful */ |
static int hexium_attach(struct saa7146_dev *dev, struct saa7146_pci_extension_data *info) |
{ |
struct hexium *hexium = (struct hexium *) dev->ext_priv; |
DEB_EE((".\n")); |
hexium = (struct hexium *) kmalloc(sizeof(struct hexium), GFP_KERNEL); |
if (NULL == hexium) { |
printk("hexium_gemini: not enough kernel memory in hexium_attach().\n"); |
return -ENOMEM; |
} |
memset(hexium, 0x0, sizeof(struct hexium)); |
(struct hexium *) dev->ext_priv = hexium; |
/* enable i2c-port pins */ |
saa7146_write(dev, MC1, (MASK_08 | MASK_24 | MASK_10 | MASK_26)); |
saa7146_i2c_adapter_prepare(dev, &hexium->i2c_adapter, SAA7146_I2C_BUS_BIT_RATE_480); |
if (i2c_add_adapter(&hexium->i2c_adapter) < 0) { |
DEB_S(("cannot register i2c-device. skipping.\n")); |
kfree(hexium); |
return -EFAULT; |
} |
/* set HWControl GPIO number 2 */ |
saa7146_setgpio(dev, 2, SAA7146_GPIO_OUTHI); |
saa7146_write(dev, DD1_INIT, 0x07000700); |
saa7146_write(dev, DD1_STREAM_B, 0x00000000); |
saa7146_write(dev, MC2, (MASK_09 | MASK_25 | MASK_10 | MASK_26)); |
/* the rest */ |
hexium->cur_input = 0; |
hexium_init_done(dev); |
hexium_set_standard(hexium, hexium_pal); |
hexium->cur_std = V4L2_STD_PAL; |
hexium_set_input(hexium, 0); |
hexium->cur_input = 0; |
saa7146_vv_init(dev, &vv_data); |
if (0 != saa7146_register_device(&hexium->video_dev, dev, "hexium gemini", VFL_TYPE_GRABBER)) { |
printk("hexium_gemini: cannot register capture v4l2 device. skipping.\n"); |
return -1; |
} |
printk("hexium_gemini: found 'hexium gemini' frame grabber-%d.\n", hexium_num); |
hexium_num++; |
return 0; |
} |
static int hexium_detach(struct saa7146_dev *dev) |
{ |
struct hexium *hexium = (struct hexium *) dev->ext_priv; |
DEB_EE(("dev:%p\n", dev)); |
saa7146_unregister_device(&hexium->video_dev, dev); |
saa7146_vv_release(dev); |
hexium_num--; |
i2c_del_adapter(&hexium->i2c_adapter); |
kfree(hexium); |
return 0; |
} |
static int hexium_ioctl(struct saa7146_fh *fh, unsigned int cmd, void *arg) |
{ |
struct saa7146_dev *dev = fh->dev; |
struct hexium *hexium = (struct hexium *) dev->ext_priv; |
/* |
struct saa7146_vv *vv = dev->vv_data; |
*/ |
switch (cmd) { |
case VIDIOC_ENUMINPUT: |
{ |
struct v4l2_input *i = arg; |
DEB_EE(("VIDIOC_ENUMINPUT %d.\n", i->index)); |
if (i->index < 0 || i->index >= HEXIUM_INPUTS) { |
return -EINVAL; |
} |
memcpy(i, &hexium_inputs[i->index], sizeof(struct v4l2_input)); |
DEB_D(("v4l2_ioctl: VIDIOC_ENUMINPUT %d.\n", i->index)); |
return 0; |
} |
case VIDIOC_G_INPUT: |
{ |
int *input = (int *) arg; |
*input = hexium->cur_input; |
DEB_D(("VIDIOC_G_INPUT: %d\n", *input)); |
return 0; |
} |
case VIDIOC_S_INPUT: |
{ |
int input = *(int *) arg; |
DEB_EE(("VIDIOC_S_INPUT %d.\n", input)); |
if (input < 0 || input >= HEXIUM_INPUTS) { |
return -EINVAL; |
} |
hexium->cur_input = input; |
hexium_set_input(hexium, input); |
return 0; |
} |
/* the saa7146 provides some controls (brightness, contrast, saturation) |
which gets registered *after* this function. because of this we have |
to return with a value != 0 even if the function succeded.. */ |
case VIDIOC_QUERYCTRL: |
{ |
struct v4l2_queryctrl *qc = arg; |
int i; |
for (i = HEXIUM_CONTROLS - 1; i >= 0; i--) { |
if (hexium_controls[i].id == qc->id) { |
*qc = hexium_controls[i]; |
DEB_D(("VIDIOC_QUERYCTRL %d.\n", qc->id)); |
return 0; |
} |
} |
return -EAGAIN; |
} |
case VIDIOC_G_CTRL: |
{ |
struct v4l2_control *vc = arg; |
int i; |
for (i = HEXIUM_CONTROLS - 1; i >= 0; i--) { |
if (hexium_controls[i].id == vc->id) { |
break; |
} |
} |
if (i < 0) { |
return -EAGAIN; |
} |
switch (vc->id) { |
case V4L2_CID_PRIVATE_BASE:{ |
vc->value = hexium->cur_bw; |
DEB_D(("VIDIOC_G_CTRL BW:%d.\n", vc->value)); |
return 0; |
} |
} |
return -EINVAL; |
} |
case VIDIOC_S_CTRL: |
{ |
struct v4l2_control *vc = arg; |
int i = 0; |
for (i = HEXIUM_CONTROLS - 1; i >= 0; i--) { |
if (hexium_controls[i].id == vc->id) { |
break; |
} |
} |
if (i < 0) { |
return -EAGAIN; |
} |
switch (vc->id) { |
case V4L2_CID_PRIVATE_BASE:{ |
hexium->cur_bw = vc->value; |
break; |
} |
} |
DEB_D(("VIDIOC_S_CTRL BW:%d.\n", hexium->cur_bw)); |
if (0 == hexium->cur_bw && V4L2_STD_PAL == hexium->cur_std) { |
hexium_set_standard(hexium, hexium_pal); |
return 0; |
} |
if (0 == hexium->cur_bw && V4L2_STD_NTSC == hexium->cur_std) { |
hexium_set_standard(hexium, hexium_ntsc); |
return 0; |
} |
if (0 == hexium->cur_bw && V4L2_STD_SECAM == hexium->cur_std) { |
hexium_set_standard(hexium, hexium_secam); |
return 0; |
} |
if (1 == hexium->cur_bw && V4L2_STD_PAL == hexium->cur_std) { |
hexium_set_standard(hexium, hexium_pal_bw); |
return 0; |
} |
if (1 == hexium->cur_bw && V4L2_STD_NTSC == hexium->cur_std) { |
hexium_set_standard(hexium, hexium_ntsc_bw); |
return 0; |
} |
if (1 == hexium->cur_bw && V4L2_STD_SECAM == hexium->cur_std) { |
/* fixme: is there no bw secam mode? */ |
return -EINVAL; |
} |
return -EINVAL; |
} |
default: |
/* |
DEB_D(("hexium_ioctl() does not handle this ioctl.\n")); |
*/ |
return -ENOIOCTLCMD; |
} |
return 0; |
} |
static int std_callback(struct saa7146_dev *dev, struct saa7146_standard *std) |
{ |
struct hexium *hexium = (struct hexium *) dev->ext_priv; |
if (V4L2_STD_PAL == std->id) { |
hexium_set_standard(hexium, hexium_pal); |
hexium->cur_std = V4L2_STD_PAL; |
return 0; |
} else if (V4L2_STD_NTSC == std->id) { |
hexium_set_standard(hexium, hexium_ntsc); |
hexium->cur_std = V4L2_STD_NTSC; |
return 0; |
} else if (V4L2_STD_SECAM == std->id) { |
hexium_set_standard(hexium, hexium_secam); |
hexium->cur_std = V4L2_STD_SECAM; |
return 0; |
} |
return -1; |
} |
static struct saa7146_extension hexium_extension; |
static struct saa7146_pci_extension_data hexium_gemini_4bnc = { |
.ext_priv = "Hexium Gemini (4 BNC)", |
.ext = &hexium_extension, |
}; |
static struct saa7146_pci_extension_data hexium_gemini_dual_4bnc = { |
.ext_priv = "Hexium Gemini Dual (4 BNC)", |
.ext = &hexium_extension, |
}; |
static struct pci_device_id pci_tbl[] = { |
{ |
.vendor = PCI_VENDOR_ID_PHILIPS, |
.device = PCI_DEVICE_ID_PHILIPS_SAA7146, |
.subvendor = 0x17c8, |
.subdevice = 0x2401, |
.driver_data = (unsigned long) &hexium_gemini_4bnc, |
}, |
{ |
.vendor = PCI_VENDOR_ID_PHILIPS, |
.device = PCI_DEVICE_ID_PHILIPS_SAA7146, |
.subvendor = 0x17c8, |
.subdevice = 0x2402, |
.driver_data = (unsigned long) &hexium_gemini_dual_4bnc, |
}, |
{ |
.vendor = 0, |
} |
}; |
MODULE_DEVICE_TABLE(pci, pci_tbl); |
static struct saa7146_ext_vv vv_data = { |
.inputs = HEXIUM_INPUTS, |
.capabilities = 0, |
.stds = &hexium_standards[0], |
.num_stds = sizeof(hexium_standards) / sizeof(struct saa7146_standard), |
.std_callback = &std_callback, |
.ioctls = &ioctls[0], |
.ioctl = hexium_ioctl, |
}; |
static struct saa7146_extension hexium_extension = { |
.name = "hexium gemini", |
.flags = SAA7146_USE_I2C_IRQ, |
.pci_tbl = &pci_tbl[0], |
.module = THIS_MODULE, |
.attach = hexium_attach, |
.detach = hexium_detach, |
.irq_mask = 0, |
.irq_func = NULL, |
}; |
static int __init hexium_init_module(void) |
{ |
if (0 != saa7146_register_extension(&hexium_extension)) { |
DEB_S(("failed to register extension.\n")); |
return -ENODEV; |
} |
return 0; |
} |
static void __exit hexium_cleanup_module(void) |
{ |
saa7146_unregister_extension(&hexium_extension); |
} |
module_init(hexium_init_module); |
module_exit(hexium_cleanup_module); |
MODULE_DESCRIPTION("video4linux-2 driver for Hexium Gemini frame grabber cards"); |
MODULE_AUTHOR("Michael Hunold <michael@mihu.de>"); |
MODULE_LICENSE("GPL"); |
/shark/trunk/drivers/cm7326/saa7146_vv_ksyms.c |
---|
0,0 → 1,15 |
#include <linux/module.h> |
#include <media/saa7146_vv.h> |
EXPORT_SYMBOL_GPL(saa7146_vbi_uops); |
EXPORT_SYMBOL_GPL(saa7146_video_uops); |
EXPORT_SYMBOL_GPL(saa7146_start_preview); |
EXPORT_SYMBOL_GPL(saa7146_stop_preview); |
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); |
/shark/trunk/drivers/cm7326/dpc7146.c |
---|
0,0 → 1,394 |
/* |
dpc7146.c - v4l2 driver for the dpc7146 demonstration board |
Copyright (C) 2000-2003 Michael Hunold <michael@mihu.de> |
This program is free software; you can redistribute it and/or modify |
it under the terms of the GNU General Public License as published by |
the Free Software Foundation; either version 2 of the License, or |
(at your option) any later version. |
This program is distributed in the hope that it will be useful, |
but WITHOUT ANY WARRANTY; without even the implied warranty of |
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
GNU General Public License for more details. |
You should have received a copy of the GNU General Public License |
along with this program; if not, write to the Free Software |
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. |
*/ |
#define DEBUG_VARIABLE debug |
#include <media/saa7146_vv.h> |
#include <linux/video_decoder.h> /* for saa7111a */ |
#define I2C_SAA7111A 0x24 |
/* All unused bytes are reserverd. */ |
#define SAA711X_CHIP_VERSION 0x00 |
#define SAA711X_ANALOG_INPUT_CONTROL_1 0x02 |
#define SAA711X_ANALOG_INPUT_CONTROL_2 0x03 |
#define SAA711X_ANALOG_INPUT_CONTROL_3 0x04 |
#define SAA711X_ANALOG_INPUT_CONTROL_4 0x05 |
#define SAA711X_HORIZONTAL_SYNC_START 0x06 |
#define SAA711X_HORIZONTAL_SYNC_STOP 0x07 |
#define SAA711X_SYNC_CONTROL 0x08 |
#define SAA711X_LUMINANCE_CONTROL 0x09 |
#define SAA711X_LUMINANCE_BRIGHTNESS 0x0A |
#define SAA711X_LUMINANCE_CONTRAST 0x0B |
#define SAA711X_CHROMA_SATURATION 0x0C |
#define SAA711X_CHROMA_HUE_CONTROL 0x0D |
#define SAA711X_CHROMA_CONTROL 0x0E |
#define SAA711X_FORMAT_DELAY_CONTROL 0x10 |
#define SAA711X_OUTPUT_CONTROL_1 0x11 |
#define SAA711X_OUTPUT_CONTROL_2 0x12 |
#define SAA711X_OUTPUT_CONTROL_3 0x13 |
#define SAA711X_V_GATE_1_START 0x15 |
#define SAA711X_V_GATE_1_STOP 0x16 |
#define SAA711X_V_GATE_1_MSB 0x17 |
#define SAA711X_TEXT_SLICER_STATUS 0x1A |
#define SAA711X_DECODED_BYTES_OF_TS_1 0x1B |
#define SAA711X_DECODED_BYTES_OF_TS_2 0x1C |
#define SAA711X_STATUS_BYTE 0x1F |
#define DPC_BOARD_CAN_DO_VBI(dev) (dev->revision != 0) |
static int debug = 0; |
MODULE_PARM(debug,"i"); |
MODULE_PARM_DESC(debug, "debug verbosity"); |
/* global variables */ |
int dpc_num = 0; |
#define DPC_INPUTS 2 |
static struct v4l2_input dpc_inputs[DPC_INPUTS] = { |
{ 0, "Port A", V4L2_INPUT_TYPE_CAMERA, 2, 0, V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, 0 }, |
{ 1, "Port B", V4L2_INPUT_TYPE_CAMERA, 2, 0, V4L2_STD_PAL_BG|V4L2_STD_NTSC_M, 0 }, |
}; |
#define DPC_AUDIOS 0 |
static struct saa7146_extension_ioctls ioctls[] = { |
{ VIDIOC_G_INPUT, SAA7146_EXCLUSIVE }, |
{ VIDIOC_S_INPUT, SAA7146_EXCLUSIVE }, |
{ VIDIOC_ENUMINPUT, SAA7146_EXCLUSIVE }, |
{ VIDIOC_S_STD, SAA7146_AFTER }, |
{ 0, 0 } |
}; |
struct dpc |
{ |
struct video_device video_dev; |
struct video_device vbi_dev; |
struct i2c_adapter i2c_adapter; |
struct i2c_client *saa7111a; |
int cur_input; /* current input */ |
}; |
/* fixme: add vbi stuff here */ |
static int dpc_probe(struct saa7146_dev* dev) |
{ |
struct dpc* dpc = 0; |
int i = 0; |
dpc = (struct dpc*)kmalloc(sizeof(struct dpc), GFP_KERNEL); |
if( NULL == dpc ) { |
printk("dpc_v4l2.o: dpc_probe: not enough kernel memory.\n"); |
return -ENOMEM; |
} |
memset(dpc, 0x0, sizeof(struct dpc)); |
saa7146_i2c_adapter_prepare(dev, &dpc->i2c_adapter, SAA7146_I2C_BUS_BIT_RATE_480); |
if(i2c_add_adapter(&dpc->i2c_adapter) < 0) { |
DEB_S(("cannot register i2c-device. skipping.\n")); |
kfree(dpc); |
return -EFAULT; |
} |
/* loop through all i2c-devices on the bus and look who is there */ |
for(i = 0; i < I2C_CLIENT_MAX; i++) { |
if( NULL == dpc->i2c_adapter.clients[i] ) { |
continue; |
} |
if( I2C_SAA7111A == dpc->i2c_adapter.clients[i]->addr ) |
dpc->saa7111a = dpc->i2c_adapter.clients[i]; |
} |
/* check if all devices are present */ |
if( 0 == dpc->saa7111a ) { |
DEB_D(("dpc_v4l2.o: dpc_attach failed for this device.\n")); |
kfree(dpc); |
return -ENODEV; |
} |
/* all devices are present, probe was successful */ |
DEB_D(("dpc_v4l2.o: dpc_probe succeeded for this device.\n")); |
/* we store the pointer in our private data field */ |
(struct dpc*)dev->ext_priv = dpc; |
return 0; |
} |
/* bring hardware to a sane state. this has to be done, just in case someone |
wants to capture from this device before it has been properly initialized. |
the capture engine would badly fail, because no valid signal arrives on the |
saa7146, thus leading to timeouts and stuff. */ |
static int dpc_init_done(struct saa7146_dev* dev) |
{ |
struct dpc* dpc = (struct dpc*)dev->ext_priv; |
DEB_D(("dpc_v4l2.o: dpc_init_done called.\n")); |
/* initialize the helper ics to useful values */ |
i2c_smbus_write_byte_data(dpc->saa7111a, 0x00, 0x11); |
i2c_smbus_write_byte_data(dpc->saa7111a, 0x02, 0xc0); |
i2c_smbus_write_byte_data(dpc->saa7111a, 0x03, 0x30); |
i2c_smbus_write_byte_data(dpc->saa7111a, 0x04, 0x00); |
i2c_smbus_write_byte_data(dpc->saa7111a, 0x05, 0x00); |
i2c_smbus_write_byte_data(dpc->saa7111a, 0x06, 0xde); |
i2c_smbus_write_byte_data(dpc->saa7111a, 0x07, 0xad); |
i2c_smbus_write_byte_data(dpc->saa7111a, 0x08, 0xa8); |
i2c_smbus_write_byte_data(dpc->saa7111a, 0x09, 0x00); |
i2c_smbus_write_byte_data(dpc->saa7111a, 0x0a, 0x80); |
i2c_smbus_write_byte_data(dpc->saa7111a, 0x0b, 0x47); |
i2c_smbus_write_byte_data(dpc->saa7111a, 0x0c, 0x40); |
i2c_smbus_write_byte_data(dpc->saa7111a, 0x0d, 0x00); |
i2c_smbus_write_byte_data(dpc->saa7111a, 0x0e, 0x03); |
i2c_smbus_write_byte_data(dpc->saa7111a, 0x10, 0xd0); |
i2c_smbus_write_byte_data(dpc->saa7111a, 0x11, 0x1c); |
i2c_smbus_write_byte_data(dpc->saa7111a, 0x12, 0xc1); |
i2c_smbus_write_byte_data(dpc->saa7111a, 0x13, 0x30); |
i2c_smbus_write_byte_data(dpc->saa7111a, 0x1f, 0x81); |
return 0; |
} |
static struct saa7146_ext_vv vv_data; |
/* this function only gets called when the probing was successful */ |
static int dpc_attach(struct saa7146_dev* dev, struct saa7146_pci_extension_data *info) |
{ |
struct dpc* dpc = (struct dpc*)dev->ext_priv; |
DEB_D(("dpc_v4l2.o: dpc_attach called.\n")); |
/* checking for i2c-devices can be omitted here, because we |
already did this in "dpc_vl42_probe" */ |
saa7146_vv_init(dev,&vv_data); |
if( 0 != saa7146_register_device(&dpc->video_dev, dev, "dpc", VFL_TYPE_GRABBER)) { |
ERR(("cannot register capture v4l2 device. skipping.\n")); |
return -1; |
} |
/* initialization stuff (vbi) (only for revision > 0 and for extensions which want it)*/ |
if( 0 != DPC_BOARD_CAN_DO_VBI(dev)) { |
if( 0 != saa7146_register_device(&dpc->vbi_dev, dev, "dpc", VFL_TYPE_VBI)) { |
ERR(("cannot register vbi v4l2 device. skipping.\n")); |
} |
} |
i2c_use_client(dpc->saa7111a); |
printk("dpc: found 'dpc7146 demonstration board'-%d.\n",dpc_num); |
dpc_num++; |
/* the rest */ |
dpc->cur_input = 0; |
dpc_init_done(dev); |
return 0; |
} |
static int dpc_detach(struct saa7146_dev* dev) |
{ |
struct dpc* dpc = (struct dpc*)dev->ext_priv; |
DEB_EE(("dev:%p\n",dev)); |
i2c_release_client(dpc->saa7111a); |
saa7146_unregister_device(&dpc->video_dev,dev); |
if( 0 != DPC_BOARD_CAN_DO_VBI(dev)) { |
saa7146_unregister_device(&dpc->vbi_dev,dev); |
} |
saa7146_vv_release(dev); |
dpc_num--; |
i2c_del_adapter(&dpc->i2c_adapter); |
kfree(dpc); |
return 0; |
} |
#ifdef axa |
int dpc_vbi_bypass(struct saa7146_dev* dev) |
{ |
struct dpc* dpc = (struct dpc*)dev->ext_priv; |
int i = 1; |
/* switch bypass in saa7111a */ |
if ( 0 != dpc->saa7111a->driver->command(dpc->saa7111a,SAA711X_VBI_BYPASS, &i)) { |
printk("dpc_v4l2.o: VBI_BYPASS: could not address saa7111a.\n"); |
return -1; |
} |
return 0; |
} |
#endif |
static int dpc_ioctl(struct saa7146_fh *fh, unsigned int cmd, void *arg) |
{ |
struct saa7146_dev *dev = fh->dev; |
struct dpc* dpc = (struct dpc*)dev->ext_priv; |
/* |
struct saa7146_vv *vv = dev->vv_data; |
*/ |
switch(cmd) |
{ |
case VIDIOC_ENUMINPUT: |
{ |
struct v4l2_input *i = arg; |
DEB_EE(("VIDIOC_ENUMINPUT %d.\n",i->index)); |
if( i->index < 0 || i->index >= DPC_INPUTS) { |
return -EINVAL; |
} |
memcpy(i, &dpc_inputs[i->index], sizeof(struct v4l2_input)); |
DEB_D(("dpc_v4l2.o: v4l2_ioctl: VIDIOC_ENUMINPUT %d.\n",i->index)); |
return 0; |
} |
case VIDIOC_G_INPUT: |
{ |
int *input = (int *)arg; |
*input = dpc->cur_input; |
DEB_D(("dpc_v4l2.o: VIDIOC_G_INPUT: %d\n",*input)); |
return 0; |
} |
case VIDIOC_S_INPUT: |
{ |
int input = *(int *)arg; |
if (input < 0 || input >= DPC_INPUTS) { |
return -EINVAL; |
} |
dpc->cur_input = input; |
/* fixme: switch input here, switch audio, too! */ |
// saa7146_set_hps_source_and_sync(dev, input_port_selection[input].hps_source, input_port_selection[input].hps_sync); |
printk("dpc_v4l2.o: VIDIOC_S_INPUT: fixme switch input.\n"); |
return 0; |
} |
default: |
/* |
DEB_D(("dpc_v4l2.o: v4l2_ioctl does not handle this ioctl.\n")); |
*/ |
return -ENOIOCTLCMD; |
} |
return 0; |
} |
static int std_callback(struct saa7146_dev* dev, struct saa7146_standard *std) |
{ |
return 0; |
} |
static struct saa7146_standard standard[] = { |
{ |
.name = "PAL", .id = V4L2_STD_PAL, |
.v_offset = 0x17, .v_field = 288, .v_calc = 576, |
.h_offset = 0x14, .h_pixels = 680, .h_calc = 680+1, |
.v_max_out = 576, .h_max_out = 768, |
}, { |
.name = "NTSC", .id = V4L2_STD_NTSC, |
.v_offset = 0x16, .v_field = 240, .v_calc = 480, |
.h_offset = 0x06, .h_pixels = 708, .h_calc = 708+1, |
.v_max_out = 480, .h_max_out = 640, |
}, { |
.name = "SECAM", .id = V4L2_STD_SECAM, |
.v_offset = 0x14, .v_field = 288, .v_calc = 576, |
.h_offset = 0x14, .h_pixels = 720, .h_calc = 720+1, |
.v_max_out = 576, .h_max_out = 768, |
} |
}; |
static struct saa7146_extension extension; |
static struct saa7146_pci_extension_data dpc = { |
.ext_priv = "Multimedia eXtension Board", |
.ext = &extension, |
}; |
static struct pci_device_id pci_tbl[] = { |
{ |
.vendor = PCI_VENDOR_ID_PHILIPS, |
.device = PCI_DEVICE_ID_PHILIPS_SAA7146, |
.subvendor = 0x0000, |
.subdevice = 0x0000, |
.driver_data = (unsigned long)&dpc, |
}, { |
.vendor = 0, |
} |
}; |
MODULE_DEVICE_TABLE(pci, pci_tbl); |
static struct saa7146_ext_vv vv_data = { |
.inputs = DPC_INPUTS, |
.capabilities = V4L2_CAP_VBI_CAPTURE, |
.stds = &standard[0], |
.num_stds = sizeof(standard)/sizeof(struct saa7146_standard), |
.std_callback = &std_callback, |
.ioctls = &ioctls[0], |
.ioctl = dpc_ioctl, |
}; |
static struct saa7146_extension extension = { |
.name = "dpc7146 demonstration board", |
.flags = SAA7146_USE_I2C_IRQ, |
.pci_tbl = &pci_tbl[0], |
.module = THIS_MODULE, |
.probe = dpc_probe, |
.attach = dpc_attach, |
.detach = dpc_detach, |
.irq_mask = 0, |
.irq_func = NULL, |
}; |
int __init dpc_init_module(void) |
{ |
if( 0 != saa7146_register_extension(&extension)) { |
DEB_S(("failed to register extension.\n")); |
return -ENODEV; |
} |
return 0; |
} |
void __exit dpc_cleanup_module(void) |
{ |
saa7146_unregister_extension(&extension); |
} |
module_init(dpc_init_module); |
module_exit(dpc_cleanup_module); |
MODULE_DESCRIPTION("video4linux-2 driver for the 'dpc7146 demonstration board'"); |
MODULE_AUTHOR("Michael Hunold <michael@mihu.de>"); |
MODULE_LICENSE("GPL"); |
/shark/trunk/drivers/cm7326/saa7146_hlp.c |
---|
0,0 → 1,1057 |
#include <media/saa7146_vv.h> |
#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_ALTERNATE |
|| field == V4L2_FIELD_BOTTOM) { |
if( 4*out_y >= in_y ) { |
lpi = 1; |
} |
out_y *= 2; |
} |
if( 0 != lpi ) { |
yacm = 0; |
yacl = 0; |
cya_cyb = 0x00ff; |
/* calculate scaling increment */ |
if ( in_y > out_y ) |
ysci = ((1024 * in_y) / (out_y + 1)) - 1024; |
else |
ysci = 0; |
dcgy = 0; |
/* calculate ype and ypo */ |
ype = ysci / 16; |
ypo = ype + (ysci / 64); |
} else { |
yacm = 1; |
/* calculate scaling increment */ |
ysci = (((10 * 1024 * (in_y - out_y - 1)) / in_y) + 9) / 10; |
/* calculate ype and ypo */ |
ypo = ype = ((ysci + 15) / 16); |
/* the sequence length interval (yacl) has to be set according |
to the prescale value, e.g. [n .. 1/2) : 0 |
[1/2 .. 1/3) : 1 |
[1/3 .. 1/4) : 2 |
... */ |
if ( ysci < 512) { |
yacl = 0; |
} else { |
yacl = ( ysci / (1024 - ysci) ); |
} |
/* get filter coefficients for cya, cyb from table hps_v_coeff_tab */ |
cya_cyb = hps_v_coeff_tab[ (yacl < 63 ? yacl : 63 ) ].hps_coeff; |
/* get best match in the table of attenuations for vertical scaling */ |
v_atten = hps_v_coeff_tab[ (yacl < 63 ? yacl : 63 ) ].weight_sum; |
for (i = 0; v_attenuation[i] != 0; i++) { |
if (v_attenuation[i] >= v_atten) |
break; |
} |
dcgy = i; |
} |
/* ypo and ype swapped in spec ? */ |
*hps_v_scale |= (yacm << 31) | (ysci << 21) | (yacl << 15) | (ypo << 8 ) | (ype << 1); |
*hps_v_gain &= ~(MASK_W0|MASK_B2); |
*hps_v_gain |= (dcgy << 16) | (cya_cyb << 0); |
return 0; |
} |
/* simple bubble-sort algorithm with duplicate elimination */ |
static int sort_and_eliminate(u32* values, int* count) |
{ |
int low = 0, high = 0, top = 0, temp = 0; |
int cur = 0, next = 0; |
/* sanity checks */ |
if( (0 > *count) || (NULL == values) ) { |
return -EINVAL; |
} |
/* bubble sort the first ´count´ items of the array ´values´ */ |
for( top = *count; top > 0; top--) { |
for( low = 0, high = 1; high < top; low++, high++) { |
if( values[low] > values[high] ) { |
temp = values[low]; |
values[low] = values[high]; |
values[high] = temp; |
} |
} |
} |
/* remove duplicate items */ |
for( cur = 0, next = 1; next < *count; next++) { |
if( values[cur] != values[next]) |
values[++cur] = values[next]; |
} |
*count = cur + 1; |
return 0; |
} |
static void calculate_clipping_registers_rect(struct saa7146_dev *dev, struct saa7146_fh *fh, |
struct saa7146_video_dma *vdma2, u32* clip_format, u32* arbtr_ctrl, enum v4l2_field field) |
{ |
struct saa7146_vv *vv = dev->vv_data; |
u32 *clipping = vv->d_clipping.cpu_addr; |
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 = vv->d_clipping.dma_handle; |
vdma2->base_odd = vv->d_clipping.dma_handle; |
vdma2->prot_addr = vv->d_clipping.dma_handle+((sizeof(u32))*(numdwords)); |
vdma2->base_page = 0x04; |
vdma2->pitch = 0x00; |
vdma2->num_line_byte = (0 << 16 | (sizeof(u32))*(numdwords-1) ); |
/* set clipping-mode. this depends on the field(s) used */ |
*clip_format &= 0xfffffff7; |
if (V4L2_FIELD_HAS_BOTH(field)) { |
*clip_format |= 0x00000008; |
} else { |
*clip_format |= 0x00000000; |
} |
} |
/* disable clipping */ |
static void saa7146_disable_clipping(struct saa7146_dev *dev) |
{ |
u32 clip_format = saa7146_read(dev, CLIP_FORMAT_CTRL); |
/* mask out relevant bits (=lower word)*/ |
clip_format &= MASK_W1; |
/* upload clipping-registers*/ |
saa7146_write(dev, CLIP_FORMAT_CTRL,clip_format); |
saa7146_write(dev, MC2, (MASK_05 | MASK_21)); |
/* disable video dma2 */ |
saa7146_write(dev, MC1, MASK_21); |
} |
static void saa7146_set_clipping_rect(struct saa7146_fh *fh) |
{ |
struct saa7146_dev *dev = fh->dev; |
enum v4l2_field field = fh->ov.win.field; |
struct saa7146_video_dma vdma2; |
u32 clip_format; |
u32 arbtr_ctrl; |
/* check clipcount, disable clipping if clipcount == 0*/ |
if( fh->ov.nclips == 0 ) { |
saa7146_disable_clipping(dev); |
return; |
} |
clip_format = saa7146_read(dev, CLIP_FORMAT_CTRL); |
arbtr_ctrl = saa7146_read(dev, PCI_BT_V1); |
calculate_clipping_registers_rect(dev, fh, &vdma2, &clip_format, &arbtr_ctrl, field); |
/* set clipping format */ |
clip_format &= 0xffff0008; |
clip_format |= (SAA7146_CLIPPING_RECT << 4); |
/* prepare video dma2 */ |
saa7146_write(dev, BASE_EVEN2, vdma2.base_even); |
saa7146_write(dev, BASE_ODD2, vdma2.base_odd); |
saa7146_write(dev, PROT_ADDR2, vdma2.prot_addr); |
saa7146_write(dev, BASE_PAGE2, vdma2.base_page); |
saa7146_write(dev, PITCH2, vdma2.pitch); |
saa7146_write(dev, NUM_LINE_BYTE2, vdma2.num_line_byte); |
/* prepare the rest */ |
saa7146_write(dev, CLIP_FORMAT_CTRL,clip_format); |
saa7146_write(dev, PCI_BT_V1, arbtr_ctrl); |
/* upload clip_control-register, clipping-registers, enable video dma2 */ |
saa7146_write(dev, MC2, (MASK_05 | MASK_21 | MASK_03 | MASK_19)); |
saa7146_write(dev, MC1, (MASK_05 | MASK_21)); |
} |
static void saa7146_set_window(struct saa7146_dev *dev, int width, int height, enum v4l2_field field) |
{ |
struct saa7146_vv *vv = dev->vv_data; |
int source = vv->current_hps_source; |
int sync = vv->current_hps_sync; |
u32 hps_v_scale = 0, hps_v_gain = 0, hps_ctrl = 0, hps_h_prescale = 0, hps_h_scale = 0; |
/* set vertical scale */ |
hps_v_scale = 0; /* all bits get set by the function-call */ |
hps_v_gain = 0; /* fixme: saa7146_read(dev, HPS_V_GAIN);*/ |
calculate_v_scale_registers(dev, field, vv->standard->v_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_ALTERNATE) { |
/* fixme */ |
vdma1.base_odd = vdma1.prot_addr; |
vdma1.pitch /= 2; |
} else if (field == V4L2_FIELD_TOP) { |
vdma1.base_odd = vdma1.prot_addr; |
vdma1.pitch /= 2; |
} else if (field == V4L2_FIELD_BOTTOM) { |
vdma1.base_odd = vdma1.base_even; |
vdma1.base_even = vdma1.prot_addr; |
vdma1.pitch /= 2; |
} |
if ( 0 != vv->vflip ) { |
vdma1.pitch *= -1; |
} |
vdma1.base_page = 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; |
} |
int saa7146_enable_overlay(struct saa7146_fh *fh) |
{ |
struct saa7146_dev *dev = fh->dev; |
struct saa7146_vv *vv = dev->vv_data; |
saa7146_set_window(dev, 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(fh); |
/* enable video dma1 */ |
saa7146_write(dev, MC1, (MASK_06 | MASK_22)); |
return 0; |
} |
void saa7146_disable_overlay(struct saa7146_fh *fh) |
{ |
struct saa7146_dev *dev = fh->dev; |
/* disable clipping + video dma1 */ |
saa7146_disable_clipping(dev); |
saa7146_write(dev, MC1, MASK_22); |
} |
void saa7146_write_out_dma(struct saa7146_dev* dev, int which, struct saa7146_video_dma* vdma) |
{ |
int where = 0; |
if( which < 1 || which > 3) { |
return; |
} |
/* calculate starting address */ |
where = (which-1)*0x18; |
saa7146_write(dev, where, vdma->base_odd); |
saa7146_write(dev, where+0x04, vdma->base_even); |
saa7146_write(dev, where+0x08, vdma->prot_addr); |
saa7146_write(dev, where+0x0c, vdma->pitch); |
saa7146_write(dev, where+0x10, vdma->base_page); |
saa7146_write(dev, where+0x14, vdma->num_line_byte); |
/* upload */ |
saa7146_write(dev, MC2, (MASK_02<<(which-1))|(MASK_18<<(which-1))); |
/* |
printk("vdma%d.base_even: 0x%08x\n", which,vdma->base_even); |
printk("vdma%d.base_odd: 0x%08x\n", which,vdma->base_odd); |
printk("vdma%d.prot_addr: 0x%08x\n", which,vdma->prot_addr); |
printk("vdma%d.base_page: 0x%08x\n", which,vdma->base_page); |
printk("vdma%d.pitch: 0x%08x\n", which,vdma->pitch); |
printk("vdma%d.num_line_byte: 0x%08x\n", which,vdma->num_line_byte); |
*/ |
} |
static int calculate_video_dma_grab_packed(struct saa7146_dev* dev, struct saa7146_buf *buf) |
{ |
struct saa7146_vv *vv = dev->vv_data; |
struct saa7146_video_dma vdma1; |
struct saa7146_format *sfmt = format_by_fourcc(dev,buf->fmt->pixelformat); |
int width = buf->fmt->width; |
int height = buf->fmt->height; |
int bytesperline = buf->fmt->bytesperline; |
enum v4l2_field field = buf->fmt->field; |
int depth = sfmt->depth; |
DEB_CAP(("[size=%dx%d,fields=%s]\n", |
width,height,v4l2_field_names[field])); |
if( bytesperline != 0) { |
vdma1.pitch = bytesperline*2; |
} else { |
vdma1.pitch = (width*depth*2)/8; |
} |
vdma1.num_line_byte = ((vv->standard->v_field<<16) + vv->standard->h_pixels); |
vdma1.base_page = buf->pt[0].dma | ME1; |
if( 0 != vv->vflip ) { |
vdma1.prot_addr = buf->pt[0].offset; |
vdma1.base_even = buf->pt[0].offset+(vdma1.pitch/2)*height; |
vdma1.base_odd = vdma1.base_even - (vdma1.pitch/2); |
} else { |
vdma1.base_even = buf->pt[0].offset; |
vdma1.base_odd = vdma1.base_even + (vdma1.pitch/2); |
vdma1.prot_addr = buf->pt[0].offset+(vdma1.pitch/2)*height; |
} |
if (V4L2_FIELD_HAS_BOTH(field)) { |
} else if (field == V4L2_FIELD_ALTERNATE) { |
/* fixme */ |
if ( vv->last_field == V4L2_FIELD_TOP ) { |
vdma1.base_odd = vdma1.prot_addr; |
vdma1.pitch /= 2; |
} else if ( vv->last_field == V4L2_FIELD_BOTTOM ) { |
vdma1.base_odd = vdma1.base_even; |
vdma1.base_even = vdma1.prot_addr; |
vdma1.pitch /= 2; |
} |
} else if (field == V4L2_FIELD_TOP) { |
vdma1.base_odd = vdma1.prot_addr; |
vdma1.pitch /= 2; |
} else if (field == V4L2_FIELD_BOTTOM) { |
vdma1.base_odd = vdma1.base_even; |
vdma1.base_even = vdma1.prot_addr; |
vdma1.pitch /= 2; |
} |
if( 0 != vv->vflip ) { |
vdma1.pitch *= -1; |
} |
saa7146_write_out_dma(dev, 1, &vdma1); |
return 0; |
} |
static int calc_planar_422(struct saa7146_vv *vv, struct saa7146_buf *buf, struct saa7146_video_dma *vdma2, struct saa7146_video_dma *vdma3) |
{ |
int height = buf->fmt->height; |
int width = buf->fmt->width; |
vdma2->pitch = width; |
vdma3->pitch = width; |
/* fixme: look at bytesperline! */ |
if( 0 != vv->vflip ) { |
vdma2->prot_addr = buf->pt[1].offset; |
vdma2->base_even = ((vdma2->pitch/2)*height)+buf->pt[1].offset; |
vdma2->base_odd = vdma2->base_even - (vdma2->pitch/2); |
vdma3->prot_addr = buf->pt[2].offset; |
vdma3->base_even = ((vdma3->pitch/2)*height)+buf->pt[2].offset; |
vdma3->base_odd = vdma3->base_even - (vdma3->pitch/2); |
} else { |
vdma3->base_even = buf->pt[2].offset; |
vdma3->base_odd = vdma3->base_even + (vdma3->pitch/2); |
vdma3->prot_addr = (vdma3->pitch/2)*height+buf->pt[2].offset; |
vdma2->base_even = buf->pt[1].offset; |
vdma2->base_odd = vdma2->base_even + (vdma2->pitch/2); |
vdma2->prot_addr = (vdma2->pitch/2)*height+buf->pt[1].offset; |
} |
return 0; |
} |
static int calc_planar_420(struct saa7146_vv *vv, struct saa7146_buf *buf, struct saa7146_video_dma *vdma2, struct saa7146_video_dma *vdma3) |
{ |
int height = buf->fmt->height; |
int width = buf->fmt->width; |
vdma2->pitch = width/2; |
vdma3->pitch = width/2; |
if( 0 != vv->vflip ) { |
vdma2->prot_addr = buf->pt[2].offset; |
vdma2->base_even = ((vdma2->pitch/2)*height)+buf->pt[2].offset; |
vdma2->base_odd = vdma2->base_even - (vdma2->pitch/2); |
vdma3->prot_addr = buf->pt[1].offset; |
vdma3->base_even = ((vdma3->pitch/2)*height)+buf->pt[1].offset; |
vdma3->base_odd = vdma3->base_even - (vdma3->pitch/2); |
} else { |
vdma3->base_even = buf->pt[2].offset; |
vdma3->base_odd = vdma3->base_even + (vdma3->pitch); |
vdma3->prot_addr = (vdma3->pitch/2)*height+buf->pt[2].offset; |
vdma2->base_even = buf->pt[1].offset; |
vdma2->base_odd = vdma2->base_even + (vdma2->pitch); |
vdma2->prot_addr = (vdma2->pitch/2)*height+buf->pt[1].offset; |
} |
return 0; |
} |
static int calculate_video_dma_grab_planar(struct saa7146_dev* dev, struct saa7146_buf *buf) |
{ |
struct saa7146_vv *vv = dev->vv_data; |
struct saa7146_video_dma vdma1; |
struct saa7146_video_dma vdma2; |
struct saa7146_video_dma vdma3; |
struct saa7146_format *sfmt = format_by_fourcc(dev,buf->fmt->pixelformat); |
int width = buf->fmt->width; |
int height = buf->fmt->height; |
enum v4l2_field field = buf->fmt->field; |
BUG_ON(0 == buf->pt[0].dma); |
BUG_ON(0 == buf->pt[1].dma); |
BUG_ON(0 == buf->pt[2].dma); |
DEB_CAP(("[size=%dx%d,fields=%s]\n", |
width,height,v4l2_field_names[field])); |
/* fixme: look at bytesperline! */ |
/* fixme: what happens for user space buffers here?. The offsets are |
most likely wrong, this version here only works for page-aligned |
buffers, modifications to the pagetable-functions are necessary...*/ |
vdma1.pitch = width*2; |
vdma1.num_line_byte = ((vv->standard->v_field<<16) + vv->standard->h_pixels); |
vdma1.base_page = buf->pt[0].dma | ME1; |
if( 0 != vv->vflip ) { |
vdma1.prot_addr = buf->pt[0].offset; |
vdma1.base_even = ((vdma1.pitch/2)*height)+buf->pt[0].offset; |
vdma1.base_odd = vdma1.base_even - (vdma1.pitch/2); |
} else { |
vdma1.base_even = buf->pt[0].offset; |
vdma1.base_odd = vdma1.base_even + (vdma1.pitch/2); |
vdma1.prot_addr = (vdma1.pitch/2)*height+buf->pt[0].offset; |
} |
vdma2.num_line_byte = 0; /* unused */ |
vdma2.base_page = buf->pt[1].dma | ME1; |
vdma3.num_line_byte = 0; /* unused */ |
vdma3.base_page = buf->pt[2].dma | ME1; |
switch( sfmt->depth ) { |
case 12: { |
calc_planar_420(vv,buf,&vdma2,&vdma3); |
break; |
} |
case 16: { |
calc_planar_422(vv,buf,&vdma2,&vdma3); |
break; |
} |
default: { |
return -1; |
} |
} |
if (V4L2_FIELD_HAS_BOTH(field)) { |
} else if (field == V4L2_FIELD_ALTERNATE) { |
/* fixme */ |
vdma1.base_odd = vdma1.prot_addr; |
vdma1.pitch /= 2; |
vdma2.base_odd = vdma2.prot_addr; |
vdma2.pitch /= 2; |
vdma3.base_odd = vdma3.prot_addr; |
vdma3.pitch /= 2; |
} else if (field == V4L2_FIELD_TOP) { |
vdma1.base_odd = vdma1.prot_addr; |
vdma1.pitch /= 2; |
vdma2.base_odd = vdma2.prot_addr; |
vdma2.pitch /= 2; |
vdma3.base_odd = vdma3.prot_addr; |
vdma3.pitch /= 2; |
} else if (field == V4L2_FIELD_BOTTOM) { |
vdma1.base_odd = vdma1.base_even; |
vdma1.base_even = vdma1.prot_addr; |
vdma1.pitch /= 2; |
vdma2.base_odd = vdma2.base_even; |
vdma2.base_even = vdma2.prot_addr; |
vdma2.pitch /= 2; |
vdma3.base_odd = vdma3.base_even; |
vdma3.base_even = vdma3.prot_addr; |
vdma3.pitch /= 2; |
} |
if( 0 != vv->vflip ) { |
vdma1.pitch *= -1; |
vdma2.pitch *= -1; |
vdma3.pitch *= -1; |
} |
saa7146_write_out_dma(dev, 1, &vdma1); |
if( (sfmt->flags & FORMAT_BYTE_SWAP) != 0 ) { |
saa7146_write_out_dma(dev, 3, &vdma2); |
saa7146_write_out_dma(dev, 2, &vdma3); |
} else { |
saa7146_write_out_dma(dev, 2, &vdma2); |
saa7146_write_out_dma(dev, 3, &vdma3); |
} |
return 0; |
} |
static void program_capture_engine(struct saa7146_dev *dev, int planar) |
{ |
struct saa7146_vv *vv = dev->vv_data; |
int count = 0; |
unsigned long e_wait = vv->current_hps_sync == SAA7146_HPS_SYNC_PORT_A ? CMD_E_FID_A : CMD_E_FID_B; |
unsigned long o_wait = vv->current_hps_sync == SAA7146_HPS_SYNC_PORT_A ? CMD_O_FID_A : CMD_O_FID_B; |
/* wait for o_fid_a/b / e_fid_a/b toggle only if rps register 0 is not set*/ |
WRITE_RPS0(CMD_PAUSE | CMD_OAN | CMD_SIG0 | o_wait); |
WRITE_RPS0(CMD_PAUSE | CMD_OAN | CMD_SIG0 | e_wait); |
/* set rps register 0 */ |
WRITE_RPS0(CMD_WR_REG | (1 << 8) | (MC2/4)); |
WRITE_RPS0(MASK_27 | MASK_11); |
/* turn on video-dma1 */ |
WRITE_RPS0(CMD_WR_REG_MASK | (MC1/4)); |
WRITE_RPS0(MASK_06 | MASK_22); /* => mask */ |
WRITE_RPS0(MASK_06 | MASK_22); /* => values */ |
if( 0 != planar ) { |
/* turn on video-dma2 */ |
WRITE_RPS0(CMD_WR_REG_MASK | (MC1/4)); |
WRITE_RPS0(MASK_05 | MASK_21); /* => mask */ |
WRITE_RPS0(MASK_05 | MASK_21); /* => values */ |
/* turn on video-dma3 */ |
WRITE_RPS0(CMD_WR_REG_MASK | (MC1/4)); |
WRITE_RPS0(MASK_04 | MASK_20); /* => mask */ |
WRITE_RPS0(MASK_04 | MASK_20); /* => values */ |
} |
/* wait for o_fid_a/b / e_fid_a/b toggle */ |
if ( vv->last_field == V4L2_FIELD_INTERLACED ) { |
WRITE_RPS0(CMD_PAUSE | o_wait); |
WRITE_RPS0(CMD_PAUSE | e_wait); |
} else if ( vv->last_field == V4L2_FIELD_TOP ) { |
WRITE_RPS0(CMD_PAUSE | (vv->current_hps_sync == SAA7146_HPS_SYNC_PORT_A ? MASK_10 : MASK_09)); |
WRITE_RPS0(CMD_PAUSE | o_wait); |
} else if ( vv->last_field == V4L2_FIELD_BOTTOM ) { |
WRITE_RPS0(CMD_PAUSE | (vv->current_hps_sync == SAA7146_HPS_SYNC_PORT_A ? MASK_10 : MASK_09)); |
WRITE_RPS0(CMD_PAUSE | e_wait); |
} |
/* turn off video-dma1 */ |
WRITE_RPS0(CMD_WR_REG_MASK | (MC1/4)); |
WRITE_RPS0(MASK_22 | MASK_06); /* => mask */ |
WRITE_RPS0(MASK_22); /* => values */ |
if( 0 != planar ) { |
/* turn off video-dma2 */ |
WRITE_RPS0(CMD_WR_REG_MASK | (MC1/4)); |
WRITE_RPS0(MASK_05 | MASK_21); /* => mask */ |
WRITE_RPS0(MASK_21); /* => values */ |
/* turn off video-dma3 */ |
WRITE_RPS0(CMD_WR_REG_MASK | (MC1/4)); |
WRITE_RPS0(MASK_04 | MASK_20); /* => mask */ |
WRITE_RPS0(MASK_20); /* => values */ |
} |
/* generate interrupt */ |
WRITE_RPS0(CMD_INTERRUPT); |
/* stop */ |
WRITE_RPS0(CMD_STOP); |
} |
void saa7146_set_capture(struct saa7146_dev *dev, struct saa7146_buf *buf, struct saa7146_buf *next) |
{ |
struct saa7146_format *sfmt = format_by_fourcc(dev,buf->fmt->pixelformat); |
struct saa7146_vv *vv = dev->vv_data; |
u32 vdma1_prot_addr; |
DEB_CAP(("buf:%p, next:%p\n",buf,next)); |
vdma1_prot_addr = saa7146_read(dev, PROT_ADDR1); |
if( 0 == vdma1_prot_addr ) { |
/* clear out beginning of streaming bit (rps register 0)*/ |
DEB_CAP(("forcing sync to new frame\n")); |
saa7146_write(dev, MC2, MASK_27 ); |
} |
saa7146_set_window(dev, buf->fmt->width, buf->fmt->height, buf->fmt->field); |
saa7146_set_output_format(dev, sfmt->trans); |
if ( vv->last_field == V4L2_FIELD_INTERLACED ) { |
} else if ( vv->last_field == V4L2_FIELD_TOP ) { |
vv->last_field = V4L2_FIELD_BOTTOM; |
} else if ( vv->last_field == V4L2_FIELD_BOTTOM ) { |
vv->last_field = V4L2_FIELD_TOP; |
} |
if( 0 != IS_PLANAR(sfmt->trans)) { |
calculate_video_dma_grab_planar(dev, buf); |
program_capture_engine(dev,1); |
} else { |
calculate_video_dma_grab_packed(dev, buf); |
program_capture_engine(dev,0); |
} |
/* |
printk("vdma%d.base_even: 0x%08x\n", 1,saa7146_read(dev,BASE_EVEN1)); |
printk("vdma%d.base_odd: 0x%08x\n", 1,saa7146_read(dev,BASE_ODD1)); |
printk("vdma%d.prot_addr: 0x%08x\n", 1,saa7146_read(dev,PROT_ADDR1)); |
printk("vdma%d.base_page: 0x%08x\n", 1,saa7146_read(dev,BASE_PAGE1)); |
printk("vdma%d.pitch: 0x%08x\n", 1,saa7146_read(dev,PITCH1)); |
printk("vdma%d.num_line_byte: 0x%08x\n", 1,saa7146_read(dev,NUM_LINE_BYTE1)); |
printk("vdma%d => vptr : 0x%08x\n", 1,saa7146_read(dev,PCI_VDP1)); |
*/ |
/* write the address of the rps-program */ |
saa7146_write(dev, RPS_ADDR0, dev->d_rps0.dma_handle); |
/* turn on rps */ |
saa7146_write(dev, MC1, (MASK_12 | MASK_28)); |
} |
/shark/trunk/drivers/cm7326/tea6415c.c |
---|
0,0 → 1,266 |
/* |
tea6415c.h - i2c-driver for the tea6415c by SGS Thomson |
Copyright (C) 1998-2003 Michael Hunold <michael@mihu.de> |
The tea6415c is a bus controlled video-matrix-switch |
with 8 inputs and 6 outputs. |
It is cascadable, i.e. it can be found at the addresses |
0x43 and 0x03 on the i2c-bus. |
For detailed informations download the specifications directly |
from SGS Thomson at http://www.st.com |
This program is free software; you can redistribute it and/or modify |
it under the terms of the GNU General Public License vs 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 Mvss Ave, Cambridge, MA 02139, USA. |
*/ |
#include <linux/version.h> |
#include <linux/module.h> |
#include <linux/kernel.h> |
#include <linux/poll.h> |
#include <linux/slab.h> |
#include <linux/i2c.h> |
#include <linux/init.h> |
#include "tea6415c.h" |
static int debug = 0; /* insmod parameter */ |
MODULE_PARM(debug,"i"); |
#define dprintk if (debug) printk |
#define TEA6415C_NUM_INPUTS 8 |
#define TEA6415C_NUM_OUTPUTS 6 |
/* addresses to scan, found only at 0x03 and/or 0x43 (7-bit) */ |
static unsigned short normal_i2c[] = {I2C_TEA6415C_1, I2C_TEA6415C_2, I2C_CLIENT_END}; |
static unsigned short normal_i2c_range[] = {I2C_CLIENT_END}; |
/* magic definition of all other variables and things */ |
I2C_CLIENT_INSMOD; |
static struct i2c_driver driver; |
/* unique ID allocation */ |
static int tea6415c_id = 0; |
/* this function is called by i2c_probe */ |
static int tea6415c_detect(struct i2c_adapter *adapter, int address, unsigned short flags, int kind) |
{ |
struct i2c_client *client = 0; |
int err = 0; |
/* let's see whether this adapter can support what we need */ |
if ( 0 == i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WRITE_BYTE)) { |
return 0; |
} |
/* allocate memory for client structure */ |
client = kmalloc(sizeof(struct i2c_client), GFP_KERNEL); |
if (0 == client) { |
return -ENOMEM; |
} |
memset(client, 0, sizeof(struct i2c_client)); |
/* fill client structure */ |
#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)) |
sprintf(client->name,"tea6415c (0x%02x)", address); |
#else |
sprintf(client->dev.name,"tea6415c (0x%02x)", address); |
#endif |
client->id = tea6415c_id++; |
client->flags = 0; |
client->addr = address; |
client->adapter = adapter; |
client->driver = &driver; |
/* tell the i2c layer a new client has arrived */ |
if (0 != (err = i2c_attach_client(client))) { |
kfree(client); |
return err; |
} |
#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)) |
printk("tea6415c.o: detected @ 0x%02x on adapter %s\n",address,&client->adapter->name[0]); |
#else |
printk("tea6415c.o: detected @ 0x%02x on adapter %s\n",address,&client->adapter->dev.name[0]); |
#endif |
return 0; |
} |
static int tea6415c_attach(struct i2c_adapter *adapter) |
{ |
/* let's see whether this is a know adapter we can attach to */ |
if( adapter->id != I2C_ALGO_SAA7146 ) { |
#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)) |
dprintk("tea6415c.o: refusing to probe on unknown adapter [name='%s',id=0x%x]\n",adapter->name,adapter->id); |
#else |
dprintk("tea6415c.o: refusing to probe on unknown adapter [name='%s',id=0x%x]\n",adapter->dev.name,adapter->id); |
#endif |
return -ENODEV; |
} |
return i2c_probe(adapter,&addr_data,&tea6415c_detect); |
} |
static int tea6415c_detach(struct i2c_client *client) |
{ |
int err = 0; |
if ( 0 != (err = i2c_detach_client(client))) { |
printk("tea6415c.o: Client deregistration failed, client not detached.\n"); |
return err; |
} |
kfree(client); |
return 0; |
} |
/* makes a connection between the input-pin 'i' and the output-pin 'o' |
for the tea6415c-client 'client' */ |
static int tea6415c_switch(struct i2c_client *client, int i, int o) |
{ |
u8 byte = 0; |
dprintk("tea6415c.o: tea6415c_switch: adr:0x%02x, i:%d, o:%d\n", client->addr, i, o); |
/* check if the pins are valid */ |
if ( 0 == (( 1 == i || 3 == i || 5 == i || 6 == i || 8 == i || 10 == i || 20 == i || 11 == i ) && |
(18 == o || 17 == o || 16 == o || 15 == o || 14 == o || 13 == o ))) |
return -1; |
/* to understand this, have a look at the tea6415c-specs (p.5) */ |
switch(o) { |
case 18: |
byte = 0x00; |
break; |
case 14: |
byte = 0x20; |
break; |
case 16: |
byte = 0x10; |
break; |
case 17: |
byte = 0x08; |
break; |
case 15: |
byte = 0x18; |
break; |
case 13: |
byte = 0x28; |
break; |
}; |
switch(i) { |
case 5: |
byte |= 0x00; |
break; |
case 8: |
byte |= 0x04; |
break; |
case 3: |
byte |= 0x02; |
break; |
case 20: |
byte |= 0x06; |
break; |
case 6: |
byte |= 0x01; |
break; |
case 10: |
byte |= 0x05; |
break; |
case 1: |
byte |= 0x03; |
break; |
case 11: |
byte |= 0x07; |
break; |
}; |
if ( 0 != i2c_smbus_write_byte(client,byte)) { |
dprintk("tea6415c.o: tea6415c_switch: could not write to tea6415c\n"); |
return -1; |
} |
return 0; |
} |
static int tea6415c_command(struct i2c_client *client, unsigned int cmd, void* arg) |
{ |
struct tea6415c_multiplex *v = (struct tea6415c_multiplex*)arg; |
int result = 0; |
switch (cmd) { |
case TEA6415C_SWITCH: { |
result = tea6415c_switch(client,v->in,v->out); |
break; |
} |
default: { |
return -ENOIOCTLCMD; |
} |
} |
if ( 0 != result ) |
return result; |
return 0; |
} |
static void tea6415c_inc_use(struct i2c_client *client) |
{ |
#ifdef MODULE |
MOD_INC_USE_COUNT; |
#endif |
} |
static void tea6415c_dec_use(struct i2c_client *client) |
{ |
#ifdef MODULE |
MOD_DEC_USE_COUNT; |
#endif |
} |
static struct i2c_driver driver = { |
#if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,54) |
.owner = THIS_MODULE, |
#endif |
.name = "tea6415c driver", |
.id = I2C_DRIVERID_TEA6415C, |
.flags = I2C_DF_NOTIFY, |
.attach_adapter = tea6415c_attach, |
.detach_client = tea6415c_detach, |
.command = tea6415c_command, |
.inc_use = tea6415c_inc_use, |
.dec_use = tea6415c_dec_use, |
}; |
static int tea6415c_init_module(void) |
{ |
i2c_add_driver(&driver); |
return 0; |
} |
static void tea6415c_cleanup_module(void) |
{ |
i2c_del_driver(&driver); |
} |
module_init(tea6415c_init_module); |
module_exit(tea6415c_cleanup_module); |
MODULE_AUTHOR("Michael Hunold <michael@mihu.de>"); |
MODULE_DESCRIPTION("tea6415c driver"); |
MODULE_LICENSE("GPL"); |
/shark/trunk/drivers/cm7326/makefile |
---|
0,0 → 1,23 |
# CM7326 - Driver for pc104+ framegrabber |
ifndef BASE |
BASE=../.. |
endif |
include $(BASE)/config/config.mk |
LIBRARY = cm7326 |
OBJS_PATH = $(BASE)/drivers/cm7326 |
OBJS = cm7326.o |
OTHERINCL += -I$(BASE)/drivers/cm7326/include -I$(BASE)/drivers/linuxc26/include -I$(BASE)/drivers/i2c/include |
C_OPT += -D__KERNEL__ |
include $(BASE)/config/lib.mk |
clean:: |
rm -f $(OBJS) |
/shark/trunk/drivers/cm7326/tda9840.c |
---|
0,0 → 1,316 |
/* |
tda9840.h - i2c-driver for the tda9840 by SGS Thomson |
Copyright (C) 1998-2003 Michael Hunold <michael@mihu.de> |
The tda9840 is a stereo/dual sound processor with digital |
identification. It can be found at address 0x42 on the i2c-bus. |
For detailed informations download the specifications directly |
from SGS Thomson at http://www.st.com |
This program is free software; you can redistribute it and/or modify |
it under the terms of the GNU General Public License as published by |
the Free Software Foundation; either version 2 of the License, or |
(at your option) any later version. |
This program is distributed in the hope that it will be useful, |
but WITHOUT ANY WARRANTY; without even the implied warranty of |
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
GNU General Public License for more details. |
You should have received a copy of the GNU General Public License |
along with this program; if not, write to the Free Software |
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. |
*/ |
#include <linux/version.h> |
#include <linux/module.h> |
#include <linux/kernel.h> |
#include <linux/poll.h> |
#include <linux/slab.h> |
#include <linux/i2c.h> |
#include <linux/init.h> |
#include "tda9840.h" |
static int debug = 0; /* insmod parameter */ |
MODULE_PARM(debug,"i"); |
#define dprintk if (debug) printk |
#define SWITCH 0x00 |
#define LEVEL_ADJUST 0x02 |
#define STEREO_ADJUST 0x03 |
#define TEST 0x04 |
/* addresses to scan, found only at 0x42 (7-Bit) */ |
static unsigned short normal_i2c[] = {I2C_TDA9840, I2C_CLIENT_END}; |
static unsigned short normal_i2c_range[] = {I2C_CLIENT_END}; |
/* magic definition of all other variables and things */ |
I2C_CLIENT_INSMOD; |
/* unique ID allocation */ |
static int tda9840_id = 0; |
static struct i2c_driver driver; |
static int tda9840_command(struct i2c_client *client, unsigned int cmd, void* arg) |
{ |
int result = 0; |
switch (cmd) { |
case TDA9840_SWITCH: |
{ |
int byte = *(int*)arg; |
dprintk("tda9840.o: TDA9840_SWITCH: 0x%02x\n",byte); |
if ( byte != TDA9840_SET_MONO |
&& byte != TDA9840_SET_MUTE |
&& byte != TDA9840_SET_STEREO |
&& byte != TDA9840_SET_LANG1 |
&& byte != TDA9840_SET_LANG2 |
&& byte != TDA9840_SET_BOTH |
&& byte != TDA9840_SET_BOTH_R |
&& byte != TDA9840_SET_EXTERNAL ) { |
return -EINVAL; |
} |
if ( 0 != (result = i2c_smbus_write_byte_data(client, SWITCH, byte))) { |
printk("tda9840.o: TDA9840_SWITCH error.\n"); |
return -EFAULT; |
} |
return 0; |
} |
case TDA9840_LEVEL_ADJUST: |
{ |
int byte = *(int*)arg; |
dprintk("tda9840.o: TDA9840_LEVEL_ADJUST: %d\n",byte); |
/* check for correct range */ |
if ( byte > 25 || byte < -20 ) |
return -EINVAL; |
/* calculate actual value to set, see specs, page 18 */ |
byte /= 5; |
if ( 0 < byte ) |
byte += 0x8; |
else |
byte = -byte; |
if ( 0 != (result = i2c_smbus_write_byte_data(client, LEVEL_ADJUST, byte))) { |
printk("tda9840.o: TDA9840_LEVEL_ADJUST error.\n"); |
return -EFAULT; |
} |
return 0; |
} |
case TDA9840_STEREO_ADJUST: |
{ |
int byte = *(int*)arg; |
dprintk("tda9840.o: TDA9840_STEREO_ADJUST: %d\n",byte); |
/* check for correct range */ |
if ( byte > 25 || byte < -24 ) |
return -EINVAL; |
/* calculate actual value to set */ |
byte /= 5; |
if ( 0 < byte ) |
byte += 0x20; |
else |
byte = -byte; |
if ( 0 != (result = i2c_smbus_write_byte_data(client, STEREO_ADJUST, byte))) { |
printk("tda9840.o: TDA9840_STEREO_ADJUST error.\n"); |
return -EFAULT; |
} |
return 0; |
} |
case TDA9840_DETECT: |
{ |
int byte = 0x0; |
if ( -1 == (byte = i2c_smbus_read_byte_data(client, STEREO_ADJUST))) { |
printk("tda9840.o: TDA9840_DETECT error while reading.\n"); |
return -EFAULT; |
} |
if( 0 != (byte & 0x80)) { |
dprintk("tda9840.o: TDA9840_DETECT, register contents invalid.\n"); |
return -EFAULT; |
} |
dprintk("tda9840.o: TDA9840_DETECT, result: 0x%02x (original byte)\n",byte); |
return ((byte & 0x60) >> 5); |
} |
case TDA9840_TEST: |
{ |
int byte = *(int*)arg; |
dprintk("tda9840.o: TDA9840_TEST: 0x%02x\n",byte); |
/* mask out irrelevant bits */ |
byte &= 0x3; |
if ( 0 != (result = i2c_smbus_write_byte_data(client, TEST, byte))) { |
printk("tda9840.o: TDA9840_TEST error.\n"); |
return -EFAULT; |
} |
return 0; |
} |
default: |
return -ENOIOCTLCMD; |
} |
return 0; |
} |
static int tda9840_detect(struct i2c_adapter *adapter, int address, unsigned short flags, int kind) |
{ |
struct i2c_client *client; |
int result = 0; |
int byte = 0x0; |
/* let's see whether this adapter can support what we need */ |
if ( 0 == i2c_check_functionality(adapter, I2C_FUNC_SMBUS_READ_BYTE_DATA|I2C_FUNC_SMBUS_WRITE_BYTE_DATA)) { |
return 0; |
} |
/* allocate memory for client structure */ |
client = kmalloc(sizeof(struct i2c_client), GFP_KERNEL); |
if (0 == client) { |
printk("tda9840.o: not enough kernel memory.\n"); |
return -ENOMEM; |
} |
memset(client, 0, sizeof(struct i2c_client)); |
/* fill client structure */ |
#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)) |
sprintf(client->name,"tda9840 (0x%02x)", address); |
#else |
sprintf(client->dev.name,"tda9840 (0x%02x)", address); |
#endif |
client->id = tda9840_id++; |
client->flags = 0; |
client->addr = address; |
client->adapter = adapter; |
client->driver = &driver; |
/* tell the i2c layer a new client has arrived */ |
if (0 != (result = i2c_attach_client(client))) { |
kfree(client); |
return result; |
} |
/* set initial values for level & stereo - adjustment, mode */ |
byte = 0; |
if ( 0 != (result = tda9840_command(client, TDA9840_LEVEL_ADJUST, &byte))) { |
printk("tda9840.o: could not initialize ic #1. continuing anyway. (result:%d)\n",result); |
} |
if ( 0 != (result = tda9840_command(client, TDA9840_STEREO_ADJUST, &byte))) { |
printk("tda9840.o: could not initialize ic #2. continuing anyway. (result:%d)\n",result); |
} |
byte = TDA9840_SET_MONO; |
if ( 0 != (result = tda9840_command(client, TDA9840_SWITCH, &byte))) { |
printk("tda9840.o: could not initialize ic #3. continuing anyway. (result:%d)\n",result); |
} |
#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)) |
printk("tda9840.o: detected @ 0x%02x on adapter %s\n",address,&client->adapter->name[0]); |
#else |
printk("tda9840.o: detected @ 0x%02x on adapter %s\n",address,&client->adapter->dev.name[0]); |
#endif |
return 0; |
} |
static int tda9840_attach(struct i2c_adapter *adapter) |
{ |
/* let's see whether this is a know adapter we can attach to */ |
if( adapter->id != I2C_ALGO_SAA7146 ) { |
#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)) |
dprintk("tda9840.o: refusing to probe on unknown adapter [name='%s',id=0x%x]\n",adapter->name,adapter->id); |
#else |
dprintk("tda9840.o: refusing to probe on unknown adapter [name='%s',id=0x%x]\n",adapter->dev.name,adapter->id); |
#endif |
return -ENODEV; |
} |
return i2c_probe(adapter,&addr_data,&tda9840_detect); |
} |
static int tda9840_detach(struct i2c_client *client) |
{ |
int err = 0; |
if ( 0 != (err = i2c_detach_client(client))) { |
printk("tda9840.o: Client deregistration failed, client not detached.\n"); |
return err; |
} |
kfree(client); |
return 0; |
} |
static void tda9840_inc_use(struct i2c_client *client) |
{ |
#ifdef MODULE |
MOD_INC_USE_COUNT; |
#endif |
} |
static void tda9840_dec_use(struct i2c_client *client) |
{ |
#ifdef MODULE |
MOD_DEC_USE_COUNT; |
#endif |
} |
static struct i2c_driver driver = { |
#if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,54) |
.owner = THIS_MODULE, |
#endif |
.name = "tda9840 driver", |
.id = I2C_DRIVERID_TDA9840, |
.flags = I2C_DF_NOTIFY, |
.attach_adapter = tda9840_attach, |
.detach_client = tda9840_detach, |
.command = tda9840_command, |
.inc_use = tda9840_inc_use, |
.dec_use = tda9840_dec_use, |
}; |
static int tda9840_init_module(void) |
{ |
i2c_add_driver(&driver); |
return 0; |
} |
static void tda9840_cleanup_module(void) |
{ |
i2c_del_driver(&driver); |
} |
module_init(tda9840_init_module); |
module_exit(tda9840_cleanup_module); |
MODULE_AUTHOR("Michael Hunold <michael@mihu.de>"); |
MODULE_DESCRIPTION("tda9840 driver"); |
MODULE_LICENSE("GPL"); |
/shark/trunk/drivers/cm7326/tuner.c |
---|
0,0 → 1,1007 |
#include <linux/module.h> |
#include <linux/kernel.h> |
#include <linux/sched.h> |
#include <linux/string.h> |
#include <linux/timer.h> |
#include <linux/delay.h> |
#include <linux/errno.h> |
#include <linux/slab.h> |
#include <linux/poll.h> |
#include <linux/i2c.h> |
#include <linux/types.h> |
#include <linux/videodev.h> |
#include <linux/init.h> |
#include <media/tuner.h> |
#include <media/audiochip.h> |
/* Addresses to scan */ |
static unsigned short normal_i2c[] = {I2C_CLIENT_END}; |
static unsigned short normal_i2c_range[] = {0x60,0x6f,I2C_CLIENT_END}; |
I2C_CLIENT_INSMOD; |
/* insmod options */ |
static int debug = 0; |
static int type = -1; |
static int addr = 0; |
static char *pal = "b"; |
static int tv_range[2] = { 44, 958 }; |
static int radio_range[2] = { 65, 108 }; |
MODULE_PARM(debug,"i"); |
MODULE_PARM(type,"i"); |
MODULE_PARM(addr,"i"); |
MODULE_PARM(tv_range,"2i"); |
MODULE_PARM(radio_range,"2i"); |
MODULE_PARM(pal,"s"); |
#define optimize_vco 1 |
MODULE_DESCRIPTION("device driver for various TV and TV+FM radio tuners"); |
MODULE_AUTHOR("Ralph Metzler, Gerd Knorr, Gunther Mayer"); |
MODULE_LICENSE("GPL"); |
static int this_adap; |
#define dprintk if (debug) printk |
struct tuner |
{ |
int type; /* chip type */ |
int freq; /* keep track of the current settings */ |
int std; |
int radio; |
int mode; /* current norm for multi-norm tuners */ |
int xogc; // only for MT2032 |
}; |
static struct i2c_driver driver; |
static struct i2c_client client_template; |
/* ---------------------------------------------------------------------- */ |
/* tv standard selection for Temic 4046 FM5 |
this value takes the low bits of control byte 2 |
from datasheet Rev.01, Feb.00 |
standard BG I L L2 D |
picture IF 38.9 38.9 38.9 33.95 38.9 |
sound 1 33.4 32.9 32.4 40.45 32.4 |
sound 2 33.16 |
NICAM 33.05 32.348 33.05 33.05 |
*/ |
#define TEMIC_SET_PAL_I 0x05 |
#define TEMIC_SET_PAL_DK 0x09 |
#define TEMIC_SET_PAL_L 0x0a // SECAM ? |
#define TEMIC_SET_PAL_L2 0x0b // change IF ! |
#define TEMIC_SET_PAL_BG 0x0c |
/* tv tuner system standard selection for Philips FQ1216ME |
this value takes the low bits of control byte 2 |
from datasheet "1999 Nov 16" (supersedes "1999 Mar 23") |
standard BG DK I L L` |
picture carrier 38.90 38.90 38.90 38.90 33.95 |
colour 34.47 34.47 34.47 34.47 38.38 |
sound 1 33.40 32.40 32.90 32.40 40.45 |
sound 2 33.16 - - - - |
NICAM 33.05 33.05 32.35 33.05 39.80 |
*/ |
#define PHILIPS_SET_PAL_I 0x01 /* Bit 2 always zero !*/ |
#define PHILIPS_SET_PAL_BGDK 0x09 |
#define PHILIPS_SET_PAL_L2 0x0a |
#define PHILIPS_SET_PAL_L 0x0b |
/* system switching for Philips FI1216MF MK2 |
from datasheet "1996 Jul 09", |
standard BG L L' |
picture carrier 38.90 38.90 33.95 |
colour 34.47 34.37 38.38 |
sound 1 33.40 32.40 40.45 |
sound 2 33.16 - - |
NICAM 33.05 33.05 39.80 |
*/ |
#define PHILIPS_MF_SET_BG 0x01 /* Bit 2 must be zero, Bit 3 is system output */ |
#define PHILIPS_MF_SET_PAL_L 0x03 // France |
#define PHILIPS_MF_SET_PAL_L2 0x02 // L' |
/* ---------------------------------------------------------------------- */ |
struct tunertype |
{ |
char *name; |
unsigned char Vendor; |
unsigned char Type; |
unsigned short thresh1; /* band switch VHF_LO <=> VHF_HI */ |
unsigned short thresh2; /* band switch VHF_HI <=> UHF */ |
unsigned char VHF_L; |
unsigned char VHF_H; |
unsigned char UHF; |
unsigned char config; |
unsigned short IFPCoff; /* 622.4=16*38.90 MHz PAL, |
732 =16*45.75 NTSCi, |
940 =58.75 NTSC-Japan */ |
}; |
/* |
* The floats in the tuner struct are computed at compile time |
* by gcc and cast back to integers. Thus we don't violate the |
* "no float in kernel" rule. |
*/ |
static struct tunertype tuners[] = { |
{ "Temic PAL (4002 FH5)", TEMIC, PAL, |
16*140.25,16*463.25,0x02,0x04,0x01,0x8e,623}, |
{ "Philips PAL_I (FI1246 and compatibles)", Philips, PAL_I, |
16*140.25,16*463.25,0xa0,0x90,0x30,0x8e,623}, |
{ "Philips NTSC (FI1236,FM1236 and compatibles)", Philips, NTSC, |
16*157.25,16*451.25,0xA0,0x90,0x30,0x8e,732}, |
{ "Philips (SECAM+PAL_BG) (FI1216MF, FM1216MF, FR1216MF)", Philips, SECAM, |
16*168.25,16*447.25,0xA7,0x97,0x37,0x8e,623}, |
{ "NoTuner", NoTuner, NOTUNER, |
0,0,0x00,0x00,0x00,0x00,0x00}, |
{ "Philips PAL_BG (FI1216 and compatibles)", Philips, PAL, |
16*168.25,16*447.25,0xA0,0x90,0x30,0x8e,623}, |
{ "Temic NTSC (4032 FY5)", TEMIC, NTSC, |
16*157.25,16*463.25,0x02,0x04,0x01,0x8e,732}, |
{ "Temic PAL_I (4062 FY5)", TEMIC, PAL_I, |
16*170.00,16*450.00,0x02,0x04,0x01,0x8e,623}, |
{ "Temic NTSC (4036 FY5)", TEMIC, NTSC, |
16*157.25,16*463.25,0xa0,0x90,0x30,0x8e,732}, |
{ "Alps HSBH1", TEMIC, NTSC, |
16*137.25,16*385.25,0x01,0x02,0x08,0x8e,732}, |
{ "Alps TSBE1",TEMIC,PAL, |
16*137.25,16*385.25,0x01,0x02,0x08,0x8e,732}, |
{ "Alps TSBB5", Alps, PAL_I, /* tested (UK UHF) with Modulartech MM205 */ |
16*133.25,16*351.25,0x01,0x02,0x08,0x8e,632}, |
{ "Alps TSBE5", Alps, PAL, /* untested - data sheet guess. Only IF differs. */ |
16*133.25,16*351.25,0x01,0x02,0x08,0x8e,622}, |
{ "Alps TSBC5", Alps, PAL, /* untested - data sheet guess. Only IF differs. */ |
16*133.25,16*351.25,0x01,0x02,0x08,0x8e,608}, |
{ "Temic PAL_BG (4006FH5)", TEMIC, PAL, |
16*170.00,16*450.00,0xa0,0x90,0x30,0x8e,623}, |
{ "Alps TSCH6",Alps,NTSC, |
16*137.25,16*385.25,0x14,0x12,0x11,0x8e,732}, |
{ "Temic PAL_DK (4016 FY5)",TEMIC,PAL, |
16*168.25,16*456.25,0xa0,0x90,0x30,0x8e,623}, |
{ "Philips NTSC_M (MK2)",Philips,NTSC, |
16*160.00,16*454.00,0xa0,0x90,0x30,0x8e,732}, |
{ "Temic PAL_I (4066 FY5)", TEMIC, PAL_I, |
16*169.00, 16*454.00, 0xa0,0x90,0x30,0x8e,623}, |
{ "Temic PAL* auto (4006 FN5)", TEMIC, PAL, |
16*169.00, 16*454.00, 0xa0,0x90,0x30,0x8e,623}, |
{ "Temic PAL_BG (4009 FR5) or PAL_I (4069 FR5)", TEMIC, PAL, |
16*141.00, 16*464.00, 0xa0,0x90,0x30,0x8e,623}, |
{ "Temic NTSC (4039 FR5)", TEMIC, NTSC, |
16*158.00, 16*453.00, 0xa0,0x90,0x30,0x8e,732}, |
{ "Temic PAL/SECAM multi (4046 FM5)", TEMIC, PAL, |
16*169.00, 16*454.00, 0xa0,0x90,0x30,0x8e,623}, |
{ "Philips PAL_DK (FI1256 and compatibles)", Philips, PAL, |
16*170.00,16*450.00,0xa0,0x90,0x30,0x8e,623}, |
{ "Philips PAL/SECAM multi (FQ1216ME)", Philips, PAL, |
16*170.00,16*450.00,0xa0,0x90,0x30,0x8e,623}, |
{ "LG PAL_I+FM (TAPC-I001D)", LGINNOTEK, PAL_I, |
16*170.00,16*450.00,0xa0,0x90,0x30,0x8e,623}, |
{ "LG PAL_I (TAPC-I701D)", LGINNOTEK, PAL_I, |
16*170.00,16*450.00,0xa0,0x90,0x30,0x8e,623}, |
{ "LG NTSC+FM (TPI8NSR01F)", LGINNOTEK, NTSC, |
16*210.00,16*497.00,0xa0,0x90,0x30,0x8e,732}, |
{ "LG PAL_BG+FM (TPI8PSB01D)", LGINNOTEK, PAL, |
16*170.00,16*450.00,0xa0,0x90,0x30,0x8e,623}, |
{ "LG PAL_BG (TPI8PSB11D)", LGINNOTEK, PAL, |
16*170.00,16*450.00,0xa0,0x90,0x30,0x8e,623}, |
{ "Temic PAL* auto + FM (4009 FN5)", TEMIC, PAL, |
16*141.00, 16*464.00, 0xa0,0x90,0x30,0x8e,623}, |
{ "SHARP NTSC_JP (2U5JF5540)", SHARP, NTSC, /* 940=16*58.75 NTSC@Japan */ |
16*137.25,16*317.25,0x01,0x02,0x08,0x8e,732 }, // Corrected to NTSC=732 (was:940) |
{ "Samsung PAL TCPM9091PD27", Samsung, PAL, /* from sourceforge v3tv */ |
16*169,16*464,0xA0,0x90,0x30,0x8e,623}, |
{ "MT2032 universal", Microtune,PAL|NTSC, |
0,0,0,0,0,0,0}, |
{ "Temic PAL_BG (4106 FH5)", TEMIC, PAL, |
16*141.00, 16*464.00, 0xa0,0x90,0x30,0x8e,623}, |
{ "Temic PAL_DK/SECAM_L (4012 FY5)", TEMIC, PAL, |
16*140.25, 16*463.25, 0x02,0x04,0x01,0x8e,623}, |
{ "Temic NTSC (4136 FY5)", TEMIC, NTSC, |
16*158.00, 16*453.00, 0xa0,0x90,0x30,0x8e,732}, |
{ "LG PAL (newer TAPC series)", LGINNOTEK, PAL, |
16*170.00, 16*450.00, 0x01,0x02,0x08,0x8e,623}, |
{ "Philips PAL/SECAM multi (FM1216ME MK3)", Philips, PAL, |
16*160.00,16*442.00,0x01,0x02,0x04,0x8e,623 }, |
{ "LG NTSC (newer TAPC series)", LGINNOTEK, NTSC, |
16*170.00, 16*450.00, 0x01,0x02,0x08,0x8e,732}, |
}; |
#define TUNERS (sizeof(tuners)/sizeof(struct tunertype)) |
/* ---------------------------------------------------------------------- */ |
static int tuner_getstatus(struct i2c_client *c) |
{ |
unsigned char byte; |
struct tuner *t = (struct tuner*)c->data; |
if (t->type == TUNER_MT2032) |
return 0; |
if (1 != i2c_master_recv(c,&byte,1)) |
return 0; |
return byte; |
} |
#define TUNER_POR 0x80 |
#define TUNER_FL 0x40 |
#define TUNER_MODE 0x38 |
#define TUNER_AFC 0x07 |
#define TUNER_STEREO 0x10 /* radio mode */ |
#define TUNER_SIGNAL 0x07 /* radio mode */ |
static int tuner_signal(struct i2c_client *c) |
{ |
return (tuner_getstatus(c) & TUNER_SIGNAL)<<13; |
} |
static int tuner_stereo(struct i2c_client *c) |
{ |
return (tuner_getstatus (c) & TUNER_STEREO); |
} |
#if 0 /* unused */ |
static int tuner_islocked (struct i2c_client *c) |
{ |
return (tuner_getstatus (c) & TUNER_FL); |
} |
static int tuner_afcstatus (struct i2c_client *c) |
{ |
return (tuner_getstatus (c) & TUNER_AFC) - 2; |
} |
static int tuner_mode (struct i2c_client *c) |
{ |
return (tuner_getstatus (c) & TUNER_MODE) >> 3; |
} |
#endif |
// Initalization as described in "MT203x Programming Procedures", Rev 1.2, Feb.2001 |
static int mt2032_init(struct i2c_client *c) |
{ |
unsigned char buf[21]; |
int ret,xogc,xok=0; |
struct tuner *t = (struct tuner*)c->data; |
buf[0]=0; |
ret=i2c_master_send(c,buf,1); |
i2c_master_recv(c,buf,21); |
printk("MT2032: Companycode=%02x%02x Part=%02x Revision=%02x\n", |
buf[0x11],buf[0x12],buf[0x13],buf[0x14]); |
if(debug) { |
int i; |
printk("MT2032 hexdump:\n"); |
for(i=0;i<21;i++) { |
printk(" %02x",buf[i]); |
if(((i+1)%8)==0) printk(" "); |
if(((i+1)%16)==0) printk("\n "); |
} |
printk("\n "); |
} |
// Look for MT2032 id: |
// part= 0x04(MT2032), 0x06(MT2030), 0x07(MT2040) |
if((buf[0x11] != 0x4d) || (buf[0x12] != 0x54) || (buf[0x13] != 0x04)) { |
printk("not a MT2032.\n"); |
return 0; |
} |
// Initialize Registers per spec. |
buf[1]=2; // Index to register 2 |
buf[2]=0xff; |
buf[3]=0x0f; |
buf[4]=0x1f; |
ret=i2c_master_send(c,buf+1,4); |
buf[5]=6; // Index register 6 |
buf[6]=0xe4; |
buf[7]=0x8f; |
buf[8]=0xc3; |
buf[9]=0x4e; |
buf[10]=0xec; |
ret=i2c_master_send(c,buf+5,6); |
buf[12]=13; // Index register 13 |
buf[13]=0x32; |
ret=i2c_master_send(c,buf+12,2); |
// Adjust XOGC (register 7), wait for XOK |
xogc=7; |
do { |
dprintk("mt2032: xogc = 0x%02x\n",xogc&0x07); |
mdelay(10); |
buf[0]=0x0e; |
i2c_master_send(c,buf,1); |
i2c_master_recv(c,buf,1); |
xok=buf[0]&0x01; |
dprintk("mt2032: xok = 0x%02x\n",xok); |
if (xok == 1) break; |
xogc--; |
dprintk("mt2032: xogc = 0x%02x\n",xogc&0x07); |
if (xogc == 3) { |
xogc=4; // min. 4 per spec |
break; |
} |
buf[0]=0x07; |
buf[1]=0x88 + xogc; |
ret=i2c_master_send(c,buf,2); |
if (ret!=2) |
printk("mt2032_init failed with %d\n",ret); |
} while (xok != 1 ); |
t->xogc=xogc; |
return(1); |
} |
// IsSpurInBand()? |
static int mt2032_spurcheck(int f1, int f2, int spectrum_from,int spectrum_to) |
{ |
int n1=1,n2,f; |
f1=f1/1000; //scale to kHz to avoid 32bit overflows |
f2=f2/1000; |
spectrum_from/=1000; |
spectrum_to/=1000; |
dprintk("spurcheck f1=%d f2=%d from=%d to=%d\n",f1,f2,spectrum_from,spectrum_to); |
do { |
n2=-n1; |
f=n1*(f1-f2); |
do { |
n2--; |
f=f-f2; |
dprintk(" spurtest n1=%d n2=%d ftest=%d\n",n1,n2,f); |
if( (f>spectrum_from) && (f<spectrum_to)) |
printk("mt2032 spurcheck triggered: %d\n",n1); |
} while ( (f>(f2-spectrum_to)) || (n2>-5)); |
n1++; |
} while (n1<5); |
return 1; |
} |
static int mt2032_compute_freq(int rfin, int if1, int if2, int spectrum_from, |
int spectrum_to, unsigned char *buf, int *ret_sel, int xogc) //all in Hz |
{ |
int fref,lo1,lo1n,lo1a,s,sel,lo1freq, desired_lo1, |
desired_lo2,lo2,lo2n,lo2a,lo2num,lo2freq; |
fref= 5250 *1000; //5.25MHz |
desired_lo1=rfin+if1; |
lo1=(2*(desired_lo1/1000)+(fref/1000)) / (2*fref/1000); |
lo1n=lo1/8; |
lo1a=lo1-(lo1n*8); |
s=rfin/1000/1000+1090; |
if(optimize_vco) { |
if(s>1890) sel=0; |
else if(s>1720) sel=1; |
else if(s>1530) sel=2; |
else if(s>1370) sel=3; |
else sel=4; // >1090 |
} |
else { |
if(s>1790) sel=0; // <1958 |
else if(s>1617) sel=1; |
else if(s>1449) sel=2; |
else if(s>1291) sel=3; |
else sel=4; // >1090 |
} |
*ret_sel=sel; |
lo1freq=(lo1a+8*lo1n)*fref; |
dprintk("mt2032: rfin=%d lo1=%d lo1n=%d lo1a=%d sel=%d, lo1freq=%d\n", |
rfin,lo1,lo1n,lo1a,sel,lo1freq); |
desired_lo2=lo1freq-rfin-if2; |
lo2=(desired_lo2)/fref; |
lo2n=lo2/8; |
lo2a=lo2-(lo2n*8); |
lo2num=((desired_lo2/1000)%(fref/1000))* 3780/(fref/1000); //scale to fit in 32bit arith |
lo2freq=(lo2a+8*lo2n)*fref + lo2num*(fref/1000)/3780*1000; |
dprintk("mt2032: rfin=%d lo2=%d lo2n=%d lo2a=%d num=%d lo2freq=%d\n", |
rfin,lo2,lo2n,lo2a,lo2num,lo2freq); |
if(lo1a<0 || lo1a>7 || lo1n<17 ||lo1n>48 || lo2a<0 ||lo2a >7 ||lo2n<17 || lo2n>30) { |
printk("mt2032: frequency parameters out of range: %d %d %d %d\n", |
lo1a, lo1n, lo2a,lo2n); |
return(-1); |
} |
mt2032_spurcheck(lo1freq, desired_lo2, spectrum_from, spectrum_to); |
// should recalculate lo1 (one step up/down) |
// set up MT2032 register map for transfer over i2c |
buf[0]=lo1n-1; |
buf[1]=lo1a | (sel<<4); |
buf[2]=0x86; // LOGC |
buf[3]=0x0f; //reserved |
buf[4]=0x1f; |
buf[5]=(lo2n-1) | (lo2a<<5); |
if(rfin >400*1000*1000) |
buf[6]=0xe4; |
else |
buf[6]=0xf4; // set PKEN per rev 1.2 |
buf[7]=8+xogc; |
buf[8]=0xc3; //reserved |
buf[9]=0x4e; //reserved |
buf[10]=0xec; //reserved |
buf[11]=(lo2num&0xff); |
buf[12]=(lo2num>>8) |0x80; // Lo2RST |
return 0; |
} |
static int mt2032_check_lo_lock(struct i2c_client *c) |
{ |
int try,lock=0; |
unsigned char buf[2]; |
for(try=0;try<10;try++) { |
buf[0]=0x0e; |
i2c_master_send(c,buf,1); |
i2c_master_recv(c,buf,1); |
dprintk("mt2032 Reg.E=0x%02x\n",buf[0]); |
lock=buf[0] &0x06; |
if (lock==6) |
break; |
dprintk("mt2032: pll wait 1ms for lock (0x%2x)\n",buf[0]); |
udelay(1000); |
} |
return lock; |
} |
static int mt2032_optimize_vco(struct i2c_client *c,int sel,int lock) |
{ |
unsigned char buf[2]; |
int tad1; |
buf[0]=0x0f; |
i2c_master_send(c,buf,1); |
i2c_master_recv(c,buf,1); |
dprintk("mt2032 Reg.F=0x%02x\n",buf[0]); |
tad1=buf[0]&0x07; |
if(tad1 ==0) return lock; |
if(tad1 ==1) return lock; |
if(tad1==2) { |
if(sel==0) |
return lock; |
else sel--; |
} |
else { |
if(sel<4) |
sel++; |
else |
return lock; |
} |
dprintk("mt2032 optimize_vco: sel=%d\n",sel); |
buf[0]=0x0f; |
buf[1]=sel; |
i2c_master_send(c,buf,2); |
lock=mt2032_check_lo_lock(c); |
return lock; |
} |
static void mt2032_set_if_freq(struct i2c_client *c,int rfin, int if1, int if2, int from, int to) |
{ |
unsigned char buf[21]; |
int lint_try,ret,sel,lock=0; |
struct tuner *t = (struct tuner*)c->data; |
dprintk("mt2032_set_if_freq rfin=%d if1=%d if2=%d from=%d to=%d\n",rfin,if1,if2,from,to); |
buf[0]=0; |
ret=i2c_master_send(c,buf,1); |
i2c_master_recv(c,buf,21); |
buf[0]=0; |
ret=mt2032_compute_freq(rfin,if1,if2,from,to,&buf[1],&sel,t->xogc); |
if (ret<0) |
return; |
// send only the relevant registers per Rev. 1.2 |
buf[0]=0; |
ret=i2c_master_send(c,buf,4); |
buf[5]=5; |
ret=i2c_master_send(c,buf+5,4); |
buf[11]=11; |
ret=i2c_master_send(c,buf+11,3); |
if(ret!=3) |
printk("mt2032_set_if_freq failed with %d\n",ret); |
// wait for PLLs to lock (per manual), retry LINT if not. |
for(lint_try=0; lint_try<2; lint_try++) { |
lock=mt2032_check_lo_lock(c); |
if(optimize_vco) |
lock=mt2032_optimize_vco(c,sel,lock); |
if(lock==6) break; |
printk("mt2032: re-init PLLs by LINT\n"); |
buf[0]=7; |
buf[1]=0x80 +8+t->xogc; // set LINT to re-init PLLs |
i2c_master_send(c,buf,2); |
mdelay(10); |
buf[1]=8+t->xogc; |
i2c_master_send(c,buf,2); |
} |
if (lock!=6) |
printk("MT2032 Fatal Error: PLLs didn't lock.\n"); |
buf[0]=2; |
buf[1]=0x20; // LOGC for optimal phase noise |
ret=i2c_master_send(c,buf,2); |
if (ret!=2) |
printk("mt2032_set_if_freq2 failed with %d\n",ret); |
} |
static void mt2032_set_tv_freq(struct i2c_client *c, int freq, int norm) |
{ |
int if2,from,to; |
// signal bandwidth and picture carrier |
if(norm==VIDEO_MODE_NTSC) { |
from=40750*1000; |
to=46750*1000; |
if2=45750*1000; |
} |
else { // Pal |
from=32900*1000; |
to=39900*1000; |
if2=38900*1000; |
} |
mt2032_set_if_freq(c,freq* 1000*1000/16, 1090*1000*1000, if2, from, to); |
} |
// Set tuner frequency, freq in Units of 62.5kHz = 1/16MHz |
static void set_tv_freq(struct i2c_client *c, int freq) |
{ |
u8 config; |
u16 div; |
struct tunertype *tun; |
struct tuner *t = c->data; |
unsigned char buffer[4]; |
int rc; |
if (t->type == -1) { |
printk("tuner: tuner type not set\n"); |
return; |
} |
if (t->type == TUNER_MT2032) { |
mt2032_set_tv_freq(c,freq,t->mode); |
return; |
} |
if (freq < tv_range[0]*16 || freq > tv_range[1]*16) { |
/* FIXME: better do that chip-specific, but |
right now we don't have that in the config |
struct and this way is still better than no |
check at all */ |
printk("tuner: TV freq (%d.%02d) out of range (%d-%d)\n", |
freq/16,freq%16*100/16,tv_range[0],tv_range[1]); |
return; |
} |
tun=&tuners[t->type]; |
if (freq < tun->thresh1) |
config = tun->VHF_L; |
else if (freq < tun->thresh2) |
config = tun->VHF_H; |
else |
config = tun->UHF; |
/* tv norm specific stuff for multi-norm tuners */ |
switch (t->type) { |
case TUNER_PHILIPS_SECAM: // FI1216MF |
/* 0x01 -> ??? no change ??? */ |
/* 0x02 -> PAL BDGHI / SECAM L */ |
/* 0x04 -> ??? PAL others / SECAM others ??? */ |
config &= ~0x02; |
if (t->mode == VIDEO_MODE_SECAM) |
config |= 0x02; |
break; |
case TUNER_TEMIC_4046FM5: |
config &= ~0x0f; |
switch (pal[0]) { |
case 'i': |
case 'I': |
config |= TEMIC_SET_PAL_I; |
break; |
case 'd': |
case 'D': |
config |= TEMIC_SET_PAL_DK; |
break; |
case 'l': |
case 'L': |
config |= TEMIC_SET_PAL_L; |
break; |
case 'b': |
case 'B': |
case 'g': |
case 'G': |
default: |
config |= TEMIC_SET_PAL_BG; |
break; |
} |
break; |
case TUNER_PHILIPS_FQ1216ME: |
config &= ~0x0f; |
switch (pal[0]) { |
case 'i': |
case 'I': |
config |= PHILIPS_SET_PAL_I; |
break; |
case 'l': |
case 'L': |
config |= PHILIPS_SET_PAL_L; |
break; |
case 'd': |
case 'D': |
case 'b': |
case 'B': |
case 'g': |
case 'G': |
config |= PHILIPS_SET_PAL_BGDK; |
break; |
} |
break; |
} |
/* |
* Philips FI1216MK2 remark from specification : |
* for channel selection involving band switching, and to ensure |
* smooth tuning to the desired channel without causing |
* unnecessary charge pump action, it is recommended to consider |
* the difference between wanted channel frequency and the |
* current channel frequency. Unnecessary charge pump action |
* will result in very low tuning voltage which may drive the |
* oscillator to extreme conditions. |
* |
* Progfou: specification says to send config data before |
* frequency in case (wanted frequency < current frequency). |
*/ |
div=freq + tun->IFPCoff; |
if (t->type == TUNER_PHILIPS_SECAM && freq < t->freq) { |
buffer[0] = tun->config; |
buffer[1] = config; |
buffer[2] = (div>>8) & 0x7f; |
buffer[3] = div & 0xff; |
} else { |
buffer[0] = (div>>8) & 0x7f; |
buffer[1] = div & 0xff; |
buffer[2] = tun->config; |
buffer[3] = config; |
} |
dprintk("tuner: tv 0x%02x 0x%02x 0x%02x 0x%02x\n", |
buffer[0],buffer[1],buffer[2],buffer[3]); |
if (4 != (rc = i2c_master_send(c,buffer,4))) |
printk("tuner: i2c i/o error: rc == %d (should be 4)\n",rc); |
} |
static void mt2032_set_radio_freq(struct i2c_client *c,int freq) |
{ |
int if2; |
if2=10700*1000; // 10.7MHz FM intermediate frequency |
// per Manual for FM tuning: first if center freq. 1085 MHz |
mt2032_set_if_freq(c,freq* 1000*1000/16, 1085*1000*1000,if2,if2,if2); |
} |
static void set_radio_freq(struct i2c_client *c, int freq) |
{ |
struct tunertype *tun; |
struct tuner *t = (struct tuner*)c->data; |
unsigned char buffer[4]; |
int rc,div; |
if (freq < radio_range[0]*16 || freq > radio_range[1]*16) { |
printk("tuner: radio freq (%d.%02d) out of range (%d-%d)\n", |
freq/16,freq%16*100/16, |
radio_range[0],radio_range[1]); |
return; |
} |
if (t->type == -1) { |
printk("tuner: tuner type not set\n"); |
return; |
} |
if (t->type == TUNER_MT2032) { |
mt2032_set_radio_freq(c,freq); |
return; |
} |
tun=&tuners[t->type]; |
div = freq + (int)(16*10.7); |
buffer[0] = (div>>8) & 0x7f; |
buffer[1] = div & 0xff; |
buffer[2] = tun->config; |
switch (t->type) { |
case TUNER_PHILIPS_FM1216ME_MK3: |
buffer[3] = 0x19; |
break; |
default: |
buffer[3] = 0xa4; |
break; |
} |
dprintk("tuner: radio 0x%02x 0x%02x 0x%02x 0x%02x\n", |
buffer[0],buffer[1],buffer[2],buffer[3]); |
if (4 != (rc = i2c_master_send(c,buffer,4))) |
printk("tuner: i2c i/o error: rc == %d (should be 4)\n",rc); |
} |
/* ---------------------------------------------------------------------- */ |
static int tuner_attach(struct i2c_adapter *adap, int addr, |
unsigned short flags, int kind) |
{ |
struct tuner *t; |
struct i2c_client *client; |
if (this_adap > 0) |
return -1; |
this_adap++; |
client_template.adapter = adap; |
client_template.addr = addr; |
printk("tuner: chip found @ 0x%x\n", addr<<1); |
if (NULL == (client = kmalloc(sizeof(struct i2c_client), GFP_KERNEL))) |
return -ENOMEM; |
memcpy(client,&client_template,sizeof(struct i2c_client)); |
client->data = t = kmalloc(sizeof(struct tuner),GFP_KERNEL); |
if (NULL == t) { |
kfree(client); |
return -ENOMEM; |
} |
memset(t,0,sizeof(struct tuner)); |
if (type >= 0 && type < TUNERS) { |
t->type = type; |
printk("tuner(bttv): type forced to %d (%s) [insmod]\n",t->type,tuners[t->type].name); |
strncpy(client->name, tuners[t->type].name, sizeof(client->name)); |
} else { |
t->type = -1; |
} |
i2c_attach_client(client); |
if (t->type == TUNER_MT2032) |
mt2032_init(client); |
MOD_INC_USE_COUNT; |
return 0; |
} |
static int tuner_probe(struct i2c_adapter *adap) |
{ |
int rc; |
if (0 != addr) { |
normal_i2c_range[0] = addr; |
normal_i2c_range[1] = addr; |
} |
this_adap = 0; |
switch (adap->id) { |
case I2C_ALGO_BIT | I2C_HW_B_BT848: |
case I2C_ALGO_BIT | I2C_HW_B_RIVA: |
case I2C_ALGO_SAA7134: |
case I2C_ALGO_SAA7146: |
printk("tuner: probing %s i2c adapter [id=0x%x]\n", |
adap->name,adap->id); |
rc = i2c_probe(adap, &addr_data, tuner_attach); |
break; |
default: |
printk("tuner: ignoring %s i2c adapter [id=0x%x]\n", |
adap->name,adap->id); |
rc = 0; |
/* nothing */ |
} |
return rc; |
} |
static int tuner_detach(struct i2c_client *client) |
{ |
struct tuner *t = (struct tuner*)client->data; |
i2c_detach_client(client); |
kfree(t); |
kfree(client); |
MOD_DEC_USE_COUNT; |
return 0; |
} |
static int |
tuner_command(struct i2c_client *client, unsigned int cmd, void *arg) |
{ |
struct tuner *t = (struct tuner*)client->data; |
int *iarg = (int*)arg; |
#if 0 |
__u16 *sarg = (__u16*)arg; |
#endif |
switch (cmd) { |
/* --- configuration --- */ |
case TUNER_SET_TYPE: |
if (t->type != -1) { |
printk("tuner: type already set (%d)\n",t->type); |
return 0; |
} |
if (*iarg < 0 || *iarg >= TUNERS) |
return 0; |
t->type = *iarg; |
printk("tuner: type set to %d (%s)\n", |
t->type,tuners[t->type].name); |
strncpy(client->name, tuners[t->type].name, sizeof(client->name)); |
if (t->type == TUNER_MT2032) |
mt2032_init(client); |
break; |
case AUDC_SET_RADIO: |
t->radio = 1; |
break; |
/* --- v4l ioctls --- */ |
/* take care: bttv does userspace copying, we'll get a |
kernel pointer here... */ |
case VIDIOCSCHAN: |
{ |
struct video_channel *vc = arg; |
t->radio = 0; |
t->mode = vc->norm; |
if (t->freq) |
set_tv_freq(client,t->freq); |
return 0; |
} |
case VIDIOCSFREQ: |
{ |
unsigned long *v = arg; |
if (t->radio) { |
dprintk("tuner: radio freq set to %d.%02d\n", |
(*iarg)/16,(*iarg)%16*100/16); |
set_radio_freq(client,*v); |
} else { |
dprintk("tuner: tv freq set to %d.%02d\n", |
(*iarg)/16,(*iarg)%16*100/16); |
set_tv_freq(client,*v); |
} |
t->freq = *v; |
return 0; |
} |
case VIDIOCGTUNER: |
{ |
struct video_tuner *vt = arg; |
if (t->radio) |
vt->signal = tuner_signal(client); |
return 0; |
} |
case VIDIOCGAUDIO: |
{ |
struct video_audio *va = arg; |
if (t->radio) |
va->mode = (tuner_stereo(client) ? VIDEO_SOUND_STEREO : VIDEO_SOUND_MONO); |
return 0; |
} |
#if 0 |
/* --- old, obsolete interface --- */ |
case TUNER_SET_TVFREQ: |
dprintk("tuner: tv freq set to %d.%02d\n", |
(*iarg)/16,(*iarg)%16*100/16); |
set_tv_freq(client,*iarg); |
t->radio = 0; |
t->freq = *iarg; |
break; |
case TUNER_SET_RADIOFREQ: |
dprintk("tuner: radio freq set to %d.%02d\n", |
(*iarg)/16,(*iarg)%16*100/16); |
set_radio_freq(client,*iarg); |
t->radio = 1; |
t->freq = *iarg; |
break; |
case TUNER_SET_MODE: |
if (t->type != TUNER_PHILIPS_SECAM) { |
dprintk("tuner: trying to change mode for other than TUNER_PHILIPS_SECAM\n"); |
} else { |
int mode=(*sarg==VIDEO_MODE_SECAM)?1:0; |
dprintk("tuner: mode set to %d\n", *sarg); |
t->mode = mode; |
set_tv_freq(client,t->freq); |
} |
break; |
#endif |
default: |
/* nothing */ |
break; |
} |
return 0; |
} |
/* ----------------------------------------------------------------------- */ |
static struct i2c_driver driver = { |
#if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,54) |
.owner = THIS_MODULE, |
#endif |
.name = "i2c TV tuner driver", |
.id = I2C_DRIVERID_TUNER, |
.flags = I2C_DF_NOTIFY, |
.attach_adapter = tuner_probe, |
.detach_client = tuner_detach, |
.command = tuner_command, |
}; |
static struct i2c_client client_template = |
{ |
.name = "(tuner unset)", |
.flags = I2C_CLIENT_ALLOW_USE, |
.driver = &driver, |
}; |
static int tuner_init_module(void) |
{ |
i2c_add_driver(&driver); |
return 0; |
} |
static void tuner_cleanup_module(void) |
{ |
i2c_del_driver(&driver); |
} |
module_init(tuner_init_module); |
module_exit(tuner_cleanup_module); |
/* |
* Overrides for Emacs so that we follow Linus's tabbing style. |
* --------------------------------------------------------------------------- |
* Local variables: |
* c-basic-offset: 8 |
* End: |
*/ |
/shark/trunk/drivers/makefile |
---|
1,5 → 1,5 |
dirs := $(filter-out CVS cvs makefile linuxc24, $(wildcard *)) |
dirs := $(filter-out CVS cvs bttv makefile linuxc24 linuxc26, $(wildcard *)) |
p_all := $(addprefix prefixall_, $(dirs)) |
p_install := $(addprefix prefixinstall_, $(dirs)) |
p_clean := $(addprefix prefixclean_, $(dirs)) |
/shark/trunk/drivers/i2c/makefile |
---|
10,7 → 10,7 |
OBJS_PATH = $(BASE)/drivers/i2c |
OBJS = i2c-core.o |
OBJS = i2c-core.o algos/i2c-algo-bit.o |
OTHERINCL += -I$(BASE)/drivers/linuxc26/include |